diff --git a/.cargo/config.toml b/.cargo/config.toml index 8bbf7cdb4d979dd1b619bfbe53fd873924918396..f113e9114acef51eaae6dd96666cc49781c8d41a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,40 +4,6 @@ rustdocflags = [ "-Arustdoc::redundant_explicit_links", # stylistic ] -# An auto defined `clippy` feature was introduced, -# but it was found to clash with user defined features, -# so was renamed to `cargo-clippy`. -# -# If you want standard clippy run: -# RUSTFLAGS= cargo clippy -[target.'cfg(feature = "cargo-clippy")'] -rustflags = [ - "-Aclippy::all", - "-Dclippy::correctness", - "-Aclippy::if-same-then-else", - "-Asuspicious_double_ref_op", - "-Dclippy::complexity", - "-Aclippy::zero-prefixed-literal", # 00_1000_000 - "-Aclippy::type_complexity", # raison d'etre - "-Aclippy::nonminimal-bool", # maybe - "-Aclippy::borrowed-box", # Reasonable to fix this one - "-Aclippy::too-many-arguments", # (Turning this on would lead to) - "-Aclippy::unnecessary_cast", # Types may change - "-Aclippy::identity-op", # One case where we do 0 + - "-Aclippy::useless_conversion", # Types may change - "-Aclippy::unit_arg", # styalistic. - "-Aclippy::option-map-unit-fn", # styalistic - "-Aclippy::bind_instead_of_map", # styalistic - "-Aclippy::erasing_op", # E.g. 0 * DOLLARS - "-Aclippy::eq_op", # In tests we test equality. - "-Aclippy::while_immutable_condition", # false positives - "-Aclippy::needless_option_as_deref", # false positives - "-Aclippy::derivable_impls", # false positives - "-Aclippy::stable_sort_primitive", # prefer stable sort - "-Aclippy::extra-unused-type-parameters", # stylistic - "-Aclippy::default_constructed_unit_structs", # stylistic -] - [env] # Needed for musl builds so user doesn't have to install musl-tools. CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } diff --git a/.config/taplo.toml b/.config/taplo.toml index ffe0417e42b149371c7c6ccffece5edf633b7295..f5d0b7021ba898ea3ab96323fa3fbc4efdd7b307 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -27,7 +27,7 @@ reorder_arrays = false # don't re-order order-dependent rustflags [[rule]] include = [".cargo/config.toml"] -keys = ["build", "target.'cfg(feature = \"cargo-clippy\")'"] +keys = ["build"] [rule.formatting] reorder_arrays = false diff --git a/.github/review-bot.yml b/.github/review-bot.yml index a5155949609e138aa6921a0de4ac5bca2bcc86ea..aa4ab8a69e02b409992581b34eda714b83e84ca0 100644 --- a/.github/review-bot.yml +++ b/.github/review-bot.yml @@ -1,5 +1,6 @@ rules: - name: CI files + countAuthor: true condition: include: - ^\.gitlab-ci\.yml @@ -8,24 +9,26 @@ rules: - ^\.gitlab/.* - ^\.config/nextest.toml - ^\.cargo/.* - exclude: + exclude: - ^\.gitlab/pipeline/zombienet.* - minApprovals: 2 - type: basic - teams: - - ci - - release-engineering + type: "or" + reviewers: + - minApprovals: 2 + teams: + - ci + - minApprovals: 2 + teams: + - core-devs - name: Audit rules type: basic condition: include: - - ^polkadot/runtime\/(kusama|polkadot|common)\/.* + - ^polkadot/runtime/common/.* - ^polkadot/primitives/src\/.+\.rs$ - ^substrate/primitives/.* - ^substrate/frame/.* exclude: - - ^polkadot/runtime\/(kusama|polkadot)\/src\/weights\/.+\.rs$ - ^substrate\/frame\/.+\.md$ minApprovals: 1 allowedToSkipRule: @@ -41,13 +44,8 @@ rules: - .* # excluding files from 'Runtime files' and 'CI files' rules exclude: - - ^polkadot/runtime/(kusama|polkadot)/src/[^/]+\.rs$ - - ^cumulus/parachains/runtimes/assets/(asset-hub-kusama|asset-hub-polkadot)/src/[^/]+\.rs$ - - ^cumulus/parachains/runtimes/bridge-hubs/(bridge-hub-kusama|bridge-hub-polkadot)/src/[^/]+\.rs$ - - ^cumulus/parachains/runtimes/collectives/collectives-polkadot/src/[^/]+\.rs$ - ^cumulus/parachains/common/src/[^/]+\.rs$ - ^substrate/frame/(?!.*(nfts/.*|uniques/.*|babe/.*|grandpa/.*|beefy|merkle-mountain-range/.*|contracts/.*|election|nomination-pools/.*|staking/.*|aura/.*)) - - ^polkadot/runtime/(kusama|polkadot)/src/[^/]+\.rs$ - ^\.gitlab-ci\.yml - ^docker/.* - ^\.github/.* @@ -59,24 +57,6 @@ rules: teams: - core-devs - # cumulus - - name: Runtime files cumulus - countAuthor: true - condition: - include: - - ^cumulus/parachains/runtimes/assets/(asset-hub-kusama|asset-hub-polkadot)/src/[^/]+\.rs$ - - ^cumulus/parachains/runtimes/bridge-hubs/(bridge-hub-kusama|bridge-hub-polkadot)/src/[^/]+\.rs$ - - ^cumulus/parachains/runtimes/collectives/collectives-polkadot/src/[^/]+\.rs$ - - ^cumulus/parachains/common/src/[^/]+\.rs$ - type: and-distinct - reviewers: - - minApprovals: 1 - teams: - - locks-review - - minApprovals: 1 - teams: - - polkadot-review - # if there are any changes in the bridges subtree (in case of backport changes back to bridges repo) - name: Bridges subtree files type: basic @@ -88,7 +68,6 @@ rules: - bridges-core # substrate - - name: FRAME coders substrate condition: include: @@ -104,6 +83,7 @@ rules: # Protection of THIS file - name: Review Bot + countAuthor: true condition: include: - review-bot\.yml @@ -115,9 +95,6 @@ rules: - minApprovals: 1 teams: - locks-review - - minApprovals: 1 - teams: - - ci preventReviewRequests: teams: diff --git a/.github/runtime_specs/rococo.json b/.github/runtime_specs/rococo.json new file mode 100644 index 0000000000000000000000000000000000000000..6568b06400c8dab64a397b0b1fbd6d6fb72c2f7a --- /dev/null +++ b/.github/runtime_specs/rococo.json @@ -0,0 +1,17 @@ +{ + "pallets": { + "1": { + "constants": { + "EpochDuration": { + "value": [ 88, 2, 0, 0, 0, 0, 0, 0 ]} + } + }, + + "2": { + "constants": { + "MinimumPeriod": { + "value": [ 184, 11, 0, 0, 0, 0, 0, 0 ]} + } + } + } + } diff --git a/.github/runtime_specs/westend.json b/.github/runtime_specs/westend.json new file mode 100644 index 0000000000000000000000000000000000000000..6568b06400c8dab64a397b0b1fbd6d6fb72c2f7a --- /dev/null +++ b/.github/runtime_specs/westend.json @@ -0,0 +1,17 @@ +{ + "pallets": { + "1": { + "constants": { + "EpochDuration": { + "value": [ 88, 2, 0, 0, 0, 0, 0, 0 ]} + } + }, + + "2": { + "constants": { + "MinimumPeriod": { + "value": [ 184, 11, 0, 0, 0, 0, 0, 0 ]} + } + } + } + } diff --git a/.github/scripts/check-runtime.py b/.github/scripts/check-runtime.py new file mode 100755 index 0000000000000000000000000000000000000000..9f3d047e01f8619364e20f6c83296e6b1a196f06 --- /dev/null +++ b/.github/scripts/check-runtime.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +import json +import sys +import logging +import os + + +def check_constant(spec_pallet_id, spec_pallet_value, meta_constant): + """ + Check a single constant + + :param spec_pallet_id: + :param spec_pallet_value: + :param meta_constant: + :return: + """ + if meta_constant['name'] == list(spec_pallet_value.keys())[0]: + constant = meta_constant['name'] + res = list(spec_pallet_value.values())[0]["value"] == meta_constant["value"] + + logging.debug(f" Checking pallet:{spec_pallet_id}/constants/{constant}") + logging.debug(f" spec_pallet_value: {spec_pallet_value}") + logging.debug(f" meta_constant: {meta_constant}") + logging.info(f"pallet:{spec_pallet_id}/constants/{constant} -> {res}") + return res + else: + # logging.warning(f" Skipping pallet:{spec_pallet_id}/constants/{meta_constant['name']}") + pass + + +def check_pallet(metadata, spec_pallet): + """ + Check one pallet + + :param metadata: + :param spec_pallet_id: + :param spec_pallet_value: + :return: + """ + + spec_pallet_id, spec_pallet_value = spec_pallet + logging.debug(f"Pallet: {spec_pallet_id}") + + metadata_pallets = metadata["pallets"] + metadata_pallet = metadata_pallets[spec_pallet_id] + + res = map(lambda meta_constant_value: check_constant( + spec_pallet_id, spec_pallet_value["constants"], meta_constant_value), + metadata_pallet["constants"].values()) + res = list(filter(lambda item: item is not None, res)) + return all(res) + + +def check_pallets(metadata, specs): + """ + CHeck all pallets + + :param metadata: + :param specs: + :return: + """ + + res = list(map(lambda spec_pallet: check_pallet(metadata, spec_pallet), + specs['pallets'].items())) + res = list(filter(lambda item: item is not None, res)) + return all(res) + + +def check_metadata(metadata, specs): + """ + Check metadata (json) against a list of expectations + + :param metadata: Metadata in JSON format + :param expectation: Expectations + :return: Bool + """ + + res = check_pallets(metadata, specs) + return res + + +def help(): + """ Show some simple help """ + + print(f"You must pass 2 args, you passed {len(sys.argv) - 1}") + print("Sample call:") + print("check-runtime.py ") + + +def load_json(file): + """ Load json from a file """ + + f = open(file) + return json.load(f) + + +def main(): + LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() + logging.basicConfig(level=LOGLEVEL) + + if len(sys.argv) != 3: + help() + exit(1) + + metadata_file = sys.argv[1] + specs_file = sys.argv[2] + print(f"Checking metadata from: {metadata_file} with specs from: {specs_file}") + + metadata = load_json(metadata_file) + specs = load_json(specs_file) + + res = check_metadata(metadata, specs) + + if res: + logging.info(f"OK") + exit(0) + else: + print("") + logging.info(f"Some errors were found, run again with LOGLEVEL=debug") + exit(1) + +if __name__ == "__main__": + main() diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index b0f9cb32063a43cda58d2e6e8f25e0ff66a134eb..bd12d9c6e6ff773f8513189a381d725243e53eb5 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -202,21 +202,26 @@ fetch_release_artifacts() { echo "Release ID : $RELEASE_ID" echo "Repo : $REPO" echo "Binary : $BINARY" + OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} + echo "OUTPUT_DIR : $OUTPUT_DIR" + echo "Fetching release info..." curl -L -s \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer ${GITHUB_TOKEN}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/repos/${REPO}/releases/${RELEASE_ID} > release.json - # Get Asset ids + echo "Extract asset ids..." ids=($(jq -r '.assets[].id' < release.json )) + echo "Extract asset count..." count=$(jq '.assets|length' < release.json ) # Fetch artifacts - mkdir -p "./release-artifacts/${BINARY}" - pushd "./release-artifacts/${BINARY}" > /dev/null + mkdir -p "$OUTPUT_DIR" + pushd "$OUTPUT_DIR" > /dev/null + echo "Fetching assets..." iter=1 for id in "${ids[@]}" do @@ -264,3 +269,78 @@ function check_gpg() { echo "Checking GPG Signature for $1" gpg --no-tty --verify -q $1.asc $1 } + +# GITHUB_REF will typically be like: +# - refs/heads/release-v1.2.3 +# - refs/heads/release-polkadot-v1.2.3-rc2 +# This function extracts the version +function get_version_from_ghref() { + GITHUB_REF=$1 + stripped=${GITHUB_REF#refs/heads/release-} + re="v([0-9]+\.[0-9]+\.[0-9]+)" + if [[ $stripped =~ $re ]]; then + echo ${BASH_REMATCH[0]}; + return 0 + else + return 1 + fi +} + +# Get latest rc tag based on the release version and product +function get_latest_rc_tag() { + version=$1 + product=$2 + + if [[ "$product" == "polkadot" ]]; then + last_rc=$(git tag -l "$version-rc*" | sort -V | tail -n 1) + elif [[ "$product" == "polkadot-parachain" ]]; then + last_rc=$(git tag -l "polkadot-parachains-$version-rc*" | sort -V | tail -n 1) + fi + echo "${last_rc}" +} + +# Increment rc tag number based on the value of a suffix of the current rc tag +function increment_rc_tag() { + last_rc=$1 + + suffix=$(echo "$last_rc" | grep -Eo '[0-9]+$') + ((suffix++)) + echo $suffix +} + +function relative_parent() { + echo "$1" | sed -E 's/(.*)\/(.*)\/\.\./\1/g' +} + +# Find all the runtimes, it returns the result as JSON object, compatible to be +# used as Github Workflow Matrix. This call is exposed by the `scan` command and can be used as: +# podman run --rm -it -v /.../fellowship-runtimes:/build docker.io/chevdor/srtool:1.70.0-0.11.1 scan +function find_runtimes() { + libs=($(git grep -I -r --cached --max-depth 20 --files-with-matches 'construct_runtime!' -- '*lib.rs')) + re=".*-runtime$" + JSON=$(jq --null-input '{ "include": [] }') + + # EXCLUDED_RUNTIMES is a space separated list of runtime names (without the -runtime postfix) + # EXCLUDED_RUNTIMES=${EXCLUDED_RUNTIMES:-"substrate-test"} + IFS=' ' read -r -a exclusions <<< "$EXCLUDED_RUNTIMES" + + for lib in "${libs[@]}"; do + crate_dir=$(dirname "$lib") + cargo_toml="$crate_dir/../Cargo.toml" + + name=$(toml get -r $cargo_toml 'package.name') + chain=${name//-runtime/} + + if [[ "$name" =~ $re ]] && ! [[ ${exclusions[@]} =~ $chain ]]; then + lib_dir=$(dirname "$lib") + runtime_dir=$(relative_parent "$lib_dir/..") + ITEM=$(jq --null-input \ + --arg chain "$chain" \ + --arg name "$name" \ + --arg runtime_dir "$runtime_dir" \ + '{ "chain": $chain, "crate": $name, "runtime_dir": $runtime_dir }') + JSON=$(echo $JSON | jq ".include += [$ITEM]") + fi + done + echo $JSON +} diff --git a/.github/workflows/build-and-attach-release-runtimes.yml b/.github/workflows/build-and-attach-release-runtimes.yml index 8e0a5ba04b44eaefbb2d7bc828fe209e51d6e8d8..680a9ecffd312dba61c2eaee3a3e2e6a9d5b136c 100644 --- a/.github/workflows/build-and-attach-release-runtimes.yml +++ b/.github/workflows/build-and-attach-release-runtimes.yml @@ -23,7 +23,7 @@ jobs: - { name: glutton-westend, package: glutton-westend-runtime, path: cumulus/parachains/runtimes/glutton/glutton-westend } build_config: # Release build has logging disabled and no dev features - - { type: on-chain-release, opts: --features on-chain-release-build } + - { type: on-chain-release, opts: --features on-chain-release-build } # Debug build has logging enabled and developer features - { type: dev-debug-build, opts: --features try-runtime } @@ -31,11 +31,11 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build ${{ matrix.runtime.name }} ${{ matrix.build_config.type }} id: srtool_build - uses: chevdor/srtool-actions@v0.9.0 + uses: chevdor/srtool-actions@v0.9.2 env: BUILD_OPTS: ${{ matrix.build_config.opts }} with: diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml index a66e3a539984ac7e96728d9812b5cdb9b63e26d7..e1e92d288ceae235d23fa36c31d592092fe8b0ba 100644 --- a/.github/workflows/check-licenses.yml +++ b/.github/workflows/check-licenses.yml @@ -9,14 +9,14 @@ permissions: jobs: check-licenses: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest env: LICENSES: "'Apache-2.0' 'GPL-3.0-only' 'GPL-3.0-or-later WITH Classpath-exception-2.0'" NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" diff --git a/.github/workflows/check-markdown.yml b/.github/workflows/check-markdown.yml index 2108f94209003c55c0965eeedb09a1336e340242..2b8a66db35b3adacea4f131a881103d48e3704ae 100644 --- a/.github/workflows/check-markdown.yml +++ b/.github/workflows/check-markdown.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version: "18.x" registry-url: "https://npm.pkg.github.com" @@ -31,4 +31,5 @@ jobs: env: CONFIG: .github/.markdownlint.yaml run: | + echo "Checking markdown formatting. More info: docs/contributor/markdown_linting.md" markdownlint --config "$CONFIG" --ignore target . diff --git a/.github/workflows/check-publish.yml b/.github/workflows/check-publish.yml index db0863888b8308663f045943c3e4dc2fc3d7985a..1941bd9816757210b0d9f238346acb71c54b9a48 100644 --- a/.github/workflows/check-publish.yml +++ b/.github/workflows/check-publish.yml @@ -12,7 +12,7 @@ jobs: check-publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Rust Cache uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 diff --git a/.github/workflows/check-runtimes.yml b/.github/workflows/check-runtimes.yml new file mode 100644 index 0000000000000000000000000000000000000000..0e5ad104766a89aaa678cc5436475d95e3ab76fd --- /dev/null +++ b/.github/workflows/check-runtimes.yml @@ -0,0 +1,94 @@ +name: Check Runtimes Specs +# This GH Workflow fetches the runtimes available in a release. +# It then compares their metadata with reference specs located under +# .github/runtime_specs. + +on: + workflow_dispatch: + inputs: + release_id: + description: | + Release ID. + You can find it using the command: + curl -s \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/repos/paritytech/polkadot-sdk/releases | \ + jq '.[] | { name: .name, id: .id }' + required: true + type: string + + # This trigger unfortunately does not work as expected. + # https://github.com/orgs/community/discussions/47794 + # release: + # types: [edited] + +env: + RUNTIME_SPECS_DIR: .github/runtime_specs + DATA_DIR: runtimes + RELEASE_ID: ${{ inputs.release_id }} + REPO: ${{ github.repository }} + +jobs: + find-specs: + name: Fetch runtime specs + outputs: + specs: ${{ steps.get-list.outputs.specs }} + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Get list + id: get-list + run: | + lst=$(ls $RUNTIME_SPECS_DIR/*.json | xargs -I{} basename "{}" .json | jq -R .| jq -sc .) + echo "Found: $lst" + echo "specs=$lst" >> $GITHUB_OUTPUT + + check-runtimes: + name: Check runtime specs + runs-on: ubuntu-latest + needs: + - find-specs + + strategy: + matrix: + specs: ${{ fromJSON(needs.find-specs.outputs.specs) }} + + steps: + - name: Checkout the repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Fetch release artifacts based on release id + env: + OUTPUT_DIR: ${{ env.DATA_DIR }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + . ./.github/scripts/common/lib.sh + fetch_release_artifacts + + - name: Install tooling + env: + SUBWASM_VERSION: v0.20.0 + DL_BASE_URL: https://github.com/chevdor/subwasm/releases/download + run: | + wget $DL_BASE_URL/$SUBWASM_VERSION/subwasm_linux_amd64_$SUBWASM_VERSION.deb \ + -O subwasm.deb + sudo dpkg -i subwasm.deb + subwasm --version + + - name: Extract metadata JSON for ${{ matrix.specs }} + env: + RUNTIME: ${{ matrix.specs }} + run: | + WASM=$(ls ${DATA_DIR}/${RUNTIME}*.wasm) + echo "WASM=$WASM" + subwasm show --json "$WASM" > "${DATA_DIR}/${RUNTIME}.json" + + - name: Check specs for ${{ matrix.specs }} + id: build + env: + RUNTIME: ${{ matrix.specs }} + LOGLEVEL: info + run: | + python --version + .github/scripts/check-runtime.py "${DATA_DIR}/${RUNTIME}.json" "${RUNTIME_SPECS_DIR}/${RUNTIME}.json" diff --git a/.github/workflows/claim-crates.yml b/.github/workflows/claim-crates.yml index 0bd5593b54f2f89aac4f6ce281a7f532bc8ca785..9e272266201837fae0ab875186adc89a286e2599 100644 --- a/.github/workflows/claim-crates.yml +++ b/.github/workflows/claim-crates.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest environment: master steps: - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Rust Cache uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 diff --git a/.github/workflows/fmt-check.yml b/.github/workflows/fmt-check.yml index e4d39acabfd776b67ac37df28b0905b0517967ef..99ac5120097d1d888b0c9207621433cc93a950c2 100644 --- a/.github/workflows/fmt-check.yml +++ b/.github/workflows/fmt-check.yml @@ -15,7 +15,7 @@ jobs: os: ["ubuntu-latest"] runs-on: ${{ matrix.os }} container: - image: paritytech/ci-unified:bullseye-1.73.0-2023-11-01-v20231025 + image: paritytech/ci-unified:bullseye-1.74.0-2023-11-01-v20231204 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/notif-burnin-label.yml b/.github/workflows/notif-burnin-label.yml new file mode 100644 index 0000000000000000000000000000000000000000..b630cd07440f925e603c920f337d75c48d871b05 --- /dev/null +++ b/.github/workflows/notif-burnin-label.yml @@ -0,0 +1,24 @@ +name: Notify DevOps when burn-in label applied +on: + pull_request: + types: [labeled] + +jobs: + notify-devops: + runs-on: ubuntu-latest + strategy: + matrix: + channel: + - name: 'Team: DevOps' + room: '!lUslSijLMgNcEKcAiE:parity.io' + + steps: + - name: Send Matrix message to ${{ matrix.channel.name }} + if: startsWith(github.event.label.name, 'A0-') + uses: s3krit/matrix-message-action@70ad3fb812ee0e45ff8999d6af11cafad11a6ecf # v0.0.3 + with: + room_id: ${{ matrix.channel.room }} + access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} + server: m.parity.io + message: | + @room Burn-in request received for [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }}) diff --git a/.github/workflows/release-10_rc-automation.yml b/.github/workflows/release-10_rc-automation.yml new file mode 100644 index 0000000000000000000000000000000000000000..7231a8b75886d04ce18bb89fcef99029e3ab14c6 --- /dev/null +++ b/.github/workflows/release-10_rc-automation.yml @@ -0,0 +1,113 @@ +name: Release - RC automation +on: + push: + branches: + # Catches release-polkadot-v1.2.3, release-v1.2.3-rc1, etc + - release-v[0-9]+.[0-9]+.[0-9]+* + - release-cumulus-v[0-9]+* + - release-polkadot-v[0-9]+* + + workflow_dispatch: + +jobs: + tag_rc: + runs-on: ubuntu-latest + strategy: + matrix: + channel: + - name: "RelEng: Polkadot Release Coordination" + room: '!cqAmzdIcbOFwrdrubV:parity.io' + environment: release + + steps: + - name: Checkout sources + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + fetch-depth: 0 + + - name: Get release product + id: get_rel_product + shell: bash + run: | + current_branch=$(git branch --show-current) + echo "Current branch: $current_branch" + if [[ "$current_branch" =~ "release-polkadot" ]]; then + echo "product=polkadot" >> $GITHUB_OUTPUT + elif [[ "$current_branch" =~ "release-cumulus" ]]; then + echo "product=polkadot-parachain" >> $GITHUB_OUTPUT + fi + + + - name: Compute next rc tag for polkadot + if: ${{ steps.get_rel_product.outputs.product == 'polkadot' }} + id: compute_tag_polkadot + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + # Get last rc tag if exists, else set it to {version}-rc1 + version=$(get_version_from_ghref ${GITHUB_REF}) + echo "$version" + echo "version=$version" >> $GITHUB_OUTPUT + + last_rc=$(get_latest_rc_tag $version polkadot) + + if [ -n "$last_rc" ]; then + suffix=$(increment_rc_tag $last_rc) + echo "new_tag=$version-rc$suffix" >> $GITHUB_OUTPUT + echo "first_rc=false" >> $GITHUB_OUTPUT + else + echo "new_tag=$version-rc1" >> $GITHUB_OUTPUT + echo "first_rc=true" >> $GITHUB_OUTPUT + fi + + - name: Compute next rc tag for polkadot-parachain + if: ${{ steps.get_rel_product.outputs.product == 'polkadot-parachain' }} + id: compute_tag_cumulus + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + # Get last rc tag if exists, else set it to polkadot-parachains-{version}-rc1 + version=$(get_version_from_ghref ${GITHUB_REF}) + echo "$version" + echo "version=$version" >> $GITHUB_OUTPUT + + last_rc=$(get_latest_rc_tag $version polkadot-parachain) + if [ -n "$last_rc" ]; then + suffix=$(increment_rc_tag $last_rc) + echo "new_tag=polkadot-parachains-$version-rc$suffix" >> $GITHUB_OUTPUT + echo "first_rc=false" >> $GITHUB_OUTPUT + else + echo "new_tag=polkadot-parachain-$version-rc1" >> $GITHUB_OUTPUT + echo "first_rc=true" >> $GITHUB_OUTPUT + fi + + - name: Apply new tag + uses: tvdias/github-tagger@ed7350546e3e503b5e942dffd65bc8751a95e49d # v0.0.2 + with: + # We can't use the normal GITHUB_TOKEN for the following reason: + # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#triggering-new-workflows-using-a-personal-access-token + # RELEASE_BRANCH_TOKEN requires public_repo OAuth scope + repo-token: "${{ secrets.RELEASE_BRANCH_TOKEN }}" + tag: ${{ steps.compute_tag_polkadot.outputs.new_tag || steps.compute_tag_cumulus.outputs.new_tag }} + + # - id: create-issue + # uses: JasonEtco/create-an-issue@e27dddc79c92bc6e4562f268fffa5ed752639abd # v2.9.1 + # # Only create the issue if it's the first release candidate + # if: steps.compute_tag.outputs.first_rc == 'true' + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # VERSION: ${{ steps.compute_tag.outputs.version }} + # with: + # filename: .github/ISSUE_TEMPLATE/release.md + + - name: Send Matrix message to ${{ matrix.channel.name }} + uses: s3krit/matrix-message-action@70ad3fb812ee0e45ff8999d6af11cafad11a6ecf # v0.0.3 + # if: steps.create-issue.outputs.url != '' + with: + room_id: ${{ matrix.channel.room }} + access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} + server: m.parity.io + message: | + Release process for polkadot ${{ steps.compute_tag_polkadot.outputs.new_tag || steps.compute_tag_cumulus.outputs.new_tag }} has been started.
diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index 567e996b8fd94a61f92e457435a05c57469179f2..f74fb6a0ad1f9e2acad44e1802e2efd8edc61df1 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -220,6 +220,7 @@ jobs: runs-on: ubuntu-latest outputs: polkadot_apt_version: ${{ steps.fetch-latest-apt.outputs.polkadot_apt_version }} + polkadot_container_tag: ${{ steps.fetch-latest-apt.outputs.polkadot_container_tag }} container: image: paritytech/parity-keyring options: --user root @@ -230,7 +231,9 @@ jobs: apt update apt show polkadot version=$(apt show polkadot 2>/dev/null | grep "Version:" | awk '{print $2}') + tag=$(echo $version | sed 's/-.*//') echo "polkadot_apt_version=v$version" >> $GITHUB_OUTPUT + echo "polkadot_container_tag=v$tag" >> $GITHUB_OUTPUT echo "You passed ${{ inputs.version }} but this is ignored" echo "We use the version from the Debian Package: $version" @@ -276,7 +279,7 @@ jobs: # TODO: It would be good to get rid of this GHA that we don't really need. tags: | parity/polkadot:latest - parity/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_apt_version }} + parity/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} build-args: | VCS_REF=${{ github.ref }} POLKADOT_VERSION=${{ needs.fetch-latest-debian-package-version.outputs.polkadot_apt_version }} diff --git a/.github/workflows/release-99_notif-published.yml b/.github/workflows/release-99_notif-published.yml new file mode 100644 index 0000000000000000000000000000000000000000..b35120ca4e128beaa37047b0ac3f21b02f4da663 --- /dev/null +++ b/.github/workflows/release-99_notif-published.yml @@ -0,0 +1,61 @@ +name: Release - Announce release to Matrix rooms +on: + release: + types: + - published + - prereleased + +jobs: + ping_matrix: + runs-on: ubuntu-latest + 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 + - name: 'General: Rust, Polkadot, Substrate' + room: '!aJymqQYtCjjqImFLSb:parity.io' + pre-release: false + - name: 'Team: DevOps' + room: '!lUslSijLMgNcEKcAiE:parity.io' + pre-release: true + + # External + - name: 'Ledger <> Polkadot Coordination' + room: '!EoIhaKfGPmFOBrNSHT:web3.foundation' + 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 + + steps: + - name: Matrix notification to ${{ matrix.channel.name }} + if: github.event.release.prerelease == false || matrix.channel.pre-release + uses: s3krit/matrix-message-action@70ad3fb812ee0e45ff8999d6af11cafad11a6ecf # v0.0.3 + with: + room_id: ${{ matrix.channel.room }} + access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} + server: m.parity.io + message: | + A (pre)release has been ${{github.event.action}} in **${{github.event.repository.full_name}}:**
+ Release version: [${{github.event.release.tag_name}}](${{github.event.release.html_url}}) + + ----- + + ${{github.event.release.body}} diff --git a/.github/workflows/srtool.yml b/.github/workflows/srtool.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb15538f559d2145700a73fb0e383d4103ce582a --- /dev/null +++ b/.github/workflows/srtool.yml @@ -0,0 +1,135 @@ +name: Srtool build + +env: + SUBWASM_VERSION: 0.20.0 + TOML_CLI_VERSION: 0.2.4 + +on: + push: + tags: + - "*" + branches: + - release-v[0-9]+.[0-9]+.[0-9]+* + - release-cumulus-v[0-9]+* + - release-polkadot-v[0-9]+* + + schedule: + - cron: "00 02 * * 1" # 2AM weekly on monday + + workflow_dispatch: + +jobs: + find-runtimes: + name: Scan repo paritytech/polkadot-sdk + outputs: + runtime: ${{ steps.get_runtimes_list.outputs.runtime }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + with: + fetch-depth: 0 + + - name: Install tooling + run: | + URL=https://github.com/chevdor/toml-cli/releases/download/v${{ env.TOML_CLI_VERSION }}/toml_linux_amd64_v${{ env.TOML_CLI_VERSION }}.deb + curl -L $URL --output toml.deb + sudo dpkg -i toml.deb + toml --version; jq --version + + - name: Scan runtimes + env: + EXCLUDED_RUNTIMES: "substrate-test" + run: | + . ./.github/scripts/common/lib.sh + + echo "Github workspace: ${{ github.workspace }}" + echo "Current folder: $(pwd)"; ls -al + ls -al + + MATRIX=$(find_runtimes | tee runtimes_list.json) + echo $MATRIX + + - name: Get runtimes list + id: get_runtimes_list + run: | + ls -al + MATRIX=$(cat runtimes_list.json) + echo $MATRIX + echo "runtime=$MATRIX" >> $GITHUB_OUTPUT + + srtool: + runs-on: ubuntu-latest + needs: + - find-runtimes + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.find-runtimes.outputs.runtime) }} + + steps: + - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + with: + fetch-depth: 0 + + - name: Srtool build + id: srtool_build + uses: chevdor/srtool-actions@v0.9.2 + with: + chain: ${{ matrix.chain }} + runtime_dir: ${{ matrix.runtime_dir }} + + - name: Summary + run: | + echo '${{ steps.srtool_build.outputs.json }}' | jq > ${{ matrix.chain }}-srtool-digest.json + cat ${{ matrix.chain }}-srtool-digest.json + echo "Compact Runtime: ${{ steps.srtool_build.outputs.wasm }}" + echo "Compressed Runtime: ${{ steps.srtool_build.outputs.wasm_compressed }}" + + # it takes a while to build the runtime, so let's save the artifact as soon as we have it + - name: Archive Artifacts for ${{ matrix.chain }} + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: ${{ matrix.chain }}-runtime + path: | + ${{ steps.srtool_build.outputs.wasm }} + ${{ steps.srtool_build.outputs.wasm_compressed }} + ${{ matrix.chain }}-srtool-digest.json + + # We now get extra information thanks to subwasm + - name: Install subwasm + run: | + wget https://github.com/chevdor/subwasm/releases/download/v${{ env.SUBWASM_VERSION }}/subwasm_linux_amd64_v${{ env.SUBWASM_VERSION }}.deb + sudo dpkg -i subwasm_linux_amd64_v${{ env.SUBWASM_VERSION }}.deb + subwasm --version + + - name: Show Runtime information + shell: bash + run: | + subwasm info ${{ steps.srtool_build.outputs.wasm }} + subwasm info ${{ steps.srtool_build.outputs.wasm_compressed }} + subwasm --json info ${{ steps.srtool_build.outputs.wasm }} > ${{ matrix.chain }}-info.json + subwasm --json info ${{ steps.srtool_build.outputs.wasm_compressed }} > ${{ matrix.chain }}-compressed-info.json + + - name: Extract the metadata + shell: bash + run: | + subwasm meta ${{ steps.srtool_build.outputs.wasm }} + subwasm --json meta ${{ steps.srtool_build.outputs.wasm }} > ${{ matrix.chain }}-metadata.json + + - name: Check the metadata diff + shell: bash + # the following subwasm call will error for chains that are not known and/or live, that includes shell for instance + run: | + subwasm diff ${{ steps.srtool_build.outputs.wasm }} --chain-b ${{ matrix.chain }} || \ + echo "Subwasm call failed, check the logs. This is likely because ${{ matrix.chain }} is not known by subwasm" | \ + tee ${{ matrix.chain }}-diff.txt + + - name: Archive Subwasm results + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + with: + name: ${{ matrix.chain }}-runtime + path: | + ${{ matrix.chain }}-info.json + ${{ matrix.chain }}-compressed-info.json + ${{ matrix.chain }}-metadata.json + ${{ matrix.chain }}-diff.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8a91568ccfc2329a2397c2835cd883707735f5e..dc4b3cf162e177a2ce15e1cf943c788237db2f81 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -116,7 +116,7 @@ default: tags: - linux-docker -# +# .forklift-cache: 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"' @@ -124,19 +124,19 @@ default: - mkdir .forklift - cp $FL_FORKLIFT_CONFIG .forklift/config.toml - export FORKLIFT_PACKAGE_SUFFIX=${CI_JOB_NAME/ [0-9 \/]*} - - shopt -s expand_aliases + - shopt -s expand_aliases - export PATH=$PATH:$(pwd) - | if [ "$FORKLIFT_BYPASS" != "true" ]; then echo "FORKLIFT_BYPASS not set, creating alias cargo='forklift cargo'" alias cargo="forklift cargo" fi - - ls -al - - rm -f forklift.sock + - ls -al + - rm -f forklift.sock - forklift clean # - echo "FL_FORKLIFT_VERSION ${FL_FORKLIFT_VERSION}" - - echo "FORKLIFT_PACKAGE_SUFFIX $FORKLIFT_PACKAGE_SUFFIX" + - echo "FORKLIFT_PACKAGE_SUFFIX $FORKLIFT_PACKAGE_SUFFIX" .common-refs: rules: diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 377236193cc5169651d1fea343aaeb61a45e4b08..20aa4a5c2a2835cdb859a0768be055064594b6b3 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -220,6 +220,7 @@ build-test-parachain: # DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-bridge-hubs # DAG: build-runtime-assets -> build-runtime-collectives -> build-runtime-contracts +# DAG: build-runtime-assets -> build-runtime-coretime # DAG: build-runtime-assets -> build-runtime-starters -> build-runtime-testing build-runtime-assets: <<: *build-runtime-template @@ -235,6 +236,15 @@ build-runtime-collectives: - job: build-runtime-assets artifacts: false +build-runtime-coretime: + <<: *build-runtime-template + variables: + RUNTIME_PATH: "cumulus/parachains/runtimes/coretime" + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: build-runtime-assets + artifacts: false + build-runtime-bridge-hubs: <<: *build-runtime-template variables: diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index f95bd224fb483ac7ac94c419998d4dab4beb161e..1ed12e68c2ce19b67dd5aca03cec85702351c039 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -4,8 +4,11 @@ cargo-clippy: - .docker-env - .common-refs - .pipeline-stopper-artifacts + variables: + RUSTFLAGS: "-D warnings" script: - - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo clippy --all-targets --locked --workspace + - SKIP_WASM_BUILD=1 cargo clippy --all-targets --locked --workspace + - SKIP_WASM_BUILD=1 cargo clippy --all-targets --all-features --locked --workspace check-try-runtime: stage: check @@ -65,11 +68,7 @@ test-rust-features: - .kubernetes-env - .test-refs-no-trigger-prs-only script: - - git clone - --depth=1 - --branch="master" - https://github.com/paritytech/pipeline-scripts - - bash ./pipeline-scripts/rust-features.sh . + - bash .gitlab/rust-features.sh . job-starter: stage: check @@ -225,6 +224,19 @@ check-runtime-migration-collectives-westend: URI: "wss://westend-collectives-rpc.polkadot.io:443" COMMAND_EXTRA_ARGS: "--disable-spec-name-check" +# Check runtime migrations for Parity managed coretime chain +check-runtime-migration-coretime-rococo: + stage: check + extends: + - .docker-env + - .test-pr-refs + - .check-runtime-migration + variables: + NETWORK: "coretime-rococo" + PACKAGE: "coretime-rococo-runtime" + WASM: "coretime_rococo_runtime.compact.compressed.wasm" + URI: "wss://rococo-coretime-rpc.polkadot.io:443" + find-fail-ci-phrase: stage: check variables: diff --git a/.gitlab/pipeline/short-benchmarks.yml b/.gitlab/pipeline/short-benchmarks.yml index 97bce4799270c8701a27a048b7920095bdb48654..e9dbe20088116721470e57a02b9b3d1353634c06 100644 --- a/.gitlab/pipeline/short-benchmarks.yml +++ b/.gitlab/pipeline/short-benchmarks.yml @@ -74,6 +74,16 @@ short-benchmark-collectives-westend: variables: RUNTIME_CHAIN: collectives-westend-dev +short-benchmark-coretime-rococo: + <<: *short-bench-cumulus + variables: + RUNTIME_CHAIN: coretime-rococo-dev + +short-benchmark-coretime-westend: + <<: *short-bench-cumulus + variables: + RUNTIME_CHAIN: coretime-westend-dev + short-benchmark-glutton-westend: <<: *short-bench-cumulus variables: diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index f6dad887a68dd50deb036e20d1e8a96e6c80de51..00d2b22c810720b79ff493f2eb36e232de12a63e 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -29,7 +29,7 @@ test-linux-stable: --locked \ --release \ --no-fail-fast \ - --features try-runtime,experimental,ci-only-tests \ + --features try-runtime,experimental,riscv,ci-only-tests \ --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} # Upload tests results to Elasticsearch - echo "Upload test results to Elasticsearch" @@ -238,6 +238,8 @@ test-deterministic-wasm: cargo-check-benches: stage: test + artifacts: + expire_in: 10 days variables: CI_JOB_NAME: "cargo-check-benches" extends: @@ -305,6 +307,10 @@ node-bench-regression-guard: CI_IMAGE: "paritytech/node-bench-regression-guard:latest" before_script: [""] script: + - if [ $(ls -la artifacts/benches/ | grep master | wc -l) == 0 ]; then + echo "Couldn't find master artifacts"; + exit 1; + fi - echo "------- IMPORTANT -------" - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" - echo "In case of this job failure, check your pipeline's cargo-check-benches" @@ -438,6 +444,7 @@ cargo-check-each-crate: - .run-immediately # - .collect-artifacts variables: + RUSTFLAGS: "-D warnings" # $CI_JOB_NAME is set manually so that cache could be shared for all jobs # "cargo-check-each-crate I/N" jobs CI_JOB_NAME: cargo-check-each-crate @@ -519,6 +526,6 @@ test-syscalls: - ./list-syscalls.rb ../../../target/x86_64-unknown-linux-musl/production/polkadot-prepare-worker --only-used-syscalls | diff -u prepare-worker-syscalls - after_script: - if [[ "$CI_JOB_STATUS" == "failed" ]]; then - printf "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed.\n"; + printf "The x86_64 syscalls used by the worker binaries have changed. Please review if this is expected and update polkadot/scripts/list-syscalls/*-worker-syscalls as needed.\n"; fi allow_failure: false # this rarely triggers in practice diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index d1f3a201c80a0c6edc19870b727de452149d6f79..6b89648c4e36ee8b804ab5771e7375d36f089bf3 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -131,6 +131,14 @@ zombienet-polkadot-functional-0008-dispute-old-finalized: --local-dir="${LOCAL_DIR}/functional" --test="0008-dispute-old-finalized.zndsl" +zombienet-polkadot-functional-0009-approval-voting-coalescing: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0009-approval-voting-coalescing.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common @@ -201,7 +209,7 @@ zombienet-polkadot-misc-0002-upgrade-node: # Exit if the job is not merge queue # - if [[ $CI_COMMIT_REF_NAME != *"gh-readonly-queue"* ]]; then echo "I will run only in a merge queue"; exit 0; fi - export ZOMBIENET_INTEGRATION_TEST_IMAGE="docker.io/parity/polkadot:latest" - - echo "Overrided poladot image ${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - echo "Overrided polkadot image ${ZOMBIENET_INTEGRATION_TEST_IMAGE}" - export COL_IMAGE="${COLANDER_IMAGE}":${PIPELINE_IMAGE_TAG} - BUILD_LINUX_JOB_ID="$(cat ./artifacts/BUILD_LINUX_JOB_ID)" - export POLKADOT_PR_ARTIFACTS_URL="https://gitlab.parity.io/parity/mirrors/polkadot-sdk/-/jobs/${BUILD_LINUX_JOB_ID}/artifacts/raw/artifacts" diff --git a/.gitlab/rust-features.sh b/.gitlab/rust-features.sh new file mode 100755 index 0000000000000000000000000000000000000000..c0ac192a6ec69ba16abb3bad2ec49de7e9cebb61 --- /dev/null +++ b/.gitlab/rust-features.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +############################################################################## +# +# This script checks that crates to not carelessly enable features that +# should stay disabled. It's important to check that since features +# are used to gate specific functionality which should only be enabled +# when the feature is explicitly enabled. +# +# Invocation scheme: +# ./rust-features.sh +# +# Example: +# ./rust-features.sh path/to/substrate +# +# The steps of this script: +# 1. Check that all required dependencies are installed. +# 2. Check that all rules are fullfilled for the whole workspace. If not: +# 4. Check all crates to find the offending ones. +# 5. Print all offending crates and exit with code 1. +# +############################################################################## + +set -eu + +# Check that cargo and grep are installed - otherwise abort. +command -v cargo >/dev/null 2>&1 || { echo >&2 "cargo is required but not installed. Aborting."; exit 1; } +command -v grep >/dev/null 2>&1 || { echo >&2 "grep is required but not installed. Aborting."; exit 1; } + +# Enter the workspace root folder. +cd "$1" +echo "Workspace root is $PWD" + +function main() { + feature_does_not_imply 'default' 'runtime-benchmarks' + feature_does_not_imply 'std' 'runtime-benchmarks' + feature_does_not_imply 'default' 'try-runtime' + feature_does_not_imply 'std' 'try-runtime' +} + +# Accepts two feature names as arguments. +# Checks that the first feature does not imply the second one. +function feature_does_not_imply() { + ENABLED=$1 + STAYS_DISABLED=$2 + echo "📏 Checking that $ENABLED does not imply $STAYS_DISABLED ..." + + # Check if the forbidden feature is enabled anywhere in the workspace. + # But only check "normal" dependencies, so no "dev" or "build" dependencies. + if cargo tree --no-default-features --locked --workspace -e features,normal --features "$ENABLED" | grep -qF "feature \"$STAYS_DISABLED\""; then + echo "❌ $ENABLED implies $STAYS_DISABLED in the workspace" + else + echo "✅ $ENABLED does not imply $STAYS_DISABLED in the workspace" + return + fi + + # Find all Cargo.toml files but exclude the root one since we already know that it is broken. + CARGOS=`find . -name Cargo.toml -not -path ./Cargo.toml` + NUM_CRATES=`echo "$CARGOS" | wc -l` + FAILED=0 + PASSED=0 + echo "🔍 Checking all $NUM_CRATES crates - this takes some time." + + for CARGO in $CARGOS; do + OUTPUT=$(cargo tree --no-default-features --locked --offline -e features,normal --features $ENABLED --manifest-path $CARGO 2>&1 || true) + + if echo "$OUTPUT" | grep -qF "not supported for packages in this workspace"; then + # This case just means that the pallet does not support the + # requested feature which is fine. + PASSED=$((PASSED+1)) + elif echo "$OUTPUT" | grep -qF "feature \"$STAYS_DISABLED\""; then + echo "❌ Violation in $CARGO by dependency:" + # Best effort hint for which dependency needs to be fixed. + echo "$OUTPUT" | grep -wF "feature \"$STAYS_DISABLED\"" | head -n 1 + FAILED=$((FAILED+1)) + else + PASSED=$((PASSED+1)) + fi + done + + echo "Checked $NUM_CRATES crates in total of which $FAILED failed and $PASSED passed." + echo "Exiting with code 1" + exit 1 +} + +main "$@" + diff --git a/Cargo.lock b/Cargo.lock index 918a311d525ad60ebf7dfb8c3d6e662dd983028e..9dafb88496f493b1f14ec27846f504fb929e86b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,15 +42,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" -[[package]] -name = "aead" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "aead" version = "0.4.3" @@ -58,7 +49,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array 0.14.7", - "rand_core 0.6.4", ] [[package]] @@ -71,17 +61,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "aes" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" -dependencies = [ - "aes-soft", - "aesni", - "cipher 0.2.5", -] - [[package]] name = "aes" version = "0.7.5" @@ -133,26 +112,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "aes-soft" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" -dependencies = [ - "cipher 0.2.5", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aesni" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" -dependencies = [ - "cipher 0.2.5", - "opaque-debug 0.3.0", -] - [[package]] name = "ahash" version = "0.7.6" @@ -166,14 +125,15 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", "getrandom 0.2.10", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -191,12 +151,93 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "alloy-primitives" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0628ec0ba5b98b3370bb6be17b12f23bfce8ee4ad83823325a20546d9b03b78" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0fac0fc16baf1f63f78b47c3d24718f3619b0714076f6a02957d808d52cbef" +dependencies = [ + "alloy-rlp-derive", + "arrayvec 0.7.4", + "bytes", + "smol_str", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0391754c09fab4eae3404d19d0d297aa1c670c1775ab51d8a5312afeca23157" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a98ad1696a2e17f010ae8e43e9f2a1e930ed176a8e3ff77acfeff6dfb07b42c" +dependencies = [ + "const-hex", + "dunce", + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.48", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "always-assert" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4436e0292ab1bb631b42973c61205e704475fe8126af845c8d923c0996328127" +[[package]] +name = "amcl" +version = "0.3.0" +source = "git+https://github.com/snowfork/milagro_bls?rev=a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176#a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -310,12 +351,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" -[[package]] -name = "arc-swap" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" - [[package]] name = "ark-bls12-377" version = "0.4.0" @@ -323,8 +358,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" dependencies = [ "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -336,7 +371,7 @@ dependencies = [ "ark-bls12-377", "ark-ec", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -346,9 +381,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -359,10 +394,10 @@ checksum = "b1dc4b3d08f19e8ec06e949712f95b8361e43f1391d94f65e4234df03480631c" dependencies = [ "ark-bls12-381", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -373,8 +408,8 @@ checksum = "2e0605daf0cc5aa2034b78d008aaf159f56901d92a52ee4f6ecdfdac4f426700" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -385,9 +420,9 @@ checksum = "ccee5fba47266f460067588ee1bf070a9c760bf2050c1c509982c5719aadb4f2" dependencies = [ "ark-bw6-761", "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -396,10 +431,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" dependencies = [ - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", "itertools 0.10.5", @@ -416,8 +451,8 @@ checksum = "b10d901b9ac4b38f9c32beacedfadcdd64e46f8d7f8e88c1ae1060022cf6f6c6" dependencies = [ "ark-bls12-377", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -428,9 +463,9 @@ checksum = "524a4fb7540df2e1a8c2e67a83ba1d1e6c3947f4f9342cc2359fc2e789ad731d" dependencies = [ "ark-ec", "ark-ed-on-bls12-377", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", ] [[package]] @@ -441,8 +476,8 @@ checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" dependencies = [ "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-std", + "ark-ff 0.4.2", + "ark-std 0.4.0", ] [[package]] @@ -453,9 +488,27 @@ checksum = "d15185f1acb49a07ff8cbe5f11a1adc5a93b19e211e325d826ae98e98e124346" dependencies = [ "ark-ec", "ark-ed-on-bls12-381-bandersnatch", - "ark-ff", + "ark-ff 0.4.2", "ark-models-ext", - "ark-std", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", ] [[package]] @@ -464,10 +517,10 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "digest 0.10.7", "itertools 0.10.5", @@ -478,6 +531,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-asm" version = "0.4.2" @@ -488,6 +551,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + [[package]] name = "ark-ff-macros" version = "0.4.2" @@ -508,9 +583,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e9eab5d4b5ff2f228b763d38442adc9b084b0a465409b059fac5c2308835ec2" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", ] @@ -520,9 +595,9 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "derivative", "hashbrown 0.13.2", ] @@ -534,9 +609,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bd73bb6ddb72630987d37fa963e99196896c0d0ea81b7c894567e74a2f83af" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "ark-scale" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f69c00b3b529be29528a6f2fd5fa7b1790f8bed81b9cdca17e326538545a179" +dependencies = [ + "ark-ec", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "parity-scale-codec", "scale-info", ] @@ -544,18 +633,28 @@ dependencies = [ [[package]] name = "ark-secret-scalar" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=2019248#2019248785389b3246d55b1c3b0e9bdef4454cb7" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" dependencies = [ "ark-ec", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "ark-transcript", "digest 0.10.7", "getrandom_or_panic", "zeroize", ] +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + [[package]] name = "ark-serialize" version = "0.4.2" @@ -563,7 +662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.4.0", "digest 0.10.7", "num-bigint", ] @@ -579,6 +678,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "ark-std" version = "0.4.0" @@ -593,11 +702,11 @@ dependencies = [ [[package]] name = "ark-transcript" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=2019248#2019248785389b3246d55b1c3b0e9bdef4454cb7" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "digest 0.10.7", "rand_core 0.6.4", "sha3", @@ -642,48 +751,20 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "asn1-rs" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" -dependencies = [ - "asn1-rs-derive 0.1.0", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time 0.3.27", -] - [[package]] name = "asn1-rs" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" dependencies = [ - "asn1-rs-derive 0.4.0", + "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", - "time 0.3.27", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure", + "time", ] [[package]] @@ -761,7 +842,6 @@ dependencies = [ "pallet-xcm", "parachains-common", "parity-scale-codec", - "penpal-runtime", "rococo-runtime", "rococo-system-emulated-network", "sp-runtime", @@ -827,6 +907,8 @@ dependencies = [ "rococo-runtime-constants", "scale-info", "smallvec", + "snowbridge-rococo-common", + "snowbridge-router-primitives", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -872,6 +954,7 @@ dependencies = [ "asset-test-utils", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", "frame-support", "frame-system", @@ -884,7 +967,6 @@ dependencies = [ "pallet-xcm", "parachains-common", "parity-scale-codec", - "penpal-runtime", "polkadot-runtime-common", "sp-runtime", "staging-xcm", @@ -1145,7 +1227,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1156,13 +1238,13 @@ checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1201,6 +1283,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -1218,21 +1312,21 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.32.0", + "object 0.32.2", "rustc-demangle", ] [[package]] name = "bandersnatch_vrfs" version = "0.0.4" -source = "git+https://github.com/w3f/ring-vrf?rev=2019248#2019248785389b3246d55b1c3b0e9bdef4454cb7" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" dependencies = [ "ark-bls12-381", "ark-ec", "ark-ed-on-bls12-381-bandersnatch", - "ark-ff", - "ark-serialize", - "ark-std", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "dleq_vrf", "fflonk", "merlin 3.0.0", @@ -1251,12 +1345,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -1338,7 +1426,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -1354,6 +1442,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitcoin_hashes" version = "0.11.0" @@ -1457,7 +1560,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding 0.1.5", + "block-padding", "byte-tools", "byteorder", "generic-array 0.12.4", @@ -1481,16 +1584,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "block-modes" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" -dependencies = [ - "block-padding 0.2.1", - "cipher 0.2.5", -] - [[package]] name = "block-padding" version = "0.1.5" @@ -1500,12 +1593,6 @@ dependencies = [ "byte-tools", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "blocking" version = "1.3.1" @@ -1523,13 +1610,14 @@ dependencies = [ [[package]] name = "bounded-collections" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" +checksum = "ca548b6163b872067dc5eb82fd130c56881435e30367d2073594a3d9744120dd" dependencies = [ "log", "parity-scale-codec", "scale-info", + "schemars", "serde", ] @@ -1819,6 +1907,13 @@ dependencies = [ "sp-std 8.0.0", ] +[[package]] +name = "bp-xcm-bridge-hub" +version = "0.1.0" +dependencies = [ + "sp-std 8.0.0", +] + [[package]] name = "bp-xcm-bridge-hub-router" version = "0.1.0" @@ -1829,16 +1924,38 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "bridge-hub-common" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "pallet-message-queue", + "parity-scale-codec", + "scale-info", + "snowbridge-core", + "sp-core", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", +] + [[package]] name = "bridge-hub-rococo-emulated-chain" version = "0.0.0" dependencies = [ + "bridge-hub-common", "bridge-hub-rococo-runtime", "cumulus-primitives-core", "emulated-integration-tests-common", "frame-support", "parachains-common", "serde_json", + "snowbridge-core", + "snowbridge-inbound-queue", + "snowbridge-outbound-queue", + "snowbridge-router-primitives", + "snowbridge-system", "sp-core", "sp-runtime", ] @@ -1847,6 +1964,7 @@ dependencies = [ name = "bridge-hub-rococo-integration-tests" version = "1.0.0" dependencies = [ + "asset-hub-rococo-runtime", "asset-test-utils", "bp-messages", "bridge-hub-rococo-runtime", @@ -1854,6 +1972,8 @@ dependencies = [ "cumulus-pallet-xcmp-queue", "emulated-integration-tests-common", "frame-support", + "hex", + "hex-literal", "pallet-assets", "pallet-balances", "pallet-bridge-messages", @@ -1861,7 +1981,18 @@ dependencies = [ "pallet-xcm", "parachains-common", "parity-scale-codec", + "penpal-runtime", + "rococo-system-emulated-network", "rococo-westend-system-emulated-network", + "scale-info", + "snowbridge-core", + "snowbridge-inbound-queue", + "snowbridge-outbound-queue", + "snowbridge-rococo-common", + "snowbridge-router-primitives", + "snowbridge-system", + "sp-core", + "sp-runtime", "staging-xcm", "staging-xcm-executor", ] @@ -1872,16 +2003,19 @@ version = "0.1.0" dependencies = [ "bp-asset-hub-rococo", "bp-asset-hub-westend", + "bp-bridge-hub-polkadot", "bp-bridge-hub-rococo", "bp-bridge-hub-westend", "bp-header-chain", "bp-messages", "bp-parachains", + "bp-polkadot-bulletin", "bp-polkadot-core", "bp-relayers", "bp-rococo", "bp-runtime", "bp-westend", + "bridge-hub-common", "bridge-hub-test-utils", "bridge-runtime-common", "cumulus-pallet-aura-ext", @@ -1917,6 +2051,7 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", + "pallet-xcm-bridge-hub", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -1926,6 +2061,17 @@ dependencies = [ "scale-info", "serde", "smallvec", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum-beacon-client", + "snowbridge-inbound-queue", + "snowbridge-outbound-queue", + "snowbridge-outbound-queue-runtime-api", + "snowbridge-rococo-common", + "snowbridge-router-primitives", + "snowbridge-runtime-common", + "snowbridge-system", + "snowbridge-system-runtime-api", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -1968,6 +2114,7 @@ dependencies = [ "frame-executive", "frame-support", "frame-system", + "impl-trait-for-tuples", "log", "pallet-balances", "pallet-bridge-grandpa", @@ -1986,6 +2133,7 @@ dependencies = [ "sp-io", "sp-keyring", "sp-runtime", + "sp-std 8.0.0", "sp-tracing 10.0.0", "staging-parachain-info", "staging-xcm", @@ -1997,6 +2145,7 @@ dependencies = [ name = "bridge-hub-westend-emulated-chain" version = "0.0.0" dependencies = [ + "bridge-hub-common", "bridge-hub-westend-runtime", "cumulus-primitives-core", "emulated-integration-tests-common", @@ -2026,6 +2175,7 @@ dependencies = [ "parachains-common", "parity-scale-codec", "rococo-westend-system-emulated-network", + "sp-runtime", "staging-xcm", "staging-xcm-executor", ] @@ -2034,6 +2184,7 @@ dependencies = [ name = "bridge-hub-westend-runtime" version = "0.1.0" dependencies = [ + "bp-asset-hub-rococo", "bp-asset-hub-westend", "bp-bridge-hub-rococo", "bp-bridge-hub-westend", @@ -2045,6 +2196,7 @@ dependencies = [ "bp-rococo", "bp-runtime", "bp-westend", + "bridge-hub-common", "bridge-hub-test-utils", "bridge-runtime-common", "cumulus-pallet-aura-ext", @@ -2080,6 +2232,7 @@ dependencies = [ "pallet-utility", "pallet-xcm", "pallet-xcm-benchmarks", + "pallet-xcm-bridge-hub", "parachains-common", "parity-scale-codec", "polkadot-core-primitives", @@ -2123,6 +2276,7 @@ dependencies = [ "bp-relayers", "bp-runtime", "bp-test-utils", + "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "frame-support", "frame-system", @@ -2288,17 +2442,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ccm" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca1a8fbc20b50ac9673ff014abfb2b5f4085ee1a850d408f14a159c5853ac7" -dependencies = [ - "aead 0.3.2", - "cipher 0.2.5", - "subtle 2.4.1", -] - [[package]] name = "cexpr" version = "0.6.0" @@ -2377,15 +2520,14 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.27" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56b4c72906975ca04becb8a30e102dfecddd0c06181e3e95ddc444be28881f8" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", - "time 0.1.45", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -2497,19 +2639,28 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642" dependencies = [ "clap_builder", "clap_derive 4.4.7", ] +[[package]] +name = "clap-num" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488557e97528174edaa2ee268b23a809e0c598213a4bbcb4f34575a46fda147e" +dependencies = [ + "num-traits", +] + [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" dependencies = [ "anstream", "anstyle", @@ -2524,7 +2675,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", ] [[package]] @@ -2549,7 +2700,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -2589,6 +2740,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "collectives-westend-emulated-chain" +version = "0.0.0" +dependencies = [ + "collectives-westend-runtime", + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "serde_json", + "sp-core", + "sp-runtime", + "westend-emulated-chain", +] + [[package]] name = "collectives-westend-runtime" version = "1.0.0" @@ -2610,6 +2776,7 @@ dependencies = [ "hex-literal", "log", "pallet-alliance", + "pallet-asset-rate", "pallet-aura", "pallet-authorship", "pallet-balances", @@ -2629,6 +2796,7 @@ dependencies = [ "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", + "pallet-treasury", "pallet-utility", "pallet-xcm", "parachains-common", @@ -2658,7 +2826,6 @@ dependencies = [ "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", - "testnets-common", "westend-runtime-constants", ] @@ -2702,6 +2869,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "comfy-table" version = "7.0.1" @@ -2716,13 +2894,13 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#61e7b528bc0170d6bf541be32440d569b784425d" +source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "fflonk", "getrandom_or_panic", "merlin 3.0.0", @@ -2757,6 +2935,29 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-hex" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +dependencies = [ + "cfg-if", + "cpufeatures", + "hex", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -2904,6 +3105,133 @@ dependencies = [ "memchr", ] +[[package]] +name = "coretime-rococo-runtime" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "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", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "rococo-runtime-constants", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + +[[package]] +name = "coretime-westend-runtime" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-message-queue", + "pallet-multisig", + "pallet-session", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", + "westend-runtime-constants", +] + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -3039,21 +3367,6 @@ dependencies = [ "wasmtime-types", ] -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" - [[package]] name = "crc32fast" version = "1.3.2" @@ -3100,7 +3413,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.10", + "clap 4.4.13", "criterion-plot", "futures", "is-terminal", @@ -3188,18 +3501,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.2" @@ -3275,12 +3576,13 @@ dependencies = [ name = "cumulus-client-cli" version = "0.1.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "parity-scale-codec", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-service", + "sp-blockchain", "sp-core", "sp-runtime", "url", @@ -3324,9 +3626,9 @@ dependencies = [ "cumulus-client-collator", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", + "cumulus-client-parachain-inherent", "cumulus-primitives-aura", "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", "cumulus-relay-chain-interface", "futures", "parity-scale-codec", @@ -3458,6 +3760,29 @@ dependencies = [ "url", ] +[[package]] +name = "cumulus-client-parachain-inherent" +version = "0.1.0" +dependencies = [ + "async-trait", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-relay-chain-interface", + "cumulus-test-relay-sproof-builder", + "parity-scale-codec", + "sc-client-api", + "scale-info", + "sp-api", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-state-machine", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-trie", + "tracing", +] + [[package]] name = "cumulus-client-pov-recovery" version = "0.1.0" @@ -3605,10 +3930,10 @@ dependencies = [ name = "cumulus-pallet-parachain-system-proc-macro" version = "0.1.0" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -3731,20 +4056,14 @@ version = "0.1.0" dependencies = [ "async-trait", "cumulus-primitives-core", - "cumulus-relay-chain-interface", - "cumulus-test-relay-sproof-builder", "parity-scale-codec", - "sc-client-api", "scale-info", - "sp-api", "sp-core", "sp-inherents", "sp-runtime", "sp-state-machine", "sp-std 8.0.0", - "sp-storage 13.0.0", "sp-trie", - "tracing", ] [[package]] @@ -3996,16 +4315,16 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.4.10", + "clap 4.4.13", "criterion 0.5.1", "cumulus-client-cli", "cumulus-client-consensus-common", "cumulus-client-consensus-relay-chain", + "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-pallet-parachain-system", "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -4096,9 +4415,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" dependencies = [ "cfg-if", "cpufeatures", @@ -4119,7 +4438,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4159,7 +4478,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4176,42 +4495,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", -] - -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", + "syn 2.0.48", ] [[package]] @@ -4221,7 +4505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd72493923899c6f10c641bdbdeddc7183d6396641d99c1a0d1597f37f92e28" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -4262,17 +4546,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "der" version = "0.7.8" @@ -4283,27 +4556,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "der-parser" -version = "7.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" -dependencies = [ - "asn1-rs 0.3.1", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - [[package]] name = "der-parser" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ - "asn1-rs 0.5.2", + "asn1-rs", "displaydoc", "nom", "num-bigint", @@ -4339,37 +4598,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_builder" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_macro" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" -dependencies = [ - "derive_builder_core", - "syn 1.0.109", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -4475,7 +4703,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4487,14 +4715,14 @@ checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" [[package]] name = "dleq_vrf" version = "0.0.2" -source = "git+https://github.com/w3f/ring-vrf?rev=2019248#2019248785389b3246d55b1c3b0e9bdef4454cb7" +source = "git+https://github.com/w3f/ring-vrf?rev=e9782f9#e9782f938629c90f3adb3fff2358bc8d1386af3e" dependencies = [ "ark-ec", - "ark-ff", - "ark-scale", + "ark-ff 0.4.2", + "ark-scale 0.0.12", "ark-secret-scalar", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "ark-transcript", "arrayvec 0.7.4", "zeroize", @@ -4536,9 +4764,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.39", + "syn 2.0.48", "termcolor", - "toml 0.7.6", + "toml 0.7.8", "walkdir", ] @@ -4560,6 +4788,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "dyn-clonable" version = "0.9.0" @@ -4583,21 +4817,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" - -[[package]] -name = "ecdsa" -version = "0.14.8" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "ecdsa" @@ -4605,12 +4827,12 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.8", + "der", "digest 0.10.7", - "elliptic-curve 0.13.5", - "rfc6979 0.4.0", - "signature 2.1.0", - "spki 0.7.2", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -4619,8 +4841,8 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ - "pkcs8 0.10.2", - "signature 2.1.0", + "pkcs8", + "signature", ] [[package]] @@ -4629,7 +4851,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.1", "ed25519", "rand_core 0.6.4", "serde", @@ -4654,13 +4876,13 @@ dependencies = [ [[package]] name = "ed25519-zebra" -version = "4.0.2" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e83e509bcd060ca4b54b72bde5bb306cb2088cb01e14797ebae90a24f70f5f7" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.1", "ed25519", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "rand_core 0.6.4", "sha2 0.10.7", @@ -4673,43 +4895,21 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array 0.14.7", - "group 0.12.1", - "hkdf", - "pem-rfc7468", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle 2.4.1", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.2", + "base16ct", + "crypto-bigint", "digest 0.10.7", - "ff 0.13.0", + "ff", "generic-array 0.14.7", - "group 0.13.0", - "pkcs8 0.10.2", + "group", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.3", + "sec1", "subtle 2.4.1", "zeroize", ] @@ -4792,7 +4992,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4803,7 +5003,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4894,6 +5094,15 @@ dependencies = [ "libc", ] +[[package]] +name = "ethabi-decode" +version = "1.4.0" +source = "git+https://github.com/snowfork/ethabi-decode.git?branch=master#7d215837b626650bd9a076821e57ad488101301f" +dependencies = [ + "ethereum-types", + "tiny-keccak", +] + [[package]] name = "ethbloom" version = "0.13.0" @@ -4902,8 +5111,10 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", + "scale-info", "tiny-keccak", ] @@ -4915,9 +5126,11 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", + "impl-codec", "impl-rlp", "impl-serde", "primitive-types", + "scale-info", "uint", ] @@ -4958,7 +5171,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -4983,6 +5196,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "1.9.0" @@ -4998,6 +5217,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.4", + "auto_impl", + "bytes", +] + [[package]] name = "fatality" version = "0.0.6" @@ -5049,16 +5279,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "ff" version = "0.13.0" @@ -5072,21 +5292,21 @@ dependencies = [ [[package]] name = "fflonk" version = "0.1.0" -source = "git+https://github.com/w3f/fflonk#1beb0585e1c8488956fac7f05da061f9b41e8948" +source = "git+https://github.com/w3f/fflonk#1e854f35e9a65d08b11a86291405cdc95baa0a35" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "merlin 3.0.0", ] [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" [[package]] name = "file-per-thread-logger" @@ -5228,7 +5448,7 @@ dependencies = [ "pallet-examples", "parity-scale-codec", "scale-info", - "simple-mermaid 0.1.0 (git+https://github.com/kianenigma/simple-mermaid.git?rev=e48b187bcfd5cc75111acd9d241f1bd36604344b)", + "simple-mermaid", "sp-api", "sp-arithmetic", "sp-block-builder", @@ -5279,7 +5499,7 @@ dependencies = [ "Inflector", "array-bytes 6.1.0", "chrono", - "clap 4.4.10", + "clap 4.4.13", "comfy-table", "frame-benchmarking", "frame-support", @@ -5340,12 +5560,12 @@ dependencies = [ "frame-election-provider-support", "frame-support", "parity-scale-codec", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "scale-info", "sp-arithmetic", - "syn 2.0.39", + "syn 2.0.48", "trybuild", ] @@ -5371,7 +5591,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -5496,8 +5716,9 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", + "regex", "sp-core-hashing", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -5505,10 +5726,10 @@ name = "frame-support-procedural-tools" version = "4.0.0-dev" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -5517,7 +5738,7 @@ version = "3.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -5750,7 +5971,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -5761,7 +5982,7 @@ checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", "rustls 0.20.8", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -5910,7 +6131,7 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.2.0", "indexmap 1.9.3", "stable_deref_trait", ] @@ -5920,6 +6141,10 @@ name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +dependencies = [ + "fallible-iterator 0.3.0", + "stable_deref_trait", +] [[package]] name = "glob" @@ -5985,24 +6210,13 @@ dependencies = [ "substrate-wasm-builder", ] -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff", "rand_core 0.6.4", "subtle 2.4.1", ] @@ -6076,16 +6290,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "allocator-api2", "serde", ] @@ -6096,7 +6310,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -6314,12 +6528,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.2.3" @@ -6462,7 +6670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.3", ] [[package]] @@ -6517,25 +6725,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "interceptor" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e8a11ae2da61704edada656798b61c94b35ecac2c58eb955156987d5e6be90b" -dependencies = [ - "async-trait", - "bytes", - "log", - "rand 0.8.5", - "rtcp", - "rtp", - "thiserror", - "tokio", - "waitgroup", - "webrtc-srtp", - "webrtc-util", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -6801,8 +6990,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa 0.16.8", - "elliptic-curve 0.13.5", + "ecdsa", + "elliptic-curve", "once_cell", "sha2 0.10.7", ] @@ -6859,6 +7048,8 @@ dependencies = [ "pallet-babe", "pallet-bags-list", "pallet-balances", + "pallet-beefy", + "pallet-beefy-mmr", "pallet-bounties", "pallet-broker", "pallet-child-bounties", @@ -6870,6 +7061,7 @@ dependencies = [ "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", + "pallet-example-tasks", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", @@ -6931,6 +7123,7 @@ dependencies = [ "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", + "sp-consensus-beefy", "sp-consensus-grandpa", "sp-core", "sp-genesis-builder", @@ -7003,15 +7196,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "layout-rs" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1164ef87cb9607c2d887216eca79f0fc92895affe1789bba805dd38d829584e0" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -7085,9 +7269,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libp2p" -version = "0.51.3" +version = "0.51.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" +checksum = "f35eae38201a993ece6bdc823292d6abd1bffed1c4d0f4a3517d2bd8e1d917fe" dependencies = [ "bytes", "futures", @@ -7110,7 +7294,6 @@ dependencies = [ "libp2p-swarm", "libp2p-tcp", "libp2p-wasm-ext", - "libp2p-webrtc", "libp2p-websocket", "libp2p-yamux", "multiaddr", @@ -7422,12 +7605,12 @@ dependencies = [ "futures-rustls", "libp2p-core", "libp2p-identity", - "rcgen 0.10.0", + "rcgen", "ring 0.16.20", "rustls 0.20.8", "thiserror", - "webpki 0.22.0", - "x509-parser 0.14.0", + "webpki", + "x509-parser", "yasna", ] @@ -7445,37 +7628,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "libp2p-webrtc" -version = "0.4.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba48592edbc2f60b4bc7c10d65445b0c3964c07df26fdf493b6880d33be36f8" -dependencies = [ - "async-trait", - "asynchronous-codec", - "bytes", - "futures", - "futures-timer", - "hex", - "if-watch", - "libp2p-core", - "libp2p-identity", - "libp2p-noise", - "log", - "multihash 0.17.0", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "rcgen 0.9.3", - "serde", - "stun", - "thiserror", - "tinytemplate", - "tokio", - "tokio-util", - "webrtc", -] - [[package]] name = "libp2p-websocket" version = "0.41.0" @@ -7754,7 +7906,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -7768,7 +7920,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -7779,7 +7931,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -7790,7 +7942,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -7830,15 +7982,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "md-5" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "memchr" version = "2.6.4" @@ -7863,15 +8006,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.7.1" @@ -7943,6 +8077,20 @@ dependencies = [ "thrift", ] +[[package]] +name = "milagro_bls" +version = "1.5.0" +source = "git+https://github.com/snowfork/milagro_bls?rev=a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176#a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176" +dependencies = [ + "amcl", + "hex", + "lazy_static", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "zeroize", +] + [[package]] name = "mime" version = "0.3.17" @@ -7959,7 +8107,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-node" version = "4.0.0-dev" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "frame", "futures", "futures-timer", @@ -8038,7 +8186,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.1", "either", "hashlink", "lioness", @@ -8294,15 +8442,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "names" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "names" version = "0.14.0" @@ -8394,7 +8533,6 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", ] [[package]] @@ -8433,7 +8571,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes 6.1.0", - "clap 4.4.10", + "clap 4.4.13", "derive_more", "fs_extra", "futures", @@ -8484,6 +8622,8 @@ dependencies = [ "sc-client-api", "sc-consensus-babe", "sc-consensus-babe-rpc", + "sc-consensus-beefy", + "sc-consensus-beefy-rpc", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-mixnet", @@ -8508,7 +8648,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "generate-bags", "kitchensink-runtime", ] @@ -8517,7 +8657,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -8561,14 +8701,14 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "flate2", "fs_extra", "glob", "itertools 0.10.5", "tar", "tempfile", - "toml_edit 0.19.14", + "toml_edit 0.19.15", ] [[package]] @@ -8754,9 +8894,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", "libm", @@ -8792,29 +8932,20 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] -[[package]] -name = "oid-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" -dependencies = [ - "asn1-rs 0.3.1", -] - [[package]] name = "oid-registry" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ - "asn1-rs 0.5.2", + "asn1-rs", ] [[package]] @@ -8876,12 +9007,9 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d035b1f968d91a826f2e34a9d6d02cb2af5aa7ca39ebd27922d850ab4b2dd2c6" dependencies = [ - "anyhow", "expander 2.0.0", - "fs-err", "indexmap 2.0.0", "itertools 0.11.0", - "layout-rs", "petgraph", "proc-macro-crate 1.3.1", "proc-macro2", @@ -8920,28 +9048,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "p256" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" -dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.7", -] - -[[package]] -name = "p384" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" -dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.7", -] - [[package]] name = "pallet-alliance" version = "4.0.0-dev" @@ -9516,20 +9622,16 @@ name = "pallet-contracts-fixtures" version = "1.0.0" dependencies = [ "anyhow", - "cfg-if", "frame-system", "parity-wasm", + "polkavm-linker", "sp-runtime", "tempfile", - "toml 0.8.8", + "toml 0.8.2", "twox-hash", "wat", ] -[[package]] -name = "pallet-contracts-fixtures-common" -version = "1.0.0" - [[package]] name = "pallet-contracts-mock-network" version = "1.0.0" @@ -9574,7 +9676,7 @@ version = "4.0.0-dev" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -9584,6 +9686,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", + "polkavm-derive", "scale-info", ] @@ -9834,6 +9937,22 @@ dependencies = [ "sp-std 8.0.0", ] +[[package]] +name = "pallet-example-tasks" +version = "1.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", +] + [[package]] name = "pallet-examples" version = "4.0.0-dev" @@ -9845,6 +9964,7 @@ dependencies = [ "pallet-example-kitchensink", "pallet-example-offchain-worker", "pallet-example-split", + "pallet-example-tasks", ] [[package]] @@ -10026,6 +10146,7 @@ dependencies = [ name = "pallet-message-queue" version = "7.0.0-dev" dependencies = [ + "environmental", "frame-benchmarking", "frame-support", "frame-system", @@ -10736,11 +10857,11 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "sp-runtime", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -11099,14 +11220,19 @@ dependencies = [ ] [[package]] -name = "pallet-xcm-bridge-hub-router" +name = "pallet-xcm-bridge-hub" version = "0.1.0" dependencies = [ - "bp-xcm-bridge-hub-router", - "frame-benchmarking", + "bp-header-chain", + "bp-messages", + "bp-runtime", + "bp-xcm-bridge-hub", + "bridge-runtime-common", "frame-support", "frame-system", "log", + "pallet-balances", + "pallet-bridge-messages", "parity-scale-codec", "scale-info", "sp-core", @@ -11115,20 +11241,40 @@ dependencies = [ "sp-std 8.0.0", "staging-xcm", "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] -name = "parachain-template-node" +name = "pallet-xcm-bridge-hub-router" version = "0.1.0" dependencies = [ - "clap 4.4.10", - "color-print", - "cumulus-client-cli", - "cumulus-client-collator", - "cumulus-client-consensus-aura", - "cumulus-client-consensus-common", - "cumulus-client-consensus-proposer", - "cumulus-client-service", + "bp-xcm-bridge-hub-router", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "parachain-template-node" +version = "0.1.0" +dependencies = [ + "clap 4.4.13", + "color-print", + "cumulus-client-cli", + "cumulus-client-collator", + "cumulus-client-consensus-aura", + "cumulus-client-consensus-common", + "cumulus-client-consensus-proposer", + "cumulus-client-service", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-relay-chain-interface", @@ -11249,6 +11395,7 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-message-queue", + "pallet-xcm", "parity-scale-codec", "polkadot-core-primitives", "polkadot-primitives", @@ -11263,6 +11410,7 @@ dependencies = [ "staging-parachain-info", "staging-xcm", "staging-xcm-builder", + "staging-xcm-executor", "substrate-wasm-builder", "westend-runtime-constants", ] @@ -11300,11 +11448,17 @@ dependencies = [ "substrate-wasm-builder", ] +[[package]] +name = "parity-bytes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" + [[package]] name = "parity-db" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f19d20a0d2cc52327a88d131fa1c4ea81ea4a04714aedcfeca2dd410049cf8" +checksum = "59e9ab494af9e6e813c72170f0d3c1de1500990d62c97cc05cc7576f91aa402f" dependencies = [ "blake2 0.10.6", "crc32fast", @@ -11487,15 +11641,6 @@ dependencies = [ "base64 0.13.1", ] -[[package]] -name = "pem-rfc7468" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" -dependencies = [ - "base64ct", -] - [[package]] name = "penpal-emulated-chain" version = "0.0.0" @@ -11516,6 +11661,7 @@ dependencies = [ name = "penpal-runtime" version = "0.9.27" dependencies = [ + "assets-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", @@ -11553,6 +11699,222 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "smallvec", + "snowbridge-rococo-common", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + +[[package]] +name = "people-rococo-emulated-chain" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "people-rococo-runtime", + "rococo-emulated-chain", + "serde_json", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "people-rococo-integration-tests" +version = "0.1.0" +dependencies = [ + "assert_matches", + "asset-test-utils", + "emulated-integration-tests-common", + "frame-support", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-identity", + "pallet-message-queue", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "penpal-runtime", + "people-rococo-runtime", + "polkadot-primitives", + "polkadot-runtime-common", + "rococo-runtime", + "rococo-runtime-constants", + "rococo-system-emulated-network", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", +] + +[[package]] +name = "people-rococo-runtime" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "enumflags2", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-identity", + "pallet-message-queue", + "pallet-multisig", + "pallet-session", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "rococo-runtime-constants", + "scale-info", + "serde", + "smallvec", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "substrate-wasm-builder", +] + +[[package]] +name = "people-westend-emulated-chain" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-core", + "emulated-integration-tests-common", + "frame-support", + "parachains-common", + "people-westend-runtime", + "serde_json", + "sp-core", + "sp-runtime", + "westend-emulated-chain", +] + +[[package]] +name = "people-westend-integration-tests" +version = "0.1.0" +dependencies = [ + "assert_matches", + "asset-test-utils", + "emulated-integration-tests-common", + "frame-support", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-identity", + "pallet-message-queue", + "pallet-xcm", + "parachains-common", + "parity-scale-codec", + "penpal-runtime", + "people-westend-runtime", + "polkadot-primitives", + "polkadot-runtime-common", + "sp-runtime", + "staging-xcm", + "staging-xcm-executor", + "westend-runtime", + "westend-runtime-constants", + "westend-system-emulated-network", +] + +[[package]] +name = "people-westend-runtime" +version = "0.1.0" +dependencies = [ + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "enumflags2", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-identity", + "pallet-message-queue", + "pallet-multisig", + "pallet-session", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "scale-info", + "serde", + "smallvec", "sp-api", "sp-block-builder", "sp-consensus-aura", @@ -11571,6 +11933,7 @@ dependencies = [ "staging-xcm-builder", "staging-xcm-executor", "substrate-wasm-builder", + "westend-runtime-constants", ] [[package]] @@ -11609,7 +11972,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -11650,7 +12013,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -11671,24 +12034,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.8", - "spki 0.7.2", + "der", + "spki", ] [[package]] @@ -11733,7 +12086,7 @@ dependencies = [ [[package]] name = "polkadot" -version = "1.1.0" +version = "1.5.0" dependencies = [ "assert_cmd", "color-eyre", @@ -11774,8 +12127,8 @@ dependencies = [ "polkadot-primitives-test-helpers", "rand 0.8.5", "rand_chacha 0.3.1", - "rand_core 0.5.1", - "schnorrkel 0.9.1", + "rand_core 0.6.4", + "schnorrkel 0.11.4", "sp-authority-discovery", "sp-core", "tracing-gum", @@ -11864,6 +12217,7 @@ dependencies = [ "sp-core", "sp-keyring", "thiserror", + "tokio", "tracing-gum", ] @@ -11872,7 +12226,7 @@ name = "polkadot-cli" version = "1.1.0" dependencies = [ "cfg-if", - "clap 4.4.10", + "clap 4.4.13", "frame-benchmarking-cli", "futures", "log", @@ -12078,7 +12432,7 @@ dependencies = [ "kvdb", "kvdb-memorydb", "log", - "merlin 2.0.1", + "merlin 3.0.0", "parity-scale-codec", "parking_lot 0.12.1", "polkadot-node-jaeger", @@ -12091,10 +12445,10 @@ dependencies = [ "polkadot-primitives-test-helpers", "rand 0.8.5", "rand_chacha 0.3.1", - "rand_core 0.5.1", + "rand_core 0.6.4", "sc-keystore", "schnellru", - "schnorrkel 0.9.1", + "schnorrkel 0.11.4", "sp-application-crypto", "sp-consensus", "sp-consensus-babe", @@ -12565,7 +12919,7 @@ dependencies = [ "polkadot-erasure-coding", "polkadot-parachain-primitives", "polkadot-primitives", - "schnorrkel 0.9.1", + "schnorrkel 0.11.4", "serde", "sp-application-crypto", "sp-consensus-babe", @@ -12593,6 +12947,8 @@ dependencies = [ "async-trait", "futures", "parking_lot 0.12.1", + "polkadot-erasure-coding", + "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-util", "polkadot-primitives", @@ -12703,7 +13059,7 @@ dependencies = [ [[package]] name = "polkadot-parachain-bin" -version = "1.1.0" +version = "1.5.0" dependencies = [ "assert_cmd", "asset-hub-rococo-runtime", @@ -12711,20 +13067,22 @@ dependencies = [ "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.4.10", + "clap 4.4.13", "collectives-westend-runtime", "color-print", "contracts-rococo-runtime", + "coretime-rococo-runtime", + "coretime-westend-runtime", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", + "cumulus-client-parachain-inherent", "cumulus-client-service", "cumulus-primitives-aura", "cumulus-primitives-core", - "cumulus-primitives-parachain-inherent", "cumulus-relay-chain-interface", "frame-benchmarking", "frame-benchmarking-cli", @@ -12743,6 +13101,8 @@ dependencies = [ "parachains-common", "parity-scale-codec", "penpal-runtime", + "people-rococo-runtime", + "people-westend-runtime", "polkadot-cli", "polkadot-primitives", "polkadot-service", @@ -12864,6 +13224,7 @@ dependencies = [ "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-rpc", + "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", @@ -12895,6 +13256,7 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-balances", + "pallet-broker", "pallet-election-provider-multi-phase", "pallet-fast-unstake", "pallet-identity", @@ -12965,6 +13327,7 @@ dependencies = [ "pallet-authorship", "pallet-babe", "pallet-balances", + "pallet-broker", "pallet-message-queue", "pallet-session", "pallet-staking", @@ -12985,6 +13348,7 @@ dependencies = [ "serde_json", "sp-api", "sp-application-crypto", + "sp-arithmetic", "sp-core", "sp-inherents", "sp-io", @@ -13027,7 +13391,7 @@ dependencies = [ "sc-rpc", "sc-rpc-api", "scale-info", - "simple-mermaid 0.1.0 (git+https://github.com/kianenigma/simple-mermaid.git?branch=main)", + "simple-mermaid", "sp-api", "sp-core", "sp-io", @@ -13208,6 +13572,54 @@ dependencies = [ "sp-core", ] +[[package]] +name = "polkadot-subsystem-bench" +version = "1.0.0" +dependencies = [ + "assert_matches", + "async-trait", + "clap 4.4.13", + "clap-num", + "color-eyre", + "colored", + "env_logger 0.9.3", + "futures", + "futures-timer", + "itertools 0.11.0", + "log", + "orchestra", + "parity-scale-codec", + "paste", + "polkadot-availability-recovery", + "polkadot-erasure-coding", + "polkadot-node-metrics", + "polkadot-node-network-protocol", + "polkadot-node-primitives", + "polkadot-node-subsystem", + "polkadot-node-subsystem-test-helpers", + "polkadot-node-subsystem-types", + "polkadot-node-subsystem-util", + "polkadot-overseer", + "polkadot-primitives", + "polkadot-primitives-test-helpers", + "prometheus", + "pyroscope", + "pyroscope_pprofrs", + "rand 0.8.5", + "sc-keystore", + "sc-network", + "sc-service", + "serde", + "serde_yaml", + "sp-application-crypto", + "sp-core", + "sp-keyring", + "sp-keystore", + "substrate-prometheus-endpoint", + "tokio", + "tracing-gum", +] + [[package]] name = "polkadot-test-client" version = "1.0.0" @@ -13243,7 +13655,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.4.10", + "clap 4.4.13", "color-eyre", "futures", "futures-timer", @@ -13390,12 +13802,60 @@ dependencies = [ name = "polkadot-voter-bags" version = "1.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "generate-bags", "sp-io", "westend-runtime", ] +[[package]] +name = "polkavm-common" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01363cf0a778e8d93eff31e8a03bc59992cba35faa419ea4f3e80146b69195ba" + +[[package]] +name = "polkavm-common" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88e869d66a254db6c7069992f240626416aba8e87d65c00e4be443135babfe82" + +[[package]] +name = "polkavm-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26501292b2cb980cbeaac3304f0fc4480ff1bac2473045453d7333d775658b6a" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.48", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903e16ad3ed768f35e6f40acff2e8aaf6afb9f2889b0a8982dd43dcbee29db2d" +dependencies = [ + "polkavm-common 0.2.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "polkavm-linker" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8719d37effca6df1cecf5c816d84ab09b7d18e960511f61c254a7581fa50c3" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.32.2", + "polkavm-common 0.3.0", + "rustc-demangle", +] + [[package]] name = "polling" version = "2.8.0" @@ -13568,7 +14028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -13609,16 +14069,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit 0.19.14", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "6b2685dd208a3771337d8d386a89840f0f43cd68be8dae90a5f8c2384effc9cd" dependencies = [ - "toml_edit 0.20.7", + "toml_edit 0.21.0", ] [[package]] @@ -13659,14 +14119,14 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] @@ -13731,7 +14191,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -13746,6 +14206,26 @@ dependencies = [ "regex", ] +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.4.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax 0.8.2", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.11.9" @@ -13819,7 +14299,7 @@ dependencies = [ "libc", "libflate", "log", - "names 0.14.0", + "names", "prost", "reqwest", "thiserror", @@ -13904,14 +14384,14 @@ dependencies = [ "thiserror", "tinyvec", "tracing", - "webpki 0.22.0", + "webpki", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -14012,6 +14492,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -14040,19 +14529,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rcgen" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" -dependencies = [ - "pem", - "ring 0.16.20", - "time 0.3.27", - "x509-parser 0.13.2", - "yasna", -] - [[package]] name = "rcgen" version = "0.10.0" @@ -14061,7 +14537,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring 0.16.20", - "time 0.3.27", + "time", "yasna", ] @@ -14133,7 +14609,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -14202,7 +14678,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -14262,17 +14738,6 @@ dependencies = [ "quick-error", ] -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac 0.12.1", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -14286,13 +14751,13 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#61e7b528bc0170d6bf541be32440d569b784425d" +source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" dependencies = [ "ark-ec", - "ark-ff", + "ark-ff 0.4.2", "ark-poly", - "ark-serialize", - "ark-std", + "ark-serialize 0.4.2", + "ark-std 0.4.0", "blake2 0.10.6", "common", "fflonk", @@ -14309,11 +14774,25 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + [[package]] name = "ripemd" version = "0.1.3" @@ -14534,6 +15013,7 @@ dependencies = [ "sp-runtime", "sp-weights", "staging-xcm", + "staging-xcm-builder", ] [[package]] @@ -14544,6 +15024,7 @@ dependencies = [ "bridge-hub-rococo-emulated-chain", "emulated-integration-tests-common", "penpal-emulated-chain", + "people-rococo-emulated-chain", "rococo-emulated-chain", ] @@ -14556,6 +15037,7 @@ dependencies = [ "bridge-hub-rococo-emulated-chain", "bridge-hub-westend-emulated-chain", "emulated-integration-tests-common", + "penpal-emulated-chain", "rococo-emulated-chain", "westend-emulated-chain", ] @@ -14571,17 +15053,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rtcp" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1919efd6d4a6a85d13388f9487549bb8e359f17198cc03ffd72f79b553873691" -dependencies = [ - "bytes", - "thiserror", - "webrtc-util", -] - [[package]] name = "rtnetlink" version = "0.10.1" @@ -14608,19 +15079,35 @@ dependencies = [ ] [[package]] -name = "rtp" -version = "0.6.8" +name = "ruint" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a095411ff00eed7b12e4c6a118ba984d113e1079582570d56a5ee723f11f80" +checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" dependencies = [ - "async-trait", + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", "bytes", + "fastrlp", + "num-bigint", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", "rand 0.8.5", + "rlp", + "ruint-macro", "serde", - "thiserror", - "webrtc-util", + "valuable", + "zeroize", ] +[[package]] +name = "ruint-macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -14648,6 +15135,15 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -14707,19 +15203,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64 0.13.1", - "log", - "ring 0.16.20", - "sct 0.6.1", - "webpki 0.21.4", -] - [[package]] name = "rustls" version = "0.20.8" @@ -14728,8 +15211,8 @@ checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring 0.16.20", - "sct 0.7.0", - "webpki 0.22.0", + "sct", + "webpki", ] [[package]] @@ -14741,7 +15224,7 @@ dependencies = [ "log", "ring 0.16.20", "rustls-webpki 0.101.4", - "sct 0.7.0", + "sct", ] [[package]] @@ -14772,7 +15255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" dependencies = [ "ring 0.16.20", - "untrusted", + "untrusted 0.7.1", ] [[package]] @@ -14782,7 +15265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring 0.16.20", - "untrusted", + "untrusted 0.7.1", ] [[package]] @@ -14972,10 +15455,10 @@ dependencies = [ name = "sc-chain-spec-derive" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -14985,14 +15468,14 @@ dependencies = [ "array-bytes 6.1.0", "bip39", "chrono", - "clap 4.4.10", + "clap 4.4.13", "fdlimit", "futures", "futures-timer", "itertools 0.10.5", "libp2p-identity", "log", - "names 0.13.0", + "names", "parity-scale-codec", "rand 0.8.5", "regex", @@ -15294,7 +15777,7 @@ dependencies = [ name = "sc-consensus-grandpa" version = "0.10.0-dev" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "array-bytes 6.1.0", "assert_matches", "async-trait", @@ -15678,7 +16161,7 @@ dependencies = [ name = "sc-network-gossip" version = "0.10.0-dev" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "async-trait", "futures", "futures-timer", @@ -16129,7 +16612,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.1.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "fs4", "log", "sc-client-db", @@ -16199,9 +16682,9 @@ name = "sc-tracing" version = "4.0.0-dev" dependencies = [ "ansi_term", - "atty", "chrono", "criterion 0.4.0", + "is-terminal", "lazy_static", "libc", "log", @@ -16228,10 +16711,10 @@ dependencies = [ name = "sc-tracing-proc-macro" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -16333,13 +16816,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "schemars" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + [[package]] name = "schnellru" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "cfg-if", "hashbrown 0.13.2", ] @@ -16378,6 +16885,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead 0.5.2", + "arrayref", + "arrayvec 0.7.4", + "curve25519-dalek 4.1.1", + "getrandom_or_panic", + "merlin 3.0.0", + "rand_core 0.6.4", + "serde_bytes", + "sha2 0.10.7", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -16390,16 +16922,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring 0.16.20", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -16407,33 +16929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring 0.16.20", - "untrusted", -] - -[[package]] -name = "sdp" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d22a5ef407871893fd72b4562ee15e4742269b173959db4b8df6f538c414e13" -dependencies = [ - "rand 0.8.5", - "substring", - "thiserror", - "url", -] - -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array 0.14.7", - "pkcs8 0.9.0", - "subtle 2.4.1", - "zeroize", + "untrusted 0.7.1", ] [[package]] @@ -16442,10 +16938,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", - "der 0.7.8", + "base16ct", + "der", "generic-array 0.14.7", - "pkcs8 0.10.2", + "pkcs8", "subtle 2.4.1", "zeroize", ] @@ -16550,7 +17046,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", ] [[package]] @@ -16559,12 +17055,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", ] [[package]] name = "semver" -version = "1.0.18" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", +] + +[[package]] +name = "semver" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" dependencies = [ @@ -16577,6 +17082,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "separator" version = "0.4.1" @@ -16585,22 +17099,51 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd31f59f6fe2b0c055371bb2f16d7f0aa7d8881676c04a55b1596d1a17cd10a4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -16614,9 +17157,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -16644,6 +17187,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +dependencies = [ + "indexmap 2.0.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "2.0.0" @@ -16666,7 +17222,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -16821,16 +17377,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "2.1.0" @@ -16854,11 +17400,6 @@ dependencies = [ "wide", ] -[[package]] -name = "simple-mermaid" -version = "0.1.0" -source = "git+https://github.com/kianenigma/simple-mermaid.git?branch=main#e48b187bcfd5cc75111acd9d241f1bd36604344b" - [[package]] name = "simple-mermaid" version = "0.1.0" @@ -16928,6 +17469,15 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", +] + [[package]] name = "smoldot" version = "0.11.0" @@ -16944,13 +17494,13 @@ dependencies = [ "chacha20 0.9.1", "crossbeam-queue", "derive_more", - "ed25519-zebra 4.0.2", + "ed25519-zebra 4.0.3", "either", "event-listener", "fnv", "futures-lite", "futures-util", - "hashbrown 0.14.0", + "hashbrown 0.14.3", "hex", "hmac 0.12.1", "itertools 0.11.0", @@ -16969,76 +17519,428 @@ dependencies = [ "ruzstd", "schnorrkel 0.10.2", "serde", - "serde_json", - "sha2 0.10.7", - "sha3", - "siphasher", - "slab", - "smallvec", - "soketto", - "twox-hash", - "wasmi", - "x25519-dalek 2.0.0", - "zeroize", + "serde_json", + "sha2 0.10.7", + "sha3", + "siphasher", + "slab", + "smallvec", + "soketto", + "twox-hash", + "wasmi", + "x25519-dalek 2.0.0", + "zeroize", +] + +[[package]] +name = "smoldot-light" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" +dependencies = [ + "async-channel", + "async-lock", + "base64 0.21.2", + "blake2-rfc", + "derive_more", + "either", + "event-listener", + "fnv", + "futures-channel", + "futures-lite", + "futures-util", + "hashbrown 0.14.3", + "hex", + "itertools 0.11.0", + "log", + "lru 0.11.0", + "no-std-net", + "parking_lot 0.12.1", + "pin-project", + "rand 0.8.5", + "rand_chacha 0.3.1", + "serde", + "serde_json", + "siphasher", + "slab", + "smol", + "smoldot", + "zeroize", +] + +[[package]] +name = "snap" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" + +[[package]] +name = "snow" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" +dependencies = [ + "aes-gcm 0.9.4", + "blake2 0.10.6", + "chacha20poly1305", + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "ring 0.16.20", + "rustc_version 0.4.0", + "sha2 0.10.7", + "subtle 2.4.1", +] + +[[package]] +name = "snowbridge-beacon-primitives" +version = "0.0.1" +dependencies = [ + "byte-slice-cast", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "milagro_bls", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "snowbridge-ethereum", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "ssz_rs", + "ssz_rs_derive", + "static_assertions", +] + +[[package]] +name = "snowbridge-core" +version = "0.1.1" +dependencies = [ + "ethabi-decode", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "parity-scale-codec", + "polkadot-parachain-primitives", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "snowbridge-ethereum" +version = "0.1.0" +dependencies = [ + "ethabi-decode", + "ethbloom", + "ethereum-types", + "hex-literal", + "parity-bytes", + "parity-scale-codec", + "rand 0.8.5", + "rlp", + "rustc-hex", + "scale-info", + "serde", + "serde-big-array", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "wasm-bindgen-test", +] + +[[package]] +name = "snowbridge-ethereum-beacon-client" +version = "0.0.1" +dependencies = [ + "bp-runtime", + "byte-slice-cast", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "log", + "pallet-timestamp", + "parity-scale-codec", + "rand 0.8.5", + "rlp", + "scale-info", + "serde", + "serde_json", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 8.0.0", + "ssz_rs", + "ssz_rs_derive", + "static_assertions", +] + +[[package]] +name = "snowbridge-inbound-queue" +version = "0.1.1" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "log", + "num-traits", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum", + "snowbridge-ethereum-beacon-client", + "snowbridge-router-primitives", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "staging-xcm-builder", +] + +[[package]] +name = "snowbridge-outbound-queue" +version = "0.1.1" +dependencies = [ + "bridge-hub-common", + "ethabi-decode", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "pallet-message-queue", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", +] + +[[package]] +name = "snowbridge-outbound-queue-merkle-tree" +version = "0.1.1" +dependencies = [ + "array-bytes 4.2.0", + "env_logger 0.9.3", + "hex", + "hex-literal", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "snowbridge-outbound-queue-runtime-api" +version = "0.1.0" +dependencies = [ + "frame-support", + "parity-scale-codec", + "snowbridge-core", + "snowbridge-outbound-queue-merkle-tree", + "sp-api", + "sp-core", + "sp-std 8.0.0", + "staging-xcm", +] + +[[package]] +name = "snowbridge-rococo-common" +version = "0.0.1" +dependencies = [ + "frame-support", + "log", + "staging-xcm", +] + +[[package]] +name = "snowbridge-router-primitives" +version = "0.1.1" +dependencies = [ + "ethabi-decode", + "frame-support", + "frame-system", + "hex-literal", + "log", + "parity-scale-codec", + "rustc-hex", + "scale-info", + "serde", + "snowbridge-core", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", ] [[package]] -name = "smoldot-light" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" +name = "snowbridge-runtime-common" +version = "0.1.1" dependencies = [ - "async-channel", - "async-lock", - "base64 0.21.2", - "blake2-rfc", - "derive_more", - "either", - "event-listener", - "fnv", - "futures-channel", - "futures-lite", - "futures-util", - "hashbrown 0.14.0", - "hex", - "itertools 0.11.0", + "frame-support", + "frame-system", "log", - "lru 0.11.0", - "no-std-net", - "parking_lot 0.12.1", - "pin-project", - "rand 0.8.5", - "rand_chacha 0.3.1", + "snowbridge-core", + "sp-arithmetic", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] + +[[package]] +name = "snowbridge-runtime-tests" +version = "0.1.0" +dependencies = [ + "asset-hub-rococo-runtime", + "assets-common", + "bridge-hub-rococo-runtime", + "bridge-hub-test-utils", + "bridge-runtime-common", + "cumulus-pallet-aura-ext", + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-session-benchmarking", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-utility", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-collator-selection", + "pallet-message-queue", + "pallet-multisig", + "pallet-session", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-utility", + "pallet-xcm", + "pallet-xcm-benchmarks", + "parachains-common", + "parachains-runtimes-test-utils", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain-primitives", + "polkadot-runtime-common", + "rococo-runtime-constants", + "scale-info", "serde", - "serde_json", - "siphasher", - "slab", - "smol", - "smoldot", - "zeroize", + "smallvec", + "snowbridge-beacon-primitives", + "snowbridge-core", + "snowbridge-ethereum-beacon-client", + "snowbridge-inbound-queue", + "snowbridge-outbound-queue", + "snowbridge-outbound-queue-runtime-api", + "snowbridge-router-primitives", + "snowbridge-system", + "snowbridge-system-runtime-api", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-keyring", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std 8.0.0", + "sp-storage 13.0.0", + "sp-transaction-pool", + "sp-version", + "staging-parachain-info", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", + "static_assertions", ] [[package]] -name = "snap" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" +name = "snowbridge-system" +version = "0.1.1" +dependencies = [ + "ethabi-decode", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "log", + "pallet-balances", + "pallet-message-queue", + "parity-scale-codec", + "polkadot-primitives", + "scale-info", + "snowbridge-core", + "snowbridge-outbound-queue", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "staging-xcm-builder", + "staging-xcm-executor", +] [[package]] -name = "snow" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" +name = "snowbridge-system-runtime-api" +version = "0.1.0" dependencies = [ - "aes-gcm 0.9.4", - "blake2 0.10.6", - "chacha20poly1305", - "curve25519-dalek 4.0.0", - "rand_core 0.6.4", - "ring 0.16.20", - "rustc_version 0.4.0", - "sha2 0.10.7", - "subtle 2.4.1", + "parity-scale-codec", + "snowbridge-core", + "sp-api", + "sp-core", + "sp-std 8.0.0", + "staging-xcm", ] [[package]] @@ -17107,10 +18009,10 @@ dependencies = [ "assert_matches", "blake2 0.10.6", "expander 2.0.0", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17389,7 +18291,7 @@ dependencies = [ "lazy_static", "libsecp256k1", "log", - "merlin 2.0.1", + "merlin 3.0.0", "parity-scale-codec", "parking_lot 0.12.1", "paste", @@ -17397,7 +18299,7 @@ dependencies = [ "rand 0.8.5", "regex", "scale-info", - "schnorrkel 0.9.1", + "schnorrkel 0.11.4", "secp256k1", "secrecy", "serde", @@ -17445,7 +18347,7 @@ version = "9.0.0" dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17463,7 +18365,7 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale", + "ark-scale 0.0.12", "sp-runtime-interface 17.0.0", "sp-std 8.0.0", ] @@ -17484,7 +18386,7 @@ dependencies = [ "ark-ed-on-bls12-377-ext", "ark-ed-on-bls12-381-bandersnatch", "ark-ed-on-bls12-381-bandersnatch-ext", - "ark-scale", + "ark-scale 0.0.11", "sp-runtime-interface 17.0.0 (git+https://github.com/paritytech/polkadot-sdk)", "sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk)", ] @@ -17503,7 +18405,7 @@ version = "8.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17513,7 +18415,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17588,7 +18490,6 @@ dependencies = [ name = "sp-keyring" version = "24.0.0" dependencies = [ - "lazy_static", "sp-core", "sp-runtime", "strum", @@ -17600,7 +18501,7 @@ version = "0.27.0" dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", - "rand 0.7.3", + "rand 0.8.5", "rand_chacha 0.2.2", "sp-core", "sp-externalities 0.19.0", @@ -17673,7 +18574,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "honggfuzz", "rand 0.8.5", "sp-npos-elections", @@ -17723,7 +18624,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "simple-mermaid 0.1.0 (git+https://github.com/kianenigma/simple-mermaid.git?branch=main)", + "simple-mermaid", "sp-api", "sp-application-crypto", "sp-arithmetic", @@ -17784,10 +18685,10 @@ version = "11.0.0" dependencies = [ "Inflector", "expander 2.0.0", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17799,7 +18700,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -17896,7 +18797,7 @@ name = "sp-statement-store" version = "4.0.0-dev" dependencies = [ "aes-gcm 0.10.3", - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.1", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -18022,11 +18923,10 @@ dependencies = [ name = "sp-trie" version = "22.0.0" dependencies = [ - "ahash 0.8.3", + "ahash 0.8.7", "array-bytes 6.1.0", "criterion 0.4.0", "hash-db", - "hashbrown 0.13.2", "lazy_static", "memory-db", "nohash-hasher", @@ -18071,7 +18971,7 @@ dependencies = [ "proc-macro2", "quote", "sp-version", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -18103,12 +19003,13 @@ dependencies = [ name = "sp-weights" version = "20.0.0" dependencies = [ + "bounded-collections", "parity-scale-codec", "scale-info", + "schemars", "serde", "smallvec", "sp-arithmetic", - "sp-core", "sp-debug-derive 8.0.0", "sp-std 8.0.0", ] @@ -18136,16 +19037,6 @@ dependencies = [ "strum", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.2" @@ -18153,7 +19044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.8", + "der", ] [[package]] @@ -18171,6 +19062,29 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ssz_rs" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057291e5631f280978fa9c8009390663ca4613359fc1318e36a8c24c392f6d1f" +dependencies = [ + "bitvec", + "num-bigint", + "sha2 0.9.9", + "ssz_rs_derive", +] + +[[package]] +name = "ssz_rs_derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -18181,7 +19095,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "2.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "log", "sc-chain-spec", "serde_json", @@ -18194,7 +19108,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes 6.1.0", "assert_cmd", - "clap 4.4.10", + "clap 4.4.13", "clap_complete", "criterion 0.4.0", "frame-benchmarking", @@ -18206,6 +19120,7 @@ dependencies = [ "jsonrpsee", "kitchensink-runtime", "log", + "mmr-gadget", "nix 0.26.2", "node-primitives", "node-rpc", @@ -18236,6 +19151,7 @@ dependencies = [ "sc-client-db", "sc-consensus", "sc-consensus-babe", + "sc-consensus-beefy", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-slots", @@ -18267,6 +19183,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-babe", + "sp-consensus-beefy", "sp-consensus-grandpa", "sp-core", "sp-externalities 0.19.0", @@ -18275,6 +19192,7 @@ dependencies = [ "sp-keyring", "sp-keystore", "sp-mixnet", + "sp-mmr-primitives", "sp-runtime", "sp-state-machine", "sp-statement-store", @@ -18299,7 +19217,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -18333,6 +19251,7 @@ version = "1.0.0" name = "staging-xcm" version = "1.0.0" dependencies = [ + "array-bytes 6.1.0", "bounded-collections", "derivative", "environmental", @@ -18342,6 +19261,7 @@ dependencies = [ "log", "parity-scale-codec", "scale-info", + "schemars", "serde", "sp-io", "sp-weights", @@ -18498,30 +19418,11 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "stun" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" -dependencies = [ - "base64 0.13.1", - "crc", - "lazy_static", - "md-5", - "rand 0.8.5", - "ring 0.16.20", - "subtle 2.4.1", - "thiserror", - "tokio", - "url", - "webrtc-util", -] - [[package]] name = "subkey" version = "3.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "sc-cli", ] @@ -18563,7 +19464,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "frame-support", "frame-system", "sc-cli", @@ -18782,20 +19683,11 @@ dependencies = [ "sp-maybe-compressed-blob", "strum", "tempfile", - "toml 0.7.6", + "toml 0.8.2", "walkdir", "wasm-opt", ] -[[package]] -name = "substring" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" -dependencies = [ - "autocfg", -] - [[package]] name = "subtle" version = "1.0.0" @@ -18918,15 +19810,27 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b837ef12ab88835251726eb12237655e61ec8dc8a280085d1961cdc3dfd047" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -19038,7 +19942,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "futures", "futures-timer", "log", @@ -19086,7 +19990,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.4.10", + "clap 4.4.13", "futures", "futures-timer", "log", @@ -19123,26 +20027,13 @@ dependencies = [ name = "test-runtime-constants" version = "1.0.0" dependencies = [ - "frame-support", - "polkadot-primitives", - "polkadot-runtime-common", - "smallvec", - "sp-core", - "sp-runtime", - "sp-weights", -] - -[[package]] -name = "testnets-common" -version = "1.0.0" -dependencies = [ - "frame-support", - "polkadot-core-primitives", - "rococo-runtime-constants", + "frame-support", + "polkadot-primitives", + "polkadot-runtime-common", "smallvec", + "sp-core", "sp-runtime", - "substrate-wasm-builder", - "westend-runtime-constants", + "sp-weights", ] [[package]] @@ -19188,7 +20079,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -19260,17 +20151,6 @@ dependencies = [ "tikv-jemalloc-sys", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.27" @@ -19335,9 +20215,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", @@ -19360,7 +20240,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -19447,26 +20327,26 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.19.14", + "toml_edit 0.19.15", ] [[package]] name = "toml" -version = "0.8.8" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.20.2", ] [[package]] @@ -19480,9 +20360,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "serde", @@ -19493,11 +20373,13 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap 2.0.0", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -19509,8 +20391,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ "indexmap 2.0.0", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -19577,14 +20457,14 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -19616,10 +20496,10 @@ version = "1.0.0" dependencies = [ "assert_matches", "expander 2.0.0", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] @@ -19772,7 +20652,7 @@ version = "0.10.0-dev" dependencies = [ "assert_cmd", "async-trait", - "clap 4.4.10", + "clap 4.4.13", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -19848,25 +20728,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "turn" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" -dependencies = [ - "async-trait", - "base64 0.13.1", - "futures", - "log", - "md-5", - "rand 0.8.5", - "ring 0.16.20", - "stun", - "thiserror", - "tokio", - "webrtc-util", -] - [[package]] name = "twox-hash" version = "1.6.3" @@ -19903,6 +20764,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -19956,6 +20823,12 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + [[package]] name = "unsigned-varint" version = "0.7.1" @@ -19974,6 +20847,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.0" @@ -20002,9 +20881,6 @@ name = "uuid" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" -dependencies = [ - "getrandom 0.2.10", -] [[package]] name = "valuable" @@ -20075,8 +20951,8 @@ dependencies = [ "ark-bls12-377", "ark-bls12-381", "ark-ec", - "ark-ff", - "ark-serialize", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", "ark-serialize-derive", "arrayref", "constcat", @@ -20099,15 +20975,6 @@ dependencies = [ "libc", ] -[[package]] -name = "waitgroup" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" -dependencies = [ - "atomic-waker", -] - [[package]] name = "waker-fn" version = "1.1.0" @@ -20139,12 +21006,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -20174,7 +21035,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -20208,7 +21069,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -20219,6 +21080,30 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "wasm-encoder" version = "0.31.1" @@ -20579,22 +21464,12 @@ dependencies = [ [[package]] name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring 0.16.20", - "untrusted", -] - -[[package]] -name = "webpki" -version = "0.22.0" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.16.20", - "untrusted", + "ring 0.17.7", + "untrusted 0.9.0", ] [[package]] @@ -20603,7 +21478,7 @@ version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] @@ -20621,214 +21496,6 @@ version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" -[[package]] -name = "webrtc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3bc9049bdb2cea52f5fd4f6f728184225bdb867ed0dc2410eab6df5bdd67bb" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "hex", - "interceptor", - "lazy_static", - "log", - "rand 0.8.5", - "rcgen 0.9.3", - "regex", - "ring 0.16.20", - "rtcp", - "rtp", - "rustls 0.19.1", - "sdp", - "serde", - "serde_json", - "sha2 0.10.7", - "stun", - "thiserror", - "time 0.3.27", - "tokio", - "turn", - "url", - "waitgroup", - "webrtc-data", - "webrtc-dtls", - "webrtc-ice", - "webrtc-mdns", - "webrtc-media", - "webrtc-sctp", - "webrtc-srtp", - "webrtc-util", -] - -[[package]] -name = "webrtc-data" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef36a4d12baa6e842582fe9ec16a57184ba35e1a09308307b67d43ec8883100" -dependencies = [ - "bytes", - "derive_builder", - "log", - "thiserror", - "tokio", - "webrtc-sctp", - "webrtc-util", -] - -[[package]] -name = "webrtc-dtls" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a00f4242f2db33307347bd5be53263c52a0331c96c14292118c9a6bb48d267" -dependencies = [ - "aes 0.6.0", - "aes-gcm 0.10.3", - "async-trait", - "bincode", - "block-modes", - "byteorder", - "ccm", - "curve25519-dalek 3.2.0", - "der-parser 8.2.0", - "elliptic-curve 0.12.3", - "hkdf", - "hmac 0.12.1", - "log", - "p256", - "p384", - "rand 0.8.5", - "rand_core 0.6.4", - "rcgen 0.10.0", - "ring 0.16.20", - "rustls 0.19.1", - "sec1 0.3.0", - "serde", - "sha1", - "sha2 0.10.7", - "signature 1.6.4", - "subtle 2.4.1", - "thiserror", - "tokio", - "webpki 0.21.4", - "webrtc-util", - "x25519-dalek 2.0.0", - "x509-parser 0.13.2", -] - -[[package]] -name = "webrtc-ice" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80" -dependencies = [ - "arc-swap", - "async-trait", - "crc", - "log", - "rand 0.8.5", - "serde", - "serde_json", - "stun", - "thiserror", - "tokio", - "turn", - "url", - "uuid", - "waitgroup", - "webrtc-mdns", - "webrtc-util", -] - -[[package]] -name = "webrtc-mdns" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08dfd7a6e3987e255c4dbe710dde5d94d0f0574f8a21afa95d171376c143106" -dependencies = [ - "log", - "socket2 0.4.9", - "thiserror", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-media" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f72e1650a8ae006017d1a5280efb49e2610c19ccc3c0905b03b648aee9554991" -dependencies = [ - "byteorder", - "bytes", - "rand 0.8.5", - "rtp", - "thiserror", -] - -[[package]] -name = "webrtc-sctp" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d47adcd9427eb3ede33d5a7f3424038f63c965491beafcc20bc650a2f6679c0" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "crc", - "log", - "rand 0.8.5", - "thiserror", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-srtp" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6183edc4c1c6c0175f8812eefdce84dfa0aea9c3ece71c2bf6ddd3c964de3da5" -dependencies = [ - "aead 0.4.3", - "aes 0.7.5", - "aes-gcm 0.9.4", - "async-trait", - "byteorder", - "bytes", - "ctr 0.8.0", - "hmac 0.11.0", - "log", - "rtcp", - "rtp", - "sha-1 0.9.8", - "subtle 2.4.1", - "thiserror", - "tokio", - "webrtc-util", -] - -[[package]] -name = "webrtc-util" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f1db1727772c05cf7a2cfece52c3aca8045ca1e176cd517d323489aa3c6d87" -dependencies = [ - "async-trait", - "bitflags 1.3.2", - "bytes", - "cc", - "ipnet", - "lazy_static", - "libc", - "log", - "nix 0.24.3", - "rand 0.8.5", - "thiserror", - "tokio", - "winapi", -] - [[package]] name = "westend-emulated-chain" version = "0.0.0" @@ -20973,6 +21640,7 @@ dependencies = [ "sp-runtime", "sp-weights", "staging-xcm", + "staging-xcm-builder", ] [[package]] @@ -20981,8 +21649,10 @@ version = "0.0.0" dependencies = [ "asset-hub-westend-emulated-chain", "bridge-hub-westend-emulated-chain", + "collectives-westend-emulated-chain", "emulated-integration-tests-common", "penpal-emulated-chain", + "people-westend-emulated-chain", "westend-emulated-chain", ] @@ -21273,47 +21943,28 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.0.0", + "curve25519-dalek 4.1.1", "rand_core 0.6.4", "serde", "zeroize", ] -[[package]] -name = "x509-parser" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" -dependencies = [ - "asn1-rs 0.3.1", - "base64 0.13.1", - "data-encoding", - "der-parser 7.0.0", - "lazy_static", - "nom", - "oid-registry 0.4.0", - "ring 0.16.20", - "rusticata-macros", - "thiserror", - "time 0.3.27", -] - [[package]] name = "x509-parser" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" dependencies = [ - "asn1-rs 0.5.2", + "asn1-rs", "base64 0.13.1", "data-encoding", - "der-parser 8.2.0", + "der-parser", "lazy_static", "nom", - "oid-registry 0.6.1", + "oid-registry", "rusticata-macros", "thiserror", - "time 0.3.27", + "time", ] [[package]] @@ -21387,7 +22038,7 @@ dependencies = [ "proc-macro2", "quote", "staging-xcm", - "syn 2.0.39", + "syn 2.0.48", "trybuild", ] @@ -21487,7 +22138,27 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.27", + "time", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", ] [[package]] @@ -21507,7 +22178,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.48", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c72324156ac9fab798cc64c9837127f0e91e078f..ab3973e5d3eef43aeca646d4b51262f1761ccc80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "bridges/modules/messages", "bridges/modules/parachains", "bridges/modules/relayers", + "bridges/modules/xcm-bridge-hub", "bridges/modules/xcm-bridge-hub-router", "bridges/primitives/chain-asset-hub-rococo", "bridges/primitives/chain-asset-hub-westend", @@ -33,7 +34,22 @@ members = [ "bridges/primitives/relayers", "bridges/primitives/runtime", "bridges/primitives/test-utils", + "bridges/primitives/xcm-bridge-hub", "bridges/primitives/xcm-bridge-hub-router", + "bridges/snowbridge/parachain/pallets/ethereum-beacon-client", + "bridges/snowbridge/parachain/pallets/inbound-queue", + "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/rococo-common", + "bridges/snowbridge/parachain/runtime/runtime-common", + "bridges/snowbridge/parachain/runtime/tests", "cumulus/client/cli", "cumulus/client/collator", "cumulus/client/consensus/aura", @@ -41,6 +57,7 @@ members = [ "cumulus/client/consensus/proposer", "cumulus/client/consensus/relay-chain", "cumulus/client/network", + "cumulus/client/parachain-inherent", "cumulus/client/pov-recovery", "cumulus/client/relay-chain-inprocess-interface", "cumulus/client/relay-chain-interface", @@ -64,6 +81,9 @@ members = [ "cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend", "cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo", "cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend", + "cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend", + "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo", + "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend", "cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal", "cumulus/parachains/integration-tests/emulated/chains/relays/rococo", "cumulus/parachains/integration-tests/emulated/chains/relays/westend", @@ -75,6 +95,8 @@ members = [ "cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend", "cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo", "cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend", + "cumulus/parachains/integration-tests/emulated/tests/people/people-rococo", + "cumulus/parachains/integration-tests/emulated/tests/people/people-westend", "cumulus/parachains/pallets/collective-content", "cumulus/parachains/pallets/parachain-info", "cumulus/parachains/pallets/ping", @@ -87,13 +109,16 @@ members = [ "cumulus/parachains/runtimes/bridge-hubs/test-utils", "cumulus/parachains/runtimes/collectives/collectives-westend", "cumulus/parachains/runtimes/contracts/contracts-rococo", + "cumulus/parachains/runtimes/coretime/coretime-rococo", + "cumulus/parachains/runtimes/coretime/coretime-westend", "cumulus/parachains/runtimes/glutton/glutton-westend", + "cumulus/parachains/runtimes/people/people-rococo", + "cumulus/parachains/runtimes/people/people-westend", "cumulus/parachains/runtimes/starters/seedling", "cumulus/parachains/runtimes/starters/shell", "cumulus/parachains/runtimes/test-utils", "cumulus/parachains/runtimes/testing/penpal", "cumulus/parachains/runtimes/testing/rococo-parachain", - "cumulus/parachains/testnets-common", "cumulus/polkadot-parachain", "cumulus/primitives/aura", "cumulus/primitives/core", @@ -149,6 +174,7 @@ members = [ "polkadot/node/primitives", "polkadot/node/service", "polkadot/node/subsystem", + "polkadot/node/subsystem-bench", "polkadot/node/subsystem-test-helpers", "polkadot/node/subsystem-types", "polkadot/node/subsystem-util", @@ -285,7 +311,6 @@ members = [ "substrate/frame/collective", "substrate/frame/contracts", "substrate/frame/contracts/fixtures", - "substrate/frame/contracts/fixtures/contracts/common", "substrate/frame/contracts/mock-network", "substrate/frame/contracts/proc-macro", "substrate/frame/contracts/uapi", @@ -307,6 +332,7 @@ members = [ "substrate/frame/examples/kitchensink", "substrate/frame/examples/offchain-worker", "substrate/frame/examples/split", + "substrate/frame/examples/tasks", "substrate/frame/executive", "substrate/frame/fast-unstake", "substrate/frame/glutton", @@ -474,6 +500,35 @@ members = [ ] default-members = ["polkadot", "substrate/bin/node/cli"] +[workspace.lints.rust] +suspicious_double_ref_op = { level = "allow", priority = 2 } + +[workspace.lints.clippy] +all = { level = "allow", priority = 0 } +correctness = { level = "warn", priority = 1 } +complexity = { level = "warn", priority = 1 } +if-same-then-else = { level = "allow", priority = 2 } +zero-prefixed-literal = { level = "allow", priority = 2 } # 00_1000_000 +type_complexity = { level = "allow", priority = 2 } # raison d'etre +nonminimal-bool = { level = "allow", priority = 2 } # maybe +borrowed-box = { level = "allow", priority = 2 } # Reasonable to fix this one +too-many-arguments = { level = "allow", priority = 2 } # (Turning this on would lead to) +needless-lifetimes = { level = "allow", priority = 2 } # generated code +unnecessary_cast = { level = "allow", priority = 2 } # Types may change +identity-op = { level = "allow", priority = 2 } # One case where we do 0 + +useless_conversion = { level = "allow", priority = 2 } # Types may change +unit_arg = { level = "allow", priority = 2 } # stylistic +option-map-unit-fn = { level = "allow", priority = 2 } # stylistic +bind_instead_of_map = { level = "allow", priority = 2 } # stylistic +erasing_op = { level = "allow", priority = 2 } # E.g. 0 * DOLLARS +eq_op = { level = "allow", priority = 2 } # In tests we test equality. +while_immutable_condition = { level = "allow", priority = 2 } # false positives +needless_option_as_deref = { level = "allow", priority = 2 } # false positives +derivable_impls = { level = "allow", priority = 2 } # false positives +stable_sort_primitive = { level = "allow", priority = 2 } # prefer stable sort +extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic +default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic + [profile.release] # Polkadot runtime requires unwinding. panic = "unwind" diff --git a/README.md b/README.md index 1f255823b5b695baf63196304448ae1d9ee23c13..b94376b35ab0c52e680379b82a67a529fc9d84f7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ currently contains runtimes for the Polkadot, Kusama, Westend, and Rococo networ relocated to the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository. ## [Substrate](./substrate/) - [![SubstrateRustDocs](https://img.shields.io/badge/Rust_Docs-Substrate-24CC85?logo=rust)](https://paritytech.github.io/substrate/master/substrate/index.html) + [![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) [![Substrate-license](https://img.shields.io/badge/License-GPL3%2FApache2.0-blue)](./substrate/README.md#LICENSE) Substrate is the primary blockchain SDK used by developers to create the parachains that make up the Polkadot network. @@ -30,7 +30,7 @@ Additionally, it allows for the development of self-sovereign blockchains that o Polkadot. ## [Cumulus](./cumulus/) -[![CumulusRustDocs](https://img.shields.io/badge/Rust_Docs-Cumulus-222222?logo=rust)](https://paritytech.github.io/cumulus/cumulus_client_collator/index.html) +[![CumulusRustDocs](https://img.shields.io/badge/Rust_Docs-Cumulus-222222?logo=rust)](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) [![Cumulus-license](https://img.shields.io/badge/License-GPL3-blue)](./cumulus/LICENSE) Cumulus is a set of tools for writing Substrate-based Polkadot parachains. diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index a5aa53f263371b43d96ad82797343a567c605ae6..8c3e8c989dbcd0938e42356eca9681221654459c 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true repository.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +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 } @@ -22,6 +25,7 @@ bp-parachains = { path = "../../primitives/parachains", default-features = false bp-polkadot-core = { path = "../../primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../primitives/relayers", default-features = false } bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false } bp-xcm-bridge-hub-router = { path = "../../primitives/xcm-bridge-hub-router", default-features = false } pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } @@ -59,6 +63,7 @@ std = [ "bp-relayers/std", "bp-runtime/std", "bp-xcm-bridge-hub-router/std", + "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/bin/runtime-common/src/messages_xcm_extension.rs b/bridges/bin/runtime-common/src/messages_xcm_extension.rs index 77c23db3b2ba2d01aef0d2c45a20377eaf8ea129..53c0579c4cd0456b62fb6355af6d34bd492ac2b9 100644 --- a/bridges/bin/runtime-common/src/messages_xcm_extension.rs +++ b/bridges/bin/runtime-common/src/messages_xcm_extension.rs @@ -22,26 +22,23 @@ //! `XcmRouter` <- `MessageDispatch` <- `InboundMessageQueue` use bp_messages::{ - source_chain::{MessagesBridge, OnMessagesDelivered}, + source_chain::OnMessagesDelivered, target_chain::{DispatchMessage, MessageDispatch}, LaneId, MessageNonce, }; use bp_runtime::messages::MessageDispatchResult; +pub use bp_xcm_bridge_hub::XcmAsPlainPayload; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; use codec::{Decode, Encode}; use frame_support::{traits::Get, weights::Weight, CloneNoBound, EqNoBound, PartialEqNoBound}; use pallet_bridge_messages::{ - Config as MessagesConfig, OutboundLanesCongestedSignals, Pallet as MessagesPallet, - WeightInfoExt as MessagesPalletWeights, + Config as MessagesConfig, OutboundLanesCongestedSignals, WeightInfoExt as MessagesPalletWeights, }; use scale_info::TypeInfo; use sp_runtime::SaturatedConversion; use sp_std::{fmt::Debug, marker::PhantomData}; use xcm::prelude::*; -use xcm_builder::{DispatchBlob, DispatchBlobError, HaulBlob, HaulBlobError}; - -/// Plain "XCM" payload, which we transfer through bridge -pub type XcmAsPlainPayload = sp_std::prelude::Vec; +use xcm_builder::{DispatchBlob, DispatchBlobError}; /// Message dispatch result type for single message #[derive(CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, Debug, TypeInfo)] @@ -123,6 +120,7 @@ impl< /// A pair of sending chain location and message lane, used by this chain to send messages /// over the bridge. +#[cfg_attr(feature = "std", derive(Debug, Eq, PartialEq))] pub struct SenderAndLane { /// Sending chain relative location. pub location: MultiLocation, @@ -144,8 +142,6 @@ pub trait XcmBlobHauler { type Runtime: MessagesConfig; /// Instance of the messages pallet that is used to send messages. type MessagesInstance: 'static; - /// Returns lane used by this hauler. - type SenderAndLane: Get; /// Actual XCM message sender (`HRMP` or `UMP`) to the source chain /// location (`Self::SenderAndLane::get().location`). @@ -166,54 +162,25 @@ pub trait XcmBlobHauler { /// makes sure that XCM blob is sent to the outbound lane to be relayed. /// /// It needs to be used at the source bridge hub. -pub struct XcmBlobHaulerAdapter(sp_std::marker::PhantomData); +pub struct XcmBlobHaulerAdapter( + sp_std::marker::PhantomData<(XcmBlobHauler, Lanes)>, +); -impl HaulBlob for XcmBlobHaulerAdapter -where - H::Runtime: MessagesConfig, +impl< + H: XcmBlobHauler, + Lanes: Get>, + > OnMessagesDelivered for XcmBlobHaulerAdapter { - fn haul_blob(blob: sp_std::prelude::Vec) -> Result<(), HaulBlobError> { - let sender_and_lane = H::SenderAndLane::get(); - MessagesPallet::::send_message(sender_and_lane.lane, blob) - .map(|artifacts| { - log::info!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - ok: {:?} on lane: {:?}. Enqueued messages: {}", - artifacts.nonce, - sender_and_lane.lane, - artifacts.enqueued_messages, - ); - - // notify XCM queue manager about updated lane state - LocalXcmQueueManager::::on_bridge_message_enqueued( - &sender_and_lane, - artifacts.enqueued_messages, - ); - }) - .map_err(|error| { - log::error!( - target: crate::LOG_TARGET_BRIDGE_DISPATCH, - "haul_blob result - error: {:?} on lane: {:?}", - error, - sender_and_lane.lane, - ); - HaulBlobError::Transport("MessageSenderError") - }) - } -} - -impl OnMessagesDelivered for XcmBlobHaulerAdapter { fn on_messages_delivered(lane: LaneId, enqueued_messages: MessageNonce) { - let sender_and_lane = H::SenderAndLane::get(); - if sender_and_lane.lane != lane { - return + if let Some(sender_and_lane) = + Lanes::get().iter().find(|link| link.0.lane == lane).map(|link| &link.0) + { + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_messages_delivered( + sender_and_lane, + enqueued_messages, + ); } - - // notify XCM queue manager about updated lane state - LocalXcmQueueManager::::on_bridge_messages_delivered( - &sender_and_lane, - enqueued_messages, - ); } } @@ -342,6 +309,28 @@ impl LocalXcmQueueManager { } } +/// Adapter for the implementation of `GetVersion`, which attempts to find the minimal +/// configured XCM version between the destination `dest` and the bridge hub location provided as +/// `Get`. +pub struct XcmVersionOfDestAndRemoteBridge( + sp_std::marker::PhantomData<(Version, RemoteBridge)>, +); +impl> GetVersion + for XcmVersionOfDestAndRemoteBridge +{ + fn get_version_for(dest: &MultiLocation) -> Option { + let dest_version = Version::get_version_for(dest); + let bridge_hub_version = Version::get_version_for(&RemoteBridge::get()); + + match (dest_version, bridge_hub_version) { + (Some(dv), Some(bhv)) => Some(sp_std::cmp::min(dv, bhv)), + (Some(dv), None) => Some(dv), + (None, Some(bhv)) => Some(bhv), + (None, None) => None, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -356,6 +345,9 @@ mod tests { location: MultiLocation::new(1, X1(Parachain(1000))), lane: TEST_LANE_ID, }; + pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorMultiLocation))> = sp_std::vec![ + (TestSenderAndLane::get(), (NetworkId::ByGenesis([0; 32]), InteriorMultiLocation::Here)) + ]; pub DummyXcmMessage: Xcm<()> = Xcm::new(); } @@ -389,37 +381,44 @@ mod tests { impl XcmBlobHauler for TestBlobHauler { type Runtime = TestRuntime; type MessagesInstance = (); - type SenderAndLane = TestSenderAndLane; type ToSourceChainSender = DummySendXcm; type CongestedMessage = DummyXcmMessage; type UncongestedMessage = DummyXcmMessage; } - type TestBlobHaulerAdapter = XcmBlobHaulerAdapter; + type TestBlobHaulerAdapter = XcmBlobHaulerAdapter; - fn fill_up_lane_to_congestion() { + fn fill_up_lane_to_congestion() -> MessageNonce { + let latest_generated_nonce = OUTBOUND_LANE_CONGESTED_THRESHOLD; OutboundLanes::::insert( TEST_LANE_ID, OutboundLaneData { oldest_unpruned_nonce: 0, latest_received_nonce: 0, - latest_generated_nonce: OUTBOUND_LANE_CONGESTED_THRESHOLD, + latest_generated_nonce, }, ); + latest_generated_nonce } #[test] fn congested_signal_is_not_sent_twice() { run_test(|| { - fill_up_lane_to_congestion(); + let enqueued = fill_up_lane_to_congestion(); // next sent message leads to congested signal - TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + LocalXcmQueueManager::::on_bridge_message_enqueued( + &TestSenderAndLane::get(), + enqueued + 1, + ); assert_eq!(DummySendXcm::messages_sent(), 1); // next sent message => we don't sent another congested signal - TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + LocalXcmQueueManager::::on_bridge_message_enqueued( + &TestSenderAndLane::get(), + enqueued, + ); assert_eq!(DummySendXcm::messages_sent(), 1); }); } @@ -427,7 +426,10 @@ mod tests { #[test] fn congested_signal_is_not_sent_when_outbound_lane_is_not_congested() { run_test(|| { - TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + LocalXcmQueueManager::::on_bridge_message_enqueued( + &TestSenderAndLane::get(), + 1, + ); assert_eq!(DummySendXcm::messages_sent(), 0); }); } @@ -435,10 +437,13 @@ mod tests { #[test] fn congested_signal_is_sent_when_outbound_lane_is_congested() { run_test(|| { - fill_up_lane_to_congestion(); + let enqueued = fill_up_lane_to_congestion(); // next sent message leads to congested signal - TestBlobHaulerAdapter::haul_blob(vec![42]).unwrap(); + LocalXcmQueueManager::::on_bridge_message_enqueued( + &TestSenderAndLane::get(), + enqueued + 1, + ); assert_eq!(DummySendXcm::messages_sent(), 1); assert!(LocalXcmQueueManager::::is_congested_signal_sent(TEST_LANE_ID)); }); diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index ffabf7f6e2f84bc43e8ac7e90ac90287c5ec39ca..bd47d37fc07d0ce7ccce84547ed71599bc3a2641 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -376,8 +376,8 @@ impl ChainWithGrandpa for BridgedUnderlyingChain { const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = ""; const MAX_AUTHORITIES_COUNT: u32 = 16; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8; - const MAX_HEADER_SIZE: u32 = 256; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64; + const MAX_MANDATORY_HEADER_SIZE: u32 = 256; + const AVERAGE_HEADER_SIZE: u32 = 64; } impl Chain for BridgedUnderlyingParachain { diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 573edbf5a659ac04c01d61dd1fb7b1c1de95a134..e346f2061e2e59d8cef9075ef164e6b203068a90 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index f238064f92bcacde89971479a501b60469f01691..c1585020be13ca710178b59aefde4a0cde2ab87a 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -15,7 +15,10 @@ // along with Parity Bridges Common. If not, see . use crate::{weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, Error, Pallet}; -use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa}; +use bp_header_chain::{ + justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, + ChainWithGrandpa, GrandpaConsensusLogReader, +}; use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; @@ -169,28 +172,28 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( Weight::zero() }; + // check if the `finality_target` is a mandatory header. If so, we are ready to refund larger + // size + let is_mandatory_finality_target = + GrandpaConsensusLogReader::>::find_scheduled_change( + finality_target.digest(), + ) + .is_some(); + // we can estimate extra call size easily, without any additional significant overhead let actual_call_size: u32 = finality_target .encoded_size() .saturating_add(justification.encoded_size()) .saturated_into(); - let max_expected_call_size = max_expected_call_size::(required_precommits); + let max_expected_call_size = max_expected_submit_finality_proof_arguments_size::( + is_mandatory_finality_target, + required_precommits, + ); let extra_size = actual_call_size.saturating_sub(max_expected_call_size); SubmitFinalityProofInfo { block_number, extra_weight, extra_size } } -/// Returns maximal expected size of `submit_finality_proof` call arguments. -fn max_expected_call_size, I: 'static>(required_precommits: u32) -> u32 { - let max_expected_justification_size = - GrandpaJustification::>::max_reasonable_size::( - required_precommits, - ); - - // call arguments are header and justification - T::BridgedChain::MAX_HEADER_SIZE.saturating_add(max_expected_justification_size) -} - #[cfg(test)] mod tests { use crate::{ diff --git a/bridges/modules/grandpa/src/mock.rs b/bridges/modules/grandpa/src/mock.rs index 7efa84971fe7f319146a62fe5b07d2de0e75e48d..a54f56c4a624951a84e65d8f3b593afa9f661fac 100644 --- a/bridges/modules/grandpa/src/mock.rs +++ b/bridges/modules/grandpa/src/mock.rs @@ -86,8 +86,8 @@ impl ChainWithGrandpa for TestBridgedChain { const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = ""; const MAX_AUTHORITIES_COUNT: u32 = MAX_BRIDGED_AUTHORITIES; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8; - const MAX_HEADER_SIZE: u32 = 256; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64; + const MAX_MANDATORY_HEADER_SIZE: u32 = 256; + const AVERAGE_HEADER_SIZE: u32 = 64; } /// Return test externalities to use in tests. diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index 751ef45168db56228aec8760b0bc6a9f4e28f7e3..4d9371448df8a855db986095d77584c19559379c 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } log = { version = "0.4.20", default-features = false } diff --git a/bridges/modules/messages/src/weights_ext.rs b/bridges/modules/messages/src/weights_ext.rs index aeb3a581a69ee6ebb233ca6ec5e0f0bd4d25a408..c12e04f692bf8304fb58d7c97ec50d1b860ccb56 100644 --- a/bridges/modules/messages/src/weights_ext.rs +++ b/bridges/modules/messages/src/weights_ext.rs @@ -60,7 +60,8 @@ pub fn ensure_weights_are_correct() { // W::receive_messages_delivery_proof_messages_overhead(1).ref_time() may be zero because: // there's no code that iterates over confirmed messages in confirmation transaction assert_eq!(W::receive_messages_delivery_proof_messages_overhead(1).proof_size(), 0); - assert_ne!(W::receive_messages_delivery_proof_relayers_overhead(1).ref_time(), 0); + // W::receive_messages_delivery_proof_relayers_overhead(1).ref_time() may be zero because: + // runtime **can** choose not to pay any rewards to relayers // W::receive_messages_delivery_proof_relayers_overhead(1).proof_size() is an exception // it may or may not cause additional db reads, so proof size may vary assert_ne!(W::storage_proof_size_overhead(1).ref_time(), 0); diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml index 4af8997c5f367653be47de7a5440ae5df3c12754..77a5366c78daedd368d9a4f412075ac803a89530 100644 --- a/bridges/modules/parachains/Cargo.toml +++ b/bridges/modules/parachains/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } log = { version = "0.4.20", default-features = false } diff --git a/bridges/modules/parachains/src/mock.rs b/bridges/modules/parachains/src/mock.rs index d95e76f31086251ac7ee8671e379d55e0afbb8f2..1c7851364d1c047dae3e8e8213708ffc6db9a128 100644 --- a/bridges/modules/parachains/src/mock.rs +++ b/bridges/modules/parachains/src/mock.rs @@ -252,8 +252,8 @@ impl ChainWithGrandpa for TestBridgedChain { const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = ""; const MAX_AUTHORITIES_COUNT: u32 = 16; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8; - const MAX_HEADER_SIZE: u32 = 256; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64; + const MAX_MANDATORY_HEADER_SIZE: u32 = 256; + const AVERAGE_HEADER_SIZE: u32 = 64; } #[derive(Debug)] @@ -283,8 +283,8 @@ impl ChainWithGrandpa for OtherBridgedChain { const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = ""; const MAX_AUTHORITIES_COUNT: u32 = 16; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 8; - const MAX_HEADER_SIZE: u32 = 256; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 64; + const MAX_MANDATORY_HEADER_SIZE: u32 = 256; + const AVERAGE_HEADER_SIZE: u32 = 64; } /// Return test externalities to use in tests. diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 3011a11db5c6bbc339422b3373bb8adff53d7dca..8c8305ef64c9f7e419aead17a9989063ec28d290 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } log = { version = "0.4.20", default-features = false } diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs index 2d74ab38f9dbd1711b62df5e6bebd697fda1b988..00c3814a4c38d9bf0f18b70c0eedc75c239b8ad0 100644 --- a/bridges/modules/relayers/src/benchmarking.rs +++ b/bridges/modules/relayers/src/benchmarking.rs @@ -104,7 +104,7 @@ benchmarks! { // create slash destination account let lane = LaneId([0, 0, 0, 0]); let slash_destination = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); - T::prepare_rewards_account(slash_destination.clone(), Zero::zero()); + T::prepare_rewards_account(slash_destination, Zero::zero()); }: { crate::Pallet::::slash_and_deregister(&relayer, slash_destination) } @@ -121,7 +121,7 @@ benchmarks! { let account_params = RewardsAccountParams::new(lane, *b"test", RewardsAccountOwner::ThisChain); }: { - crate::Pallet::::register_relayer_reward(account_params.clone(), &relayer, One::one()); + crate::Pallet::::register_relayer_reward(account_params, &relayer, One::one()); } verify { assert_eq!(RelayerRewards::::get(relayer, &account_params), Some(One::one())); diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index e4d25fae9d3bcfed1555e1a2798cde54d84050c9..1d84f723ee9d490359c32bef05e9c9547abeb31d 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } log = { version = "0.4.20", default-features = false } diff --git a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs index c4d1e3971e74777668b8bfa4dbcfdb88fbac3779..922e4bf94ba8a947f1fcc2f83db675f539cfc295 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -21,7 +21,7 @@ use crate::{Bridge, Call}; use bp_xcm_bridge_hub_router::{BridgeState, MINIMAL_DELIVERY_FEE_FACTOR}; -use frame_benchmarking::benchmarks_instance_pallet; +use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError}; use frame_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable}; use sp_runtime::traits::Zero; use xcm::prelude::*; @@ -37,11 +37,11 @@ pub trait Config: crate::Config { /// Returns destination which is valid for this router instance. /// (Needs to pass `T::Bridges`) /// Make sure that `SendXcm` will pass. - fn ensure_bridged_target_destination() -> MultiLocation { - MultiLocation::new( + fn ensure_bridged_target_destination() -> Result { + Ok(MultiLocation::new( Self::UniversalLocation::get().len() as u8, X1(GlobalConsensus(Self::BridgedNetworkId::get().unwrap())), - ) + )) } } @@ -61,7 +61,7 @@ benchmarks_instance_pallet! { delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, }); - let _ = T::ensure_bridged_target_destination(); + let _ = T::ensure_bridged_target_destination()?; T::make_congested(); }: { crate::Pallet::::on_initialize(Zero::zero()) @@ -81,7 +81,7 @@ benchmarks_instance_pallet! { } send_message { - let dest = T::ensure_bridged_target_destination(); + let dest = T::ensure_bridged_target_destination()?; let xcm = sp_std::vec![].into(); // make local queue congested, because it means additional db write diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index cf51ef82412fbeb99ed9b1b77b693ebe3a288dd4..229628aedcb8a67f1bc8b652a55fc967f106dbe0 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -89,6 +89,8 @@ pub mod pallet { /// **possible fee**. Allows to externalize better control over allowed **bridged /// networks/locations**. type Bridges: ExporterFor; + /// Checks the XCM version for the destination. + type DestinationVersion: GetVersion; /// Origin of the sibling bridge hub that is allowed to report bridge status. type BridgeHubOrigin: EnsureOrigin; @@ -319,12 +321,13 @@ impl, I: 'static> SendXcm for Pallet { dest: &mut Option, xcm: &mut Option>, ) -> SendResult { - // we won't have an access to `dest` and `xcm` in the `delvier` method, so precompute + // `dest` and `xcm` are required here + let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?; + let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?; + + // we won't have an access to `dest` and `xcm` in the `deliver` method, so precompute // everything required here - let message_size = xcm - .as_ref() - .map(|xcm| xcm.encoded_size() as _) - .ok_or(SendError::MissingArgument)?; + let message_size = xcm_ref.encoded_size() as _; // bridge doesn't support oversized/overweight messages now. So it is better to drop such // messages here than at the bridge hub. Let's check the message size. @@ -332,6 +335,18 @@ impl, I: 'static> SendXcm for Pallet { return Err(SendError::ExceedsMaxMessageSize) } + // We need to ensure that the known `dest`'s XCM version can comprehend the current `xcm` + // program. This may seem like an additional, unnecessary check, but it is not. A similar + // check is probably performed by the `ViaBridgeHubExporter`, which attempts to send a + // versioned message to the sibling bridge hub. However, the local bridge hub may have a + // higher XCM version than the remote `dest`. Once again, it is better to discard such + // messages here than at the bridge hub (e.g., to avoid losing funds). + let destination_version = T::DestinationVersion::get_version_for(dest_ref) + .ok_or(SendError::DestinationUnsupported)?; + let _ = VersionedXcm::from(xcm_ref.clone()) + .into_version(destination_version) + .map_err(|()| SendError::DestinationUnsupported)?; + // just use exporter to validate destination and insert instructions to pay message fee // at the sibling/child bridge hub // @@ -358,6 +373,7 @@ impl, I: 'static> SendXcm for Pallet { #[cfg(test)] mod tests { use super::*; + use frame_support::assert_ok; use mock::*; use frame_support::traits::Hooks; @@ -451,6 +467,19 @@ mod tests { }); } + #[test] + fn destination_unsupported_if_wrap_version_fails() { + run_test(|| { + assert_eq!( + send_xcm::( + UnknownXcmVersionLocation::get(), + vec![ClearOrigin].into(), + ), + Err(SendError::DestinationUnsupported), + ); + }); + } + #[test] fn returns_proper_delivery_price() { run_test(|| { @@ -488,17 +517,14 @@ mod tests { fn sent_message_doesnt_increase_factor_if_xcm_channel_is_uncongested() { run_test(|| { let old_bridge = XcmBridgeHubRouter::bridge(); - assert_eq!( - send_xcm::( - MultiLocation::new( - 2, - X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) - ), - vec![ClearOrigin].into(), - ) - .map(drop), - Ok(()), - ); + assert_ok!(send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); @@ -511,17 +537,14 @@ mod tests { TestWithBridgeHubChannel::make_congested(); let old_bridge = XcmBridgeHubRouter::bridge(); - assert_eq!( - send_xcm::( - MultiLocation::new( - 2, - X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) - ), - vec![ClearOrigin].into(), - ) - .map(drop), - Ok(()), - ); + assert_ok!(send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); assert!( @@ -536,17 +559,14 @@ mod tests { Bridge::::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)); let old_bridge = XcmBridgeHubRouter::bridge(); - assert_eq!( - send_xcm::( - MultiLocation::new( - 2, - X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) - ), - vec![ClearOrigin].into(), - ) - .map(drop), - Ok(()), - ); + assert_ok!(send_xcm::( + MultiLocation::new( + 2, + X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)) + ), + vec![ClearOrigin].into(), + ) + .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); assert!( diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index 2d173ebc0457a941c9f9038a9c1768c367cff362..9079f4b9c4c64e980f5be66e9cd99fe8dd7e20fa 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -19,7 +19,10 @@ use crate as pallet_xcm_bridge_hub_router; use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; -use frame_support::{construct_runtime, derive_impl, parameter_types}; +use frame_support::{ + construct_runtime, derive_impl, parameter_types, + traits::{Contains, Equals}, +}; use frame_system::EnsureRoot; use sp_runtime::{traits::ConstU128, BuildStorage}; use xcm::prelude::*; @@ -58,6 +61,7 @@ parameter_types! { Some((BridgeFeeAsset::get(), BASE_FEE).into()) ) ]; + pub UnknownXcmVersionLocation: MultiLocation = MultiLocation::new(2, X2(GlobalConsensus(BridgedNetworkId::get()), Parachain(9999))); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] @@ -71,6 +75,8 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type UniversalLocation = UniversalLocation; type BridgedNetworkId = BridgedNetworkId; type Bridges = NetworkExportTable; + type DestinationVersion = + LatestOrNoneForLocationVersionChecker>; type BridgeHubOrigin = EnsureRoot; type ToBridgeHubSender = TestToBridgeHubSender; @@ -80,6 +86,18 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type FeeAsset = BridgeFeeAsset; } +pub struct LatestOrNoneForLocationVersionChecker(sp_std::marker::PhantomData); +impl> GetVersion + for LatestOrNoneForLocationVersionChecker +{ + fn get_version_for(dest: &MultiLocation) -> Option { + if Location::contains(dest) { + return None + } + Some(XCM_VERSION) + } +} + pub struct TestToBridgeHubSender; impl TestToBridgeHubSender { diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..061d4b7ced881d6ac7638fdcc0e56bccb08a7be3 --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "pallet-xcm-bridge-hub" +description = "Module that adds dynamic bridges/lanes support to XCM infrastucture at the bridge hub." +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } +log = { version = "0.4.20", default-features = false } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +# Bridge Dependencies +bp-messages = { path = "../../primitives/messages", default-features = false } +bp-runtime = { path = "../../primitives/runtime", default-features = false } +bp-xcm-bridge-hub = { path = "../../primitives/xcm-bridge-hub", default-features = false } +pallet-bridge-messages = { path = "../messages", default-features = false } +bridge-runtime-common = { path = "../../bin/runtime-common", default-features = false } + +# Substrate Dependencies +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-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } + +# Polkadot Dependencies +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 } + +[dev-dependencies] +bp-header-chain = { path = "../../primitives/header-chain" } +pallet-balances = { path = "../../../substrate/frame/balances" } +sp-io = { path = "../../../substrate/primitives/io" } + +[features] +default = ["std"] +std = [ + "bp-messages/std", + "bp-runtime/std", + "bp-xcm-bridge-hub/std", + "bridge-runtime-common/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-bridge-messages/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "bridge-runtime-common/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-bridge-messages/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-bridge-messages/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs new file mode 100644 index 0000000000000000000000000000000000000000..5318b222c5452e05aded6151eb8a43b806c405cb --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -0,0 +1,209 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! The code that allows to use the pallet (`pallet-xcm-bridge-hub`) as XCM message +//! exporter at the sending bridge hub. Internally, it just enqueues outbound blob +//! in the messages pallet queue. +//! +//! This code is executed at the source bridge hub. + +use crate::{Config, Pallet, LOG_TARGET}; + +use bp_messages::source_chain::MessagesBridge; +use bp_xcm_bridge_hub::XcmAsPlainPayload; +use bridge_runtime_common::messages_xcm_extension::{LocalXcmQueueManager, SenderAndLane}; +use pallet_bridge_messages::{Config as BridgeMessagesConfig, Pallet as BridgeMessagesPallet}; +use xcm::prelude::*; +use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter}; +use xcm_executor::traits::ExportXcm; + +/// An easy way to access `HaulBlobExporter`. +pub type PalletAsHaulBlobExporter = HaulBlobExporter< + DummyHaulBlob, + >::BridgedNetwork, + >::DestinationVersion, + >::MessageExportPrice, +>; +/// An easy way to access associated messages pallet. +type MessagesPallet = BridgeMessagesPallet>::BridgeMessagesPalletInstance>; + +impl, I: 'static> ExportXcm for Pallet +where + T: BridgeMessagesConfig< + >::BridgeMessagesPalletInstance, + OutboundPayload = XcmAsPlainPayload, + >, +{ + type Ticket = (SenderAndLane, XcmAsPlainPayload, XcmHash); + + fn validate( + network: NetworkId, + channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> Result<(Self::Ticket, MultiAssets), SendError> { + // Find supported lane_id. + let sender_and_lane = Self::lane_for( + universal_source.as_ref().ok_or(SendError::MissingArgument)?, + (&network, destination.as_ref().ok_or(SendError::MissingArgument)?), + ) + .ok_or(SendError::NotApplicable)?; + + // check if we are able to route the message. We use existing `HaulBlobExporter` for that. + // It will make all required changes and will encode message properly, so that the + // `DispatchBlob` at the bridged bridge hub will be able to decode it + let ((blob, id), price) = PalletAsHaulBlobExporter::::validate( + network, + channel, + universal_source, + destination, + message, + )?; + + Ok(((sender_and_lane, blob, id), price)) + } + + fn deliver( + (sender_and_lane, blob, id): (SenderAndLane, XcmAsPlainPayload, XcmHash), + ) -> Result { + let lane_id = sender_and_lane.lane; + let send_result = MessagesPallet::::send_message(lane_id, blob); + + match send_result { + Ok(artifacts) => { + log::info!( + target: LOG_TARGET, + "XCM message {:?} has been enqueued at bridge {:?} with nonce {}", + id, + lane_id, + artifacts.nonce, + ); + + // notify XCM queue manager about updated lane state + LocalXcmQueueManager::::on_bridge_message_enqueued( + &sender_and_lane, + artifacts.enqueued_messages, + ); + }, + Err(error) => { + log::debug!( + target: LOG_TARGET, + "XCM message {:?} has been dropped because of bridge error {:?} on bridge {:?}", + id, + error, + lane_id, + ); + return Err(SendError::Transport("BridgeSendError")) + }, + } + + Ok(id) + } +} + +/// Dummy implementation of the `HaulBlob` trait that is never called. +/// +/// We are using `HaulBlobExporter`, which requires `HaulBlob` implementation. It assumes that +/// there's a single channel between two bridge hubs - `HaulBlob` only accepts the blob and nothing +/// else. But bridge messages pallet may have a dedicated channel (lane) for every pair of bridged +/// chains. So we are using our own `ExportXcm` implementation, but to utilize `HaulBlobExporter` we +/// still need this `DummyHaulBlob`. +pub struct DummyHaulBlob; + +impl HaulBlob for DummyHaulBlob { + fn haul_blob(_blob: XcmAsPlainPayload) -> Result<(), HaulBlobError> { + Err(HaulBlobError::Transport("DummyHaulBlob")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::*; + use frame_support::assert_ok; + use xcm_executor::traits::export_xcm; + + fn universal_source() -> InteriorMultiLocation { + X2(GlobalConsensus(RelayNetwork::get()), Parachain(SIBLING_ASSET_HUB_ID)) + } + + fn universal_destination() -> InteriorMultiLocation { + BridgedDestination::get() + } + + #[test] + fn export_works() { + run_test(|| { + assert_ok!(export_xcm::( + BridgedRelayNetwork::get(), + 0, + universal_source(), + universal_destination(), + vec![Instruction::ClearOrigin].into(), + )); + }) + } + + #[test] + fn export_fails_if_argument_is_missing() { + run_test(|| { + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut None, + &mut Some(universal_destination()), + &mut Some(Vec::new().into()), + ), + Err(SendError::MissingArgument), + ); + + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut None, + &mut Some(Vec::new().into()), + ), + Err(SendError::MissingArgument), + ); + }) + } + + #[test] + fn exporter_computes_correct_lane_id() { + run_test(|| { + let expected_lane_id = TEST_LANE_ID; + + assert_eq!( + XcmOverBridge::validate( + BridgedRelayNetwork::get(), + 0, + &mut Some(universal_source()), + &mut Some(universal_destination()), + &mut Some(Vec::new().into()), + ) + .unwrap() + .0 + .0 + .lane, + expected_lane_id, + ); + }) + } +} diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..44f6903b018b839fa3f4c97a0ba2c84c7d239c89 --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Module that adds XCM support to bridge pallets. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use bridge_runtime_common::messages_xcm_extension::XcmBlobHauler; +use pallet_bridge_messages::Config as BridgeMessagesConfig; +use xcm::prelude::*; + +pub use exporter::PalletAsHaulBlobExporter; +pub use pallet::*; + +mod exporter; +mod mock; + +/// The target that will be used when publishing logs related to this pallet. +pub const LOG_TARGET: &str = "runtime::bridge-xcm"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use bridge_runtime_common::messages_xcm_extension::SenderAndLane; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: + BridgeMessagesConfig + { + /// Runtime's universal location. + type UniversalLocation: Get; + // TODO: https://github.com/paritytech/parity-bridges-common/issues/1666 remove `ChainId` and + // replace it with the `NetworkId` - then we'll be able to use + // `T as pallet_bridge_messages::Config::BridgedChain::NetworkId` + /// Bridged network as relative location of bridged `GlobalConsensus`. + #[pallet::constant] + type BridgedNetwork: Get; + /// Associated messages pallet instance that bridges us with the + /// `BridgedNetworkId` consensus. + type BridgeMessagesPalletInstance: 'static; + + /// Price of single message export to the bridged consensus (`Self::BridgedNetworkId`). + type MessageExportPrice: Get; + /// Checks the XCM version for the destination. + type DestinationVersion: GetVersion; + + /// Get point-to-point links with bridged consensus (`Self::BridgedNetworkId`). + /// (this will be replaced with dynamic on-chain bridges - `Bridges V2`) + type Lanes: Get>; + /// Support for point-to-point links + /// (this will be replaced with dynamic on-chain bridges - `Bridges V2`) + type LanesSupport: XcmBlobHauler; + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + fn integrity_test() { + assert!( + Self::bridged_network_id().is_some(), + "Configured `T::BridgedNetwork`: {:?} does not contain `GlobalConsensus` junction with `NetworkId`", + T::BridgedNetwork::get() + ) + } + } + + impl, I: 'static> Pallet { + /// Returns dedicated/configured lane identifier. + pub(crate) fn lane_for( + source: &InteriorMultiLocation, + dest: (&NetworkId, &InteriorMultiLocation), + ) -> Option { + let source = source.relative_to(&T::UniversalLocation::get()); + + // Check that we have configured a point-to-point lane for 'source' and `dest`. + T::Lanes::get() + .into_iter() + .find_map(|(lane_source, (lane_dest_network, lane_dest))| { + if lane_source.location == source && + &lane_dest_network == dest.0 && + Self::bridged_network_id().as_ref() == Some(dest.0) && + &lane_dest == dest.1 + { + Some(lane_source) + } else { + None + } + }) + } + + /// Returns some `NetworkId` if contains `GlobalConsensus` junction. + fn bridged_network_id() -> Option { + match T::BridgedNetwork::get().take_first_interior() { + Some(GlobalConsensus(network)) => Some(network), + _ => None, + } + } + } +} diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..8edd4b1f7aa9b7890305302093d462785a64a6f8 --- /dev/null +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -0,0 +1,334 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +#![cfg(test)] + +use crate as pallet_xcm_bridge_hub; + +use bp_messages::{ + source_chain::LaneMessageVerifier, + target_chain::{DispatchMessage, MessageDispatch}, + LaneId, OutboundLaneData, VerificationError, +}; +use bp_runtime::{messages::MessageDispatchResult, Chain, UnderlyingChainProvider}; +use bridge_runtime_common::{ + messages::{ + source::TargetHeaderChainAdapter, target::SourceHeaderChainAdapter, + BridgedChainWithMessages, HashOf, MessageBridge, ThisChainWithMessages, + }, + messages_xcm_extension::{SenderAndLane, XcmBlobHauler}, +}; +use codec::Encode; +use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::RuntimeDbWeight}; +use sp_core::H256; +use sp_runtime::{ + testing::Header as SubstrateHeader, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BuildStorage, +}; +use xcm::prelude::*; + +pub type AccountId = AccountId32; +pub type Balance = u64; + +type Block = frame_system::mocking::MockBlock; + +pub const SIBLING_ASSET_HUB_ID: u32 = 2001; +pub const THIS_BRIDGE_HUB_ID: u32 = 2002; +pub const BRIDGED_ASSET_HUB_ID: u32 = 1001; +pub const TEST_LANE_ID: LaneId = LaneId([0, 0, 0, 1]); + +frame_support::construct_runtime! { + pub enum TestRuntime { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Event}, + Messages: pallet_bridge_messages::{Pallet, Call, Event}, + XcmOverBridge: pallet_xcm_bridge_hub::{Pallet}, + } +} + +parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 }; + pub const ExistentialDeposit: Balance = 1; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for TestRuntime { + type AccountId = AccountId; + type AccountData = pallet_balances::AccountData; + type Block = Block; + type Lookup = IdentityLookup; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; +} + +/// Lane message verifier that is used in tests. +#[derive(Debug, Default)] +pub struct TestLaneMessageVerifier; + +impl LaneMessageVerifier> for TestLaneMessageVerifier { + fn verify_message( + _lane: &LaneId, + _lane_outbound_data: &OutboundLaneData, + _payload: &Vec, + ) -> Result<(), VerificationError> { + Ok(()) + } +} + +parameter_types! { + pub const ActiveOutboundLanes: &'static [LaneId] = &[TEST_LANE_ID]; +} + +impl pallet_bridge_messages::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = TestMessagesWeights; + + type BridgedChainId = (); + type ActiveOutboundLanes = ActiveOutboundLanes; + type MaxUnrewardedRelayerEntriesAtInboundLane = (); + type MaxUnconfirmedMessagesAtInboundLane = (); + type MaximalOutboundPayloadSize = ConstU32<2048>; + type OutboundPayload = Vec; + type InboundPayload = Vec; + type InboundRelayer = (); + type DeliveryPayments = (); + type TargetHeaderChain = TargetHeaderChainAdapter; + type LaneMessageVerifier = TestLaneMessageVerifier; + type DeliveryConfirmationPayments = (); + type OnMessagesDelivered = (); + type SourceHeaderChain = SourceHeaderChainAdapter; + type MessageDispatch = TestMessageDispatch; +} + +pub struct TestMessagesWeights; + +impl pallet_bridge_messages::WeightInfo for TestMessagesWeights { + fn receive_single_message_proof() -> Weight { + Weight::zero() + } + fn receive_single_message_proof_with_outbound_lane_state() -> Weight { + Weight::zero() + } + fn receive_delivery_proof_for_single_message() -> Weight { + Weight::zero() + } + fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { + Weight::zero() + } + fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { + Weight::zero() + } + + fn receive_two_messages_proof() -> Weight { + Weight::zero() + } + + fn receive_single_message_proof_1_kb() -> Weight { + Weight::zero() + } + + fn receive_single_message_proof_16_kb() -> Weight { + Weight::zero() + } + + fn receive_single_message_proof_with_dispatch(_: u32) -> Weight { + Weight::from_parts(1, 0) + } +} + +impl pallet_bridge_messages::WeightInfoExt for TestMessagesWeights { + fn expected_extra_storage_proof_size() -> u32 { + 0 + } + + fn receive_messages_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } + + fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight { + Weight::zero() + } +} + +parameter_types! { + pub const RelayNetwork: NetworkId = NetworkId::Kusama; + pub const BridgedRelayNetwork: NetworkId = NetworkId::Polkadot; + pub const BridgedRelayNetworkLocation: MultiLocation = MultiLocation { + parents: 1, + interior: X1(GlobalConsensus(BridgedRelayNetwork::get())) + }; + pub const NonBridgedRelayNetwork: NetworkId = NetworkId::Rococo; + pub const BridgeReserve: Balance = 100_000; + pub UniversalLocation: InteriorMultiLocation = X2( + GlobalConsensus(RelayNetwork::get()), + Parachain(THIS_BRIDGE_HUB_ID), + ); + pub const Penalty: Balance = 1_000; +} + +impl pallet_xcm_bridge_hub::Config for TestRuntime { + type UniversalLocation = UniversalLocation; + type BridgedNetwork = BridgedRelayNetworkLocation; + type BridgeMessagesPalletInstance = (); + + type MessageExportPrice = (); + type DestinationVersion = AlwaysLatest; + + type Lanes = TestLanes; + type LanesSupport = TestXcmBlobHauler; +} + +parameter_types! { + pub TestSenderAndLane: SenderAndLane = SenderAndLane { + location: MultiLocation::new(1, X1(Parachain(SIBLING_ASSET_HUB_ID))), + lane: TEST_LANE_ID, + }; + pub const BridgedDestination: InteriorMultiLocation = X1( + Parachain(BRIDGED_ASSET_HUB_ID) + ); + pub TestLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorMultiLocation))> = sp_std::vec![ + (TestSenderAndLane::get(), (BridgedRelayNetwork::get(), BridgedDestination::get())) + ]; +} + +pub struct TestXcmBlobHauler; +impl XcmBlobHauler for TestXcmBlobHauler { + type Runtime = TestRuntime; + type MessagesInstance = (); + type ToSourceChainSender = (); + type CongestedMessage = (); + type UncongestedMessage = (); +} + +pub struct ThisChain; + +impl Chain for ThisChain { + type BlockNumber = u64; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = SubstrateHeader; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = u64; + type Signature = sp_runtime::MultiSignature; + + fn max_extrinsic_size() -> u32 { + u32::MAX + } + + fn max_extrinsic_weight() -> Weight { + Weight::MAX + } +} + +pub struct BridgedChain; +pub type BridgedHeaderHash = H256; +pub type BridgedChainHeader = SubstrateHeader; + +impl Chain for BridgedChain { + type BlockNumber = u64; + type Hash = BridgedHeaderHash; + type Hasher = BlakeTwo256; + type Header = BridgedChainHeader; + type AccountId = AccountId; + type Balance = Balance; + type Nonce = u64; + type Signature = sp_runtime::MultiSignature; + + fn max_extrinsic_size() -> u32 { + 4096 + } + + fn max_extrinsic_weight() -> Weight { + Weight::MAX + } +} + +/// Test message dispatcher. +pub struct TestMessageDispatch; + +impl TestMessageDispatch { + pub fn deactivate(lane: LaneId) { + frame_support::storage::unhashed::put(&(b"inactive", lane).encode()[..], &false); + } +} + +impl MessageDispatch for TestMessageDispatch { + type DispatchPayload = Vec; + type DispatchLevelResult = (); + + fn is_active() -> bool { + frame_support::storage::unhashed::take::(&(b"inactive").encode()[..]) != Some(false) + } + + fn dispatch_weight(_message: &mut DispatchMessage) -> Weight { + Weight::zero() + } + + fn dispatch( + _: DispatchMessage, + ) -> MessageDispatchResult { + MessageDispatchResult { unspent_weight: Weight::zero(), dispatch_level_result: () } + } +} + +pub struct WrappedThisChain; +impl UnderlyingChainProvider for WrappedThisChain { + type Chain = ThisChain; +} +impl ThisChainWithMessages for WrappedThisChain { + type RuntimeOrigin = RuntimeOrigin; +} + +pub struct WrappedBridgedChain; +impl UnderlyingChainProvider for WrappedBridgedChain { + type Chain = BridgedChain; +} +impl BridgedChainWithMessages for WrappedBridgedChain {} + +pub struct BridgedHeaderChain; +impl bp_header_chain::HeaderChain for BridgedHeaderChain { + fn finalized_header_state_root( + _hash: HashOf, + ) -> Option> { + unreachable!() + } +} + +/// Bridge that is deployed on `ThisChain` and allows sending/receiving messages to/from +/// `BridgedChain`. +#[derive(Debug, PartialEq, Eq)] +pub struct OnThisChainBridge; + +impl MessageBridge for OnThisChainBridge { + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = ""; + + type ThisChain = WrappedThisChain; + type BridgedChain = WrappedBridgedChain; + type BridgedHeaderChain = BridgedHeaderChain; +} + +/// Run pallet test. +pub fn run_test(test: impl FnOnce() -> T) -> T { + sp_io::TestExternalities::new( + frame_system::GenesisConfig::::default().build_storage().unwrap(), + ) + .execute_with(test) +} diff --git a/bridges/primitives/chain-asset-hub-rococo/Cargo.toml b/bridges/primitives/chain-asset-hub-rococo/Cargo.toml index 889475840b325b25ce05fa044c6a0c375bced378..d5f724e581fbf44c237b4634863f0d9c2437837b 100644 --- a/bridges/primitives/chain-asset-hub-rococo/Cargo.toml +++ b/bridges/primitives/chain-asset-hub-rococo/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/bridges/primitives/chain-asset-hub-westend/Cargo.toml b/bridges/primitives/chain-asset-hub-westend/Cargo.toml index 84b9604a61b506bcd0010e326f34bf1008280d16..d309e50bfbfeafbf0e32103695e004bd1bf0f7a8 100644 --- a/bridges/primitives/chain-asset-hub-westend/Cargo.toml +++ b/bridges/primitives/chain-asset-hub-westend/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml b/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml index dab1b065f6fa6202e7014933ee5ba7b2d8939427..73aaa53269fee9ae6e50c240043cc125fcf44a74 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-cumulus/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml b/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml index 8e6364101f2237e3293eae969e69211427048f25..ea09712ae304738ec1b468317ee5aa28eb1d0cca 100644 --- a/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-kusama/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml b/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml index 961d4aeb2e2b75e2855bbb9e58c3598bc6ccb3e9..de208895fb4362c59705a6d1f9911f534d0d3669 100644 --- a/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-polkadot/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml b/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml index 28fe3c283bcdcd51fde8e105b7ab6b8dfb80bb53..281e1f7426178c1c1924ca40e382ea63f7a75963 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-rococo/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs index 59d293edf1c249a10394c1a0b6f0e7fb8fb0ebcf..f79b8a8afb32173a12f7b02e93f8df7060478d71 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -76,6 +76,8 @@ pub const WITH_BRIDGE_HUB_ROCOCO_RELAYERS_PALLET_NAME: &str = "BridgeRelayers"; /// Pallet index of `BridgeWestendMessages: pallet_bridge_messages::`. pub const WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX: u8 = 51; +/// Pallet index of `BridgePolkadotBulletinMessages: pallet_bridge_messages::`. +pub const WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX: u8 = 61; decl_bridge_finality_runtime_apis!(bridge_hub_rococo); decl_bridge_messages_runtime_apis!(bridge_hub_rococo); @@ -84,13 +86,13 @@ 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 = 1628875538; + pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 1_640_102_205; /// 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%`) - pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 6417262881; + pub const BridgeHubRococoBaseDeliveryFeeInRocs: u128 = 5_651_581_649; /// 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 = 6159996668; + pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 4_045_736_577; } diff --git a/bridges/primitives/chain-bridge-hub-westend/Cargo.toml b/bridges/primitives/chain-bridge-hub-westend/Cargo.toml index 409f84840a88ff56cb578cfbd3a89be74c969e1f..beebfa8f1a04a8b46b0529218d0f18fb7b649f6e 100644 --- a/bridges/primitives/chain-bridge-hub-westend/Cargo.toml +++ b/bridges/primitives/chain-bridge-hub-westend/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs index 0124e05bf8871c35d68a64f00d9b34719b900c66..f4524f719f9fda3643a43a5c9509d989e4f4a777 100644 --- a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs @@ -78,13 +78,13 @@ 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 = 488662666666; + pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 492_077_333_333; /// 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%`) - pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1925196628010; + pub const BridgeHubWestendBaseDeliveryFeeInWnds: u128 = 1_695_489_961_344; /// Transaction fee that is paid at the Westend BridgeHub for delivering single outbound message confirmation. /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1848016628010; + pub const BridgeHubWestendBaseConfirmationFeeInWnds: u128 = 1_618_309_961_344; } diff --git a/bridges/primitives/chain-kusama/Cargo.toml b/bridges/primitives/chain-kusama/Cargo.toml index 41570d4f9bcba3a29487c2a4d7fdc1826c0aaf07..6ca4f051f1c15d8794ed50626588a72093206038 100644 --- a/bridges/primitives/chain-kusama/Cargo.toml +++ b/bridges/primitives/chain-kusama/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs index d5748aa132cea6caddeabd102b62345bbdc6153f..5f089fbc589f6de3921d30ea47e05aebc9762992 100644 --- a/bridges/primitives/chain-kusama/src/lib.rs +++ b/bridges/primitives/chain-kusama/src/lib.rs @@ -52,8 +52,8 @@ impl ChainWithGrandpa for Kusama { const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } // The SignedExtension used by Kusama. diff --git a/bridges/primitives/chain-polkadot-bulletin/Cargo.toml b/bridges/primitives/chain-polkadot-bulletin/Cargo.toml index 3be056dd0a7d4295731ca5897441a217d59b3081..98633847462e4653a57cd1a282b74e43cc253511 100644 --- a/bridges/primitives/chain-polkadot-bulletin/Cargo.toml +++ b/bridges/primitives/chain-polkadot-bulletin/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs index fcc6e90eb1b298e703b7c4b1a83c914f0a018031..fe82c9644b6735393ab55f6053e5d35d963d36d9 100644 --- a/bridges/primitives/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/primitives/chain-polkadot-bulletin/src/lib.rs @@ -42,8 +42,8 @@ use sp_runtime::{traits::DispatchInfoOf, transaction_validity::TransactionValidi // This chain reuses most of Polkadot primitives. pub use bp_polkadot_core::{ AccountAddress, AccountId, Balance, Block, BlockNumber, Hash, Hasher, Header, Nonce, Signature, - SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE_IN_JUSTIFICATION, - EXTRA_STORAGE_PROOF_SIZE, MAX_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY, + SignedBlock, UncheckedExtrinsic, AVERAGE_HEADER_SIZE, EXTRA_STORAGE_PROOF_SIZE, + MAX_MANDATORY_HEADER_SIZE, REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY, }; /// Maximal number of GRANDPA authorities at Polkadot Bulletin chain. @@ -207,8 +207,8 @@ impl ChainWithGrandpa for PolkadotBulletin { const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa); diff --git a/bridges/primitives/chain-polkadot/Cargo.toml b/bridges/primitives/chain-polkadot/Cargo.toml index 579e997e0ab010a508a58cf747c69f86ea8a4575..361901b7ae09c121d27329b2f2a238dc61dd7f2e 100644 --- a/bridges/primitives/chain-polkadot/Cargo.toml +++ b/bridges/primitives/chain-polkadot/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs index 61c8ca927d807ac6c169bb30d324f9720118e010..9a5b8970accb2338db542c91300ca8568c79cd65 100644 --- a/bridges/primitives/chain-polkadot/src/lib.rs +++ b/bridges/primitives/chain-polkadot/src/lib.rs @@ -52,8 +52,8 @@ impl ChainWithGrandpa for Polkadot { const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } /// The SignedExtension used by Polkadot. diff --git a/bridges/primitives/chain-rococo/Cargo.toml b/bridges/primitives/chain-rococo/Cargo.toml index dc7c482a4cc50fc66bc7bc9a739f8b7e238b8def..d59a00cfd147d4e5d3110e44cab906a72836a06a 100644 --- a/bridges/primitives/chain-rococo/Cargo.toml +++ b/bridges/primitives/chain-rococo/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs index 5436ad846468cda632aab9ffcf46748f4c1546f8..7f3e762715f3283d83fbdc91b0e69704071b55ee 100644 --- a/bridges/primitives/chain-rococo/src/lib.rs +++ b/bridges/primitives/chain-rococo/src/lib.rs @@ -52,8 +52,8 @@ impl ChainWithGrandpa for Rococo { const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } parameter_types! { diff --git a/bridges/primitives/chain-westend/Cargo.toml b/bridges/primitives/chain-westend/Cargo.toml index 7c74cb1361bc3924fbe92bac73efac75d2729ba5..6b6d2748aff7411d5247c8336aa2ac1251d11ea8 100644 --- a/bridges/primitives/chain-westend/Cargo.toml +++ b/bridges/primitives/chain-westend/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # Bridge Dependencies diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs index 45c13d600601fae14f48bcfb61dda225682f300a..7fa5e140d5707eb761ae5408fae729de43c1827e 100644 --- a/bridges/primitives/chain-westend/src/lib.rs +++ b/bridges/primitives/chain-westend/src/lib.rs @@ -52,8 +52,8 @@ impl ChainWithGrandpa for Westend { const MAX_AUTHORITIES_COUNT: u32 = MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = AVERAGE_HEADER_SIZE; } parameter_types! { diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index bc92054e5dc8aa40250f52db87129e360e7f4218..7338996d69f2231f9f1d9c576a1a245263ef414a 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } diff --git a/bridges/primitives/header-chain/src/justification/mod.rs b/bridges/primitives/header-chain/src/justification/mod.rs index 72a5f68918d9703babe1e9c263f9148c57df4340..b32d8bdb5f1d8ce05722c938a083d7f582139835 100644 --- a/bridges/primitives/header-chain/src/justification/mod.rs +++ b/bridges/primitives/header-chain/src/justification/mod.rs @@ -82,8 +82,8 @@ impl GrandpaJustification { .saturating_add(BlockNumberOf::::max_encoded_len().saturated_into()) .saturating_add(HashOf::::max_encoded_len().saturated_into()); - let max_expected_votes_ancestries_size = C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY - .saturating_mul(C::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION); + let max_expected_votes_ancestries_size = + C::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY.saturating_mul(C::AVERAGE_HEADER_SIZE); // justification is round number (u64=8b), a signed GRANDPA commit and the // `votes_ancestries` vector diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index d2c7ec0759e884713cb37a871bbcc65cabd256af..1459b1c1994bcd867cb0bc4aaeeb3983d3102be8 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -266,23 +266,28 @@ pub trait ChainWithGrandpa: Chain { /// to submitter. const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32; - /// Maximal size of the chain header. The header may be the header that enacts new GRANDPA - /// authorities set (so it has large digest inside). + /// Maximal size of the mandatory chain header. Mandatory header is the header that enacts new + /// GRANDPA authorities set (so it has large digest inside). /// /// This isn't a strict limit. The relay may submit larger headers and the pallet will accept /// the call. The limit is only used to compute maximal refund amount and doing calls which /// exceed the limit, may be costly to submitter. - const MAX_HEADER_SIZE: u32; + const MAX_MANDATORY_HEADER_SIZE: u32; - /// Average size of the chain header from justification ancestry. We don't expect to see there - /// headers that change GRANDPA authorities set (GRANDPA will probably be able to finalize at - /// least one additional header per session on non test chains), so this is average size of - /// headers that aren't changing the set. + /// Average size of the chain header. We don't expect to see there headers that change GRANDPA + /// authorities set (GRANDPA will probably be able to finalize at least one additional header + /// per session on non test chains), so this is average size of headers that aren't changing the + /// set. /// - /// This isn't a strict limit. The relay may submit justifications with larger headers in its - /// ancestry and the pallet will accept the call. The limit is only used to compute maximal - /// refund amount and doing calls which exceed the limit, may be costly to submitter. - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32; + /// This isn't a strict limit. The relay may submit justifications with larger headers and the + /// pallet will accept the call. However, if the total size of all `submit_finality_proof` + /// arguments exceeds the maximal size, computed using this average size, relayer will only get + /// partial refund. + /// + /// We expect some headers on production chains that are above this size. But they are rare and + /// if rellayer cares about its profitability, we expect it'll select other headers for + /// submission. + const AVERAGE_HEADER_SIZE: u32; } impl ChainWithGrandpa for T @@ -295,7 +300,67 @@ where const MAX_AUTHORITIES_COUNT: u32 = ::MAX_AUTHORITIES_COUNT; const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = ::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY; - const MAX_HEADER_SIZE: u32 = ::MAX_HEADER_SIZE; - const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = - ::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION; + const MAX_MANDATORY_HEADER_SIZE: u32 = + ::MAX_MANDATORY_HEADER_SIZE; + const AVERAGE_HEADER_SIZE: u32 = ::AVERAGE_HEADER_SIZE; +} + +/// Returns maximal expected size of `submit_finality_proof` call arguments. +pub fn max_expected_submit_finality_proof_arguments_size( + is_mandatory_finality_target: bool, + precommits: u32, +) -> u32 { + let max_expected_justification_size = + GrandpaJustification::>::max_reasonable_size::(precommits); + + // call arguments are header and justification + let max_expected_finality_target_size = if is_mandatory_finality_target { + C::MAX_MANDATORY_HEADER_SIZE + } else { + C::AVERAGE_HEADER_SIZE + }; + max_expected_finality_target_size.saturating_add(max_expected_justification_size) +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::weights::Weight; + use sp_runtime::{testing::H256, traits::BlakeTwo256, MultiSignature}; + + struct TestChain; + + impl Chain for TestChain { + type BlockNumber = u32; + type Hash = H256; + type Hasher = BlakeTwo256; + type Header = sp_runtime::generic::Header; + type AccountId = u64; + type Balance = u64; + type Nonce = u64; + type Signature = MultiSignature; + + fn max_extrinsic_size() -> u32 { + 0 + } + fn max_extrinsic_weight() -> Weight { + Weight::zero() + } + } + + impl ChainWithGrandpa for TestChain { + const WITH_CHAIN_GRANDPA_PALLET_NAME: &'static str = "Test"; + const MAX_AUTHORITIES_COUNT: u32 = 128; + const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 2; + const MAX_MANDATORY_HEADER_SIZE: u32 = 100_000; + const AVERAGE_HEADER_SIZE: u32 = 1_024; + } + + #[test] + fn max_expected_submit_finality_proof_arguments_size_respects_mandatory_argument() { + assert!( + max_expected_submit_finality_proof_arguments_size::(true, 100) > + max_expected_submit_finality_proof_arguments_size::(false, 100), + ); + } } diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index c2f43523aaf8e1151150d4f51b2246e2b97478da..6333000a71ae8c7e0817362c03da0d17fc5739e6 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +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"] } diff --git a/bridges/primitives/parachains/Cargo.toml b/bridges/primitives/parachains/Cargo.toml index a339203fd66f70621ada6c1006825b1224bf0bc1..99b447f6c0aa92d9613aa6241c672e3a63808c72 100644 --- a/bridges/primitives/parachains/Cargo.toml +++ b/bridges/primitives/parachains/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2" diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index 67fb9af8b213e2caba011f07b919084075c323d1..80382b3289faf94eed3adc58cb0500b4ee8d47be 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } parity-util-mem = { version = "0.12.0", optional = true } diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs index af39b5ab9babae2b2e6858bff83eaf8c29ef74bb..586cbf8cb9b47dffe66ea683306f41d31b7aa83a 100644 --- a/bridges/primitives/polkadot-core/src/lib.rs +++ b/bridges/primitives/polkadot-core/src/lib.rs @@ -64,30 +64,28 @@ pub const MAX_AUTHORITIES_COUNT: u32 = 1_256; /// /// See [`bp-header-chain::ChainWithGrandpa`] for more details. /// -/// This value comes from recent (February, 2023) Kusama and Polkadot headers. There are no +/// This value comes from recent (December, 2023) Kusama and Polkadot headers. There are no /// justifications with any additional headers in votes ancestry, so reasonable headers may /// be set to zero. But we assume that there may be small GRANDPA lags, so we're leaving some /// reserve here. pub const REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY: u32 = 2; -/// Approximate average header size in `votes_ancestries` field of justification on Polkadot-like +/// Average header size in `votes_ancestries` field of justification on Polkadot-like /// chains. /// /// See [`bp-header-chain::ChainWithGrandpa`] for more details. /// -/// This value comes from recent (February, 2023) Kusama headers. Average is `336` there, but some -/// non-mandatory headers has size `40kb` (they contain the BABE epoch descriptor with all -/// authorities - just like our mandatory header). Since we assume `2` headers in justification -/// votes ancestry, let's set average header to `40kb / 2`. -pub const AVERAGE_HEADER_SIZE_IN_JUSTIFICATION: u32 = 20 * 1024; +/// This value comes from recent (December, 2023) Kusama headers. Most of headers are `327` bytes +/// there, but let's have some reserve and make it 1024. +pub const AVERAGE_HEADER_SIZE: u32 = 1024; /// Approximate maximal header size on Polkadot-like chains. /// /// See [`bp-header-chain::ChainWithGrandpa`] for more details. /// -/// This value comes from recent (February, 2023) Kusama headers. Maximal header is a mandatory -/// header. In its SCALE-encoded form it is `80348` bytes. Let's have some reserve here. -pub const MAX_HEADER_SIZE: u32 = 90_000; +/// This value comes from recent (December, 2023) Kusama headers. Maximal header is a mandatory +/// header. In its SCALE-encoded form it is `113407` bytes. Let's have some reserve here. +pub const MAX_MANDATORY_HEADER_SIZE: u32 = 120 * 1024; /// Number of extra bytes (excluding size of storage value itself) of storage proof, built at /// Polkadot-like chain. This mostly depends on number of entries in the storage trie. diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml index cf94ca44d00c25a2573fa4d62aeffc6c32df12bf..563d27c91c9eb9ac45c54784961d494b6f66b616 100644 --- a/bridges/primitives/relayers/Cargo.toml +++ b/bridges/primitives/relayers/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +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"] } diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index a713f636bb842ca2eac0962def7a200e866552ea..779030b5278ad2fd1d14352ede6e1c31e2087bca 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } hash-db = { version = "0.16.0", default-features = false } diff --git a/bridges/primitives/runtime/src/chain.rs b/bridges/primitives/runtime/src/chain.rs index b78023efb1b863780dff63f98ab13adf89dfa968..81a2070bece5f471eccbf61ab971d604becc9041 100644 --- a/bridges/primitives/runtime/src/chain.rs +++ b/bridges/primitives/runtime/src/chain.rs @@ -15,7 +15,7 @@ // along with Parity Bridges Common. If not, see . use crate::HeaderIdProvider; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{weights::Weight, Parameter}; use num_traits::{AsPrimitive, Bounded, CheckedSub, Saturating, SaturatingAdd, Zero}; use sp_runtime::{ @@ -39,7 +39,7 @@ pub enum EncodedOrDecodedCall { Decoded(ChainCall), } -impl EncodedOrDecodedCall { +impl EncodedOrDecodedCall { /// Returns decoded call. pub fn to_decoded(&self) -> Result { match self { @@ -57,6 +57,14 @@ impl EncodedOrDecodedCall { Self::Decoded(decoded_call) => Ok(decoded_call), } } + + /// Converts self to encoded call. + pub fn into_encoded(self) -> Vec { + match self { + Self::Encoded(encoded_call) => encoded_call, + Self::Decoded(decoded_call) => decoded_call.encode(), + } + } } impl From for EncodedOrDecodedCall { @@ -191,7 +199,7 @@ pub trait Chain: Send + Sync + 'static { } /// A trait that provides the type of the underlying chain. -pub trait UnderlyingChainProvider { +pub trait UnderlyingChainProvider: Send + Sync + 'static { /// Underlying chain type. type Chain: Chain; } diff --git a/bridges/primitives/test-utils/Cargo.toml b/bridges/primitives/test-utils/Cargo.toml index 050c879c6a7fa3d37d1c163e121ba7ed61d9f16e..3ccec9d9033d782d73631a7594b98f8e38b61461 100644 --- a/bridges/primitives/test-utils/Cargo.toml +++ b/bridges/primitives/test-utils/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] bp-header-chain = { path = "../header-chain", default-features = false } bp-parachains = { path = "../parachains", default-features = false } diff --git a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml index 5a49db62fec1ff4dd43a1d32054bfbf480dc9126..fa537bda960a4ac2b14c974df1e37fa9e6095489 100644 --- a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +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"] } diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f9f44fd0f8d974e7aea66477ab818d8f96595031 --- /dev/null +++ b/bridges/primitives/xcm-bridge-hub/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bp-xcm-bridge-hub" +description = "Primitives of the xcm-bridge-hub pallet." +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" + +[lints] +workspace = true + +[dependencies] + +# Substrate Dependencies +sp-std = { path = "../../../substrate/primitives/std", default-features = false } + +[features] +default = ["std"] +std = ["sp-std/std"] diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9745011c902d2c3949b81886c872f438678a11b8 --- /dev/null +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -0,0 +1,24 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of Parity Bridges Common. + +// Parity Bridges Common 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. + +// Parity Bridges Common 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 Parity Bridges Common. If not, see . + +//! Primitives of the xcm-bridge-hub pallet. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +/// Encoded XCM blob. We expect the bridge messages pallet to use this blob type for both inbound +/// and outbound payloads. +pub type XcmAsPlainPayload = sp_std::vec::Vec; diff --git a/bridges/snowbridge/LICENSE b/bridges/snowbridge/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/bridges/snowbridge/LICENSE @@ -0,0 +1,201 @@ + 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/README.md b/bridges/snowbridge/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a38910da3164e853f54b284f8d38795d4220aafe --- /dev/null +++ b/bridges/snowbridge/README.md @@ -0,0 +1,127 @@ +# 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/LICENSE b/bridges/snowbridge/parachain/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/bridges/snowbridge/parachain/LICENSE @@ -0,0 +1,201 @@ + 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 new file mode 100644 index 0000000000000000000000000000000000000000..ddcbedab0c635983dff18fb00d207a9408d353db --- /dev/null +++ b/bridges/snowbridge/parachain/README.md @@ -0,0 +1,155 @@ +# Parachain modules + +## Configuration + +Note: This section is not necessary for local development, as there are scripts to auto-configure the parachain in the +[test directory](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test). + +For a fully operational chain, further configuration of the initial chain spec is required. The specific configuration will +depend heavily on your environment, so this guide will remain high-level. + +After completing a release build of the parachain, build an initial spec for the snowbase runtime: + +```bash +target/release/snowbridge build-spec --chain snowbase --disable-default-bootnode > spec.json +``` + +Now edit the spec and configure the following: +1. Recently finalized ethereum header and difficulty for the ethereum light client +2. Contract addresses for the Ether, Erc20, and Dot apps. +3. Authorized principal for the basic channel + +For an example configuration, consult the [setup script](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/scripts/start-services.sh) +for our local development stack. Specifically the `start_polkadot_launch` bash function. + +## Tests + +To run the parachain tests locally, use `cargo test --workspace`. For the full suite of tests, use +`cargo test --workspace --features runtime-benchmarks`. + +Optionally exclude the top-level and runtime crates: + +```bash +cargo test --workspace \ + --features runtime-benchmarks \ + --exclude snowbridge \ + --exclude snowbridge-runtime \ + --exclude snowblink-runtime \ + --exclude snowbase-runtime +``` + +### Updating test data for inbound channel unit tests + +To regenerate the test data, use a test with multiple `submit` calls in `ethereum/test/test_basic_outbound_channel.js`, eg. +"should increment nonces correctly". + +Add the following preamble: + +```javascript +const rlp = require("rlp"); +const contract = BasicOutboundChannel; +const signature = 'Message(address,address,uint64,uint64,bytes)'; +``` + +For each encoded log you want to create, find a transaction object `tx` returned from a `submit` call and run this: + +```javascript +const rawLog = tx.receipt.rawLogs[0]; +const encodedLog = rlp.encode([rawLog.address, rawLog.topics, rawLog.data]).toString("hex"); +console.log(`encodedLog: ${encodedLog}`); +const iface = new ethers.utils.Interface(contract.abi); +const decodedEventLog = iface.decodeEventLog( + signature, + rawLog.data, + rawLog.topics, +); +console.log(`decoded rawLog.data: ${JSON.stringify(decodedEventLog)}`); +``` + +Place the `encodedLog` string in the `message.data` field in the test data. Use the `decoded rawLog.data` field to +update the comments with the decoded log data. + +## Generating pallet weights from benchmarks + +Build the parachain with the runtime benchmark flags for the chosen runtime: + +```bash +runtime=snowbase +cargo build \ + --release \ + --no-default-features \ + --features "$runtime-native,rococo-native,runtime-benchmarks,$runtime-runtime-benchmarks" \ + --bin snowbridge +``` + +List available pallets and their benchmarks: + +```bash +./target/release/snowbridge benchmark pallet --chain $runtime --list +``` + +Run a benchmark for a pallet, generating weights: + +```bash +target/release/snowbridge benchmark pallet \ + --chain=$runtime \ + --execution=wasm \ + --wasm-execution=compiled \ + --pallet=basic_channel_inbound \ + --extra \ + --extrinsic=* \ + --repeat=20 \ + --steps=50 \ + --output=pallets/basic-channel/src/inbound/weights.rs \ + --template=templates/module-weight-template.hbs +``` + +## Generating beacon test fixtures and benchmarking data + +### Minimal Spec + +To generate `minimal` test data and benchmarking data, make sure to start the local E2E setup to spin up a local beacon +node instance to connect to: + +```bash +cd web/packages/test +./scripts/start-services.sh +``` + +Wait for output `Testnet has been initialized`. + +In a separate terminal, from the `snowbridge` directory, run: + +```bash +mage -d relayer build && relayer/build/snowbridge-relay generate-beacon-data --spec "minimal" && cd parachain && +cargo +nightly fmt -- --config-path rustfmt.toml && cd - +``` + +### Mainnet Spec + +We only use the mainnet spec for generating fixtures for pallet weight benchmarks. + +To generate the data we can connect to the Lodestar Goerli public node. The script already connects to the Lodestar node, +so no need to start up additional services. In the event of the Lodestar node not being available, you can start up your +own stack with these commands: + +```bash +cd web/packages/test +./scripts/start-goerli.sh +``` + +From the `snowbridge` directory, run: + +```bash +mage -d relayer build && relayer/build/snowbridge-relay generate-beacon-data --spec "mainnet" && cd parachain && +cargo +nightly fmt -- --config-path rustfmt.toml && cd - +``` + +### Benchmarking tests + +To run the benchmark tests + +```bash +cd parachain/pallets/ethereum-beacon-client +cargo test --release --features runtime-benchmarks +``` diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/Cargo.toml b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..db3be5f91b8e85dda02ae3982ad8d63d3c3c276e --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/Cargo.toml @@ -0,0 +1,95 @@ +[package] +name = "snowbridge-ethereum-beacon-client" +description = "Snowbridge Beacon Client Pallet" +version = "0.0.1" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.195", optional = true } +serde_json = { version = "1.0.111", optional = 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 } +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 } + +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 } +primitives = { package = "snowbridge-beacon-primitives", path = "../../primitives/beacon", default-features = false } +static_assertions = { version = "1.1.0", default-features = false } +bp-runtime = { path = "../../../../../bridges/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" +hex-literal = "0.4.1" +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp" } +sp-io = { path = "../../../../../substrate/primitives/io" } +serde = "1.0.195" + +[features] +default = ["std"] +fuzzing = [ + "hex-literal", + "pallet-timestamp", + "serde", + "serde_json", + "sp-io", +] +std = [ + "bp-runtime/std", + "byte-slice-cast/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-timestamp/std", + "primitives/std", + "rlp/std", + "scale-info/std", + "serde", + "snowbridge-core/std", + "snowbridge-ethereum/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "ssz_rs/std", + 'frame-benchmarking/std', +] +runtime-benchmarks = [ + "beacon-spec-mainnet", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-timestamp?/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-timestamp?/try-runtime", + "sp-runtime/try-runtime", +] +beacon-spec-mainnet = [] diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/benchmark.md b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/benchmark.md new file mode 100644 index 0000000000000000000000000000000000000000..de976e121496b773fcdb9a55c1ae77f9de86542f --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/benchmark.md @@ -0,0 +1,88 @@ +# Motivation +Demonstrate that +[FastAggregateVerify](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.3.4) is the most +expensive call in ethereum beacon light client, though in [#13031](https://github.com/paritytech/substrate/pull/13031) +Parity team has wrapped some low level host functions for `bls-12381` but adding a high level host function specific +for it is super helpful. + +# Benchmark +We add several benchmarks +[here](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs#L98-L124) +as following to demonstrate +[bls_fast_aggregate_verify](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-beacon-client/src/lib.rs#L764) +is the main bottleneck. Test data +[here](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-beacon-client/src/benchmarking/data_mainnet.rs#L553-L1120) +is real from goerli network which contains 512 public keys from sync committee. + +## sync_committee_period_update +Base line benchmark for extrinsic [sync_committee_period_update](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-beacon-client/src/lib.rs#L233) + +## bls_fast_aggregate_verify +Subfunction of extrinsic `sync_committee_period_update` which does what +[FastAggregateVerify](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.3.4) requires. + +## bls_aggregate_pubkey +Subfunction of `bls_fast_aggregate_verify` which decompress and instantiate G1 pubkeys only. + +## bls_verify_message +Subfunction of `bls_fast_aggregate_verify` which verify the prepared signature only. + + +# Result + +## hardware spec +Run benchmark in a EC2 instance +``` +cargo run --release --bin polkadot-parachain --features runtime-benchmarks -- benchmark machine --base-path /mnt/scratch/benchmark + ++----------+----------------+-------------+-------------+-------------------+ +| Category | Function | Score | Minimum | Result | ++===========================================================================+ +| CPU | BLAKE2-256 | 1.08 GiBs | 1.00 GiBs | ✅ Pass (107.5 %) | +|----------+----------------+-------------+-------------+-------------------| +| CPU | SR25519-Verify | 568.87 KiBs | 666.00 KiBs | ❌ Fail ( 85.4 %) | +|----------+----------------+-------------+-------------+-------------------| +| Memory | Copy | 13.67 GiBs | 14.32 GiBs | ✅ Pass ( 95.4 %) | +|----------+----------------+-------------+-------------+-------------------| +| Disk | Seq Write | 334.35 MiBs | 450.00 MiBs | ❌ Fail ( 74.3 %) | +|----------+----------------+-------------+-------------+-------------------| +| Disk | Rnd Write | 143.59 MiBs | 200.00 MiBs | ❌ Fail ( 71.8 %) | ++----------+----------------+-------------+-------------+-------------------+ +``` + +## benchmark + +``` +cargo run --release --bin polkadot-parachain \ +--features runtime-benchmarks \ +-- \ +benchmark pallet \ +--base-path /mnt/scratch/benchmark \ +--chain=bridge-hub-rococo-dev \ +--pallet=snowbridge_ethereum_beacon_client \ +--extrinsic="*" \ +--execution=wasm --wasm-execution=compiled \ +--steps 50 --repeat 20 \ +--output ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs +``` + +### [Weights](https://github.com/Snowfork/cumulus/blob/ron/benchmark-beacon-bridge/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs) + +|extrinsic | minimum execution time benchmarked(us) | +| --------------------------------------- |----------------------------------------| +|sync_committee_period_update | 123_126 | +|bls_fast_aggregate_verify| 121_083 | +|bls_aggregate_pubkey | 90_306 | +|bls_verify_message | 28_000 | + +- [bls_fast_aggregate_verify](#bls_fast_aggregate_verify) consumes 98% execution time of [sync_committee_period_update](#sync_committee_period_update) + +- [bls_aggregate_pubkey](#bls_aggregate_pubkey) consumes 75% execution time of [bls_fast_aggregate_verify](#bls_fast_aggregate_verify) + +- [bls_verify_message](#bls_verify_message) consumes 23% execution time of [bls_fast_aggregate_verify](#bls_fast_aggregate_verify) + +# Conclusion + +A high level host function specific for +[bls_fast_aggregate_verify](https://github.com/Snowfork/snowbridge/blob/8891ca3cdcf2e04d8118c206588c956541ae4710/parachain/pallets/ethereum-beacon-client/src/lib.rs#L764) +is super helpful. diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/fixtures.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/fixtures.rs new file mode 100644 index 0000000000000000000000000000000000000000..b50be81360a3cdad6a50c9a4058e4c20282eba70 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/fixtures.rs @@ -0,0 +1,1215 @@ +// Generated, do not edit! +// See README.md for instructions to generate +use crate::{CheckpointUpdate, ExecutionHeaderUpdate, Update}; +use hex_literal::hex; +use primitives::{ + updates::AncestryProof, BeaconHeader, ExecutionPayloadHeader, NextSyncCommitteeUpdate, + SyncAggregate, SyncCommittee, +}; +use sp_core::U256; +use sp_std::{boxed::Box, vec}; + +pub fn make_checkpoint() -> Box { + Box::new(CheckpointUpdate { + header: BeaconHeader { + slot: 5809344, + proposer_index: 101696, + parent_root: hex!("ea7ce4ad810829cf37a2235b1126c82aecfc5955a1647ec83640cf3f7db91bd2").into(), + state_root: hex!("56f6363d3604e61a907c774edf0bddf6477a8d410f026414bc420f751de1f092").into(), + body_root: hex!("8c799aeef815cbc4499e0b46723623105afb177a5c522ecda3415ad9fb259e6c").into(), + }, + current_sync_committee: SyncCommittee { + pubkeys: [ + hex!("adf5a4907639db7bdcbecbc295b57d8950b0abe34ab17798686643427023c4f3983550d1496f81a27e52b070e4f4e6ee").into(), + hex!("91b036b30405531cacebf5d4f7e939b44438bb9942123ee55b44453e32febfbf2c846e0e4fb08190b01a000d072dcaa7").into(), + hex!("a86e70f00161ec6c4b780b4fc631c8dbae979f1e6c9ed037dd0745833ed6e3e18831478eb4753861f339293c0508f4d4").into(), + hex!("84196b1f39fba1fb7570074e7dd2768ed5c28db7f91a6374e413c8fc82f97738af771f90496526088bfa1ee2c01ee299").into(), + hex!("b9a230fc12d85281cbfcc7f5e6b13ab17b3dcbf0adf256d031c01acb30734d061683d40fd62175da73a621440bb04367").into(), + hex!("80b8be5a3d6f39aa7362c5feee9f89b75d1e5c2b485ea9a776c60fd60dba611e9bf5ca8b2528f42651b3dad212acfe77").into(), + hex!("ac8fcfc40028d04bdbea87b4b335781e35e10f881bfeb07b94c538eb37a43b18b1a04aad3dbe80bbff6e128017251f2e").into(), + hex!("b0b2136cc729b7de8868c02de6247ff2a68694296c78f088ce967219f08cfb7be9e1830e2630b10ca650c715d85d89f3").into(), + hex!("a70627c99777970eb9bf3268bac06bdc41c2ead41b1b76d30e7fd2aefe83319461d03aaf7ab93150343be9fbd2e48e7d").into(), + hex!("9599aae109d31ddc9028c428a148ea9ebffdb5ab6a684895dbd3772c1baf947c8a255e4c7ebaedae2a9e046219d80d76").into(), + hex!("8652e099adb88b2a25ab64fb01314e24cf26dbc4ae110d7fd73d74a0e0a4fea2ce2ce87cb1ddb7c0b9fb50cc6afa2153").into(), + hex!("83ba74c6e31073865eaed3c38a8e885ee715f03cfd6e36929655b6aa790d8f676c4ac4ae27f963e311d00923357eb087").into(), + hex!("86b05425d880027fbde9be3ea526283c5b958ccb31eff997d9d7b5e3b70e2d011ed95f891248082e870d55704a471deb").into(), + hex!("a4eeb958121bc5be5b1a68b73faa83b19272ef2f2cb627431be08e9844ef9d4548b4208670754d0954e5b012e3933859").into(), + hex!("87b0119e4c54aa2b4f8260f9ace7722788578bc6d822361d011e751caf13be5ca94b7d5842df5c16e52bfb4d658a405a").into(), + hex!("b696ec7f5dc82655cf027f5827fff3ce39195c1ee4ea9fba1808880cdea16d6086fb583edfbf66608e4f33211ddf9f27").into(), + hex!("a69bafcb3af59786acf009cc31e245009156a7e4fd2af98cdf2b7e63c39aff2baba08a338cdac93e94f6132b2cfe7a7c").into(), + hex!("8aa0aebb24b8c62168b255b6041474e8abf0d589a7467bc4712d910a9c2d470432d251e336a80dded27c0e9eea43aebd").into(), + hex!("a08d6976d3579411080957dfa2ca9487b1c4d8dbcdf640c1fea0a46f2c0228c2ddadf0553781c3e0dc5c7503b1bf29a0").into(), + hex!("a4abccfcfce6754e4d6c5c8bbad6668a55dc555ba99189936385e4dafa0435b323b813ce69f76e24299b5842c244139a").into(), + hex!("b89e4fd2dfb46c2af6df73f7185693ac535b67ab31f2805b1c24f400e0068cb32aff164b53668512f5895883189f7c02").into(), + hex!("b7c221a5884d10048bf9dd8611fb5231ce444fb756e59dd60d18a2332e889c014b27d739ed6012ff86056c04f36a87f1").into(), + hex!("a82875d66a4da52d6eac9dcd9ba9c728332253ad4b83acf1601a34efdd0c398eaad4acd1a948203d1bbd4a17d01a7b56").into(), + hex!("8c0a8ec162b3d48ee6a0f39620432cd67a1eb33a6d6f7bf1aada4f24c7498cf2e2b2476898f14c875f5efcf439aa0bce").into(), + hex!("a4b5d6451ae5baba3984bfbd5eef59543bf7c923662ea182cec6bb29aded9afd4c89e618ff756b5290506e0bf5a7e690").into(), + hex!("80664b43e3bd2f6e8eeb81a46cdca4f571499f3f0c77eb007ceb33c5b3dc18348a68cedc19d9967117f21a6c1ea29060").into(), + hex!("9174e46939c6915c757e793c9a02e46ed49d869216b720d0924b9697d94098182cad2cc6d4caf4cf140445377f1b4c80").into(), + hex!("b6e25cc134e089d306c648228d84aeee9516c8a276a2d37b54b458314b8f8980d49614550e821aac31000a5e7d518fca").into(), + hex!("ab1ab623a70f1e33cdbee356530a6f8fd9d00b2f9d8f94c0854816e5dd2bb8b258b5fb7a89ebc0725d8ebb74395847b6").into(), + hex!("8e75ea6f0678abfe1a7a9201c9fde992451327d61290fd803b3a1cdf2f7537fd7c23e0e06af5bb886a28928d0baba1c5").into(), + hex!("b41f4d4421de7b94eb9bc61602d08d77dc3f5f5d025e04f44c1426f14e8f707031b9e1d3a6e92c200f54166b2040f0a0").into(), + hex!("a8fa0c61435b851f9bda4da8dae0d544984ba5c0fef338eb6896b9c08306c3e2aec0a6c2028da319f210387320df4f73").into(), + hex!("a884af449df4cbd39e161de10f9d1f645eedfae0d259ddd84347733836fded047ca079916d365ef3d93fb354c8c795a6").into(), + hex!("936970bdeffc92f32915d141ccd8334df7966a3cafecb6d33fab9f477c389179f612cd6e368b615a98112d71756756aa").into(), + hex!("88dfe631c1e16ec3634e06a83a857ce9c909cb5b05d40490e6d02e553dd3bf213f0e178d31b4d913a4796b7226ab3ba2").into(), + hex!("a0c8357b4fb9c4431bc88e6336c2218590a7cb351ef4405a80aa6d352912f0b3110f3a09eef337bfe98da6b0841c6214").into(), + hex!("a7def54e08e2cea7def767d1108bc5c24d64e2dbdea9d07f0c8c63e60eec2db4e095b4a84bf6a4822103560b0497d1e7").into(), + hex!("80991c4f933985d9662d2e047187f244dcbb79606410aeb66ab250b2fdb9bd9daa392339e9b16d0d07648e847b02f942").into(), + hex!("b63fe559e2b4580238a0b0ce52ab8258838c2b64c1922c56b64cd65be2f88c140cb4e6ce96932e92f1ae06b20ee9e613").into(), + hex!("926ece3480c5c1f24f03d8289dcac7d3b202fcda277dd4453294dffc2953d91c842653a9e272b76fe2cebe1de3aba63d").into(), + hex!("930d29990821d26a748018bb6998488d5de811c8ed506c213c0ba346c8011e2d7c2235aa427a320129c1d016948eabf1").into(), + hex!("ae4e3fcd6c99a8f320dd4d160704db6baaae9aaa885f5d792212ea03cf06a459eb4a2730da9161ddd72a5e6544744e81").into(), + hex!("a01ae9d4f0008efdc5203abfdd0807ee7cc58c49cc5946d0b991ea2e069a224f0d99ea714b4d5cc464deb57372790660").into(), + hex!("ab17902ab255c575a133ed47fbafd62f898748ef6fc33455a2adcb2d4ea72c31255a9cd0c545d2ad8007a5a694f13371").into(), + hex!("86e2384e0469ab8ea9c5628d619d10336b3c0f334969dbc3335dfd72ed9899639fccfb5d1d9d6667a0dba20651584100").into(), + hex!("88ededd6ef3502be7c3c1b018cd814d4a8f98a7cf9521fe9cce6faaa6056315e22828397435b57d15e996fd15d7d700a").into(), + hex!("affa898a30a0dd2ccbe8d46c5a69dc1a8696b311094ac00e8ef6d398f6f132edb2d823004bc7895ac53bc09b6fb1059c").into(), + hex!("ac9f5bc3e2045a1eb356d88f2a62781fdb5775349c9bbdd9417b7bb7a9132a1cd42bf4986fcdc706eabbce45ab9f1cc9").into(), + hex!("986e3813ade7b9533ccfdcd76ea17490bc3dc62c8a596c8d07b246c28d7dea465c4dadda05111d2793d72d317c7c2833").into(), + hex!("ad4fbc51581cd520bcd0b88379ec0482c94b2ac344ce48a5facc1cf3e026edb088ce5cba5113f2c31f630a1fb37c5214").into(), + hex!("b499bfe19a22900c46bc0af1925887bfdd62455a3b85144e44b9a0a3756548c72b4f5c61f21c15f5e70b627d9f53a9c4").into(), + hex!("a5f40fcbe99b494c6acdd65ec3516d5a4a784ffc501e686764bf655c1f2f4bf784bac6b85e961d6a5ae513555a638323").into(), + hex!("a44c695e5490bca1cdc6b69fa80d6540239e42ae1765bff39285d1798696574c409e27c288b3b57196cf7c1753366969").into(), + hex!("a1ec5d9b8e55803477fbdc454d9c5cc605ead24147e5b04a03f80558a14f1e08bd6557f3fd11dab06a2816d32138cd43").into(), + hex!("98957c2e0a82210e2329b60dac05dd8b219ca00b18206227b725d011bb2f8b0dc1a79149a7d49a9a5505301b1b3847c5").into(), + hex!("ac8df8b1b596039c24c185108c89ef4da384e92c957c84bdefdab923e4be7c0e9480d837e1ff2e6269828b86b0b9f6c2").into(), + hex!("9358e3a48d4938b189059c3696169d31339851a1f4205dfe4de423ad4b96e7015c0dccb8bcebb64c1c357b65d5da6914").into(), + hex!("af1ab721df52d8b106449aaed05761b637c38f4c5513062afa55a09b94c8b088fe98cf430be4633b4f2f818da545b37e").into(), + hex!("8b8c063568a13f6522fc66285ab116a06d5e226a72f5a00fb321dd9b7d0bdb53a6b46ac17a1c9faf468c19b128c84488").into(), + hex!("b537f79bf008cd16711390c188c52d8dcf23cc41990c82005de2b0ff1fa09de85d3c34328ad9124e108ec252ec667f70").into(), + hex!("b9e8cf06d584197093d4b4b1e1f745786bb1f9772c0a771b321ae2dccac167ea79632138d60d8ed6dbbba2accd0a3c11").into(), + hex!("a674106b2c965c708b98a9f28a3f511f5dd8dfe7b2b9ca64a29152c7fd8de428316638d5091412e22e50406c372d9950").into(), + hex!("900a4953af5a28ca5b6f789a0dab9a03e3e5b9a0e866465d371b871fc59ecba31b524b0fc414eb7467d4384ca1e4ab5c").into(), + hex!("8b12a6114f8e0947995d72fee19e9204a4b552a85743d5320c1942bd2294d52a2d6d346f5e930ed788fc929069733297").into(), + hex!("a118f4b631a0bfc00df74a94930d69075b83be0bfec2affdf5e1ca4762d40592132108a7d2088891e7fe078c84ea9d9c").into(), + hex!("b074d18f84787d245018a2dabe2f0a51bf2f3786a802317122982a598f7f953339468e0392665c3f3a6b8fb17cd8f72c").into(), + hex!("8e6230b8186009b765ae6b176eb7dbcf503139472c0c2c5574e3d608a49932f0b852e744d2e215c58512ed2b4e8178da").into(), + hex!("98cd13f8400ecc9db458cb81840ed6467795f54f25680931881808909cc661230716fcf8b3fc2b18f4c12d30c32d9e20").into(), + hex!("b861f89275776bd640bc7d0d347df31e784292fa14ec468a6b0e8d64a9b077b6bdae1bdbc56385bf8f5676ba62002606").into(), + hex!("80ac9088ce82fd7a90c91b24e5e91636ed76844303a3b1105bc284b4e9a860acca3207d1804200a55eeb3ef45ba97558").into(), + hex!("940bc07a53373f075128b6c12f59120bc7368289d5a960c55d52abb68e8444f6f830b07850d31a9ed137f5357b38406c").into(), + hex!("b8c5919657811270976eb45b9f3f09be798fbcf6b34ce7443fb36207816b6a84d2b945a6e6522480c2feed12ca980df9").into(), + hex!("8294e6ce7d7d3b77bf48252eb97cca47a5b9dff1b8c97f8b7af8034b938864f38bad932bfe44a9bf4df81e394aee7ef2").into(), + hex!("8d9075a2c42c1cf51e082ab1a57a66670f0e81af2a651aacac5aaaf3876711aabbc0c96e44d883e6ad8a91a81be22940").into(), + hex!("882aea71e4512d5c41b5fd6509ede0fa35a49fff9648d277b531364613342711b2c7520a5f8d58ff31b7fc9407f968fc").into(), + hex!("8a34a824cbac06cd99fc68f36ae09c6adf2d472fcbd378cda61b838c29ca9750a7f7d63d9b61d1f30deda1ce048ffd42").into(), + hex!("a4f027b5466de4fd633cba98d2c8c78db10f91c8bee76c32ca5a80c5e87ffaf19df84e675f31bb6c14ee733ac3d4a33b").into(), + hex!("b990db2d743b389132d4d8281701b348bfc52f707e6553f309e4f59706871142e6b8e3d081057a5c43243911148318bc").into(), + hex!("a4500c88a58971460c33a404a65260a9e21c4a345601d1686ecc352ab088b2bd93d30930383901fd2d042dd037286761").into(), + hex!("a4de9078286cde237b4ff2da52a0fc1b8fe7b931ac35b3033dca0b87b5c86e58bec951a66a7308fc951ebb70fd0d0bfd").into(), + hex!("a9adb6dbff69b115be1cb37d5ee3a95d8c9f466f059128b6ab197d4119f6c0f87d1cf4d14d4beb2a43f7913700c1d909").into(), + hex!("a8a33f167e473eb2ace3bedc1cac2281bc9f522a0fdf6a9eb365859b9116067e07b7c380e8ea4dd33a4fbe23e2412be5").into(), + hex!("88c3785f853a192c50c38040ed52c413084dd069729cb14d806223f8a51aef40c21648d1e03bbbca031ce811e1b708ce").into(), + hex!("a7db446f88ef0d018675c0e7be0e9655d098529ce4b4c92ad809ed9f588c3f6c6dd98267ae0efb1659dc16a29e1feac0").into(), + hex!("916fb4b864ef46a2f8f570364cdf02c93d8432d8155e7ad1a15777d7f5d5fab94797257d0943aed5f16033528a4290b9").into(), + hex!("b9b7ae2ec02b0694d7828a69bbd1fd6e9070cc217319dc4e8fc854e48f5ce6e133fb0d5492e2f87e7cb8d3d557f17037").into(), + hex!("8cc36ade2b8039ffd41459272091d9cc4c46a49a187e18e9e3b283136831724313ee6eb5954c34acf9ecf7d79c30dcfe").into(), + hex!("b89a28db91eb06f191731a927445ca64cb685a206f4d77f335f510eab3a4973eb1d199525aa12df17f97cb4e079bc35a").into(), + hex!("87fb2881b92d5d9a2555080afe033512fd93306bd25f4a841d034c0fd9685ae09ecd29d27523e8f18664cdf2127ae6bc").into(), + hex!("aaf63cd83256de5b8558bdfd7f5fa44b3b3cd767983484ee482241286f82c6f51c1de99e2c03c8c99e6d4a27b7379cc1").into(), + hex!("89b52eb17e1c2868a3da6727e6f122046739c99cbb6aa7e30f65d7e649d09540b7ba77ea3efad77be597e48f7abe7643").into(), + hex!("88d097674b763a770872f17266f38200a4034347756c5ece6f11fb841e82447a75f6f3279ba8ab54e668b2072c6596ef").into(), + hex!("8de58933f07ac60ae919adb4be0becfb5e6f7330708a39cbc4c988c79033f0ffb365ae2a6308c234c218f0e45ec1ce6b").into(), + hex!("a4ec080db7716a0eae593b0ca91a16273abf534b8d153232e4b7d613c9fd21081bc8442d6e57a6e8e026c66c93bf0289").into(), + hex!("b680857b566466d1fb92d78a09b75d93753c275249dd05a17702e48560e4df5c7f9ca68370847250fd001433972c8b0e").into(), + hex!("84ec67623fc58128f4504b071b53e3c0fc60ed07318febc8d450035567691e2e58468b6799e5f3d93536100bb4b5f3f0").into(), + hex!("b22ce364b11e164d3df52050d59085c29c398556617277349637290b193727e3942064693ba1d7bda313905908a071d5").into(), + hex!("876198b5734f1ab5f7ec0231110e9cbe59d116ccc410678d5e0108fae42bab5dcc7cd15df200bc71f471ecbeb0e80d68").into(), + hex!("b081fc87056c34a8a78cc34f308502c5b6509adb0b344e83227b997aade90d6f0f1b8e5302601ca3217ecf9dcfa24ddb").into(), + hex!("8fe71e517db52831a0c4adb83fc6524817b9a358fc6bf58c03c91d4125482921936391e54c767ad2f13071e0e5ea266d").into(), + hex!("91212d83807ca11b030e5ab72ec85a8d13d468c714fc97c20b5d0569c382ee270d2ca486c88354994ee54f02dacbea50").into(), + hex!("b022bf24d2ea893125f3a89186f444aa5f301333f0667eb5862586a5eea8233c90202064eaf9363f7b367a6dda15d45b").into(), + hex!("b1f45fa596a32d3fe267c830eb042432eec6b79efcd5c84ea835d7108bd4290fb64749ebdf7e7e51e6138fedb3cd2eb8").into(), + hex!("8567d2366c584d975bea34d4f9e2ce62a62765125326c5e56b5cda08fd2f2e7f769db47eb514d2d9324b645879c82a66").into(), + hex!("b4816fa77c76e1f75e6fa903a4c0c031ac7a5f5ccb5f553b4eed83bb34067480804c0f6f308f8d0fd723dbe2198b0608").into(), + hex!("a9ceeea8878d799fbb6d52dc4112ae712429b9213f72b284698d68ea6827432f34e51daae1727b5402916a836567f611").into(), + hex!("984c8ca226df18ec574e0fabd6dc9ab3a2e1b319c4b6ab5b19872239833dcf447ee5d720305b2385d65facd297704809").into(), + hex!("b02fc16b3530532a2373776e2512421d6d2e42f3fd3c3e71393706d74ef9324571c8c1ba7b9caf65dedfff2bad946d71").into(), + hex!("849faa2060a75d08850b54e06376f252d9cb4add3e740225ee37d23e29f80cb9f98188a7eaf6a381af4b4bcc9874b792").into(), + hex!("b5331ab6646a8be3bc37c5fec56a597b914683402828dd4098b189f245c638e063c933a542c0122f063f98a9468687b7").into(), + hex!("868d8214f26e14e71ae0bd514275ae9442760af72269790228e733005293019984d3d463c2bb82936193dfae8c267fa3").into(), + hex!("a88c6c4af4b9d285997c9b5cca7e22ab9b8e5873ff36455bab6a6dd4518e966079524f8c35391811f16eca421f588694").into(), + hex!("a6f508524a938fb49a7da70251fc8300192882a3fe784f0bc51027ef2193d90c75d0d0720f2f5be634d81f38f3d85b8b").into(), + hex!("80a923acf2aa0349b4852f47edec37cd47bd74447f2f91c110ed092d015887a6625d5f1fd1f5d00c994edbff1435956c").into(), + hex!("84ed9f1ee0db4c9ae55492fd07fdd44851a6d0209da5b521435229b1b45d57c3b835af627122756ad0e63e915c15909c").into(), + hex!("b4c4c1e6fdc418bd29cbe8082fd774f678dc28a51896a51349c888ec98124b7a522baf70b820ae0729696624f3695af0").into(), + hex!("a8cdde029503aa3e23776a67952b2bc954fc0dc06f07a78bc94e6408edf381bdadc29efe4e5340a9b7efe99a3f3b3687").into(), + hex!("ab02a92f5ee21035e6e3e40a026d8d5680f98afdbf82dc037dfa30a87a1c101387a0085da8474989b24196ff494aa618").into(), + hex!("986c88a5d0bc6ff4127e34e0a5fefc1290936ec88d1765e776e199601f9660a8425c1fc6defff07fb81576461d0ed7bc").into(), + hex!("abe2259e880aa8b587ac9a31f794895bb18ff1bbed378a70fa09388e5a6a7343c072401c856bdd92fc2f60ca11056aea").into(), + hex!("8c04c98815c0c1f281c8783ff8098b0d806039a39fb2f4642a08f821c02506bc7c80ba7a1f4b225ecda9971ee3a22121").into(), + hex!("87b7bb0cce6244aa1a540941f0ff5ae4126fd4c62bd98d34993380a35e1c9a9f43b561506e5eafc1ddb8aef131403590").into(), + hex!("93cdf5f956e8b40ca0e31cf559937b997897c137a411930ca28075899abb6c08dd6aad5b2bfd5c09f07613a6854b3be5").into(), + hex!("83ebb284e03b4414694522caedabb391062ba9ec373e94427feff071644df91635ef498dcd9351ae259c5b15d6edbb38").into(), + hex!("80747223ba06d6465e26a354b79c42ec0624d33cdf015da7bcfb31f7009d92aaf6321f4a921f6b38e0a394a412edcefa").into(), + hex!("967b4b10a341abc5c01ffd413103c6468e3efa32c8ddc7e8622fd3ab2a765a420e0be3c81e1da05679becc5bd03e59f6").into(), + hex!("8a01b90d551f26c265b9987c67b641d15bd3b8e5af5c25472037645da05d9d54c0e59bab32578eeb3c1c5889f4fd9aa1").into(), + hex!("b6b38e40e3fbb31b257ecc18dcfff2fd850a41c2cfa5a642b0c383cc1a86b2b9adbcb22130665f544f1d9fdf87e92dd2").into(), + hex!("b490529e0da56e7e4d4cb2f79b704576c8b6569e9960dafef059dac0144b29ec337f4beb515465a57414d8965268a3dd").into(), + hex!("8a3b14616bb721543bfe007f1a042e76ef068a6e4f8964d68dbb7a733ea92dfe4e51f4008aabaa01a8a3566b00d083ae").into(), + hex!("8b82dbd0d0592d45d6309c795beb42274b74d844cfc393b34cb6992e3b25ea8f62f777124584eedf482949bb999ca5c3").into(), + hex!("918226266d7f02b2081edba64ca4b70339b7a63ef9194cd77a214620bc25618495cb4335c0c3621c75f821e685af3f1e").into(), + hex!("a61f56310689b9f383b45e8c8c647bd7150fc6dc3be96afb464b0c67c6f8c73ac9941bc8a5b0e2093255c204646c94af").into(), + hex!("abe204f55b8dd101bbd554561c1a7b50c01b31c967f6cea18dc898a10021eadca3c314f6b7afcc2251682717540d2100").into(), + hex!("a0cc3fbc4a05e3b5f93f4df85b92c1bed221f21700d8faaf84e99954cf6994d0052c8ba8ab894503b5515bdd1460ac5b").into(), + hex!("a5e5e175726b31163e13447e360f835ca64c3883901fb1fdc275b487106b39fdf43b83ee8f3985dc85157b94aaf8c389").into(), + hex!("b34106e71862b290f7bb47e5492417b07b541fdba23ed474f29d666cfb50bb5a3ea137ab717a41ff769af53ab385a3d6").into(), + hex!("aab726a0317c365aa15ef9527e5101b1a90cdd60888b733cdbd61dffac3374f995206abd154d099b2dfa03dbe666d503").into(), + hex!("b3fe9956454604b2aa1d51480ae96182ad1a8af64e80adbba1034619090c23d0d7ddb4163f400399d5946babada2f5a1").into(), + hex!("831a0d4008865576d9d0200dbe80eeafa7e6e6d442a46ebf949f39e32ad311995535a261795a7e27f2b23a6d506b7a33").into(), + hex!("8d7fe284c9ea1a2dce6ac70e3f225994f65ba9791520fecf5359b80f7c32c1e45e75b8b787ccf24b83c79301e046bb4d").into(), + hex!("b00df7ea640dcdaec66317924090f49380edc5c669ef1249ec8a24a3436b4bb41be0edd4cb0d04bc6ffd540ac8efad18").into(), + hex!("aa55d72470c024627edff24f9a19ae958d6b382bab6a24581183f762d736ac10f189ec3f34a7a41516f81696352f16c8").into(), + hex!("99e172cdb14a23161b5e8aa80121d98e69506ae0ab956912eb2ed959b73ee901852f263cb65798554ec0ee35089b4c03").into(), + hex!("84ce8ec6a7debf3cb2e53afff7d1f68bf75b7b209938192c7675286b17489d7996ecd9514c5233af0a17390b9982d805").into(), + hex!("943cddf3f5a6dc04f425aaca25be44438aabc7a661476761de596fcef5746f9d83c361da3fe1f21227ee5b9bf9a3ac76").into(), + hex!("aa81e66cb01e77c5db882792e9d896c83aa418b12c3b5201e1937ba5b74bf5fda974c82f6f40e3ca48bae72ed93437db").into(), + hex!("83bee12657fb462a5988ee26e2d0ef8b11e5fcec108724f6135e95913d7f4ec338031b697b24fd7a650cbeb088b26733").into(), + hex!("ac0070705c447e635b8df509de9ae03ea9b0314fc58b3befe14c316cb7b70ceaed081aad115a2126ecaef630c6bbab0f").into(), + hex!("a1c9c3b6f28c14ba91cf063153c50253d82440b81dd8ef938e181ef4116ede0b0ec62844e1d7e8b387668ddb8644852c").into(), + hex!("b41811ee8e385836fd709081a9a65a33ae1571bf943937615e97d49aeec3289624882915a5f5b6ee98601e752bac1212").into(), + hex!("a59cfa6d60f5c3b62197d3058a9b42c66bb841ee5d67ae34ad452dc70974de0a56a770c4ee5905c0d214c3d81a5269fd").into(), + hex!("ac47ff714d42056df3962cb4494019c977fb6200cdeabfa3ba85ec7d7d70c7d3ff4aa05e26aeae6ec6a3afa460244ea7").into(), + hex!("83c0ff348f1d018485c18417037016ec592c249830fa649b27754dfa70b94a549a42eada20ab1c4de2a5a513d742186c").into(), + hex!("ac5ddeddc94d18cbbad0e1889a2b64e91b3f927d3eb666ba018807f1e4e1451a43498ebacf12fd370b62c5386e36fedc").into(), + hex!("91606f0315fadbc42b1b27ff35b5601196a7a8beec3d5c76643e38ef28f0aef0aba9123bac7ceff0b297ca53727edbd5").into(), + hex!("af9cd077736f17c89ab4fd21fa2cee63b16f67277e9c5d54663f6d5a7abc3141f3045558899da70419b1e92ce88eba86").into(), + hex!("ab0757213aa3fcdc326925e0dadc2206f43c53f7abaf34a077f1cb29427261b4bd9981dbd1e33fedbd77fe00bbdaf8bd").into(), + hex!("8c97d256f9d4e0f309522f3899c5f74fd7e8c4dab6adf4886e7b058b323e294229fafce28871fd39e5c43f28c670b8a0").into(), + hex!("86eb85ad6fb7a3d5cd9aa5b22fd648fe9db688fe663c835abec75a6bfb67af0df0421d24203083aeb8ccda06dfb230c9").into(), + hex!("afa2dd3712eb94c9097135a69573c1f373ee0d7916f4ccec5a62445726aa4c1548bca45038e1a44ab7c8b7e3ea22dd6a").into(), + hex!("91e82407c442937af665ff8952d8b7ce3d68ebf807166aaf0fd710b76c65b39283e511b3314297ab0f2a9c8a2d76ffbe").into(), + hex!("a52cdd05f6e254c6da7a00c9210e33a49658f035b78bc7f15b527fce20c3893d3f7dc27a616eb3e107da060df251b082").into(), + hex!("81e6ddbaad6a18404832d2923697ea8df8dc3b39e53390269f197b976b5edbad074639e1e7bb25ae87b00681973fa021").into(), + hex!("a776979b38184661cc36ae9bf99b98cfb64babe37b16ed7e16e33e2187af71d9f62af4fab2bf0671baf3172727741d79").into(), + hex!("9395a004323f4ae604518224292a1fdb359fe9d4ec2a2262f13fb33d90a9dc50040d03fa6315a5ab2db043e7e16fb971").into(), + hex!("81bfad9e94c00fc810c4e63042fed6dc54c7c48637064376d5a4df8c8d6be3c2eed335640bf45bb8df99327a7e070d06").into(), + hex!("b6b38236ea973f91eff175206c4328cb97335bc8e498d9c9a2040468885f7d8464a8a1168929cdcc4c59513885e1589c").into(), + hex!("aa14a7baeb7b6d0048bdb8c772de512001174f764b37396c6481bff5aad30abd6143e4654a3d80406f8d08948ff8145f").into(), + hex!("a7297d6c09873e481c04f2e9e9a07567d78da504d2929c8b9d8ecae1c4d919611e061caa632776a8716b20e031cfd203").into(), + hex!("a0942935f58ef26a111d77b1c4598207eb6e3414c106b286f1c4dd344b3e70d3a46595ebb657e43f0e71dfff3b532382").into(), + hex!("95dca5de041e96f8c64f945817ab1ad62414b3002073b18331e288878c7a889774468d3c24f04e0714958ebd79ebe71c").into(), + hex!("b64f58f4eea309ea03c60f6ee66107fbe45c5ba81b8ea397a515435a179ee86bd098cb9acab4f374d29c8a388152fc6b").into(), + hex!("a70fe2ce7cfbdc22183a1a81c779c6071199768ad9b39ad0727ced4fcea5fc79e9833279ce93e1ef16cfc6dc0ef4f15a").into(), + hex!("8169d05ef0406b661022af53dde8ccd7315b3e35065c568673bfe5e59828480312a8ad418ad431beee12e7882d11142e").into(), + hex!("8148070a20eba3b61bd168e00ae8262d698263a8f22ee01ed6d46d154a08708a85533f54935bf92ee6ab0c04569eb3ac").into(), + hex!("b5e4f8b8a011c9cde6a9338d7c751ce2828fcf41b40e140ecf543150a5b4859f87836461d0ea2ed7cd0e6bfb8febdfc7").into(), + hex!("aab7b0022a1791339fbf567e771c43e9a2a46fcfed394b7216b556aacdebc259e5fc599eca66b12c23467b2443fa9c76").into(), + hex!("a843e5929fa14bbdb5f370d28547a7b585443f4d2fdf8e7237fcbb93a5220d62c8033665996f36288127a2bb4822f357").into(), + hex!("92a5abe1a8d508193c88827f93156e84199b14731a68b0b434663e5b9ce8e6e3005ccefb3ad8330c56fc0898eb9334d2").into(), + hex!("948c5a4bce25157f5f779fcdd89bfb4747a6178d464d15148742920aa2ab7fc63d6989b586152e1e79eced93f8686206").into(), + hex!("8702f3fccf470a294946970f8ecfed499f5ab3df799601f872d7be3d9227ff78a764550fd1a97ab25b7be96d366c82e5").into(), + hex!("85c5f99e913f1cb67a30807386b5292c841b51e959a13912fef2e0f4ba84ab3b1c483dd5fd33e80774de19695b622888").into(), + hex!("8a903e39b9b46dcaaca4fc968b298430b982ed3916f8ad533ceef5131dd507f1188fbe856c80bedc7bf34799743fa86c").into(), + hex!("a91ecd938c2a399b97576c43c5d1621fe748732090e360fa1e3ddd145438f9569d39a7be9d032b435a5d14ca4c905d15").into(), + hex!("aba9def4db5bbf2ba185c134f7734feeef976573e20d76aee476bfaa2af389ba5576a1476aae2d42d5470a46ca3f58ba").into(), + hex!("91d3529480d066817c0111bbd92714a40472ed6c877df358de98f0258f79fb8ccd54a4fa8bab3b9cc15bfabeb620c196").into(), + hex!("8cbce4e674d90185c47225c587dac654428427cef8a563cb89aee6fdc2ae6f12a8b11be46c779ed9afe38ea97d7d71ec").into(), + hex!("ac684cedfa58b2adbb6a13a94aa8398ef4a14970f5a43a344986cab68fff7fd48f7bcdc0506026eb0a2867efe86f283b").into(), + hex!("a21a2f8df2b811550d6e115c095d4d6781a84ba25b7f4017adb318776ba7452f48ed8b83a6a94aa68d83f2226a4c0549").into(), + hex!("a0d96e01d937144627c695aa4256f1d1a16c708894ce854f5ac656585e6852a43c39080909e7029b6611ef519d9983b2").into(), + hex!("a2f32ffa61e370d087058cd3ffc534da6a917f75ed5de568938885cf5220d474c930ba9bfdce91e031aab3b3167ad362").into(), + hex!("aa6a7c0162520c6706ab0f6188b718c1909a4aa12e71afc1c2d40e51fe44f667db0e7f1f0cdd81594447e267720f2dae").into(), + hex!("b4f16474ec3f37765e8750729f3245167b82472ec454329c9183a5d5ec939041d85b83523d11f2b895e2d15586f81422").into(), + hex!("917af7d2995b6466baaea2b3eaee5f76508d0c117d0452bca6a07ecb87c0cf595161abd5bd31a904e05684e55475a4ce").into(), + hex!("93dc25ff6a8ed93fa40d198a97955d40a5f29e50fc6fa6dbad34582478d3d1bbac0ec5789f11a92a738e533939c281ba").into(), + hex!("a41c824ff14ff5ee486a6130dc6cb01043e59f71e234390d464c95ac49ceda8b5400079ed4edecbb59be2083d8f06da7").into(), + hex!("850da042d678ac0aa31dfe0eca861ce17cc306188f260195fd10f940c67d42c9431cb68a64d27232e989c9c23a6e3d1a").into(), + hex!("a3e223f30d9782fa1fae634497c64fc58bc8289e48a67c8517621918e2b921cba1e90b2b01f838ea36071ed89bf64ed3").into(), + hex!("809001b01c33bf49a97ab6fbdf708fb224879c71679a2b335cfbb3cb4aba3201a32113de289878606c9feca057d9faea").into(), + hex!("a10ee1706f4c49a9cb2fee4ec6a0dbdc883fe40d4e1cd7a0388f49edf1f5f23a38e6075fa5fb54fd8e77ac3742266a6c").into(), + hex!("8c96e17529d7051f09f93faca150f5313e4d7f32235a4af6d12270780d6c14418749489a2674c728095a56585e0ed924").into(), + hex!("a9a7bf56eb25c9bda003a70128117adca9c33c6bb24bb4c381ac405e014d9c75aed0d704d801b9feacdf81c3b7f0040f").into(), + hex!("ad53d11d31bb9ea53bd23d673fb26211ee39ff4442e9efa1259bddae866e97bf07b0f9ca44e166b3b85d19b5865b1612").into(), + hex!("95e86d1427c8abd87e7f966c2ff9468d0bc3f76175bda677acea5113b5bd0d7631972c4172220e3a72e0dad1496bc14a").into(), + hex!("b8894228542dfbbc6eb65c9adf6549eb4ade838701356e7d672c095b1f997be5bbf3ff19474ee99e81320efeb04cd529").into(), + hex!("88bc36d6d90424e86374499e330ce5afeb63164fe81fcb4d56c5c997e07093a37df33437389879895c8b2cccac28ed0e").into(), + hex!("8ec1d43488aebb9544ae0a12ac7311bf873bee05caafca5176e26d681b881ef6b5e3ae5d9853b33577cc21d3acfa1e82").into(), + hex!("aec567db9a542eacf68cf4b7b9682ffe0b385dfd192296f8d8cd9e1db9d7da0b4a4a0d0ec2419825177413faad458cfa").into(), + hex!("ae242e9e2c7ff2c0898f92e7e9742ee5e19376ab97195c4eea0487490068199d0fd7ea08b832c43e208336b5c77d1947").into(), + hex!("93447c215479b68442636384d29fd5b4815cff904668e909c67fdcef1cf5d594219371f62edf189d6e54f04872705947").into(), + hex!("87f993a564e69e132c6cc4874fefd83bb5032b98bda5eaa8fe9e1713baaf08486aea21eac3231028715e846e33a3fa23").into(), + hex!("a9c4eade07d3d51bf733d8357005e08a5f86cb44c3dc6b66dccbdbb67cb5727bb456d6092418275f34b59063b3fd64eb").into(), + hex!("8939e3cc9c1dd203d8079aa4ac0d40d2e1b85bd876616bc6b589b0bd187701fcc36c52d79ec7f14b5e54fff459c99028").into(), + hex!("978122dfec6fabe4f737a3c9326f2f721cc212455001ca7e09b65b70ec1ada1ae26d451632f31f648cbc65a3337250c2").into(), + hex!("8d1eeed7fb1902deaf7d6dec7c86807c4aa8ea1d7130d6caf01d65e36f6a30e3442a97ad6918e67d2e17fff4ebe7a97c").into(), + hex!("a16203ba484b5b02a1b210d487a54c3da41b3815c307a30fdbcda0c3f5f2205e16bc7232e1b8d57d5f58718ed4941ec1").into(), + hex!("89b4e7bdd90323c53aa502a9839f57133ab0cbae1cb133fd0beb54f4d7785988eab89eb0bcdf61bf62a29b341befb883").into(), + hex!("b75550a71a4144a4f23ee27d86c10c44e8e57c118ff4f9a2685762a98a3770a6c2d1fd9229f9792dd4e784e8b2eb675c").into(), + hex!("9171ea1599ae47b04ffc307dfe5e49da0f48835cda926355606ddff47b18ce3c224828ddb942e63dd8153c273105125a").into(), + hex!("8e0e78d069f4d51b9b0c370100a9d10e395b8f88d009e33ed7fc4959bf140176cc316843c76d2a741a3471d56ced5db4").into(), + hex!("aad6b97330e76b22781e78ebb2fb2e92148d74546cddc7348e7a7f0563b986a7553907c8946258cc343a15a8918f7491").into(), + hex!("a9e8e436356d44c945d8248d249e20f5c50bd147de94418d4f04e1f67be2319e4d2a7291981378a1e457874dc91a9948").into(), + hex!("800b092bbe1f56e78d766c510dfe42f0d6670335f5931b3f821c77689fc11a502d7c82d2d887ab21caf312f8e5a037f7").into(), + hex!("aa8b05f90da0056b7659c26171df70c748d7a8fb52bfda42b7d129df386de331c1fef9d5ad1b19f0452cafbb813c3ec6").into(), + hex!("a6bb8153637b097a905342895ec1c927faa92ef8d59af86e43864ffeb6b8caa3f5b025079ccfa83214332aa4f6b71a9d").into(), + hex!("8244cab3e6f39492c8fda490a363dcbd8af265dd3a158c2af0e66182b48fc2b49f473b402bbf0951b42da5bb669504e8").into(), + hex!("8696508e20c144ef2cd954fe420c60f8432529b97a865a52de5292215c448984dd591170d88c286d7bf5a1cf8b94ae53").into(), + hex!("abb5b057c6b91d51df313b4690b15a218dfac6a30a05041c5cf451f515062eb02c54ee6cf6ff2df7640d15ddc7a95dc9").into(), + hex!("ada583911773366a4ca0b5d407520a590e4f3c6628c6d050f2641655d1654809b886807c1efeee9e9ca187b79b7676c8").into(), + hex!("9730edb86b7161715296bd5267bba55d3bd956dc9f4c640df92cc8aaeed8ab2dc1ff74988ec2122f6c3ea57b6e30dd91").into(), + hex!("8da42bb48c6f17c0e96b5edb71ed5e937c9aa65af142234e3ce61403df7f6ef05a4309e92469eaf68d83afc5bd800373").into(), + hex!("b3cb17866a99dfd048c4ad6024823841eb6602c7e4728340f1167b8af3c810926f0eb3e1a0f51ed6fba4a80743660db0").into(), + hex!("af121178dfa05ac08a2acd56f895f444e56968b703ec6b6cfae1e836d78afe6f51021d4a415aed89913df49bffe27ec6").into(), + hex!("958d5dad1aabe840881f29617dcd2f759f220974515507b0a63b3487b4cbfec69be7d22f4f7f45d693101177ab205303").into(), + hex!("ae6160e53c2c9ce5495bd0f0476703684d854048f2a8bdfeb6cd1e93fda36e44d879531f213feb1dce706d35f9fbb04d").into(), + hex!("8dca41a5808d3c75f41919cbe65a226355df9ebc7c1a2d2263d654bc66d1f5786ffba84a1670a7369258bc92d6bd68e6").into(), + hex!("81585a607df11d0a5dd778adfa1eca440a49e37b21677fe88709142243f5ffb2205e703366de53fbdbe3d7ded093e834").into(), + hex!("a80b1f358d284d3d8b18ef9f101d4f0d84c2ff99342e7150a55bb2f54ee231e333ffa930487a86e97f460696348e897b").into(), + hex!("a4a1f79af8ca4a5c5b44b05828449002c92c313c8bdc33465c099ce8f74c3d575ffcf0ac1ec5d29e80ff96b07f08636d").into(), + hex!("8797e455d44ad2721ce7de2fb8125af1bc4c0757d9c2fae25394e44b8952dc5fa597e0cf5d2b1c2ce996e380597a1db6").into(), + hex!("acd840fe9ed7f38ceb48d65c9f9f02fba4df0fd871efb58b35547c9b526e6e2416195d2c131a04408df7298db50a76aa").into(), + hex!("a9ae67c2d2bfe04d64bd4def66509c108f9ce85394da48d97407535c1aec05df39e3f7c66203f72bc65fa72c18bfa77d").into(), + hex!("abb785c66a7aa06200bec1960c572d61a9cf2c283404601259ba720a506b3831391e998346ee73392a3f7f12915b6f6b").into(), + hex!("8a453eb657c2a85bf93193d47ec26102c4d3adb666a7e1f05f1991782319bbfe104cea57ae1f4379cd115ff711be67fc").into(), + hex!("96c7151e34ed488a06946059722dd9d1b5a2ad2fec96b545ed662a3d0fc23bce6973d93dd2932128d95b0686f9208fc5").into(), + hex!("b5f39ed29d3f85e56e21ec2bce47b04ba16d72a9fad492815b485a93065f2a83dd46f92d74274f815c84792278a67cf0").into(), + hex!("ac9449a216a875c2f288e34443a94a521f8e9de28f70b729f393a483359ffd3ef8537b8a798b8c9a259ca390f9fa9751").into(), + hex!("b15584e841de0e25a301bc3378e89baee55989d9610c513b79748e7c51c484f7bb1d9adb33f3b63d52d36918514aed2a").into(), + hex!("b4dcbabb43cf694b024d36734baf824830304257d959f6300ce17f892a23000e036c4d3d59d7d1198bf4f6ad5ff07e57").into(), + hex!("b8a1f0a8ae246442517606f34ca4029deb727cab005c9952ee9858dd99497ba8a0e3311bd43aeee35275db74c7bbc52d").into(), + hex!("b1c443db1b5a00a87a399880ccbff4481f5742423c47d38b175527e84b32fd66110791c117fdb70782d75c476683f9fd").into(), + hex!("8e018c4b2b4cf1f1a417d00b13fc51ccebcd09a502bb14795b8274585d2e30d71c2c7a9b9f56a717f0676e685e65e907").into(), + hex!("871ea4444c7080995472fc8bc08f9091f9f706e9cfc49eeea5357867badd837649f059163835a7ad7263cf03fd13b198").into(), + hex!("88b67b5819119372e0fd7f97ba1eef877cc32d4be465001c35096adfa18e1811bec1620849a608de8420126fee9c37e5").into(), + hex!("9060dc7f55fdfc237799a2814a6bfe2d2f539ab76c38a9b1206890323bb4eb7b1ce011ea4fa552b412bbf6c67a95f025").into(), + hex!("8bd156a3a54bffe373fea65ecb2ffb12c96f04e07eee582200a0ade24d543bd6523ae5eb8a710c1de1912b2b4712fa0e").into(), + hex!("a8c3fb552f1a8c6cc2714b97d0cb8b2b6028bc3aa4571a7e3e33f46eb4c150771556c7884d575ce8fb7b62a5770ed2aa").into(), + hex!("a808f5a34beb7d62d23405a64d27ee5d7bf83cd880caf7bd4a615b84f22e1dbf11eab129d9cc9ad90d4e1dcd68613f0a").into(), + hex!("ae56febedf59fe99e79e87d7fe7aea5989493833a52f2e6012fd3400c69a6dde951fba50e0c280779d530d74452d63f3").into(), + hex!("8ed5f6de4a3ba85c6c857068bad6432e96c6054ea38ef07391b914c052c2262856d19403a590e8df63c6dec99da35b68").into(), + hex!("89c01fd1f37d826b9ef3b73e2b1aa5f4b4f86a263b2822cff0153fd2b945bbcf16eb3868ce66910073bf86b222becfc1").into(), + hex!("97b3ef6e0bfd3c399ec959d22d29fa9a79fe8746eec49e1675afbf7a955d02db2e89190ebf43118b65a7dc2db0c4d72d").into(), + hex!("a95700745f0ecbb1e794f4db9788af60df4772b5ffc8f5f693f213ce6230810df31716382dccd5a832ced7f34945d144").into(), + hex!("87d0a6a8cdc36fbd788bf744a443b632369fa0cd983d2b60e20856533ed6451d8476b9b3cff39ed0f75de94ad5c7aa48").into(), + hex!("a14c6f6463aadc8d1d2985b601bae8e74de54954ee7e3aa918837064a98efa5ab736628446582ccd13bd458ec2d50b1b").into(), + hex!("8b68ec274484c910f1a73a8cf8a8a149ff2942ac9de6e73641619fdd9e778e9c0fe6198745f049fa9fed9e56287da0b6").into(), + hex!("b77e981a03493852e7ebd6efecaa647c69ce6d46b2190bb2d08f0eede4addda776fda92e9d943ba57331bc985cc8e112").into(), + hex!("9870ea49ed03991dc1a4f47fc978618d549b4f0ddea01d91e7c409db775c91cb2a58c0c0c57eb73e7b6d3418f850b0e6").into(), + hex!("a518fea50400ae263ab9cea0180079d0d353bdb7cd440cb4d2156b9628e487b704630d931bcab742e0f3d7230821ae91").into(), + hex!("861ba1b761d0ce92972f28b7a65cbf6026bdf7427774fe78ff1f45c67f9083fe94fd2c42f47082b6fba722abb648c61c").into(), + hex!("83d257a40e8418407c80851e2f14d0bc47c3b9ce9e2de53b5c6cd99f31dd25dc200fa90c822060d47c4225d61560706e").into(), + hex!("881f7f674959e4176731ecaf6e2c9b490e70c07abceef15707dba8c9aa3cfa2293a96bc9d5455f769642c9717a4fe949").into(), + hex!("835116735f8e21064c497fb0dcfc929004ae5eea1f3e6863ad0b227c820d36255230090812da0800e03af9fde4354a13").into(), + hex!("98d9c12ede55af8067af5b62b89002c66e3b6556ee201ecbaf585fe5026f997fda75105068d62fe5d2403c6c64c314d8").into(), + hex!("ab359e8e0ef4cbaa9830e2aab892db7cd7ddaaea54cf455c2ca24f10ca337f989641ea33fdb1772ed90a988083405cb6").into(), + hex!("ae9ded9c9fc4e812dcab3d8a1c74ea264eab2df0715c7107ec1ec336c0bb5f3761ac9580ca18109278be5cff837f754e").into(), + hex!("b373013674404122f39dd6fc29abef1b2634e2bf650b42c15d5a2f7d762eed98166be26372e8dc6bddeaff84cc2aaf4b").into(), + hex!("b34adf4c3acadd7e11a9d61f7df20cb2520cdbf2d16c217f39e3afbdf2180abe59d37115910b77a504b81a6000b982c4").into(), + hex!("8161d446296d39d0c27a3db1bcbf0619e0c49739c655af49f49ea8403374afa4f98aaf530413848b7d4b53eabc16864a").into(), + hex!("8256cd8e3c9354ffbb59818f0b24db969a7765d64c2fcedf591ce65f619237d6eabd110293bff42b388c9965ff6d51a5").into(), + hex!("a1c562787d2ba1cb64dba278080fccb1c6538ccb00b94db34b62ed1cd863792f8acc4df78d181badc38dd9bda544e395").into(), + hex!("94ad66b4066f53ff299dc4bde2bdc23a891959903174e8ec08dd79f163c6f4661b3eb3458a786bd8f3fa153c806e793a").into(), + hex!("ad1a50bcfaa5641422c6f10d31316035eaf061ad1fc0a36c8835e078d3fa6efbe6dde4bdb28158d9b7aa74fd9241523d").into(), + hex!("a5952780f78fd6afd9c31226a23d307be72aefe0bd99c32a139c3909b1ff1769e2441dd2c03f33cf98df25c76178e492").into(), + hex!("954100f83b800dfb89721ac06728c3d5e8a8edb7e1b56513a63c2b49dd44b9930edd897fecd262984301cb6df23a338d").into(), + hex!("85de42b8de3cc88181a50e9aae696d92e66cabcc7b86425f846dfac138d26eda7cbc420cfd10f5d2681b63bcf411afcc").into(), + hex!("abeda3b142d621f94f829ed4174d042461e95d978be206fd31f8661263bb7a87c648aeff8bf640ec173a77ab0970a93c").into(), + hex!("af99434f06a13d9c5ee7195ce58beea07940949b686b4ce06727bbbdfa1621c608c891227e2f026bcb58c60a6e925533").into(), + hex!("96e97ce1ed97b8afbbf282bfbdbdb4f863a6931cc781e9d7938617310ded35dcf043c2320507e91e94f470f0cbb98621").into(), + hex!("8693fe1860981505e4540b79ee7a7ef33b26535cde6e9aa019bd1e0d26f359a2d26f0341b7c1634eff1f5859ed3a8625").into(), + hex!("a94e940bc2d8f826c23bcce8fc4e49e29c5f918180f566a67395d33cd573e6dfb149490de1ae75068feaedfe6cce0e40").into(), + hex!("9697d6be370d808a49563e062c2b3a0b347281e00839dc3dc0ed888c623f346a42094fbe2489d0487049f2fe47887cb5").into(), + hex!("b06d4cc8e83acc4121bd278784061f6fd391b3ac378fc6ef46ca2158207f5c0d33a51d3dcc4a499aa48e5b27539c4a16").into(), + hex!("972aa57628ce57381aef9710ebb7cb7a8db28b1b64d7db8be38936479f39772c60d766b0c5dcb79676e9330d0406761e").into(), + hex!("ad2164467404544aabb70605a56e9b0f7887491a1691302b2bedf271b50cb6f1bea1b9637214aa3624f5d8f854359607").into(), + hex!("a9da2595107e9db07c56a59d2af529b036c50033ff43c282e2da551bed8faa96eca744881e600b6406569675643046cf").into(), + hex!("8680660eb867978df2474b25e225fd7536b88e9e73f0188c0dbe835677de701fd402916d8e3d17fe652c7ac6d2fa0330").into(), + hex!("b721e239f50bbc7af5578b75c8befa439474bc4e6ea8d35d1006ed54c6d81c718fa675901df591a69b4cc30899974362").into(), + hex!("a8af30765f1b00ad51a32d856a2b2f97831843878a1668a43e66b65b8d0bd4a2e2826fef5ca5bf140050dd81eaa6174d").into(), + hex!("82baad156e89d7b3da9ded4516603ec9aec36e3a0a9bdd0ded604e4fcc0ba10179f9517fc8372d3743cce5e676c8cd17").into(), + hex!("b3392745016dfafca36a7af4be273c5fb4170b71938d6b93691d7a3bc8791bda537ef001d19ffcf7b393a89c898b8b14").into(), + hex!("92f83c901eccb742618313ee2f5ca571406bbfb1d077ebffb92d52ca962403d34a24f9d333c3a155bb9a72a0fc2eda34").into(), + hex!("9792598e2f303896010e35bf670dc2f3799cbe6f0c66379030b0ea01b44ebf24b9257841ab80d2d6a401fc56bb722e68").into(), + hex!("ad1a3c9c5d699ebe4f1b2727dc94b290c84f44c9ffb38d5498b14fcfc5914f4ef4d1d57853e036ca11e42396808556cb").into(), + hex!("90729c7ec4250613062a4ffbcba5829743ba7fd03a4e3407c2ae00b4513c21f3ebd68d10759ac4dfda5544e77b2ac306").into(), + hex!("840add692580be7aea866045826baa4a07804f8e3f56593a2af6fe317046d7d0fae181632f4009ae0d64dfacd4600c4a").into(), + hex!("b1be58941fe077aa8721b020f34d3ad94d1d5083244c276b7f3e6f4c918517f8c5c8d5c1376178bf27cb35ed76699e6f").into(), + hex!("97ba3e3be55d17113fb63abdc808d89fe205d75fc1ac808ebb78ea1b7570f7a014fae099cbc4b2a4b2ec884977405f80").into(), + hex!("a2c57bf9373db5382465ce924bf7dc4e62f406c187a39ab456a7387ed9231ce059d197f8701af9ce2d6cc772367ecfde").into(), + hex!("a62ba8b81b9f8d40fad7fa1d7e8e49ee547f170889b5d6a2be9e2ad2ab0b265b4197fbbfd3b17803a6e727d41cba83a1").into(), + hex!("a94559a51d438b194fea96975a4571a118105479fbb7a37abc7d676fc8b8d2ca30c66b25b7727dcd297384773ddca074").into(), + hex!("957f9be0f15d8eecf621eb0978267c3fc85607f31c501179d9c83864ed9a9e5d526aae278af3767632bf56a20eca62d1").into(), + hex!("b89928c19d1101486d4299c493db5bb72f56f8bc24b71bb54eedf84284452250427b179dddd7fcfc0f521fbba09d0c5e").into(), + hex!("97dda9cbf61015b296307e510295be258045a1de9de52117f0aa28de48e27ffd24ff711f9187936293babe89da226fa1").into(), + hex!("a66b4064c5b1ee95a35a93209c89206b352e0666abd1b5c95eed3c382210334fedad7d531b9939cdb2fc649c4369236c").into(), + hex!("89d85e413030dc45194b2676a1f2a76801920535ccd909277af1ac87fd9b0d16d94d8c62a421c9d95e7a053cc4c3e0bb").into(), + hex!("b8f0350e1ff988bf23846f2d64e40b35a370d1c5d1f9dca4021508205611f788fe5485e966ff2bb6fe8acc1540a2e751").into(), + hex!("aef9ed7229aaadb1ac4344e5b4d0eaf9b89b20d50d8ae8dae24294ec3c0f2f68dee3186dce35d46020d8d1b2626a29e3").into(), + hex!("b9362f34ead1b0cef1fa9b35b76b644a323ff71c48f375c27e22c6878cdf778b1a3125445cc8cffb6c8b9a3ed046c3d5").into(), + hex!("b69c578e2223f3727b7b5e99f3926eb7424869b356541feecda54a0882e4a009b182c358052d788c2a7e776768ce2b7f").into(), + hex!("a9ab536cca003598d88f76cd0d666b1738802c7452201f5d99ba4fd82b6d7da3c9ad8c4707445d6cb4b2c43de7ba06b8").into(), + hex!("a02648e465634db73fd49bbcdb23cb6feed9688eaf4c5678799734d49f2d4cec9cdddf598114896de961ebdc07436884").into(), + hex!("8253283df9690e171af958f2a3cc37e0b2cd67768f2362bd604ba5c5db8d3500c0d7d6cdee982165eaece63bd016f2c5").into(), + hex!("a06d8256d22ef3891a751716449f97d374e27bedbb9dbd0f1c9528307787e4d9ab9450002bead2922c31fd4ccab9abc9").into(), + hex!("a3bcf7d7c1f1c5a2bdde1e0f4cd3cc6dfa061156534aacd6318d8192c27218b6e34e734a118a282b0d5fdf639e704c21").into(), + hex!("b5577e876cb3de8196b485ae828362419c2f7f8ca9c8f38b1254059873fbe53b57110be543c864bbcf8b485f63925169").into(), + hex!("8410e1b1ab99cf1868fa4494dc75129f42a5e633448e64321cb379175cf6eb704ad6863e3a6475f9cd3cd3f1fcd4b49e").into(), + hex!("b6b21b346d709d4897da4c535556d599486878f5c574bca2823ff9d382fea2f45e8d03aa0fc5a5d623098f1b67c77a60").into(), + hex!("92951386b734171accd57b66afad7ddd0ffdebbd9da835e273b11f96639aefa6259a1387fe3947f9f7eacdcfcbb54b65").into(), + hex!("837cca28b3bb00034d619a3b667b06b66d7cd351ee2c161014b4c33692c705e0fca1352c1e5a7fe8ee00515f4b9c9658").into(), + hex!("95589319552ee815c1e1b053cc4452f9ba600142c37bd700feb3c27468c769e45e6307d7c0a3f366440cdb4d3b997d1c").into(), + hex!("87ed23d8d5fd6aa0922565367ab405e666996e7b918795da299c204cb6d1e51a9c6ff1760dc3fde555b0900e677a9b9a").into(), + hex!("88564f50eb215320ad93b979c617cafcda9122ea02f113029702df1f39116e00b35e0beb0c70bb7d7f2d9b4bf68a1419").into(), + hex!("adce6ad54e5d24a2da5a04751151034245184bd7e61998ddfed673fddbe4e3d069b580e833b3bd4509c30a2e6a81f528").into(), + hex!("80800ca4fa6d0f3ea555ac7d37e54e7776f640f76fe89cd7f172c74723cdb3324da01314c6f66c4fc404c393aa8c7841").into(), + hex!("a16f473cbc9070881b9dd63be9f99670ca571822a67520cba885b2636137731b440561f83e199713ecdd51d4dd542997").into(), + hex!("a08c0625cbaaaba847a63ab4a96206bbcc7bfeb659505d0b6b0e58d22f00aadec41f8cd62ceb116258b78063f26796ea").into(), + hex!("a358200c83ff15fe3fef7a1610bfacdf86d55452258e7f4701082f993c8bd5d234b4d86f96d444e96c01367503663886").into(), + hex!("97278525e5e590ab6cc4fa4a1bac4ed7164a65887b29e658bdb2146008b02907488565dcea09e761f6922118d9933ed3").into(), + hex!("b4e76e4cf2c711a7f3be5b404f076b9d2272e15ea7c61fa4b7a14c5608c352a92fe6674a25dc493a6bc9e864ff4b4a85").into(), + hex!("a4612d268cffd31d092892268e6b4f9f564f0036bfee4f200d270a61a8e5e239996d288d28d6f281d8446a88da56cb55").into(), + hex!("81a70c88fd7d99165b41ee12883ae529458758650fd13001c86f8fc6ce5f8f9b69ce3a133e310619692faa1580dd5d67").into(), + hex!("a87124cbaafc8ee5418639ff27795003a43ac18d07a60fb8a1c155c5fece0f8b25525166ba697e07a5f2d47af6cf0bec").into(), + hex!("90807f573a322aac9d1cd546100e49cb8c771f3d32c89da1890c1c819d90b1dc668c2374249a093c0863d5988c358d4d").into(), + hex!("90cb26b40d10f193da22975b0507f04de6cd9d002e33226852cb40d948d1814009a39332c75f1067e8192b0c9230ce63").into(), + hex!("aefbe25ad8bef226d434b970bda8a47bce8269ad69cf91e02e04208a74055ea79a3a7dbc988981b79bcb9298af467e62").into(), + hex!("b4932ab425f4abad270b32b6f66066cd01fd6f18cf8c84dde99d21c9c2676d58be4f9ea5ebbc454c9d9f921c0333cff5").into(), + hex!("ad19368c70cd241b1a90e5b46f34c44351d298e2fc9ee5906596b20f5aa9e592e878afe8924aea112be60c07648fef8c").into(), + hex!("8b2d2953b4603b73bfb1edda8313cf07f6d9d16b0272d90bc46816677b602b4a7e6fcb36c4f69335918f1ba4ec95dec3").into(), + hex!("ac64d362f730c790ee3f9311990d9ffa3b99d4952ea74f63d141360d2edb7fd0b70d94e5865504af3caa94b63e34ff4f").into(), + hex!("b71ad17d1cdf6be4b7f0bf7d3fca689d5a69a7426dbedfc137bb162142d26e079f49c99797316e2a577225d881e31a04").into(), + hex!("8dd692a01ecd819981ea31f39a5950bac2af0deafb35323358736bc59f8fc69f58c865ed9cfe239ee34d6f80bceb562b").into(), + hex!("b2ae1f2d871d48b6157aa9d74c24e3fa3f09d6c40f421de9c545de5ecdc44d44d6c4a7caff315694d8173974b92c6119").into(), + hex!("8c934a58d5d2c06221c10c14f08f17265e918c6f7f158f6989acca4b1bdf3db58952e5500f930af02ac3e6e44133669e").into(), + hex!("9466e7c328d12ad5439ac01c994825a94665091aa00e212a75c4ffd39f4473a62c160d63e568b534dd7d5577ad266544").into(), + hex!("8e35a84ca6f167c75f98728000b5df8d9c5611080d2dda8c49f8d4afd2196da349cda481fdad8a0e7dad1cebf4b82446").into(), + hex!("8ec436a9690744a1c6a31fa796bfc8f054ea5efb1c8d6a70e9094dbdc32bc199a7c1b29216d706616029525883a9e342").into(), + hex!("87cadc50459a643648f5995ff7ac751c24454040f788e218c4894854ac658fc64c2ea0a8cae4973056ea11f7bb0e5a26").into(), + hex!("aa5a2d8278dffefc43d598186f1119bf1a6d2343143f4874aee24d3869fd4b58401e8bb220f2a228416c89c1f5344af4").into(), + hex!("a36f48c232cba1daae013418421fac6278bca09ee3816eb46cc4059254f1e7672dcecd6eebc9bc0896e96cfa0d8b485b").into(), + hex!("937f8e28abdb3859575cff574517975b96ad41dddd4efb23af86429b01ecbfea553be6cce336d170116752118368e05b").into(), + hex!("aaeb4e5c7f67ee23b1976b2e86f2897ced033e79012532599d130cdbe29d8cd551d9451794741d2ede8564af9070f07c").into(), + hex!("884d9608b7556dbddd0ed13bbe04a5bd9f2bda0bd090d47550f7362bd769a3b3dfa890191c64e44378792d97ee4df5f3").into(), + hex!("a8276ce24aabe42cab32ba7d77c2ef2ca84b3a3e3d750f8d0385f9027f0e009365c78196638dadde186bf44b780fff53").into(), + hex!("9148c5c797b4a6438360072c463008df8b17978335f36d4972b4f826861e8a175265a9eb00c56f47d3892783dcbd080d").into(), + hex!("a7e5100f51c611f010b2601d340ad7aa65bab89c8aeecb181e76185f3a739892f8b172e5cd2c108d5aed8f5cc91cb6e7").into(), + hex!("87c46e67d6643c07e0e318dcb22032753c624e646e4871be4098005100b306f3cfb25b6d81c718e83b42b92841c577d3").into(), + hex!("8bf429c735133cb05edd9b8943b5d4040e83191553452e69b3a1fd06edf2a9eefe8e280cdda811795e1da33e41e58345").into(), + hex!("800ce4d6c257a365e5d8e1bcde67a38c04ff723e38b0926af8b9fc352545317beb40621497aaac8d0e5da291c0208630").into(), + hex!("a5c795afa0ce78cbc11de13c8f58d0bd9ca5b8665d5d8d28513a0d8666f9336b0dc3295557801ba253983fc99f45ce3c").into(), + hex!("99e3e4a8e16ed2ea44aae56b537ca9b159d57e987b0511b6d34767744b04700ff287d431dcc6c67d7cba5748b3580899").into(), + hex!("ac88e78183973e9730ff0c88dd62eb23e2794067ac2574a1c8deed85a4aa6229ee620668dd16084c8f168b2555f04cb9").into(), + hex!("a9c4fa68984527577c6da60dfff110163f35ab7393b852c053d73485155fa1536d9701f660a08b160bf49a647ce3cf92").into(), + hex!("902c3138ff660230158ff69c33911cbe958d29178a54cbd13480addd948b19b0b97f6b235df2beeadb7f7e1803b8cbad").into(), + hex!("a6169eabbed09e08a0d290f9075c62ca4b14e1f7adc53545abae49858b3d5d7fd6cf8d8ba2ee1bf13f36f79ee4092935").into(), + hex!("85a0d7387b1db5635f17899bbadddc0fe6b11976385e12ea4272a8f61d81004406604c8f04f10ce928fb6b547d3bc654").into(), + hex!("a39e5f2112944a7ee31fdccaca927e4f4bbefed1274a134e8a038307820c1d14a6260e25ca5a3af0589f8faa8f516c5c").into(), + hex!("b5c86d0482a417bc94b42f8478de06918b36c5f45d0695275da2a3e773088268cc127ac1380f912307b6455dd0b16d07").into(), + hex!("b8b5e32afc4cf0fb92251b422ef9b757130455150c49ce51f6d6d95d895dafa25649363660608bbe7507d787db9d643c").into(), + hex!("b11f3856f691d84d49fdbeb4f33c45c37dd401a2bb71bbf946f3ddc53a57ce5c4da583f76cf4467d43034a61e1dc88fe").into(), + hex!("a56d1de276b2d0a4482e17cd358455aa19a47bd25c7fc97af8457ebc37781358cbf8a7eec9427881550a8db1bbf51771").into(), + hex!("9508c85488f15d2772522dd1e991f41da1f3af7e3527e098d5408ea96f11f4150415ac68ba0a3031e95528664b261de3").into(), + hex!("a28e129c656bc44d0b6892103b7d7c0d15ef1f1ce583e90e2c644c3016ab348ae26a662652c953a3c447272e52b007a5").into(), + hex!("a68dcb67d0cb585cf22f753602c46c7ccffff246b106db9c56248ecc5e94a036009bde23864879af62a4ede81d040c56").into(), + hex!("b9725383c63b2a522f4d976ad6be14a35b9e80145e058baa622238500f1a2ffd6869cde87fdb984654b2e57615bde3ee").into(), + hex!("90248e4cf47ec8b00ee874ac98227759a3f7ce4819e44176dd9b1acaa6270d144d3d707a35e0cbdd7ae23b15537a20e2").into(), + hex!("81327abb95401fc2fe0b1c2d27d7d9972811a63e12be0173fe8311678ff1fb097d73fa32cb85f13935e1a4b7fc59113a").into(), + hex!("a31bd2dfc9ddfa9ac748968b532a41a26007b23cda258fdacb3b0abb751cd7ce2eccecab2d5e3781fedfe0d8da027481").into(), + hex!("809b28c11c1abbf53f2dc005d30403937d9826960b24f4de857a9470067add08d49345586d7e58bb1107d232b3b47bbf").into(), + hex!("b0597958b75e64fe5c6e56ef803284b3b7420fd537e5625c75af3aef814a87a5ff01951261c2c7b27e374466658711d8").into(), + hex!("ad8fdb91216db4fa779774324162fd5bd7ff9a999030c42d9f90248bc328221aae315ef8617c9ba623091adaa0556074").into(), + hex!("8668991c8bffca4cbfc06f3429961c595d85803a262105907361758d677920796be70d2cf820f0b1caaef708d924e676").into(), + hex!("ab22ff4c2ec9e683d2d1ecf57f7af9b3aab1cd289e22b1b66f37dc3779e83e35211f7d4919dc3ef0babf876d491e0bff").into(), + hex!("b8696c811af5d3360951d7bcc9ba4e82e17a125501e91ff74b915de28a8cc217c6fb05d90985fea7ee431e519e494d9e").into(), + hex!("845c0bc4769c428fb30e63c8e4631f22e69f934b0e6089431ddda2c232172a4980cdb6f650563992667a0790f1a3870d").into(), + hex!("b4113cfe79b6b198b517ab5d14900a7189ba78b7ef85d04551e18fe1ce6a69564377fb86a0e11627cb794d1f416fbeb1").into(), + hex!("aa6848837b26df24b04198b0b09b77d7b59d26dd3b20f7843352f2436c046e9975af2985e64fbd6267d897e728a9e721").into(), + hex!("861456839cb76a9490dbe055559e3dfe3bcdc41646aa656d8aaabeb4c0e39a1b370a4f0107b78f900614e3fa09b46bb5").into(), + hex!("ad6a8ca9a21b7874f8b115024a7f079f5a1dac3b165267b33a59d1c8de2065bce4552369c930e9a949d0b07110a71452").into(), + hex!("aa83179de1682892257c57774844b040299789430a262c8b44dcbd81f8062deaa731ff4bebab1a815d3148ec719f4cb0").into(), + hex!("895549691861582abd102fc19a4ed269b335010aeef45ae9ae6b0d9c6d26f26c31371086ddeda626e76f7f07dc622fc6").into(), + hex!("a94e590989e81d269b5246f22a9c97b604af58352c70100f8a20454fecf36d19e601dc1201342841ab231dcefc461f2a").into(), + hex!("82541b5b7a392456d1936373396012b086c370e6dde41f6d4409d35373d1586d3c7119f6f0d1e38bce9cae67c97fcdd8").into(), + hex!("8b89fecfe83adce613e75a52e785ffc90847c09ed779ebef4d29048bbac04b58e27311461c25d4e68cc0e6778228b037").into(), + hex!("b3eeb09bc9ace8a62b71747711bbfa308b746c86ca87297cf2a8e768765a86ed1add2e1acd2925cf05537dffd4dbec50").into(), + hex!("a5364dc8221a37d73250f8498d9b6e163babfcb01e1fdef8ae570538e128b562a0ed8b353235159c3781ada8506ada33").into(), + hex!("ad0d54f0f67d0231ca0ab54ae881386b055c169fd7415c12e511e5ddc5d4b1beba0e1a157211996c7ab61f6e94cacd64").into(), + hex!("b4b978f7f9b084c089923e73a593a24d5aa22600c879eb03645a19ffe8b36cb8ae040378a378ebcb4a5b73331a2064e5").into(), + hex!("b3d79038de0c62c667ab4213524679934b33de22111626c258bf8fd8e16134425549dc7e3dee15239c32bcf122f5bbcc").into(), + hex!("af7a243eb665d9b2c37033363b252c42bc2c202c266ee125b5676b6f9f94ee5d46d3e2ca217f107719051de511625dfd").into(), + hex!("9726a0d607674fbde3fa5c346806c4083e092921c303ec86bf5a16e4b760d031a24585ab407b9b1b0692d12276912961").into(), + hex!("85a8eb7ff82937e2ccddf2f049af9d871c653de4d71ab36b198bbc7bfef2e32eb3f22462dd01affbce13813332193262").into(), + hex!("a8889fa7291017a3363a5e14cee8cb24c273be4aeb74c4b0e1e375f70060dbc9ba296b291e154eaa56703b8e3f7e85a3").into(), + hex!("a789cbc52c5468bf404fc1b19651ef6a805d96ab8a8991407e149a68d10d1f67d1d4b380c08f8be1faea8d1b32bb8c1a").into(), + hex!("9535392a86b73ac66ff8ac1ee0549d266a9e25e1db542b077d136d26710282529f34c43dd94ee77aa97c647e0e05356a").into(), + hex!("a1ae9d5fd0ca16021e0a33fa116eb5b94991aae02efc6f116e073def47253fe2d1f2438275f09f204cec0610ad523ff3").into(), + hex!("b5c6c5b37943e99bed4e63c9215bea95fc365a576a9f8f0b9da8d5ceb5f9da881a273f5692b0913a3bb922772923c07e").into(), + hex!("b3b0144fb027e0c7f1a0c4c703cc5e1c09422be38de4e10010c28bfac358a6c834df6794e5007ecc4c8d866ffb9a8725").into(), + hex!("aaea409068fd2cb94a7c6fe031ed47c7c5b366a33905d12a107799aea57a052b9bfbb1c4f88b1b3775d5bef7d6204b73").into(), + hex!("a10cea5af8405d807b66ad492a1aab8618324ec3d9d01181ce29512e38f03bfaf556251dc490b3d1e80576bbecb27dbb").into(), + hex!("b700d4046b0be98b3cdfb8a2eeee52df68bcc1c5550d1c17205664b0d896028bddaf5dd38482645e76f643ad9d2ea9ae").into(), + hex!("ac9163d5f57a2d1def901e74c1b07f4d14b1e9a5c362d3b082c45389ae8add929a1dfb0baf14f64790f86cddbdcaa32d").into(), + hex!("8e3b79b19d49d77844e3401c01984af7211268bdf6609919c9867a83cbbea21870ff108143795a85fbdb2c15a2d127f1").into(), + hex!("b9db73eeeafbeee4edc52c1fcac7de6d8acd22a8d3d1c4cf760a81dfb6cc91ee454385044301fb2253588f23f3a24079").into(), + hex!("b268631698668059d8eb44d50d532c9d8c49953ea2029d6a2d0eaf69713afc85c42d989cdd3bc0a479ad77cbad24fc0a").into(), + hex!("95f74950a24d82ba9a9df5d839d17d7ff830a5dff38663630efad5abe9c58724802d5bef891ca2b3b81923b55a94c6f4").into(), + hex!("969297c612c35347019f5bc80d2887a3c95c8ffbb011f5cefac63a1f51e48dc84d961aed56afc353791589a45c871cfe").into(), + hex!("98743e9521e5fb6a643c086a00423fea51b8ad2e55bbeadf791ede16eae64f9fa45c41101c6cbe4a8e96c692fc57c030").into(), + hex!("9374001ac3a8673e337b078da1b72090bf2450a5f53f6a600f4cd43ea4b5fe86a73d14bd0103b110f23e417dcb4c2e47").into(), + hex!("a91bd42a87f28a6fed7fac68b5306e5382a93fde2bc9aa5c48b747ab774f9c557343cafb46dcd4e93df5aa95ed832410").into(), + hex!("8fcdec0a825737ee2c61401014287079e729a8b8e49337e99b34c25dd9da570a1fefc532a0cf6ce1bec80be2d9ff46e8").into(), + hex!("8197cd84016cb41e4287d29b3a0fe8d221868e5993aef8c15c1578e038f9c43e93bc26dfc67fbef919322178223d0b9e").into(), + hex!("a4607e2b6ff802a4e497c53b206972d35520c78f14f7d4d78514333e90b2a8852603bf223f1c9eee3793008f87cd8fc3").into(), + hex!("a72b6185e451b3be2c140fb3f48225927e7c052805682e3de9b2c826997419084bf1e2034aef0c5d364b0004b3b7807c").into(), + hex!("aa99a2cd46884d2ecae4257c1db8fe1ef6b0cc1a0c25dcefb53540ae91ea7bc8955b8acfc6d96ef47fc3a5733f2f28e0").into(), + hex!("878ea42dbda59fb6f839f0b65eec295f2d543541f4fd576a60d104b94b49b1a1ac6e9a15ed3274e6305de3f35ce1e3a1").into(), + hex!("ab2f77f6036200b4ffd3192b8d06dfdae4eaed4e1105b27e64ae2e120c909095e59f4dbbc44e818afdffc7f9ea1f42be").into(), + hex!("a37e3fd9b1337734cdaf34111762403db11b1aa0324937a17e053242a9099b3db0d396b485ca996f91117c64623915bd").into(), + hex!("97c65c28a6a81690d4d6ff17d5cd3be0e15ab9cafe66e6f7b8da66ead8beec561c3abbc79af52a8986d576363f14cd27").into(), + hex!("ae138b3020373ca238d5dd780862fa28a2c3e05903366cdc0fd7a142db3d18eda63b8d049abed37f1fcb25f6cdedbd67").into(), + hex!("a800bf90b4e7aa7b5b00fbe6b5f45067e0d7ef2b1ed9a626211e07b66b12ddaf90ed05d369f8da13ecfd8cb499f192a1").into(), + hex!("96565fc4ef721f754b6f53db97d32ef5e5c3cc0f55928eba3eb341f4962b815381178763ef05c21c1247124d592b4449").into(), + hex!("b4896f9e2f88c990fab764a4e006f3f39ba6bdd0e1c75fc8dab2973544e052ce63f8c61a6ab213c85f6799d988a6fd61").into(), + hex!("87aa22b60a13edcede78e629d54714436a8d7d1e6e232d4df6047213b4a91e61e5feb38216e0ebb209f1dd8d7e4f5f9d").into(), + hex!("ac4d5e8bb39a2564f3bff053e2058f261209cf14e65f7dc540070d40dad7ec4f5fa81efe6274aeab8691b85a774307c5").into(), + hex!("8b13673c306988222f09ad896a75a6232ef3bfd2f6c37c2d751668466d45511542fe982ca5720c6518891830674e2cfd").into(), + hex!("afeaafa07eaf14f248d2a34e4f86064c5bccd92d3a6c0ace1ae827dd59111e9b3cf2722a270234eb5aa633c12e140354").into(), + hex!("aa31119422a52a7a5ba90f4e0b5676434b4f05289ea3143e8a2162e32b1e19b582a586da796ad9876d0991274f3363a8").into(), + hex!("82c78ac9f2018540eea744c003a76cd7bc8984103e941c680a4a833a7c81defdd28165256890d534aaca2991dfd856b7").into(), + hex!("b4a99846af0ffe14d0f820a574d42571e0186cd078840ebdf0684c806c08eac1d6afb2f7f9f9dbfee19c2ea12af5307a").into(), + hex!("93cd10366714618a7e8d4edf3c93a9bd30a280f765cb93071a279eb5bc4fe8dcff8d91a1efe8fe697d1cb5e760a07fd0").into(), + hex!("b17dd2a8817471efd91b60ee31bb5f7c2848bb40251dafc0e2250cdeec3202cbfc7a8af6d7f5c3300a53a73bb4a11b54").into(), + hex!("b396d11ed53f287ecab591707ec5ecd0c5d34a67854783dbf263fe2614c707a2226231fd8dbd6bb1ca0760f06f2fe7d9").into(), + hex!("9982a0fbdde6ad91f35e64de34183e4e7f7df6cf422912f3dde0cd16394f0f172dc32c4f68f2a09647fed32894471fb6").into(), + hex!("ac5889086fbfd2f2570191b5d92659fd17283509477f442dae81a8491b8641a5f63e275659e6592ebd0e62a8c7a9bdb2").into(), + hex!("8fd6151866cc75461b69b4685ac4efb5a21c10d5b3291617bee4ff300d90fa2290319967faa2b7189c090d3b60994fbf").into(), + hex!("adb4459e4e6410606a74742d6e48f7b84f30ecc8e849b6af9b4b617236dedf1707ec388a97523f18ec7e047743fa7151").into(), + hex!("a9fd9329ea3b6fb77dc577e2c891eb66c61a575ab75a66dcd897f1127e8bd9ad8d3eb9059c7f6a8b08199913b83e5ba7").into(), + hex!("b94cfafbd3ef7673023ea37996084acb3109ffbccd210184aaf8ce8d29bf36390bdbbf2870b0970e66831d28e90b248b").into(), + hex!("922404dd76801113ba23df87ba689e5cb609c94930370576d0d16e99a489de0fb079fb273159b8b07fc58bfe4f787c70").into(), + hex!("8c6005451c02b18458c3f069a521aafb44fab40f4260a60da6b9bb5f920e91990a868aafa4b6b071a899d3bc51fac72e").into(), + hex!("b0cf68badbb39413649b3171281ebfbabbcda1123549a4c6c09dbd4dc0427b51d555056c79f38840c52cf920dfa2c8d5").into(), + hex!("a08a09c8dac1f7bbbff2b7ea96899b64fed53e971569171224e675399467daea6870e48fddcf47179d3c7eab4cfde3ad").into(), + hex!("881c89cfce577898ad367abc2cf5989c647bf8904be5e061e632256b3750b0aedaf4d30c17f994358aecd069b62cff09").into(), + hex!("81674253d6664d414f667b10f6e8edc32af0a67b2b99d3e4657991f8a8b1dbf260447871c1c680d92f99abdf3da2d035").into(), + hex!("92e34adb15141ea58b2b481f9dd69e2584f512531bab13779fe99e18d48b6ab039bb28d9f444d517298e11464ebde4da").into(), + hex!("81ee1554da84a0a487e52c57528b69cd79f1b6530418354095ab976207e368379ae2fe0a4a340d209f13ac9783cd6d5f").into(), + hex!("839c0316ca07242ab52b76f55049f2da3e83f021591b0bba295677d80d4b407f88b0d207f3ecbe7eb85f19eba5d152c2").into(), + hex!("88b0ad748a61db5eb96016d9bf16bb05ffc4cf5ef56569397e9395f454fc1f731b48dcd8b3368163c36a3fb41577286f").into(), + hex!("b94fac438903b1e6cd11135b8fd35b91b61a0354addf8ead5cae4fc853ebb5be68bfb9901d8b4d3bdf58224675b6d675").into(), + hex!("a015eb1a7e1b814625b13c1b1bca7f738e80be5972f3a3a27cd9b21b033f16e4b5934bab69e38a6edb8d03e84e8725ef").into(), + hex!("83ef4eb739353f7679b27d3679551b2a0eb1bd4d372def5c0e8e5922a9ce7138dae4b62d5c147e6439a551d3ebb1e1cd").into(), + hex!("95907a3b288f3d4199434590340293881b94322e82f7fe9c186da3fdad7881b9a92cdcc5fc29d4124b1d05886bc9ec2c").into(), + hex!("868078f74e35b72a894d72f93d45333e423b1aec6d7e3cd7550254ed6a156957d4c5919489a84986f6134be8334bdf4e").into(), + hex!("8f089ffa10d8ff27470b8f6fcff49a69bd06ef3f88faee54a6bc8ea0dca6bb799199bdcd9a9e7686c43302cbadb584a0").into(), + hex!("980f4614de867eed7571cb3100f9566542b90d2b4110806dbad64249944cf3e4e484d03543107fdff0e91634d5193530").into(), + hex!("abab8578ecc6096bf063da248b376bd9e76a8b9364000a98c85813ada835017ffe693f908aa789cf09ca3020f3bbb9b8").into(), + hex!("a2beaf5eb12232e44bd251aaef3e007989794ad1df7a5f41ce1e6d862ff0607db47cacd4b04d684dc22d3640f6b8aa16").into(), + hex!("8f57e3d8d68264b5a07c9fc2399db0dfdd079abac46e1023373c22377612d3b005053e0df490d2435110c9fc2791b8ec").into(), + hex!("af7d2c45de945cb09adeaa1898fe0b026b5a5c5de2ac21f3b2c298d82fe4ae253d859c47f71def164a77b542905bfc73").into(), + hex!("93b026c1b083d82b3bd52d0004985f374acc81f754f7dd4e563a9ca5b20780f7872925a03e48d2154c526723b6d3fa88").into(), + hex!("b8a11c00250f9e148086818aad4dda9d69480b378ef5ca9e2fe85dfbb709d2a919067d9abd2eafdb5202cd334081b5a2").into(), + hex!("a7026b1a57d9d64f2526bc42c771cacf43a718725c2d0dd889b1af12481e3112bb5357e6434b3e83754b067bd5d533f8").into(), + hex!("84771640879d9628fea0ae1106a45bb8a383ca0a9a110093355395968b4d5af9e4a34201024a187d3a31b78a839af6ea").into(), + hex!("b99e5eda7144057e439ae752c2d879345ebba19e83c35785743d6fc1646069b0258ebb1ef62fdd43493498ed08a8de15").into(), + hex!("b2a17e5253f695a61697469df96f3182a62faf0b5d10c150f833543e7b3f5979506a068ddaebf310fe86efaf3adf13e5").into(), + hex!("8ce51f0a8ffbcfdddead1a94f45a42ec4f9e8770c0ca33b58207b8db06f3004c05e88f5b31896e1b7d3c79ff571426b7").into(), + hex!("a775ae2c3c59968d0add4c446c5f20e92f92eebb704016ea45a47c5496945b7d0935b4d8ea315990e6f58ec33d00aa9c").into(), + hex!("93bac2d2038c87f111c8000e721e4637b04ff8c3b7b1e8a9f02cae40e465a1b2928aa226af73ed643a4c21f3fec436f2").into(), + hex!("ae050b3f4784f2c12c902fd1881f6bb940806c0a9d7cceeeafd194dcd49cfa48acb10a09c55ea47b508d5d16702800db").into(), + hex!("a8c6680208271473433781cafde59a4aca2496d58c85f48ebaf447a0c175e784c4a1351d94b5d1a64c78bcf8c84f8f2e").into(), + hex!("8f9f71c20844682e17bfc30d745ee2848bd8782b05572ea64f9b9bdb2c24b3d1953bae317d79e267982c42f3f3aa60f9").into(), + hex!("abc4ec57e1625bcf3c10754a07a2047d359e503b05e2524175b48d5844756ea21f626a65a08f4f7b32cfb0646cc68fc8").into(), + hex!("abaccbb39d4fc4c252997503604764731889f5bd66382c90477df2f2d413f86892f7efdbee0e3a908b3cd69321d3db78").into(), + hex!("906a87855cc2b3765774d3e8c7daa62630fa7ba761ee2a0bfc5594380dc1d9b62740d1190fb882ffaf17c0d869356261").into(), + hex!("afd0ed291ba237f8b626698e3c54b8a84341628a524b897e53aad5231aa01977990c55215a677e9adf4319a98e81bef2").into(), + hex!("89512dacd42d13f5618f1979d9afd93c06247de2e1e9ab6625b5bc9efe24439189a564ea220c4ce2795f059064f5f5d5").into(), + hex!("90238fe2ab6b623ecc990ef8d2849d26229db5f3c8587cf06501e459c7742d81653c7dee45da82f5946e041f129a8df3").into(), + hex!("a420b197f4863782fbd676bdf1808ff1c6f49a506d525952c575c10a32cf63afef22f977d4b7d6258334c498f36e7f95").into(), + hex!("b7d8fe926876d1d01529eb6e30a78db47b6693af3171b2014bd18180066609cca9036e35953ebad7355362fa671d903d").into(), + hex!("a4b7dc0ae7e7d8c8eb65081427f5d41a122bcad56456dbcf4a4e83e188af1e3d80dc7f450766b8e87ed8b28ca6c5c479").into(), + hex!("8d3e1060196b4e8e827052c2ed782aa554540f21690b67d39db46fcf088d10f67f2b6e0d98689e7497e8029bc8f355bc").into(), + hex!("861657c4e2a48891939b49706791a03f63d092643cb491adfefb3cfa7d86ecacccaf86e4a1382444e466a61e29a12bf4").into(), + hex!("92f1c5b122943341854ab7d5c4603e083019344e2252da5664c676ffdd564345d7e28e2a5692dcaadc4371a95ea2a142").into(), + hex!("86100ccc2ee10dedee28a3ffa0bbaccc1ecb19d36a99e6deef1f2373c1c1c3dcb8b8fbec5809304d883c5d60f617d875").into(), + hex!("97c06ca658fe3ec45a6573bde07b3a95d80cff213c9f4eb8933c8dbaf147262291a5ebd55b0e58a4686c4971cdc45671").into(), + hex!("b13e9055709868f723014736b1fde59c05899bbf2aa6d4591d724c35c15ebe6d215e37fcb1e7b8585abab0b2ba309c00").into(), + hex!("8ce0532b968d8d770eb611131978a253e7940ceb627b7364b3a4dd517f26c31bee51c134a5b1297362f6f9b6d714ef33").into(), + hex!("92edce89bea01fdb25da5d0f903de2fee626681cec3db418a9161286a2e95bbb90ef2ccd8dad434b51776f85d982700a").into(), + hex!("aefc7dea295547984ab42a64f2f59ba2ae8220712778a71530623351a44372ddd1018cd8d4951934ae0fa39653ad6aae").into(), + hex!("ab297f28266bd5ed104c1b55088f114592e80aa098a0865e5543e12c6392b0f94b5cd4e0b6f375d1a0d0809d80c1fca0").into(), + ], + aggregate_pubkey: hex!("81a5778df2e724c98b4ef79ff33b9c5fa3ea265de81d49de5c4ab3be2165d32fe15c59c982f758c3b9c522ca5e659fce").into(), + }, + current_sync_committee_branch: vec![ + hex!("1da42c54eb912009030c084b700d2e0031c0a0e5759b0cc593601b99764a725c").into(), + hex!("0c960bd59f4a61104153da676eb38ebae603e9cbb55b0f6677cc1df0d535d60e").into(), + hex!("1682c67e0936255e351f8be6ccbdf048db06a80749aa900bd4265af1c366bd52").into(), + hex!("d95bb6af7d6be07e5d7d27337ab9b54d5bf725ac37671b9483434d22d724bb92").into(), + hex!("3abb1af4e9c3acb052119a42c2d4222d99e8b5b958c520a03526a8177b921cf5").into(), + ], + validators_root: hex!("043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb").into(), + block_roots_root: hex!("ed6ca045637c1c7dd54fbef547b8b1aa3f5b9fa8f0bfa5df26142a0c4237e617").into(), + block_roots_branch: vec![ + hex!("620abd1a8757614facfd9d2fa43795281bccd4055bc9b12e5cb3742a16a9f9cb").into(), + hex!("14c793be544d5fe1993a1b25d39f4b69e832e914c3d470745276a25d982df4f5").into(), + hex!("46aea5f0a7d66cffbd55e676b915be97cbf3dc6281146cdf4952047214ff74bd").into(), + hex!("0ccefa47e43d03e26def9fa07bacd91a5a2a20c6c5dec2ea090f71f91ac99282").into(), + hex!("f03f3d7a52241ab959560beb9b748a8ab93e2b7221c8070561a12a5fba8d4434").into(), + ], + }) +} + +pub fn make_sync_committee_update() -> Box { + Box::new(Update { + attested_header: BeaconHeader { + slot: 5808573, + proposer_index: 430716, + parent_root: hex!("0be3932fbc9ebdf3220e2195d87653f283d9f999946e53f6a9f6172b6f532779").into(), + state_root: hex!("cdfacee5c92a351843fdc4591ccf16c4f040d0276add8421f08dbd5c71035a1f").into(), + body_root: hex!("3d248ca71ec98250b8dcdeab1207806406f1434c11874655af56925da6bd88da").into(), + }, + sync_aggregate: SyncAggregate{ + sync_committee_bits: hex!("ffabbff6fcdefbebaefffff9e37dfffebff57f7bffe3efbdfef1f7f987751dd176f3b3ff7bfa3fedff5fdf7f7afff7ff777bef5f9f7fe75f97fffe7dfdfffbdf"), + sync_committee_signature: hex!("b405701a0227b7c40805504a66069fb5ef99cdd84f1e295c9b4a4eccbe4d93718740efa9f8eca62f563dbc73021c00e914a69b00a9ebaa906e78f26c1cb8088af916096801c787f18f493b1479fd43f1f5b28d15af827a1e580713fa82bfa1d7").into(), + }, + signature_slot: 5808575, + next_sync_committee_update: Some(NextSyncCommitteeUpdate { + next_sync_committee: SyncCommittee { + pubkeys: [ + hex!("8e9fbd36b3cbaaefc176cf46336592e2b59a51e3035d095da9e1df9d2fb5aac5e47ad05d27784ca675442abb875a6559").into(), + hex!("b4c6164c5ea19f3da5a76a2435db598bb012ea34cc8fb6d749f1588463e5c39d29cb3d45ceae0543372246549b17deaa").into(), + hex!("a89c780da1a713e86b149d63312aa840e865dd926565f0ee9d9627d363eadadf5a4bd5f79d8039f2e2927ed7fa60209f").into(), + hex!("aeff2bd9faa0201abd7dd681ff97888c0ae71d84e71590f424facb2e37b5759f07d338dcbb695ad6ffd08d903c0f92ec").into(), + hex!("b89cfb61a59cdcb61e9f3ed76cb5cf13c907bdf6b2622e16d140743c5021d45cb6d91ee94331130b876efd984575948e").into(), + hex!("a2c889cc5195532bcb5c83d035cd6881b889ffb9d0536843d3fb6f7b1c093a927162add5ab6ca5f06e7c3ec4ca4522e5").into(), + hex!("b5966a6d047ef679a9613114149530facbfc7b4bee6ab23a60853f45de034435b624ad0126ac6c7d6a12b1be93177e0d").into(), + hex!("99249360fc064fc2778b37b107d834eecd5eae29e8f10a45d946f11fe358db065242482935224226e83f518fa6916962").into(), + hex!("8c3548aa879d974c5542e59ea43bb34db91f92c7d21eca5e3e4fb9d01364c21e8e2341eeaba1d22da67f1f455644afe6").into(), + hex!("828b95590a46cdc4756fc1a7b7d7c4031637494938521b74a3740a970ff532b88ffcb5333197088f6700925dcab5c42a").into(), + hex!("ae2e6ae80c16831c02170dd273ff6808e4379a8baf00e707d497eec6cb50b5a1f132eddf053f243765a54695ca35c443").into(), + hex!("a7bfac686f7b307d794fb1740a05cb1a6ef14b06150e64353a0b6544e7b0c5e3a7c8985d257c5bd74e411c0cc8424479").into(), + hex!("b3671a59e2d425ef0ed109932402ff7dfeec72cee39c1840cade48a13f3ff36bd0f9b3931d0651fddd214a2dcaf7bf89").into(), + hex!("b56c962ab20fae058c256e37ba4091d7a9e5d3c602e3eaa2d90df65fd5a11ab68f245a5a6e53262335c6dd4f3e0e51f6").into(), + hex!("a8be83de4b06ebd8c14bce332a1175a4c651fdddd4a58ec85bf4c68cbce83a50dfff8c26070d104556883af678693076").into(), + hex!("b4b33b7013c6af21797478b14b1dc81fb7c5661fa2471d8cb4eeaa62a62f795aa9be2cfb65ff6b957cb7f89487a587af").into(), + hex!("991a42351791da02bf6c1a9ca8248901657d6f2a95225e4827ac3171b5247cd31f9465c9ab1c2b78e268c82b61db1f36").into(), + hex!("88a38e70998cbed82ae7f9c192e06df8abdd35278efb25a1112246d46a3d3f0bddee41f5c492949f15e651ed7fdd6a15").into(), + hex!("87dcb7f13c6af7f7d102c643db0406ac7fb06fbe1fc647f436ea839e75561b27beabdd6133da332383bf22ed4f83fb9f").into(), + hex!("a441e2c51448b6b2ddb38dab213d9ae3d1fe70e91e1feb0f98590b5fb6f3c18ec0adccc221fc44ba027511c52e5fa626").into(), + hex!("ae5f4dc4266016943cbe1db6538619c430639a1179d246cb820adf8edcbe55e9f79471134d06365b0d459b280aa2282c").into(), + hex!("8165bbc59ef3b15f29379a7ef90d8b3610590c662207ea7c49267f36b5b62af3d48008d182ec3384ca7c1063bd25b284").into(), + hex!("8cdc4e6a238afc55406920620fa90f696403afc1797562b424c26e679096950e7f42b8d8327ab0d7573608056364fa4c").into(), + hex!("a1f4c958f7bd1182cd4ef88561eb534c9ea3563d149a276fc256645be0b2e86a3d642ac17261696ada39a04a866973fc").into(), + hex!("b19e5ca1ef1d4fbac5633cd29e9510116bafb3229749e0e4444caf9819fabf9c4c805b5966c02446c1eb0029b3c1293a").into(), + hex!("a4682af7e19328a145a1a5c43ad3e14648b90f664c6139eabe1a13da9b763ef23947dc3ea2054af7d0b7018f7498df51").into(), + hex!("999ae1a8f2e0cad6a0378e7e0a67c8a6ef4a824043b34e67074d05ceee93cb7b49d3c3acc961f1aab69b45f89d12180f").into(), + hex!("94a9f1686e91ec733799b569e3b0313db64f3a219b48482e2a56c21016e800d4373c2f8b876a923e0753a464e5fe4684").into(), + hex!("b4936942d807ad09cbaead9f56ca124617fd1fda2ff5cd94fffbbdf5ff2b295867acd1e41599928ae455d597ea45cfb0").into(), + hex!("a74166db86410c9722e657cdd0f4d1da86a4f83168e2bd9ac71850bbfd9471e1ed88a6476b75ae5ddb42afc62a9ac121").into(), + hex!("a97909c10241e046dc707ff9d822c385dd68be297d6b54c84fbdc18f5a1dbb3350e93496698d6304ad1d6bfd34b4a041").into(), + hex!("b6d9b775129b048a6c577656ac2de15135c2bf1a3c7c8140ce20a990274e42d7b602ebe932855c1d03373797ea0bec63").into(), + hex!("b59f975937cfc8eb510c1da0a7fff1960c46b9235550cd6decb514805439f08b8f18d88ae0373bbf50b028a08612d552").into(), + hex!("8af01facbaefb24cc4c11e13c64445600b1d716be66908964ef79e12c0eded04e1d23295444818f024e55df2aa911034").into(), + hex!("b79607bbe31f159b208a0d1b2f95cc5373631908292126b8b75fc44b22a8bcc9550de7b51ada33e5596d0f17d5f4e48a").into(), + hex!("a1a474a66940bbf6e601b6c6e63103de2d5eb76d7ad3d39dbd74149658a14e31143a9723327a73bf72eaa75dea42c3c8").into(), + hex!("ada18b62cf80098f36921cb0c2f85200fa362721c4673546f8554e2f5fc8639f2ffb2cac68e888af7ead8c660b0db13a").into(), + hex!("9198582e8aebc174dd168c6cf20836a21cbb6baeffacf9f933850d8e0fe0619ac1ebb99fa6fe902c75927531c108ee5f").into(), + hex!("b8a44b23d29cc5ae1f00d5384fd06f31b73ef1a7ffe334b59db668c924aef2cdf60c3070a44a12b52a14ba185198035f").into(), + hex!("a688064e0b3fb3baca87d711b29419a02c06e6a1dd764af31574dd84fe870c8ef614d4c2d42fc9508711dc05fe373776").into(), + hex!("b86c167a1c6738bfef1feb7eef8f553898f69a933876acf675596fc2e39f0a8c83ac37df69dffb669fcba4e3f1caab92").into(), + hex!("a32d52f3e9acea45dfe9ce6c577dea8200e68d6ca39eab5d6fd24c508d2028f533b8b04f1a4fca7965315ee5dc5e2809").into(), + hex!("8ec96bf235d5e9bf36382d79b4bd1be8a8e2b23a9f7f9e02ab6d708e96a1c12fa81eb236f02b0180a0cb9f3c1bc28cc4").into(), + hex!("a0261a76664fe2fcebe1501e18eac7bce32b947db7bccb7b746757ba51cabbc8bb385600a99b248887edeb84f82a6f49").into(), + hex!("a44313f945a1d462376e03fafe6d7a9659dd81046460f45ff8914732ed268b2430ff632aa0d368828c2076144bdc8595").into(), + hex!("a55fbee79559e1fa7b85718306185e3769a92052cceb600283d0236accc6ba2343799c1856609faeb7c685dd504384e5").into(), + hex!("8a3d4ea2eea81742fcbde7a1bea5ffda55c58b5e4618ace17773057932b7216b96ad4a117d9054de18f71b3345a0076d").into(), + hex!("aeaa0984232b1fd5607a1a67051d42df3ffe71363639e5130de243cea84c87554e6597f3f07952b7d40d19b6e18957ad").into(), + hex!("986c868f8f25db957f44a39bc209f1ff8e98e9bff52236b2473b8ba977d0b7e90d146ec86a518a581c5de796290d505d").into(), + hex!("b1cb0755c54e0619c8306636e926930605f15901c01e36822131dde1538b063d0dc485a97534ef1e12f2f0febd1092c5").into(), + hex!("93cf415d4d7ea309b85bfba7ebefc0d1ea91b8e93fa351262d9eb34b728c7a516ef0904cb2b3549db2b3b3f788b147af").into(), + hex!("97359ca81e9fa330f4c0a3b4de96ff45391c2f83247d1f73a6884bd123d34edc66a4d3f29718f5543350204488ee51f3").into(), + hex!("8348c9b229787630ce26a41e7c016adeef5dd3ec1f124081baf9db4ebc1a3f3a37b40d94183ea9eedf9a458a2e65fb41").into(), + hex!("a4c6b13c7dd27497917bc9a4c4a91b953b88c819e147087b125c93657ad082971152d384e8c512f48cfe07a69f54fd95").into(), + hex!("88008b395718646492ab944a9139b95251214c42e90720c703b19b99afc971824bb87c2a4d40202cfbb62bd2ee30c15c").into(), + hex!("946969ae721cdb08dab293a638387dca6045e230cd7c7b7237c75e123355db9b8e444089633d0977dbb6e42a729ab4ea").into(), + hex!("83435817ddeeb242c37d31877a55194f208f4cab406b10a4a0605a54a19745f3517a880dcab8a5a4422c0e19e2ea8a2f").into(), + hex!("94fd0a0f870a6ed2e6a4f53f5dff5b5adc1a6943203da6a34c73694702733c991e146f8c7108ff35d563fb67f55a106b").into(), + hex!("89bc609d5223c73afbca46a8c3cc271990a8bac5191f1ef6a2c88d7984adff00d67bcfcdb3958c259e17d5cba62beb28").into(), + hex!("b39077093900919b51e68f647d11e0f78359be69c405fde5735ce6839f739081437b899f33c6c9e6c86d4bcfed059186").into(), + hex!("a0a6f9f588e336c14b91a9c0c56085830611df85ce6e99d759c72a4dbae500b47dbe736287f6b2d65b448a2a0e6ca237").into(), + hex!("8222a17ad961ad325b819bd0625e079a471e597adb89f2170cb490c40f6b8b1a08b2e23a1abec02011452d589b183702").into(), + hex!("8b77ca7fa195450ab3399f88341a9d323e8b9b7b9b2ca30985d97ebb287e1f9b7e0279f22ab3a2dad682d7906f6c8d59").into(), + hex!("b6b8389382d3336bc5ffdc752bc699a6bb0057dda7879901c7633787a2b412fb7852fc896ce95cd09a9b98c76bef1b1b").into(), + hex!("970a20613047ad84b61ede90efc41a91ef7259a5fa79ba23964ea907fba1fd88d2710b69fe5bcb0d75ed9fb68d02e557").into(), + hex!("8843dcb71117b6044b1c7eccb5010d0a2f93775a98909bf23c1773ac9eb1c0f43aba26dad08ea7823da593c38a30598a").into(), + hex!("b5b147bf651ef9696ed3ebcfc3ce226b2748a4c4e7cbfbf12b3ff5f1f0b2ee1372477e1d7d8aef8d9ce3ec602a63d01d").into(), + hex!("95433edd328aa9223f521daf6d78ab272fb83150bac78ef6639cbc032de8834049d4992af0828946eca69f359987584d").into(), + hex!("b2f4d2154ac750245e966f62b92022a136ed0313964ddf534ff3e9b4456cf58bfe429ac83b718bb38db5a4fbafab23ab").into(), + hex!("b6b1f2a3a99496dab156c6159b8c98990501894b5b0cf200c792bc462263cb0aaac570f5a785aecf367a0531ac2a87e2").into(), + hex!("9985e3ae265653082f068b8ac4c09d10b2543f920a19911cddf18ac53a7f921da86f11836f51f2adfb26c7bf4ad51efe").into(), + hex!("90c31f4985c7481e5939766dd080f6ee01ac7a4fedb9954b9d1fa8fa1cb0e6e7185a1e31d8503542f1d409ed2f550e88").into(), + hex!("830736923cdcbf7de3ae650768482845ed9b45c4dc9928d66481c76ead9b27427a96989389c8583a153851ab957d67a9").into(), + hex!("82312631f5b301fd3ecd8b0a6e83b130b6e997a5a1e6255e883c590efa00b0ac3bee45c15308efe824aa665c8d7a365b").into(), + hex!("b8ed7b3c092f7bb05aca8cc4c2041161426908e8db349cdd2064e95044f9e7649cd569039e0ef0a94e006094113d0e22").into(), + hex!("b1d6e5344b74a67699cda807ad4883369a77d79335cb8eded6e0ad9b64c8661b7ddb47ce4308ff69f947fc173f496ac0").into(), + hex!("922f0a2f84e476e6fc00c196eb913ebfaa6b205fa8ce8c8453330a58956872eb2e4ec0087b398eb51819ea2d0aae6b21").into(), + hex!("8c729483a3d2ea34337d9c6260944da7e2ea8646de66d39617924681189c79672b0ccdb63525cb4635e3cce1d8f72f13").into(), + hex!("a00af936fe87caf17f5b365f59d019f8438a62b8f174510d863da59097986011a9e76319d4125ceb64f1d83defd822c4").into(), + hex!("96d33c3832bf0af5900a20c067bd45dbf3f0ecdc086eb065afea6c44f117eaf9ae8841848578d2915452e61bad014803").into(), + hex!("8a16c15a161a1e898bf06a23f62a9ec042c5b9e875cbd54d62e11be181647cf09e6a0bc65fd62017ae150525c16ee746").into(), + hex!("b4a0443c452085bb77b5790be42005178dec8f9085e2f1b963d55de6978cb608b7ebb42e4a84f24350c768c2e78d22c4").into(), + hex!("9168aa07b4e29c67723f4b87a025fbe6876f13c69505520b4dd6b387f16530a886bfae5e5304539564debabc059589fc").into(), + hex!("9504db9c5ece4ac0b703ccf751503665746bd580f11106df3c8a903ae7a5c9b0520dd16c89671967e2aa12775af4f67a").into(), + hex!("a776127d4e2e46c7dee8559ac56b266e7eaaa26eb8db0a7f4df0c66fa1564a349f414c9091d1e4c3e7ba96938916c769").into(), + hex!("a85785ed5832dfa8c9a5dd9d20523591f04536712c19a38c2c1496ce9c8787cf37964d739f83d9979db5574ed524d557").into(), + hex!("a4ba9a3312c2c253394891714719d2cb369eea993353b07f9a6efe3ecbd245f08d69f3e1302d6ee312e743c05ae85cf0").into(), + hex!("8329604134885c08173b14b7c68b74ceceb3694a0a3f7997f566ba94bb3fb2ad3f78ab3d02c496858bfc95655f072e7f").into(), + hex!("8e3f5485c98cc317653375ceb44636054a3202045bea6e9f6faac128e115de7a658a49a6432858db7b4b14fccbb93f7c").into(), + hex!("a706c82514d19152bc4097f8602f792a4917f5cb409c42dd42a5e4f2ebd1bec8318019934ed6d19cb43123293bf4ec98").into(), + hex!("ae14d5f32cb99bf3eb0d844157f12b836963be0d6f91b776f973a66701924c1ad9c3496540db4292580e6be871486486").into(), + hex!("a45ee325452d4bb2c60ef5be60b7d601158ca1cfeef0734727562b94ef8f72190005567e2007e8940f8cc538838f1147").into(), + hex!("b8f1fcdabf33ba011c86487a082b19fb146de932a469b19518cda2ac046c319059382cb8ab3715f8025573ab53c5cdd6").into(), + hex!("a613f3dd6c8361893be08f816c640cdea4d57d3207704774eeea8818edf102cba7ff7b06c4c5d0fcf0873b09f72d1ef4").into(), + hex!("aaeab877b1d16a4db6e47a8a864e073c4742e0a84e46ae8dea1a0eed0d2cc9f23adce9e0c8d88464ec0c059df99a9583").into(), + hex!("8fed26ca2cc519a44ae38398d856c3f75d1ea6cd02dd36dab004188f3ef2167cd67d279580f37176dd70c1a0ab08d72c").into(), + hex!("ae18905c02f96e110f40d3bd99ab26bf28e0af939c6945966fd5e3ff440e54bcee56d667a0a21d8326f88e5c22e42506").into(), + hex!("97d803614adb6571f4ea11833d0d9ca8221e7fc99a960c637d4990a72727ed2713da874bf156dbaa70bd4c2f668681fa").into(), + hex!("aa86c3bf79ebf46e1cee54f517b7bdced4c7a96d3ab27405e7d68dba92ee6fc7fc91a107f3cca85096f0d2581cb4039e").into(), + hex!("b75cdcde1702b5bd6be180dc8ea26e5534da77b1c7bf711c8447a565a63d073474f0270d78dcec78ecf5baaea1f75d1b").into(), + hex!("a36e810e50d283e8ef625cf684c1fd333a0373e5b0a9d81ba40cabb76299af93c536285c5d7239e86ec56905245ed2b8").into(), + hex!("a16b6b41e5c31901f3c0fc2a7dd8c084fb508947314c4bf4b6fb338d95ff2cf49fdc5de1d6b9acddb1b096b835df6ad0").into(), + hex!("8b580da99256b1d0d7a90dc46a98ce5132fb3928d416f2df5ed1769544692482ec8f2ad5f57871041d8c78d00c949a0f").into(), + hex!("945dc91cffe575f06b4b01fdcd580da57403469a21db6ffaa77ae06d31b8a2aa9957e26db1bf89554611f51f10c8f73e").into(), + hex!("8c55c4000195fd1155ea608f586a327cccd1221036ffd29eb9903f8f28009083203f18480b35cf82e0390a5ffef4bfb9").into(), + hex!("87cec982094c85f6c1e402b74b52f7c0495ab4a2d3f2309734aa0bd2bfdbc88b8bdd9556664015c7d9fe2f138dd7c807").into(), + hex!("8fc576e4f9057d82e2fd2270a787c596bce5fedbdb9f6d612c2caeb1a778450d8c1f6e86dd011a45f3fe7f201e520438").into(), + hex!("a43eb1acf0de695d478a661a71128ce9c58923e3adfb62728a2e9f185c9f46877db645398546a300b75f2c849f5ab14c").into(), + hex!("b57be020fd23d3fcd6057997099fdd648dae32cb750e8d058b62a5e902ee5ca27771d762020cba2985884ffcfded3500").into(), + hex!("ad3ad1089c8232280e9fa2f6c314ae57758cfbc3a0663ad9517e35b74b19e49345e03d1d33d0d7b69d736501ec5b3f4e").into(), + hex!("aed7faac2e65c10b52d7b3009eef010a624c7f57a5c76c55afd310345707bc8959ad619101b9c1ee4bde44152697c537").into(), + hex!("a6b177c7f945cde42c5389f7258689aefe1b6ee0b243f9901c6e60ef1bebbea9bc297689cda0c93aac9b28c7d70d0022").into(), + hex!("b079f925c29da333461adc949ff4daf19d0500f516d95e3a4c3dc2e2f5ce26ba0f08b2473c03b6974146b239532deade").into(), + hex!("8c573c73d603c8ed73ac3eedacd8ffef4c18425699e30d46be2dbbeb3590380b0fb713daf3ff3cf7544da502dcf35cfe").into(), + hex!("a39331c8acd40377f020611ac9f3a758832e0a644a5cca318c01e654696fc607e299b744c0cc2ecee2bca755c9aa3581").into(), + hex!("8e71e261664d5a6094ee912fa7e3e866ebb5c4c610062fb5fd733359d0e5a5d806a3370155ec3b04e83cf7a2d7c4a0d0").into(), + hex!("ab048af1200dfb67b4fb6bc8bbcd8344547e57942f7397c06988c9c42cce53784a0282fc13bc878635a3317b8f306a81").into(), + hex!("b32fc2da89d3a3541a61338e6b0c5a7f477a23bbb9a7c63b1087f36c49b6d9a42d4720708af496d82c56e1e6836f5cb4").into(), + hex!("97d0e8f961033e4aaf96a75f585d16eca691dd05f4a5477e8d3a0fd97d02555d67b29d314b5d150dc0de3b72810338fa").into(), + hex!("8223bb67c99eda58237a765c8fb426871a1a9e02e6e91d956b16e57b8dbc30c0edeb76abb30ecb2f4139a19922a4c62a").into(), + hex!("8d9ec5c0575500e433a4bc66d196d404b8619ea38b0dcaa036e1c1453eb23c6949509243531ce59318c22db6e33ee1ef").into(), + hex!("933ac0e3e6acc7a238fb5495835a591db77c39e27f4034dfaea20bce7c072ff6bb6f59a9823a07a76a431905afa2dbbc").into(), + hex!("99f3cab4e8005fdb6bb44900a4f166ef0c2c48dad85c0a127c4d854bca4ad32a2954a586734ee0e57f3317e5b81923cc").into(), + hex!("8f01dc4011ea394a9f7a73b7f246bb00472632fe715314525f1db2cc6158b22dad22d1371e7d0b2d5e72cc408f07cb25").into(), + hex!("96e702adba7420e819338f6f8740946289bca6f24a5f14a5bdc727d1cd66bb7d2a573cdec8ef1333ca39685c33f6e7b0").into(), + hex!("890ab24865a2652a8fb96ced381530192d072cad275c19539cd74e03c001321216a0999ba83c8f3a162bed003dcbaae1").into(), + hex!("96e2f1ed5f78c0b018cb388447bb85b33da331a5a306ce4e216d1070beb7c3900f979ef128e85180c56958c0d729ecdc").into(), + hex!("8ef642d5a1fa4b32fd69f7f57886d1d9447ddd9a8425a03f15633cd688e41054d5243ad6f352a5a3fea2be2f3cb7bede").into(), + hex!("84a3177be656623fe280f91e2acddba52c068cf8a37bd79b9b4186ef199bad65f52cf3e47b581f1964c9987f088fabd2").into(), + hex!("8b913725eb48feaaed46b2e3ddc0cc414aeb433dfa584155e2eaf29020f6f1fa0e801b85bee4bd28831b5cc66944f411").into(), + hex!("88d802c75d422a713c19a600cfc9cd843ca41e35722e21a0614c3195ea84752337ee30991d860fa75a57ce3f614e0a50").into(), + hex!("b43cf4b09d02b20073903bf152f569a43864095622a472656d8a96efebfe3a20dca86871268ffc528a194bd951662d71").into(), + hex!("ac1c65ef79ad0e56184bcdf0680dde5547bd01b95d7e9c3c71671c71683709cdf7fb988440c3bfcb847c26f198b94f81").into(), + hex!("abebe453b3f2430a9287d0d5fc043f7ee434b33feac6b7dab58d5deed7568e0730d59f94b1883e3d43f3c2934d3f40c8").into(), + hex!("84e0acddddd0c202eaabfca7cbf88774ae374e841899942a2353064f132c6205ee378277b2703744a8bea9bc16449537").into(), + hex!("a69865f8a3f66ff4e548ce29a212041bcacdc85410b8467f0515842062b3204fb1b7616e45fb5f46a5619808fb390dbb").into(), + hex!("a55b426b402e9b27fadf87b27cabe5375c6941b22597dea75586eb9dcb699d925db77250b1d755512aefbd4eab0a2e4d").into(), + hex!("8047da13f072c9e848d33a0f397ecf3e783e7dd507ded7a4de25327fe89c183c8dd1da3d419b48f53d93537bc2c1a8e3").into(), + hex!("94a9345e464b9b28798c608115438f1eaaa60a56abad028729dddea3c856f7f871031b4f100626f8bb7a06d88f7cc6c8").into(), + hex!("aac8cc93a4bf5b383080738021fc56cf732988622fe0d493540545b19a6a54cdfe9f8cd2d2dcbd572bdde0d1f8cbb101").into(), + hex!("80f24fae3c8d202e8072092342f8b046dc9edfa1234c86e9f06cdd7fd2a1dc0f81ad69886a8c219f53a94b9a75cf6b78").into(), + hex!("88ac9c1d5c036f14566203d8e18421cdd21b2305cbd20f9857e4edd09e002ba0bb5c89b039cba417b353c6f2f63c50de").into(), + hex!("a45c8ac231d0ddca06f1bf03eeed331e9b524ecafa74642e4c4591cead603d4228cbb0701af58770100964fc880ff85f").into(), + hex!("88767dcda5fc82e5ee515639992868790ad56d2a4fdf1bd1ba1c5be51b381c149fc9db23b93488b54adc89fd4c48dcf9").into(), + hex!("b23e34136c22ac73157c7c5cc8a9491b0b5bc968c95a9c104b402cad9de598e323ada4cc527555157cbecadf48faf87e").into(), + hex!("99910638bfe8b9974a1bb7efed279de750deb046bc21a9655da4ea81a1aa807f2b76aa2a64d773b1b23af283ba3878f0").into(), + hex!("a05869387ea3b4c8f7403d85ec788499a993482538e0e2078d016f00d67571d1342187ae088c788dea518bdf295da88d").into(), + hex!("a7dd1735f178d53908e29db85ba6166640da8c8bc6f717e0da9bf74c547bb98a512266cf737937201cbf6d14bd9420ca").into(), + hex!("87f5b096a1263b51df28417fb423604879b18c4d0a8a48630f70e0f95226bd51a252d8be362df801680344330857fb5c").into(), + hex!("b1a5a549e27b8256c388465be3017dd123a7d257fdb49b2bb409c6430b6056cb8125bf88b5f196bc9e02567a6728c7e8").into(), + hex!("b70862d190351d6bec9c618057e407b43864a0dcf860b31ab6617f75e1ea02de49ff338a45af53783cbf10400c878a32").into(), + hex!("b27a654ece8541b9bf9c6ae0047969ebb69c4687a43030b1c412991dfaf349e2d3caeb6b7ae3d72ff0e2d758a04510fc").into(), + hex!("81368aaa4489c992a6ef3b55df26ece993958df2e40f04a95ca514fda56c2fb98f11d61faedd31860b89e89eab965f0d").into(), + hex!("94e14e03de977732b7c7faa60ec8180e77233a43d513a37c443be4fa0bac64308d6a1929de075b5d51efaa9bbd6855f7").into(), + hex!("b2e26d7b979f93e8dd55eea5a0f4985bb254128963a939ca07fbc33bf83ad7796e9426660b2f35088d7aa5fa0cda2ec2").into(), + hex!("850aee846e93c5204c1906a2782da71c0ff9e2d1962a778dac77561846e6f9290ab10daf72f189df0a57c1548bd4e6cb").into(), + hex!("aabcd7f870c299cabe4dad1857b3b6cc3b9fde2b525e9d8ec0fc1f497cd199108971173e61cdd5937c45758cbf7b9403").into(), + hex!("ac069d7ff2633fc73bb0b7607d9c27305a4e15c189c8da396d6685798c12ef179bb44cffeeb7435667fb03a799eee5cc").into(), + hex!("99e3eb82b955b2411d1b81d946e5ef6b9c6957ae0e368f4a9c279a0541c3a46e289fbff526a1f9db4aa21b92d13bc9e9").into(), + hex!("b5bc3dd1e05a66a1d775ae0ad159df19c7188f2c73a8553525855ab34617c7f080e217732003e09b29a5b36b12ba564e").into(), + hex!("aa631a69aa4a9c14de2c49fde83453633d17bb258a2b7ada723bc8e71ef22c617ebdf8ba64c72675440b35d419d0f836").into(), + hex!("8f42bb48587cafbb3936adc495e82981d7fd81d8c0233a4e4d44f9df72f8439a9a0228d6cf9d156ea608caffab8d9eaa").into(), + hex!("94e079215b8d187d546f33d5384673215ee65c70d3bd0778f67c11665af5fb025b4302518a0db6266996c136ee90d4e8").into(), + hex!("b3dcb504b50dc58ee7f2e2f78ca884d5fc081b570d1177b884c92bd34272ececf2a9319cb1cfb9df011d4db3ad266e42").into(), + hex!("a5e5b55940e379e6c0fe7c6ac9ab86f3836f261942e3933087f1e1deecd280af9afd95d1bfb384976d5947d5069531e9").into(), + hex!("b1a36c3a0a79836817a2890ac53c6768ed3965bf5d1663e2df69b1bba60910e84dcd4f917991812b305367786edfa288").into(), + hex!("a8d07cbfbbf31d113b80d3a1f82ec7c29c4d78007efb66b5592255acebbd8e1b0c8b927a866c79211d5d4994648153ca").into(), + hex!("acad1228fd1ffbc118ada45a27f33ea02a09455d0c295510da693d741ca3b5725af41b99967ea6d429f604736a4fac81").into(), + hex!("b201ad414928e315aa00dc60b89c7a15464d5e97c30b551a462d02c35e327d2ef3244a98a402f9e055a2f9af6e970733").into(), + hex!("861689f35fb72780dc0be92c140dec07857290495baf3137bd2e83ace2f268f205ffc58edfb0e09f323ea5f14d0ce10d").into(), + hex!("89138730c80c30dea01abfebbed79bbe6016b4924193d9c2e8bfaeef30616bcd92f0eb24d5345bfb005bcfea989fd8d3").into(), + hex!("b78092afc3f16397d2eaeb5bdd7fc6c01ef516a71102124febc0cb443f4446c18037ae75c7c1d0c8177454b092922ace").into(), + hex!("a739cb664cdefe7a2f38333fff13bacaca129d718a043fae1a1b7c4251a77319b44589429dcb9ad113f24e11d3b75024").into(), + hex!("879996d4bed3d3235c0f73ac8f3f612eecb6aef6756896920e0229f5deb1d91feff95734e6b4143ba89badb5cc1f0cf2").into(), + hex!("a213d854a0496d74526b3c37a48d6f610452b44202424a419acf206df1cf76f7357ff5c0899e45adb565535bb09c29c1").into(), + hex!("8afa04d66a3e8a2759ff088395cd98597883b3ca6d8811703f5fc74b822ce4e56e1dddeea2c099fb3e0f6648f990f1e9").into(), + hex!("8a2e7ca192972af2b77660b07aa612811fff94c951532e3fb6829e8031355363f4aeed0f9e02b845f00cc9ad4b744c4c").into(), + hex!("8f0757fa7ab1eabf429802c3811caad65833e763029c3aaaa43ce921abeb277d7dfd06e0e58d36e494871ab9bb090668").into(), + hex!("91c0c0b0564fd95db51c73637fad622e6769206bfa03e41474a4e68369d10de7da5d1bd2b5d226f0564cc1ee8c3e9074").into(), + hex!("82b35466d835a6f13080628ce407cfe495cbeee26a5168de9e595a122ae3757f2eb0a64a71ff1ef6ef26c8cc97ec1f52").into(), + hex!("80d11c7a711fd2dbfedc76fe018fca09295d5a3146df92496ba01063e5e098198cd9c52d3802e6cd033f64b3c651b67e").into(), + hex!("8607de2cda6838c70f262dadb21409649900c27a5bf3505ce2166ef6f616f4b7119aa3e3f3c62c1a508662d7d68e8f0e").into(), + hex!("a1c14cbb653115b6225f53e3e6ef8e25f87cf47315b25dea5e658493121ca22733d3fd2781920dfa3a04271d58970749").into(), + hex!("aa876cfb3d572bc1f84a5579dfe8df82a9177b441492382e8ef6528e28e46ce59fce9a82d42c1c2000b28cff06596d18").into(), + hex!("8742ee128452ee98f21360f903c0a57e600d622d4ac793f32d6732f5fc315f757bac89b0f39a4ddcf4b8668cd02f3e78").into(), + hex!("a2e04418db55c0d9163a1bc242e6d43230a943ead121bf8a5f50c109e4ecf0fd99e5b126a4fe2ae9b0a248e613b54f7e").into(), + hex!("841b7c0ab57c2cfb1a180a9b0a2875a7675624f0e5c779f01f3f92a1ea547cd1164485f54bc433d71c7b054a6fdfff15").into(), + hex!("9855f3506dabfea5a133ad49557c3c9e1c7b6965215cd940bce4bfa90e98d9c62999feb29da0af8768b99f5f82c64489").into(), + hex!("a65939fb29f1d913e36be1f877c8b9a3549ec17313c4354b1834cf7ca9ae220af26a72cdfdbc59567bcd7e4152d90930").into(), + hex!("b64edc36e0bcd48cb350350fce955609ae51f5bd197cb7d42b04a2ec7f8dbf236b2a3b23a6e0778d57796433f0e6e9df").into(), + hex!("9379a72a722c1a5c8399acf72ebadb7ab1c5a2e18137cd3850b211dfef907850399b6151ae7bdb590a6eb04387ce0c31").into(), + hex!("b621023f0d3c731f49a48378c3709a0c051fa1e3f8788d27169a76dc35d46cd6095b32d7e91794c35f4af8d75f950411").into(), + hex!("a82421a53687a444a065ff1e11c439cb7342a3edf496f2ccc04f56fc6630bcd79ccce1437479a6e7d6dce918d3d45181").into(), + hex!("857fb59242e6687e940fc114df3c06af5a89d85c762140b1e4b0f8cbcae9d604f435377d7a2d153a65e0dec099e3e8f3").into(), + hex!("95e6a571cbd7c7a58c1c599cb4c837c9f31757a6ee4ed6740e9d55c350baa847ce6d081023b43b397a3798c6843baf13").into(), + hex!("adc1e1f8523fc6e3d683dc0ca15ebdbf471de635f25fa7be6fde9907bd3fad130baafd7d21b43fa04738d4c19448d788").into(), + hex!("85f8ff5b661f9e529421f7e5f831db1919ba3170a59673546db695c3af8a82cb1ca352e07e6c801ef9fe6f501d5896ad").into(), + hex!("937b374871e35266c2815e4d0ad72dc2b6c756e840ec36fdb90a71ecdd4afe13f6064ef36b9d1590a39a7be2156fd728").into(), + hex!("ad4aa9b451187905652222dedfe6135111ce4eeebcca74ecc74f3464a07831754eb0072abfb96adc23d0c5c33a1d9f16").into(), + hex!("815bc7c9c7c84396bd0de5c71b78f2be5fddbbca3f600f341a21533ae6dcfef8bb94f4340ec2a90f40ab091efe4cc6e7").into(), + hex!("90bef1fc273005610cd79161686b25d88ed2ff2abe18f16a4054fa05dbcbaa339825616c117f55ab26d12bdf2e414f70").into(), + hex!("b96e51c2d2bd0fd78c4d3b9873d217eb76642c329a9ef293010fcadb45ef2f9ee3a9c34b0365e344d33c464c08a0f51c").into(), + hex!("94c4048c3fe7dfe736458ee16566027290f93b1f052c3cfaf28f5c33c32af6b9cc960d86181d54361dcc10aca9f81a58").into(), + hex!("aeaad402961126722aa5033c6bc7735d4cddf35ededaa08073ab1a8412e5d1d06e95c58c7a95409edf1566ae904d795d").into(), + hex!("93d019b814d00d5eb6e7545e6480da089fa48ba34f0a961c704db12e34a144818c306cdc4f31320e542c75eb0f1ca96a").into(), + hex!("a3c21f7864512c38a58f02c0c83993ce6294329b074b801404e4f941d21e4e7e5eaeccd41da8ac423c967b7230b2a505").into(), + hex!("80c26a2aff26da9d8d739496b5a63da6d8d35544c71b7b05b41ce4cbda89e6d32e85fb1e38a215aa01dc64cb43e089e0").into(), + hex!("8c5e66d7668ab7e0de06ebab4a4ffd13f24e4458610e64a972a1e1f15356f6e745cf36b8cde658d03817f2749616fe84").into(), + hex!("a1260c9a6727d6d4a4c147e0c7ba91c5e2a47b5a08a07a3ba0eaf9b50360b6919495b4aca5f85e8fb2e4ef1c307286c2").into(), + hex!("9150fbf49242afc6ab7f865d4a92e7013eabab432b341710232e0fd971eb2b214a3da5b82617a8b7defacbe060538ea6").into(), + hex!("ab44ddafebcba5a0fc1002f3bedf595f3245ae07c9184d640154968e4993d85087efa8a173a670d07eba1c00d3ed1c5f").into(), + hex!("91580bd78343a09b62e31bdef63dfa9e0c874d7b39eb8a4300388ab053f262e118f790392f73d4fcf7714b521690d94a").into(), + hex!("ad4b8eb477ff8e573a911a1a4c1ba027088828cde7907e193ccc4b853aea74c66d19ea99c3779f6ce4d505ad83f2174e").into(), + hex!("a5791ba6dd8534607100f405b2f104c987336d5c47a544ed571d0babc6dd88a634296773faacfc3fdc13a5f7ab0f0cc6").into(), + hex!("ada0d1c948bd8f66442cb4b9cdc3a5368ab6c585cd8be766b468864a8fbd60535e454943731d4121ca134743d05221d8").into(), + hex!("a83556f376d8cc4a26b53223b11426da96bdad5351c2fd451ea053346b334eafef9773e3486928b9d407d3e13d5dcdd0").into(), + hex!("83be01b65302a31c5ba09c8329f683904943cc8017cc8975272d7d284b6a15a3313e27887e4de9110f64445581747ec9").into(), + hex!("8acd831b27588a99743c5d4f61e6f0610faa530d6259f187aeade29f1c9d5956f1c01a5faed8be8924fe1e8d9de03571").into(), + hex!("b17e88d1fc760f3d6633f5b48411b7237e276e2fca621d3db4619da62993bdeb3c12cfe7d130a92e4d3a14693d1b87eb").into(), + hex!("b769850bf9b77a1958d5eb932f99807cb695eedafd476d99131e3d7340cb845a33cb2cc7b640a0f3b14901b506802ff8").into(), + hex!("89e7eb7cc852326b6e18cf5c720c4e44c474d254de9e912d22510aa4cc1952b5e5c40b46a4907be375b89bd57d9f1152").into(), + hex!("af7ec9a4b836709701fb497f69dbcd0d94bf986fb6894f48c67014bc8b0ca947da71722d87de0371923f5bf2ec82ec64").into(), + hex!("8d3ff45719a7fc5254e91c710d956a16b8e8435fc4c8f68d1a47672335246bba9627d7058510d25417b7eed5ece5c110").into(), + hex!("a99a7b987f7050c230ab1adfa50a30b4f3782cd31467ff9c2a749182a1974a36a6a375ed5b0909d1e627b32ce0245ef5").into(), + hex!("a0bea35f339b54d82e345204fd4b75d41af3bd08d33b223211e496ef7fcdd8e327dc5a9ecb6fcb7de134b3eaa43d30f5").into(), + hex!("9419df6b2bd022fb6c79566f932c37828ca7a5a1a9efb64f470d7ec06e0d6b0b0147cba88814581a0773c80cf3d21033").into(), + hex!("948b0cd553cafa57de03279b83fa4f28cdbd0ec4e2219e25fad53c9d3d28c619ede568ad6e095a155d117caadfa87551").into(), + hex!("b21383b264f67c8c66011a79e20a4d739d1f8fc258562e6351eb1e1c5b83e42090f1525886ff4b65875868ae17a8faa1").into(), + hex!("aca93939c30eeac8fc83c82ff6ba3549ff38121115a60b7fc94b7d64e1f36f65e932bd8f3bbf2bcba986c9309861fcfa").into(), + hex!("a728507043b7e86c0bf19cbd81a45e1ac98d2edcea4c7faa3381df13f6352232b711b03829e3eddb7770213866dde7ff").into(), + hex!("ae0bafa42eece82975171e94b14e7063a09bbcd44cea6b7da4b820cd5d984be4c00a2e9e5137b0d34603e1ac914f889b").into(), + hex!("b63cf4b55c4a62c50c356cc2721ae5a89244ba9aef2c9f5c93762837fb14197479435f593947c8943ac77e6a2ade0208").into(), + hex!("acb5cdbe2cc7c44ad3980f9ba74b0a97f36add3fbb4e9b513c62157c14812aa73fac68be8c30170c39d1aee626f5a1b8").into(), + hex!("b14ddfe1c42321feb8ffd76ac041814f3d690fc16ff47b23ffcc247e8722d50ae001b6df4e1af6cd7c67c8799c8a1907").into(), + hex!("abe60d6024a9d6874df7e59b4bbd7e1e55da22adba1d16320fbfa2b68e8db995997ce6f81f8809e96c40f548ba005787").into(), + hex!("a23307e2ee6d96561452294a9265cf0eb1d6f86b30c7ec48066cbbe889eb7f0d64819225293496db709a1fd60dde7e5f").into(), + hex!("8bbd00e149a9fd5eaca24581821c3dca114e008c3e92a36db536944f6b5e5e983628f155c2319cba9a8a2a26d3885add").into(), + hex!("954fcbe0655b82bfc15679237d98c3759a49ffd0eb7f5da1827711814b92e0a4be2c0b7a96fc16ee3e31099c993ce6ef").into(), + hex!("a9bb0a14061ab4de136605e94899a41c3585ed190b2a97f529e911f02ff389652049616b408aaaea81d38f08a8f6c533").into(), + hex!("b58fa12cd0b69ac2e5c50b543bb15abcc3a0c96cc9cebfff34c4f7dc83bb5ece69d881348860385456eb6198ecc640ad").into(), + hex!("b73597c5ffd3a812e8a553bd3ad2216282fe7c1203120624b86cacb8a7421ea6807e29fac3383cfd61d632db8e3af5d8").into(), + hex!("ad153b873be0eaa71ad3b0191067874e085164f8428b89c7d2e01af0802169a9afa1775ca0f9491350db9e6c7c6581e9").into(), + hex!("8a7fcbfc564fd1af76df52ac5802a7342aff25d745307d2b9cf29c4470273686d9877b4588754af0e1bbbbe0310c3fdb").into(), + hex!("84553c8b77c7e5c81bfb1413cfcda7f8fd95c78c011c19189784be6a5e7352248b3b30cf5c80d9262de6c35ae6d4f1a5").into(), + hex!("8866da76cc8ca6522c3d41c950ea7fd67d448e1d567ecfd0cb916912d597b754807f7489f5e3bea7b4110cc8088ded24").into(), + hex!("84adaf9a79d5c3bc8dc7e669ccc5d4964254d0fc32bc535e54c5e4a4f45aa3c409c11eadd4fb21f4c831329087adef06").into(), + hex!("a77ce1ee4f8feed6ffa0c5cb8fb7fe0f95a03117e746b56b5e8178d27a1582804e84a86aac1cbab53aadffd9f84c0bd4").into(), + hex!("863321bf40995482cff032854ad5017bd885baaa6ec4ef47ab6bc713640b1e258eb40797ba049fe677937e3ff7a2ba2b").into(), + hex!("a46fb6fad471bb923cc38748253f887b53153ccad475240bf7244c1f9f568ade931b0522911348d64460021639bd831a").into(), + hex!("86604e383195be9c40ab728db426af87698d0e34157edaecc357544037d66d40e558cbfef7b005f8db3c9faf541f2c6d").into(), + hex!("abb7d323687c1d0ecfe89d411d9a81d05d009b84e652af437cee40e89bd2657641cbacf28120fe93deb0a1d3b410fbd4").into(), + hex!("a8d2488d99e04b79057739e6e0522b38a0f68a21bc190696c38d96f0e58a9395e3b9011e54d2cf7e8fd0b380e753f2ea").into(), + hex!("a36fd6a64f64f40ece0babcca8926dfc005cb1d90e4adcaa9c01ff3bff8d73cc0f95bdcb4f09d7e3b5d761ad5c3c065a").into(), + hex!("9699c0c5b1695416470c302f3097e93b94004f42369be26afdf04aad49bed67851d50f14c44efc0a90e311ecb27b3387").into(), + hex!("929e6ba1338579c1cbe76f1b075c0fd9725adfa97ab8b821aeee75133a874426414fbfb5cde7f7f8b74fbd8b27bbf7db").into(), + hex!("ae874a087a61c3ba4bdc2a582cdeace6d321f81683636440943dd860c783344d4133196197a108f6f473bb1e75c597ae").into(), + hex!("95bb2da076a9fc25e96affc7e4adf71496dd5802d7443cb5a77e3d52ef544aaf939c0884169df547000b3afc55cc208d").into(), + hex!("b0b63d1993f601c8aa96448183ff560f291903e649192e2e34e796bb66a31f9d0edd0f03ba4f1d299fcfb1e931abbf39").into(), + hex!("84cc92f8897d0bc0efee72d62ec3a8c07b7c72e00913860623982bba412307c2c42069ed90dd996bc56ffd0573b607f4").into(), + hex!("8ae9804b99addebafd3672785d4402a583c97821087589b7a129961b6131fb18c2fa60d606cef4f636b6cbe46b5d6415").into(), + hex!("8f4c85506e99d383b103217077c70571ad8b9046d039174df6d9f1902f8b85143754969bcec37519a1340c79046f1c32").into(), + hex!("81a5a0214a381d72657e1142a781fa8db0d849e1e012babe4c912a1edebc5dbfc265bc7fbfdf8b6ccbefb55eb0fcbf86").into(), + hex!("82c0e11c9016d95501a97e551b8b926fa317f02fb6764cdf0981796e6e23cbf13e48d46bacc875681900fe8c1741cd27").into(), + hex!("a00912f7bf9abb33f1624dbeb5a960ed32addb4d6bbf9770b6d82d514eabdc339f751e41c8f4461e560141b53f086f8c").into(), + hex!("8d905bcf245556e52587f92957459a41b9974b5d8b8d2baf2d8a98edcac2a77fc8b1eb70024e1e28d5c7a190d9f2a77c").into(), + hex!("b8e19d883289d97b0174cebb92d12a8c6ab16e4a8f0db9d7b67ccb9bdf97f070352e6b24c2decd89e7894099445d8b96").into(), + hex!("89cee93ceec6e742aff71ff60085f04e9550ef5568012b4ef0aceec9928c677f9711ea553499db812bf80eb5df021396").into(), + hex!("89d58564c0215295070329974e51e528ee4d9cb197b089755a86451098bc2f347be8be5b0cc06240315f75222ba2e9a7").into(), + hex!("a312cf33dd49ba6488ee13200193f06a5801407a15fc79956f977586a27a4b2d4cebf0da22f6c1100ce2a0d08730a383").into(), + hex!("acb541c487eb8fb4034ca6208f542c5adec863f1346dfd50fcc0ba1c6866e43f0071c8cbdd62ce6a2498e16e80855fff").into(), + hex!("98022eb774377f49b90f41439cc6703fa152d1d38c0e0c78eed49cbc54670369cb2b7acbbc37dac6617e57e527e41b83").into(), + hex!("8f00d44e73473d7b96a686d1d3b0848095d0514b70128c22ccc4141219dc3f5d2ddc3345cc506ce0e747ba358289bcc6").into(), + hex!("911e37896367a3e8603eaf995480bbb62229a3758a608c4822410e46de45a1048ee6f67c2039aba9fc95281ba5476623").into(), + hex!("80a349a605d2968fbe362e40672b33eface969c975ef75a8fe82ee7ace1d0b5034b7af8667650e813876a8a7484414c8").into(), + hex!("8b00779d873c6976d8b01afc94734fcf943c1819ab1c46e512e0c43469cd08b93158caea1cde84d13a48f27407048748").into(), + hex!("99030a66c4afac7e3753abb669cdf576cf96e21b1d698135148ba133e2d8fe97b4875d770e6246597461958224f653c7").into(), + hex!("8766f592d757c09f617090eb8f226016073a992990e16fd64a705c0c3104b202d36de18ccadcdb3dac5d68afc2495b4c").into(), + hex!("961cce69a7a39c20500c96332d2ce4cdeb3d844082edd527fd1694cf499b30bd33f06da66047d3849b49f4c2ccc8bcf3").into(), + hex!("8bd5dee639c3ef32712931295cc5bd0a8820192aafd35d1f1f9a24130bf208b7cc3f2ae99d6fce02dcac4c8225564d5f").into(), + hex!("a89ba310a62330e7396ae361da7a74a596e4a8be02496c8f4b3c860ac5c3cacdfcf4790d00b2ffa75fa900db2bdb15fe").into(), + hex!("89bebf6f59151404989f282a567c378f2f5a04d85225e23e22e0963da27673f3c7e8990dfb526a1133a988811cd03f45").into(), + hex!("81e9ec6ed189c12ca8d4fe32e21c60834d7938f739545c7dd76303ce347b69beba9eb14ab780c00cfe3804c5756417ba").into(), + hex!("8bbbd7b584948e34852a26d18b9dbb46f2974fb68bc8317ba5a168094a74bbe2304e1dc438777ccf92117831f7986c84").into(), + hex!("8ffbf94991bddefde2bf0ccb115b00d7b19a6a448f093816b9db0c65a43a27519a52ac7b88e6e37a7f33d384d42b05e7").into(), + hex!("b62267be83a54451ace1b8e2b54994990d2e1d619e040c2075cf1906c25089dcbc08ed8c2f2f8f62953b822f163324fd").into(), + hex!("af6d55c342e0e7f0bc3ec547ebb4688a884b59410aca90ad6d8730b4fd3543952fe2476e2db871618d12901fbbd2b91b").into(), + hex!("9027b69f6e5d550acb459f8b4b9e3f05cf291c104594cd244b224eeb8cac419800ebfd5255d87e0dde31bba662e20134").into(), + hex!("8bd160918bfd8049878826f443fa416fe32bd018262b1b4802e015ffb0049197c34d730c5ecba951a93986cca1e23825").into(), + hex!("b82f2bf2bea66697c4b5ed6d340ba74bbe0dce84b2d23904270f3500507318ccca0dbc967a69c4379bd12766708dcbd8").into(), + hex!("b4d00a38be54fe5ba5984af648c9092b133f21b22e56ea106442421c03c26d282b81d31ef8d22ebf92c0c26f86d27512").into(), + hex!("857d95d8aab25f91e7cbe0ea70a3159723566192c1d6dc0e68c2b19565a865a0daeceb4b1c733f75b0dc9cbfe246d870").into(), + hex!("b222a1f3f2d05912987902a861796f43cde8124f7dc398170beb76b6434e7095a8e2d5de54b2692490cb0c325cef8956").into(), + hex!("b016f69dc65c3e72e77d43334863db2c364f3697c552d4bc0730f45cc32fab5f60a5dc0f9f2f6df409fe3e2ba3f2f3a2").into(), + hex!("931b966d70e048570c463bfe7ce7decaea3ad80d0540ca079ddb10958398ea27df85de2fb2b7c238d0763d6293d34b4c").into(), + hex!("aaa3cacaf65a90a6a8e8fcbb98b673160e5f410b28b08a8733444cd71de9e807e00b146ae35fff05160a786eba6793c7").into(), + hex!("8effa24ae2c3cc12aef32e737faf7985c03e2ccd984cdf740f31aac7a93ac295be7fafa3a4d47812d9e0fa5bc2b3472c").into(), + hex!("ae8f1044330885e22c376ba50926ca10177799628b1c3f6b731113126ace5faa7756ca80fda0c535ddc77d051632266e").into(), + hex!("8a6613706dce5417736438d9bc779e29646a0b12fb1c5b5e118aeffbe72d37ce71ef78d3da4f2cc8c1f3bb47f8721cd8").into(), + hex!("8a435b4d265a01f7de575ee8893105de8be608a370c2f1870b7b097bf3635abbdfaf164ac1c704a6c9b31d7baf48028a").into(), + hex!("ab842c0851dc81e247a42806ff83a2e23b86147d884cfc828cbd1f3abc7fee929657bb49a2910975be746f97d0bb7c7b").into(), + hex!("81966af3ed4bba12f6895bcc1e2d4af8a0b313f45446f4f2e966494460f77015d2c9e65eaf396653f8f55e50413e7986").into(), + hex!("b61357419b1d65649e79ccef51d68d1e7c746d77c7f32692b6ff315d9dacefdee2a527816ac3118e5c80e00212725c87").into(), + hex!("b3a0c1e36006ab666e4d4e98c78df5630abcc76e86b3c13c342efbb64c2f669d12a98e797429871c12a7171f7a751422").into(), + hex!("93601527015bc30178505d37cea121f19145e366be178e42c9ea7380ae34053c45938a3b4d8ef852ff8701764bf74a52").into(), + hex!("998a446e7b4dbd7a7a2055f437859dd3ddb44c52d3dad9250f085d797c821ed91a17dd13d00f532c5f0f2321c5b3eb9e").into(), + hex!("af35c5f4bb11a87ee3f626007cacfee4ca892851459cb9cb2e127e92c9c274f9082c905165758976f9c7bbaeb984acf6").into(), + hex!("a0df73b065667fa0f6a4894aba39b3e4aac620fb1a8a3be96c94423231917c3a7d75f04383b40cd802909d9cf018b0d1").into(), + hex!("85c9c5a5302b706af5af436c07d1a1a952ee1cf4a0cccf002f514473fcf85d05bc4c23b5da2d6d0b5d0aa503f7e41a65").into(), + hex!("abd1ffa853de61d8b26e6eb6c7eba5636967c155233a6d73fdddd361379ec51a74c242715b6ad0033a6343157aee7ebb").into(), + hex!("ae45b130af61f3e76012da75b19d46a786e0c21ca7c6b5bde193b2203d6d8b7b8afad25a198e8c920c69954d3d6bdc14").into(), + hex!("93729120899ef573f6f276a1ba861a400a35efff7a2074400bb6b5df818e3fc1f353cd5a8e4ce122a2bbd8f5b30126dc").into(), + hex!("b34615b2cf8912c51c02264008e0cd78b79c87b87d56db810d899490bc438d446f734ca958c7c291aff68e3211ec8c5c").into(), + hex!("a1ca372d158fd7eee15091582c6c1b9ac9854959677ee25e5786a94cf8c1d15b64f0019aef20331d9675e1f1ce41fd6c").into(), + hex!("a4bd66cf90f38233b579b8698f5655f077bdb1d626e1d36ceebc67cf7ab8ee8e129cbdc307895bf0fb6e34a4aeeffc68").into(), + hex!("ae06f6db3a3ea3a21193f6c6231db42d18fa3aa06a8295741bab35589dcb1d51256838dca01356d580e8c423c45ddbe9").into(), + hex!("b70b5f0cf21cb98c70996a9eb6e4b3562732505299149bbebef821477ff406dba3979a2526b9969213b9ba75e35de3f8").into(), + hex!("95cf5980f21a58f4604ffb99c8651752f724faacaa216f8c7cbe400774deda53f26aaa15fc6415e936f52ce13cec6ecb").into(), + hex!("933c0e5bbb358f5f83cf9e60c67ea8fdcc0b7a203fe5b07131e3bd69295c507880a1e542ab2a6a8d182866f7c6b14a8e").into(), + hex!("a8baa60ce583afcf85e4756dbb0a4871b330ee70f7872c3e36ac4d43f2587fbbdfa2a13162ceb7efdf897bb96fd2d97f").into(), + hex!("a127b3828c422ff51a067d482fb67074d45fd0a86bea5066c7f6dfa83f4b82b4584646518e898ff725cf5de055c6b236").into(), + hex!("871ef5a7f50e5ae528eb16bc30ceb64b97d111896d34fb4a65c93c8d0498eb7032033eb663f7b169a8af4b96e7acbe21").into(), + hex!("80d0bb10037029f0d8f8c9b9b46f0d0ff32b2198af44a4f84c8c0ace60f2b39f8f8d284308769c6075e9425d6229905b").into(), + hex!("b882ecbb78c758c951fe53b434af25b594e602dd783787f09ed077b79f7dd7851fed769a1593f5a5b938ea2171987d3c").into(), + hex!("ad41cb47b16077f73cbbc157527a17c936efa78a59d1f36e3c0dad67cd19fdc60cb772556018029611aa46643084f024").into(), + hex!("82ad3ad7a706ec19b39b0c8cf75d061ea3a1966dab04643f5b9d711e6651b45f0cd22ce5048cb51e4e118b305bdf231b").into(), + hex!("8286a4970b8db361abd04e5d197849dd335b7074f9c3fb91dfd19b7f43d2a3ae9e114b0cb6342463986d32d262c34d79").into(), + hex!("a844f14ffc4c99989a6c666dcdcc135c2fda96914220b1c565215d5c2c3102f5413b7edf9b25882a02a19aa78c2bc545").into(), + hex!("8e758f3b03fab7f5d0993e78674efe3f9cc211e268c12d23911fc01ae7a4c8f879a393e3fcde0c05c10106a59b59ab72").into(), + hex!("a973caa021aca6f0470460b84df9324ed894a441435a53c4f0c48fed4359242ee71fb3a0e4cf438839ed838f37e5c02e").into(), + hex!("ae17c713f10747282798487f02d25d2d8e7459ed436d90a895617afb9299ee81994ed68ef87ecdc0660b7565c323f0e8").into(), + hex!("978e68aa5f44daf9cfb9220147ff509ffde89d121d08d982a0fabae9f07cb3145c2312ad200f2f0dc051820fc54d07c0").into(), + hex!("844e58a9e35ce1005fd5785f56fdc9b3f7e8e073f48fa40da19a5e9e84aca00b8743c5407920cb554e926873092015c8").into(), + hex!("b296da231b6ca9d5535432c81f7d0c20a71cbd32d357740d1543e1e3910ea5d32b005938f9273af96e401637180f4606").into(), + hex!("8aee25d881e7ba99fa6aa2fc65ebd44aa498d31ecffc595ac8cab010f6cfdaca308f56e616ae51d7e2c2e15864eda0bc").into(), + hex!("a04298e32052d7f91096285c73d67cfb3f3f5463abb3d7caf3108d8d77aedf9896c359986ae598bf9602dc90e6eb3178").into(), + hex!("8c336e463dd98ecccefc55fa366ac70a2fcaf60acba2f2171490642a6f616a1c6b72601bfc3533a5f49ba02dd1e39fa1").into(), + hex!("b00b383ef5d68f0939c0538c7564614401283c6923dab4db4c72a05a88e05bb576ac374eda61c024345227bf45161e05").into(), + hex!("9273a1a6c9cbbc8d5a46498b7658f6125e955cbf19f0461d1372ea9de200688e4f7376b23b132b41cfd672fb42ec48b1").into(), + hex!("a7f04c0377207a6bb5d96e2e6cb9f7696d5dba2acc3dcd6021ecdb3d121a558999b2b3d92497f72f28f39a551b2fbfcb").into(), + hex!("8d530cf98af85dbd0bb7b1f2fdc24d499b19a941ef431bc7f37ca9328a4f6ceb0660eb87edbb1a5d868d3141fe6c51f5").into(), + hex!("ae88acd7fddf35c72c3ad1c507f8dc185546d8acdd92d70f00991f50afd67809167ad3171c7e45976456fca033f0a95b").into(), + hex!("8df71ffbc265a4bf475ac7ebdd5eba137f4c3b585075ea8957757661b3f11e7a92888094e85c8863ed53d91df45e37a7").into(), + hex!("917737cb24287f30c899dd88853ee3b9be54ea707ef38f1545ef9e436865be1399fd2de4d2c04e3fa5b4a3205f4305ac").into(), + hex!("837e57594b1b71fd9f25f27967b721df500e3d7c72f22e90f4315e10fabbef027ec1db0dd9863b817071fe3c9413a5af").into(), + hex!("94b9c2155509b2189883d2237cd37c9ed19c3a22203e9e2b045184aed072e406e93eb7b5c3fbfb85eeca4c5e630e4ed7").into(), + hex!("b022c75923080450ffe5ecb8e01972785628ec2027b6bf3dc2b09c92ba8ba55222965767c21a240705ed5af6e9d92695").into(), + hex!("b993e15339e28a472b3c98bec723ddeb7728822571ef1fb1c2a1607a4023f37d663b615c7855426170d9ad8f6a971617").into(), + hex!("b34db4df6a97056021b088c53cfa7cedc8e585f907a67d1ee8412a50d84e5e3d347011fb99d5d71b111c88f5efd44610").into(), + hex!("b582bbdd7e0d2ccabe94e6d193d1b8dcad1932d1e96ae8e1a295cc05b381646f682f1f66cb90ddcfcd7735b335ea0242").into(), + hex!("b289c068f7c988a69173d347361047211c302abfafbac1d87259388b5197274f5ee90d56228093a42eec32039e490868").into(), + hex!("88e84114e8e536051ef5197ad181f96ce13fcd5627f18964bf4bb2f461c6638033ba363800326e494e43aeee94c62125").into(), + hex!("80d16c3e8717274533ff3b764984479b3bc709f11ef5129644dfcbb5d8bdedc7a8cb2e539a4524bb0fd4d977ffd25fb0").into(), + hex!("9405d059e30017152eca6d6d86366a7a5570501d78c3869d638a2b8a0bdc8c5bce9f0b46764e78680e2fd41697af9d52").into(), + hex!("ae23483a1d25f8b9a5adea9527560cce5552994b3964ffde2fdda0ce7b6156e1d76e698d7314b0820976776baee37b63").into(), + hex!("8d8395fa4ff7ad0ae3be3d3c446cab058890cf7a07d0a2825e22396cc938cf2d7a986745be5e3c1758c5ca0dc29c0ea3").into(), + hex!("8c5899cfb437a72d99085d8abf54eeb345d7da59ef93978b0cd9207853dc491451939f4b1a7bf317c87504ce949713b2").into(), + hex!("816ca0740bce43365bb20e41c3d0c88cad587e4c743b2c0cac9dc966aa8de220da347d65392a9b750a2001499027e3c5").into(), + hex!("b3d23c55cec1d18bedd276d1454f93ad28c72d921dd6600d8102710770f52b79ee8cf445f6781c2ca095c9a25d41489f").into(), + hex!("95d541d6196c1221dfa5ff213dc3e658649a3cd4afc8e738631fc7b6914bf0dca74f41cf382fab364dcb0d2d6ed489ae").into(), + hex!("a1ab2fd361b973027f6ee6a9f8f2c081cb5d7199d69e36c280c7f9c3e99b1cfc994ad7f68ac0cd78c61bb419251fa14e").into(), + hex!("ad36ebc0cf82369457b665dfb2f8444fd2add0b49658cef2c800ab0297bade2c0249bb124f3d321536933128c1149c92").into(), + hex!("8f917e000d7688a0f508777b8db7c0ace39677c4458d7e50d8c9dc59d32faba4abdedfce3af26cf3d04c020e526ab597").into(), + hex!("aa994238e432c51896efdfa240f75fe40f1b1a7b624ff0aba66a35f827b9bf1197de3cdc0bbd9d0147304061ced0325f").into(), + hex!("943da065ec673dd41351270747e40c1f5a8dbd7ba259c501349ed754ffb91c56747a78c392cb4b78a796d748044798d7").into(), + hex!("b0c45ed1457710daa48edf2e61ba59988a8257ddf902318c6bb00be7a4ad8235b46180f7353d9f0c0f747a4cf219d1fc").into(), + hex!("81b56da0943d2940fc8041a51c74dd03f6dcd8a705ef2ec3b685395e313224861f29de31205d45e944e437179f19398d").into(), + hex!("aadd5eb1be98f3fc7e93925e46062353576ef2ee81421fe3f6850701728b8f74637d66cbcd344364565b0893d8bdcc9f").into(), + hex!("a5dd3dc1e172c899f4ed17fbdb842bea7d7f3f0d6b284af9749e25acbfbb2f9c1afb1848490bb22da9ddfeef30232323").into(), + hex!("84ba149e940db1663271eb16e920442bfeb035fc601a7389f85c78ce7bf27b13b5c1a5625d8b45dbbf199caf2d753bf9").into(), + hex!("b5634eb31a68f45a2aa17e8eae7752d8a58673fcc9efad34118b0f3db7415edfdc27166e6d809406da0bd26a0ae1371c").into(), + hex!("a2ad4aa94a40f1c159c7588ebdd77b80edbcfd95e867ec6991f711d39b4dc911cfb6da6075db23bc090218976e9ffa38").into(), + hex!("8de6002f3e789b014254b32161b5595257eb01ace67a8cd9657235e2d04f7ffce6ae0d059488bd9dc070c32b5b7b3fb3").into(), + hex!("a5f659b41f35fe6a9f43d1f72c803da876ee4aa5b879fbe5d5e93be38dcd5f10716122d83afd003b79b8120d83358884").into(), + hex!("a64e6b71dc6ab9eeb17d50e1d2516c5ae63680b50a6077fd870780aebffca70a8b7f8627e23731b79b3866813a20af0d").into(), + hex!("b0003260e70f86eeba286d3d9f6d73bf15f084e7240d9aeee38a1173ea5c47fd9a9637384204001873732e6a407edadf").into(), + hex!("8d4df6703cc9f1d0760c67ae6b20928ffeb6b13d67bc406b8a534ecb07d6ef415a106ed992b50267677f7d114f9d69c3").into(), + hex!("8cb982f382ac327918387c16c73de1ec5ff979923f1110b9660836ddc2f5f742aaf970700f7b27fb2fdacb126d341353").into(), + hex!("b6988aa5e4043e278c01c83a9774175b1188bc2d78b96817a7fb406f86aa4395b7eb666267e819a5a0615803f840171c").into(), + hex!("b69c70007de643e3fbaf7b557bcaaacb67288ef6ff616c9c89dee0cedd33a76396a90cb44207225568870c7c5601438c").into(), + hex!("971102768c0dba73925cfff2053b1cd8fa88f5c21aeba2cf3a78ac853818bb4a85cff714134879e5c7d7c7994cff20a7").into(), + hex!("b00b8d49b1f1fc0e792e257b0c3c33ab554fe231aaa6366f5aac11ff35051ae23cd2a5c6b9eecb3ad00e840c78d6a587").into(), + hex!("a34bf3b6d9659f1a89e40e3b35afe741a670a3a9305278a52d479535f4b5973b23b10ccdfa194cb2937f150beca3535f").into(), + hex!("a84c4fd757d86613a4bfe72cc9d7864c2c236b9463e365441360686e19f7f772ebb6c07a24c680462ffb1f3939c870d9").into(), + hex!("b3c34a2470e32395bd9c8789905166ba77b7b7d27cb504b8647dfb4fa6d65884afb2f120d83afd876b4a00cb104347a5").into(), + hex!("b9d18f57a669686eb8ec08555972e54505b7d487dfea7105afc76333138d5f934b9b5f9a3a7896481782fad5328bdbda").into(), + hex!("a23f65babfef6a2442833441200291028ebf56031d7154a4d7d0fa18acd4b7bd78dc34b85924c3073eb5be7733ac10f5").into(), + hex!("80948f3e12ffccd88605cd4d67abb83014c35d8d1a3254f6c546aa7197f810cdd06eb49b99f424187df218c8e8d7254e").into(), + hex!("b24ac23a86e23a16f4f04ed683ebaa51ccc6d2d038674e36d00a14fb71d46c433373a8a0bb75a0afd3c2f9605dd4867f").into(), + hex!("b002aef96f1702f4e3c92b00aa2976b57b77ed97a4d64619c6db676b286c6e633eca63fb232cec0d533a803660c20147").into(), + hex!("9867492f550c1f12276a201717bb4c420bffe904d55008655f929368b664e0293c1dabe9b5a5a71ad884a12686c1d9de").into(), + hex!("96b702733a7ef38b23e45999a04bacebe414a01bb41792cd9aff566fc23610d02e069c85c6fd4173846a40657a958e78").into(), + hex!("8e588db21f84245d034d7427055996f109133b9b3aa095472b879bd180052657da20a48513c1f625c339be90a64878e8").into(), + hex!("b6821bb1a130460ed1936d34cc189980d8fae8c5debc5149d47e90aad69ed3b143a3a00de5959e21ada73a06b3e8c9d1").into(), + hex!("a751ae06c6d699b5593f5928910e1ef3634dedd460ccd3f23d74f5f61a3950392aa66149ae366c048c6c7ece968c2d9c").into(), + hex!("86835cbe686b81fe7e096af5ac8c24f7cfffe2ce2b993626a606e42f6356c0d7c3d4ea19d110aa1a7b7f7ec5e68808f1").into(), + hex!("93719cea4911ce7e436f7b3ea77b9cfb83a1db903cb38f1de3b04d0a69a0f06bbc4f9acd3b313d46113009a917dd5996").into(), + hex!("b0d240c09c137a742d77edf11a7257030ef8b1a785b810de104fb24b22535cf0d62bc54f544b027d532f16b43a2df7e9").into(), + hex!("9376f46ab931f5c58b1be49c529ace24fade089af1af43b339721321a273169c5bb668250b2c2b0aa16aa522e6675bdd").into(), + hex!("82636e68d0d59d20dafd7486176b62ca2d5dd0275c8fff552fbb974565ef2406ff56cff5c43a5b9e383e0b09737de446").into(), + hex!("8f300fd7f29640aeb759d09735f9ac36d1035248c35ff38a165d2d931058268a055971b4fd4dd9d467960180cd255dba").into(), + hex!("a7d5f4e2b01dccd9cd7cbf566b5ab604efdaf9b682bb4ecae1b7801c2f93425350620e91ec807b0a110971c316e68cc1").into(), + hex!("8952700221cb45e3ab933ec20978d9c9c7b873784299b86d0c2d9998bb6b1d1efc1ee3bcd00c5148b2fd9473838ce067").into(), + hex!("b787e77d194e4dfb89968f4e289e97195c2674adba0a3e7d5582cefacdedf93a0e5f27d9d144aab68aaba878fd640414").into(), + hex!("8466c67bed3fbc30e46639de92c422f06bc80df658340a47299ad7798cae412c976fa6ae6bc0a32ce655b93b08cbeaf5").into(), + hex!("ab5d78cf76e16fcfc0491886fc1c95a492ccc67fd31060eb183b89bf59ed6e2d349324f9734c504e74c360272a22f369").into(), + hex!("a4f9f8539f9f89dc5a2c8296af591277c2d308275234729dee23e35e3d541919d1aa9a260780899615e2a895ad8fe703").into(), + hex!("94ed2cf23c5d28497b506515597ab71120f4ca42f7ebc5a4e87b798353eb70590923eaaf163bc550bf312bd6bb2c0b05").into(), + hex!("a78f064aa69402af33f1c9c1bcf04634384ad8528aaf8d28ce1d1a04804bdf93a8b49baddd6870eedf642e566d091af7").into(), + hex!("86796909d2dd3010e8d46c3d99a3c0efcbd4e986e581ef5be4c7810ed8b92268bbdac1dbf1b24d6805df6642f53f0b58").into(), + hex!("a6b555b50ec3b60e9768f407b84c5fd8a055150c17f78f2d5a3b0daa13f2c692e5041184bfc8919280c230c57adc9ddd").into(), + hex!("b4f9b0a0242ded4dc0a4903f16d270f21f2e15668b3abd45f79e1b465bf50074d232f905c6de6f2727a0a9ef039f7681").into(), + hex!("930b7dd8666a35358c6a0a42c600dbe8ad5d9682dfc641474fbd8fba90ddaac7d3ed5f5395f297dd571051ac2d603333").into(), + hex!("abba3bdcee368688a8e53d56627b915148fcab59717174fe8136c85bc24e8d15046da09b31c0c7c9a5bac1016bbfafb5").into(), + hex!("ac199e71110e15ebfb3e58b8ac14f1de8ebd3e0894f273f84783ef3fa4fd16ce6bfa5d41421e884e258fe8e2dce68075").into(), + hex!("a53eaed94fe550c07375ff5ed9706d69563bdba724a4022cf7c639737c97683f036400dcb87268184a9877eb116bd479").into(), + hex!("86e8269c1b368228438de5aa4065ab9d2888de7599b00d5382ebba8fb0600cf357de27e20edaa50127e213c7f6be1f6f").into(), + hex!("8f8d9b9b03b178d370a9e918dd54264f83c4ed20824be79427cbc973a9acf3d74637e5c35080192bb67e64390299c19a").into(), + hex!("a230cbc17aa669c2a9b02e736d20519d93a6fa1b6ed452e065fa78a0fb4b3a0f55fcbc5e719db6417353ef0798f70b43").into(), + hex!("b4e28c30e57cd33fdeb257cee66389365f4a1b9847f94e8b0552c65adff5719e51d50ec8bba36a71ec24641ce4fc6338").into(), + hex!("91799f066e16f9a07a419d3e425c959052f8ad1aee0e2f613d3b023829fb1946c81b16e8b733ed03ca924d03481bafcb").into(), + hex!("8ad935420f026b166233ad387c58857eafdd2fcd4efe55ce1bce9ffd668b8997555927fcec88b04de795129a10263d1e").into(), + hex!("a098f20bf1ae2510f1955c586c7115a29c64bd22a086a2f2a7ff5e08349bf24086504a1e5e1fa82b3aa73a097bcd948f").into(), + hex!("909a3d2e7bb5538bd89c446aa53f1f05a1ec17c88d793a310866cdda6e5d836d53fbb80afbf8baa8aa49db3836c912e8").into(), + hex!("89e64879667f34f1127cfacbd9a3337fa28c0227bac5c5ac907d6ad9c3a853472f4f7cf093e3b735968229398bc7c94b").into(), + hex!("b6ea32c4d640f9b7de841575a00003c1b25d0a845eb54b065129c271790bf602cc39761316c4e9dfc1644ae3ff4b05e1").into(), + hex!("80ef23a1cf6f50f96d5b4645bc79ed4c958f1394da9e5cda0cdaca3815ac8435a8d3a690ed19665b7cd1bef8bf7b0366").into(), + hex!("8b7346d1f30de7e50e3d3b32b441ba5681335d25ae6025623e1469466addc5515415a29ecfed987e07bd6a85ec1eabbc").into(), + hex!("99277ae52f7f2f193549739a704aa2f756c2ddff68b9848040ccacc675fa12d62c9fc1318daecba514089537a4e7b83a").into(), + hex!("94d392e29a3a1b8ce63c52288fdcd3f95f80bdd2a600626e2ce3517162e69b9c0eb36bae6786701ba12e23a33b8f90b4").into(), + hex!("8f270be40047911a4cd5997668bd6d62c90780882451c544ea4bdebe061a9e61bafa1d8bb8af62e0ce4fd73611a7f34d").into(), + hex!("a841f7229fd0490a853523edbd12a5dc6772bd607afd0516c582a813a10fd74d19f480d02b3e7b7b6256896620905976").into(), + hex!("aaf65a2e4e6a3d903ccb51a063d64688590a3ae54c7df3c5e8820d88533a6f10b81b130ab476c9f16c660a81f66dd3bf").into(), + hex!("b3683ebb70696339844ccac03925ec85c8dc06959608527a1744c23a67a805e71b5a91610fc6fbe0f667d054b4087e37").into(), + hex!("b042a5614245184e5a4620cd3a67c51811fcc0a0dff63ac06dc8030f01d8343ff0bd39ab3cdc3c09e4f3c1503ed35e62").into(), + hex!("8a343ff96dfae2c2e62558db3cc424b3c055e2cf84b53f63bf49c95d98ad0b372517df7afdb189007b7db725a3fbd567").into(), + hex!("a3a81a2b37160a371ceee3d07194a70e58041a3e6f75f47c4d8e2c619cae3f44b2de5703e3b80fbae9778e284927170a").into(), + hex!("b9554f94b7c611c2b3f63df5ac0f920d1eaff7b424bc8e857b94c354aa1d3c02a4348510099750e0af3e16dcd0f9a245").into(), + hex!("8678aae32f5bfb9dc249a39eb5d0638bd45d48d2b5b7be32b5e1c2be7b8d29d7198a15e2c24fa7813f060309f2493843").into(), + hex!("85f020a228f8952986c979028dbf2c2e8e59191970ea5e4cff6e6bb46251138d9edd90a211ccd53fd395db8addfb6d71").into(), + hex!("8b0801e8ea30467063d68caa5d3315809b3f21c7429f256fc2a10f7e173f0e1d1bbcf59e025b9e44238a53ef1b8318d9").into(), + hex!("a4d3fbad305853d8057c09bb10fdea9234ff90d51ad0c21342248895d77ead5a8c104c554e6008677e396b55d4efdea5").into(), + hex!("a7f12f870c5a3e2332f10cc2db7dc26ce58a94ff60ebc28f3ae06e820e6514d80c5133563225011213e53f51f748413e").into(), + hex!("a94ed25fba32b4302fff40e2c55e06b6fa4b9820635beb0b43df61010ab5cdaf944199bc73c4827214ae1f57bd75e70a").into(), + hex!("8607e973bc67d217ea67104eb538fcaabdcefcaa7da981ae0322916f7a74229b47f18820458823e7ef160f69f5363dbe").into(), + hex!("a83206fcc995ca57583c5952bef2027503308472bd712c536050217a391bf0fa9617d956ff6c913c2566cbc515cb291a").into(), + hex!("b9110bd697c294a0905503490d17d5536ac1782514bcbdd6f67e8fbb75c0922b39cb7f742d28cbf5356e4f1885b060c2").into(), + hex!("96e1d2b723a458f6b8cd4a8b2a83f33fc7931901cac1fe2169ffbf7c1ac8b4d8547165af3dc61c2b37ab88d3e81f940b").into(), + hex!("92cddef13af28962b7e281d5c0552ee5135b7a401944c9ab31d617b072cf00365e24dd87f86f6618b202d51c25f63fd6").into(), + hex!("a751e27a646ac1f3c828cb88585aeae6899d0940252973329b0589b05714bdc1cd271bf745d482f670f4ccbcd9a60d98").into(), + hex!("861fc00a2edf468353c6012a89ab7cddeaf964ee387e5eac48037eddc536df3d097c69a689f09bdad189384719d50e0e").into(), + hex!("a7dda298ac153aaa6a59785ed7b6900362b1220588a29b44764cc45834859bba0f5b9f8f17bba97fb49fa2c7ed4eb65f").into(), + hex!("a6b679d47e1ea1469e5dc14e1eba97ba2a0f2cec0a9a0983ca086f917298c93af45de60765aa2cb3759ed62c9eb5e4dc").into(), + hex!("b95b1f4472f8f69ee010d36db46c268c59cbd13864c63ce9e6a4755ad00c2db04c951e312b88060e8411018cf655e76a").into(), + hex!("935883b9eca730ef868329a67fe99ed5363b0384e7e6f97147d4c80a76d9b2d8ae6783e80d103149a8a3fbfd51f9f6be").into(), + hex!("ace980d1e3c76dcf78bbb87f3ca9bd0bba7897fcf9e24b27e00fa22855b1b4ac224137361ef6817c94fcc81fc3d3a3de").into(), + hex!("a614b6f113d74d4dd6dea66125b11195212031fd7d3da825b24739e5107cae653fb89c34527b399c43340064a9744a6e").into(), + hex!("83c27783856f9af7491ec9fd34be730600afa59484cae9d3981685cadb869dbd05555a07b93db7d0f361c9ae8e0bfe73").into(), + hex!("850f1389e21bec1c8785d17316a09af9355d32b75d02d9ad72791cfc5a411595a367ba9eb641c5b7acd6be1ee21579ea").into(), + hex!("b810405c7415c49bce0f7893aecde90da33b71684668877c5d6cdbe82161f9cd7eaa2d68597140e39fff8b9cd67424e5").into(), + hex!("a5923930d34526d70e083f4633de4766f04df901ca3adba4a462d03423b609d9813b78a2fcaa2d770a5abc27c260c39e").into(), + hex!("a6ec439bff50a4bef8d0cd47c52e92cf00846ca3fe97cf88e5b6d7800ea22d0ebcac49d9ecd123d4c156642f8bb4389f").into(), + hex!("97b7d3fbd11886976291a24e9e7d6f4974345e06024121eaf57097057c103b6f548d1f523416cd6b465e8109aa0be911").into(), + hex!("b45f04e0d4c17df2815a6fea2b04fd7cc2cbb8e6789084e224db3c1d00db1c6f1d325a63df25ee4a9a992553dab420b2").into(), + hex!("a91dc563b48b4cc210119ff55bec2957e8be50aa25928147d0434a9ea4088e98ba1f17c2050e713d2891a3c741ea6c6c").into(), + hex!("a12256c39f3b17c0540d2d3442b732b2485ae9da240a1ef47782549dc7e84f7a9c9240ff59a73fad228a3c01fe953169").into(), + hex!("aebc98b50533d844fe3149735750c10eab861e765aa7820e3d54fb66089ce15409206bb58d3aae5ef22a29ac5207c702").into(), + hex!("b023d0e4fc2eba4c60cedae9d2ffa79cdcd5c79279fa41baa94f536c17d746a6aa76e8fe203fb1678da126c4343eed8b").into(), + hex!("a673262367bde5d1775961c7d9aa26d9859c59600536130f9adc8f99f81f0106d2eab2c5ac3912476affcab3821fecdd").into(), + hex!("8008298954370c0c3026fd71fb48fc619caa394c9ea17273284a903b07de1d385336fad69c8ab6fe6692774d5fabcfc1").into(), + hex!("a98a824454bc0fba41a6543ba11ec6879c979e97c87e3f7f3a228ba995e33bcae9378740f925800b81d3627f2af36c51").into(), + hex!("a29c667540db41eb7ce06d74ad91c7ddeb3e1f019a028b8ed4ee705d8d079d6f7d36f1b64fd4b9b807f0dc1ab3d65d2d").into(), + hex!("86c32be8a7155f26696c1e541096ab43b3836315490a4bdad867b973ebfa8ac414e4074819b9639348a048bd6fc4bdee").into(), + hex!("a0de748319bb0c53c03f172c9aebc4f7538dfce6ffdd362d24ffdd111448277a8a705832e94f65c61aa635a5f40b6f0b").into(), + hex!("970de7621ef90cb3921f747ce1cf9d389f322cebc286c339395714a6d40167fe26e04e1fc3116d3b7bea1c99bfedf0fb").into(), + hex!("841ae6b0bdd22718fd917ca4a871d39cf6811b9a460b99422fb324235b2c51b460e48159d7e1bc1778de3513b7ab1f29").into(), + hex!("86764a78587892407b311892c4c4e70890bd757d3f72a832257f411646e9298eb4f042ec1c929cc6ffcf539dda90fe7e").into(), + hex!("b33008c5025d35243e91b6749e7d7934bd8334e5f58e88db907715435a28f02d018b09127fb0302d1ee68d7f97391040").into(), + hex!("b5ea29ebc8107525894d9872ac88b4c9662fd18d87316565a8b013529b478f17f6c1c0ddf6482db6ede54d27c0e80782").into(), + hex!("9576def9e5dd8673d3dec2090536672d44368b78b2c2f70dd9e36a9d0e5889cd9e2a47b77ebda94388ac75679257b829").into(), + hex!("823dd44b4635bf095786e47f836c4af02d4dea848bc1cd823876266341d56496e09bec8612413c302fb34c3383a55e0e").into(), + hex!("b6a32b13f8b1339b0591b9db343696aab77a0c2ff181d2069ff39581da8904499d619c26e6e44209b7791c3aa2fb7aa2").into(), + hex!("b9b3262a97049e236e29afc4a7c6d2e2253f437e013b90bd0882d35d46d7a67e1344b3dc822fcae27717a9887d842a81").into(), + hex!("8a94e08cda133175049eb2cddae936b16c477db54a749b8d3378233034f56fdea99520755ec8eb355b738af61138d9a2").into(), + hex!("a55933f63c4cce4d99d8df4fe6dc9d9a3054379ed2560bb2a9147f9e456925ab29f7b1f14321ab2501d67dd759f5ef36").into(), + hex!("8b5a713fb50a2aa0671f1405876b0a829a5c1f7c9915906bcad26de7e2011f8eb2e2b7a1cbf94c19685edf8b364f242d").into(), + hex!("97cd2419d4aaabe457b1efa6d2e557a0c4d8725e57e33018626fb31395ed78555b79cf90da49975360a497049004b20c").into(), + hex!("90d640d0c949a73543b476e0b634d442c0bbe0f4d3f1f4f99d19fd8d37d3b99c4bb3c5c08ee77c24f0af6dc7c29ce658").into(), + hex!("8f65263c9ac0026d9a536360c8b01b40243cc27a7896fada367372ba82ffcf2c758bba9f92c96fdd66956b25d7162903").into(), + hex!("ac50c7c4b3201f2c98e812e83452ded2e2dfbeb4c1910b873978c4b5b443b34dde042b13cb4943759cefe1a546bd8197").into(), + hex!("a07a157ebb964282729ab09205f5a2df132b5a0103233dca67dd55215b84e66423c576c6e0b055bf85c6a27025fe1aba").into(), + hex!("b89462ff76b35af9a1f23bbeb7f3b6e2f72f4d96aaa30fcce820573654895c76d813f27dea9251c96ea9e3f1726da99e").into(), + hex!("a81866a83434f91d464a93a50c4906224f3108777f731a1d363d16f769795be8ffd23c25f0e051d8c43a55b665c15bda").into(), + hex!("a974e15a6315d56d612f5e8d70171b01baba976ae1602265cdb74e1f5cf3a48c94e8b90bf1d343f3f56f8f9ece604ecb").into(), + hex!("97ad199e4ba05c97c5aa90cc6e63ed86f78fbea35b9f56f5107df643efe40647d1ac7e50cf6e1de8b4ede8e2225e090c").into(), + hex!("a67c3822c0e5902355fe053c3c41765146018dd90de0df89c6ae230826056e91cad2a28f06859b8ea899486ddbdbe6fa").into(), + hex!("a5dd8329edde8fdb8f82fb71637c7a4d1e13c51ab2b24cc98b80952575e821fe31c3e7de5a566f8a98e0243cebc6d1d5").into(), + hex!("b7070e8349dbd4359414c0d909de10a472c4a7fc4827804b3e1980b1cd8f468d21d4c6163c234c05f05534e423f6199b").into(), + hex!("a1acc31ccbb89ba6ba5be3ffc26ef83a28c3b0f76be71f87ef618f9f8171c8afdbc5f05c521c67171ea9fde1bad7a9dc").into(), + hex!("a86d620245d119b34f55c6145a65937e5ed5281803675e120d8c2de05eabc08f85eb8d8c877aebf535663518c4fe2f88").into(), + hex!("885317798c5b49430d50d5c5eb527540b0b704794d88d510b2511e9fea2de299d8f2cf2ae2e96196a9c0925ab1f30da1").into(), + hex!("8a5c37ad34f1867fc9fdd3303ffb86fb0d98ce27fd6739078949c070b44081349368302d7c910fd3d886165b5fbd3304").into(), + hex!("a1935e801664a3f51c22e616d25fbecb3f3be76b936e4d2dc28a4bbe79d002ddf01b94cdf9e18d07f2e2c4bccdfd76a9").into(), + hex!("b3d13a611ada38fb6c802e6f7e09d03703e9e7bf82424ded2774d6493dc0c7d8965e7839ff4d3f9fe00a1e8fb20bf13c").into(), + hex!("84f6edb320cf92e147ba3ba8b2470c57dd107b80ea8083219d3c91b77fba7b20b6774ae7ace26924c02c9a2247842bcc").into(), + hex!("86546f16627c3b875790b604aeb19c925f6d32db27c2ebf405df0324095fab1f8b748fd94b6c2b5a2dfeaf748a565b3f").into(), + hex!("b95563cebc351d8237e5f9b8ab984976e84ebf7e16c62102629cbda06c86b013c6a973d007d72a870883408b8343fd1f").into(), + hex!("8d5eff4d3238446be0f805b8f387fd57f298dfcadba88a0d87234bbede46d0ef833beb0f2ede7a51fa84a808393757b4").into(), + hex!("862179dd50e7a0fa7906248c0f3671d8d3b25504e30276da74a36edfebc8c92a04abc9dc3cba8c1b34005beb06cceca3").into(), + hex!("b12bf5775e3f9ae29da6b127ffe2892d5dec12f9c1bc21426c225220cb0d5fca5c6376afb5bf5f112f79c6563694007d").into(), + hex!("957cd40aa0864b86bc64420a184988be4489c0f0a3363f39571e71a7443ac6815a1b8ce4862c736be8441108dd78101b").into(), + hex!("8f31cdb655b66e1f8ad877639f71524aa78c09acc24aa493bd6f6be383c295f51a6e70f2573081cf87cd41ef55f5428d").into(), + hex!("a8f304c1f2faa78683d409dbb0c11e26583fdfe845f2557e378c56acc9baaa88cce25442733edcdd0da70f3e5557e53b").into(), + ], + aggregate_pubkey: hex!("870e9dfe2c909b7116a9a4180da4fb6ac4865f9304adc4c36dde6f82338c43352b58dfb494e6095bfade1dbf86e7f939").into(), + }, + next_sync_committee_branch: vec![ + hex!("d138fae7ec85d4d5ebd8d7375b3f39f4bf0d05439e6920a44bcc977e62ee0dfa").into(), + hex!("a6baa91932e6f9d9fec678e9fd75a140c8e74bd87f11d37093839826b95ceeec").into(), + hex!("926c0348ccc4c44119ca84e50911ac22078ab704b0784ebc593155da5c5adb53").into(), + hex!("c4a04575645ebf0cf5b3317a092e595adf49dd93669424c2a5efef700ed082a1").into(), + hex!("81a062566009887529ffc6350f713cd2aa30460c13173fe9ffcdbde71fd69f8b").into(), + ], + }), + finalized_header: BeaconHeader{ + slot: 5808479, + proposer_index: 218610, + parent_root: hex!("ac0c3b35e7e21d11d0563f98fb16bbfb0460aef2ee5fe39ea209aed66694601e").into(), + state_root: hex!("c66e3a4f1718ce82f35c898e8df8080c540aca493a535a2f6170a13b550faef3").into(), + body_root: hex!("207806f82ac8c5bdb6793dc61f31ce91dd06a7fe3a143d29b6579975c64d1d9c").into(), + }, + finality_branch: vec![ + hex!("0bc5020000000000000000000000000000000000000000000000000000000000").into(), + hex!("8c04962a994aadff4d3042da73e167e666323757db5b0234a497c7ddba058ded").into(), + hex!("95901d6dae3edaab0f29f2c6155edbc4eb3980b6816339a464fb51b91fafdb7a").into(), + hex!("926c0348ccc4c44119ca84e50911ac22078ab704b0784ebc593155da5c5adb53").into(), + hex!("c4a04575645ebf0cf5b3317a092e595adf49dd93669424c2a5efef700ed082a1").into(), + hex!("81a062566009887529ffc6350f713cd2aa30460c13173fe9ffcdbde71fd69f8b").into(), + ], + block_roots_root: hex!("93a5736680a9dfe23df1f8a6098c0671c583dae469847e25da3532b3649ae11b").into(), + block_roots_branch: vec![ + hex!("31a647639bd26edd8e3976b4475933d18d7d238210881f57570b7b4030133da0").into(), + hex!("0a3392c5febec2f099f93c5465c68f4f1630927d0326ad84c8d0b318364dcd82").into(), + hex!("986071ec073d43597d67a6595f7f6fc807ef1042c6821fda41ff80aa2717536f").into(), + hex!("732f545955de627e65c46201f053569dceab609948147690136bc64e060f38b4").into(), + hex!("2e7c74db495877af1e95da27113e89757ea475e8d672d319e655810ec64d4ba2").into(), + ], + }) +} + +pub fn make_finalized_header_update() -> Box { + Box::new(Update { + attested_header: BeaconHeader { + slot: 5809441, + proposer_index: 169069, + parent_root: hex!("a4d2fbf3ee62f32589738f386559a1e2358f4f54aff5f7eaea61144d3d9c00d1").into(), + state_root: hex!("4aad4183bc21fc96c90f8e043049f8c1d5ed205c6880c89cd99f2e080ef85138").into(), + body_root: hex!("406c96c6adad01df901df3625cbd622f1d541249b05c768ccc4db5643d973141").into(), + }, + sync_aggregate: SyncAggregate{ + sync_committee_bits: hex!("7fabbff6fcdefbebaefffff9e37dfffebff57f7bf3e3efbcfef1f7f987551dd176f3b3ff7bfa3fedff5fdf7f7afff5ff777bef5f9f7fe65f97fffe7dfdfffbdd"), + sync_committee_signature: hex!("84dc756c452ec9a3ba01cc98d03cf5471b871e9f3f77ddfe72ddf6d5d318ec3de9e5c1508e47ed362300cd45a144655a076d50073c24a67591b0454d2a4632bc01e97eab80f937a8288131a31ab76f400ba9c26a19df176c7e67b724f70407c3").into(), + }, + signature_slot: 5809445, + next_sync_committee_update: None, + finalized_header: BeaconHeader { + slot: 5809375, + proposer_index: 170923, + parent_root: hex!("87fed31787712fa6e802b9f296c1eb0b0ac5bc77f6945d4478c4d25bd7160d1a").into(), + state_root: hex!("246ac89e1854bada03be1da64081954e008238de219088609ddf45efc8000346").into(), + body_root: hex!("ffe0fdbdc2bf57bebdd084fce3820a13801095d236a2fb8f3a64c9d7cf94f8b9").into(), + }, + finality_branch: vec![ + hex!("27c5020000000000000000000000000000000000000000000000000000000000").into(), + hex!("8c04962a994aadff4d3042da73e167e666323757db5b0234a497c7ddba058ded").into(), + hex!("95901d6dae3edaab0f29f2c6155edbc4eb3980b6816339a464fb51b91fafdb7a").into(), + hex!("34e68ed57efdf18c5d2f455e77fa8b2a5be95bb827bdf7f7f6648103688d84b7").into(), + hex!("fc1d45f882aa66020a92c55da663ab9758581a020eb7336173fe84ef861bbdf9").into(), + hex!("7d1745c42ec44d4b2493a55dafdb770f6d38eb4a7ad68ae0264949cb7432e4a7").into(), + ], + block_roots_root: hex!("9e5aeee5467301f3a44d1ab664cebd198519423e73e2118ad046d9bae217f497").into(), + block_roots_branch: vec![ + hex!("ef671e41918c36e23a3673407050b420366022886dcce1b707622de97a695121").into(), + hex!("707cb79caeaf310c10ce1c177312e48b2331164c8327d2635203148c4d974f09").into(), + hex!("1fd802c27384482fdaacfa7406072f6f96ff5428f003af748068d1965cc36981").into(), + hex!("8a31cc13bddabda4f79d948e5e3d70806f638b61d89c87b40aa7131af43c18a8").into(), + hex!("70eb43218a3a6f619f1d0dc7f173fc9c3323fa7e3824ae6cd79af2f7d19634ad").into(), + ] + }) +} + +pub fn make_execution_header_update() -> Box { + Box::new(ExecutionHeaderUpdate { + header: BeaconHeader { + slot: 5809374, + proposer_index: 130336, + parent_root: hex!("2bb54c61560a80d1cfb0528e8ea207dfb9d55ab49238523e21609a9ee3b8a9b5").into(), + state_root: hex!("0116ea76f5c36373dfc8e039811eba86c8e8e16cfe9f0614376559b6585741a7").into(), + body_root: hex!("9a10b47e30bc11fc2ee1e21943a8382d727444f646f09664192236458b555ffe").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("eca009f3262f75b055e6c919e2c0a2c017f017e581a825a2618a2a76926a264e").into(), + hex!("a647371a5590630186dd47b9b8571f27e39a77b4aac1f763fabefe104bf94985").into(), + hex!("9a414690540e4c87ff5171b619b3ab6ff1115c21f247196989f5a0a9085b59a1").into(), + hex!("1bf7ae16fcde0833c6e97a83b72aef31a0b5ca055b87f86602b9b4aa193f557c").into(), + hex!("588d993d05b59bf3352f0f5ebb4cd3ec97ca3e41800da675996741e8fca374c0").into(), + hex!("fdfc6280944bb0a18c9cd0afa9f4a255719a4650233f19de478399276f198c92").into(), + hex!("1d96235b47c604f029b9ab7eb913b13b3c0c2df7f79e3301341b1ec38ea44e4c").into(), + hex!("3ee3af17ce8f5a4946d30b6bce7d6e7580b3981cd2af92246401e2326224f6d1").into(), + hex!("b53b5450e070adf02f4bb9d7c65dd131d07ae2218340eec95ac8aa5e5cdd82aa").into(), + hex!("4d7c09715a1f25574afa1dc3dd7bb44e4c1a723b9360c893b8510f675f85227a").into(), + hex!("38c159fc38dedc1e4f399a3f773ab4376fc40b126634b40d172d5daa6602cf94").into(), + hex!("9faac6fa44ed19fcf530f77b7090dd50dd17aeedabe763931ab7567276025a75").into(), + hex!("c6549c1b0f0027ac373164437e7010b955fbae1a0e78485408ec33ca906beb2d").into(), + ], + finalized_block_root: hex!("f6e721e4e65d9565091a557705285ec6db0a3a3072317317719ec8ad563859a3").into(), + }), + execution_header: ExecutionPayloadHeader { + parent_hash: hex!("6d51d7c94763813ffefa234097a51c6fd7009424d2991695f7bd6203157c86f9").into(), + fee_recipient: hex!("000095e79eac4d76aab57cb2c1f091d553b36ca0").into(), + state_root: hex!("fe9f753520a7b5c0263bbf4fdba728f69e9cf861ce1883aa13de5da30ff75d74").into(), + receipts_root: hex!("cf6ab47d8fc336155b18abfa2d965aae57d9d35a2fcf5cfc992b8dcd136958cb").into(), + logs_bloom: hex!("8427414fce71480d7e70cdbac68dd6f77608c05cf349c34c87ad3256e8dde9e3f9c52131945876c03b6e83ea5970536428283a180eb40efcc5fd834ce424f0dbf622dbba6cfda7945cc1f93a1b6e7ae448c598b4f45f7cfa933fe9808d835cb86e8a38261a031448e262f8e4f2dc4c3254c460e5faae4b518438c1330012154a1ba33ab7d85c8acaa9c47dc582fd003a771c9b09aa16c34d4f0c01fbb3f8c0a28e11d2eafb4e73b75a18e182eac7c021706832a9a785836d31f651efacf88a329334e5b3def3bf1871573dc3553f415f298a9457f7837a31302937a4178be1339cdbb83af329ae7e88d8ab6cba62f018be139896ecbc7ac11ef24b0b4ae343e9").into(), + prev_randao: hex!("5a76eff974d26bf74dc3003fac473ab4abc541be26bd61f124a1818a70ea0b3e").into(), + block_number: 9143323, + gas_limit: 30000000, + gas_used: 28165724, + timestamp: 1686220488, + extra_data: hex!("").into(), + base_fee_per_gas: U256::from(2267_u64), + block_hash: hex!("e4a67cdb1512f29ad9b331e7a37cf8e376222eafa58e72cee7771ad582cc0610").into(), + transactions_root: hex!("bd7eaeb676c14c37bbf0b6f3db2ce021a04a41dbf002f6c7df3bb61639ac7287").into(), + withdrawals_root: hex!("8647d3ecaaf62e1d087c5ab54a23f1d64f477b7ddd16fff458847181d89fc432").into(), + }, + execution_branch: vec![ + hex!("795608ac1294bcc663127b8428513ba4a5ffe952ff72f8322dca23628f13d716").into(), + hex!("336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("2a8f5c65655edeb2800f248f2e14044fc651061d0c00c8e8b627cb21ba421fb4").into(), + ], + }) +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..cba22fc86c99f2c4e6c10d6cf0aacece175b55db --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/mod.rs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +mod fixtures; +mod util; + +use crate::Pallet as EthereumBeaconClient; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +use fixtures::{ + make_checkpoint, make_execution_header_update, make_finalized_header_update, + make_sync_committee_update, +}; + +use primitives::{ + fast_aggregate_verify, prepare_aggregate_pubkey, prepare_aggregate_signature, + verify_merkle_branch, +}; +use util::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_checkpoint() -> Result<(), BenchmarkError> { + let checkpoint_update = make_checkpoint(); + let block_root: H256 = checkpoint_update.header.hash_tree_root().unwrap(); + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(*checkpoint_update)); + + assert!(>::get() == block_root); + assert!(>::get(block_root).is_some()); + + Ok(()) + } + + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + let checkpoint_update = make_checkpoint(); + let finalized_header_update = make_finalized_header_update(); + let block_root: H256 = finalized_header_update.finalized_header.hash_tree_root().unwrap(); + EthereumBeaconClient::::process_checkpoint_update(&checkpoint_update)?; + + #[extrinsic_call] + submit(RawOrigin::Signed(caller.clone()), Box::new(*finalized_header_update)); + + assert!(>::get() == block_root); + assert!(>::get(block_root).is_some()); + + Ok(()) + } + + #[benchmark] + fn submit_with_sync_committee() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + let checkpoint_update = make_checkpoint(); + let sync_committee_update = make_sync_committee_update(); + EthereumBeaconClient::::process_checkpoint_update(&checkpoint_update)?; + + #[extrinsic_call] + submit(RawOrigin::Signed(caller.clone()), Box::new(*sync_committee_update)); + + assert!(>::exists()); + + Ok(()) + } + + #[benchmark] + fn submit_execution_header() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + let checkpoint_update = make_checkpoint(); + let finalized_header_update = make_finalized_header_update(); + let execution_header_update = make_execution_header_update(); + let execution_header_hash = execution_header_update.execution_header.block_hash; + EthereumBeaconClient::::process_checkpoint_update(&checkpoint_update)?; + EthereumBeaconClient::::process_update(&finalized_header_update)?; + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), Box::new(*execution_header_update)); + + assert!(>::contains_key(execution_header_hash)); + + Ok(()) + } + + #[benchmark(extra)] + fn bls_fast_aggregate_verify_pre_aggregated() -> Result<(), BenchmarkError> { + EthereumBeaconClient::::process_checkpoint_update(&make_checkpoint())?; + let update = make_sync_committee_update(); + let participant_pubkeys = participant_pubkeys::(&update)?; + let signing_root = signing_root::(&update)?; + let agg_sig = + prepare_aggregate_signature(&update.sync_aggregate.sync_committee_signature).unwrap(); + let agg_pub_key = prepare_aggregate_pubkey(&participant_pubkeys).unwrap(); + + #[block] + { + agg_sig.fast_aggregate_verify_pre_aggregated(signing_root.as_bytes(), &agg_pub_key); + } + + Ok(()) + } + + #[benchmark(extra)] + fn bls_fast_aggregate_verify() -> Result<(), BenchmarkError> { + EthereumBeaconClient::::process_checkpoint_update(&make_checkpoint())?; + let update = make_sync_committee_update(); + let current_sync_committee = >::get(); + let absent_pubkeys = absent_pubkeys::(&update)?; + let signing_root = signing_root::(&update)?; + + #[block] + { + fast_aggregate_verify( + ¤t_sync_committee.aggregate_pubkey, + &absent_pubkeys, + signing_root, + &update.sync_aggregate.sync_committee_signature, + ) + .unwrap(); + } + + Ok(()) + } + + #[benchmark(extra)] + fn verify_merkle_proof() -> Result<(), BenchmarkError> { + EthereumBeaconClient::::process_checkpoint_update(&make_checkpoint())?; + let update = make_sync_committee_update(); + let block_root: H256 = update.finalized_header.hash_tree_root().unwrap(); + + #[block] + { + verify_merkle_branch( + block_root, + &update.finality_branch, + config::FINALIZED_ROOT_SUBTREE_INDEX, + config::FINALIZED_ROOT_DEPTH, + update.attested_header.state_root, + ); + } + + Ok(()) + } + + impl_benchmark_test_suite!( + EthereumBeaconClient, + crate::mock::mainnet::new_tester(), + crate::mock::mainnet::Test + ); +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e5ded6e1f0d26cad99b5ee84f97eeb277605954 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/benchmarking/util.rs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{ + decompress_sync_committee_bits, Config, CurrentSyncCommittee, Pallet as EthereumBeaconClient, + Update, ValidatorsRoot, Vec, +}; +use primitives::PublicKeyPrepared; +use sp_core::H256; + +pub fn participant_pubkeys( + update: &Update, +) -> Result, &'static str> { + let sync_committee_bits = + decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits); + let current_sync_committee = >::get(); + let pubkeys = EthereumBeaconClient::::find_pubkeys( + &sync_committee_bits, + (*current_sync_committee.pubkeys).as_ref(), + true, + ); + Ok(pubkeys) +} + +pub fn absent_pubkeys(update: &Update) -> Result, &'static str> { + let sync_committee_bits = + decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits); + let current_sync_committee = >::get(); + let pubkeys = EthereumBeaconClient::::find_pubkeys( + &sync_committee_bits, + (*current_sync_committee.pubkeys).as_ref(), + false, + ); + Ok(pubkeys) +} + +pub fn signing_root(update: &Update) -> Result { + let validators_root = >::get(); + let signing_root = EthereumBeaconClient::::signing_root( + &update.attested_header, + validators_root, + update.signature_slot, + )?; + Ok(signing_root) +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs new file mode 100644 index 0000000000000000000000000000000000000000..3d22ad82cec0a88aa4cd0c6ff9a1d6d7c6141165 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mainnet.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pub const SLOTS_PER_EPOCH: usize = 32; +pub const SECONDS_PER_SLOT: usize = 12; +pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 256; +pub const SYNC_COMMITTEE_SIZE: usize = 512; +pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8; +pub const SLOTS_PER_HISTORICAL_ROOT: usize = 8192; +pub const IS_MINIMAL: bool = false; +pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 13; diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs new file mode 100644 index 0000000000000000000000000000000000000000..affa86db976143c70773e017ee4fc14337aa322e --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/minimal.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pub const SLOTS_PER_EPOCH: usize = 8; +pub const SECONDS_PER_SLOT: usize = 6; +pub const EPOCHS_PER_SYNC_COMMITTEE_PERIOD: usize = 8; +pub const SYNC_COMMITTEE_SIZE: usize = 32; +pub const SYNC_COMMITTEE_BITS_SIZE: usize = SYNC_COMMITTEE_SIZE / 8; +pub const SLOTS_PER_HISTORICAL_ROOT: usize = 64; +pub const IS_MINIMAL: bool = true; +pub const BLOCK_ROOT_AT_INDEX_DEPTH: usize = 6; diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mod.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b959ebfec9422f52c986614a4fb4c356b4273c9 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/config/mod.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use primitives::merkle_proof::{generalized_index_length, subtree_index}; +use static_assertions::const_assert; + +pub mod mainnet; +pub mod minimal; + +#[cfg(not(feature = "beacon-spec-mainnet"))] +pub use minimal::*; + +#[cfg(feature = "beacon-spec-mainnet")] +pub use mainnet::*; + +// Generalized Indices + +// get_generalized_index(BeaconState, 'block_roots') +pub const BLOCK_ROOTS_INDEX: usize = 37; +pub const BLOCK_ROOTS_SUBTREE_INDEX: usize = subtree_index(BLOCK_ROOTS_INDEX); +pub const BLOCK_ROOTS_DEPTH: usize = generalized_index_length(BLOCK_ROOTS_INDEX); + +// get_generalized_index(BeaconState, 'finalized_checkpoint', 'root') +pub const FINALIZED_ROOT_INDEX: usize = 105; +pub const FINALIZED_ROOT_SUBTREE_INDEX: usize = subtree_index(FINALIZED_ROOT_INDEX); +pub const FINALIZED_ROOT_DEPTH: usize = generalized_index_length(FINALIZED_ROOT_INDEX); + +// get_generalized_index(BeaconState, 'current_sync_committee') +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54; +pub const CURRENT_SYNC_COMMITTEE_SUBTREE_INDEX: usize = subtree_index(CURRENT_SYNC_COMMITTEE_INDEX); +pub const CURRENT_SYNC_COMMITTEE_DEPTH: usize = + generalized_index_length(CURRENT_SYNC_COMMITTEE_INDEX); + +// get_generalized_index(BeaconState, 'next_sync_committee') +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55; +pub const NEXT_SYNC_COMMITTEE_SUBTREE_INDEX: usize = subtree_index(NEXT_SYNC_COMMITTEE_INDEX); +pub const NEXT_SYNC_COMMITTEE_DEPTH: usize = generalized_index_length(NEXT_SYNC_COMMITTEE_INDEX); + +// get_generalized_index(BeaconBlockBody, 'execution_payload') +pub const EXECUTION_HEADER_INDEX: usize = 25; +pub const EXECUTION_HEADER_SUBTREE_INDEX: usize = subtree_index(EXECUTION_HEADER_INDEX); +pub const EXECUTION_HEADER_DEPTH: usize = generalized_index_length(EXECUTION_HEADER_INDEX); + +pub const MAX_EXTRA_DATA_BYTES: usize = 32; +pub const MAX_LOGS_BLOOM_SIZE: usize = 256; +pub const MAX_FEE_RECIPIENT_SIZE: usize = 20; + +pub const MAX_BRANCH_PROOF_SIZE: usize = 20; + +/// DomainType('0x07000000') +/// +pub const DOMAIN_SYNC_COMMITTEE: [u8; 4] = [7, 0, 0, 0]; + +pub const PUBKEY_SIZE: usize = 48; +pub const SIGNATURE_SIZE: usize = 96; + +const_assert!(SYNC_COMMITTEE_BITS_SIZE == SYNC_COMMITTEE_SIZE / 8); diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/functions.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/functions.rs new file mode 100644 index 0000000000000000000000000000000000000000..751e63c7f86afb6ae4161e9ec4b9ebe750d67436 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/functions.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::config::{ + EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, SYNC_COMMITTEE_BITS_SIZE, + SYNC_COMMITTEE_SIZE, +}; + +/// Decompress packed bitvector into byte vector according to SSZ deserialization rules. Each byte +/// in the decompressed vector is either 0 or 1. +pub fn decompress_sync_committee_bits( + input: [u8; SYNC_COMMITTEE_BITS_SIZE], +) -> [u8; SYNC_COMMITTEE_SIZE] { + primitives::decompress_sync_committee_bits::( + input, + ) +} + +/// Compute the sync committee period in which a slot is contained. +pub fn compute_period(slot: u64) -> u64 { + slot / SLOTS_PER_EPOCH as u64 / EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u64 +} + +/// Compute epoch in which a slot is contained. +pub fn compute_epoch(slot: u64, slots_per_epoch: u64) -> u64 { + slot / slots_per_epoch +} + +/// Sums the bit vector of sync committee participation. +pub fn sync_committee_sum(sync_committee_bits: &[u8]) -> u32 { + sync_committee_bits.iter().fold(0, |acc: u32, x| acc + *x as u32) +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/impls.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/impls.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e72b12631cc482a712129befa7806352a82f449 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/impls.rs @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use snowbridge_core::inbound::{ + VerificationError::{self, *}, + *, +}; +use snowbridge_ethereum::Receipt; + +impl Verifier for Pallet { + /// Verify a message by verifying the existence of the corresponding + /// Ethereum log in a block. Returns the log if successful. The execution header containing + /// the log should be in the beacon client storage, meaning it has been verified and is an + /// ancestor of a finalized beacon block. + fn verify(event_log: &Log, proof: &Proof) -> Result<(), VerificationError> { + log::info!( + target: "ethereum-beacon-client", + "💫 Verifying message with block hash {}", + proof.block_hash, + ); + + let header = >::get(proof.block_hash).ok_or(HeaderNotFound)?; + + let receipt = match Self::verify_receipt_inclusion(header.receipts_root, proof) { + Ok(receipt) => receipt, + Err(err) => { + log::error!( + target: "ethereum-beacon-client", + "💫 Verification of receipt inclusion failed for block {}: {:?}", + proof.block_hash, + err + ); + return Err(err) + }, + }; + + log::trace!( + target: "ethereum-beacon-client", + "💫 Verified receipt inclusion for transaction at index {} in block {}", + proof.tx_index, proof.block_hash, + ); + + event_log.validate().map_err(|_| InvalidLog)?; + + // Convert snowbridge_core::inbound::Log to snowbridge_ethereum::Log. + let event_log = snowbridge_ethereum::Log { + address: event_log.address, + topics: event_log.topics.clone(), + data: event_log.data.clone(), + }; + + if !receipt.contains_log(&event_log) { + log::error!( + target: "ethereum-beacon-client", + "💫 Event log not found in receipt for transaction at index {} in block {}", + proof.tx_index, proof.block_hash, + ); + return Err(LogNotFound) + } + + log::info!( + target: "ethereum-beacon-client", + "💫 Receipt verification successful for {}", + proof.block_hash, + ); + + Ok(()) + } +} + +impl Pallet { + /// Verifies that the receipt encoded in `proof.data` is included in the block given by + /// `proof.block_hash`. + pub fn verify_receipt_inclusion( + receipts_root: H256, + proof: &Proof, + ) -> Result { + let result = verify_receipt_proof(receipts_root, &proof.data.1).ok_or(InvalidProof)?; + + match result { + Ok(receipt) => Ok(receipt), + Err(err) => { + log::trace!( + target: "ethereum-beacon-client", + "💫 Failed to decode transaction receipt: {}", + err + ); + Err(InvalidProof) + }, + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/lib.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fdda200251ac791d67cbe778b4e8d4363a615357 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/lib.rs @@ -0,0 +1,841 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Ethereum Beacon Client +//! +//! A light client that verifies consensus updates signed by the sync committee of the beacon chain. +//! +//! # Extrinsics +//! +//! ## Governance +//! +//! * [`Call::force_checkpoint`]: Set the initial trusted consensus checkpoint. +//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable +//! processing of conensus updates. +//! +//! ## Consensus Updates +//! +//! * [`Call::submit`]: Submit a finalized beacon header with an optional sync committee update +//! * [`Call::submit_execution_header`]: Submit an execution header together with an ancestry proof +//! that can be verified against an already imported finalized beacon header. +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod config; +pub mod functions; +pub mod impls; +pub mod types; +pub mod weights; + +#[cfg(any(test, feature = "fuzzing"))] +pub mod mock; + +#[cfg(all(test, not(feature = "beacon-spec-mainnet")))] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +use frame_support::{ + dispatch::DispatchResult, pallet_prelude::OptionQuery, traits::Get, transactional, +}; +use frame_system::ensure_signed; +use primitives::{ + fast_aggregate_verify, verify_merkle_branch, verify_receipt_proof, BeaconHeader, BlsError, + CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState, ForkData, ForkVersion, + ForkVersions, PublicKeyPrepared, SigningData, +}; +use snowbridge_core::{BasicOperatingMode, RingBufferMap}; +use sp_core::H256; +use sp_std::prelude::*; +pub use weights::WeightInfo; + +use functions::{ + compute_epoch, compute_period, decompress_sync_committee_bits, sync_committee_sum, +}; +pub use types::ExecutionHeaderBuffer; +use types::{ + CheckpointUpdate, ExecutionHeaderUpdate, FinalizedBeaconStateBuffer, SyncCommitteePrepared, + Update, +}; + +pub use pallet::*; + +pub use config::SLOTS_PER_HISTORICAL_ROOT; + +pub const LOG_TARGET: &str = "ethereum-beacon-client"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] + #[codec(mel_bound(T: Config))] + #[scale_info(skip_type_params(T))] + pub struct MaxFinalizedHeadersToKeep(PhantomData); + impl Get for MaxFinalizedHeadersToKeep { + fn get() -> u32 { + // Consider max latency allowed between LatestFinalizedState and LatestExecutionState is + // the total slots in one sync_committee_period so 1 should be fine we keep 2 periods + // here for redundancy. + const MAX_REDUNDANCY: u32 = 2; + config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD as u32 * MAX_REDUNDANCY + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + #[pallet::constant] + type ForkVersions: Get; + /// Maximum number of execution headers to keep + #[pallet::constant] + type MaxExecutionHeadersToKeep: Get; + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + BeaconHeaderImported { + block_hash: H256, + slot: u64, + }, + ExecutionHeaderImported { + block_hash: H256, + block_number: u64, + }, + SyncCommitteeUpdated { + period: u64, + }, + /// Set OperatingMode + OperatingModeChanged { + mode: BasicOperatingMode, + }, + } + + #[pallet::error] + pub enum Error { + SkippedSyncCommitteePeriod, + /// Attested header is older than latest finalized header. + IrrelevantUpdate, + NotBootstrapped, + SyncCommitteeParticipantsNotSupermajority, + InvalidHeaderMerkleProof, + InvalidSyncCommitteeMerkleProof, + InvalidExecutionHeaderProof, + InvalidAncestryMerkleProof, + InvalidBlockRootsRootMerkleProof, + HeaderNotFinalized, + BlockBodyHashTreeRootFailed, + HeaderHashTreeRootFailed, + SyncCommitteeHashTreeRootFailed, + SigningRootHashTreeRootFailed, + ForkDataHashTreeRootFailed, + ExpectedFinalizedHeaderNotStored, + BLSPreparePublicKeysFailed, + BLSVerificationFailed(BlsError), + InvalidUpdateSlot, + /// The given update is not in the expected period, or the given next sync committee does + /// not match the next sync committee in storage. + InvalidSyncCommitteeUpdate, + ExecutionHeaderTooFarBehind, + ExecutionHeaderSkippedBlock, + Halted, + } + + /// Latest imported checkpoint root + #[pallet::storage] + #[pallet::getter(fn initial_checkpoint_root)] + pub(super) type InitialCheckpointRoot = StorageValue<_, H256, ValueQuery>; + + /// Latest imported finalized block root + #[pallet::storage] + #[pallet::getter(fn latest_finalized_block_root)] + pub(super) type LatestFinalizedBlockRoot = StorageValue<_, H256, ValueQuery>; + + /// Beacon state by finalized block root + #[pallet::storage] + #[pallet::getter(fn finalized_beacon_state)] + pub(super) type FinalizedBeaconState = + StorageMap<_, Identity, H256, CompactBeaconState, OptionQuery>; + + /// Finalized Headers: Current position in ring buffer + #[pallet::storage] + pub(crate) type FinalizedBeaconStateIndex = StorageValue<_, u32, ValueQuery>; + + /// Finalized Headers: Mapping of ring buffer index to a pruning candidate + #[pallet::storage] + pub(crate) type FinalizedBeaconStateMapping = + StorageMap<_, Identity, u32, H256, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn validators_root)] + pub(super) type ValidatorsRoot = StorageValue<_, H256, ValueQuery>; + + /// Sync committee for current period + #[pallet::storage] + pub(super) type CurrentSyncCommittee = + StorageValue<_, SyncCommitteePrepared, ValueQuery>; + + /// Sync committee for next period + #[pallet::storage] + pub(super) type NextSyncCommittee = + StorageValue<_, SyncCommitteePrepared, ValueQuery>; + + /// Latest imported execution header + #[pallet::storage] + #[pallet::getter(fn latest_execution_state)] + pub(super) type LatestExecutionState = + StorageValue<_, ExecutionHeaderState, ValueQuery>; + + /// Execution Headers + #[pallet::storage] + pub type ExecutionHeaders = + StorageMap<_, Identity, H256, CompactExecutionHeader, OptionQuery>; + + /// Execution Headers: Current position in ring buffer + #[pallet::storage] + pub type ExecutionHeaderIndex = StorageValue<_, u32, ValueQuery>; + + /// Execution Headers: Mapping of ring buffer index to a pruning candidate + #[pallet::storage] + pub type ExecutionHeaderMapping = StorageMap<_, Identity, u32, H256, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::force_checkpoint())] + #[transactional] + /// Used for pallet initialization and light client resetting. Needs to be called by + /// the root origin. + pub fn force_checkpoint( + origin: OriginFor, + update: Box, + ) -> DispatchResult { + ensure_root(origin)?; + Self::process_checkpoint_update(&update)?; + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight({ + match update.next_sync_committee_update { + None => T::WeightInfo::submit(), + Some(_) => T::WeightInfo::submit_with_sync_committee(), + } + })] + #[transactional] + /// Submits a new finalized beacon header update. The update may contain the next + /// sync committee. + pub fn submit(origin: OriginFor, update: Box) -> DispatchResult { + ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + Self::process_update(&update)?; + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::submit_execution_header())] + #[transactional] + /// Submits a new execution header update. The relevant related beacon header + /// is also included to prove the execution header, as well as ancestry proof data. + pub fn submit_execution_header( + origin: OriginFor, + update: Box, + ) -> DispatchResult { + ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + Self::process_execution_header_update(&update)?; + Ok(()) + } + + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(3)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::set(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } + + impl Pallet { + /// Forces a finalized beacon header checkpoint update. The current sync committee, + /// with a header attesting to the current sync committee, should be provided. + /// An `block_roots` proof should also be provided. This is used for ancestry proofs + /// for execution header updates. + pub(crate) fn process_checkpoint_update(update: &CheckpointUpdate) -> DispatchResult { + let sync_committee_root = update + .current_sync_committee + .hash_tree_root() + .map_err(|_| Error::::SyncCommitteeHashTreeRootFailed)?; + + // Verifies the sync committee in the Beacon state. + ensure!( + verify_merkle_branch( + sync_committee_root, + &update.current_sync_committee_branch, + config::CURRENT_SYNC_COMMITTEE_SUBTREE_INDEX, + config::CURRENT_SYNC_COMMITTEE_DEPTH, + update.header.state_root + ), + Error::::InvalidSyncCommitteeMerkleProof + ); + + let header_root: H256 = update + .header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + + // This is used for ancestry proofs in ExecutionHeader updates. This verifies the + // BeaconState: the beacon state root is the tree root; the `block_roots` hash is the + // tree leaf. + ensure!( + verify_merkle_branch( + update.block_roots_root, + &update.block_roots_branch, + config::BLOCK_ROOTS_SUBTREE_INDEX, + config::BLOCK_ROOTS_DEPTH, + update.header.state_root + ), + Error::::InvalidBlockRootsRootMerkleProof + ); + + let sync_committee_prepared: SyncCommitteePrepared = (&update.current_sync_committee) + .try_into() + .map_err(|_| >::BLSPreparePublicKeysFailed)?; + >::set(sync_committee_prepared); + >::kill(); + InitialCheckpointRoot::::set(header_root); + >::kill(); + + Self::store_validators_root(update.validators_root); + Self::store_finalized_header(header_root, update.header, update.block_roots_root)?; + + Ok(()) + } + + pub(crate) fn process_update(update: &Update) -> DispatchResult { + Self::cross_check_execution_state()?; + Self::verify_update(update)?; + Self::apply_update(update)?; + Ok(()) + } + + /// Cross check to make sure that execution header import does not fall too far behind + /// finalised beacon header import. If that happens just return an error and pause + /// processing until execution header processing has caught up. + pub(crate) fn cross_check_execution_state() -> DispatchResult { + let latest_finalized_state = + FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) + .ok_or(Error::::NotBootstrapped)?; + let latest_execution_state = Self::latest_execution_state(); + // The execution header import should be at least within the slot range of a sync + // committee period. + let max_latency = config::EPOCHS_PER_SYNC_COMMITTEE_PERIOD * config::SLOTS_PER_EPOCH; + ensure!( + latest_execution_state.beacon_slot == 0 || + latest_finalized_state.slot < + latest_execution_state.beacon_slot + max_latency as u64, + Error::::ExecutionHeaderTooFarBehind + ); + Ok(()) + } + + /// References and strictly follows + /// Verifies that provided next sync committee is valid through a series of checks + /// (including checking that a sync committee period isn't skipped and that the header is + /// signed by the current sync committee. + fn verify_update(update: &Update) -> DispatchResult { + // Verify sync committee has sufficient participants. + let participation = + decompress_sync_committee_bits(update.sync_aggregate.sync_committee_bits); + Self::sync_committee_participation_is_supermajority(&participation)?; + + // Verify update does not skip a sync committee period. + ensure!( + update.signature_slot > update.attested_header.slot && + update.attested_header.slot >= update.finalized_header.slot, + Error::::InvalidUpdateSlot + ); + // Retrieve latest finalized state. + let latest_finalized_state = + FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) + .ok_or(Error::::NotBootstrapped)?; + let store_period = compute_period(latest_finalized_state.slot); + let signature_period = compute_period(update.signature_slot); + if >::exists() { + ensure!( + (store_period..=store_period + 1).contains(&signature_period), + Error::::SkippedSyncCommitteePeriod + ) + } else { + ensure!(signature_period == store_period, Error::::SkippedSyncCommitteePeriod) + } + + // Verify update is relevant. + let update_attested_period = compute_period(update.attested_header.slot); + let update_has_next_sync_committee = !>::exists() && + (update.next_sync_committee_update.is_some() && + update_attested_period == store_period); + ensure!( + update.attested_header.slot > latest_finalized_state.slot || + update_has_next_sync_committee, + Error::::IrrelevantUpdate + ); + + // Verify that the `finality_branch`, if present, confirms `finalized_header` to match + // the finalized checkpoint root saved in the state of `attested_header`. + let finalized_block_root: H256 = update + .finalized_header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + ensure!( + verify_merkle_branch( + finalized_block_root, + &update.finality_branch, + config::FINALIZED_ROOT_SUBTREE_INDEX, + config::FINALIZED_ROOT_DEPTH, + update.attested_header.state_root + ), + Error::::InvalidHeaderMerkleProof + ); + + // Though following check does not belong to ALC spec we verify block_roots_root to + // match the finalized checkpoint root saved in the state of `finalized_header` so to + // cache it for later use in `verify_ancestry_proof`. + ensure!( + verify_merkle_branch( + update.block_roots_root, + &update.block_roots_branch, + config::BLOCK_ROOTS_SUBTREE_INDEX, + config::BLOCK_ROOTS_DEPTH, + update.finalized_header.state_root + ), + Error::::InvalidBlockRootsRootMerkleProof + ); + + // Verify that the `next_sync_committee`, if present, actually is the next sync + // committee saved in the state of the `attested_header`. + if let Some(next_sync_committee_update) = &update.next_sync_committee_update { + let sync_committee_root = next_sync_committee_update + .next_sync_committee + .hash_tree_root() + .map_err(|_| Error::::SyncCommitteeHashTreeRootFailed)?; + if update_attested_period == store_period && >::exists() { + let next_committee_root = >::get().root; + ensure!( + sync_committee_root == next_committee_root, + Error::::InvalidSyncCommitteeUpdate + ); + } + ensure!( + verify_merkle_branch( + sync_committee_root, + &next_sync_committee_update.next_sync_committee_branch, + config::NEXT_SYNC_COMMITTEE_SUBTREE_INDEX, + config::NEXT_SYNC_COMMITTEE_DEPTH, + update.attested_header.state_root + ), + Error::::InvalidSyncCommitteeMerkleProof + ); + } + + // Verify sync committee aggregate signature. + let sync_committee = if signature_period == store_period { + >::get() + } else { + >::get() + }; + let absent_pubkeys = + Self::find_pubkeys(&participation, (*sync_committee.pubkeys).as_ref(), false); + let signing_root = Self::signing_root( + &update.attested_header, + Self::validators_root(), + update.signature_slot, + )?; + // Improvement here per + // suggested start from the full set aggregate_pubkey then subtracting the absolute + // minority that did not participate. + fast_aggregate_verify( + &sync_committee.aggregate_pubkey, + &absent_pubkeys, + signing_root, + &update.sync_aggregate.sync_committee_signature, + ) + .map_err(|e| Error::::BLSVerificationFailed(e))?; + + Ok(()) + } + + /// Reference and strictly follows DispatchResult { + let latest_finalized_state = + FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) + .ok_or(Error::::NotBootstrapped)?; + if let Some(next_sync_committee_update) = &update.next_sync_committee_update { + let store_period = compute_period(latest_finalized_state.slot); + let update_finalized_period = compute_period(update.finalized_header.slot); + let sync_committee_prepared: SyncCommitteePrepared = (&next_sync_committee_update + .next_sync_committee) + .try_into() + .map_err(|_| >::BLSPreparePublicKeysFailed)?; + + if !>::exists() { + ensure!( + update_finalized_period == store_period, + >::InvalidSyncCommitteeUpdate + ); + >::set(sync_committee_prepared); + } else if update_finalized_period == store_period + 1 { + >::set(>::get()); + >::set(sync_committee_prepared); + } + log::info!( + target: LOG_TARGET, + "💫 SyncCommitteeUpdated at period {}.", + update_finalized_period + ); + Self::deposit_event(Event::SyncCommitteeUpdated { + period: update_finalized_period, + }); + }; + + if update.finalized_header.slot > latest_finalized_state.slot { + let finalized_block_root: H256 = update + .finalized_header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + Self::store_finalized_header( + finalized_block_root, + update.finalized_header, + update.block_roots_root, + )?; + } + + Ok(()) + } + + /// Validates an execution header for import. The beacon header containing the execution + /// header is sent, plus the execution header, along with a proof that the execution header + /// is rooted in the beacon header body. + pub(crate) fn process_execution_header_update( + update: &ExecutionHeaderUpdate, + ) -> DispatchResult { + let latest_finalized_state = + FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()) + .ok_or(Error::::NotBootstrapped)?; + // Checks that the header is an ancestor of a finalized header, using slot number. + ensure!( + update.header.slot <= latest_finalized_state.slot, + Error::::HeaderNotFinalized + ); + + // Checks that we don't skip execution headers, they need to be imported sequentially. + let latest_execution_state: ExecutionHeaderState = Self::latest_execution_state(); + ensure!( + latest_execution_state.block_number == 0 || + update.execution_header.block_number == + latest_execution_state.block_number + 1, + Error::::ExecutionHeaderSkippedBlock + ); + + // Gets the hash tree root of the execution header, in preparation for the execution + // header proof (used to check that the execution header is rooted in the beacon + // header body. + let execution_header_root: H256 = update + .execution_header + .hash_tree_root() + .map_err(|_| Error::::BlockBodyHashTreeRootFailed)?; + + ensure!( + verify_merkle_branch( + execution_header_root, + &update.execution_branch, + config::EXECUTION_HEADER_SUBTREE_INDEX, + config::EXECUTION_HEADER_DEPTH, + update.header.body_root + ), + Error::::InvalidExecutionHeaderProof + ); + + let block_root: H256 = update + .header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + + match &update.ancestry_proof { + Some(proof) => { + Self::verify_ancestry_proof( + block_root, + update.header.slot, + &proof.header_branch, + proof.finalized_block_root, + )?; + }, + None => { + // If the ancestry proof is not provided, we expect this header to be a + // finalized header. We need to check that the header hash matches the finalized + // header root at the expected slot. + let state = >::get(block_root) + .ok_or(Error::::ExpectedFinalizedHeaderNotStored)?; + if update.header.slot != state.slot { + return Err(Error::::ExpectedFinalizedHeaderNotStored.into()) + } + }, + } + + Self::store_execution_header( + update.execution_header.block_hash, + update.execution_header.clone().into(), + update.header.slot, + block_root, + ); + + Ok(()) + } + + /// Verify that `block_root` is an ancestor of `finalized_block_root` Used to prove that + /// an execution header is an ancestor of a finalized header (i.e. the blocks are + /// on the same chain). + fn verify_ancestry_proof( + block_root: H256, + block_slot: u64, + block_root_proof: &[H256], + finalized_block_root: H256, + ) -> DispatchResult { + let state = >::get(finalized_block_root) + .ok_or(Error::::ExpectedFinalizedHeaderNotStored)?; + + ensure!(block_slot < state.slot, Error::::HeaderNotFinalized); + + let index_in_array = block_slot % (SLOTS_PER_HISTORICAL_ROOT as u64); + let leaf_index = (SLOTS_PER_HISTORICAL_ROOT as u64) + index_in_array; + + ensure!( + verify_merkle_branch( + block_root, + block_root_proof, + leaf_index as usize, + config::BLOCK_ROOT_AT_INDEX_DEPTH, + state.block_roots_root + ), + Error::::InvalidAncestryMerkleProof + ); + + Ok(()) + } + + /// Computes the signing root for a given beacon header and domain. The hash tree root + /// of the beacon header is computed, and then the combination of the beacon header hash + /// and the domain makes up the signing root. + pub(super) fn compute_signing_root( + beacon_header: &BeaconHeader, + domain: H256, + ) -> Result { + let beacon_header_root = beacon_header + .hash_tree_root() + .map_err(|_| Error::::HeaderHashTreeRootFailed)?; + + let hash_root = SigningData { object_root: beacon_header_root, domain } + .hash_tree_root() + .map_err(|_| Error::::SigningRootHashTreeRootFailed)?; + + Ok(hash_root) + } + + /// Stores a compacted (slot and block roots root (hash of the `block_roots` beacon state + /// field, used for ancestry proof)) beacon state in a ring buffer map, with the header root + /// as map key. + fn store_finalized_header( + header_root: H256, + header: BeaconHeader, + block_roots_root: H256, + ) -> DispatchResult { + let slot = header.slot; + + >::insert( + header_root, + CompactBeaconState { slot: header.slot, block_roots_root }, + ); + >::set(header_root); + + log::info!( + target: LOG_TARGET, + "💫 Updated latest finalized block root {} at slot {}.", + header_root, + slot + ); + + Self::deposit_event(Event::BeaconHeaderImported { block_hash: header_root, slot }); + + Ok(()) + } + + /// Stores the provided execution header in pallet storage. The header is stored + /// in a ring buffer map, with the block hash as map key. The last imported execution + /// header is also kept in storage, for the relayer to check import progress. + pub(crate) fn store_execution_header( + block_hash: H256, + header: CompactExecutionHeader, + beacon_slot: u64, + beacon_block_root: H256, + ) { + let block_number = header.block_number; + + >::insert(block_hash, header); + + log::trace!( + target: LOG_TARGET, + "💫 Updated latest execution block at {} to number {}.", + block_hash, + block_number + ); + + LatestExecutionState::::mutate(|s| { + s.beacon_block_root = beacon_block_root; + s.beacon_slot = beacon_slot; + s.block_hash = block_hash; + s.block_number = block_number; + }); + + Self::deposit_event(Event::ExecutionHeaderImported { block_hash, block_number }); + } + + /// Stores the validators root in storage. Validators root is the hash tree root of all the + /// validators at genesis and is used to used to identify the chain that we are on + /// (used in conjunction with the fork version). + /// + fn store_validators_root(validators_root: H256) { + >::set(validators_root); + } + + /// Returns the domain for the domain_type and fork_version. The domain is used to + /// distinguish between the different players in the chain (see DomainTypes + /// ) and to ensure we are + /// addressing the correct chain. + /// + pub(super) fn compute_domain( + domain_type: Vec, + fork_version: ForkVersion, + genesis_validators_root: H256, + ) -> Result { + let fork_data_root = + Self::compute_fork_data_root(fork_version, genesis_validators_root)?; + + let mut domain = [0u8; 32]; + domain[0..4].copy_from_slice(&(domain_type)); + domain[4..32].copy_from_slice(&(fork_data_root.0[..28])); + + Ok(domain.into()) + } + + /// Computes the fork data root. The fork data root is a merkleization of the current + /// fork version and the genesis validators root. + fn compute_fork_data_root( + current_version: ForkVersion, + genesis_validators_root: H256, + ) -> Result { + let hash_root = ForkData { + current_version, + genesis_validators_root: genesis_validators_root.into(), + } + .hash_tree_root() + .map_err(|_| Error::::ForkDataHashTreeRootFailed)?; + + Ok(hash_root) + } + + /// Checks that the sync committee bits (the votes of the sync committee members, + /// represented by bits 0 and 1) is more than a supermajority (2/3 of the votes are + /// positive). + pub(super) fn sync_committee_participation_is_supermajority( + sync_committee_bits: &[u8], + ) -> DispatchResult { + let sync_committee_sum = sync_committee_sum(sync_committee_bits); + ensure!( + ((sync_committee_sum * 3) as usize) >= sync_committee_bits.len() * 2, + Error::::SyncCommitteeParticipantsNotSupermajority + ); + + Ok(()) + } + + /// Returns the fork version based on the current epoch. The hard fork versions + /// are defined in pallet config. + pub(super) fn compute_fork_version(epoch: u64) -> ForkVersion { + Self::select_fork_version(&T::ForkVersions::get(), epoch) + } + + /// Returns the fork version based on the current epoch. + pub(super) fn select_fork_version(fork_versions: &ForkVersions, epoch: u64) -> ForkVersion { + if epoch >= fork_versions.capella.epoch { + return fork_versions.capella.version + } + if epoch >= fork_versions.bellatrix.epoch { + return fork_versions.bellatrix.version + } + if epoch >= fork_versions.altair.epoch { + return fork_versions.altair.version + } + + fork_versions.genesis.version + } + + /// Returns a vector of public keys that participated in the sync committee block signage. + /// Sync committee bits is an array of 0s and 1s, 0 meaning the corresponding sync committee + /// member did not participate in the vote, 1 meaning they participated. + /// This method can find the absent or participating members, based on the participant + /// parameter. participant = false will return absent participants, participant = true will + /// return participating members. + pub fn find_pubkeys( + sync_committee_bits: &[u8], + sync_committee_pubkeys: &[PublicKeyPrepared], + participant: bool, + ) -> Vec { + let mut pubkeys: Vec = Vec::new(); + for (bit, pubkey) in sync_committee_bits.iter().zip(sync_committee_pubkeys.iter()) { + if *bit == u8::from(participant) { + pubkeys.push(*pubkey); + } + } + pubkeys + } + + /// Calculates signing root for BeaconHeader. The signing root is used for the message + /// value in BLS signature verification. + pub fn signing_root( + header: &BeaconHeader, + validators_root: H256, + signature_slot: u64, + ) -> Result { + let fork_version = Self::compute_fork_version(compute_epoch( + signature_slot, + config::SLOTS_PER_EPOCH as u64, + )); + let domain_type = config::DOMAIN_SYNC_COMMITTEE.to_vec(); + // Domains are used for for seeds, for signatures, and for selecting aggregators. + let domain = Self::compute_domain(domain_type, fork_version, validators_root)?; + // Hash tree root of SigningData - object root + domain + let signing_root = Self::compute_signing_root(header, domain)?; + Ok(signing_root) + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d1d14a10158e777603baf250246c84c6831c9fc --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/mock.rs @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as ethereum_beacon_client; +use frame_support::parameter_types; +use pallet_timestamp; +use primitives::{Fork, ForkVersions}; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; + +#[cfg(not(feature = "beacon-spec-mainnet"))] +pub mod minimal { + use super::*; + + use crate::config; + use hex_literal::hex; + use primitives::CompactExecutionHeader; + use snowbridge_core::inbound::{Log, Proof}; + use sp_runtime::BuildStorage; + use std::{fs::File, path::PathBuf}; + + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test { + System: frame_system::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event}, + } + ); + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; + } + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type OnSetCode = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type Nonce = u64; + type Block = Block; + } + + impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); + } + + parameter_types! { + pub const ExecutionHeadersPruneThreshold: u32 = 10; + pub const ChainForkVersions: ForkVersions = ForkVersions{ + genesis: Fork { + version: [0, 0, 0, 1], // 0x00000001 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 1], // 0x01000001 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 1], // 0x02000001 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 1], // 0x03000001 + epoch: 0, + }, + }; + } + + impl ethereum_beacon_client::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold; + type WeightInfo = (); + } + + // Build genesis storage according to the mock runtime. + pub fn new_tester() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000)); + ext + } + + fn load_fixture(basename: &str) -> Result + where + T: for<'de> serde::Deserialize<'de>, + { + let filepath: PathBuf = + [env!("CARGO_MANIFEST_DIR"), "tests", "fixtures", basename].iter().collect(); + serde_json::from_reader(File::open(filepath).unwrap()) + } + + pub fn load_execution_header_update_fixture() -> primitives::ExecutionHeaderUpdate { + load_fixture("execution-header-update.minimal.json").unwrap() + } + + pub fn load_checkpoint_update_fixture( + ) -> primitives::CheckpointUpdate<{ config::SYNC_COMMITTEE_SIZE }> { + load_fixture("initial-checkpoint.minimal.json").unwrap() + } + + pub fn load_sync_committee_update_fixture( + ) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("sync-committee-update.minimal.json").unwrap() + } + + pub fn load_finalized_header_update_fixture( + ) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("finalized-header-update.minimal.json").unwrap() + } + + pub fn load_next_sync_committee_update_fixture( + ) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("next-sync-committee-update.minimal.json").unwrap() + } + + pub fn load_next_finalized_header_update_fixture( + ) -> primitives::Update<{ config::SYNC_COMMITTEE_SIZE }, { config::SYNC_COMMITTEE_BITS_SIZE }> { + load_fixture("next-finalized-header-update.minimal.json").unwrap() + } + + pub fn get_message_verification_payload() -> (Log, Proof) { + ( + Log { + address: hex!("ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0").into(), + topics: vec![ + hex!("1b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ad").into(), + hex!("00000000000000000000000000000000000000000000000000000000000003e8").into(), + hex!("0000000000000000000000000000000000000000000000000000000000000001").into(), + ], + data: hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000").into(), + }, + Proof { + block_hash: hex!("05aaa60b0f27cce9e71909508527264b77ee14da7b5bf915fcc4e32715333213").into(), + tx_index: 0, + data: (vec![ + hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb").to_vec(), + hex!("d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c185510").to_vec(), + hex!("b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646").to_vec(), + ], vec![ + hex!("f90131a0b601337b3aa10a671caa724eba641e759399979856141d3aea6b6b4ac59b889ba00c7d5dd48be9060221a02fb8fa213860b4c50d47046c8fa65ffaba5737d569e0a094601b62a1086cd9c9cb71a7ebff9e718f3217fd6e837efe4246733c0a196f63a06a4b0dd0aefc37b3c77828c8f07d1b7a2455ceb5dbfd3c77d7d6aeeddc2f7e8ca0d6e8e23142cdd8ec219e1f5d8b56aa18e456702b195deeaa210327284d42ade4a08a313d4c87023005d1ab631bbfe3f5de1e405d0e66d0bef3e033f1e5711b5521a0bf09a5d9a48b10ade82b8d6a5362a15921c8b5228a3487479b467db97411d82fa0f95cccae2a7c572ef3c566503e30bac2b2feb2d2f26eebf6d870dcf7f8cf59cea0d21fc4f68ab05bc4dcb23c67008e92c4d466437cdd6ed7aad0c008944c1855108080808080808080").to_vec(), + hex!("f851a0b9890f91ca0d77aa2a4adfaf9b9e40c94cac9e638b6d9797923865872944b646a060a634b9280e3a23fb63375e7bbdd9ab07fd379ab6a67e2312bbc112195fa358808080808080808080808080808080").to_vec(), + hex!("f9030820b9030402f90300018301d6e2b9010000000000000800000000000020040008000000000000000000000000400000008000000000000000000000000000000000000000000000000000000000042010000000001000000000000000000000000000000000040000000000000000000000000000000000000000000000008000000000000000002000000000000000000000000200000000000000200000000000100000000040000001000200008000000000000200000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000f901f5f87a942ffa5ecdbe006d30397c7636d3e015eee251369ff842a0c965575a00553e094ca7c5d14f02e107c258dda06867cbf9e0e69f80e71bbcc1a000000000000000000000000000000000000000000000000000000000000003e8a000000000000000000000000000000000000000000000000000000000000003e8f9011c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000001b8a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004b000f000000000000000100d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec701345772617070656420457468657210574554481235003511000000000000000000000000000000000000000000f858948cf6147918a5cbb672703f879f385036f8793a24e1a01449abf21e49fd025f33495e77f7b1461caefdd3d4bb646424a3f445c4576a5ba0000000000000000000000000440edffa1352b13227e8ee646f3ea37456dec701").to_vec(), + ]), + } + ) + } + + pub fn get_message_verification_header() -> CompactExecutionHeader { + CompactExecutionHeader { + parent_hash: hex!("04a7f6ab8282203562c62f38b0ab41d32aaebe2c7ea687702b463148a6429e04") + .into(), + block_number: 55, + state_root: hex!("894d968712976d613519f973a317cb0781c7b039c89f27ea2b7ca193f7befdb3") + .into(), + receipts_root: hex!("cf0d1c1ba57d1e0edfb59786c7e30c2b7e12bd54612b00cd21c4eaeecedf44fb") + .into(), + } + } +} + +#[cfg(feature = "beacon-spec-mainnet")] +pub mod mainnet { + use super::*; + + type Block = frame_system::mocking::MockBlock; + use sp_runtime::BuildStorage; + + frame_support::construct_runtime!( + pub enum Test { + System: frame_system::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + EthereumBeaconClient: ethereum_beacon_client::{Pallet, Call, Storage, Event}, + } + ); + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; + } + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type OnSetCode = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type Nonce = u64; + type Block = Block; + } + + impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); + } + + parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions{ + genesis: Fork { + version: [0, 0, 16, 32], // 0x00001020 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 16, 32], // 0x01001020 + epoch: 36660, + }, + bellatrix: Fork { + version: [2, 0, 16, 32], // 0x02001020 + epoch: 112260, + }, + capella: Fork { + version: [3, 0, 16, 32], // 0x03001020 + epoch: 162304, + }, + }; + pub const ExecutionHeadersPruneThreshold: u32 = 10; + } + + impl ethereum_beacon_client::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold; + type WeightInfo = (); + } + + // Build genesis storage according to the mock runtime. + pub fn new_tester() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + let _ = ext.execute_with(|| Timestamp::set(RuntimeOrigin::signed(1), 30_000)); + ext + } +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/tests.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..92a93720ae9392a488bd4006545b2fc848533976 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/tests.rs @@ -0,0 +1,1032 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{ + config::{EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH}, + functions::compute_period, + mock::minimal::*, + pallet::ExecutionHeaders, + sync_committee_sum, verify_merkle_branch, BeaconHeader, CompactBeaconState, Error, + ExecutionHeaderBuffer, FinalizedBeaconState, LatestExecutionState, LatestFinalizedBlockRoot, + NextSyncCommittee, SyncCommitteePrepared, +}; + +use frame_support::{assert_err, assert_noop, assert_ok}; +use hex_literal::hex; +use primitives::{ + CompactExecutionHeader, ExecutionHeaderState, Fork, ForkVersions, NextSyncCommitteeUpdate, +}; +use rand::{thread_rng, Rng}; +use snowbridge_core::{ + inbound::{VerificationError, Verifier}, + RingBufferMap, +}; +use sp_core::H256; +use sp_runtime::DispatchError; + +/// Arbitrary hash used for tests and invalid hashes. +const TEST_HASH: [u8; 32] = + hex!["5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371"]; + +/* UNIT TESTS */ + +#[test] +pub fn sum_sync_committee_participation() { + new_tester().execute_with(|| { + assert_eq!(sync_committee_sum(&[0, 1, 0, 1, 1, 0, 1, 0, 1]), 5); + }); +} + +#[test] +pub fn compute_domain() { + new_tester().execute_with(|| { + let domain = EthereumBeaconClient::compute_domain( + hex!("07000000").into(), + hex!("00000001"), + hex!("5dec7ae03261fde20d5b024dfabce8bac3276c9a4908e23d50ba8c9b50b0adff").into(), + ); + + assert_ok!(&domain); + assert_eq!( + domain.unwrap(), + hex!("0700000046324489ceb6ada6d118eacdbe94f49b1fcb49d5481a685979670c7c").into() + ); + }); +} + +#[test] +pub fn compute_signing_root_bls() { + new_tester().execute_with(|| { + let signing_root = EthereumBeaconClient::compute_signing_root( + &BeaconHeader { + slot: 3529537, + proposer_index: 192549, + parent_root: hex!( + "1f8dc05ea427f78e84e2e2666e13c3befb7106fd1d40ef8a3f67cf615f3f2a4c" + ) + .into(), + state_root: hex!( + "0dfb492a83da711996d2d76b64604f9bca9dc08b6c13cf63b3be91742afe724b" + ) + .into(), + body_root: hex!("66fba38f7c8c2526f7ddfe09c1a54dd12ff93bdd4d0df6a0950e88e802228bfa") + .into(), + }, + hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into(), + ); + + assert_ok!(&signing_root); + assert_eq!( + signing_root.unwrap(), + hex!("3ff6e9807da70b2f65cdd58ea1b25ed441a1d589025d2c4091182026d7af08fb").into() + ); + }); +} + +#[test] +pub fn compute_signing_root() { + new_tester().execute_with(|| { + let signing_root = EthereumBeaconClient::compute_signing_root( + &BeaconHeader { + slot: 222472, + proposer_index: 10726, + parent_root: hex!( + "5d481a9721f0ecce9610eab51d400d223683d599b7fcebca7e4c4d10cdef6ebb" + ) + .into(), + state_root: hex!( + "14eb4575895f996a84528b789ff2e4d5148242e2983f03068353b2c37015507a" + ) + .into(), + body_root: hex!("7bb669c75b12e0781d6fa85d7fc2f32d64eafba89f39678815b084c156e46cac") + .into(), + }, + hex!("07000000e7acb21061790987fa1c1e745cccfb358370b33e8af2b2c18938e6c2").into(), + ); + + assert_ok!(&signing_root); + assert_eq!( + signing_root.unwrap(), + hex!("da12b6a6d3516bc891e8a49f82fc1925cec40b9327e06457f695035303f55cd8").into() + ); + }); +} + +#[test] +pub fn compute_domain_bls() { + new_tester().execute_with(|| { + let domain = EthereumBeaconClient::compute_domain( + hex!("07000000").into(), + hex!("01000000"), + hex!("4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95").into(), + ); + + assert_ok!(&domain); + assert_eq!( + domain.unwrap(), + hex!("07000000afcaaba0efab1ca832a15152469bb09bb84641c405171dfa2d3fb45f").into() + ); + }); +} + +#[test] +pub fn verify_merkle_branch_for_finalized_root() { + new_tester().execute_with(|| { + assert!(verify_merkle_branch( + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + &[ + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(), + hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(), + hex!("002c1fe5bc0bd62db6f299a582f2a80a6d5748ccc82e7ed843eaf0ae0739f74a").into(), + hex!("d2dc4ba9fd4edff6716984136831e70a6b2e74fca27b8097a820cbbaa5a6e3c3").into(), + hex!("91f77a19d8afa4a08e81164bb2e570ecd10477b3b65c305566a6d2be88510584").into(), + ], + crate::config::FINALIZED_ROOT_INDEX, + crate::config::FINALIZED_ROOT_DEPTH, + hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into() + )); + }); +} + +#[test] +pub fn verify_merkle_branch_fails_if_depth_and_branch_dont_match() { + new_tester().execute_with(|| { + assert!(!verify_merkle_branch( + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + &[ + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f6f02af29218292d21a69b64a794a7c0873b3e0f54611972863706e8cbdf371").into(), + hex!("e7125ff9ab5a840c44bedb4731f440a405b44e15f2d1a89e27341b432fabe13d").into(), + ], + crate::config::FINALIZED_ROOT_INDEX, + crate::config::FINALIZED_ROOT_DEPTH, + hex!("e46559327592741956f6beaa0f52e49625eb85dce037a0bd2eff333c743b287f").into() + )); + }); +} + +#[test] +pub fn sync_committee_participation_is_supermajority() { + let bits = + hex!("bffffffff7f1ffdfcfeffeffbfdffffbfffffdffffefefffdffff7f7ffff77fffdf7bff77ffdf7fffafffffff77fefffeff7effffffff5f7fedfffdfb6ddff7b" + ); + let participation = primitives::decompress_sync_committee_bits::<512, 64>(bits); + assert_ok!(EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation)); +} + +#[test] +pub fn sync_committee_participation_is_supermajority_errors_when_not_supermajority() { + new_tester().execute_with(|| { + let participation: [u8; 512] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, + 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + ]; + + assert_err!( + EthereumBeaconClient::sync_committee_participation_is_supermajority(&participation), + Error::::SyncCommitteeParticipantsNotSupermajority + ); + }); +} + +#[test] +pub fn execution_header_pruning() { + new_tester().execute_with(|| { + let execution_header_prune_threshold = ExecutionHeadersPruneThreshold::get(); + let to_be_deleted = execution_header_prune_threshold / 2; + + let mut stored_hashes = vec![]; + + for i in 0..execution_header_prune_threshold { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + EthereumBeaconClient::store_execution_header( + hash, + CompactExecutionHeader::default(), + i as u64, + hash, + ); + stored_hashes.push(hash); + } + + // We should have stored everything until now + assert_eq!({ ExecutionHeaders::::iter().count() }, stored_hashes.len()); + + // Let's push extra entries so that some of the previous entries are deleted. + for i in 0..to_be_deleted { + let mut hash = H256::default(); + thread_rng().try_fill(&mut hash.0[..]).unwrap(); + EthereumBeaconClient::store_execution_header( + hash, + CompactExecutionHeader::default(), + (i + execution_header_prune_threshold) as u64, + hash, + ); + + stored_hashes.push(hash); + } + + // We should have only stored upto `execution_header_prune_threshold` + assert_eq!( + ExecutionHeaders::::iter().count() as u32, + execution_header_prune_threshold + ); + + // First `to_be_deleted` items must be deleted + for i in 0..to_be_deleted { + assert!(!ExecutionHeaders::::contains_key(stored_hashes[i as usize])); + } + + // Other entries should be part of data + for i in to_be_deleted..(to_be_deleted + execution_header_prune_threshold) { + assert!(ExecutionHeaders::::contains_key(stored_hashes[i as usize])); + } + }); +} + +#[test] +fn compute_fork_version() { + let mock_fork_versions = ForkVersions { + genesis: Fork { version: [0, 0, 0, 0], epoch: 0 }, + altair: Fork { version: [0, 0, 0, 1], epoch: 10 }, + bellatrix: Fork { version: [0, 0, 0, 2], epoch: 20 }, + capella: Fork { version: [0, 0, 0, 3], epoch: 30 }, + }; + new_tester().execute_with(|| { + assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 0), [0, 0, 0, 0]); + assert_eq!(EthereumBeaconClient::select_fork_version(&mock_fork_versions, 1), [0, 0, 0, 0]); + assert_eq!( + EthereumBeaconClient::select_fork_version(&mock_fork_versions, 10), + [0, 0, 0, 1] + ); + assert_eq!( + EthereumBeaconClient::select_fork_version(&mock_fork_versions, 21), + [0, 0, 0, 2] + ); + assert_eq!( + EthereumBeaconClient::select_fork_version(&mock_fork_versions, 20), + [0, 0, 0, 2] + ); + assert_eq!( + EthereumBeaconClient::select_fork_version(&mock_fork_versions, 32), + [0, 0, 0, 3] + ); + }); +} + +#[test] +fn find_absent_keys() { + let participation: [u8; 32] = [ + 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, + ]; + let update = load_sync_committee_update_fixture(); + let sync_committee_prepared: SyncCommitteePrepared = + (&update.next_sync_committee_update.unwrap().next_sync_committee) + .try_into() + .unwrap(); + + new_tester().execute_with(|| { + let pubkeys = EthereumBeaconClient::find_pubkeys( + &participation, + (*sync_committee_prepared.pubkeys).as_ref(), + false, + ); + assert_eq!(pubkeys.len(), 2); + assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[0]); + assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[7]); + }); +} + +#[test] +fn find_present_keys() { + let participation: [u8; 32] = [ + 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, + ]; + let update = load_sync_committee_update_fixture(); + let sync_committee_prepared: SyncCommitteePrepared = + (&update.next_sync_committee_update.unwrap().next_sync_committee) + .try_into() + .unwrap(); + + new_tester().execute_with(|| { + let pubkeys = EthereumBeaconClient::find_pubkeys( + &participation, + (*sync_committee_prepared.pubkeys).as_ref(), + true, + ); + assert_eq!(pubkeys.len(), 4); + assert_eq!(pubkeys[0], sync_committee_prepared.pubkeys[1]); + assert_eq!(pubkeys[1], sync_committee_prepared.pubkeys[8]); + assert_eq!(pubkeys[2], sync_committee_prepared.pubkeys[26]); + assert_eq!(pubkeys[3], sync_committee_prepared.pubkeys[30]); + }); +} + +#[test] +fn cross_check_execution_state() { + new_tester().execute_with(|| { + let header_root: H256 = TEST_HASH.into(); + >::insert( + header_root, + CompactBeaconState { + // set slot to period 5 + slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 5) as u64, + block_roots_root: Default::default(), + }, + ); + LatestFinalizedBlockRoot::::set(header_root); + >::set(ExecutionHeaderState { + beacon_block_root: Default::default(), + // set slot to period 2 + beacon_slot: ((EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) * 2) as u64, + block_hash: Default::default(), + block_number: 0, + }); + + assert_err!( + EthereumBeaconClient::cross_check_execution_state(), + Error::::ExecutionHeaderTooFarBehind + ); + }); +} + +/* SYNC PROCESS TESTS */ + +#[test] +fn process_initial_checkpoint() { + let checkpoint = load_checkpoint_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::force_checkpoint( + RuntimeOrigin::root(), + Box::new(checkpoint.clone()) + )); + let block_root: H256 = checkpoint.header.hash_tree_root().unwrap(); + assert!(>::contains_key(block_root)); + }); +} + +#[test] +fn process_initial_checkpoint_with_invalid_sync_committee_proof() { + let mut checkpoint = load_checkpoint_update_fixture(); + checkpoint.current_sync_committee_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), Box::new(checkpoint)), + Error::::InvalidSyncCommitteeMerkleProof + ); + }); +} + +#[test] +fn process_initial_checkpoint_with_invalid_blocks_root_proof() { + let mut checkpoint = load_checkpoint_update_fixture(); + checkpoint.block_roots_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::force_checkpoint(RuntimeOrigin::root(), Box::new(checkpoint)), + Error::::InvalidBlockRootsRootMerkleProof + ); + }); +} + +#[test] +fn submit_update_in_current_period() { + let checkpoint = load_checkpoint_update_fixture(); + let update = load_finalized_header_update_fixture(); + let initial_period = compute_period(checkpoint.header.slot); + let update_period = compute_period(update.finalized_header.slot); + assert_eq!(initial_period, update_period); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(update.clone()) + )); + let block_root: H256 = update.finalized_header.hash_tree_root().unwrap(); + assert!(>::contains_key(block_root)); + }); +} + +#[test] +fn submit_update_with_sync_committee_in_current_period() { + let checkpoint = load_checkpoint_update_fixture(); + let update = load_sync_committee_update_fixture(); + let init_period = compute_period(checkpoint.header.slot); + let update_period = compute_period(update.finalized_header.slot); + assert_eq!(init_period, update_period); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert!(!>::exists()); + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update))); + assert!(>::exists()); + }); +} + +#[test] +fn submit_update_in_next_period() { + let checkpoint = load_checkpoint_update_fixture(); + let sync_committee_update = load_sync_committee_update_fixture(); + let update = load_next_finalized_header_update_fixture(); + let sync_committee_period = compute_period(sync_committee_update.finalized_header.slot); + let next_sync_committee_period = compute_period(update.finalized_header.slot); + assert_eq!(sync_committee_period + 1, next_sync_committee_period); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(sync_committee_update.clone()) + )); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(update.clone()) + )); + let block_root: H256 = update.finalized_header.clone().hash_tree_root().unwrap(); + assert!(>::contains_key(block_root)); + }); +} + +#[test] +fn submit_update_with_invalid_header_proof() { + let checkpoint = load_checkpoint_update_fixture(); + let mut update = load_sync_committee_update_fixture(); + let init_period = compute_period(checkpoint.header.slot); + let update_period = compute_period(update.finalized_header.slot); + assert_eq!(init_period, update_period); + update.finality_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert!(!>::exists()); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::InvalidHeaderMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_invalid_block_roots_proof() { + let checkpoint = load_checkpoint_update_fixture(); + let mut update = load_sync_committee_update_fixture(); + let init_period = compute_period(checkpoint.header.slot); + let update_period = compute_period(update.finalized_header.slot); + assert_eq!(init_period, update_period); + update.block_roots_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert!(!>::exists()); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::InvalidBlockRootsRootMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_invalid_next_sync_committee_proof() { + let checkpoint = load_checkpoint_update_fixture(); + let mut update = load_sync_committee_update_fixture(); + let init_period = compute_period(checkpoint.header.slot); + let update_period = compute_period(update.finalized_header.slot); + assert_eq!(init_period, update_period); + if let Some(ref mut next_sync_committee_update) = update.next_sync_committee_update { + next_sync_committee_update.next_sync_committee_branch[0] = TEST_HASH.into(); + } + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert!(!>::exists()); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::InvalidSyncCommitteeMerkleProof + ); + }); +} + +#[test] +fn submit_update_with_skipped_period() { + let checkpoint = load_checkpoint_update_fixture(); + let sync_committee_update = load_sync_committee_update_fixture(); + let mut update = load_next_finalized_header_update_fixture(); + update.signature_slot += (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH) as u64; + update.attested_header.slot = update.signature_slot - 1; + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(sync_committee_update.clone()) + )); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::SkippedSyncCommitteePeriod + ); + }); +} + +#[test] +fn submit_update_with_sync_committee_in_next_period() { + let checkpoint = load_checkpoint_update_fixture(); + let update = load_sync_committee_update_fixture(); + let next_update = load_next_sync_committee_update_fixture(); + let update_period = compute_period(update.finalized_header.slot); + let next_update_period = compute_period(next_update.finalized_header.slot); + assert_eq!(update_period + 1, next_update_period); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert!(!>::exists()); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(update.clone()) + )); + assert!(>::exists()); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(next_update.clone()) + )); + let last_finalized_state = + FinalizedBeaconState::::get(LatestFinalizedBlockRoot::::get()).unwrap(); + let last_synced_period = compute_period(last_finalized_state.slot); + assert_eq!(last_synced_period, next_update_period); + }); +} + +#[test] +fn submit_update_with_sync_committee_invalid_signature_slot() { + let checkpoint = load_checkpoint_update_fixture(); + let mut update = load_sync_committee_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + + // makes a invalid update with signature_slot should be more than attested_slot + update.signature_slot = update.attested_header.slot; + + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::InvalidUpdateSlot + ); + }); +} + +#[test] +fn submit_update_with_skipped_sync_committee_period() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_update = load_next_finalized_header_update_fixture(); + let checkpoint_period = compute_period(checkpoint.header.slot); + let next_sync_committee_period = compute_period(finalized_update.finalized_header.slot); + assert_eq!(checkpoint_period + 1, next_sync_committee_period); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(finalized_update)), + Error::::SkippedSyncCommitteePeriod + ); + }); +} + +#[test] +fn submit_update_execution_headers_too_far_behind() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let execution_header_update = load_execution_header_update_fixture(); + let next_update = load_next_sync_committee_update_fixture(); + + new_tester().execute_with(|| { + let far_ahead_finalized_header_slot = finalized_header_update.finalized_header.slot + + (EPOCHS_PER_SYNC_COMMITTEE_PERIOD * SLOTS_PER_EPOCH * 2) as u64; + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + )); + + let header_root: H256 = TEST_HASH.into(); + >::insert( + header_root, + CompactBeaconState { + slot: far_ahead_finalized_header_slot, + block_roots_root: Default::default(), + }, + ); + LatestFinalizedBlockRoot::::set(header_root); + + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(next_update)), + Error::::ExecutionHeaderTooFarBehind + ); + }); +} + +#[test] +fn submit_irrelevant_update() { + let checkpoint = load_checkpoint_update_fixture(); + let mut update = load_next_finalized_header_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + + // makes an invalid update where the attested_header slot value should be greater than the + // checkpoint slot value + update.finalized_header.slot = checkpoint.header.slot; + update.attested_header.slot = checkpoint.header.slot; + update.signature_slot = checkpoint.header.slot + 1; + + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::IrrelevantUpdate + ); + }); +} + +#[test] +fn submit_update_with_missing_bootstrap() { + let update = load_next_finalized_header_update_fixture(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::NotBootstrapped + ); + }); +} + +#[test] +fn submit_update_with_invalid_sync_committee_update() { + let checkpoint = load_checkpoint_update_fixture(); + let update = load_sync_committee_update_fixture(); + let mut next_update = load_next_sync_committee_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + + assert_ok!(EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update))); + + // makes update with invalid next_sync_committee + >::mutate(>::get(), |x| { + let prev = x.unwrap(); + *x = Some(CompactBeaconState { slot: next_update.attested_header.slot, ..prev }); + }); + next_update.attested_header.slot += 1; + next_update.signature_slot = next_update.attested_header.slot + 1; + let next_sync_committee = NextSyncCommitteeUpdate::default(); + next_update.next_sync_committee_update = Some(next_sync_committee); + + assert_err!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(next_update)), + Error::::InvalidSyncCommitteeUpdate + ); + }); +} + +#[test] +fn submit_execution_header_update() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let execution_header_update = load_execution_header_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update.clone()) + )); + assert!(>::contains_key( + execution_header_update.execution_header.block_hash + )); + }); +} + +#[test] +fn submit_execution_header_update_invalid_ancestry_proof() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let mut execution_header_update = load_execution_header_update_fixture(); + if let Some(ref mut ancestry_proof) = execution_header_update.ancestry_proof { + ancestry_proof.header_branch[0] = TEST_HASH.into() + } + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + ), + Error::::InvalidAncestryMerkleProof + ); + }); +} + +#[test] +fn submit_execution_header_update_invalid_execution_header_proof() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let mut execution_header_update = load_execution_header_update_fixture(); + execution_header_update.execution_branch[0] = TEST_HASH.into(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + ), + Error::::InvalidExecutionHeaderProof + ); + }); +} + +#[test] +fn submit_execution_header_update_that_skips_block() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let execution_header_update = load_execution_header_update_fixture(); + let mut skipped_block_execution_header_update = load_execution_header_update_fixture(); + skipped_block_execution_header_update.execution_header.block_number = + execution_header_update.execution_header.block_number + 2; + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_ok!(EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update.clone()) + )); + assert!(>::contains_key( + execution_header_update.execution_header.block_hash + )); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(skipped_block_execution_header_update) + ), + Error::::ExecutionHeaderSkippedBlock + ); + }); +} + +#[test] +fn submit_execution_header_update_that_is_also_finalized_header_which_is_not_stored() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let mut execution_header_update = load_execution_header_update_fixture(); + execution_header_update.ancestry_proof = None; + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + ), + Error::::ExpectedFinalizedHeaderNotStored + ); + }); +} + +#[test] +fn submit_execution_header_update_that_is_also_finalized_header_which_is_stored_but_slots_dont_match( +) { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let mut execution_header_update = load_execution_header_update_fixture(); + execution_header_update.ancestry_proof = None; + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + + let block_root: H256 = execution_header_update.header.hash_tree_root().unwrap(); + + >::insert( + block_root, + CompactBeaconState { + slot: execution_header_update.header.slot + 1, + block_roots_root: Default::default(), + }, + ); + LatestFinalizedBlockRoot::::set(block_root); + + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + ), + Error::::ExpectedFinalizedHeaderNotStored + ); + }); +} + +#[test] +fn submit_execution_header_not_finalized() { + let checkpoint = load_checkpoint_update_fixture(); + let finalized_header_update = load_finalized_header_update_fixture(); + let update = load_execution_header_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + assert_ok!(EthereumBeaconClient::submit( + RuntimeOrigin::signed(1), + Box::new(finalized_header_update) + )); + + >::mutate(>::get(), |x| { + let prev = x.unwrap(); + *x = Some(CompactBeaconState { slot: update.header.slot - 1, ..prev }); + }); + + assert_err!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(update) + ), + Error::::HeaderNotFinalized + ); + }); +} + +/* IMPLS */ + +#[test] +fn verify_message() { + let header = get_message_verification_header(); + let (event_log, proof) = get_message_verification_payload(); + let block_hash = proof.block_hash; + + new_tester().execute_with(|| { + >::insert(block_hash, header); + assert_ok!(EthereumBeaconClient::verify(&event_log, &proof)); + }); +} + +#[test] +fn verify_message_missing_header() { + let (event_log, proof) = get_message_verification_payload(); + + new_tester().execute_with(|| { + assert_err!( + EthereumBeaconClient::verify(&event_log, &proof), + VerificationError::HeaderNotFound + ); + }); +} + +#[test] +fn verify_message_invalid_proof() { + let header = get_message_verification_header(); + let (event_log, mut proof) = get_message_verification_payload(); + proof.data.1[0] = TEST_HASH.into(); + let block_hash = proof.block_hash; + + new_tester().execute_with(|| { + >::insert(block_hash, header); + assert_err!( + EthereumBeaconClient::verify(&event_log, &proof), + VerificationError::InvalidProof + ); + }); +} + +#[test] +fn verify_message_invalid_receipts_root() { + let mut header = get_message_verification_header(); + let (event_log, proof) = get_message_verification_payload(); + let block_hash = proof.block_hash; + header.receipts_root = TEST_HASH.into(); + + new_tester().execute_with(|| { + >::insert(block_hash, header); + assert_err!( + EthereumBeaconClient::verify(&event_log, &proof), + VerificationError::InvalidProof + ); + }); +} + +#[test] +fn verify_message_invalid_log() { + let header = get_message_verification_header(); + let (mut event_log, proof) = get_message_verification_payload(); + let block_hash = proof.block_hash; + event_log.topics = vec![H256::zero(); 10]; + + new_tester().execute_with(|| { + >::insert(block_hash, header); + assert_err!( + EthereumBeaconClient::verify(&event_log, &proof), + VerificationError::InvalidLog + ); + }); +} + +#[test] +fn verify_message_receipt_does_not_contain_log() { + let header = get_message_verification_header(); + let (mut event_log, proof) = get_message_verification_payload(); + let block_hash = proof.block_hash; + event_log.data = hex!("f9013c94ee9170abfbf9421ad6dd07f6bdec9d89f2b581e0f863a01b11dcf133cc240f682dab2d3a8e4cd35c5da8c9cf99adac4336f8512584c5ada000000000000000000000000000000000000000000000000000000000000003e8a00000000000000000000000000000000000000000000000000000000000000002b8c000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000068000f000000000000000101d184c103f7acc340847eee82a0b909e3358bc28d440edffa1352b13227e8ee646f3ea37456dec70100000101001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c0000e8890423c78a0000000000000000000000000000000000000000000000000000000000000000").to_vec(); + + new_tester().execute_with(|| { + >::insert(block_hash, header); + assert_err!( + EthereumBeaconClient::verify(&event_log, &proof), + VerificationError::LogNotFound + ); + }); +} + +#[test] +fn set_operating_mode() { + let checkpoint = load_checkpoint_update_fixture(); + let update = load_finalized_header_update_fixture(); + let execution_header_update = load_execution_header_update_fixture(); + + new_tester().execute_with(|| { + assert_ok!(EthereumBeaconClient::process_checkpoint_update(&checkpoint)); + + assert_ok!(EthereumBeaconClient::set_operating_mode( + RuntimeOrigin::root(), + snowbridge_core::BasicOperatingMode::Halted + )); + + assert_noop!( + EthereumBeaconClient::submit(RuntimeOrigin::signed(1), Box::new(update)), + Error::::Halted + ); + + assert_noop!( + EthereumBeaconClient::submit_execution_header( + RuntimeOrigin::signed(1), + Box::new(execution_header_update) + ), + Error::::Halted + ); + }); +} + +#[test] +fn set_operating_mode_root_only() { + new_tester().execute_with(|| { + assert_noop!( + EthereumBeaconClient::set_operating_mode( + RuntimeOrigin::signed(1), + snowbridge_core::BasicOperatingMode::Halted + ), + DispatchError::BadOrigin + ); + }); +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/types.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dcefea9f80f4e201d8de633a7a323f530220a45 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/types.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pub use crate::config::{ + SLOTS_PER_HISTORICAL_ROOT, SYNC_COMMITTEE_BITS_SIZE as SC_BITS_SIZE, + SYNC_COMMITTEE_SIZE as SC_SIZE, +}; +use frame_support::storage::types::OptionQuery; +use snowbridge_core::RingBufferMapImpl; + +// Specialize types based on configured sync committee size +pub type SyncCommittee = primitives::SyncCommittee; +pub type SyncCommitteePrepared = primitives::SyncCommitteePrepared; +pub type SyncAggregate = primitives::SyncAggregate; +pub type CheckpointUpdate = primitives::CheckpointUpdate; +pub type Update = primitives::Update; +pub type NextSyncCommitteeUpdate = primitives::NextSyncCommitteeUpdate; + +pub use primitives::ExecutionHeaderUpdate; + +/// ExecutionHeader ring buffer implementation +pub type ExecutionHeaderBuffer = RingBufferMapImpl< + u32, + ::MaxExecutionHeadersToKeep, + crate::ExecutionHeaderIndex, + crate::ExecutionHeaderMapping, + crate::ExecutionHeaders, + OptionQuery, +>; + +/// FinalizedState ring buffer implementation +pub(crate) type FinalizedBeaconStateBuffer = RingBufferMapImpl< + u32, + crate::MaxFinalizedHeadersToKeep, + crate::FinalizedBeaconStateIndex, + crate::FinalizedBeaconStateMapping, + crate::FinalizedBeaconState, + OptionQuery, +>; diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/weights.rs b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..69d3e809986b61bb54b5d98dedfd2d0b41053b14 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/src/weights.rs @@ -0,0 +1,68 @@ +//! Autogenerated weights for ethereum_beacon_client +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-27, STEPS: `10`, REPEAT: 10, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("/tmp/snowbridge/spec.json"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/snowbridge +// benchmark +// pallet +// --chain +// /tmp/snowbridge/spec.json +// --execution=wasm +// --pallet +// ethereum_beacon_client +// --extrinsic +// * +// --steps +// 10 +// --repeat +// 10 +// --output +// pallets/ethereum-beacon-client/src/weights.rs +// --template +// templates/module-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for ethereum_beacon_client. +pub trait WeightInfo { + fn force_checkpoint() -> Weight; + fn submit() -> Weight; + fn submit_with_sync_committee() -> Weight; + fn submit_execution_header() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn force_checkpoint() -> Weight { + Weight::from_parts(97_263_571_000_u64, 0) + .saturating_add(Weight::from_parts(0, 3501)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(9)) + } + fn submit() -> Weight { + Weight::from_parts(26_051_019_000_u64, 0) + .saturating_add(Weight::from_parts(0, 93857)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + fn submit_with_sync_committee() -> Weight { + Weight::from_parts(122_461_312_000_u64, 0) + .saturating_add(Weight::from_parts(0, 93857)) + .saturating_add(RocksDbWeight::get().reads(6)) + .saturating_add(RocksDbWeight::get().writes(1)) + } + fn submit_execution_header() -> Weight { + Weight::from_parts(113_158_000_u64, 0) + .saturating_add(Weight::from_parts(0, 3537)) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } +} diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/execution-header-update.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/execution-header-update.minimal.json new file mode 100644 index 0000000000000000000000000000000000000000..3e17c14f4adbf38d4c57919bb91f9574bf515cd3 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/execution-header-update.minimal.json @@ -0,0 +1,43 @@ +{ + "header": { + "slot": 3622, + "proposer_index": 7, + "parent_root": "0x254c9215f6cce83e21b9776afb482181639602d3cb58cf99452a6a4a4f603930", + "state_root": "0xea98df6d30817d63f3e54ea118e2b1ba8675753c72dec1661c503d4eb43f9bdd", + "body_root": "0x765a0616a31d38e0ca2d10f6e8b234dd3d07e16aa929bcbc4de775c93f1972fd" + }, + "ancestry_proof": { + "header_branch": [ + "0x7690506882ac8c5f01d00f3ade06439259a3a0261ef5d61ec44920678b4104e6", + "0xf01aa0fdd7c9ef7b1affb7854fe8cbcc5c70643ee5b83e032faa702a0675a8cb", + "0x273a7b300b75ffa2c765af50680aa836299264f2107f38010278822313181801", + "0x30fe73a3bae6a31af32656ab759a4b67d27a213e01012b96cc4fedd0f2e77c75", + "0x7246cb3a35f13a1f0bbf907887985bb5382c45f2aa1699dbca48a0a82d5330af", + "0x5e7270e88a22dd4a905b2e76da2c8c358baeddd34de6c64a71bb1c80070ab717" + ], + "finalized_block_root": "0xa6fdc5df11c1759d11c9f0353a666715e5677e9ffd7d414e44cff0970553f1c9" + }, + "execution_header": { + "parent_hash": "0x6c9657f1267ad6040ea017ff6d02b55c4ba25cb092b8326d321dd98d01d1ee64", + "fee_recipient": "0x0000000000000000000000000000000000000000", + "state_root": "0x01f975f7cdff9b0a8844304aa59062fe18af0fef4636539312dfe20d238600ba", + "receipts_root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logs_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prev_randao": "0xcdfcab74bc26b3f4311afdc72d2d21d33a4b045187a01fa208a9d687a6d1d25c", + "block_number": 3622, + "gas_limit": 30000000, + "gas_used": 0, + "timestamp": 1685722543, + "extra_data": "0xd983010b02846765746888676f312e31392e358664617277696e", + "base_fee_per_gas": 7, + "block_hash": "0x38c80e0e26cb80730df627d32f50266bd0fe32fb12b7606300ad81aa2b4033db", + "transactions_root": "0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1", + "withdrawals_root": "0x28ba1834a3a7b657460ce79fa3a1d909ab8828fd557659d4d0554a9bdbc0ec30" + }, + "execution_branch": [ + "0x005b8d55b34b4323bfd4773c28b09eb53bc87959e65411ccd23728c7e42d5ff2", + "0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e", + "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x7061330dada1ba1c602ba98f647a441885460ed0db00483fea1282385dfab84b" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/finalized-header-update.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/finalized-header-update.minimal.json new file mode 100644 index 0000000000000000000000000000000000000000..c6473529b10c6d32398e55e11a2f71cbdc50b279 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/finalized-header-update.minimal.json @@ -0,0 +1,38 @@ +{ + "attested_header": { + "slot": 3640, + "proposer_index": 5, + "parent_root": "0xf062fcec9c3379a08e6add37a834b1e39af395fc343973e44957ecebbf2ecddd", + "state_root": "0xb1581cb62fe376e305e02f26463153f5dfb804d8df97ef40fc315c1bc30731ba", + "body_root": "0x98461abcc6d130b7bcb9430292c8a269ea9f01082685347e2968d892f716067c" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffff", + "sync_committee_signature": "0x925c6e4b67890a7e28a7ca19853f88247e92014b9d233ac9058efd4f3827f0055db308debe17596e635b93727b5a851e1366ca801f30b03fdec722f45011504702a27646488b5ab5e3428fe7b4d4a50132f374612f66e45d68db27c568f96f08" + }, + "signature_slot": 3641, + "next_sync_committee_update": null, + "finalized_header": { + "slot": 3624, + "proposer_index": 7, + "parent_root": "0x7690506882ac8c5f01d00f3ade06439259a3a0261ef5d61ec44920678b4104e6", + "state_root": "0x3726ebb8d9973977a71a8389caf5fc5830eeb8cd4fdfbbc7b0c4e6ca3e6a4090", + "body_root": "0x0f9a3f0fa5a4ffaf7c10504c86f23e7d554366ffd069fe958a160b253c3fd409" + }, + "finality_branch": [ + "0xc501000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x83c3d5360d254f4a44be712c1f433e88e810b6d1e0e789e90bada9e36126b857", + "0x97245fa01a89a6d7b4542cd731fef699f58b2bbaabdd6f641334c9e9eeae3a20", + "0xc3d19c773f66ab94bc2106d5e75a3205398dd6e94b6f8a5716f347741eb9fc5a", + "0x9e5040e56d765c1add56779a716be7497be27cba37f866cd8d34418d55e48715" + ], + "block_roots_root": "0x29a54625749fa25f9e36df14a3baa335c58246bba2f8c7eb8b1ec2e4908e2fd0", + "block_roots_branch": [ + "0x53616f9298818a8423c98adc47c92aaf82f0c5c911dc4ee5f88ba6d3022341c1", + "0x5d2f1c4bce6f63f26cbe3fbf480281c04a6b14bea74350a88ee945354ecbd79d", + "0x8333eefc7eaa4d10091e2014b3aae2bf6bd2d10c22c67100e189f8ab6caab261", + "0x3edfa69130bc193dec47c27a5903f03d5262b75899b69c0e95ac1816a664a3e7", + "0x5e046000f85aede8d4c28140b27778488d4ad21b1e16e345055d07ee53f2711b" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/initial-checkpoint.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/initial-checkpoint.minimal.json new file mode 100644 index 0000000000000000000000000000000000000000..a7e48f459019e39af7e6eb016b3547840b7c028d --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/initial-checkpoint.minimal.json @@ -0,0 +1,62 @@ +{ + "header": { + "slot": 3616, + "proposer_index": 7, + "parent_root": "0x6c5e8c7b32b7bfbb250fa8fd7bc348d7325fb2bfc869e4c506af6802fcad87f4", + "state_root": "0x3e467e3429a1ae36572fe3fe1c953381242e950254cf97c7527a8cea8aa6c9de", + "body_root": "0x7da749680d2b0b4f779047fcfe7d0c13d247f6d23478817fe9c6fbe07993adb2" + }, + "current_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c" + ], + "aggregate_pubkey": "0x8fe11476a05750c52618deb79918e2e674f56dfbf12dbce55ae4386d108e8a1e83c6326f5957e2ef19137582ce270dc6" + }, + "current_sync_committee_branch": [ + "0x46af3f54acbea439b63aa5bb699c8f25ff584b23912366788f7c8e95011ce324", + "0x41dcb71ec3b3940399118d28e09fdc58a8e33b818b8c5cbb933c59929504ca08", + "0xfa53febb29348e3493a50c0e7c6d35796bf69c54dfc6f42f7600612789d0ed6d", + "0x5e7ea1693066b604fc60d4657b43e7a4aafd3f4f54d9a740d2abe765e92d8385", + "0x16c9bca64a82e80c23817bfec345d088e0adc3865e392965c1244f97979f816a" + ], + "validators_root": "0x270d43e74ce340de4bca2b1936beca0f4f5408d9e78aec4850920baf659d5b69", + "block_roots_root": "0x00f6bbdeac1e1a922a9bf0e78720c0bffe558d8195e8ede8cb72bbd295f242f2", + "block_roots_branch": [ + "0x7a61086fb9e53ab4dd87243d6288c51793696168a73773277630da5b20bf6091", + "0x60733905cdc5dd65d05161bb3138eecc47d6d6057ab36b0d36cf5a3200484143", + "0x86d7de634ae45de5b3cbbc562dd976de7d06a3d96f83147413536e6b108c7a39", + "0x0ada571c9e0da6fce8dd13e6d9ce173768521ac32e0af456634556176789fa6e", + "0x2341538fd0aafbc1ff0f513545e5dcd4b8905dc9e00d6173480c18a4e8086ebc" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-finalized-header-update.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-finalized-header-update.minimal.json new file mode 100755 index 0000000000000000000000000000000000000000..8f1ddc827c1f45ade879bfd611bdeb1cb223d17a --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-finalized-header-update.minimal.json @@ -0,0 +1,38 @@ +{ + "attested_header": { + "slot": 3696, + "proposer_index": 1, + "parent_root": "0x04a63c5dfb726c31a32a72c1c426ff89e21363223d7096486b629f1d58abe5d8", + "state_root": "0xbe20e69420cbf9400224ec5edeb0843776a2ccf945e9a3ba9311ae812cad1e30", + "body_root": "0x1d2acd1748f1c58096d1edc8badd3a1d7e1dc3c33bcb9229e4c03f3a84efeadb" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffff", + "sync_committee_signature": "0xafa79bc0f3c731ab1eb6aeafc582a7dd1c100ea471df3af6ff485b58661b3ef8077264dea0b60df9aec2d3ca8ddab6770fc9d061462e5a6dc718146085425f863d00921c42413805cb5b4c5175f36f2087cfed740bb7d57e8d5b48352643cd5b" + }, + "signature_slot": 3697, + "next_sync_committee_update": null, + "finalized_header": { + "slot": 3680, + "proposer_index": 7, + "parent_root": "0x4d8f4fc47ad3eb045bd20cae13af6df02f96a3f8d7c8a285190ba10cfe2b84cf", + "state_root": "0xd498766d77277fe16a6a4609ab3ac3a6e9887d162d8dfffdfc9cc4ae833e4127", + "body_root": "0x9ba73bc9a4907cac0b887550e2b01a63dcc70473753ffcc243d33394cc64b4c0" + }, + "finality_branch": [ + "0xcc01000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x142061c4bc3673bf774cb8c7b6085057bd0ca85672b43afa2d9581b0b6a44e54", + "0x48b8cd8ca9d9563e30c1cca2a854cd7f75eb4cb013d10809b3138a72d94ea0c5", + "0x9b39523d05013ac7cbb9f43e5d6f9dc033b12aa1d6d6edd994ddc4f5efe7be9d", + "0x066c9aa26107bc8cb28bc73e518da6cc865ec1d67516b6ca24663b6b7ae3cb21" + ], + "block_roots_root": "0xb15aa2483811d8c5616cb93710f4fcb809d97443caac9de163f943a30f385db6", + "block_roots_branch": [ + "0xf7a43ad317417daa4c2a1e93c54895895a824ef1e43320eb44eab16673da5a61", + "0xe4b8d640660f765c2ef4dc886025dc8e54c6e70b66192582f42837ed5e9d8d41", + "0x841f113dc81e76419b6cdec8b0cf2fc20f9381492ed3c79e9b49179b4d3eacbc", + "0xeb5fdc4d8b5282b653ecbc9caa93bcfe482f6d6a32cbb0d9eb011bef947579bb", + "0x1f328cc5640efb191ae6aa86223b1aa9d083b26ac3e1fa3c071327bb09dc5727" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-sync-committee-update.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-sync-committee-update.minimal.json new file mode 100755 index 0000000000000000000000000000000000000000..8f1c8b9ce21cda6d7a10fb7973c4e628cd40b122 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/next-sync-committee-update.minimal.json @@ -0,0 +1,83 @@ +{ + "attested_header": { + "slot": 3664, + "proposer_index": 4, + "parent_root": "0x15ac23a0c16bfa81e8595621118040c3e6cbddd4b09bae6fb39ba5fefd0258e8", + "state_root": "0x6fb81aa3827e7d580bb05b4df2686c9a49508bde2f8342fd75be609a23dd8362", + "body_root": "0x9906a1ae8065d268f8acb7f1b3119408d2f7f8e6e0764370c16ea3d15134981f" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffff", + "sync_committee_signature": "0xa9b5584ec9290a4ac6c5616639d031f9ab1064d63b4889f1da52f6f4d66b645fca48bbe2fe8484adb0c05c647edd694d0340cf684b8ccf8e34c6d8cf447cfcfdcb856f5abdcfd85ada5a4a04d4c8f6f40c6e99308893c3941485a436d6c8e5f7" + }, + "signature_slot": 3665, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373" + ], + "aggregate_pubkey": "0x8fe11476a05750c52618deb79918e2e674f56dfbf12dbce55ae4386d108e8a1e83c6326f5957e2ef19137582ce270dc6" + }, + "next_sync_committee_branch": [ + "0x46af3f54acbea439b63aa5bb699c8f25ff584b23912366788f7c8e95011ce324", + "0x5b118fe110ee4a1b0cf9823bc189fb38eb55a7b49adbdafcf466ec7cd4b7fd68", + "0xc2f12fb91a61abedb47f62a98258960edca21f31494cdf59b47a1c721e3e98f8", + "0x16fdfd5e6b591b3140a76efa4593a9c4d105b9e5c62d6f44edbd24790657be50", + "0xc8175ab66690cc94c0a24452754addd62a06948de5db9814e813437a130de452" + ] + }, + "finalized_header": { + "slot": 3648, + "proposer_index": 1, + "parent_root": "0x991ee98a70e8f90bdd61d0f5554e53d37473e75e16af171f6d88f27d20223dae", + "state_root": "0x59b04d660ac772005a13a7dc1d5f99bb0d0292f3c422f04f7365198d70dd30de", + "body_root": "0x5151f035e146258e7327ad9cf1df13f8ddec7a7842c19993cf739358717b5565" + }, + "finality_branch": [ + "0xc801000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x142061c4bc3673bf774cb8c7b6085057bd0ca85672b43afa2d9581b0b6a44e54", + "0xc2f12fb91a61abedb47f62a98258960edca21f31494cdf59b47a1c721e3e98f8", + "0x16fdfd5e6b591b3140a76efa4593a9c4d105b9e5c62d6f44edbd24790657be50", + "0xc8175ab66690cc94c0a24452754addd62a06948de5db9814e813437a130de452" + ], + "block_roots_root": "0xe6e2adaaad45363d7112945ef670e21c66bcb3276dc450962ade1e8950230380", + "block_roots_branch": [ + "0x386ede102258966d4c23031c5a02de2af8180d475c4c1716b07fb5b9f142a817", + "0x35e6c89bc38d993a1957f8a9fb1fbeab7420688091ba2cd7ee7b19b7e187f7d6", + "0x99249309825cafef7e694c09c4fdf95eb4b1e8743d3b23f6959d9980ad2d69b0", + "0x5e028d1d905db6430f0ce4aafbc78f442047ec3a132b4e69557fdf804a4cfbf3", + "0xd34afeab37851937920243683a1c926c41c626aacb145718fce755782d4996dd" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/sync-committee-update.minimal.json b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/sync-committee-update.minimal.json new file mode 100644 index 0000000000000000000000000000000000000000..a962a0c87c4c5eedee944bd85f54a754576935f1 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/ethereum-beacon-client/tests/fixtures/sync-committee-update.minimal.json @@ -0,0 +1,83 @@ +{ + "attested_header": { + "slot": 3600, + "proposer_index": 7, + "parent_root": "0xdf60c2d58beccd89678b9267c689e9ba1cf1d58ce5114ad5c16e8341459cfd75", + "state_root": "0x023f14c7a38ef4d6ec19b522edfb427c6b70c6ffbd8610ca802dd1491c92c852", + "body_root": "0x0f78a1c45e42711efc5fb7b7f6238be1bee9273f7c44ff6892d815858bb77e25" + }, + "sync_aggregate": { + "sync_committee_bits": "0xffffffff", + "sync_committee_signature": "0xa4dd8f0991de88ca6f81476f72f48cdb67b9414ad7bf6bba37f627c5ec84dd2c2ebc12cddd5d2e7c927276cee2d3d144158b4c067db3e9911fe52fe1875b14c93f90e4eb57bf5e8f0e6e6effe22f9ba076f30207e0ec683354961ae8e9779556" + }, + "signature_slot": 3601, + "next_sync_committee_update": { + "next_sync_committee": { + "pubkeys": [ + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c", + "0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34", + "0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac", + "0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b", + "0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b", + "0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e", + "0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e", + "0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373", + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c" + ], + "aggregate_pubkey": "0x8fe11476a05750c52618deb79918e2e674f56dfbf12dbce55ae4386d108e8a1e83c6326f5957e2ef19137582ce270dc6" + }, + "next_sync_committee_branch": [ + "0x1446606d0129c324a4ea374bd29a625175e0659512cd8650097e0a9c38ce6379", + "0xd92466c7e9a53b7b55f4fdb151746a3058931d7559b7e84e7b15384ddc903ca0", + "0x9fd10c3f68b75cfd3ebd2af0d4e2cbbfbe120e0b5423dde89ff0f743c7a4f937", + "0x1ed6aac0ab29a883de2bb2e3579ad4d6807ddcf3db8afcaf0ae25a076ac9a5f4", + "0xf17a840df410a15f0e4e48abf521c29ad0d296d3fb4e8b847ea37f2cc8236f1f" + ] + }, + "finalized_header": { + "slot": 3584, + "proposer_index": 1, + "parent_root": "0x91c285af2ec25d485310391afe667108b787ec570cdbb0e3fd87b1e0e2c47bd7", + "state_root": "0xccc4baf90024e035f1252520d2f2ef1e50f840ff0ecc8e6e365721e083871a32", + "body_root": "0x91df5e0077434aad609aaa7e030005cee77cca83868ffc2724e5befe9a3f6a02" + }, + "finality_branch": [ + "0xc001000000000000000000000000000000000000000000000000000000000000", + "0x10c726fac935bf9657cc7476d3cfa7bedec5983dcfb59e8a7df6d0a619e108d7", + "0x83c3d5360d254f4a44be712c1f433e88e810b6d1e0e789e90bada9e36126b857", + "0x9fd10c3f68b75cfd3ebd2af0d4e2cbbfbe120e0b5423dde89ff0f743c7a4f937", + "0x1ed6aac0ab29a883de2bb2e3579ad4d6807ddcf3db8afcaf0ae25a076ac9a5f4", + "0xf17a840df410a15f0e4e48abf521c29ad0d296d3fb4e8b847ea37f2cc8236f1f" + ], + "block_roots_root": "0x9eab8a05c396a29c32f4f8ac9654fc0fb7cd97ec659236392ede48951a794505", + "block_roots_branch": [ + "0x5c175efdbafacdfdab21c93a318b0e8e2291a5a86c40b1fc564f91ad33c106d4", + "0x5c1e0b76176ab033858b2835f90d5e25d708b563f77efd7d9938f0faa1c20878", + "0x7aea32464adee801e2a05c3af227f24231d3c088e3b7265a5fada9ac850549fe", + "0x9d9fca29e23c5d4ae433adf17e7fd9a0e4d1b09b68f5c45e7ca1b13ebe4a9e98", + "0x6b35238f188021c859d6b317457ebb6fe4cf362cab35c988010cb1343eabbfc5" + ] +} \ No newline at end of file diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6a5ea6f82223e2e26d54b2116cac6e8fdb48b8d0 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml @@ -0,0 +1,93 @@ +[package] +name = "snowbridge-inbound-queue" +description = "Snowbridge Inbound Queue" +version = "0.1.1" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.195", optional = 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 } +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 } + +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } + +snowbridge-core = { path = "../../primitives/core", default-features = false } +snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } +snowbridge-router-primitives = { path = "../../primitives/router", default-features = false } +snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false, optional = true } + +[dev-dependencies] +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking" } +sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +snowbridge-beacon-primitives = { path = "../../primitives/beacon" } +snowbridge-ethereum-beacon-client = { path = "../../pallets/ethereum-beacon-client" } +hex-literal = { version = "0.4.1" } + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-rlp/std", + "alloy-sol-types/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "num-traits/std", + "pallet-balances/std", + "scale-info/std", + "serde", + "snowbridge-core/std", + "snowbridge-ethereum/std", + "snowbridge-router-primitives/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "snowbridge-beacon-primitives", + "snowbridge-core/runtime-benchmarks", + "snowbridge-ethereum-beacon-client/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "snowbridge-ethereum-beacon-client/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs new file mode 100644 index 0000000000000000000000000000000000000000..4f2382d072abd5c145b800f0126ea076f3549cce --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/fixtures.rs @@ -0,0 +1,40 @@ +use hex_literal::hex; +use snowbridge_beacon_primitives::CompactExecutionHeader; +use snowbridge_core::inbound::{Log, Message, Proof}; +use sp_std::vec; + +pub struct InboundQueueTest { + pub execution_header: CompactExecutionHeader, + pub message: Message, +} + +pub fn make_create_message() -> InboundQueueTest { + InboundQueueTest{ + execution_header: CompactExecutionHeader{ + parent_hash: hex!("b5608f0af7c3b6fe5c593772fc25436b8d6549eb236adb0855c6ad33e0004e04").into(), + block_number: 115, + state_root: hex!("47ed174789836c622499d9659a4ac32c3b91a7b15642d39b0a11b82ff23995c1").into(), + receipts_root: hex!("42c08b5303fcdf9e49c833fe5f1182cdbc8206bf8aec581125fc34aba11e1f1a").into(), + }, + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + block_hash: hex!("add15f439c8a57fe375d0a679870b1359921d70cb0e3e44f0dd3e272849f4097").into(), + tx_index: 0, + data: (vec![ + hex!("42c08b5303fcdf9e49c833fe5f1182cdbc8206bf8aec581125fc34aba11e1f1a").to_vec(), + ], vec![ + hex!("f9028e822080b9028802f90284018301ed20b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(), + ]), + }, + }, + } +} diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c10de9dff2ff0e288068274c1d8b4075a471cf25 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs @@ -0,0 +1,55 @@ +mod fixtures; + +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use crate::Pallet as InboundQueue; +use frame_benchmarking::v2::*; +use frame_support::assert_ok; +use frame_system::RawOrigin; + +#[benchmarks] +mod benchmarks { + use super::*; + use crate::benchmarking::fixtures::make_create_message; + + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + + let create_message = make_create_message(); + + T::Helper::initialize_storage( + create_message.message.proof.block_hash, + create_message.execution_header, + ); + + let sovereign_account = sibling_sovereign_account::(1000u32.into()); + + let minimum_balance = T::Token::minimum_balance(); + + // So that the receiving account exists + assert_ok!(T::Token::mint_into(&caller, minimum_balance)); + // Fund the sovereign account (parachain sovereign account) so it can transfer a reward + // fee to the caller account + assert_ok!(T::Token::mint_into( + &sovereign_account, + 3_000_000_000_000u128 + .try_into() + .unwrap_or_else(|_| panic!("unable to cast sovereign account balance")), + )); + + #[block] + { + assert_ok!(InboundQueue::::submit( + RawOrigin::Signed(caller.clone()).into(), + create_message.message, + )); + } + + Ok(()) + } + + impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); +} diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs new file mode 100644 index 0000000000000000000000000000000000000000..826d535c2cb922610ba4811d607a9024de8d33ab --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::{inbound::Log, ChannelId}; + +use sp_core::{RuntimeDebug, H160, H256}; +use sp_std::{convert::TryFrom, prelude::*}; + +use alloy_primitives::B256; +use alloy_sol_types::{sol, SolEvent}; + +sol! { + event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); +} + +/// An inbound message that has had its outer envelope decoded. +#[derive(Clone, RuntimeDebug)] +pub struct Envelope { + /// The address of the outbound queue on Ethereum that emitted this message as an event log + pub gateway: H160, + /// The message Channel + pub channel_id: ChannelId, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// An id for tracing the message on its route (has no role in bridge consensus) + pub message_id: H256, + /// The inner payload generated from the source application. + pub payload: Vec, +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct EnvelopeDecodeError; + +impl TryFrom<&Log> for Envelope { + type Error = EnvelopeDecodeError; + + fn try_from(log: &Log) -> Result { + let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + + let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; + + Ok(Self { + gateway: log.address, + channel_id: ChannelId::from(event.channel_id.as_ref()), + nonce: event.nonce, + message_id: H256::from(event.message_id.as_ref()), + payload: event.payload, + }) + } +} diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..834e805fbef5ab95314376f6650bb8dbd4955ada --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Inbound Queue +//! +//! # Overview +//! +//! Receives messages emitted by the Gateway contract on Ethereum, whereupon they are verified, +//! translated to XCM, and finally sent to their final destination parachain. +//! +//! The message relayers are rewarded using native currency from the sovereign account of the +//! destination parachain. +//! +//! # Extrinsics +//! +//! ## Governance +//! +//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable +//! processing of inbound messages. +//! +//! ## Message Submission +//! +//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination +//! parachain. +#![cfg_attr(not(feature = "std"), no_std)] + +mod envelope; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_beacon_primitives::CompactExecutionHeader; + +pub mod weights; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +use codec::{Decode, DecodeAll, Encode}; +use envelope::Envelope; +use frame_support::{ + traits::{ + fungible::{Inspect, Mutate}, + tokens::{Fortitude, Precision, Preservation}, + }, + weights::WeightToFee, + PalletError, +}; +use frame_system::ensure_signed; +use scale_info::TypeInfo; +use sp_core::{H160, H256}; +use sp_std::{convert::TryFrom, vec}; +use xcm::prelude::{ + send_xcm, Instruction::SetTopic, Junction::*, Junctions::*, MultiLocation, + SendError as XcmpSendError, SendXcm, Xcm, XcmHash, +}; + +use snowbridge_core::{ + inbound::{Message, VerificationError, Verifier}, + sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, StaticLookup, +}; +use snowbridge_router_primitives::{ + inbound, + inbound::{ConvertMessage, ConvertMessageError}, +}; +use sp_runtime::traits::Saturating; + +pub use weights::WeightInfo; + +type BalanceOf = + <::Token as Inspect<::AccountId>>::Balance; + +pub use pallet::*; + +pub const LOG_TARGET: &str = "snowbridge-inbound-queue"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use snowbridge_core::PricingParameters; + + #[pallet::pallet] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn initialize_storage(block_hash: H256, header: CompactExecutionHeader); + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The verifier for inbound messages from Ethereum + type Verifier: Verifier; + + /// Message relayers are rewarded with this asset + type Token: Mutate + Inspect; + + /// XCM message sender + type XcmSender: SendXcm; + + // Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + + /// Convert inbound message to XCM + type MessageConverter: ConvertMessage< + AccountId = Self::AccountId, + Balance = BalanceOf, + >; + + /// Lookup a channel descriptor + type ChannelLookup: StaticLookup; + + /// Lookup pricing parameters + type PricingParameters: Get>>; + + type WeightInfo: WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + + /// Convert a weight value into deductible balance type. + type WeightToFee: WeightToFee>; + + /// Convert a length value into deductible balance type + type LengthToFee: WeightToFee>; + + /// The upper limit here only used to estimate delivery cost + type MaxMessageSize: Get; + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A message was received from Ethereum + MessageReceived { + /// The message channel + channel_id: ChannelId, + /// The message nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + }, + /// Set OperatingMode + OperatingModeChanged { mode: BasicOperatingMode }, + } + + #[pallet::error] + pub enum Error { + /// Message came from an invalid outbound channel on the Ethereum side. + InvalidGateway, + /// Message has an invalid envelope. + InvalidEnvelope, + /// Message has an unexpected nonce. + InvalidNonce, + /// Message has an invalid payload. + InvalidPayload, + /// Message channel is invalid + InvalidChannel, + /// The max nonce for the type has been reached + MaxNonceReached, + /// Cannot convert location + InvalidAccountConversion, + /// Pallet is halted + Halted, + /// Message verification error, + Verification(VerificationError), + /// XCMP send failure + Send(SendError), + /// Message conversion error + ConvertMessage(ConvertMessageError), + } + + #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] + pub enum SendError { + NotApplicable, + NotRoutable, + Transport, + DestinationUnsupported, + ExceedsMaxMessageSize, + MissingArgument, + Fees, + } + + impl From for Error { + fn from(e: XcmpSendError) -> Self { + match e { + XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), + XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), + XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), + XcmpSendError::DestinationUnsupported => + Error::::Send(SendError::DestinationUnsupported), + XcmpSendError::ExceedsMaxMessageSize => + Error::::Send(SendError::ExceedsMaxMessageSize), + XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), + XcmpSendError::Fees => Error::::Send(SendError::Fees), + } + } + } + + /// The current nonce for each channel + #[pallet::storage] + pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Submit an inbound message originating from the Gateway contract on Ethereum + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::submit())] + pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + + // submit message to verifier for verification + T::Verifier::verify(&message.event_log, &message.proof) + .map_err(|e| Error::::Verification(e))?; + + // Decode event log into an Envelope + let envelope = + Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + + // Verify that the message was submitted from the known Gateway contract + ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); + + // Retrieve the registered channel for this message + let channel = + T::ChannelLookup::lookup(envelope.channel_id).ok_or(Error::::InvalidChannel)?; + + // Verify message nonce + >::try_mutate(envelope.channel_id, |nonce| -> DispatchResult { + if *nonce == u64::MAX { + return Err(Error::::MaxNonceReached.into()) + } + if envelope.nonce != nonce.saturating_add(1) { + Err(Error::::InvalidNonce.into()) + } else { + *nonce = nonce.saturating_add(1); + Ok(()) + } + })?; + + // Reward relayer from the sovereign account of the destination parachain + // Expected to fail if sovereign account has no funds + let sovereign_account = sibling_sovereign_account::(channel.para_id); + let delivery_cost = Self::calculate_delivery_cost(message.encode().len() as u32); + T::Token::transfer(&sovereign_account, &who, delivery_cost, Preservation::Preserve)?; + + // Decode message into XCM + let (xcm, fee) = + match inbound::VersionedMessage::decode_all(&mut envelope.payload.as_ref()) { + Ok(message) => Self::do_convert(envelope.message_id, message)?, + Err(_) => return Err(Error::::InvalidPayload.into()), + }; + + // We embed fees for xcm execution inside the xcm program using teleports + // so we must burn the amount of the fee embedded into the XCM script. + T::Token::burn_from(&sovereign_account, fee, Precision::Exact, Fortitude::Polite)?; + + log::info!( + target: LOG_TARGET, + "💫 xcm {:?} sent with fee {:?}", + xcm, + fee + ); + + // Attempt to send XCM to a dest parachain + let message_id = Self::send_xcm(xcm, channel.para_id)?; + + Self::deposit_event(Event::MessageReceived { + channel_id: envelope.channel_id, + nonce: envelope.nonce, + message_id, + }); + + Ok(()) + } + + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::set(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } + + impl Pallet { + pub fn do_convert( + message_id: H256, + message: inbound::VersionedMessage, + ) -> Result<(Xcm<()>, BalanceOf), Error> { + let (mut xcm, fee) = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + // Append the message id as an XCM topic + xcm.inner_mut().extend(vec![SetTopic(message_id.into())]); + Ok((xcm, fee)) + } + + pub fn send_xcm(xcm: Xcm<()>, dest: ParaId) -> Result> { + let dest = MultiLocation { parents: 1, interior: X1(Parachain(dest.into())) }; + let (xcm_hash, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Ok(xcm_hash) + } + + pub fn calculate_delivery_cost(length: u32) -> BalanceOf { + let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); + let len_fee = T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)); + weight_fee + .saturating_add(len_fee) + .saturating_add(T::PricingParameters::get().rewards.local) + } + } + + /// API for accessing the delivery cost of a message + impl Get> for Pallet { + fn get() -> BalanceOf { + // Cost here based on MaxMessagePayloadSize(the worst case) + Self::calculate_delivery_cost(T::MaxMessageSize::get()) + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..6b79a55e3c933304b720a42a97960df7de801dc9 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32, Everything}, + weights::IdentityFee, +}; +use hex_literal::hex; +use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_core::{ + gwei, + inbound::{Log, Proof, VerificationError}, + meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, +}; +use snowbridge_router_primitives::inbound::MessageToXcm; +use sp_core::{H160, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, FixedU128, MultiSignature, +}; +use sp_std::convert::From; +use xcm::v3::{prelude::*, MultiAssets, SendXcm}; + +use crate::{self as inbound_queue}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + EthereumBeaconClient: snowbridge_ethereum_beacon_client::{Pallet, Call, Storage, Event}, + InboundQueue: inbound_queue::{Pallet, Call, Storage, Event}, + } +); + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +type Balance = u128; + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + 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>; + type Nonce = u64; + type Block = Block; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); + type MaxHolds = (); +} + +parameter_types! { + pub const ExecutionHeadersPruneThreshold: u32 = 10; + pub const ChainForkVersions: ForkVersions = ForkVersions{ + genesis: Fork { + version: [0, 0, 0, 1], // 0x00000001 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 1], // 0x01000001 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 1], // 0x02000001 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 1], // 0x03000001 + epoch: 0, + }, + }; +} + +impl snowbridge_ethereum_beacon_client::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type MaxExecutionHeadersToKeep = ExecutionHeadersPruneThreshold; + type WeightInfo = (); +} + +// Mock verifier +pub struct MockVerifier; + +impl Verifier for MockVerifier { + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } +} + +const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; + +parameter_types! { + pub const EthereumNetwork: xcm::v3::NetworkId = xcm::v3::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetExecutionFee: u128 = 2_000_000_000; + pub const CreateAssetDeposit: u128 = 100_000_000_000; + pub const SendTokenExecutionFee: u128 = 1_000_000_000; + pub const InitialFund: u128 = 1_000_000_000_000; + pub const InboundQueuePalletInstance: u8 = 80; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for Test { + // not implemented since the MockVerifier is used for tests + fn initialize_storage(_: H256, _: CompactExecutionHeader) {} +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; + +impl SendXcm for MockXcmSender { + type Ticket = Xcm<()>; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + match dest { + Some(MultiLocation { interior, .. }) => { + if let X1(Parachain(1001)) = interior { + return Err(XcmpSendError::NotApplicable) + } + Ok((xcm.clone().unwrap(), MultiAssets::default())) + }, + _ => Ok((xcm.clone().unwrap(), MultiAssets::default())), + } + } + + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } +} + +parameter_types! { + pub const OwnParaId: ParaId = ParaId::new(1013); + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: DOT, remote: meth(1) } + }; +} + +pub const DOT: u128 = 10_000_000_000; + +pub struct MockChannelLookup; +impl StaticLookup for MockChannelLookup { + type Source = ChannelId; + type Target = Channel; + + fn lookup(channel_id: Self::Source) -> Option { + if channel_id != + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into() + { + return None + } + Some(Channel { agent_id: H256::zero(), para_id: ASSET_HUB_PARAID.into() }) + } +} + +impl inbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type Token = Balances; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + >; + type PricingParameters = Parameters; + type ChannelLookup = MockChannelLookup; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + type MaxMessageSize = ConstU32<1024>; +} + +pub fn last_events(n: usize) -> Vec { + frame_system::Pallet::::events() + .into_iter() + .rev() + .take(n) + .rev() + .map(|e| e.event) + .collect() +} + +pub fn expect_events(e: Vec) { + assert_eq!(last_events(e.len()), e); +} + +pub fn setup() { + System::set_block_number(1); + Balances::mint_into( + &sibling_sovereign_account::(ASSET_HUB_PARAID.into()), + InitialFund::get(), + ) + .unwrap(); + Balances::mint_into( + &sibling_sovereign_account::(TEMPLATE_PARAID.into()), + InitialFund::get(), + ) + .unwrap(); +} + +pub fn new_tester() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext +} + +// Generated from smoketests: +// cd smoketests +// ./make-bindings +// cargo test --test register_token -- --nocapture +pub fn mock_event_log() -> Log { + Log { + // gateway address + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + } +} + +pub fn mock_event_log_invalid_channel() -> Log { + Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // invalid channel id + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub fn mock_event_log_invalid_gateway() -> Log { + Log { + // gateway address + address: H160::zero(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub const ASSET_HUB_PARAID: u32 = 1000u32; +pub const TEMPLATE_PARAID: u32 = 1001u32; diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..6dc3ac4537450fc0a930620e173ee3dab59e51f0 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{assert_noop, assert_ok}; +use hex_literal::hex; +use snowbridge_core::{inbound::Proof, ChannelId}; +use sp_keyring::AccountKeyring as Keyring; +use sp_runtime::{DispatchError, TokenError}; +use sp_std::convert::From; + +use crate::{Error, Event as InboundQueueEvent}; + +use crate::mock::*; + +#[test] +fn test_submit_happy_path() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let channel_sovereign = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + + let origin = RuntimeOrigin::signed(relayer.clone()); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + + let initial_fund = InitialFund::get(); + assert_eq!(Balances::balance(&relayer), 0); + assert_eq!(Balances::balance(&channel_sovereign), initial_fund); + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") + .into(), + nonce: 1, + message_id: [ + 27, 217, 88, 127, 46, 143, 199, 70, 236, 66, 212, 244, 85, 221, 153, 104, 175, 37, + 224, 20, 140, 95, 140, 7, 27, 74, 182, 199, 77, 12, 194, 236, + ], + } + .into()]); + + let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); + assert!( + Parameters::get().rewards.local < delivery_cost, + "delivery cost exceeds pure reward" + ); + + assert_eq!(Balances::balance(&relayer), delivery_cost, "relayer was rewarded"); + assert!( + Balances::balance(&channel_sovereign) <= initial_fund - delivery_cost, + "sovereign account paid reward" + ); + }); +} + +#[test] +fn test_submit_xcm_invalid_channel() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of parachain 1001 + let sovereign_account = sibling_sovereign_account::(TEMPLATE_PARAID.into()); + println!("account: {}", sovereign_account); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_channel(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidChannel, + ); + }); +} + +#[test] +fn test_submit_with_invalid_gateway() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of Asset Hub (Statemint) + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_gateway(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidGateway + ); + }); +} + +#[test] +fn test_submit_with_invalid_nonce() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Deposit funds into sovereign account of Asset Hub (Statemint) + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + let _ = Balances::mint_into(&sovereign_account, 10000); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + let nonce: u64 = >::get(ChannelId::from(hex!( + "c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539" + ))); + assert_eq!(nonce, 1); + + // Submit the same again + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidNonce + ); + }); +} + +#[test] +fn test_submit_no_funds_to_reward_relayers() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Reset balance of sovereign_account to zero so to trigger the FundsUnavailable error + let sovereign_account = sibling_sovereign_account::(ASSET_HUB_PARAID.into()); + Balances::set_balance(&sovereign_account, 0); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + TokenError::FundsUnavailable + ); + }); +} + +#[test] +fn test_set_operating_mode() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + let message = Message { + event_log: mock_event_log(), + proof: Proof { + block_hash: Default::default(), + tx_index: Default::default(), + data: Default::default(), + }, + }; + + assert_ok!(InboundQueue::set_operating_mode( + RuntimeOrigin::root(), + snowbridge_core::BasicOperatingMode::Halted + )); + + assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); + }); +} + +#[test] +fn test_set_operating_mode_root_only() { + new_tester().execute_with(|| { + assert_noop!( + InboundQueue::set_operating_mode( + RuntimeOrigin::signed(Keyring::Bob.into()), + snowbridge_core::BasicOperatingMode::Halted + ), + DispatchError::BadOrigin + ); + }); +} diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs b/bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2c665f40d9e5e00ff452ad3e1151152b428bf07 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Autogenerated weights for `snowbridge_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for ethereum_beacon_client. +pub trait WeightInfo { + fn submit() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn submit() -> Weight { + Weight::from_parts(70_000_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f99fcc72e19f1828d91aef8c41a0e88c6e4add05 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "snowbridge-outbound-queue" +description = "Snowbridge Outbound Queue" +version = "0.1.1" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } +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 } + +bridge-hub-common = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } + +snowbridge-core = { path = "../../primitives/core", features = ["serde"], default-features = false } +snowbridge-outbound-queue-merkle-tree = { path = "merkle-tree", default-features = false } +ethabi = { git = "https://github.com/snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", 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" } +hex-literal = { version = "0.4.1" } + +[features] +default = ["std"] +std = [ + "bridge-hub-common/std", + "codec/std", + "ethabi/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-message-queue/std", + "scale-info/std", + "serde/std", + "snowbridge-core/std", + "snowbridge-outbound-queue-merkle-tree/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", +] +runtime-benchmarks = [ + "bridge-hub-common/runtime-benchmarks", + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-message-queue/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-message-queue/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a3432163622d4809838a6a1e678201ce8f5e8747 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "snowbridge-outbound-queue-merkle-tree" +description = "Snowbridge Outbound Queue Merkle Tree" +version = "0.1.1" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +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 } + +[dev-dependencies] +hex-literal = { version = "0.4.1" } +env_logger = "0.9" +hex = "0.4" +array-bytes = "4.1" + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d03eb578ef4d51f9505e63aa98e0a42b107a9958 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +//! This crate implements a simple binary Merkle Tree utilities required for inter-op with Ethereum +//! bridge & Solidity contract. +//! +//! The implementation is optimised for usage within Substrate Runtime and supports no-std +//! compilation targets. +//! +//! Merkle Tree is constructed from arbitrary-length leaves, that are initially hashed using the +//! same `\[`Hasher`\]` as the inner nodes. +//! Inner nodes are created by concatenating child hashes and hashing again. The implementation +//! does not perform any sorting of the input data (leaves) nor when inner nodes are created. +//! +//! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer. + +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{RuntimeDebug, H256}; +use sp_runtime::traits::Hash; + +/// Construct a root hash of a Binary Merkle Tree created from given leaves. +/// +/// See crate-level docs for details about Merkle Tree construction. +/// +/// In case an empty list of leaves is passed the function returns a 0-filled hash. +pub fn merkle_root(leaves: I) -> H256 +where + H: Hash, + I: Iterator, +{ + merkelize::(leaves, &mut ()) +} + +fn merkelize(leaves: I, visitor: &mut V) -> H256 +where + H: Hash, + V: Visitor, + I: Iterator, +{ + let upper = Vec::with_capacity(leaves.size_hint().0); + let mut next = match merkelize_row::(leaves, upper, visitor) { + Ok(root) => return root, + Err(next) if next.is_empty() => return H256::default(), + Err(next) => next, + }; + + let mut upper = Vec::with_capacity((next.len() + 1) / 2); + loop { + visitor.move_up(); + + match merkelize_row::(next.drain(..), upper, visitor) { + Ok(root) => return root, + Err(t) => { + // swap collections to avoid allocations + upper = next; + next = t; + }, + }; + } +} + +/// A generated merkle proof. +/// +/// The structure contains all necessary data to later on verify the proof and the leaf itself. +#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] +pub struct MerkleProof { + /// Root hash of generated merkle tree. + pub root: H256, + /// Proof items (does not contain the leaf hash, nor the root obviously). + /// + /// This vec contains all inner node hashes necessary to reconstruct the root hash given the + /// leaf hash. + pub proof: Vec, + /// Number of leaves in the original tree. + /// + /// This is needed to detect a case where we have an odd number of leaves that "get promoted" + /// to upper layers. + pub number_of_leaves: u64, + /// Index of the leaf the proof is for (0-based). + pub leaf_index: u64, + /// Leaf content (hashed). + pub leaf: H256, +} + +/// A trait of object inspecting merkle root creation. +/// +/// It can be passed to [`merkelize_row`] or [`merkelize`] functions and will be notified +/// about tree traversal. +trait Visitor { + /// We are moving one level up in the tree. + fn move_up(&mut self); + + /// We are creating an inner node from given `left` and `right` nodes. + /// + /// Note that in case of last odd node in the row `right` might be empty. + /// The method will also visit the `root` hash (level 0). + /// + /// The `index` is an index of `left` item. + fn visit(&mut self, index: u64, left: &Option, right: &Option); +} + +/// No-op implementation of the visitor. +impl Visitor for () { + fn move_up(&mut self) {} + fn visit(&mut self, _index: u64, _left: &Option, _right: &Option) {} +} + +/// Construct a Merkle Proof for leaves given by indices. +/// +/// The function constructs a (partial) Merkle Tree first and stores all elements required +/// to prove the requested item (leaf) given the root hash. +/// +/// Both the Proof and the Root Hash are returned. +/// +/// # Panic +/// +/// The function will panic if given `leaf_index` is greater than the number of leaves. +pub fn merkle_proof(leaves: I, leaf_index: u64) -> MerkleProof +where + H: Hash, + I: Iterator, +{ + let mut leaf = None; + let mut hashes = vec![]; + let mut number_of_leaves = 0; + for (idx, l) in (0u64..).zip(leaves) { + // count the leaves + number_of_leaves = idx + 1; + hashes.push(l); + // find the leaf for the proof + if idx == leaf_index { + leaf = Some(l); + } + } + + /// The struct collects a proof for single leaf. + struct ProofCollection { + proof: Vec, + position: u64, + } + + impl ProofCollection { + fn new(position: u64) -> Self { + ProofCollection { proof: Default::default(), position } + } + } + + impl Visitor for ProofCollection { + fn move_up(&mut self) { + self.position /= 2; + } + + fn visit(&mut self, index: u64, left: &Option, right: &Option) { + // we are at left branch - right goes to the proof. + if self.position == index { + if let Some(right) = right { + self.proof.push(*right); + } + } + // we are at right branch - left goes to the proof. + if self.position == index + 1 { + if let Some(left) = left { + self.proof.push(*left); + } + } + } + } + + let mut collect_proof = ProofCollection::new(leaf_index); + + let root = merkelize::(hashes.into_iter(), &mut collect_proof); + let leaf = leaf.expect("Requested `leaf_index` is greater than number of leaves."); + + #[cfg(feature = "debug")] + log::debug!( + "[merkle_proof] Proof: {:?}", + collect_proof.proof.iter().map(hex::encode).collect::>() + ); + + MerkleProof { root, proof: collect_proof.proof, number_of_leaves, leaf_index, leaf } +} + +/// Leaf node for proof verification. +/// +/// Can be either a value that needs to be hashed first, +/// or the hash itself. +#[derive(Debug, PartialEq, Eq)] +pub enum Leaf<'a> { + /// Leaf content. + Value(&'a [u8]), + /// Hash of the leaf content. + Hash(H256), +} + +impl<'a, T: AsRef<[u8]>> From<&'a T> for Leaf<'a> { + fn from(v: &'a T) -> Self { + Leaf::Value(v.as_ref()) + } +} + +impl<'a> From for Leaf<'a> { + fn from(v: H256) -> Self { + Leaf::Hash(v) + } +} + +/// Verify Merkle Proof correctness versus given root hash. +/// +/// The proof is NOT expected to contain leaf hash as the first +/// element, but only all adjacent nodes required to eventually by process of +/// concatenating and hashing end up with given root hash. +/// +/// The proof must not contain the root hash. +pub fn verify_proof<'a, H, P, L>( + root: &'a H256, + proof: P, + number_of_leaves: u64, + leaf_index: u64, + leaf: L, +) -> bool +where + H: Hash, + P: IntoIterator, + L: Into>, +{ + if leaf_index >= number_of_leaves { + return false + } + + let leaf_hash = match leaf.into() { + Leaf::Value(content) => ::hash(content), + Leaf::Hash(hash) => hash, + }; + + let hash_len = ::LENGTH; + let mut combined = [0_u8; 64]; + let computed = proof.into_iter().fold(leaf_hash, |a, b| { + if a < b { + combined[..hash_len].copy_from_slice(a.as_ref()); + combined[hash_len..].copy_from_slice(b.as_ref()); + } else { + combined[..hash_len].copy_from_slice(b.as_ref()); + combined[hash_len..].copy_from_slice(a.as_ref()); + } + ::hash(&combined) + }); + + root == &computed +} + +/// Processes a single row (layer) of a tree by taking pairs of elements, +/// concatenating them, hashing and placing into resulting vector. +/// +/// In case only one element is provided it is returned via `Ok` result, in any other case (also an +/// empty iterator) an `Err` with the inner nodes of upper layer is returned. +fn merkelize_row( + mut iter: I, + mut next: Vec, + visitor: &mut V, +) -> Result> +where + H: Hash, + V: Visitor, + I: Iterator, +{ + #[cfg(feature = "debug")] + log::debug!("[merkelize_row]"); + next.clear(); + + let hash_len = ::LENGTH; + let mut index = 0; + let mut combined = vec![0_u8; hash_len * 2]; + loop { + let a = iter.next(); + let b = iter.next(); + visitor.visit(index, &a, &b); + + #[cfg(feature = "debug")] + log::debug!(" {:?}\n {:?}", a.as_ref().map(hex::encode), b.as_ref().map(hex::encode)); + + index += 2; + match (a, b) { + (Some(a), Some(b)) => { + if a < b { + combined[..hash_len].copy_from_slice(a.as_ref()); + combined[hash_len..].copy_from_slice(b.as_ref()); + } else { + combined[..hash_len].copy_from_slice(b.as_ref()); + combined[hash_len..].copy_from_slice(a.as_ref()); + } + + next.push(::hash(&combined)); + }, + // Odd number of items. Promote the item to the upper layer. + (Some(a), None) if !next.is_empty() => { + next.push(a); + }, + // Last item = root. + (Some(a), None) => return Ok(a), + // Finish up, no more items. + _ => { + #[cfg(feature = "debug")] + log::debug!( + "[merkelize_row] Next: {:?}", + next.iter().map(hex::encode).collect::>() + ); + return Err(next) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + use sp_core::keccak_256; + use sp_runtime::traits::Keccak256; + + fn make_leaves(count: u64) -> Vec { + (0..count).map(|i| keccak_256(&i.to_le_bytes()).into()).collect() + } + + #[test] + fn should_generate_empty_root() { + // given + let _ = env_logger::try_init(); + let data = vec![]; + + // when + let out = merkle_root::(data.into_iter()); + + // then + assert_eq!( + hex::encode(out), + "0000000000000000000000000000000000000000000000000000000000000000" + ); + } + + #[test] + fn should_generate_single_root() { + // given + let _ = env_logger::try_init(); + let data = make_leaves(1); + + // when + let out = merkle_root::(data.into_iter()); + + // then + assert_eq!( + hex::encode(out), + "011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce" + ); + } + + #[test] + fn should_generate_root_pow_2() { + // given + let _ = env_logger::try_init(); + let data = make_leaves(2); + + // when + let out = merkle_root::(data.into_iter()); + + // then + assert_eq!( + hex::encode(out), + "e497bd1c13b13a60af56fa0d2703517c232fde213ad20d2c3dd60735c6604512" + ); + } + + #[test] + fn should_generate_root_complex() { + let _ = env_logger::try_init(); + let test = |root, data: Vec| { + assert_eq!( + array_bytes::bytes2hex("", merkle_root::(data.into_iter()).as_ref()), + root + ); + }; + + test("816cc37bd8d39f7b0851838ebc875faf2afe58a03e95aca3b1333b3693f39dd3", make_leaves(3)); + + test("7501ea976cb92f305cca65ab11254589ea28bb8b59d3161506350adaa237d22f", make_leaves(4)); + + test("d26ba4eb398747bdd39255b1fadb99b803ce39696021b3b0bff7301ac146ee4e", make_leaves(10)); + } + + #[test] + #[ignore] + fn should_generate_and_verify_proof() { + // given + let _ = env_logger::try_init(); + let data: Vec = make_leaves(3); + + // when + let proof0 = merkle_proof::(data.clone().into_iter(), 0); + assert!(verify_proof::( + &proof0.root, + proof0.proof.clone(), + data.len() as u64, + proof0.leaf_index, + &data[0], + )); + + let proof1 = merkle_proof::(data.clone().into_iter(), 1); + assert!(verify_proof::( + &proof1.root, + proof1.proof, + data.len() as u64, + proof1.leaf_index, + &proof1.leaf, + )); + + let proof2 = merkle_proof::(data.clone().into_iter(), 2); + assert!(verify_proof::( + &proof2.root, + proof2.proof, + data.len() as u64, + proof2.leaf_index, + &proof2.leaf + )); + + // then + assert_eq!(hex::encode(proof0.root), hex::encode(proof1.root)); + assert_eq!(hex::encode(proof2.root), hex::encode(proof1.root)); + + assert!(!verify_proof::( + &H256::from_slice(&hex!( + "fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239" + )), + proof0.proof, + data.len() as u64, + proof0.leaf_index, + &proof0.leaf + )); + + assert!(!verify_proof::( + &proof0.root, + vec![], + data.len() as u64, + proof0.leaf_index, + &proof0.leaf + )); + } + + #[test] + #[should_panic] + fn should_panic_on_invalid_leaf_index() { + let _ = env_logger::try_init(); + merkle_proof::(make_leaves(1).into_iter(), 5); + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c92e725c60d5acd38dea2a88326862fed5863c13 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snowbridge-outbound-queue-runtime-api" +description = "Snowbridge Outbound Queue Runtime API" +version = "0.1.0" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +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 } +snowbridge-outbound-queue-merkle-tree = { path = "../merkle-tree", default-features = false } +snowbridge-core = { path = "../../../primitives/core", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "snowbridge-core/std", + "snowbridge-outbound-queue-merkle-tree/std", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "xcm/std", +] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..51f46a7b49c8838eddf44d9d3ba18f07b57c5dcd --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::tokens::Balance as BalanceT; +use snowbridge_core::outbound::Message; +use snowbridge_outbound_queue_merkle_tree::MerkleProof; + +sp_api::decl_runtime_apis! { + pub trait OutboundQueueApi where Balance: BalanceT + { + /// Generate a merkle proof for a committed message identified by `leaf_index`. + /// The merkle root is stored in the block header as a + /// `\[`sp_runtime::generic::DigestItem::Other`\]` + fn prove_message(leaf_index: u64) -> Option; + + /// Calculate the delivery fee for `message` + fn calculate_fee(message: Message) -> Option; + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs new file mode 100644 index 0000000000000000000000000000000000000000..44d63f1e2d23f48f3d13d7834de5cde8d2c78dfc --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helpers for implementing runtime api + +use crate::{Config, MessageLeaves}; +use frame_support::storage::StorageStreamIter; +use snowbridge_core::outbound::{Message, SendMessage}; +use snowbridge_outbound_queue_merkle_tree::{merkle_proof, MerkleProof}; + +pub fn prove_message(leaf_index: u64) -> Option +where + T: Config, +{ + if !MessageLeaves::::exists() { + return None + } + let proof = + merkle_proof::<::Hashing, _>(MessageLeaves::::stream_iter(), leaf_index); + Some(proof) +} + +pub fn calculate_fee(message: Message) -> Option +where + T: Config, +{ + match crate::Pallet::::validate(&message) { + Ok((_, fees)) => Some(fees.total()), + _ => None, + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee5754e86962f807b9bd68d0a18560ad34c08cb2 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use bridge_hub_common::AggregateMessageOrigin; +use codec::Encode; +use frame_benchmarking::v2::*; +use snowbridge_core::{ + outbound::{Command, Initializer}, + ChannelId, +}; +use sp_core::{H160, H256}; + +#[allow(unused_imports)] +use crate::Pallet as OutboundQueue; + +#[benchmarks( + where + ::MaxMessagePayloadSize: Get, +)] +mod benchmarks { + use super::*; + + /// Benchmark for processing a message. + #[benchmark] + fn do_process_message() -> Result<(), BenchmarkError> { + let enqueued_message = QueuedMessage { + id: H256::zero(), + channel_id: ChannelId::from([1; 32]), + command: Command::Upgrade { + impl_address: H160::zero(), + impl_code_hash: H256::zero(), + initializer: Some(Initializer { + params: [7u8; 256].into_iter().collect(), + maximum_required_gas: 200_000, + }), + }, + }; + let origin = AggregateMessageOrigin::Snowbridge([1; 32].into()); + let encoded_enqueued_message = enqueued_message.encode(); + + #[block] + { + let _ = OutboundQueue::::do_process_message(origin, &encoded_enqueued_message); + } + + assert_eq!(MessageLeaves::::decode_len().unwrap(), 1); + + Ok(()) + } + + /// Benchmark for producing final messages commitment + #[benchmark] + fn commit() -> Result<(), BenchmarkError> { + // Assume worst case, where `MaxMessagesPerBlock` messages need to be committed. + for i in 0..T::MaxMessagesPerBlock::get() { + let leaf_data: [u8; 1] = [i as u8]; + let leaf = ::Hashing::hash(&leaf_data); + MessageLeaves::::append(leaf); + } + + #[block] + { + OutboundQueue::::commit(); + } + + Ok(()) + } + + /// Benchmark for producing commitment for a single message + #[benchmark] + fn commit_single() -> Result<(), BenchmarkError> { + let leaf = ::Hashing::hash(&[100; 1]); + MessageLeaves::::append(leaf); + + #[block] + { + OutboundQueue::::commit(); + } + + Ok(()) + } + + impl_benchmark_test_suite!(OutboundQueue, crate::mock::new_tester(), crate::mock::Test,); +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..201e524fb9120849ee3bce514deeabf4ae304a03 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Pallet for committing outbound messages for delivery to Ethereum +//! +//! # Overview +//! +//! Messages come either from sibling parachains via XCM, or BridgeHub itself +//! via the `snowbridge-system` pallet: +//! +//! 1. `snowbridge_router_primitives::outbound::EthereumBlobExporter::deliver` +//! 2. `snowbridge_system::Pallet::send` +//! +//! The message submission pipeline works like this: +//! 1. The message is first validated via the implementation for +//! [`snowbridge_core::outbound::SendMessage::validate`] +//! 2. The message is then enqueued for later processing via the implementation for +//! [`snowbridge_core::outbound::SendMessage::deliver`] +//! 3. The underlying message queue is implemented by [`Config::MessageQueue`] +//! 4. The message queue delivers messages back to this pallet via the implementation for +//! [`frame_support::traits::ProcessMessage::process_message`] +//! 5. The message is processed in `Pallet::do_process_message`: a. Assigned a nonce b. ABI-encoded, +//! hashed, and stored in the `MessageLeaves` vector +//! 6. At the end of the block, a merkle root is constructed from all the leaves in `MessageLeaves`. +//! 7. This merkle root is inserted into the parachain header as a digest item +//! 8. Offchain relayers are able to relay the message to Ethereum after: a. Generating a merkle +//! proof for the committed message using the `prove_message` runtime API b. Reading the actual +//! message content from the `Messages` vector in storage +//! +//! On the Ethereum side, the message root is ultimately the thing being +//! verified by the Polkadot light client. +//! +//! # Message Priorities +//! +//! The processing of governance commands can never be halted. This effectively +//! allows us to pause processing of normal user messages while still allowing +//! governance commands to be sent to Ethereum. +//! +//! # Fees +//! +//! An upfront fee must be paid for delivering a message. This fee covers several +//! components: +//! 1. The weight of processing the message locally +//! 2. The gas refund paid out to relayers for message submission +//! 3. An additional reward paid out to relayers for message submission +//! +//! Messages are weighed to determine the maximum amount of gas they could +//! consume on Ethereum. Using this upper bound, a final fee can be calculated. +//! +//! The fee calculation also requires the following parameters: +//! * ETH/DOT exchange rate +//! * Ether fee per unit of gas +//! +//! By design, it is expected that governance should manually update these +//! parameters every few weeks using the `set_pricing_parameters` extrinsic in the +//! system pallet. +//! +//! ## Fee Computation Function +//! +//! ```text +//! LocalFee(Message) = WeightToFee(ProcessMessageWeight(Message)) +//! RemoteFee(Message) = MaxGasRequired(Message) * FeePerGas + Reward +//! Fee(Message) = LocalFee(Message) + (RemoteFee(Message) / Ratio("ETH/DOT")) +//! ``` +//! +//! By design, the computed fee is always going to conservative, to cover worst-case +//! costs of dispatch on Ethereum. In future iterations of the design, we will optimize +//! this, or provide a mechanism to asynchronously refund a portion of collected fees. +//! +//! # Extrinsics +//! +//! * [`Call::set_operating_mode`]: Set the operating mode +//! +//! # Runtime API +//! +//! * `prove_message`: Generate a merkle proof for a committed message +//! * `calculate_fee`: Calculate the delivery fee for a message +#![cfg_attr(not(feature = "std"), no_std)] +pub mod api; +pub mod process_message_impl; +pub mod send_message_impl; +pub mod types; +pub mod weights; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +use bridge_hub_common::{AggregateMessageOrigin, CustomDigestItem}; +use codec::Decode; +use frame_support::{ + storage::StorageStreamIter, + traits::{tokens::Balance, Contains, Defensive, EnqueueMessage, Get, ProcessMessageError}, + weights::{Weight, WeightToFee}, +}; +use snowbridge_core::{ + outbound::{Fee, GasMeter, QueuedMessage, VersionedQueuedMessage, ETHER_DECIMALS}, + BasicOperatingMode, ChannelId, +}; +use snowbridge_outbound_queue_merkle_tree::merkle_root; +pub use snowbridge_outbound_queue_merkle_tree::MerkleProof; +use sp_core::{H256, U256}; +use sp_runtime::{ + traits::{CheckedDiv, Hash}, + DigestItem, +}; +use sp_std::prelude::*; +pub use types::{CommittedMessage, FeeConfigRecord, ProcessMessageOriginOf}; +pub use weights::WeightInfo; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use snowbridge_core::PricingParameters; + use sp_arithmetic::FixedU128; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type Hashing: Hash; + + type MessageQueue: EnqueueMessage; + + /// Measures the maximum gas used to execute a command on Ethereum + type GasMeter: GasMeter; + + type Balance: Balance + From; + + /// Number of decimal places in native currency + #[pallet::constant] + type Decimals: Get; + + /// Max bytes in a message payload + #[pallet::constant] + type MaxMessagePayloadSize: Get; + + /// Max number of messages processed per block + #[pallet::constant] + type MaxMessagesPerBlock: Get; + + /// Check whether a channel exists + type Channels: Contains; + + type PricingParameters: Get>; + + /// Convert a weight value into a deductible fee based. + type WeightToFee: WeightToFee; + + /// Weight information for extrinsics in this pallet + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Message has been queued and will be processed in the future + MessageQueued { + /// ID of the message. Usually the XCM message hash or a SetTopic. + id: H256, + }, + /// Message will be committed at the end of current block. From now on, to track the + /// progress the message, use the `nonce` of `id`. + MessageAccepted { + /// ID of the message + id: H256, + /// The nonce assigned to this message + nonce: u64, + }, + /// Some messages have been committed + MessagesCommitted { + /// Merkle root of the committed messages + root: H256, + /// number of committed messages + count: u64, + }, + /// Set OperatingMode + OperatingModeChanged { + mode: BasicOperatingMode, + }, + FeeConfigChanged { + fee_config: FeeConfigRecord, + }, + } + + #[pallet::error] + pub enum Error { + /// The message is too large + MessageTooLarge, + /// The pallet is halted + Halted, + // Invalid fee config + InvalidFeeConfig, + /// Invalid Channel + InvalidChannel, + } + + /// Messages to be committed in the current block. This storage value is killed in + /// `on_initialize`, so should never go into block PoV. + /// + /// Is never read in the runtime, only by offchain message relayers. + /// + /// Inspired by the `frame_system::Pallet::Events` storage value + #[pallet::storage] + #[pallet::unbounded] + pub(super) type Messages = StorageValue<_, Vec, ValueQuery>; + + /// Hashes of the ABI-encoded messages in the [`Messages`] storage value. Used to generate a + /// merkle root during `on_finalize`. This storage value is killed in + /// `on_initialize`, so should never go into block PoV. + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn message_leaves)] + pub(super) type MessageLeaves = StorageValue<_, Vec, ValueQuery>; + + /// The current nonce for each message origin + #[pallet::storage] + pub type Nonce = StorageMap<_, Twox64Concat, ChannelId, u64, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet + where + T::AccountId: AsRef<[u8]>, + { + fn on_initialize(_: BlockNumberFor) -> Weight { + // Remove storage from previous block + Messages::::kill(); + MessageLeaves::::kill(); + // Reserve some weight for the `on_finalize` handler + T::WeightInfo::commit() + } + + fn on_finalize(_: BlockNumberFor) { + Self::commit(); + } + + fn integrity_test() { + let decimals = T::Decimals::get(); + assert!(decimals == 10 || decimals == 12, "Decimals should be 10 or 12"); + } + } + + #[pallet::call] + impl Pallet { + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(0)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::put(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } + + impl Pallet { + /// Generate a messages commitment and insert it into the header digest + pub(crate) fn commit() { + let count = MessageLeaves::::decode_len().unwrap_or_default() as u64; + if count == 0 { + return + } + + // Create merkle root of messages + let root = merkle_root::<::Hashing, _>(MessageLeaves::::stream_iter()); + + let digest_item: DigestItem = CustomDigestItem::Snowbridge(root).into(); + + // Insert merkle root into the header digest + >::deposit_log(digest_item); + + Self::deposit_event(Event::MessagesCommitted { root, count }); + } + + /// Process a message delivered by the MessageQueue pallet + pub(crate) fn do_process_message( + _: ProcessMessageOriginOf, + mut message: &[u8], + ) -> Result { + use ProcessMessageError::*; + + // Yield if the maximum number of messages has been processed this block. + // This ensures that the weight of `on_finalize` has a known maximum bound. + ensure!( + MessageLeaves::::decode_len().unwrap_or(0) < + T::MaxMessagesPerBlock::get() as usize, + Yield + ); + + // Decode bytes into versioned message + let versioned_queued_message: VersionedQueuedMessage = + VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?; + + // Convert versioned message into latest supported message version + let queued_message: QueuedMessage = + versioned_queued_message.try_into().map_err(|_| Unsupported)?; + + // Obtain next nonce + let nonce = >::try_mutate( + queued_message.channel_id, + |nonce| -> Result { + *nonce = nonce.checked_add(1).ok_or(Unsupported)?; + Ok(*nonce) + }, + )?; + + let pricing_params = T::PricingParameters::get(); + let command = queued_message.command.index(); + let params = queued_message.command.abi_encode(); + let max_dispatch_gas = + T::GasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command); + let reward = pricing_params.rewards.remote; + + // Construct the final committed message + let message = CommittedMessage { + channel_id: queued_message.channel_id, + nonce, + command, + params, + max_dispatch_gas, + max_fee_per_gas: pricing_params + .fee_per_gas + .try_into() + .defensive_unwrap_or(u128::MAX), + reward: reward.try_into().defensive_unwrap_or(u128::MAX), + id: queued_message.id, + }; + + // ABI-encode and hash the prepared message + let message_abi_encoded = ethabi::encode(&[message.clone().into()]); + let message_abi_encoded_hash = ::Hashing::hash(&message_abi_encoded); + + Messages::::append(Box::new(message)); + MessageLeaves::::append(message_abi_encoded_hash); + + Self::deposit_event(Event::MessageAccepted { id: queued_message.id, nonce }); + + Ok(true) + } + + /// Calculate total fee in native currency to cover all costs of delivering a message to the + /// remote destination. See module-level documentation for more details. + pub(crate) fn calculate_fee( + gas_used_at_most: u64, + params: PricingParameters, + ) -> Fee { + // Remote fee in ether + let fee = Self::calculate_remote_fee( + gas_used_at_most, + params.fee_per_gas, + params.rewards.remote, + ); + + // downcast to u128 + let fee: u128 = fee.try_into().defensive_unwrap_or(u128::MAX); + + // convert to local currency + let fee = FixedU128::from_inner(fee) + .checked_div(¶ms.exchange_rate) + .expect("exchange rate is not zero; qed") + .into_inner(); + + // adjust fixed point to match local currency + let fee = Self::convert_from_ether_decimals(fee); + + Fee::from((Self::calculate_local_fee(), fee)) + } + + /// Calculate fee in remote currency for dispatching a message on Ethereum + pub(crate) fn calculate_remote_fee( + gas_used_at_most: u64, + fee_per_gas: U256, + reward: U256, + ) -> U256 { + fee_per_gas.saturating_mul(gas_used_at_most.into()).saturating_add(reward) + } + + /// The local component of the message processing fees in native currency + pub(crate) fn calculate_local_fee() -> T::Balance { + T::WeightToFee::weight_to_fee( + &T::WeightInfo::do_process_message().saturating_add(T::WeightInfo::commit_single()), + ) + } + + // 1 DOT has 10 digits of precision + // 1 KSM has 12 digits of precision + // 1 ETH has 18 digits of precision + pub(crate) fn convert_from_ether_decimals(value: u128) -> T::Balance { + let decimals = ETHER_DECIMALS.saturating_sub(T::Decimals::get()) as u32; + let denom = 10u128.saturating_pow(decimals); + value.checked_div(denom).expect("divisor is non-zero; qed").into() + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd8fee4e2ed08ec0f3090b765fa882b063a98300 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{ + parameter_types, + traits::{Everything, Hooks}, + weights::IdentityFee, +}; + +use snowbridge_core::{ + gwei, meth, + outbound::*, + pricing::{PricingParameters, Rewards}, + ParaId, PRIMARY_GOVERNANCE_CHANNEL, +}; +use sp_core::{ConstU32, ConstU8, H160, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, Keccak256}, + AccountId32, BuildStorage, FixedU128, +}; +use sp_std::marker::PhantomData; + +type Block = frame_system::mocking::MockBlock; +type AccountId = AccountId32; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + OutboundQueue: crate::{Pallet, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type Nonce = u64; + type Block = Block; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl pallet_message_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = OutboundQueue; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; + type QueuePausedQuery = (); +} + +parameter_types! { + pub const OwnParaId: ParaId = ParaId::new(1013); + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: DOT, remote: meth(1) } + }; +} + +pub const DOT: u128 = 10_000_000_000; + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type Decimals = ConstU8<12>; + type MaxMessagePayloadSize = ConstU32<1024>; + type MaxMessagesPerBlock = ConstU32<20>; + type GasMeter = ConstantGasMeter; + type Balance = u128; + type PricingParameters = Parameters; + type Channels = Everything; + type WeightToFee = IdentityFee; + type WeightInfo = (); +} + +fn setup() { + System::set_block_number(1); +} + +pub fn new_tester() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext +} + +pub fn run_to_end_of_next_block() { + // finish current block + MessageQueue::on_finalize(System::block_number()); + OutboundQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + // start next block + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + OutboundQueue::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()); + // finish next block + MessageQueue::on_finalize(System::block_number()); + OutboundQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); +} + +pub fn mock_governance_message() -> Message +where + T: Config, +{ + let _marker = PhantomData::; // for clippy + + Message { + id: None, + channel_id: PRIMARY_GOVERNANCE_CHANNEL, + command: Command::Upgrade { + impl_address: H160::zero(), + impl_code_hash: H256::zero(), + initializer: None, + }, + } +} + +// Message should fail validation as it is too large +pub fn mock_invalid_governance_message() -> Message +where + T: Config, +{ + let _marker = PhantomData::; // for clippy + + Message { + id: None, + channel_id: PRIMARY_GOVERNANCE_CHANNEL, + command: Command::Upgrade { + impl_address: H160::zero(), + impl_code_hash: H256::zero(), + initializer: Some(Initializer { + params: (0..1000).map(|_| 1u8).collect::>(), + maximum_required_gas: 0, + }), + }, + } +} + +pub fn mock_message(sibling_para_id: u32) -> Message { + Message { + id: None, + channel_id: ParaId::from(sibling_para_id).into(), + command: Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: Default::default(), + recipient: Default::default(), + amount: 0, + }, + }, + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..575ed9e0e7c225a8be4b3ad09f67a26975f5a94a --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs @@ -0,0 +1,23 @@ +//! Implementation for [`frame_support::traits::ProcessMessage`] +use super::*; +use crate::weights::WeightInfo; +use frame_support::{ + traits::{ProcessMessage, ProcessMessageError}, + weights::WeightMeter, +}; + +impl ProcessMessage for Pallet { + type Origin = AggregateMessageOrigin; + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + _: &mut [u8; 32], + ) -> Result { + let weight = T::WeightInfo::do_process_message(); + if meter.try_consume(weight).is_err() { + return Err(ProcessMessageError::Overweight(weight)) + } + Self::do_process_message(origin, message) + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..a84e2c520e59000ab44ae6e160ba6071a263bf99 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs @@ -0,0 +1,98 @@ +//! Implementation for [`snowbridge_core::outbound::SendMessage`] +use super::*; +use bridge_hub_common::AggregateMessageOrigin; +use codec::Encode; +use frame_support::{ + ensure, + traits::{EnqueueMessage, Get}, + CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use frame_system::unique; +use snowbridge_core::{ + outbound::{ + Fee, Message, QueuedMessage, SendError, SendMessage, SendMessageFeeProvider, + VersionedQueuedMessage, + }, + ChannelId, PRIMARY_GOVERNANCE_CHANNEL, +}; +use sp_core::H256; +use sp_runtime::BoundedVec; + +/// The maximal length of an enqueued message, as determined by the MessageQueue pallet +pub type MaxEnqueuedMessageSizeOf = + <::MessageQueue as EnqueueMessage>::MaxMessageLen; + +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound)] +pub struct Ticket +where + T: Config, +{ + pub message_id: H256, + pub channel_id: ChannelId, + pub message: BoundedVec>, +} + +impl SendMessage for Pallet +where + T: Config, +{ + type Ticket = Ticket; + + fn validate( + message: &Message, + ) -> Result<(Self::Ticket, Fee<::Balance>), SendError> { + // The inner payload should not be too large + let payload = message.command.abi_encode(); + ensure!( + payload.len() < T::MaxMessagePayloadSize::get() as usize, + SendError::MessageTooLarge + ); + + // Ensure there is a registered channel we can transmit this message on + ensure!(T::Channels::contains(&message.channel_id), SendError::InvalidChannel); + + // Generate a unique message id unless one is provided + let message_id: H256 = message + .id + .unwrap_or_else(|| unique((message.channel_id, &message.command)).into()); + + let gas_used_at_most = T::GasMeter::maximum_gas_used_at_most(&message.command); + let fee = Self::calculate_fee(gas_used_at_most, T::PricingParameters::get()); + + let queued_message: VersionedQueuedMessage = QueuedMessage { + id: message_id, + channel_id: message.channel_id, + command: message.command.clone(), + } + .into(); + // The whole message should not be too large + let encoded = queued_message.encode().try_into().map_err(|_| SendError::MessageTooLarge)?; + + let ticket = Ticket { message_id, channel_id: message.channel_id, message: encoded }; + + Ok((ticket, fee)) + } + + fn deliver(ticket: Self::Ticket) -> Result { + let origin = AggregateMessageOrigin::Snowbridge(ticket.channel_id); + + if ticket.channel_id != PRIMARY_GOVERNANCE_CHANNEL { + ensure!(!Self::operating_mode().is_halted(), SendError::Halted); + } + + let message = ticket.message.as_bounded_slice(); + + T::MessageQueue::enqueue_message(message, origin); + Self::deposit_event(Event::MessageQueued { id: ticket.message_id }); + Ok(ticket.message_id) + } +} + +impl SendMessageFeeProvider for Pallet { + type Balance = T::Balance; + + /// The local component of the message processing fees in native currency + fn local_fee() -> Self::Balance { + Self::calculate_local_fee() + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs new file mode 100644 index 0000000000000000000000000000000000000000..454a91d5df5c130b7327ae1e15e2eb8c959fc06b --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{mock::*, *}; + +use frame_support::{ + assert_err, assert_noop, assert_ok, + traits::{Hooks, ProcessMessage, ProcessMessageError}, + weights::WeightMeter, +}; + +use codec::Encode; +use snowbridge_core::{ + outbound::{Command, SendError, SendMessage}, + ParaId, +}; +use sp_arithmetic::FixedU128; +use sp_core::H256; +use sp_runtime::FixedPointNumber; + +#[test] +fn submit_messages_and_commit() { + new_tester().execute_with(|| { + for para_id in 1000..1004 { + let message = mock_message(para_id); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_ok!(OutboundQueue::deliver(ticket)); + } + + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + for para_id in 1000..1004 { + let origin: ParaId = (para_id as u32).into(); + let channel_id: ChannelId = origin.into(); + assert_eq!(Nonce::::get(channel_id), 1); + } + + let digest = System::digest(); + let digest_items = digest.logs(); + assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some()); + assert_eq!(Messages::::decode_len(), Some(4)); + }); +} + +#[test] +fn submit_message_fail_too_large() { + new_tester().execute_with(|| { + let message = mock_invalid_governance_message::(); + assert_err!(OutboundQueue::validate(&message), SendError::MessageTooLarge); + }); +} + +#[test] +fn convert_from_ether_decimals() { + assert_eq!( + OutboundQueue::convert_from_ether_decimals(1_000_000_000_000_000_000), + 1_000_000_000_000 + ); +} + +#[test] +fn commit_exits_early_if_no_processed_messages() { + new_tester().execute_with(|| { + // on_finalize should do nothing, nor should it panic + OutboundQueue::on_finalize(System::block_number()); + + let digest = System::digest(); + let digest_items = digest.logs(); + assert_eq!(digest_items.len(), 0); + }); +} + +#[test] +fn process_message_yields_on_max_messages_per_block() { + new_tester().execute_with(|| { + for _ in 0..::MaxMessagesPerBlock::get() { + MessageLeaves::::append(H256::zero()) + } + + let channel_id: ChannelId = ParaId::from(1000).into(); + let origin = AggregateMessageOrigin::Snowbridge(channel_id); + let message = QueuedMessage { + id: Default::default(), + channel_id, + command: Command::Upgrade { + impl_address: Default::default(), + impl_code_hash: Default::default(), + initializer: None, + }, + } + .encode(); + + let mut meter = WeightMeter::new(); + + assert_noop!( + OutboundQueue::process_message(message.as_slice(), origin, &mut meter, &mut [0u8; 32]), + ProcessMessageError::Yield + ); + }) +} + +#[test] +fn process_message_fails_on_max_nonce_reached() { + new_tester().execute_with(|| { + let sibling_id = 1000; + let channel_id: ChannelId = ParaId::from(sibling_id).into(); + let origin = AggregateMessageOrigin::Snowbridge(channel_id); + let message: QueuedMessage = QueuedMessage { + id: H256::zero(), + channel_id, + command: mock_message(sibling_id).command, + }; + let versioned_queued_message: VersionedQueuedMessage = message.into(); + let encoded = versioned_queued_message.encode(); + let mut meter = WeightMeter::with_limit(Weight::MAX); + + Nonce::::set(channel_id, u64::MAX); + + assert_noop!( + OutboundQueue::process_message(encoded.as_slice(), origin, &mut meter, &mut [0u8; 32]), + ProcessMessageError::Unsupported + ); + }) +} + +#[test] +fn process_message_fails_on_overweight_message() { + new_tester().execute_with(|| { + let sibling_id = 1000; + let channel_id: ChannelId = ParaId::from(sibling_id).into(); + let origin = AggregateMessageOrigin::Snowbridge(channel_id); + let message: QueuedMessage = QueuedMessage { + id: H256::zero(), + channel_id, + command: mock_message(sibling_id).command, + }; + let versioned_queued_message: VersionedQueuedMessage = message.into(); + let encoded = versioned_queued_message.encode(); + let mut meter = WeightMeter::with_limit(Weight::from_parts(1, 1)); + assert_noop!( + OutboundQueue::process_message(encoded.as_slice(), origin, &mut meter, &mut [0u8; 32]), + ProcessMessageError::Overweight(::WeightInfo::do_process_message()) + ); + }) +} + +// Governance messages should be able to bypass a halted operating mode +// Other message sends should fail when halted +#[test] +fn submit_upgrade_message_success_when_queue_halted() { + new_tester().execute_with(|| { + // halt the outbound queue + OutboundQueue::set_operating_mode(RuntimeOrigin::root(), BasicOperatingMode::Halted) + .unwrap(); + + // submit a high priority message from bridge_hub should success + let message = mock_governance_message::(); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_ok!(OutboundQueue::deliver(ticket)); + + // submit a low priority message from asset_hub will fail as pallet is halted + let message = mock_message(1000); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + assert_noop!(OutboundQueue::deliver(ticket), SendError::Halted); + }); +} + +#[test] +fn governance_message_does_not_get_the_chance_to_processed_in_same_block_when_congest_of_low_priority_sibling_messages( +) { + use snowbridge_core::PRIMARY_GOVERNANCE_CHANNEL; + use AggregateMessageOrigin::*; + + let sibling_id: u32 = 1000; + let sibling_channel_id: ChannelId = ParaId::from(sibling_id).into(); + + new_tester().execute_with(|| { + // submit a lot of low priority messages from asset_hub which will need multiple blocks to + // execute(20 messages for each block so 40 required at least 2 blocks) + let max_messages = 40; + for _ in 0..max_messages { + // submit low priority message + let message = mock_message(sibling_id); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + OutboundQueue::deliver(ticket).unwrap(); + } + + let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id)); + assert_eq!(footprint.storage.count, (max_messages) as u64); + + let message = mock_governance_message::(); + let (ticket, _) = OutboundQueue::validate(&message).unwrap(); + OutboundQueue::deliver(ticket).unwrap(); + + // move to next block + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + // first process 20 messages from sibling channel + let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id)); + assert_eq!(footprint.storage.count, 40 - 20); + + // and governance message does not have the chance to execute in same block + let footprint = MessageQueue::footprint(Snowbridge(PRIMARY_GOVERNANCE_CHANNEL)); + assert_eq!(footprint.storage.count, 1); + + // move to next block + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + + // now governance message get executed in this block + let footprint = MessageQueue::footprint(Snowbridge(PRIMARY_GOVERNANCE_CHANNEL)); + assert_eq!(footprint.storage.count, 0); + + // and this time process 19 messages from sibling channel so we have 1 message left + let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id)); + assert_eq!(footprint.storage.count, 1); + + // move to the next block, the last 1 message from sibling channel get executed + ServiceWeight::set(Some(Weight::MAX)); + run_to_end_of_next_block(); + let footprint = MessageQueue::footprint(Snowbridge(sibling_channel_id)); + assert_eq!(footprint.storage.count, 0); + }); +} + +#[test] +fn convert_local_currency() { + new_tester().execute_with(|| { + let fee: u128 = 1_000_000; + let fee1 = FixedU128::from_inner(fee).into_inner(); + let fee2 = FixedU128::from(fee) + .into_inner() + .checked_div(FixedU128::accuracy()) + .expect("accuracy is not zero; qed"); + assert_eq!(fee, fee1); + assert_eq!(fee, fee2); + }); +} + +#[test] +fn encode_digest_item_with_correct_index() { + new_tester().execute_with(|| { + let digest_item: DigestItem = CustomDigestItem::Snowbridge(H256::default()).into(); + let enum_prefix = match digest_item { + DigestItem::Other(data) => data[0], + _ => u8::MAX, + }; + assert_eq!(enum_prefix, 0); + }); +} + +#[test] +fn encode_digest_item() { + new_tester().execute_with(|| { + let digest_item: DigestItem = CustomDigestItem::Snowbridge([5u8; 32].into()).into(); + let digest_item_raw = digest_item.encode(); + assert_eq!(digest_item_raw[0], 0); // DigestItem::Other + assert_eq!(digest_item_raw[2], 0); // CustomDigestItem::Snowbridge + assert_eq!( + digest_item_raw, + [ + 0, 132, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5 + ] + ); + }); +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..07803ed9b738bee40cd1a0981f4f75d1674596ef --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs @@ -0,0 +1,99 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use ethabi::Token; +use frame_support::traits::ProcessMessage; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_arithmetic::FixedU128; +use sp_core::H256; +use sp_runtime::{traits::Zero, RuntimeDebug}; +use sp_std::prelude::*; + +use super::Pallet; + +use snowbridge_core::ChannelId; +pub use snowbridge_outbound_queue_merkle_tree::MerkleProof; + +pub type ProcessMessageOriginOf = as ProcessMessage>::Origin; + +pub const LOG_TARGET: &str = "snowbridge-outbound-queue"; + +/// Message which has been assigned a nonce and will be committed at the end of a block +#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +pub struct CommittedMessage { + /// Message channel + pub channel_id: ChannelId, + /// Unique nonce to prevent replaying messages + #[codec(compact)] + pub nonce: u64, + /// Command to execute in the Gateway contract + pub command: u8, + /// Params for the command + pub params: Vec, + /// Maximum gas allowed for message dispatch + #[codec(compact)] + pub max_dispatch_gas: u64, + /// Maximum fee per gas + #[codec(compact)] + pub max_fee_per_gas: u128, + /// Reward in ether for delivering this message, in addition to the gas refund + #[codec(compact)] + pub reward: u128, + /// Message ID (Used for tracing messages across route, has no role in consensus) + pub id: H256, +} + +/// Convert message into an ABI-encoded form for delivery to the InboundQueue contract on Ethereum +impl From for Token { + fn from(x: CommittedMessage) -> Token { + Token::Tuple(vec![ + Token::FixedBytes(Vec::from(x.channel_id.as_ref())), + Token::Uint(x.nonce.into()), + Token::Uint(x.command.into()), + Token::Bytes(x.params.to_vec()), + Token::Uint(x.max_dispatch_gas.into()), + Token::Uint(x.max_fee_per_gas.into()), + Token::Uint(x.reward.into()), + Token::FixedBytes(Vec::from(x.id.as_ref())), + ]) + } +} + +/// Configuration for fee calculations +#[derive( + Encode, + Decode, + Copy, + Clone, + PartialEq, + RuntimeDebug, + MaxEncodedLen, + TypeInfo, + Serialize, + Deserialize, +)] +pub struct FeeConfigRecord { + /// ETH/DOT exchange rate + pub exchange_rate: FixedU128, + /// Ether fee per unit of gas + pub fee_per_gas: u128, + /// Ether reward for delivering message + pub reward: u128, +} + +#[derive(RuntimeDebug)] +pub struct InvalidFeeConfig; + +impl FeeConfigRecord { + pub fn validate(&self) -> Result<(), InvalidFeeConfig> { + if self.exchange_rate == FixedU128::zero() { + return Err(InvalidFeeConfig) + } + if self.fee_per_gas == 0 { + return Err(InvalidFeeConfig) + } + if self.reward == 0 { + return Err(InvalidFeeConfig) + } + Ok(()) + } +} diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs b/bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..e4b6f8439b0f5b97924cdab3d87c8282f6ec7b9d --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs @@ -0,0 +1,81 @@ + +//! Autogenerated weights for `snowbridge_outbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-19, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `192.168.1.7`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_outbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template +// ../parachain/templates/module-weight-template.hbs +// --output +// ../parachain/pallets/outbound-queue/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `snowbridge_outbound_queue`. +pub trait WeightInfo { + fn do_process_message() -> Weight; + fn commit() -> Weight; + fn commit_single() -> Weight; +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1) + /// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Nonce (r:1 w:1) + /// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Messages (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured) + fn do_process_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3485` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(39_000_000, 3485) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + fn commit() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(28_000_000, 2579) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn commit_single() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 1586) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/bridges/snowbridge/parachain/pallets/system/Cargo.toml b/bridges/snowbridge/parachain/pallets/system/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4356bf5722056fd0fa13fc0f1d24f82bb458e260 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "snowbridge-system" +description = "Snowbridge System" +version = "0.1.1" +authors = ["Snowfork "] +edition = "2021" +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.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 } + +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 } + +ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false } +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" } +snowbridge-outbound-queue = { path = "../outbound-queue" } + +[features] +default = ["std"] +std = [ + "codec/std", + "ethabi/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "snowbridge-core/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-outbound-queue/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "snowbridge-outbound-queue/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/parachain/pallets/system/README.md b/bridges/snowbridge/parachain/pallets/system/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9e4dc55267d69c47fff971cb0427bcb2e0ff871c --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/README.md @@ -0,0 +1 @@ +License: MIT-0 diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..97d0735bf63d6697feb2b74482156b4f6c3db3dd --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "snowbridge-system-runtime-api" +description = "Snowbridge System Runtime API" +version = "0.1.0" +edition = "2021" +authors = ["Snowfork "] +repository = "https://github.com/Snowfork/snowbridge" +license = "Apache-2.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +sp-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] +default = ["std"] +std = [ + "codec/std", + "snowbridge-core/std", + "sp-api/std", + "sp-core/std", + "sp-std/std", + "xcm/std", +] diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs b/bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d99b456c84885ca649c223663aecb4471880cfe5 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_core::AgentId; +use xcm::VersionedMultiLocation; + +sp_api::decl_runtime_apis! { + pub trait ControlApi + { + fn agent_id(location: VersionedMultiLocation) -> Option; + } +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/api.rs b/bridges/snowbridge/parachain/pallets/system/src/api.rs new file mode 100644 index 0000000000000000000000000000000000000000..245e6eea1c1467e75f5e56184808152157bdeefd --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/api.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helpers for implementing runtime api + +use snowbridge_core::AgentId; +use xcm::{prelude::*, VersionedMultiLocation}; + +use crate::{agent_id_of, Config}; + +pub fn agent_id(location: VersionedMultiLocation) -> Option +where + Runtime: Config, +{ + let location: MultiLocation = location.try_into().ok()?; + agent_id_of::(&location).ok() +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs b/bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d26408b38e5ecf9258558534f3da930e38f3fbd --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Benchmarking setup for pallet-template +use super::*; + +#[allow(unused)] +use crate::Pallet as SnowbridgeControl; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; +use snowbridge_core::{eth, outbound::OperatingMode}; +use sp_runtime::SaturatedConversion; +use xcm::prelude::*; + +#[allow(clippy::result_large_err)] +fn fund_sovereign_account(para_id: ParaId) -> Result<(), BenchmarkError> { + let amount: BalanceOf = (10_000_000_000_000_u64).saturated_into::().saturated_into(); + let sovereign_account = sibling_sovereign_account::(para_id); + T::Token::mint_into(&sovereign_account, amount)?; + Ok(()) +} + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn upgrade() -> Result<(), BenchmarkError> { + let impl_address = H160::repeat_byte(1); + let impl_code_hash = H256::repeat_byte(1); + + // Assume 256 bytes passed to initializer + let params: Vec = (0..256).map(|_| 1u8).collect(); + + #[extrinsic_call] + _( + RawOrigin::Root, + impl_address, + impl_code_hash, + Some(Initializer { params, maximum_required_gas: 100000 }), + ); + + Ok(()) + } + + #[benchmark] + fn set_operating_mode() -> Result<(), BenchmarkError> { + #[extrinsic_call] + _(RawOrigin::Root, OperatingMode::RejectingOutboundMessages); + + Ok(()) + } + + #[benchmark] + fn set_pricing_parameters() -> Result<(), BenchmarkError> { + let params = T::DefaultPricingParameters::get(); + + #[extrinsic_call] + _(RawOrigin::Root, params); + + Ok(()) + } + + #[benchmark] + fn create_agent() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin); + + Ok(()) + } + + #[benchmark] + fn create_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + + SnowbridgeControl::::create_agent(origin.clone())?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, OperatingMode::Normal); + + Ok(()) + } + + #[benchmark] + fn update_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, OperatingMode::RejectingOutboundMessages); + + Ok(()) + } + + #[benchmark] + fn force_update_channel() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + let channel_id: ChannelId = ParaId::from(origin_para_id).into(); + + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; + + #[extrinsic_call] + _(RawOrigin::Root, channel_id, OperatingMode::RejectingOutboundMessages); + + Ok(()) + } + + #[benchmark] + fn transfer_native_from_agent() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + SnowbridgeControl::::create_channel(origin.clone(), OperatingMode::Normal)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, H160::default(), 1); + + Ok(()) + } + + #[benchmark] + fn force_transfer_native_from_agent() -> Result<(), BenchmarkError> { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let origin = T::Helper::make_xcm_origin(origin_location); + fund_sovereign_account::(origin_para_id.into())?; + SnowbridgeControl::::create_agent(origin.clone())?; + + let versioned_location: VersionedMultiLocation = origin_location.into(); + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(versioned_location), H160::default(), 1); + + Ok(()) + } + + #[benchmark] + fn set_token_transfer_fees() -> Result<(), BenchmarkError> { + #[extrinsic_call] + _(RawOrigin::Root, 1, 1, eth(1)); + + Ok(()) + } + + impl_benchmark_test_suite!( + SnowbridgeControl, + crate::mock::new_test_ext(true), + crate::mock::Test + ); +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/lib.rs b/bridges/snowbridge/parachain/pallets/system/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e5077abd92135c525cbf02fc1532a4d35e32eb73 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/lib.rs @@ -0,0 +1,679 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Governance API for controlling the Ethereum side of the bridge +//! +//! # Extrinsics +//! +//! ## Agents +//! +//! Agents are smart contracts on Ethereum that act as proxies for consensus systems on Polkadot +//! networks. +//! +//! * [`Call::create_agent`]: Create agent for a sibling parachain +//! * [`Call::transfer_native_from_agent`]: Withdraw ether from an agent +//! +//! The `create_agent` extrinsic should be called via an XCM `Transact` instruction from the sibling +//! parachain. +//! +//! ## Channels +//! +//! Each sibling parachain has its own dedicated messaging channel for sending and receiving +//! messages. As a prerequisite to creating a channel, the sibling should have already created +//! an agent using the `create_agent` extrinsic. +//! +//! * [`Call::create_channel`]: Create channel for a sibling +//! * [`Call::update_channel`]: Update a channel for a sibling +//! +//! ## Governance +//! +//! Only Polkadot governance itself can call these extrinsics. Delivery fees are waived. +//! +//! * [`Call::upgrade`]`: Upgrade the gateway contract +//! * [`Call::set_operating_mode`]: Update the operating mode of the gateway contract +//! * [`Call::force_update_channel`]: Allow root to update a channel for a sibling +//! * [`Call::force_transfer_native_from_agent`]: Allow root to withdraw ether from an agent +//! +//! Typically, Polkadot governance will use the `force_transfer_native_from_agent` and +//! `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; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod migration; + +pub mod api; +pub mod weights; +pub use weights::*; + +use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{Inspect, Mutate}, + tokens::Preservation, + Contains, EnsureOrigin, + }, +}; +use frame_system::pallet_prelude::*; +use snowbridge_core::{ + meth, + outbound::{Command, Initializer, Message, OperatingMode, SendError, SendMessage}, + sibling_sovereign_account, AgentId, Channel, ChannelId, ParaId, + PricingParameters as PricingParametersRecord, PRIMARY_GOVERNANCE_CHANNEL, + SECONDARY_GOVERNANCE_CHANNEL, +}; +use sp_core::{RuntimeDebug, H160, H256}; +use sp_io::hashing::blake2_256; +use sp_runtime::{traits::BadOrigin, DispatchError, SaturatedConversion}; +use sp_std::prelude::*; +use xcm::prelude::*; +use xcm_executor::traits::ConvertLocation; + +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::OriginTrait; + +pub type BalanceOf = + <::Token as Inspect<::AccountId>>::Balance; +pub type AccountIdOf = ::AccountId; +pub type PricingParametersOf = PricingParametersRecord>; + +/// Ensure origin location is a sibling +fn ensure_sibling(location: &MultiLocation) -> Result<(ParaId, H256), DispatchError> +where + T: Config, +{ + match location { + MultiLocation { parents: 1, interior: X1(Parachain(para_id)) } => { + let agent_id = agent_id_of::(location)?; + Ok(((*para_id).into(), agent_id)) + }, + _ => Err(BadOrigin.into()), + } +} + +/// Hash the location to produce an agent id +fn agent_id_of(location: &MultiLocation) -> Result { + T::AgentIdOf::convert_location(location).ok_or(Error::::LocationConversionFailed.into()) +} + +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper +where + O: OriginTrait, +{ + fn make_xcm_origin(location: MultiLocation) -> O; +} + +/// Whether a fee should be withdrawn to an account for sending an outbound message +#[derive(Clone, PartialEq, RuntimeDebug)] +pub enum PaysFee +where + T: Config, +{ + /// Fully charge includes (local + remote fee) + Yes(AccountIdOf), + /// Partially charge includes local fee only + Partial(AccountIdOf), + /// No charge + No, +} + +#[frame_support::pallet] +pub mod pallet { + use snowbridge_core::StaticLookup; + use sp_core::U256; + + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Send messages to Ethereum + type OutboundQueue: SendMessage>; + + /// Origin check for XCM locations that can create agents + type SiblingOrigin: EnsureOrigin; + + /// Converts MultiLocation to AgentId + type AgentIdOf: ConvertLocation; + + /// Token reserved for control operations + type Token: Mutate; + + /// TreasuryAccount to collect fees + #[pallet::constant] + type TreasuryAccount: Get; + + /// Number of decimal places of local currency + type DefaultPricingParameters: Get>; + + /// Cost of delivering a message from Ethereum + type InboundDeliveryCost: Get>; + + type WeightInfo: WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An Upgrade message was sent to the Gateway + Upgrade { + impl_address: H160, + impl_code_hash: H256, + initializer_params_hash: Option, + }, + /// An CreateAgent message was sent to the Gateway + CreateAgent { + location: Box, + agent_id: AgentId, + }, + /// An CreateChannel message was sent to the Gateway + CreateChannel { + channel_id: ChannelId, + agent_id: AgentId, + }, + /// An UpdateChannel message was sent to the Gateway + UpdateChannel { + channel_id: ChannelId, + mode: OperatingMode, + }, + /// An SetOperatingMode message was sent to the Gateway + SetOperatingMode { + mode: OperatingMode, + }, + /// An TransferNativeFromAgent message was sent to the Gateway + TransferNativeFromAgent { + agent_id: AgentId, + recipient: H160, + amount: u128, + }, + /// A SetTokenTransferFees message was sent to the Gateway + SetTokenTransferFees { + create_asset_xcm: u128, + transfer_asset_xcm: u128, + register_token: U256, + }, + PricingParametersChanged { + params: PricingParametersOf, + }, + } + + #[pallet::error] + pub enum Error { + LocationConversionFailed, + AgentAlreadyCreated, + NoAgent, + ChannelAlreadyCreated, + NoChannel, + UnsupportedLocationVersion, + InvalidLocation, + Send(SendError), + InvalidTokenTransferFees, + InvalidPricingParameters, + } + + /// The set of registered agents + #[pallet::storage] + #[pallet::getter(fn agents)] + pub type Agents = StorageMap<_, Twox64Concat, AgentId, (), OptionQuery>; + + /// The set of registered channels + #[pallet::storage] + #[pallet::getter(fn channels)] + pub type Channels = StorageMap<_, Twox64Concat, ChannelId, Channel, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn parameters)] + pub type PricingParameters = + StorageValue<_, PricingParametersOf, ValueQuery, T::DefaultPricingParameters>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + // Own parachain id + pub para_id: ParaId, + // AssetHub's parachain id + pub asset_hub_para_id: ParaId, + #[serde(skip)] + pub _config: PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + Pallet::::initialize(self.para_id, self.asset_hub_para_id).expect("infallible; qed"); + } + } + + #[pallet::call] + impl Pallet { + /// Sends command to the Gateway contract to upgrade itself with a new implementation + /// contract + /// + /// Fee required: No + /// + /// - `origin`: Must be `Root`. + /// - `impl_address`: The address of the implementation contract. + /// - `impl_code_hash`: The codehash of the implementation contract. + /// - `initializer`: Optionally call an initializer on the implementation contract. + #[pallet::call_index(0)] + #[pallet::weight((T::WeightInfo::upgrade(), DispatchClass::Operational))] + pub fn upgrade( + origin: OriginFor, + impl_address: H160, + impl_code_hash: H256, + initializer: Option, + ) -> DispatchResult { + ensure_root(origin)?; + + let initializer_params_hash: Option = + initializer.as_ref().map(|i| H256::from(blake2_256(i.params.as_ref()))); + let command = Command::Upgrade { impl_address, impl_code_hash, initializer }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::::Upgrade { + impl_address, + impl_code_hash, + initializer_params_hash, + }); + Ok(()) + } + + /// Sends a message to the Gateway contract to change its operating mode + /// + /// Fee required: No + /// + /// - `origin`: Must be `MultiLocation` + #[pallet::call_index(1)] + #[pallet::weight((T::WeightInfo::set_operating_mode(), DispatchClass::Operational))] + pub fn set_operating_mode(origin: OriginFor, mode: OperatingMode) -> DispatchResult { + ensure_root(origin)?; + + let command = Command::SetOperatingMode { mode }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::::SetOperatingMode { mode }); + Ok(()) + } + + /// Set pricing parameters on both sides of the bridge + /// + /// Fee required: No + /// + /// - `origin`: Must be root + #[pallet::call_index(2)] + #[pallet::weight((T::WeightInfo::set_pricing_parameters(), DispatchClass::Operational))] + pub fn set_pricing_parameters( + origin: OriginFor, + params: PricingParametersOf, + ) -> DispatchResult { + ensure_root(origin)?; + params.validate().map_err(|_| Error::::InvalidPricingParameters)?; + PricingParameters::::put(params.clone()); + + let command = Command::SetPricingParameters { + exchange_rate: params.exchange_rate.into(), + delivery_cost: T::InboundDeliveryCost::get().saturated_into::(), + }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::PricingParametersChanged { params }); + Ok(()) + } + + /// Sends a command to the Gateway contract to instantiate a new agent contract representing + /// `origin`. + /// + /// Fee required: Yes + /// + /// - `origin`: Must be `MultiLocation` of a sibling parachain + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::create_agent())] + pub fn create_agent(origin: OriginFor) -> DispatchResult { + let origin_location: MultiLocation = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is some consensus system on a sibling parachain + let (para_id, agent_id) = ensure_sibling::(&origin_location)?; + + // Record the agent id or fail if it has already been created + ensure!(!Agents::::contains_key(agent_id), Error::::AgentAlreadyCreated); + Agents::::insert(agent_id, ()); + + let command = Command::CreateAgent { agent_id }; + let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::CreateAgent { + location: Box::new(origin_location), + agent_id, + }); + Ok(()) + } + + /// Sends a message to the Gateway contract to create a new channel representing `origin` + /// + /// Fee required: Yes + /// + /// This extrinsic is permissionless, so a fee is charged to prevent spamming and pay + /// for execution costs on the remote side. + /// + /// The message is sent over the bridge on BridgeHub's own channel to the Gateway. + /// + /// - `origin`: Must be `MultiLocation` + /// - `mode`: Initial operating mode of the channel + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::create_channel())] + pub fn create_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { + let origin_location: MultiLocation = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is a sibling parachain + let (para_id, agent_id) = ensure_sibling::(&origin_location)?; + + let channel_id: ChannelId = para_id.into(); + + ensure!(Agents::::contains_key(agent_id), Error::::NoAgent); + ensure!(!Channels::::contains_key(channel_id), Error::::ChannelAlreadyCreated); + + let channel = Channel { agent_id, para_id }; + Channels::::insert(channel_id, channel); + + let command = Command::CreateChannel { channel_id, agent_id, mode }; + let pays_fee = PaysFee::::Yes(sibling_sovereign_account::(para_id)); + Self::send(SECONDARY_GOVERNANCE_CHANNEL, command, pays_fee)?; + + Self::deposit_event(Event::::CreateChannel { channel_id, agent_id }); + Ok(()) + } + + /// Sends a message to the Gateway contract to update a channel configuration + /// + /// The origin must already have a channel initialized, as this message is sent over it. + /// + /// A partial fee will be charged for local processing only. + /// + /// - `origin`: Must be `MultiLocation` + /// - `mode`: Initial operating mode of the channel + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::update_channel())] + pub fn update_channel(origin: OriginFor, mode: OperatingMode) -> DispatchResult { + let origin_location: MultiLocation = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is a sibling parachain + let (para_id, _) = ensure_sibling::(&origin_location)?; + + let channel_id: ChannelId = para_id.into(); + + ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); + + let command = Command::UpdateChannel { channel_id, mode }; + let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); + + // Parachains send the update message on their own channel + Self::send(channel_id, command, pays_fee)?; + + Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); + Ok(()) + } + + /// Sends a message to the Gateway contract to update an arbitrary channel + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `channel_id`: ID of channel + /// - `mode`: Initial operating mode of the channel + /// - `outbound_fee`: Fee charged to users for sending outbound messages to Polkadot + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::force_update_channel())] + pub fn force_update_channel( + origin: OriginFor, + channel_id: ChannelId, + mode: OperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(Channels::::contains_key(channel_id), Error::::NoChannel); + + let command = Command::UpdateChannel { channel_id, mode }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::::UpdateChannel { channel_id, mode }); + Ok(()) + } + + /// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`. + /// + /// A partial fee will be charged for local processing only. + /// + /// - `origin`: Must be `MultiLocation` + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::transfer_native_from_agent())] + pub fn transfer_native_from_agent( + origin: OriginFor, + recipient: H160, + amount: u128, + ) -> DispatchResult { + let origin_location: MultiLocation = T::SiblingOrigin::ensure_origin(origin)?; + + // Ensure that origin location is some consensus system on a sibling parachain + let (para_id, agent_id) = ensure_sibling::(&origin_location)?; + + // Since the origin is also the owner of the channel, they only need to pay + // the local processing fee. + let pays_fee = PaysFee::::Partial(sibling_sovereign_account::(para_id)); + + Self::do_transfer_native_from_agent( + agent_id, + para_id.into(), + recipient, + amount, + pays_fee, + ) + } + + /// Sends a message to the Gateway contract to transfer ether from an agent to `recipient`. + /// + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `location`: Location used to resolve the agent + /// - `recipient`: Recipient of funds + /// - `amount`: Amount to transfer + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::force_transfer_native_from_agent())] + pub fn force_transfer_native_from_agent( + origin: OriginFor, + location: Box, + recipient: H160, + amount: u128, + ) -> DispatchResult { + ensure_root(origin)?; + + // Ensure that location is some consensus system on a sibling parachain + let location: MultiLocation = + (*location).try_into().map_err(|_| Error::::UnsupportedLocationVersion)?; + let (_, agent_id) = + ensure_sibling::(&location).map_err(|_| Error::::InvalidLocation)?; + + let pays_fee = PaysFee::::No; + + Self::do_transfer_native_from_agent( + agent_id, + PRIMARY_GOVERNANCE_CHANNEL, + recipient, + amount, + pays_fee, + ) + } + + /// Sends a message to the Gateway contract to update fee related parameters for + /// token transfers. + /// + /// Privileged. Can only be called by root. + /// + /// Fee required: No + /// + /// - `origin`: Must be root + /// - `create_asset_xcm`: The XCM execution cost for creating a new asset class on AssetHub, + /// in DOT + /// - `transfer_asset_xcm`: The XCM execution cost for performing a reserve transfer on + /// AssetHub, in DOT + /// - `register_token`: The Ether fee for registering a new token, to discourage spamming + #[pallet::call_index(9)] + #[pallet::weight((T::WeightInfo::set_token_transfer_fees(), DispatchClass::Operational))] + pub fn set_token_transfer_fees( + origin: OriginFor, + create_asset_xcm: u128, + transfer_asset_xcm: u128, + register_token: U256, + ) -> DispatchResult { + ensure_root(origin)?; + + // Basic validation of new costs. Particularly for token registration, we want to ensure + // its relatively expensive to discourage spamming. Like at least 100 USD. + ensure!( + create_asset_xcm > 0 && transfer_asset_xcm > 0 && register_token > meth(100), + Error::::InvalidTokenTransferFees + ); + + let command = Command::SetTokenTransferFees { + create_asset_xcm, + transfer_asset_xcm, + register_token, + }; + Self::send(PRIMARY_GOVERNANCE_CHANNEL, command, PaysFee::::No)?; + + Self::deposit_event(Event::::SetTokenTransferFees { + create_asset_xcm, + transfer_asset_xcm, + register_token, + }); + Ok(()) + } + } + + impl Pallet { + /// Send `command` to the Gateway on the Channel identified by `channel_id` + fn send(channel_id: ChannelId, command: Command, pays_fee: PaysFee) -> DispatchResult { + let message = Message { id: None, channel_id, command }; + let (ticket, fee) = + T::OutboundQueue::validate(&message).map_err(|err| Error::::Send(err))?; + + let payment = match pays_fee { + PaysFee::Yes(account) => Some((account, fee.total())), + PaysFee::Partial(account) => Some((account, fee.local)), + PaysFee::No => None, + }; + + if let Some((payer, fee)) = payment { + T::Token::transfer( + &payer, + &T::TreasuryAccount::get(), + fee, + Preservation::Preserve, + )?; + } + + T::OutboundQueue::deliver(ticket).map_err(|err| Error::::Send(err))?; + Ok(()) + } + + /// Issue a `Command::TransferNativeFromAgent` command. The command will be sent on the + /// channel `channel_id` + pub fn do_transfer_native_from_agent( + agent_id: H256, + channel_id: ChannelId, + recipient: H160, + amount: u128, + pays_fee: PaysFee, + ) -> DispatchResult { + ensure!(Agents::::contains_key(agent_id), Error::::NoAgent); + + let command = Command::TransferNativeFromAgent { agent_id, recipient, amount }; + Self::send(channel_id, command, pays_fee)?; + + Self::deposit_event(Event::::TransferNativeFromAgent { + agent_id, + recipient, + amount, + }); + Ok(()) + } + + /// Initializes agents and channels. + pub fn initialize(para_id: ParaId, asset_hub_para_id: ParaId) -> Result<(), DispatchError> { + // Asset Hub + let asset_hub_location: MultiLocation = + ParentThen(X1(Parachain(asset_hub_para_id.into()))).into(); + let asset_hub_agent_id = agent_id_of::(&asset_hub_location)?; + let asset_hub_channel_id: ChannelId = asset_hub_para_id.into(); + Agents::::insert(asset_hub_agent_id, ()); + Channels::::insert( + asset_hub_channel_id, + Channel { agent_id: asset_hub_agent_id, para_id: asset_hub_para_id }, + ); + + // Governance channels + let bridge_hub_agent_id = agent_id_of::(&MultiLocation::here())?; + // Agent for BridgeHub + Agents::::insert(bridge_hub_agent_id, ()); + + // Primary governance channel + Channels::::insert( + PRIMARY_GOVERNANCE_CHANNEL, + Channel { agent_id: bridge_hub_agent_id, para_id }, + ); + + // Secondary governance channel + Channels::::insert( + SECONDARY_GOVERNANCE_CHANNEL, + Channel { agent_id: bridge_hub_agent_id, para_id }, + ); + + Ok(()) + } + + /// Checks if the pallet has been initialized. + pub(crate) fn is_initialized() -> bool { + let primary_exists = Channels::::contains_key(PRIMARY_GOVERNANCE_CHANNEL); + let secondary_exists = Channels::::contains_key(SECONDARY_GOVERNANCE_CHANNEL); + primary_exists && secondary_exists + } + } + + impl StaticLookup for Pallet { + type Source = ChannelId; + type Target = Channel; + fn lookup(channel_id: Self::Source) -> Option { + Channels::::get(channel_id) + } + } + + impl Contains for Pallet { + fn contains(channel_id: &ChannelId) -> bool { + Channels::::get(channel_id).is_some() + } + } + + impl Get> for Pallet { + fn get() -> PricingParametersOf { + PricingParameters::::get() + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/migration.rs b/bridges/snowbridge/parachain/pallets/system/src/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee94fc091bd1ecb0511789c998e65c0b8f665451 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/migration.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Governance API for controlling the Ethereum side of the bridge +use super::*; +use frame_support::traits::OnRuntimeUpgrade; +use log; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +pub mod v0 { + use frame_support::{pallet_prelude::*, weights::Weight}; + + use super::*; + + const LOG_TARGET: &str = "ethereum_system::migration"; + + pub struct InitializeOnUpgrade( + sp_std::marker::PhantomData<(T, BridgeHubParaId, AssetHubParaId)>, + ); + impl OnRuntimeUpgrade + for InitializeOnUpgrade + where + T: Config, + BridgeHubParaId: Get, + AssetHubParaId: Get, + { + fn on_runtime_upgrade() -> Weight { + if !Pallet::::is_initialized() { + Pallet::::initialize( + BridgeHubParaId::get().into(), + AssetHubParaId::get().into(), + ) + .expect("infallible; qed"); + log::info!( + target: LOG_TARGET, + "Ethereum system initialized." + ); + T::DbWeight::get().reads_writes(2, 5) + } else { + log::info!( + target: LOG_TARGET, + "Ethereum system already initialized. Skipping." + ); + T::DbWeight::get().reads(2) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + if !Pallet::::is_initialized() { + log::info!( + target: LOG_TARGET, + "Agents and channels not initialized. Initialization will run." + ); + } else { + log::info!( + target: LOG_TARGET, + "Agents and channels are initialized. Initialization will not run." + ); + } + Ok(vec![]) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + frame_support::ensure!( + Pallet::::is_initialized(), + "Agents and channels were not initialized." + ); + Ok(()) + } + } +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/mock.rs b/bridges/snowbridge/parachain/pallets/system/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a4f61189305d004481ce2edf5f10759bd6936ee --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/mock.rs @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate as snowbridge_system; +use frame_support::{ + parameter_types, + traits::{tokens::fungible::Mutate, ConstU128, ConstU16, ConstU64, ConstU8}, + weights::IdentityFee, + PalletId, +}; +use sp_core::H256; +use xcm_executor::traits::ConvertLocation; + +use snowbridge_core::{ + gwei, meth, outbound::ConstantGasMeter, sibling_sovereign_account, AgentId, AllowSiblingsOnly, + ParaId, PricingParameters, Rewards, +}; +use sp_runtime::{ + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, Keccak256}, + AccountId32, BuildStorage, FixedU128, +}; +use xcm::prelude::*; + +#[cfg(feature = "runtime-benchmarks")] +use crate::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; +type Balance = u128; + +pub type AccountId = AccountId32; + +// A stripped-down version of pallet-xcm that only inserts an XCM origin into the runtime +#[allow(dead_code)] +#[frame_support::pallet] +mod pallet_xcm_origin { + use frame_support::{ + pallet_prelude::*, + traits::{Contains, OriginTrait}, + }; + use xcm::latest::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeOrigin: From + From<::RuntimeOrigin>; + } + + // Insert this custom Origin into the aggregate RuntimeOrigin + #[pallet::origin] + #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct Origin(pub MultiLocation); + + impl From for Origin { + fn from(location: MultiLocation) -> Origin { + Origin(location) + } + } + + /// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and + /// filter the contained location + pub struct EnsureXcm(PhantomData); + impl, F: Contains> EnsureOrigin for EnsureXcm + where + O::PalletsOrigin: From + TryInto, + { + type Success = MultiLocation; + + fn try_origin(outer: O) -> Result { + outer.try_with_caller(|caller| { + caller.try_into().and_then(|o| match o { + Origin(location) if F::contains(&location) => Ok(location), + o => Err(o.into()), + }) + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(O::from(Origin(MultiLocation { parents: 1, interior: X1(Parachain(2000)) }))) + } + } +} + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + XcmOrigin: pallet_xcm_origin::{Pallet, Origin}, + OutboundQueue: snowbridge_outbound_queue::{Pallet, Call, Storage, Event}, + EthereumSystem: snowbridge_system, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + 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>; + type Nonce = u64; + type Block = Block; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type RuntimeFreezeReason = (); + type MaxHolds = (); +} + +impl pallet_xcm_origin::Config for Test { + type RuntimeOrigin = RuntimeOrigin; +} + +parameter_types! { + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); +} + +impl pallet_message_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = OutboundQueue; + type Size = u32; + type QueueChangeHandler = (); + type HeapSize = HeapSize; + type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; + type QueuePausedQuery = (); +} + +parameter_types! { + pub const MaxMessagePayloadSize: u32 = 1024; + pub const MaxMessagesPerBlock: u32 = 20; + pub const OwnParaId: ParaId = ParaId::new(1013); +} + +impl snowbridge_outbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type Decimals = ConstU8<10>; + type MaxMessagePayloadSize = MaxMessagePayloadSize; + type MaxMessagesPerBlock = MaxMessagesPerBlock; + type GasMeter = ConstantGasMeter; + type Balance = u128; + type PricingParameters = EthereumSystem; + type Channels = EthereumSystem; + type WeightToFee = IdentityFee; + type WeightInfo = (); +} + +parameter_types! { + pub const SS58Prefix: u8 = 42; + pub const AnyNetwork: Option = None; + pub const RelayNetwork: Option = Some(NetworkId::Kusama); + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(1013)); +} + +pub const DOT: u128 = 10_000_000_000; + +parameter_types! { + pub TreasuryAccount: AccountId = PalletId(*b"py/trsry").into_account_truncating(); + pub Fee: u64 = 1000; + pub const RococoNetwork: NetworkId = NetworkId::Rococo; + pub const InitialFunding: u128 = 1_000_000_000_000; + pub AssetHubParaId: ParaId = ParaId::new(1000); + pub TestParaId: u32 = 2000; + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: DOT, remote: meth(1) } + }; + pub const InboundDeliveryCost: u128 = 1_000_000_000; + +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for () { + fn make_xcm_origin(location: MultiLocation) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm_origin::Origin(location)) + } +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = OutboundQueue; + type SiblingOrigin = pallet_xcm_origin::EnsureXcm; + type AgentIdOf = snowbridge_core::AgentIdOf; + type TreasuryAccount = TreasuryAccount; + type Token = Balances; + type DefaultPricingParameters = Parameters; + type WeightInfo = (); + type InboundDeliveryCost = InboundDeliveryCost; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext(genesis_build: bool) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + if genesis_build { + crate::GenesisConfig:: { + para_id: OwnParaId::get(), + asset_hub_para_id: AssetHubParaId::get(), + _config: Default::default(), + } + .assimilate_storage(&mut storage) + .unwrap(); + } + + let mut ext: sp_io::TestExternalities = storage.into(); + let initial_amount = InitialFunding::get(); + let test_para_id = TestParaId::get(); + let sovereign_account = sibling_sovereign_account::(test_para_id.into()); + let treasury_account = TreasuryAccount::get(); + ext.execute_with(|| { + System::set_block_number(1); + Balances::mint_into(&AccountId32::from([0; 32]), initial_amount).unwrap(); + Balances::mint_into(&sovereign_account, initial_amount).unwrap(); + Balances::mint_into(&treasury_account, initial_amount).unwrap(); + }); + ext +} + +// Test helpers + +pub fn make_xcm_origin(location: MultiLocation) -> RuntimeOrigin { + pallet_xcm_origin::Origin(location).into() +} + +pub fn make_agent_id(location: MultiLocation) -> AgentId { + ::AgentIdOf::convert_location(&location) + .expect("convert location") +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/tests.rs b/bridges/snowbridge/parachain/pallets/system/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..e07481c1e33e5a9496c441383f2ba2908390f8e6 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/tests.rs @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{mock::*, *}; +use frame_support::{assert_noop, assert_ok}; +use hex_literal::hex; +use snowbridge_core::{eth, sibling_sovereign_account_raw}; +use sp_core::H256; +use sp_runtime::{AccountId32, DispatchError::BadOrigin, TokenError}; + +#[test] +fn create_agent() { + new_test_ext(true).execute_with(|| { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let agent_id = make_agent_id(origin_location); + let sovereign_account = sibling_sovereign_account::(origin_para_id.into()); + + // fund sovereign account of origin + let _ = Balances::mint_into(&sovereign_account, 10000); + + assert!(!Agents::::contains_key(agent_id)); + + let origin = make_xcm_origin(origin_location); + assert_ok!(EthereumSystem::create_agent(origin)); + + assert!(Agents::::contains_key(agent_id)); + }); +} + +#[test] +fn test_agent_for_here() { + new_test_ext(true).execute_with(|| { + let origin_location = MultiLocation::here(); + let agent_id = make_agent_id(origin_location); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(), + ) + }); +} + +#[test] +fn create_agent_fails_on_funds_unavailable() { + new_test_ext(true).execute_with(|| { + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(2000)) }; + let origin = make_xcm_origin(origin_location); + // Reset balance of sovereign_account to zero so to trigger the FundsUnavailable error + let sovereign_account = sibling_sovereign_account::(2000.into()); + Balances::set_balance(&sovereign_account, 0); + assert_noop!(EthereumSystem::create_agent(origin), TokenError::FundsUnavailable); + }); +} + +#[test] +fn create_agent_bad_origin() { + new_test_ext(true).execute_with(|| { + // relay chain location not allowed + assert_noop!( + EthereumSystem::create_agent(make_xcm_origin(MultiLocation { + parents: 1, + interior: Here, + })), + BadOrigin, + ); + + // local account location not allowed + assert_noop!( + EthereumSystem::create_agent(make_xcm_origin(MultiLocation { + parents: 0, + interior: X1(Junction::AccountId32 { network: None, id: [67u8; 32] }), + })), + BadOrigin, + ); + + // Signed origin not allowed + assert_noop!( + EthereumSystem::create_agent(RuntimeOrigin::signed([14; 32].into())), + BadOrigin + ); + + // None origin not allowed + assert_noop!(EthereumSystem::create_agent(RuntimeOrigin::none()), BadOrigin); + }); +} + +#[test] +fn upgrade_as_root() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let address: H160 = Default::default(); + let code_hash: H256 = Default::default(); + + assert_ok!(EthereumSystem::upgrade(origin, address, code_hash, None)); + + System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::Upgrade { + impl_address: address, + impl_code_hash: code_hash, + initializer_params_hash: None, + })); + }); +} + +#[test] +fn upgrade_as_signed_fails() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::signed(AccountId32::new([0; 32])); + let address: H160 = Default::default(); + let code_hash: H256 = Default::default(); + + assert_noop!(EthereumSystem::upgrade(origin, address, code_hash, None), BadOrigin); + }); +} + +#[test] +fn upgrade_with_params() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let address: H160 = Default::default(); + let code_hash: H256 = Default::default(); + let initializer: Option = + Some(Initializer { params: [0; 256].into(), maximum_required_gas: 10000 }); + assert_ok!(EthereumSystem::upgrade(origin, address, code_hash, initializer)); + }); +} + +#[test] +fn set_operating_mode() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let mode = OperatingMode::RejectingOutboundMessages; + + assert_ok!(EthereumSystem::set_operating_mode(origin, mode)); + + System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::SetOperatingMode { + mode, + })); + }); +} + +#[test] +fn set_operating_mode_as_signed_fails() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::signed([14; 32].into()); + let mode = OperatingMode::RejectingOutboundMessages; + + assert_noop!(EthereumSystem::set_operating_mode(origin, mode), BadOrigin); + }); +} + +#[test] +fn set_pricing_parameters() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let mut params = Parameters::get(); + params.rewards.local = 7; + + assert_ok!(EthereumSystem::set_pricing_parameters(origin, params)); + + assert_eq!(PricingParameters::::get().rewards.local, 7); + }); +} + +#[test] +fn set_pricing_parameters_as_signed_fails() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::signed([14; 32].into()); + let params = Parameters::get(); + + assert_noop!(EthereumSystem::set_pricing_parameters(origin, params), BadOrigin); + }); +} + +#[test] +fn set_pricing_parameters_invalid() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let mut params = Parameters::get(); + params.rewards.local = 0; + + assert_noop!( + EthereumSystem::set_pricing_parameters(origin.clone(), params), + Error::::InvalidPricingParameters + ); + + let mut params = Parameters::get(); + params.exchange_rate = 0u128.into(); + assert_noop!( + EthereumSystem::set_pricing_parameters(origin.clone(), params), + Error::::InvalidPricingParameters + ); + params = Parameters::get(); + params.fee_per_gas = sp_core::U256::zero(); + assert_noop!( + EthereumSystem::set_pricing_parameters(origin.clone(), params), + Error::::InvalidPricingParameters + ); + params = Parameters::get(); + params.rewards.local = 0; + assert_noop!( + EthereumSystem::set_pricing_parameters(origin.clone(), params), + Error::::InvalidPricingParameters + ); + params = Parameters::get(); + params.rewards.remote = sp_core::U256::zero(); + assert_noop!( + EthereumSystem::set_pricing_parameters(origin, params), + Error::::InvalidPricingParameters + ); + }); +} + +#[test] +fn set_token_transfer_fees() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + + assert_ok!(EthereumSystem::set_token_transfer_fees(origin, 1, 1, eth(1))); + }); +} + +#[test] +fn set_token_transfer_fees_root_only() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::signed([14; 32].into()); + + assert_noop!(EthereumSystem::set_token_transfer_fees(origin, 1, 1, 1.into()), BadOrigin); + }); +} + +#[test] +fn set_token_transfer_fees_invalid() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + + assert_noop!( + EthereumSystem::set_token_transfer_fees(origin, 0, 0, 0.into()), + Error::::InvalidTokenTransferFees + ); + }); +} + +#[test] +fn create_channel() { + new_test_ext(true).execute_with(|| { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let sovereign_account = sibling_sovereign_account::(origin_para_id.into()); + let origin = make_xcm_origin(origin_location); + + // fund sovereign account of origin + let _ = Balances::mint_into(&sovereign_account, 10000); + + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal)); + }); +} + +#[test] +fn create_channel_fail_already_exists() { + new_test_ext(true).execute_with(|| { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let sovereign_account = sibling_sovereign_account::(origin_para_id.into()); + let origin = make_xcm_origin(origin_location); + + // fund sovereign account of origin + let _ = Balances::mint_into(&sovereign_account, 10000); + + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + + assert_noop!( + EthereumSystem::create_channel(origin, OperatingMode::Normal), + Error::::ChannelAlreadyCreated + ); + }); +} + +#[test] +fn create_channel_bad_origin() { + new_test_ext(true).execute_with(|| { + // relay chain location not allowed + assert_noop!( + EthereumSystem::create_channel( + make_xcm_origin(MultiLocation { parents: 1, interior: Here }), + OperatingMode::Normal, + ), + BadOrigin, + ); + + // child of sibling location not allowed + assert_noop!( + EthereumSystem::create_channel( + make_xcm_origin(MultiLocation { + parents: 1, + interior: X2( + Parachain(2000), + Junction::AccountId32 { network: None, id: [67u8; 32] } + ), + }), + OperatingMode::Normal, + ), + BadOrigin, + ); + + // local account location not allowed + assert_noop!( + EthereumSystem::create_channel( + make_xcm_origin(MultiLocation { + parents: 0, + interior: X1(Junction::AccountId32 { network: None, id: [67u8; 32] }), + }), + OperatingMode::Normal, + ), + BadOrigin, + ); + + // Signed origin not allowed + assert_noop!( + EthereumSystem::create_channel( + RuntimeOrigin::signed([14; 32].into()), + OperatingMode::Normal, + ), + BadOrigin + ); + + // None origin not allowed + assert_noop!(EthereumSystem::create_agent(RuntimeOrigin::none()), BadOrigin); + }); +} + +#[test] +fn update_channel() { + new_test_ext(true).execute_with(|| { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let sovereign_account = sibling_sovereign_account::(origin_para_id.into()); + let origin = make_xcm_origin(origin_location); + + // First create the channel + let _ = Balances::mint_into(&sovereign_account, 10000); + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + + // Now try to update it + assert_ok!(EthereumSystem::update_channel(origin, OperatingMode::Normal)); + + System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::UpdateChannel { + channel_id: ParaId::from(2000).into(), + mode: OperatingMode::Normal, + })); + }); +} + +#[test] +fn update_channel_bad_origin() { + new_test_ext(true).execute_with(|| { + let mode = OperatingMode::Normal; + + // relay chain location not allowed + assert_noop!( + EthereumSystem::update_channel( + make_xcm_origin(MultiLocation { parents: 1, interior: Here }), + mode, + ), + BadOrigin, + ); + + // child of sibling location not allowed + assert_noop!( + EthereumSystem::update_channel( + make_xcm_origin(MultiLocation { + parents: 1, + interior: X2( + Parachain(2000), + Junction::AccountId32 { network: None, id: [67u8; 32] } + ), + }), + mode, + ), + BadOrigin, + ); + + // local account location not allowed + assert_noop!( + EthereumSystem::update_channel( + make_xcm_origin(MultiLocation { + parents: 0, + interior: X1(Junction::AccountId32 { network: None, id: [67u8; 32] }), + }), + mode, + ), + BadOrigin, + ); + + // Signed origin not allowed + assert_noop!( + EthereumSystem::update_channel(RuntimeOrigin::signed([14; 32].into()), mode), + BadOrigin + ); + + // None origin not allowed + assert_noop!(EthereumSystem::update_channel(RuntimeOrigin::none(), mode), BadOrigin); + }); +} + +#[test] +fn update_channel_fails_not_exist() { + new_test_ext(true).execute_with(|| { + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(2000)) }; + let origin = make_xcm_origin(origin_location); + + // Now try to update it + assert_noop!( + EthereumSystem::update_channel(origin, OperatingMode::Normal), + Error::::NoChannel + ); + }); +} + +#[test] +fn force_update_channel() { + new_test_ext(true).execute_with(|| { + let origin_para_id = 2000; + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(origin_para_id)) }; + let sovereign_account = sibling_sovereign_account::(origin_para_id.into()); + let origin = make_xcm_origin(origin_location); + + let channel_id: ChannelId = ParaId::from(origin_para_id).into(); + + // First create the channel + let _ = Balances::mint_into(&sovereign_account, 10000); + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + + // Now try to force update it + let force_origin = RuntimeOrigin::root(); + assert_ok!(EthereumSystem::force_update_channel( + force_origin, + channel_id, + OperatingMode::Normal, + )); + + System::assert_last_event(RuntimeEvent::EthereumSystem(crate::Event::UpdateChannel { + channel_id: ParaId::from(2000).into(), + mode: OperatingMode::Normal, + })); + }); +} + +#[test] +fn force_update_channel_bad_origin() { + new_test_ext(true).execute_with(|| { + let mode = OperatingMode::Normal; + + // signed origin not allowed + assert_noop!( + EthereumSystem::force_update_channel( + RuntimeOrigin::signed([14; 32].into()), + ParaId::from(1000).into(), + mode, + ), + BadOrigin, + ); + }); +} + +#[test] +fn transfer_native_from_agent() { + new_test_ext(true).execute_with(|| { + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(2000)) }; + let origin = make_xcm_origin(origin_location); + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + + // First create the agent and channel + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal)); + + let origin = make_xcm_origin(origin_location); + assert_ok!(EthereumSystem::transfer_native_from_agent(origin, recipient, amount),); + + System::assert_last_event(RuntimeEvent::EthereumSystem( + crate::Event::TransferNativeFromAgent { + agent_id: make_agent_id(origin_location), + recipient, + amount, + }, + )); + }); +} + +#[test] +fn force_transfer_native_from_agent() { + new_test_ext(true).execute_with(|| { + let origin = RuntimeOrigin::root(); + let location = MultiLocation { parents: 1, interior: X1(Parachain(2000)) }; + let versioned_location: Box = Box::new(location.into()); + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + + // First create the agent + Agents::::insert(make_agent_id(location), ()); + + assert_ok!(EthereumSystem::force_transfer_native_from_agent( + origin, + versioned_location, + recipient, + amount + ),); + + System::assert_last_event(RuntimeEvent::EthereumSystem( + crate::Event::TransferNativeFromAgent { + agent_id: make_agent_id(location), + recipient, + amount, + }, + )); + }); +} + +#[test] +fn force_transfer_native_from_agent_bad_origin() { + new_test_ext(true).execute_with(|| { + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + + // signed origin not allowed + assert_noop!( + EthereumSystem::force_transfer_native_from_agent( + RuntimeOrigin::signed([14; 32].into()), + Box::new( + MultiLocation { + parents: 1, + interior: X2( + Parachain(2000), + Junction::AccountId32 { network: None, id: [67u8; 32] } + ), + } + .into() + ), + recipient, + amount, + ), + BadOrigin, + ); + }); +} + +// NOTE: The following tests are not actually tests and are more about obtaining location +// conversions for devops purposes. They need to be removed here and incorporated into a command +// line utility. + +#[ignore] +#[test] +fn check_sibling_sovereign_account() { + new_test_ext(true).execute_with(|| { + let para_id = 1001; + let sovereign_account = sibling_sovereign_account::(para_id.into()); + let sovereign_account_raw = sibling_sovereign_account_raw(para_id.into()); + println!( + "Sovereign account for parachain {}: {:#?}", + para_id, + hex::encode(sovereign_account.clone()) + ); + assert_eq!(sovereign_account, sovereign_account_raw.into()); + }); +} + +#[test] +fn charge_fee_for_create_agent() { + new_test_ext(true).execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(para_id)) }; + let origin = make_xcm_origin(origin_location); + let sovereign_account = sibling_sovereign_account::(para_id.into()); + let (_, agent_id) = ensure_sibling::(&origin_location).unwrap(); + + let initial_sovereign_balance = Balances::balance(&sovereign_account); + assert_ok!(EthereumSystem::create_agent(origin.clone())); + let fee_charged = initial_sovereign_balance - Balances::balance(&sovereign_account); + + assert_ok!(EthereumSystem::create_channel(origin, OperatingMode::Normal)); + + // assert sovereign_balance decreased by (fee.base_fee + fee.delivery_fee) + let message = Message { + id: None, + channel_id: ParaId::from(para_id).into(), + command: Command::CreateAgent { agent_id }, + }; + let (_, fee) = OutboundQueue::validate(&message).unwrap(); + assert_eq!(fee.local + fee.remote, fee_charged); + + // and treasury_balance increased + let treasury_balance = Balances::balance(&TreasuryAccount::get()); + assert!(treasury_balance > InitialFunding::get()); + + let final_sovereign_balance = Balances::balance(&sovereign_account); + // (sovereign_balance + treasury_balance) keeps the same + assert_eq!(final_sovereign_balance + treasury_balance, { InitialFunding::get() * 2 }); + }); +} + +#[test] +fn charge_fee_for_transfer_native_from_agent() { + new_test_ext(true).execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin_location = MultiLocation { parents: 1, interior: X1(Parachain(para_id)) }; + let recipient: H160 = [27u8; 20].into(); + let amount = 103435; + let origin = make_xcm_origin(origin_location); + let (_, agent_id) = ensure_sibling::(&origin_location).unwrap(); + + let sovereign_account = sibling_sovereign_account::(para_id.into()); + + // create_agent & create_channel first + assert_ok!(EthereumSystem::create_agent(origin.clone())); + assert_ok!(EthereumSystem::create_channel(origin.clone(), OperatingMode::Normal)); + + // assert sovereign_balance decreased by only the base_fee + let sovereign_balance_before = Balances::balance(&sovereign_account); + assert_ok!(EthereumSystem::transfer_native_from_agent(origin.clone(), recipient, amount)); + let message = Message { + id: None, + channel_id: ParaId::from(para_id).into(), + command: Command::TransferNativeFromAgent { agent_id, recipient, amount }, + }; + let (_, fee) = OutboundQueue::validate(&message).unwrap(); + let sovereign_balance_after = Balances::balance(&sovereign_account); + assert_eq!(sovereign_balance_after + fee.local, sovereign_balance_before); + }); +} + +#[test] +fn charge_fee_for_upgrade() { + new_test_ext(true).execute_with(|| { + let para_id: u32 = TestParaId::get(); + let origin = RuntimeOrigin::root(); + let address: H160 = Default::default(); + let code_hash: H256 = Default::default(); + let initializer: Option = + Some(Initializer { params: [0; 256].into(), maximum_required_gas: 10000 }); + assert_ok!(EthereumSystem::upgrade(origin, address, code_hash, initializer.clone())); + + // assert sovereign_balance does not change as we do not charge for sudo operations + let sovereign_account = sibling_sovereign_account::(para_id.into()); + let sovereign_balance = Balances::balance(&sovereign_account); + assert_eq!(sovereign_balance, InitialFunding::get()); + }); +} + +#[test] +fn genesis_build_initializes_correctly() { + new_test_ext(true).execute_with(|| { + assert!(EthereumSystem::is_initialized(), "Ethereum uninitialized."); + }); +} + +#[test] +fn no_genesis_build_is_uninitialized() { + new_test_ext(false).execute_with(|| { + assert!(!EthereumSystem::is_initialized(), "Ethereum initialized."); + }); +} diff --git a/bridges/snowbridge/parachain/pallets/system/src/weights.rs b/bridges/snowbridge/parachain/pallets/system/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e532a0d8a8c19339cc39574aeb09668468f34e9 --- /dev/null +++ b/bridges/snowbridge/parachain/pallets/system/src/weights.rs @@ -0,0 +1,249 @@ + +//! Autogenerated weights for `snowbridge_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `crake.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: `1024` + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// bridge-hub-rococo-dev +// --pallet=snowbridge_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template +// ../parachain/templates/module-weight-template.hbs +// --output +// ../parachain/pallets/control/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `snowbridge_system`. +pub trait WeightInfo { + fn upgrade() -> Weight; + fn create_agent() -> Weight; + fn create_channel() -> Weight; + fn update_channel() -> Weight; + fn force_update_channel() -> Weight; + fn set_operating_mode() -> Weight; + fn transfer_native_from_agent() -> Weight; + fn force_transfer_native_from_agent() -> Weight; + fn set_token_transfer_fees() -> Weight; + fn set_pricing_parameters() -> Weight; +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 44_000_000 picoseconds. + Weight::from_parts(44_000_000, 3517) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: EthereumSystem Agents (r:1 w:1) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// 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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn create_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `187` + // Estimated: `6196` + // Minimum execution time: 85_000_000 picoseconds. + Weight::from_parts(85_000_000, 6196) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumSystem Channels (r:1 w:1) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, 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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 create_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `602` + // Estimated: `69050` + // Minimum execution time: 83_000_000 picoseconds. + Weight::from_parts(83_000_000, 69050) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: EthereumSystem Channels (r:1 w:0) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn update_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 40_000_000 picoseconds. + Weight::from_parts(40_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: EthereumSystem Channels (r:1 w:0) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn force_update_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(41_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_operating_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(31_000_000, 3517) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn transfer_native_from_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `252` + // Estimated: `6044` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(45_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn force_transfer_native_from_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `252` + // Estimated: `6044` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(42_000_000, 6044) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_token_transfer_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(42_000_000, 3517) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_pricing_parameters() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(42_000_000, 3517) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml b/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..eb717325e8a025b3cb8ab1b18e8a3321dde87a6c --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "snowbridge-beacon-primitives" +description = "Snowbridge Beacon Primitives" +version = "0.0.1" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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 } + +ssz_rs = { version = "0.9.0", default-features = false } +ssz_rs_derive = { version = "0.9.0", default-features = false } +byte-slice-cast = { version = "1.2.1", default-features = false } + +snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } +static_assertions = { version = "1.1.0" } +milagro_bls = { git = "https://github.com/snowfork/milagro_bls", default-features = false, rev = "a6d66e4eb89015e352fb1c9f7b661ecdbb5b2176" } + +[dev-dependencies] +hex-literal = { version = "0.4.1" } + +[features] +default = ["std"] +std = [ + "byte-slice-cast/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "hex/std", + "milagro_bls/std", + "rlp/std", + "scale-info/std", + "serde", + "snowbridge-ethereum/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "ssz_rs/std", +] diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bits.rs b/bridges/snowbridge/parachain/primitives/beacon/src/bits.rs new file mode 100644 index 0000000000000000000000000000000000000000..72b7135ee2939bdabb98c9c06df801c43c3db230 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/bits.rs @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use sp_std::{convert::TryInto, prelude::*}; +use ssz_rs::{Bitvector, Deserialize}; + +pub fn decompress_sync_committee_bits< + const SYNC_COMMITTEE_SIZE: usize, + const SYNC_COMMITTEE_BITS_SIZE: usize, +>( + input: [u8; SYNC_COMMITTEE_BITS_SIZE], +) -> [u8; SYNC_COMMITTEE_SIZE] { + Bitvector::<{ SYNC_COMMITTEE_SIZE }>::deserialize(&input) + .expect("checked statically; qed") + .iter() + .map(|bit| u8::from(bit == true)) + .collect::>() + .try_into() + .expect("checked statically; qed") +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bls.rs b/bridges/snowbridge/parachain/primitives/beacon/src/bls.rs new file mode 100644 index 0000000000000000000000000000000000000000..589b72e67348f70122ad2362b8bc51474cb577ac --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/bls.rs @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{PublicKey, Signature}; +use codec::{Decode, Encode}; +use frame_support::{ensure, PalletError}; +pub use milagro_bls::{ + AggregatePublicKey, AggregateSignature, PublicKey as PublicKeyPrepared, + Signature as SignaturePrepared, +}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; + +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, TypeInfo, RuntimeDebug, PalletError)] +pub enum BlsError { + InvalidSignature, + InvalidPublicKey, + InvalidAggregatePublicKeys, + SignatureVerificationFailed, +} + +/// fast_aggregate_verify optimized with aggregate key subtracting absent ones. +pub fn fast_aggregate_verify( + aggregate_pubkey: &PublicKeyPrepared, + absent_pubkeys: &Vec, + message: H256, + signature: &Signature, +) -> Result<(), BlsError> { + let agg_sig = prepare_aggregate_signature(signature)?; + let agg_key = prepare_aggregate_pubkey_from_absent(aggregate_pubkey, absent_pubkeys)?; + fast_aggregate_verify_pre_aggregated(agg_sig, agg_key, message) +} + +/// Decompress one public key into a point in G1. +pub fn prepare_milagro_pubkey(pubkey: &PublicKey) -> Result { + PublicKeyPrepared::from_bytes_unchecked(&pubkey.0).map_err(|_| BlsError::InvalidPublicKey) +} + +/// Prepare for G1 public keys. +pub fn prepare_g1_pubkeys(pubkeys: &[PublicKey]) -> Result, BlsError> { + pubkeys + .iter() + // Deserialize one public key from compressed bytes + .map(prepare_milagro_pubkey) + .collect::, BlsError>>() +} + +/// Prepare for G1 AggregatePublicKey. +pub fn prepare_aggregate_pubkey( + pubkeys: &[PublicKeyPrepared], +) -> Result { + AggregatePublicKey::into_aggregate(pubkeys).map_err(|_| BlsError::InvalidPublicKey) +} + +/// Prepare for G1 AggregatePublicKey. +pub fn prepare_aggregate_pubkey_from_absent( + aggregate_key: &PublicKeyPrepared, + absent_pubkeys: &Vec, +) -> Result { + let mut aggregate_pubkey = AggregatePublicKey::from_public_key(aggregate_key); + if !absent_pubkeys.is_empty() { + let absent_aggregate_key = prepare_aggregate_pubkey(absent_pubkeys)?; + aggregate_pubkey.point.sub(&absent_aggregate_key.point); + } + Ok(AggregatePublicKey { point: aggregate_pubkey.point }) +} + +/// Prepare for G2 AggregateSignature, normally more expensive than G1 operation. +pub fn prepare_aggregate_signature(signature: &Signature) -> Result { + Ok(AggregateSignature::from_signature( + &SignaturePrepared::from_bytes(&signature.0).map_err(|_| BlsError::InvalidSignature)?, + )) +} + +/// fast_aggregate_verify_pre_aggregated which is the most expensive call in beacon light client. +pub fn fast_aggregate_verify_pre_aggregated( + agg_sig: AggregateSignature, + aggregate_key: AggregatePublicKey, + message: H256, +) -> Result<(), BlsError> { + ensure!( + agg_sig.fast_aggregate_verify_pre_aggregated(&message[..], &aggregate_key), + BlsError::SignatureVerificationFailed + ); + Ok(()) +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/config.rs b/bridges/snowbridge/parachain/primitives/beacon/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa5fda706f9934148a74683d1bfa4ffc872ce8e2 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/config.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +pub const MAX_PROOF_SIZE: u32 = 20; + +pub const FEE_RECIPIENT_SIZE: usize = 20; +pub const EXTRA_DATA_SIZE: usize = 32; +pub const LOGS_BLOOM_SIZE: usize = 256; + +pub const PUBKEY_SIZE: usize = 48; +pub const SIGNATURE_SIZE: usize = 96; diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/lib.rs b/bridges/snowbridge/parachain/primitives/beacon/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..3527e1ff0d195a5c4c3b988f17f4eebc67bd0e52 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/lib.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod bits; +pub mod bls; +pub mod config; +pub mod merkle_proof; +pub mod receipt; +pub mod ssz; +pub mod types; +pub mod updates; + +#[cfg(feature = "std")] +mod serde_utils; + +pub use types::{ + BeaconHeader, CompactBeaconState, CompactExecutionHeader, ExecutionHeaderState, + ExecutionPayloadHeader, FinalizedHeaderState, Fork, ForkData, ForkVersion, ForkVersions, Mode, + PublicKey, Signature, SigningData, SyncAggregate, SyncCommittee, SyncCommitteePrepared, +}; +pub use updates::{CheckpointUpdate, ExecutionHeaderUpdate, NextSyncCommitteeUpdate, Update}; + +pub use bits::decompress_sync_committee_bits; +pub use bls::{ + fast_aggregate_verify, prepare_aggregate_pubkey, prepare_aggregate_pubkey_from_absent, + prepare_aggregate_signature, prepare_g1_pubkeys, AggregatePublicKey, AggregateSignature, + BlsError, PublicKeyPrepared, SignaturePrepared, +}; +pub use merkle_proof::verify_merkle_branch; +pub use receipt::verify_receipt_proof; diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs b/bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs new file mode 100644 index 0000000000000000000000000000000000000000..a6ee6e9452c39d766565765e5aa5682e323c3f34 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use sp_core::H256; +use sp_io::hashing::sha2_256; + +/// Specified by +/// with improvements from +pub fn verify_merkle_branch( + leaf: H256, + branch: &[H256], + index: usize, + depth: usize, + root: H256, +) -> bool { + // verify the proof length + if branch.len() != depth { + return false + } + // verify the computed merkle root + root == compute_merkle_root(leaf, branch, index) +} + +fn compute_merkle_root(leaf: H256, proof: &[H256], index: usize) -> H256 { + let mut value: [u8; 32] = leaf.into(); + for (i, node) in proof.iter().enumerate() { + let mut data = [0u8; 64]; + if generalized_index_bit(index, i) { + // right node + data[0..32].copy_from_slice(node.as_bytes()); + data[32..64].copy_from_slice(&value); + value = sha2_256(&data); + } else { + // left node + data[0..32].copy_from_slice(&value); + data[32..64].copy_from_slice(node.as_bytes()); + value = sha2_256(&data); + } + } + value.into() +} + +/// Spec: +fn generalized_index_bit(index: usize, position: usize) -> bool { + index & (1 << position) > 0 +} + +/// Spec: +pub const fn subtree_index(generalized_index: usize) -> usize { + generalized_index % (1 << generalized_index_length(generalized_index)) +} + +/// Spec: +pub const fn generalized_index_length(generalized_index: usize) -> usize { + match generalized_index.checked_ilog2() { + Some(v) => v as usize, + None => panic!("checked statically; qed"), + } +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs b/bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..0588f3f73f715b417476e5b9a9dd02b62388e585 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_std::prelude::*; + +use snowbridge_ethereum::{mpt, Receipt}; + +pub fn verify_receipt_proof( + receipts_root: H256, + proof: &[Vec], +) -> Option> { + match apply_merkle_proof(proof) { + Some((root, data)) if root == receipts_root => Some(rlp::decode(&data)), + Some((_, _)) => None, + None => None, + } +} + +fn apply_merkle_proof(proof: &[Vec]) -> Option<(H256, Vec)> { + let mut iter = proof.iter().rev(); + let first_bytes = match iter.next() { + Some(b) => b, + None => return None, + }; + let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?; + + let final_hash: Option<[u8; 32]> = iter.try_fold(keccak_256(first_bytes), |acc, x| { + let node: Box = x.as_slice().try_into().ok()?; + if (*node).contains_hash(acc.into()) { + return Some(keccak_256(x)) + } + None + }); + + final_hash.map(|hash| (hash.into(), item_to_prove.value)) +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn test_verify_receipt_proof() { + let root: H256 = + hex!("fd5e397a84884641f53c496804f24b5276cbb8c5c9cfc2342246be8e3ce5ad02").into(); + + // Valid proof + let proof_receipt5 = vec!( + hex!("f90131a0b5ba404eb5a6a88e56579f4d37ef9813b5ad7f86f0823ff3b407ac5a6bb465eca0398ead2655e78e03c127ce22c5830e90f18b1601ec055f938336c084feb915a9a026d322c26e46c50942c1aabde50e36df5cde572aed650ce73ea3182c6e90a02ca00600a356135f4db1db0d9842264cdff2652676f881669e91e316c0b6dd783011a0837f1deb4075336da320388c1edfffc56c448a43f4a5ba031300d32a7b509fc5a01c3ac82fd65b4aba7f9afaf604d9c82ec7e2deb573a091ae235751bc5c0c288da05d454159d9071b0f68b6e0503d290f23ac7602c1db0c569dee4605d8f5298f09a00bbed10350ec954448df795f6fd46e3faefc800ede061b3840eedc6e2b07a74da0acb02d26a3650f2064c14a435fdf1f668d8655daf455ebdf671713a7c089b3898080808080808080").to_vec(), + hex!("f901f180a00046a08d4f0bdbdc6b31903086ce323182bce6725e7d9415f7ff91ee8f4820bda0e7cd26ad5f3d2771e4b5ab788e268a14a10209f94ee918eb6c829d21d3d11c1da00d4a56d9e9a6751874fd86c7e3cb1c6ad5a848da62751325f478978a00ea966ea064b81920c8f04a8a1e21f53a8280e739fbb7b00b2ab92493ca3f610b70e8ac85a0b1040ed4c55a73178b76abb16f946ce5bebd6b93ab873c83327df54047d12c27a0de6485e9ac58dc6e2b04b4bb38f562684f0b1a2ee586cc11079e7d9a9dc40b32a0d394f4d3532c3124a65fa36e69147e04fd20453a72ee9c50660f17e13ce9df48a066501003fc3e3478efd2803cd0eded6bbe9243ca01ba754d6327071ddbcbc649a0b2684e518f325fee39fc8ea81b68f3f5c785be00d087f3bed8857ae2ee8da26ea071060a5c52042e8d7ce21092f8ecf06053beb9a0b773a6f91a30c4220aa276b2a0fc22436632574ccf6043d0986dede27ea94c9ca9a3bb5ec03ce776a4ddef24a9a05a8a1d6698c4e7d8cc3a2506cb9b12ea9a079c9c7099bc919dc804033cc556e4a0170c468b0716fd36d161f0bf05875f15756a2976de92f9efe7716320509d79c9a0182f909a90cab169f3efb62387f9cccdd61440acc4deec42f68a4f7ca58075c7a055cf0e9202ac75689b76318f1171f3a44465eddc06aae0713bfb6b34fdd27b7980").to_vec(), + hex!("f904de20b904daf904d701830652f0b9010004200000000000000000000080020000000000010000000000010000000000000000000000000000000000000000000002000000080000000000000000200000000000000000000000000008000000220000000000400010000000000000000000000000000000000000000000000000000000000000040000000010000100000000000800000000004000000000000000000000000000080000004000000000020000000000020000000000000000000000000000000000000000000004000000000002000000000100000000000000000000000000001000000002000020000010200000000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000000000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffffffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea393ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000000000000000000000000000000000000005d415f3320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078e").to_vec(), + ); + assert!(verify_receipt_proof(root, &proof_receipt5).is_some()); + + // Various invalid proofs + let proof_empty: Vec> = vec![]; + let proof_missing_full_node = vec![proof_receipt5[0].clone(), proof_receipt5[2].clone()]; + let proof_missing_short_node1 = vec![proof_receipt5[0].clone(), proof_receipt5[1].clone()]; + let proof_missing_short_node2 = vec![proof_receipt5[0].clone()]; + let proof_invalid_encoding = vec![proof_receipt5[2][2..].to_vec()]; + let proof_no_full_node = vec![proof_receipt5[2].clone(), proof_receipt5[2].clone()]; + assert!(verify_receipt_proof(root, &proof_empty).is_none()); + assert!(verify_receipt_proof(root, &proof_missing_full_node).is_none()); + + assert_eq!( + verify_receipt_proof(root, &proof_missing_short_node1), + Some(Err(rlp::DecoderError::Custom("Unsupported receipt type"))) + ); + + assert_eq!( + verify_receipt_proof(root, &proof_missing_short_node2), + Some(Err(rlp::DecoderError::Custom("Unsupported receipt type"))) + ); + + assert!(verify_receipt_proof(root, &proof_invalid_encoding).is_none()); + assert!(verify_receipt_proof(root, &proof_no_full_node).is_none()); + } + + #[test] + fn test_verify_receipt_proof_with_intermediate_short_node() { + let root: H256 = + hex!("d128e3a57142d2bf15bc0cbcac7ad54f40750d571b5c3097e425882c10c9ba66").into(); + + let proof_receipt263 = vec![ + hex!("f90131a00d3cb8d3f57ac1c0e12918a2ebe0cafed8c273577b9dd73e7ed1079b403ef494a0678b9835b834f8a287c0dd33a8fca9146e456ca688555ed4ec1361a2180b778da0fe42da181a46677a043b3d9d4b8bb05a6a17b7b5c010c17e7c1d31cfb7c4f911a0c89f0e2c53241cdb578e1f2b4caf6ba36e00500bdc57fecd66b84a6a58394c19a086c3c1fae5a0575940b5d38e111c469d07883106c26856f3ef608469a2081f13a06c5992ff00aab6226a70a032fd2f571ba22f797321f45e2daa73020d638d21b0a050861e9503ef68728f6c90a44f7fe1bceb2a9bdab6957bbe7136166bd849561ea006aa6eaca8a07e57176e9aa41e6a09edfb7678d1a112404e0ec779d7e567e82ea0bb0b430d303ba21b0af11c487b8a218bd75db54c98940b3f11bad8ff47cad3ef8080808080808080").to_vec(), + hex!("f871a0246de222036ee6a03329b0105da0a6b3f916fc95a9ed5a403a581a0c4d74242ca0ac108a49a88b57a05ac34a108b39f1e45f6f167f2b9fbc8d52fb58e2e5a6af1ea0fcfe07ac2ccd3c28b6eab68d1bce112f6f6dbd9023e4ec3c05b96615aa803d798080808080808080808080808080").to_vec(), + hex!("e4820001a04fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8").to_vec(), + hex!("f851a096d010643ca2d47412ca66898286b5f2412963b9ec051b33e570d575914c9c5ca028cd24c652989542fe89479ec6388eac4592432242af5ba97563b3ac7c71c019808080808080808080808080808080").to_vec(), + hex!("f90211a0bb35a84c5b1dcb78ec9d32614912c696e62df77bebf9ab326ee55b5d3acdde46a01084b30dac8df0accfcd0fd6330b7f6fc72a4651246d0694be9162151686a620a03eed50afdce7909d784c6157c445a444c806b5f23d31f3b63786f600c84a95b2a0af5232f1df6c6d41879804d081abe867002abe26ba3e5f8e0254a83a54769831a0607915fb13dd5da594256389a45007a67a7f7a86e95d38d8462792b6c98a722ea00e1260fda1730f2738c650ce2bfba83857bc10f8fb119ebc4fb39acba24e6fbaa0d11de17e417327457812675ca3b84ae8e1b64827abfe01420953697c8313d5b1a05fcaf2f7a88f76336a0c32ffc78acb87ae2005454bd25d658035331be3173b46a03f94f4952ab9e650f83cfd0e7f367b1bcc493aacf39a06f16c4a2e1b5605da48a0bdb4ec79785ca8ae22d60f1bbd42d707b4d7ec4aff231a3ebab755e315b35053a043a67c3f2bcef37c8f47a673adcb7061007a553696d1092408601c11b2e6846aa0c519d5af48cae87c7f4538845417c9735813bee892a6fe2dda79f5c414e8576aa0f7058256e09589501d7c231d739e61c84a850e139690989d24fda6058b432e98a081a52faab520978cb19ce14400dba0cd5bcdc4e5a3c0740678aa8f97ee0e5c56a0bcecc61cadeae52518e3b68a48af4b11603dfd9d99d99d7985efa6d2de44f904a02cba4accfc6f39bc5adb6d4440eb6358b4a5103ef93298e4e694f1f940f8b48280").to_vec(), + hex!("f901ae20b901aaf901a70183bb444eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000000000000000100000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000081000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000f89df89b94dac17f958d2ee523a2206206994597c13d831ec7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e514404ff6823f1b46a8318a709251db414e5e1a000000000000000000000000055021c55847c00d764357a352e5803237d328954a0000000000000000000000000000000000000000000000000000000000201c370").to_vec(), + ]; + assert!(verify_receipt_proof(root, &proof_receipt263).is_some()); + } +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs b/bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs new file mode 100644 index 0000000000000000000000000000000000000000..07f5cbe724ed92bbda1d0cc7ded1a60c92a38cf0 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use sp_core::U256; + +use core::fmt::Formatter; +use serde::{Deserialize, Deserializer}; + +// helper to deserialize arbitrary arrays like [T; N] +pub mod arrays { + use std::{convert::TryInto, marker::PhantomData}; + + use serde::{ + de::{SeqAccess, Visitor}, + ser::SerializeTuple, + Deserialize, Deserializer, Serialize, Serializer, + }; + + pub fn serialize( + data: &[T; N], + ser: S, + ) -> Result { + let mut s = ser.serialize_tuple(N)?; + for item in data { + s.serialize_element(item)?; + } + s.end() + } + + struct ArrayVisitor(PhantomData); + + impl<'de, T, const N: usize> Visitor<'de> for ArrayVisitor + where + T: Deserialize<'de>, + { + type Value = [T; N]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(&format!("an array of length {}", N)) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + // can be optimized using MaybeUninit + let mut data = Vec::with_capacity(N); + for _ in 0..N { + match (seq.next_element())? { + Some(val) => data.push(val), + None => return Err(serde::de::Error::invalid_length(N, &self)), + } + } + match data.try_into() { + Ok(arr) => Ok(arr), + Err(_) => unreachable!(), + } + } + } + + pub fn deserialize<'de, D, T, const N: usize>(deserializer: D) -> Result<[T; N], D::Error> + where + D: Deserializer<'de>, + T: Deserialize<'de>, + { + deserializer.deserialize_tuple(N, ArrayVisitor::(PhantomData)) + } +} + +pub(crate) fn from_hex_to_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + + let str_without_0x = match s.strip_prefix("0x") { + Some(val) => val, + None => &s, + }; + + let hex_bytes = match hex::decode(str_without_0x) { + Ok(bytes) => bytes, + Err(e) => return Err(serde::de::Error::custom(e.to_string())), + }; + + Ok(hex_bytes) +} + +pub(crate) fn from_int_to_u256<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let number = u128::deserialize(deserializer)?; + + Ok(U256::from(number)) +} + +pub struct HexVisitor(); + +impl<'de, const LENGTH: usize> serde::de::Visitor<'de> for HexVisitor { + type Value = [u8; LENGTH]; + + fn expecting(&self, formatter: &mut Formatter) -> sp_std::fmt::Result { + formatter.write_str("a hex string with an '0x' prefix") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let stripped = match v.strip_prefix("0x") { + Some(stripped) => stripped, + None => v, + }; + + let decoded = match hex::decode(stripped) { + Ok(decoded) => decoded, + Err(e) => return Err(serde::de::Error::custom(e.to_string())), + }; + if decoded.len() != LENGTH { + return Err(serde::de::Error::custom("publickey expected to be 48 characters")) + } + + let data: Self::Value = decoded + .try_into() + .map_err(|_e| serde::de::Error::custom("hex data has unexpected length"))?; + + Ok(data) + } +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs b/bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs new file mode 100644 index 0000000000000000000000000000000000000000..4f8b19ca8892ceceaff6e039712b67ff9cb98d2f --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{ + config::{EXTRA_DATA_SIZE, FEE_RECIPIENT_SIZE, LOGS_BLOOM_SIZE, PUBKEY_SIZE, SIGNATURE_SIZE}, + types::{ + BeaconHeader, ExecutionPayloadHeader, ForkData, SigningData, SyncAggregate, SyncCommittee, + }, +}; +use byte_slice_cast::AsByteSlice; +use sp_core::H256; +use sp_std::{vec, vec::Vec}; +use ssz_rs::{ + prelude::{List, Vector}, + Bitvector, Deserialize, DeserializeError, SimpleSerialize, SimpleSerializeError, Sized, U256, +}; +use ssz_rs_derive::SimpleSerialize as SimpleSerializeDerive; + +#[derive(Default, SimpleSerializeDerive, Clone, Debug)] +pub struct SSZBeaconBlockHeader { + pub slot: u64, + pub proposer_index: u64, + pub parent_root: [u8; 32], + pub state_root: [u8; 32], + pub body_root: [u8; 32], +} + +impl From for SSZBeaconBlockHeader { + fn from(beacon_header: BeaconHeader) -> Self { + SSZBeaconBlockHeader { + slot: beacon_header.slot, + proposer_index: beacon_header.proposer_index, + parent_root: beacon_header.parent_root.to_fixed_bytes(), + state_root: beacon_header.state_root.to_fixed_bytes(), + body_root: beacon_header.body_root.to_fixed_bytes(), + } + } +} + +#[derive(Default, SimpleSerializeDerive, Clone)] +pub struct SSZSyncCommittee { + pub pubkeys: Vector, COMMITTEE_SIZE>, + pub aggregate_pubkey: Vector, +} + +impl From> + for SSZSyncCommittee +{ + fn from(sync_committee: SyncCommittee) -> Self { + let mut pubkeys_vec = Vec::new(); + + for pubkey in sync_committee.pubkeys.iter() { + // The only thing that can go wrong in the conversion from vec to Vector (ssz type) is + // that the Vector size is 0, or that the given data to create the Vector from does not + // match the expected size N. Because these sizes are statically checked (i.e. + // PublicKey's size is 48, and const PUBKEY_SIZE is 48, it is impossible for "try_from" + // to return an error condition. + let conv_pubkey = Vector::::try_from(pubkey.0.to_vec()) + .expect("checked statically; qed"); + + pubkeys_vec.push(conv_pubkey); + } + + let pubkeys = Vector::, { COMMITTEE_SIZE }>::try_from(pubkeys_vec) + .expect("checked statically; qed"); + + let aggregate_pubkey = + Vector::::try_from(sync_committee.aggregate_pubkey.0.to_vec()) + .expect("checked statically; qed"); + + SSZSyncCommittee { pubkeys, aggregate_pubkey } + } +} + +#[derive(Default, Debug, SimpleSerializeDerive, Clone)] +pub struct SSZSyncAggregate { + pub sync_committee_bits: Bitvector, + pub sync_committee_signature: Vector, +} + +impl + From> for SSZSyncAggregate +{ + fn from(sync_aggregate: SyncAggregate) -> Self { + SSZSyncAggregate { + sync_committee_bits: Bitvector::::deserialize( + &sync_aggregate.sync_committee_bits, + ) + .expect("checked statically; qed"), + sync_committee_signature: Vector::::try_from( + sync_aggregate.sync_committee_signature.0.to_vec(), + ) + .expect("checked statically; qed"), + } + } +} + +#[derive(Default, SimpleSerializeDerive, Clone)] +pub struct SSZForkData { + pub current_version: [u8; 4], + pub genesis_validators_root: [u8; 32], +} + +impl From for SSZForkData { + fn from(fork_data: ForkData) -> Self { + SSZForkData { + current_version: fork_data.current_version, + genesis_validators_root: fork_data.genesis_validators_root, + } + } +} + +#[derive(Default, SimpleSerializeDerive, Clone)] +pub struct SSZSigningData { + pub object_root: [u8; 32], + pub domain: [u8; 32], +} + +impl From for SSZSigningData { + fn from(signing_data: SigningData) -> Self { + SSZSigningData { + object_root: signing_data.object_root.into(), + domain: signing_data.domain.into(), + } + } +} + +#[derive(Default, SimpleSerializeDerive, Clone, Debug)] +pub struct SSZExecutionPayloadHeader { + pub parent_hash: [u8; 32], + pub fee_recipient: Vector, + pub state_root: [u8; 32], + pub receipts_root: [u8; 32], + pub logs_bloom: Vector, + pub prev_randao: [u8; 32], + pub block_number: u64, + pub gas_limit: u64, + pub gas_used: u64, + pub timestamp: u64, + pub extra_data: List, + pub base_fee_per_gas: U256, + pub block_hash: [u8; 32], + pub transactions_root: [u8; 32], + pub withdrawals_root: [u8; 32], +} + +impl TryFrom for SSZExecutionPayloadHeader { + type Error = SimpleSerializeError; + + fn try_from(payload: ExecutionPayloadHeader) -> Result { + Ok(SSZExecutionPayloadHeader { + parent_hash: payload.parent_hash.to_fixed_bytes(), + fee_recipient: Vector::::try_from( + payload.fee_recipient.to_fixed_bytes().to_vec(), + ) + .expect("checked statically; qed"), + state_root: payload.state_root.to_fixed_bytes(), + receipts_root: payload.receipts_root.to_fixed_bytes(), + // Logs bloom bytes size is not constrained, so here we do need to check the try_from + // error + logs_bloom: Vector::::try_from(payload.logs_bloom) + .map_err(|(_, err)| err)?, + prev_randao: payload.prev_randao.to_fixed_bytes(), + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + // Extra data bytes size is not constrained, so here we do need to check the try_from + // error + extra_data: List::::try_from(payload.extra_data) + .map_err(|(_, err)| err)?, + base_fee_per_gas: U256::from_bytes_le( + payload + .base_fee_per_gas + .as_byte_slice() + .try_into() + .expect("checked in prep; qed"), + ), + block_hash: payload.block_hash.to_fixed_bytes(), + transactions_root: payload.transactions_root.to_fixed_bytes(), + withdrawals_root: payload.withdrawals_root.to_fixed_bytes(), + }) + } +} + +pub fn hash_tree_root(mut object: T) -> Result { + match object.hash_tree_root() { + Ok(node) => { + let fixed_bytes: [u8; 32] = + node.as_ref().try_into().expect("Node is a newtype over [u8; 32]; qed"); + Ok(fixed_bytes.into()) + }, + Err(err) => Err(err.into()), + } +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/types.rs b/bridges/snowbridge/parachain/primitives/beacon/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..f893551d9d1720bf12a32982c6783c457dbc92ba --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/types.rs @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use scale_info::TypeInfo; +use sp_core::{H160, H256, U256}; +use sp_runtime::RuntimeDebug; +use sp_std::{boxed::Box, prelude::*}; + +use crate::config::{PUBKEY_SIZE, SIGNATURE_SIZE}; + +#[cfg(feature = "std")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[cfg(feature = "std")] +use crate::serde_utils::HexVisitor; + +use crate::ssz::{ + hash_tree_root, SSZBeaconBlockHeader, SSZExecutionPayloadHeader, SSZForkData, SSZSigningData, + SSZSyncAggregate, SSZSyncCommittee, +}; +use ssz_rs::SimpleSerializeError; + +pub use crate::bits::decompress_sync_committee_bits; + +use crate::bls::{prepare_g1_pubkeys, prepare_milagro_pubkey, BlsError}; +use milagro_bls::PublicKey as PublicKeyPrepared; + +pub type ValidatorIndex = u64; +pub type ForkVersion = [u8; 4]; + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ForkVersions { + pub genesis: Fork, + pub altair: Fork, + pub bellatrix: Fork, + pub capella: Fork, +} + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Fork { + pub version: [u8; 4], + pub epoch: u64, +} + +#[derive(Copy, Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct PublicKey(pub [u8; PUBKEY_SIZE]); + +impl Default for PublicKey { + fn default() -> Self { + PublicKey([0u8; PUBKEY_SIZE]) + } +} + +impl From<[u8; PUBKEY_SIZE]> for PublicKey { + fn from(v: [u8; PUBKEY_SIZE]) -> Self { + Self(v) + } +} + +impl MaxEncodedLen for PublicKey { + fn max_encoded_len() -> usize { + PUBKEY_SIZE + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(HexVisitor::()).map(|v| v.into()) + } +} + +#[cfg(feature = "std")] +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(&self.0) + } +} + +#[derive(Copy, Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Signature(pub [u8; SIGNATURE_SIZE]); + +impl Default for Signature { + fn default() -> Self { + Signature([0u8; SIGNATURE_SIZE]) + } +} + +impl From<[u8; SIGNATURE_SIZE]> for Signature { + fn from(v: [u8; SIGNATURE_SIZE]) -> Self { + Self(v) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(HexVisitor::()).map(|v| v.into()) + } +} + +#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct ExecutionHeaderState { + pub beacon_block_root: H256, + pub beacon_slot: u64, + pub block_hash: H256, + pub block_number: u64, +} + +#[derive(Copy, Clone, Default, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct FinalizedHeaderState { + pub beacon_block_root: H256, + pub beacon_slot: u64, +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)] +pub struct ForkData { + // 1 or 0 bit, indicates whether a sync committee participated in a vote + pub current_version: [u8; 4], + pub genesis_validators_root: [u8; 32], +} + +impl ForkData { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::(self.clone().into()) + } +} + +#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)] +pub struct SigningData { + pub object_root: H256, + pub domain: H256, +} + +impl SigningData { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::(self.clone().into()) + } +} + +/// Sync committee as it is stored in the runtime storage. +#[derive( + Encode, Decode, PartialEqNoBound, CloneNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr( + feature = "std", + derive(Serialize, Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +#[codec(mel_bound())] +pub struct SyncCommittee { + #[cfg_attr(feature = "std", serde(with = "crate::serde_utils::arrays"))] + pub pubkeys: [PublicKey; COMMITTEE_SIZE], + pub aggregate_pubkey: PublicKey, +} + +impl Default for SyncCommittee { + fn default() -> Self { + SyncCommittee { + pubkeys: [Default::default(); COMMITTEE_SIZE], + aggregate_pubkey: Default::default(), + } + } +} + +impl SyncCommittee { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::>(self.clone().into()) + } +} + +/// Prepared G1 public key of sync committee as it is stored in the runtime storage. +#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct SyncCommitteePrepared { + pub root: H256, + pub pubkeys: Box<[PublicKeyPrepared; COMMITTEE_SIZE]>, + pub aggregate_pubkey: PublicKeyPrepared, +} + +impl Default for SyncCommitteePrepared { + fn default() -> Self { + SyncCommitteePrepared { + root: H256::default(), + pubkeys: Box::new([PublicKeyPrepared::default(); COMMITTEE_SIZE]), + aggregate_pubkey: PublicKeyPrepared::default(), + } + } +} + +impl TryFrom<&SyncCommittee> + for SyncCommitteePrepared +{ + type Error = BlsError; + + fn try_from(sync_committee: &SyncCommittee) -> Result { + let g1_pubkeys = prepare_g1_pubkeys(&sync_committee.pubkeys)?; + let sync_committee_root = sync_committee.hash_tree_root().expect("checked statically; qed"); + + Ok(SyncCommitteePrepared:: { + pubkeys: g1_pubkeys.try_into().expect("checked statically; qed"), + aggregate_pubkey: prepare_milagro_pubkey(&sync_committee.aggregate_pubkey)?, + root: sync_committee_root, + }) + } +} + +/// Beacon block header as it is stored in the runtime storage. The block root is the +/// Merkleization of a BeaconHeader. +#[derive( + Copy, Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BeaconHeader { + // The slot for which this block is created. Must be greater than the slot of the block defined + // by parent root. + pub slot: u64, + // The index of the validator that proposed the block. + pub proposer_index: ValidatorIndex, + // The block root of the parent block, forming a block chain. + pub parent_root: H256, + // The hash root of the post state of running the state transition through this block. + pub state_root: H256, + // The hash root of the beacon block body + pub body_root: H256, +} + +impl BeaconHeader { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::((*self).into()) + } +} + +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[cfg_attr( + feature = "std", + derive(Deserialize), + serde( + try_from = "IntermediateSyncAggregate", + deny_unknown_fields, + bound(serialize = ""), + bound(deserialize = "") + ) +)] +#[codec(mel_bound())] +pub struct SyncAggregate { + pub sync_committee_bits: [u8; COMMITTEE_BITS_SIZE], + pub sync_committee_signature: Signature, +} + +impl Default + for SyncAggregate +{ + fn default() -> Self { + SyncAggregate { + sync_committee_bits: [0; COMMITTEE_BITS_SIZE], + sync_committee_signature: Default::default(), + } + } +} + +impl + SyncAggregate +{ + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::>(self.clone().into()) + } +} + +/// Serde deserialization helper for SyncAggregate +#[cfg(feature = "std")] +#[derive(Deserialize)] +struct IntermediateSyncAggregate { + #[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))] + pub sync_committee_bits: Vec, + pub sync_committee_signature: Signature, +} + +#[cfg(feature = "std")] +impl + TryFrom for SyncAggregate +{ + type Error = String; + + fn try_from(other: IntermediateSyncAggregate) -> Result { + Ok(Self { + sync_committee_bits: other + .sync_committee_bits + .try_into() + .map_err(|_| "unexpected length".to_owned())?, + sync_committee_signature: other.sync_committee_signature, + }) + } +} + +/// ExecutionPayloadHeader +/// +#[derive( + Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[cfg_attr( + feature = "std", + derive(Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +#[codec(mel_bound())] +pub struct ExecutionPayloadHeader { + pub parent_hash: H256, + pub fee_recipient: H160, + pub state_root: H256, + pub receipts_root: H256, + #[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))] + pub logs_bloom: Vec, + pub prev_randao: H256, + pub block_number: u64, + pub gas_limit: u64, + pub gas_used: u64, + pub timestamp: u64, + #[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_hex_to_bytes"))] + pub extra_data: Vec, + #[cfg_attr(feature = "std", serde(deserialize_with = "crate::serde_utils::from_int_to_u256"))] + pub base_fee_per_gas: U256, + pub block_hash: H256, + pub transactions_root: H256, + pub withdrawals_root: H256, +} + +impl ExecutionPayloadHeader { + pub fn hash_tree_root(&self) -> Result { + hash_tree_root::(self.clone().try_into()?) + } +} + +#[derive( + Default, + Encode, + Decode, + CloneNoBound, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +pub struct CompactExecutionHeader { + pub parent_hash: H256, + #[codec(compact)] + pub block_number: u64, + pub state_root: H256, + pub receipts_root: H256, +} + +impl From for CompactExecutionHeader { + fn from(execution_payload: ExecutionPayloadHeader) -> Self { + Self { + parent_hash: execution_payload.parent_hash, + block_number: execution_payload.block_number, + state_root: execution_payload.state_root, + receipts_root: execution_payload.receipts_root, + } + } +} + +#[derive( + Default, + Encode, + Decode, + Copy, + Clone, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +pub struct CompactBeaconState { + #[codec(compact)] + pub slot: u64, + pub block_roots_root: H256, +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + pub fn test_hash_beacon_header1() { + let hash_root = BeaconHeader { + slot: 3, + proposer_index: 2, + parent_root: hex!("796ea53efb534eab7777809cc5ee2d84e7f25024b9d0c4d7e5bcaab657e4bdbd") + .into(), + state_root: hex!("ba3ff080912be5c9c158b2e962c1b39a91bc0615762ba6fa2ecacafa94e9ae0a") + .into(), + body_root: hex!("a18d7fcefbb74a177c959160e0ee89c23546482154e6831237710414465dcae5") + .into(), + } + .hash_tree_root(); + + assert!(hash_root.is_ok()); + assert_eq!( + hash_root.unwrap(), + hex!("7d42595818709e805dd2fa710a2d2c1f62576ef1ab7273941ac9130fb94b91f7").into() + ); + } + + #[test] + pub fn test_hash_beacon_header2() { + let hash_root = BeaconHeader { + slot: 3476424, + proposer_index: 314905, + parent_root: hex!("c069d7b49cffd2b815b0fb8007eb9ca91202ea548df6f3db60000f29b2489f28") + .into(), + state_root: hex!("444d293e4533501ee508ad608783a7d677c3c566f001313e8a02ce08adf590a3") + .into(), + body_root: hex!("6508a0241047f21ba88f05d05b15534156ab6a6f8e029a9a5423da429834e04a") + .into(), + } + .hash_tree_root(); + + assert!(hash_root.is_ok()); + assert_eq!( + hash_root.unwrap(), + hex!("0aa41166ff01e58e111ac8c42309a738ab453cf8d7285ed8477b1c484acb123e").into() + ); + } + + #[test] + pub fn test_hash_fork_data() { + let hash_root = ForkData { + current_version: hex!("83f38a34"), + genesis_validators_root: hex!( + "22370bbbb358800f5711a10ea9845284272d8493bed0348cab87b8ab1e127930" + ), + } + .hash_tree_root(); + + assert!(hash_root.is_ok()); + assert_eq!( + hash_root.unwrap(), + hex!("57c12c4246bc7152b174b51920506bf943eff9c7ffa50b9533708e9cc1f680fc").into() + ); + } + + #[test] + pub fn test_hash_signing_data() { + let hash_root = SigningData { + object_root: hex!("63654cbe64fc07853f1198c165dd3d49c54fc53bc417989bbcc66da15f850c54") + .into(), + domain: hex!("037da907d1c3a03c0091b2254e1480d9b1783476e228ab29adaaa8f133e08f7a").into(), + } + .hash_tree_root(); + + assert!(hash_root.is_ok()); + assert_eq!( + hash_root.unwrap(), + hex!("b9eb2caf2d691b183c2d57f322afe505c078cd08101324f61c3641714789a54e").into() + ); + } + + #[test] + pub fn test_hash_sync_aggregate() { + let hash_root = SyncAggregate::<512, 64>{ + sync_committee_bits: hex!("cefffffefffffff767fffbedffffeffffeeffdffffdebffffff7f7dbdf7fffdffffbffcfffdff79dfffbbfefff2ffffff7ddeff7ffffc98ff7fbfffffffffff7"), + sync_committee_signature: hex!("8af1a8577bba419fe054ee49b16ed28e081dda6d3ba41651634685e890992a0b675e20f8d9f2ec137fe9eb50e838aa6117f9f5410e2e1024c4b4f0e098e55144843ce90b7acde52fe7b94f2a1037342c951dc59f501c92acf7ed944cb6d2b5f7").into(), + }.hash_tree_root(); + + assert!(hash_root.is_ok()); + assert_eq!( + hash_root.unwrap(), + hex!("e6dcad4f60ce9ff8a587b110facbaf94721f06cd810b6d8bf6cffa641272808d").into() + ); + } + + #[test] + pub fn test_hash_execution_payload() { + let hash_root = + ExecutionPayloadHeader{ + parent_hash: hex!("eadee5ab098dde64e9fd02ae5858064bad67064070679625b09f8d82dec183f7").into(), + fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(), + state_root: hex!("564fa064c2a324c2b5978d7fdfc5d4224d4f421a45388af1ed405a399c845dff").into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), + logs_bloom: hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(), + prev_randao: hex!("6bf538bdfbdf1c96ff528726a40658a91d0bda0f1351448c4c4f3604db2a0ccf").into(), + block_number: 477434, + gas_limit: 8154925, + gas_used: 0, + timestamp: 1652816940, + extra_data: vec![], + base_fee_per_gas: U256::from(7_i16), + block_hash: hex!("cd8df91b4503adb8f2f1c7a4f60e07a1f1a2cbdfa2a95bceba581f3ff65c1968").into(), + transactions_root: hex!("7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1").into(), + withdrawals_root: hex!("28ba1834a3a7b657460ce79fa3a1d909ab8828fd557659d4d0554a9bdbc0ec30").into(), + }.hash_tree_root(); + assert!(hash_root.is_ok()); + } +} + +/// Operating modes for beacon client +#[derive(Encode, Decode, Copy, Clone, PartialEq, RuntimeDebug, TypeInfo)] +pub enum Mode { + Active, + Blocked, +} diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/updates.rs b/bridges/snowbridge/parachain/primitives/beacon/src/updates.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a78b4f1e2d3de4af23c27c7e4fc111a79e22dc6 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/beacon/src/updates.rs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::{Decode, Encode}; +use frame_support::{CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_std::prelude::*; + +use crate::types::{BeaconHeader, ExecutionPayloadHeader, SyncAggregate, SyncCommittee}; + +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[cfg_attr( + feature = "std", + derive(serde::Serialize, serde::Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +pub struct CheckpointUpdate { + pub header: BeaconHeader, + pub current_sync_committee: SyncCommittee, + pub current_sync_committee_branch: Vec, + pub validators_root: H256, + pub block_roots_root: H256, + pub block_roots_branch: Vec, +} + +impl Default for CheckpointUpdate { + fn default() -> Self { + CheckpointUpdate { + header: Default::default(), + current_sync_committee: Default::default(), + current_sync_committee_branch: Default::default(), + validators_root: Default::default(), + block_roots_root: Default::default(), + block_roots_branch: Default::default(), + } + } +} + +#[derive( + Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[cfg_attr( + feature = "std", + derive(serde::Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +pub struct Update { + /// A recent header attesting to the finalized header, using its `state_root`. + pub attested_header: BeaconHeader, + /// The signing data that the sync committee produced for this attested header, including + /// who participated in the vote and the resulting signature. + pub sync_aggregate: SyncAggregate, + /// The slot at which the sync aggregate can be found, typically attested_header.slot + 1, if + /// the next slot block was not missed. + pub signature_slot: u64, + /// The next sync committee for the next sync committee period, if present. + pub next_sync_committee_update: Option>, + /// The latest finalized header. + pub finalized_header: BeaconHeader, + /// The merkle proof testifying to the finalized header, using the `attested_header.state_root` + /// as tree root. + pub finality_branch: Vec, + /// The finalized_header's `block_roots` root in the beacon state, used for ancestry proofs. + pub block_roots_root: H256, + /// The merkle path to prove the `block_roots_root` value. + pub block_roots_branch: Vec, +} + +#[derive( + Default, Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[cfg_attr( + feature = "std", + derive(serde::Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +pub struct NextSyncCommitteeUpdate { + pub next_sync_committee: SyncCommittee, + pub next_sync_committee_branch: Vec, +} + +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[cfg_attr( + feature = "std", + derive(serde::Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +pub struct ExecutionHeaderUpdate { + /// Header for the beacon block containing the execution payload + pub header: BeaconHeader, + /// Proof that `header` is an ancestor of a finalized header + pub ancestry_proof: Option, + /// Execution header to be imported + pub execution_header: ExecutionPayloadHeader, + /// Merkle proof that execution payload is contained within `header` + pub execution_branch: Vec, +} + +#[derive(Encode, Decode, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo)] +#[cfg_attr( + feature = "std", + derive(serde::Deserialize), + serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = "")) +)] +pub struct AncestryProof { + /// Merkle proof that `header` is an ancestor of `finalized_header` + pub header_branch: Vec, + /// Root of a finalized block that has already been imported into the light client + pub finalized_block_root: H256, +} diff --git a/bridges/snowbridge/parachain/primitives/core/Cargo.toml b/bridges/snowbridge/parachain/primitives/core/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..706c508363dec214318c7cd32547723f831846cd --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "snowbridge-core" +description = "Snowbridge Core" +version = "0.1.1" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +serde = { version = "1.0.195", optional = true, features = ["alloc", "derive"], 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"] } +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 } + +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 = "../../primitives/beacon", default-features = false } + +ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false } + +[dev-dependencies] +hex = { version = "0.4.3" } + +[features] +default = ["std"] +std = [ + "codec/std", + "ethabi/std", + "frame-support/std", + "frame-system/std", + "polkadot-parachain-primitives/std", + "scale-info/std", + "serde/std", + "snowbridge-beacon-primitives/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm/std", +] +serde = ["dep:serde", "scale-info/serde"] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", +] diff --git a/bridges/snowbridge/parachain/primitives/core/src/inbound.rs b/bridges/snowbridge/parachain/primitives/core/src/inbound.rs new file mode 100644 index 0000000000000000000000000000000000000000..4b04470ad02615d86f1a1c530f3cbed809649328 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/inbound.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Types for representing inbound messages + +use codec::{Decode, Encode}; +use frame_support::PalletError; +use scale_info::TypeInfo; +use sp_core::{H160, H256}; +use sp_runtime::RuntimeDebug; +use sp_std::vec::Vec; + +/// A trait for verifying inbound messages from Ethereum. +pub trait Verifier { + fn verify(event: &Log, proof: &Proof) -> Result<(), VerificationError>; +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, PalletError, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub enum VerificationError { + /// Execution header is missing + HeaderNotFound, + /// Event log was not found in the verified transaction receipt + LogNotFound, + /// Event log has an invalid format + InvalidLog, + /// Unable to verify the transaction receipt with the provided proof + InvalidProof, +} + +pub type MessageNonce = u64; + +/// A bridge message from the Gateway contract on Ethereum +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Message { + /// Event log emitted by Gateway contract + pub event_log: Log, + /// Inclusion proof for a transaction receipt containing the event log + pub proof: Proof, +} + +const MAX_TOPICS: usize = 4; + +#[derive(Clone, RuntimeDebug)] +pub enum LogValidationError { + TooManyTopics, +} + +/// Event log +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Log { + pub address: H160, + pub topics: Vec, + pub data: Vec, +} + +impl Log { + pub fn validate(&self) -> Result<(), LogValidationError> { + if self.topics.len() > MAX_TOPICS { + return Err(LogValidationError::TooManyTopics) + } + Ok(()) + } +} + +/// Inclusion proof for a transaction receipt +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct Proof { + // The block hash of the block in which the receipt was included. + pub block_hash: H256, + // The index of the transaction (and receipt) within the block. + pub tx_index: u32, + // Proof keys and values (receipts tree) + pub data: (Vec>, Vec>), +} diff --git a/bridges/snowbridge/parachain/primitives/core/src/lib.rs b/bridges/snowbridge/parachain/primitives/core/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..ecbc3bb365fce14d233ae17ca36cf690c135af13 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/lib.rs @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Core +//! +//! Common traits and types +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod tests; + +pub mod inbound; +pub mod operating_mode; +pub mod outbound; +pub mod pricing; +pub mod ringbuffer; + +pub use polkadot_parachain_primitives::primitives::{ + Id as ParaId, IsSystem, Sibling as SiblingParaId, +}; +pub use ringbuffer::{RingBufferMap, RingBufferMapImpl}; +pub use sp_core::U256; + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::Contains; +use hex_literal::hex; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_io::hashing::keccak_256; +use sp_runtime::{traits::AccountIdConversion, RuntimeDebug}; +use sp_std::prelude::*; +use xcm::prelude::{ + Junction::Parachain, + Junctions::{Here, X1}, + MultiLocation, +}; +use xcm_builder::{DescribeAllTerminal, DescribeFamily, DescribeLocation, HashedDescription}; + +/// The ID of an agent contract +pub type AgentId = H256; +pub use operating_mode::BasicOperatingMode; + +pub use pricing::{PricingParameters, Rewards}; + +pub fn sibling_sovereign_account(para_id: ParaId) -> T::AccountId +where + T: frame_system::Config, +{ + SiblingParaId::from(para_id).into_account_truncating() +} + +pub fn sibling_sovereign_account_raw(para_id: ParaId) -> [u8; 32] { + SiblingParaId::from(para_id).into_account_truncating() +} + +pub struct AllowSiblingsOnly; +impl Contains for AllowSiblingsOnly { + fn contains(location: &MultiLocation) -> bool { + matches!(location, MultiLocation { parents: 1, interior: X1(Parachain(_)) }) + } +} + +pub fn gwei(x: u128) -> U256 { + U256::from(1_000_000_000u128).saturating_mul(x.into()) +} + +pub fn meth(x: u128) -> U256 { + U256::from(1_000_000_000_000_000u128).saturating_mul(x.into()) +} + +pub fn eth(x: u128) -> U256 { + U256::from(1_000_000_000_000_000_000u128).saturating_mul(x.into()) +} + +pub const ROC: u128 = 1_000_000_000_000; + +/// Identifier for a message channel +#[derive( + Clone, Copy, Encode, Decode, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo, +)] +pub struct ChannelId([u8; 32]); + +/// Deterministically derive a ChannelId for a sibling parachain +/// Generator: keccak256("para" + big_endian_bytes(para_id)) +/// +/// The equivalent generator on the Solidity side is in +/// contracts/src/Types.sol:into(). +fn derive_channel_id_for_sibling(para_id: ParaId) -> ChannelId { + let para_id: u32 = para_id.into(); + let para_id_bytes: [u8; 4] = para_id.to_be_bytes(); + let prefix: [u8; 4] = *b"para"; + let preimage: Vec = prefix.into_iter().chain(para_id_bytes).collect(); + keccak_256(&preimage).into() +} + +impl ChannelId { + pub const fn new(id: [u8; 32]) -> Self { + ChannelId(id) + } +} + +impl From for ChannelId { + fn from(value: ParaId) -> Self { + derive_channel_id_for_sibling(value) + } +} + +impl From<[u8; 32]> for ChannelId { + fn from(value: [u8; 32]) -> Self { + ChannelId(value) + } +} + +impl From for [u8; 32] { + fn from(value: ChannelId) -> Self { + value.0 + } +} + +impl<'a> From<&'a [u8; 32]> for ChannelId { + fn from(value: &'a [u8; 32]) -> Self { + ChannelId(*value) + } +} + +impl From for ChannelId { + fn from(value: H256) -> Self { + ChannelId(value.into()) + } +} + +impl AsRef<[u8]> for ChannelId { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct Channel { + /// ID of the agent contract deployed on Ethereum + pub agent_id: AgentId, + /// ID of the parachain who will receive or send messages using this channel + pub para_id: ParaId, +} + +pub trait StaticLookup { + /// Type to lookup from. + type Source; + /// Type to lookup into. + type Target; + /// Attempt a lookup. + fn lookup(s: Self::Source) -> Option; +} + +/// Channel for high-priority governance commands +pub const PRIMARY_GOVERNANCE_CHANNEL: ChannelId = + ChannelId::new(hex!("0000000000000000000000000000000000000000000000000000000000000001")); + +/// Channel for lower-priority governance commands +pub const SECONDARY_GOVERNANCE_CHANNEL: ChannelId = + ChannelId::new(hex!("0000000000000000000000000000000000000000000000000000000000000002")); + +pub struct DescribeHere; +impl DescribeLocation for DescribeHere { + fn describe_location(l: &MultiLocation) -> Option> { + match (l.parents, l.interior) { + (0, Here) => Some(Vec::::new().encode()), + _ => None, + } + } +} + +/// Creates an AgentId from a MultiLocation. An AgentId is a unique mapping to a Agent contract on +/// Ethereum which acts as the sovereign account for the MultiLocation. +pub type AgentIdOf = HashedDescription)>; diff --git a/bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs b/bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs new file mode 100644 index 0000000000000000000000000000000000000000..9894e587ef5e7ff31d19a0b5593c3c78cb7c1f99 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs @@ -0,0 +1,25 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +/// Basic operating modes for a bridges module (Normal/Halted). +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum BasicOperatingMode { + /// Normal mode, when all operations are allowed. + Normal, + /// The pallet is halted. All non-governance operations are disabled. + Halted, +} + +impl Default for BasicOperatingMode { + fn default() -> Self { + Self::Normal + } +} + +impl BasicOperatingMode { + pub fn is_halted(&self) -> bool { + *self == BasicOperatingMode::Halted + } +} diff --git a/bridges/snowbridge/parachain/primitives/core/src/outbound.rs b/bridges/snowbridge/parachain/primitives/core/src/outbound.rs new file mode 100644 index 0000000000000000000000000000000000000000..bce123878d3a456fc8b50f841cd64516a5d58dee --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/outbound.rs @@ -0,0 +1,413 @@ +use codec::{Decode, Encode}; +use frame_support::PalletError; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_core::{RuntimeDebug, H256}; +pub use v1::{AgentExecuteCommand, Command, Initializer, Message, OperatingMode, QueuedMessage}; + +/// Enqueued outbound messages need to be versioned to prevent data corruption +/// or loss after forkless runtime upgrades +#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub enum VersionedQueuedMessage { + V1(QueuedMessage), +} + +impl TryFrom for QueuedMessage { + type Error = (); + fn try_from(x: VersionedQueuedMessage) -> Result { + use VersionedQueuedMessage::*; + match x { + V1(x) => Ok(x), + } + } +} + +impl> From for VersionedQueuedMessage { + fn from(x: T) -> Self { + VersionedQueuedMessage::V1(x.into()) + } +} + +mod v1 { + use crate::{pricing::UD60x18, ChannelId}; + use codec::{Decode, Encode}; + use ethabi::Token; + use scale_info::TypeInfo; + use sp_core::{RuntimeDebug, H160, H256, U256}; + use sp_std::{borrow::ToOwned, vec, vec::Vec}; + + /// A message which can be accepted by implementations of `/[`SendMessage`\]` + #[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)] + #[cfg_attr(feature = "std", derive(PartialEq))] + pub struct Message { + /// ID for this message. One will be automatically generated if not provided. + /// + /// When this message is created from an XCM message, the ID should be extracted + /// from the `SetTopic` instruction. + /// + /// The ID plays no role in bridge consensus, and is purely meant for message tracing. + pub id: Option, + /// The message channel ID + pub channel_id: ChannelId, + /// The stable ID for a receiving gateway contract + pub command: Command, + } + + /// The operating mode of Channels and Gateway contract on Ethereum. + #[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] + pub enum OperatingMode { + /// Normal operations. Allow sending and receiving messages. + Normal, + /// Reject outbound messages. This allows receiving governance messages but does now allow + /// enqueuing of new messages from the Ethereum side. This can be used to close off an + /// deprecated channel or pause the bridge for upgrade operations. + RejectingOutboundMessages, + } + + /// A command which is executable by the Gateway contract on Ethereum + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(PartialEq))] + pub enum Command { + /// Execute a sub-command within an agent for a consensus system in Polkadot + AgentExecute { + /// The ID of the agent + agent_id: H256, + /// The sub-command to be executed + command: AgentExecuteCommand, + }, + /// Upgrade the Gateway contract + Upgrade { + /// Address of the new implementation contract + impl_address: H160, + /// Codehash of the implementation contract + impl_code_hash: H256, + /// Optionally invoke an initializer in the implementation contract + initializer: Option, + }, + /// Create an agent representing a consensus system on Polkadot + CreateAgent { + /// The ID of the agent, derived from the `MultiLocation` of the consensus system on + /// Polkadot + agent_id: H256, + }, + /// Create bidirectional messaging channel to a parachain + CreateChannel { + /// The ID of the channel + channel_id: ChannelId, + /// The agent ID of the parachain + agent_id: H256, + /// Initial operating mode + mode: OperatingMode, + }, + /// Update the configuration of a channel + UpdateChannel { + /// The ID of the channel + channel_id: ChannelId, + /// The new operating mode + mode: OperatingMode, + }, + /// Set the global operating mode of the Gateway contract + SetOperatingMode { + /// The new operating mode + mode: OperatingMode, + }, + /// Transfer ether from an agent contract to a recipient account + TransferNativeFromAgent { + /// The agent ID + agent_id: H256, + /// The recipient of the ether + recipient: H160, + /// The amount to transfer + amount: u128, + }, + /// Set token fees of the Gateway contract + SetTokenTransferFees { + /// The fee(DOT) for the cost of creating asset on AssetHub + create_asset_xcm: u128, + /// The fee(DOT) for the cost of sending asset on AssetHub + transfer_asset_xcm: u128, + /// The fee(Ether) for register token to discourage spamming + register_token: U256, + }, + /// Set pricing parameters + SetPricingParameters { + // ETH/DOT exchange rate + exchange_rate: UD60x18, + // Cost of delivering a message from Ethereum to BridgeHub, in ROC/KSM/DOT + delivery_cost: u128, + }, + } + + impl Command { + /// Compute the enum variant index + pub fn index(&self) -> u8 { + match self { + Command::AgentExecute { .. } => 0, + Command::Upgrade { .. } => 1, + Command::CreateAgent { .. } => 2, + Command::CreateChannel { .. } => 3, + Command::UpdateChannel { .. } => 4, + Command::SetOperatingMode { .. } => 5, + Command::TransferNativeFromAgent { .. } => 6, + Command::SetTokenTransferFees { .. } => 7, + Command::SetPricingParameters { .. } => 8, + } + } + + /// ABI-encode the Command. + pub fn abi_encode(&self) -> Vec { + match self { + Command::AgentExecute { agent_id, command } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Bytes(command.abi_encode()), + ])]), + Command::Upgrade { impl_address, impl_code_hash, initializer, .. } => + ethabi::encode(&[Token::Tuple(vec![ + Token::Address(*impl_address), + Token::FixedBytes(impl_code_hash.as_bytes().to_owned()), + initializer + .clone() + .map_or(Token::Bytes(vec![]), |i| Token::Bytes(i.params)), + ])]), + Command::CreateAgent { agent_id } => + ethabi::encode(&[Token::Tuple(vec![Token::FixedBytes( + agent_id.as_bytes().to_owned(), + )])]), + Command::CreateChannel { channel_id, agent_id, mode } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(channel_id.as_ref().to_owned()), + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Uint(U256::from((*mode) as u64)), + ])]), + Command::UpdateChannel { channel_id, mode } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(channel_id.as_ref().to_owned()), + Token::Uint(U256::from((*mode) as u64)), + ])]), + Command::SetOperatingMode { mode } => + ethabi::encode(&[Token::Tuple(vec![Token::Uint(U256::from((*mode) as u64))])]), + Command::TransferNativeFromAgent { agent_id, recipient, amount } => + ethabi::encode(&[Token::Tuple(vec![ + Token::FixedBytes(agent_id.as_bytes().to_owned()), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])]), + Command::SetTokenTransferFees { + create_asset_xcm, + transfer_asset_xcm, + register_token, + } => ethabi::encode(&[Token::Tuple(vec![ + Token::Uint(U256::from(*create_asset_xcm)), + Token::Uint(U256::from(*transfer_asset_xcm)), + Token::Uint(*register_token), + ])]), + Command::SetPricingParameters { exchange_rate, delivery_cost } => + ethabi::encode(&[Token::Tuple(vec![ + Token::Uint(exchange_rate.clone().into_inner()), + Token::Uint(U256::from(*delivery_cost)), + ])]), + } + } + } + + /// Representation of a call to the initializer of an implementation contract. + /// The initializer has the following ABI signature: `initialize(bytes)`. + #[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] + pub struct Initializer { + /// ABI-encoded params of type `bytes` to pass to the initializer + pub params: Vec, + /// The initializer is allowed to consume this much gas at most. + pub maximum_required_gas: u64, + } + + /// A Sub-command executable within an agent + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(PartialEq))] + pub enum AgentExecuteCommand { + /// Transfer ERC20 tokens + TransferToken { + /// Address of the ERC20 token + token: H160, + /// The recipient of the tokens + recipient: H160, + /// The amount of tokens to transfer + amount: u128, + }, + } + + impl AgentExecuteCommand { + fn index(&self) -> u8 { + match self { + AgentExecuteCommand::TransferToken { .. } => 0, + } + } + + /// ABI-encode the sub-command + pub fn abi_encode(&self) -> Vec { + match self { + AgentExecuteCommand::TransferToken { token, recipient, amount } => + ethabi::encode(&[ + Token::Uint(self.index().into()), + Token::Bytes(ethabi::encode(&[ + Token::Address(*token), + Token::Address(*recipient), + Token::Uint(U256::from(*amount)), + ])), + ]), + } + } + } + + /// Message which is awaiting processing in the MessageQueue pallet + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + #[cfg_attr(feature = "std", derive(PartialEq))] + pub struct QueuedMessage { + /// Message ID + pub id: H256, + /// Channel ID + pub channel_id: ChannelId, + /// Command to execute in the Gateway contract + pub command: Command, + } +} + +#[cfg_attr(feature = "std", derive(PartialEq, Debug))] +/// Fee for delivering message +pub struct Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + /// Fee to cover cost of processing the message locally + pub local: Balance, + /// Fee to cover cost processing the message remotely + pub remote: Balance, +} + +impl Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn total(&self) -> Balance { + self.local.saturating_add(self.remote) + } +} + +impl From<(Balance, Balance)> for Fee +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + fn from((local, remote): (Balance, Balance)) -> Self { + Self { local, remote } + } +} + +/// A trait for sending messages to Ethereum +pub trait SendMessage: SendMessageFeeProvider { + type Ticket: Clone + Encode + Decode; + + /// Validate an outbound message and return a tuple: + /// 1. Ticket for submitting the message + /// 2. Delivery fee + fn validate( + message: &Message, + ) -> Result<(Self::Ticket, Fee<::Balance>), SendError>; + + /// Submit the message ticket for eventual delivery to Ethereum + fn deliver(ticket: Self::Ticket) -> Result; +} + +pub trait Ticket: Encode + Decode + Clone { + fn message_id(&self) -> H256; +} + +/// A trait for getting the local costs associated with sending a message. +pub trait SendMessageFeeProvider { + type Balance: BaseArithmetic + Unsigned + Copy; + + /// The local component of the message processing fees in native currency + fn local_fee() -> Self::Balance; +} + +/// Reasons why sending to Ethereum could not be initiated +#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, PalletError, TypeInfo)] +pub enum SendError { + /// Message is too large to be safely executed on Ethereum + MessageTooLarge, + /// The bridge has been halted for maintenance + Halted, + /// Invalid Channel + InvalidChannel, +} + +pub trait GasMeter { + /// All the gas used for submitting a message to Ethereum, minus the cost of dispatching + /// the command within the message + const MAXIMUM_BASE_GAS: u64; + + fn maximum_gas_used_at_most(command: &Command) -> u64 { + Self::MAXIMUM_BASE_GAS + Self::maximum_dispatch_gas_used_at_most(command) + } + + /// Measures the maximum amount of gas a command payload will require to dispatch, AFTER + /// validation & verification. + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64; +} + +/// A meter that assigns a constant amount of gas for the execution of a command +/// +/// The gas figures are extracted from this report: +/// > forge test --match-path test/Gateway.t.sol --gas-report +/// +/// A healthy buffer is added on top of these figures to account for: +/// * The EIP-150 63/64 rule +/// * Future EVM upgrades that may increase gas cost +pub struct ConstantGasMeter; + +impl GasMeter for ConstantGasMeter { + // The base transaction cost, which includes: + // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000 + // for message verification + const MAXIMUM_BASE_GAS: u64 = 185_000; + + fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 { + match command { + Command::CreateAgent { .. } => 275_000, + Command::CreateChannel { .. } => 100_000, + Command::UpdateChannel { .. } => 50_000, + Command::TransferNativeFromAgent { .. } => 60_000, + Command::SetOperatingMode { .. } => 40_000, + Command::AgentExecute { command, .. } => match command { + // Execute IERC20.transferFrom + // + // Worst-case assumptions are important: + // * No gas refund for clearing storage slot of source account in ERC20 contract + // * Assume dest account in ERC20 contract does not yet have a storage slot + // * ERC20.transferFrom possibly does other business logic besides updating balances + AgentExecuteCommand::TransferToken { .. } => 100_000, + }, + Command::Upgrade { initializer, .. } => { + let initializer_max_gas = match *initializer { + Some(Initializer { maximum_required_gas, .. }) => maximum_required_gas, + None => 0, + }; + // total maximum gas must also include the gas used for updating the proxy before + // the the initializer is called. + 50_000 + initializer_max_gas + }, + Command::SetTokenTransferFees { .. } => 60_000, + Command::SetPricingParameters { .. } => 60_000, + } + } +} + +impl GasMeter for () { + const MAXIMUM_BASE_GAS: u64 = 1; + + fn maximum_dispatch_gas_used_at_most(_: &Command) -> u64 { + 1 + } +} + +pub const ETHER_DECIMALS: u8 = 18; diff --git a/bridges/snowbridge/parachain/primitives/core/src/pricing.rs b/bridges/snowbridge/parachain/primitives/core/src/pricing.rs new file mode 100644 index 0000000000000000000000000000000000000000..33aeda6d15c4701ce4594b1e783d7aa69f84cc8e --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/pricing.rs @@ -0,0 +1,67 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned, Zero}; +use sp_core::U256; +use sp_runtime::{FixedU128, RuntimeDebug}; +use sp_std::prelude::*; + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct PricingParameters { + /// ETH/DOT exchange rate + pub exchange_rate: FixedU128, + /// Relayer rewards + pub rewards: Rewards, + /// Ether (wei) fee per gas unit + pub fee_per_gas: U256, +} + +#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct Rewards { + /// Local reward in DOT + pub local: Balance, + /// Remote reward in ETH (wei) + pub remote: U256, +} + +#[derive(RuntimeDebug)] +pub struct InvalidPricingParameters; + +impl PricingParameters +where + Balance: BaseArithmetic + Unsigned + Copy, +{ + pub fn validate(&self) -> Result<(), InvalidPricingParameters> { + if self.exchange_rate == FixedU128::zero() { + return Err(InvalidPricingParameters) + } + if self.fee_per_gas == U256::zero() { + return Err(InvalidPricingParameters) + } + if self.rewards.local.is_zero() { + return Err(InvalidPricingParameters) + } + if self.rewards.remote.is_zero() { + return Err(InvalidPricingParameters) + } + Ok(()) + } +} + +/// Holder for fixed point number implemented in +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq))] +pub struct UD60x18(U256); + +impl From for UD60x18 { + fn from(value: FixedU128) -> Self { + // Both FixedU128 and UD60x18 have 18 decimal places + let inner: u128 = value.into_inner(); + UD60x18(inner.into()) + } +} + +impl UD60x18 { + pub fn into_inner(self) -> U256 { + self.0 + } +} diff --git a/bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs b/bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs new file mode 100644 index 0000000000000000000000000000000000000000..dcee20359a78ebc27fd5ce5c1479f8241d8583e0 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::FullCodec; +use core::{cmp::Ord, marker::PhantomData, ops::Add}; +use frame_support::storage::{types::QueryKindTrait, StorageMap, StorageValue}; +use sp_core::{Get, GetDefault}; +use sp_runtime::traits::{One, Zero}; + +/// Trait object presenting the ringbuffer interface. +pub trait RingBufferMap +where + Key: FullCodec, + Value: FullCodec, + QueryKind: QueryKindTrait, +{ + /// Insert a map entry. + fn insert(k: Key, v: Value); + + /// Check if map contains a key + fn contains_key(k: Key) -> bool; + + /// Get the value of the key + fn get(k: Key) -> QueryKind::Query; +} + +pub struct RingBufferMapImpl( + PhantomData<(Index, B, CurrentIndex, Intermediate, M, QueryKind)>, +); + +/// Ringbuffer implementation based on `RingBufferTransient` +impl + RingBufferMap + for RingBufferMapImpl +where + Key: FullCodec + Clone, + Value: FullCodec, + Index: Ord + One + Zero + Add + Copy + FullCodec + Eq, + B: Get, + CurrentIndex: StorageValue, + Intermediate: StorageMap, + M: StorageMap, + QueryKind: QueryKindTrait, +{ + /// Insert a map entry. + fn insert(k: Key, v: Value) { + let bound = B::get(); + let mut current_index = CurrentIndex::get(); + + // Adding one here as bound denotes number of items but our index starts with zero. + if (current_index + Index::one()) >= bound { + current_index = Index::zero(); + } else { + current_index = current_index + Index::one(); + } + + // Deleting earlier entry if it exists + if Intermediate::contains_key(current_index) { + let older_key = Intermediate::get(current_index); + M::remove(older_key); + } + + Intermediate::insert(current_index, k.clone()); + CurrentIndex::set(current_index); + M::insert(k, v); + } + + /// Check if map contains a key + fn contains_key(k: Key) -> bool { + M::contains_key(k) + } + + /// Get the value associated with key + fn get(k: Key) -> M::Query { + M::get(k) + } +} diff --git a/bridges/snowbridge/parachain/primitives/core/src/tests.rs b/bridges/snowbridge/parachain/primitives/core/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..725fff1a9c941ae2e270d591aabd80fc8fa95b54 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/src/tests.rs @@ -0,0 +1,13 @@ +use crate::{ChannelId, ParaId}; +use hex_literal::hex; + +const EXPECT_CHANNEL_ID: [u8; 32] = + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539"); + +// The Solidity equivalent code is tested in Gateway.t.sol:testDeriveChannelID +#[test] +fn generate_channel_id() { + let para_id: ParaId = 1000.into(); + let channel_id: ChannelId = para_id.into(); + assert_eq!(channel_id, EXPECT_CHANNEL_ID.into()); +} diff --git a/bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale b/bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale new file mode 100644 index 0000000000000000000000000000000000000000..d5f6696ea69fffd243e7b5b8eb5cef9a7943802c Binary files /dev/null and b/bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale differ diff --git a/bridges/snowbridge/parachain/primitives/core/tests/mod.rs b/bridges/snowbridge/parachain/primitives/core/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..2da5d2df182e9411c48427ef09048b83e18aca55 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/core/tests/mod.rs @@ -0,0 +1,14 @@ +#[cfg(test)] +mod tests { + use frame_support::traits::Contains; + use snowbridge_core::AllowSiblingsOnly; + use xcm::prelude::{Junction::Parachain, Junctions::X1, MultiLocation}; + + #[test] + fn allow_siblings_predicate_only_allows_siblings() { + let sibling = MultiLocation::new(1, X1(Parachain(1000))); + let child = MultiLocation::new(0, X1(Parachain(1000))); + assert!(AllowSiblingsOnly::contains(&sibling), "Sibling returns true."); + assert!(!AllowSiblingsOnly::contains(&child), "Child returns false."); + } +} diff --git a/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml b/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..4ec2f3b8620332641758c95f2c1c685e261cba42 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml b/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..3624044f86a519a900591617d3491697deeed828 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "snowbridge-ethereum" +description = "Snowbridge Ethereum" +version = "0.1.0" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde-big-array = { version = "0.3.2", optional = true, features = ["const-generics"] } +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 } +ethereum-types = { version = "0.14.1", default-features = false, features = ["codec", "rlp", "serialize"] } +hex = { package = "rustc-hex", version = "2.1.0", default-features = false } +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 } + +ethabi = { git = "https://github.com/snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false } + +[dev-dependencies] +wasm-bindgen-test = "0.3.19" +rand = "0.8.5" +serde_json = "1.0.111" + +[features] +default = ["std"] +expensive_tests = [] +std = [ + "codec/std", + "ethabi/std", + "ethbloom/std", + "ethereum-types/std", + "hex/std", + "parity-bytes/std", + "rlp/std", + "scale-info/std", + "serde", + "serde-big-array", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/header.rs b/bridges/snowbridge/parachain/primitives/ethereum/src/header.rs new file mode 100644 index 0000000000000000000000000000000000000000..f0b51f8c79de8fa3f1b37205c38d8a8640771f0c --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/src/header.rs @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::{Decode, Encode}; +use ethbloom::Bloom as EthBloom; +use hex_literal::hex; +use parity_bytes::Bytes; +use rlp::RlpStream; +use scale_info::TypeInfo; +use sp_io::hashing::keccak_256; +use sp_runtime::RuntimeDebug; +use sp_std::{convert::TryInto, prelude::*}; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "std")] +use serde_big_array::BigArray; + +use ethereum_types::{Address, H256, H64, U256}; + +use crate::{mpt, receipt}; + +/// Complete block header id. +#[derive(Clone, Copy, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct HeaderId { + /// Header number. + pub number: u64, + /// Header hash. + pub hash: H256, +} + +const EMPTY_OMMERS_HASH: [u8; 32] = + hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); + +/// An Ethereum block header. +#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Header { + /// Parent block hash. + pub parent_hash: H256, + /// Block timestamp. + pub timestamp: u64, + /// Block number. + pub number: u64, + /// Block author. + pub author: Address, + + /// Transactions root. + pub transactions_root: H256, + /// Block ommers hash. + pub ommers_hash: H256, + /// Block extra data. + pub extra_data: Bytes, + + /// State root. + pub state_root: H256, + /// Block receipts root. + pub receipts_root: H256, + /// Block bloom. + pub logs_bloom: Bloom, + /// Gas used for contracts execution. + pub gas_used: U256, + /// Block gas limit. + pub gas_limit: U256, + + /// Block difficulty. + pub difficulty: U256, + /// Vector of post-RLP-encoded fields. + pub seal: Vec, + + // Base fee per gas (EIP-1559), only in headers from the London hardfork onwards. + pub base_fee: Option, +} + +impl Header { + /// Compute hash of this header (keccak of the RLP with seal). + pub fn compute_hash(&self) -> H256 { + keccak_256(&self.rlp(true)).into() + } + + /// Compute hash of the truncated header i.e. excluding seal. + pub fn compute_partial_hash(&self) -> H256 { + keccak_256(&self.rlp(false)).into() + } + + pub fn check_receipt_proof( + &self, + proof: &[Vec], + ) -> Option> { + match self.apply_merkle_proof(proof) { + Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)), + Some((_, _)) => None, + None => None, + } + } + + pub fn apply_merkle_proof(&self, proof: &[Vec]) -> Option<(H256, Vec)> { + let mut iter = proof.iter().rev(); + let first_bytes = match iter.next() { + Some(b) => b, + None => return None, + }; + let item_to_prove: mpt::ShortNode = rlp::decode(first_bytes).ok()?; + + let final_hash: Option<[u8; 32]> = iter.try_fold(keccak_256(first_bytes), |acc, x| { + let node: Box = x.as_slice().try_into().ok()?; + if (*node).contains_hash(acc.into()) { + return Some(keccak_256(x)) + } + None + }); + + final_hash.map(|hash| (hash.into(), item_to_prove.value)) + } + + pub fn mix_hash(&self) -> Option { + let bytes: Bytes = self.decoded_seal_field(0, 32)?; + let size = bytes.len(); + let mut mix_hash = [0u8; 32]; + for i in 0..size { + mix_hash[31 - i] = bytes[size - 1 - i]; + } + Some(mix_hash.into()) + } + + pub fn nonce(&self) -> Option { + let bytes: Bytes = self.decoded_seal_field(1, 8)?; + let size = bytes.len(); + let mut nonce = [0u8; 8]; + for i in 0..size { + nonce[7 - i] = bytes[size - 1 - i]; + } + Some(nonce.into()) + } + + pub fn has_ommers(&self) -> bool { + self.ommers_hash != EMPTY_OMMERS_HASH.into() + } + + fn decoded_seal_field(&self, index: usize, max_len: usize) -> Option { + let bytes: Bytes = rlp::decode(self.seal.get(index)?).ok()?; + if bytes.len() > max_len { + return None + } + Some(bytes) + } + + /// Returns header RLP with or without seals. + /// For EIP-1559 baseFee addition refer to: + /// + fn rlp(&self, with_seal: bool) -> Bytes { + let mut s = RlpStream::new(); + + let stream_length_without_seal = if self.base_fee.is_some() { 14 } else { 13 }; + + if with_seal { + s.begin_list(stream_length_without_seal + self.seal.len()); + } else { + s.begin_list(stream_length_without_seal); + } + + s.append(&self.parent_hash); + s.append(&self.ommers_hash); + s.append(&self.author); + s.append(&self.state_root); + s.append(&self.transactions_root); + s.append(&self.receipts_root); + s.append(&EthBloom::from(self.logs_bloom.0)); + s.append(&self.difficulty); + s.append(&self.number); + s.append(&self.gas_limit); + s.append(&self.gas_used); + s.append(&self.timestamp); + s.append(&self.extra_data); + + if with_seal { + for b in &self.seal { + s.append_raw(b, 1); + } + } + + if let Some(base_fee) = self.base_fee { + s.append(&base_fee); + } + + s.out().to_vec() + } +} + +/// Logs bloom. +#[derive(Clone, Debug, Encode, Decode, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Bloom(#[cfg_attr(feature = "std", serde(with = "BigArray"))] [u8; 256]); + +impl<'a> From<&'a [u8; 256]> for Bloom { + fn from(buffer: &'a [u8; 256]) -> Bloom { + Bloom(*buffer) + } +} + +impl PartialEq for Bloom { + fn eq(&self, other: &Bloom) -> bool { + self.0.iter().zip(other.0.iter()).all(|(l, r)| l == r) + } +} + +impl Default for Bloom { + fn default() -> Self { + Bloom([0; 256]) + } +} + +impl rlp::Decodable for Bloom { + fn decode(rlp: &rlp::Rlp) -> Result { + let v: Vec = rlp.as_val()?; + match v.len() { + 256 => { + let mut bytes = [0u8; 256]; + bytes.copy_from_slice(&v); + Ok(Self(bytes)) + }, + _ => Err(rlp::DecoderError::Custom("Expected 256 bytes")), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn bloom_decode_rlp() { + let raw_bloom = hex!( + " + b901000420000000000000000000008002000000000001000000000001000000000000000000 + 0000000000000000000000000002000000080000000000000000200000000000000000000000 + 0000080000002200000000004000100000000000000000000000000000000000000000000000 + 0000000000000004000000001000010000000000080000000000400000000000000000000000 + 0000080000004000000000020000000000020000000000000000000000000000000000000000 + 0000040000000000020000000001000000000000000000000000000010000000020000200000 + 10200000000000010000000000000000000000000000000000000010000000 + " + ); + let expected_bytes = &raw_bloom[3..]; + let bloom: Bloom = rlp::decode(&raw_bloom).unwrap(); + assert_eq!(bloom.0, expected_bytes); + } + + #[test] + fn header_compute_hash_poa() { + // PoA header + let header = Header { + parent_hash: Default::default(), + timestamp: 0, + number: 0, + author: Default::default(), + transactions_root: hex!( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) + .into(), + ommers_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + .into(), + extra_data: vec![], + state_root: hex!("eccf6b74c2bcbe115c71116a23fe963c54406010c244d9650526028ad3e32cce") + .into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + .into(), + logs_bloom: Default::default(), + gas_used: Default::default(), + gas_limit: 0x222222.into(), + difficulty: 0x20000.into(), + seal: vec![vec![0x80], { + let mut vec = vec![0xb8, 0x41]; + vec.resize(67, 0); + vec + }], + base_fee: None, + }; + assert_eq!( + header.compute_hash().as_bytes(), + hex!("9ff57c7fa155853586382022f0982b71c51fa313a0942f8c456300896643e890"), + ); + } + + #[test] + fn header_compute_hash_pow() { + // + let nonce = hex!("6935bbe7b63c4f8e").to_vec(); + let mix_hash = + hex!("be3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").to_vec(); + let header = Header { + parent_hash: hex!("bede0bddd6f32c895fc505ffe0c39d9bde58e9a5272f31a3dee448b796edcbe3") + .into(), + timestamp: 1603160977, + number: 11090290, + author: hex!("ea674fdde714fd979de3edf0f56aa9716b898ec8").into(), + transactions_root: hex!( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) + .into(), + ommers_hash: hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + .into(), + extra_data: hex!("65746865726d696e652d61736961312d33").to_vec(), + state_root: hex!("7dcb8aca872b712bad81df34a89d4efedc293566ffc3eeeb5cbcafcc703e42c9") + .into(), + receipts_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + .into(), + logs_bloom: Default::default(), + gas_used: 0.into(), + gas_limit: 0xbe8c19.into(), + difficulty: 0xbc140caa61087i64.into(), + seal: vec![rlp::encode(&mix_hash).to_vec(), rlp::encode(&nonce).to_vec()], + base_fee: None, + }; + assert_eq!( + header.compute_hash().as_bytes(), + hex!("0f9bdc91c2e0140acb873330742bda8c8181fa3add91fe7ae046251679cedef7"), + ); + } + + #[test] + fn header_pow_seal_fields_extracted_correctly() { + let nonce: H64 = hex!("6935bbe7b63c4f8e").into(); + let mix_hash: H256 = + hex!("be3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").into(); + let header = Header { + seal: vec![ + rlp::encode(&mix_hash.0.to_vec()).to_vec(), + rlp::encode(&nonce.0.to_vec()).to_vec(), + ], + ..Default::default() + }; + + assert_eq!(header.nonce().unwrap(), nonce); + assert_eq!(header.mix_hash().unwrap(), mix_hash); + } + + #[test] + fn header_pow_seal_fields_return_none_for_invalid_values() { + let nonce = hex!("696935bbe7b63c4f8e").to_vec(); + let mix_hash = + hex!("bebe3adfb0087be62b28b716e2cdf3c79329df5caa04c9eee035d35b5d52102815").to_vec(); + let mut header = Header { + seal: vec![rlp::encode(&mix_hash).to_vec(), rlp::encode(&nonce).to_vec()], + ..Default::default() + }; + assert_eq!(header.nonce(), None); + assert_eq!(header.mix_hash(), None); + + header.seal = Vec::new(); + assert_eq!(header.nonce(), None); + assert_eq!(header.mix_hash(), None); + } + + #[test] + fn header_check_receipt_proof() { + let header = Header { + receipts_root: hex!("fd5e397a84884641f53c496804f24b5276cbb8c5c9cfc2342246be8e3ce5ad02") + .into(), + ..Default::default() + }; + + // Valid proof + let proof_receipt5 = vec!( + hex!("f90131a0b5ba404eb5a6a88e56579f4d37ef9813b5ad7f86f0823ff3b407ac5a6bb465eca0398ead2655e78e03c127ce22c5830e90f18b1601ec055f938336c084feb915a9a026d322c26e46c50942c1aabde50e36df5cde572aed650ce73ea3182c6e90a02ca00600a356135f4db1db0d9842264cdff2652676f881669e91e316c0b6dd783011a0837f1deb4075336da320388c1edfffc56c448a43f4a5ba031300d32a7b509fc5a01c3ac82fd65b4aba7f9afaf604d9c82ec7e2deb573a091ae235751bc5c0c288da05d454159d9071b0f68b6e0503d290f23ac7602c1db0c569dee4605d8f5298f09a00bbed10350ec954448df795f6fd46e3faefc800ede061b3840eedc6e2b07a74da0acb02d26a3650f2064c14a435fdf1f668d8655daf455ebdf671713a7c089b3898080808080808080").to_vec(), + hex!("f901f180a00046a08d4f0bdbdc6b31903086ce323182bce6725e7d9415f7ff91ee8f4820bda0e7cd26ad5f3d2771e4b5ab788e268a14a10209f94ee918eb6c829d21d3d11c1da00d4a56d9e9a6751874fd86c7e3cb1c6ad5a848da62751325f478978a00ea966ea064b81920c8f04a8a1e21f53a8280e739fbb7b00b2ab92493ca3f610b70e8ac85a0b1040ed4c55a73178b76abb16f946ce5bebd6b93ab873c83327df54047d12c27a0de6485e9ac58dc6e2b04b4bb38f562684f0b1a2ee586cc11079e7d9a9dc40b32a0d394f4d3532c3124a65fa36e69147e04fd20453a72ee9c50660f17e13ce9df48a066501003fc3e3478efd2803cd0eded6bbe9243ca01ba754d6327071ddbcbc649a0b2684e518f325fee39fc8ea81b68f3f5c785be00d087f3bed8857ae2ee8da26ea071060a5c52042e8d7ce21092f8ecf06053beb9a0b773a6f91a30c4220aa276b2a0fc22436632574ccf6043d0986dede27ea94c9ca9a3bb5ec03ce776a4ddef24a9a05a8a1d6698c4e7d8cc3a2506cb9b12ea9a079c9c7099bc919dc804033cc556e4a0170c468b0716fd36d161f0bf05875f15756a2976de92f9efe7716320509d79c9a0182f909a90cab169f3efb62387f9cccdd61440acc4deec42f68a4f7ca58075c7a055cf0e9202ac75689b76318f1171f3a44465eddc06aae0713bfb6b34fdd27b7980").to_vec(), + hex!("f904de20b904daf904d701830652f0b9010004200000000000000000000080020000000000010000000000010000000000000000000000000000000000000000000002000000080000000000000000200000000000000000000000000008000000220000000000400010000000000000000000000000000000000000000000000000000000000000040000000010000100000000000800000000004000000000000000000000000000080000004000000000020000000000020000000000000000000000000000000000000000000004000000000002000000000100000000000000000000000000001000000002000020000010200000000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000000000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffffffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea393ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000000000000000000000000000000000000005d415f3320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078e").to_vec(), + ); + assert!(header.check_receipt_proof(&proof_receipt5).is_some()); + + // Various invalid proofs + let proof_empty: Vec> = vec![]; + let proof_missing_full_node = vec![proof_receipt5[0].clone(), proof_receipt5[2].clone()]; + let proof_missing_short_node1 = vec![proof_receipt5[0].clone(), proof_receipt5[1].clone()]; + let proof_missing_short_node2 = vec![proof_receipt5[0].clone()]; + let proof_invalid_encoding = vec![proof_receipt5[2][2..].to_vec()]; + let proof_no_full_node = vec![proof_receipt5[2].clone(), proof_receipt5[2].clone()]; + assert!(header.check_receipt_proof(&proof_empty).is_none()); + assert!(header.check_receipt_proof(&proof_missing_full_node).is_none()); + + assert_eq!( + header.check_receipt_proof(&proof_missing_short_node1), + Some(Err(rlp::DecoderError::Custom("Unsupported receipt type"))) + ); + + assert_eq!( + header.check_receipt_proof(&proof_missing_short_node2), + Some(Err(rlp::DecoderError::Custom("Unsupported receipt type"))) + ); + + assert!(header.check_receipt_proof(&proof_invalid_encoding).is_none()); + assert!(header.check_receipt_proof(&proof_no_full_node).is_none()); + } + + #[test] + fn header_check_receipt_proof_with_intermediate_short_node() { + let header = Header { + receipts_root: hex!("d128e3a57142d2bf15bc0cbcac7ad54f40750d571b5c3097e425882c10c9ba66") + .into(), + ..Default::default() + }; + + let proof_receipt263 = vec![ + hex!("f90131a00d3cb8d3f57ac1c0e12918a2ebe0cafed8c273577b9dd73e7ed1079b403ef494a0678b9835b834f8a287c0dd33a8fca9146e456ca688555ed4ec1361a2180b778da0fe42da181a46677a043b3d9d4b8bb05a6a17b7b5c010c17e7c1d31cfb7c4f911a0c89f0e2c53241cdb578e1f2b4caf6ba36e00500bdc57fecd66b84a6a58394c19a086c3c1fae5a0575940b5d38e111c469d07883106c26856f3ef608469a2081f13a06c5992ff00aab6226a70a032fd2f571ba22f797321f45e2daa73020d638d21b0a050861e9503ef68728f6c90a44f7fe1bceb2a9bdab6957bbe7136166bd849561ea006aa6eaca8a07e57176e9aa41e6a09edfb7678d1a112404e0ec779d7e567e82ea0bb0b430d303ba21b0af11c487b8a218bd75db54c98940b3f11bad8ff47cad3ef8080808080808080").to_vec(), + hex!("f871a0246de222036ee6a03329b0105da0a6b3f916fc95a9ed5a403a581a0c4d74242ca0ac108a49a88b57a05ac34a108b39f1e45f6f167f2b9fbc8d52fb58e2e5a6af1ea0fcfe07ac2ccd3c28b6eab68d1bce112f6f6dbd9023e4ec3c05b96615aa803d798080808080808080808080808080").to_vec(), + hex!("e4820001a04fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8").to_vec(), + hex!("f851a096d010643ca2d47412ca66898286b5f2412963b9ec051b33e570d575914c9c5ca028cd24c652989542fe89479ec6388eac4592432242af5ba97563b3ac7c71c019808080808080808080808080808080").to_vec(), + hex!("f90211a0bb35a84c5b1dcb78ec9d32614912c696e62df77bebf9ab326ee55b5d3acdde46a01084b30dac8df0accfcd0fd6330b7f6fc72a4651246d0694be9162151686a620a03eed50afdce7909d784c6157c445a444c806b5f23d31f3b63786f600c84a95b2a0af5232f1df6c6d41879804d081abe867002abe26ba3e5f8e0254a83a54769831a0607915fb13dd5da594256389a45007a67a7f7a86e95d38d8462792b6c98a722ea00e1260fda1730f2738c650ce2bfba83857bc10f8fb119ebc4fb39acba24e6fbaa0d11de17e417327457812675ca3b84ae8e1b64827abfe01420953697c8313d5b1a05fcaf2f7a88f76336a0c32ffc78acb87ae2005454bd25d658035331be3173b46a03f94f4952ab9e650f83cfd0e7f367b1bcc493aacf39a06f16c4a2e1b5605da48a0bdb4ec79785ca8ae22d60f1bbd42d707b4d7ec4aff231a3ebab755e315b35053a043a67c3f2bcef37c8f47a673adcb7061007a553696d1092408601c11b2e6846aa0c519d5af48cae87c7f4538845417c9735813bee892a6fe2dda79f5c414e8576aa0f7058256e09589501d7c231d739e61c84a850e139690989d24fda6058b432e98a081a52faab520978cb19ce14400dba0cd5bcdc4e5a3c0740678aa8f97ee0e5c56a0bcecc61cadeae52518e3b68a48af4b11603dfd9d99d99d7985efa6d2de44f904a02cba4accfc6f39bc5adb6d4440eb6358b4a5103ef93298e4e694f1f940f8b48280").to_vec(), + hex!("f901ae20b901aaf901a70183bb444eb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000001000000000000000000000000000100000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000000000000000000002000000000000000000081000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000f89df89b94dac17f958d2ee523a2206206994597c13d831ec7f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000002e514404ff6823f1b46a8318a709251db414e5e1a000000000000000000000000055021c55847c00d764357a352e5803237d328954a0000000000000000000000000000000000000000000000000000000000201c370").to_vec(), + ]; + assert!(header.check_receipt_proof(&proof_receipt263).is_some()); + } +} diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs b/bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..1a10ea9abb7723180321e546e9e177e32685d94b --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod header; +pub mod log; +pub mod mpt; +pub mod receipt; + +pub use ethereum_types::{Address, H160, H256, H64, U256}; + +pub use header::{Bloom, Header, HeaderId}; +pub use log::Log; +pub use receipt::Receipt; + +#[derive(Debug)] +pub enum DecodeError { + // Unexpected RLP data + InvalidRLP(rlp::DecoderError), + // Data does not match expected ABI + InvalidABI(ethabi::Error), + // Invalid message payload + InvalidPayload, +} + +impl From for DecodeError { + fn from(err: rlp::DecoderError) -> Self { + DecodeError::InvalidRLP(err) + } +} + +impl From for DecodeError { + fn from(err: ethabi::Error) -> Self { + DecodeError::InvalidABI(err) + } +} diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/log.rs b/bridges/snowbridge/parachain/primitives/ethereum/src/log.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b8e35bb1133ec105cf8eaf080aadb2f55b7e02b --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/src/log.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use codec::{Decode, Encode}; +use ethereum_types::{H160, H256}; +use sp_std::prelude::*; + +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq)] +pub struct Log { + pub address: H160, + pub topics: Vec, + pub data: Vec, +} + +impl rlp::Decodable for Log { + /// We need to implement rlp::Decodable manually as the derive macro RlpDecodable + /// didn't seem to generate the correct code for parsing our logs. + fn decode(rlp: &rlp::Rlp) -> Result { + let mut iter = rlp.iter(); + + let address: H160 = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected log address")), + }; + + let topics: Vec = match iter.next() { + Some(data) => data.as_list()?, + None => return Err(rlp::DecoderError::Custom("Expected log topics")), + }; + + let data: Vec = match iter.next() { + Some(data) => data.data()?.to_vec(), + None => return Err(rlp::DecoderError::Custom("Expected log data")), + }; + + Ok(Self { address, topics, data }) + } +} + +#[cfg(test)] +mod tests { + + use super::Log; + use hex_literal::hex; + + const RAW_LOG: [u8; 605] = hex!( + " + f9025a941cfd66659d44cfe2e627c5742ba7477a3284cffae1a0266413be5700ce8dd5ac6b9a7dfb + abe99b3e45cae9a68ac2757858710b401a38b9022000000000000000000000000000000000000000 + 00000000000000000000000060000000000000000000000000000000000000000000000000000000 + 00000000c00000000000000000000000000000000000000000000000000000000000000100000000 + 00000000000000000000000000000000000000000000000000000000283163466436363635394434 + 34636665324536323763353734324261373437376133323834634666410000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 000000000773656e6445544800000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000001000000000000000000000000 + 00cffeaaf7681c89285d65cfbe808b80e50269657300000000000000000000000000000000000000 + 000000000000000000000000a0000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000000000000a000000 + 00000000000000000000000000000000000000000000000000000000020000000000000000000000 + 00000000000000000000000000000000000000002f3146524d4d3850456957585961783772705336 + 5834585a5831614141785357783143724b5479725659685632346667000000000000000000000000 + 0000000000 + " + ); + + #[test] + fn decode_log() { + let log: Log = rlp::decode(&RAW_LOG).unwrap(); + assert_eq!(log.address.as_bytes(), hex!["1cfd66659d44cfe2e627c5742ba7477a3284cffa"]); + assert_eq!( + log.topics[0].as_bytes(), + hex!["266413be5700ce8dd5ac6b9a7dfbabe99b3e45cae9a68ac2757858710b401a38"] + ); + } +} diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs b/bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a2dae486dcc05ee5c078e0794ee2d27193eb207 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Helper types to work with Ethereum's Merkle Patricia Trie nodes + +use ethereum_types::H256; +use sp_std::{convert::TryFrom, prelude::*}; + +pub trait Node { + fn contains_hash(&self, hash: H256) -> bool; +} + +impl TryFrom<&[u8]> for Box { + type Error = rlp::DecoderError; + + fn try_from(bytes: &[u8]) -> Result, Self::Error> { + let rlp = rlp::Rlp::new(bytes); + match rlp.item_count()? { + 2 => { + let node: ShortNode = rlp.as_val()?; + Ok(Box::new(node)) + }, + 17 => { + let node: FullNode = rlp.as_val()?; + Ok(Box::new(node)) + }, + _ => Err(rlp::DecoderError::Custom("Invalid number of list elements")), + } + } +} + +/// Intermediate trie node with children (refers to node with same name in Geth). +/// This struct only handles the proof representation, i.e. a child is either empty +/// or a 32-byte hash of its subtree. +pub struct FullNode { + pub children: Vec>, +} + +impl rlp::Decodable for FullNode { + fn decode(rlp: &rlp::Rlp) -> Result { + let children: Vec> = rlp + .iter() + .map(|item| { + let v: Vec = item.as_val()?; + match v.len() { + 0 => Ok(None), + 32 => { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&v); + Ok(Some(bytes.into())) + }, + _ => Err(rlp::DecoderError::Custom("Expected 32-byte hash or empty child")), + } + }) + .collect::>()?; + + Ok(Self { children }) + } +} + +impl Node for FullNode { + fn contains_hash(&self, hash: H256) -> bool { + self.children.iter().any(|h| Some(hash) == *h) + } +} + +/// Trie node where `value` is either the RLP-encoded item we're +/// proving or an intermediate hash (refers to node with same name in Geth) +/// Proof verification should return `value`. `key` is an implementation +/// detail of the trie. +pub struct ShortNode { + pub key: Vec, + pub value: Vec, +} + +impl rlp::Decodable for ShortNode { + fn decode(rlp: &rlp::Rlp) -> Result { + let mut iter = rlp.iter(); + + let key: Vec = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected key bytes")), + }; + + let value: Vec = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected value bytes")), + }; + + Ok(Self { key, value }) + } +} + +impl Node for ShortNode { + fn contains_hash(&self, hash: H256) -> bool { + self.value == hash.0 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use hex_literal::hex; + + const RAW_PROOF: [&[u8]; 3] = [ + &hex!("f90131a0b5ba404eb5a6a88e56579f4d37ef9813b5ad7f86f0823ff3b407ac5a6bb465eca0398ead2655e78e03c127ce22c5830e90f18b1601ec055f938336c084feb915a9a026d322c26e46c50942c1aabde50e36df5cde572aed650ce73ea3182c6e90a02ca00600a356135f4db1db0d9842264cdff2652676f881669e91e316c0b6dd783011a0837f1deb4075336da320388c1edfffc56c448a43f4a5ba031300d32a7b509fc5a01c3ac82fd65b4aba7f9afaf604d9c82ec7e2deb573a091ae235751bc5c0c288da05d454159d9071b0f68b6e0503d290f23ac7602c1db0c569dee4605d8f5298f09a00bbed10350ec954448df795f6fd46e3faefc800ede061b3840eedc6e2b07a74da0acb02d26a3650f2064c14a435fdf1f668d8655daf455ebdf671713a7c089b3898080808080808080"), + &hex!("f901f180a00046a08d4f0bdbdc6b31903086ce323182bce6725e7d9415f7ff91ee8f4820bda0e7cd26ad5f3d2771e4b5ab788e268a14a10209f94ee918eb6c829d21d3d11c1da00d4a56d9e9a6751874fd86c7e3cb1c6ad5a848da62751325f478978a00ea966ea064b81920c8f04a8a1e21f53a8280e739fbb7b00b2ab92493ca3f610b70e8ac85a0b1040ed4c55a73178b76abb16f946ce5bebd6b93ab873c83327df54047d12c27a0de6485e9ac58dc6e2b04b4bb38f562684f0b1a2ee586cc11079e7d9a9dc40b32a0d394f4d3532c3124a65fa36e69147e04fd20453a72ee9c50660f17e13ce9df48a066501003fc3e3478efd2803cd0eded6bbe9243ca01ba754d6327071ddbcbc649a0b2684e518f325fee39fc8ea81b68f3f5c785be00d087f3bed8857ae2ee8da26ea071060a5c52042e8d7ce21092f8ecf06053beb9a0b773a6f91a30c4220aa276b2a0fc22436632574ccf6043d0986dede27ea94c9ca9a3bb5ec03ce776a4ddef24a9a05a8a1d6698c4e7d8cc3a2506cb9b12ea9a079c9c7099bc919dc804033cc556e4a0170c468b0716fd36d161f0bf05875f15756a2976de92f9efe7716320509d79c9a0182f909a90cab169f3efb62387f9cccdd61440acc4deec42f68a4f7ca58075c7a055cf0e9202ac75689b76318f1171f3a44465eddc06aae0713bfb6b34fdd27b7980"), + &hex!("f904de20b904daf904d701830652f0b9010004200000000000000000000080020000000000010000000000010000000000000000000000000000000000000000000002000000080000000000000000200000000000000000000000000008000000220000000000400010000000000000000000000000000000000000000000000000000000000000040000000010000100000000000800000000004000000000000000000000000000080000004000000000020000000000020000000000000000000000000000000000000000000004000000000002000000000100000000000000000000000000001000000002000020000010200000000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000000000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffffffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea393ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000000000000000000000000000000000000005d415f3320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1078e"), + ]; + + #[test] + fn decode_full_node() { + let node1: FullNode = rlp::decode(RAW_PROOF[0]).unwrap(); + let node2: FullNode = rlp::decode(RAW_PROOF[1]).unwrap(); + assert_eq!(node1.children.len(), 17); + assert_eq!(node2.children.len(), 17); + assert_eq!(node1.children.iter().filter(|c| c.is_none()).count(), 8); + assert_eq!(node2.children.iter().filter(|c| c.is_none()).count(), 2); + + let result: Result = rlp::decode(RAW_PROOF[2]); + assert!(result.is_err()); + } + + #[test] + fn decode_short_node() { + // key + item value + let node: ShortNode = rlp::decode(RAW_PROOF[2]).unwrap(); + assert_eq!(node.key, vec![32]); + assert!(!node.value.is_empty()); + + // key + item hash + let node: ShortNode = rlp::decode(&hex!( + "e4820001a04fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8" + )) + .unwrap(); + assert_eq!(node.key, vec![0, 1]); + assert_eq!( + node.value, + hex!("4fff54398cad4d05ea6abfd8b0f3b4fe14c04d7ff5f5211c5b927d9cf72ac1d8").to_vec() + ); + } +} diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs b/bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs new file mode 100644 index 0000000000000000000000000000000000000000..665a93dbb1e213c0752cf4b64dfea5469a7513bd --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::{Bloom, Log}; +use codec::{Decode, Encode}; +use sp_runtime::RuntimeDebug; +use sp_std::prelude::*; + +#[derive(Clone, Default, Encode, Decode, PartialEq, RuntimeDebug)] +pub struct Receipt { + pub post_state_or_status: Vec, + pub cumulative_gas_used: u64, + pub bloom: Bloom, + pub logs: Vec, +} + +impl Receipt { + pub fn contains_log(&self, log: &Log) -> bool { + self.logs.iter().any(|l| l == log) + } + + fn decode_list(rlp: &rlp::Rlp) -> Result { + let mut iter = rlp.iter(); + + let post_state_or_status: Vec = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected receipt post state or status")), + }; + + let cumulative_gas_used: u64 = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected receipt cumulative gas used")), + }; + + let bloom: Bloom = match iter.next() { + Some(data) => data.as_val()?, + None => return Err(rlp::DecoderError::Custom("Expected receipt bloom")), + }; + + let logs: Vec = match iter.next() { + Some(data) => data.as_list()?, + None => return Err(rlp::DecoderError::Custom("Expected receipt logs")), + }; + + Ok(Self { post_state_or_status, cumulative_gas_used, bloom, logs }) + } +} + +impl rlp::Decodable for Receipt { + fn decode(rlp: &rlp::Rlp) -> Result { + if rlp.is_data() { + // Typed receipt + let data = rlp.as_raw(); + match data[0] { + // 1 = EIP-2930, 2 = EIP-1559 + 1 | 2 => { + let receipt_rlp = &rlp::Rlp::new(&data[1..]); + if !receipt_rlp.is_list() { + return Err(rlp::DecoderError::RlpExpectedToBeList) + } + Self::decode_list(&rlp::Rlp::new(&data[1..])) + }, + _ => Err(rlp::DecoderError::Custom("Unsupported receipt type")), + } + } else if rlp.is_list() { + // Legacy receipt + Self::decode_list(rlp) + } else { + Err(rlp::DecoderError::RlpExpectedToBeList) + } + } +} + +#[cfg(test)] +mod tests { + + use super::Receipt; + use hex_literal::hex; + + const RAW_RECEIPT: [u8; 1242] = hex!( + " + f904d701830652f0b901000420000000000000000000008002000000000001000000000001000000 + 00000000000000000000000000000000000000020000000800000000000000002000000000000000 + 00000000000008000000220000000000400010000000000000000000000000000000000000000000 + 00000000000000000004000000001000010000000000080000000000400000000000000000000000 + 00000800000040000000000200000000000200000000000000000000000000000000000000000000 + 04000000000002000000000100000000000000000000000000001000000002000020000010200000 + 000000010000000000000000000000000000000000000010000000f903ccf89b9421130f34829b4c + 343142047a28ce96ec07814b15f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a116 + 28f55a4df523b3efa00000000000000000000000007d843005c7433c16b27ff939cb37471541561e + bda0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a00000000000 + 0000000000000000000000000000000000000000000005d09b7380f89b9421130f34829b4c343142 + 047a28ce96ec07814b15f863a08c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200a + c8c7c3b925a00000000000000000000000007d843005c7433c16b27ff939cb37471541561ebda000 + 00000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da0ffffffffffffffff + ffffffffffffffffffffffffffffffffffffffcc840c6920f89b94c02aaa39b223fe8d0a0e5c4f27 + ead9083c756cc2f863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523 + b3efa0000000000000000000000000e9c1281aae66801fa35ec404d5f2aea393ff6988a000000000 + 00000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da00000000000000000000000 + 0000000000000000000000000003e973b5a5d1078ef87994e9c1281aae66801fa35ec404d5f2aea3 + 93ff6988e1a01c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1b840 + 000000000000000000000000000000000000000000000000000001f1420ad1d40000000000000000 + 000000000000000000000000000000014ad400879d159a38f8fc94e9c1281aae66801fa35ec404d5 + f2aea393ff6988f863a0d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159 + d822a00000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488da000000000 + 00000000000000007a250d5630b4cf539739df2c5dacb4c659f2488db88000000000000000000000 + 000000000000000000000000000000000005d415f332000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000003e973b5a5d1078ef87a + 94c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f842a07fcf532c15f0a6db0bd6d0e038bea71d + 30d808c7d98cb3bf7268a95bf5081b65a00000000000000000000000007a250d5630b4cf539739df + 2c5dacb4c659f2488da000000000000000000000000000000000000000000000000003e973b5a5d1 + 078e + " + ); + + #[test] + fn decode_legacy_receipt() { + let receipt: Receipt = rlp::decode(&RAW_RECEIPT).unwrap(); + assert_eq!(receipt.post_state_or_status, vec!(1)); + assert_eq!(receipt.cumulative_gas_used, 414448); + assert_eq!( + receipt.bloom, + (&hex!( + " + 042000000000000000000000800200000000000100000000000100000000000000000000 + 000000000000000000000000020000000800000000000000002000000000000000000000 + 000000080000002200000000004000100000000000000000000000000000000000000000 + 000000000000000000000400000000100001000000000008000000000040000000000000 + 000000000000000800000040000000000200000000000200000000000000000000000000 + 000000000000000000040000000000020000000001000000000000000000000000000010 + 000000020000200000102000000000000100000000000000000000000000000000000000 + 10000000 + " + )) + .into(), + ); + assert_eq!(receipt.logs.len(), 6); + } +} diff --git a/bridges/snowbridge/parachain/primitives/router/Cargo.toml b/bridges/snowbridge/parachain/primitives/router/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..40ae12920e7e4fa405240a9d53ebadaec3b98396 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "snowbridge-router-primitives" +description = "Snowbridge Router Primitives" +version = "0.1.1" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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 } + +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 } + +snowbridge-core = { path = "../../primitives/core", default-features = false } + +ethabi = { git = "https://github.com/Snowfork/ethabi-decode.git", package = "ethabi-decode", branch = "master", default-features = false } + +hex-literal = { version = "0.4.1" } + +[dev-dependencies] +hex = { package = "rustc-hex", version = "2.1.0" } + +[features] +default = ["std"] +std = [ + "codec/std", + "ethabi/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "serde", + "snowbridge-core/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a07e0eae5d73d6c2205d5858ad75d631832ab1fe --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +#[cfg(test)] +mod tests; + +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{traits::tokens::Balance as BalanceT, weights::Weight, PalletError}; +use scale_info::TypeInfo; +use sp_core::{Get, RuntimeDebug, H160}; +use sp_io::hashing::blake2_256; +use sp_runtime::MultiAddress; +use sp_std::prelude::*; +use xcm::prelude::{Junction::AccountKey20, *}; +use xcm_executor::traits::ConvertLocation; + +const MINIMUM_DEPOSIT: u128 = 1; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V1(MessageV1), +} + +/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct MessageV1 { + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Command { + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send a token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, +} + +/// Destination for bridged tokens +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Destination { + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, +} + +pub struct MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, +> where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, +{ + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + )>, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The message version is not supported for conversion. + UnsupportedVersion, +} + +/// convert the inbound message to xcm which will be forwarded to the destination chain +pub trait ConvertMessage { + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; +} + +pub type CallIndex = [u8; 2]; + +impl + ConvertMessage + for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + > where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, +{ + type Balance = Balance; + type AccountId = AccountId; + + fn convert(message: VersionedMessage) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(chain_id, token, destination, amount, fee)), + } + } +} + +impl + MessageToXcm +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, +{ + fn convert_register_token(chain_id: u64, token: H160, fee: u128) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: MultiAsset = (MultiLocation::parent(), fee).into(); + let deposit: MultiAsset = (MultiLocation::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: MultiAsset = (MultiLocation::parent(), total_amount).into(); + + let bridge_location: MultiLocation = (Parent, Parent, GlobalConsensus(network)).into(); + + let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location }, + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin` + DescendOrigin(X1(PalletInstance(inbound_queue_pallet_index))), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + require_weight_at_most: Weight::from_parts(400_000_000, 8_000), + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + RefundSurplus, + // Clear the origin so that remaining assets in holding + // are claimable by the physical origin (BridgeHub) + ClearOrigin, + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: MultiAsset = (MultiLocation::parent(), asset_hub_fee).into(); + let asset: MultiAsset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => ( + None, + MultiLocation { parents: 0, interior: X1(AccountId32 { network: None, id }) }, + 0, + ), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + MultiLocation { parents: 0, interior: X1(AccountId32 { network: None, id }) }, + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + MultiLocation { parents: 0, interior: X1(AccountKey20 { network: None, key: id }) }, + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: MultiAsset = (MultiLocation::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(X1(PalletInstance(inbound_queue_pallet_index))), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: MultiAsset = + (MultiLocation::parent(), dest_para_fee).into(); + + instructions.extend(vec![ + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset.clone()].into()), + dest: MultiLocation { parents: 1, interior: X1(Parachain(dest_para_id)) }, + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit asset to beneficiary. + DepositAsset { assets: Definite(asset.into()), beneficiary }, + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit asset to beneficiary. + DepositAsset { assets: Definite(asset.into()), beneficiary }, + ]); + }, + } + + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a Multilocation that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> MultiLocation { + MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(network), + AccountKey20 { network: None, key: token.into() }, + ), + } + } +} + +pub struct GlobalConsensusEthereumConvertsFor(PhantomData); +impl ConvertLocation for GlobalConsensusEthereumConvertsFor +where + AccountId: From<[u8; 32]> + Clone, +{ + fn convert_location(location: &MultiLocation) -> Option { + if let MultiLocation { interior: X1(GlobalConsensus(Ethereum { chain_id })), .. } = location + { + Some(Self::from_chain_id(chain_id).into()) + } else { + None + } + } +} + +impl GlobalConsensusEthereumConvertsFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) + } +} diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..8c96c13cf223bcaf72acc2010137315599ee3258 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs @@ -0,0 +1,41 @@ +use super::GlobalConsensusEthereumConvertsFor; +use crate::inbound::CallIndex; +use frame_support::parameter_types; +use hex_literal::hex; +use xcm::v3::prelude::*; +use xcm_executor::traits::ConvertLocation; + +const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + +parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; +} + +#[test] +fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = MultiLocation { parents: 2, interior: X1(GlobalConsensus(NETWORK)) }; + + let account = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); +} + +#[test] +fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = + MultiLocation { parents: 2, interior: X2(GlobalConsensus(Polkadot), Parachain(1000)) }; + + assert_eq!( + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); +} diff --git a/bridges/snowbridge/parachain/primitives/router/src/lib.rs b/bridges/snowbridge/parachain/primitives/router/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d9031c69b22b88a8ae9dfbcb8ed3025a36332a82 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/src/lib.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod inbound; +pub mod outbound; diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c7f2f440834caa41efac3e7cbdf89a50065c0061 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts XCM messages into simpler commands that can be processed by the Gateway contract + +#[cfg(test)] +mod tests; + +use core::slice::Iter; + +use codec::{Decode, Encode}; + +use frame_support::{ensure, traits::Get}; +use snowbridge_core::{ + outbound::{AgentExecuteCommand, Command, Message, SendMessage}, + ChannelId, ParaId, +}; +use sp_core::{H160, H256}; +use sp_std::{iter::Peekable, marker::PhantomData, prelude::*}; +use xcm::v3::prelude::*; +use xcm_executor::traits::{ConvertLocation, ExportXcm}; + +pub struct EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + OutboundQueue, + AgentHashedDescription, +>(PhantomData<(UniversalLocation, EthereumNetwork, OutboundQueue, AgentHashedDescription)>); + +impl ExportXcm + for EthereumBlobExporter +where + UniversalLocation: Get, + EthereumNetwork: Get, + OutboundQueue: SendMessage, + AgentHashedDescription: ConvertLocation, +{ + type Ticket = (Vec, XcmHash); + + fn validate( + network: NetworkId, + _channel: u32, + universal_source: &mut Option, + destination: &mut Option, + message: &mut Option>, + ) -> SendResult { + let expected_network = EthereumNetwork::get(); + let universal_location = UniversalLocation::get(); + + if network != expected_network { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}."); + return Err(SendError::NotApplicable) + } + + let dest = destination.take().ok_or(SendError::MissingArgument)?; + if dest != Here { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}."); + return Err(SendError::NotApplicable) + } + + let (local_net, local_sub) = universal_source + .take() + .ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided."); + SendError::MissingArgument + })? + .split_global() + .map_err(|()| { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'."); + SendError::Unroutable + })?; + + if Ok(local_net) != universal_location.global_consensus() { + log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}."); + return Err(SendError::NotApplicable) + } + + let para_id = match local_sub { + X1(Parachain(para_id)) => para_id, + _ => { + log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'."); + return Err(SendError::MissingArgument) + }, + }; + + let message = message.take().ok_or_else(|| { + log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided."); + SendError::MissingArgument + })?; + + let mut converter = XcmConverter::new(&message, &expected_network); + let (agent_execute_command, message_id) = converter.convert().map_err(|err|{ + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'."); + SendError::Unroutable + })?; + + let source_location: MultiLocation = MultiLocation { parents: 1, interior: local_sub }; + let agent_id = match AgentHashedDescription::convert_location(&source_location) { + Some(id) => id, + None => { + log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to not being able to create agent id. '{source_location:?}'"); + return Err(SendError::Unroutable) + }, + }; + + let channel_id: ChannelId = ParaId::from(para_id).into(); + + let outbound_message = Message { + id: Some(message_id.into()), + channel_id, + command: Command::AgentExecute { agent_id, command: agent_execute_command }, + }; + + // validate the message + let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}"); + SendError::Unroutable + })?; + + // convert fee to MultiAsset + let fee = MultiAsset::from((MultiLocation::parent(), fee.total())).into(); + + Ok(((ticket.encode(), message_id), fee)) + } + + fn deliver(blob: (Vec, XcmHash)) -> Result { + let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref()) + .map_err(|_| { + log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error"); + SendError::NotApplicable + })?; + + let message_id = OutboundQueue::deliver(ticket).map_err(|_| { + log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed"); + SendError::Transport("other transport error") + })?; + + log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}."); + Ok(message_id.into()) + } +} + +/// Errors that can be thrown to the pattern matching step. +#[derive(PartialEq, Debug)] +enum XcmConverterError { + UnexpectedEndOfXcm, + EndOfXcmMessageExpected, + WithdrawAssetExpected, + DepositAssetExpected, + NoReserveAssets, + FilterDoesNotConsumeAllAssets, + TooManyAssets, + ZeroAssetTransfer, + BeneficiaryResolutionFailed, + AssetResolutionFailed, + InvalidFeeAsset, + SetTopicExpected, +} + +macro_rules! match_expression { + ($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => { + match $expression { + $( $pattern )|+ $( if $guard )? => Some($value), + _ => None, + } + }; +} + +struct XcmConverter<'a, Call> { + iter: Peekable>>, + ethereum_network: &'a NetworkId, +} +impl<'a, Call> XcmConverter<'a, Call> { + fn new(message: &'a Xcm, ethereum_network: &'a NetworkId) -> Self { + Self { iter: message.inner().iter().peekable(), ethereum_network } + } + + fn convert(&mut self) -> Result<(AgentExecuteCommand, [u8; 32]), XcmConverterError> { + // Get withdraw/deposit and make native tokens create message. + let result = self.native_tokens_unlock_message()?; + + // All xcm instructions must be consumed before exit. + if self.next().is_ok() { + return Err(XcmConverterError::EndOfXcmMessageExpected) + } + + Ok(result) + } + + fn native_tokens_unlock_message( + &mut self, + ) -> Result<(AgentExecuteCommand, [u8; 32]), XcmConverterError> { + use XcmConverterError::*; + + // Get the reserve assets from WithdrawAsset. + let reserve_assets = + match_expression!(self.next()?, WithdrawAsset(reserve_assets), reserve_assets) + .ok_or(WithdrawAssetExpected)?; + + // Check if clear origin exists and skip over it. + if match_expression!(self.peek(), Ok(ClearOrigin), ()).is_some() { + let _ = self.next(); + } + + // Get the fee asset item from BuyExecution or continue parsing. + let fee_asset = match_expression!(self.peek(), Ok(BuyExecution { fees, .. }), fees); + if fee_asset.is_some() { + let _ = self.next(); + } + + let (deposit_assets, beneficiary) = match_expression!( + self.next()?, + DepositAsset { assets, beneficiary }, + (assets, beneficiary) + ) + .ok_or(DepositAssetExpected)?; + + // assert that the beneficiary is AccountKey20. + let recipient = match_expression!( + beneficiary, + MultiLocation { parents: 0, interior: X1(AccountKey20 { network, key }) } + if self.network_matches(network), + H160(*key) + ) + .ok_or(BeneficiaryResolutionFailed)?; + + // Make sure there are reserved assets. + if reserve_assets.len() == 0 { + return Err(NoReserveAssets) + } + + // Check the the deposit asset filter matches what was reserved. + if reserve_assets.inner().iter().any(|asset| !deposit_assets.matches(asset)) { + return Err(FilterDoesNotConsumeAllAssets) + } + + // We only support a single asset at a time. + ensure!(reserve_assets.len() == 1, TooManyAssets); + let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?; + + // If there was a fee specified verify it. + if let Some(fee_asset) = fee_asset { + // The fee asset must be the same as the reserve asset. + if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun { + return Err(InvalidFeeAsset) + } + } + + let (token, amount) = match_expression!( + reserve_asset, + MultiAsset { + id: Concrete(MultiLocation { parents: 0, interior: X1(AccountKey20 { network , key })}), + fun: Fungible(amount) + } if self.network_matches(network), + (H160(*key), *amount) + ) + .ok_or(AssetResolutionFailed)?; + + // transfer amount must be greater than 0. + ensure!(amount > 0, ZeroAssetTransfer); + + // Check if there is a SetTopic and skip over it if found. + let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?; + + Ok((AgentExecuteCommand::TransferToken { token, recipient, amount }, *topic_id)) + } + + fn next(&mut self) -> Result<&'a Instruction, XcmConverterError> { + self.iter.next().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn peek(&mut self) -> Result<&&'a Instruction, XcmConverterError> { + self.iter.peek().ok_or(XcmConverterError::UnexpectedEndOfXcm) + } + + fn network_matches(&self, network: &Option) -> bool { + if let Some(network) = network { + network == self.ethereum_network + } else { + true + } + } +} diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..153d934c390962bd68c9fe9d43751a37e1d4c756 --- /dev/null +++ b/bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs @@ -0,0 +1,1063 @@ +use frame_support::parameter_types; +use hex_literal::hex; +use snowbridge_core::{ + outbound::{Fee, SendError, SendMessageFeeProvider}, + AgentIdOf, +}; +use xcm::v3::prelude::SendError as XcmSendError; + +use super::*; + +parameter_types! { + const MaxMessageSize: u32 = u32::MAX; + const RelayNetwork: NetworkId = Polkadot; + const UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(1013)); + const BridgedNetwork: NetworkId = Ethereum{ chain_id: 1 }; + const NonBridgedNetwork: NetworkId = Ethereum{ chain_id: 2 }; +} + +struct MockOkOutboundQueue; +impl SendMessage for MockOkOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Ok(((), Fee { local: 1, remote: 1 })) + } + + fn deliver(_: Self::Ticket) -> Result { + Ok(H256::zero()) + } +} + +impl SendMessageFeeProvider for MockOkOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} +struct MockErrOutboundQueue; +impl SendMessage for MockErrOutboundQueue { + type Ticket = (); + + fn validate(_: &Message) -> Result<(Self::Ticket, Fee), SendError> { + Err(SendError::MessageTooLarge) + } + + fn deliver(_: Self::Ticket) -> Result { + Err(SendError::MessageTooLarge) + } +} + +impl SendMessageFeeProvider for MockErrOutboundQueue { + type Balance = u128; + + fn local_fee() -> Self::Balance { + 1 + } +} + +#[test] +fn exporter_validate_with_unknown_network_yields_not_applicable() { + let network = Ethereum { chain_id: 1337 }; + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_invalid_destination_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = None; + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_x8_destination_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Some(X8( + OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, + )); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_universal_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = None; + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_without_global_universal_location_yields_unroutable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::Unroutable)); +} + +#[test] +fn exporter_validate_without_global_bridge_location_yields_not_applicable() { + let network = NonBridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Here.into(); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_with_remote_universal_source_yields_not_applicable() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some(X2(GlobalConsensus(Kusama), Parachain(1000))); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::NotApplicable)); +} + +#[test] +fn exporter_validate_without_para_id_in_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = Some(X1(GlobalConsensus(Polkadot))); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_complex_para_id_in_source_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some(X3(GlobalConsensus(Polkadot), Parachain(1000), PalletInstance(12))); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_without_xcm_message_yields_missing_argument() { + let network = BridgedNetwork::get(); + let channel: u32 = 0; + let mut universal_source: Option = + Some(X2(GlobalConsensus(Polkadot), Parachain(1000))); + let mut destination: Option = Here.into(); + let mut message: Option> = None; + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + assert_eq!(result, Err(XcmSendError::MissingArgument)); +} + +#[test] +fn exporter_validate_with_max_target_fee_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some(X2(GlobalConsensus(Polkadot), Parachain(1000))); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let fee = MultiAsset { id: Concrete(Here.into()), fun: Fungible(1000) }; + let fees: MultiAssets = vec![fee.clone()].into(); + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(fees), + BuyExecution { fees: fee, weight_limit: Unlimited }, + WithdrawAsset(assets), + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: Some(network), key: beneficiary_address }) + .into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + + assert_eq!(result, Err(XcmSendError::Unroutable)); +} + +#[test] +fn exporter_validate_with_unparsable_xcm_yields_unroutable() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some(X2(GlobalConsensus(Polkadot), Parachain(1000))); + + let channel: u32 = 0; + let fee = MultiAsset { id: Concrete(Here.into()), fun: Fungible(1000) }; + let fees: MultiAssets = vec![fee.clone()].into(); + + let mut message: Option> = + Some(vec![WithdrawAsset(fees), BuyExecution { fees: fee, weight_limit: Unlimited }].into()); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + + assert_eq!(result, Err(XcmSendError::Unroutable)); +} + +#[test] +fn exporter_validate_xcm_success_case_1() { + let network = BridgedNetwork::get(); + let mut destination: Option = Here.into(); + + let mut universal_source: Option = + Some(X2(GlobalConsensus(Polkadot), Parachain(1000))); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let channel: u32 = 0; + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let fee = assets.clone().get(0).unwrap().clone(); + let filter: MultiAssetFilter = assets.clone().into(); + + let mut message: Option> = Some( + vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(), + ); + + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockOkOutboundQueue, + AgentIdOf, + >::validate( + network, channel, &mut universal_source, &mut destination, &mut message + ); + + assert!(result.is_ok()); +} + +#[test] +fn exporter_deliver_with_submit_failure_yields_unroutable() { + let result = EthereumBlobExporter::< + UniversalLocation, + BridgedNetwork, + MockErrOutboundQueue, + AgentIdOf, + >::deliver((hex!("deadbeef").to_vec(), XcmHash::default())); + assert_eq!(result, Err(XcmSendError::Transport("other transport error"))) +} + +#[test] +fn xcm_converter_convert_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let expected_payload = AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_without_buy_execution_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let expected_payload = AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let expected_payload = AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = X1(AccountKey20 { network: None, key: token_address }).into(); + let fee_asset = MultiAsset { id: Concrete(asset_location), fun: Fungible(500) }; + + let assets: MultiAssets = + vec![MultiAsset { id: Concrete(asset_location), fun: Fungible(1000) }].into(); + + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let expected_payload = AgentExecuteCommand::TransferToken { + token: token_address.into(), + recipient: beneficiary_address.into(), + amount: 1000, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + +#[test] +fn xcm_converter_convert_without_set_topic_yields_set_topic_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + ClearTopic, + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::SetTopicExpected)); +} + +#[test] +fn xcm_converter_convert_with_partial_message_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let message: Xcm<()> = vec![WithdrawAsset(assets)].into(); + + let mut converter = XcmConverter::new(&message, &network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_with_different_fee_asset_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = X1(AccountKey20 { network: None, key: token_address }).into(); + let fee_asset = MultiAsset { + id: Concrete(MultiLocation { parents: 0, interior: Here }), + fun: Fungible(1000), + }; + + let assets: MultiAssets = + vec![MultiAsset { id: Concrete(asset_location), fun: Fungible(1000) }].into(); + + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); +} + +#[test] +fn xcm_converter_with_fees_greater_than_reserve_fails() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let asset_location = X1(AccountKey20 { network: None, key: token_address }).into(); + let fee_asset = MultiAsset { id: Concrete(asset_location), fun: Fungible(1001) }; + + let assets: MultiAssets = + vec![MultiAsset { id: Concrete(asset_location), fun: Fungible(1000) }].into(); + + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee_asset, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::InvalidFeeAsset)); +} + +#[test] +fn xcm_converter_convert_with_empty_xcm_yields_unexpected_end_of_xcm() { + let network = BridgedNetwork::get(); + + let message: Xcm<()> = vec![].into(); + + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::UnexpectedEndOfXcm)); +} + +#[test] +fn xcm_converter_convert_with_extra_instructions_yields_end_of_xcm_message_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ClearError, + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::EndOfXcmMessageExpected)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_withdraw_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::WithdrawAssetExpected)); +} + +#[test] +fn xcm_converter_convert_without_withdraw_asset_yields_deposit_expected() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::DepositAssetExpected)); +} + +#[test] +fn xcm_converter_convert_without_assets_yields_no_reserve_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![].into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let fee = MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }; + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: fee, weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::NoReserveAssets)); +} + +#[test] +fn xcm_converter_convert_with_two_assets_yields_too_many_assets() { + let network = BridgedNetwork::get(); + + let token_address_1: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let token_address_2: [u8; 20] = hex!("1100000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![ + MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address_1 }).into()), + fun: Fungible(1000), + }, + MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address_2 }).into()), + fun: Fungible(500), + }, + ] + .into(); + let filter: MultiAssetFilter = assets.clone().into(); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::TooManyAssets)); +} + +#[test] +fn xcm_converter_convert_without_consuming_filter_yields_filter_does_not_consume_all_assets() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(0)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::FilterDoesNotConsumeAllAssets)); +} + +#[test] +fn xcm_converter_convert_with_zero_amount_asset_yields_zero_asset_transfer() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(0), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::ZeroAssetTransfer)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X3(GlobalConsensus(Polkadot), Parachain(1000), GeneralIndex(0)).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_asset_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete( + X1(AccountKey20 { network: Some(Ethereum { chain_id: 2 }), key: token_address }).into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_non_ethereum_chain_yields_asset_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete( + X1(AccountKey20 { network: Some(NonBridgedNetwork::get()), key: token_address }).into(), + ), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { network: None, key: beneficiary_address }).into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::AssetResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_beneficiary_yields_beneficiary_resolution_failed() { + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + + let beneficiary_address: [u8; 32] = + hex!("2000000000000000000000000000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X3( + GlobalConsensus(Polkadot), + Parachain(1000), + AccountId32 { network: Some(Polkadot), id: beneficiary_address }, + ) + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn xcm_converter_convert_with_non_ethereum_chain_beneficiary_yields_beneficiary_resolution_failed() +{ + let network = BridgedNetwork::get(); + + let token_address: [u8; 20] = hex!("1000000000000000000000000000000000000000"); + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + let assets: MultiAssets = vec![MultiAsset { + id: Concrete(X1(AccountKey20 { network: None, key: token_address }).into()), + fun: Fungible(1000), + }] + .into(); + let filter: MultiAssetFilter = Wild(WildMultiAsset::AllCounted(1)); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: X1(AccountKey20 { + network: Some(Ethereum { chain_id: 2 }), + key: beneficiary_address, + }) + .into(), + }, + SetTopic([0; 32]), + ] + .into(); + let mut converter = XcmConverter::new(&message, &network); + + let result = converter.convert(); + assert_eq!(result.err(), Some(XcmConverterError::BeneficiaryResolutionFailed)); +} + +#[test] +fn test_describe_asset_hub() { + let legacy_location: MultiLocation = + MultiLocation { parents: 0, interior: X1(Parachain(1000)) }; + let legacy_agent_id = AgentIdOf::convert_location(&legacy_location).unwrap(); + assert_eq!( + legacy_agent_id, + hex!("72456f48efed08af20e5b317abf8648ac66e86bb90a411d9b0b713f7364b75b4").into() + ); + let location: MultiLocation = MultiLocation { parents: 1, interior: X1(Parachain(1000)) }; + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79").into() + ) +} + +#[test] +fn test_describe_here() { + let location: MultiLocation = MultiLocation { parents: 0, interior: Here }; + let agent_id = AgentIdOf::convert_location(&location).unwrap(); + assert_eq!( + agent_id, + hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into() + ) +} diff --git a/bridges/snowbridge/parachain/runtime/rococo-common/Cargo.toml b/bridges/snowbridge/parachain/runtime/rococo-common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..656ed6de26e83acfcfa35adff0ad9aae4ba8ba78 --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/rococo-common/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "snowbridge-rococo-common" +description = "Snowbridge Rococo Common" +version = "0.0.1" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +log = { version = "0.4.20", default-features = false } + +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "frame-support/std", + "log/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", +] diff --git a/bridges/snowbridge/parachain/runtime/rococo-common/src/lib.rs b/bridges/snowbridge/parachain/runtime/rococo-common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..97f0332fe66bafb49461d4a619f05f13a486306c --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/rococo-common/src/lib.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Rococo Common +//! +//! Config used for the Rococo asset hub and bridge hub runtimes. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::parameter_types; +use xcm::opaque::lts::NetworkId; + +pub const INBOUND_QUEUE_MESSAGES_PALLET_INDEX: u8 = 80; + +parameter_types! { + // Network and location for the Ethereum chain. + pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; +} diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b835152cac0d6f74b1995aa3d2ede1184ab010bf --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "snowbridge-runtime-common" +description = "Snowbridge Runtime Common" +version = "0.1.1" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +log = { version = "0.4.20", default-features = false } + +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", 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 } + +[dev-dependencies] + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "log/std", + "snowbridge-core/std", + "sp-arithmetic/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..b7f54d262bbb33c2dec1330c9de55cd399a70e04 --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! # Runtime Common +//! +//! Common traits and types shared by runtimes. +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; +use frame_support::traits::Get; +use snowbridge_core::{outbound::SendMessageFeeProvider, sibling_sovereign_account_raw}; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use xcm::prelude::*; +use xcm_builder::{deposit_or_burn_fee, HandleFee}; +use xcm_executor::traits::{FeeReason, TransactAsset}; + +/// A `HandleFee` implementation that takes fees from `ExportMessage` XCM instructions +/// to Snowbridge and splits off the remote fee and deposits it to the origin +/// parachain sovereign account. The local fee is then returned back to be handled by +/// the next fee handler in the chain. Most likely the treasury account. +pub struct XcmExportFeeToSibling< + Balance, + AccountId, + FeeAssetLocation, + EthereumNetwork, + AssetTransactor, + FeeProvider, +>( + PhantomData<( + Balance, + AccountId, + FeeAssetLocation, + EthereumNetwork, + AssetTransactor, + FeeProvider, + )>, +); + +impl HandleFee + for XcmExportFeeToSibling< + Balance, + AccountId, + FeeAssetLocation, + EthereumNetwork, + AssetTransactor, + FeeProvider, + > where + Balance: BaseArithmetic + Unsigned + Copy + From + Into, + AccountId: Clone + Into<[u8; 32]> + From<[u8; 32]>, + FeeAssetLocation: Get, + EthereumNetwork: Get, + AssetTransactor: TransactAsset, + FeeProvider: SendMessageFeeProvider, +{ + fn handle_fee( + fees: MultiAssets, + context: Option<&XcmContext>, + reason: FeeReason, + ) -> MultiAssets { + let token_location = FeeAssetLocation::get(); + + // Check the reason to see if this export is for snowbridge. + if !matches!( + reason, + FeeReason::Export { network: bridged_network, destination } + if bridged_network == EthereumNetwork::get() && destination == Here + ) { + return fees + } + + // Get the parachain sovereign from the `context`. + let para_sovereign = if let Some(XcmContext { + origin: Some(MultiLocation { parents: 1, interior }), + .. + }) = context + { + if let Some(Parachain(sibling_para_id)) = interior.first() { + let account: AccountId = + sibling_sovereign_account_raw((*sibling_para_id).into()).into(); + account + } else { + return fees + } + } else { + return fees + }; + + // Get the total fee offered by export message. + let maybe_total_supplied_fee: Option<(usize, Balance)> = fees + .inner() + .iter() + .enumerate() + .filter_map(|(index, asset)| { + if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = asset { + if *location == token_location { + return Some((index, (*amount).into())) + } + } + None + }) + .next(); + + if let Some((fee_index, total_fee)) = maybe_total_supplied_fee { + let remote_fee = total_fee.saturating_sub(FeeProvider::local_fee()); + if remote_fee > (0u128).into() { + // Refund remote component of fee to physical origin + deposit_or_burn_fee::( + MultiAsset { id: Concrete(token_location), fun: Fungible(remote_fee.into()) } + .into(), + context, + para_sovereign, + ); + // Return remaining fee to the next fee handler in the chain. + let mut modified_fees = fees.inner().clone(); + modified_fees.remove(fee_index); + modified_fees.push(MultiAsset { + id: Concrete(token_location), + fun: Fungible((total_fee - remote_fee).into()), + }); + return modified_fees.into() + } + } + + log::info!( + target: "xcm::fees", + "XcmExportFeeToSibling skipped: {fees:?}, context: {context:?}, reason: {reason:?}", + ); + fees + } +} diff --git a/bridges/snowbridge/parachain/runtime/tests/Cargo.toml b/bridges/snowbridge/parachain/runtime/tests/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9b5000b56cd91a839cab89a1223ddd264e3159e1 --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/tests/Cargo.toml @@ -0,0 +1,244 @@ +[package] +name = "snowbridge-runtime-tests" +description = "Snowbridge Runtime Tests" +version = "0.1.0" +authors = ["Snowfork "] +edition = "2021" +license = "Apache-2.0" + +[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 } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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-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 +rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", 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-dmp-queue = { path = "../../../../../cumulus/pallets/dmp-queue", 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 } +bridge-hub-rococo-runtime = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo", default-features = false } +asset-hub-rococo-runtime = { path = "../../../../../cumulus/parachains/runtimes/assets/asset-hub-rococo", 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 } +snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-features = false } +snowbridge-router-primitives = { path = "../../primitives/router", default-features = false } +snowbridge-ethereum-beacon-client = { path = "../../pallets/ethereum-beacon-client", default-features = false } +snowbridge-inbound-queue = { path = "../../pallets/inbound-queue", default-features = false } +snowbridge-outbound-queue = { path = "../../pallets/outbound-queue", default-features = false } +snowbridge-outbound-queue-runtime-api = { path = "../../pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-system = { path = "../../pallets/system", default-features = false } +snowbridge-system-runtime-api = { path = "../../pallets/system/runtime-api", default-features = false } + +[dev-dependencies] +static_assertions = "1.1" +bridge-hub-test-utils = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/test-utils" } +bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", features = ["integrity-test"] } +sp-keyring = { path = "../../../../../substrate/primitives/keyring" } + +[features] +default = ["std"] +std = [ + "asset-hub-rococo-runtime/std", + "assets-common/std", + "bridge-hub-rococo-runtime/std", + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-dmp-queue/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-utility/std", + "frame-benchmarking/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-collator-selection/std", + "pallet-message-queue/std", + "pallet-multisig/std", + "pallet-session/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-utility/std", + "pallet-xcm-benchmarks?/std", + "pallet-xcm/std", + "parachain-info/std", + "parachains-common/std", + "parachains-runtimes-test-utils/std", + "polkadot-core-primitives/std", + "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", + "rococo-runtime-constants/std", + "scale-info/std", + "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-ethereum-beacon-client/std", + "snowbridge-inbound-queue/std", + "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-outbound-queue/std", + "snowbridge-router-primitives/std", + "snowbridge-system-runtime-api/std", + "snowbridge-system/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-io/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-version/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "asset-hub-rococo-runtime/runtime-benchmarks", + "assets-common/runtime-benchmarks", + "bridge-hub-rococo-runtime/runtime-benchmarks", + "bridge-runtime-common/runtime-benchmarks", + "cumulus-pallet-dmp-queue/runtime-benchmarks", + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-multisig/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-ethereum-beacon-client/runtime-benchmarks", + "snowbridge-inbound-queue/runtime-benchmarks", + "snowbridge-outbound-queue/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "snowbridge-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "asset-hub-rococo-runtime/try-runtime", + "bridge-hub-rococo-runtime/try-runtime", + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-dmp-queue/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-multisig/try-runtime", + "pallet-session/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-utility/try-runtime", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "snowbridge-ethereum-beacon-client/try-runtime", + "snowbridge-inbound-queue/try-runtime", + "snowbridge-outbound-queue/try-runtime", + "snowbridge-system/try-runtime", + "sp-runtime/try-runtime", +] +beacon-spec-mainnet = [ + "snowbridge-ethereum-beacon-client/beacon-spec-mainnet", +] +experimental = ["pallet-aura/experimental"] + +# A feature that should be enabled when the runtime should be built for on-chain +# deployment. This will disable stuff that shouldn't be part of the on-chain wasm +# to make it smaller like logging for example. +on-chain-release-build = ["sp-api/disable-logging"] diff --git a/bridges/snowbridge/parachain/runtime/tests/src/lib.rs b/bridges/snowbridge/parachain/runtime/tests/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9a5d12e28926b995929242136927cb1e1038f9ec --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/tests/src/lib.rs @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +#![cfg(test)] + +mod test_cases; + +use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; +use bridge_hub_rococo_runtime::{ + xcm_config::XcmConfig, MessageQueueServiceWeight, Runtime, RuntimeEvent, SessionKeys, +}; +use codec::Decode; +use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; +use parachains_common::{AccountId, AuraId}; +use snowbridge_ethereum_beacon_client::WeightInfo; +use sp_core::H160; +use sp_keyring::AccountKeyring::Alice; + +pub fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { + bridge_hub_test_utils::CollatorSessionKeys::new( + AccountId::from(Alice), + AccountId::from(Alice), + SessionKeys { aura: AuraId::from(Alice.public()) }, + ) +} + +#[test] +pub fn transfer_token_to_ethereum_works() { + test_cases::send_transfer_token_message_success::( + 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, + } + }), + ) +} + +#[test] +pub fn unpaid_transfer_token_to_ethereum_fails_with_barrier() { + test_cases::send_unpaid_transfer_token_message::( + collator_session_keys(), + 1013, + 1000, + H160::random(), + H160::random(), + ) +} + +#[test] +pub fn transfer_token_to_ethereum_fee_not_enough() { + test_cases::send_transfer_token_message_failure::( + collator_session_keys(), + 1013, + 1000, + DefaultBridgeHubEthereumBaseFee::get() + 1_000_000_000, + H160::random(), + H160::random(), + // fee not enough + 1_000_000_000, + NotHoldingFees, + ) +} + +#[test] +pub fn transfer_token_to_ethereum_insufficient_fund() { + test_cases::send_transfer_token_message_failure::( + collator_session_keys(), + 1013, + 1000, + 1_000_000_000, + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + FailedToTransactAsset("InsufficientBalance"), + ) +} + +#[test] +fn max_message_queue_service_weight_is_more_than_beacon_extrinsic_weights() { + let max_message_queue_weight = MessageQueueServiceWeight::get(); + let force_checkpoint = + ::WeightInfo::force_checkpoint(); + let submit_checkpoint = + ::WeightInfo::submit(); + max_message_queue_weight.all_gt(force_checkpoint); + max_message_queue_weight.all_gt(submit_checkpoint); +} diff --git a/bridges/snowbridge/parachain/runtime/tests/src/test_cases.rs b/bridges/snowbridge/parachain/runtime/tests/src/test_cases.rs new file mode 100644 index 0000000000000000000000000000000000000000..e5a45cdc92bbd767d06bf6274b6630fa747821ce --- /dev/null +++ b/bridges/snowbridge/parachain/runtime/tests/src/test_cases.rs @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork + +//! Module contains predefined test-case scenarios for `Runtime` with bridging capabilities. + +use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; +use bridge_hub_rococo_runtime::EthereumSystem; +use codec::Encode; +use frame_support::{assert_err, assert_ok, traits::fungible::Mutate}; +use parachains_runtimes_test_utils::{ + AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf, XcmReceivedFrom, +}; +use sp_core::H160; +use sp_runtime::SaturatedConversion; +use xcm::{ + latest::prelude::*, + v3::Error::{self, Barrier}, +}; +use xcm_executor::XcmExecutor; + +type RuntimeHelper = + parachains_runtimes_test_utils::RuntimeHelper; + +pub fn initial_fund(assethub_parachain_id: u32, initial_amount: u128) +where + Runtime: frame_system::Config + pallet_balances::Config, +{ + // fund asset hub sovereign account enough so it can pay fees + let asset_hub_sovereign_account = + snowbridge_core::sibling_sovereign_account::(assethub_parachain_id.into()); + >::mint_into( + &asset_hub_sovereign_account, + initial_amount.saturated_into::>(), + ) + .unwrap(); +} + +pub fn send_transfer_token_message( + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, +) -> Outcome +where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_outbound_queue::Config, + XcmConfig: xcm_executor::Config, +{ + let assethub_parachain_location = MultiLocation::new(1, Parachain(assethub_parachain_id)); + let asset = MultiAsset { + id: Concrete(MultiLocation { + parents: 0, + interior: X1(AccountKey20 { network: None, key: weth_contract_address.into() }), + }), + fun: Fungible(1000000000), + }; + let assets = vec![asset.clone()]; + + let inner_xcm = Xcm(vec![ + WithdrawAsset(MultiAssets::from(assets.clone())), + ClearOrigin, + BuyExecution { fees: asset, weight_limit: Unlimited }, + DepositAsset { + assets: Wild(All), + beneficiary: MultiLocation { + parents: 0, + interior: X1(AccountKey20 { network: None, key: destination_address.into() }), + }, + }, + SetTopic([0; 32]), + ]); + + let fee = MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(fee_amount), + }; + + // prepare transfer token message + let xcm = Xcm(vec![ + WithdrawAsset(MultiAssets::from(vec![fee.clone()])), + BuyExecution { fees: fee, weight_limit: Unlimited }, + ExportMessage { + network: Ethereum { chain_id: 11155111 }, + destination: Here, + xcm: inner_xcm, + }, + ]); + + // execute XCM + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + XcmExecutor::::execute_xcm( + assethub_parachain_location, + xcm, + hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + ) +} + +pub fn send_transfer_token_message_success( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + snowbridge_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 + + cumulus_pallet_parachain_system::Config + + snowbridge_outbound_queue::Config + + snowbridge_system::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, +{ + 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(|| { + EthereumSystem::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, + DefaultBridgeHubEthereumBaseFee::get() + 1_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_outbound_queue(e.event.encode())); + assert!( + events.any(|e| matches!(e, snowbridge_outbound_queue::Event::MessageQueued { .. })) + ); + }); +} + +pub fn send_unpaid_transfer_token_message( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_contract: H160, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_outbound_queue::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, +{ + let assethub_parachain_location = MultiLocation::new(1, Parachain(assethub_parachain_id)); + + 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(|| { + let asset_hub_sovereign_account = + snowbridge_core::sibling_sovereign_account::(assethub_parachain_id.into()); + + >::mint_into( + &asset_hub_sovereign_account, + 4000000000u32.into(), + ) + .unwrap(); + + let asset = MultiAsset { + id: Concrete(MultiLocation { + parents: 0, + interior: X1(AccountKey20 { network: None, key: weth_contract_address.into() }), + }), + fun: Fungible(1000000000), + }; + let assets = vec![asset.clone()]; + + let inner_xcm = Xcm(vec![ + WithdrawAsset(MultiAssets::from(assets.clone())), + ClearOrigin, + BuyExecution { fees: asset, weight_limit: Unlimited }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: MultiLocation { + parents: 0, + interior: X1(AccountKey20 { + network: None, + key: destination_contract.into(), + }), + }, + }, + SetTopic([0; 32]), + ]); + + // prepare transfer token message + let xcm = Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + ExportMessage { + network: Ethereum { chain_id: 11155111 }, + destination: Here, + xcm: inner_xcm, + }, + ]); + + // execute XCM + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + let outcome = XcmExecutor::::execute_xcm( + assethub_parachain_location, + xcm, + hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + ); + // check error is barrier + assert_err!(outcome.ensure_complete(), Barrier); + }); +} + +#[allow(clippy::too_many_arguments)] +pub fn send_transfer_token_message_failure( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + initial_amount: u128, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + expected_error: Error, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_outbound_queue::Config + + snowbridge_system::Config, + XcmConfig: xcm_executor::Config, + ValidatorIdOf: From>, +{ + 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(|| { + EthereumSystem::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, initial_amount); + + let outcome = send_transfer_token_message::( + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + // check err is NotHoldingFees + assert_err!(outcome.ensure_complete(), expected_error); + }); +} diff --git a/bridges/snowbridge/parachain/scripts/benchmark.sh b/bridges/snowbridge/parachain/scripts/benchmark.sh new file mode 100755 index 0000000000000000000000000000000000000000..c47649b2eebe213e45b2c2a18393dd0dbb85f45f --- /dev/null +++ b/bridges/snowbridge/parachain/scripts/benchmark.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# Example command for updating pallet benchmarking +pushd ../cumulus +cargo run --release --bin polkadot-parachain \ +--features runtime-benchmarks \ +-- \ +benchmark pallet \ +--chain=bridge-hub-rococo-dev \ +--pallet=snowbridge_ethereum_beacon_client \ +--extrinsic="*" \ +--execution=wasm --wasm-execution=compiled \ +--steps 50 --repeat 20 \ +--output ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs +popd diff --git a/bridges/snowbridge/parachain/scripts/hexliteral.sh b/bridges/snowbridge/parachain/scripts/hexliteral.sh new file mode 100755 index 0000000000000000000000000000000000000000..e34a2b9b5151a667b10354d987d89a187b807b1c --- /dev/null +++ b/bridges/snowbridge/parachain/scripts/hexliteral.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Creates a string constant from STDIN +echo "const DATA: &'static str = concat!(" +cat - | fold | sed 's/^.*/\t"&",/' +echo ");" \ No newline at end of file diff --git a/bridges/snowbridge/parachain/scripts/init.sh b/bridges/snowbridge/parachain/scripts/init.sh new file mode 100755 index 0000000000000000000000000000000000000000..1405a41ef333e6af863080d83f854d3edb5fb4fa --- /dev/null +++ b/bridges/snowbridge/parachain/scripts/init.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +echo "*** Initializing WASM build environment" + +if [ -z $CI_PROJECT_NAME ] ; then + rustup update nightly + rustup update stable +fi + +rustup target add wasm32-unknown-unknown --toolchain nightly diff --git a/bridges/snowbridge/parachain/scripts/make-build-config.sh b/bridges/snowbridge/parachain/scripts/make-build-config.sh new file mode 100755 index 0000000000000000000000000000000000000000..a1b116a5dd0c18e00213599918d7f2825f9cdf2f --- /dev/null +++ b/bridges/snowbridge/parachain/scripts/make-build-config.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cd ../ethereum + +truffle exec scripts/dumpParachainConfig.js | sed '/^Using/d;/^$/d' diff --git a/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh b/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh new file mode 100755 index 0000000000000000000000000000000000000000..f060cf958b75800cc6c8e1e86940b4ffea188db5 --- /dev/null +++ b/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh @@ -0,0 +1,113 @@ +#!/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/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/.config +rm -rf $SNOWBRIDGE_FOLDER/parachain/pallets/ethereum-beacon-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-ethereum-beacon-client +cargo check -p snowbridge-ethereum-beacon-client --features runtime-benchmarks +cargo check -p snowbridge-ethereum-beacon-client --features try-runtime +cargo check -p snowbridge-inbound-queue +cargo check -p snowbridge-inbound-queue --features runtime-benchmarks +cargo check -p snowbridge-inbound-queue --features try-runtime +cargo check -p snowbridge-outbound-queue +cargo check -p snowbridge-outbound-queue --features runtime-benchmarks +cargo check -p snowbridge-outbound-queue --features try-runtime +cargo check -p snowbridge-system +cargo check -p snowbridge-system --features runtime-benchmarks +cargo check -p snowbridge-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/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 35945bf4052f8f58b54e3a1235ba77f682314a6f..1bf25f3963a4194f13aba44b6906776f5dc1a266 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -6,8 +6,11 @@ edition.workspace = true description = "Parachain node CLI utilities." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } url = "2.4.0" @@ -18,3 +21,4 @@ sc-chain-spec = { path = "../../../substrate/client/chain-spec" } sc-service = { path = "../../../substrate/client/service" } sp-core = { path = "../../../substrate/primitives/core" } sp-runtime = { path = "../../../substrate/primitives/runtime" } +sp-blockchain = { path = "../../../substrate/primitives/blockchain" } diff --git a/cumulus/client/cli/src/lib.rs b/cumulus/client/cli/src/lib.rs index 7e78afe6efb4709dea88e041976990f694ca619b..1cebecb004312f553cb49e910841c5f8f2f310b2 100644 --- a/cumulus/client/cli/src/lib.rs +++ b/cumulus/client/cli/src/lib.rs @@ -23,20 +23,18 @@ use std::{ io::{self, Write}, net::SocketAddr, path::PathBuf, + sync::Arc, }; use codec::Encode; use sc_chain_spec::ChainSpec; -use sc_client_api::ExecutorProvider; +use sc_client_api::HeaderBackend; use sc_service::{ config::{PrometheusConfig, TelemetryEndpoints}, BasePath, TransactionPoolOptions, }; use sp_core::hexdisplay::HexDisplay; -use sp_runtime::{ - traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, - StateVersion, -}; +use sp_runtime::traits::{Block as BlockT, Zero}; use url::Url; /// The `purge-chain` command used to remove the whole chain: the parachain and the relay chain. @@ -129,9 +127,9 @@ impl sc_cli::CliConfiguration for PurgeChainCmd { } } -/// Command for exporting the genesis state of the parachain +/// Command for exporting the genesis head data of the parachain #[derive(Debug, clap::Parser)] -pub struct ExportGenesisStateCommand { +pub struct ExportGenesisHeadCommand { /// Output file name or stdout if unspecified. #[arg()] pub output: Option, @@ -145,24 +143,29 @@ pub struct ExportGenesisStateCommand { pub shared_params: sc_cli::SharedParams, } -impl ExportGenesisStateCommand { - /// Run the export-genesis-state command - pub fn run( - &self, - chain_spec: &dyn ChainSpec, - client: &impl ExecutorProvider, - ) -> sc_cli::Result<()> { - let state_version = sc_chain_spec::resolve_state_version_from_wasm( - &chain_spec.build_storage()?, - client.executor(), - )?; - - let block: Block = generate_genesis_block(chain_spec, state_version)?; - let raw_header = block.header().encode(); +impl ExportGenesisHeadCommand { + /// Run the export-genesis-head command + pub fn run(&self, client: Arc) -> sc_cli::Result<()> + where + B: BlockT, + C: HeaderBackend + 'static, + { + let genesis_hash = client.hash(Zero::zero())?.ok_or(sc_cli::Error::Client( + sp_blockchain::Error::Backend( + "Failed to lookup genesis block hash when exporting genesis head data.".into(), + ), + ))?; + let genesis_header = client.header(genesis_hash)?.ok_or(sc_cli::Error::Client( + sp_blockchain::Error::Backend( + "Failed to lookup genesis header by hash when exporting genesis head data.".into(), + ), + ))?; + + let raw_header = genesis_header.encode(); let output_buf = if self.raw { raw_header } else { - format!("0x{:?}", HexDisplay::from(&block.header().encode())).into_bytes() + format!("0x{:?}", HexDisplay::from(&genesis_header.encode())).into_bytes() }; if let Some(output) = &self.output { @@ -175,43 +178,7 @@ impl ExportGenesisStateCommand { } } -/// Generate the genesis block from a given ChainSpec. -pub fn generate_genesis_block( - chain_spec: &dyn ChainSpec, - genesis_state_version: StateVersion, -) -> Result { - let storage = chain_spec.build_storage()?; - - let child_roots = storage.children_default.iter().map(|(sk, child_content)| { - let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( - child_content.data.clone().into_iter().collect(), - genesis_state_version, - ); - (sk.clone(), state_root.encode()) - }); - let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( - storage.top.clone().into_iter().chain(child_roots).collect(), - genesis_state_version, - ); - - let extrinsics_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( - Vec::new(), - genesis_state_version, - ); - - Ok(Block::new( - <::Header as HeaderT>::new( - Zero::zero(), - extrinsics_root, - state_root, - Default::default(), - Default::default(), - ), - Default::default(), - )) -} - -impl sc_cli::CliConfiguration for ExportGenesisStateCommand { +impl sc_cli::CliConfiguration for ExportGenesisHeadCommand { fn shared_params(&self) -> &sc_cli::SharedParams { &self.shared_params } diff --git a/cumulus/client/collator/Cargo.toml b/cumulus/client/collator/Cargo.toml index 7ac0bbfe6f126eaff4bd9403db2a4204147d367d..5aa260eae1b4c34c158194bbcb299b95448e7a76 100644 --- a/cumulus/client/collator/Cargo.toml +++ b/cumulus/client/collator/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Common node-side functionality and glue code to collate parachain blocks." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] parking_lot = "0.12.1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } @@ -31,7 +34,7 @@ cumulus-client-network = { path = "../network" } cumulus-primitives-core = { path = "../../primitives/core" } [dev-dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" # Substrate sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index e07f10d60900c413b055b4b58435b8eb256ba411..d022fe4df7e5a89725af4d6c9201cedc3777ebe4 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -6,8 +6,11 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.28" tracing = "0.1.37" @@ -38,9 +41,9 @@ substrate-prometheus-endpoint = { path = "../../../../substrate/utils/prometheus cumulus-client-consensus-common = { path = "../common" } cumulus-relay-chain-interface = { path = "../../relay-chain-interface" } cumulus-client-consensus-proposer = { path = "../proposer" } +cumulus-client-parachain-inherent = { path = "../../../client/parachain-inherent" } cumulus-primitives-aura = { path = "../../../primitives/aura" } cumulus-primitives-core = { path = "../../../primitives/core" } -cumulus-primitives-parachain-inherent = { path = "../../../primitives/parachain-inherent" } cumulus-client-collator = { path = "../../collator" } # Polkadot diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index b00c3952e2bc90e057dc464ac4b9179927cd67da..db0799235bca27aaa4456da6c8649b0b76fef030 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -30,10 +30,10 @@ use cumulus_client_consensus_common::{ self as consensus_common, ParachainBlockImportMarker, ParachainCandidate, }; use cumulus_client_consensus_proposer::ProposerInterface; +use cumulus_client_parachain_inherent::{ParachainInherentData, ParachainInherentDataProvider}; use cumulus_primitives_core::{ relay_chain::Hash as PHash, DigestItem, ParachainBlockData, PersistedValidationData, }; -use cumulus_primitives_parachain_inherent::ParachainInherentData; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::{Collation, MaybeCompressedPoV}; @@ -124,7 +124,7 @@ where parent_hash: Block::Hash, timestamp: impl Into>, ) -> Result<(ParachainInherentData, InherentData), Box> { - let paras_inherent_data = ParachainInherentData::create_at( + let paras_inherent_data = ParachainInherentDataProvider::create_at( relay_parent, &self.relay_client, validation_data, @@ -172,12 +172,14 @@ where inherent_data: (ParachainInherentData, InherentData), proposal_duration: Duration, max_pov_size: usize, - ) -> Result<(Collation, ParachainBlockData, Block::Hash), Box> - { + ) -> Result< + Option<(Collation, ParachainBlockData, Block::Hash)>, + Box, + > { let mut digest = additional_pre_digest.into().unwrap_or_default(); digest.push(slot_claim.pre_digest.clone()); - let proposal = self + let maybe_proposal = self .proposer .propose( &parent_header, @@ -190,6 +192,11 @@ where .await .map_err(|e| Box::new(e) as Box)?; + let proposal = match maybe_proposal { + None => return Ok(None), + Some(p) => p, + }; + let sealed_importable = seal::<_, P>( proposal.block, proposal.storage_changes, @@ -234,7 +241,7 @@ where ); } - Ok((collation, block_data, post_hash)) + Ok(Some((collation, block_data, post_hash))) } else { Err(Box::::from("Unable to produce collation") as Box) diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index dc0078b0d6a9838534b5599c8838b261c470b7b3..78f6b726aff0cb63cd08259c327bfbda71c05b8b 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -203,7 +203,7 @@ where .await ); - let (collation, _, post_hash) = try_request!( + let maybe_collation = try_request!( collator .collate( &parent_header, @@ -220,8 +220,14 @@ where .await ); - let result_sender = Some(collator.collator_service().announce_with_barrier(post_hash)); - request.complete(Some(CollationResult { collation, result_sender })); + if let Some((collation, _, post_hash)) = maybe_collation { + let result_sender = + Some(collator.collator_service().announce_with_barrier(post_hash)); + request.complete(Some(CollationResult { collation, result_sender })); + } else { + request.complete(None); + tracing::debug!(target: crate::LOG_TARGET, "No block proposal"); + } } } } diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 57cd646fbcdef58fca265454eeeb677c35beee7b..5d62094e4aa1746b4301a3d98614d2b322d1d05e 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -359,7 +359,7 @@ where ) .await { - Ok((collation, block_data, new_block_hash)) => { + Ok(Some((collation, block_data, new_block_hash))) => { // Here we are assuming that the import logic protects against equivocations // and provides sybil-resistance, as it should. collator.collator_service().announce_block(new_block_hash, None); @@ -387,6 +387,10 @@ where parent_hash = new_block_hash; parent_header = block_data.into_header(); }, + Ok(None) => { + tracing::debug!(target: crate::LOG_TARGET, "No block proposal"); + break + }, Err(err) => { tracing::error!(target: crate::LOG_TARGET, ?err); break diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 92918cd7b5b87c829d69b335738e992a4571528d..e7fc7a88640e2c1f9576817549fd7cebff62fb9e 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -6,10 +6,13 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -dyn-clone = "1.0.12" +dyn-clone = "1.0.16" futures = "0.3.28" log = "0.4.20" tracing = "0.1.37" diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index 4cfba66cec371835f36afefc84eb8abbc50c1a91..107f466ca1049623c14c009f060deb010c2d8ac5 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -6,9 +6,12 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] anyhow = "1.0" -async-trait = "0.1.73" +async-trait = "0.1.74" thiserror = "1.0.48" # Substrate diff --git a/cumulus/client/consensus/proposer/src/lib.rs b/cumulus/client/consensus/proposer/src/lib.rs index 7404651bcd96cc40eeecc3c465b2fafffc25c567..7eb90a5ac02bbbea708406980b9a8c2e06d1708e 100644 --- a/cumulus/client/consensus/proposer/src/lib.rs +++ b/cumulus/client/consensus/proposer/src/lib.rs @@ -76,7 +76,7 @@ pub trait ProposerInterface { inherent_digests: Digest, max_duration: Duration, block_size_limit: Option, - ) -> Result, Error>; + ) -> Result>, Error>; } /// A simple wrapper around a Substrate proposer for creating collations. @@ -109,7 +109,7 @@ where inherent_digests: Digest, max_duration: Duration, block_size_limit: Option, - ) -> Result, Error> { + ) -> Result>, Error> { let proposer = self .inner .init(parent_header) @@ -127,6 +127,7 @@ where proposer .propose(inherent_data, inherent_digests, max_duration, block_size_limit) .await + .map(Some) .map_err(|e| Error::proposing(anyhow::Error::new(e)).into()) } } diff --git a/cumulus/client/consensus/relay-chain/Cargo.toml b/cumulus/client/consensus/relay-chain/Cargo.toml index de280e6e9a890fd7e4680b3ab8a1111d6a02b717..d7702809779db34e0cf47f953e45475e37b674c4 100644 --- a/cumulus/client/consensus/relay-chain/Cargo.toml +++ b/cumulus/client/consensus/relay-chain/Cargo.toml @@ -6,8 +6,11 @@ authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" futures = "0.3.28" parking_lot = "0.12.1" tracing = "0.1.37" diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index 3893647e7c58b0ba5d23df7b3ee6c9e24994c38f..edd349155fa6b2f6659f445683c7b3605ac14386 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -6,8 +6,11 @@ description = "Cumulus-specific networking protocol" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.28" futures-timer = "3.0.2" diff --git a/cumulus/client/parachain-inherent/Cargo.toml b/cumulus/client/parachain-inherent/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b6d477519ecc6bf20747f9286490b43eff6ae9e5 --- /dev/null +++ b/cumulus/client/parachain-inherent/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "cumulus-client-parachain-inherent" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." +license = "Apache-2.0" + +[dependencies] +async-trait = "0.1.73" +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +scale-info = { version = "2.10.0", features = ["derive"] } +tracing = { version = "0.1.37" } + +# Substrate +sc-client-api = { path = "../../../substrate/client/api" } +sp-api = { path = "../../../substrate/primitives/api" } +sp-core = { path = "../../../substrate/primitives/core" } +sp-inherents = { path = "../../../substrate/primitives/inherents" } +sp-runtime = { path = "../../../substrate/primitives/runtime" } +sp-state-machine = { path = "../../../substrate/primitives/state-machine" } +sp-std = { path = "../../../substrate/primitives/std" } +sp-storage = { path = "../../../substrate/primitives/storage" } +sp-trie = { path = "../../../substrate/primitives/trie" } + +# Cumulus +cumulus-primitives-core = { path = "../../primitives/core" } +cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } +cumulus-relay-chain-interface = { path = "../relay-chain-interface" } +cumulus-test-relay-sproof-builder = { path = "../../test/relay-sproof-builder" } diff --git a/cumulus/primitives/parachain-inherent/src/client_side.rs b/cumulus/client/parachain-inherent/src/lib.rs similarity index 91% rename from cumulus/primitives/parachain-inherent/src/client_side.rs rename to cumulus/client/parachain-inherent/src/lib.rs index 52987d2da44ceee275b88a441f5a64951cf245f2..57353638e197acf255b73080c6802e94647c2090 100644 --- a/cumulus/primitives/parachain-inherent/src/client_side.rs +++ b/cumulus/client/parachain-inherent/src/lib.rs @@ -16,7 +16,6 @@ //! Client side code for generating the parachain inherent. -use crate::ParachainInherentData; use codec::Decode; use cumulus_primitives_core::{ relay_chain::{self, Hash as PHash, HrmpChannelId}, @@ -24,6 +23,11 @@ use cumulus_primitives_core::{ }; use cumulus_relay_chain_interface::RelayChainInterface; +mod mock; + +pub use cumulus_primitives_parachain_inherent::{ParachainInherentData, INHERENT_IDENTIFIER}; +pub use mock::{MockValidationDataInherentDataProvider, MockXcmConfig}; + const LOG_TARGET: &str = "parachain-inherent"; /// Collect the relevant relay chain state in form of a proof for putting it into the validation @@ -132,7 +136,9 @@ async fn collect_relay_storage_proof( .ok() } -impl ParachainInherentData { +pub struct ParachainInherentDataProvider; + +impl ParachainInherentDataProvider { /// Create the [`ParachainInherentData`] at the given `relay_parent`. /// /// Returns `None` if the creation failed. @@ -178,21 +184,3 @@ impl ParachainInherentData { }) } } - -#[async_trait::async_trait] -impl sp_inherents::InherentDataProvider for ParachainInherentData { - async fn provide_inherent_data( - &self, - inherent_data: &mut sp_inherents::InherentData, - ) -> Result<(), sp_inherents::Error> { - inherent_data.put_data(crate::INHERENT_IDENTIFIER, &self) - } - - async fn try_handle_error( - &self, - _: &sp_inherents::InherentIdentifier, - _: &[u8], - ) -> Option> { - None - } -} diff --git a/cumulus/primitives/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs similarity index 97% rename from cumulus/primitives/parachain-inherent/src/mock.rs rename to cumulus/client/parachain-inherent/src/mock.rs index e40cb49acddd1f7cd9d01e37c4f1c869a3043c6a..7af10a661e09f32ce3a2481a8b3a003ec4928266 100644 --- a/cumulus/primitives/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -19,6 +19,7 @@ use codec::Decode; use cumulus_primitives_core::{ relay_chain, InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; +use cumulus_primitives_parachain_inherent::MessageQueueChain; use sc_client_api::{Backend, StorageProvider}; use sp_core::twox_128; use sp_inherents::{InherentData, InherentDataProvider}; @@ -168,7 +169,7 @@ impl> InherentDataProvider // Process the downward messages and set up the correct head let mut downward_messages = Vec::new(); - let mut dmq_mqc = crate::MessageQueueChain(self.xcm_config.starting_dmq_mqc_head); + let mut dmq_mqc = MessageQueueChain::new(self.xcm_config.starting_dmq_mqc_head); for msg in &self.raw_downward_messages { let wrapped = InboundDownwardMessage { sent_at: relay_parent_number, msg: msg.clone() }; @@ -188,7 +189,7 @@ impl> InherentDataProvider // Now iterate again, updating the heads as we go for (para_id, messages) in &horizontal_messages { - let mut channel_mqc = crate::MessageQueueChain( + let mut channel_mqc = MessageQueueChain::new( *self .xcm_config .starting_hrmp_mqc_heads diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 29f793c732876dbebe9cf5fc53b34fb08194bedc..ad55e0e9c4b846306abf3101c89fc4d4c35e2d7a 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -6,6 +6,9 @@ description = "Cumulus-specific networking protocol" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.28" @@ -29,7 +32,7 @@ polkadot-primitives = { path = "../../../polkadot/primitives" } # Cumulus cumulus-primitives-core = { path = "../../primitives/core" } cumulus-relay-chain-interface = { path = "../relay-chain-interface" } -async-trait = "0.1.73" +async-trait = "0.1.74" [dev-dependencies] tokio = { version = "1.32.0", features = ["macros"] } diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 1d414736503299aee855f6326361a6670852181c..63f4c915474363467205c5d9b49226f56fb30f27 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -6,8 +6,11 @@ edition.workspace = true description = "Implementation of the RelayChainInterface trait for Polkadot full-nodes." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] -async-trait = "0.1.73" +async-trait = "0.1.74" futures = "0.3.28" futures-timer = "3.0.2" diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index c9d50afe8fa9d225ae9cd07396b065f0af0fda3a..5100119a2e49f41e7822f9810589b3813f484328 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Common interface for different relay chain datasources." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] polkadot-overseer = { path = "../../../polkadot/node/overseer" } @@ -17,7 +20,7 @@ sp-state-machine = { path = "../../../substrate/primitives/state-machine" } sc-client-api = { path = "../../../substrate/client/api" } futures = "0.3.28" -async-trait = "0.1.73" +async-trait = "0.1.74" thiserror = "1.0.48" jsonrpsee-core = "0.16.2" parity-scale-codec = "3.6.4" diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml index acaed5a4f6c196627cd84f3f2b317ddadd0ed507..45b958998bd8447416d47c56b1aa1d9ef07b90a7 100644 --- a/cumulus/client/relay-chain-minimal-node/Cargo.toml +++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Minimal node implementation to be used in tandem with RPC or light-client mode." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] # polkadot deps polkadot-primitives = { path = "../../../polkadot/primitives" } @@ -45,6 +48,6 @@ cumulus-primitives-core = { path = "../../primitives/core" } array-bytes = "6.1" tracing = "0.1.37" -async-trait = "0.1.73" +async-trait = "0.1.74" futures = "0.3.28" parking_lot = "0.12.1" diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index d9e4155d9c5e3e24bfde58c7cfe098f3199e1c80..ab56b62c4ca59b3058e9724032e2e09264a45640 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -24,7 +24,7 @@ use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ async_backing::{AsyncBackingParams, BackingState}, slashing, - vstaging::NodeFeatures, + vstaging::{ApprovalVotingParams, NodeFeatures}, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; use sc_client_api::AuxStore; @@ -427,6 +427,18 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { Ok(self.rpc_client.parachain_host_para_backing_state(at, para_id).await?) } + /// Approval voting configuration parameters + async fn approval_voting_params( + &self, + at: Hash, + session_index: polkadot_primitives::SessionIndex, + ) -> Result { + Ok(self + .rpc_client + .parachain_host_staging_approval_voting_params(at, session_index) + .await?) + } + async fn node_features(&self, at: Hash) -> Result { Ok(self.rpc_client.parachain_host_node_features(at).await?) } diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index 11d8bc9b4df83da81eccc8e48df85ca92fec0ac0..515c8ead32aaa56dfb59eb1df0e42c712973c3d4 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true description = "Implementation of the RelayChainInterface trait that connects to a remote RPC-node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true [dependencies] polkadot-overseer = { path = "../../../polkadot/node/overseer" } @@ -33,10 +35,10 @@ futures-timer = "3.0.2" parity-scale-codec = "3.6.4" jsonrpsee = { version = "0.16.2", features = ["ws-client"] } tracing = "0.1.37" -async-trait = "0.1.73" +async-trait = "0.1.74" url = "2.4.0" -serde_json = "1.0.108" -serde = "1.0.193" +serde_json = "1.0.111" +serde = "1.0.195" 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"] } 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 8e0d5fae6777c2c3ba69b46083444a5423dd9eb9..c64fff77a29fd016d7e1723ab461dc8082770682 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -32,7 +32,7 @@ use cumulus_primitives_core::{ relay_chain::{ async_backing::{AsyncBackingParams, BackingState}, slashing, - vstaging::NodeFeatures, + vstaging::{ApprovalVotingParams, NodeFeatures}, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, OccupiedCoreAssumption, @@ -625,6 +625,19 @@ impl RelayChainRpcClient { } #[allow(missing_docs)] + pub async fn parachain_host_staging_approval_voting_params( + &self, + at: RelayHash, + _session_index: SessionIndex, + ) -> Result { + self.call_remote_runtime_function( + "ParachainHost_staging_approval_voting_params", + at, + None::<()>, + ) + .await + } + pub async fn parachain_host_para_backing_state( &self, at: RelayHash, diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index 55623276eaf50ffee3cc05398978da89b30caded..997413ad0da8302c615dc5b6738d7871580425ea 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Common functions used to assemble the components of a parachain node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +[lints] +workspace = true + [dependencies] futures = "0.3.28" diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index 16f73aa540e67e8fe0afd44b1a49183acf3fee55..14dcd10ddfcbfb42f38e068ac7b75c7ee6356f51 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "AURA consensus extension pallet for parachains" license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 76efbf1caf6cdf79fecf2c0bd2f6300a75deda0d..9c2af8893ca11ecf005be4c14ee1b718a3674f5c 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -9,6 +9,9 @@ readme = "README.md" repository.workspace = true version = "3.0.0" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs index 46143674bb39991f55394b1c730bae38d9da29e1..ab9ad5ec11a21420c289f92803124b7106a48554 100644 --- a/cumulus/pallets/collator-selection/src/mock.rs +++ b/cumulus/pallets/collator-selection/src/mock.rs @@ -16,7 +16,7 @@ use super::*; use crate as collator_selection; use frame_support::{ - ord_parameter_types, parameter_types, + derive_impl, ord_parameter_types, parameter_types, traits::{ConstBool, ConstU32, ConstU64, FindAuthor, ValidatorRegistration}, PalletId, }; @@ -50,6 +50,7 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 0b64410433fdf9a47ed7bfa6d7df3aedda7dba32..bdcee0f5ff857a9323b4b3568d7b97bad4630dd6 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -7,6 +7,9 @@ repository.workspace = true description = "Migrates messages from the old DMP queue pallet." license = "Apache-2.0" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 187cf21cea67d44eeea1626b799122405634f7c8..d24fdfe101e9e94b7d84008cc0ff909b630defd1 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Base pallet for cumulus-based parachains" license = "Apache-2.0" +[lints] +workspace = true + [dependencies] bytes = { version = "1.4.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index a0f18237c792df3dc8404f15000342d027b0409d..bbef5d05579e24edbb5f40532881faca204df9b4 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -6,14 +6,17 @@ edition.workspace = true description = "Proc macros provided by the parachain-system pallet" license = "Apache-2.0" +[lints] +workspace = true + [lib] proc-macro = true [dependencies] -syn = "2.0.39" +syn = "2.0.48" proc-macro2 = "1.0.64" quote = "1.0.33" -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" [features] default = ["std"] diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index bac1ee28a7ca3abe17bea66ad3bac4830d62bdc1..5a0fa57fb171c60f13b87d70305a6d58dad475a1 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -27,7 +27,7 @@ //! //! Users must ensure that they register this pallet as an inherent provider. -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode}; use cumulus_primitives_core::{ relay_chain, AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo, GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, MessageSendError, @@ -50,10 +50,9 @@ use scale_info::TypeInfo; use sp_runtime::{ traits::{Block as BlockT, BlockNumberProvider, Hash}, transaction_validity::{ - InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity, - ValidTransaction, + InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, }, - BoundedSlice, DispatchError, FixedU128, RuntimeDebug, Saturating, + BoundedSlice, FixedU128, RuntimeDebug, Saturating, }; use sp_std::{cmp, collections::btree_map::BTreeMap, prelude::*}; use xcm::latest::XcmHash; @@ -169,20 +168,6 @@ impl CheckAssociatedRelayNumber for RelayNumberMonotonicallyIncreases { } } -/// Information needed when a new runtime binary is submitted and needs to be authorized before -/// replacing the current runtime. -#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] -#[scale_info(skip_type_params(T))] -struct CodeUpgradeAuthorization -where - T: Config, -{ - /// Hash of the new runtime binary. - code_hash: T::Hash, - /// Whether or not to carry out version checks. - check_version: bool, -} - /// The max length of a DMP message. pub type MaxDmpMessageLenOf = <::DmpQueue as HandleMessage>::MaxMessageLen; @@ -204,7 +189,7 @@ pub mod ump_constants { pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + use frame_system::{pallet_prelude::*, WeightInfo as SystemWeightInfo}; #[pallet::pallet] #[pallet::storage_version(migration::STORAGE_VERSION)] @@ -677,16 +662,18 @@ pub mod pallet { /// /// This call requires Root origin. #[pallet::call_index(2)] - #[pallet::weight((1_000_000, DispatchClass::Operational))] + #[pallet::weight(::SystemWeightInfo::authorize_upgrade())] + #[allow(deprecated)] + #[deprecated( + note = "To be removed after June 2024. Migrate to `frame_system::authorize_upgrade`." + )] pub fn authorize_upgrade( origin: OriginFor, code_hash: T::Hash, check_version: bool, ) -> DispatchResult { ensure_root(origin)?; - AuthorizedUpgrade::::put(CodeUpgradeAuthorization { code_hash, check_version }); - - Self::deposit_event(Event::UpgradeAuthorized { code_hash }); + frame_system::Pallet::::do_authorize_upgrade(code_hash, check_version); Ok(()) } @@ -700,15 +687,17 @@ pub mod pallet { /// /// All origins are allowed. #[pallet::call_index(3)] - #[pallet::weight({1_000_000})] + #[pallet::weight(::SystemWeightInfo::apply_authorized_upgrade())] + #[allow(deprecated)] + #[deprecated( + note = "To be removed after June 2024. Migrate to `frame_system::apply_authorized_upgrade`." + )] pub fn enact_authorized_upgrade( _: OriginFor, code: Vec, ) -> DispatchResultWithPostInfo { - Self::validate_authorized_upgrade(&code[..])?; - Self::schedule_code_upgrade(code)?; - AuthorizedUpgrade::::kill(); - Ok(Pays::No.into()) + let post = frame_system::Pallet::::do_apply_authorize_upgrade(code)?; + Ok(post) } } @@ -721,8 +710,6 @@ pub mod pallet { ValidationFunctionApplied { relay_chain_block_num: RelayChainBlockNumber }, /// The relay-chain aborted the upgrade process. ValidationFunctionDiscarded, - /// An upgrade has been authorized. - UpgradeAuthorized { code_hash: T::Hash }, /// Some downward messages have been received and will be processed. DownwardMessagesReceived { count: u32 }, /// Downward messages were processed using the given weight. @@ -928,10 +915,6 @@ pub mod pallet { #[pallet::storage] pub(super) type ReservedDmpWeightOverride = StorageValue<_, Weight>; - /// The next authorized upgrade, if there is one. - #[pallet::storage] - pub(super) type AuthorizedUpgrade = StorageValue<_, CodeUpgradeAuthorization>; - /// A custom head data that should be returned as result of `validate_block`. /// /// See `Pallet::set_custom_validation_head_data` for more information. @@ -982,7 +965,8 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { if let Call::enact_authorized_upgrade { ref code } = call { - if let Ok(hash) = Self::validate_authorized_upgrade(code) { + if let Ok(hash) = frame_system::Pallet::::validate_authorized_upgrade(&code[..]) + { return Ok(ValidTransaction { priority: 100, requires: Vec::new(), @@ -1001,21 +985,6 @@ pub mod pallet { } impl Pallet { - fn validate_authorized_upgrade(code: &[u8]) -> Result { - let authorization = AuthorizedUpgrade::::get().ok_or(Error::::NothingAuthorized)?; - - // ensure that the actual hash matches the authorized hash - let actual_hash = T::Hashing::hash(code); - ensure!(actual_hash == authorization.code_hash, Error::::Unauthorized); - - // check versions if required as part of the authorization - if authorization.check_version { - frame_system::Pallet::::can_set_code(code)?; - } - - Ok(actual_hash) - } - /// Get the unincluded segment size after the given hash. /// /// If the unincluded segment doesn't contain the given hash, this returns the @@ -1563,8 +1532,8 @@ impl Pallet { } } +/// Type that implements `SetCode`. pub struct ParachainSetCode(sp_std::marker::PhantomData); - impl frame_system::SetCode for ParachainSetCode { fn set_code(code: Vec) -> DispatchResult { Pallet::::schedule_code_upgrade(code) @@ -1630,7 +1599,7 @@ impl Pallet { /// Get the relay chain block number which was used as an anchor for the last block in this /// chain. - pub fn last_relay_block_number(&self) -> RelayChainBlockNumber { + pub fn last_relay_block_number() -> RelayChainBlockNumber { LastRelayChainBlockNumber::::get() } } @@ -1714,20 +1683,33 @@ pub trait RelaychainStateProvider { } /// Implements [`BlockNumberProvider`] that returns relay chain block number fetched from validation -/// data. When validation data is not available (e.g. within on_initialize), 0 will be returned. +/// data. +/// +/// When validation data is not available (e.g. within `on_initialize`), it will fallback to use +/// [`Pallet::last_relay_block_number()`]. /// /// **NOTE**: This has been deprecated, please use [`RelaychainDataProvider`] #[deprecated = "Use `RelaychainDataProvider` instead"] -pub struct RelaychainBlockNumberProvider(sp_std::marker::PhantomData); +pub type RelaychainBlockNumberProvider = RelaychainDataProvider; -#[allow(deprecated)] -impl BlockNumberProvider for RelaychainBlockNumberProvider { +/// Implements [`BlockNumberProvider`] and [`RelaychainStateProvider`] that returns relevant relay +/// data fetched from validation data. +/// +/// NOTE: When validation data is not available (e.g. within `on_initialize`): +/// +/// - [`current_relay_chain_state`](Self::current_relay_chain_state): Will return the default value +/// of [`RelayChainState`]. +/// - [`current_block_number`](Self::current_block_number): Will return +/// [`Pallet::last_relay_block_number()`]. +pub struct RelaychainDataProvider(sp_std::marker::PhantomData); + +impl BlockNumberProvider for RelaychainDataProvider { type BlockNumber = relay_chain::BlockNumber; fn current_block_number() -> relay_chain::BlockNumber { Pallet::::validation_data() .map(|d| d.relay_parent_number) - .unwrap_or_default() + .unwrap_or_else(|| Pallet::::last_relay_block_number()) } #[cfg(feature = "runtime-benchmarks")] @@ -1770,33 +1752,3 @@ impl RelaychainStateProvider for RelaychainDataProvider { ValidationData::::put(validation_data) } } - -/// Implements [`BlockNumberProvider`] and [`RelaychainStateProvider`] that returns relevant relay -/// data fetched from validation data. -/// NOTE: When validation data is not available (e.g. within on_initialize), default values will be -/// returned. -pub struct RelaychainDataProvider(sp_std::marker::PhantomData); - -impl BlockNumberProvider for RelaychainDataProvider { - type BlockNumber = relay_chain::BlockNumber; - - fn current_block_number() -> relay_chain::BlockNumber { - Pallet::::validation_data() - .map(|d| d.relay_parent_number) - .unwrap_or_default() - } - - #[cfg(feature = "runtime-benchmarks")] - fn set_block_number(block: Self::BlockNumber) { - let mut validation_data = Pallet::::validation_data().unwrap_or_else(|| - // PersistedValidationData does not impl default in non-std - PersistedValidationData { - parent_head: vec![].into(), - relay_parent_number: Default::default(), - max_pov_size: Default::default(), - relay_parent_storage_root: Default::default(), - }); - validation_data.relay_parent_number = block; - ValidationData::::put(validation_data) - } -} diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index ab1775b40a84451e150ad979cbf8b3f7888e8ccf..7528d3d9fe8d97a24602c4206f5489f1602bd957 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -1127,8 +1127,9 @@ fn upgrade_version_checks_should_work() { let new_code = vec![1, 2, 3, 4]; let new_code_hash = H256(sp_core::blake2_256(&new_code)); - let _authorize = - ParachainSystem::authorize_upgrade(RawOrigin::Root.into(), new_code_hash, true); + #[allow(deprecated)] + let _authorize = ParachainSystem::authorize_upgrade(RawOrigin::Root.into(), new_code_hash, true); + #[allow(deprecated)] let res = ParachainSystem::enact_authorized_upgrade(RawOrigin::None.into(), new_code); assert_eq!(expected.map_err(DispatchErrorWithPostInfo::from), res); diff --git a/cumulus/pallets/session-benchmarking/Cargo.toml b/cumulus/pallets/session-benchmarking/Cargo.toml index 4c85b3d7171aaa3a9d9c01be57c38b4f7b0913d5..af2dc2300d74b1822c9e926c719d449c5f1f792a 100644 --- a/cumulus/pallets/session-benchmarking/Cargo.toml +++ b/cumulus/pallets/session-benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME sessions pallet benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/cumulus/pallets/solo-to-para/Cargo.toml b/cumulus/pallets/solo-to-para/Cargo.toml index dc79d287d4dfbdeb4800676ccc3c76976d55eaa5..e1c94cbfde96ebe27793f713f92dc0e7b915711f 100644 --- a/cumulus/pallets/solo-to-para/Cargo.toml +++ b/cumulus/pallets/solo-to-para/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Adds functionality to migrate from a Solo to a Parachain" license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/pallets/xcm/Cargo.toml b/cumulus/pallets/xcm/Cargo.toml index f36d0aa52dec5fe655ad05eacef87f610fba3c9d..9bbc281154ce3a7936ca3aa69dea615de43adbd5 100644 --- a/cumulus/pallets/xcm/Cargo.toml +++ b/cumulus/pallets/xcm/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" license = "Apache-2.0" description = "Pallet for stuff specific to parachains' usage of XCM" +[lints] +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"] } diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 1bc21bbbb582906a47aa5dc332dad37ca10f3748..50ec5cacb2e9d022e3c5c4f1bb361fdfdecf4572 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Pallet to queue outbound and inbound XCMP messages." license = "Apache-2.0" +[lints] +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 } diff --git a/cumulus/pallets/xcmp-queue/src/benchmarking.rs b/cumulus/pallets/xcmp-queue/src/benchmarking.rs index 81dfbc2bb71ce7196fb7adfc95732d7c56a23d7b..49e2cc8367348ad6a81c0017405f8c3148734be5 100644 --- a/cumulus/pallets/xcmp-queue/src/benchmarking.rs +++ b/cumulus/pallets/xcmp-queue/src/benchmarking.rs @@ -85,7 +85,7 @@ mod benchmarks { } assert!( - OutboundXcmpStatus::::get().iter().find(|p| p.recipient == para).is_none(), + OutboundXcmpStatus::::get().iter().all(|p| p.recipient != para), "No messages in the channel; therefore removed." ); } diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 755b685d5b80ed594fa41a12634ebe765fb0dcbe..a41be6fa9ca3098154504b30b1672d1ee2a8609d 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -30,7 +30,9 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -use xcm_builder::{CurrencyAdapter, FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset}; +#[allow(deprecated)] +use xcm_builder::CurrencyAdapter; +use xcm_builder::{FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset}; use xcm_executor::traits::ConvertOrigin; type Block = frame_system::mocking::MockBlock; @@ -130,6 +132,7 @@ parameter_types! { } /// Means for transacting assets on this chain. +#[allow(deprecated)] pub type LocalAssetTransactor = CurrencyAdapter< // Use this currency: Balances, diff --git a/cumulus/pallets/xcmp-queue/src/tests.rs b/cumulus/pallets/xcmp-queue/src/tests.rs index 50c2a057d4213bef94fafe0777dec178f64cc9ad..8e8f6e852e1e0ada76b58d33712fdd1324e37d93 100644 --- a/cumulus/pallets/xcmp-queue/src/tests.rs +++ b/cumulus/pallets/xcmp-queue/src/tests.rs @@ -22,8 +22,7 @@ use XcmpMessageFormat::*; use codec::Input; use cumulus_primitives_core::{ParaId, XcmpMessageHandler}; use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically, - traits::{Footprint, Hooks}, + assert_err, assert_noop, assert_ok, assert_storage_noop, hypothetically, traits::Hooks, StorageNoopGuard, }; use mock::{new_test_ext, ParachainSystem, RuntimeOrigin as Origin, Test, XcmpQueue}; @@ -100,7 +99,7 @@ fn xcm_enqueueing_multiple_times_works() { } #[test] -#[cfg_attr(debug_assertions, should_panic = "Defensive failure")] +#[cfg_attr(debug_assertions, should_panic = "Could not enqueue XCMP messages.")] fn xcm_enqueueing_starts_dropping_on_overflow() { new_test_ext().execute_with(|| { let xcm = VersionedXcm::::from(Xcm::(vec![ClearOrigin])); @@ -112,12 +111,6 @@ fn xcm_enqueueing_starts_dropping_on_overflow() { repeat((1000.into(), 1, data.as_slice())).take(limit * 2), Weight::MAX, ); - assert_eq!(EnqueuedMessages::get().len(), limit); - // The drop threshold for pages is 48, the others numbers dont really matter: - assert_eq!( - ::XcmpQueue::footprint(1000.into()), - QueueFootprint { storage: Footprint { count: 256, size: 768 }, pages: 48 } - ); }) } diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index 19ee334cf954141038f0b06f0806ffc22adc80af..52c6ee7050d8a4c2359fe9f2afc2cdcac20480e8 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -10,14 +10,17 @@ edition.workspace = true build = "build.rs" publish = false +[lints] +workspace = true + [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } log = "0.4.20" codec = { package = "parity-scale-codec", version = "3.0.0" } -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } jsonrpsee = { version = "0.16.2", features = ["server"] } futures = "0.3.28" -serde_json = "1.0.108" +serde_json = "1.0.111" # Local parachain-template-runtime = { path = "../runtime" } diff --git a/cumulus/parachain-template/node/src/cli.rs b/cumulus/parachain-template/node/src/cli.rs index 098f59b0f373669e6679d1255f2e763506c49991..73ef996b7504114b3578604a8d2c37661c9261fc 100644 --- a/cumulus/parachain-template/node/src/cli.rs +++ b/cumulus/parachain-template/node/src/cli.rs @@ -24,8 +24,11 @@ pub enum Subcommand { /// Remove the whole chain. PurgeChain(cumulus_client_cli::PurgeChainCmd), - /// Export the genesis state of the parachain. - ExportGenesisState(cumulus_client_cli::ExportGenesisStateCommand), + /// Export the genesis head data of the parachain. + /// + /// Head data is the encoded block header. + #[command(alias = "export-genesis-state")] + ExportGenesisHead(cumulus_client_cli::ExportGenesisHeadCommand), /// Export the genesis wasm of the parachain. ExportGenesisWasm(cumulus_client_cli::ExportGenesisWasmCommand), diff --git a/cumulus/parachain-template/node/src/command.rs b/cumulus/parachain-template/node/src/command.rs index 4dd8463f6be67cb4d8012b8dba1f32be341dfe24..6ddb68a359a786be617e384b16d7292c3db45a88 100644 --- a/cumulus/parachain-template/node/src/command.rs +++ b/cumulus/parachain-template/node/src/command.rs @@ -162,12 +162,12 @@ pub fn run() -> Result<()> { cmd.run(config, polkadot_config) }) }, - Some(Subcommand::ExportGenesisState(cmd)) => { + Some(Subcommand::ExportGenesisHead(cmd)) => { let runner = cli.create_runner(cmd)?; runner.sync_run(|config| { let partials = new_partial(&config)?; - cmd.run(&*config.chain_spec, &*partials.client) + cmd.run(partials.client) }) }, Some(Subcommand::ExportGenesisWasm(cmd)) => { diff --git a/cumulus/parachain-template/node/src/rpc.rs b/cumulus/parachain-template/node/src/rpc.rs index ed4003ed6d206b38da693fe5de38f4567cbb873b..bb52b974f0ce61713904aec3783770dbc8f95aad 100644 --- a/cumulus/parachain-template/node/src/rpc.rs +++ b/cumulus/parachain-template/node/src/rpc.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use parachain_template_runtime::{opaque::Block, AccountId, Balance, Nonce}; -pub use sc_rpc::{DenyUnsafe, SubscriptionTaskExecutor}; +pub use sc_rpc::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; diff --git a/cumulus/parachain-template/node/src/service.rs b/cumulus/parachain-template/node/src/service.rs index 43d16ee0d5b7b326e5cf56f8b964f84fc84d16f3..830b6e82f969190b69425bd59a939c49372bf9f3 100644 --- a/cumulus/parachain-template/node/src/service.rs +++ b/cumulus/parachain-template/node/src/service.rs @@ -59,23 +59,21 @@ type ParachainBackend = TFullBackend; type ParachainBlockImport = TParachainBlockImport, ParachainBackend>; +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type Service = PartialComponents< + ParachainClient, + ParachainBackend, + (), + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + (ParachainBlockImport, Option, Option), +>; + /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to /// be able to perform chain operations. -pub fn new_partial( - config: &Configuration, -) -> Result< - PartialComponents< - ParachainClient, - ParachainBackend, - (), - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, - (ParachainBlockImport, Option, Option), - >, - sc_service::Error, -> { +pub fn new_partial(config: &Configuration) -> Result { let telemetry = config .telemetry_endpoints .clone() diff --git a/cumulus/parachain-template/pallets/template/Cargo.toml b/cumulus/parachain-template/pallets/template/Cargo.toml index 71b78a7175c6009d56cd61d21aff44a1218b9555..b8d95b5cf7801efa9c5040b61b7d3118da9dd424 100644 --- a/cumulus/parachain-template/pallets/template/Cargo.toml +++ b/cumulus/parachain-template/pallets/template/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true edition.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,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.193" } +serde = { version = "1.0.195" } # Substrate sp-core = { path = "../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachain-template/pallets/template/src/mock.rs b/cumulus/parachain-template/pallets/template/src/mock.rs index 8fae1019f42dbfaf5ae0a3a2aaac1cf8b048523a..411a16b116c8f94757c29a686022842e159e6924 100644 --- a/cumulus/parachain-template/pallets/template/src/mock.rs +++ b/cumulus/parachain-template/pallets/template/src/mock.rs @@ -1,4 +1,4 @@ -use frame_support::{parameter_types, traits::Everything}; +use frame_support::{derive_impl, parameter_types, traits::Everything}; use frame_system as system; use sp_core::H256; use sp_runtime::{ @@ -22,6 +22,7 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); diff --git a/cumulus/parachain-template/runtime/Cargo.toml b/cumulus/parachain-template/runtime/Cargo.toml index d83867a9c7c6677ca7d694032d0cff949e83c822..3944ff4ca08e0b0f2f6185d2e0037def823ceb63 100644 --- a/cumulus/parachain-template/runtime/Cargo.toml +++ b/cumulus/parachain-template/runtime/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true edition.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/cumulus/parachain-template/runtime/src/weights/mod.rs b/cumulus/parachain-template/runtime/src/weights/mod.rs index 30fa2c4060689ff98cc427c84f81866172845e52..b473d49e20e67329d893e1e565330cbe9290c64f 100644 --- a/cumulus/parachain-template/runtime/src/weights/mod.rs +++ b/cumulus/parachain-template/runtime/src/weights/mod.rs @@ -24,5 +24,4 @@ pub mod rocksdb_weights; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachain-template/runtime/src/xcm_config.rs b/cumulus/parachain-template/runtime/src/xcm_config.rs index 752137c96f184ec731d2ce5edb4860ff94c0e1f4..7d1a748819cebcaa738f2c2ec2a17d0fb3bb0e2f 100644 --- a/cumulus/parachain-template/runtime/src/xcm_config.rs +++ b/cumulus/parachain-template/runtime/src/xcm_config.rs @@ -12,13 +12,15 @@ 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, - CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, - FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, + NativeAsset, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::XcmExecutor; @@ -42,6 +44,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. +#[allow(deprecated)] pub type LocalAssetTransactor = CurrencyAdapter< // Use this currency: Balances, diff --git a/cumulus/parachains/chain-specs/coretime-rococo.json b/cumulus/parachains/chain-specs/coretime-rococo.json new file mode 100644 index 0000000000000000000000000000000000000000..39506095bfe0983c182850084f2602a882ea0caa --- /dev/null +++ b/cumulus/parachains/chain-specs/coretime-rococo.json @@ -0,0 +1,70 @@ +{ + "name": "Rococo Coretime", + "id": "coretime-rococo", + "chainType": "Live", + "bootNodes": [ + "/dns/rococo-coretime-collator-node-0.polkadot.io/tcp/30333/p2p/12D3KooWHBUH9wGBx1Yq1ZePov9VL3AzxRPv5DTR4KadiCU6VKxy", + "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/30333/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "ROC" + }, + "relay_chain": "rococo", + "para_id": 1005, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xed030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x0802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x50cd2d03000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000829e74677a0a0600", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9bb2c1ae8590211c475b041d595d99230b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d440d8395438d7269bad990f83715c2002f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x59933c636f726574696d652d726f636f636f", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058f41c04ee820524125110306b291df7ec1bbc35021becd0370bad392219fb2238ceceb48c316bc12c7a11911586bf0e3140e7c0b16cc35ff0661d0ab1738ce914ce06c7dc9a10bd34368cb56fffdbfe2664efbdf7de524a29650a291376109b10d43fa5f9d446f3a95d693ec9a7de349fa457dca4c63399fdccaf20d3ab4faf994abecc75c2da671ea74b9f3365cd8fdcb79434dfcecda7ae4d49f3ede07c921d1b8dbebd6f749672a84fba7ce293a0549a30e9f3bb66c39e379aaadfd43a64ee09a30e353b94a0c41af44fc9ac6fef306c1dd331d83aa6532ce6c7faf3cb4b2b62d7a450f976fb5d9322e547eea773f389faf4703a951e7ed7ac08bdd18449afed530efa1a87d82e9f44e9eddd7dfd73ba9c33f74c4a205f7be328631e14777cfd7de3e3e6d3c864430851fa08fe2c80285d8874299aabc4b3164e909b4fa37ccebb7b481fa97f141b40a4ae01cddc374189eb83dd63891cf453db3721727d1494987ed8e4827dbe09d8ea1e4b24eda714f0d2a77728a4d2640844a4bd0422692f5d9c724af1750f35e0843e13d61ffaa813d60e35bbe9eb1ba33ff5848ddc8fdcb7f7e4014ea7e9ed5f63205297ff7506e2d71206b16b51ba7cd7a07cf9b1c64f1fc077ed49a09fa18f21a2f4ae3db9f3a3d283e07cda9c7afff46f863eb6dc72a80127049ab07eea4c0da012a875f4cb9083d953ff244871ff86e57f32a4fe514c25a77cd409ab0e447cea32dca09f7a873e7ac246f0a5cde3ec2d47373cd61fab53d1bfdea18faf430d6e1eeb5fe77cc3a3ffe6148fe3385210421847f8cd7bc23687a21c9e923df50d8f4d5ffd93e0c5fd14f77728a43e75a809352d9ea24fbde29e30e91dfaa0b94e9874a8e9df36669ba42f7c2da7d4240962d79ea0f951fce9b0f934824af36984fdf4e939e693fce9e27cea1a2dcd4f379a4ff3a7f7e4216363d34fefb00154aa681dfd50737adf98596ac7b78d999472ccd7f3ebf955ef1ba3f8d4bfda53bcd18475ae1346bdc32999f5d6a1e66731d73aa87f1dca3cce5e7ae3517ebbc4228ee0531feb5307e7533b7528daa10fe950b3cea7e9d4b9ee619d7ad73dfaa95bdc74089cb09eb051e9a7cbef9a0d80de68c2a6572cbf6fd02c257edb98512971685946ec5a163c2f694ffd73b5f78d51d27efac7e377fcd8593fb64f87a28dfb29eed6d1fe49b0711151aabd7f94b41fdba7cfef5a963f2f5ffa0c8348dad33c4adab77f1d0a11bf3ac54dead73c25b39e7ae371f6eddd3dc6ea2f20ded73ce57aea9de777fcd8593fb64f87a28dfb29eed6d1fe4db0b1c412a5cbefbea05d8653fed43b9c129fe6a609930e35a126d4849a50b3714f587bdf98594ad2be6dcca89c4a7c92264aef096b6ff06b1da2f4f6ee6ca89170e290f16cf460238d0d3436acd8b862438b0d2836a2d838c146161b586c3cc92dd89062a38a0d2b1bb40c45c622f371381e032e23abc960b2991c26c78091c82f6418b2982c439e01dfc958f90a2791a3e429380b5b81ed602cf017d92a43c956301adc4326010bc27e300fb9842c0543c159e041980bec02f6026b81bbc060f016784be6015781e5e038f90b2e02d3c1476037f80a6c04c621af8073c857b055f682a5649a57e159f8152e283f712c9c8ecff13ad88bc7713e6ec7e57820dcc5ef602edec75b70187c8d63e1295c0c5ec15ff0205c06fcc75dc08170141810a6024be133602dfe03d68387c06c1c0817023f81b3e035d8074c048602c3c17dfc8f43e17bdc07dc8403ca3a6035d889bc037e93abe42eee26a7c14ce41bf2991a6f6ac0a9b1a6469b1ab51a6c6afc50234d8d1e6aa8a9c1438d196a8ca941438d2e35acd4b0aa11a506141a65d080a2068d46568d2734c8a0d187461e1a56d0f88246201a55d0d843030b1a52d0b88286201a4bd0e043230a1a7f688841a38b170f2f2568107a2d4143091a4fbcf4d01844438b26169abc34add0d4a5c98526304d2d347d6982a12986a6ab26129aa6344969b26a22d36443530d4d34348d69c26a9aa1498626314d4f9a6e6842d374a6c94c539926159a526842a1894bd396262d4d599ab0345d69b2d254a589ca2b8c17a1d715af412f305e5abcbe7865f1c2e225e865c50bcf0bd0cb881713af3baf235e48bce4bc8a78e579a579e9f092e125e635c32b86570a2f185e2bbcbcbc547861bdbabcbebcb2bcb4bcb08840d620f5baf2aaf22ae16585890f13144c7a98986082c39486c90526304c6598b62c7db1548217094b602c65b184c592a0a52b96a05872624989a524969658c2b314c5929b252296de2cc95982b314674908a52e94c05022a4f4855209940a298da1248692174a8194b25012a48485d2154c4328d1512a42698e521da59ad20e4c4524b9412a23e987241e92cc20fd49ea21c94a920f496592aa245149ba923444121ba42e90b8400a03c90ba42f92d62001429a02290aa42c928840e283a403d20e486a907840c2018906242ca41a90d2208d41b2016906a41c904a40ba729d1066f1b042128e78100175551061e16a473b147971a4c4119ea324709ead96d4a6cb02a90c121924335d954e0e51a1253e358458f2b3a4a748cb05e2fa916390a4181db105d15de1a27556885bb8a288828e8ae8b428c2234231f2a1a5e7480dd1174460745a5a4c101152e2417cd2e9e9ac2c8d41c487480e911b222288de101541a404511c223c447088e6dc2a88aa20a262488f92159c105c10db9b8dce501e1a630c2df1f2e2d5c5c6a6c84a0d1fc2329b122f3b442c1079211a43b48258c5d11177869be576b9295c19ee975be692b9365cac4bc3ade18eb95e2e98cbe5be7055b82e5c2d37cc157359b830dc15ee139b7569b68c8bc26de16eb9666e0c368ab5b250885a2002933426092b6986241992c424c5900443d20b4961925c480293d442d297241692bc24ad90d4254985a414925048e292b425490b9396a2276219ac03d11434b2706d3942624b33c4a668900d12bcc661915110b3289a5364470c246a215a2162512447bca2a888223a3c442e38268ee474588457e21762174c3f0ce1e9ec74578ea6781b1b26d8b8723d21ca626990d19aa52dc418c41744179686b051829826ab40140327088fe1b2429455b4c595820b850d8990875087a13de10e2e2e442a1cd929ba53e4a6284e2b8b83a255e8684e1111ad12707c88521007717b5a651cd52932a2a84e119cd6182e2b57094c6d5a54145d1565a1e18591106109e1094c4e7446746e2e114b59d74d07a723e2beb9703a2c9707a62744508a4aa8914351151a5414d1ba37dd10446474755e3b7c3c7c52be34df942f878f04223444377c553c42de171e185e18df095b998dcc77e5d3f26dd9d61471d16dd18141c4860885a337446d383d476e8e8688bdb8745d1cc53922e205a6fbe2084ee7c58b8a170aaf2d4443d41842a9874d88d7109b115d1c233545515e718a4c10cd10897194a6688a7803d118e299221298a01449590aa3e8caab8557151c15dc141b139b1ed79416192e29dc1347743827926a99050e502788fbf3e282f363e38c38c5ab07af0d931c242db62236372d2d8e7a58aa6d70b63a4b768c7ab87198681d1b1a5bbcea74452c15518465288bd08c521613111b10578a1b6833b3d9e079e17571a7f0acf0aef0aaf0047981bc2dbc41db0d1b0e1b9a2d87edccb6c3a6c3a542dab0cd79b9d98e582a83a88ca53c448144323550a891420d158ab4b8578abab85490065d2cb28b5ce156617a6374c3518d0bc4e4a6e80dd74416e48252834bd116477798d6e0399e093588e07e30da82c88a1113a01dd008a617b83a1b1145543a402f2896da1c9539a2a10b84db80808e745862738b60a243830bf0cf521d6e0d0ee275e559dd1da23b4945e42d4683381f5e7c72961612a09fa138b90c77c2911547a7c86a1be2280a47e5a806a5326814c24b0cc931faa1884bd109344a70cf30a160e44497658bd3a530e444a7c21109e1195c73295a49f89b21385c95ad0d7ec2b4a786942333dc1c3108a2308aa0183521d2960a3151e9bab89fee8f53d171c1859c8d77b1dd212a01d39425325e518ea00c2931c44488a66bf3b27a3d51aac208881b44e682afb8b2363b3905a6393907e762a8091f62688a1a6ef00e1d0add9617142ecb26e715e82565e889d7141048291205c9456e0941469129482b0e54cda6462073c26489d422b3c82a1a903724c980071288800f087420e4011c3400c8061a2c00a6000b24940b7628c931000110c0a9024903f7e493dfd2b5a79b348a49910e0c09a148140843867c927e90246ac8104f878e2130ada1163e407244840ebc86bef4078e7820899228aa22514b8c3039c2242a49940637865830324489079200418244c902a88c442141a26401150c79d931b4021224458c20415264015d40a0fa409228242a745494078628a11a4205c2078e4485c06928856682e44811aaa80f8644457900c7100aadc403498e0c11c1922811a6861c09e1a99244814004453af0212e1d6524899224438a0c3912c2471949a2e4015449445062c4c8cf18daf2811223511fd418d2d24c4410253584091311440549a2e40813265112c043593a044742f82551524b945440081ec2d25154453e3882a44893215122448900010f3e88fac0078a7850c1d095662265240a0423569a095592a82551202439e24355921cf1c06b884a1b39c224c991255423440d110192104228326449d407488e88109332a20449ac69e88466c23464421719d234544283604811aa221f14a18a72c0d0d0d5088e20297284ea489223474324b4941125483a30248a4a0423512344110d4de9254a16000e4969265147a88e2c5102029508458a86ac3a89922354459688f0c1102a2548980c3912c2175922c21fb58c42d28122544944f0c0102a23514b8e7c90e4c890214bce99d9be2ea20aad082a5e7ccfbeaed1682f6774e9725038283204b9d5a222b0a828491487869244d148a441831a19514aa9d1d2d2921135a246467469b6b5b5dbded9d79b76de7bfbde9be7b4f75a5b6d9db5daa87d5bf6edd9ddb34e7b6f774b79af9c3ded6c79efb56de595f4ce3be76dd9b7db5a3ba7adb6da9ead69bb72ddf3d27be7b51f57a7e5ba4ac4715eabf62df2acf5b63ab7cedabb71d3ce9eb2ed9c745a3b6b5f6bafedee591dd077cec941dfbe72de69695f2ae5bdf7a3b7d533e4e6b5b75a6b996edfee1bd3ce7aafa5d3da7a27ecde396d9db3b6b5b576d7f376ad2d5bde9eb36fdf59edec0e626dd86ae5ecbe5576f79c7576b593ab2d8fab1d11b76d9bb5dc9cd7d6aecebeb4dada725acb7513b7ad1cd7d6f6bdb7d25ae5bdd5ca6aada47676dcedbeb7efedd9755b7bb97b27707174ce06f4e492bae7f48078b46ff716866dbbabab72f24a8febda498ece6e19864ca1b4ddb2a81ba94658370893e69cd376f77667f766bbafc7d9696fdb683b39ced2297bdadbd3da69e7bcf7369d52ce3befbcd47295ab5c37314774efec6e0dec90ccd576276d755d39e56dcde9b3657f586fc6f551279d734ed05edb5cf7e4babbbbc339edacddcd4ddbdd7baded9a769daeeeb6edc9596b611369360316d0dd7d67d26dd9ee39aded5cb1eece356dcf9eb42ba5ddd49b96d27bc339e5ac6f6b77cfee397bbbddd31e7d50ddb3a7b573f6b4f3a86f2b60da696d51d19cb6d6aeebe6388ee32c57eb6cb56aab55ebecba39398eab5cb5d6daca59db6dafbdb7ded9ddb55addb4b3b939bd4b2b476bdbda969b1cc7d96ab9aeb676adad04c8eb72755dd74d39a79db39bf35ee99a7376b39b977a575e6bdbde2eec393b4f4b6dd7d9b6daeea694f2524e4a04d8d7ebf5a2b356cb5dcbdd7b396b6fcf39c3eedb073080adb6de3abb47774bcf7a73d6b0762ecd696b9d349c35b4b6bb6ff76c2befa5d5729552aca4a4a4444464adadf7ce396d9d76ce29e7ec2965cf9e5dbbab94cdcde638afb6bc72b35a3aede46c259addddbd595b2b676b3029d57aafadb35a3be5acd6b69dd6da39674fa630bcf75e39e7b4d6d66927c7751cc77195e32c77adb556de7b6f01b87a398ede2b01800347ebde3befa5f75aeaa22d8eb376cedb7d6ddf0c783c71f5d28b010106000001d04b5d13547a6ddf0b76d4ba5d5b6bedaaadd536776f17de69e7ac76bb73d27bbb7bce6e7aefb4a1c6860fb4a8a708962801411408eed717443589423264842220a891d4177c400992211f4872e4031f50826409c98f4688fa20c99121498e1401c188073ca0245bf041d492a80f962819f2848100a28e2ca1a2600048901c91da11001c34603a60620e11470e0a2ad881f401922320181942854484221e88fac089004f33465012022a1a4f1808c0870470c1100144491541d281211f487204042645407001d8a080aac892a824511f583201d8028862c2246a490820506264c892a8264a8c442d195284c90746a2865c20f5848100423012c584c70c0f1c29c005186020800fa28628a10a41044b8cd85852848a090e24264b375a004a942c61923252a4034890285942815391a8254c2288921a42354254121a4d54511f581a6242c5e4c808511b9591224c921459423584ea080840a00426b6008e78206a4908f96849912824465e481f24890a21842243a23e506489122a26b22f00018d8e8a5ae281214b94501531a2040992224d7d4114088e5025116149cec0035422cc6892248a1ef50543922839c2644954932123448130842ac99125210c31c2e4080886508d10d5d42449148581806657556d557636e388f4ac6a56359b715c5555956c223dab9ab33aabaaaa9a49d98cca66b50b89cce4d7b9aaaa2a9db4aa1299c9aaaa594589cc64d56c5651225d359b51d94c56c999ac9a7d1d91b644aae4ac6a468954c92a49a46755b35955d56c7689f4acaa6a563d4aa4abaa2691ae9244baaaaaaa6a668974d56cce38223359258954c9aa2a3bdb887455d56c568954c92632933dbb334ba47b12e9d9ac1299c9d96c4689f46c3689cce44c12e99924f2004b4576b7040193221d78f4f00b48a5d13ef4faf43bf489e7870e3527f875eb70b9f5895beec2e3ace18fe9ad4c7d863fa68b0e4545fc0a32bde5d35b19bf82783fddfb99995a07dd44caa363d427369a30ea2e6fe1fed63182be8542bc079da97b50b7a19057eba00e66a6560b4bda77a881a4610d39d414298fa14cbd5d79e4febad474cfc7e9cdc14c08cff3bce932fc217d4e20d33da956eba0ee6a1d94f6b575d00d8895f6e4c3ee0bb3cbbb7574a1cb5be048fff3d6e754f75b215ed272810e355da0b7be10f42ec7973b879a50b37d8ed27b4a0ffb0e6d599cb09bf3db6c542567adb97df98c88f5bb8602114644fb5d43a1d6adc346188621e89f87611886de37c269f57ddff77ddff779dff8c8e8baaee37cf3aeebbacefb4607c6e65ffb79a3508ac6f267d687ad639b989bb0cde5e68de5157945acbe6d1e760f1fd3b7cdc1eeb179dfd85cfe757777b7ece9c260eb70b9c4d28ad8f2d041ff5c5a11bdcda515f1ba1db977b5bba6bb5c3eb10b8acffacd23d8ddf48a7f8cdc63cfad8ff2a787e0bd979378138f555f3bff5a0a89f73627f1e6773e76288479fb5de71d0ab9dfe59eb0b1829d9dd63b0a76d6e6292db823f777723ef194165cffa8bcdefed539fd9b41baf6338f5060d321eb5dfbae71e9f36dd9fccc75c2eee53824e0db697bc27cd45c276c3a276bd667ad389fb8cddb3f8b8154ffaaf51a75fd93b3bf0a76e8a3f3eb33f401452f3692b636915f2daed69392eeb4e691eaab5fb77804dffaf7e73fa73ffff38be5bf80b41d2cffb9fdfa1d02d902fd975b5a30ce09fb2678b1f5afe56cda6a6981755a3112f06b94285d69c2fa1584faf59ba5ae53eff7e2a75789fb3bbce3bd7bc86ff7ee043dbc79873997f87a8753dc37711bf6d62173e79ab91114eb181f90cdab7f158fb3af5ef1567d839a95eb1c6a562c9ff3eb9d8fb3bf5cfdb6cd2b6e227ff34f82dbe6df9c52d5ad77e880ea96c3157378ec5cde2e8ff53b3ab93c7650744af6b3bfce3bebacd3e5ac2efcaa2ebdfac4affa55bc01d4e6f29be0b66d3e25b3965081210021f29c73ed52d537ff383cd6af4ebbbc64aa07d1684f45f49c534a053e2724040f2f9952c0cfd6519deabe37bf7a8797784e759ff30e830cf9cea12687a76a4c29e065ebe8badae5ee1c75863fac5797a18f8a390ecb3b61168fd3e5929a394e6e6abea64320b53294f647bb897d2e209c73ce49d02de81ee89d7b1e369ab0f60e8b98c34de48ff23997b8897c8ee3fc9b2d7bd696ccde3adaa12687a538efc4f9f4ddcf25ffebd047df2171c23cdff1a0cbd087c4a37c29ce3bbfd85bc78e6fffb03861cdfd0dc279975d1ee832ecc0e65e569ab076301bf584358747ce6597c7d9770e62895d3a1405f1e8755e1eeb7bfeb9c4748ef2411f1b3013e2bb738079f43cdc2fbb962e88d44570e2febb611d7f318747f9d63f097278b42e7dc32f99a7e4dfdc3461ed15fc266dd6c74d27392a89de3dbc75486f6f9a4f46d349fa7423ee029ff44fd6e97f9d9b61dfa13a6179a4a22fbd73a3f9f46ddfe67979f4bc736ff39cfae7e1065d861ad4f83a619d6fb81fc454f43dffe46c495b1beeef0983cda71c4d2f5de6f1ba75ea1f95f2e2fe0e730ac8caca7a0e53d997463f47f8eb8d476e0cfb0edd9830ea9bfb846d198a36ee51fe94cccacacafa8ba9a80c7d74b6599cb0e9b4fb9be6933ff51cdd833e1d6fbc276d0cdf10b1dda8e84b62cd7b5ef7e8e17d4b0b46eec7fbb4795ce944a192b0d33afac73600541274da82fe9947e9e315f2bdcc63d53799ff8125f2a577469ef881201736a9adb16b48d47ecaf5d2a9bbba4793f1d4af34403f1de78f17ecee2169128c28fd93a124234e879a7668edc6c754a5d1ba4f55639fb87f5a1a40067ae2dba7f4a4012aeddbbf0da4d2bfde388ee3eee4382a398e83b5a46f51be9e9f0324ed691ea126f56eea9d477ef9ac5b5c4494b28ee347dbdfb52d4ffcec8fea1e4b763c56fbaca770e9ddf966ab293a62d7af8a62d2e8ab33d958238e48defa8e1f8d7e6c4010e898d5b5585299b0ea17104749e5ab8f0de8abcbda45eaf4c5171e5ce0014d1f1a2d765dfa7771c724a6551bacc40005062062565374c4a45f15c5daada6ccd8e657098849bff82a013159190fa4cbe41d59190f36bf5906b56559190faecb2cf3bcc37fe47eec5a4dfad8771a0c12e46061d1440d5120e13314e80001060c1b3d387411eb58fbb8c303a7d16292ca7cc2f17356305f8f71c7d799ab6bf55add563d7569fd7ccdf656df72929ba590e4ebd7479a47d957b7f83ac56de7cadf8cc5591fabfeb3580a49fff9cd6d92b683e5ad8f23f8bbe524fde7ed4d8200abdadb8ce5a4ca823c6d2cc8d3268f233c75e95be87cd3776d8b99ff2668319693aafe13ebbca76856d663d1665491814b98251384391387461542ff792c9a0579dad09a20c0aa3602aded6089b58f23780ba5ffc4ba4ceb3fb1f651f6368f6d806e0bc6ae6599e2a9e463d190f8dc10a50a1a2dd6d94915976956ed637599c7109e3a87db2ba63e430a5890874d160586a2e033431951b2b262408c7e56cd999db7863a9facc52312ead66ffdeee6dbb651219bdb3c5edf3256a7413fd695c7ba5cc57919ebd796a15dad5e73884660d59cd559057d7d639458be7a770fd9df9447e7a3f89cb77f9f7bb5c39cc77d153b2c2be341e75c9795a853f5a64eb60cf5ce391f95be7af5af82b27bcc6fff3ecf39f7e0f3ce6ff6c073ae7e59f639e711e858e7df0bc83873e1af5bb7799cd1f0372ff1b73efb0e81786f33d4f47006d4a9fa8761b8a9d3e61dde419d6a06b0af4e373c76edab8f17063ee7dc03cf3bcf6ff6e0732ecbca5cffb205a853f589c7d94f1b2ef180732fcb36a04ed5b9dcd4c97a043ad60175aa7e4321d46f1e3bf08e713eb69eaf6e2308b1635e2e43bd639d8f1de7abd7d0c77428dae18e71b863168fdd27880704bff854863e661e673f644f495be304be5b87c4a2e7c71ef4d5e5d57c1a5bd008f36994575442c072e7ab53cd27f9b50b7df56f82b27bf4b7cb20f9c7aa47329fa857ef1b354b492cdf36aaeaacce0dcad7f24aecda952e6fbf6b56d47c137dd4fbc6d8777efa4771953e378fb3bf1f05291a09ce1d6206f369146134a4153134f431441cc571c78e0c260fa7d168b4d874a8c9c363d337984fdc4fe7a07bd09fae262b5f5efe8c866224f2e18f7ea262a9a0b56265c11bfa20b481d7ab1f60f2e898a0af5e696d5b6aa345ddd53d68eed6216538441c77fc749b6b36cae0ce4fdf81e7a713603e49f907f8e97de7dbc6cc4a5b8234b2662ef70d3ac2e70071c25c9fa480d86103665feb4c4a06fcb0802169c450d7ec5983ed529da7aabe7d9c2e3b5359ab92e6bb5685cb8faeaf5ec1778d4aa0afdf27430e66df1336d69ffdcd1f0d7dd81972307bf05ac75503f9d7a126379f5e3fe45fbf997be9b64e193e074829896db091b27f7c4d01f0eb7faeeb3353aff64ea7fefa319d3abd2ead4bd1ebdc7c9a7fc1879a507326a58cf33505c4cf55c5cdcfcac14c666ec2aa57ff5c4144ead3a7aabeb6fc9a3bf0f574d1896d0821d64aa33e107f7d63dcf1d4bf3c722fa574e9ed546a0c10e78f4d4fa9a0f9b1de984f638e1cf3a93a759f4f2ed78f301e3d9d6ae5e693d474ce7bf2a8d3897a7bcd23f75d877ac2daeb84491febf77cea1a95ab6faf3c3ad6dee1fc31bdd6ccf57c9a6bbe9e277cb126a0796bc298b7d6fae7b2def3a9fa08526ffff6517ef50e81549f0e35fb86cd520ea59b07a5eda37ae326f5db2b06227e3ba54d714fd8c440d2269daff55d3381ca1b3509faea9747bba1d162edb46faf9fabcea7ebed51befdebeed1f2ef9b81dec2dd3ac03c7e53f3af7f799cffb974108f550fca6f8d4caafef3ea9e77f7e8bcd53a5a79fc9c7ae81deed611e61174da2ff1d496959555c65707f138fb21bf3ad4fc70bf87a56a6e22ff3a90f9d76918a4facce3eca7f77f2e310d7f5c6ff9875f405e861a80dead433a14859a209e9259cf790bbf420d36eff228bf730f8ff23dff2e1ee55f0fbd8642aa830e35bf9666c4ae9550e8ab5ffffa9b60871a84fee5b1fe476713ee5bdeca320452fffaf775d8cae3ec5b0ee226dc83fee151fee7dd3dc03c828ea4c777ebf8fc7328fae10f53efb095a7e85ff7713dc4e30d52fdbaa141f9cf3d6f97a10632ebc1dcad63fcf294ccfacfad77b841ff97bb73744e3ddcdf61e957d25a879d2ea743cd5927afd5e794871497bbf7e65290fcba8ee95296bce501a02ea504e94a6e8137e5216529cb889c32b9941afe793b477948d92125c997bc5d89f29082946548ee72f9d290bbb023b90c29cb866ef892cb901c74974ba159c6712e6699e81b7519cd322e97205dc6390d1ff2d0db45ca438a97659ecfe9df529665df6c380e9f9b2fb96cfa529659eb6296792eba6cc93f97529d29cb8c8c9c28cba87759d639fe5c86fdcb324a5df6f9cb8b44d1c12cb3d6bd2ca35936c33d17a98b2e035dcc325be432eb4e3e81cb449f20cb8c8cfc28cb8e7cdb9c469619d1f0a3edc82770d9e61364198d8ed27059772397002bcab2223f3af257d1d151761b9ee7346678dee775f8680969888adbec3c24ca7d47d6852d38b9a521d7e4f02652d1db8e3e0b222d2171d83564bdaea39fb81d0d812252072e0d6d939b9e0bdb236a8f9628878302f19b2efc79054313783870142d7db4737272d97427a70e09293b4ce41c06524081cb3ca78002bc6d2ecfee047aee646130976187e18071ae23d1b353b034e41478052e43f20a9c9c5cf6b9d3044e5e81cb3a8b03c7873d3bcc1e396ce2c0818302976d4e4196e1c03144bd82092698e132d067749edd8986cfb08154813bb96cc99d603000f8045906731b13b893cb3cb7e13231dff0a2a2b9e4d971cc98e13668d0f097cfb0e1335c36e43338c7e1395ce6f21c72479357e0329757206fb88d0a3c87cb90dc86cb3877823535656f72f22697616f922efbbcc92778bddc068d2c9b916533dc86bf68f8cb65a0bfa4cbac4fe03097890e9300c0e1b2cd716419007078ce3200380d1a0e7319f5ecb2ce7364190514f88d2c7b65d9cb8b8abc29cb9898fc95654d5e54e42f971df92bcb985ecee4323043b593f41b5e810d90c8676419539631f90c0741cf2ef33c6719910dcf2efb7208ed249dc82b709997476827e915641905b904e9f4f90459e6796e946546fe4de09fcbba6c812fcbbccf9bb2cc73b073d0653877d04ed29b5c7694376827e9b02cc371e400c8b29c65d927c07e23cb6cb8e72c6b2bacc4a093d542175aae62f80812b81851022dbc48392226053bd452e6da493ace32cf8e94bb9da4d7c84d9dda69cc58f2a62c7b29395396cd7ec8e18a3b7a66a8c1ce0fb1a5190b509ac8d2860931f4b8105b72d950566a27e94b59a6949b3a599047d8b7cb5c596c27e94999a34eed482ea3198476928e946547b904199332e44659e6f2a25cc28e9814ea445936d4725796cd64c022034b06425e60b1458cb613837288210b0b84da88119342dde5323167d04ed25d59d6ca4d9d0290c70cbe5db6e51ded243dcc4ad4a95d36339176920e66d9974b80c5a45cf7b2ec003cacc9e28c17490cb221d6e5123c26a5061b307410a2892925b8222665fae6322ec3da49fa9665373775d291c71ddf2eb3d9db497acd22756a6f9fd12aa7a923eba34094ce8925d066a09f73ced96452a794d22b354f29ed7a8332b9e2e2f3697afd39fa0d120a3dfde923f8a3cfd8087e379994522aa7434d3aa7cb39a72b3551299d7b995f41a45797348ff2a5cbf97a3ec996dffeb97a3ef5d3f4fac9d0879c9fec9056a72e9d863ee48451f9339cde61cd3ea6d4823a06624fd8e832fc31028d467bf2d2bf09f67c92de5ee7d3e42406278c7a0f71347aead3ab8fdc075c1e7bc2ae8bad71c609a7aa5afd9ffcafff86429cfecbaf2099069ab0ea9d338d5ca1afee7d994f5d966198a418d1c5881123cacdfbebe650338cc879ff4805cd384c328c28bd72598a203e95e831d94c13a6ce0bb458550dc4c64b9b981311c866311a4ec28ed8941db132d22d95f94405cd27f95fbe7fa4837ec6f9ea93ce0c1a68c68c331330639462beba1cd334dd34493133014db2cbf78fdc57aff3e9751debca632fefd77df97c998097c54c9d4336f9cb69e8c1cb5f339a5e5e98a469811d57fbab365bdfb53657dfb44d50d7ea13ce245f6083e7c59f9b4f3cd60d5f796c03b72dcb36afda362ac3cc762ccea1e6c4b2af3e3187af3cc681dc95c7aedf3cd6047059c679d5e63d3198b5d0d7768e4e510cc6aeb1b17aaa29c3d02cc3f898796a7be96178f1d5bd3f5fbdab7e6b6efefa574b137abb915293df80cda7ebb5ebc00e049d03c1cf3f09829f7f5fc73afca45e4136b7be6dad2d48b771ad6d04fde55dfcf2f20fce3befb294f52e83ce7560e71d02f1bc5048752f4b591fab3399fdf51b0a61327bd0a5ae90ea5b1eaf83b9f38e9bbdc5a17b78f60ad279e89d87fecd1ef48b5df862a9d06be72e6c656565e5f229dd17c8f9d7656c4af70523fd9b650c8a4ec95cb863df14b551470c9d06a9fef9cd63e85f7e7dbe79985f5f0ec2f975ceaf5fec39b7715b106ee3ba3c5efffc93f2c6eb600dcf57fff008bebafcc3fae75ff630d8f997bb7574ee5f1e3dbf0e353fff2b9f23c0f778806f6fca3df9f6d68e0caae4cc9b2009d547a5b7d612603e4df0d6958448f3d69b6eec783ba95beba0948f86f9da39cd3684106598afde9c779dcbdcb9c49cfc715d76d28c67418a5b9e9c3ebc8c254e97791ee749f730e8176fd73f0fdb7cf187b9d03da79873107f0e45b9cfab7f2d3cfb6b50e2d0bbce69277db3fe39d4c4129d62eb5f87601e650f7a54c4571e13bd6702c42cab72794f0c3c97f5c480bae8b097ee127f0ee2e922ee980b97015de230e3f6b00d7f5ccf3f8fe336bfd8c9da5823ca30df320c382f96d96f4e55bdf34805c466e9148fb2254d38bf54d5af8f5508ecc7aab7e3083fb67378aa8610eb328fd437aaf9d6db3acd4db82c25bdff52cd0dc7d33cce5ced32cc540d1a04183fce7ea43e83a0f3b0873d0d7fc0bee0e802002e1360b941c5c6557eead20a3f7549f3a72eabf1d447a0f1d4a9663cf5109a9e3a92d7530781e9a97fb0f4d489283d750d243d750b203d750f8e9e7a07464f9d83a2a7be01d15367c0d05387129ffa015c4f3d83d6532740f8d477804f3dc7f7d461de53bfd13d75e79e7ad3f6d495ee5337b24f5da44f9dfb319fe6571039bd76eda5d31088bf94fdf5b64170fc48ef389dbac5a3ecc7f61a04f6548699b06e62bfcbe2b0be397bce9ba33ea594b27dce29d9cb4e48a52fa5f4a926e95fd7656ec2ba3a615d936de2717a13f19154da931f3b9fda0a3dc9fa26dc26559dfae732775d9de2aca03cf9254dee73de647b26b3e7ae7723d57ccfaf97a764a1af54f3abe71710597bea33f441fd4e8a9bccaffecd293b47e7d7ad5f3c25fbea9c94d8e5c8494d2e8ff539975dd775b29ffec864f6ed73cb3f5ad69aa5a8d32cd53edba56fb44fb620ce33dfb51fcafc74e1bbd68395877dd7d40421fd9318085594413fced6dc1f45c8f2e6db5db20591b6f9aea529f4d2cd772d07359fe3bb86431f196ae0d5e9f353440681f9a9aad68f5452f05172e147aa312e7c52fc48758214f647264ce850810f777e64a2e6ceb7d368df9df5eddf3e51f8a6e46f326bf03966227ff3b294740f87da57f05dbbe1cb2fe9d6319d8afba9cefad02d1ee58fcfe70cb3084a4fa5f43277ebb03ff1a841ff6dfff078dd9aa91efad7f28cd8351cee7ce89e7ff6f35ce28969e863f482d420a173a07b38f41906e95c7a873f3a97211e6796ad43bae7201ee74baf09f753b40cda53c99f2e7fb6492ae52bf4137cd7d0e0e93800bdcb3b640038879a3388a07720164104f30bc8a4d19e7a158df652d4fb3beb5d1e8fbe497d249dbf36e8ef40b74ff550f3bb814aa5d30bbfb025431f9e583454248a62d1908b88a83524b65aae560b0321fdce8dcac779cfa7ce47f0416faf567fffa27173eff541bdc3f33f0aa2795241104fdcf74c197ffffa6739ef70f5226e7e9dfa0e6901e739268c73d88471cef907a33c08309d38e708f01c27038c0013c6518996f39100cfa1498346ca7f54564a5b13bfa637d97e4ad2de73f91d960fe2d7f457e7af2e3760f65ec6224101b1d0bb76a6d077ed0c193f431f5f7e01a1df39fd2e577de7b97b74ce394779b89c7391f29031ce95280f22e7dca50140e7fcf3fcd57908e87c08e63c117e752e6333e6c2af2e438073f9141973b97f1224ea4074108f21a8f35efd8f4ed9b4b6c6d9bf3a68f9ebf3faf45f1018f2d7973b18ca513a8b50e8f5e7cb879af4451c45c65cf9e5e52957d6635d1120167a145719b1293216ba15478bb28545a4cb100331f4b6fee6dc9dd969c5f87a3e59c7f5616ffd86d5e7ef9a1929d4703923e84c9cff2858f1781d407de64ab394e8b5cfdbae60c552d3abdbd00193be6834b314f5e9a3d369413350fcd8f456e9db4089e777489d03a501faadd34ca59406986f7dca10c8054f6f68ed298304f5da819514277ca4dcc122050e9f3fb1e952fe081cb6d0092446186c624d9da6f7cc331c228eb09f30caa339e9a3f861d3a13277be8213d6d2e7f41d52c442f3d38d6007f84945aee9d0cde084d90c7ed7cab8a17366e524746710191ea4b8620916b062ed47ca14486c795203963db1f6ce3bcaf8641571b66c08f4aee393554429a594338c8fd68aad4bb9597b9dfa66adb53e43f9a75a9fd6eb94cc9aaab6d63993a092724eafd3ebd433e02e757a9d52eab669c2ae7f73f6a90e35fb0e5d97d75fd5e5cf7f5528aedb2d374dd8edcab58e9e557c5d7bebd5760f89093061b689df3161d63b1c9bde3a0d65951df369049b260fcfad1f8073eb39e693f50ce653e7d63ddcd389f31b930767349fc6d75bb70e726e09f0d65fdd43beed70d384714ef338c729f14a9cbfb6d796bb66c39def9a0d49bc11965d6e9a306ec3b675b477582f96df376c96ea9a9df4cc77fdbb32f4e16d3874ebdf877f78dfad639cfd08bab7796fde0ee271734ae76bebb0df6c3c3dc4db748ef31073de6920f3c879e3f9b9fc28e714e4f004f3077a871a78ef7d0eba034207f3c83d28c3a911aca03ca1fdd4188231c8c87a267d670c1713be9dc9ecbb3c821f88fb43ec80308f23fce76067d06df8c37bcfa168fb00b394f7c3f3ce53b21fe97bdc7f598af3e923c779d78f7bf6c93a44addf351bc8f8061de4c0f39bbb758e6bcaa3a7d328bef551fcebd20491fe98c16f4ebf6b3510fab1f5fce6196c4f23b7c17cdafc00f36902374f377e6c3abfb975a8d9349f26de6a3288dffc9be0c4f395e653d76a583365d3d6e646f3a9fab6f5200ae56b4945ac4ebb5603996febe07c9a6ebd6fd80290a18df9234df01abefcc87d0e0e50a526ea75836e3241346aea1ef2a78334e8f9e94a0aa03c6aad95d6c98dd1f3d37bcc9d9f5ec1a758d26aed0c7d1411a56ab5d502a9d6d6eaa34a5bc4981a1a5bc358bfc7cc70bf6b63c28cc132c60a2bceb77fdf352c31baef1ad6a03a29a5944e2ea412cb2b62d7b0fafcf4b152ffae61e979a309a359cc10717a4f576a9c633e81d0387e7a6351f9e935034069070a6586a0d4e9044fb1c43ca51ca881afe73782383d04e2746ec270d30ada279e922f9333390df0cd3e4f7de20fc4b1ef3c0b62fda11f7f7af779aef6d529fe401c39345fbd5d7e11eb8f1cedabd39143c3d568f5fef313cf4f9fc18b9f4e9fe2517ade790d39b05ebd8642a653aff8a392ba87777c778f0b5e5ef059d0f31d6f4321d447f9529e53e9a07f1dfe28c05397e10fcfe987a58fd4a55f9086416676c2f9bd53648c73275364ec5e77722dc0b913ce37efd0029c6f5e25f5f2d1bc98ed1ff4d4a5f7b9ecfa94cdcb8432f4713511ad7b7ebdbacb875ce621bf380a57e5c5ac77f7d8fce62a2f765d4807579d5b19eb70992905fc387b19ee7c3889f79f831888d722027dc452483e073df419fec0493e0f1d8a12e171f643f90584ba947497531741acee721183de21073257776511c41f2b8d26bd2543dae3ef9a0c5633a4f91bdfb519b0cce0e6821f7eb5c2700b39f0c3afaf855f5f68c3ef8641c2aec920e6a9e653bf6110ef3bb8390a9765b39897a76c2e6336c458571b983f877a9b65d7fad6dd5c657314d7755915e7375f79ec7abef298cd53ae141093328b59b7beddf965a909ccacbfbe5a25723271049b5f9f5beb52e6d76767619e863ea84beb1d76607353a71957752707683ecdf934a9cbab2c101329107a959152e84a4a4b1eded095420c4a0374fdcdd002b2aad8cb83191331e084c94177223a93cbd0032607b3ace52293434d8bbf4f861868f991bbb09396bb7c08cb5a3ee42296b55cf40e3150e44b0e358fb0cce5351cc4b29683b3ffdc13f190872e732d6599e8610dec44260b5df4ec3346849dc864a0879ee41d7361190d976197b9cff021ec04bb137727335c56e5a4c989fcc8654a2e437299918b1d7ad0f221efd002a21765d92ce672a8c9846532e6ca1663a0657d283b117de8852ebff45d7b01cd7f1627f180786f9fa0f3d437b0430c2cf9908338f41676b2e42ddf420c140d6559cb8fbcbb070664a0d77059cb89bc430f8a3cccb22507bd6344d809e8455e033b29f2253fc24e64a02ff986c7d9dbd002a0176527a117b90d3d58f2962b6159e845b9ca4992d7f0d02fc6c092b7b213d05dfe75680130472972d05db9ca8b0d7988abbc98d8ddc3e671c7635db51ccc555e0cf45152973e845d2ee2d0a16888654ce2b1861410b30cf4304f6979999ba59058dffcfa9077f7f8f2b8e337f7f2287d3a4d62fd3a147d178e52e443593663afec24671993cb5e2e7375d23907719422afe14958a6e449fee50541ffc95b25ccb71d2cb74a98bf2f5c7d1ff1f204b16b65acde47b17a993465b8c0f0c4ebf8aec140260631cfe3bb0683a01804fdd3772d86345d622814e7677cd7c48c79315e88d1f31f085afce27290eeafcbf77e0bf4fd670bf4b7ff20c0aa56a52843fbebdbdfff5aa0c52feb1c7ed90c843e710517da5fef9efb0e815890a70ded0b8120c0aa0624635db5bc4696c958986538d7105de63568e4aa263ff21962ae6a2ab3b9e8a1434d89a1bcf4217ce5b1217fe12b8f81eec2552e3f72a65c05fad150ae72b9cb432fc2b2596c294f69e532530a78a9b1a50563d752a0bd74ebfd32a684af3c26e364515cb22b8f55811e7a9127611952ae02413f725915911fe52b8fb5dc285f79ac285711e52b8f8179ca508ea280d890831ee628aeb16b28107a99a9ea4bef42217dc4cb0c02d1ce8f160bcd23e93befb330cf81a0b7bc7a531e5e7d0b29d072978b94471497b7f2d5502e737d16ab7ec30dfa6f16417c2af1a56c96daf204663171c65a5eab444e2669347184aed168f965bde5533cd6f2eb63087ff3cb660fc4964b1f893ce5e1ca4e4207bd3a270d20b392b420a4408dac58cb6d0864c883d041a7a107a0b7b2ace5a0cfb0835616a99374197610668e3a49efb003303775929e34e3a6a8c46744ace2d777089cb0ea463cfa022528e5f1557c5563d529f6408c55ea2f20ded33ce57aacea9da7a9137592b439671e4177a8dd88d82efd6b5c3f6c42e40ab17cb07b74776b8642aa6c6f1f4bb0288eafb35bc7f5ea126f7ef1287d3a0d7f48dff274ee1544fae6d2b72cdd7b99bdd64141fbf57489eb84cdebd36f28c4fb76af7b4cafa190af754cefecb58eb96d58d2fee291fbe9bd01713e711c4fb17c71b6a1f3d2f35bf9c9197200fac4d2412c35359841a64b9f69c4aea1f0c47b2d4b10bb96c49df71ef4efa3a00c35e0bc73a9cf3dff66dbc651d986b8526a8f14c8b021072c3488c9e3003159e431410b1c2f6354d1c3c928dbb66ddbd643182d7e2c148328dde6a029c01c3d74d07060eee8e94425d148490935a212c7c25117585cec499cfac5201f3c3e46542324ba1c68395c8905985ddc7b2f0c69a653afe96230b851fa441aa50eead2767e20e38723f41cb184952dfa6c01e82f9c2fb2c84384973f35a08941112715165075b36ddbb66d826e9a3ecdc75aeb7556460c102b5cd9405be2cdcc0412acf072c294327058413e016365d0193e6fb240e992e5a5082e7dc4b88ac10949b36ce2341e3c50d48ce10394293e0c9149214ea02865ea7c31a74f6b4e16465974c6dc191472798c9a8c949af8d09a2c14d84b5016286e78a0c1882cca3833e80918594f78387343196396b843469f407bb448c1014b4d0c1cf2f09192464b1b25cc349161adb52c1002b9dac21f7bbd9e3014856e5dac10eeb9b5893ed4a939ce05a24253b539548411c28b345bdc5982852b386f70d853660c30d65a1ba8ebacb5b696012e56e6d0eacc41a14b132e78c2949105892f2ddcb0a9d15102894605ab0c6e183506258e929b2f8a2a4a70c4184e6441c24a0621857088a1842758d45153850e2846065cb7591f44a3d148b8f75e2ef470d1878b405c0c52e282907741837571359dda66f050b3e3ecb66ddb76c33d81b6581a3439dcfb0468c352c60dc33d56e402c94fd785b5d6fea0405d67adb57cc828210a3d56f2c8412368c6831836357c11a8509f355cccd0717186508d9dd1c345e7429f1890a013ea893874498aab6f87aa74858cbbc77e6dc0ac2b3cc76e81b66ddbb060a14ecdd5eeb204c1d5a163b8de62cf9b31ded0808556a5c9175674b0c2628a3f5daeaa5ca5f0db16469637ddd5810d0d59c6cc505b13c5ca9c30bcf4b0a78c1e5c380346d18c4606153172b60863a68b2d335cc9a9a3a5842eb6f8420a21ec50d1859e12aeccf11204134da8c90246166ea85c552f8ca813dd93a1c75a13b66ddbbee862bf0803f5c51851498a6d8b13ee094292d1a7761d5a41151c683a58a960c20939c67802a84b1a3b5624c4b6ed04335ddcf9e6f5617ed6986df3a47c9952c0d46ddbb66dbb6190b5db367390a2002c7875d6c8e0a70b9a9722703842101768aa50838610961d98b621b0f4b1b4b7d4b6c4a973852b8f119ba03673cb7282c845fd2f5d5ad8b3596b6d56980e2a6b8c386743c2b37685708fd72f1dcb32280ba13659c6f87ba1f6e831f3d7ebbd77a2d0078a7b034d013be27a4f10d578e2041d18b054a0e204ab14c70b1d3d64c8a0292266ad15b9b0d65b0b0c6fbd0ecdc9b3dd40bb5eaf38ffb4a9b18115c61024051a2a4ad8e0c58f0b6c7058b3070b32e86419b4c51705c76d4bbf6ddbb6fd30284e4b0e9638e19e2e5d9f3b4a76aab5d66be8c6da5bc31745130260f2c1893368ccdc21f48405b6d0f9418a1aa408a3a78d1e403306dd189b419c13795c28b4870c0c73dac8a0072462880f0002028b0a3d41d4292376b540faab2b23b18441bf6511ee9938b2fcb8e0c78d1053bac440e6892fca8cd1654f172760a92770a958349c1972a042072ce4c8d12289384cf4d122083e609011e4c2d1939a1d20b85813830e325499f1610b0c5fac38e2aa8dd315634e8882900e41f0b0678c31b82e62b268e86245137dde70e1851516eb6a0c9abf5ec119074df8e74e0a57747726578707bf61d502561577ca74b9635523020c973e5608f124cf10b98a3a85cc74ea296938d12d99de8110137287383ca0a132063429f4f90be7ef9dace58c6bcb09df9b2d501123ae28d385ca0e59d41021e50b165d640983868a2c47d42a5504b2db0bf7de9bc255d7398531a7863d4060e1414b0a7e2c9b1402852d28f834c1860c22a6f0818a196a587de80b8382e0b3678a0a2d142ad4431e2c4ac0e285acac3648ac699a82cc67adb54c6fadb5d65a6bed10d65abb0de19489a30ef809a3e54c0b59494c6927ac8cd1a60b96174ab892840848de69c17b3184b828c2cb0e7d0a4591a6082c391cd6119c066bacd21401466d8b17fe8c31618c957b67db362e8f0b45615c69e189516786afcddceeec81016914c24c9628600a95a0842f2cb840021a2dacb001509b25aef0d305dab66d83b3f9f05beb374abd88f981883a695e78a126e74f173a646cc101081aae164b525c7ddbb66ddb9647b6165cb65843e80b23809062c30c5904c9b0c30f5f78f14541038d18e89460092ad48431a706a83b25d043870e191cf0b449020ba73e34e28457df153fde0c4b94276a9ed4047941a748ca1275f65c9185972daab857b080c25a6bbf1002396b5198f3d6deb1d612d99124d43cc0254e0e77a464a1c1890c601168093c337041c699ca050935f8d0253625421a211344a9654d17d3e2a6ce15385a8f0d5128e1a7ce155bee74392205314e50d1420e5bc4ac162e6faddd33820e6286f0e385192d94d01203187ef82451c49c306259066199f2046d4214420628356061040e41ccc8d4d27c49b38410377071e4c4b66ddbb64d51636dad4f0bde768503578782208687385b94e0c2192294be9861f3e6082ea09823c745829e1b4d0cc1d2039d42341401480d08c094c04d187fca5c115168080c1a98684342c78515f56d896d0c98e58a28c3e5bee8f202478d90b0d65afb58685a35640183115450d863a5b0c0147a9e28a402154590d173234b15247050618a1d665750cce0d112e64b0b5d60a9e9b0d870796a6c883628e0f97b6f7b51b3b11ceeb491a205d826073dded21d42540282ca04828bf8c4d5e667101613b5b3e20a278aa00f34aed62de1a7b6f998a2e5083d83f0a030c316b438246c4106991ec2c820bab084b5464a4bb85962cd746a1f57b42eece05d28d65aaf367380a84ca77e4056d3a9ffba0d7d5c31cc15104c9dce895aa585826a940a8af3814f587471618910095a18163c36478419fa3d7a5c34a32876b09ac28827290cea6090c2842b65f23881441c3c7fb15022098b2f615859e14d1779c01ade88b17343185cf0c11386e80bdda1df45a00dc090f1a687287860c10a0c2d1688da9c1596b84325871a624974430c92828f96397ce4bc929003440c42080126d01765bcb54850f17ad033673d60c5e0a7064259f0200526861f3a4c7c59c2042e63007163b0388213395094a34616ad31e3a776e7ca2a50029c50c1083657e6c8c913dbec1da32623a52623b018353da197ab23632d74e9709d30aa174e40d3c40b57acbac446a5076203f4db6f39142ac10e4fcab8f1a18c9811b40d983cdfadf950c7cc15327c9101081b26a84346d6971db6dc60c296db26053b447600656561829d31bc5cc9132487365300ea12a80b0bb1cdd97491e5b7dfaab8f96dabd383b5d65a6b6351d0809cb5d6da276bad050b60c2e77feb77cc8318aef890c54f1470b418e4454a0950c46c31869b2362f819746cb031e30819de640123851c316690d952338386d01c1574189af32111c20a105a7238e20527e8b8b0c20d78b4f0b3c60a0b96a32254b821cc0956e4000395199648b1e2094130cca0868d153f7f8dfecab1d682b7d65a2b013540ec40278a31666a3df0f9a28621c688d113049b13ba388a53e30a4170663033458e141d55d040869a4034a8d043ec0ae4f2d6c271aa47e61fe6005ad3c30f3ff86926b6d0c2e5ca9331b290e28d38c14c40a4092068008485192a599c156c8ce09382096e9e64f98223020bbc0244d1953b7488207386e0e10a3092a04185cd0d961151288144153db010dbea0aa19b6e6384152c7e18446788137e94d152823228583168ca10829ab4f851f3429c316590b873cffce0c6cf1a2fa414ca32a186bfdfdf3634dc09dce1e20b2b4049a8d0030d596688828414c03803050b0e554ba0223614702993268a17405778b680e9a30397ab2ce440f1c56f48bff5c1faedb7da06c8c093660e0c62bc2c314b334309659030c419247ac842c687e50a4f0157b8b5d65a6b6dbfb5d65a2827682077e68c1381bcbc99420b1662a3d24371c30605a03f7a2b06909723e89459c2072fb1047022c8511e463d87893885cad4f006853676883b326c2184202b305ac4acf70e6fed96b7afb708786bad517523a35a4569078d468b12dbb6dfb66ddba470994efd653af58fdc4b11339d941e6a52a1e21dc0ce2993850e942a584cd175cc80c9d246892c799c7002a4621ec0042b8e8d0954b60cec0fdfbdf3efbd7746031c392854d1850424c2b813a0f2c2943e4160d99112bb70a6f89bc51b409c63b84e89e84d8d769c550a5b96c85203095c140008268ce033484f0f53f0e953f9fc117b18294111676edbc51af4d45ce5a4e8223118436ba20a9c2778001d29c20f0e315491c6061fea5cfde6b585231efccd7d3e15e03787e5c84a1356992064add2a563632f0c9a8111f4868c12ae50b9daa2858f6d13831a578e27bc680163839f2a64c860dbb6cdebb6b9789bbadfb66ddb8c26acbac5e284d52b693a516914dfa8a995e1d6ebb536e5214ea7ea79e49ec914405d02f1c9428e0f3191f2986131e1050c2f5a7ae86226b661c9d3fdc627ce6f1b8fdfb6ed66517cebb58aaf3110b9af7fbd6ecef975a3f9d4f975a5c943a4d1684f62d7bd7b8837fe5ecc09215e973c7e0c3dd4a9bfe31e6a7278c375c26a531e3dc61877625022083d2454111b95feb2c1b2e2ef5f3660a8f0d7c1f9e4fd7591f220c1173b5a6ab073650a2962d77d3e3dfd7518e55163d777e41149066f333861d5af1ab17fac3cb858bd72025ae28bb779faf80d181110302080a0b79e0326a594d7e9f56ee954f75bfe8df93425b3dea96e90213ffd4e6f39339552d465ad6438d00244e49fb74c04faebd4250e22fdb1e3bcd5f307d6c17c1a450bcca7518983f924f14cf0633ff1d661d5a5539761036642500da84b19faa0148f24f8eb32274eaab89e3fa6530ea6b71497c77b9dfacd4e30f54f4eeaf2e620d2a75b6fdaf0c55d5b62d05f6c5f9c4f7dddfa77b124a38c32bec3acced17eb1ccfa0e699da39df61d02994fe6d3a483382dbe963a889f0634f3b0f2dcaa8c7f5f87bb36e737c13e21073514423b363fdbb301b32bcf09d9d1831e00f59cd330c874fde69f6d8d5558e60650cff90c3548f25355bf798740c0afd180990bcffd9885f92922cff9d76a8d5561fe76e7183997d7a56f1ce8711deed8d7811eee58873b364320d36ff6c6aac733e8af77d827484321d5691eb9a7348f341452a7e84bd1ede66e1d54fa94ecab087e7aff9d9786a74f127d9210f43538a34c8dc4183c61981c419b7ddee679e99f6b4abead7994320c427de6b1082fbefea8739cbd82d83c5f9dfa1899ccbe3a9eeaacac2c2fe4dc79fcf86d9e9fb8c6cb50036f722433d5f49f3e29969a34bf8048a7ae81749aa9be9733e8a9e6db3c4e6f229ffaecf1538c7f527ddf3eb3728af14d60169bee84ba149a4950406cfaac9ad2bf5a71fc18074f778f71c7cc5a1f6b1e3b07b49fb7198bf3c69c5b5cb1c57252d576b0c4b84cab3e764f71fc273bc4008e47020e9ea7eeddda5bcfcd53f7e0a49115f43cd06b2884bae735e460cb537a3bde86423cf7386fd2d0c754b6a18fcfbf2b7bb6a664969c77acecace5ac9d9cecbaae73f98ee470bc78ea5e928770fe8020e820187a4a3ebaa82be151869e44d3125ba244138661e85033e9e66f0b93bcf324ff94b8d0c7c4b334f12c25254d3cf653c24bea2bb924339f5aee83125a4aa284925aad25b0e55033a4add08d3cf42fc449ae84b99764e653925bff88a4a4ef789947974f270a7f78eef21a06a1eec9700a04bf0c290d0d790bc44b8080a283a1032172ce4131832148f405c4b29594e4507308f72be129f9490e26f954c2fde0db24af49a11f79e8dfad12098ff25da16324a423236cbb4c98f5224cbd9830eb1dfef0287b4a88127aebb6cb7c629a78de3af5e29be0e75d971b5c32f32974eb5f18863e4326895b1ec72a0e973862e2a489e3a63f4fbcf21813c85579312f3365191357c80bfdfbc010ecbce59db7fcae994f2db7feb55aadd6e7792e1074d1c34b8078dee79c7b9ef781127b53555ecbd56ab55af9152474e961966426ccfa5d336196c95f2fcce44dfef2a64c63f2d70b7c7dd784d8f2df045f32e49c0997b9ee811c28bb50081b324f35dfe965be6b1cd0bd97c710bce75fcf04d098aafebac4e3082f9d094ff19893aa9733392dc694ada6788cc9b93cd6cf2ff7e5fa94bc798a6675ee61ea1f1e477807c03c0fd3d17e79eb77cdbc3361d6ad44f3368c659a77241a2bd3bc1599bcdfba24c3641d9c0960725b430f5ecee434f480c9654dced4f462ba7476346169bce520088214fc2808820e3545716969c93f098a7869c967e863a9d5fabe8f8e17e66bd2463d898cfc3a17d3772d8827dcac79ea49feb5c0243cce57f29627d958232af98e1fe58edff11d2a39d49c177c87492d90c9c61af1fa8e1fe773de0a8524f9383f9cdcc5a3fc71f1ec2556f2161e937cfad70a85ccbfceb906377379f612b7426f51415433bc968ae4b20c23ca07587739d40cc30758bfa228863e3e11f7bbb8429ee746eeb991e7799ee731759dc7e4791ee7e55710d03f073f974b8a49c2a37c25df96fcf3a5cff351c9a77fe10fea4afe791c15440575d6c83b37f26e9cddb5583e97c5e2eae4772e8b8586429cbecbaf2047eef22377f9e7e125f53d77d59be6bb693e97cbe572a8d9fa7bd3b8dc955f4190bce548def2af853ff730f732ccdcf4749b9eea2196dffa1c6ab6707f177e41e45fdff428e12487a249b81f096f7826acfa11be6826acba11ee3008acc85b5e9439421356bd55e98f9b9e4dcf57e708cd1f6f9a9be6ab6f78e693fdd162f9ea17cd7c6a79f58f069a4f43439e17683e795ee70c7d783eddcc2729455174d13b849ae2475fb8c5bce834f4210e09717a314f371356fdeb3ad979edbc5f2ef1fd9c082a6e9e20020f115fde7c21e2cf9b386fc8fc1b426ff4c01103e7ea3f2270c3a3fc3e5026be7c81dc95c71250e5c57a62c071fe7de0abfae5e407825db781f5eb2fe9fdb37f69c051415f5dbacca3eca5cf5e52aaf9d2b95088d3e72dffbc952d9509fbbcfaf77161101c3e3e795d862ea5cb4c35bf05b6dc7543bc0448e89c0321f2d07b3e71795c0284f32107223ae72151ee090b3decf93494c729f9a1079b8ba8b4a995ca33991a2471902134433333012001231640303824140c07f33c13861f14000e88aa4c544492c662a13045510c832010c30086210400000c000631c62c5a0344aec8650c42440201d3924271f94b4cbb6210b10a946717d5ee0628a9212e982b6da126ce25b1185453f038f2ea7547127312f3a9cc424236df4747b031b63066f313b256e6cc7c8a9f369f45fe2123bb130413bee4443dce4abff0eef0f4725844b8e4fe0a1a79d015b68e22500cb51261432829a1ec176850d16811078a5edfa8b748451ec34b5d18473c86b52c753cdf32787634a7abb7227786ffa0d87c6796ce33fd8c4df43abef4da7b7916bb33c74eb3770756659defe355c7abf434507d9f827ea09dc2ccde3e5b99f8c6daae99f9f945632761a43ae98bed9ba0976b07058e8402d63be993d5ec3e1e5bb5371889ef94e65dda29823cc2879ae58177ea14fc949971de320ad27781a854f87c3052c4e78703f58d45ab998438284ab193030823cf365f82e53a515457e7598d88fb03311df8e115a36fd399f7d09fdb154b02245431f9131d9580a2a4c7b7319a8f4ad838b60e76c9464c5ad963134836ee4025707c5fd0d821fa841c0cec6d9c50c39c29c62ef8e2e4943ce169134d13e2be4a787ee1643eb775633c0ae8df84e6484cad733468e2b7115c866a1c3924c04d93ccdeb23b3aea03aa5ea07b5911a475e9f153ce510200a994aced909f6e64737be3b9ae6f5889adafa142ee16bcbd719e86fb6752b8fdd90e6b930b9fc144ccf5159e2eb623e8c34a96f889fbc42ebd8179ff7c883d6e577075140ddf3e4b046ae169c3d5d839825859a6a55ee35dfc4a19312185aa6c51eca048a041b787ad40018dcb6e8048cf43552183f52436e4ae22c9dddafca09e82d3ea240fa8acc358d4bbcb31f430a32df26ea0620c7a689ddb95a781d9fb878a8bb4780259a7fc67dccb6922ec7e684232908182dc043ab6153e33a6d2dd5d541ac9416047270eb1588c3fb2423e61d2c07b2d48e75f337b55b2003f25bf54d2bc626781200aa02023a7ca9229dcd376e6ae829d9278fdc0bb486b77800329c9d564e99b2daf07459ef4b8b89ad10ff7c76ada4a2e2cd5f28d396260bfca3dfefba21a2d0a0cc5756b62bd5200afd9f2d9ae0d5947fff7c0ac75dc96ef8cc7d84b5273b985f4a021a46c5924cfeaac8f7e039822d36078069fb664482231c97e5e34732374727d54caf9d21b345519842437b8c0b28145cd2764ff07aacea24211d499d00349147763b9c9d5fa16ff5e634a029afde4a1d72f0e02a394fd88a22e64ec71d8bb3b2794d710d1823c8949993b62f2a85ee208c4082a8f6d3791a8d2692eac758a62ab4b23934c06e997148a041759e529e56c3576f47062abf59848f09445be43b868c51a250232b36e1669f1f0ea53aade865931e13b60b6c77448820c38dce7859353c5957c105c9d3055d991a322412fe82bd011c3b44224056ba0db7933c393960364d2ce65826dc06e1ea306f3503c32e2df0a37069ca36f32237f38eb9fe9867dcaf110491ee0786e416f2aca5b1faaa704fbedc7b1a5765eb6f20940c5d094adcae5040e6f4ce1af5e3a8860dc5ad004d92dcf54850971c631d558d9f8f96c58c4f171212bef00208440d6c75069e0123d2f990c8955de134b0a655281f309c038735c9790c15c4294199bb502024e5929c2d102e39becc9479fe846534b489af38116f6856f2e0da34c9bf60b2f146b4ed05d022e96b42bb189b232556e9361e9075034996e73a7dce10a0f12c60dfc8c825db1a592b7c256608a71cfe14ded7662fe399f24a92e80626475c66afef246d1d9f2999b8b96606a80696b345811ace3ad00a7981201024f9d8712b733c3edb4113389479f0e1b8e339a32a25f397c668a19347da3912003c182141002ca2e22587f14c9e37bb5f93f55e0e7a1ed580d80abd61ae4b15b9d500c763412d72cb8cdc0280b054764cd7e41cedd9af39496a8d3691e4850dad2502d9b704f48f55c121af45486ae859c718494cadc6bc34821b78d23f53106f35e4c49b42fbaeedbf6bd1fbb88fa89c5e20b76384725676974d0edc0915c9da73676c39e4d5ff69aa7e67bf4eb1d50b957f369f4ab313cdb50fbc34c0a9fe10eb3140daf43ad55ce696fb316d5ee716e4738c562aae558f32bdf185e748bb0741b9842afd5f20b2cd59220b10d9018a6b87cd726ba0ec08b892049ac5872d6500b8fd93b7f5e79760d9b5188b88b1404d6b43cf62930e664402b6674770afa0ac80ef1b8bd501d59c3513808eda13868a75ff209c33312c2d92d10957d364fb476172fe91d58348aa1330c526832bcbdcf726406c64d7d9c448c7841cc9487586f5fb1fc2024287d761ad97f52643104fce076e7b8101310632a5de2b08491e627add4caaf3aea89a27b2d2b457a593d4fbc15e160a3a04b56358fc3591cce8f584c180bf3d3830cbc3ffc304035468f80fe75b999741ae9c4257a35c88547a063b674d2844bf9a7f274f03e453bd5613af261f6a4b549049588e5b2b345cb5c825e1c2cf3bb991681e3a8d5d4582cc2e197f9a5d0cf4352d82c23d550181c271315418941712d53e31bab8ea35aee59e1aa469972ff089456edf96f40527c74db9c0b6c8b0bf686f0290540e7a87613f33e94a5a43f446e92c74e244a80ed0270a2d112513b671c63f6524e7d9cd6dd66661abfb9b93c8c73974421eb4bf86e8931aa838c4d2d828314422f3c18c70abce8ab24e30066d84dbc2cc8425dbd1f0960917ef29b29c258a2b702b784a8609ca0db03d3352dc223f4d1a34ed447b4a593dfdba50ca038eb8d088c1f7289f189b20b60a1a808c503958ff1e2555e5450db1d44ffa976bc85c233a9b66792c4635df40150b0a54d86d3d8394d85566d8bf713f069c5362148e94f604b4e431414833c8569ca106ef6508d0526e549164d0f5fa4cbf0e5b984cf534900bde579da6b51d1d345bb43b3ba3a3fa30ac1928cd6e088aa26bea94172f23812df52424e31172da1cc48ad36ce1d3ec0280be4c46fe1f66e5d8de9ed997bad4c96ad5aebf8a3258219a1a3daa758ba3b48cded0057a78638128c3963d610928313ca0ac0e32730817034ab305782239942ceddf8460249233643c7986a6ca6041b1347a436076daab11226b5c234d38ca7169b69d8a47e2b9308156f58041d6863cd3d3f4b9ac2919a4022717b869d30d9369734c6c487eef180b80eb538c23e69b4027e0eb20a111aa8b0c8d6949dd2e7ee624854d8a0692b40d9a58428b3d9c05534d2ccb9fb7f9c1d933e07853030efc7770e43fa50ca72599c710c1d2a6a091f90b629f25eea6bbc8590512e2f42075d66ec947f8eec8a62c8a49c98777620926b94969862e301102d37fb0b9745e4e91ef7d4a6b88b0371d1d1be9655419fbe4b0e194e03a368182115b84adfce4b3be184a6372dd7c10c7bff9fb8fadbc6ca8df2b19ee0a81fb355abc6ba3e5ea1228a01b168d48dd9c575a3d0d93df21f16fb19e4888a5b1c0afddcf8c7c5f36b365b1662b93d2d96135562594c29529d18e9bdbff96bb03451efb4c881f964d266c87a71a89b5d22c82cc6673483e3c4e28d966cff7c7cc1daa4bdd433985845c418a32b4088d4efd420ddaedf95935fc0a60a22ddea1e73fcf8dbde53fe9de888dae010af22598a5549c16305bca7241e05b2d499aab7e419586e7ce0183040e959cdd2d87a7625c716a04cb9571e1cf91b0b56b04d8c8dafb43b8b424ce3cc95656c6e3424b8d4c268ecdb412bb269248e55c5218a7d2ebf2ce91028e45895796c73458a0321032a6e8f066e71aba560aa247d88c634961dd1eec65feb5cade94596ab0d51ebaa676c194216f137c83ccf48b00d3a8a48e8bceef4c0e23cdb493ce650458a4c8bf29e9bc9e376b9bd44656beb308e9417520504391b241412f493ea1ce3f59b8f39811a820ceae6da6fa3c286c1cf0281fd3e8014f5a8c2fe41c5945b4597c12248dce54447ae70e6e231373cace143cfe0c788ded98cc1ff990bb28cc5a780949c696951b337198e9b3b6d97f243a3e34a12e6872b5acce1e4bb5cb136d204a350afac9d38f11e48b3077c812e28e81685bc9219c1192e82602c263796cf0908202eb2ea28e9e740839b8ab87cd62436b63e8e50c2d601a03577e73ca9923f3722c8e3a9214b92c37317449f4e013168263df5eb07caf92bc17a08306c1631ec2de3bd2efee84713704afee579fd52ccb7fc34004a46853e5e3f2063adbacdfbc671ec3b2483a9c79fde518bc94ebc48ec4aa9cffe88df3298b7fe5e6808c40eca4008e6bb909f066a45dbd9509442db48dca0258b80862ef95428698aaf102b75ba08077e950814a4711b3416c9c1119f2aaa2c8d8d17f02e384b86fd7603b62ff7e417c300aeb0f1e98015dfa24cf3e4b3375b5a2a9ca3ff0218521fed1513d3c00c53d3525ce2d2fc39dd39e2fb56560bd7371f1bf2e65d5890846cbfcbf676886984c9544787c161759517b3bc8b8916aa8a290c6d59bfc83771539eaadf02b080b367cbc9525e0b51b3c9cd234ce3ce6ecf9128dac82009b13630a9b21da7108339ee35b8efbf13b3b8e662509f54b7f8d0f2fa9dd7b2c730f1474314dd99fce98c6316f00b10437d845373300368d80b16267b2db123e878c2a333a6138de5f030ca6f8eb96e230e56b0b871f17a94c9074eeddb2643aba38f7ff18db910ef879df31e278a5b72e6eee736f1d74e12bfa9cd0bdcb9bd471f017a9e3f47c88d97a9403d4c26f3f1bc7222db6d050f89a11a8b9b03838e27d5b8359fbb1645d9158c02f50910d8658a21c68517454639fd73d190e88bf79416d2644245204a5e97a4c3d2a2c066e9690529a3625c7aace89743873a36bb602a06ec20e359770e53ff05f92a998381270ed44485ac4b1248e68b16d2c3d384581fc9be52311205124a5d93034be561d6a02e1e27176eb0d5dfae85cd8aca07acc5986ca2dcb4961a5355a45fa4475ff88d54f015ad3cf4503e0efac080d5c075df0fa38c2d870fbc7ec33ea4949a7cef16e6c1eb0b08ef1fb712ab7c8e19d8d2b3a732414fdc285021b5c2a4678f84f63469c7d065c439126a79e84bdb69b066ba78b3971201513a6a54afb7a145ed538b6f38c6d25f44c6cc007307a15fa78133d40d2b0313181fa6373ef8d9a0b5575981110b53a2c83841f1e44816d8b50b2e5aecf942cf505a75624cb034a9399bd81c1c54589a1424fd6f176b60544e1ea36c2f703692970e91273180e1b808c234b777344cdbf4ee6e4620b1c4f60ced5efb0218390e7213e43183bb044a7ea35a863548aeb0a6999f337a86264cc1bf53b29d6070f99b8a1a3a1b5eb5e02aa8a5eb329e1b2021bd9bd13ecdd9d59ea7141a67a83704ddae8eb52b573810aa9afe5d3e8a3c835165148265751fce0718d166c66d5dfd9841b4f0d589985fbb302ba789c2d954dc2d8052d81e11fb115e59aed3385ca50223000f2d40e3b34cf4bb77f4a9b0052c72abd1414e64144ea22535d4f35247d937f33eb35eddb2b9da166e0262c1d76b260ba74dd71ede4d5010eafdef150c834945aefc080b6ec22a79f4e61eea715861b3cee1467a3ce2f588c36b491eeae6bb94453b67d7a6aaf43a484e98223a401fb9a7c4b25eb8d8229476c9d38538928e499422951b90811b1d9833adf54c9f114058d2e817cdd0c51a2630d392a10e62afcad9b0298194261209c4cc3451e3cf7a650b353a6e4186f6e456b6642413f9b21e3840109a1d4103d78b6484bfc8d5b98d208b41442fba12f736db21708f60983909c2d2ad5d2e46d4f6c44b68277cc485d004fd7416aa5ec27a3dabaa8b52de2571eed494280e6eb758b843fac04e3e74a7609e7956c36adc3de92505aab589089026de8d5943f44b383a66025209a68dbc46c1ff035c06d7767483f8d525baed8ece01e8a05c6629c7f085e49086cb575598686c94c3435c120cd845ced705426b88f6d5406a3bdc859c5da06978dadcd16f52726b0a403f660dcdee2685726a51dbc5f4a05293bb1b9378d71dccd1a7828376d20178462f32d9ca818c0b7b36a155b3a8b551bb07a468e6a028591c5742b6da0226fe504294c1b1bdc9875184294ee2f21b05eac8cd85acb8e0104c55b18850cf2029b8ae32b5dd411553ba209951820b31818e8283a2d8fe7382e0deab5636a0154a33a4d9de54a4ac636c0a31dae0df5d31b272e07c33add9a48cf803295bad2bc95712725d8f301fe0c6c46e99e1da0e822bfeb9f4ba6b4cfe54d97aa323100e2aca3b0f5417a0b3c47fb8e445b601c4d269ea0374052c4d1998dbee1894d41a3dc9e697404120a659a5e71d6e80c09d039f82eb3e5b4d6c7a630e12b882b49d464c8c1384aa1cb6ad3f4d78b4a57dd6c43df26f4295776241111c81972b955ec03dfdb3811806cad701ddc5d4e50fcb0d33c7879cccedd243e1ddb15aae5ad6efb7a03c9e154292fc7bbc7edd53870a159d9b48f73a2461b327a9388d9b82d42bc47bf5962e3bcbbeb99633a89edaa4d6dee954a0bf60036a53b7eae52e926e7ae53f7b932fca8ca8ad2dffa9dbf5aea8623e19cce1e4622705515f6e735ab890d8c6c1d0d010f531ca122976e70add2f0a1c076a310755597e319c08fa8408ecb55bc020593bc3e250332231a70def43de89967f19cb0ae6893a607a91113bad497a894464ff749d8723001bb4eba4a44c05af6dc13fc82b1ddb54af31d2d7341a65e6a9db50e7fd880f8cb69482f7b55b2fbb105dc3d4356b69f65f397f0391e38448892108a8448f7286b0b2cf816fef8c8eec88d2a3acd1803077a1dd5556483d242dde49a3e73fd94a4b910f054d776d2c4f7220c435de97a4116172910260667dc926148d562de24f2c2cfc4f65a22845ca894829ce2ccaa6ab42b68535d80fd8422b91bbc2710f7ac7799240be2c2dc31166a1915aa91e69a133f346b57f32a08bb7b988076a9bae46fb5af827347547cbd76d6003d75415600e7f957e82610817866b7e101a5d69f9604ef532f38b9fb3edb0b395fd2389c567912e0e9d0da93b54d728a09c7c1cb1e0c07117e2fca8f4b55ea6af46e61fd3b85e1018373f0625f36e834dcc46be7fb7b8c5de766030a8d6c479857c1973c6b36676b8c431c89c6029e242830e47bf986504736ce9c774d8c350b991a76f4b394ea54075e1d9c2ec48b193727508e4f8ed4abea5113d7f5048722303cc470f8dde90ae167f9a37d2cbfa69d116ce9adeef1bf2e94320cc40872e17de7f36d80d49deff5289bfb0923f0cb08d8bcbeafd69461440d5c328df08e3053c9c97eb4d194f348e753c4073d655d4744d0e15559d9024c27357e7fc22b6acbd66476958d3e3771739d2f066c0f7d93faee5366591e368f9a21d41f9c408402f7e97d5a76d245240a6a8d40cad8a8a23bd4290102c66a24734fff5ff4a944bdfd40d0c654a6978d31a7221c8b6a51828d7a4528f3bb32d254797a02e8c47bba879f5aca165d4dc20e653cc9120ff39d814d56f501b370e62cfe89df7e00d85e7acb2c0212081526502be698426c334e77c9daf5d29aa41bbc359843c891ca372c489845615c2008e6041cbe7a472955b44d3bd97fedbea241838b5c372243c175a14cd61712e84eb24db5b2457c3cd0f8b769b154f6c082d6e4aaa8a124248154168fc0a0b6c370c6ae99e718bab494f6432683afa914ad1ed47baa5cda8b61814a2882ce8c5e9d2f0b9f27fbe09e8c232848f205491477c509ee1d7bb36df686fadca21862ecb2619c9b9af9ed5037de30aa9a73333607c469a216a3ccab35d672819ae2fadd254a653d3339c6916e7029c82a3e6b2a73f438ef569b0f32b309a826027d4b0406472366ae6a25a6757d7e0e2db0aebd8deb84ea2f398cbc39cd4a4626da03609687ab16e073b382c2377ec4661bd5567a15dbe06cc7b22fa870c26c8d66526062545a904340c1b18803b009ffa3fdd5a5918bae3675ca6d114f16fc72a1079fd270d58d9e132aa6353c101edf289a6b9cc3a0206d42e6b98ce99bb946fe43277041039ec12fd774215cf736ca27b97cf50e88e2197a24a01cf666aada860480278e97f1376d7d16c7cc970dc74903ac9f3fed0001a290aa0f9260c98254c5cea6a6b3ca728b5274235573be8bb96af15d086278826cc507fb5e7a1ef35c6859a6114991187c77ce8bb8a8f0c44e293b3d3c414aa807b9e6fd8284e69083d449154ea4485a8b1749cb1227d4fb6d9adf8bc11fbcca5f856006b21c2100582e0aaf815a39289852cc15fd90f29bc73975081024a58404a709131bc916467863436c5968d658fde70a53c39b3fde72b49729ad556b90197efe042ce49862d9cfc39577203b09c4421b91c6fbbb7e66bafcb87b3c72ebd77b84ab1942d98c854fa24ee07c4d051cb08b85d7bc71c8a9f3b989770cff512288a0e1d98b4c906cf8868cd27fd088ee653f11542ca0f4d6e392a041a0cc67af79a443a96e4b650b781eddfabb711316ad6fb99c9ef9420c11a7f793362e771bfbfa99f756b7b24dc16beb8d89d393803422d8a5d654e3b9f25b3db68ff27fd283ba488cde19a1808252398005d6da71840f814a76bbedfff2c89c1837cf38f357d7a4d6a9713208687cf03ec3e526d53010612e7b4ed5d650f293f76b9fb2392e280a9fdd3234f65b8948455e28775736d99dfe9e6dfde035b19742d6181bd64cc742bc5b92736b09b26a25ee71898c06d807f2d776d9d96e2ab95f565c042c37d8ab068b2f5ba5d5c91b5a5340acb090ca70b59384e68ba54f13204dfddc618d38369e2e3592eb98def94eb511ddcbee3909fdb3d654324524e12e46fb34c1b917473e01db0aad428f0bb92d275ab067f8da1249873de103f55de0918f28b14ab4d88678b4ff2a07c91564609ff1dbea4e38eb1a94662a5bf6b3ab74e481f9841384f1b4e4b7972a6bd1a1dd47d2a68db74ed1680afd2736741fbaf4ea34163d75e8d7ff501ce9b6c4f1b9ccaf05dc6f019cc210b3b433843c7572dad4d0815f6da8155c1123773e6b022b177ed9a3486910c6baa052b6f296bef730308b057d298b6db2b47eb36a2e8227ab1df5e76aaf0680159981c0443505aa3b55a93ab4e370b5d44630243b5fa66af295f6e58d058821d36dc71901da1754a5d807a9a89b371f0e69b4e5b2bfa2e7d69d85c023262fb5027325e0bafb495f0f5524baa5f8431b7ad06e1ce6adf1e01c25c40fbf632049aad3c585e42fd8dd818a545a294e9b6d69bd7c7646274868dc66853b3a4da0cdd3a6b4b62d0beb641396341c1e61018cdb46a8f53cdf256cc55099bb220fe6623cdb4d6164e3e2882317b7f8febb19434a52e45810ab7e276ac05a53e6962b80208335ddd1a106c09b834a00e3b9ba55dd23a9e08ba6ecb6afb63ecd819912c6c48a6a1839165af1ad62f073bd5ce832e561d08cc11f8b50b84edba490eba4380a9c5197bac1bd722d5acec09d16c5c327b550a01a603b6f91d1f0177cd3f876f8f7d34edc9b407dad881de812ce84971284311fd372245b2c1b46139776a27f429ac7be8475fd6a2d898557abf34667986de9dd748440f8a66356c45b49013b62d5603fdd1c6ffed3de80b982bcd7dd3cef6fe58b45afacdda313a75d3ae7859853363d2bcdcb5b0d0d0ac27443fc849fa53816d2df7057b34d6ff82805575a4354349b96a5355997a6afc1b5d0aefe3d141f5c8180741d7ae53023c3241b64d69c429a125c46f142208b429459f1e7804b89617fea0288802c0083ecf102041566c6c74e3900e2d76e5a008d08aeb9a0de11374981e0061941718c0a82c5750c5e3dd3ffedd3a9620c82ffef6cc25a5d83f81ba50d42896e94fe83f82b14f81f34018b2925c2a5dc83238c837478a2be3f34b875d04e35b3b99d029dc8e804745731a83eb9e533a357c228811ee1c35b8039e3cee1c7eaf4ca77b0491a175972944cb9628c456e89abb2b66d67ed40205c3f7e76480677433c9c1c375fe1cfd11c80eee0a8e49abce67f5f8615114849f83d3b6b9c7ccd7caade47a59dbe4ba207fdc02c81d9277219f868d1405238a4d575df570c0ad79bfdc7143e11f51a0e3746cf598aee191533b14ad2fce31e6c1e98ca977159578ecf621a5d03792c4ca6fc3c002faf9ec58565654de87e4b3c4a12b1c77159c7d85c8eb8eb4ae6ea8f0f5b46bb217f9b0a09c3d80d97cae5f703989609d9707c3c46971a01e57009af3710fbb78bae17cf7c4c1aa05fc8bccd85ab9b534f26fa70af62a985d785aceb4d969b2344c853c76ebb875452e390fd132c796f2465258fa0dd1517e466e6827740523db66b45078ea078875d966d9b497630c55134a65692844cad5b94226f92321ee91678828461938e36c738cb9ca16ea803e9c8e77fa10141032ef056b90c8ade6fa0203b4c4d92f554f425ac65fce40b74a5865a3431392d466880806dcf759db4910cf1a8330e7a5fc9ad52fd222374b7c4e7bc6aa08dc6042b20821bcbb986835bf5c87942c70812d3df04473906a782f0714f28c9f247abe1552501e171a0df15d3b883463028a03ea8f0e6d0994aa7b87ea9e8be169c916da62e43ca954ab6c61ac47417ab57c69607013727a68e5a80494f898c12bee605626d6ec0d96eecb67268263f05df80a38d5407e91b9dfef91c26be64d170e5f9bbebd4aa7f755e909238a1f58e98350c983d2d38e046311c5a14d2861de7a2cb5478cc9446562c235bd5aa982e0f8ece1efd62d1b2da07127718d13ddfe743d8e0816a9f78ea7bda55f6a15229f0d1d33bb254672dddf4ff22134f48f657cc5bfc846353bfdecf98f3b053630c0072c28b081051ee0a0800d4cf00007152c60c103382861010b3e0043b6c4b9834d6bf1875dd24164bb4e46f9ff9097acf8856e71075d28c5ae516f4ecef99d6fb7089ee0cccc41a4c77ce1eb9b0d514550a34741c7569a01391e80c8d01231733a1d328ce7982a6151ac631257d899ac00366dc55ed34c4b802896498b06e1878de0ef200c4ec84e64cfcf4858c400d4681864cf114e5c17cef324a1b20b9dc98a52c2e28b80cea75b1ef60e5d6d56a1f4df83b2b11d80440c7e34df28eb3a69a569b5d296f58592520f06a35e52958744f8d58ebbeb934c4929d35ce48156afa44a8003c14ec69130136ad7068ba172ecc90e61f1a0fc086c155843a851837496b2d95a8b06a4dce0be4be46fa3182a0debbc9efc1a1b9031e96304bcf84a630497edb4576594a3ed20bab2f7ceb2644df4ed00c5229bb8db8961f829e6b795ea6d8c497fbe5266e17a9c6a4617d4bd4823e8303ad27a7c3026f35326d2e69cb8da42576f40667c4b7f81e810e117f049af82a0a3ba25067dad8ef390086cbefb7d8bf87307741b0bfd0e218a48d9b6853b68af1c1ad2821edb93c654dac6a21a1ee9527e562d1fbd85253a3003830dc9941f8c712ba599252e90f2c832299e200ecdbd80ec1b6fe4ad3723c0719bc562d310b2afbe602f5446e46832f2e0815841671543282bf50805ca77e5fcee227f2cbeb425c9b044d7bd71ebd9dc8ad042370a79941a8f00c381d9ea2165908a3750873fbadc8ec658bc307291bab0458eca9d0d81a8fd1b62397e768166fd8775c6278f1058d33b62f590460e6ef891cafa2ada85e5fd992452ae4e59eaecff75092473c8ff8c87fd29fc4f30cb0d7d0b680b72c66534413da70e0924b7f21fe1575bff8bb5fad4c200bf203b4e530a3b5fef1ef982d605a9807f3bbe53a82b40b040f9ee2048d861a1d977c405801b538b4530b1328d89febedea4dff99f6e4f87a1e40cd76ab6d7b42832683f15d1a29a68592e139e80441f17496180de7358a117271762d7a8a3f5ae0d27c11609de0d877bb89786de89acf9eaf942cf2e35bf6c3ba0b7731c1e7d9f24109662452ef4c586e5b696f587870447fab05b8d5596bcd21ad024ab6d11e602529119061184892491aaee3264a9b207ad641d19719e195649496cad7cfcb485034a5a845666c76a978fd399bca014381008ebe9b6a6ee32e7aa352679fad9915335f02596bad6db88aae6c5ecccebdf943ced2e48c8592af3298ea1aa9abaf99d03fc4435ac9ce0d45007e97a5583431a48227ed54c8ee90c047e41c31bebeb1ea7ed35e3a7c73de8270016b4042ccafc5d060421271036d1fa6026df6d933f11e5e55bef8f93e8d5de2e5f36d3c5aad84eb77313bc02b460365be98d72874914856a30bb5de87c8a7aa7c99eaf738e2a2a49a51d18f194df1d701b3e6f0be4c7b13455f953ba21ab20c9239664e277e051f1bf6b72eeee032ec53d6a02e89eb0f2184651dabccb0e74e7e3c5c802141171e5d6d5a3cd70502c637a6bf1dff436c9c6013dcdfa960424a55606fb5ad45ce3348884fb844cc3cb13c3af4d74a645914838497c9bdadf920ee0a3ff28bde965ed373bddf5e0a13e530ac48d34b70904e67875fb0ae6fcbe29dba934d44b6bb61c219805e4fe61c882c32bca63e5c415b6dcd9f2684838ec9120d3df86336c20f5dc1c786323514e309a083c659c0eb74a125ca124235bc4b6ed802d70ae023ab9171e60aaf258690b28900b41a757417a79a9660483dd673f12736a0f0aea1a0f02557fa01eead9158036a263e4238a33ca194b049f21b881bc04b14f094bd7c0fddc588be84a45d36c1463047645cd35dd22e0753776a7209c7cc1cfbcc2d250712d73a2bd046e5dc1517b02e853575608f4d7b5e9e428bca97b132ea744136b445a7410ac9ced36f4f771e5eb4447eef19bce95e8306a7d42ebf88648b4a11a4af286329677186594dcd89cce61b26d56d9952f9ea8d43f6139156ee890bba10d91a54d463c49ea60f2f7c0f22c403e741a147f5d6e18bb6e900333f3c56d09bc0ec00e16d17c627a568a20dcc987bffa7cc601808be642d0f633b6c743496bca1fcb8ae394964f007ecfa2ab0a0bf5e17544c941c9c26ba070e041eb1bf960ed95f5c4670c164f5bb63fa1a6e015502edbef9f62ac4e37b702370b3f192e67acc39a12746aa2ed342f0be827ed9941d40c35e94c6b1170862535309c6e0fffc004d6f2b7674f53dcf2c6a0f8448df28971879ba42d8eb32831de71ad1e734d296fc57b0e3b7f2b9201577c25b79050533c6ed84a8173bce268227be1005a7914bb5f702d15ac9eda10840ba096334e1d0d02a4d105887537949e11efbaee2adecdb30c32d5eb1f6801073f8858c030081d50b37d84703656b5605570c301b5be6a28247b2a546a8b79dac60e830230d3f0cf0b08f5dc5e25fd167c89aceb7686086ac2373e941100e8eb21d946a35444f0694cd5cd0be026dcad92f06d96ab638b3a1d13eba8081427679cbde49524fe000884dd8c475d1788a2ee016d261fb0aeedacbb9a7c91cd328d4074357e491c0590cdc02dcaff8fa5537a16174a1125b5e38d4898f7d566ce2784d92252a57dbe9a71ef66c501c297b362afa64825399c051a096f9531ec0bc826e8808908992f778a02fe9cdd3663e186867f4c2350ddc3e2547c52cc8400eddbef4ec8be9a217014d0171e52bd83177c7461040f01224003bec6339478286b76f0292fbd36351a64c556efe721dcd0439f2ad64b0a48c91990df67348c806e241f33396fa2593c01aa01381ee3e5683b835db755f6f6d8d3d6522084eb3d34f633b0a4c83716a44e23dc1584560cc15e0b9b0de873403d0c3c093024af404ca93ad5aa7e5d3ead9f07e639e567a0711ec0dde568771e693817559a3f5a71a425b2036b5925a97af99a7299649d2aaa32543ec0ef15bdd389f69c3424c3880ea8a9b3b25a9f1967e05b3fcabd87f276ce042544cb04456f2690581e908bd697528e7a6721406c4255fabf1e154160cc1e3570fdb0fbb7662900753a603fe2027598632320b3030782e0140608a249db2b251a738d1c7673b692f0eeb0330258d27130823a5ad4f075cecfd315d08417bc2fe42886bcae86b7db3ccb319998ea26c43ca6b059aae7c8952f4606fe98a977009be6b64d6a07cf7271290a3fc2531342ca097244fbfef126e0561c3c07fb8ed4e6a940a658e2a9e8c682cfb84df397e3d8ca8bbca3cd7b5211872e40d2a6ffb1fa86f3e060df028f24996b5624b952f0f364ff1ab13fdb4c1c8021dc12e2967205993c8d84411879715d442fd0a2eca84c6e47174570ea10b7235754c82c2125ec8455e5691de1676aee06da592fb75f848eb0103a25c2fa12f279527673c69c6f43fbe2b64c51ab08335966bc85b0219bdf02e3ff44d429dc99f863d6c3491b3af5a6a29f14bfe369654124e7a8433a80a65348a5e6ef73380479b84147083c88b48b03b136db058d54d52a83165ff6416483e131f2e308ecc73a86771f5e4d4754b44811f009d40a9c223969993be1afa226979610465d6fe40257a06175bdec17feadf348c3e4067a88ee68a2a1628b9e3cdc010e3a36069622f5219cae2cb8c28a06a6e6564c43cb22b87295b40ed4bfd8c60a84a0ca85a39f3166392cba33f3eacc03fa7ece2c9155c140c5379f1f292264d5aeb8c4ce8415573be00d55382d9f5ec81a61f13357cc0eb3ea61ba50ae8ef6a065b4f536a2ba3e6e28d4a034b7f018fc511ea0255ad2a4dcd3175ca207042769370abb16f24b8b1502c56bc0b13bd48b7a4a8caf3afb91093506a1934a2a3fcddc3b53991d88772f593ea9fd96c1645920e190e6620dd32dcbac3728db86e65d8d184f6d2b1cbc26fb19fecb43481ef37a477975190ba3a3501e182dc550e14409c4e978495415cd921c954dc8f3288effc6a2a521043814aa9c1e064d7c8e41c7a2b15bcf241f6f9b32a025a3a23d3d2560f15a42547e174fc2d571bc7faa2f091706d05e4f4b2314374257adec60cf7ba7095f2d53ff80b70d0b0bc0e7880dd76675e8d6fae4adc0f6e980fb83e9eac3057629e06ade2aee21fae59037c97d1b582fdef2eba42e879922b714e20b085f97ec32bc4e0fbf58eed2824cc55a41494c2e66eaf4a411371031c130546b3e87008c80d914dd9f0484059cb3b704883da58b0a603d522ffdda6b1bb86d03884b2a8a0bad1c31ea7bddffc8e48362983eff320c362d2e54373fb17bda843e6b2b3b9d3a069dfaa62304945f2532a30d588dff9aacfd993b595234ab9935dbb166e68e232f2f94904f447f5b58666dd4abf884031d0d8b366250258408eb95a94fd00706fbff03e4053196216e8418053f6104f444582df14be14488b5cfb99ab2c52787718519004485167a5a856909d052b248e12cfae454f2e9c6e6492be4acdde0f128662501ed2b57560212f90108b5291ce1cf8b4bcab9c04a02cb5a590ed61949308300b2c63f341ee5ffa6f227b3cfec5d11af2628952f56cff2b4ca488f1bf2e4ad48e49a0cbf6374c42a5eaeb92b8e3db44c3c8dc58234e66b37172dfb287b3601140bb5ec5940daa31166421a05a49b1219b5197bda0f175662a45d3efef9da5b9e85b3445190aa5be3ef648fe14a05396166a6ae07c0d5daba6d6607e51ba52457c48eea27a87349edf2c294ec5c881f0f9acc1d5a69f43c21d6a15072547a9e00ddf524dbec306eb7a03689405aef2f89091a9d08e6d486cb00847da26852777120885becba08267e88c570f54770e226e66bf1f0c8c2154208be70832dfa015001a246df5be2ca225ca0d48ffac632fc7fbf1320233402aebd2d5d74a781027e33cbd4ffe0113d8efaa5b192d45dc3bae051195f3e0c5d7c8495276aa759cacbaef1495bdedd13c8a7b9de866cba30a838f762900b8bbca60c77f806bb8685d628fc10b1ab7910d518e8a83ad7a40c22b7fdd15528d43a3608435ee96ac0a06c33f513f1fb23490186a4af11113b7b28980435a1f74b29a61aaeecc58c4b4dcafebfab54a066a2341c1950a8b093ac1b82acf67d5e06022453f8de0369bf8501f41890c81178992800d36b7478318a090bb2e986f67dbe9bf19fb4d4e0caf6a9a8d4bc524ae2875d2e20476a146c9518b7ffe731230a7d3ab2b57bf83a58b17d054491e8080e7e8d903938107ee9dd04fd29bc9e983013faa54e38b8149ef7b63713d9e3a0d4ef465242da24fa6720d0d78fd9971902397a647ef576ec1e76001ba853ab26477e8801fb96394e8eaee9a5cdc719561e47ed74f7d97401e0fe695b8587270d88845587228120c961b905017df8eebd09583fa4e8ed004804da1d001bc16860e827f821cd806baa5ed030393713c733b40bcc9b01df9bcbc3d6cb2dd8d9549e448168fb8a7a954b844bd2f4ff023622e63cfb8bfc244e7857038141cbcebf03487f834494bb574ae04913db09bcf6d20a8a41627da854c2630cbf8de506d5b8e2cfb669d3e90353173926fa15aa075e51d9458169873c8b4db7dc952e296dc24d1920a60558efd864e42c8677f4170b3857698545a1bd42faabe65d943a3ef01a94b27ca9e6f8d74aae1ca55209f8061dbe6c0d48fc5d25797bb274e96fbd7ba33bba237da47617f8f5c81c1114aeda9ae311cafbc5c84d982af66b0240c8c7c63c4fb095d6ef153e620af7196e761860d5261ce52e3e2071d195cacfeebc4151932f6ccbd941dd4356cb02f5664f2c04267330bfc87bc5322a4ebbd87c7922316340812c2895c5096c41824582c5915aa43b265367a26fdc1f7e834e38fc9d85fed8a8443ee861ba190a1dbd9ba256605b6293d2f0cfd08db354b89112b66d764b0edbd2bb1b6477adbb84a43649816b4e481b260081567a6d34a0756f209b789178176471409842f5969989a36c7a7266ebbf12d506fc822551665f4d5d01f1473afb91bbfc1bfc6618c366cd3c5c87d985d57f03aeff4fc790dcb6b35ac7b7a186765a304548ac9e813f9adfca2482e2b282819cea128f1195cfbd3fb08b3314ec42193ce8fb65b47265be621793af11a44b6d40ba7c68bd658c6c62a9260399cd9d77a044916324446f98f54d13b149db29ffb6983f0645e5cf02896cd8947bb68d209591027a901bc8fb26f3e570258ee59094f35f65a000b26510f7abca60d473e3c89ecb89cf871fe0d61924b84c1b74636cada2a9fc6529c72be001b6c6d1adea39d949dc501abf2aecb3fc99122125547f18f3f2861ca685650123318b6a9c49decbf6b43073a4994b7e180e73c69b826e12ecc0b4e5daa260205406800cf34a6bb024587c3847de6d04cf30212be8d842442808841b9f59e1824251fdb24e6e90c608a1c29ff25b3090f1486e8ae556aa18d0c211047c88129d12993f1fc5ba93e898bd9fde05a67a6ddacc00ace910ead7b5eae2da16f179c6d786b7142ad1ba71bb6e18ae88137bac70e71c077aa29224ae05f87fc3a44b57ae8ad651a04de92f4ab25cae51b485521b52253e0df0d1a1899cf73b2d2acf4ac65311044c4cff4b775acab0f10a7a6a4fc78b2d24b572b950b56ce74bc1f53862c617fe8289fa32f904a265998e0e3c1b2c4cbd7dea447724cb3bd2c7b6d0971b00c16df612e8e04601341e4c41db86ad5a2a21bad29dbe9861a201d720c0da8b6534c3f3e26a498d2e75a4c987f2801fc50c2ffb77c1b60c10fc260e0b28b54545e70aaa4b4a155b7795f2f602213b37a5110f52a4b8e408ec036b068bf424774957410cb0bf16ed06ca3954ba85aae1093b05406857a6e293c0c5cf80f6161c410648aefa2b012ba434bb0f638ec0fa08a5a1ff7149b701d3777880e7df00aa88ae243c3bf1fdb20aeb51a01d631ecf39502cd00950d832379d3387a83a11534f6de3226ab47d5c54544a13a31e920a1357a96daa68208aeeaf025728f883c2697b1797a788283d0de6909edf316d3ab47717d34204ea100a26c626e0650d9f4366b04ced56f9f01f7cddbcd8486068718060ed743c574811c0733de8649c452106f837b60eb98f668458ad580a884ffd033f5146c01f5055094101932b33aa08349e50470b7adb5c68a921578152495aac7fd021d4e474f6b26f8740e0672a1ce603224c135dc71f66a78a9247b878f578800a82a8b40104afd9dbd0ead905ccc60816fc79bd344f6102b331869d42f4a47618f90ccf1e43c295e1824d75f5ff99f31ef4e3cfb383cfa027c8ab933a0e0ad8731d0417c21eddce3a5efa3717285c24b04022bf7546da6a31566d3b0e971f52ed14661c6c8ef87e8e7c23b4505e4fb519e3a07ff373e0a19d2a77800553208b8f9dda7b390f9cd985b875f820a7f48891d8b440b0eacde9069446f98b08f6da124bb3d1fc38134fb0df32b6502fe8bd2f529969d74511b238199bb7ddb03f68e3a35675c691659d354281627d16289f2e7c8ba7eb3f6ffbfddcc8beff67e18f5c46349e8385ec58397498934d26d56ec858cba67626eec08281e55e6e6eb960594133812806dab748d652a9cbc2e8cdfe73a1036c4be6c0b86b32dcad360a983c83daf63c5cfb18b8fe5563f438bda50bc785ac81ba3d8000d10259abacc2fb7e8e68c0d20b6d03892e2bb6f2880445ef80598e47b94dc7103a9a62490d396252100a768700f33ae718144cb1dd30405ec721868253b43b0094d770c4a6608aed8400e435ceb1149c825d0180bc9e3336055164470038af738ea16014d8697deea0e1430c0255604e18305c2775a423188862cb9d6bff3445eb476ecb5c9dc060e6eed573fd7cc337136d8e8a87763989d84679bc26a861d0c386fd4059859d24eb6dc564439001eaef1129e43519de3109ca2913c5a05641bfa8428f302617c002e14285d78e047263ac87428050bf1d9cd13e2c86d3d1222fe016fe21d9fc8acc906b2626c29306ea5eed2cd322f58c6a69853361b77d4f1c0fdb941e1fb77555ac4e53dddd14de064b789d7416e7adfbc65aa68990ad02a2d988ceb82efadeacd61563ec6bb1e95600f3d7a3e6f477a253eafaf3c0a9a1a8cd5ba7700c39cf52f194bc4c23dbeb2c26adddb14ed223ebf63abb0283cc83ed9e2bdec1bb18960d5df583f4985aed7516b7ec76f207bf1f405fee916bb18f3ed5bd2444b9b28d999c217e5af7361f5935bbaca0022946536577a39ec30ad946a0112ceb3a720e741254e62d808fa630688180ac5b049e7b21b040109ce75b2840bd2c68084c97fb0380fc7c49417150d1f5bcdf7370ff31977046d355fcdb1b47a1b6aa2111d8393a16f12df76ccc7a2a7ee9f4f69434fc3dbbf55e8da627844c76fe4041f8095e758aca216cadf80fce404c8907a5c4ac9dc1a6f800448c611eaf95096e2de1b1f629585a626e6574793b84a6cc46c498c8fb65ec5396b38197be8353f129abd4b329c584b13c7dfb2bb5bd011a9530b4d884ad72a50acc7d32bfa607ddcd60793d26f5748434d19e34dfb8cf1a62e9793b26e636fc2a69d3f9b7b45aaa8f4f5867c05d6466085197b668440be55b70574c3db30f973d60e36712a2ce64737ff837ca84b4863a22166960f6df532b3accd1c8c1ba643483888287da0771efb0f0e6fccbc0e3fb3c9049d1d89e75dac3575559ff626fafccdf58a3da54fc78d005ede7334c8c597e75e386d6bdab1bf9fe30792b7535f5d75f2d6d4f7cb47fc75f0eccbf608ede7214be6bc492b6d794ae6f07b6fce6a53254a0f4fc14e24bf8ecad93769645443f3f9b5613874b0c585b5358dabe771e4cb779aa5db1b05c85c838ddc10997e9e66966679eba2e016a24b1cc0c029a039f82425c8271aefbe7cd2928a6e5d9e584e6bdb8de546066516a61610bcf4785799f3a540e54a70dd1f7cb7882f89405d31a57e54cfc0f1fafcb7a8c3e2cfd7371b5f5323365e40d6a4b33920f79c92b299aaf0a91f36f462aefcd320c789e00b5f40059a425a04d1aab985c0b1c4fa9ee97ae05e8a34c25dde2430363d09d010053e58044e9f9bbd0cdc1016d4b243ac25f78053c76af949f03b1300f17d3457926595bb25001352bbcfbc7935f346c6281183b493ce991187460e9faea34ef3690226411b1b4a13fc3e0cf6dce42652a9120ca81558cdc2cd0d35b42837cc8be008cfbb93d550a0163207e69f6b8b1070b7eeb371583910db66fa0242d6a32e64e8a5e2e2a49d680a8e92d56ac71f3677e796d84a6d9586e235c4cf4fda0a395fb1a443b00fecbcc3ca281c59e6a3a5c23d38b4fdb35995219e8ffb1790676a458bba632eb0ad16b3466b9e4e5606ee23487772a5f46add7deb46de4340f260b3640a878959c90de132031f2982f535da7bb8167d107a59160fa0c08612ec5811e0a17205fd0e5f136a37cd046c49074b53b8dd105e7f56deb050d61c080a7c40fa6f7c616ec384cdc8f147a3df7cb861d31fb0817d0c9e8d5b8ca111be4898bb1180cb0603278eb700babb6db2e681ed9948c683c5298d8560f96223400749d8f4d81bf6d5de3d5357283b4c7baa103b82682d3e00bc60ac98a09a02f1cc8b15cec0e22db86d0812e2ec6a1f8560063508b1627053e1dedb91c61454b2830951168dc0b78480c7c55bd8f29de5b433abd6044c4901c088f7495b0998c5d92266f9d22343095ef0c31e809e18d12355635fd244b844827fe13d359b38fafeb0093af75df2c2ba123c89fb92bff1b9e8c30ebaf3a606a0204cd9ff0fb4ae49a0601f38f07633e96e93626af8c56a3671bf39e406b047a8f78a5a0076330ee0f543d7992564b647025cc79e9e96421d8efc14e106adf0a51e11bac24280e9a3760b47bfd2e2490b7c8106ea3c509e7d6e0a03daa6f1dcaa4c3a6b1dd9a5ee749d35b5051da1a7c705c8127d08a311ffd706fce3edc1773132848af9e8a1f86fdee51daaeaac14622cefb0403903fc95fe592b17fe218569a2d23058b196984047d3bf966f80d9becf5abca98436470fe3369cce2b65d4f19ede01920d2c5ab0fd3cd0cf794a446e3fdfcb515e4de8e2583252d6cb6f3a58bf42b9d658ec70a20d70eedbc35229688741d4917374ed07546e51611d91e15b5965e70848b59d4a4be5f5842647d8fec1d30f3273aaa6dfe11e89cd8a4dca70c977f8cccdcbaf4ed032add9810e4bd1623f079c73e431630dc54fff3d0ad5affafda61b14d7ed8aee36bb661b3d1c2babdea4376cc60c8e720cab25ba41881a9f177137ab9c4f6049a6727f8c3d534f057586cafd0918c99cf8fcfbc35cb0e53116d58f3af5e961dc019fd4b9713a117b403ad3c375039a8cd40e25edfd2e3fc85c10bd008c0a5241572d2e1a0df4a1440ea343ced68c298d16d9ace8a8707082f759e816c4829bb5a5731d6f5751e224c0d72ebeef70ebc1a885f542aa9276befb5133b38d1b29b8b52820922cb322b806cf5fdc7897b500a86c0312d891fc3598805477295cd834724cf109a8320e49a0ab144a4d56ff8da366ab0fe0f2462029a04ea695d500a4a930eeacb10acacdd93371c8edaeed6ddd9a5d8383ef3a012556ddc38408219863f2a647f6c6e5340110ca129790db11e46018c20280610a0184910c7f035932fcdd3f72729d7305e9e1442a51fe0a34df6de7b6f29a59432a51dcf077606f5054ce1122cd982d30b49c45a6b6dc8bc4793b23115bea62667b5c24d755a8f26d9a3dd095885c15203ca162437688f275bc09450e34b12148e62b4d395b24429a54edea3216112be7610b9812233c6c8132b204011634324350310593e2ce510a4a789504aa91244b247bb4210312502a48c0a276e90e3c60c4e6033a81c28289af2012f5c7c5cd12185831e8282460c46a48a8e21ce1049d1a2ced4f0523585288bd30d408010d23d54a09260aecca32f8c60600c4f1387fa456a3e29aa1293596bed192c14c6992ee1962e5b7224814a8288dee0e2802b329cb9820205c89118dd642728723f2f1ef026d08cfc82ccba26249bfaa2870aa9e141515195180fab580d61cefc30e1696986142ad02cd913598a2ad70c44eedd6d2a4ad5538eed22a3324587f8bcd83aa48bbad1b28cd0d18408a433401841a30310a248c901c89918a2d4e084882043a4aba92503e743cb0daf17fa30d574aa483b01f6e2610485c20a466a7082fa4e8022e48883590b465449740ab12e9b47f34456af2cd1bbb9785c2f9e1d8a5c3c44f505cd92dd8f999516ea778b044235a33da628a5b44e81168c5432b950870e3a184ac4ba8a484b612901368e20e2880d4494a29a5ea84d4eb0c814a112e248104845c460ba6244169825a5242b282728e12464468504232420cdc20d47cce0430b3faece24e029ea4756901c4888e91f1e0395758560ee0fb78a95ab9aa5884a2974939235a70f2d54b01d609b961d6653aeb294264aff43b25c9e4210a28822b0c0ecf01445630853932760a270f0b24488148dc28bd6af49ad4d334aa917f620eb412aec01aabeea18333d4ef40ff6e9933a6cb037525c95c5acb596893fa1744597a323a214baf80012637ce50f7ed47c8024042324221f4a90e5d812a397e49441dad8a4489e22acb596060799d402855b80a961cc8f1166b8b1c4c1911fb120c2d4e8814a845cdb870ad21572920613285916a32a3ace08513566892c5d98b63cd92186b5f6760f19c2f9437f4014e383a2d0feec40cb682839c2036090f041a6e40603dcc044020f39b07a7870c27d62a686993f95e15a45b222a91b319c56aa2fc4649582f936c56589cacc8bca657aa2e024220a949218254e20c304861f46c6508102d3240713e4132c895c6b729533c915a594524a7fb8f2ee0f59945e615991d2c11632583e6a60814709453c31e24c8f2031e8fca14cf3c02163bc6046877963adb54bb247bbae30c8084fd9be87c57a08b0577e48d513960e8809e20b94911907647a104287972a563ad0246b30e51a4ea177938260318d00e50403074749f7a8870498ec50e48a132e446a88d1b1094ca8527088ac189d612a410b8e22344ef0018b8da732b21172d8b004ab042f039952946a695ae205cd15162146055586e948aa082d4a04614487138c48012288a421fd34e36c8aaa192044408a5e2d5fd2048357a570e8ce10c9228650c16a071c3faa8a70dab294a4490e12aca2ae8828d124d45a6b0a59b247bbdbc388e5c88187185608c301079a80900444191da4a88c90056ca49b1c2d1821e3818f2248706c604a0e593c60c547114b76f44cf002123aaabcf4b0f4240350594c4b3ed0a40a142a37e8c86076c48e29400c3902450383830d244218a922f20196a4110230c48d0d3446a8b163c9ca179992202860c6c715161193111267ec8e1a5bc3ae2683cda9dd6d4af640c17b318b85d65a7bb75c9192555494beae928b184240b8f164e4020d5ebc1742308383931d488ca6a0721d93c35558eae181765144d6de2a3450c9a9a51ed245cdae8baa39220a8c23b0701830fa82c2d50747b282a8b18452d48dd8121168c862440a1cd65a6b3940f21e4d89660e985eae30cae64692efed1006c8b54598212088963475c563891d61b0c4c070e4c3508c070ea32376e05842c5a245b0d65a28d9a3592b937436b7296b6dcd149a91669a1264986069a172a203564208258890eb104ea0c620493963d4473e50280358591a824814207810d1e1089ad98d5c551ce1d14313262ba724316eb8b2b5d6da109a680ee129b436a42c504ae90fef36a94029a594d2bb85a35284084aae5ac8d181141d47ac50158492a72b48aed84861d4f2a0a8560e8c0bd991244610f9c043941d2270c40e31544bbec480946584131d4225a20906114f168aec6e778b0d57b5d65a731058ad55c42c5f6e983204c9142723ec00018b283605852b5bc0ace8222136324aa9950127acd65a2bbb923d9a8c2a26164eb95e63c6d4f0ca144d568805aa0b51f572e4e908982f0693261e1d5899c06504303c10b1a3ca8c62d2e486460a5a64286324364be2c88694592b1b4a3b1a26d5900f2a38b1c103ac0f806021d28153160e2f2d340828504a2985c56e08654605205db0c674f042d48e292741108141c90c49b094f40794d22b25848e9127a5942681cde9dd4c9aaa8a4a2d0250e2ba7451e1105688708b92ea3b9b88b84dcbf4e6f5acc85688f104ca900bc508cd0f947655a6a53025c55968e224ecce2817aa6afd81524ae9ccbbb3d65a6b989f396c12302d8827ae89524e8653b6eb961eb5d65a2b0a4437b2502ab7d6cd87851d1860ee5d159098db14d9dca6a8a6b4a4222e4906d64ca194524ae913d4b6518f4b612209211f28c2a4c4820ea62270545de1c2820c4308d1690f688c403242c908a6a91d39b9a42c51e8a552596badb55166648f769bac6c535cb2b444a9778b962c4a29a59452ea5d4aa4db81d25007025c2903a845954ae846dc45f33d711dae97a51db4542f3e6aa626a5b5a10e5ad9f15871b7ac96ed90cdc95f7c32aa7f32a64fddbf11414e6cbd4bf4cf40fa6856019afddedea5ad698312e2ec6b9eb5f53ffbdff6f479dedb87e1db90fe915fc7fe1b0f8f9eb9bfc1a3e7dadddfd7ef9658e87bee65c54e4303db3737541eef7b6d69f14823db9ef9be77dd9ef278adc442d5e57fed0fb90552b4e2b839e5fb9ae68301ad87fe03349f0ca403340a6ede621bff6bed8f333c5b41ebeb8f9789c87b6803388b5bfc5ee21deffdafcb2a1e2be8af5fbf3f0bfaebdfa5ada97feadb9a65bd3f6e4ed9bf25b6be20db7af0b3c06be4134efdfb7e73ec7fd2df93344899b5f5ef53e3d1bbfa39f771b07ceabaadcb47b3276ab9e65aebcffc89b57a2dbc0d6916f76178b318b7a65cdf7e41ade7fe8afd30e48d08be4bfcbe256ad9461c3dfa3562eb2ef98a0ca48fcb2406341f7711d17c7f41ee32e92365d269d07c240d5a0f7d1ad2885cffa8cd72e3b0bb87d85996f7fbeef7f7fb9ae6bb78bc4cdb5bbc0d192fd3f7b58b94499f269234683e6d43eba163cf3c9f923e19c81efa3e95edd7ff4415318433b459ee6f7dfdaef5de8f9729770f8a0ed00af0fd6d61f06ddbd07c34ad67d4a8f2f7f49b48fa3c40ebf99e6a53f9c3e30c6dd6b34c3f83efe96f43ec83781bf261a67c97b621ddd74f46b739719ba8656bb76db3e2b6d96d889cca9a0a1a36ad03420e58f9b316c545d6c529b7c85a540e48642e4f7900598bc2a1062c7b99e737ac3caba8f201b21655b594a3bc64e515598bf22295a3aa887297db99e499b5ac4cc3d7aef6b7f3bcd19930c07be86fb8bd9964c7123ff82d80cbdbf705e16c52dbbadf99f80bf9fdde27a35dec3e1912c390373f5e9ba7f9f1d55911e7ffe66d9ee6bb4e8a639865bbecf7ed5d0eb915855abf4196d9c477dd937cdb6ec2f6c66381c5a3e7d6f33893dcdb86ddbb37c742e0b7de691e1b8779ab3434d820f74bbcc99103791bebbb2884f31be416e75781cc76abefdbbbcbf2587105641e1b86cfcf82d133ce6faff90b13ee42ffb867fba3c4a333c9f5873cf753297f9ce175edbd5e2fa9c4799cdfeccd6fdf5f035afd126fa237de69bdc3903df477fe73d554b07d5bb07d43f54fbfb40f435acf3dfee84cb23f8ed8fa8278beb1503fce833cfe30a40f06fee3cd39af798eceefe0f17e7ddfe4267fe7fb82b61792fd3cef6dff2eb77e9efa9fecdfd1c11d164873abe696a865b07b6650ecdc3850797bfb05e1bcfd2af69b10bddfc4da3d8ea8e5578e387a388dfbc957d4f7f90fdfe0f1bab00d1ec35c83c7170d0ebbb1e53e0cbb2d16c2d685fe99db6bafb9773a2a9c844333b71efcaaa3c24938c3cb2d2cb48d33bcaebd06d8073db1e5dfdfdf02b8dfc26f917b1be29dfbd20703d943fff51a43fbe3cd0ca0db57d9336a5832b558450ce1d0ccfde3d0f466ccf0b2fdefc74692bdaf5f50bf90c5ada79f05e0d3c7c03eeded5bef81781c9a797b6af138c3cbdb7f786c24da4ccbdebb0bfda365efe727c363923b7172b4e63b6a20730e8e1bc8dc73af7d15d0cce55b41972d76179a45c5af36b1035f4089cad3d58e259a98e025f428e3c3c7123ab03c41e118d59051b94108a5301630812025440d284e5450f4925a6bad4348c91eedd66ac1d4a06086ab3f62b56a018340d9c040555f4821a2d75a1b046c811a23eb8e64c9a1481120c014c1c2820d49367a5825f1b8d21213809217c86411720584294900c29c7003075398964899c1462771725dc97ea294d213c68027182142adb5825181524a65de75a10aa55428b53f504a294cf66857de78a1a40a29072f3d6445c580d9a831039923a526537440a2c1b25ca194a6bb29d55a2b53d19db56ea9b5d62bd9a3dd3b8d92e46ac4e40321b5d6dac191f768484a4c4e4872074c3428d9a35d9867577049b231f5a506335958b852c40d386e08d254821258b0a03822a49200452593cad4a433f2ca8380d578750cb079ab84a1924fe12b74bdc22885610a83545f21e581684ac998a4a829e302cc09122638ed3013210a0e48217c3045445663c45a6bc9506aadb5d6ce32665c567aa9899524513310488831be6a909262a4044bc7992c36b020442241e930699826a59b169c6d470a1a45ae1869da224545891396b6f4804415148c5445d991e47ad2716126bb902065958493952c2560588218e198f56043ca0c52dc0c565464eb011a96709882dd4c4d5dcdf015ba5e445a951ba8abbbe586a67e020585e886841b143d68f08186282eb10e440da04a15a3205bac24f1b485a888e431a12aeacc0a644aa082c0142c3123a278f8414915aca95ea1a905a5182d0448d97e8e6f2fb190fc9ccdf27c8dd461b713d85a6f7b1ebc8375b0e7c927b4e6504a29a50ca584e5962f11fc166bf7393912fb0ca7fb564beab8577efd641886fd0d798847b7923a9a456bc85b305ab43a9faff8ec07c33eb5b64a1d52870e241d9a897e2921d33dc980f349cb72bdf19ae6e79c13dbfb17dbc6f3fb8272fefef417366a3ef13e19f68af393d158ebfc79449a1773eef3e8ec9cb08fe2fd530088c7b0f1f89a78fc6cf11841ae134b1a1a80e04fd97c508a42dd6f90a7aba77df9536641d75b13734e5727c7f9245b14f8041e3f891d3cbecae8e031cc638bc983e79cd83e288e2d7ffe38ed9cf2bb9f3f5a8bc33c9d860676ba9de39ccaa3ff1585727e836ce74fd0ffcebf580a7407e77c92fd578826685ae4feee9fdf3b3afdda96f6602a09a9323fe49e20d71f35302d644ffd51f3c2f3a3d6a573dee6e8ec34a06b3ccab778c7f5f7fd1b76e2b730c57f2df15f15ccb76fff8229eb1f0b82f3c9389fe4fb395dbfc542f673e6bcaf17f11841eb1f8f9f028faff051e013d8be8f276effe83578f4f91f88ef7b1d75bdd7fcd6fc6f7e38ed9ff0df3c6f92912e97a86569adfd99a568ad2bc7df4e59b66fbfa09cefdec5f930248f48f33ba2cdeb88356f42747d4eab61a19833f105bea245fdf9e31d5ff2fdc708f2d8a22d8e156fbebf20497313c14b3cfe6b7c551c51eb5274d30d7654ffd82ed72ac47a91c9fd79fea821917b6c0f7263eb710f7e3f28ef37085e1c501c87e48ddcd9def5f63190358208fe5faff0be2540067f9c41801c7eeb47ad4b76c90cfe3824736823d688f3be7d1a51d30ad0e00b8517fc86499fd67afaeb6bda0b4d5b31ae58223e9968a2c9fd632fe5ba1120df6fe151eb92ed8f1dcbf56754ff80f7bff7fe821955e7139a3751ab79bb79bb7846350b5bb150cb783a85931c078a02a47433cbf43b9f01c5fa67fbee5baf97fbc5637fdf02b82fa8e6b79ffea236100f20257ff1c9a8dc27a3dd25b6ac58f336e2f6b5d298eca95f6dad3455a4318b63de759eb540310bd4e489429f6663adf74031afdab8807878fffd98e3c239404ab9befb67bdeaee2ebddb6f033509d5fc06b9fabbfbfded8162404b0dc3bde66d7e0452baf70229e5fbb3df7ebf7f77dbfef17ed71e85bacadddd355ff3f786e1f8e2a6648f6c406317f1cef7dbdfb00934a1f338eedbf703c5dada71c84720a5bc7d8df805e5d47ccb31108f66b149295feae0f16542ca07e291b3f9fdeec12fc85fa8f1f89fb7ecfa39f7c3a67ef9403cfac771f0f8bac16348413c5ee90145f13ecff3be1a0cc403281654f3f6afe80f43bac4d6db88dfd7d4c81f295496cf4d499fb048f6c81fef186edc5496dccdad9722971516499fb049f6d07fbdc2d07191bd44d93d201e4a597adcfb8f1e16e2ba391a0e88c7c86565fa6193f4a150b287feeb15363565fa403ca852eef71efcfe7af87b4b63799c52997e4bd47c8587c719548942794f1f88c7878178bc5e1e85f2460a05c40348c9721b108f9b194ea32892a2a792c4f25e5e492cc7923d14bb53ffbe209d973ffd856b79d3b263f98b4d86cb0d0389f3893affc2013f6f693cfedfcbabcf250f16b29f14e33c9bfdd696e2c329f0e8edfd8dfd65e39658c87eebddb1acfe5bdda419219ddf20fbb76d3e935732cb379d6f9e1f259698fe91433e6e789458d9b7dff038c31b6748ba49ba49bab5cd8f8e65b3d57b35cd67d379d7f15e3f5e1cd9f2b7f61defb45e6280e24dfce7880fba56e45173b760e62faefac77ffb5cd4f228b1e4eb7c29deb190bf4e0a29a659c8c740fab438f1e3cd3bafb9cfce8bf81f8fe1a3c0e37514d2db708af75a6cfe29b03f8a1318ccd9fceb9b984a56d4b2bb75ff99ade8ded2717995fbed17a4e3cf237adf9fd8bd8ea8e51d71fc3c6a331dc73e6b217be89bc02f3cde1c3c8638780c3388c7571e5bbc3e8f2d7e451e5bacf846cfca8ec3eeeeefa518c7ca8e6bcbdf7ba7b98585beb7bf79bd6ddbe67ddb776f18be341f4d93628668e63ec43bb785f4c1186b00f8f1f18f2f153f86103c0070c3640ffdf121b8574518be3431e08f6e952da6f46b981423c558e5fab0d76b0cb59e21ea3329466249317dc24f18b1bd75d99ce454d781ed5de414dbbdf7ba90d77bb76ddeeae190bbe89fede7fdeec7ee3d0f8f12e4f95cc5439e857aecf090e7e65af4cffd6d8e437e29c52ebafb2d2e752181bfb89f8c4bbbefba908606f4b5164dc7afa21047698bfea15f27c863cd5faf2d1794a64583479720df1a3ce4b972cd6117cde23ebd7887d2fb2dfaa7ff764b90479a9f3f4a90b7ef44ef0beabeb1d0f62e461adc715f107d211a3cf6f659e065fa2efa87e2d102f793b1695695e3442dd75a39ef1641dddf9f627fe83d7d17fdf3d99dfa5abef439fc7d78a7fef6d345ffd09fafc371de61eedb057deeb7d75cf48f04998ab4666b73a5d845b398345addb22273b90c36f579b5524f1402bb5ae97b5e07c23a87ddae8617fcf6c0223f6af797e300dffbe92fba7a91e3709807fe75d1d0a0df2bf223cf95c2967b911f21e5b17a341f768ea37fbcaf2da7719c0ec761e1f5683ea4f9fa63ad78277cef29cdc3f0096a6cbcc85f785fdf8fbace619dc3b2f7a00b74798f73e3ddd3ef5ccfb9cb06d7e0f1ba8c26a4a1a10941ec38fc086c818ea3f33cfcf23ad08b7c05fdeb7d77c357dfff3ebc63bf5ffa4c30410e3dfce2eb95bda7ef4de0fd749ff1caf527c8f31d87e3f022c7e130c7a1b95eb0f166d24713b4af71b556fa9c28d4fa6aa5cf71b4b653ee691f653aabd7b5576b733ec31b27f75dd43ff5b971c88fc6e9e1d6b7002e7b3ff805b5fefbe92fbcab76f47d32faab62eb359befbaeefbb42eeaff629d28e4fd06b93dfcb07b1775b05cbf100b75efd518f5cff7357477d5e0a389bfeebd7eaf7ef7d227acdf45f5fb8e8e32f72df1fb826c5aefd97035ed35dd17e436cfb9db7c35789cdb27836b269dd77562b71765fffb05b5be3e15fb43917b17e9d64d8f528de3707f7dae5b40fdee6bf5f7e30585eee8e5ee7e830fc341bc639f7bfae1fcf9d5a7dcdf9fb58dbae7b86fa3fed132f7f593c1f5d176ad15b57cefbd77665b711b350b3a69ba4607291dc9b6558b1caefb9ed9b8cb6fbcf52e251e1be7475943f3e13b9652c7a596e505fda5ad5cfaebbb0ea4a14183a2d0f71b6487c05af77e503ed8570d01f85f5a8594108010b455ee97e37edbeeeeee2edb6b67595148c741f70b7ad63fde57b2a78e8a1fdb0af4b6ca524a97524a2aa594d629d8f9d166aff9c44f8d0214295ea3e0dfbf350a44dc5f1aef40f00f0077ffee9904c1b602db2adbd7f9a4ce03e04db40c8095eeeeeeeeb8ca1702adfc97fda50b0000bace913c082010b5ec791e04fda567d95588dd63b1e64591e65f0c3f8588f328c4d69f106f9e4774fd8e68f3cebd8ece05a1093cde9cd6ae72e7b4c542dbfb777f7110ec1e043b2ce46f1ff41af48ec0717f691639dfdbe784a1dbaf2d882373ff0dcdfdae9afb6d6ceeafa1115b2b407f28b6c4a915a0fd2dedaff697feb11eee2f17f497d956eedbe6fd2502b145d3c0fecae166f360dd4c3e859a371d6796de5ebbd65575337fe1d2a5e87df749d97501b596949fb76ebaaa16687363d3cdb283dfe9a87022bb08949686065ebdc312f2a4bbd675b19d957bbffdd8cdbaaadbcd727dffdefc6bdc7f9be15d0771578577c0ae8b4bd9cdbc9b65fb9ebcf16ec4b1b3a5914bb287be4d6ffe388e6fdedb70e492dfb86c96acf5601f088a5afebe0ff47057d575f15eb66af79e9769629ed630eec3b18b3cc88db9aeaae58f5de481ef9e07765735cedc450df33c267deef558a6df557ddfb21ed590c8e30c0d89bc15e5fab7abeaaaac87c7d95559dbcdba2af954d3391e4849cdd8844da4ab5918be5ef7ce249fb0993463523e99266c224da5993463d9af86e638ab9369c226d2549ab1993463b3abd9d09c49133691666cc666920fcd99d44c9a481366b92fec6e4ac399e4574373680ecdd94c9a493d9b49336968ce266c26cdd84c62b2b57c2291662dcff32412957293d64a980e89f4de2743daae7331e7b51b1bb756da25a1f0b356c2fcc44bebd2c4166e2f0fff2591b27c4da3c1a194fd58e8fbf0fb25b630099330b964a5f75b19db24920d7ffc90727d6ff30ef19067c74272fcf090e7d6b44d7e7bf815cadfbeb7782784217d1ec755f33c128f52dadf5ec2fa47fe865d8a5a76399e0897f3a8644c783639c99082040000400053170000300c080744a389a0474af70114800d63a2545c4e308e062391388a62188801180441100000008061188661404a92055b9a951d830fa3a83458c71bd5c0c43f435f519d108bc46d9abd239fe526cfd40ce0296c0856ae65d6010c251deb5ae1c949e65934ae09994c885e6fe000be4b21234fa64c185b4858b3f9d19041316aabe9cf1966d59035c63a3261e5b436262f014bd187d40ecf432910000f0fa635f86b10d71688b47f3f839388d20816f51c750a8f34b35f110109339699b585b8561358a2134323cf9d0ca2ddec8d0b3b68901850ff1e9d3ff052fb2c7716dd5bd53e53999eb053b3e7e77a1dd11de7edd9709015755b050a157b04139bf4799dcc5b0ef5ff113cd56287b543742a8ffa7440700eca93e43f881a9f2ed84a6023cb8d8f3f98a3958da91ea902ee59123b96863a0a07180f511f0c6dc90756ca8d0ab4d43a59b399075d55f4e95a4747f392b84fd8faa30533dbb7526f067029686c856f6b68bb27c67c95e4ef507ab7aab8dc6e4449772db624ddb315582349fa8c658bf72427de864e34bd89d6054a9e4f4a4cd5994561c778444786148607886a443fdf390deec2675eba2ce034629da0bf3971641447c226c781b210c0a75c19a738169add0e70f9456382f203c5320141228e48fc5052086f4e10df40ca87ee62bdc8fb42e013ca0c1a7523846ac426a4207f3cec26086962925675cd4909d420fe4702f8be502d7e4d2f08928784c1d388000af7bbb3a362cad5d9c5288277cf1e858e40a942d0b64a04f7b62c14b7511df9f31237d4cc535c118c9f3429ded0fe18c7445b8e293d250e52f009f97b218343d612bbee55855142db44b64a95285635aadf9490426a9c75a0e2f97f383f359665249b60ff00f1646d16764c11fd79088406fc316ad0092b71cb939592cff647742fdbbf213bd2f97b848e9156e4571e4be41e9d31beec0115222121da03633e0bd4e01b34653315c720747bca355ad4445e22b6a12728ad4ba510bc6c9dc04cea397f9ca0ed7475d8fe828618c9243a6e9b3f7ccd09cd3cc797f13927479b727d32bbbc26908a960e2bbc6d7cedcb70884ebf23306df485256e68e1224e128db4b6ac95a966388b3a8e119c166ea2742804c700da89ca88332aca231b3cf233b927500ba46ea4c6e62551f86ad69eef0632c8186c122c82ac7f4c8307b145081af96eb710dff0df45a021bd644f633922ab7264570b22c9fc24979e954f3878958a0cde62ceaa28243c7ce0fe60bccaed6d6b5d8351a667e20eafbcab56b7cc00f79fb5cce57af57282156e6852b557d6bbad9d1c6694faa48b72751d3a44db8c87f355b4016e00cd82ebf4ee1832d8ccf5290bb1ec76d622bdad3656ba97220192e500d76e53ff85919f0e99ad250e4905d2704bb3f92b4d780e406a6014cc29088d87484408d12872bf53933f710f9fab58d3b9a9e2e204aa51aae68f6eef2ba38a18d65816b6ee24c319c947f8fd30b55cd2ff983819c1d9877860e7c9c918c9b72d89d206bbf82d19abf729dc5d1841710e05b0538180155a415743671006055e330450ebbfab81b8884603b730b40f4dc51744651c8054401afb0f244f89274e8fed38a1b3822a13c0f64a56d091144b53ac86e36f16e386f1fb461f9a8fc79874da8ddb6eac0a24dd0b54b293dc02600821c0a79508448e412284143c9f3ecc09dd162e0f60a45a99c9956e39e64e14014421c1f57f713fc0ad0a545ece43615325d64ae77024ff40d4e0a2a7d26d5c3465661f798eea55d7f35c34b0b5bb819b40b19b7ef64327df5002c34dda17e604618ea74eee309060151cbcac602c0d965e60f6a4cbea1593d42d519d9b9cdf9b55da3c6e159b933a371726b69955d9eb6a86d014d44076289ebe9217e91de2ab4ba45fc57c46de206dfbb80a92dbaf51a97ae71ba52e1aa45502b2d239fe9256f2e9df84d94374fc3f2d90beefaa183e69f3779c97855f55d85a87d1248e03186c015588c615a5201449a3c1d2f16507925db8e418c1a4238378cde5732894e9c188e5e17a4d153221f18393345fb10f18b7ae80a96ac11cb6f0fb7fedd5b6e95c6efc00d37f29482be6dae947e5b0afbac844909da935a12d4a55aa8bc3fce390e3fcec8aad74fb4397659aa867d57ba36aac2c5d655727358420941a79ffc05914bada80e0da82b16cd8f0532b303e6ffbeae1dc15d910009890be94d5f634caa6a6d39647b25680a6f66d0d8df1dc17bc21b4ee45306bef8769439e6817d17874d97545ea1008567770a865dc96528a8bae2174542a35c7d77070b745092f610c3664e4a132b3aba6785b7cb3687cbee8c7c95f6143c4daec954b9ed60547f91e366830ebcc6266e3440b3beac8ddddfa47e7314ba8de1e20e50c4d0563d3b8076a03189c89d88163de386296e0549b1d2829b70d82de97283c771bb6308553ec5a656c27fdb19bb43a707ec9f1646f4fad00fc36f06c5afc3607e321dbd82a92cb914db88dfcaf1f2fd0f5af73aa18582698508c3264485faf0260d321dfc7a385727c1d4666f0a1949cd54f23191a5867b7ddde89fad812e748a5237db5af739923ad124be84c41dc56e73ed3f7907aa145e6ed026578be43e80dd00e5a08bd019e035bff2ff931192285a270ea69f6cc8b18522dcca0b3730b072a1800608690bdd273a7ad592c15599b8ea2bd6e789c0dd7b29c5dc524d57ece21e572b5f86b283ec14250b933ba55554752c3457ae92cd20de1ab966b9179660929e6ac5a6e9480ed4ab7666d9394f895336923294f8a861af7cd5da63910800f57af40e5c71e12e012edecea662b53304458abde6f30f5120076a4dd5692a4242262c3a912cab6ab57759c48732f50b11e214cff83f2aab8acda1e2995db1373d46d2a0429c8f565b3c76ed351700f82e8fed141d9bac10819b0252c1a08c4a0ba33d4a2b4f6947df8cd9bb9ea0a17bf4fb3d7679fe06aee2036249eb4d6da24dd2b8378ccb0b0c313d2697b22cb09b8410d858c344f6236171171e42e576fbc138e2970ff24a91c323d9970ccdb739f77c7458e8ded3511dc16c8433e2514852549e86dc558b173010a22aedb91faed1d3f0adb0b8e1ad2e6c0336231c74dffc8cb901732b717f56e7d378bd4944bdc4db863c874cba4d905601a921d4ef8b11a7896dc818c4239219a0308ae1c1320d91512a457d7c5ffca75d0b993c6ebf652a2ef94c8501732a586f1d5c577fe4c86506c0ef65fc6494d1bafa744e5cc4901e1d830a60fc3f984987e172ba1098572cdfa10df5c657502f5b8aa240688293e0ab1a0bdb214b9eec5f58fbe90d21df7afaa6a890c3768a69cce400e2c9f34104c65ff3524190343c5d6747f36a7281c9131b45b8aefd1bc774d517a65201c7495aa5e2059385016a38dc1cc380a5a643bafde53001fcd5a430c00609d8cd41ee4a0887278e1538b6a5cdb29dfc186e184669885622b35ab38065d89301a128a5c5094dc4bd751ad38d6692ab56a5483edd454a9b899b984eef2af350cf478dcf00d6abe952c8f218e1d1a09e01afe9cb35160d5f3d90f10c1826a5f2ab6b18b0da36f68aeb0ad876cd85f84b10e058dfc6cd8b2bbfe337495c3c5015c6b43a42a27da75fa5b5fc46210845257899f17d136b40f71090ef6ed8c5d40a1b6428d04a9d8125295482657126aa4312afb4ffcbd18d3bf82eafb32b3325066ebd143b10a5dfbbbd2ac24ace376f08e61cd8d8d0f66e7f43b078a95a66d456490e2892c5aa7d72ae41663c86e1a10b92790f2aec269836b908611a12859565c3622fa1fe4a5b3ae9bb33e12a614b416a29c8721146956fd428750ee87d18b6d0eb98b50703c402218f34384088756f8ea6288ef8820a97badf2608c16ee635dcc75beb60004da022df51525230abfb3e78824b1c36c0b66cf52ecf24f00699185e7084b6a021fde299c614cb3fcca6152009e28be5bd2421ba315cbf14a29db55bd812108c2ce9acd16095e601ae3b06759e689dd386885b061cf3a398f6955f1725e80619288b38cc6c1ec527186be118addde241b2565411e70e1f848073581d97332a2d18d71d4c85fb8b0fc55271d5f097e12311df5ba4e3e0864f456c63ee6bc31c5199784ca619b4c02e1b683854265147aecdd8c1c17da1f625141c95ef6dc081b03e87a16605a94a44e20138fdb36d436aad9c3a4243d6e0588e7ea3b18ccb189c1c20952caa4243f56969af0038b537299812b33e2f9276d1f6c6a314cf62ed8cc3588e77c7fb2e13513372ea028758dba01960df3ebea95f3c25f34137a171a29084810e72605a6d0c1088ff51187e049a6a1156ae6f5b3d10a96122fc73569aa68b4a204cea51b70ceeb1f576d16e03e6fa0f89bd78c160942d409243fc8a163879a49841460df02d2b9400a7450c1f3200af33906c507dc480405dd06d1baea94bb48feb1f63db485a6b417bc2b4d6ea06eeeb43283b6598ffeb056a8846ed75b91f3f8a6d0d07d7c49e09223ec834c3b663e81d58bc21be984dc19aa9d9b555c60a22f03c99f7d69887a660e69af6a55e361487b89ef569fa352050ddd78ea7d89a3ef03db907c8590ae04a9ca9cb0c29cb96f9369afec1df08538f26029764f2d66e6cfd9569a7b7ebe82856c96b281dad9166d488c4d0997973ade77bacf4fc0d6dd5b0310b499942e029c974e3823442fcd942bf0ccd6544117b133a1ed4d5ec2377f69bd605ff55fdaf066b2e9590bf0a488bd51e815905813e8e4c4b47b79ec136edf6600376f2e3e84e7fc53b6c91e08873b1dc1289d8f29f0d39331394c4043e043b1c74a9680d69d64bfa2fe528a4c48be980d641c200c8bcba341376f2c28bc7f5a23c827a8d98a6ed4a06f48f14feb83be50d3a0e273b14cee35e82a715ce0ce0d1098ab5394bd1663b113e430a6b725df8523235b13a00ac3e331afff1a809e20e488db47c3a279169838c8c9bf7916e5a9c9ed37b8398bd8cc33db26de6ddec47b224b67701527b2620cc70dbf13f1848b200774da4a518d2a0234b29827e3b8e59c9399b30a91d8303a522cc7fbd389293bf138353dd0a8fc5b76737b079d68a4d92dd0257ca7227c589fcdd88f2174752c8387ed2da98b9b517ce48e508d961ded8c102391ab63c28a44925db77fae4e8a9067a657ec29505b8b605a3cf62e5d4ceebdd8d3cb25abebfa941eee04feb8fa3dcd87901c78e7d2d63ad2286d771af295dd3f11c548f153b713a8caeb1e541620b6d7c4de02d739e35f17b7296aa5a91c129692f9c7ac6575f74683683eaa2caa4518252734b10bf71290e69830a441ad42d8580bd39b3012086bce6039f351030c4cff75b5a4aa8dacbc578af5a2034775caf36d0e47d7147d1c6bb4f9c774dcd3728208f7f3795b0bac80f9ba0ddd51f49928fe792abc70d187933959a7b92860c711bf4a5c04d6acb733f73e0a582d49d91dd9e66c97be1700099c4b319e35d907f0f941236176b51a2b616ad7562e9612eb12074b1d012c0aebaf45ae315f851b4475b900c3ca1e48cff51d0dd425a16ff6d48c688f276e831da442d87083bea9479ef9c578a8dee5d1d4e2990828ff5d41f52f0677de8c05e349c31930a70a864dca3a5d3d6641c3c9f92c1530290426c532d516ae8132ba84ab2f0d70ac6bbac7062cdf46040766f7b44c5e79440ecfcdf8de69fe4c3bda5c50b10b11506d34b5d952573e53045509860d576c98726194520d35aeae24f50161c15137df78f585e50c327ae2a635ef915bddddeff081ddbeafe3bc9173247877a5b668a0945916d7cbc2030967aeddabefcc1cdbc3f2d8a83a3942568137c898c4a85c80de9423ce6094bf4a8a888cdc2b2698210f66240515c140945d61f512b8962169cb03edfdb02e68e88b2f13304d6e984187a85395974025884cd322389d74d0e4ad9362ebff1e8cf4739de9e40a8c50eb267c8f3a2108798da51994dc9ae901e0b633254a08194b5605d3b4906cd364a453a7b0a7505131e80c15045d749b5c684ccf193a075c714793f70435fb246df6e0c671409f30fbf8b85936b3610db36ce785de7db9c9525f6104efdbb3a0ad2fa874e18d5385236f12c4332e1a79d4decabb9d33aed812d4c746a368bd8d86332b86cf28329bcc11208f538b4352681901717db1a07ee6cf012bfac08dba7ab5dd68dc452fd9e65f2622e78333e1de4f6f38be9a07a0ef856801c2afa9e885b878d82b048d5e264752ae4a46b6b7fccef1bf26ef579dae15ca26842bde303ed32bc446b505cd2b0037e692b9abf72df86d447c1e724408a8aa9b538847b3b1ccb51a714d61f4ac056f4903cf18069d50bffc860f15d5116ba2dff5713a2c681177e46b33c89c40af346e4d79404d8921bb9ae46e7e87d9f7e9ba9b10ac98c7adea1cf17ae2d2adf46e5bc5b0cc3c7c275a9047a1cf8ba1bab6d5c65ae785a440ce5cb1f6771ed9920dbdd1af051bfeb53c71255b3082aa15bcc478d2f42f5ad66375b341548fc06ba1d171bb0c98dd05519f5ef5cfce838afab37497c87628d0f945ca91b87e10e4332cdd6c1a5fd92b51de0f7efd7e907b1478be801306308aeccc604a4155b664ab60183d2c31c0e2cf9eed4309dabb0ab287184332fb8f71577b1485cbd38eed237711c25da404384851570c07fcc037943b6181a503ff1c4ed3fb06ae017a9c6d5d42f5df0364f56d7fa19d782cccf5d981b594be73783de73ebf96e61eac7f7929b4550fc8cc8d6e1a246ec03895d522a55db84ee9c6aebd365fef7e2e35d456e899aff783faf5c455ea659d709a72c6ac5a4bdffe9066b0217d9a2bad3a7f415a4e926021ea2566ba86d89c5c8fcb41dcaa714680753b18a2c41893a36359df9a7dda218a7d04be48bc8f442202a268508d803494fb6fda9a38d746cda2b78de1455beb1efe410f5aad4c0a39aacc5654d3cc522954a98e45205f16e5c06785e1e841c98d87646b8d9826aac79da5a6f805c295b2d2eff7ffda93f343ac4f63f2549ed4b4fa5b65d3112312562c11287173d012270b7c48840a35f7cffcc76a1383710336d0b23df3f8ef7d9839d567fd719eb950b1e9e2f52b762019708dcd10d18515930c17a7f5083508651dd0d519925d868a4da440353ecbb970157c6c007309cc42b3481836e09d41ed406739737aab3e6aab29b42d9d4ad8ca0a094e75167b496756f3d2244c82428bb0d48b2668458799a3b2079132eba21631195c7892d521d4e5d80bcfb202c5ecd4d3eea3658556d734eb60084a4b2e442e8d1f55a5f0b44e7674455d66bbea0259b0bb4cf5a185f6c9c7990ff8564cdf96b162e5f33dd2d1a17c959547263e9b293cd97b0eb902a904a5790eb224568be5366bc5332fe9e5f46cdc0c1c761346fbd1dbb955558967134f4a00be14865d372bd1a13df591b3454a203c44b10eec649225ec91c7c417d6761b8a4367d48df0f5b5ecef25e0693178bc44cc5ea7e7fac80ae44b2e89858218473065fcc9c8bdad3056d62e4f49bd8966959a4ed7aafdaebe1cdf785a8322b7053de309c8da9ccd7c539fb800eed63590da900d4e2db922b2624af32750f52281512496651caf8c7f64494d533fd15ac24cffd42e47125e6d534cdc2b8c9917f05dbe75498893926f0b3772b9e517c5372dbf11959c6dd6b22603da6a05f7852dcb5cdee281da7510558ed2fce89e22b45bffe941fee8d6b5306181d237e8085ab8e6b314dac6e3e3d168dc22d5b97d42072e1984af87e463ad55baf340665a0805b322038dc8c6f76c0cc3d0de5e8d281d447e3f5fa2b90338fb7b9f59e2d9330cf524b1c391449a4b51bc981e9e39c6503ca71a316e3c5b86b64e7b65c2f03cd0e42ba076ab613147fbbb2dd2f27b422f5672d3e1947d31cc57e6d5640fadc06246195fea2d7a5b2a88241936a16bbf7770a1e3d1155f2d8ad656792f7ae59d3c8768bf57b4dcc5e47cb4b507d83c16487d3234c7f2c7d7cf6379de5a98997f3af0598b81d6b1b938a1ee0839d93b79f3d89ea4b01081c84f0d588d2652157cf1095c60dc4c66b829caadb4bf702059cf983594901e103639d261f90423b84120879a8530b31208f6d52e59a5a84ad122ea650619f9b639b160c5a0b5c21d9eb05749d969061483fe84e8bfe2582ebdc9b8b0c2554fc9df1ca75a0ce113e14adbce401aac63a0d6c6d1433b9d7fde5280de3d3e21d575183506e834c6e79ca2aa3330223785b6534db30a2ee1ee6ae324eb3152f20a565f40908b0af6d19dfef1d0756ad82f9e46aa3a44ffd636bbd475a2da66ee94f967e6bcd79c9d229d09b0531dcefac16d74c34204a8ecb279e4c8cdc4036f523b9714781ff64bf19d8115fea6fb0e1ffa628d6020511c610bf2841dc61a287687d09b732ef0993a8ac6cf14e2adb879745ca8a0b470910a5a03a15089219e3f5c06b2dbaea5f07900b36a13fff98dfeab9d4d9bfd4ebcc123eb3b1ce60248a4481616bc28bc04b7880d4e59c1d0b97bff5935cb6fd5b82df38eeaac2027809fb0a680a416183e9c95c742982a981f3543e12cba2e62a99d63e0b7d3cef62fc31683c93a21b9a6dfb97ff20d30358e4d707c4534d1420d3d929a5cbb0c7ba36aac2c5d42d1c50b65d851b88fa4f4fa7d7619ea7845074692ff6f82dd08d639d4721d3437a1337d4ef40db35a9f1c3382d1529f21403215f6f499fbf8faf3375dcf8c91695fed15fa2dcf8db234d49c6c50db3777a047f86521b3c74a56d9b529af0e95016183cca3478fb43b2a697235644a43a1f85b75e167af1d0208ab1167a7f691f655f68b6b3ee26838eb14bd2c4d9c868e209cfa959b957b4e66d2ef4734b4df7b0d1ff532e3e54e1857bc7c4f93a706696fe80c1a54a53d138fc8f1e817016931575bda830a486752c30549c05ad2df119565b13432979ff453d49967de88d199f870c8cc525d6cf2d13687f1de9d49dd72714933094f8a1c50fba29a9c07e3880b1524cb39ecdd36d4d51b22379a5845006b974d11c498be5a7859691bb4dced537ca421cac112f52dfcefca752c6df32a024b620bacf5102d1047882eab76e2d0bd18bc98175bd6a44d6347c0b54e9aae6cb409eb471433a18e78d7f8fa6335a13d551f9df9145e49d67c205366e61f4eea1332ee8c4b072234231da32e6c90701f69a5b09eaf59a7fd30adfcd6cd08fe7c98b2784facb84d47bb379a55c8173d3be159abcd093ab8f6f7dc36df4a79dca9c552830f095320ee84fd556a03b85e0a298e57f6813dda4ee4ab5dccc9066bd79529eb166f2c2b1a21e4cf45b1c54b6fd2dd86a225bd982525ee8f9676da662158d2cd34cef86ee0d6a3fb1b4b4edfa85d2eb82ef786bc47522e96eca51491cd63bf55717ec666c7f38a1d50503d903a092e99aa7e4537ba3ff7b11c5cd8b824e8f5a68f218703cae2de0e70bd2f2d15d86650323d96847904d5f79b89b2871de34e2a336270748b19471bcaf53f074d5614428d89da3495f28943d3a427164d054e6ee65cfa757d446ea01cb274a8f6c69dd9485a1d69374d6af08900decb78df9ba5355969aa169899c44ad35e468b1b0ca801012221970f03732aae852b3c2a38a8cc8f354a44a8cd5f073f5fca6e5603e65eceda0d372741cd6e7398124c9eeb898b95baf93eb59436b24d58f1a5642a7a2d348afc0bfe41373f4ced8cdb921b65d3d5cdaaeaaafcac1975af56d11404818c15af37c6c02c1cb48bb3220a2d8b391791dc55e14d06e56928f9675fc6f39ac5f903294997226a1ff5271c72e2d134c995384b5505f3cad0a976b397090351d64722e26ffcc67ffe4bf753942ba996aea9a331abcf05969a61f49394e0dac01987b77492a2fc91638c992d10b4242f7d38348c3d1a706be043cedc7b59eab6e88738ab9017efb6b4e44735cccd7c70ffbd6ba84fc3f0fb87e32056a5e600bb5663e6f526fe84fcc232c7d656e5d24f9bb476350882690bd9ee097f6f9d3bca8c2b4610c5cea129124726dc9c278a38a60f045037419bb9cfef1cb8ce436834c08b82ccd08db94a13fee34c5a5081b880386e09d2a28f23127a3ed0c67677b1f2836b2489257843c622535ccc95d46c4f7c2df817d8f120a7024375e53113345e627171a9d574c8e56db5f674d018c9fcf111c3e440fad5b68201f916126b615301994c805cc549c712fb5ed87968b83442c634390dc5d249f0d8de92e5e259986d813e88b2c4a0a67caa300d1289ce9d2a5ec9427b42d4456650c40734fc46c1eafc0addb91fd5844e31e545f1eb4131f765a1d0bab4e1c0f775cfe14c626d05d51800fc54089b493e796f7da6ac11ea4d7222b30406d3bac153d06426644cff0bf78a12ffa9c0b17c0b2f139fb6471ac72ab9f7ece8b2c304c7f6ee9695fd06afbe2e5df51a6f70b12ac11fea967f3e980cceded8b51cf01beec5576d5a7c21a132e8cbdcd426773a1d0f0c6bb8eccb19086b5d9e92c7f1f67a749df395544632184618a432c02ff96985d5cd16327988791432cd0c779099021dfbd2ce2d111b276dce544d7953583456410beb5ab537e9ccfda272fdb66268c805afe9bccffe4f0fc3944803c21c8b5c56d943aa0af9d8f6f48bbb9a34e598750fd563ba644dcf8496f70d3cf6e9cad4fdcf4a7d711e9fdd80e9d1ac2261c714d718dd63bcd073b2df5f4e1627b850d6af23373599698f15a1ccd44d0a39e24ee40cfb2ddc3df414d1bda34fc53736815fb6876e523fb35d66dd76149c3f79541cb7c8032b65a8d23067bc865a3b3a8a2c1fca59777842a846cd6ccc946e40eab27d2ddd24837910f28ef5f015d0ae5ebd2ca5ee1012c787ba6739c20673fb49c0c614c01aaf0a1075505cc1d28536f4e72db6702209174c0966cf6e3af95cddd070b845842a471baec1113502b71afd9fceacb96bc3ca18e572aecee1a18de702b81b138d43b7ea66c3d0f179db9a32e6e449205a0b169f1833b367cbc217e8669eca9751698d1159fcdae4e9d05ba2aedb4789c9188f652aa460307494902c5924794e67882b44769fe1fbd2e6b10cd0fd85044604254834c0b0622127aa9b637ae9d3ca61368a118884a958e97288231621a59fbe2d279cf5b793192717e0cb049b20863aabcdac887fe5e309925b5585b96b3f3c01279387aa0a71b153dca94e5e10e1a649cf689ee02d90da62cee981376cb707cb93a51cc824bb1ffefe009a8ebed36bb31c1d20cc75ec821f0031149636cae176210aa8e115ab8bf84d2956013f79bf73c3045fad930adfac2aefb10df633593145b88a5a6114f1046b8090ea471c95892d0215aa0f55b0f662b420a9ba1cbd6a8faf6eda391292e2edc51a4eda242ce65f49d9c8f622a93066c503aa6787d5e8b972cc744c90c56c5ac6a3261aaba77fd2fb41ae91871bd82251f4f5a8e3b1c8fbf771a93062e52525925d0a81bba7147b8e5f8eee17891597e66ef7fce243a3c5c327b3481eb97638233908cd6c2d117ef016eb6687272a64a46a4efe936157e1f2664087ade49d4e5bd4d1197861ce062e024d28a609221a13b021d183064db395bcbd885c856c26d48b2728bdf0e98a3e1b08b1249b690812a9caf60168592ea3967de301c4e8ed1548292096ba4638ae2562fadee853ed386845b362034983f2afbcaf11b32054444ded25471c75e634798f15b5e7addcba88c503536fd83eeaf66308416528b40e550940d06cb68d0d8c8853265a09e27ce276301b26d35eff83341d60824258bbf7cbbf98f06234d6242f094b6b3ab1c027d6092808750410bd85f2224cce5f92b6d5288a5182eb26aa6c50182c4978269e017c3d7c950efce76862e2e755860587b8694ba249fff050de85b35b0379dc995554c429d874520d60d788e9462ccdc8d3735a439a1442bdc34955ff31f34c4221e754689471f3cb298d9014a2afa336021d255ac29aae3a8ea9a913970b8d324510e5966b63ff316ba7599552906a40a5aeea43a01942600a068a441411d54daf15e907e750c16420629578eb19a9fa315ef65f4e59c335cca8cc1372b884834e4f503baf99180e2685ad835bbd3be3359e454dbf08e32d9871761b248bed867012cda1f605201caa38cd1ebac565082d216d1cbda29f21d1140862e3c6a52e05b58e3193a13d91f60e6308d42d5696964b4bbda0528b17a903e500bb220185f81c47c25a0d9a6be2d15a56304d735892bd2630ae38f31d3a6bedac23b54c611e38c35e0333d71e67c5626f7d5a6c03f400f2e6a5959a57ff15775c8708ec9afb661598a5c28ce35909931f22eac087e04f18f24e036e585ab97745fd97c84001a7f54565f7d3a58d905d141c5a6336c60828fda9f48da42f8e1c6959e5b8925860695a77eddbe3012a54699e73ad5a89013ad3de486e8da5855beb15e3c899d9fd4e220a3796afb6dcc221a823482d406e476bb339c50ae7133d7172b36fce00297459cdafb9891ba5108a97ac43d453c32a09f20a9fc7ddd2796d682e9a64103fc8c46476211c188f883e6a08217198994aeebb8a443661efdec80d76ca05780a8411198ce8f56cd708ccd3a7471087c215c31d5de3f2eca5a6ae521923fc43f45051c3017cd06834a84a268d2d0bc50b136ec1f4b1c9f934cee6d822ed67865ad57064fa5e6b5757a07101f8ea3e748cac18a6a9d6f881f7ce843a48df40fe85aec0a2d177559dc5d02a9a1dc431f33780e61376a1d5f314c99910ba619d1aa6f061dbaeb51f4210315db30616bf1d2177aca76a7ab0f092b5c67575392dd675cd1908ef0b28d5338f1227bd1951747245094a2e3175c5c62fe5c4be5823dfca2efe3f7142847ceaa2febe46fe2f42d3ed453bef20ecbccf6cacb109ef46b383014e9af3fbfd7a9b2d23b17b907c0c12c3d953b3bf946a5ae44f8a71c52c100c1ced1a49ae24054f6551da85136261c6dcd2f6122385de47fef3a33c9ac21ed48d5abb479e8a9813ab662f161e8d35fff45fbc0196219351c046b1cd36d11698af112e714c1b951dd0cdc605c63ab2b5c0017e22788e4f47c03e89c22f2d8d09549b5ad12822dd9cf081ec166c2e3291b261bf17d4395fc5d3b771c304321abb79e1b28a8c31897ca2747401d6e040b0a87db71f521e3e3db09d87f3105b8c1969d0b389b2342854c9b8c4029f8f058aa477c5b17b392186087a57e55f420af41191817070eb5ba53eb4bc8e5703686bc2fe5c631c0047971d8e5f4266e27d83519045a46d713d21f5920a9ff25df33ed120da4c13983cf76b4656b6c513e9abcbe22d832bc19893a6efc8a2358e185fa74f11083459a6ba621b0bcfd7a8a5a289225da9e69d090e1e1370eb89690461d53a8e202e50225be982968d336ff6b51a02f626e3d7f7eaec4984207339fea6fd6fccff476e8bee49df6a8c2c3d684a68e8730bd0ff7891831207a3d811136027a2cfa0cc4bae4008404cb42c9d9a6205675f57ee07c5ca1ec1b8c15fb1449ff227a6f17ce4654489eccc2d2e2b06769410e033536a64f99bab6bc5a179dd73897cb48d40c8619b928f66d9d4e6f3a2d83cf063b7c24d0c0622ad9a214efae6e873fd67e7496800061dbe23021288356d5f1e678715192b6a0b73f77cc5428ec1a7017fdc8ee675807560e988a0fa84e1ab62ed63ae57097dc49c311f1d3ce3faf6be6c470439651b9e9bc87424244b5fe9b9283049daef0bd1636948be6812156af3d3852b192310bb9a85005d3bd490749446f13eb2aca920f2513b3718f9662d0a0925422754639e20240fa2982265b34ccdff98e58461573c2488d2d4021d46931de9688897e72836849657c2d1715d18c6cd005abb0e55635f35a2c87586e44045a2cdbc35aca62114f82086c209a8e6deb51590ee794c85f4e45c4fe9dabe1c9fc4552da0a7ef1d1f96d0f27611d1dfd04ce92eb0ae883e22dd032d310ffcb08a82645f43aa0fe2edc67341e5a4c500df322e91520be9ede0a93a7dcf665fb18f0de6199c29f5d52428619bf469a66ed8230e435d2e27f84028c6508a0876f7e9e2136c49e76d0223079d10c661b62143413f4543261903e3216aa7d8c79a80f695fbe90b48567e87535acc5085dea08c0e026eb9d62d55a8531a7abf51fa3c0d1d9d3284d188e929fdc7add8c40877a57b82e2140097e83571ab45c08867c8ce67fa0f6b516de25b830d365c76d04d6c0b35ee5e8eca8cde661027d22dc2f224f02d36bee038ccb43d9bb1e5753b0f03a122cd0a75670de002a8f6d27875d41ab81a470c1d3039528ba1dc5353aa8844073228ba4723849a65e9164e1099c35407f4b9be55000756c2731d3d391c6042a851784938b12a8091c45515fb566e5f77524842531b7c00110b1986ed2ea936f5c8068602a47acd23d55133ef1fb2d62391bf23d8f0292364b43d30a3a5b4f82a23cfc2bc7584525dae60756279dc0ae8637d95d3d53930dbf6636560d8110c992d385214b9b57ec4a57e4a41f86b7ef419e96a8c8f829f38bae842ca303013becc213babe35944c9f8426dec6ed6c64ddc6780dff3621537f0c5edd9eef8938b299a13480ff4670d8e571dec66e94fd2e364484fa84d6cf46d61ecfa85eae1b9ab04c6b0371cd1cd30f8ad74d1ef605dcdcb880a8564d4a96bc61385a9ec543cd83194af06dfe4a53534c38473e909ae79bb1e36b90c5d8ff55ddcd376c212bdba5621858eec88db2d096531e6ccf0ce446e4ebddb2674d84f7d0c0c4267a970ff53718bc850ca3d9f127a2cee02617ff3a4fd7658ecc4809b5238e9a55cba4c971a4e40276553d5c6862656925e92eadcf27e9ed80c1f63a2904051a76a5dfa56b027e1220a0054fc20afb7e136aab3189c83a061800705fa9f0b378b267dad7c7a036fcc14eca3bc5b1ea9c85e28d055b9acefff165183a1c024ca74d992c1e3d682d23ba91bfa5599dc6c147ba5e595964185a03f6ce4b21e2eca01d6bcd1ec3d8561c19765c8c30684e8da2ecafa70cb788baec408c80fc72072526949aff3a2375dcd124de5620cc74fb852a8d9a31f979b9f4fbe8ff6e1307b467104cb68e27393052c803422b98fdee99138699d677f022417b0137c54e5681d2c516195d8441466cebcff0cc9543271360666d50cd12af06960a1dc93e458d5fe5319d7b762e60ccf1e88e3c165a4d9215784186170e938adf81a8223c7218389b98bb8c2207050ae2913279a7f591c0caed8bc467b4d2ad744e2d08dea3ae8d53779e34940a05bb7ca26a94562e81bb71d05a6b27761a98e2382646f3e25b4041c8248d1fb38d8cab7437277c86db4153224d454575e67565472740a911edd0c98449dfc284b774e4240ceb1e4ba62763223ae22be6eb424f813d119245035995ba2b2f60ed5db5c121f5d74df3586368f104ed2926c5a9e55133ea39eb6ca472d292ee957a0677683c6a242b83313604b148f064239ac51b895acf0eab07c03630a98293478ee669f2f734635a926e925bcb08571ff49b90a798577dfe41378eb38b53947a7b4985abe1ec574f99e9a1644d949d64fa963705cb821ebc1312a0b20edf6de46c34619a2c5ba795bd812f7c8c3f1d075ea803c39626490ee2faa92cd58c13fd5b091c9db935533ca597fed6c55f5435eebbd844a95a6532210c802b2f2a5054ed4b1552b4d2ae00810f85861652d22578276ad04d87cef40f717ec522475b136efb22ea1a97709cfa8690ab8eb01f615c813f1e9afd4ea97f1461be9d091f57c3adb2ae33ea85b5782a7714aaa27a963f86547f27a3e9c180fe29bed45f6ce2d448d6a3457900641d91f961d09ae6337b0124d3e820d6f98ba51d9d680cbdd3d7e1b52160c0bc9ea25dce1b4fa0a53f3c94c05f70b19c8e5b086e4055c5d4209f7d3b02b15a52410d49a125698cf86d546d4c15244364a587d63d19eb3a589ae232d7b6a3bc8f494b523769f05c5027eefaa18c7ab14d9378a3d111178ef391b3b207a2626ceff64133395f6f1c61f49f55c8a2b1a1a12541d411af6f2a730285a327ec325cb1c49b41f21a0a4f79c711495fa45e54dde78c56f10628687cc6ee285c471aac9bf5a333f73d86e02d5d44aa9d639798f742f201c84717d7dbe570bd0f961119e71092a466279e9efa6201d59f06b581265b0ab84b38ff356ab2865a46bb3e9dfaa418d891d3184fa97e2eb32b38a09f5756cde15dd0412b3bad53f704755836e4b00db2ad7fe8a9a80add4129376db8ec8b240f51a5b57aef64c644cd064afaaddbe943120729a97da7817cb51bd170745f6406b1daef86519a647d97cc4f685592211ef639639b550688a79b4c520ac5eb15ba5a2918cb0c4897c1aa3b1def1f6390a1eeaa09d51029a55cd35be44455d0faedee916f4c4f61fb46fbcee11706b4c3383c654e5eb61cfa5e29e09f1952aab5e73870262ea8e59659ea90fd31ffbe71dc6a28ff0fa72ebbad420b3f94388293dda10a98cf47c35b4f790337246efa71bc2885b35e5b8ef840e66b07428c9d148baa4e926adfacf1999081ff426fa9257dd1035557a704b00aa79be73f0de22acaa58f9474288a1dc53f242c8445602252c496a267da085e4b0a13c371e19685b126620a8a1d67450eae68e7774045d79e5a7f94b2073eb6ecda7081165eb686cb088acc100eebbb8af37f614adb19050c4b6222716822a9f3c09e5b4a1a713cca28992edc4513d12557b77c50fec71b6248b5cd8329a003b95a6c8282fba0ed4e112f9184cbd436e0560b19094b82d34af7336cd91c9943a086d52f6887b94d876bc671b7243bea3b4ce4ff1dda94c62437cc7a51189d2c28fe4d8aa2aed85df869ca4dbe2ef6940d81078525c6b50575397ca82d867daddde03fc9d858e8c9b9febad45216e081dbe3b9fc0d759a16389d8f055537ed4f6f787559d323a38c533472566fbf4cb779c1480d00a538699cc303b66e49cfb5ce22fd8b7c511521a2b3133eaa6ff1616e90354f9c0ef7b0348302879ea95e0e159577df61a2b1b28015d7dec0b11079405a1c1d1ac4fc069831eccf6414372fc6c9c7ad391b0c8e4d6d67485e93b83cbd115c8867936c045419c8de80b1ad3337320f8b53eae493917049e7cbf84b08553e1d77f6a213a91f984ea8ebf2c4c44fb3519d4d7a26d428c2776654b9a046d603ac2442b45838ca05ca556930b807795068aa8b940af1eb2296cafe43b97f9db87569c509022d76c4eb26c2bc9912e76a54dd12d70c34436b1792b0c691dc476cf26d8d55a70de4af9904423499b45e6884d77234b5af15d647db54930bcbb98fb93b8376e3b519602c4ddb0a76bee300ffb122204020cf108327fe4177cb5d260cd8a5f26c724d00d8d80e1c936cfdecfd936315ef7fa9cf53fd9ddbe22743c6d747fb9ff38e6f4721c788a31e552886410242fa8b7486dba109c58d77d8a8f2d362d2862089a6ef8c4338ef21107a6f5bc0df98830def0296b6976c9692909f8a2d8ec9725136144fd32c9d8c647c66c23f79ba6a9867f0866621885455729ef161e8b474304451a23d317898eb56f1566833a6430fcd74b491ee0ae95b866229d1b6102936c0d087272398bfce44d2367842173d422a5f7d67e551495039c31e4484f6f46295a4b568ee30b9378cd458db077bdcf50b117e18ea3a4f7c78e23a23f2f6f14ad2b23652cb76bf0b4284301d5d913401a8d554959bb3eaaa54ca48502f212518834c8baec8a5254b2ddae3e272e2846737f684a8afb856e7593c96802eb82de7619092d2a1e11ed01cf72bab9c4091c78e48994113feebabd60d6d011b788c14cc53309e436bf9c2cfb748e520b12c6099375c2228f5bdf7e1ebae4d4698bb92e9fcc55f30dec59412e8c915504fbd076141621a18fe236b655e46b4153a9bcaf891de0a8db7aa9556104869e4547842c00a655b448e1eb632dad4bf33e693f904c9aed8da7e218c80e3a1ebb084be13b48bf47584034ebedcbec8a134a41628d84ccdcf8a9f99fe588463583cef2f8ea0a340d91026105f98bb700620454a2037a032b945830da11a70de59446a24da13de081f64b75bb9622029bc10c61738c7036a067effa2b71eb0c66664c791628480a31120f203c743b050428f82bdc6537e5a4b859ea99608aa57296a61e481b46d1d336a32d894476f7ce2403b4081d093709d25ffedc1c867883f061aed8c55538f3fb1bf6abc52ef063aef846af6c10e3c8f246af4819210ef96e381c12c618df0d1c4f462735b7dfe294cea7f5ad5677507b432f57d72bd427b519cd803d8f0ce7f13032f415b5289678f355c96f74bb2293971fa2a1a77f421f45738e44f126927fa2912c92c4db8f087fc40c7ffb112784f37ebc0fdfbad0c2d684ab2cfd8b5e913242e82b195fc96c74bba2538e47fa11fd0bdff12f7c2513fd0b5f45dff12f7c25e35fc4e85fe478a7221ee93dbdf0766f8efcbe5e91e8b6cc23bdb7bedecff3795c6fdde8dccfb7e617f132d0db779f1ba4703f5b3f45201c71f32882beeda93df949fc44224aaf9793d27b37119df83a9527b52cbfad65fe47b994fbb792affc8b8e2f202f09a33a1b30f640321e8f87917b9571eff8686ebd23bd772cb9d1face9db2dfb09f7f696e27cbeb4bb0fcf7e37d481ffcc19f8f76c314d7e2ed83ebcf2d3eec87d96217877d60bf1cbbf4c73c23c5db0b8ed2ba221f857ce410b81ab94379f311b251da19ede8ece8ece8ece8ece80491b373e9c8d9f2ee6f3f9e4e103941e8ec04a1b313444e103a3b3a4168a3477adff2ac51696774af48f4bfa3b3a3b3a3b3a3b3a303ca420907bc7103c78dd20d1c37e27c5f210c6e8f9825bee1a6c79143355ea89be0cf9ff5dd629fb7343af10677b01ffad3e26171e7a96d10d32ced63d1b7ac83d447f6867cd4386ebcd8b682f041748c7c52e2edbda849194406116384f8bd4ba2833c195f103e7aef7890fd47e341cca3b2af767e05c918638cef8388cf5edf1fe36ff6e3831b751f23063df6f7c7cbf51bdb3e1fffbd8ccd0bfff0c67670790f33865d28de7eb3784aac07c52eef63c63ef8e5611a3b46783d8f2ae5d5b2972daf3e2bed4b6d13655af2549b2fff6a2515fb12399fdec7b46aa193e857cd613ca66b7f1ffb56d32eed861a0886ffb4f792257c8745089f11ef05730c0d7546151cf2645c4867ec3fe76f11fff088f1e8dc3fddd3f9d6b4fe7a4c734ebaf006ad663d758e3ecc0ebb54ece4538a3718b3855de2bb1e11bbc88fd9b9fb9cf3fae83dbc39d42efc96db8f0fd80cb14b3f8435f2837eb2e1a437013f61bfc16cfd66f3b491c4ec243a4827d131ae93e8209d448c2ec2a779b361c3475edcf744a20ed2495cfe91417dd00b22b986ec8e781193ebb3710612792a1f92e7cc12cb64fc84f868621f5fda9758f4347ea328efdce9e4e9cfcddfca3e2729c2ce91e43bd2e548529edc3fd19c2365efeecbec43d99b649f8f5f32b51ba6b814ff88995ada0d535cc71828bef53053ec9261fa307b68b1f55bdd89f9835db2a71976991f338d17de5e1e36818833acb67559966519dc9e1077fa9049499f5a1ee9fd94b3e7c41bdc7cc7d928b5999a4f889be457d1f220b6239342a09b32e839421cdb01e5046aceb4c67640783af9f7ff4e275888477a7ff219de44282f1f0ee18da26093135cf2f541cfa424fbeb23de6ce496e7849872874f67f7d184786f82374a658637d1b3912374527c4b4339d6c8d1f9a87e7cd05fedf30df3367aaf077a90e63438428056c2f1debd7e14086e79f109c94bf40a7c89788897632cf19014168d1542081dca7f10c20abffa9b9fa7d6d4b6fb02a47ec9fa1fee724d6c4e3bdf5af57e8b63d7f5b3821ec31b8e6c610cf37bfdc430bc59fcc32bbe7169377ac5a2136ffd39a601a3315624b3a55f2f40ae1a86fdd8af48e8b7f8875a8b3d6afff39866b18ffd60d761b735494d5293fcc0be83f405488cf1b96cf3e18cf1e994cfbafebaaeebbafe87bbd477945efff95cf4f32d8ed5ff50a7d794f1dddfc76254468a514b3f4fdf3da7ffbe6a56a7572e8be22db7633d9d007a01122d4fb42bbb6377ac8ed5c95e80788ccdb13cd985d9db9cccaeb2bad91d0f02f220d96f2f06a4dd6afd16eb7673cdead7faf5afc8ea581dab6375ac0ead819be035e9c75a6be14773d9ac8f6f8465ede7d3f361b4d6dc60ce7ec391e98d1bbd62df7efa3f9f8ffd1fee82bdcb2d2f08643f26dfe217c34afee20d47b619de2efce01f9e4df036827bf106c23ffc73a375c8878f69377ac5d64be2cd9af186bd2d71747ab201b23ba263854e14b96390dd912e667205dd77f07d8abbafb5be009911c6d75cb6ebe505e177c7497f4678d1b7e186292eeb2336bf56d00b905a27c8c1acd68710041f54718b67da6604e82106e1cbfa0d5ef930565006c270bb5976f1368297fbee36a87efc8f5673c5db65e1d0cf9aa457423e6a19b1b5013f3dd3e00e63f418a365c92735cd659ff3bd9496252d4b5a25f92d4fba945287fce72ffe86233bc556fe452195212b9350a1bf0099724e9f2f4066ceb43ceeee6e57734ebbe373ce49af9ffedc7a1beadf9ba1adf5ebad587d5babb5b6665f4ddef1764ddc430faa260ffa10c841ef6f5202c2db07fb1065eba563983aedc24174ac0ec8ad2a728640f9f5693959e440d1854cbe91dd919c9df75e80c86749fa6f88972dcce325d3aff005886559efa1e612df6537279c0f2b1ce2e57eebdd3bcbb22cfa967d5bdffa0d8435b683cbbc4013f49f0fe8f3f9dc0799643f3f3e9a78b3397be7256f52925d10761a86ed5631b66a9290cddc825165873507be00711509ac61d49a33ff4a3d927f7ea3d67bea9d3ae691fc3db5596b314895fd3be69d4adee95fbcd97cb30c6f305fbcd10f76146f3eac0d9fbf66fdcff556b5eaf560929aa426a94954d76710e770b8afce392c5f80b887f04beee1cb52731922b706b11e325a38b24bb9c9bfbede30c595db455d1eb6e0fbdf681d13bb487ca35722c45bffc3377ac56b432bb4904ab29af0fd0fcfad69e1261720bb235ac8099271c8ee8816b6c8da765f8050f79ebecda1ee654b73190248a6efb2d336cb935ffc1ee2e59dec36987ff8d6d8e1cdeee4f712db4177d077980dd87f641e696eb4fe7b97994fdfc21bbdf35ddb44ee78f3204e9327e867f616deee7debb3772a798a614ed9d32fc1d6671afdab5149b23076ec28867d3833f27c8b370ff2b41ba6b815fb10e579fdf6922f0cdf0a92dfc77f6f755a07c52efdb347ff94d8faa8e688b77e88ad4eaf3cecd8e5d1603ea1501ddc7ef8f6d1608d6e074786b97e2cd4023243fcf15846b5e856caf16d376f22ecb7d1e76a20cd3d1fa54ca92ce352eb316dda1aff0a419e7ff37c4ba33e1222c36f4b00194aaab95c3af181163ea0a50c31bc1c3a518a51d619638cf77ae9798286c8f2a1552b2441963bacff0bbb874464f9b0c70565745009b14390db881ccaf25d8b437fe9913e31639c1f35eba136bfad85bfddd37da4777d8cf2add5eaa579ae5a75a3f8a02132ec1133ed5c633b7f9f0b5f4e255a78ab3992805e391291e195a19c33461931842e786a61741ebf9d3644d31ba7354304b370e1374e86183a6aaf774319483927a68fd259138a216a6f10208cbd31768fdbc9d0ad32cc2ec7fbc6dae6c9b05719764e868df36edee97a88bda67b24193cf59ef5a8a7def4d4939eea9bbc89bc86020f75e1ce79a4f79918a25ed91bea1e983a12d33d102622414ae8a20ba566a594524a193fcf87934229e5fb4b4c44cc63bc8610367648b868aa490d2e7c87ed100048139d1842860f69244c4c4d25a2139046c20820470c6dee910d0e21e5042ea82095e14503052c4a1813244d866f82850b5dc64859033485b455e8e81076a44080534eecea8e28a33b9190a8867d08c6140f0d1a8d48c4384d72102f95116cd03246ca1a20319c7b4ed08b678b118918a7099108634b4185149126c3cb0bad637e7c491d8bf5f12dc772f9b066b8952aebca9c4678bee494d9f2613f602f84c1c767b90902b160b454a33e7a193e25a8e47934197e853bb23b4680490dee4976081f148fc81bf284bc2019be23327c37c34fcfc67bf0fbcdc7a391677836f6d98844d6b3a1cfa694b767935f4fde6e0f4a96f8c598d8e005a1f6d1b8108a2c411f8de4913313c2890548ad77f7c0adf5d10b6577d85ea1773bf8714e05be8f10a6945e67b9d3885e6aaf732050de46314b1c9a4708c590bdee5ed450c8bda3fc76092784f4e3a4787e8d76ca372184aa88edf091ab62832fff613d608cd1053e27e48c6cd4c8f0ca319265c87189727c8ecf15463558f10511e028e75081050d7cd003237c09ff49fcf03be38c2e027ffbbcbd254496d562968559bcc5bfbede89f9007dc8408e97062d6a516a519cd2c37fbeffc444c42c31c410a653f65e45b890666b6d28844365001902e024143ac2de9048243a4182c8de508e12224f5822c71842809efb9b7e94143684109a71e17593bc618a0b7f8b0fdfd280305ffec47ac48ff05da614420a217689f8d227684854a3ddfae0b4f081163eb0aac671395a6c4e0c6906a7852c7f73629424c1e55c5af8400b97d3383238026930bfe913dd3ae10aeebcf70af151463927fe110570c61967c858b2fa1d893762071dcb7c13c6f3c4cea9cc774f35e746f2496adf8c11ff78594a87c99721862b78efed8eeb1f271c85441e1fca21608e3b426e0a49fbd7b1c4173dd2836f43f68644ef6443867c64a3e6c2f772c4d0a9c00c4d90a373a309276e79f1e167187f402d8f4915ee26a99034315208928a1a202c406a254dab7066a8a4c9d0f2f81835f441907f236427d247c38511d7283dd2a313e3f1b2c454648284126c9a21dee0cb8fa5f811f301f38104db95e1d769a126f2d1cb2e105f1a12d508e1d084155f84a0c8de188223910a46ef343289e28620b5220368e1032d44ce4605b0527aaf8fec2b20435286ef9c766b70a71f448e45ea90e1d31b84dafbc3fb22de13408e02c89bc38938d1c2ae674a8c00a7f29e2e71fbb7507e2ad45e1db2982a9e803a64d8a92538c801c1dff9c7e8ef76c0777fdd32f424b59920b9f05b1ef4ba5d57c48778f371d96408670a2e84d71459fe765d91df3bc6a394e3470a117484d1c765e383eb620948d0941e1fb86cb2cb49eadd207e7f48d41d23debebbbb5bbe778762876a3c6884f531542324aa61694088efe8777c1b6e88e2c689ed80315a0cb18cc86da939378a3047ec8038863b6ab843e9b109f910fa2846e978f318638c43189123d486e81c9f5b1f3df83ee20d46cda6e1c687edff7b7a7df4dee2e2f6b73c77184229f10fe88303ce38e38c1c537a7cc444c00c614eb25364a344f2e6c3fa884ddf82c247d029b4a0c833fec3ae57e443cd9ac954052d1ff40aa4224807719dc4911c72c821871c72c8c1539ef21857c2e990993a64e8741d321f6b85ff9e0e21b975a4a08e19c0d9be044fb91220faa876fd7b957ebe3a8c87763dad7eb41d5cfebc7d874900c2ffe0cfc3ffe0ad7e7ffce0141e5cfefc0e2e7f70caf52e9f977fe11ba470537a7c5efe07531fbde4166f4cbbbee5f5d7976c694e7ba9ed486c6282a4626a4ecdb16c3ac6e6fa2769204d5291b89a443543243f6b434369725093d42439b0992e7b507d168103879450420971c0f853eb09dce895c63610e0da6befe113d75f7423f49eb84eed2d4d519d8dedb51037f543e8b008bbe3a369777c3463778570739cfc5ef44ef6e53bd19a477a0f49106f3e44d535f951eb15ffa849bcb5cdc9f0bb35a8d32bf0bde3224d939f0827bfef9a771239e9bda826bf8744ae5f3f0109010c0c0c0c0a8947599dc601087efdd288ed85c8f58714faa81d7c38964b2e6c84cc39244f21746207979835cea6c1dd7a662a11adda1ddf61755a9dd71e6353c58d4d1537ce660938a75dcd19ccd814a989c2e64689db3d8bd444616375ecce9c333451cc4051c4ead89ce8315348de7ac666d2396d329ddf3333a219ab63d353e1d6411adffc288c41d71d99bbb54ceabad08f755ab6c68e767eb528ae495ce5447cc6554e64c6c118039761ac3b42aefc7695cfa46e0f51351137d17fdf330dc5f53549afd09a43b737a4659a484d42b1cc11365814bf6d042fdb6d99777a16d3f854961797742c6d7d6deca033d1b671844ddcedf3c35c6ecd650883eb627dccdb67eb5bfcdd342b6663bf38cce286d9c22d2f8634518d90a8869bdcc1c8048c11cae0cadf42cfd68030b83d3fd67f5fc291a3f376a357f0f672240b47e4edf34362467e6f0301377aa5df6115c34b833e821bfde111bb579bcae414b1a2c84d912a8ad81489a2480df65bd7e40df471c27f26d2fa7aff7a99234f13bccda7ef54f2aec4650efbb0b966782b5d17842f591dde4a1b76e10dc6ecd8c5fafad1c22eb28df48ab58c748d1128a2357cf43e779c6f79c8facef0ad88b71f44c0ec521f521c3e7a5f63a4c6488d911a2335468acc1cd996ac112335bdca5132e28a382335ce488d2be28cd4f4ca883352b5928f4a1987cea51bed9a36e272409e7d515fbc8155bcac437ea51cf9611ceedbbec41b8e587fcaf710ce874138219cf01dd6f42de0288a811881728fd4518b323e8298d2f7cca8cec63653ef7409712e93a1c1e048fe2da473d029d82a54c97b25a4310a38eafa5453e1546cbac6a994dea98f34696b001468b27f636d6b95eb30ba011703b6131d624aa8eb371af941fbfdf49d1ae5be5697dd54f25103707418a6776a55e9f3c95573548ca1a8fa0fc288dcd431ed827681333917e3a63e42aa5310e6912a8ca9c9fe25575cedf3a70820417a8a1842a461401df0e12377c16a1ee1a65207549be3b9416040be27b2f7bd0ea293d822889c08f8c8bb9b871d2fb6e04e1837f160596865292d9beb65a1d65d702aed8526f99b81a6c79879a994908f95ebc3ecd3446de3581c646bdac6b150e14edd0547f2f716e309d9bb078707c5e26e8da491e4f48e9be677c88a3b3fbe7c6a599645572ba922d22937f5aa738043f6f7921bf9c34e657f9b84bbb5cab1c0fe2bbb235e704176a7fafd16b6a3a9897d9a95210cd9a779aec18abb4d18647f113886ed90b445b083cb3cb8de028eba30102350d7b7aa55dd4e2555df6ab5897a854ba5ec46efd437af57efd4336d937d5501475db8558fe46f85bbd1ecee4fe9677bc933451d01211b2bee169a22fbd71a77cfb9e798c331d53a2cc7f2c9ee4353b897ca4dce24c7b11c206688901c85b809beca47fe7789fbf21665fa3bca449938c4472d1365a24c94792f64f39e50b3e82db27bcfc29164f76f0bcf622707463929a4f4795339a925ad73d4baaa85d63e6f4bad7357b518ce077e3ecffb637359e7acc31ca8649df621f28122bb07cd10c9eeafb67991ec3ea329812525cfbbc47bc830a3870c3fc261786eaab0e2a68a2c706c5a46480f699916d2443ae53b6164f76fb543068f103872e880c35551aaedfe1e845142299fb76c7f0fc218e584733eef09a39473526a5df0ba9ef735a9655d57ad168318f6bcb1ab5a8b619f0fe8c27b9ff7b5fdb10d02d9bed77696d92e31812626cfdba4c43a173a39b10e05c53a1a2228123d6f51564bac0934b1ad8568c8f609b6c3896d0d05dba1f37534200d1acf9b06fda4f4c88e4fe827a507cdfd23c88ee323f790c6a97827c089ea6c6c2834d9bf3aa9da0260aa60b891bf0dced4d25f836389df9f3953ff752caff356572bc712ff0c65c0820539d0422a0aae0a4fe83c61092e6a8e60813f988a37bc5442c6c4a7f1a0bec48d4a01e27afb160331810ceafa1794acfe75d29475bd77613b206ca23e1961c45206ca3c99d6f1f2fd8cab177162166ea275655914d7157cb61491b8cfbd4c3c638c3db10b575cc9b48e58865ba15e883252f5282a14226e9a9b2823b9719315386e82ef5fb370d3db6286d2296aa1c9fd35b93ddae4fe08df0c261f8c114af824b4537c648c54a24930bde294e8936c128430ad43cac83e4530c05c188f4f96320e0b99ec5acb09183785c7ccfd56c6b2e788c43d01e3d66c5b1ccb27ef30ca8debca47fe99142ac702dfffc2712c4e385349e558a4090737f2cf52d71515874aa4e4a6ea84bbae902aa9722a54c8db6d420859049bb3a92ba71280dcf8ca38157f2d752d9553f1c6178e5389c90fc61885806ccdac7b2747b95f42f6df6c64d7a26557e24e75e548fe29e7590f49c0f8c8652bbff7394b499a777a91a2c80b6a6e7450d3849a7782400c8a8084174081b38317a05e92c08425546348e9e0c00b94bf9c79a720b8b082119832c8388204e5ff308984bb49551108e59b2d7ab29f567b54ad43e25a866b7c86ecbfc927b2579efc7eaa2b47a13e39b2e046e126f9fd9b549986c8dbe226309c29c58dfc453e527db2bf57e980bde2bfb92ab2ff28aee8fbed23613cc599a40adc4b1520c91b68128c9b2ee04631ef74611e1ec9ff9b67fb782153ec1e2986089ae573c04f3e3c5309e12689332156865062d1e2f1a1104ec53f6a1a702afe2760dc1af2566b08c3edc9eec816b2c89da5cc2665e410377512dce48e7c1124fbd73050f52577ff0e9d1bbfe4949b3a937927d0fb67aa77c2deff83f1187dbe6af641daf518eaa339546b3cb8fe5d5fafebfb3e293d5e96df59e22d080c67338fe4ffb44c8896c93c927fd532d523f95b2da379a403e4c659ca47fe252cb8f5af7fb5c647fe488d6b143eba3e4a3922f7a73cccc309671892e5d7959f5c1a6cfc2b8f1936c64d31fb5b2148b267a921ae3aa2ae8e9836303030302820b8235b9841de4479278cbcd920d9f36653d9f36665b2ab7a081286d6430b70b41e5a90858f5cce64dcb495dcb44519216e6a24643f4296ca1e85f71084c7472e4359e3a617e3f5fe39dea9c619f94a9e8c30c207e5833626ae753c5c8aab0806195ac1ed971fe37b73be52eb9031bdb27aa72c15c1d0a76c06a51f9c8a3fd4b25369712afe32a67550c0adb84635c9246fce264bc29db1aedec9a11eb5ac988a4288e47e3993fb670d59916371d93dfc4dcbeeb7928ef79b02b2fb0d87ec3eae5ac7c332a65736cf5b5c654fe1ec66d024ff6d94ad7cd92069ac6544e2c218fb9d8f640d56dc0d45083531a9776ac966d0271e4df28fc9fed18c2f647f8bf1f8e4c6350ca7e2150d3cba9291fdbd495b05433d23d79553f1cfc8b8efe15657d9bfd21471534ce57e37b31e92c0f191cb306f5295fda52a0d79e21aa2704db273021878724c6951c88baadc5fdd65d129237cdeae7eb6971c659c4a3f0a0df508b4a0baccbd01da2a01a525a01404f7522628bd7713658733f04f2faadf9f7331f680d9c5bdc02e41778a7b9753783815cf9dafdbf1f0f5fcf2ba04ef14e53b94d49c239520bf7f0e15b546396c02f4792731320174791e0361ebe877efade3b995edbdbb0f27c8efa1b6794fc0b3cbf39807ddf5d1730f9d730e4324207408fda10b8c30420891581bfa3df8e07b35de7bf0bb0b08b31e92b8f191cb5b7f7c18dffa08867c04af8fe06633ace12691bda108cde0fe2fb9825c3b167ff7d577f080f8e617d84438bcdd0c9fdca49492fa2862617bff1ef4913fc07af9d49de8cfa9b90bca2e6d69ee915cfddaf25bc2129be87d06dc627f7c8f98fac8b31f58ec4cf1b77e6a8391ebd20fdd0f98fbbb0b5894309e26be80248c2a3cc163047bdb3d3463052778c9117086406aa3186c44264aa8817404a185500b48ad3383c7ded00ac280852ce6dc66be7e93796e8e05fbb9bd3cefcf6fc702faf9ee583e3fe79c325ecd3995f820ad9d4afc8fe64e253ea641a712df6acea9d4f8577c2bd22232a453c629e78c51461863453f2ae5f821270b2ea5a11f5dbc8d4aa09be383b71c98fd1fde29844d44a2128c9d736e9a6fcda71214bfb98ce3ac261677e6b8c97adfc2e4fd1de79d6a787f47e2a692f7bfb01da1905583e628ba7aa7b9a220ba2a31c9e6cadab9b27365e7cae51a4a6a10bd89fd8dae4a6aa8415475eed62a987caddf1c153b2cfc5034873ad11c2aa3a1916026063da8d65cbeaad576fd316d731b94b780a3507e94fd044573a8c6e243cd879b28293f9ca0a0a09c340a0a0662040a05a2a09ca0a0a09cac5622fa62e22124b58272e8506b84b27bde107e527ac0dc0f64a2c58d5289bbd9ec62afb877bfce592cdabbdb865670fd9da891b4b76bef88c4755ce994b15ed80ebb034629e7a4d4bd3572d705aa193a956e69775c41b89bcb22bbaf367f43eeab16eeaba3cef27e9fed25ef0851f707dddddddf4790658286eb78b357b5d8a7e7677bc9d44720ea993a956959d28af059de36a5079687e8331cc73e01030d1e1a61a0f45e91e87f445331227153bf7b0d4e769becef4f813c2b7001072820266f84c73841060a08f1b75be29feeacc87d1f7039b93eed55c051278f124209a1a03c0d1494a7f1212d0508d1875e14a281e205745da77df4e6517aaf48f43f1acdaf2764dced77c47fb7bd9968711fa4d6c2f4e6c6b1dcec4f23e5589c8dfd1637d1528030f913937f419568294094fc494948eb9e13cd61425a0a10281f7a14dc3d29f4438ff2275a0a1034fee469603fc129f451b0ab661e89620a38eae44fb087b0cbd01b6c0b375118fb14c74d2218a2cfc8d7ffa0f9be3fbd79a793f7a759b809f4fe16b623cb4e3447bd9977a294be193a53af26821fd7722befe94c7dd1bd95ce5cb8c55d3ed1202af4572382e6ebeb6f6f26fb5f20cdb6f39393d0d6325bf764ec5bc6b1d0f72d07cba958260e656935bf8889600797af37790b38cae4b196b999b664be49f4e1a8eb4af9a1c4a1ae872626dfe24e83d49a681065a201a1020e55f2d5ad89e6a867f29d7960828118812ac1ee911ea637f4e63d9ef9729dc60d6cc8c16a14b506be5aa5a8358daa9a7da8f1a0d6dcc89dabab513e386b4d956262515f37d71460cb6bd5158978ad5a47a9576090e85395a22e71615cc52ec41e29224ff442bc91fde34dc90e925be3589c9b1ea3c738bba514326fae295c53e88a445e5d37ef5461dcb59af3b21718d7ea9ac275536b4a6c80712c4e092ee23021c72b5871f34e14c8b942e602277ba9b54f2ed929c9c295bf95e6a3f4515a6bbae710156582d62830cda1a466651bb4e0ef22cf98e6504b5cf8be519776c411483c0a7b244aa8eb3fffd16a136e01fe52ab2a279c8a3fa63987c3566fe09c02b68a83ec1f803ed599579fe81101f20eb2ff85619aa3ac9602847dec310ca4012950020ccafe0baa5aed5aa560f85aa5f4dbc7be6a29405c3c38a51fc3ee91647e48697ce10af36a4c8917b7c2d494e85cf8dbb5cafe2556b85bada935efb79516001137cfdf4a727f89863b5d3726ce64e9987f842339085011c4ddb879a74829be6ee2cd75136f6eb2ab80435d57adb9695cf34e150606a68ae0c7d5b1b2e590fd2bcc16303031d973386a6a44d04cffd2527ea8ff82320205536b72f8c8dfd21e4d01e2a1aeefcc838a4b8fe41f6d62156e92b5a666ce276b6a6a4d29889a77ca51aa29d5e0d49ad2bd89c1bd71d3566bb2156ef3166fb27f8dec957e1b72a81b6008d551e13d67e5769ffc2a84300747724705280718778b32b945ef7994b2bef7e614891c4ea4427536a2840f621bbd626394001bad43875ef1bf09e8138a0e365e9d02c2faac8c283adc0e8571b7e8628c3d2f46291fec8631f6c49e39e183daf7b48e87b51bcc90fdce0b83bbc59eb84594c9c945a2a8361618d5d9d868d8d42954d184fe645964382f3b37cab2b87137b567dae074c4efafe14cf0a50a24cccc8890331a37f2f7d1c89d5ced79b5fedf7c4ca27cb024ca765045ada3a2a4a8544ca4542ad53b65342a26687e00d58c1e189d1524c04304aa199de13c522646c63346d6856c8b47f27756c8321ec7e2f4130240d4323156097850460993d174eda96978375a47b5e9151e6a1afa24816ae350cb7224d05be4d69175915521f3a21ec9fe9de1a4e1ba5e79efb2ccd02bd4b22c7841657c40201044394143e8e4e4a446ca2cc3b52125254533cd2085add42a0e15f58a23158b36a3f6f4f4bca669af26aaf2e66ee24cdedccd0d4ddedc0d01e600329c5526863bd5322c0ab2326c4c094cf6a767b8251ff5d818c00006e05433f454cf507baaab677093e8a14ded90fa9e946dc2a9f87f8a15626bd0247f1b839485b133f6091b93b2327dc6ddc1a9a832caea91fc79284a4f761a1a3034aa6a53a9a84fa857542e60778d13f20ea51cd9bfe6b00e27c3a93e549bc7c3ab363e3853c644ad2295ad3ea3a9c9c450311146364693b22eb891e36c8b5902939921cbc9feb48c27e3c9fe4d5170dfa9f8dbf0e0fabdeeeeee0763377c8ee62012e910d92086d2239552a5179bc81d9dc84d8425ca073355b60388bf57b21dbc2c6322b5039566639c4a194ec55be056aa19ac0bb2ff8679f4645738951d190e19b7e4a38c86871eee0ac785eb22fbbbe86037ec07e183df9a67184bef6534aa999a6ce98043299b41967224ff52b519e3369e62dc0d65d54343e3583ad7329cb328b02d703606d93f01756565a5c64dfe501489a20a5771900bc061a5d6a3875ef1875a047cf48393dcc15cd72b3770c00107ff1c2c7676cec142ad52c1c33bd523b50ad9ffa6ba9b6ac373339ce75c86f3640b4ef1bd98c33ef81ece81bdf90eb380a3260662046a7e8bbf9a92308f14e354329cee066439c8543e82a936590e5ccfcd52bde24fa2c966cd3bd15803078ce166aad410d8f21c8d45a2a6a1f63c474353cdb0a1e1c1031dd8fa399373ae0137f2ffc1f55824dc18b5a7033c7840089a6a43b345b5e141080fb8537645c6851323cb713d190e196ea6f2518693e1643819ce7b55c64d5ba6ca0eb04fb36460ee5653d9bf3ac0b658a8f5702a3d382cc454c1c2647f131e9c8aff0cc3dd329a8c262b92d57803e6ab86f848c6479ee1a0ac5078b27f454d0923619c1519163c4498e1645264469ae4eff28c9a953398bba1e44c1edc94d1381638ef94a5b2225654c0519346967aa74c95a5aacd2375517368dea9ea34c91f09176ee45f6f1ec9bff24851bd405371fab76a9345b9e638161b1ab58bba452a9bb8da64aacfd5e69ddc168ea50b776a47f2b76a179c9b63dcadf6f4bc5355d199ecb5a7d264ff24dcadf6e4da93fd250c0c4c4ddedc0d4ef698436405054ff658a4a724bba00f66ab098d540d9a509d8d0dc4130650cab12ce0e626c7ecef62d6830b7ae26f5af677d4cab255c30eddb01bd6c67ea0ef3077c30388a7abbb7bb8fb6f98e2b6c476c08f58045e68d6431231b931c5ee27a5945ad69443dea341800d09e5d4867810ca1a809c6379ef35d9bd6f1d56760f852452b994bbbf7d88f27b08ad8f607e3f03a2e8a8742c3786452c55cd08040004006315000028100c87034312c1aaea217d14800e7fa24e5e4c1789c35112e3300832ca186390318410638c880cd1903601550a0a63ebf11b3c5136b6d458e560247adc254c60a5d2a6c59de14c2dbc05e550ddc2c9fc0f8142d15e9b81021d175183199ea0522b322fff88990a7c9d9a8fc4ea568b1db21828927baa3e2ba450dfa8d65afde6a36f083103b51db1927f3a001f7f831cd5a622db0d600bde6c15c77ffa9db51b4936e6e82e78e5d52afa53cae412cd2954ab3644eae55b1bdf685d9fd7b8256d284f965597e7d16d4f87e3d183535b71b490a692f8f5896d7a418e824fe0d5b68886430bfa53c41ad602bdf49b61693903f29b963381bc1495fbf90d0ee78b82860141cf9a28d7d68367f081d5aed2994d57fabe781f679a7092e611b22a3bbba4223b5ae3b5cf10e246945c06afc29d66a3290303e488893daa8c5d56c56c82a177fe170f0be1d3d1fe680dec3ff428d2a03339fb1bf5d925f229706e284c73354607d0328bca57f8c6495a43e641a0ee81b222267e433d9c87d04d1ab93c0892269c2b56fe3467fc5bf78f4a24ee966a21fe7b76b44854f233df104fc0105e760d5478cf22f5f58823d811c70093a6967672deb538c047983e871a52b3962c50caa340776f01867e67b6fbdbc7b4c3300dfcf12d0c2b9f395f3923dfdc16cc98ae2a4749efc01bc502ff9b83ea3039b0c358abbe293b07bc15721d3f856ff06cf13537d520d6020e118e5b0897c850949d54d5604f23a5805e1481f553d46a9697e698c550370539251a9d6a6593c4d7943db2afdf9d1c896022afd4745ec145418004538ecc56051219021454931f158ca6584a356d07106008bb28396e847404e40cba0055262a1003a920b1c43e0ca3c259b0f8a26e80eedcac5c7060b82331bfd502c869e3d08338638d83764a3149b47e19d80427af4b45c93c01fb45061cb75c69d33d94bf993e670e64068ac5eae2642c2692efcb8dd99df46a0008b7493a82862cfb420dcbf72730cfb8558f94758c9a463c376f2c330fa2faa4a650154030434ceebcff194fc7807e6a5f34661f4a7dd6e04758e3fb8de432dc521f733f05d3bb2d2151101ad2f5a8fc28cb6336a1e05bfc14016b794a85623014dce9b6d02352ddd3d9d287b4da8e057a7e9bbd9fbeb761a9edb6e23fb40d933f213eece00bda355595d40a5e1b48ef13b9400041ea01c7f2a2870d46b6147f0c53d2811f20e65d84ad7eeee7f5193d320912294242e0a1966433461476d7ff64db4ea7e712e34bb950e4ae8743d6756687441ac7cf8ca6a00c6aacbc08668b27180aac6249676775a021e51d2c074bf81b3e0d4f023aae5924b484fcb5d443a219a05d2cbf9c18d18fde846854aa8746258b13f8f4cdf97f82b29f3af4f8196d0e8e77259541b551455b72a144e308bc4b0ca0851034d72661d69c6d716a1da998ddef0f44c01508403ebf768093838e246a59958c1af600e976d699213a48fa6967d77e48c5b2816400ea77369c2fbc7417e4b6e50a3567be62708b8d9d8b28745fee8cc03b559d6325f70c218ee3f432481be183222152f93497e01083cbcbd610efd7333fb2eebb9cd3fdad56901f07676d76269540eef07acd48bcfa069f121ed06bd307f51d907e42099d433b058ee2d0791a1c09d694ed494aceea396f1533f266ea86f592e7c9ebbca8626f1124ae0012d5766439ab9f903da9de5d816c5f0325cddd45788cc323cf23ed913aaa6cdbb714e5ac2016565e26346a8190e7cdece27c6c3b1e15612b7378ae997502b093d178d5054afdc7f51bca38b0b8b1911aabd1d0aceae62f5324a739c86101cabe4bfb97048303d2ba3466f60f1d3599098daefade3a58124ad6dadd38d7ae75401a53b3d1925e9e02ec6c947ebd492c83c14ae90710a20ad3b3a1460374c2091bd364f284af70292a8fac92e801b9f033e456dd83f88a7d9d85527f2e3d2ba2d30c44b1d6a742964ea0b2fa198e4e8fec98063bb0c7ee171b06626310ec22133dd1a0e2722279c3b7362fc33ef127229a5feda35f0e5f0eec0b193633cb87600267626f8a189ac2bcc7ec4b872b3daeb315f4472b6e05c9f9a051e15b284c36c402823a2c36158a80e1f8c52ab504c1c78d7446c57821b10e946e96bb6dcd62ba61598826bd25fe1f22b86c7913397706478535d3308ea8cfc46988b74a33f273e80bd2fc25d55f9f6978fa12da20072c1e5dd5dc1ed8f61e291660eb281cf12c73fb8afafc2d3523af18ab89cd2e367ea88fc00645840ee8768bf77a083f86a8a2eef6375f4a708d05117801fe0b66caa1655e94366f92094f99d35b28887c60d752fd15bf45ce8fbeaac846abfbba4369baf864024c3887b266274bb5961c62c295b94b427e63c2c900d6063439406470a63a3a80dd24a7577fa4142fc42204d8b1defe6f0f1a29a7bbfd70ba8cff1c99d0ab68d5fa7a241d12ef63375bb8b78391ceac4570886fe3af067c71b11310a528a8748a20978a6690136a4b6b62834d0026747e44b343a162441ee45daee8980b373361ad125fd3f3169ca9115d6de075c48e631c05821cd9f6ec90ba653fe8f4c2a5cc60d2ff027ad82055d0c0e0c9babd99da4e09d9160daa7befd8ca5b6e6391f37ef3d2d9c12ee1b714845a9804eb5c240ccdc4f3c2645a97b1395966f53b444840958e7ec474285369468cc58684a6c586441fabd41ee71d7377233f2087fc096aba4ecd4734e24c1560335a4a15d0f439010048db71095f6a433c0fb24cfb963ddafdffa2f36335d24c8ce46a281aa117249888396fddd2c51b3828455f92614291a5c865f387f25ac92f6e5b5626effebacbb8a72b12059072bae8380c7034d48ca137bb34fffe769061e932a4ff69e3bc71c37b1e61f9dec5b48fd8950749a9672bb93885f6dc5837363d7d09520e59d06d248dafc39eb57c36aff6582874906b2f1e392a290d2796ac9e1b6bad8b1ade3673d93318be00bd8de33db279219d1b5bc9850baa22f2e49b5124b570cddcab7706dfe56f95a9dbae29a750c971766ca3cd150c68773b087e6e042b53af27286e1fefed183802e64d93e48397aed97a29f6aa9b4ddb27e007c74840765801591aca80bc99ef4c4ffcf107a3aaad0dbc8b014ae1bb5f324c2ba484d99ce352066b8814d274115fe15397974c94539f6a8e07368daa5726c90255a54277ef8d13a0ff09281f1e9fc526252878502f4f0c9840a2b97d99e1dd4b6a1021c2db9e1adbc8c289eb1bda465d85d1ec22dd4bb6e5785d1aa23a6419713b621a33402f8435f1ce2f9e90af0509026dc889737e57b835ba58a631b09b43addb39c3b99ee8589dd5c92c9e8026818a5ebc685109303d1c8c053a411cbaa8294c55bc096e142c6f56d364eab0cd670d2e9e792795a66b58d172fbe400c555ffeb4fa7aac8672175e903929ec3fe30fa8c905cfbd3f783a7b68c6889fba317a6dd1fde1e1c3d388ff32a03405107f06192053d996bfb01cdf0ad83ec9167c2a5c3bf0320cf5082de5766e0b1846542c96e8930a32a56b527e36e38b0ebdde9961a36aff23bd1272f49d3233e7d0a19f092f8da7a6e5ca234eeb3a7def8e166fb71980684e5f555d7f2142302f88b8370ab1921eaefec52db15b8646075873cfcd7df0fa6ef614e616329d99db180e8acd5641aada04c40b73e973c06f60d484a85b62590c09d497ab9833c2a2297307569604ca6e3d3159b31093112891e1645e3cb499a179ecfbaf9108bf38d94f4eaf99d5c435ac9f5950c7a55328e41707092ff63f395102f7e817c84cf6f9374d5df0fe39c782d0d281c8df28b39fd5be9061b221d246f150013e402645130695b17218f5856880320ff8f297977a0d806c4a413655a47bb657e473ebaa4370cefc45a2769cd71cc5640c8a11c468bb505452af1e19a3cf60166e8f8cf49a9a96b7996239ff287fbafe2ae5687e0744b7aba2ed8254605543711876e689ff2c26a709605c6d28276d0db50aae75eca9ac9749641ef3e8e5c677a9ae67f20ebb0f458df8fef70b2ae4e902775d2b1ce241134c158caabc7ca8e693fa7d94aeeee97dbedcbccf5a43a527d4d374fec4ef22f29dcfbe1a7f672c102a8388f344fafc1e748e374f1747592372400a795cdcc41fb0610d76344a86c454f9c8cb1bf0f76c8e4184ea6d0d13d6c2b4793e76f881e7d1019339889339736cc900d0c2f720660f4f26e970ca00ca30460c4745584437012415ed004b4b44728cc363b7344e9a9e60f617d1f614419042029050eec15df3ada13299d952c2db00037375a78981ede333096e11eb5e40048f0127dc6e40a5e62dc91da15de46dac04c6bb5032b52e0323a323b0d4405bae98308b4cec54bc378d04d96dc1e51858b88954e388b128498a30a2b13db4cfa9c179a646febb1b0e0ddeb3b911718919c631629e921416fbe7c56046415c8c481ae2a9c6aa8811b91cc1bd44be608dbe3ccf0a3139b7ee752294e47ee708f534f6e6cec5acf4b4d0ae0f59906d2632eade5d413f3fa248642464b55ecbf1da455044221e2f783fc1604c14c33c67818262563c274661744e9d88d32882cdda856258cd3939b8006a164baceb5a523eeae37d50793d9eb35018095e14e2b53168cafd4c7abd1820e88b72d56675a983e7994acf47bf65aaac6a4a991cbac277c19daa17cd6f28c0464682d3ef1b8056c959658add0a609dff45d1fbc78d67f5d6da12973a3ba620681c704496b5c73a1d155986b9e7affc7d10f08994049376591d7cac83b541c6164d52aae3069f55da8405a26d536f35c8d559f44a061d24ce9c824225dca04cec667e5efad51ba35be76bd97b36ae15f1d2772b3d158c7943f329a567c2ebd9e78fab9dc4608136464b7a7f388dea4c6a566601eaa8051d3f9e0b6b60968016a42ea889473a8025131bde1a10cf433f2c0cbce4582acf417d8b59620ba46a5f99a10abd1e09f5dc7bcf0086888ed94852e56c7fa47c18b81f19fa5c40aef71a92a1331313e83dd6a05241569dcf09716bb88b83b0c8275c035014d268f080f27262373b4b52460a8ccae62fc1aa9728bd10de051e0d279d77826b643cbf4a84fe35532bf3ee319e06c55b9ab2ae9444f74e2835578ec7e1ba0e08a6b228d75463a6e36e7f99ce7893f33bc1701fab8af52a46c1c0720ffba6824cfed4bd1961e4d8c63d049908060dc63086f04828b93e0ff534858d4c46000309ab03a3f00b63cead4488fa5323b80713d8428019012c52b23ad3c7033b95b794f730d5e7a73be45992429d4160a5f16b117c217238404551f95985573a7e43af7a3a0e18a6d7640ef1550d027c328d886d440d8c340522e7293449f3b57d7eed3360ded9acc85e093fcd3b42ec87c94aa1c7736af5a41ba90d9624b827d0e48c0175bd8b40f23175bdbaf3b6181ba2ff5c220ae8f04fe527cbb6eaea4b57b5ae233971b410a09193555403bd6bfccdf15358cd6a017640b051d4e433adddfd9b2895f58a5ac31e4e2936ae6f58dc1e456ea37a1f98aba96073ae24f293e4d3f5c5d09d7ca6213c95f1f7e5078aa68888c86226964a5211477d7f22aac5c0bcd07910b128b6c5235d1b68f4566c87ab091e5da3613866d8a767b80badbcda85ac8cfc172fd7c23ed241d5f9e28a9f22dc99aa16cc88dcb2e720d1d4ac4a21a33921e21cb4eb7c8ae46ffac09ef8afe9ec45f1705b7164c9b215882a7adb0aef08a577393427dd286c29052ea1e03020408f4b405c7bc59818c3fb272a5200dabc6f531dfd6532e851796cb71b877ce24e297f25305c08205440531d67c579593cf35788908103fe220133ad25170d3865d614258856089b9c9df850796f992ead2f9e7e48971b25ac2decb84f55f22fd82af57b8a95372680d263fe37e83066a26ea0acd42d0f360a4becc8c2b20268bcdc556c29425523e8ebabff18a35f24de618578dcc057f17c04f07ffa643dd880dcc379eaee7035fd1ea3f684f3c1ccdac07ccacea83b770fc8eff932d3e3491dfbb086534dc2873f6a2b2f757ed6f60623f457660be29c0f58bbdd0f16332ae24bf34788dd4ff5138c7b50b8bb6859d71453fbdb70a44e0a0e01fc04f7fd4bce6f7872f35988f3785e64bd2eeb1a8c27f305b34a64ac91a4ecc4ed21b88fe72695adb2d85037686b6cd96b3907d00768faf918a75cb25015d27cfaeffa44be2126fe4353f41f3753082f1a69d6cea03b846483238d70321a72a275992341b01daa5c54be58f5dd230ddd55ab386df4c8a61168e60e8a7d1821a46f6c235d30094896930be1fb60bb5b9561e409098035981c4bca21666c6310ef34e5eef921b5548cf42bc49a7f542076c61a26d444db11904f5ba6edc501908fdaafa78282bf5b68ff72cf576c9fc873e001d80695e0f3727c80280eb01f7107ff8e8200a826500713b42007c09b2c2893b36c4d932887e89b002c054d29315845c9b4cc79bba3ec0f5a4b8ac1b2894de8c4376ef03c6c61bbe20beed5e873eed1a4621228dd9f2c0b6102073cd7b7964511bcd8d1f9622ae4e65dfcb6ea2f8338590e8e9dd8ce8b7f60b4b72bf9532f707c9d622df8e72073cea8642f6cfab6da17822c2dae80bfa786ccb2d0641b73bda239e869ca3299c878000133f27dc161387283b5fc2bd9392c64d41ef5493818918b66de7dbe66e574154149293d06a27afed24f4d087ca79414f5aa051f01202324c80430eb349713a2f1e5e3f3e42b1b718bf81bd632f0cbaa8e4e9c497a11898b571bca78d3f241104de6bf212a80cec901716869b2c0119c9437fa7c712d6e2e87d2acaaec3bdacd26a6d5a2560134ba49765a5572630a3c1b5a6af30ff292003358206b17d2b913e154202428922c945ae6ff389ac206aadc51779f79f2b37747ed5a81b573302478ea92dfb6c8a7830a33321cb4fbb3272063444b74716410c6e97341d0f03a36c2aa5b5a2911a68161a1b284076afaeebb08d382ecdea405df57f7356734012494b46d23026f71f13da4d990c7182d1c0e02ca9d3eacd8567e164e7eefb6a16e2a61e2ff336c7362172556ab53fa5fda00ed4be464c37ad1ad6f83e37f1e69a7d13b84491ad5174ca00772922659c10b91380904a830f1f6098757fe4563404f7cbaa57646b2a9e1c3f72175e5442c1b9553a0437d4c58d0a9985a42f6d525d8f2a4805c0c590cfb915ab025a1967ff87d2f769f0bccf53f4a27acb6bac28830011d3a5de140647d2f11d8a3c31163410fa4f78ea8082694f09854d4348ae5bd801dd34b58b484ec720af0492f6c42c64b865a541ae0db48f9b99351c30afed4a5166c2777268a067971548adc843cb6a1acefb518a33c100835665a8508d24408b1055a820b9509a539653798cff505dc32ae26d83220e2a14b466b4c0de9042ca7b9a4ad6e898fb364cac0c89a49af338e89bbaa6fc20891b3d9167f01cfda08307c7880e1297e7676b5c9af0ffbcfcae511098a10c634b2b92c9386fc8ad77df76f835c822ccc029d81d8eb367c010463192014cc77f820755812191cf8b45cbd062ecf439018980f10f46ce3595b929ce0d114b2cdb3d6d42b7c90d08ca172407037837068003d8af6313e7316be2c01d3a05134a0ead81c0b48a179484245cf6d32d36f14524d0947323701275cfe74923fe8904e6503b2bfe69aa90938cf355064a8dd7570cded7f2b0052cba4ea84c0f8d2c5502a305aeef057d4ed09f58dcc566a963d68cb1dfc5879b5d8d1d9cc2c8111a06412a72fa52bdbbda01a21934634590d497591a823042446e86041875b8ad68559a561d34b087e4291c90519a3403f5d08e01192c631bb126f2fc2a58482ee385a74ba9adcc780f72a20d7ffd9ad92aa611f4d9595024b6a586022a2477aeaba698350eb4068da7b462abbc80978c03385c789bb5c8ea0d2cd4d76c799784bdc40c7f395e6ca7964adc70e2cad3422a3e6230d8a9cd6a57c0700c014067aafec5bb5a49042f49ada7905c552b676940f59dec9bb0b56aaa705e83711764c9f77638ad06a5b128b52ee0fd1aa00d7c43fa4939de0504bb4261c1d1d8cca7d7dfeb90171a27bba22cf89a11ce54915f6aad59474c2901757a2ca913df433caab34ec4f99d79eb03f5116590e2609a70d690fc8d119b185845c8e429575013f2d813d68c85bd4d2347edd90c333fdebf1814e7bda62b95ed22f3a428d22a811e912926369722d5a93757ec8b542bb73a7a33f782fa5d7eb280f88cecf471e368be0f4a1f364a85e5aa277bc60c37b882c3cefa5fb331b95ac4bc311e6d8e9d92541b78fb9259d4261ff0c675abfe71aa4ceda71f12c1a72c482e5d15a239cb6b1465b4928222ffa67e527a489aaa012c99444ea0fee2bdfcdbc64d979ff201fc5647d50090073988663c497ed132c312caadf35161dc1bdba0a67c0e462eb1b392f4a0324bfb3957c137d0d7c1f7bd6edc4cc5024bec6f181499e371c0b6919aa33a67c56b6545c9bba57a3f1f0f14ae8412620483e4f1d618cf7b251d9af7d7b096a7be6e708a1f845fdb623ecea6809458139df07de64b39ee9853f8840d8dc22ad587bc606f44b501151d51773a86d5297e21945adef84cf4e965ac341ce3160e1cba7f88fcebb206109d057a396250fcb1ac876ba745cd0edb065d5a10f7a49adbdf942fee7dd446dfdfa77c5692b5df07348eae3b6c90d84d46aa655de4f1d4aea28ea97fdd539602c3e1975975023e3db39f169f00fc5cb06555b8a38631f2b38e417869d2e0319d05f3566fc0e8660c240844aa90b71db0a0e1d07866444a01eb40c080d8ef7f5ed1ea45edc81a6fc7f0d815e329aebe68d09e90fe36bed4972e49bfe121f65ee9dab44eb66b27cbd4350712e070e5f8db699697921de5db06c054faacaf16c957e04d1c6f13b5fe522197080fe6a1cba8720ff2590ac6ca6aea3e3a3f5e5fc1fda9eab33f26c0e0bf654ee278d79fcf483b768e3f3440d414f59096159c5a33ced914d2d25461aa73f65082d0c4665f36f85a428a11188a06f14c481ee1004e8b1901fc5698e672cb72ef61b5722e0030d51aa2fbcd1268ecbf5cab6b07ba627159a23be2c59e1f832cc89ed6ecbed591e5dcfe41abff612ccf8071af5407be508a61012b27df57a6322a06889c0d3ae0bf07453116713d2b00994686d57922c1b3a0d7ef330cc2ab9440e89cea124385950891c8a0fcc83019a5342e22a2ed9ac46baf76385dc51bd1f926c803504901c04a8c6119b0b376d50a7f08848d8b27def97275a52a1cd0caf568153f0b01ac8ddad4773529bd93031da71b778fca14fffca83ec6240dd9b5efbb1b2527b2dd4086e4d9f5b0f1a158038aee5a6d301c0205544db72f10a552f9986e96cb4ad3c680efa660815020b88cb7f399d66eec3b03d30a6f95b32701012434aaf4db64ac342da7be730560616129ea92bb90fbc0ac8edb80d81ab1d61aa2a5909060f26a1830f82ffe077c859b4604c516de4a11e7b063554aa21e317918c92cca10d53ca1976017f6768956c38589b9eb4d89c8f752dd0c0834f816158a60d5b24aee86d7198abc25ae2322b4f52c6c6cfc47d273ac8ff9c16209b84a0aa205fc9f0b21c6e8e95679aab576098729004bedd858c2a7b89e7d5a07fb9c5babbd0a8a03c495f8f7f088556309f3d0bd2a53e2bd400aa047551b48a38c28e50c3fde86696490b6a70c6c681cf5f8b73438b99d38a9a6650a318e5a38a9fac2ce0a01588de954378a3ddb6a43e0ac645cb0448880810840d882fafa01ccaaf43f423c00d358a30e7e0f9fb65d24f88afae05cd6413595d9e6610608f391423606ebe480b24e30978a1527d9bf196e0afc65a5452957e24f1a5bd67adbacc4a23ca234a76a2b1bdbd9036d5eaddfd6aa798dca9d61e3a04dc968b45aad4b4fc14bb31228a5b66f9c85a277405d81cda98eef3a3546613126e4fdf24fc9268b4a5e5f656550172f37ca88033455b6b91432ccb0695eba77080705eb5cdba178621a8e99fba9bf2e495c5afac3ec5f87c2a071642b569acd8282eaba6ffc9686af063496d2f9a449ba488a630ad981857698a89855185574bfddc145fdc83a902cb7804d92f761b4fea4bcf354a5fc2900ee24ad0d18edcc2459b25ee29175c7c3ce83385ff319d335b101a9f0ced15d79499c6804e4626e20d0384de3fb83088e0292fce342e294a4feeff24269514692110a1e76f2419df5d9e5c57ff41f0ab12933b8deeb860962d190b3f8d0156dad12764989d3a354b9327cc6256b44e5abcc8a936f8bd50fae072b7540d0ea068e4ac43409221544e760695cbd41d0a133bc3a0080de36254542b02c0254d8cb2ee5a2a255913c046e9f10883d1568263ed2db2108001d87bd007c7ddd861853912bd57dd043a29409d25d9420dfe48e5f4a76b73ac145195dad17a80eea86e8f0173a41eb41a8a78b22e6870d041c1e8d4b3278d5404d00afb35213790e1b1f01297ccebcd20653219c42830e2ad8f14af305d21d987af8db52cea96b65b3daed93ff1ced3171db5b05201b5d2684b22d4e8008c1077ec25e1b3476f27407fe4197011ca73e7cc01db54866d2cc89c4c1c6db5deba9009aa9ab9275140e9c57b87b11e75ec493f0577d28a15d752b76146a76f85976dab51689e9b3e97fb2f32cf7f27d6653a08c480388a092d635bb5a656bdbc64522c11bbe88b38ff971bec49fd601134358c0f6d68f18974e132cb1f7334896ed357dd48448084c57af40fbffb82cf8035b166c6d6ce672a0140ca6b256c6670785c9a7f50aa23783ee5deb9363ac23a736de5c9af07a9a0dccdc2a188a6af351cab4adbd7baf16019e516c576ce9870ed2be840ff4467f1b7c4be094290dfb425be3c0ef41bd02708a2fe5db4a2ba88a8a78a4d84e412625e0395a676851d409364b65d17dc5de922af68ec17828920b241dddadb823a3f33b467efb0a670e1c3d4340b494d648e21e1920fa5a15ca5ad4e45401b2afc78e683373fe81eb5fd9d8c34475b3953e643b8cf46e37c55f7b1f9c8a0f0caa95085fc0ace87de87f73bf9985e7ca75abd0a308891a3e1c31af9f1af1f304a4991709c983d9394cdbea5ff6a1e7f389dc09183473ee1f81dea46253f5e3d5d6ff1f2124eb07444ad71fd0a4cab526a2609f2b5f9677f0cad0e87dac8b3c22350a1086b7c0e60ab977046e14ce476b0b27794aa1ae2de0aebba134b52d749a971d259d048012869849635746b5d344665a7b7d18ee4877dda74ea2400310a19fd91a231d0d078d724b4860198f8e92575ab8eff8b1304e564225cbc314f20e69de0d5986d604d90b8919e789c756fd88bd006a8ed9e9089d98d5528784fe24fb0107ba57f7588609b86fb229d32fb3afa8fd660afb55b8111319623adaa9e0e1cfa2f8ecd34de6da57b3021fefb570cccf66c86dcf677b32fb5d33bab9c3f9ecbdd38607244325a9e1cf1b1515cfec0b532e1895954536967169d243133f4e2aaa3172f344fbc07f41a24b1e9ec20b3f3fe51376aeabf1c05316553680bfb59e0650f76bdbd5825a3c3854419d849dfddafa7db03e4a5c1ff46646121bc2b7c457011d98b334acbbc6e40e2d5ab7242f83d929c5dabe1ec9a2f1e234d88c5527530395377d73e3f01738f1d0f5e468122c06fa691f23e639721203b8a3c263b2a7ff5a64fb973d8001c57de736f788a5049a979350d59a25e0fc9d0ab5c60b86c58fe325fb73d095a8e9b9a1c1dda4e688960b6ce76cf0bd8a71626f420ce2df32fa94a7b3b06683e81eac58f4c9d9407f2f21f1ec3f728d148b04743783debf08dd30e917c470e9aabc8a2c831bc7fca0305a15bfa6ccb64f5580a586184a4287fef1df252abe8f34aefe1af1a91564100768cb725033097a3d86890f6cf6812f4ac1d630cd452e81f9f8124e84a2913804d6449099776658c99822d31d0893be7291a494d93516c6c7eb1bfe2cd6b328e6fe600290e82c65838314aa1da67375eba8817123a7ae22214f06cdc7f971c292dc589ac86000f006afe717dcaf22c6854c67e1eb9228a1243d5736424c610638c70435968a82e24b342c1d72a233888fb899e8402e4bd4d73280e097d1e86c38db23e5847acbc2cf5817317ba4b5970d996dfb5084ccc80abb3cb4607891e24ead6d9085a73aac043f0f1fec5804d1894da6b0c852bc3a3ea7bcb71d62ae35dc34ac5a8c8a19ab6256e70dc1738502191c13152d7be44f09b2f5cef582c059267fab3b57faad75e1faff2aeef8fa4e1835343abdf7393b6e6c3a56b650e2be9a25046fdc72a311e32bc135a9585904ae8a812f42399e0454af1cf787c67aa3039df49d1ce5df328240e0a07616fe793075b0b19fcd897a31650465c9ea00292615fbbed245fc813eac17e2d17e21919b052c658b54d98eaa7844c44569d16cb1efe85c066097e0837607604927f86e02cecae50a073195a1c36b1b8af1d1ae46ef1b842c97c0ac68ab018ede528ef483af937ded88bd5a99f9e1108b1f83c34503fd701e124791972b27359b2c9369549ad8030673e41db480a258b4f5aba9bcbe03eebe49899eae214567b403551c7da0a97010e792d468639b933ef147cad907cc8e2c1c44820224e0c90e39574b806fe800627f06a2808261ce7fa617b4284efa3fed86016c3f99f47854eb40274f8e3029219d808f24a124077c63506752acd7f6912a5e9cf01cfb66319d3dd53c6ebc2bbe7f5a2d4f1c46c0cf1bd9c7a449559fb51c9fe2544fa7144e24937dc0512e23e4a1afc271533779ffde9f33449014e1c4221140091d419470319e317a757dd8184412e9ef25f19ce6b42c1d29f74f00fc92ddc3def651fd3ded3f22973af58b14d6faaeeacdb58c414d4ff79bb2ffece335265004e80d32442fc6b1f4ec22a11af44ebda13b89a0ed90c54b82653866afe3e3a0e73ff291c8b619bba61b20a529d4a5aabdabfc944499b04030b36ccdde3e68403ad20c48623b5f8f501f67dc1dd07e5ca3a4a7765564a8e548bcfe3ffa44362c783e6495997a9dc4ca6c424c3f636231a07b77c3c4858c63884bf4c80dcfa7c9754e9505925bc9ae5959dd969f2ab3a8cad830077cea057fd5f7deade00da1d4cc7da0a3aafc0ea73e53091f3b52ded9ead984530cdd36e5881421889a1f8dec7101ade58d38db713ca41f06295eb198feb2f0a29edfcd0a6f8d90935b10ddc495b7834e34c8fc20aad6d03d4fca07941b75f011690bbd07ad2b72df8ef7d2c0857a4861025a291deb8b06dc04121bcfacf40d53ecf954815dd2081082b530ec4d5702ba4a421c85d49e0c3997e07ef3ebbd01295a22b6faa4256723bbb547026b47fb20858306e9ab5b817b98548d8706150a9aedd0b091a8fb5a08a23724dae1f19f6b0563afbf957c05eb6f8af099509b97915fc9207099f479d1da169f705f12475cbc74c8dde15897edfcea8c93923f2f5780589057d8941312127c14bfb929b25ff3014165858e3820d1c1a7683068002c232999eaab68cc44c48c0e2167c0da9a5c584ced6e60914ee7c92b1a3a7bf691cedc8667694416f06342f0e363791ee6d2c9e679c2f4f2b116cac0e72cfb76ff2e1b5bf8b460c9936083e3c6da8544d77b7db3150ee630bfc949f9c440883363ef64e002a01b42f5a687fc6c891ebe23e4778f42da1b12ed7fcc6d0f9009b859f56c2ad2adad0d4a3f9cd912bbe7e641f39f16a1992e2db01c3764e71d6c00d6b6a2af17840d8618cbc5c43290f8059f4bc61ac2a262dd7e242658cbe8d359755d724555d88a68ddd4604d474cfc4933875760419e6fd0b204401cfbbb526b9008727ed857c38ca7f8c53b84f39b7a09f903c51cafc56fce5c20d450e7881d08dd2bca9b97b7b1055665e00e4a2af40c47fbf119320ad633eba9607732f55a426cb53015087b64c176ab4dc939a2c393711eb7acde9415ee6d993ff005cbc857d82c0a86a73256388803034fda4cda008ec080409b969f39acc9df9e86322c2587a1794b40efcb806054032a3d2383082db21a9c244740972562a4eda95c961f8e3017c121b580b9395d6e524f9487d8eb4889b944ecc91c46214aeae5293ccca3018f527ac4239481f8fb170154faea4a30f82a1e60098cca61da6e4705a1469563ac0ebcec221149ec3dcdb0436186b4ceb992c2b923788613f851a5ce82ad4bd8adeeaabb269a57888ede02f75cfb212a79893d90461919db217e348ce268f79c33df9bbe91d23e73d6cdf54d7b6ed950edd46ce1eadbef53d0b07fe282c99060d80fba09050512c5fb53ebb036e679fae18dc4fce2d27bb0b4852f2947ec094790dadea92ba68a0cd61b9aaa2fce22681b5b80cc4b6fb1461f4363a80a1fb7230ca62ffac67188df4e929c37e2349d0c5caedbc3698919bc3e59dfd1be6f9dff24f906cff24d182fe9ed72a3e832123bcb9e0027fc325cf16bf138041c58aada3341079d2f1ae08f73d965fba648c1e50e6a0d2f71118723546f741c25e1b856e5a0b819b165ab6969a648652ea7d72acf9e0b6e3642560a4ed7a2be6bcfe7483b08dfc3441ceda168f12e1947940aa82e871e041c919f3e6d23e70d1873ba5e2f7949a052f0a64b18e841761239dde672bbc44286e4fb620f49308c45bbd812fd1195f32b8744934e7d6f0294cdb005a8c1efe1c2201f511160df9acfa05530c3263bf804454cdcf13338fffd5ce5124ba1d6e3ffad0b9425b20162d1ac16d8572a1b48ae0bcf45a272a4e9f59b302a2cd9652a046862713d5452a0d1bfa3558e860a38deb51dacfac3d39cbd56798f41107b13d65189011416455fe97d25d0508c4001ce15b085d9d3f1f94d18c815b6a0352022ab233c8a86e3978aea8ee8335272ddeeea5eaef974bcbedeb54cbdb0b2802d043500429ee8a0578ed4e924015d2206cb354eb4a787e4f629d37a64538c88773ef404b401a7172618b0d6fb307b1838f110ac0ff6ef91ca7cbf1dee2dbde356cd8094b151cbeecfcf8293ee0531c0e31117348657d63ed9647a1a8025a08704f8a6d31eefaf871310349605ac77236d1f2dec5b68f6def558771a525f6e5b2513268cb07f706955e90b0bcd4ae31b2449b3cba411ad4cdfe04ce86a9eb4a03f6a81153d393101de381f61a30015f8bf8bdff6bf3cf56aacb141d4c899d4f0e58c2b193e6c3b07bdb4329d019890358ca4dd88984a2325a20856ad24de5b05e8d12a14a084bc865e52dc4251001bc94218668c4140df7090d2a66e58a4d9ed1c2b79db070ab32c04e86ddb449ea003696514e56b3c29b5ef2f0e783647ccfae5d3ceec18cb44b6a65d3ecfed3820ccce13a50efc8c24a19008c55e8ce2fd2fd433598cd1027cf41846d85981e7a292796c1dc0a90e9ee00d4f4c06143173fe94dbcb48bfb94837439bca8ff51217c9c04383ccc5ba79f4ae888abb6ba2a180280608162567180498510f7822261be7476150324c034224874bb017b1c703b6058b171a6089b732c69248161754b4a2c6d4f149045f542885869513e5044eb654635a4a7c6e4d34fd849912c20ce660167386336faba12230364ddf4c42236876a6435df3004794301210285b1c6f42c8d8fad642fad173abca6862536b8961294947dd69eb5d3a45968cf4294e7bb188defddf162d11305246a9a5ae92f1430a36d94f0f4746774335685512f42391edec633b49725053c6292f5905869539f7ee8ec4879562ef8d7535b92dbe1572d047e88ea3c24e93b941ebe4b67decbf1b001e074a954f17da0f76ade028610fee6a4150c45a43c36bea3720b69091494f643fa2936e8d403aa634dfcd708d242c4e08cf8b4927b67ebd0477301ce1846748a44866a87accbd664e378018ec226261151d1fe7f05bb5e65bc3231c4d7dc3c66b99bf142de7249027a56e74f278b5309475dc9b93afce576f9a5e0e54c789c78bc9dbf5849f79993ba31f4c8b33c81658b600cdfdc93339ddb0c17da6473648a04387b4ca47bc28f3a8927e17f73668bbc1a9969d81ab38a44d3e38e0c2059dd03040c9dcf40dfc6ba05b458879b780f77562543df1b5d03b9ee79d56f3cbd3e00b0d5b042cebe96d9da6b8e91a3ba44e7eecbf4ecab443a6cbda4539787154651b64b8f6fa7500bf01727d90cf9914b11ade7fce00faf9b1b0c51d5893c5b51971b96bb4fe2ee5af64af474e03668f52b60f2159d62c8b69bfe257961540be877841013ae355128343674f0c6ef5b8ec313dd6e9a5e784829f9d739bf4584a0ed26323c368f5ce62100f2f80d6982048f1b2c9b05d34a1cd04daa8284a2e54c0c0ae0f4d13f8f5885ff95306678b7381339e07c3b2a3754344854e1a4ad10c681436aae6f93378ad1364c31551631d6a05f80934834e42529d26e9a4d1b5081a14fa86e4b44c81f4cc8e501c613b3448474417013a77890447e50004ab21aeb00f08afdca560836180294f5f4e10e4c7c76afc10d232245f13f27324bd8355112f153032c91505a04cbbf0e25093a22e879459a7be0d91928fd5f30784b63e017d9f223be508efcf9ad495c04b2b81b7ce557d99f0200b5d2ed1d2df49d6f5b40587a925450cdfad83a994c3a53407a1b39ce78b7cf3e9565749efe729f7d122106d9d8b122eb40349d69a97aa4c1e9161adc06c55b1c386d2e32eaa1231d012d72717904243a4494aa4c7923dd90ae54183427d0c8336935b4f0d2ae34500b5706e780f7a6cfc6b2cff8b78019d3e234f07fd2853c32287010101834aa8eb337b793aae36491d7983c004e14d3476d1a815fc57d2125c34dc906381ee46a5f606ebf6669b7b53d63f280c6f389c766ba975120449f45b91c96f521f8a33cd1a0a7083bc1425c916e3f6e38d0338900312d8647cf850fb171989278955687848e26b3644c8e01d7f3ce2b57b1054d73d857f8651c464a80be4ac9e2c64fe801585819999ce30470004ae477b3c5b497565494e6d1b9a331f1dab912d3440be70b842ef748d82da82436368975900bda4f375dc260513faeec7e522499aad2e5a5468936d366b69cb2061b89799a1a0ecd854115db110b0553fcb7223907deadfbe3fa495536ff9b409b7e727988407dc5fbc48f381b23c61dd3a9d3f94008e314d383f2a65a5f141c4d4327c5b9abfaa2d7e333706865a32114ba18902b337c2d8e2ef9155722095ab97568cd2eacc6f20ea3df0b0f83595e48f8621f4157d85a20ba5de6925e0fa7af8dec1465d2d14e375b171e8dfd560cb2851191120d8945db724678af72240940190c456bbe19b27357a53aaece03a04c28bb2a2c33c855dbd6898b4a98e30cb1667af8e97eed227897eccfb24a4daaf0aba555a1276a9f3041eee874670217af516e46a8c65698cb36c673127991d6ed0fc10b5a84ad013118a74ec2e6f3941fdb9edfd34993347dcbf3aa56a86641375085574d4f5f40b080e57cd497e7096e303ce91f27b7e1b3afe6a8ab71cf383851aa5173bc1f6c9b7c5b3f0b1e2c44720fd27178101b0337b96884cfdae8fe2118c3b788990f5176065bad46fb4396c9e5073f21d084a0d12c540775a6ae2ced9454a1f756de1ed4abc73bae35edf147db9465f41ca2693811b1e1b290d209b2d61d32ea9c308aedd2129e892c6c320a93c0f7ea5fbd69505f6590e1911ea333ab01a447a1e2ddd8bbc9532fca7c4f54d3155ddf5a30c58ce0b1a2eb1c9ff921699e0bafa684c9c4577382f000f24be24f4ba42264da384c4eeadc6418d8762db1eec4658f34157ea68b021c58dd5e6441e47d1dff20f3ce8f530f607adc03830dc02213ae8466fcd6e86a3c25fddaeab4788ee81c4f1804877a5213c20bab0b1aa5ff07f91cacb2d3782587dcda4660bbcca74b2fce783d936351a744a42595db7c3481c70140f3df58d8730730fd24b4188c49c6add4a0ca8f938df6c373034d3918f910494ad4d2fb6f1f91fbe7afa42125557ff69943e8da58928d23286a655e804cd2f3654f72d99d72ddc9c24741072c13194d205120b866f11f43688cfbd9416deed365a5459790ea264e565ccfccd2dac91a74e07ca035dd63b50f3d30139fc4998315db31e101b99bf15304e9cedbfbda7bdd4c332eeef086d049f9d3a7db1b9ce10d36f062d4f219cf24596dc9756d0ed53548e54342444e2068a6cf434e72e729a3349c914059b3e1e5940c032c19739109f7490e881c8c172742f6c29204574d5377afa8ab78a2e0b7b2efd5e238e787ea083bbec3ee422ad2329a144bafa1c4db08c58d50db784a60d909fad01c44d5720c643fb9e5117e8dbfdaece606a064d6e48151aaa46ecd9d9424ca30d1d8db3b31fe14933b561b023c4d260813b7908d84448d3c643f5015ef13a3f5725c02c1617c02d296c8f98de03e46e8642ca949e12ccf5628446c49e334011340c0a967312f5921738bfe8a97db9448e1eef622e61a8d9c189ba2cb73dd0a8d4fd903880c31b0b11e113eefb52099f3bcf6580e43e59b576b0391212615fc2f2bf5e0ef87809937366e8f18e34dd723102e849056a34eb853d81826c95adc36a97ee4fde741d92afd6dce02784531a078c74e31397626b327f508628e96c50241acce48aa3ecc7e889be6606816935b8790d39506ee063fbecae3426e0d6d7e8109fb5bc4d696ae40233df243dd5143308292235ab44365939c3f3198827cec29be66586f2fdf7933ba3f8e4bed63e1426cf9f3d6a3b6006b502155462f49c3af4fb82a77a8df6a3fcee8553400697f024f4e509bf21a66270f5c3b88b12b2a61c875f5084e7ed8dcd152b4523f07887be1f5d8930bd1e16d2338911f3750f9ce27b981990f02dead8817e0f01103629742c5caffdda5b1358ea4b7190f9339eb20a7a1809e6b53d584b3608b7fce16922e612db8686720a32892561b0c0557cf34a968486805c390adcd7c21606977fa707e6ca9ed679e960f2861c4d3ac3773a7fad103fab749bb54143951f55c4ffad37f7739b5f46a3b15dac2b8f9d562f436d5e8e938ca5766241b11d8c56cdbcb04718ed77535e70081b4e4cccb8f69df171c1b662f9b904561005bbd38a00077b98a1c4f92fca857db690588273131580df439502b5b4c97621c5fc67564ed83222c2ed1240a3d478453d44588aa5ee0411fd31c03dbb82079cbee6d44514086af17364a19e2a5f32e2a890fa93aaa9ae375b95970a44f5b74925afced4b322faeabd92354fabfde7c1667882e6acf6f3742de8b0aeaa5774b19be99dadd5ae80a05009ae12a10e05bf9910f2bbe69b1a72660805b6fb67be952544eec6a29b2365c298f8f16f78d75093e6ad5fe11a314bf0e5235ce93eb10245a301de58856bc9b1c4f0d5dd4068036f4daf3dc480922d2a56af5708dd2b3402d0a10c8ec40402aca421e15837f23bdbc0259f3d0e73916391b94a1b83839de1c7e546725bc1ea4bd764d00a4f69a17bd8a5b352da96d94185ab36a6fc5764b5892bf05d0aa7a4d03f52c85090506aa2630429808edecb46af7d16b697647959e3038bd7f83853291a7906744ef2ba4dd0bdbc75f3adfbc808defb69c309809bf4975560ed294aded7464aa87661d1b400e5c91fa7a3850616d999e5f850298fe77ea86f313be60e21328d7fea55006927c37501272516c7607a3f03e1d50c8e29bb853fbec9eb0f512bc36a3fc08f0317505bdac15d890e84df1229ce8a2a84363f18671e2637bdc028e0f56e5224e57d22d5e1d267093040fb6c901fa67b4bc34f8cd9c0eeb3c8a7f8440c0051d2ed83ee86185ac1cc83bb889d0ef93492f9f1a5350db5daa7cc060ca4b30ed24946451fbb8e7bb855f7e4b9ccd601b54cdda7eaa70f822c085f9fc4d33008f60d2a07030ba2fbca5bd492d856d779d2ec66507d4507f940aa77c1b6a9061393bc0da9e34183e4dc50daf39bddc1906c6fda77193ead2aaa327b12a68b9aca31a7e9b70149ae247cd2a9439b5bb0942360edce080c05985e975b4e984be51053ef057339090c98bf64e18b5cbdc4eb81b0eafad265e555a889897808ce86587c8a72899b0d0d808ab0de29a9c22ab85354e73c41427064619eb4d90318bcd2b56d8c15d1ab8bcc71f789afbabbfcecab33eda2bfa700903ccbf314cc3bdb6a649291da01d982bdc81d35929508ef000f77121166100ffb941d45906247c098655e42db2e7fab9d80fc02c37a455aa82504ee5897c9fa4f9778680580543fa0f1b915b2d8126923b3326b7cc4fa7fd9e20e045c3ede01a8bfef67d67eaf88de6d69f23bf5ce4dd74fc108d13aae1897bab584198d02ec822143acdff531d1b597002251899e4fa4ebe9b62b2d8fc468d3d118d54ab40bbb8bf47081c85da71489a628eeca70d46168777babe0340d408aac45951f3f814678e29947b5033897356a2b8af6f07bffd63b92fec8e1ac24f5e07c19ca1fd8d2fe5e8234606eb5971869880fc269b501ced7eccc62203318df921650666f73631fd1ef80d1e3882b417b371860eb021c74fd01aaa9754b06c87f0c7cabb30e3f8540db6e877d3f4d809f41c49a11b8cdb8b54112a9a685b47d2ee6ade7a3d42efe973917174a54a71d60010633e45c3fc23749a001c50efd8e2aa6974ee69901421a1287427b20518e0a091622f50b1cc29cea7dc93301fd4d65e9d7061caf37718081000042c02b19a56ccbd8f305d4916a985c7964e49f2caf680d2966a4ebe094b04b52c0f457e16a5f7a67164a8c2d561be91d7a3b36251a519994dc2505ff601dfa3e12223cf3a0378f133fdc69dcab752f27106f9f688e475b4ee01de8c661839ef9b7cc10032e2426f31e70490a8659397fa7ec5d909106bd153fc6d773602a4ef9f9bc99472d8cd3b7181bdb2d41eb6f21e543790c1708599082e0c8412f26692ad76dd81f0c7bdea184fef1e3aa2bf0ff5b6c82793ae7143c8433e0f2ba3ae2d12c41b30a68737aa82e1fdc9c3798f2cd4dddc88bf7ec7a2acb921fa6ff0993ea99c51a4f29bc3fc07bf3b7cc72213a783a5704cd4a85bfcf7a9ad884ccc04983fd6e32532aac4c5200d6468fdac754eea595eb0dd22048e5caca86cbd89c2bac73c0dfb97af8609e9f47ab85363e0d608a3348159dd116faf283efa09eaa0c51cf64d6c4392a4717c3a93d260166da45667226ed14ab39dbcb5cd755ac0e9379f1d31bb19cbe6366186e9c18643628bbf18526416f02e0df09c9c3c8a100d63c15f2a35741b27c674f62f2032dec7b0191e270525fbb67903b50631f6a79c9d4173eb9249052f8a8babbada6b279ac3c75af10f8365b77d10cd294ebef3ca440b03536a61f39c635073201df4f47f0685d9bc59291612056c6c020c960f06d55ed3a7817f4779d07993754e6c15dca7917873f6a4d7411c0a0a53df512a6176bba203d97aba7998d28155098afd5b89467e7cff48616040e9506e73a7329c7d2aa0e52581f3aed2cd2f7f074a9f83f4413b3c61245fc9adb8ee9bb8ce8984407f0968f71a940264a89526610a77e6c482f32e08c20b3bb005c4ad984a163c7e93c2ce8cc1c9b5c4a6371fe9929ce5dc44289049420cb41698b2b130526e3ad10620d4676dff25c2330af58b61a761eb54061e33c57fda5eb7c269cca98f759f5fc6137f27e96784b75c19bbca41817ed27052cb9a4cf4618191d624b23923e5d4ba47cfebde88b9393dab59497df61f37c1ea4ab1494e3bdd02a80a4e44bb64c6825bab4017c3b48b71e8bc3c30168131fdebd155f9ea91fb71b9a6e099567ef3f8c57eb044b63420be6cb57f6d76671de0cc025107a5e5dce2102437a2b73e4c00bfa9457887f65767b2c782915900c6a75866bb62e975d1f05d4686d13dc7caa4014c60d1d3382ebbc19354a8531b20f4795541c18684501346ef2f6b7c4498a574b00a32737487dbf4ee52cc1544266d6571c3f3c982dca6e470ed63586c82adc7c827bf324def039ead8905190ec1a0b74a96e27ebafda75527864ca58ab1610248fb77bb83f8e43a5106e91ce9e3e6a15f552f82af31b7bddb44f5170f1adab163b6f9b039472b53b636ca03035b5ede36f868cef2b0da8d0c4097ed053c777693cb6341821261789ecd6f7fa64101785e51650cd452bd38f2132eefb420e6cee9490134036ba300f3b1add0309a1a7ee17d15791b94805c93fc4b44ad411353c81e7a7436292843d9919cd099ae731d733b34e7bc2ef7eb91c1510b6edcd6d00b382a2438fb764e6db069c41f6298ef5c6911b8a2b3e4cedcfafed3b047878659dd4ffc9a302003c6147e88d64eb625d4e7d7f2b0fc60d5d66040cd9452889aa84557ece261f3c32fd6c0d356e8a16933057cc2eaeb555cfea8dc216b68f908d144f1d391d5ca986efe2367654f98f01d57dd068476962893f253c6e973a51c88313e71df9758078b84dfa43d606fc65f679595807f6fdbee0680114240f39184d73fc397b75c7156f9ae1dada949d85accd62367c9d57010d5c6ec4befc434343a434d6d96621cfa0baf9cca492c162119491fb92df7fb4a00ded1ad705db48a9e5edf477018c08457ace2bcad990612fa5c84119822287928929021c0bc08e3e92d5815b1e11128e24e083e1c710274b8ec9cf18a4c0689b7c4c5ac0021819dfca523d00888f8f4bd596217a257138eb607e93ab28c9316724400e761240c8c2f6696b181afe11592272a03005cfb87b56e85895daac0a1f6305d38ee632ca6456cb1f4bd0e38c534760508b42c9d27b102682562a81596f4bc341ad908784db2af391c26ce449ba9a375586b594503f760d283cb20593102ed980e0bcd8cf1c35ebed375982ccb310b6c57140b6e7925d009b25b1f275e553ab1f854fd96a37ed67ab8285a79de1791a9ef5910370598ca5429f702a89b948e47f3692ae03611a8c9df3efbc204d40ab78db94c445d6eaf02378cea3654bab7a4e76d5a1f93eff7355bef8733740970b5c48516aa6b62ca177cd47b5f8e221c4fdd28373b6fc4ec53532811d29814df600434c5a2d07539add105ddb03b4f6ac6d9c7be770d5835fe93d30596951e1f5f7fb9cb7767c188c6c8486904adc4605006c92f3753c9dedbdc8b0fe80b62d0bd5db06d54d6d28f5e26b6819149cae8dca0539a1e5f3d116771cf792f80a1abf7b7fb4e6d0a7dfdaa94a03571482ec0941f0d5148377bf12aa849bb35a4ca8f0920665f7162e1691243702b218224af21fa5115b556e05370aa63addb0122fea20ae30b63067d2ac55f04264f89bce60f9b855c6ed378d7d71f602a7335763e78ef57e0390e2750005b8b8fc15c0dc7ddd06dedc9704a1a9f0fcad1535394a619aef55df9fb38eecc14d944ba2485ed145f5845b08257532e83850700a3ab856879eca459543808bce59a2cc5d9f544ba212ae421c7d57d92b24222688a592de217161ce6fc59ce2fd6fdfa0fa41ea38ff2773d42e874f117808a33fe38f91dd5e98be78c37f08a6fb7da44cdd1f78db62c620aaa3e87847b1786ba1b97d07cd06b7510e83f0a62a9d440b4b6e8d4da548e038a87ef92c2d5bc4552c4586a9ff813c06eaca3b01c16b65b12892fbdd6db638110c7a7c56c10880b22fc7b5fa0507c006fbf243535f8224e25963a14412ef65aa5d7962ba994a228714d37496eec68fd1f0b2ede6d47f22fe394ca04d11fae70c41ae87abec319cd93222f9e52e92c764a5a2539ad81f633c795af71101d54de6e23b8d9c4d748d5a69cdc20e153bf07b11e35cea8ed1244e382ed25246e4d0c9108f5c930c3fc1d6f8ac0205663a1107a62d2c3461e698c24fcd12fc089289b4b74089f47ad06cd9844ac75865572c8950bd4a437b23c12d866fc537316646c07397611ca1b59ae47838dbb3e8ba848f2575f449aa90289451fcd145d7d368565bfc3cb2a8a30f78d0223c9e2d157b62b546070197f00143c8c0dc04814065cb1ce1c5f0744780416c8eb7bf51023470dfd080b0dd45b7c01d0592e4690f8ba4a052562bfc7948e8f95b61ba731a36b53e881174e607026a42972ad29e46fa85a17671db363eaab05ee3a9f4142f2773309a7f3f87e94caaa6962bef4584639463cbe3daae03dd6eb32cb718e4298973e7c69c523ebfa9112ec6586a478666c995d90d91b1e8c22884d8c5b68be5c9f859dc926d41e4c0198927552b8972eca437a50d46246870326c51b610935ce97a985faac86c5b9779d4bbc99dc7d97f0e11b5471a66e513fca0cd1e76daf3850c09ae6d8ff8ec7866424a3d4e83e69ff37c5635e3dea7b456df9450846945cf1a8116b4932111057f64fb000bc9704f240abe0316aaeed6921bd67fc5179483b810645a24b3e66556664aea7bb1600750637c908b45428f3c2fd2e09757ab2d45b19e28c0d5afc63bd820ed28758a0030f950e783c1c30cf5f5ee68197d071f11f7dac155d4e8bbfb00b6510b7ee0f54d421757a0a8fec5ecaccc4d0576df2195197ab03c0e6e3391d03c1c61ef598cd2e7b3f7097c18e83190227f4930511e4385067471cbef80d6284c8ed411e55be5d934fc6847c8638d88885d9a348e0eb0cc14ef7c40c36812f8f4e86d6d5712791d14ce61a89fa48db48b4cd7bc92d71f24211aa1c6abbca8f2003d753bde1dcb1c066f737a07780ddf9503f182e3ddc478cc0f6acb0e040cdb8d00059044e80a83ff4d7a41dedd29cdca54b5558c67b80bdc1d6da67b7ee907994fa5340a3b2ab8a3446eb5f5137995aa35cd733ad6ba20fe273ef8a6c38d08ae92f857fcc0c518b563ae8045dee7a3f8efd08a398747f0d5df86bc3672f2b4a7f10ff3a0302249183d7f964c9285c34c000719a2b1d605c80dddd54d0bb1a59f769beb4f92aa7e4b04354be3b33d0e25fb82c16fa6f6b33018eb041b71604d735b1001a7d006b8ddabd801401b77e3544d357fac3e56d3cd700957308aba5c7acc8c90d89ad904e91c6aa4360fd37ca1cdbe263e582b553fb6005115b60980f64b43ff27fa8bdd82a5ec621b098d051fcb7cd5fc3d0ca0d8421f29b01e5aaf97bd75c569c9233caf362a4fce14224f2267d7b002fb95a3554ae91e2aafbbf1afd787564789c4d23b797b15de96687a85d274bde6d1c31f0fbb480f84624142d52d1cf07497a55fe9b6d1539a3e37e599428de6b58965caffdb9a440b2fa23adfcca26a1c14ffe1cc71dd02a2e006f997c81195214a0dc934b6cd01b6c40314e95b84df75eb392a2cbec1fb45ab8467e93daa1a61b29cf31e6e8bf075e362809a7d58554f5d6a77a1e5bdfe6d7674d784f6ccd79d94b821791c34646453089057a2ed9949f1f73b812919abad431d515fd73c81562de26b420ca769f3f382fabaa981336431e68cb7e12b222815b1ec2862a26ff9453b4991e8ebbb13be1ef00fc3db0910cbe91484bc8660b8c004b09558eeebfe9bf346d3c436d42689f8130b74a0551b561c83af991593b96ae4d64ea6f140d69d2d9c690f3dd2f88b00f0d0687b26274c9461588cb711a143426301b0218eebbff0c79097c19ef66d1ce17b8827048536d157b061fac4619df23c89d4400858e120ee636df8a731652206ded5f9c73eb7df16b7227ade4986b69f18cbadb475bd75300df0ed6ff1e020fe0c5565e9e6ed1a8478dca97d75774bba1fef3131034e76205b13a05c4f63cd7f650422e23d5de66f7dd3ff5d40f45c3c20debb6ed86e99c78fa08d82b90ad6594b30adb29799cc578fe1a65e9eb2d024c70741e4ae64a967393e58c2ac9293b51a59f00cacb3fef355885c4079d390e10129b14ba8a52d843556a92491fefc9b24d5cf9ad8d6d92a24f64763e884c3b2412e384d66778a67d8db87d2c279dca58963ea0f585a35f64fa43e80268ff743583f872d608ae93d0b095a5209a69a4a898f36d9a403b0bee9f304ab4b52ec1ece8014c2ea0bac1f91abf62b79aea7d66f4a963ada601cff8309d6f888dcd2e71214ea0c162ed583c2bbd7c5807fdb450bb339ce4add147dd0027e9071a7ff1901eefea221e418773bdd0700a400aedb46371bb65f488280542d62b6e7b2008e6bb6d1a70371f5363ed43fcab43f1addfc920c68d7e9e62e65157d32464a217a7f0609c747dda21f0aa7bbb36f52cf5b699d329d4dd9bcfb92ac526db93d3da9f4e9387b88e10b9943f4c54636efc6aefe2194d932dd8eebcefd4e4c8170dace3a6e37de3251356a615765a9fce92a5b08bc0978b88882e9c5cacc63c7c900d85004094c04dbffe56e1fee5af85cb6a1452e606c0e4131df0f561496b0bf445bfe85e3fe937eb5554b76a91505021a7086304232d98679a6a5810eb59275c9002c454b7338735aa5983d8d42c0044420e292e331d408c1990b774e59c9195a00cfedcc9277d21393d7c5e7c256f64e8fcb00f4d955ed47bd933b9ab04eda1fdad5c45da018417bf215807ea8f66475dffe21f483219afb36bf5632867fb8b49601087487314e3426b1b7eda0a96cdb6d990a59a256bd770673f58140dec8e1e2c943ffa3e588221f7648548b8a37b7d9c38996f0a966f5d162a0af4efec559743b0c5cb1ce7206f48870b469a1c87032cb6e41c16e2449337448d0ed8b4d4f6a0e2dd2ab1405e595857a0807c645c3311ce9fd2acffc0adff7414a1779420d992253cfaf2f6dc0ebaab04995f32bdaf36642b862c4442157394240bfb2ce27a0fa6412874496cf4a476d0284d8c4b354dda80e058668fb5c6b403797e12eb8a0bc30f1e7534024df480b0c7fc7114b58e0594a8f1fbc8b79d60ff5d289d88d561a75cf1d4ba03bf7f76795437e6214e367e782b4e32e7219a9ce9c4d40852aa0f9202a967a178ea9c90e920de8833faa4f30bd78ff9f45cd89d6439003d08dcad3199276552e68c6595e6d6fd3b5076ddcffc3ce5a8bb1a81c3650e19f9216e0669ec152aedf32935411040422b78087216becffe59559473f5a6f0b28190566b5de45040894c16d42814f56d6390c351b2d5aedadfd5799b1165e98542dd78f4237ec23ae04b0aff931409c7899c594754985d1286c201f0d24b20369999867bd9c6b3eb0e3f043b937e7078c90816dd08d43b06a613aa563c84cb434de08d375adfe29aacb15ce9cb7b7e61cde0a9753dd7890d4ea344fbe2799d93c7911f26a18f0833d069e7cb6083390915c338220cf7e4382f8f0a698059515a0ea974e5e6c5c5b137380a5260b22724d225ec8b182294f51ef3f3565c0b004ccf9a3aa038ce90243bc37e791b7c26a9d27e59731711cc7d61a998af40bfd716821f5cb99d58589e416ee83a40898be007147ba2fd5753775722d7cea5c52b1c6fbb3f841cfc18571717c82d08d1bdffd25339ab01b74482ff5d3b404ba71d7ed81c635764ed018dcef6601de71fa57c2dce960dca26fadb2b6058c8cb606480504417e85d73428ae0a363630099923f7851c9445b6587f3d0a211d420c2e0d01cb9f95420b890364525b06572b5430c3f8e3c345119cc64216635c05b04a7b7476bf124e3d8518da58d5144ecf6e3b851cc84c59fd2303fbbb4543bf4478b37e112dccd81344a53959a6a5b0dac1d1c21cb9c167e51ca4051dac1d83601e9116c817acf26b1a74dba7ad335592923c4ee201fb0cfd8509bb8bcc93ae6edf92bf1850113fbd2d152dac7f4ec265a4f80eba8a11e447348d9f0883fef7b10cf8a7f31c1ce8f0595cc49abec929c9cf26575be2a949ec823b6188d391555060a581de66b48be9c344d24afe2e4ace98de28f0b36625b3b02b1b8f65b9de432d6fea81f0934a89b8a567acc35da64079fa980d04c283c0fb5939f226d972be632eb5c002a11459e0a1b8183fac1c8d01d3081b2f6050e4313f97d006fcd97190c186b7236f88d636109f72a616d825b860c9df247ba9f850657eac5dafb875206dccc3337134184edb0f8c3a8689c4a909487498e2db3758992031a83818dfa3c9a4d39566a0f15b721d234cc518e738254e640f40cf9327413877e92fabca0ddc883878f521456f5ee651b3695db63657a1a258fa1b322dc874e84df666cb4499057da7da0cfa80774c2f8a67c629bea69ed9522ee5d95b605cd8164feb106dc84896ad97626d079a38b4f6ad98f65c401026036db0d4f40eb4c2c20555bcd52947999a34358950d6883cca7c25d138140ea731287e631546ab02536d60a56abefa1af3da300542a864544543e22b4faefc071f6bc194afaceedfda136b0a764c9a610066035ded639112f2d2bb9f04870d5df834879f0e9ee58ea65f6f9fc452ec9dd17ca0691c8fe160443baca22e08805b930d330792339a33dcbf4831e8e28f618bf43376ccaa67e05fe4691a50177b2c9a163780e29b28f6a62ac2fd4f8f0a094aae588a6d3db3204aa7f97c2b5d832a989412fea6049d368b8a18d8991d1e978e0203921e5d91669ad2252513a9ce830ceaf3644daa365c4a48b4e011c3f4abbd121d936bbc6a7ec75c5d5e1f3ce8643bc694e5695e9ace154b09383dc3312b63bf34526fb8561fd3282c7761d7a6360b2dc1ebda2eb8af67bee160a80e83a28c21d0315745d23c9e56efcbe9dbb0cdba5cc58108d7891d65e5166911b36db0e8e26d6d703db2ebf132e7c6398eb4dccc886e015519735e3f6554ecd2c2b87390a52812c7a117afdfdf1ab7439b88de32d2c3182c5b98246435fb4dd7412742824dcd7f452e5e8d9d2a28f34a9dbcd98a578df6127c846b2f65625a1364543e03fad0a2915b20d2aa9a0eea21e9a7e9c9437d75c9f43b2b8c6ddf00c3be60db5ea03d1bd0d1ffe9985f3c16d10fb8d0ba39c8599530188343a0088039080a764f31afc0502231a3ac6502751c69b80f3d341093400cd9f6deddb6dc5b4a99a40cfb0843094d09a59934a3b02973a486acc2941912cb5e4fb0e9aebbf7520a0e2921bd40e9ddbe52a9de17688f3b66608631cd9846434a43ae10b99c48df257e9d66217d36d78eb767f3883c5babad3585ca240559bcf1af84f3095e2073e38b49f5224c5c285d05b346ae4e5db579ce8b44eed74bb78d725cad35f0a68e325b4ce7fcb64b290f94f5b4c71d3d3079b4cfcc55cb42182940a6daac69d5d60c5dc5705191d1b7c332e88486d0738b9b1c9c651aab2d1a98dc98bcc569c81ed6160d266f5151bf093dfb4d8b9b7c822a3aaa107af615f2b842cb4863a691c2f2dd5ed68081f24061305872ae18e684c2c1714040a506fa412e294c45dde4d3a5a2a36a48e8f36fa8154d6ee03279e8d3d5de6326019365e710067f54e770676bed30c618c77c404040990251200a040494c799ba721d49f2366b9657b2e26b2df8813fb0832090fe4b2b2ed5f0baf5baceebbcaef33a9fa0b59d3de2dbda8a7f66452e2ed8b3875974f9b0bd61e4f97824421fc8cc9ed7d2d2d2e1c0d1384e2814cafecc8a44ece5187aa9a31990bddc416548c0d6d9b4e7652c2edebcdbbace7a80f2680ad4590efb26f67b7513e95f713e9dd4d6f02660b6668fa63f9447bf7c394339ecb9d7e0f781df07360866a73dbc16dae30379b4884e9e0e3d9d565ef6829f6fcf2b61ae237aeab60889ba27c2573b8c45f84271d6d5969d8a4a60a42b8f5794213f760e8f7407fc41614db6c32891e787401426030a0483c95262b2aa834017088adfc6f2bae0291016699de94dc5cd9c0ee3cecbf1e5c078cc01e29c479c22aeec368d818030ce228f357199f6758d0edb6bcaac0021115191cda2d08c8eaad4626f002d09028acd12649f8e8961524eee98a191095d59bb4c66b19ddb4be64d1de5b5b15913809fb35b382f2f5087fd52e9e1ba8ef36b7483a25ca06e511d786e4ca859338afaf9a48c9fee30e7d3acf2a6f149ee4fb3a810e5388ea34cf27c5246d063f047750a9e1080b732dc92f2cd3bc039f69283f1725417479022d7fe66b754579517e8c5dc6475986ee9f4d18e9f163de2935b3a7886f4c43f6a3e414af2b4455dcdd50b7497348b46516bad0d6eb55225a5afc90b54025252bdbcb95b25a7abe69c1b7193eddfd306ba35dcf88a3a6f0baea50e6fb3aedab6eb34ddaad56d20c0820674befaacd5bed05842f3b5e341230a1a49be9c543ed7fae544fa3a6b5f7dbb969c6f7c751a7c15faf2b3d65aeac2e29c2bfc7454c2798e263187e76d1192834f9373d257ad6244330d5961054af3b4871d794c1eed08e0318bac0832b71832df98b5073c2cf07cfb9066cd273d807dfbcda492b1e4392484eccb49fb21cd9a4342c4befd66a8c7cab7b7d0ada902766e54e15c573dd93750c58c2052b0032b49e0f0012d88807005126e4c5106193558a203116eac51e382e6871a6a58c306178071c485c7d7ccc1918b73caf90d8e016a2e3c048faa94cb2f0bb2a7263086d1174e809048f1821c28dc6081cf154e54e144e782736b1192438f0bb54502268e5822894f114540e16a7fa1b6a618b19e9a123990e2072870750fce5841932c41b0000535573b0bb5f5802334257cd828438c335ced0dc600a4adc593368452ecf1785347e9c2d92d3bc08604000983928530c0a68fcb67d6f4e6009bdcb9b79075ce32e4055698d01ab2bad5be98b95981ca28177a38d2a4900bc7175d83d62a0f1506726e6b85551e688df6a8a30de55179a02d034c6a437b7416186498336f993279cab4cb907ada439c3c5ce8aa47f0f2ace9ee13969c73c0bcd8fc0de94103c3cd90bed263b21a4b9eb386e54ace4dcb3d0537adb668ee9a86c7def9576df50f74d54726d91d7d3510b513967cbfa43edb56390e4ba65fe6ee8425735ee60f6ca0267b499e356185d59ae5cd9ae9443657ed5c7c3b4c36c4944d598dd2ad212ea92a994ae3baa45bf43557ed38280b35d1b75328e6a443935a61836fcf49723969305872e77da5c9f65a93ed7d0335ab366b2a6cca10f560b6ea91493433b35a89624c4c4b4b596118fc51c73a43924b2a1b5257ed18e70e8a97c56eab1deb6794b73aab5c11549b6e5aadb57620908fe33eae54e2b812c8711cd76d75c89c467312953cd35639ce34660fc92cdf8eeb91d9a2afba9a3513fb905b305f43d8e02cf5a5d3ac4b29a5f947c7e59438aec4711cc7715cb7d5284ffdd2e8ebf5ba3d2f7cb455fbf259b161cdd33e802ab40b7450a4b52e72e8d9cb0aabb50c86beb2a7e48e762aabd67a1ea574c96c328bb7a7ae88c210c0eeef0fb9725d47bdd477c292ed9734848ffacc1abaa45b15c94fb7ea4eafba473d3279e83065da4b5a04eda9add0dbe91041cdea3c7b9b1c7b496348dcd13a1e61d468ca84e20c3d1c71f28f2120ed825a09c0078a2d6b56ee3526cb01936cb769b2b6c8ba6a87117259926b6b48ce9da51d3059fdc624dbc921b54589a0ab3792749666d1164c18bc1b491a9bb424bec759db3925a1530a0bdd0345eacad9f388637270c4b9513c69c355c4f2b898709ee3f2e8072ac9e0486575d5d424d22291ba84516b5ad368cd452857efa0c865851139d141427bd0df1167adb0cf2897ddd3765aa3579a95d42c7002b3ca5f1c39a5ae2ba5be54d7a5c00e04479c222ed06dbaabb00aeb3a10db9eed92f1f365857d7be56956e9b2a4c2c6a0f2ed75a72b925bc7f24e79ea2e31af1910848b4e8ef2d3888d66664a7c84695e68a2d9f3680dc376be791e4a54b9380ecf719d4455c9539ee3f2e8b77c9bbeac40a6395b1568aeda4d36db4ebc41398e110787a3bc24de9c3ce5e1106992d82fd54d8ce3f0989126a93ac7e1311ed3a53c85a3031dd3522ea9944b0a87bf7c2ea9148eb0f3ae34e1a7cc74a1386f114bef4db8288c951506a5ef244e170a372e2a7a691c63a478523d34ad6975d575d58e71766fda0d0a35e260276800e12ad22a78030d1793eb39aeb28fbe6766305936d2b7abec1109294669e7b41b4b8fe32eb839253bf0c7e6f796393edf015ce6f8bca50187e3a86e507e72d478438e2be5274f35cadb5d9cad5e62825ed27cfb63fc51eb3052320e118706335e3fb87038468a9d3cc64f9ee38a719b46a91ce5392e2f25f64b6c585da544d54d089ef210c67ea93a4f7908ae125539c686a94655e7218c0d14eb4615da15e331a9b1abf47c3b9864725cc348f84ad7b4dac2451d16350d17e1225cd4b49898944b18b9c448014de4f27474b2029ed07fa2019d6893888a262d70011752c2308253830cd090adc0481218a722668d1d4f594c990602fe8966257f509c68cdba4038f9a9418b2f4f53befdbb924bd469863a6ad645cd5a44d26f9051b4971e54116a3646d6f9123543a1cac8e589f6edaa92cf2adf79899a9191b1572f4fb413ad5946db4b4f6db9d05e7a5e7a664a503293d343922a888c729881149d06e4a0c88c1d98fce0c46872918509d80e9808c20e761080d012284ecca0066690e16adfc008a8687cdc237249538215207ed0e4061cda202287066ce8240105610732a25cd1254a93ed2fb0da72096a97a4da7249d2ab76a117d8cfb78b0bd0926f7799d5564bad2529c35c66efc2f32e48efd2e3285a6d69000d29c4509286ce14535ced28a3da9a64a0710592a428888c40c2d58e9ad5969743166d60d1e44a95295ceda8a2da7ac9e2c91533700276d4c4d5b56e750b7d857685234e11d7565457bd7d34d40c35a3b4f6e5342a7a23a146faa911bdd1926fdf62546a5564df1bac379ede7a3e8a25a1f67503d8f0bcdcf1d5bd22eb4186b4f002119a2cb3e8c84a2dc95e1a9b6695b4071f34f7de3bd382c018a4c9ea764abe5ece14a500f332dc3186eb3110c14f270fea44526fab154ff0d78bfc15fb6dfdae2843573a6d2882fa779cb6368b8e3a9aac3d2ae1bc66fcf1dc2be69c3e9d07dc751d16b3ed9ae5d66ab7766b55766bedb8060302091f66ca34b9e34adfa49ba5599d63dc39f538f007376932f54d249beca7e3ecfc4319e1ba71064bf62f6fedce36d8addda49244099165660f19f0db5973536fbde4f17d45259f7bf4bbe2fd09a270fa3a5e985953b2b604d6ca7d30df16fc71af2c354bb5babab126afec865cbdbcb11b345e59ad72b64ac9e5adcd9aeedb6164f4e28b74ade0f156b935b2ca97d823c9da0a47901b4b8c3d3b96398f73aeea58a666df315094917cbf84a1a212cfada8e41b61eef2c60bda11b3269802cc57af220651b0ff358971b546b7f6b456abd57ae2e91adcc19b3a4a116c95611f300f37a33416e39935adf3aa97e36854d08474a2a0af66b56c6846a3b5900e05da6572b076c5308b9016164d99500bba6a0f8fa80871200b67612ca4855d737d2be78a816e806c985c969d08baca63cc8d2472f3d0fa55573ded3365fa05d2c0ae816cd4c006853dedd13e4c1e6d43a780896ceae621d638c87ce8576de11fc01a97847d7878203c8cb550cbb89a894aa147980a6a34654c6e1a7b565748d568d674a53d3c9d512fa8113da24474367b6ad42ccea8bd982cce8b49766d71475d783159731a35abb3988d03babadc6c07df5eb92326aaea138b205093b3864820f60e3b677a7d7b19267d87b56fff74c86508fb2f89cd8e5f0b6a16064b0e7940a3da0a837a4584140a1d997af87e4d3a446760acae643e4c196e6892dc8ce362302a72307315f994547199bcc407e8c1b5c964325918d40a68f74c9f92968e75926ec16c5a1751fc40e1c229c259e171652f42a5ccc0651a710ed0830ba788025c7964a29a01a7c9f62224b84c2393249dd71899864d422721949fa9afe2f4b1e2c4e2c32c86260b94c16c16186b92569341948f352b8432c976da64c30d23925c45b6249a7031515d9faf4ac13af5af3660e12ac322537659b3c05a935c1893e48e9a6ce76675055e69a1e7c2982c3a34c9763a6b72565b944b1ac2f8c8244d381406554c1184abc816c4192ed388536dc0c255e4002ed3c8a4a4cfdd6f10d3b2d1910af3746de47d2823b9fbb28ddaa8bbd081f572661bc3181b79d6555d6d230804c6be6bb515ca6a3896a47b2af56369ac7561ac7f4ce3ac2b6e2c679684b124b90c7fc29f6695606c0950b3300c480369b3068c4d992c660b4ca55a5a4090863292c32fdbe8268fb39a6e52b5ba82412e537fc1d81d4fa06e5b47c5397d4ec58dfd6ad8fdc9feafce8a28cae0a20d23d4b841490d252a88e20b1da09001c6c018180363a5ea7d1e481d046ad6e645b119105723e3cbd5532f575f72b55a1b5cd2acb99ef2e13b1f9918d7f06d2dadd59669b3e16745cc33653ed38791342b0c6ab2a7b64259afba47b8c4e411ca00a7c016914b90464dbe421e675dad00d2ea6a457b5a2693856d376bb7cd640237b762ff662dadb64cf98a9f0b08a45fde89760d61ecb5c46cdc243bf90ea1fce4da5445617447457d7e4522521e6befb5559c569c7f4ffdea9e59334423518fc8742c41588c232a22734523376bd23b23972d9b7d7b376901082b9b860494ef219c5cb1df244ed786d43218b8c4531f24c01e90e7b99a15a7b59eea4319c9fd650800930861167da48befe6098fbe1df76b49a65f72351734d064fbd1db57a160552f786054599fde18b99c480f438dabd11ae56aa9cf21820822881fca48a65fe2113e2b765fc539c28779b00fb3152e1136c12d0867d8278cb92097200da481461f0c60ccc865cb664dbfbac96c750d73d5de2f59bf5ad6afd6f1a18ce4ed4bccf31d066de309d4af873191c8fdf05392cb901b552cb4d064bb18b95ffd1a4718610493c964aad52361baa66bba4a1c9f00285820b9a8fd328c89600dfc66b9e4c0dab74a85ec2d7e81ec2d638b5fc0e42aead3e5d2ac3aaac8dfd1a418b9243397c48933224d5db5b7889788ec5f86b11c246c58015210106644c1c57d924483da186540d9c1de0bd6c0a4dacad5346bcb140685316e0dae66aaa67e35a9230218afda6a195dad5182b55ab3c0da24c3b065dfaf30366b6ad2ac31e1db9dab85b3dad2401a65eca00a10627c5185ab3d2caa2d11f0200935b428638a14495ced61acb6661a660881c3133b58624705aef650565b393a3c3471c58a28a624f9e0aa4e7e3e0982d0f787b230d6acf924083cdf1e068542fdd481f4539bfe326cd62ca2665d9e3016c6b6ad0ec064c2af29d31ec6688f3ae21ab09190092e89ab99b89a170b1a229761acf6ed2f45cd0a639d08476067600d6fea284b5ded76b73107e5711b871ed29600ece602b0630ecaa239688f92959295ef1cd55edadd62521eb4da9ad982b658b407a63c58b42501ba6ac741ee1697e67d1e1680833ca534b6eddb3f2172e9cf79d70d51fa6abd591c5dda35ba578b1b864f8e01c8b4d65a6b39a794bb2623723d80cd9d9932ed02a0ad494b56688f9f3cda11405743687cfbf7825cce7c0309c0dba4297324d36865ce976a0098e1313b4feaa594c8b3c954adb595ea1328c7d5add6ad2649d53af3c486f4c224aac0907b452594bae848ce23162eaec0648d30c9767f01454ea48ff2086bdd7a60d1f40893d583eca1f3ed9d03a5354c1b1848d40f01f75e3085fa9b97d0828e923827894ba5b1c4f9ad1b679aac439aac10f03c6e0a1df88e59b3e159537f3a9d3d68288f1a1f34d65a6badcccb079eba8de1ba4c1e7deac0538bfa2e9f5adbe2f20d029849b697441d1cb26bce0394217c7b6a26a09de67af599f67a695028bd0db731cfb48460a65935371d9560dc1fcf6dce888b2a4ded37a5bd83ecee16fba94827692f9db586b6be7d591ba6f818b1e082079a357588e88e512d0b85511e7b1fd0ac12432008d19ca92d6e7c40af46a2194f8b5c4ea2b7004c15b99c4644e5e4c2470b2e5860d64c0ec9878d0f1b1f363e6c7cd8709fcfd6c56b0769adb578ae287e11ccf04d73406b92c2b683cf5f6f7102f6af5bb7d65a1decbdd65a6b5ba5bb713e3d95a3d4d9d7ebbb59cebc98613cf0c140319119b02d95be1ff45c75ab3461dacb224f6967b947f952308559e56ff73d81b5b42b1b3498dbb5f6de7b2f4d29964b9aaf3d4c1925f24d90660de961d6cccd49b372e480d1d1d3ed6cd9b9ea12e729ed9725828724677c04b99959a890b32834a379742d4eb3745e334bcea9943b49ced0d0e049d655d5412ee795af4e24c70c3c1787b6f75a6badede922c0f96e246b376bedb66d9b03a1bfb5f015eb43a05775b375925c09befd3ebd7533c145a15f316c4e047f9d3232f46f5d9f063b6b87b459d5afd85f42b3ec5816f9fe719bda69438cedf6039b23870e1d3b76d84e0b3b06945b032956b0029f2d7ca04031c288241f9f258898620a15a800a9c806403d37f8f9e40d27a57e15fd1c7ac9e6153f9fb871e5b79e7dcdb2e29c732269b2b2384fe5986996aa2ba159aacf5d47b31aa8c9eabda457753af5a9ad3c561518f443140484c51152a07091c384e808932a72a8fde8f089b8494cc1284247e868288b170fa0624889297670c30a52d0b6d44f504528698284152e5e52c2c8010c0d2dacc0a684d18959843922cf1004276651807145195b60c08a23689094648ba3345eafe75a4865ddaaecd8414dca9c04bdc0c54f114984b4f899a24212120e51dae8c10e0ff501fa9ae3440e6e0061891a134d7270d5793483286e70c60d4e9ad0c0559dfa54a740d54f69e492fa80b25ca6907cb5dfd45a71135bc8521f0ae4437dbe67608740ecb003cdc94f1451dc7043d1a402d35d7fd87bb78de330b64107542a79def7a5f294012d883deb85489e7ad8f3f62fcf94b93fe4eaa69af429f047f5172ab9ba4d6d697181d1c9752c6fd0cb0bf803460603fe08ead3899e6ea84261fbeafd752c2df02f2f3030a7ef14c28de559839d289f6f904790229766a55993f53b261513138303758a898179898989718969c93131311d1e4038a70c8d40e96d9943fe15e7260ee08dee6dd60bd9316a723ead298d3685ca119526e70bd9a9d2e4fc16b273848dba35a7d076a208d969c942832297f84b45df25a18c71c6251a87830b5c20bbd5d65a6dbd8287e7565b6bb5f58a348870c289277e9a6862b3d75a7b2d14182c76abadb5da7a45104124113d71a30ac6b5d54960accff7576b0aac734e18582a36494552452edb22e1f16956ac59b37954d767f36c364dd63c797c1aa8796a93a9a47c2bcc5bd1d22a442dad6eedb64d3b7bd5d6a713238802996f9d7a5fb368ab52a8f36bb77f4d4efb5121da63d974ce3961ac9d75d2a693ce39e79c3de91df25a30d9f2205b213b9bcfb7f29353373caa5028b79caf42768484ec0859099a142c89b07e1b85ec083539ff85bafa0bd921da36213b33213bf613b243d442ddca2f6447d62cfaf385ec04352be7e73728642768b608d98965213bb23a0ad9096a723e6eb2c5270b61ad12f672dcbdd75e252ec7711cbef7de6bedbdf6de59ef66afb5f65a282f78818f1524f6c91957fab3a9211d24f6fb524dd28f87ef4ba54a224d93f2e64052d93afe9ab5b92d6a564e49a3fc7c7286ce57cf3f9f98a1c60b513002d6c26c5bea090fa8fc7c6245e7aba77e3e310307efd5fa51dc644e0adc1b6fbce1c6dbed0a6f35239a7123d33ae29f75912c68c9cf2c3fe9eb270d020f39a7d29ecd6bedbdf7de20bdaace44be9e05857ec97411fad149de13dc09d40aa260bf699d1fcf86c31118dfeecdd03889439c2d4c451d247c7b0932b525ce55af707c7bf5491b72d3acca4dc974c4d57d1e5923dade6b7dd0586bed84f9721695397e8e414e50e4fe786c143edbdaeeeeb67392d429b501c9e9e7500d60fc89f6f4fb49a86514116a66c3307453b630ec6a719bdafa9ea0abef8939fb9ef8ae9e3dc7e5326bb25d6090cb971e15ade28dc9f389ca4d8b879e459ceca6d39426a7eac6e4d9e439ae50c4c1a3aa65c469f1d0735c988bec1205764ae52d72e992f42dc93fbd6ac738e754eaf4f3ed519a04714c9e3d1471426f21c333726d71c0066d5cc103273c24e16a4701d5161983264682d861688731787a92bae5e2dff72f587279a2b98828a3eeaff435812a32ed901dc5c3d3ac59afdabfb17427496e3cd168be44cd682cf80335439991cbd3cfc988d311b4853ac5e034835314272aa8972729ffa3809f433524f1a8d9acd9be1de5c4ebbeee07e7289eda3ae19c3aa1789ea4a869d3de155144a82968b585a282aeda5168248d51fb02a9a2b6381da1a4187de3a02297a79fd3cf09a8190073970baae8db1b4504a266386ec8feb30a2657be7ce961225bbfde813fae53f1d2925ada42bd9ffb710bc04492d74f133cc0a2c634c30c272e2882054348bcb02168fc3954030cf320972f3d9e911e9e1f4a5c1c460a6d05b838154578f19e8b789ad2ab761771ba4eb49b447614adb626304405ac87a12a8ae041c011471c91c4133db08183674fb413ed4588971ed48c04d8ac61bd635af63faa2d2ca402222fdea0420b255ced1f526d45000b268cd0b0c832032caef68ff619a19cccd656b4d15050bec77233426551b72254172f41bcf46c52b6a2b96ac75072398d5868a159163b10f21c7a259d8ab8d5d61008351b7a193d6a565b275aaf868cc87e3e71634a007e3e09c2947fe9a92d14112a8acfb70fe9e80c19216a32e50bf093880837be8a2aa27c899acd2aaad0f99e555041848a127e34140a89565ba822d44743cd3e1aeae8a385236a7691c8e587a2d5f0a5e745881721e817271aed614797d9e4515b2ccc6fc318e3cdd6264f7d2b1a828684d16f47a6ad6893b249a15b7c0fed61c7136df228aaad8f3657f544a32010fa5dd775a1db74b8d52671284e9745cd9a65d3f301282212d2700972f17149f2d2f3e2f3d2e302d482f42df9be5fcf7b86f992bcf82169b23f5a094a2e51331afd2a0245a1f2fd7c1315356b0acae8f3396ab2fd03819c68b3863af552cd2a4bf0413353c2ad755f20f8bcc53fbd63becc181bbda546bdb2d64b49b3a6ddda99c7638399e52dadd4c65beff5d6522f4b43bcf592cfacb96e6bd8addfa49ba5698f2a15beaf1d07f46f2cf14df87968d42c2ad627a59796d467f694ca681015a2945ea3661d592b6ffdce2aad59d7e8da60b2ae1793b46e2d85f2d62f259bdc31530206af17b5da4af9356a96fd98a4bede165574dcbe9675b70fa68c750f7fdd7d5dfadafb5a84792a96403a821f4dcaf2369c6178af4c19eb4d7bf4262df5f92810e8265f21f46fa4e1f3d04d230d4c6e3e0f41d0a70bc459c44d5a183272796b37a9aeac874d5a1f64fafd9f835eae7e356b6e0fa68c75eb0498fd47de5a1bde7a59f3f6076fbd44c0db9a8e5bfbe8ad519fa5daac29f9584add9bb457756f08b49bc4637d4bb2e44beaf3f3d65a246faddd80defa6dea40cd2a75d01f6b5d87e783db44f3a68ed28bfdadb40576cf7da1cffca1b8c1eaeaba093c8f3fe6a7b77d52e6e49cfed66dddcf8dc69abc5eb222dbd4edd52ccad32cea33bf4b7e7d4b82677b09f39e9739369d5edd66953a0e08f975dc5ebdc1f2f5ba796309f3e5f62ab7d7f64241be138a0ea6bcb77d9c38bf8a1749bc569aacdd2b4d5e07c59bd4abeb5e0863d2bdf2d73dd8aca17e3da0bfb4baf20963eefc3df28efcf578fe7a3d4bfc6dbfd7b757e73ba0e7b8369f2a840efa4de8e088c3843a0ee8db8dc943d398e3dac692436a96956eddcd716d81be95b30b1df466a9ec37a73121136026f0407bd0df4ce0f9099e108017017d03a959b56e4dd7bd59fefaa51d35eb22f50d63b2ee1993bc7e2f17bf8da5ede2372f67177fbd45ebf3a5b1a489914d6ede899b77cfb8a9ba426a169de4e677738ffb363143790f7f9b08f34dbcd77d40ef95be4d7c51c17bde478b7a60e567911a41fc26aae8589dc7b3defb72582203bfed0895399483ce6fa308707e04a827927f135dd830f29ee9dbc40fcb7bf9db4417315ec5f92cf24116cfc13c76cfe5dbc45215cf79f3f34a1dcc0cc376f0a9388b7cf0c473b0bf8eb71d20dbdf1ec74da7c9fb6af2821e7a19334407f4d04da899d70413fefab4e99c4486a961caccd69c36fcf5988f89f91ecbed8796b1dc72f81bf3f73565aee724726ef27a08b6c47249fec6b9f7e2807ef3526cd6a0fe6e0078a3ebdbdd5eddca41db60489ae5b32d69d67c82c5ca1664693f6cafcec7074e5c36625f6e3f7f4f5f6e407fb7d8df4df6d731b77db2128606358b0e7dffe44b4ab489d4bdd84b1ad385bd13b9eef088937f0cc1144fd7e694767d6e6f8d7e5ea09fd5a7c8fd047f0cd169af2ebb8d2a153ac7be42e778a481c94d75ec34b477e30a3454c763ae2bead345034da5c1a6aeaee31667a5a92dd54de7ed9d4d6da96eb057c7358ba9babace5d57711c4db74488d9408085356a7fefbdf77ab72fb7d9ab63c79d5d1dcdb2de7e3d555bd5af4f1db7b67562a38f9b05f3d3a558eb59cb591aa4c9cd4b56d85417606816b59c7835f082079a657d069c4ea46972ebcacc897753596fcf71a9eca852a17afb4df51e719858c769afe37853f2ea75c4a95e1af34c4dd5cde6a51c17756b4b983bee68721bcdc841ea86b7dedc8559b3fde62f7cb9c17e1b715d6d1e820c7299fa0dabf078423f76a15b395e9bbf40e4ae11fb6d07921a1bfaf2c3af34bed8d2907d49757efb5a5d25edf9cd317777346b236736df9cecf86df642766a64aab6aa6fbef1706c785347d9b12f79e1110aab6fd814a3ee9ed251f9ead76dc1f33986f166d9ff7cc7d73eff7dffa5fed3f11ff9dfcc7f34ff79edba7dbeafbb1d88fdf6aae77cdd496269779fabbaae9df6746b4ea1959cfa2c21daf992cf284deb29e19a1a4c4a6a20f9920a7dc94b3afb122dfa12a57dc9bbfe442147a6b0ca56e24b548a2f7ddfa84ac1fee740ec7f34d9e97bf79b81f634f975e8585461cf79557bcef778fa217fdff4e99f6e85fe754f931f92263fb7e91eb292f646f29f7bf8131b87227df5eaf30bfee811a7c9cf37f0c70c4d7e5e82227fde6e3fa742cd9adff96753b1383bb1a442ff79fb306b3affbc7fa03db07f6ec5ef73dad3acf6c197bc1b7b872953f25217b99c495ff292cfd02ceb25c779350b7bc9299292e7a5afe4dee79e83e0573ff01bb1571a3bd03190896afdf4432e61dec2d4eab656cbe39d7ec84d53399ada4adfb65fe19d76b2098a1214fc0451307d92dae2b69bcebb316fde8de0924c4730fbeba59b9273234ec91bbce97c1b71bcae2a05a6eb0621b75b9b7a450f94667fdd7ab33a70dd7a8bd4555799d2ee9e2d6169829fd355bb653c2dbcd9b6965adadddd76fc62dbeeb576dbb67bedb65d6bb78dbbdb1d95609c3d6720e69059e6b82a1508559d409f561a8ee32e777d34ee660e5257a20f1aebe54c0a9e35a1878f0750eb44f09b30656a4ec0eed96f010e0053a8b4f6b4218d0940bebd0699a141be6b49c473be90ca34536692619b35afbd97bb9cb5d6da3b49da3d25c137c75d3ac337e744f0df29b371dc68b791358f38e3de5a2b10623633ca9c482fc090182541a22fba4379680ff561c18566955489168cb098715d55f7aa24d562442e276dc8036e6666c81db1257307497ed3dc34eb4bd596933673d5412030c44e72923dfb1286a76fad1dad7da5a06d0af4ede853c6ddbe8670a2698f169bf2a8b3bbc5692b1ba5a352946b4b519a35a55965098ca795a2d458167c0cf44b72be1dc929d342452e4b4425a25251fdd211e5e195d261efb53bc894bd24c8e868e19bb261efb5d65adbd1d4563706b1944ef2fa7ddcf8de5bb113c17fbd5a7cfdd2a693bc5deb56b7ae9b5806299cad48e7edd3d3895376081f34432a3706a9abcd8a8fe707b447db7bad0f1a6badb55e2afd74f9d9b42584521e1b16bd75b3e56d88393b49ea97da293cdaaee3bbafea5edbdddddddddddd7e2d6d4a299d4a36b7e9d9db38bbbde280ca0c6b5307dcf7d9f1a484e3b815349e64e33c6dfbaa6a505aa7bdd75a6bed2c7227c9594443135ec946fc27f63cadcf6bedf54173039c6da8b40b170d8b9a748c9100000000014315003020100a86c462d180449265d90714000b86984e64481a4a84498ee3280a32c61863100084006208013053431833d3ef907a259df4a3eba1fa329c30137c03b36b261f4eb5143f74b4a0299149485213ecc213724776ec80ffe76e35a1db3aa42c42fc031a03392a63a09270844c8c94e0c72b9142843c6529dc318dd255be155edda244084d1f0860cdb2b7fe9a5e9e8fcdafb8c8ee5cea8910a1cd13848b7b146ec326982576a28b7bb9e708852eacb1083113484a7d2bfd020a12a16c36c513a1afd8271812c89522fe26a3e811b07f7cd0e3275040b2b2114acdbf8c17c07f38ce8a2294955ba278ce81ce45ccc14e33582e5067edbf6648b1af8c508c36133276cb9bcea9b3057db2a51db79fc3ccee5e63fade6b8c9017e64c00a989ccfe1d22e4cf58285657b4a403cae717c4dc1fe034fb86e6cb7e3e63d30410e1fc8c38a0f555f054901440acaae40ea00d1d34dd71928884de56762cf3ef4c6c38ff19f58443393c0f07cf58bfe8798c4a7907e04ee01ce12dacd5f0143a3149a40b3951dcded9723f867beff3b416a0dcfb5d4a1aa057a62817e0047e9ce7da3983b7bca7ff22f9bd0fc26fba04044ef879cf8b1f940c52a6e5fad98d805b94689049d3958ccabf7f37f0408a3715075fffeffed56d9525cde2789b3d3df9fa6e7f0ace615477e775ec8bd5486b950351310718409cdac4c43226049a5a9fe0c14f39635becf3d9360d353159f505d9d532a729be17e184e59681e1032a7da9d2d9388be66a15244fe5a96af4f65d04cc702d9d546ef48908976cc9cfdc7a8574009a881594ce4ebf1c87b93b2ec520d0a5a780ba9c3b80e51e1c23a0605262ca4d5b7575bc6fe03413618f5c08731d116d80487c1925decc206d84b2a006988c89d949f7baea17414829818a05ded5170b457fb3077c6c72626dcf2fc35bef9818d47174018c857725d8cd68f0d9826e43f3e00e9450e903457447b1d7dcf40edb9b580530666e36246ef873c8d381aaa6936ee3549fad3cca2ce3b9021b66041e6bc318384d8031425478d2f8d348cfe9cbea41fb538d0ccbed94a67f4514985697218be6e29f0ebbd2c2b45635d0c87adf20fed1d5048e788b7884b94d682bb0a719077d8e442773223447bf5ab26f67e52ceb56998f535e22ced995a09a2650acebdcd124afa18a26dbca461dc4dd0bc54ea8d0601743e45bb010b06f993e5336a582c9b2c936dab2f99c7ca92a6a3f73f7e6b6753a4c5eca5458797dd23164b9fd4881d144e121e4c69d0e1772db036985589702e1dbd120906f8335eb116568ac3ea71f5642b4afb69386e3f6a16e912382ce74ca040e37f55fb2642b258afcc75d496e4ed81cee94b23ae340517b69bc79ce600387813442815a05b3b6ad16f65e35e5a77148241aa71f70ff24cfdefd5a099ceee8577a2b25f6a65766101a0d2ef0f2fb20fcb025482cd6f1112d0f25047e03a38f1c918b7da79e2945820baf953cfc481035d7370a984a13b92164266cc77d55c5b271d9fe507f14c6002928ca6f901d3fcf3d5f3bb60975671c84bdb68361ad449fd8c80949accddb79517fb0059c96f87977bb3dab6abe02e8b73e3b59de7dbdc5b487b4d5c3307542696a0bb10e573eb58052a587c7170192cbbf47335cfc93042bf953246d332a0f434353dede919231bfcbf71e8864258412a2ef44cb2a8d70241a82c577c74514bd7f8b2959f984820ae5b82bad509941891ddb06b1f8c9cdc8d1dd2e6f54cfb9e0e2ff28e4758b679f3520b62d8ed0990afc6375869dbe61664e9fe49e818d52740bd5d1d93c14bb26b454bc72190cfc25d6c02b6ea94de7ad44161e32a2539499b06831cb5f215f54a0f0ad28117af44d12e42ba3bf43ebb12bca9fd7b9f9b6c68d9b5def5735bb8f9d8ed42a85dedf10bfb1dcac7e2401ef7d06fd57c61645a68a7fb344c2c45db2b398a49e81fba1baf315566ee0416dfc9fc8801ed437861f57d4189af1118a3adc22073c1a2f1cccf3e1bce93196e4ee55da5e9dd43bcf64fccf14a1ccfd29b23ac1e1daf89c7131b095e002041878ca85665e3b9c570c370819a7bace502e8fc0c2a69cad32679a63d7e27d8351f6761fe2a45163ba33cad94d3ecffc5facced308a8a2077e5ed52e0038e318d2fbee8596b4ff048051f41e54097a168b55f989ec6f8e73e3ec0fb4633704b9810526e235fee15b14b45622ef40691b9ac818b6c2c5b0c64c11c2dcf0109f4382db34257bd086100a64d1948df5517d28b5002f544ae6bd6099fc7e682251dab60242399622e8170b142e08456196586b49419e3115c1f949db6886531e49a5075b3951ffea46302f3dac99f20674649803a38763b78d3a9335391061dd461af86cc38069782f070a7178e77a939e1436d0d2f9b52ebad23d6519d4c37d0f67390012ff4fddff83b27cc19a0702faf78eb04bf2cc37b5b8ae04c2dc7043a07be45b9d6fc51fd6e87a596b9d574569b5645e2ca62cea61b902e9718473796bdbbf9af5ebb2127b189af0c7eddf8b7863c9d662bffad4048c0d402f788667ed89c49df23eb970a6a8badc60f8556d98417a170f56f4c79adf70d56173a186e92fda810b9178335ba9c35de821ce480260db99e7a95eb285acac2b9fcdb0df38e509572f79ad26f6f1406ed52897a10b380514162a3758c534d1014d626b6601f62a09e05a465babf67cc40f40a53eb129beb96a6c7425a63c123e3039100803bb4311e26fa10c57ffe393ec73521b900c71901990101b1e020d11d164459aa50b37bac294e420f6e8c10481032093554f9d886a839deaaf152aeb04b39588a1c8557ac0d24e2231fd7714426af7d914cdcc853a863c2fad4dae5ad4cca5dde7ef6bd0d295563c6561b75ab92deaa1e854be72085a8e83489fd8970d615293fdb10804c76ae2aef8cf1838f179cece0bb4cec7be6463791ec176864d040e3da5bee5a809e88c2a5bc20222a80f3b9d621f0ec4eb612f749a90416bbcb383e3992588cb052733345c07ea046c41beea34e5dc01aa2600b34bb080616926deef79209df0e8b25292673d3cf381e338d8cf13f05620f64015df347669df53b6a9691fd65f156f1148e7fedb63a9d0052d8d354c62fd755f078aed8e9bef45a7dfe5d398544486aeca5fb9a2b9dd142b7f95e9529b1e5983cc708c8e31ffefcd7a968523fc5c1d48d6409fc8821d3e032e190a133c1be3843df4e33afe8580c2161c578f708dc7900677bfecfaa943e1a82b53d3e8c71ddf71603eeb7da882a0aca5f12376dc589e23b746bf7400ccb8ab64ac2818bf9d7e79bb686775d0cbf65d5e9f21c4a9855c0760329cde4865b69b4c654a5646595a9f5e05ec7a43ec48c08812e1f448d02a9d210f9d280794801689dc118f5413e22f9d5d453835f8d4c23a932534ba99accf6d1dc8fcc85d3ef77fe872548ebff46416bddf47c58ee9067e38a125696de21b2d01be1b71711c4a2b99db9ab380e1c6009927b499fb9b217018edf70bc292943074b80d1324fd7bdbee6d9e50572a531adc9db9848efb65e195bc9e6434daa24d0097948ab7682d931027663114240f53e620f08707d4ef6ae016cd125441e1963ceed056ea9e851082a159ee8cd59b8bc89c594e778821c6554f6a87269c5eaa99620b9788b075c6acbaa2d6811676157945d97decfa8eecc83da50555f7d3ce9de12de30a1b6089fb6de334ab80e0b19e8874f709b55cf8a709c1c6c2e8a8e0dab753d3ba978279daa90700bbf54e394230a4ae4e985f263b0549c7985d018ec00c02a6af81b2b8cabb35fe484f89174b8f2c482cb10f4308ae4ea354e3512cdd9c70115c77b1016f1422158ab9cfdcce479e11f476d28cc9c2653fa1d73d8e18da3e0b04685a9d4ca015eeb2ceab0482ed57bc2a99d1202fa8e819d5f31db5be2c0777514ff982cd663264d0ed7e36cad0ac7305c3b5b906f1e68a31096306e6842936079857e4725469a34aa78789f69d0ef88c5432c6a9c9065a352429d1976108aff0b6463c8c8a85e088aeab34192ba9bae12324e7373a080c86ed7278cea0ac20662b8f3622e40245b9e8138660b4cc90c23e9e9ef27d3c44fefc60e1197092e0e2ab1196cf2d6d768ee4a373ba7c36b71d9dc7f19bb5d19476f37e813e647f16d0d9020e554b65675368d993bccdcc985df1f015b051a27de89e30b744fc13b8142e0ed8987f077d9d031b92e9847fa3e2f375f81cf3e54981b3fc60996b0ba4888e970109fe6c5a8b9bb67548e649349dc46840a5a13574ec08d8cf3b918cf2b2bd0f266ad7680da98e7422520f2d7d420bbfccbd06095886504aecedeed385ddd265ece151c4c110746872d8a7669b5e926f0f4b347331c60ba136c71ca9061b26599194bc51fe2870862c088ed43421d95195c673eb6fea3e5f0cbb7b99cf85e0e124ebd0f12ba531114ac693c0ece818447374a97dccf0c4def7cbcb5b30f751a0343136c41001c9f5770764b10c13a4d7953478f19fccb7bf79d665d60413bb44cb846f7ac19d51597f019d0bce1c641df4331e32956bb3da477028da0cabc35737fc9a7e15c056ece088841013a13771c0fe6b3d2cd0461c22724a04f9e3689436cfff06c1fec3c8a0d769b6ea65cf4327766fa742c9dc862ec8ec7a853d48e6a5739bf9591ba14f137be47d1e3ec43fc127224856d386ed5eb3fac47c845029da1b0b2ef7ec0ca36063d998da3760a8ac2342ec4a44b223055fdd6f93b0c52645afafa0e924089342408c4c05cad469fcb4ccd39f975a74f2540bcb746cbc9cab000b590da84cf879c87b8444fe50add3bd46b77774d949d055dd23cff55d6bcdb55c0f5b9eb5651feab019100eece720e15b09108f4403a27604d63047609e46e7dfc9bb26fe6c3f0cba38e048fcdd255c5102b9bfe19d54a6e2fd050ca9d50b4bcb7ffef5871690da0838baaa93cbca7963318f1529a54d7b149b3235707480aaec154aff57a20b9be817136d78f41782a2d3d514a3ae732dcf381c1b84003a9e2ef6347d854dff4142c7c3afd545cf57442c1a7c314c67cf0aa46a4cc765a8d8408ffe4f5e75876258b52e6d564d1481a7ebc031e902adf539d0b4eab1240086065a9d3d058e505b53d8f0260b0edc6bf62ed73fbf2c404f4daf119cee635a1883c7956609af556812ee75c372e53efc836f5f0c7c361ccce7020ac7219c68b58bd6412f70fd6e23ce95119530a8c114e3afd67dda7df57e4dc725bffaa2f75b41c2cc0d49aeb70f347ee76387412a67749940baa2d21a882f2e957e181231a1d1b76a2e2310e6a29bd6d34e9d102e84229c15b8f9e56207608cdb16ee9a94fbbc06103b02b6bb4e7299e48682276dbffd759ceaafad56a4a3b76f500a30313116eb1162939d7e1626ae38175aee74162e4ce57e4e55b89d7a910a9f819a52e407087ed9d6b7867ca2cfe5727847cc1c566962eb3b06548bf9ed7732ae74f49ba106396740baaf7e87cb84c5a600c100f77da17054681178e1777c14904d1c850dda2b146e880baa57abead63263d7f63261eb11311ad287f5e37a57ede313aa82f97236f942ddb3821b9ca0b7353ec3359b0bb3613f397395d7062897c34e030a336dbf1a75787ee906915d093fcf846c6de784c81a9b9dc9426249bec062683bcdf039612d928f266d01354cc4830c5a44a96e101e7401ef8944de86a3355dc8ff4ded5b66773c200803a94a25eb34c43872088b33c3b762431727ea3d4fd370a8f7442badfcf0e66760e9bcc2354056b5493bb15bc16cbc174dab919089d53f1785cc20d5b0b9963e14caec08d384609eef91172508da6d5338d005e095155296c5981ce746ebce9ed0731f77899589b98461426c5504feaac908b2f890ba36f325c0b571b547dda63c1e81c9f0310d62432f4d376abff10e4c2caa7ab795ebe5a3add97a8cee61f8ac91eb1dcd1af4ec384732409f32e8344c0f33e87a9019755a9bf035b7e7b3f5e58c5efc9829c384e374a7e504f32017107c1c6ee4e65ddacb742e4368a45b04dc31ced1900825d0af7848f5bb9dcbf5739fbd8c7a493d0d3c4068c18711b12981f6592064be2cb259ac5b54c9afb1353bd901c169d15d8d240a5af7a1248691d888444d58c6a48f6b4e626a55165cfbe147cc1a0d5dbf4525736e8e4eedd576ccfcbc6ea1f44f63f58046695cbb2b8b4d1e64bd702e816fdf18b91b58bba5657789d5e074649d99365c13f9039d8bb630b190eb2e3e934d01417656ab77ab077465b6a28871f73004cd26924ca954367e0d369faf99424e7299a76737875bf617011f0070420dde699b38c34cb656a57bb980d7f1e7fa378bcaaf1c2c4ec30848b389a6dfbd0ed99b17ff5ddf4c9679471855945181f26b922617555a2a2a7c2e375652e39a7f77bba78525429327401ca0fba5c7aac941ce677a0ca759547c6b606afc5bf7de7cba66c61d1212e78b0466ed0727fcfc0cb74fbcaf6442c4e84de6e605e5b39ec8fb64ce0dcd15fa67b97aecb6a22bf154f168cfb59ea6ee26b0da9ba6005ba23e3fd434336ac6609ee6b27460704c8b3f2ca3d4b0d76d2d02ac77818b0ad9379225da7838102dfee53005dbfd720e4953062eaf44f5ea541f32eb858861c227a832966beab2b457477a83cb5e1ae57d604177a5ff46579d67705c1a8eb6ce47ab42ba074480e42a155960c759b789a9316a7f948142aa092156ff4109baa23b62eaa2bdcbabd7456480d379fb3f2ffe73e0bf0592fe3823303dcf3e22f26af52489fa0755c58fb8b81fc4480d28bab747afa0c41e1f28f8637e84c477926b615689f0be84d17a26772feaad17322bb356e905dde165dd7bf636de18472916c013f0c41d03e3fb83475af4f75ff805f3666f7c4b5059f19cfde5b470262aee344ed42d88306b612bd57826dece576d068fbb3fdfa0ea302879d4cdef37b32683e65644ae7931ec44c36d7c5277a09340dcdb3095dbabb7e3d52170d0a8da6051fbb2e2b3b0c3ba1fe8b44b99855982642f0fcf7360be4e149e90ac83670131809cfa465194323be843fcfa58ade59553c9572d9429ecf0e52983d1c2da8939121fb1f696f356567553214adbec86ca6193a0626788694c6d11655664518930cf69afbf3ae9760380c4f446add21a10a2a9fb6461524653d8aade0860946b6935c993a46b4710da8dfee5b2ed51a71b1a47fc8271d9f7c12299c7aa3379f9a0276adffa43065af4d5f0e69cff337a6beb45315db71cb1942a22f9f2a8a3eb509382663f82ee02a0f21c171596e0375abf7724ad22062611d0126d8dc128fed5b6a0de068f216b5e12ef4b868002d0cbf19826362905584bd9b8d49e88519e6759fdb1bcca6a2e00d2f263b0acbd3a1eff4613bb8b480e31911e0dfea513e73bb70389222182f49f984651188a002ae8bb2ce0adeb89b0b25e3d9162a1aef1bab19227042609d3ce98ccfa693d3da17bb8b05ebdeb79d8e1f774231e2e05b4f6eb4595fcb68527fa0ec9e7bda28012c58cb5a737e2759390f9a72adf6ec903bd43f3f05c5540a9625cd5ab977a1f2eb4474f7a0f7fabeb8178b773c04efb30c4b06387ba075a9147af9579c606e1c1e681681a300f3ef6b671a42d890a7a8269ab32537523354fdcacc36fe7b6b6b7a7551d613d7839f5f31fd4cd9d836b989a20d558d22bb36f35adc78a47fd9cdcb5e520b7d2f7c33bf49acfb4a9e1a42644e74523ebfb70c74157780df24324272a3250e91a726d6ce4ba8f1ab9f6d10850f3e86ff10388b9303a2ab4fb724b8dcdc722fa909e6be5608aead85778ea8ec7956a36364e6cbe321197c14112c0589a3901600e72baa6ab7a08baf3edf9492fbda71e29ac603c9cd214abe2eab342841b23f0a8721affaf9aff2b1f58f1df28b2d73782d2702284aebc8dee3a88264196df3040a996a6c010cba141fda028011bb469425d3fafa10f60c0b2ac264d05605b0edde106cd5ccb639cc47b3ff703684aff11b872d414affd0a85a1120b31c59130c578a3809deb15aafb64bd10c010e976d276e8a8e37e3ff2c656b46a1fbda8cebf5af300fd27612d4f53e2594aa16ec5d887333d81ba1b8d45b9c6cdfd827bf477ec62df935f0174a863a1cd2f5ccec30e28f5cdb1f9c9548f18592de0732f00b7d60b78802e5b0c053483deee745dc6b8e186ca1091360b6cf96d3bd817879950f0aef7f280bb2ebc57f9b4d0e0c494a7292b5ad27f1074944d6c9e2ae44c303a3b8f41b51af4372fb6c357032488e9cecb06efe9b70b00b31235d331d63eda942b9400dba12965ecb93821281aaf35e90541dd3e411db005c5a106aa8ce1745a022e5bf5733b598dc2f4c7e5117bf1126e8d2d5fd8b938f86e8898ea3089b5020dcdc0b638677de5f9504908dcdde7f6795492634356ac599c9e7142e32c1893a4ab3a00f3f6f2cab88fb85b6aa02ee4dc7548914e1768a5801b6c9400ccf469069c2f5550d08dc7c8dd4e9e509af70d6577942f68f9e86c10744657f034fd07e7a929a4a9809007d5566afcf11122f506539ee4d0822c6705c328a48ccb16adf4c8531044231d8c3dd1c4bcc50e20a064af4efc709690aef1e570d363cde195c12d36101cae724667e20b3c3570b03d91134b090e083129b9815455d8e4046f39b1481dd290e5084feb7a4ed5499fe7af46e196b2a883dcca20c43c8119b8588ff223ec1c3e99d6667583cf09da9959830bdbd23a6149d0028606bd17b4ba6718502bb49b5e419ae42c2af83774b9cab987c9caf252734b14a4d51e1356903e0727a8abe67b9b9aca92bc10e1712926fa0da96b0aede2f1fa22eda345505657e669492a4ae8872c0d15712db39fb6f1b21538aae45b163c87de1377cf4f815325ff66c92bb4529ca2b9f0db3ef717753fed43645a39cf1205b8df38445742a45111d3b2fbdf16215b4a799628ce7d717a882f49af63afe69b19685a8dabed85aad82d18f4292ec0b98c220b8d380dc84b93d539b6aa022345011e171a44170543d2c4a4a030c5215d84cc966e2016321f56406cc0fc7363323160ca5277c04465706f784ebc9a78730a6cc9823a9b6cacf4accbe1c11dce0c871abeb45cb61aaeea44a5dbe68d93553fa03cac0353f59e7076d5458e2f711ec59c8108b2981129ca56aa210e5eba45bfe622f5bc26d7a99df324588563708b8c19b3769eeb1a98513a0aaa7fc132193753e8b759dcd32dda8ca1fe2464b7f85f077fc2724678b8403abfe33c1334be51430c403465596d02e989545a4f631e41a982ca0bd4edb3015fb2a517384bd96aa4836d5681ac8c0afe75a43b40997249078213a538ee214b748742255b47793f3041b8e28555b2b134b86f2ec8a8e9dab3cffaecf50e2feb1cc259467ba7238f6ca69b9fae9864042525da858010f3edcd86f9833d855245455ddd8965f1be4f41b71ce2f0ad5e678592589ce65c76df27f63daa62700379c7514b4553bf04b425ff50fba150cdc07cc7afdc4d8132f154bf20e83456c2f6ebc1295e3bc83e3326092fd945051a0863f8c97352d9cb6bb7c788bbae282cd849b703609e1fcbaa6cf7b05bdec504128092b1ac13064001664b12eac896212230e7efea7dd1b15770d911c90fd5bc98ec23b8db28b8a1e325d5dddc8e0cf9b4e1b3ef2e22378f6df9d1f3f0f16035ecbbd83f929ae75201005762b7febf4973736a8782a3d6e586f44c875260932ee65fd3480a1e04f13b01dd419e9da5635185d1529337d58bc97f0540b3082e807612c027b9ef41e1e774bc409943d66abb966621c4535d42619bff11a364d3e6c02013d075b8c2e3ee546204227a3768bafebcbb3d196fb44da9fda64c53d0088167cbb7fdb30dd138afb812d218430f386d8859447c6f6eeb2470139b4326b31d66bd49fbf7ab3d63c5e61bbcacfdf6e03fa26aae699bf9bee06ce7a824eff7c8a80450286abfccdc9e54e000ccd9bb878286f454ca907413937569308689a541718078a2a0f899dcd2da7e69c27a4641c016a082d6ccdee9226aad0974259d207e20fc30553c1b0eb3d31a1ecc9d0afb83c4690d24cbd641b1aca44ec5a836f5f1e0688e57c015a7784730f63660f4eafa832c29d435466828a6f192fb0b94f34bb980e347ad7ec26245e974e875057c226c7ce7737571f7bb2d46a694f12c555cb7c552f371beb666d8b419832d11c9f8261d9de261639c8affdea3467e360eb01c849d86a35fdc98a4cab963df26d8a55ef476ec4f50452d14ec20640a5e1c5cb67147ca32ed8d6ce627b7fde0afd6137ee601aa4e96ba4be7578183d04b075569bf2bcca4e45babda2aad519aac039fc09e68705950d2361421f6c5ae18d1ed3abdbb566d98b7399fb1988edb137b86d3e5ef4c50c2896a62bd4faa4bd58f0596ce23bcb9cd28d880a9da1805d2172f7994ec6257edf40bb82a110cead3f036514255ec9653f2e4b94d890a830d40f2fd678044bd509a40d8e6cee8daed5f50d8a27d4baf097cb84a122f6840a303b01ae5916d54de53482bd6399a8e4e70057b1546c1d0db534be522cefc32910c0456ff300a6443b9a5e1d9935f5e26f790f5e0a4d33dba63b0595279572b52babe388a2112200efe74139e90ba94695fbdee610556174ad4c34a3ce36688b738fa9d7590043fd519ffafda8d6a03cf7253382035fe43490adb65a27090ca3f0ed02f2d7d9738ab1cd0c2e259a7a3b5a0080cc479560673e3ecb3707e1677dfb8ff0863c61cd6d9181652deedb16c7e2a544f7daec55e48ab1a44755f516df4abf64da053c3ab731e289786af4414b6da340c5c4cafa55a41aa3f3a98c9228c662bcc733e30744cf849e85b5883a89dd4f8f0551f7f8980f3ce323d97a1cde70601b2742db7bccefff20fba31382d37994754a2818f44a4bb2de0d27080f20122669df3cc3cbc90c4d69fe451436c72aa402a3e0358e0670a4a90d087c84b6e5fd030f16c53f3c48a645393e7429a3c18ce9c41e5ae49a8d72c9caf9986b2ba916c79de7bb31d24ce9c0786f1ee19fc2ff0cc208bfba44557d0292c15dd48f9ebb0c5238e9c3f79433fdc24427c8097d788bbe39e4c11e42edc9a4c6069fefabee93a9ebe272c016826b9321f198c22c297c3739dc8a9ea669c2d0f1e295109f159e2211ed0b51d4a1e397bc430f10f76e8cb35e1bd2ca2c0c7d40ffca0883888cac6bde52039569e5cb294e245d449b780a5194b6f2ef1be0f031a1e898de7f8569d5424364a8c243386aa840cf241afd98ffab159a462c27bc3580b68ce1577690e32a2bcb710d2988c09fba816066d21dda80235809a71fd8f7e09514378d28c31ab4372b4cd5eebbfc35fc9438048dcc7a2e5246e5b96d9ea4ee1080f1448b392eeba4b78f87560ba7ef7a45db783f5b7707a27f338e9d4f8bee66c679cf8de87234bcf855a7c96c006d4cf03d41aedd08f73b11014f37f855aa42650ec3c837617115b6e517c96761afaff0d5e656ff04a8d7078abf2036dcf28006298a9fa79a4d507708403ecc29339ed8c6ad733d3e99c84ae43f15b78fb19fa90b86c45f6bde6fb5e665e5be013c9c957992722e40d32d5f22f51bf521d7ecd1f9ed8081193e52b5d9ed627b5c3d9ea1033ede0c27530876c2ae5d238a31a097a380dbd5a702aa510c8a09270ac5c14aad79c1026706dbc08b82746299f236ce0576719d61c6db948c39444b328aea424f66880625f1569d668c4d0afa99f9ee75cc0a00c470431fd48c8b00c73906b26508e75e1536f6c0d47951b2bc3482ff38e314a994a6d1514d1518f9361b1eeb65f62a7fd732d120749e47bf21fde9810e390436c25d9174fd005aa6bf3b71613474e074d6c7b7fc6f8eb28b8c7e03d4a178c1ce4bf0ae5493eb2f64b3d412394450e28c4f28c1bd7bd900546e9f545076a014231ac3bb38466725bd7fcc4540b11a459c778e682030994a3f0ed9cb8ee06e34c643a0d219d233625f0538722724e70caf9a042791f1d68b251799467c6691792b18ed169e00d6e2d41c1a7d14d93671bc60ba5cc5cbfd91d6386b1360764e312554b282a3081f65331ab24ba30848e146bc379570d667be3531d7ea3a512e666c5fe66f1c920b441507aba75253aa9a113f72adbf1f74aac49dd8b357ccc5beecb3c1209a1e33cca3fbb34b092983644343dd93da02df194fc6524ee1bc76f5be5800f317dd9556abae917e3a7d457927e09b69c02fc157510175c75fe6253dbb170dd5642f5e7f65dd76ef7359f97d39bfae2c29f69df5ca1988310bdb3458fc23359107cdf92afe6ce3a1062173e0e6db87b36c70e0ffa05e5c8347aa03fae599c372c402083543d671baef9053f2544ee4fed5c6b062ca235e7360539473060ba5a14d717099a936c0a472226e76a38adcadc91b015e469b25c6427f66c2f22dbc9df520ccf7df5e38c51af74f3acf5e1ebd57dad22ed3d66656c837b9f356462a0d09a85eeb75fbd1fbbc3333a1807daa0ea56d0d1e1ef4cc1a29ff0ae750d8412160fe95fe86865c9d849647f1afe6190e54ba5e2713ac78a722bc11ba976cd3da72c2374e2cedc3231dde1c03444c895561e9a080286ec2c005a82841881ffc043a78d33befa9936eff2a85b848e0bf93443993598438588293a77e3e6cae179c108e2a12b49b187868648a73648e87aa804cf0c061d06a0b425b19d0617d34d900120a1ab40c49938ebf7666d92749871f71a6190496c5cc5044ecb893199be4ce0c1458b267a4aefff1d5bbc40d04260baaa7b53c852afe065abb9d170ac0029d927fef697f507c26660e5c50adaf552850486af6529eaa12920eb33489971602356b5fc3dd4f325dff5d760c3a8a1efc54deaf31abda99e96e223da1a9bb69a9fba2ce6f58aa95bfd77458ab0ecbf536fddfeac7a6c62cecefacbd1b475640d1d66aa99cd5686d3864a9dded98724358fe84c6642faad6f16c5c93773fc2e0f3674ac7804d5474b1c7c4dda6b1d4d2d30fad9bb58293a162383a5e82d09f04f751a7bb6aa75dd5f11972a58bd76fba46bc0296fd0b14d1bfef1b8da2e1642330b99daffb1ee592c70647004ac863575558db2e34a7ed24fb856d25bebb95d2bd0db5afa3fec44a6c25efd743585ce6edea054f24b181fd1a0f087cbb028ab0b0c1c0a542acc248306110750cacc04f0a47bba0e4bcda9c53c07d286461626d642cd5c7240ba7ab50db1fc7ac909cd19a3e51fb1e8c3d7a7c5e87ff9977d295925336f3197b24a8edf23be3c6f4ca55521112bcbbf1b7438ec27b7eccb85f76fedab6839f50402ac92a27209223d0cbdcbc1eb1b9387a83c6c6b81a70b9a9548cd18aeccab33f2cc8a1aa4d3d10a1973d4ca11de630faf161d5445c8a66672d2f0a66e4234ac5a48f89aa5e8128b323df85a699d20b9954a28008f7f6b84ccbf7adc4cc5a3643b5800ac2d3a3a594dd7b659001cec527a2112ca0aeef79262012c3ba75f40bf029c658d08acf9b6d7ce8994d4f446e4056b758f6640052a3844538770d84c9f125877313f4dae0d2222448f7eae4c4304510ae94545d188b94280f70fb9b14cf32a892401106b80a17fb1d37a89d6a4d77f0f70379394203e41b5abccfe6829cb66ee2127652d08ca88bd0ab836cd385dc0623703472242cb6c1a647f5ff7caf4a06673df93811e81a1d9574ca5092feef56d21b1b715f68951147bda370a65e4444475be6b6ef63db522c9b7ce435a326133c24fcf4ed5a3e1d3d9e88905d101a58f5fdbf98f113e0f43d82efd65485d7bf1e92c06aba9fd26254f367c8e956f219091da42c73498a1718c4c6914881e0004d208c73934876150e535845ec64b2dcd292acfc45512194fe9e0e0a4247402db336bf6a03812ee2b929e4583a1b7b765bbe0ce4faced919c957a8b5b1e91901e0ccf939c985e50af95525c5205bb83fbb124726054a6e684b86b6ec5002209b27ce337e5f4fae75402168f719b6b51e5f480a98900e0ea51e0331163a9ec0015126c217a6575c522b3b14eec642572e249790c539bc7ba2ef9d6358ad6baab2d02dd7f351e17f138b72644d3177053782e7e8c5d955cb6755c93cdaac5a8083e0b832c2bcafd932a96c8f2fb100de0e51f3e5284e1bd56ffd0a58b7f6a5988e407368a73fc01a13bdd081501d7b1cc775dc829dca0a15d037bf4c4019d0cd312c36a2f1dc4a1777b865b65cd0ab407530100c9144ffa44d4bace352c5d5c631f501cc363515bd621efd9d8718a81656f52416a19bdee5a2a5e3911f4511495aa3f22742167f00b302034aa3932c8bc1b86444a81791fdaf93abeef1709e1fac3612471c893b29d0e681a750bbdd49e9513bdd1dd4d216a90d6373500cba78c1d5300e74b328406b1eab68c8fd86e4f4ba335917b71da8e28a08aa7b035ebdf843694113f5b7bc487d6f8461828892cf4987166451ff6de02b2fed1dbef7e86a037abd35dbf064bf64a77a57676029750404563a281f583e6e67ced191a5789ce7c611de493d421fc5ece3f610aff3e92ecd53a9817469277fdaf0acf005597a46b782ca55d5d1be76a0cbcaa4ad348ebe5d2c074d04cafb26ce229e66d3cbef17a677313de2503f59fed0c3c3f60c85a154a8653c63b98b52bce24e0b56a4a2f93e71d685b8570b15359a23e958b567fd754ad8b265da0f88eb06e52a7e358ec09292d6b483c6e10308e90b53344cf781a6261bfc2755050d38dcc1c79958184ce41d5292e6f584a613caaf1e6585f330b42700dbbf26736e99a445899edcd33137d6ebbcf72054f3b040b6ba579cd52b45a5bf91f6eabaf180803606e502078e7f4b6a51708e1192303c09cc8d872efa97c17b21723cd5764b26fd6510d57a0a7cc7d86b6b82aa23ad742f666557ca1277d21befe911e10396de47db8e39905a36a51971d2147d4348b30f8c9ade74521167a1e43ccca56b6468230238ef09da30e34cbfbb0c95c350abb3b57e171e712ce050c6a6c6c6733b2e7a78eef6e4149ae7b1baa8d396830b36953f73ab420a5e0b04c064249b0ebc6aa9aa5ab01c00aa2d5cf74d59a41e71444edf3b36a0d23ea19f388542a793b42564e6ef3381263ddae08a636fbb768fd11da2dd19ee8e885476d22de01772154cf9b060b608a91b0ca73361d68cbdfdb314e810c2172c9127f935b444ba4625e9e9ddd7a89227f8698544b03ae70e0481cda59f46cce2d2ff6a359e3963091ca79551c2d6d4f315d77cd203d46e57aa641e31e91c1dac070e589ef77bf094031b528061c425a87c6875dabb0225d126c3719b9a35cf1be1b3a0c5d7ed1f9152fb8580395b4a52fe660c38d2c61eb18ac0a03484a65c711cab46a271c965fc87c996e5e16348e80d423bb2b8823ab954cfeee1c44cdd01c84ca3e4705508af0ebfe3c0285d08f82a6414291fb21287ecbbd8a9152eb75f04a59a07f23269182f5e90a306fa0f44feb9cbdfdaf92eb6d90e730b2e6fb49b8199f61959e4989df35456feb31a732988663f4328b53eee5c99869c6ce4ab69af4d2fe47169ff07a7de3502b600a064b209d65bbf986a526640f948c4f4ad462be9294bdec90476660079e4b52a7c2ffc4fdceeff1b9d4aa12887be89896d9ca46b5468502418c63034ff222f122021adea3269b4cf08003fa3141c434e940fb1ee8ea2ea69f942a4538baca3f75171888af3bc5696df9710061f5ceacef7251746046c4304b6e92cef19c6e2517c5061fcb9142c4a6dc288ee730739b9d4ec31983bc84a4e0203617e7e2db003d131dca7dfd8f59537638a0d1d600df866f8a7af827c41aa453c48e6d0e33da75c69ebd4a05ca84811716f3a59ae976d880ee5ff38e98361532f06143ccdcdd7efe17160a574885830da67a6e77732bcedd9331f97c70969a8c01348fc9936030564a64ffed72af7bb3e6f9ee006909ef98d3fdb7ca0601711614ac35f0ae6d3a648ce1d118714a87557090984575e599aafbbb430be565ce747c9ba440dde9ec52c8b7ea8cf87090df00c1ad7b0e7e4db2a8fbc1d0a1665d5701f42477916a0743f4916226955cef0eb0b2f1d574a2821ce920e3a0010a3ee8cbeb9d0cc47f09c06fb2593fe957831c384b5406644a27fa560c09d6f12d3580e3f7b22eab085b80e704bf5a49f843c060c3d0d81c5e8240735307455a54a57642e18e98a450404c7a77e335741eee359d398274b141b7f2e7bfa7eb609194c58e341183d4b257eb2e4969d7c356b7caed6f08f0aa56171eae012678a900ddb7a769b1cc2f4aeb6a8fb1ca18c37c5601ff0182edd29fb570abf2b00d9be0041ace92c9562aa0dce70ad86857dcc701e0433053a87b100d5658e3e34e1fbca0627755330f019b6c9f02090660a50e24e3e68a522f79435ded827228dddaa500edd4fc9ff848cd905ad57e3e2f2b1cec1c511fd7cfa366b682ff1989382ec26eab72f95ee70f9a01deb92bde46f887175ca7dcfa7d1d82cc799464b34e94263942b12fa64eb41519f18ed378c5e5b94b80e20692e10f4aa83bd2b35eb6c712370b00d3dbc71423317728a07a7acc2eb0b911a307e33cfe55624a170bdde80eccc7eee2a648cbf8b78d787a1625404c66c18a4904b115cbe233c486c80baa0c17115c3b450ed8be75e2f5d7226fc9a8a18cc16a2a4a2261ee0dd024ea4bca453b2922c57bf657379d0f531328ab8b367f8f197d7e9d7452204bdd70c46b24f9f920b7d20be6364c6d70f9c689e825c080891c24365e44b03bea24b6adddc90999f1908efc8cb2c755492ba1008b2b4aa0523995156371b11c597f21aa9688090d604935181d82bc66f329c515b874060cd5dd7ec0e9c685d58d5b6a3e37b7d6e5090c538ffb1185a74d926c7cb14ab1794bc93d9a439ea522db598bde96b322202f1edd6ab1d0b130588f54c180bc6d4fcd3a682311fa8c520e2356b200498c5c31aea0501a5c01f9a4aa40afa4b7f3f6c6d385758a64b26e428d8d117c07caa511127f9bd4f22f198bc46d242a08f7abeba7d197d8db573dc22542424c358e843db44f79a284477d869e78f853a621796141ff0b780e88fdfb679dc4b873cbe0a484d62ca135e3faa535c0185c9c08f65413c445e9171f0967a8a83272056793d81a891f786506dfe1e9d229b922c5aaad948abe267d74183a510c746871ae142d725db0b8bf1d45b09198e78a6b364bb97ddab2cfd49dad03ed906994a7a407548198f879fb4ae045da9bcd79a493f46adeffc801b60f345bea921376f8ec29490b7a2af94cac4674b622be6996ce27dd9432bbe85809cbd6f9ad23cfaba7501fcef1091ef3669176c365360f4fc9d1a015269236207f88ad3661041a72d7980cd3d3fac5b37229bf2734523283e28b89ba4a55984218b313ea75c20fb3421547fecb7ef7f96629a13edbb9d55d755ff8e6a46815f7d92205d6a94f740b30d836f52a15baf529af1f72d9b03a5593ae045b00d0edc179732bd60ba3dfea40eefb0612a19cdfca72405f4a15cdfdcd190b6517ae6abee7abf0ea2111548f52f17c310728b12069b318e065d8e3fd537307934cae9bccbed9b0b4fe0d969a0c25a9a4c9eec13f9d3b61f5a205ed8f2e667a710f4d8f10a8e40048cfd6ce65330a2307282c2b18ef3d51f6f9c7c7659e9a217aeab97c1721871b801e8f971a4e15330bfe6da5a1ef0419540e73d0bc95dd8d581e8785ddf0b50ebe9f410337c0baacb4d85ede9b0b4033abd1605c2c841bf2248ee154c405677e57a7a5bba810777885a1d148ae3f03a7d64c304d681f34b5a48c81f829e326840c5ba465b86d82ff99dc35362e5c22db9940690cb327a071ae2293c6a2945522f09c67a4c1cda4543fabb3256f9f9e2babe76d8edf46cd346319c80b821cb1f282f27c25d9bfd8d1eabd3ac2d8abfb4b621f34f32e0a86ee194984e25b1ac185d448386fef7a036cf7d58eb3cefbbdc0e0acb38bc8f35e314fb2e2b4ce62cb89d8994c4617d83ce215f6f62622b505af3530a72bc5fb243a9e1af40ebec3a2b7ba22c583d5b46c8921260f87857397b889c6df1ccca7ce90588d899fb83285231ea417bbc8d57fe18ae1ccf09c7d766c2a614f6fb2a90fdc8fd931c095419f756ae5bdc3146b9e612d4a9de6ad56b33d813cc2d10b51bd35df39d6caac81a879d30feffa6ca5262c6aae0cdd23df707c48d4d866ee9309281051b3583dbe655f1f30884a18106740a513fa81894049c1e327eecaf8eda4365acf6d328f40f53e818cffd0e712bf40eb5ea325149aae4d8522a0d3beea61221580333ae0f2243798708a85ab0be2ad052d8656e512e2479d16e081ba3caca4cacdbf5b1a008eefadc8306079c06189340a8f2afa25db0245a2247b3f42922a49f4870efa920d6c3bcc7b40dd40f661fe994f3828e3bc79cff397ec1ce3620d9f15cafb47cb20ab34b592dca37f68483811ff96c479ab55d40711e33de1a441f080f5da745da545199fc5864efb5e3b01728844029f0e1a33abf3b1a137caf01e823f0e2a0caf76563e0c29ae9181e9e7e2bdea7cdfc6f249ba810b93fa7f21b40e870ad61a4ce2f9250e02983eb0fc689ad4ff23fed5136ba0a08c8099e5812b0f414cacdb9ab88ab83c02d99d78b6de08de6691b52f3f53e31923fa0256f484200abb62e4c219f165afd9e855a17018b39efd295a42da5a4803ba2db5cd6ca1f735ab38864e738424b3125ca7a1246d749a57cdc591da39cdf5e76535c81c825f6732555f94118b69cd465438513d826946824b5919b40b0ca38d090702e3f97c04f53cde4ed8d2b65a73585d97bb2acf09e604211373e0684327b310717e4e0867e8b4b04377a0817bb3b406da4358bf46cce6e067c2979e22203259edd198b99713bcaf2e91b91926d60d2743c809c320aaee28c7473e14191f078cc4792469d9507a7b233253df34e140ba2f00682bd367014aeb3aaed9f579ad478f5cf1d0bcc56042cccb7f05e5ae102ad1b5a112dbeedac25d7990ff144dca705617f39610ecdc0deee596388e09343673fe8376dc9b13a3f3e08cbccc6c65813d5aef6b1f168d222a92dd9de41c9b79bfdfe97278ec59c510e70811790f5ebf70bf39da4e06362ccab28c25e5cdc4f7803df1f7ec5cd18c9f6361bf1188a9818c359407c1c168a4a976f87e83dd1a0728a7462faa9d978d315b602fab3d8d6f0376abfea10d3b04b5ea9ddbf0723708c6f71bef337aff4fd216d385679674d450288986e1496349fb4e2912402c6926c2ad64deb2860685467006fe5166e3a7cc4ab07b64dd87453742a8725c4642a6da9ddb695706e9bd17dcaee7cf1672c7f53480c968277081db18a8408c1bd0f0e77a89e6466ea15fc622cfae8803592b172174f80723a0bbb6bea91c3d4ce235595db86c22087c17cefbfe98cefbbe0bd7128e7e8680c9509d351f98125e8f29649b9f7fff1380626f547c2be4fdd4a112d1c5c77183fccab02f3fbba584443fd38a6b9ddec655ff199440e5b5c264080a0280c05885f47c005b6e12e7993b8663bb7ad96fd702181f7387758b89fc373e1bd09b33f5ddfdaddf4eb74ccc883d46b7f025d1e0b12b0c70acc6249ff5927db448d599af64dd80cb97e352821e9d764b0e63a352277d7f314fb108f1f20bb8057ed09bc54dcbef6f9d2a24348a646ebb089dbd3794b6d70d70322e175dd8dda9e4541f2c7bf45db834943c3d183ed3a8cbcbc9693c502b2b9adf6504c91938e06a128ffb8a20eac97e19c4e13e4990ae55a337e16e3f45524a42296463e196f116e476f28c1bc6909cc1686e49e408c97ebde69a095e7538e576583fd9b9ba2f39756efb0514466273b00df3f565c59e3e489d999018d30e79f3b5deb2c46280ed61050d4067f7ee414e398a722a6ce0c74ece563db3ff18f2e04038377a76ac028e04f5c2f6254dbcb758e0ac8cc00527b2afda7ad49560ed11b185fce5cfa264af0093ef62d4e7ccccfef27c83fecbde52131ac1a234bf25d841600b61181d9cf827f9808624f0e6780f14d8b9fb264325089c6f37f904291c97ac367d50a3cfacf016b947a44c0619e7c16787b3d274985fbf71fc151a6ebcc0ed6497922c2732a20a373d18dc98316c5e9cc819907c2cc97c806468b0c2f61104a73623477885d4d481c1bc84a12c1230bff69fb5ae602f30cdfed5b4045c96e2836916f2991035d2f0c61c9f3187fd0239ca903b824982e0846e7e18dc42ca11ae91004f59504518f85f12988dc31cee1a4d281f8acf8cbe864b064c6885d79eb9c9071a411f70eef02df3e6c3aaf1049aa345cc8f2301940b12a9f3f454e8a165ffd1527dc28ac4667307e65865d83dcc7087d3360304faf53084933c3f8a4dd408d3053def4813c7b68d0c44732f174743421231223bd965409d4f427cdea8212fd24fb84c0028135872f99ee0f046a488772bd247048d74b8470808c1c53211519d5ff24b0ecc1e6e919bdbf2531bcd739a30ee990318ec5781fdb8632320cdaed72567db064c8105476a0a1a4c6e7c8d40e0aaa980a47fb445c8564177af569562352a8cf1dc8762dfd5302cc0225f5ff6c5ab79090b655d8c5516e6cb7892701809e357cadb8a29bf1864819847307a36f1d87c7cd3adc71fa58869887a608dfdfb70164004b1c3c45507071d6d2728bf09c5273bd80b7d318bbeb0a2aee5e7b4829eca8c9e8cc8d6e5826e0a22afcb342a079a7047cd370769a7da5d44525537a6b5144108a7107edad0fe04a101f69aa49094b227611c6bf00711ed702ca1be09e27d54141e055383430ad0934d4a1aa0d77db051c099564a2e11a62ae0f3d57b1052886fb684b77375745b632ed29be33e7b62d936a0e0c6f7d53645b0658a5bf62b9414567e5e613279c7f79eaaaff070edd2409ebe03920081e055147a71b7fe52b05f79ca986704d0bf3b50d345275641368cc8e844984b27562b1e2e92e59632bb9ecba5b6990efc913082165427eacb899cd82725fd68ae13fa20caa1648404d7e97bd7709db889418fd660eaaa2e7232a713daa8e92035d8e88640467c05883e19e88b1e7bc90c73fb044baa584a1364fb811843215c224802f0999510feb20fe1baf12f2a62cf4ebd57d2ced2191000d93f6bcc6aacea6008ed6041781b48909186188100d65b40e208c2dca3d2c86b36483b8636393b19b6a5ea1343c1925c08beff82df568890fc561d94eabc0453f33c4bf87a0cf3d66cdc250079465bcfa5e4bb8f75e02382cee1c2cf747e00af478667030ea46ac578d74009527239d023c37bfcba7a5d54d75881e590f82694266d6c7599a808c68c9ef4f3b23607aa881ed616428720bab80c22d52676e1ee062ee8d5597450c69c83ddf6412650a4b89edbc466353d3d68ba3a858d26a6329eca99b5feb94cf4e36ff87acfacfa7f3ae3df40b4ab6b0fecc846a98ea023a891c5184178387677764605421347385c42ff83c8bf692dcad928d8f4f09bee8dfec1ac2a7066a5c61d1a9254aa35b294e2563f637ef3d4b6950eff90e2c366129f74885e4dbc1c7ac03a1ae323f892467928f51d2d17b43d6c63142eaf3fb317b97aac773e1fa05c9108e2fbec4f32e89ca95a119e3bb04343d7522bc37736f425f6fe2057ee5115d857c1fde002bc5c67ec60ed56cbe58a6fcd40f581f297bcbbbb2cce93990e3b4376b0a817300d185ec2a776c971130a47d088e59b39c04fd7afc577a3f8bc78cf743c48b99316189300d50b4810aa51341de5c1ccedef99029a235c2e40c494610ccda0131350fd35c77b34aa2eef5441f4cf5cd62ff575a777cbec7bcc8264f8110347d91e4da0b698e42427153f568efbc441a38f788ab5823f5f9e22496cf667c1cacb90d7bdf75f030a1b88e7e70f766c8c158c0fb40d7d23275cd19d5b6080937e216181ffc570eb0d1f83eeeb123342a2b6940ecb2263a79996b1e16f371d29eea281f7db8fc9c76e15f35fb9ec59c745a784751d4ab415295023e94a27c1b6677ea62818174284202905bea4f809c20282875be190497b5c535ea2eefc7b42d8e8a406d20f00ae0111b30ba37e0786c11ae2b43924af551ac5bf64fd3cbc8424a9f4aa0ff734fd4200c003d048a7d0f7c7e1ca63be7d1563e1f650c228a743ab157cbe3df9491ba80c0be65fc06738c933ac5dd226b8c7b3fd544e53468a6e36789d72e308f6e2a4b838f548291f463e621d081fd2641a348e15591af2cc9138ac9a42ab76b91685066db801c26014ab84460290a344786d78166714cd2166cf93f20e8f60a4ac3cf46bc14683426db87a69eb19f4fb070c0ae00a757cdc7b177c41ea2b8a3610a99891408a3323c4acafa6ad3d1ca6acb129986da06e19cb97add4c76b6a14f01a3961ac4841b0d7ce1cf089f5e5d0185bb0fbb8048c22c8ddbe351e2630e286027f5b8e364cfc262133b40af643393aebfcb6d3dbff11e92f311b8e7e2ffe1d38c00daa59f70b4323f5fc865972c221ec0befdf0d08a7498ae3f09cc20db46943b8aca9f21b28e7eb9ec274381e08201039dfde2aabfc10ebeb4282d7079cf5a6e56e013141bf2646df7813fbff886c1135f6c116058da42123605c1938f17a53120f57f340a4ecd6eb12c1652e94df6d125f622ac3d28a5c9223d9b0e1b5a27e00d0a6c6016aa31f7cdf371521f585b079b768253ab6a7aefc4369831a479f997c6bc335d6c91ed6c108393745474b1eb0c7bf6edf671dc5ad5ac40c5d8642134aafbffabef516ea2880d5322b7f9794ff22e6f2df3786da09bb14fb036c63bc4706c736a2f894a2c61163e15e205222255dd5fe55913ff19580ff80c516824be8c043e91d84b8d784aa998b7c0424307b857cc0cdaf804607dd65d59db9706f5722a0d563a7ca4a1d5dff6463c88a7be1a8de5ed69c293a927fbc1a36a7fc2a8e0ee9bf7bb834e0cb39be9d9034b1b2fce97e4a3c5ff88cc627fe908ac623d09974048d8cf28cef442e1f00c7bba98356e482fc8b29224504c426f20f4468e6a4f84f5b921954e640e538423b8dc113c270c8239e4140d10333748f0c505676d08302edea3b20f4372020e9a7e63ae766401f030a96878dc2a61cfba80354fec39698229ff28ee5d6e23eae400ec4d54e9f86434f6a80fa6fce88a622e6d5ea0d0b4eef8efe9923df0f04b4210498be80b8619792d2490e077466a6224ce1072bc8c39cd40e178a558f50557fb8785b7f887a9626f2fbde23851ed4aa3621d5d50746df352ae75491eaa597d2991e847c4fbde576c6011fc042ed4f05689b397828b041719c61673200ad04f09e662fee809b5fa0a388a0b4a8083681f3497ace429bd42dc5fe5b16d397e305907a9416f8ee1a3ba832b7307d09b08ea08f4e6c2965262dfd0b04827c586ffe2bc44d424dec9ccc6e1aa4d94524b30ad01abb54aac11b1592868643b133f622e5450b578e484762ce0494565e500e1225e51a5b19640232058651631b0e740eae09856790aa4db67116e920ada4b2a21ee1b1a42166b42ff3eb131d50c9a4e4e4040baf4026478441eef45158ec434402cca4bb24336418454bc1c6dbdf224cf1490992c82760c23f68d9670f9492773fb0c81f3d9af7c7287f8dbccde25aeae2dca07cc8a7493b3e59008b4067f80cffc6e8e20e364466c6c48b1b9c4b454c97842bfa5db1ec31c96012fba0c9ae46ea1cc3c01f255e62355802f346645e1a576091db650d0f81d2c8a91302a7c57eeaf7da1ddeb951712771970c3e79db2480020efb4d1aee8358291f60877b0415425aa07218829db3a63f1664682340f9d606bb8da7283584fab572d0d671865cd0384e2023b44803cd305d8321356c849f2ce195f97e45380328f8896856a5f7608437c462f4bb654f4af8b7e8a1206ab643e8d112e67bf54972ea04ea4d1f49aa6536a8472b0b8ef7cd71c0b63be0f4b68ed863daa1a1b986306912d0e04185d9afb107b1b6be1be80791eab0f42694ab025e2adf1f0c0c8daa0a9dd6867aa0a605a0907e3d5e712d7ddb92e1ee8d2bd4179b490343010f0c37a3f4f82f6bee81a0d702c5221a974b46103cf5326fc564f245b57c9a06746cea4ef72cdb99ebc3efa62ef718a915d846007747365f1e753f288bc45f6963d7d5d22afb9f07e98a5848c7383fd8b6005a1d6e07444c348511835ca6de25ca22ded8b596789dd7a15058a55750f82636cc1662be4f793a826806b62e01afad793808aa96e2abbf3e072fedbef9e15ab64bed3bf4dd1957aed1d5a2ce450818009714bbf3754cd1ddc5a22268f52f686e614d47db841e7f68f98758620e933880c760adadb8cc037aeadbc48a341937deae9e0286ed502ff241dd85b82c74821e244ccee170573889db36bc92704f41f3e9e0800e166f0fb593c47ca7037afefa985820d23a4a0705cfd5782493f82f01ed07c6bf0f68a8f536c16b7d303934bccd2ba773c013d734404dc4d9bc886637103800ba331be0ddf0673d47fd7b86a774c8783b3cc886f642b2c7303e6bbf2464efbdb7dc5bca94520a0f096e09b709af7d2844ee17f92921e451f96daad0505579fee42ce2161b6f313f55833931d8bee94d59d2ccb22df33efb1c20d792254d908754a1b3840657aadee1e7ffc1f7f77ce1911f10e3fd7ef44ecfe7a8dc060a1a0295d03e5df987a4c03ab5b7a8439ec0820747434822c509960b614059a3a98b2a82e08115f6d4fe85a2f64755e760a5f6cf2455afe626b55a8d93035daeae520e4dd4adae520e453554d55dd2b67438b32d63235938778376bf0994917ba8280deed790a10c84ab4ffce95c8df8e7bf653b15d6c83efbffeeeeee4eba065d2c4644c1f28469b6cf6231a2a8f673585c1066b5acceaa11c740b758f22f8edd2b1f3381253f253fdde3607c96f43057a2625d1ee64a284b7e24f0aa8409e45f9e92c992df9d0ab153813b153689253fa07e6fb9c5d3489909482209259458d6f45554820609aa5282fa4e338b0609cadd818ddb25c65d7f7541361976cd13f7289b0f7b7cd853f76785fc69ba3b6edb4d406808231ad00022cb942dbe60b5d77d21179170341ed79ea1d277ef08576b6c3c2552a90fec9df8341e57d905d1302827bb3917ec7ebb67249c17632743d59ec1eebde3be7086aa3dffa67d31a668a0803d1a8c4834a7c1a86a30fe85c47eb84975c3af5c3db2226b27bf8a7b7a6a154bb84476e7c0a0b1dddf3bc6f84b96b0525175af4429b2b2440a777472e1a249cdc2c550dd2c4c647eea66f122a67217744304ba55fec73508038a7d38db872bcb50339f5573a19a69afc180554d732852b873cfdecb3e2eead13efe3111df2b66f1abd8bbbb3143b75e0fe4334cc9de4112314b88a8abb48449ef5057298999ba6d1774e38476922f75959270a921b3a0f22f96ba4a4986aa77413744b4df7d6968f663250d32b7b6d030564ee9b48f7cae93a3aa9cf6596ed33ef7545443754f3faec118e9a3a8b7952e8d7be41a649447532a2fe775c01e3fbc1f4f4883fc2da05ca3cbdafc1961a299243ac718e38d13206e7ca0ab394b693e02a54196420428554a1562662ab6d58aa01181c64d667375a477e46fa853a5102d0a699f75ff5c68507e1785da482ef66ae8fb24cffa7e7f6d9ee6fdd7c6c66ddebfe6b7c19a3fa2c2f10f0559fdfd93a1c19a8f0dd63c50afb02db772c177e4c73f71a72e68fea90bca9cdde36103ff0d0cbfe77d38bf39478277e40524b173e10be5dff1fdd793f028d49f4effb30bfa0f07857ad4e9f437247c38a837c28f3dea777ca6facec3f19a7783f3b84a942a512e8ee36244c50db56d31d2ede4454a4f1f3dfe53fc6d8bf169fc11baa018e2ebf86deef0b641146ac70effed3a94ffc9bba011fc47f84d725f67428ea72fbb13747cfcd805e9f8af0bc279ad7381adf89cfef4b19570c3e4bcf6b13b61841f61ab79f66c7e8747f3db91f0400ed424cc47cd11bc5483f27370bcd7013dafa741f939bc9f06e5ab3ca006e5e3f04a6850fe0d6f480a2a3428c4c653e2fb9aef5760a17d68fefb70b78dbe4eed2143e577e7dcdb9a98ebb8f38dfa9fb66ddbbaaddb62f0a7f1b641fa9f37341b01f4933c6b0aea67fbd0adebba6d3b5154f71bfbe8de3dfece0b372feb7eebb28b6b9fed62a0e16477828c5d5017f41d5b79175ef227c46577c236d8bd7f27276f9ffcc9b09520ee8b0dce504895afc92ef586074efb42e35f9c3442abd0d86e84a0f16b6297a87541fd63fc1e2cc3300cc37e6b67de4d10b47f622ac89edb9e731acc7e9f3d55835996cde76e7ed0b21aaee0205e58cf967d49968cc51ef2eb515529dc34a1dec395dc40454ae9de31c6e8524a295b4a29a5945246295f4a29a594f2b52e687c19e5ef5284b2add606767accabf928509e2b0ae85a710495ba66ea5af18469dbbaa09adfbaa0133bdfd064bf38d5f86fdbc65d1066f3ed711764f3e1609b734135df435228d2ed956e7e7b5cbb5ffb23aa8abdd605611f4e7f287c3234a87d6c507b89d36fc4fffa3e3d7741a7dfb25985394e89284a25405da51dccb87b47645623db9d00e4996704060c06ac629f91fead5d90fbe4bc399f3dff19e45fe6694d84fb57754131f06fbfe5e0b83b81e639768fbee675ed276f3e90d778d80339c80de9d550ea933c2bf54da11fbf84f6e161034a1ff5cc943e7da06e9f5842efc412ba2668f6e15e098fa86a17e41f0e7f28c8cacf9f0c60f79151b4fbf8d49b0d6a8ff252dc47f793fbc9352535e4aa4540fb967d68efdef6a5fab71732fb6bb20b8a617bef4e88dbfb76413166d96f3d7630a1e15ed1be84212914699f66366a3055a3c6443dc626940ddbeaec20a2fd21773304e59f0d5660db1b867c54dffd4e9a16f454fd4ff8661ab68e47a27bffe51ebdb95791b54bc9843baa64ce0bd51199237bfcd15b1adfc16f1d8a98435ae5ec171af41d73c5a805a85fe815b3b4f8ea05576d2f5c8d31cdef9687acc59398a877fcc139867214551287d102ee126b79a177fcc3fd54356a66687fc6cf953dbe1e2856cfbc192be6c58b53f9b351bfe0abddf8ad2dc3444a2a50633c85d81601f42b5730759586bad4f83d759586a0ac7af44efc9efeedec2103fa75af2fa45d107f8f5e312bfe8f6d8b3cbb131f0153013562a9f1fdebb13cf1352da88351a7c16f30166d33e59faac1f9dafcad81621f72373b64cde30aeafe338b6d3536686c9071541ced81aab6ed6d8bcf1ee3d3546418b8366fd95667ebee96967666c952fa4d34438474762985007d3f0de248ea36c28cd01ceea8fb5aea76475513b39146ab86fba5c67b289932b58aba54b40125cb863f2227bba0f8d725658e2466e3cb5e6183490df7cb081cae9865feab65cbee547b11e3befd3730a5f27e098313fe96727f2be7a02e156c48a9b1863f3efb40fb3acb58eafe4ffb2c156c0cd57d21eeee530a3dd346952b0448fe34b82f3ff01b5cba030d5521277fc70271ff0be7bae48dd66eacfb1d0053f77737269531b34b6b14edaf991aeae8aceffe25fddb87afd49d9ca2cc42a01f72f57108f343992b3388820a6bbf88afbe8a2f986c88a28b285f58bb4c5a75b19c29427de78f5b246871a3490da6a2eabfe9a031a2a1aafabe74b240a2168faa1857364bdfa3fa97d03e4b451927d57f7baa0ba91e7752ce7f434e16f90ddbeacc289b6afcf942acf832ccf5d8ab70cd30853f15b09fdf0cd887c3f367b8aec75e887565cbb44cdefb4e4c8141afc7fe67815c20015dd4f8415822a3c60d6385e589ff03bb96493249253d2bd598168ec8c0434335fec68ff1267694cd01d163238ec2040db9294ad59e9b5cfb75aa0a5ffb9ef6d1b417d23ed86bff030ab75ea3400b84abf6cc44fb0ef42ab2b470a7e83e5c2bc2cd62b51553353255d3b4544d0bbb2f9c93fb68d5b68fa82e8f6e075bb65cc1a5a9cedfb6dd8c28a1825d624da1f16ca0f99a2f525d865daa27a0f9adf1661062d114a1f968de3d14b46aa4b99ec6b341a8955444a464a8061a5aac1b8458cb4283b38506e70395f0a3c1f9db05fda46aa0da6f56d0f91b5369d03f4a048a71a58f7927faa17e6b8e3b13b8437d1ca5c1f9db3af7097be7a10582fd7c56d23ea88fa96c8c05fd48a5acd8c746cb337fbb01e51ef5d8cf8f0d62d94b2f063ef1d0500c1ca5cee7bc1049f4196ee0f0857bdc92a1c1f9a92568c84665ea7c36f2e994aa546f74c2cef9f35f689febe792690a53e77c16da67af405154e7ffe85564cd2f32bd9d3a9384d479a5ce493466ce5f9df919e1a6acb934b655a93ca7073cf0a08c1a5f0ae7f6c21989747777776e2f3a3f29777777e7e6c2dddddbdddd23737177777e775671e1eeeece5c17eeeeceefcecd5eb8bbbb3bb717777777ee2edcdd9ddf9d3b3a1744558c312489b608a15172e5082164a21041e440868b0d9389510269c31cd9262f57d1d212d775556eb55a5d24d7600a1b48249225916fb55a5d62eb4035802d98f0a2d28196b46028ad404bc201126cb65a2d249873cb9962e7b40f92b8499129750021b88c913ac2ed164c6cd104c7e446ed04151fdc888b6dd2b660c2cb36f5dc30b145133d48ba5bad560b49338139b8f8800ac8652211154bb80c171dd41fffd6e62641e4ce638ccc2fd4cc396c91fda577920410359800a2260ae2a451ab65c9e0ffd19d9d3907db5689e261dcb6ee6e3943a1befe8afdf1a394ed1f14439fc0b994d5b677b5bde34fd987ffd6d49a0ad41f9031ba4a45e9757d1faecfcf7515c11e01150015a8691241f0e852da48413f757d613c92f262837481704d5def7f7d8a260c4195ef434353695cba3815874828ebee7724f555f735367fc72e19bd976e161caad490e3b2c839d8361ee84644c3597558db86da6e2e5a6a1428432d6ff22f56aa3869d2647acc12172951940c31186e4ab24284932db364ba2cd59e1e62f8f9eb8296b848891295c421304d49562a11eea40c992e4b46529a492b0903e64a1217311166cad465ea525171291168ecee82f8fbd1e0fe0e478940c3c8421e3dfaa987aff5c64215d45314db6e2c8a449829c3d4858a911339133161b45c81d245dd32f385a90a156e529968cc91e53244e71283f21779d94f42cc528a70d67e9d7ed00a27c4d47e6e07dd49b61d5828876199b671d8e518b769d9e4e22ecb7360fe99b2bbafad2878f58f5e4f83aa2a941fa8dbb3887cc28fe5d9e48c1ea3c7cd5923568dc4c7917f3dd7040d636d1dafbbbbbf41eeeece89f2fa504ad9d794f343296355c99f0d5212ba70a9195e3fbffb49e84223ed9fdce21009638d37357e581b71bac03d47f5e114b93e47cd2a50b398b8bebfdf74f7a791427dab6f8c32935ed72b5e7f43af8fdee9eaaa77ff60df7f1bf4df08f8676449e8010d69dd52ed133915776a36b8fe513992fa924a2d55ae1b2a466a4439859e5cec8e357a2534c84bbe54815aa7fb062ee3e454fb85f1f7fde77a741ad98f721b095d6814344cbdff460fe76525c2ad5695f2eb32b6dd207bb18f209d2dcfca0643720ac9c5448f7095638c47b8c84168a93146fbe517ca0faf0fe79c59967de6f126a5bcae8b72dddc7dfa4ef48c6c9fd5ed5488ad56acbb6d5ca91761f466839ba2eedc87557ab3c1e813a3fc70e28f5077d4d5f178b4dd2041ca28636c4e5519a37f57dd77a6ed54f7da9d8d517ef4e64af6717d31c66ffe008381570e351f578782a330828a69180a29b86a6c5e671ab65d9fcd51b141f937451be41a9ccd9e455c90fbe5322d7644aeeedb05396e062dfbe210e882a923dc8291be3017e49044cab197f1f13327b2c7b078798c6d50fe7ead9b3da8611ffd920a2167aa6e7a40f963f69763d903fc63c699945be63fb1cfbc90c3dec3c85d043a145010ce3a3b221d0a28a8f1a591f85132684efbec82fecb72bb66e61a5fa0f6abc5df3c8d7df45f9d095de537d947ff54e30b109ad050fddb056316a7632da357d97683f8f7ee02d8c7fe7401e55f21403a48f30ffbf8d160cff7cf5dc5efef63f47a449f7e15b45077f401b747796133db346e01fefb4cb7ae47fb60ffc341b4df07c24138880ae5d1ee2ae71ce39ecefa9001083150fdc37458d0bc5f1ff2af6f039997b3a07cd911e91a5db5a0fc98d381eabf3bf2378f8874212e28bf3b22b4c679c967ffafb38e6814c520950317802ac67c01050852b2c85264854dd1171afe22643426871c8c6e68410f470ee8418d27607ce0a1054578f2829b274160ec8590122d88b852449736c04879a2d4bd60a9db9e3c41b7a2324e456d50ba6de16a708a72a4ba8d14943fd2dddd3d920ab2eb2346adc6e7d5b592ef5fe84ffdd9a9efec4b6f7dc77990653768d907d4209750a6d35284195a478bf0a265452ce0b26e2b1a422ba2075bf84306e6a3c3132724a81848b10827fc8a0cf309c19e04e820440e197802072eb0b22508295414d1e28a6082ea628b23bc8851030b76802288228648512f6ec6d0128086a44006a67ad58dba4a3ad8a05ac2aaf09225a08aaccad46a82f044db2a29d503bab87245e989245dc0bc608af60228281757acb1822f921c11851345b03afb7a7e66cffc7e3c912f4802f3f9a932351f32b094979aa202cab6ebeedf1637c6ef90fd657b11b5dbee36afb715db2e3b211d08f3b2fc8b9f03b223f2d5887cff8b6b8cd74ffd3dfd4213e2e7554872b7a089b56cb392311c65bf7bb216e5dfa53ec35d5ed8f49e9e8a621ef8ffd3d333c4771a688522bbd4c4cd6574625722395582004627c98643edaa275beec121031a7295b1c935e01a4c51da68d0abae12932ab5c7571c9521563f176beb80970de8d6905bc5fe745294db9eca3f7a7e8424802a3169824b71595d2526467049d28cd72103627b4192eb054c364987690e268eba4a4c72a8b40d6ccbf661fcd820cdf2eccff81b235d9e65192cc7dccd2d89a2749730042971bd0a1e9fd8f657a73d465f8f59d67ca1a12ca2f2f3cb227e59c4b25156f78c1aeed4fd70a776770b49a206afeb9b4105ac95498ed4a407474918f124ca0d3ea071f9b0c6c5e4450e6028a1c4c5074b6838239b0c317b01438723b86881a1048d229adc209be02075b059bef8f003d20e86304689b78c275c6ad0c51136cc000a2d98bde460bb1205109c3041c50e3fa862f68046eac84ff56f245e89d440b1c287233fad832974a05269ea4ec124a926d5c93baa0a675740bdc3015f6481dc787e2e7a1eb34052cfdf490bc4e6f99d6881d43cbf372d101cdf1e56e35d6fe3d57ccab3f91b2ff537bc9b5779375e07d51fc763de8d9a44f5975b1ad57f7257aa7fe643f5d7ba30aaffc675a7dfe6875b1223641f6e69e45cc1d93eec7c684f07f7611786e4dacbd17d783a6a4f757a1c5553b265aa9f55f5460be48609372a0ed4879a92ea8fc34bb596877e2897d8182d0faae525a1395a0b24bed300f98d1bafc8f2f0a73c2e5a1e7e1b8fc72c0f7f8dd7494ee44dcbc34f935a0ae3510d51adea8ff2c2140d75b3a84f420e42ed2942e7a13a18953f5b523d944cd55fb616c8356b887d384466ed9fb5bfb0c66f7d2d5bcbc37f54397ad16879f875788e254e8b2de45f8efc3c83b8e5b909831a9154c3c9b551c388fd40b9c6c7c2fb718370696a430824b04421630345d061298c0fae50ea81656689073f2485d9a9affdd388219eb4513b35c6092d464668910312ac90a2608a9729625441854c0f58de2bba40b8fa0f69a3ee152f506c9c97c270a0ce6056031aeae8f48a59fba4a976edee21cabca8f19613289a7a60e24596858993ea3f89e852374b5d252274a81b6a43751f9e4e1f76a857f5873d493b0bd20f379a0f352de573f3382a1c7f63a5a2599bdfcf1648cdef75fd0c2f8e39743c4de5d0f1f18a2f548e2157737cdb20906b5868b84b455e689f1b2aacc042075c681f950f8e6f9c0f91c43a3f65c27cffd4957d324bbd9cf3813a9b389e91f9eebfcd9fbf0d7eea81bc2e92581d07a76512d485222b7891eb73446c4e9c4fc7174ef96513dc512be3f06e780ca541265ecae3214f85e5e9aff128e0a5f6531decf29c3ace8b2f9535b06ba68a34caa82d46ed2fd260ffa98c270311373009e96054ebaa8be54cd245b4e46405463e0081e54cd285238b1d90f1c3162520ae18c152134a9e687103911a526cdb3e24b8aa91dd5f1edbb60fb8dbe1b63a32c81548fd392df82a6777f827f34d5d2c356855ff421ae6d4b00ffe8813e34adbf5556a8195ca4db58d805023a73251abd56ab1a6ac52104b6af8a332104d372a3b77a9fcb15b689f30e542fb7096d60e95f93bc06cf0095e9d83ece56d698c4962496f591b77f1795c4169d569f0bf9c06997ff927b7a495274dea2a3d194a405da5222dff64492546e98996ec48152f1d9e74f19b897ee57290c4929827434c78c01a2149185cb8000c19666a60f553557f9934a5ea516d21b3a4a585972224add005066d5c3146124d647418a3e9c0c6698d9e9f9e1f3b28d961872170139b3f7e84645c66d7d8420a1fd2a832c61a4a0480a2c60e9a58b1030d865020c60c20783006131ee820440e170f41081e6a9391c40c6670268b18fc90868eba4a4e8250a5982b8d6d75b655c680ea1fd2d42aaafbd81ddf6e0cd159c3786388620ba4fdc3ae364960fb2bb23cfe292edca1aafba83dcbb32b03ec8e7f8e1c353535f4119eaafb581e4783b6e0ef827f9115764c0cfa39b65aad5611ab7f6fe608950150b90b6a6e79f8b1ede6d85384241b4c5e1d5b09cbd3e1e57efc085171a91c01d45582c202273c702aa2a0435250d247ec1096f8103c444bb1031c6e90e45984692b4b1c898fe0b204aba12aebfe8efc5428ceb05135860306d4ddf3a35454a961aa1accb2e0e0bdba4a596090c58bea4b12dbc61218300ddd831a384c9185c75852d2c35eb1e4861fa8c481daec0a4893d74f093e210e462bf7119d2ae407bf3741635d25275dfc58519fcc899738698fde61d5e65095932da80a8789131c50b924046b1afbeb015663c510a30326ae5841e5075fa45177969e9061e58a275b8a300024564003262e4c86cec009a305126ce4e0822396c8e203304e400185510ec494c10a2999ba3850238b163348599c1081b5bf2e5460d3802832cab22c7ba13a6736e76c1762c3a9c00c4c86114bfbec9170c5000e7f01c6b06628c018d6f6d9735d6f064141aada51fa41e9872c4010d55502a25579ea2afd2044a534bbeaa9e20406550c11032dc8b0fab94301ab5b509c1fce90adf34fd83a5fa8aec7e30aaaf4030f756b0e637fcc13aaec21f17af94b2f62ed2ed81c95eaea14a517e65a3635672aca26e6b54777a6220eba56b0758c93cbb48deb3647a5e2b653a728dd3454bc5cc90877e9f5f2c42f945a87827f94357edca89211349bd6054913b0207a1688dc1c95eaea14a518e7158bdf60ef34d7cb13ffeaf8ee557ff6df26b702bbc1de067ba3f7ee7216b7678c7dcf037495783053430e8d902af110a6ae120f4c75ffaaabc4c3126ba0676b4fe5adef5f3f397af57417b4660a8d51520683d53821c3c87e663552e27cedbb5e9b9f65bf55d3b26c6251faa047e7cfe86a62aa6674c69eeeb21ed713be5483bebece33323db64ed3fbf1b0c1bc1ec37eebe42e05a1eb931dcf98349bdcc7c9456e7219563411a6cec77e662bbb20ae9d933a83f29ac13efbb09ff3b766f3b16ffe11454b304154fbbf8b8a0bdd2b3f7a255bad9f1e5487e6d054d3a1f6893a6296e43bfd5c522a292749a7472413cdd49543155bd9a4ee4731bd8a4bbed349becac26461929292923231da50662613938de126d39cdf3638695c526171292ef5b2115d251e92aafcaf494361ea26d92026c986a46ac599a5ea524757045daa59986c0ca52ad5771626290be3abb8d43bfdf2ff552a4a6397282682d112bf74092353ede727287f5ca04eaa21e52e3072494747040de35218c94412b54f18976a4b0e5d51b2f46385198aae701826465dba0c11490e4649944264a7ca8f9e1c8a0b848bb48f7c6cb9f4e815f5cc933a2a2a6dc0a492ded9209286f5215b4ad090b6e450eff473210d652b7c4fb6641499619c1c6ad90a2593fa556eb9814bdd2d3758a96116a6f613a0ee961b886aac5d2739c95b8db38b0076e6cc99346aa40db26cf54eff9c94aa54ffb4368f25280e0962c51e0654c5be56edc7ba08f8631f09b84a1c146afc8cc8979f8cd25bcbc8dbf24494e3d4aafd44d4586364ac45846c52c389b5c034d81f37351885865c93a62f956bd20cd626dfe91f3341ffc267c52f54c9ca430621d7c449afba31ce35e15235e4a6906b525d7663c0e739e3b2ea8ec751bde7aeb0dd715fde58799b5d1077290899f075f68ac1f44e7f73f2e32633b89e5a1816d8e4aa12dccb2ea83b0b08b1388cfbb6efb24fd5e0f6a906b70f43a76e1fa6eaf625b4cff63aed73c2f7d8b6ffd13e277c0aed137ffba9db37b6cd8d0729dbf6dcea6cdc0d042bacb801164c6cd9c2e50a2bacb80116433451d57ed33eccb40f69c0aa7d98a3d2df16a8636cd4fcb2efec63a606b5a6b240fab5e72aed937d0ce637f76ca04f1323fda6a1f4a31f3d49bd4b5266d2a0a6bdd0a086a441ad030d6a2e34a871bf6da1614fd5b62e32185e2ef4b7bee4252f7949f6eb259b106b7fdcb43cda6f57d09ea6b9bd7b3170945462e07af24266aada635e88444a83da73dadfc08186dcf4dcd43e61b320cbb375bb09304b6381deceee2c024203d47e6e75801001b57fd3bef79d1f5036283f20e47fd0bd9c069b7d3419b3af2b11550617ca0508d41f15c4b949fbe01091df2354238ef455f81307858f045c71885cd78783fdd6eb6bfc14da0787361857cb8a4f5539aaff16088bc7ae1ab03bf1552a1b9bad865b059264d0dfa4a54b0b8a198711ff08a66a770f773791a6df1addd3ffd3ff6092108c8eecaa38ba2e2507d9ea8969812c15b15ed717befcb81b21d01c752348552823bf4152f5a01b21d0f8fb1ca4abfe3ae0290c47854e5a7196045c6383385526d1f3f3a0bba77a7e1c95e45a145ddc3ee3106fd96a7190a8a5065e816088efc435a38735e385d63649b9d68ce6d66a91ff3f643908f387cb834ae7cb0800f9f7575b2d25442d254b5aad56abd5e296efc44fa1c890068b2e36381aeabe46030d1ce444438c9f23ab11bc6226bc43a557237ca48c54434dc7873935e4229c0ff94ace87cca5c6e71df8e308df909c6f7b875b38df0abd13bfed9d15da47f5f159e020393efe061688086e3c8eefef3edc2fe49fae477db860604f3fd4e6d37cb83dc8bee6434e7b9b0f578ced534c22e0fee6c31d233e0478b59b0207c9f1c51fd23eaa6f5d4b7855f62e88c71554ababc50626f5c90281a13342fee988eb5150e233120b04069a24b2af51427b9b28dba796e0fee6230c2210aa38be6b79f86f7cf16fbc0c5b1eff1aef5a1e7f1a4f2e8f3ff5e2f2f8a33c5f1e5f1e7fcee3e1c73c96938db868fa4ac7c7a73473ce3957071371938f9934189949bdd11a129b10684eddbfb6a05fc3df2b36a0b2454b1643b09284c5b346921ec0c82245134bb0fa1fec6e28367e7f1c143e23df4ff515f6fd5949110ee2d51fe5a4c8b91ba8cac3e9f554c73cb0fab3d7959dddddddddddddfdfa8a442fa141dfd6a0fbe15e896bd0a4177c273e07457290adf1590b2a0c47e19f1f3253b73de6b5d0ea9df8f1e36ea02d1ee220497825c48aac78eabec949d9171ff33ae0b5d0607cee091af66c96517eb23bad398d9cdcaeec460814d5e33fbe427d2c72faed88b82041c04142be3e2e5a9ef85819b41ff5b2ab807f7f385805fa511f0e26c46223df8934aff29df853a6a8409fe69b81fe46d49702140ac33e163e1c14b09f3f3f2333760f98df0a1fced61e4980f1bd8b40c83ee25f1f08d847fcee1e20bf23ec23befc1e5f9d9ea3f0125cd4c345614f0f17853d5c14f67051d873fab8c877e24b31f4ab9093246480858b1958a810b162192c8a00429429a460228a9612e9f5b01157e1229aa4b5d646442dd5e630ad310cc3304c7b0ed3bcec4608b4a6aee47eb4daffe3548693010d7bea9c5dec55f7b387ba4ff2acee9b7255eca3a98a7dc67d482bd73bd877de464d70cc8baf79d71f49d50be30efbbdfe5a5fa5686a0cfa15a87f487f4fff4f3f6a69ebcb4afc4dc717899108fcf91e15e8ab56a17df833d27fade0abed221c2455e3a3b6a99d51d559dc0d3446e0fa7e1a8c2fcfd0f0aafc917df003e4f7b7118cdf5f91065d558b78569ba629fe5c202b64bbbac0eec4fff931800176763c2f478e1a439a1a9fb733680ad7b7ad71c50bfd1abf055fed192b4d679cd9c249131e58f15df09550113e183346a9280a229cc18a5f24fe0a63ae32dbea84a8a21a7ff3f77c90c282c657f949cb0259d518698c74bb5418b4a28a9ab6d51162c9df9f4586d677de7b7c077b2a6798a2c2f5d837c3f5ad4b29bf1448d9fdf1fbebf1e1a0f019011bacbbd4cd411853fd8ba8d0a07f6ba8412c68c84b15477b2156cdfbf31207e1aa3f6bc141380eb2bd77ef4e16c85251fdb98caf6e70b4afc810cda75e8865f3e1fbdf782df84e83fe9df74283fe29afc6e326cfc6534dd59c5f0a6c1e9b8197282dd6b2388aeff84fe97e59537088749f7df7b195affb8c7095062bf7040d998883705fa884cd5780cd88c196162b052af85a366ff3a960caf636efcf5b38c8f6e110d9befb6dfb60f033b5fb70b817aa1d1618f6d90bbd70a57d9034c885836c567c3fa213b00fffcbe3306f7acd3ef86dbc5423809f9b42436e19e99e7bff8df3b0cd08120fd9c8f5dc6f6702f6d7ffe57527cc201b3128b73a6c694c97f609978deaefe192a95fdac79988fb5add5696ff764c24af506308387061c6066a70f105006620b1060ea4d8c1cb16ae879399cca37f7dd7755d1bf75d87a4761f08b87a7d46b89f3f43aac6f91bf71bcef6d777dfe4041c04fb7088b89f3953b32fe42a02195aadcd1341f7dbef0876d4eb3b6f48f6d7e60dd9da6d0b7bed3d4cd5ebb7cea30961e033f5d4d5eb436eabd787da07eaf5e10776794254bdbedb6c1ea83b6f2b13b54f935ed97cb177b677ce0bb9baa13ef32ae7a19e7adcc443bd7741287f390613add4f8620a286200850c2a1bc60da01c210db92006ad1370903d7da1f6f485f6d1dedf055fd9bc7f0bbea2b1f1683c9ceb8558dce43bd7576448284cf5c7c1429755f33fbee33f45059b292ad07c0dcd67f3f1a8015d96cd2fabc6c6bb7e5953b69f01a70af59afdb26cf0336758538c5c0f80c1cf7c3844b29f1e8de785aa701659dff97eb81eb4ef870e34dc2f2cb4cf45247b18fccc99ba7d215fdf3dd6a5411f727df7656528337dbcd4a07f1b34d47e7b8ca87de65f42d8c77e40d8c7fefc16c03eb6a7c17d168a08d590aeae141af4e7a5c5c1d214349ad821c9961a58fe1cc6574252a6d062064b68818529072c6fc1c90f3b8012041f744982e5cf4cbe2ac11840f4b024c60c88ccb07ccca96f96d46e14981c464818c5314784596a419706835a252492babb97e8b0e8314d4308910d0105161db7c56ac9938468c12c0a828a2db81dc0209a81106b00a10a8b7f2004145f74c0e4891a5fd060a3624385326a63139bcd13338c46757f1fdbbff6e57e749b27babbdbe609cadfdd981448484848484848374829241ba41a241aa41d8f44834491504827a40ee93da40e8943da903424ef43d29032a489f4f1409a4818120f1d240c490700480008c0cd9018ee4224ecf823128e4638ca39c239d27194e34875f4de91ea08c7d18da39ba3d491cd91f71dd91cd51cd11cd123d4d1c7e30875743aea8eb8231e3a47dcd176a41de900e0483bca8e001080a3ec280002381200c86d916fa8788c3d70e984c6ed515ed8ccb48deb4e284a536393bab981439543074ece0824704d45ad689483a323870ac70d12768ce0444572a9e9c64dcaa68666c7e774529113d150d4a97b0f8789ae319dd471db35a6f66bdea78389b0a4315a363f1e39bee6a249c443475567535d1d6166d454fbb1ce841b35b331a2fc616684a3f2b001dda51b756d8c00c0ed6f01e0b253eb66896c2d10ff8d461818eec9d6d0b63a618ea5968ccfdf1a2d1012ea925061e033351ec15457e9082506a8ab64449718b8b540b0f71b9eea6fbc1b9ff26edec64b7d8d67f39757f3ed5d8f79fddb9d70a362aae7a07da8942c9775490b992a1a010000001315002028100a884462c168304f7459fa14800c8aac4462481949a324484110c33090418418658c01001860880c150d7500bc2829045770dcb203c4c8542f1d3ef89961411794a4e0d4937febe59952e7d4794c643a4b12a8c4be5f21128c24474de1ecb4b9ce79acd28ab490be4965322991d603b7b70ee3431f8c0d5c88f462a24d61dbc3ec972a837baa3c6b862e88a0a0d63c37d9aa118edaa075d07505232474ce41422ee6aa790cac07dadea7817c68fd2fea38b3502e10db5e852cf087bbc95d959ac7c6808d5100ba56e525a40989383f92bde8b874c0568e42e95c551ae3cb4e81df1bb1d18ecb9503d174446e20e83ae3ee4ed60a183438383a326ebb72a96353de6565ed81151daf2d34b61ab456a653291fa534f0a443be084e9331f63cf648d46096ba457c2b8de7ede5bc4c3b1163d348d100630e43cd1441f45c946021d5fc38d86da44ab9044b959e8d1cea68838beea1598465919b81495db6049f38e4e674e1e7e48ee6f44f7ed6b0c629340a8ccadb441f010550e2242940ea3f4ccb555e32e3a96e310d3e00873a4cfef9a79cbfc0a9967d75eba7b48fa7713c492389e607f3e489a1a7b56639b474f5b27168bc393f5bc3d4726e72b2e703296972ae33569d4c039eba21a63f85728e4bde2435b2b098726d6e13a6b30d932d531580d23a3543a5d73cb6fa3611444974f77267de19ce4423802cf4e4659fb0ee586acf8bd03ce6e6a7c4b24597c1349273a8a079c41c3c54f28d33bb8f6b4ada9bf1747a21ff221e3e3ad800ca28968e5615de1479009bfdce1bf5949ef9c299d6512787b0a95b2567ce5170854701336a061b49bf4a700be1c769ed9aa1482dc2a7d0a17ebbd0703301efc55980879ac8457f4e14a0748e7bf3a444e84944d51386b027281a69f7c94b1c813c92c68055d8db16d0ca0339b222e3827365f98a69797be332d24bdb084149f3d7292bfbf9dd55c7d1745aeca55c8ac317c74004b4943d2f1a622bc8e5adfbab8b4eeeeda2d8281d806efaafa16c65af7826807c99bc985b265ce5ca045c4413c9ea8feb42c5bc45620675ea097eb9d29955ff8416018e767f96d256f1b3a5a304fa1645e808f4cad7d3d916e477611426b9110b326dd8c6050935eb7f0760e3cc85b8b9b765159ab1ffdc70525532ca21522381c614b0e7f9d4e9a62a8dae3ff76cf0d3b65eed69a3158514694adad424c73929962cb5ea650e07acecd6b4ffb1ec855f8027b29bc30867b9c246187105f17d6afd12ad1322c4093d25f89c919d3b1a24fa3737a837b54b09ec9a3600008b602ab9051fb43c1f73f391cf8d5953e2f727d64564f31b9cd67a0bc70bbbb4537cbd7cce94e2397b667fe09fb6f728bc7cfd13f82760c133747711507a93c3b4c57db846382cfe5d553e6c9c1400befe39d3c1af2d68777d51ba223e2bc76ac6936364de86daf618cb5d68d2b4aeffc310cafe31b69d551a418cd9e33802c52542226f8eb9b490a2490364be57e84155589fa8f6d31db16662fd2555f1831d395e7c1c8be1af17c62fffb476f166b7e2a31d81d9477fb6a27f3a7934231ea52e0af3f4a34a77448720ce5493785409fd71a25fb9c7f0054098ae9e215c5d95a07bb89b417517d1c8c15afb90222527eae9eb5aaffe695b0cce5a4bed0238628c064ccf927d28b8323940b72547a09be28d161c7cb6e1bb7a68bd0fcde7c00e10f5868532f09fa26fec828818021ddacc0bbc5509b906845a8ca45572e9cd6e08b962709fcf2668fa61939495ed68411b6c0f3ccd01e8eb008be68f0809fa6c3db6906f771c9cba4c83df5c6c509f0fcb33caa45867d1da96bb53a353674776509f9b0449ea8728cb7091aa9f9c78594384b0ffc70468aa71fde2257c92fc4f2bf05d920f8cacfdcb944595eb5d9a20992a69944351637d9f9e1479de7003b2f19f380ea6aac04fa4858c0aa2d5986eeb4918fecad925a6076e7726b94787065409c622d59fd7eb980aeacf1fa2e7e7f465b3219429a6f2ce5dc3213c4ac6d260e6c8cf09a659e4f894f134d8fb907198d2c9c2dab1285de9ef58879b30d8994d5d0b95e40747b40cb0b45058fbaa3a77933cd8d71523369c39562629e90d88b117c4bd3d09b638d2cb43718720bb355d112f2d214bb429d4a30bd9540e10b8a606e050c28f874acc8a668e56aa3248d00a3bd10c8018c807d9baee1063b6603ea0223559f8a6742e01199ab4d118e460fbe33502bc333fe660732051e8d3da1b7e7b169d5c4219fb884452d8ce8ccfa223440b0a8beea908f3672774c6967082750b5ec93faa36b0c94d73f0a5baef051ece636b184db6e50551c241b59b175f56ec54bb5d38f34752ad5a5b79df8f6f2903e98ca15690191e4579f3608671f0dae9aeb91a6e369a04d2d0e0a96bbdc6b48b7aad50d7b2ce250bfe85e9bc92abe74adeeb8e6f6a7f90b28d7ad35a5e3a37ca642aede4f62e621ac6ee232c93ee1f3e73c513974b51e58c138f31b3d76d57b82ac7aa3a86cc773e00b2cb58581f186550bbc31e7cddf27a754ba19d2b75a00705914afcec268553d1f99079844872ea1bd61096c2f23a24649dc2ced9093c2db0d56395998beae7b459aa12a7ce0c18e347a52dde7490320bac2b1ac2e35aebb2e896c41ccd4b9ebf5a84b74278ef174a401f4733712a99511815d994068b7e122f78a89c143d00c89da105005ce5c4c057a05a81fb378ef935e592bf161bd979a71c138b3c23028e973a729aef8ecf8aae230d27e33c10e847e1548abe0805f7a795163c1d5f68ec8a4b28cb17de3d711ac75b3462a4b5802a27ba782a913592d7aa33705ad190b97d04c8e355f2d878f5412fb2370836904fbb9f877de800f43c367d949c520efbaa4c0a3ef055d6aeafce23810eb3e68654253ec75ac7d5c6e3c63667656e5ed7d2d117183eba737d47a1d38c9c79ce8fdc40c2805f2eb4c590ebff0de37bee3f527c876e759c70b0000c405e2f4b3885042c9f783ec3c6c0b1c47655d98d1de0e61fb82a7458337695f0387912c8191cc3a3b1370d381e9d5a200469c7514446d47a7c91980ad07159e7650e8729d5748f3c609b538fd5f2d1dab640c457cb788f3523cf45f3af7455353978d259659fbe80caa6ca951ade74208c21bf28465e80c6b703d8d4256d9041e0d1ed65547c793f521b46dcc8caf6a2ef71bb7da1ba1995d3b03fdff7f8269b756507406d2e3e70de4ba3a6e98db7277027f4de8a35f116cbaee4f1e504d2b1f9f24624e4bc948a54c405ee1064ebb363518a94743a23fab07f14f5b9891a841c8df7a6dd32a62c15c45e85b12e09425608eeade127ed86bddcbb0daff16f013af3e23cd230d9cc84108b9698463e76793fea3f13214a004e1ad658f0ad7c9d21d6817a7d004df93a5e293913c1c1157c281e156ced69bd1064c78287fb86bc00062a464845f7f0322de4ebf29c9a64f29d13725e46e5c85113b17372ef63e545ce5ee0267cdf6d23e1e6e17549b46c6f153ae0f91c12b8f098bab8a7db77255f6ba51363278559ae957199e8c373a680ed9d94a82aaed4b848e6c3b0c35732fec7fa0646b8bdfb715536fb8dd413c34f987f66a4bd5d113a8d371f1f396f88d341ca40e056db81d41b422e0ac44fb85491ebb1a70da5f4986f503161f1a5e25f14fae3fed0f75058482cf45b644a34c4bed084ee94a161c93d8faa758d2ad4df5dbfe2d1d0dda5cba06f14cff2684ef0f3b6ec3c80177d4ec09eb62815b03ce283d345b448cdee7f437825586215a08ed17bb1b6d0e69d7b1721f015493c0f19407a474f1c841d9dced1cd95f565feb2f056467b65de71e039113ad5cf27289a76ca00085346a896513d220042c4b8ceb76655f628af256e38e376a48e38e56272c37d4d008babd80c1d8578981065c8c35193c0fb66471be2d92535adfb62728389013d21f24415cf7243f0e49b41d7d2efe9f39e93a9d2e850e2402ef8d38c1c5169319a1d97edc8b8b195398a09174f04e260383c6f1be6668183bd96ca432a7bc5a880446a48888a6c7c8b19d6d1c2be53166c47e1e584a89b335588dcca67d7d732c0ea66f23324aca15f4ea7a15eb3e8fa762fd01347d06ea6e6c0ede175db6cccbb714756e9c82e9db74c2660581ee684cc43c6f9f19a08153fb7c1b096d3a7f9435af9f355df1b8e4e8a3c8f7de0d56b1099cbcb0d8a5a4541b215f6c142b50c34c30f02b9a8593b1deb27cedf5cab274102f26201af9ec4bfc078789232b5c1801418d7408155c680f948e77e467a28a735c026dcbbde7e8d0465d74ce88d7733ad1eec92408f69fa45cfa2fd9ce99f4d7cf6159f944a2be21b0661eaba6aa2dc820f80f3e37b002cd7555e01765cd08186b9a365797e2fadcc05479f50a5e2915ac1920a4969e82c04178a9625b7d8aeffa4c5a820d18ee083123b33bcc1e3f0ee04ce3cf863f1b288d6eebef5211dbb94d8489f9c591d2dcad9fe969232b19a9e3033caa49f503c20ab951a5032657784346151ed88379322fa16dca78e669b0eb5e0f2f627b873139a2680acbfd50fbcd3e378a4934e46b515a5524daed7a56ba644d368813a8bbe7a293b5d76ffcf79b2d194889f20e999025deabccfd7ab988db1a6ccdf7fea7f355701ce49ff4f1b6c1cbf6ec3c9ba06a6bde1fee6d3463f0a004be38010c03bde82c17245961f39f018efbeec7ee0ef9def863c7429e4436db655dabdb98eff4112e5c37a9a8d34e7c27ff4d482b3eaeca4d5c682e412eb2e687a76212dd67a7244e3b215623624a0aea03682102f3679819b174a687d2598d081219426284447816c51026a5d6807ff909f80c5e0d848493c4f0a995c341b5b7271049dc6f0e0c5defd03b764e9cbe1f1ad3baae4d2e3fad9988fb15010a21f7a438539770eedabdaf6f1b1a5e61100079a3655f38bac3d7c1cc929fa0410b8dcc431ba1e54fbcf06a8f6ba9c982c10724d8853cacb899bf9a14f0e755b97cee30d7511201ec205c99e623c7d34405adfb76067f85a93d77734141aa904df3baf040711957a9ff97ef5faedbdb6856a597819fffef7fe3eef64121bcebc8a1075beeeadc46cb782216dd23933f1edfeed446cb5822d653eeeed20177b2968f9f2b61b69d332f414ea789535d828f2756ec0b986c5af878126d797311b4949efceb06e115ac93a75a1581ff0435060a0d759de6e84bf6decc5384b9b867e8b72c0a84d7a0d3271fde955d1939277f7f6f1362cc04697afef96d8f6f927ceee70b616b171252cdaf9fbb78e62c97a4a70ab77bb781baf61a690cf6f07cbc55a0a7dfedd6d47d8b404cc841eaeb20e7b6ade1d3b202e81b86b6151ce7733ec46e8727d192c5b87eeb4d5befa01a48d40b82801b480fcf96797c0e204003d4823bb2b2c5388bf09f77980278b4b80465d95d0488729f628183b987554f7f1a471f9790153947bae448aa7dfd59892dc6f11b590fd5ec6a365c30c1af2f045d7c88dc71252807584087baaabe6205ce1e1febbbef377cf7bb1bfa7c1f9b009472ac8f9798ac23fbc12f447dc9977c3adeffb7eff2ee7f7343932dfd4190862fe3c5e3c9790430ebb130656876befaa7783f93efbe276aab1e73dd44dbab5022e85360796c6aa70b857ef92f8ce238d3dd3a1f6743812b92610bdbbc715fddd8e0c1a3575103ff8f51018ce759470d626d4573f86adcda25e4561ff68522062548121fc5b34ff6545c121db5206eeb32112b032f4e5b56642414f591d7a6c3843a6c259a7a07d705ce247dae6bc4bcd555031efacf6e1badf2f6f22d3debb4ce24dc0122f71ce827ba432d6ac7063f6e43ab311601e5175afa53464cc841f0fd96304cb4f183c7aac3ff6e9ce24b394abe70b4a18f0cd20225e2ab260253cd4727f45125ed8d983a9665efbbe0d4fada0c41c682ded6fd330fccffac3ad974271f2b1b6347f8da644b085d6424f1839fa13990949085d4a207241494528e4903caa10a17ec2104a0367b8eb6e71a121260e2c44b9d5f10bf4abc0847a1af76549e783abe20b7d665d23a376a10149451d45aa60ffe633b7016e6c2413e603e3b71922515630b6112f00cd8860cc4eef58d57ec27548e300b0b517d1a97d62a1f7205daff0fe5360b68f9f064ecedc8dfedef03fa2842b5346624c56165ef71cd1fae17f1edc4a065d2d2711db7075de6bef1bb68ae7f50dd92df2eccd0c316fa035cba203d804b16a4127e93eb6c34b21dd2e27b4ae28ab7b987fa56671244c6cc1ba91dfcaa3e888e215c5d7561804cd9a2f6f74cbbe1f38139a1a79948c4257f11f64355163b70b013119320fe6203f482499e1594e93d527d71d2d5736cf0e063d513634a3096b328aa08ae245445232852dfb4cb4070080b970963ab5c14c9d117d64ff82b364938a443e5963a28823c2c20a39d9fedb743c89bc811ccf377f114d2d563c167c8e4d55a5f3315226159b615389b8e77f912c05d74b201d484f6a312b6c80011a7cd3b5bd4a34584f3de9c4c74ee5695e73b62f4822b4e0e012492154c8ad4a6ba52771d3a28d21577a733615aad850a0cebe128005a1e4ef2053c7a756cd6d0a67b5b5f2dd936a64852d19ef54c6374636e3d9becb6b5760425ebab5017d37b0a7cb13a512f72a7a5ade1a4f350994d815d2d7b5c4bf3cbd5407f8516d33ea87422e9d3cf4810fe325bb32c7518cd33d7ad1a142483d76f4610b9842087ebc8203640abd20734c06ea8511edfb2165a8abbadbfbf9083661868df56bc78910a462c1f6995a4c60d1578abcc34263e6c242c3e27c1d8529ddfbaa03cfb45a12e432df83b0c61c2c09331532bd2fb254783b1ab6246b1dfed37d724b3a7bf94c1ebdea428eb95a381d73a08bc484ddc3909fb35a714e7d093f22773dc358da4e9442b5b90d9e1806c785306893be2806dc462206162da1013293a65c7d3931614945f4955b37676df1d36385c5cd8d1787f11a39e34023ae104ffa9ddd8031860478ba56a0423822afe31d9e410601c952115a63bb02afe37e97708047ce8aaa9a0a7ca16b426e14d986e01ca0a65884a98d8574d43bf5304d68d3eb6b9af9b72a09ca96a057496cbc6dd81a1a6c804a056f2d5f7e71f939cc8112f2cf206c88c621f91cf34792cd3a4fb644c7e476e62c064859d04f834f43d4a844440dd07db0065db88b1be70de19f5fc7230910e5baa6a3c3c0492f373659572929c5cac248acd6dc8f3472b6d0be040725108cc6ad3eeb1d25fc19791973ddf956ef062275bca0c053ee348e3e52d7b5808f5cd8ec0b431da9f1e1adbf3866f9ff862c3f788a29dfa3aefefa3017c8fc9869efaee546334ad6b73daf284f2c2abc7a76955faf035c6d6824700aea6b96f539d48a691f423c8b1936799a7c7d3eafcc8047d5c853ab40207170192fdfca9d95c593a913b6446e7acb4c951675fa9d9283da0186a36a9fd11371d313a6c9c89349ade37ba66b6ce581e37dd1a2bccbf3c543e85346932974a88afcca30645680eb5ce6f020252fee7bb7d286f22cbdc9746e7e6aa14cbb7a71017c7722b86071d14921a282596e42b05b9a734a7ab0f56a6f370fe46384a2444f957a498bd51586c0faec01e6e27729e1eb7b2e7e9d2a5f633dd77daafcd2d2336380b9086b8f9f653ff03157d1b9cd453a8d9284b2ccb6c3af29683f5b50b7b63d4b05b4f4382bee1a809b4e3246cd39633c30acaacab07948375cb8aa1f42120a0b7850d4cbb0acbfc053375e2a3488365a7f9d6b2c1b2afaece5d361b32c1454d878ab1bdf6603a803e46223791b92766d056908385d5ccd1971de2ba965a9e33516a49bbd5fe472c5c180563d46f7da14ccd946409e35f3b21c29f82e491a156a4560dbfca1a74cc3212e6df5cdbeab1c9c6b40db98249c04135ac5a042037b562d0096b90a5c58554dd51be286b4a883a7947156802720d788bd003725190968d999951815968c640f65ec7a9bd673352c757807f0a92ac7a148476b530e3e25b5ca1135dc82a5ba847bb8fc5903925a62f1a55e2a353b57843e989e1dd297efed38a175c1f2ebfb95f6905684d82ce0eff24a1506ed6a5dcb4ce9f74187fc494d974f3045ccc4e744f0e259e088a4763931037d61de64f9d3019e259a2dd12610b5c92991ea09026df91145600586e0cc36b55e1a3301c4b6a1870883ae03c713e6697e2aa112efa843070df34e038f5a67be4d2b05c690db0627a9779011ee997c358d11ac52896bd46fa369a099880f3349d3ba6e58a9644e0447fe4af7d7324a80d1914e4d2b5d44bdd52fc15e2da95e103e9894f41961a655472142f518d513e4869ac8b725f0876d47b60739c084269d762e85af22e764d8db1dfc0c8abe2647460f8d1f31fb4978274430b1bc7eb0a8ec7834d22f4cbc1f6bf4591f237eb2b867f6e73afd7a5b3261f103826d4850710b2979e5f95675629e179649d24d33cbe92ac8da18c3b5fff7a2d8ad20ea641d75221e2b99ac596e37a76d7fee1fd2ed76303fb5a58e8cb6e3eee61c19549bce8d70586be21b8f2f32c44bae795979248a14c6ea10bd1510df298f227631a4956c8c0c690bb46a86c99f251341c728d7f2c65ddc61bd8fc3dedc812a732778acd0515b7ca3cbdb13271cbd1f5833ff9d55e1dbdc9d25b288f64218f9a819f27daa2036e17d8efc7e88bb09619c9978a21de472255c58a2bd10686372b3f8ae73975964a144f43ab0b88e9ac585f32bf9297fc1277678fa3e90677c937445b17949da91c18347171fcb00896433711124114cde89c4429f3e5cb0a51e1217a0624b148ee83acd21eeccc37be93da9b6544a832ecf36af1d93ad63f9b9ea5275980dac41fd255830f114d60ac8b2dcc055612aba40e15762d8dcf1d88f15a7d194a57440ebe78ba531f8cd578238a2b0fa464a0c3a87e258fa54dfd48409111d6129b077cdd5975228c532a681907320c46e7a00ebc8187e0d7f873957c35de0ff10bfc983aa0c8d76af681b9fdd443211bd11723f12d3b7f415d3b6b5f423bfcfe26f22508231f503bdeba8cd17fa410be08ae1410f42a3168c823953ea4e655f531e3fe262e9e14005fcc2e459cbd171e40216f6ff14c39b061ed90e92a81623286bb3a637bfd9ec5a688e6494eae38caf8443fd62c7deeb87e56a53fd99228c0347cf72d360b070f1e5abf7d5c6435245607eb941d86e0c54e6fe7885d1b9f5836d513a220e12e8f8fcc2aff20f07596a4156b9e81c5a4692eea0de260ceaa647743c89b881a2308f761af3d28f1b73317829eab7ea7e169231d88e03f6cdfa559e7c5c7b3d8d38f6a0682f4ffe7f733deab8ea2f09ecfe787a35d356a45e81276f9851792f55ec9bee8dcf54658b99fd12a52be5cb6c99511b3068667b00829686322b4f380ef2045d6383db1e519c919fd20a8acb138cb2cc1352e78e048b3ed3c5540e7ac6941ec7241442e3cf143332e5844c4da6945c6d32d85908ace89f99c0f2676280dec5f4c5616c9d7ea1bfdcc44914a922cc166612bde34e894dec0750b36f9450cf3c0c4febc2c32e8c9775ddf162f3380b36627915796f9275bada588630cec82a9b598fcd5a0773f03b00c0e82487552ea6935d41a2a7ce2d075ec6da0c4a1826d8c89175988f0cddf85ae3e0a58bde1f349c1c7117f701fc191a8d6e4f51d181fa4120bc3c8b37eba33cef80c1fcba04f8f4ea0023f3a51e48be4d9cdc936a758e7b5c47e8a88b8db60fae75bef7cc2564529f88b9311924fa976acb768f8c532c7fecd2e788a2a84e522483a2cfa42c272d4604e10123143347992d34fe31ccf6575ee2fa0896348c417c7dc7def7649504897f1108f53c2cb4f6e3db44cb3d7f40d076593dbd94d673b2a8ed99c10c49019d28f478f45953f0645936ab1351acf0d0101ae4cf7ac2cd0c2d42e702381f92d63b3319fab5d23ab102303e6767ea9351f6bfb48cd09be121ab77662f73cc816a74917bfbc7a116d188d8f003658db8559461189f5e921e61c1cf058610b5e3868f4966bb1c825422f41e78d36dcf5f98b5df4d0ec6b295037d72dd290b2ac49d61cdbe1a9f4855fd52b88928974431c2b94c06b2b40f160415c4713dfa42c890c148c361275bf410ce56fc15078e0de08c2054e8ecf8a828818d53372c148455e6c413e6b2296ee116077200413c933686c116255d916f2921ad962376407462f9db0adfe7cf8e1645e7d00c7661b005051a834419e3cbe6cc38635400f900179439a72bc5a11089cc16886fe90f227507543b23375bc77fc74f2df2e7cd0663697730e074d37aa4733b67126934a681e840c31f6ecd9cbb1c5e9e513ea99c43e6165270f0757b12237b1a1b0385d8dc3a158dc2f55074ae814e75d3553898562414584fe448466331482f76beea91af7d9232f890838db6a17b49879615df9e435a877ac58d985e88f0be621debbe926892a2200d9460f22632a5236266ae29f7dc7ca9a88b0ddae906c82e7436a580b6b82bee88332503aa6e0f12d46b8edf358a6693f898d82a205a4f6b5e55411c0fff401059308579f133f789fe022769b4ee07f3988f3d14545be2d751f927f2501da6a518dc24ee6f56eaae55d0cd9bca479b43579fa442d6c75ef22cdda0669eb8eecfa364ef0a398b6f472a24871ae5c5e91f7fa1ec7d08ba236ad5f4ecc399f205c958517e94bbf39059a11008be638b1db3cc14f63406093dda804852549c8923905d6199ec320f1a176e5a574baa793c8679c7dde414a57f406a01f71d5a0253b2c5c4dfaff066e6043573466bc1c4a0ec6c9be6ce265120eca3643f52ac4c22f0a00212066c374147ec25f3323a849be19c2b81b1be2b8c25c247570d9c00d06651825b715fb8eb8ee44d18099cc547c2fdc77156320d101c83b0689b5c1869d618a4dac2d30e19994938c7b8c315109248665c93ac559a60c88131dde186c814f6f631c6d4ac7bc054d411b8161cb45f9fb8d4d860d5a19d931af44fcb3ef6f49a9ef6a76808b90b4453008d31271147436f2277d7602074b78630311f328c5b9ea643487ea315bae2d70798a4c14a9ebd54a5b57bc88974fc85118d7cb8998b4d910737f1aeb46118291d40b0bcc2c2428d52e3d941326880f955e7c365e993eda6e42031ba254026d82b11025464f6065f195093b1e6bfe3d01aa0eac5cf2ca465ec6064a8f808bffc3981f688f30845f6cf801cb7e9384ab4140618d645f3cd6462042451d4cde3355368f10c5dd7c7980d8431548173e7201d128a160cd28f1cce816531c206375306852b17b7754c01bbc5fee34954330123180bfdf104c96ca02390ef17b9c8906eeb2747f23bfeff329e5123f2125cf8c442a4fc6c2e26c196e009e51a765932fbcbaefe9004df112923a1f646fd9dfa8cfefc6dc8865fcb520f8713064a1883bdc935ec2f0dc4ab6e798c5a22e5c3cbdf50714c092287ecb6f0519b82f1211e105d05567db9d92dd304c4ce97a8e3456818a88ec1e349d1a59bc204774c3fd34a5af6e270360ac2b61591183e1f91e63e05a0ef60fc7f1596386ae16c8e29d403f5655604c242a234d936c74a29c8e43425c97ac29b08827dac10240464f8ef602286dd5f16718301e18027022e78f676b28212a806d7c30dca90f77d2c7d792124dd2783e35bd6e2b5da40d179b75bd1cf4f701a2ebd1a1035e4b1a6a931515c283406d5453ba93426a7b1a185bc2bb52436928c8790930b8194e4c7905595f7447e1a13ec26228bca93286a22d956f4907b23de951dca751c50644c730fbb31a332a4074de04e42235f410eda07052476cec7e136ce1c32ba443e9d46191cac3ff4f1d85e46fca272ac5c1226d195cfec4ce1300016c1c3ad1d9e0adfa9836fe1eaee98a6ec8199ad41e7e3382ac75eec21265522a472702cb357cfbbfbc2c35ec9d1e67369c9080e53f9f8528de2af1f88c618ba6150eb9d5a4630ad4ce892a16f25e9e5e449174d671ccc83e317beb0daeab48fce570c04b122e2d6a06b2483cf841db6e07dcab2b42db017ab0d33b749b3a98893f670573ace89df450791cfed4f8d9b09aa2b18e95b71ff01de4c4fc9475ad352cc13b7687063ea93aeddcd0240db3e07371016e6c0fea642f55542c3384d46c9703ec9cc709a48f6a5056069d1c32237a5bb44dbe7bb43da6f79713a3bcd56c26c845827949b56dfd95d1fa8efe27eb8b8ea1d133e4e02ec42ff6a5082f9c8025e85fbd83f79ae77b09078a721a4da10c0d4b49a24a0da4a2ab6020b316e0c88ef59c1564887aa01b17530578c3f028e00837d511c81324df8f29cd596c3480d56a75736ac1144e7adb93cba15c5ad4e1ea738aac78ac2bea3852c0d9977c7515b4171e37aa62b24cc50d9162f2ce8b8428c1c0dc6c39e3c5db15858fb4de21c2475704ce91c97934309580b4ada067ff41766c5964d618eebf43a2eac6831c59ed0a445a1aaab3e3a49db7e7b767552aeaf974d271c610b1b90837384d8259fc899c2d1d1ec46d3db1c52f897fdccf2bc1488badfb112caee661e8dbab891c28912a04d16c1017272ea1eefb7c1c7fc6ee4da4524e811068a652dc8843b92e00b0ef48a791a628c3842e3b1ecd27fdd09b0932da7dbe5ad5354eadab7b01417281f2f183a76cc706e56ca07f89945a951565281fd763fa6862277c443adb19572a0d4e92bbcb48830a9f05ca98e5d2442e9b637afbd45dc13dea679034cfbf1ae2f9a7ea21be8df8f173d11ba89dc6874c3575c6ae3e98bbddbf9747bd0dc481e4f128c09536ec5a7ae04136e79bce284a6c3c92b97e01a23de777caf1d4243512da891ca1ca23a0116cd2275426c7442b59e2b5c913a370568ab6a3279c050a3db34c0efd33556f380ef1debc4d5ecea354464396482ff1e57f3c19e856a2a1f7d685106644995bce26b23d0c1024f5f14829536e0b274c0692b9504eb5aa37e1201a3109a268436ce2634c97efff9ae51414b9cf2909e28c5cb42c2f2cfec9cb9a01866f105d77451991d08b2f7c7a4369c799486dc832af4e041837e563f76a447f1ae511058f743247a194ea9a9a66200579880f8f3d0c9544ab745510aea47b4a7cfd03a7ae910cfca010d82599d6ed742844f8e8d7288361f57d9eb847747cd37cbcd27de54ae3952d257b2dc0fe14e383385abb0688b0b08e2c4b07dc36131e0792ac971c5f9afd404cd9fa082831653e2a15ee41a5e35a8c879405f333eca6560c0924caa802825eba80fa43e5cbf00caa6df0fb8788aa45cd544b0c3641160ef0cfa04c6840bf4994a1825d0f1191202433b5cfdabc266ef387b79dcf323f446e8b9671337c0a2380f38af3cdf6899063d66bd75fdbcd245b07fe5cbc2b6c033deb99ae80e73c415c584d4c48b2a5ca89254c938ff238a5d182a481e167c269c380001e2d899f25c99cf960c53acb4a93343e5820f4077af8290d685b5454193f01be8b61a34efc747a8b4e8074417dbbc3bf95a4a222fa2012e1de1f8ba1d285b380a1a63365d12bedf260011ec955356782b8cce2ee768f37fcfd883228cfe1da1a9d4413fe1a06bb8ae61742981520820240736dd4f899dea452697e11edd69220abaa009e6b25d0f64df55171d07fe890b9252f9e8293fe28fff565b9ee4d7ed3a43471a710ab39cd6b669fa0a3270a95b0e118d04156e00a851fcb3a002d5ee549e15c446e56d155675d1822734d75f9d18dd6981a25f320a721279d0e861dd1149985217bb05a2b39565d3fe80857d338ef18185bd48e81914edd27194abd8b13b5660fbf777946ce5a69586699d631c14bbd5d7762ac0fac4dd5adc08c9dec6d205261e1e62d2fcfbe570dfc4f4696d89323a0d02673ca5ceb7e8b7ab0decaf25e95ae8a55d49c4dae1536e87417cd75dd869e318ae76b053c65a2e736ec5ad05de704fcaea5aef7acef6088ac7050a38159200bc329920a86264f17326fabd8e6b6c88089be9fe5f621186f0569a9e513a472c317be7b69e9bc92a6e565d80ed11de9114c8fe261d2981996c7132368d0df01bf1bbbe0cec6c595ae92b9de729e9c4b30c7dd2d533ef5d846dc0dbcf8a984be7530f17c56d3d28e9880b4a048c99087ae80eeb3b26bc289b01246b9f28881c485a19c88133f99c5a1cf3917c4f3429a315828814748d5e2b349b459e2ca8c6e2972a190edf824e80ec215e3614d84377487bc2df9bcbe7ac3790effcd3a11f8eabd299d5c3c37d13daf1f61e6187053c23e1a90f624720e1e24776691141576944655bfebed203522f942829de54f750a7e946f9ce7f5df4a1e973550ae1989cac8a54b579a906897245f9f34113449ad929ec9fa2a89ef005b943aec83b533563939446d800c2a5410a4d110fd06a7aab426e5de671cf90adc430b9c2a263ba31f9b646872ba321193a1d785c2067d50db404fe5b1bab5fe97d9a482a35c4d8b07313b4298e49847ee244b92446e41a5482d6b7f6f2e160879d9d0cb035dfce13894cb42b01cafc7f709c4086d2012b5b4f4a2f95c218dbbbd6ec1547a7c09f14b265c431e2f2acd3d198b67f86861d5a59bf8f9ceb12723da048558bd37a71aff9237f724329d09f1bef8a4bbb6d6ccfeb4129e5a5fb7211aec94a904a5f095d93da4a2528a5a1f63eebb245a9e29606bff21e7a0ed706193d81ef52a4244d5056d095162928e85d4ff2087ad143180fdd399dd460dbf4407cfce8af7ac96ce250aa07d18345aa204ded2247c498c67ef294cfa255d9a6d1f0721d78c203021cd40a0a4e60e80380d70832bf3d494c5be8389ce3dece6ff4f8b973896f22189164cc12ba719bc47ff1ca5cd9bd5f002b48da4447dbdf98870a6f2cece3e1c36540ec1ac796f1c97b7102ce1685fc5d8aa224fa548a1eb20435280b3bf025f88d839d46367ce877103202b1feba3cc10a7648ec27bbef8302f1096cec1065fce1c1a12bf56624afecb83125be910dbdfcd3a78712d8b4c07cd33cda4ff0d013088903e482064ae2851425e0c313f6fe552548aaf0bd42a98ea7652c3fcb2bfa17bd6c2cf5a2897e8c2b495ae24329ef1b84435c2c33ff85c8da8ff85d7c27b47633a1f17b9097c1613577478bb9383d8fd3d388d22ebb4ea4d9ff5a19575e5bbb28d30ff261e8161cdbabe222fe20877bb47b03a808f4a080076c7c7a508950598dcae66821abdd522067c31dee67939ecb6edec243c2703fcc034c9871cbb07ad33d3feb93ee1b96dbab1b2ae26bfc3f64a54d9bbf7fbfabefbb2004499e422fe936f7a7bfdafa0a7ef2000787237aa4944827f6612554da503874d6d8ab7c343271f57dc9d68dc769bbf6b144f0c5d8acd34b2e68254b5e4e67eae19b129e711be9904a8f067761072b0c68b5ac9e1148cc81eaacf13077248adbbbdaf455d45632ca348bd426f1ef5dd44b28f242fc116e119300e245faf954178442f2ec7b145b1cd52fffd861479e60445651bdfea4816d4732a057d6f81372719005396f0994854bfcb99beb036fa8a6c3b874fe8f538842a07c53f10b5e2acedb83074834edeb4ccda33cc206e410fcfaa172b4461d99de68cbf6c130da672f870c12fbeff971d0433861c8c3b2beba832573c62830289338381cb7d3ae40e89410475444237e20be9723cc11fa0b61213b6a5054c1547722fab315ede83cf748c698913894c4fdad0774d4008971d50f4411159b723e972385aac29d397d9e59e3a653774fdde9baa5842990eff511a8ef157d981f9ac05f886d1e4a405ee31a0c0110d6d4215e6a2f44205e73e7f877894fda050108d7d8215e6b3fc403228ad6f3d3cbba9734ae74a98df900a97a04d61bb8fa7a59a58633e233256134e83c43f35000d21ad12484825aebd741fef825d31ac48158b3aef8ed1a8f340de1a0d7ba43bcd016843448b275677796d702c92f946b967658a139de2397af151e73c40b28fad68c12b442e7581c415aec1637d86c4b20c68a56595088f6655f140e09ef18d75676fe3034104377f81c38c5f8a26d59f637e607e6e93ca77f4ce692a4ebcd98e4f1731aab804125eb68c8c17318abb0ad75dd21342315196a95f27e6f15a4ad6e4dce0e36ac09358ea301557b710dfe9e895ad62121c5df4a0c5ce6314133c9ced4e8023cbffcd05412748028ccbc5e72efdad42a27067a9e3570ec76dabb48b8a24535e8d4728207ef806c66bd301d6fe751ba70a3dca27fdf60b56e8c03ce8a8b01ee54c390f2cb213ee9ef50f81c8c6a8c28befe0600d7efd24c96a76d0f07b2e7cf2635219ed200d0f5759970d4faa8bd6a7f6363eeeaec193a8835a9c349f5078d25c041d93c8958fc7a3db221f64a030607a3318667c703af9d17a044b14b00ff08be8a7e8ac95052f9b0aaf99968534d210d0dcbd2276e7f890358895d6a81968dd49de24e8c885faac7c038182a1374a104106efd8b98c91dcaac80e7d8131ff68aa91b0836cffd39ba2b7c7652c37d2411296913436a2e815e5d0fdcd21eea1d3011f218fdc28119743b391643166785eb1b67a761316621d3e8b18ce59f7062398d4bc8d2715b1d2291a32105f85def5ec0dde50b7fb742cbbddd4274fd9148731759846b432fa7955eceffb0056720941df74c279c4f23a5b1725b8405e499089501325d54470b20b13f87bfcd11139e744f6a87967a9b1f6b3f1d218282adb81bb46d1f5bf33bd1761430075201e2293d8f1c8ad21ed1a22e313322347f41736a3b298ee837898798ed53d0bc63b74a2fd6b350ab016ff69831f06f1c7deb42a6b48e49499d10de5bf4775a6f1ea85eb609ce0745c008b80cc26ffa5cd59d4dee526321ba4d3d918d05c7f1793b940b223d715b9646bfdee62869396eefded10180e6c30d85daf6d0244ff3862d6d071eb960ece3d419c75e021fa15527dccab6bfba707e188c89e45009a91ef9c1cec23421f4d491f2837672ec40a51c030f7c0083bc7cfe0b55589ecfeb7e0831c6fda661ff0319d3a5981fc8825bf0a389e61b7a76c00b40f5fa5f4927510ef7e3535a60fe2f64527bca6293290a28c2c13d16c91516113549cce0d097d08102afc365b8110cc0816b7d89a4db12ea7a08bdfe33d402ffaabae6b635a5ee41a221ee6c783c57d6a29c37b7cb51f7287a30e356950ddde3212dc92b5ab9227a5711929d26b885c4db511c495dd95468273b31c63ab5c9f35b880fb0358a7dac3f14a462b77ae65816e1b65e9f7ca0812709631275a78ee52ee527f5d6b383320adb66c29e7614be38050b6bf1f035f7509c345b7fad0af6ab319ffaf3e0cdaf8635f8d3c67f9911ab5dfa6e307bb2b9df31ddd19abb68e4182054374d6cac108fff5611efbfd62e47abd2948502d1ee23c50ea3b031fc5c15f7e0251b6f0ce2cf6d024bbfd40a83c543a1dc09558501ac71842d8b1573ced9997e1fe094d03c9c9b1a59458b967ae838a7c1060ff539839dab1304e00d5f84e74c7881200ba0ebe8d42c45b3bd10cc44224eb95f420514f02f9a44046c7e57f9707dc5e8a767bde0dbf9d498eb9cd678518965913d86c3659c8b64f55e4b0dd86aecadb16498a4e2bb17de4c2ffc16c8b6b4836a6d0f499f5d773ca7a46c867f86042212a27b357ec972aad4f60db000b957d0bb960efc274be644d5df222f50a051baaae9d7a84f9b224189de071ed374b42243839331396aa21e15a035aa48c8341e01a755020cd251427fb4fbb0714a105667fa76b1d1d0f6d53781e390b1dff41d3378d79ba09a1e71db8587903f8d41ee443812932a683bfd1b004fe6313a75e87238a8b8fb0cb2be234167d0827559982ff0e6fec69de316a0abb64b4a213cc079e81b9b7257d496d56ea4525cf6ae2ae14dd6c9a68843cd62810dfd267633c6f6a776519db96c52b540cb81c48384da553ca8fc158445fdb9df28ac2ce3206662f47a814048c0dfda92a3000db59bce168d290c249b6e6c3f2f3646078e87768d4c71ea24ea4363435fe7841449bf0988cc0c2c8e9b6c0a709399603709a343f2d9bf6e58b7e35c6e1d798cfb42872eadda0516032752cd12343d13ed5be7ecbd4c421712c5922463328010e944f011a3bf2c5f5b6513b3b9c202f38705641fa6a591f3a0cf7f0a3a576c46f00e2d21c183f7d8ba0bfd68290056072cc0b5f7d859d4d7650e75ed87d285f9a16b2384b5296e271b881c8a4c1f52f6b4fe42dd0e65f026efc8e90ba3d7a1c06fb2722885199114dd93f4fa6998ca76cb061d9fd2fbfa6b76012ad40636f271bf34b1efe9b4574027366cf9d16d4970fb5252d97e636303e394388c91c4376d04010f65741504d536fc1704135926bd38b812f8a4bb8b702b3031fe10eda80b55bbb201845ee4ccd3404ca37aeee619230e7617c71075b1724bcc13b82fefe1f28b50d37f2b0a10638d61bfb1c1011ef557851c83f9786a3c4fa930f4daebae17305b959a9cc6f0cdb97f9b3175e3618214d76c09ef7042cbdcd1fae26ae6426bb193dd852b1e2e31e7f67f3ee63545f3f94c7fbd5b132deb1edcf4349efd07556ab2f9c5f33860ceca872580cb08485cefe6667201ed2db703f193766770bb628927f1346ae0e08a3b1a95ac4c712e188c25ce0f1f3556cfc9e2176a4fbecf8fe1ba637964da46e41b1207caaab553c8213bbdb2fb2115ff31298ed5e7d3a682702710441c40a4627f93d1b096765af164a957e9e2a2294c25d37ea998151d2ea14ad9cccce53fb7df340fd4711ba5028c3e2c5091c9a16b5549db1d236decb82223716960bf0499a49bd34f3f182364535262cae59f5776a4296bcec12a2d900e409fa90e6f261e7124db1182fea5e75c698fae56df2c6a779d009343fb5d1c1e1b80dc4788e7f56a1ab312133a6c9cdc02f89c0c6658963c232b28d1d31a5c30cbdaa49e6a3109429696761fd72c6f913f155892b4aa8b8518a8c7528ea1df190159f45d34c1170210fb7a853e363ac2563f0318359876df5dadf979d25f5118eb834da7ad3e0bc35f9aa9580d01636f5e019d7f278fe8aaf0e28ff8944e55543d20c32739dbd1ce017b8f03d810961a6cea70ec4ea1d6b829c2ade62b695609cd03a1c88d0fb174c343bcd35be48cdc75ae7ffce62b0cb4af6b001c2b2797d18cfcbc76d56e34d5cf0f2936140c5a3e5174f9254d815214a8c2efe866a5f77e3242640fec490845fc9f54e3a7986e0a1f6d43ba411a1c09cfc4eb43800f854007eaac6d82d05896b97843cff3ae1d84a07105ef4a59034ecd067de935873011a20e8fe79d1576b17613294c06bf36e5af497808e283f33f7aa9bb8c5649f237e99714d71fd35ba632f155f1196ac21fc36451d2e5d0da6bfa5d45e4626e76f910d0763ed9d7276520ae927716c786fa34edae44e1a3740a6ca9b3201a2287454b0321084a6533ac0ea1eb3166b93b69619da5c17e21b9407c06c2393c33129ff65cd4af4be3479d83b6a1700d0efc31d00e9a57cfe0fddcd06a05f5aeb0daa2cd8390cf1e6e30eeb36bd4eb966cfa624ed2aea698b71a525fe53a64d4bbdd84b597470f936ee2dcadca7031b1ba55d6f1c5f71dd081b8b27b209026ab924481397ff9e83bf48a83e9440306613e044058a8b2bd796187a253a54e8fe8f77007d89868c3d743e9ff2335ed804745073414c567ac56d44c320b612b68ed760ca3f4f2be1ae6ae5ee8554a186e5448e048f019c803f401b274571a2d5ec919112d17e09f8b136d00c41ee3a8444d8aac6988f481458454ed15c2dccf7a82f001ff6372c21f2b930f41f25d0ed47d35962e1fa874ac128005af9d8f97279ab6be85dfa9ab20808fd74257efbdba1af52531aca3ba1e59d32cefc724246e599dea7c8ccbcad45654b526d7ab984ffe4c76b1b1ee7f035d427c8b3f654611e71ab743b5ab6e90b9e21f3671c7142328872332d82303c512be98b206cbe0df650497ba0c1e914e9a4c16e69c9840df6fbf7aceae7e6d6a9deeafb53a4d1e6333ebdb494043522d41cb8880bfd56398da03a52af96c1905117eee15d7c1aef4d9811f4ab18c69a3661a11cd8dcba6e452f2eb118b754a1515ec85d0c4a41b3e06ddee2f3ad2b566f3eb75dfced1856d1a43371f0bb2e124c5da94551654caf91895228c8b05cec427f637dc3d86988cefb1e1da4e08f8367a775a304f66adf781830fbd775e89bfa2401485af3108c2e401a39bc3d76b7ab7f5c1ec520fd03f098fb8dc056acecdb4d12248269b2a7195aabea182f44d335515c8375e2ddc5b7c0d066de300af4212f08af9fc0d88af1c7ca1f1fa77be24207bb80a136c26113d79d690243578b8d1415659c1fb6738e52ffb5499f2ab527140850afb06022138aa0dab142dcbd2c7ed74f6e2a77e5e0e8377f7bd3e7c81946e9bed82626c757ba85b536d859d46b046336ecb3c2a117abe207883f53c978f321aa469170cecae3f80ac928b831bb035be77a083cabffd135df035963df9f926c4834db2611262c9e5f110f70842d0b5affb2c34a4636a8eac3bb395227162156e168ff734ab84153860d9039b191677650d76ee347ef31a3ca3de04d67a40fd5eab590784133f5bf3198d99f81ef60acaf9102310e516b41a439cff7aeb800e8872077438d70b02d26a423451444dd0a09f27d4e53a01bc8cc2816d7d8e6b9e2e0a02aafe26302f05ab210e727520a8960a551ebfd707fd3d63c1938532b094738e26267c789e7fc078abac74a24fd783ab1cfac45483208e4bcb0a9c5efeed27a051d0d0d1f2d355e3b5ef10eaf05ee05f341a1a23f57109feb3c69acc2c6fd4e6458687763deb82775f928aad80b4949b26a371b497bc6ddeaaa24c9ae7d0dec0971cc4d85ceb9f83f61e7f94eb8ea017afffdc96777fb43979ce484f15fd61964dfbaeda24eaa6166bb979283756786c768ef87130131d8788a7eef777b05f92fd3ae599b6b4c0e634631da0415855976e0c1dc557e7e24d615c385585c96d6544a0d494067186b80061eea13ae8612520354be4050c6fb99c4ef70abcc236fe3c8447be1d50538de8b3e94c8a7983e3155c1540b1384e84774029884769a83b4d65ea3a20abe28a20552b44f72bd44e029b45b8251af12f544c44e8354cf1e36d561fc582bcce8d76d965041da12a847f560a332125f56a5de351824d5adebfc784d8c7aef02d7fce8411c82eca86ffccbac7e624bef9d5389643b2af4eb4061e3f760d2df92016d021b63fecaab360e10020ffd8c2afdd1b91b6ea472e317cf435e3fc6dbfa6e6165e6f93eb1451270cee98d0ebc5844c63d8977220cba00d984c24bb64b47eab41121cc76c9f5872e1c92fa930dc64e2e647986e9364fc1d87abc5c8307e123b5b3d9978b6750a9d4597e7dd006ba69097ef76ed8d171ec555f405b366617f95d79762f221fea45d1c2fe60ee43355294ad29f4251e420ad1184df5e62c81ac189b009bc570bdc4e905aa360843ccfe23d5f84c9d4903b4ba8207e635fbbadcb24cd34820cf57ba06544de5274d914f3ba1c6755e72e955125954ba42af9843d9b524854916d41b5c06928d7fc537e0ae12666d38c1c2bf8b7c4e524b2449e356282ce713ecec8aae7251bd809a1378d9ad58eb79796cb0371502cd829438ea492747b35d5d043a49166c184010e2808e25cdfe295a7c802b9aa59a3c7799a42783bcf46a007dd34c58f99e72e15e9da562305469566708007d6cbc6a726330d3e4946077689f21206022d45fc7c8d63e82503d9e0846effa3da1db86bbce78bdc4a3b0baf2c9d739848f0b7d0208aed8a27f93678da443ee7993351a991e297d5289ffabe90cbfb169bd76108d89893989c8917d797a9204dbed97cd6387737de8e62b32b9497f1f689839bbbe772fdba2d169fb85cff9f6372089b5a9d1c22181e37d643e13cf32453b92ddec8cfe5254361d7433870338b0e06745c041587e8d55cdb59575f0a6773e81e0251470e5d4166bd152dab16b5ebd0195c56afb7286c3f9bc827e2d1d88a5aca4cf967b10d800893f341ac2393c26eaf51e3e80b3b4933e592fcb8fd14cb6689930af1351ae862a111e931a333d459f9030c63259d117665be625ac61d0820db802b0c3a4eebf49a60070b4ee66a57b4dba7470e38cf2b318184a7acc7861543fe1f9092bffe78595839b46615b965231dcf35b850daca02ed7fea203d529eb54d95bb8404fa07a097fd39d942467973e543ba45f125b33c4e592f87faba0e88a58879b70370affab55adb4a0be3233cc00a76cdde240ef2aa2851c9c1207510588b08780b05760d849d7dc3810051520df21aaed6ae1714021239275851a052f5c5908e0183a665025c8a740dbce56bec23f4a3a2f370015c0ce3dd0619e1fbbe9dfab828bfefaaa261a83c066b14eeb81eb78749031ce0e66078e307bb6e77549a57613fd489cb07309901db6aefcfcd2fc300e872e07b85e3668d89b03a1614c84cbaffe285db5f04ba8b5405dbda0609e0786e134de77f16c481b2e825269093f4da95db1b7d74573f2116b001ddb0b2d94a342b608fd719c06b950505bf104d755ce44361c41fa655514a55d53b89a93bfe9929c2d4bd902a494730da2553eb5ed1d6c0187d8fe81b3ee2aec18babb1df811da610f8216f5a6147c1c9cd5074b79e60340b461323543107da2c9ba6dcd72e70a523cbbdec4561520d0a4349218bc32c7561cc4fa2ec23f1953460c63ea33dbd0c338df65307f52781c6f06c4794d575ccda2d29f36b2019b73c7bdf4b145d7758f53bb29c3fa52bf60c4bfe2e924c20c125e8ea7bcedef2a9618e90fa722be3bbd6e568a7cfef39ecb8ca7af73adac2265934677b029c9e2e5e7a03be01e410c89138f2381b419d026750820d81020b390d7fc81e3d53140d87e7641916446dc106e6c0696d7624f625cfdd8a4729b6f566c1620bf90025e01862d1a3286e6aef76376f57186321f3c3ba3a4f44ec892ffee888b34fb82e1e489ce05962ee64110e1a028ebeb51e8dd57a6edc0ac4d118986421fd5c8603bd594fb47d667d97f1d432f70441c06ff75070dab152f4a247ab242e5435504291c909e0f545d03f6cd659f92d472dc90729ec69f8aa27088477587b8524afa9b6bfb97447346c324ecea4ccb2f0f7791970afea8adc7a08dc412f02e56ab9a44415339cfcc885fde72fe9025894a148609db9b6b9b94b28bdef1b3aaf1000b160d0acf86890e406cb930f0abbdd54811e796ee85a315746495b52d5c0141772ef8da1867fe46339d35674116ce1d2c258205125b311cfd1fc27b5f0e9873877eb01498cbf3c90680a7b855147e7055ee5f21913698a12ed9792ba0972be7125aea59a2e52989f1925ff0600bcecd7c0e63159a60c0244eab2d32160b493c84c976d37c980b23c97ea243d3fee8027a63278df871eed1e005c3321c501b713cd37215d1f8a8648fc7d2854fb44788041434be68ed4f07560aa500f099eb3f32d0499d2fa8fd1f2f40361d61c3a88a4a6923ad6d601927a2fc19636b138f45ee96e3433ed6574fa17e4d61323751b237151496ae1d583fd97bd8bde5bfd3a30eb5598d1e58e8d16e290ed1999a01ec696a8e8c42e95bc91ffd28d51cf483d80d45ae47d51b1266a45a67825351cdd9da0652948f71fddebeac502ce2fe5b115025d5f7ea29e5659992346a6a2f35e4e1a56d80e58b73cab326ab21fe985dfb4361435b0eb1d96cc4cc051755300e1e92059351416a09e6b5a610c6a555750f07c8e791336b359a0d36dbb2d011fe116b9e5f2ddc5ccc615d742a0951722185817e389111003e7bc038a565a782d7e5c1c8aa4ec3c39a64ebbfd014068edd89d935e3d443d323e5e84290e13d824f4d2e186988737535f1328ac66416c6bf66c6c8a653458c9ee3b41d9e2fc4f8ecd3b358d1bbd0221f1ea0d608793f02bea8da2d5ad101e20fd6ca89e07387a501adbb420e9734b57797fee0c7b2413e088c80c03b7c483d90156554d1d9455af936804348b0077b700926cbe26321f4995e28312445aab0017972dc9b7cc21c1e50d3946a3e4dddd034e3de34ce9306795150b226a6f4340ab795a6301962630b92341247ad8cecc9c82869136e865a059345a981a0fa80e88fad201c04670e2e5826380d3c96b0f63d40af834b5a6260e49730b00a5140e613741f0943f5761ee40850df91151cb531651318843e8d7dca30fd909d003911a6a1776a60ba7c17eff05683650c1f3aa55e81b13bf6d9183e5c78c8c31760107b89fe3547eb85e98349e715c1f661972d80fb855f150087b5d552b214d4886b52bdf7d901c042c0c330aa26492b6d8654e24f3c7e6615b17df7f0cb6fc44b3b9b826b4c6c67a87c249b714618809e682a704a763e4c508531b0d4a0aff5ebc3e193d847334b690e5d562e662fedc68cbd18d46ade6ac1d037c074c9838937d290e399d4dfa805283241b1fba295fc70b1f13e7fe0efa64d2d66b37013437e7171b02b945cdf1ff3ac2aa5bd5771bc84875542911914de423144abea40dca6520dc8d700ec792e22009ca89be915b01a16f19321398d28342629ae6ee194ce3610fb2bad78595270b17cbab0e1952983783d680b0e99cc2ed7cf273a7f8dcae27fb5d4a9d2b7131cdad41af16616913443986cd3da85afc09d98718a4c3c2dc761e6b498b1e4ef253c82768e46801c07adc9a1255546746e3d9fdaea0f4c5d5fb1e096479c159ef9cf2acd614445999e29f032f3025104e9ee6492be4aeb7d5ac7180a53bc199b2ca46647316120ace88f6084abbf128ae9245e914b33865249013daff59fbcdf1aae94a8f0633c49e844e0859c1509bc3f4fd10757f680579f1e81fe50ea04960aad197d18524139fc3190233e78783437f91bc15c1fdfad10a0e4a426dbfc7c90c0c93a135e075dd8a5c3927dadb61aa74e21bfd8eb5e5e90938362711c7bbc5a94a26a31c026c53d2aaa3ebf225818ad2b2ace5b9ee815dbbca32382705a01fd7e6af827e6020ed9e58c6c7df739f6f18e05a2df32ae5d499659bae65e70b2b8b58f60c6bd4623a9496fccc30100e71ef24e101ea96cd636f817d1e20ce61b74e6d4c1fb1a57b387087e3e7a87f58c58d93b26cac77f55d301afa1e9a87d3824695ac5e1ac5f7ec68196dcb098e2f05421a8a665fd54142bd6bac85cf5094525e0b912fc6f087acf2225003b550faaf93904f81b79779a4bdbb4680f5d8d6d84b23088588f4acf2d5b2cd494924a85d9083962dfdbe557a6d025cd516ce3ecc9ed8d27fb620f2ec3a3c94be130a5434929c08275d9b09affb92c6c5686405097f2b59618f2e62ccaa818f9a8234ad0a185911000e200b10644aca8c1eabfd8e7dd4e34c79af22476136ffdd7214a1af37500a24b80d262917facf13346b800a81874befe67cb476d77fcdefe7d77f11c04ed915fb2380fbded2841fe75ce4d80b6e96fbac837b78adf5b5b516f44e7912b6d65bfbced642aaed1f2c06f574b246362a9dabb74b267ab42387e46b3c007dc2d717311eccd74e44703ddb53ec1263503ea07d8680a5d96da978e58216826e033365c1c6f1d5e63ea088f468b2eff8fed167ea9d890b400fde19b163adbcd25fdec0abce004beb8604afb864cd5d876c58ea1da39feb460bc4d1b48baa31a3c95dd49e0e086e0088dc2ac2c22c03d88fcdbbbea3664c271b22efcaf701d313474dde8e7906fe56757e0776a515053e0de95b511efdbe52410fabc255b19949ed11cd356f216a96f1aa2a44afaa034285f77de1de8419f7cde33b208f8b186ee0b077134ea23c518ff260cef8ede43dc88352755a4486108c52d077bba2a30ded664e27c30aac67ffe8712ab92b9a520a083416ae1d6d07f5bd61c661275c00862a6aa1536a87d1ad84587ad941fb9d376587c59cd1e03b98d188d922b6c3f821238a3f5a2530f5ae6b475a5a1bfbdc4475990205a2621624180321a4d40e90da00603d175e0661c2647640cb96a88856ec0dbca18008cb865b2e428ae2f415ff8a54ab864fe5781e54f252eba89fb808f67206b6d00ed8d8e924313cbc45d58a1dad025bd83bde5003749266a2eca983330df596d0e084fd2f01ea6ccf3fcdd7048b18bd89ace256c166cabd771e897d9e0c785c08ef2b86fde2bcc207b7295a52411bafca5219e209eb81c1c9297b32464e10d8e5a62af99b4be1c037ee48aabc142048de8c07f1b60d927bb25bcec79c92acde29a1a1a77dba13865709b1196fdb04e198c5397e6852c1d1efbc03f9428e9d4286352dcbe41f585393daa077cc3f64cb86a36b9f31f28ec7081b2780e04f11763c89bc4fd4a8032d3e4f3983c311bb43a7c66732da724eb349baeb9480370f8a17cfe44d4891cc78d489878737ad5b78c8685488347c5c00296c2532cdc2fd9ee9069407582050e65fbe7d71d73c6ab4e0dd6c2d7d3d878f65868d4b9acc6280bfc4e4bd48e051d9a4d512a22e9a39ebc98af0678ee130e88ab0d9a2df17fcb1d565856d093ff523911068c1a101f5d0cfe5116b1dc871f0c40db6e57118c6c3c8481615a1ec54b2f2b7ba2bb134cf2d5137d1ca4fc8b34c2c58ae55cd47982b440f21b71b19dfc079d019f89e2b3e5359ff10720648e88ff844a6f6c98cf3cbcc6584e9e4539ed642bdce5a62b0866ea47ea2a070ac632df7a559c03c9638f6ae96424e0e7a2d386e3aa1234eb34cc484b062ff3f85fb9ab7f190dd60f1ef23a6c09a3b47e0d6c86f572d9bfb77515733d0ff9ab713913a7ce1c8540b1b8ec400c044d379fd422d293b24cb2fbb5b5c03b602b9eabd6ef5ad690a252b810abb65cbfd0362cbb8b22a7330a72ddeb8a7577b1b5af65010af21a9c0cfed7827a9ce07c39ae6e1408b194e2dd4175a2d17ecd5159060bf23043b7343d710762a9ca6821638250ed60f0806ae6353baf426ba76b8af2bc4479106d25861cb3e3d4f07510563dd6cb338feab820d1e659f3083f8fca729ca6b0c34c7595933442b6eb6d50bc85e2382f3d905a5fb9221cdd07a48475bb0c796377b1221e8e5ad33416d363c0e811748582a0c2ee81618565e5028b6e4d21a8bda04541e171cdaf1d2bcc5391131c192e154603f81e10dde072808dac46817227d3600d576173e4925bac32a63db2a9557263edf96f7bee0bc522a42cb1d420ff173f63c4fce9a454f5f8348ba1f758a377e1f6b3d78077d83fbc695a9a26995bc389c111036abbf9e8189eed54c9eb58d9dfb23151e5e71b21ed66b12b3aa83409d02dedde4f8e9618657f686f0e3aa7b75b318a84285304225131988fa7f20a7f54bd8c8cc8d611b1b6bc2740d51a02ce2142e9f3452bfdf42bc9700022f4fc73b62319776ca6cf5ed50951e8bc247d1c81372487c68fb02688aa4f50a96adc2fd43ffb2d4b4b6a58709a5b7edfdd4f01688ca067d820fad9326dd40704bbe641823ed90a3bba1b320a4a00acb2a80b34fe7c838e79299b9c761a4e0dc7b869db97ed7a90a80641770c000c122777d849cf4e8efb12875dcf9bd1373f30232b1cfdce5a685d886d1b5a55df313a34322e6f63afd4f54db7c8524c29a210401caca35d2b251ba6b15c90792f74180275392044aa94144437c891eff7514e191f90f937035a8fe18c9b0d3804adfa82969e53599c01125b40aafc633de54ab8836f2ed68ab5def9360a3acda8173d8cf70731982755cf4dfceef1b3f5d5033b445e5047773707cd0d8a023c86654b29bcdb5601d320500ddf20e8f38c0f0145cc2f4cd03b887913c5ceb575bece57d9b9b7d1fc4b5957885f8c052e1e4da1a01657824549c0b974217f59e10ce2e9cebd1d6ffd661157a587111a6da9051b24584a8f02ad02eac234ffc4c609a550d6f7ade1b3d0c8ba43eaf7b6b90b56a7605c2169593e87b06ce685e1c90b664becdc126f3eaf7a4096e03090e74792a4e6fd8d36da9bdab05960b1c7233096b429869660c4f1350cbb8182e9a55c81d060c97e4f1c31dcd12c7e28158b3320fe260b8b012b8b102e6e6331d4c0677c20d8246900cdaf041b4455813b9624db611f538e8dc44bb460bff530e773cf8e0227b6439d9e7a6f10403ab1940f7bc0c572fe01c08a60a1926339a8c5e23920de27e756cc9b1359206ec7648641b41d50abbe7ae3d7bbabe705d26ea271c7bd3efb4ab88f1bf2b8bb9c4664759909ea1fe52da416f0fce4a528372c24dd79a48f0746431ca50d6162ee90bea2883b174e90053f37ca79cbe2f4d3f79e45e2b8ce9b8b1c7725f61353af48e5debd15022c2a6cf185a5fa818ac588b77a50dd1b60e99abb93e50607fca9f544ca8969e3ced34f090bee8a008a8acd9bd886905ae7a17d4f94faa2fd62dffe21c7bda631c29fcfe44a89ec4046a06fe67eabc3214359e8b368af3abafb80621175c3a627c0a912e25e8de50a62f91da38cad7aa1e0f35f241cce7963ed362f3682b68c7cf7c017ade6b387abf70e929ba4cb7578a41e7000d67e0dbbe18742cb6129e557b3a407225441854d27508a50edc6004ea7c7fb457204cbb4a07d5692488b70924810a29e30a2d1c6083d60163a349b349c2cd9280b8073a501e17bf6e26df1b9a5c950302d1955ceeebe7831d05576b67ccbf11ce5b18b70d7b217f7e390416553642f6265beebd654a29051f0b280bb00b0ddbcaa24fa4e01191b43a62051593d207840a1d92778214e611522a1f6470814362aaf321854d88a2694709b42d485c1bf2082edb19165c8b93366351567ad2f9d121bcc8984fa823f6792204aa4d530edb3adf5747e6ec1e9e5870cd3aab11510eeb9f959ae13815e8d34fbf39a94e0a4d5ca61301ca8c7365b186f16fd73e7a953f27092ae717c4eda038778c41bf378ce87786b7416fea3ec940668d0c563815d4a6ee930c4c20d1d510ce85ca20d80cbaf5c06368770507dac5d0686458a10eaad57d9201054e43a71d2e50ef0ddf42bb3494c468332857f7298614d431e646d0aeee530c59ec6c40539377604fce16ba93ea5459f7698832eae822c21b79ab1eab3e9feaf9573ded6390566fe33e0617aabe2ea80b45f53ad5c720ceacdef73148a47a2bca9a36adeadd78381f83b11553bdd63ab978e4a91c426955a2eaed7c0cae14d55b7d9ea7ea26c76d9adc4ac4ec33bc361ca7e438517e108af3740f7797741ebb2375b8d67420297338e7892e9093c451dd78f4739b15b35645ba99871491f247ba38bf1bea1ece8d0aa7fb7efcc76566d9e2e474a47fdb26864a8dc7b789a1fca374edbabb3b9094cee3ee2e7b7c459a798890d4b49e2a5f3c74943f5d44f28bdab8a49452ac727b5e3c3d5c3c76745a3dace521fbb2333c1c965da4b979c84d53f7cf46b23429b7f97152d3a4c72b447aab45a9ea54bec5d739cfe97d2e17a5bcbcbb05e56f630eb522dddc3c847bb9bddc406933593afd64bb7b38dfaea47c77779737ceee1b145d77f71c570a39d5a5fbb3df546d76943df9377277e98a65f363ac2eb501592b5e9876e3819beaef79df4a761d4f26171800029b5033e966d76923e8b8db32d05921fe2c7542e8a6caebba9706aa3fabba706eef7437fe3c65e7792b253d55ab1e9fbafa40ef820daa3f773f375578d385a0b65c08aaf38a5adf70b5b988736ff586deec901c0e547ff67861a0f67be3da8afe2c5ddbbea09a8685993dcffb56abcf8b857169bf5dbf54f03a95e7e347ce0f1f4088c462437cf4ac9650f1fe9b9e171886168c8cf444886f306281cfecf2a291174fcf4a09f501c02a8828f200c08c99270ceb0f032ce3224dd55a46729902c030d298a5dacfa38a818b7a0480911806963de170cb82b24f8fd57f4b0940fc7683945e3975df7b41d5756e88b0af7b5c8c94041bc0c73a5046da1856f6c4e70db58df480a9fda3cf98200a58581b59213150fed18890112d28bf37008eebe15a2da1e24d80adba8bc76a89076e6810215f7311fe9d382b4ac70c79ecac9678e0860a29c037fb8757307774b6001cb73aad1d22a5901124a86644933cc43e2f8c1831626419b0b2f93dc327f3c7e5a26e98a56219c9cc0cb761484fb0a017ece8f02cb74b2da77609bd499cba2c9bbba9923bdaa9bab9fd10b95529822937b931ab3cd7ca58bfdf8fe28fb1ca5a819f2729f5be55d775df276b2d13978b52292593aeeb5641feee22f01dcb2b4a968daaee7f94a7eee374bf9d6a04d5eedab058c655f63c496fbedda059552c9b0e5b99f7ebb3ef81abe27f52f9bde7cbd07dc8f6bbdaad6e22a8db90bce1aa73098db3d5f58d4273beee129a46a12d9d6ff60ff33bbbbba7e3554adef93a0c2a5f89c7b71b54053057d67561ac613d5a50fbc757ed1f739a76e1362cc643fac17cad9c9c29ae7f244d337556cf667f9f2a33096a9018901c95eb68c44907673e4a16561d370dbf0c5778694c652d95bdf0152d5ba7df91a32ae7820d4b30d100e64b1433ee82061db2b0020d30d6cc46eab45e56cdc84d95b90c8144edbac90d8142e57e5b1de1b42d48d5acd07eba4a8980ea42b450bd9f8fb0994ad4f156077ffdb4d9ebcfbdfb6be6b0c5ab23f27bfafbbfe7aa85b3dfe059b94aaebcb2bfcf0e2623bca8f2e937871a362668c9224595cf5a1d912b1204217f85012431cae4000596252666b12aeb2842ea4b174757b6b062265f5cf97256a9f281fa67b3e8a2856902ede1208cea00628cf0885c5501a28b8f2abf6303081bcad0018e304f2cacac3023c71465e800870e5d44668639778a3b6a3fb7c5a94b123fe4a08413194feac0e28a2a389459c1e90e2821b68d013fa4d1c33fe850c12a5fce355250e28da42c689821c36ca444456491010a247628e34c1c33c90eb3038a87afc77b2a7f10ae5226675c97a6e8af7f98563925962ac71cad6a1327922592f93380a52bf2a36778dac788fc56b576c9f7b8c8f6fc6ee3e5e659e90387f1100f5a942b44fe77448d54f94f2eb03226145898fc558364ab4f6065b11f2f98b82c83854913d471a7a8d208c906495920493648caf6bb42946c90140e74c00649993fba966c9094ce020d36488acae7011b24c50300043648ca17800d3648caea77858c02c86083a4b0fec70ae12a2b84046c021b9db042e47720a300720a5f095490c0ce8c4e483a8191bee59c8baacb0f9eb4e8ad41fd8df40fbf873dafbd0d570d7c2de1105c2d4a354ffa678c0101b528b9a8632d0269445a94405ce4b21f01a8f273ced0918be48f98cb9ec4808cf84814fbc715848bf4b83648fb69e3f2d26212a864f3426db3b25d59c5266424908bb8284a8b4f7888b4a945669092bcfe4ee0b1d8eb6c6cb8af6b6361fecb45b4dff7dce022daf39a96addefb248739cb80b4428ca09f097464a42f89f673cf4a2ef3c2977cef59efcf2fac8ca71686b4411cbee437f19ef55f38c4d4e47b2f1c621ab3789ef75e5882cd53a10efb2f1cf29e8643df23b11cf38d95b151c37805d59fb5e2d0d00f086792cffad5fb5be0c42a7cfabdf7a5504214121781818b3417d1c2d712e69ea372e6a0231b316be9239809ef5440bff79e8d8e36a87b7f2ec1feb014abb1a2bf2a34f21c16b2f31cabf1d40ad1c2a09c344346eae7c2282d6e109630c443dc7bff241bc46a5688bfbf9295f11c0bf30702bae002180c047b3845a0675ec85368282bf9f31252920662a98e444682bc84c443fc85ba8bfcfb684acdc7858fbf0cd2a21017e1ea1e2591eefe398368e8483f13d09148b542472222901f3eaa7c11f88e3ad217cf08c2ce0a91cfa353432416fb699990e860ac0e1aa44519a6fa1391af255c3168d9ce8f2ae58bf26392488b52eb822609855a94efa9a1e387d4878bb478887cc92d4cbe7ca1e6eac2e4fb6c135a839ae9e686e8ef225e74caa0acb4001db669e721756ad8df2de77bf852126e8b1a5195475e43b38399aa6d9f8324f80a71919caabdcd91f6495cd67fb49a365fe70ced85dabbd7420d346ed3c2106cccac7183541d47f2d191cb807ac965ada3a98944bba93a1ff506e3f64e5af48da76422ab31ea1fa4feb982c65ba8e337bbe8f0431d7b4d1d5b86eabe75fff876c9214d652aaad751cd78d030ff273adffa11e8c8e5e235de56a4949f4fe56aa545fffe9bd7a07a64fb54a1d6403e021969ea6875e3b5c097f63db8efbc1b2eaea7bfab08eccc28e75d4d74bef51a18019d6fbdf7a140b57ffdce9c67ae85af08ec3ccf73c0e35f2115d7f3789e900a5313d7f3782a39bff33b4bd3a23fabe1a91697ccd7727ee79db89e0738c4d4854d5aad1fca799d67a510e66bef025d39a00553d09d3169ffea5e5d28a549cebb24b0337174f1f851649abfb39d0d7485acd4a23f0f90911ce6bf03aec372429e9a3a9a3ae22987f97bd0a27ff0840e2ac3d71259e5f62d7067131c41d07e0481fb1184f9dd8f20a87e04c1fb1184ef4710563f8260f320b0de6b1d5e29c109c79b6d813b4bd33f6a68f5a75c3668a3781ab23a95c769401860594ec84d0ef3771710500ecb72c2268e6ab1c9659ca661febca67a93515ba9fe32f09416540bb90c8b41838e6c46cc4d47441b270d1b9e57f50fb9888fe69a8ff86b50f67777f72eedb1ab79b88dd49a476d9b8f994159af62490189bd5e564543a323a019eb519833ef5188cdbe5fbd4d686402df3163928fc2ec3e88d7d10891da6520e1c51a3ebcd0841b5333ff024aad84a5c5fe5ee281a57fb6ceba8d5aec3eeaa25672231f614fd84db55f4bc21178c544270d8f073432c4ac85057d81437d9443095c989510f36f252ed2d5fee6818b3ce1221e3a8087f427e121fd68d00fb808cb58c961fdcd49d43e7a81ec050c31a2663ce04b31f37885a4ab875dc443fa83165bc9654d02e623974169583f270183920ab57fcd919686464649aab9b4d20dd42472b5056c48a9c9e60d4aab724ec5524d40dd271ca02a154922154835c8ea08122a9e4e5034f3cea01f11b4fb5d55a0fbef07ba52a20277e655e848b7b0fb7a852ad210a3ee8ae3f6e3f6fe502a94968359e65397cd108396a285411c2b0515697021e38e33c2d4e0031524a0410d0d74a49975bfa81579ebb25d0ae5ea7663179717bc0d86900263a4c41a3f70618138aa58e3e9e8045674c1e58ac66587d509ebd46d4e31385cefcaa9fb34c518ae1db6a0f10bbe784286267ae8d2c5060920b9418232505f98a14414ab303eda68723306644455f7690a2c4298a1fb0c46972ffc46172e5d86f0a82e3a4824de69aae25881bba1cc0d676e40d3b0adae26c0a8336936ff6b996bce3927ad818986ad14f38a86750d621ab6fd4d5c0183531353a0304b5a6a220924a826984a30b3e1a8892c28cc6868e2ca0a50b32b510ddb3a4e3adb66cc54520fed857657623d759f6ab041c6e08d5aed6a0dd2e23eac0aa9fc637f5803d0c98e01b5b8ef75d7a014b9fee89d83d259b20c25e0b398c3f6b584ab4f8bfb6005405de92b485fd183c8988873026529a5949c8302ca3827f87095ebcd45d1ee717731efe29c20d7dd69d8b8e9464c395db6f46de5e65bb85a1a6e97da74f5a8984c871141b76df3b05b366b5fff70d709f68ada6f22a8fe5b75ceb990578826b9965c5e254115b8ee436cdb4b6e32d98a703d2744f7dd385ff3e97dda06fedaabbcb790caaf7fb8dfc9ad96ac968c60941a8e5173b35c33c125caa53a1baaf2afb85074993e41a08a8a8a4e98f5d396dab3fbb7bbc0507bd526d410a801c715287a2093c597d90f3c7001038731a05a988d5c2493fc559edaaa5ccf00eae853b9a7272af792fbe942e59e72ef92e287cafd4b2155b9d75647381d5cede4949986e6abbab004f6c18d2c73fec4c9828e1f03ead8aadb2f98babddca2c052b728d454d7d4a46c55f9bbf20931553a0151251455aa7ce204511615c923bdd450130c2f3de30419b59bd2d9bd58d486daa2767f120d9eac14a742100f71e20814e838ab13550e30e9bb1d9e4a057a375b41f278e564be8a03ee630eeb9ebb67a3230cfa028b85765df7495a36d1a8dd6ca37673d0a8aedb5d8d9565f5b2b6b0a07cf43beb38e0a874dd5f6083b6ef9e93f88acdd20631a0768f0196cd85754fd358376af7cb74d4ee3730b642bae77625016db60a4587756f06dd444f05766108b448dbc271cf3daf8e60c01be4fd4ed66fe1936da2fd167ee030ee896623ed9a43ea1fa06f90b9b4c8791cf75ec8454d6b837c04c84cb4c8bdc702d9852f9628026aa0ca3dc581e2f0c61aa99906ca24e498511a2827d430eee7a4bd4e1a885498fab7507c97bd38d88688665bf80ee33a2e68f755f51ec855af97bb25eba5a4b22a9c4450a219d1ac5f8adc6f358acbb4e7fec9c649c9fa7daf54ef81af2f94b2819316b9ef1e49d5eaae96f87b61ac45eec711d45685bb5e535f1d794f4937be9434915c29e97077044bb84aafaf02e5cfdf55120c36888d5608f7dc1f59199fb030eeb99e1e1d9d15f73406eabfeac4a0a3ace3acdc6cb1b9392975b99e73eebd72ff33ee817e9de33e089fb4c8fdac1c7b593c7f6fa70aedee9d2a94a5703f65d64d6883bc35b039da200654ef1f09d4619044684a7082465ae4800ad3fc2fa41cacfe5b852e87f9bb27116af101fdaf25f2b564d6ee67edc20eac13af947455f5aa3084d712957c55e8d385a2061c8801934680ff766487389046a42590cf49a9cbf5b40a1921b24efc23119bcd0e332df68fbfcbb6dd0d7cedc71c7cad4b884e979c4cb47fc1fd97a641ffc824fdd3328e01c9a765463e950211557e9058031991c2e962a454e5336959cfe46bbd5d4e58aaf23f795454e5144afc8e35d58d26d4d151dd176a3781b73e4b5d94b96f49d923f744950b47aedcef7763683f839e2a945be8bec7dcdbcd9d31b5629adb264e3ae89f7d6a428629568cdcdd6515b158abe7a2966d1a483ffb8402a3eabfeea4041875fc2a272971451d190b2f71136b25f749092b95a9a8ce8499ea52db059b74d0a2f6792a1ebff393dbfc8d3a23e8486b0ca8c5dfef554786f6c71ae63f27a5b116352e281fd11828f781515a84f281072d6a24784e3a68d1df0b9b682b66ca109a34363739ad772ae9911596136a7393d3a22e63a41b2a3b089bb4e868a4131b57b78536e9a0457f964dffd0f71b9cc701e5af1c64231b0af252cc651c2bfab64ad02b0bdc0ae4b29db974ad423aee99d54b39bf70a10c097038a3862d3db4317322a6a82954e842055d802862e6cf45f3b9676a1ee08b09cf749e68e67d201bf1115bd199202b311690975ad4b279d5422ec28202c5dea05038ad13e5795d187444f2bfbd7c0719fc5ae44dd4c097ffcf1a7c799881ac4055c3826af0069d4c94a1180bafeda05cf7898b9acabf1680e1a246dd6faff2cf56c5cbbfa78edc5a214c3c57f0931a77dc855f2847c9b146e577f526586d7c3a47528c540fa76815e8081d265e1974ed5921ece14b897cf973d5aa9450be320050db874e955fe56b2b73f1fe00b5f85303508394d35b5a3ee915ca57cbe8b270552677e6eee6eefe15c2f288d4348d47a7ae833915a7bee072b2e06077f952e22f5f7ae8e3573d8462a44e28466a6bdb0ab92bfb460b6d6d8ef2bd93edb94a73d0376742867c838e5f654d8a56a74813bcb94b6d24b05c06d7ee7618ffeac88b89f63c55c5614866aefcf25bcad6d7faa826fd1b0eba46babfb645270f89f6d27f32b7edcf7039395fbafcf99a8d59033f579ff2969ac6e04c93600bd49fcd90d2e6049433836e9bf44d6a7584d254b5a6fa924f54c116a8875488217643afb79bd3c2d155ddbdb9c38d5b8bb7afc9ddf96678ae6d76aff27edfb541ab9f605d87ed0575bfc7f6260049f50757a16b7b6ca07a6bc556b71f17c24f9d40bdee5392918f6b8bdf1103e2f2a532af99a2a2221466cc4feb37b6b4a8a34f4f1d8324d142b5c09f264144f5771955df42a6fa8b5c84881500a83ab7197eea45f7c90db3c55367c43cf6fc1c21854413471451fbfb81b2c8c1a778840af2a32f785254efd57dee89b649f7f377c326f3b970c87f86af1d5aa26e2eb85de91914c86554baf7577d2c89cb78a6626ad2bd3f95d7bef6aad786af086c3fbf893655efcd99e7aafffac4652f4ff51fb8ece585af0870efcf01f73ea9303599df852f8f8af73be37e679ceb1da67a0f74f140bf56d5b7cea0be62c2fd7ab3b35efb53b8be56df84fbee59cf7582af7d2a4cde53e99e7b2a4cacb089fcf954b8efdec1d786af55d8a47b2e7cedafc3665ed864be7c2a327c6d186b51f540b3f9ad3474672f8fe3e477cfc1a4e2fb63acaab270bf5331741554a7ee931170d4311606fdd0e035d403030aca9f28abee931151f4184634814173ea3e198183144620adc0882a8c024adb588252315a0a7a03f504fda47030d4438133d1cd8830589a0edaaafb5404d29722aa90a1b48d1d284e1867d0ad8b1ce8cc420b55517105135c8820a2890820a476683b88482202692795aaa30bcca4934e6a333d15c73f3995ffc7eac836e7475b6387507cea920b2cbc92e0e263a94a0b3c4134f5c46696375ae811038a1101dae2f412030697114b3cb199258a470c284604a8c97b4c2c54d82962caa5840c3a4d4d4d5c78d02c5962687141da89cd2c35b49013038a110102620a08353a348b1516707ef0d29a23869b20d4e4acb102fd610ac707338e6c8480baf1a10b961635d487302aac84506333635394e09b747eb488a2866d9d451c05ee2358a505c0eadb392be35cc685511ad6ffc3138be645652f51ec250a4b0d95bfad24865be7ae67fb88739c71c2183462cc10a305bd790acd58ba61a79c512c7d417bd47d32c3855a861c493b8b426a678f6bcae55c9893ce2f6aac704ddd97335ed8664e98995fbcb0a21d715f9cc04172d9be60b1c19dcc685f7240a1e7479b986a424dc3b68e937e514186279caa902306272487edb429010d3828c181830b405057dda72b7298b972267602a94959bafe632c02b962539577a5b458c27543eb0c1ad5cb30a2fa87ab23eee2c2c31d5cb87044868e1366c8c20b23b6a431450f33740b63b4dcfc70668902547f708e51a307196590f9d511a76470a8cd53bbbb5b879d15600c33d5fd27195baa3f902d5400bbbbfbe787343206162b8e38759101264b1859cc60514111a3336a58010a6840410a9a053260f068f9737b6802c480a1fabfc690d13133050f6f11042b270c999cb9991b08d0c325c5df61b801abfdb3dfe5ca226ac5430ce34bf5df591d71971813748a18a0ba87e142f55f30bea8feadd511777d204b1c44a851e14c1b44ec0a8ed802451930696011c56d450c1754d5448bdc1d8c15aabbbb7fcea3f1b57aca7ca180da3fe91736d47ed7176fd47e9c6f00194c5682d80205193f780b4b20518413603071b10216377bb33a22f94b99188e748c28098c151bd628e3c5cb0eee6a0107a83b947458010a335f27ad821669765c4a38ad1e9f205e2c95a952fba9ebc5d8910662630b02d4314817395c50bb8b3029002205751ce998ab0537c030e3cc172a08e247131c4d184141061c6866fe25e06289f5b57a7c64b57f52d7479dd1a21b56ccc8a10225a0e082341b694d0ac344840b4ee0f0c4ccfba83a1938aabbaafb947bab2332ea350621f205991ec8d4c1451217696a7f5498315e6a03418d69a2077fad8fb6ba908a313e54ff0da3facf305a6469f1d7fa688b031293c5271d689031c7520d66829822c232459634aeac4963c20cb3a8eeee146a6fd1456d56edeeee9ea55d6d714325b24518313654d71e7173c3cf8f2eb288820b2d6ad062cb1622adeebe52d5a1d0a8ae53dddddd49149a3aaed4ba9cb1ecc294a9a34f951de68ada61c64061c9a2088b386ac75a2d104ce0c41a682831cda005014ca061a38a982c9ee862e6c461fc8d426d2e58a8fd187437127414ab6bfae0b2a20586a9fa4bc72285ea5868f1ad41c72f2aa783d47e2bb4b082cc1542d47ed7155080c9a1f65402ea686ab2ebeda350fb6da29a2cacdf06ca43ed5f0ec500a17193fb54dcabbaff9aa02323b1be5f9b52979ba7cfc4f48a00eb6fbe096bfba1755813d6d37088153e23ed8cf54cc61069f8608309518a8a66b15f4d3191516209a4a2a2d9ce98465055df25abf7fef3a07cf513ea95e77550b70feaf653847c058a303406a9dc7bddb7b9e61fb875b58114c16bf7ddea48174e368242aaa16ee473860730a65068224a05a2a6324b6f48f1c2152fced8406d5becef672546e2a4a62f07d93f212c91d5e54bca9f46a1294913284f5544f546551155c73d03a4aee41ee048da0adefa782bc3dfdee35e8221f81df3e523e197a1913ac351fe263f274249da28a10898229c664cfedaff8f873eb55f33837e1d6342fd49fa6340fdae7ed70705f534707a29b346d1ac9f6a52359ca0a56846fb5dfd3bc7c4c6b9bc44fba3dce5702ca1ca176af91ab864081750dc51f79fd458c202691c52d5c502a9aeaa3ab8ed5654485016d74ddae2583f27c7eab8d0f9ac8fc24f4209c63ea236d209e8b8665ac965fb4514097c4853c417345499710096901ac206388c500189197f2375d2134a5dae7f518cad190e870b132df2e384ada344c746ea239adc9b386f1a41d6ce0b13759dea3e51b1a6565154f7c9cb5458f7c9cb15f58907fd331fe7f9a1f40f0e9aad72a1072d72131ff1ccc565ad671c2f38610be4c0343c9ca04ae6e3847c448bdcd4223ff752b271214eed6f46e1331b74c20645b58c95be67eff947e64024fdcaa02b17f2152b84bf899bbaf23b712a36e8284294da8521b0de9ff5ac1f59891ba7d09f73ce3971401116e7274e3882ac341ca3d410b89f63943a6fdea38b239fc58d3464fdae8ed8841fb056a0075fd0973ff79abf216df940df606a638ba8291aa6c690a2c50d6b788021081c5ce04021d7c7041d8d2041fd3f292854c3f8e7a4502dd228fc24701ab42ce7f97914d1118a913aa54586128e33a93d88d222178ef12ef3a061fc68d2ac61662b957f5c2fbed27e574f5a34e3eb723455050a2b5dba3c992075e4850b1a23320c156503a178d04f5ae41783f2b3197e96e267288e728d0eda4d5ba6d858010638a4887a48e1083b705843e98e2a515c968120b44cd144111d3a6099f193e0b2fda205cd1865d8307534c38cff09ff074d84d32e31bcf519250f5881cfa0c76b3660575f21e96af39c7c1baedac81512af3b3f41ed3950be0de8d5ebc2a01b4339f9a0c58d3a961b3151372143f93add6c4a373755946e36252c4b6ab434f777b329dddcec174a379b12962526b868f16f083a245748365f21f1bfb9d1bea09f0b31d0ed875aecea2444e99f9d6f4fe5bd6aa702db163e692327af7d825319a93438479a7427f6d23ffbb4c4154f3c688f3f5f1d69a30f3e68b1a3186aabf7ca9f822f25f4257da2ca3b4b212b690ce504f550373fbada632b2fa2aa85233bb3b3342dd4deaba67d27866aef840a138fe7099dbc38e0f9d7bfc221a2194fe87a07bd7abedcbef35e775ce12482f6bc8fe7117ecf327ceb75beab06ee84730b3a7211cff7c79c88425d819e21dc909af1842e21176a096444a961fd33271c274e58a4e41d4563a0db4331b3bc418924422d762fe0294dd1163ef1a0c5fe80bce9cae5127ac25af192129052e5690ec4ef55931f648298fd08df6dc65957e1483f2029b28eeb34eed45493d7f384430c471960d4cc5ea1cb61dd6fa47f66c85f4786d2ea6ed0fe6cef2d43a1ae2315ada15a8ce23258d024422d362b29d12fe85733fd2cd5cf50fd3f3888fdae3878fd088704e0c28c277c87f1fc2b1cda2f504320793ce0ce7c80434367c85c419af17c0f38248233accc7cbcc72de321cc6c9de2359da636aba92d5da8a9a8fd49a4a8fd3b7e3ec09df11c7177e7404b7a63e6e377d5c46ff8818ca9190fcf8be7c5f3fad7f7bb7c843f9ec7c7ffe0f1c1f33c3fc67ac09dbd4097e2f1a92257836678eb33de40d5fe7da94069c9f37070d231834e610f3b68b17f84f9b27a5e7fbc52a2fad50b7c357971d19a3450b3d713cd787cbc8f70c8a78a19cf13cd3c9ed57b7c74a42345fdb7b68ecef77f1fb771e0e421f25b3aefab23b3355b2f413e6ae9fceae8783c90ecbc87e3878424a78a8bb45e8623940fe9c8cae422e3f69387e8bcce7cd9a20ea7a331d2e421fe2d50c543fc7b9581571ebfc3e35d20ab4973e4bfccb9873b30d0f1264cede735eebec67fe4346a784d08feb3a2ca6b5aec288aaa87ec460e1a94d3b8d156588acf1cf54f6f6df7a2fee99712a8ea21473543b560e829a0b4eacde77c3f43f50feb9b7e3f47e57ce37c4fb141546c90970de2be9fabe02f1b64c506b5be9fafd8209def67301bb4f3fd8cc506f1f87e0eb341aeef118e44b5c73b084a91538af6bb4238500ae7bf8152364fcafc5d212d504aeba5e8fcae901d50caceef0ae1014ae1f1bb425ca014d7ef0a11f21ea0cd3bc87a09bab6ee805b75c0ad2d70ebb4f997bf7cf90d28b9366af1a8c5a22e687369e8ca1b0bd84b2c19f651d10ae8ea3b219fc2b57b11d6298819bccc5e3dcc78763a2d9f111dbba826f595fef1f0f58124dbfb27f170e4c2976f619396e1eb0349e6cb4fb2200052a5ff32c1d26237965e6a5183a25cc872d0ee050e6d37a8208a8ad95009da1aed370a23663ccfa941b5f0090f79c2433668a847435fa9bf938a8e9cf44fe7afbd0c5fb4a5f940df498bfd4f6a1cd51e759f96281a62292b768043210c339ef75c3d3e7a00f9fee6f2e3478a8212ed0ffc7e2c92573fdec73b01f23d20f8920ffece1e7cc9f015011fffcf41cf83211520dff31f52616a02e47b9eca8ff7f13bf35a6650f0ff697379a76ef0c156faf140bec865df4a0f6a6926fa88da40fc07281fc813cd5e3294d2e4c70309877e8412d819d16c147f673d16384177f6236492bf331f3fba88aa27b9b4d83f7e0466807982a18bb74b95a5d801aa7238c52ff0d665de5b879c081ebecc50102344104e1d8752992297da92050acccc9364c868418df639b85ad830c6d1acc19e12ccbced7370a8b72891c30ab312be2d5ec898f93738143b6256424ed4cc43d90860b09d304189a8598305403363a51fc6709935e853c5cceba6ce4852c1e1450b2f9e911051b3065f60ccbcef73b07f28424bd2ac419f14ccbcd5e7202f9910d5346b909e30f3585f831b8511330787b630aaf012352bc167cd07e0f76ca2d8010545b306bd38661eed22051450379f833b050c5353b30683bccc3c9c1cd648542f384198a259833b2ba1d97fd0a4bfa669feda061ece97ad8572d3dcb5f7972ee58f2d19ce77d9aaf25bdca669eb23493066befb77da4f876d53e6cf16e77bf70e7acda29429dc3f274f1254fba97d08d3480502da00ac1a9d2efa5fb939673872e5b86db506b9f256eacd396718c2d4a6a66d9a167e2d6e468cf44f87929b4db79f2d722cd0af0b698bdb9c33c8eb6a5da59b736ebf6d2d243a7eae9e0db76e4e2e5c27551a26f837c64808f95da6f6abd0d4af39afcd2252a6ccdf25329caa6fd0e31166ddfe3b1c7d6a73cfe0949cf7d79ec79d27099af39ef3213891aa2d9aaae56c00d69cf5ce05a43a8fea36d5dff727e79df523ebfdb90d478ee35ee388bcd36f8568ef5519893879d03fda6bff01081b245fd3f9d5e7fc4b275c22569dd70983b438bff5da1bc901658bf3717ef7e6d7475a6dfe061c5dd5e6293802a936a114667d555b81dfa4a19ca3fc96dd21f4d82fe50c3404296f82a0da0bb526d7593d576381aa5e7ba16e50c5717283bcaec65af7ad17287f0b898eebc4b531e81faed3b8117824aa5bb89ae20a29d9b052a719fac486511de5055a0f25278db7aedb23e9ba7d4bd03156b52dd63f5c0c63a2a8523531ea48a46abf53445f7b4debe650b2b1a0445db6a8cd7d6d9841d7950d000a0803261df4cf3ee9a0a66abfdca434431d8d544de9a846558db7676696ab076c2c2881f37bb5c17cce80c7afdbb8fdca8f9b4726922d9c33147d5ad4be45eda384b44fa27d4c7b20ed5b4a749fd658aadb8f53b6df8d8b3a2fe40fb793d7ed1ed4526a5b77b795deeeeef670e32ee3f96bee2ea56bdc499c19bfbbfbc5d37a79887cde97fea394524ae972782c30284be9d377598b216fb988941ac83bc4bdbd76ada2016149c48652fb4dbeff000264d3b466421c8126570a3aa4e024d8a3ba2a0f09ee549dca81dbc258526a3f570d6c556ffb249883536f2ab5a9dec7aadef7497055bdd527c1af7ad5b3f924a8aadecdcd27c1593d9c4f82dc56bdd62741adcaeaed7c12f4eaf1f85cdfcbc5634787878725437fde405e0478bfc66333fbe72567e07eb946d2557389e2fb4f91157b7620cd325d3d000eaaec856d535537d5e695fb9b856ddd5b58e57bd2dddbdddd793d961632e7d54dabebd352935bcf78b681bc307f0d74bfa9fd3edb6d61b3a8fda357a9bcb446298dac4ce56f315db44150613779440c51c759258b226e094dd211875c19db8fda452ee3c434acbfccd11924345c1a6e4ded1f7b49a76a21078585f20ad13c675f678d44958174d3063df710de6f4f6c68f1b48da3ad96b19cea189b1b8fdff75525ee0822d8547e2e662449932750483042bab27484d31567faa78d983c6eb544f51d8e61dd7ea56a8541a814245bb947c22b11fc8e3b868cdbf77bdfab2554eaa67a2f1cbb6ebf44b55da9fdf3bd9ed3a5f62a399770f53816bb27c856b972ed40479fea4142311c47e03a3eb1c144ed971bb4557b804de59753a865178ac0412e5821cedef23790488b546a9561ace5021d7da63a219490ae78eb33ba1309435ce4d16e292ae8d8500dd5539e98a8327da67fa41a4dff7cef5f5ba552a954452d6676734e95ea3d954aa552a9a05a74a2150b7464299642d33f5b554324f40f0969a45a0cd8512d321736549a2ec3522da6a37a0a09431eab3aca1412faa7fbfda91d517d7777f7aa39c3522dfac63ce79c73763fbf7cb9e2fc650ae8993a329aea67588acdac5040a1f44fff14280d05de22806e2f6b7b2b9b1f5dd406ec154824c139bfbb1fbdf97be92a709c63d72f1c998848acdb0fb1122731121fa9593325b566ea16b5502b6603aadb18e31feec720aedf7e77ae6c93fbc3fd04411186ea74afb35747240995fb0d1cdba993a6bcb0929a85128184cafd5882085ce5b8d6b6edb77ffbe514f3eeeeaeb35b8466aa8eae4fba74e8062d9a6ec1e37ec7564f0d2513e16d9d7d319aaa9c543b4dede72bfdd32ed4fe62c29adadf419f995cd4220a3f537dab66dd56c8f653866ac4332326f928fc8c89bf19e442ad2883dd68dda7755f8263094575ca7cff795239c641965d1e39e1d9432a03a0b28fca31273ba49f7fb5b26ef22f388d283fc73d87041dd94a6de690adacb75a98014bb6d23ff2fd3bd08227e8fc97d4fe08bf64b844acf265f8b5285b7420a011883794dff60b8e44eaf6be6dbbc6daf616689168ea264a96db720bdb821f6896a3395399c0776c44fac749c3fae7a4d4e51ac54a061d5fdcefbfb8708958f7396adb28512e4ce9d38265f33d01f5028ffd776a3d0cd761dfe1f62eabebe44ebe21876a50ff76ada5104f6a243de520a669b6089ae0d48307ae80f2af8d4d5d2f26380257c0cfbbbbcfdbbe4f2a804ac153129915d28dc56bd57de2c2456df5a47154574cdd27348ea89e8da6d3fafd55abce57cd706c0e94502a17a57273f5bd506bcf25a66aa2aa3b588b1634b05458dd272935d5f39ac3d1486507273f4fea36bf1eae60a0ccdbb66d38381bb3df80231423b55f076c81130cfae27e1d66138eb42e5451d1ecc5854c988bc3b467a72cc4682145d18cf5abd75e8303054988c122441d58d04144440a34a4c912c6872fc66071218f6bfb150c74fb91955e376ff3ba79f9f4bd58ffcc7f6f594bd57cfac7c379ed83e0e0e0a894f419a29147eebdcf7927aa53bae6840e030ff15ec8b9a9c5167dfa67642d1b6bb901a528a1e1119bd5db7c505aac65955cc64c344c7b4d8aa5aa2931176d0bfada183248600216dc5841862666da03b92cc8094dc871e58a2c61729869ffda8bdaef98e0fb1587b1fa400d5ad4be95a469cfe44994291d4069e2c4830f56dc732155811e07b6ca43a2f2c291fb11b8aa88fa5560ed9e73b5a8bd4a09fa31aeda1be91f2e943eda16b220b536665653a9be9bf3bbad9309ff9c943ad1bc5f009899cff95fc319ba6a372713274249a610d435952a045d75d071be265f93d5dfc6c121662387a6a359091e1b57929831c9f7debf0a318b2ec45815f262882e4ee060431a245e0841106898014205a9a43a66fe2fca2c524a5249361b6826208d719a7d5d6814973169a8d0498b51cce96740d59191cef0c257681c749fa49ea4a0aa7f95ed6ca1a60560a0a24c4dcd26dd29ba4ba1e8c2fce7a434e6327e6161fe35ece0f4837fb1f5157282e58010459c3142073027a830730dc2c461851a13c071b4c4cc3f0a090d6e942955be5665d8dd384085a6aad571a12a1213a424cfa269e17628e5880c916c65a41d3528ffc8484e9e0abfb2cedffdf9c9aafdf6d2c6d6ea6344fac726f4622ef36958afb850fe31e69384058a4024691fa6aa9b50248901716010d1270aa1243120daf24128490c2869c788fafeb88c83d67ea017e7007a51aba36b670ce84535f46331a0f5690d8c7f206357e68de9eecf98cbdddd7be5eeae74d9bbcbbbbbcbb15434f8822b647fce39d7d7d7d79f35c7ca4d05a2eab39bf389874ec5ad3220aadc4b700cab7cf7de346e7a73323ff1e031731c1fb77da169ee514a4e29a5ad69ee514aee2ec7955672f975ef831bc4bf41570ec1907aa6c6a8d48c56854d0c19aa191100008000b314002020100c88c542b15830509561fb14800b88a4466e52954bb32c87619032c6104308300020400819019a22d20400003365fc89744578b1fb8e15af4c40d83c06965b3b4d9bcd8df41b24a0f0ec0989aebc22e5e046003ca503a891e93349b3a3bc2711692107b7b50f9ab94b2fe085d9edf5e3760275161d5dffa9b11768c8b67f609b24d043163d935cc0aef777ce1448cc980981fc5b1d8391e0db4ff8562804a28f9f25965dd33a378fa2664daecb53106eee23f7549745920e6a63090904242edfde5b91b11046534b0c6d6301450ef7d737cfca82dbea9f7bd51c6814bf0a16d89304227864f5f886d1a26c98ad86d1565be3379a2ba835e5972b0280145745b60ea57828ec9f40848ba1a2f0f06f2c46bbc206bc3de9fc1851e847314e8b412d0e73b6050e942482525211280b138ca2dec6d09ab390cb998e2146bac45f101f7358472748d9abcfa05a651d208af8cd4296378022eeca6a577a2a824529218fc537642b03cedfba1e5f79280774ddc91bb768f28b67988c88850a3141066d452509d668609e9d62377c4ff927a60a39c02368f1c823881dd722f0049268dc69d0e051ccf59afc0c79578b6583ed2fb4d69b3d1aac7085ca5d804715c5cc7d1813723cf687867a0c31d6c7b23a0aff27011a73ac2392bdc29223b27b3478f53a7365056882f9bbe3e6c1e5439005e4cff6e08c4fdb0640223a5b69df439d363a267a2820fe22a6d22520880be5fbf4eeeb7f82af33e1ab46329d12c97cf3017fb87a6f5c681c2d4ae68a435b782556a3c7467f6600bab84b58ac8e33dae9e7f954e5946d0bf99b764e976aed0212c1f80bedecbda6a9b75f0e4437359266e2f176744cb8755849c6273fbf68c7a467a3e82c6d80050646317e4f5c7eea58160544136b9b6aa193e0fe64c95f1fa9a99baf77e456eb11c087556177bf193f648b7e7759641c0cd07426bf066d94cabd6dbb130254094b3a9e3256f1c7716a510ba48ad8204f6f95290be229ffdedf1509de5e339762022476ebcd7a3f0aba641f7a344c2e0db21d14cff508e18c07a86c1e0ed7add0f49dd1e1fdc99a0c44b78d387642791b59bb3218d6b3e3088cec8a2cddaab1a54940f95bc7bac763f18c04dbcc209b503868d3273498e75603d9733f58575ff1e37dbe83b5f71b053f96a7cd87aa84b93b37a6d3d96199c49d2ff869de405d7c0c830fe0e17f7f746e265e3481dff67cba395fda0663f95721de462194ec952ac626008c5df7b187e63619b40bb0e021279ac08388ec3437a21cf6d1e518e7c3dc68a622264b69178dcbc2cc11599cfc553772b9d50e063b31be6715d7f2cee55f6798f0b2fd64d21dd94c16d2b9de04fec14c87f72fbf47f79c81a8beb6b10993627d605bb0104e55d0de9d450869df49761a7673f79f603ba5daa49db68d6300ba1db546e43d76758d1531e6e0c93f696128967605d5192790f48978fbc012b440c0306309f2552a30251335d7fd15e6a239644de4296a34c1351b0390cbe8c190f1b80a6ab8eed988a1063251e6d91f505588eccc355d47e2a81cb4737c031dad9e31e18d6346826088d74eb0c1ff4fbaaed5c7eac5f11cb75ef7e310c74e965b7c2c97cc47fc02d637216447448a1c484d39432119f899e3da62991e01c0bc163192e309cc05b7606f2600bd93798c43add40c7400909b1c41908a2a5cc913bd34371e7a71daf9d4cbae058c167dec31d0dcb27c81bc4782450d7ab85deba8f052eee3ed30ff1d8d568b6fa06e7166ad1147a339dda95183f242b5eb68ee5910e9b993f4ba26c94a6c9a4fe47196bf9573e64f4d6676073b92b54e755d924e46228b93418a4bab1de57b70af4cc6e178fdf4c428a04c6194315e7796b42b5429b893f459f56e5f472da8484c80d5cae85998002129d4dc10b6f051a220fd50ea607ac28ccefe8a8fcab66f4a5933e9b9e612a1ea1f61dd8cbdeb49f9ccd79e010361d31468a7c12a886686763c9f911cdf92f8db2370318dcd398767f32beb899d76941f70a2d999930b2eb13dc1668f863db3c026611d17285a3248580f97f180549abf9958e277becd94abc8297c08920004ad0859e34ce9ffd49bb70f1ca7f8a0b6248dfd76c80f520a7598d53725f44b886f135a42509c0f328c569a7b029032b3023d77fb2d83a5ca9564a6f02c2e2a29ee6a6854c95713a7251a08ce54a0b1cef644d691eac55b864079e90002ccf6c730170ed16243fc5a0a914339295f90a92e03d8fc4a06407d4cbb162b6f958d8da9a7ec904e98fd3acefa623b3de48cd029c8ffdeb70b251cbb837312cde82aa04c4657284a57cea4857b7cc399da96d07330b553130af67e291dba0200912e02c681a2ad8e614926072d6701fd959c4db6d6412031f136e321b94faffad6b710c86f332e4122889b2df1c47852f3b163f4ba68a25df91d0fdd32df7b06fd51539b6ee74886238e1bcfa52c1b03629fe3b89da9afe1890d1992814d145c916f0e3b1402dee4b93d32868b0d761d03eae57cc95ced00a1087cb0ed3e910413c9cc4033d1a0bb712602c4b7ff4510d68f1381ec47545ab8a2bd3c7c939650464261df01cadd85e5f8fc263e4293e5b2a5535a5169726661d8991601a279ef76782e9bf4051c9f38fd1a4f1bc49a0950eabc37db5d4a77b7d88c250027bdf18f4c784b451e833b356d6178aac30c99acd5e352670e85e7b59581e6157c6ecd3429125b192320ef7565ff1c04106988def05ba54526ee5f5a517d400201818354300d7f8fde28b6330140df2b46649807b57c4c4dd071649da9e592060ae341cdf0dfa4d930f445272618878909ff54c3851411a830268d4823f8941466ee91ac998bdf44b57e769b4a579115aab5d02caa5eae58e6963a6dc6c9876deae19fcc308a6d3442b19907ece39e353cd95f88e9532912e9dbaf08a7287678753739d73df3e8b03013f31e6dff46d967585d8f50ecbfbe9e6df003f23a3815f8a5c7915f53ec0403533abf9811b03742fa9a2801e2a85aa87915c45abc4874407e490a0343aa1faba62a174bf92c02b143c48619bb7d86c489886cf637138c6510b81eb3e52602b33ce4e68111c113335fd33a0a20d0761e00a73222e705cb31a5c4e149f564b390dcc2f5d4f3ffc26e12af7a701bf66fe61bf500efb7e38dfadae9fcbd4af93bb8453a5ad161f2e3920d58bed92bee7f35789ebdc218437c0ccd45e04b09531f2fc85072af37cae373be63d0806b25565130cc44a5641e9e4e731015dd8e4b03ea2c7239f94354dd35763f49c71d28f0c6102668751a9e80889c7ee9188eed1cac02ebf74f2171708b15da8e631deec5b684dc0ac09fe73b874ce1c9dfdc84f9c8a80b6e2668c17dacc370882ef669a30286208e8b489c89169e1812d7a2dbfe1d55c7d2158a6173213418349fee88b33d33282db0369f02164d0ca42886ae4013b3dc369519fa0d04f02e13dcb025e5c25d84343e6cf0c4b57d1d311b2a51ec1c77607ed037468b5197f59bc5c1686704fe50bd20c062e777e7e8b30ccee170b5441ecf506713e08c5010df1bb49c2a1253fb51642c180b5931d9b4202b4a57d757994817bcc2a9728cc6399a5164c06727d7c25efcecf59fd52145873896a45de5ac40ab968c8112d7f9f75f529dc0c30241a036328f442b7e908c2a3f56a01eb2825ebb2abae41f9dfbeb47c080775407b4340fe834b9abe69625f6005d241a9a0d14abe9a13b76a70b4c5b6e30716af723a7dd410a845592b77b1f61d24776b402bf82bea60d326e457dd055579ac23bce2b133690d465647bec6c9da579a250ab11137b4e7664632179cc46ee59ee32c7db023d4f3b12999813fb48a8fb15d5d06b39b04c0f000ca9e74c2ea7bf764f9108556e9009f1a3125b6d098e8ea01db7d217b49b8333d34dd7523b0825340487c3a7bf2421d7b0070f6a4422e426f1aa14a0711c80ad47ff50a7a2634310c96e7029e316e7b7dfde0de5d95478a749089ce92513801f307dc139df329b2860c63f25232527bd5b2b94202d9b7f12400eafadb0632acefcde14cf35165c71f178595335cc07c0f469d92748ba052af5e3c71b7bd9b08ea449ef898b144ff244ec05594a14d8279cd8f316fcccd674cf3cca85b433589d001147acbe15f0294a1970da64f5d37ae3906ff499dec13bdf0d701685f431c1cd4c0c5b6dc4e2b90125d779abb2f3dc31b6adca7344210f965ebc890244e8bf84b43bd6712c33cdb2037d0e4e4121ed5ac4c2cd93864f7063b0e9d63af2ccbc662ed530ce5206b34fa67b6fbe3c4b37eff9739c946d8565376805a41dee0f7cfc2f067e19602a38cb5c2497400d6a82c33d541b1a008cd37b92eb4bb28c50434835dc3f371f74a8970dcc357d5963418af2cab01b339e18ec2189a6d02ed7545fab15624b9c51c664e96058f36d42752f08da00453353485e45a931d8913e57fd95f2c130cd0f02770c1331a3e05783f77dbcb2f809c32bff1c7eec0b09d6120480dc4c5f8085341b419b4776ee729dac5e88f559b2817aa453b509b8e760ff57eee83eb4caa2b13f1fdbe381c8e69d3523b93476924c6de366dbcfb7028e60e10c0d99c491a3a536691da7248fa1b9f0a7c7a5a93d324c617e3f34bf989359b81e9ef1bbd35e821ff40cabcde9337679f67953af77c0a6aa42a9298073304017906e6ff899cc8f72699add05c6b40efc3981fd425320f5ab0d34edfac5a02181674d0a03573f362f8030358d7f48ff079d75645f76e6f3b617fce66dc28409977ffe8f7c7ce4726da7ca5ae132c9d2c9a16fa56e53f27ac6911890d84f3b984b6aad0f6e278a46853b1fbddfaec523f99d46ee6f0eb678138ebd0f67654df8904006e93712c0ec5ae29b9c0eb523932c238f3993e6548c7540c23ea104f8b3564487031bd36c200d0361404f9aba71e2ea8a89df9625f42bba98f7893593e87fff92661cc4788b1864b345e5cfa4e8d8a6c99af02f0d95860c7294fc256c81756a8bf9213cb313c6ddba17d152c91aa36b6bc2f708105026b3081c45a182e15562b8b15bdca8b0e6bb5a641db791438b8a516113227179023990d4626804edb1eaff7f567ea97990f1ce3f6bf25d73a8be99099553aebcf4b41a64d899cb82df2fd4571bc4d07f30d760d52a70cb72c4863401d8f8c8538365b08ddbacfc00cf7f105905d136e7152e1b23b976afe247b91eeaf136fc09edb77f549b54c1235ae4ec13604badc073b087391496c7634911b01e09216203ae13442c65ae0667e39f564b7c2cd9c3f4936c30e467dc4146084f0dc83954b56c054b33c18e6e32d57ed7e44c0ff19528f83cdb9a1367106f6e4877aa0697546330a9c4d361cf822842c9e83258f1c447e350fa698cb0d96af0b45949b1c165fe3b399d1e8de091c5df48e9ed9b34ac992a4dcff96ef0069ea75ceb7a8b1ec4f1c951643360512fc577dd964eb4947f881458d9274730788050913ca8cd7b99ca0149c5ac50cddecfe1d24fd492147894e3333ad4854b82c4616252c23d1f31ab30b5b10bdd950d8434a64442d7237ebe98ad29df9e7c2612a22921e927c11fcc3ae2b70244b5bdb5e8a82947a899f1cd558e66fb16563decd84c51c63896239a80bf21fc00f8bc6796bd94bbd06e8ff8110e0613a8dc1f2d5ed0e74408ae5493967e7460b8480360a14b073b498668a9fce69d04da7bf767a197ff63db88f1249d40c6bf6a86e2ef24c727e2826162ca3712eb1c42032421131195223e58167011a1a2d6a8a3601b49d29886615ca3152759f1011b15dcac2991fb4b9e337911c03ec51b770919cdba91d5fc61f7f6adfdee2a46ba54710991b8bbee22eb4827b24236f74e279cbca17f1d6a54d2df04e2e4920d688faae95043df02ac32367411978b362cd9351b431f7048854ed8a94037a8d2223ff7e61950de9b457adead8a0618b64ba0e37c39ab22d4a0c3306fc9d8027b8b7794149ba045cdb1577dbd3cfeca5effb48ec191676b2f6df1cd986ef252da2d2fc54954e3be99f20c283eef53a272fae27144e0498c582e6bc500cdad209954100032c55805605612140409d69919f1ede26dfaad5c78ecbb9dcec71f9d08c8f1ef5496932bd6a05d97cc07290d940774c4db21796c65e5c0d8aed9b9d6c368766c415ad8eef039938047e86808a79ea03f4f9ab75761ef88e19e10d5d3cdf06520fe741ab63000cbd1eeb124bdef1c1b8cad22c55e115250a0059e8efc1f6aa4bb7168d9d5ff471400ef8ec65afbf001087f95a9bfbd2af90170d40f04a0b70f54502875fa2d7a10d3d97a944375569f6ce3b1c4004ccbd050708828f8625bf89babf9a4a1193c793d352c32d568f80735ae6ac727dedf82967a8d5785abab27b4609af8061401c9f32897b2aaa14440463445baab4ae731bd6a15896a6c6185983698ef14eb71aa16ebdf33772dffa6636f70f50d18aa3d2ed69053aa377cee953f516d7545c94b8d9baabd3e19d0bb5f7ab2784c07fc75e27fc26d9c57b540dff13499449f6ebb600a477d99ac17e42d0067acffaa7f0d895bf7cac7ef730683affea57f5a077e59aaf3d692c1e87c98a04e6e16190b831e034e626d372e4984ed30809637b24a4efe602f81bee9ac2175530005bfcc3e17e3ea574e859ab3e7d3563d43be41ff50608a90077c470d10b46dabbe489ba01d67b094f6e42a3496ead7d556673994f563684ae2696d89615b861b6eec821e105b618962de3f248fb8b5027a661f23b43f7b01eccd2e4746d0029822e2896d5004f65877d39019eb09d147a86ec8af2e5d2fb2d3300c334603692fc433786c573b14ffd981cc676a10b020f5def29c19e9af561fb2696ab5a36d73bfeaef6ca1d49013d3e850f07f77025f901a248221974f15008795f193550b990d804e0dd81a09bdb4352312d1b26f26a4572ca17f836bbcff211f26029112440e8bfe4a619e8dbc8153fbcb5d0c4009a03d02b459b6bab9e3345961ed4846d45103482f5a946dcb7af39a445acda28020342e26a5b5ab0372226769991097e444b5d8d206d2f1a21e3a4423b541292767a20d2c3c3fdf30da649d5b26fbb61cded2954342d2459287f47b298bb58ad3cb32a26f350ef27f41294ff482f0a1885c0fe55b1e755191449aab1d26e9f24ba23fe6b00a12aa8ee848f3b83521ae08dbf051f14aad2865d19c12c34a39b1f3b54a3fe1169487cc0ca53eb30ac92a826d8b7c6cb4a59c41c643831e62ae4b8ff7482b622cbf8ae9adf0300bfe0261a2c3d248dea00697a9be5d461516818ee6af3a96125bf05e52ae55324274a6ae597a62c8ba9d612b93094976c428693b1a3029c5f89a80e5541f9b2a13ba6f3f304c1766a3aa00602fbdf6df1552834ad18eb0d5ca6817b6997bc28a00dd5fd2fac056f0a181916d2a7ea9175026abb62804ba9ea3999fc87496cfcb317ebea8a9ae220a063512075fa555c03286c1b2ee6d5a4fed4ca497ea99f08c31701fe140e2b3f863ab3cbc2117eab6c0f7a3a892ca5ac2edffdd37702a9c8f1888a974224a49db100846b3ac2b240ba5bd6e832964a33ce86943f06b594cf51df3b701eda4fcc0b34d52a5d27ad719ff034d565c3af4d92ff74483f3c107f4b9a3f197d0a7d238b3b119564503dcb33117734c481ab25df2a2d76cfd73a4fba9a6ae8d253656e859cf8834c1260ffb4034b9e7ce9b7b3f7547afb237bff9fc5b25a0eba9973feaac690897ee82d56a75954c090eecaadb710813dab028ca782e9079c602531869a0c15b135d94c84a424f5ce0ae97594cbaf16360e0cebac440b0cd4a84cda48d1e01a1dcf30c65de0a25c67079d2bbf0f24bafb9802a4d88760b2f3ff3d158351f25a48ea8d566635812786a6c59a9fd655a1cb6f2e74b245d4974738cc81c1a644914760af2f2efc6974295c2818c676cbc7906b92112df1d14ce434091dd22063fd49a1b0242ccd8b3000f140889aa8ea8ccadc51529ce4722ac90031164b7498dc9b9ca9179adc2179e05da728ad4ddd091b3f1393048460ca06a930eec3910bed4a54ec01bbf25a50fa4abda465deef28e71c49e1d019e50c0bda3d72d229e012ee0445391775c96a6be330496b9133359b897cfe85f182529a8d7a81db1e06d4fed42f87276c4c3a7a78116b8a611122df76e3cd46933c82c7bee72be8b819d3aa148cfc4c3b800e4f579b5d32dd00f63319eea8b246af48c003e725a92786cee12d1c25a6399d6dd6bda1b11b66a02e2246de3e6746146ebb1803c6cb7af9d38feaa0c6b8079b7aa6d5671e41eb61845102153f8a0c7f163a9c1484fb4500cd37b9de826c25cd8d1705e86420ad143e36d1eb4996ed616af5d9f84304463a6f6c83c5fdcb68f11823e1adf8049732a05089f79d584ab1200e174c5c93da31073f119998ded8ab62fc2cf3f2d6b8dc5e7ac18d65ce65565bc948a5a467e93fb3931ec7d5f8aa7d032ffc28800de51daa1512d8e11f066796ee7797a79397bc1e60202c895f7753f586cdb0b4fdeefc28af42b67611ab21be384c4c8e32c3c61686c0165aa25542b7635c0f2d65d9150d0fd5428568a1eb1ada52a03d09dc54d2cf986f66e423a861e2b3a72052adf2561cc154459bf3e87c0790b466b0fa9f0ad667a3ac4a9034a033c348fe1b82d0abc689d0808b5ff47665850c8bb416865d6c4c2607da9b6b1ceb00018bd2384afa62691fdf0a638fce9071a19d3b1aa0cc087f19e85a74d2d5d5df1d485c4824ceba621e991e594a37c3d904fec165ee72d7c98e24ca872722e1beac4830b8bf6e002f5770533295fed08fa24812bd9ecb9de0e377ccc5be2784e258e598a805f100312e673c925a2a98fa584f0a18088f32254c1d172a688ed1fa75186bcbe368d45e1002e42164974bbda900cb24e49640bc9fc501a50575af65b549fa33aab716c3f532483aadc7d1f6c87be2c59382d8a16abd0381f4af0f1c548b63c243bc40f9612c3473d0443ac97e10d8f3f6aaa832c9d317a81949037bb5165c7ea9f2648830203772c0d71a0c6c6e343f27886cd51d54588fbc511368800dc60b59ed48857697d4ddf46907483e20a22a7d2cbf854a2611a9eaf486ce68ddbe6a3a400b358ec64caf014415fa703cec59087c09e89c4841dbf81eaa832440239501abe9e7340470b5bd994da65a2ad394d194e18fe7875d9699220a1d32c3c16dd7db9df562255a062d346edf8e1b6c3e7c164f530d6faef927c6f2a80e2e67401fd573f46d95ce78cbffadce175c5ca4991255fdac833c18d2e273266b1228e31e35f0cd012c276f15eb963042439f1a17ed7a72c50f255502b8826e63d4613f1b5584a985314487c2616271d048eb7642481ca30d41591cf4f9c8de72771630406993b6f0a60fc17157bff20287f1a3589414446d5c8215be82899d7546efbd502f14c5f37e036bb5d14540a6a51cf0f94808150062d82a1702dcd3cb71ef7ecc2ee974d5f512d356010a1e3991f863d241c43ef81c89baa419b624e95c66076efa08ea64ae3d90cda71bbf691240f2c915b53975d42bb7a35d6a89952071f31d079b5e5bf427266b999855c391add1a0a19216463b228e58987447aa09b77c4907761a2848d46f53a552eaf8cda8399058a31ddc972216e9b4619985f250936137f1c2b3828e763898d0cf98fac1633e541fcdd9a8a7c8e81fe455768f64af91885971a268af10c423f2dfc7849683960dc27e04e50bbaf62d1b98bc5c9d5bb87dcf684a0b14ebdbac70e055336e05a045cd9b1fe200984702290207f933c8c41766de63ec318b65fa9afdc4b7fc9fa7b5e547171bdf2311e412aec175ac0318d102fa0024020524bf69482176e402eec205dfb0075d9f5992f84cf5ccc3b755e075ba183b04f60af11b62647fd83d60832b22061cd5df35aac7f5bf7c442bcf882030438cf3ed82760cbe3c7e931dd45d46149565ff7ba28a5edb04cb2a95e95ed6185a90f28ba568bdb6005532a93c7a08bb5da0e56401501dc09b926ccc3b27c373488f5d22891c4b573dd37e7cd89373b3a23a4453ae325f0821faa0d0a3691390d44bd1a99deb2b51ef15332534b93d106e95db234f1793985e41c2d29b19696db3aed5ee0a1ab9309058327a81d793c195e56f63f68d5cdef9103b68e120e2b0c428d86267856aa4590d8a8b558f62872703c80bb3c6d9fce460df7533a91f9b40cdaae6d17913295c6a8e3433d6c591d2629f19cffca17ab75ed8197629bf84cc6eb6521c291d7640a300b64046c364337f08d2b2ca9a60ba2d31dfa0be95f3f1da5d02baf0b4f9326dd1960e67f64975b596a817d5ab41a80a5a34050ca0936124c98c29cbe805a488a27e0c4e57637d8307c4f95544f222f7d9b25ef943b72670f0f86150a5a61e3b6bb1ac0395cc2fb0028bc202bf50596d67f281e4a4f340f9d427f370e210cbdd95945227582e29ce74081b4a5d9a0b2e020488c4492cd13012b234b84a30420e04bc6beb7c4f96ae5218979f370306101a3009fb9a7bd39a9d71ad07ecce75be2564617068c01527c33e80039ff081bd6ae5d79b0906d89485acc9c4956a69eae05fd3c0aaaf70ba05e82164cf55040d270ab1cd2ce887d07585378c4bb6edf65b6fb15ab0228afe50ed463d10d98891e431a6c1ba85b096f8a8a3117dd622c92ea7943bda79c806e24d7ad3a27ffc0d944e7e1edf9cec57b4a5e0121131e10c130ddbc190e73fd59043274e0019e90af9064494366be3f85a553637c8108256bb95ef7e28ea88327d18079053c46ff21a497774270b6163853ce4d460148453c186ca946253a8d5f4ca4cfd01c4d79fe4606ca418bf0d4923af1060e261da83657c837ab99265c07db9c95f92e8fd92255eb182e4146de681f1d7684edeb11adeec5a86866a83d1d55438a3d5b6b895988dcee622b5d37764318d5eeeba118446d4ca0619060e3c9d912d2f235c502292a1e3019e6992e1968003f4a2deb2af464811032321f7a098f24e12fc9362db56c9d06a9cf2f76002033b397978c153673508972d40de1a3ca9a71630f9a48863246646c9c9cdccb238b9cca45b14d187fe42465cc54e357642050a565903b6066f295c572abc0cf78b19b044fb5587666f953e734691002e13f9db1a59b0a33b2a70fb4dafbb8db6d96d08be8ec8545ea5569d25e49c778f6121a2f6c42e637f2f785d6d119ac2d54d8169e84145ab77298ee0c67430183ab501283f35c373fba41ce055b1aa69e9d521ec0da0007a02ffd4749d3356d766169b05673b86db119369c0be433a2052fbc7f0318c131c85bb9b60001498eecb29bf0781e8b01de6284d7ae3818fd27410f7d7a4fda8994ffbcc06e8ba2cbc601fe16d19a620ff6ba8869856566efadc6fab79b6f1531166dfe7e310cbd1f231d7f27ede5805d129a00e2dde4da35f766dd7456db2b32a1b434675299088d61594c35c4ceb33dc71b415b475241e756e9eb3d7d9bbc951f0ce26a83f938b4ad1fabc24b56b5a32d3f0d36f78ffc6dfa960bd3fba43141267786143369a5c8e1ab69fc8f92a12b7daadbf4bea4f2a9ec9daa253fa73fc49215b9e4f890d49513e48f8a1b6df60f2da6e765ad70d2c9af0c54b81e3a4469896de32b344cf92aedb8d378543b9283aaaa7af42d75ed49802abcb6f66c52875218c3591cf74cce4745d6f9581c3645b84a9d51564b4ed84a81e1faae06ffa5b1b8b90eac024c6221b7c3a22030f0cff2acbf621cfe586e229c6a2fd6782c82cdc86861a60604d8115389c656a83bc5ac1ea38c8866a8b319fbcc211a4903faf0915280b98f8d319898e39897b13ca6ee16b8c29af248ea2bbfa208aa34a465effa6bd94402347dce9d2849763c724c627602b2a0c4717b5c044e1a85d8cd47ce98a8b01a70891339e571e5d9c3a3e0a95341a12343a6eef3ad5d154540e91b7883c198a207c0dc66aa0a906f8841f458862aba42d049e1cd908d9c271f6d04fb59545e2756c68c602fabb8dc4daec0597a816bc56816104dfe00fd49616076bb565a987a9004680249cdaa96b26bb2ef44aaf346d6d5c85ff573b31fd1a934783c5462b681fea5d58177fea95f7d6bcb511d3edea8aee22cdb1e79cef3c4cd8eb0314433d45f2396a54556ec21a943072af189c6c27f9bdbb5de27503efaea51c6cb857a9e40458183f0519c1afe3332ff4eff2efd2e0468936aad67e39ba8658ac96eb6270da52f1c533814fbd37b6a8ba015fa0d9858259d9248efeb0b3db5c392ac6f3e9b88d94d5b704e9d686f0afb696e10c702e64cb3cd99cfee350a9ef0414f889d636b2e91307142190185f40ed433903441f9903b145a7c8e91d236a0a85c53ce04f2fcbacf772b23136f3a6b8bc62f8ab6c096aeb5c1d4ec701b2acbbfb67ffeb62e13d241fca00d7b94e38a1eb53b42e53d0ef9de91046e64586e627e54557b5474365b2d7b3100f52d46740dfb58235b074f08420b752a6576045e0eb6f8f910c447789964eed3733db04a3b9d20cd8a692e12c3e42bb1c6891b41c4be22b71947f1ea06b8eb770a002e9b91a7822c952ed302cc0ec5d03641322d16e0900ad3efdf768abd129034da127d36995b4050bdb8d2431491b1c8ab666a56a9cea0767ce4c5762b5e967ef41619328d3d8e5f4a191bc799e0fc2706db12d274374f0723ac0a5155c3c95535fc0cfac4cd6c5b5900902224b43a11de5a282d4ad439e0d606671406080d551b557b0a828caa555aac74dfadd80d15f03b02f58af639f9e96795088dcff87f2a508e24a802732a39275a2327e1db4ec90b2875a0bc40613323c04176346ccd4f9856811eb50a388f9cac5ee380630d34f758774784cdaa263299cb1560138fb902e2889673b30f57aae4cbac05a4d44043df70d6abec87ecc4a7d9d1d9ebadfe877508695652e072c4de401403bb1dbe4f2f348756ef1762487a4cc734554256ae21d10f1e52e7979fd39bca13d2ca4b53fb20700de90a38caeab2869bef06cab9d3d6c51cb286744da52e5d5651d9670d89d8b5a9336d5d787cb6deac65260da8257755d3b6149c76a022081d1d8f54c5f14bf0a0e9bf8e9bfc4a0f8f43696fd2bea70fd5bfa9a19bea47bd6eacb15f95fd09c6f0af4f9eddd81f2ba117fcf0862ed2e62714c020c8062d8800f8d045c9e4a601b8711a70f0b42d8461be06628cfccdf1c10ed7b51cfd5d0df2df4dbb4eb985d4859a2860a14f51c73eddc30261002006dba5eb630e4ba253d3fde69a46b8ee9be7c7d58bfacaf1d2394886f5b0d0f08b158beb2b2f9a9c7b3ac052fbfaba7eda230b15fddc8796e455aacdb7a061cd011a741b8dc625bcecab55bd9e072ba83635c4e6a8d1c423b46d7075897a6d79dabc0738818788a8cbe90b547ca834ed3b88da53682e3ac3e09ff564aedd27b74640e42edd1aee99b4e4725199e0198bac871312c55dd7ead487a24f8725cd8e09c318cce984fba5abc6e04126c1cbfd674d93c47ebc09c0062d9c003d7f06e9aac04ff4f2aa5d21d6605fef3e32059b947d68f4060bd996113531f8ea16781d2678af9c7c1031614d8620a9815557e6d1262835b0c7464a91dd710b73ae6ef5b72966b2d58927cb5ff7aadd4a31fe5afa39775158c53499412b454f78dc2c107aac01915b421915d8a505540a2ee119c56734ead1b6c88abc9aadf0a1958b735ae1a5b095ed8ca56cba9f936c1b119ec4cd9a421ea3a05664533a366b6b0ea587a94e33ee554e828e374657349d18308eb33006949058d19a34509c3cb868084b68f8b89745a68fcd88615775c93d96e215d99bc959b57f3c1c8b32aafded7ee509ecd4456514c6aa2ad62718745582487dc9ff571bc1063f396ff18c82a93b8e8a190c7cef1b0e4ad1cce156cd1c1e19af092b71a2933fb08d2cd01090f5114e916e40f246aca8253e2a0504600f5b9a24e84bc4245d5d73f069f389031785f4922e1c2af73ecef590b149e746a24034496f861856cf9694b4e5e94346e5f5e8459f4125fdff1792f6243acb43755cf1a1a536869df38b40f0486b6483659f0a3ea809ae6691ef9fb54477abb44f32580fd3b7a004b389b608ca4b3c5a484cb9369d7ace8a12e5c54f004e71a6b69a96a3987ba60847e585ed9a2a2f22f6245e7643900092747af32aefb4e9b394435cfedf7600e63a54c0d6822ae8ec4a17d71a92388fc64b4d89c0bc195b644acab345d4f4ae262f2b043fa662d8f7aefa3feee560558675c4ffe3d48fe3c3563fd1791f7111cab8a6bd11f09cc8268a314b7dff6683b059390537867a3042f34bf849f16b4d29af44a5fe44924cb65059850c6678eca0992c40e990a0e5948f133685b376449e27e3d2a2c2a9cdc7cc540b8a28374eac168f79e46ca48b13482b553901d7b7ab7717e91e52fbb0e72acdbb326cc1b18d188e41bf307938055ec40a605cedc09f93ef8667e74f7b6252a938e8ad047c8e8a177cb958019049f344377358ab43a4f5d45021b2c4dea8d8a015a242815f9a79e85c040f8a72cfbef362305ea5b1ae448b58628d9680abba2a624b724609c88b66e9844a7b333732894b85253d24a2926d46633688b3ff318214c647bc3adebe01c443f79b753b16388fc9022341d08a58620d1003832ebea2b9ec9cc90b3e0214443e8e1873184cf66d143c9ab00257b4344b0024a363120499bee90cba438cccd86c05a29a73b9f6e39ff255a11bc63d9885fa8b82b523c6c876a5e0f37b0d47cdc0804311fd000f1d6d0b7e27c28ec1968643c83becee1def9ddecf70ab987d11b1507fd37bc72cdc8eebc6ed9e45480dd13f1a230c79a46411201ab27fadf5e1dd3f4ecfa228c00140be6e0594fdd4076402a1d87e66dc0167cf004394bea18429e9a595361d5772099a0cd4650eef888b4b34c1c183539c42bfd8e03866d542948cbc52e11fdec1a13f756addd66f919a4e7a1175f5f3fe60c89a4a99bb2e261ce9ade02d5da664cf7c44df658dac2a0e904250396ba08d7d7101998fad656051726a92f702a66c3e47ed35d1d62555346d98924c15a9cf0f293019b538365a29a8d571af996079e225c9b2993a04ee54b929cbffd0a728477602c2819aa82a1aa2c2ee14801f9f211158e0a584576a4b86ee061dd98fb9278095d229cc4c840538cdab261030273681d8cfd4a9e74162a74538e6bd9d22b15a124634b8c6b4553684e825a9a6d04af4f99474dcb2d91f0ab48c4ce40b86b07d845d18b7063fac3c0d23406846347e28345ca5da74302e96035e2f89c9569684a6765fe2d9913f21a243548e544fd5df813cc77ec7254fd90d366ebb828c9640bf43f0dd2c045527b0f7af22599082dbe84ce521d82751fd2d021b6d244a36ee23023364648a9c9d23bf8516db64e80f4949f8f66874394f9b43f4dc420d8907ca9f8441db5cf325ee4c12809920baa9a21b784b7f7ac38ae849a98ddddc978c8315fc01d90cbd3e8e8382652ced23a54707d9c1e481393c528008cebebaec55072ace0b2a524d4950f24da406c77485ca0e2eb4bb201d0bc5d620eba5c07b15c33458df05d55415ccc80220e7f7972fd0397cf6107482ca8262e0c7d3440c666097bd0d5ecd5eab9878e24bb9ac2376e0b27e0f0689c6593e045d15f4db78031ada836125b32c677167e3b64b12140af6a72aa6aedecbb9f801532f9a048fc48ba73f11f2f96fe0bba4ed120687917ed6e2b31bc654f3b9f114e77dde6ecb08a9e63ab212fb5618b1bdd6acfd290f15144f7ac61a191fcbda192f4b34a7eaeadb2f77e53af511a2ae02ae328cde8cc23b37c89c5434d864a0760d51cecfbd39095fdf76d48629575498a88e2e8f54c7d020820c6a91ed2afa9673b9c475ca8b837c1ec8abd628000f4ef12cd0a93a620eb04d775923fe518ab8ed645718a1a6d476c873b7ed2fff0d512a0cc599b93dd6a0835d3714b30d2c0cd35ff2e060f65f916c9193c8dcf022037508dab83e128385567e90cda447576b81ac2b59ea8cc215065304462054519d670cb772dc465e7fdd3644a4bed9a5d3556fb1abccdbb5733acea2623b0f6a91c1ede326757f11f30d06f5a03a97b6644e677b5079877da41032703d276ada86e8d1a6414f0a4457f856da7b70c53bac2aa195a9ed40eb3f6567b3225296cc89ec209c6c15f71b186efbcfd4019d58fc8f7ea342f9313819b1907dd687293c801ded01dd719a6542c1f1e8a21adda0b38fff4ca05e912876db9334baf93efd0f706ca5345f4422e62b67f93723668d12f3cb19107f593fda76ef6b6685525a38052717743927b6f78a90c69f071a82ec6cbb2766b71db4dc191e48c4abd433272af1cf8b31199b851cdad6657d2ccbcd200fe363054587a29a7f3efdadcc72ec8f684f2033302efd5cd405fba3e3fd8ed2b04c2aeb88fcb2969c2f7e69518c239a299fe19fe39ab9ccffb7f5839268a9e670b8eddda0a244d78b7b4efa73c05d75bbd87a9aaf3278e4482d13688ba752ab235c604b8e9e631e655ea8df22c8cb513074a48fc9781492d8aba5a4d8e0641bd70deeaa9cfc98355be20a5d8afd8c729fcc6b18e5eec0fa3f86da588f03bc82879b96cda112f3ff8a15a4f0ec2fffe1e29971a4957f3dac74e96c98a70ca246a4eb94465469170280761397c8cc50c838616007601514ed597208c816486c03727205b15fcf5f0678ece22459870a1a5a352ef46cd1a4475b7110dac84c4955ad81d016cecc2b2d1ee6809c01ff8502d26553b84b069a354c4180bfed0a59d4056ad5a2e6f7e8ab925f3475cb31bad2ba5522e7359b4bdebe5e2cbe201272bcbae03ca049bef12995d00d9b33201bb4c5c127f9231e255e9cee9238391416c67387f7c1e056fa1bfe1bee002a44d2ac3cdc9cc85cbb29deff2d06011a9ca0ef84d8a8062504382b33991000c94faf7afbf01c61b710593bbae11f8a0a5a8800f045ba1e2c44d12b84b2b678582a64ad1226102c6774d2331c503bf055bb8145db01c5e087d7cc0ed8eb094671b8a72ca43301d281a7bdc0c11e3879a399fb4766ab896dd20e08afe1d00adf430aa72cf30834277d2f1a47ed787a6557a2340958e409c572ede4437fe33f4ec9c18ed282570891cfa8b9b234ff11d53a022f96b0819a57c785a6c413fd954bdaa563d07b30386c180035225d6ea4e92eb63d47d6d7d64ccd58a7384d6614b07624702b38846f93e2d27b60a69a81478da02261fea8de8291daa0702a26dcfa661844a273504607838ecce9a819e23622c1957c2b3d409df1da71c10fc0efcdcd3a025031d462d95a298e118376305d1b128fafac7accbdc89c8fb8a827d63582d0e110ae39cc2e6406230380c7bcb835aaee4f2564dee3b0a15fe5bbdb7313da759e41ec33891b3b9e9e2179841322001eda946dc93364f542138adb9901ce29855cfde4ddd891fb2b90c8fc0edf0dbacae4324e8411963d0305448b8474f70bc225410fac69f8d8af609e4f0084330d75a7e0d8c15396ca68a7bea59ffc4b47456c308991101fa1746b92177f2b9124216389afa5b16b1bcf1b5983aa52dddb89a5c4c36f5defdd44415d9cc86d3612ed2a2e278206c5a106449f692b4adf570b007885c4dcf3c269d1804e2ef9b2c06ae086fa5827525fa85d06dd07f2ba5fc08953d2193d2e1832f6db35de9a261f01986592a736b017d1fe790e23e8e5cd04884303923dd19953f7e0f0aa2afac03d88e6c06508d39aa841a9b93ab73fba70bf04997a89dde376a9f5434f2acc0497e9c851707c979088c9d0b5d35991c3264ccc0389823fd90a460064534200b9455dcb81c78a98c0802b0dab9173d47de86f2f01ab99175e83f09b323174d3e7941a04973e4a090ed5764380fed72028451143f31fb8247add12e24883e4a42288a380e938c5412b14248038228442f1cb2351992a0852f4422345b699744d96cf98337531bb8355bd948346ca7a4663a2f46bc243de55dc2bcdd97435c19c7bbdcad0d2ef8ad6480daf84c2ee1d2c467ac7be82e1698869fb09343c3133c8d2ba510231b37723e90869a647d2cb080deb4fe380f15a0074ced7d9f81eb0b2e7fa020e699924600e647ff291e45a332cd407e92d2770f9066c4a6320a452c3d94fe8917f5b59850d22e9df99397fa8c95e383f1a3cdd6b4fa4aa26062aaf6e14c101548ba095ddc759e04ef70b3efe90e32a259d2e47f06a4634408efd9b691f96322144c6bf59328d7016b6219d24060cc2c84b58c898b024d09f888135015dd8cf41b0baa902ba4339f7fa48d0b8335a8ab46e3cb9b98032615d4e605dc29317e6ccee34f9538b245ef646fbfa8057b38160e773c3f20f2a5f99abd120ef97b23f9dc7d237862782b7f7a8a02a66c7a8088345b47c47e3812bf02c01d8d299c373cde6d7a5084ca2220d240706d62a3140815bcc867ed8d9f2beacf72276cbc73d9661061d87013d0015f5b042027f42610ff3ae7296db994c0cc6a3d0689d294b0e907bcf17d541bdd56945d6ee08deee65f2afd660210861a99d7000101cf0ad2b0d0da621fc154cbb0e5536ae7375bd61109e2da4914e29012dfb5dc66738718b7a8baba40d33bc11683400b196360e128bd8c60c1908ee8380028b9bfe7fdbbce7f0dedad4c667362eac31e3f01e423b88bbb5fd5f90d570dfca8eac192bbde69ce6babff0b7166a0af316e551f52c2243b55fa1b77fceb857d601d6fbf7269c2a2dd342fa000b149b2c5726a388fa3a948079cad98f1821f9bfe00d59048f5459a38fea49e39b8baa0810cde4c9636d4e59793dad5f9e720193cae6e3351d6f13806a78e2af0f1dec8301616705604e95fdb0959e3d712666292e264a1ff4bad0fcbfc4e62babbc0b78cce3a0adf91b8b449e506e43e827295df9da124000d75752c26abb56433db255c180099cc0ace77b5e9aba179112ba368e750079397a0903425c11c13e7f53f0d1116817d86472b7d1efca1f8996297698db5f2a76365ae482a1bddd3d145a6d8c399b9111a5ff7313b49ad400740077ad855fc90a7cf48a964b6244987e06d494762261ab1ad96befbd65a141858771ee58e04cc4dfea29360b0f7aa685a88fd7b062947341fe9e331c423def51696fdf2ebbeee150f4389cf074b688402e191fa51524a1d595f4b3c261a94aeff6898ff496d19542843451cf823853db3600a18ec85257725d0d125179c2a56e3e933408946661b425222445a9cee59aac37fc502d3102cd282c7bcda292346531c8d01dd41845a72adf974c52dd15dd50c5bde6980e114ea353b11f237014907f6de1799ff10a50fd63132f84bb8c80cb53542f6f2098e1ba16361ee0d098c609a036ca46124e7dc75778448cf4f1d295483f38494a1978cf81b4098872416f6b37a04a298fef8bedb489816d2d90c1b61ab77a0761613c6265241a38773bcb9adf3962fc714935375ea78b584f22ac60391391f1c44b25cd52a8846f43b4c8e120e758e2fe491c30c532684c04276cd4759dc51a855db2891c032771b80addabb9f61bca48e34320b86d9a129064bce5e4f643145a0a7177222254e55d5472526c825f5d3c6b30b88a6a84809dfdda210f88dcba38f6e1f26e6ed245da3a40c4863dabf226cd5f83991c077cb3b4d6e6370ea2a36beb11b127a474298362a66c47cad2c51531afc4951bc249d8bc3c7b77243f994b7308ea4b80a3f9672bb6b6962f504a192f94ea1343e5804e47d29d49d125af6b3adf59fdc0dc92bb9591c4bf1740a3e6872c1b7deec6d841e163f81dd60d58f3d0ca07a1736246f14a2a5077db0e4df08107624fccbbd3cb45ec7918d2f67e5a85190017d9725b78eb71275d4a14bef80ae5a49ff879f087872984486aed02e31c3d4afcfc474c36d753101eced506ca15c9c4782a1ecce0f308500f21b1edcde226d25472df1a526f4183e8162d5b108405fd1bfa3d2dd7b5a7d97d6c1160d5b872547d352ffe14749d456ccbbf623c0a803e0246eb824beb0cdf93353dd11358f5967ab994e0c3b64baf5e77ae114c1d02ee23c8a94c5b3359e9e7b6d793ca089adc68560b613050e64b47f3af6c173048fc6c6f02bfe7ddbe0589400b9c4d5f7bf7654712e3bfc2c306717c29ded4dff1bb9b86f41b1e2fa188604bfaffc2e68a02c51214e0ff64e082bf5526425b2968abc4ccc458e028da3309443a6130ee0abb8bcf22025a972bd6044e6bee82a41642c5b515d802fd7ac371b71a3c467c01f0cac298cc01f15aa39ef75840a975971653740b827ed9e8deed5a415b30833821cc15b828c331eb55879c60a14527cf3a437a59743cd01818fda115fb3e85139982599a66b9aabb14f64df4c26f0847d49ccd0cc7a5fae4570f92065533f1522a371c4a223905b4dba08a6df443441c874fa30ed1344bf059debd9a9d13fb01d6af90b4efdd4ed63bd50a77623cbe6c8db43b2de9602d3eeb82fb2c339b595a90ce7b48d06077795e8e58a423feff7180eba0e1ac312a266a23533c156142e4f1ed380bc742ae1cc8b319b2a4bc3fa76595f7a3fac0ae02a829ebd4406e968442b3cf28a661fa2eaf3fa9ad924554757ca0950f1fdf9034b097a34b8ec58d15ce19f40b6d28d77e2b028f44fdf150f03b73a84b907984c7512f0681f357d55da3735f2431031866b064230299d8bdf28268b5b1b285cd165aeade9d8d61c2026acb839ed0de8349a1fa60d79d1569a75b59ab0e1e412d759810d3b0aa308cfd39e6d780c81f135dc948dd7afb0054b082705d96019b9dd5fc97a04da6ab2dfa89d3204d88c90f47ba59a14a33f24329f29a3e916c10d1ddd6585953ec868da56344dad9c73e2607e725fed1f44420e10d2cf377c078812b2da31d35d378d6ae2a79123bc884aa3866ad7a4b12ffb095e50b0ff9913ab4faa147203ce878ea86bd7a808acb619b4844cb426e2f2c9b3dbb2045179030fd142d6c3575e3f120e95a12690b3add30d953f5b97cda5f0bf1db70e88e32bdbf99083f3f4cdba0efa6e8e76ede27dd6f5e986e076a0653b1530fba7bdddf9b33ce95450c32e6ae179997888e2add51dd386dc51b24c632ddfc6f8cd3ed3d821800c634dff52de5c6837ee5e4e5b4044531370b1c1b155d641e7bf6cf4dacbc740a6500be09eb79e121e3a5ea7a821345ec59a2e1dc046c85b9315cd99a7ef4da8360b2458991464b09259e0d0d2af5b422a40be37a52577a1d3e85e04e7fc172c8e2f1a106d825bc7c95f471fa16a522c9c63d526452293044200e5fce10c79ee52eeb20012676d7d82cfcf26a5947813cd71801cbac9f7b693c9c83262e5319cb88b0404b2a9f5cd597f3f07d17b4fd5ed000e28464648254cc8fb61580867f83dbbc665dc55a9c80143da2a5777fd2f5bb7c2cbeea0a0108a26d16023bbd9ee7a389fed3bfb66b7b4f229793e79123c9009f1134de9c00f950b5fe89ca71d60bd8e3f04989a4ea40b23456f0634ee5a0d592e4ab149012bff980f14a0618f6d8665da5347e47865db3f82f97c06d9738116b8c782b468c8d70db68471fe7e754243db3a5c3cee927db3cbfbdc58c5b94cd076d714570d472ac80da158c116d4956c95c23d137305197d2633327a4a30f6c40570625bf3d1a51bac5ba662d3ac495de0d92362437ab242b417d8825c9b7c4570423943682f5e1784dbb7437ed8b25de6ed2cda10918bd9f993c649657b93cb0deee1935f1247b5694b24e965f9b2bd5351bb3c19ed57daf71c18aa04d77f289574e5d660620355f9b564a58e54b36608ec03fabe746e5eff9bacf22dee1cea38919cf33c3c7cba8892f2fa6d4742ea866def96e0282507082f9e13d8246194577f8381efd764edec6894a06d97064fc34d4d8703c101bfae502fb2611782c65034c4440304fdec4025d1004cccc8c151c1804b2fe4118b63fb299e2fc275b3a6a6da6474c2856d4e5e277588d6767ed14756dadd2b04f6f787df1c38015ab95d2f0401ccad2ed0b4719ae9d17ce4c15348047c80ba7dc28a870f7f936478e8d275e710a582cdfcca1c432ef24adb8148bc0592bd68b406edf7581c83e10f83ddc72260e515c9de6fd58108bfd9fc418c57c36ddabcaec1a508b2c2316f726b2b7ef062f18d7906c1bf76bb8554caea80b5815f6550c01a582e83f58ee7e8b3e363684c967f6fe5bf114478d39e4d8bfa18c7ad1e81f080a96a3e86f3a5302e7510fa4c6a0bbb969d4181bf13f4cefa2694905330141f7a6d190f2c65c4348e33c5d9ef81287e7e7f28a9558d5f5b973fff25d2b6502c5b4090be6e7ea4ba31dbfc493f48e07db256989803e07a8585d879bb69f875fde2797fdec6aaa00d43291a1ac97da3a1a476dec1678a92e68d8dcc4d274ba3c01219b196817df70e6ce56fb44f1490c253ac794ac11bcba14af96fe2451fd9e75fde183c1afa911f2eae793059da325bd911f34ca98c00d181da28e880ee3d87e255e8567b023d5ce5edafb0bc844e14d27c6f1c1e78b03e3ee2809470b15953f47c8f4c520e96e62467cceabc27dd6e6d7ca8e266f95a04028e3f6f958cd62363a9bdeccab44a64684d8c40a65bc966ce4be9ca29df59610cf2cea95ec47e614bc0a90c5db31360a51f62ad348a3fa1e32ef5c9bb1e56d60a8a4e81ae23f0d452fcd1293f76cb8be89b1876c7d07f6b5f4ba80bd235d291b7a0574df324cb63643ace4313baaf97f0dea55204ac7b2f4c4cff286854abc9030720e41c0e3e8afbe86bd4e84875c139521574126d2f54ef5c39685eab2a44efe93cf28eff81fd6449be9764a001a0c680ad804c7f8ad24aef7d102740180670d8b34c29a732a0ab9529efd21c03bf77c6be687bc66b1dad7e0e7a0dbc9a1c722380a57821cbc77798690b0adf5fe624d8f69704cba41e32009ac2b968b782b427d9cb4830fcb0e2a87835be5a62d5521a6503b2ab180746a086ef585881fc6e07bda87d8253ccc0c0ae860fee088dc4042291c1ebfa18710cdf1417de9bde92247291ce6524496f05aa670c445e19056a92ef2e820f99c017804746a457af5ef249fe5a934db024acfac663a3afe97f505941e6d18cef871bb8b2af57d0620ec10670de7d86ab337cd3c9472523d2f15f95c281961357b935fa163829801513a47f6afe8b2bff370a852342963e988ce30a648f04bcb8e4d04fa8d51ad620d2fa08fc3b9e642d0604669c03821fbe15d05bef82dd01abd62fd23f8d4f7f7afccc09029b75739d675ff69a7141d744398a12d6266b89fd9d0a608f63fce175e899a3cd7a490f267b53cad8917bd3577bb26a80e21670f66598ec99bfad4187172f9db1371593c77464b97bfe2938ca7c8e8a17f9a2d4632b76675e970799e4f2f6bcc001abf4dff767bb90641207b17505c14e2af0ab3d81d8680dc4ce2c8423c29ab798cfdf81a313a309f94d5e62fc065c063a290b9756d63b4fd15bc498a9db091917d0077a46550a223ad3d7042208e1315da8c5aef7bed77df463c4e66127c79e31c23ed2a0b199e52c7144310b12caf0b2b3779587e64f38a229d8eb169b41a9ebcc304a32b662ebf72ad60df1e20f2d4ff350b54635013b582acfd06530034e94cc460c298da7e79ace44ccd7f838dc0c7d00fd0de19405191039f2f4e7bf6cecd3e6e98c29889959174c02808a7474787fc8861b6fe5638ae9e5824f614f66d81e5143ddb7b31538841807af7fd0a008daa970f10cad39b169205f7627ec960e3ec82ff9ee50ed37bbae00b9369ab4bd9a8fb6850e6c9d6f61948da7f77fde435840a89fe2f20e214c8504bdae00ef0a842023c27e0b5c94dcbae87b8b625b1bf854c7925ad791641d4d32145e18f5388f1dcf8df42ceb676f8f602906b86ad9b26561d455726c0ea144b03da64271d9cababb83ab82ef8a293a56a9b99111b195c9104bb18d94ec097ee21f345979d335aa0d58aea3b60a456680985c82a029f41c929e7bf95b35c9792e6dd062ec24a844a980e8eb969c1816afdf3b32054194233f4318eaf9449c582cb51dfcaf78691acc316696a1481d349ba04626599122a3473e5eaab975addb1d98f5f5d4248f79a07aa27614efd80fae2f7e73e1fea48579c41ebc063d740398eabe76d2d5c1bd341c9433bf6326b9082487485b78b7f541e081f8c03c11d907900094a7e034996304281683f6c57212a716a334495323239f8f09c246024025bad88e9be44e01d52a2b8b044a0b8b4a3b6acc1846d8d00e4c83683e58eccdb4a1f83e1cca82452a6b060a3c98b794e4fb0dec6a63878bae529438d07d70246f8a410f5547945d27a3cfcb10d9940c7a09874c208a9252a665655c88c9d1e166a68127fc0a5c7513a5a0ae18e648194b60d3d7cbd535e2ee9c503a22646282d4c21c592a81d51333c703d896d836d8d4b2c07146d4b42da4e33dc40376debfe7dd868a3f3786a1bf0ecc9474d8f19a50825ed2e25d35e1cd9e60fb2e474b028383b9ec9e238c0e7e939933dc89be875e9a9c66802627b06d3968a5b2ab17897d28292a02f6eaeba7b567b3bbfff26314284a3c8607b99ee64098ad7fe04c1c066870488ffee2bbdd5b57f5fe0edb131881080342e4d8350412e77fbe0b943f83a4a03e1398f32a3719ba71c446d73b3b85ef4a903ff9bebae5c1a37a9037950e744aaefffd46015531611f27b3a5e8d4c19f22d619e19c7653bb43015b4a8f74d14e97c6370c09ff3d4630f3d704e5fc0f4d52e01685c0be01ce7f142d96fb95f69600b69ef165a860ac7f9a84efe134286ab28cf0b7e5d776df0f763aed3bcceb14a58fa287119a460d6a095c93bdde197e80fe31c15eaa454b8ae034333249e84c311e2d004c67760ef977fd8373663258994f7514e30fdfe4fa30bd20e9135401864c9bf1848d5fb8ec50af9c84d07e46bd0ddea8bf1561a3665ebd604e5f2b9f8f4438299ae848bdb1733359c89e5d46e4e85b346e352a1471483703adc5d98620ab69b9f22650f47e285d2da148205088c933492519a5230a031c82c5ca186d52c499c4c707704582f4ef8f8a348195b4d8bd3d2e12273de052c8ed026fc8597847a2d17dd000859facd29a1fecb984088f8ff808aa04618a97351bde1c17ad06747cf4399118bd18e625c78707caf1f0ca75bb08ee3df9b4474dd8a70ce1b44b2ebb69ace34c8b488c8453419a20e4a8c1938a4be2d095f17b6c9b8216cae9de7057e384af299c12d1cb36b6e4648b24802d808c84736c22f855adac74733849095200a34879accb9c2b008c14d3671235912cf18feee52e0907a8b08af1ac419de55c12b4370ce7b45706d4373ee1b45ba4641870e976f6aa5623b2c16a7d4952dfd4ce8a00d2243c990ce908b085bb385c32400a5528bc41d0b83dc374476dda09df75d895c192467deace4ba4539eb5d91ba4a459ce34d91b9aed0a343c43885a9b68755a5a654c2462d582b2a7553dc0e168a79eaadd3cac15cdb86495015270b862f7dfe6e29d117ae8c95fb33b44d8912b2e5b58007a65dccc65e6674ca6023e7070830523e54875b615627c7461cbc892da27b92c82066a71a7820664e8747b268548320e537e5a0654dad0318d33739397c98dead6dcd61baca4a42a470305fba071c105ce312bbd1a7b444855b3bd2d4686bc208c0e5d6bc69072dc0f916dc86e00289186b2a5a35be2000dc96fb4ff997d4572c2669ed2da57cfd62cf0c11436e1dc327edbfc94809a685bb68160b76975850cbf7aff65b759b0c09b11189e746347162267c53f3875389ca606ce0e4b2b55443eab9792eac69f04cd5f8a0c76546e79099cca4060b9db260885514fd0728d08fdfc101722a70a0ccdc623b3998ea15200538461b10de79f55a5b68f9b71bfd5b92c922d5f3d3db5701f8958e28019acc87fa52ad02521dae03409ed2316bfcd20e4ee7c3b626c67da40064bfe6c92352646e19b805a0240a48cae94290ca8ee33ba2c68bf209a518c4ce4ab63abf7d328289f973c0f448f0d8609a540a01f56ba1d7974ef424fb90d786075fc26171516bb7b7deacfdd6a8d142c6be38770055b1f50cf5434302312fca14d406f2a0e6d6655425ae23a1ed6f055cda21f58469f8cf8217e94a3bd1f8eb7cafe355684a88e30a6a7f37eb8d34a4c54beac103edd65ad1bf41267ed41bce6892785bf90e4eb8833cc3bd72140fc85fcd2acaa60cc9a63e814957f654d8295820a6a68f65145ed7ef6cab7de3f161a2ab3521f8dc361a7263bddb1554c815b2a9cfcc72a3fda69b42d38a38e630e1465b0defdc3ce87ae1125a47836bac37a8def691da09b950f2d9b5d3d583f5af8f89d690673b47a53e45eec2c0da305cb6907c5aaf4d118a77156baae9b1a3dc8171abf83809d3eaad203b09daf0588832bd2630663835393e3debd7014de939006a82247367f8cb0e4a7ce321f1dd2ecc8132dfa02b505a913909110756ef7fe697b6d63708a2029af73e6e806b63c50ad29117324e462ee40046a04ecfa810c6552eb2a589a91cab43fb004a48264c1af7ecf07d65f472d2e3d699cc6934fb930fdce230210350c632e4a4ee421b253c8f6ab0bca09ab1c883d60ee5d6ea1836ea13014f9405429d710566e22cc04dc980485678af53323020a1eb297a45b15d8daeb9621f530ec7ed824d4f4460f5461ea6f536b05f9283f34818d00043895d33cc5a68f9fac54bd32e3a2ae03edbb1f5766ae1e026bc41ac2971e72507ab7a1ea2124282e7fd507a0346c0267b8c30282d542a8264ec169871c46a40ec8b8f8d8fd25772dc968495aac829ed489a225d0bfd3b5f485336b85b75b9e35e66213a7669936cd90062b10fc41b042ceec3db1878f483fc6062f7d2b464a36b2db8612201c5b54483153135846c7314d397050b44ab34a372726b8c0f31221e4aaee3834f1c1f5a6664a8ed62cae034ae94d88cc001f005f2fdba04738b05aefb9d61adf029bd8758c592c29589ba13f2ab1ec74ad6ba79074b965bcd15bca47e3cebc1e79340109080bbb2c3d8bc08a00540bf9324ec40086d283d8e4677fb0b6668da255d7c9d5622f446c693d0dec176cdd3fb4ae133ca48d3e1c78bfedb39803577b83d108b5730b6989efc34120c22e6f5a84a3ec5079eed23cc8b5ad559fb1c6fa3777b82e58b1f32a2458ba2dcc01fd882f984034bc6e79a1023bdf5e11b0f00b8ce98a07fc2da95a6bbfd827dd16aee2cbd89570c0c829e1dd7631df9a20592cb8c22e4f2e17f6e453313b7e720b2242b36ce8e9bb374bf959980d8b96d73b4a2c9d2393db7b482d9ec0947008f428509f95f8f9e31e81297c6a0f80a0331e57d713d981c48500b83fb01e19fd3004a97dfcacda0bf2b4ab1be2be862e4f711112128f5a87cd0047f066eb83be5d8a32a112c79dac5250f3faa4afbcb344e95e36b27de2e04d910a2fc2a9d489edf2eb128271294cc936d09e155d5282ebdf8b35be5cfb2e62b18f29866a228fb8d645bbaba10a874959ab69d198a5ddf8ac221fc8114474472a62e7ff578edce69daf92e530efec787d6140fcbb2d80f78b9765c74286e32876ffed622b9efbed42a41c22ae886a5eeb1e472b759814528cba196c50e0ab408486ceac4ae3162b951d937c1d473e1909521470d396de48701259c3129da773e1e8f1bdf4319a9c9bf1968fd0986420455a5ad391277dac53ab87f132ada97bafecd7fdd504b279fdb8a8ede98afd9f0838aa66005b6884b5c7024fdd2b7ed636c0b65119777356e065a6ad8046e6090c780e3c150f783f1ab6bb8c553eb8c663542950be71728613e038142392c64fe1c2b19a03bad303e8476206d9841d8b243aaeed9b09770b6c54363328d82ac9240624f3a0c6a3135a6abd215626d88d41ceff2b5ce335e2cc4ce39c8d4f3a4af7e870e1e74a9d03da4c17d2ceab8b1246fa554d230ee8dee04844f8b9f0727b41a0b2aba1cb15776bde4540e56841c5309c32f58eaff86f168b90254af72a9161f3cfdaeea2eeac8bc3376d29ffffe84bffc3d5ebbd97642cfca102c5a1070e2060dce131c9f9d945590059b86c302eeac0d7ed33457bbbcc11b4aa4a5092d4563a1f1830da78e79a70980e63a321d5353e2e7b8d7fe36108422a0b822c217da69aec17e30f3d45c4b07454fb508815f9ff9e130a82564522041159c1cdb2588ad74e5118895712362afa02d94e09dfb6ddd7f278ac2b247905f1006ce6983eba52dcb65d442434126f4dcd7ecf31f93af6183e16e385b180106321c0ac918a1417c3b42bc9118b887cc8c2731938a75f2a4fc9c3035922e6787cfb07ace23c42ea7e7eb4b650fb1ae215a5914a802bd0340fe5177e3308f60350ee6a5c6f3053e1d50ae501ed03344e2d6fb5bc03bfa1c1cf3526aa0929233501104ea51041a2b10fc2643e798a3db2e107cd78086d9c0800b7bae122c13edce542df0fbe1adec428b2a24a4b1025e40e70e7cb3bb9015dbd81bebb060e4ffd2cd1dc025a8e9ff9722cd211cbbd0c7559a737ae14247ac31b81c141a62d2e167ab63908cc4c276d72323f7e0efbcfccca22d5214ab2a2bdb3c31a27811823fa8810e0ea61fa46a028715e8ac02688f4d8c77251704699ebbd44f442b4907373923d634b8588924ec009f62a142a66b8677c027b1b3a96c9ff7a8a027ee31155be41d1b6ee3e42a2e9487a9a808d1a2eafb3d00578e2863b0985b0f99d20eacf77d9aba4b8cbe1f5dcf833818596ea244f7bb0dd6ac00a0bb4f3ecabc6b67c42579af72b9c9f42a4ee5f57d903929e7a95a8106e47ae4fda3b72ddd20816ef2fd1defa0615a7274598bb3ecc5f04e0d126243d899cae4d86242dd27021c0201af7198bf499f7b0af32f56b10b8abc12ff72b7f638f25e9178907e96001e835e9887260314c79ee2478a0d2ac3049dd2738d67d97098e196c49e4684ad6ca18c5e15f681addee95507d1ddf3c1d89c3f64f754f428d35cbf8015294419d02c323f8ba06c9d2a5ea2cfb9b54e7ff9101c46bf3c4ef6743eaf6d596c16140ed7e78b75bb9037d6376b262bc5898e84e6c4b429e53798e0aed13eeabf2ab2ef8790809f0bc70eb221a4b26447a1f3ac742c3bca080bba7a56c8d11031bf934ce46a9c7aae55524c6cf722a4af2c40467051a5ffa1234f9c9d8662c99f49150d5f0da6339fa40c9282306b3be2f0afcf13a4e9a324db560dcffbf09aa2359aac43515c2c477b7daa568818c3254b67b3e55882baa4759e393786ff793f07b9dec7cad8ab6f803bb712087c68b8291f59c783d6f4b73b8c7a117ed726ce4e56013307e385da17676a72510f70696bc086251f6bdbaf0b569f46d569eaa50d8b11d0a0babe56dd2d078641401dc8be4d736ea2b1f537f0b86ef89c50a8972254d2469dfc247685ca88c9dbfa7697edbda8eabe6465edd7b3c54913e81eda0c016296f9c17ec7e56da45f541186fe3c92c844566238a394a2fa1d5eb8201ce459ffecc0ea6ec8817a08001edd5a8839b5de9cfd82adeca2b6608ef7237d8cb583c13a1f3d307f9096e6a993ea99679573a452352efae00f7a5249a03388bf942606359cdec7cae78f7890233a74e4abad99c6d39a2a4065c9651d4eb1db7fb3aebc820e187fe2d57304cb6e0b22a486e78329436e3f566da073d984a818abcd97719d2e39485313464be2ba8a6aaecd31b89fa36d4ac1368d5ef811ea3d53e214e03f0cca34845a03f62d70468a55d00dbbcc48831879504e12c259513669b2d7b638ee1bed9236b2c683c014270817430c0b7e3d3045ce1561c9a88eaa18bc99a5f76923ef15e10106559f9770c99401e7c08338111990f83e17b57faec90980f9cbdc87ea84600d8fd319dd56c807644ac8389209a1dee94e7bad55f3d37f6d7a86edc437aff78c4430b2392350636168b7dc14bd7a9e1532aee43d24a38f44998aa2fd9dfe8adb4a0db20f6b11f543f009c7608a40673b73e617cbad7dc8e36890c1671cbd8df5841e144807698ae2e4d3250e0c13bcbcbb080faddfcbf51ede8017027989a3902def2d8a01c8b112416db9898d4c0ad6160f5b4fd7cc2cd503f9cac58952fbb80d3b6f8affd5ac9a63b9896be5ea08eebc521c02886613294d607121b149886b778d77e7b752e2eb9878a2593e10efd41cb48802ddf6b93efcdb98b49333251b166592ec32d4374121937da5cb7aefbdc003ca48e5017f9a518d2683bb50b5f0e785bbd8052374caf5465175fd04f20bc5f1b974ef6e7006cec34b32cbe9041230b4ca4752dfd9785312e54d3a20496e0a46286880c9d87bb519ec9b1c6c466db91f4e41db0c3ff7e7e0dbcc2724dc465974da68f47b14a19798a20ed025b1d54a3abd018423e20be2dc5dcd2029f05c6897d51688b3ff743dfa6479693518007303e10b8d5337ae9bd016300a45e377896c9da20fb6411b6cde8b06383cff380269dcd28b12bb7323897d229baabc07aa71d4767603a053a55f14f7a26aa253b453de2be2d5d4121f92177bfb398db9f56af9a03ada085e9e3f3e69fd9236b2968dff3c230dc1d03de41bcc64a8635882b89b40d864f0cfdea0239a36f6daa4e8ff0d5566c166162d6f6044e08ce1d0083b1368d6a1a0e1e04ba3e167d916307b924330fc76d38ae507d85187acb3c7390cd5de2d15354ff4552382e798e21e276134e4be8b8238f7971755b937547f48469e38fd490072c241aa459a50d60eff00369a56711a7414e2e3ef8cf070e30087e65d9aac78465113146778979682d15b75d513bba31048532d3de2a64003aad419a616d03bff8162d9a7a0e7f878230966871c6a2f40a0b5eec7e7ab94d74aa971318119b7369bb9ae97b3484b65019871818da62fdc0db5f0b4abb0e48911928f1b4b84899b570a22aaac8e159ca3c972ddc1e8bb6ff7890e6da470f7346ae720b991ed924181dc75c19589635092efda0e2fc00e9bb820a4f7fefab66d2ea497d4708b80c76a4dcf643e16ede6bad3ff47e8dfb1d4fa84a7354fdd148c886a77934bbec47ceebb6950079d3db01e2b9d3fccc2fb143a1eb02b14a15c449bbdc1cd42c419da0a6ec4d4d0171f889eb3ba8151a11f5dfa696d6a6231c4fa0df574e68b2385ac5579bf1520be3f487ec5a7e162becab55b6669706b12173f41c999771e2598542a228c00524e1e408d41ff1a62fb7a91706dd0e8e6e8a2b3df3843631b1548480014f69d2cbfd70bcbd68706a4d7785081624f1619f9a8614f058436b21d6e193f40bd9564b6c454b946aa99f6b94a882eca7a7f4adfbbca085082074ff730c76d666d100ca69e0edfeae389c4c784e766ce59d53d645623980c382545b429f4ca978dd9235ec465538ffab97bf07c4d27957681c5b5104a03280f30513b56713a85cc94ba9afaf1a6f0f7ad95ac9385eedb47ef0a54e8082c22d08caeba46d15a48d17eb43d27fd56b009218ac015d993bbdb7452d1ccef147d5bc3f2d4c626c37868264465dce07b13efbefc06b935cf2d93fe494ecc33194448e850af3c82020802c4970e84d5c9f504e8791f3b50869715c6550769627a753eb294e4642414f9153e9edcb449bf9c5a4e946e1064096e2c2945868a9b02c886e382a8043e6267dc0823caa15c9dd24301a723bda2138db9200d0399e35574d24470458176028fb1d58d917eddde487dcf239cf49f94cbbf90b2b62b18873568561da19d68bec7a4f2653399f83f5e3b9124e33c37f569cb76d4915cf517b6eab2791d725a33712c7bab07842737aec8dd41a4639d324f8cf93abd817d2ade8e6273d00bb848550797f085ddbe9c468b11e47d609fc9a5b8f1a3829ae6294236643b6d9adaf35705b4926d643a6920af281017d726981d753805be9b39692a0149d186c0bdd5102a7d402474245966297c03238f8f7a8c048b2d04e8dbce9f1935a11daa52bd886b7a1de75143d41dd901cc93660f074e97909a9f3a46af68b9ab90340f703a20d73436d6a5710c201ac07ca3202403956f306ecad41f03c2587293898ba64d21eb24a39bfbc7e1435f0d7463b808a3df5c9ff1a4d166dbb43f9ad34eabe8bd8b556eac136821dc206c1d171ddba75cc26ee3805ce8a3fab0d17724d26666a711ab30ea885590466f3fbb93ab6100092dd9352f2a18497fde288c7cf4c27927e175686ed880cb06472a8cc68da0389038528c9720c06dfe7b475e097cf82dbc9e9538b25a566891d5c151b87fe7befd25744315b06aaff748cd0ebe29714e04412ce3e91433afdde6c04cfdd2b1f9e62733effc9414c1e4d1980d07a1e9cd3bc80eb042145afe7f9059c1c9c59aacf421c4d1c2090aab6f48a1490456177210368f8097bc84aaefa3fa6959b01f1a4209f68923a3b0687f298a49586ecb5e12989c4eab6fc2cdfc14e578a438fd096de688461e7bd73e6c432480df97a1dcc78a62170028194108f1153647b0f1a52dfc3818d6531327fd569feb8e491d1d347ba0d769c8bafa7c4aa38eaceb19f55b72243f5c9b83d1cb0ddc81be175aef340f7d1a759ffbcbd71d966fc701312fe5ef570aae3091abd636459fe3b37d6879413dfc50ae8ebfe284229b6905299e1e51e100f78636c45c3ecbda41de5e39f5e2f6e12e6218351f48775afb43e59cddae2aa18d789dce0f06be7024bec828366b913496bf53c1847e2715132081b673684a8e1f5fad30fc39f874e0db3312dd1bc21544ea6e4067b34afb963872a5424f858b89039262381fd63a8d65b6ea3ce8f6ccaa196630ce4b813d3f6d20683bca589725bf1f701eaefa7123ee40e34214cb3c3269fed2bdb929954980777d70462c21d29e7d20217c88de08ccc2853dcd803f0829725dd269e554c4aee35ef119b8a7b7c1b3da657a98f5f7eb753980f13e0291e09f4636ecb3c8fc54ef52ff91c1f693ce9fe1d9a313fc7c5442343bc0e3528798d5a614f07ed0c410da5eca107ec4ac69bad719e7627be88ee5ad6493cd4263f11878a77a8d515805512acad27ecc00673bd32c800d33965366ea3c067fd12cb4c3382c574d6c39e534224c9805cc4187c9c48eb2a7b7831edbfba96f380ec90335288925ddb6f3a7d9976613d93a035aa563953ee60a219b6c606aa5ce2175b62698ae99171f7270d59336df10a8fc7a5a52848ba814698979c7f66edea7b77965dfac89f3c86acd53b5ac6bb84b611b4faf662997ad5f7e406c80aa6d01d4c1fcc96c3c7cc94d9d8a22ab966105a80e548894af762ee51e482430a7f65b1a2ed7f50adca1e022015453ba81578da0044dc02a3094de5f37104f066bd4f9e1d5008230310267360b045b984ab80fbccf9be63451c74088a141ed547b831b67fccfc3f140ef58c057e7b9f7299e9100d13b914092effef4c0c6d0f3b55ebd9c86328ce098d5079e12c0d710550b49ffb8a84648ba941c9a30db1ad754b97e4288e9c4863293835e2d2e8cf4181fd2e386a2ecdf7bd25b2bacdffb724d302f48e923c8c06d391152d038e6193a383825e35727352933865d07516c6e7578405c38db9aec97dd1febc8d0c7ac5298b2d859f0cdd48a5d64a929c22de5bf57a3371a62458679b16e8a963f4fcf5cd087acb9bd8a0971b8db86ac4eaca45339991ead3a4b34eb87ba46207dea42f8d8f16484c3dbb9f01853eb037b7c0d4f7e6d3c9394700f3d632ddf348611a5c2c9a434f84cc209080ba0e8fd34387a656e492247de0b8d55db3f0cf6bc11ac14399d6a4b4d104cd9c50fae5d88d88f19c81fdcccff01d6118504ce0da66f89451d96fa59cfe8094e9c5e90c36839af72fa933155fcbd322019219fb52cdc03f33ccccf7b06bc1d021a332c42d3aed3558d36452a331eda4d1a7d4843ea6fca7fc867263dbd0feefaab5e1c10e71c13b349dbcbbe701f4e3f128d289f3d5487b4cf45d5f46a67ee320eb49a55f1b8d713895a720cf4819ec823968c74c500ac9e642ae1d8f16777fbbc193e225109fd0abdbc84f7d1ce93658a39639f8fec8b8f2e9a546c7dc99d3102d45fec4987fa23b3d72d9a45bf828438abbbcee408f3078e612db639484dc2673f37eb91ac16fb0a6cd6b4047eaeb445fb86af90f6032b2dc6eab8af67129a23c3ca8d58e0f14c896b05026fec3aafc3e053da15974fafc7762bfc04a024222bacef42553e60d76b6719cf19d0bbe734c1efa49369b2cca0f523e10a10ce3058753deaccfe0040512744810be6bec707132f1357c9d21d37871391a5824fc13535b55ef40b73b0476af58f06085f81a04cdc20d841d99f09e1156e4b9184b179e18952c564557fb6ef0f2074335495f2678f03be91418574440ec213bae4ad1ceb06be238a034b8984fb43334d2ea52e4f0077e75f623ede84210a82f795826f9a55153875e92040bc5410150e8dabe2077c54c1c472c6dbca97e61ee004b486038abc4a8a5730903a4a1b2a00e38ecb70492c69cd2567a73ca427ef4873c99d13bd47b3d8e437fe9cd06cfddf58a891bd253e5828c20a0485a22154f47bec61783d96404740d4e59bc5893221f67a2eff81ffbb49e4d2caeeee9d49061f08230981083aad812768a2aba61e26785b68728652eb79fe7652b53a3dd451a2f5d3737579ae54fa8cb5420f406969a4a414cb3a90ad59ed7d38274e296bbd2412d2de8baec12d8c0ed2e476a4c54cee5ca7f4f6e25b7cefc31b74e7d544317eb5e6ac745ad4dafbef06dd39299d94523a2deb819c5ad69c0e42b72e561de1444d4815afeca6b42f27aebb74adcae672e2a6bd178df65ef4dbb59e811c530ae4515a799ddac839a32ff42912b85f5e9e4d4d48d6f7b221dc3cf5dcae0b41f837d0b97a206f3708c75a16bb5ae7acf5a7947356d5a4349a01ab2bdf2aa951afbdc1a93635c78610c20379100ec2f9da6ed0bf7505e5c88bd2512d8b5d9d0669e7e3cebf9db58d1c5639446e404e2ba503e39b9b07f22b078f07f2921a264a078eb91d693123b7cef5032baffb39d788e7d38dc203847ac489c883abf307f63ae9ecc1d71d7b593728c7c6605dfb2fb7232d5e72fe920f02be41385cf0257fc0c157e9ab8bb17ea374759211cd26b7a3226600f2cac38670c9ffe0c2b9413758276dc191573584676e61d2c891adab330dba7920af989597cddf201e57679babcd2802cb0f61c72dd299c7570f90305d9d45e0719d7436554760f9f279aec4e811b89f5e2dff75d279c7bb940f7460172ff1ebc9ca18a5fd887f8306f799f8a75bc47327c53f358175be736a0836510b97be45b05cbf5617bd5ac6f30649103e78fa1f80f001bde8f7bb1edfd9f5a8d65b169661164f798364bdfa8aa3e2cff8f46aebabac5f45b0d54569553dd79cabd5cbd3e5d983965d78f175aecce69cab659cab65dc72d56fababb3a909b16efd7bb3eae775aa64b5f9934de95a35897dfaf6e9d35abd13372b8ceddbf72813c7d48498da8f1df6c1ea66a53d29ad5756faec7e667d569d4ad7aa64531ef65ed408dc8e84e89233d721c7226d8e5cc52057def334b1acbbce8b52ac6bc5de742a3d9c880cfc7576ecd881b5999ddfb1e3b1e02827a1cdb487e23ebe25fb37d114f7e9ceefa7f80dca796cb3b3e3dfe31dd7ce8ececeb33b3bbf43e777ecececfc4967e7edb03a0fe4b0330beef34a08e2ed460dec6fdddfabf77ff7a96fffbef5a5c76f73f338ffa65fb2c47fb2a0f3bee33bfb4db223efc0b20ee41d09384a075b0163c1743592f341a6c7f4d1d54660fcafbfbdda8ba3fc6a2e541c55c5fdf415e7c39b882e81fd1d0bfe4f9a901e4adaf12a0d678de02043c58d1948346fc8e006993a281c669c587345911b539831638e54af318317624ac0860d69a84089f6d7cdc58bf75ebec4da6d91529d730f9f0855c07f3d941dd6697c073684adc1eb44a78cadbdd79e7baf619de6da1859beb34eded0500ff949fcceee26d99123967520cbf70eba1de7cf9d7353c6e728ad281d23438bdf0df2f7cde1899ee649fa9fa243f81e7cee7a5f9d834da5d0f11d57a4d191df7391c7ca3a901f26e588ddcfa77be66484cee7a47366b7ee9b7bee1c7ccfb9e7dcbfea5e7baf35035859072e21daf005b5bfe3f7fb8a4ed9d1b976eeb9e6ee8430b57ef9bd97d1618cd5f75774f6f32823fc9613d687d37d38aa099cd9975d12ef36c0a5f010b8e516df0cb2874819a67c1805828da1dc7ed6ec9e72c1ed4f45e02abbaf12ca68ad6c02f3b9d99795091d11617b1d0cb57fefdcdb779dfc41f78e273a6574eff973cf29a9dc2b79d8abd7bdcba204bf3608a110fcf6ddb0a17c262756a43ac953ec3fbd86cd89cbae3b49c3dcfc5f7f5f141ed6ddbacd179aedd288df6e5a5a0496482ac7de25b1acb35be47de61d4791ec4648688e657d72cd2394ecd10cd2d36c7f9227d83cfbc7f8aed65a12eae255e21ebc49fc7adf1785e9e855e2be273bf670be8e5a548a2588af5967ff8cb43372d4c2c56f11b6dc98c48728442804bba1b097c9f9b852aca0c27aaadd08b4d4c54460bfa552f6ea54ea5e70ba03981c01dcb727024bdbd70894115429dbd001f66bcc22eddf22ed8c224d8b22244c0ccb96647a95c06cc9fbd8a7273b3aaa244f8be55b19af764ce0fe8c66b1395fdd208c05b7dc52fea97a6d733f2e25a57c7883e4b78c104a767575a227977f9ab2fbbd8c9d045a7b2de15fab935a64f97e8b9090e5bf8ba492f1e535d2932726b1e7a657692acd82dff7fc3c31893d6c82a01ec9ea690347dec76740cbf563c498d47f5fa25b30fc2546aa9f5faf8c7e932ad7aff4fb1a69683c010499131a1a4f00b1825cab2a537795cc77b1aa3ec6ea63569f56d7a9c60acacbe657d7e9c53fc1d33cc958559713285956527e6c59ba2cbf7ab7246315963555325fb63ca2369e37dd014efe58ecdb6fcf65f2e349be76282f9ba77aaa4e74be96edb702426880b0d896fbf659f9bded6b5ef6b404ee8fd65e46dcb7df9701428e00591b6394263f71393a1450182154061937a4ac31460b11fb7e1920440122d6a3c81163127f0311e3400854b8c7b98900e506d63e761febadcde2c3b7d6ea98ddb7aed3b57faaf64f95fd13b51674022547cb89cbf1ed15632e51213013fb8fc9fcecbd6c37b35064e835a925e08fa04a2581d97abf0e0022e5be5d15789041e30920c8a0f1c49594a359b07d4783c0b6db19d985cf9789fdd7acb76ffdbb4116bde0756a0aadcb899b50b2f5f4ca5efdaceab70faf53a57faa4e91fea929b42e2750f2fdcef05e7bb193b56f412c5b922fd6f1ab2b8b4f6367a1aae30e6a55bffaaa8ef962283f6a2f272ed71a83aa7a65f7ebbb2bebd30572ed06d98b6550f2cdfcb3669d1c2b526557e52a77fb51659733908b1d679df5e14d22bf6259e95f6b3facaf5709cb3ad78736abffbe65b5487d7f5b3199d82989fcf712cb6e4334ea05c34d1b5b4a45dca0f16be214971adcbec90ce545e40c0cd120850a2d3fc8008ed4096d94a102cd0eb8b8b245aadfb4059603e4fed63ffb4d6a026fce7f179322dd403211e4fed640b210b8d0da8f16ca9dd13cff6199cdb43d2c7b5da2ff7a887dfab187e0a73f9b90fbf469136277c0ef87c60c65c928dc7cfd1c47b90db8edd7173af7e3bfa93737160b5ada1a483c723b1aaa92fd35f024cd951babde5aeb1bf15cf1d0d0508fa3fc7d863a595fbf657b9d2c7b83edcd8d5503e9065ba4ec5f425b6badbb415ddfa962453c570dd4404b7dbde64aee2ddadfbefde93e366fdfdaef51edc55307b69ff5c88ff1a050fa9bb7ae22167e6b83a393f34fdc27435dd851271dac88671d9c86c609fe2bb0e0d9bf89fb71297f9d1c1c94983ac864ff12829cc9be050e0e0e8ebd5ab6ac1b7483ddd85c23a06ee80db2f9be48f0df3cce75b2beb3f536d7c9fa96adcd0827eb0890a350a0d749af1901080d7e9f35236b7d3d8201e11bea3b716bd6c8e769af9d8572d418fcee35db0ffaf6a2ed07fdbe5e03a15fafd840e803397acddfb9aa2b6b62dd4e0e383f201d7afc7bb96fbf240a58b3664d96123b21e9889d2086ca3d8ef211c82c5101e8488eef88a98d2783551ce57e7a7c2299815d1ce51af402b96871946b46b00ad402abb80ff750cb359d8133a8256b81507817ff8160fc4b17ce7d4e1b38834445f2335825bb8744708affcc773edcdbe61392fca2fbe8dc73c2092e39b3f03d90fbe87f5f9a1010a0d1157fda78e02b4dc890ec2d60879da98cccbc57654a13816962c2002337316124e56c669a63ce6c6e39e07a6a24a433f5eaa4aff5ad4fa1764da1fead16a9d610e0762465281b20b7a328642a2333cd51ddb1db8bb4f02669021f3e26f1ab8f608081afbbcc0d187e15af26e33ef25d3e99a3315670fcce5fbacb91fbbca2f6c3ff75ee215967c86489a31a48632c42197ed645d9b38622fb6b20fd108b0da41f8ca3badba8911c15af3652836315df86b417d9939cf5c3874d6c8e2fbfc2aba856ac8fdc877fec233060da28f611197f81881a7cd1451d40ac2183942be181053d88910227a2c4e1ff1a698ac1ae91bc240491843f616355baa8622a94e91607153ab23491a3528716276aa22954cae430755ce92cdddda63370d6cabcee328dd4ceb41742e0ac91607e4d4e45f5e3975c19a9e07e2af61ca3a19eb3663afc64fc39a594cfb9e7e0191d3328b1f6d595f43f11aac0fd307663b6bb184146ce198a851b4d78c3d113c484cc44c70fd96d8e55b3468d0d9aa8ad97c02dcc1cfe314dc54ce0376693f8830066d7ce1fc90d18e7aac79473f66c29a594b08272329130cb77103ec532f9136bd265f93213f99a90875836ababdd8821392139653883dc033ae99c73ce39e79c734e42e71c8410bef6e2bf07b3cb4290ddcb52c60be677a65693fb9c90787cf7774c280be59cf7977394c998497cc7b0b5b97dcbef9b04041976fed77ec08662f230530a922a74103e141523295ac2c8a1c555453b177b640f8286a7c7841d4cc98ee4be603993337b14840c392b618ea39c35a376d4c844f8ec1c55b23f3ec292648786725f246f08860c93642168f728f430b52436eaab1e7ffbf6cf83b2b966f3411f671ac057bbcf0379ce3c3f37cfa5ab1bca3e0c0112d8dc39cbc280bcf3bdac867a7f838198ddbc567e2ee4d89c5bb6fd782edffc3690fdfbd927d95f7b1ec8eefe39b7fc49d1c394f39ecf7b5660414692b412c6b332c66d2de80962821c27b0d1091f5e0d3a20b92061a6d042892837d01183377ab860811a628ce040b3050bb64282cba1f00252229a70140b167c81565640690b98c8fef4881450e7013ad20038c4a821c50da429637819daa108267ed862469530de1802f027be7856a240376515558ce55da5099a4515300f69f200113972c2940308398490438a1c3098902a1a4638b4c997d90878818221d8f842055aba2071c2863238209326071b7a00a37a09e0084017b974d44cb23701bff724feef9fc02cb3e0d7fdda68078220412881c3141a7481a20b224e1841c514560891022e9cc8220730f470a5484c94a12f456544a1c11050dc80081fb0781807f8a19d310188057001c68813d8a0288c1a04d0450c0c4148e1a589304d94a121cc1c64ba4cc14206266ed00217dce0431964a8400a2c252cd230430757c690410928d6a0a189217e18420e2a9640a284124c54b16285044d78119a02290d17bca087a234c8c0d2821a22cc7459c20d1b841102325c80e0620626e2a041090114c1c6163278a812071612b43a581003196b90214496218050001c4434b8c2883273ac6961073f9880c88a15615891c40e0d5ad810c3063557c4d1e58914314eb08689a228b2b8c253658b2f886810431b533c38030d185829fa620d294548a298004b0d825001114448e8d0c50d42cc210314a4710510425176b842892e49d8502606a9ccc4041962a6709143982f8e109252861c626a488119613817212a4f1411071a6ea4c006381c85513e396d90a946182ee8b042ca1a62bc51040c53743863088c1631a4a2113c39d89a7a4a086242379c260b17a409f3b98549b32557b98549732588ee7fcf9f732ee7397c0f3e57458e435c6ace473f2781bb3bccdd5b77eedc79bbbbf3302b38663261fcdeb1a3a083cf5d0a7783858e6d036908dd6472cedde53cdc1ed63bf909ccce5d09e6731e2fc0ee4470e2e98e6736dfdb4eb94769ddfabd81339b5f7c0f0b93f3d13fe59482b37723a5ad730ef5a6e3b10f4f491a848ce5d63ec80f0535b7263827c0fedd13c2f7263919c26e32fb8d18216af604c1321bb1162308d0e44725b7c761809c73ae09a4e29a8dd7ddee9b49f73039ce71ec286f3ed651fdeee898ce39f7dc73cfd9cb39f79eabc1cb1572fadf430825665fe75c4f606e9be3a8175d78f909ccddff6200616b0f2184d039079d73cfbdd0c194dcc2c4e12503c92d0c1c6af250e601225242901fc1a970a5e0a8146e72d012709ffe2529f84f0fa57ca8a704ffe954ff3ca17970772725e1232fca05f7dfaf6d6addd7aaa89cb34bb364539a25ec7e8fbf09e1039c07e1db0311711ffbd7b73fc17d6cfa4bbb68775515d5ea4de7ceaba954a1822a7d1176ddc7223c9017f11401e0ebfcea921fc25584fdbc8a44787b1501e0ff2ae2f90f44f8eb2a22fa14001e041ee76a73ee5636822c67be32c8dcca683fe26d3f6800c07526e67673fe5e3930609e8b837552c5052e490109c25ca374414c0892e30dba474e18e310888891086d860dca18214ba552690516924a2f964aa5126a04149ca552a9a48204ca504a4ba52f957a4ae8c1ae36c37dc49737e806db72032564372108eaa7532f7e6dd7e5b9b3c378d0c0eda3c34c18fe1c3fc339a6da9a10179a517ec1b39356939b10175e9819fe93983d47f79d631205648839690ca8a14c0369f9a9516d6270ceb9e75e7369936c4d4adc9abc4eb6d22965bbf29e90c4f653febca46c784a68a4d19133e7a42f29a5f84ff8da1a2b6acd9f5509cb5a525a076eb96d81a54b36390a6247c1973c92f0b0defacbfae93e9635315a84db476abfb5c54c601aa51fab8f71c6fae857dfaed3b595c6d9e49c5fcdaf2a4cb606312b097f5845e4ba32f3deeb7eddddefb9a34cf67ae7c32180ab2cefbb31c258d129e17bfd9aa3a4956f732c4e42e5a340e75502fff5e42c47e2d0139ff59f4e4d0bbefa1e9d12beaaaa5505e3c36f179fa353c6086184efc465f80d3ef70060651da8dc5ac2ba56cd807f06f7a6b5284e2babcbbd62329fa2309f7e8b52da79e9cb8b7e9552ce5ae9b42c5ba9bcacaff3f2dc37e879b4780ed4e4fe9b351ce4eae775badf726d55d68ff33bc7d994d479addcc2e8a08a3c2fcfb7c6921651bc91a7b372d3228a33ac3cdb06cb39bf5617c5b8546bb576d65a71ecbc3ccf39bfe64ccf3657f62da5b4ac652b087f54d89c560a41e4b64f2fc06f6794e99c6bcfe1a1d85ae96b7b4e5eb4a8962e1e8c7d298c0ed0e4362d7b6d452dcbb22495f206952ef97e4b2f4b977c7cb55cc24e5ad6b2f82bb4f86aa58bd28131bda81b84b1e71902cb5bb261d2105d6b2f7e9d3a2d7b2f7eb9854caef22b8672542ce2d9fafeecad8bbf4efbd5b2158e652d7bf3f46f706eacb5f75a6b6faccdc54f6f90acf3dee0cce692fdcaded852a96459cb969e7e0907a7f4a552a9c23221932d6501933320dd1eacd2b296ed06e4b4c7031c62c0598e8e4308a1bb4ef13929a17b4ed68f5f3f5ae9baad93b0eaef5cdd243b7263b556d6dbba734e4a27a58c5fddd7ae3a5c21f26f3c6ecaee4b30fd604676dfc33d51eb79418e707e27c899cd540d46bd1fbae4f7de1818a3c9fe396e8176d2829fbb46026edfae961d19b59faaa1e2c807a429bc8d2fd945d01e6cb0cdd15c6bcd4c8b51ca28e36bad7dc3a6a31ed6d8683fddc4f3d649b933116832440189c31c667294138194db9b5b2b9857ba6421b97e7dffaeeac4f3bcd6b610faafbe6bed87c5b2fbee9dc8fecba4b1173a4f249ee757f38772561f2a1172b1ac7a60f2fbea7d563f5bfbd155a5d89195314756cae4f7f6ebfdeaaa7f2f244272f5b362181092cd7fb7c8b4b3ed67f7ef9369af6cbeabf592df9a10264d84e40ac380905c7d85b950fd933967fb41e7f7673f1bd6515b6e8fbe6bb61f677266e7503e12a24c6e6f56368c3123ca18338672ce371db8fbd776bb6fb8d21ac2c6e362c6cc008aa320c4f05469427e3f216821b24402eef3f2abe2e53d13f26b5e5c396b66e4f7fcbd8cd0f4ef7bdccfcb0226f5bec77330a6fcbca5e01b7919c61b70bb29bc2c605e1630a97653f032495118a5de63f7e3de9160ca39a59b02efc82d8c1b3ef41a6ec0e090da48c3d5e09cdcc2b4f1450bda30eaa932f4723bea418a17a6c2e7d3dc796bfdbad4e63eeeeeeeeed4fa05a7a44fc6ca45d8aab5175a9950ec6624b603e3450f5d88f4a0450d9c3c195b47e120872b6450497119c303520ea923ae10e161cccf2bc3dc8e781022a7817a473cd8903b375283e1021e1dc47054bc64b7830cd9ede005ca479ec98fccf41ea33aa8b536cd910e4970e1851496bbbb37520e6164f7745e529c28e20533480aa38c2a398c11c51a5dcc88218d944b51831a93708642b99f888396547f4f10f7e3a9fe23eea7a9c04806548652fd2ab89fd8bfc2d0502a872ad9fd733f8fca7cb1646c1b4c22566a70438d0f65905144cf0cc0205a028e3526300223e58ffa2326c8fe41b490818e1c93fb414113eaa5dadf902a867c848315d9fd731f2079da6004181cd59004c6910d52dc70c30d654c9122fbd19439b27fd700a3463023861659093660b2e3398e680023fbbf13d91f754414c7510d5c64cc4375df5ffd72004c3dd7dc31ccbd64e4a8ea8bf39104cee015f7f31a00e5c0de5f41e13ecde6acfa922b23f7d38408314968dce8c10d3552fdd518f7430030a6a8a28e2dd6b8f2a5025342321327b63fe04c7ac76fd3bdfbb42882d7627cb089cd45f2a7fb911899ef09f228d984f4dfa0be00cfa48e02453372dee3c839e77a785081c0cd1dc4f24c365dd651f40b2d3290f9377858ea4ef0df7fc64ce01a04ce2c4dc29d1b6e3be7b4eded1b696f5f28d32b7b50ec557dbbeeeb2594734eb9e83e763f17e3c94beec31808311143982d44626ed0831509a082a8872f5c70c50f6ca4fc818edc0bc8f9f07f579aeff10fe2ffa8ea95993a73729381063ae7734a021f06221f8f22c3af5535e7334dbce4e5a29b2529e01b0ce57c40f7f083c0962a7d16242f71377456744e4a8696595118c634a594dadc5c787ac1f31de7ebd757d959595bad20c9ce4a4a1a5855b1b2b3b2b6f2c2021e4752bcd859555955558542f5a7222f5b6ff38544fde5b4e4e6abefc9b005f8622b38aaba9194c2cdd5d5d5472e201ac1cc1354f4d0c5161c709182ef01f773a40a3568145106143f4c49c15f017e0bf05b6b37859bc718949315299b2f61505a0b36566094b229c27909b8cffcfa958ba419be445b1999a9aaea815c75595ab1e028f845d83ece85d7a8c1584f711f9028c93eac0f2de602ccb5624d4e15c59a380afe0aee537dacb0262a6159e23ef05380dfee03dcdb7cbb0f78a5b7c1a0e850a102a354097f93011a18d2507cd97ce9baf9eac4d0220395d766928228a56e43ff34abef243a27fd06e33efed3c6a0d254aa402dfe03bfbbb4177803bf8dfcc753f06745e1b3008f62e892e137a194d20a3bc151f0e6aaf68a30386b22d7cd2dec88a3aa07bcc7dff7012e059bc718145b45cae6fba670f3250c0a02b038324adddc5ca5b7b9f0bf1b54b9471fc88f5447dc875618bd5e4be1aba5b29664c4bf912a07e18d1c6c9045c82d0c0e6234951e3d1c9c3b5b3093fe471f634267b6beb985654d9e64fa1d03391f85c894ca79a299caf92851669829194a6986864c2919a49aa1a154ce4799019592c1d53194caf91982a47230fa56707ff52df31021860b889698b24317299c2933d02229082cd0384a097189d282285bc8a8a101d2fc01055de4308329c2a821d5dfaab7425fb2fd1aed37c8c4ff848466f74d647ec165d76b7236bf7239728c5846b1229cf75b44f4291cec84e4e5f833bf1dbc91fb2795dc62764094bb5fb84026943120737ca1ec1789952336343434e43ea2f0426789359139622f38c7fe06099b264a982b3618734588d78310af872ca2142992c55043a15e9a9600d3ef68ed45f36acdfa7b112102a413a79db6738d9eab94ae3d084d3d4c73da4ade2013da8ff8d5d56abdce5aaf5ab1208e8a2f5b3bab88c7f3c0809c8fe83bae13dc477c9317397e250567cd8888b7856531a109694d8d19b965592b90d72fdaa91745623acb5ad6ad5da246354be48aadbc727e5ea5dfb9a22c817564bb453b0fe477da10b9365fa9ddb93a957315edbce922fa94e9775c5166f8940923fad40e76c4f9007254b474ae238e2a61408e8a44b250dc03d699b40eec72db22065772b5c96d8b18d090e5571c355af2bb463a17ed7cdf1d64f9eee6b685183768776fd0ca36902b76c451f116f17cf3143502aae658d6b23937d65acbb2d6eac84a776a4bedb85aaab473b5d4cd95d95c92f36a594a296b0d52b2f24cce9a0c92b063661031e52062cab14517470d28d2c514cd47cbadf99865e63739ebd7f61be4c4524ae9fdaafa3adbace84b1b249f4aec9f75f7d137d2b2b4d65a17acbf7fbf5e9dedfd79d9971745c2a63d4a87556fcbd65ad7d64aa36583ac9d6dadb5a61e95b5bafaea16514394e1b5d85402f3dc224678e4fad5c65ef9d6beb5d6455dde243c72ade69cb3d1a6b47b565789bbd662f4089ced7c39796ab5fd39eed372e91ae17193f0c8137336d58d8eadaea6947edf206a59cb525cedecf0e86167962e3c8870cf3908e9103303924f45e0b6059a2ad965ec28d8e81c380362dfdfd484bc6ecf5945f5b1f3e152159bb1479e8028674d06cf3dd9c351a61e28d4bfc98471939e2c3fa7479403cbcf724c580f47b9c9019eb29db2150f38b333e5b48c34379cb1218832390b21fb3fe71ca6453a35e0ce5d5d334927a57bdd9d45f75ebff817c8e36c513a29657c192356b55cc10a9cf577407e33853bef24b8c0419068e85dbbc3de171ae0e69e73a20ce4400e87e038012e65422ac0fdad933cef175abbd0b9b11b2970fb4ecade42df7f058500c2073d4e1ef030edd8d1c9c1b9b1c125ebda5ad129db0fd87eb41fed47fb51e41ec8bd612368217727c35dbbf7c7c4752f31a543f598b0c32617dfc70bf6c32be87dc5ee674e0ca138cabf871cd543ce87abd0038bee033c75310f381f5254703f20681ffe3164b7217b99326b3ee0812529c02c396b22a696ea3a63d41ed23edfb30f762cf759cccd73cb5e76d97b88e7f6f11daa87c4770f361ffd1d82ec87976b401acbe4fb748872949b588f7c99679c583bc7b09d5d022a9b90dd9b80721f450f5d0390430620bf4a2f5a9207905a480365855ed23e3d8df2f7d6a8cc0024e401f410200d480b9a84ec190905c8fed908d949683fbc85cf48c8ce424b7243778dda82e0f63ebebef1f57bb7d8317647dbaee4ac94fd16e191638c58d681ec988d1adb5ad73a9290bbc4f98843acfaad7a25eb56dd5d7d9d734e5a5d9dbdf4f40a9a5f5d46da12f7a3013473ce8b2241dd96aceb60ac2c55d2b9bfa0b991550d98041cdfa1709fcca7e4f83173281c4c7cefe23f9d8aef59e2bb16f799d23e9d43a3e2471c5aeb70359546c56f34f84df8f157acda013b97f72cae4507dc9f79961c5fde68c0fdf497d6bea0695fd0c4b7e96ae597aec616535a67beb1d54b39e7aca0503bff549a7fb2e69feeacb24aa96323eb8cb24e1eaece2a9f012cd7742e0f1ed5af112d7d8b781b54d8b021571d9b9f53522a6945652dcdcbe5be46da1c594aecf25c4bf54b8f7375ae743ee773dc4707cb84b2cef75b37482706d0429cafcf96aefa555ae99a9b3de7ece67125fa3a574ece654b41a5b6b1ed866eaec4a816dc5f732cae6a9df46bec1c2b78be7ccb446239f809bcc3ca1b247bde20b9632b7cb0e7bbac4ce808195fecd8317603c0cab3ba7596a4cdf73512a4a64c43b5dc551c15bf34858afd7763df96ac6bf3f46d6e6c4a255a6dec955925f4abaf6e5075b14ca85a7b4b3f2f23eda2bf44caef5b5f76e79293868aa5abb306dce73e1af7a409a11f23ee1c1a48fc7bc5a73372c92d370eed071a2b397e5c22c7af58663a22c342e488a6a7b41f15159cf59729dd84742e599f5debdad89a654ccefc0a6784bb87cb67af9ff99009a794afaa0193803354f6f12aae5c91400fe9f79f4085e54ba6efd3fda0a6cb454218838ff410f82e7b76b86bf6e51aaa3520f45b0392f53f6cc9090ddc980b9fdd1899db35424276d2d6d49161d9fd74c149afc92ebc1033fc27333b5140a658db668050e680293f3165779178ae50b11d0dc99053c0b90136238123272c79a327622847553a60f719ca0607cf7ddebb289757553a3bc2ea630fa19fb52c723bca1a16b9154066970d8a766e2d381f6326122a484f0f0af526a2ec05f57bef5d123a84b05f5ef05b703f3d2736e5337dd4439c4b03f10fd2d38342b96ea23e43e47df4be691176b9f619f7e3a84c79c6f48f42f5f404c9990be292683f6ec2024fe18409e3e7a8b81d1a10ff79b91e1acadfb9f41027d340fc87ecef649c4b132622b27f8f294243a5fdc8c151d1713867c2cb557194bfe74a259c16cae7f9aa0a722fed87ff1816bb0ffc7983a0a9878c0fa59498e3a17df86329e3b6789223c199b692fdfd0b104ea687381e1a88bf1363927045b4005e74cd0eed873b2a38e0ccbb64f7772f9694ee1db6318c0ebce4cc7191fd9d8d4397dfd965c36f42babd43c2ea38f2814b6e473e54c95990232b67f2d1152fc5789d82c0ed06e5a000bf3682bb6b703bbaf246ce82e4173a0ba01ca8f4ccf7543d0a99b2190800000013154000200c0a864402a15030a2cca3ef14000f8b9c466c3e19890321887118841031c61002002000c00080999999b100eb93099ad8df342822813b146085d03e541cf43e0efaaf4e432f6976d233e937da0dfe79a724b0e69c7d7ae8089c52deb7e9a8040a8f8a7b039e91a6f76858a0d4949f9e87a53060722559e244d1d5466cbf21396de51286f75a1fb65dd8807274180b8bc832a387fbc6f4f8d024db5fbd71306dd8ef7cf11cbad1e982a2e35e9274ecaf8483eea33a7d020c65d37debe41d0fe5650b435fa5e1e080bc10c8118a326e9a86f45c646e674f507adf7baede0cd8120efda7eefb4feaf7b96e82ac2fc7dcaababc57bdb8be6a8ea99bf87bbda815466855d15c8891dbf7b38809d5438ee7fb0ba468a52f22181c9de9ffc056f7b081ed8455feccf4e867d5b4ea3ea8f787dc03c79ffb02e69ca96ab0b259cd8568fd96c72102664e4bb7b58fe40c1e059912608c5fa257717bbf58bc08d3a2bd4d3da77dfa5cc4b1f904dd1be361bde9154e94054f4eba5b02a95a16778a3dce899ae6a0c4f4e2a99f92a13b509d50a291cf23dcff6c9a61941354ae711eb72847a6dca1205f5840da39b111f8aacaa4bf1ee8ed0005d05c10665f660fb589c9e738d08b7747562b910cbf32acec895290a213a4284aa4750536a14805b8f487e5d3e1fb49ee74027fe8794cf4d135ba67412d1093ea2c2e7d972a2da011aab951f96792aad42b5162009a8c9b9d00d298d1b60f93284149d67620b547fe765de42ea83b40352ad362eba48a68b3db38191c25b4f595be3a9b13b15b0e3b0398bacd76a8c32e77292896f00011fc5c3695b18fd000b5f95d63de94b44632b6b7fe5f25fc9425e1275d68870172a2b7cd6ce3ef75b1dec8470c9ac31afbf526cfb1defe354f2b6594fc9ffc4cacd4660ee82183a1fb68f37ec351dc8c432c38d60b50531207781c9d6bb079e038318b45defba793e2d5a6f396bd78b7a6e32066e1a6573cc1573f1614fe72b19d0cd2f9a59399ec9143a7d7c1673da643d8a595ae012c2263f99579473d4d2757e78035ce21cee61c97d18b32e934552b90cbc68a3ca3e82cf169cb76a517370c9f5c0a15c9958b9ad98228533abbf7ebd4c30c38e78f76093995407d5df701adcbb1c38c7c7acca25fee2add9f1f9d06f38daea9df37ad11965e14a32cd2ac308da671974d6fc66ee3adb145e02a020a413ae95dc445a8da1c04821d3878ce1837c6ff8617db600056159d8b0081d6678ce9f5c15f5a3c404082bd6ea5a9df3e244987811109a0bf7dfe103380d6343429a8209ac0e1bfa45fd7f29c8f87790e8df8acc4046cdb0e038fd664c823e427da8dea1a826e12bc66ab3743fa6a87f3ba3a6143dd403f4cabb10fde2ee714e0d0506d350a12dd1e55d5aee0b0eb74e129d0153c8c42b76c8a0bac9964e3c9db319e50474d05104259d60bb829cfca89e8eeaedb05ef422035b277d82dcc6e938e294dc4646938a06e01c96aba04e8076324eae1990c3802a2283b33e12b160ede27948fae0642a05c138be68896d92066786301d46941c31d8e6479eb53c5f5d88c171c93d90e25d82998fe3e33c8a3b6938b3b1f089fb0ddebfe9a1cdfb1d6f19e7d821dd0161942546ac237ed1b809775fbbeb2458d0fd8c8e1806f43e5ba254ea460cbcde85ee0611ee0be4869651f24625e98813aa565770640ca869fb5db1e62d52890a7c62014b4616548e1a5fc8f55a31a58d4de4df6799e26b45ecbf3a0a1bdf341bd3fb3ebbe7ba73e4b75314f6925126931eb433d36a4266c7e26c1427243e3a180c91d0217b1a4efcffb480ce6c12568162202fafd66827bb0a93afd116741a9bbd43b78d69ff6a52d5d56e871690fefc0d8941dd91f6bbedf509895870325ed82500ac898c962c3fcfa1beedf44b281c39c4e5a6e977b147e729ecefbad418a9b00c1e9f13d1e297c6b4efb4543f85d792762dd2f66007634eea5fa650d512062de9971dfdf13dd25d6afea87a58945ed893ed02b20e98b4ffecb0a0e0de4c8e9d5342989e95c4b1563c29eba23a97e725773328cae224881b35ad4553f9ae7607b909d5e2b85e60a2b16d4e1c997b69d9b0529ed6bcde50aba2b98663919d3da8d3ec2c24359e4ae71c69842281fbd99073f9ec45871b222c0d1a410aad9d6866d92438c4e31b3b2fef6508c3a4e8ca16c06cd75966ac22fa1c0c3472ee143fee5fab70f472e3d328dc5cb117a1def71253770e1bff9436837939bfd0ae41d681254042b8ca949d062629e98d879cdfdce1452bd62538f4815f3b344a0132b9e1d3666d55f14fb12b077929b0781715bdbdcce9aa82e406efd36f08ced24f63cd40df5a087dd996a02ce00845c2c89f571601bf31496f93ee6b5c2139a6e32a63377b8a19b8f2d0c78a9990bc4c812f9b4cdeebe198632b3eb0f6fb0f62b82a7ccbda4161c3b23e98c710cac99fe00a41d4305bc8437bc8514af12694881ec27deea6559cfa3e029d45ea829d1574067c75abb80ac13cdaee3d35d23a880d3abcfd3aeb402cf244095067304c0ec5f90e9ee32ccbcfd0c0e30fef1a0906402ba0b23f1059345b4ed73446fd0e104c69478c614224c25cb4927ec2bade28fd44c11f633488aed934e5e0971c97497163d075bca181c5b9c0557933238da6e46731b0a2442edf635fc57ca27218d73c8882de0d15b7c0f266da6eeb2f2b250c5235ccd5f60754d26c2883abd99d44b2e78b59afdfb7e54c37a7fb8093d0df0b8aa3a85e058ffab6154dc1d10f15c7c67e6c6de3df32ab848ece303fa6d9d779cec4097ffe35a55fa14d1b9e2565fc53bc6eaac5433b4b8c49c7b9f292da84f69e37ab4165a1f0ee2d625add0eef38f025709a9873e2d32e845bcdf077c51dc4607676175017d45642ed9bb24a053479a7d6204e3f0e16055c1dbfe80e93aa377f3f37c84efa1ab1f47be2e3db69712a0e036e6956f22a9c3e8df50d73199e103f7d77c59a7640c8067905c090da3dc6b949eb7e3bd42755c7bf49caaec59907086136b4afcb620c436d6d433b87d19c7b5ac981ab159ed48f3f6256788a77ce0cf80f0b108d19d396cc74bb1aa4996d6121306996556d814c086e1f112498f47bc2dc8623c4ad9d15accf3383566f42b801d2bc736398f1fcde7bacf13aacc29c34a0a696ed2256363f07641e0b50387dbe95399665f90548c539a63f60514718da1e971bed2f512705d33eb572a609f9bc4aa71e15d9e30158738ed2882abcf2cf032a023d6454bd5186fcc36e341b48f45350fb549a0a14abc05df11afeea4ff82988c272e504a68215773658c3db126459ece0d5be6acbd771f6f59deb4172e2eb21d3f488f4feee303493210cfdc909e1f5f29326783a205f763632ae46fc9fecc7f25bf068eaa0f13773bbb3e232411cb52e43e4810300c34d70ee4762c4d2458d1fc34455b1679c8ef0413ace23a55ec80083e3da3e2122bf1bc8a1b9525355e4a8ca1e5e06d86bf215d8ae2907c6a0226c744c4834be9a4a35d40496648e08fcb1648f0dc7ad9ce1d6e95ec0fd9dcf7fcbb33529a91c0a96b1b009e54e1e271982667bb24b2a8c616484fbb6e61075a183e50e030f62ead5b4b7b9d651826c7ec7f25c5a44615cd5149c10ce2d823c567fd2d85194a57cab2d5138394c29d402fbe835ae3761e53e03ce2d0e18d290d43b3134ab968f54703b6cb439aa488e0c28380805c78cb95c30c5a5c2ee34ac13d5eb28d9342d351a4c210af177952db8d88e271805c882e91fe7af9596ebc7daa9313f284e383561f84a778e852832ca5c4303ed4ebc2a505b067e5c54f9a1a28431a96beb08d6887df17c6821df6f79fd4ddd7db2fd482a768489af1c2081d4498e6c9dce92e60536806e04d6851511801c0f1534ff5d1d9ac0974904dcc06da927ffcdfa7aa4c4dbc376f8560d2b1b36565a8a2f7512f9e397e38015f4325acfe06df0a96f80d7f43e40a75ec2776333ba2329282c9d1a43ec5df2b1140c54f78a739bd360850b5181f99003af269bc82db110bb3cd5721351d50d3400c108f010cb4ba8e1750d2f51365b25a181aa3094db846d7c6828924c43fc9936df476f033170870bf5cee062fb12e60f0db1af42eb1734aef2736331113ca70b23118d6ed95d8955b02a686533d0664272b6ed48eea4893f4b69902e03f2995442512837d8fcb10885285e052470a591b930b4b3b01950f110127c49ba4f11e51312e20c6a90d5c09bc66ae79581f3ad92832dbdd9225c0b161333b2a94f125f0542bc088f24d24c66ad6c4e4f55042ac6d383226861b834c32298becb4d70bea485ee4bcdaf3aa29621924f91fddf17b20b28fc827a223cba9be411e673ec408d9ae21d1afe12e0282376ab32adad2edb0e2f7c84601855cd9b6172a25aabf0125e889fd21b39ea37d423d6e3a8706336ca3d6c32e00baceafff5a6408d884bad10f31372e5cce5525006c4a0a381de44c51948f6c37e1ebbd2f2de73e2ce091ea28ded571d1bc98f3bbf69ca4feab89d46724407a441e4a92b5d6cb8a4f26b5c3ae4b985a6f1e2c42863bdd47ef642a2ccaeefb5cddd14be16079b580b5d1919e1176ba40b3fc188873542bda7e6d4c0935f56cd46c5943360a7785e6e5006057e4e6282ebdff2fda73ef86021934dbd844408777e9b488901cad5f3c96709b8857a7f38cfecd10cdfcde10c937aac6c5ad61a8af5fae06387629222cb7c86e6f171596f12dee59bc8d10b792bd73da55acd46e99eb8ea335d27a54b2b4e714e0f4ae4cbc5ed9a6ae6e808a5aba38ef90e9b6093f4282b23cbe1e9e659e298c1d2946817eceb28878a11063e04a2a7aa6001145183d4d3c02b75843b2c512c26016604a4cf13396eae0a9d41ec89886f563304bae9e3cd90b072d22b2fbfddbea077a190ac571219b70ad31f28e420cef8fd1d44e1eb82cddd820ed9c61d2c7c971df077cce2c39a31e488859f285f1a8eb6ba011ad356332df3365665824bcf14a7ba494362cd88298af387a0fc910d0d9bf83ee45ab249caf6af89bfd583651919083a4838fa6ebcfd0ce4209352556f6d4bbcfd2a44d3672a4ec6ab6b705c91a5c27a846b57f1cea1311f28ff191b672391bb1660857aa1055abc96be140065a3535c90996689c6fda68a30808041f6c880a479c527ed3ed8764902da0f83810c18b26ae5ec5aa463135df02b321fe8a8ea0229e93333908e44fd5e6730fa057285f7ba11ba554874b28029d37132edf87cd8416b8395dd179afd6eebe6342bb4a1a17bbb8b4b614e45a0e926f00fd4a8fac8630caca097ab297188400a65326da19c5ed82c349cd7094c887431310a516c0013362470970faaed667059366ef927eb01e9029c7dfdb88915ee138264abd1773c8e2703bbf2bb769781fd2138cafeb844f3822b5d1a9c1a791ebaa7df323c7b51bd4998d0185ded8629bd26eaf6f64ecb02cd1c2f01327d8c53b01021422168362a8ee1a901c6b11d24f697b57ec3015268a931912300847b6a24c430dc6431dda8d195a8b6103d31d6596bbbb2a2e3541b8f3a314d069061e0193665f2a72b52ee299155ed3c9821ecdef1e85473078700e6986929bc170bc53aa7e0d494898ddbe83aeb041147b56ff4284c7a3c2717e17e2987422e0f7ce4de17e553086f5d4d67200a647e8e39ab996443ab27a962d34fd4fbf835e3d5bb85ac9b350461aab361ae3bada2b8d2532022e210d1ccb417c8b371cf139341f3907bbbee8885d5f8a86689a449bb422b855c496b2d5b609fbc27e72b5726e004bef616259058de2f00faf2ee9f7cedeb8ccad25994423143f29f1f351a72b98416375290913298a9a46b1b9d1fc086e0c36c30b5615f80667e0d13e928cef22bb2e05c2ce374b5d39111885c80e1847dcdc6e46824fed8f7b36211a025b9d53dea31707c64c7c757c6314c22ba50151f714b11dd44cfebca32050d2167f846b416585e736f25f61822411e77985a94056b51eeedca45ff565ba34403ac2ab47aa1cf0e9a4e9470f822833c6a5114dd0742f4f6519590ad2a0d0b3b7f7bf8124262ff3cdcfd6d6128a96ae9183ad976985d927f4aa0fb672ebd4cbe85a30ce4e884ca5b4ac5e32e9467a46054a9e39ae9eae6ee7c2929644e38c4447c091dd7b4916af22036ac4bf0b4aec71041c1d943da9959f1a87a482cb10b1df7fa41af9e24df82d4c2945a8242ecc8df703e3adb0d7914caf542b8133a42a65bf681675302415f4301c476b74b41f652288e8eb0ef6a36586ed4fffd462bb5a64eaf7942125b53fb66b16a4e37315fa3942613dd9d06f48eb8ff094cba2c2c63a518974bccf1e8c6a8d422b8ab857aca53b8a0319dfde4084a3b4aa45c78e6b2f84fc1c570581a3bc568ea9dea2ca73d160662231115bae84a14bcc0752ab445158836abb6bb5f0414583b58743648d622ba1691d25ced2a202c61ce3803f205e7e37fdb018dbc67889011cf5cf1008d512a768428b51b8795b011fdc100b5a87a75fd36b00fcd0a65cffce072429413f8031b36b935a0bf96022e0464866e66b391638d52c9a950625d0140c630c5fd35f0ea5a1b142197c465958a5da2b8c8b50dd439c908fbec2a977080f1ae1207f5299482b49c4df5116551afe65c1aa0e2d786031d5803e271dc528f1b301630256388e612d055e4c164e1180cae074417472a35073befcf317f3b6ab9d5323e0c7093ebfddcdb2ca023b1911235a1f9acd225745a513e128fc069f04ebc29158df96869ce06d3fa87d40388bca49769dfa0ea2b79d044cc8823821becebdd9313b480921172e531efc51123a5cdaae10cdae3c427ac71abe2f33fdb2d8a0c5f88e9ef878f6dbb3a118cfcad5d8e3c0ea460ff55038407c0def9e62897acba1ceb668065a4b9ef3cabf14072fe8bf6caedf463b042018938eff3c40e89093b969d91ce9d2c6e1d295879085d33d33c8c771040ef0370b79b4fd568231d4b3aa3e9fbf1a79b4d6d39435b12226f55675518c8cbb6eade5122f59d6f88cdad8aa24891e6342c02c21f3cfed35b3416ef9a09626ce2da90edce57308aa26be367bd054aa6fedc3fd2923d1fbbd18413f327eab1a8782649020b3ab7c91cd1355d81d2809554bcd4b9458a05c468b7712a62875f1f414ef5f4096bf63cdb2845563034d1064427b3d82611e52e836bb8e98ec32ae893a8eaa7eaad5a30e630062aaefeb376c11356d111b802c7094e5078f27c4c454cac98215eee7d8558c7de7c59f17331b46eedb0f4c964084d72079893d40b5575f8ba7faa89e20aa56b3479ccd03bdbed9b7ccbe81aacb62d02ff7cdd849a9154149624a770816ffd0974e79f8b547bdbd09dfa9d2a1715469900b9e88d5fe050b1972e4c30952080d54c2848b1c2ab59ba649ea0a7e635ed472f9fc4ccc20321cec36721b775282196f30575ce58f8f63109de486cce5091f661843c03a6a195f488bcdea4cf6b8071064d082405c21430139672f30e2c5c8a395412708ecf0631f50ea13572537f564a06ef2eaa7d501bde9f63a0931322f0437db24d7228172f25997a17754c8a3ec503fad603b23c194d06031188a345c4f22967bf9ba018aa0f507c0c222aeb86b3964f612d6175c592df135aba581cef640a4b90ccccb448dc60a228abe2696a8750a3afa55ce92aaae88139b0f5744e09306e68313824071a626b4dba3113f66e10d11787ba58363f5687b0c682224ec2ba78e5430d82c0e1d1da438d9598dabf500d1bca95e274842ec9e3cb59a61fb97609890c872a222c4213221a8c43d40427febcc6e9a9267078b5112b86368c2eab6d9a81f80e5e3a8e6a20dde50159c269fb8673f5634a552438c50e732b522bb732e03b142d2291357e926fcc07712db029dd732f95064cd2661056743605403a3fb4b47851509efcaae3820a176f81992c56040d2283bad59dd532c13ef6443268d8eaf04b332805e22916ce1c36aae275170897d76f1447ff5e86b519e999fc6a7ae991028e7ecc0063a109c9d17b3a1e74265388b89dae0278b754f92437b9f3b7927df13d7c0ac9fd7af125cf8adbf9f1127371e3cce129efe33994236fcc94e38b706c5f129fb23ff5f8223458be59f82f0f862783ed8d1663d6b2603daf0b78f83876806098ee8d081556635423947314980428f0d1a57a9ea8e356ab0d76876b94363b9d5c39176bfff9e7376a8715a9eda69c7000c85d215962d3c09aa18bc6b9187ffee6c38bc81ed709b94d04be9c0f3e19c0f77892bdd6d0a54b9bdd85779cd5688f835fb8366bb74b5bc26f46425f5a61000ce7e863e734baf3b1672cc6d2930860ca31dacfebc4cbc956fdcb541134fc86f1c2946b6fd4ddbbc83b352814153b5c299b8d14f42a5986abe568ca5e44681a22c88729db446ed2ecdea8f583f313036f8dd6db226c98a48124661795d27331de6435e691beaf5a7041aa3fce33f46e7c7209933c1a220c72e3125ba6622699554615ee8684f6b000ed4598268bb510348655cc5f59435b02ff36f944c14570db86da117c575080fe5291d2554658a19f419567076fbe1f3d8e684adab3f96961c7936c46c0404e7bf8489d30f50bef8db241985bc7c217213c755ab3485b4b48113d75ccc2b1147c83459759c051656a88571367b9dbba09bc21a131c6712c633f16134a5f64928e1e1cbff9ef03d90b8ae5357b9ea20fee9224e48a12521820b6262437af28bee581f122b57c68685c88d8c8d9f3d109569e7163f841932c4e276c48d214caba756444d2ba93a64f9dbcba0fd93b0b5a8955bb884b4d1ccd3b55d590ea7b8d98096e76dd15842dd917eb59ed0db4bf8d2cfa7a5d7a986b9c63dfe7ac755d655976b8650f14fbb05112d4227c56a0407a1ce138d3c842a23f0b0a37ecb8ca032b4ac8194419769c37be9844e7eda928d73b4de44682f34656e69adbc73da10463419c0a1bf7e51691e0bb0167137c8bd55440bf78dc7bb9b6426fe0b59f2278381667ea69eace2982bec3b8a03449311f4cd04089d3392d781f0d89880e91635e8c3ce27422459a63ecdb81d14e7c46c51aab4d56c50645dd2add89da99535b53f79fc5038bb938565c51a525c67172032876ae6c09620c3286d1686e002e7ad398a7b5c6bc2dd60e68955e3a975ed677f03830750756d77486b29b8fc7544a8b72e79ea9ec44f852eeab1fc815cc3d73ddd11f178131e3e76b75baa8ec6d12e6de3bf9f2a2bd7b2e86fe3bd0ef900d53863cdb8812b47f59e79fe4679317a87ea4a46196bc2810a0a7a19b3423ca3182d5d021acb28120e287a33fa2b4bbb005f460ce5ddb866f19f71875034e5ffc641034568d4c5c4a021c9defa08f1331207518cb303be2fb12d2b8eb142e5e65bc5003cf353a1545ffaec440ea444b2330814eafff67922cea74ead8359e90e7a346f39be67877bec404745cd609b54fb98aeb0a293d2be81f98f5264ac166c916330a302a0dd67d949dcce00946203d3ef099b9421b9d9b077bc4f05955a2e8d86561afc67956754127072705bb43696f6b18d90cef9c0e46229329ca2338793c8349ee7e347ef2349c51ea7b27a6c9b137c4219459af899142820fb225dd401ec8d06919c37eb7ebffcf46205a3ced7c851f28aeca8e5fc316cca3ab2813ee04e5ed43805271f8e86ab3cc87ae72baec45aeb016bcf9946c2834feb1a667f3f5aa3a6e0097d17376816703d5edfb59ad2b9f1c084c75e7db6a60b1722559061a72cdb9f3c0e91357f6079ffc07da530854ba3fdef9ff1d0ce288bb748af3d25550606609bf0db182524292eb780588d6efde0b17bd3b4fd6175eb2d8ee1b712e4a92197490f855791cd207e7fbc12c4059690f22a63399f42fda7269ec3e5aea75c2e6456601f6ab9027a748b9c9432f54497c2cb15f45da02f46ccf212f34669da9fd2871dc2dc86f7997c694ac0dc9c664c2c5eae213e9c3a637bb3c4e41e35ef1fe682fb3a7779c4017b36768e418a1f27d26deddfa703336c3bb60925d2e67fc4f956a9382cebb28d4de183bfd105ffb3dbc19515ea13f594a767cfc3c9655b0c42fbeda5ff2e20f401bcc94807eb3370764cb0e8a17a61193b50ffb0e4b7b10ea2154bdcd172e1a3f772b642603eb90537f745aecd9519bfc484cc2f0fe1d095e7fd25b8a99abe73d4b2a52b487437f49f16d453a7922ab8ced8502a62a9b190a6bcd32e950c4e9619e737082d05420b5eae88f6732320e973ed642148f4ad9a0942c4f16b4f06a24cec08d2b5d39d1380fd44136f6341dcd101d491e89bcede3519cd06060aee39efec05285b65f9119effac905ab8f5f058c0750eb5032e4d6fce215a579403bba4261b45888adf0e17e3004c5cddf62e282f5e20ffda147ddb62783444a94d67bc9e929ab32720d8bddd46bd3550ed7118db927d5507e4bfd73d35cf0f64b1a1812e3871d0fd716741fcc51d31ceffeadcd6a8e6dec9a7c9644744590dbaf4d7070f406d8f397be12177529fa75cc68b3b375af8c31cc9fa005a2c619e6f6ace3835e6fbff8d220ea0139a7af0886b230560384c4e76b5e0ffc7300cc3cb43dfa4539d8c0ede6bb54b1a1ddbdd1235509b58d9a8181e9f6dac0e2d8130f611eaf66fc04ad1dac6986d957612521375e449ca68e0aa770951e531ead52c11d328fce74862e500c4012870320fd27915bba44cc1e8901c35681b50749df185159e7a5e9faf4069dcf64d2cab19b22f932472efa2cf9fb34e13724718791869fac2ca32e93f7caef89618b673c057ea78374ba39db1b5d5b6f42778406ddbf5edc713a1d86a0444b73afffd12007fae0027068d8ba100576281fe78ce55c54d7b0d66a3e234afc6338e2b718f03f2a12ca00e3941068ca59e1303177cac826675a8d694213f9a7e04eb867c7c1d26243768ab27c7b5325f043dfd455349462086c80541058fda29e3c7362f90b8830dfbedfbb2d382c111f9d2ba6ae30a1dae6fd41e68d8fd2f613225b9442614fea0fdf52e87c2aa67d006c898b35f5802205c3bc64b76b85d0b13f727208e7cf1a3686b4146c614ea0fef85ecd7fd9966eda7d705ef6db63f9af9523505de79abfcae46841f8348277c05dfad726081f6e0184bb7468cf789a8f2e34ecf88c94ab21c9059f47eb083ce40cb643b0d30ca5268f39f17e11ef8c58c2976f805379ff7b849b44c950c37ea9553cf3dbe59775ce10fb59982dd789b3b6f7418f0cde3e6100f7d27085290dd0bf0d42846f2e05205343dd19fe5853f90753d4b14820731481aee7d3de828d6481e5c7e1d8971cb3cdce5444ceb7c119dec96e9f62cfb355028f501730b10b788ba9e4672e6ca55beb3bce7d60902c64e9559bc705174aeaef6e2424d3c8cf9627b40f03f8eb4682e19565dd02f05fbda560dcba9192e880f258ffa623a16988d9ae3e12de72e4a27216a6dd1fe8d915aaa577b09060c0877ce198dc084e4fc218398e27b453be9dd3272710e7398cc22971bc7a67a15fdfde156e56a0026a3cabf6f6926a6f4f147c3acf4f35b7aaef6ae31759b9d4d57ce1eb7d15c32fc7eb5c7797df97d0d2fb112a5b64b22818784b40569b3e344c55efe4e7bbc26c738e207569d542daf27f77d51ce74fef545db85880903ab604e1432f91206fe7415f6697842e9796150e38a2b66c7de76b666e44f0ec4bedfc7213ad9bf8f101c9e04ee7e88889281a1995fe3914546d229c0c0358e01d465942f0e3cb09abe6ff0b6f53f2745053a8711423ca66424aa0040eab50ad75f7901d9a388d924a48ec2d7fafc35286798ab1da9a277fa19bca507ab75b91d4d01c4031a242787b38e3d3966fbaeeb67db09fbe63899ba5cc41860b853b54830cc40c8653dc5ba716a43dca0a7f3f3ffc314e6ee4b8144e49d7d18fba9d58667cc90da1776e85d46c4d462e79e1911858b3dedbecd7c5e0cfca5c607fd6e8c9e1a3ff381eaa6a3a00f04657807e50063493d69569c530ba80325f598f03bc46c6b0fa8d4707dfa2b905302daa151a535df67c03824c461154c93c81393aa41c3b88c8d76c0901d04a91cf273bd5bb6db0b8856fc602c0162ddc28143858d86b178d8ab600c233c639bd775aff866a88f93c330116090aa8abf824a10d777cb47d9d4a7ffd29d30837a19af5185639f817d5c8f323340e1080b102032b84dcb16aeb8d3caa8cebe5c4516fb7b398f769eaa5e3843b1f6c0c8415389e387ea8ccda7736b4cd8f0397645ee2464ebc571da0b474072c9a3591c41b22fc7830f1739f1b0cf04ed6804f9195d9752c9ffeffac1ad5e0893a4970ea9289b95c0d20e204c644b2818c870f22b659386bc815a7cc30e1bf2e6b28193a84996424600405da01274b0bfd23e5a56c3e46f701a9ff4aea411c9092dc4e2298638dab8c914855312ff1a7076b5c40c4c30b965ad0ae62d4e9249758b0c389746f54e3ac970e964373432cb13ca0e34a6a5e6c973474f32e69a6295a9e906d96b8bbed046ae48aa1c9134b87a0c8979e8279dd7d3df479fd2b37c817c4e22b5c848c8a58927aa34ac302376d5483b93aee5257e394588da47d676cea2b8ed40a01ba84252016014e099034dd0ae61cced1184a668f98253656d2558e667f4a1c92cef6eb6ab6878b4a308c6cd6b79ae45c511f63ef275e3f30cfd1c7442723249e8ab9d879c0ac3c2e124560527c0928f7c23af4b32f17048ac73530d7570feba7b13465c511d9cf7036661b235a1fa9293665560b48c73917c0d1893abfbcc46b87b55655b789b5bd4729515e4f62ece9994612af0ccfdb9dd30154e08d31b58cd23044acfa541013c1bf2cb295c026be4ba9b4b1a0cd17723202c941c22b7e16f6482b0c3bd4922c04c7e9fc81033a3a30580875f04932684e2cad9612fd3fce87e2900cc4dbf319523c8b9e7e9ad9a9444d45de0f4fd0b5901433cca14985ba4811031568d8cc0ef1215fb62d4085b7f7d8a3dbea22aff338c98f96ebbfcd7cc65814ebb1bc9f55ce5d3a04fc80fedf52fdd20bd7047b1ecc8a3f354dcfd2f55ef2b38e4d2fca02f8afddbc135714c3a9fb3db3c95512c1f4066ab0aa2e070bd39a0c6cce2bee9103998f7e434b840721f2f0abe6aab0334bdaab01cd2267640b1781128c7ccce833969c1982b81a898af4e1a7a42c9158291d43079ab44ab7adc1da8207a720f139dd0e6920b64937fa9b14f00179840a14f40172d93da1331dbca80cb8baca6539a5d192c62f10d77fa1ef206eefbee219b5a08209eaf45ea011f98e84fdde0a16fe68733de65c0398ca7826e4c041da2ecf1b57820888bd7bf5878f5d0151449c8f0cc454e04a1af0c6f6afa0ebb65665b2fc4664d5d0949f06bfb0589db8846d885ac0281500ad6b3640966a8129c5842e4d6dc87732fc09a260d2061ea7e4fe749991f8896ef35990ddbda0774db097d4e32098155f048a80c384fa907000e9abf6b02c862c7e251cca3fae02169ba8e1636b56be0d587c3d385819b5a71400ee3a3164536332fc5a3c37d7c3f79fc6ba4fd5fe1589d09bd5efa2177852f6acba389f9719436a6a30e187e0ecbd3511e21ab8133edcbfe36d2c4e8ab419260c2979e98846f65ed7fec941354bc198e5332b72a36cd228487958bce9854e492735ee44e7059405d4b998d2c31fb655f40eacc5a08a4810a0a6b1dac79e06ef92f266bd8fb0f14c4a9d3176942c0858fb94f2d9717ef909d906c21d701e259dcb0b61cca3f56812a6ff76e0db1e1c1392bcc52f06db8ff2fbd9762b4ae48bc9f8cf17bfa8260d912db23b9c1ccd734c75e9ca74664c99a3e29339f912aaecb5e476cbdabf23f99366c1f96ab6222e04c16d9cd9c70a6875def44c799726847c59aca766942b7169bb7e3b04ad007bd51897c755544a8aa4103964f0d51e14b4b90ac66e2d7aaf7731343131630842e0c57206c70077059a78384e5d80b25b33aa8600ad6cf1e1477c0bc8caf5082ee22810693d9993c0fdd28e867c754b0d522e8edfd715335e1e455fff1c315f4e2050d72a5a5142e0e3c3e808f15a47f0b706d152092a770b865a0cc0e5512815e0fb2f695de25229845a7fdff2ef49b7a91d5ff97ff3c8f3be126bc2d1d7bc01dc758aa65f93ef8869992174181fce5986a6e4eda376a8e21194072612af55085e60d64b4cb6a97207de0b1829c9e248406e4de710fe624527b6b735843869e3f168bb076ff5bb5e541e2cb86b862de2588cbc9fb268c79638367cb5d7ef38146c0bec2f848fab81596eef79f76d1d77b5b9418f94d5277b00c8196e70f24c81ce622a7c7273b3f8142d09c2702666302dce418fffa0de02003e6e2f1318fa8ac1d4d058aeb12040d0dc01e13d5c290f00b9857c5e43e4df08db63c15ad32e6f3d9dba47f1e3a13bac44288700b385e41dfe47748b8b85148cee13c8f442ae95f1f05c0ee1e77a7f8c1a2f88d33c62d7e773885fcc468331473e0d05ec6f485b3a91c22ca9886ab846347747bdd7265c96bf30280b75f3ba8b852cdf30efc4bf917e74864406255f772a7aacf6bdde76de9822c4cf3e23e592470d87f9ed53c28f1d17a9ddb34ae740bdb33856ba2de90f4b39b88b01c0ee7ac00632caaa5897912d4fd7496b6d77a08c532a57831e0763a359944d159a75c737b0c2095a8720946e1ae5a6670f83ed7c61bca8b385320b00f0bdc116f31c4a626fe9d5c9b9ac14126c80b3260ed9bb3078274a545a564d6ca5ccddb06ef207df272637e65a634194a61d5145079f81dd921565f64a1fe9f870f0e6cc31b184fcc80f410218405a430024761dc55300200d0183549dc8c384e5690b4e3b54384de36c328ae477076797ec9ca3c3b52cfb845ee125e582f5d66456d5ed89b20e4c22cc7186877c42355a059bd1e1a4d2f822cdd8073ea1a7242855580163b9c249e0107e020471d361512256c79a92ab3e68c8dfac87928b4402a2fef282767b4c3d627e7a3c71bc41cf423c04c271a11636abca2f59df13a5733313292bae427d7aef286fa5815b391955e12e39f948ec87d6db47c6cb59bbf9dc1c62425a103b876019bf3b43c65277481d41e1bd1b6ec5797d2c7e12943a934707923862b5cbda6de2f0011165bada5c6b4cb9bc8f8c81df96e9eb48653c5833351aa5e9bf36c12406224507683961e82a13c71767388d7a7b4a9f029e507fa8d3202f27bf03b99bc53081901bd7abc35e9d0c51614017f3282a5e120cd767aa6e469102b21e799487467dabf4398f5c1836e56cb37cee4f966514fef7862012a97b677aabefe1212be0aabfaf4b507652e3779da91255b6b6752f9425f68efa44b18c8e499091c2f294b7362bf59542528f19073c0a37d91ccf78bd3073d3f5a48df566bd6246bb5db9058560475b5f0278035f7ede52c97a257addeb55c272813a8eace93434ef77b9742c0fa55e1d2b1a4497e882f75205a227f5d0f837a14bff7acd5073f548fd82841267226ff59131d48c8112990a1af3626f540f29ac7dfe418ffbe7e81f8fc2872bfba92a5082d3fd42ff858b0e0882947f20bd02995c2e9e682da84a510f9faed69539c011c0bcddf72249dfa9d4ecd6f8462e96a1b84f4427f5dd247b8a8cfd2ec538cebcd2b2a98259038e624c444025df9a8f70d3051cf31d8c3e491d8a17b0388d696a254151b85cff8fb2674bada21ca4b838d58a59dd6e1c5ffb8a8bec90969c2b23a644a228a051bc713132f51cbf54be88e0542b90e11770710dc5d43d8d8fa57d24ddfd72f38c11f7f8849096a0a3543fe55f7603deb9da62fd582324ff636268dfd31f3f1bc9f4b4058903b3ef57d891818942238ef9aba6102bbca6539b3355d2c356d3ee24039fcf9d3f0cffab9c99b3cc965ca8f872681c0e7c7df31ceb4861e191d9674602f161d5ac8fefcedfd0e018acaa8758125777c43fb1a912a802b273d6374ed7c748ec6c2b93bdad52a24e5b477f488c63766ec21a448255bac2b32df5dcd90926ab1d8ea3c3138e442a985b871ccf6f4c1316e7a3e2add18482030ca12ada9f2fc5c93908efd184306ea80d4f1f90268ee7c8c040993ec7f0b923915fee0c2c109623d0fe15ae9c03d68f46668107b3258b576f4638228e76dc5d9816ea4a82bd13293e78e35ee8bc3af64d08ca9bbf3dcb15feeca151db8abe72afa1807e217293c77fa728471eed25476e49fec89c4964e1043c3f3dfa08957da7a1fec35e814bb8caeb6cd08823eaf579a30daa1b2e1fbde22808705770828614abb71a9f6f344003be10cfd6b1e1eff8023a8871e67f99d12cc02d9d5c01d1c8bd8a20baa28064cc8521230be0dd56a6de6fc7f989102698018dce72dfc094540a9b67370de4c03fa99b23aec3723aa543801de1af66d7a9046c0a136c2edc1d9a15ccc3411eb87b699e2c7a58de65867002862eacb756a9c85b8bb7111b0fa8287c6523b73befd39bc984208ffec1c0060086c445f093528d982d4e39c8629fa9bd5f56969c70a2eea675ffc896c94c3edcf6df46091ac455e23d650fcad542531469f9ad1fd048ae3a098b6e2ea95bf14e648fcd001aafdb42d8ba399c60186ee5b363529331e0ec78fdc2e050c13da11a882885780ba684c617403e9769a58a0531e53330e545124478a2335cc41637b2a2141a6e7b7f4927802521833eacac69d9898fe094eccbd9935004ed413516432b45144aa7922801404961642aa83790f0a52e0a1c1385dc3c616162826229801db923e6ef0c387f7e42080fe5dad4298bff1e24035e35836d8d9e8800f32e49634f137583e698284e5144ea9b28eaf332d57891a503e758c44bbd8d3fcc5db20d2bd391cdbd22e999921530586a76b5f18863bade5478cf969c0490d4c2096295fc1cc88cc2937c829443c186116865f20e2657b21f5234e812bca0e5085153cc2973cbf902347a3f00a14c40d5569913e46bb72f348250802284b3205c8515deabc987c426b56b4f2e240de01fbc05206fadcfe8173e4fce62bba4b6855c64861a5881fd02d1956c4204e3607c1acf78c21034fa26297404da0fd66059a68af9a40784d673adc57910183583a9ed843b08227e36a9ddfb58a71fe4d89a66b44e39333b1f33455aa597c712ca0b4724e9897141aa3efc0cf1be1c49120683c371b2de67610b5b33bc6d089ce038c0148c06c954f174a9305037ded836b6a92c4a19e5ee8370f99069c9695711819c28118d3d8352e2332858f0de380f655970b22e9e721f94eb92f1388ca0b75475d1ce901a3ce20183233a1c14829f11e8655710071a03be39008c5fb8128e56c1647a3158671e4058a4ce4d55406282501e84124af4778d7de0290c1e380e87a9308bdeed86390ab81d572e28f03c62575f3ae23fb8966a7de1cc31c678b2d3ad1576acac926cf1e70fed1d219451d4caf0f8aea0491ce9db010f889b77f063860577be79fe19bc570e0e3a5e4a0a09127d27884f783ed1a39fd429c3ee9b8c1bac8049bd73391f975c6b907de123a1e2fd869462e97322b677093855da24c0c44c236a11c89df1ce4025edb5991c81c3ddc72d058eccddef12a58541c06ae1239f3a3200779d2caf9a23010744a1b1d0c37481c2680060e4309a17ea575fcd2897a5e381e4e4e81b154e0bf3daa97c8af6421072e99134638974a4853156e1e635a875a8529cd3940c2123482c590f28c4786ca0629e9ce44ba0fdf51c2f308ee36a1ddba2d6bf675016bb373b3b8ed2464672d2ba6dc85820822e65f615e44f700cbfa3c1f5c8b08d65ea49d27a3f577a4b09ed2d11b7dc636bf83b8409154946e49fb62656fe4d7271e739e643d3f7ade4dfbd8df1c885f6217c658104598f649713fd4b442e75d00ff558dda4a4fb04e0663c208e0ecbe045849cfd2b384be7ebeffc83ccd464529d517890ab87935169cb5cbc0c8ac7354fa5a2a5b8971e59150d94a989f35089c45e69c71c77ed0d64adffcba25272ef8270b77bd227fd203d86a4bb17d00353f83c9791609ae5c6e06e615ceb351541f98ed4bec84bf679801b21c2cfbcd6f916fc70f6f96c86d78b8a8c55b7d38e0a4182f3d25a0995fd584b5238874f0a1c99ceeef902769beb50957a88cb51bed0634865e0660fd8f52cf9ee3e85b98d46badbb1b4eec50dacfa96266036c7d931d96fed39a6ad4b161f9b5531305b072975b265ec8b23843093ae1bb92207d738657d70030436856206cb68428b1da3bce526c4b41a02f8f7c56a22ceca05ecf54befc2c5dbd5ae6ebef75ff31177e71dfd73922a6470060b99424b4a639981086f5036d0e481a8295fc5ccf8c578153743fc09fbed072a301d9b76e77d7c26a226d47ff0d261b889c6d80ccbd0e6b65a83d757b165f02854e561a7f97c042ab81eb29ac4cf910503805dc91dbb4601b3eaf243594503edc1c71abdc6123e5bd077114f1bacbe078731a18fd53afb95471db819f6f7c2eb7fe38b59040c3780ccda54d125f0a269236952dc0fd3b99b912e2de33636bc38d809e3438d4288a8c6cca5d6d8e7da350d01829de3d1cf032ed3ffc76ebfe2c2a877ab1ce67b827668480a3129832e19fd9f05678c75a29a1ec51a223d518d1700144112d294b5a16ade6eb1c5ba7f829393f32706ca6ae733029b1e9ac67096e026ed8d5bf105514865e2752684c5062a7f79dfb0d750a390dc8b6619fa4c977699f4befdcee9375d32ce85e64339d26c0bfb9d32d3fd8a53705c6cfcf8c825fdc2c79e8e62bee12f11834650a35baf05c5bb94863a6f0d8375f621b544b1479b30e5ab5e80be8b9f3cffa464e8594921da5f15c8ff59d9cc010a7e65f75bab95b60f7b5025acfc2ad4c08e4af95edf0ebf5ee2e4608f262046403b8ca951ab4a0b1305f2eb2771f3edbb133b18fd22525243a8d254a9f474ca08a9610b394bbe708e286eed61015e5f1fde776901641c57bf52fef37c2ad08f554c02e84240efcf215e97a18a483764422741a12ce23a924f0e16301e7b87f17e6d95a11ba8d41c441c3ad88f45f92446ea1e20c76a3902f506e8d3cd0cb96d9076b149382ca8b16a8a08041e01a65993a4e9619c215148ba584fe5fb8f16da941558cfcbd7b1e48b924bf3828e072801ef5fab182134a3974862295705314cd43409f3a6c9aa11388bd44e21fe7982952a5d55c52801aae173b498a91765643e7cd0c2ba1ac600310b8ba849323f3a73414d6938604943f2eb8a540dc703cc5395ca591b7be399bfd6b1f718ed4f81ad7762178d5015033efe18f051f9211163942998c1b6127f4f26d826cb1781556fb6df71587da3da3377db15f38f24dc87461659a13ae1026694e47b3a74bac3d41dbb1b653a23845c85d5c093d2474db1c8a4b060f74bed83f8e48985b8eb7aea827356c44fe21b5dde8f876f5d2a70fc93db386524c02040ec8864b74920c5757466deabfbb0dd593f623deb1c1112d9d7b05d0eeee87603fe2892de2883c299dbb0268ef6e87b81ff18cede3889d48776c85d1f6b47df58943db3378620f8e9c4c775692b441a97ba022404ed89f815280efe9997e472d1aea787ba08e5e79b7066d8f02283ad68eb88f7aeba0c3df632a6abca6eaeb4d4c1843dddd3679e3f603c74b4ac4e20f0854434c7f1528f0dc9329d6ff2a4b3d3c0500cb7c0fc420930bfad017c673be4e74ca1cff04400acb139071907eb0de4922adf1329e301c0f0eca05fa2246e3330550e6050d9510c86789fd400e80ed62d2b516890db78c60cef1867c96ec1bd040b2fa6d34f893ebdc833d2715003ff3bb06274948e948e7d5ea040ec28f93171980700045e56a8a0e2e1bba57a00aee9cf8e2e9678b299afd74e3a53e921904562fb6006cd5379b9cda48890f69ef0729a9797225a1eb15f10b89634547df34d8f487a361b09cfeeca0e7e123398c5a4cc2060ff6fc54c320d57bb79c7f43b68dcd3d915973fdf508c70c57925ff0db7ed1e4d509025d13797bd762963954306d1979fbf0851c014ebdb96e0e5526eaec9dc6cd60c3b4484c25958326cf402a7c6cd5887afeab3109ff6c18719d2d88b7831bfada407f2136efc6ebc4f458518fc11dcc6681f70a16206ccba75a01ec3cc808dc9aa6afca59a1ae13844104351ccfb8228adcb4613e4e9a2bd511bc87efed26f5de8a1d3c38633f71e3b58b85b48a6c6c9e2dfb41fca8e731e2db1bdcb85871093914f84a6634256f2f1c20e8fc6739b79d1e57b51f9900c888e1e2e665db2d8c021007ed8a2e77b4b1d56ffd10d835e65bddba89819b1330df306b97ef60cde9d563e2b7b3ec5b88e25815f562a0b12782ed184ec9f2a472fed5bfce18721513b01c0480984686befe92720f79581340bebe0faedf9693a698892291fa9086e95235262c6438362b68c5b72a8065979fc168476c6d7b28ff442ef27d7d2d43c6ee92aee96a646dd588fa435530b5ba2726d17f17ad482192ee6c3e1ab30a677f05d2279e0e20bfea8552b1fe7fd123cfd55d46cf9c519cdad70311beaaeac27c8354a5cf3a6d93270e551582accbdde982ca8c1ebd656476eed9ec21c68f53bec389c4516597144154303d32d0420cc958509490d7b2dbc8713a28f1d268e48df5a17b9ad10c59f957027843b33e9c9b2ec162207235abd4ab87a38d3f51c83d55274d9db08bae98a9769ad76abb330bcdd4f8f14e74c867725f632e3c4b378049ccf308c585394a36f086b5d3012dffd7d38be6aa3c460866a88f26a235497906787b0ef9e4e7f942f6356c59441d659227f9b5e1eed9905d17e1b403f8abfdadb0a7c7295bae93b4189cdeef215e19debf83d514771e65f1464e4b4d7e83ff4f41c00c809cb77113e6d0615a2d5a97c2efbd498dd4269228b56f05562667712490f812d10d0efd576643eea0d765e4b52f53d5e8d2dc6006d351ca5b0bd4dfb523e3143c8702f4f1181a2c13b1a60ce1b085215857354610796b87fd2aa06bcaad3135c9103284fb74af88fb6ce9e67cc45d484f19a1f7c0d037d852f18f594fd196d9226ccd1004db41bd0778e1282ccb9e7f10ae2553daeb022127e674c52a13bc239c45c9a066f5354c245e516f23d1677aabf271bf51bfd9c91aaeac21f3cb259a9e958b519590659368a4a00d40c1f34aae25b33c61a0fdeed612c27f65af381e0401954fa6b6a550058a75c3474c57755cc349339e0ace549b8f4ed6c335bf3c3ff4ac58eef8833bffbc6e42c64789a93e43853215221cebe2caf50c0dcf064edc0641a7fec2cc9ed083eb882489174bed3ce573e63acf2fa5d934f8751174d56eed3ec480590c42ad8ff40924eb42045aa3eaab43712a8dd0459f4ce6a9ba907eef9f75203e58927bc1c01291fc40dab24c5cf8d885df47c1d0a031a0815258a80e8aa6cc2dc0b0f28798e8426bd978512fb6872d10ad94460f7adf029dccceb621d0e6b72401c5c62ec42e331b407f6ba9e2134825cac5bb5ba0db1ce2ace1a7be9c8824a4652e580284536e021ebcc4fb90489ec2106a3ea936f4b93340a1a756c6050bb03e305489f71a8b1a117e7324f40a0e8d7f1936df1abb8f38ce093878dbf52549d1f01c0ebcf98f2827b1ac7124a6df6e8597ccbba85bebdddfd590103a1e3d9b0ee1a8b04e13b84b87593854f03d67ef84aaa5609eafba4726ed0fcec2542edb3dd921c56150d1c8ca035696e34972bf5ffb936ad8316fb8f18bc5c492b619bf2355bbfec5beefcac51759fc7d5b2d607a276982ac7511af773e8e48198feb3fde4dade24a7c52fa5fda46e5991d7056388de8a4c0fdb9b1916ef9898faa7162465739d5695de3904f6eef062cf0dfe3fae0456fe6644b66366dfb969832e009e4bfa7fa1716106b26814496f12270a8d26475d7e722ab9621436a53fab4cf6f1a9ff74ac4c60e46a21e2490cd0d69480d796df372d044c48b76186d69845913184a6dc84fd640ff009211b16d70971199ee07b0b879c11e5590cb5a009139e2d99c0bb2a857e4304f66ab9d3fe7285ec604981e60f07bace26b10cb8cd7a7d186b7ef3e163143b038683755cdfa2fdb76ca7c7617f2154d5896e8ba17b71b97bac56d835928f4b31bc7a4191a31e5193460b8707f72f0a45a5557160a6f9a79c87b24fbe7b32e75e813cf3ce45e878e8a6fead835bfefae3a5ccb2070ee95242bbd810991087eb6c0dadf360142035414c0f0c22bc1ba02c623fdd24c033958ab12629d5653f2c24216188a6147f6efb71ef592f6aa5239539907555429b4e5c66c38ca42c6ebbce830a0df39523c3ea57d2fe3b30686845d0c054295b19158dd42c15b4901c7f8ea1314530fa9100c9207f0bbe9734fe82c8745e0203e2af42efd03022dcc7f408b267cec5e6893697022af864ed7b9e9cbc3d0a983d41e9c0685c2fd15b3fd636f8c97692951309dc08f11d204215db1d53dbe289acc7c4eb8f121ec4e0c915c1a87c0f8337bd5793d165e0551cf156a6915b64492db6cbbf0817222232e016f99e470b305fbe533edb1b43021e72b761913ddb0518bb862d8175f170c21b10eb30280d6505b7fa5f2de3809d3d276a03f3c7eebcd26b3460721cd2e3ed832101458b70cf985406f60b27aa55bc255e61fffc2e3738000a5064c8b12bc1f08142a7081fbc10a591c2734933b1bc7646b507a9450423e44a5ee89e5301b5fb1972f4172cf8b9ea006167f5a225aa34689b8fffbef3e3cc469105608e929e1e84a0e591275a14c09713b1d2735a8bc6ab52c9b08e9e1c87e1a58a0499131e9a33ec70836234cc288ef4bbb091ca2fbc27572fd1a833d856b2f10c4e9286125f5689dc790b010a2156f51b5e713d93acf7010e533a508b5ce0ac645902b2aa0e0611ec838ee2ed5a038f68dfcf8b5d126106cf6f0de5db7f32f72e9031475a4e149118e733e1204154eab21bb2ea9d459fcd1c3f3eeee7fa812c7e98f52cdf4ab202f173e545b0063c5bbcf9e9ccadb51d51f7934ffb8b6ba3c20f8437f79cc3e68c759e394e7a4fdce997e1db5c3eea7f561a895ec3abb222d2cd7f7313b1f624d3b087ddeabdb7d2bc26e577c6eb7137f75dbd3b64a38575a8e6e85437ac8b3c0141409deac355e01d14f1cc326a71349b5e6d3f67cbfde30eaa159d2cb5151a6221954b52cc5517133b5c132a69dcbec894e9d7b4ff671378c9b9ed71911243938f04415eb4e92844a0d750ace421dffe601b710b4bd6d57b83bbf88d3b2adf71560f2d23bf3ee6e8329e3df161b7e4c3aa98aee49de74dfa6bf0b525d2a6720dbcb17c95da0981a35deb2718faed8f08588b7764d893eca37bd706cd159c9e96761582a9ab5723db40197d634062b56e5df0f28c89640a772c37841d3b26293e6019d7eee79185b9e009292a8f97325e37e25c010fd722b89b8141955e935ef17f901d32ccdb9b912a2e5e6b337d37df99c9be7b686fc7bf7db376698ce92cafcba805a539265056385d41782bcd45082920a8d98e10d08891748d55b6491f57d64a6612c52f4cbaf8fd04a534610fbb28563de98a9c047d0de1737dfbf376905b644740c8d81009293f0d8d0f1800300fadddea0c168e67fd950c9e01441a990bf036569b97739217aed6cafdc2dfe1dcf16a6fc0d5285d9b8452ea35db75e802cd4c3cba8d61c7bd38a9521b1cbfcc5bce0216dffe06168656d656dde60604d42c2bcfbfc18ba8d9b92550bce8971c29d785f2031454581b82b35f5d01e45d911c1cf0c060142229b1da8f30adb658969491c48b6a85af64711b0344b35faee3f53674bde01bd634f103899af1ec665628e5cf411dea0a3d9dc489c46c310c2175c747f3b0e88674b4b8809f6dbef46a27121e0b075a7312cc078cc0c914403c2f91b08e42c9b3ccf32512103669b1a8bf35fd5d290c0f045bae7e9bca5d1997f26bc835c9cc1e68af71f2d8caecf50ab142ec1319e3110bf739d27f05adab26e3aa77d99665ae749c764cedf128b81cda22d73c19e72740e839318ee723b448b408d5cb41970629d7fa9abe9570f221783882ec0926bcb4ea360d5d7a7733c792203890c541edc000ec1086b3f0e6e867d102c8505903c710e1b4250eb59d033648a5f8cd1ecbd2ec0f1eb4ef04a1ca28efe2bc7ed87d112728c60fae0a69bb85f89a64c65b3dca37abfebaec755e6441cc3b8ad7efa38697f588ac06df4ba94d39c62f9c1f3a5b88b2ca5448ad2eab3d5cf68703906fa2c248d3c3e34f849bd52a6285ef1360f039daeeed4c9d53a6540feb6a46684e81d12c0f030fb7844edf543a71abe8ccc844fd97ce06b5aebc1def40b388c21b359357e82aac10f54473f4538966ee3f5811c5c0e6d47eb0849ad0e5c39cb422dfd81627fabb1f4d3f1fc7a6ee14c505e85a5c8302392bae7ce82c07046ea97ab18fc18e62ee0f499c87fb5b2e77f55b0595072ff7e00fb861e817f15d2301e8113b4da8f00b3c78872bb215f2709c48cb016deabab2ecda0e15dad715a4760c0b0f0238d47d1bde3a226ad0bdd9a0615042c728bcd57ae176fdaa9f95e9e9da903b9a4fd446a1d6d98d9ff58e961bc00bcc5438ea7971b61d51644b7e937b2b5f6245c665ad5d41900ab264512ef78b8b2d398a1953b8b25e6a4d7891c2137fe3d162776fb65be69f25a3c16c5cb0d8b58722397b7e6d3b8f2803a2ff866ebeb89ad4450f87903624c4c2ca244c3ba9ecf3fa1c422fcfdfda7e37ab8848e454c276af091e2ff8eb3fbbfbf89a1f7f13958c3834034cd49dc4b84ef2b438b6140113fcdf8c664a3ac4a8acf2eaccd330dc9c431eaea082aa3f2a73ac970e20670e0b422983316b9467774b8fe10c85214858ccb19db96344102482ae400a05aba0d55c06986cf397581b2676a3f372a7408d13dca8d4faa5d4887c1c62d3dfd7dba91b334792ae4fd02e65584e693572f8d83f38b4a527bf34abbf530dc449bfffc0283176ffff16e6eaae41fc840dfa21dd87fb3b8ff9e7f5d5d81e62cdedee33d1ebccdb2fd9ff8113f85c07edd949900f064753e3395f26e0e22759288a67affbe29bf9ba6ba1d84ab5d3b8bfbbf1b2d5b3b845a041d727d0dfa727ff55829fc337cac67e4c4a485db216192b50af99181f065f8ec79269d6745c07bc31f16d6d4b438916d3f2bde8d3414bc6f66dc4897d3779068437484daf15d258a5da03d2542d94d432fc45879ada633ab6f35a2c944a2c0261765e01d9fee5c58f33140c544e3127da53480a69e9924cf6098226404fbbf1103ba51cd138fe54a6459ac1d325860b8be367e378a655bbd3c028c491e399bde810274fa065adc234ec9d9e1df1eae259304287f86df73c1e37e5df383082d24804d34769f7cde7fb01dabef3e560b0832e1809293c6957f3b28e8026db06624a2fefdf5a551d964da53aab517d8598422a8b7723ebe94334de2fa9911a2349a51f44b4612fa5a1b208098761243f49792598394171a0e5109a01098de928ce843b0f2bda20688154b0456cafb6b776c4c5c6e01a06d259133f1daed637faa1603e78423b44612341a2f294d76fe088c121769e0b084c30c41a4bd52cad1379e7eca33432bd7fdfa0a71d2c2b3461fabc2a40efd5d771ba61a81818703c05a3995d091843d856057ee006eb9be79afd14feee1cec53de02da863b5799a10d6c03d993f0e53f366c8e5a6c0da58e82a1834ff4a6b8c2e8392a315876751d1f185befea3cfa0e8f7a963fb0e14d9aa8beff07902c7779397c719fe1df39dc0c22cdcb93ac10a223a262eb63349e516f108145a2960bfeca560768e5923a45c4f9e1949af2c7b4c2991688d7fab2ca48aab61f8f3db08183145af9d5d383b8f8e13130c162c4131ff5c1d5e8cdf52f962d7898fef9a8fc5295819a423d265e51dc15f7951ad04e1dd7c22146991e07750195315ac8e0f4d84ff6141cc055034a1ea57da24626592bca418440c3b082e74c629b98a5a3273630bd3fcb60c1956b26556125b4610ce4990c4870e0b5660dc3259f3d1b10089fd2e2fe74de70b46eab7cf849fdc64ab9a096d25898116ec9516af73c961041584a2860687b02a8b51a9b3be392b5d030388d6afd580bc8249d14990767769a598fe76471bd3099ff679762e04d001e77c741aee67326da5901c006d17e30648843bd814569ab9cf5ea9860a0988b20b1429d4cecbf264c9a49bf376d05815edd3e9ac6e2606cc325e92f36082619aaa4b0a9af6a3ea5d07cbf7142e46d3dd9a0a11d209684f8be8c01528f3483a9a66bd00c33278816b348c93ca2072d17040d969f7236093c236a8e59b95ab82598844d8eca145e08fbea14a39b11e875baf0620bf1625bddeb16cf8d210b817b9a84692edb4b3c4a852f771e28818071a06c3b83867aaab735754ebcd716d04885443263aa7d42a73bf92e3e848162b39ee9d3ff69ec9246efea9ac5c8b89f2572397a49366af5d14b404c31ba9e0d80af264ff33984fd118b4ce57ce894d2630f6caf5ff42a547db9629f0158462082ebfb9bce35cd31b1039c639ff3d426eff892edb21be87148d902fb92981caa0d45a9e3e692bda055654e48c3f7c539bcd13a8de00d11033b2c75641c326d94888737afcb820b0a97958c323dea8efdc408dc8ef68c110bb693c814330a0e7ab5eb5d2c2a9fa007afea4e9722ab42e8e7eca345320ecd777bd413f134593eafa013e2086daea732275eec4be066f1d1180dd44674902f0b98490ff696ce979adb388c397db3ad3a0ed2e9c3b6d036d5470217bfa06f1256469beb1b0ea7b3269085c2dfdaebea445c98b13aa975dc4cae8b9901346b7f62e8629bfdfb8e064cc9bbf981227e4c9dea39ed0a67a0427e9f81753fb06402e30419e35d69f258e503e2b56d7dec358a3a4a14bfef27ae61a68eb633b6196befd87dfd56a17ac2cb6b2673c172c1a804391b6f590e2169aa609e3c4b526c54cde3bb9ae4d5c922f7a340df2bbef2d27c5b8766684961654fe02af716df9e69ad8312104a728fa09f3b6eff3c53d1b56e2863464621346c56de7875801866dad8939fc35e27f5f20c466ec1e6a5e9997c5ed400184eba4593fd86a6d34eb1a5f241e313ff9b0136312e25468af1a6223de610e8704ea09da39d0bb2a5592c2a935a05383e15475b0ecac5e3a6a954ce4311b0adb4324941944312196f3d0bf510309de0cbfae99622702ae921ba0d2c970a3e8b40bad30a3544a0a847dbc6baf412c95c1354181b424ad3598b5afa2e6444621e43436c54b4231fd19af433a64ca6ce38c05c7808b28e65cdf97d72e7e008ca7c7b5ea0455abcb57a51c0adf42fb1dab9af5da10caeeb4eee1bee7e4358729baeee518c96bf4099a6b8acaf50d8495564e8b373770c10851826df45dfff4da64c5f8ad6226d9dc61f59bdc42150b15cb71723a748a1c1a0f4208437e2e86cd58aa7ebc6489c6732a9852fd61c6d785c4210df8c0de039cae3370d59b4c29ed47740a8019ecc1b0e6743e2b103c8a592d5f9eb74e67b82348728081445300ed4ce90eab407fdacb799df1b8482b3994ede907612cd727501bb5d1013f859069a3d215d5f10517f619cdc59a1cb8d951688cf4db52cbcfad9a44554a5491b1f636f0209bfffc57fd1c64f9e71c40186c1f8a5807c501fb317f1668d168749b3c614257628e605cfc69f027ed39d3cdd6756fe98546918c853e36108f9c1fecf8375d1cabebab6268ff8797abc6aae1b5e4d3fb6d260442145781806eb1428cfd643dc0ef1ecedb72ef5969396b6bd5fb8a7530bf48c3b371fc2c5ea9df46e0b5a8d0525766de75daa9a55c62dcb3fe84cddfd18d074ffd50199ee6aea315edf6ef987749380ba5e4b4da422cd9f37db100501bd31a15ad1bc104dc268662b7d8b82bb785f4d8b6208f6bb1da477b7f0f2cb666e65a9e0154405d8d39bf2f09b8cb5517b2c0c9fefa82d2143b4545115497ad7fab6bec9cbd2d4d7fbb200ef40355eeb60d4f1beb77d22df0812ae2767805d034c50310a63dbb0bc350c2adeceb36196cd062e3407c1fc01acbc68353c4913291e9574d2593d07e095571463c225f94d464c5abb3ffa5c2013bc83900f889a27999e52d327656c9356bc5a2f78e8bf86a1e463f7cf6e23a61a4db540b12f2d09531d8bab50f243ae5aadc4dd467c754134d387949641cb63ee24bac2ea25ebae26401a7366b18f49ae0823c106c3a0beab3a4ec42bb0899ac482e139328c7a93759e7db61de422d7adff78158cba284ce53767224d336c96169a44eecbb67d9509c5385fb8f821062aca86b56ca1873c26a9c5bf2efc55456b1a501a04a5cd1518c944ed1eb9ed0b1a98c65d27f2b6156535979df5758224ecf2624e5305ccb11a4cf509591844b386cf5034a047b965b405c514d07c95ac0718805d3303aa29d1b03af1128881a29027362b5cd4b46861dd708746b0f550ec010ff676845f385b9b05d02fe0e18f74539c38f93a940c0024934aff25464e562d8aff7a39b08917bef2df796724b29939401560939090b09bafe9d892ad5b79cc9b1019e8f3b1a1d47075e521efee88a42a89932bf19ef8978189e06d275cf3d8c775da7b70e586badb51c67125568882aeec34c997fb10e6fd67498a32b278cae95992d238b881c9caee32d0f82a5f4610504ff5128d771c91d0753b616865c1ff320ce82e9784ba755aa2287ae636bc8b7f1d97dab6bdcc7743c75b1a11c77d517c36af420f7ae6cf9f6b18738e4ae51a376d7c1aa3ffbb9afebe090db8338e83a0e4ee742ad18c6c1d4f7201e64cafa587dfb982ec9be467df999a665d4975f5ffef6210e860a1dd77196fb73c88378ca895cb8585901c17f1da1297258b3431ed6cd963171cb2f911f69dcc657cebad9f3070fcfd32ee429930825894aeaabbe7c393464be1472965a2b0a854209cd5484715da803b1c55131ce940bd48c314561c47f61038774913bfe0aec9d0b31657a18f6fc6c0eb1e72c624fa95d28aaccd79690e9cb39373771c94d4a293175928c94ad65dd8e195f22aac8f770a210e2a0ebc7c1a0971ce2a0d4e40e0725e9b1a7a4df1ca428c8a48f3bdc3649bbf51ccc94c8d3b883611acb2336fd5022b13139b3e96fd807c330124993b490e9e2a1444816d40b3b7496eb60425ee1d484aedaf45f50534d5f8a2ddad39747c416fb2e4f1f457a2f35f2b44b893c7d1e9c85fb8d05ff283ffd2fc2c2954d49a40f1fc663e69f34cbb4daf24b9f14daf2c7963ffab21c11b2e5779f88fcede5375b7ec6c4e6618646ba50eabec4962f77d46fc7bce98470943939f2f08862fb3fbed908b4ef9fdc39e6b79d714e1fe6a67686712828e6839032d246eea029c16a8772f595f4b0b524f8f480ad4c941538d93f44b9a95ca88fc3c16dc553ea0afb821c795833112bd18083f5e3d69250edad9f7d78791072b515a22d21d797710263ac4991e363f7192995734e1e3d3070791185f4821c469e4d27fd162fdbd315d4c5a65f7c07f18751768efce5d8b306520e3511b4230f0ed10e25097870a6b09d83895f8d2cb4b1dd6b72502565667268e2019a1ff104e2614e4d872ef6fc18a2b6f632f20cd1b4f73e9ce241f3354d0ea9216acf8fb1438a4b793f533935face5d4fee23e26100d1ea1ca27d9ddbb67d4cdc9b3e2942febdbd7f8e8dbff071ec2cc7cebe0fb0f6fd70d46d736cf9b2065b02a654a3bc94524ab9a70e83d02d1ca48fc3a61f5e7a844d530588297425aa509f1c7d7a8880042880238830126d8c7e187f221053e407dd217188268129a574ce48338a7a96534a397fcf19044c05fe21c225052e352e3535358e0412355bd01605f72d0807247050e1a0527910c781b7648ee340e604f1202a1a1a235446d0d0b45a4fe8c4275ab1258364147cf041061151a9b559666d66ed902864643e248be9f573ff39a594157b7f536bad9bbbbc1253adb45a4fe83cd172c1c5d9a18dcd4604ca58f726fb91fdb811b3f6e381032572200510c84053011f5e30420945a84108ba206485892226c8b0fdef66daee8e3c4c0cedf0c0a4ca0538bc565b3103ac18a0458b236cd41c171b971a97151321ddb5d65a31f69f3a4a16f5e547b3a54442b2903f3faff11a076b9d2ea30b64ce0e6d5415c5a106071a1c54382021e7c4a10603e19f88a9c0403d38a2604d8aec8fa5cf49ebbe21abe840549157471c1b0ea00838487de881723052da38b8a235f4e90daef1d77ee2af79420e63286b36052e107958339b3e0e9733b4c42726eeaa9390c315da83b3c47ddf3ff2f010da2bf12ff594e9e2934f7978fc5cbd224eaaa90e5138a28accb157a02b3bb4b1b55a4fe8c42764ab05041c50a1171a230b0e5e5e78f034bc3568ad1f1dac2f2bad5a8c7faf76bf7ecd6414248bf8b67ef1ef471f08fb32c8c14aadad7217490eb35f4cdc34faad3af8e951e4f1678738300cc330fb16cfa7fecdc7beeac3f4f7a72fe7c4dc67f56fd2a73cf10459fe8b0b1be4d034df64ea9808c66d7228f82069248d3442aa7c9046489a2dd0ec0057d8d8a411d28566478cf426c1832696f01930e1033b10c6df230aa9e394d346ca503e8e1c8d6902c922be3bd034816421df3f20a6cdb4894ee3d34f522e4c1b20139a447e1d76e0a107ba25917d73560eff1f880747941923ad3146138d1edfc58effc2e30a07274a0fde3c8aa0b5734d74891c9a56acb4c0f1f209e912b76c512472885ac0f6f7c8823c5d6a31309580e4152ec1526c89724a209a510a52206ad5e19d58adb5d61b4298ec2bb7b599e6e1b5af0707e96b3eb290ad83c0e1207d1e3c2561d0f7c18482ac63e2ce9e664f67a4ca66e52cd9d3aa370763381861ecc0923f36ad311ca43b38486374d03bdc1bdebe6fe22c26f4885c3f34edfa03c54c6614c2830434e02c580f3e44800217b0e12cf53ab8c30e0edac76210e5b863788ac2a0bf430c1d7670160e06fba8e3f02dabb0d4e2909babda0e72f8fb51c0152dee637abb5a167278e58eb6036c63203fffcdce8fdf3918ee318e74c69d3dd221dda3c7a42fbf4813f9c54c51c0f432e8f4a10dd3267d7c0a987478777ecac9387dd621e94f23119c1cc69dd20bd227778cdfdaf7fbf4e1cd1f66d2737fb30ef3e6463aa65ec414ab3d7e20395bbe57c4ce36bdd7c8601ca69162f226e950eed37b5fe8fde8f436ebd0c6cea42f346dd24b2ee6318e8649530749a3905483a46d98b6f7dc7b71576e867cef4f1f296eefcba17b7feeb361adb5d65aef4b5f0d7fefb72ff4dfbc2f89349149aff9c8d13e49cfe8de7b1e1608edd3fbe36eb38147b65fc3fff4a1bfe9e73fe9ed9b64f89f7edf3bfa1847a3a71ccc88f4fdce6f69604faa7fbf6ebbce42fa48fa6c7022937e6edb0ca7d97b5f890ceff37b9f1fe7aff43872261de6d8a6277da1dca4d3e72f3ce91c6254217d8c2aa3cfdb9bbed2e72883f4dd934c9bfbec7d5dbc60f665c0b44d2ebd936f3dc9bd00dfcf894228fd1bf5630dfb17fb30cbb22ccbf40d4c63e5f8f186dc56d3ab6fd40fad7c5aff0befbdd2ea1b98e621bf6a4a759c30c040c8c8c80cb1230aaebf3daedb75961a98e37ece3af363b991ba5ac5c6becd8d4ddf7ed3a1bcdffdfcec0bef73397276e4729055ec8b33fc3768a67f20d9d99471f70dfa1ca8717f628aefdfc757dfc8e88f70fdd1573f6e4f66c7a8927558f57c3cf17bb87e1562df9f5f98f7c46662153c78c216c030063294a10a18a50f8184a1b23074e48051d271a6cc796362d80d19c6c47dc3eefad817670ef4b54dfb3047ce0eefdbc891b3e7dfc83e0462ff40b2bb0f67ca9dfa46f621f63fcccdc303a5e7217becef7cecee928e3125c4f48d4ef328a2f43a84d0910386a9542a954aa5d213e10246e98528e93c53e4cfcccca85cc030bd0a84113f7b50a59fbb347b343d7578e7cd44a1f090966ccd540d43cfae430879ddcc2866cfcd4cc91676a3ddcc9b52f79df6213ee44227df6bfa444f2bc494fa9a0bbb7e18767d3dc4aedff3ba5962ded4bce48fb7bce5add6bd37376ee32cb1c65f3ec4852c8ff7ecaa69df8b83f5e7eb8783d9bef9a5cff4da17770c1cc3411b4a4db381daf65f1c8c11826c3f7c79a16388bc0d1fa3888e0edaf09ee360f59c1a6c4947cff196b3c43d6f7cded02087b1c7069c3c830e221fae947c08be367b5656c0121d829ef3251da23a4fb141c794e7784afdeed6a0c34b837e9968fb790d9e343d3d3d28d49d5bd313c7c1fa9a2947cd1b17a87054f76709d3fe85977c899e34d366d6bc6a4987579baf79e3029ca99aaf9ed5aeafbdfdf91a32859c85e4ebcfa149348b649c05f5f5e98cb38c7c7daa721691af3f7b66ca5fe10df3ec893fdbfedce12ca1e7ac9ce57efda99a34b326c7c68f71d1c15789f657984bfa95e76b061d461fcf711d1e678923ad3e26f9b4477ddac847af83f62bbd9df334a7160ab135d3dbd74ca592b53ea3fd922d653f1cb437ecfc4ada93488fb7efc6fa79be3620bb2f9c3c1fde174f511831a3a8b52855d7a035255e009f1e34aca2eded48c272ce39c10dcec7aac8f3bf5b1d7bea29f3de9cc39ce3ca9cd3e95311e90e307d4c5f76c8f98512d871c7a6f7b32fb1f83976fc70d45df54b125ff8819eed2e778027a59ebd67efdacbf9852bf6ccae7d8c3ea50eea9de34a19445dbc47203a14d24546cab3d2c2c5cb8b183e63402e871c3bc6a812e28d9f723139e4d831be6304b37714142afbec31e5918fa52e89895bea12ee6b78fcbe1088153b931a8eb9b5ec0b5776a4588c0eb4055cb36d5f0d1cc341f9f7ea8c69f08c8e1ce69f10cfec2aa5941ad14cc5df34a25d69587f3c686f31b5a1f6b67dfee24b12d205667bd9517f9c65fbd9f5f117c341d3be7bd32f4619c5e4a9d8d56fe1c253dd57cfeb36fbb2e3decb711cc7c92f7c0f046dfc36933936febcb5b50f47dd997e4942b2e0acc63599632df84be99c31f08d46ea9f713aa06947f0e00471bfd4c179e7bfa350234476e4bf2cb0cc590ec2445f4cffee67d77eae6d6c1bd33fd05da5f021c5cdaeba867c7fff152464901016b8787911c34483688cb0e5db02dfc191467f4951804940484e648f1d26fdf048c211855213539e99ce7712dc80070a27fcecdf118a26ec6cdf3c657adb377afa8534942aeeef7d7aef772f8dc8b819dabe5ccc7dfc38723018cac169922d0783cdec5f4c8fee9a70db6655499a1964e32fab42d674980374961ae9cdde86726734f0ceb43402570e2673cf8c902c6a7dab43fae6dbccea536ab919f6adc51c203919b68a2aaa20c30b97f6632603db158687ea6168ef1f0f4d0bb1c10072bf72395c6cdf2f717aef47eed6af9b3a11f1470cd31ba53bbe97601f1fff86611c7f1bd6a18d6dbf38e8df420839ccdb376ac50d7c9c8391cfe5a055018bc17a6331b917cbd86b4f63dbf7318eb9b19096607a46dd17c3a890a5dea8b3dc59de06413bf2d8c0c706acd6fe1d795aab8da79591b2b71219fef67efd58a97d19e903e2767feb2fb1a73f5820cc11f70b771d3aa64bb0c7c05d35382710704469a9f668471e1b0c6dec9bdfaf4f23ad5f09b55f097dec4be8a531aac81cfc25a69f8d2d7faf7daaef839bee5872f50c705b0d3a08ee1da90ecad3c78bc847b473632e1ca42d367541e77b0c6789893bf4f0f1276e941065c729c418638c1ef0288b281a42869f6d6f78b7ccc1dc5a69bda18192fa210a9c6f7549d533c05ddfc4c17943ee7b758ccbe5e0dbfe8f4ccbb46c25b7132039749cea38de446cc9be46fd9b7d37bc5de9cdec0e3cfc2077d53d38e834dd3623dea6dc9c76618e9327327dfa3d3a38a8fd7d5ba2e91960ec01295e22fbe7ec7892e3ca6c0638a28498b5d8cecdf9394cdb1f6db2f4dae9454e586403b2daa63f3d4615c1a3238431e06cfa580cee6f3d7b7bcd89ed35218cc1669b5e2b95de745f0e8108570ed18e47d891270767e36f93273db2b7bbd79b8c2d1144d1df179ade861a6a2841399dbefbd2084a84e451a817f94212540947a3542a8994444ea7d3498484a3717a11fd43ca469d45bb21fa0c2424a41111912b77e9b13fe9914bdf3d7596922ef9816ed3dff0b6e935ed4d3ee3a0fd1147c3f427fd83dca527713994b449bb4a009b8c2df6c40539f49a006cd88e45650fdbd916db2f592672487db6b51f5216a653c4961b5268bfdcf64b1c0dfad874d19fd812b77d1237833ef6a4ff416e920e495f7a1a85b3841a15b2bd7dda833337430e8188d6bebffdf6980c8d355312284e96656dfbdb0990dcd5d0de145b44debe2b115b464e1fea47be1bde1ef9d317a2f48816f9e4e6381aa6d7843086996d7aedb16fa1f65e33c374d2be1f560edad7c21256c104d05e85cb01a76c32b6c898d2e390fba45de5604987a68f31751c27c354b2df0913b4a686af6c1cb45fd2274ce4d06b6656261b67a961fad2dbe74abf71324ca5026c32b6e450c3691cb48f436e579d309183ace28495439fd9d6bee3d8d0fbecedfb8db3d4f05ebefd8d9b21c3931a4fbf32ceda61fa3aa85d7d6fd818f7210d1b3b6decb9989240778b2ddb635815199bc1defb777b3149f90baf8e6dbf7be133ce32dfd2501d31c5fe1128d8f6391d66aac4b64f67a28ad558eff01b076ba67d477daf4f9d05a852a012ec6509a66780bac5ce5b5a6befd5d849fe178436dd530613b21deb59b18382e56cff2da630d654c188dfc25311c64fdab35d0a61ac99922f4ff1bf423bbcd965502a07dae1a53d436c1aa99d86891c831006209aed4f83ec380bfddf2bba9a1a7450d2d57619cc0728023ef4a001650c738f0ec26c1fdeed9b01229f8887111d01b2a74a9e27641e2ab25dad648d83155bc9b771b0aee8bbfd72ec28778d0d0773e3c6146bb1b115210af61bf69483c16a1445bbf280e0571e59e355f24c91c3c843810bd8d0000effb85df7e060ed41c6e0c159ae0ef5ffe50f624afd22ecfa554ab1eb141b2579783a19cc489781995f23ea256c11aed8ef29f566f33b29826ac71851a83be79c73d6772901d4c44bc8c63611772c9209c23553eacb48959c71b04a14c494faae2632905d39691355ea63db0413398cae96cdae2f5d7f6fceb255ffde9c654dc5a40ff562d5559790fd65fd47a92d76fd1d7f00c58f1da2b0adc1207f5b6ee9241ee4d4d7c1c13ac5aeb57a615708c494fa51a5d29f2f71ead719ec2bf6b2a6beb4a9400441f495c4a8cf1303ab52474ca97fc4ae9f055ac8f6e773313585664aa5f4865d754883a6c3934968a65c2553eaa350395f3a9a8fa2922e7304910431d57df546421b76e82afbcd08da9fdc67ed3b372786611886330e6662179b1856391ca06dc3363b1661479e18b4b613aa9d37f6dc6fdc6fdd6fdc63d8db16f79b7d8c7b6c065446e9cba669a8df9a76e272a0f43b4db7d3090c7228b78f7c02835c0387dc35700d2524a8919193c9c4c9002f0d9eec71e2639be17dd8c61f7d4b8ee32ab7d5adbe98bb7e97866dfb7e050a2534d91bfd7829a534973898adee1707e787eed3c3fd165bba9f8fbb4f6e9fe2ab03b6e7385dd295705f7fc4c9d8bed3dc731ca79da586ed71968d9b2125e700ee77c7c9b08f6ddbe34cff7469d8f4c37cc3a68f1fdbd0bdd73d8759bbbdf75dc7d1e07ee37edbde7258b7759cb7e1dfb6ef52eef33da3db980c6f632f9cfa0e3fe0dd3777f79df4c871d799f13b7743ee1320397c79917f7e0cfbf35f62cbe8e72720b690fe8697df1bd9c730ec4933ec08abf1616e06f7d86b9c0cabb7c7f49633b3231676e47162b53100b6e82c255b54911f77c86d2963ee6b63656eb9e9e30d6bd9b6d5ce6fe69da7c30f6cabef47b14a67170ab1af835b9cb4ebb6e7367db5977d5f3b7cb1b1b2f5053229038e2821cd31652628d888e4064f5f5bedef8df306d1e44b3ebca9191440f22e327731676c72486522957116192f8db3d48fa1359ad2ccc854b1aafef8575e77285b386a0b0757ac384853636ce11a103796dbcdb2ccdd3f26ce2f7c096ceaf53ea629902ceedb2ff49f7d378c2fa55403c2a24ec480b5b17fcc7de4d9dbe29d72cef91c07332f307578a5d62f7087d1db5022231f1f453a7dbf47fe85b7c856b4d091a76ee1a08cc867227f45defe749919b3fb917ee562ea8e5185686413f9110e66e4f11cd9fe919331b2517ffa303f3d96db0b27d934dbf425a522df7ccc8978ef32b10587e53f3c87a6a8cc4ca9df85b713f9b8f738ee473893831cf72f7ce4a33894e560a553d39bf9a1cb84f466d777199ff1cafdfcb8a732336532c39374f85903c5941c81f2048a291368d7377131bfb18ffec8877d7e91c7270e46c484fd29b660778b2cf467e9e7933898e98dde1f670ec64d5a867b69e4710ea0efae4b4625de7132e88f7489eb195a0291f40db93d19a4678fa7d48f07c963248fef36439540d87b581e8dbcc7f22863de1442f24d1f4fc1b4d39c00c92195a133a31a1e95892da4af4f87882da5af8f4ba3f73ed22777a6f44b5fdca419b7f19583d5d34ee3e0e4f1386590b3643abcf76a5408663f332a7375081639384429a594fa8ccb48a12532a7b50fa59046451ed991e7c78e1dbaa8590b8cb2b1c9c61b7e8a43fc949b65d497da1cbbaab5b20282ffd965a6c81270f1428777f143b557b4e0284d9990bc024a3e047dc6e496bc0c8996b9becaf5734c1e0016e0b83d82d8838951c583392942a60e6a0e53e9710ac126997cb718b30cfba8695f65fd248da65ded629867d288ecde2cc6cf7ed78a6559ad59fdb20ffbfbd558312570a90c597e75aae3a3802bea108c65d8bd711cc6d6e2b971bb7eadf13d3a1292457c0c8b31c61ae37dfbf273d5acf7daea5acc36beae70507e116ac562c4ac7d49ebe6e455a2269445b2887b6cabc6400e522b335f16f1c8457bfe943259adeeee980ac9227bfaf7b3ba66126d1c670efa821cce705acbcda08f3f863ec69f634b2c0d205954ed4e98a2b5bf1da198c266c6eb333338f658e52c410ed2af30b58aa8e252a68666c6c12672385f56115ba2946165ff97f3060c987edd951e11a27e7b9478c6c81c9b525abf1c9b5a6d00c9624ace3d0b1a4593dd1d9426a0bcee2a24a40b07236b1c33c9a4e45e525cf4c82ef6bc9bdcc13364dbf3f03efee02187204f4a29a5d9d38cd2eca5fcecfbb1b3bf38f4b3bfd9ceb21ff466138de12934860ef4458c2a4bbaefb8b7cf615f8863e32e7beebb0e6636b6ade9acdee855d28a84673fa2e83c824ca97ba4d672a9cee2ee8be129aebd9c9266628c3f266eac4bb6af81bf6e4fb76d237e1bdbbeda7ee18c42e810709ea939e78cb100612cc28e3c60d65a1b630174a881ad590cc3300cc3b0ccffc5a41ad8612644adb55a6badb5d6d28f31250462ce39fd634cc9b656c4080c1d73ce39e79ceeeed80b51b50eff4a29a5f4674440f633887880ece7176301c21c3bcc5ed3fe85eb180b90bdfc7ab796fd7f4160af4308151e4ca107145b3802132e1855881c3c96000231c060053e8640028c39e7d43a300d44d325999e016e94596badb5d627c2058cfa3a82b85fc4080c1d4260d65a6bad7d225cc0b0af2308ad054bd4400528e001116e60047185882e161974f8044951062030f24c91f7de7befbd56eb889e4275640cc3300cc39e081730302d848e1c30aad641e79c73cef944b88031b58e1c5308eb2620d806db504a63a434527979eebd37cb62501cf415bbbed4218e19a34679ac662fcfad148a8a6195ee50282a8f83b2deec4601049d431ca456526c5e01c3e615b039640ec1ead7cf29d5a466855aab4dae3b5e76bcec7849e265f592c4cb8e971d3a684d9595ae1c8c185d559a825aa90dada129a895aee8aa7e5e5b80516406308e31b2602c3d46efa46b1a55a2f42143085699c990e36314b95130e99de472985b7e6cb1a39e4dc01125cc928789a03d3db9637e4f8f6ecac92aab4f7975c0ba423a393b42b1230f0d866cac016539c4417fc9c55c1fd9ceb87648c32cf5c913a1dd549eba403385da99979dd9fe1768a6e490574dc9219ee2ef1bd5f2a5852c35d5f10a7d01fb16e947a9a98e1bd4e00635c03ff14f1c51702bcb9f76c3d913f9e7cf00f7f4cd5a6ced83dae77e1fc5ced77489fce8a0fdca39603e2ed19e93311f830efe20b7a657f030f53c8941c6a812dbcbede5f612b77274d0c6383a8141aeff440e7f63255d49a76780fbf13f76314c3f48238e77f4d83f0a185be4bb2e497180fcad04bffc4d97603d03dcf8b1067f901beb153c7caea9fb3e8941ae4f7fea1dba436ddcb3bfef4f3549affdfb51699f7ef2ed17ce1a079241a78dacf2318c6231554e77bf9b574a2b9dd279e0e822d78f39eabb5d14e0f8ece747213656b3cfbefaf8d6ec4aebbd5fdc58bd39a60d76efc5deda38dfc459b62c1d65533d2b0f7eeca035bbbe54ede5eb5f30afa05cfc0edd7457120d7228653cae8c7c0886df7d88ca52a6b512858ce830f69874187750b9a4c3950bde3bb590833551f0c0c4440315011595a05028140a85222121791212940847830485327992372141a11e85426dd4dc316fa60a5a39acb90212122d67d8f9d9d9d9f911399d4ea7d3e98442a11e853a9d381aa8d389e4514f823a9dfe743a119d888abc603369e60c1850288ddd04b9b9b909326232994c2693e9743afde9643271344e2613ea4f8f3a6993c9e463f2711d41e8059484d3494bdf229f6f8cc9603b30194c06db411a8d46a3d168643299de641a95381aa6d1e8f4a63fc93dd2a3d1286794a303240a9e204e984c1aa8080808a8a89473ce39e7d16834ca248ec62867d38fde34caf973ce7267a7f11a2f82e5372b1fc168a44b9f6fd2cecfcecece4fc7711cc7715ccef973e6461c8dcc71a3cf3fca1cf71cc7bd36f71a32852d7fa668a80a39eb9b2037373741bc6ddbb66ddb388edb324783dbb6fcdc674e6fdbc6e37d726f3c3d3d007209f1418c2abebbcf37ae32754795a93bb42ccbb22ccbb66ddbb22ce37e7b6ed3599665f206472a1144e7876cc1b669a0222020a0227cefbdf7de2ccbb2db7134b27be5bd454552260c2b59235563c8322d7d6b3b3f3b3b3b3f18a594524aefbd7f2fe5381a9752492975c59f28c5d090a025dc9b20d2e50a7954cef366cf207b3eb6b5d65a6ba5943ea575e368d05a6b6d515bb828c0963f85ec081d6025aac8016c2f14fb32b12f937d6d170977e5de9d294488a7aebb747729c8edcfd1a8faba47208b853d1fc320fbbf84a0c8ec49b4a745c29e1f71904370afd850a0102133a546cd5e177907d5f342eccbe572250b99b22f97b88008cabf42668a01f10ab95310c1b5596cb2c5090eb27d613a1462539de5ec78926302522ae10b2c1e3e7cf8f0e1c3870f1f3e7cf860b158ac1e3d7af4e8d1a3478f1e3d7af460b1582c164b2583d5f6a6fbfcdfa2ca8cf357e644c595a9a7c7168fd2cb21a5f700479450026dfad8e7f5278ab67c29ed10594a7da58ca462d3afd145fe4816f4634b11b924876bb7bfe468485d22e3beff762d276a821be547ba7c9105fd93a8427f36adfaa38a0bf2fc2a2329053f47f7af727b34a118f6986395d65a5f5669a910b2b5e73efeca0ab0071f3c35808fb3e563779cabeca9d99a35bacce9922c6add1c50de80561363b462f6661adeb8cecb2312ddd97463d349640445324389090d35d8f09ff61bee748a714033e5866db65cd2c5dbb939bb76cd7263d1a8b41a1d9185b77343f49b08767d6fc7baaccbfe786a05b8fd00be0d67bb7150263bebfe70511d0f00ef7937bc875d2e97cb43790f13117927efe1562ba8e5e9f7b08f8ff7bd8757decaf3fe3daca3e3d9f09e46444444e4d5f01eb6f168784f73b95c2ecfe43d6d68c82b794f6bb55a2d6f86f7341f1f8fe43d6de5799e877a4fd3d1f146decb888888883c91f7341b1beff45ee672b95c9ee9bd6c68c82bbd97b55aad96477a2ff3912caa8f377a2f5bad3ccff3bc4cc7bb2e97cb35e43d06a2cb1dcaefbd069c257bef6dc416dc6ab5bce7c153b73553aa8ff732a2cbf5f156de475f792befae3ceebdf99e8e47e41179449ef7dad5f13c4be479d7c6f3bebb8183f10030146e32ae1b5e903d141791cbe522daf5bf934ee3960f6ee1166ef9ecfaff718f575807aff00aafb0ceae6f830d5b0d44d886888808dbecfa35d0804d349736e4692e4d73759a4b1bdaf569d066d05a9a8fd76ab57c767d13920ca5ad341d4f5b69daaad3563abb7ec9c815c988341b2f23d2322222cd66d79fe1644d992b1bf2329796b95c43bb3e49092365adccc7cb5a592b6b653ebb3e6af4825c7768a3c856994eb6ca56d92ad3f9bc9728cff374d8d5d4b8865c2ed7d0de187ff8b3118c71cba7d56af9ecfaf89bcf89ccbbba3a77755777757576fd39ff9e660036edb37bb6662a4feb922ceae73cbf8db5e16caaedcb33b5d158d78e6a639bb0eb63550e2fd125ba459e12c06f330e664f713c656ff890fe00c0875427001fd220bb7b7ae32ce0774f95882e7608e543da82dd3ddd115b4ebeb32c1655b1680d8bae8a9ce5c3105d6c4fcf1888628bfef19455a982860cb99ce59322ba581c9c25f8c4161b5a9ec25eaf9d57cf4b888eb398c820bad8193b43839cd852c3773f6d3c85f5f4cc1d3d383d3f76f7b3c65968f8eee711d1050b0a9a24d8dd4f9ad85222e4298cc522624d19d61067415921ba603c3c5878c59619a2f014a6f2f909da1dc90fa20b86831384ddbdf3c49691efeaeb95f36a99be7b5f22ba6033ee82ddbddfc41691efde673c557b9cc6a63b9121bad4a0a02a8a624b09c85395c57ab184581d8909d1a5f2f03861772f5db165f4ddcb209eaa2ac9a38aa27bd99235882e1547b2a83836d85dfeeee5ca53f4f59237af9cdd6927d8ddcb1244973a83c353b467a6d4ef290adac2ee1e03d18506c91a0d388bf7dddb882df3958158ddf3e029ca9a29f57988b0bb97115d288f6451ffa553ede02cf8bbd721b6d8efc200ecce73f7dc37f70dd185e2a0c4169a9aafee6574a1339245fdaefb110ea6dbd9a075d9a10068eb0280b6ae1bb475a1e8a15dffa489ecbd65f558d65b9665f5ecfa25dd7d566571aceaadcaaa2cceae4fb2813379d99997c9eb557f54c346434f508f494f4fd0ae9f4b300a63f1b09e67d7f766d04830158683a950980a536138bb7e3792995eaf17f7c266767d4ee49e7a82ba1e530fd713b4eb6f254baa2c9e4e87dbcdae2c166b846d555571509caeaa4e5715ceaeafe5aad1579dd9347dd1177dd5995d3f6b22d31dda28684f504f4f4fd0aeef65b27633bb76332c1e168bc5b3eb4ffbcdc777cec91151150e55511555e1ecfaf6a37f652cd5213744009c8c948d2f1724804dfbb0ed1ff7da52f26ccd1437c4537ca40b0785c882ae7cb52acadcd06c7152882a75faecfa9a038a2a38399c3f32f7e6acfa38a08f0b72b0be3ca57e279339a0f9338166508c6f6e08d1b5b14496c8125d1b92ebd9782b4f09801b12f4fae48a4f06cd7b89664a952eb8485ea2d7257ae122faf9df8f1b324334539baa48ba6c434416f5eb0d0a95e78d2d5b08a24afd10972107dfae3799a8521fe364faa1b79363f676bc1d1b85b3841c908f15e22c1c1017e4a994afcf0de184b8a1016c3382d8fb6723883d07345338660c9505ec43eeb56be62bb84daa6c9bd4210ee0eb663ca53efe3a199999e2803ca5fe26237362831cf24879fbf63b958acb19556a375ffa38585f009f74794a65ed7045ca87f294faf77b076bad2b421c50fde78038a09327b6194fcd95ccb69ad3ea1417c401619ae38076fdcd59a82704c53700041c51f00dc94aa6c838e7ca7ceca37b5b1520ad6a845de7265f31b511a1ec1a57442c7f24900c5ad975743f9c425328b6482ae343285a4357b3c83f938312e6becf0e5d5527109db199430e4e224fa9ae71b65167c122f69bab3ee26c033fb7dc3d3121a94beed3f739a4aa3175633d85426ed7bf01a497a8342df675450dd5cc0c00000000c314000030140a078442b14830a48abaea0314800c8aa0486e4e9a4ac32085718c314429648c218010300002203020330500aa4cc2d4c4ff20fa4f14b45f1ca5da28d899662d62548a76288f915e7e4ad1b4380d4542927107379583247665cd283c11dc10f1a3b572b4e2d9a05c1c2d3c9a98806dc228ca3a8206b1ca4266664c32515c69216f7a560a258994680653c669b8688ed3d3ee703727103cfac4a18ef61050f3b930134f26c9e77f403ecb59432c642533372691a84efa9019bd954669725a1421a40b29054bdc89af2f8aa7713a167678d349a86745c1ab6d66872eeaa81c5abedda3597beeffff853300e15457f531c3d93203d16fa49e17b3503654478904e894763e5d6feb8e36709a6bf813ea5e8a509e120757bbb39a34811b05e78ba2b6176b146b59e4cdc64a53da911e72ae6f2bb37fbec3895ca0b92bf4a550e41349eee614089562f4b7666c1c3b3066546b4040e55dc0bc7a0b984c85ad41d6b2609c8a9562d5260de46e7e4bd0882085dac209f18235cac1143ceae980e57bb751cc21966ddc3623d4324d5c99e55e0e886c86fa35c40aab5c53b1099dd2d08aaccf9042efd86f2e8a1151b07121afc521844a43d3ddec75b100351080b5395acbc59934a13f82831b9108a3e5f2a7485e7390877d067fdad348e9a725daaee9d6e3a129b230fd8de8b9d939a92747ddd354ec2b92ed8e4d3c0aef2ffb86f8814fd2b4d4b56da87fcf9577211e9cb63b24a489a361ec79a320bbc01b669115a649ac445519ea42d633135646276f08d11440022783eec2c94927d6bd92d346b3a6e1ee92491bcc43b9e0cdc067e487b78b39c7ce04c0cf563cbba0f97f2fc9c536c83a167113639289eea409b9d34f699426a745e0ab390161c291b982f37f48cc54e3e52262439528dd99ccb616176c6f69712374490c6e6a11becd0e2826c4ef546c31184ca361a76cb17a4a90ca0fbfe37495088947334ad506e78112abbd0596ac0c46f8032abc16b343e68b216647b255baf689ae40ae5cae504b34952db3478e850afbc9aa4eba043bd883591254b28f7e25dfdfa5ed8d1236ab2d56825c4958f2808a397119e93e02426699976627cc3042fb23cce808352407f2c626d8cbdd510875d3c93cd3a52b6a3a7ce8c59c8801656d2d883a3c7d292c924ac416a85d31dc3f5dba8fb2b9e995c46b5a295145c099fbd7066e32d7122aa92703320c2e7562b0c69022041c25fa5af03e23e9c844468ab116c84e9d71d9afda4ca5018732b4f6b1e0703fac06b48f2c12beaa185c8758d58530f8ae3cebc40f7011c0a98b974690f3d1eef4694fce4c054427607bca075e2d5aefb801b014baa11ae4d82a0f372560863e08bbbc019fd7e1226cf557999886424a64c75686aca49c26f50b100c951f904ed1ebebfd0995c4610605c70378c919b4dfe092e361bee1b01da25bd5a1429341ff0f2b593c4bf25451a1ff375b1204e747709ef60abadfa1c20d7414c31bb1890f0a598d784630362dd1009b40867c83b1053b05517aa4f450d603ec2b3ad709669b31f5089728ac25bd1e490d29537c15f6f2e3fdbd65ff62343742af20fd483d857a44f416ec15a9a750cf88de89bd46e8fe8edde7e87e91de7f9ce816d28fa05fb037528f423d11bd0af644ea59a827a2c7c45e21749f63f747745f9af749907d0ad2be53c47ecbf3f2c66838951df3e6ad4ae93138e5e7ad6a3fb36444be3c358f6a27b00105418e14a88099889d2731e7029c8740b6aabe1dd107ef96465ba6f503558781f29cd109fd8674db0bf408379dff48f66716c2469ee376d02e6522cca2551c2baaae1501a9a6a6324a4d5c19cf5598ed1841f179844da0f61b796ccc75782c6549d2beeff6330d92c30939ad09cbd947cc888c1062ca16cb71143f6efbd24d9fdf4354ae8111bdf1f40b0bfa1bff27db8f2efdf5e01d69ac882c3b783d5858231536cfedffc1b87f85364a4c89e53e8f7fa160b47cf95d7872de1b5ca516454008847c2e69436558780e72259a1aac29d90147dd2352ddfbac69c042ced41a345a840b1ddf5d221c46e29c9e8268165e9636df6f0270b9faac968b7f58998fd3dcff777f684368521aa1c6ed3d59305ab6829426e5aa65cc9c900c79126cfd6416fa0168c44868fcf5e5167ba030daa369b0955e274b894740c1dbc22aeee6ae94dc33b716b6fcfa7ce53b1d66584a26daa07edb6af7ddb0ed902c9cfa9857f02ed86c1c40756ced888ae5332d69fe499292486de148e0acc42072f1d01e8f84b622d708340119cdfed39a18783b47588b00aaded99e7a7acfaab0808217efb93ffa2d7fa085c735b64b8c2829b427d0b500b4eccd4bafdb560f0b300d10bbb7d1fedff5de6f0993e5adc167601f8ed4def4c96ad1d37e310677c32ef5c6c31ad3f16862bc35bd25d7d07f723e6275adf528e00be11fd1b2a6a15f3d9529dd1a81e15ee5dd3404e10d8015be41b08798c2f629fc35a1888a4a7ae111b0dfbb6e19d45766f5c8b46e32ab43a6f59accc151b75cda96d7bc93aa1e32ac974cf59551bd64aaa78cea2703ae6725237f8305042339ff9a78917074193c2763e160364dfd04a031c72602b7d332de3c17d58c93891c55de3ed0b3ac1723c00b5b7aae892073bfcea2738a11c1840381b32902aae29ea3dadebe52c743ff627c1456d8973c53fc70cd3ed2e9f5da7f5e4a89b5d9c2736b85cbb3d14f9ce9d4cd65165d835ab6a28524f79b8b91475f2833850d2c9298c2ca60146c313d13fc33bca9543500bf588e1b5825d3a798bb288b30556909d872658cb39677e9cad956de7e53a92d951d41f73d19610a63dc83dee8076bf03f2f95194c619c9fbc5cec304538ddfdcb3d281c17b0ffa12899c314c6f881c368bd66472b948029d39a6ced014ea5c415ff45a610608ae9f89712f9c114e6fd9335ef6fe9e1ef8745075363cd9b6c4d4517ee0753a9e42fbfd97fa637bf0a3f5d616a14fdc70679c21426fa99fae5bf69f2efe44e13a646b27f16d8b3ef5b8802d263390703c90b11aab9e885a9d1fc9fcac8095398f00fd4467f48c1bf3921cd616a80d158053422624713a65ee99fa8b3fe91de7f7546bac3d428facf6d7283294cf4b3ebf77fa789df999b8e3035929ff31149c014769b0de86bbe28eccaa050d874c25198fa5b6e4318890c6e9db17ba4841589fe1b47d553e74931834b25981a1dd00de8eb926ddaec3d6a5f7acd0ac08129030db83083a9c84a0017a507a6d21c2e75e750442c495e4f3c61d49b77e210a6d2b37335b3a52d89dffa1efd74533acb5ea35f8286d874b09198f77330c5b5e64173a683523e4d75564a32a406c6950c064b8d5924d89a9a439a593e06dd143a7522afd20c17dc208b9b2450a5119452a4e8a682879dcad6a1f07451995567797d3da226705588a906c32ef0871e03f5341f8637f28b630868e586148dcca9e28a61e2d7bc5652889250038320bf87e71b1c1a1923d9e3c45757e4dea27b0bed596b55c68c84c96748656ae92f65310509c30c57e9ab5897110b028b948834abd4cc2098280354d3be5dd9fdbcf665e032728bb5db23fde1b5da1591331f6f91234dc49947e73b1a2a9c5935c2ca62b53220038a05b75c483ce2698a0be5cb01b786584956c11c302ebd82b49d21d1720a8b8cab8657b48afcafd770b5845f095561230e67ea413087bd428cfe7b84ffdfa31ffd267eab1266754076623d5262202e87690061b06a31a40f1e174a2c3b26bf4a782f9b1a022e9fce400dfe2f7d284cdbc0ea9aee367e33c34f9566703b618136bfe2d379cf41eacd718cd3be2c7eb0016ff65d73e4f1c193ba24e80505666af26e2d404b0df61357016ff5367f0200a4c0ee9670e4606e850255089180f2a4a8c7d6ea93a734862892b21fde5da46d8a4db181ffc6b8f15461e285c51582ece490585fc8301479fb8b9181ee30eb5f9af5fce72f2bb0ca32faa374cf2b33f83f8aa140b186ae9b4fb543c7055b8a3ed249f3291d0fbb778685ad612a35eb457c19a6eacac1b685ca2ec9fc456fc71c28a2f74d057ea102184a92dc208d0a9e05feee401e87895bef3925022d9f4b3dcd5fcd8cf10e834886a7c1fe4819953a3453ae0c2800f8841ea1350661a07e13a56fda621045f2e29cce217824e5c111af0ba820039477f4c1ada78cbf7835ed8e6d9f27356cacd9022a9df9091abae0d386ab932ef89802359cdf474e64a6c1fb1e26ab350df7a83e9ae8d6f1b54e93667a630a2bc8f1682205ef3e791845569e4f664ed80cbcd4d65dd2720a6e20e76cb991b83e03a475b97c9e507697f7fbab09b8fbe2da8aff50b87231a7902a5388db273a92666ca7a675be58fb117cc29bf40e8329eac093c66afca4528a4648d9a0b070e7683b15564dda322b999c9610e9c80856fe6d27904c4a9d611d51c101fdc7c3654577c9e799311ac57a1d72fbc8000fa08ac679524b52d0f827497a0c4f4d3a3291ac9c9616e9c8211ac07f6b1a58b19889a39b84eab2754a85d2892ade0641449d47e858e06b48f65903f4bc07c47db89d49856f861849adae7992dab84b432b35196a638d1b8d589bbb1aa456a2f9512a4d37a87c4a4cad5220850b37ba22665ace7a49adcdd5eaa683056f1c4b62d18e88aa830ad2f4190911f2f08fef3e4d6b48a5f28f0d638df561e65cadb8b30f761ec15164ac0a9e0826b2f833d7dcab0558aa2f049c8a42cc1293fa4c747131ebe1c68d8498454f858ec861629e0902ef1cae4f2595db1bbb33d86ba0979849ab7692e07ad19dd207630b2cd506ab3ccbdbb7320eaa413db7ce35c2d7e8958c481eee1f135af8fe3011844944b8c3b044dfe49ec9ef4a5339f8ce6630e8d5247b5b9614cbbc6870199bb437d4122bf1da8e2903dec54b66fd8ea0707072ba88fce5c0af13d88bd038272443aebefeec0ad74a5702fe97b93be88d1921d34d8b40b3018809e49642010c60c66ef7c74eb733b3dab55b07717553148a826abf5cc30fba514c77090d1121c327d16a870c9c42d6ed6413a2e6a9573a82900cc744a75383fbfce1cf391be069357486ac8832c831bd87a2efcff00f826b153adcef14e9ae73da2a9363c91df62126919c56bcbc99dbcd4cb4fa15d3ef6a5987a6079de0a1d25bdfb40f088cab69ed20a32788c4c9fca40392d8325f7192c13636b9e404977ecf2a14443e464b5a0b24d2f20c3fc05e247b42a386de192b323d9572355050436e7942fae7dace428f1f4938e0bfdef990e6cc4757b10a97a7abd3a48818443e2ac088dc4c401463ddc50fcb4e8a4ccd7735ada8f721087c5aa0942f35370706030b2105f449a00a58849c4bf02a6c09d0a247d5093c5bc5cedccbebb3ec3cdbcaa61b94676a9d3cd0d14a8184d7a6894f96096db8ccd8a865f5892bdaa4be856c2364766c0814469e6e3287597033f445464cdab4a2b054f0f87e760e6fe252c616e7ad3feccd87637faf2fcd866f8fe60af6e19b8c1cc4c31d14b6df84460b6e3b4c6d9925273226d25e80814ccd5afef02d9e621ef1404e8577447056ff1eb2fbd346676d7cad49ddaa36609117bee2cd9995cf51f6d4a8417d353f2b80976696096f3fbb9dee2aedd220618fd56b6fa8c796db16cfae62065dbe6759971d109ac1c02eb940fadf1535f993e0ef74cd76a555f0d659632f2909c24112099c33472c2303e861034444fdf5513b0d6c17bd3da8b8b14de3631139381c063acaf15a4d12472d1a402b1a6346d4dce38c35f3b6ea9af320df5f08488748406fa39280cc1b71ae65a4adb5c13d9704ffd644c7a7b03822c45b1723907ad1ad99e7db161ba9677acb43e7466df21e6874d049692308ca724c38ac21670c4dc1570ba4dc6559b42c5ddf2b1dbc60b76532e027e86656346693fd2d7c822934c097a0c46e9ac13dfe1cdf7e2e4484fd2961f5cbf2f118a959059d082ea1967d001d5aff2b152206cf74a2f940bae4fa3645998dac3851e33df4fe8c18c1d8383990cf542fdfa659e0123cab4a61ec3621b665eb84ec75110686dd4c1088c8afc1ea891c9f6bea5f5b7a47d9139986963dbeaeb38a42926197ada1d63904ac8f7e68cb38f49cfbe0351e92852efbb294caa0c2314bd8dd264fa98ccdff227617bbc4f4d89843830ca76a46a0b6f093fc089c1cb50a4e0c92278e6c1d69d05b4647b5cf344f6388ed50f633b39875baa311a9080ab7f745bb806485fd70cd886894a11ecce0344c6269d2d82bc6dd018cd03a39a7fbb98a570a2f759598701c18fa88047b9b577f9fbd6fda5231b712b16fffae7e36333982d2dac344889e6b17c14626b00a6c306e88842f1ae76ff4b95b52b7383b2fce14509a260143d5986743bf4aeac9601286fb1af080525b22c863cebaf0bf20956bbd8759e29415e0599988ef16f86973ca1083f92ed97ce4f8dba087197d510de193bdd16b2a10286f7b2ba3062dbab2bcd000aabb5609ff3cab9693297eb868310c75c72fc08626ade83dddbbdd8f27dc6a6baa2a4e031ea8b93cbd4b55297c0f94d6a4f415b2ff55c0d63d2a1468ca620eb1618d66cdad68d1b1605b521619d51cfd13f8c3fc53200d026c4c071259f8ebe16779e2e44c35f5b296ab4ce04573eb8b3f23e61fe9ca9b5ab82b77a9bd5b3edf0f5b698fdebd0fcc610f700cc96dcb98958a8a269fd0bf61aa97f0db81000d53acced671170c908b7145de6ebb4f24393c1436267a68f6dd2638d7ee395cfca2c71cb16eeee81516a72e9bdc2d288948ae7cc40c3d6aa169b216cdb8d745cc2860ff26c1435ace0e41ea4dffbd26d75cb14e9f02412d2c8e8669c40abf537a65699a9501112b08d4cd425448d168db7c8a01a461c7b7194c534fe143bb494de30366ef5f6c900e8fb0129a9ba0fba1d6a72ac704c22affcaaee9090fc4568ca94bf0a3b2ebbf73fbd8dbe188a075133679d5ba3ad1644020bb56b23f50a318d3ccc95930b20d2f55b90dc94b160b5ed6a04baf34abe0311d629d8a0f29a2a170b11029b995db375c6057d155a7bc820f4e4bf8e1f61ba5c3e24974e4ab8d4a6af481414161e2eb0451569306f1758630ec865b567ba1c5f274f73a880385cf688877e1f1641de656d64c4c9f4e041d51cac0669a1c06ff91ad0aafb07dad0df00380ff9a5dc36d3118bf54fe59d030860a14ba788b1d18ac885467e01e553ebca95700b30f689108477b69658c35acb5ffdef37c0862c79455a09ff4bd4fb33e0a3bfccee495247986ad4037a7374030e2ffe41aa28839fd82a0de951070aaffb3418892ef0f792be3c0e9c21f8234106d1f7454ece9d80cd53fd08c72cab15f5ab6ba85cb179b0ae40351b78c7eee6b29a89c0657397d56d4e651d53ca361c2d56cb4562beaf822e0101afaa660d566b532829b4b5e3df99b3b50352bcd3fc10b234eadd304cd99d15214cbc636665091bfc67644cde5aac2e0951764763e8736b8e0339983e95b96f08a5e0421c6b3ea61ebb6756c66a73b827e5d01202d0fe32de93d3788c6efacfd5c84f272fbbc480f05e1c6ee3c081fc72e34cf97449982889463c4b94eaa4e9ca5b4b9f62960f6479b7d4fb4cfdb8fd0969d549878e2d0cc2b2dbcdbba5896bd4ad1ecc6dadd20775ae2d1e5477888fc5c834d145414775801b8e66c8153f61db6c493a485b8f1d76fe659f0989951cd85c654e5ee8517aa517fc51681425303b32b4cff68b7942bf1fb796c7b70ee42048cf9b2660d356ef6cd6efbdcf5217c83bc39c761122b3a157050ec5a1d74b05592e75c5fdde6e70f2594ee8b18640b762b58f720b4fbdd490bebb03fd6ba72a148d19491420a67c398000f83b4af0f182b1ea8d0428791304e57311fc0232256f1ab657d96ca2975e11add283f03e131113d8bf18a2844e5fe52715bb3db8725c53cfc7ca8258bf76fa103ff3248db168e30568fd092c775f5ca0cf5ebf6687be7e8aaef5d7c0a574f4e891dc453c5f5fb68c2ff9d1d8718b3007f2a2601a7686180c4093a383a6711b48b08e2e1c0b4f1077fc24bd5ce86e75e6a8a228f12c58e0f9eaa82cc7635efe665d34df8b5ee6ccf0302430c5a7a8482061694dd61179bf1b928ce8dabbf9c0527a8fdceab465fca939254b3b6f440626e1f137e857f158798b7fa4afa4d5b8a2e2a1411e211b4a053feb041908b80ac8129832ce85b1aa24213363eacb36c56d36aa08ccb20ae847a5c06e8d262aee349d970df85751a97744faa1e6883a7bb6f791234ce67f467ffe2bcc30aa21ff3120fbc3541135a3fddccdf058aecdc87aeea6d05f91882aad000d6c5d9d97a2c4bf2932c0657b5a4ee6c59282169c43615a7e15795c47e64526ecb26b36be03f403ac946cd6ed65d6ca6c69452ff44ae44a9ea8c4111bdd89eda315d030d0d5a7d2c07f80bca62503f423cb80b0b963390c3526e2db3adb01056506beded03ca8d585d58e4505f2adce2610e544ee3d9198d6a7227fcf16c15c7c1b488b3c7e54b8256c345c3daa8a4d9fe56314e675bc4c1305a670c1174128d908b0f6fc6d6e2c87775dd9fc14a56d66424a63619d6411be2378d91bb3a9043cdf63431adc7e60296958f97793eed1615852d30efe61110c8d9cd5c27e8ebb0b79c5b5aa9d59b255a89824d18259f5665e4af783b3d84e5d3321f63db20a6a4acd19bfe8afe0b387bf27692c8269cafeaf2eb635312ba7c0dafae0bfad48464887b8818a1d597cdf8be92ab324aa80303f628814d4610560cd8df3aea5ec357e8c227bccb072831ef7b4bb75c3d187291ac840550f37248812c73d5e0cfc260fbe8fa8d8c62e4029b9abb5c2fd92acc59c8373222d0aa97c7238173f13271c71ed699bd41d3509b9ddea23a7d8da703c78c60ce2160ddbdecb0329f21e4aa7c4f1083e242555558d2aa7fca7c2ef6b23055ef6d6458e3cba7542ce917cd45654c38e926a7ccbd0246a93c4865074e3125f90b277a8a94766601479929b9a86de5eed68e4f019c834af568e76f5d5350015198e5ef1584d87b5dbd80417cf4b22477329fa036c2aac4a86fa15c97c9f142819c97dd9f682863f664298829414362d787516b28daa772a0e0a84af0710c1b98a85047c33b3beea843705f9ca33d707b6d6210ceb238eaca2d6d3945a2c84c298ab07131cfa22bf97e20af094bdc6ddaac4883c5349572eb0a45e905048c52fb8c9bdb0bffabd7a03a2abbf076fd235ae0b89442f69833ecaba0d00ee4acb21e6be6abaa985110abf415e6bb58f345183c2ce116cea3b678ebccc6210aae422ba2370f17e38f749e71f88f54ce3162dcb15fcfc2632d7424332eb7d7e1c3f20e5688f8e67397c8660747c011b428ef3c2b87e378e4b23fe465a47fe8a4579044bdfcda0c0917e19236bdd45306d437b1ad13ccdd8f0fa60cc45da6892493038c5db298351b1f492fd2ccc4adf62d780a0400ed1a0730deaa8106423cde0e9d001b31850568b8fd140f7be9c466050720a219b4d52730fb2c9603b2e935a0eae6984b49b208d4a3d609f4c5e3fe7326486f87a13b6ecd87b75591096bf3b8d8f411ec71e28f81f7195fc598189b4742f3cc22907f57b7834f7869cfe8859f3d31d1d67c3512333175fea4420321271194ea8b7e19b536951032674a93e4dc1716098375b95bdcf00718a67974be723310267fb05ab7181f4b1a0f1321e91c934013d12898f6473aac4d1d920507e238671a5483be9b1286590182871827d1ad6d0000fc7f610eaad5ca98d3fb8e3b92abb1ffaf6a7840f00334eb2768c7cd18b058298587f1b1d55f2310e79d79b2fdf311acb8030b45ca2d8d0a1b0e7f19311db95ef5fd20f1ac6c34695e6d5e69f488089a4cc8d3f3c2f5ef05da43a08340946016ac18baf9af4c95dca0d4504e2a0e251a6eef7deaa6c8e087cb65885974bb27ca44c5ed8d1991f06b3b88319cfe379946bc406151549a3d605822e7ba7f9c98400a96fd59daa305e325fd991c42979fd06abccc6ed638b3d245dc1443a215dde51458e3d453e6cd1c07cce39de3d41a74eb012c1e79db7e167c64022066d4e5aead247a11e6629737c570031113a648b6418b170bd9a896d6b9dff13d22fe55e98c797ad0f5905f8ca1f20a1b20207d513880556a7dbab31e7f0bef61498f76453c4891acc046f704d02748e79d0e0695b3f972b813d200ebf91d4c3f4d990eaf1fda99e6dab61d1a73d86ef1014acb01e87dd6fb9fa2e8acfd821750da3099da5140488315699f221ec5df1a577610857bd582da9b1500283a60ddb6934545e57a32afc9074d0552fe7be639e42569c0b54e6e98547767ea3d27dfa0980649da086c5447113c47df859c77e0688bd47f35c3bfdee4f28ca554b8d49646090fefd07f0b9a2154bdfee638ee8619d57f2e2a0d5725c6173b0ee4045ae7b67aae5c7273dcb84da980e3b653ff484d3a4f5e4dedd0ab127a9d21209a01e2f53204a9383b6b6d459487fa53f83eaa2c01991da43ca0e00be6d0576978ac81fb209e816f0c314206bf43bc061cbf843a8b2d56322b6f08dc78279baec3d7df211d162446d95e0196e444cf7db80d65820bc80ef4e265148e91f02b9abe815df741efd1ee3a326d0e36b4b2184d5d330ab21ffefd65cda8354bc94e7f5ea990c571d97058474bf8da406e0404210c8ab5f1e312acab67b5a63f47b37d64bae618a6eaa1191dae5bc2a40759c3f20da46d1461e1a4bb12d65546553001056367aa5ce114d11e36fae178b2980c70e51856b433a5c847bc94b8615cf616f3de2ce8f2a92c19ddd35373c898074b8ce930631ed22d51039ddfeb598a807abb4a0cd93128efe8a144880a80834b37b5414ca9fe6cf967496a9ce5ed60adb6bf12b5938f5a552845f6a6167a722a9482c0f0956fae929051314de902785bbedd12817957571b7fb3cbc6c9b93e853c2850c73e2c46cf41b97314f69083f9ac312fb6077967ade358f572f02eb5f9e96911e15a62c813dee74eddcd575618d106bb39d69f129b9ee6c9d8a6cf8a8f58d17226702b9f77b2aec956a1f6d8ee9b3be0c0910baafc4542496588b163f19a684d93ca23cdb1c74215411bfda45f3aca2f33c59c32304aa33b718c714f245ee036260d32160f1c46c67894310643d0a17b28e43f4d23ab20a13dbf969fb9afa13de52594edd3a7f097bf7d3af391ab447b5a545c4e463fd718cb320bce35e9a55854d4df4fa41daedfe3689ee6bfef66077f9d5a6383dc62bb839219920689ba38b7911e0c4b4751941474af1b5ba2758ac1b539c9165da19c9f4aa47169e8bfa16b77967e3bda250120c4956c4da899cef70594d70197978fd385073808b7500264e16007bad9cb7eb50d50eaf65370154095ed28b9c94df3b6fd9c85c0c51413905e54a256ee823da0d8e4ab4d0b3aecb59efbf720922cf25ca3a7339c8c2ab54acfc5c94b806f06bf9b54a912963af890a0bdd0b590a480ff2904c30399ff4412cb88d8f90674a0f4056e3f94dc39feec7970c03abb21f93c92f7c07ca257ca731db009bfa8df5fa42d894376995699662f81e4068a076d854cba92497f5c7970b9972ee819d259640c584f6947e94bf0198f540fddfa87c429ebda2a2fa23a9d4a74a7e5d25e1988b3c0115149feca32190aed94bfd39ce25c838ca4add03d29806b819a858c95cc9d2219c909c3863f1dea47dceba50aa43db32515d1b5ea6ae50db19fc110b909224a6f22de7952a3f2fae75b52189058b039da1fddc60dbfb6f41207c0aaf5ee32e2255014669afa25fa47f7936b60c5903ec8d9da1b917e943a617e821ddb6a60cff22e62af242bc46eabe084c3e338f672309999bb146bccd614fa1249dd4f9c062581ad4c5881e506faacaf2985a591bdf07345f6d2004ed7c5f13a385e0f77d7e17a2ddc5d87e3f570750daed787db75385e1f6ed7305ce9f5849f40ac6d6cb186db35b8bd1e4ed7e1f25ab85d0fd7ebe1743d5caf85db7570bc1eeeaec3f55ab8bb8ee148af97b897fbdb7c30a73a35202915522abfbce941506d391ae59a68b8d507ade135ed6b546ee5388a17164b83bec2b01262984ab355305ca26e86b0b95d93d4d624ee26a17948c3fe18369af1200f49625717dcac4ee28468a7fcbf9b5f951e4baf021eeaa6ca3cde611bd2896cb3ad005341736bfe9807e699ff66743d9bf972f0fcb6933e6c01bbe03f03074ab3b6bafffdfc774838db79d798fcb0a48bf596e8cb68e970e652094c0c229fdda27cace90368eca54c45d276c5b06e4b64bdaa5bef51f3e668ccdea9f5fb0de71c6b549effa1d10905f7e70a57208ad0eeb70af3de577692db89847b1b6bdc59b4c6b2f3396e9d39716789edde46fb4efff1e5aa252d12f2083fe8e50261cf856a5bc3de04e2c00fb16ae6c539d1e5ad3914af115ea94f2074ce630f82516e69a9d2f625b2c1b7a6d2182e9cdaa897a8feb8c0b9465418d5d1a61667d2a392eb0a0e923ee3964202f8ee128e74db927c9347003feeaa1d1a46ee9a63283b0f9dcc07f0b62ab35cf3e9c635aad357da05ce10c02b7f00e0c86228cea58394a04f480369d694350b0c20cd093610d8393af73cd085ae53c72a170a71c8b99b9503403e3eb0523b785e42c0d2b1b039dacbc9595a650c37036f951f7ed094f5fe33de28f62ac808867162c8b81f4b441f179a61af4437ea8e1a6bec1616bcd5295cf86f52f5d288eae2136569af34318f3cfc927aa7814503e39f9b9a5f13cf156fb08c716999f621065dac9cfafee03b1d4f05f1426c476650a6eb4319b858f24765cb1662bcba2539a98530d63c79d19d0e00b150bddf25f2d274e7421851ae6d3c4d2fd1095e4c3f79b9fcc96314aa2e3598c4117fb59a8f7a58cf2ea75fe7c900a3f3aaa910eb85b69c885163bd1303d2b34ae42ed70b42980420f496979552b2370f9a634d2bda5e2bae40d7cb34fb69b9dbf4fca6f641d2a016d215356551f5915163dbe7fbcdfec8f02819c611a295afc76b115b6ec1163e5e3f5184f2cd60b4156c065b23e3eb857a79351bd6c39e411f342da7ba6146b54b678934bf1491a896c75df524f42db0c3802fd87b4833991ec6e133088d55c79dd59169fae16580de480450cbffe34282e5cbcf9ac92e8c40ee0cbe30633c0df9529f8f9c50f1ad895ee9a0ee97f8e34b1de6d7c725626d1cfb1ed9fbe76f7a21554d04069a8856d0edbcb2f0e2a8d4525aca9005e4016baba11d199a6b54ba1cd7c5aee44441a73042e649a5353769c0bd3119ac196ff3551799344bfcab0d62363ba833f07cf4c629d2a0e3971523acab57bab6cd83ddd704f7c99b5784d369f91060b003f80721e15ac911fcd3391bfdc211ebbaabdc5b4caff5b80b7a535b81d9d0b9c815c4d954d8657bcdc047a98ac0ff8fc14c09aef01623ae71114cfa4c6192ed168ed5a9174b1fe93e5ee6fdcc5558c8b13e388ad814f4c7d28397cf55a546551e603f12e0bfd816ef42c2c821e95b943a60aa36e706e0829e08568b4042d16c4682c7c4bbc82d8e7052fa0973bab9c1c29e2af709958a6e350e1a2dad95d59bc698d72149f5f9403b45db3b2cb984227ca425ef3eba87cd0324f12790e08c5b2d2e2e64daee376dc4b879a6a75887f1c34ef8b59abc3cdb647071ee6c0adaa3a42a9b2a2b5f8ff19086aa88855008bc19ff6607f41e31badd0f6a3fc47cf0b25b287eb75fa01ce2ab5bb4812e2d9fd9abbae1822fd593e8d2d4e43a114f622f632cf4638477d0a57c17b9c65c6b9758075bc66fc047f922d87d8418262c349d6dd81e417c619af21dc0427d25e4d460954d8d18bee710410560ac4e80d1261db1b336d936061800c71ab770d4044a7a806cb1c44aae021e1c91512d7e7009a64604911216f8076fd850c2013c077668bf5dcab8afe9be8dc311bbafd4de580d44edc58998dfaef18a7016b87e799704ad02636ce114e0d090624ded06017cb3c132acc88269af8e546be56684f1dec469b3ab3d6cf62b54fc6e322b4489e7a517c493ea7bb37667ca020dbadb3768e766e82107bfeb12a98694c94193d449c51ace02c978df962d4db64aea2d75c7ec91c94e6ed8f19d0b312c22738d32dfa4b33b380db8887176651db26ebe1ae23aa494c7300809897b2f0580aaf6824ceb205e47626ef710e98aa1ca9610cdadef77eaa2266273f4cc78d827ee33593d7f53c3b0826537b4930ab292c9b21793e6c16c6e193318f15c332792f3de5a36db89d7d1e8be3a8d6108441023f0dfc8ecd6636e8e20f0870a3fcd1ecf9512c7bcf044bdb48c028a3c15814f37e6c2cce5a768b2431dd7961d07e00b78c69af025d8a5b107313acd8d7226bc2576753ac8855ade571a293234225ad5af0cc691e00023b50aa311a57f8c25a067413aa8caaabc5dfae888d916b331c8195082faff6467640c07550a6ba03c53c1912d2c99943aaba3710dcb87d46332800951cd80e05baeb3dd027e8bea1b378bf66aeaea19a7709327dcfabb5dabbd48a4c76e17a7d019d6b6c2815e4ff014b30119b6b548dd4639fc332bc5df7fe9f9506b0e2af003222ecea15dd03cd77ee2e897d9cc1f6db14dcf45e46cccaeca43b69712c51eecd8593cc81ea45eae1d558c9614bbfbdcfff803ae3e0271de025ab30aab1fc8d02c50de285da8f9ee167040ea3574d7606246f2c425862e461792afc8c339cd9c7cc4a0b05b3156e4a6c979aa9be50f013da301267146292c565373c52e9358ed2f8f974d999d875a82649f2803360d1c14c92a9bcddd4b9faa805fc25b2d469e77f6438844905e143b20aa42bd9e8eed514848215634e65b007a86deb1c1271fbb224e9e234c4bc0d1ea0f37dbb241bbe76a24daa4b954e28d1295d6941e9f84c0660b478f361622e4e391bd8bc919ea64902e7b40ad9e428dcfb90b90c2f1064e4af970f41d978bbeb359ae5090148d0a10127056fc15cba2da67ca7a7e6cc1ab092a24508b5259475467c0dac42d467bae53efbce94d3a0e332ca627883af57a64f46e651361dd439172717ad74808b1a0a7f0f18386719c509bce8525df375c59a2bfd824b95bc391b5c2fc7d5d017861b7a5a43561d3ea928a34108d8dfc3952a072b8921dd2c014857b9f856d6d23b01b25b30273448b25daf3a1458b667e6496eb9b921038af85054118d051440d9f97566849405aa930ed0a37aae6ea00590d7a6e807b2b6c078a59b44f8a36eb448d85852b2d7e3dc36c2da666c1bc885a641504292404ac55325cc840325b9ba928724ca6a00952935231d624fbeda652dca1e6e3add850462c52b989f53023562f3bb1737418ddb1c547e27d23f8ad07641ba5f6e83b575d9b906f3ff4dd7f48c3e4a0b5f05db52af3fc401b19dbb0ced35dd92e934db6017d5abb49fad46537838e8321e22f0ea12ffd6d9506b44ee76bb6c8d77221421c1bb2ac41fbea1287da456f67c20238e2a8d6f076ae6da1f5f07f150f1d600ed4696b8725da1e23dfe26aa59b8da674e8f94806d3071b1646498bb37ba905500c62821911ba370028ba8093effac43f4c574ffd9b9e0035e7659df4011da1d922457b72ee8927ef9b7a180af364f9fd006785a9193d5d24cd0ca595a44d40fd904c4a8f76a8de23a61a28c147e3c4f011e9db3b13cea66fc778dff9dddebe2485c9348c77f16b67b68673e23d80c85eda4aa1743a0696360479b5aea54dc63950a59d93e3b87581e62713d528abed09f7d8e62269ca8f4839f0e7c66266defec9de6c0b312157280588e649de28d5de7953228a347d11531c741d712d35b5ad1e11bd9b41e2fbc09a44eb4bd763d5bf0d6a33ad12d6fd39b4878d98a440642abb6c3fbbbcf1de2c9016ad9da9b9eafd4360025334b3243612529efb735ad89d4c2d35542ac87a9c716d3b7fdccebf9c4dfd2f768ab24cbc8b6100edb187b30a189379e9fa32221061c9c474b39e92129c77da161be4531ac76ab5bdca38552a407568e7bb7b1aecef1109683821aa25a415056e309ec2a02baf74f29428c6961bdfe13de20f2e66264ff632159f7a7ff8c452cf61db98e8d91fbb5cf6e27d503c758aebf2e95d0c7de1016dd5a063ea90592629fa7b5fafdd423ba63a9df5bcf77234aa05e73d78be77322d5f618b6df1b65ea9074fa21004cd00d1c796de3fc33d04904d3c1883c0541bd0aa01f6e3f1743c4bfd5022272b8da5c5fbd4508d2992b94a4fd7942d801b954fa5d49f984c9d27f1adb6ff964d9b6626bca0539570de280ad4bea4789dc3bfe9c92a3eb75b90198cc8c7ff29e1a015eb01836a60d7a92a2d3dd2e053f48027e942137e634f0ba8aae873a8ec5f74cf54ddf7dfb37cd451e2d9afc9bd8f1b1af0ab1cc537d100e618857a2c92d53ca4e84dd832be10ee6dc1034afdf8ff6e834e212136771cf520498fafbc68ea5595c7b8c766724269756ceb22db3db3f107006e34cef363986ba747b5e92dfea0cad3fa1086eef118179d9df11d03cd83c935637236ad72a012cd4ec892200169ee0ed361557e80631557e9f1aacd4411fde33e1fe631560ce858daf3d9856a1eac23043af98f5dbb54563ddb379e3c808e77354f7425641ef40b4d3fdee4e3ddb4f70cd526b50f68bdff4d44168301e84d22557a948598be36f3b629766f800338520d47698c761e594314a19d4560ba83d5008850cbcad4a52b01cadc2fb4393b1593c40cacc72b640f2c952402644e7537cff7db9239b6487c4fd189b6f16695e164ad69103e03e312f98216640397e13722bb0002d62bd73749a3e3321f84b600707579f8242784c679bcd70b21d6ba2fb91b0263098c3cb390bce8793f576b581e4cfb61153e2fcc8d1deaea977fd11b17964c301e2a302bb4a0e53b1f7090154c07c2ee57a35874389a6ebecae94831ee66f62a2c021369f24d97477916a3675cd16da272722a775e86daac8880fea678100278a734847e925708fc80d41a2ce02e78ebd474f4b5523a13b78b3f0665e9be8484b1dc43120d848aaad60f1482c706967d309ed96c59478398b5eb8c45668a8f799b8b854abf8e144cdc6dd8acef28106ef42078c44ee974df1e60964f8f06cd2ca133faade73ba1344eb23e112a5ecc4b8e4e749cac2575e7c96458a4fe3ed7db9c4cb46b36c1c0cb3d8cc97259e0572dd82a832306aa0564a036d62f5abc3c49112f7ead1d32191ecf9ae2900f09b724e9a6195bf90fd1e5957bfb9ded7f3ac9fd1af46ca1a5b05e7dcc2499ce29c3f234f4698653bdbc26b277ad45262f1cd7f5945f5ed6fbd54d95f8bbd36788bb8c2d1da5c80e00ca6152ff3ad9f758fff0fab3d78609ede343330f785aaa0f231bcfed05d419238136cc9a8f15cf020a5c6def35e330057a25f381d693710b7a17e83695f30081bdf1d9e2a6ac2cdc933545f8f66e4577a5cf56aa36a08bb34ac8c67c0d31123ab68a0347e8c9de3d8b4edce3fdc0165b34259827cb9a94979377fb32d57f559a65870ffd882fd39972ad56ae39f16088272b8f9543a8fa591d9027bcd64ffa847c6229d28dfe075e412206217dbaba4f8eacb0edd4699110213c22e05bf470422472ed9cc485e35db0710479f367bb06be268e77b40cd24cc6da31ad9ec613e2543ff5317a0fec69763be626f501cba834fe09efd8c3c495959650936a993014b9c5ca613c3ad59206db29c61e0eec53b4c16038f005f43a6846360671bcfd647af896935a5e9e4b9d63617cf9c8763fae5c72af497a44d6de499e628dd00ea17fef46d48a55432dcf6f644c4021cd304f8557e9c26a6cb9ea485b8db126d79bb69d4e3593fd55dd3560f1fd830a673d1f1f0b7b0654e125478da3c8629e9d7a6b17a2087da34f6c9bc61bb9e7497ddabfbcc1f2d18b44f34bdff54ffb8a9a5711a34114b1284956797cec4c8b4523189adbb8a2d71182aed56fdf720eca4bd6b9688281820da05b1abfad81c4783c2d2bfcd35fd0565b5e8a884ba3cde99d793f803f97526a5abb9b7c51ab1eaec84310acb69229590ec3c0a5a754063dd3d3c85188aef5bc6709b492416bedd0fe2d101663a51b2b5e4670263798bc065abfc204a96d8d81b8a5d2eb14b6871ceafa15016110142bff098055f94ed2cec5148136d3fc62bca8a86b2ed98477afc61ee9e35c125d03b168919bc82ed9af2a83bf2281b28406ae8facf11219ba402d47964a578d6a0988f0b9c46f9da32f952f06cac743febc54139a6b2cdc45d4b166bd8716108a149f86e86e00cbfbd5402f563acb8e97d21ae06a6ed0a30ab6da26fb895b12f0455be569b7c152c1bac686ba4c35e382ee5b157008361c97216b6aeaed8bcb3ff8954c6407265f28564b860f543fc95b6a16763467e99f86088ac62dad9566fe5e5c376cf7c7e340dc4dfdfbb4ffa68226fa382220a5b40db8e9de900fb4a58a846184c0a289ef1b64c03ca0c539c063439f2c8fc84f1c9d89905dae5ead4d6d4162c43d8394aa194db1375b3d20e0f4be413e0855fd4b274e45fe0b7151468f163e47701331b65c57231bb2960124ecef24ca0881244208720dce39c2b5e20a987c532b6253e50387b06a75bc5973c92056264429a9d08f89bd746083378e0119a3d9d9b3d3c2a596a5674b190b120dfc5e4a87b31d4303ca7fd291ea5bd9e49cbc86cebeca408c194cf305c03b294db433cdae2cba4c97a9b472510eb1be0419a8a144969df5bdb2276d6adb0e9953dd54dd818e0b2f897104e998936d8b327eb07cada9c79fa352326745df951e489f7af21e4faee20da49380bf8a144fb6e20e81ee305119b33399f656968697aeb83d7ef8876b172fdda180c931a489b0a392109695bd7f1a45f241c16a1a8dbce705c5e5f315556023d8fe2f4a9e83655050386a1abcc9b60f93018ca47dca4801243c8f9323a0d074f400321c3c6ffa41b4c60c50ea6fcb5710653611020ee5de57558549d366a0c58fd3dbfddefa80a0318a252386f4028fcabab7ec78c9260894f4ab943445b240845bf2919ae25125ef5a3e1c14f5c189ba93d1994c9232d6a56feb20c868c09c7258b711f2d4c2864d166d218007beda9574fe34d0c3f843023095e81156eaf47bb2a4d072d67224a10763bc8c089cfbc0e0457206a87f886a6e6a641e8595b0e411bf5f5e2376352d045fab3c0dea1e98c984f2803913131740684c1d583151282272a73a44d911e07a1ccd492188eaa409b721a56d4e04e4269b159adb199201aad345298977bb91666f0b711a05f0b982517837d5819527850522f01218792948640af7300136160fec7ebea34d025e027dd49d83529426d39ef4e200c8b83d75cd679c69e9c2a57324abc7f61423184bca43b221184d334e693ff79d227db2759f1b0e56c82404896f15b38f4eae6004260020b43cdca630c98cab961186efd1bd092f2567baf0a3aa25f9a6e30c15601071b6292db6ee8210c3cb83362a19d103b2aaacf17bbed3da2b08b23357b7102d389e0295d1aeff09224da2ce412e4e1015a8b4202410d5c43ad5f58153996db9e8fd082135e0e7fe9bfcc320813b426ef324ea97a1dc3240c87d8a8605424039664e6de0402035f7ae5f300ad5d235acc85266f7337b859b6a8edad2e9eb274fffaaed740bf0ae50df4f395b61ae763624b4b2ad3c27bfd42490d82eed52dfefa97a603ca9a242be32875b71f286c516b937f7d18d2d2b6852045b1f68edff068a0a46fa651a9122d544a06b0b13355a9f414a32fb7adb21bf82b8c596e2b130cfaf72f82f2b3af9facc887d7cd370b8e7445474fe46142180e7c51222a47545810a0c28bf3e0b850503374ac65d5362f77a043ebd3be23e9085759041a45036efaf26c2b24d2a16e304e75092b8625c473f33a68e18271e57687eca2770a2d3fe9c3c1d404e1a1437dcc8802d7558a5a4ddf097514d9d4958639fadc02e2a7a05213f0971fe99ce8871648fe867f675cc1e9b9ed967e20e6b8f2f21b401faf58a1b805e74b225be39747e6f28c05252ed33a31b4b36c83a2218b281261d1df85c36dde09ebcc83f0e8aa6262ef74bdef31080839d16612006e73d6254ffa9cec2edb967105d64af108ad41e5d341c828786728c52d208ce66f873f1ea5418546bf7c79f452bc183eebd91104355fc335fc07c470450fbd1c545bcd3974c845c8a911588f993cf1728c467653662665f465871881fd0c81dd05c6a65ca378469b1a0091e6d8d0c2a22360ca6e7d7ca803b07bd03c1c8908b991a9d6a8cb4ead290ee5ae30514c7a70075ea7bdf85c00ae3431f26dc03500e7672fa8223cc36fdb0048c7a65a22a5d6d8470c7f4a54b3c9fda7f8651f52c737ccd1499b7988dfc8aa22019808a01e44d41b256b402625ebc53c9c747f28e948e1ca851596ba8fde04a0585ea97824aaab03cdb6f0c48edddeb2aced8df794d6d127bc999bd8aad2ec5904ac00f41be4c0b0688995db709ecb7b2b214dbe1184ea770bea047f277e26ee95d5fa9157044ce3e2a4f1358ea8645efec448f923eb3e54eed89f4705c91d2f23b875660ede259c5acab66d86d71362f451f0f066b65d936291d3838275df33cbcfec4b7a263cfecd9bbe7a58b1e5c83d26920478655a42665be990ea6a3a37ee53173cfd20fe932f89dc40f35d28a95f2f025fa54712a5a2c5d2c8a7ea1181de6b877c8262bfbf38474a346e7a6130d3e6acd73a3b5ab50e4c477ddba044c8a42c6c37ad5013e16134154b544e25441067609e1f18b550df7310d12c84ff8807a9404647dfa584e81a9f068416181da56a0d30904d9c3a55ab854bb3d1e346bfeb97143eb5c4f685e1fe9dd4e0e48624592c0f53855184b68a6da948b20c0d55a20e97d65ac2180f965abdf0f9ac530f9cca89440cd34d2a995bbf209ce9904ed53adb0f7ead22b93e078a8f799c795da22fc09586b6a368a988001500441a217628c3b35ec65c5a48d2480cc89d66ab048248949497f31df65215913533daa0bc8fe18acaec25043a748b54d911c96124b3ed495d2325eb092cf7d002388d31a108d3118be298c1d741de160de5630b88a82208ff0e7dcfac07c9dbf5b5242104488d55e977c67fd80f05d64acc9c64b1cbc58e45f1643053e99404d40df0797b3434f5706445ad7c24056431c8b18c381332db456d89d9bf4cbd18bc1c4cae21c26b993c24d7c9770ff1b18788484a390b6ae158e5a103089ff2588f2db8fa9a434bbfd11ad764465a152587b18294dc67613f78478161bd89310c4dfa30859932425c1cbc07e6f30c298b1e929f743d761758d0a39eb0b53bde7cb508b6d7d986bc94e79d288a59e4fd538408d058d795b41634b7c7f886c304d2ae05c2cb2502ab34102379fe042db49bb7afaa60d2c8da2c7daab58870e8334ea2286be5162b3e450174ce07f668335365875df7a1f3b972c018187e0178245123c5a64a7f80295e3e54fdc10fe8affd5d244372cbda70e0e95eabafa60ec75b72a98d777a5a69b06c7cc0cbd5fac2fb5faf92650d08e1314ac55306bd9f3aec437158fb253e1510367edf03f6cb98836cf9c79be1ce2685a49e12182e61abfae8c6f627a93570914b753bcedf367ed4bc9ac11dc5ac2b9dba4f73c3ef3756dd8db15954a4f5f89ef861cde9f0957c6bd289f29554fa7648d8315a7c2ca6195c65233b773e6ef2369a7ac4bebf5b84726c9cd1f40d586b4383a812463687d675d0f646a79645701758be0623a4174157c60a2721ed61c05a65f58ba14c7757a197b5921cfbd718a57e9c45fcbf39b0995e404e33e2f07568d590d7286f2c969c86a7550376e3c6b0d7f9fe9fc9d2c35cdaaafb078d4b0889dd855284e9dfa5e04efe4f40f1a206c0bc9588335f8be7e4656b0c82c08e6ddbc2cc3a57ce484d9c15399feaff074cd421367f5a4724c01b081b7ba899379a8c43d6fa7488165750ec840632bfe020277c3e257d8caeea6a6caad40d21ad554027a0c2829d3716f24a651addc1b9bddb846c5c9dbd06714ea3d6dfb35b905a660fa5ffb3e11bc830d34ce5c910455fbe7619d226b5661a3fae5e8b1c981380bd14db352c4fb0addfb0ee9fc5262f268a105ad9efde20a63d7742d62f2081065f96c9c8498f70d8cf744f60dec1a1a6c0203f8b6c0fe0f55aeb7bd650e33f55852d68aca6d57170b80655aefa424614f70e2c2b4be62c17c8d90a2c1c6668736f6769bc5421753d98132588a309a2c0fe745ac9473005ff9bf5056497840aba983a855016cee30ea245a9bbf6b37f45ba137be5174de4bc430d71e847f4aa2d7b1af0f65e33563f6b4a1230fec6aba7b8b8b8fffd614b3e2129b5c597a7767dc669e405c10ee208d4942420a8ff7a55d474bfbdb39a8179acddb625ffdb6460d1d842e2b7aef32ec742caa558c9a8e8eaaec4ef1741bf6171c8f2c2e91ffef1bc5976e0903d1e8b9c160dd91af996c69ff33221474c808a4ae41c965b039a547ac6ae3db9c2dce395a2e6ba8e91093979608059efbc6c8c395770885c5194a98f47ea2ded5d37ec1b8a82b07f4639eea693b37584e64ad342f644c0e7d3d97e04f04454387b983128132ee4e733c6c44f18e3b878b47e61b670c3ce249e2235a8232793a78d69ef55b796d64d3b980e93ea92734cb5fdfcfb19c05bcded65224f3edde9aa864330e901801742d16d3ddeede70b3914f167f8be78af62b0660d639223feaf867d3dcf7271dffbd65c3ba2f9179a86cec991d3e650b9a32009ff0e0b3e7a70609c6a318a0cc3fda53bd5e72920d489dd5c84842ba6039af8ab3dea7292ea557c8a2af28cbfc35882f93ae74e4593cd3d07e1e01c4e8634a42f28edd617265d0538ef8eba12c17a75ae82ef9c1e098d5c0113b632bef4be1dd57bb8e1b9a1d7be5ec609d4ba611343c31eb59bd57ae7e3f7a7d6c5e1f28b6dca1403c39390a98d62cd4c1992205cd8b0d19f91c8e6f0deabb622c8450aac01252e88a2519e2561386370604c2be9836eef56e0fe6c96e5f1b0281454392a8dc51d25921a8e3ae35d239a0ed96514d97460b254575fa7d3128143f022faa098917193d88a8f5f5b88c8dbf402072e711007369d794a5285c0569cb586c7d8f7dd77f6e471f875ea373205384a2375576a41da9d7661eba9aef88e189e60921927b1549c3c89dcde33c706ad28558d5d70a4bdf603e0b41969ac9414ada9cd0510940e76df40f69e989a980b128710d94aa620a79078c7e9f81e497642aefacbcfde9310afe1d9284b3a67955b4a82509c20ab545750165aedf1e008c99be1158b9c4dde744819c22415e410fbed329244ee0bfa507a331c92240176a217c0b4c6a1add848913dd2e34827c9a3afe57c2d8d1e9c99a89465def68d758a8be050a627177adf383c9d3ffaab33ef3bd2b4b6120c524378f6a75aadf0995a279bbd46d7bfc1c6610b998b6472e215cbbb9e978c180beca944c13bc679e4f26643fb3fc2835a1a2f1286cf379703d5447696caaab312ceb43d28c61030b081fe06cf983dbc705228fc30a58aa7cfd91eb262953cb7110f050860be95129dd47e54ea16de6ee043a30d020b46c12008c85a3111c8cad9f40ab084a8641d470fab4dcdc3900a76c2e95e8cf004c2f24134b80a521f5eb9ac255e8a1e7fbc2821329995c8f508dd41ed943cf5b09c6bdd09cf3444ce674dda20b0a14236c6ee2442810703f0c629dc8efda44d1eb70c9a706b40620317bb3681c5642766c688ada6cb545a1ea2a0f17390e046db9747fbdf89c12fdafab35e86f3adbb20f5ebdf91e760e337785089bc460b241d50b02c9451b12bb25b289eb6130c94baa477df324690769a8fe3e98aafc2d1fe5d8e9b947059e241983d82cd9d15ebd577d0771515032572d5edd835bce4b8a7639a4849dfad3d191af8e6a65486cf0ac7572a927cacc75a22647db76722016a90a3d6c9de8a7d8d14cdeeb3bd47d42ab0b0e4f96233e36f56d8ba0a9d4d7a0ea8c23d3c5fadc3bf102dcfa7a60b2bbcd70c1105c42650d3dda350bb792267ab5c851bcf6d7e6b0b273df0e03dabeddb4cd50d981f8ce6fc0366c7627e194eee2f27271ef4c3119261b6968294e4ebc02a7920c13fcd6b1c1b7d828810e137364ad5d2e69a2e87e800cb833a1a3d107d543724d8ffa1452091ea6c8c627b816f5420940aabb8c118c3d54ab71528238ec23e1700515e9b1112a386de15bc54e97b31efca274e975c97de9f64265152973c742201171a7c817fbd14f5d678e5608013a6bbb54911c63160d9f3b6a4b5e4b909355c60725049745ce7419f09c321e36da0e30f464f5e68d4f326e6538784aa33db9e2a1d0664f2aae9a95966d82c172169016ca06b5ffe6850a7921a379d8e04acc47764c935e8bfb28cdef4f84b3be62b7744ccf4628a2c7836a7f95d3cf017ed467350d2b0717cf395fbc4e1db8c0fd3f97e8265e067377484e51ea0fffe0a20c6f33eb14b28fb4310721597c4e140fc149f28c978fa130ac8fb83f34c64187165247c057a458ff53516838a958b02031da9b6c063df07a9ac2012f8e230cb8d85aa5f2f57fdc92db32d50413503033632176099aac1a75d179ff0463227eb8e9a4ecfa59003c6c96421fe23e734c75cfe4d65043405b242a675a4398f8348a163c34608910687f586a58723a98e857a7e2fd00dd49f4166ee228133deb1035ea93ef8a8410c0541c4ba92e14135f201d2098dfd5d32689c922a7008ac5e06c5e5f89205f5fea6e7a47ddebab93d25b0a294a2a8b551f191f25f1656cef89da04ac4af11266b14f4519762810bcadf005c5a67dd72c607a08e81e49d807133d3478d1caca7c492248aa22d784a0a62f12636790a6a38bdf70611d62a22ecb0b9176920b703aa79135859b5e5c7f37a1f1d3f0b998740294771ac6336e29bf384c4f64ae000fac6be542ff6e5278f98d1e174d050c942145517cc6ef4345e81d421489bf4d426559d81da7839fe0fde731663f232dbb86a0538a60a4947c315398b5a1497320ba17c2c9dbdda77274d2e820ba34ba9ec9a8b5f7f2c7b902382a633cf13e0a24dad92544a00c5497f0b8a29661dd5e215b2e578804839898d1183c6b9ca7b1326933c47201cd93059c54978ec3445008b542634fc213a752f006551f58fc515dabe672b001944ce6a485e000f38a6fc4f9d55a19fb4c113ab4f07473d0d92797cf7a1818b318b010bb5efd989c50df14d1f3f0a4cd1b77150c8392c10a899c5f890ed18e7120d15cf1011d6de1a14006ef0c95eaf8960f8317e442c8d45f013ad76c3c299c26b7cf8f7f9a0fa96fe9fcf0740a2740b47808e244fd39fb8f19a20bddb02e601f9244c09d3ba0db1936186159cbe4c955614213b8fa2bc895d0d5136351f702d2394d54083da1cb9f719d3fe37cfd32e78e696ae2045963cef704ca219fcdf109e8fdc9b339d97a3f49d4474c984132713048d9650b92bc2b6e249c2069518f2d9650a16a06632498bfb35daac27a80f76b0f409491f885d8a4edf09cdfc10a218426cc7842c60bfe34365f8ebf6767815e24cbfa436bbbbc1e7598dfc54e17643f60bb61b07c764364aa1a7cd795b20c6ad9b71ffd17173610f78ae5914aa3b9e5c8e36156e9ec7e58fe7dab4630b9801d95546825c2ca61e0eee4912ed9dacba0aab367490e587a2ced5425cc8202bad72ef8403ea41d91c716eb816d8b67231e33cf55cd8bdfc0e434ae8181e722ae85b26659edd799db22dd9b4eac6efdcc45e362e9bedd717813dff102b02f18c9ce94bafc8b25817a8e92e4d55971cd96b20dff8b32ef6b37be6323e3cc3feeb7655123885eb7b5658dc571d2f33ebca0fcc6cd7f275de887ca877fd1e1fd3d749777d4a173328196a0e3758accf146d88a231df3c3f4d38314ea04ce1418e261a4ce9bfcb75906e4181778dd4bafcca77c1a9fdbfc6c711f3eb5082bd3c10bdb322e2813dfc961b0c988d721eb8a315e460e06194ece15e35f2d155206a81912fa979cdbc84f277178b131fe58cdb62a50d174a20b8d03384bf74d6265cc4c332af65d0751ab63a5771b2dd9ac6b3bf34b0bd53669682c0e35e6376f3438dc53f15ea34454ccbc3d75d6e2e9edafe4e21c4f443b82941169e3315dcb211df84bc405220ee4ab4cf773c02e32c6bc44476ef0a1cdb01a01a89cadebaacad7eb6e525fe0d661164ef21d17f013680a47652d82c03c48e2c4a5a02dfd81c1dac53cea4f1ea1278a1fd356ee6ab59e9211f15b48c64ad4dbf3bc556af5fb8c5c9b9f9ba8b8ff4dd44e9cc99239a4eac3a6c19c05a7b35bece25fc5f2b6dada7d7b6158c8a7cc5650896be191debc8dfebf0d7845bcd3bdee61831b4ef7ee24d253e3d9d2c355a69a32cd9bd63c9435add79cdad62e46cc20d30e5ef83bce3bdb4c00ba9fd723839b75d1ebe7f24a369bb94315a74bbb1b9fff2e61bd05051e05c255b21690d5b29278c03eb2262e2e754d034a2052b0a6c17a6606948d480f097b2d0be74229b448958920a058e7d1a1131395deb3a24a80a50ba0f2e67d62c092fa19232b3b216e15c209808aa95af2e26622add043a63e06e843019ce5f07ec69e6304732bca922d952fb82ab19f785e25708b6ba1c82bcffc0f21d6239a3347f74a68f4c68aab7236603037432a74aea51616759139dd6309525155450612e952456ee54356838118109105524396d1bb4a953bdae8223ba243756446784b1ded6164a875b3f0315fcf1a4c186215123f2aaee4fa1676f4b66d4844fc9139e15d69a921ee1a545ef8b217050d0b24c2ec9575302006103f4eac9400843c3742b05fe031dd6de684af74447a1bfa604f8c08de29300560d0f3a4f71510cf441672b9525202d1c9528ff9815ce63a73c2bb12470de9914c56411ae2c062b7fa83ad83810c50fca0b8f22866477ac122bd99113e9636e2c8bd2f2dfbff716041acf2627e4c9438f9615c69fca9944d66dc71db81eddee03641073126f4fa61a507e447e3cab17a69195366dce7bec3911ba9c806075d228cdeb392fe7920c3878495a47f75939885d2aa2591db9833a77b2c396248af5c24a3088d3f906868bc293111c2721bb0c9bf8c6596f72acaca94c84e2e163b95e52df99a81f38fb4ab69b89ae77e11b5256450561d1638cc6a30c9939ac504192975bf7e73c39234bc8904594e6f9dc55bc3f7994f1d531e0c51b2a83cc77cbbb5ae295b3a6ae01c840516839a1629ab4490b427499000c5a27e724da603e67b168aad68bfc2210cb73ac9497dc216bd309b9b18dfc7439834fbfc5a3e360614ba155eec2330a1c4ca0f9939c44e4c13fb5e62686389c41ebb5f5e6613efc3160392ec0f70e3fc15c04200c6b528cb95efcbcf142716783aa60c8fd57e1b15a66096866d6fe3797d89c52bd909d8c4153d402f4b81fa7e6ff7eab74cf4531b6b3ff3d533c40edc311f2ef1933b9ebebfe8d59f22a5f517255c592fcd77e48ad8605545ad618ff6407f4794f8261776ef98bb23953731af62b15daa745115f7486415c8f42122eaad9d67c54e3b8155076e1fe7be96deda3e00f09dd02d996a8168a35dd5c10b70582da3accefffc4f9e67d72f2bf742b96f033960008e2f61fd417095bfa237c5dd1a30ff41a880105c7d874a4643407de8a764897c57c40d76e2cce6c789d62be029a4edb96713075971292e0ef0cfa699b1d8de730dbd3ba082e2e91ddd4172512a5db3006d5575bc5f85964fd1b28d8d8690c0a830cfc678906de05507add26080c946edd5640ee6686be0ae69db4198280581e74c93d417a987884b1d332af4e3f63e431a5bdfe91bb68c92e6e40ad0578efc0a22f05285a40d877f81db7aa3a58aa983bfe3fd60a1303bd0aa172f54e7375855e73663bf95af10d799b43b36261888aa491502cf89e597d49e475919042eab8709a0b7d3cc626e957cbfdd02e117ecf835f184054e15c54aafdba62b02b2aba8a78e5df0df424b145fe6b6d28ae4abc987e6b3098437c7f3ca7148dc053ba4fc9e066202a78f62a5c96dc32255c4d3ef63916e302cab6081b181a6e9a3ea2b19dcdc10233317e4aac2106b8d18bc84f7ad7251d65a6fa63cc03eb83a50fd9f997fc43757e10bcd800ea4ba780b4b5cec366f14199757079c72178790f9458bddf160a1e8acfbe7542ef55a78730c3340a13d297cd4616ebc8375e653a0f33eb1afa3ae56fb1af566955fa0ffda2aad44094aac9c15107bedaa009c370c1077410a68438f29782d48cdc1397c209e1f8fa58403cca84b8eba4373f11a857416a1fa90b4791ca4c3820ff797df03527ce0e0472cd8834cd8d211b14201a1c900f910c2c4756db458678bf2667b1dd65f4aec31dea345bca26bfc43b199e07d71d6344dab65809e6adb10571b14ea389cf42c8ac6134cdaf583a2b94d5c49d7f3e0588accd8a327f1f569e55b75f01f2122713a5176c9f970827fca918c530768d2241878d6cc7c804644f76f13c86373f90726fc389e254cd14312040ee4b4e2b8a61032cc78dc9643823631f1cb150a0a4d4c2414674472405bfc58a63c4bbfac97e0d9922a5e26f98e26694ddeb8ae0218c8c4e403c81c0258b524c90f7b2526d2d02bd7929cbf8851ad6f124fbb67c76e2e09ba45492aba3a09456f82540abc09991532ce9ee762bdd646580fd2dc84e65458823670286003284db3088ccd822cef846dfdc7a1a170ed77093b34200b9b4659e9e721e1a6cedf718070c0f6573e8945e9e169c26e98f6c6f8deb1e3ab9c7e978d0f04736d422b9087cccf24ddb8b2e31818d96d858ba91f2f3ccc03c5799851d9ebb001fa01d5c4eb231880fca81737cdf0f226a2f9409e3e6ed0aa0355e1d30c30168c4818506eb8e3922264e23a149b3e9b19f032af48ee04e3df9f3c15d30b708f07b165255c8e5f8de4202f0c9bd8ffff39927ccb3fa958dec2c53a6eaa92f4fcc6934cf4447968eeeba2bb322bae410928baea2b8471e503da3fc7b5b1a4287884ffa6b0892b0d1470026a17e0b46e1402103fc930b944020e1ee44c8b63a1875b0bbe89c4409a372f3426ed3c7b8a35e5cb34a807a5ba75e88a4a77330533c367aace2cff37b925ff708bd7e24cc481870db32ac6c6d80257bb08c30fc39add7e9a34568d1510602d4c245b14e2d76c381c9be6163b082ba4f2e204b4268237d57a411150e0108616ce59c31d7c5167e1eac6bb087ff974b99f98ea4600ce8ed66baa1e66e20d38895edceb593a95bba6a124e5a5273265f8e95a82a1a3fe9fab0909c0a0543e3a212915b7a13d56d7fe67c318ddc93163d419e26a1df24aa541c141b7e148b949e1d9729e70254a1d45a1bfabb7ff715cd4ad7d62d42945db64d6e0863ce812d8416ccadc12f3368f48f76d4eb6e99ddf151f561fc8b77a22855fd7b40dc5b7ba5a1c21eae524f6f973e53274de99ae71df3257ed58b8f30208faa141bf2f6d402c0378ffbeace5d20c49266a15d146bb552c9ed1e4a771d91383b44986e7d65273d78b91dd54ced6adcc4cc19536cacf2e2639f3c5459f125843a29a50558605e7ad19001433190d8e5467c71892c8490bb6d4a069ff7cf3006f3430f07175861c085338fc5ae1e44e03e056676813451e61c5c17aeb50743f870b54cceb67b6d0da506745f7de913c380566a91e00d8560d78f0ad47961c53937a99eb923f56263e40edc846988500ad946ffd45737bcfec1cc4bf10f43345951809566bd047fce90b16f8d39eff0188f7270f38c72a94efbb0af39ea7d9361a693e4a7e8a9556bfec0e251d00d0406837a812169614a4942fd0892140a55c4aaba48c8c70ea3c3791044ef0e7e671fb3c1a7a21a881a8aeff9637cb0fd7d222994c5af492456055d2582c49fa35f02802d37b8d3d2c9a11656b3293b689f23044d9b1f3a74289c8f0a437170d4e8c75ad8ba1cc332b2c80e0dc4e5700414f629a6778e9e7dac3ff0798341103da6083a6fb2800f361c70ab9af7b6acc82c17ed0c25139f34a6233d9f9174394b2f00926fd645c9e9dd6c1ca15c3e7fed1cb54414f1669daedc3c87f408c6ebfd0e9609a10ca1f000dab6d370b27af0e927aa336dba8a3628ff83e74729f079eb2f69f35408b6936d8c5cfd1ab185aeae5e45f74b062a3d7f2d0a9dbb3ab1a6f6c8c9eb94fbca42a833b011e29c24070931e4428191feefb9d4bf68be40ecdc6ad17aa5a995a3005861ea71ef18d505f08f41799e7b815840431d1d3a0dba9d2456e9b13bfd3a9b5f9e6c6d097dc1bcdb17be0f232f9777b1295164ffe1f20886f7a6072d1e3776fba4f494ac4973b81758d75cb2617dba1e3e2f603be95f60b04ce4b7a711b31e68f0f166b85f11b0c1aab342645d9dfd39870e20854f738ca7991698f609315d7c92a985b3ee0a615434bce8d2a02a27566019679ed7514d149d3a16de0f8feedd4bd50b223ffa221f9a8c703ce409c02a26a7aa4a4115d2e87531a16e478a5344401015121b60471025b089a26da1cb9559015ed0532d61ef37779d5fdce2a8f2a9782c99c89fdc74f3b1244b10d35ccd71104e6328ac4ef0715b347c1daa5c5250ce25a58907b379355d9637516cc1a2c89a680d327529589592e17884abe09e14c829f9fd7890bfcba593537bef33f83afafaad34d5b4af497247e305c9b350a9daee700b576a11489de93f4ce23c3df93dbc8bfb37eb42812347573dce887a5dc6e7f16c886014cf3b817df8c865be9a4713c52e0d70e0c1593dde9b8a6c7f7b4adc0b797f4f860f7927e23caf6e87f1d0a01b2ab476a8cc640e921e50386b38f650e664b0e86a1009211d4b408dc4f2a6950e83a7ebf9148853fa009e2dcb5669fb366d94c38c206cc68b2dec01bdc0cc0a78a501c226fc7c5648a16f999d39709e1394a5b4a0447e58f114d4422f08be851101580a4637b7befe3f77c4929e01a0dcbadd4d766178da332459a29ffc04f8820463508207a32d20a36ec89be438368368d49e2dffc2ba1590febd35f37760230e64e596bb4bc647e8032797248f693843104871222c9a7adcd2cbc28a3e6e00037491fc0b22be7baa9bbbe85664432416c688030c0eb7c0dbcef815ca76f8790e39b5f9019d3b798b5fb996c35670fe33281c678e684b7197cd05909f47f7c2e002c72a71c6488bb0c3357fead51334c5f04f526ed2674523c0a23605210d6f73f71c092e11ffaed059e7ac6534db75c3e9c87d692e01fb25267f9dcb91feaabd0765bc04793589e364aa6da39147cf98e90c7688a17710c6ed1f4ca2996c96b9cefcc8887a4ffcfe62d6b8795058cf8e63816ec55891e419a76cb91393779da66ecd54a1fb1ac5535d73b3bc1e5e7b2fcbad43d6bff4448e55cbc3b12b036ee0947bccee3422ed508a860b6715ae358cba24a153121577d2d971124efb09ac582a33bfbec33621c0a1a359111b1443e283237161e73b60e6b0dab9a5de22a849759432dfb9edd728fa8e80d7da5368e9bc40fd0ba703765478eb6a0b2ce7b331052188fb0db63d57b6c75e28d79ba03d220fd3e095f0c2dbbebeb3fc04ea3df785ab178a2a81d52f2eaf0120d09504c2e2ed9d7e13b1606712afcf794ac534f53fd1885966eab527f9ec7b02497aa490ab34f248762c2ef1de4d9d36b8e4912b4f2e80dd6693f196459494c6d81f2f1a1628d479a1cdf60b1dd7440aa6ad76f9457cc6da829159648ca1d6af9ec7018a37046318a1231ece3d926723ad3541acab7d21525c09a2a6ca1b4f0e226efae4e5ad939de336a3bf162313a65eda52b48032a05ab45a6d1825b3291e5e2e44ad4539bbfec6b96fd439101e2cc3ccb71593d3fbe2d032310a31bac0658f6c7b722d9bf208d5e3744fb7dc005876feac082b034f65eb4182ce96751ee311271eb065b5321a706a43e787018145c31f132692b9d2319454867734c3f55131ef322512b24f7261d622dd849c065552fa9b0bd3c7558d347b74f9912122dd517f87dcb83e63acf2735a17ee45786273b5f2e57ba9b503bea3ae928c26394fde6f78ed9b27732ade761d52fb4d24a198c0a55eb175f1555a881f1cfff4787f3d2c159023905c83d03a7264bce81bbef605ffb57a6b1e467d8fd618d2466e4bec073dfe8a326cc9126ce9547625eed51fd6e5c863bfe2262ef0680102143835f7590a7bea2455fdac3ecdc2dd094adaff58a9503b31313683315c1b527c8354fd06821dedf5c8fb915c1a54f39358a48eef4706b9103e002932b20b25786059765d202049d021a468a725d4096914d3a94bba618fa011fd6feb86dcb4fb75b70bab41ab258e427d3c7180356a4c3fe7aacf61bf339a877c64cf844029bd171055be8fb67fd2c27256a9ca46a41b736178427ad4b3dc886dd98b5b407d99046089146f6267b6fb9777f07cb06cf06239195ad729665284f7c6124cad90fadca5e311015d3d376e1417f0022070a15cb34bc45ac81e1d51eb6b44cd935560d8c94524a298d77843ef6ecc4b2875f760764cf84f86616e3cbd93dcddd6957bbcfd14857032ac811ea7eac62d72366ad23045e7210f10d86dcbd6fa18fdc8ff36b95bd69488c6fc1223efe8ba74f7c2537df987b0b1cdebc9d050ebf0f87af91379008993d900425c44b9ef761c48c34aadc4f1cd3f1f552678b119bc03025c7b06378dcf92f3c2226471c0a91928be0f0871b6f94b01a24a657f5f77e2d6b7c9b9854d8478ae4c9f5b248bbf406a4a4319d18d1208a422d5de4e249f089cc172b78d2aa5aa5ce4ec453823c226d958fc843a75d7622484d37e9878fd1644a193259305eaf8b0854618031aee25fdceb02240137799ba35cbe387cc9f7218e7c1f5b76b454ae3bdaaa8bdb56196ddbcc33f99181e111f97e7297e43b41a5dc46a60f555447274495efbdf7def89923184e205e4cef487cec71a287a4553102ad8affda65cad8a959c490a8c04729a594526a844a6b7dadb5d64a2965a9b5d65a69adb59ed65a6b7dadb5d65a8ff002a2300ae51e5e2094e97d681cca7caf133b3954e51e2760999eaa688b8cd194258b1859ca4b29e38a5e7bc974769bcc471f734f7a559021711a010683c16030180c0683c16030180c0683c16030180c0683c16030180ca6e9e499727b5bc163fcffa9d44f4e5e527214ea2e5c9c84e42323ffbe77dd39ee9af67b4fe92b0a874a89379a60be6474591cb9d21647ba6c2c8623b7538bb89237c992d6e8825c1a638c54d85863b97f8162d2754fb145544918f4d34661c6aa0b42812cb99e172c91db25f6b8c028cfdf206bd42eb1e7053779de1e4d9bc4493c33ede811100d60805d40ca0f0a48004600e8c3010cf002a0f4408001a4042083870000e06487194360742811122308901c503f4c7cf4e0e162c70b1c6ed0419261e4b0a186111a58e070b5be195abcb05cba1b31c8d00283c79d58565e5069c1051b9ae9ae543558a0b1a98068000316f083021280001f0e608002f44080010880870000608721303a08090224871f3e7af0d881c30d3a720e1b6aa00187ab35c30bcbe5468b0c31c0c0b2f2828a0d175a58a96ab04023c55a6badb56d5de86052aca5f194d34801adfd49c132db1fa02a8c888472408386cf0522b2d6da163a180bde1eb439393014f414d961c2da2540dc3965658f6f6564662a8063f34202e39822383838455444444541984b494743f88764e0ce28358a1489c94008c8078a548a87051b9b9b0fecc09600c1c9494d0cdc79c6502c161ba29194a4e484d74c0c144a4a908ae0e0e0144901020ab2a28fbaa88774d0d9040506d349814f8ca709172e72c099199a2496b8a9b940671843b1586c081715195d11a38454849111a222383838451e8bf5442187827ea4e8dc62c6cd0d4e0878747244d075363294949662146a6864a2143a9f524141423b403222e281a6019d7430f7b2642c2d2a3f3d3856ee056de0ce7489d800062ca0f19b871ffaf486c644560f0a78486712f090be10d05d840fdd9f439165c4011ece22033c9c4705e8d45bd6f4d03278224bda10e0e18c0de0e1f41140b7dce1a1fbb326b2244f001ece1b003c9c4becd0256f1934a463204596148279d84a3a3c9c31421ece239914a47f224b2a0179d841393ceca11ffdf6e91a1f0f1b073db1f52227b2daa6c7c3d6e1f1b0797634c9bb777078d8276899c86a9e1b1e368d8e875d937be4dd41391eca2b2cc5160ba2c86a211b1e4aa31a1e4a241afa72a893707491c86a25d743d9d37a287f66e84b9e592357e64b7b91356d580f258ecb439973a3b9b7f4dc2922cec3f0302ec520034bff01d135834eefbe10b1655a79188d5e781889543e24b2a6d05c4d1b0f630e5c3349aeccaf1ec69e161ee270c146644da519c4476c6d573dac916b7cb9ad8d5c99efa7c456c6b24673752357661fd5ede80bd4009001e00278902e5bf443d05580500284100016215d96c80730ec6cc464d90318a00076a8876971086033001b01d8ec48972dc2031876b63101e00100cf0ef66648509e19118c900e4242c07999245dd9521030ec99c5e66a1e08988354fa21956aa42b1bf251633359194e0f1b1e363b6c76a42b2b82c30ecf64653137f0e8e0c93c41d295dde4089aac4a34573608d5204483509eef24e9aa1547b7d264d559235db5ce1de9aaae566ab5d20cadf452c3025dc01b60cb4e9e35a6dec815a1c9a2443349bae8925c99978127069087055c0143d4f702187e2a423640a5c9a2311be9a24372c568b228ce8d74d1227265de85a9d402a8b402955460d89f91510a78818aa46b2a0101014da5228c31c6183736e960408c530e3e05c4f818632010cb8c8182a692154745433a985448490125d1bd9708e5ffff638c8ff1637430f847393e0afeff1f8682653e4c27053e319e2630065170673066876cccc66ccc0ec948a552a9542af5ff7faaa483792a85ffe383a1cc2930954ac9c032a730a5a1492c416f680dbdc00fca17c1c1c12932e3e4e4e4e4e4e424954a3d953a417530a993933ff5a74e4e7e7272523403cb7c526474058d51422a422a05769611636f6c8c8db131f62646494949494949c9c9c9c94f4e4a607430272525a99f3c755252f2929292580c2c7349ac278aa1a02c034b647bef2f119ab50f71cada76bf7b5fb66dfbddeebdf7de3973fb8adb57dcbee2f615f735b2fa8acbfddeed5c07e36ddbe9de4f1eb86ddced22154db6f7e194b1b68fb2fdb673dca7716007691d90ad83d14cefa156d9eeeeeeeeeeeeb6dd8ebee966dce7dd6ea6d24c2af285d94cf6e240b6cf683ab62210d32ff58940dbe9c46226b1b869159df3de3593a9df7db6e96d7a9bfadafccd7ebbdfee777bd3ee1b77ed8998dc428e3d3b41b9fba542cd060347b117b394b7eb26c5d1aa46eab628abc556ccb4ba62de26ee601ac43167905669276d7a5aa7bd41191d90790f2422f3090c4fef9eea60e6b5edc68deec72ad3249a2fec1c972971ff3450abac7c374f371155ec1b770ae28a7dc5ede1ee70b3a057d0304a6774402428674c2693cd6b2606692299b0c9649a4848136922996474403290de56cdec1eae37619a6ce685a5e6a294e033ddf48e2342f3fc10a7fcee6f441742017d3e31967c49969677300d76ddb9ee2dcb40b4f7f100900884249134cab22ccbb22ccbb22ccbb22164ee9eea766452668272460343d3254d6cb23a47f5a350df275ff2255ff2b5c957679b34e980f433303c81dc37cb79b87ebe49dee226ac612237d37798c897e9b71eea6b0f3b0839c8a48331bd3ba85db48ead08c4f47e18c4c88b9099c5e7b399c9ea9cfb7da7ee24784221aad8b7c0d30971c56249d3777e6078eff7cdae6f9232303a207d6d22754ee74495a489349572e6ca5e87e32189592016149c495ba6936899d832b54ccbb48cc9b4dd7bd2c17420d7fdd86e0ae20383e04049d3a09c91325286084a7f92b2bdf589f7435d65c36793819333a95596d664fb7e8c2095819bd8e60c4fa4e984f984993495e8ab5535adb269958fc99a4b3476bb73ce73734e22fb9739b32be696d9a28399776ebfdfe6ccbaf07e7631bbc1d95ce4a340f1ee204f70c467b6cb8dad08a403e9bdadda48f04da4a8629fa3b33d4df66a1716d792b520db6c86468e31c657fbf82c03913591a28b136f0275824f0461e60119a79c188a48b480c892f187d8ba361235119afb439c72dff41855ecbd0e667b83df449a2cca91d80dccb217cd4c4dafec775e60d93e6451dd647b4a29a538424a25a5f5975294127cf743af3935e715d6acd815a85d7e503dd9d62275279ba941d98235e72b42d5a36ce38fad4bdd96e974df402232736088c299705624e3c97670a6d32afb0c67afa862afe18c0351c5bec35907b29acc0613b97982b15ed9eca657f63e17e81322ca8cb225b2e5d17906b165b33d55a23fb135513367337365351fb962df6f301422259b10a1b9fb10a7dcfd7ef370e068e1308e0a7c5c8fd82a8e22654b8d80503e2cf64d6329a854eec7484bd0b404fd1a59b40471654def56a6c170deb57b316b4164e3b025db1e5411fb18eda9cf7d38edd4b8733a20dc359088ccde8c8fe95fcd419cc0d6e1527c08f1da11924b383d6d44a31334953a5fdc3930dd2f4844e6ed59c7633b87b3bce11dfd8bb3998ce6d52bfb1ff57df533d3c9f69dd3aa0a364cb64e4613552c145ff6df56cd1e416ce7ae838b90b9031b76718e2907466fb23d132e6edf3a5dc38bdb7bda0b50d531b19917100b1c8d8e47834174600f6d600b8128f6b7833890083a9384dac5e44e24ee927c27a85b96d8e783027ab4df6e70268c9aac6ca657f6f77e1fca8657e51095ed8c299bc9f61a8e169c2011fb7b9aefbccd9072442122f6020bac6005322bf03ebdcfa9444d5fca18d0e4f93efd26e3aaafc4b739a0579493f7d52a1a35177cf322b58b89de7647157d43332626460821a87c873326e6135b75d6ae22d3779d9079afd6f78f79afce4a6f64752caee8db27d33fc71c9b9125d8355bace184cd1f2d892f9c4941f48b98455329a4b15c5b69be8cb4a276e18e3c27c4d6e9fbe9740e879fa983d1b44ba11cdd4d3487668c4caf846290c889678d9c7e8b78e64c58af3ad65d8f2934d42e1aa811b54bcf194795c819adb577965d8fd9456dd42e75048c754c93f9822842e6a9348b5096f8c299146be47c7eb8d33e2afaee66b99eeec72a134125ac552972feb48ace9ca8428f812f88f922646e4a3f6562da654603b1c5ddbb0772e04c9235a95554ce4fa556d14f702eb5e00b5b2929d3d3cfa47631093ba6526a1713a99a4bf36702b54b9dac16415cd19fe03691e99dc8f40d45a6efe9a34cc38ea1863aa87fb8a66997d7a9db21339d2da3769ab579fa566a97902e51a9445c7d46096ae49333b48d70c75a45dfe1f6a1b3d2a3157c0d6b97507bce4c416c71a753270f71daa49452bbbfa0bce4a132d23a1355e88fdac504da68556cb2664eafe8e9ccf92a187ea899f38e65da9fa76f4b698caa3f41f920413a08931ff3f5f3f2392a0e82c309be30c67a5429bb3e0ab6a6d102d1c801d98568972f3b49f6cf0a77f6d712aa3933596d93337332238b5a258ddabe264bca000cff7aa9f183ef20d72aec6b8a5791a852bfd5571ba128727d8dd1bc65504b5fad455708c27b80b2e2b341ae0f6f0525cf56838ffef614064be4941c7b601093efed133a3966eedb0837d2fd58e5d38b5a55895a557764af56d5d84777284f6c69afa732882d29224bda40115db6d77b2357eaab8e0f5a07b9de504a43c923643bdde69c9f89fdbcfdc526f6164f221d4a1ecea37026c108a90c0b92872d7ebfdd1624adaa17fc4c328b4db2b3b8cdaecaf72db26b4b50305eb0b09f9724ebf9614f4f85d8d330080f882a9f7eba8bdf56dd5ed9d7df1be99236363837b9723765d7344dfb96791c3e65c9618fc31d87390e6f1c367158e3f0e5b0e570c6e1ca61ca7da0101c6e0ecb0fb4e1c556b9c0d788a097c946c5174f50cf516badd63e54c9d53ebc91ab45e5aa3357b1bedaeca68fd3ee26b3cd5e7fea7a689207e7d6c8b7c69f3a1e33df869616650a8629598894fce50c9fb2bc892aa69f72863d252f67b80bea7286391d2e67789b345bceb0c9c89433acf56839c3b7716eceb05dca84ea4ecd19a639c3330b11554ccfd13e664a5431dde2ce2e388a433a8b9a05805719529a1d1e5299210fe7128c0ef5d96bb20000529db9aa0777003f0444c18071ae5abc7400e35c9164af5c4f04f7212290bd872eae7dd330d5a13bdc4ddd0ead32a1c32936d17e3ad5aecadf5d7c21d52182529dc9d201943cbdaabff76301529d11303c780243553615e1a3371d06778ba0998bb9dedfdfdb7b6b391322e884119179e69cc326b1a3d7f0a5460c55d4c927845c55bdea77ad3666962bdb9e669b5d9c1f2d3bf003269a38d1eeae9d5d7630b27b562c8fba937aa9e9af15caafe8921a882bb425371055e86f9ea0a439c117b664d94614756f5a66da340deb6b615fd842a506a48efc40a67204994a1464fa0c474a29e513afde40a612943424ad9a2a59c1988da05a158156c9f74bcbc1b68a614ac1178f22c5f73a71c80db180d471fa69c80722f13deb78ccff6437f1eeddf351651fa23eec4e64662624f01d89091c71051ddf93a0808e0f4c82c54f1d3844fdfcb4229bb4fb4fccc4c859e023908857d03153a2630454e27b12472441011da7effbbeeffb6e81171ddf8ff8c06faefa743a9d4ea7d32df0a2e304a2e6aafbb2bbbbb64988228a2966e771dbc9e3bab99d4ca77bde4f9d89a34814a855f632df93ac5170ded69d4edde66ddee6715e149ec7759db7799ee76ddec9f3b49fae79a0e76d9af7ede1e95bd77dd338afe3e47dbfde93f4aa576b355593173b4c1bd32a622b1e8514a87af75ebb3f518e3bdd7bbd37f5de71d39be76e764fdee69db85fefded377badae974af7d60a8dd9f32dacd294f1b5769f5a8c9c4d2b174a67b266fc3f4945d90bb5d967d72276ff3b613e76d1cf78df3e69c73ce39e79c73ce397fe266d6791e2737cb75f51ba6777192ed743a9d58b418f93417242d589c463eed74dabc89145bdc74c24c9a4ab486da504a038ff33c0f0c62e3b0cddb6976de65e95ab16583a5f338aef3388ee3382fb6365089d8f232980ea534f03ca4adab516c1de775dee675733b715174278fe34eddb66d5bb7755db76d276debb4eddd49d3368ef3bc6f1cceb2d7c9bac94a03ef9bb7712f60dcfed4c178c17927afc3f41cae19c8737d114bb8f7fbe4fc36659892e5fc94e20bef90bca7ef61b22e2865801e469f6fdfee357d9fd005658472f62a6341997bbf2f4e54e78d46d95e5750873665c2dc1ee77ccd39e79c73ce39e79c73ce39e79c3cf4bc3880e83eb23fb9498c97a00ee3c5e553dac5c5492e6fa35d5a9cc5c8e55fdae5fbe95e77791f4186f0d043b3c0cbffd02e9c9c1d8c9c3e643c7519200ead92731291fa9439393339333996fbe43e69adef2a2f9d710e6ba75846f60e9beee1ee27ecfdc3a71118f8c55118c64b302a85873822660879857b3fe993738267dc049f3c063679098ef10dcb0c629915c8b805a4f8a451b3a44fc77c4a0f5be70fdb05d9fef4ca7efb9df8e6ef43a1fa670b557903c396d91ef60f0643554ee19839f05b0996f9f37372e752a9cfe7e819b883848664e0b9444eafec251071155bd4b68d4bb94f84cc5b0a6b5ceae613dc413806ee21dc44adb247e12e6aa33e6a95bd0bdc48b8935ad54aadeaa5f9e12983e7cca469d5ab66dadcb40aa755f6209e4bf4ca9ec313d6a37f4c2f92b1f95016654b1ff10fadea77bf2ba574935dffb40a28f685b2e828944bc8f55d248de45105fb4e6b4d9a84082fcd42cd95bdb52f295f6e5524b1578b2dcdd5d168f2be6df73413e763b26850afec878ab0cc9be77573b019ccd72c3263ed62ad9d41d9ce9f6c1fc3d993ede76bdaab7e14ead53e1e48391034819b890bea806c3781443a0e4848cea476e92f352ba3a9324db38161b644b6609872936b53f44414aa511f37c18b93fb9bdc94f874a24aa53c9127aed4a77c27d15e96efc5beaea238766523abe6e4fa054497a542daff8080e8b237b0f721b666dcbe00d165aba80208d9be87d832dd5e00d1657f7e7c90ed79882dedf63b44970502e241b61f125bd9ed8544970d0ad241b60f125bdced7f449715b2f7b123e220bbb87db6cf711a6cc8f638628bc5ed677065fb97d86a71fb1bac6cdf125b24b78741866ccf125b309256b2bd4a74d9242517b2fd2abaac923d8de8b24bf638ba6e8c8dd842dd5e155b25b74f892d939fc8ccc8f6a9e8ba32f625d17567e24abd8be8ba34f623d1755f27b195ba3d2ab662dc9ee48bad91db2eba6e8dbd165dd7c6be46d7bd91d1256de24abd3d175bdfed6f6cc5968ce3770763a9a59ff4deef53b968cf77dd63648185c758a3c6636ca185c7e8723d461c381e230d343cc61a6a788c36d8f01873e4788c393f461d3a1ee30d373c461c70d8f11877f0788c3c7a3cc61e8fd1878f1f390009a27ad859c863fcf11873788c401e6390ab2ee42bbc99f08c6bda399cb9c0a77bd8c559600f062631c1253fc1264fa5309671213894363736352f9a19991bb3a494847464544434241404f453c50da84051d1f5a6a9f8244f56e59076254fb56995097722e80d084495b97360edb9da3d7d9c96d59b65afd3c76959f6aad9adb566d5c7569f6a33b52ac5ad367155ab08222b8ba96a50a14039f135916327c8f52cb6dad49b7631c9b2ec99f6aa36f7fb502895e4a9f1b065ba879d44ae9742cdb212a9764c46b7592fa188ad7a2983e8b2347225b656afb79fe56cb5b12ffbd22c0b575df50c69b25a68d24857ad912b130944ca3596268b4d56ecec6563b12c76eff76d9468724270100c04e780c3fbcaf53fb00fdc03875726d7f3c03b300ef806ac03679c03db806bc034e0d00ee57a1cd8855bc035300b38b455d81b644b45ae6f152642ba8043fa44aeb781432a835caf82431a45ae7f0187748a5cbf82436a835ccf82435a45ae870187d48a5c1f030ee90e72bd0c38a43dc8f52d38a43fc8f53770488390eb5d7048afc8f52c1c5223e4fa171c5224e4fa1970489790eb5b38a44ec8f5347048a310d229e47a141c522b84b502b9fe38ac49542572d540aeed8514f869be8c872d3431beb9d64857f6aac9f55503d1657f7e3640135b9b12d16581803230135b3392882e1b1474815c5feb6b0562452257cd0ad1658786aeb0145bd914a2cb1211554129b6b82844972d2a9242526c9d9c105dd6c8080a48b1e56209d1658f8e9a70145b1e12a2cb22212921d7532323d423e47a5a145b2d5e4faf882eab2457aa5211628b2408d165977e105d37a607d1756576105d774628b68262abe4d5e44b43c8314090f1c10c0f723db522ba2e4d15d1755f72c5065344d73d794dbd9ef6c4568cd7d3586cbd388d0e5e39a8c141b6a941aea7f5d489286291d81a71491bb952799e882efb922b7527b6aeabdac8957a2a45a650e4fa8a41aedf62e016fe02c7f8087ef10f8ffce2ef32f0fd86579f8137175cb0614345e585175656585860802186186490a1a5e5c60d171716ebe56586195a3452504030855bf8094e956093a370c96160d449300c0fb3b80becfd845d9cc3a767983361ed3230fd0c6c3a10f616c80517b2d78f0d1b3f402a2a40412fbc1024b4b22234c4c2324404030c444531c4506424830c46472d2d4748376e2025b9b82429b1584b2f7f59caf5376686cf20d39aa14193f24201df92a131934293515ee02ffeeba5cd0c19afaf36d8e69f711b19af6a4367e0e3b0cbf61883387491ed51700a0e53d99e060e71b66fe19046b69f01bfe05025dbb3700843b677c1e18d6c7f03873364fb161cd290ed65c0619c22dbc780c368836c0f030e6315d99e0587d18a6cbf62ff020e630fb2bd0a0ee30fb2bd0d1cba80c37845b6b751c5fec337aad88fbcc05f54b18f8149a28a7d0b181555ec53f824aad89fe047157b139c1255ec4bb02aaad8a3b08da8620f03b3b4bc44157b16184754b1f7708ea862ef02e3e0234854b1cff0101ea28abd09f7e04354b15fe11fa28a8d949aba1f33c336aad3aa9daaa33a0baf97b1d8aaf17a39456c75afa720882c23a807e84bbae6d0ab05b9ce30112f09e6d0cb7eb3a7d45a1536a1379dbe0666019bd05b130e9b70a051aeb388cacca5ec354f288d82f4dab50f11014deb84d073b7fb41cfc91deef55427b6bed75317c4d6e995f270d73e2c73467ffa668ec9c526144449c14784e6212452d67474a46bc7c512995e3b182ab3464472a627903c2eaa50251aca6727afe4d8f3442c6f1f67a995925e4125d4422336b2baa6552b1da88e512533673611590d84b8aa4741313169d1a2b7f8f2c5503ed9cb649e08aaca8aa0b8e0b3af1c5b5cbb483ad42ea7d7d322cd3262aeeabd91a382be9f5cc1f0569bef43a1fe500583a3accab107063d798b1da3a45e48019bc9ca5e73655f73551fe3338e1f270a8d170f6ba0587c2dc5479732b542aea7409335530e569d199bd526fc6880e14d01c38b02861f08861f0643d4c119d5e63327bcb73601983da834ebd92993245468460480001004005316000030100a050342c178168589b4770014000d78a04a56541a0c255a0e63196390418601000001000006040060804a046bf5fedc06be292428f72ccb61d6ce7c033b807ddca82ca8ae0fa80e1d97a2a0ad5fb73c5d57dbfa8feb54ae3be01c6e6d3d5b5b49b3a6b7460516d548361fd223091fd93f86b86a89eab7e64204dd73e6302fd9249e5e70ee2ecad5c622ba890719a3998f81bb9a708f87103fc3c67685141f47feb62fda6f9077f8a164a6b27ac942aa9ace0969e06c138bd8f08369b3792ebdc2b9a52308a8779d8a4f82980c36bd8cea1bf0988914fb4107d93a31f6bec0fd25668fc09ea090101ed20162400f81b72c4cce012bad41da87e920572e625c83c2752cf8bfeb16895a406628468e01a7220c54028e3a0c2224dc48349cc5ca79183f33daec1f034034443a54c44ed5f97f15cfd92be1967f11dd15d547e2c8284615195a37583f7c00a3c48dd6572dd7315ab69fea63d41dd0dc95adbabafdaec2ab2d596721ef228cacc2e98c79aebc933a0ae121c0b8652a2fbfa3d8e50edc55f6d2f9e4518aa2268f405348911df311640a29a2e33ff24dc735751424fe22a8303107b370159d82dfc9926a3a45bc893f3f49785be903301b9fac3bf85e38ed4af19fe5a8a6c554cfb65bc9b19e38dd6dd5493ffb45fe3e78bb2f11b37c89a936777ae2886dd125c7d6876411e1e884e131020fa162df4128e7c5072ad9e42297e6d78156c5a07cce3831d107da62ed2e1c9b703e274f26b30979bb48cd8f07d37487f08a2e3f5476c9f7e2f53438d55d0a953316005e0c281da0a2653cdf4c36e5aca355a1a2810f481a6b1fe7fd017f16ce24b96c3007070bb972ba1cd3915d7c920b8851dd78571f3fd15d9754f05d6a8600bbb3058377431f81ec3a228aefd20502d89dd213bc3b731016a88d176a784a636673313a9df8f283de62f5b95604cb5050f54a5c0245f811ae96b56549ed58e44306c443b48349b1b1086c832eab16a73086b6cdeecd592967b7d355ce40533c1fe5314e59c47e9630ca94a48f40b4b72f5852f8068216b1b60760f8979f3ad15234238cdd7ef7b7a098962a47195e4725491e8d7d4e09d33a0da56a9c55dad54e317408fcf117132dbb0117de032f4487f19fd6205ded4d6dd19fce82b0549595018febf9f4ebab17827deb196065ca1c64748af9f0866ad68e85708252386c36c480a6d434128751101272fe37e1ddb10325857892bb06d7a11c41fd92dbeda1bcf62b17bd1fd237228e4e520aadd4930645c9203f1c8a9ddc4eab9a404a4ef66c196f9c12129f60ccfcb8a8a3b8e2809378284da0d4083f35dbeb8b28d9474a1f32bebd2fad0c284e9c3d34d85a8d95923cc4b045b9e797143f69057a9b60c8a67092c64efb2399e9764265040903b5cefa4f5ac22314590486205982d0f2050ce28f7eb99a991aebf547a41af5d0896ed718086d7c5176c5e2b9042839a8ed4cdca38853359e3b133b3c21772ae95ed9c2fc93ab8bc3461bedc9ef4c6ef3dc6ad03b47714510513e9541960eed59e73ad47587d3c825e382f3f5197cd66dc61134b2af65c2b47e71eeba4ec0f87066edfc11b804c4db9e5cb0e80ebaeff8ac73cf07389097d608ed7d6e0cc34620d0986260a55e65dc62bc7fce210bd5aac1c9830d87770f51c3fbaac9b8358999274f746582032f2330df9b19469097a3a6549de00939aa73a0d8e58e681fd1224cd5501dce91e766da4b9164d5c7e73f8b7291986adb7e6102eb19d31f88379364c2e882eae5ac6aa6f3d0242f6a4920256d68a0c44d5724a613849074ec974834555be287b898309c32ed2b49349c19d5f1dc8623f74681d58afa5650f2f2cbfa9fddb8ac16ddf839b6e2dc66d921dc2e855c49e6fd34a5e3c1f33001249e9166108c31d7ebcdb1a0f62679a24d4ad0bda21d9c400788c85a4e508696b31694a1ec506f72bf6838da43c3059f021cacffaac3bc41b3a3ba18a695fd2e82ce0b3df1bc56a5c34a9b2dd2b723dfee32d7c216bfd85b8e5f32d07a15b01af0d167fdb448c7bea700da933a88e0603e46783aef5f48ce51b0f30fb59020dbe145d72f9c22569f24cff4300641a8c009cc21ae7bc56c94c2b16a3797f944eb4ec88df61aab98c83c99fd1aa1f27450890283db054c9f2e917fc1874d7cbfd363c6c1147be31a21e63e3ac2dcb0ad661d7d8832c2340af0a48c1949cec5ee536a6c988da8a499b526700ec9263342279db96b04437cb3d31b66c957336e49652a41493972052716c296f67c45532a6a73206d2c765cdecdf4926cd955be8369d2323a176330e0050a82c8cd6e45e9f745ee6a667abba35afaf592bb329b9ab63a710faf979a5147318a9173f5ac7c0fe7587a09fbcbd18f2de217be2d737f58401d6ea38042fbcff5c0e76f85e8ba358cd62cd24b57ff7320c705887334b9181704a3b67f1f62934bffef60582323da7e3de0c35f3266d22158d6a2ee9e586485c31a8d91fa2b831b76c960ce7346983854044bfc446233e08bb3dfa31e1c730e27f1851dc4a40c487e0928753ab39730941f443582175a4bac25061c26fb7257999d0bfbea50884dd763df2770e0cabf597df7b9315d6e44a917d6cfd7857ce397e4330e9d55e8b772b35fca4dfc1de9272eff86fd4f1f88dff7820181496c034bb79f5a6c5aa6de7d188abebdb5c91c76a19ff21bd3eefb4c8ba5bebea5341fa0d366f2252817f417e5a7fd2ab0e4815274bd389602dd6b756e4804470ce596ae15be00f1a2dc90d345ed3be1c58029be136e02ccf456dd149a9f926d430d53c05c279c1de189c8de668ffde600eeaf5bdaede0f408eff07b457045ec342b3ab0678baf9fbc0c6ac80a61af35d85c5554ed5cb5c03ab8aab91d1f77abf14b1d6aa2591a07cefd1cfac66f7445a9abd92ed8a14ba23e60a9de82f96bda5f4af874cbf5aa3bdbf626af737bcae3b89e3a431c166abaf2dac4afae907a9cd4474ba7f0f4d9d40f4a8e19824457e381526195e83365b2e5a9e99d4d9bddd986a99cb073efb0ab2aebeceb724b1a320e9d485c0d8003d0694b15d35038742ee151c0fa75b321b47c1fc09cd322520ded0f1e3c2738a73fee035ac139fd60a26b97052c96d9fa41405b6d614b6f9169cddbc5a55c84aff8265ed88dcea1839a9a75bf29c31f3a4f47c645fd6764123e70a41d72ba870460d736f469f9393bcffa2f4c7c6acdda9527fd2e18e7d8cd6384b570ab585c2b7ea66321ac41e8c49d71aa9b66efe9520c210f763316e8fd92508058ca9fb4fd4c218da27f0f86b2fb96a4dabe9a3816dc45bd9cd6fce19db98386439c0aa7c2f438fd31d69db552f681f56f1f06a2932220f9653c461a63a1e478eb09ae97c4023b8350f4253f047a8855914ab8610fe0966dba0346e41968e0cd593a8474873271b98a845405d49573b84f93e0b32ed336db6b1b1fb189cc4b3e9df3b0e5543840d3024ab298e768df8227bcb7003eb709318e4c0af35db5090652cfc8ac95b6ecbc834f6d465d7055ad236d61bf78658a71d787fd4a6dce60a7ece25ef5c8f2ad31e5efbb5efd0c0192c6d497cad7668f4edd0f883e92abd5aedfa512623c1946752b37a1ee64d8cc198e5759a2a248c0d295a6157813ea6a2876db1d100c6d97e9231dac04c3ca4a767a2ed91056dd9c9cc89c1dc5242753bec96bc564aa02400c777d74405fbba0e3bbcc3bba4ef3b377a19153df952584e5eb51151b3f02c6956eda0c31590a8577a2897452b33363a309535d0f8c82c681334e08b8efeeb106113698156c3714b6817688c8ad199a317dba88a95a306e614c722504e9cb009f825c2bc8003bab06e645714b28232eff48bb1b50d142ddb488488ac49bfd5b326c73fc943aa1623673eb413eebda7865b42363000d94b6fe221b9b0ae5bc6c8c922331a62e95eb9e4a094f4c3ed3be01d67dfbab975d005ba6df8bad332462df249a4d875a3fc522ab6236e557c15025dd7957ca588fc279f3e21d94b1ea9856257172804a94151b6a158bf155772928c34f135e52f45ecd76a03120b700182e6ea4916f099d28d80e4c48ba3f4c39202ff3b6a5e8c220c43fece65b52de4349521628560a9d1027ac6fa6ad7e8b829cbb96696d9c71872b51367c7e43a58cdf89964a5dbcb937b17ad93ec65f1b31f370b848f8b1f3a7877b71b93dc3cd949d4d54b322724ecb18a9bfc20ee8dc8ba18d0e2ee1e4147817ed4bd66f07a2bd24f771ea8c31156453167703f83056ead89079d5399f24d90cbd5921d932df223e91b44584d7150bc28a3993c0d0c04bb4633bf7f0b8058a186fae7324f05870d42292f61c250fe25d4256fd9e90cf22e9bf1744548dfceaaa89c2a10d883d5148dc2203fd834c18d4219d39394a72d30580da72529271c1f4070ad91c4d0db37597865f7e994462cb61e855cc85bcee810735cb5fa871c087d2752d137e688c7b0b2b2ee4a9750122060f3946a927e9ff1e2f880b21a28ace848474f7d0f82f04b8bb5258c285c8ac32c33deeeefbf967a6151cad0ba5f75113d013ef879eb4d4ac0006d12096ece114c37376b62c2d5024aab0e052efef64d0daffacc51fdacf319775283f4c5ad10bdb9c722677baa41c9a725a150d001805e91ebb8deaebec5ce69b5ede0aff2b267d95fc83d516744a3b2a2651cf182f9b9ecac5a60ba693acbce7623204b0a0181ad4e40b37647544a2c5863102b2522a321b0a2222218a182229aa10723e4a05315b4a0129ad94fb2167d4f2313154e92141d4e001649d6a32ed00dc452d5cc981f5bd83700cd20b2e2a0470eb9e66db2ca360b4c194ce9100f051b06704d4b73555f50e5180fa809f0e6ed5b35d2c86de052f56aa64be4b40ea5c369cc5221a39fd6fae989268e18066f51efc36301464d7b787d78b337ece5fb0c5bc515c580a9065bb785bcf833690a6d63c7de4daa07d2c7d2425188048233c90133b21f6613aa584b8937897d540da82e108ae844110557fa1238b2c8e9b114f425ee26686e2d0b2fd56222f85d150a399a5a2ae1be1ecc71fc3f43a93181e9d662869f268df026de4667bdca053f12592a9955b44b39e58d7598c53665eae760ea4a7c0702947bf21c04d2e8eb0cedf6c05876b6bfc1a9b37742401ef75943a6147f2f2f2a63beec2d975d7d69fb4371f6275b385a887137ab6f89f01fa45cec843d7aa8ff01e49f07a05111bb0ff21ec6c3bfa8ee5f07a11a0447cb6e2f62733e708dc53a958afa8da404e73e159bfed0e02e43b3681b899265f8c63d76a9f6b374f0e87c315dec96bb817bd8a855eb29b273d50d0eeaf57c05bda9df2042010ab7ea698ce9f4e5a8a20ae5f60055e9280023eee97f00cd42c2e08fa9bc0242cba4f4c277d780306881ba5b6e5423eacb9af7bc099f6ef0b5409e480afea531539a6eaa6abaabe9a7913ab687e704dd47ab7c4420cd631426d43c7bc2199d8348799d001ba7bc293775fdc6fc992dcdee6299f3e70552c70e9b1299d68476e0e28bbcc128acc64a6242f31afdb78489980de901090df0c763738cf8eb9dd507107e4e2d66c275ca1406b87adda0a7e4bc4dfaf8c4c3c20714fdb54ee61d415c48c9503d0bf499785ab3a2e687bd2616dcd9987bffafee918f2fcc1828ce1a6b526241f67da8c4ab385f1cfeb53dba29321dec0d9545a903f96cec657b27798ab17792c57bd4d07d115394186d211c463592f177b37d00153c9009f41c8260549dfd7af8413d62c389b30493d6f0435ed17d5ec42db97dd926b696f96a6b19ebe3f56a20d8dd0cde6f19071dab1bf60471fafcb4320f9bccb4bac088c16700952d180dbd871ba2b3531c7c4a9f41b2873e7854d2f46a7c167d0a420561324318bc09098f26369c951b3257e31f26ce3ecfa2c6b2a872e35719704329d154b397be8c5e8e337877941aa4e686520f1be03421c540bdf847e96189efbb03261b93ee0a7f8cd615c1b7991f6bbaaeb7bf353a03360698db1e9b0ac81d6f4ce318680b3147dc91862926822f70a2fd66c4b6f01c22b24837690a85017862cf3d82ec4758d8cc5b1cf174118c547d1924386916425a978c810d15eaefc5b32c40e41795722d472e3d98b09718c42c5a5683a653651a0a36963b0883952457d98be869071099682b13d99b1376f9dada44d5f5d3969b32dc9e500200a7ac15cb82c7fba6c4b5f4edbba1456ed90f66d2c0b260d6964af6c39382f6b69b0b78f2ab01bbadef1ffd024d2a995ed6677452526ba9dadec4ea0ee45002ea2a03537376771082480d1f8d8cff1aa1d5498c74b869cd9da60fa965b166b9a66c54c5513db5fe3a6c89069e15e0a195b139fba4566af353f00b556ff0419e5697e34cedcba2412917ddd48e1ab412c50f32934600a69a39335ed75b912c4a4921c2fc5396ba8df39b59142de8ffe3ae130f92468e95b59216169e1c2e2e6388bdfbf51925d0b1659a16244a234f45fe04e4c9a2d37742f9ac11302b78619726e28a86bdb6eb20b3f96e2d4b53161c75792585ff37b8b147a57c57cc30fb703759300ab0086d423b6e8bc78e58c2f7b9b718dd1dfa71a428c048b805c49b055f095935e0df41227bea061e6a374a8c072254e268c8edc2208efabc484c9fefbc264bb7b151c602da47c36cec3a5ec4a982e7fc365739c62910e7090e4c86e806aa62fd09648eae238df8d26a89ed87030339d434afb9c12e2a8129262f4d2081fac23c76cc04a6ba56b457b1a6b6e669fbd3f1c9bcd4156c1adbcb048a1f7786554cc63311ea4236ac999b3b0fe4c0c697dd3aa2363c2c05c35c95431c8022d9bc3c66fc70a37615581ef9e9e313ec0a1823aa53c29fd1b7d120a9d15a31e2dbd90117c2879a07e4dbf73fe7fa3be959110971df372e4423054f088a4256c1c1c33695e211bfef8854c3cb1cc4e0dd8bf41406c4f93655f5e88871331dbe14b72f60d0e5d448ab2dde0e85ca0b4e60fcd16a6a359bb715cf612d0b095830aa3c7d9f565f669c68f4d73802cebfea025d40a2b5e01f6aabde8987ff111b98a10fe18e24f029304afdbb7c185ce13fce104f18ee8c05e09b0f384cefb02917db24262f404468bdf53672693d5dab72aa10e026acc3fb5865ffd1d2062ec2178f546edaa81e6848c5943113e0e053d198035541d24de9e5a2aabfd0ab1214d80a9613123e3745f89e66e52935f99cc0513131d2d4af112d0775c6902f22510b4698f8aca3476dff69a207f3ffa1effa606db61ac0959e609334500da79266ab03f51e236157f689ca4771e8b674c1a367a9ffb22b92b6ae489529bf7440d2460b5514b00cb051024704b934d942babfe4688f4bfc94ff3587985a4ada0a1c03af07a6a106f05074281b419b3a0ad25f48dcc8ca528ac4a43878171e1f2e10f3316cbe0beea3331f9e56d38197dcb40352c970bf6cda2812819edc3392c514b3f910f6f48af63a42ad9734b834d3d2b45d754312c2f42df367c597970be4c9128e05be588dd748622ce19aace0d2ead061851d02213d2af164d018a90de9c035a8fefb9e73b7e884e0c4d17f82422e6d2b590288cae99f90c0cc0bbfd168cf5e8ee571ad2bf3520be9e1b3d0f8edff4ea4655d9399ffe790fe1ff5998c8c889610467976d13c231e661ca6518044d9f9cef3c16a367fbe4fbf9be489a7c41c3770ae7a7aa0fb5f50cc399f2ddb915e288b6ea1a4c2e5b0092f8fe30fe95f9ee766504c10c3167be232af96cead9a8bf1faf9b44a63998f38dc1d4faf488d6c71138f5c5850affccf3252fcd10a181e45255f23aa80d6d37dc19d90d5e75c50c3165b7c8338c72cc28b49f15f3d8c8de3beac9fe8b4875a1c0668acc0e4e364225fdd12f9b34203c4a27afe0a9a4a8091d68c65aaa141c4e8d295ca90d47711d952de225513d109de9a0b6831747ea981da48e5c531946083abf1fe52e52e4ef53b5170a150ae653334b07ef0e6e9469db200929732ab4570e6b8ab58a2eaa6f415006803a29400e08da8dbfd519d67d5b5463f0176c51d6b9c2ef2609b54d952148caf4c69628a6a443ec2b74eee03094bb5f395eab6465092bad6687ba406e22e69dadac8460433dcd899619770077972e0bad9006a8fdeb5730be2a18cbcdc73fd00251683a1cf2d009adbb49bbbe392c467d7488487086cabc673e6d5da2f390e69ad38a585305f2d8615e7ecb01354f1f44f23a73bec05d66fd1b00d689c2a777a33a4b1d17c0e2cc287c5dffc626d97218e8653f13480724b8e4cb433f184f0af5c61a5d493ecf5e87539de78c19de42103cdeff1205143c3c3b8b67d0b10509e4936c5dfc083343405cf1239b62b0d14b668eddce605a26bb07a25ecd51582c961febb7839dc1fb10b9f4401cefb59568ababa686bc8757ab024ab1ad8ec1623f5fb89a03c345d561ce08dea89bdc88bb46171d5fb74f6a1c0bd57f7c43984a5fa0f9b3fbe896b83590e5dc4fc8e6e166b64339161fb44b805cd1ece7d50a06e6fd1dd871427945f7767ff51242312dce4afbb0b907bd23fb3569b542e32fe43ea6ddedcef7030f1519b94315d28336966e7934885d292fd1141803e7cafbfa23fdab47a75a962f357a568a69990822917a455f5ac320743c740bc18d75bcc30b18ac99d8e6011ad9c83f2b54cd035abd21dc50a47034e48688c7478e63544b07f5716aeecfb9d089a5af014735ec1aa49d51e6118c5a1599d466d4bccc0ead1fbbdde0dae75facfc20fbfc4140826e4faa0aaf3138424d3d5f5f63e6b23463302b7084d88da674924ab2a2c4d03a248bc9ae1f7d372f2c736561556f9be11cae448c6de9d1c1b92436ae36d358a7a1c7d7838c9116c1e2e9499a05367e530eb2f84c07e7b779a0c8cc4e167db3719fedb92b88f6f2a2cca7c27679190e6dd65036ec156bf616fcbf89d055eee0b7887069c3ca193e5262ab85d3189e19d59bc0c06640b0952da69fae0dc01430d2cd688eec34d7124565cb1d8acde710b94cffa9d241f77c71862620563ee33c0193e7aa8d44ca3d3f88789a883809837358945b6a122e9d852344c9b61cd1a25fd160ebe4b2943dbe6a4e8cffb41f3dd7a73ad70654a302bbadcf9a3f0b10c4045a2a10c59604a127564430af0b36b5b87827c6d38f11be6211d7123b1ad85e73e1500e375e0024dd7f07966baa4f9ea6db39ed9743f2d3116872c9fb693710b783770a70dfd8aaf590ac5a0176f55dbf7d108e5ef9d1adfaed14717d8ee775db90583e4c4550d96f37a253cb3074a9a4eb426277bb53d63c12e764ee8184ee15a5143765f50718e30eb233958137edd57919686c7bf282dd3158a40840be32b755ad2a28d0dd2e110c02e4df9145c401328ae002e2af5a8f13adf68649a0ece3ef0a28b5cf7696b5641586141cf65c4dde37a0ca2d7cb8d5c38499508aea8d169c89a81d459aae638470fe3cd8bb8a7a6fc2c479a430d76684580e033472e0270c34bf2f2264528800760fd4063f1563395683a88da392b5b176ddc7e0e65ecc0cf67b5f581eabc03f6485d04b394ab4645ccd83236a8e90d52b842f32f291c9be299e81fc8f5e1d4f1d72b44bbfe101402aea298570bb650b6415772a86aea579e24e29d1b5500c883b8500f33a70056e3066c5f761ddeaf8a0d676604137fb6a05d52da72639f6c17ba045baeb127543ffb00212bbe446c0913912c496e4a507dfe7bea9fecf15e2775258f27579a27a19d2532f08995e395fbc4bdc4acde109f43aad019fef4c496315c25bdf52a910b3ae2c8d19a557bc5417bb068c14a5fcaba31b185aae32cef17c0919696d7b09bba4c0b2b62885b465f13fc1684ee4a73abf82ad0ce9523a9ea3e1356f420d583a553c01cd0a18d693f292bc944fb61f92ce150d681e31d059fc99192278194c882326ae5b7fa98060ea22f336b7543a2732b545296c1e4abf9216abec19f79848d99a98a0bc54dc73dd7b7993a09b3d2e98ad2904f13879f181c7fd5b3c397bcd26e09c2023c8b09c9902f51b942de84f5f43ebcd3c042c8f103e34a94df687c30fb8038d423bac48a01f4c34e761a2f8208fdfd6124f3823141c24f0d74b10fe82fad5c4af11932bb0a04b97c8b398dc30636e5724dbc26257c503a54c2bdf59f3c3ac3a50cb3a8b16f98c49fa0bf44411a4d9b02ae01320f0da1954b1095494ff449a20c210f03cab389f8c8b575fbdaa61d319f38ffb30c7d32a434c0efdcba1f202f04055c9d504d6a28e77bdb2b59463acbf7d7b0edc2156511d00ff06b294f13dabe82a5366db453dd2759ed080d0c1345dd484149126ae252a7413d435a268cac2248b645112300e78566921ed625aec13be38b4dc86f79f0a26d43520b21775fcdd1d1f4bbe894f8a27fdcde3fe693630a48c3d9597e5d01f25ecbc8e7f7ff1b1572f38c053e48da9aca9e5af6554f2edc1668739a15558b8d9ca491ccbe2b044546fe8a4d448dcb7075cce6f28b77e03277d3ae65047848f28e10416b4422fae6b0a36e7f7b1710ef0ffe4d94f73c77641b8535f66980efe7a7a1767e35d28be923bd8457d405cf8b0dfb09987391c03f2171731b07f924213b974d1d19afb64a2af0aefc08483639e36aea67480a3a84fe48983d92a320e55d7296155ac813a1f0fc36329e9a4f13ce9790ea6d45dc8867d0b615a06ebd159062a62b1301a9153b088d2e173d5a5509ffc1959975d8f584718828ba5eb0fcb45b6059139fb2ed03b73a388590d1e35ab6feb9d3d7d8952e0f7337daa4765a11753b6ca511f8e9c69278a0be94a6fffac51ad740783fbed7eee179890bc0d42637b108fc48fe43474698d3e089d054c80742c7b1889313a31b1d1f898f426c03c2747c2402bb2e4b16e52861c12425f5def23c007785763728b293bef9e32731a4531847affd80766b2169ecb292117f2ae48efd16d7d3371bb317364ba8f109a3a95337691affc8811e003efa821d3f0c908b79fdcfa762f51102628bfd976dc4545b4bb61829dd94de21a88a87cacbaa9cb84229a160c4211c9c415538d5956b5a21f2f3b09ac37f227650f4e3154a68290aa6b0291d8dfd99005f5b1fc20be9dff77d3f9cd0a82eab10a11e148fa38a27ecf047f39ba62e460db9b0a9fdcfa922e434be2851d16b862b46cb0376b300876700eb1934c390e80bc43280dda6d50c496e0f1214b0c08f583c81560add0d9b1dd7a6505cc7b16b11a3a937c5507aa088506f130fd31faaf72b071c2409e7828a40c049acea849dec02a848e1790ce75d414284d19e5a12508a06a0ace2a5a2f108d66591e96f0feb463b0e16c6eeec36b9d836881ae019b2b7a933f8124747bd4c14eb450d8456d6b9e0ef8b2b595ddfb81aca5567ee1f4a381493e0f17c0077f4a963a508f6604e6626d769ced763a7cace67d94899176c10d4c08a496321fde17e19d82a9902e9ce54fc3d291dc9d6245fecb9777c9613d15835fca1d2d3f1eadcdcf8059268a19a2fc347862e2d1790e3b3c56a0dd0649c6615972766e6dfa17dc85242dcc2b519fe6a03c91266b3340f6276f6895a4d51d567b4c6123825ac1bbacbb2ef00ac0fc0487f8b611a6592b68ed3084d1ba9833f05859acb0348367abfc86b1c021ae299842c648fcaf3720ef500a2887849026ee643b9013a36dfa4f7c173b2960448ed14c1eaf5d7bac8a6beb1aecad4ad908732a61079f576a31448df5a3707bf59e6d94b830baf211ccb4b1f12a63398c5aac2b7978c5a875c47c8bb4f8a7be587a0bd00e777230517a21648486bda9d8734cf541d113b9c24c41e75fe646689b0df085e124c1f3455b9b28dc521603490fa46912f3f846e08373231122e0122d5a9f058d2bfd1fd1883fe26ece32db1259fc291ccc58e393db02db57b07bb1b12d106f47fbae053231f451390633819822ba0c45c53548c6c03875e4d8b47181a923b9e3208bc7bd8747600889929ecb4ca44b38b899dd524fa931c2e35325256c338ea1a4bc2c8e30fa516244daa5235c1509727bfaafa7b30b42b7e80007df1cf8293cb4728224e1f8363b7b1823c1d4b73be31b0e2a2eb5af9ad17bc33033229bb42f8407a145374d6164a56a4672e7e39ab29389774cf4662c524f2108cff493068d4be29168e78607a72424d99ab29c2af05a0dd04c79955aa014e0a1841ac666e1c47080fcb46e737fe18caf92d1c73209e957550c88c8e87d1fb65759700b6d65f48435b70b090f5ac93c5513cbe34c2fb4ad954186016afc18409b9d064add2698adc7cf5c4025cd85f5bde25a78050f4b0aa22d0df36ab88873e770e8ba41c73d704e49a2f0892a60cec406198377c9ea8f95dd3818c9a8ce2aefdf2fbf0587361e96f1c37021bdafa14750b5a5542164b79667b01c96d8173a423c6966225471885981c38ff89b6f61f656e30cc3e4c4507ce23d94a85c42b1617b2549b9ba9cadcfe87bce14569d16ef26e44dd9f86138441aa5a4a01b11b1bcd3817cefc42fac7824918bea82293d1722b9988fd9458aebcd56b2fe85dc579ca72fb51395824e7d5a185809ae88086baca0638f332677fca7620134e684f6148b303f9d27ca50c972abcd54bc49be214a7743297edd6842164a91b2fe6e9c8d14f2f5ed0e196ad5d618f71d96eca0314a2038a8ca1815636ee8c470ccf3de02198524e93066943540f49393f36129d7fe8db99c9210372ceffcf8925f06c80d3de221267242e48aafadec4d7db57b731699bb8d2f9afaf7770b542efb075f3dc399ce3aae2840a449baa101a48c6a5c8f072ba6797dad70794d8b817b25e3edc19fb772509afd1ae1ede5d556d6df05954f3226a43126fd3bea0023f0a75a8b0dfa4e1aeb2bf6fef2ea11cd174ed6c63a7a78a17c08ce3488bbd16a5357088707b51058ec6049b8cd0e67106555e663892fe2dfccdf58460ed7043f2eda11539e4f7249896f9919902cf4684850fe63884358b7d0448260afdec28c241ea9f4145a8a610c1a220b494e1222c87375f60c1864eb35addeb78b6bcbb6290ccf24086179a24e339a7e33301a9b64d83172b5baec21970da1e2c229427d39b4f77cf83420fc333d841c176546d70e1bdbed2e73d1ad52e581055b1a12e2ff366f0084362133b4d1db7f99ac2a9546923199c5814f321485cc12945c8ef607dea78192ee6552936c9e7705d947257be0b393b260ae6e873a69a68902429678f8a89aeb799a4159c73b881247b728fbfe8b18e701b30a5fa09869a20df9893db36e41ca44e8e9d50b48fa2f37f2e911855e4c530161787335e4e433140c9271346be01d58d19163f50d7ac290a520e836d1104214d108848846e27ba1e431fd31cef9f377264442c835c6a682069f510db531c175a309e4bb04cca0c6c6626274448d1b090995672fc9de9a72e547bffe4feccf5fe9aa4166d70314f830a67d80188ec734e07221d64fd569415659af4500a261b5247bccbd70269846d988561467f433439b758c68888347d72ff05b727c95558ca0c7021c6cf8152d7e944c06134ec28bc7b8116bee0c71d5c0dc3c8bcf4f8652fdb1828193f3f70cadc7a3a28a277d9b4229a2004b682ac2adfa01a7a1703e8dcfef7104ce84f36a2e7e400de40d1f21444338ffb941701c279cafda3e102b3b42c11dc043e333a08c1c7c883281344cb381f2f4f49105c11b18867bfc4fd2b94f784c268a843f17d0e3c2245cc951b2676988df89df944f66a2a0b0715d71ef47a61404b0ebcc106c00fdd8facca6f051c1629f9c7899598eb18a5870d93454a6253f62621431a2d93bee75305023cd8bd2eb4ea5088678b0bbcd4029d72f5fd6c1ba7e7c8cdc1ac80ed5e03c5137eb63521bbbbea756cb31d8783b15408e68e819053017f7bb11d0594b0aa9c7faef8368b8b55b18481caa7cfba453ee983951a943baf86dbdaa82f106ca6736a3ab2d15d6e13cc734d982fe76cc7406a0d54a1c5d6cf0ae0e91a4fe5fc6a243ceb3250fd68b6ad033183fb85b6e68f0c4c2a4488c2ee3fa67d42eeb4a4712fb1712528b495c20287471bc2934445a627061a7c36298c5ad738dacd3524e96128a66051b8dd8005fc85a166905d53fffbcd350148cd0225d8a13cc40e8a36360e77c519157d990e6452b720689fdf3ec3286a9615b93fcbd03f22639c957788b7d0ba6078b87a34b2b97166b009c7cbe6e8f1e0b323dca26e79f8bff1f7a4a866161ed6d77931f3f2f40f4294e12769a911708f0ebe531dd4025ae6b32c680507aad76f2fc28791dc8c7179fc49f15dfe52a1402dc7c7cd8e4c18663624ffe2b51a7fb05993efb9bee9717bd4682c664edb9fa78b22da1f1c53e3a97b31900e0f388c7ea32f119438eb8674d829419ebbd525bf8d3b539365abe55bc8d452376d841399ab75130d8c19fbb155edf17dd8c940bb818ea1bbc33e32ef18b9ad09c9321daa9eac5e555f7f241461e0ee13afb971b16c8f357749e36387bd82ab5525aee8e227c5f59f6562017bd60f4ac3fc09b1aa48d1677da8e82139ec5a7d23fcd2a153153896cd214021f3b6af57f005d3b45a1bdbb8daa25c485c3e91a11e64b8c297ed192b0520fd7194792c6e50bf8201beb34dc1939cd1dbefa0f406f088d14332e1547dfdd42b87ac199e91a7ab9e00bca95605af4283e189cbbac642aa4fb8098a3e4b3a00d8fab88d0dd3602841f255d906c3bd59805014fe77fdee99f40e26d71740f9a3badeedff6eb7d048ca114bec5f654a5443a3ffc3aa453470b1ca1eb354a7f151bd6904b9bb31b7aa18d363fce30fb7b208b47d26ee8ee29b0969f039e98fb6d6569eb7561fcddf953c31fcb97d307531caf5937de168b26112c690582884be61625886d720556847d7f81f4056cb8194ef16b85472d1e08fc622859650e1c2c9cb0c6937eba28dbaadebb8ad357f95a332b422113e6bc2036d539f8b039f963deade9de4d5d1a9c8945036c778a5e7a381edced855b90cf2d13fc7a2009285a4971f076bf21b474ffd39f85be963e81bb0164199060a027d1eb787665b917b5ee2d949c97a1509a515267b9daeb2573391643f45ebb8a349c2336eb8c87c93c10722f58a06be2e0688b0a5923537b0c7ff2e6fffd069c4870ae2e49142ea51601498a05b6ae27d61fb270d75bddf4e320ec2a0d52e316d0d9c36d96d82eea6042be79ef804eda05356ee106797f421dee8e625630ae60c36dea8942a1a3947435a899e3bff4d1d59b242fe90a8398fdb01af9589e8967c9d29e15111996c5a85f62f2f1df7a7d25c6d477a551c958624b560c681e7b0b520cd771649abc15d917e5f0ceb0c75c916cfb1167d9929bdf117860948e2e1a924cdc7b346a4324f587470b9fc5836a310dd16bc4baf8b12688531a4a6e4d6adeea3a659f5404eb8dd0889efd8b6c83082ee301db9d869f72ac3fcc7ad9d61f17b67338c9fafec9f12ba6bbe870906ac923314eef88594ad05756e4553ea772b09d191dd3c364451a3de068ef6f51342ca8bc47933e145f7cc74319e145c166f720157ce5fc183befb28f196d094c2953da94f7edcdffe1e372ac3f0bb59c1cab6f9e5fec29f62db87d261002b30cc271c1a0273163399634e9c02962e16b88a650146326ecc84f429bfc4fb1253f35281d232d9acb27e8bf86763b3afa533053fe24d225a75fd2f4059128d6b143050ca39f49d2584203eed013187005e0b7b68a04ed2df52978f35661827bd26a32ae575642d2672019c5623367fc4b398cb97412226f6812969d8f8e788a5a9b9e156c843b9d49291e31556dc22e5efc148f68877c77256ef4687f9aa3982b3fa45b57280a050feb44f38a7a60c967ccb330f7c2a743623b291eed143d8553dd33a754c498a85425d9483a497dd689ec5c625d790771804c5af5b24df148fd694895717df854a37bafa45073430e1ad14ae7e6d10f215860bcd5f4dbefa481ab57b1932eeea51555182ab4cfd149b6c7b6f5c89ff02586f8bb2185a7c001ae614bacfc6534ce4aed17b99922205830dcc898b75df3403cd58337851bb54c1047b8d1c03b167c1977a78d290580a894ccb8742142b39d8acd43df53cde20e76a4eb6a76a6f5dec6b4b1bf768a0b4963ed5862b6afb09d0d5b2528f43ab1f643bdfbda34f52d35b48489f1d56647605cab3c828a7611761922ef556cdc1130a5cd8e7a659a8ceb0493cdb29b33a279d79e1737df53d64d2ffecab7ed89ed721794f789ba63acb1e8aedd7313376416a6c7c51e8325bef6b39b568ee21e0e3241f0ed1ab716d11aec119869411abe8eca0b585706f58115052036516a7f9bd02df8098980db647a4a6d4dd7da64cd52e9f47b341ca8a8a02b3d10239278bb732727274f37f8f4e4a76a4f56d30d0b5620248e9cd8518100e011dd48b00af17f9e33b201f2ababee980264fa1c21a222eb8814e893306f9704d87f41c767d8cce6974db075c0a6b28c3d8ea178adab48e157428aa1afd0c0d7c44a5e733dc4358bd0f4dc8aa412e6a84171bc3370cf39ec3bca7015c936d0fa98af53207ac6442128ba493f1e1ded7bff1154c26f94473c621736cb6c0ffafd702916a6452d24be2b22fa267a513e7d51eaf50df92b5b0df41e850c2ffa5f855074f2b4862db18b7a9488bde8ad0a91e4b2c39c0fe46046177bb2751208f5b6621854c1fe0e413b11661af0d4ff1c3a4f1c5d5b6ad406dbc762ed8e2631f60298e1c2a4bbaa071d88c5f6c9960b8c6ef31f0b658f2cfcd6e107dda75dae449ab0255bef86cefa5459efb2ae4a26e7f63c5ddd65fdfea374d910d0efc6b305d52f5646cbbfcebde79362c62a21c8d8bfa06075d4b1c1efa71b6d78c8519d92ed59d18d1973c4703dfd2177cd5b812bb0bb2889d67346bfaadd0fff746aabc5a76b2cf98ce7c415e5d95469713a726559ce545c36922af47a4ca018e3aa7b1bdc9ca0e7033d27defc60cf097a3ebc75829e0ff49e70f3c33d27eafdf0e6843d3fd4ab03badbe0ad137b3ed473c29b1ff69ea8e783b727f6f850ef096f3fec7da2de0fde9cd8eb433d8ffc4d33be9554f258a8c1cea5a9064d9dd06bd77a32f1ef4d6742ad48d18c9cdb9fb485593699cf363961b0b68cf5e94b6b6a8d67d815780a4cccac039f6a927fa9e43828a0256d3e571b7b700b0672678e3ece79faba9350a1eb0cc2e0510b770ef448c62c43544417038302b34c092b5899f384e636664a61c349a82a90f74d4891222ff5cf7dd3d24c159f167104e957a18a7c0a4fb756d4bf56c8c5c337a773018c9998e525e5460d2b55e79348a3e3439561ad2cda8f82ff3a5335d329e73283fb6e8c0d7fe8634c54ae792b3221f618d25f990fbb7fc969c6c0dedff858383d70c23e007c3ca9790384aa84f56b6e7582aa52bdef91119bf89ba73a133f50cab2a8fdee86d2ff60839b315f670f9cdb667bad1f1975a2688ec6c800e68554b2daa69a069a3e690e9e988cc81baf9ccbcbb7cdad5a189479b693d3d94978590e05877385850626bded352cb5b8c9eddb3dec6f59cf023109b47acd3f9474301f1d7edfc3de2df34c2086406bcfbca3128eb383c3b73ddadd729e0562146cf5cd732ce9783e3825e7cbc26239e32d0e7f8f883bb6e5ffd22aa931c72edc30c8649c9b41504b701eb8e186fbe00bd7b87dbfef247ea013089de8819c2054621e708250257ec0898492f8414e2c74220f7282501279c009844af4014eb0991279acb44fe5be4a9a19891ea9d457a9fe4a6ca6441e2beb5fb94f658151288e0a94c200886510ddc60b82315478a5e9f15edb1e49aacce619cb18a5d073e6f3fee3e43b95c12af390fb7f899cac2b7219261566ebcc4ef5b52f9c976af7ed475c2367895d192a69871bb8c3d639fb870937d87e33e23aae2ab1047e30d71e92f46865b013ac3494793d56132b7a64fe2cadb6908b6f699dbacef9a52e1ce6bdad928915eef874dccc6e7cfd69ae1d2eab0b606213b1eff808b9916df0fe6b5e3b242b37e9caa098c13420635dac935768caacb595b68e8bba695d5ba9152301345019fc058b46655c87e5c4cafb071661672eb3e109fa4dc849c4237b408eaf9e2d6e22f6255f1d37b38c6f7fcc3586246514e232587c52acd7676611876515054224e41ee391727824bf2e8358556b804c75594e5cd990f9591aac3a6e70ade1b650a58880a08032e8172c1a90713d96c92b5d1f98051c5ccdf921f584dc2f1e29c102a4e9ec92492bdffc755c817df8fe716e1b3e5b590897c1c42762eb915948c982ee0529e977c3cb878f7a2fe412e291fc7956a867173711fb92af8e93acc7371fe61aa0d3b2106b2bf3ccce693b870d21a73360673e161b04ee47192cd10242f680e463062da006c3145a8e106f1b4e643adf6089c271813cd62162060e307f7cd8598580115f5c6ca818f22c0fc9620f4c8f7801ba89b402ce17381cd17d8fd753d9c3a0cb8cf93c84398c91a2e47ffc216361ee5c19e8d148371a65068572f55863d7517934e8de6339ee619a46c5a7798ff1acc9465cc1418f343670f950cf561b744d727f0d339e7c564c5e147ee3bf811bd1ee0acc08eff178b55a6f0ebaf686f6b703418b53fde8cdbabfc63c92f276e0ab221d39dc8a95743c18804c50f5fbf6e9817e1dd9427b90481fa444adb10d3932ff9aef09fd3e40f305cb2319511d49e36de7d8aea16d17089d90e23d3f99821e35a2f326ace249dc2908218201cd09a1c8b3dd7621fc74faacb70b411418110651610e3db0a31e0a8854e294f9d47dc64e8d80bba2c2a248e905a8d641e9acb0546acf7d237e4e473647db33254a81d5991c3f89485f25066979479daf99f3664634850babd0a2b3fb9a31bc55d3dcf1d7ccdd8891daca60308b3e68857f8afa7da7690354778a11deebb73249ec3909bd150059f46dde7d9ca94f3c91a212fe89f65154e2003323a15601eed0a3043654698834caa96c40f41bb9c91d1e765fa554396c01d6f9db750eb25f76c004bd73f91e3f3e2751bb53e2d963463a6f9b1e5e608266292669a29f69f6414ad0f3a41d281b5ea6d9f387bbd4c2bf7a6fd220599fef9e495ad286467d309f6eb683c4e63ca843096159e84b41827c83e4c481f51186de0b1d8094108ca4dfc9cb338e8cec12bec8fb8ce31ab0fe68dc8a9623f05e627aedd12bef2f233d2e7b0074f05992dbe45fb188428ec1da05bdbeda0ae54bd55b35c5945f1680eb4ec56d60c21e5540bc04b9ae45ee6e55b09cd79cc94f794d703f9211a63ff84fccbbf1017a1fa7c86567ae4335e76e084d27e9e370fd7a568da6982e88005b750376181da2fc8396679efd730943ec5760517070e0c3bb4fb48172018239e3f7ebc31e485987015c553fbcbcabbd901491301de4d24ccdf6ba955c7fd783d7ab9ce7cb582ad5a4a378f89a9f53814daaebf59a734267ab826bc7625e2896f796a0463bf43cffc9d5aad688e711e442fb3ac5b7474bb844ae8db8e1619ad5882906070b4169bff248449a0a7629033779242b79895dc8c0a317e0d38f47eb332c25dc3f24e8ba11192cd1c4e4e79fcc179303944000eab540b3a1926044027bb0c693accd648edbd1b0e246f8f1f3434450f805f7db9b68a7474e60fe9e4869a090961231e043818508209ba6a4239c5b452dccc2a359c693a1c92835052f37d787505c5c7a734bdf450102e5861fa6e9349200b66ef990100d08d3c84924ef842846f884b5598f47bef75de45ebc8db0ceaf9b264438e77b3ea5a15d028309079e633f97b1103b472332a8cfdac87828a0100148d3146984e3aaa809597868ca78622819492556436509a5302e3afd531ff638f3ebf911ba1bfefc396dd29a04c4fad302b51090da66333bb62b4730c846b151c7dfa8e4382525c79c61d902fc23033a2efa6c5b30161aa703f51ed0dd263b6ca40c9040cc2e373384dba4be43ba7965e9b49a1e7446ae7143ce58234479739c4eb9a2d875beadf9711c2540b6b6fff72880fcb5860d43ba640482f1bfec130a6240d1ff2da4eddd6dcbbda54c29a5ab06b306b606395606071234e4ea338661cf3cfc05685a35546065e4700bd863af636bb2c7bec52dcfc15618f60b45c54e547e9c738996289bd9cca4943e320c1d8cc0e5cac1e5f3338da872ff3494067988a6f3aaafe0fe25aff35042d0f9d987d1155d8e5a1f3d6af4c52fbab4f9b594afa334e82f7841d4aa17bca0d2a0fb662ec5c1581fd24840b5ffc142224df7fccea459d991cafc718890668d9e3f128945b66606bf793694eaaecc8863cfecd17c7870979256f5f8d3743ee4525ae58ab1677e3ecdd2b6667b7e8f62be001eb5c2ba8f16a378f7314ab3e650f7f138d2e00602eabf95e34fa6cd25cdd298cce728a5411f91a50cf1d0ca308fcca5a4593cfc5dbea4594cfc08b7a1f4d30bb12fe4f843f2d03ffe34c84722121cd090eb9f9fca1f4621953ffe84510978e45d11898311799a95394d5fb12a7e25cc2de817958772e08f4a9ab597125d2828f4c5519a35d475359c471ff2ab72d83f8e04889934b8a441fe26e2d7a689997d1a0a734777ff6ee0d48daa7d3cfcb3cfbe39d4347be3659eb62da9bcc953dd3ae79cf377c6e747ffe93a26157a658b8e10e7f4d1631cc35c268433d5bba8fc944750f943940656c59f7d9ccb662949a3975d8d1e0d720df2c8f38fd5b78f7d56865b2a6b318c1a72f59906dd5d9b6f33dd7dbad7d0ac140cb3ca90ebe7d0b1a3593b002694c3a8cd2b4d300d72fcdb4757b352a3f759217cc4f6dcf66157ec875be0c79ef999c9d68c7e4e66bee2fceb935763f6e1faa7f24361d2e354ed237941766ebaf8a31cba0fb273d37df4c29d1a9f7d580610aaf61c84aafdf6855d479ef6853baedafd56c7e9beee354feb004ff5ecb793c0de74d9119557c68d414fdd6f2f4783fb91c877b82680104e9c3859c28993259c2c111404c43cf48e06a54043eed9c84d8a2e7c6b256e9376ac8c7c8f5f0d2bd36aa02021ba20d05bb19d7e776b4c540773ef22a3f4cb26af5d2ba324ca505637e72677f2dac5153b01b596ac5c60a9543299be64fa9229fbd2e9e3e9e389077a3a6ded7299de9bcae9c3ae52e5f794266ad689eb64387da9894a5b77e3743a7da6e9459407d5d3d1a7b98e729c5783fb92e7e3c79ca8203010b60528427ca541d39b7e8bc1c56699d8c44556fc4a7777130de087496990c9c5f3baadb0c2b6030d39888356c685a3dd45ac348b05fc47a182808280dc7bdcc787b870d012050108db024769957c2b3c458ac7412be3a294d24ab9096faea88b7851b4359433b9705dc454642a3215998aa8e9a9899a4ca6df2f9e7e3668fa138f19b77bc1f49d4e5ffa4e279f14f5208b3c7d202884f3b61514e4402e0f92a13fa940a62f016dcdde3095fe06aea66fba9c66bbbbf4a52f5c2a314eab2622225aa0adc94e2fdf5f134a87dea10339500bc779354ceff252e9b713c779dc3b68217a89fb9679f782d71d2b63c2bc49a107aae9f348132386c7ab2a71b4f4e10aa50f4d35e4a0d2875ae5d29bbc5d9952e96d28d5d2b73fea64c8bef4dde00a12aa3e3dc44246313a18684f69292a7025ef86594d4f9dc685b4ca954893fb380f3d541ab4d23a584657a1c313fa8ab5a24a97af895646fef6a2db0eb435985f297275bb1c0825651505845265f49703fda0b9e8f4dbacd3c7452d5ebb942ce1208fa160b5d4c4936d41be8dfc66d2d4f4ed6a170f0f6a50bebeea40cd4a959a05b69452b3dab5a355f299879d1234055577ca150e12e272b936171954d599501692210ba172329c3e06319466096d4d77e3f4f15d5cbe9087cbffe07a45e9e577152ba3f4b28585a65dae22da1ad34bea6a178aa882314f5e8cc1b978b25297976fe334ed426d52b68b6c17d92e2eeff221584f9e0335280740032a3f1e271f3f28e7b1e6979acfbd4ccdcfe5f3116be8afcf815a40961893b76365560955dae50ee4342ca455f2798a5c44aafca148504a1ea8cadf54f0e6554192481f2ba9fb6a5819f95cf9b535a3cf05edfbc1f2a7c7754a9c91860fb68571d44712bcda6b0771b1dba7bbbbbbbbbbbbbbbbbbbbbbbbbf00b4ec22557a9602c00a281562b89c5a584cb444eab86da461dee4ba5f9e8517f77cf8bf7cf7384d4f7a24005db798b9bb63ee58e6a8889a93859f01183de76ddf795cb4c1033c35fb97e9f40897f258883a2409c08b20098aa469e68b325182824295af56c99f94a250fdfa2ff4a017707abfa13cae0278f9ae9301f5f1b9ee06ea23ea230ac5420a002b389122ad921fb7263b82f2a273bb93fc5fbc59630735140078dd517ac86b290dca8fe135514f39794dc54a5f296a309cc775f01e779f06ddf39c88fff44b09eaff72af21077124fb20aafcf674384d0d2d5b7ea67510e85783417ea45f2e9965d9b71fd91b59c63db21bc3be10c3b0974631f16a1206cbccd2043228210a6c85fb372f20b054d028b8e05816949381392666c64830a83f7f18b3c2f8933ec4c470516403c0eeeef2dc84adba6fb32b9fcfdfd360f73b9d8e11511d7472737274d21ed22c225387d34ca289f5c0c52aff25558f85d852d1d62c606be29326d589a8fe5996750f426064eda4ca1e2b69d097b0ab41c7a4a04d73e8a8a1591867ef5d0d1fa9516af42df105111a5e818b8787e7032fa16671f57d01315012356426d59368a2861c1485874226aa9e84cb471a3333f3beaa675e0e1debecd1275019b9a04c85ea0e60677256655b18d3477340f75feaefc745ca2f846c7511b632f98d7c66155eb07b44f2c3eb9d96b1bc8915e35d7e9d06f554852fe49e93b70db67ca60fe5adabe8b77157fae8f68528aa7dd95764c3d9154b40aa7c04c9c73fef8f4941c3597f6a4f4a51a84775f7f4f473ff641e5a3121586d1c57f553147d14aa86a0ac36e41368fc362e16a6b18e4ab09ad67d45d14336020cc3dc7d9ea07a6bb14989a9c1ffc718631c2a5aadbcb812b32ccbb29759966559d69ad1ca3289c9968d9c59a2b0b563a8069d56abf5928204ad56bd128780a26bb850ccc032a693999923b018638c31c618638c1851ef90acbcacc85c2557f715c5162d7944085cad5aab566b35b35acdb456ad181822288721e4459899313531337304c3c4c4ccc4ccec4ccc2a266635e31d330336f80df6c7e4801243b3f863626248ae981aec9ae2f3d34c1e1e1e1ea09bfde8adffa26ab0ffff8feabea208e3b7ba3f08638b1a3e900e8db93bf3b71ab469d0f99b27f6dc9ae1dc58f74c831e33a3593634e27b8cdfcbe28f11f377f7e8af287650632b09a624985ca0c5aae5deb2f196dbb466b4fc5b336c30d9b2f16f8521d098dadf1a9a41c3b7d56abd869050824705356cc915bc868c7095e02afe2abecb109f87b7fc0bb16fad1a5c39af5a1cbf18b9c210284ec70fe9aa86a4ca8f2d0db6aa9511e3aafab75f5130b1d2c263686666c57ee6b10f67ea8c66615f63f39b31d35a1d9531dfea3333c2affcad5618029d199a11d6fe9955f5cfccbca238429da98dd55582678b1a8fe007f929ca05fca00fdb06b0eecf4764808c9561efe78e1f03b6c697d856d99137c095533054decaef34cb050a82b0c4063c0c2144921b7ed069568a1e4c71851292980209266ef8293f8a3176225b73a5993de68f696fd32d5b661fd72af9fc7faab235994960f4f3bdab817d4ba37ddce67f8a9c0ae6e38e6cc70173ed65990bb39d3f4e23dacb2fb62aa222ca8536b96dcd3cf9ed8d3ee3fa33ed9469cdbde0b2656fdf7ca1d96f1ef7528e9e9e34f9a30de33e6e4cbed0c9b58a1fe3d94a6094b8a03cb35d77c74c49e46e8ca13c9d5a4e6e42529a42a34a53a0fceeee930a8f524a2c2bcab22ccbb2ac2b9291a86434fbb26cba5399ee1e84b63b23f6d16893524e8e9e744edbc66958740d088e93df4929e37c9e5fc4322a5dd7d9d570f722a4f8b109b747191d0a0d1a5e6c9bf4b42aa587d52d72d29375935b16bd99d5cfa6c5c99136f9b507a2c4341aaf168d2c5e98a61dc9b87d6e6adc08e634397ad239c520699d8f1afed9488baefdd05c6fb37e1b65bf7120f73aea625f84e1f3ce3f1a59bcbc9df7b71f8e23418c18892b95ba1af24bb4ba4d97ba7039aaf1b38142b33696d1c6db67d3aafe5985328b5651aeb201ed668d668d5bc362eaea763a7530a7c9d1182ddd0d1ea7138bc98573e97cd4883f514eb3fdeecfd36664baca089d23cf460d6f991d47da6270313a1f35469f75e08f307fec4bdd0d0a845f9fab7bd3a361a3c1b6a9425992e4b72b13923a15628dcd8a1d8adb6675dca67517044f252812445798b8848a28a2c47c217165a758c20f440461450aa83ce186a540a58a12424882080f0f37c89cd0785e4708351115402f239ef03222c8890c033d10b182094f94c0074fb8d00404427811f9c1073ea81abfd3a95540a38c31c628658c31c618638c31c618638c31c61851508848f718a54b778fd163f4183d4677f7188286d2607837144c07a1dde576b7b7b7777d7d7bd7d7b7777d7d7bd7d7fb429dfe84565a6c23254019031a72553e863df6db450cfbed390d8c57a29568a3c18632d41368c6d62017e3c7d353e3376dba356da469236dfa9c9b94dadf1b15317af4d8add33f351ea9f10b75b8da2325e87e182b8f98e827519a5898700503d3ab9d9951c06a1530d33342443152894da5a934163da5b1682a4d45ca5db9929995b8bc7db87d5c409a3551a8ec81340b7b14a380fcfc206eb75a35b65a85e2956b456755f1a90f7e7032a3c6f71991a352a031c7cad8cb3b5626d6b0040d5b3572f41e1a3840065606c83a5b837d44d1dada1aafd17f9b608c07a811cbc1088c318c6b5605eb728d61d8efd3222001931bfee59f538bcdf405fdaa1262c90f905cb1450ea0dc300f0e6cf0c34b07527471c34ff951fc91bb04fcdf1d3b7a333367e12f238aa32e3f72ede811e401ed18dda3f7a7c4450654872832e8e107278ad8410e5a3481a2440f3e4efc4ce917787b4ba1b71783b25f0a273e03aa8af2a38cc0fd5bb395776bb666bfbf7f8a107c7eca8fcac04a6e171ad0f5800f4418b26264892c7e64c842a8881e1d9c98e08930a63831052ca848810820181141103458228c130c61e29777ff4695fda89d4443a0ebed7dba4137f78faefc28fe9d452d56b8b77f93f2a3f8275f1153b9f72b93b2a04d1373c331d5a6d9c9decc1bea2afe157f8bff41f6a9dceeefd31bb614ffdef42f4c7bd355fb4c7f1fdc5dfd906280262e08e2490cbe20a2c7951415c18424503162094faa2401090f151994c089224338210753080114a684c1cc14b573c518b632a2fd1657abd50afbf85986c5e84428d78839b732d8875ced8a75375a6afc95efea068f9652863f649d5f88fd0dbcb3ed93cb96b05e63c866af751113034aef87c45ebe16bbbf591e23079ad590c35ee0307834fa212bf685d9dfc03b557e26e38f4dbf8dfe550b8a9638be1c21a06d1b0df60072a5071069e30fb48ad2a526e74bcf5699ecff17609cf318778154b71839d0517710e8e15ef63821a768d07efb68986fe358f6a61ac6c46c8d7c7f99a5b1e14accc6b840aaf2ebb132fe272e280e4972463f1f87edb5c7e1bba03824e9c741fbed71983ffa4fb904c2bf0790183950f93368bffd0cf353f28b80f6dbe768bf7d7f39f347d9d772e4adab703c06a7f617e2d4c670aaceca33325ff3d63f1ca107aff0704f0f201d0d889a521a73e434e8ff355ce188e239381ac3e2534a5d20551a6b4869865379f4ebac4e5dd07595f7a775af2ac6d25bdae0cac6c6aa25ab7077e677a72246f6c81c656856af6263e6f8bfcac08180760db9ca5b435ab51e6c41a491078c8886bf03d52ba00d8a120304746b48353ed81ab590dc5675e4451e9e1bcdeb1bccc34c50e466bb1a382821d0477ded0144c70e9fd8514ae6dc4babfcf7aba1c1cc6ba982ead4d03bbcab21b35683ae7dd455feb232fee1cbcbd690aa3f0a55472fc1cde326547a7408e52d722f5bd3ef1f80a5a92ff1bdbbb1578c6ad740eaa921a9a7dad8d082f2038183c3c646edcf66659c0baafde8b7edf8b3fe0cfbd5595523da53a7d1be97fea8d3cc1b7f148e3f0e7f1b7f1b57b0d696d8a8a350feef20e8ab12e7beab1f0df6734398f358481534649fee755695e33e200d321007312f31c2f23dfcb9a7ab1a52a117e20fac2edfc3553cba5fcda02e7437bae77456b51b4279db06d07d51f1aafea6df22a9593950af3ee21186eabe8ec05343765111a5b2cb6954701a976fcaf22dadc28735d6b910bff352a4e73e75a3f43bb5f43ba5fadb748f4e4580e55b7e06969fe1f42edfba8ad359d570033bd5f4fe2cdec97b5731bb90e937d3fbcbede2c82399bc54f7dc9b3ef352dde84b1efd8dbb54045a9ee56968f9d655dab37c34acab4a1eb79d0b1d3fb67d46587e34755695e563d776f209faddddb4493be8864273c1cb518b1bdc918b4eed33e6368d3a5d59e2013c982e333333333373d166a369f8e70a4e8e1d3f9238529997c80a3326574a297796878cf8f9999999ff267a5b351433df30b3abff85add86f894ac94a690a50694aedb7a114880251200a14724fc842bce8e404ada59ead5940edb7d91a47ed9314ff4ee5cfc7be94eb695e110a08e8cacd7ab4bb1b2896ae482c369d1ae46a0a4e0e8e1cad192c078e1c1ccf2bca57eff541ec5bc92d903c8047d01442414618daa02988d4d6e134f24be1cc4f49606fe6a76690dfbfc355d2ebe12a1c1d7424c9c15ebe943f3f474630477ef6050197686fe48753a6a212a87f517ce28eb76701b44b982869b05f0a9a3d8ef5e622a5679f971f9fb9a7f4e198b3c3be9052ee8b1f581baca0a73d0ea769e7c6d14595796e36d491c7e13bb41f72bd007ecd6bb98a9f81b0963dd8967134d8397a307ab6a111c3ca69a804650c1e0d1bea342b57f57bc5bcadfdf1db822e3bcfe6d90eda23fece4df6fe409c06fb52b187abfc776e785ce5dbb75ce5ff4192044992933df64192e48c3efb4dcb99af7d41501e24fb828cbe2be1d4be54fcbda1d897923f4ad190bdf639d88fbe2049b42f08e9490f49a420c4cfcecdded8c071954b2550fe741af46f02e56f6cc7db9db0bb5fe88f7dcf58300b4b6dc0c6fcec6d645fe83fbfd4327ba706bba51095df89b1e0aa16747320a007e61d68c725957fe376a0a1f4a9ac01811c9530355e1ac430c6b86f905dd5abfc56c6f8405d6a08d697d8bdf052d94d5170283c59e9b2e5affc5ab66c19e5b25cb971979d99b9857dfe5e564155fbc2dea91dca4f878b6afcf63de49c88d3706055fc1fa024a89c02ee8078111fd220ff96050d776af310281dda5cacd0eeee96dddddded5dc32e5628ff7ab77777fba8318b16aea431e62d3c8081793919e2e1c45527704d136f84409813e5b8d38b9323f58b1fb0a0862b181d14b1830f448451e545041752d0aeee8b882a1d042aebbe889842c3499119b7c913b53ff6ce5712ae1aa3c6baaf2642a821aa6e2cc21676b77b6d9623686c7e9623e86c7077eac65e6ae31ace59bd1a9fab91f805f1c0cdba8a3f4fc1b0cf753f962368f47cc4aab344954838599090cdb1599a40f93d3a17614182f2bbce12d5d87789ed6340b87b363589613fc81fa23692d2889f91478f31762c45484e4a237e383682b785e83878eda43482723f13d344c031462ebec432f7896519101810becd38a7866155507354838507d4dfb7d1866155e8d6118175357c5b881c865529d261a31a743b743b7437dd5d6e07ca42772af4f8d459e56f4f68fc0eab1ee8aafed547195986acfb2f261a5096116bd4213b9625347ef4a92596e175ff44297f3a7a40b9056d763770f03671cb32b098d9f7e3b0f8c3cda246205096b118a64dd9da949c203b394176168382320ff7f400c2379352d4528938f8c76fce2071882fbfbdf19cc4df36deea49510fb6bea0bf3e50c1104a403902145028b9d9e9c1892a52c0841330c187a29bfe9d4e5786b96317dbd22fe82aa185951af21123479ab5b50a1dea36a9fb2202aa5ba94146e2e2237cc44889c696bfc9df2f6cd5ddb0ee85d16f8f3d1b7901626bb6ef656558b0324950928d6efad93b9f566657b5affdfafcf5ee0be936fa4201d4f0c38542659dd611cabf030809dbc230dbcce061c108d2ac70064f47e3f2fc3662c0f9d1acd1d69dbab18dbcd48dd1939ef4f198bf959f44faa6c76db1c1a5f5f4a42f8c0dd2277de1a99e583c1ea42ffd466a616961f9523758def42fb0e8cca82d5f48fa1bb8b2fc7c13f72c1ef7a4753aea019d11438e66959e4b9e8d66919ef4913ea75924ee7080fcf06270f16c5a9533a332b7952fdc29e10c9f213cdc5324eb625b98c58a406d6cac5a534a49b34b08b568e0e4404d596d509421221931d4cbfb547e700a28a8180cc3301a2306c6ca84fb11f3c2dd97bf286686626363d5621e6c4a1542b5a6eeab0a21e1d4bed31c0d41e783f304033a439d56cd3eae62a44b9d2b139eeafc745ad588a6f9a9bb614643507681014a8612200cc0ba54a6f156b5af9a095806bcbfac01fdba4ff751fbbb3fdd636d96517eb5dfdaff7d30075b8aef8ba214c5ab699ffd688710f958488f3d7541b9273d8de9db0f28347d4dbf75baae0abf72fb85a34ff378c4cf36771901ee494f03f7a46f5d4543f7727556a8064f0dd2277070d8d8e0bc1d75d6c4b630dedb9d31e19202467767bbbb3b7f541efb3adc9c9fd1ec131f1e1e9e0fdcccd8bca420aa3ca3b88f142bfe8a1432a8614b8a10ef50e5c7b29711ae545ea2f2531d2a3f4a4a50e52ccbb26e3fdef75f1daeca38a134336990b7161ff61b2d89026ab4c469b46ff6348b6ba8237daabf37b31350df8adadfa9fb852c7b6af6a4495005eb3e015a42dd22578485aa09ad0cff88bd83c05e51fd43d4164a9f0743f2d4acc67b4143940f711ef91b903f3f57bfc0420d5a196ecf88f6da375ad23e5a32eb880afa351c2da92bb8800044111f40f262e2865f2e719a0a0ca9c1144f3021c4097070c32f8bc823a50c49fffe8c5f8a7fe726e30f4787f8d8efdcc8d7d143fb96ab3a8bf2e3cbd7be2020d60283cc19cedf9beca3f14bf9cf947f291ae4679f133fdbb9e1cf064eab473f907e1d3ba4b7ddf1896d61b6e798798403670a6622b4e1346b3481f8f7580750ed45e4aa1a90ae7b9c1c1c38a5526a86d2f3782c6ffa2439f44b5f2a3dcbe7949ee50b92a43f4829a7f4a6d2474b5efa52edde5fcb062c80132aa0c206206082104ddcecf4f8f092d245151e7e50b061dcc8eb7e7adcf3cbd173ccddbd5c7b614ced38be4dcfd73c8c99f6e82307001daec3ba1a5d8c18d63c3872d7bc757bb16ec731ec54fee4fcd163dbb750e1df03c836fadefe47fcf6b8ca5f0a860eb741eebe54fc9d1ab7f047d5e1aa36da60e86ff4dda6626fe5fc8f0469d07b34e89fea8fa97e4c5a21a4c37f873f0e0ecccbc1c1d1c4bf071018e7da34c4f4c209c3c215345ccd342b722dee99d67614b60de468735cc7c851f277e52f9c409d0dae155be948832524cbcc2ca4db3dee47a07d2ccbe8ec6a9c42409be38aa04bba1a4cea8e806a7f8b143a5a5277045447a3381a798f78b43148e14e55c154fb0b2d50e373b7478945ef0500b7e0dfedca441b1cc084faa39f6ba76020551b6ed4f891f819e3c1c6972557dd66d982faf7a596eb4edda948d841131a20e1ca1741f04c608a14273d37089d4ea8a4cd978439e9149104005000b314000020100a06c482a1502c9ca799560f14800c85a2406a4e184ba45996c3306510210418020001002002303233330c009bc4eb6ae9fad5382041d6f6defa7197281ebbeb31903c6b407ff8db8ee315dadcbd1e341c6a99e46c9070ed48453625718299c80b96ec6fd45b4557098156c968c1b6c5fa16ec16508f681445f4ef8ffe2838bcc16733912eeacc3a9d0e15b072296d36650420f6cf609d81b2f30213ae02862f0e86a7d717d49aaf22744f124e73f942800ca2e45eb0d7f9d3e2c4e1850ffbdaf30d9c85ae69d0308306cef750dc2fa8e22cf89962c885683f3648630294d2c4da02b42f6b15d45f4f8bb303be34b724655187f7bb7ebe9c07001f82e511f139c610783c6da58bc6dd56e2c8864e97218847be6c02f11713081a11f610f25863dd16b38293f90148185ef85b3dd10b4f17e2f6cb1d730ef38aab00227e199c5def0322a96971c1b8655e7e97cd84c4871826c9e4a4ccaf48eedac91b0e3eb12295c3ff007649695b626c066b22131f6e4bb525bac3ec17cd53bb0b391be9c6f32328b11422ae7cb61449ddec315e427b36706122ba4727c2cdf3951d19c13341cf685d76df3e46eafd217285becca5468cba9b4032bd7d0e8ae4422523f3428b735d0d9985bf0be5f4c7a0d368fa1ce743528cf460f87982149b0c85e82208db6d4d7c5be84258c18cdcf2da08f8e683a16492003349dd7dd1adfa7504e608129b64ba6917a71108c17130c84bdb13e897b4e00776ed2ff3264ae3a0748cc748620ae163d2ed6eac50342a63bd49b2e2d341afe31f99021df4a6dc94bdea42bf31e06dee040608723b1321f90dd7dd441390c6a0be0ae87b4c7ec569300007d735dd89fae96f3a7250453662bfbfe2edb43250601f204ad12973a00265395ed89628e48a4488b196cb2b44409204f0f3e6160bbde7de4ee3070e6eb5321082eb4468fe6e50122757294654ff3025496241098e67853b9fa935e6e0e01626b75682ed275c2e9402b0e1d6414f561899d1ccac740660615521e86dfd3933eb85762fe037631c9823bec309381f0f00db7a05322e94023ce9b6c3b2f098f1645c085a33a8454e6b092eb3d3ca5db409004ef83d95d02116d484b1f59b14485373e431c0191e1501159314598dc1a4f5ffe965970e6d04810e3102f75a9c2bf7615110ac8ea6f0970a3968903304755fa382b6a53fa156aa319cfcfd65d560c63428feacf146b60546065917bf28ff28265b25e8f9ae235a1bce6dfe5faf80070d72e38167e1888a1afbd4557699a99772bf4f555992d621a123adeb72f3fa657cbef3b6b6c6725cdec35be0fdf8c8482f1807cc2960e8dfff5b690e0c10c9c354963e0d833f3e8ad193d79519f04927fad56269b537d78df9c6447ad0c13a059b132b0b55a4c0f579420cda7b46c3b0f451aaecb53a7b1d4a37a64cd57c6c1b4b41c545be82b7ae337be78b3b5ecd9519727bc0db4d62764fb24b58aa6173fd9deaf5a700e97a5b6b980b01b317ba59ca53e93e1a201be331d22f6684e7fc29e80a3ef8b538d0a5f73a809c2570838cf2993ac381621cb74c06f985d3fd1d64019628bec737340317d20fbdf119b734eef14a9c780a12a771e9e697ebc0c49803cae5d2bebecc5e5729907ed3c79484341d39cf4e441469b0f1f1e0a03e5a1b5944d48657227ddf312352273fd5b8748fe20652f77ebb984a2201c122a2100dc5edf17214c59ceb87395025e2094f4eedf3d033da85ba6c35249e66d66a5fb4052ec37bc6dae8b0469c95ea8a0beb18224609e376014449a70c036d958edc0e474253a50c374f04287d5c66521d7063a604adccc4ec8432c15c4319cd022a16835b1f7180bb711f6351df602046f622566fe3cee1f4b68428d62afe35eaf05911ae75e185feb2bfd724a6bfb3f871c0b6b551925617a0c6b81a993363400f1fd9e07dfeea9948725f9df4a5a24f748577eb661c3a409062a59049acdda90080157a4a4c671a704fdc2120724c5a3383ad90bad97bbd3242646b7a418df5a12612e4ca553988e43a0450b8adbc34767e3dc46b454f72b6643ecd2a208aa34f0141ccb7ee6bc1b6b70db194c176a77c4920dbe2446d7e54b16171d853db9228c9c5b6611dbb59fda8baaac287adca599fac5e27a6ac74030cbcc701c66a5da301594d95a9298d3ca9c67a5cc10056d4533570ba7d7e76f035ec7747a0e811817c83848e5c0eed4adbe3781ad58919ee4e91263c83f5de8655b0af0b322d0a9ed336a5b405a9d500272c08befeb9e9c7e4af1874bbdd52325ea68fb1ef8922a180a57fb40738bb3faf9d0cec26be3063f0bc0707660ed60da9b7db56642fb10e960979eb3df93ae9203be08c9ce2ef5b374a13905c6ed63063eeadb9b426a2ae48aac4efbbf071ba62cf92660e85c6ec58e3555019427f652696d71a836d3cbc203f3a27d6e49adf7f5c2adc3a4abc0ef6474407dd9cca41003006e1604b111078332ec4b480a1e20e29fd44fa360da71c07c388c6114b4df8dfff9e8a370cb78d7ea4bb86f7b98df11b104446c5867f6261548ce1e72bf6c0b6c0e9c675893307e32676c2fc9dcbf424b618881d320f6d6a4acc58f1477c81691399aae74c309fddea210bb90e4b0baff50d6bcf9a027fccde0d050532fb805f69dedd5f13729b3854f173241c8e906e00148da35a4282803f26a4c88f0e5247533c0f541f7ebbf139d74a25501cd1559f02ef3f2e48e64649e53af5b90f66677dc111183d56720b541c00c673f37daad374c79e689e40cfda381f0a5fa85665b86a1a853b42252283e2b89bf98ac88b7a253af27c22efd54443922fa5afcd7b3c49a2bc3d4dc7e1f32624a4899bbaa32489433ab02bdfd502ed4327ebd33409d4bb3aa0073c60ea6bc57438168521ebad3c42887b7bc1d54e845afc07183ebea4e22b231bb675d9cd268150f640b332ce2240ebd263cb9dc9485013a4fe064e2d80e074a2e8d9fd90c11068822ab2950f44fb662ef1ee0ca988880b48132e098df2cb264097bfa8783cd8c2ab739f0f1f5c3d73212365168d96c93641262891517590ac6a231299805ca862423318a8d13f945cb5caffb71d6b2d3ede839c0605cc937ca24e65631905e6ce9c994333288f74467d5487090fb79fb6193dd42f8b4c9054ac37347dd0d9fe6ea06459293989154abcec00fe941513c7fdce6afe0d22a19dc19e34e19b296b6f9e1b316eba74480ce41e4f806af0695b39f78fb3232850f3922685db646eab1070fdaab07594cbafa65a277dc828c6d6b576591bc7aec6bd098f31a8e125526b2484a3a51c11e17b6d2e47dbc971e4d0e05231d7c98845d0ee84842622ce6e6b7a33b884baed00542c9671747368246168c2f0076a018497f0877cdcb4eed1263473cccca88f0d9f93f6ef892ac36c4b660b8525ba0a2aa26349fa677023e0d6b8c249444c87b4d5d0837ea8a6b6adf6050a0c97d3073616e21a1a973387ae87d6047e140b579130a9b533cba1addb8e60171965ab30578a8fefcdd1e84a1f595bff109549f5963061973da617a06e22847928fc656b8508b4c87ac540afc51f3515c8954e56360d615d4808a0e7ff4ec14500b4bc08138c6d7d97113a7bb4fbea317a037bae6f0322b56f83445c92312934243bb3f639a1d0d4d144ed6a46176b5b19252ae6ef740b1bfb8a8a04dde0721928e22054087011a2f26503eb446a5de39c7c8bde428e3139d6c1f41d6c82753d9627e2b9a1ec5eb079f2302a09d82c56ba59d86e2ad398cfdae1dbed99ce9275b71486ce01861eaf95c1ae5710ab57981492c93fe912c466590b3ad1ea96a4eac9404dcc2e41e9ac2f052347155d2396a6ee95f22d6e7b21650cd1c0aaae8d5c649bf81a7bd8f4e313f5d88cea6b833dc16d94982e58160faf3b419bc61270c757260426ce39ce2a7ddbb5fa7880b12dfac1e231498f39aa1704c7e5edac52f23903cb016337b2bba1b4d8fa554ef890f3048814e147ef9326b31a492fdde10835c41425a348a4484d88c172dae9d4e27180e02750be23c47103af5493575d713910c801704300610d863cd4a81dbc68524f2624b6b21aa6beafcab6182f255e61ca8bed3db25b7c849a61b9b10d8c262225ff23483157020d2d3b1fa70b096bf24260c7c625da8205bac4b714dd2b77085c52f42771a02009ece5d601be58c7830be9d0f94ec8f932e66eb85bcf080464c42cd4da6db3f854c2e8cef4dd2793952f4caab52cac3620b2290eb169b98c63d8b5199b882cb2e3318c24c9b16305f8ce4371aea217b1f7b90a272a391ebd71f75ab304ab32515aa743ec3bf9244e54205131561cc0df90f2ada81d39483d2c24e89e937f554bf70b1da555f72f803e8893c7a4950ae1051a91e0d4b8aa57d48f4f28db7282bd45febd57d5c4ae0a5b62aa2ef0bdf756cdd056d517c4d4389c6136989a3d5860090ed38a29f0db6bc068c867f756df3568fbe9190c1ba97a3583042a26f21797ac04f9a063199f8c871ff228a2280b10d2c410c4a81ea2c713d5344f18f13f5e2a8311cfcb2050677d786adfb61e6cb40dd33fea4bcd4fd50b8db8058a9c3029179c447bfcd67a75bdaa694510fe5e04f81a21e4da7bb602682d758e01f86796acc0e06b2f788f93e84734c57528e3d0094bbc28cd2fb8632cab1620499db91d76f191144d11ded5005c170f8d24c0136a3ea07c28fbde1bdbf4dcfd2795e0bbd2a5ee3f1440abb2d586f12d09fff3ce8c6a47075aecf61f7dc58c606ecc366421c189bcfe1a6b9f77af72c28bfa92ed1b454fd7451786c20e81056c914439b1b75ef5b05f5bfd5a36501ffd6d0ba00443200b951178b660b7def3dbfe2361604d3503550e0a3d5c1030d8d614e4a92b5603ccd1bc3d8ac46ddf02a99ff76241fb12458fec373bac80e6550d0acee1b4a348ecd80f214772eead51e686c611bfd19e2ab67191dbedbafd6af88a30c5a7a4bfc27ae41f1ca21ded92c81dd990e089fcacbd665842d1ee76d9bc1eccc8f28245e28800494538be18c10caeeb59015b2d9b947059896f132f3b811eac3d899c169d4f12c07d5d616f13783687051973767f91ee5115ec94840e3cfcc8db08a3b1191d7dfd2eaee63698523e9198c3939afd7ac331b45aa21e3cdf1300ee3e546997c51c1676e93e4a593e5125f081d3502ca9371e00e2006fe87341ac7dab93c87211c0106f211ba1cb6dece8dac3ba476c8a4341c46ce8be10a9d4f0d7516c67b0477823903a8e65127489f6ca6b326734ddcd6bd8d35f39cb7f9b6869a68cf55c4885b8ad5208cc7b79cd91536a3979de1af01108213b966ea7571f4e87a0070a5e18e7e22d8694decd4dbd2e2bbe14b232f19b0c65a916f8dc850927078ed01cdf5ddca9a7654c540395ee5d9c46474e2d10ea2538243598747a315062e67c30bc2c763a98115073a3b57dd96d9a2a8144695241a6d66383306abcb4ce1c011f5afec7bdcedad2f4476839df4947ab006733c41348e3cc41b1196b26a50f7eb086f2c6d93a6496e20e9f3e6ebe7834b24a4a8271d49fb7df0ccaca5c9bd7015c10c81432e8f0128412faa7640d3b190c2499448b3d5de6e3e22621d51c0e39fea30165097c0de3e6aabe428db25ea3455a3c6112ea739bdbaa7a720bf7ea9128905d214490936f3c434ac808967f6ecbf5d00e311fc731de342de3d4039c7d4229ecec8cfe356c519f23dd42126c7a8e1adea99669294bd757a450d8b57b6402426c0b0dad64cfc18edc3f59c4d538c69ade3208f352af46488c46bdab832ea3d328aa50aafb9a6f5e2febd455b00d47ff071fae2bf147675e1eb0557026a4a9e6cc83fa47ccd770121a6ef20d82e5be0e917d444449e34d2eec636d9d8c84e94d3c82bf84a4f0e030a5aa64cf4fa446503d6f6e722d9f174adcd59d6118d74582b988520155745f9ed5bfe03ce8d5e5478a36bdb8c9ab471f1e0a78d0e38a498a48e3a033caa20509e028901105556515573a04e41b33623183cb0c0463fa3acf2bb18cadbdd0e13d8d8fd6c9845abfa310176f980b3a080e3d5a335011555fdfc7adf4ef1c486b49ddb54f04cc09acc0950f3b9371d5f78b8cb6b9c58abd0de0bca2e37c3e89696a8c4b610df65621dfccfe2a23d9793aedf75d9adbe47ddcaf3e2ac4c8441c0bb82358cfe9cc0cf940221e6c43cbb9766cfa50bbc12b1bbce300c4fe92869551df0297de018f37b1f912c637e514a8f3ced12d9778a502f8c4c3fa88f1d923583b6533da8aa9ba949e6215b28ddd35a0de7040ef682b99d62fe3bcff859fdab68cb572a09631478f572103cb0a93e0af69b9c267354a2e6b5d34f61db0bd8269c2ac339f285f037de5e443dbc61d49043f0c25ae840865e95e1adaf098fda6d7d65315baa3d2480b4ee652fd4831e451307a2fb0f2754bd64c0bb6c0920a53ebfbdaea9e3f8cb23e7ab9c88c058d12549cbbf14ce259555487fd31f58cc53cc146ec520078d6563a2c46cc908ad662d16cad6c02fc4517e70744d42ee84427886fd5a7f74adbabed57d9334291d2f390448b9c6a2d2155d06976b4d3f78e253304ed5da5037ba0ce3571aeb4e06e51070cdabbc027888698e79994ce3b545e3a98f5c320ae8d12aa095496fa6b81f55cfd7391a703765863de434a42365810aff29371f7d79e652fae2e4fcf84fd7dff9303442f1da770b2a5d9fa69bbf6430c7e0bd32f0ecc5ec8956f90d370c04c66097125656213203d5b0160d10f8cbfe03ea5cb47131cd782be8da99a9e814ee4b69b6eed321fadca1f4d29afe398dec9dcb61650005e70fa9497f1d38f59a353bb7b01145b92b71a77636f513246a5850d2df4f55e028ac8e066d5b1e7b62e036767169a631ad742dd083cbb88c6079aa93c289fb60efefc57b91f9d5fc8b002b98bb601d9fbb4204998efb052f28f5e59965f43b55268de8606b4cdbf7cca08716db185d71b0edf5914df33435bf44106d1449dd129483f7bfc48270b211bdc7170a9d3a59f8aea56d53c8790354b62c441bac6c3158086922086d4a07cadb790cec40aa9a3be6656ca311cbc7200de1d6c9866869303728a6edb43a6e93786332645bf1d0bf35ec6fadc586bbe999d141b60d69693afa2c3797c023d4d550318a8c190213e9444c407f442bf6125a16a29f8edf3f7de479f54e778e4b97898e5dd70a8feb70d18db9dcdd854b0861a36cfcabcd8788d3430584d5fd1d8045e23db5e9191f30b917959f4f0dcb00d5f6816e7ca31b6cf01b3cd706ade0ce7baa58d5c9d378aa5eb4351ded4196d89820fb944f82e8cc9c40b2738a9b8ea88d6ef7a4d0595485b328062748a7845d03c63142bc8042c3d6639e03482418169d534229ec7a037a77c5843436f4e8c0fa98d1fa6f6b05d69d5c7fa1a9fac8c9c51823039a3046722d9098dfe7175d8e0b5663a8c4a0cd7048b2103e72d4088178622abf84037c6868b7b25ccdce30018394b60a264ddafeb423cb135256ba33f1036193c4c8ef8098cc70094be6e28bd8d35861dfc51d3de0b33c0381d9700fec2cbac1127e6b089d697cc99c9b7cd0f3574ba229bd063a3c49b08cc0d01877968c6eafe0834959a79b6375caad87b874ffaba5cd4d72a31531a8ba59550529eadf33876e57edd7882030567cd895a7adfb85e7c43a6f09d6bd7c3a910615b7913ef5a6216f40222c03ddddb0b50cc57dfc4397b4dac82339e08c44365bfc0d2b8e5db29a0711535ed71666a5bce8dca5933b2d9c9a016310013ed53a98f4ac7e1806dc55c47fc149c23efb13841b1a58639a4f88a3c7af7f332438b3e4fe1353f0d9a4d198422c887712e3346bc9f4d0e8ed8d0d9a0476a27b274f74f6fb4b58b54a47ad3e036e7ca6f74cad837c0ada4929ef32929c845eb2c8e97c90a4ff77e612089fc9954e59bd04732f6d02b7b82440dc5107e6aa554ab3ddb59839c55e9470ddaf56544b91efac3ada3b40363ff7d604f93d7f486d87dd2bfa80eeb582d1952e2e8c28cc7410fd37a60c0ca8a4f5df5b36644cac9528cf65492c2223a0198f5e20d4dfa1047d1c457fcdb450c0bae11b98eb40e94a069e43ba527955f2ee87309a3d6d42d0fc93539883d9bfdf84689e8a5d18cc4fcbc709188569f5a6b59acdcd4a31a88b88ae8cd567157c6bd52d33237594f7718d6afa63c897b9f2069bf8be02295c9fc039941aa3f45112a3ccf82f805a943446a1d8701eaf53c0be8969dd7059ab8a75d7e07d096e5f31bfc0af1dd8536837e73d97e7296e39250cecf0e71aa637dc8be92c95ff84e7840761f811424f0dee16053f71f45aa188e23002637b7656418f5e8b40e5c6ef228fb46eb0b470e37d60fd3cc417e889a69b36d781891affe29fd62734dc5539ad9699e1aac7256a85ed781a3666ed44a774cad05046802b04fc71292ec99c6638e5eec9a03f54972c4d536901a1a5048cd9486adf5c710d6d54ea2c883322a01be819bc15ac78802926f3d74d58b8116ab058d05cb5a43ceaa661c0e55521e74e9cf7b81af53d8c528f4ac7771bb4d38a1966ca1fecdc143b5c71afe25baf82a6ca9bcbda2fa148096ba27e2280ab945330216e688615630401b3612a26013924529f2300615998b95306adcc678fa3485172443f924433d59c2a7fe8c9b4a0696fadf3a040567801d14cbb7f0bff6609feabf3571fb8dfd7638e41f12aaffd352c840f7d50c176265aa05d98d502f3367a3b6d033385bf4f155407e3effd758b425f76a499b94467b059a6d9ab915894b50943d50eddf691370f3a3f59fbd0d6917c828c2c0ed1c7797e9a40861e1492a51ef3e23fcd2299f7b4db9b979db914d53d68fd35e77c58a49d0de2d5c2a03968bc98d8c1eae23b739983676721f90639f945e095d8e5918feb10c897e815d1a49fccb7f6d494925a0d6932a086f2fe6cd2419a14a1a6af358196ff97bfd4bc03db97a33f67db62a245bad289003dd709166ca7622120e7a2699850665404f604058dc0a2a69ce98ea0e57f68dd95d2b65cdbc22a02f30206c6f58ddbfd2e420883d0ee4443710ee5a98042afed1be360c0a8dfb818a6c79abea1dfb65d794c6ea85454b37a010dddf1c3a56610df5f73073847a315417a91a5f0ccbfd955570d553c2aae2b39151fe7d45126b00d2348d12d291923b99374db3c2690ac3384dc170f351746ae3bc2f6379fe1a145491e432e68579e4d6a918081ce9488c3d9d6a5710fe9775f282b196d75c0d6138d955ca5758445345704441ad4213b40ec3b349a6f11d2a099ec6c96c3315a66727d1a753cab1ec3cd873d263e642b73a252209eb92798d7216d40a510d999839f6d08a396ab1b92b2dfd430abfa170db98168c591314ff3b595e5a8f7ede2213494720a4a206e1ab2546d9bd2ddc7a89dbed48110a362337e8d3a10f9f43a16e392549e3fa5437484cb95b608a14f000d682d80f6d0d0441789aab83ec1aea83f6d29cb17de952813736b96cb02998a93f00b0289977492ec3242926a5b634fac448752f0e8307eb48e43aaf2386f29e0152169841653df4e3104ef9295398dff155d5f9be52db74a300be9a6de954fda61e98c7413d21c85926e7f3ed6ca9fdf79d55c926350c0eaf31f66b073bb7248fc2e33aebaadca6665df32efc30ab6d7874d14b97f42694441a94e1710d55469239c35c384b6b6d568f634582ee86e9115d2f3209a82ddc4cccd6c38108b62d0dfff4b0bcb64c5264bbdb13436161380bc29e058d47ca45f00c2db1a868bd6d649ba3a0181e0610f5b5e044d108c8c4c4ac2e1026f6da01fbee82a0435fdcb6d54b1903f661f05b9b9ca389d97d8882384fca7449403b305888293e0ffbc1ef08a31186fb21d919c994150cbcb1aaeea64d1a365d325ca4562b5cef5a03ef88213bcee7b08e2736732f0d697f1cc94c2409503a462cebbdb6ff04e01f685c9dc2d2d067e08751e5903682153f9b2733212513ef4e77874af088ba7c136ef8901d9a8ec60a75433dbb0bd413ca6ec2e949a3605fa1591267bbb589d71d0013b07fce13f4e52f02db0d8487ec358d265c01aaff05099d12226e563ab8fb988f25ccd1b228c879944e478a992d4b5f1b1cdc25b79ce7c045d9771daba857150fc17d3ea941858341be85d3770d306b1b50278ef32f59495fba382a3a5961241a1da300ddf07e399fa0e51bf4480aeb2858c36acbb85de47e03c0c1e3cf526beb04f59ed4b2fd56a6d761490467786f5f6f955ff4163ec0f0684ffde79c1c8acdbcddc5fd19287b673a7d97104a5095effa4a98e19f25c375c1408eca55bd95117bb49f0b4496612d14d9b0f61be1125a47f5d8b1c30118907cf678335a3f875f6c4b9f59c35a5c5340a30593150687ee454f4b3b9e28abe4fd9231fd968eb1eeea903405300a526fa450ea2c3d87721f42f4b43f96fb8c513c95595ac89a299e697ca1f50872855ee312740b9e43c93a102eb8b40b0573dbcfc28a0c3bc0210ed9f0621055cbeacbb822aafa0e6ec4007b01b433b21628a07087c1672d094b604dee2b750910a80876c3e735968860a7ef1838f93673601476bc5b861cd4999467fbdd5453af76dd18ba33081ebb02ceeb78f3584a9db47ea490a2c200918717b381dd88fb45b83303ad569363531dba8259b8b0ae1ac1bedf1e949f292d7f0a5c17286139543ef48010c8c7fe1af9a386240208dc11b41623f9dabfe62e857b97a35262921cd2d1256e055e479a1bd4b6f7d39ab2d963d883a0ccdbb5b40b9e1b557168502e923361879f2edf97a5da1b79c0ad1774c564ae777d351711287034f637c09e55fa0f9697c29817d9e3105a754fdfeef46f34c4674d693545c430ffe901a0cc66e83c4cbcab7815ba51a03676aa6de205bca957d9370f9e04e5c4bef9fa0af7e1e4ab7966385f0b9a829cdd9ebf0e7bebdd7344014a42f30013903d5de0b754c6bba577c35193e10944472c80b2897c07391f3fac8297c4d8e32bd70b5c3f600f65e1a7d627dfae58ddd5d3366f309e2ff5271476813a7a8a18cbd5c7115fd0174f6b01966af6b77700367eb9bbf803d54f3682f17dd0feccb225f931efb2ea59650463491b84b074d6fe77f9f63aae0c7729def310b5f21bd3c9a4bf20351c61803a819bb5f400338bf764939032cbdb906b680407436a3853edf9c4c4e007c3c14513fd84b877b050dbc049927873a631fdc3926e6df0fed073a0f7c7c64990f937db8bbae1362be2e93edd14991519b02bf9a21eb0a811b1e88ea6a14c749fc0b33bc00fef1d7a8b9dbb5e74ad617fc62459f390884defe145602a498978318e7724082895634cd4049478031d225ed66a87ab89da39cd03a13fbcf2c5dc18223d8831aebb6ef72e1998c59235a410e7b0688fde31c9a12e248307c2ccbf0126d0a489cddfa7a1020427065033fa78fd128e309582bf1473d891592f6d4c4b0399c85aedcc8dbf379be4cb08c9017d45aa6d37409183a70e955964db600ec6e27be8ef8fb9035b38774dbd70bf99480f89464fa5d4f50a89a308d6e2463529f4605a4f770a57429d82f20d6eb499fb5581928041c2d65155baaa19d55916a183e00caef4e1cbdc7adbe515ac6485600c83e83b851f41a6d0e3f64ac670bc7aa3decfe191bc9fdbabfe42087017e2f218236c3653aa2560ad58ee5863657e49a6827330f5ce7ce9471d30619a52113e3b5a5237ec1a350a498e94a3df30e9437b2fac4ef13838d45ef705949a0a93c44a553246b5549fa06da14656a161e0a24eb7546613b335c48aacb664c659a5912d723fdcd2e545a0564da13a78beb4c1b38aa907a8d5ff522d8b54907203e12ce7b6847ca68677d49d0e70b3a28ae343a7e36e7c06ac7ab0f5ee8698e5a04d932cb8a67bc686709a2ddf83269c89cb992d93b0f4bbd7570f5d9aaaeeb23c68bb7b1778d485a14e5ab4ffff8d1ef8f1b13f2ca4a538f1c928cac5f2341b51150fd86a53ff7984ff53a0c4a229d479f1b0529b1687df781cc03c7f10821de24c9544b5e634e2cea9df27d55437e2524feeb306abe970d08b5362e714440ad4b400406b0c44e86a0c607aa7686fc24cc976b09530bd64519bd555c8f0f888e29e87d06b1015df6a174274bf2fdc4f242a1ca9d90f0eb82c4ee1662794506e2a339731a7c34caa98959f8a728e584f1acca6079c3db3da32676d01b7c0b733996e6ba30120d5644c13b23879006f71c2965c7eb986c1053b50535c627ccc85c2cb6ab52e1398747a66cc6789ec012d5c81be535139d3e9a451d750ac1cbc11ab26c442d084f3644c0f00b4cb3d59aa77163e5d0b601cf85d608195783ef198b8f3e000ea8d401d10c6a08cfbb77ffc8bccee3a5aac262eff03d362dd620440b92f2abbdfef0159cd0b6718b1c54a594be7b8f24b846b302aa5faad00d105d13d717fd17f12371c9a57f5e0b76bb19887133295eafa92154bf02f90fedc8385787b5871f8f43fede0dc3142a763aa66e829d427714813d50d4081b356c8cc6d48d81a1844821eb78c3b1299fc0eb1c6b711c8fe006cf1dba4b025a34cd175ec9d282cdedfb46d0c55cfcc6ac1b6dae9ccd999221e8fda70fa363dd93475bb40041f025bc917d41515317af8fff2cb58108a85e4fb0874274c2018d59344ffacd04b9ffe147612f8e138b036c6ba3d4c749fbb6d5418dabbc5b62ffc4abd27b77802b7c8d7f362f6810b9b88e3445d0bd939f2a91c5831a77a251bae4bd64c0bfc904bac366808c33656067cfecc2dc721c2d5eb3f913a7aebd42cd7e13bb46f200ceacb49d5b2d679e9b65a498f68be09dd1e8528c8d4be727823770af8bc8fafa2fb313e794712a4c3cabc198e797fb07a11bbc236a76ecdbb8d1678f00faea00ee5516c36f5bf0d5763350cfa6cb345af9c0913e7f2f78e31c8711d644fdbde49118cb08c83c150e5e29fa37271a095945dfb70bcc55d877e723e03492a77fcdefb9e830da46b7a40f1b83891098aa528bd219161b3048acc0dce294abdea87187c102f4f395c6d044a0e4218d684595bcc250b0c369d37246934c719947a779000f0a5c4ca8492508ac1d46dea47e25c668742b728e6046a6aa3c517f74a86d105b9d9c327e4729d608a6cda3b04d5cae49245ae02c2aa056cfeb65d06dd9525a28ad2748b314f3680a820570efb1182977c9f81eff1aaf2449100390f841cc969c01155dea03e278f8e5116c79d4adfa39ddb39b6417f11e295c25187017c47061516a3160543427e2b14a5869c5076d9782aae4ac89ec5fda880a18cdd0f24e7d8553b6e5a9b5609023747b6cf099629f0decbedc1a494f1c4105f9a4c0630657df3c2d3acc1fd3a5b4e6250a09f24a6f315e7d8836fd5709bb70218be7b8b2d93a66d393ae13d557f864aff7f389a81e1bbd7b3ea15c40243b2ce783a00bdde5a068b8ceb113c3afaf7a06e7944daa93881a09049b6c1a1c4a404f8300a34a98ec9fa884c4c98971992720385fcfe94070498371cbf3e4a555a6d944f3b3e03f725d392dce64ec3d9ff5f7fe28791262d2a46b153fdac3bd5240caaf45052a3c4e35ae9d14349501d3a390a126b805a21b55c0f71a96d7625f14492b7ec922f01dd2472914e85f9e8aeaa979ecd674fcb6307d0f606a64efbff3423059a0581986a1cf2524096c109b88a5bd3bc61f906feb368f64d2f7130e2511ec42634c61329344d89d29c83960b7fa8add6d68e90c28c151b3617a3646538ea1e0063bce063a9035c2467ae1ed6c2f4a8eef98c7da941556ae2b4805d299ca62e6a736966a423112060cdad95dfa8c81a06f3fdc04114a7d940d560f514c39cc4fc13a24bc5596026fc3e9193516221c6b3e96e054633ef24031d7b5dec57dae603f3c9b2f61fe21b4ffd4623b15a542abfb98bf1170cca8208fa4293ec31f884ab6b3dcfa214001ef6c46161c43deb85066475939259631b2e0f20cf522e97c03709ce2bdd8eacb170bec68601f212ee0fa5f726a9541de9e5e98c255a0860a487c7ba1380e76ca08952e54e976d8d9fdd29fa5936c863c2f4199d3481d7ba24af39cf4d2aad693452391e02e840ceb3108681e55308e0ad03085329c4db9f05ed8660f63aa2fe9190cef8e55fae083b53cf1b057b36cc172808c7c6ecf5068bd4bf2fa958e01e9e3102c1d274fb2e9e09fa0541e695ba3dac773df5c8bb54d5ce24de01c72e4234433a980600d48353ba37b990dd5bd02dfb9246b30efa4194bcf050a6ec84d538073536cc89d17d2d550e5629b3e457f45c7e450ccb81683e84bc5fe8f62371c014f2d0311780be223fc1791c8ae94b02337e52d9514d6952a31e41ee3e39e01f3d970678928932ae013f8bd2589110cb803ff3b646af6c6d6dfd841b7fdaaaa534703be106a4f0a30dbcf238b06ec6f476ee630097c670770f20f26ac4caa187c230fcc60c54575ef37557adb9a774d42002fa9ef76066d1552422c70614ea02d2aa510d2276d8977d1166519bc8e3ab1971ca1a7799ca64f88fab903c28332f152645237bd0da97c1a168f358854130a08176ad5c4fab4d2647ba33c492eb99227bd34b069f998dea0c95d288f59912ef6bc35f81f613e6f95a84f4008ae633b16f966545d9eb84bcfd88a644cd42884f3cfbb022ce58a492d318fffb4a60a8469cce8ff8918ee8975e05540dacf95716b2f4a83506f2a3240350dcc12b107dc1461a94995978a5b1db9defa8a269ce6868a4e73b058ced40ae4b2e094166287b6d9ab7fbdedc8a5b78ee1bbd4266227d707e4dd714ea7f886f2edd846d090337028530ea325d5a02fc4bb78b245b3af7fda24eafbd9fed9844a2a56243939a79b2de873d27e186b4ef37ea667d0fee10a10dfed3287552f0328d612569a52e79bb909902303111613bb76e5e512ae18822bf5e6c0a865a46a861b2ccabee8fddd40595acac2224192084a6dce56a39c26886da69cceced26568e658c42a415b714b3ff0d486d10105af6b0499c7af47983b13d7c838d8eda45b7a3e8b5050ec0188a089cafa258c69aa69731f5d039b3d9e8e27acb24088061122edd915c9858030c28846d8c15c35b1c3cd6fd9c05aca26e05ce1f00c94d431cdad78b5dffef228a1b47e80e24ec2e83aedcd6e32075b81b24385dcc5b6038a0a2c62664581b57f2524e7d9c3a0fe2e225313d1196bafe0ae4be5dd938dcc40e8c50873b3f9ebe51657decffe05ba63b51d8f4b44348e8f466fc50a31d2b0d9f0f9b92741191d3bc77ab4cb5309eab8c26bce16c8e89647ebe47fe6ceabc9728a1ff399915627288451c861c0c805fe085176e3bef4522ca1e8f5860236fe25211f11eeecc13bbbe6a8961956b62a7b2f70b077747180b6bf7785ce475b0bde777ce7c108438aaef2f002d6e06b77628a85a62c7bd5ce8e4c84126e1663cd74585453dfbd3854dca7c872e5afccb6bb985373ebf356bb125ece57fce8d960c3717aa43cafa5d818b4e16c7238997a2400a43ce0320ad165939f40480f83e5af8a0d8520a0923306e14834bf5867ed2e020fb5e2d46af67c2358775207099b08a1c49bc9c909dee34ae73b6f84c396a7c67d42ca2ed9aa3de2ad4e78c2d92811f01b6dc65054f35b127f30700e1eb041dcbfe2ca56fa21b9f5bb3b28486c38d4ed50c19218e3801f5dd8b82cfaed537e267ac5ec94068fa51c8346fc8730b6c58c8fb58a24a290e1917fd8d161068793f7dceb125d2502cc8639121cb8cac8d7db50950f3c68b9f2c429ef5e75ba1676d8e9e5db371e1edfc6d829cc60ba0f77c0a35ac24df30e202c11ed77190e0d82e4659cf9e960c4bece6004937ab716df1775f49f739858030ae6b9d719e120a533c9b134a067360fe342e739099a592d6b78726ab87c0962330c2aeeece8ea8e766ae29a85b49dcc1e36143c43a9709ac19598bc2c3c8e6374649a41bc2d6d868e81d34955fe6d9d2bef53c221685cfda37ad520390610a5d7847c8ef37463b97bed4cd0022a2db5aeaa7efa753f69c36740aa8ddaef5c1736875cfb980591843c5b7c77480afd8e079416448263fdc4b36f352048cb53c33051425a57313e42f30ef714ac49d7d0837dba07fed79e61ca935b4dd29bf65eba7e5fc14eeb861cb0dd4c929c953824e4ef5143fba8a8aae5761ea274bbe59b3e0d1c83980c20d5f24f2a4f83b2181351d94f724f85ac21739519dff9bd8e2bb72f5a0a90b5ba55cd20ec6c4a6c9c5969b898938ad285ee48dbae8e757292fc432366ff25cf7cc593c19cc8b75e1be78f2db4660d3a7eb5f79c69b96678430a1a197fdfec5c73d8dc41621647fa652bd120baebfd1d7174a44dce2c86854e43cc0f84b5176b601d1eb7b887c96a252ecd52d55571bfccce8325ed0388c0dd632ff217d8e64915336513683031380c2b14fa4ed583d18759604cd90d8e0b61a22b23027b6eec641f5b8e7b99851f5a1711e6132b9657df464ad44500e7eea3f2ab0cb6a37c54d540b9b5c616d7c707220675f859f59b61c6e7f2f94965e6cd34cdc4834217cd2b387c685f65c0165b02e28d8ea625a53f88311d6df445e1f1ef366bb5f43700dc5875c89bc268b77b179e2812b6bc254d9dc0150d8d537ed9cc85fa3d709eaf0dac1b48ea5206ddfbc55a8a33d9cab8e4276c127601a120f8ee5530a680411e52ecc82113c5e3d4ac5e2d995c2a6a5a23b135f56d8957ffea88e590c8ce8231ba5629305702ea27ab3a7260fde4433148471db4a83c5233c5af319171360a4aa58f92e418136f0c828aff411b05fe9b09dd1377c4b836b43112aef049b187073b47bacb14d4b8505608a656ee2db8af1055679b706de41775c044b46d81086487c89863bce6f79a305813cf6278a112a0e6b2841e33cff7ce2a582f245ad430fdfb11609e01dab8f4bf56f2c540ab1739c6451aad248ebf59edf6d636b715867515fcd4c6d7aa34409e90f9ccc1d4e1445019cc4580ac29eba510f12a3b9c54b1672ce6c7041adb799eebf4a21148b5f91d1128b86de525d8767ae9f17484e18e5ae64db071be3e454d68a1605bcc8de7c890af8e71826329616389592af9446b59a59c19ceec7f741f9b6c4e605c096854c44d1ded735593849a1d71f18efbeef1b85dab44c05b766101018fa5457b1109e7152b6360184e3b62388bf9a8f01ca0d8f60a8c8ec50902dd1be8bdc6e48d630688c84cf050548205bc0759ad744a9b4bd021fd79ae436e9ba5e89c89e5a746fe55f422527b3d9395c444e119c3df183aa8a0a38d75f934ba12ccbcfc37bbec1b167c96223988e2472f5bd1604a45132059b62054eacf69a396690988f750383758a398ad854bf7dc02c83de0c25c347e6bd012c022f2419e2b85153a30a416ff853f605840b6e32c71f77f00fc5363ccce70f9afd24f02e94896386ed058ab64fc8a331e3e743578e25420160098285715d9410dbc9a046ea2b7dfcf150bc909b9129aaa1de32a009a39472a56944c5376921ad42fbbf40a47094d11f2e927fef95a4ee4176b59d14fa05a018cfb1867564e344f2e42cb8b84654229fb6b9161d9c5a58d0c05267ef610915539cebefd86d62c1e07bc3dacf4d4db89867e0231f55749ab2671005578aa2a5d8c3c68fea331e2c6f220d6b9b2e8c1a12c1a7908c46e7292f8235f205c485cd00b87717f0b2d0d2a0c712a4432b446e77849f6406a0872dd7437fa604f3dde3502ed88a3d6d58fc69c95ce4fd54c139dca3b35160164ec7610083af49446dc5610a980828c7c9ff29642fc95798a1e729ebe01de3d533f41c05642a14d799b1e8054674e57a44875824b5a2c5ab7b4d05e9eedba26e39bcd8254c5948f88a4fd79322df9a3f39260739504f92177e9b3f5118789167cb5e38d4a795eb163e7a3599f33be2b3e809b37adbff7600f244056ee9e7e1efa012f8d32189fd1870a0dbbb92df93b93e4cab120f5bd451fe7f1089963924afceba19ec9022bd544622027010995b8b0220be7780a7f8c33ede3fa3391338655ff0d7b84c69c5240ff3ee8ee6be2385d76942571f3de01e3d206b2fb572b6a5256042a3d8f59557677c064e6b4434e9ac4ff0c6dcd2986fa1d307ea4a0c80e05b06c3a612b741b34dee5c794c982f40e6454eed8832b1742623eedbbe4d7d621010a2ac1571b5ccf6c95535e9435f48fab6126e1041188fd3ec6ddbc468800311655a6e45c61373358ba9a846395456864e67f0e1e97ae16eb737da157738e8a5b2d7c5f47f52eaf15b3996a7e31375f8b50ddc47030af40dbe0367c40509c9c3ba17cd4b614d2ad56bdc1db818e997b9fbf699145ce26dbb6eee2859ffa1cc15791ca8ca34c4ed846b5eecd7ee1d0eb03c40aa50cdbd8e076907b5cbb347906243c3df9eec4404d46c7f0a7d7e47983f39d3982e52a124f1441df1c2ec76f8fb7d8ff0132a64aae5185daf91881ab803b3cf3679a606d982d33b30b771f37ed71a7e270bf9b60e7c385dc43cb37ea40bb772230b05436bf8581a333b891e4d7fd2ae9208ae20960beb16728f923523fe0ab7f6ac2b3bfe1d022e610594eb5051eec40cf9c2495298018998933e8641f1e9d9187f8d3c3edb43cbdc28e4eba5030585dd29be1cb14910795deb9306449d78a3b34de60854d2594cf72f98a74c0a892e54020bb5aa1cbc140be98b66b8d073bf2225145ccacd84c4b90b9658ed016a609b931216c787f79644336dc00de33bc184be91b4a9bb3fa8c4df9c80b2bd533f65b73d0cc4b818972284c1c01ff96bc3d500406ea90868a58590157a61a9c67f0f2a0733b0a0716d65e8f283d82108b9587be22c5b9212debda1383647fe2e857ac634f2563240f3bbee3004e02a32b31df77a9d00cfb5b821f58778ae15c7a49e4dbf16f39804d9d0087b158919c9238d3779b917964b64cb5c1ff0c7988847aae77cf034b98bf19a69149ccd4adbf080085f150454049f0182f9a5cb203ed887b7a53e4b137e28893f023d6cf70775171f65d35ebe63d50086434540f61f76f88248b30320b249c915a8810cf8fcb6bbf0fd0df7f051b7ee86d0422084093515a638a2688a683a4134c720cb3422bd8ade235b18de9f8bd3817fc2ee2f1379f05c04488b6c8ebb657cb52ccf8d8cfbc0c5b208612fac15a87f7e3f3420d16cefd9b2a119d2096a8a558a07d0963d0c10cb66299184abe50c48aa0664a3c56b624a03d3ace38ad0fc3d8e7ae431e98cc43999314aad654767ec07d124ab45df9de2d8dff2d61d5b516f9d5b89f398293aa7055c88949151053939911d584c08a8826fc93b2970d9ffd96aaf7dd8d72505b4ed84050d2126031626c28e35a06ba212eab85cb392c00aff233576969777444fab2530f4c60b40ed0b403044069053ce392a565978be970bb97eb0739416abd4587bd57a82cdf9d3f3a61ebf5ab08ec22f1667e28f63840a8cb2d63d16201b37c29a2f104f3ebdb77c9a1c657f38d88cc6f1ed89603507f683143baa12a88da4a8d018f8625b376087cced9797d303e1b64c66d90d6d542bb1941b966e731afb2f8ebeca970720f6cad7ff86eff8950301a14de5ce0f4ea0a97ea16edc5b96457f26dfc8739bb8226fa50ab8863dbab8cee39b62ec20039da7658ae4216647e26827b57607a3b815a5e01525af3c8c4e48637e89eff50dba258180d8ef69952d34c31fad185b3c79a50a441f99bc08c243c9d7799c06b2bf7d400caa9a9c472e21b5e2332815c38a565c8833869c0047dba15116cd4c7adaf40f97e6019e81bc3d4642a7d34c4bb8223b639adf1460dfb12b0f2b76e57389eb24f70ce25a01499f72e9faa577f4fe90eff01982524a456838b41511da30020448a23196955c08a955eab7021b497b74002d7c54bfb0f494bfc5a1a2c068bb163954ce7a62237de750d9d0519306147b048d8ea36d4094f29a7c49b18f47be11f359b3bd5f08d093901f3b723fb6ebcd585bf74fba77a777c414f1c6070a74aad17681a194dfdf12c8349812f46a5ffda64d4c02a276c100739d7824ea852845e9ace6e607655d57d71f1f2427f216051fcf42329f1f070619b6a361208da321b01f6681eacf1931fc7c81911eece9656e26f83f044ca443bfc8bd0f1c69a50bb64929defbaca6d03769f0fde917ddfe895f8b89efaf0f98f5fa201c7a82503c0cd60473ec86c57ce9a98534b9959f618afad309cd9b833e6964348e7771498d07a5e1217e187cb2932ca050c2a24b6051fad0652e4c81678f2273ada1108ce1cdaec77541e02eaa02ecfa713e052bb9d728dadf7ef06d03e6fed7e237615949934de14e142957a7fdfe2cb82f50830c545abb3dbd06f672cecbb51cfb57944fcbe6bd3e07f51ef34e9d77c2d7f48cf59272323892064ae0c8b28051ca802c72844c5e21ae8c14c0576c06e94368b7557e14f868539fbfc879f84da5f1bfb0be3cd144ed5070978413fe8a2c5d39c57084e62ab06e315a579679a9c004ebc3f885eff386a5d3d2bec4873d889eeb04a9de84080e5d7b0e56e1151ab595f97bec3452f5b050e0a19559241f1018278b21880d1829a636001c29ea0c97910a4c4e5c5da7798740dd924f79e69ac321c6fcd4a1d2d1655be4f39544ffc716f10af2a40e684bfd9a7fb36be97d2fdc91e56e6bcd1cc301dee5e78193e53fdfd69679021c81eab9c0ea4b3e2cd8e5b75bde5352dfc45f87f223922584ac419a9cc7327db3ea1b6c18797d29b8f8caa9ecc7ff7da52e8cb0f7bf13087d5874151ec27b4c2e15416f38448778b6f559a880401040bdde1483fb94a4eb5944a4eb44afc212a370d31c5de77e9f308cf6aa1071e135cb3e602a72fb40d70b821e32167a7a29c2f58bc856c0c48315feecab84f557c1f8b05ea0c212f7b7b1514c635f35854a51f8e1ff08b79ca4b5bf1d2c60f9e63d796a08ef40ba2059ff99ae355401f57967569a36082d6d972edff72a6225f50090545712b4a8abbc6c8ff04b69165aab5ab6900ea2be772549a68432f15e8e614a14efeb9eac3db8d88bb77f2e2bbc34863dc02015dd3b38e870c7472de9e25112c43cbda059dee8b1dea19a6e4d27b03d256c3aeaceb3a7c4334addb99e12730de516942a1eeb29e09cc75da60626f9763b4516b0ed7b302fd37e1f298bf0b6c5cb237512d524611c04716afb6c949e04a67b0b60ad1cf0e2b11f2d58af02c5c08e50f0084a10b03dadd2e31fe67cfb54227a1a10aa6f983351caeeef9327e0dcb19984369be984437cf5bf321071ebcf1bb638c8c581d3ec07cc837eb920f735116d0bd06af2ff037e5b8581895b73f7c8c28a8a18f2181e7b99c8e5bb3a38ee86022d4810a3a321a9208435e8fb116e5d7d48e03ace47e1e101db156edd2a33e7126bfa1288970effd47a8120249522c35290ba2cdc2afe006faa3ea0cbe5563db6ba263199371068e0ee826be69dda635808f4b636ebd500ee6e3682c754f3800890660c2d53ab46d91c9e4430e24d2fd7264cf59edb5a71eb15b8f55a683aa5244336852a9dee789fd58b741222916c315d618103b8552c7aeb2251c5c655e826812e7df62f3009642941cb4bf20d7a82a6f2e5cbd30b13e020520635646cd4f31870ce7f2c160a56feb051ff58e5d432f9942737a547514bcab4049692c802e17d5c374a7f1d6f83f489b467ec78361f2de6a4a8b4160a9982b88a2f5572f4bd7183adfa36e532b5f54849acf3ea61c3a19c6f9bde90288824e13086ee6f2f7ee821e533656428e0a626840c48d4fc0d05165207ae5828df56d0451e9de58b7576679cf53201c50777fd9e3b24a7d397fd9e6df815b976d8db5374ce2d440de21dfb977b2191021c78948e5d6243b6ae4ad2239738c34aadb657301cc909e8fde0851d10c63f08c26bfe9a2b48c022f0c1a3e965d9f8396455087f820444958ff5e2aff25e15d97aab6bdeafeaed524c3a05aab010c516b6262469439bc275dbfcaa07d5161c2a9e068c1463904ff9e4dd27424a2ee74a954fffa809cb6baa9d69f0ad8a10b80c7268e174aa59702ac2d902eb7cca97c2ec79a3e12c901b5221cd2319974b73d116b364707db25fc60d51b042b481e060f36395e423e8259fa8e8146439f96f69667e5d82cf60cfd9abd219f601fdf6883078fee1e4f2316f743fd27ffaf689df74d84be2239428eff69feb8e561a513d0c3d2b431ea79fefef7f659702c07ad7a76c8846a4df62b092ad64154b0416dd80068d8bac7d226a07e39e30ccbf482e1e7ac97faa3a082dfa8e3e24172316eaf9e68c8b4b52abf95610eb467b7b76420066ffd6a16e4cd5f8bc559423dff3a5bd8e3b68cc8459bbb3f27739b4a636de1233472edc88044a4b2e9ac690e0f2d4d8121ce2fffbf9809f6a6099c870ede89eb235920ddec87460a96933447fc5089c24a8bbc211b2bfd3531023c50df69805a824483c7601a359049223644e58d0ea1b34e40f34560fc75d6ba54e5be6c2a7daeaa77d0e465aad885a898f74a195d2d2d92e2bb267f8989589ae8439d1285935ecc33a494389589d4a543e7bd3c7d0712368529c6bf787527d313397e362079fc6dfa8025d96b08113c429fc3b40186aaef7f01897319659a65c8452d54f4d0e0a45afa5ff9745f529c82e6e50446f6ab82bf1c6df56e5833158be199e59460a6f8c28707a0e3cf998a5262e437efef81ba29307992c83fe75e1e49e68cc1ccf346c2c807dfe77d7894fbf421009b8393a17ced4471c07b22dd0bc35b1a8c70b36196e9d11bec4ea6e397caed0cf36f5c5abbe1dadefb5fa24519f327a8fa3be207abfbbf952d03326f452523a52f8c2fc813d2d5f526f55cc1ce7f5209456fc282d9b24fc7fda073f4b34df8e940729f18607209ffce3cb3131b35b78f2e90fdcbf329959914f46bf64a0b70ddeaec1e2e21e11c3ed07160721b23e90250512c1edd2bc7b1184bdcfe09063712490e2485dc7980c718e0c85f1734c0a3550d6231bd96fb5ece1491084eb0cc3c0c57718c663ab9483077efe8e0900e4fe95c6331f9d645b1473ad00f078291d412db6589888f02ec1f37a0f00de524eb3d87f006c420825afb69eb58467292f3cd979a33e8069ecc14c14c087086c7ee2db71c9cacfc84311bce8998378496610fbffd8d1c5abf96e3c71d7241550b38cf5568152c4a3649f261f466a538a819155f4929f73f653deec671df2be3677af0d5ab8438c3d0fb243efbee020a35cf78a95b60eee1fb4a46c86fd3723ad7a530e3009745b00ce03baddef8da21022b66e13784145dd59e8a632c98a6be9d7902c85cbf8fe159bc89401c996cc470077a17204363099db2be82121eaf298ac6c7d7e453666e64aad2f75bf4c53a3d9cc8fcae58cfd7b2c3fa1552d76bb7ff160f8633f03306769889d87c1fe286dae59fb8d36a4cacd5a1a9b6fda2b50da2b891d0b7a4c183c9817c8b9f93002a332f4fe7b603ec32cac473870a3d10260ce16e5ba0820117c588ce2d9243f2d1333ee014aad327a632555fb907d694bc334901eab044b24a6647925e91e22987d2609080d750a82c8589ba3be382ecc89cb4b0611063a7582ad215509aa1ae109efbda5a8785de82d35bf3134fb052c004b2bb8af385e8bc33879d2ef2eb5c4d647228ef8c3066cac367aeab196d8a569b68522547adadc7d82774369710d30934e86461fc62cab2034bba101e316e41964d516c50f6780e46e676cacda29d86eda0bcff61e094ede5f8387871187ad01121763f6db2d07128633d2e8d21fa73e2b912b9de101fe530492055a2e96dab26da9169b632276e0f35573c229a31211a150876e49c3c8f63ebc41cb9e2b3243410328679a9c43804b6c9d4946e6745d522d328ad1647017109addea9df26ec464e604ef43d1e850b76c62cbbbb8b8b63772544714fb062b1d62d46c5c10c37b1922732544ead3842c8b9010b7884df090a07875b0a2dee1c53628e427dc079ddc5a624d00ffeda1d92148865f6165d5aca77828383f1ba5ab69af7e84da0d82cc4c46454460bc886048dd14e3d27da207725653efe8e5de9adc005d637678f0efac9817bc8c14c643db8e94d3d4bce3fb4d0ad0536eb2bce6507999023419d21f1a1de5efe0ea924dd4f290852cc2ea4e07b87fcc3eea05e9c5e6ba17047ff709d0443633993e1298e3d687d1f50bd0d8409fd5c97172cfac39be2e91f65a7cb82482c890750c66bb80844d7e33c4bbaf2127789c3bf379ae72fc0c11e8ab51814880e653fd95d0389551e1dd4f2feb03625f573bd60f85226c4040423c07c5b0d6495189834a8fb5c2fb7a9a9b0ebc4d401463ff008e9f2701dc8b90e2baae47405cb822e3da2388c2b3f344560d59bec019fcf132231e92698cede535952d9ad58ad2ca1062a03a21d282a425e9975d7c270246d84dc1d29400eb8bc87cf236f003e10be7c1ef2a576ad3afd6b14fe707c4507ac67a520a48cf54f418322958d179823ee85f9f7f298a2da201432f9b41b9361a5bd8f7b5511f81e102e936bbd4e450b312915460f199b61dc59cad81aa62d0dd9683a022b596978f3ddf7b9061c23211accd9900b29331131386e457add7133bac1a2da28350619ab9952e18e00be3594f15bd245fbc6e67594d24a850d5f08885bc27cdd4522a4ec493b69958a039698cdcb636669dc611ba74ffe9c263d8ec0e0eb11d0a79e62efe35c229e9c58364006bef05cc08d73a7b1c1343ee83f539be7e96298b66cc55b9d023dd5ebde75a41a83b4267a006d60178f0296cbc92c8018584030c0424b889486d46185f8225c1bbe28e500d8b23c0c2554023b34b7ba194d2f04abd5fa08a232a53026e3d631819f6d848ba60b33e67bdd728665aa79881319c94f6467a5e1fe15ec6508f9115b22fd398c18ebd9a0177ecfb0d5b878b98c12f933f6c0a29dee829c4b9ec43c30b30f82f92f4df5870ecd1b63deb39be0748d9b61b1acb332b856aa084873a7b4c7bfd13bc57fa7adf671160e6c497f6ff94a1eec856b778fe5abbad9505889255fb8e3a0bc9a986698fff8d8c2352e6d9ec0ce7a7809cbede01b36d660ec8f38b1f97a2a6b4ad5c543e864f0b3bd5b80617d324ddbe039b2f708ba9f9a9c3fa00b5d9c4f000d6c54e1cc72b70b655d4fe03ce9ebd54c44f27287a49dbf5c2a40230b44e2711208e016b279406215b3bbdb754c7478cec29067d530b3d9045b5c9bb0bb53a293acb2d4274bbc6dfeb0ac18763ab3b88a7d3c0e914989607caec23d99424d599ec972cac798eeaf496527cd54db1a7302c5642b8f894437a56eccc5cc56b29703029513821d3a320aee931594a2807da81655a1cb3c2a65a0aa889848331a66004994467d30c5ac47b317415306549a3c568f2d1627747a587ad82720e0e1c0fe55aeba7a42b2288a8396c2abdb61ca0b786e976a1121b475201dc7b4e8d98dd9aa439f52926798d5e54c76b98d30a90903c837c86d8d0365b36db6de318b0315a534dd0898b46c3f92f7068174f19fb97b29d2d450a44ea4fe5169b63f2ec9f2289124db19c9168b62cc6be5873584746ffac462c5b2c12d6f7a28ed44b7b9b926a69c3b1845e3a4f42b822d8e1a4bb8459249bdba969758502a26065ae76c790ccb94e26b5b678cdf11e045bd6a2695657ac8dda7475698aa56b4b131dd9fa9fb6d6ec62a4f73394d69a38ac76e08750a9d0c69c8a12b261b2ce1118b4399340492153124c8998827d1e5227139988c238519e7d7d026de5734de5b7ac43d37bae5626eb2e9900259c082b1bebc9676b5c1092286e1180e808fcefc08e253a9e1e6d55319e74be668799643a148441ed0247ac20d9bce9809e566d161ed5c7bd560d15756bfa685ee61439821009e67200cd8e3f5e32fa0d4c41174322699d457ed136db4ae9a4300a4066cd29977e7f88b9994d5e07dcbec856dccb91ef6d46170e2d3cc332e21069c30e3c4f5b93c1f2e9a5bf0166ae897e5624280b1ec4bc43ca90e71ff46580d0340ef3a8f6b8815b251bf4ce29f14bcd1c286e997ef91ccb295d7093c5c41869955b165700625ebd9249af73535188397f7c2be00af5bcac7bdc30665849706c5e1a00263c18fd1143a374b3892501a10acc1db21a7ea9deee49e566694fcaca936303d939aa3ddf32f98383677e739773953f4073ff6bc44e9a90123d04d1eb25a88a96db76e599ebdfa9948da16f98242e66b42dcb8d982dad65467b2dc9cd1c632875a7212e99ee85c818c5347b5bd568f4894dd16e6f65e2ea2bc6f8c4edbac4f5294d034757736f5e23b65c85c449b2e58fafdb07ee647d200ba12933fd38fed879562ed06fb419459c0122c63d7863c45b17cc1d4ca886a52673fb203641a463652bd39e75f1c962a8ea915f0a8f49e46c8713e616db33426425d6bd3fe5b9b9c2b3925bfb4a980fe40d6d687b2efc1903ca8213cf00081e2261de0abc2082ace48a09c27c1e7791b883794fde60fedd87a86d3a2d59469d6f96cd396eebc62511caab8cfa9248f2f518559824e53a84f1f3bfc9e42b5e3f2f688403040a32d3a75e308e3a6e0e1501a201bf742f0a171438600928c81ed46ced491af75be02a36dab2c1b7e80934fa6a709e318fa321ada5e23c280014401510866110d9d5c6970ba04612580556f24270f6413fd9f8c79d2f7e6fd18bd9eeee95bda59449caf309bf0aa10ad6d22896dc9fb34ae58e813a0603db4b992be059f7ea83114c9b0a60d392045cefbdeefe1f8cc06d18bb9623ac9c04bcefc5d8becf07f52d8bdb239080f7eab7b79dacd9dfee0eb2a8b556fbbafb937b5c5a90e340b024ddc0beb5d6e62d74fdeed2d8f1efc8b3834d7378666d7165d4191f3495c0c6d75eeefee6beb6e8e963cb0efc455fc482d68bb94f3c85dde79c52b15c380fb6dc972f4b860dda95362a964a54a9685235acd6ed40115b8fcb5f289667d6b53b46a768666446d48812b53e99f44dd77d77b1f5b8fce5a0bbf27341d79c3a419f68f513fcc41bb93cab075ca96d9873f7816f1e1f92d872b56b83c33e687a3b8e8a5bb75c973f503499c41bf874efbd1c0ec1aef3ee2dc103c1eeef87ddc5a04bfcff442d7a97ebf207862f173d09fcfe3ed0cb2c95aa3bdd2b73b92e7f6068e38b41d71d7ade5fd0fb0ef4fe76a0c30fe6cf3b73493b2430cdf7fdf1816f197bbbda4c880a516257dab89c0d246ab46bb20174eb96ebf20776600a4f27f1868c8f82dd06b546072f1136bef495692e815e1f980b5de23f680a3b701eee6d2c076763c77bbaf37a811ff781bfc1cedbd903c150fccaf0f3582a55d661684573df81f3702619b5867dfade89349136b208747dd0c3f49533f7f4953f77203fe8d1ea262fe7b807e06a168333e18c78837bdab36bd6c6b5a14587222aec00bca18b4db2ded14048b12bb82b8da05dcf0e785c2693c96648995223ba24bb62825d23f3d0a46b655ffc19078105082cb0ea5e1ed2d2d3f5fb948164dbd7d68a946d8130615b20b06c6b74c54d775dbbca51b9b4884ad1ae6f8f8020601e74ccf6a7e20a07dd5c89234479e2df803fa63180d4758eb29d1f531107ddfa4190827ffe156f9c1e0cad5c4192432d88bb020ea77d84d227b4c9ce78c9fe00e5e2f70a150c9a059a818460be4b16075d251be37e96b1913009bdf3e311d01a74674c82ab527d5d5849ba9ff688cf3d15e2b07c7f1c964da03af94f9c1af70374460671bdfffde502cb77894ef4c0b4bd1cabd05d59faf0159880852d66576a4ba3a84033a969821d33c664e20f82611882a115719f7f64a6529f2a7d702083c9958ff3cafe696d3b3f2e4f7bb42b10263780600b0ca482c9c47b1f1314abe9834ef912c4ccf4f1c3cb623291f91f3608164c261e073298a76a3f2ec246ce63a2b0fc24b857ecfc17ccce97cbce8f93680cc5e21fcf4274111ad8880974fcf306a17f5e22f4cab0f3eb2099feec55bdd3eedb70ef8a3eafecf4a3b13cf34ca95566156fe8a016b642fd698dc62134fefc2821da43a3ecfc38c8794c9f1f17b94bd0d8957977c815e3503c260236e25a76c6578a76c64ef49fd8069c243f75fcb3336995a84eb65c282c7fb64b4376ce3254d90037b9d21a595836ed9926f47991f2cf6cd1a735aa3bbfb5927fa634d2482cb1737e19b297dd5f2b86c0fd45b2b0ec4a48384c59a17d8b7e5e2406807fdef799ffdc490ce0c8ff01587ed8025dff96f9fdc85ff9cf9d6d2a73121147585e1a096e100c41f2dbf9fee02b747e3d449f347616e167c8116afe1268cff7f945c89f4de59975589e9aba52beed9c91a84e7e57b2b12b7358fe9cb5966599f2029557e6affce30390f3d09dc1ff3e83df4c0f9a868d5d240acbafd2f93d1ef495fdec2c7efe2ba4d6f0cff94d2f92f7c75ff94fe599b788c3b4c5f2caa84efef048cb76fe8b94194093b6f7b9e431b64c784a6badd8de9415ba7e497a0f2ea0ee5c3af7e1e72a0978dfab04acd88aee1eccb9b433266c0b5dadd6e8ca8a5467fc407b6c5999006f3823fb3e2e63a06ece739af36355a8e9b618f8b12da5b5c608f8e9a6af2ebcb1423fa13c78c68389d0d77d4b739e24f6eff7f7ed90756289684ffefbe6fd7ab375490be70965ce23fed45db789a44926bd1ea399210cf9c8be4bf6b52c50d87d7c23b433aa73bfd3a2afa5d662530254e73e475a274276c8da40da249616448442c133e7b14fe8ebfebd7f2d11d5b9af89b85043d13b54e77e57d62cecbee92f92ee4ca5cb2cec3ec6c1064da1fd6d116b6dc795766683c620f4696776e6b1bb0aefd3ee5ba1fb7676df06e121d311faa1a618638c29c5b65e4a5f45df4280f35b4e00933dbc47ad958685550af8ab62fa6f27d0a37a2982bf6ad2ae3f17709eb3d69ffaa7a6499596a70629d0a39c80bf44224d43199b3eaa0a4d3b2d63119a7ea845afbefeabbe5611a1390052d7e951347d30aca26b5feb0eeebdf76a47aeb485d51765a8bfbc5c7940a97c315a8590bbcf4fcd55089f07b8ff9e9a2488fe15b5fd51db9f07c61bf0cdc33ea5f2c508b52d49a944a1c576f202a84dff235721841ef83e7c6a6672150267525104f0bb72b5a3f3bee34a11beefb972954b1ff9b9b0f4a1992c9aebc8fce9f0486a66a1dd56df8fea7bcd8d1c09eca9f4b58cc381a35997519d190ed5f768d32747ab462045aa4ab4870bad8159a0249e511dffda85c630110af3dfd9094000707056ab8da229d027ae79ad0044dbffc3401ccdd6688fc9c670c7d12c100d2c3110479b51429f16cba55da1d0df86a1f666c1d01af7fdad8dc63090b558680f7e77e2896cff20dbcb535f1368162a258d447379e46168459faeb4fdc1ae086fbb929b20c7e15aad614b0faa337ea03db5f418a88ebfdf6e4c0c64bb7ed53b8824776461ce216d7f0c6463972b0185f9bb0a08a886ac83e661fb735290b66327b6bfe56c1646a33afe97e48ed019fe1a05da7a10d1d1be474436f67afd5b7ba431919b43fb549dd4562fe69270b8e468fef257d22b4c83d69b527961caf6af2a7ff01d689faf7d7a900759987fd617d76ae94416e62f13469fa014dbffe4681c6dcb4c01deb41120a5a1b195edfa3b1bf8bdf75e6bafe372c73eb53a6c8c8688a3224ec812450d635ce8908218783012021e888455f4e95e36edf1d50ef0c372e5639edec704cbd50ef0c5177d4c1fb3fe8974230babb446f30f5ac6188496b157fee4cabee97dcc1de087a50ff1c1b7248704b562c4aeef378fdd8881d08d180cf912380ee99785d517c9d40af4f9fe21494d90a4e6f5d35ed99fb2c5099b8a384edbb5f017b562c4f69bbf7ca97af9a2dfbdecda44882c5984545102c315667d116cac0411c0e0440c436a784233ebdbc8517f1442efd8988d5a0f30470efffefbfce3cd10da1fc701ca1c9ef7ee587b99a454c731497b8c34764d2bfb8040eaca61d63e3cafebbaae2cc15fb5cafc3503922205e85cea3e8891cb202088cdca0b479b8a373c17d9f669838a8c6c128d85a645538cecd306cd828aec4ab3493c464d27dbce8068335bc4796a151b767d6bc426d9f52d90c7e84d15da0aaa53dfa9a033ea5331b2ab8c4a911f1c3a798ed8f338aeebb2882384f63eeb2ce2a0f4e53d78bda75427bf77f384cee5e9ef257cff955566bf528fa1161dfe4884b65d27e3a4232b5097368f1f4058f4e0b45d0cd5a91f16a14fbab46b7daf599a8fa8f5cf7a0db0a65856230b734c9e5ae5461f921b9024f88bfe6d8ad20eaab2a22948bbbe8f5a7f767d1b04764b7b840cd4ed95237cefc1077ea407158717f5c3a2ab14767fc4a2c3fb3a39ee3bfc1dc6a53e1990df43e065c916710625e208c41170ffd41f1467d01cc7711c157190aecd71b70605c4116899528c97adbdba35759902b5864b65124bdf6048eacf36f672d51a07c8aeaacb1e54c7be0ee015f9ee94e8fbd47940eaa5876b771f82a077f77a7738b82140bf5fad43e85310ed21c0beaf83f674fb5e1ca6ad83eadc0f9128807e60cb95f8749fde67af40d05fbfd2513ba1ed8b3f8323c4c996f5c51ce46ae6eb9e79912401efd54c798004f8eb4ae03e05eeebb82fc27d5daf8a45031885d6914fd5db9347566067f3a05fbfb3f4a42b9744ed2c073298d607bd82c20c34983f6e121cccec1cd4cf5388142181f9c36221c4ec8250e28528e68f7ba489d995d439f031410f732e7308dd7b791542eede2bb3cef90677cb991e342e599dca76a40f1a2508154fcc1f77e6ce81adb7d61eb47d1b6ecb7007a47de653469290ed1ffa0e8602a464de2aaa932d4c635bc4902e210043c3c5d704559072d7e5285f627061cc17dc851eb9da910b00821e64662ef30c13fadeb05fc91b9c07f5a4c0e6f0dfc7640fd5aeeffd0f073a22c66462dfc7042be991f82fb823ff6a47ce1eb9dae183dab0f9fe0f8ab455bbfe0c131adbafe42d7ba8b6fd4ad27dc9bcb32deb3be72212154511b553eb387e5f513f8ee338a2503fd6afa2c6f151a81f513fce3c6ae6513334e33863c7d3288ee3881a51a811358ee3388ee3ccf8a8efc40f5028d4a350236a44fdf728f14ffda57e44fdcccca3503f33f3333324be911ba87ba64c80bfae8803ff7dbac7711cbf711cbf11358ae3f8e0378ee3387e156f8ce36b8d4291a1bfc6d25f4693347f4999a7e218a26650a03843a3479349447d28d4cfd070292e7f6732d19457e6a550258ff1513f539ee3cf8c8f1a51653ead490cc1cfcb1d57d2b18e323411c484989db4e9a69b7b6cb7f8cb03d2f7f173d596b7b5b15aa4da36539d6e572f04facca70aacdbbeffc565dde2af6a3547f2e05ed53d8fefb9b72eda93df7e0eedf170e61e73b7f4ea1cae149bc89f2be3c680408ad0b75c473b8ebcdf719ce52c0e13fddcfb5f4c7bf27b683b06e0bf6fe566dc594ce22cd155c95fb452778c4d4b40a14db7d2db0a45acd6ae529de16f9f9637b2dfab546b54aae3b5ca766badb5350a73293476c320b6ef2c9a7b4de2d0fbc5dd52859fd670217506c64d10edeeef2b595d9ef9549eba4888fdb03cdd6e2930c6182b5198fd8c77bad01a66db07cb5aa33af6bf2c98babf3e7284ea91b56c935efb7edd02c269dfb22af9abd6eef963d399470290bace8e62a46d9f89a2ad7f1c5f8fafad6ce105e442ee28a4a3fd935513d3417240292dfc656db696d9baead71e1f6f1119a04b9c5462259c1a340f9c1ab47e7d85f80bc85f27708b4d56f4006ffd238f2eedcc0d9a939578c95f5601fac7efc40fc61f7fd4091081d67f5e195ea23d23997f8ce9a030fb5ab5f3cfdca0695e7f55c0f8a9e7c40f523f7e4d7daa74f9cbe2db58b7983d8e5d103d8a2fe8f337dd55d425f6e22fabad26b913a8ce0fd57141efec542dd35af62018fa4591a76fd4632ff8e63cf64792521459f354d46fc3b1920d57967afb998b4e2ab249f03dfec9fd703f608aac91d9bf331ba3660896e76b94658bf758ea183ff527f7c3fd5c0ea943b230fb614d79912c4d79e6fc458f250f7dea4f3d2886a2fd2d377479f6a862767eb02a6dfdfa8790ad7fccb6ce18f8b1c7d7e4696d1bfcf16d38488e37c692f5396c64b63ebdadf5571aa3a6fe2aded09589a6454c68d974ebaf2203f48f0f6ad283ba29d5493d58433d7e8a34e94f912399fd752acf709f3cd04f559abc4143f25ef223f196b376e479cf2bdbe3e57eb6fd1cb6bb8d47d41e1429ac429566823e43d9cccee8e724b492a1bf2c09ce53df522d2a2d7b5c80f6e8784aca70fbf97507b4a2da15232d36ff9c5881508354aa8ae4155b618b50122abd29e811eeb64fa33cb3c7b42739520bb546ada4a8b2d65aeb8180df1f07f6b1e97bf65a204f4f92c0be6eed0c200e102a947eb52de51882a82efa5481aa1ebaab5819a70c51a52255f9bed8c05a6b07b586b5d67daf1d8bb4b5b46708907a68a5beded9d7e8082d8bed613d41ad03199640308416552c4d6187c022ccbefb1281050e400061031320b6f054b083952a0b96ba1d6a67479e0823668531b7232ed82bbc24004a0e5e62f08213a4a86105ee5caa6ae8051d9e2c1989220aeabe840410290a664a0c9200f1825a0c22a8351bf18357d3e758bbe4f06106244c92203f59a4a828b2444634840943dc8a2a743849204815b49f1f213a9cc0004b98b4602483d2134f5c531dff13d01edf388af7e1c18715b4f87005962512502a6ec061c2b0e0852f4ad0ba3861de7befc560f25e72773007b638e10829921003c513d6c52041033d9275e9f204120c487cc028a457b646ea2b099a088e70f192c5051b068917f4e6c08519887260c1881b9e30f283edba585081881c44f0440a6a484fbc804acd882c3d30b1474c81440a343051881c31468a2527e424552a9621424014660610dc9c304f958321c0181f202b78024a0f5d8c88c860de7f555108266e582108a01d98b8edbbf3164327d8b08c60052f68810e6d6d689949299215ba4e6d3090b9f8876c249b12850e45c484b1428a2d208d121153b0c004a0245898d98b74f54a85ab1084cc7d7dff65351117081c80c8aa34e961a9866bfd0b15bc8758b1b4c4ac36e58a0ce6bdf75eaff53a016a0c473e7c8e04687729f52570af6adf9d7d6ffefb34ae126fdfd7f75ff7de7b47ffcafb7ddf579e5eeeeeb554a606edf77a25bdf6da0f686d5fffa2aa99ccb90ccbabdae7ed386bafbfac85e27ce51dee2ba7f1edf4c618637cad8d5bcb54c8b6dbaa90c21427921862cb17211ac5110a5c9a3841c31623b000e2266816ebf52a6a036186285a6c103ae1862d28f562c40fa706dc5e2801111c8c1003c401ac14edd800ad0c5bfa06632e81b643ffb3030d3ff4e082161051f32e46fc10f10408a225aa2c617962011d8a8840ca0f2fb8228942a1658a9a304e7200aaf2821e80b84942051efce0c406179cac20040b9729333e26d8e22643410a6c10c1103d769885d1410829583c51d26244a44684b1220b971f8ea87842ffddb93e38fc70c416472c21a10188c360090e035a403086871110f98901c5c40ed17fae5514b1ed1f207bb6bb1ef63caf3c73e95c79e65b9af64767d9133c0f25c4d82082136cc912843e4104119f2633294c866809c2450bb5b13014a3cff06e55a8bfe83eb530b8620cada8892c0721e6a9b7756217653932f208bd400c182f2dfc5831ed0589d0a7cae5630dfa746d8f5dd379a8d786b86d9d4d8b84c8626321dae70f6d7f978c222c491bcb9697bf0ca7e25633a9495f3b208703a8799f7b77811412a4e005cc120b13c0cda4e6fd338be02f1d392c2c49ff3e9f8a494112102b40412c283161416a4ab42064c99518c0b0a8a2055388c43001c5bcf969a105297468f2c38f69fbd9b4088b987d7e91105bf693b4c7c50650937b6c423599d8072f4929aceea8b2923acc7497bffcfd557382fe4dabdc5064d777bf3168d1329b160911847581072242d460a710822644d0952ff5a48d62504104523a190519120259322d11015710a158e10b320b9508e2099981470065214a1f16a02e04441e14221c10a2acc405bc025257c58516ae0a465c951f166c10a612868520352a494386d8a86899a930cb5a55c38355c283917dee1cf120a4ba40e74d8b78f8c1418f9b16ed70abd9b408ca10e81cc771dcbdb7837befbd9606be1cb7c1e538eebcf7def17a918b736b4b6b039b1639a1c3e6362d7282c9f70328dea0e9d30ea43666df042d59713041cd042b0a412612140099a015daa16682778b2013e4bc800132412eb4a4dbe0c2cd04b3132248443a102199a0c7e466825f68c97a82243713046b0d20b085fe81264296d44008949b099e422757389896ac4d2821726482a717884c50267472d5846949fa83123bd44c5026b46435c10932139c092d5995241999950625904c700c2d596b560899602ab464650285cc046b503084109909aa5cc84c70e595aeae5cd9416682363a644144cd66b5c262aeb0c84c90e595b2ac6c11e2e556443341174e926461b4c2e22c20260b88ccb4544766822fbcd217292b7e4c4b56295194082333c155c5828605529220aae322b4e46a873fc864f541d2ae655d51aa736216767d99799fc60a57aad4cc1fb40a972d376c64d75be299932b2ca6256b0e5f8ab099d588156826f822b4644d12859109de8496c45144a999e07b6d051f1022eb0a4d5821da7162dab20c9d5c2d312da9b1c042c9fc51b7b4406652e7e00cb7357f502a4b36a11f2c20a62da973405d30704a12c6b76e5ec0800103c68ec9c774d2c7f4a93fdc41166eeca77b14deec4a26f7afb53d5420e08d1f9725f8ebbe75772a40ea3c3506de50f3b07e833fe0a3f066f85ff7c5a51786ce93ef83e20719ecec1b38d71d862e82b0b36fc6f9f36e0dcde03e48d2d00cee7fe4983ffb8bfb5c86fee2ca300a18ebfecc55ac6577a5dc7b4b2ac34f7e14decc25138cdf5ab1e37009affc45eb2a454511a16b9982aee3971750316e7264348c1439bab05119cdd4d03ccdb72f71def5f4411c8b31aeb1c1397291acc721570f7638250e8e8a354353238a5263831323c6a364348c940b1bd548e6b43ec6e3903f2c9c2f71721e851d937c9c2bc480c962a6862687c45962b1565b8eb61c09c1207f704a18a3cd622d6168999c9b183847a40b1bd5584363b3586d2cdbd191cd661bc7116944599cdbe1d0460bca6653cdd0dc0e87e088b2363696cd863ab9185330b44cce4d0c7bd4ba49da2b24d78f24ea2debc8e6081bdb4926356a1cd2858daa05b6666a688e8eb07947a7a3a3a3289a7584b2acd588b223cada48f287893dbd8b19aff75244ac2da8a323d50c4d8743d0d3ac1ad651ebc876843ab988719323a361a446bbbaed96928dddd86c472e489b67b16c9946759463dce4c868182914e9c266b3612dbd90d9d8cd11d2d18db57261a31ac972955fd8fe35344f9a2c994191ac9fa929c9676551b16e34ac150ece8fe4e38036eff5bf5abd3fe9c246d5025b353436cfc62663638b62631b6d6cdac666630bda33a8239c6a733343de6c18323937bfd29f8393139224355dab7f12e8cdd58331582cd6d1d1d16ab572e1c285281691c56c0383008424494d5046c8fa927cf14f955230a18e31576fc3b5928dbdcddeb6837fb6b4d8fead9b8dad8e8e8e8e566108829fa741ee380d384c92d47cd6b76e5eb0582bd1c7c61f000482d5dd3de836b76b99a9ce0656b6efe9c475f872f986b44dbf1b47249ddd85582c251bb4e98314487b68fde8cc5974e7865ae07f02d2a7f2a465666aadd6565bed6b3b4a46a3502814cac6549f959a01a4ae3387b6fd412b64dbb408914d1fb320037d3906427972108df68c4f1f1bd9d8ebe93f85f1f4b113e79979fa78c879649e3e16b2b1184f1f27711e9aa78f836cec67f5f4f18cf6b878fa1888f6d83c7d7c84d6a88f4b90b1c832ed8a1a18222ee75d6253a18d44161acb3425288cfe8d4263f9c8c2fc7317fa72faf709e5c94afeb2a99ed63cbd9d31f60f0a6fba3ee787c92987d6107fea0c7ffae2e9bf8bf6e8a70f82d52dc698abf85a8b5f90379f43be78b0cbe5f77d320435113a62040624a31a2dca13d9cf0929a850b3141118ba098bb9d4337fe599d7eb47320788eae418c909ca99e508f197164bb71c19131c742b2173826cff1c207486bf3e87fcb97914b299f32e124865e694301e851d13e773ae1003268bed423070725e646ba9336f9e89119696f1064b8bbf9c65a3346e4d278fdda2f94b8c41fee43c0ad9fcf941e1cdd6a3b063ba3e462be934e3f53fd6abd4bab5c816157400fe2eb26505cdc03f06d902433958f2d7ccc23c2783d93e59b6936dc6ebb358dab1b28c84d89cc7da188de398a2a149a552a9711c6f291c52422da556171677bb5692cd151ba36ccae2e7711c6f291c58e38d1b6f77bc8db7f136de5a49ada4ed401aac116f9844318b386c8c584622908dd1a300491cdafe224d14b2b320d696edcfb2b18e4ed0352f6a4de89175972413f149c85b8665b3c16263749aa1698d2d310e6bddfc75833e45a02531372530ae30db3f1ca2b16c36584c2c1bc8b2792cdb89659b296958b614183ac35f245b62fcd51a2b0af49922128144211ebbf99ab0a686d29e1d957883fbd1a6d60e87fc61b27a14b269423766cc1893c90824e8edfda9a5d4ea425ffe22d068c578b330a52e335e1fa6888e585ac80de43d962c9bbffc4d3ca2a9bee995bcb4b8a048d75010d56962859011189066ec530412813a1148040202daf7a6b524fbf913e88c4f61fb83aad2459be993657341b66c54c7c6e8689f2fd45a6a85a13afe27b2058616341302246b6941b6c4accad68dea38a9e4c5eb198aa6b4c33f6d94b6bfcd910d928dfddb2087f9672140fa6d441d269cc68c1933c6c4791b11c7dd2ec213a9e4c66d8c4620a1c6c6b6cf1ffa009de1ff225f886661415fe46734e4ffbc8ceab4965a624813b6bf8d51add10213c645d9ba7d0a54467eb4c33f7758371b236dec6dcc0239cc7f7614b4fd95aedc18d9942e3217c93420362c46b5274d6856cf50b39f0dea998bd0c5895472f328d245447584a88edfb89ed0a1ed5a425d476892ad8465092324685988ac0c09fd3e5bb7d69285f9e7bc205ff6402f84eabc0a74c60fd9fea788428d173db3b19bf7d74136f6827643498e929c67859694f914a904e84d9c7f4102bd59be8bcc456a8d51a9ce08729e9490bfb20c942755837fa6d9988d915582c6c421f4c8c644200b53729ef1e6afdc85f28c61ce194f43cb343dbb6d7ff1a7d66829d519a2f8b3fd4b3205c5617e2a534443f4d9bad50200bdf9a2964a5ebc3f0ee9527225912e24d2352ad51aae25e88c540d5bb6f3b890a02f7f6f62fb87a392ab8674d918d998eb08850d0972b14061615c42c69bf3b880d05779ceb0f28ab2fdbdf2fc27aa26db9fa63cb56bc9f64f953646b37de62d43b66e644b0cd5f16f29d51a2d2a5a56501d7fb1a574b351a58b46cef4996add5444542787d4451ce60fa6ea0948dbdca0c89b1d836cdd2ccc7f866cc1205f6661fe4f3e9085f9287a3063cb94ada5205a17717f3d4ba28dbcc07917086347cc258c1087a4e68b13a9e4c59724d09b2f1e7cbd78c162b156ab7025ea207fb99eb94075e1648439e4cdbbc8d6e390d4046564b40562d9b466d95836964d8b0dfe992adafe29221b7bf1fea99af3984ca22886e1cc5fba488a88eaf89346f4d9bacdb6ffcb9ce74c116dbf89692d394f7eff9616ce73f6b043fe2922da4335f8bcac41c769f0ad9b17ac950b1b950658032c5e3be395b24cdfe790d47491d4c421a9592ab97992b4441656ff495b53d22a81debcf9903cadd1ae4636589eac5d6fc8cf232bd509c717a4b6b09cd067e51160c1962595e54f6d166f5c8c39aeeb72f66cd89fd0078261286aaa239aa8a594b6e907511d5358c913fd53255532d66a7f51d5156fcc88ee752482b2e9a768682a0d6a1c533435a99a9a9a1135535323a36b6a6a4e3526b1a626043f2f77351c8e61aabfb3ab8f0df4b58d65ce2ec9aea473259c2b71db44467f9fb596033fe32010f27f7fab075ea9bd205962c5e53aee16190579d7714bb4e41c5a6b6fa77372dce725504e5de6eccd4a3a251cb54d58f7239af03a244890d48b2fd749a19de5ecbee56905e430676fbe45b809efc87a411202ecb3afbd996b026710f0777fd6cd592f48bce05af7815e066aedab4475a800957c9e001d3682d8792979e11a761b85572f48a81724a617747d930bbaea705d48ffb543e3bb7bef167a6783393415311131b56062c134d3f54d434c2b9854300931053185c004c404a4eb9b52a831a160536b55623ac1f4a3eb9b4c3081c0f401936c4c0cb6d042cc42d7c7e27505006aad4896aca8820a3161c4293408453003d8b176001a743ddc90bac1b5c18d50a5f5eb3f04527c8962d6e5f73e9a8d8100988194c1124348517c3653335513fc54266af644bc09601fc9b75ba3e59b78ab420000f642d9ac17e344dbeee57ba2e5dbad79b75bbbb5ed8fb21541d21efd8c2c0cc6a62336c233daced067646339a8d167b4fddbf26d719ef34be24b821a61a3a59cb556a9c09d8389d187743fe0b21c7a441d9240ad25e0b8f51e7fe5a67f396ff497ce1d4ba5726df02fe3e55e3eb2fb7b447b38da839f5224a847772cecf5f2c0505cabde0d579f8ec446fe720e5f1217e15a07344e1a353bc3df18f65a31a97298dbaf7e5da4b7a454876ab1b35b3bdb1876bd85c9f00d5e262bffda5080d475cedcb67ff63e99eddecc11f156733943136ff46677e4cde670397343f282b5936dcc5e179b3ea6e1daea828a2635ee987d5bb25495c532d26707ac5d637b0fadd5a0893aac827dc2509b624c56b3e3ae79af1dda2746daee18a904baeada6c86e221aae33ff44501675f1470665190d13d95c3426a60217586e3201b5ebbfa25f9927c421ec333ee3923380817c1426a0dcc0226825798850169cea69390e5be25b7f62de9244668f8852d030cb4062e5267049d846c0c07e193d0761c74827282e23ce78987130f5696047d4b5de91905e12498868de022b5068601cb805fc04177094a562f11eda95705fbac222ee2f9e041a9335c04f3e434d9b68b378eebb89f7d7a573c237f15f9cbdf83526b78513c2954c7fff47cd85e4d89ae7f7a3594cc0ed909377d7a6d47668e621ae81db2ee359c66e07681b3028d53d8a7b7bfd937f36ab3d3cce3b97964a952d8b15df609db1588c66a912254e757d9d26730d9ae8df670d74994997b4731cf66bb419f5e11104286f6e949f1a4983ee0fe942b4d32efc8d3e2d93cafc8798c3ce669f16c9ecd6453e2e26ab344d96f7beeed77e4cb55fee8fa779a4701bddec0f772d6861c89398c2be642f16431d135659065d3bfb62a5bc28736587a484b509defa9f374f466531f19325b58eeaa26555554a2f11089a1e01a898b486c646133aae8fa27a6e11b021dc7e0f5554d6a4c65d3f62b08e3b6a5cb5fd8e9a2eb8b50e8aa7365e000ec4a7b6a056bad55e4a2eb8b4ababe08035d5f7c42d7179dd0351493747dea129b60e2148a4be8fa2292ae4f95d0f5c517e8faa20bc650dc925ad184621235363b2bd1164603a80ac516d8d48848ac42f148d7ef195f84e211babe68442b1459a0eb8b5ac8502ca20c45227042318b2b146dbabe880546481f8c118a43bcf2ded9323458280a018050bc1280500c42462802a1138a5604108a463342b1ca4e2852c920cc606786007464040000b0570c18392e9c92dce9968aa270b26d362d8ac256db9ccb563a42aa2257f7c57befeafe0b7c1fe27b9ce8f3342cecb5b9f7fee3fe2b7d78e5a932fd49039bd43c912bfc22581887cb111c404d1da58f1c16c67de8637ef83e9c58ae76880fbe58ae74e85fedc82fbe09964c2aa0a47b25e05373854b1d3fde873ac2d24735bdffde877b1775740fda48a1b9a7bb4eb1a9941bd2ae44ce735aa10de43ca71db243dc5b27dc5799f350294f7e368731c6bafcd11fbe0db742ce438bbc08b1b9d6e6be9ab2f0b34ff8320f8aa44cbfd6df9536a8fb6a763bc0920ad926c1eefb322513fc61f8b9b4b410aa99bf9ab9726f876c6c553fb4443606becc87a0f5cb941e60b2237c2df31ed00f824fcd4a52e0025c9d715f83b8af32ee2b10f721a9c92a5699bfb8efbdd357998e9f53b9aaa6b0721f7ead4416c63de863822019924243ab0a7ee51e2c2b908571ef830babccc2b85b6aaa735f86eadcffca4ccbce823ed5c11dc01148c8bbbe7f7d1b3a6cccfb579994680f8d8dbfcc2b7460b9b2e5cadaf7f1bd2d77803e44b030fffa2bfaa6157dd3fb983e30057fc7f7e0d3075f16f695187c0bc341340eba4750519dfa3df8960b4b5a7ee46a4799bf925a180d4cdfc2e867b22b772c8cfec91d2701a90b0c2df5f7ea7f5fcd8ff4c82a662a34dd2b1d3ffef56576ddde7f6f6fe8f8f1ca157e1f67dee50ecf86857536fc23b10f6a615f03bef71ee3525b58f795d48235fcb285753f13446baa83df61f86dd27d7ffaee6f4b1ff67df8fb0cd56cd8a766fd1cf7f187e0fed8f17bc0ff962bfa542331a26d71450c4a70c4ec08d347468309b8e4e0a4872949a6cf094360f0a4ca9091103fccb8c1861ba81081030b82ccfa9674b2ba37e15caed0c77ce91104ff29cfe9b3f10df06e27bf07bf2693c99298dd3fe5f9de7bcff36e78aedd7d00962beff3d3fd81ddc7bd47eac816c66131ba87bfb8e7c02ab4ff499336f7d4c55901b9275720d0efdeb775211ec6efed6ccc9115a85e203ffe127c581b033f67ad7776a812fd6e04b05bd2e2ab3fd4c2287e9d55fa553b18632b3a809a3b16861fac46ca5a6467e35a64e3a72ecc39c601638ede5b1f735e8ed674459fe1b6648faf66cb3c3acfb82fbd34768faf66cbb741b8ba0d8f559a85598f51fbe72b664bf04ab3b1bac561f6f3773696cb1d0eb3df9536926805e8b2add9a80e1913f4496fdb5ec0c64687d9fcd6ddfff44a19dbbfd25857dab030fbee24cbdda6b14dec419fb4768e544a10186cfb393c76eda336951244cba626ae432e636ba0b6cffb3dbe9a12a0373bf7c25c1ab374068dd952c7197b0aabffda5566d32a26402fa173ae550cb637588cbff3382f09241d8ec0b67fc993d676acd39dadc50fac2d331295ca350811a0c12a21e9a03195d1b843633b218d690bf3121ce6244ca1e950a96c96cd90730f30b45f3fff59ab65802d593ceae7d1091df61837d6dba9edaaba7cfd15e6e7914bea75dcce96a9419f77741eff543967fa793276e5aeb5df63dc5846ce2173f9c092162d8ec06064081d02e9cb8f911f252c3210c9c18e816d5ad4a505db44822368d00869119d8b4d8bbce8e0440a4850da5d14110d1a444e905d6355855d90084756cea6455d9c246dc18271445d6babb54e29a5d75a3abbf7de7bbdaee3911a3f9b3b30ccb9562948c24a2340eedb99b0c553705c8a47ca765dad3924e1381c816dc98d6f0e20a6638fd12d6b7b8aaa680ce3d2658f5cd9be041e6f90c3264a75ea5f262a8d2762f6b9c1ae5fad68af8b156d372d4a52daa7a62535b1ed17ed9b16252db14feda9a8b671801c09084770beb6ef245d2140120bb44aab362d4a72b2436d8b22b4a94d471152b2509a32a5043ab0fc556d463814514e65a7822f8c42f52a1bded5b0429398977a8522e00a24d8bea3b84e0cc11539a1a5284968af02a36ba8e46cf655490b99221a110000002315002028140a064582b160308b543db20f14000d7a944e7258990a844992c320c818650c3200004200004344668868a300f42ecb87a1b9e974d49cb14ef8cb25854c2b1e57508e47dd4c88e0230ebfcc54b0452e73033b8bbd2f3763a75abc7fd260dc8948d17051c9d37dc0fc4bdba5ef28289662fbd4387ca714ac8397582c3c20dc8a0fb0d1ecc5c78045018f6bee9d4ee3a41e172faa0bf2b1500a2fb93a0e2fc9b4f75b6117d3f0ac954c01248613d7ae28094a51e66e7b5d621e0bdd986bf26a730d2856a6ee1f15ae6b67c002452a94ce7e0e6d349c34f391229fab2ffb22f97e7e726bb52055286c091b93759e6df74a3412bc0c0338bc42177be672763711ba5d08fae5a2323769dd03e9cf42edc4adc84ef22f897bf50a5891510bbf51542660ff7cfddd99b5b259dd6e5aad10e0ed4319dc6c93014e34df39c69c1fe806ae284d9903c358c974d72829ff7a56d4c35a9364da08d02e310482db60812c9edb0eec2133320156535c4c5ff57360cf66aa1362bb26185d4f3275b22747209c872f66b0adeeac9af2988e3864706bcc87bb32598e2199895c6268a5035123af15e0f37dbf4ed7d1c6bda1d1e744d9e569933745ab2d449ed86bf05f20e2166061a81c2dbe0359a4bd5e5db577ff83af49a9ffa7a7ffe6e972f21d40016dc3c5319106c0066a4939057e7060fa75f7d2fc0967cb54b84c36415b4b1fe2f02cf02781c68e146775d73b0ff6c9582ee420d56c6d8922d255b24a7ce52ec5794db65c81534b404d100ab9975848390379b912f05a0433e97f3affb83a8d5bfa18d7bdb40473d11ccde3ec7e269144674c88bacf6b023f757e148adc9a8fbb0e874f6b42cede454568556f2f332d573f0a549fb0a1264acb84cca6aa57cb982ca2696551cf8e17691aae79330cacc9448446c1c8528dcd59df0866d59d9303291c6fa6fe68ef012ac9593ed3c806bb44ad1fc76b5b2065bdfbd38cd7f95c889a0b5209a1d360506c53babf9212cd402d5e7c6ab987429b302db5e87e96b311eba3faad50b9ce12ff77e7bcbe95c05108c99019e0889c28bda09821dc681e1ba01d0a38f609a6710c61be0eaef5ea508887153fb799364032c23b12541665dc355a3c48b73d78023aba8a24ec357977a1977e85be1670ccacceea101e4db316423487bd075e7670fd63a126e56fb16be99109ede5021d0659525970cead93646615ae6f825c0b643a76b055e8745007805224a3d9854349e3a8fb1f51cf4d097a95a85754d665f8752f6ada92d21ff936f56f2a1fe7e09e47994bd2ec81ea31e6aa427aab648b91a0f79cd5438fe7c5b89a35449069b444d3c690c15c5333cb613e57ab77a91c9c553555d3b59f3cc25f8baf643031ead7be9f411b4adf0837efbaceecc00816497f77059338aa192974bcdc4122e0bdd9bbadbd5e649ff2721fa1d743e6de4fc8d440686663cd5916ae6ea27177bdf6545a949fa152f683294c6a8c10ebcd68629139c0ebd785fb0cf619aa5eefd27d6678f1e76fe677949bea46e54c4c8ad9183277cd646c0c51cbe328744a64c045735129211185530f90c87a1091f39efe26ba94549853409a0021918dfb9fb7542b6314bedcf8dd2a461d1d3dad62f5e660758c6db3e94d72a634936de20bda03ebac5d8dc91fd3ec3693339004270e8de11e4ac53356296421c8a7d6b94c7fbcb51138cb35e17630e5a5a503c0133dcad628312644582f240ffb20c07bdb4e9acb23698bc32c803b0dea2c8e102db48d894ce7f9afd28be02bc75b8095dd841d2e2de1aa7f04bc305e55339bfa5da1eb05b3d3d643235487a02992cc08b033464a0a1cf8d1dff9953dd506a0041714f463406564bb74413b4c974ff7ed7cc929e2293c743a5d018cd4604c87ba38e9887e5b01c06cf2a93e3ad038a209572f7779ad4f5935ca7ea0d8f109d1f58fff70ab36dfb6cb5bd247e3212a09581f7b1cd6ae680812e7a69b64f04959668660a11bca4141bb8ea03b445ae63128e72d5c539c82fd87558a00af117b90938ff6244b70d6f45e3892796ea2552f3ddf425ede4a3272a1157389808f31420901e5d569eb4cf7478880dfcffc05d1a032a948a6e8f4df92fe52e89bafb7188ac4678fc748251f70631b521dca5e15fbb77ec9a49f6a45e1f4869cdd7a13e7bbd8faf895a9359a7db259efa415ffc11a0027cbc61167569a7bebfb7e56aa619fecc53679c080e613f0b89f31d9eb4b8960f4f83f81d081d4ec66654f6ac1ee37804cd7e8fa63686d1d099abc22c1d55cabae0263070cd05255467885af2b87117387459986270c1b5900972b938df39fdfd0b0f91a0b934a1e9f2d230947cdb4117b2e933a1b7c30b99ab0231128ca09edc985d1f9cb34eb371adc67cd5022eb862ca0e70038d07fc409aad87376c7312eeb62f4521995cbf3700ed9d2b16bc3d92b412e450ed9eab28759de2cce6b579eece2de6ba5e45d69d32f8c5050644162129f08e7128bc746cc32d52d2e191756891b24c59e16f9ae99bfd8727cb1d30338b88bc336944739e9491d9c0c9476f2b78bd2e136ca5998448973d554e0e7c884210e8862ec1c40da8a288571bff95d8da44fd6bca7d015f277ce8383ebab225b25a785ff417cc6fe2dccd34a938b66b76a663105697184b460ae7bb1b1fb91c3b62c0e8526444563e62ce0826520819cf8b38927151ce6a06727352405ec896947ce23c05f17f07c94e3966f2bcf2e28349f3257766035ef9da53592ec5b4798f9de634961a24eb2ef388d1a6e2e37a9fea2c2538ff1fa3161ca8e92f59d66a067fa75714dc9139dfe97bc1b21ba723c6e76f8070668eed2107aa0c3315537854c84dd6939d8831ada60b9f0e2631c7d7771acb3a92106af0bf8d32bd1c12036dd0df719d5c0407b2dbcdadf5923048bddaaa256d788866fb1360fe43a2ad9fda2d266a24747db3d44a58d15f9bf0857000678e5ef9a83bc96e7c7b3f98c1630c17ddeefed50f72f1cd82f762e797d9d04dfffe73594df84695c3fbfbe4fdd8c6d4534b57e60f31f6e6b80e87cccad3b0037b51daf8740caacaeafbfeb8188d737f97760689c66f1bfdb8ed5d300a946be3203a3971383e8383ff43f70f35d8c08e9f9bb496bf09de3bbc7118bc7c5cbe241f3dc1b8cc0e8919cccd3b8f4c1b209b0e2362bba509839ea76c181d87a6c3c4c8c11fcc1538985b02414499f9215cf8a1ed095b8b41fdafbbffb5c2baadd8bedfef9dfef0959c3497d31c619338d8255b2491e2ec2c953873dcb9a2f786a88a98ca0c30cf43c77e73afec09e0d64b7f28133509b9c134453afcb4fb7b470c3879c98eeca15f8f61811fe3f3b8f23c39fe506b5228f1dbfa8a43dd92b100d833e8b06f7178e14eea936e2435535cc5df35986ae002c2e2a711b6a879eaa77fad177c79233008925a0bde42489d13584bcb5d5c86adf5c28581dcb360fa9298317bc2d9cb38a509a34f5c703175171ec84828ae37a5a81214b10b9cf61581faea70547f2cf2e02d66a1fdcbfeaf84ad7f7c07de76cbfe65bd7d772e26970a52d30e0d17260b14020d35874d5f0973d80da42b1f7c6671dcceb6e83067c35b5b14d99cc3381474f6d3bc14fd8a2a6c54317dacd1c07ec11e38936d9eb34b7a60dad74680bcc27d65a91157b497852380e999990b43c2031cf472ef6dbdb511e1b36bb4f5e8e346723d65f28131b96933e89c5140e2285cad26c66688b4f7ff3c2da71e2824db2f5cc8b97f6697876d4e2ffef7862bc2d7ece05678b97fd61ee178e59bb45e13c49c2fc09dc256f76c4de3dbee4e517c30e2b26ad56b3a687e34ab48cf57a6a2d202448ea117f24e4cb6fdfab0164d9acb3cf407086bbec7d204d68a90a264baee949e22d56f3eec1eeabba389572dd0a36512ef45b95cacfc09d0db1f9f1216667114bfe96fb5722570d0872c977dc4ef75c6b56d991cfcc3e81ca02400ffa475b152f77f73702288457be24fcd2bf65b6d992c5e73c306f6dac8bd651fb79807c796b506726fb751dc4007d817fb9cedb21bec8f8f7f65450f0ffcab366b7e0fe1587697390b1326a1215a9663089b0067f19103e3f0ba40aeec821df9684c5fe8e0acdb0c2baf3cbb0c9a465e3d055a7b404aaae4e4522838e5ad4fea71836ed36c2d52810056b490a45931d3347ae4f639e143a028fd94dca4399a1eda0640227d5b19e05a0d19cbb0df1f909a0212227c12831441ba48a57cce1a8c4c671eff4f68a77221db39f4c6f103ca8c0598829f5ebb47d6a81ed4f37e8b34b1192b6cc137b49e8494e416e1a1695f3845e63a4df96f2b75fe2ea87115f97863a2ff56e7d92b82ec6f3eb9ed08045bc86e240710a99a10981aa509c41d0657dc8bccd5259d04c6bc26f8a4cef2c1dc5b85d9c4a8e341a233519481af168f1a4e01201d317c01543587268ab2be00ce864b3c92c670bd3dc7f8ca87672fb3f6aa5df17404b8f643aa37cd5d3890642dae0d78728bf17a515d94d834bab2f96ea26e8b79121c2c78d4782f573828fdddff4ba255eac28dd49bd84fad0ea891b10aeb47492f7642bbb5648475ad29420e0422447afc1224878ac9af4cb75c93354e827ad9fb3e1c8b9ac3cfe90795a4497c566e5f0d34ebac129c2b8ae2dadc5ae9f866ef9eb1565f63a867e4cccb24ab41da16eea41bc45cf0d9c19da2281f178844782111216dcaf6b7d1c5fe28223ee8152689da00ee1bd0aa3bbf28317dcb72aa1df8f663eafca47ed4e0488b31562d371f367cbaa9f6f4b9e5a6146edd79c1ea38398aeadc3fa66110e63669bd9b334d5ef35acdcd7e5165ca1db226af0e3654ec1d81428540505ab4c65fb18e9678beb427086c33ef1b2aa32df3984942f47383c56e012a710a736f5607d95a8198d89b97d919d478863d3c6126c02ac4d8e864a1169189162801d006634686d459f8d4d6b3b618fac6653a9696d571fc4e75b4422ba5855e46b80d7434ff6cebc6bd11d160c746b8e18791323a7c856880308e0992213e38102a0952203153824d2043613cdf4015c11687613bde5d954f4a826e103590e872880ae9911e1a4c6ca74597d4ccafefd3478d2f6cc5961ed65c27de51a4da47174e9a2fc71e1dbc7a9ab8f60f2364500cfff739e8f4c047ba13513d868f2626ebcad658321b3776d907e99d62349d4c4b117729a6cb9002c30a5024a786c79b6c88b502bb5a259e120c807c3d2154a32e53514282834dac3c6deb8e69dc0768b180ec82a9b5c96064073d0ecb9a3052addf7844de64fefab55be6b122d594eb0cbfd39a6c5a09f78648e5f292de8fc92085f932495ce80249566f0211c0823ecef0d9dcc96582e951d492e7b1f98d789da57fed891707ffe36c61334aa34c6e256cc427e779c5898868ff002d5402e98833e63346c4d332130c9e23f4e4a49daec93ff618bf6f69a544040e94f38c01401312271cc67e7b4e54ad12775ea976266dda58ba286f5b2c45d8ee521ef9f25bf6dda022b29f1383dc5ae4cd41e78b97b4e9becd02b21af02821e11ed75353f2c01d6f24b928aa5081ea80c01bc8d81c4f779150e3a10fdbeb8154ea506e232f9b618b02729de273bb959a40829dbeae9f9cc1fd047854df1864e314c6fcc17309c728afe482fa72b6c8faf98e3bb5f48235413c1a6f10ac0aa0bf1738e4384e27b64790eba21aca6b9b63cf01faea1a6b49492ebb89bce373f08281bdefc6aa05d6ba68ec141e2e54bb149e64c72b1c2da9e0d76f7b965396da4e0702522cb7cc81777fef8aa37b7c5c122936db7a175921b01f103f038c5e87204d2e90d85cc95db21fb17a6a2b49241cf4a4af843f92c579979e2d08fefcd5b09312aaba3a43b162c5b115347dc900a9784506e796b82ff6f78bd122c31a4e24af44c075388274cbe65596bc7d7691d506acdcc14fbc94d7ca045958b031a856054a69a92860db889c6bc0ddbcb143dc977aeb323aca19a389078ae1e4a8b86c8b12f968d3f4ca2b7fd345c47734a106aa8726930511cf78ff1d370be28e24cb6a216230863edcce8f06d2e223642eb11fc97c53a11aedb555c449928adf04d3fa260f0ec72682a2f76aece386d7f912e4d8ed528e1c4e3e35cc9d10178a0bc709c6bdc18bce204cb74e9f921954524f79fb1428393241a92b0373e3284c76d206fa082d9f827d91ca01b78d435298e3f6c49d1115899c897c6b0ecd43c460594519856ca31426814a0cab8f8a119665bf77eb7fabd24e23de8f4c8a85db9483674466fb79eec222a8bea79fff0887434701f514e2efc3e55e4894cd6229d1c89bde7bf04b323486910e59564133911726a1cbce0406f18a3ced5f271c3c60c4261cb03999393b43cea10aebff7788fa8c36bfc76ea5117ec331b0a950d52b0f1eea65cd2a6dbf96881aaf17475a78b25947d7b06435f869935fbbe8475eb1de5f17b59d3d32a08c47628f643a7de3ef6956a35dc72b423ff117458a87fbd9a3d6fdd2b5160d34e2d8f09b418db1a945b198e14f6815a3cb5c92614885bfdbf42c318a040cfec809c5e415885f4fb90a454f7d17606b4500450594f9c0102140154f6ac4bd4b1362d04036cb83e5430d9f550cd73b24154ef37268b66bfaa1e5e3ecd596fd41ddc10c37fa0da587ac2e6323f374203f99ea67336182bb013401e2496edb8432d4023073ca2d6589669585ba3d534cff2e3bd3f3d230c18aed6d47ce688f858984169d28500eb982d2921709743ab2dfc6c8a1f57c4e62c03ca016fae126a4a249425dbc98554615e5b69a0e2d9421f4776ba8f89a248fd0a3145bbef236ca01b414babc83c5b15ff4a8b222d23157e1cc1944b1991d2959db456604909131b32807e5d0fc430d47c817c8edf632ff09870527b34ed0b15c1ce86943945f395453e6d80e4d74afa90b8c47a43e4c6ffe1fe890489f5d15cb712a6ea44b9878d2fa59a029def9441e20ec825ef423e70b6f238fb2cc54300b5a96a0b433cd54ba58bbceb4a1772023b670a9c68782786b9fc23e449f70031aaa5420eefb22c4c1475f1c8863ed8311e0bccb02699f1b1bf9c99766a30602ea012df2815e2826f259c991ec275138821721a197b02b9fe1c350de437b0110ae07afcf5e60859af19ab343236491e2745f6ebe70316a49e40c363a8ba601704d278fb728068a9e104a686e5f084be246b4f1d18108dd3d0b8194f387a5899047f8b12752edaa171d40ad025a0bd2d877589b649c2d0cf3ed7ae2bb0b2f76c01b1367649631b27ac3e24d2f8e0350ac9be85aa43afd1b295492c6b0465cb1a272ce4004b84f71401a09496bc0f4c298339052943eac4cf0a2d2c0e445017f440401d3435d6d2dcf055831b75c8210f3df8e15b610349cb9f70b50dc93bd4c7ead9a9e45169c7fb32080aa71b0124a95a508f53341a078caa6e72f45b0111830508264b26a7438a8a8008a6430394ecea7065f02135d72dccb20a3685fdba9ffa0941cf3a4dd9602ae81b0913de56916b0701f15f6e8a596cbb297a69af46c6a7ede9622b6bde8bea41e16b1ff26b49bf323c93d0833a79b1f80aa6174aca4d062214dabe79b66450ba7501bfd93f419d429743d64b289129511adb26b441368e1d5af71bd9e6e1bc4c36f7ee55ca8b720c1140b5927582d01f885edf2d56ace1b16292dcaee23895074a4e0ca604b85804030547d3d2206bb98c1636d15c539178f444b6ec72a9472a15dbd5d2bd9240103674a9bca03f15fb7d8b91f8971635c325508b7a0d5643228d9384b6eb218f7015a5011d7421d19207ed6b6939f1c2d56140a9ff3e630534c46fae3d5e86905bb1f708d0c3b77df2cb8a241a0ec4897dffec9e985e061a024b74a1c992ec92562ce41d88a1fcc47fd77a03f6957b43e4e3ed271f312c17b332fdea9d9035e33f94373a8957e86121f642e6f2ece31ca5e9bcefb4fda74bc9f155be5be897d0fc8764ae14a25b8e0a85b07e34325dcad4621a9d82102b2f1ca7d9a862954784fc369b01507686953b8e3666fea801efe0f45163852be9839978b7a163e75a1d8262af09aa90b6bee318e1ae7d421bd4762a0a53e3a509dd4b65bb1f8e6fc90c3b2205f03dae16b7596075166de6ea510ab0d81c784556323f95952d3d041e90dab91430504d82bbac5fa96cb933545a9c313e70c346b3c181b49746826bbb11805eecc94c25e957d4ee4e3e55052469e5487638d1ec5ac71de4fb622ad0a6dd8a8e09f1984d21233cd4eac791d68951576e079f6c67f223fd0cafb0eefd96d9409bd925d7901a6a781e6e87ec8332cefd3c68c2dd8d36a4176089fc41d7be836abf3a47c30000fe8088c987f095b0b9e707a51860e2c1d94148357414f4b5463515e28d3b648e58835a0f15a94e027154902b8e022f47cd09a2361106459a6b95a59f77dfb0796139b31c850343ba184f5409ca97a68ec69cb28ed6322251888b1ac6844e443415c9e1cf91ef404d8fab14138a6fe1437f459b5d26be5231b4c1aa41e063a3dad6cf45587707a5d882b579286f0e1d73c984b39248f181160033be3b4099c91e8e449ea926035473d0c4b93728b4b560e86a331d4dad3b7f455708ef5734e9a02ea140dd0bda3351f34bb192c624c7e4a707cb03ac1148a47e6cf15248170514583065ca4d9a40093165dbcc5895ec959e4dfc87861f41acad772ee393a3e61d48e2bf77ddc04ab7cf401241d883fbe7bc8f8d5228e8ed77375ed2984d2ff3f4b3d83df031d045a6c41d36470eeb0e363e2012480d1b18d0e2868d684fde2d1c131e32b86e4812d46fd98637ce40628d3a86024b35304a13e3901dde6943b83850438c3f1a36caa8fffe368c1fd5d3c6d8a3f9626a1fe0858cee37936117c83d7243af4c783988768d770eddee368c7b34cbe07e9cfc9e6aa8b75d87f77884392ef42b140fda01117a0ff2040797561f57e855dc725e47095b5f142bb4f97f56df9f3d0234e667d6e5503f1b8ec7a9fe52ed54c1c1f3f33b65ca2a1d649e9b1fe8b8b3a8403a5293d452c0cb1f9ed7a68ab5a543ff3fc1178f9d6947477f4b14edc994b35a5de02fa516a7ae33deaab8153d1f833d6d4f9f06d0b423386e86e13cbf96f2ed993fea6842e9fb09b546e0b75c79a11af937d49d45cc52fe94ad0b219348329a8bd826f0c1263ff79ac3ddf257d520e311d0a1785c59ffc37001ebdf11e45c2364866919b0efab0d121171b539ceef7f180c59168e216438da94063ea7e8bfe4aba85e9956811509ae4b0094f2fc5da5f865a11c6c7696ac849abe06bd85e77dd80dfe1f3060ea3d3344888ca6b93d67a1b3498ac58392d8ec133459dbcac5535fd7bda1ed3acd133c74f0ca834ba575264760c9d31b2d92439da1cbf9dee68b4f3ca0fcf9c86d0e8025346060bd1e9aa78c575afd1a921eea6a1ea447ffc399e0d1ab34e7def624a8fff28f85497d5c8027bb50e2b2f9a2438a16e2da23bf5278e4416c63f9e45d520d927fb32252bcbe712a1958f6f5e80c08bda11623364b9106e3df607a168f81cd247bc523da940b533403a6223d45ebcb9e0a0324bb43085ff64cba7865161b91c27308855e791352cc6dde4dedb44f915a4ddd996bc079c17f845975333d4386d816f1c8a9738de7df9dafe36d439043b9f19823968f7c76253586222e84fae6f2c94e9599f7f205e48c35b8f425248428721f65693a614e8698c652e4e38e747b5a49d26c8149b6ea4824c01d2b558d67a496bf38d5263cbe6a00c752f08e116276fc8ca555cdf3668a39a2eb488b080c4e4cdc38c9d5d73226a2b87074a20dd29f03423927f5ddba997c335db0525950e26ecca795d35f08543149bedfa8b36dba65adbd31a62f3f7c0c7cee999fc19116433aa4f6648d1c7714bed68bc9c55cc30a800b2e6079f0141eee5ef147612f6d8aef4dccdeb59205cd822446c2de3fc4b039cc0f20be88d566af6ffef9f2431cfc2249a1574006bffb4ae44c6c031f811d016c944e63290d148b05acdd9138de0d0236a49a3674ba29091bff30b49756ce021e2384385e6cb4ac510a17b0ed6bee858da8d22ae31a4a738884ec806522ee03b8015c24940bdddea4cfc1f65f2704f80b33eb5657f2a794d52ecd8385c0af8a0bebd758c7d6a7a65fea3bd799c7954c240616e01763f5eb36ec929a0f2def8aecdbf00a778a5b95997f94a67b63dc8611661ab13e8f8ec0bec3411e7cda53e9852f51c4b5d55169342ab6d2bf441c53510c892b9727622f42115f7f35d491c8bd58b34a4bd5f5b7ad175f3e2e98266f5f2fb60b2bb03c3fe55b47ffdc2174e6309935167f1ec482676ec8ca82309ef9522ce1355f0c890110885b5361b50f535a21d04410118461007bab64aec3871aacc940deb07840ac84943563d5c79b194bf2014fb1397122d7c35c15f2bb95a32bf37b30f90954b22a6ed9aefc2e0ccf08871514cc125931693df6fe94e3258ecf67b650d60bfb205b9b81de9e57e4d357a49709e3cea6647e3612fa627379605f2f171dfafa265a914aa956e9eb1fa33635a3f2cf9d5ab303e955b65dc5921f41d9fa5310f1f4ac1065e8ccf36cfe132220127070754fc983dc0ec9bf043b559d9f838234dfed3e50b122a181182211219460641256ced1a2d50f364efd2515bdf9f3b828ae1fa3b6eabe9ab5fcf2aa56507ff65aa00dd53a17fe03b7e386f6e77ffa567af0c06c83cae2d2edcf68ba74d43fa08fb62d70b711495305f16d6019ce15d39c256fd923bb79b8683e6a1cf02714e90ec535b9dea8221ee6427641c8557b72d137b632c7e85ed8627f121392ecfc9261cb4b5c7f4f95694bcb4cb6d2bc2f2aa01113f15ebbe2aba2e747ca348bac4593be949b75087a90885959d5a4f6e74ebbbf070e5de81e9b23beef76a95555b5f0538be21b4e81e73f8ccb46e2cb619506980a7dbd13cd7d0754d75d6828221785df1cca59fd50f57e9fd4207254d2ea32c92f3adc5d5ec98f4158bef63109b16b215d757efbd07aee6c79c319806d1d4d74a59e6bd0f4c37ad8efa6d37d281033cd783e44a549c4e05c77c9176b04cd051e3c1d69732f7b7850efbe6bbbca44adeeb576710aada370ac4268ac9f12365ac54817b0029e7e13d3f79b963939ef54c98219a0bb2c19ac3596e9c37b08ef6d05795ea51dd49510545dae19747f97d5bfbc04e518a48f5599772162403b7787ab4e4444787c322e411d96da24ebe9fe891b837a9a345233c5bd2b86d8d481fe6ca4d89cf08e87011b2c12eecef7ba703e85f05998b8f5200c95889bc17a1d01de0c1b204de1a3b44f11da17c5e3faf132cfea8070daaa7e28d5d955daa194cc013f3f82fcac53bdc338dc6f950119ff37c90dfa322d7aa4741d8f12f35cf4fa17892a2529c79eacab0cdc282113498f28861713d7f9d03555b59f3aa34d782886476f1310f1695498e8c78c30f0422c6398e06f38502798a9d33c348a5db79628a13331ab41b272cbe030569d38c53b62b32167cd11076bf4ac2324edfd32a27b13e59dc8005926676400dde5f0079a386df8942437cb805e4b6e2e499f07d7b02d0e2d653fbb5008213afb17b0855a31ca036e108f358168149b383dc91b6659cc906d2becec89272ee64f98e2829aaa3b3037f5924ed2c0d14baf4370470c9bfc0f0b77dc2ceeaf3271a2cc8b5a5c66f78f9a34f6d036e5a726a8b57316a3876682e3fb3304f683aca1d57caae6c4a2f5ba5a104681cf1840207ec8f99bfdb8968243bf45f22cc647cea248a642e3c601d6d5167795380421b6094d299e73aa6d794fe3abb93c9ee2b3966874037ccea3bb871f0c205f63381441cfc6fe67af4114c2700833d873b27106f3f8229b953d55a4596062aaadbd3884ebbff2b0b045c6d4d989ffe06f9b5ef6fc932580ed52beb2f6b432cecfbee693dcf777e5ff725b264ccc0dc1b3c67fd4a080e60a5c81bbfff7f32f7ebf31eec90281a855e68e64deef8116bfeb294a13b86a8207fe2ea85985e12fa41c52e5561e65b0996c058fe47bbe94169c445a4f35f71d92021421aebc5d92ea7890e8b9307c7eb86e3ab5929f65c6b675bddde97fc5b15138a69a3d064ca0166970e6dbbcb5733e0b334feadc662bde3ebfaa2b6861735616f9b4185ddacc7907646e883a055732440c6ca21c1473f04b90961c0714f6623084ed74f0885b1cf532a1a35d9d8b9f0220477627ee33bc3ab9af869db2cad4c31cfe0c058cc2753720f0f6f631e3d436e7c4b21deab3a403bc6ed8b1e77f23aae6336df711c076a1f2810b91056223af0d2e5e4edd5e4999fa4f0c1e16a6dcc8f89e7ba1610ca8c21553522aae51802e20a6f52d891d7ab7aa98ec822b5040ce32131f10c9ec540c9c8c42204e5ee0e11b8c00af82de166228c24fffd37506d2d1ed8a35441cb8173a396a8f91abc14d170e1845cffd80ef8ce60f8a1fa4558ab5254086b268e7c3be822fde35f7548f9e7220d5318e70460ee5aef03512626925a8e2427c4cc102e4e53da08082e48d05d2429e0f3c30a3ea971652d473189f3acec89cc554aecc6808f2ddefe88a478f005b9f480899404fc5b937c608c4bd718ea434d3d960423ec1b8b8afbb8f0bf3e1ba18045de1a66a28b78621ea6eaba848c1396d385345653dd2eac0a4152f3a915e2366f4a1bb3680ea823c4d60d38327d421a58b4ed39e5724939faff87176004688012038074c774110e927cf000a6d81ec73afe5a3a23261754a93b3801577e6b658856ca5c2a81d4a35093af7231c4656af68d2928d931fe5017ca541f0c40441c1cc6f70434490b40bf973d9bb9a08f836c5a74784ef162b0c7e8cfd3a07b8fb550a3d1907c64e181b481b4efa007435c17b94aa01a0c4919d259f1873df03988c4fe8b2d1bfeae78efcbe1d4fa215e654b69204cdda5ec651fbb296e752ccdb44428d3898a781baa5a415a371192ea4909969afb0fa45239c37f44e3227f125a5b8303792e8be85e0630e71fa212976dc55efb76d9a59bd64bb3fa5951d6831c9a3f74f754d4765acde01d92d4396514b48ea8a85fb76a49df720351200735002f312c0002234cd505621fa5bc31bad7201ec6135b4ac8e0512420df93173efeeba503f618c8e7113f66a5af5242994b97717e823bf1347716593d5230a71d47b77eb189c76dc7b187087e59e9966d951a71016daa90d2d9ff9d6ccc51aca49b63f0fd06c1e94dd0098b85094791090a26667efa16b1b1a3721676102c8c5aaa756df44c4a87e0626a6a891b68b372c4520337ab254e8974463d9a0554f6883db8614c0847c0baea0efa30622f619eae59381153f2e499f1f4f5a214f57354b969b9c05b3d49c6e3330aea6cc72f20e41c61fa864754459f7480e10901202c935ff37f0dfdf0bbeb71df88190308083aea100ec3b60bb99d0dc3828c8391d77a65db87ad75c4757617ab5cccb28a5dd3aa80f4bfafae5805a0dc63990ca94961c347ae032a81a154204de0c83cc6fbac04217084f507d4cbbf14a6cbac620ce10ffbe456b1cfb5e0bcf67b147a7ccae4ffe3ede4a32545b27db68c97586b6f2b1d11792be9bf0d0e8a65c571d460dd549de588abc25e71bf2f6141efe1fd50236d56d42a41c9fd229291144f74a9fbf24c16cf23c2311d3cfd410a53fd57b742df24e65c01bc72957ab149981e3e0bfacba7bb875cfc3379e63c57d411f2c6e2d7ae61c97a2ce1ebd1d9d041a1621c0b1152def364baae72d2923c6f26ac4c22fd5f7b206b1f58dc195d59183c3c843e537766dd9a62c13b2b44be0fa0053cc95f9759ae797394148d6d87172b4f76556fe6be66ac64069a1d499dc92356a51f4b650726206a57dd00d7e1184064c73214fbe5b4509916259601fad91ea959c167ed20374c5731854ce8cb293618a51f39dab70e8293b4828689b13251b2913a618918956f49c92429df20cd45a59dad62b2c5f1423adf5335b90df970e69c29ad1ffe4276ad69697e242426c2956bb22dee7eae8c5aa2a7fe852429721ded58b6d9f2ddec2346c8f04788e12e4c89e5558de99b9f4d2b5b31bc9e45a3d0f7b0e044a1128bfe5a150e8a49c1f79a5010a29abf48abe13d1bfd8f4629a1db8501f2bf8d351f96fe67146e901fd786529634ee5f4cc712edd20e971111129d7fa8b066a14f813f621abb90d01eb007a1e3de6d6b2f097bc2ec554849009080d0a11711e9a712b5fd361185d8d0ffca08d704207fa1fea7df232300813144edee7e87fa77dfd33f76daf1380ec4dbca043826bb8ef0fcbc80758697179299fb811c77fe0db709617e08e83686ed3db0d1d2ff2d88bc9ed05f55b94db27222cf3987f3ff39d55417a9d397fe75f4e4df4876307d3e47eba009a017923c55fe44665904f194cdafc29f537933cca00f6413b4b8474af78dcc30086a92bbaae17a1105842b10b9535b9e1f170305462999b4d8dd7da9a4d4df4d4ef18ddef8d46a36f10eaddaab53b3be2b1825c05cf05522e6e841684a557b17c89e4466d46a279d2abf6fe7a1df5f3e34e83fdde25382329ded976685d99a974561401a528f9c2df0b66988e73ed1fd8d5ce56122d528105a15078c342ce0d42dc499d7df4978d28210ee86c0ff28288611598bb3aa4989a618201347969f46900190ea853fdd6a42cd49ddc8fa507ccd3c0475f6451a7f2fa639289b84f4b5886e07c3551c9d583003f2a81fdfbdb674132fa5c05145823280f7c2d2122a115f9956ac2d8097870460a333f94936a76edefae89a2889728a55c6f07c62ef20076db231a0fcabf8c46116b0ed5f7a440e353bc3e23f5f6f1c6efdfafd22da2e72a32cfdb1245cb49820e5b79be69c786a0d0605fe9ca082268a6c7a6cbcb55ee1910105be4cebbd23b577fbe031a87ac02d9c5ca2ab046a5a772c275021324bde0bddec254f58644bd06c059f99f65baa93825fc8b26373fcd459c162f2888ebcea1718b1ab3e8788a1b53d4388b8c59e418c58d57dcb8458d59743cc5f598ac08cc27d8553be60ebb3a32be62e315754c91e317335691711539a688638b195fb1f18a3aa6c8f18bb13169913b51067d2eac71fde14abd98829297f11d826a6a7ca37637ba86cbc6604c518f38f35124e203ef0fc95635b0092ef767fb1367efd4680630d9346c57504241a6ca70cc55beae8ab81ffc96bf5eb9a56262931399826650f106dcb968ddd07dc558684bd01fa4360145a25061b3c364a40e30a4e5e93a0f8f407f8f3ab99be1c2e5a3a18704c01a6d2a11b2fab962677e9395db2274e61e79ab4c8624d48e5552dea524846a3c7208a2ec297db21dd1e7b7131c9841d59c556e5bd7a092fc3701165a9b3ec7c9ac89a8d23a019d8a9a4a6d82dbed05d04be3516ac0ed9819bfa7c022899615955fa0edbacdddd7f8000c7617c19254d7d0c1c304b42a262798b4e4d80ffca960796003811be556beda4050ea16952f62297aac401aa446650ed04a54cd790a46105b742d9034df09d175e3be2ecb7ada87e9232a813b55594f3df46fc770d758d2378e47eb279dd8895ab07068dc832dc9ccb78a52534a57118f3fabdd9c62702d153807e84d8fad3969a5bfe1069a2237c2c34129e3e4a455b73c39e6ebc10022b3263aef34d3bf2efe9096620ab7323cdc11875e4458becb72b0f8d2ba019b12a174bfa86eadeb6e47bd2edbe1ed0b31e796a355e93db20db984a78abcc7c2880b6209848ea2e0fae726870d96285cdb6ef06dc7246f50273804f6ba55b525f8014a3ad88fc35b1d088aacb7da4bb658af2882786be26c1ac2d62d659a634c3b5c665125bbf578568d2fa0136810ef572c46ca75430aec9ba7d781e0831690cd344a704d74466cd4120a526497cf20a33bf72162eb7091cce717662606449b119253ac5456433017dd9ef0339af4a38a5f3dd2fbb5bf4567002c5931952246ea3c7180a9e4eabc82906161a5b358e95c04e2a25fefbf95021faeb4501e68aa07e2b467f30c75556a2b3b7b6d63c716ba6cfa4a920464e844cfd8e48b89846816df35119ba4cd51a8335fca0094146423b8f17d423dcd036126ce920ce78e5617a952008dbe05a6bb2ec1626d43025eddf9475d533814f7a7929232f901e58b7db0ba496884676d815ad3c63038f2fd21453837fe106dbec07a25e6622110a76edca8961bcce147d2d6997a34435ef86835514120619a4169762a0a6bac002d65c34d18257933e6d52897fb5688a21fc1b648b4e11d18230829cd16bfa69ea89e52685daa04c813d6e264a2ded3119d2432ab65e3c3b6b34384017e5d6f860cd93cfcdd954bc33b5685d269c0fde8e1382a07976c7be54fe62250a306badde7ce230b5be9fce9ae70df472fdf5539a93c7337793f3f4e365234687a3bfe36f88abb14a7fc5b6144b6b6f11b675a2ceba4c1a63ecdfa65abf36083123858a63c9f98113f8108eee47000203d8d1ef19f3f61acbcdb0347fdba9fb362291ebf0cadba9a13e5893720a018b432472e572a5052a3f606e4db1ee72b05b2c2c5216161f4d87f4ce73826f207a65267a2541275e126d3bb259fff86c64bb3740cdfdeffd2f729a4b91b9b70e082595721f8ffe25a690f52ba395f75d6aae67ce6c2a1632125634ee4fd524a08a7401817fad648692af9c50d963d82268c88683898023131efb34c8c17854c6bdc350d6eb98f538804075bd0ee98b953e65a618052acb813e654594c974e80cf55d23e95428e4a9243b9669a4f798ff13f9562e175a3c84af90b213ecff8160eba1679473d536738763cdcfe3cf53ce8f3df6b7514a6924566a0d2876309bb081e0ae26d077fdeaeb4af428e45995115eeaf274a9d123134a570c503d447cd65588f8aa87d525a063310d7023adc0a48404ac90725799034f1bb9cae6ba562b9597455f85f8ead060ef9636522b959afec2e5bb49468a63abd5fe7a786b61d9294edcfad97b4472d0c91e81086920af6f850e7aad959783cfca847d6234a04c9fbc50f3a038e64f759ed08294a7766cf11a7292310a567a995fea7641299892c464c2118ebf85c8cef3ad76845ac3cc048f06d3b4dca633212969732ab39a9062842184390bb51dc42717ac2e125eadbd0694407dd2cf01c37c10014c8fb78d15224994fc7170c49bee461c5db39cf051594297e7712f41b3febce7bef60c461b004e1e177278b10537471ddd6244b1477a1aa7d6d98fa822508da576f7b29465220a5b8d90f4a9e59aef28ee1ea505d2adc806e62c9384de4002c8992f024727347e48570e96599c4401525c02f8c55da93dbd2fee6f7f54e4feef5f709f600cce393449a98f7da866e3b23bb51f0f0b0625141713c45f13c003545ac7a9cadc6e85ac3e3d18700eafcf97c76851030661f47a8618a74a877ebc854b128f5b6de6b5ebb359f1aac18706701acb61d67f74b25d73fae0ff8f0cdd69c8cc0038b25f4154881a91bc644581321fa8adb1fb6b1e0aa85fd95a0c4a320763798ae9e62e0523c5fb8f506ef4389f7ad79f2ff15ca8d999b40d572fa1464a2127852bf7c1c3d11acf0d9002a8140f3434a5fba963772f22da8f992059b37a8ab8f0ab76bd88fcfcaa892954d23e74274f5c4c3135f3c2b8bb5e30f43f34439b8096af1d1920e75bc031f0de651a779d134eb2d1ace50a24685cfa7be9a3ae914530d361972b4739c67c91a818d4fa6dd05eccf5407a1ce98929fd7356c5266d2d799d9b76589c04ca6657558fd17d4e67f09121a86005a795ddd54afb0ac4790d1e4c7e2d78d80327e7f75ec347a2774beab834093584d703c395ec109fe5c89213a7a98abd679a4284bd2e357dc94c71105290a1cbd9703da114853dd5f9a06ce53e264bddf437895b12c72bcb92c58289b3818a634462d0b60a71fa34e755607c36cdf095791c76ef828913d02bd90a856e88e3de694e2530c28dd049d34908ac371245a0472eec29087edc958b2b62bc6642021a893dbb936ead8ebad202915167877c42939fbfeeb506c10494544d47150ed8cc8d6a2f98a5946d7995446182e45e5ba6cfee642bc1b353fa591d4c9fb52d337473d55bec487854c6db1a0b2a15c607f585ae480cee1e227b18a607cc89814a73ae09bfbd5111ebd026fc82e20810aea60c8c49bc44ff33b3fa374b6d180075219a4981a8cd50aa7159dd4cf1cb86cd3dd4596e22605bf03806e199800a9d9a218d40b376951c7660d5be062fb49420e9f4d8407e2fd02b92507dfafa92b372c8db35f846f70089eb9b84d9305bf67dd7ddf729dc2c373b1c8fc1c9f12ee2e4f1cdc3463c12c9c91951fb104e3b302c2efe3a3915b19bf7e9d5cc1f96cc20102e1e433643889f85725ee8578c261b28eeb00a5408a49692993aad20e557a98129e6822943862bc8ff4c207b9803e8be2aad8a3db4c9b5651cb63e0ed0d70cb5ba9076f290c3c4b5b6834cec5f35804532bc0fcbdea1b049fc369df50c8382d62c729aa65081d26aa861d576a122301af97bc02c852b18708e84f8b22fbf00203804e4e765849e366bf649b0305da80277fa0a36457d8215eb53210e23e6454c2e73085edc433df041460b106cc645a03fe97686d87f37761e784f71b3e0e812525e6580823e7487e0375886f368ae8b62b2085ab353fedb0a99e6d971449fb9a94007d58a794482a8c9469db9e50158383d37bfc1314d68a2d293436f58adefd8e98f43c317105d594928197f31a8186d98e77c4c80aa261a5b814c7f88822a4fcef4a10d20fcaa54779de2ff9d601111bb671b057c422fcec089b2537c5ab5555dd6dc7d02cd3b4a26135449519c0d73e80c4e96fd922073824046027e2446ecb06d582cf4127c178434d498ee08d87e8315257f68f9953d3b6b1755cd011daeda32fa09f5f3f2f3f08ab7a2c4ff023ccfbad54598ba015cd2283822f2da66fdfee73808c3923a42172da90750603033924d1fbc9e0711a59e5f4e9a3cb192dbb390fa28989b6b6fd7e7cfb168860a462d742cd5ed2f24124a7b98ec350f1f7fec708d12f09bf12083f4295834713a34b1c893c7df1601222f9c2ffd96bacf1ea1a2022513e7f5418d96b85ec8a3081921f9b2080070f89f9c2a92651f4f095ea2ae1c5111dbbf46dad61b6ca89277b14e4ac0eac068168e8e0691f4fc702fd8ffe0e3fa2b86bd876adc4ae15182fa069bbc73dc763df8e4027aae8462c1f560a8d6a0e5910224c555e57373fa36956c7a82f09aa777bbf60d35107bcccabc6446460a2908762c2487de332191958621cb2226689c5f41078aa2a603cf189f392786b873022677084c40ca48da4cd3250436fa7e5d24aae2ea8278e201d12b379ba4ac6abb1594b3d039fbaadf4f1b10c08d7cbde89e5aaacd3601eb711961944459482835c90415263b4750c86afe7ac1bed3004bf082cb99478ba0914f2808ea42fa46e27f7b4b9d08f9b930b4801fd57aa2c251902bb273be286536ad028a270e384e36bfe4c8f806c019212caae14f0c195f0a7267cb6b76cb420d775bd21beef6c873eb830ab342c4bcfb4abac108ac4c65e7c2f86fc388cd0f7203bbce450daae73b8354450725d76e74840cf9c61b24799ca8493c969e95fce987c5165bf036f4b9748973a86a948fa8833af699f999eec15d58b6a552828fac0449c8020cf0e521174e423936d1843fe2123f65b999b16601557314ff805e0c8885dbee7a4b57f807f16867a3d3e379a7c6e3422e1adae831868c155c17a5e54bd2ef9fcfd5129a4dc00281e477db15361f28bbb5965fb7163d124b093053d597eb2802669353d03b4dee4001d0f7ece20d22d3820020cfc6c45f2d9add8702e4cbba0681d6ae56d51d70c007207bf490d81182b347e3c6a773ce0b3bd00565762ff5212b0c10ba85297da7b2edfc656baaafbe642bc401079ebf0668c93d7bc99f75ffac638250dfbd84a34e0cbb8f6b7e33627d1ca97a88e3285f36370f73078a669a17e5ea59099e730863db927e643ee9d50da191b37e50adcb89e98d565f8a1bc2ea765df33ab8b688ea8e2670e81b39e334a7c192e8afb13bf1f7abcf34127717a49aaae783c1559aabbbc0eeaf789fa6248a449f97d19477cf15713dbd2002b17122996c7f256c8e5b10e8c390eab5e6317f385df9f8d769862b57fbe86881a04f262991e55dea74f816c4389f8ef24dddee919590e75e44e149d72639270ff1a276150b132c64ed1caad01e01234a3a7b9c58255ff9eab3861576f3ae74717c0260046a64ad9ef4e7494b7422198ff717111633f17ff092c31cf334e0af277d652127de3ad79c0a1b09788479e8f67242cb668c5d307a4a575b4f5ea8e86cbab4aa6fd792e12c8ddb8867e99437c5adb412af042db92a25a5db1fb5582f09d09e9d7adbe9d32549508c7ea34187cced877d09ed633a1395e349d7d8d999c5c36ede274786e0e84bbeddb29517e0c967551b84bcfc124ee345c4864599453f8681559157965b416cb4520c2b7e61cf93d2095386d2ad4356c41ee6348d7e23510c7d1dde8368d934dd2e8bb888b88cc43477399b089a5bd9ee6d6a6ac449d925cd8cdbd3fbbb8b30a70d764d6517b336b6c7ddea2d643c46e996f9cfd0e433b9f9a34fa41c20f30b284b6369abf7ba4722c7b908f62618c98f2052707540c4356181f88c2a2ad02f4c66b163fa42c97e5a1e983561c3985a3828c4dea85428184714c4a8f5aaa1a91f44a2ce314e9553e52075c0a5dc5348a19a1485a46b0af5cfba655a0e7ea27d0cf7d07a66a2db2a89679ff482bbc94592536200002a4d18d31ac667df28e13dd3a9680cd493ea30d77192121029bce89da990d004b8ef81bc61b283dd6371cd38fd3140696ec8256802d0ab284a319cad6967090a960ff2428be912d4c23bf7479c48960f86f8654681034f6ba2694388144ddcdde00ea704f6f3deac6a4362fa00a95a050d0f28348879913a0d48d885651c5c09692a9a43f73c0dca4c6c76b341ffd047007c73348c289cf393701e6526658c2642e0c4b6014ee66262973001eeef831d1edcc1f5c9d1b953cfd9f3195a45b907c1211574b33764eb80396ee212f953b5c90b47bb34d06b4d7ce65950870afdd362d9cc09de8cbedbabd76e370aa23169b2455a692c21bb298c1f79194a62bf514cb841495c7e439a06e18046c621ade218c1d31210c5a8f3af59cc2ed54aeb5b4b563ba66b6e7f6f1d4129b77664d22b1947229d14747ed9d336ba29abea335449259f569a75a554e0fe28532450f913fe68b706372e51a32339d36f72c8629de009ae80dfa9f921c3c8430ab2a774e2fa15ccae230ec0327bc0002bb2f270b0f5ecf9808958b00edd98c4a8bd88216f1514a60a8818d3810dc3db5f0044cacaa9d32c9b08e397fc65f562ce61c466c072aed5d1e5446afaf50284b2f899f8fa035cc8cbf4771d2ab91ef5a84f24986b6630e6e2248eb1fe20cb5e2acda7398b145e8a9e5e15db587fbbdf2c024389d8dfbcc72cd35be6aba2e4780e63c8e9439a0d1b4a499008fb849f336fce82a0488212e7b6b67c3af892354e7a46b3b937aa57990c1864f457136e4c3739b36823f18e1c014bcfb567c175e0f7a26d4940eed313ba741fd6f80233bee5b96f413834638ef4f09e79b8a15e3066793029707a6337ab69506dcb0fe7cdc241841aae8a398bd6f43a4e3eb39c61da1b3be63c2d77d008d2516eebfcd252eae3a3ba2c29ff1728b3dc54a9310c90856cb73bf9a4c92dd0fc2e7d0f9b5ef7acc718692c8ba5ff0cc6fc9660d33ba944bd05a57bb511b5e140302442f002e561ea16b2a169189d6aa188d54c7f1301c37c0731f6074a433025ba67793fd93ba82b82b95fb377d7e30681275045f8201ee343f048808248fbb5bde8c1d7afef0cbc5704d2d43a9e8be008f07b1139537dd0aba3e5da2aed81a9819194a8bc0875504c7d650d54e8b3ebc7acb8ac314aa2cfbb3624a71b651a3d6e9c67957dbc45fd691d35f81d96d5b2b89705fc6b325f12eee25e089e24cf246a8e6225618e05b5d1580f04b01f2e8358ae13d3317da2f5e9b8e8c01d26cadf7aad2503258d55e3b4dd312ae880b0418e425357d3217c88dfd35892e468fe37be1099b8fd7e98ef33726fe2a1bbbf259a0a703a2ec52b88f01391f3eb0da00c97ecc5b83b32a2b8211b41a113057a668a8d828a925502f5bf265ef06704ffaeb28043933e9454dd91e36a93226d997c823bec574211312d2cfac366aa0b1557f8a09d6e1d0ed894d5fda054b6e02b8581da38f9da967554bb1da7ca455bcb109768bf17c38cd4d104dbabd22498ff3e28e9a63e11e9cf165e56b4728a5bed01db25f1e6d467372e3881ba30567553d3d94dafa9271bc087d37441855f816a9679cab94375f703a773786f2344131d725162c84737123568957bf494911c77dcce2c024e550cde2b0cc9e64dc6d452f069cb03912f3ee62855e01a0bcf41bcbdae382a19d4f45a5ed2e32beda719fab0389d0ed7d0399f881933a903944aec24a387891f47038ca7f6d2d9ffcfbc68fa49042034424f64a81dd1b6b6d11468573e669312055938bb259dbe9516bb4391f4ec5842218aaba7af02159dab125849d6081b1664635a26cb858847002a7513e1ae9e111673092834bcbfd1d0fc025a58886e46dd7ef6816994f02de4cba6d8965a0516516f1b7fc84aac7af8f38a6ef5ada27b8c92181ac5014c71c75bbbead6056c5a5b550b70a6fcc87c7732f4126e35c619ab1925165506b3dd677ba7ba34126876b19e8816a79d5c1dada7dc8ecff50d9fc8f1e01710ca81b8f5764532221f9549326a9aec9d865839643cac108338efa853f4f36beb6e5c776ce640b7710e9e862123d023eba5f18bc2b4c7a93731b2df3a5e94a818846cd1f8110be924288bbfc885d0a9d759fb71bed4c1b22b6cddacb23c5417eb55a9017c36c7c5bb40b535ae5a77e12e8e0509bfe7d5f4c1cad6ab3ccdb61f70584a61e3499f2343df66e34dcb747446136a0db262ef39d2ccebccc09520bfebda35beeb0bb0f6b1e48f37ba59053e310a277912587e50b89f7aab7ea780ae97d65cd69f884e49daa9a631c85f82ea2cb6d7c42f01dd2cc713e42f45ed2e5b63c21f96eade4d87884e8fd64c971e50b19bfdf56857210ad8e5f6b03f61f301c9822a2797bcf1196e54aa8d7aa09f4b326af121fab097ca7f5af4670d747b5d3c4bd8a54046abf9536dee8410cb193ad0e03b7d79fee8a48ed2e18649b03bab6ce2dbd6195db119ecf8aed3decfa746c6e1287bbea0bc4ab8537a838af4bf3f9bc6450487578bd8801fbb7192083f28b767b09ab53812f03cb19c9f7b77a128d03abad126b30d0024324ed58f698b8d2be2c052d98e1c75d9088f662b5787773155df7892b923a13a2db6ab821567371fa176f6d0df790bc240cbf5d36d9e1aa49d101d24f5e550527bb929898a807ca29ec16dd87f47bfa9a0d4df48485c1fb95f24540331ab20169349506151e0ec92f25cb92211ab3b6e2b5a7580711d442b2ec9eee4bf6ca3dc6a92616c01ef65d24f1e4b81da207e6f4a4e98f21d7755378d0b2e261f6f502501fecb4c866418a0f6a93404a16453ba537bfe6830576a6898d55d4c0868f5a0c7874086977613e72007a15bdf50770ae1301a27a65bda999f5ed16a8d484903f7677dbf458347e61b672a808c6a319c7dd415e979e6e7c3351de50cffc4aeb9aaad5d0a455214d221f297b8a077138a13b41472b5ad7d9c1b4d2adb76a7de626e5d3c1b712fb62ba29e12ef39ec7786dae7607c7b82201233c85ce50bf722bc81f998a7edff69c4a6cbfd62ca9d03e3f53258a39a3f9900e1abcdc0d2392b4e4142703e265ef72d34ddb16f0dceae0bbc9380e0bcc505efa8cd5f5701e02da7b81454fcd7f2bd6b4b5839a460e66c49db5c73c89df802485a1c6f6008855e216c4b4a00107fcc81631642bd1eca268a011b691bc93779fa9f43f55b03c8952cb0a5bd3e8e8e32dec86570810eb31489355c977e4bc9e53e5f3f3408c0b8b0fe2dd5034aed83eaa51dc665c7a4fc2180b0e01972aa928e0e9927cb1a109cbba9a0211cd32e4390e2dc6f2e10034a951c3e0bb33ba6ba57b9ddf61326bbf4d24eccc6a571052900dbb3422972bee15dc2a44b1b3c45097dc770226661f467190dc9a71c378d82f57c80c35a528b3979bc65cd64ddeefcff48774e2d5651c0badac0524ec1b84ed8eac4aeb9a604516e448baf05745420bccccb273383722993e7b42c00bc017da0550346cdbe0432a827030b5335a1cb442bdc5fc3b5b01fed597e648e3e988b771dee1c774d24f7168b4ff4872bf2764171b16a27237aa7f2bb870d91b7639d2e61af5557775f1065b4700e7a5c79d8ed312bd88b2a23d148b9eb0dd707b8cff67cc9656a635145ef8ef7acc4aa93c5a9ba9ebee48ab1330f310ca637e135f28f2abbebb6800b87694e03f9f63e800dddc78996d06747a931cc4cdce706ec046b54a64009318ee425a0e6b8b71cfb046477e0553ceb52e72f4f936a3ea436b4db0a220f040dee61b7d1d9a33ce2a9654c2f9d1390fda6d8c02f6948bdaeba60e7270bde1a0033920c93d50bbf6c1846651076256dd796950a9b00443c84feaa3acb03a22953134f59f9d552dab1cff2760430371f461ed801553768ed4d843cefd31e8d134d264846673211958af649e6b2b4ce90e28bffc9e9e638430cc302fdc0d75b28773c724a69a0b2da3c1033bfb5b1a05ae58483b198e250ce5b38376cbeba4c5e2babfae18c2fe7ee658429b4363a3c423bc287eb269830294c45c948100e8037e2ba0541f56ffd6eb9281ae71e4ff2533efb4518987556904bc51659c8ae838a4c35b6aa3a929793e5955b68880f510ab2815f44b5856c3a4765e131de5f28091afcf7e2f5b530df58691f58b64e95bf1f126bfc23116758ea6ac3cff8c47ea383a5221c8444a341cc961649f79581e1325cf5621a56376584115a2529caa0b6ce1d8065b47105185ce7a0865f9a592bf2e19d8fe68ee7a237698522690fe60e22a052e6d8d3d8a7e6d197c3ffd345befd237f80bd283d8ec1c0e4eff25b5df7a42833827c397d547958b4631b6dae45751f5e0bdf61cae4e837d5e4285c279fe5cd7a90e67831cd1b370cd1ef2aa9e5630bab20a14ccfd09478f31ffa0a7d9639467f20b3eb43dde55d06666001b9b0dc965a9a5181e43e47f8fd70a9dfa0925a588970ab40098e0e7bb0780a131152298236eef86c1e0bc75c4287d820d10041bb48b4b6ab81d83a184816fccd73241e41dd795e9855d85fc364e73aa624c33d74bfa23c00b8e12bd53eaae38fe73c2f3445780a5e4c4c49e04abf68b728e00e7445ebbb39f9b4b5760f30580adbc571b59ffd2b3e8261563c2d7c361a33842ee631108bcf51b2a0eeb13bab23a47589238e1ca50602442e5a6adfa86b0f9406932ba481743f0144769fa23d3e7b6a2f87e3c53ed89227957e700ab3bcd2f9ebb4768198ac86d5a2bb9e4bfdc51527e0fca9c7617d6899394b9f523b706ed783470b46d78403c2a2a2301615f62ee8127fa5e8612e5b5e8f20456346fd397aec0a2d0698b37a0f5ed66458375cad7051ff286c8bca0e76949af061298c5567421be9f72ec6343027e5fb5d9487304feea495247d6a765614f927decf396ceef6fa65ae14f387ac35b3b76202aeed64d740cbad1432a7c8e09af3642ee74a3f3db715aee3eaeed5a45d32faa0e5ba0190da1a6d010da7582e6bf348ad292bc91446b4d35d4b5a6be5b7961655250ec20c819c04189aa6f6a1484e2772a1544b379ba70447425fcc4f3aed8c9ccad82d2c8c9e5f0e64310cb27641af09948db08d6b896d3afa3f4d696ef240e11d6b5307bc939ee6def516b2039709d34e6a233df09517da7e90f6ce26a7dea7a965aa822a3ae3f617a0cb10e85401604a95c94fb3dcc939ad32b9b337a1e4807aa95cd88da76e4a2a978bac2b8eee4e8149c18438b0c9786af1bb7b845d09678781391b70aa220507e904871ff809c1a1597a05f3d9c461c815409cc914618e4bfa18a1c378f6491848f8b79d1415294e5d6a7b851b1da32db08ca543c6069112b79c3e7397c6a9a2ec06ff42c4c169502aa3d2b20c490cad59609001f737a34519e878160577e37da0090d9285ced4d1931d3bb7d8af3ef2bfc40fe8cacd523b5f5849022975f07ec60cbdf42e7de21d3663af3f804e6be7ef83ae0ca052554f74a0fc2665b4f22c894b56b1387e6a2ef8ebc6efa1dad1aeb603a29a5098983dbd78b7b2124c79121ed47348b485bb3d0a64bfbbf3399aa4705709e65bd6d9fe0385b02c33c7aaeae8b7c2d109ba137271a02b1502d214d7d4f86144c22b128d62558243e50bec24995ed937e61c696a82d85c7e2759a0c596a5c9adfa4aad00014c97ca6583de2739aa273ec2399608bd94c4fdf2f0cebc88d709ca3541284548cd69f8e384f6d550121653757e1029f5f41175f538716504c1a75ce45b842c2b15e87ad098e43a7f8c142c8533824eabd89d3ef4abd8f227336b06313316eeacc7453cae3a8165ddf1e3a7439ed84ea068a87355b379362f254c28d927d8ec39b49ba4315707cd9ef27f75c8dc18c3ac04244f3d9d90544516f46b3b3553e10dbfeaa0b075d88e741da98903719820cc43966b332bb314ab5714f52ca241d1f47aaf1e22a5b3a334f1725366a304ae0ef178052dfadbcc162e0be21c055bc3ae7cee0faca39fa4e89feb8db9ac9eba7ef6d4c04160aaa596c36b3d88bac02cff3012deb659eecba17501deaa9fc85c5db87d797c718f7413b5cc6f8dadc76bc84a96998d955bb2128e8e921ccb6a550fe3cbdbe6cdf7ab292f887bf4a40f34e9851d4a3cce806ea3db1c4af783858b58cc41263573c43979f9e61714091c3d0ed8de090d57ec50ed5674461ff60de5f129ffab062e91ad518e614e0c8b2a9edb3169e26d1afff878ec8d62b6ec8405d844b80dd8703b88b83ec03ef488abe2540d3c0b0631a2fce4c24695a1ce489be00e5f9f4251380565f0fb5a7c0e8ad7c40802c8e072352ee4e905ad3dbce2e0fc66217c82ba1fd4ce016ed43a1788bf9a233f250e3dd66781ec77e0363fd918f163e239b9cb1db449a74aea5757185a74819045b94a8166cf74eb1d9d48c2560a56e3a09a917840e403385d432b22c8f03c9f8f8e771932ae27d44737427f2d81a63676e9170f2f3c1741bc79a71b7fac41015a90c08f138d5d9e7d0ad04195ffa86f7e58f64fe8c42c19ac2efdce29a186d4b541043175c9a8085a7cb8da530bbfddb4c6a1d8cb59012930531ebf5b4e9171740d9c368d8f4d3f8659c93969e9669a5b2076fe4a7652ae5e74dc2995359ecacee554aec7fa5a5df3f79af93e1b89a64a67dbdd48d7c48bcd8416acec0cd1da1c237a1382527c4fec5bce82da5680628f6f91dc22bc7d1d196655dd53e2640c0f6e027430a5ce33ffcee969dfd2ab30d1780cefa2708c7515225e2204c11078e7da71d39b1cd030e1a93b4e80eb4ed2e0c1b95f4f41fa23adfd50b9f802c037b9f228399d787ab78c24366d9cea9e6a20a79a64f7da8c41038beb081b67b1f80c30dc8b34a6d9f52618922a5d1da9208f63307c293f58ac061da59256ee7766b33a992d8b3bbd693740598a34ec8f524ce0af271ecd6607797365c4a2bd8dca7634ebb95bea6b2c7f8398f65fb92dcbf9548c64fd541953a07ccd21429aef7536a5afadb16f54b3834e13cf6bdfda91a7e903d26aec0ffd5b7b18ef483676117a16e7916fc26f41ac26e1eda30d304790897a8bd90d7727e3644d2687ff63b1d38897c20d3558242e2bc5c0d9422631b22e708bfa06e55ffc77031f1fd23ab05e310e8498f4ba4d65aff349f281f4f77b291cb02ab2d46373f5e55772ca83acf0e93efc2d24a77885bb3b76c55a19775762a20cf5cc7147ea0356542fe3b712acc1562bb39fdb7d8aa0bcc711d596efcffe6cab5878cd3ce5e6eab7bfb6600b50e1299d2816df0d34e83e5fa385695a0bca79a319f269c3d29ac9015f7235b4d1095869b4c56af948176ac6d92a84a95d79058c426e60a22dbd48bdf66081f62818b9b476a18428bf4298780f8ba3b51ba37d6d3cdb0329930d1fdd42e131b03139bd4b3720024017034bbf0668265dfa99218893c166d5f923449cac7cc6e10d70c74ad5e94c098ff334d86d9c983fe7b36b1231e6137291a98ebb863bf0e4e5c5617b5804cb4bec92f8d95a01f3a5f5d670189487e330d89a421b77e58d5dbc7706431cb4f57e4c6575807b1911f269086b5af633c3f39999f2ca239bcade012adb935ac1ef66d38c8bed95b230a1893f1b078972dda0a0775e31c15618f7665cd3c82ae892019080779773aa0668d99953c1c1dc065ca35a3fe1d7a9077b37b0dcb4a33867434ebacf0c5486f3a4e36ec1482d0f90e42aa10d15d3f14c9831c62a9a7f52036bac1de0b0cc8b409762c09e2e9b48a83c5ad08c05c9b136f328b995e7155b34275c07636dc3f0031586b9bc433393b2181f2e0a52bccf92f2957dd6328dddc3f843a71936f2c8e4a930fe04128055a49f21e1187ca34ae786e022c712001d2c23f35926361968a9e45986cbf5a1d7255407bafa5484170cb1138096e822287b74b21e4dd900dd2cb1abbb698e7e8521e43e50c9d9c9a60c05eef18133d05c6845a97895d7d69a3c30b1f11429d97734f16762d702c239ac29c71816724435314d1047602912d0c6f04425c64d8558e188e3b8ee0f02e7d7a524586a2f151ca67b28f59970935ac0996b0d79ddf1183991c9ed1a0d52b07aac76c938bb6a9599f9a14a4b34f60be9aa83b956f4d285eafaa3eb4a760ed89b4f591d7e0a1020ee591fca0b73782106255a1bb0a523d130d6ba94ec0c95935c39aca06da71c2f6c28b87294130c4ac49aa7a52caa3ba1ffd93a4ba0f39c55916a61af42ce1100711a987e138b07b52f46c42082046ce64b84ff06ba74270935afe1ad88cedb17e26c536dcdb61a2f7762c0ec14914a2931cfd7753eb5ac3907c12df1b1e166829f587fc4637509cbc2d5f52cdc1476a2c7f6ee632e1d8cd91f1493f45b63636921bf09ec8ba8b852b7a87c5c2d97197345dc01506f883cf1fe2ae3e1b9b6f6736b035d51a1eb6d58e10293597cc04d097d4a9433c4902032a240234174d761a48f15ee1cc628d718245d9e76a14e285f584adb5f8bb0fc932eb19cc2b22f0450387810e5281359717966d91669ee3e0faa3a26640d4d5156e9af507e17dd9b36585550e0462ffcbb06aa3161808f120a8aaadd5ee99c708074bbd1ab04a7ef7d03e2d76a382987692473087d0b3d8f6f169db8fdc41f8379a1a5c8b5bd65d3d71b189c4e29c85d7d16407305bd5e790c74afc9d22c886d328bfde3d4b8e60c8dc5b6b2c1e2cd8c3c252f3d6301a07686dc619a008f7c5b2f98f9ac8919553d0adf8e5997462d5729c2b7d5112530691d162b93b797ac202097875ea6406a8f6351bb56a9993d9ea2a255ec06e0272332f26839342402acda3c34a1319bc2188910b93f0eaa83ceb2b602e57899b1b8cb818ebc1cd132c2a536112520ff8dbd73d3b928e803baab6d889ff15ed752160550f1d197cb4c1bbe0379a7b73864c90fef9698179d7d73be9a5a025124d80d05023d104e1b9b9484e861aec7671ce8a18de2b9c8718f2e1d11e93e5c967c0ce1ea95c894eff555483b84a0fe2592ed705165b478516eeafbac0f3facb69c4001914fc81695681a0639f3ac7a182ad8c1359c2884c60bce890a559f095d7ff82baf3c3f00f49aa92fe0d23d337d4d781bbb379a1263f7cd466e0ff3c9211e655659a004047cbaa800c8b4369bd7635c155ce985e55cf853b9feaf803ce3b0340f6346f3bbae21795348e726dab6a460756bfc0b78417f31c0a75d8bd292727dfd7eee756889fd3f28c8d8f291c8b8063a774ee8f2f585a7e0dea14d5dc2566e473a4552fb60db0446076f404524479e5c07c1dbd2c2476f01ff8de97afa36a1c489e5c61854018a84f6aedbdca50fbabf4d7e95d317111ede5a641834b778d001e7de763841b2cb7b80fd836e1399f84f178b2c125bce3bb200c385c383b8ce0b0649ec8798b2a80e353326c9075b33b34909a3130500d9acaab893225339a03d4c20b3c716c16a975ccff3a64643e755d3784ddbeb79a55579815aae78c393d53f8d2467717f4b932a5bf3082e831dd252cb3bd8fb80c1fe65811ff2a4be333b2597a1b1b8479e6383a66a130e80f5c116c2e9500cac58ed4907f029eba92b86e01ef63b218f19ba86f078d4bf71bfe728dbc60972b65b75e2312a14c84d36f51239c17b331ef8d766f9ef2c872a8d891c16eaf355d8453a9dc602d56deafa4925d323417a020b24c236c82d0634e0e977b684ff340944578d9f2fd906b99e2720100a4f14e42cab53121a66cdda5f4487cdeba7cc92be732357628f1dcde8969d35469cf7a628c136b956bf6a5b1783581a2b82d606a0860101a3c31522a3a6feb41eec7b29305dd284eac612388d6c242e1f4303f735959e8aa9529aaa2617a295225cdbde8b6d763753074cd18e70f39b678d06a52db2f1d43312f7c53b672e4c79361210b5d69f3b4526ba236132978735a2d75f7c834d4cee250a7a6809bde6ae85f89645f3a05d7a5c7a6fb86429fbe2ef44c2164179befd65e9719080d3e0ce523c7f9580ea878f1659e4ccde8196e1b47e3d5f4899d453a4bb99a0f1a47f182441ba66820c740bab769ea8484ff48b83d7612b5f46a8299ae49eec691f206fc0f93d0d391f29f55fa5fd979d2c106e8b7f38c777ef8fccc765be0f0dc2f256e7c01fc886a3d1c3136efa67312b6b49158c65b15f2977d93b4ec1b7f3275280b8ede106961c2b015f058217a256d069fc34737fb45e4ac4cbb05392c9d86411d9679cced593b703d99d21ce627488788d0b3bae3fed60f5d5db825dc58d02228e9fab5c2f7be43d12fe873a138a7c1d13c7150c7bbee86efdc27ea4a98ccbad7ef4119eb6a81906422445213b4d772f55c5e38e2f7104e30a260a4219dc360b535ce644ca976a88a4c8133159b02fe505ae20e9a98293aeea2f740b307c2c55110a2501dffc3a0a5ca3a6061b1fc24be41bac9508f7ad117dd95f3e617b29f0a345800f2a3bc0c9090062922e2817cc83036727fa9ada224da4f8857a3a13932031506d0317ad7ccca22fd5123d934b8f1916dc94e2517d1d3ea65b2c4ac86066e921d78af7365a9270149cfd332188d789bdb91405831e282839b65cebe1d6a8b880993cffb052c58024aa719595b200e4a9336f5165e760f739aabbae6cf6caa6adf80cbfb5b4661c6963655b6853c55dbbe865c049bd0c33b76e7c615803ce10b00040385cacd73ed9cc498a50900c40866c0f9e8e06111a1f7d7037c1488642392b29bd0caeeee1d5f05680559059b2b637b481f3e7a9f19d4a851a3468d0c5e08d9a855de9037e40d7943de90366410d00fffb13be80088240248908db104e986dca93a7bdab183070f1e3c78eca03db6101fbd6f0f6a00e04c00d81b743fe4063f7068e093833d800e80ccf824000e0d33005688156285582156880d801d9a71650cb0e7bd17494a69bf4ab9ebdfc718e37eaba5ba00db00dbc6c058e8017602ba1f485b226057a2ce7e193e6f57eba5fdd67a2b916dc1b1c8e6402eb41dd1a22adae74057d50ad8d9c57d11ee3368cb35ee657448564989a8439a9c6500031ad00007dc273edf6a07743fa4d1280b32e5e37e7b950ed081858e524ac9fba49b8a45bc2f62973fadad798bcfa7573e9db5661264359a692ea933f933e44b199448ca1a63c2b84378c63d5922e62763adb5d6d74b8e8c0d423856824629a594fb077f3c4a68bf8fe262af17a4ddd5c5c652517edef2e9598d0b9c9b16319c9e988c858d149e6cbf6f474a163c3a321639b218be221f670b6f71a7709ee0b4d838516e7c5cd8bc0f7fcb6121b86dadb55e262fb4b62ebfe244f9d95270de409b23c3671f6badb5064929e47a314a29a5ac36514629a594a3cd1bb453126118f53d21e40f753fe44b7bbf69fc0b637364a494d65a6b63ecac86cfcf1b82cf2a8388ce9051b481abfb6446118e2aae97b5502c14577ee939cd701be3d2957f779ce5ac55e63acf5a270fa6040d94862989e6d472ab18033125d1a573ced9e586ef0c5cae39a803751797add4242228294939ab7984aa736ed741b66ba19d6bd72f7606656d268a5e0db96abf763ff0959640b55ecc6597f8f6abf2bdf4ee3c18b7b9c189e5e8ec7c38760d0f0b594f0b173e0f863f4041422fc4dbf56c08460c9148f42ca746dced84540e907070ed5f5bc4d92695f15f9d455fa51515b9a26825b26478fd294452f51ac6788dd1a5bd3aaafea868a857f9559a8da2a24c8d66991acdb0d8082df24a5bcaae9da991f8a2eb2ad4b50deafa6e0b02f517c83b03c85aa6827fbae6c2ae33d8b5ebbb7ef974ad5d74bd5b74ddf574edc9ba86b1e89af3746db3d3358e4e4ecf601211c22b679d0572ba20b973af73625debe074bd73d3f567d3350fef9a856cbb7a5aecae5de8ae7d7ebbc0edd221d7f50f5050ed5ae8850567d7b317954e298a9e0dad24ba9f4159e340af3fc57b6dc554ae5ca9d5de2a1fe7e2bf093f546da9aaba16735b3ecec57f2c3e50793d602ebb5e78cc9c2e7ab573d66a45d76935341369bdb1f0d9c424ed75d5238ab7290b912213232054a4afab4a1b7275d587dc2b7cfc5fc10aaeaeae121021d2e8478af4bf8f6739a47e1fe7753626a0d79f33570de2eaf5b39cd9ffe9f7a97d295e6a7188f554e5e7e9e949aafe93dbadcaadcaedf69a14bb5e99bbaf27d7537ed2a2ba56556e4daaadb6caadc66abdd5504aa216e86baf05e2c53a06632e736368075c741c43975b97dbd3d3d3a793b303a7a2f6e21a7eba4f379a42d4d3d31309367cce695d39e7cb44c52eb7a81fae44dde69c1453e1c732aaa2f0f4f434e6c998dbed76bbddecad72bb4ff68952dac47217d32bdc1817982eb72426c5778cb5b5cb6dc3b8d7e9d76b8c76b97e723fb3ebe5cafc09f6e47541eca71b822eb72eb72e376f074f871818637c2fc642669fe19444b7a86bbcbd2c9e09bc1cbc290f07ef060f8b77c5b3e255f1a878529e0d5e0dde94274f8a17c5a3c183e2457950de13efc9bb7933784e5e09bca69b278347022f06cf890783f782d7c463a2713929cc8153a2d49454fd38b753734e5be974c1d04cb4f65a5b9d7aa872420a951f27a72d4e5b9c9caacd2fedba5843bda05c50d3a575a9ae15c6778b13978bb1fdac53d49c135f7c43d1eac55ea9089a5cb0d0c7c9946d4c9d6a008d131a272828a84f27e7066e3197f3e4a6ac5d6bab53166b0399e3425910fcdc73b6e371771c86a0a35f8ba47f135a4a69df07add25a095a11e87becb5d6daefda7beffd79ad7d8d77be13b3b0e4b55fbe083f42a56998374aaba0222eac00cc83d22da07bad7fd674a75f49ae4d49c3f51b9c35dae9a7358cb50fddf55a78e85c10a9654a1f80d4a3a3a22251fcc73d742e88f4c1bec41840548096b828b784a6b6d707a1d2680d844ae3a64dbe963e4a206638c698f3655881d9c7972254da5c4243cff49ae0f3399044140a20fa7756f8ac515148e96ab4041aaf56e75ebfde0735e242e97f21edd745f27447c122da81b766f70f971195566b52ff2cd23f5aa3e0ec7c89cb9aace17f3da79ffb0be6a6e06251232d8983fe8e0f8963774ea75f2a8943768ec857679708afb38ba4df0b3ba4fd5d98f4fa1dca0df2fc75c6bd400d727a83e3de044e7d703c52c81374a945aba9df7eadd000b94250a35a42e7de8754e77e036a3469b3065e34d468cc130c800a5ab4d7d37f814b535cafe46b53a6ec3d467c3d74c6bdecae9524e2caf06be56303201ee4a7cffdb5da0004027ae0838ab0267cfc3ac7fd77b572162d816e9ce03745f0db748316a94fc323f4eb116982be1a728bd4bf6112fd9a541f49cd0790a2a3ef8b7c00293aaa215dd2b2d2b85ce95bab6f87941f871d90119935fcb38643dbaee5b7d9da127e25b926250d0ec9d672a82409ac869fd6b810fa4868af9c0fcaefb496ae60b54687ced3a5d59aa6975297f628835ddac819fdfcd1bf33fc6823c6cdf01a42c990ee8f3c09ff9112eee35046e4087e2e9c22cb54ace12bf9d317e9f34f9ed11268e4947a95f23a2169aeb001a1a4e23d23a57c2ceeb1143aab7f25ebd06b551126f888b1280d37552d6a64f1d588b196a858e7c436ac44c5ab4ac358f8aad7e740a421a0074d95f614abd2b655af5454475358aad7bd7b3a1df88895b052554ac15b82536f092e65d65a6b0d528b744eadf88b294483a4acb5d66a7f6e8841eeb38fcbc5ab85eed9b28e85c703fbf88e8d8e9434fb3973b9ce726ac589c570726e746c76f807e3f15874b2dda35bbc5cb87cf273200eef8f05aa41544aa129e50b49298dad8053dd8b9fe027f74e1966ad42492a6bda4e493d3c85c89576a59cb2d6e60b14068a43d5b257ea3e72cc1d6939f36423cd9917ce2c418148cb191bf5816b1884d1a26ab8d7659599264a308344126688b8c28c0f5466aa9891c105a01fcca430a1e0fb0aca244d4dc263585847603dc1450ad73c4c1eb80b0bf7c40c209051314fb8e0e5091644c05f5d563d6142961a14762a13595a256431c1b3be7c204697c8b2e22464e923ef51595190c862426d59273849e384ef2eabca64e9c0b50aaaca5cf54ae3b4aacc0f29bcc4190b3f1cf88d151a04c7e9b2aa4cd34482f3745955e684b903cf5768e1b133748b774b54285ee6155ca7cb2a32595cc073baac22f38482e15d9755649a881fdf008807fc7b322bf02b9e75bbddaadcaadc6e4e4e4e5b9cb63839d1fad37228dbd7749da0746528fd180b297abd4dd3774467428a7aa6b5898c42adbdd74a1533dc30c5cc114a4af045e9014d540ac63c494182103040a9a105261031025b8f19253078418d922b5a6af4650b32744a95c8a06067975563b2c6a8e9b5cbaa315b28f0e20507ada568bb17fe31efb9e71ced73af3bcffbce0bbb6bef77ed5052adffa56777612ee7e7b2f4516b08629f748eb6ce3d2b197d3b187d21247a36cdd339aff4fc91ebd85eaa3eafe809a2c47e3f0a3e5fde70da9c77ad52ea8d4343f6244c02f5ad0dc758a8528da33ab45acbd9ac7d706c4ce7a32ca8b540db0a51111c89a88c29b10c91b1b93274c62cda208d4d33a861e3c6ce4f4b7e1b81e1d740db3857f23cda41414242424242412188a3de1febed96e2d96c686868686816823f8ef66bd5b14356ae631ede0976741c889d479dcea1d5a4f542af5fd55aea354d56a7b3e72b72e8c8a1038a354e58311f3ba363878e1d5f341042e02eaba4a0b2312af2983b785c4944ad1417f552305549a1025f93a6afc0655514b9cbaa285250658c8a3972e4c8c1a3874e961e7bc78e1d3b7af8c879e263f7e87ef4e8e19244f47b741d80d6471762b43946c55900301a46c29210044e54c8311a021d8600ecc2258c9857975cc280e9924b18abde259730545d720923d5c74f7209f3d46dbae41286499f21045e39f069b5f8fcfbf66d48e1d35a6bafb5302cd10f4008e102d448f6242d567b77a7811c1d591d8995369736de2fb1d635f0712efd7880153e4a2c231f80f00089436e2a6610a848b7148f7134dd0a3d54a4df97ffe8681f65ee8d745b03f5dc807e5fcc2f39106edd7431615dd65ad7bdf6ba387b5d52ae07aeba25f8daafe81a7161652c8c6593c034dd70ce36601bf4113c735272f2b52535f280d238ede2a28f8afd036ee44505840f8cc46c7c7e6044053ecea53997a458546946ea8cfef741bd04208403627efb4630c6e2a8eb7641f25b7c731704bfc51fd0193592adc8da43c36b1e43d0916bcd397339731cc7e17aabad5fadd7c9a4f5881a1549497ba3cc5969250349f18a76ceff7f234f82b681db2f02c5b0c9543ca2227da9e3818aaa15f200a05e6f0d93d69017153d8b293eaf4cc029e12dbc865be2a47ad5bf9fc86f4adf3ea5df85494948462802c216182059d22a4b8241827dc71ff636444acaf9d896ccbef7b2b6c1fddd13a1ef8547bca75e48ffc81259e37f546715e675ef7df73c4cfa3e16c2c22431e685495f52ce11c7bcffa36f43590160df3d11d891d877df8547723ef60bf0be7b50666d1803711e06debcce23a9cd39b563fbc2a419c53415659b4c3a38373366838d936927ea7b168fa4c6f3b276f3e39c9ab1e7b08fc1beea844933eb79983405897f4e9834635660b815079333e7569f32fc36321cca0aa09f3e119bfc2f19961252d2b0a0e9e6a720d9bc7ead7f3f92fe4deb4d98447548a3987afd91da345394ce579b8d0d4ed68d4e98a4f3fb6dc2a4294836bfc3249d906e8538a19259bb09b758f07ce00e28ebecf297599cff48735ed6724219c619abed7bfa495390f4e77cd214a49dd79f54dfd6a3a6f38549df4f41caf91d267d21b5aab3fa3961129daab3fa3ba19259d3a156b197b5d88f742a16caf0575828c3e19025bcc5d928982948b1dfbff7c31e693f8ca9d73a85b43f274caa4c619d8ac2372067abb3fa36e016deda3f56a61d1e9982041b6bd411582843f23aefbbf7c2a429b221fb0d206b3bb4eaf589e02d119c8921062b25a55ab5b2d11aec3bb0bf0bbd1feb54afff43c5650d0c18abaa430a58882042cc910eec504987321b0691c986d8ac4d3624ab57d877285858b3f48f15cc8e25aa9aad71334c7c692a420a6f59266bf3421992cc72b66e996444666dffaced1f6bd6feee6d380473b5ab867f1ff1d556af8fb1eae3acfaf80a83d93f622a990d2d1515ebdf78e1a38dea5175360546f5fab21fb8acbd6ab236daa818124b3f1aa42ca2a2b4b7d67b7388b99cc9b7f7ee2244295eccb98c5ebb33f262200189457afd9e2d63b1107addfb02bdda3bc2ec48ba693fe082159b6a09ab1a029e1a2609385a98309957348c20ab8824544e9ca1f26443411027046185135c5c6842bb0045ae49e62816a1a132a14053a5d3cf1fd50b68927c2e20e64d163a41eb0dc1e54d27e89244f33735aa21be59124d58d59d38de0e7ee1f66dc781b8af54d7b8d23b25c64f3b0e752701a5d4f3c2c7dd29adb586f0a2fa1c03ceb7adb622a1937ae1708c0b37b68f71f738f4d0f07177cc7de5388ecb780c4147bf8f6f0cef8a798c1ae1ee6bd775dd7347316ad485e3f7fdb5ebba717f7ebdc1d8e6de157e72261fc76af68e1cc771f88236db1f627459b58685ce9f685123a67b4c40b12dbf352bcce8b26a4d967ed365951537d41e26d5a64613f8ba04a6d5945454d39c4ba88d8a73044e8d44a84b26c818e3049430574f78d8a1764313903cf9c0145143b4c0c50b0e351b1aa9b34e8daa531fc512f4515235f5518291a18f528b047d9cb618a6933ecea9164f280185294a122a2cd4c63ff28489530bd24c894a521b8f22b0830a364491e5090f1fa8d5700cc0150a2e1c8126872482a8d1709459552cc061044c5e9c4ad8aa8d53e900546c89010d962b2b4ed47a4ac801889b0d55b840426d9c5215863ebf98b1a14a4b2a882d2dd4c616943044152ec002450c350a284561c115449801c1093416e42035842dcd172437786121cb18274060660a19181da056608416334ad4e68f50690ed8818b07a4d2589961a286840a19189441410bb08288a1367f46551a122b6b9e20c10a1f965451529b54dd07c52cc84390be2ea17ebe389f7f4df47c9d01f9d349971d01729761e6e0f60cfb6167d7b62340ee59124939e7b419099d18d05bd31756d45b6f3cd35bef3ca4779d7abf5e7aeb8d5f5b6f97de5b6f586cef1cbd633b1f0f0b594f0b173eaf73937303e6e0843f4041422fc4d9900e8c18445bbfbabd77f7d2326c66f0edaa79bb6ad1de7bd3a05e145313274d766f57cddb5533d87bef1a366e18d18e34e0fd81608079f3bb790e1dbadbdecbebbad7ddeb6eefbdf56b078f4c33a0824d51595971dbbb367d61453c1beaf9af14ad55cf9abb8ecdb84cfbb78679373639bb036f0b3436a73ad0b5710fcc4a1448bab05339f4f9d4bb4a9556a59194d7c601ee39cca0eea9724b860b9fede2992fa7c421413a9f89a9a9292a6aca8af007e807d797753fb8dce3fa9c5974cb7315fbac9a47d67980c4bec659c3c72cb698f50cd5d9cc3c44df968c19f2715ec0478b73ba9cf3f3c358e0e37c570531d7fdc839087f10a671331505458a940bf650a44c494981322585a5dc281898750d9b2051b43678d06c7663729bd8178bd9fce7aa39c68d76d46d729d3459210b03c606697f396912d554edcb490d2f13adbb2d5c261be5c026a63edff59989eb68e74aab526b2073c79143c7b653efd876bee6b1f908d4a8be5cf33a46f7839b3526376707e63ae3bac7f77dbea83a6c091feb63d7d7105e54dd0faecbce4668e3e6be2def4848a5a529dc772feb9dba54da7ae9f3290cd4150af61d9844040d036f549d5d99ead303af5206af6d0d1fef54951d0e27919a189d036f54ed3a10bbfda18ec1abb44467f45ee16e3826a0db70d6778d02605b4abbf2da70b6f86867080501fdc8208a012304dfc7458b9ea19958e41d914923afc5f81addf05a7c2596968a7b63549456bf94f5de8b6598a968e5eb4729a594e168bbfefa5a6bfdf74887a3febad62f7c5f2ec985f61f5f2c435990fb575a0c0506a8c617151029d85e5441c04e80c1603018cc06b359c190a467ebb0664d155a2a48824b124274c925091dba54975c9268925c9268d25f5d724982850ff4fc49abbd98cbae97de9d07ab55dadcd8dc486d31b0a76ece7983f325cf181563d6daae8319cbc9557cc9d1c9d1b955f1838681ce8ece0eeea1d3daf9763e13784d7c3c1f0fbe72a134123c2c78585421e542992182858c854c4c154e78152a54b1d4f920db7bef2deba10256a5674f3332c06030580b17d9858f0b1f98981d26f75071c4bdda57f8bccf7759a8a0a2e28a8d5111070707e7c19b8d61e5e4e4e48021154cf411e1ded9d9d9097f320f0f0fcf0f904c26930105b9b0ccd80ba1174c383178c2051f1f1f9f17a2982ccec419deea934f9ba56668fffcfcfc0cc1d05a4141414130624081c6c298985f886410c998226b8a144c4d2184b5cab2923143c68c15ba2c39cb8ca219453c4a118d221a576864402383296e5338e9f52753af750a2619d4c8a0c6b46238539a1a366ad8c8532cdd253366cc9861e386569aac821b9b060d1a346e186989c9467bea1f6847b42357111b4bf53995b8b172a4c19106555a45580d7068806303b17997555a376415b4a470e4c011974ca8740c5c183d54330340002000b316000018140a874442912049f24857f2011400105b8240625234178a4391308e82280a822008a2100662200628658c330e31463600cdb6e46a16a0f2a8c8d1c0b8bedae3f41195bebb7f4b69bcf3cd4d88c2a2a2b74891112f8d68098acb88232cc44726e090fb1ccf60db5d44b5c179192604b48ebe0eb926d46c61cfc9c82144592521361d5f9efc0a031c4ab9df70e59ececab1acc20fd146c310b17359bae8d912690027139b41e70e572cf39368b0b4500918fa83ceb9365313410a7786032413c1125d22278b0389f682b45784df94c97cdac2282d2e0949925ba30e7a6383080d195a231f7e0c04010a9fc84854bdf218e63f657f8849b4fb521e50455ec6d381e224845f5003baa322607d229e971cdbd8f33fdde40b1981f303288bad75351a763d1a5a6842cb7ce9c29175cb2ee0a2e2a1a9efbe5e2f924a6a80cc2cf72fbffb63dc4b2b90edfb0b5b2fb8b080ffe7879cf5dc1b0ef7a10dfcff951e0efe4d5338d2fbd803b1bdd6dfff4fbd84f0fbe5a6100b76cf900d48fcd2030f680e832c79e2e09dc1f72291db7108ed054ea7b65225b55289bc33f0be6c40c4d7fea368321ff1e49bd08da57c13b2c96a4571f9e35554b056905696c7976bec0c125b9c362cfa781b1300f416d0afe0af6817810e0175642e42afd12d4a7dc812086443b4cb3c0938ef6398582f21aa5c5b956b21b62ddbde0b554c11ac9b227a78ed149136665efa097f62b5e85de632ee4bff834d20dc54991dba49e56e3020401af4b87925c79442350aae77045522b4fe00f093fc5ef6889513060f7b75e22ea646d088bb2f238e130d47e96fca510dc04556b7344a8df76d094c8edc55237f4f73596a54dea2a7e977a94c9505761f69a0bd6743a068f110235875629450dda6dd643260c1809b95c1fa3025e27372b0cc6e2108aad6b4e0aae0b456dcbdfcd581582d8d76596bd2f3978178b6a484d6c419a7c1014b6658b13d5d85321c8a5189d94b93266ffc42f6b430a7a3151d8251dbb9c3788fcf630bb22322ef1d6f99412e4267e8f8cfd1cbeb86d522fa6b8b68e97d998955cd54a4d24ae7662ff9746a4e3728ec124d445012ae6737b96ca65cc821d947a800a08fafea518360ac288ca13759495383ce778de799d1f2cc0709d935c0037d91912edf238d6e1024f02ae7dfb971eb4c467a48d5c92ff5813cbbd0b3bf3d3bc367a788ab268e4d4b117c186bb119b274d9df1709312995fdb4f1cb0a22b2a74424ec855e1ad4e55d8b4d3c1cbaa2b18159169431c3dd2503e3c1d556d05fdf56aef9b2fa30e7d70b8e7435984c23a95122b0775b99306522800948e54363a225fd23c9ce5fc9a3078aad0b2b5098781cac228636396ebe8f6d397a01701aa7edf76fdab9e9b40263b64bad63a3c7a720fe9bb547e4b93512fa2910faf43452c705622d823bde83c0310b2e165e35139e7137178dedb9c8dd79e3f4fee71acc2b6b262860249c5fe0bdd75d60a635f4d892c152cd64e08fb0b2b32758d43290c46139a6e52d7cb5cb2c34c3d5f0ca241e80beaf6ca2cb6266e7106ea9da1c5c862ecc80c745c71d536ae240c9ea889794290b1c8685619e46b9e195210ff4d57a515909d447ee6ff7bc0fd2241e4a8b81a51b3ce8b507db84b37427479323c18aa8e24e4f4337b5f7bf3782f313e9f7ab7c0809bfec14c14faae5214e03159697ca4bf877b68dde4b769ce893d001f731d66fe67c0aa48a58af1fb790475a52e8e0d7950db36a697f8120e5cbc417ee7d7feb00b33f76ed924097760fc8794f53a68d813d76912267d8f0460a23d46d7cf295c5cf66153f1a0f2d2ef4220b0b895f1b77c97e69020e12a407e590568fd170e477b6440ea08aeedcae14bdff63bbc36c75d4cfddcb42c448c0a6a0bb36ceb20ffa1ace072ef1d352cfe68743dc0be7c9f4b7fafa7d985fc82533ff4024956e15e23ace7502177295990122a7f0c9000e1e43fc07a7094032416464c98503b6e0e98948f2bdd5b74bd64b7b7a3d03ab5c4030424936df81e0ff0cecbe6ed1aca976098d9d4f758e856fa8e39df75342b0002e257cd025651c87af1313a18b46714411bd71c8d051761a6cd2e000b25650fc901d38ec1a25162b306ff3cf5010f75dff06f14f551cb956421c7aba12e8817fa3209c00a1284035464ead9ae6afa05c61f121d3b9ad0c223450b448dded4ca1009bd1dca511ca35478d2ac63d91fa943df2fd0a2431eec58caf5c66ba2576b2f518f50d58f25bea45f019664152e1bb0b5f133988d2b090313a442c2fb646c4c209ac3145fca988a97ec3f57b52ada48da36b8f2f1925aa5116cd96331247ac547d9f76c779160c2ec38e515c4b36d0ddb26c3968b6e1eb6f0ee2077bb41d03ee5cdc02e22e8d6259ed54f4175142a4773d430e3511116fc1ef0cdd492f97f119b83fc839c80cef504121e0f460ef8f37d0db27c40cc9359a3bd77bad531b936609097696bb670bad30b704b3f0d76674830df0737529c46162fd62962a0b089b6a4c4b069c5c2f4b02f919c72b58a31025ee36dddeb2c89b7832b87e6629df668184c4612d2fd48e4d496b6c4d31620f72693094c507b6151ba6aa2a5104c8dee1fe76b998138eabe686fa83b0b1dd5a8b35ebb8f27af30e4c57bcc0f7ddfa310f4afc5150805220bf0b11137e2d6d2622c66a98c05ac4718cf62630bb9f45621a0218643d08ed1161225c44280af9ba1ba5a0767491893c6b809412f18ad25296115040a2ea0e81da3c388dd532371cec43033d1550be8b68be74461874f631f7e132d51264a89cc7dd3fee7201adca61c890d8e82e5c2870da93aaaeadf0fa8084a5fa8bc63233eadba27e13e3c8ce04599698ae0a401efda4122ee56a115eb3d636f5245158a143d1d2bdf226e000a21ade5c4221879bb38b16f6bd3438feff7fca0355f00c0821cca04ca0e173fffff921c67206fa4975dbb2a0848d81891a65526ad80bcd57305e371cfbdb28d631603adada750c885d60d63be3d3094ebb798e0a2b8b34745a5af4425150c430dd3e2f6a6ccafb7d22e5096228842cdfa6a0c88d326440c03e882b182864d597286f4bd336dd0d1325fd171af4f196a8f08c901f929e78d14e51c938dd13ff045dd553d30b124be7f5a24dc31c24d4bb67a8a366dba8b063d8c843c31614b21c352c350dbede8550e5bd83fe65cff1602538e8a307a9e39b126d865d0c5e246a3158095d0fef2824deffde56205c0b556ca2d65bd2178541621e5a9bff26aafa7def06d617c0c1f306c0abdf17c98bc115f6558e87e8ea2ce030f2f569ac70301a9f28d0688521cfb8eb5bf1f3f403a00319e705747204f9fa94482f69e3520c3a99dd21d7fc58c8fd78cca17f5379b22c094772f55a46d172fb4990b8955c8cae3d705061d55bd9b058d173881c456e856e5fe294419fc3c83b3bc76a8bd01da1a11633bea02aebb786ee8098eb0c96424cb838555934f9eb89df265065a1b994564a1606b5a3276d292b3c32064737913ba4cf575f71f5eb5eb3cd31e404d1d81fed0fb4e6eb1a7cc05bdcb1b48db9460ae45f68162de16fca3705f6f6910882a8040b12f13caa46292be8e7e627a9d0767bba9cd543cfb7b621e8bf38a3b2983408aab77b8e86b99e4258cabac79baf850dafd83fafe103bb91b278ba4e26e7375870355f52176cbfd73b5efb1a2d64dc3d2b699ad07624a4e02a789728963278b598005171d65ad8ef7f2ee1de20622cabac8994b62f5750a50de28a76e192eb54b6f9844802a057da5e4e41d53688146d1e05943391ea8656eeb6457f75b9aabb40e0161d0d2d2d1c200079601a17e513d2950a84602543ddf74bcf7a68f873f128b10a6a46438452bdd754b5fc904c3e3bbe8ec578a9c5ee65994f0f20a6da97479701e6d21eab508ac72a4af75825a91f679582900e7e806421e9e9b9b742e776d37e8c7da523a0719024351e0528e23de94c809d8cee00b3a8e43741bf20ee3ee2668003469884c83d221e819d96a5855bc2657d48a01c849925b47f3ba4ed6de060487a4393fb6fe71c5ffa7197f44c3753c16b7ebb4b66a1836511e176b4ed3da400ab8f02166750e3a0768994e2031cbb74d09ed207093920504352deb6b09d770d016ac4969db1215978bd17b6cb9ded2cf7d04dbab5b9bfe271dd37336318e8cc75688132922dee75b963cfd200229d8dd01c0c3b0ab318783769fa57499868d9de83800b5eb91427a777f8969bad656eda3e9149daeec0ea541cb9e04d27a25462d3ad20dafc581a12f80d46991308a7c1d4da4b2dfc7ee9fda22156db11cbe8a35232f3bff5f60a0c7400c10d68e2511e14cc2692808967099acb5b1f3028a9255301c89b71f185082db4c0f2825049ce4518cb0384764400c9abccd6199bee579fbadf485fa44643c121d4b71f6112711bd29a1041cc66be272af1bd601d5334102c0028d3456bc19d078428a24624edf70affea29336a7f1b00ace15050d9558b01ccc82dd4b87f322dddd0b056f3631b9db50e7e4e285961e5c2936b6ecb3b7c57a87426750d85161d189336c82e88e154c5c184ac5d0941f6b41565aacf058dd611984c9d5b7456ec67602cefb6e5d7a93873bc15de04b07c26b12121f7df92fdeb2123d61358833d585e624ff26a7decefce01dcc13f04c01ada2ba746f754cbf2fdedfef3cee8e242c0ce358ca06e8091cc1a8751da6b76f4086824ada3850630a414fbca1d22e41009009f56b81340e57621cc643da4d66a37ad6b0f68ae5784e2d6a3bf8dfd20fd1edda403e0939922b21ce2cdd9e21f619cfaeb19b8b1ead680fefd4539d9688dd6c3eac018450b1148f902d2cc676f25e4a90eb9de092f485212f364b88af313eb910e82ba61c304b48fbf10bc66c1286ab64db2df54d40a18dda49be4f25a68b3103c74b2e958929e631d047a58f0426a982021c62de7329d1c3c13df4eab0d5fe99ffaf06f632216250216cc7496d9814c7e04a100d24611ba16836e62e5a7c5b9434d3a306380fb43c2052c94e9167e40d4403a6bc9a6e5d0406609dcf40f333c77b8d7dca8e214570c16a7026eebab28ac37cda7dd006a198ee68b6202bc6e9082e297460ba26096fe8086510a8bf7d86c5b451a2726cb40240534cb7cdf17eed00da6bede955426056d18eb93d02ed583f3cd3265c8e7aa9d1cde3c67b21be5fbab233a5900f62341e2404c31ecf288ee5645ad991d8e5f5d21804421fd9eb2ef5743b0440b19076bb4b8737f138a6e92316faa21bcd368f509e3059f103db4e96143da5672005a0e8d576d28e915caa4aa28ddf3b97d3c9896145efe9dcf19cab9fda96add5e202451468676ab2781ebcc69a48aa721d53aa461e56e4e3878f26c28cb9438f6310b0f8443c3b8ad89461266355b01e8c52401266a6fa65ab7e8414cb7fbd99948088066ce61a0dd4c5af0ff05653d17d41cd3c48e5a5b3c8cf0c0a686e206f9eb0b1e602dbf12be44de602f94d4b648a9e929a09441109db7f1d224e93ea852742cd18ce3c0fb5a10960556bfd7d1f88e7a9eeff1f5e7e875aad42a483085fe7df067c9da6bcdf0b3a9655e1a3aaec3039f7d2c29155e0dbb3c16adf70fb6c4740811d0fac3e9b309482434f7ec72bf5cb10f79551e99b131dec4bd2b3f11b35e72a0ae0acc4de63586ee8105241e5c6d929dde774c90d9985b0173245bc10f1d35355562ff4daada67c47c967d252ed76e2120a85a3bdf3a8fbf20c0788f04aede4d3bc962edd7c7440515b946daa62d38d3ad4cb3831946b7825930c1c0ac1b089abd657b31177c22c20ec5dfa147ca860b1a6f6653dfa33fbb3aedaad175f5efcb4342462a7eddfe2843e745b8608abfc84b61f64a28d39589fe4e69912df452be9e021c252439ac9ef0d5224aeb05adc6382cbf78620b60c1ee1f7a010161672625396a181d92e1af554f6334ac85ab1e03e0cf8d1617f9102df8f37b0f524d5ccc0d21d11316c776a4409cf4c388a213917efb7723de9d51466b0a88e7f6a85c9bd5134137906abd4de9883d41cc56c6212505df818637558ab261b4de7ad8b7aff07093f593884ecf975b31c475e2a0370cc911d7c47a1ec3c9365b47a62bf4b995146effc19b71ae1e950ad2d674ce1a2f1cfab538e4e75f54872b191b105d7d2a0aa3db25cac99d5ba2912d14a70852531b778b5cadef4fb59f608446085eb00ef854b70975cab5d6323d7df05a1db4ed274c2bb1f088a03295cef0a06cc275eb8e77700539e919d164ff6aa26fb9515569eb7f1e31d635e9cd4087753e5cdfa3e61398d9bc3dca5ae07042bee6e33851e3ec599ae774da2c97adf415feab0e3e3803d070a5b7e5926f743ce5545ae9e1d30e94692c166e805710c8dfe5e9ffa86dfb8648d577795aad30720cc9742ef0ffd17fb457a56623062ad60a06db8f9d7b07f8c6a1d2a9cd302ab8cca81bc22ddde211938cafd956c42821e4e66eff79d081ec55c2dcdc0c1f11680dd45a0bd77df4a2abe50499fa5fe95c0966180d09db4477532f271fc039cb87ef9ec4ba40a3ca0004b27e931cc74ed7eeb375c8117415448cec93cd4644393165d24658e197535283d4a5e6494a08bf79b0e1712ad03f75631ad49c66b2292e802a66ad819a71f13255e1a9afe90d1772427d2b4df783c010fbcfe849127f426f85321e03290140fc58cdb8c54f3b722119d780eb17ecfea2e6c51f309f02f75098268952159d1f54118e20dc939bca6d2cd28800951a3fb85267fa20d4dfa63f8590f9d161c16c30284646b77dcd21cf3e3ecdb1dc9c020e3979d85f978adb279e1173468ac4d6383f8e13dcac8b3e01497346777b32c10ad68bc3601de3c1dafa2948cea2475fd518f544d601f267ffcedc289cbeb2b464e90b70517d8826f392cd8683e6a187cface88dcb207f259254c03582e5475fb09d275939ccd4951c0342dd7419f583a39503d31a82fb283e30ba24a50bf0efef829e8982dda1195c7d4626a9b84c9780211d21ef48af94634cd2ba693592f3593ed30185862a9809363aa759d0b484eebb0b329432a4f8fab672b6b7a578b3590207e29c4748ca4ba45b8f2633e744ec588cb71ce4c5439806d79af27d6bc23a9c4660b80077614be1920a118001cc0b3537923dc8d4d3dc3d3bcd7dc14d73199c4172039f52e97c925ff27a22eb7164063e7948fe59d09e7947cec4aae7f6763306ac6db800ee558a9f677369f1b40670294751ebcd6c8ef54f1bc768ba3179d4339d37cc3426c93ca77367306c7aa094c2d51457460c045efd414b1b11d82e2e51bbd320496617bdb4c5582ce4e4ad65e599cdaab3b253ee332e62801ab919480b5b108217b76708c12444f612fb1952746295a3842284b6c0176bc639b7f1ded209182bf3eac24dcc70d0518beeaa27a1c1bf2a39817d0dd29f14cb7efec5e08cc12634e0ee45070a1ae119349bb9cbe35d31aaa3007c00ee27ee80c419e748a62914ff81a9ba2185df6550863cf62af9cdbbc23e880edfdc83ecc041d233c8db865794b07251922f415e58bee7a1f0a0cc17dc5373bd5649560f60a33c4cb00f99110e2a3d75b62722f0ac8e45b27b612e16b179f168171e95f0709a6e6d0a470e9c5aa356fefbc454aa146f43de53b7f17a83a1560783046012062af4148f4d1f48580723d47cbe58ce08228ae9826054fd7be081be4fed4fd95c4747514dd3b797f605f847ab3a0f1dd80b6fbf1a79f29a0595d74d0671f9591080360462bff75f3cae897a743ea272672e8082f6f26188144dd9a254ff98c22ba82436a8c5c466548f99da78b4748875b7241f409aaeeaee0785251befba217f06c3e9cad5ef405bf99bb6a2d83366da09260062bde6968471bd05a0f05ef7f5c4da2e229512c009166ea1ed0ce99c1c2796497e23c8d7f512b1a685841c522cb6bba36f521befc5704b5b6ae9ce19dfb5490bdb64ddde8ace90fec9516fe83b51543199208b33b86ece64b4f827a6b7ada5f882a02e05df76572c8c638a32aa4da46a8b3c06a4ca4059162abddc944e3ca4d685cc3b8a2c9b9e1cf4b0ab2b87ac6cb2f28bb7ebcd6d1a4a47823a33fbbb47b7098bc45441f6cea986853b90b8f78e447ebb6dc2443b0781c7511c933e3df3b465c46d7c90407686be2467e8a12a0d2750483b233822feed99e5e02aa0c177b81df01de70bb823044ed33e88be47d8d4afc2a41e951e6bbb08e341349dc04634e8ffaec0f1d594e53985e2ba82d767da587af118f7290da6fb3781ead2ec6901001fb5796978596a1ef33213c799d2210282947a3d42a734215d3b12714a29e5e682e41fad2ccd5457ec549a55863f7772f9c05e39fad78b65eef1d81bfd6d5430b6d92382376c0d4da31145024cd11d2b8f43dcc31116d3a6c420ea3c2c1be10b8d4ea1267e8c52d53a24cfd3becad1c0ab47578df5ca72db8d8eb5d97b344453db4088eb707cef9b5b40ef6dd6e2e42cb40d09ed1ca802d4e948f09677d3107393e1618118a3900cc5a070702c5543bf7b79c14f5ffc1d7f65da484a5dcfc5ee4b48070122b5fa54d357ab28871ee2db77391a1ff1ffb456ee8d633e31d8788c3ed2499405c273baf641bfee8d9fe0110ef01b3c48891315400270b4517fa2443ca88c341bad33380d45c1d7dcc069a051c74eb591f8d18df2b9cc28ab40815dda018c05e3bdbeaa86f0aad998aea91ed562b3c9c9eb58bae76eb80ac06c34406cd7a14a55e5c1c3ce3e4945aa25fd6605979bdf2a77b673b10a83a0d4ecbdeb96a3ea9a39e4656c0039b53e450542357590bd8f61b02da52eeda32ab2a15fba66f1e10646e089c28a41b9f19471317e105e378a0619874b3a7a3fa7a555c354b9c926248470117a356bf4c742bc0015f0eb821a0631e0c2ac1f81fb60e71efb346dae1efde165c7d8b5fa6bfd31ae224c6ff6645c6567381b6f2f7f1236f44b13358a44b585e708c5f3a6551afc7810cb51d8012a8be4c819cd0388f97f4b045d820372918856ae7bb4157963175809eb2e512d1af0c8fc3ee00f430c10a89cba1f36ddbf74512e1a75a714e168643f0f40a4da5e323e83e366fbb0df2a6163ca781182f000155ab929d77374df1af412e466afd7525ff89b7780728641fea06cea9c08460cd887602ef770b0642fac52c20d5a91c78670ccc901beefedc0530536398739888571404ce013f3f26228383c1f6a0748022b6e2a0394662eea0a00516777b1f1673612610f8379e65c6c74f3d29e6e944b1fd9c43261998b6fa17e547a6f6536cd20c1a84af19ae4987165ba8a9724b006eb9cc9e35f73359163a8d6bdf6208fdc11a8d2b4f692a0380381ccfb38966ad011f8b550d144afa8097404244a3f915088cb022471761d3efb8b94388a00e7385914b6119929814c805258d38bfea90ee4da7d7ca548f63a025d18a6de2f17eaf55b86409b3190c9a822245a0a2de6e4d7feb22ddb6a043857da47944effeef6235aa9b371cbf5ddb56295d6f3d9518bc8e96bb42043a6f84b01ce33cc9d2e5f42448b3f8bd53285c8e4d1831379e67de8890f3f090b2eb40616864ef8a1b55036de34c1ed8a186eee3b72c7a60ca3a66e474a8921bde47c27c5271658fc5eb683f1e23c1e9efe94774adfe6bf33792998efba276ad1ec5da90c3e1468a872a93b73c4742c264b67cc49cc71168e7ba78b3bcfdfd00217682fc970587ab590a8c27a04e1cba51e7ca0f68513719bc3836b1930940e575340cb49c3e2aff21c408172eeb72ad4ef822f4d8dc628f51b67cf233204eb62f1d32006d205e863f578b3f1c014df5d37109c23bc9937d1f29a8d6683592795ce237f17975c56db4209a35bad278e7e162338914bbc75d9a42e170a7a30cbcd82315c668dd82a09adab1ecbe2473a77c8261b20ea5ce4294d8a644f1725a235a44ed93b938177d3452c8d4682d33ec52a1299ea8f48568665f13b7b464596b0e1f722d89e8ce449804483e27807c6c509f78831371e79d91ca131f97cddcb0c58409470450b232978c80510e2c5dc27f3fc8d25e4e7a6165a0ae54d9c5d0162b715ce5d38495a33c1a17849875a387c5cd601c4f6190511272374462317cbb6e4c50d7d00b9834d3fc7b48701153383e4cf9474c0abef8054686fcfd0a22a80a831e45905b8e66c61b2ae9e62ad7a8e967f42547493775eee3b1a8747f3a622f58d5ec501eb6d17d420a6869bf1c1b6b6918e0a2953977e16a39c344f3913fbb2998002e0d0e8b21158e0598e8b7998b19bc3b9c744d0966ef7c2ff96079cb5922c03f5e048054eaff6f07145af844272af89f946995e81679a8161ee84939672f77e2acbfb4c666b1ce8e50a073f062c9108622f688aeabfa4c51007b096ee1a19ffd014d7a304aa1bff1fa6345259aab1fea8c69396513562b84ac84de3f2fe492c92f6890884febff1e28c243ae39125339e05effb56e92b8285284a71f9d7e5ec9aac69a51c8bf0384dce9a58e70419d1acf6705e6240f3a40a20cb6994870478c62ba73446f15b9dea717025cf05b5a816d0bfbec99223627484bbb7474a73fbd8cc770125d653436fa3c2232ed22b7d325f95f05ee365041340856fa84ba26e8216385dc51dcfd8f6aecb6868adae4a24a70287148e7c20d8dc46f56e7c2eb1c3f09e5bbeddce7fd54dc2dbf0076dfaf94913aa18aef7d8afc12d00be802047e2b4a14f25b7cdfa1d5e9b194419cbe8de2263bd85de384047f12a34fadcaead2d8496cdb2b64df24a17ecfdf3b85c33673c4bca22d08ef2f49fcd4a832c75879e4f19e845571ca14420c53efa80ca93b6562d3d44fe35e048955a5fcaa49085f168e42ee11577302288df6758869b65b54468b1d690abbe01a5a487caa5cf31e47f433c541aca77fe03d950367e1e6c5f124fc8711ec20727d32c390684467f7d07c528cbd518061c32578ff5b0e17c0cd003e05aa9e70d5bfffb3cf2740a3c3a93f21014d198c0167ffbf59d68ded25f1a0c3252619b7c1367474384b9acc26b102e6adc4c36c2be196f3030ea6b29ff3c150f08e143e225f79f9cec7e1fc201306c648b22671a3e521ef001d848531bb5c5996ac727683ae7e6cddfb65bf4b74ddfa370a45a7ffc770c97b55b0634f1ae58521f74c325cd86cdd763da2ffc0fdec0604872a1ab5e9267d25beaaa3ee990ae17b477834aecd814ba6496513ee7b75b747b46fd49148aa11bfedde562a56d4e52e0737324eb700d7718f73edb792b0b320030962a8383382b5dce4a44354a66bcd8193c10837f1c8678c3f510beed59e85e362763a23aa6e0575360b5f6867580ae28cb06c1585c3c387a4361dc9bc460c40ce26ac4a3636ef811a8fdef4bbde5020e72090881b9fbbce9fc4f50b47e579ab9d970dbb75e69c72bd906e95c1d783ae81c3b5acbbfe39cfc8e6cdb6cf85c369ea55ec76ed7af6bdcf7de439d519cdb6806481e68d7c49ae9346c04d7e6111746fa6af5c6c0a88fa9085b643e64679dfc54d87058e844de0a882513ec1f1a80fcfbc91a48e7e04f20d12fa10620c710ffad14dd4b94d7a05ba66e5894b55eeb69958eaae60d6898c085c56fc25f2cf2f3a43b2fcf2a0c836b5d8c6d70adc5d80faeb518fb60b517671f5cfb62ec836a2daeb6e6318804390a806c9aecd31eb20f61761b5dab8b607c55cbd917abbd3cdb62b596b12fa6d632f6456a5fcebe58ede5d816a9b59ced22b596b32d52fb72b645bef69fdfd08984c18a9ce81b21fd5d839ce5625e03dd7fbcead97355dd44c4a641cd6a194e6c8c52404e40a44a595069af4002729efa1949c8688f61d210325451ee9113b0e8e893830e62287ec63fc25d5c92cd51bbad787a4b436ed4220d721535262f63ef35807d66b898d5e0ee83b8559aae9f43ac85cb98744523422bd7b76d3308df048cbdd9a4596612daa69d40d57aa871a2838f910e9cd590373f7030b8dd4432fca47e9c0a31387f22dbc0faef577ac2a1a59b75b699d1412b743cc6a13899206c57b15f41f242383491bbae9851360a7332583d34f01eba32d065d97145206d651cc54a4b135f8f290fc9533445d0c52e64fe69d1091ee5149b5a99dc3bf51d16c96c549df368184924f5f0a5207af0de1957889ae73b93f74b8940374d156930ad053b45bef1c5a5d491b394706d2726a901d10cfa3a245802e4ffc4065c41e02e905e4144badfabbe4a25f89645598e8309c0eec7bb6f68d68fe4f45fbe3885472c91c208a021c7e59661cb13510eba44b410103402960fd30978e5236058bd695c52a35ba3328798185f39d654c371fcf280a14e25c0c27d1b797d415f78dbea7939da246029028445e65e7ea44067be4d946824799b470b45b5492e6b3a25d978c0153e6d28e1ace0f5cf31929c57133d5e87439c45740ff0d994147ae35def88adacd15ea0d3f24a1c2b9b59614a696a071aa2b5f60a4aa5ef3200daf6f2953dfc460e03ebd7ee764b71257b6849ab8eecd66ed25d640f2de99ebae7ad329fc07fc780cb6dafe445b5b19f34a8b66c2ad843c55d8b9ff6f0c52824b4011057d5c4a57b140f4c0a15ae4e74c9070063ec7e4bc9cb12a95e0a8f3efde51cb7f143636c3f853cdfdd773b6b8af515dd67c0945160168f8d1dd37df1262eda42eb7e70cef2ebaddc9749dba0f1a82e2ad5d3bc80d574a4e672d57dd448e5417d5e00984859fbd152cb411e41278836d64ad0848ebc7860a43beff70dc4f8dc91f291fb106a3574540b5a033c183fade836c36a8c8dc559c142650bf58ced39a1f58dd7abf16da03e24fc3a835a5aaa8b364b3107d4e4a82266c57bf64c8ea064b6f918815e87c40f8aeda884285202857625afdbe90b65a6c35b31d1d807dfe8bae12fa31e375a84ed9caf986698afe82200b951175ae1cff646f810b052eeec16b2676510fc95c9a162197ee37c1e7e3892b12071387c365b318372885f3039a4b0d710d93c5ec1f16140b195397721dc819641cda9cd43760149b5ea689e16318af7fab1f4b314dfb74de2fd8e856588f0d36f6f46ca4b77514c4b75bc5e2a5333c9489c4a21dd4bfb4a31a55eb2d9f9bd7179e25186fde712256363cff27a90b93396104e997683a1176b2691727a97e6f5542c20ef80cc8758ec72f30921080b41c879bd8e68823ae72cd62bd3f63927f8daabec197909a60d083ce9c8fcfe1a81b622b197cc2c5568db2fc4b93b4cd42f96d753bc283d7adee5217028907101c64ec6c4059736d69e5514db0521c6fb6cd3f905f0a9e0afab27203274fbc6551b7316f9554c20677c2bda57eae21c725e084d2df33a4f6f496c4d127fe63470d6874cd0276e10df5775510cb800c684d98e8879884801b22cf209066742c677f311c3612b8efa1aea4d27cc91fb150b289dc3edb8eb99b4fb6e5e3d63fa58fc25988d0de9bd8770a4ca159d11cdc38cc5b03db5279cb2474aa25837e58efa7de5849008192512f579f587c4b06318dd8e05ea34a35a27bae2f3b1e2b0fb86c38551d87b3c3cf27b246ce932c5e34cc5fff61e5fdfb4b671e7f5113c745f6c9ee4b1e45e6c55d5eea57cd70f3c6b34b5a28d4a7136831684eb4dad1364dcb1cae225ab8e9ed81d2e24299965dfd177d50e9e6643bb9adebe4cbdcbf79ee41e9e5cd4d163e272b5ec3bf708ed10d382f0bab32e4597557524bb02d2c82e584bc20b92c68219e512b12b66bba5168e2cd5e8b4c72f38bf66f7be0c6dc9c4b930076fbf19ae056cb88338da1f5c0dd5ae6087aabd11b9039b8f1daa4be4de2fa94f0b9704391dab4d34fbbb720102e2c4c512d606673f1506a1d094f1c15fa3d2389aa101d1da1620dba0b330fbb2b88857a0602d7b218c5cde054047016b33d7dc2c2f5609e97502129172053430394475e1b2ae8a0c3e761cc4e227c92226c6c4c5bc0b8e941ef23be2a2b6c16105797031b47ea72c3e0aac47694394768874557643c148c16c6daea217e49e4177a6a95f2da52395611abef1b49a7f5165a53c40fd3600b814bdf3d18307d9ae246a436bae774ff38c232c158040c59fd8abfa78cfd498d942f515cde47aaf82cff858ab53cc031942d4d1c1a1edc0da2a603b64547003a00133d810035560d0de3e04d90707ac91e530a75c1c8a297053e6aa8f863e130c83b572c7473147ebaa7764a027fe05a659b8cdee0b4abf7ffa56ff85856e43e7de25de650ba0906ac3f02044012dca3712362c68ece80daa1d15cffe881e1230e5024b73759393d2c6c4d84acdd0e35b31a521f2bed28ba43e5300162078737cb8ed94446649022e578ee69d1638d6f461479c3f45e33d026d9ced8683d58760dc6318fddad98d5439c040334ef6b39f9213820b29a620a0407677c8b21c29879b917a3ed520dfe5a4e6ffbd64a44d598d19f9172674d91d325777ba945c922e0738b6d71604b6b5ca97080e820915b00ca24a9add4d9bc30b9a0a2158c41748f2b2ad7a916a50af033e65b00309333801bb3a466d5f01534998bc97b3a18ae5d5bfddeac2f20877da23de84f1f5ec35f794fdb7f4a60f28180f563e10cbfedae0ed0f82b004288dfaa657c73c2ad0f2d820a64b182f769abb3d0abf870b5856c9a307de58b9189f497ce121aa3f542a738c1b7080e7718c259b2423b622586a2466e22e82b446a25cf80ce216e4d94912b140c3b9fab21105843dbebe745e8c6e789b8415c1f1c992b70c840e4321ed1309864160e4ceb76d7fae6490f23803e9139507ed094c76971d95d1572e9fd1e30f15cdc9ab20965b6ec028dba51dd39e2017125c9ebeca9578a002c4f6253c0d8e8a8fcc1835bf9e5ad05926cd5997a66bcef676512bac1d7bc76454f9cca5a6a18b58c790a1063f05dbf84fdb4e64c74f0cb779c43817453ca44ae0808f2bb6000478cf26e3c90d7cd5274d28da8a5cbc3742342caebf17ecf99f26231873257b0f7b2453198393ae737a2e5883823b8a594b27a4fe14f77c1cb9733198f43f45e6b643080ed3282b4f626d440deb86af0d1618a1d0a7eec5c77ff08aff8a5d55b381f38fe8dc92600a082dbb60afad37fda428c442a2199a22cc7d9b4edd471c6645a5aae7630677cb6630afbdb8a0c4621a894fa23c716212f1f6c07fd5cc08e1c6bee8c583a7ca84cddc3ed51f8f17b0457ea7b5471493f6daac25da745fc376be4ede98b1a0987ad797538a98b4050ea70ae339615f33d091c201fdd902158b58c2dec84262b401f90651f71720330f9d8521c480b0ff71f78cc9620f112c2636acc0aba87116d3c998a590a2225eda17e0e9acdefd76056639ccbea851bfb82438f32fb61abe49e7f337b2fde12e1021fb8e59ced35e99f520cf81a875dd6e3f47219c9186f03028d69da9713980fe45bff662a17585bc182f39f71bbfa7b2271b46f26d70457fe037cb4760d63dd6771d4ec0ba3e9728fc3f2b04481fea9764fecc09094fb8fd0dd207fbafeb678725975a182b1c3a636256da72b7c82b8b4ea18afcbf02c08b6d97125e1304eb8e4681b2ef259653e86687fb4a55f346fc603df1e73e73bbf6f6d764e61f118b13effd63b12b2c7e61f15045388c951dba5cfac613f3aad76965e310264f2387e442e5cd4c8d45f5701ada4cad84e5b287d9cfa094a784176a8520dac356aa61e50a64f91a55793a0ff9e5c96714d607f27abd31046971352c4dd5b09ec6b5e612b7a5783b5b1d860a06d998ed179187a6f853015f9880c08e2de64a44a0de09fec4a51d0e53d342fc774612a1c53c48e6360b0397294e34cb931d73bf75e20c23576d27e18f2a6c9993300ed576041a5534fda43bddc4835a46b6346d764f92d2d0dee024bc48974da203512e8c092dd22306ccf1c7d272aaec36d9fbaf23882a9ebcc62a0c8c713111fcb7ca7a8eabed33fec00b530b2b755345bb3752ea0d549f6ce592f46c65914272ea030f98f5df33e53a9c9abe37e249b6dced99e158e12c088b642f027bf544af8d62243dc141c968055ad55133e5a0731f39ca5a74432a622465024d6a01b41c97edde7a15d74e8d59e5349e8405549f90346a06c65cda1074d4762686fc7e38955e7741d01e6a88e8f941269924fa9e3b5da11496b168688cca7d7489115779c0d9b8d1a07ef156de173576011100d3c8ebfc78942db7c7a7148c46a550fa99f6801a2dbaee677d4363d0711c3a1a038ba2c091107826b610c3272f8d6a6f7fa939375c80256212e4171577fb6b7005b4de88e62d90f905307f09e62f91fd4bb4df9548e0e997d92b9611163752ddb663f0ee18653ad55e37c4563f50c396725a9ca7f7d934112de9c4b8e5ed532805154585b6e6d27a1357c9fabb5d76479c6f03054e2a9556b97ed59f46f4b7cb11d2547d3ab18f25f37181daea52a59ab5b97bee9e0f14ffd6682ec97d4ca0b8bb555227266485576bd2be1890636d171bd78bf4b721627f7e696682556b23e1e6803edebb3b994292f62484a303d3e83ed1194a860c038d53216dc61d902f3ae4171907958495e495c476175d111658476aa7b7bcb949cdf7f48a2ec896b5d00d9a67b9df0af801a803462f6c027a7923a89e0d5d376a8af9599dbc6e3ac332cf6eccdbbe0869006d7388ae27d733b24d98ca18ab7b8b6e4bbe378956afbdf05e9f10b99214568ea06dedf7880528ef5ddda48c8f03a101f508200c18dcd9694365b4e0454458a2fa04295448e8bd8a45f4f1ebef5023a2a4863fb44e155e7a715e8ce45db78c993536454e6c6cca0242090d26060ed7344d348a590b8cae430aea3a1477d2fd454d1ea4140f33f5bea5994a133d1bcbbc477b072d9c664879a232e8bf9af9970ee89b2a50f892c07e3c308c4c102403921b6ebd944748528acf1cfcaa893cf280e48c5ea993483941b3b060bd497b43e40d6139eaa23f865585c8b3bfbbe3f1f754431029820c3a0969a3402d68a3cadc7ce68fe4a4291b452515d1c4d41a101b95a0761df5f11e7b7108e162452b367362134f11ffabcf7784876a8640b730ea1ab132b7057a0a34ffc514d82ccf6ec18ad110dd8625d7d15348770bedf7be70bfd2708cf557c6d54cc850ff08b5c0064f5d23c3b901cf4da625e1dc4a1c94b7deba34b56f7f4f956c6871367305c7bcfc9e4e413ec574f31d3bb8c693a18c85026e9c05156bc8b8d9152d9f63bdf896b2542227a5fe4f8f378df37aaa116f9f7c577e18c003c3b69e96f818291edc01ffe467e77dfc48ef155924a020f1f48be82d2ef9fddda0a3dd5ce3ff063a5054a0ced1f1fa94084431874f1c38f96ac432cb91de541b9af4a10dae1fb8819e86a47749e15c31ca56e9b5a41e6c776c1a3769f4706827541c6266718c5c940c79a8eaebf4c23955098a2e36a96a5df40914556d46352ed715c1e57a6550d5c61d865a8e13040dba5095a483c4991ff030d4d9b284d9e332547344aea26703a9fdcd9868e39563bfbf65ec73b043d55fd00b70b1861ba69fd7121a274679cef420d4e789044b18ca0c5fe7b9e440d8f91294e8ba76e95afd1ee1479d05bea9b019213ad637881780c2035e4e083203eb03d0cbb55bf49ac2865dc16a42cd70d4246dfd20b4412bceed9f8ec24f2e09a5ef93927cbae7f354fc1c7a577375e0d0658fceab7e536f77013f7f1d413eac2ca12c2c646ec1f635a440af9c612194926c31c0e65f276ea070925f27fc30a7ffd1839df851fa05175035a5243b04427af4d4ed8cc39d191f849db06c258b488b385fe7d573a7ffe53382748abed228666680e7e38522be70111ff434da5c42da32ed659a32a52842ec72cca2c4225d4312da221cb7f4a9d0cb0e29ed6740ed74fb439d565864f729939f1530060b09eabb93502d3e914265a9c8f309c1799434933e67331ba385711c9944d8243d2697072c5d57b59cb9815e31e0db47847745663ce38199b8aa750e10195efb284d48d84f898d85cb5c0722ec90c17df708aabfe8486197e1e5f68ba4bef658214ea167e547c4f071d7ae65b656122d65af178e51e4591b274edab703b7c3403a2271b2c2075630e6b54823e9faa495f2d3e463ad4ab2c3e24414239906f2847d390875898844260f19d4dacd6a39edb2d384d90b9cd696c0eedb51e9a0bfa2b864299c8d0a5c1c7f42b69a4d277e30bb0efb465069e4ff56cb6c5a0a53691a9948d509e768d9332516de50ebf10f48206aa009b656be0ba6114764465d4214801ddc2aa2b9f464b234eecfd285b25f2b7995cb97e64895a3e32779fe08077039f7aeebf576ebca2f004c0313fd78dea752d0f0df5a40abc286b22d00f73496be431ee1a5c4029b1a9529b26b1b360a1012669c40695354739860b40a2872eecc3ee8db7f4a75260933465fea03b394299333170f13de53b2a3dcd03f598bc9b36ecb4cc4ae3a868eb62f6d5f197bebbb57ed5fda5fb9bbd3dd4fc8dd015a5d7b6bd9d68add4bfb57d99e72aa2c593ebdf8d1a20002008727c8918d8d4093c4f181a610a845e307099a19b47c32605b2b996f386df6733abe53a9291da54d9fbf9fdad1d488144416bf5f4cc0b5025ac8e2edfb45d83f815e9f63832800835a9dcc86235348103361cc735b81725cbabeae53a841b41ccf2ed4e5422d924b022f841b5b4d6418f4f7c71e7af5805e5fa27192d8d8d0f976702c4b40b4c326ea628c2f08d3cbc333f47002c15ccc0b180bc3a51dd4a38d146e592de60c9fc6a409494cfa4eb845e1845f4ff1cb4eede09a007fb6b06c60b45577235e41f6d852ea7c3e70ce8e26cc6bfbb533eb78de72cb92955a41bb40e04464580a6be9b1ce4d1d11f2c6f9bb55618fab1227363dbd96e3d6c49890ebbbbec195f9fb1ec6e87ce9b0464333e4d7c40d2dd43c5f7bdf78d63568ebf83ebe6375680f38758227e68487cab843182a74e85f2923dbb74bccbd157058bc10ed58360216a1ede3aedcfb60fd26b8cced3da5cfa69f674b8d8961dc054a70c05a7a05ed01b3421a0593c96b651a12a819040011b181b7450fb86fa5c71846863e436fdacc81623b832f45500fedbdc07e67dca0aeb500c0c875c5809fa7babfeb9262b5fd86795186c3dcbc10029a892d515e405655710ecaa58f381e5edfc99adc3a8b13f0cc6eb0d84b56122bc735a8a65582d7b59bccb56e0bcd7a9132aba57a2641602b19e8b8eb80db47cc5c15cbb2a906e66c7184aedf3cffcdababb5575afeaa45b57decb57bdddda494294919c504030512053f40d9cc6616bb57a7d315e9727e96b35dff2ce764f396cd4ec997ffff0fab2c84601c89b35cfbfc00fd2c71201dbb2ea99aa55ad16b2e781e0ffb1beee63ff63dbec72522e5debdb5d69adaaf3f4ee3f0e3c69718191f57fb4517a0d0f901b72e5f6a7b2d2da3c3a4c0db17c486cf01b696e4c367b94ea51b4f3c5a48454838f8ac26dd80d40349876403120fa41d483927520d54ac4d6d120d3eabeb0c3eab49e6677525c980a4c36735290624229fd51506a9950a0b59e9d16834f256dcb7fd15d9fe9ad8fedafedafedafedafeda4293c96432b1b0fd35b1fdb5fdb5fdb5fdb5fdb5892008822ceedbfe76b6bfb6bfb6bfb6bfb6142850a040a15aadb40e4331c54d91e2dda6186d7f6d7f6d7f6d7f6d2d61188a2952b4b4a850b182c565c1e2ddb218efdbfedafedafeda548cb6bfb6bfb6152b588c238bd51a1dc0b670715db878b72eea78dff6d7c662b48dac7f22feffffffffffffffffffffffffffffffffffffff511445511445511445511445511445d17f144551f41f4551f41f45d17f144587886ab5aeed7691c4b1ad6881884449707777a7edaabddc558772d5f8be8b92f0595dddddeb7653144202bf3b09e7dd3dbfb66bfb17e7c739c475b7ebde6d97b9ce4464e28d4e48271f4ac974baa7d3bb3d9d3c3b4778ce1eda9906012d094ab10efc4a4daf9bed3a45876b959b157a6cd7206ce5aeacbcdb95974e4e111d73e734fff63efff3b707faf45158ac878f184c56c244f6f26cd77e77fc1ed9aefff23ccd47bfa4219fc8b994139548241299bcbcf4999baee3a7f3a4270fad86bef972daebf57abdbc86ded2a0ce344dd3345fafd7ebf55aa1a0a0a0a838a541fef21b879d3cb41a7a9ee7799e30180c06833d6a7a699aa669ce4cd7399d87fbf33ccf130683c160a6699aa6a9877c2a4ca7e5b1345b4361e7799e270c0683c1608fa6288aa241fef21b87792c749ee7799e30180c06833dfae8a38f3e1aa2522850a854ab950ec3771b96605cacb433f3c6799ee779c2ec0983c16030cb43b335f4769ee7799e30180c76617f61ab950e43514c91a245c555a1e2ddaa40296f35bd699aa6e7799e67cc96766675cef33ccf7ba61053b4b4a850b162058bf18ee3bb1d3f172ee63bbf2c6f7a631cb506c194bb7d295883f40bd368218c6f1aba69be29bee9bde9dfb4a565b65daf90ad20b25db398b1f06c18c31d4707b0362471ce76cd325bb7d57ab72dee0485806e5b6d454cda2974f0396d97cf1a3de6d4354cfb381a2e451aa6d14e38eefb4070aca35d8df8568c9ce6ae945d75f79f5f0f0852df9e1688a74104e7123b6a6c6628b144f4815a095409daf676dd5122fd682cb8f7de7befbda42a7c56df7b00168c31fe7bafaef7debff75694ef2b920fd82fde1eeccaef553df7dfa336c84d21b55d8cb56f848949825add257efc70251cf9c103aad65a3fc7791c8fc3df8c433e32596badd5fef938985088e09c4b2213144fe4ddf21b79231955d71c4e461f9767bbce4e726cd7180c4c397ca398873f8f848352329d52508860bbc20f1512cee3781c9e84f3f7de0b969ffd8f65c551cf92c2f1261cdedc55a34632aaae2fde44b5a5be0668241fd9dc55ff3f0ff9e6abc3bd45942e34c06031e52cd79f0f1fe9826cd142a778203bb0ad07446ead576b4dbc7bce3de4367ced9e330e5dee3b37b1a2179de89be35a2e07d168efe56ceebad586277759bc5182701573a11fa2b39c873e5f5cebadd5ef977bd4ef36b745d8fb30b4f3c65bc479dda89a6c9420265b24f2b2787296eb244edf792ffe63933d84a8b6e82083ec85569c2a94b92e74435774213131b2ec8c3b136badbd71ee42897cb072d5f8c2b25f74213531b2ec5fddd4e78d1f444d02aed58fe3aa60830d52c017dbd2c74ed6c9ba59575e51eafef7e2bf18e7323bd76551373a199da0944ca71415a7f1f0a05ef39ab57eba8eeb681d829ee3a69b620a1522b3594bca0f76d949452753d1cdb66b152abab2e3e98e743c5e6c05d7e15b5776a1c71c063355368947059fd5a10caf87947e2964783d2d2c4a5d19943e4b48aa4acbca021012243e3e3f3f28fc06456a34c45ffe4a31a2fcdc6ea9144a05ebf341d1ae448952140b8bd56ad1c245cb22a9d558c616f8e675a5fff8cd1ee1e1c18f8bf8cd6f7ef39bdfb08edf6c0fcbcac8d248650b9a477bf16224c3ebf9ff7fecd5680020952e3c1ad7c321e17cec8e8f7ee21bc8e9dcf0ed05876f5d6867b6e474dc85b9220e434282c4c7e7e70708c422a9cca1b3c2688835cd1700c0b71714b677a54713a5305c3acbe3e6a607ccfaf031e66275044000780230dc64a8000e1f3320128037002f95e21bbed170e1466a34a4060a25008f947e2ff886af47fbe964b52b6b664825894692398c08a82b3d5a577ab4aef4685de9d1ba12254a51fee3e9cda3d1bc1e87e9645d39a32b6d442f322e8e816fe68a703bdc117cc3377cc3374ea793cd7cc4cf3292f1322ca398981d632423c7c02e936b126a10df9e058483f09216561b8b385d195d0e1373fa6008e071319dac2bf1ede6f578b4241e4d4400fb32801021e0c51a871fc7a9a008213f84d800e5870f3e32d90f1f92c87e608caf8f0c63d12b4fb21f1eb8b21c57f6c3881099101e24d97729994c9692c964b0ff4b034e0867837c644b66b3d96c360bc992c87a204b220b2204889024b224b224b224d65a214fb0b6066b6bf871421392b831018925f828c15a2b3b2209a8113624203942ea4e01e5b3f86620c0ef3bc18e1d45a811610845e0e08043acb5d671d698e3586084c738671c20f761e7df2228f7ffffef2d2fc65e8497f7bb0c04ff104409e4feffffbdf631cef98bf8f2be57deff2a101aacf5ffffbff73afeb2340111c27c9c33ce7d7b67387f0369f65d5796652ae4924aa5522d2db596f2c3f9eebbdc2ec779115e86f292d96c369bcd42b3204a2058cfb31a443904670a384194419441945608ffff3844e01cfa1f4188fb1f01471a8244fbac0e02107e70c40742f480e7f4ff252908120f8cec00f1c30e4e14a91b646ffd21d623009af4f8e85fdbfeb4201ffd97eca0edaf137a1b6c72e5693ac4b9b9c9d08b16e4e3b39a0cbd4c767423f6cf7973e35089b35d8f13763eee689709285db5ee9ebffce52f2b0b17a82ccbb2ecb2dbce7c427652ea7ddcf51a890b331167cd62b3edfa63eef2e1305927e7dc724e8cbbf9e83a76be10a2da427a2629fd3ee775def4d1f30b87bc1e9b4ce5b39a5d0fee68b8837567d7c3613c5947cb6747cb6747cb6747cb6747cb67f64aff7c9a1d2c9f1e6d069fd77aefbde0674747f89663dffe71180ce42cd77809900ebc84c7f69a7b641ed947eef1319c96ef5f821ba7a6b25c229ba91429fdbc3d863ce86ad7bec461f28f3fbe498e3ffef8e6bf9871b6e617e952bf23c35eee18f591e482ef5241820449050902eeb018732bdbbf0a637900513f8efbae92d22f45a6defe8dce39ce72257cde0d12e4aed91287e99020f1f1f909ae5aa285a59eb05443452292a92499ca52faacac39ae947eda43530c76032bc54cb0522cafe895b0943a8adb6fc5dc5a4fa52c3b4fa4f64b33d3cdc3ac74d626d8c3689db529e62e169d752975d78ac3acce67ada05f4524b21689e7f8d8d2a9ffa5ec8e64b47c609c7328c4715d27841d1930784c4e91c8f346a3169196d99ad51759b74a1f3218393074aa595f64184464c0307dcc9a343ac0dd30669ff7d5e043d1df4846cb6c016bd1c3c78c3292d122e2630e855a331f73cb6c95a55228841261f4884c1e2a84710406ede4a14218b41e18477ccc30787ccca7534a8a4acb878f59ab8c64b44a1f732b565f64bd0a5134561aaf740e1a1da268687db5ce4173451c57ab441b1a2a8d451c58abc41922df3f0a101f52c0e4dc2afa8e5a78bd8269f6acfcacac7d24c305531982af14b6cab0357b6bbfa29c901bd8a197d45d39584250ebb0d2acc24ab312bea43e66bd3ab3fe4e56e7ce76c5d46357566c0b13043c40a0c3ae846c8c9b183cdc6571f0923ecc4bed5959731cf9ed979a8f19dfe0ab42f038b2c997dacefa25751719e30675179969286eee225f5ab11879c5a45bb1ca4248671d6c725573d921f13f4cd12be1b8c1b0ee1542f2758a5609c90b9cfbc509784a8fbbf28a9488cececfce9e910f1cd9599b68eeba3b6b53cd612e4d2be62e72555bd5dc856a41b545667db1b36ef970d70f29dde5c966eeea88b44c778974d6ad1c77d91e663e25bc710a4d610cb614c670c9566c67fd8531dc4d0a4f5ed29df5a894922558c92c9d3b974e24fe5359304bb487e168b22ef1f894744ab5da2633ad13c95229e2d964dea437db9904b99034959956ba658d923e4ca541097a956ec0d9678d5273573577d63977d6f5d19d517e76ae290d5f6d55da4555582b4b4459c0ee7c535f6411ec552d850a1f898376d628744adff03193abdace1a07e5d7c3dc5416ea76e15959e3b0c6b372c8352a1432950eb3629ae5b8abeeac4da7bbf0ceda74e4459a7afca4c1263dd4091e6c12dca4a9dcd989d8264d3b3b9b787636d17636d576d621eb9996d6dc455634a3ee222b2df7b8eb717616b2492ff7b0c9ecb3f38dcf28422eaccf729d758d87c131602a2e1305516d11990c3959e1759df7ed9356cba7e30be9ac5f93a31e39ced5bd70b13ed1334e0a75c4c044a44721e723a7b97a398f316e6ee4b4a80b533e725ac469944864a24787e95217191b77e1cd691a1cf7b2392e06f50728d876d5e1d0cd699149a739cd7d607309e09176a173baa62f57c2a3e0976079686961620636092672d824c8040e600e4c9c609320c884089450e2c44d0914941c61c3e98621dc408910ae83efdb7547c90d7ea0a405402841c1f3a0040446042da183a09f4d824508320204c1a01e5083291df443900c360906a1a0862012f80cbe13134d3c6193a0107493d5872584d82478c4123b6c5b852566fbacb19901ea518403481315e143870f460cc1061184e0c7043b644b18819022e8e00499771a21091f920041021f3841c30e2b90500412487c8270431325a801450b9620c11235bc7bf7ffff6badb57e40c4b96b055c41e5a69c4c25948f7432f244261d17caf8be5bce5d75056439a9cff251ecda5b6badd56badd55e7c5f22ce5d2c2f22ad807b85acb2537628c495752f374200b7f3ffffffff7f28ac34a653feff176f7d7135ce22c9817ddf1dfbdb9eb39be531178243be10c78d8654ae331179271e7742fa503c93c799bc1b72a18cef6fd18a9742b174dc0d65ad2393c9643299b556f640ac7ff9a5ffbf976009a2941c96fdbd2f2e2e323131177f1dc71b3235323532f5c6d4ccb8d8b8c8c8c81c212333da941cac5b5d1fe0d6fbd4497735f85c93a7cfaea05e1df330ae6dcd2df85f5f3166a66a32a66a19578d99899989f9fa3a66a6d69899fa326a1756bb98917151e3a2c6c4c4c4c408c139135d87988833de8538059f87b4e319346cf4d4b041c3468f0d1004411b346af4d4a06183860d176a646c32605f3aec8de58143869e13d096b12fa01b404032f480600ff811d17382652a94e0736d6d3897904c0dbe585fae6686bd363533eed7ccc0e28c8b5018b35333c3c605d7313133acbef6e29a459402f0ae4674aca0ee64a0ef085b4929c7715c96a991b9ddbd575f2deaeebd1d1792027e81bf5c646abc66866bb2eeb8b6f1f15d24adbeb9668604ee0c775dee5eeec319363bae6dae7bcd0c9fb1e28b0b4f2bb023430670b88b64293ff2eeaf4967d8a42ed89a9dba438314f615ed2b0b899999b1a9b199b1b1b199396b666cf09db191816102a7bb9f3368cdfdabefccf94674dcb06f3dd3d00e3e3e3e0e30ed6a64070d4f885688f85cbb482c7e2212cedbfb811ff89d408c3f10e78c43127c3afdacfe6b028b8e2f3256a6c66a52c65a91742de3a44c8dbbdc5dd7ccc8583d53e3d6cad4d818172d2e367cae49972de3b27117f936fefe1ba181887d65f6cba029b8b9868f302f217d4337a4ef02425a0257c7c858fd2fdaca70dc6bfbfabee5441be3cbcb23b15f43c016c0f5f7ec7fa9e1aeabc9bf30fcfffdffd3be8f957c1603211d038cd732801b555f28e09f7862a522006f8cf5c52bf5c5ebb7db1fac2f5e23007f35b4bc13518413edff1f8b39b678e2f1d16eb76f8a42bafddc8e6948a5da876fca1bac0e690f431412dfa8b6c8470dc034f8e6594ed2d9de834e65019386d84eea41e5483bd87eefc521f144c3b31390af2e61879a0e465830848d46a33759b587dfb36ca875585df5e7f766d308b74bda248c3c91c4a73b4d1cf972870a4ebc769488ed50e19684890090e0088142133b4a72d4a0040f1234e420840e2031aa0a6bedb3a04f9c261d7ceda33777913888084ef681c126732bf630dc0d184c32d36ce7d24722e65e1159e5c8f6aaf431af661b08457472ea68556bc566be1573976bd72ad3c7dc92b9cb5d2452b76b5335f3317fc5a5aae22a2a60007a5a3c2ae0aab642ff051607a5f44ba96832df45d2a4b3b396f1342e8743140dac555a25e2f85074f8bc059569a56cc52cf8da194481620bbec156cc6160b4ca1691964e6bc75dd588077cb48eb4622db355a475de56ac45db59832b945c29775e217197dd810b36e96d72f5a39ac5807a21a54ae94a8343d40b286ecfcadf8e4fa5b1ebab43908366a55521cab54a8720070dd67aaf6aa1ce816200d65abbd6228e18b84863a54311c7d52b11e5bad2ac340e95c622ca57a17e56ce27267c642ba60a2b8d0e556d8b48a8162083dd2b1d1fb32655e6ceaa223b6700a430cead35d99aedac35d6aaf3616658a974f617510c58e9ab5f58e92bce308316ebb356b59df50b77062cba8895c6455245db79853e2bb3d4e02357b5ac57e8c3a06888a48a67677db15689aaf35959a72f4135bbaa390c0a7385feac82b40e77d51fa69092a594c7cedeceab253bebd7cea4cb2657350ddb59dbf7525a0a7217f9522b019da5f4e5a5469a4a55bad22b110716512fac6650e995ae342a55d0c3a068601c5744bd70b5d65a9c81068beca8c60b2a51b51255e8b3f26351cd2b379969fa86bb524e3a6b18dc558ae540d150a9c4124ce7a0094b3acfca382c99a59eff12cdc794b084a484967cdc55b78f8f26a1eb94d0d63c957ca8440b1dad51c886060010000400e317002020100a85c128cb61184653553e140010597e3c545e3e18c722b1502406621406511cc3408821c610638c418a21650e2900729c9fbca5a718649eb11db1cbd19688a9447dfb4b5f80bd3a74159317edba42edd6bb97f805283374c8fbe3a64e6cdf4833e9d1082395459ab58340bc7854b4503aa8d7877abad807899f05e9a3e55c4e6a0f84b2b1838494176784ed67bebc00ab415bfd34018c13354d22e888ac3544d0421c63e65c994ea0a07aa53622378717c12dcbcf0a1d6c21cbd5a1a148c4c359060a61da5165e826410c5613fbdeccb4ffde95a167ed34a223411e31b7d662af08afb9cab08e04ed2a93a1849becb6e9ea31777e0a28c33dd8b5521aee2929e73992dd5d4a89a132ef980b0fe3a38656b0773df475c5059a5855605ca3ecb88936979b3c0e6fe48ca73a81710b3b20b2d5d387b64f9ec5e3e525f116b5e092f8233b684d9ab61e4a24743ed6dcf8bcfdfb6353d2482ff7ab1ba62fb697d08229537ecbf712ed7dd1c4f658fdb259f614477dd9a9e06536a83c6653fe786191462a19e3834bb96c5c158b472b80ef5ef0b3aa70a05cf6a5172b39bc0e2477eee294adaf2e38615cdbc106e26aa4d9737c558f5295d311fb1c9a40dfece30ed0fe5a9b97d8e422368f8b778b0ded31152fe7cbf350192eb79e2a4794fedbbdda1918e35a7d1bb0f95ac2529a6eb9a06388be383144be62d83626651d0110e0a03a4485bc5d91bec2f17a83abcaad89195a777231c1617572a5f3bd21f56f73c57cdbee8f3ae49ef3662849bb310bbe3c4f2f73aea43a073065fd2b608fb53ba6ed0455fbb094d380335bc708fc2a5ca07c600333c87f73158f94a681fae4d1ca884ee9e2f63aa3ccc0ca944665d143f8df34d3fa6b18f559b1794a175ddf001ead64c5b0ab6e560f507289bdd666227afc94620d77b83a2b8ffa851403b5b7be9a10f664cc1cbd23bf57e620d4666946f8a8b66b47416e9181630563d6dc10a9595046e4c392ce355a7da7c52fd8429634fedaa7ab602830ba23c0266779c8291d99581ca5f68654f864d5309f9eb0b648476887eee943fec0f299189dbdaecdf398c8bb7365a801497290d6fcd8ec8c6adec6d8967c543d29cf631683f3f2ba61b1cce6bd690a8f1670a31f8a48f4c953d4611ff37910dd04ed8afd8a3156488acbc0da79cd2e5770f07335e25e856f12c727b3dae11d842e7b6e35428cbc08f400aa26f3b2569f408fe6da869c2c97f60545de86fd08856ada46098a69d63db5401e2b40b9e63032fce0c23d76bc84ad9a51e280c5eb6d028f51e5f8132984c15c6875fe6fc965e7ebc6831a2b6bf950973ff5bfc5e8fb2860cec937afbae180c2004e28c2b4ad1b71cb2d83a28a88293cbb3fdb76ef5acaa2bb634f92ab2c86ebc90a427842b443599d2d52502c9ebf9d90609303102dfbc56eb71a3fe96b8ac5e02cb9390cfe5537e187a30b0462e6270a81796daec5d8308110c99f454918860367cf7cdd49b3520617a2f33f48ea3ff2bf1c78f759d7b1fbdf74fcb552474806477ac8ed20ea55988c7320baa7e43e037fc6d0cd660d285e79f2aa2c5c796e82065e5787e3ca0620994b8eb6c53d10277edbd4ac819f94428a2b29bca1bc114eee3bb512304e24b48fa0117f649cb8b2120d2bccd8b42d0d576a603832bff449e2dede95dd05406790153c9df6b7520c35a1a5ba3d935342b9e8a4544a2a8caaf4184bf9de3402b3694fcecaad2cf65a659024d896e93762f159553783d0c70114a87142f0b0d86f8fa053b13ff5a8177ba253f2458e1fa85259ad7854a811ec97c5df737854bb3e15608a3a476e623982c6b9894d3ece9ffb2ac812d479ac85375f4d04a920d34105e14b7c1077083225e38138def4d6ddc08c52ee5b16212317ccb34abef4d26203567e2884dfacab51abff9066b004e556d1bfd3490d29ba2f0c6b1dc8e1c5ff120e00e91040c2dc9ff8a3c580812d1649f0edbee447805e972a3939318b6231046808ac118d04ff44e5a59589882c03023fa4158d017d20605a60e506968da5c3e158b1fe3d0d8f665d14a2fb07b5bd8ff39fc60f5f791c0c648d18b74927308b8197c9f8abb00eb71b5af17c8019e4d812ab383d309d652aa1d9253e2983eec3749b7b996a726bee55c8385468daee64afdd9b78bfa9b234bb0bdcc8e35a91eb6e6e5232b672210f6b53d58317260bd50da22bc9c3d07c40320af96ae30dd239ffa794e2082dfb6345a3b0fbbd79f25fce4c5e22423865523a5e510c3130b96a0a81ffb8aca928b1e615e292d2038212b5135c595f9e84642e1646a358bbad9ed4382ae6dce6f8ac27cb3b725eb3ec6073c498b5c70808e1cc522145bf4331ca722a19a8ef9812334bd1b2214385791ab80f5fe85c420232b5d9d3c33ef49f405c3a4781fe6fe82d8030b9c6de5cc171b273274d5e9c2f2de71374537f13b78efe6f44b371569c63d0531b3cb88f050cad4250934150666d6702c17c71cd5fb4558881d80c43c486e007580bf6df5c03b358d36bd0c6ea92b0b1a2a68e397c7694821f29f0968306466d6248364febc56fc3bd5b41d3b0760db252aeda1e336c18aa58207d90314e68d7e690306c520f88bf0cb4a800364a605932fc08bd277281b5e6daa1cf480db499dee86f6615638f75693a917ff896616cb50c5cbaaab995c1c2aa91945f280f2b46d97f4f8074e3b53600e896f8547fe639ce34489ada2399acda8ba7ee27e6a83c212b745fdb2407df6d4660756a8b484fb22e310cc98d30966d024ece87b1b05dfa96ba7e2aaf4b01995b0e9c98ce06a6705e23d90f94f9a0037f914e88c4768347ece22450a321efdc69e0be4a9ed47878e0c43f9b1395a284047ac9a00215c7c1a0bd7bc3ea0bedcd7f97063554f3044cc5610e4fc902d7bcd1303ff7dbebbf297246fcc7474b95454e8a59b75e238b7903e90c0ce4662bc70d0f328e5ec4e4517ddb51ae68b1c9807177852a490247303efd18fc5898a7203dc16b0bf072a5c1cd21e9601b4a404265a775eda74040d1c6f3592649a25cd5081625226db8de48359f5b3f287d4cbfecbde35bf802afa1e2713d81231baf66d950ee6b755dee074971d6dacb5a98a269804baa152798349e598e2b1fb0a8c3d8f7e8f5394b0616ecd186e7d3c94250d92b10867eee3eff633a27aa16f985d4b1c8e8123092a783524cc0c3511d255622c2f5af4b314d1a4a9969a575ccb36390a2e138218513f4cc603eae804b7a6992b0d2550b655e123ddd9857052f6f9f2b0029010988f234cf2ed1a467d1ac8899a49ef9b3bdd92bbea31b718aa7ed68b59e731a43cfd56b406548fa6d5d974c3a5325e49250555fc1a0ca680afdf2c2e325f739790d4e1661b8a22cd68c6897c9c99a21664136c05fec4c6116f8b1b42998ec4fb48b08ce108e41b2fa95a9c93e292e9040c1b30f796749c9baf91e52902bd201bbf764b62efc505a732b19f14092031c394551f816bd2bd841ef2d2e6231d3f648aee91a4f3af549bd4a481756747992ccb42be4be3a05442e418232b99fdde67c792bf65c4cfcc59e434f9c9b901f060b20edf25ef1324c7fe0a4834ad45d7b61f7f193d5cd28b3e53f7438fc4e1aa500d0a7fa9eedc1074b0d703059af6531a5bf0c4d90933db7f7336131c2f6147d302c65682046fc5f6cce7e9891695a93caba2515bef72a8dea4142960b6925da7cb70c1ee889af325b76198715ac8987b95165ac530c25975af615a404fb538e069f599716545f1dafc5721ac8471349025d1234f2a4bb3b3ca3fd48d0271fc04553804552259304bf34125e2345fd9732c8be39c673e35b7bfb4af94d290f1129f57ec332b4369d0080ae2ca9bd4eee99c144c48fa795ed5ec98462724e264d0361b51a0e1e262a5851fd1c11238715029440de336fb788fbd27638e2bda3f0b626bea1a2ab2a1c912ba7b8e21031f48ac319739707f879d5b0546c9f60ece8be12bf8295ef714cfe44da635cb4efeba2531fab003a4010adff3ab984d86efcb83e08eaac0a02ee77419c296b262731a8ae09f9bb5a018e3a84ae292693c027ab41aede46e8cbc5c1c3c5b74890d775cd89e8ec7fee8f6c426f228889e4aa546fed0d2c506555d07e47bb47d89425e92f94877adc0154980539436206873f68ed2751a6399d7ad7797f9011e98135cd89446bdbe392214d2762decc96bb7afaa9a256150ecdf5b43cc2b286a3c8b5db856e3c8440d8161c4f1fe585591e97c60c66f67a0ebf01df4d0060af433039a64fd8f60de463030d21096fb18f870b9376420e27ebfe275870426eea555ff3983a4b6c71d82bd8542a151156987114cabc32467e2abb9611e56aea12aa64e0d57cb02783f63fdd29f172296643df6665ca3c221abc6c3fe9f3f81e0d0d06f84c0d66ad9860d8c0da75633c98a3982d4305a8d7f69ee3815425660761b83cd06f80c6ef0076a1ca340d00429b446820c8764b5f6eef9d407f8dbc06ed1e9640a365466780fca5aaeb4b7380078c049ae98afb26a0218a4a7f6ef0cec2e2661aef875e2081df98e076ccb11bc882913b1d38e845dae677878dbbe1d8bf092befc02c07dc1e13063672341b180dbf2fd55f97635ae0400564e20f55b4dbaddf0534ac980f2bd2a5dcf30c0cf97de0ebd34c0ce97827b7b1818c09f4936d27df8d117c510128d45c900e8f751b0d3e5477fcfa814acc3cb79d674b0b283e8390f2476eea4b93614fabc8bc6bb9bff68a8f0be984035db1c36ce30ee1d0150694ba8d048257677a8d1204e78f7fe5879b270951635a97a2e8ce35404231daceb963f71fe10a2835e2ffb4f353ff34725cee9d9bec4b058faf33f89ece163ef7a53f773b52a6eb71679a86606c71e1bb5faf3480b6ffed0141573722571029e9b3d72e3f0eb01734f56ff8daa52a3ba27b922ac40efa7cff942ba9986b58210aba796153a031fa2eae5e152e1087836e5795d72da3d1b52018d7e9f8f5fe009d5e7703804fe663469a88e8526a362e7d05e8b69b9bf322ead56319eeb63707ea2f75c8e08bf0defef47d64c322821f2aeb5711d07e99a2fcc6d200d512ddcba8393cc5295dbf0aad043fcc393583846a7ef27eb5472a0194ec0fc0b95ee29e04f2d8eaccc232b914d45a7152877aaa2f8c586d67a948ae47f2600a701078d77633e1df21a4c71f705475be8b6019ad72da19d2768b3cda72e8caa0bb107add78c50d898b82d2c6d8d193f9e4f9c3d7fabea0cd7eef5960afbea5082e7b0154df27babd5275c0ec9b58e0d5958c5512ad089847005f36de4610da9081380c169f03db08c8894cc22868e9b6e87a26cba160e7a3053c4f642eaa19dcb240f353d26aca3c13c45fd609818fa9fdd7a000d6882100ab3b7d9f0e3bdfa591a8aace5567db9e3f68c7f69ee1e564776e345d0526afa937a0ced5c890c41abe98ac1bf551d209469f8ebfd953330f49085497984dd63afe20f3b1ea3816718d1b167c54d1023d450d33df9958d796c57aef9b5874937620ab02538367798abf042c87ba4a8b2a365e2596dceb4ebd430659a672481c6d89f211ae37b70cd7fec12d8765660a89e1dac4d91a4c7e2868fa608de3fa3c695859481451be755d9a428f109fcaab14d63d4d992a3ef563e57bb79820f804e827c1bebe1c06d293523baa6783c1071a1cc89ab31c493e969d4970832ad17f079b29a23bd35fb48ce01325da58967991eae5d7cc7e23c25fb3a7977fce4c2152ba71692d04f46e24ccf298e6cc1dba33b0252274eb315e5b59fd6c0a82285ff62d1ab4bd37860130960535d762138aeecc7b4b8144778690af375e9369a137fe97241cb66d58861811bedf38a621e8cea6c94e36747467372cffdcafc928508e8d2c1813493b6e0a124d44eb0ab78575cba9dc9860792d9c5fcad8ec83d1978c45091c0c0062ef7823d82379082f5902b3d3e81b263a101bda9daedc1b2b6c2f6f8a19fe9555f6860723a7e676c166c01017820c5e8db1941eb231d7acb216a204d0cc99785e989b0638f2ae26a8495d71c2fa9c1ba5bd0f281d8da0c9aaa488efdcce8e82ef92186dc0d8e49d2de76f36dbbd8e11b3a62bab9a345d59d454d39555cd9aa6ac6ad67465a5264d53167526e48c58c6223748e2d0d04f917d48bdf4a02a4184ee7779e533f33c9404d04321fa0dd5d6811099f173cbe5e80c587f220ef486011c30d60d6bde85f0c24d8d27b6566d6752b72c86cdd0cfecb8765a5057281b54df15ce623734acccbd8cf850781962c134651d52c55f9d4e68babb5ca1561d1af41a0710c3c3ea826a26d683a7776cbe25df7399efa09812d0cd9f56514346e7c747e880f6e24a6125106165c52d636c849256df49011d265d41755e9abd29896602b643d823723eda3334a8cc43594d67c16592a656c9414b333de75a48ddcdea68eeaeea6886cce4166097b1f25bdcfb464cee4a3a3b87a9acd872e8bd78de09b633949a62224721da63eeb171dc75ee4c6e96576aaa26bf16c5d5e40686419a881d3c277c03e504406e94ef4db9553f241f9130649807ae351503a824358f6e582999c8a424f35737de4c75dc54f7747ed633498f1cfb8aa972dfb73b8f99d9df78af295fe1555bf4fc5cd736ef984319bc0beddd26a4e5eb82fdedda9c92d79c11287e9c631f6270f4655c74285d791c130ac2fc86e097c8c078cec8c9df24a32ba2e6becb2ff9237b241b95562dd4690c82d81986c1dabd4cd2bc2e6b4fad3a8310a92effed4a76034171e4700f2a22a669d3c8d4db15da0be009d8b0e1d4137af959cd3bd602056f82ae929038bb308200a5ce7ce3362e14d5c78bb4e3526db13a017b430b4c4c4e60934533162c6fbba684b1a3fae7d803c76c8b428e3b41945e1be9a7fa70825c57fdb9c6d1c741c5c36f108d08df8a0cca2cb546a250608e2c80f511f6391500d41f5e61e266fea2ad1a367d46fa3109bc740473a6277d341156b38eafd7c91e6b44cf8f7d953f96fe88a7570055cafe60389606315f7042f1d34b8e30f33ce41519dcd8ec130c0d282e9f44f37d570e3e3cd39f740b5d73de47b344b81064742559cb744f48231993cfc51e8882dfa82ae09f34e6dd5826a622a1b0980edb06d20c288a8262aafb937b586e9b5fa0105df1f65b3cc69b12168d4b9f18612fb67aa11fdbc268e1eea9b4fdafeaf337fc110fda5ae5115037e43027eb12f637aeaf4eb7a89ecc3e4d0430de7d9297c74b5212f6f2b0cc971a7472d7f2ea3b6eb122c64aa4b991e1e19547fa1e81a3ac2cddbfd4200b3d1fd11903a8b3b21ff8018e974f4ee9bc5e5a50437c493c0e8f9a371d981f30ae8e6b407933571e54747084c877dba5c468ff249d4daba5795c1c40d4964d02d4554dda3e81d4e31257b709402eeb099460ae3af68eaec36789111b8b0b45c61d7ee8666e31818ac90a20b1d0a5265cf472177406cc61ea7db7fd17091ccd84a09df2673b21a5ba641f79dd1e5284f74b197476e77601e738db9429174a28a1392b239611454895b2c4a5f038965000a7f7197cd3163f8af6ab347a95be92ab90b403646c5fc9278f58ff1deb9f09bc19350462b27632827cc4f0437bab591fd3b6398785d1ca18afc2480e2d417ab3d4eca91205ef1feb9301e29096ab80da1bc2defeb14513cddcf7280ac723b69784f56434b8954c33dd68019b9f98e37f96d6ad2a4a296caa9fcdcd541b55522d8d7f9f5e69c5f0c648edb2e90bc370731f63174a803ffde95a8b1e89c1b151c98b07ab57ddcb7b2e0bc05620be1b7f0cc2101682a66c0076c16db0d70b74ed4069548c6e467c2db87b55e355aee6d9945326a64e0b081918ec6792796941d944c07f1a130c18af817c6befae4f6f6c0a3c27477dc2a9cd0ab29ca78de3373c2baca9f173125c0e7bf0e8fd44e5d82fab8c0ae44e0b7b5ef42897c966c99be0089f389d2cc661d78255159e2b42a229c81a640530eb92858d109b191020c03774ecc6ccafc0bcfb8d6fc3e6a80e255f224f8d2c089f0207d6361add5829ecbf58fef70515e94411706f1378bcfd0175b9354d16009b7c1f3506c7b1028ec968de6b9ce0e307fa515c58d7f6bbaba11d84896c1aaa83eaa7bfa4d8aec0ca528eb2d932d90d91207096766b7869009e2be031655f2c75246a60fca9383aec4a324d6314afbaec45c101e86680a08ff12afc70fb63ffe1bd8da5ab6ea1bcae8e51cd49eaa8d4c8db1e3dd2562801532bfc3ebd3a17f141e41a8690af19e47d2d811b9cfccca17013ce36874069120255e93d29aaceb61078a535d85021f9743ba2565f06ff29a3fab0dba0578cee6d15bca43184d919873e974ce00ecdc9ce01a00876bba5dfb3740b5c5368cd957e75e3cfcdfb0ce5c79806cb7d621864aecb01bd647935f3895aef7c44db6ad4285dd83483b8511b1d3dd9a37c36691748a5e664eb9edce38543b5717f328bb44434e8595d3f032346cbf27ff4cefbfde556e77d1d3a8f13b8c29c7d2747568051f3cc151797960178f97144aef996c4045db1332890bbdd25c3cba0641de2ec808703f2fec981c94c9d8b83fe570fe261a8b61ab6e1dd7979de8c7d185cf32143ae00a584b87f771f97bc437fcf5f71ae12a1b71b2ff50c3362c32898b6146deecb806adca684841476528ba56ef46c26056e96900b2ef545616969590925d444721a798e0944da281bf42712dc44933ebf94313cd6d4e6af9ecb45b43eaa571aca4139497ba5ccbefa7f7d43eb7c6b60f5870899d7073c8cf4a22d49cdc8de966eecb5d77365729a0f3e3b02d30dc68b262f626c8ae0b7a93c874f9e556d27f4f558d4c56d42d3f191ba8b94d011c552477312fa8ab1364201ab8efd3e107bb11baf9bbaeb361a32b175ecee0892040336cd7186a2f8a88335c019dcacc6be3b56d463bd684aebb0c2dff4eef39555ac12bbe1fff9f1b15374b8ba4268cac2e25133614f5bad4c2753c6206ca00fa9eef324c2fa218ed5c4f04c0f03f502d91f4e52bd591268aef8edad361e3dbd6ec097096cf4310d72c5445b1b7824020c26b9f6f452773472fbc4dd502e4f6326a0a696bb3c2c69e81a2c442df54047b31936a512f70d703e0883cc80c263f54d052b7f7edbd57aba48db2412fbdeca5f2ae9aee8535756626e8657271ee035134f74cde1832c4b09c3f2f48c7db440a7f6324c8acf8689a259cd41bfd744ae39fea64a58fa46a16d96de8458549d58429d35f6e3a0db46afeb0a9c8a4654bdb3e1129ff4a10e2e8df889e4f86fca5b7d5d1d78a0471518512c1fce37c931a006c1f597cda4657bad0a49b4f6bf4ed9f0456b52fcdfaeff98d6732721293e6e92f81a73854eb136bcced5f87a8e9938d31a7228a63f4233c68b4300cbc1eb4ee26c45a24e6dec00b9abc46805cad6897f7cf92306c7249d11d0860f2e2ab96892dc60dc0b163ef1bde5e921c06975ca52c1c3f1eccdb0d48917b2b9ae1b8ac5101335da2d1841056244a07cc59d053a69e05d3792f94efcf836caec764c358f7f33bf063859093092b2d46d79eb83f1e8b63044694e6bd20b83148d970b2ff938c2fa27b9b53c914198e0a4deb0f2dee2a8f4601d6aa137cce4fcab70e15874a1658a67249de2251916b1d86a1c65782b47e441ff5233c8c403185fe8d763bb72acda664732573a6693c24fa70d5a85a1c847ade03a4ad76fe17478c1ad9ce77c76209c438ecd1b1487b644377669ce9e6366a6c283961d449c833d31523366f6bb03268f3a4b0df5022608ffa7a894d0497e583c22864de78b3952c8b76a140e0844518d5e08e2431fd33847dc841e4367a6ac93caae098aaa0333e2b5b03a214c4b2072275f270a444fd449f7a2a227e13f61c9c012cd5a872a249565bbb4bd752d45c641892459b44122a7242f417c89cd3e3652a0567c42abc4c32d329096ea892f17086c0e1a320c86fd5417fd2343a68da47977906e4ad97da2c809b6b729d198ed5a0a9aa43eb7d551138f5a999c0aa8e2224c66f7bb76d1c2adb242b49bd6921148a03c80c1ab3ad93e0b8595ebf66eeefef2776fae630169b12ab633989bf47b4f29728c621dbc02c4c1c90cc3c5e38923d343ace41c3444954c6a43238ec465ed2849a0775d8bae40261f41a6e5efed96b4f710be9ea4abda24b80e7b13a2de24e098386d9e743207ae0db2e52c1632c4eff376c9a6f54e17a99c44b72f3d23b3f5a9434069750805604c6a6ce3a029ed177ab9080c06a6f1655cf992c570eb2f1d6863c98408cfece71489bd5345c3d78fb13ea45972a0966091de1a7aab1a67bc133d3e1a03a677a324f3705c07d57c58bc82d648a071f4acc12a45aea235eac7302f54a35b996b52dc550307a59e2500f855ca582042b715f2d472e0a88df995037b1b64480c6cc90db7acdc80e70a60337910970275d2104f09763f8362b13cb0699d079a9908fa20866585771df46bff0a730efb72a44abb22454d717ae3f1cbbc71d181082d7cb0dad9730166c8d014b21b8f5a294a227192dd3acc0079416698a8c671d9c73b04456d3cc6b29c29224ec9b8c5fd083729997eb1aaeefa444ea77b6aaa84b012c0807db2d09b932538f35b0058257e48fde95451534c8fd5443abaf787229fd38d63bafd266a42888669feaeb43c5b1d9b45dfcaac601ed10da7dced477f696c6c02fe180e7f5d71e1eaae9e4495574f55eb95e7dfea087ee3caac15996fbce05a8c8fa474bcd4d08f745374c0e9665011a274f112b9b8551c4e0f561e1541132c2de1c77e6eeeca1990046206ed8bce67bb7ccbe7adb799cd19f48f55a011c5ab826fa74ff8d24d95d30381d29a1ed5955af4b3bf624a112cdc56f8c120babe53e52b4202b7d501943e915858e0bf7d2c8b7ee71d46f037075fd8cb801cc9aaa41c34ce5e8689d7e59316e91bff6e2107d33cb598a919911a31eda0de547f71b40d85528516594a567baa516d0b5f0c5a934588cde8ddcf785c72c504e1bab2bc50d61979321a63ba0ec65a8e55924d87563fabc469453d7ec3c7e07c3a32dc93c75a0c6996c1b97a5540147990260635cb805635e6c0a282494f37d984ce7b69f2e8eb61581e27bf39716e1f2f2d0233dc2a12cbd2811bccd65a1bd28c201b8965cd1a9c1225354c4ac3c0b76571bf615aa0c1979d222697853e096b6d58508ebd2202d2029cd4e81182010e1342ca904f90d68fe30516a22b1f5bf4b31f5fdcfc57cddbee63922809a971e04978ae018f1357d87a5727a3cfea1a033cce98a95dd9872765692bd284aee4dd204544f367a2adad37c5ac65420db5c8876be162f7f2b758975541a77c20d7e4fa459668c6a9288d2c1160ded1f86f7e3509584c9b09d9ca435d3e254a8333ea8d0e13e23aab14fdc8375bb0702068292b07e061c35f0554851b05444a3927545a97df9545b1f9a152bc3f9100836db8b64494ac58659418808c5577ba5925e7a49589b495a9956e28165716c475657abd326f07cf1615beba638e2490add0f8778500ca36cf24b3217d669d5bf2608c2387d9f59cada080d1574a73b52809fa2d02c1415abdfb6ab95f3c4b090c2d88c30d04ba585d6d0f4f1a71c484596930d7a2246ec710748c6f6b517b773ecf5694ebd83826ff39d735695ddc71d573dee41a50d2c4c3029ea7d56897c75454f0842337a4b8956d2103da68438906e5c5dba10a027537eeb2bd9b1177f6ee8be3d86139c32be51e6bfd035c30cea54790fc832fc388a2505234005663b1727a1276c5d2e1abd2385b34929746448d0e5a880b44ff933c471eb897321bc5973567b9e4b63b537b5dec2b8001a45bac8110c275c3a1726a4a7eaa8083a6dd9ad11a84814c67bd626654b262c48921938103e472235abc3134e339b00b553252cba8121c982287ef6e28c2ee265fce59cceb5364069b2bcdb131aed4050e04440b9f45a6cdbd2647e3f828acec8f554ff82443aafa8c81e82e124e3712c68018ae55fe9fd8359031b985f8e8c37d54e359291354d4aa49a9fe243464ecc80119c75d19d7817d749106e9827163868b9a4dd81bb0720c09db4dff5675cd2d9f93f08b0c293cd2fc69e6f4d5b4b147aa71d5b5ab191b56c64fc3fdedb25ca1f1861824a299559a1eda89d7c924eb0e1cf154e478440faf8ba5d8c0d33ae522ad9eb2318d1157e9e8845ff2ef24a37b5d6e4c7c69c37f064c2414c0ec7eb43a0a3516f2f1ce0401fcc849ce667653dfc5fe305ea589a8cba15d48e52c910ae3d80249958cb7140ea45a668a7a028f075239de99fa4f2795fc05ec6713e0fa83cd234d9c9e6a2deddc47ceb8ab1cfa6201eb1ac87aaee02c29d89a8649dc7f76b54d59c24db2076c315160e20ddaa846598f39c0b7a3aec8ace49e125a500626a4b303e18d400fd57102b19fef8ba4ecaa3364947190e33a256779b97dcfc6a189f0573ea0c0cf6f027f66cffbc0b9063989e68dd96f8f9c074ad07b95cd876ae32eb496e58425e4a86c636ed4fee1e675f16b1fb6f4f5965c8d9c4a615eb8dcf8599c9e9f53cf0307ee5d89b8a750bbed9fb64926d4c15bb677de32b31168f8bd0b13d5b41a7fdbb62c3b0f66cfe8de4d0caf914deebbe1f62e4c54d372fc2ddb3bbfcc6c042a08fcfdb6173c3e5086bd15886b0ab5dbfef3cbcc66a0e1f73eeca8a675bc6ddbb3f9c1fbb7c85bb765f36f5e96cbde4d14268a69196fcbf6ce5f6e36020d837f7f5b1fc355188c4fe0002fbf8730e95520ae595d76605bd7663390305c5178c115da88e5de2ccfb9656633d030f89da74761c82a4d1ddeee120b5dbd3f01546ddc4e345705e29ad5fd3eb77c54dd95a6ca21ff1a0e82b50e1475ce950fbad38d6b5659318096ab86cf76ce930569fc68779e28c8dca89ac6985ecf8dd598adda9d2715b46ea8c66cd5ce3c51d073633566eb76ebc982ce8dab395bb7334f14646eac461ff8affa9939a2e2b545b4546712fd80f3de9c29faad39a262b555b454d6f655576a8b68a9ce10fdfcdcabf19dbd3a53f45b7345c569ebede64d6715ab2da2a534b3d736bed71c51315eb920d31f56b7415302811135046116ec94e1917358c06fa9ef19e5b92feee60b51e635007aae1436c42550815395b3ac1481a494cb5211bba907a71d36ca88ac70a6e814c2a42524628aa4cba483aaf6aa8517351dfb3638e639f5ad6e9a8538f154e98a3f8bdf11bdfb6afa6f96aec9af6bd57aeff50b226a3b924d01539868c2ac0acb2819a32d36419b4c9a086b3b6493da78a4825f2f0b5dc1f2bc3630f5c38d62e331076cd4c8c24c74c0d29f0594173b783b0f3a479a513aa8d4d640761503aa8e1a48f20c07accb12937462347aaaa231b9e8d07ced6a4a50ff8a3b544aa97bf472dcbd2ca6ddc8b45affb5c0aa426a807747dd8b7c01e15570dc5aa7984546bd3aee5ecbd19a62b41ab35e7cedc2b2d3ce101a2038d39a0387e3f5985b658cb58b69b539686a07af8afb045888a0a8ffacef05abef361dff710874160f6aa308d4c013cbd01c34157e481c0dddbd11572392e9b48f1cd83c08df213f35f94ea323b95f2bf4632dff1956a7d8fa29a020cac0444e6b245de718f2f6f4d723d29b3c4bf555200ced4d6e54b66d3dea4c5545268068223381124e4843eb066e7cc838d1b246db36953a7365567808e676ccc4022c10b1a2bc7d7053082a4bc95f1d8948253f48b2936a3a8675c3fa21a4147e15dcab7882bf4f4879b06c25e37826fd93a9f7a6c6c4fd6733d7c0db77fe5bb56e8609c4e0c2ac2467e87f7f6e710016da16fcab52501c0e090340570e405bd97bb5ced56ed5c84ee15d87b13432228d0e3230e786e13dad2564c7eb86b49f5ee7f061a5557997565f80faab593b2164d0e2e7f535fd2da6836daeb3f4f43c72080ccf1ee3ddb51fdaadd279e0dff72f17cf1bffae5fe9b5fd5693c0fa42671bcf15ac44823d348aa46182d457edb8a5b11e584807a4dae33e84fd7728f894db8f024d6f526ed6a7998c677bf137495b3dac50fd7d5a7d79b190ecc9e34bc977959f43e6084e433d6c7462aa50f50e7d7d05921c5b455fb596d057f3bc79b9ed63d224563b0bb84daebbac4d613f5c97fca2d9f4cba69fdebc7982220ffcbbb2fb81d1e6b5c2bcd63c703b7ee4ab988fbce3ec8bcfd59cc6012b34625d214be34e9fd4ba628dcf3205984c30379862bc63027a37c76a453af57b747a40ab2412a69a236d8f8b3c2b1ef60724cdbbb8cac1d553b1770d6d08360110ba36a067ed6c6160354fc7be3891292400719c20d2263f625f840c497532e3a0ccc605cc380e500dfe831d9774fff9e51618f14fee6acd743701ec94de76184915da7b8465273cbf830315e961c5df22f3d60e7b50a1ca5dd126ce5f4045633ef2309a1388846fbc9a829ec71c789c8e5dbdcb76181820d9acadcd02015bb359929773f58aaf7137da9a2e21a14b4df20e1a540db79230806b7c805455042ef1bde1f3bfe709fc1eddc10cd01aa5245b04a9af3beade512e392c20271e037c06ef2b010c503b29aac5a6bc5d5c57d54eb99b792b0fcfcefd07f47dfb61796a2870d1a37230c9d662ab4e885c3f83b8390d0bf59e6687d1217c27feab29020c17681d35f1e019149881365343f33ac9093331fc698bbd318ee0c4327a2b954b2644b038f08f97aa08381c9445213c4a4166ae5ea15ecd283ed349f99c80b02f32fdf55e7e85a7aef354364ccf30a19ea0e9465061bcf8fa426e435e06e8f9c40da649e0a2c3e4dd24028dd8936c2b3d37eb780079c9c992b20a3caa88a34019f8ac5eae14dc38343715d86ec67e8242edf8ca9b6cf22bddb9b5eb916a521a77b6508cf124cc3b5ccc3b0275e7b63f533b7bb0eb9fa3743500fec72be34be0b7f722e9d3ac3ecc66e163e95191df262a303a8eb0ca31f0bd84919f00cf04fd3ad2e7b83f99c943a0678404dc4e77b06c7b709b8b6982b5925d60328c8924cf9124ca02d30adc587be68a2241ee092c636086eb47a12d85622c3070d663e0e3a8023dde39a140ee4b8da0cc6d0af39dc3fbae2d84d2fb51c698562ae81405d2602bdf1caa3d6adc745388dc498b6cb28e9a3fdd9501c70021f4ff5129c8342cd74d50f37e54d7669e9fb9a01ae97c816264615a90af47f6078a43b0ac7e6d5a8bda912a6335f33dc56493fa3e9f9e5a96f15e2be480bc41a74c9cc26615124cf3efff6a79bed4ab9a9f228ae40814111a27f02c995687facc55ca554b5f08bcb198592005006154387bf847f9edf2bd9ad22780f7ec26d018123f07011ef0ebc4bd80f7f20ba33c78c2c9be21370b8510605b51b6a675c92fc049e8b4783103ab4ceddf9c848a1917fd772b55cdeb5efa6806cc24bf6d10bd623c171f287f0b817b3430d0c648f4d2787261cf77e63389507fc827367644204ba348c742195ab73c64482173460ab3ca3d6cb83b37f4196b65470195d9ab11001e4a5ce24effb34d9c95b8545a4ac833d97dc2743acc9be3e09292aab2a29ebe6cb8c7fb461214f4a2b2307c8a91487ec7fdda5b9f2c17477a8f27edc5c0fd783c832e09363d3d1f6e4c961014191f8669b0882df3920669e244babcd972a0f576d296a7a39eea14126839a7c6cd79ea81ba745e18f3eb3a9a5682549463b91cde8aa1c79513248d36291eadd0c59d898b536d31d1381f2ad282660e1ca90ed1afec83aea8ba8eaa276621dd1664c35afaae224f4ec5d1769bac28b0592ce9c9d429e2b105af10af752503c774e11492c12649a54d75662115630cb0b97219ce5594c05f10a2c712eb0e122b7ce9f0eaa01c201654020a7d33b5de71cbdc072a6d70facee469f5cb9f8434ed7cd57c2a34513ae9b8fa8c98e17408657c5523eab38b16f76a6b7dc0eb319687adc00049cf6d2f69456976bbba4452aca24f8c6a05fc69ca6843920fb7bc98b6895053ce568a155b3d21cf3ed69ba69854e83927025185ddf6e6c6ff886aaf8f9c8594b4de77b7b648de3cf5a3753a3b7cbcdc3e44e8fe998a83829e1bb02515f50462472b103b94fe267491a7fd3c4afb89772871f288f693f342f6d781ef5ad79e8193784e5e62dac4edd3512b5fe638d71385eab5e7e80d4739d682e0020059f0ec8614d2a4c8a6ae7c37667b244dbf41954ae21fa0df94013fc33a7eb13bb2e01065673be0be6318a59ae7424feccca3c499903fca2f9fd1aa98c99e3e27021ca6c07699fa5a2bacc6a5c51733a14daa621d3907dfcbd76287613e7d1b3e8bc7f4a6c82e60ca10f8263161eacec3e6312f0d7eba36a3c032e5167a2f82f2ceb942d4436664b5d89c8a2d6373e8df085a75ca8820a31d7093d6a6ebfc283016987af6b01cc2fa4de109c21b767036d0eb4283d90ebb4bffd0d314a1b2531e5456ab8924e5c1f05cba11a43d18bab9d11c131164da6a222ac3d595509680bd41f0f886634831b8db96bea5a3b13c87c0a56f563ed57f4704b0425bda2d887436cd810f745e3a1b8f28f5b422d3e8f0f9e5c96415d4e2805aa0231335470470f73a4b940f810d170445001b1d1e1c17045a19aba15a2016565107f16b4f6acbba3ccb776318f08f55fbdd42d8e815a61f63b985fc97e57e8bfde810a21d6ecef7606f78ba690178387cf2f3196b4eac3f50c5ba291f6e8fee5bbf1590d88be9bc4ccff8486964ef1222f796522699023e0ad7091a0a634bfda6f6ab55d1e07d905933c32a62a02d77591f53375c9f7ecef1a646be7ef96e3670b347c1fed2c79a29d37df7a6b6f1d1a663ba9fa1addb5fe96b6828310374d58de1bd20c69f4a65fabfb9bba30e74d57d8e8754ace67bca8be98da10ef00c6b68d7a5352c385cd618c1fef3555d20f6fc6977a17f86425e08ce747c39c1517014540e95b3ef8da50dcadedfc472a77b6300dec84010044fd754b2a79f62f82fc06c4cc0a58d75667d4fdf490567887a8b24445b78b4b6d166ab4970698b8a68576444168627142a8786a178fa0e51a3102f9f4ea33d62654e1a1167b633f0e9db9895396b5a209bc459e16865a0688f3849679fd8d68893f44ba245e224fd2b5a202769eafb31b5332ba32b1aeb84c09dcb5be6d0d21ad9c1e6422ff9c089140d52d489efa75bddea666a6af5ee307dfbb75bfede882bae801d89d410b8fb93a9046de556dbd1ce9ba97172be93d184d32740cf8b2c869002053a2042063c7095e48c0b9ec8feaf811a60d18413283182043e200b78730f95a104195e2e55aa77e1c01480642b10030c307aca90e20a256c40c405d010194b786400511af41174d024054db828c2832a1600860cc9109cc8e2095790518493710475a20c23bc2e2690e4d6c51760ec80093d180292132e5e393801111922b0539021848906e5ba28dc2998e0848acb20227b76a7917b484a13a424a1f4fafc3f540d9f80ebd3bfdab5d65a7dde13fd20ed33763f958d6eff6eb6dae5650e1aad7695a71b3a0229a38a263bd31148193c6899ce35a5d44caf421d2c03ec4266186496321d92edacdd3fbb334211209091e7d766d19f370701b30bb8cb33cf707641a6bdb99633958acf2444575be0d29aeec5f7de6b5dfd9f8709b0ae7b844b9390c9643299501508169619344c6314e1eb9eb3e7243267063e540ef77f28a794d64a2badb3ce09258b1cd09cee1eeae0ff28d539fd572b90b55dd7853a97a59a7e7673ce5209fccfa40a75be0f7fdd17ead0ff40100c75bcff4210ffe0d202e5f9e1e9f4a1421d8c51f814e2540a638cb19df9a3e2ccdf1567feea094485423e296e53f290fe994fe10caadcb8c6d11a54465b37be9fce68cbc64ba149efa74534cb939c741f1a7328dea22fba729b1f392b3c7dbfe37a94d8fa3b320dfcad02c1efce7cbf2fec3a9d1934c4feeca9aa54b9ada0d940a356d06c6905c45e0ef10b6a9cc22035a340958fc6b8f0c95ea933dd2347420ac7d134fea0bd3a66499e4036090c6490e77b230e17ac12e2bca898ca3c6c34b655110fdbaa28cfb856fc71b9e1c8e86a46578ef3c281217dc1e428e370116ba0d4e09c0c029737b99b5c949b246775a1f763e888dee4dafdc6f783f642f3c415ad63fce8081c4b47427228503c4a141d41b67ba165b7b2201aa14ef726b13b6317601754be21b27074e537202759495841ac252ca1fb05938fc6429a1bcd8dae7034399a2834526eb428b182584b686e34b791e6661a4b9c17edc1799534b75cbb312bc56ecc54a10ef8aaff74bebfb95be373bc0be2e276fe6ac292f8830347f982ec0558ac0657934b522575ccd08fa21f4e7ed4a440c1284af69b1cbdc9d19b9c0f59f69b241f2f1f301f3e3e8cdcf107cdbbb608b7cf049a44968d3563216101b192b0b630a2b9d1dc6870345068a27838af1b229057b7e057fcf2d10d2986035423263b82e48eb9f662e0b29bd05e4d682f2fa18ee9a9d87df4a1c4995139ec36df1073d28bae724e88096da8c84995087b170d9ac7706284715e383d383e34379a9bb33c2467e19c5573bddae6be7f36f2a323a4d34873c379d191e686f362057d300cab815283b3d71371e9835dbb42a8636f7254acb924feb4143931b23979f431249c170e2ce6a41b39f253a37b7daf28deaae1acae06156949fc69799588f3a22b7f1a3e46b2d7c01107b60497f656e3465b1688aee67b2e9760402a7ad762672ace7ea134f30c4f33a3c469c59f962f893f351eb7a83eabe88a8a4d83e42ba7b911c106e5d27776ac968a2df99301a978235bf147f525d54843c43356fca9f175bc2d7cff3a74e808756890beee57b8c09da5c1d1dc429d9b1c0e97382f9c98b3ca9b5c7656f7a5ee26474596fcb9005271255bf107c797c41f97bf21ba622e195d398ea7e1a202c805cbb56c946df9c813f3cd7315c9482f4515650a28168788f3c291e11cc141a223acc15d351ec73f2739aba32d97f7cf52bc558353e19a8a1e5c53e1a332f2471c87e4051915496e72248e8cb66c28a22b960d3627bd593674d1a4e3883462340d127ae56f4317cda23142936e7316cdcd4931da66c6fbd32c8146096dc3f2fe345068c2689bd4fbd32481e649dbe0f7a7c1d120a188b66cb8d180417384b609df9f0689c6083445a0f9a26d4cef4f736b9b5b340700fb283e60332ed597344ec8fe3439ffa17a7b5d287b6889ec6fc38db68682aa0ab23f4622fba392c8fea923d95f4589eccfb282ecbfc282ec3f63969d9e00080a80a4a063fcad38242b0129420462049023442046ea0cb13be60736e3baf12be20facc675e3555237be8a1bb8d93e1637206e4095edf87363eca6297bc805d93f881957770b650ffd647f204bb40bee79d945042213bd1148ac06475b2c26b8a1ec8f73f23282b148d60b329a5b37b282b0c02599cb1ea2b9d916d1060dc5a878ca56fc71f9ea32da70a3abf26b92c4175e3f72e28f24baf21adc0fdc8fa01f4b7e08fd6022851f50b2ff8f274dfaff18fa81f4472aa3ecffc309d04dee06146f929cf41b293dd99f4608832a7be38f528d7f2e686e261c1f1c203a8b1ee41ffac686a22983f3b201c95b35dedf865b0df1268aebdcf1261704bec9657f1c1c7f1cd88dd00dee8689b7b211ceab0687f3aac1e1bc6a7038af1a1cceab06971de7858361460e67c86ec854a10ca2c98a253c4306d431f33d1189e948910a92983123d4b93fe3bb6d5c82ccc8df986b62e012e3bc66c4584219dc2bd9bf11a3b9594fb4e1366f8ac8f3fd88dc59dca8587a818a9e053f165e5418455b6bd0f0417da24e2e271a62bb3a8b1c8bebb0c58d79496802038156435b252bb3b3b14912d395dde1e4bd3f2917e09224ebdb8b13b254d4275d1150e496476b9b895b724b62ab1dc9f669d448ae660c2aa6e88aa0853127ede392d88a68b95c399369333d68b4151a098f1485486a6112db921b131c2d17c6ca994c9be9419dd29a9c023080023dd4b53ec235145adddd674065fb3bee1466c0c2047bb3f57fa7fed6be35a24261bf2892edfb91b7bc18ac6da6b33a4f88f60449018a523a1499f5685318916d584418eb18fb9d28240548e6e389d631f6bd237839b217a32d10c957f6a1c8a2cc4029403d41d9863e42d9961e784382ed97a658ca5ec0431ddaf45412293180734b1d61f05c732e640fa06c59b2f56aaf84a05ceec82b3c8032944b1fd94e77778f79475032b19d73e68e8e3d7cf4b0511cb1a3ec9e7e4d1d69f715a00fc449a71d437f7bdddd63e9c5d0620e10fa40ba75bae6c4d08d3e9cbaebb0038de6dc80dc62284053ea4edd9d7a0d9ba0565a9d804b646571f23e6de1dc720ae0ac99eb33c0594dd4331b4114720912057144fdc6e53aa9cb138de096ab4bae9452aaa273543959ebcfd04e7aefd947ec64fd8aebab62804b235877e45aab0df241f6c8f101c4c707ce5a42df446b1b5376232d7ca7d63b59748b87d645a65f67bc3216b8849961992c646a9f890239ff25cda59187293652a646b7362abdfb778fabb95294a4fe624e7e3e9f91d993c3f7729206f172152dac637ca79644fbc0c88a64f7bf2629f02713c5473207982c6906471e2616cbcf47264a89db082713c559e1fb2de7acee598cc8d44e33b12c5393919283744f9e8165faf9ccf87c3027658c6c47324832381928cef2f1eb7192fea4f28bb65223cfd093ef05001800609992374630ba1eacf2a91acbcab37cbb56522a63cb0d2a3eb8524fc43553aff21ddeb0327e3596f1a3a5522a371ced79bd60379c0a9813ea007192ce9933c6c05a21f77f2b608eb6547efe97445ba99feeb13c7df2acd5c7c96964a7465b397445695f8eaee6af8c1f2d29cf5f1989b87c2cbfa23c5f45e429a281874657f43febd6d6528ec2cc3e5b67ce2a656a3048682dbbe6ac159065069ea757853aa5b76e23544f6f6d8432fdb03bf1149d441e9a93a91b607ff719e0ad918202fbca8a78315dd550a584b0fb58ce7caf3c9b8c01257793316434b99be490b20d2bdcdddd9d873667ce7726ed3c79869c2cfb492dd327464f6c41bccc439bb9ec9a411b60a8c333e4248d4ce6fe1147eb2737fe46a853bf45c7be93d644cb377f2e2ea18ecc921f9c23d3fd09d1ad9551c6f699e04b41a6ffe9d011eac82c7192be4cedc8088699e918d191790be3dc202c94bd69456a2b9f7aa76599da6d8c2263cd750c10302dc6e8c85552a971c7c8966917b86cd60c4dcbde62794a83018299d1154dbdca370c121206c8490ac457f43b31255a1166e624ad63ce0a94522628d3f7fa4e66fa56dcd9a9396984532e13445b55c6c5856563b60d510c754ae1e7f80c42e694bc019cf1a384d02953a629c8fdfa3df8f64fb8c62c775065ff6ec7b66354d3c10445ec724b6228bed072904030499ce5956896a78e1745b1bd68b029a535da5a197754ee7516177d4591e32cf0f7a22bfacdc209a3c9299a858353b965fa0497e97f386134eb7b3d0ecec7d2d62b8a32b56369816cccc1236da30258fac23ec9f3ed18796e9167cf070000421def03a045b7564615ec452321d312660633a32dd57d3c13caf43f9224572b18a04c7f35eb96b941b756c64f054b91e9f7e241a62fbc050e2ed3ef95bfa6e1e49c0593e9e344f1d64e0d474aa6e50ccb919a8f413abb4389e2ac13692465e63406e95c83e01384581d658eb20c929346646e3236999a2863e4249de9c153e3b1f1d0788ac6307009338399e5196d05d6f6d108cfe82c531ba0d6ca778a67c85b2be38e0d6ca546231620b00d4ce5cb6f6603f3d68e914d0ca42c36b0dea98d4f70e75f82cb1eb64c55446b8361810590466e12db700db2532361b0e34fea776a752cdbc89629dee12c2f08d60297fd4401489cd552e363a7d636331430c3e1d536aadc341a189013430c1883ca5deb510a30cf100fadde3a66be055c14b3580497dd0394110418f203e4da267cfa04a00d1965ea23cfee012d7f3146a65dd403db8c93e1e8157f3c64ae0cd8b4d5b97a06ec16c019ea6021cc04c1a1d631f4bb63260eaf165a0875bc1b99c67f040323e08b1db1a2d61a8655a656b651adb653bb39cbfe69e7e6240dca3b46b62367d9c7ed4071961d776c4ed29da393d83b46a1b883e424ce499a52d9c1a9883bb71ddb4eadd4341e9a006c13f53e283ea1951facdcc106cdf858ce8430e4c8f5563d3ca1304da954bbc23283468dce53d9b8d1e28223878e176f6954e105002b980240ae603e1676c4f09099e9996fa7e6ad1bda8e0d6927477f474ad2135a107a326da3a34c5fb96ca328499906e195691060997e672b0f8d67c8597449a662bc4e3e8174f0f54bdfc856e007dd4e930f30fdf2a59d351f74d69ce324f378044ca7531f323328b3633b19cbf38f643ae5eb172f5f67b9b815ea5e5d4fe9c652ba20c9255856d66c17af935f5d4932d14eb9b6a9a822136d25a963e85b5451a6365323ceb2638bfd462ae5da4695e99358c552a3632c531df94bd034a32d6f3c39f1157d7a7136cd66645a3694dca526997e3923d39f49f21c4f4e32fd2a968a3a86be15c524ec2ef9139f80cb1a493217a0b6994fdf895cd648ca540cea182a04f62f559232fd12c72bd35f71a292b4f25a29aa0184e38523864386e3850396238fb129be7ef9267069632c47a6ca65a4ff52adf7353e82749e45993ac914ec1109f822e51999beaa6d2c98a92ad3197d0b44dfc68e98b434ad083a496fe75dd308d6ead55aa994aeb396890ec784b575093308c290115094d2a66d03da34262a766bab918f76b70f70ad48ba7b0948a6f8ead7f4206b7a7488f1bbf4e851e3ad1d4e82bd838f525a9fdaa794524a29a5b4b6fda29bb681707f695fdec5d7dddddddddd3d9b76dbaff5d7a0ee861dc5906440d97bda0a2b0575d79b5ead45af73dc75154b9028a56d7fbeed1cd8b601929eb91f095ffd7e29bf9bb608b7b4b44d77ee44ec0453fb134a77b78539d9efeeeeee5ab853f0e7dbd79c73be179ba3d84d8220088e3021fa03acbe2724c50b8ae20141f166d73d99bbbbfb5bff2a8242528064a7ae52777777772153ccf39b2956635e16292532b87d53791852f3cfabe586e9b23f5d7684812efb2df4676e910195e9a3f2cf07ec4829a5145756a54e3acb67d0b309eba0f5938f3e917084716ea2170fbac02ab9895e352882c3dc442f209d97f7c537b2cd3d34252997aa25be931778aa545d92d4dfdbddb3bf671f4b0554a1af5fca4acbdd7f0e346d5c4ca0db948941dbcc9116a1c662150aa01a53b9378531ead673e81d8802d11785c1836f4a21755df73e6b1bef3bd477ef4b34ab1669b2fb0e35d2db97aad49778269dc672c78e6ebca9119bc60e77a5f1bb0aeb98ee43295a6e403d7e3cfa801ff55d64bbe1721f4aec21f53e8800f5a9b1e506fca94f8d3eb87a407d0a352a69e95c5f80bbaeebbaaeebba6ec66a826a7262a0810110c8f184ee2f567df7e4ccbc61699b26e2ea1e2512715558dbdc68aea34995567290a8e64b35974aa552a954fabeef134275c4fb886cf0e453a2c364192aff19ffebff96052716d42a64440067aeff53d96909ffbeef9bf9a8285eecd95fe53fe32cff9f74cee4ef9b7eaa6f879a6c1ac1b1228167ee3b962aafb3631dcba76349665701579ef0a5bcc0a5ef69ad6feb7c5b85607ae4abffd1a01bc5d11c4daaaf0aa3af6a59748b26ebd79106d155fd5a7794f475eff530ee542a6b8302bbdbe68d8f34c8b9a041ea0b4cdf3f29c95f0ef398cb7c565454b3dd70b9299b404142b4c6e990810ed05675d1603356539313030dae1c4fc00e1484555f9f9c09f29a0351176a5aaa806acc63e29b7d23019acb09449e37739c3d2de3df4d84e02a7bc8478f1df9c525370b7847b21bcb193be680631d4b159ef59ebec03387f66da410a0add9d32b7f1fdc914e82006db50b3781e8189f49c689640281c4737be4fbf0d72fe9e0bde96fb85f1a7d28fd7dd3e883929dd25fd3fb70df0b1d848554b42f226bbe6a9d29d87c55a34bfca56348d9bf82dd775762825be877ded142dfdab0bfcadd67b4203241945cd62632011dc2cd9573d69722c0104904f081f24dd06bb5dc9900458c9cd54d5020943b7f0ec5592d04f081d22fce84af8fba1bba2dd5cee55fbcf55deae2248bca495a47f0085ff03ffb9e4d9fe3f707fc01ffbb607f9d8976d4c59c3422f6cc9792d2689d9868d67ead8f5a01ade27d2beed0057d4a06b8b4b159699364da7da65ecc88b3ac15eddca103472bf315fd16cf4b65814b3b8b390b89b7beef704e9bbfb07b3a11b022734ea7b4a9171fad2dfbf4288c2ec8279e7345e97f993efd81bdcbbeffb04f5fe536f408acb57608b2dbd910a2218a72131539ca2ce4262a12945dfee5ad9d61021e081db9f7959d4f606f546127edbb7d9620309e8f67e608039189d4732ee0fbcaafc7fa3e89f884e1fb807d816ddadd83585225f0fc1d683b0e66b122af5cee762382d55ec04fb4cb104394605694fb8994b0e51e8738cb264a91832838714bb774812e5ea204babb974cd7f499be12c87007847ac41785225dd77560981482f7de1b9e92a020c6099c61115018854b0003a50841c14141083885532aa00b74c11ea454522a380a951595952f6678856585c5e3820442c80e0514b0cc609951a300a966d0984183ca48e0a49280c94d7a028d1a346a10910008a94659595959a9a12222814c6564e3868d1b24a0e246cb8d162212f4b4b8b4b810f92411f9e0ae8fcda7969dc505870b0e1a914f149912f920f131e203cb3e338e1c38722439a18b1ae559a29043470e1d44207d36590784aa84d7f1a6224c8450908aa8c88b8808a07bc38103078e27f440870e1d3aaa288ae2a8021111b271c6064443e07e8ca45c93245cee2649b6dc5fcadd24c949ee26494c72493649aa410e73374902caf39330fde99c73f6b7cb9ffa9c737a4bbb48ebd803edae5d314845f73001aea2ab04b42b88e9524283373d220a2cc2235c1105114a532881e4b5123a5be9d4919b480827b22a3791102a20c267da074756a0e055e4871d24c1401551bc888a500223a4d0388802099bc48f0d86d00225984802a904020a46788083a2245cb05c264c237030bc8b9ab831598218b1237a14e044104a68d1449129a240a1a04245104932e4822362d0c4163d5fec80c204155c318327385041dda1c1c91f5caa6e671f81fb12442c045c9a6416d6312859db14dd2a648db04f32bd94d6264cb2272659c7d017a3804bd2d4fea8199336d18ea6b55c8cddf2bafb353e4376814b30e399db59957e60642a6a913f9fb4bd9e521de35f73159b075b5891a958ca33d4c14d70d71f4cb55c579cceb7827af73ea596e6647d930f7228a6e2a4745479898e2f6405eb83a28aae6ab56ffaf9f63bf7f84de0fa2a20814b9c2b55b98bb33adab5545abd5aa2b5d2da42c73a2fe8659c2b78a463e6d3563f61a67a99f1018411480211a5baca2ecaf5bd8eda226795e0914cb343cea237c8f5adcd59f4e98fa5d92097d628d71ae4d21e5953ad74c5f7e127707f8d9467bed93ee7382706be7eb1623ae0eb97b29b657b747f850bec3f6349276d0bc5aa22db2f411c64fb19a0add3d8538b1c8e57646b3bc67e4f19fbd231f6a7904fdbbdfd39f309e42cefedcf24d3fe5c5293cb29fba68c9c9f7fb49dd5b1763869bfc4b47a2747e58f5aeaf4e47597651ef195fd3965375c7d05b36fed9333d63496aa1dbeb25f126b7c88394e5a6fece1a4edac7b67ad9db26e7681be5a69155173d2be7e2955af4c3fc7f5ee58995426995658ccdaec919016dd585626b3c62cc4a9c8421cad31eb184affe26eae24546212ca54722b30959c4a92aaa6e0eb9792855aee9e767fadb5d65a9b524a29a50bc8b7bf651aad8caebaef1cc447d876ef9d9c9c6adec2168876f56bad6dee57d34baedfdfd7faffd5d2342b4b4d72676530e8f822b9eb64b4a5f2e9543eb734f25073a9f25971f1242661cfc5930d00b8b6f1ef56108572f70d93eb98cefbd24528775fae40cbdde3f0e95c84da6646eede85d63636778f03d6fde3ba15682b74bf42f72b74bf020bb5ea17e9aba106a763a575d65a6bbb9c8c60a8f3bdabdd87aaa1a6a652cb926bca5bc3209f7ff9a2609834f48c600462c7cc173be69682c0007e370c4296a9dc414a22d90b83605c43e94325c0544bf53fd0411004c1d9f774c320b7637a9a4c27fbddd8a3903925b7c8609a99889ddd103eff0fe5419c3a0d83d03a9b511aa4da5a6b10db596b6dd7755d17c4c35accfe7bcb93c6b2d39c96cbcdd99c612df034809785cc293d9d56db79f75412c9f4c7b2cc35a81b81182f8fdaee186bab779d7b5ef77545b0e9aded6a2a08879ac5e1459fcad44e0ae8399d14f04ae58e5aa98f92e4ac19b093f5f27b0129c0cbaa00814b9819ad62e794f8896538ee608332f8a009144b3fb8a69ac0fe2abfd29253f7d251957178754c8eff576ba8f37df7f1d8786a3c457445c1f7b7624edbcd99437968ceaab59bb3c2aff10c15396b3a7196118f8de7c85975e4297292c7899375f218f1d87868277fb0bee94ba75aadbdb57eb704661cdc7dfa385070643ac769d5f927823e7f2bacfdece9f4d6765d673bdb95c49d232769096907b7530b02835f36cd3fb1dcc10681a38c908f98e5892ed48199750c7d394959e8c9f8890e7b814b98590f2d37b0bccaab8c3eb4f8b3fc8c94caa73e35f6d07243ea577e65f4a1c5c71f1a9ffa954f7dbb5456766c3b3577a182938726b6c8305f2d33cc579ed9f38c29b87e25898055be4b799386f8c1dc05fa33c4a6abd71170d94638dc704072966de1e0562a14a44c656a2a9f0a6f483d8b4c2d9771b8e180e424b63db61aac2be5045c431d5411b894a9d95adb386df118f98ab67868bea2ef0394849e9914599418141852a63249f9494da6e64548991ec960d1ac1d2c648c9ab563d413093629974bd5fcbc5b327d60f5bcc0e527f4097dafeff5f57caf0e66860350db4c5b2b0a0f019732b5594d32b52953938952b780c1b4325546464a77f2199f58ed66d866d84e28afeb6e879291a9c918f92803c3e19f70c2b03838cba202330baf77efb416270c0c33c360e94399ec298419b2483033981bcc0cc609cc91edfad5dec065fad7eb6ee0463070a703ee2910a380accb41f97bd3101cb9a78df5aaedac57b6489335c864c7d10a3c6186f24e0d6646c3203a721d77b0b06cc71d5bcd0725cc10522cd337e19e649ac4c7209d97c008c13081a139ab659ebec8895186b1659823985bf68cd44f9c640a8317af7b635ebc320c108c500a84997daf53d360ce3abd6704aeef9e3b7187eff5f5cc6f02dbd086a0e993eccb64ad1d6192c02c99c120014724607f958b0ab4a38ea453a9d2742465fa4118873a286bbd2f0b8cff33c24938385fd1541059dfd2da66d208325e404aaa250c47183c87197096673a2b40aba7346bc20d327d085080be0ebf83b39a0a27d832fd0e78cb5df4c39a24e4acb2972025531926ce9a55841748ba80945a588249c204230377384967bcf2cd1f0b2a9732359b8b3943e05368a32d95a75934491fecbe9bd56515b1c5a6e34bce11c6a1327d1c1c6dc904b94c100e6ea50638c8f39ebe4c502a485490a8205141a282248c5bbe2894d8aeef4b3981713359080c395235c0253f994cffcd39bf1cf74be9fd3ec72350e42cca42cd71ab9fe3e62cfaf7ab98c314b1115fad73cef9b180a14e57ef9d37b504e31c6ab465ff7e0e36daeafe7e0e37dc1315f2fdaf73b15d28b15d2c56e4d0a46d4a0f00a7012881a927707d162b5062cb7c54cb1c7fece3f739bfd436a59f7f2a6a1bd3f308a2abfb9dc84388ae7ebc5ef28f178f2077e1fe144b1e6376a17cbfe341cbf779dcecc80388aeae114a1e40f9dab0a9dc7099e396e3465bd3950397514fc31bece331071a5ddd47893914d1d5b5610393c0fe2f349d97d312300f584c128f57db847f9fc70f6d53fa98241e321eb3db369eefe730e4acf9f77328a2ad896ff90807c600b358819a65033400d73662be9fa201ae5ff60f66f94e32974d1f3346b37800397979c820264acc12f97e4c04683c64d05f0cf100725604a66854910a45e4ab42ec01b46f8400aa28e504beff99c0b0bdd0f235893972911c68f3c67fb810486a9b1c5ee432c7ed06051d37c8f7bfca839a28fdf285e685a9202c644ec9dfdb578e2d9af542f31cf7bb592f553479bfcc71947314c9f773c0dac6f4396ea28d5ac7dc7fa9c28651be7fff85e62c1b583479ffbe8d1fbe2f73dc6e63e4fb396ef9be9b586a356aa6d1c6eb630961d0711fbc5f6221dfb742f95e1d21734a0e6d7c2b7c514b05e56272e49859c7c4ccdae6f4f763ee1547b45cbea71102620c5731a475cc3de53ae6aef2ad633993ef7d2a5e2759acc01580b5cde77b5494efe740a32d3781230f64c7dc0fb2237fe30fea55aa7a731872f27e8edb1ccea1c8c9fba631079a93f74b6349e6fb5f138e68398c9be73680be7e297304e5fa9f150e7698838a350d1573d69c41df118bc3d636ab5c8b720e6de15bbbfaa5ed95a37c727d1f67b574f5461f733e3168ebdbfe450297a8af2cde42c56e46c5322a868a39cb8dc88ecc9ce52824282054125490b35a667dd41267752fc48496eb07e9a18c2a4239c915e525ca28d7ca4337823982bca1995aa9374ec24dc44451266242289757e5f22f2d34dcefbce32f57e93d881ad77dd875954adf5fd1641233d092807695be87a6b93c70df03a5a7c9f5f5da992d3dd2501a3b5728ff37ee73b2280f9e8778feb934dcf197eb86dd584ed875ddefb6f9b95fd2f73a8796c4a6ab0ee4eebdaeebba0a0cf1fb2bb9c39dfa7acd1cbf63d35545e2eb66d54afdc6bfeceee661e69e7588d7a7fd7538a910336068c1efebc02af2f0de7fa0d8bde2406904dbe581a0288663f70a032919704b0da70f9f01a710bc5fd395c9f57dd46b17480382a2123aa582a292bbe3eed8f142ae3af7dcfd861f088a4a664cee2df13a5b29c5e2eb1725f63f10fc28088aedb2220f32d4262d086ca6cdc3dbff4a538652a6a0d8aed9abee2d3876a9dc6120d34f652ab6d4103ef80c0847f019d0af021116d249a56f5c26e222e2f2ff84cc8b7bc0a7a20a38c0423a290fa1b95bf0de2486209c81265fef6e1a9e4ae3376fd77594deaefb4425d33aa94d7ca212cf7b196a36fde7b39ac60ad8a75fbfb17b350307b22de59b2be0752e7da52fa7e93fb1e7f3bebb05d3cfd000f44d6339bf0fa462bb4ab7338506a053a6d0aae42b95dcdd644f77bcedf3da2e645fd396925a95745f451e3ab18e32d8ec3bd39f013025426a720586d07c9fcc445c25fa25b1a7f4412811529387d016baa6210c446ae75219825c675899ddedf674c70d78f6bc5a3d255e3756161deb1134a028fa551f4b2219df50418afafef62b100a9953daa66bc7f413a1d353f0f58bed7ace80daee3d6bbbb7e0e781b67af3bdd000e094f1af61aa05d8730982f3c67a68eded9e42019494edd3d14304f4d056867abb65e60f51dde9f990eb5fdb0408e7611022f93bd037e0de5504d8a7a101ec785d045f1d67935686fa7af120637f882adb1eaa103a458a0cfeca73e4a1eb189f21dd800ba176fa0d3eaf2db55215dcdd958092dd3d880525f04b63010c7c0552dc9dce1767ad204af6ff32720966f7ce51289410ca3d5486931572132911cbe1f6982507a46aebd3a6c1491b141d9c9cf3c61458e5083ce7e8137622039713e63f6bb98a3ccc7c94694304652a36cd0736f3e2abaa999a29b8549592e072a655df5f0958fd3953ce5cec82597070c428fbbbbccc4c4c6b6906b804b3101fe9a4d764eff198c54505e2213dbc7089af114e02a90403a715e5ccd43ca9c2630b1d69b0df53e454ce711522f08ccd22268cae56b71bbe2125c172ebd5e176e76bc8ede6efdf81ae4eb3e2ebb12faaba9c36daf21903ed81886b5e717e8976dffeceb5f64b640f77bc988e5ee44dbcd1692ee45229022ea78dd6ef99346df30809282a3fc0540529707b917be461f23073b7ca0f70fbb4a940814b1712722660ad6b810b4e8f1a17a1ecff644f0d5ced5242c95a69384fa101a1018957f6df39f2d76c9ae3e8ca63a0ad966eccbd0a16d68793ae9a216b766a7c34cda2c69aa83123b528fbd77a444989f38d817e0b59ac49a9a8ceec549e22579106fb56cacb494fea981a664b748c7fbbda5563a92531d09bcbb2ffc4d3565eb006c5e0ebb12e5b02975765081cbbb1582eab0964ce2a1de63e3115c48c1cc9feb346574ce09a05d953b2ff05c95bf6dfd1a369b367d5e1075cde4bef9867cefb7ecc429be2fbe40a707981d40b84fa17c0fee7be814e0142c620db91dd250620fda272e92415ceaba2647f9717db3d549f846b669c54c2b43f2c2ebe9aa36aa69ca13d7da607663182105220747d478f977eaaeaa7f39df49a8ef1aea8ab95814b0c6a81cb5abb31daaa51da6a5a6b352f52bbb3ec37287b91b3ca5a2bbba2ec8428053dd91d3f716d5ea45b1e4457eeb5255a055e44769cd792c64e46575d0e77ebc612cfb1d6b27b106dd1f6a08c9fc065ad2d11b2419d2c3b023ecfb10e955d51edc6c6c075a808ac9706eac067f5224141115010f5a089e496a491b8b8ec10c17f21677ad4dc76c15fe5075c561528b8f4d994cd19904b18b8646151054501cef4408191ec9f53643b0878ce39a70f1188144cdfc793ef237f0e90189a8e3de88cca125cceccbcca12b854b9dc54d0514e70a4a4becfe9799ee779de9c151cd283bbe0150c022eeb2bf6f578ef307721e62ef8a7a6804b0c66814b87d15aedb19cd861b507b338cc482efbbb8f1b59824bdc19e818ef6ed47fda4a3c6fb75ce2b19cb83c963337730eeb465a1b0397b5a7f6541858634254234c827897fd0aba0bee827f7d02f6dcebf52ac123a152b9d4e604e775012e411b3eb89c2f95ca250a3cbfc9222fa22364ee83e76bc2dc05ff962cb00d1f5caa5c7080558e80cbf9a22f57f24d5707babb7b963bb04cd18e438674a0878e32917a6021326249f6efc09c5dd7755dd7511db0e72b8b1de8c1491f1242e1fe13e9fd2f38732ff8a60bc402836e02efc930674d98cf34022601fb2b9760165be6f72f718b28cafe200bfd20de557f6687cf0f880bfe9d17b8bc59670a40be0aa426a824ad5477452c15a201000000086314000020140e8884a2f1702c4cf4409a3b14800b82a44e7a569a09a42487610a19630821c41002000004006646061b00caa1588870fb323ef000dfb810b78af011a481acc80dcfd1b5d476b6e0b8303f80330e15dcda84759e6db017cb34ce20a4067c2593e0a718033e037986d5c22c3d2450e4e4c5526866fd70407a4e7ee500b02a59cf6fac12d082d1c87389d5ed5931eb3d358d0e21448c0e2c600a543ad4017d9cc09d6cc7341962b2152122c7e9252a62ed6cd1a007d3a78c7d488c52f945f6a87a3b8bdfe19233693fb3067a09e0d871d1c13e3bee62c33e6c376cc83f3d5560a9c75e957be6224356a7179703d3ae6f4a8b4ddaa876bd7cdafecdaf475286ef28ce195a7ecd50ad6db21085102f979622c227c9b39a51e7802119ac260661a89371bc9e4a6ee0a0d65432bade8da0258d522269600007960077260b19cdf388833c02d38d9431b76126139dd8b7562bfea9fe77cdc219de17307ba87748cecbaf7e6e7588781ce825e6c4b112a24a4853316c5737e3cdfe279e75992dbcc4cef03aca3a208e3c7bc6b1952bf15923005e846e9e22452a57bc3b572d65fe30603fde73ea64f5cedd73fdb001caead3dacc0e4316657e24e18d24bcddc6ebb0489bf9f26c539627c4e64382467d301711e1e91861fdf01ce526cf4eeb07105e53eca3a5ea936841d25aa3a0b9c21bf7a057a36988f37285fd8e749a1bb7d6a7178e15f1bbaa9e874e74dd9da24bade750ca98675cd89b35be167bb4b186d47a6f68612fc932dba940c502f25b2a0edce54b0692b1f58f1cee8b75c592333fb6841a3616d3e17a7aa2bac4ef7ce497c69837d6a8f031f6f8425c086ed747488640a0a16d88871d4f36b19a58d917a2fde4b079ce6a895286dd881cdbc2015f6550e4e124b14e84cb6818c00dc9f6c0bf9afd9f3875e97eec836574b799d72e84842c0908a56ce1a13e7b27cada58f5033ca5585e11b6c9e9fdddddef3c3af466c609250a7eaab424bed0ab2246ee411a82645e9a13816230e76d7a48b8d1f0780ef36ed9a5cd66243da2ea785ebaf0b11221985989c7641f14875f627f383adc9073eff373f87100434c33ce0160520f236ba3f446c220055a882c4fc6b8f3ab0393d206ac810191d48d29b69efa73b03791495dc17dfe3f255a913a25f82f91c1fe7192289ae264447835d3bb469abf95631d1898bbbf5e3e8af12f86d248842093f45040481a097b9c30b0dbf416bcd2be32fd0082b9dcf16668ef0b4e5deb0fed2b407ca28b569b7003566a02244bf72e42945348b25f5248877b3f6096bc3ff736069f9ca455ef49de5186162f481be4c87fe8a605ea4449fad77501006bc9ebac1031bfd133f1d7c8beef72fba38a3cee8b8bdfc6da2b7376b21ef01b3f7e9a0f086eef84fb167dabc50eafe31f95c2210d55639daf7cce570f921023070d82106e62ec6314d5aa1bf85a570e5ecd4a65c3c3abd1fcd4cd6a24e8506f6e628de3c11c930b5e13d978ff04eaa2a2e8b8015ff311199462dc8cbfef23b040d988a9a70ba53f8d0826bb97a921dd8111cef8bfb2304a653d3e21c70216743645112ca93494a2521c0f175aa260abae063a58f843bd6f36299a01ef9cc3355ad74323011c22d5e465f002208975cdca85a5e822b211f4640a0bf743731aeb7961834c25aabfcaa6c00a9d1848e2a02bd6c4a893dd894a5c92cf53b356fd4c6e64ce3512244e97c3244da92fddb8490a34201556f6f7a5ac56a7b3e033affa23084fa434823b97a4a6a4d11e143724ccf0346b831fbe79dd48a65ef0837292631be77667eb2b4a5f9f9d4818dee0176a48f8ef6b507ab188f7d9aaa1a41bb433e11fa5360b6dd171f3e1c8bcfb61648c4bacb46efa8bd616e6c77e48a5a31b1a9c2b2cb6ea2cc5b963adb65e5af25863d0e650fbbfe40ba030a28844fb420b07db48230a6b97b6615c8626c2ef8f554da83dec2ea0ce03a6bbbf74b7899c2aa89c42350a6b941d0907c48fb79c3b6de79460062a484c50b94d0f00ca1350a14747991544f9aab5085f9287fa7e652f0a8a6a919d2004c23e746f4a00384ebbef2b0a61309a91287c990c2caaae95a133775e544225e7dc42301a96339f27c1029b6c73f9441749ea39eb892a0ad01886162959baf5d2c9909bf42fa2ef1b36d0424c3e7693e8717a2ade1e7f050610ee206560d008d010329769c457d1874e77d1d2e2be3b35d39d25c0f7b8a19dab51f80e459fec9acd1f539b9c180f8c1c12f565e2def42431a12562fd13283784cd41a43a2bed0fd5ce979e5ed2f43b82e6d603c32ef91385da61b4441a267e1647d81fc8853d1b04525a202ef37d22dc2c51467b330459abaefbfb7267c7461f189c68e98c5594994aba88941d62e21919a871e3e4fa9cd51aad484a261f6bc045d850b73620949f1ca67f4079948ada722fbd82f1aa1c687ee10e9811cb64bab7e4c2ee59d2c3aab7bc16cd0226d9e43a7727db356bb879a57c36155f000a04b689aaa0c13f39e39dc9ecaa34224f4d7db0be6f3bfdae32ffac7bb1061292c3b5a5bcf92833fc9500f1e8adcbb2d68eef909e22856b9830767b845e0f8b733b2b44a3a02fb1b58c3cd1d14fdca5c98b59a2e39e471f549223da8c379557412167a8f4ad0b06a16e54aa80f4199b01cba1cc53f80898f13b4ae896659ecf79eb7d6a36beb7e213112035306b4686509436682afe15f49f4f2300ba3ee18084696c089b7a2a19b2fdbfbca95dbfd35fc4457311d82ad1b21728bd051203533ece9c21e01b8e0c2676520b354c46796467db91a6c53c06dac05add4f652262930137d032b885bd545803365285327fa33946c0d990120a0a761d81e652e0332e8b5df407b6a03827893ae631b75fb1fb8982b4d8fcbfe35d28b903d33024fd4c14b22779007f34424b95b35793013aa4d456a82d15068c51e1a0084c15417d220a8a494bb051d5c88ae3a435ab2ae90ee21872d9295a5ca09274c854cf70843f543b0d2b3bfde70857299b3c5dfd6adf0807d3c153cb6f32db40904527b4eef81dfa80892fc520183e03be7ea3e110fe86feef9eddeaebf8724073686f2e1ac135fd16ccd67a1ec1fc04754dabd2a021972223d31c00f0816564f1b587b153bf0275c303b2e09d824840c6ca842850481e531bca4e827fdd1d81d49546649b7a9c5370843f3049de5fecff43d7d2b4a9f1be9bcb8c50814ad3cbf51b1fbf28a105bf47fe581308fbfc4664cee36b46b611e0ba57602683b190d082fb27a64dfbaab1f78b64ec29d7fec76e51fbb4bd9f3d811846ed59861d69c15b9ea44b5f07a48f5048b22394ee19820b883200e6082248a9ef7a132ba344f8080275a2b223e09fcd03de161a625db68b66233645e61cd42421ff6a3502d5a4cc77fbb9c99944fa6446b4fa09f92791e4d2348749abe145917036ed0512298c69d3692c2ea76188110932858c885d5d6b60f7a38ed1c1043b22e5ed79283ac122b20e972839739b498462a292c882e468234a3f3541118ba358fbe1e70c274a6edc3af0e3474db170d50230d5a9c580d59fafdc3dab006d41de324251c0986a4e66cc2f00aa05d016d50b5d10cc02939f95d7773d79b089bef28a99faec561e1a87139d236255a3140a7b261983f9ed60c0d5a9897a7e9053dbed5f6fad1115758c2f59354b4a799f66e851914fc641f9f3558f2cf093d271149d2b0c5f0f5ab86f292629123998df8c6faf78d2b01fe70f37d3522f0b59bb4326e0c8f87da67e2161369b28836b22ea8c302c9d333792523141ce4497f66bc79229827ceadad66ebabd7fb5d7fca8c9142c7b23dc05fc3cac43b874a048c2e4bd921aa44b89a43719b25f34ce068c5439e2bd96e2b66eddb8bd3dbcde32841c7eab5f66ffc4e445d5a2508d92a6c12284b5a60a0c4bf783cc20bc100e5e3d7a457a0e4bc49db0d23c4fffa10727c4161f6f1c2811382ccb85815ac828a066ea266c9509b7e9fbb41f60101377f73efd98e59456d18a011c18cd9cc00b52d1d7582dcbd1c59ac875276993a51d93c66baecb6b26650e8fd7b654050b22a73cd81ef2ee897d04b1f19da80836ec03d55ca50c2859ff568f101fcfef5ed94d6b0ec0532ce7e937a4378ef221623c5690249a8c4371775b7fe0a6412d9404eed0a617cddada1a3e940662001d3aa8cfdc9dff2c65efa905f33d29a6bbf5ca4f1001834f3e6e94ed58789756781ece95bd2e4035e6828999317bcb9501df64bf9dc0b29f19b637942cea6f5ab80f86bd5ea1ade68cf540f828afe13e44a598827df1d7631e6e561bbee7cbb5a8f3e571bed8ccc6e68f0a4bdb19c8060691147a548cad9d6a29b58899ad3b617c49c28b4aa5046858a3cc137587ddc4bfb7449e85d841affa4f29e0082324323f04e3f33d179a638af8482dd82a92766ede08d2b86e3039d6b9ef7187b9297fac39badde83befbbc82b61f4f80224eda74dfdca6874128d40f0e5692d88f0f93201c29422eee67e48c67eb81c7c8d7b3530e1c66da4e8b7b34b5095c8ea3aa3a08f6a144171ad8a6922ab470b7305b3c1e387b954f77b3641102b039fc84d3292696359477a649e2d2517b0c45ae720139484090244db750ce31dd5b2c77042bf423d3d02db0142ee1cd0c6ba10b6e25c9026e484bc4b39d6b7afcf1aa8002d9fdbcc6802fccbd696ed2635be6c8667c2b2e5b0e30982c3f08042cca47ed9847720c13cbde8016dd0bc1938bdd5391621bc0bdc0dfda2015a8eb112c45651f078ad294bdb3e99d04fff95a09114f6cbf8f1ecdfe4d78079ad9108da4c714af8222da78f32fd93c7f0f265cb09161a15ce96045631c23d1cfd18a511b21a23be996322b8bd014fbadd21355d4b3b00b7b747d6c391b52d49379ccd7174ffb5fcc8f0d2e3c413e8e3f41b71c0056ad397fd004492ce98ef442ad2910933cfac428487fc706f6e06907a66527fff1cd7928e09f4a016047eec0d18725fb64a2d2ba2c851967ff8ce909656412c7ff77e414fa0cd7f5ee68badc741f3b039b40c97a0841a1f15c3b7ae689dfbe51ffead89d4602bb999038fa7f08b827c639d780122786ac1b87e3a3e807e3118a7fdbeb3bed443f0d4e3fd89afd395621de5ba786ebb64d7240255d126a43212cb905920909743ad905178a46987a6ae74556bfcb27a4131dcf5e7711b1fe2596e8974f79ce557237cc358e8ec24a81a1cd8a2ea9585a17af76576dcb34613beabc32e7a030238762c2887c71c78d07694f0788f9d731792b8b5c90a8317e24095f922dd1754e3e4898695c42ea92fa1a82d65b4b5c44119198cb7ec3b87f00580c493c01a11ff8595df7671fd7d3ad64684c92821ebdb22dfc8f0c586710bb5ca00f9e958802dd4a26ce15924b337d6e1bad814e1db0e9ab76eb7859565d5e1600051825ef0cf9957ef36510aaab6927a9d6746b164fc36382d41ff85a3929f449be00b326a8047fa9e630295871f42f6bdbdb888859820a9093fd6f276ff0a207045e9caf930dce1f2f02109d70ffee5718aded0d66718234b83ed8ed21f52008ce478c7cfa36dcfa454a588d8b490ff5539494acac9cb154e74a66f9fc29242db36e11a2833afa991b1a4b7dba1d5c613ab18b83db503f6c855ebe959c3cb35045dcd7fb07b54bb9d19ee95a6b1e633288925b1922148bdb6fc45385608d5539924baa89b88bdf4bd1a76f6b4bb983909fe332878767209115996ac9b41821853c004b6d566ac2b029a82be434bdc357e953bd83ed47b00a0d28005fd042eeb1b4a570eb2133ca8e04e397565c9c05c13a35dc51ba4f01357338e6e73555e13cf4505916adf3297342255c3d6c64259a051725cada1de24eca9f4953ebd0b73c273b60e1e28274347e4659bb7383b32ff1ff55c572c93360fb58c496865c7bd791ee5807e261f6b0c1d3f538ba0734ad031aae52b8acdca2f8bf8ce5f9d75b02b869e51d579635487cf44676e2fd2b05ad6cb831660f4d6421fcb884e1689ecd4fd6120819c2701967d8ec66bbd7f7f910e001cc297379af214d8b4eade3d91c6ace31b53cba5e12f2cf526fdfd98b3c576501ba44cdc48eb89c2493856ee46fa6ad57cb101b7ac1e1b24bc391720418040e3b632d62dbae62f082b2cf81c1c8c5eda4d9715e1af3d1962b366fd77a2abd2d282fedc20ce8c894c4bf3b447c21be65cf9a5deb137c52b6de19cf1c6db96b95152aa52cf85d813c9dbf57a937097503a6882dfd26c6dfeae9cceaf73495dd63995dfa29d559f8fe31afb549e53c8c36d8aa6af215be69ad2a52a55d7df4685d174e29c800aa16b5e494e9526ea543c83f757e455a9f1108c62a2842fdcf8b9f029c28fdf692d5b24fd0a8ae5e369824da8b5be39517d215321a674e42131bb154069006bf7f03f319057464d3a897727d2bf823bf1a85eb1983ff5a97ce0dc976738f6f4cd88f30ea0808d1d50db054cdc70b6526d41cbaf71360628ddaf8d8957ba2013fca28b18cf380ffaa0dce17348df5cb815b8acb8bcebfe045f31919fec8b3b4d181b6fb610a87b1841c448e40035faf5eb4041bfa0d480e96634200afd485becfb74e80408887a4cbfc6f920958b36fa6d1d6cce8b30043fe790eb2abfefc1c101bf4d1d1052104209b757b66155f1e92af1a62d580a15260d2c848e0d7315dbd7f28223b34809f8bb0063077e9bd6f2e9d9fca13d0dd02c64c307fb8abd3d8d9dae6ed7df7968d82c62cd76f10d0e0e16390661c416f435ea6d3fad31c0e5d21fe9b2316d5100558b2cb3965d708d80e519afbe4bfc4a658660e9a78c32339531b0290f5f305c243896062749799f6417a4207f3c761efacadd1e63bdfc7dc2aefd784ec1a2ec19b581acb9211f7453eea6c6e7ab37894081e488f1d6f52119999adf1ba79251653c7197830281ee90b03ee23eb0d60df3416ba5399c4b3eac79d21effbf3bb2468ab35eb704af0b509afa96c3787037d4f52a1d9045b7b53101642b217296e2aba57f54f6ea011e3882ed56283188e421a9bce39ef0875f53a3973b949baa4af7f9d6446a62cd23179d0a74288e0fb94223ba7b0c31556d93c9c3a08c21e01ae3508651b772d5a7bb3a557f03778ef23c1a68736f4a0e795a32192106f06a1692e3725172ec692ec184440b8aea87416d99d93c67d0d19e2be9421ed7cc8c1af902d1bc0483419061f67e54fa06750d983ab3e66d7e47a87a28c7cca141820a9b49cb2e7ac1559e2623dcbeb8e0a208964b563a7b9e3058a1ea01e685f7bf05b4e4215e6ee6021c3fe8e78336a6871b04b9b3b32b0d0bde7f38abb07ea82dccf8ec931378515a57af523c6723b48d1afbc1c6a3834530bea3c7cea9276bbecb262fa3a4eb35069f2fe4931b5e1432434172a0eec4d31df0152ce58964ba1d06a1a496b40b12ae6bba8158ea7f544b5db3225852647c62e4a80cfc597f0dc07c08a80f8b0f33b9bb31060b9ea484c25d513e9974408ea8235e2e952495b28348c9ef5075e8a0ea5d46cb3ab9a536deb0e055bc08839edbbb928d95155972c8a609295fecb97b884b70c4f710f49ac9025a31ec13364a5bdf5b16a3ac687cdf5e6dee361a20d0754d6c13e2c48eac9002817e811e273e36ff48e4e79205396d1eebea8feb51b4fdfbf159226dfb694b70bbbba12df1a7df00bc96ffdbb491cc8178628689536ab1cfbaeba19fe9853fc8c09d67654a92e195c69e67a45a2602340d1a493af414e9a2476cf08c349821c8acf96dc2ac8991283b04caf258706991625b53569800a0f161d403edafa09fe0345d58b9a68cd780552fbad691884c1018c3b63590e36162f2d0340b79f1a3a142084f001632f41965713eec36c45eb314bb031eb9f46af2d1b131293ac7a977aad589e420af844d3f4edf1c2786d2af74cf89281cacf4d74c110c8c80f7306625b296c8aa63a4fa2a35ca748ad18a10e80e1f814878dc754d4c534217e4aa8474402223c7a2304b3441ecac4bc2c809413966eb4b27221598b0f03fbd13534df43c8ad13d1f0be389bafd680312a7b84c412b05de4114ecf37cf1730dd3dd27cc23792302e8902095e455080862a9d67627e66a3470c757fce93bba28d089e64c3faa4a1e9304820f0fd07c3ec4f5b7d25f5326327a86a6c8ddb3d6be2373aef4021275bf11440ded66f04837068304f3364badf7185c8def93c09f0f835cf86dc8b7e873440fac732adfa405bb7faf09c02bcbabab92a26b2a55d1ee418a49b45d4adc69055f6a7918bb4797ea3a519b3b37bd49862073eff268de6d3dab5dd5879e51a9823b12cb6efb6a20963e611e61983213f5f9709cf310939526ce4d0d41191343c913221b92d9d5b4f6b634cf6dd1301a86143eeeaca8470c5d908e55166c99cc0b1093ec892699974d98ac119cf019df53dfafb9e9157f5427ef9e2b600bf19cddc50bf9e3fa13c30aa864485de7cf9c609d34acb67c611c0bc1d66d7bfe83eab091e252b6e8711abdf13cdbf7e810e04da350420d32a410310d891d6a61fe053970a39dd8ac0e575a1e2fd6588f8fc20fc802b867eac5168809e6f4acbcb84f52c541a9c65b0ce3aa781ea916b3bb7f60616d1eda0c6c6ff330b0c88ebba1ba97eeaaf8d31574dc1162bcea3e5eb13c8099810baf54731660ab15cc63e63a537deccd4dbca242561804c80386c7141f737da06de2ecbb6be821f3b06b4702ddf84125ca2120c6ad3b964f01f56107e05bc16a3368ac8f6860511263ca3c48f9ddd53c01ece16f91ca8d0da4e1f900f2df0909e4772547325219ad7c9d6ba7576a0f9c2396d8fb6d6d24b982db1ab8bb3e2b34ecc2367c81888839466c4a68155eca218c2769ca880314730db20308b30a32d4efd33ca539e88a429b47069774a502112115299d025e47fb61f9309e312d4923d22260c5c2be6237812d52535c8dad94887153c7e33458f7de0f0d2cbcea14f286ab12128ec879237e8643ae8bad5cf2187fc79fe33dc45c6c32e8441e7d0f08daa2a05b5f7ffea1f0c78ed714cfb9c4b4fbf0fdddbf05a206a1ebdf8814c0330a4cbac821166882a1244850081acae5ef8ef3b37dc0d43ef2a50f4dcaf562facefe4c2cd2381b6073a75eb4a7acde2132ae518c3cd581d8795f0a8a283ae03e6c50cb3bb2d9694b4150148205cd7ddc574759ae57c8611d04db5200ebc46257214208bc1847cc52256aff1d833a3370592843966eb9c1cccbebad9169054994b849e45577676ddefd2f26426a7c11c42b9db528adcf7de78a2e2116f81a231d224a18061758d7783d5519876649b2eeb1fda4543b89433eb34e3a1258475a7e6783ab99186469ab1d3e32f4caeeb0230204755a766f4ead2d66ad74ae68964abe570cab1371e14ecf35bcaf6fd6b440f349980ea6a8a61a3f00e51c83874bd2b502a9c4a62987b4d2cb5f0508b14ec46a5330cef38976422a33bd2dd4fcd45121fabff4669d3c9f2a1313d33c9afdd8b35455121ee83c042c403d532a1b6abd35a9f41b9c0c841625622636786f3d709a4cb835ce9c65eb503d332286dcb221e13153c25531e9a98c325e94dc60264f10e02dad02213d764a716e9afb8ad5c28a8257cab5e5bf51e81dda98ca09e479cdfbab984edf01b7b3568421a2469f0614b0e6ff8ff6d7453142d4e86fe009cd7e4e80c3ee8eb82bd0987999ed0b4f2fc4e146d7008d0b6a6d9579ba71f0375bcdcc47313262aaebd9ece4ff841a84603646b9d81a010ed98da5069407e690d57fe14b6223c48becb5c71737004bd5ecc2033e0b8ce43cdb00f3d3d8996c8cf9ccf6b7733f64fcaa2a30628f82e11b617c34e07e3b2941ae3677012b4a628c71bd582c073e8fbbe2fdd2bccc6d0cf8c956f4f993d6f58dfbc333b91511618a7040f2767b0fb75834229f6c9c3622fad42fcab0ad29beb9891910a4b1799bf7969bf5a31b105562a5d864a33c288072af54a3c0c1ec6cf8b391f51c47a5e4e3a02e19464a41063627a96ef51f0b118d9fdc46982bdb2ce1f8a05a7c55e7bfe9bbfde31feb8df919751ab4ce121a0edf355225d90412ffce09d33fae23d04b05f8b713eeb81074feeb75efafc96a4db2cae506549e4c364e9cc8c606b0f6deef1f0949f0595914c74a344e9e6cb495609ed6d5998c188a06783df3194963f02a1b2ff3fb380a5e740c14520098565e0a147fd5160f7a252d96d5531f896984b1fc3cfeb1ff7966ddaa1fe934ca583e045002e124b0e2f9165cab4e1423a2e4564a5b6e33eeaeff0acbf8cdb0614ee4a21ff3f64202e8a35e397a75aac32ef440beb302f8559d3ba5cbede4ec13432c9da1f79d07ecff9bdff1dfdf95c83609c2cfa5c1cb11549f387bdedc0c686f2e8d7e6a694857ceb8f5b8b5c4736914c0197c25d1dce374d99d43911649a222ed07b12ba6a640b39258b3f72547f013f3e78323e26ba1ad044a30aa82574cd87ba71f00b332ac2d84c926b7e1c4357d2d2b744df8584bdb571c7c8744a0f241b1a15c4b325729a7eb6d9658bfcf640e2395380906819bd45ee1f82c7dff37b9c7fa50d4ae3d1ffa6bea7add00b3bcdc7d39d336fb59c06c98359dfea566e5c21a94211444f399eedfd81013ef9c662909611962f924adf0b00dcc2f3d7691cd9be3c4ce0298ceb41ee440b82ea1a5c64fc0bea05adb32d170d37087ad5b45b8a19cc4103b064d80099ba6c1e726ac9c9e622ba344112a7dd56737508b69971e4b14162fa6021f5c20d98fa001682eda72bd69a87a10c1c46f9072fbc5c0cb55c5ca857c74829e5c6a4503a58f5a48d668f8b2e20dfe80223461592c8410c696db9f72fbf4245e1c19354c4569395e8660346bf98123ea9582e3a17bf8254a3cea1bc9ffd1ed6eafb68ac7b587d3158df4d30ebdc665f98d0c6b475ec28905088eabd865cbaa60bb8e8e016428e7f235067f87e1cda54f1f3b5e464fe313411f9b7bea82ed39cecb55e0c0bd8fba286eca360931577074226b02ee207627813c7b4358470407b8b8e46ca566777c0a58e4ddedc9224063b1f26b84c00a4a8d673ad402151a215af30ec6055fd9149dc78110946d6c109758a7e68650fcb87a1e123416ed1fc9492a0d7e7a294a170293b2a05590eeaa096c6c2b54bb8d7c428b11747ac5df281c0cbc96d152640cf1043384fcacf53f7c9a5652eba4748118e0bd60a06725738b451df27a00c0dc6662ada018d22895df1f657bbb3e346434e38ab4a158974d6f26ae0f25eda1eeccbd7b3791e6806258e2c2e692007606b4375dd77a026acb2de3ed62d149adf627b55c85ae586a40e4fe502e4ccae878aaff2a81b9ec0b6ea6f03885e373198c29b3902bef8df5eee36454692190152d020065354c17978b8d36f679ad279f4bcaa464f8452ae8b7d52d767d0f5adde11cbc06a683fbe06a66a186d700477670bbc105dfa0e622aaa962e548eb348abc72b110b62c190b96d5db6ac2b3ef2b0381a20b581615b1cb62b03f6fc85155254297b15f3ad0790d8716bd88ec9c16458aa340e80759f265f65c1e87df7f5dd4541281e95b5a323a4cbd60d8d2e2bd5b77ae165100096561247e578f5032b5b458b0ed7818e90520783889ca0cf70c72e4c7fb6dcef987e561b12e5ef981cb76fab25d4c44737bc4cc91dc9f6cf525b40025e04e29fb646f7582db1b2983f7f3757ef2a66f115cf47331b8432e8e4faf0c06f4d640841aad95e2c4a140ceaeaefec3e56018ae84354d8ad512ae4b8f48c57976e277acbee7cda35961034bd817829f29e800e11d531fa2c11ac93bca29c834b6d3d6a5a1c31c627241927b6d4904cb1cb1cb00a5b77e04d9c65a3c0d05e73f043bc6b061f95995b589d137444b774ca74304aeef9ee0366c270a6e3926b2c50d40c6636344f42792d4b6e1e0275f1841cdb7dabb1afe496a6bf49a32cc0525637b4af302e55c1a995cb864f41d7083f063750208a5d6cbcb2d189a41069e3d6a2f0be48ba941268cace90bf99404854a2f90af5d39b603ba9b1951c3e2a0265889ba1048ac084afa2b277ed0b8fd0ddb63cd99add15c5681dfaf97609a5b3399605c2339d5936218b81d8f4e94252e59b7ddb09f5c0cf7c0a4bfc19a1ded5e481984daf8d5b62a51a089b6cf4473b4378c77fac149e0f5f8c79bdf621a606b71f5fbdc0c20e773c02378c758f4efb838ace1834ea2599e701af44a633eecab01f2654e904edcd6d4b4f5008977daaaa502692b79c702d17db2f299310c5ab7c468229b54c3935d7f622fe56082ea36e01be9bba40032b2adf5c6ccc732e37e346e5edd35903f4d12d7069065b5edec8c919dbfeda36396561dbf5b880daeccfda1a87c51dd7be4cdd40a20e9a695ec0faa2ba3233fe9484d611355ae3b3a7153fbfc671a9638523d047fbaeec3fddd06315146502d5ccdcbc6ad9576d80247b18ef2977ec9e853d18317ccd71a7ff20f67901a5f175a26e602982908d7570d01711139c569e38097e25feaac66883f1f3ca98b1896ffa8ae2ad6d848205a75314cd45365842d6c6c32606362a095915aaff1661a1f2b1c5dd49adf331a7ff10170e60971b45b84ab9eeb5d9f3ef814874bbd28441ed3042d58de44495fa01437c8dc97d398b1590e86c8d1523a00699e1b9bb1e2f3065d7fe17bc7ec1abe1617a72ba05b5f0e0defec93aff3268ee5f4c6f7f167f0b262c9ac24b21731aa33bab271fe64e22a4be47646b42e70e41b875850de1bf85f6c828ca13ce00131460e2e5282675a23251105d037f72095e9d585be4d3c102087f56ce7fcbd75412ec5f0a5d8e06c1db6f1beec494d244464adc7b6e22009da224a1c9395b99f1724d97a8484308f1c2e17acef671d8443d5431cea26fad701e35fac50cc712e294c78eb5b7c202f5b489f43fdf5f5e263694f5c0d2c1db6dd9d88d907805f514f8ac2ca7060e46fdb27a473f87301c6d0b558eee8761c366f3aaa9a19b7ab7114e90a1314ed928b2e8d051e5547e1b9ad384cb8d455b6c767eebaad1dfbb155f4da8d2bb8cfa8de05340b16c2a9b4c3534b9d2f9744ea9e0066dbcb5f629ac72566b48943df22dbc88523501d2096b89bc55a8625f24683b8652d81f6831258c162ec8518295121dbbc222b75baa0d3c6ac8cc7b52788f916b5c4f5d12b2f9cc8f7051db8867813db0ecb7a8db2e686c56051c61d3230c77c8b86a7bc62539726eaf879e17b840c9fc406d221625bf06f4b6f4ff233a957bf6895d5482a0e050f30531ac912ed148cc1d92a4a744f668a24caf94fc48439312a210df0386c94c190d48c55463854c5857a7814cc71547a812fc450b70a8b7a895a3b9d950a050156aa93a94d51374783140c49cbbfedb9413e495580b8948b04c60158227eaac3bb22990c58351a40000a13112f80e4c2acd6f4046d719b0b5f1f1607ae2350e283a3b7b8b65bb07df73b7a73e6388c8c581120b006d8793ba34bc981373fbcdbf7faa872176b21e10cf35c06d871578b96023f3a5b47abc16f5ef92d993700f2aa447819c72b2a9800dbd90ee76bcc2cd5281546a0c95a39e0c3d7f7531ecfe139282a3bba3dc199504f84d14bfa94a1221c968590c2fa8908b3c9f93ef8cd97f786bfa048dea4d096cfbf249d8a9801cf95aea4ac5498645b34622fabbcf78e1731687f91f1e748757185c4b5935ac63107fc51eb7089b53aea5a017cb98e903eee0eb636c97fe56c4e760057d2bc0157c98b66d2a876dcf1230d7f3b9ef2383cd1a0d8b60d235dd778b4182326bf6a27bd0131b737faf7d92b50a9fa30ef770f147f590e8167c70ca825379b9ca76502ccc4056ee99046de4f3a6cb4d7a96756efc1161d2684fc69ad94394fd34ff612df4dc96b4b99f8950c91c619baa2893e36e91c13f69a95410ca6d028fe12fcbbe416900039a2534c28cdecdf4c2ae10cfc556442cf062389f8470aca409916fc394dacb99a2826833f7362a204149ec0b02eb0c3983f04167aeeae546ab05f7b05a85d5267b4bd715791f484c9fa07c7bb69cf8a4331a6461b06d42d740cb0906254aab662ff35cceb50ec9cfb69c56270d10f44d468139a0a427e10550c219c0a6bf616a8e8295bc7a5f5478c7c481322a08d1625f005adaca1f1e2002ce5b83b73684457d743520ee4ec95bb151983db00f7b4976b3596dd9fc4d3d13f69b0e24c87ce0def8936969f617d36300124fbf139c9cef37052ff3847c07d88802f7b5cc36182b1cbb932a4066ed379d3959eede0e48cedc7e8b1bf71635f41cc2c7f09847ae9fa3f3e788b9aaf523aec9fcd989b1b3e7ec97ffdd7fb1c76087c822e72c6c818c6ba895367e612d51c093294951abc7c5b48001b83bb4e534ad8cfc5d6ecbe7cf07ef2418cbc70f6d569b5f348fa48a63889e5ee72107d6b21e172b50f67e2b8181186a0de6b4ffba2075f1a426d17f7675d1cbd18fb6021f4bbcc4328d3df43aace037624a5c0e42c2a453ac1194ca90ff5448cc8ba9696ad0f3fb88db6989af359e6ee560fbb5c3e0e1515ab985db769d60dd6f53540bd0398bfa2d205cd1e9491df33b45912e7bf7c5b6c46555e267a8bdc4300b1850f0ea0a73615c41702bd36764c2b3771614e243b347b11fc4e65b76d0e32a2c526e54b5e4b71d8893e6e5b6a0c57d17f7fe4dc0b4ce4c82c1d7b33f523f1bd2bf40a22443fe26186f9a68cbdad22c8f7a1bfadee8c400782b1fea8b138c1780bfb48486ac5b2cba4908a660919dca5987871267cb000829b858456ecaf9793f7605067ed58a83eca66912546db041656ffbf45a7da4eb861c31adddd08ec8ccfc1abcf0ef487156fb784efcbe6575a5decbf7c4ec2d1011f97dc171b2d975cdf30ad0376e74a6576c90c12fee4b612bf748e5c51e7eb3ef566e19d2f4929ff51d2a93ea7a42c21fcc101c96e82a41ee29c7f8c7dc74d9d4d1e624dcc83decfd5103f4dcfdaf1c9520e7d4bf16a80e0959218a1ce43e66a71b9faa453310e5dba750bacceeead8622a6d23c4c1b7ba21a77da1a9a146e4f9fd329a1219102c0b226bcc43513e01032ce7f934ddfb7b8f9fbae354b55702891cff0397dfb65e2968205daeb2f1c6be380f83cddfc3a04b611d101ecbe028a66de4a18ed1bc0a69ef8b1fbb9b023591f06149eeab73da90ff154a612b65d8633968f25d0688d1dcaa8c460d23c472c1966552232fa1f85ef8a66e2e4028a14d048532ee4c6dbe1c2ceebbd72bef8665c8139d6b19cb0e6b95202144463dc0aa39d59000f044a1c0c54d5885e591efe3fda0e35b29fadb84f5eba516e58b17f7c6ca7ae26242af7e30725e98382afcad6f0af5b85ab5ff01eb22980a248192938e9f1de22dc87f182efd308383827cfd3cccf450d3d78bd69986397004675f2ea4d38e206cfdb4a1bbfa94baff9c836583df7c50ce878205d8a0e86e85a81509d4d544365af7fde1579459e48132e548c2c8c33cd0b26efc66b98ddc5a433b397c618a5f3314ceba6dc863b1e99c2238822d1263d90b60a948fb5d2ad0bcf554f11185e08fa21c9c0d5928d34f86401b3e468fe3594a21f7635f836a0f426cf1aa856f63504aad503d0051821fd805dd4753786effefb0fd5af80c5a8fd8fe3b9bad1792b1bd92722af4ed18828827ae45194f045f385c5ff6367f619dd691617136cc04735f6c57f545713e49915c37c5489c09ea0186c585a2ee756f7338f91d98c5064630dad46cf495184a04e032987b8d62383d126ffff6f7ee8e8f6083e9c686ee0e68b2bf32335cd1254b50d75385d5246494fd64ee267a7789b00605125a974239ec723a00d172218b777f4bd39edaa1d6665dd5852d218eb44c82e2ecce545a8748fdd39aa7d4f64e3c7a8799e72905ddcaa9d596ca13bcf738abdfd57740cbb2ce092ee83e6af7e8559aba513532f3cf86f6ed5339c8754468e1607b17ff2ee058ee640d499aecac865b77337eae40780ea2e8d5596c8fbd9d0613b883298b5b8ea187c7d01d33196fc100f7f461d9c4ac75a618c15de0be5fde9ab0a335db7ff68e00cd49aa6be5f9926f5358c8eb24ca38682ba2d7de59430798575151b90ab291f36d8d7e12cafca2cd7cd02e44b1a9987e3da6f6ac3af2c9321e4b44eabfe06484faca52e0c287a487abb48b9d164f45ccc07a56413fd0deba69382a631623f2ff6cf463edabf424b7b8cfecd0d7b170c69cd3da036bb449b9a6c94253dd89e7635afca8e89eaab4d0642f640ca330766481bc58daf251761d4da80d741949e2b227b6a622aacc0f5db3e28e33a8dcf3542811a4e611b03a976ed631a76c20e74ffa8b1ae03c2987c818d8930158288ce85128dd5a9b2d2ef806a72280d2edbf070cf09540bebc95bb719dceb11bc4db31c8fb6c316003bf2428db14d0ef01615e34edbc4661d609447aba2dccb0128e287460f626428ec320851e1900ccfb28a73117fd7d47810358a939ce932e454fff38d2996117c195877cb089cd25fb179763910b13f21c03fec73c981b07b43b69ab46bbca60858cb5c71266ac890b26892a7dbe6794ebeb22ab67c0d897fe36062e15ae566df2237692f6a7bf5d249bb7e7a112a43d594e1c8a4fe834a3489644379d1642fadff89f4c575a6c88a7b39d5c70ebdeb289b909b9c49fc0dee3f8da9882680f259a50c3fac6d60ad58af51a18495c995fc50f9dade0af0c3ed65d958ef4ab07b54714e3a903d30d97027ea17693653bdd5f0db85d576d7e97452d0c3d6904f34aea11b10f0b0fb69f72a875fba32c96c51142d273f143ada87d0c8a723036b8f9d52baf50345e4f60010c1ea235c969ac56c44065cd5d857b45e83b9f522a00d562a684af84814568d1f29fc8f40d5aa929fb0013e38dccf114e4ca35dd645bc97bba343c741457e5e23aead2acf2b3f8d627c6d903c14df20aca377395622ff2ca04c99bf9419d07f1c497ccdfc654d12c4a618ff986e6ebddfacb6c28ff49d4b7af269204058ccbf2df5e8a6b89220fb387915bb12ca4dd1788689a210051a370f9218acbcd81b388a5259bce8e20ec5882e3c440a04dafaf84c42c94c746c2005293259a4d12e04f9f17a2225194058070344451bc1f3310d09cbb25da02f5734e281994909c311e90a6786230bf2e55c697828756877124dbaa86fd8ef307f34e853634834fb6ddef27adcfd414112051074fc8908cc17237073ab387c1cd93f5a76c24ace5f4916666d93912491c5586bf697801101e96d81e1429229298e485cd59fd1a5d26c2fbed2e1bb6cc8848ddeb48b573a7f000d8be62fd90962672cd48fa4e4e7fce1990754a84ab47f3bf8e55fb0540fac7b209e542fe030c4826b0f8ca5c2bf154b3401958fbd7a0ad495aed6daa727dd22644d3184d2a01328d7248cd4beee937dc5a94c317108c5f8999fa520534d2fc2cf231472fa1b875c64dc3f7107f4e1a8772fde72f4337f503a7b3675c0841f711e2d8997de9c6cf58b4ae7cae8153162c8d3d4b828218c8d4917589ae0f2c9dde7766d24c0f566b499068dc820317df7a418f687881eca3360fcaa500c7fa8985c9f0eb286049888ff01a735b9c027ef7d8dd629d207268434c68ab7f71798591b48fb2833122d214886fa461c09179b4220ef5944561b600bac88e46d9e56e69bd20bce42504b9d5a1d26edc98817dfe4115cdd88e84a0a6d582ff16178a28e709ee791fd84a1d6ff3e83f768ed705501fa8141ca3e8022e4b75f83a0a84be683366f371169171661d0e3257a1adbc85b410c60ebfa0523a189d581c00e6c8813844c0eee977b6b06b71924029c8f8f8bb3e113d5263490472b49baa96bf6cdb4f406ffd08c5287fb2f80a9c9b67427d7180a5d91825b4f7c00cc4780a54547358f815a61b854702a227b49490f479250f4649b62649b656dbe17bc78cb5b59a54d1a14c053e508ad1a8001fa6051cb995aa69a62fef270ae7d72bfaa613aadb740e53ac91755ec5312bb9fff10aceb738318bd6b5ef39e669dee9bc7b98c9a5ace6d6f994688021b4e26e181ea1f7feafc36e050cf30ac139e6e7e164b053a7c3604863b6444859fd1a0329243b63b7819b6095de5f792973c5dd141ff31529d50eddecb5f9203733bc542e82c74c5c35fa9172b3687e9605c3d91a24a4038981d61a96c324068e41ae68eb8659787e1e4cbfcea8ea18d2e54bd647a4f8f43650f873f0244e4775d7914bd4f62aede54273231758cf5e2c46fb2dc7a77c962cbd523fcf5c4fb06a9fb46e7ab5130496ccf6ea2fac7ef99590c1e00d8dca80c3414bb075d9b26e22c07aabf0c2f027503e81fb328aeafb6f66a1d6d38d12e02bcaac7fd648f28a2bbe6e418b2f824d7b0e37566c501eecddf337b974ad9a6e8a330b00a0908d22cc2ce4a77f69a0f4d3fa9d02d4b9e76b919a7eb927af8ce858dab0d7882ce83d9dcc672776bccf44826e3b0cd4696899d4c0642339f1bc2d174512b99561dde96ca6d8850b9ca252fa580cd2b70251629751f3b38821b87106e204401e12b1a702039286ca243ae0e1aa445ac31feca4495307c2181034deac90dbb5641f1c513fc2f5a4b9a7aa33f62ae156cd9171ee54ed813e3ca6921d9e221a2fe9547cd1b0716f02141e6504c8d76becff18ca1dd5a16b96384c42597bf2ef406e138de7e7500f18bf96657c68e17036110f11ebd4ac450a6f2f326bc2343981198acf1eea0193116676ea6d4eb05455eadeee6800c8191acf43d23ab08e6340d7d8340fefe3747655557a1850dd911d5c5151f526c4407de2a26c9675ec34af1c210fb5afe095d4b0dafab8e42e955577a6b021e712a3cbbb8c601890951b98461f09b9bbcee23491aa4c3bc3212d20a45392b7061f4bbe7aa441a01fd39d845e93c83e2a0b7f4a2942863460993684bd4bbb69e0eb3f58d39e6b8c2216e016a0451205ed728b4ee887b4c98acb976579cc035ef624a87bf5dcfb020748442887e7287c6034b4726c4802aa8978c72910ac13f6b0b94c36520bebb2d01f1309abf5a197a81596ccda4ba70305c45ad610f1a2a26da880f4c442c83fb80056f0cc022832b3ed860be026c57bd17be5505aa5170061eeb6f20a205d8bc268493e31658fed9b94700bdcdc5575335ee3187bc2a4f0cc64f2b34aa84a96733ce0a2c97b00c0e6e7ba9208350116ce894b1372c90eb22fa85534742e9dcba79be12444e7ee5019270164b3c6c7aa980e210ab192ea0bf839247526750523d5d2eb5d65a1f45ed6a00fcb79174ca999b482b39cfc26d1bb6b6a2f975649ac09d9ac6ca3c70f3c9f031ce5d5275916bb06dd5a4b3a47eb17720050d3b425fede18d1227669e164e5fe244c5e91f3ed590d259fa8f92ab239f6be6c79c6a4d12c3ad8cc869daca64d602c1c8a84fbbba0eeb64c5149dc2bef21530bf871902f74217ed3cd018ea22eee2df188e3747f13210d42d42890650e06487d167af3036b4d0a40be55c6f54cb8d8a0d967c5bdf0eb60b3e7a68fd15c7ff2b733c331e87a17cfaf83f1447e99511fc19ec320e0fb86d50a452bd762537b904bd6e4138df76c1845905bd2027a8b015ae632032d7b1108503617c640bf0fcf06badc7c2e307c65e3133b6072244a36fcd660fc59c1dbf29354de486c8a7c75b632200aab00683528a698e770229a4f2a2c483cdfd9f41d1aabd02246c6b730b5faae820fd3a0853feca9ffa182531e8681c0bf6f9733e15d29a23825d28d61f51ca6b62b8f1db640d6760085a41c60d6ac3353c19376e8724b5483e791325f7612f8d8db5afc2e2732a6d16d72ee645e37ae62f9df6d1f60630d8d2bb42c7b779cf8f45c813f007fd2cc30c9c6b7adf4eccfcd17a5bfdea5dce16388be0b791f61b8eb3e35dd389f4ce7ede23ff3a88bbe2042258791ec7cf632e192c722d2950856e68f4e4a9fc40b2d450b85a98e264119161840c2bd6000cdefb36ba9cae387cf0f1f9176202a0bf833a5e4cd642042e934323e5643cbf1cd8071842dfa012b14bd7e39eb3f12a088ce42637a94bf8d3c425d51ca21959073ad93e8075fcfe604f682e626e7f6dcc60ec0543d2fb4953754c9a78da5dbac19cd877b7890d270078bc9efde4ee8c090a1b25abd579dc4690c24a8868c1e769524b0229d75fbb10779325685ae13d99ab930cc2df3dd97296885d21e2b7e8d4b916c0a40a263345140bf19d7ce7b92a41bb716276add5c6d4b89634da3c9c15646e095b613e024cdd9b83d9173b419504c9e59b606b5caeb76631587197eeaa258f41fc4039ad0022e4623477b11daea70f13bcc1fe69a3e1f259a6c5cf63dd300a331972400bc29fff7e4765e0cead72c934db4dfc51329b8c3b83fbfd62f88807ae340ed41e5173f40a3abfdf359f570fff4a412154904a1f66857916b06f4c2a6714f5db3ec33e2f7d382c4a8751b54bdf67c952bac57c1228bf2de551d0f8fd4978de6019856fde6b30e9f2acd6d9f5bbf5b12161e1f3c6335f0ada710c850ef553a8aa78377e8f55b92a222ec2aebf59aa8fdc1376318070ae208d15eb01ddd5e84d98847cf82ede2f906f5e076b71d08471c8330e6fa85d6b68f1c347d5c01578b0294d6f9bea83904f00f2902ba708634e6db1c7016c40453cc357bc08ceed1fc6491db80fcf3030c273fb305430910b35f3dfb0000a6bf03f526d7fed690b7774b1a21093e83b7e5ed120d5dc2072ccfa57109482c16acbe8e8c2fa3e93adb649c55d14030f9a0c88ebe899c23ef0bc3c5193b800daa91107656b6cb3d399c4b26aff317e2be07ab5b8ca138cd47d2b1f128693384e46b1b7eeeabda81c51e5f6c90440d24702ae381f73d7d4ae187543898153577aa442dda5b703144aa77e720ccf157ef6ca62f14b48cf66adf91cf674cc8e52db3da479451b5746bfbc27e94e86ec58555cfcf44e121de3ad2a9899151afa20c21a147e6fce6764668ca379d614a0cf4ed823b6bd51fce23125e8b7ddc2090799df69fbca0b32e486d466270e7c27d1a5c14731eada44058f02e2af97066ec63c6a42454a091b083624f763e0110ac444b35f810351e3c959b904f42def014728c7902a9e93fa1360a1cf513c1deccbe5913811cf9104d0a97487fb2aa4a60b9d9978ab1b73e6f7754383fdc1b3632cdab385ed8f10f55ea0fa0384f5e4e4491799ebd399d3b1691b879fc115d134ddc4b6641d2fdb0df278513686391f18e368be875d3bfe0b85b312426127dcda4c330022eb7f3bae9030ec98cfc350442a080b5adef18fc61874bc4210b32ca4669bf5a14a64bb7361bb0981cc93b05a3e935db679de2324936484b832986ac014fca2d73d7ab0b5bcd299f9ae617dda7cd3217b7dc16d12e9984f9dab736d9efaf14316ff8b029b3a5537fffc9327dc30a5a09e18d28faef80df67d56b86e4a818c5d54b2afa322d399a6c44ddf8df327b19ed8a6088fd840aadeb75fa9d2354a10a8e6497e8500dbac1120f7ecda994af63da3040eca226f4efba531c674b8a4b96bfb486dcd442b686a2eae629bc424705e2e24b8490975865426a6e0a819abb8108eca2cad46650d6a330f74229aaf1695cdff375a723c86c6fb9b3fb10f81698f0371ff3e62ab8a32b0e36e056897435fc3e5cf7d313a66f71791458484b1db5ee0b48af2e87e5781572b8791a8e2541d4e0b4258291f09b3099587d59ab7af920d4d3652c971548ca25ca491ef073f764019382d026b1ec2d01ec0e06f0ec072188ac19928105f4a72e457c424a82397769ee3b2e9eeffd4d63150575e01c710efc970a5701d8e86388fe5b397b7a0639c1c4a30ec3a204578e5e995120ba467755ff0ca3d03e8dd3de649225ec8ce2a2dba9ca08fda8d876a50ae5ddf79bdd83a44e0e49041645dd13be93bdbbc590a5a9f022d47da530215f3ec1927b1251fee0ca1b00d57e39097746a8d1691f8d7d197949a6fd6be9107c0a7f5978f6df545043c850546d3e5444dac1b96bd5019245c23236c0640b5390fc7a5e3a9ce1b37b964dbce24b412097426d0c376063255597d9b23af36e4907c9e65356e73d12da137d8de31f726f2c5cb116db2dd67a8f11e2da327258ed1a1d8a1a804e20901caf87cfa9afa955efbe3e3cbf74e4320dd03ec6451929cc2610f77527d43dc4b6de0005083c3280ffdc5f6a3d8046ff712cd53ec583428706943241ed4ab337277458cc105efcac390128e7b6ca697cbf4a8df9d2f2247af9245beedc7d8e254e34b14d63e9d45ed34ff4117a082101ce92ff20ab731fa78019b9b14362c9c50ac69148c7d91f559121338fd47d869f7fcca76c39895cc1273517f55b06aede142bbf39628fbb82ca72cb660e335a756c352bb63938efca412488134796cfaae6488bc0484f56977d130907c5172f7a390519cd7084ca26d56232ce050622e400956e8e6c72df5041fe05224223994abb4baa590f0f93440a859bb0d0cac2caf7493810a0bd896bf604699aabc61816b56540d85fd53c7535ac1782a774c62c0b04c332a201e8058971db4630579846f8f1c1076d0314190e20820de76211c807e53bc0e4c53d14a617c6b41f673efd6ea0d20b37e1298c274c5acff1b8315ffdc063cbe6369a92955fd346d8031816d7d010b4797f0b3e2ef93cecf8f947c982c6be13364a86000d4a9a6b037d4f737b39b2148c6a0ff6cbbb10d941664fd2c4c595aedd9450d2b42a4b72e97bd176905b84ddc96aff99a95066e0cdaacf208c1f858011a0354e95f400a3d953306ea2d3c37b123653f35e6963132caa185ae3abce076f6680abced140fceeb5b01ff7ed39b18abf3af86e9ef28ff8d21147ac4616935ce36256f633587d462bc4c3068ac668611917d9b83e7cf728ff74525ad559ae819f73ab2a48fbb3401bed1d0ff51b9aa068f37e028a6a666bddfc60eed03e8a0f2ddc20ac80525333f08ec32472da0fd387ff12814ef22806ddc50217c1153b3b74da0f96300cf8007e5cdb4c2ccb46aeb1b118f99ebd9a36424f1c99fb71dad2a7c065343eedd8ea57a10842c97ab58af2d9647da1060758f9a3c8c9a35b2659d6e41d6c87e796f0f26cc32701c90890f60fc85d8eb734e60b552be4e85931c9fc2eafd162ead9a92a4fd50267f0eb6850e4617126beb9eb91bf01822b7687c896ad9c57e3044dcacf6bc27da3ec694c01296b8265a05addda121d36773fd3c26ef8cb5d392e1c0dacbad99f67c355efdc65a377a8c90c37dc3813886530d6ad3208d2a7a753ba45e3cfa50e7922232c131c755ae7590541382f1a8a2bf0f5a55da93fdf78b80e8b997f1db6648fe1e06104ee3bab3535a672011771df1dcf4f0dbccac97011c48ec192780bb06681ee7ceb805c46b336e6e63cb55331dd443863a6d833d4107e109d68a774df0543f1985973c3b449f2d4f52a7dedc3079d9f33751a99a939a8290f9b89c6c59b8bf4f321a9ed6890a15511635183907012f329c7b8b20f7348b2827774564c000fb05cca4c73a9a06177a6ab2d7ede3fc806b5e8b73a86cb3046b30fc6996061126a4fa044b7522da67c04e039ea25dfb13fc856811b3d6dcade088a3a3b8016818b7430ab25ad0bf478746a3448e2223595c51a5e90b2dbd6b2c547bc51d8b47cf57a82e6e7a3b4d9fe3fd16ae881699a8bcaba4b21e5bbc0fc267f9bada53a5c29d9531f8c8e38c757f11038c3630775d17ed6ca5adf9e8f3f6fdf32bb53484e8ad8e16fbed816a15fc688b5ce4b825568154f806349b0f9ee79691a7e339c41959b468411e4e8ecc6577092133815b3be633a48dbf358bb44b1707edb3609e3ed5597e8c0102bb1d0386e7990590181edda4be1a061c013dcddef367635cfa7cd3b5694dc81a77aa25de23c01132555e0ee02e607b3c129a27a6ba737879a171c2d80e5dfc1123d06acc091245bfc637e6160a625e4e3cab6c1e6a9d36df7281c90f3f69882a76ba8b9acb1c62ad7087871d2d40b9998527265c40912a9e4642bdb32509c3418752152acace78fc26c7186a1aeaa381eee5e791397d021496ff4d1a9819e0cd40c5817735ca402fb4a7e009617b141754a4129c7812d364dec8e493ffa25a6031aeced8494b2b6d478a26301403a77aa7f090d6285a12a7eec4590907808a96017405e37cf8db6bd2361163707c9131ba0e6193607864307f21f96d93d50ef460488bf81c60f0b43f4bb8782d9e6d2b6050d6aa782c8212bc00f20ca8226b814d07bd1a1fc5ae9c29a478f9beaa6a529ee4c749f1a82e94496de9e7c4d1688232ca9e43af132d377a429ead90165a0fb7a57d4014905d9741ba826d53542aea96e3957e202a2ba81fcf11babcc6b1b255c7fb5e98bb205c2d9c2942ca64a11da30d03615cb3c954a49b82cb1c93299c4c8d1174bc34462120fd4af318ac13126efa5abfa2ef7f08a9137a8732ba434882a6f36a8ba8d20083b5e8991dfbf72b27cfc84ae421ac3c57b8dc9ae58d0aed0fa17ae47c816dd098cd6224482a0495fe49dc026e13168f7ce6a0c35973361b66267dbd6ec892a45dff7974d58d261b6134ea15b09f4e85b059379365a56b47043d46b33f148359f4c8ddf7f65c2ef6b016472d3cd71831bc50d385d3a4ef6fc9f7e45fb68a1a598fe71232402ecdec673e8e53836ce4bf15ad0eb4de1e9285b9ac11b3871ae381be668e20cc96e125f6ec34694e62f249a911f4a821851e2d76f7ac90a58c1d9abe8c32162be12a5fe53994aa8f71882117001fd4000d18247c405c9efe5583f46ecf9d2628b32ba15b6cc6dcfaaec4fee68f8b9a338660e9c21ef0040f3597621984a8a8b326691008a597c56e05b88d6b71fe3c34a86adef2e903d875616f6e09f074898bf525d52a1816e52ea0d083b7915fc987023bf38099e62803a7ecfa0a5d7acbb2f9ae9b533cd50b1b521d4586ae9d05513374233cfae836ac0362d6064e60e5790181171e91ba8b440803bc006a0d58b0041895352d70888958c3f19c5c7ddef353be260158e869183851bee63e3d97044dd3034573c7dc199db73ba13c3a1c452eb8cc6c62d30d064abdafee0dc78934d184557d8c35590db5fe50b0c68f331abf42fbfffe2caf6117459b72a601882e611ae0f486f467758cc2680469dcadaeeec89ceda14a2dc6d8371b905a81974df1f998c02a4eb35e99234f5d489dc93ec5025fc90306feb8bf23cdc9c47cbbc7e6076923b41e6c184e252a007e2cdceab91fcb5048f03ed5af5de7abb6732110eb1559057dc159ccba614cbd81976bddac88f0473c402a8937f8b89b3b99b8e7abae9917d0c0211c34ab815b2d94e2903638515e4850294ecf895745d0fb5ef762ae14705815bb40f063d9816b34ed3ae48262e16a60d024343525443e580e072ff2b51c0235da226872944b5b41396fe01886c05cb45be8957042fdfab12e76196410d80d7bdab600ed247c4150d4b329ec22c50a0027e87b90973517d0d17c457ed687abc423b9cb0d00d65fec7ae039422b3fe16ef3e2382d2ffe811c5aa7dccca38c0ac94f8ad5efbc59bba94bccfc5e8d811f6f36177e8a1a7d200437bf8d1062342ab4e0b99b8ee98ced37c43e5ed57f10adabe0d11d6999b8fc694f5fde6ebaf295aed82eee618ca63c8d08104ca60937e52c316d3d4d64edb3f24b18edd464600182c8b4e3a69e95ed417318f2bdeeea185320ed8b94fb0b367bc1f73e5a6b4d45b5270701ae1f796b4b013d0437d826a8271f622978f7e3f27c8c5a70dbf3cdf86bc150e82afcd833e3d5d09714797e4949f6d1116295af7c28b43231fe87baa46ab506054f054604ebcd001dc04bcfac52c2e63426597fc6905c8a5a5c0fcad708436be7bd282268025e7629d9a4c01aec8eff1baea183a73f89c5befc39424903c5cac0e3fc1cc5d472d35e0c35fc28ecf90c1eedf552a6cbbad82250688bcbe2fafec354da55ca01072021b20a4f86755262a4ffbe17721abb55018242d3d2d73184d10ebb8b2618e6428a65a6629102933017c496615b77ada36bacdd6202cb175875d2c771304acca65cee694235954240a823b69a164bf1f1a05b57f2758b612669edf530a1520fe11022690a3c31f4984abffdc8a4adf320a57611fbe7314bc747f9a69fb637f1a36dc92d64eef83899cbe370060611471adda80124c40ceb35387939642dc9d8cd50b3e79e39b8544abd3f7b858cd4294228eedd2b84c42cc5d62109de47639aa31eeeb940306158aa6c2508fe48644cd33684482dc8d671b0a4a620e3a35dc536f2b7e7feab9bbcbf0c69b72477e73626bb831ede568363d602ed999a77bd9ff3c4a77c6da1cc3c05eec32b376ce16187ca13f5e21af7b1c2cb5a4b2296c06520e11ba1bd56e480b10d3cf65d19175631a52eca7c49b01cf4f0b63af003a9f7f534b38589832612015feb94ba101d85e244a13129d492bea2680252587da254114ed3adc26ca3d56180bfe47abccdcd85ca8c6d09a302951a1b477d7d97c0f35b22a248626af64730c4c1c233751ca37971dde55b7740b487a1f2299a6f086e4cb75cc0f50ed7a37fbbfe73cae9dda3ef1447d16ea041700a59dffd10180d8e65505f02e4224d4f36af3bb68c3e07fa03ddfb49d9e47116bb12661c790af4c75628388e3a70745f2c0f471eb56e813680c365dd3567cab05f6934915096ffe2814efc926e30848697d4e8dcc4256902d415b33506290d9c8022f3996509b2b32ac5461e910144d3b3ab29b39a5e2d63395ef00e87bd8f429abc57fe9d55e20ec00b689829eec36c000787f60faa4cc76644a587e4da14094d05c3d571e15b7914df111b65f5290c76041a84d186de0756c622b34348f6d49f953bfa49978af4803e0acaeae997157f4897ab4f4f367751fa6dbc84e3981bb64d797a16774e4e35a3a2b2643e447f8e575a652c905cf8e2c4101910d07a5d45f65431cacd9d479e3cd8e57b8c61cc550087bd945f16e087460d16dbef3d445cc5d16bd7932c3639f5fbf4d362735d3ccb7d3503b7aa4a6f843b320014130c05c8c898c912ff097b178400ecb53c562eb675bc440784670030723e8ae7143f99ca8c701df436aafa15aa6023241ee24e6e7e4a09f3a4506326c605a78900f03980500db2a80ccd0d1d98111d98d2d181d5211d58f7d28175990e6c3c1d187b1342d23729a9f535903f5e907de506c16d3423c7c8186d0ed2e6bd1e502879a287d6914ed6dd011646ed9635612df6c083ee74b18fea51111cad4ba68621bd4479f87e3d67dc91269daab599314c0f03d999c43cfb8d3404c20ab7dd1fa06d49cafd0b08969a0e03f55073d924e6894d9dde17bbb2181028cf889fed901d8da7e4c53c2e40aca64087ca9aaa35e25317e232eab465f0f31821b6b4f6534d80407e179f247a4c261f0aeed066957395686b155664d1decaad566e7f2a13b4e935c93fffe245ed99aaeee693b22387f221023bd3ce333be713e7a74fdb327a371e5c1a91a49786061c8a1ba9ad00e2d339bac0b52d5332e146fb538b381309c04c5841d818a59e3c60c53f05aed2174840a549583c55979348064e421b7d129e42f5c51e1981787c5b0134b1faaa2af53dcac94f1e0084e7ea428be456327a5fdadeb7553feff89fcaded8c17acf398816f7f8c8b9b9c259463f8937d6fa2903edcf5c77db91d20f4298c2fe1b344bee6615a843c2a29e852615490e2679aaed336041a4ac481e09536087b0ed2fac283ff17672b250e3f4b786527f1b90e234350b93f9be01f8c60a37656e39cbc568f6eb53d7f1ae37163060d49e597aa2e5fe0454dbe946fb56d1d25ba878fd9db0e2045d08e3c753204671e2fa974a992fd07e2a813cdfdd03d124a588acbfcbf28c7f6ffdc3b8f73d19b33a42985a48fbc7812074aba2c8bd7635119d5b5bf42fe7395cebd6e71dc2898dd432dc172df94476351b7d7d388e8319184937154ea4ca9446ba4bf09f11c31784739b32701413d1ca967d921e8a10c4bcbbd2d162e2fdc0ec34a70ba166b9ea673d0d93a0682ae674b79cbd31622b9981fc573153398b1d7e5dc7903e4ac8c1e686486e54f0cd80c409a9eec5546f5a4e7946f0d00f21b50a82f8607c2d3011e8952e8921c292404caf2a9ea8f7256742d1fc3ed70a9e9fc71cc4655af18ba4ce416a673e316bb495a559199d20171ef5e6b26c48ec49949822f9c2759a3900f4eeaac0c697cae0255f2b437328af74926f8e5a3d769fd1f080681d2233353ade976c74d14724a175eed9b1afeb27fbf622c918c77d7a34c33204408997175c45ee490e476b67ed89ea4965b951cc62f6f92bc4c6c7b9ca40c6a39f8154ee2faee8d20c5238ee821dbc5d11547fa6831c1e22b0294cd94b7b1d47fde60229cce4b39147ac0b04364205bfb87a75b33d7edeaf578db825470c6a838cb8c5cb909bc2c3b60ea6daa23c0d55cd9da4001ebada81bd16eda78859bded971d0a3958c17287a8ba9c59010baff815228ab9e369ede27b3a198648dd5f4a91df4d792bd94a12854c1a0e3bfced242dfbc86449bbf05192e4ae9ddd65e465cddc148fd2023945da87aab0f415be336c7578cf1acfdfb48cb7f7f13b98f45050d9391fceec299acd417f4bcca242406c160d2a042abf127239617e9595319c07b207be5cfbb02ab39574f74a71192a112ae696defebfdcad5970c2801b2668e1e6031208481560f4292ce2ec28375df457e828f5da93c773ad06ff5fcd08ede2b319502164fd3bcfb0852b118ed0cf22f7a5fc4373e23048206b4ae84b1fa6ae6e1256fca587c7521bbbb3d42d4189df6e1b885333fc3d95bf7a1aa77283329191e89504aad678811547f456e82fa13b23f271a2d71f1f65094b98b6dc39cc5efda71018b2ed888732d213eff8ac7b958196fdd4efc5c197328c2c86f31490a6bd0b47b79842f6bab580415267004108a929c0f52e2b15923b48f7406a61ab4508a15f98c19d5d8355e9073e257d9e76b4dc6741e1f8b8b7af99ab88321e26db544ffcd26d7a90d0a4062018aeb0646b53967ba2c5e6d09c0230b625abd55ae045f0e12b9d15401d93e1c5ff64b563f8396e93d3f2593ac1d763cded13d74ca29bb131013fc4b511d610ea6eed9812870c3c8526bbd61de45527c195117b8ecf659745cff19b1cd1792deaec04979815cc29c226aa3f0bcc62716d050a254bf1035fc95b92c5c3089b65fe6cc7b0655608f5c392c9fc789f9772d2bf44ed4046bc6b61a85940327e05ea5f314db59f4ecdb077165849d355fa325d4c8ea6d7025957a25dbbf2baa171dda648e23343863f5596d2a0a0a161454d5b9cf41617dd0b56422d1858cdb3529e7b1bdb5aac0057d21e81e1749009dc9cbdc13969a109b8c9f624adf194efb0cf3b613492190ec98890ea0acf49dfb279ff06f4594988fd2d381086bfcbffd777cc2d0a3389ad64024a00f1e1549f4d8e477bfde2e64a689ba2365863b780becf004868022b7800d00b868b9abd599ae00b07dc44bc902054125b40b504bba011cd11998915e1b78cd9f54d3b66cb1b608b58797eb7bd8ae97cc76f564ac7dba75c66fd14b35db1f2200a523c6d6e27f2bc884e1d23751af9dc0226924a08dfb0b1a62bbef78b8ec337fccc0a97dd3f5a4217b326720a468485e00603c81717eea82fdc6dc2c3fbdd7ddd403a8cef85d0c652ff13ec07b8fc5c6775bd62be0c23d970fb20855847534a8aede825e2c875fc91e7cf9e1739a96316529fbaa9490fb5c79847c8a56ee70a849ea22e2e58d50d8fc48ca4f878ee834b5bbb1416328ba439dc36bd0a9e402da22abda2cace46332b735088c58bcb084adc4b8ffc71f5ee942ac0ebb9537a6636be2abf9b2fc5cda45d7773bdf91b85fccaf1e48abd9b6d16f6e903718b5a77e02af0b9ef72afc124c34776678b55dc8ee2980d9be70194acad72083544930d16b4a3de39b35b61c8d98490f9320d2e0f395051b814278c275cf0cc9537db83cc9de36c70e52569c45a79f18ad635d36615fd59f20c638e2baa9f57b7bbbd3bf99cbe37bdc9933f3ab3f2cd1c5912c96511aca1025ca54f698bfb5b454b3bfada11db2cc4df6e4edcc86d92e1e35cb29934fc157f6dc809a987ca85c7aeb3335e7cd5ff7fb8d5b44d7c4d8cd3b557e85c791d9ac5576ccbc34ccb12c5996cb35e93fa65e3999b280cde07a3ff2a4b698263e04bb203c2bfbd658fe2f006638ff44dad92fc7465646ca69471d8737105561574b30441b132d0c301a842390da0defe35ca7fcbac9da68ab71ccdeb7b34bc978a5dd39be808a1f68645c8edfd95d0ac463bd42107032bf2042c8f10a007d200486ee1350b31a181a498c1126f91bcfd1cb5d2c646d43552760f00d797f7dd799fdfafdcbfb4f3bc32eaa1d346abf49f9ad70bb92065fa815d5922778932df0b7bd4a82fd0ec7fcf316fe6be4ba790cd57f825e7f4da7b766843396f0909f45d18a37ff7e5ce0ba6f1e980484b04ccf7896d9f0414ce8b6e0ec2cdf9d7a39b5f2522e148c2bd6be5646adb951369aa4cc5334483fa1137bf0669a497001891e4e7398da26214669c97e29dd9f370e7187510f662ec9c44ad4293a143ec959574156c2a4dad8a3494da50b9ee89e4f6467c4eecd6afc23ac53f1ef50701d2c804b98450cca4038d292473e91c24f0462ba1dfa29aba107124c96ee3b42cdba698a2c1211deb36adfd28539541d225d0a98cde0124edd9a18810d15e04ba18b030c85f61c63d5dd5b33c79048d0ec1fc73061e13a9c34709f38c525aca8292a97491f389d5a439f2f60e40985d0be492fbe17dab405a8499382302fcdd1a53c610701184f68380d0d516d0e6da279893aa43d5cfbfe408ca3abee26d9fb5f61d14d2dbfe5a52c9f5b1873613e5237283783333cb2f7228de4f4d8ff1989747b141a2240c07117f59050bfc2f30f6f1d288070748bc7e42fa09b71baf85440647466580e9f8386984f860919efc2458c2bc6a7ffcefc2c1d55484cc7d5be14c1d3f31a9fa7ff9bc66b2951089501c8e495726e22fc6c38661ff05b4e911e755d69049b3668927be651254dd9e763192a41ba1304ccc7a89143b1a1219a33b1496aac43666c5afce6379166e2bf7c3e105dc320a6a55689a8fb4a1f812c171e98996e44317ff9230420d4f15d72ef8e4366fd695ed45c885eba3b2dcf9953d163cfac27e4bef13155b15de310a77c05527f1bca244fcbc67e6bca0a764ddf41c79a75a55b143ef0766956a7449a6bd3fcfcebc075a7e51c0f5d76e35c8e71f8f90aef4c6e55b61331be6ff748700dcc2ffc380635456c557d354bc5aac5e0fc2634b1e7472062bbb2664b465660f9362cb7108e21aa727a4d5d9ccadc6a71aed759a8ac53ae34051727b40a169b110161ff228eb2884e8da5b6fc2015c21acbf8f810cde8c06fdf71037c33a92865b1bd8a77e9885b08540422290ede67e6fc62805e8c3ff1b2a41f8abdffedd431d5eaf0265622612ca60de91198ebe739cbc4ea5de15247525976b3259c7cfeba1ddde7a7335d48179526c25ba41d86949cd70365329955124bae96ad5a8fe198deefb83b9fad533f52f620a6301ae78946d2cc9780cabb9fd25a32813edb1c406dfa2dd811af970f6a43413c1b8ca85152c436c8b663df97b041fc000e164183276ccc9b0f59670885276cfe7f6d3b1270fdde13cbbe2a0f5b28a3ef9fe81afea499ea730436e7a2e3082eedf596460c96adf27a822b9203b03fbaa8c6b5e2829e576c5152a479272e64205413dbd2025faeda7d09daa282f4902ad86c6f5efc1a5053e142e1872d198fb0a9a342262cb024ddb3047027ae4abddf351bfbcec12357ab74a1fd7044b4b2ee645627c5f3b6b35888a9d3abac126ccb8967cc8c1c1db510d7333ef6bcf40b86646294b8bb1450f14712c06bfc46d486b9fea627696c6de7983ea9282b68f83d0d9fcf82f8cd7324d4eb86560f3aed862f72e0905253bea451896f31d58c80ec188ac9d2568c76faf8aa3cf3785bb7113141a69d6f936890169464b8ea42cff007f61286a56dd734009e7d26f9fb46319a189304c9962225c8f68cb228b1e0b27648930370d5cf819aacbae24c7d61b3372598a9c953ac2f9174acfddf39dda9ce6e72ad082c6eaf9133953b681efa33956c92fdcf18874cd5cbb8b8052053b79ef48bba17d0727f7256cd2ce078ae0766d4ff105326fd3a489be4144d4f5ef74452cceac361c86b2a019f0af884dc6fa1705b56b58b0307b871bab8c7c628f1fe94e5e1168ca5d3a7392b30a0b163256c9f3a22eef0f1646c0e6820c2f1e3b811c3143cb2630693afe96bdd813217860d4c964d91d98b125f03d884651962f538faf9e568ffd8a4dd007dbb9ab929b06db964acf623a92dc22d1ec482b3d1100eaf124bf43c410be0ae78793097c50deb2b14f8b7e91a4230c149f184ea9e8396aa67cb3acac0617dcd23f68c2246270514997e8b98ffe89748732e4cd01f12345fff018fc97dbca47897229d95fff5230df3ad9f46317a936b496563dd12899bc13b972b2944eeb2802de8944645869fa95432b45dc89ac4e8eea985b9ecb62cfb9028e72bcae459a825d5e77b70215bbaa77b00baeb83126edb01d38563b1bec7078dce28a02a6b2019d26146401757e60772e44154a65eec9f2a70660e95d828b6062793f19398d22d67efb27437b66e76c21b7d1766c32525ffee4974085a52bea6194cd7527bf307a609980dd5489bb7de804af346caa717d4b1b2f732ccd27b5bce401f238484d88e059196b6da610f0ecec6fccbf4c5ea5a456043ab4afc2541058a98a3af02779dcb0cf3c67e45740a76bf892b428b2277cd0e981514f9cd210f05e748895b1dff6e22bea531efa33e8276efde127ef2355bf3f9e519c772a62d45b919dc7ed3a1f51554bf6de7b6f29659232e90b470c1e0cde75fc6a96cf2f50b326e91339cf513426281b0a4aedbd175a94f750b5f790187d59f3a8db7ba8a2f7ae4f89c2bde71f7595df7bb576ddbc97264fde2be3cb9a4b851903ecf321008c05040c301e1fd14b1cc47b6eaa3261b879fe8b50b7bc17da0bad592f42a623d450bb4ad3114ae82ae918c4198ad6aea3d933a71806add84c484c9749660a6ac27464c589a909d44c866a9934662fb4175a95c151d40cbca6193545613a7aa1bdcca68de7a62894bce7de0b14143543cda6cdd17b61afd8a68d370bd231bb43d42bbf54d1b4f17cb5b244eedbeb7befad96ba8fbe174ab3fa1635ebf604dcf9ed06e516854761954605cd1e017c7beede28f7766f97da7a9bd8e8ad3f1a60546e88386bca99d7b7d7a64daf7c575badb7629d34047d817e7bbb756bc2d16c324289815d5c809afcbc7d359b3d2ebc2e936eb98cda45a5c98b047555433728df235786c3269c94b6d63f6c3d26c8e7dff803f5f1ac67c29972957c59f28425d688c2640917f8b49b8e2a29c409346e67dc88744185277cda4db8769351fb2c59ef4d52acc96834e18070894b13ee7be6d5810184d8832e4e9729b8f33cf7d1dd9e0cf47b566ae9470f3fec4872b147a887d6332476ab6c18765dd83df678d2c38febd65d642e489a54cd3c18f42018b405fa1861147a9c01c4833dd278d0bb1eb6520f70c9fa5eac243b05db15972938c63afd1819bab7630fdbb39c9a803e5f5cc24fa227983cfdf2167d9476512a102539f234ac5293eb6a72272c70e8e2a08b5de944c3d46492cd9e1c0f3a6bf69c1c741e96ece3661f2ed9070c4df688d2ad49f670325ba08330e8e8b4d0424d8d8a75ea51c402decb82f3836564350b74d683ac1670c2dc5f67b11efaa9f31dc25ae9078bb593257f3f4bdd18ba4cc12f3fbdb35dd74c62f4893774baee6103adb5b6874da692333e239367dce62432dec9d4bad2394a0c5c86b9f08d76b1b88c8333be72d0c55825dd417f199a3dab1daba10757f966a8b64067c9373734bfa9c9e49b596d817e33eb675ca660db8d76c44160199f71199ff1f24656c91e5ab74007bf9525e066cde41eb7da025d660cd2c3565ba07742e0764d2937d7dedb8139169207c32a0f8a46444a6fe72a6f642d8037b276953db4ae87d623347b7a86a6e8650f0f1ef49e2ae60baafc03d42dd0c3fc036b56d79576209936a08bdeef7062dedc58e896547a20f3b1c7a04828efbbbb9de3efc2aeab81e3c19a1a274cf20756d3437b528509fa0ee6c8772eb1ef3cacd1173a8aefdce5357b4edfb90cf9ce05f61d9d64989b2dd0bbce9d30c91bd9149ec50269a90b6bb367c575f2326c5286b60fc35b8f0e2f4daf0fbde8c3281f866118b27818fa0e1426203e74bd82f0e187e18974aac99d4efecd1e173f39387b72f849c64f337e52f9e95403c79f7c26cb64553eed40b143a6a305c583ae63f6205873346d82d4e46a0bf41c85eb650fad4609307463da80ee3286b969f32d590f4d0caa890157635b3526db83336e887185d7700ffa8f19f3851a25e64d94fe295a028d076b9800a388d9736ba8fc0c315d356c4c165873232bc9e03d8bf7b04d9b7c8463c0f5c8256068f6883db40906ccc680bb5eeee8a17d0f27d306480ffd3b76ecd8f146bbc231a6f329e543079d478ce8333e7bd8664fd7fdac648c38c62c40e52b7f808ccf8c2c9871195f8d2cd0810d332ee32c5895f8cb3057c99971c80b7af8a81c467d6456238cfaa87ad87a683d6c5e625c93abf1524c1095cfb7a17d643ef42eb3fc4d827d85156a6a48a9efbcbc45325925aa482e53b0f7d35b4a28540b0a060646bcb71343510c493a50c4d08aa168ad15dd47f7b0852717d19ebac0a19fbac03b662619e090c524831db32eef905d2f77c8aeb3b88f0e6f64aaf9e0587aa0cfd265c9837e12ffa6894986b97a1acb4f64b91d8c7719bbcd281773c94db9c5597212953fcbf756cca14387c87222b9b89c5872acac8830a41c9352a55a4aa898f15744d19444e53e9a8505d79a057a13d79066813b90340b743276c876cc7e80fa27d6ae9f98b5568af0adf50f2c77f8116dfe8959203f40e34f4c8aefa173ad690376236bda80a0b5654e101ebc7103bc71c283374876943ce84bd81317d8fa890bfc13131d0c6ba618b65ef6b0896e72530cffc44cf9076604ee175d0cdd70a1cd9e70f650d7cbf5fa016ad78a83fe03fa0fac5da61d33981e4078d0e9247bd8660b1c59dcbac89277cc40f75c8059c0ce59dc66ec6edb81a45ddd9844e5208b83be43d62eeca08b94b4f23d7e3049af750b74b007190fba297fbf43d6e4e703fa8e194b4682cb0906ec4111157aed617b56018e660f8c832f9eaa30e158da1eda83ee52f4654dee41cf71729d311ed41963de80de7d01ba8e1d2876a0982cd0ef4b4ee5d22ed191cb1b281ef42e97376e3ce839727973f4a0bba05a7c967eb48c2e58b34077bd9a057acb112e5daf079de5946330aa5938ccd51ef430671dfc709ecdf25c956db73c37ad648cb2a28ad465ef676affbd2d7533b5d274fbeba5098a0cca708bbfa5bf7e87eafd78709de5fdd7c181bbb1ec9103590f9a64d306bc913de8e0cd1ba0dfc8668fcb837e339b3df741bfc9cd1ed2839e739b3da907c517e8220cf43007769e81d0450f734dd22a6eafd70b081fd0c3a32162ac5df7c149e484add4a13224427c495ae2c89722ecc127605fd67c19e674be14831e2c45d9839f4f29ce1e749218f6b0b58b3ae83d9e80de03f41ea0f7c0b5cb3ae83d8c7ae4dad539e83d40f741ea1105ca973da490f165cd185ff67823085fea7cd9c3f6e0edcb591bf2e0ac01471d2610622dcd8034e61427aba95f20a6eb6b163d8d39deda50d6ac3ed170f97d6d4b55ec6ed7dd1bf35b0212d26a4906064c1fcfbb129024a027f15cbca4af54adb5d6340482144848bb40c0319c8d3136549fcfe927b92c67e059cc9a68424bbebdd3f8f6f284fba6cd58863033853671d694acdc7bee3205c7d0b87c8e176368a8ff79cdc07fa5fc1f3e022e6fcc412f698c4e3ec09d9721ed3d37c9a6873956502843d22e927b1eceda25ba177bb15592c6a8b684a66ba5c664bd2c99ae15ae5f3c7f19aae48a7b34b9f7bc7cb9bdf7527befab2deaa229afd4a864ca57b8f66872a6ee1bcb1d2e4df452ee3ecc3b5cda8b4ecaa9a2d9f3b967c67bb8192dfde86104348e4fb2e23e7c9fc4e42e533002fac5d1479b32ecf3595982db5bac6eaf3827088220d875d6650a0efbba4cc1610974d123ade495946e792b5cb356b866ada484b97c84e6a85d6598a37963c5412a1dc38a118de7616e82b665dd246bb9a19640c950485a826092dd215fd218d91b9b3da17b0e7a1e16315d6150b36273bac22426cbf3f20e09654b6eec3d17c1cc0a936051f9a076b1d8982ccf3d90c688c668da789eb70a73ef799857b869e3d19126c7b0e7600680d1b4f12838962c24de337acf5941d3e6c4c488955b9122ce9a5226f7d55950626099a0d993f375aedc3015d55b8e6472b40a53e779dee73edaf4ac8e0818a792d53be799a15d956ceffc267ae7a6a0d983f35d6675eb7385def90d146812a350a56aac09339849cdea7c0587cb790bd22ecf3bb7c1f3af92edd53d4f55f2367b562dbcf7b9f75ee73efabb610cd2acce947382c630f5fa36882b540aa59219cd1e1d3b83185512077fb97b64e90d740c428fbcf523416e6816fd7bc328b6817166cbfaf7618c636df7bdf79a66cdb226da5b1392661d814b93cc84647a13416acbe6257012cf7125bd314890fcc2a549669275a34906e4f473da94a6d95bd47da19af4daf296232cfe4064ddbeca506bad5ef5eb3eda24fb96dbb4a99ea7e79964a626ec6ac8d4c48ac9ea0a19363c2f5735168e5e86bcac1ab2a38cd1b4a9ee8de537f4d581b06cd3c666551b73d3a6662ae01245eb920c339fc24d9b2a63d9584ea60b3573a1a298acea25ebc95747d1be3a0b47b3c7f35a33a14c47be7a75d4ac5d2624e68a93558d8528d068562cc4b0cda5a06953bd71c556c3203a7be5ded1d23c6956a5b9d1d89a55bdba486363dda2209ac4693a2dc174ce5a6bb5aa66d5344bd5ace935cd9a735a21288f1238a542fdacd652162570ad2c4ae0927427ae0c128b12b5d68aed9cee91d13206ee7afc4052ad354aadd5ebed3acff3beef0341d614c33014c5129016a747b23e58c7a8eaa456fae453fd63da4c0f9522e1b6ded53a96a4301449a58a82541cb6a6f0e779246ca75f4babcd94de9629e092f4b43fd27fb6f6a5b3ab433e02ed303dd03afbbe18c5a12fb4917671259bfa13698c44636d1ba22f74137ad39f1bf97a2509f6597a00f5c1e30f2df0318d36563ba892434efbd4ecb19595df0bd92a0dcc55499d35a12f5428a268f2ed2b46987a5967d456aae1fa4a6e9f39e3f87beb65c2d648fd251167bb28b53ecc40ff82dd8334a873210f660a7a7b3e4b54c8d3ecc37d6fb447d8915475405fd6d633bee401ab195fcea2ef2efa0e18b1ede026441150a0e123e4082586901d4142c4c9129f00102de186125c8c6114039f72de6469fce0073058430a6a64e1f379e14416506880c512317cca99cb020863b4fb10025116468821c3890f9ad0420b1c18f974bf48281211ccfa499445922c685013f12467cd0c3680a6908610675001012344f941195d1821074d4c9f3e443c491d881023092714a14a142d7ca6a726498136a6ccd67042891a6af84c9f73fa37a7cf185884587859d9154fc0d7154c10628199d286114a08e2c84c0b1f3a64088c22d8122868644105922e98c8d14fec8d0b26acd0ba50e34b9d2ea2bcd7b69628a0eedb310db91bbd5545aa598b88b3c60876e66ae9471d7928291428af6e16ad42aae0ed3c1bc59b4694a807503ee627510fdcf8d24fa21e48a9a9119b4f6aa02fb4536755b286f9e409ab8e4fcaf9e457f486fa7c524393f666030e3ba0a0074a9e16cd274cb8f1e594927ba30a1df2d46ded95e7e0c3a75eb0563604a9a1005b2899f28427ae80a30c1f5a842f9ec04185386880238a69d1759db5d6763d905e5f69fd6aed7058f5d47598bcd1c2531c9ed96d6b57d94de850d7babb2b799dbaf5aff4808b6bab879e523c6de6d7b72ec459d34d7afd7431865291c7005f9d024c84beaa70be5653add4b66a085f92ac2d1d60364bec7ce988eb18438315f238d567a88ec3d324ebf57a01917bbd7cbaf67abd45439c35ac6963c179639d25049fd4c0d3c656a75388fda3ec8a269e4ab52b66beadb9385ccfe17a901bc83c9f871f90fcf579ef8cf2f7bebaa657087f5df44ade90c29e7afd7512ebefbd38d779aefb75d625798169e8a07bce82d041167c1e7eb5e5f9e79e7f191cb337529947a58fc8826a6d1a05d1a076557f4972c60b7210060dd098f9b4d1a40924aca802e4041af8b4d75ab5cd399ed4c0d37d740e957af961d697d388bc396f2c5be81a15b66389face91c65ccd973a6d755c64b3d6712bdd304b0f98b5f52d60fa884e4b23b801f7ed4737dc2e1c6faeb64a0f009d961e10249cb5555b9f831f8924c4e7faf4b293dd4ed6644bedf57a0121d4aee983238b2b3b25df606e288d2fbba12125ed6567eb6e6507e5d259e79c0ec4fefcc61f3e2781e30fa097ddebdbfacd0d11479c31555b3437746ba5cd313680b837b2b0da6a1a24de5c256bed2885bde635778f5eb02ea8d6ca4ef6ddd1baa14ed6cd68d03769566f912b17f4a66ba72394b596eb9a850c8781be409fb6c3d0303489923d25e24115da25694291bdf1e5c4b9723c95a76f3c755b5dd4512a3a0494f24164467d52ffa8134d22a15400f84934d4032bd898370ff04c97eb9b3e3b40e19854e6174fec608d2a96e0c4491b4d04fa8226b40e6818af386c0086115c14604da031a12cc1f527911549d06182c80a29a9a9a24c7cc953b2842cf98114bc58e30a268ca6e053e2502bd420b282085fb26e6e88623cf549fdbbf7899a154356d4645240e0181afe7ae94fbf36ab3a4e759eea5e9d559d9485e7ea4df610391d1f35546fe12b4f13b876adf097de59bb4a15cea82730cbb111de1d2156addfce9b93e60c054fa7638ae6f03e1d4512cd9844a2f97bb1043e1d33119854b637d8d9e2dabaf7de96159a49e28b988229fa2cfb6225d1f2a3639fc09f230b8966ff39d2d17fb69068ce65e0e9e244a12caa0554886d9210a62670cd51eb75a928db72693267e94c61d09f3c3ce79cd3e4c4e75d597861d3da927895346bba6cadddf5ee77ef68ad6cd6dda0d99a5ec519ae80a6cf8bc5fec3dff44e5dad29252dd605a7cd376dad2b6160eaa0d756ad75ce39bdd761c2beaed53e096b21ad59e16cf674ea58d1f1a7af639981e93ccea8edd4cbf6ea35372bccb70649d596adb576fd4c049e9eb3b659f7e79889c0dff7709f3aa5a39d3e62da5fdad3318606eaed2575212fe485bcd8dd5dd28fa1614e3a524a9f3443faad1001d35a6badf6fbaa57abc909dcd50a02816b77dd8eb456ef332581a9dbfa35689bc0d431e933dd0053ff9cc0d46729e4c524acb5d65a6bedbdf75e6bedbdf75e0f64119fce9e31cce10293637489a1b1374ae7799d779db79468b0cfb26aa161a1f95600c0acb576ddbdf6be05000b96762c90ba0464f5dd785fb75ae90f6ba7add5566ba79d96659f603569a969d51300585a2716eebdf7eeb0979a4a3fee181e8185b4acb57687a9ae947e60310ee0a4240660ca14dd5badb5d6ea916aad62db1228965a605230ab994c0380dada21e616429f59a9647ed4156c8c6eb72036f0b89428b8e4f13330f5d259f60798171d299b8f478f7753fa516f6cbb7ab26ef7d71b715ca8f55ada7ddd6e3aee8bd65a6bad55001674e2cb51c244f847e591535d50256c636939957e5417acb5b6764ef129094c1d97b00d5fbf269d5aeb4ef58254fa714b256cc3a51f7547f47b7b81415b4f146bdbce24964c2e312f31335ec302e9c2fe6b41a4196b6ec21a7269069f9159a958241c128642dad0377c230f5e4e2390871534ba6c001f53c014e6be94f017389f0b021040f730f07c6f60ea393aa47adfeec4940eb082320cc2548764ed9c765a6bed8efd92a44af8898ed20feb82aba2b08e1c2897161d9258eb2c9124dbae277d3fea182281a9b3947edc938924599f93270c7cbdf4e39a4af80b6c9d8a3c2471a594b2c19c95527bfb26ce9af2bebc4cbb6b65f0ecd0eca19ead5e6e9ffbba821ee152514d10f077eb25e0d44fa22a862812708e9f445530d170544104115ef94954c56b095394c02f638081513f8984b2788150926a0676f949246444154242d43870e94809ac238d9601c641a03dc02c5ab4116ef949b403254fd8c111ea06fe7e12edc0c80c7600832150f10422a870a3b681c19f44541851811bea9ef0618c63a6c0222885841acc1451c84047128c5ea688010e722ce19672820d5c9200d311a50628358e720c418896345e2e52fc00884509462829b620725282ace5c391428a19e00f7f243cc588450a26804c4527298288ac50c961295c7044c9286772a308521bb315358810d5b8953e1c1d14851ffe48180931240491b01844401f4c90895b7c3ea6c442a118783ed698814f10d1f960e35b4288eb438accfb7074304490fdf047c269d83a1c9c0f0e23aa8f23bc6e1bb0f64125c84a2942d50052c308566246cc875150b7e6971f7efc830ada8753f727111530f858c0a2fa60091c6a601db82cf0cc16750aec3f89962c3104af7e122d098209f8450949f0e92711cd0c27b0f793883646d700ebf84944eb0136fd24a229a14cb0fd49449bfda0114d2104a21b183dcb4fa22588bcfd4934c5185fa62e14b2efeb62d75aab4729a59f0cc98f0e431ad6cf3a2b352facd2407dc6d4cd87b2f01eb94780a868490161506dddd97728c35f9261e643d947279d36b71d245d2288f8aed51474dde40612df4ea2d2e44956834846edb2279b911ad0506908adcaf769467ae3740b6515886963a33234bb96b6ea28b313ed3474aa7d5739c9e0f276ec1b92da79d4fb48a725f8b34d9b5bc3d9f405f0d25a3da73df8f07d4f9bcec18e056f9d7a9d676d900cfee0d0b4b9feb581a997f7e5c6df7bba01f1d724abadebf766b29964a62113edef35cdfede11bcc28dff86b87db0ecd9de189a5970c8b32659253d29332ab437aa7cfb109b5704eb69732b0b6e473b5a6dad2819cad6a2b20ee7ec79c1f5244b6212932c8d2f4d44f92ad8d15957bcd9338dc0a3ef8bab2fb869d38ed3c065abd5823559f2f656ac65a415d43ab2f2add7b4e9b2c59ab518d4bdb0ac9222908867df3831480412db48bb8098ae4b64b25e3706df5e8a43be8b58f2ed6127e29ab5ca629466b587592cc255521cea568b414636f1f65dcaa2ac5bed601695883451a8592dce44b135a4596d6d0bd6ac6ebd9ad5ad57ebd5de0bae495a85d88bd1cb1b30af76d96f870152c2e45b8431f2fd82fb7e3952f2e44b9821a52f6160303198a06ff73a1bd4aef2e25aafd6ebe2c4232dc497a4b66f1d887d0b845bc4f762c39a8b9e112f6b9c68781225993d5d7de796ac0d314a29d5d1ce98d30b7146a16d6fe7913127088662adb3ce394925d3caca490853371d4d5c4f2db5bba551281717ead28232b9e470c9912307aa8525478e13ce91238729c74a29478e1c2424a018de7befbdf78634eca31fa51fd75da6e06a3a6a96e7379baa342bd53ff73ae3b5562033ff1253721917e2a323c7543f2d49b9ccf8c3ec428827643e29afd9061dae1a7f986c28e1a3a3e4b32443e521e7dc728a7270394639b99c239d970e4c27a613a423d399e9d07486746a3a369d9b4e910e4ec74827a773b4f3da81edc4768276643bb31ddaced04e6dc7b673db29dac1ed18ede4768e5c2f17cc157305b964ae998be61a72d55c36d7cd55e4c2b98c5c39d711f92261648c0c2265e48ca49143648db49137b288c49146648e3ce281f1c4788278643c331e1acf104f8dc7c653827d3eaa91e7555bbd7ab1493c64189a0832cc5008324c0d0419c6b64386b9e990618aca0c83fb408631ca21c3e43c90618e3a90635e1cc831b00de4989806724c5006728c0c03396686438ea15d20c70c5920c7d42a90636c14c831b709e4982209e4185c04728cd10d392607811c73f480ac7a3920ab60366455ac015915c480ac922d20ab6641b28aa680ac1a4a4056d5109055b61ab2ea7680ac2afac92a1c0d5965344356e58064d5910c59e6f523cbc00c90656205c832413e5946f65966e623cbd07a6499a118b24c8d0059c6064396b9bd90658a7ab20c6e00598627cb9059c695573b79a5935739792580bc7221af70f28a7693573cf2aa565bed3679d5425edd6aab3d0079b523af8c5a79955b1dd5563b0b796606565bb1a031cfacf2109507d18297cfcab348f2551a73a757b89c9beb365d58b96e3cafd9b3fa763fc2656bd6c26cdab48d4c8689c1cc6418586d35ab5bb36f5fad51ae90f0faf632e7c653e5dbbd5c0e00f6cd05127f23fbf697990d70c98376c483d6a5bc53b92a95636c48b9ca5529952a95552a9f25204c3071ef1296287274044e7969ca9972a1114f516d8546332b9954680b6d32a12db43181270f5a2553a92f274abbacbff0a0e5446956cb70fd3c7d7458da2eb066cdac64bca666563229fa2e438eaa4820c6550e962400e33ac6181b74cc32d8f0d1e1427c5439c60695a73c35fe408f5062c4cb47e5427c448c0486a1ad705e055cbfe4a1f114358b4707cfeb22814b548ecacb2fe5475ea6be441dbdcc9873eb56ce2d9573d331ce6e95a9cfa1827180e7973937daaa98fc0a7d768c5ee14eee3be7f6b85b5dc71c79fa5c2060ff32e796836bcf316acfb9e5147191cb8110543b20867126aefe4db2c326afcbf34edbbdc619633031fe24bae2c9ecaded493406914fc1f07d9b6148818322b828a38821a0e14345d557027c59a363a2f972d62fa714177e56a143be52a0217c498dd023bff22545f203233f50c257c7a91f3ce1abfb13275f9d15c60bbe3a4e18667c751e1f507cf519c418faea416c5ffd061cc498f2d573787d751e9a7c751400e109af1b18c1801085d82d8c20244441a8f295de2684585c604338921285299a204114d282c8122e8c28a2895a841bd14aadb5f6fc6230860b66a86e4393d667f6509247044f72fae021889f0dabe3923ba97e955669b50e85be328635cf505bddadeca1ffbca4b8ff3286d55b6d7d5588cfac2dea3144416aebf3eaa41aba8f1e7fc0a9adaf86e30f5e5b9f8b3835f7e9d34e93854f8f31dd8843b33ecfa1b63ebf53f0ad16d5cf6bad7e5e6df5f39a51505b9f87f9a3b0ff9cc28a6a2bc686d0ab8717f7b5d4f9cc9a6fd4e1e6ba1988b386e2306de8f5ee0bf2e5982ec7b42da63f6fffaa7f234fed52e369dad46ffa8894f47d194f1f6aab2d7a044c27aebb5621debed7926e3f18726d7777d357750d0e0c3380f184746351dbb5d65a5b146909578303c30c601c21dd543a7445bb6b3f4a0492b5d6da31c9f49ea3774b8562034f2f554fdd4b82f883ce19c6a8cda8a69cb6a6d4ad0f76f6f3d0368fa0059e4de1590282e9db799a9d37bb6e86395ad43ed43b6fd6d9790cf8390f9d589b817a101b9a0c1d07ea1b68d7ca89cc8726ab0f75f0eb543a383cb4ebbaee53523a408d630a3fbdcbdea4efa93ccc37973e4c3067bf98d558da1745b001f1456d5527c9e9d39a39492ec0d34bbc2283eb7d3eba670002906fac5bf70235ebc69a75bb8e8c0bd4ac5ba4650d5cdea0bfd76f918be473cf63f882644566cffcdbeaf1ba29097cfd7a3ddc7187986e75694988b3c6e79c93d24973fddcf378f00105242805a44cb09d52a811c52101532a540a1698fa135afdda6c7b9c73ce3967177149dd0aaef5489da95757baba2152ff71a2cd39eb68c7ae20ed3e69bf455d6f5e30d1dfcd0b2dda0b58c3745f29df6eebecdbecc9f1ddc5b7cf92f404bd4e5c30a308219ea8011b30274960220d2162800428e4200adf95083d79966802892bc8e00111c680814f896b942e66f0012450a8523baa72831dacf02b3f89b8c0d9202c4d1bea24d2b4f9caee4b1fea57fff011fcde7b31ed8b1305dc9180807ead67ddb35dee407aeb979422cdb2e6e91dbf1afbd7d6d1fb66f17d294acbafa134167836ab44593c6d0eae0b155a4b5b6530bdbdb44443f72cd16913e5f53e34ed5e297b88ddfe3e53500ddcba78ad25d9ef5e7aef0e24da251a5a06ef4740e3a84fa70d654d9b3aa4569f2d807d77fb5a3bead0a826f40ef4a8566c3f123dfa762bec17056b3bfaf756ef6e4d15c3ded136cb5eba124635c9706949f7de0b56eb799e1d9d0a181ccb8f54ce39e9ed27b56b8ed7926a1cb8b46f72029796564bb23ee3442ea925b50b046f57aa41c8db18faf5ef1843c3bd657f8ce7423211b873cfe1ce455a67af9419f86fbcd78594df6722b0f51cb6b58647636e94cd1e1d7f6488ee0a3b84fb721615fdb468e0f202e1ab4f4a299dde4ab982b7b47de8a11bedd0b4a9630fd493c99a7c79cd846a4fb8b8c5a0c8869ad86e45b1d23312f3cc001a628bafb5c71e681af3e39836b569539dd6abe42a69b22576856eed3eb9454d8eee918bbb4a6e935aaf94afd455e28be4caee919b0382386b4a9ad75717a9c95ad3ac4952ce44333131d94c506a505247a628f75583c334ab6da1c497a65a4dca97a6275f4d3713eeab5bdaa9a37695a6541506c8e472f52894d50f6547b84db3db5e4e0bd8090edc5ee2b134d1445a62401aa6d9981b653253e4a8717144660a9929a669565bd559d0c0d5656e954c1dd556f5540acbdc646e5f612db8afd5c725be7ad992c657a7347534caa64deaa89232b4d9aa3ef4aac1bedabefa14d3a67a29939be2ab67d417d3e6a68e9abc2f2fa07605bd2069d7242252f2d5a6e4ab14475f419a1650be7c897dd522e8cb97235f5f642fb317da578ab2b5ab94c97df1a4a281b235ea8bd9337b9ae604f4f4fbe600823046572b7da929f1ed449c35a50deead4f2fca9c9e77aff7bdd8912a48e958da7ff9c9f273a2c4d86292285865b181696234b84a0e39398b5ba7316205b18c5839d61bed5a711f3ed6910d6cf6ccbc5d15bd5d05e1edcac9b3a8b09078369ea68834de3ace2f504c1beb2858255f72405bd6ed8b692c514ab0a082de864f51b097d9b421c2e52a7cd16c315da657b36ab387e43688c9b24ee4ad97a31b6f7dcccd1ed14d66d4664f0955a55daf959ca4e5f369e9bad04db195951cd290d2eb79249729182c6d097c51d22dfba2a459d6f35e66cdb247d3c6ae6eed2a5147ab272d0e36e9185a6a2b1beac856d3ab5da8599b82b026222633324d6dda589a266fddd26cb1aaad6a2b919a51b25e658aa68df51cc3b35c9b36561cc75cf8f2dcbe5e1fe4340641e3f039b98f76c1db494610de5aeb224b9e3e5ecef1a73c7daee75dcf5bd55e682520dd75dd39786f786f7849b70464656b96756f7ca135cbd2d8deb2b897939c509e84e5766379f2843ccd6de5e4ad779e7b25202bdbe7a1c655adb6ac7ba3d39ca4c559cec02b5a254f6e7d3554c916b732b8d9e3cdde7acbcbac922c6e3fb71e440b5e3e27db79893a7acb320e5d582dc1a757d8e04a9d0a12674df9e5beba68a91de9d8b5afdfeb6d38df375975e6688bfaf7619cab7db963b5551d874abaa75234c6a2851441f4856a446f6295fcbc2aa10cbfecd8d73a2589af77a82a61f65c2ca64d185f7a754a279355b28b62b6c219bda95e7a3ee82285cb4ef6dd1ad1d7aee8ab979f135f85f01da22f7465cc1b2c664f37846953bd42c13cc0652783f2df87712ae5acead5e91426b9c3c4c0bca14e98148ee9ba1e8554cbabd104d24fca8514386ca07d3e22d012d39ca20aa83ad031d5ac6e6cabddc28509b8bd2484dbe910a747f5e87db419b4ab77c976f5eb4a5707dbace9795794f20a188a24af5792984c2b243104bbaf7a6c78d54b9d9589edbd21ce9ad2d21f9edb12907eeb627712c23850cfa15d9e53a7b64aae381d674f8a9b5126116afc740ccc172810bd699f1e7300abc367471dac579fefa3db367b3e0fa4229c84668bd4454ce958ef1dbf69535da7bebee2d8db75debd9ded3e4bf250289cc2e1ef23cd54aad4acd5eb122c36097f7f5a8245f13b09e1eba4fa460997502d67e0eb3eaaf515d26909163f13c954fa616dd7755d5723627c3ad56a6dd7755d576d2d77e868324ac459537ab16fcfbd198e02ba05dd823f3c07bf5b716a97564b5699a26f5c6a6405a64d6c5d9bb40bf4267d599bd0aeacd66f2c2ff831d5eddfa7b4da6a12a5d2e415a2e07869f549b39a666b56539cc7b49abab4dae4d23a2f6b93a15600e89676858666cf3c00e8d6c1397ed68b2d416449028316830104ec27182fa00fc04f2771e05e34d90fa522d1ceafb5947aff67c759e100290d4fa2210c3de8feb9f87a39caa1b63eea20a50ebae73935a220e8b4f4c31b273dfd1431aa4188ad259a03100dfac0247962ff19a1476a6e94b3a440641c36d0ac8f02cdfa7cda13a9599f5f9ef2fb523c40fc41075d6ccf61f6d4ff7c877655f7bc2424477dee9fe38cf3abf9af052aeba84044890ce130a91391688b298f52896060ead68e6577cddfea44d2ac1f3ef4ffa8de31751c6fe71e634a1e76f8f6fa22b9012ebb08973de4a78b42c0d46777d3234d71804bcae4a78b43c025a5fdc4b95e904185888c2136d54fa231841ecad04f2228b32fc136c6b8110961f6363f89ca38fa2ebc30d285174046be5de56178efbdf7ee8012c3201a5a3f9b05d4ac3a81aa9121df6e5f47dfd5f6f58975326dc42270d931dc4f9ad5b7ef2e9242058910d08bcf4ff97c1db47771234d86305152c4081328289fdfe2f3597cfec9e7639fff5324e857869ee4404d861c41f244e8419fdff97c5b7b2fe8effc1f22b4af4d2adfee2c46f0125c7e600c11e53fa376950da30294336aa0a26fb746ed2a3b189576953dfb1c7d61c68268959c959c3e4a3e9c2703f2663e969f9199b1fc8a5663f94591194b4f4835965e2d662cbf213063f9c1befd8708ae5df5093fff25ff10c1499142c4a85df58d9f9fca3f448c8850214245d6aeea849faf23ff14912141426b576dc2cfcf917f8ad0848460edaa6efc7c97fc53040604146b57cdfd7c54fe29123362a4d6aedac6cf6fc93f456a4d9abcda5599f0f359f24f91d7902143edaa4bf8f9a7fc536488099359bb2a959f8ff34f919912254582da55a7fc7c53fe291254e4489123b1765525fcfc95fc432466c4c850bb2a1b3fbf947f880c3161726b57357a2237285088ccda55d7f8f960fe213223a2848892218eda5593f0f3bbfc33c4d1105586a8026b5745c2cfb7f987080c08a8d6ae2ae5e77ff98748ad499357bbaa1a3fdfcb3f445e438604b5ab1ee1e787f98748d09123b276d5349e880c09125bbb2aee89d89e3cf921426b5735c213a11111fa59fa2122d4b58993ef235f763092c682bad90489942fbd21dfb72e08df5f67c6b711a474b76492d6a8b6524ef016577c54bebb37bebb2ade819efafcd611d0bf55f233b246956c98c964648d8cbe3b18ed6666befcc228e3db69478155b275305bed55f8e0db4b0d6ba26f582b992531c915bcc6cbae091a2fbb28c6f6cfa8922b8cdd10b3d55e53c4370d12e39cadb25ba24c4109fab28b75b0764d2228b16f0a9bfd682544e0ee3b980c87633977207d3e6b1a936fefa288eccbd4eb2802971dccbae81d4c0c8faa98affff0e0cf6943eb0ef4e887d02a4deb59b354b44a939702a9bc8405f6867840efc12ad91d75abcb0ed6c1603d5332be00973d83f950cf68cebb05385052dc1b0cfd007e767183dbd76c6dad9ded3ac1b1d6197078aceb945fcfafbfb1f4e122e01b93b84007d643e7c199e112e75b5c0397df9c2dfbd9b1acde7db9dce17740bd37e61063ddc6d8110728f8980187c785d9eada8e336dba099c7614d4f61cea0be422f73a7c5671798142d81f7584aae2f232b958f25aab98e9d7fc21e15746d584c30648788925d4511632f0bc786e9634c2c8d05638ab82dc8de524395cf5421854d472c4700eff3ce7d6aeb2e69523c37c30b45b04913484cbd66cd62e5149bb2cad5d422a17d8a7b2cabef8d52fe5242f5e731215955cb360eef3826b564ba1d67d7429b3688d050ea0bca4e0a3a314762a93c9888ba55269e54d4fca2dfcc9454a52651eb3bce3595ce431d4ac76d2c883d62c95cfe41895c2ae1d75e4e2f7a28e72847284da55f2f69ca192ca4b335e5a79a9542a5d6bb95883052210b95552e5365fe362938ea03981e6939a65bc910325a728e7d6ac761cc3b57e1219e731cbe9c29553e36153347b4a7ef3ca3cb769d39ed3454ead5ded28185a8b1f9546924d69ac79b1f062d15ab04ace8c2a8c6b0bf6a2c1d1e03e8c697032fc7959dec84abb0488dc823a6acd2e107f4b17a06f3f6153eee7c18447eddb7d340f5ab37808896ec799ed13f0fccaa3c9370f2673dee0c047074a8c1c5019329f940bf1c9b9b5ab426997a9a85d2b33540b9a05f50165427b40b1a057d021ca036a05ad820ad11dd025740aaa031a05858226a14ed026e81254097a039a044582d680d280ce801641654063408bd021280ca81034080a841016ac20052728010946e03df8600214a8e08716d01775011d425f4061940805a244d01835821aa147d0206a037a84e280ca281314097d82ce680ea8126fe729aa2495c2db797095a4b44a522abc9d27c773e4135403c6be84c9a9d55693343871e5e7f5566eb4c822f099108060071dca0fe4e0810e7060034e779035e0948abc72ba2467c0292d8f4ea7c818702a45a671aa838c835325b9c66914f9024e739057700a45b680d3590680d324b9024e9fc82c38752253c02992cc72da449e80532672cbe91259024e657987532572049ce220ab9cde20dfe0f4480e80d32432049cda20b7e01489fc00a741d9c6690db2039c1e917938a541b6c1a9917ce37406b9014e8dc8384e8bc80c701acb2e3895415e805322b2009cc62007710a94739c16c90a704a24eb381d2227c0292cef3885414680d31764975321720d4e8764d26910f9004e5d90799c02917f9cbef2005c48a6c15b907b9c057906ff21bfe02bc8405c0519064f4196c1519009e027c83fdc0439062f413680fb907b38097201bc87ecc347907ddcf37bcda62fe5166a2bd69a512d320f4eb3c82270ea831c02a74c32089cf620efe0148bac83d32b72e974287fc0290f720e4eadc81e705a45ee8053a1cc0197c92ba73bc81b704a451e9d2ec91a704acb344ea7c819702a45ae71aa838c01a74af20a4ea3c83838cd4106805328f2059cce320b4e93640b387d22b39c3a912be014496e396d2253c029137987d325f2049ccab2caa91259024e719003e0f40639024e8fe4169c26916f706a836ce314890c01a7419987d31ae407383d22df38a5417680532319c7e90cb20d4e8dc82e382d2237c0692c0bc0a90c32039c1291739cc6202fc02950d6715a2407714a24ef381d222bc0292cbb9cc22027c0e90b32e954888c00a743328fd320720d4e5d9007e014887c00a7afdce342f28fb720bfe02cc834f80f19065f419ec1559009e029c8401c0539063f4196c14d907b7809f20ff721fb70126403780ff97d04b900eed9c74b79c7b7809ad5add8d882d1d4be1d1781cb9c5bd982fd4d50dfbcf2cd907c93536ba7a1a9d1d8688a686a210dee5bb682c3250c92f2e6f53cb8083c040e026fcfc95572076fcf39aaa40edeaef3aa64e9ed3ab04a7ec0db756295ccc1db75822ae9016fd79155b203deae33ab2407bc5d8756c90d78bbce502535e0ed3ab54a66c0db756c95c480b7ebdc2a8983b7eb1455f202deae83aba405bc5dc7a89215f0769d5c2529e0ed3a47959c80b7efbc2a29016fdf81553202debe13abe40ddebe1354490878fb8eac920ff0f69d59251de0ed3bb44adae0ed3b43956c80b7efd42ac9006fdfb1557201debe73ab64106fdf29aaa402bc7d0757c90478fb8e512511e0ed3bb94ad6e0ed3b47953c80b7bb5e95fcf17617ac923478bb2b56c919bcdd15544920deee925552066f77cd2af9a39206f076d750250be0edae5a257dbcdd65abe47bbbeb56491fdeee2aaa640f6f77e12a1983b7bb8c2a49006f77e52a0983b7bb8e2af982b793af4af6783b09abe400bc9d8c5592c7dbc9a04a92de4eca2ae9f2767256c91d6f276995d4f17672a89239de4ed62a29006f276d9574c1dbc95b2571bc9d2caae48db793b84af2f076d2a89236de4ee62ad982b79347950c402555257fc1e793bd9d0756c91ddece13ab64cbdb79822ac9f2761e592559f0769e592501e0ed3cb44aaee0ed3c4395acf1769e5a2569bc9dc756c9d1db796e30486066f503a3e4a8744cd4775d2d19a219080000040500b314002028140e088542b15828cd634d103f14800d859e48724e1b8bc324c6619432c6184288218418002002404233890400b4da9ae535aa8c6f48eb256d04edf24b5683046973ff14018e9c1509b8e77cf75454177546c06ab77464d7dd28d08bdcd19337d3e21692abd83fd65acae42be53adcd5781fbbfaa934e4e92d09de665b6c6d656151f5f485b4a7aaf69b909d0386d7ec27005b5ccf6d8d2a3ec0bbab2297c07891a55ed25aa50dde50c49755e58d93380c31e45755fb3dee598b2c93a1d4b030b722e75023305a18a1d70ac080169a760f27d15046c0d716b39739996bd8f4c1f1f7980ba0538af89efa244b1a4db8d7858c1411facee574330be3045f65893eddb2655bbffc02b91d1513e9e08a031a38ac585b30e2ff911dec75c417733644cbbb82eed7615ee343f3ad640e2cfc70c5f53a7d81b757e980725347bf1583a35d3dc799ab4e9dbf6a493e4e017010a8bf80320b125db3c3cc95217957ad527578b2ff28812f56235a2f90f1e8dcf20a82f806042099b2c402c92fa09780d792e06468210c757eccb011219f2f0a437d5f70db4593999beadc4e7833ce7655a56ec54522a80b3599f15f941a13adbefc8d2b1bf4e82c68714ab197e3bb0c981092c1af084e3219b72de1c2f8d4a122ef6637c3cd77459b17b1e4d65a4d7f95cac5a37145c83d6458f492f918f161fbec1c2dca8ee53eff152c390e801190cffeaca7d6778f96bcbcae427d2dc3aa2ee19908c89eab65e70ac9d9e57e13331340e413701015bb13f33d6a5c41ce34f2413be85b65fb3fd9027790a1e0a566f20bd59a1daa695a6025a0b8ed00b1112836f328413aee2d365ad296049284e704064691597fd617dce76bb021709781f4d14ec7636583ca862e1d1f62e1c8d209660b8132fa9a10b149ac3c60aec98b8d21d4ccc326080876daa5b96457ae13257da62700b989a9cd3cbce83935ec32d24499498ba8ebad2e08446e0f1c79f0067c0a56a1aa84030b17539531756cdb3e452263850aa440cc7eb72ec5ca134595a3048feb5c30f60778a0a6fd402ef97383ac01ed6e68a47f8a2851b440fd3c8333b2269b53891010cc660f09988ff1ac030eac549089199d44c1ac5b01a62931cb8d331e105c423350ac869ec8b89f2c50f4d2db5707030e3aecfb68f689180aadcc90976f36a43421625af59197ac33f56b0a04e56ea0d68d52b734536a45ddef5b5424608120657f774b48bd100a875421a0ed16756ddcc0818bbc03ac4e4864b6c190ec0983e2309bacc9ca607c1e43bc5429b534ef371bf3005a6ce6581ae75bfc4a9b4609755ab7b3afca9f953338fe391f13198ffb05d6fb6e2f006bcc445dcecf4b9ad04178e06b8f748ca1b2205bb6d64b009b942db5a0533b989ad590640a39d1a19b61f22b57507a5400e25542a8b59a931855346bd2d2383ae264050ccf9e67241b89ddffeb7257830fb37089bb9a2c90badaf838cc40723439700b6d98f8d281776e18fd537ef4c472b7bd4b6cb6c1671a392b54f05438f9fc3b37fc5f7b28082382fda123027badf058a19ddf6c79fed024e86ef011621354bb766ba09b0c54c0a9d997adf8b03ea6f4cf6b47f5c3c7e2308bea0f1881e7b13bca4edfedb7616b743fbde4ec9487b20d5b564eae043c0246ac94f3b20dee9f501dc0f1493b21afdea840bf4e7703c8ede7929574c03f15a3d2884c031e528e53e76235ae0f61fef7864261f370b289158b8396ffcc5ad2bcc2db0be6c4e2ddace88e4db195808ffd88db9bc4e453479e5ebaeccdf1eb39c6127496750b64b4b69d45cd80ecfb490ea544c533a49c457c9bd957df2a90549f918cde757478aaa3e3b9301eb39e9c6e8ea39a365bae17fb0931c39904e72ca2a9b28bbac7b5e8bebddda51347bec1292ecaf48e4446e33b4ede4666a01b082a9058515f5aaad11398f1ea121ec5cb97557b99503951ab2190db9fdcb960c86ec0c83e6904232263c1e1247609abbf8f91553f96172f1f8fe3054b394d95907669ea25c6c196a3102476127c68d1d4731f7fb99573aceadf347316b23d5151451fb0759186b3c13b679d617a40d7d222d3401639175a7afd38459b1c5744f3972e0f1684c1eaed528951c26a0aebe3200c99f7e624ff9142475b464f771165348226d9c6070f33adba54700e894d0f52924076a05c599c8061191b27be4f9e8a8a91d26185f86e896f01e0e364f797155879d8fd5b2c644a750bf3c02f752681dc39619d228a7e0cfb3210b4c35658df0b9ac05e4b3258e7f345a52a19a9d25c046069a649978c84cf8dd93039450259527a5f0cd07d42bda88156a57a71237d6d2327c536b1334042ee5847bb00525f5f03d850548df45a03c82752668cfd51a9ccab38c0ae4f22755d8293d8ae48e8275235a6cda345bab0ba136317f830498734eaef732bf94056d8de9f165314aea03eb823282a517bf07f8446c909de61c5c7a0541f0b469d3e415a1b095529344c78ef3c051e99ae10f77a1fe7d338363c09223de099b40a8ccfcf82d47a96a767cf32c872d63282b328ae9f2d61ed59100e9f6d37c52a607c7e16aa923dd55cebe6e5c29048d041d12ad851810a4c969cc5c0fdac4ac6675160ada7f2a22773693044aae641700b571ecc289b6668abad2f634c2e06fcb0181a1142109694e5f0209b4e030ae9341b3c473fd430ab7ef1eba95c076b22123ea09c2072a4840eb0d3d4c46158dd2fd09b41881d5c76fe0b40ef5ef8bbac64cfe735922eb9ec30f9840de17a27ba343c9094f0385d9e7c1fa39066920202aca2e5418bf01c85d9e0f3966278363bc8705e7e40d64b7cf65d6d99fb0730a95d9edd62f04eb6668a4d86ec3d7bda8d6b51a7abc8c0192ecc0b23b9926745cf5d7ad421c2b40882a6b28bc0541acec77fc86d904c600d16121f1db11ece5db4daa24b5d96933ea8b26070939e11832ea76d162454807b7bf5d63bfcac3a6ba9c779666e48d34214603356b3a76177d6788c34b43cc71bd61ab144528a8962d365e41a9f5f19c83649840429392d0c7e0c940c6a6086537514bccdd5f04747e5521ff2042be7384a865396375f3678e33af5de8c11255b3d85aefa79979cf9b5d38303e7f898780bb6ddd6df37d76390e72020625757c76f423e0e28a80123ff09b3ab77aadf08c3298b1a916fb5bee2cae65660319c61223697daad3e3828f8aa58eb162b998e76250ae80559edfbda85a7a72ed037c341e35ee1fe136344fd63e4b425421eeba3a5d66470671fe419c1f0d88b200852f4a5e962e494f376a6985a803ee098d2d9d380d42b67ac5dfa5ddc1c10f238b741c83d104600a099e072ad5ff8090231080a5c0a1ff0c17749c51ac1e65830dfae974e82ca896fd4adab911a1f2bc0b1482914dc5a21aedf8f29d098406ef2039334cd4a83a302518abaa59c125e74b2a4c28b914b42e6c32a2acaa2802baf0337881dba738f5e7f71517132e29eb3c28b5b16d84a31f196d5d8478f5062fa81791dd44f547d034a96330c41e68866044f1e1db6ba9d709c8519569449ca4accb933fd91605ef831e6c6382d707dc3e89ae549968420a0a69c2087a671ab8cd24c457a76e2258d21c44649a36ba76eba732b701928d211f1414968d801755521c50ef5679bce0050bfdb484f5c594495f4488d98bae3b24d57539c639bd007af8cfde1d6a00428a9e282840da547a44beefe5f87cd2b52809fb5d054dd7997913cfd5cbfc6a496ed407b89fdb3cbd03b1ad5c960414bdc3d6556e8b26bc65afde667b6a3a886f8ae989b03a5c7d90bce8e0ae464d591c4872810ca5111dc228042a38d2c08d020cdcad2956b439f6b217ef0ee46242a247a2fa58c5ef45c9b4c479d5846e657ed8a4f421c03adc7a9c8720c445814b05324ba6a7a0de126e6682dec2355c371e7d311384f6be97c9b85de7a9a89b01c6faeb2a53af7b71626a2148e0d26bb9f207e8099178aa46646e3402ddb410f0b82dc7780e4fb8f22b02e02158f20fae7f40f0f098d660abe9a1926d92ff7eaa004118a6f416708ff472ef3041537d77c6e33493a2fafc0a39d9409f05b399ae5caf59859292ae817ae356c6be182b0df0582b25ddb8a9c178777f350fe5fbfe01d4c25c458151a41a6f76714f713b0645d781cf54af9ffb33b6c14c7eada325e005f13f23003091f8206a48d96d156daa0204efe110ce177faea6799823137809ad6bed3f1fae74570a4034a2a0432c10479b02660e992564ca961a93399229d68c98ecc94451782d4dbd2111e0ab635ab5d10aeca54c25917a319eee394ef10abfb39d38dadd492364a532074724119eca9b352ebb8006bd1324ba1b834469518eb612e607f24853eb1f7652d27c28accebd6680e41ca6282a784052ed42db1acca73cb607d4de27ef0d587570be7a2b0eb2e5a315d476c63dbecdbbcecb55783890dd09ddba625a69420ce71be10423f2388124317b76d2163a1e9615ff7ee40a32d5777d7c78bde179d9d94435628584a6e2aadca06f5565ed15c5deb7b94d00bfb6b847762dc3af3dec99d0a757ce9762f50ca72d2885a1648464232a86730d620a5f1609480473f561d89540e06e6075b6fa8455a917b98ede88a2d7e2ef75a0d93aefee180b0cf855775efb73def26a43e4b02f7e9bb9e189a5e37790d9d6f8c73fa155aede15c4cf50ccf35bca543da0c035861af2cfeafd6208075e81e9196bebf33733a9abe65d963b95fb69e02d148361169260ebf5b00b0762a014951eca8dfffcd296fc62d707f3ab2379bf6d745494e24dd4f9572c9983480e774019deb2fc98c72f79244fafc55107bdf7ed6932afc9648fe9412d37077a7fb6855caa6a3ca06f4ac4f02ef8a8c1f4b8e426ee3136536863dd344acee0048714b4fa9e96e063c770986f58489648510d51a722c94b23257d9b6d39608615631c8874e4dfa94c8dce619820cd572395ccad879fa84778b91aa4a92c8a02324866a11db4d4bf3047f475321919dcfabee03e02c1b5127e81ee8953220e8bf4cd32ffddc97f4e5cb04f7d255f492667999195efa769754b0cb18ead20f7449f9b94c5c2e5d1f973487cbec6fe965b7246b5be6c8969e5e4b8aaa659469e9236849db59a666966e2b4b3a90651263e901b1247d58660e96be7f25d5bd32f6aef4335752762b13d44a6a33abcee2981152cc0d2b838b44a8baac980956d27095a95ba5dbab92ce5499ec547a502a491d959942a58f4f49254e195b53fa2d2b298795095ca52bac920655999d2abd3e956497cadca3d2f31fa864fafe7cb1dca4003dee077f433cd15abf5728db083343e474704a90f827d3870afa095f128bd1a19ac05feb5f1d47c2a20332b3bee73f281d0084c639946eea18a235cd1be0ea220e40cc06601f2a0558cea0c90d6471298e5aa970feb60666787ff8509aa120cacd145aee4570ea13ba5045052f5f710ff4fa1c449984d9e6413be181dff6495e100903754a8448cc266839a477ac05a30553a4815e431a4284e40daa2c506bd994b4d1074324f3c17b58ea9376fa739f304edc1ce1569d1e9a2735e9d2564b903055f497f36a5bf5599bd9a6ecd151ecab0a4915a9d8a5e28c82edb1e12f5612143d41cf000eda898c5e0ce00ebee6ed0ccb7dab5d37b7d45f141d786eebdeb3d6036db9f1cb00a2e322417d7f3bb936ae085d6b34e1c3b1313a10eef43c7bbc378bc9cd2556e1a9c101100df63c3b3c3f9dcbe94655b2a4c9c1100c147c3bbc3f9ecddb54d63291c0c1720c16083b7cbf9ecfcd65e253e8232a8da02f58baffca2702e5ee6c058a1755ab022c3cb209809e2b845af1e0637b60f56e29aa4081373b80ea23452a083247ec0d8e3fcaa316e8b8b22b107c5b015530e2d486027b442dea80c37d7b01ee2b22219afab6b830ec98b2ab5f154d8bc76ebc2cd5ed4ba09c2d2219626fed0882ff380926425f600260066ea31d108ab2e5b2a6d7db66cad7fbe189b413801e22106f6d3858223a20c3e717ff31bc8ecdf08ebd7bb03b0a3a9b0c95d591950bc12f65c613920633e590b8b8cdaf863046db7b8c52af3769b21d6c08eb767b554c5d3ca533f49ad0987ef25567ee55942e240923101334f40ccf4abe966e9fc59dc96402402af6899858d34339cb22ad5c4aa4a14a00db0f4be7145d1b5fd97f6269de905ea5d4c21ad5da8869caf6e7cb9e8751b60b142202deb3ea42b164bd62f7e7a80c2e04118686a1c35c22a01f0b2654a353e7905e21ce6cde76a911bb905c5583fd150f324f0e7494241b435c94c68584eb135a0201549572594b59383637638e209ef16d2fdf73821abd6756196ff4207db87cf5c74fd03f5d36cee5bfc231e56e47b21a59c956feefdb3aa69e7a81c7ac60e39929c064dddc9192ef74e98e7b99dd6faff577f980d23f765f7543e8b17963bbef776016bd0951a1db218d406509b451184e01dfec9cf49263be3d12326e0a2c2da54cc29634a05d503bd41dfa2f42e0d205c12f5b56f975473a2e32972962cc8ccba359c44dbc8834574568d387d815a6d97a109a7c14477492b2f8f69fe65f945400492b9694c32ac19db8d45d15b60751abcc455f9845c0447ccf7b1eedd012c29e6605087b2857ec263f0a1b30cfc7f3df269374ff5766b162cd38c46f265635ad154e9d915c25462fdde6703f755cd41c24f981f1c3e710c13d39dc99a126c62728587e0b4606929f044c18034c0c1cab3177303f8a1b1b35f1bb24db5a60e73a788f5a07816503790e39c0aef5c9270c9b4c882ed135eb8c4129eb3528ddbef978a69f4489699a5e705255f0e71e7f0f3e8f5514a527890bca2880797882a0d04e5cc6fd2afe3667376383a48b57365364d342301303cdac90b619e8ced40e5b67d6df0de953bceea180d050b7afbdf54620beca0589ec62586b0df9af741474e5ddcbd54417979ecd01583f3ecf5526757c7534f84005a98044eb04f0d2c0ef70abe482f35a4bb407cd146f8edd60a3b14b9588a3fa74cca877ab407daa318ce9c7ad5aab040a283f64da188f7e74ac70b68d87625770c9bfdbe1e21a1191d938137d7ac163f32f22f2c368f315560f0b40a16d34c905858cfae6592f1bb7a9ec28797f1ce2baf02c9524b0fe110601b3f290c23c52a2ecf00f92bca0c0a8d9c3cf863c8d4d57a7cf07a0b72ee00e21b85b040bf6603fb96ba7541b48f0b109cc942f24773ad4d1ea04837eea1df8996014ac939ab6c90f3cf011c3a2604d5e926304a5b2464d1a44e394e843d37d99c9c129f1c8e7c217be60b943980864dc6092843d44e93f77e8e53250d140d5f4340a61159026db200899111533b370c504638ccdbc4485f3b4d205c36e1e445427aa8d88cd9f558bdf73a4d188a2b6fbcfe4816c944b04910b83e9460ca6f11e46e0e04c7f2edc9b72b9822ae6081714be54fae7ee8a158dda15aea6beae1cbefccaae3eb6349119afe72e9ca902fd250e333ffaaec32aefbb7467f1142decbffe0f7bb9fe8b21d37f364de63ffd46601e7b5db7b1d35fe12c89c1b7030eb1fb5198d90cd70ddb52b987f82aaab57ec2a8aff9bfee90dfa527ec891046450ad1d8a1b3af7be114dc959f2fe7f4382b577273ff79080b73ecb2bc5aa93c347640197e9a12e932d3b5e01099b28fe4a3bf6be9703ce714e4e5eaaff590e1c63658d7f6c0a565b4f78882891affa366ae64dffcc613822712f36d781cd55f2742731ab2f3eea5aa3f32eeaf0146a7e242d405c10d83525ab40600a346ff35d1b52025a086b8708c00fa96078337de0b7f33cf2dea69be888f854e4cabd910f247a63e78a729c9b984e4849b0b61e39935b4193a85cb9ff5bc64c1c2c984d4e2d06ba8b80e2f04d0a84a55d272ddf3d23e904eca1981476843e5f9bec7c1086dc0570f3f99cfc102c8ca67d4fcdc695dfeb0f0b6be49da7a42d83764023c474ba16348e2b4f129d0b4a4609cf61fb5bee2a37bd7f241fba6bb3261543a3c525946d5e2c04f295a33884d6818c3af977f6b9fe83af57972cce9520913d285d3a3856275943aa6077d7077c1c9172374e5a630f2683f233c60e7a87401cbe1b152098146414c55ace14268b85c08d036ef684dbf6c1687bd88878245cbfad43c64e874e90f0597740cba47b9361614648932c576ce3e1ebeca1bf13c5277396f4b3017dd0b05a0c7e93a017112fe6596ccb34e38b023d39d4cb4ddd8be51ec48e7876fff7991a937a0166fb5cceea37eaab93ff1d613c86ccee776afe7b9ddf5c6fee61b98c61870dc79ed581998501b9487cd9d78ee5ca38f875774313d6c32dd617460611bf3e311e1f3a9c0c67bb20734b3a9ba672da842db03613d88bf750e0332905aaa28d6a17e5bb2abec2139749b05f762fe314ab5a336569ad9b24e80edde051e30b9ac40a0911846b580558264f3159c5aee8fcaf09dce919555f1fd544719404e271c62d6ef5c458405e7400bc09ac828996956e3a999fa1d723013dd0c5985ebe25a6ff6711d7d5781333499fd7819129af6905217aba5c83945b9a6cbef17800020c054d181958a47be789427c18124ce40691c0754e6fbbd85364c87826c6cce7817918ea75fd60883d856b6afcc070053afeb546d73cec46e75a05104cb6f5db4446b36565a80e28ec153eb4acddab58f156df8484f2653fe09a4ac94ce416c5ffc98ee783498f77c7c03dc0d76071f9f681c46f80449ffe8c2d270f6523bf8df8b3e7617e008576338f920703cfe56d89c4f2ee59aa8fab242ac635b6c2b365b738bdeb8b29fab4fd43a038cdf250b48b72f323f17fc123d14c04370dd6e0193972fdfbba5ba1284cb06925ec0d89a1c61cb0849939773516cb66ed5184a6ebb41f0b1d0f26655a364dca22459f12ae1c9d1b2f6dd430e90730f15effc5d4bbbb6c41d739cc519849efb168e16cde7d1921fe176df89df65210f7d309acf7a5b6bc46548e45203d7fe9957ec854565b05df14f24f72bf3ede44653088bdab28423fc74b107d9bccb10ab065426e704a62b42aaa21413a2e0cc516bb4e3cf260be820163f97c0357f4fb7331afa6854e9b9ac65c98919d91a21a1bc42a30ceb19c1181ad8d4f0b5bd2f16f9f9acee37198c60c80f7dfa59d05966fd67c1994c0c15e649f51d52bc17b020da7f02aff219f52b3f4b51c430e548ab05054407952a5132dbd1d266f82c5c43c99221d082a777d4fb4e0e46fee04dee1e1ab8e5b5ff3bd54fa3fca94d53d8946b1f1e035c36c4b44723db98fc9326616081748dc1eafe05135e48b45b76aef068a3860cce9774b3edfbf52eb88acb4b54a6e298bc273eb8dee79ffcd3a5ec5f6f2529268b8305faefab4d17c2b78e3923cebf0ac5815b7797359fb124ce9fd9391316edb1bd2303f900d8c1ef196dfc27cb0ec4fe16a8247ed57bafbb6f80dd2e2c75ff3709e98d3e9e9466e77f2c56049c32d1757ee3a6a394419f6a6e3cd7cd88157bec7cc3fbdd2690ff370a217da9c9b6bab8ed6703290506326ab5ca08c5e416b30265d611c39c06a4583b1074d851763c3e0bc84a95baf0c278c611630102c54947e0e11828a5f7f8cd5fbb5f8528f919cf7bc7dc2d70ebc0568ad6964c0ad4e5ebb0b88f025928dbed4a300111152f1da31d0b5ea6f910459f19c5c3aa2d33b91d885fc93c4fb6dfbbb5573849392f1f847087c8fea4984af5980051e090d6c8865f0c44e874a98cc35178b4dfeb1ced72d2a486fe020077e106f5a0930758f388df8bd039d88e1738857cbfae3ca5bfe81940dffdbe9ff7204c2b6637bb1071c8f894b2866cc901e4c053f9cc62d7d153e54b25d6cb9634441c3620418ec2eb5974ab2444cc386a8becba7c6b2f14c2753bb91e5f4fc78a7886cb961208867ab94c282dd98579428e6e3c478f9195f55b229a1bddc0e883158b6401ce420c4d8d35726af9209198c17ed2a0229842f816bc849559213fc175ce69cb6d1ac4dd36cd6cf5b82cdbf9dfe93a3bcb59db774e8ffb1f3d4e69791ea189c2352422f45721d7d40dc5f285c0fb8eddca135a3c91c304913c53e09b5c554d76e280c10f6b52127681316a1c03c58bd63cd03a965715f32899956d681989d8085cd50ac9e9dda0779e7cb8924f0c048637083a1f6479f943cfc4b942b90e371134ceb44a5f4f7d613e4caf1d10958286eebe281738cdf1d195eafd8c3ad277176fc6d78b2a738f081654be976c9bca1e6a95c0e5d466ecf313f117b1148b2918ba7a4643356c3d304858f16c4b9e6fd7456151c0b7ad92cb90072e569c53e2e85da466059191163749e928e3bc7ca3293115131756361e6b32d7312b0fd6a841ea97d9290f26d470ba37ec5d736931a48f55d98b660ae09f77677ae582cf25bcfae82f7564810b62c3260feaf9bb15beddf8056ed05b31c3059a4b3e5b01d676bb1e271ee8c704c750120da0954f09958b26b582d0f45edcf9fa2d4962d14c4b53a19f19d9e7770ef7e8bd7124a22361c1d0787458d32fc1d6997e388948ed6659dfa58f40b8972a9fc342403948d33682816516f10e118205f36ab9edaf24e52b627700789440427e48773fc0312998ebe06680632959b678b3c8482222bfc2b758ec6b022b4af6cc53ff2cdf1de0093da3044585677629f1dd1286bd74f58739d30ea6fb54172de5bf4f76df76c33d2a2927927d598a93342e692c438cb532b90f335c686434db29f4ea10bae6cbc5689610ac91a5e945f1dc61713df488055f9656430e8bee3b98c605dd5a8e9dd8c1b956d4db68020e33b27417237b2b4b87e34ad6054cc648182634eb02c3a73500c20738f99907f650d1f7cb7fd246aee694ab823ac801d7928513059402ea1afd520ba3986fd31cc64d1034a8559e7d77b423a8da50fb748522684442839c5136f15da740bd5951e800e706d406eaa4941f4887158ede29067e6d9fa840ac32a71dd04c1d433c679adb8cf83e3a910f2dccd12e33a6f8bf2b7356a1e442b2adcf10b4184d90976597f5a8b2b748f39f2708f0054d65fcc1549776adc75d694450aa3ddd48356e211c2fd00c241a9f75441ab74ae4507688d7275aa173cfb6ac6ebea06dea728b88d432fbcb1044651f85582e63b43aaef762c621988f76115af2c7f93d81a033511554f31d20647a02833695780baca5a4ebf1bb0ad31605838ca1a2ebac5b05bf8e77081c32337d3e999420cbf38d4fc143ffcc6d951c5aa0f3cc9f73dc87ce48f308d8bdcbab52d424fc8d590e72b8753ec74b668a5540f6fe4a2ac1086bf9706eb46f44d7878918f6f2cebd54bd21e171cde851a4f23462d253314acb7e5b524c05099aa634ece8fb012838cd89a834798328a5360004d6c02e089df5399a2118e1415f4442f2ea0b0719b7c9ec3c8fd48a6877226cde9d51249adbdd7a72f8b314795653682d7f6f8ac17b2d326f96476e05d23ebb5a1150237b783e33561b000cf99d831387fe7451eb5dc54e6fe340cbead0278a2628d0ac02a5ade97232ca1218b93244dde52ccb58e8e7120ee707b133a3337e67e83eb434c7eb227fc74d55cbb2054b68c49cda847014867621bdc48af7ee4303e3efaf7dcacf683978764cd412fc2088fdbd5f8496c67a4f3e832e1a80a112b7298dd372a32324170eabec731122a970e8d91315af01cb847a8b7f65bad5066f08471e28bc8efb18707195e820229ac5ae42ad93662536b2e17a832adf5c304485f9f47982f0050cd905cbfd28686a0650779f8263a7fcec9ebbc5c66c18351b58094ad81a23583a026ff735e88d0e854fc5898acf06e81b613ada22d25313fe41a359c845cca9decf52fb57a856e6c577e7b1ae2e5ad4c65045721ca26c49ef040f8764289f06ef5ef55b39057e264d3d45c527b3e1763e7942ea04aa570a5662bcaf16c66eb95fb2bea95062263f28c6ff0404e64d864cf8d981e2114fdcad7396911807e2f26e05983cbf95cf83d21bd107fabaa52c0029d90a0b42500c58fd8a133371248b282d4600c1c260e1ca6381ba95a393e3078eadbb1bf703b804a44fc008223e6405530c0b0a9497d3742613774731f3413973a43200bd3613d9d67caf84fbab78bb6351beb8909401853f27dda7c91b6a0dc7ece190051bcb3a808e83a690a19f1045090e2fa8ed33e55e7d38685aa4f087cf84d3684b60c6fa44fad383f87b2ae1587d9c1abc9545bce5bddb08ff3c7bfaec76206b91ebdf9ad78c46137deadba71aaf4ba0530f9fa89d8784fd606b1a66f7f15d652eb72f20e583eaeccc4c1448c5fb64ecc571e97b98222ca0aa30f1c04b5f1bc28b341bf0d0cb121e106e6d31c49fc2eb678df39e7d046c686a1b6ff089f3fb38166671c53ed8f058bb75281598e0886b3977993d054c31b3dfefffa1ede2c28b6315b584d6f3197b61ca680c9eb056181eaeaa39284ec3cc47f13d5b1aeb2e4afd0857e314cf32e337035a51645b2de16fb9e27193eba81f90adcb38d528ce5fc4d17ede182d7196495052ef73b1ec649c4b11e9264f5e1ed932114b2b80772712e046b39027f2189e540d8759de8cee878cd8c39d3121af7a7d4bfff3a8ca331291db1a008e64c09b13070542a797303a3dccd4705e6250a859fa0879e41e52a1574a54da91bf4718ce557224e7cedf01b535cdaa21b4a98766184b24bac9446ca35c5400c357de876f923e3d421feecf519241b46608bba1e58bfbb568e3fbf495acc1dca7ef866d5e5cbbf9bf0fa66cfe70eb2ce8c98f696a5200f2a14d44390d5423233275796addd90296a7ed3d3c157c38ca6f28c068310a1687b5f8cff9b0538dd35f300559c3fb7da159afecc431480b57d383aa6bedbec86659c812c34bdcb732851b535d482542e881f82bd1a83efc10c419eccd1f699d36e9b30740fca25f81f325992d240518c2c27e06d406802d1e0ea209f4eac62bacb6c143baa82c3faad02409b023533028e206d287e064844b1a0f17920b224e7405e1aaa8818ca09c5f0ae21499ef8f411e00b4fdb465f87c00e497e9d02d7300df7c77266788e155d19e6cf188dab87a05f1f5c32084803bd24428601a290c221568f7246249adceb821dd6034ea17e192b7c4c3909480d6f1077798ca9332b614b5fd08067688fe4ea4bb60ec5a4aebb324d00b07958da3c3ac0646b9f3561bf9ade75c580f60a9a6baa35e060fc632637f81088076e5ab1c3884200d1a0c0b2f3ef7d506f7c67ea83b778c760877e7699209d03e8557000493f9fea0ccab4f41da08a2ef125041d0f49e4e6d67c5d742aa9719ac70ece730c1e6892d9bac01ccd05e72c43e195425cc48fe001c6c4c469670b076e7909e4b0131b28ff46820c54767683a52f164a6e482d34dba1700cd881291c6e8300253cf10ff069c3a43cc74cf7be0db2f9b0c1195c052288baa51fac050d4bc69d253af5fd06cb475b42c14e082c98de1a318480e6b17e2f071af3322d102f6c923de75ebcc92a60fd4e55e4ac8b85d02008cee38b1443bfb58904a081e4145548921315345235dd4f48f4e1005133fc4b5fb5079704348bc902e012a79e741c08050c3fec80450b194e4f04fc5fcde32886061729bb4c796fd8c329327873113c18038205edce69f8d9d3adfc32771e2555dfe21e53dfa11283e7e06aa48e9222b31eb80e4b4f24a9dbbc06b3e1647f87924e6da01cc7be6504efd22e63eef23f0a7e4e8c85b33195ad5164fe750d952c801ebbe4b0c149570591f950306c9ef156cb6518eb1c49605533289abf8a82daa312b28d2503e926da103e99c82fa33e7ac44a72e6290943df4e2200ca3f363cb38f7b2e0749d5ef5fdb1dde03ac732e26d1b323c443914122c7b915c65861155cc593d56a937796abf263206f25fef5b29341c31304d89dfed2db9619bbf9ef2d04efc494ce5a324ab398575924dafd1b2e79ec0c6326604543639d89e4fd89b7f6ecc9e789a0182df15e54ccad3231986692aaa8fa46d052e183c0d85a74948f4b2cc4b1b4e8709289236f45f35f037d0820e743818bdb5a5f3c9c2d84116fa941aedfd8a194b29746c4c41b67d780f8542f005b1603d0e4d19fbe73bc2f2316a0f4be7684668756aad5fd72a8d2e8809d6e449fc25893e0a3f53e09042687c1f03140dd226f8badccb035b1e9f7c54cc3f4cd4620160411af70ba26276d5e4c22ca311e6b55a91d88f57ea6364361dda771be47912038f9c03007702b78be00460b9988ff187df882a2855e10e20501510b2370abed4e2644532acc72322cfad12b9644416076955c1e0a0db3174457b6a1a3dba4496460c85afb9d1d86e1241da26c0a647281013204a5fadbc0a544a746a277174392d4b89c479305367378db5bcbb82f47eed3c0e2f478992cec3a0578a6707cca8129373c24ec923c2fbc91dea8443d651fd2524b80a0e21e69406d1d1e7d751b035489d11a47cfc6c8645943a880fb705416351b30a2720c13b4f1b9bb90cf0d8c228f6c9c1b0b004b3b8941014ac121c12da4662de66b45d15161db0dd939afc1ab0efb6da1de92bfaa6f07b054af286fd8f9bf868ae0fa371defc3de6eba033b2c3b00d41d3338e0f044d265d06b37048dc63d54ffa3f4bd8cee44f30734cb6a83cf9ceeebc78a554cf1869451d35b3ac3193e50d0cbd4d1a32b2bdeb8fd3c6c09451d87d5f4108106ff20d273986e6000ebccd0263c1519544c26af64723650703901633e932eb3bf3ee2e8aebf614ea4d2eb1d21e46f0082d5fe22b415e1b8635bb3792c31d3425450d5ea0f3abe0e41be97bf70caea098bb9ed426a229fd6e69fdc6753b407bb5a1787ac229dddb859caaaf86c9cac4fde959c4a67532dbe0f409dfa480cd1d4f039b3cac909441a0a154a2b04d03ffe1f8c5f6bbaae1a1f896eadb7b9b91b48870ad1563e2a60860c7ce852822e96f68b3223d5e2722a9b32e332bfdf7de9bfa81ef21d320e9dcf415e4bfcf56f41c3b8af4df1d3c50a624d00da1758ec0387b79e3c27e40e737b38405c61f65d9b30c8031cbb796c5d6577d16992dd9e37774568c48e1c7c63bf60ff51a9e0ef7c8b40771030f903da419c9b8000daa056a8fff283cfd802ecfdb202631ba949d0acaf254a3e1eb4cafcb5d8764f462d918f255b19b679ae549ef2c546e01a97c3a23174236abca3961b86f8c51e4341a02738997a24707a5f872bbf5eb0090a7f1e9cacd5b0e3c0a9b9c16a2f74c74e67a842cd6009debe97a0dc4e514e59d85a2cdeddd4fe96238066d16ae273de221776f5f1482d7ebeab4a69acfd1feefead1967fe54bc35376aba2153f948a470ef71039d74b90d6bad011a95f2cbbbbd6826329169e205ee94458bef899a30c1734a6bb3114b19ee36c40602569681b4039a55967fd55b91e31a370fd4a922b5e485f5bdbd4602746e16d6240e3214393bfaf5e0a850a46a2af7cb288bc90f732c7b0fcecf26c11e08e365e638ebb4b840493be9a333b5392367c5f640951b9d2ff960529dc5420dda044b1dd3faeae7dc80a60b98eb84a126bfc0746a24bbf4fffad79144b0d596145681df62078729f23bfe4e5a2188b2bf80ab94d77f4b013b1bee4594111b77993da401b3728212e122cef8445855fb5e8a134550bc68e04de17868a73eb84f6ecca3cd44c99dd2d08a3934f1570aa573a3e7b2304a7aa80f8491b1d919763481cb977548b1ecd8e31977becb499670880090e08169259fbb1b30748e80494e4ee046c301fc3884faa021ec236182a3ecdce88002530c5560d621fc98bf3a794a20009d84912f8c06c98dfa6b85c4aeb0e6cbec58c237c441539ac4f2c3207e928327dd894470f7a7335663a88021703420c963bf1492a2cb9e35fc9d633a81e58c9ddf4b331749c24906643d8cec2f8aef494101610dc03b79be8a3d11f931d74caa258643d37cd4862ef7991564c507068b1bbb5d9159d6d899fc7772ff1335d28bde9e02af760fb00eec2626309a77da3a6ad00d48b78f708a1dd0568d6848965daa38956e4cdaae1b17ec12974371a8a1daf5141d2c5dabe9bd9d66658a81153744200e8517d9b5f29d78d42bb1ffb94668dae373694ac90823ad7d1240b72f8eb23ce1f832c115778ed5f089bc90ed2c1f40f3a86d5ffebfdba73a2495f40b327b3bd58e66fa4343285c85f4d8a0d914343fcd9ab1bb006c465161741b8dcf3fd762629732257ce69bd65889259ab42670d44121417838e7630b315d4c4b168108f49a7cf3e738e463dba617e718b5f39e0dd679c9e5110f3187527c24c9c45178bda2329656b7d19aef64d3493557b0995993e4733e9739b1944a254cc78dfd6a090351a419ae68e2c8b310c491e2d98ae90c3add382e94111047839f8569b4937df5169496047681e47e956540e4db43f725a0f621b6dea1ea7535f6d98c23da2b7f1784e76553674716020badcc87c4f3098573a98504a21f6255b570a0bdc8e9d1578ecace1cfdb205f9e88fd4b540ebd42630600201f3ee035f77952787646c53904ab340254d6c4c9b3e5653b7f7921380a99000a2e88451f2b36107967db80efa276961301a70179301c7a2c3a83e3e74890a7fbd5f8c0f57ea0d26545c24f57991d8f97c31aadf3ace3edc9496681a3af0faa2699ac8236e7f5fb1e6c044416ac535634d2cffc865fa0f39d5f5bcb4aec5da5f6db0664e8b0809741ee0fd4e271afdca4831d625bbdcf8114c46a99e01cbf7a2c50bf82b156673861085baf976e5b1f0c1c20d2404c1243fd12cdef725a732c9d0761c6a93986eba1da5eded9ffc8f563248fb17500f488c2798fd7d61048623696c094dcd142a658bb5952783a13d6940d337df060100874bc75763151c2511394eb14ca104ea2b2a400d86d4f334a032be8e17e17758ec2541189c87f5da606b4c0361ebdf930ab475925bbf67bf8541e98d2b25afa978acd8922e681557bc6b57cd6a356fb83cd03e1162d384fdc4e53b0451c35528c10b148088142130d8d7056f51ce470bf61a134162ccced736549c570364966b044148b142d1076e603853aa48649f496aadfa0b187f351e238e495f9ed04681dd3cc9deaaa99f8ece0b8698c4949e13493adbefc60d0ca0f26975f3d58485cf0999120abb6b47a81374055cc00538258c0db034290ac3ef8ea7bd385672ac15a58b6989a14ddfd69559fc61d8c8ece4461601753d5f57007b6362ab389a4110033e3a7a4a4662ee62f50fc91a97f4b8e447dd5a2468e24eeee655a4e15a60a7252404ec7b30a915f058815c2173712fbf4e864affcc98930843cd49d650b083b3fe5752e0575912339473ffc40a7f1d66410e00befd0b42e07c3bd35635fa275a4563bdef57bd3516e5233e4a380489a1cc03721d44a1e724feb0495bee665502be87c6a9022bd02db19c8c8ad3f4c78277c06e2c5d2a458421f50a6d5df35b1f9856af84747e88a517efb75b436a45f1688111f1a7a5094b99a9bbc2517d63640ae3a79439618ba8354df91b290e7740478a2f91ff0f7ac5ef296f525ce2a775c1c4de3c5a821b762057ec754bd4168ed4ea6a97ce97a973dbdbd46723fbd9dc7141175ed03bed29f54eefd5e0de57528778e41b65797531b8bf41bb083c74eab1085010bb3b41f446e30e4adb1486cbab63c2414caeb84ec76142b095201f5523d8f7125e75ed5eb046e53c6692ff9a97638358d68147b025f34a398b1b61d4f1dd599cd306042ea615bbb53dc8840f156fff3e8c4282f9d48f31ac4cfe97822daad42eae5e00f556ff1d6ce25b13ac9cc40bc3605be19735e81761a573835471bbcd53253e0dc63ec18cedbb351eefc559f440418d79c80f3535db71df2304bb57c0f84f92fff41dbed85e8b78a88be289ced72549ec7d313132f3358d297eb7c6b31bab84fc7443eb50f8e738e969136bcce4876f2698bb4932f6e749f79349b99034520104b6f694078e10ef76c451f8455f221ef837040e2768989ef645d125c1ff73d35fe9f088b5779948d820dd2bf0f6536f731975c7c9bc3582cfc12e0fe3341be813303e27e24a0607f4f76f9bef81820edae99fcb476bfd71808f7ec4b3c1a84e6c08c76e92e8ed96531b75cfa68b9a4aba36ec1da1b65dbd1b18c2cfc71de7dd0913a98f52b25567eff38a120653582d18a069f40e2885d28fbbfd41ba2745cdd009e0e48fbaf79857441352bbec710a27e79500c6dba382957a91fbb71bca5fa2eb05af6d1c4368c936f43a1f9e7a4f2e913950f03dd06942372ee4f592dc80b273c467f549733f9802c8f8f94669e0147a18000771d71fb7729844e99b801df90fc90badf76434e324d3441687337b921dd3d8f4f0722e4a26a03d46b2a74054d3439455088e76c31c699a87783ee30ecc7365826207edda9fe97b3962567b64ac1dac3bb33ce86f831c2bdc7d99165ff2ac450a99bd34ce49d05ab253b6b938e459dac58eeb85caa8f7597beba853d89b0fdb201d173b2bb953b09552333505af9327a6654b9db445c5b38d0ca44ccd0145e76d387b8ab4af5241cf798a2e42e94dd5686da68f699eea7322ca219060b2d1da2a994b607d14f8d92ecb507a1653d1349517c0212d913de9988826da75343e3fe2270b171643cd4d03cb4f64b5794ab40e9af7c6f5ea53fc764e216c1c516b995352f2b1bc8327c7b8142e27c96601a78a5331bba91fab7ad50b8321d094d0dd8536b395e727dd816cb418bebd78c7141ba2077893574392ebe7719808ead7313266572cacd0a199ab3c1a7736bbff90911533c4955369bbb291ef9686c1151fb77a6399e80511957eef6f40efdae1c1c9fb5fbe423da347a93decf358a74668d67eb48e7ccfcce06ba8fe90dabf3f507a96cd141120f771352c2b1c5d43b11b7ace03d1b625f7973e2caa7d48dc86f0eff08c4bbe337a5622db7b9decb1abc60da5d501e86f37e26b0b1cd7610ef503f984731f62201ececa0b88a6af706c51522356921ced5e956ae3c0280036bb9d285ea0516377bf99758755c2c4ba04fa4ecfa56061339359cf092852cb3124f369737202fe2b9757f94d9865c02e1287d654ddb04b875eb9529b75c1a956239a02fb2c3a39158a16bf6546da2d3fa5b232cbb377563137648c8285af88fcd91debb686946024acbbb3af169cc9684608f102807f6dbf616be18300ea00a6042ef4165f36f248facb3cd50e5173194a77800acd61b201d3e6dea3cc1213a8faaa9e4fdb01e5960d00f31f60068736b7b1b1c57036c10d888aa58f458ea41fff53ef5f894ef6fecc32f6dec5d1ed20bfcf6cc71bb551133252f5cd2a4e7a57d59e84e61672992f9b692669b2430e0d0a76e92e9f8680a294b99bd1afb5e6574e9341cdbc2c96d71fe14853d47599b27e9db67a4e5ed712dd22324d542a6c37dded05021f16652397f20a0f92f1dfb311c824b80ed918cb921362a11e4c66adc33b2d2e06b2e2490f21f6e141f7263aff149e7527639975459d9046ab64e53a8e9724723d66d4c8df0f2c9ba32593c76c58a6d6edf215b4490425f1e8c202aba3632edf6b1d24290c453f3f34f930d3e8145bf8d21eb2db4b76c7d95dab06d5a650fefa80d91ccd1e7c499ee745d3da268a35613ee4d27d3a032bf120804ebe99a0b7dded54391ab57363fea65c9a539b333787fdcb52a9f8402fd7c87b4b136e45a37d569920d890e6403454973970d0b56e9c5c370b219f75b7f5278e4910ba7a4df97ec4a9d35116648d5b6955e554c6c639e55c0640562e605189900d9d871ba962614a616d3b1821c18bda07ee2630b775839b3493bc7c1ac139a6baf666a34c59f25d8d05a26da76ee7756df216294da2d978a21bd71afadb67a6997c270e632361942e8eae77fd4b51d071ad1fde0bcede7a6f2cedcec52709f7968d8ef2c6636746cd10d44d0b0192e8411af675399a22428be7fbc2519b8792f69d3df4dcfa24a421d99283e4db6a90f041f4e808e7f37a04db264182553169aacc151907a20549241a98051a5a4db60218256311fbd926a91754f6b54a8ae5a3752eb08b64bd83d682b5f7feb5a323f2975a038bf7b22753231fac952cd8c719196e48d2e1449b06019b51a6f5f9c1dbe68f136487c8988e199dcf79548dfab8fc91cc2844a8c68c9f709db270009a6f9056e91e4e60d36bb52109c622f26c509de240fc004410cc5c66f6bbe0cfa026bf5ae8ba3fa51532ac5275f009caeb1512e7e6f62fcf541c04a56183f582286f1efc004c65576516be8f1b7c21870e1c51c08416326bb80c34af0222d0e40bbc87f218e563f4de06b3675c5210fb4eab4f16062fa399c8c4109f3640559757d0308d5802c154c27d6e5ffd2507303bfe4804d9dca3316d6306b9f5e6596c6c033283b94b70251720744f2bdb132d950579992220df9a3620ffed00a26ca01d69ea089bc02822373b5b88a5b491287108c170d2e4f35cee5940b279543e4e17d3d12503c6454c8f07ad6010dcf60d37c30cdcfdaf5bf934a1921ba08049e8e92c04b4c44357755f024cb0935b930b46c7dff9c90d77b372b45c3689d9814d30292ceb004cc1e4d9b022ff4bb3491921cae4998298333c0c8d45883b2fe400ba0deed2a5971e85d71cc4f17600cfbdd37de551c4f891e5f167e4e00b90803b94cb7c4b673e5355a6c7f911125ef8dd68524fc0d466b4caf55b61b5ddf8da950431d38cec956a7ee7acd7b5ab2ad449a336ca35127781c39fba875d3d6eb3be2f830b096862fb0b47a7645daf2f830b113a1018d544721b1567f8d5db72ee5b7c748cdb2dcc74636ea2ff3c4cf2325851642a828e5c2759d62461d871188b6de395b500abba369806425a7e0c64cbe58b55ce7549aac7e61fe9283d6a32ad754ecbe9b3c595bd8f620e2f88c161bd35dfae0e4c5d63224dfed53fcb09d316db6e2178a73e0f88ca2dc04c6e9773aa3b60090b55288b21d78ec7a180b57e7feca9ba04043a19c9503a7d85c785fa123537f2d771fc1e9c495d37e445b9a8b0cfe3320630bff632dfb71143d9f1e11869993f05dd0dca0898625bd6084565125d9136ec605b9a0b0af941385ce30891c2a1699c42f508231703a08537b5d0ec81d8a221623429c4471e2b9c068a51c86d478769a1146b1e79223ade7b7ca464f52dd7e5cc16b54f8b90f72ce9e5a95a0452965eb2ad9a044bd64067c7dfd239f2c0575d7bc7178f32d98b2c55f45e0385dd2fcd64dfb8abaae1661caf4d52dc479bc2496205f80d04cebdcc55246e92fa13cb93159f35c8f5e5b4d875e8db266c21aa389c5e1f6502c45aea42383e52a371b07dc1e2715d2872732eed190184852cb3a3715cb752506475ac857ad66e87fe1870af34068ca3a367af3f36ee405d34b93928efdfef658ed390dff4c1349d2751e515d9e59c60e7958baf542a9aa227f96bede7da929e6d6f1bdd6a89aa67950fc52eac6da7d62d20b9393f831692320e9047b9a58e0ce27d558abcfa3745f17956262f57d145c0f9c51903f53938170032063e2b8ad50516e0e1710c12187f39ea33dcdb1e690dc770e56e36a8850ed72c9c7a1a99e224130d18886b335146c4b932c722a173f8b3bf60a2ec699f4792b88af02da448e9df718e7b6891ccf0661dcf7b40558c0818b1fa32cc413a11660a6661cb0d3485adb9f8c3e8e2a8260684afc0f462a1e34cbe0aa8e106af9b77de307dbc8bf64a4baea295a7737436a4492332815450622d08e69e020bfe6c778787d253dbe5a5f176cb094eb9969f13dd2024aefbec2737054eb18b07e3e0ccfc6e4c63f6a51bc332e4c42e82b16c7cd219e1bc54f4d58ea9aa682c72f4b9a53422be502773d712e5d88e100457294eaabb34f414610364840095a8c2e74864c9bb1e8e4145db60b9ca3a34e32d3d9d1b06d61686eb313c595eb117ea1e06a7dfa591a040da6afaa6c29f1c4e126703f33c4d72f54f4dc5fc1ae520d9a0f2b5c31d94ca9bc74ab934dab151413b9285ff79f0724f313095f78b883a1e22cf3313ce23bee7718665cea65e67a555eb4e6ed915bbdcc01860bed5d3b90f77f115f0f4f5ce2fb97ddfdc10068e9c65ec1a38f02b77773aab8dc5bf0dadb6a02f8e9138a9cc14354beda95252704edd71a5f25fef38caee266dd9b4f69e3fd7420cce0d05f5ed106e80f9789bb8f4efbc13ae8d8e90bca90f1ff7c80386eb8c4c873d07ee094ea930f684c6c58aa3fae05c0a07e69bd2e0876cf444cd64dcd92f1ce4a2b8e994056ce3bbdf7347a5d00619dcf711588799f0bfc0511ce11bdcb607a5b995d3a6fca90f8423f4030b05a4a0b7580a062b11416e210c5c5622516e44041b07405167888a0b034450b7a8030b05a0a2c74b02260bd9658e0604132160bb4cf9ed2a24a1e8387cd73e2bcf1d8ca036b8793c34229b290038584855272410e15066b5772a18305016b5358b80305175868d07e2bb935081824705c3e2f613cbceec4f3960cb077c848c02e8eabe03d7850b00ad997278520b2901995c8af2c47f2e0c0c928175f598ee0e103132c955060ffcbc2ab2af8cc7c2bc75be75ee14e29b46d437b4d5f680e110cac66f6f5b93088320423cd02b7ed516824fe48b8ba301f57fa431bab1fbc1a10beaa9c17094537cd37605b0c195df3deba4f1de1079b1c41b01eeb6f912360ff21a5360159e971c958375f77aef3c39a77767f28d882c412fe0273e9ea78e97322d35eab4e923b055306d2f20a7f331ee9b294a098a59d3bf9202eafd1f77b813e9e5c6e44b99b1f82df243621ccaf3bb527d65783f814813728a70d7ff8213fbd2a8a039a6d787934c7a0fa7c433cbcb97d34a8ccd6b131c4f7dce9f7b84e763b5fb5005f1a61b8dfadb791a373963b50faf3e40d59a703282f3704340a7b2128d2b91ade11d1c2475c8dddc338b3fded1bd8052edb8940fa31a7794a7cb44612e2eb2ebd0ec2f875b8a590bf4b7fbb7c2e9a9ddc4cc94c27a2a62d2f0b8b21f09292935daf2315e70b1fdb64874aa8ffc3525d4efa48b672e08da347a1692169b39ba3358f19cc418416398ec8fa8b32ffecdbac1e50b96bfe16f4e209841622cd840f54347350e6236c1daba2ba29c62b6d1a18ddb0d281be9e39189eb87261f43dcbd7a79ad72f76641a4c60a9f6c56e5e971c35be68d627a8324b0c6ff74decb23a33282c3d15bef82e7ae641f3544f464efc61c2425afba4526c1dcf4b822c774a20a7063398337adf1b74731545988707e557f7f56691d001b06a3b8187ceb5c421199c3d8b13b6df59c82819b10869c334ca1a4ff686e97734433245deb067117a727be30367d23188f007ccd24e1d91798b82fb6eb3c113083adf27bb74ba5804bb2800d211ed4822a4143c7e85aef10f19b4316fb7d1561b4c55e4f39f993fe5b65debc13058d7f8d158d99536a3b801dcc84fe68b09f6d735a572cfb437074037c64309dac628785743def21b9505602dc2baa9d3120a4893712f90da1cd7677846713ca6b9fa4db94e8e3f7266d7b0730f431b599dd9841063e6ef6769bb7d35edc7bf6566b1948c1a9b30d288ebdd1e4593904a63769719c85ff3db2e09e1cb1acffa8fb96bb7bd54239a99774e7e2cc24bd7d3de891ea2af279beb9d578a1e3f637ed72de02962746010a94542a056954ac852a9217e03427dc7a41dcb781e3862391d392da4bea294ee41392777a5a9be28deb28c6da727c8c54bf5fcf572fda941b0b7ef361c83cd353d613bc8dab28026818031502a0bd17a96b40514ec06011c0407253b4134b6f42f833fe6ea45fcb458d080136a2c3bf1021aaa8422a795db22abfbe888f4aca70ab4352b2eaf52b249877ad8243a76164a36e0d5832575888200ab41f7cd6754446c9f4b45f1e23b1672217afd1932a83a0b5880428d4899a783f6ab018898529d7c0ec05f8e72a4d0acb5a61d8d9a4a6286f8503fc0c7b018967d2c3f1025050611a84819e0e5528736c7a1ad385ffdce9332d576da99fa72822142f85575215b257c5211c0665c642749d16e8949147c370b979c97c49a1a6ebe85622466ef4e22368211b960e4643eb39e4f7273efd28da15c93ce2e6f9e8da48f9ca8a6c80de41b6b19ca0c76064f921126ab1b5e7ca40c41c6816812d80777a5721ed52c6412e8d1bb057389656c3769cec8ab4e64d29382de904164ea3c1631c9db1a298ef30532bd262097598db5a94db6e063d98cce17a1db06bd8db901ab7122ad4ac6a3eca7a42463840f6d3241d679848d3bfdc556740c4af3c3368dff54d5565b12bbc83ad425bf701c9c1b3ab061557457a8298c57abff242f889fe1db97d16eea4b849872cd6920440c396d78878f45637026601157551fc5598de794aae544887c5bb2dbda69b6ef4c0a3f26c930c8dcf004470fb265227877196ce82d0c9672d093626d9c0508146b5589858cfc69e5f7802ff53af9636abd9e873e005973762874e5dab069648068515939182ad93678fe205af4023f7e4d86c81cd68b8b38839333f7be815a5148cbeb40912120463f50949e8cd88bff7b1e23aaf47063a02099d738e9fcc8f19be19d8a05d3cfc2092306a36288a7b6c87fc13fb15a007c7a5723807ed8f3875676c72655ec28e544f085c95c33ab0b4e27f92ccc59c6059f20bce116fc756e88cb07d27f581e366d653e355300a5e5878597e6fbd91295292bf89bcfe670eb7c2768e0d7314db294a0791a460dc338123124813924762e523598e1a056a1b12a78b5116c3824f7b0851cf88b33ebff0415c5667e406d97a30f172b05cc6f09923c75fda1ee54b2cbeee5e816b960ccfc07fef47634ecec55d9ab93621a2290f6b56eb0386c5850dc4a66d8050b955614831dfc225e8275fcde5fc6f2d659047c76c3bca0164ee3c41a59db28c913b63ca6e8857d81a404c4ef92518fc226917ff84b42d1ca924ffdb3e7787db6bf4429b8b6290088b66e67376729f9bd05074cd4a4507021c7825c889e6da3e49c324fdbb0479e5b60303533d6061ac425ad336ec9827c59066e4be0d1cab0996f2c6993824e920445b6d25b6d70bf1f4514799cf59441f1573507a5e14c2d5996ea7cb94373b2e16141a74191d982b568d9d48eeef66b67761addf1deee9b6ffacbd172bb3c09b86318c5b0a3660c7103e64436879025a77938b4caf73ac424f0ec64dcf3985300b75c3aa6326e0efa2402758c10a890472e5e42213e8471fb28f980d5ee904fa5169b81087eddae0ba207bc4281c5d00281b9d2b2c02cb0082f3562f1c3adcd41069b7d1c0f76c36dcf50840ba75fdd1fa92f9813b362c5c377d8d85d4b62db9c0eb27abe95de1a9501961542750b63aaf3aaa91ac8da1fc4b487f8eb8cafe12e72c9f71397b137cc3b8450fb8b9582970e8b4e01002d854bd349306becb4edc2f06df708d4cf18f04f68fbef82752bab7bc8d75e56115554e30f384c98347f90a2984433b7031f1d6e01211e57d3c396ddffa1c99c42baa9a0cf2e6c790ef0d0c951b20d3e0048074270d70fd8cac60fa838e37f46083e603ba04a63b9242489830036d525d42e631b47282ff37d29c2297e094ebab49c95e8db64673a648948276fd2042e8e4a7b4a481f23b8ac45728f361d20f40cd03c7107ec057ef45b8865a1f3a818aac49c96049ba22c50425b31248bf141792fa6e21a42e1a17456a2ec95b9b148bf2da5236b31495ba0ecfcd1a91cb4c8b4b9af81b1305d031257e40dbae8db28ac4bb9a28e3542dd5ed8b0467b9244a786e85c7ebd4483ce4c3e35b60a3cbd8acec89a6bef44b72d539e3209101fcb28ec745a453f146b90733769bf96b9766f2751c3783639d7014977e60d3d2553bcf0276fab24c3277aa4a1d6237520fef0654080e8c40e7918eafc013cf8ad4f767cc278b0ca8c19029d844cccd841ee1bf3fa4e5aeb3f0c157c373bb03c7c3eb30b20efd62ed34c1a55a430685487bed8c631f164e1eb2491c3dd17898bfd3728ae7fe80bf22f9b24fcedc66c808d813ad1cd8b02e9e807ad2126ac4a05c04607763b740996be6ef94c0416300d70343b47b9d30adb4973de430653979821075227010cdbba100e46cfbb9df4932160987c30a8a2589df229bcd99cbd1b47d974c24196f7b2a1241fa358a7d4d201e15e4c3d052e88e6eaa778c4a31c3b7c058a89b3b8abc0b3c1d435ebde843d90ea4b78dee9a6703957e0e09d7a7a04395079094059f6922d56ad39b4a64e7423dcf3674f38c290b1b7d4c9a54015016c37039293fd2d7a721019ca5410f0d205f5f90230c7916ac16b6f7a467fd32ac07cad9bde3c65fc614194ce0bca06c4d16174b8513a7b20c9be20dae55c84b4b93ddfdcf8275939e77d7b353f0044f970120934144256698b865d265d6d3c24ccd7fac60d59559afdab11f3a0ece2a5897852cfe0af36db26f6d19834fba45f4532a8f0824c22e5a99752aeee04003888315899aaab9bc0cc7a742b06c250765f990e4e45aaebf0a4aa8da75854948e3f75c198ad2b927d964f08185e496f58fcd2ed53b4510df3458f80bb480ee038842028dff97b426095640c614f68c62c22953d52d2a16112a333500d0b26f50064ce6325854ca806bf76176e1809105c4c842d46ee2d159c1c16cf3e4ba8c1272f4b0f9bf66a6de99e7d048016fb81f59748868ed7ac9d2998ceee3b95a1a0aecd312d76318eb1beab073a5728e442004da4a08a89932bb73e19d22fabc5a035230868537160b48f31daa60a52848295f5376aef2bfcdb694e4e78d6ea143de79a4e32be80f8c9fb051bf23b6ce012e1b3955efb896c128e5339902f7850a425f5b39c0c47e5eb6777bb201f0e5f70128768ecbecc6c3430fbe950bb3a43e364c14b75309eb9f833d742e147c9f8fb99cd3121fcbf7f63b385751c0b48cf5ba85beea6bc7288f6b1c3d8ea3fbf093632c83cc08248f69677a696d08e16208645e580f56c4f29683e3a8f0636d6be20eadf1c909ab2ff26092b72de2edd60d2b2ffa34128594d6549873abfe7fc5446af924f73341dc7bb6701a344df6e4dfcd08b1cc43b8d4a50b5765aed4271a5e3b394d2b421419c5bd714f16392940e56c6f95840dcf7c57f18447d4322fa67fe3852b766ae6b18296f11cf16a9a2abc6c8df3c5ac3885edb6490076c8140377384d86584f3ddc74c323edee8a5690bf78458c7b3db23afdd2511465679181bac5504a8c8defc120155be93191fbf716c78004a8ccf0f505f3cb083c11ba8cb5bdc70ec951b53c13ef9a9ab9897c7be6cd8deba380593365046503845cdf9dbee18108ce31112ac9076892973fb98bab05db180a4dcf625aa5422814057b9d9886d1dcdc03871778571035c48248442602040c7e129a60ea0decca50b607921813304012f6f03702269eb1760e1c14fe5bb2f96f0abb0812a54c9268108a54d79d5c87c6c5254bf2698d1c2bc599afc1b6b527a38805a78f0e59b855444a1f1f31d16756e49b88f3e89a1baf2dcd2fb952bd5dd832b7ebf4d0ad7b61c79721e08beb8d66b21c0f1487d5abf68a49d140b61e9ee184944c727ceb67fcd0638698064d279333b6541803f52a905953420580c0482bbe8ea7cda262ce40aa223d10e2923e2bb51a6a6eee34b95a414647cb946c68f7388e87d0b55d2f5186e301be67719c2387ac1e18d7db8b85f09e59bd3411c1cb292d77ab8e976c4e2aa2a7e00b82e50e4956ea24a5297d9c8dd503028975b5a675b75f448bc25acbb5f35ad5839b5147ba1fee8267c1226326ee1f690ebd976494c996f84a4a8d171bea2d21f12d99044dad0cc1a18c9a28591033fb427fdfd375fce29bf32342040adb9367308f552c25935e33fea65c568cc2a818006fe1c34c4082c663afa62ffcfcc752f3eb9379c05962f53d605b35e6dce92cdd0ad5b849d50733b171402f25f75101676d6c1212cab899d35d5268f3e5660b177992198d784c2fd32da6fa643073f80fbc36f708c4c1facfa5ca6da45e66ed6d1716625df833b093ef9150468d446eadba73e997ea6bb9c698741212607eaadf046dcc7e69c2324362b16d99c7bf2ecab60d86042c60dd5a4b4f780c6022a6f3ff4677d0e9e9a5df32ab267cc8d6993efc337fae8693722ec05040a6d3cae7b48f4a89460b57836a9c2102150772309886507fd8255511a7c7cd3bd68506a2a71befef96bf06a7082a7e69f97a19063af0541f0744cb9fbd4b30f997f3ced07c494e47c6dc42cc38dc0ba5b34ab3090620dbbfb40cfbbacb45cde7e36bf2ad5ee8e5c33a53910202948acec0eb61a05e54bc11accc638b78a126dd99d2861111348289bfd9ff78599ec6b0d104aca220089154aa31a56fffae66e5a44b4f93af5b66a68ac48c42b7369757393b474a45fec3036d8a0718b26bc6015a248f6943d85a87ccd509bf41c6a18c520611a2a9be1f28b50bad5aaff0a33758e714d7b08cb5b47b8e1e44a938150534c272d530337a674a30152725dabdfefbb6bc462c88e2b8bf14cfa8119945b07bc0e0364900f7b66b0edd0ed61a803253a761db81a1ca0f7f569baa28493bcf3a8600c2892f6078f1825a3c758c7c71fbf05d9b3e71da4d4586bb54db65f4368e1ce8a298674c5c36c73ce69d5a691ceb5349b0d3fef6de03df33c1806eec22e19efea0176e38b1a912dbb2fd91d8a1d9af8fd25d2dae92b279db4c24d352ddac21bc3604eb484ba63c07a9e550f8568b80c49414460470f3488905e06ab680ed750751af431856a3e900296e4f4a29cad410b5cb3ab5f6b7f61660f8e4060b5b2ac977ec9b0d32f1a4401e287b74dea2f355dbdd52a1988a6576d7f1f4e6cb3d17f1c05504d40a8f56d76ccdfb91a87247db9a01aa11404452b4d58900ebc62afeb24271b10d73279590dd2fd0b13993c905fe3d8de43e8600a1ad24cf33cd18e53653c3868d24ae66fbd7648f184f60cae20cdf30519786207d0908f69b9e2a633cc9c80fdb9a55aba35a6fb621b636d5380885e8f6880de3a5708627ec7e2e0dc86b15a08847c1ed14344870a7b78ae67ab0964705691575b59080474fab4d40a0509a7347391215c139f8ae3908752cb99edbf88a3d00ba88b7ff1c2ec02ea16f01c222c84b6b946a7751842287e6410e8832f4b045929f27cbc089fa4cd7cae6cf3541dfcfa2a8d0ddcd34b741283096a466eae22d0fd67d640cdf962ba6f4438bea8b700f81f26d75387ad39f9e342a2c3038d9875c09e08a82a7acad643990f47aab59458698e398555fcd44a110f4eb25abcacac137471e1cd320554e67b99ea87ef67e685631348f76da230cd0db0d2ff31803083fdd8f634683402560260856bef7db15620480ba797d055759b5ae348c682c89b098a2b73c48d63a55643355e189040c879c88ba01e7e82f759a4e0841786820ebca0322abab56ed4932379d537d115a2da3711a4705e55885348194158b0484099e288d4ec8bb4c6a9adf85b88ae2582c657c53c655c94e9e63dd57df60de4ade03731406020c1162d3b07e3c2da2e0b4b6c25ce089be7f2369df3fa4d1d0523b59a0421ea265e5377a79b454bf441c04fd3d8f6ca840fb5921055b2f750bc38343eb4c662b8c9711714b0b702f401bf4796c385c2ed534645876f4974444d5a83cafd853555863e09a1e8d314b5083c275183038515e4feb7ecd44dd31828cc9591e6196240f792c4262e5bfb6e33ebcd136f9cb9c34a2a9118c917af4552706ea1e8c46b256c4f24f83fd9e35483f4c5ea687b321ce8670ca22374a63bdc5c75bfdd55bc3619d06681c2d1fdbcac129b0e89616776054394c29124b73f10f3facd409b46cfdeadef872c051c1061dc59f71c3c739233a35cdcea6611fe28c163e678d85cb16117d5b24ff8721a0f357b86e07032b052f668c997481c487e42547accae00694c1c90d8a25723ca36abce32ac1c80d7706634195b70dfafe4fcbb2b913eadff4c0c695c5f540261b06ce33a89418a391191f6c89ed4434a66e2465e8e1bf5ba596a5e412e8dd2a3a4f4da1cc8ebc48d079ea734c34b9c72ecaf3b1e700d27b71163dd13891ac50accbc68258cda02eccab09bce8d8f75f8496a5daf61ce0beabd8a9b0af4ee901317510f8fdb82743bbbd60a4844033b8bf7add0b5b6b040301180e9b26e2aa536c4c23eeda2fc8d312eb3b511f80206ea4ccc2f51bd5f62f8f7b7d98736b04b4640f02816e677952eebf67a0d9304e865dc805eb6b51596a219aae1d66f9a061ec1ef2d62009b0c05923e4807f3f1232343004c60621a64c4c894f9f3cfff4a798f25399c7e25b34f935e261f7c5fed408100a47872ff0e6f69c60a791dfc322b2792bb6f152a3ea773ad3ebe5e6f75db01f09c80ac86275920e709140061f8808aeb327fd32a9b931ae2d58d974ba739305ac1012e0b3847cb426060d3cc6bb035fcd306178a5ceaa5f6fa8e3e8babacdfc0fdd53a93a3dd3196fd6f96651de4ad40eaf1613952666c2187c0587c84a01453bc2675ec6e57f846a05dd4fcfd8706a9c44125f2de110d30a454417398fdbb97484177e0faeb345ee2bf95e635d39a7a592a489cce1d320279f79ff2a954cccfd5e5bb6d87f54c2ccd6ba1ec10ad159e8bfddf04d884955554860962bb70579cf179d7b9680fa2ea55eac6539be5ed08b83bd80b144312e2b762ac0b4c5ba73497abc46c6da135bf101a2861a9efa3159add399b3d9e5d3e4b33cc3f2108d9612c0981f606ef82c51de59a5d1b8961b8ecde313936080671e0591ef1c5a37e9dd174d670d1b20e2545e79b5781d18adabb987bc415f9d07faae348b9432782dbfa146eac22113385b38fbbbfb8da3045536a0199c073bca5c4d9754c0e46f81a2ac2648bfe53c9019fffa477a03816d358f1c6ca061c02017ed30a0e1ba0f2341c17f8e0171df78f65f39f7d15fa6ea37f6dd19f63135af21b119ab5d9e4328cde6a386374d697e97912bfb36605619f065e91888e36005033373e26311dd1714f569e83ca371d3f421c0ccfc6b0a141f80040200280920240df7e00102882277c79225bc7175c08214d5af9ad867ca706009497586206c8d431d89dda027de81a1f7ccb04489dfda79151a059fd6c68619ed0e4c449b627505979bf49cc8dc4d52a43fd959e35f821330ad0a1f5878385a1e9e16718f46f6c15791d2ccaaa00b40e7c9d12a3a21a799c99994e787d8aa6c6aac0cafced4f67f44f93f9542781b97fe493a52edd5be71cf1c0270fc6a7a7ad8c073ab32f90aa7d16505079cdce77921cf38f63f87d34da61f87bf7d836098dcb712bdded4b41d78583e8a78da82d1db7dc26318debdfa36763c1860ea82d17a795e5a28002362cad398b7934a34f4eb90b16849875b766728aa568f87594726750d65978b10ac6cd2fefc752ccaf25146ac0a48b71ac1182a9bd59eabad692a5acdd289740d5ed9ba661b1dc3b03bc460013162b4d5b1cde8a1926505f3cd9c06524c9af2ac1251c0d006761f61f70b03a36c3d5310ce6d1d678e7483abe82f9d73424b04b760092b20ef9af454e4addda7e0e57214bd960d240c2d1683a4e127041039073d042ec9dda52966105cd14242a3e0b4783bf6b705a2f888b8fd183b7c3cc3a84562494ec6ba640ea731025e2ae92b07b6ee32b0eedc4d3503407b6ee137edaf7fb3851aad025898319c6539fe2da717ddce7c0f4626563b907e32c7ea543d7c2f9125faafcabf8f227d811fe300cc2a41be00cbb68f231aa11fadc5dc1141f125135f230b237d843a02202a161b96d16b3811ed7cb93d11962a39e93e4354969e9ebd7bf5121c9a8ad8bb629a83f73148f5eb33d6d0c3140510dd1b91feb48f58dcb1c5fa1d9159864c3885c414382c345445d34d16be3a4a765f7fb5b4f21cde4d63c641548baed20b7b066200c691ea7b0cf525e31f6be8dea9e308f07289157a0dd0e9839d838a4df452fbd61983ffd5da5f46b95219649e2a33ccd20ce5cd0997b3ba6360cdbfa1327a4924292ecb1b5468470758b158e841c0257f1d589186941a98f41681caa8bf847bd4ab48adaae1da065fe08b53c75f44ac9704020d216c015cbc6be1e3cd53f19e5c2ae87373c73bd0cc39206df8ffb1f270d0aed453ab8c4fbb97742a63e71ebd191e7a163086a9306049145d7620f7db99b0978805065be1dfaee8d28c0e9ca66941bb0657a01d3b24456ce88beaeaf307ce9bb93d0413bbf764e0a0bd5393bdb317e87c42ccae587fb8d2507422f6c4d8ff5748efc258918f457591a12020072f8eee85d25f7a3d2bfba5cf2cdc6438e41a3e2ca009c498b53844e8d3fda4238ed15e498d18bb36ff5bc3019ce8cc5680305d4a9495c360a722de08e3f9b17a643d08f452acfb6e685d195654f2b1eef8f1ce3560117a609c146f615a2fe4c1c8a474c023d5b78aaf61e8984308385ce832cb19127eccca8716bbfb430d547a872fa0b48e8e2c4b73610a1cc17646033217af5d90a0bf90f9e8449900c83fa7816f9d790f543837838cc230cc30d1b3b72a569562ed80998536fa0895b1756fbc4df006326dac7d6e4f9f0cdda0953f6d39204df12cf2cd4e68b823640372282ebea6ae634238dea608bd0b3215ab28855d3580bf1925667e3afc61e9d3d406b9cd6fa8fff164052f7247ccbf4eb478e07ab0b206772547eb00001f4f3035a2167a3592090ba9a0e4a6b8fc3bb00821d8c3722e0fbe229a0a7d2823c1c5dc1ab87032778e72abc0176d86882bbb4b519414496a30a84e93ac732479bdf10cf968f37187bc966863fb56cafebff4dcb6da485b4bdf7de726f29659232a009c109d40aae398d75608cf3b0471611aa5ff1d676af3b199aca57afe502ce5fe61981569e2a66ccf37df82595aeaf91ead4af75aa0d2608543cefe9914e57dda4e7d53d67a33eaf5ff7ac28bfbfdd9c734e94774ee46dd88db2b6bdade7899fd65ec7cd7fecec58bb791eb7ee1d8e73cd3d2edc197f8c9ad7cd6b0ebf47166cfd8a72c61d79b34a1debf7d6b7ab2738e7bccd197fb4abe6fab66ad5a55f3a779468b3edbd0e90dfdd6eaf36bda6e2f9176622ff39a59ba3e09b7719ff055d82d686297c0f8a6d8319c4f30ef394f29edf30abf09ef7d0cf8a5f8fd37a27d61eabcfffacdd66c0357cfab5eb3ae7de0d3f4eab1e78eb1c18e3eafdc2b762addb114fe6e46df696fb2a0f4dd6602c748f937db5d3642dff38d14718032c93c95a80f9f0113ee9444dd6a3f5cfa77b148ccdae0be5cfce27e7b20cc6ac321306599665589069320dc957cfca7038659e674dc6f9fc3eff44134ea8b56e3dd48413b416a436039cda85d3a775ace388b9d5c2b24ccfdeb2c0045725409a53c7af297570248744f5389e352538defd3459bb38d75ae8c06dbf756da85df7089ec1db0e3c6b38bcd5883427ffc1674df6851b185be101fe9d38f688e3db0cbf4e1461725ec112887c07619e8fe333a7471cf7e837fbc3f1f975da0fa8c9b45513ecb9e6d62b8e67311699f8f0a676b5030101010101edd8b1433261f93281fb9cf3ca55eeb34d9d533016ba091384cdad6b400a996e7d03d2d541e59cc63be07ce3bc721cc7e18419c7370e83280f460f28aa721cc7d5ef7395f7b4076e393e37299b518e9cb0fa84f5c78331eca4d6b1ee811e6aca52d38705163af04a548baa515d529d542955a803efe91ada0a5481aa921a245631a7f07d3d5c02f8fb5ca5a42101525265391368d727e6ad84766131c787ccefc037b1870fb8a773c2fad32e25ee615b815641f8ba8ddfd47ae3f6baad59f236ee59037a4dc95b173f89f489287c3e643e7614ea8fcfd4b41f06f4286ede475da57594a6122e9c40134995e5889bc8025019a07655dbe46d1ecb67dc36759b6de242f71ee1083c78545193b973a26b322964ba5bd790c894444950be4ce04206c444b1270cab92204d76b18b15c8c6b32653ca15483bf2b363478df6b3c3a77362c6f195f7509c503e8f50fe4d88593b7484b2cc0cd2649aac6ab230d75f85aa70c87c2c96d0a375136c4a544289a0a8f4d94ec458cc3b33b1c7313ccd3d4a55a0af1caed1f887c3bd1cd47150a7a1741e05693e9750a9e292d6b1cf78f5a979de164e501461f3e9db08f2379fa008558521ed42b9e6d38b7cb5c8f0345e23f39acf79af066eee6d61ed7187eecc803e2795aacdfbb16c3e47763eadfdc19a9999991155309f25e6cd51d81c55addd50b83d941f8846a29191278d3e39d279348dbc29e904fa9d0d3b057d8a2a70946b5e02d137504385934a8f9aefd0d73c6a1dcdbdd5c2da34ea51f31513bce3d3add7294a9f5f669876d2ae3901aac3b481629db586702f991f7041485eeb1f1e3952975b8a82aa0861217ab004220fba94946e1a53bbf23432d27c52d17c6a5f5e73ef7e74b3af5dd734dfc4137ad47a9c3329c4a75d34e552a6664e08f3a4aa6ac4854a53964955da95271052bbf254f29a6b2a5c25ed5abd0a874c683ca5396a02a940fd3e8d78023701ea5d946a9c163cf7a4371d63d9ef790ace0212e4ab25b58c5abc97a597f75c5ecf2b23b74d49e9e5979756b2f8f2de046f8f33d7bfe236efbc5eff8a3906bf7957c5ea517369250ba5cf21bce73604cfe5f9089ed499ee7d9e98593f5b36d80d9c5bef794bd957f62d8113658fd72fd20852087dcf4b681775cfdbc4da9a9b7bce6ad775cf658ea7bde1c99cbc1dbdbd41f1d861bbc198bb7783e2c18ad9a6164bcc23f6dbba31ea567487a445f4e5c3adf70f7df910ed2c5f1dcf58479c511daa636753ca9b1b9f237de9b47cc6255db94e38a58e0f6e4cb27d339c4f8b78b8262382b2b396b50b25a66e7c8a2ab79a751e7e234ed16b9475afc7949b2f311e3ec31a1ea208d36ffc46ac28efa1a8e945ba44b79e6d186fc9b0f16ccfa8b156cc5b32281f3e6a9c87a3aa0af7d843d6fb6156cbc673acf66156cbc6b55b7b941bca51dd3f6c6a6a6a6ac41b1751b871d6ac5fe6b45fb5a951dd8cda86592d9beda34bf3e94c21e7068542b9ca270f478937cef29ea64b3d5a47dd8819255aa75c3c4a83c0791443967b3ceae43159218f1ca7a7aa4d8deac64728a501988cee23fc71d8e81e760367ca3429538f764e8f4acad42e144b0c41ca717a4d28e5a80aa51c2f580291bfb911658f284739eb83da8309ce55690ba6a74c4c4ea4952d666fbfcc9b7923eef43845ba454e5b694b0d554382966390e7a750356e673eed9aa24b29e2175d4ad15161ac46dce91125d22d3d5a2caf0abbb4b28593e64cedca63987665bae4449bda555b9eefcc2ae596d5630f57854c7cb818ce86cce721522e4b237dd51faa4367c48cadaf44eb43a45bda95abac7fe8abd5c2788237620fa73ea90af728e5ad33f1e1b27b316eb5dcc7b1ca86889072e3f35574438ffec665e39b1b94dff8d7aee95edbc5c36f7a3aec7183dba3b5aff1e5c329137d8de20583719c164d106cdc3a8d228518215d3ddcdea7c14fe3b9f5342ee56b933a374ee33d422ac71bb709a50c72e33c70fec6c71c7fe33ec21b9f375e79784fd784b9c66d3ab8c9717f2b5c347dcaa723fd747f2f853df0c0030fb40e3a700f7272341b158f9b9b9b0a4a20e537a20ae6f310f374140a36a2cf4d49bb7ab8f52da85f3edc7a0ebf6112431b1f3a4e3d5af7a083b0653d5aabaa2f8cd548e5411ed51bde7ae6a949de7adea9b2ea4295d9d854994d95d9800df0cfa9396e437bf4f09bb0f67873e3de2e94bf4c0ab13c44d9e34dcd0af351ae82fad35748fd1443cf76268114cab1f7340e1eeeba0963459b7036e58433ccf679bb5062b671540a05d187ccefe136620f1f30ed11522ee1e843a7cae84b47b463481debd63e058d637c8868bcb5b319c4da2f2410ebb9ce6edcdef88ddf78ad9fdf501e22a0bc870f38b794de8a28a07cc8fc94a340675ce49267cbf4d6c67b84f34b8f2dcffdd313028f92288a60df461c7bb437552655aa195f396562d950a69b10c7c3704798e7efe021dad90583a7e338e8d9ceec4c0a99cfd42e1cb76ebfb46bea68bd0e97adc3c3ead3e34f8fb6036737331ceb5546c6f429811b9f5e67ede2e1d6ed9265baf169e33761b511738dd784f277e0cf76d6b29e23cc1f8e30b768c2136c3376f6d657b93e0e87cc4f89ad0fc29da2e9166fdb11252236ac890402b4026205c40a08f0225131c0fb616154c1fb71e1c99c96215127ab4475a62b29cd664c734ef1a756a00fe3d4236d9240a8046afbf448eb8f156d52fbfc3cf5f6f96924edd24aa0febed393335650569155fef64859b75de23067d7be1e208032c8f49e93480e4f3590f1a50c26908c01cb334fa339eb0b9be1f384f2791695f179067dfb2c7d3b0b5288fc124cef122aacd798f2edc6df6e5222cc154d987e189a02cbf889001d91899245173048a101d63ced8373bd2ee08f4bf5f9e9d489be6a119d8ec1702d4162d2012d3262e2ad4d7aeb9ef363c7566942c953bf4288af316b048d826982c0998281737dee0b4c0304deba4c679d319d6608ac1283573354f9e92b24d3596470aed3594e38483afdbc768a799bf4c65befadb5d6da1a14f4d36b501502c2b9de24f84a8159ac2db227e21306df0de6134c4cb78d8a8a8a6812464646950a12927dd29ee60409e9b3c9d9ae242561a256911b51ca73a370578a204fe893791d144e4eb4e6eb25616b0496521c296a57fe903c75fa216957fe649fac5d3f7da9ce744b5f1f50cac8ecfab5d634b0f6ab25dc5e5b02696b451662009354a788c7c990038803ceb7d28a59a8478c8151318629aa6882d11b85438273fd9ae9328d34b42082c92735ac28c1a4df1b21d84d14181238df9fac27720a238a90c1e41530a99208f039da98a4a459c2f9cea65f247d5518a5b5d64a29a54568b705e224b556b356b3b6d65ae79c937bdca239f4b8019e64bce504d671394a29edeeee246ea5f47edd73ce595b522a29a5d4c234b7bea359211b387753f31ada9f09d3441a3c9993eb4f8de3446dfe24d1b10f058a85222d94998f23f9b2409dd8f2f1d94291393e5b28df9e568d5a2d8c9b7c4a396adeee69de9b675592bf4d9c735cc36a137d59200bd40464819aa64b70851813cf2d40619ec8420c609df774e6b2ce919f6e8168f763671b51aa5b76d3296bb5b581249e4f449fbfef3aa990d708f03a15abf4e538e7a4c1a97a295bebcbca1bb3a6b52d509dd3ce7e4a87820da097508499c9d94b28c410f114480aa147463aba601fe9ab0be3c6182c32d86c6201ea8a155200c9fa480644b56cb1a28a0c367d0ad1974c92c960b3dda002d442f4d546d35bd63faba41b96644df4d549494e4c4af4d5b3d94b583735fdf449344b405fd4a809842c518e60d34d80491805a23ab307771bfd04b2b3ba85d7d56a74db366c851356b0f8cdbf760de037f76ddb729ae763d974d9f2395e5a2903c68b2d6af082a5d79756ca28b59555162f15c2a4e1cc15b8dd862b2abaf10a0b96e29c1207b3e79cf36bf90a0b6e1a30d27b686fbeac276800fd3b834c6fc06a1279edae9a22d8bf1248871d4e568fb78b3480f5b2f3a4a6fa7aac3d4ad6eaa595325c6a5e5e31b265ce99adb763b93ac299f5b3e71ca2e8a994b329f5588725a05134512d987a2490a01e6513c94ee50024403d4ae93df73ea0656fc5117aa4def3a9211d37c104a6dad434143ace5bd0b9ea56ceb59a2b4ea013e55b401ec1a415f3cecb59c40de1c99c8c7f08f0cd743e2ce3454254557171b83178b0d0a83868a2e039596fe0e91df77db5d4c9f2625bf25afb6a359a1dac1dbc7a0ffc5029568d9c416e1ee73d6c673e2f38df2f0be66a53e71e7c6fcef980f9f3b99fe25525e17c2dfd2a0ee0bd1bbed77d3354d9f7be6f88056da514047b4a8ef58465f79c7d8e7bafc394cb146b48c8757adf9c29b4aad57e93f7bed771120459f05202914efb3a35fecb049eab25b09c4be634d2658a29ed9a330bcad6a673d24dd3a20ccd6673ce2237340d088d8a396717271e8abbd588e84a491b7561f32333298501ac87e69cb8262e4c8f9a8812929eb926291bd5b03b01fae29aeec549568ed6b5b0b926fae2a48e0d5bb2efa66f173b50a79c54d69eb45619a29a369fd994c2d0b40b5aa5a8f61e21e7d4364aefc5b8d5721f4715151bea6bbb346fd6497fa3f3d32c2a1399f3723fb29cd699b3199e79ab3567d669756a9deb282da2f372e0d51a04b5f403ab0029a55c5039c8906eabdc64adb01a7ef806580aa9a4c0d2430b648bda02b56b4e60e92da577426a7832e7b26e91cbcd64913446d2099f172ebb7b319e413ea4ce9fc6a0712fc6dee395e2791d98dae13a6fce3eafe3ae0a0b28b67ae4c468a15293c928d32193c95e90af7bc539c4ff30759835c8e07d29f3edb27d907c3b9814cfce00461d0f75dc3bc5dc377492ce731b79cff37cfacff34403bcf7c3d86bb53a776e9cb29d9da7818267124819dcfda52f4c25139c42ad66bc98138c9be433929b499d31e48bf31727e71839d1b8efac2ef0f4ec1f0a447d1eaae350774369165551b46701503805c61ac7174f4a9ff40524d8b730ad653f3d22a140c3ae68555360e90dabe10936b7b2b6b22aab0f69fdf5964cf62d2d09e15bf7b42927456da29440aaed69abb5324401c9a5af6f9de8d1b757f7b44faeccc0216051f7c49c1870ce413281bec61e81eebcb06c7d314158fabad3b29139638720a753f8c08a9b0df004ab5bb001f3b515c4e0e745157aec09c27c2a73a89db55a73ced1fb521d25b077c3a8b7f03d9dded9c0f2732b962381708167fbe079b26badd652960b98f39c734df08fd61c1adedad3d87b3aa75d2807fdf3ba9477cb06f4d5aaae5cae1cb0f29eee52989589bcca3dfae108592e41957b54ac19413ef506c8a7620d8da3405da3a9751566fc2b6785793ecb694249fb6359d60cf68a7d1566151e7b0f5585b8471952d7422ab2f65887502bfef85ed64e0a9c3ff8f60f1860019e106a0955b49d109eccc9a9a2edfb2e087ea8540ac4df55a9524b292fa9d9a76630b9fc74d91dd10ea9c342bba44ea9e6d85ccd7c33b576429da705b68944602d262e38c1ae68856c10d5e92d2402d360c03945249752433d36ada1d406f58b6b12729222c2f26d87be2d157b6491ac2ba7869c74cd9dd0bb7227d4d177d16f39ce1d33f8e99f74559f73d2f8fca441f2d3a7cf1d403fab745197f549d3c4cfe93b98264bc993261aeee2148ebba55039be1b360dd6e04209b6853661c0bcd677c34e82e20b2798a7e3bbe1006c6043b6e3bbe107537610826da1847935dfc6c20b826057bc54c7e6bbdfe6cdac6ebe2d2402f33cd01281799d1676414bbebd7190633b53d33c2a09e806492022877483a4103b6512c80a8cd5397fe884baa1d669171a0a8104d27289da212d045288fcac0d92610f58a318f3f09854916106dbc29a22604502b832e790946c17c7755ded681fe139403e95d75a2ebc3e5d52df42222f35ea967a0fada96fbe04671694669e11e8eb6b9e49e9ac736c1d4a7dce397b7adce9970a94eb6cb779e75e9889bc576badd5af57bf0e3ae7447e4efc6d5fe7b3732dcc2c7ce73dd4ebba5bab73e2b853ada5b5d6b1e69d70a49556bfde56fb1d5be92d12f4b5485271e707538a652a88596ef9985ec39aa60a7bd6af0fb48e75cd8609c27cad067d1f08410952d0dc02edfadc3a03fa85dd7eaf290ba15ff3873a7d617102511deb188b9585eb1e0d59f09b0f71807cea7d0e8699887b538441d15fcfd451298ec873e110fb29b1da30837c8e0a7327fde7dbe73d14fcc46fcae84b7ab30be77bbd3d6a200db2f933b2bf150609420d9241a6cb1c8cbcecc0c7088a0c4328312981491baadc9084448733bac0f214aa620333828470420664c8797482242f1871831f6a304a029667d2b3f04445941bac74896209589e4a532416926e21ab14e957ba942dc9f3d277e408d23f207dfa4821f5a509781e05674105e926b48b6af1d27ba48fd2b12c92c96435c8304732990c897439c4dbceadb77c87b7ee79fcf1f6839ea4b7d762915b3e60c532c92fee24c45b1cf607090ecf53c914b2f2760ebd2572f2764ab176569958deb6acf5390b43c3db251d9e27d39719a66561ded2d8fe41a264c95b6f21283a3ef7d05bdb4ede4a796bbd8f7278fbb9abe446c2f2b6b7f46c8db728ebb93bccdb76fa725480573909604cc5d4c4af56abd50ac757ab2f527ef5ad8c58d9f86aa55ab95cad7cd5060e9c8882892ec818b372e2c409105a7a38b2058995ab58afb279d58f313e7ff0aa2d5ec5c5abfc9231e5556508bdca8c23af52f98e19595ee5238031158f8d8d8d8d4b262cdec61b00c66c70588f63f3384f3c0e8e18621e670c2f8f234694c77111c0184e0c8cad6ed458fa1b670018bbb938be26cad7a8bca666e535355e903166062e5e5889620b58cd54238991262d638e5ca106155fe32bd6afb2cdafd238fad56a95c618bf5263e857be0030b672954aa552390c8ca91e8cd588f1e23fffbecf737eb4cbc63fdf614387ff707c45dfe7277c37dff77d5f8d7f1f8feffbbe0fc7bfcfbfeffbbeef63238d7777ef01637e71707070705c01600c87071b509e8727008cf1b835353535358e00207f03e66f3c043076a352a9542a954aa5523908604c65c5db8899e16d5c0818b3b9956eb11273e4577e0030b6e201635f18265f2b94afee393fc240f95a8b4c68d7caab9f2097c2207dad2a254bbe7af93a9db86a8d579f58e696af5cbe963942060b143460e9426691bd01c30c435869830a8cc8cf91197c9106132f2015c1b24462e9500485173253a80cc1b23c92559967d157afe1e26b6a6abc55e32f3056e32a6fa954619c7895bbc0986ab55aad56abd56ab5720380b155ce7f3c35ccd07f7ebf303dfce71f80b1af0060accefc77aca1c6ef700280b11d3a74e8d0a1c37774e8f0305ec7949dff7c0060ecbbdb1a447cf51d30563d0818d3c1d3f2e2e85b2e0030d6ea420a8db76be528cff981f2e2c9a35045a0548e427d2814ca3f70c51723311cf142880825d3e842250c12228ec09205e5afcfac2e923ce8828dff1c0818fb5a2a95b7547e555d44f12aff01c654ee44ea87fcc0ca57bf2e7c755ce5abb77cb4d285ec2b9725be7a00c058e5915a0c71431128b844a961cc8a885f9df1c5144bda68120a0307d88a4b12bf720080b195eb8031548e9993cfe12318cb7159de625567b158bec3f2cbdae1595e98f11f01fe731f60ecf3008c55ef008cb1787064f1383c078ce1b8ac9f79f233339e3333e33bfd9a403299ec086ce68717cc989521660c1b60335a8c82a490624411266019ff8ccb277e466af19965f3f5470e41e7b3ac72460d3bce78f2d55b608a7c750793c5571f85f8ea3b6844f1d57b9a72f8ea3dc05845eaccf3dfe7324ba51c4e44d080c549163328f2f39dff5c7edfe7e43f7723ff79cf91ffc2e880c49536c83879c202f69dd1c27f2e82b16f1682b1191a30c8781a773046e3858ba7a9a7433c75cfa1be43bff0798aa2730ca5fe0115a55f90f1748692a12ba7339ee712a22d9e3a792ac4e789d4aeead42716caf23cb73c551a40114dac70f1a4882d5a60b406a52c57e45006cc124ec032965306a4c90a19182423ac48817951450ba1209af890052c4f23306078965796e38031566d226b05a385afb53a0f3056abf8992f72f819bf0163334af8500324dc001a028827b08c7fb56af22b3396c6a8024b0c5ed4e802b6fa82865fb90d185bed789597325ee535604c753f9799f5df57f3df376be33f2f48ffcdbcf8cf7780b10f3f8a7b94d3a3fcd2570086d8e8a2240a192ce1039b61f128d7e12d304667b80cd063cf01c630992a0f8293060f8220ca413007353c38035622d04d00fd0490822ec11c3c3da8022b13f0c2409f41a0cb3c85884c5922090d969c601ae20d18e845063e7431c6491642b08c1f9481794187a5229834c10496e5112acc0f58e040e9c91169802e68f265c90f42489cfc00baa4af00741163f4f4440d2bba007d4706f1b5d61c54f1d571b49af8cceaf2d946878c98a76d1cf114cb53a70163d4bfeffbfc731618fb7a3c8a4c0f8ff21930868a8112e9534e577cca55602c75c1bcb7bd17c47b30cf47cf777864f05e089e9b7002e879ae8295f776f07c02b54b8987f2a6d07b6480926278328214260bcc2b23c30e425c0145861e60191f49a7f7ac38e9f028946330869264ae50a2063146639079025681f8eab77a11868e07c53ce828300612a15286873596ba7029c20936f43b9230b9e1872f6080a10618fda2ccfb37468afffc03631f18f304f09d4b37bef30e8c75177cce5bedaa3f3ce73efed8e19c478d109e83f226d0d78571cf719c5ffae27152c68c2e4a181515c1b8304ed020071e7c68c18a1d6032cba3e7241213614cf1d56bbb7c7c75ee46f19995431d8b79baa4e5a9df18d7f431bba447ea23649edf7c5a31051e29d4e3ac414ae04a8355a5c18a009f57356855835679b55a2dad5647dae75592cf2bd9f47975e4f30a4989234e5a34518394187a955742df2ba20cb45143194a487c710325b0bc3a32620a19d9186060f102cb2b312a312a9dcf2a27954aa5ca2aa6efdbaec2d2afeaa3daf2edaa2b2a1a3eab2693cfaa1e3eab905860d43484041a4b68b102cb2a1e25cad0d045173184aac0b22aab84be55420c2d51250c27b4e882042cab8cbe55475eb0173c7ec64b780987f9764c0596f219cb373e63233e63a46fc749780be682976040465cb81206c992256019bb8ee84209175dc46872029671c642df3863a36f3c450699f2f49dca29a6ef14354aa5b8a496be3d85a55f34a975a6b7a7aef83ea7728a87cfa99c42caa91982948cc40c214142039653a99c324ac1922a8a986a785a4207584ed11d50740754f81945855054084585504edf8ef2829a427c46fd7c4615a150285446fd7ca3866a28f2451644450f38806594d0378a1e01e911d03f831409f805c94fcf60986f0765a00f199fc1221004c10c0a7d8344626410314fdf4ead333d7f5fbebf353e99c6e76ffa7cfe66f0f9fbbe6ff63181c1431351a87439c30d58fe7ebebffc097d7ff933fafe8eb2c820599ebebd248fe9dbeb24cfe3e261e9572fb5b7e5db3dd91b5e93cf1e119fbdd9670f297b41404368a1618d1cc8b2c0b2973da16f2f7b46dfded1946e4ad7fadc1975469d51d719e9baaeebba6e07196487a76f4e8813e284384e06f533e71354c4711c37fb86e1baf1303c7ddfbef75e1aee0d9f2f189f2f129fefbdb37c7fac88210743376021032260f95ea2efa122486ab860c50917b07c8d6270e1a88a29d45842062cdfa3336490339ebeb7a6ad696bda9cbe7dc3619b417cdea4199fb7213e6fdbb6e5ede77b038a011136a8c187256568f981e54de87bcb9b91511833a254b1a288953260793bba42bb42d39434254d49d384e83e6b9aa669daec897d62bdcfb6c816d9226badad5245667d8ef86cad259a30c820b3270c3fbb96993f95e9a7d6caa52e7d7bc5d2afb924d72ddf5e6557be73f5c9354806317cae483e7c11439227597061052cd79fef0ae4c2d3122764084262062860958886369e7491c61145389002ab62cc0c47302591e4852cb05c8f5240c53c65ca02da44653e414594d219159f2913fd020119c40d99e5a91b969b4b63e957087a8b017e82600019647a9e6e7cf3b43717df9fdba7831ae81b06323b160787b58ec10778a0de8b71ab3537279d42f1f8703c0ef86dad83cb6c5a2d0e8d6667d4f1b0a503ab9a0592887c75de89b3c8a99827131e6e48826a7156349b45334f5480a557aa92341204ebf26f18820fcc529860096e688109249d6042f7943042083c3564418ff7a9bf40baa838a4471528a5b7a7ec0eb5d53a13524ed257373271aad3a49b38be7b797674421dac1458dcdd348b43e725eabbbb69b6e29048473c79d474a8ea148b8a7c7c9eb3a2f6168c8e1cfd0c26120edf425988e03d15592081b43b4d0e8ca1beafedfa54429d04c251a6e3faaa9c6c55cb524a29a594524a0effec26a5b4fb5a4d3aa54dabd534b9c94d6e72939bd4a4ada0bd5ce7e9a03eaf4ecca695521a84d5b6dbdee898bad4a9d425a99c9fac4d34afbbdb5f2adb53b5b0c417a75050e9504d9942c9f7561e0acc81a543d45a6b9dad16c6d28ad95a6ba7b5d65a2b42b5a20b6c10563ea1bda54dbde9cfcf3dc6e8a64e29ed6e9fb52705a2a86eb789192443fa631c00b8c2fc9d1d6dbb07005798cf2386bb9d57b534413483b61a5630f65168e1f19a107d794452a7fd5e8c85fe096b1105911015534a5f10e990405a5f4fe95554156da59cf739211222121aea97e784e80bae44d587af8863a25e6eabb572439748d3aaf57343f5524a2bf5aa795aad5ad5aad68303db52d3b730d0aa38e79c738aa1183090568dc26f276f5a6aa3694deb64611ace2eecd7442daca0f64d3a289aa7699a56692a89263f9ad10c5739e5565154b476bb120ace9accfb917948bc1fefe701ac17c25cd26c5aad5f4dd65aeba4b5d65a899e8be2c955cd0438976f8196c1ba9e7914ec76439f223c304edcfc7a0fbda1165ee7c22ef4297261d77f78609c7bb55a8d7eadab8e03356fd3a8773f09e342cd2f0d57687df74d5002dba320b5c2f65444c16e9ab76073bc614df3c231f0744ba4f6d644db6af77dde6dfe795fe77d1cf7b33201feab82fbf77edff711f51537f7baadca64b21ac66d2e0acf6a3389cc9379324fd65346376fab7793636d323bae89d4421e3f659a97b6295995a410b749542d5e6a3fdd5167d421f5d8de4d61445f5d1252bbbad914d2d581318d26e0e339e71d765ee4c2bc8ea99bb5ab73eac090ae2e07e1342275dabb7026910368ef98e86b67caba1c48d77ca1639a4aa60ddf3ee7cf0e7d7937c824d24bf2edf1f0edf5697a3f14b432197d79b2eac9a6ac5d9eacc7fa06fe3ccf6712f9f2bc7d1af13421362a5e4fa1b4f73aeedecfabe2b58ef347c13abd29289eac53d9e36e25c13ac5b24e8b37e7ff4820d4d62a6dad545aafb536a07ac5f1745299e3a9d8493dda2d56a3b5e5e30e0f1228428ea7a26d0a3384b4642ce34249dbd0156a92401a5731444e3610c9d9281c5fa9bd169800511291937ed1212126fcb58ab422e80fa594524a6d108e9f74db28a5500871160d6eb6bafe48211473692df997718727c757d1ce7ab44bad8407c98ee63396f1305f80807a6c2595822434bd4c333704511c3fc1586dd51c0ce8d182be97f48b2aa14060806a8b53d25ebae3588c53f56ef4b66bca91aa9e4c0d65bdd6fa445a51de76d5b1c7990a82e829ed92acd5b4ed72db7dc207cea98aeffad44e5686333da23aa06d01d3a31ebbbe8133e5388e13c24eee6e42d0b661c342b7cb6d5749887b6abaf3a84eb15615be72de2ea58ee65d398ee39a50716ecfddad49d0769308a1c7991c70e6f9a63f3d3ab50b07e7452eacf38c7fb4248d8baa21511de9d2947ad4c6489d762ed49ee400da3524fad26654a7bdb5a61eb9902e6d0dcdd3c6480ac5373d924fc8d7a6a4f32694e914df4a3ee851c94e370b90379fadc8b76fb23640d32ca44e3fc99453537b31fb3e3a22c19d68b77a6b02fc5a137db5d4b156054f9bdad572b4de5da83dc9d718f972aad32d8fb31baa8ab30b7f2e8c13893c7ac40374039e9ee9d15684c54eb7965609d4d05a2b64b5af9328a594523ac35a654229b5d5bd1d3da8604a2b15eb50b5343564c578322753a0efdb6934cf810fa0aef9d5ee262f9571e1b2b4f46576999f62cf34db49edc2612b05aa4f9ea43c74736a3fe871e6b48b32f97cfa8feb93ebbcce1351e8bc76e28f1d2e0fa92f5ba7d63076c4d323ddf95ae6d059ab76ad6356b8827c7a7a7ae3ababa0fe1551d844d0dc829bf843dce971ce1c7ca36c3102223750a20a2565b4604450935a05182ede68034b1958c86861461211464c68204619546c408d2b4b1871449719d090a34b154e599e00c38a922c537650020d32a6f039a30809a6d308b48c4d92d278428d21a236b0b0d0a5880e59b6d4c0098b1108118416306c20f1e4f3c21420a892440e3a1cd1258619a638a306305811050e5dbea8521421838b37c448a282a549137106155a64b0c14b17522061c486293dfc60858731b04461021ab64c41220a1c94b8300489a7309c4872440c512a12ea061db4300289a21d86461084103d249948d0452558a206295e80ca54114412287650220a9331538049625602ca850444dc600a15a02d455edeb8b20366498a2c080a258848b08217605a50624c0f489ca856522d8011c3125d8a08228911c83370808030a3081fac8011a60a91236180d1428c2e46907e20010696a0d480072c42a041a53ea105b14412d2154cf040c50888467000e95204ed840492098aeee1a7cbe9777a0b8b58111ab428356da14110d5898fc00f3f465cc001104f52f440f1e488238a1083861bc0a6b7ee11027c5fe87c49e335128942bc84abb4e074b7b48ec3833a15bb698ca05925f168649d15787a7777b3b2c0d3bbbbbb93c1bb3160e9de6ca51fd228598624e3bdd47e29e6157628c132092cfdca8f931fd22fadff1489bc0549e8cf177839c3506505966e432def75de07a25278d36cadb46713342d60e921a594524aa978a9cea452632981b6a250db03c2af4e1e17b8bebcf25344c918578cb4f1794c4ac3c347be89b16d5bddaa6b229316abcb1eb5276aad95fa7c265464210631e8ee31530ab1ff06e65e5e3182e4a5905bad94519a52c836bdb6cb2b459aa614527b64c1530aa115906b44513a430c19a038f1054c4a1d29a47b64c1530a993db2607ae59d77bbb7ab762d0a9559f8761b78d4a91878beb4c2c5e83f252cdd7b630c972db6262c545e74c1378b2e30288517587aeae595224a7cbe345ce02b45a6f415de3ca2aa5e5e29326446edd9b56bad52a7bd2211ea51e6b4cbfea0b56bcd914d5508d3dd4d810e55e8714a2729354dd33a05d64b294a287ee4509752cacb72ea529453f0dca9c12bb264ce090393ec9e934a4afb004d577cc2b0baa90c180fe8ca0b495760f051d13e82ca2bbd25951288d604cd12f84e6f8595150596be61819bc02c29300b09e7f17fb4e49dde61093d4aff7e79c5a7c9b38e3095914104be57ccc01b15b505fc5d21f3526578a3010bacc2c18c4e63961143917b6f1b583bfae00abe5786c01b1541e0ef8a0e035f2f530ddc91d157b8cca5c9066d03afa4341bf85ea14b2dc4f7f2ca8f0d57bcdeaad5e8f6f2ca8f0f9f731a8c9784bb5a8de27b6fbb81b5ffa0057cafacf144059d017f2fad3c5d417180ef4b2b4f5edec056603a03bcd1409bc02a1ca8c0f7cab401e63e633138f5940cb6699de9b37b46c02dbf62048dcf6352118f3b8f1fdd16cf45fafab3bf7e5b4d34a2d9a884acf7a44d0d11d98c040002001315002028140c080443c150308b5355ec3d140010789248665e1d89a45914c428658c31c6184200000090191920198940007f6f21895ecbcc46d3cf5b489557de69cbc3c44ad1b182b1cc51440e66d7a51017bb7d4008e6f5bd9baae9779099aa35f75dec4e4e1f88f1129378c1d661e18794e0aa3716674eeb7ce1fbf420c255e390ebec55538dc8c55b5d42c3138767dd2e1b65898525a84df9d8e71f095cc6c92e12396c2179d3172c142c0e2b5a4c952527f29231308e9bcc08cbcd3acee9261487cfd2da56434859f0b7084b43ed82b067bb92e50e80ba1fc25a08db83fa34a443bcdbf84081cdc07908ff89902a9dabdf0f912c11ac1eb4ead06193d2bb9943078056852f230925aaea927d05c1e5cd49abe74fc4bb7661926a188b0fe3a2b2a85d0c66ad9e5f9bc8d235b936a0e0ee4f316afdf9c2c25374393a83220c5e81670b8a852759729dc765b675b31034e26d190bce86944cd438fe9f9df823f536bba2957096cbed4358955bfa494eb666d8260bc8f0003490b9f2e796ced3e71e22cc535b9b6144d67fe1b40cea680e05b6fc4a30df386b02001d86cc1de11d1a9fa33315fe5ffbd0f2c2bf445c75ecedac5aa9d3ff374b1ac1f2a1ebfe156002f2965ee02a0c8be3e6ed68d5af4c169714ba2bacdf9c0e325ae86c14c3168c1f6206f32c9db222a6488a870cb99fceb5938d9cf23875427521f45d171e5ad23e80ea31f928050c7e690736ae7089dbe3ee93f6322e77f63c5a20220308c830542fb46d6e441da7515208935e7b50ef28105505a07d3cd9ccc287aa676070c7055c8f9b2fc2879101872a6e690fd703cd00017ae63cc096c2a7f26fe00d9932296795db71e38efac4cbad590f81d2dfed8a51ec4c4e1d5b2193baaf2601113303fb196c139517c2ca32709b152f447b43235691b2e38110ee432a4771d4061842d81f4339e78c760dcc45b71ede2e632730441abbf0b50fd55ae757ce9c23b29e40d461846b498de0b46acf6ba5a8f27afab7a27a3a34d86c2d5d3b4621fc080474d9c54f3c4df308cf50283ba00f8f4cf585dad841e44ba429ba871d09f906fc5c4fbc79c5647c6981627ca96bbbc7ec250b22adfa6df9ecfcaf09c3dc11984da08c7b8b2ddf4627fd17b4d4d6185f03c93aac012a7feb8ef002bc1673b16183a0e79652df7531c9cb9901ae3d7653a988928d3eb4c28a51a011255803b382132041337be2bab5c52f06b5a63877985715284a76c0001d3e79fcee073f40f3ad01b71ce584ef08271e71b8414951f41db5418c067fc2e16fda60c917963ea8a4e3c065660b556f9bb32b08b25e187271ffef16bdd7befd0e04fb217978097553a77004b0043fcc044652e0debdbfc4f00ff10f8bb8c821b628c0a74a0e094da19e4a5cccb51bede4ae92ace4bc4c5ec793b71f53e5129afded961aa7528fd593d81182b5ee66db7331d14bf3b1a58c94f53728325419020e94c90049671b05c8ab379f88acc53840ce62fa2822a4048a6357d6d81913b072349e876f12589b11645f50f2b324697409ed474229a779bec5550490e3a30fabef7cfedf8642ecbfa46b4c87c5e8afd80204d9c4a974af1aac43f0f73a28e821dbd187a82e34b23f29467030dccf8ae60d7dc1f9a39b1b4927934955886366cbbe19d547c2b3765795ce27be71fd59b4e04a16fce5a1fdebd10fcf40dae713eaaa1205832b2a06134153fa9c26d6e64a37b3d5d4bcd460794cac36430bc80a51a85ac087655e798ec58fcec801ded5efb37fd91a608e969bad9facff42ebc12066c65d362008e54800a0d970f6d8291c1102650b25fbf454fc610e7b4760dde2d0ac4306a626cc8fb2b998b82040f88824488a2f7211f07938c6e20f860bc9d50aa0b9753587ba30975d3770150a7c97ac74bb52a75125244d4795632cc03e4950b59623518e99c1580fc44908e60bcbbb4e54dcf513a4690758295400c0c3fc5f0be2612cbf054e713efe3092167fa5f8ab65e07dc861426359d18d1723a88b447c7849f35483814d0b992ecb78b29d6407d049dbe96bcb1a4551170895cc2888148da33dc313081e084132bb03fb0155ec472a94ba3c52740aff10eab23c14cfcef3843614ec532b3acb79e57a328c41aa1c395b400657492ae3d28a929e90cb589b25ba09dc8bf0ca9e9236117c5f01a71e245fc22c2b704004368c01deaf1eb2f97d2345c3fd2e4d61418d0cf4fe577cc12d43e921cda1f1a3abc5fc6fcf89a40ab11a308a6d53d993a330c0a19c5311d6a845c151deca1690200a6d2c82a833ffd8fc86243f7a7d3f54209a905328c8a42a2ee9f147fd15fc7be772907526c06961e38a360fc2dcfd62c1850c5487db3a56890e5b835452d75bbc717e673700a4a03ec801fc063478ceb014fdd4c9c3cb6c3c42e9ad5b4a5ced7cfb46bf0441ea437ea456dc03021589bebdbc9942f62674fdc842ed4a0071bc2a5da5bc457af6632ad3c6a000a6a7cd38419c855aa6d8d97d5d155a434b5e83bca7ad5055a0bbfdcfa5b3e51c4c22480285caf9bd82881a4c625d78b4a1c3ee03c9fee7d3011552cc25844585ff9065421430804361b2e4bbcff5bad04e1a85a0053bf0a9f226ae26f6222451e744df2d117b960fa517d3a861d7fdf44db5ea4b7e02f27acaeb3a4a97f12103859a0160e1698454e4556aeb6ec102246c36d394ba5e1e1f9d83416bdb1eda6ef0105de8512d71e34b2fc303a162e9bfb0f960bca02707db3ca4d0477af8ddaeded5d3d6dd451736a3fa097ab5c7047f126fdbec93e216968558f222f5e2742d91b3567e71e54e8cb51ef450ec9beb74f40e2e9b7c100795c862c1ce65fb39ac481262ce35f1b074e64510c78f998e05f4277c8ad1e817c03a5d4a04d45071f076b8559eb9a928e68d55ec24cccf3501691b3760d1d779e1830ef3ebd1483195b9e95847621b0d78f6132db6a23c21201e4a664b47c64ef420eaf545cf23fab388a015ab3c0c4383034ff00891cf6431310d2383351241304404b33a357069125338109ceebb1afa9255ba992c8dcaf7ece03e61ea810f181768f9962c17d5bd94dfc832566efbc263b87dc421a029a919fb39feb3e10241a2c6cd427d85a9bc293fd1dbfd5ac62bfe283f333ebdc0eada35c257a779676d0da8e09435e7920b811054d8db02a02dc8d86cc9aabc9f107b48bbb4ba82eb1d68b0dd36a663878e4c1e59a5b261892035b71bbf7247061441f19b622b1b67cf5191e2670b8e25b5a391ac4f709e2a21285dbde21598243bb5dca17fb75925f41b61e935790ad946776e2719aa1f6f4aacd5f233181ffefb4b7501572e03e3c37579a93d18ff35b623dca8e263455c42196473c77415ada3dfa44e67a7ede8bdd028049b443549c8d69213b2121f18169c22c6ed4b4b0175d9198d37cc11fa9d616b17a7ac7cf4801d5b3beafd69a526007622b3dd7f0de3a81f1e19e4e5a6691fa6351ba8ec7eb8a6ae5c45f03761e8eebf440339ff498d502dbbdfa3913a8fa1f371275825a0761d40a9cf47820cdab853bae80e17213172f00181ff7daf8400dada4c18ce001c4c4025f8ba1ddc07257198053214164a12e4498233cd81cbef34503e6d06073d7a3ea6a75a0e806e9f89d17960a5e09654013927d7f88da7517d4bbb1076d1f99935c37c74023cedc73c9cc0460cfdcb7d34cbc3a2407ac46bc1f486238dce96623660e72ba53d5a898fd105bbe40f73cabf52777321adf6a151524a26b1f1926a03ef67404880f8873de5a1925b9b804057f730328d21cac8227d721271e9250da423463ccba439a05ed7fa1439091c67bb745b400d87e374979f2219379dd82a8dbb57fcb864d0d7d5bf15b192e936c781373a2ff33d4dff54c8abf574424be4f4439fc3d621bc94536f4351a1e11c5541b323f50fde39cc2a288dabbb5f4f73a36c017a13fa7e37656f8e0aa56f15d2d51f7d693c3c640aa74267ce33e8cb3c8b64cb2fc6381b46c3241598b98f4f20253502c871a848a878815e6a1f5aaa5408e1b45c47989ae0ccd35e5b4235a9fa2ada449d8597f89d65b65bbe9740b3550ea4980a7b83cd5b7c914e7fb11978b9d81dbed3eadd3764262b0ad76dbca36a0763a36eed0b76bf5dcc8a32f421c78b4a00665bd870a014a87c17c7d07a4ed61b3763389f8f10b59db15e5df5c17106fa97190d3ae6e6e48073b54bf548f642c2cf869764bdce8e40469c71910043b0ff044b5300905f7492736ba46835b8cab4da3a67f50b49dba639c96e740542481246110f96314532b1e1ba7a6d2d3024609d74015c708d73d846c3c49a9514937316a9c9bac6b8101534f7f9f6b6259420dd63f3ea70d7d0949c2ce0d66cf0006c074ee0807c29fe9135ee895fdcaa12006081d9c273e03d5229fabf4ac82558944ba4184ae626f8f8aefa51fba2b44a4538477656de43562e2f0341bb2dbf689ca37af9549eb5c0894a3c107e0cf6f753b882bb21ff3caa33d8d0921610d454ba2620e2bacd28d73f4e59197f29b123990f199565b3c7f43ec8f64ef863dfc59e04987e2b7da70459af68a15cbc20f041475d40586fd771198831651b2c3b2e8dd245f866a7a4477b22f0a1bc87cfe0c3c7c7284e97e54fc1896e146f613a7e405be0b4d39b3e01e50536c3a92b41cfdca5b3609be2ee195c15aefc3b958b479f77c91656e2aea29f09368df17c03dbf50e52cd4a91b1265cb8fa1de934f453f360b8d348a8c5eabd8316d2b39f81b841fc4b49ce1b56a70dad6093ef38b321635e7ff751f0445329441bd3aedfaf8f32c0f1712b6870551ccf02d0b82a70c88ffa399bba5405d5e9029bed2262df63c8d20ca2b4b07b480d3a6ba3ea93953c0bf6207ee49a71de0ea72072f66ffb3725374db68c22ecc80e635e4f20b3b264244b5d9dc55f8104bfcdc3729cb84ef675f3613448b32757b2a9d632459fc9e111ce49c8dc248a9f5f2d39626a848ddff7147508ac7f29a49428891659e325b868cbe8a66f2324e056c75e8677b7fd28b0e79e8298927249fc198f2e695ddf5ced483ef36acbb99209b04553ff71204a4e045d0a7ba2ea972b772bee42f8605ac528c43df51a493f68763f0fc0b4a0204b81413635b3a6c2059cfd3dc06f66c7cbcadc9f433f894559613bfa876cb32f64bc695a6344905b3f1fedf35a8b4d1cf103ab334c44dd91d4ccb477466f06ef7f30f3b8dd64574cbd4818db16f679b83842ed5dc84485cc1e9e75a40abdac24c6df3706643c537dd421d1114db7f4232c725bb908688e1c00d5401892596fd05eeb63890e4b1e2189774b896ca897f4edc2c664fa88a13269c8396957b9260d6759967196cec7563e1b22d84c73bee6df1484d357bad043c86c488291aba6ac76732cc99bb023fcfcff0f78ece8080ab027af79aebe87c6ac3783c0aaee4744876e3f4be1c303f91ccc4eb89678cb24f7d3827cd9b05ca74a9e9a50e047f822fa25c6eee65df609bad0fa0bfa9ae32a46711bec3231fdb74334f443a18097ddad8960fc678a59e43aaf5f52054cbc76ec2b42cdfe48b1581ef6369f72f880ccb76a477bd23d64317a67a612b4dab6dab7575cb77fc46d1652a3bd470265f8fa2407b4576698870ba20382bcb001ce4ec4b4c43710fdb6cece7926ccc0c366366954f091f496cb615556d5a1a137a15cc1a18c937bf0ff23271b64bfc874ec321904e43f961a63bde615e89762c9fea3a4c29ea0f318e59a334cc21b29ccb1a23560c1e19167d0948dbf07c2ef35e4acd5ce1dc0d3a11e5c4fbe195efc1bd895f8bf9128b04c735036d2d544c64a5333551ce4e6681d33f177e9b25da472a00977b1de5e42fb156c31f91ccc5e7f5b1c520555d12055a3efe4e810937bad9ca1263222a8577142a5c7f93ae29aa4841e290991cc7fe3dc0ef4ef8beb4cd03dd6b12118bdcea83d305f3f9ebd3b832549c025d61b649127153f486e7e8b154bb1becd613d4e8860c87a9bf37512da6cd1390afb6dbecebff0938a3040dde53c59b5e1b4f6e9542461c99b5bd8da91108d42940d2a6b5051d59bed6d127b677f4b9973d99f26af34127f2b2116979bb9c5c9e025baa1089eb1188465881925be695ece3ed9658b714076c2295f723bed3a3bfaa71a6e7dd15af68f0644217c705161bfd6780d413dfcdf3dfa92c73e7bcbbacbe08c6772c6de136e2f83513950e63384d837f215277d05847d7263388c5515c253524ece7ee2b6a008347c357d0895a3c73284aa032a33527dd5ac2ab1c1c25a445a83dcf5c19fac120aab02e0e995ac3c25b2653184bff1858a5241692610e81ed6961d1a74dbaed82d25775c9e6b78ddd7b84e6e347ac7b0aac6417276d0fb8cdcd2316ebda64c743bfc234d2e67320ece132b857e0e446b860ce7c4768800e22f49f2ba2ab08f3dd5f4269d91de9c177142819b3b972cf87d1a46f11d4115a13e5c8340724c87bb8b2eb45f3f3f5f352fd4bcfc39790fac86bfe030ed642224d03a10f44f514ddf3f9a137635844e1d75c131e7b6190aacaf3f4ea5e0aaf79445bff2afd9f3e71191ef290dea290b6d1655c7564cbfa572f8d8b4715ebe650818250a2eddcf30b506d93b99fdf6c168b3b6e629498cd28abcc6b6574d4fa5dabf172ae21fd61439d8a58d9447ddd3561fe8ba88a2d163a0e22e2175bc263361de152709aa72c76a01b7f2f5b74814cb47008a72d07b16ce6f68698301328217426c16e629461bbf60472d443ffd6935457b4c8b18af9a0679effc3d3ed87cb391b34201cf052fdea5bba42dc9a2a5044b5032a09d32b6a5038262d9f5a75b98f02e936bc5ec6c2c190bfb2558ccaddff6b56ee62cee54818181430980582123e31dbe975202360acc713ee0dbfe8b08b816c2201a735827c329cc302a3817fbcec0e4505ea8bcbfa8c9e79fbb388c2b9b7e91d3c512a97d8e482ac2dc2bccc3d74a96b220da9449ecd4f796873258e7bde67aa019fe0c104405ab9691aeb12a4c7e6fe257391a6263b5139183f14b190a19bdf4cdceb3c13924d706f35eb1ccd3c12018bc99b1793381629bdd6e5c0287aa77ff077f8df351301b6ae2e2f21e73a81d750fd5b71ecc0aea9ed8a5a46af9825958a534a259cd3fb3e486b5f799eacd25907345aceb21059022b6a840953edf4224b454b7f8fbd6313daa46051ec74ed58300cc0e2201f5fcca2cb9565712859e198059d38ac6af79910cac4b54a2a5b1155ee4e437793a29d65d566d3b9f999da86a5c315f916c175d95c4ae14cf97fbe7fd397abe149fc315d14b8bcf7d68895e5a9bc3bfee4b0b4ff2eab5fbd2d160bdfc3a2d18592a726406d36d401de45a6c5499387300a30214aa19ecac49815e9ebd7a63fa801cdad6d0fb80748a58aa73165e78b774d548766b5d572fa8f549d40aef6fa73752b4f907d7729e3e6bbaae24b92cac5257df5591189568f2b2f6a1e7c1b99bbc309fe1a72e9883d318ab92576b4f8842a02c6397bb278d0d0b596317b67f30f3574259023213628a5718326ee5209b3520c20779145ca955fc2e7857044c521afc2fd5464dda97e6eb41084cb08dcf73347eb6484d4fd05419b845265d1b27a44bad904c86fffdda81114901ef43cf2f6bfab47c2780809f89d4b21aacf4bc4b16a39e34478273a735d8f01f3b71728b6e1b73fc38a3edb951115acf7ea07cb92304b3270c99d7610301f40dbf61a73ed45ff6920b5167ed459d7b0532c04ebd184a01d9293b341c8a96cc1c801f8c1fead721144b541db2020ce9eb3bc5b5401b9947fe36740a282bf29f1c8f8297a83429ac12502707b74358afaa3bc487fb020e533d12c436af04a736559a720b7e1db587ac427bc853d802ceefabbaa302b411e03716d6ed8946daa55605f62ccbb9d7538fc7ead977adb1253f0f797cb9c4d22be7585f028ddb67c653a5d3c858b72756e75dac69aebdaff8eeaeb7b396cf6db14836f094cd1a58f0332ab342bf81af752afb2649eeba049dd486157247d262a4b8b3f0cd0e31ac945fb841ff5acc529c8e2ca5e5a52e463caec935b57c71c55c54e2015c1a4d29b76dee955c10b1378a1742a5d345284d3c125fc7fbb4269de2c0b807f31050819406985408acc28ba99a4efd79082da9b407a400b5e5dd289aaa40c6071b0360077239b62291f645934d674ad12f11ce4209bdd0a03f8c57d6d336aa033276c0130cd22ccff5ed29890101272f08bf41b022f374ecda94d92981242cdc47479a73136c922e652e38a81b96ba6b6d078ae868f4626ed442d61fd8522444959e7019f4a71dadfaed8bfb4e80e1cead015d4ab11ddf82e60a9050e8336717f5087aec804401f531ff105dde440169a6812d43e3236bd3ae34c1c4bff0a5c8f07685729fb2ab97ef0a888e04073233d953dd6d55095bcbfb345c5478381372b2e2a9fb77ad36471af6967f6f7cf3cecefe0b33f70e67081ca6c548056d9a20e9b3daaf91b3c7d21fe3654f6426b12114b173ccd3c2365a2fe09effc4519ea6c51ccc8f711ea6324154b460ae244844770297b5cec22dc177e7dedbf9c338fb3ea936afd74cc5ed3faaf5f5538235a6368494ae03d11e385d81375d2711cb6ae0e6cb268751d115aec2ae8195ada2718f4188adf61291ca99bd31dbed7562af75c24370a32a9f2a2daa2af19a99cae499fb49da2771a8631fbd0651ebdce79cbe0c4e212c50d1f2a9f1f65b6cad7a6181465a3f182a525a36c950f4dbae788be5671b946fd77ae9777101a04f5420960b54097b6222fa58e8c32b14636485943c9bb0a40a417b4a0a95635ec7b5053b7b2a1ecd1c51fe1bb60e9e150582f906f01f578360c7bd406f347a15f59f087903e044ce36d9da2a42679369c1da23fac27f0f5e14a95ca0f004de28d7e519dcf23c9d79bcfda71711601b98f200987c9d9ee6e9654c3b871e5eedd2d50c07a7daaec41abb6d849e067fe1f5cfcb96e52ce813a802d2050f806dfa59d5b7159110211c49b099a07a036c0f3bc2d7d3be823bd1c3fd4bcc16c89d94ab3d00412ffe7583d0baf4766c74f079d74c0d694a4988a45f2f0f4e45ed1c03fd669b802b89b0a1362011932d86174e428bdda65fcf975cc4ff6cd5b04e10a30dcbbbde028d257399be8a5946226c0d75c16437aa3bd6911c63bfd765d9d4b1c3f63e766dd89b3266811d241cc971f420cd02c110f683ca11b010386beee552f814fe8999269f0e1e4bf7be844c687f7dd4860ce6848fd12c7f973ae198b87d60a273e4cb2c436f8cc2e00d0161b4f94e3031388e23b60e1d30ee9ddc3a0cc33dd74fd2d71c075e33ba1f548cddc5be4d48dfb14cc75bf9ab02cad62347b4add62687c182052742486afa0ded65c1ac639f4b63cdd531ced98f53cd06943a7710d170b9f18e7e775f3a8ea5825bb2aa74c2bd26a41ce23a48f6fbee660a1c66080d90bbc948bc820cf6d815a6cc5801214ac09561062f09a78e12f87850323e442dd2cc482f82be061e0f6431575a9a2c9006d45cb0cd017e61f0b769c567af73f1ca35b77512f5afc773a16ef67c52c65e71e03e6e363ee205a2cefeadae257a00ab4c6757aa8c159eb1bc094dc6b43da2698b2a50b43ed745ef97808d71046ac50d5239ff2bf3c47ce02d0d9ee12a237334c04b3bb30cbd2d4865d3ff104baf0f324a66ef59e6ce9702da6b5a6e90befe756940f0c05473e80611577b9ed73050223c2008bc0d4e5f0102662010539fdc66bd6480404b0f3da220dd98a8a4218ca4e31fdf870bc56d71ec32f7b76c4aa5bb1ee7580944115afb0c7aba4379b11b220d6cf3f741bc5ab075accbd2b85d688d5baa8bdac1078fd9b2d06f12a7db36b565891b91497e497446af4607b53a6f4870e34e40a2e241374d73ff1b274d088a45fe88c3ffa9344367b705a8a49d8613b4ea882f07775bb886235a0c186bf8766675a7c0086ca04fd420c6362d4a1b0b52eed9258d14245dd24eb7af4994fb6a9d63d79b26c31169582e89d007a9ca10363d6c9b60016096cd687a7ec51dcc2814ac148a7fa5d3236281bc3e73205f59ea4d83c595bb8df7559671a389cd340e85d26520b869f4dc6aca0f651c97ade62fad446073d86039713072a341287c8c1bd82025038d50fd3a1661cc980c0b0aa1a18c90764762f12b76b26850b331ca98726507c0bddf1d11b7e912c21340a71cca6e1254c815726efce08f9e10d65284f0d69f68534acc9090d27f196db48ba6c3570df76e922e15f44b07a938c5e8b078028ce98b761d3830866bd806e2eb5e7e046fbbb0065310756670889019cb2719d33bc9f4c5f45ab11a94523c958a677f12f426eb99f2c169c6a38c2b545049109adf24c57c7b459be4f4a03d56f863f1bff6a7a2a8cea57c45c38c1b37f84c5b3010025d45cc70b7705bdf865bace14f001e1c8e1af41b0d8bf2ab40d972c125253f93a10ec253442e5d93ac13b4bb15cac6517d028cba72da37e5c51a77e8784539fc0b84c02122536be71745300e6ef98b2b180784000dacec7daef61cda04c26615bac9c1058b70e90a80080143b2449838701e090a34fd80d6ff5f686580a85df1898f24f0f3845d007044be389ce5901f2ef991f93ff95ba38022622544c2630a14f09f5ca903686dde391de793631d0d53cbe6580386a8736240e0e198ef8799d8d98337b2ad15e5c88f3996f1a331cd3c665486f96292abd51ffa86071cc7013fcf6e4e8d77f6395ac7ee1934d21d4bb79ff63e30902a3a57f0ac6b5cf5647acb935ef92faae056787103727b8e806b2c8f6710746c2ae2dfc8da01e67c644174cdb818009d3343340b0416192aed994218ad07369d3c91ea60a3fce0a74f3a0d4c05a101add9f7523dec3758d77acf8ea556a7af695144195f74ebc50abf0da780de94dbeebbf583b8d56b785791d270502bb0b22f7fd6b98f114a76f5ad5aba532bdc7054352439b4ae96377d4798f142b5ef5d87f9fe12d625686dadad628518266a5ddc958f63e0a4a90b71d93a633b45fe9ecadf47114816a94829a9a7181f0a92fc0d1247b2a3d92a7bbacbdeca5d5488f1b3f3bc714388aff9df5f540c104e6b195e990c6434c7e020c16a560b69220fa514a617d38771408fce1e14b0858ed8d1bad49239ec2d0e27b1e1354ca7353dbd1a88decdf74caee281e9c932711a8f050b8320b831a9c2c56d8660f5c2fd9fa471d76410a566cbf23e3f8b0e75c99d68399a3adbaec70968a566dcfff4b0151cb51b928e743058f96024159bebf55b4de6e616818acf817184bd446aa0e5d22ade5909811f175828780e204e39538266c8a3b7b2801ca3210e1e3cfe66ba8bd69acc8eea1d1ba9a1d2534d77d76368bc0b78472f7e3ede57ff288b3f5eb0491fba77638a4683a88dfbfe0c36b3cf27d89409e0188459a24036859fce5a222574aa8f0dfd115b88ca629024517817cf42fe2fb385521910124c06a55c87d78024c913d1ff3dc55415a53dabfaf876239c841674ab961a939b1505b384ce947a1f15088904c717e69d464daf81690126f5ee1bec93cc9eb95806cbaafa0d3f57a4491bd53a44b349ef08d58a534939349beae768f85ba92771c3d9a5ca6b0a765e77709ec848f199056e4bead241428d0dcf6368d85a4ad54662ab48225430f9c4b08a210460343f703c0674258b5dc70b500ddb173c897ef3335b0b905ffae9abf13be4ccaa37d4603ef3352c4d9402af68c982be3ab50e96883a51fadfd03f040704f1ba50a878607f2b371390ee731602682484f64f6011987334cca158344d3680621c30abfd3a88c6a54846edee5cc693309086e02074d7a75714632f5bbceda5dcbbfc0fc3d25395e6af480516a10abc4ce7771eaffb321a71ebc9821a878206af62877a3151b1479796965403f42b539ddaeb264aa7603b256557240cc540792b9c3ff790d6091c8e9061528a7006260011650f7d7f1632177d48d490fc5211f0592dac820f792115377ffea8528e9bc14a11c830a095b36a430f8985363c1a68e317d32f07a92c267d8e956388fc264e4f9a15971ff54fad3791cecb46879431029ef8890cea582458c40273e76618ff0a5e0958077ab474712be83a7b8222d38104a099429a70294a55e462bda0b29440c0a784d76a40912057a7099417ee661ee8c691580085e086d39d7dfb77ab1755df15c01fe5837ba1bde981a13c171330eb5f737959a05d2bed48f86cae68502a75c3032a2949994470f703022a8d97042e4c4151de600a3a15787c1a35cbf3bdba237d2e728f9602bf56637cceab181bb92cd4688bd9d0e951c1de305239e5aa0292517397490d61adb8cab2ebdc29422b61a623db3fc13bbb99f9ca040961709b9b04d04364f62bacc36e9269097f38a049e45b5e3ae2e8f8fee2ef391121204362f102ef0eb429495de6117f6ae1391eac944e9540a6e07afba3890caeca9ddacab74cb85f499bed564936db3f9f451bbd275fe30ae6f75e8d1fcc967c52751cdd6fc9cae806c60b81a0ef6f15a88f45fc662c0040b2aeaea46b05d6c4d67be14ba87bec083b727184fdcd73f7352e52c5aae6f1934ba422a362040b0d921abe65bf55dffb1e2a063e04aa8612ecfcf60d85199cf1d02b55776ad78ee6d3112fe4d3d14a85f771bad7f94674aff37d7a0ca3271c9788d589f262dbc2df89b0762764343fdd44630d32392cdf11878c7424b57340039ae741b253c278fc9f36af6cbb8f525016487622e0f166c69fdf8f3115e67144d2637cd568bf53b2ddf50aba805626c5039cac6e9240185ca31def69d7dba42c8330ed8094754f647fd4d3917c5860a75157602f5f173dd5d76936480cfcc54713f1040fce62dbd27b6cb3c32950f5fa4274c29c88a62e7f0fe0a062d4969448d657b4513698a28d8ec054c0334cd8488cc14f62bb1368fcabceb5c362c12e2e01c827f0ed1258fa3abfa4a80471989c18aaa8dbcdce2d8db5ab33f68514ce67a68376bbc9413b2ff54c8bf7e41ef0b227f76c84157b8e2006121b4e4fe227341e253bccea8d284fbf4b39bc627164b22893101e663e2e2c72017d22f757b4c24e835b0909dc8d20994b910e42bf77ac3fbfb7f0edf985bfbd52f9faed032aa0c2d331a32c68333bb8ea81191842807b99d3135a818230b1484c1fc410544d765576a70b99f1c040438fa064884baa081f26a6fdf151a918fb5e91625570a01530d07ba1aa7d1fbefa6eecb7d729cbd09ff72e49ae73b62b59118c5c4f94f108a17b0399241dba28511d993fb66d9cd0c06894a1b6bd3938eb6bb6dec32a479d0f2b827c39556226d97c547f8f4a774ee034cc7f113102d8bdfa4b980bc136c0a97b7bc8e3e18e300a3472845144eac256acb223a0873a6dfa97d1f8c6d3ce9f73338133dca9da3b069b9c1bbfd3d07b43938cefeb23083d9ad854e53a56a5c2f5c9c602d7ff0b48507406dc1ec3bd38e4c9de04f59a5d0e448094857bdeed766b48a27348ddee614930e1a615e5e902a11a227158453978fda90ea9ea8478cf80bb479f21c0ab1479ed038daaa95d392b62ad1a3f2e30c0253a0f739272631590954580afb727efc59c54990b39322fa6ca9ef3b61a63db1eeccefd97080f726f97540d0f751235d206d13971558af6caa8aff30cf486292e70515463916c61903806206636fa7a9fbccf6c04a046d8c65a2799906d48dcc17539bdf2c6a4626acc9b570218a5b7bdf0abb0be75f4a7d83852b2ea21eb1804c521f7954b25eae932fb3cf7ee12c871db10e23575fae2097865e1c4d7b5fa281ce0e9c92d4cf64bb6e312351c0c11d57181a42d704c6d93f13dc7025f4460b51b0014828061c1863f8fddbc5de684c876a7dccace1122bb39e2984a1374ef2f63ee920f74af2a42b4d6dc7e7f7d833a58de62cf8298efb25331ffa5cd10aaa143735e041e6558f141ee8571206048971f1b66d4856df0da7aba716ca17062f5b8bc4f430213de2ee0b1b664c75f10d29b2a8d0d411d2e499012ef595a1bc78ae36d21f22fd49d1ace9abf9c5de7c79d3d7bc5f79570d7a5bb3c39b25f3cda372b3ada3741eb5a8552fdb38f55116bf7b5424e6506973b710c6e0f55ff3a1df97c2822f760463b074ead5570b064c3614be14d1c54646691183d6e7fa128224c0468353666c5c2fdf78a6054d601a20b0920fd79b8d045e6f24e982d0a11455c682bb7bbacc4397787ee54cc93f0895aa5a12c73f5da07146b262678a5ae19b842b9771407611794af6da30786d169752a208c93eb009d92198edec0059b2aab2fc275f40c0c070d0af74512ca07d5a12bf5a18a5061261cf1e9f25a78f77f440c945eaeb411a1698e6d833671217fc2eca6d8da04043b6dfa45bc7c4985072ff93f257019e7f7390cd7f7bf36304543660a56ba23a46d4bb5b9bc68f4af5581d7444a7252b8b8862ca7cbe8b23d15e5f05aee5142d15da231712aac6806c04b73240fdc605b812e6d344956e4fd37f8cc90785bdc94d07888854c7123a096763ce99e5fddd047eb7099e441c8b0d8c71de9386e1e6f6c32971fbe2a1923d06c01abfc8afd49f94688e69123fa6f828c369fbc4f7a4f962ad1c07d894785f976a6342fa22e3e90ff58ab0f0870350883ed5acd826453ae971e6b0d73d6fee37917698a6f85b6b4174a10583900dafd9511ca06c764b6240d4e07ef01ac0184b3811fede2a6b531f95a4830be05c311d2e468cea3593b8e76381082808f702b0d6818a850bc5f9b8440f262a3ee5fb1cc2a087984642f3a70c805e254996a2e64891e0a58b26913baa5b50f468aa9be48f47bc5a73845f2f5707893de5f7e3c5fbc51ac5bd3ca6f35989e8aaa4c017d60c0bb3cf8742ab1b1e202b81c98bd55a78973a04c35a210a1bb33da312329393b314e4f25c5a83596cb68df090a752a278f977f2a4c330517a7f6414e3cb5c23d8aa87525e023f3903be88c77fd3b2f893c2179e21ccc05d9372190de5656cd1386ab88983cc09e02c0be68521ea1cf2f57b1c0433615df2e40209e024aff287939279faa29488d29ea2e59ae74ceb3fefa65af282027f8e9a0f0227b15573a4281dc07eeda0069271008574d11b21add39eee1ebe78937fa0fff89dc3d72530a84001be39e0814427ffadde7e1c44f5758eec2c91af5a00df720c6246f486a0dd9f8e5be9b80b5df89ae8e27aa407e85200ed8d614ca32be91633309e08e992514cd98b6d1c46ba9ab9603458b3d1d717a7979bf01f1d109eb8eabb5869b232c44974a08060eed5ab7e44d1dbbfa6cbd88089effe1a7b54e968d09c7a62c81305cefbd45822f6e513ccf85435dd208d62382bddb009d21023f3a1880f2013b02c88b8694fdb32ab957faac4989196abd3a735e466296808e1ea066479bf4efc48b32b21b3104b58f6707fcd93bd45252a476ade7e512175d38cf91b06328d9185952789019439e2e7d36af845abb131206f6011119d7ab60e868a930d14bfea041e83eae11a1c2b92de349b55631528e81889f07f9e5ee08b0e1fd22396945410a4eec7644ff76060187bbe6b3fba8db83a1dfe7dabda9a534b2ba266a7a8991bace93d12fea8e02b9e43921161006cad50b532b4212b3be6e8c5c5c9ac46684adb136bf1a04b3e3882ed30b7c90be40ab55dd43bae0f7f1930c299a1167b860a4c6fbe0caf495195b92d22e180a6c01b5188c0da77bc7798cf2d7d41405bef5c1d6a6b19535d8d4cfc26b8e4c57e14ecfbb07a2fd3315384f6d0301099dd54963ef8b9a5d0a015b4e67aa666b6653f19cdf58343949b1e6c6c832e4904c1831d2a0e34c429d88effa68dde2d4142f0abb83693393b1d4ac4954164f94a48156d80f4c7c3d0c1810edfdf9c95d95799cf76b14c629faefc5b9330a6b655b77bd6c58ab335edf2a287a414fd511a5116ca513a9c92636a77ed8576aa647653c6c9286d831c8db2f8f34dd73f961eb32c6b7d40587dc31358dd8e41127eb0d642a86882af769a99d5946cc563b0227af8aba18046df1b06ab4d321679c8e75dc58e029873a39de62b240e4b74694235eaf73b09fc87884f24598ac981980c0cfe4348c788b5a1fffc0e3e7e7631a4e34c8c22a8cd86a120dc88597064d5941ad6ecde5e71612167027c062a0e9d712e74717070f408d7a56b3a0a8c97c6bdf246568183aeb044056e9fa16d338e372aaecfcb8b0de7e0e11f238e0b1c6aa5c743ac6b309a9bcc3813770821722dcf51dee457235156d7bf3d3c0cd32324f07564db64dcb662c1ac46719dad281e235872d1b5a198e2c798c8b3c39d04455822d44ef70bd60635997e7b2252bb36507642b02e6bd9e3be4482aa8d302980a0f83f5a01afc3cdeac08b7069e8737025810647e31dbadd8c05ba5eefc1b2b15044f59dd5a80721570633831229d2129fd97d6deadd44e3be2bea44e44b2b0127a9bfdb9d16da0a051385f1900a36085be1d4397c4107923a9b843538899eaf5d7ccfdb5e444fcfd12e4126d570fadaf65cd4e9f3811725c10da0104b1bbdf2a9f0f49d4980732d30991cb4564d7787e9dd94148d14fd9a84af8a9e1f2aab6dec8f6b18ba8c31f91bfe162643887e549012e5694a428095a6f6f5899e3bb7b9c2b8eeb25e82aeccbd7b697103a9355341ffaaee34b2f017bcd2e052da6a8fa3e1f9f134d6577eb55f201a5701bfd2ffd1d3ac34f35b2c7cc28c2f279fdf26c1d838bb135f7a97c51c521a58a76af85a0499c4f38261387a2c9e3cc254d5d6dd8a45777bbbadaf2cf30fb3765d27afb05e73847777081eb6aa1afb55293c5a05b3efaecd779e98948c843c82eeef074d74d514a1a4c669d679bbe5b0466a5a0e13f25a16c0550f8c50efcc82a77ddd584a98717099f5e2102580a6edcbd12a8d23443932b33aae8a0013a90984fbcde63a1654ddf48d89838e72fa991e840a2c643d1805eb84060dbd0c4265313d22e4c3c82a51d2102dbffc21f5697486aadc63b550816bcc151945e04e66fd7d31d897419c8ad6e70d0f5edab0b671ca999e47f851c251a1e15d2c35d2d460172a7c2422cea981dc84206d6feac7d049a29e74b3384338acc5dc150bb50cd9debcc920769612823513f552691c088a6d0bf24b69581b4fa826f2a00d1c61c85541580861adf7824b2dbeedd0290d711e6a921babe288f8069c1331282fdfa9f7c6dd0f5f0b0159253135420323a23c47c3fbd6945648d83fb1a5ec87914a8134c2cad430e0419df8703018a61602430abb5b0fa73d842cee0e1acc78c26f46a96b3f98a3e7db3531554500a7f6e465feba40912f917f5c5bef59806febc4c767742ccb0d3303e8e54d29ff1681b436561a861c6d00518a89ee18b7a99d7b79964f5da023d7c8422806740e47e508ac0fdc8e6f5ae2ab4c66303be0217c81b2aebbcf80c00d6d6a38e4f0875e7dda471e89ce5e03344f0b3fc4053006bd105804e375cf518d0c389382c79dc7ce133db6b4f98434356a4608c1d82f52c859a50edb18044ca3301e50b2664a061692c22c465b4ec0026c28a2fcb95e8351190569f74a203e654066fa22b8c98edc6c7fbe04084fa9a9054c04ed7e2c2dc8545134b058c959cacd288f016f22309c21a68eeaa1bbac71540462038b51d0f14e903f5c6e6234a6112dc4ce30815763074990d67d74d779eb2366fd9f80e93785ae7cc06a6be763ffdebaa0f1ec6b6b84492b11ca5e2a474330f025b717db4ef18bcb2896c78c8f8a9e661f5b0986ccb2572f3668223ef2fdf4bb79d3a9394d21f6cf118ad5f84a06ddc1f9f9fcd95173969415bb47fcf39cd8db086d05b097f270054d38e501bb3d8dff727ce38cd3a21e9a4eadec211dd984b14bab3c9b8ba43ead392feb5c6c7fcb590583d6dc6ce89398a4ce9809498b7f08d08953fa76d1dd529cbacd382e3412e4bc79e3045d51bd304e92cd425953ab04efcf896c8aa80e92a2dd4f4ccce91037eb2ef7d70eb3f431092afee17cad74e28cc099bfcd80db4b30f01421ee27758cd5a9c872937a78b7ec3c6c84dfcaa81adab8fd550c65f68fcd49f4d012dee8f5a2f0bc3daba3c7f8a0d87a9d97af99e9ac94214d280ee913cd1b6b7f7ce498fd6afe3ace43d7292eacb5d8b5233593e3f451c25b5ea057cd0491681f335356d5891dddc0efb7a4a5efd25062d5502acfcce4a9a8dad83a04b759e6aec266fc266ad3c3a8b2eddacfd9c79a3ca37ea57a1db4ad3885fc511fcea2aa7bbec1ef2c32ce997acf4e249005c14e8ff54a424275f0993cbcccd03ee955a611eead8e0d0fb1211b3acc23b95f4ec92d198e0ddc65c632a3d9f1dabc1b8272d9e9980bae1de6b8fc5f70d1ebb9b7b5f7dbdd0951ce8fdcfe066879de8343d8bc08efc29df08f7450064f8448a5d2c3ca94af02347407443b88325ae908e097db3460db17c9c74f4f836f57a12a391da6be1186d30e743f6c6b35702999a591386081c8c2abc52d914de36bda2608c9f2efb0fe22657af991746e5cf621f5184fdfd329a096962443224bc016fa5e2beb6998298ee62015a6cd45341fc3e744fdf7a728cb1d81ac13e289e9d30109df1e637308a5c0b007d00a7f8b3f8749a25b5aed799181d521b37b637dda4de9bac446a4f13413139ba9d2e745caf12f4683323b092135fe5c9035121b75315c77d38d330717701e0d3141b2f40b313dd4d9c08310ad4b882f8428d29728cd7779b0d5091d964708b85a405dc0da1d3e7c269b12c8cf03b2c0864dac83bc44c15f50321a57d6d10714d481e4c16520e19c60884accbe8449c5ffae5871ba1c73aa525d8d7988e88b64f85bccc63f4bc9c621bb9c47310ab5fb6f65d7821797540aeb52b4cca0dcdfd96c1617f04605439eebb067ebee7284df7065486b62f0d9378edbb5d6941f7e4d67b74a43dc3af52d15ca72f8eb91b84eacafd9377684f67e0a86bbf6f3cbd83e137a8318541319aac81107880cf670e42a7a9620b7c1b1cdf9dac1550b0d18659276618ef73281200fe1f8223329e921ce0cc40287137f9173bee1b5a427288c81c5240a76d3c667fb12960680b15a60852dea0eedacd7093e5c4557102b242bb825ba5148f3a89fa513056de64259dc399f406c3796e1407f0f65de8b99dcadcf9e53d94929e02dddd6f5091ea21d05cb628490c8a067a81fdc91e6ef60bb92cfdc11a9440ee241d8c3237eaa7e20f6519fea7e432231d20112d0ef83f4c6e233cf57305f4866b1facd484826f08cd5035a2daee373f312e1054d40c0a75fa63ef042323d02b26ce4ca70e7afe79980084d99062619495f92d69bd05678376b8592399e5e525b95c46f2d2b14891ed612ea71de94f536f565e713c3198a1580b136ac5bc7f7b90a415c7a2be91ecd5669510ffbac7da309c6ee91470c34c1417cb584ae889e56dbc50e01501492a5d15b607a7a370aef044e698bec0af649ee04b7cec81344a8d9a7f87cdc5b5af7d7aa64ad45cabf92bcec09f5b7ac9f3f0d5d8f3d7956c629115510add214eeb2a39180f4a33d8f01ce270b383fde354161284438507148403e15b75a799e77d21522c7169835e13aed63fdb1538fe16dc069c2d7e886dee8428621b432e1381c1aee36cb6a70a4d81485a0336cd513b811c7c611195f7a20e499c8b6dce3e5599dffd1dbe3d02d475132209dfd83ee90e0dce6f25fab18328091f6681ebaa5df904356ad89e3ac880187fedd0a3cb547911ff7a3b631ee2c69e45033115740dac55d3a5abcd7fdfc3075c3b29eebe705f7fd22d276a8124fae2d8e0f6d55e8f70dc08a122a64cd29a89fbebbe499ae5a6b1548d5d6604265d2db426ad62223997f8f0386c7add7de7ecdc4d44222a15f2a7c8903dc4986fc1c03d084312028cd6f3a69a07a65ce8b2a577026a12b9bbb43e0c264f0a171ccdb2778c84400ce2b660dfb6db5136f8f3864dcb55183a981213cd6fb3aecd70cbe269d8155dca66e6993311257eb4515c6b2680e18cb02fc671239ce75fedc91cc75d42f01ff923320ab004d76d91bf6cab198bc50bcaf72a323e3848b7cd6bb984561f12fe8d42253de7d3b636d5f9814fbc724c650621a2b576ce5d6c186f2d8a875f970f1a1f0ffaf4badf0f70f1531047e4bd17e0638359960c1d7ddae812962b1ecb840447c2bb9d91ff85e37c9e7a160514ed55ea8638ecf90a95c07d9968a03453488ffc3097c09044ba1cb20716c8a5b10834f0cc3e264eaab23109a82554e19ba07b04608882140a96282b1a30b9587ead9dff8b5a393da68e73ac88c666a8d807ab5c93761de8acf8cd13903bab6d3dee5712339a583ba7c1dc7fc454f15bb92a6239416f980e04a0c32f9002b89bb91f7c3f3deecb0b12b959ba828b446111d846c85d53548b1b8a1164c47da7610adb8de75f74054879315cef0a12d477b468f1c95b7664da1e9c771f63d471746b344cba52cbf18e917f9d38c0e1f49215d39e94fafc9654c9ebe4e8db8806898852ab6a561acd28a907b10320e246fe4c7efabce8ccade6b8068c27cd1c1b479a55792d75eb3373920e3d41e24acadadbdbb675b3c2c70d32ab9784dc0a2fb4506ff9944b8520f8f4fd94f268d2fc468e840f24cf83b0189507a500a02113109456016f950f7e5c7ba7db0862fe53e8ddc41265078a1f83b0bb06f308dd86d9b427392d2f378d149a231a72dad3dbfc04df690f0e01b390a354675c9353653a674a1d03197e97553ff3867d8ee45bddaba03ef0188b2b12b1ea0bfe28359a4479ffa7ea51e273208ed1fc995208b847250589d7aa5ff9e8217096ee9fe6487fd014dafcc9316867748ca8c16855af9a947e77dfb1fdc28fea2161767bdb40270837a75ef129e1b8e091f60818633f5783e6eb4307e49cf8efb152eb98b63889feacba0b15768f1a1d03b1e7092db46d6ce84f3f65f77eaedd2e4dfa84d14e43460e02a7263971087b086883cfb23ae2747af3cb2bdc33c6b807190e8c32cf10fdb9a3bf7b9adefa108fdb09d8b35ede8e939fc9f6275292873bb34f8bdcf7e36f0f33b322c9cab9809255bab01589a277ccdc037bea8dae76ac730308b5cd313245af4cd5238cf303351c1361a7933bb412d13bba105a66f2756bdee8b4b0b7d9484d75f2720908a94d72b7cb8ffb8462805a8916cb3edcf595830b8bdc6c1d299db7d34753e1890b2fd6233ed42692eb5a1c9ede7c599441ac2815ad0b9cab3cf98f4811331099391b874c8b381e2b20051b7d400da701ade95fee340a1059bb8c434131b06e0d249fb0878092a296c05177b48f2b997a865331441a6cde7e1213834400e32120aa926b609a8a7ac4bec91edf4a4d5c07549c4b9c6aa7b8aa918ab66d0c3692836b3cd27e382f7a041b3be3cca203aabaf4d933554a82d824bd0e4238afa625cc0e2634884af1f0b919fe904c9859a714fb5b38c31807c496f0e8306509e45f48db0766d0e5c1c6c819d988539484d80947435f515c7355fa646643db2f7d0f96a3c70f731d6efd5a624af083a2f7d509c4d3d618a27442360765498c76a30c099f87ec7d2120fa4c109f76ba67f662d5fcefa5bdec83798b450d7bf6c823ed9535dc3450749c3ac83caa4e6cf83b99ce0ac3ef09657a83d293e204bf8c2f50593d6ea6641ade6a43ead6ed5113b5cfe7513928dc24c35e28dafa177b9db5736d6769d3acec12a23d080c48110009328fc09fca054c3a2646552926bd5000ab9edbc1f8d104c69d85a0cd83c47b19e6286fb0f390ae74544396fb52e0853ac00a31bff11383d0bc619c648a7bf3b230fd2e19bba4c4f0522c47640a9abf54994fa2bc2daa627d127ea332ef8b0ff9dea288f2407e4c81a0256cd871d55f410829ab8615f99802eb605302444d6f41049be8de2ec066dadaa3600b68370387c58cfb8ddc3b0fd6c0625f71f45e024f000898afa01cd72c50de3ed9f4a43483174de84742b319536cef386129b86dbc74858c6405837e0d41097d290e2bcd5a6c076102bf28857f5dd361123219ffa8c38f7d157fe21f3cbd2159c3a54dc35803bbe23c13e3e9ecac09f845097e6c5dcf923daacf00918504ae6efd4a6e7259881e7fa90d8ad8cc169144a4ecfbe4c25f11ae2b7c7a69a28d535d7d01a8940e4e988f91aceb3a880f7ef9ab58a62a00fcea6002dd43f0f6c33408cd9123649fccc2669151d4e2f983547625ad795b18b406b53fb48b172880d4e6525c586e064a73460de5c516694cfbb8fecb5f24a5ac009a07613572cfd59782fc691a078a05ee17b88cb4f8a12d016279ba8e94df0b6a6f7bc5b03e62d3af0251776922bdd6f05e8664a188ce548725002b85aba6ecccbeecd1b8738607ac0c4cba717011d8c4c4124231cffb7c5000444efed47ea09b74e54affbf9a356e18161cc84971462824e59e3a7b077b637541497ca970377f0a2b5c60e3a1cf1b224619a5163707cf190b508bd4b8ca1306d5e1cf38164974c361207bb10b7f8c21590e876fa74a45a015003e77dc14dff53e3e7add51b88b8e248f6d139d5ccc6255b98feacdf3309588a6805f28e208ea513df6623db4f95b43ffe3b3fafce19a98634c9130c246cbb54f6f710c52252356b1018cf0906ec67c84c606d7452ac9ff94406218019d450257e8d0f8db0a67ded8df7aad957e27630453cc51c8f4a9222642bdf704b3b30704a590032a615b95e202538db3435d670df5f99f167b4dac1c763111192151fd4cef948254a5d8027efbc9fb2dbd846aafdb009bf2de9aee5b19e4300dd331d70912313dc2f91aab8b6360ef708b84c86d88220daa21a4a36e319d22bd0ab22a7b7bf2d527f56d8ab423ade49c0a34df061cc6dbf025ad7e3f340df58b159c6a790b2c1f10a827dedd9d6ec3bc818d33121133d5eec9210328a9eadba0e9f661313522990f6e33228183dcd97c8d2a74e2e9bd48075fa58b173314f7c65ae5ce7c67db60463902acce76c84da3a52d217f5d96b392da931919a6be53821c10e4dd047685b68303ac03c2d82b87c0703d8cdb4e2b76e7878b458e44fe6ae1827e8bb440ca6ee0dd0c9952c5382f1f0618427efe3c968a9c6abf06a141106ec884d447108060ba34033d003b0b25714aadf1099288842db2f522809169f360442016c46c24a5418c51b504454ddac904851627c313fbc575b7f68f3027ab680e5340e6516620890eb8f22df46694dfa603bf299a450e1808824a496bc5f606630f0a012ccfee7497923398ee5f1091b8c0631b041f395d1b9bdbc2c42d7e3aad7898695f52b019d97871cb292f5e86282b7ed2c16d4aef3706570ad400a5b8fac88d30e4498344f3547a84c1a4e204bd5de172d245b8e70eebd86b06be32534a22b04b12c7503920f306f2f560624106ed8bc444544234b05906cc00e22a68188a9bfb9ed515db4d232a6c9556deae4c0414e3ce2d1e8d3936d30dfb53b9ea4e69a7c83130c57e667a57062477f24134f5c35e969073f452beac9fe2ccb98b720a937971741b11af950b0d7d92f9f832f6859d9dcc26d94190d89254cadb3eee1c8bf395582be08fbcd0bb40fe435009dce25022cca8de347b7dc0df316927c344132c783a334ecf8e90dabb1dffa6f5717f3c2a9a1a507c88277ff54feed9ca8a6dfa0f60aa6b4ef963fe21ef5653bc58c628a6307e3b79585674192cd3339315ee297f0416141316bd48b60f993760c53f5a05f45bb002854703d9bbb629888c824e9963e1430f392a15776695ce1ea0e8f3d6d12697936255738ec23fa0c28d60b9d98c3b0ffed06f04d4310c570713a6ea1322e3300011e885e950969a616b6f591275a18e96e4bbeccff4ee4840036d61dd88b11e006443394267d3b16a3f4238cba631b143d2a6493166defb1e9a19dc47daa472443b987a2ab0a832c9b44d106d320db701bc04bdd9ab909e3976af38e9902c89fddba45b4a6de5b335e82152bde2c15aa68f48b6018ac2f0ebfe7c37331db31803012ca9e09bb583365159c3c7deccb39c56615171d9b859e61f642caaee9882a439d1f3864388b14735f563c6e78ecdbd615271aa58773a7cfa74d9d5646988708b609d644866a504d17d89c868231fe920670c78507a4ed26db3ed33bd66510a6d61d17b7e99bdf07a8aa2aa13f72c161374b24c151e356f99aca67e58d1d59675e4193a475e40c27296b4164fa3148e701a5bebe3fdf1f6e881ae68f5a0a7d20417ba92646c36e85db329a19b5c09c2ba46de3ab3dd3edd46b43656e77b93a29bc663f3644396b710e616f2627361ff89e25641e173c87ce510dbcfaa6e7015279f659801fa8c1ca2d42b36154ae48d1c05554871b02c31f1556cea0949d99d30b9d3ed14bced86e0f6013366e80db18d404027e3635d486f89f6f420131cf326d162b8f80759862d8524bef4984039351522227f988ba88b571fd23ff528fa12c44d59cabe2c1e6792a55ad21e766744777ab3fef5277023bd6afa55987c3c548213b7a1923200f11d696e24f93379aeb5191c527f5080e1952ba6efc2a019ab17e4721debbdcff1c9a403d192eff709e070b21661d8200380bc07228496103361c8d26034145344170cf4106b889d7037e8d37c0b1d6c5a1234c8216b9368b157df3bd1ed5b496637ac35e5bae43940bde655699868060871e986a26eaf634fbb74580bc76379072e72c2f6955e8ce7ed6eb8d29c95ee6940d0dac83af01e928c5015c2c5156cd2d8f20b6be4f5ddde1f325b639bdaa451a66017742d04fe3a772df5d7acb764c7745d2ce9c923b69d1790ecf2311a391c2013fedccb7b13b87f7fb49a48903050459702ab0010a83a8307f0fa5d8998c82ef0553498b1445e825097165eeba997a3f259304861d27b39f52f40245ff0f3846c48e1d7f9e58bf2089c76769fff71b6610bf1189486bc19621796777a65e7e931016bd2386aeb7aa3d90dcd1ee9e44efac272ad5c9804ed0d3334da4beb1eceecb0a4a567f4bb9008fd92ded3ac8bc685fd839a84162d35693e0bcb48858951a2d714741d3e109d6e39a02a26966769b6a5105c69e4026c93c9c15ff78630b0d03d06776c04771ef50e31afd6501cf2877c5224e16db49baceb844c462cf81764142adc6fba3060895132942f2b32225314d5eade927bc611575e30c8c93dbe4b87be3a55705dbae61159371c6b3f2d6f0dd352a62e3ee7279d42b63864f67ddf7afac3b1a6dc5874ba893bfa4b5042195578ab1665835943aac7419af336b8207f39e8b3b0b5358da2879c9cb6157ac1fb2baeca87f4df2ebd6c8ef1cfaa4a863eaddb1087947509e69ae04cee052087b4ca1b1ec2188339b22390e73e5e508c08bd3f51a477417c45938350b5c2a6e81ad3e286aa006e6a14885b2586915c28015b32a70b9efd42c28ccb4aa71272ed952b70d52586f3e51b5009d7da572604582820cded25405bd063e78d3983b9b75f4744d8569364c94126aa69938faf469a04a0b13283a3bf8409262e85960755ec65411b1e262cd1da5c0fdde02467016235dbb95ae73c8acbea5bce763eb302c660e922e89d2d6b6a068d42b8a1e01ff15f93cf600f3aa350c6412a916906ebe28aea34b474569b0e5bedf9b95539a82824546191f460f4bd552a6c898a9ba989bc29dcd2bccc8645e754cdef6db2b8a598133a5ef2dcbb2074ba053c36d90717dc1bf5305169caa81acd0b0a251465c2be78fd1fc3067f03d5341fea152a6d4a365657dac8eaa1acd0839d22f2da3554fe4278351db692fce3cdecb3d3b90e4b8f17e81298a945c395b6f42a456bd7198f5209d00b9147b216912606d685c91508e80080572b843b3e90ffd51247b1acb4f381ba1ddf43248f900cea7b60f09271a14ebadaa533f5577c9e01870111e128e4ba0e5c05dff37e5dadf8bfda129392c85f80504e3a6a17c7a7e2f8d4ebe5598aa4f84ee3f5bfc5bab6bdedcda5673f886bfd518156a4963ef8dd0fcfcce8e8256d59220a1a75a2deabb6de905a1519381236c85a72b192d772e6363d6d09c7b38c5d23f1622bb989326d3811ab9860dab22fd2bfa460141e3a436ab4ab2e638191dc4de409d696b329cf9bcd83cc2df55a9bf36c5bafcfed4154cd89ed5aef4046ce1f7e2e1307d94111d9af9116a7fbd274b44fd2d46e2d80e4361341dc26e55cef893c1b275e92f72c31d30620b48b8645fab2c12e9653fafdab5871d89e04337aab6908153cf8ae0fda99d333e633ff3b782a125f5775d98fd05222dd2431091f44faafd2830f2cad1e8a1b73f947384506da1f1cdca5ad61a5f30a4a052605bbcead06814e26756b143d1274f7bd050b1c4b017c3819e21f86b047f8f599fc66e16a763aa8484de8e1ec20013f41b8bea204db769104715a8339c44b739f424b67d4bbf7be1ac8ea485aa5a1fe0ad558e66e091d1c20fb5de1a47ae2441a10748260279fee8330d73859f6e1a200c55c4d21c3bca2d2595951bd7105d0289c821f5dc2b1bc7fe82e1e2e3df14e1e9f8fa2ac3cd178fcbfa4ddc5cea18b1e63cb42adef0e968b28c1747ff9efc985732bb4b0b9e57ce4fae2b5a5603e0b4cc5520b8b18d2f331fd387c2c8c9a79ed07cfc95710fa67fba3f5e9663e80390fb0a3bdefb4f76d741fb1fbab86ffbe8a4ff7df7e845b69e40c74099c18a62280d61fef30fe204fbab260da4aad13e714b6e973297c06fd30d94a2d3030a156b5614b83abc40f62ae727fad165ff140a3d51cd0378711ebe2227c6c2cc04f00ac4b575f5b3576d5e68e696c00370562a5b27c59194804f6b3f1b36edb26b0ac273f436bda18b8b14ea0ead880c219283d88af684de335a94d2d3fe51be5a9510850b128bb72604213b2c09f50d247c52a6a0268e739b123e81896909786576cace5852c24fe3c95dd84c27a443a5597952eeb0c33700ec5c300a0896fe2502aba217df125160a8f6bd5dd7183f985cafb77fca0c93b78485c18c75aada5796f3fa44bb5d46f115ee44be2361ba17184d97cc867393c366a17918ac274471271f86d902d4131d463dce356bb20b16f6cb8b75981271c5190125318e1a9d97be20f6f050714d03286954f686622d90f912fea4634392fe7327f59776b02c8cfe4bc1573a56a88e5f26a9fa959005d6439b5e277ddbec33b6d4fb4d22c07e3fa193117dd336802579fb61b599a8f8ffeb483abf6959653959918da72442a6b4771179280399f1c242e173af16a94cd073ed007302bfd59e655f97a0316cf6090d8686ea608d93e4b3fa417a1002cd624040e42b96904b5a303f1e77f6e6b565a3a1eea45b4c6431f60ea2172e2b4ef714834507732c74d05cc5186bd8ac3323f8d2b83734670f4d65ba3020dcfc8d2484e394f7be92279325a707cb910375f90671e377ac5138870eff386046d0d16560ca3c679d511ad792dc26b4d0664c0fa0c736ead7c8f7c50703545dd3074adf326d78ed420b4ee3e8443db3efad0919c2139a746d7524f17355ac75fbce01422b1314b989866656f90209cd47a0a70c49c5e4c1138cc82c8af962aca4a806d1fea5b50aeff1ea7097e81aa0340ab23b91ac4209057df47efb11436956e65c23e970141e948754ce7a170c606c24cbdbc9f790822933d67c8a08ec193683294648a6da37ce7e5522dde96d0f2ab0a4fd28bbc6ade5e5ccca9fa9ff67c4cce67b14577751a181014dbd91daa932e6eef48329a58beb70d606124d23cf490b1ef92f8215e4929db67750648fac893c87ee204460a008a9b92b2ceb6bc46e18b7ed20fefe67b47c514563d310834a3807e80b19ada9c29f90b10bd101ca88ab92b036b6a9f47826df90dfd0a60f2d326a7faaf1906dc2002ad48a6a111a8d18747438d1cca726a4dcb363a02d3d3210b9531c61653e30a00d5ff93221a634fe1aa4a14f10475a96c6fb7891a9bdb4cf665889a36e0c2630b510604178000514d2cec6f980d0558fc618046be1b13b05d51b043ceb03ba09c934cf0451a44ed5399c4678105ad6671ac28c343e08a98d6da6971dee07bc6955fba8776e565a918ef5dbe19be6193f585fd9e3fa5b9d2115a5fddfbf897ee95a16211a12941010fc079c824bae1d7b1bb9b8acd374391d9dfb6e2eb4e58f5fa34ea95ba1090cfa8bf8a3d2ad4a5a71707cfc1cd6a8de11654d60b37c53674f64d8739adf7ae10c9dd532b7145c1f6bc81a7b2345ff5596382a64ef560e9523f94b3a1b15299271231a9530f56d425e718a975f57cff528c9c6bb3a86dd48ea4609efb376be40612f934d46b126ee7cbad6c119845a5d8f0bd02a2e4e483fd1d92401f277e2c468606186bfa1558da9a4967db9d694fce8bf4ae3fc0174f0c19d05c24090acc0f49900866eca90b05b9c436cf889570b5d33d23e67f43ed359229acd7cce4be3b02323771d4054089aa27fd303e41e06a603253de9d1a6136e02eb4d81116e4a7923a9463dc809f51bd8678fb8404ae5a3438001bee957e39a99879745e16cf55e19816b79c38bdd46519b04f8bf4c6ec985fc15fbed50cc40724d247628d7effb75de73c664a62060c1e2cad62bea4741e1f37c92410fb27bfe3dc66f1040f9dac3bf2d875f5714d49cf949151996c85e03b04856e0346d3c1cd9a50c983125b0a51f8cf4225861f3d50da5b8f0a819d4fbb8c8742ab5c4361882925de488c91c2c8f46c773b9c63e79d36488c72e4279c5f7bc95967e29015b2b426c36cd06140c430ce842918c7685293137ad9311a8d441ee9ea7f79e30582e62792daa00bb0c102309164e0921ff9f49028e7a64d176f845db73610e545720f4399ec992f6dfef3c49f9e5f9f339fdc0ac9cde5bb20661c68121900bb2a8d0838e9563a6d004677422890a62fb0550a2e1e7c318758c3a4c4074b22f7e8ac093f3f5903c9d25be949faa5b34fa0bbaf49a903edd515ee9dabec67f3f11838c913f7c1c1300b04323fa1680b869a54ac3a854bcc2572313a25b9482999d5d25bf2f52149ad0a254505da9d88ed1b402dffdf84aea755b2ec24f7da64aa1a2413b8cab7ae23c018545cd4eca3396517bc79423227307921e8623cebf2e3b5f83c3c34206b2674de67447fc1331bd9949a3167ca559b8b2a4118843affb19a1ed07937de861db72a57cb7282b4ee3b5ce66ac961c4d117a9f4ed9258fea59ceccadc29108016a45272ba3cb76982a3c3166bbb26d6b4f3a9362efa04f6d9f2f05fe72357752e680496e9da97ec50172bc271252b86dead575a24d8480672ea62b2e813dd31bac658b8df6de2f79bbed94876486b8ebc0051f213c15214372d61dade93b0738f15a502856419b202fcbd51af4d52642e6973a1cb061c03c83ba4dbd080187e05ceae4f238589a5cfadb6ead6ce239c90d9947810b8151469c247c213fb1929fc31ae0b5a445e7492f47fb5e4649a4ec2a8e5f62c54aa9560c29cc835aa5807ccbd6296ca2fd25a1ccdeb94f3394ff2751820a8426c256bba1d1dac35f55647d4793df239f6835856d6b4dc2e01fdd3bb051c7504ce8c476f4666e369cd84bdd10e08657fb105f32e0f44f3dd6f5004b1ccfe41b4948163c659cebc8511320212ca49db072a475d8d98e9b3f3c0076b4704d9d10de6f1944aad40b8a255a0e10209a76964a03e6882bfd71d9d07fc4feea328982ec9c1591dbbb5c8c70e81f20b364288b56d62fe0e1ea0ba5f29ea54f02c7814b0a5b6a940de2630aa2f90bef860b59f8f5e14f94297a5abf3d19572b1263ee87bb39f6a41bb406c950ef57af18aeaaeb231cbfc8db086c90f6bb698d1b9e042724c7290b40202ce1964d402548491b40e05ba1449532cdf6cfe901d131edd6eb1b9e4eb826763f86428b7e26dfd06ce9d6e65a87d5e283acc7392f0b8eb24140b32510c0cf58d970be241ae19536e9de301231a7e72e2e3e08cd17ff07e4cd85c1e6ff0ce83e6e7fb98cce96cc06dd34c63a2043a282420afcf0418875067a245fd75acc30402727e9dc2fa40f1f4a850a4915ef11e0f844dca7a3679cb2706ad06348c27b0dcaa286d5d70503ed9a2ea8f987448de531d8fba3fd5ea7b8aafde4b5995835b7b421fa96cdd70dfb778f9f60c72a4ed5a085a6945845f7f7cb3f464b6cafd86979c79178f0a1f5447a1a58463ae096e06a1406622600b86a1517099b2871d0294f4ba396a5328ed14c7a3a289d72d85e9609936840771fb98b2f132ee81885de24ed3c27972a936965065fe1f7fb740dbc61b27c6add1b928dbf668463dcb32a45ffce6bb3545a8226c0b78da2bde476e79352d9ad38a0c57ebaaae6dbfcd445f02814f29d272e44b0ce3f98c6d28e55178b9f1b8f273a0a2c75278dde43077c87ea1ccae27b55ec4d765b7c227bf5cc720cd0693c4d2eb9a4d7b881df25102f6b281f09c19c231b07497bcbc3d90f0ee943a97a254ed6ec06dc7c5526ac740e2344b07b5af154bfc19952ea6b5813e8433f37a0bfb2a78c2cbc9b97e0e21a07ff254f09a0a145109e89f2aa15e6ce318ebc7cb861d6ccdff559712d33f73cbba06a6eee298d963e5d3c7ee88401df7afe80e2ee04a39432ab042fd3938c53d9cd1b06c112257771e04380857185d98a8a0f28b2fd04af26cca1939141ed7eca06673153884ba907f627f7c3bdfded82ab07de9b616410717874b8a234aabf53db2d0f7c7a55d6d9538a89b20c52e6c55d37a3701b098327e1c4e16950f51784d28fc01a5c32a7ff177221e0cdf4e7fbaec8ebf869a7a4df52fc295675e5c742db30aba8d37b0bfa3253cbc8ee7c730a11d2f7f4cc1fc85e2ed81173f52ef0eb11e30185479eba66e94bd8e0a9c348a0946c82ceb31d18dbf1910c90048ebe5ee793e849c1ae620cafd80c4482fd5034f038320389b93b87b27e3a89b26b7dbc39d884f2c7b4d90d0693c9d7a2fd68330d5d05cbd2c6e203589da7344c80c24d4009b4e0d538ecb15288f25ad9bbf533f418e479c598a06e3c57cf43ba6b3da8e28a0ee434692f167b91078220364ce46fb91a297812499d0f1b7d6069596dbe74ca10e9afb28883ff86f3c68045a3d2fa35930d2aa4a072a7a5c4566b9ece8b80e53c0974c69fbc188273d35a781af4fa31b067cd55f00ad9a8d3d18268e7ea130f70841300006a8de636ef18aa115563b3bc960854888dc3e7440360e8d404d12b2f7de5bca2da54c29059509e109850a73ce6f4ea7d8743aafcba3f7b55f92b368ccddddcd41acb8bb9f4018a31cd20142c2133541b7ba2d5dbd94524ad95d5787ba3a6ddb789359f66245625ad434d81a6b3b5a5e7a9f05e8d2ac94527a9f908fded1afa7fdc0dccde26ed720076485d5c7a5815e2d428ddc26e8b05bb502fd851c8136eec6f6b88629ec849d2c10b6196c6368758ebadd2e54d14eda1199b3a04dc6d21690f97a0eb4d1bc223dc3ceb44812f55ba4214cd7665b3cb1ed1ae8c3a887643e9642f58ab9758dd947d77c3dddf42241a8dfb69e8f5ef10d2bab41fdb69899f2a257945f0e73605ca25e434b7d2cc2dd1d00aaa8cc8c843f3e9c736592c1fc4ba13ed2b845992ea31e3dcb280540155573ea9a96a5daaf23ab6f1879c5a34a1a2ef8de1ac62007d473e6d8b209d0e51154e48d4c044934c4a552208332679327a5a42253b035f205bb6a9752a8b4cb17f847dbdba5c1fc723944e5105311032a68b4613f143e145e16c0c5bf9917ff3a14fded2bead97b71693975a68edfddbf94a9dbcbb50ed2eee3f197cfee080a9021139d3aa2e0e4fc92b7675eef3b759b631d7c6d6b005045ddfce418a7c3d4db5e716319d6dae2649a89ab99899fd3b293633898b767d44dbeb9464fde42cf9f3c53b74bb375fc1a3bd6b133b782f4095da2e25fdd722df40839c00f55d278e85107f4b513fc3222a02af76727999312d267338878131943c5612966c15488a0d020c926250bcd0347cfd288da72a80636f3a02d4fa95b54821fedb4321a6f3e36b0f741a74e170b1f1bbe3d4abf4637029d5e3efda30f9d7a4e7f8cf351ef6ba78e2dc7427ff751c7983d7a9f8cec291cf4918962627cf48a81641ae31cf63eea31397c313e2f08b1d0cec7c30e855eade02ac637e97da998975eb4c98133ed32e88bf012458f8b40207fa054aa0201f5aa39242aaa57fe31506b3c7f106484509011428b80da3e1d844211d1c140affa884b0fc2d5c31b257026c9a57157a443e95039a504c6e89cf7b136ef5b69dee7917a5fdd8a744d154b83834a051e694fedc28d2bf75a7dc6fa2ccb7c986867da976eb0ac929052476ec494abc041fd76a98a6f57c2bbb44bbbc4de11a7f6e99a86de5783180155f3f8104595bcb6e4355f3ec1e49af74de75ce378a81f3f0ca8a72e35cdc4ccccccccec5b26514fbb6f089dce2764cef5c742360c24c258c16b9e4a087ccde16b1e0ca837c2de874202b3287de61d9147ff65a0a59e96467a064cdca66519a04b233d03189b8039e6d56d20f0a7346215c483081ecc2d565c30644451ea77115d448bc3ca8846b896c888629ebd1d434b2354bf1192867e8424d65f4419d145b46368157455e1e3f24dd3bc2c03423da723cfe5a79863584e7f2d2d2e425418aea13722fd72da5ae78ea85f961d2d4ecc6bd2fbaea1023c5751c37cf63a2515d44f45a4f42aa2efece8ca64e0ff3aa5df7c0cc92c9af3f650dd086480b608a8a88f2a1660962ccf0eb3645913a3b6a8392d3ee172e82a38f4163087bed3352af654ec9fed9f9a2cce5af1d08724c5f40a42cf3a19acf36e2f6614fe4aad4dceceaca00de770b378e80c58c8aad1e2e1cc4e6f635fce01ec97abd0d8520d22bd9c94f409d9841af8849eef69458f31ca295afca32e343abd16070975c6fc257394e63a3687e17c071293a74ecea37a0f6c693aca23d55d3b3a09d3451da8ae5fd8c5fbaac9b46d5987edd2b4ac91b802ea63f66b648393ffae6fbf68fb3c793b5d1c24d14dde8e691f9df3f68cfbe89ab76f99b773a78f8e79bba97e3c2d0e92be4ea6ae3bb834976f5dece4d25c9e75d7d25c8e7573692ea7ddecb0a5e978b084e364e4a36b2d6f5a56cfa985d97499de32fd34ddc46dd791977e71bd3cf3b67adb19890e8b46b0f43b40df2318dae9a1a0269799a329011f0bf071e663f7d1b58c7e73f4e47d372cd66af33eff8fa6326f8174e0af5eb592bfe67e34a985690ee874ba73d26ee7941df57637095c6683f570a76b7bed42deb973e77a09df11278a7c745affa2bede77f9755dbbeb73d7e591d4eff60f6f1db7c8d22133cb206edc8dbbbb31c6dd5d2c9a94de56b5b8ad8792d225f4f95dbebe55548c512a4549e39c54db328de368e550105a81dddddd500ced54b99977bbb7b7b77765bc68dc82ea2e2fa51bad2f66ec0b10288e7d0342e6b4f2515bb6bb216cd8b0e1f2b6084163df165ec2e8ee6e9d8cc8ca3273d00b121948dd5d77f7a58b3a467d11a376d3f66294d73527a5bb59c6190beab760a0d0ab4d12bda250d0c2a89a0ef3b7c3c6b6850c6dfb250ce855e6f6beb8e9c0f5e8d2ac9457bc68f4395fcca85fa574a35252503efa8b192f40c41720e24b187537468e93b2fa4b8c326ab5b6b448cdc565777777a38c715d6cd01a42d8d11b220aa528ae781a382019e1a00347d7100e58ae21765ede664005d00ca8f879c30bcd895c065e10beab001983dace4150b823f478e4a0a611638c8c86b67bc2fa079bb5dd9aeeee2b76941e73777777770ea28c32464ae794f3dae8d7a5236394310ab5317ffdfa8d8b93f9c24dfa9a9c21e79b6bbe3142d6893242de3a2debe2d248c7a4cf4bb24061147679397548bd9dccbe3f9927b317a54f87d2f901127a101418e6d3a337e6ec52ceee9b1e1de3845c8e39e558988e791ff4a94323a4107a716e47bf3821d0a7f4f5282f2923ec2242086137e4dddddd85e1e6c80d1030dccc2093c821289d41320c326f04fad6abc84764be671075fafac76d9fb907cce0c120f317e526811629dc1daaf4f6f560c8883e7a46d88db4f78138a41481f41f4dc5b8cc85bd11bea60bbce6456937e78451c6abe70b0b322d4a2937e811426edb2f46d4f572fec7e4a557972663cdd3b26db9e8d1167621e1e0163d23f2ba28a53e2f6a425d9d0afae5d467a7824d6e9a0557d1af8ececbb5d3363bea57379bd4d6e5266e85f9db81cb2fc738caf1f0e46e96059557e896d8cf15f1b2048d31c698c4964412b569121263da135e51ca085b92e8c846658f0f45651a23ec96665d46e9314614eae506c218a316638cd1bb8991b27e5f96a8ecbc506329e5b60c31048505d4a99af65112c3005ee86c86ec2166434bfb0071930978715e50463aaff851480a452129242485f8a5509cc1862345268d291e1463c61c4ba5584a8a3d42c85ebce609423aaf285733dea578d416d60debf112df7cc4b711f38a12c20cb50f642ab37da00d1fb5cf6c9ff6396a9fa3d57fedb3c2f64c8350d924741ad2a182e9316e281d308c619c691bc76d5a862901b323d26b4a18e3cf4e024390925a08721dfd84512efa83158545bd0c14591a21c018230c17032771524aa25e74ec987e495f0afcf4d8ed743859980edd65f260c61c891ebc38fbe202f39a2a6652d6cc52c7e3061b01c509318242bdb8c48c002516b123e8bc608d2dd1c49d606efa8655fbc0bbacc5e9bfdcb4b438367ff90db4b92e47e9a0725ffed1c75ffec1c8d8c4f0cc05f3b2f91217df3c55a6c12dd21b5e831b9ddf9c8e6a07c6c521a7f992e89b6b1e0f8cbb784ba0c338d6d1a77c42e25499dfc0992bf3764e564e60b02ed79a9b5dbde1b5b9c5aec96e885dd34de834cdae6f782deb668b6f1e8116df3c158d407515f5542e6c0ee33c9bc3784ba6505f728231398cf7b466275f083f75fedd71f1acbb3e739934dbd91c02dbda3c2653a83389bef90bac451460b4603c26d0b9f930ce131dc653650ee3dbe251b9f0e230fec28bc3784ca6f0c0b88b33d9e12ffe828ba31ce53181c969984e85440616faa7efa01c0990fdcc21b02d94c3b86a6787ab20b0ad1daa17601c4375d9e5f3726ce7e705a653cd1d9d6a7a2a1776b80e7f01c6511e1394c3b80e8fc9141e94c338131dbec3b7e5f282d201b34347b72dd4b65c2e1ee89ccf6efed5ddc099cbb7ee32facb4fb5db56edb675dad6723a3b2c3873c9c4c4a85c88be394ff4cd534ddf160f74d5f45a517282b06e5cd8d64f74e85bcfd55c1747bf478bd34b24f8810fab8916eb24e9157f95ae813128e9156f4dd9636057c2be036dea51d5504bd4fe26dc92fe2382145ad29b706bba4b6f494f6bc9141ee9cfd3445112aa9d4b8b28c26849cf691e257086bd9d18e00cfbb6a41b7588b7d32b96d212a6cb3f171a3a9ce1835da7b45e27d016dac0364a2208a4d6120ccb154b2d1af68a2d94c25832cf68d1804aa2053d192c2925b516690b368e5aeb45174a492d3ec11167486975c76aa2a55db30c20908c5addc58ca04543334308571c83da5b7d8b8914956a126444f5a32b944cd7ecb624e1e3db9be188a153a27c40e5c0907a239807e95573a742af52bd62e620bd6a15bc6bfae5952dd5c9a64f4fc9fab7c7bcbb3bb69132842a16e20743ea8d40eff9e67460841e23ed764a991522dca48491f6114d828162dc3ceb3e99cf7c8b98f765fc51bbe2859580cd0957bfa19e6e3615ce3403615e26f349e0e796f0d49330482263c851fd52b173b627375bcd69229c1d21c9e8537a75ce3863242325026eb20d91407b44f6a8f6201503b46d62a060163738f9afe1a0f29bc50d3f5fc3f0677b72c339275346f9a2cc57f521b0c5d39ceb25443e3b7b3d4465c835e7c65870b79f3a9e1db2abcbdb591a4ee2a8c74730f5b1c322846fd7bacda8888d28ad35957238d3303580309008438ccade4bbd6a8a3aa27edc5524dc2b5d237b09361155a8857aa544afa4f4aa877a25843d6108b71b8b23e3b75fb46fffb1510b0de1135c5813a0f717483c3b6457d0bd9da52922c4474645b0f26614639c744ecc352c46dee815310ca35d08cde964db7cf4aa97fc75fde8194ec7874c0aef23c6f883d52b1f2b48c107519cd717bd8ffb8bbd75a99e81ee655ec552bfe8b433d28cc45fad12f2de514058c627f3d13766e620583aec2c7d73e61a139f7220947953c74ab2d65aafeb07f5be94fff5e3ba2e99182a4140e3a5a712f2d25349ef79a99551d97fdc503f5e03e8e335dac97f47560ed41029a3c76ef46a937e8d5e7556a5b27fbc0634f14482a8838e75289a699bcc627aa639cd4b71513dcb328fde7133ac4b3d959b4b1923932aa26e267c3bb34610e30fcee42621aacae43d6ffae2c35dedaa4899ff44dae2452f7ad1ebe5a1537a5dfc7d4defe9a984fce5a92eef1922bd8a5a1b09f40cb4f9f1f9d88d79e8a986d0b7257af3e788b56dd897e4e105f93da7a24e7f6cab94fa7694752938d3ec3915754220562ae517a6c1940fa918de684c7d968a99988dd69827b345452df5e945f6a1b5eb9bced333272afb919b87e1e68db0b731a8476e7e52df1873d9c170f36dde77f39b47cf6838ff7ebcf40fa5e3e5082f0350519b6b8eea6af7ec2d3a9ed94dec9c9f50cfdeb779ccb8ee13c0b7cf2c59b204a18bcf30dcbcf48c44cf63491f94bee96fec11a90dde1aa9c49bc713d42f7ef3c7d3add02fbd763f7ab5791ff315a364864a6db06b3cb4340c39f34f2ed5aee9ae91dc955da1026d4c5e8694212951bfcc4809970020ce35de60f490cce762d835860968d22b3658bd6218a19725f52a43fa2ad2f7e3ab752e19d323d2cccc9848d329b439f99c91fec68f1df68a5f2e2d19d7c9fb92f4925ca2a799e5a10c29c6e8d1bf2523caa1bf7ce4d21615fa279764aa16952f2f4936f9e8655a286d7d2a431e6d2793e459c9cf181faa5f9f6a1e493df59c9e9e10f9975f9e9298e76efb56abd5eac322dd300c764b9344212964c992257ef42e4fa2ea143598335e73d25d41df55fcebcae215af897a8d7aec1acda95316dd15e6b16b78d384c0a78cf9621da9ec4d8d1b0ad5c57c4b68c850a8d4d64d61bfb0eba25714aaafc52ccba8cf0c939c0e866d5ea4d35b8f368855767882b49428c01f2c59320624506b209cf3f2f8a97ea010e43fe681a8efc14a95e71a21f4f58ea0fef23ed60154c9a86e66886f845180324208218c3ea1cf107f3d23d12364c9789fd47aa19c5764a5aa510f271ac4b841a740953fc6b8462ee67428c591d18b73681af5ac63af99f7928fbe1ce567e656d8b86e0ac7cdbbb408b5e95d9f76738b10d5ab36a3b26b0de55082ea1a2b12e899de7ea870ba9c9e0a37554702ab14434da66449d750fe41616b6eca6bf3723c15e854d2ab66d46bddb7607ca914a5b5a652eeab158b75f39c7a0cc6d033ed1816b118552ca858e0372257d009c5d8a8283ceb47ccca870ce6c940381d807994af264ea0675cd4de5873a2ed07ea160981e5657e8b8488c15fdd8dc1cd9b17581ab841e7cde31fad07157a0ea59957597a7797cfee724ec249dc33c88f9e11f68e60689a8af39ee7345fdf9cd6b2932637ae53b1f0999c33bd66720ec8fed6795cb7c2f7bc389dabc8044acfe3b728079fd7b66deba0675d7f732b4ccec89176cea5731e0cedd2374ee7e280c0cf9ce38078b4579ec990243ed1f373163a0cfd669af7657e49cf48e69ccee5996f33ebcf3a8df7bf9ea71867f91a9e3a53cf16a79fa3e7346b1e12ce92f9d2ad468eaf38500fafccafcbb3c5d97038935fce2dcec92f372d4ef5cb4f8bd3e297d7c571f1cb5b16e7c52f77e113a85fde7704f597ef0f95ffba3c0a641b2e8d34c9a5917eba96467a9d4b23bdc5e5c56b599aae9e4cdcd2403775dbd240e7baadcb9606bad6617469a0635d74611320e4cd856110346d3a952e670edcd983f4ab6bc9484e2f7d937432c7a1758fcf4befa9e2a3cb90b39c0e2f8d11197d011f777721094ec2b7939042347dbb0cd5f4eda612be87b37c771f0d35b0a0d30b3d22e12c6f64bd1d762a210bf87619542c2ce0db17f0edc52cafc595a6e903d5e5f24299a0b253ff28cc4a28a184ac3e12565ac11f09273d75e613ba066da967ae719734c9787280f4cc618779d6c58c39ee0130852f85a71e92149eba56396b184d1be89fe19a9727e43ae3729590cb4f0eb83e861f67799ae5b1e9394d9d76a7bfbcd3470fc9e9598a2a4144519e100a6389a0a28d92a3f4d3afab7d2ad5e8462edf5ec1cb67425db3454247cf1ad0b30cf3d6fab4a167e8444a3f79feba817876ee209e7b8c67ffe44f15cffe4d379e3fba045d627184ba2696d1c41432557efcc74156aeac6103b6f21fcbf1d5ab634b990f1d427af6e946d7cca5b93497e612f5a14d28d05ca2411cc4434c85a5c82a58449a67437f519144462f7fbae693506493264d18e8b9891c1ae388cd376f07ceb003a0e889c01976005051b7b5575adbcafcdba41736cf3cd54ee69a6f6bb99dcd53b552ab8bfa35d2538142744d10b4a1545a4aaa7a8a0e394d0a8236db923244a58d9ee9109c61292591340a2286244a402c0e0c4dd4443e3e0f3d959225bbf4c3e24087e1c85ef9a8e4e3f3d153b1cfcf95ef6eafbccf93264e9a003d69275da36228bd12a257ec74e8999d1251233a44a96844ee84b6bb1cfbfa6fd0a6a70624d2b8018d10c051030c5a2e52b28c20c907263892a2b55e531a0b989d398676254da46bd66b15d495b23808f8aeb0026be36383a07c51c3371671d420ec0e18b026ac37b7cd97c2dbb017433e3e3e352491cd4429e0e2bfed77290544df0408cafa68551966f8f8a4189410292281ae593fc00957744b2e0e907d19524b13bd656958c573794f6b7a4f8b1be8db2eeab749db4dea45bf3ad5bca2f47c606340686d25e9d7615bb9f2eb3149aef1bb8dd4ab7528e325e745e5bce8c46836319a615ab67191e360338769d9a6719be9144f27d87cd2b8cdc49d4c753b29514f4afc7a4bed2980f0b78f1cfdd33e72b4936eb27454058ca32a56908c384808160a6221580996c250d67b0d3794760d39961a1241147e3d6e95df538b93161797971729bf8e52e29a93520ccb58a3589669dab671dac67126d3e9545b624b0b6c6e319d6a6d697171796971797941a174e880d91177ec80cd3b503a606076ec48a578700f1e3d625423c4903042091edd61b36f5b772a0e08bf6aeb2e860312b375370207849f84480209b0998411a80e0e0809f1934ac0d01ddcf60af553dbdb6a775feda152b18af66a535b775fd6c42fb395a11e3d7ac41e3d60f7e01eecddf0a35b77ddcd087b4ede1e102eecf6d8434010405cd48da252f92f75f45c8293672fc149d7f489e884e2a2ae2fd77b4a5a01fe1319a141a50a8e4e49272ca7a3135209403188b9f2d29606e0d084df7766590b592bc4b3f755d623e5208e2fb480e38d211c5a7b2444124b6fd8d002164091b31e649d7503add8d51184daae230815fe7aa45a368331fef206400deae955de1c90d363de1dbfe63ebaa10a7d69240152af16bac0738274f77e2c8fd1bbcca8febd82d0dddb9c20df5694029fdfdaa81feb210bcb43ffc18a9ed3399d033d081945c851841b5e54768d3126aaffe7acf60a6d36d77cab9fb3d86fd87d054344fd968be8d2ab1b05695514e5e899a308f15b14c5c9dbfc161501827719faa5a8373b89e474a28c52b6ac5bd0c5114afd3c7e49faa3631ea997b94f24a29bb4f9d4201451cc4f8ac49763614a24f3a987e4ebf9f692f437eca3ac312228a52a96674c0af6146f4895bdb2a75a0914288102a12e2a96a0277f13e4e4517856ed2c9c91996ff7b176b21d16506d6715d99c777b7c28b37e7ac53e35e6640c0bceb0cb6e4a4fb1b62775951efae480405fcf070aeae7238964ffc1bac9599cfe4eea9a34f8846d2c3044d48f7378f61823e6db7d47583fbd638c9e944484c548e9e346ff7c45c4dadd882181d8b6e462a7348fb1db65c55dd7ba9d4ba6f0689ab744a341897a5f4f90f7edcc7cf4daae9531f7062a6688fac524e936c08fed09a55b3d41fddc7df80115c4eac8569dc4a0faa5f0edd05ba8b650b35843a0cd1208022e5a4b687819a3559d061412ad29eced3bd066c9aa21c511472d1a9606406a60d1aade14951036429d3dd5b52a98761ef2c939893c75dfce9b1c7647582ab420c462a1f9fe8d2a33752acde96b1eab0576cde1a99bce49e4cf8d37e639bd9bee47af9827ab5742191b47f440c5192498811a8d0120ec20075780d0c11959b4da8b7041d46f8fbe23ac57f90e0f2bbea9494d8dac1f611029d2ed578c0e6aec9db6d9379230d0350b7f8bc4b0f2cdfaeead66dde82264d64dd77c8b850a5da372991fb18cff3622457686307c7620f0993dc8d45c0daca38d0a9f7ea78727131ee2ec89233333c7ebbafaea589d44040345bac61f46efaba7a581ec4420840578e8d031508448afa0c6060ffc71e3dfa90c37d600420a63097a66edc42b9d602568b356c4218595347cb82288242c053750a9320426cce841c386aa122f711263e12b6254c622935c526e472fd493fa2591d4f94ac758ba39a594ebebb4839ef926910fe92e74ea7152afd8f94aafb0f48afdc8267d49e44f9d4ba33e2f91968e948cb034515212111623a5251f6fb20262dd4829dbb78b3e912c7118bd24f2a1eb90e0c807bf454180c14957d4fda04d7a52bfc552140416bf454134c16ec26f51103fcf46d086af34bb1b9c5484fa2d96a5612beac7523e89d25aa53ca5749762598a852621a58eea5cca49d02727bdff477f931eee129dde88bedb715503209498b8c2054438220143b001043ba840a80c2f98dda14d4f156bf0d004133d80a08918d50c2b673c31820a2144b9c810e3d9233bfb8abda65011d22a83285d480103891e7090412bfad67a09f9f0440f49bffa2d7a42c66b1ad3aee997ebb187ffae1a58430c7860e5d7a3f4ae972e7f9b21fee65ae7824772facdb51ee23518c46bf187d7e4095ebb361d9e72403c19318861585f188661d8ec61fa7a845ef3221f78f8acfbd6331f7c1e5b6ea26e510f4a0f1d73e83dffadc93fccdbe4d2e419e9e5bee99c890b92833934795fcf73deb78e39a679464c5c71921aedb1fe495f8fbdea3ecc8d600eabf85d1f1fe600cca1639af7ad679b75ed18dc40cf43bfd6fb6f7d37f6687c253985280d1cc9804679c16bce1440a00511ac3cc94111117aa8d8a0c61855e67062082c910c54afb8452f1921bb109e502f7a72748577e8418da3567705c8a135c4103964697527c345eb063e60a208adee3609044480030a255add411070d1d2e2063b1e7242488d5677504a1b07980117585adab5c16e490d0fd2e8a14503e743132fc05cd0a2e165081776002028885addad16da9cd22fcf7e97a65309910e5d4a1ab87905d481f82c8e91cdfb3b1cea57bf735239d73a12d44de386515e73d33a8a9954f4b9addb9757a48e97f2e5f6f288ea9f0549cfa448cf80a46750e48e979a896869a404ca805e7e1248fbcd975b2100bf79bb349af7650fa0bfbdc2fc729510ea97d36b5e4912094da569994d4d90cd0f3cfa799ba5df8aa4507ab845404879e82a64583fe640fab1e588fe923e4b13a94ba0a79c8e8a3e76f4d4b5d993e7ce3963357d3dbf5bf743835e50c477b72a219747df8e640dadd8e3912ca0a769e0e61a34e9eb69915b454f863e5e1ea54b239ecb8b54e04cec5efe010ba16817a85bfbe8061e7c20baea9114414598cf0b88cef86f85c50b8cb698314156c0f01686f4893e422c250d010507540849ab1da0823a080721dad036a236b0585de1860d57b451650eec02823f00fdb0c40d33f3e9211fde98402718810922cd512715ed500411d0143bcc6d2802119103aac483941f5a5648643131688dd58f265c8e947c40c1510e598a682b2505f1031604145b702e3ed0070a14336907276660b163843c86aef001698b57a400cb7fab2320c0b872d262c7087900e9d0c446e4850e69e830050c47d81bf37316cf03702349bb3c76cbfa2b08b4092edfddddab188f5f0c0f5845130f896c7848e445154fe0aa07071bac3022c90a0f3cc0d113c50ba0c841b4c609e21842bcf2451cf1e6bf8d8a12704185956d83107aca0b351e42c8b19d5e81810fbd6d1042d885130fb787109230c5d2f7b65100288a2a72ac80063aa0d2faaa0688180216705c99d24406918c8761f4f0b032af4f414609a639341d6dd4184392236ca890d3dead4dcecefc5780ff6e5e034b03c3ce3487608c39def83eb2341dbd22447ad5aef5a0b67f9bc4de0d5da0adcc00d606f38e50cf00ec41ff701eb651e5af1c9533b034d0992bb234d05156d46f8d82404a6b35aadb42af54d2db290267a0bb517782f40a42baa954cb7f94084aa1ad77706976e5633d991ed4f54f8619021a0a313ed666afa04118509e8822c7166cb4da7fac4d4f14315ca006114743f88117ad8ed199a3eeee7a6ad7f74f49dfbc6eba82e5ac33c99cd1a8d083401b269bc39eb167cec39e79f4781676ec9987601d73d416356771e6b79f9a2c8ecdb70fc19294b02e089cc9d141dda77489a8c4ba161a42b8b9704a8a466872ce49b76e3a06b166421d5668a3b99415ce48da2be9f42704d4a18700f3dee96979aae8455fe2f1604b643799f1865f87d4a1f35087de9229d397401eccdb5bd28e794eaba663d3a96febaadd0e853352ca558a2dd0a6703f4d6e5a9d6af5f762f323829b168c37a1ad9885f18ffb85f1afe517c6bf975f6fb8c3f4de1131bcb303b545cecaa4c45aa33d82d8081e7376a04d0fe7e1718d3caee46871d9d2ade08cdc31027453d70edb4738f9e6cba9664eefb83aaebdc9959497af5008c2734a5a2b2ba2adb247f2aaf94e46a7a4d39553529f8ca00dca08658442c98dd7ccd244e7ee66003c13bd3bd37f2b047c06f8e89ab75a49d7a46bd3db81b07d033d7f794ec7c048959ffca7876faec18ef3ee4c9e7537bd8aaee38d0aff5bad16c7f411c663fc05188ff154d2637c5b2fece03cc65fe03cc6e42f9898b49b7c5b9d8b6f5d8bab3a94f7e85e3cd555e7d1e940b98bffbc788b5f54c503abaec37f7e50fee3e29cffbcf84f8b9bba1d9dea72984e75f9c929ab25bea0746cd262a92b38f382c9e4b27b817381732627e7a8b65dbeb3391208b4588b28c2a822bbc9b9e954a557517539abc575aacbdb391d9d8c5627a3934b0f6d171c682ba3829ed1e99abf7079e631f1984ce1c9fcf2ac53ed689aab5e9081857ef6e6666e873a47e79d9fe91743a79d0abae698bf90658e655a9c1dccbab9f448afa24cbc7aba630b26fd18ca274cdfcca1bfa03199c2d3aead431b6ef3e9399c99a82b157a4ca64867e23169d79ca167ce033dd5e59bc3f654d1db5597e751ed72e93c55987b2beb54983797f46a3af592f46abac190a8db16d7a3db96f70adaa8183acf06bdbd253c1c7a2a5ed2c3b7d6e63dadc81dad312986e84aa213895e245691e3d706bf455550f01adcb88e876ba62b9508b4e13c7a911dce5349e7e19d55c50055127c739447188f298ff1639f1d2f1e8b7844de6802833311d378833f31ae7212b8aea7c539047a5a9cca398f711e1a9a2852a2c5a99c848e5b1aeb70942fa792312ac89cce54cd690380a418a393587d3ec6187f4aae4dbae6739fe8f527561f87362dd02add42158c9afec6a7b24795eca1b29b9e881c15890c98679e0ebb8e376a7c1922a723834a7a3b312a9f8eed701dfe03e328d7623a125cd58de03d3ae83c3a2590c8e2cc8f361fbd08b4698f31724e82f3704e0274cea5c7b3f908de92f68d7355b7798f6d699339d50e6ca79aa73a17dfd1b5384c575d4777721404b6f5f3d3e227ffa96ef2d9a9763887c0b6b86de7e727043d3e2efef3d3e23f27e7fca7fa8fc9b7974ea657b12b0267a2731d3cf92c74e9825cba6d11813371e5bd8ab107aa8dcad0db2baa67760bd1f46da7fe02fbf49860ce4e3d265378986cca5db54ebda7a55a4fe5423b3b0fb3536f09e6eca97609b7f7b4b69351aaff0ef421d073a00781aaf524fc2ae93c94a33369a7ce64cae5f14c67674255eba9a4e7423b759e76ea458f877d3a93e9b1200a07b59d258bd026471863bcaef5851242188bfcbec4a0b2c323299df5853142282184106e73eeee428e26ed886b5e1165ead0e87497b6804d9f8ecd0e72c5526c8a2858d644944cbb228ab65d11659b53080730ca31f315514c5c469f302104238c113221f4859017424c0831212425470e60cfcc4c081141eb9aaea1bf28c4195b601558515e58541fab95bca41a55c6fd9a312695fa75240a95d0fd2aed1189f27c3217665c038fc8bc0f99d50aa46841df2085d00d6af65b2405109827450e24581aa9104970865238eae9b7480a1f2aa2507af6d8c21293a2719756a2ff526ca46aa5323f7a65c58984326b90437c284a97143cc03f0192a4ac2186685ae9568158a367d60d0a841c3db3ffd5940d4058f91fae1419e1460e461fc411ad54b24ec6eb46694dc9dfa228a0fc96450f91c30ffe63411ea0d140a8db18bc46d5aa4034ea0e276cc8610a4ba89a7e8b7240011b39fcb0421440a8df221dbcf8f85b44c5e8bf548cc35fe38d1d76a9eebe2d7203cb27e96e4dfbbd7ee36f77432e767767ba009d80aea143d850c6eeeeee6f777775ac51f99aa2ee35c7d0e07add9eec168f2af53b3d04f2c5992d0dc0e1a857b4956281b37cac42abae346e471cd7598eeeaf837c42d0de5300d56cde430d858a6f1f7368118dc84ee419dfcd4d809eb0104152589083ff988836d663fc6cdf368a3850c00e446c71c4c6196544d1fa9668c7d0a10923277898620232be5dfa7e42053078838830a85cc1a1f5f1d033c08a1fb6b0a2861358e6682d2b4d8008043b484304587c40d46a23588ed8a18c2966a00147966d95ff623efb4fe6b3333ef3cc69e6b5c9679e82428ccfa844599dd0bf39c6e96c1d23f3ec454f3cd1bde694d3d1b22c8bb96e90e29be73572628dc71cb331610c2b7ca85f9c0eadd38911e84c5ae33e7f79e474ae38809760bc941b1b8b6ffb269efc78e86c45fd5258f4d17351e2cd6977db2db2c2430f4cd8000ea52b780c3056605a95c7c643ef41b918e38914f3e074226f49452e18c1434f552eba18810edef105134f3c7498bd410e46fc40065676b062040c1540483e3d004d61d48a495fa8e1b2383a388c16e0e0a1a3381d7874f4841343085591050a5ab5268716fc60c41737242dd18243da86837c7c7c7068410821843e88377808218410c2174e074677e1742207008c2e12b062ecb755a62841a708e1db6b8a05407cbbb3e007dfbeaae2db595bb4c4d0162d28e2db5b389dfe228a8c6b6a54628e877e52e22cfe43fdf82f468bff7ca4f0df8fef2e2a12717c7b8ae8db5749b8f1ad44157de303247a40e1db394ea783b0823862be7d43f2e1db5508c2355ab46205503cf48cd38175c537dddc7777771069c42e01a4218d713a1d9d723a91b13c74e853084552acf86ebf389d3e1ae30675251b0f21250011680cc10467f830e4821364d96287263d5c2103285a438804e98e677c77a183ef4b8ccacccc44471c3d84a8aef9f8e8e8c9c223a25022c4b00ab0e07c6f282fc2882bbf453a38f94fabe2d97dfc161941452332ca90e04ca764aa24a24835c9c7da4822b8b336746867d817095298213dcfec3b89c84aaf3a8902593e3a083e3ad27f8d844ac1125d5d27b5955e21bd511d29762ae89208dad016ec0c7b14463178e6193cbbd1d1d0d2a4806ed952dbf013409a8f13285d13a7fea8748df694410b51cd0800000000a314002020100c074522a15038265184c97614000c879640725618ccd32cc9611432c81863082100404406404668360d6453d6119d7208c55de8cff9a5709497f3f2ce8dacc49566e1ca3898b441b196e96bb4156ec84d2a51e027f7e3b619aa5ef4a6fa8a987cec9fbe63e4402a8b25ab68a116e8a0c34126018980b701d3e97293c74a4a04d922064ca4251b8dd1295a44b0709fbf5b30482551d3649e54db45cf998abb4d7641f33fe29d9341caa62bb32a685ed7fbcce8b632fadaaf4d562268139782031962edeba0709ebf3b7e8cb89af13ffbbd26cda4f49c2b15ce248f09ed672f1c964955bc512465193808df56ec366f2524425ff3d89bd3b2ce44037d0d0456dfffb03d7cc5f9c801adfbd87b76bc7428e2ce082d9ab94fb1696d5340a2830a376534e0ce1c94a8dc553b9654027ae67507066874843762fd3f8c2042d58d214a07c41c868561cd61cdd13fc1958d6b97867e6bd49f2d995cc1e9a4ba1742fd270cbbe21247704bc77a298c6430fe23325df9f792727f3d010fbde6f5237f13cac293becc2363d08ee68216e4c24b7a2319b663f1df5b683d706196623673f5ed90d6a5ab3259c6fd17c7e4c7e8603f7b96de88e08d298f96c319c9da8be3e0a0dce1e27187b3ed052f346b243a304b92908d3eba5fbcc09b4ae323e3e15b27000fbf474c83bc1d7701ade1740ae0dadee9eb32b6f3e9188458b45abff2b2c021a89ec67243d2b905ace33888c28b439f31f547640bc412fbb75ae918c8edf19346b708146e4f24f60f1219510212af31c8e52c2c5004287dc44ae3bdd14cdb75035b1a3948a676ee4078740e3e7005d82e79e801804927c65496277e421f067f97a9a6a4f30af1862cc245afd6c458a5fdd68d1190362ae30e204a4502df2fec85ed65370b7d43211b686256910d895acd30969d58138a6d4647b538bc4bd07551a2df1e5038f2d9f21bb508f78d747e156e1ac406f949a3db89580b88ad1a5b3f1d15157191443f8f38057971f6fa300553ec175f9ac7b06660197b4ae27d264e3d28567f08e7bf1ef4de633e17c221c9752158c9d6cffb53adf57957fcbad7cdad7f368ed74cf85972de13d443d49c306a751afc76873670d1bf8308385dd94db9f44c2a6f5828dd6d3fbef01465511a7333ff65d6ac7daeb376191780111e5cc9cdbc8913854fc213480f9848f83d748ff887521c31e9cadc087a8bb98958df69bd1ebb1821e0a7cdfe1ad21edd2adcda248bc670a39a7406aa57bb16930d6e03dbbc9f6c0dfd1907d2dfaea5ca55d3992804b444675047a188b387088f3c1a4b53ca715c5d1b8aa3e030b6b5a3865eb922dbbf4467c972d08940d0f87a33c1d91a249f19c0200110ff9a62257a4bb721284f88543ef92f75f47248062150ead2e049b68b87b5abd61c0da3861704b4819919d290572565afc792e8f054aff2edb9cf6e552c1aa35b9ea1a2ce22362748b20ded05214a27de25626af9d1b221d0427be0b13ba05841cb95f879fb83502ab95db8d98ce51dde8c0958df8c2dfe5e5de28770f05288d0770597f9678806d4a55b432bd22791f928eeb039df41b9316dc0724f053f7b5000a8e4c657b45521afb00ba8fdfdd9068380f7cfbb747e6579b1b9f004c835122765d4274e08305d0581d760c1097c582b7696796f138c7108cb84e4c27d39a6c1e1e87e3c5eb2b844a5f952359c10d938c02d835196c9f91b0b548c95450459d97ae1236f9301baa272e38dee227a932f94ed0d1f0ec5be5ffe7576de2fde377ab598f041cea6cfa1db20dc3253ff1823b622e7f6b4d9619b6e1124392dc06639cda5e9ed761b3539fc0faeca4d927991b19dad9f7d1488751b955be935b4cfde2e0f54351bc75c8371076917dc78581fa6856e1fd98037e43c6d8bea42380e7721b7af53784f7047999db99f9883d1fc6d2eb558bad4b4f8598c142b7ac36ee598d28d8fee449b264475f27800b0db3a9be6800a497a1708d02fea26af5a10d78583581abe8f7f07de37e40e4efc7be171892dee289b831e52cd7ff175973457aa0d7875038aad5057c0a80848dbf985f019b446694011dc9846ff4a620b4ff1a531445cff48c96a79df6a5a168616150fe2debe5ab42dfe720f8bb951b835ae2eb050fb7c58fca7a9e8fc94dbb3941e3a784ab394ca2cd674df5da3138882ecd701b0429d1c61cd7fcf0d665a670bd84318ec89b3060afb87091300018ff030e28528fabca9d42250a687776241ca26585cd5ffcc54f2598b81b60d5ffae8d9470e2b4080fe23f6db768b50d91f9c533a2cae2aa89ec8b8276a454b41a85ad21d1fcc736e6f248baed6720db621dddf328ad9b8c4b7dd3997574b8238552efdad0400d911cbc256dde90c21b65ba910681f1020200de582644b651021b62ce3cfd3fd45a70d9de52623302ad2503bff5ee031bd4edc81f5511fd85c39a63d455f2a970be6b01494cc63e23186c95cdf813d2d717cb30ed576d48906db99b9aabdf50ca06f15c368b4666130b4a79e817f98f0e45e4c6081ed1d3ec500a510d6ff229249197804199c16202929c1e009b99e5c1da23d32399ae089ddb03942a978de2048c0d639d894c1362a9c2921e7d3752d925aca5f84f94e8cf968f38e4f67b75ce26d755a502c14b978d5ca942e3d56ec1f4287fe5821fa20bcb613948d72500cce6830c9a5e115101caa3dcf511a1e7c078edae352827f675ed1fd5c6711386e588703881ed2e2e0d42de37d039f6097df2262331577c02b85ee3b01d20b4ab333fa80bc49cbdea4ef67954c8999a2a246b2e3923a2fa26c403fb3ab5b10b68c755d05e360256cbd012a9a0227d1b80f3dd2078d96a6d8b5f23a12c32bc4415fb69f2b10b54797e350ae08aaa4bc5d68673b5008dc71dc63acf9b0511ad2ea72600118a8ab9e660691d1109210f371cd13cf0a32da17819223e85985785b78385a62d751c8ba83b7bc7c3e74ee4672289c7c6362bd8b29a1fbd9183e9a04d71c377f2009381480bd11f7512655d6b9fde4510e9fe04372079d1aebccd8a976bf54ad56707f1d1a7de8aae6ec9583d101bf6074400b3f1f6e3eebb98f1d52b3d814624830eb869d4a492f660861aa2ab9935dda75f75a552c392ff790213205c6d0abaab7780c1147557564f2ca07c7105749975a49996cab8c2f129951b29a4630b3b0bd1e608958626b9abefb130e02570cb16c9f3ef0677d339990add3e06a2f8d5b602a0a277a12927541fa98ab4dbfa3cdbb33a8b59b356a63c255d5cd1e766a47079abbe22463fcabbe86295546098ae128ca486a32889f187b07e0dd3024da976d870f14800124579249b6c7243a788fb2670b1a82cda588a38ef945935992048d2715068b695ef9804390ff8204cc4712416bee0744fa43b2a4f0b87f395e46cb8ad0cf43a8f9cc94bc1ee8f9a152ea0d56d5959050fbd1a32ecc8943a0a830c4ff930d5a6f2c01a7f51a874bd66a2f463ae7d2ff7ba074cef8e840bbcd333e4e7f8da3112b8d222bede40c125dda970278abfcec406ffeb0fac656e947b49b5f1d49baa1fa550156e2a618eb0d21287b1ea75baa0cadd143ef2d752106d3939672b26f83a586acc3961b0309a690d41c81b0197d727d50570c06828864dcb4de333ad7177237842efb1c5407d01970d166f2dc1a69e20dad4f06a525df01a8efd2df3043134e0e4a7342219073847eeb31a41bd702e559ac34cc5b313a34b42dc64f86da25842f03ce4f6fdc8e42c9b7b8ea3a69f554ff786ba05dbceaf1131784cbe1bbc6aa8803be209cab1c69f4b3e4320082eda7ad0eb098a2c5f7d089b786d3d699353a94d345c2e6a4a4356b5e4967d12775300fa8a8d560fff776197474ceec7a72900ba8c4aad2752a4d605f951f48d22f36f83580e351ce06ed90cab58e63504f5484a8df64b03881ed1f11793294623e45480b84dad6a09234c45b65cced302da350d357f64c6273568aad50cc27381efe04870d383c425c86721ad94be26e1411fa6664f900464a45b13b42cceddb35e431e28899fe63b51d93e13f7ebe38833da5127d19fe4339855396b533c2522defe57c89bfe930c62c9fbdbd474528f371c1d3ba3f385cf71efc131c9bd64be91133d65234a5fafb106aab39538a7a62df2a4e69756e4a514efcab0ef98a72c08693c5290cf17ee7a5fa6d5efc70ff91cc9cb2b0008568b75dda13c609f78334c1513c9ebde14ab29cfeb6466c39676cdeb9cf89241434a2ebde06cc393f23ae519498a05574bfd7e87cafa2fbbd86ee9b88a6c2be9699caced35bd779f26200072f53e4f2de7fc63de500ad436c08f87f2533f6319d455ff0b154ef85c8e884c2a4eb437953025f2d3f8b394086f73c9858418f6fe529c8a721749b940a9ad166ccc44e0330d799bbe3122576d2f02533b55fbb04766c9e8294f122e840d92788fb112b9dd19e9a761156fae39e26d4c41d1efe153cc841268e144a8c1d206eaf6552bafbe1709a976332d3e3679b05332e570c7a8e1eacf3d33bbb858b62c27846fcade99f92dbad35248a307a1d81f422edddef2be51a7ee19cfeaa7c65f1956bc47859b91d3d12795898b28a4500d4dc1ea81beb571408533097f1bd9f1b4f5cdb91c9f6e1a679edc3a85c22b8bb1057524db9cdc3c79079cf7e3a6083100731682cb88542f75c9113a98967ecb012635614cb44179f88c7e93e14cd3e891162cad45d65069f26c4f01c9cbe0448873d07815d4d5e2347f8dc5a75d33fcf887210eb96ba938cee9cde50d1c833e518ed9ee8f7ac28f138a3ea8faf848232703449283309e9dbe2bdba3572ec6d3a78ef929e89337a07dc99c25e6a8ded39c75cad6df80b5a21e9073fbf9628d7dceb792ad8cff2a698c7ce2b439efa55bea53ec312bedcc1a1e795ff33929fb676b0fb353761a9c577db67867c5ee05bb8fcefbff22def1db8aac24deb1f9c5ac69aee122a43b2c1a086d42b73ebe1f2f80f5374a3cf40ed32112065d2ec1d6fffd3920ef400e70de107694b88b77f7aaf058a60a1f9ade2819b7cea6338ec4e4dca99888821d20e70e2515aff579ecc32bf38de0edc00c56b4a87621ff4d31d3ae74c96eca5291cebfedd31cafa29c46a052a7319a6a0a5e600f984c57349bc9461e61dff762c51390211137aba0060c0e31598c24f9a26edbca2025c8e8f51d9ec07e042a9b4ea7632c9e65e8848b6269f46f82ea6df7c54ac237bb71dfd9ea4c2ba610bd57479d05deb2393799f6378515dfa031377f9513223b6fc5756c5ef3facd1db995681826bf467bf1e94302ed2a082669f6e32a99fb45128828760c3de2325c9947672d92aa3e5222454820fc3f1d69c6bd2fcf7709e824d3cd1f85581a55eba51affb0ad386515a2416d8815ad8e1474dbaeb89b6aaa3ed809ff0a426ced04437b6bef5895c317b2a71a620b19f2d5ac834dafd42098500a8e3aa0db1eab403199678b52aab7c1856f9aa5f55a32b5b1c1c15f5a55981ce85dee753bbe981c877d109cc9baf69fdd99c4a6dab154ca7f9ea65b237403d0a37be7b0aa71f37b1053d0cc585907aba1b321267aae345933bc2280e24518a4b13955443a134c64738902ef2142fc91ee940186fa106bb2dddd7d26b1af76a367707c68b7be1d18e6f1efd1a825d8099aca31adaa5042cab6d5998e34821a16675124b512871c387b229e6eaa617d411db895eadc682c84261253d9cc9263da9f687cb1191833dd3a063f5e32a2164facbfa9a4872916e48d6065a18b368c36239fd5eb0a78b4a650e224a36f360b8065119b8d7875e44ef00880836c8147c3e82daa989e2063bdece8e44c0fa90cb3040ae5089b4077990de433f4bee022ccc953043f212910e49b86d37e115565259a68d679e9c763340bd0fab5deaa40534f641a01ebf4c4e68510abeb6b86f19328b1ae655d0062d6a5f73b8c68216c91b0618c2df58bed7680f9b44a1d8ddd6729fa6167908150b3288ecd55e2e34f0a7e2cb594d06900f781ced129765e4309a743532403034f97bab39088af8195548cb3a5d89e3c53f4020d28e0508c808c5f2c596fb19da3173a44baae1a3be7f815d02714201176cab16e3925ce5c58d3e4ee139495484612f5538071998b6081cab2c2ae56a38f34c7c7c42b53746f0be1b995032eee10813683e5011e262dee686f6d21e41511c52e022e9316e5ae591708b2493d27f08276f56687d3ef4b7b5f8b92b0b2170f102b47fb2cc0cc5e64af28caf57ff0ca5a5f04a32ba772d7dce40dd65593fe21740f52630a19e5aa1b71fec2897d84287990e31c2ed79ffcd543d567a25cea6be7be043b16671c732af9fceda3a853702a74af52e7802ba0516f692bac1dcde9dd1ced9dc840fe8b39e21784aed8762b31c6e1196cef7e3147fbb10787f45daf1ac77b459e3a69bb2f7e61a79d536ce3250097d29e27ecaf21beb3ffcbe213c9d0a3b97e89ce13a11810e0ddc156dda5af57615cc2b8b6f6b7d733e4ec39ddb90c79612bc7e5a3896c5fadb1c96b4cd2e6dcc0b37658283acb2e0e1f27a17558ff4ac816efe2b62f86490503bdc5dc747f974dc16634ebe8eb8733d20a4f02e607d7e5ac212f1002ee135a2a452a525bf43d75b089d0f41d7a7e7e3bfb1e5646d2084ecc7e02a51c98cb14ee2b365ad2a6f7e5deb173461abd96756b9f2e2d6eb586b00e7f29887e699db051bb167a1656581a8d64e004f8d2b37ba2f8480a37e703d5c8e12f5e4686b5539ca387a27ce4c95696b208d8a860be99a90c60446737a8febf0553d4c85d638acb1ff2389505d412db491e643e7fca1ba2cf5c723e6030b3a4184537a19af8f148367a25a787587f22c2d4cafce467300403947437850deb904ded80e2a44aa82fc91be3be06884e252cf123e2980c73ac2d34ee299197326c579175d440ad6f94c5297a5a0b060da7340adbac7503f770aab33910c4b71202969883e31fa79c76b6bf8db0ee1f16b29bfe69ca64c4724deecc224c6bc5caa9e87aa5c313688d33f5a238d82dd29f9388510f51ccdb485e33be0ab048acbda4a1ffeb2a92fa5bcc3d435585ba17dad6ac6d2f14d3d9389749843c77b2f29918e7e017202a3ac283395324303a0e8618c483fe41a3954e058cfefb3fd84eb0db121188ebed6e61866885327253a392d8d0f3063a8bfb9d90625d6f91efc2c30b8b3a27ad09a637a1ea1c63fd87a00e56c085f3a2796b2973a7bd6bd8bdf327ef125047c38dd9c486575805d588723311d2a3d214f310512df2d0af7ccf0d455e481bc2cf5998ed4eeb3c27279b449056e8dba78855d8a030c1a6b8feeb09644724fd7c03a699c4978553dbb1c643c1179ca38fd94df704248e7218cca3be3bda7d0081ce8bcad9ddbd7e020ac07b071cb606df90b57fad1ee5f445ee09661cedd1fbdeb63b5bb4e5a7b9a15af81c8e24cae2b225e655b28971ec32382be6f4cc5783ef16cb275a930500979f72a4312c9f73bf7a1156acb80c41d2c9b02e1a5e7b5cf88eddbc8765ccb0fa0c277b88dbd06bf9f44abfa51f589ecba12da6fd22e8d585c3c9cea7ce95f8eb936dfcf10e1de09a1252a64c43cab95e590cd7c35ccf47800991b30819bba3e0cf1bf34bf465e03850d0e630324639e28e0922612e66141fd3268f4310b91bbc8ae0548999f53560fa725dbdf8b3e8c68e26c778b062bd09c205e199022890b60ed1a91d92d468f30b2118a0a4e722bda502f25d8d7816d8dfc8c71cf660722aa3c92ea4aaebcc3696d09e749cc2ba8b54b76a9fadda474cf90448897a2fe0698d30800bcf305989a50fd318adcbaaee120cebbc065540535ca663d378f5d492c204956a678ec156b5e35541b69295f6d7e0835f152de2563bb3de82182cb293363332a1a0bd9453289c70bbf1cc60c0260c5655b125624ff789f52d9064fc2a304a571905c9b547dcc7840cf6a8e20f0efcc8c5164b7219100171fbcb13fbf6cf853e9565c801f9149777335bc6668edd5c31cf0dab5a52b790a0a9dba6ed8db19e23e861e1960752df3095c272abad86b7ba5f7de1e28301b90b36122cf41c7be7410129e1ac1d7b68ae60a66d515ff89cae8d74a015749bae259f081502b03ee85d9f45f4d2a97089a18e19105fdaeaae12e80db07aee0a2e1206abe53aba18f3a7ef3901ef2cda3e203714f3f49fb338f5453eeabe9462f3daedb6285ffcddc84563eec0f66409a5e5139aead45c07ffa1ec41fa91701bb17ddc9f2a480427c504c5adde194cd25b51955df4713cb54d5a15fb0b571b0f68f3b80444c1725744f11150b35e0d82d349decb427a9a181be91709509f6962580929246f457e34099110115dd8d9d507dd64f31dd28ea2619e77fd5e455a076adc0ce33c6523f80911eb48fb11df7dcb00078704dc12fccd2ccc2888025d60e39ba25789743ffae17016a2456c8780950ecf2c2d663dfd91599b4801408c67822ee022cf989df81b90be2602a1db15bd617b7dda01007f5b3da1ecda649ec540c98a4c8a852e2b6e07d630404eed85f4199e30c550249a37ce7c414c40fe0b883991149fa643010629e164f32f3e9191dd6dfbc2524a08e47e739689cb469d228f99bba39c1934b280b61c110e0ee6e8e7ddfee572cb4a97b105ecb8e952d88e4ce82784d0a37572e0e3614a3037266cb0d6a989b7b3a8f4fe6632d48e2c011081920cbc142e09f19ddf2029600d13c56ea59fa891e70f7e1e32afcd6ba39327153c957b072414b59c06485c3d594c0b47ac0873523da6c7b83c07fab4a92d1d6658775355cdebb7bd47c66541486f1a161dc0e6af755b525546c0e70b503312583c01533c1ba1aabcd78a28e357a0175fcd22cc98f7b2a5de05922500a5b2b01a67d4751ef2e8a098c539e13e3230a33f39b16dcd5e2b7a7591ba5a749d8dd84190853fe699837ccd693632e5c8241f20006e0484ae14d839e0de93d3a161b117942fbd7fd14a796ad2edf246bed67dfb535c72162f362147f451e31e24e36789f46762c39c39847db38f28c15f668e12eaf57b850acb209f0de942bf4af5471d0c133ee290ecfee13bc6801b830e74bdd6b9c78644d67201d9311e1c9d5c1c9272628609a749650e73a32848ea1d98f5ef8d22fd2d050233ea2409cf9ff04cb1a7660bf706f766196529bbd04fa7ee98adc207e3772ca0bfa0f1a3656f81cda6895c0cc7a5864ba0443757d8e7322c1f138f04c0060e232eccdd4b8b3d6aec5fe087902e74004c0601118a25c5f42e08f0326234bed0e088a5114bda11184a6da1a1059fd63ff7a6c119c4e31676f8a220759e0fbb1a5260a388e077349883fc563889be3438c7062d474d6839f3b08dea0d0397560052ef954e9d4768e6394b783ba7997a6a79b94d412494f0af42793ad2a15a01f61b3897aa586ff72d9228d734d297f6af0579e4f2a7e228648b0a07d29e31f59664d435d83d43b15fb93cfd3357e0b080d25bc74c1ff29bea80df51a1a02df47c71af30856f166819962101fdc2fba6e2b597bea147ee244f7eafe28024136a9c92be7b319b516040d5e23f25e4003ba0206cc02c8ef7c5898548586bdcdae4150970c60e2895bca1e9942995ce0434bc759bb06f0169cae61d242ebe45e981ad2020bf35dddc5d1f07332141eaac722e2664620e0afde7d922b64e10940c8f00729d52aa0c2f3a341cc918b02c4bc4b7dbbf2b58a3a2ba424c5d48e0cc14cd6813922139706feb3ca73991de3cbc780f80b22f861e0e6d50a90a9afa57eb1e1f87b0d80b77c89ab920e44c4ef4535f09221c074ec170abc43a60e79b1334b672b9748bae28b9de4bcdbe016fd03110609b76ac2eea456e3116db29c96c4394ff1703af8fa6c177e35f9ec4878b6683ee34e602f5383fd0a75c8990b8e6c84e74a1870cffc23c96f4b8ff15e58ba676aad1f8a8393387912e8d3a82d3fe77353789ddab678ee13fb13e87d488e597f1719c6bed560eb9bd33cbdbf4181c74e05f1e6299c1ca4b579f45b25a3c1966c6b8aa61866376d10cd2c935d8238b5d3579d9e7588f9a237cbbe355ad9cc99a1b68498fe34a5437462a1e9ba33747516a863ce265b51d38f30741cca888d7c7e3abe28041be10dc7716789eb5d9ba89f221b70037fdbadccecab83db3b455f3c57fb29948ba9151b2d895b6eb11d5886c3e8a6add8c90e31f9b11e0f57bad879132a47b264c3b0060fa265905087c93eeb28b026af2ad86007cfca3711f2f247138040053abbbe33f40800751b23dd04ff18ec4b123b57fda4ccc10d508b07c5ca69a09730ceb2eaed0579e138943c9a476bd8af9a1fb2bf4262b00d70209b5b5ac521f92f5da5f0723b728ac538a65db3f3ef5fe22465625645e81c5301346b779c97a827b1edcdf8548ef87fbeb4c50fed47dd7a2b1373f18d32046ca202007dd1a5d040b0aec8fb8e269a84b2527d0c320f46a6d2b088e9b1f37dcffa140d1f2d4fcfafcb0c380807f894852de913c94f12bbd2ebcabd02b5dc7ee0d5d9a7c8c97965638ae37055fcbd7b2f40a5f2088e88cfa6b0f34a105e4d8a2414f251bc0ab8de4d753d76e8eccd1b0caba70296138f785802752e31e47f4c0b1b099f67eacbf0a346717e7a0d322689979a3c586aae59a8d7fc33825dc02100939e27c6b0978c12035830905bc853bb61107267ff62f376b69b27bb41e82dc81395b4e1fe50faacb2cedd57b5f6c9e5072d50a8b21706f429dc5d2b21955584834147c15b995c9c2fef8027c7cc751fac18f216f477b5cb03ea0ee3b4701add6111596f9df85c8ac60318b9e830a260fc1e89ab57841a00d17c67fc2a72c115392710aa895bf1ba4fe4b1ea513a4ef3161d66cc79171235f9464409e7e2a48990e02c9d163dbc4c770f8e43283fe064e4c0c0c0c1950d22dfcea2fdc11109406c6ab04bdef6287dd192274ec2f38f6f7f56e2156c7902b0804d8eef44b8add4772039bfb0fa8f60d1da3d6eeae943e75ebb7aee019788bfd99b2a16b28bb97e226caadec1163ce5cfe642d2c5d0cb2e3d4ccc518d9510c2f69856275cd8d677d33d2c4edd615b8006ce9f3e9d76fd6eb308ff206c75365f4f6827b89f4908f2ccddb96e9a97fab277d3b20256d9498eddb61ed6d9a5c62efb01da6f49d376724d692aadeb2cca5fafb663c8c4999dacdeeadff9fba1a1bb83a7befc090dc84c73bbfe32245834d6ffb596a36f06156d10c569938aee7eb0e6571e7154ae3bed6918c5e2ce3e55e7046db632a03de07e8aa445a83267486f0f1ddc55ac34f8cbfa2c0f499449f72f00b0f313da920fc040367446709308274044dd7f971ff955c81d1b88d1bca730058f2b705000621bae6d5c6904bc0bc39feddd982c4474a15060c640d13fe1c35606bb1db8fed70b376caa9a11e471dc5da307acd3ae70874c8c65f373d96811bdd17dcaa0443d0a1e31cc33eb173c6496df708dded12ca32c77de250d5b49a9dca8438918b98052cb7a23e3182e0d4a5ec8e5e9b986cd87a96d5caa23a82af89e3b10b0aa0be239ae88af3735f567e00bc7d7b8c7bf99b55635c3fd9d14474382a5fb678ab53febc88be98aecfa89e19eaa0b297553954a476577d8351c4db5d694996196ef04903b2911d04ace69e8b980827b33c6a08ae6dcc17d2e7df2341912e9599ab6dadf907c5b2086d2c1e7257367896f77458c089f70ac1ab557813b517590a7120c630c7354976a9e124c596996bc42bc87167cfe2db5d9cb58ce1ef68ba6e15275550d408dc86e063c52d4cbf989ea52572cd20e8dcc11e2d8d061f4ec15d23be792f9aa52b297cba7e3d2768422e11950c9bcf69ac0fd197498243481dd4c6938e9d10872a7fd76cfaa3653de7acec1c361c18f1f2143b37696103ad300ac864295ced055613c47bb3f59d3eb4cae3f24578d0ca3fad9eb96d67b04860af789e8fd9321f02ede971463f28901385e8fc02e91beef01ac1f91a83f29e98e0aa5297e06124e2c5b82db23700f3cd359f1391eca018942dfa6543d2a8b866b609d36d7675482da546fed27b442a1ebc8005e33e1be7e4b15b398e28eeabd1e89642f4ad1165a9686141d74fdcb8936edc4b3fe698baf56e241a8e46671094c71379f743f4ef3a3af8bf70179c2653aa59bea62b679df5dca81ea4d48d13d0cc7b81d62837830ad9d5364b50d3a91328398581b6abc2662e42576b0f08d7e1cf7dc5145d122a0c3ba7fcd129c6a695abba7817d1a4bd8e4c361f978569d17f4a7fa5ee66ee0fc290b06e7acc5f8dc6a639341531fb7323eb4a098cc98c92492b57383c975b9b05eb403edae6b0c045e8c15fb3b5e79644d83060d0a153e6cb4aa2b6da8e0cdf4cc1e9bac86459fbfd6567111af0f281c7b37d223321e919ba6f63dce692c11c2d53797e52fd046f6d54fe15a1e33d1d0a9b4c9ea02e04550db18d4af4a794d1436b500c87fbc87e2f3c686a36d4a546a045ac32507476ecbb114200d8fa79ff0e62b78fdd1abea0a98e7f1c05eceec6837577980e78ef3deb6b60d76f075428cece639a0960d1a11a2d570c720d8305056526b9c01ee2aed095e0421a26d4dbdf2153c5bbcb75184d16b7e05df9124927dd9dd7182d35f3b404914c7238e11a5f9b2fe3c4ed64720d2c473bb0bce36d9515545cc7c87438de802f8b755ef2965e5c971007e7758691511d03dbae2c9cc05aa7a6d723e356a69523d43a2bfbb4114c3e893cea7d9735dad7aae11baf478eae444f7af927bb789d60eec4648109b264d4cc89b76b27658257ee11a50013823e1b1e399f4046cd541d2479527337f5ae0e790a324566ee773c521292a07960d20287695887974ce776dbeb0d1457dc595d0dc78d32ac64bc25d973b770ee55d25ae52d1c76ba8d646db277b95f8a6048062acfec9bc368c573820c5fba9c7f8c3f9ad81f2762e3eaa9c1a3bc84f03fb7e98f0746d131d147f997609c86f9b1004afac00072a15ac93435f43d1b458a24de5302a095a9ae8cb3b6448a9f69d7967a1ad56c75bf987ad40a96d7cc866a72e71684ef324296a2b8f6840b932fbdc3ae10c0e9323caf9c369841521a59f4ccecbfdcd53cc6188894f9184e6d9713ecefed60ad1f85bdbf4efb77655faad7530f171e05578c1798a07481d0fbf55241014c32d125d0a16c93e75a5a6e9da6b28ac9508562dc660ca954ab82663131c37268dbe5610fa6f0216fbe2fcd5cf9cd118739b5484c2cc69b5ee4482b802276ac04f195760b09b5b86944e1e3de5656cdcc96491770f6486515c481b0ba4e8cccb2f095427de71a574b1c47dde6c00e0278b9209be9c19529693322d166843adfabdd9833456294cb4aa3a76925368e99f8d752e76e8abb757ed6e38fbe8deb22fc4d9d93a51607d11b68ef4c9f6a2f027839610dfbcbf2cca9fb58ffe0828060586f70c6b964be05c09be9e262a364fe890165a7e6a3758fda404c388f88a4b4c927417dd4a658737915995ee1e2ff8379837107508591cd8e380cc9250acc2a313598a3f3615f9f027a8a1487a85689bed257b5bcabd02e2ffcdd55c21f26511ba5c96259ebf83770fbf6428abb0e703e6a64a57a06de3b8c8adf7e7ff9c29f3b43fe4442fddc35cbcc43164f430c9642c273052ced775ddbae400c3b2aea84f98d8584a73940e70c0bd944c6e5b99a1a89f9d993e6fbc5e05b6923ea8512aa947ff408a2f2d6634a9aa0d1877384c5e6f981620f413e2c2b3a83e93b360598399552293937d1e32f44aa29264ca92fa549d621b12b59f72a9bf1f6640f35295d5203f189fe5ff6a4bca6693a85a7cfda150160000d85c3de28596a2ba1de7c9beeaa9c29e7790e98c509ad2bdc89429a68dc62272e36cf89ce97dc8c36c8d8bf0b7b7a6e29936d1e98a0cb8f19482661688d9bf9973286b7a3a657e38a40bc8e11f4dcd57093d41f65106bef3f155ab63f5bbd5257cbcd2656289d74dfe1711f84a4ba57ddfbd26942735119ed9761ef0c72bf5ad1d6b4a7c388adc8e33056e7e59ab8db8939c08fede6f81901b0d65e72f9c119f3ca1374eb38350f247a42f95ac09f57d32f708464b339d45c2b1a62ae78765d0fb05c7c9234e59c772b2482e3664c4d466b4eb3a3d9bee04123fd02aac971bfdbfb980e9132c78a85befadc61a2a0c6c25050089f8befb69fd22eef553a4213c1cfa01bdfc6e18db43201eaee969b6197c771980beb17053f774eacdf295ddbd1e984e772f831f1ab1abf1167b62023919976057086cbbf9ff9fac57940053bf1a8790dbbd6bc88972bbe70e37e4d1c5845fcc3fe7654499ac99b4d88d9073097d49e3a9b2226943dfd31f1ec67aab6501d61cb8ca44717f93f01338a004c79f5eaf96aca1906f138e64cc435618385b82bfd48bf05a59911be21a8d544e72df50bb898d4cb207a1d162ca9cb6f2b285acff0a9a1df4cf095eb97c19df55fb13fde239933c645e431ee89f5df0b8a0d66c4180cb3533f3948f33438796af23ab9e588a9d6bd0023ab77875c036db146dc91650d7e4c265d56d2d8603bbef9ff8df401e52276d159449fc2a7de0d94ee7da1b36eff5888ed129b0c20c88f4a0b417eb4af17f3bc93bfbfad6190dba47d58bd7e2be2176cab11b1f9fd5fdbb27c8a79daa1f441e579d9e7421d12d4fef984762ec4130eccba607558b8e41e19f3823128dcf54ae29f1cefe28b6c7f5c2148b938ab883029304b5b53762be9ed93225b565b062bace605c3c970f25dc7be2d0b60a7bdf7ee88f918e1de06380f94e5d47f92178873499a4d0edb845efbfb78dca95fe34308771ad69c9a0ca238537082e8e770fb2bbd3f1f6c3aebef7a72ea15b5424ca83b030b599b4a82b6fad70ef735d8d146724174945d9136dd2b048f5bd2d4404afb424ad0b32f6b5d09cf7131b0379433be79de1f7ed87ba6b4a1b2729ed0cffe0ac945e4cf6cbdb113e2808c058a66262a22065655f2e32f196a22e4a230eae7e123784906aa25463380bc4b1995680ac1055bbc6c5b90fb59a98199293fef7ed00c0a788fb5458aac9f65d7b334d9f22985801e4e500a8ba7ecf05b24160319cd854d0a68462124d5b812ed4ae9bf9ead658d5b57f79901bc74d3a0026fa8eadda4862f62842f828ba8e1885e97b6c97a2f8171d5e639c757d4b406a1441a1cd721d52ab12aa47c8b80b330327f03a84cc2968214950114685c951e974ce98bb883d7a4350768bcea7b72c82e5b9f13e3c2953ea03d9abb5acfd227c046f51f2a11613e1d29aabaa07bc1fa18f0097a434d97cc21fc4d80ee0af6928b087d6dda4099a3cf4bdc8ff0461fc53c4a1d11a12b34998b089fd3612bca288f2585e11f712da01b4fe1926b884d48ad62f55641a0ca09f5c496a53d2f1b6bded06cd4b44fcea954f351a426c7038636b81adb503a424be011b8ebc0846f3307f86dcf4a81761a01d981c342aa9250576f669d15ed5f79bd99cb7fdd875538a8fa995c58009ac037ac2c61ed5cd5315fb8d26398c4bb97687d81b2787a1cb735834d809ce557f04dd6d671f694243160a71ec6c20ff314ebfb062cac9562bf120523190de0fe9f9d84acc1fa8510622d69f9c676b28fe67797111261638aed3d0e1fe2b110f6c1cd7452d4182f7114da3577c21e6def40c44906c48af63aba0f4aa42be209ff55a336048537e406826439a1ae020dce1a4a9615fa6a941201029b61ec5c23d0a34ec34fdfb87249a073ed9eb9c0222331e0da1b7a539a353a28727dfe8074482b225cb171851a3ba8ac65ecf67aca87b487368acf63397441d2b06d14741e9b191df665aafd0232000521e8be954f57ef9cb19b09b81719a761c0d5124992509e05d4d02984564cb806034cf1fac12c170da94fb985714ccfeaf23b3cf2dd5027b80721ab258fe936b38803f5014108f8dd3f752bf07e1b7719ae358ae3f39e672b6a7a721223d96baa60f1bc91858d6d19bbf25ee39175db80fc79323e0fe11220938675c5874869670089fef72a5b427065e4ca98b5d51ae77d3e302eb84bcd3c57624650c4348663339b57c6730bead0540ee61bc66217dad0d55d86894a452359a385cdcf21c6c21942b818d59eee1c0c828a25f0a6bb4a685f80712163f8693321b04014a7bf27c24590ebec38c8caa634b1c43efc37d962da6d9c69492b1c544f652e200d4aabb9160ab30b28e24270993f44832a5995a78d721a16a2f7d7c160f65090fbfbf76736f5703b401ed501377ef6c65dde60d2c3abe0dce3146b95d05546ed7ba0f1f554911a55a08192cf04e91a53d8890545567d1686acc35c8a0bf4771fe8fb48e43d7e7f97c18b80ea18becbfe2aac2a117b62003d99fb03f43e3bbf620ac1f72684b6d2419e329bf3cdd8c22351ecdea40b5fde0f3513a4fc5b0683556a6466833a89a1edc6717f7dace6113871e1cb05fc94697c1affa9cbcc4570d80899b4e7f1bbbd413a3da1e0cfc4ecb54f8a36ebb76d59348ef74d19253a0c62369ab362c96424931d127848ba5db4fad09c81867a2f1020b029af3410ceb3f292a37147bb712bbfadd3d7c0b3273686f25d18ed98e0a11236107e8e9c73bdb8d6779b9ddcebc229ac3b9e76a40d125c2e002b6b56ebbe6a07f1a3e6c618fa72798a89be6156a870686dc14a480458b7dbb32899face6d1ca509c3e897ed05b5295be6d6dfd63b63206325b360699adcd42665b0c464d4bf4f951d15261136e9f9d6c421af0b742d0b60806f1e5e9c1aebb2be6c4f91d71b509949a9527963279c6b41c9ef901aa4048706cd0720857465106c5131df299db2f95318a50a19c7a9627e8de03cac5b6101040472745d50a3ebc3276bffb2fbf97fec16184640112c0600015608c48b3bced1b6f51b06ace8d471da5dbe43ff10fbc2e55908e4d0db558c3a1e387118feeac2ae4615f66e3582be3a5179e7e8a1cc9cf9036db8af5afd0c29b3335740eba29a933fb68dd10858c7fed7c82590553f3a2985155e98171ccec033675a5516875deaf13e9506c672bb28b9c74c678309a02ad66515c0b81394709d6585238046fc0e84192eb279d239c0b341e8bb9121f1b4691e690fc75116227bd0b37f03d69548a3a4f907f6dcf09f8cbb93a9e2ffb52e83c079562a61c50b8e93f48e0135bb30ac6c85384c82524cb2ab55fb813e590c975a216054c6e353224feb9a922c4236eeeea086749398262ee248f1c342354f21f376688e7a24335c36552a63c15f88efbc3e66aa88d40a9beef9ab13bad026df42451d5434a67864a7d0e7f938e7112d93a28e823e21496a92654664fd679e152c34d50f088e0e2aaf19371dbfcd8bce2dd34f3789b6ac762c375eadc9193cdffe9103f324b26ff03431112c28d12c33652ea719b55b6644e2d7a04cade2038875a3ad2ec9397da9183de46adf046b0268be67dfca95136d89244344b1cc34a70aa4e17ae11430b1af5f84a2601c834dcf569adbc4429526138632b5f87e6f6d0479792f0715f8abb7dce95aa8182bfaef7337d514c444c9c036b13e4d232098ba5737af6c57c1bec4e31097b925c3a98607ad6a1a9b8994780b3bcbedd925745d21587db26e27f2e1fb420262e790dde4c649a6c220fa750addf527bb6408413bdf9034563abfb90d07fb70e88418e5018c46bc62a65494cd766bc3d44b9dbec275219b0eee23aab3777c94de13d8ad4ee7be1b0d6a05d43a23a362c155aa4005ccce2f957fd5e67d271edb913ad01e17bb5100efa7f1e2663009d2376f22704b2ceb58e542b8dc8e083f403659059894f56a4a5c65a61ee0bae948ab11686e32b37f2bf4fa0ce2451a9f6411c55838a7610cb0d64a3f72dac82b37f9c74b0aa3a648bb2ab92b1493651d73a61ca9ac15487da0d49cb204bd614e4fd4da9fb7821e93956483c2e5226ad078509ff090b5e263d372c50ba68a82038b14a4af24d26146c4a5955352ac97c97d7cc45f7fc9cc64192894bb246e89c5a02950c5f95a010d079781cea5ee5f5ddee76b1acf0ae4cadb781fe172945958945600e14646e615933ab54a791050b1e8f5d4f6c13f23b76eb0dd059fea5e130f3e0ca33a5816f6ce00edee062ec7f31eff6961b74e2c8c8d18ec875613503da103afee8a73652e9b449dd1c880e0720cda8de7463405a33ce7a21102a345e63e6ed24ae52315de21f65cd4064254f2951604b2f6a12261c3b34e8f851546c08e00b89781e8bf2d5f8d3a363c835226067dc5a7caea2caf6ba1b7d63b387ad2237cd560ed4ef73118d48101eafe16aacd87275f8f72341ec4e348e4e45f37c7dafb5c87d0568d31ddb66de5aaf9abf5a517fb80e3f9cfb015ba506417aa1b0e82f45076359cd4872e83f41462893230aacb490699930a80c78c3c5c475ab710d8d12a33406c830973d77068d354e2aa303c7dc0c0abec7622e9899cf42754efb9033377dcbc1cd673bebe41884d9adbd6ad891529a7c2e298c15288710d4043fb473ada31167a094a60a294c21345ff9b8bbda2569e8911efc8edcddf560fda5987754bf1b9f744ddb1b2ced127f606e063bcc6029dc1aa4b52804cb8e6b00da3608867d20b81b73a2b178a501f1d90639945543c2ac740da20fb239e535adac258b998d4c58103a90fd91075636e11c09f1586a9879a42c9cd3c111b942f409a43161326df653f574371be44b699f67d3375b29464fa69514835432078fb6dc3e5fd47169dc155bea8e961db4f7ae20ca653d73beae6cdadde380667daa635d825f1d423e9d997afbb82f11368be4df74778969bef0b33da486afd420ae99f564a44e311aebbb91a2cb126312624a47a11a009f7995f33a7723f53159ebe6a63936c33f20ac57647d90aef4bcc17736ca8588c66dbe97a65dfce71911e8b1afc2d9688a91c4057bdbade38e6f99e5c943edfb6a6a961e92c6c983a7944fd24cf5d1db79a817258ff2fd0515475f7a1637e0c0d32793764bca2c47314276da284e9efecdfdbab391323b5d65c81a5e8207ced5a8d5c75709e18ef055ba13be7fd0f54f458d134ebe2b6f18b4b618a8f4fbbc705eaac280bdf336318bda3e2a6179036fb4447a2ebef05e04499fc9880e28b774e700171643976bc1ac29ea9129b9241636e1f06c5af9eb4ccf6a7d59e37975719f67e22d4f9c910a67294dc8178ecb38348f168d3847fb4e0333c5fc6a29a632516512f2f1cdc4c179068493f8763b7a267a7a3f12d5124e83b2debd3535e8ede0fb68d63fbc01a26d5cc986368378bdae29565f5ad73b16f702c3c077d9b274ba841f85358e95bfb197b6b8c4fb6a91d19f0875bc881a647db237ed50e0d00d44d9082c3010dea9531d82b33bc5f066bfcf592af3834b74e57a07daa5d51de1ab8fbe11259e7092bba1ae1f43fdceacdcb03b3e309f0209fa18fde1df5451b297557e5f60389036d23d4c405ca1ae4e5e65057bda3aa301bbaec8bf910b12365afaa1665840c2cfc21e9cfd853b1c6a0161c607083f40108128929afe52cd43d8a6068805217f52255080876bacc245827add16ab55619a4f30de44d2431be8cd1c466de51a3be901bf5fbd1422f6f51d2b9a0b624907807c181fb9740a39d158aa8a0fec7897a34eb9034d46665e4119243b6c402d695e4692f91a493e122327c14a82910c1a26daae1e55bb9f8d7fc04ea00610a1a67fb624c97ae988c9b54f691eeea70987272ca22c828712b35421b31793e743edefa8ee3a3811a74ad8df745bdcdc0ea16776f7691f11055dc01c242fda068858624c967497462056549b8d9fd89264df5c6d2dc425fe5d229eb16fa1d266e2b3f211e8ce335b8a1f9d87841764bd428195ac4b5c0bb5ee74be14339fa4dd72afdef36acf3571dd0af0cbac15f41c7fa5b177d2bcc67e9fe06d15dbd0b9d08501e671a49ad487294ae7d423dd9f5b8ba3dd178c3e4669877601b0803a6ddb6eb17c92bf0c846e9c486804d2fd9427ebc416da95347eaead7a5c07f333eddaa43601a7065baf4d31cc28789eae3ebb5c6c312f68d7207b42842494b20c08e28c1088d7c910d4ce4858d1c6a503d32885ac69c1f290193a7f9bbfe7c33ff178071db42901fdef070d575ba5607f50867b92494ca5a2767dd3ab3863a0062b102692b438c34d9d070aa7238d25964a9da5bea756c93956c4a69f756dbbabbe9e5fd80654a82c1769e0e62d7e177e95d38f1e660a5a80dee9a443a6f699561654618e1b2edccacae1e00bc4bc3528c442cc409c967f920a2d45761d11523b817a605851cc5e301e6d23d33800ecceb70523719aa9bbd8eaaec031100adb471be4c025af9caca99aad70e0aecb91a7cadf7470de9ad0697d7e44674ad7e974fe55fda7609dee93cea354c8066260198a55a820de1af04cfd7f7d2d5d20dd0addf098eb857b29667b095e50e3567ac19a614a44533c9d339c485946b3df8085f81051eb1b6b76c99ec996568f2e95a565e7945c045c160b7eb3e1fdf140d40a86888044183c4524032334ebeccc5ed850fdeb493a16541b2af28b293d7d3fe8146eeca24ad47fd0c195798b601e95a249633d3e8c0a40759b03584b410fc9cec4e1c779171fb9da3cf96b5ea69f238b53b5c53d28dde0c0af0215827a6f3fa46d9c11238d25257f93d3fa91b8b61852239d58678a21799c3f65d6b5fddd447f8c9b997eb467623e08cbad9681446d4af6882ab5a0c562ae599a854fa044e39ac700dae6d49561ddc301a548a78b07685277f213ed59c7aec486aaeb8b1a49e941ba625e5fed7244f974651adebe00d6dcf15cb2a5742d7950348b0f0d6022c01aa6e9c5bee8b8c222e10bffa0431e5a57fccd7252ccd1ce631b501f5043d593fdeedf080083add2729d452efac7582687e8c4938e74813def79535cf77e2966aef1b184d0930a8aa375a99b6407de0c8085ecbab2280c8b29df9785a73db412c831d382bc5e5ecd44ed8ad2f74b5373112fe1b392622d1cec25331bc4ce403d9a0f3747c39497c941da19ef922ae1379edd5f4e0921762267383e390d14ea0325f378fd07ae50c8d0cc1a5f94f5782a8372a06feec0088e24516cab4fe2ba890cf52285940c113ad38c356da04ead51a4e12f9b18abee7c7d798d4635a1a583497d9fdbed9ae759991de5ef7fbf6be9bed9436448a752c0a0d1c25cca88c20c48955ec38a747aefa7fbc248f746cea579d0692a74e426e3a2c3cc6d973d08249b03b173fccfe2c494539b52cbe4ca53b4c18dd44e9832dc926274ffcfee940985c2d63e53146cc2afd7705830a32199844895abf0b9ab8ba4e40552df377eaac0437efd08e6e4c5d426967c2f815c261e8add11c24777f5803c1ff01c765cbaed4eff20aa055eafd0b7d75d558f9806eaa08d221d7853ed1c66e384f3c1febeb2fdf52050c462bcbc69e6c76bfd1d075e0d39d17cd24f8d075e272d119fb2c530361deb6a6fce52468ab9887468cb9d0fbfd2caa686f3210aecaf0ac1791be96e599089c33119e05649dd04c03bdc56156ec718b9aef1aa27f27fa8efe63e88f152fd2fe96e3212b506344a41c7e84eda9abc0c46a6ab18144ca54777b29e1a5183309452296aaa2c4968d91e9e11ec85dbdbf93bcdf7e88eb2dd3c1509d2eb622a15d8d74b54210dfbe20c64f7fcbeab1951b43650af65892b81cf1c338a976a3b837869d43537eca7f109d0fe08f112931e7d5bd5d4fa106473da9b5788f0b6fd81d3633480cf4345144369eb4255416bbf9770ebe8ca54431e1861463bb740a79fb73f7cefa8232022dfe622df78146d44628ed469c5cda2f58e4e1997b374568eba839edb3ba6a215e2ca20960e76224d6590a847296f27e746a30debc39bcf1c4b5dd7a693dd2ec73bd7a12818d17736f90b6b2c768aec1721db4ca7d008530e4c7d1b6c7342920cdd02fefe2c7eb0b41a3fa8bd3b4b346a3f842ea6706b32d910b36450336073a0ca6dc3e0c4cadc1c0a5df1de906f009c30f30cd536f42e85ded2029dc028cc35bab1f71041fee375910ede67e263af629d37aaa4287612ad8f149b6aa27f88b50e45b5171de0166b1544d95e9b6b1e70a477be99ea7690e8a1ca1bd50197cf09caa700aed69bcf9f6360ce18b476ea44328bb8df0373e908a94452313fd477d2bdef7646b4ee008453b1f83e0f572726fb2592eb2993c5bbeba20f0bb02ee8f4033d4c48d739e7fa009032f7105c8d80810415d42554ee27c27a8238005e4e08a65505a5ce438af8dd46e2e7cb29a126daff1b32c2b67ce73bc5e78b2e10329c7a170abab8fbfa7b47e45d92146d7fd8954094ed709c2c6532eaf532cb6f293d7957597cd6e642641834c355fc3f14b1903d04d0a8a43b799640357d5647ce353d683634c9068cd653f0221530e1900a39bf177d0208a9f829caf4c757b523a19552cb26a1d5d20605ba9e281105ad4880047ff7090a0073d4fd1110bddf8377b283fe7525d0f3cbd0097257f7fd0a91b691f46c873048cd257331874fa2ff8a78d42cdade431d22dffc268d7482a146eff2f1b38343aaa0dae37c88ce6f664cc23055858b90a66c038e63e87d14ca3bf4a3291620a6876cd3b7a4a8ae69ec798d9ea66b4366e1097e63da02c0bf25d3992bd0715fe5deacd33d7c15f4992ec8b3f265162d67afab12e5557a721ddd036b7dd2df17149e1781e236f81b330ae5bedb6c3112a6ccde9fa1807bcd0ded384f0cf563ccb032b45af9f6f56fa65ef8f9aba51fcdbc36007593204a5f1176e4952d3ccd1d5bd0870e90704f71f068b874b8d7295173bc75f38502ddc1ebcdbd0020e8975994d9526c2b4debb2e01ee5db3eabb4c945ab855bcf0f85f550aa22ab5c9c6ca5f94990d07e097724e6543d0332a3f2900a0628427b079e8025afae803983da8303b54be444c7c6367b4c076d61726a05c3bb6d2f10f86081df67e21124f76cc8b7308d66b4f4dc604a6f315a9a256162897389d5739405e4c7fbbc4a4ee6ad656da5f2442ed4aea196ff8f218a70377bf764e477a6fdf18f0eab25a8d73232bf2680ad1f26710e87bd19a2872f3236bd4e109588296e7f113175d7338af3e834a4f247917922cd893800d28e975e890e620a7c15349c48339dc5974be944a6351d96c32c93bfab09d2c04127a41b327756b0350c867fd62783cf973e01d3c4a1c238d2ed7259889848296c82d2bd2dac604e5aed13a799f5c0c45a423c1a4a18a230906eb111983a3f941a3e76cd86e0c19490457dc7dd14a48e01b920b6d8335ce334c9b0f156c970cfd52ca1446e1e69d0f0368daeb17a3d1cde272c3505a2ef7b0d788857f882ad2a92abec9a669b490e42a00b7c8d0bf8387e7126ec81c4951335a4df310f090034c88e33ed767f8d549a6a7a625ea683fb54d1ea085ff13892363d56c42860caa022511456bc779dd49ea4693b0ca9bcd860759e87726dcfde9381d31f0e177b648888a681662345317ccded0bbcca2144d504f1fbbff2ec83bc149ed15f4d7621724893ea11947c0e112f0f1724ae2f2ea609854c9d0292365c9d454b83c61f0d7f02e5a55f09266a581268ccf3b7b6f7fa7d85e8b0b02a18d48f62d6892e18ed4cc7db2996263f6f505048930405dcea0d4208e3dfa3d22d9cd34863234252cdab49592a6c0c7125a5e324cd413a6be2bf65d588656ec03b803fec51cdf8104de0286f7a6da713d6436bc5b17a43842579243ea2be7cd0f8c46ae9f42d2c3984b826011be79502a571b3297d8607dd25e17481c674d5cdd720c9eb2681aa2cabb01a523e4e0c44c7934bb7731d6c7e991e0aa2f60a60db8287aa61d31237753749b3614c5c7a27b28a4c378fceb491f97e0f050fa41b72a7f9ac7d571e423221f839597c17f5b1dfef0555ae206de46636a8334414392669574d63b8744c4d34c2df9b788e6b079f7e1809b0ad006bf9252e56150972b60f65dd36af0b2c722a2944cbf668c19bda6484e1dea459da0e64dc4236354bf4b88abc26438c694f0e878e8317baf37fd8d70c50d309040363ffdba8f6629bad416dabbc8716636bd9c9c55dcf0887149b8e0720f059fc91dab99a76bf13f364d794473e1f22d66912e59abce2e4cfee9446c381d147ec44439298dc07180bee69a1eb68ea199a7cba5336906c39c60298516a9cf8445500c878e8008b167948153e30d8c743b4d681c1aea81d14c4a947a09946a8ece9f1e7dc7b581afc16195de2cb23da423ed5f6e279bbae826a395a30e7ecd0febb4956816685e31adff498b2cda42f83a6ec7dd5c74613a7b832ad15a546354d7c21e2bfa352fa7354b954b53e063fe344d0bdf80bd48c6524ac2fc0f21bd496a27f90b3bad6a65ff4af38a002d27c65c0e1511c71ea2ac25c65ed13a1724129094e390d893b90da677aa700b553269a1cb0a41a62e3a1dedeaab2ac13a4e1e8debdc145d32d47f85ccf31409b9205dd57f350d1add17d63b8b9daab44bc50383c3e4037830ab16718ed7f418129e6506fda0185f5b7e3a68bc205a1f7ac3367d7bbe253ef11d7f79659828543b389ba007e9c33f1928a65a5a8664a8674a8cabf204b180030c677f564bf612b9eb0015e22e3eca93f823e7dadc90b71d7fe9cb3ceaf12adaf02e92937f9bbb4e839893143248ea8c74f8311b5f40869c0982c7a8ce517cbcce7c1a391b4da17fe75e6510fb53c6cc7e4f10ef2500f74bb9492204e328a2ea2dcaea3bc6bd95affaf5074bd32e12e1f2afea15b4e1583841fb77630cdae7456d68a76f05a350109baf4c8fa28bea8b56b6fe58e4bba0c10ad1327222818493b3e9bfd84e14bd3d4d11547730121dd11a8f8952edc5bcbb5428f4c9419c0c877fe3e06133f5f109af80f6f6081825c5d69ac82b9dc5b7ad307f8ae9902f65d92bdc10a2564ae8cff466c84f51e6841f0b43b8ebc9db7e057871950e9ea32295743b360389ed23682d0208fca423c60be7198f859ce99b4247da37d671003e1a1c17082ac68007c050e383615b46ccddc9d531cea062d337bc6807dadc449fde27be03627613ccc456b7a72fce84f4bd9f76bcb7c36686c5c9aca2b03bbb295d673f6ef808f9f694d9721263c061a7db09d1e4df3e5ca209e199a9e1144b50e99b757776589e543c4548dc7c0b0f93888d251ce2b631e29cd2e9b69e875b30095a639532ac1cda7b71b014f044930fab6874d04d74876e77dc18ed18c1d06d6a889b27325cd5f3755933228c81e9e85bf396504e083ba0ad9a2c4c661ec47002bc58ed0ed67ad24f161f57b0d2f28b69f1eb79a07603508363b97e50a86574dc06f260b5537cd5361323ab2d0bdd980516e39e778a845a9c16c9987b09930b1b47806d3b1d78547f16cf966935445088f92b01e3aefd0103b22f88650390fa84bbae4cd87fff89a3c1198703b93b92734855440841c4a1ae3a3878a22e80d3dd00a5dd0117a47276854e8122fff453f3b54f2bc775f13de2fe6edfdb4569bfd80824fcc2fcc45bd1609ccaaaf3a8278def32fb6cb39d17edc03a518cdbfa70a3375f807fd5f49247a50baa3ad52cb2c3d2e71eecb33142f85713b4475d7b36af94f00db2297279123d653ba051e239fed79572833f2889858add68ba7a03feaa7331ba0401bd92e6b8afaac4561b669356a173f25781779496352334579027afd3da35264b23be630c564cc0d6dbf9aee0c8d61efeb96a970bbc5d827cdc8b6eb03f7d117354f1567c31384705f5c6da5bda081b518f4ca93ca116c1b6ab92f60575a2b5dd36e22f97d26b1fdaa35f5af1b21087dd100cba7f54688ff5714f4ecf1946a3f1c20e39c58848ab72ed3b1c057f209df42809b9a63cc55ad8300e5860c639b31ac84e68b418042f02da04076181be714e92f2243f6562a3c10a0cc7f024070fe51c930cfbc336b8acbe4da0f63b157d9265b9b04b6f170bec1591ef312469d1874f9b69783ca7526be60eccd17c4d0763c9ee003a3ed93e52bd2919b8986ef2bc3da06f5587bbf2753ca8ac967ad22d2eea788b9cdcafdac575d6a4936491ab25fc1cee4817c81c13116ec4c293dde6718c254fb9ec5de099366e377757b0a86b9dcf435723359a34f991baa9f129efdddf0cbafa189c94b9af8be8c0816682cc8042d8203853b6c58f3433cec70cee48999115f431f0186520947c05646f66e2637fa3ec67145b844dfcef2d24d8098c43a051200409ddbe85efa483685a1c891969a94c05850149e40f9e73e62f824b84247d2650e0b23171cd9eefd206b2638ab193e96e85caba7bd9022061584e1c65a4d3e5040c3f86e6679731be02ec5e09b3baaaa5548f69d41a1fc2dac6a796524a22022ba382e2bbcc9234cfb53941471d29b68a954e435e165c58b2adee2039a9ce49d1c930318afc997d7e5e6d212ec8382c9b2fd2cf37a5af86727d1d3ac3c34147223743ea0602a09ad21e6fad18a44151e3d3c641ea92acfc02b86f7504676a8257f2d6e04d28a4b3d3b04824338ca84223ab864f3dbdf1c82a47e8f901b6016b048976e5d5d61b205ce6a10af1fe8115c8e60386df94f6884916b16c062e09d446d64e436b172bb0f9691bba581fd5be0979223235893b3e52312cf9b69e223a3bb15fce4db18e041554357c85df6e3dac8a3eef3557c84cda62ce444596af390b637e319d3a2e6a395061b73f9fccdb342b4da38bc86d871a40c6ed524d9e6ab26fee8920c243ecf06644aae0b120a61e82a4ed269784ffef3da4dc1e99aee496e67960821d68de582a1d9309a500eb191f7551567453653b9401799c9fb9308a158481d072228b0e36533c77b4512b03fdb5feef21a70ca735eaf41a688219f0d9d90aa90207ac754a5c0106d52fd1eb738fdb02c48930443d77d5584ee8824e586cc9488c879c46225442d5ca714d15de1a6ea0efd41ec5c31949999861509ec2b65404578d5778601c98b00a797deb857ac3eb7c90e005ddddf9d7c7128548c6d0834f6638eb773c38bfa022f921c042b3777400e28d155251922817fa3237b2bf1d3032988b414fe0030ab18421cfbda609c67f8e5061d65ee57bc0d2affc8eb5e1d6f1bc80a228e946a75f65df83d40cfd0718d7895b258fe6fe4f2a65c7c5b8934929a8ccebbbf1345896966100ec651e4b58cf032750cb71e4d51e89ca9678fc88dd73dd3fecde17c0f8e8d19eef814e4f96644708058422e7c2f11f649000b374cd324f5119bfddf46489e498c9b0f5336b9f0b59f47765f10a4c417ecef674813b0d5f500c857500b0b1beaeac21317ff6c8502e61695f207ef55c9b7e96efcc9a18fba5c281306e8257c007aa9197d18ff6af4e18eb9b385112729e9ba7ae3bfe67956581d4d9d90ddaa60aa8406c7105fb6670c2adc92658cc87c467bb5f6994c0bb6995a82777c47578c19a29c13cfa480a92a2bc6797142c895618501a045ea793f72b1a373b66cec398ac919881c8a92c35910dccbcea9839e257b639434814e5af7f9a30b3bee8a034e0f23d1a52ff9233d313befa8a7b0d3370cad7b1a97b4c4359075a96376e4bd1e6b4c08e0af56f39f07fbc6e363424499ce5f4773f48b16c8661e6a0bb9695f03976988826c21d1cae391cafb1abad3c10b1bfe2afbe5ea81a461df08d1e3e8e24b997df095e49b4bfdddedb5887ae35c39387b5f29eb22210c5fdc45dbeb57e7d3ac005225790f7c164b9b8dd38c804c88a07fd34ed18f8c4844468cae9c4ea405f74a6b374d1467f75df0368ad0e5a261c7b13242e916f738a206bc0f838bb6c783061895f178c40bf0033bcada442cf94984ffa9432b403f68315312f3bc82e08f97390c4c7546d3725659e332d32013c5b6a421a79b0f7a9b62da8c150652e4a04c98d0df2fe1821348b9c98af1c4145abbd240cb98dddbfeac6685cf4c1cfaf024b64a6f8a98e63588b2ce0de069592524b246ca94e4c3dc7e2ec3325d0c7a876748f0f0a07a52a37df1a9a125b5b5c30395df1d223c0cecd8cec6b251884199198aa9025c6392d0f7de928ae473b96dfdd52252ae57ce1b5dadc37e15772618c18d6f60137e050de9d184a7092fb1a2b4dfe558f2c70458c948dacfdcea2865fb0e3f143fca9624995599b1ee736dcf6e271a90028ac3de8132b2e789f84f6f16c7d724055e5dd3ef0eff6b5652956656a6284e60be6457ee54a5047ca8a993eaf8b44da9caec7a8fd91b123417e7c193a6b6c8827188b30065d0548299c7765381219880c9193017577cb2232e28c2cb46772359f2cbc3ff8bf4fe5f06e75203a84157ec0a01b3ba29b405cec0d43c552d758fc5c414f3c5fff738cb691ab62d634e4a4df373ef72c71effb457af3b4937fe4578e6bdc3d1c40b1a18591fd1ec72c890920a25d54605cd1337d52080dbaecfb9bf66d172818046653c9f619af5502cfc1310f9e964b6ea1d797e3cfd38011242d5e3413933b2f5ca6eb787d6375f47ecd3822c45519c6d9541024910902411591960a35c878e31466fbf9940cb2711ca8a4801be07c58e76d913b79588a9f54624cd9357fb05a982fff592121318b3541a5d7b43de4b1f0c754305b9ad2227538b230e495749703be3ba5d3dec2e01663ef473fe792b39fe51e09a384d592de325cbd6196eb17672409874fd0cd81e869754cf07cf1ba527f0ed557153b74cc91445fd986840fa501821198fb9f87e9a9a9a1bde9d7159c940990ad69dff591d2b83046d3f9561b2c27550354c4f97ee002fa6f58e4e1e2f5f64916042aec1be6d84ad2997e138f3a6a2845d223c8237fa439841312815c8fadd9341d64af8c3bdf3b6043c8a3783b0fb5a875ef62993c61676a5729a4eae26ca306606d960de43994ed71aa67a61a8fef90f6651cecd7406a7480fb061e2b7a5f820cf49c4808b591bed4fe2788a2f421fc181d2f3b0eedb7e3ea98e29cfc591fce6de191842a114d31dd8869cf8f111d37f7d03ea27adfc46abcfa8243bd399aee2e8a8a88a63714933d9da5d7746bc8d0d12453401a2aceedce62a0e08e4de4c3e5063811fe43552982728de94218e5048e5ca0a9f91ddd6b25f140e731a957bcba77114a401d305b5b0f5787d7311071acda38edf3fc1420a57a183a1fa5d02b94cabd6ef2e10c4d4545e675f7117850a00dee6ecf10813350bdcf0c6054d6d36684e23ce2fa87f400bf6671dc83062d1db76cf64d9985d4be759dc589eaed94bff49aef4d694955ae570a501a55b093cf6e19b6c2b4ec850c05c5183445e5a1c54fcbbff4ddd39883af50417ea1df340c9fc7041e0627bafd8a45ad5a2140f78a36acbc0b111eeab16ac281ee60969db6c58cb0b50090209015a086d21736b5ce4d40b0aa2e634d8d181d1d812d120c6efdc767f5bdf74e1e6fead270b4dd70d20fff18d71f555963de9778fc18b2f37315723e737eb66ed676631722719888946d1ba457144639d357592b8524f5e58d644246f28747173c511a33ba60d49603639c11b3d62245382ce918c93bf7e10e58d813d812e1464e49e6504bf1c7f21324f4b212f983007478ffc1ad27c6420acfefdda931bf21b08aae735a957620e130b8b47d5cea2db96c40305e80e765db0426ec2b5e1810da1354dd61b461a5fbfa6fab164caf9047c84746b2d19b1f35d093b33312bee29fdf65ae5c6d6add64c9149bd316ccc538d450234d55b480a7e5c632ba93513211274fcd7315b377be4bdb4ac05bbed9a507d817e7d85c56d61c7e80e8cf8866352efdb3c2f1bc506fd88adc344a8ac410d67e994dfbbc95b22baf118f3426774c9950f8b350e1df9e144545108ca044f272339b4ed4f0d4128b207486e69197a332d37c827090541de65995c9f7c035c18b539054d0af015f3ab1e9e488d865d94ce3cb950a8eec2a509ef10ab5715b17dbb79963e811cc408dbf92674b1e3d10dbe56e4d97d178ca9749d5513b993a044e41488173ad5febaf2b6c1c205709b0827e9dc267e3f3bdafdde5b2e90314ee44182cb544b4bd74f186daae015e06924d4904a0a4925ef79c4c7a9f50f35847f0a9990418df7154e81d50b6320d0bfa47d2edcd504622e7df5e200eeec413ffc650b7e03c72b7b37d5404af2750a6c35453400152b05c772c420a0cf6ae94daf00903bab261cacde76780183bf1ac22cda52f74615a30bed061dc8f67bad9c60fe53ab13279d8800b6810a8ba61995d7cf5ba5e60053d302ce54a03d84e09b686618fe9062b2746d209a3d18fe2d4bea34757b9427d00c3ebc29070f5a93a652e6a5eb33ca67fca2faea1cc912a0be6b0977a3371a1bd597199c6cd03d02ea31293277fdbe7387dbc315f2c96e1521647c8558bb491cec0bc2942cf53bd9d38855e28a520828a46bc41bc6a580268d92ce02b5090c25106cd5395756b14318d01b8e36ac07d47d9313e12e6d1cdcdb2aa51ff4eb46a0f1ade77e29698c67c10e0d2415ceef89d162aa2c8204f262be2d0d665d404fb9ac307e85756581fbfc4d46ee76ab7c4b2728976adf064875c7dfc39d5f1374b01b8347d558872f5d57d9c83844b9fd7c022132f733e5cca8114796f4f961bc844c7d0ee1d1a6bc492ba0fc8539ba919c746dd4ed0edb9c57e2260d5cf8e6e2568cb705ef5fe2e9b307fe81260e0a04813f591826ce128a7184cf398422ec0096b33dc50282cd0c72d832b8f5255aa156c798809b56e920ef81a82d692af7ea1d00c4c9d54cbd5e35c866d026c0ee87cba2926e2ca2e14b352e2e535d9d7603d93a4cd8265965e19ced2c99949076ae02f29982ee35c6128138658811ea28a079b017e485362f6228a107a39b89234eac74a590a9d988a20c5a1634fa4da99cc28bcf030de99f8a43057d35d703eacaaa26b93f9fc1d44a4d329b115d9367e9ea76397e1a3b8f6a68f390aff449d0b81c08d60e6c210c46c7d7fddc29655ade11e92f1524c10d967cdc0efc412c989131de9dd6a1ca79e4309a19934887ab75fd4989b690e7baf7144fbe2ec19478558e7bd895660a8c0bc58139d11d5181e7c312a96ac3aed3dc77f604101ebab94986de21e0d1a7e7174380256329c09ac615f73bf29da0c2a26c13114c16be7553d1d63dcdb119f4f6c2e15b4b0004b34adc05bc5bbc844cbd217f27be509ea1aa8f0450abda24865f617bf7e0103e10a70c018e64dd4814c5c8c8a941c2d2eab2fb210b3bddb451a56f1d116a779029291337c8e2e5598389e75151026164d95d98512f124f5253499b58374932b354ec00e70be3bb0f1bd2978273628e257cf795fd055003a3429f5f510a81941378000cea7611db50380eb641061f39f5e37214e8afa0615faac79dc785eb99ae02a5af0f2d68586b7ee62716d778a46d671949651c8653adce5fe9575118d005831b57ce3af1fb1f55027cfaf9c9ccbb3b50838625857cfe16608f72d07992f819b629511f6f54d8276bac6878d797015d406faa910623c844ab2756da89485396d5d4ff682f11e9ea579175c3f01e7c931c1c85dc5a9c3c8b396bfca866d142019d72096f5a561a1d3aab1b8a0df4b4db6e1c1029e74d75f0f7a5e4c9ab7b9336d9c173bd202538baba0af4f5ad6e057f556c6f0856a57c57b7bbd00e297f677addbea583d5ca336b91fb8f6af661af6eb3b4e6481dd251a3ce43bc4e670e07a1d4113d8e31c0943d68cae39ce512a7af5d3188450d8ed2675ff8d71b2227679f679783364f382072603dfd6ea458ff898b2c17676758234c5d0bd4faa1d44ab330eb5dc071ae3b336beefd4605f0158db8f8d9a79acb786403d79985966ab9e6e5aff941931e48f64d35c2f034b007a7336341884d06b0e79788f7957feb09d42185331689db9572864b5fcff6aa2596408d98d6c87f314fa9f922c87aff8dd0fe1982a14654eb855e884f2fdecb2c62b865aa1e4beeebe5c13fc26488b62705c67df959dbeb3459fab50095148812e47050b885a80af1a01385d6778ae470de6e214a1b5da043f23f8a9f465c3fc929fe0f594b502a97eea3734be254756dc9024b43f99706446ce228100edf0fe7882ae66e745c12a1172d022eadd454d09cc13d376b08018c61d74be0d22df1fa3beb664ddcbe615b424aad8316c79eff16ec4f66807b5f77a68d213b21a5f39020db2557f458e9a0927b12107e63c8338c9475807dd6fa8ecd2120e3174028c5aaa56bc3bcb61e8c02a958005ff78c43411b2772d6a7943d0043a35c2904fef97a4b21c4dd9a52bae6591a133092bccba96f4179af86436e778e7d2b3bfbf21616aeec7d12d08477b087f41b0e0b41729dfec923a710edfdd3c67513c0bbfb303383cb80e4cc4be4359e4290a2afd7d137e9ad20908325b3656de08f710a41faa9c33d724887455d04c563b25428b291ae2a16904627cc4fd0c7a825b021a28952128e51da1a83541fcd56028b32525188c5be96b6bec324c4eb7416fb31271083d2d9dec579a3d23fe74545ea9bccf0191e83de23300a0e1f98037d3f8ee1e6b7ec5a4ab77f8f5f2e01995bfe5232b02bd32bca898c5c396de334a8e7a95f479d9f34d30f87f00166c179031bddcd3b59f8341537ec921453a283730aba15b8f31654794529b77c37d766d099379783a20e6c83bc43c837901debd0cbbfaf4c9685806bb412faf1c7427ac802b07948bb24c5a5da854f082364dd4bf8b9569944b97e1850123deb805a85b89ba8a4892be99cc9ba0e0c50a386e08a62b1342121979c214de46682190400c19356144daf6736eed08176e731903d6d3589420f4d6f28e050a7f16d9cb892bdd547683244f6f329119dda9e795740ceef015ac645d1d78929088d185c2c55e0faac630102aa9619ba22efc49548e5109013f6bc07e8b5f092d7592af7ad832b3e0a8e396118f7b1cda97efe91b4d85a4ab04939831721037dd06bebcc1f395139e7c45ea2c90c70bb89107df27514f5deb58182cd6d913469152cb1c37a84d83f7fc080dc8e371e7b58ae9a9daa90944797cf974aae014adc0ecd45ba999e0bf88634885c5b5a2f11cb0fbd21a92d4d11a0111f8abc25c2a092a0664fd0ac6e580af47db1ee292c19df22038b8304c1210f180f0e5f595bf93a365dd56fce94325b851ca362f02023178df7894baa98cdd83b7e5e5d5e87c97133a36b6d2191e07f5e6e95b526e6ee4cd03893469f12239593459668aaeeaa01ea769d65a49b5de635ecd8e27d7253c1c03de67dc97395325e0413905441861cc877559e3e29451aa5244acfc1e7fc7a56b9dbc52da2ce01e092868c82af23634a198a44350dbeb779e93da73bea2d43b19f0e6b5ab387570ce6103cf0f3315b1bc057266985ec46aaca27db6b604507e472666fd650842c45721b0a0f47765015e1822bac9c58c3537450890e832cf258d69fcd2676ab0c6388ccd3dd068db40d25ccfe7aec452a3c64b2dc58c804b66ac7ad33cb6b84753682d83bcb4f2ba7ae331e60d5a929dba0aa5ed0bbe76d1ab38b4690fa071e01188ff637c7d7b23b06686903403ef7a3a9406cf16934fd8af9b812e20c9bc157e109191e0c14a4e219665d8dc878714637cca34b12c6a617a0e8ac11ddca464575f5d051fb067fc215d3d601d41a70f11cd2ee811665801afa184738e640ec867e25c01ee4f742d98ff793d3f38fc156dbe66e2b4d4293dbb6dcf765f10398231306121cc65f709aca9b1e0739051a45839d3c602d035f1c8194cf66ef6850d9349647f99c448c6bbb99d649616743c2f56d35d3ee5c7586411f8118e21a476764bb3904e76b9227ddba18d786d407c6b85142053e6a779f7b5488ef98963eae18a801489d0fe96bda544cae9b93686550d78042a20635de78ccb06cbe5117d7dad4021fcaaab42ba476b6974ca19def483aabdc5954b876d69f2d2d07fc4a337c0618d9d23cb7ab76f8e73002046dd23635f088364aa285d644f2cd0a25b5d18096abc3ff1dc60c228d8403886e7e6bc7ef95306fb73e271a29ec8e667be2e37af734bc3fbcf1465af735d8e5e9e67f20fabf20b3d95794b98233fab093460b0803f2546af8f51d116c0d6384e37ef775d995389745ca43dbfc7bdd2ae47a85879b669b8fbe89fc15d1d13a6074b8795fb51237a528977d792f7eea8200f99d787d80cca99cd42cfe84dfd5dd65f099870e19870d1a7bf9db84782aafd641c64bec364714028364e3b9a9e5a287347decad1a505c2b245a6bc36301adfd86abb3911267871b7417030cda91743eac2f145c25efa65bd863fb13e8f33dd419bfc22e30eb78ab4670ef37ea731bcaa52d3010d06ba3e5f10a6e54f96e687db1f9b52f18d04b03195b3223d6000d146dfd9f5064da90e6227dd1c41021edaf355a067f192ca1bc1e6556f9e9bf99f1b24c5785fe8a59b6979e84e73acd8d4965d83ac349ba435f833dcf2b9ecf15d8cd7698f75658e8b3d375d5f1f2ea089f21d38be56ba13249a9708e789a6366e519040474d7317544711e7c9204c09de4cc2663ecff38ab2aa9dc5681633fc9168c6fe8358111be26479f2b711945aa6d2055c3afa7a4fbc2404b68d53e6b5b545b8f66912a196804d533aa587b77a8b3cb48c3887621b13170040693c735f9003990ee098416ae13f70798a05067ba5082b7faad34980b5a293897f798604267794ad9bdf8929534a8fcc4c49645eeef0be3da79692c14d71d1d3be5f4c313f499ea5a0544cbead4b5d67e4dca09c78300f0ea01aa06a618004914ff7584d68bf0d28f04a8ff4ffb09dff218699204afbb5cd2a3c981e94ae6891f3ca4ff12e10afce18c0792577fde820b6719f4432875f0ee518aaa4f03af6cd04a2cebd0c19845b2c5aad975242c6219d0b262211d65db5d2ba0f8e58cd9833ef9410301507cbc3d385a195005c26900a61de55885d528355ecbcebacf454dda19af73711b8183acffb858da5b81ce5c003db4f6dbec1e5217368460e8832f8c9693ba748b18022d2a85c14acb6bd56b3fe21bea9229e9fd722678e2f668cf1a0616647365eb9d21b823f4080b6e8a9c122f8fdbc7816f9ca1121e0071aa2d577e9748bb09fbc9507e448f6168dd40eadffc2e0c3a5a117ea9bf9884b851f2f6e7a083181d947fed99149b9847fd9f43cd577846e219a72aa787b0a62d6816c1b2f3c9bdfab2f06f61572295f47e524e0456f0a22927baec422bb58e19a2ea1d2647fb382c694683978b802319721d153b917d403b0ff1b6ecd5ca8e74233c67c93396c58a2932ec484cf9c3241deb608004c52c793eff69381fc6e2d7de298b3add988c9987f30bdfdbc584ebc43d91bb36fd50d1442175dc590c09aac8708b6fcd91bde644b3c2254cc91ebc7ff8ae6894f262876bfaa2196f679eb4b74d0e315d56b42322b4d6450713a949f41c98ebb6f6a2a81e915dbfe163dae3287ccdb01f88b22fa6ec97492d57e781ad1a71d0c42b04657016ad8f38eb4c52f5735d03ae7460b08760f0b0c8514f71a327377c8277f998bb2ed050efd8418684777a22781ceee6858a603b5ecbc70dee26bde5748d0f38d3eb08e19347b993f526177492c81c6452e7abc00dcd27c6defeeab40e754e1daaba9f7a1706881fdcb2d66d4d1dce475168c88faad04d7416ad785e5e272233cb4e274119866a9bbde5acbf6d1964c3e5844cd11a742124df34f8ae61a451a0f7afd33ad675ea48d439a61eabb17f2faa5165df78075044dfcb51dcbb48d56937ee5bd2f9709f24900360e13421f0ca9acbcddfeb105ce887e0923d8b7136bd978d1723feb010ba2482dad1f246dc041e0b26438d38dad18df983dc5e2ae1f0eb207a6598b98b09e77925ee080773e5b88c717a59b1913bcbefa2d5e9f7f785b7b632afbe702e5c436930e0fe67b1b87db613f4e10494ddbf82779a0450e6c5d969eeb075fa4ebf969f7e6bdde9e596f00775264b50c63294a10c550689251ce15914e5afc05af9d9f3c7321e097c5ccf8f7363466992a1cfdf3278b15e5787e073773cd409e3259f183244307cd11b2ad62dd156a26b6af660f5473faa9ff6a17d8a046b348166d0ba205348a0a898a34f3c6f74acdc3af072b935194c946a5bc25e08305fca9ae64f48efa21a968264168ae7fb7d16e1479eb272000b90cacbe26a5d67f86129bfd4c7b8d5e6ddebda335f63144606f9ee8b1a26f2b509db1287432427230ba161160ecc8ee60d04ad6de8a54f23f053e1a5290e806c273cb03552c2c2bf1654bc05d257b43affabdeef9764c82d047b1cc632279820f5f105024621e9885964fb51f73a461df88d1b903324de192b381567f83a01b1c2e2744f71e14383c0a3591cb922df3c28271a3cc48312aee4eb33f31135dac8cd0b501c2754a3033ac18a8d592092b29a0e31e0fff5a99eded55fd338ae13f8eb5b645f1b718807c5eb085f36b1f65f0dd76f49ef41a25444f396f500ee160b804010e8d8115938bfe8bc5383239717e76145671f8c41b81dd3500b697befbda59452a62465960862085d09dc56746da5d391a015e5f6721b9d95db8a26a5f35624aea5d33a12289437e43ecab1dbcb6d954e6e2bbab6d2691d09ecfe448cfed1c6d87943fcbd799ad437eddaba694438bdb6723389134ad36850e9a4b352edaea2191a97920105ecdf4779123841f6bf4684e2f492c08f53b25269ff3ee70db9eeb69b49a452536652dfb46beba611e1f4dabaa552776652df345befa61151adaddb4dc2a9adda4c62e667a66a52dfb46beba611e1f4daaacd2454ee9ba6eac2f5cff659ada66db60897123c89dc50a23422beb6dfc0234c30091dd902f330ed701cd7514dc359ebe47793686884969cfb9543777a9e7772213a3a3ad7eb3c8bea9d1ffd924242418208803fb439baec1f9140ad7f5a835e0a5d7be408b78c4902a1ba477910e8ec8f2f8763e9879646118e481f1ffe86c052bad57f3f69d4ad9ed247a408324af4d12f3322f6e86cbd1168765042893dfabd23a0e4bef65ea949a499335cdbba7669e04f94fbfd48304c7bd71e09866defee1ac8a2dbe6eeeeee5a0e77f7eddd5de5abf7d4fbe9dd71bc738e72f7767722bd9e44fca7f7a9afcba1fdc67ddaa7beed579fdc1ebfb60d49a13e8b82fd7d38a0bba5b4dddddddddddddbdddddddd2dbd9ec27c14c8dcadf96fee7f4122b1873f37467ad47684dd789a3f7d1116b3cadbf941fd69e6e31ec727a3fa24ac3fd68e373fd68eb633133d0c9c3e7aedeeeeeeeeeefec51b4ff9d4464971711c8a514491a5bc49e2fe80bbe24a998511596eb2a97fa494c9989154ca92090b2d32f793fbcd1bc27943b6a9612143d6fe7a43b43babd86006b71b8354834cf101b8dcf0c8fe2388155db22071850bb2cfae48cafe383a4830581194fd25ab68620b1eca3e9cd4d8c0f867f75eef878d3c7ffef5625ee439dfb631aba2897a3b1fc88e8a28f2fc4905530c58b5922a9b1b1e4872f8c327961c462d394731d3a76432e5932e221539863c7ffec5ab6fcd5c90e7eb0821b273a5499e5f42912b79be09476c90e7af2085975817a02b42424bf27c394474258da2232b8c8af29c4d61469e5a9a30ae92fb1b633cb7f4e0cd392fc6af8803600506577642ddeffd2ce878a73a6cbe8543a5b7a3f32a4b26c70acfd4e0b0768cd6d2b6e764b7c3fbee59f8f3fef47518778fdfc3ddb33e0993f958b079d6cf7c2c446141c7a3b61dd65a6a69c78ee3c021f72988029f6c53d84016ea37d0cf7aaefb58da83f03e30ef636920eb02f8bbe7c1df134503794e0f02d863f3a7dff13e308ed3386eab31aac2d474e44665b491c31f559874c861945571024b0e23166ed38ee6a3b0748ebca269da434066ed02f8bde7c1ef813d51b6df01f6e80059dafba31eff06f29cbe037b6c4096e68f7a9e7ed6f7b3fef433cffa64be46f538bed4afbe1b343ff33faa97c1b8a6fec6fffcd0fccfcce33fe1c032332a9a1b37be08c3a9d6cfe9bbc7a893d7c1fab64cd2fc1f243830ef8130ac4550c12a4b50881f2417261f0986f987ae6ad77cecaf1f201c983f120cf37ece9ffd4e644793ab2244229028b4d3547374446487e6a70ac70c63d3fc1d22dd3a72725a226251cf5ad57c8e9aefbe1c39defb9a1c389ef5d1636939fec301bd020c20cfe738798feeb54f64a70326a87094149722536cba16a5a4e4af1f3f947e28fdf8b1fd6b393e124a88403c8a58e44924f698dbd3ed2938e4a6c0d272bcbdf138fe87e659a830628ff9f64335a1c4f0504a2833f2fc88528a3d68cdafbe841c6ff3f4eb2cbd1d1d9fe363a1bffb9a8f859acff15447c747c45df36d82fca8f9a2689fe363c1dfbb79f5389e67f538564f411e1ccf027bfa71e0f856cffa707ccde77d8eafd3409ecc9d683efc331ff7aa4fa6cbaaa59d134a89a92906448449a95b734e21db3252d493d79b76d57f7f9d401d6f390e77fa20f5753ae704467759a93bf5e4fd06dcbdd347d8fd70adb5d6d3d77a025994c5aab5d65a6b8dcd10ebeb8eafded7d5d71b5f69be9e6a8e8aa3aaaa160df5c9587d8db389b0f63070fa6adefb582f3d16f53f7d0cfd5e8e5fd5b06c742210ed6bfd5abfd65a6bb55aaddaebd45aeb4bae56fb9c7d0e1cb24deaed0d4961a7469505db1cff73e3572f87504712a8ca58047273add569d0c0ce1381d0afaf81f82a4174d5af3dc041aef5043430eb02356ff325b05ec74b6f67c7e3a21d9f0e1b2a066328ddaa4f41fca45bb556d64e4d4df4e8c963a1dffb9a8f8528f659f03f9d9ec7fff416fcf0af3ed4e3f864fec6977ad5d73dcd37f39bcce3d73ed6cee9311061a7df4084adbe08bbddcffccf8fcc67ab57ff54f1671461b5d68a8d720ca185199fda324744db06b22a96a90f31fe2933f3da733f5f16c929fe42fd2432ea74c0d2aadecad87ceba9c73fb37da9d73ebc6df8b74f6df86752292c2319c0a3bf9b76cd7f7f39a833bf9d12b6adb5ed7d60da2685e4908c49209d08a4022941ec317f3e10f9328174cd9f32398c4b32db96431d4e76a91e7057f0849962551d05c4977441675a50c6284f907ef5cf0fa315b7398599c388b4937afcd363557067dbe4908c1d4520afdc7f8970d18e0cf8f3e33df75c773cf302dba79e67fb14d813856e4f31873abdd73df7d1c34084fd6caf512077cddfb84e1a4d91321943f27c66a366e034e003a3afca92e357a7a408e4f5455704a301b294b0edbe7ee46ac99b0b214922c9341724f9282d1229c618a7f86b4e3c021b38c66fe54931296755c6643905cb8d95a01c0639f28e533ae789ce9936c8ddd1dbe95e48fbcaed57ed6f0622ea09ae5af993ca295132d414d4942c93341fef2e86eb03bb3f3feefcd479234f992735ca3747cf280be99d22beb217ecb9160c8106b8be0585d8a6ebb8cb5ffb847c3d5178ea5bb0c7871654da8055b0e52e7f7f1a0697b0a3433f6ce553161cce13b69dce913b9dc3224176676fb06711f75a241998702e15489c8c220ac50c4d06d30ca289fca1530691bbe27d428720328edc152f95a66d8bff80b71c6736c486849a6ee4385312832b638c3fbda3de63541ba38e4dbf8488a05dfd57856d5652043ec8e8ac74ce590be3196737868a120e8dd04616267bf714822907a8596a2ea8907d4686c32eb77f33c7d2e9b35bd64e195a4add4970d5961dcc6959fd4110abbaeb564f240ed033677a7bcd9daa73fa43f7cefeb8a50fd9a93015d9f1f4963ee4ff0643bd5dcb0478d9c113baecd37e051dd32d9ff45e2bdf3b2a439f6e7977e7d3d8ada965ce5d7af4767b9a609419c3c0671759ad40950a34cf4ec7adb5f54178211d5fdb6b6058ffaa2c88bbb581d57de6facd6e65af95a63033180699a04f7e0a44f4973fc12b25d55cd896c0e1f49ed9b1a2bbfb649fec5e486bdf3e756bc1209186383f8e3f8f1a5345e070660f326fe794fa14eec3f8f4b948e7830d26e0fe002f8165f7f4de6212a1e141274b19ae5654a630f3093a59ce2f73ced9d57afaee546bd7ddaebb9752c9f2de48f7feddfb7bdfdbf6f73ef77575dbbe13baac7dfdc229c4ad34ead6fc227b4918cd1aaa9595b65a7bdf6a1a1806b1945efaaad58db4b652ef4236cd974cf3a5987973af04c355103894319510388c5d7cc01c57c10f041db0c4b982d1b6bac3aa9b6f7e1f750e9646eadb4842fd538588e44f9064e2a4736a0fd55bfb3b70420f92cc454460c21115c0c672d4ade9b15a4bb7e64ba358965f5762f0d21118a60ec4973c72d7944ccfc5949c801c675cc0903919a45df3b5acc26be5de8bf16ad5c2619a63da359fc92a08fb8f875fed3fe592c33e3af2570bb56b4aa676cd47c2396ac9be8b5638b875af0c1f39c8347f1b839fe4f95c942c9116304eb0207549a25fccf289146945fbac94750e05af7ce256f00a71229c86cd328360039e6f739c6d3126c725987efc9bae73e8af7228edd93d26b6e2085bb4115b31876efd6f68d3c0d58f37a1cbba446f080d1b58e64e9ae1b05beaa456ead6120ff832c947dd925878c0da735ae0b6d2044b314c5a4b73e968c660f98aaef8da1c7cf905e6933cbf8b662b758bfb429b375013c324cbb7db0cf3c09d1367a6a01cfae4500569c66099dba91ce3b293b2fccef13cc7748e3718a5ce11e3ed323b869b344dd8feed1ced6bd7c7aaae4fdddca929a5947ea55f51f3f485329f1ef5c5d3e7bd4f768ffbc9759cf6515a2badd7fa0ffc83d6203fe6a4334e6f269cff6c8125c719173139f4e10df168d8c0b13555373694d28cc111a7d2a871ddbd65a5f324eb50444af28c6446b249063735dd26dc249beec5d84129b301fdb9ae7b0095c98e64324a69cc1f6394c980443221b2bbbdef5abdee1ef2ba3b7753f9343360d9b128de26e5774bca93ec38aea35f97ba4565d352a62f9b8086b8da3f4c3a2776500be55aa5103926fc7c7a8fab6078023b06caa66ed19843d9c4819e9782cc2f9bfa2561f43bd6395126f94bd55f74d1775124fa15fcd883fed605ee7a623f40f5e59074e2757fbfee455f722b027f91ebaedf5f334fef87f865becb33bfd8926e5ff0fdc18fe461188ce2795fbf9f3b7943daf3bcf6da7b8fe36aed34289c56eb7b5feffdae7b8e6beefae4ab7dc76d9ee7717fc22a7baf79433c8ed3f276fff4b6d6e7b83f79f57d729ddab7f69cf6427cd34069a4b2e9167d82fb6d6d22ae73a051b93a63a573822d24949b49f3e8eef7aeaa0631f6b7ffb1fce7576fefb22af74f9b150f1e4126d03a2e87ba78d32d592bf8607cacfaf3bb3e954f6469254b5928ade4ea848b37343fc0da6fde12ac5ad9cc493bea0db98ffff4797af8e77bf8bd3f7da14feed41bb9a9e7de27d78a535f44e1cf275394f7d53b7de7442f86b2be7d6e5ebf17bc55e6374de6bb9d80a7b5de1ba4ce695f665b55ab9b396ba532fb43b412a4821084bb975f73b5a9b97e77afb5d65afbfe2779faa2bc2fefbda95e03984825c71917a2cc4528c799164f72d8000e05812894e30fe5f8ad2502692d5d151cae261081c81daf82fd069c3f343be692074a298d5cf522fe13c5dfed3f9dbc3f61954f2fa44f20abfbdbf350f7bae76d9d4fee2aea51ef7da1fcc9a82d766b935df5429facf5e9747a08c47c3ad947fd552177bf7dde873a711fbdce2777df095de6408d08e9a33ec99528d76ed9c6f50a8e3e8d1a9d032399910c95db27fb27b3ed6e69feeae91cacf67602b4a75eec56c4376afe3e40b9fe09abec43dc863e41924bdadcdd883fce1cf771868314d9276b5fa802e79c17e9630a71fa49a3e6127be026a4298248ca710a20b4e4104f0184159a1fe0ae5bf16966c091557572224eb7242cbaab95e3fdee536a6d901fddf2f0477e996d280585742bbeccc3080905f9d774cb80bb6590ec30eef65f4929696b15039e1050ad50905987873636b1876f3360bc3ae2e20dfd39e79cd3dd3dca562b9f784e5be79c3e2794ffd12d2a4abcd0238f024950e5fa73ffed549d96e3ec0649aa0ce2141d9da41d9c1c4185e905313c2a5696d820e689142a62a8a2c5802584f9853ba1cd90a429a00c1e82c869395b633684506c0621b69831a071c307da4a6605e4d811694bd29630b95fc81633726f5923770c95e36c8b0df214395c216d32c0f3ca2d4db856b97901145894701a7035422e11448b417434d4658bd00d5b9224d9e242165cd71012d34be0bbd448d87ee14558260b79650a1fa86cc15b8e332d4ccc60c4137c739c6911c297b0cd71a68548cb100fba2a3cd0a071e1aa2809ba69146d5560a8c112694d2fa8190da5fba509cd3e28b2516899410d24254d445118b1a4e9280a1d6e4843298a201a9a9adc156f164c9ffa81bcc19226404b928851d2d44ae02ec799922559059ec971a6640b07434915320bacca71a68409a15ab57e2c331c7aa030703d208a331ca79a69194f356e547777f7c9eb9adb5abbb65bba8344dc1591c0ae74ba8eb74bd9dddd957ef7ae56ddada239b58ff700d2e054a0194fbae3aeee1fdd8a3de21b99fe2c989f7256047772cb5df169f247bfcccd29dba7fbd670ddbd489e3152804a778c51eeee3fa7d35494f7a4446b91ecd5bc21f735dfdc5e6eab74725bd1b5954eeb486cdcb66979c2715c47afad742261af4bb7f75acff386dcf7fce4f6725ba5333660f9fe37e6cc77f78eabf958cfcd2f94994b6e252eb9959e34141ed6eff8e85d80f53b408ce4aedec1472c58cd5330383aee1a0118317658e2c5121148ba5fc727c4e6234225c7e7535c2689349b0aead60b58e6fe8908a5148100c93d1481e0d794a89e4e2889266bd57dfdd5c7aaf111ba9f49bdf7c97c37f3712e6b9669a38d36b28c0c28853e09249b8881437c24899ac8275c3a27c7dfbdefc7484e9a442010889cccfe1ec6c2f22eef7d32267fc2ce8e8f3a070556a1021adc608a22cec0420a176fc40e48a0c430f4c20b5090b9fe6f2f8dfc53a166ceddbd53a1e6eeeffcc28ec918972624a6232559928c8bec8f5a7eef6ae591822ad0cc3d0762a46e612cddb22fb305f15112fe23aa24440f5c68a103106cc480c9077230424c521959d8f0037dc52c42526458c4e4fea3dcf289a02ca7cc600c9d7e846585ce61ed482269e42f16b070a1c5c8852f4b66c09a044c3451eac1884a1a6360fd92a85f16f5911350967fe32b22a384e633c15dfdaaef88bbfaef0ce32499aed1a2ca95a55c656829cb2a57c064f93c9d233914ce0f9c12381cf784702962c24e09a931b0bffcfb4f6660566445f0dd2591524b388c485149cadf915f82fc6b2532cd2eb813b0fc9bb0933e1074b072e0a09949a13c4e7b0ab2787aecd39bf78105e07d60adf781b9de0706fe0e9b9ad50d950c3e755febfbc042781f1800de072680f8ae0f73815bdf0be05be10007e003c0d7ea826f3e219f8e120e75c00ffc9ac66e0c62f2ff23e22ef9207c3806bce32bc15df2757c45bce0307231c15df259df11ad081c46a59a1c35396c929ac47e2be0f864cc6a81c3d87483e6064db44a7a28b13c2471aa19d54c2a87ce288794cc2781529f14fa02e34f0ea13e49940a83433974f24e9eccdd7786c403528b99dd27dddd45ee320a0387b2284f71977ced93b22e389453f291bb90c4a02fc74811fae598e8724ccb5a26bd481ff2e51839a65f3984dab3fc2eea9c381b6a6aa3ae9934513f692859ca9fde2d13d35ea418d9d4561c029e5d7a91227843e6876005c6b9fba75b32d62df963c62835007c660b2089949528168bcd506369e47e192a148bc56ab82e20c9fd291acb626fc8fd7812c5623126764bf743ee474da1582c36f4e53291fb4f338ced8adcef31f519dc97dcdf25c562b11c7ea0b91026f7739ec4fee4fe8d29168bbd8086b943eed79262b1180d53890d22f7df1c3c879494dc6f5b168bc56c2062b322f757a3582ca68314192fb99fd630bb2fd090c54b92fb5da80bed86dcdf3dc462605040e45e4d67f2ea60fd2a18ace4fe3875ecc0c52a937766b3c46e4bdf2f710b437267b84d32698856894ce5b0111165a4c83bf3b244ad8b8ea81c7e98e3bf14215230bc31c67facc526f688f2afcdf6d176ad937695b405d9fda65f0f68574f1e39d18a033a506587a70407cc18bfd894c0aa04f40040d3c4978ebbfa9d3582ccb43d4aa97339faf997e0ab15c67ebf3cab4dbf74dad5df11482b81439d213e527018b988a08d223e1148ecbaf564527ffa91f6cc255a01b0203aa3082c64e850440849e80006144fc04051c2891f4e4e1cf1f2050d6aa71c676300592e20dc1d463d80e2878a0e7ab0d48415ab26be681954435021a58b51184bf800021e9811450717b25061a589254e513461652e19c0892fc2ec00040d5a5c11c412a429bc3062430a2c804e4ee0e0cd3057dec8d51849722320c7d99296252c3a98ff4d2743c0ba9b495a7a2262993b664fa0738680cddf9982c54341b69161fe4558ad957e484b4295ba4b11b606324129d22745980813d53c8d3a87be3090101b1d0022ea9c3076c14f3a678a1126499b4074cd97ef58667cefad3a92c21cc55b79740be7fa8bc274c4c248a1b686280cd1a45d80c20cd94a29a5ac9dfe067b24cc07e632601d77cd6f4ce4aef93992c41043840329a5f4578338ee9a53a85b524a217f455f252159973dc9914ca29452b69c1fb3a4418eb33064799a20869325ade5534a69900c107e0026ead6fc1b62fc60224a29c5449d23411edd9aafca121990e77338a85b748787c6071c62a0880503758ecc4268624b7169879455b66aad2ad904029a8541b42357aa12ea9cb035d439a14e50e74c2e661840532987d8499e524aa9e32f2c34a403e42e2ee22b2e31edc08399184b106981cd5f21be3e276860a2862a6e40220b6cb6fc8581dc35315004ead6a45f00e5f947e6af30bfc87c13a69321600ec60f725004ebff9f6f8443e5afc0010f4db0306a52031734f11c7097e3acc90f5d609a1c674d689001873165e00006130e5b92582a5600b9190ab85e8e33a5286550cd75e8eab6b44eeb9ce08aceeab62fddb42d65c34a4ab75ddda3a494d289ddabbbbbbb76a3c63581a4f9681c9ce8eaef6d898f93874add7cf2646cf6c0f7331ef85e4573c3bdce93c009527876dcaa8b32c7473c800a88ea553450f99043eb3c12b419504aa99d77be9d749290ba36e7f83cf0554ab5d40952b405544a69ad945a9e68b4895bc5578caf5519f19179f39cf1089e965659e4a7e77804cfd933716eb6b8d1dc6d92145c93e34c69490e71945e88616cc615acf222b1c027a41c7620731db02ac7d90ea074163bc8216807405f30ce71064653ad5aef7067bc682284655024e3451221541145a92f6730391911c35f7420e41519a1be50914337c4d0e90b1433705088bc2f4430d860454add971876d090b8a43270b830238cb626164a939654438d4274ad584289e0629b60328928aa51825c46936020d430284733094913394493bbe2ed810d4570c15f8e331ebc60e100658453a91397084401d91f472076e5fa7264dcf98043140a65248ff0aa757348e5dcc0065e0bdd58315201c028fa428dac52d9804acaa76ac7173000d346ee951a60bc640f83c34e15de7811860b1d4891fdf14a075b64ff07b3446b55461a62289181c30dc428e2882f4640d9819229603ebfc8028b078517ad55b155e460a98772f0c57390050d97485673e0440e5688ba0bf08d1c6739783225074adccb1a4de6bc319838ed92ef65a906ffa1d3c394247097e3cc4b1219f8e638f3c2a58a198880ee9d915b62a643d92afb6b55e1aecb9dcae6739c3969f22ab8ad5bebbe31da18bb76bffc5e7bdd45638c31c6d8dd2d2377ba52384ef64402cf974f811bcbd3de596db7e0f3f3fa71051a9df5230d5b90fdb3fffc6c8e99b360a520922b0129623df0c00a1ebef01a6ac04653161fc03862ee400b1fa0fca65b94c6071c622f42cc6e4c12eb33c41429bc2c01a28b180fd2c8c105a2a0202492c802eb098616655ce97fb1171770504576ef2e9dc39b2e8c00d0c5962e47e00028772afa455760c0d2840887a6ed8a0cb2dc6f99b27d2e4a2277d99fd1c2252645a627ac348cbb466c30a13236ae6c3136ce6043cacc932223590482982e4061b688c25449e0d00ed927554bb764ae606ad72a7bc8666d1855d96090948ec048a456f2173d9a1c11118f1dca2ee42e7f1f60555a929eb36ebe2100642f141d2921dd7a7465cf599b90c061451acaf6ed90bfac91bbec5b3b8528ab96c06155ca76a932758e98ce710974beb2bdd3a8531609347bc5f2d523ad48b345fdba49d6c83e618fac1627b68b95592c16c972b149d9be76bd3659891d0109754e58c1645b9b82ecc7af56b42f02414c478d54b980a9b5a873641bd529b2cea9a06432edd34ffab742b7245889ba655bb6c85df695c06125b25f9bea98d84f2887beb6b56b953448576420aab2a3181556e891310924992c815923ecc848315fa46e59cd0e699f75d2ad01c4aebcb0c4c2e9bdbfdc777f026dcc5df63df0baeb7ef478b8efaad2579bdcb5dd6dd32891934dbb15c90ed5a6aa74bfaf4876c83ab1317f29f1748ebffd123a50c484239d533140611c2887dc65a5e0b03665fbb6a873da4aa94d9de360f7f49b1f17ba72cd5165bc3fbffbf4bbcf23c7d8e382e1093e9647b4b6b26ab5419244d6beb4d239f18a0b5d4289658a1c76760d3072288dd6782287f2896ca5cc2265fb55daa2ce093ba3ce09f194ce0957b2ce097958289d23a358c31665db440eed13d9aa91436b255bfbf4adb5f6c60b1729edcb29a9ac7e6f6bd3aec4949adaf935f6e8d73ef975e8724e307410cb96445c8905e1000607a41b6dc46e48623ac018304cb83091c520a4ec032b58d228ca5c6c018eb33486720839ce9aa6642e72b1eb2f3c21f74735ace4b09970e7d08de24c0d296e149b649669e4d09fc8328da6dc53e22c0da62cbfa5f4133d36c0611b492fca5b2a024161839fc316a2407c196b293da5bd291814e28528730758febcdd1f4374c99f01e9062e30c832943f4a519894688af2674607386ca336ea9c50aee02ff9237fe44f1bf5142697d9a2f8a13b95ec2f8db8cf39a7bc17e3d5eabf1571a2ce9d321b99df94078ebe047c68100509914a36422bd5361c03bc4f0d32a592e76f465a46f71c81785d8cdc16a3d6ddb6d21867d7ddefdd2d638c31c678d2d030ca71e683a02a729ca121cbaacef1550472bfdfe6505543baa44bf68f7f454953765085ce2958e57085582c96555865994f1984cc6d1f7a39851450906de44bdf46d9b187ffecc29bc31b5a107b9639f4c96e05176fc2ebdefddd2dce5fad77178c8e8185c4978f1042637608224b920d67c0e413892f1f1acc7c40c49822807062062c0a176fa408ac115a8ad01f710ef1f4afbf8d0718e710e3f8aac01749c260e1e209248660f257f1a55a2243121548aa3cb1f29452dade5182904429a54999524a29a554524a9f521aa3048188524ac54cc994524a299594524a29a5de9436a59476777737edbe5204cf947e677be0eed4d61a77773141d9ddddddbd4e9f734e67595b6bdcddcfd092dddddddddfd639e79ceeee3edfdd27f5e9ee2effb670ee0c3784ce7042836406184923cb42da7fa60b8edf48d28c224e8a9c3921559a63b89454c95f449a8bf52bc501714c38220e4a17a61ee4900bca92e3010e39e48698c0e4907b92e28cb2e4a4642967cf07c41e7213b389e99ca61a59f6908ba184707ca5ed46f691e3cf295468934911ea1152dec0e4f8344fa214418a4023b35370d15ff3234e7d3d2e069822580b5c118da48908d64290a02396e88951c05a8833d804a3f80e3024c15af0810694177aa4121c9a602de0b88005170bf891c1fc8bb09e58a5890db016dc098cde784f6334118612cc3fce0a587f32b70bdd5dc23089e506636c85fefd4d411266366203a594d26ea794524a2995d16bc06d2fb8525c28e87334ce19c110273ba54e69a4f1dddd3dc4c1691a2d70fcfb1fcda2893ce73d82b35ea40c3836e29cb37be0e29c73c6ef720eba53992ba24c524a29f5f994524aa3534a29a551504a299d94524ae7a494d24ae99c9a125ccc35fef62d058d506b29a53146ea5e3f7efe9552f95d657b05434a7dcbe1116290f1d91bac75778f11888b99c648bb4569b6c0fe5fe4b09b5f7094f67c016423149cddea984d884052c0c9426c8f93c318c6fbb4d073ff6ea006fe20e901d31e6806767172a8caf353e5991ce2504a3f9c60d441a6a010f1e31ad97f033eb4a0d206ac274f23274430612ad3b71514828516621621580463318b100c952918aaf2478f03318b104c823df157eef217d2b9074e98e88109a3a4ab7d1176bfd82c82861f5ef0010ad80f921e30f94033b0f883a461f781180093af7d400c80dd9e283cf27b4a7097bffc9e283c9a06f644e1b92fc19e96bbfc2f7814d148328510637041c4172c6011167d6ec002050f3c50e182c419301d9e8fc5605db8f833b371ebc7783f5aa08246ea6fd9c8bdb5ce5a6758ebec961fbf7e595dcec8a0c27bcb598472dc72d79aca71d64592eeeefe68b4c0b15b40d4e011711087c4af79d48f8645b20a3437b8bd00d3efe2a7aa5b36998b31e21a4072582b186b5434c0fd7246209dc3d07eec56bd018373041971362a292e04b8739c9171c4c59736c7d8ddb2a59473c6e9b2bbbb6577572a63c72a29a89a5bb8faf503237b3576165985826c235b1a2a7e74cb8be022e3d05081439b223864f5b3a2a728181466b6919ea2604ff00a47730596fff9f4029ab0f9fef160d8740751406521a6bbb4c8820b019e39ca6e90220498e628bba15b01f0978c1f731825206331cf025805334f30468fdf391a60cb7794517094d9a025cb588edf07b091f93296c319653680c932bef424206339fac8ae5f5148bf9a553ffe68b605ed43a203b32a98993e8b044994a983610bd9bfedc7cc8361f7630412d66791b0e50aa280ca1414e27ed422e3e4d027fb0bf1f8c958be9ffd3810dd15a7135c1c798e474818cdb8d8e206424732009add2006493535355f03f6c42b557e88c174e4781688e36924962a5c60345ff33eb01def03fbf7815d90c5c362812c9ed50a64f1ac6c7eb5fa15d8b37a15c8e251fd0dd5b3de0706c27f310afcad767cb80aeb6bd57c1fa2a2a8a4ec588a72542a2211000000001316002020100c060422a1401646aaa8a70714800e73a4446a54184c83498ee3288a8120638c210018030040c68019231a1a713775e5adb8ee838bd69b5c12223ec7b0f2b1b33b704e1a02018fc1a47eca94c278246d10259f1cc1697da292bfa925383ba3e1b581140246c2de27c729861f721122d01fd2e8fe331d9787137f6dfdd01af0c6a5ffc550aa69fbb2d1061721fd950407906af3e7bd6ea5c1b64c3460c6d6d62276ef5c66b9046df9d706898d718814e0b127ba928fec284b80d513ac362795e8f43cc9c2ce9622106a9cdab4eecac3cb670dd5a62dbe56c300ab45898808beb4796d0dc3658e28d998de2ab1b9fc0e31d0c947a5437567ca79c6f28c42c987925e113e272be4b1e98322d3e44dde6a528f080999f4863225bc435a28f5da3ca0a34b7cd6745050e13e0fc437569a55f4ad0df8ca75d50dca4ca1b9902bfa988772f4224cf0fd46dced5ef905f47a31130d9d6784cf765d3cbd576d4849d0ca30e97aaa06f1015b3b6218cb2b001d7b63e049193cc026c1fec2f2388296786172aa4c4ce2ba08bbe53424828dc40770e040021aad555ee35ef90caf7e5ca3ea1121a973ce2cece9252a103037a092afdad24d99ead92e343b18d674129073e6b2a7af66bb041695cb33ff31fffdd53139c9ad01d69594823759b4f875c8b3d3afd08d33ea86a5dd94370b3631e13845544e904bba35433bcf27b66781457cf06df2cb86492eee8f46c53efe8517729acf0caeb124674156e9de4e33407a456b7dae972d98f97dbb04b3f959e36d32e7c1c7cf34130508b157ba698827194499dc88b3fe3eaeef871ac3e375d68c600bfc5fe56ce0d253adbfbfcbca32c3e45701f39cd81fff913824c6776c2601ef7dc7c3f76fd1c66d2dd45a9a8c56843ecc4204145f219472747cb4a15c3d8e51c6ce15db1b90d08242d289b566d05dec78af279c80b704cb92bdf1f1f9744520f06f96b808c6ded4da04a33f8212271a0286787f105a12d30911fe9cbf01e91479a23da383ad10cfeb99d6bfd27b5098891c8aa4fe7b06c1a14d4ba5ac9be678c7cd728e26401ec207d6c3ec4c836937d2ecef567d1a8e948ebaeaac895eb70a2fef5bd498de875b95a51aced40b34248c722971c00c92fd69cfab2fdf2974d75c3462b945cd631223578bb1e6f58d69ccbe620c087f05749689686a821d63aca11af8f00df9188c971564e7b016078214219f3447cd7b93b730531b889acc04124c25e3f6173a1787c0cb55ceadd71dd4132a4712377a2c872048ee4dc925944d048e999e1088d753d30723a3b3cf96cc94f9e6d1272922c4945213f981c01c26ad5d814a88a4948d07ec4134638475ada57fbd3cd56a349e0ad2fb2429950ff1e4084c297bb446984833f3d6ade5544478c30e38a5525c1f6f62febcf838947dbc909e57a6c297a478dc878bdf47d2188520ea78e92109138fee5aa121086974a66920eb9c5381835cfd223eac5efe9e6d042a1b0914f5730a5412b7bc1b73244393e2aaa4a6c08fbf6c780bdbd8dc3c3b1a3a2e77253f2c0aa149a41a8244931d5774dcfd46110db07da63bebfd6acc05f8c8227b5dd74e0952d4d9cda3a80665afb5a58b9828011e37511d293bed110581d70568da796e24cd123c8512ac7b8f9c7b5ed1f6315939f4c98ee88feb6c255571ef636444a2619fc81d6e6fb567549f9ec85532cc7df71d116b0b0364d9bfc45a6f7876b5c1e89e6ce34c10c91171c9182b7bd460a760f63450a99a86e951bff7561182a7601ebe4f687dcc11b05c5c4943629cbec4ff265fb6a46e95bbf935c1dcca6c293cebc7adb0e0b662d7d6febd93c4514e2ac982969f6d4b04a1fd532a23e21119ee1e63ed4d1fb76dc93e6f3da719f522afaf99445500400db50a4b69a00c3978f4ef94923f3a3d08b0de2d942abc59c87c8bd4a796970ac80d9c81cbe8a5b199045685b0af5cdc29d0a7829b680b7ae4de88d74cd611303b1b3fcb9cf5649bcfbae03a1edd54704fb23035453a4cda90cf3ab7245f63a365e7d95bf34af7e152ce808e6a129916faa5d6c9b3a9668c442c2d7f6eff45dd53de51c1de4d9c2f5457653a688a2e37766adcbc8733e04c772f5241b363504ac131006da4011195bd06a0a51e8f1653eaa9a11042fd06295b17d65d6acc1d0310643e070edf629d9a537aec51e7ea2ef0541377f79905211df3d5e4ceee3d1faca5ac8b2ea10e628197d711ca63fc8e24970250435e3a7ce1f2b8fc9a037a15a63ba4b25c5627772dc11032bf0be4f7983b75be7038571510a4bb8d1a678096dbfc3d814d63f5f3045208d01010372db557a3330b87dc9e51cd2f0e638254cf112dbb41f8f63a73e936c6fa017203919a3d687f56441528dda327cb82aa3b986db2cf7e05b287d7be361eaa0d96b194367a64b36bc1af4609698c48a8f91cc0b52772febd344f3e5b00c273587aa8c29eb7d87d2998f0bfaff0b0cc3b986c05a32898e20f6b75aeb8d8e088e193040223bcaba8e7efadb3de2501f7a751824bec140b7a08dfe50e0eb3513fecd914558e5670f3438a3206bf860495c9eae5d2f151af1964a13438155fb0324f652aba560e79ebae44416dcfe1ef24d2fdb46b240cfaf142ec9421283f45f397e803603c8b16ccbba9d0b0570e4e249f96c22e54908ab79727558558bf043285326faea4d99873fc8339476e9e7678160078753e2b1c2f48d4ac78c936a038b8324edad329fdf6f20d6f5a6ebd5b687d51f04f64bbc5131248148ef69b339864470a9b31dac8137301c80d2d8bd670acdb2ac5122a370f0da9fd63d9590cf45faabaed68f7a456a0e868561e15aeb0856000795abdac581db022d48cbe56c6715a2143aaa404b2c6906ab966d21cf6b1344ca89eeee3c0d7a02261497968bf8ef8df9f580c7d85e8e460ee23430f04dd2756b5fe06cdeaa69c793aa56e4ff0d7a0b46f8ed1243b85509f64826af1d1662c37b5b286e871e3f34da117c62430fe114b91fdd07df6eb8c84b7e6389f39fb9ee50991ff4f308155e45ccb23bd3564b2e6dd041db9ecedeb392232113e5faec4f665043cb1312d3ccc0f316d63e71e6bf832c2b9db3e895197c71a33b6cad7052c748acd1fb8b96e5381b73dd425782dbcd7c33afd884f6e6c9ff50c1af4bdd8fcdec5f2070bbba24e0d1b551696414cfe344bac276d2945d836040c8795c18c46b0d7cd63f5e8a297aa903ef9c70d6516ee556f30a7b935ba9a0737cb6ca958a074bcbed18b3a0dddee18e30adf5f8c1d3d01c239c59cb406d0756e4455fe6b2e959c1701d06a278ada478180fe48bd6816eca798a18a88ad5f9010e9caa3b6783248cd99453a4f546612be1b757d5bd818dc6d732926ab0685866046123b305956bba81149f2b7e03c0ca84aa0fbc9367b9df309abe8b1c571a5a0b256dd8f82fc403ea575418706782b9c857cf12b7a2514d58b56dd43793a6dfa96e9f8490b7dfccdb052bd53439d129cc2d88235fba2fa342d4e58289a79791eb9da26ee06af192ba1a3f247d78243499fadb3af111688da7b127338b9ed2acdde7d56a15a0981ca505d3d8f1524d7bc944f4b7658a136bdc0d39d4438447081678d4416cc10a594be5f5ee58bcf967360a17866b01b44e70f8325281c42eedc6ca1cbd8007acda6eefb1720fbcf76666c2d16aefb0bd3ff9bcc043c85cd93e266dd2610441ddc33b3d2ae08a11b62be35e5eb953989c8b23c465486b279503ff07d2bb73fd228580b9e7cc83ede80277ba2927a64e882ca34cb21b377ca84e08a1c020a16d50a1bbca48e686d51ccde48b923a57a5936b103787c3518220737942fed2e376d0a909154407135374e89bb934cc1d2a74647f7ff7b16d4953459d07244623985b64ea29917eb89f0b7b61546cc91d8476d58f2b4f1e3b12ea04254bfa45f88472bb92087b8148c9387e0e6bb343a4224f50904ece71484e80ba9130aa8166de743e1e3ff73a182d7541f895cd105be5faf088a41ebca7956b2608749b5e71be27d58b37806f70624627853ff0d85b9d39472826e67e6b4bfdca164c373216f550f74fb0c0f9a468f55952f898755a73b52adac0978dda3b0e65ec28d2b2b96aa66138b43c86379d2b8e69cda1b2108477e934f2556df449cf68e7cad2d147b1bac4f5be34861d7943620dcc3048e665de7ab5eb39a5a9095c0dadd01da0b9354425531997e22900e2da4de2ce910d97a104e6d7b6ffdebebeceb228446c8d0ffb4ae4484d2f9b2127740d1229f8a51b66c7610235194462f562bae1a9a72087e82f2dc8533faeb029bfcb2e5f7a29b515dcc91b549395261d3ff4fe12fc956b7398fcbac56e250fd0634a9f2fc8f5b9839d9a75ed09744c1771ed293d47e78f4df3dcc8fa797a59e60bfd316c4758353316bc105809e82405dc5e673c3a2f1faad54235d66b3620a880288733b87c9995c1c4808e18e9a6e41974fba45a12a6bcc5bb2d8ee2fb716d4f97aa4ac87bfb8941771d00a4130befe3e646ab8b1f7ce94ec9eca7030f3e4f013e264e98da1987c54e383e7dff6603b842e6826963254dbbc45917d8b1f2402469d2e3df1f380b8b94c01895f63bf4b008a4b00c422263a9df1c83daedc3d3ca114260820db83d2cd8c3ecbe8e5415ad202d1ce87c91a7200899da2797746cb2b0f16fdcaf4e751351df23bca609289e0d85838c9f73b4095df5f9fbb080cac3cf6ebb410b13dd6232ffde6a3a61484d32baa01ebfc64bf19555248b159f3a66aa8d242ef3785a40a63de57073d15952610a8c81aa10b2f51c51d8a980d0ae5d638ae6887126983e43d5b84b2a14270e43834fcba018026d8fb1ab6c9529b3631b02bee6c7217ebb2c52100a3431f5dbb86179c8022a0a293559ba0729bac5d43ba4939e13994e606be7decd78b3047685a6b1f8956e59082bde03dfe21d7c644ee295b0dc7e981e678a8c5097c84aa8b4dd3d054ca398e856b1f4cd3d229d190798c8f5c8efac6216bc50ae85bbc686b0f103bca0b3557c94ac6770451de9be12ae976bdf6d1738a21166c4c0809891e63f4e311e66fe193a66eb1404190c78d5a6fed5165cc162af60f1052986709239c30b4679b968871b7dd7f5798841ffef06dad64c08f6eca102f82b7f4280c2c803fe889a11f45ae066fa3942047e175adda48ea7999cbb7f3507049974cdeacef27d5769e15c26c3e06ec2b5f53249080d1db3cbda7797288c1cdb4b39850107cca7d8798e01b348076537ddf48c48b9df3886607366d7365508d9ea96dc3d3a0917ae2b2e824fddb16dee1b97cd927a153a7abdf6a61b3c0d626924c568549dab48791a55532131390e9fe43560aad58f8ff4143304a62303fb27cc2abce995f6f675815a915bf51d4d39ee7275efc7116c383a6db73c7403a0147622305adb131d768ec887560192f584bcc0eb1be404996b6d9f744871651d58a9d9d7ffff7668deeb08e1454dba5953cdaf24c2f7357fd9d128fcc69c7e76f951a051907d0c867fb539560bbbb93810d8ecf5477b6c7179f751421217fdee630650560dc8a2bf40a806639fa831081d10c368dbbf04d4cee731adb5d1c8cd471b6dac9b5f3b78ee5312562b6b0d536c2a53fb53a5036c2f3731368d1bbc747d7d9bb6d30b48b7c7f9ac85ea3254170e15ba9dc8b5d249f92e23984a176053001a8553260745dd121246311aa19613e6f37117eeb3152cd08ca996341f62f640c646640e8438e735298a1e04057da2e09b1f17ff556ef1160941bcc2e2ddc4a581c015beb62f3408b6e2a582daec5d6caff9b0a0cf60ee05dec8d02459eaf09327c70e1de87da024cbc075ef8ad8954fe2ef09393bc1b1f0ca255f8c71eb9663864f2f1f34a48444b621bbd5381a41e61090114ba8b08883dc8623d734f76455be16f5b09dad4d24666b56f7e68ec5141c5c1e1067325c525bad23cf50b1121f90c1754b94419cd974d221b056abe0362d0a28d3c701a3f98118fc370b24f5e9b0413372110b9fd3667cc3ec5686897791880c6e13d41aac2923a3077a71df950ef13d8fc20889864d14dc35bf5cbcd3e211f0924f343868808684972200b972663334dcdb11e81d053f7d61f484b75a7282c66ae1a683bc76f53c15bb872e5712d7b326ea18573fbf19822a1d4b9944fe75722e15e15cd607253af059373495a554f8a45d0826cf52eac753bc3d9bdead2fc171bc7bd218e99bb8e30a7d49af846270b67d0d2f11638449844e4b3ea0424d48b5d82466442a091be2583db8d29ceb22007bb9e0cd622a29522aa46b6d806ecdbfb137608cfda340b59d96241a69a70355f4e7c1461383d1db96796773d872a36c3e83b44ea085f06d13d19e83f45132c77fff9abf7152b07db9d2a1b27d050a15b8eac9496257f0452c2f85034a122afc37aa9944ab41c69259831b715d2047259870e8bdea6880b55dfc82746a4a5cf0aa27fe2d96105c5d762ea42c85df21ada2575a6d88ddf6d4256d251f72553ee569da715fe1109d52712f4e8ce94197d1fb19172d35d59cb89ff79c451e4fba5a3822c2ca9d28a7d25918de3bdf247dc2e89fbef338d4f69805e3b3be00eaa8a5190c96726cb1bf28f750180bb859ef1085f015dae79f810ac97d50a4a1af9b51d30a7fd0bf1108b3ec8efc7ce0fa6bdae87cdcad27bca41290d215040b1fa3aa1fc7056285b7cfbad4d4d266961c87615485d97fca36a9685f5c5a8bfa0c0d041107a369d611e4d9a4b5c5658861218e2bb70ca9b5fc12b74b8315ea044d487f28700ca2423061007d9e43504b32362da4596b866418dd750a0c7a8b6221579443a90b06139912069a583a2db63afa1553b186c41e03f9880e8f0d71a692a3cdf59acf19368d2485c7e3eb9346015a566ff34cdb8e5fcdffc43fcdbc80a41c04d771e5c4f49bce826bbcb87f4f082b8d54061b8c414030567b000c147ca77b8f19048accbce8d726a20f559bb880fccad33ae12906f5d23e039261c3b81cc4a968772c4e2902e57929622c6e602d608cc11e88a62b459c349266482b399fb9b8812e18058ac04604f168dce4e2990e37d5f1523859caa12c661af48428589f1a1226134b32c87b191e1c1ec3d9f41e37aa7f1bdd24dc357fd529d77fe011329207d5a559698b1fcc816a843e6ff0547f4a4eedb79b2b90d237971c5017b12cd0c29d19c7d381db9e181580a5b12204a76a0de818212282346f114a63d1e02193703d7864e2c7278e562f19f0595951d84ed0eddb98f6f047eb0a6b9d96e6dbc048ef1a88ffb1e346ca5723135858fa032fb0764c0f0e72518c6ff7839f02c900b8041ece1f7ab056fe804d82355892767efab74c9bb9e01fd5ef418479a8ca23ccf6a7d0c7826b1e5680f02c8180662fa283b6daa5057532666ae34d214ae9da0e58cd361c78860d276bcac4d55e7bebef26a8fde6e5f06420814900ac0ba90b7b4682be4941616835e453ffa733ebe78ce59a1ad55c4a612989003d2aca07969830b716e04f69efcd5e48b19581d8eebb30ec56dd710296315b2acdb3897c59d59ea7b2aaaa6eadb13ecc96cb12f3260c1bf2573aba3a107bef1f5c2cadc4e6cd79d4f41e256f21ae1973f63dd2ac20a90bf28e14a9d6e41934b191ccf0929e68b7b01008394d6beb0297875ae21c4777af831131da1931e0545a60cac800bc4c39557a18519e2f4010e18eb4da001b3a259f600761afe02d6025d61440eb604278c5895960a69fa4bb3989331f29ffc6cd9c754aafa25d25bd32f6011f8e29a8c1f6602f147a09b26ddb06e4a55f1f847f36965dcea0261401bac0f9dd313baf8dc9b6f8d27c3ec6a96733ac3de512c90a55dc51ffc160e128bca6ecf53d717d29f83a897b527edd15666c6e10fffafcba642ad71410c7c6cd8c2a80372f12ffa0316ea63a470fb10559198c62125c94f5d058f1f4c7c9e83a6447e7479facc818e0f0f58cb87b64488484366fbc0c05a1427860dd2de976f2310dc38c6ee9de18db038434c67b4c2b85baacae318a6f38358d1d04a5edb2d9fa7699e8fe387426f7fe3071ec0d7c15b6707cd6eee6313acd9f5489a7299e1bc54866d2360e27941edb5e876ed6083d2b5a6275600fd7e6ed53931fc9129291f53cc0edc136a63ffdfbb07303e7200937c28dab3d1a2fc05935c7bea02052d5173a65e1ad1f26c9019eac688696539234b0f47c9d1267eb187ae2bf99a336dc0654bb1c5768e380167359e2299f77a5c8cd2f14dd93c0be04fc44c9c19c6e0fe5d55f73e410cc43b3da3d23c92d0205089e62aa656965254133921d4672b77224e133f88a40d77c807c9a8112376a85ed1479c67415108c8e58839eedf1849825b37373127ec361e31c152dcc18237810aab5402e5350287e041fd422c0aca4dbde1ee097aacc5d76fc2cba3c94933b48ee59d30d450ffb088fac6edd778a2bb2956ab61d01d1912ac500ed81eb983eebe26884122b01c3c8e376411d8e06a7045be5c116da2ad34ecce6d938f73cbd6774c4d4bc1542964210316c2a817fbebff1748cba56b38a00dd1a07aec5c47bd4b1ed8f343c9cdaf3a505a83d71be3e38de66e4cf50833fc80e5dc6b44271d94186345b6b53db2231c1d7fae092a72c2c08cb2fe9ea658650112abc5afa9dea72857798051ac3900a3ac7900a6280b5807b84aa26089cb455d5b9f2751aa5980e46271d7ebdfd3944b1600b9b5e8f5f2f4f4a070600f048c73a96e4801a54ba5548f2ee64bc3d7796421824477ad224179fe51179619aa010a6cf90789e9955bbd865f213e3661dc1708b1011578f82be4237addea553fa3f3971353028f673b3e230aebef7dfc51d61867d1e2157741b813be10b575f61d7ff5620c7dd4a0290b64951900ea94f0597711917110776a50c2f1f566035af313b2da190b575b4091cc17711838becd304e0eb805b7bca836f713b4c3f3d9ed03648f2242b2c3b18c34502ca2ed7bdc81f906debd4a8b7ee9e1e29e69f8d3966378ff83ca21afa4d116fa464c820c8994d8af2b33336e10315e0c07f51b59e2ef2c9974cfc5f5706854e6939fa17ffc689444edaa2bc16eeab3a08b45a83f7ab32a6217e0ca6cb0d97b3cfaa4532700f1863ba05d84ec0611c4536083205b0e208c94a2dca1d469d3844f99ce2a40b7f2607b476c7a73c2cfa6da74f12ecab0eea76c10d6847b04da53ca7054370b553348a1e70854cc04dcc3bc88fe7fac1b5653983f3d020e05843248e5b1b88cb2971630dc28acc38650cd9f8872c998759442907b7aae448cacc971b62d4587be2c52e8ba436c7ed0b806345a5753cb6d110aa2eda1acb73f51e25f305ac2a5770f90705c2a6b93ca4533cdd1a44cd8cb47585081c8b988792f46a32c3a2cc5a090b13b4efbca808e7aea15383bfec7481aad69e2b7eba883df79bb870533ca569f23bda30ef0d1ee59c0d6577adbdddd3a713c04158480306e4a6318cd858c6e8d62a7e240ffe9471fbd3a0e0ace43af034b9ef0fdf24c2d90a3487a3b44dd49ff3d990e9a87a1fa26e0d4227f130e6727047bb62eb0b22bb7226b990436f996569bc58ddd195901d7a9ec43c6b08e0ec70ca83c3147f1f10ee73af2b41ddf811239e3e18a03f2f7aa2a3fc54e1e2e0f2fe1cc58601b8f1c9650a7ec283e250fd5215d095717897f6fabc5604c0605fd5db4222b7168e187ac2179eb72148cdb481639fd3d31d81bc743cf5006c1117213332656674c0c6574bffdffff4821fb0d5c9cde36a6c6d6a1627dff1b3023924830b622cb1f2697a36575d88621bcb9a3ff4d30944ad0d2fde779f56198f130940abcec2323b8419bd18c1f0e01d7e7b7d240fe1ec1e3332a3666726e581bb7d2cfa92db6e78dd089935ea7580f32a390950e58a2b94b96f886372fbd1681856cdbca2ecca420a405449b7418e8734bb84101b9f484d00390c4548217f60d9084fbd2861d9bb0981fe16d2fccbcf407c93563e6532d421c3ade8ef2a01cbc32301067a23f737d5e635928df69733701a1797236714a26336766b44b95fb5d03e76c4b5ccafc55971c1938d684659b66ae09d49f2fa4fc1d934d0fb51005e82743364d6910d1f278d65bd6746a6deaba1c07546681f6cb4724a792ff7a7e51acfc71d265c234274041e56013da3a344d25df54c4946fe162faceba592f4e3e49435a46938e57b69159adabc60c1ff30dfda907e02fe70bd337f0328128634dd75a7c65dab00b4a76a26259fbd17c47e1f9956cb4a0207f6be82af6a4535d685dd62790f1acb0f4e0003ca124e00c83683a74b8211b643e87e2e1da77087feb07a151d4b097c9167491d568342779ba30e2bf55bd79e51d6949ee3ea7f1776c3e233d437e2ca9f9c0d10bfa94595a8fa9bed48a71320f3ef531e71994ff6dd10776bf5a1c6af4a41ae82c3a135c95983c900b6b8f067384595fa6039c93b063f45f267c338fab83d32e3d524f432efca3edd49e7edc6be5c34680dc7a3cef32850fc4faabe50e28bbd0cfd44c4df37b67d004aa403d1368b9a54f5bf86386c9953a12773c6e66af81375b390fe605b71b6573b67e7d445b9a010a5011a088bc381bc211e1e3d1c1a9e681b159cb03e4f513c5d73d9efb716371096296e1caf209e9058c0624ad01db62eebd091f4e21e0c100ba3c006a5577cb5560563f16bbf5470219d1336fd76cfceaae276a11cd2114eface2869315e38ee842113980777b552e0d74090dd1bfa668884365727da465ffe705cc672b00c39fee8a6808a0d78b000d5866e32dc8138a03fc57e3b96c93fffc176d53593ce070a90e77b6eab7e7a5716e04af182c9bf905195a8b71ea1a253250c0c7350ceb256080dd4a0d7cbb1a985717f82e619dbc1a320b5f5c7854a5c2017d4fa1392468e5204bb422ad05b84215391d6e2d2c7c67ca33e3d514af992b5dc232cec362890a4b1b43ba099bb88207a9a8e7142edaed497517df257d094af944587afada495a6e2a0daa3ddad00f59fba51b7505b2c934451d305611f7b70322730795f5960e97a6b330886fb3b1ee5264c31fd94a9000ade96f702adb1736beae0d4a1a66c36a4cda1ac08ed0204c0d7335bfccb50b944936114ac7d62c63a2d4869f4538bbb54ba8c71822de9e3d30cd48c16a1319062e2f323fc6aa5df4aab55dbfdd3a429e2c59e204d4a21658c0599602db63c91f288b7af146e705353457c1a8459ded1523bd452d2ad98a5eab0950b481f11c6f90b237646d3d7ce8c3913c9ceba9aa03b22ef176a34328f9ef92b1ff341e6130ad311608069c313bce3ab874e156a14c9a4bbed05a8a75818c54341ec99a6d1b08ce83a290228337e1d8972892cb0d2b5231a3a1ce97b41a0278512bc63b51970da3234fdafbd758b1cb3589df78b3ce6b2c1be7aa3f851625612e6bf8bb438456f4bf9e06554b1c870c51a320627da886018de9b250a1584b007058f47069f997809d908ed0544e97553535fc12d61444796b91cef3ceda301b20bf9c8647d3fa416111de2d679664c492de3e34a8fe784197119f0c9672467dd15cd6f4d3016b66aaa41e2e3276644f7f9735741283ae29627a57cad2ec561e9b0c952298a08e88da267a95f9809658fd2c24be7577ec001ff55ef64f32942df0cedc745432ccc7345dc32894123b07556918a9a57f8b58fc611d80caa76ed725807647a0f2915c0facf64c1329f232a4905b5c444fe2f0ee65cd74b0d68b6981a76567c9877b00f2ea06d5e7c28e844cb3902b38592bfaffb456ce08f9bb821e736f2bf8524655f9e5900d1712b18f991581451fee2af668894da8af9af8dc5659dfe5ad0f6a2bfa385980bd3a6d191fb8330b120ed147fe804201ec5adabbd8b04a56711e8cd46292f783ccaaa0da2c4a2efff7028fd650995541c175baf02c26b758fd29ce250295d91510ae60da475de3660c812a867bc82e8685b06522d33104c0ff2b154cad9545c616e3147dafe30208502d07eefb1c1fc920c30bbf0682744064290db29eff14b07b874c59840779041f29eab4b7b8cfb2bf8b980eea6e6c36fe5cdc087ee9b986c89c4838bdc07420bf6ad83bc8d4180d0a5b572a1aa1c161d555e5463668583a54d9c80d0a4bf72a1ab5c161d7a18a63ecb1dce4c1b283981bd76061eb4a5b1e586a5de0b0d74d64373ad10018ec76d294b1c1c0a27bc946161656dd6b307290b0eb4c937107879dbab775a091a7f79eb861d749443696bccfff52a53ec7886b4b9cced6760cfca8bc3e42130c7959e575c3cdcf02f1196d98609e2c60c0e653cb4a5f4bb46c2499f4ccf342bd91040c4a4b961cb2f72602bbbb6277e5f303b61306ba14821dbb94330ffb1b95bb064d6c1110f765e0b1bdf06d5f1e1ca48bcd5966f168aa87c82e53c0dd80fcd288ddda43cfe2139f95c61e3239376dd482552d58b2aa6512d97cdefd83d71d936e613a2c3c40e1902005b443ff946a499f6706b91312fbe4e923190928219776afcb8ddd03f0e078ac996a0286afa54643c30ec27b793ac0be8ad982b88a9df9d8465665b3703338f2c7d723c0c3e8f067f835e94c565add4067a2a5e8f78c5948a959c302a3e5ac55044f80eb774c9859e86fd5202d1de0fa3e02aef7e15c2a6ba30c37a4665c3976d07db2c942206b3871e2af1a732bb64ff7dbe29f65faba605383f6c7f19fa5fb332cacd38936c12076c4b7bb20fd0a7135f8fb6e3305b46b5fcee82b9fd3c058ebce4398a1e99c6fa073d3af1877f54511a4dd639a5b04485b49279f9faabec2499cbc0d2211bbacc4312ff624b9582493df1424ab8dc7f17748308357c3fc9cb3484f0848f128f1d7690bd38f86db1285df6a2f09455586275e148ff09ebfbddf2e91b1667fc1b01bc59dad6e22872675a825846acfe3679275836c73c1e0d182127c8c997d2588b87b43c8286234ca73f023556f705a0052bc19ee94bf9448d91b02c747debe7cf1f4c2fa973aacb9d05f73249f2bb6c531efb9cd24c5aaff47a3fd96d14122181c98410f1ecb2352fc13042133d1a4b6a728fa065895717f62dc1da01cf22fa7f4c588bc2b1d34310973f2dd22df56a171a8cfb5bbe76dd18fbb8c5b498f26c4f3853fc68757690e3e599fd63783bf75b9f566c05ad0fa204ba8cb56f7db4f0236234cb70a67c433cfa9523496e4ca2c4864146907d8ecaca7ec30d26d6c49bf0a2962bca479fcb2437568a1c57d7a368ed03c161df06d63ad32b6ff643115395849809911df22bf0f0a592125f5dde492805ccbc49d9f9df9089255d6a27be43aefbef09ce0436d8cb614346ae822b172926f5f3311237c8916377838ddd53ca0c13ab22d58fb76acb5c0362b1bd8c92b3a6e6e30de82385b5cc9845336bcea4c62d584f3d921fc24b1e2a404f60f7f80180342f6f23bdbc8c712f76ba7375d31d64f0d26f6a90f7acd15fe93f55668296dbd386475ee38f77f58b60d1abeb7cc050e15de047402f6c23f26ea33ad4738a5472f31d1798b0e5975a1e87e388c883b490a6bec628c9dbc1dc0d35f4a15dc75b0c9f8d2c740c2dcab5ca55a4ef99d7f6eaed50c3e3766b05c65b7c70044a73c5d31a0f6fc7341814af889a228199b2ad45a1013f1259c74e502e4d43fe3736df154b757d4c2dba30e2f450a65e3d17b3b5ec8c196d9fba434ccb85ae6ee2eab3d147ac95dc16430497b7e2b0283a938e045aab91694a70caf39a6976c5cb65bc86fba383651d9bb2ac1394769c0a5f30039120d503a782423888a607dc8caa4f847d0e3b931920d97618431f83b0856a554ab5ac48df1e600c856a58326e2d0815d9ff4058abf8661e4078ab01cd8a56401f845abb9d45d2afc03a86664f8e9c81853e0c4737bd896f705b27eeeff76354814a5a0f0836d89901622d0cfc1744e8cd4c2c903cd169a9989328bb205e5068de76deda56eeaf406780dbce9e0bad220e56df892149ea4a042fb622106c47ca7089639cfae6caaeb2708a62873c61ad60ba4dc6f999dc89adeed9403ab0199567d9b7e799a54b693df1cdc10ca48b1f151ecd2ee9f0dae0a13ba6d4f5cd0144c8e40db398ca1f8f040f5638088b0ac24ee589a64d5a4de4933ed0a08afdcd40c91d091836c6bb966b632623ec6c774f23c1400ff2ec17cfab18214a17721e370f811ce725a2f8735e723690b92d3070bfcd5aba435c7a6367be97d9d83928bab14adea2637d54524f54b95c8362c060cb59d397a6c89baf82dba06725c06a9605c49fbd26ccdc20bff7c1571047ac2f04ce43e97875f7c875374dacf62ffc007d442f592c6cff6c36fbcc8ebda74452740ea790fb657b4bd856d9037bd7cfbec45d88c6582a3f7b96b7dceeefa7d39a2055b826a33a2e8ee4519129fdae48c4b0b080cc47d31bb2f6851b64b52188a645156ccc2f27440ed7d2555f9682981cd957ba79a7ce49a8d0479cd1fb0ab37b11529ac18e88000f719b443d01f9cb2c2f38007eca9f95fbe0a5c37a401562591cc100bada8cd7b5d443fd6daa05e4ad8db4fa52763e4675a45e9e9a31ac8b720c0c18ef8ed0a6c067acc7d9fc3e7b50203eda036f18bf2d63e842945241445f068d73d941b23c9b5098e30b8b981493a15079fd4434c9de8d0bc737c9de96a4f154329870ca06462105b6931e2dbc83e07ed61dba7f2e698ed45a40fda8782009c740725f3374175f47bd98c67e8585b52f680c339552def8b29bfad3440e4534f191c88dfa1bb6e51457161e5c5fefc72b94afe86baae7a2dcc8b6e353e7b2440b15e6d3c9056ec062eb2faa64d18ed9484ec98c0475c74ddf4e37df84b00d21df259af076808b6856681f55eeb26c7116cb15e1d447c778d034d12cb09603e921cd5e8f63d3eb6e6dcc9d330378d01b5265ee2b22bf3d47eec4fc6c43a3858fbc4fed8dfc0b66403330984ea49cd7f3d192daee34788b5dcf4cd8c024cece1c6b83582ae581f1a88b06c896b770df3dcbd312b30083aac98af8386afa60b3add96a3cce4121789d81831cf1c590c7af98edb63d455fa67a1e1ee40975b52462a1d7b22b7c7ac72d502a2853569a8288374ac752ebad443712d7326d8500121c4be5cafc4994c76f0a54517d426442e9670183e9668543820cce05c1cd273109c3040384b4d58910186619021cd3b4c391d63b8fd7046ae3a0fa22f6a477ef6dbae94d35470105fa7f0abc629d43bebc0854b533e87cd461d3ad7d8a1d8c3dce412904b600824650b89bfb581c519b481e55dd64f894efc3b31055c2f32bc27016e3be362a2c31db6833729baf668b64c785dad0bf3011441b94dc86f1146b2a688592128962dafe0ce45054afa43fea12071e2f5e618b6d25fb9ebdbd5df5da355c37b0d117e71074abc66fe8e5c8c27a27d23bae46f7856101102484de3825490384d8545ce3e76e8404908e7a98bd8d59eacd0019a72464dc33387c4bb99d6d067cb52b2ed8ca08cb7d969aab9120559043f4d34ee603f24d31509b3ca86a36d5ddf94d18aed155c80f2ea0e7feab6dde2679b745ee2f51dbdf91ad016cbf7c6a852744e55dacd62993d6e4f059960870c57fb4263d2d7c31eba4b559ddd0562130581c3c73580b422f6863446883923bb2a395fc65e690158f82d5836cf3d367d9f349e5b42e1b911e1812a92af59d5da83c06069d4f9201f70849b400ceb0dd1ef725f0b031f5d84ea49e4e64fc3e37d8cc23569f668b530b15341c60e503d3c7c17ace183ca437df17b349ab8f212ef08f6a3ffaa82c24d7208d6196fe14a1e5d0ac65d08223a20b4ed3e54e7249c06212f1d67d75fbeb04b8f132f45f4cde70cec1c8407ccf9a5d8840c254345341b2b8651bf9e7e905264a08a754e71dfac73e760d45217d64dc461e0c7a90d98b0a1604fa6dc7091159e7210c0ee6fc0da560abb824eaea8a1633c69bc4c74e059c08e808546a1b5278920e40290cd596d00e0f538390b751d1eac270e2864ce3f2db6304992047029d38529414ad9b9c0478ec067cc71bcb63ab9c8f0819a348eee270bf2c8a98c2b4cbc06c4f8919a71d0a0d447cac32ab43463a56dce467daa60b786ab398a78868b781c61e93ec48315c9b6bda4d2197c36ee6d921d34b846d053c47649d7cee3e3a771cf35499b940d66f451952b559ab52910bfdd049e040bd0b965911615e07a48e4333083bb7420b7bf7956264cfbb7b64ad87e05dcc197245415378ff8ab5970876d633ad58fd7c34b1917d1ecd24a709775c85b145f2542da3c39f7034c0b1923688ba4e2f6eac86aeaa2f383e188c084c9a48757249700280dc183296f58c2abe0cbef0b84fc138527845ea2970bab50ed8c37f5d0304b2ba811f0bfb0d94e39b3ecf8b699cd4eef4bb85ba2aafb95e87b5ca3c601e8d189b0f162843a92dc0852e5b1eae9f93f7f91a41b8690d17c6e2a613df87984523412af658a6aa08408a5de93b30de04062eff41e0ca8d7fb11b17970421945c6607294d722624631d640cd78e2284763b329554d872d32584f841ca2cb0a1bcca07a2e3997ccc9a908b418d170c673857489918c67b6532cb6ee47df2c33f606f5a2813ba22fc23b8e2bab43115750f5ee09b897643aab35137db6e9fa279ae387ab40051006e39923a88d7ee3a23bb64b4dd7859b363638d1725bcf7d20aa081bf229c29e24bd8879bdf0d26af0a16cbbd9d4519f18554700c0fd3decf01b00f7112a423c0c1cca4d3e721327888a30c695463e215ef5d01a6dc21ed3524dc2c2749a3f494ddc208f4b401890a851aa4e4242f1b86d3fde781c216146c1a5593c278bf2202cf2ca7603aa5fbd811323dc11e1b81515761fd0c1aab5549a9f738f2ea9b54992889078a8e5242f33f807ae1b33207439cc184eb2bc8648f68f46cfc0714f6815356bd411b3c9213fb7beb674d5d07cbed45a5b33bedc35a194b0c5ecd66e6e3ddf68de77a2699e179c8586a16eb82fce20e2d0e1a134e3028d00ff372a33efd032246176cd6da4fe6dbcf43ca9fb85c7ab3e7883bb56ccd259b6a209443aeb576ee38d3adb2b632689778aa8262ff6fe7c0fdcd90e9d5e276e4871ec92abb1781af73902c34e4c30453c023874cc00e1981c28102e16c28a8e5484d0adad7956d16ed7861e43aa270fed2d5728b35da529a0ba43420396165ed3ac7459ccbdfaf3d80d56fb8a17e318253c0f2c14b0cc54697b78a0f8da9f37e51244202cd62551a1d3037ba688023989b8de1a6db86e636787f5e883122ae39ff40566908db1086dfcfec44da8b1ea24ca2930ffa8cd242a3aaf23af5329f2603cb92d3fc0e3e410d7f32cf59a1ac5fc02ebf68807f2cf81773cf07d290328747d5929e3101e21a4644c51554379f0cc72f1413442905a0c917040770334a458815e9beb9b58b0b2fe200369c93ea80a609babc465545e37053d109892fea5e269420d5547c6b0b4b7aa192aad90e2ab1d40515c7053529985d8691df9b00fa97e2721680229c2e03af9bf30f02988a170ee3b21736641b96fbbab86320141e52ad0163d8deaa9e244702b760499618a14a7bb0d9cb65e263ce5108ac70cef37bfb1ccf8a76ce09bcc2a347c50a2c30cc3417630b7570bbc63e8a3b79ad035603c6645b8894c670495547cf5c9086005bc0a12f27ffe16473d44de248966a1c003cd4661731c6a5a2a8766fd55a6ab2b557ca8075e088de8a6f49afc42603e370009670a70025bdb8910b054042f673f88e3aab760fc9ddaf487f5d35269c58494ce423d54e289403c697c47808d143a0cb93fc548071d95072291b2cd9494b36c83248bdd4216d90cb25800b4d647afa2940fff88fcd1ec3d346aa4a0882ba47371a4bb0b3a5e17fb56b912459b6091110b929d0677cbd227c307b1e4d047601752dcc8eff9ebcec376bc75b8766c8ee0c6641b6fa74c6be2b4d3b2bce4be99fab1f22dc32dff798ba47911be3b0eef24b10e066e8321c9331487f41cc5546dd1b73cea4e3ad59387a26296590f25066f2ea9ecb87bb8922786ca482de9517f1624541cacaf4f3432d58e83f67c30fc97793155e74d9e1db4c23263c721a90d6ec8dc17ccfd0e4c83f3522d4ee2170249e03a616308bb5330ee67d7d7d710695a78cbb3629dcdab246012707d7c72261b5902dbd9412c647e938916190392aeeab92c1c7a93ee24a4a032295c31e9e6eb5506feb844554ab952d82272deb0ab4c2e22b6e2b157a60515e0dc898a327ae10fa2ff3ea658267d51aeea1446dc73b12cb609404f5b3015bc9001c9f99a22966b17ee3b2139daf7b68ced48c2c8466f60a06817f8ede0ea174272ee307711df53b3f961435c33fb6e42ab5efd0eaca1d09d0d6face4a8f82463dd06d3ad33aba36a255232c33c54df7f5b3cf52eadad847d005de9a501c1dbe28c2534837b897f903f57508643d7b49e04eb9457cfefcf0f6013f008610b2c661a7dd8e6732560ac69414d5abda0f7827d114b00e954acdb5f5bf0b4edbff669f333faeb0038a408c74f76067b76958192d987a73510bd1f80f0e41473783ecab7c81a9a2a69717305531f4afdd368c4963e2660925edeaa9cc84e727d8ee6f3e3e98c6b12eb8c586d9a8f3e9390ea7a2636cf8d39010e9b79a01906b86387b84019e3e13e6d93acf592b3f7188a560cdc0f927bc335016dcbf9680472b4ffa383a8663b3f49d96e38b7eed77a22ef57258ce3459ae37f687d57c0822374b3702df0e25c48d12dda477f71081d24e564fe565dd1a18e3b5a4865cdee547567034b62af1504d770b51d0cba46c7e1aeaf1278e70a7a9ca91a03793226faca4196d46776c85ce3de108765b039acdb7bdb1f60646345f511eb718ace56720074db28a21d857e200e71ea9d45a10a02310e42930c7e8fe77674df3b0e37ff5134442a05ceb090ac8b94115860355e4d030523a066f1944380a744a11e44eff575558d29a35b74fcea0a3587e7804118ab715b8381ddabc798faeeaf1b317f71d847e5e1fa510f9d4d8997df6c5ddff8d38da1126da02992076919925fc0f820e4d5819f206a6d80a11cc8e808586d9a6e52b4d98e863660d952ac1003d3d6eb78ff6cfe59adc2d570f3a63a1195da7ef6ba0e5a6015cf10ccf1c09753f0ce49b290ad09d7366ee3ac9c6461bcd0014fc7e337c74b6a3590a18f5c2ceca8fe861e530f04293dd6090cc15ba122dbd20ed9bdbca4b6ab778b097cb44bba10706ff88aba5a6ae01dce54fc97a3924db401a331fea74093951a89e14dbfd339b9f1ec64d88a617492ad584371ac10e0382448a026c63c7044b6b5c2d16918ad7ddc8c3b400e7f6fe371dbb4575a14923eee12709fab5daea7d86fb32e1c0e92267aa6f54bdf69faf5814d22626ba23b7a351fb8437e8d411369ee27a2aff99bba1e3b7877c83438fc469e35312a0caf4d33fe6fb7a88c01bffbb0a289c8daaa808e8b992179dca9274288267ccbadb5646b446990dd0edd1755df12b6afd3d46538dc3dd6d2d18bd3b4aa5c8404f28db9d6658174c5065c076ba295a9f9543f348f6c25b40abd39ac9969cc948a22456b312f8bb5f757350718c1520d3fd6a48e547900a05fe3c71ae06e864975760494c96f2ec7ed6073fe532bbf22eaaba3b4694378c5575b7cd9e47bebedf809a12645198027434231ba05b5f20b3dccbf1f84a94ecb584d934270f48a4892600302e76585d21b885ef59e420f2ea7f4c6d7a06426425541b57091a82c0f6efbc8480ebc20295205163e4b1935aa47525747e9d305472eec0f6ce7c6d8790dba6122693d021bf3f614db6c0db393fc8c20c38cd9a7d075fa9a77340a5232e382b82292863eb29c0e8e83b5484f8258a251431d0bb0b190c03f67c49ee01eee57e398b75ac5a4b9dab1e5b1723a4926fcf3865b75329deac911f5571d32ec2d99405b8971c418f3c5b2d6aa011243ff0355fc0feba0dc129fe86e749fb6489bf8d28210315ae0936cc58291d59ee1e49beb44a0d64391431df4287b17512ea3693c0f1dcd6f0fad1257b40bf697c9849a2c3ad55992ffae2f5905273249296ab518cb8ccc169054bb0ecc22610ff9f20cb2a1da14d768ef260e7ef0884861a1b677d95c72ff0ee62617e56a9c0945c0514154bcdf3761efa0648c2762a2c56a1e779011802e50d08074cac2b19a656c479f8ecb89c59d878adec662bc9c70b054a42c09759d83c0a6cc83c0ef12d6a5d3fdc73fe71307c76dd9646254f736aef9265eda9efa5c2e3b465f5be4276ffe719a77a54647f8b88b086067bddc1c99bf8d6d6a5de737c77a4d83f1c9f929130f7691055f13d520fda64b1d5a7675b2f887a717b4ef20a4bd2706ee1d90d4104aaa60b70e81430a3d0253fdf0d1c32a3536c7cc398da865173905ee6c0e616b845054d075c9208387ab6e64b53af60d756771e6ad2d3513534d8b14970514904c8d586c98423d1ed02d10a7c7d068159b6bcea6890319e075a529ff0871e9997870c032457deb83b1e5923240dd246deb5bdef2d64bdbec55e3b4f0a008eb2a351b0648b6090340595f7321c8ad4e29127a08393cb568623e60b41e7b339fe626fafb5a154248d34b26f649877403e8c7124d280b97e2adf569aefd94bfcc0393ba46e5f01d93844613b6683e976e67ee9a58693a80db216eb012d85267e8795445dbaf1390cf51b7c928b742b0fb872fbb12ef7277c7cb6f0d69eb37fea8c0586fec4f68e2a98e07bcb4089848c1a62eadd93542e0da984173eb0c98fe1e2622e68595ff6c3f44e03e048dccdfa9569d07944564259765a1a32101910bb7e1a51d56c505a5d4a7ed9f9209653b758230fdf64e307e455a556091c539fb78cec6b7a8d6865fadca4134aff2622a1ab27cc38c492de732ddfdcc0d90ea8c3a946a7d671fc21516fa116c74cb4343d9fb67e813817f2b688f1b88af313578558304e7c84d559d454c377049c1875c6f22120c190d46a50b1b8c43c1412a5a663ab252ba06121a31e7266d80d20a89bd5e422235295d833cb193129957c5c27bb84f0c95fb2f3e9c0d6290a997f6e17ce4200fb42ea89ad599029ffe8f9a1932911363884dda9ed77d8dc1769dc9aa994806106825e01c2e67bee81837538b1a910773b55536bb2b85b5319600dda497c2a1986c2548685d9634f0e0d546b2f1a4226f4f0904d99901ff8e176432055f001a6522d4386fff8dd66106de2b3344b651c199e12f83c1374b883daa73d423b9f0fd70b5a1d5d65ffdaff78722e9e5c6238c9d684d3a908120d142e97e9500b454e2f741883e07bed8796dd19e83423b8e74dcba05960a65788882c32d1f928e50d362c19f6299ca54e0f956e98c769d6dfd020b4f6f5cc7dfa95cf52d9c9459a417b4f700402b6d453a244ea2e36f3e25d6143abdd134a70707c7bbb7fdb365f55b3f14e6c0493fceee15e923e496f2a8911a550440b7737cb3c4f61e119f4a5d8f9c15a3a9c235414a88a948b3843ae49ce0b35d386a8218d62f60c3c5de0dc5ecae6ce36ad9a698239d2000d52c3b11e89ef89bc681af057194e4a29531bc8e15fa64fe94a11aa59b8ad2ddaf99b226945afdafb45fea2517a12be729097665a4cbe587b8ce74dea19b55c6408a853933f4ab5aac72200d5935489a8eac9d45590cdc0090535ef9e1694c45a09a5769671ac9889d051cb65d91eb017deda27fb553699753366a7a5eb654ea7ca6336abf14f3e3051bb2c416da27773d96c48efece519482e4008228d1a10e234421305bea0228615e928ff98de4c929ca779d49c350d1018d88749f2e0f9161c56064f20b79a7276e4cd1539743e5033c320dc9ef0c3d5419520488dda7d7bb2c9da81314e01cc3c9bd90eb90c008023da6ec578d89781591ab190ad807a9cd2fc7ce4dd63360a05b06d77ddc462af68fe97182c10addb9da1e50c2789270c428145df86893286c0a9c7a98dac77c78d5253191f4ff3d7ff7364e701474d0fc273d36525861c7fd1ffb8f5420903d3a662c48d9394704e82840c850ad69d9f9a02ddefa682faf4a98736f80fb8ca721c57d62ea8a09fe7b7fe87ad7bf8d65fb9947ed6ea56d608ddd9dec3aece89e0301de9808e56e4fcd3e7a2cf900f9bf414a583a653dd907723939ca62e95869fd60141f2c7b6c5acb8e597358846d80fd722c483d8ba5164c4c0ddec516c0def803946025c23a88e2b4b9249c6c1544c4c829e853681b600a1a7ca1487835c523038e7ee847a7c0db4dad6309e5acf157352a5d58eb830ee90ad34fd8dc41f37c05ab25bfeee3e72abf89863db83ae58ecae63b8f9651dc4f848cb68384eb4692b0946876b63911b807470ff866287bf8da14d0d1ba5f7bd46c2bbbca241b44af51b89ba869570f17f21352a0d6d84302eadc9fe7dcb5032518debcede7463bd4a1e96dbfc66538a0acd7600f834d351ea04bc7593b640130209a50651b2dfa37957958a06ea42c3a330b967a1ee678f7a236f81204e92b1fd6b639f32f822349c457e666f913f6ec1a529232e55142c0fc4f3d0ae0165b891322b737af444b473640e9f48fae32481771b4414b0c9a93d259cc329fc8ac4e6029168b4b97e7a603474ad305bbd8b73d19e6e2f7415fa49950741fadad0d58db35640ea093de94b07eb3dc4f1d0b02fe57ef1263b53fdf5ed5e8844416a038014124c58c25e22588bff34126062ed7ff8c4d1578469b91a4246f48c8fa619cc11938668e0c335385fd36f0906335999e38cc24354d7b61e5db08a3577a077e0beaa07ccc88740a7a2a0458fa9b77b409cf76768c18e62a5dd7172c3cfe69247fc5450128fd1afc61bd4f7f260dae24bd0beb91211198ad5edda17a69d12ca707a0e37100afbf59ceab8a5b3492946c110b4c2517ac15f116a6fb0565aa8b21dc87524e0fa77cc005483a8d5ca7b04668f1b41c5a4c0d8ec31b9d4674c4c82cd5a570428feaa2a854450b17025d765732fd9b75ba163c10f8fedeb3c44bb4746465245b331451807ce305667436f22be0830c6fa98d31206c161281984c0e7bc58de83e8f44bb2a301a9d135a510b141e3ae3f5cf94225e2add6288c3469586e9d45853f3ae4ac49bb536ffcb1d365268e431fd0705c9a6194724beb4b771752a65ce75e2436a47283cd23036c4de85daf57f09af015c9fea49990b56e91b9d5bb4076a07aff0c7f96aace05e810437aab950a71408d806192d288f01f46818c0eb37db9c051d8754acc0323e7ae1f6b0e22a1ee90b163edf68545fc583009a66cd7d31f5a13f5307be2022b33a7c67742b85ad5d944b8544ee821ee947e8c13a229b0b98418d060099beb3dc8f1a7ed02e0849506cec2b942a78da9cfe59e0e760618a95ab18a5480b2b7a81fe58c6c9eebe3793f1424c2d1b4af8f04315699e7d9786ccbc32b04f3adb2ccee77adedb48fcfb237587e7d5007a5c9a57147a43d686932a6023477a23db6f59f8dfc41ba145862b5c6b545d1c211e0258349b6a919183b40dca891aca397ee2455249b801d79f943ff815fd2a23fc0dfff1fb2f13bf3433d75ad9346940323889f20083a5d40d69b83b010835832e7276e8d74826e15de3281c74c33c14a30a755eb836eca84f201f619a66875a03c5ee1df766129a8f47666d4c41b1021b611d19f38824fd1a235dc7d17e471c94afdcf3ebc7f92755a44b753dd48c57e4162aae88e4191908c076ba0fbd7f158420c4a151f499aeab21af06b0477d2bdeccf414d37402f1728369162cf4a1a68b4c20810a8e773a31b12a528f269034272cd619b015266f29dd0d5f5194751509b103c4fe86bfd007817a7e8f2e2eca96cbc2113be75beb10088ea7fa37e67750f58d1f606be96c26b31abd4535dde4c73ee6da0b2413e034c3ca07c88a9630a998f17f67fef22614412e91e5b231c96420f13980008088203dad2169b626a151a572564e4dba19fd401da95b9ea7c30a3fcd7588c29184577331a9ca948d7bda475cef0942aac924a49c40b22c1474550eb13728143815c5606b4c85789cfac1f91cc10d575ff4b409970bc92467e9749cb008bd5f52f4d00bea84090663b5bbed9962de326578ae9b87b3f5af8243ed62295ab293ac2a6758b6b891f075f9c5b3e58b2d23558e675daa106081ba9d5655d9220a4ecb8a591f64e887b43775fe687aa372fcda0afe60ac8c8fe29c22cbf71c95edeea6f3f8fdfc7c4824f5fbeec0c89043ea2e5e44659ee8bce57d1db8891eb1a287f42a504d03e1c26009ad8e9792c5d3f4dc323b7060e75a4b2f85650b649337ed7da48e14c9d7140df45c9ce63dbc158725e3c3d4e6cfe6b746e1327c7eb05265e8c2d6ae9e5f967076c96dad1d82652b1780713b538c81698a271f4b2e4fa935257b45c7c45600481056a6ed2dc426386c581f41229a366f9f0a1651b18dfbf038d7f6f59623bdab25ab845ac98c15819bb743785634e09e5395bff3bd4c38109e3bf6a4097d9f33e38cbad79c8b65cd37fefc6a61be32640e3b24e942ddb7d966fc60fb8deef1b092fe722e3787bf6d486653a0cd04e33dbd9d480d3b725bc541fe23ce5c53ea00dc502af5bd804463f496daa2a93cf74676b369549f9843e4db2625a06b7ede86fb1151378183077aafa482eba8c0ce4735647bd4a6ca3c8ad1102e2b35bbf1776a89302e037ce37d431b477982a6786052a5d819603cea940a97a62fd294b2c466981d4a15ae5644535ee5acfdca492f24006fa04807783b4b1de09fa0e0aa3104cd8484c6e21d467b516075f6a5b5ed4640098df4ef217de03c5d2ddccf4252d83acc304683dcae0f923e24c0ff12bba81f4b99cd05b0bec84262358620c7e1dfca43f6733c1f0508380609a6561066db0e05c0201f8b7454ea4df46d5840a7459918176669f09b8d8859bfd50f9a9f06d7acc7656d7ad9d9dc38ad22e164f47dc17c64c20a3af57d6042c78b548351406322b73d84cc05910927b6499fdb58449017383e2c549020f66231934f195f7888efe837830052ec231b2b3b7cf9f1b157bb9d5333b7320a8be269718311f6fa031ced330069300dc01187bc3529b1e7353c79273f38ed20c46394009c0c77c4ca15062151cf874af74d69775df6d68b424582b43dc61a4bb2390865b90ebe5600f2c31083731e2270a176d93038f5f1ce386b1a7106fe842592b46a54c7c88503730334f8590b1efd4b817fb6410e08bcfb3c50d7834e7c0330be4d88bae799b53e919601cfdd622708fc275e505c4bb7a7a0af8f9ff6437359faf50a3cb0df1c509ffdb6c954782c17c5f4b988b981c68b4dd4fc9594c389df1a28ea660d4e33c0be82479fea34658bd64f455ca00ef7363c7a2110d7bab6f2587b2efa2552d86e19d398fa14cadc218cb83a531ddbef50eb079a50a4a354be8f05c39f44b5ded9e6ce3bec4cfde8c6c69bc5451c7cbcce69a478d904be02565737bd3922c65baa12f1f564722fdbb50b346c34153513a622fb041e48a8d79819bfe779a3d7f0ba04e4ec9f9ddf3950b5f0065781f626b8f14daa04d9b61623952f8eb7bf3043c2056b6bd798e15ef1e3bc3bb3108486a9faabf48930e9bb8c6ad261539b7812c2d600acafdfe3b98c06ace4d1870c1f0381e5f41d44d243d30edb3f3098320f273cb3664efb858a24d275c98c6067f4e6685f10d5729c79d6284ddcdcf37072c8de4ab83dd35d6fc47beeef3143bacc27be3b9e5c6c238f125c90b0e64dcb4b269b20407cb34322f697077dc3af559de06c760ce06a87319b123b36cce42992bc5e87693673b0c262af2efd248689071e5edb45c0af0e6cb0ef907be0415cb039c1b703b8b84f857944b170ef85094090275518056a398dfddcff8b210f6a505c2a41103a21c555ecff481f6ca9ee978705e54c72f50e52cf0379c24501aca2c3df95b1771ee9867ae24e11faba13196d723ae9c24c1629f69d0dfb8d15fe41264632eeb0ef05c6b0433d7463dafa5b68338e4a1464caadf0cbead0cbb7efee0164d36b1475381801c6084a4c1345f3a56f1eccefc40ad219f02e2a263ff35ed5f011f994de8cd5af2e31d2161dff8843cf9954818795da3f161054f7c537728e65fcbca703f4a5dc8b3a8684af7210f1ba31ba91e652411e0a66e7151edc16af02bed577faccc398771ba9e397b998145571c3d0fd94f231dfdbfd9f46a7488767178c7338df3e0bc9629e3be0acfe87217276ce88ae1bf3023ec20ce6edc1604efba6470e2e54eba518e6a9955edad3c1918209ff3903a46cd044484b2b48a8fa36a9cb3f231692db75d795755bc879f073c9bb634f8ea91a6e9062a42706446d0fdc5df4e41d6f6c093114df2f59bf54d4561110d91f41473d069f287902782a01e48e698ad06c27581fbf6ecd5fb96ed9a3db0ff4844b80ff3988959500ec486fd1566ba9945b5c66a8458aea8f5c2614efecf54816498b2c1ea83d3889121e8f5b8cef16a978249c717e1bbb250d6eff6724789a19ec1e89961d98751cec963dba86af60839a587af6b4f84e2c433e509c87423eb941dc3745ad791191ab2a80ab0df2d0d244e20b7b430cf8efbfc2c0667065fa6a8b290702e090411c9beaa7b0184c07f2e8610baa841339b224f6e80525204be402cf66f7a204d22f002c0be4c2cf0a3c09a7f2348ed3f601338d01a9e366402039fbe704208697412e901fc2909e7310aa0c786e1cc27a907a729c2abe9c5039de2cabf75e49742d7850bc61f435f1a29cf57bbd68ded601f34716af6b9f05b6277c0799cdc6fe0e0954e814907eb14c98944c3adcfc4142b14ce5c0a06cd29bf6602a484300638c58afca5882b9267694ce92574b904b8846aea0d97043ba61676ff43224480f579381726fd59b6691439c5b24eab7eb027f0e00e007571e10fb7b89aa4ae460af869a8d563713970748961f240d41d55b8a30f972a2f060b3187c2fd1492b6495f691248dd956333e173eb0147c767e7aea0bef5d146a57a3b0a57626d4b3d590d33ade2ec563df8e775a0bd00dd2d3eb58b438bb32bf755e1c83aa9803212f69348a57cf6a19bdfb13ae790aa0ec9c0b06894491a7e39bc0cc6f227fb91ba49b6965e58915edebd62f5ceda6f0b1ac00b5fc43ba9f112fcdc8712d6fe6b92ab2243cbf4d9d7c6d62e1f3d8011a48adebc620b12072518d03d27f7f100e4560269f8bcd76d51b1ca4e5f2c2041cc2372365fe1f9c3a9ebab2f97b8004bbc084edd97603e4c1ffeaef623205016d8c009111f595204013f9653903bccf59c21afa653a56b250b40d076a06bd58636a737882a89078a856383819e1e0e674c92e11c0c345f5629b322c9bace8de812aa277240163e8035cf777660ed7038e7dd31910b62b5b73d4e9a13aa3362812bb4f6303e1430eb6e34f08710692f9cf7661c46fa82509380994e6a8809468eb74984312aa0397342f2445e59af558642594e79f9242bc062c989f92ade3bbff1f1bc93964a32541b5decce94c06aff36adaaee3e1b8d15d34ace774a59692bb4e5c9fa929891744c1b933f7431f678d1a8d1ef76cbb6383dcd605d52c64d7d416ea18dd34ade365dcc1cac1c04818b14d6d5b5d6a6f86a4565b346a7baa45ed8d89c3e39d7c80d53c0adb3d9082aa86dd5fee826d4a98b284030aafac82cc68ee27da74957d8095b5e1d9bd89d673c625306113b980b38afc0db357922ff7a5d929e9a7f6ffa61749bfad0367a2c55b68f3c43970c0eb93c31cf304dc38948ca6348b5069a9630ac5aaf89ce73a535d73e9f857f21ce9160351f939cbeecd9c2e14c8dcd6bc8176979f4176158332245dbe692b31a2101415af70e6b3160a9f762d485f1d615d0130c923331f1fde731bd2b4e9b72860b24415b2c8af17144a795c233fe623cb530a2580b03d8d2e19a5345e20c1648fbaeddb00bde2f04f4ae7acc1309ddac8e3dd40ddac24f0fae668b0aeba8b650076f68f33205ea0ca43ef839d8dd7d4705802d554e03eb4d4ca30fe247113b01a090d998cfabb453a69b44cbc38842ed78c54e900ee10944ac4c568380700066bbe7922c73aac462d255069884357fc538823df05ca8040ee6224417827e42e75fd9fa63194442c806d84837b3cb98fc4a8fca624245f9ec6ed96d1749a7adfc386d8f8df6035363a9fe1a1226df880ae63286e07b78c7d283954253ab7b90f379eac8aab74abdda146e8552b5bb1317f6563dd6f2434cda1ad4219d21d8ed0b823fb2c29dbab542c80459dc05df77740d23c5548e2d6ddd8dab711abf28cdaf21c596e0f560e13c5c59f1794a4c7c874d634d42abd2d726fb8dab569b8303371b172f5d5faef99e924bfbc9518009a882690287a05afa8f095471355c228316dc00b4a02b4cc7069b8b0556f2154e7f90a75938d767139561c158aa7b04a5bead15102d335f3936a4188429865fa35a6b18a4daa25bc415bd0e4d762e9d497df106b3aa812753cc72d49beecb19c48d052288a4f9dc0adfbbae6f2acf60f49c6c86965e5d5d70f1bdac346e009622394e47596e5954bc76ec8e61ed6843364ab9587f9e378c88328e1a2a1233b2ba4f6eb8d9db8ee72ee88e231cbcebc10b8980cab232ead62f0a36f4ce6b32e23c234b20287fae93af070900c1746913f5a1615661a438a3f0c5e0140014f19c2de28f93dd90f21e7bfad9385da22e2925dedfeeb5e997142ea7553b225c61f90fcf2e0d7c9f6cb212b36b1d71889942e736187607a6d12e1490bd1cc6a1df0c7ffa46004bc5d8f4c4c8a436a0ad3ef97115feb22e6083d9cc605a5af7503389f0371cee42f41730d7f696c43f35a094ad4571edd9a5355b8a94a71d1e135b8c89d673169f987e8988c623d62ce1aac489b35b1de69afa9ee26cf55ff86330ff636d8c43b451ae37a9391dc00c3a6dc8f11e50d9633bac86c5b695696c9a42b066f998149e1e3967f437f0659adb4fed03ba449fbf9937f2050ee9c4e535ffdb151030b27928bfa3f12d0a278430db6e9f74bcf1c6c9d808dcd26eae2901d1d3fd35a790b065af4c80655f6f90944d7937a0377d4fa7bd859b87dfc158298814456b232d1f41ad0dd910db4aaf07805d0390f919e979d349577429957de2b1e78a9e20bcb6002767000baf441a045f208257bf97d07af50aa9f606ed5a2d2217fcc00e41cfccae0dc1ca3db338e5783fe6b540dab97263d122bc453e0c72e6789b31f9517c9c2c507167057cff2c430a0e26f33399997e1d3d82aa5eb3e6cee1e0959ea27dba0fd49590a2c551a3fd39970f156a43f5db106ce60dbc06f521032a0a17a73c56c5980395c8a39fd63d751f212b5caee22f79850cbbf4b3b6f51a6baceb82a5b397b8acbcf161baabc9e4ce8ecf9a4ce033afba29159409ad9241d4dd8a615a51da94bed885d6a3a34dbb0724d5e665a5bb1f1352072aed8b34a0dc0e163333d036b53b274c328778a742301e8c167571ead1d00aeab2ac6fb63131db3feb6b5d6486b641342f6268410b2f7de7b07b008dd08b6089c0ce7dab72e07074f4c46cba1fdbe6ac768aced23204bbc3f98af3448ac3104534a77501baba5c2790357e8a74c436d3240a5f33b0db5b9a1364d5816e757e7d7e71f967bce85ecb55bdbb580e4ab37ad7cf5a66df9c5ac7d66bfe16c7b4b97ebdf598ed96e63794cd4b6ace5daad15613bea370dc33255fbeb0d5ce9fb5c3796968de51b9e6139866740d7682e4b86417f0f71a04fc7c37157e757e7ea5c97bd965d57e7ba2e7b69bf1ae7c21cce75ef75ef75fafbeabad7d584655996655996a5d9adb39734d4a62dcba236990669ec1abea1365db1f577eb36d5652dc906ae501b6ad34f069010b452b8753e8bfe613b8868ea93f52afa2b8e28fa8912716540a3041d0e7450cb3ff784ce92fd661d76db69f73e147279115dd3eeb31d1c6b2d8665389add2e6a79cdbaad2567dfb0c57e9f759988ce3e6140d86b1b16d55f9c453b38dfee359c4515cb3411ad655bcb6f97efb18b49b3b76b2ddff04ccb2b9e0971eeb52e672da0dbd0df593cf7119309e1194720fc8fc7a3691cc769b7af96b3d9bde5b4dfcc5eb39d4ea763adb5d69640d3b45b6fa67599081b30ced138783b8cee45187b6bb16bd9bdf73e266610db71efddae8cf60dcbdbc5b657e62c11f3409c64a69920f7aaa216a54dc3f1a0e1e2173e0290bdc5f2177a07dbae7df33cd3b46395e56e65b9cfef1131daa37ddb9e7599eb2ccb38d80720edd97e7fbf3de3fee932e7eff59eade36adece6dcf3cd7260c88ec97cbb7378efbc59986d38c00d21c9c8118cde14cd3f799a6b56bcf1c4ebb47c3345c73f51c9ea9f7e019ecdaedf28669b8ae19ce345c631d0d63b1a7d7700e753ad6dacc6acf36ed99d5b62c038140a02ccbb22c1b8176d89aa671cd7deb324d6f17e7dbf7d93dd9effd7c70ae3ac3b9f3ed1e0fcef6dc6324d7bd6fddf44e9769b8d66ecfd1344dcb340d67bfdd7679bfe1ccf58d7d351946b85563cf5cd3701c0d0dd747c050c48c60a50bd0af5482255db53b519f26227ace37bb76d5e4039016599f3985297e31bf93268c8942802202d890c9165d9d04539a00fd4a24b0e95ab91619595c92ec6c04905c75e821eca07fde32c3b9ad9caef3adbbf01c12d434e72c574a23a0d2970f2969292526d3f9c5b9c7ed23449dfd7ec276d873b4735edc441af6014867338e38f77174cf79a7cbdadf7dc03e594ec7f9c31e001b7b4ce47cfb04a273cbf98401b1fd6acf5573342cf201486bbff5f7d608205d710662b456afd56baff51ad6fee2a8e24c445f22dade9e93b54f9c534c076cecb6cbf61d2c038b6b31760e9ec15ef10ccb39a4d99c5f0cbf380a7d63f97b4b97677f2eb11fe0398f980c0b9e7114c2a7e7e00cc22f8e3e9f71f4b9405c9c617b80f0dcf2741989b65eb7d7ad93916d1d88ccf92644db1db32d20f23de79b07e70eb465ad1db2ad7b0e116d71a627c9b6f28ca3ed17bf38e230777ac971f7db863349b6b5bd9381c525c9b64ebffdea48b02dd26ceb39ea470457f403ec8b676ac42219f87bf88bf3d5b55482246d43bf1209bce899faea95535f554e97c1d208a274f56a7106d2b7cb44b8d810fd6638577dafdda57b1166bbdf2eea5e84d9b0df4f4c2636e79c5738d7731e133388f900e4613d707086cd7976adebfc76f65b27357bcdfe6ed7dec1db6324a78bbd75d99e7a3c47c76b11e733dbef39dbef373ce308fbc5d8357ce34826768667eae1b3c5a4d915577fbf6d75afdffbc470d01ed4175f7c71a54952c6d33014b32bea8b41e695524e729292ce39e59453ce49279d724e3ae99473d249a79c934e3aa59c723e34e5e794724e396708cbc0e242221dff847c8d7172211f956839a750d9c28a74f2440b264b5a3e2945cb237124dfc37148912612616ce6df9390c8a3714817cd11e978646aa2d44494214d9eb8f2441428022b4f0c51c1138f902a2a50a20226350821a2029b9b2bd8d253ca8542f024daf0dcac1e26aff06c31ab6e49842188d75eabaaa2d5b16714cdb0aa7a55853a0885524a29b59834a94e2b4a2badaaaed1eab24e69762dfbf59a75ef57471d62d6ad4a25d334ed395e6b58743fa351bf55750cbb9d56651866af8982544545998af078e870c03459c0c9132da250a184c9929b39a30586201131a53ef12eb67134a7684ea2897fa0ed238e249672424a29a4d246c00118ac2abd5e51143d453f435d22ca1242923a5f1716517c2128a532230ca5ac57224aa58f38aa37373f2a94c636bc10f14faa2794268ee22d104733c9ad784618f9c7c521f2bb8c40a50a8dece55cfeb25151e7dfcb4d102e1a32a15f2904455e556dbc89a308a588a323f3471cc51a38040691161882444474000482e011470988231c7888a3181a456ec6a49c7046f892dc8ce2e848b4ec18fd8b01c585a797f4529262432ca2190e2b7233c240111441fc03a5584451188eeac4a5b13d5f44125b54469fd1cf53202d3b084f51d2fe61105a495524daa976aa5592c6106efcb990a4f422f909484f14cb078403e2e85df4039d107837d8387a50c9f68425874da57fa0d707b42fbef8e28b1d382f42fb269fbd6edd6badf394e17ebaf537bd723a25385fe23b70f00cee7d76ef611547d95619b6717445d5ad3f1358e95732814dd3e8576a820a5da3ad3ad13dec7b4e27bad8861da6298e463d75b0b5ee35c4b40d8b2a1f71643dc3daa8a7fbecda696c571fc1514fd9bdbff7d671288ea6df0e4271c2c4cda125499cf474b80432a95c3e6b767816ce30c8fd9c7e5fa70075807def2586e3562af088bdd3749758a3c61adaa720f555ebde6b571d6257c470540dde7befbdaf7eafaccba4cebe752f8eac9126817456a70067cc695993f5325954ea542f7779ca70e7a516dd55315ad785b0c010b1463dbded1e86e3aaba1caf61ad15c25334a18ac24ff92ea3a6c7017609e081de061a3762e8cf451252ac318adc1ee6332976164dd33b9c6e2b055c2bab8a840bfb89a8d889a8479df6f48a7a5df4ecf7697aa70935756ac63b93984a4b3c29294145576dfebde0aaf394c13af57923605727aef5eaf634b6eb653b0bff40dbba85b9387a9781c59da82ec76b6a5e94e5833e27378724157a840375b1819a4c2851e4a626054f60be08246a3f83e10c4fb11db6e1a10d45713602485bd76e338a3d0cb33e390175786d3f311c76de4cdad5d49506faca1ce8ebfac364685fb365ea0423c260522a424584624df86e70d2356da48a7eb75e0e9d1058bda6a6c6036fa2e8e5e2084e17f4bca54bf49c93762f731ab941c9080e3a1a31415ffd4a4682482e5e666c1997255fa8f2e2844f0d2a39e13109b6c433c3e8848997a851e895b4fb8c22091353144964948302288cc4e089223da0f2bc68c217a0cca00b49684195284a305921003dbfb0a28209c5142029a1f881270a2ac8c6414a288a9420863ee515ecce391f13473ae673f04075a1389a73ce5751e839e7841f0acd39e79ca12969bc5f290a2cda06f6a6c25bfd4a51248111c6286ea67ea5284a508a22891959acc829a7144fbf12143f284131051445e83491e0be121443d0d0b9c24a45ae34174779f674656112411128f1c1521126464a4556f058504489defa955660838197ce2f4d730302f27de240c348cb4f59c9d3c041ca9858c1a657fe529e060e4df147c924576003d6d810ce342f1f1bd8d9bde09986982a342105095c30411228a5224fbc27566070062bd4c00455ba08451b7387978a1029428df5148bf71e0c7c8b0eee7bef1d3f5f84d2c411cf95fb3adbd0d361bf924d94ce978a3be667e236c5f4a94311a43963cfbf083f495d11f0a21747ddeb13dc7c595e0e229978eb455cb7af9f30979703f6e95cac3173dd108c9743f61e48c47703ec013bfe76ed62f4d565fdae5d03faea6ae81a9b7c21aded604dd70e62d157c780165518b688c2b367d3d841ad2d32b0b82478adcaa48867f08340483c33130f2710335317176268821def11cfbcc7c3535956599a85af8e26d3de8ef88767e25ff53a69a5799e9a99fedafab46d2b6c76d527fc343b7bae300da9e34c8f1fd7c434b2e31f56a78a696cbef094ba269e79a7b48ab7aadb55b02926131b56d56f476997e3e9e7e9df74a58d87b1bce261177f759986d44fcebcbf5ccdb7c8c8e2d2d87ea740c4bfd7164d2c13ff1e82472e2e6c156b90dab21465bb1947f533d7abd7ebd5af212e187fc002cbbaae236268b71c0e8dad2a1289c6d25816589665599665599b6559d687f5f12c089410f4ef519cabeac2f9be0b41a231240a03233524ba08747b96ce764d6be9f13ea265bba63dcbb2ec9f2e737f079da375daa609d15a55ddcb714ea25988bedf32a781be7579d340d7f00ce81c3cd3c275244aa22da21d961651c8b2b08442a150a8d65a8d5c88deb20ce7189e8b738c1a6a8001c3c545246a696161098540a0cfc7e3e1b84e87c3d9b67b352dcb30ccda5aafcbb24020ecf9a77bef06cf33d5d945cfb46f4884f3d4d959ded2bd77030bce53df7feedfd3e1bd19219c67df7783a786e7d9bda77b6f44a2ff9fdfb8baef424cae646d541445a2e6842191682c8df00b5058d67589ea6da7dd1b7279c1ae551adbe96051c544ef882aa943ea90482451a5b1bd5d137dc3332216dbf79a6d9165f9b0843ca0cfc7e3f1783c1cc7715058966559966559965483f30ea77b716433cbdaacb56d376bad7743360db5a2a85a2945294a7d9214fd857365b9846c104d4ff312c48b7cf215542cb7eaad63a1b7dc8ab2733ed7ba8d9b3edf383b3cefbc5455be9d439ddf1a167db08c07bf38e2bca3dd83baccb9a1776ee86a41f4fd8401a17de3702ee7f97d3be764df3a98134410bd9d7343a15f3c137a07cf70bf2f39c3342f1dc22f8eb097e6fe2eb21c565bacadb5d66dabdbc6c2c2c2b26ddbb66d2be874cee9f24b739e753988ce3cf7746fcdc5a53750a8b57b42996b6ddb42f87350e7e93617eed93d77d1707ecfbae9a539dbc6f9d6e597de38dbf642f3f29e04f1c2c48c7452f847fc11a1115400e23c74ced362afe1dc63f61130fae3f101487fb06fdf8efd7ec3ba1747d572381e1f8034961901a4379c81188de18ce3e1705a385c4b87e33a9d4ea7a302ec8d8de1d6b05e3c455911dbf12296e3ba86e8eb154df3e80b67eaaf62a72c8b5568e5a817ceb7ad8a8693d28914d82996b242bfae96b7b0fc9375aebdf3fbdae940bfdd06fae6f9f688edd0dee9fcde7b7f5fb96ebb87eb3a3d703ab9f3774fa7e3b8df4f8e7b06fad479ae9acbb7b9ceb9d74eb779dee9346c046ccf5f6bdf663cb887cc8ba31e38f7fcf5b683d3c11eaecb1d0e0361e7f00c760f9ec9cebdd3d16858cb6ff677d187cb5a583e9f6dbbec66b95f8dfbb5db396dbba15028b4d93a3f29b822d89d6f78c6de7a768ac9d8edf09bddba19dbf1e00ca43dd7baccb576aecb347dbbbb2e20d0e7dc3977e9369cb1bf73ddf44fe7d83ddd11b03b8f1293e9dcc23188defef86222f517133b5d7ced745a47fbedb8733acfb72e73bdbd4a4c26c7f5741193b9e76e077ba5bdd3c5ae1293e1fc6139362e8eb2474ce6e2ec1a9eb118a6e1da62abbb9d4719070d8a395fe3dc41e1d9c94f14fe117d440805059675717ed987b8b7d4eef3db6959764ef364af751efb86e187fdd3e5edef3ed87b1e13319beb317c3f3fcf5ebb49e47986b368c7762cf36099edf58fd65e372cfb27c3a4d8983d8667ec3d78863b29574c8a6d2f977139d4e970389cec35e36435e3542d038140204dd3344d4301b65676f82cb32ccbb2300c887dc470f488adc5c6ce3deb72957df4fd7c709e9d7dbba78b9d8d90d839effcdd6f96eb7a501cceb1ee758783332952cbea3088edc0a64cf60dc7c81b1bbb66316cbd5bfdf0a249108b6227fd0575b0a718a6f431fb9c32f4f5599783d2f4f43a444f38577f10eb60cb1eaa5397a188a05d264df0823368f569fec2958cf5e955974366ca319d9a38d3bcb98213783edc4307b55ca49db37557a6feeee05c3302b6bdb665228b4545746eb55770c6bdbd76dbc96419b6b693b1cfecb317479af6da65fb771f384dfbf6d9426acd6251764e76fb3b93615111f59ccb3c3823ffe8fb076770fe2a8783399c4e267be7b5cb91bd93753029bbd96adc395de668bf9f9fd80eee1a9ee19ee1191acbc134b6b90d671adb2d7f17815a3e20d0e7f3f96cd738dbb5cfc6d1348fc7e3d1344dd3b4136c87bd6dd93b9daa63a4466339db566dafb60d623960cbc86dc319f6f64ceb24262aa2f37b194efb3dd7f590f576325c0eee6a9d2ec7bdb5b13519d9d9375111dbb5cb547006760e7e70460e10092d7f75af9fb484522428623ce4173f94226d5435a0ce106750ff31b1524ad207435dbaa4c0d3d4a909cc9ae3176126a8444d1cc9f31047f213df906f71c2cd504acbc3293555628d2d628d4c03ca29751ca1e2c1bc2a1ecceb0933b1d171c4c983799de14d0d050223e22244d007eef4116ba0e1e55083bb28319932e19239ef0dfda35195e1121dd0a22879206dbbfcb69794ec48b42fea1675b9389a8f4a5c90cdcd70c912497154b285fa5eaa129112c9477c6ace88490a76f6dd403f754b543b6c4d3b4d8f3d05d07fa8706708c70351e1121910e43b50f845981d402099a82f8708b917baa11f00fac168d99c67a2acf8f76e989fba69e39b31e79fb4e19973f6a4a32b468ed11409764465718580acaae7694923e6de0df2be917cd5d957bdf94652d2f4bcb68ab8c78f9e7f188e0004e288cac7bfa0be5852c245c90635a5294f3e4f3e43a0c1421257a670c114514a408710e1022174010c53a2502386181f4fe325fd8b2d6fa89c5415c2a5cff38da6391bc7a4e8f4c5175f7cd1d3346574427d714ef5d65ec7307ae714f4d2e29043c4915585a6d70ac33151d39dd317e644371eb39af595c270544ae7bc756be77cb5d534b5604e3aabd0e9f114758384144e25c849527a6392d39ce67cefbdf7e87bf3c91f9c9a66171fa5a7ae08d8d4a9575da45e5355931138e9374d737296b396ea01a546b812e99defdaa494345249a5947146196394f10834ce18a50c228eaac75ba7fea6c7d36b9a8feaa494524a2752ca28df7c51c6189f7cd311e6a4265a55d50c6550e8a4949a14a526a5739a74ce4f483851a880516e1163947ff14126734e8afadc28a594a2e89c9356749a74ce49e711a6890a8d1242086b053251135228e18432ca9c5397e3f544c591945bcf49c6b67642e8c44e0921847036843680559b8f9f9d11b02710b8c5f888c168630d6cc61821845c7d4f3e18e183efbdf75e7c5008395e3ea574e3f17c4394d218294adf7b2fc770f2de7befe518efbdf7de7bef139cac7802155032101b6bcc470515d1c6576dd59c739aa016fd4a533069f9dbaf340515eda434832afd4a5430294d41844a21d1d7d4d490e8e707b32316fd00f18ca3cb27dc8835353535a428e50738402613937342f82982d82283c98587f108ceaa80f4a09aca484ccbe2f1e2d2c5eed3e4ce471b3f8858e23b40211001f0f3f6076e1a3b198f4467204dee7bd30537ddf52b49c1c5112f32d798e501219e10bf2999c06ac18f0faec43e3db833d469e777fdcb489e06ec349cad07675c3ce208fe306324c55917ea5c422eb1c6f4266532e98ba31ef20bfbb9e24e879dffe9c1859dff32ba2e4f23013874869da11038230329495145ca9214535a4a2804bbf0fc0e8a33104a67bee88bc4a085c5e9b483c910261367890376d3344df44c268603d2898794b5a1908e471cc9bf0923d99e6daf967ad5a6299468967e251958e92a2d3d328bc89b48a429e60183404bb33dc514eb01f6ac2edfa98d6ac7e0332c3bbdc454be344cc9cfc6f04c952da6684f15cf54c72a3c234f1bc31167208deda02df1d543ad70b63d4dafddf629465669abdbe590e5bceaa8df8e83314e37234c55dd635d7d85580fd42b7bb9501cd51f180a813324c53d64cf87840482450c0d75ce3993a7cb2331a7395f7bc88e8919ad2c5b6badd64971e6fa7b58add65ab53e2662d775ea62b7cfda312bc70d6bc9198b17676bc55f14ce0db538414106527a00fd4a32a04206451a36d1af1483205db5272f8a7f758273827382738273ba9941a2a7675dd79ca679526c6a5179f6564121daaf2ccb32ad9bc1fe1aded77a5d9af6646748f40c3c96c5cd9cdf963c6776ae25b655fd5edcc1a4d8d9330793625f38d33e8f75b6f685b355610e0a813328460202b9919a01122f223d2976922a920c49d2451fa05f2989931881fcc0e8b1782c62947a0fd943cd0fe7394dd83b3661efd8846113f68ee11839c5cb6e66ca9e0ea7494e973652367c7dc6fefee8310ceb6464715b646481bd853ec76986a44d384f99d661facbaa1e67b1485101832b3078d255ab339e141b49921640bf1212245b3402fa9590cc4751ef3ca8275b527ffdba49bd3cdbfa7b86981427ca4edbf3f498eb2b182ca3d9624c923dc9aacb17ce6f4a8e0c6446e2f86861526c1967e02719f88962fb436d197116e90e300a32f9db5e6bd5309c022f0ac372609f9f5410c8e4c2efea300adfbe20f5799d07d5d59168ea8a3caecbcfe17b4809e32b6a87c43976d591a270f59a49125213cfc457a70ee4c7454d4d1ddecc6e461e8986543dc583fa8436f0f24c137bae7e5d38c321bf30a4320c9362574c8a4d5d17bea08c98143b831f3c211655b5cacc5f8741a00ef01784f9f3e9190ed1b01cd8219ee949cdfcc120cc417d66e20c2d45c163b54226748a10c24ac220d8af4f1dbce7bed369cf3a7beaf5766dcf2488c16342324992e48c9549aff08278e28ada0e3b6cc322ead3612765ceeab5ee7533c3f94de9c90a8142e08ce9f53cea90cba2535b96655150c8063969700194161c916248d3e85772c1946e818d112daeb4ed187fd213062518d4f40ba2f4a85fe90549baf4022a5d1b9edeeaafab95b7351286c21833ec87ab2186e568b9fcb45e5b363716c31171585808f9a8b43cf4f1bc5321c6e2b48197a4887533da9168e872abcb5dc6ac6351ba60b71e13238cb1437b0c9c63538f89b0cbf3065688a1c433d4b5c71c713e901f48743cb47207bae194a2f374529c6939121dbb2c5b36f60a2fe32cd299147bc8ce70478edde232619685f3bce9e9a4d8d1b22c4b524b524b520b831d2b472a16b5ac4709bb0cfbf37c3df11686118bb44b215840dd338e732064e0e30e1b225087784bc6c4185b3a0dcb11e311cfc4b73cc3760801e3451ba277f8b1066961568c5b2b3e460d3070661d44a22cf54953ad4d548c4d75b1e35b88b41cfbd4c5bb74f6b09bea5bbad7a22edf268e261a12869e05e72a847305c2f97e70be19ce210f8d3707e7516f388feea5aec73b735d27a00ed3845d462b3d45da1a9ec14eddba855595750faf5f2caa4e1d63003c1f3f87643cdf970300439b97aa92712f7e925a9f3e1b3ea3b00c2c6e0e527250b5f70861045571631510468aada8a01c5c8aa2288afe68f9ebb1bb9e4fc9a7c31070064581aab8972a3d79d2b7271dd2463504d4e105ce80178bd84aa761d0c20821d430cdbb01121147f023781ea00eefcdb0309c436d23b49cc4063171238c17ce16767e5b6c61453fdca6bf4d1f1086948450a45f2908454a4118f22983175b4c8143eebc2c39814217008a41116265e7070a375399eb39a5942f076aa226ea73849bdf14c5d9409da228ea2197699a268a464ca287f861021422179af82de1663be7b41756d1e2726f524a871047d384e33385fe32b29d0c2c280a420a4e4ea0d1155521b94ecd508fcf1416c9e0174f7d9eea5e4cacaaaaaaaaaaaa2ccba29145451ffd7bc4707c88305f2a1ba718a117b2b0e901543142132d9a24e91a5f84326bc2d5a126e1d650e78b228129575a0a4c39f1fb2801563f6afc20e166f972788915a850f0e70917fed1a81f29d99e477dd8270a777e7e560f9862c90756faa55fe98a2a5da185148bf84cb148266224f27d9c400426a64c708a2a8ea61a54c1628a0dee2b35b9914d6cea0329b9af844592d7d5919eb7252c9274c470e0db2a4251b5f7e68c93c6ac3f2a941493103f0b6dacb819da34a1a2c4fd93d286761885bab18915273ecf5543100c751586036e8f89b00974126bd4d733814bb6c7439b58a322126d846cb0fc03ba75960e5a01c54fb76de77038af14c3300ca3ef9c7b9671db3bdb6597e3940ddf388a423c98f88b737561af1786c3be669761845b4f8fa73c8458cd99eb0c6d22910f1a6e4c9422791df8e10901ee0a283d710e35176d0c401fb8d9e6780d4570e15f0f1524821b5fc189561dec4a5555655b0ebbaa2084a1072faf40a3d09a1df2143e02367d922b9834fdfb8b282da90e2e280438b8202668cd65c3928239ab299212e929d39454ca2614e7919cd2491cc5432c47d513cb2671140f1272ab2a5545610f9f8ad4aa819680968096643ca81514e43e3c8b78d185142daa50226de4902dac3c7122453ce2c514294baa8036576874bc5450900be183b407a025208448ea7ba9a0256e8c50ca99c9d0a638ffc055b6a01b5cf97923019ccb2b5d11a5330e4f3c4a694563274ae95bc1341d899e37313a41a7cd7482d27933e3675751233db061c28409553dcbd3393cac47eb767e626ae10cb11fae9ef3950714326138a274f9d65d07548f91199c62271e3ce0044aa2ea3c9364ec742fc2d48e907a53e2f429059f0d63841f2c26a510423340ee076ae6c9a9c3341662e046d368a2be9796d5278b9b674c6ade0803abea735aaf15565575247aa28a78715455a7f083334e75d485e43acca4219130ddcf152e50a4075e882207566270a546154498e8c28b3070c1891d2a7ad331463b7ae1ae7da14853106f9085174c810329b26841941648611304193881620b3b641a5650b1a20a1d4b5648e1c11c3244481021438e947aa0a4648515a6132b9e587143c5198a2c81b284c90eae50d90195283b68d2a4a4832567b022c892293b50526794a62f2f2fafa02537bf2e5eba19541ff5d761c3ab9b47780431a5c8f366faf80195a87c5cb3887d7e5d4cf0c81c725d780e99367348ac6169e0d9b8494026ee336ca2ce2311867e2eb963b8714a1c8de8eda7cbf310e2e5f06cc00773ff2c25b6212c7d7dfcc0d700bd8f9743912a89cb44d3cf336ca26986473294e27571d50bbbab5ebf3ef7dec31a1f3fbab8f6d3739472ed90a6f83111c9cb678d12b1867c55dd1b0afd510a8c2cb7586eb1fc8ae1639ae6cd55e7cdbc9949a4949b396faa6bde54d01577dea2b0ba20b4ae0f10ee9bb35ed44b07009d4ee75405c08b96635bf5bab75e27aa920180d955d4bcae0b7e067e2b72c4a55a918aa8c483ab8c2e3e89237a0074110ae5626311193264decc9b214d3f6f620dea365e4e3f95cc25b3c99462563199cc245450135a37f3a6c811970b869e5f17107ee30e6190213e248638d743cb05535691ebc8cd4d1c7de15687f11a2e68038fc41a550b115824880b9c810fd6d447262f5d6cf2813643ae0e06c13132469761c7a8d8471c51eb17dde53e5e0ed0028140a03f0c87cb6362d7cd22f3481cd14f0cfa9055756f603cbf2e6a789e3749dc47acd142b11d2cb73e613fb0dc6a39fd8f9a97c3fb81a5073c02f615136374af6be860745c4bc7d2853a242f0775f97eba0c8964cb755d1441757e52ec74bd6068a76b46891103e0454907443e6328e5e048a98a2ab489520e6e4a381852ba8117a51bd8946ce0056849849d24437db2896c426514584536914d2cd9a441426e9e1da77486542015d015d7e2a022a535e3a115b885a602a9c02ad08abc3231a99f30946cb08594524a29a5945262ef5db592324449c993cefd4a4a78d0f033c6182d565222c59c736299921164769aa629d3b0cacad06c55559576957ce0daebbaaebb4d68d8acb5d66e1c4e173836cbb28cd3c9a4d0b1f7dedbe1e61338cbe170389c677be2b11c8683e3382c47f5b112625b8325f1d58006cd3d7e6ca9062db0f5c2322e0bd5464c65c908bd845c1e0ab9fc2546070f717485864920839085138ca0408b333841870c9e28031186484983169c88e03a59012641e814355981c81523d4a00a5a486149a48207d2c005941d2c71842c0400042b32e80862745452582028d9bc3005a19aee52c9d7244647e8a34fbf520d865451545068ca1f10c9e2c8e70ad4445109882327321a718403455113450121044d51a51fcca0292a08f6c5863e56c0e1d1c0092da35f890655bc22d0392195e3c12db8f082054ad477f8727813c2f76e9811b8f845181ae7112847b43832e5481523f5bd5c25aa539f761e49f7dae519209eea3acae878184f013144e5e61b3762a6c7df98a24b1cc51b1d9f6d0ef1e08088837c14551eccdbe2c184b878302f0a2f1e4c4b2f6a5abe8a188e7b498ba3432301daad91b5df336d18cf5345a9c5e4dd209f271b7a39501dae1caceaef38dc381c126ddc1040022e008e69c07836ae849110e76aeb7cce030f3778b811c383ecc203825d4cd4a669f04817a4d89c1bd1c6e3e166384f1c626403f484e9f9f758de0c7d9ebe1c6af8fc047580f179d1ff79a3e769e050ba53ca1a8ae54f56ea31708ed135c0e87c8033e45d3a07c491bce85d04b01e62b7e020de0df2a14e87166ee8f3445e0e1136b9f1f15c99f3efbd1ad3618db4f172c982eb9fb4d13dfeda43a0d72f9d0e23501ecc6b199d0e235a3c98d7b8d36164ca83795dbbaed361a4ca83791dbb1c6a5047c97f3a4f97abe63a526c8ac6c7c97d4d75381b9ea623438e103952440ac8248e9ac49186e1928f957b5813611048054a89a31b34f20d1a5b70f1605e7b7144091b3ad64ffcc39cc93e31f77a4acdbaf946b8c7f41f644f184990432a500a94d23582d001011e42dcb871c388124686182162a448d5af524bfbbd523bcd378a184990cb1a1008ae0d3da578308f8aaa8aabc3c8e8d5a06e434bb844c2c8df254b96c8253207a38fa8144dd7b4410b55d1080000004315000028100a87c342d160304c3455f30e14800d8ea84864441589b3248761140519a510320610620c8000c8c8c84c1b000f6eff59562f8ebf03b9c4206dd1c7ce10fc0e811883deaf1502b08f5a9794817ecd2e9483e6da6e2903f91add2909e2dadda10ce235baa604e4b576c19432d01b9f40ac489e516fa4bc7435e89a12d0afd98df2205cdb2d25205fa37b4a415c73179441bc56d794807ecd2e9852067a468c5c39d8c52540d7693a30866868172f1f269eca1ce3a9e2c1f3c95443330595994200e86dcd55af29868e848d4952d43404c7e77be91c332e3fe161ffae83e9a968bc4f9bd0570424333c93a2017eab7471380b679d2ad9a3b3b74ee8aac821bc46253f4fa402eff556e77f7c380297972fb15fbf035434c8c86e352a1bb5e4dd7df0a7ca91ce3927c4ee6a445b306662c9abdd3d99c41cc79593e99d09f3fc4c0766c80246b047f31684e0dbacf3191c3e06357de2c61f23c2a649b01c08f42a25181da6f4f8a69731a16857237ce23c2fa0d496a5a40793e9eba0eddbbb12c74324cfb7eb5578022b82ebd05b4771a41529524f2214bd98d639f502399fb7ac5595897600a2ba14807f94874abe5204b7523e8d4f1d54f7b960a0245a963dd4cb6984b6779c6f1015278fd7f008b764f41e2fd40690c31d09e4e6f6f428c0750b53a75e46eac67e63428dc157c04bf0f005df58018290dd226f31c42b427b09c47411bdd5e20d67ef6f7a712d92439f2b1284439f6d7695f1f7d757a32ec3d8c15852863b27065e576ce1702809a1106ee334bd6b45236c41420685600079fd1bde719e0a19f9a11d05fafd9f7b078453c6265ecda2e3aa2a02c0b8aa3e492b30a74625db1a3f2b1fc387e00cc20d28164396264a19596afb891296c57bbcd7609b22da18e145cb9dfd7ec880f6dda1034728b19851b07d2e87730e691b2d08579c8e3fea154f3c7af6832106c1a3f7e152273ae5a500f1e4178ca437fdbe86b8a2f1a392dc2b9cf974809043207c0be4cc0e65ded699341140751a95322f22aae3198ca5bb41039c8e302a19b697ab01b61ecdf6768f83bc41b748529fa28fd09fd005bafcf391f308493503d62611b0bcf8bed02a530ad72b994c3b1c038d5fa89a772465798c8eea596661ea1abcb646d0d5cc485538f9bb5c0bfebb490ca6bb29fe85b955d517f5980ea1c91a620f090a138340745e8530f801862e1b93864e87b4f913121e87da93de646ff4af1a42e3df7aa4ae9ac70f2260b72815945c5913398bedeb7f2d3e894a492fc56acc9e0b7c031d06106c118ee81757ac4a836895dfe15cd1b53a37f761e1e373afc6a6f5d4bc286d113669a38627324dba5578ff8b836020dbac21d2812d80771aa164f530ffabde65dc508da574a90d96c03cf68d86f1137a1083df9a9d9f46859958ab46b23aea65fe98327a6ebb8a40febba401cc4b046d7273fa4ad42509bd6dd9e16f597d82f307d6d0bf11950692663f50b1a2c81166ba2943fe49c14077dd768614abbce9a5997199ef647bc86421c082cb0b58bd4992f0df71deb40d45274d5265eb54b181308b88d982906682c5ba17a648ec9d1f9640cb0f77c8c7bf1e61d15be9b480e6bf53e06db2739b521f9511f2da17dc094538a32c11365d9f1b12eb1085d0d324be009f2cd537f35c5a4f2c689c7cf839b46482ccafe719c1650ecfd91eee2c1eb1df21c5087165e7f2a28a244af4dc22bd16641d55e55ca513e45e56b4f14d2b5db0a5486af471320d0a6ca12e546f5a5e7b5e9dd26f1a9413bfe8a8980963614d454778f7e1880d2a33730180cbab596938e020f3cae4b38ef65925785563debb8628f4a77b4bbac0dd5b74a9e685e5a5511d680c63280f4c7b1145b147fd1428d90827e917e9449232e53ad3b2519259ef9bf3402ceeb530098aaffca4ccb533317ceff3defec8cf47ee6e4e07e4e0ec0b34c154439487bd3b8ab5c93c63c49df3a686c2c78f8a690acf1bdc778f286736c382c5941170a834f2e3a89d35a80d02c4a7d939bbc3748cd65a8697bfb6b28aaa8829f3076a483f0f3df2fbc3f3f32660f0a52ab046239d91fb33b3a41b8409cbb6813046a6ae68d8dc17231ada49c7c8df80dd325aff615702bcd94c187332d9a6e2c4ce87e834baa063425c493d47141c0140bb0858907b3f04b25f7db342f8640562c52d201c5760efa4b65302afe9bd4af587e962840d749b7c5548a7eb361151376e0fe19ed4f118be16dbcf23c492e4fbd01a721858356fabda7ab1acabeb6614318d0fea1a4bcd9dae492b3936e455d1b7330178e74a54c6b2c061f14f21bf3411483fed9cbe49d77700ec8964fd8bebf6713dcdb946683ccfd4c89f7a95db1ee5a173e33c3e2ce7d5c61d1c1b8576518f380d644c55d48e091d878f7cc94ddd169c638f1e67d480b9381511bed0a96376db75b7b57994f13a0d7c137460537b84c9d35a1b48498e33dad43d3aea7273fee9780ca9f55270f0569144abb06205c3ad15ddd3c129f35a383d8c64c3bf53cd287ac4ef49b6b3b6c27238f94bb915f7afa72bad864a98f99ac4c5c3ecbc7d212aa42cd3d278b15c3df5bf348913f72ef71d0fb0b718cfb43c9206ae0a4e2fa012a8894670cbbe4afc146d26115386ed46a142bc570784268957e70be4f2a5b5e59ab619bf4dc13800247aa5296d3a3bb24d5f0e6705991437a14049341606686d2bcb2fca7be857cd3ffc5947dc1682f6f2ea080005227f0706ac8ee14405cd2a7dfafce4c0e69f81bb300d250b64aef46ecc0b6957288283d2661920d2e616962448cc68d42b17a32789b46da66e3db1532730c357313e3d4e055fc878fc2f649da06c196e23e234b3d6ad2f95b3f2555b04ac33eaa8adc09664dd254105b54ceb0772208244394f841107da49386dff4afed038c1722d294e6eed0896912406c47af1dedcf4dd60bb00795e7caf4b61861dfbec3114bd8c40614f7a3ae0cc617f5fa711acc04cc51425a8e99367658d2bcb057beff420d1ea8dfb67e5e8996727fa278674f1b88378acf497e93f2beb9990d85ca8456b0ba529c668e57e8842a9b6b625175bb78cf285041506adcc0e29e64cecd5dcfc5d959cc51e160512352cb60cbefe99fc2adebd0e27e7ecaaf635d65ebe2ba91bc3fc8fec213391c9fb0f8ddd0806aa15f8be2b0e4513ddef4a46f0aa03a21130f3b8229e6d004358ac954d06781f816eab94e70e5719c20600138720e4d53d38acc41b37453f27d077aef8e1b0b44d448bd5c5b02c92a5a259299ec522ac178765b1ac15cd4271561661b938ac8b64a168ca2a15810e057511feaa03dc19ad65b538968b61bd4816c5b3a2288bc5b15e048b45b22a9a2545592d8ee522581777b04845a1ccf0fe2e5c5cf6ea0f2c554a4008ad7f5a24f46c2738993d216d4fe0b0c4f47d818f075709d946e8b684b78deee83ea0eda06d05fc395078e86b2192c30e126f852d64463785710401433e9270a8202577247ec35bfd4154556f650d40668f8623206540a5004b49d634daf6aba1977986f27dcad93eea4e605e75a3b65ae32f826587ec6e3a703b435c9e8aa40a558f3f590283f1944f3808e409757581e0499469344054bb796e870a33ce33183197eb2ee74dfa7df6cf466ca496dbe54be41f819e1074c837ae466dbe858b47a18e9a0ee90dee177b45c263e50960c0ec4f5002fd2e8c7465ccfea0444246d232e75b16181bf59f9b261908e8250233e558284f971a9122d636e8f3584cc80cd5aea6ae0eb5c242aa4b6ad6528db4a8cb102c012261219589e83d7a2e136addb54b0356134db6854b58aeb6af810185ffbf38d336c1e50cf3f455002a1d8e573d77208a8f5a5c926cc299e56d09fe80f001ab0a46bad487bd4c3b197b62b5783cd2b452a03c2b0a403b144f450bcdaef654ae91fab2b0a84f2d1ff54bfe129baab6487e5540e132bf1842bcf2191495947e07fd459f11748e2805912ef588f0487d93c75925b04e65171817249b0c358fb03e587e6c6c19d28c5b6a1fd3a2335088c8ee64516d5978b9633df8c1be1ff6c848f73c53390ed5f5c68d565163c0122aaa38e01706d5a74f5853a147fa7259afd13a7c2e38b7737ec1c69b92c2c3b7eca9ecf79ff254d78c86d6171dcdc3b0d35af82490972e48941f600983c68639df9acfe0561af38055e3ea3bf14ca1848eeef684fdead62b256668c27e98616df7e7f8807ab41a8307329a2e090745e71f6b1e0989f0f9b3b7db7bdbc113d0536607a93381d2b568ab469db240502623cab6cc81238f983dbe2e5050b93e955cc0f29ebad38a380f778d95ff7819f74b5b44788be49a82b1c965641d04d253ac1370926b26264aa90111b25c6ca7e45689a444680c5ee0790166d31ff769d1ececb8a49721bc1affbca7d1db395711a882d8560420447c34a1860ee6ffd9a0c07860e82ec072050fbfa2ca98bdf9f7a2466b977693af8d9fb1e7c613a186839744334ed18c7e26c73a0bc504a077cb1d111c42c822503c94da7c5a9422d901155695939e102d4ea6ec27e048644c7a749db2030c9fa6c03cea93043d3bd9aa351934ac942de6805ac1c39aa4774e1dd107c9d9907c6a2e6df7feb5ebd3ab410057ac1cab8ed02b5af1cadd5e94cd95af53402792460f53dfd7a0fc81eb49a55f9681f4d40a17231e00c7a27c1ff6fc9231c4cad1dbabff65e6137b6e97cea9283d9cd48df529165fe82e8202a4ddf112ba8f6c7269ed16ba2f87e04d8ee29e73765a287a3f63d89f407fc1a5dc023fb8e8ff97390c0dff3d6e0be8d7c58847ffc434c2ade0e177aa937573cc79258021c54bfe9dbb4051896478ff7497b9979f53670867dc2843f9c8aadef6f7ea17698466d79124a1962bd0515bbc04e2c2606251b5b8831dc82c8ab6a5d2245ba5bb4d1eded6100da13619497bde3190606714ad201b855da7241d318adede493f59617c51b474648c037bd77133047875bd3a209089de72517479d2b90aa3680e66095ec7349c87279095b45d6eac46d193b30dbe3fafceb65134bbb0a14b1e1d37ffa594fc59f4cca2e19337c326d33e9e51347207e9848389bfa27d5661ea10afcb287a83136713b2962ec725aace0eafa15cfb7944ce8da2ab95b54b383c6b08f657a123d52e29e407a813259338b4098223b44ed7433790f2e8394e5bf39391b97551fa1efb449fb84c4a56e9785d25f33c021e1fadb7660bad4a31647ad203298e4955985f7490d4c79d4137839538549d2729b12145db78d6a6b8b4fec4cf527f47c08682d97554a88614fd52d327a224143951b35dd85d996d46444443ab31f0990cd391a2f9ad15bddc4081c6fce92d9b3e10609263c92baed43cd475132c4e094bf899665a09e6831ab0817e06ec6f35948a6276455dff3e0322942fa293a29f70c819677f7231f68ca1747e69d0e568046143b5b167d0a299565fe3792fdb12def53237102da3616445733314b844e3cf324521655c59a72e867d9660fa96ef95c2ac43ff8f5734b74f15f49180e1a713ab8a579e9c9fca3e6dec6ee69e98db52212aa0f75bfc7c098e13c3a0a585bf0e8c097725de79ff485451fce3636972b9044034d52b691289648af8d0a0898b13a57b1c46bc05ac0543afa59d834ab627ec461b175271e41b6653d36786a223f82dd4b53a353ac82da4886ac6e4208d1809c17772235df54349b57c150bd3beddcf8f0391f51cd229ef5116ffd0d080081e4481ee6629119a7905b87f3aa24f27cf4b9ddb029f358c447c01cf7229d664a3e4ac5f6936196a5bdfdc84f2e2af126f010ba9843112ffd0e1364bc8c12f1cc63254de0b7ae83184057abf7a9f73d7c279b027283c473db213b8ef7b15595799b87fe8490a6b25ea117a93be517c71bd5bd092300af07fe847884252d48440c8adba63d12cfbdca40ebaad8cf792eb1985999b6d82d7b74067d166a48ae08adc0b20ba81d78442d37d4634556f1da8ec7480e84c55e7ad42c2e985c04cf44a874ce181d23010fc5b2d68c50250d8dc1eb34e62d09345d66856b587c8ede9495201d1845278bd13bc53299cd7aa9a386f7ef5776e40bc66548f664541e047f4e0ff1fbc3ee0345f6e165cd2ecd9c43aa34d8b9f79fe9db5db3b33e3016b1d582c49425f5e24fad164a9e6b9b62833386f231255690d6c16e7b71e206f5e2e7135c9bbcd623eb6c85a22b1ca447cb89d0a69fd67ebabd12e0710123dfdf5ed2d050a9e439f3010b10382814a54b62c15890635b4a33499eda6cc06b26a7f845450a277d427376eb41f3c891e249fac2111492cb4003d96ec3f93b91b4be5dea8235c8ef35dbf74485ff6d9b77b40eec58490c4bc2ce94b831beea244fb66eb25e4d6e0ef04d867ec643962f88bd69111553b3d2446454bbba9e8cbe58ac13d7a17a9156f503475357c06022f8bd07fb991c0ed7d2fb7dea73ced98157fa459ac41076c5b198baa2e4abb22c916d5a46cd55235b85170f6b5f54c36a1a7df6493ef86296f8daf7805905b06e93c786fa7c5f78fc9106ade198921963224e955b26075779c623f732b813194d1e925a707c49ebfdee4d790256119cad5b26033a45afc21b9b6cf29d86d10ba9f7aaac5f165e57bda6a7160d1ef05fbd478aa72f2311ff4411ff3711fe2431ff411f43102c0eeb3af5280af888ff6a228143b93dc9f5e567acb084773e4da58af21a5b4f1315bc575be1d54bcc17f7412c03c31e8a42151704ae94728200e11e4017364e821c5f59ad7a95038196eddb02ae5ca9409eb291b88c04c2984d4888afa8cbd5ebc2408aaecef5a159aa7877582a73d6a584171665c4776da058d94d44f06eb8463bbd6888272e6beaef0b4158d2a53ce4ceb45e7b60fa31a62933a036e422f6cd4e20f05b777520646194a815e2545568f5b208095b06c38ec9d4409b35f6792b319f97739ac8c5cec867fb54f80475943a74122e8361bdcd31d20b87d57bcfb038e34cb3f280bbe78986f3230449fab39c2d6756ed5e29d2020213c5dda388049e45a7e508ae7746cd27aecfe3cc0c5c2f3622523d83989913e9cd8b2047c41ac411f6ec5932f18520e7f2a4091196880cf158cba7b381c06cc8f2480b3ad608b8006dc86eda44129cd2bda7a0dca465a2d7f4bd9e159112b701d33cce24c9d1bee5832d49a6c86482eb42617f43c084123395528174a2ba4a6325abf61f401b4aab39b0edad9ab0d5677b9ed8b62e13038eb68b7e61f8423b6e23a8354e943b7887073bcb3565ff2ff5173ffe36a4ade982ecdf1c1fbda551c81e61a5586d6be5216f354bc188086cb22b4df43326fa68ee12a6e329ffe7c35d89c9628872a8ad994544ed235aecb4e79d84cba2da88e289fa91ca3a4de6601fb93ab9ed48214a46ec05f8986db36c7cc450b65096dd8940d7a100ce7176c74e16039ae192470b49a27789816ea6131d60066888fc96917193b3ce4e8c5a6dba498d43c4cd6a1d5af82ccc05a47df17dbc2eff39d8d0196488ab2333f29b5d5cf24b5702f95f180fe08244245c8dd0438fdf74391420441178380372b141b3065eb3f1910792f1d3e53f102b476cb348372c44d424847b8edd037098f153e1900f31d72e7f6a01742e062487320292d9743cbea81ce8b008a292a405cd0a26213775e987046031bb1d8d7985ac580988f8e09c44f69e642308586d63dd8c4f0c990df2266c754b8b1a0da603322b2c07dc2e312e319a5f12cb242462050a17ad79f08cba646fd38e941aa116bc02a5e0e46350b35d4c8adb0005ba9630df9fd8bea960ae8d5ab8ff3de7c2f34c7f2b28bf014c184d7e024a736891d717a20c911ddc6b32d83f2c0f5ce1d2a322c199d7963b7a46add4e6d31dbc3a5b7ed569a0a20baf10c27cbdc01190de1a63f7531233e8957348ee0b2a41aebae71943e80ae6f02b143a457811f9606d352cd9dc62f8d16576181f5016185fe45f05a9b515f15c872bac2abc81257f026e170e084e93018625dfd4c5df3be4ec4098371c0406ce7165b43a50ad7c1432461a32934d85f8ed5d56704dbf860424133615b85dd7a08ccf039e8094e157add34b05d549d047c08e149c30f53a8ec0763321e00c76ad3a51b003d542fdc8c700116a5f2e8cdfc27ec429b6b09b844e6d9b32f63db5cc6915fe07cd13f44c81696179cc35fd4a585bfc13d66fbb8cc3549d82f4075394b4d273792ac651018fef49029060eeeed9e8f131580fba8b4b82fe9d005edefdb9ac1279e9353383747342dfb3c5545a06ff46c7a9cce307f9a30c08179a1c2d3eaac4d0a54023b6134e408a3a068d0cc8372d55d332b3ce8f20bb23ccee76f0d2999cd8ba625e3cc80fa8277573fa367f9b58e442af9643211c819931113b5db132c82c25b23b9f389e1744a0e25ddd2bed2851a809cb57a431b251a0a40221bffe2afe156fcf70238bc71cf7d64b45769d8b662fb57bbb63096722c77cc718e5c7cf3e601bf3c8a6e2942aa204dd3d5f9cecedf66b669913701e2d7a1b7cd291b6e23a4109a1f9cb5395810289fd401a8c1874f247db8ca4fd76554fe45653b679e908df3c87bbf46b262eae85643af0185d195bbe05d494ba64d963f9ed5f3e0e97f6652b2aaabecf4596733fe3f0329c31ab7fbb2bd13904b88bc0a25bb1c345f2ea3cf9268090fcf9caecf00af3d6f12e9ef4cce94144a66dc8e8faec3268208fc21fa9c1b0bebc18b3c5c1d8fff5476ba28fe5e66fbf68fa05cd6d0ed6f83f6403f1f11b80f80f38b289abe118d7e3c657283f6238e78d3db561c62d7983cb508721a4fb2b62c73d552ba7c09bfc0579987fdab477de859da033011dcf5855db7cac8494a479801927010953968a48a492268b5130645efc4a1969306d1f594f1d05c9b7e9e4f0a88d42400752b0541ebf0402626f9250f4c151b6a52c7aa9fc0401001d8e4914a0b23dfaf01673002d571491764b6d3d0212304407b6a168f82829c1be2409cad59881fa26cefa4120ca3bf3b751219c7aedbef83b76c8094abb6e7dd85887e6a8f9df026c7afe987192f656f81aa49474d4a0fb62d1c4b8683d8119995039011b16e9bb3a22dfa06a3f60a26448f5524ca6bcee92408faa941a01dc9730030b805c95525c0e174e58ccdcf94c73ec49187cc5adecbc293af3042f0a0cb0893818fec437a89a5ca8857d0539f31f9d787456b27e9c948975eef49e0fe2a23aa7769dbde78a1fe537f6b4840fd7fea89aa3bbc953fb5e0c67d62eda6adc4c2223e26005c2ba228df0aa69a22821a08516e0902da3069e58ac43b914f4d1c61adc16e69c167a28db03de296b7c6d03316abfc16fc2f4c7808003cab53ea82738057215cb9db44089867f1c61320f123c4ebaf1eef5462052e7b7e1e8cc633585441196bffedfa91dc61522944748a456e83a896b36fde63392302848b4d740c5d1b600d548e3aac10c6666ab078e75067eed294bec280c51cbd85c53c079eefa310ce74cc9a1bd3465b2a7d8568ded081974a5f1745d52bb7b8f34a4ea2ccffd489711a3e21ea2f43d0c441216e488b0944611399840cf5ffc95c329214b57d82d7ea3f2b8d0d7c348b841968c0fc89214a489e475674b78ae09465094d58447669a760a8324fefc100f3ac11cf65ae432a1d3589540aac26fee63864ea7ff116525fd94426ee8b9a20b827a3dbaacac01f5863fc35c780b55e7104a0bfd127ed28186b0fec705870548cfa27ddf645619abeeac8e461e6e879982ceec7ceabbc149ff4864206a0dbbe57bae4d71892645cf210192a0ad093fb90282630c1c242bc13b46877b9c1214fd49c0dbc1b018f7c101433f7dea91fb337dc126e48ff853737d572285a6796bcece43ea6bd9ddcc1c01b3ecc78feef2d72c3b01a52591664d0d44859a87ace37cec8a2127834a9f10bcdff56f8a5d0f0b4ce77bbc824a024bfe7804d94f420f8c419b2091ee65816f2909edb47e9fb4246e681a2ee54900b15a9f6eed01ad25d9a827de0558d83a0ccf518b42c0af3cbc6c98f319af46ab30d1ccfb8fbb37c54b63512d52ab4c09323decc038b11ad3a7e8d114f959f223a457cf1a837bffc44ea963fef412afe4d5fbf024ea8a1431cc883a0998b406d963d62c9578613c9b4c64835edd7fb54d73ba939a90d9e12c0c8e8c1b5072ecc795c4265571d884b1418455ff38d2354afab2d22b1f30a754b5808f06638fb0fc62d4d8835587a235eb6a12c46ce7e494b262e6587d9fad550a58a69afed6fec50c070cb5c31761236d435b9729e780edc128e5ca213c3a2ffdf64accfdcf24145c407b75623b9264c735fb02529d0c55314f57c5a31d882c5eaf9de6cc272b7390858438a4384fb2991cc22e0df553472305ad677bb63df06adf32dfcdfd612b4bda1423f6a212991e25042da04040e0be5f4f9c3c0ecbf51b41fa0ec93d69b3bd41e212a9de9d9f7475e53bdb48fb1c7e9151085822187b1d35142bc5f69f55fa688c56ee89c90ff5a3f6506dd426354f4c9b800880d29436ad4cfa7f4d56789adc0ca274f9419c6c5aa22f6b39c620a153d97176a66d9681d6d72c9f8921c6f77a400f2fcd11ae10ea55cd69786dbbe905f70bd10954a89d3a6b455d9c932833936161a2dc6c4844b3564cc0eb711a77d6386b3d87dee5c34de5c7082290f1706ecef8ee2addaa73fe101fe50943d98acc266b2e596662657ddc3e030dc03a2c484cde068a8024daa572f69076a295bf18145213a488ca87b7c86f1a1a6dbb1c1d843140be9b9e7b4e9f253d384b5410fdee2c061e613571fcc449d25050aed2de4c1c2495555cfc19d9c6e2a3b4e1193e0302be7be6d501a97a334d12120385a15216833ff939256dee4e08826417c9297f94183a402f6e035a80b5a0a41ff02923b6677b8bbba22ddb91902b97323c9c575933dcaf908c80e1b2ea43dd147dd943cf81a5ce1dbbb5b20fafd14ea2cf8e16f6badbfcf7b5c2180a7f965c57874ea55de0985107ae850c4dc48f42f2d53d0263fca492d22d1f851c59851e498d388bfc302e7cda532b678f48bd774e55c9af520a3a3cbfd36fe9eae4b085de2e50267d0c29b8982a0ad776cc4805d41f70b0d561977d6a03c7aa652636fbc8687267bd4570ac067336be1d2c3a35a5b7fddd3d24d8f14362e25c2d9df9c47e742362d5c3e7942a8dacd4602a247cc5d8d8012972d75277619ba00fd78c2baf6d6576a323b1f2855cae939294435a85274e61dbf4670827e3ec646554c3bae2f3112f05ff0055893ae307da79f3897ac593268d56bb6198b210c68af67214e2bbf1097d82f7b9ce1282f1ecd78eeaeae8075da89c5cfa01816f4d5b8139fa3f957d5aeb9241c32afc0b2469010960325a99ccd7ca9997f1a17b407643fa89b20cd2a8b52912d5caa243260ee8f425db57229f6338823e413b984461b9b993b09e6500f4fcc1da62405a7aea561329e434de45337d8105b1a695d179fce5d75703f2a6f12be744fe2d860aa7920c9644dd019443108a0f451fdef1acb733553da005fcee45b90a76eb5f10da2d29e25bb7ab7b65e3b77561bf92c6e4fdb6f4d62713a98072806d7a9b00d69e6464a6cb6d22fc27e77f6812c7238ea552cc77a732c79b27229d5e686c6820c547cb2fe487d6e8cde65a0dedac81c7b5623dfd80fc50d39bb4908e0a5df8dc87cea7be27b8f52ccbd1b429cb2573b5a30d0d974909847865cb978b641d486c94de96cf49bd077ad35f19287c68be1183b04c92d92a4d9aa36e8b6afccba20e589e847095253ec52e0987e7eb590da5d8f34e40cfae282724de904d7fc659ec3dd5c0eb66a5eda9afd81d53864f21aeb6d9e3674e4c4a240299f558d8c6aeac97e77d1c2d0e5614e83656ebd49b2fc0a28ebd164aac0e148ea2e7b12276d79eb95ac37c62744d5cc8073b7a40365c741ff2876c656c4e113d85d6dc28223db45d6e882ea01bdc0ecbab522cb91c4e8a970a746fc5dd3906796edeab1ceccc6a4c674f68f56240b7ea26f306e407da7d414f8a300469ec8921fdeda1668c87376183b71d067fdb5ac5ecd75797a8c39f18e4330d8e5f2fbb5b1cb8d478434e4be176d507509009322851fd8ec2a62999fad44804e28a978cd908fe65f9509ef245773299dd4bcbc572a2340ccb51b9823c19851fe82c9a286a31e0e052fddb7413ba321ba17f23b3f0cb4357ab539bfb6c2c8d49dd9fe0b71e77e5c0cfcfd5225a415b38678ac378ea3639b03f7775ad98e4023da22e754e86afeaf19edb4c7af9435568d484f4d9108e6ba08c7251f8cab2dc95c170a4a1432ce7dad4a2420f92604800e5089a591ae3cb1907de75cb55735ed29cee7f5b79d06b7e56577de3f4745db8936663e51ff314d7d84152ed74b35b058ad91f4d4725f7041c862a16540deaa39a02dc3e6bc3204db0cc3f82541a4415ae47d1615141334ec2a6b051815f83b066339b6179159ccfd40bd189560a4e57b47874db74663ac8566d2f2ad5d76ecbae0e8bc081d06b37d5f3ea3ee992d5622c82a528577417d012539f7e27c61f4e8737e3838ca538937477e63366daa600ea5cee9be006cdd2d6f8391bdd3a519e45d5850308cbcd9ca7364b25774a3195ddad025fd7c938b4eb9d55083c23e20eb243e31300ce9ee979cbc23852180c1c8ae5ee7035b949cc0b25fea043d45a0d582d0142b00a51c8fef3a62b41085490543e4368eba1b104b04ae284bba3afd9f16ddf3e0a71ee1068b67591d0b2bbe6d4ca891b7776c78395509453ceadce44ded72c9a3a5ec20fbaade4b1d5b87a119b68384c161d367a207eefa07bcef4d7828a3febfab14a886969638b1bd30312bd794524b1a5c91a08a18da35e83353e1083dc17530af253109e8d75b079d6059f25365f33fb3cdacea28c9efd54c9f731ab638f0d829de1d56b473701b5f934fea9443ad40fe4ac5b7988ac99ecb2ddc604563139e22664d9dceb1b4e0e553617abb128b5f967405da49ed1da8a5d3dac170f89fc11a8f6efe31202fe0e229bb493302b6535ebcd2d327fb3a9f48ec0d5b6e4cc0e3b7983b72b48922f793b39d4f109cea2e8040e948d9538a8a033f869a4a695954b89a40c19e4058f860b9fb94365d0c49a428fe2a451d2cf76cfb7d9596a2674b5d22bb0c7cd01ab5281aa9ab255fe3191a16b9738c18830e148eb9878101b2ed9b45784c0569682b4a0c666c1706df0d2acb752ac21acff757de1852bcbeabdfe0a1687c05afa8f1989cacaddd3d5586417ec0342f180ce02c0341208b07338646a91e11ec19c3c5af21922b6b098f6cf0598dd798233a391a251f15d361fec0c5e6c7a431e2f1cf42e8e7114119ded89666a96cb810016b38f0095595ccad1822f62d97b8d9a0703c2646f611c0526c5485dae759948b29c0f53b686a74500d08d9309f0a3fc26ba82bb430aa004675f7a2b7a91baea5f0686241a6c83cbc629d26c90c385bfff8a9f911ef3b3c73ffe4fad35c404407417ff0c79b8d93123f70113784c4b2ba48eee54c472152984b74fcc260c2a6a4bcf6483517522a270e3a702fa78a05a2a67fb74f831d0035943ac15cce78bc77b30e8d1aecca8906e62ccd55b5f4e6a344538567e4401c29a52f5742443b25d7cde55258888bd022300697dec7212eb76656ac8091488800605ea4f65ab4b1ba0252835359e5ed69907758c03104308939ea0ade137d330886e56c4856c3bec75a29b3fa81063df96575831ff105e10839d9de7d9b1f0f6af8452f6d6bfd49261ff2e5f15fb65b3648c2294f20ea74514d3ed85eadb431248b7ecebc2c89f682be272fad096c2a1fb339238e540fb9d687824d48cda6655617d35888eea8753d4579761d5d133a744ff595d20442bb2f018fa7ae21601510b582751a429740c87e7122299eb975f77b5dedd5cf4535e050481127521e32d18bf03e45be8cfe0f6da52d945c8b2a9a953e57f404e6aabb49f3162178ad752a4fecab289d91d3c5eb6c39f9f55035f2ffaf1a8f27dd821f42bd07dd299c85a4e7d2dca9a8ec7f48e8332d555237ca261c6d90ac1c1e70b08c3e9ac5deb945f11156d91d3a85cd03df0fbb81304bd2fce36584bebf0e75f4dc1e02c961c7ffb2675321d64e1b411884867f43f27de6a6fcf649402ea7352f4d71343cef0395db56830aac1fb107f46c5e447583f1a3bd2bfc46722da4253b1809953ce525ba967194e0ab4a31226f978675d5ca701c294865c0ab9a7c45a3623d50d1345e38215ba43ea33ecea5862ab023c1960409d3e800fc87c60834e3ee778e4f9e4762802cb4094702339831d23221a02b46c1b04580b9c5417bd15ccaa34bb24e906ff583619fdbaeef60edf363574fd8624c5d2a7c75c554301c1a464fd8529635183767a57fae35b5f02e8a1987d3a7b6b76894a65f91f531dfa9156cc7011378299a56dae0be8b30ff7bec165742510a193864d97705198d3120d7b21f8678353173165151998562a46b8187b74d6dd19ebb03113ada5980abb4cf6d59f7b438a4957ebd4db77384b930a36c56e5dc5bd7f1145dc60d8c0d42cf1b2b901c62922a48400323b086944c914ddd8fe668dc99a72eeb5e4a4cc84e0898c2e99775367d1416b0bc6584222b6233dce186abc113e787ace60d860d42d3dc50e4d13b3688387336186157b618a6826df4617ac743d37f4e59eb0868a1882df720d3888724aa7b819e1251342c82cedbcbb33fa087356745168503df6c8560d3cb2f938e230e3af59e360420fe9b33c6523d7eaacd064ac8880ac46af59390c22a74e704c3354d79a4a63a7419043160bba48bea8530db1ea53bb6f3d4b8d57a5cd5d6e371634d0930d02fbaa1eaf1cb6a35e8083101bb66057f750bcfa3f8e0ca24eedc0cc0d235fc5f4d2a0ba82f069660ab6190302959c01bd8baa8fac70e4fe983cbafc7e8dcb78fcf58df8538cdee12f70ab236f0772ad9966b47450b299fceb334c2762d7c2e9aa53cf5eb530bd18b6609cb7c849130f5c367189c57bfb4b9ac83996968ccb2080e94d22e8131aba4816dfe88ce40f9ae1a4b0775484b35e5bf31709162da95cd26b070ba6d0a2d1d438d1e4009a9822620a785974113a5d31380a286a88d8daa8e636339a80e180615f28fcd77f6c1775400289f040e87af9d9808a3ea3630e489b2beac919409444218d201f1f281124b51f4e806e53c04f279e25bc7ba10ad930bf0a700243036d9c7b7e4a3e930150065aa7b5f33390b635be313b8e6d528f6fd233e55bd8ab9a77186d8292a557dcf1a38ae55361c435057268303821a391a163a5afc67acddfa677597ef807a7ab80959deba7e258ff4d9e6eb55a717750dde7687262ed12ccb2bf967b7c2337b93873cf63125828256061c5cbb52b16e643ea3e8ed39ea5013a028c74b24b1a7ae94cf4cc90a043d8adb442e233cc75f1f52b84377bc7416354a92225d42e81f42c3497fda5de01664ada2ac29c06db3d003bc7f29400f67699e154a4c88646e1cd4f22fc46775136bf38af5ecacc8f22a74b9c72122ceffe59ce4780aacc8d530520336f9712665752a25e584518c9d5de7a998d0e18a2c2f347500ee00ad9cb4dcfff145e9c03b848f49c4174ae08388b109d6aeac1a569497600217356cf45b4976972e5e0963aec01dd8ceba9fa33b3af9a121d3dddee190e85bbb2136f54d310ff30c4f87544c8dbc93573f41c35a9607e2e60acdbe69ff7bf4f2c770420216eb667590141bce53349153d12862fd59eb2ea7ecfcf7d55298e5f7e6a851b79950a71389287d48e57c79ab19e3229d69e403c3614497aea70f580512ced212cbec9894f4c707af838f09ebba6c3aacccb040e160704ea530580e7cf5706fe86e1a6e35846ec9ddfe1866a2ec5d244f1f88c75f52a04bb6dbceede39c8d2782b6a8eef3e8dc01d44f934d0f1cd87d9597e763d2f628ee4daa792b111eb20dc84c34144ed33606de49f44105e81eff0bd08d122d29bee93256b2869833fbde0f642620d1e9e97c42f653f04a59c74f0caed59bd616ff0574dcd033c084c5998c396138e76309799fd9141e6ad162504fcf37e19a4f5e7f31b30913543c8d742745de9a30edcbe6163017bb933ef97ba56c26032ac8282243397458fca626f6b69c6ca61d29c5c0268bcb4597f9dd5ec5a525883654e8e1eabaa7c1253ee9e106a1fa78a4a849e9d1beacadf13e4cbbc72b2d14cbbeeac9a7a60496ede0d570f4dfce82efbee76b98610734e3da3f7500f3b58726d0da2b05624bbe41acc494d710e4a393322cc17b8bb34b310e52032f9aec3daaeac690cffcd268cc9698f4d406101571a7f1266b6565b0c3e83b902874817eb460486a3586baa33ed32840e925875a53cbc498f0321015e52a77947aa405838457205cfebf03d0d44dd66b22f66ef6d2d900eab71b37e3fb0f8e60991ca1d206352d0adfb9898426ccf26d49829aa9a0f6da87a358ffa386c06418ce4a417bbddd013c445c25aea4f60759d6fd946715af430f90d0a4105db71a2216fc7108a840e287196623ccc8697a9ca2267d43e84d0e04b43a9fa87f5c97ce3d9a7852279e299d436c2353c47707f3f351d27f5d1cc389ab7c7f16fbb18c00b2da42072db67a658c5ba94127aa3a126376b5cb0a42e5dc8a8b2b0fe9ecfd340e55e6822fdb273f8193597d12748a8f9584e27dd492c8ce43240adb11ae0fea478fe830dd6cd8ce974afa90262e1dd59409b545b7bb3e5852d30cb0d2bb9a403360ba865603e300fff32257192e4262071da104a2574beb006dc4cd83bfaef5183ab7e78651249540dd012f01ed7a438eadb3a4d66f5fd096ea8cebfe0abd313c99766a322e725826a11ab1c56572d478c5a34b29dbd77b9cad06edb70f739b62659a0e9717a1daf3351d2d49bd314fbd47d75c0293a25cc524596c1ebb4be29e705feed1bbb9cdde31e880f6b65e58fffe4b44dc30e4a2d382ac8d73f5c5baaa40642384622be2aa12a93cf0b73fe31a9f8d002df1db59ae4f5a8b3488c2256d5f8550c070bc32de2343d8b729047d0d7794f38f2bc0eac3601568c4bf3518c9639b2fa3e71b01bca454f8bdede47864cd258ffd60e9718a22b78b20079d5bed961e9eed5414460c1f50fd41c5f9832ed57daa271b60b1606229e28abcf1e059f4adf0eafdda38f93b417644991219c0df48374f4c6e08e6dc152b1f78d84b343274ace896607d10a18f359c3d1450fbbbdd10fd47c5455774ae183f95a1b1c5c47dea9d1608c50a7cc2819ed2ca6e723199a7c9b953187803ec5264d0a34bbcd141ce28ef4ff9428c33b076836a5502bb4766fca6dd51351cdc3933b4c749e6a8a8a77cfa5f224a9f79f45b225d88dc2cbc5b0a48edc2b71a822846241a892010b297e2edbab9cdecb483565bab70b5b5b0b605ca3832613271b6715cd788049c8c6492767c76f523126b6b29ce5a72d5b9a069baeb975e5e6877d5433cd781c49625ccb0fcbba6f1e9a04928dd67c9e2ebc8031f089665d0792f783d2400d9299a63960dcae8651fb9a1e55a6747e7bad9a9d0a8173cb56fc5b0969992f2fd3fb6f4071582b53daeaacf624d95d1753117cefb2567311d8f6552199c40536315c012d1b7522c35afb91eb38183a5ab9722d16b7464256bb95f04275b2e4d8b140ea2066ebdef9ba074e2599264bf9aa0031086a9dcddac3003cb2742b14a32828a04015e5cdccd8be1d08296d5e9d619db911ca30c1aef231b4b0bc3da110444f89662bd833ab14e9896aaa80c2c60453e52c689dc2674a378fa203ddbacb9de8e6c56fabd5de9e5ade8e63ea4a2256aa2d17b75f360ea9cd9638d7be8a8a0c624a0dbf481b801655b89cd8ffae55eadcaa8bddde9b5fe280b22325885c1b866f1328ce8a8a07ea42fc50ef96f1c92f154ea7600af599393df4260f368d8dcfe253022c830fbf134229e33abfc2f72dfc7f03a5fe5b034b37a44b629c4ba5a8be9df3c2154093b5cc6b60f5407422b87dacc0d6f59fa3f2bd6c4ec969005fe2710170016949023fa05e078c63883f7218cb72e85d47d262b3c8d27429dde01695251bcbb3867afd67a9a1c4e1cbd26f90283d8c1840c3bb8b7598748ee0cae46a163748221ac34e488c7a10911ac2f36686f4c6f3309cedcacaa48163142ed41d8944eeb6a4e6b2514f4f2553f8cfc9e48da71ce0173d2d43057cdeaf8a95f373a2dddabfc6ff4482504f0f8cd3101cc86f41ce2e0000885fb2c59aadcabae486067290b8af3ee9ad5b885d2b04e65002ce489eac40016b5b9e0c6dc426070fc65098dfba97a4517eb0b59ee48dd6cdc771d224e6fb8b350aee4fab61389d848868f10771d930f3ef210f7a050f6e5197ef22debfc1a7aa524d042c496b198034d58e3a834ba8bbc511612cae83c736cb9ac85c1659c12ee111a26a64d9010328b7fb9e8a43a5ceb1422870519e713e4235baa78fc9ddfac59c718d75636289ccdc29f8f205115b85fd34ece391a0034cab385e1fc010742db498ea7fb4e910f9f400d22f395577a57113aa807d6b02235cb1c604a5bcfa63e3e8d0a43a0b888ca836d40ef3aaa9dcebd873101635246d11f69500e43fa9858a6a310ba61526d798d61f06da3cacc78f13d01e08b08081c04eac97a2f2f11468ad849621be524ee23839862f4782658ebc337da7339373cd5a68bbe6c3ac7405a96ef35798f6325c666b6fbfa6b726485c987b16c4f0ad24416157265e899bc6fa92094f06e185e34b6aba161cc0dc5815f2973e24c0f836bbb970b01081ed84b6f28f686228aaf46936c322a21adcbab27c8814640116e3c89f6e8439b3378180595a8af4e26230ac31da6b03541ce3ed09f267c7e04fe753f0006d83dbdb6e0ed296df75f87aa4f14cf2252391772babcb02a37b463302b04407329a75a50d04352877ac427c5286d7a0d1af878435acac6b11dad37b103ef0cf549181c53faa76154a0d4c150c374116a7470c6dbde080a1fb105e97d345e7cd44bc2d7085120d346ba52592aab5ef1202070a53ed86e9bb158823af49725b9be6b031f8caa9f57b53ef7ee5a5e41dbc860d0728898e935f08ad566ef9bdb4e2529105fa0faea6be8bed67288d0271bb03e0b779ca32e16a85ef837e3a5ea77457b8416ac0edb01f70ad50bff36e215d1af23843fefb5f38295bd03d048aa754ea0b5e46b4216834110774c182d231d7304c542d2519224893be8706c603242ae600b1ea0c24f4860420f59a95ff869f7b34059e51c58dca10dc6bd6f2c28b392e86866285b018b462a9fa1ebad4e0a793e57e9f0f4880c8d6b454589710fdf5bdbae0f75988b47f800722ab54e3e24a3ab35c1eb54fd0ba4b755f48111cba6361b760bf236b5cbb97e97b773b5e660c0f29dcb48120c8ccf92f160f8902c7b96284c732c4260cc095cde9eeff44d3380c635c0bc67cb38c9e9c50180c16205b9a89923c129f8ed9273d15f48023f39690702627a8f6c060aa09f3aac25d51bbdee4e5f332f3e565ac36d622bac7fb5af799850d4fb135cf75cb8c1d1cefb3860287eeb8af6d1f849dc2113b576670d534253d1812e17a98362ea176175b3632ea8487304d8f98475f3367fa8f5d25b9ca2a9689015985becdbfa63e243a06ef0289dbe3522fb58640affbea9304af5d9f0760c62cddd688a75c177ea15e3ce8d1609c7caec12e8cd5ad06640e4f3973ec7c4ca5c0430f97d9b6e1c459eca5589f4d531bde3ebabcf385588533c5b56f2382597f19e9dd2ab54811074a624f514273ae51c0c622819a14f521d0015866301c17479d3aa48cd7d7ff1ef87a45c00c11b327a5c49f944f56d54c58d3473696c04da857f6099f8958d63d9f8e37699230dc70119db1e5debe9b775f4d03096342f13422f2743acb50623c56227820bf9e43396b057a5b51510dbbef760ab244f87e835161b47c02bde7830bd7f58be3286bfc00de318a7b5b628519ff2ece13dafa0df4b493741a339450f903b3efc2675386fbc41fd6549a516b3ec8f9ce3a374d4bb28dfd30bc84a83eff4c16aa6b8904d1ff959519cddae58c10e3000e986c162c887f3d27f3546779a1f2e4dd133ad7b518513005777fa3bae5841cf44e61dd906de6a4a29e65e8e5195900859d0c7f7f56dca98142a13e559433847a19a644ad1bf6361e0011a3613bb31b3e356124245ccd64115e80c640b9d2f1ef74214496ff46bd2353ff71278b541748f112af6c92774b7afb297d28d50a40ebb00b6e5b588b41f535294e0cd40e6258ff3f568352137767d1878ebaafeef9a50e49a630a000d01e966e26cf2b2afa2ddeb5a39585790d67a453584d16f291aa99b4f2cc85292ebdf356be8c288491fd2631d16bdf0201054869992235ae782fd667ecd6fe03aeb6f7e3fd990723dcd3b2755105e7b2dcf8b46f87d652ed256c2bc986af329e1ea974364cb0c64b17be798eda297d4bc0c97e2658a70603eb532482526c01ce3e04c9944e91ee10b7a32a2c54a2077380145332c281de59197216c0715c0a8d64d57dce5eab71eb716cc8e807f8c790ce6548a3e3cbc463bfaeed1bf859e79431c729915f6950628f93142a1085033149d95461eeb89ae3ebb81d01f611865277cab58837685b32dceddfb0116e1c30e15838eee5ad2f27832b1fb669a20475bf03e440a8ec7bb5ea11b471916524b2888f7fc5123974407c835c5fceb0818d59e4ba41628fd9f432392197f9c6315fd2def7f1f0d9e0eddf2c89c2d4231c899e065732ab773d6ea136088f7e17df2b396667f3d15a4bcc8866db42078eaf6d19f111bb11dfb6d174f71151c1ef9d00c4a7574b0e2d1246de9be537729c97ee4b8e060f5fe5abd36f8732cecaafb8144b4711f5b8bab4afe3068a7f12c14389e3469827d50e8d9816a7c87e2259edaafb6e0d1b9819408c3e96b3f4c8c7f52e5697d4973bfacbfa5ee083b6d4eb00c5be8ffa7ab1c668ac0d8c4b697c7d5b07c8ea00f742d64d1ea34fac5ac3a08d23dbb0a415c0cf492a4e4a47f8638c920fee8650e57fc93c3b8c2fe98c0b987ace8f5eb0939f661fd4c211181247eecd9f8c1e834b99ed3e4ce208658f24c8180c1adc7223781f72c8ff1da8186ea3be8b2930ebc7648a80e510d7e603bba9968fc38ad865176ba67dd9cabf030d058e77664d92971574ea972af2cb6785bdc181d978ad37e5384799c2c63b655bf30e5802fa26669f9ab4317c3e6a9c469ff352969e8cc8f4ea8e4b9f2f344fff4d4e0576733800f6d9f57a063ee5286339afce9d458eefd2e880d66af3a9eeb1c295d5227a19ec44fa73cbc6bbb0198ca77c00f71f696359fa1d2b8ecc3e6dd7c74929b7aa024b1f2d179fe5e79391d6fd64306bc4f5df62cf6d03e3a6910aea2c45ac1b1f3fe831e814d19ec0500b10b58baa813822c22175be8d13146a5884314328ca8a0970e754e6a3b6df647cd0e8dfe57d9a1cd5fb57735f0a7e2ee6dfea8edb4dd0f35bb34fa5b6d370dffaadfd5c48fba3b37f8a16ec7e6edca76c38050f2c774f91cd65dc7d32ea71520b8a3c7746707e89401e1e3bf8f019a18b2a8938a921c84270a2a72e67fa72d99856d9a7305083e72326e16769f3ced9919622ebbe78d466ab99f784c2e652889ec171baf5920138dc4104b0416817e1694b0157893a8ab0b94a82304ab58233605b6af0ddb1c002f69f8cdb54e7571661e2d2ee2a6a16717a511790a16dfe601db9091e2a278bd2008a7a05248cae1dbba944d490a311e47f47f26a0f142ab2251de407ad5fa07d8e0db3a6e839f702b34542f69694403cd017d30f8b6bb73fb8d309dc2d1580af363ce1c814375bf83b0a5eb9a0445d83d00fcbec0bf18de45402e163a279a673a9339183bcba479c2b78b2e3c6394831441161a5c518573f5703505ce5e714118ae228f895d494fc5d930f68afde1f462a573027013977cb12e7513c4973b951329e3c2b79d4bad15ce2bbfd6653249c670e251f6c660116d54cfa04318d1211263578743e3c7afbc3520735da9dd0861350bd4f4c664e9984873b5948b752adbe554d1dbe62633f54cdb58ca74459c2e76d40e075f5d2d01753659d60e02d05091079d4d21c2bb5ef647b532bc030d966a42e2a93293eb28ec71dcb82178403269e59c03ddc318c48a268da0f801b9c3c23f2339b321fd0c1c741baf322c510467860b50881928e9d74c602254458e2f28ac1ac64321ef3d5fa0a4257bd96f0320b0e336652b6998467b449098c660e648f7651a77e0f25f06cb690c64902058cd75e5eba238f8a300a326618d04ac19d98582a02404f2952eea71d28f058fe16cf62f15fc116414ff176929d929491042ec66a2f5c001cf769296fa9a9766ad7ab161908769a2a0a680da61a8117016a03e79048bd77eb33741aae4215987bccf3d10b537f0a1e0fb6c9a62b3fd175f803a8a81a2935541b041c1332f8ac4bbd57c324ec1f1a0b69ff102f16e5b1d5a6940a874213a8514d03b15069850d955d7f5c5148089d32ee11f07b13921c61af5137426d7d6922dbfd19f25ca1d685bd2699860faef29bf8a97981107103ebe1fc6e201c2ceb20ed027273be4075f51eb7f1ef24a010585896d05b2b52dc40ab775883cac4964fab30cfa55ade20a8fbbfcfde0c8ec38a0b473debc7c877bc571a5d087060be840e2587271424a12abb22086b501c60e274b29f6a3561123538e00089b152dde6b77266815f4f3bacdbd1056a81e3036a6560a6a0134ee47c64b402b1ec840457ee45fde5a6814b0cf097eaf810e3cb912ba6867bc12ac1847c51a584a9a404513842fa7ebcabb1ab19a2326251c88592954ac2f8b1d6bc2abef88eef7a0a01bec6161277d040e188337122dc7d4ee673f7b7efe38442e1125d00dca917ad11ba52d14536bc27ba9dc251ac1c09dc098a932305e5d642394b7bd0c1d6998465ebd9628cc567ab41d5072f71bc40538fa371996921a4961614bb90ff0b4b95598c228408907d9bf43a01d91a1a36e3d44e63f619526621047157d040e1f8ce8923cd218faadf044bd7c427b2f9e316cee17965c53ce29934d8c46e0f2f3c8cf05d02b06ef5fcfaa970101cb051f6ab0e3547c2135606d321b5bbea726e822991107e1f1b074ee13e95148895fdee5f09e6918f0e94783f9ae3f67eb502517df49a014e9dee6fe3bc053c22ed1977d176ecd2adb2813192c0f1d2cdda72510ca9adc4f27249baf26927e1b6e65856e08f5a6d22782f2305c4d10153e84a7ab4bc012b0dd486a1d3ad799b22f9dc8d3ed1e0a9a0e5fe71e050a54858fd18f7c40d56d44d24731c8cd73c4ca545bff318dffc79936cf1f7fc60e8fae672b04ed6f3813416b27f7744980a272d9a5254a52b1e8aea043507e393c7e9007b79608440bbcfe966463f405b17f6be8cc62ba8cb4bd066eb95163c22f24214c34fdf08848ef8818bbb7c1d150f82760e87942c6f2d02dd90289d30f80de6d68d704d241a0fc40bd89c62b0a56e1e00655e88515940e5f253fb6737ec66688653b302435a6a377da45882888df230f7d24b55715970653ade324f2acacb58afd187c04e1702851ca4282024b124e75d9bb4ab0a290854776a4961d09b4bfadaad24840bb519ae41b2c7e1642f086cdf53016c2e975805b54d4c054c363e3c224a0c5cf890be802804004f4d15c3c017d0881c3429852e85818b5d6824702d9bbee7660d2956d21caab23e6d67139e202a195414e6621e8c67ae97b494334750a12180cfdea5e0e6896cb83693a119d31d3a09c20faee6a70ed50ba4f59b61746ce265f279734e800089f33d333ad91911805484d393d46dc10646a8a7a96b9451982a1ee704f1bf6fe9dea508d1e2651a788adc01e12961c74a089a1425ca4af62986a2c5aeef9883092981252e908280dc5c66fc74ba99ac6b9ab3254916e02bb6a43069572cf9d04f3570a77070ce473e0163664219b024821b697dcdf7bfc498dfba14c58c5e9100ac62d6242a7ca69d5abc03a51b25be57da0e471ae17d229bcfc4c3edb4508bd556a4d3374d637fe4d21f3aa38dc88ae8dbc0ccc0eadf510dce675f750f8cbf7fde195965be8138cbc59ef151b7cfbe23cdc0d84deaefc0ff1dab9c26604f7ba68a69750cc0871518177725d7b1bad9b2ecb464c74164c1801672e94f3dc35c257f26b33f6bfdedda8c6800ae460330ff8acb4f629e8eb19aa315e57d1dac4b5debe9e521d5a65e358a04a6261876d381e4d1b005dcec00fbe9c69646521f38c1cdea64e6497c42f2c8fc1145f2c8dfcee3228773ac142b25c80cae13517374cb432abc1d91619c24a62442870a2274f8f987468c3a6921b529b075c6719008a3b01963e2199c1b54cfdf455e6ab5041f5ef7b8573f82f51c7ec8960f833d2b08cf7fc32fc07e4d122711fe203816493868d80291190ee994a3a81342ff4df0a68465a78fd5068273f0123d064514875e05ecff059b95039d1af95bfc50bbd501f2004a172c6c432a3f0236558685b0b4c0ee24699d2b3040845e1bc27daa75418702b4981b343409c1db06627584604a992b6831c5c5b04034850860f57324295de94602863a080ef547af137455eeeffa0ff6fb44d871a54007dd065b7ac01a50efaf0e57fb931b49989cf18b2a21bbce2c406fee5bc063826074b02d82bc3412b259dbf31d73dc50642d015238f8218cdb5a13fe621a550fe0fc57cb25bbeb3126aa65cad69960fee60837bff0f38af4312d9b630aef2e4a0fa763a757bd31ce591e92fd83ff474eec8c688971956d413f9bda4c59fc9f9da11f5dab3e50653771447380a331611592ac5fc3e2e1784bf4b4cb4b70ceb36380f870e68e85f3121a56d91f08f5a84b6fc7d06163ae16949e8a0f2f0c19716676472d40d1fe3eb6179afce70d4a381c60c086bfb2742f9209ed069064d4ae52cd3005abe930cce9dc94109bdb12e775eba475b967ef3bfd627daa89da30d2b46783270b9a2f769da5a32de8f66e21268e0e7aac1190c6e36b3eba0732bf7aefeedb4fec2f39d5a0bce32c5e7cd13b5d1094ef0465d0b03259cffddee269735ba04b0130cc02a3c76a2bf05954741746825716506abcf80aa2734077578aef485ff571cec042691bfaf3b40ffaca586f57af7992e7ede45b5c60d69efbe621d75e731e3768ec5f0cfa573f65c80c8199afd3996d63fadf8a57f0d47f1ec416960fccae9833cd48f0017c8d93dd5283da8112d9468a61f52f760daf6cfd228e75eefd9aec173729d5dcbea4b15dfd7fdfae0a7fe62bf9b3fb30807aacbdd591baaac1742c23210acfa876b8088b4b4a62807db121354293751fd556cdc6c946ad2c21201a11393a0b7fe217b861a5a97029440c8f9bce28d80800eb117196f71479cc446675901d609786bbd21267b01c509901f3e37035528e907ff04496a04d33719f972bc6cfbd798e833fa0bd48de981709e865d57a8b9a0e1ebe20041a8150e9adc00303117ce753e68ab1ac3fb539de43a0f42b60072c272d0441a0f6ceb7a3b0871496694d5380feca5b2078495e6fc05c4d5087b865888bff6a20a484b7f204005823c8042def4692358836aa3900b060d19968f803e3e747f683bf09cc241370634100637c29a1e6beab64ef4c0145af8cdc93eb13a30438829024bed2a900fc86bf3336a8cd2c150cb94116a798dd02e2458f382d99044ab1852bd50bca67b80a6377135226cd15216e53cbaabc261948474a6b7aae1dc96d5ca01e8dc1efb67caa57bb2e61230e6ce8fd5374bfe8281ca232ea4b208fe979efe16be77018bbe577a39ff6b3cf95c39dd4d9ac45a2c75c7fd8f20dd952b1a95a4f15ef6c18625aa0181d6f46b2aae045368400d80f0f74cd7b2229c5f207dbf39482a973c31338d06f4767da301c0b1d811a1453d677a81312d770dbc89dc928d05efbe9fb8a18805a9627ddd3d847f78951695e9fde50d70e0a6128c37fba5f7f4fc2f6699cc6e4837fb6d001d095b9295e4b8321fdda2871a74529c9637ee2d27c120b0b708178d6b3d8267e54ae5408ed6e6065ff492728090649433136b36718ef34898defc5fdd0ada6694899e30e593b097c28837336841570b6e059216b097d60492af05605c8931618047290df266de3d02ddbe58b0d17aa0d8a02432b90e2fccbabd79aa62d8e5eb52ef0e86a87e8a577853840223c8ed53c4be323b8b54fffa734f0dbbc4a63179ea20fc7290d599efa687fa8f4210c668746da1bfcb2077f24ac23a9eecd10d6545d7f7ba7136c82e130503a4909e6b0944a2746dc921db362de8f1d6e18a64f9bc5824b4d089e64f86f8125d8225cb2db4752b311750413823b61603810d9c9c82e94a3b6b34bc53d43ff7f332d4d2b02b1d4bb0154070eea832b2b23c0c9a5ea0b582c1a82442c7b6c075851c6d0f1d55e7c1f7a8b84916648fe180ea2f6bae0e0f90c5530c0365fe83bba0b2906be4d40eb76a53920751273ac088f5edb17678611fb2fe4ca27e2ee787efed70182ba281c3df61abefae3a46938cf332e90c15bfdfa55f1976d50914636b292a70d1a01b12ebeafb7e82efc4cc87877cfd2e9f7975f444cae3b6bc5bcda7fc172038cdfa2e3ff2bfe1c3f55c0a47aa7dfb378095bdbbc2b7a1bc2748ab2cd65459946e3bda3bfcc140f4b34a046d4b34a56d1cc3ec24ea5f444e67ae34d5b13372acb4544a09d7818ebd943c1cccd700286fbea2f7769a4d3035a2e7123da8e6ef2a1adddab209eccda6376c03c56023dd2fb24c23b674c1aaee7a9e0173094a185afe5f75935e0410acff01e3dc88ad6394511a2c57c071888e59054ec50f4e134097db6f3c15c5015bdf0e3c1b5786002e733edef7c8fb9250d7e31ae4ba16101e1deb6e58b87d4f7c29d031509396869ce83b2de4063502d52bf60c4405a9c87383870732d2752cec9c9c84e1f45229bfc6d9eefe25ccf22c5dff49c6d2874237390a63d61a3d796f38ecd9fec34e1b591e5c4e491d16e4d24bf333b09f6af658bf227a2cf13d33a2efa1e296738dab17ae617f151f6908701b9f8a0574bdefaa171122420119cfc5a234af4434f327b4e3afa33675860d90da6dc007c829cf320386ee14cceaf0538fd80ad8556efaa1e6919ee3d53b2f85fae4542fc1c3f9721351cdc08ead805c02bfc4483a0d1d452c92812030dcf5a80936139bca171b1d7be5ad8ea34c727441fdfacf065bf9c4927e96aec1bbc50220b8ba122717d6b029caa2d2f780e48b89b78ca8d842e07e6324b244e953524765897a26a5d73deb94680e0848aafa9b4cf402ec898941e49554eaed0602d1d16289a4b72f0a77cfd844f19164e8ea404da4830c61181c5f4a238a0fa3a427dd497e5d636008937b9826c5c85df6879ab55af5001d7ea4c4ac7e83775c80f3d08316c83b3fcb7e6d1dcdc27c8a350fbcf7aa866b86e515193023ab1e9f98859317acfe138a69fd71f7bc19412468dc73b3299ac926ea815b739402992f42d7ef34e0dc648ac85c937ae9ac82925ca68b12b37acabc75442c34f78d53daf08ed45037aaf5e78a1be5b0ec3608617b44c0675866c99875f707264e5aaa87175587167d731dba146b9390f85ac3d035b46f63c28a38b977d7c4219bd731282a0e25faf0ece95a243e3438e28eb6c22ea18095a2929828d35496b0c503bd5b9419cdf3e1dc81b77b9413e612cfa486c34a5b1f15e072f66e829c36df1ad3cf0eba80c415a9b1884965501e782e8fc2c5d3cd94a74df911fbfa53e9ae2f9d9eae8c4b1122abeedc16c8d6fb5019332f2cd20c367352df3420556f1f363bcb83ab001eaa9be830d4487074b3995a008a9884e73763346be0300b8354a45b363b843c1c8def716d750f476d83595a1ee8c16659cc74723488282aa31ed96cde6e0ac8662e232b7a28b864a4bb144caf05d143bbeca80ed475f0b63dce349cd41c73c96d09735bcdd1c395047c9cda5cb671cec213c0868d531e6f34cbace768657a93c06d12140f2dc500e29228b9194db0c4025005a25903415f5ac62a8d369c3a7014321686303950a0c260461ffc3916bafaee3efc915ed3ccd9c2888b1d717a314a4370402c61ec19a4b99239399250c9cb7359031cbc1ca2248594b0de8cb81ac3974fec23e4047460c05ebe2ec24830208a2eb6cf1c9e58fd772da812a68d37a57909e81383b6134efa026ce06f89402b702cdc6502339e9184a96d52b1ddc5941a6e539fc8dc5d159a2bddda5677a1b1615c6f52b517d55bc8c4be257d829fef47ed5e06953d688bb067e54f0e79c3a2c337cbea045cd6ab554a52f3ff6f9e3798d03c458202e3ac281a2eb36ee1e8b6883542f28b0eacacddf889ef3cb7ea909f106a09074b435e32d8dc3155bb75c280212e24d18a129f1a5b6d3a9f00bdb6f2cbc9012ce7e17ff1872598cef0b43058c6001a497220339e4411692cbf198fb3958b5d4d4a7d4de040cf317a3f02af297bacbff74040fa54177492145aa1b85a6c33536ea91b90e795c7290cd052ca344ab18e6f4215ba2557ebb2fcff0889870c0a8515c0a218e7216b8485f760daee6326b3ae82397382fd19f72c824fefe7ca2b026a12b8644556bef212c32f59f67ae2cbf03b05cd8295e800d065c49c8822fae459e0abdb2004ae39ba702fbf37c28caa3851a72d09a4902c7b695b1af311c71bba73189b8062b9ffb2beaa2331b2c157fac3df3836e60ef6f02cea4882af16d76115ce9a6ef8d3abe59914921a1e287c1191ce99cd115340af621c3fd0aec3a322a384bdf2206d5cd2952bba42baaa04bb19486399dd29443e19408e9f46d717558b00cb3ef64847742634d3fce58f4a683b04e323916dec4c1ee3222b623b1333dc04058a2bfb7f1a07510caa26a1cb66926314282e8d0dadbfc66d484a9352baf2730019755351687cf8d35df38d92dbff6674a32accc68506ea2ebd96d1d9fbcae11d19b613a4fb0cf3c676e45d4e72e720fc3fa20b2bd1ac56905e08d865f3f38f73575156f67f68f9cb779a02b6b98e3e470d64aea4523019867d9bd0c73d38b188db8048558ff05925df0c29d34b3497c19ec8894748a46aa064f38d4a5e19c3edf050b61962240dd6b9724ead64fee7fdf8e3481a6660c832324a98e21372d36476e641e027a15cac334612463679a8848acfdde6c895a05c915c0c3854b15a0eeecd946fbf4d7843c58f4590f59ae7d9ab0cf25ea854ce2017eba9aebdecc35a67cea0bc689d225e3ac785322c90708533e960bf7404cf5c992ba3c00da93b4d197e58343357d6eb1295dd657478e6206e51b514efb45c2c88aa82dd8ab5f772aff0d6651a365102186eb950d28fb98dbd0b78eb1150eed8af18f518b8455485d97701452d1a7423060f05b770e2be03cb18832f9b180790e21da92c48cbd63944ba0aa18c7d417cc37b729da9484e5183b06523f91140da174602b9bf26509b99c818c13450258d54db899a2a32e8014823d655324ac4d4ecb72612de2022d1f62a41c1d2f084c698f54c568e5156212f00de66338b9ca74ec0621981e6870b1e003166be7deb4d7806c35e53362b63247fe53938bcbe8e30db82fb43325b133d1dff9d367ec0f52d51f91a8f7ffdb8dc5d49be023b0cc1409c1e23b83f4b04dc4d6b6eb3c6d44c44e83fe5ccd730f2e55ccfd78d959621809c37165e18045a7640e10b0e620b78896b8ca94c19fd3b0e922dc44fe014ef9f8d6a8635e13ed1d9208819b14493f8a05f7f4a958300dcdb05737930fa8b32798bcab709363b1db5eff7fc040857ff6b60605cfa11e00f0f0be2a06b10fe5dc12c9a8e76a652771b9407b3015783491d37df490eaff2923b3b049c4873f3300f638d501a31e0b284ad42b8fc724a3ece63aa63a504401d10c334e2a7019ab643a14da5e49f5974274d99a4ca2234681279d43335e3454699b934dc94838aaa0447bb8276837510feeeede2c60bea96218c9bed03053972979b79d623026179dfe07b2d3c38fa371ae02995c474c96f6acc60aad7f46c8a4dd3aa7d29df5c5bbe9193ab95283d992eccbe89af29381c2f19a51a8b2ec2b6f96e995e7c84cf5b5ad72ae8caac7f2b30b617bb7724b2a337578be88880f99b4d1c652578750adfaae5ea2c9351e0e1cc2b7a2c91a680a13b3aef4326647b23ac8603d2b3b6bdc17ecae2a9943ca7e6cfe7aca902bff1f749bb3b3bff6cd482357f31f2ffad0b8f18c77291345b0ee126ed10d5950eb478544f3bf1608788eff52aad4dc3d74c473e70c309d3d4d39d6a5c6e9e1ade2caa88966a29b3d95457c25504967e2910bda9aa6913fd92afe3838556acb9aec334cc40a1f280c6eb2e0db6451ab76329f2b1864b32fc60a9c8dda3a3a3e2555ab957614688a99d74493f6013a8c5b5c4c5f5d8bacea846d151408cd09cbacc940020f74c809ddf9d1f9c4a7516de19c543d266a7f67a1449151d84127d0da7ba07ac2192aaec54d8c9bcf8c00d4edd61386865c0929534cdcba0b5bbcd3363aa085d752cd19c11f2b161524e9f8929d0afa3d25c7c2528a4a2fb1e748258351df836ccea976b9d0846443a46e7fd39b9e9cfaeb6c7e7283f8875198508e2e0ad73e0235792e0262aa6da44cb7eb3a2822d40cb35dd593582763b95d6c496da771681e9de32bfe0cd3727d02750574a8be44ea33d29cda5abe7dfe14424299521db2dbdaf831532ab211b33dba24055eac8dfbe53ba3b83c1b8259489ed7202fd2e0dbb4e2cb7c08c4e1caed5e03bc5613b959b2ca56511f4cb04def88fd76c45247cc24a68195e579a4ee765c1e90d55124691b3e0fd0c60c24f66a9dc5384e49d5d46ed3962e174436b3bb7f4fb48abfec5a95e69fbd67e8e8e673fca8c93b445b23c058449684f6f4c0b646335ccefa387591ff7190b1d74e95c67cac9046ed3d7026ca1017a011d355f50cc419b0e29cc6c9777026aca732158c525612e73fb59c837430a68d4942eedfd55d6bcdc6fdd7e9191dd7f122883188490033030fa1100d8a57a027f2b46b66396d38a60ac165f5d28a64c7df63c33113618472b62936d6d6f2965925206470339032d0324549964d2240455d69473e729e7bc5556c6c536b1b22cb6098effb03229541bb2b25b45a328a5b6a775fae5d4d3ea89f5cc16685ef445af5a551fab4daa0c4b4d529b702e025a65ad2aa1a65489ada294ce4927fda4f0a439c67c7d6d234f730f79da419ef68fa71df4b4733ccdf3ca2a8c91d032a5d96ddcaaaf176c5afd6c1e4260d9412d9b49885a6899ad1aea6a7c7c67afbf2e98ce3b7f5a49f4d5364c738c76b6eb37821941e82a0d69b53624a14525d09ad5f8e82b9eac600f95a029315069822dd7399252494aa23f241815231d1f949c68f39146a586b71f695482d0a58f56a9adb452f93f2e497be0108507214328e0e821c9914f091db7261d70fc10853e8eddcc3e61b02979c3c71c02d37f133aa48ca060091eac52c0c107c484950768500207271c48e1d8c91338cb710b98a1880df4d02245871713902822c48b0d48483539c9e27232c56208524c8c0f38ce3a59e2ea9aeb236d8a45c289cc73328425a5490a521529452e182bc0ed732716ad783873277bf77a6109dc7b9ecc317a5e6449198a62f536df7bb30c2edffcf3ddacdbf25a61832c304677534ef7578339e1cc0036773a47804620ce97921773bcef06aedd94167603df29f4d24c59610bec30ce9ebd12b4f966064d9ec934f9e763cc2854efe2230d4ad47b9c748130c39a13b926ff3c37bbae418e9527d860ec0237b7402f773534e00970e67a3060cd31200c0fce39730773196711640a07a86182121c8bb67f9a1fe0cab1ee1b1003c61fa1dbebf08606976d96c1654b310803c629614c982f33bbf1014488c93034bb6c1dc528ae30fe1821cc9d0923bbe8e9608633db2cf3cfd350863733b8987f3e1bb8cb358603d2588e046a61a974ee74512671650fcaec6776658f5e1620c033cbec73a7a300cddecd2dbff0b0e8bb40223199159367dfdf853119bb32f2bba82ba46de48a5dae3953b0675398a6aafa8ac70f2123454735282afa65eeccef232eeaa261330d63e17bb903e172aeaea3808c483788797966579e2e1a1978cf959b8695d9ae040d003d973dce4583665618cbefe5cecbb773d1e8a2f9a2092f2e32637ecd46d64583865d14c62c60250d4aeda7748110f3cfb55cadb004d946dd24b6e8ec511929cd16eca30c7c6cd13ae02e8a15a8d32166100158c6f98a1acc0e5e781bacefb1f0726e83fdde05deeb36075e00745e9200b4be66f79c301606cf5d18a12306a255c728678cb2e90db751204f721b95dd31c61823b63f6e62545272257f72a5efe65152fef4e4522ee54f6e732957f2a7fb24337382a6c6b1ebf0d4353aaabfeb4f5da3a3daafd7c31058e6a60ef0b6a9d1519de2a64647b50d4e8d8eea9beecc624b43c0855c08cfe11bc26f9e8394f9b37cdcda0e2b93dd14fbcd73f0264c1b46e5391ccdbedaecc76c369bcd2617b27c586bed919dcdbc4a27adf3aa7378f9588ef967cc36554dd6e22ef3b85096524a29a59581f302e7a3e3cc9d2e83b9d3b8aba08a19f48978b9f3b2e7811fb4cecc56040514c0e66b18021292b5a11ce802b4210cf63c31a68e071dc2c0c975ca48e94d0b7ce52b39aa16f9144c650f1ff22b4ee4a3721cfc061fe236780dee011772214e8377c0933c88cfe032780c0ec461f0179c036eb3d96aad954e1e3cc993b0cea4346049af590c4ee44cdfcd9ff8133ff2250e84dbdce64a2efb6ece8313b9922bf91367fa6e7ee434a7f913a7f9137fe236b7b9cd59f027fec46d4e84a5bb129615cd1bae2f3f8065a62f7dc0f463f7f377c3fdcec377c3f37a0dcbfcddb0fcef86e3bbd26d0d5acc0d348061a0b31f6de0b60883d10167e24adca8b3a4d3153b9457b06f560a960c2be310d63a33cfbce263ac155fc349611dd67476982bf81567e222f0203c0877c2498a71fc846a3f2a9e646b4ec19944fdfc88c0b138e5e7ec965d61daeb50cf5a34ab50bcb558b8b57cc41633bb938e9569ab53f1318c1d8bfa15d7cc313af8c000d3be807d9cb1d628aaf98a4a421ba66af02dace0ceb2c30c5e193c0b8ccd026b549daa5e6a55eb58cdd7550174bec34ab5039601f80bd6a9a9336b14d1e4a935aa46d17c87392e4fb2303aef813dad4304d63a57b0ccada80e055f15f7e12bf806bc8707b90a9e82a3e03cfc04dfe13a3c87e3f01b6ec381baaf7c2bf854f035f15d7d29f850f031f1597d4b7c4a7c497c5f3e24ba61ef49df09be23beaacf045f093e127c5e3e2306009b9c95f49ada0adad035a35259e57d357c3d09c7f8ba7c52dfd427f54d0d7d5338d67d521f97fc49c924a1855209e47c50df53ce77e59562ca1044af2c59acac69262172e1656eb2f25d09ddddeee44eeee44ecea5bbbbbbdbb9d068811c404429a773d73c547230825bc191866546c322cb5e05d3aed41e0b101a16a138c33e81b39576e74e02125d1ecc3b773a0149b5a4d9a425cd6c91524a2b6db7514a2975efba5ae5132c73535a6bad61ad93522a69a434b67a50a7c1b4522a6fd71aa294d2171aaa35c1327214a6d6ae5b5568e85bada1966c07cf818873ce49651f73cc9cadb4a70311638c31724b7ca9d65ab160e1380e8803e2806cb7cd3eb8118a8f4617a06001d5a07065f1858aefeea6b2f86245e5e2c59969fa93826996b9b34f90d15bb1fa48ab8283209a3bb19f3a2376795bc39b19a14f90914f90518d47942e9fa0d9dbd027c888e881a64f10d17b3e4147d6fa04cd7c82e8f5093a9a46ef1354f43e41443f5d3e414451c62748365ff1a9cb27a8c8278868bee2cf17478315aa182b48f3721c27a3f7ce9d1afa7cfd5c0f019e3966e94b98cb31fc5e65a53e3ed2aa305509e28192843ed2ac305541422d942574da6c95cad99536cd97c4f22557f225bec46d3145b8124f722caec4935cc9af70b1b1b32a357696a5086741b2b3215c930126cb0e992c30c47ca45511f22d22bc58ac666b2d655190722c5ac149e7e4388ee3720de6b2046138d76b40fed8d93ae6b7e5aae5b9041e131d97bab34b5a5c4b23a0d10a03809494b0b1a3c811a1125197147eb0b1846cc9495daab92a9125da92d173dcc75405051610532e19515af245e5e438a18093e30b99c3711cc7e57c4df413a2e7d2e0a4f848a3524585cbc7729af01c0ab679a982e74fe69c53c553596c11e5bb610c00f0f2862f78c994f3f2865984ffdd30f8df0de7ef8657bc54f1f2862db8df0de3dcef866fee77c336df0da7b8df0dfb77c31f0a1b1f70f7b12ed6fc77c334f7bbe113df0dcfdcef8665ee77c3f8e50dc7bcfc6e18e6e50dbb5edef0fd6ed86bbdbc61d6cb1bee5ede30f7f286ed657173ee0517032adbf2d0b197e963f6e6613588c103e0bd793d4bc7e2c3073fbfd7dd0657bc8ab700e76fde73dd066d52f87bf836f8bd277302c704c2d8b9c635a37b79e23648f31ecd6df0c47b35b7c19997c131efa5b80dc2bc67731b74bd77731bbcefe1dc06bdf72cb80db6de53b1e236d820f71e781bb41f6e82eeb21381e9471a140d5032d07a042a062817a82a92ba03e5a2eda71e2c2815bac6d1c10a609070afd569eaebcdd7a7aaafb5733c0da14585167680d1c20d12ac40c3c22385242a2c3ee099c9891e5c4629ae4e49bed6271bbece7cad49bed6eab0eee905d60da71ab83abb56a71c6274e8b483de499d803ca594d223ba9bddf791e664e35118517362aacad244ace749e8f9da45ce52105025341509ac67be6a76116376d391efce5dcf6fafeaba6b2af233d3ce3fd29a5cf8131f694d403c3c6b1b5b6badb57e04b6f6f5f34214243721281c5137d81f1b4a38cce0831039a28ce0c05a5b8fbcb5b60ac71752a3a33a769132deeed6ac4b74263896c10b4000f1458cb17a70730473429c1aa6f315c349c23f34998548e7741de6cc6e931d4e06e94d87b26fcda4fdc18f78115f722547e248dce6465c8b63b11f785a5c93ca48616ce7a2304d28a560f3a84f94a2f0a81a831773625762cf2f584fd761ec1ec31d0241da82630dc43563be9552562fb352cd9d9a876445338b646b36880552c19ab74ccdc31a620d5926d69050f378575367e61c37c8caee0f5b7483d8d91d6291ae115bf3aca0861ea69bb350538789c56219993c1da4814c69203ff3957d64d1d97cc99f24d3c73c6d1f9b69daa6a25cd06ab98e765d4fd945cb75ac183b56ab2574e9bd3de56d79d7156f74c518d88d20cc1018231f73ccd0908f1983b149f431cbc866e8cc4c4f393385e62b66ab2f57565f668e095485743405e9c8569b4920f2870c2281c81f55fa98535e3ee6a69afa52d58098a78b87e1221ec1a494524a39bb29add5d26a2dc7751dab455bad9eb2c5752c56abe579b7e5ddeb72c1c0c44819137325c657cac85c3973234d4d4481e273eade53fa0ca39cb9f284a491608c4a14351d4cac022f736dbe62d64088b705f716a8ee9e41cd9dde1277bab913381c91c74ed9aa0f39153cb0535f11533407dd41000e064ec8cfdcdd1c1007c40175777777dceca0c9d3b3a2f8a233d28f5a901965a4a988623d60cd7755f3704055cf0101c166cdc3b24d9d99a190a66a5e585f585754ac1a073469b7bc224136d06c48345b905a35d2dd353f6bf3e54ded533ee5533e456357468c5629a4bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0390afa84470d2d756229a01500409d316000018100a8ac301398a8469a688ee0114000a5d8e406640329387e270501818c55010c3500cc0500c82000804410cc551188c69cf460020f507b8ca253bf59991f2e03b1df20effaaa2d60d730a1f70502bc2aa3d7b2f23d09c158c9f356e963da1ad3a1008d37b6aef9fb419f32917109e4dcdea9200b04948b857114521575d4261e93163155d13b9d7c5d2db5245bf3cfcaeb7d8e49881b1ded6893b5d42a1f3fe4f40aef347f2999511ca08e080089b086d50fb79f9ce35d4e45c386e4463cc0908ced846326e1c8eaa11653132376bfa9c71a0295401696fa807b156a4907c38396e0d7921b9b153b77609ab908bb3a04156342f3576151f635380c2ba8b469d27dd0a2011aedc365b0a86d21a091539f07069e566c08e9bbd129c7e322505279de7a7cc30568a79c1578b7aff8981d2ef33595e86c2ff025fe6eb9ad09277b93e016f88130984d00846d26e06a89b0599c9e9e2f632a9a3b356592d58256eb3f886c73518e203b64acd1068b2cc2a52f59fae05d0e4316aa83bb373fa2756a84148a9bd806d7191575927c54214b4d7e3e9f2a4410c05b41a6a007c6b16857ec646ec517a101ae219851ed94fb938bb66159103eacd92a146017d8e8b5ecaa270036d68d7bbe9f14448b6e7641bf965a444e1f8a3c29ba9dbcb13eb5bfa8ef0019c86c15bdeb33140094e06a62bc88b5fbe39cda26d6dae799e20e80697561fb43a511eef5418cbd5e021fcd229e91efa6bb7e6be905d68b6d4e1a5d80b240fac37e549e1fc5cc862aaa9a5eb8b1f6be7e09c6ff3d25be3b42b514a3393857f98c5d3d731b17ebd25bf5fd32d0f06d9cdbf3a57413786f4eea5060388a993faaa6a75ddd0a603c7f52e6de1599e72d59a8fc8322de34be2fc5faf0d36fbd1a2dd559b1bfa72ae39d1777a2b00cb0dd3204b7c2f18b501453b4ade92e6ddc2616a74991407efa201f974b42d5370045a645bf91518baa8e95f66e435d690e2a1a9dd4961bf09f6caa2c2bd08c11f0360e0401c998aa44e3d08922ceed09f9e3e2f881f8cf33b5ae36b0f2dc0d92bdc8bef705aff6c7b0491bd75ab1bdabe5b69ccccafaaea8aec1603335b1b38d717c8800f1b41aa94be4940a4980da4c6e11be1381655122232ed3c271eb580b2fd002da468fdf20c821aa67ec8b5a8e343d4cd54bd6406e28d77c9950872349b8ce39dbce167e544d68addf4d10c9cec2032551cfc2e26ba9e811651ccc09729fe9a745cf0640b3d91a205c719a316528c92ff9b834230575c4383116e2c7ecd74f2c365c0f67bff74d1c197836e7f802ea0e1f9c3e987ccc25a2d2515745aaafc00137f0105ac04231333f63d3ab502066d8a6ac4e8c9628e4aa4452829d66b064ac66468fb2d7b8a097700d55b691a729082372a17ee48346c6f32cada04dca5b2525530ec2ee41198cd23b592017c79072d01c48521bc31e26f2b589e2e1b0f0a4c69a528e7c3507881d1ea39337e8a6a442c4fc20feba9e5dabe942bc1bd8c167c6cf8bcc11045abf768bc045369f0845dbcc8e81f92277cb968ee1270c9216824fef9c6dd0472c25ffff6b2c064f811dcdedd2218fb1294377535bffee310171604522c0d984c0a5022a749cb43efa9565610c6474c0a16a49bd9834b8749149375a29a770e2c95012bc1b08f0b41ac2ad958d272ab31314853eb01b99842586092a7a2e8dba383159cbc8b8904140ab8f56b08e81e2011fea8c4f6048e5122bad8baa545e185af42492d6661180b830543c387754ecaa3ef62719ff5fc4181ed0b73b4ef9faa4442a2ef8c8a9415793f5c036527e88297fadce25776b3ce240ba50c0c0a3e60730815ac0a03d85ccb857b848545b4e51370b49fd28c36ef2e0b57a22a707b0ebb1b9433d29342bc6749d9d0e5cca870591c8c5bbfcec5d4f63a35546083cbefe2d9b58b619a4deefdb43daed7ffa0a667d7d8496aff950f2d8346730bc22371aa6036b2c525ffda4a404c0824ed8499c483a9397720e8d357b668d9ac684235407bf67e58f3002bf60f6cdac0c39db1f32604ed6f260a58327f7e853b1a8786a38fdd015fe026167a1c0ddf74d8fa27c504e5f0fc68d014614d956909c798ebaff9989ec9295544949a21f1f2702e0a315f7628ad839f916c6b0ee05c87aa04cfd5e11b9806a4b072ffcc4f209e9d81ed3818bb93dde2f79002e06967e58d87233042e763a833b6b89de42311fc13b8a7a92e084a2a9b7082a58953eb164983d7eccfd337761b8bad4fc42bec48d250c24e18568cf87d9b0a173c80a236e800f4811385c8e45a00907b1ad5ad5af59ee17ce6351eb95cd62cda1fae887270cef7bbb11ae0f05c2572a0d7fabddb8e903b68a2565f7535640ac78c09d0d9e588cd97603f649512b59b493311af11eda38678450fd871a146b53a13096cba6ad4c84a1fcf8e5201e05804a684f0d102c6d1ed73ab24982e4f09cb03df46907214252414d833b5dcc77b4894d38a41212130c15d0f49c43f6335e4df2ea41a4767b3d9d669a84920737c45be4b312cdd9015e1a1917a57b2b8c0a669a31f593fe2673f4518979d09550e1f6ceb9c9c20e2fb3fe63bbc26ad2716184f676b7f03342934d2ab45ddf28abe1d86e2af5db92db19b0a2c477865424bcb9a11ea486d2afcfdc6f00f93e0fcf499f3e2044c33620855c46442c7021f7d33b21a8949fabba978f78b44a61f334cbdf0a9e79bb868f593110edd33ffaa4981ac13b5508b396590574e12f90bc25d48ccd9dd53ee092045967016c406e618ddccd7d8a4fe8c382c834ce727b2d52a862b4fcb3d98f880c6f205104a7e029c2e40c65d0767b6899202acf725a4797102375795ac5cf5cd86362b386b1ce9b897ff9002abc9d5707a611a56f351e6798617a06bd6418f1e0e88ca17f7c0f4a98b55ab0ddf1c281aea92afe8aefefdfbc216c5ba8eaec16928017cde199e76773bfa251e922393107d2295fda355a202c628095826ca8e81c406df0bc4f2ce5c05b3d43cfe336403d527204e1b534449371df27bca9f2cb3d8e466c01f2360e0fe809fd8081f8411da87bbc01d12ea509dcede385352638c26c8619728f535a6c49a4d8efd79c2ac9a210ab27d159c9fac66a228b70f4fe99deec993e2ec6bb10adc6c437aca53b25da23a4dc2df79c0c176ddc2fa1a54bd5f54ad5b2dc32faa6ebdb94ba8b308d2660dee1491763f30692c9e5dfb47cf0720e78827745568f8d63b6caf8f14be1ac5b23589f7e846c72ae8d5b379ec16535d228f8d1c0c324c93ee2d63d3d1016561342eeb73ea0eb0116a8ce6f430e6fd346afc4ef2517f18df6053d674b746556d80b35f245ef3adf1d96cc23e331a49b5499266f34c9a4d499e4c9b38316902d99d4dc0f53be79b4ff3e96e92edfcfd456a2e5f228649dd469a8fe720f9a678225f315a084064b4d283085195df4413010efe49cc3bf589340a9e90a43c451af96537db15d645954e45502a49182e158ffc1c1e99249b6213f99f4ca7ef6d0d329374fc0d31752a23d21c3c87e42bc513f94a36c294ac62ae666c16b629b87c8918367513693e198ee97b06f9c89bc743d24ce1477ea02d66e79889b00333f0d29302f6823fb886f4ac41ce319120a8f9240aa1c7afbe7dce34c3effbece272e43c53e154bccbc2fb8b257875c992386c5cb9aa4d07b6a0afbb66d6517d1e59b1999f81801d09876391d06eea13892ee956ce277bdfdcf055ab4874d6cc37ecf5e716508afd7f3142ad4e6c320eb529129d589d97a10d875bce005925985d62e409da2fdd3e48fa501163328174dd81000c60ba3aae4fa9580bed46e525a19ca3d98d9a9b7a4143fe818a5bc67da654f7f57c2725fd5caa821385131204c543765292c170ad447ab17fc9fe5e5300ab31647c2307c174decba5a9cedddfe7abef2a74a7e384868eb63a7e5bef256a9fef33e329484a6366bd6031610e9c4ae5f9bee2ff5515ca89880931c5234fb7c6f48727d973f723c2d2bbb2c9d93a308eedd15a1ecff1b561c257ccd90dfe5310ba7ec013113516f8e7a406c12cb07f65f7a2fcdb91abb22c851261aef89bff5e7b2f5b104b2590622a5a325f7a7063208906b79f8243a1fccd2ede61eb4973b60d7d0ba7508b2e50905540013dfaed0b7ec5619feeb3e152278532f3192ce2e807e2abf1ee807896601dc40fbae6bedfa99531acd57cf0e0b420aa61ff72347d2239b84e267117eab906cf2e4b25d71ce3a7c778946ed7497c286346143f5ce6fd190d36255e962d6d31dab5d7f160d828b498208ab18706385ddfd4404b1488aa3a229f2cbaefe36a8a69463112a19a0c38a3513ffbd87bc78244f065587db7ce699fc980f3e1a7c5360929b7a9e20200f1c46922bef31f72acffb2d28ac85f6fd8f0bf98b80742b6eb9c187bf483a1614a2e98c0ce87f50108e3122b890a3c88be1a9c077e90a4d47dd30c9f0fb13e44ca71de08911da9e65e7205fbe82a6c77554c0ad986742269b30a038d45d052bd96c36256f82a584a15e3952bd654d329f952430d30ea61a626e7c72bc3add3eef554e29da1444278478086bff28ae2c9eec153f2a6fc529672ac945947582813f4be2614dab077bc8b5fd2743c799d54e41fb629f327aa81db32707d3837f1df4597d3c2f158ce5ab9c2fb426115d4250aee50fedcf2cd143cce93851769940f7cf1281fca244e10e1de71f51fbf254c987143295ff387783c5df67061f3c017bd06e18efd8dfd4dfb6163ecda20260a993fe7f3b8e5fdd16a106fd8773b63d7c62791c3fc60cfe296f647ab4318ed8c3d1d78459bd62418a843bcb11bed8c3d1d52c58856e88c6a10481b6347474bf1a25ad5647a67ecdb102056734228238b4212da7a13c731e514ff50bd76c0fd59203ac51ffb864ba576358edd1b37bcafae8d38bab963b6441a852f0d2c5b554de9ecc3eb715877850e2b4af5d855c7749e39b247867a04290ca5844a2ce0012a958b1bfbea3d481b78ab8131545cf6b3286ef54e38e97e731a6bff8c25196d3860cb84bba330b2772a4ee5f923681f32728351405a05ad58afeb76ef2ac0f88eb59b9e6584bacebc369c717a5516bea3ae05645e4617a90b1a7da2c60103c2aa444d8737db04d2b627785ef56713527ab69a0e6e03308e6ee60ce98a6940ea9fbd081c2b31028f56680bb9840e1aa4853f2d823771ef709b67e88c70ff68fee9c24d334ba114e4da2490ddf2e6c872b05c4d10e4e2b5b9c1af0e8c74876a28f4092ca1e2173113da254ce39b772c4e2151adb9dac4152c149118857261b49f29f229bce7af09d52393a5d112ea15cb8b917b76785b0cbcc101d4ee99ec11b4a43d06551703f0dd5a07af0fb318578559cf971a84ed9b5044241792125aa3e8d701f6ca1bb5291cf1ce94b9001258cf6911a0c146770618c277001c9a1a57bf39003ee90295456cb529a12669a0955423517cf80ef365257c5e31d565ac32b49a4c304fa6ab7e7da5f0f097d1fae29502b28c52db12f70d2f09c70a93c28e1c984dd510bc4ecb8c91ecdd4a293450fd6f879c4264aff498ab1137c9404d997b3d1206d9e0b329ea73b1d4908a424747e670141a4c5188c93b88a34a2bd1871fdae5ec29c418852fe45a2d49473f3c3e81c7f4a3b9e985a8a351ae49f5dabeec88107877dc3f0f0ad31035c31349606f54de529e4c8c7e8d62b18cf20adeab30b9b59436dec298a0ad816e079fafd29b4d508f0ff3783832d1c8855262236215ceb444a16e3c272caec7d2ca81c54374df7315f936d137582e2729e1e087969caea4c13d8d39666987cf34cfb12f1debc63eb6d496f6d6d5c7bec6093bdd11982c817d07483a48dfaa8fcd7b1573440be25c57debc94b994e1216e8b6e6d0d2af4e3cd6a231e70e8610a917bae51869cac7dcecb5f00a7a17cc7208ef2575a711387ea98240d66d70fd9af7b02041c6924a075446ca8c0b22342d410d5213cc85a26099b801ddc1d93c29cc0b973552d8a9c4f58f18e6468dda8fdcaffaa4fc756aed46297c2426e6fe3b58034abc2a1370df0568daf5618d531400e5ea86d1c1c25d386c8885d7e336b858f1043326a4055e1b00acf81b845e6693ff10e27da6b2afe8a52badd4e515ffcaf8f1e04c970ee6faafa834e1f39c72fdaaf17957db258125bc24f1627d2210b7bfe128e35c2790fb0bdcfd48ac7b505cbe6fd5dc9460fbec13168966b2738b62e77013d28d32f2a96c074e45ca11658d2ecfbb7bd5985c360432de828d820c19ff747b8595ee91fbb903e186805054def485ac4289adac9de65cddd04d2ee87ab455150312244be8725fb4015bc1bfbbd892d547dfc3df68d58661d22c51c4651bebbc5b782dc07096208510a20ea8a70bd26ad2f58446742567ea768a746c62d5c40631d9e5b55220738700fd92ca17ed29b80382a9041a12652e3cb9ed02114185655bc65b99f46bdd4dacefd00354f37a1651c48e4ddf4becb071374758af4d428a5fb1d7ae5a69f4809b962a1705bc6624d4eee8dd82933dbe986dc028dc165a0348b225fa371a6adb53a6e1ee61675058db207434eb92d591c934c54ce8d96d54f0d44eb6179ac4098751666b42c79fe2f4acd7fb3f0007f6cabed12bd491e49a9d0b9833febd64e408391c658ee2207dba8f804b8fbead2e989108e7f7b3e2468c7aff5d778729a2df5df409b23cca6afcdc5165960f1b24a494142bcf07fd6f920347d94224a2bb45b8c76724be3be61f67e6c5cc9503fc5d7facfb433d40ac7891c28a1ee986a13af644fad9942bb699a336b9abff1549fa8dc6720837820bd9273649cd79ac164ebe44cf008b33d26f9e47c41303d2612679a6b8d15f10a7ab57293133af76112d3a2cf0f2759679f9d3330073035b3bb6d52746d8a38fbf0e7b27c52b87c3c8d00f92914e59b9324f862b07557e8c15fc6c456ee8a684e1875bdd21b6cc58370683554bd4e85a5c59a1e910a004f0aa9f4b0366831e12050f688f3dcfd59335aeba21bde0c90d09afa3f4f04acd7042409698ddc7a374f97930605d5cd00dfb382777e400d05a3165c11a6e5bfd0305be9a49053b07e66a9fdc4ec681855bc278f9b579f000fd8e6d49cde8abf5491682f8ff23b5fe69abdda1633caa21c5774d61f8375fa43cd6c763fc22c1cd7d7c60cf449e0eb996157ca15deb57154e57aacbc8adcdf2ccf10050924d9aac73dc613817641fdaca064fa0433d9eddc0a3c796d61bab8c025960be193be8dd2b03aa74f4d4ab7a1f9bf18f4bf60a0229fc13c36f8c79f3405953da2a87508053ac5047448d0ceb627bcb604d1d2ee52fcf74ff00f3e0349ca515c3425c033f9301edca34ea71d91581076f337b1c29f22b1c0470b116ef639c8c502b3ec42d15dc0893dbda633286fa09ea80fa0ffc9b007e00b4400a0699db57786118444a6e8e64243bc5ccc43ad63b0a28dbf990f3f73cf489a19aa81772a0fcd3a0dc38366073a9a46f13c128f9b9e9134065805e76981263405e37e9af34fcbc292a6bb6d44485bbcbd0e1d06980837664e2f1c05cc31273c0defec73fec5a1a4043bf4e6a7843d4d7b9da7a3edac38918fe0e357bc8793abbdad3c038a3bb599e9bb95738092db3d5bac05dbc50f0540d6038331e471f156a407aa0c08a528d7dc09f1b7cacc0c0c1ae463c38d748dc5f5e131b1d470254faffaa319b216cea4a9826951fcf5e1053571684dd16bceafca24336b404ef18e33abe65e425650a99098df89a8c28a108937240cc403f2f51f5170a761a91501b22bd99ea8501ff6ef55f741325587182490981f5d806c04d75116123c44d728f8ab1f1c02cf85991df84745691f67943f2e96bd725d99b8478114cded3cc9ea0ca6510338903ada8cf9d00a0176a63c39aedcc0d15d395714f296824b0b8b49621b522e30959750f312d57eed0a3298e090cb9196b60a4f45cf85349205403c7ea2e08626e69ec5f606c8a215b71f7e672e9a09901139c5b5c9dbe2d36d5a76919a84f8a35e88b4d4ba98c10b36580013208bae1d4b34ce2e037ce790dd108371e03c843fa9cc8ef890dd485e741cc6d61746bad8e8229344c185e7a2309a55c3501e445e00ad02b46be717c27515cc93c5e434c115de8a70f1621014af9df1b9ff13052424579a8f24f84c77a51c13b20d4b4fe1029f4173ee7b6477534c9d02c43dd3580603407e509e53f2cc31a127779f76b4cca110200b4342dc01aafa558046ecd034eec23c3b473cabd6ff00b5a069d1fcf4206439450969a2c256dbc3bc03fb6e7cfcfaf589889bc32d6c4eef4ad30eb6cfa68ba517adcf9c029df059e9cca8a07d64443f927995f2f60fe891ad2bf40c03fb3683c830fd2be9b2bb4e62a41e70cd11ac4c796df83a4b34649e4e4d8b8042cd498f16f71a7664a9d5a9eba76a1132237f1ef33395ed5be2407f0cef05e9c1bf2142ec60d7dff648c0bd39b5098061ebde9e03b8f79d8e47c435275919ac49c28934977e0c61f8a35152ffe94b14c51c1b3e4612fd9e385ec79bba7d86442ae52c1ba91f8d8ea247620c94acd163d31a7a63639f225e53868dba393c55ece92ea4a0f0480210513f0bec9245548ab5a9697a080b4faff34ecf59aee6c8fc635814e93fc01f0dbfb2a310adfccb5dd26cc24cb4312bbd18f907d4454c6ddaea8ada707e44137245bc1b3f562718c5f4c9884bebeeed9ee8418aa295023ceba943c147a62937b5d0cc71647813e804d6c3c65f1f23b4b1205ca29785839314da8b09676194b4625c2f45dbe3180693a42189a9e3440e4ef561f6703f15a592eff21b9a0ec4261f80e6a07141d03ff21b2b34ca0f41baf813b3fffe260df64bd946b2ff2bf34ee292e0c5487ecbcb2f2fc6188007545ea849517c879f5a19e21dca90e124122e1704c484425447310b5dfa708c99bbd8cb9f3913909bf9e97109c20ede2a7e184f2414c8c996e4ed08113315737e10748764a393278b6ddf749aff1793463ac9b407e5fcc71623658138cb0c76b1e7518a8a9e010cfde8e3980de8af3928b580cec6f276eea84042b0e32e8080c9bc613c2d44ffefc2d55b51a2080678eb5b80b30648cafc8bc9f8e58ae508ff5c5b177caa43842bb1a4ae0fd8eac53e25d63ac254043341b2a756de9d3ea8897f35e794fa7234a0806b9202db8ab21eb08241996185a9fce674a8d7a089cb0286789cc3d2c98f5a729362642e982c5d7cb460c31fc9b860aab254b82f79960ad5af6e333ed7f22de088b68ff347612ce17d3cb4b4f7d1120ff10bdb0445431d76ed658be322d62ca7df61502cb438dc888b6bc579c165f85f80332b478dc26e1d8f2b5fc2e0cac4299579d798833ad979152a463bf866e59a10e7a8829beadb6aaabe8e80ea0326ea948b1b832aa8a590b66ef0fa830589cab8533e571e8444208e2f84ce7ec8107a24b740ffa06d65ba984ecd5917ad2dcc30f716f908d77d592c3e5f8dfe5ba1df55612d9dd2ec962c885e9da5673f4eab648b16aec63e0e232e7b9cbd0adcf4f84455651f077ea8dfb786841cbfdbedd89905be04960926712a33655a4f5d4c5f05ce0870e0f5e37ea9f0f1f47b3731bb52e2ca086ef41c79721b7c1b8ac3728bec521c42ce74226905cfc431671bf9077792d7822e70fabe2aeb991f803777721b16a445039a9e1374dbb167790db4b094a8f810b5c08bb0f1a45949835cd738939c4fdd8c970a02469c4c152a2e25e6d8115c9dcbae1b3d8364641c56fe5bd08d661fef538d4b520108a5ad5fc33018430800fd23bc8f51788816f57e11f070e51b765e063af36f0b175da22a9b3254444dffae59a6c77aca07ab8c6b94fd5ce3e2f9b675d1763702910ce0e45d137cd95e0245da6696fb63b44c295e6507682b25f63051fa4ff573de2711254b0d70b3911ed964cb6506c00701b2b606bc00c16e02113d710bfdf5a57722d5b4b7d7ffa08d6f02f6bac4b105ecde412295213264f5f9c5d38660acdd2dea8d3b0229a03f0313a812ac5205f73191c8937c75be610fb82e19ae3ac24125d320913422a59f8656a1115200577dbfb888eb152184e9161e892d9f2b297ed09b92216acc3daee5aa6c1d3ed6df49d7b42b8b7c4caeaa544110be1b505ed94f3b3c3e025f4763a29d0890a3471432ac2fed4a0a48034a3cdf1b410ae84b68d3f27332583e2c6f9be1f0ee019e1df4ff6544e427278e740dfaaea2cc5de67c5677e3aca52aae2185d7737db83d9b27b83116f6d121cd2c1214df89eee4af62bb04471881d50b089214006829d5ede30af3089bc19ba608fe532933de5d0bb5051c09003096ac4ead50d83fdc8c0f2a288786b985f6200f63ba990e8012a1b98ed36b4206c89e02c06db381f164d5d97933889037004cb737b2435a4a25dcd28fa7ca960031ac75f9ea43b715a77dd79581d760ecb70ccb71bb50386048477426e02cb8cb7fe50c10177e6297556016ca5bee5a6f28d7e07df4d8a1387f70e9739eb55edf3d7b9b78dc5d8e730901a3991805ccd4c233767518cbe903a9e04937fedbebb90e662b53d8e1bc52106927cb579105d546593932080a86bcff30d0c9ff54e3e3945b025788e2ed033d1afb6ffbabf1919ab2f926bb0164fcfbce4170e826cd7b5ae3e0b8bfa4b9729b9494b6e4c4cdaf3a2380ff6bbc2eb0665eb1b9af1d3136127ac6e3356c9f50291d23b2e3c483048034b14e054fd366711b32eb41b08144722994e3538f5f0e4b80d379c480c953540a6e026763621057609adb936dbdced470a32c86389867291d2cb3458cec2c10369464a5ab37a302d83125a2b20ec6a69114bc6534aa9b0faee7b1265e978e24db5b087149d09904cc87232fb647251eee591ac3763d51813b32f2ffd66fe96150a85a7de1153b35e8d8bef5db9a755fd9c1e5fbc70131941bab9947aebd566b928d4fb76547d85a6edd6abcce9471bef5d372a0dfa15e675ec501c11a7cc324704303048532e0918341ec9f0ea468288950ddf5f72f6bfd61906094fe16dc26bca160a71f3cf280bc64cc1e6b73cec7b9dc45f35274e0d6c9e07b052555ebadb64ce72377a8cdc76c7bc68d9a16dcac2543daa5b2cd778257b5be96c1c53aae017aefbb5cf885f23e28f5ffe50657df05ae92ef8d23fc6a60390480aaced0364f33dbc5c490de25c9806727864430603085cd23ab284d3f2950f74f22db28677270b76891a86d5900ef2a508cc7f9da42071c309ca06b81b5360f388720358a8fdde13c01993842fa077e73ec359afc279d4e884c6d98744d12980e7e1e3a5d32f9f7130a3b92e85481b59af00bacf4396201518b75b3f9b5a6539b7ebe0e134a751a0af61fb414514095c834e2a28e9673df047f24d15e4ddfcce4979534c660949e2911f3dee8ba35e5e8d5ee65b156c6793dbae8be0715c713061623bb3e30721badb33f41d0b222061a9ad7c2e84f6383ffacd03abddbe4d0a4b3c6062178cfed4d9155f711077090b7db6d64d01b564bdffb121e2095e00d0122f8e417c841b70c1a2b2de68d1d67f15961790f7e87b58c3f9e3030230c3acfb4a5b16530b37ccf518fc447f3fbc8b0f77652fc29db370d257046aae3f92085024408cc4489677d4a63cf60fa49c72e72c6005349cd00cd776c76cf408811725755ef4c4a8c9906d0d551e01104da3e386479b956af54bba73058771dce0be514db7985b769701bf1cb618785fe3cb503ed35061747280019ca7d0efe6e18ffd29bd8d77730f49dbaee65fe348d940f0884abb5e6a44f0f891a7950d8a8a68e17174538e6bf34715e0676acd7ad96891456485096a2f6cde4a61199b8f859bde5f59b8fa346353cdd922a4978b7857d5277642d9db03c18ab1e1367f51597efc0e543699ca33ce4dca26945a3dd441e5300f9b9a240d8e70267f0dc698517a470d22e102b36db976ba8cb6cbbc352a4b6d0206254b1dfb862c82808c71a0112f0c12308cb47d9e026b07e9185b8fe9a6b5d8a844ce73960f0c49a69d0bc6869ed78e020bc76d61ba20768252a674ec496c5f2708505356e6bc09eaa57102c4fd91bfb2589319d33c3e466f4c26100ab8798b90c3515285ee73d7977f4652c803846384eccfe42259b80dc7b2015bdfda76eab07f5cbbfa40710712c97cec518859f716f813716e3a014a3ace698d158b961a98f1e096a66e8576f025edc7367f4f555d309de61a128499f3e40494ca2ef05032f7d4a56365a2ee8c52dec15377ce929035449c3e9d41e2e140304dd41fcd22362727654f8ba5558e68365a923e06ecb1d2e3220e58eb133dcde8d03cf8a177dbca9c9f14d2ff91ce46573ce7f9bb8f09b9a5204ceab3a7986e93f1d0698c82156adb5f33c701eb09def8582d871276deae1148b1a3d5ab30e3832cf1fe3b40e2d1b2ae697088d05a75b796b28c17f731ead6a9a3ba535b8918de834f9d67893717374e45f2ed4fac3ae9c814f8d71b30799ae9cce0e9f27c34a155ca04719577127946c07639c9cf478ee27e4cd8612f54f3182e3858582c65a064379f4e4b8ef66063502e0b8fd57d9ebadac39818298289513741fd9a81b634b11d88a1e3de080376d6dbe97dc970e0bed5ecab0768ad1eca430ec4eb27c04dc383470a3e06b6aa612f9837c33cd2b146b1c72d1355d30a978e6408d8657fa70166336be973b4d4c5c25deb83257b467db5f9950796003566218130165704b920656273f0ddec0e6f67c1409d1a46a2ccc42d3262caddf3f99da2cde3f1d2e453d4129092c99663f23ebc0086185cf157c34835eea643904c917cc97b9926a2b7330971c36a4e26e98c800a94f6e7e743c20363d8bf886b2d76c3c801cc2781f2cebb598f808dacae4f194ecab02b894ba5b9ace7769a1f2ef4c70f3b6affccb9e0ab52372819bb26a20a958c716f4ce3f2277d3c4324924681cd41ad2c8259af393a11ef89318bd623231f5be90a45381032683c96edc2f6affa008c63fb304443a9cfefc35f7deeda87434e1fb8a4f18721a5be0a2f380f9e64ca5fa6b7411cdf64961f192291972f7bdcb4d57af2da5aef0cc9f926b9ddac64b64d0b4dde5c17f69a22c8721603a4180bac0ae88825e7b60c9f106560f0e565707d2f9884e3d488598715a1453bfa605dc2ca126dc2d5ef9166c9d96e791e03d9ad7b1070c6accc201fca1cadf408928a59f261c99bee0967769aec8e3c01f794293922f3438f155f79d112b7645e9e3603dd9364f2966eb9dc340cc8f8ffdd98608d9e11657b33a177507b41770693bcf4e27408d4a846e6e3204d88639340bd5271cb8fc5e97d396e18936f09bb80801a8feb071f5d3ad42f435de137a8b727ecac2d4fcc838581921000c4b4ab9ef11c98042448a6caac4b0bdb29c74a8196e3b1942f4cd1f6a10b5e620553f31edad520166154c9c9357c1042d04bc4e88f96b435d5d40c968fe1728f24653bb6f5a22f833278d5708c97e06d7a03451fb419c878570e8aa127efab03cfc0f8262d13a49e400107c2361372c06d19228208053c28b35b46631520aad6efc2e8c6a6482e8380b689ddd0f11208c089875876c5f1d30703a8df4e7e159b76d621a5d761a877aaee3f8bd20de6cc154df344f593cc3b96601897af7eaf802b9e2b009bceb8a638ee20a3aa14f5936da6863dfa4dd0c5f574f1185c5e4d7c0787af248a9dc442ec90b03b9b6a3021503f89cb62fdff9c3ea284ddfb6cdc7037b47896e772e51660db8a8f0936a8fe55d8b6858518c75759f965e1c24355b06a90bf6dc1297f005a4794b7e9b58814afd7f3a52f7794410e4e3e59c3165b9ba9bc134ff5fd5aa911e0154a028b8becad0e0fa6cfe49c58588ebcacf174eaf7fd11b6b01cc85b97830ccdca7f563ade93ccb06e30434fcd65cc8f6bd6d5ffe4d06248daa230c879b1ec96b07dc64dffbbdf2f5a105f089b9b76191c1686deb47d8003b5ecdbf068c61f0a5ef4b396b4c31bc10f7ce611394aaa4d5579b983411a0b1f2532a121bd1a782d93a3aa7aec8c053d6ef23b9850b6c0010c229e203249693e531ca9aab348c444c7b03fa639a7c5c304cc29a367776bc8e4e7f926353bf8a58b4f18a24c8bab689d62eb1e12e688f56a577953227fa722102f52b40d4178c8ce0a60a70430380717a0e6a0b0b6a24d9da52ad1daecea8f5651b4958c7ee9692dd7ead25228944767777efa8072308bd076f03b2c04e3ff71bf4e66c255b2a2ca5cbe33b6a0fb3ac66d624589c309e1c88501d087fc66018a717a154df33297a9f9b50a7d7561b6472f3353620acccee707e7e8df2bcf8f9dbe6e70cd79c39c333fc38e06cd2f0bf0071ecddac46795a3fdf36a33c379dab67cef0af4019b3831be00738220cfc37609e341cf1cfa6cec69a568b7e7960cf9744ddc6a61b64edf6736cc29709376771ca2b30d83df72ab0e2f7f40e5d5df31d35cc3973ef67116bad95c1841d9d398b80cd968708d9f29b6652a5b18ad24e8f4a4d4c9a6a65d23c86f943fcb74b1ea321abe6c2f9435df3180deff56b1ed6aedff37ea8ebfc7d9f3fce796006afd67f755cca60e63f735665d8697bec5bfcbc8cd87a8c31f64418b165addd1fc866c821eb76c6f7f24bc9966f755c3a27ec77515a6754629524b14acbfb79196df974a7d39aa322102d108a3064b8a3236d66a2a4a64080a22363c0caec57931dcab8af39f9edd3ff8e775f7bfcb88ee7f07ae632af2da95d763079e412a5713c32fecc18f76b5ec68ca139eea334d2b90f797afc182f9047b102ee4305c8fa4c67ef709ce538ee6156c4ef9b1c2725c77196cbfc0549853b4a9ac2658ee33809c65eab01616576288395d9f85f403637ade67ea2e81afddcc7832ab7208e399b3bde563557ef3c304784315f1271dcbf9c70dcdfe01e07f71cc7bd3c1284fbf7f6b9efb9e738e735f5af760dd66ec1c8715c95ce3dc7711f651c273b2e5e4fb420840843fe5cedf43d63d4f06c950b947d665be61d87b26d5dd3d9fdb43167f2b75ccd9ed23a5eebaaef4f7958ffac0fdb5e4ff4539cbfa63ded793c87ad774fbf0d584a8e68a1775f3fa0ab8957f82a8964339e1f4abb31a2ed7d3067697618bdf4fb302b76288fe4119ef7a5bcb1436152ff06e591ddeb042bb37f288f0c3269f75f51eedff800874cba47484aa6bc2f7950ed534e2e8b304058993dff25eb3cd9e7be8e6df2449ffb59d6afebfea63cf5e592fc9844a437c6f824524a29bd31a755758c3fdffeac56cb7f27c28844d65a6b638cb1f542daf2279698a39cf32da5128d8eb2fa05ca171f5ea6bc58f9f2854ad7d4b34aea118a34fbe5fc85cea7d19ce18751308d6ed0ec92e3a75f06b277adc75ff3f76dc07bbf5533e5b1b1618171d2f69db4200cd68e3f0d80e4d78e3990c7956ce738d07c3c39907cdbed10e5fc564a3d0b42af397a8d5f41c0b25fce0157af1c487ee55ea84871e4cabf6110febe1a4b464b3c36b054ea524a9755cea21964c834aadf0c5c3487c80b62c71f8de612beb328f64cd0c6d3cbc36c7bc14029a5197f05b91d222ba85fad0eda2d55cd0da5681acda239c4da691483eebe8fd26772e0e432982037b9f4b40ae472e70f6c4d5a47411271cee80c4ee6eaf5e60ed3baaf61f6642ee95d7046e63187f4ac63a651d32b060c7a64fcb8e6901e76f87d7e955b82af1568ce7bbb5c5b4841f4548b54a36a44eb5111972fa3fd0823e278b1b0e5db488990f7de1b6d8d4f5f525e4a2f29d18adaeca584658441eb4b69cb396ba5dec41fc85aa87f2b953a7b389e05fccda8ff52aa31845e5f44826aaf14df0f31c63888569dc34be9a5e445d99c2a74916292c288290a2941d482f9de3ca7118f34482d9a438ca691085e435e47af21b358639dbe945a29c849abbdab5711b9d4abcd5cca6fa9b0e5ab68478e552109fdb5c3963688d5ecc9d143e89487f6e563c7e80f752a270673362f97443abd82a1cb35ebccf9f3e4717dcc39d3092bb349a03cab707b5fb919cac35f71d553fea0b6c77534c329954424bc5cd8929b9c53bdc5255b1395e1630c72f5bf331d3b09b8b5d37572c42a796651adbd597bda9bb3cbbb8eb33382d53ed6c1a3c75867ad32da9c39fb1aed0e7a04db81eee37f8fd39af74cadb5d661ade739e3ecf70f1cc1f6ef3a6330fc9cef370a3bd4e9f3b9d79931b22a87adaf3e777bcef44cbf0af4e64cff065d93a6b5ce7f79e45e8da2a5b409f51a42af41bdd63a076ca18dda7a9575a93fced791edfdfcfcadba5781e1acd7c29f4e7be0e639641b084db9428aa42ef6ea34ef2b873d5f6733b2e855c3ce1feeeee714879f0377548d65acb2010f22404260a65bfd9303d14cb71ca86932f1eae12a80099c6471ed1ebd97d2bdf7de9792112fa29afd9963c594298a27fddecb5b957b74f2288c8c6096c50a2550a0c2855b92272e364f57d0ac10110c910f04118208094e90624b511726845942082641723e90040c55ba10624b0c4f3bd43c15110b8202524592b8e0812c53483c05a979a2e166462931820918a433533d6080620a284e8003c4007a648282887ed323131477c995c58ecf138148a3ef8a5576ce79ceb6dd58ec189563c6903fad955e5482c20885828615143eb84471919af2053ba1b938a1b984a0b37a64e2a2c4c3cb2d3fcacff25d468c60a588252728f3c448e9070a4840022f508c6c416483069a7696d1f3738317856e21f30a6285eb29b55c84744ab91cf9a194d28f4f9d7edd12a6531a6b59e6c4137d7e96e94029420b1457a641f68a6b9660e6beead09d6d65b573dc63eec3ca3dc727e696d75a02a5245b8c000b2b887431840f0058220922906802298d100c34b00296252845741142a4871e4660610b294a9e54a842852d4d5ab4501ceff65a1968818c0f4b763862294d1155dcd004a587231ad258292f13300f08685202229894f041cc11506c69b2a5080a51b694408b132d4fb420913d66013f3f8828ede6e3e6e3886e7bbcf9a8d24317131810181026e22d8c567abc667860312609245ab238b1b2c3154f1c397d408ba43238e4255e5ae4b87abcf548eab9c75b8f233fe6650204932452383123e6871f646c53768215f528cae1f6e8615743802ce5b853b3757163162a7aa082840a6374306adaf245892b4bfcf0e4c2094ff8042a5ed82b37a213bca5ba42d3ce39ae27818a3b210817104d4ef66eaa27a450738291dd94a633228b57066311776a362eaaada886c0082ecb9ec25baa45385eebf2b1b9f47007d663e31e99d230a133db4c3d9c0dc971fd951264a71a826a4491945e81e09a8a7888b294afa475da6a3b6972f8214b214d92156429d434407a28db37d71e193fb01f697af4d095860b1fb014d038d1553d32a1b142aba8396eef881268904033823e1f8d93de9581a960b3c156c8d9b694e34ecd7633c1e84644c313dd17550d81ca8918245782c080e5cb8d299cb49c89810a24a05ca937134a90a313ace8a2399286481a2196806ca0c3028b1c2ba6ae8e46861a834cd723131a224c56561befcae9c2e98c1734417ac8a6d014f970a1f9d1433655bb18d3718f4c5d40f5b056a5e8289bf563d3365baf3369f1c60fe38debb88ccf60e9d1043d327181149ba07aac3ace7ba7b85a20f5dbe4444ef1a5062f5e645e98707ddce207a930475c61a489316776c8618912910e5870013255c141d1795ca0e8f851be9e7618f9eb69c71fea435d724ae9915aeb9194d65acb67d111fa462ba3e767ce2413b9e7a75625b85a3b10e60448442cc9b204123ed50c0fbd56334bbdaa7a35a2d75a6bad666c5099897297bedc2bae9b4587f1433a5fda7b973a4b6db1466931a4334a2975515a04c36aedb69da53662a75c9d7acef94369b136953982edbd36024188e0c7101d8088c2851a463b7051d285d399277cea4733407afd6a46855eab087aadf50b6ef5c8544688ae7b642aa3349f7869d961ee730ae5b97249deebf43485d2e66b1c9407ff1349a5f8cd883e38286dfa54d006a595706716cc5f7da28f961efefad85650288a07524c0f4810f994c0237f7d211ef7f167cee33eae7f84f224519efa5301d4c986046712912c8a94e681fdfbf66fcc83fa77ce239436bf2625295d317a9534bd67fdeae3774368ffa781ecf739883e35afe306ebeaaf86f549a59a5b736f707e069317acefb5d62ad9f4b12611c7b393d413d40efa9fc6cf7a162fa1e6f1479fef6fc43c60bdcddbf012ae6ebd4dcdb73e7e37eedbfc8d1618abdffa211f4dc11df33d10f25effea3d10daff7df73b74dc587dfe1bddab7e0613aee6c67dd6dbf0c17aec3d7e168fd9c7b9f8058f9476c36dc058e53622953f8f288dd2aa0e1164d7a18383e8839f83e8c37a1d37621e74bffa152f417ff7d107ff8d9807ab12be5f6910bbbdaf6b3e7e37bebfcf0233b863b382e13cd2a38fd5774f41f979a0bffbf9ddf87ef5f4867763f359c463f455ff4369b3036394dbe0b88bcf3038e50ff9f0600dd599a7e83865322a6a95bda06c620da7894a6bad5a0cd1ab164caf1865abb5d6aa850dbd562d9ef46ad36b955e6dde4c367533815eda9934f9d3c6cf894df9760531646ed76eed4c3995439019fd70c7860198adea9169ea480ff7cc532f74db23d314911ece96eaac4d62c5af26a8a5d49ae1be6411a5bf9831e1669185b4457cb1452420890931529a0401536646114d9c8afc80658b1f643a64c898e9bb47263250686264ca420b531930383bda449e9e18ebf2e5440e067cc756051929301928f6143b769eba75c099e4614e5c22c3254746c6a937e970a9be3c943a847acc2fc250a79203d1cf21adf26d8c1684b4dfcbf1507efddb43a50e30b48fb198646e3d745d71ebf35d4b7dce39e58b3044fbe5401958b0e5bd84d8dcc71a9ca561edcb07a29f1f7f33ea9cf8de7b6900d107a27f416b41a0fa9647d2bf56bc3ef0a2f21a22e7116ce8711809dc71083ac6987b2d571cf5dc23d3154bbca52b8c8aae08e243aa0a292d5a7a7a7a3126677b6a1a03f52425a487b231507d003ddce91884718952f9a4699cc977bd6df2d025222222137c6e60404b9fb3a84ae4c3c619318951d2c3d8c464c5540fa39608652b9dc53496883e259599ecf435901a8a5c823973962f8f538523bfe5cb344a4a949229bd44d3c8044826a25ea892beae08019325e765aecf8912f2a3ca8078fdb0bb1e99a27a74332b5c01042a851fd7cf04d023539823c2f8d0c31a8e2136d96c1528428c0c65ca8c4183043fc1028a09099890c0c18721b211d552713dc61743b13a7c3f6c418039d231be4baf20bc1e99c238398194c96e48618629b2822c8c110ab2fae3fa59981e3dac5934608caa407a2d0106cb096096e821abaf2b60a27aed91090c946c8a6dca0e5b9dbaa21c41005d84216ac5f69a425f406c3038f41b975e41d4a42a824069d93a6710fade3974793cdcc7c3cc71dd3d10f7f93b4e41123b3f08367a183f87eb06a9970f543c411941115121e506e7c5bfdee5b5a850d259efbd40858f297c7c39d3e97bad1104d07a16aba646b35ebc085bad16087beb30870b84d60bd68b6771a017af3990f7ac0d862d9ec3d5bfaff15c1ececbe5c2c1c1c169e1b46e6e6e6e5e3e4059813a32c5d40a6c812108bd65b363fb637bdbd4f0f0e65b6f63730386395c9df5deab72166ab95aaddfdf12aa392e57d73cefb80382eb574dc0b84309be966c499ff27007fd201ea31177d49e2356e955c6b7bdc65ac4b74f30f442d7a4d5de31f485c3ea8564879e6a7ed4abda7697abb5356350deda35f7d0db31c6c080b7f98b872d7fa8775b5e3f76e899b1807d3223857bfd3902ee40d3081e9bba945286b56e434a296596d87ecfbc36c7a4927b58521c768e41758e69c218db6c36d96aefe76961306713dc99b419ad8a73adb2956b97dae5e8f33c30f6effbbc15975d9e5636a07e16a8fa09be780ae248a539dbfb6b3cb9a5f43810f7b872b24b1894bbeef3a53cb4a676d9ab2e2b5e2b0e18e9bf0023a55595942e5da45217a82e7bdae4d0e5e9e9a304a40d1f6152ecd0eb5eeb8624e9768394f8258d181594e0c20811262a92294ff45a2b2c8a99ec8620370ce9b5c7db0d4a5168fb51e649beebaccfc9f3ef93fbfc08440b689f3fcbb9e14877e2029bf2ca6d41fafc5a529fbf8341134ccb8c616db416679d75d75c531c39908b4e6e620ed33a73a7adb6da6a1ed6a7cf7d679f03c35cabe540f4bd3b2466b1d962d8a265bb1283171bdb956c2b9d04e8f156c3103540c1b16d5fc2b0cc3c6786dd6e3530c94b49fa9c73ce9d3b57a4fb560392eddab71a9090604eec7067c7864195cdb407b63c12ecfcb1e7b0d581bef7be861ade671ade67eebdf7c0ef3348c3fb8f871e10feeef5043d0f0492aff17f207e0fec3efa1f48df03b98c5dd76a2b6f342005b921cdb8d6d2c06ff3dd3580a86b7ef72d4803dfc740f73108641f5b0b449f2e9d60095b13ac044924914412b6246458c000c15f7e5c5be99c810909c32293dd6668022253a2dabc56ce0c43f450b644b730922573f19a3062a5335204c7c312233278363dde6678e2e51603965b912eb02c141ce5c005982c42a6a43ad08311535861620726323290691901c2b24e183fe1d232f41093a149cbe0021f2373c229e226ca8b195836ccf1ed68c8778b218b261bec4a8699e0c7f5339c1e6f46a2faabc7db0c2db89a56533025ac805d8162f6782bb20509d5951b0c666e30f8b815b961cb046c8822459dd6e3ad880d4562a83e6aa71ce84d05ec9d56ab06821d7af4296f7973d67a6b2f4835f55a2dd9a5ecfbb6a494198cb9e6cfb8f2188d7ab9ac87b65b3ef441240fe5db6b2d6c29ca975256f1019b46f7787b616afe0ed9efbd3b66b7608c31e6d3af6faf7d4a29d20a3717a2fa0c75c29c797381a8cfcf33cbcc60db975f819e5a6e2b8d6a3f1a9588a88e5079cefd6040e4e4707dd659ade84c697c400f33868c524e49e57c39e6484cdaa7efc1e5915a1ee9c376d8944669b01df61f4949494d22892479603f7ce64ba3a884914ff4895110ed608317a91807d1c7872c8a79409f09e5f288d2e6ff8ebc80c8e7005042f065c927fa5c3003912c3f5e50223396affae8a3c1d5ca7b152f41f55e4c96e0fd8a6bddfde625acbe5b7df725d4bcee400dae5ef51dac84a248ae7acffb158f491ea380fed577a0bbffb8d0eabb17aa79fddd4418f963f2df036392efe8c0fb9d5d9b0b09c53de453030a45e9a0e6575c284ac864f5abd5af388f127a288dd6f01aa5d1a7de67be37df530139ac0da1e21418b1a3cff7de7fe0908f070ef9d497e00872030a4a22a110288d3e0e4aa324501afd1d1c441f10288dbe0e0ea28fcafb5012494579441a1d393a223f1a53f51eb87aad2b28f908b2d7e7401d532e511afdf9ede860f52a2e24a47a15974a91d2e873107d7cc829aba5d5471fd5f7d0d5f4f1f1246f4518f9521cab2f2d8f55be41ed320fe1999bc072c8218729838419d961f56aad5ecd73c29ce49a256c86186badb552b05a795b2d1b1ba530275b7eacb5d69a614e60489ae072922df1bc36c61923d094714e3ca64a8a048664562432c66a9136d725d2ce481bc390f6ed12695b18d2ae5d226dda250c09d29e34c09060483b76f9194729e9852dd9f26b8cf16b84516badad964dabee5c2bad159664cba731461bf39cb95c52533969b5b2c29070429df29b284d604ad1e9491426902289302e341d2d5144c91354d0c0c3152d58f9b678000a0b4750a0b05054a394524a29a594524a290e3b74b9384a29e5baefa11a73292bc77d02e8c79efb133aca203bde8ca6babcb540a58739cf18372253ba03eebdf7c6f8e1bdf7de183fbcf7de1b614a36d1cda8c8da18afb5d6569a420b482c2425dd887880c9a63dde884061f57823f242bf3dde88e020ab0e92ff9c4d578e916d65ab15251535251575c60cadd5da7b71f5ecbd18e7cc518a73e6b8aed394725da7b5e77ddaf3be4fa55afd782a2a4b54fafcbdadf439a7d10c325f309a4126d23c9a455a9eb47879d21205e524952414294549429154a6c8a43392e88cf42189cec820b28804da14462b5ace4969ad564e4a6bb5f6567b2fc6397338735cd769ed75daf3be4fa55a7daad56aef9a1a16cba6d5bab979f10247b33c0adae4d8783beacd873a7dcee66cd2979d56ee9ab3495f843ea1e66cfe2dc280b24357bdcd88801a604818007bf264c29ee06b2b7df2c42ea1614d72137c6da513d664cf9f435362238ac88ad872ee6ccd76aaf0c3b3355b6dc7d67485385d215c549604bbc6455765fc0851a2348b412f67bee74c8a1142f3cede7eaa34cb91ec6849c64f10234a8b36d37c1be272a2b45b0a536a8d1caf1dd55430b302918c1e573782a1d7f7eaecc7e6158a5610426bcd355b0a4a2f2bd2a2682aafa2e8ef94d708236ae9f1364449a760a5f3aa62346c287bdd116f42a286aaecc0ad08cb0d481497b3b4f6d830109329b5cd598f0d0324d6467964cca2a84e3b9571b463a79286cb7362c83e3266ac52d37930b7a2307dfeec56e4a58581442b178812147480e2842cff06244b0aad39a33016766c4d9edaa2c17abc01a102444a0f5b6f6464830991f990d2636963c9b6823ab239fb51d4addd33867d97fd8f16c46eb96cc88e4593167b46c168d2620f3306eb51b7168525ddbe37637891c88e59c6135f9ff29ad4ef03ddbf791618de673599d2eb73d8e62ba83f46189e0782a1ad51e9dba11e3fcacdc0d1206b90a39cfe9e993df0be667dfefc69c03df7d9c899f3e320fae4e4803d628f82f4e1e2e77a9bb9d73aa4ef7a2d3dfde21edb8ffb6adcd7d8d360ec3abf06acef5e7f352cf71c8f6da03fbffedc43611f7b0dc6ce7d1efed8f7cc6cdf0502dd7cec1eb8c3f52f3e7e190c85dce37f812eb0467d8e87b7c67ecbbfefa138a00ed969dcb7f91731f00604fa6c7838d4c3cf06ae402015bf5fccbeb59eb6deeb1ddab3afad08b2d3d799d39c27bf0cf2d7b71be4a7e0f5228cfc99573931584bb2678fb72634148a6510bff74cf9e920c1c7092610f518639f40ec31ead0b7129094eb10ea21fdd7f4b2d41943d6dfa18bd66c97f4625d6db6f72bd88d407b48420fa2f69bd1e50eac9dcb5ea5bdce06784f7ff6fa20dcc0e18199d25c55c860501b037fe3031c20cc18354ae113a49cdf9a9ea7b52a56d54c4bcdc31cb2eefdf7e99775dfe34ecaef99f325df687618a7ba2b8b1d5fbed0f1941cab3add75ddcbefa5d274cd99aedfe5a0da6fedb8031ff3f79cc9ccc5a6e53294fb9d57b5fe65330a40b0bbaf79ee7507528e523ae7ac75b5d78033ad5720befdb37d8332c25081f661dd7fdc73df8c8ffbc0da3b8e07cd6ebffb369076d6de812b90aac0d93fb07a2b5082594555981dd670f7d9ad658179d2b8ffbc9f41b57b18e7f0ea51b07b0eacbd7675d67ec1a89a378c6d2288065127605275c2a46a50750aa58ebdf2e8f4bb9720c5174847a73a6494d9beb419d4a7bb891cd6a8a432f75ba7a8d6ce266a94793adb25e587b51b3218163664decbb7f5b056abd56a326f97cbf27036abd56e4cda7c6badedc609764e3ca3a57dca3953ecfcf365b4623087cc8e49a298f43167393fe74cc7cb3c639e1debd769e907c1a6d861ae36dcbbf7c7793aafea0aac6fbf19aa5abf197b55fbe7e98e0bb274ae246d217fad7dc6eea57cd58e16b85e6e9904bdffcb83e806314dad39f2c1d606c3efbb661c9bb0d56f58af6fbdeb2b0ff77ffa05ba3e07d10d627ed58ef302bc015d609cb3bd7ff53db40654fdfe667cdfea57aabfaa0facddc60b5a97b46380eadfaefb9ee97d1294f2a74ba98347d72fc1303e750feb905d771dd73107641dc83ef6915dfe16dbf3244d526b558b1dca64b39973be4a5b429a34696e95b627116c0ae3a7df83c19e39abef8900a381a3d38f3c6a6a63c698130c2910fdfa1361581ad10255b440f6a38e3fb6817cdcf2643527a9a8e31b13339dfbaa7a4d4377cf7a9b690e6d3e4618ddb740d5c78f864a83fb3bb0f53566a86a64301ceafa75f6269743bdaa6cf4903e43bdfb68705f8154cf7d0b0c55dfbd0d18aafe4a40f5dc87435d55b17ef9f543fa35fb29187e7ff35783d6e0bef2f0ab515fab5ecbf96910238ceebdaf06fed6df168f6da07aca51ce7a998d1ed287becddfe0af06fed0fb907bfa322645312220cc7fffe6576f03862c1e0ef520590302a95ef60d027d190cf515bf01552068fb6a60bee3450ba494b2e32d29a9d3af5eb440840f024320f834a8bfbf19311ab576dae34763f75c858828833a649a9e77e8885244443d88d5f5d320afeb1a21f4fc3a1cd06784c17dd0ed3bba0fba3570745087007438a04b080ce9703372fde9e931ea84f529c7f2bbafcb5a0c86cc60761e72c8667e4c52da23bd7e524f2cfdf8d6a794524ab96cfdbd52de37e37e33e80bdc7fc1ef35d8b2519e38b9777da4b19c1801f8f965a079e8923c7ccac359bf3caca0dbd79ad35acf08d39afb971ac681385815232fd02fb99ff7c2261756a92e03c0c30a9e00c0c35913393cfc25623c741de1bafcde1d1de6d8c1df9010fdea991e7e8f41a0d6f3e897d6fe3da68feb540f3fd97a1cfc1f0fab54ff9efbda13cd096c7a599b7cfdb2d6358d543c3d35e1b444d311442cc08680fefd0db9ebfa2b9f721d833dfeef65525e7da45e0004f037ff2f68d0bf354a5e705ff29e796f98f3df8755aae36f69f99703dd6fd197f8c53f0f2be8619412000f0007008ff1c9ef7f727e2c8753fad1c9f77b928ab9bff10bfa372a8fde8ff1b0d6bfcff848e7eced303076eede7b2facf5dd1ad5efe34f83d6eb1e5af33820fe1618fb0bf0060c673d9449369af7b4655b5f7d58410f6ddda663e990697a0894669f55c1ecc377d94d6dc09a175615b3975cb7e739763ad7bdfe30c74e58eb9ae340dde3d79cb6012beb25583b2ba9820a663399f449aea4304a89c9b1d3bf0f73ecf4d5ab3eacf5d5cbafd960fe34f81e68e53dfe6a78fd7b99442d13b426f0f65a9253a8e4618e9dcfe3558cace03eae62e405518aebbaaee3aa1899233fcd92a3fe3dde929c59e2d46b3dde9624f52451dd003dde902875afe73773c361e98683d16d09d40d8723503dd7ecd752e9494af9daa5cbea959ea45217a8557e1a127a924a4f5340de57e5536e7d1ce87b2f77b1613de6e1ecdeb37838fbf7505db25adcab58625c397ef93424d449a5a7a955567a8acaabd56309d5257ff841759907f150f30f3f1e343bee3e64498c3d2ebb8061508d794a4f3987377f7f9557f565971b1ea098a814ae5645b564880a220000007315000020100a07040281502c9246babc7614800b7b9a4866529889434992e33008a2188841c61062002000194494293a15079b64e12d77244ccfc143307634306ce65aff626492a34192449a9c1d2df8c30b51e1f7ef19cd9abc177ef6d43ba5c80f6720f2cfd011006254c82822a8127f024776fc7e08b6f72eb687b4ddecd8c97fbc04eb06eb6048e8d87238011009aac9807cc8aed1ea55ef9ce81afc5718814c6d42e872f67bc78ac985e43fb33a2e0a88671deab36d4684778dd13991d16c72003b2dcaef0618d42d69f90252027a29b0a9f5ec9a47fecedcbc929413d9b7a4de4d62eee357f44a3a03971fa2dc6339e722abf9ee0b5d37c7829013e68e6f7829b49134ec5f9adc2d10c3a7c40dbee80bd06d7ee25f77e0d7bb1edd83824057b8cd109a6afdcbfd3b31e3ad26098f6480c63467622742d06c08c87cade0d2af99d3feb831ad509856643cee9aa1298b03d0163ae199ee02171317a508d36dc91a96fe410c3ea77de9641acfca481fb6f419c0fa3e8714290285bf8c8c4287ec5f74e5e125d5ffe0c6103a3c92c0a45e77a99ac19ee36e62b7fcf7750ed7d1064ab1e0de5723c0a38710514a0335caf56c2f432c515041776fe5954d4057525d75de80217411082e6c910171798a46a92770b7296d2044d12232412330321853439114f478fdca2b7eb824a6ce35750048c8f98aaa771d48e7178e7bee8775868080476a8d50fe0497a7d7b6a6962098449e98aeab7aa3b9c8f01188bb2c62692192f27f7e9461b28fd9c0426291bd008106db8e4dbb103d149dd81d1b6e20708e3c9d2b378caf77234ab60f9f89b41caf56c69a78ceb030bae1027935a5278d8492c6f066e01b8f35246774887210d80a531b0d3b3868a7cf965a8984331e1d05c56f0d284faa488413c2f83b2d6537b6c7f9e4162a6c983191c8f7422b24971dc3214017f630487555f67ca137b7602fb31fe820d3f71f1db4c69f6f9f70b170c18ebbd03c421511c7386a38ee660d1d17a1e97327c9e8fade19a22aab54448966f7dab22f284a48417bcb40e3c9cb4721645cec2a95cce4f4a19c876af56f49c0703fcddd67cb46bf0ad434278970f99dda64042aea2eb9cfa7af8906f15a050d116c69627825448521563f7622441bfa3cb447981c0d3b5577721359e89bf3c1516504f83166490cff87d53cdb288e4328a8e99396e55692bd2ea90f9104f73896a2d445020a083965c7032d4b0f72cbdcacdd52ce2fd7e54b2a262050ac776a869a3e37cb3e819c53b4d3df4cde9896b41ac826fc7122deb99ce5da3a7ed4e8f2e23f9d0d765e221f991b39a31fb9109e1fed1b4096ead7e01adbfa48938bef0d662bffa74ea8133ae422e74bf64840d22867059ebd752b26764461fa9a016b3fa879a0d3fddee5dc08f3d037d5b20eaab368af49371667b7afad62c3621bd81249b2782214e48e610f0ce21e73616513cdafdfac699bdff61877f124e5ac1c0b63c21dee9b44e1fc66682bcc9fe0691d5c18b09c9c1e51daf55d4f931e6872ea44beaae448f09ab168765db574c629ab060c8a09376a8c18748e04bda16261e6ffb81d82bca89545fb4b7a344214d45f2682a33c457c2b2d0122172b4fb3506a62d310fd2331bf2e0dac26373c4124fbb66cf8cc136ef03afbf86e40bfa71d8b25d09995ba31808bf8a966a6495f6bd887960a4c88e3e28d0ef668d591b739e521b190d6b36ec129fa7a5ec7ba780376825b468a2269559fd069080e6e35e998c2c49a082caf9d01d3e0fd0b0f11d56bbe5158681f2beb74b8a468e022d0250525a45fc8a78ccb802ead5296fbe62f41bf66c25bcc423db9e83629e9ac17b1652e85d0ccbae26a38b0824d202ce1bc091e9bb2efec9c6af2d318704f3fdd3124964029fde8f0e690413eb174b336feb28ae6bbea1ec543753753bcef69003e0789a59b6f90289e6473e75d09e823ab89ec52c12e32976e109bd9ed7baecb34ef40e038456e94d2025c1a1454faca82188fd5ca48575e4958b261d0afb3eedf50d8fc3cde5680f5132484f2d608baef4de55c89f051182f44044385c4f6823315fe1ba9100142b95e5dc0bc5e52856bd6f09b0660145785216005f533cc9a395fdb01df4106201529044bfd03dbe6c3738d736001c723c7596c9cf9fcc3d45d42e6aa062038845e01820e5e37fe8704c48e7018dec2e086272f0b2a14b1694bc44c3030725f29066806f407258a2663601bd2f507e145b304ae9526d0dc4d98bf1298ad792ab59b2c397e1f3ca0b5452ec1b93a4ee9405213eed20778d09514970a29de404c957481966009c16763865d5d051ed70c9c5931127dd7b3fc6ee75351293adae966ca1e1fb38ae912f3da92a4a24538a47e1a7474bc8f0f5de818c943ec436037d64d591f76575c84d960ed51d0d87b1126320f4f6bbf51164d4ab918aa48135887b473f2105aaef034ff871d13350065a098696eb56adb5495a6a6954989e687c3b89039233395de206bd21a4a32327a3a4c21639af6bf9e1edda836297a27a9f4797f5eeda29362dd1a90f55e0177963deb87a4d41007222aa34190c831a4a3ab1025e0d547b4a2fe549f6b3801be9da52b9d278d86efdc8ed0e327d51c75deac97602d55911cd93a2cece35b788890b82754328af66de2e2225b8f6d6c0276dde0926bc876fb9cac8e41bab8fe6fb38b6e9e3fcbdc0bccd7d26d3b298ab3cf3e7feff68acfcb367cf7cdb13165b154ddc34f6298dc593390bad176c677ea5c20b89952951cd36fb0aac599071e66fdeb6c29625ad413bc51d18f5b89e28bb40a5c3b960e37ed46a12efff9774e099eb6249d73a612bd71b78d6fc5100e906fcba73a66e00dad0d68732cbd20f744e17602a928c6d8dae4c7ab878b67e526ca84dcb2dc15e31f538cc353af803334afddf6a924f235bcf95af414c6fe9a7935258eae1418d25a0d47ee74ed9f86828dc4607e63f0b02448c3e5bf270ca220d99d5dec4b34b7d375a956e416d0bcfa4889faa4fde75cfbc47247206ca85e295ba42d8bdde9749c6e826bd89a46d84a02e574eb611ed8ef65923ef68492196c3dea13b508b7fdcaebbaed9a938fe751ab4be988b55da5d4bd37afec2fcda042f573f8d3b61bab52199481a4826cb8eb1bf2e8a8d88cf29e8edae13bdb0e7888b709150ac7feecd070f7746d9622d8bfd88fb12f68411fe34b2ccda9fd305cf81cf84028c05658871e2c55500f6a1f4c7667bb535971da6d39a26dcb8078f93d282d4287d782d05fea517a6685d5bf4de4cf259e1b8984e289a1db5669a958c329b9cd97f2d89e4dba14e1d833b2008960a144cd9d344f05470849fab0c3b9f712d30294901a52093907a34359305b978a1482ccca3994c67254867c61c63bccdf3141e82eca84847eaaba09607ec4717b69e73cefd5c88c02cabcfc90a7f4f2b5a3c367794f41e8f2efe9b8c407c07be0074b2cbcba90ee2c07e65784da33bfaceb169b528627230509a53708c51b8b793e89a57987d5bff606970ab0bbed269531fd9797e6682e87d70eb16208143f3c25a790084c6040a55f8852bfeca34e96ead7f1fc4588473574c3dc4b94db044f3cbe7fb2d886d98597a8c8c37e04d795306d5e9c009f8b2c5d47f309bf2f530def97a8b00910575376c80aaee6139c79a8166ca1b37e4bd9eda087e9cb31c2bef79e86180642e0bf44458c24aba2699baf76917f7db9bee2dccaaefafbd42fab4c50158bda24084dcb43c914b246e2e2ae3975d56c2775f00dbb433449bb3f2d91c42111302e8a8c5db3f17181c8a63aa733bc6f2e538e5273bf196f7167e3ed41fef328f6e9ae1cbb28dd402c94a4772c70d6fee06e9b83c65055090229d80335d4c900f302e07545d95b2a93bd92a40b6c896f8506595869956e4eaed454c9cad3a91130a5a412ba41c7b6ebd97ad55551eb810343dfe234093f2e8034cbc85c61a1fcfe25c1c2bcf3b9094568716381ae02e9bce00cc724daa9e2bb1774f3c669d4a4c1f8377120b596bb8a31629a28a3031d111746c4a6b8c3afd2d58c263056b305283dfd77b444a5b4eb3ac100b31d172bd53502e915d8cfe3e54db6aca1eec50ad1d3f726731ef817b0d3491e096ec2aa97a419e9c533ab27e92aaba1e54425610c6937595912d1e1489e2d2502124cc87dee51ed7b2cc1cd62a634154f03028bd3f61d1cbd090db394726ace3e0e082450f1f5c23d2f9cb450e3b62666af2393d8bef111582e4720d35c15aa618d65cc56a75b7cb30d2b689a6911810f792c76ab5bf2d8af02df3aebfc14ad0b31a5aa8dfccfecc5627b4ba5a9d87d9da6bc1f3c117489b64b8a197d8b6f1ab46795e0311a404701852a93935de7f4898512df12b3914c1bca55c5b0980fc592a1d6469bb0a7c51b04b316210c7369c4670cbbfac82a897a26876b5f3931a67d8330d6a953820981cb5b02f538800aa145ccac40b79faf9c03b9dc0e4ec44d2cd534ae9a998eb0bc57556fd17bc4457d65aefc9c70a85fe1e49df303621c1458f1f4a007eff2e7c48b538e06e261854035b8752aea63ce9f648622cdb33812280ac4b7cd3b8dd89aed83ceeb289a0fc8e6c0667e3c0e774d26853eeb8ce8f757891aa263f7c0581b7a81110a75bb9431e0e4cc940c1923ea40ea869b1c54d4383f4cfecb54740844908b80799b800eaf3c2ea10bb3549a831166f1b35e2c3014132e2f9ad2b98b699b9f340efbcc0ba3977e3403427b9978903adaccc292881e6fbabe0d4ac51c7f01a10946904fbc0bd84314665730e52f4b8927367d96620551414af9df324b0906f703ff51fc30297d836a2d3af5626cb286e35784897c049fd9cb0c0813b3e16444f8e1a017d7df61e9b2bf073c4baaa94a9146fce5ab9a0829606537eff577505a95f899a3dd205e99dd0ae64e69d04f0ed157c2e8ab55ad5305ab423d6cb89d513711491e208396bf976583ecde9ea849027067e7f39fb0ec2b45f2370a8f73242c623c196402d6bca2d2abb0710cd937ab38820cb8ff40cda2192307966757073e16fb05ea71a3d98091f180ab53b85249c6921f4d88793c0e8240f28bc274e2c074349a0deb4087656c6171ea35954dece5e57681abcc040504c51226c59dd441bf9e5bdb3a9a1fccd4d92ebbe814a01300ba950aca048ac71923706cf034fd63c99570f7fc6e80999f083199071d6410a24beeb18a198bd74b72113c281ce921baa0a3fecf14672b476c385fc086d0089c33c499f5465d80b6fbf64921adaef35140e041bbdcdde39af89d01d52398cbf580361dbc4ba3eb91564cf0755a51f958a5ded6f3ea65682794b94d99d4df9adbd75f25507c24d39bd141a470b98b6f28d941b93a0cdd539cdc0fb1bce19480fdd14a02b1fcc402712566f5dbd8e56e0a32b19e6e905cb577c89ef0ce44cc0a2bd1020e0db69f2b626598b00f6d1a73c790c6c3652dd883847dd60533da6e4973d68070e84a599dafde5d0531634c2243b434487b366caa8565f59c30aa16dcf754185328bf3243265fdc0110c32617fd75eff45e8ca7ef41032d19ccc3569f8186d9d9df196c0f946c9e6c30a51ee063d2b757860f09f6783ad2e1ae57dbb0b49d346f91a266c4e53b247293fad1a71465854ed5a87a68eed060b46618d19aa0f3b46cf153d6056324001151f8d7c9fc48f9e496d6a8f23ea0ed2032cd9941a332a4f843cf508c30107dbdf84e306ceae9bd0e57765387824202014e1de2143f78a1c15ce99436e7f76addbcb91cdd38c707c6733d1cfc8ef19de3fc85f6dfac44f37acea41a44e74ef15a5938a606098d6d81a39481cd521dfcd1b94616216166c0160441a6894404336160c7f4d42f1d503b1eb38392c182a9b466f7db3ec230ea350819278f4d18fe7192bf46f714e1d7cf3f11108929a940ccad20ddc4cd0f3ed9c12038c2435fe4522f7d2f1fe47e862d6cd80c02317b9175796100cb16c7a054fc092faaf4c19f8679d2a00a6d5898e8d9010713c8c3df007ad9fffd8aff30bfa94ec34dcf80ff37834b1b850ff7a7dc4908ff0d820961c00f7c4dd069c80ea6dc11a352ee6c5e2dd3b3511ac241eec0854f1930fd932cbdbc8514238024e50e1f72ba4a30065b28ab2877106883f703742142b913c62f70fcf8c99d157375c9c91d1f265e97435d4ceec48d7bd147ad242b5f728722e6c095efde55504aeee0affa88ec1de73e954fc92a46933b28ef4e97f7e88f3e98e4b28e7f9806ca9ddc60f9d909bbf076c282e4c208b7e51b2cc13f2e3ae44e1f476c25e40e2a3ea9e81255c9de94bae5de2b5f7fdc9988d98d1f91b31867e7e30e1ab18cd8f9cf888f3b172d6451d76ed34287e6eeba1c3d2aa67af1647e689b89b069a88def662a1373ca22a2e560f2da03f175c03b83c8f6a4e707bec80809c98ac51a779645252246f4835aeb7187e367144f733ef2b8731e471710c786c696c79d91c70a8488fa8475d305e58e03ef6f557f9a221231c609995509c08e3bc3bc345ae4b678f1c6542de3a7801bb1ffa5f609e8b8337d8e4d698d66cf43c8fd2a2bb6db7ddca9ac3e6238dccad16ee337c93eb3cf2870406b2a6b675a29744a07a59e8163953b26b2615a8b8ff81260b79e8a1477c46c77d760c5c9233c9080001177a228eebc0fcd264c9a0e87b84311df0752ca2976249eb893183bee3c929c9bfc504cc21dce947d2b43c670c43e257c6ed1c49d51750aab2e716792c09b2c9abdf1aa8773fa26d27d8e220e426a284c80d29a80b59133d3491f2c85b6e54b6e863606296b18de428640c18978d50c4d04b5d5a36628a2bf5f683b6238e2210380e9966b2632450ca91b5d956c95f824b54889114792b6d1e067af25fba8d7ce0169e76dcd8d6bc0993b24e21b30ffaec55b5e966d4a03cd10a4dac98e7c28e6b618204d5fbbe0d5c8e798b98a9dd36d983dd34aa735e9610b9f6333f4ae11b1b799e95b0c44904ae471780031de2d2161e6eda6f1e48498ad329c40c0b6ab1a16ea39d7f2d45fa078d3476b7648e964504cbd7c1c742a586092a04a60bd5bb99d839166cdef8a1fd171a35da5ad7f8d2a5154cd9ef0227dce26afbd72170058011f71770c22481f967a7bbf30f91ae78caa7d09893821d88f4af1be9dc4aecd335f886d0d04b2896e1e87733d4e85d45b57e616b65735cbd40738472478d91a2a01ce754f56198ef81a58648a2acfe70b82ddacf0a84769ccb83f053787cc632e7cb98196e93fa36729cf0a4f8733c29b4d670482907e3d70c784edd8b1b599131013055aa1d9a369e3f534571a4cd180362fb66ad19936c95a2d33cd0c101c890ec43c2244fd360a4cf86d30e53b7b3f0a51374758428d08976ae67f2201803dd0e7561afae6eb09afa7d8ddd145f621a8157af9644511c123a405a8840267640508c20595f47a257b41c9435997606fb3dcc3ba033837966fe23cae049555f10f31532f8ba2ad47b4ca6fd6b63b31ec59b7bb85d584cab3e59b7e2a91b7093a0d4b167d30dbe47537e81e0aa82f2104ae859c07fce09661f7e06e2a3edb26b3797175f8d70639ec54ce1aa61fa07fad9961778d91b2ec722e6c00a8b6ed9378057c8e57190f3168d0d2002ef2bbb415e0db770f9306cd5d461c9a4586c62ed05c51919c1b0a8c7997afbefef2f1b65f0133bfc2301247d76a4928f542201ce8923d0fb8beef5f83043a1d5db85daff182d28e6215668ee604bf4ca2ee3eda4103603e12d72448f2a1ae01489250be2b2da5f9f736151db694f7d95866d69e151ef4d3cd334cdbfd44b86f388024a61a61c7bc27769f136842ca1f6625131b49e30b4d560ad3464df48c96350628ccd967bdb0f7203389d7c943478e022476fb5b4c6f46144207f860bc1f80bebad0547d9f550833a2bbbf3434e1f5c5e131e09515898d27f0dda53038d65abc1d5000242bfd619658bff15dc7b419cc163b868d22cab5544e778f7bcc1a8fa2ef61dfb0556e6844f306db97f9366bae7346e9d2b80db9c32b68c2514ea2de32b7e2413ebf4493a5362eb2ef6cfd31bcdcf6b8f6af5fa0693f58368344ec973688d8deca64f9a4b2b5387c6e3cfd1168863810125193b15fc58e80eccb06aab3e29fe09bc8430f29b97d86b22c65baea2d7b4b3017072495f27deb679f7f3399e99af3655463bebe7a42645faf7d50ae5d444f813b9fe83e71eff6bfd3ae41a6bdd05cf2f246cf1f5b1bca18f565f205bf0009367ca5627b497de73089e1ffbfdf66476ab8a9c10624fa0bdf7ba58fea324b67db08b889208409d282040ad074fd32fb5de8e70cc2b6a46b010a41cff34031cdd52bd7e5b898d7d28f9b1786168987cc95835fb1d222f60c2527ca9f15d01a9f296ecb25a461e9600f83c6327f942369446dac3c3309bf949cbfe8abed003fda4d0c54607b111847b638de51bbf5c70ad13e3e7add51bcd234c3e1cd7f7fe839b9c6af274d5f85e0fd4152fe44723f0a8b710eed3b85e7521a32dbd070af8cc76c04ecd29043a823994f9dce523deda6e387da0cf76dd73b220e2021690d0a34a7b1e695845a0b1a295303ca1ace9c188762df78edfae17d66e4cc54c7274bd932207772924b2cbcdbdd49e5e1f60e4c2b131bcba94e1645ee0dcfca6c0ae05c9c45c6bc5c8dfe4bb25d05900e99cb05b294dbb6580aa09e6c76b5e5e760a7067ba55efe42a9d643c1314b6a8ca9b534d13d6d29c6602da589128f17b8b2bae0aa1386edd1437637cba9c2b5a379e13c83a56ad356c7547df0ad680ecd16a6a81e867323f7dad7ffc4b9e3fa899310121ade55886451b6dc49941e0a513c4ee4f1f427395d1ad73d42c2a9489d5ad48de580f9155c6fe25eb044c2219b859a10b55b475c4a77e5b9d6fd25be3d04339de8a343870cb99c868cf2c02d7cbf08718187e147b24ae349af6348e6c8e2eecac5f1c66bfbe3bb6bfd1498ef54cf66d458af73aa7e44db961ee058830e3673121dab3cd7ebfcc723ae5187598e0a109654de9b1baf376745f865322e28d79726af327887b39ec61e24a7b9f824cc997285ff1f0e6ca82f9418601f4590272a7e4adf19c840396145b142fd87004a155d567cd7bdb03e1e31b2cc124d075aeffe7a439d9ac5f981226da64b5a95e881cf9290a6db4d0c7ccb495dfbda942f77361de554470e9613fac4bf910857ed3c8dbd714c7b6aafd060b5d9438d383bf3099d6ca1f2a0e9175c983eb5e5cc4a37a795c8edd02fe92278768e80a3049f750ed01e01bff6aa1c8420fce8083bc2ac98138eab1f0174dd13a4b9b60562c5a6e9f28b423563791806aef7cdbf413139195a0bc52d75643feeeddc01b800bdc161d3035782d16d3d96f5e828a4e70852f0ac3290525dbd024f88cdb60539280611b22960a8f82ea2c85e043deb6d13c06dcd31fb1814092a12e98310161a309ffc7dd98d5bbb35c4ed283b821f928edb32860826e358d24f5ba476bb30c185a663605b44a778365305c6de3d4f5bfd21b2102f85fa247f6516cf948801ebd74598efc08c95413c420ee10ead2b6ac01a7a8639ff0258f7631b809d17325769272fdf026e51b4dc039f1a2a316dcbd686553c3b30a6aea448c596454f9ad8c982f130b71ff9c997808dbc427bd01f26d819d854b0216e3226606b7985a3c9cd572b82d273dbadfe2949129ef90dc53b7aee6de6b6f1a8d13836364b8d1f863ef93655d5d4d52876384ff54a65a24c02e00637ca1d84aee595b5cbdc08b1245e8645d86a03ac4fa5d9f9be9c0b410f11808b5aec9c36c14278479b21aa46a786127948b56b1506712df8c1e478918d8cd3f55b19a15b027bd6de6eba94a8cb8ff9d0dfb5223685d249562eac088551701d140dad88dc0b61738deea8ce017f14d518c55e4d80e314097d512c1d10ef79846df27f24057213a51023bde8bc54634008b26d99a52dee4b7005873ba838f1cb55ded4d47f75f2fe26434b71736d656bfa35f4a46af0549e998d70e2bef0161d98ace46c0411ddc0ee2a007c00a839eae22bc966b500ad8857b68327e33e0730e5014335085ddd9bd85e1ddb1254b2717a8c98a1642573d25e1c444f5135e3c4de825279428e5dda8e0c40aefa23d883274144404c244c495cc0d422f015beaa343343f038bf2d7b62bf120ad46d2e84fa3ff54863ea75634b68446bee72ab04b607de9250ceec298ab8dfe08895b0585df2823476ec0c2639895ad63fb84160a206c2720d9480ea92238adf8c4cb59d5f283b90ddb42e4ef80f82e1635878bea5744cacbc92445941974ad243990db7c85a91f4bc3611fbed3419bf35e979fd7d70e385d50432b822ac94ff22c5bfd8b21db05a6ff57384299eabc5bc7192d8243243b971a76603a4f27aeff961a878738f2903991182095841c7063b698ac4e4dbb830d40b97ee684f21c0688365c0b24b25ea1650889ee735810ed61f00a9956fb6764b2654db0d932d097bb967ac4d496b4a0513cb1769b534f12c968e7d0606c415a090a1bac45fbc47d541683ebc84da9b741aefabf14269a74b7760f38c544f3cb3dd213f197153e30550de49a07b9e9b624b16260d6458a88323c512b603bb5661f78eacf8fe9404a37eec37b91271a7a26207364e45009b3abca46614bcdf10cc183f6045cc78df8a95b9b8a66bf8f5a8cbb3554a47d4e5d6b9321c3e13d80eb67d13a2eb32684a992a2f0cee976e3c8f9d517911b30e17a5def7bd8f3892c1d61d48a0a14cc8eb1e02685ffaf86ba366caf4a3f29a6e953339a61e8e34846f1173edd473a5b6417518c262b3bdb466fcbc124d43a2f0ec0da028639d059cde60783b6dd8654cf69bde13c634a74b3481501ae387abc6bfdaa784b78286bbc9757ad9d2a3d7797c345d779d012635193d23957e8161397c893b6268eeffe604b8415d49100b7ef021c6b92e0cc5b388e8410341212849c043504cce1177b0d1a72f0528e2990b2deaa629e18efec6d98019e4247ead9c4d3803061e327f465dada1b3bd955acb9c18de853d75d6c0b491733ea2da031babaf9cfdc3788ea77df3915957955b7cfba71863e10da7bdaae603825805862418b94ae83c9260ec92b37f94b8fd1ab8a5e0a005a6d90fec6e4664d09eaf0a074addd264b38e2c15f3dd647a198380f0c21e269c47b698a0f8504d7a0e377dce5667c4967b10da526cdd6f2dad95fcf67fe2acc82930d140ac400838a66666bf2443d1ee30df4b52a2a7a28498c5e8695ea7ee8d4fd9b1ec22f01d426f0d2a8c6b2eb8eddfe354fe5f634ab33b73f47967b64ca2d3b237e140f964a19310d937073c6c6dfc8c537d75875a50dc0c82697e92bfe6dc4cf4fc243f606572eb87d936b642eadece303465a52fc6df26e97677638aa2e30f3e561047c5edb3bf5945237aec90c61fed283238475c78042caf7351e367e0440694a51989a034bad33c43b75d07d281fd50781f149a2ea24dff889a7267bce651fecfe542638b80eadea4683b13aac07ae5065ace842e88b43c467c4a7c8955f764b838b981389b7158597cee0d2822d7f69793e64d246ed716d62b47bf521beb03bd1a5484eaba28ab43efad003bae8c244cf7b1952ba226f579babc215adf5362d905a3de23eff02145ee91f445afab5da7fb3340fe283d6d2aad415916aa6759c3ceb1571a9c8f35ab16a35fa683de3bb635690d8d089a84b8a5ef2127a223b7ae76452cb3762de98662ca7a8e059edffb9bce0c433ae48db242fb6f7f3b80b3d85772b9091ead76987c81c9d4b55ebcd6c6c91a57c6fa3432e87c0b69e588aaba0735b01338e0796e13e278528c6ed0aded8af1ccfb6de09b7144e8ad68c99cc820480d522100a9290ecc795844512e14ca7a406aed1cd9b900555e6593db78c1cadf7821c457bb36bca20ebfc9e4b14fe2320839556ab9dce6e008bcc2ba728cc1918c085b912d0770046d638ff6d0ad129f721dbaf5d6582906a7de8b4c34e227c946191c379e633583cff08b469664c01b9441c1fa4aa1cf93e8bc768fb80ede58487b83d250bd2787e094ca838756f0594bdfb74eb9357b792f426df18d5808d2d691477be8966a025cd297f10c166661969c68a206e0ff1757be33aec103faf615d8abb8c7489be2190110dad3edb064956b0b05dd450dfa9132febd5eb6ee1bb28c101b4b6f1801b5e35a402abd83cf262783791541f1c8af50e1f78c2c4f983f980fd05d3a05f9f70e1a1b98885e785ecf55512c82e19f5f2f100db82d46f035fc8ccd42b09461059f1d032095006c38ef750fedc4ce25c64922f25239792b38ac75a7147a5a89945459c8160bd00e7ddbb599a76c205b71dbd5dd77c018934b8c1503dc2df2c8ecb92833cd89d69586ce41ebacfe7a8752db1e654a8560d559255fd851174b87642912a0acb3642bbba4cae63823a9007dd894ec729b5c593da6081910b1ae925c6e136baba3a9440053892289827579a463e79caa48c94e28b65f912dbd6917d5531c099d82f6ff36509dc8481ca598c2ca31e4ab13aa44278269b302017bdffaf7e1a5603afba98f69616cdd9aba9fc4e826a1a495e4e5e0f84960f52511511f1a7c8cebd1a050c5246097f536a648c1ffb4b3ca166f3d0665c17c255f5d3d9e76ebc304229ab7abc6dc4a4340fb33c5376cfb0c74a129de13dee02fb332ff581748a23155a5b310a74417df9a71c8e773c382007d9ce8264fac2c1f352485600204bed1c8086670e827706f8dd5a2bace0425d436750c59105e40ed1e07b1cf70e984901a9095622f5dccba99063c13a6133af52f158563ba9836e3512e5ec9616ac300f538972bbcf6f105e71d9d356ffcfced9bd3ff7883add9327a2531f6b9844ef7439966276f573e3ddfb7ed1303cd16c5c993b52b7d5524f17048c75d0921261a4228077a1674fb31771fe59026ddd8e4aee78f9493264ab9e1cdb2239016f3547457b0f8c7534f9c96992efe40116c6df19639469cf01790a4ddbf11e784fa11b128cfe7c920f3bc05c567859bd56e492e910ac920565413b21ed299a3471d00c3f08231067d802abf65715a7c9412dc8be86f2e1bc1cbfd5b7f7ea30461e2aceb275b2c43801a67d39035d63fe0c95f7203dc998cb95c959bc46a25bdb799807398aa50390903f2049ca833d81fd55d7a483ee899260aa65ccc085e91b35c92dea6aa8e7a6c55e39647955c0e41f137cca0d86b203b3e0aa142a910986847d1d0d87eb1f75cdc933cdafcb437818d313ccdb8e3a0b2f799b3e8dd66a3789a3f19831122d646ce1f53598c5e6337c95b66bd540089fafdb41070573e7e49b67e04e63ea13da26f8e64317b45c0ab72387c112d199eb86b28eb364a665c55e365e8af351890f77c6c46c97060740351fa216bf83f24366b898dd3119ff9d6b14985d930170e7c3d831f754dd8ceab13a7cde2ba24ff3b8b64de7b7799ca750353024cc37c1bd4b84c429dbb1850d3745cdc64f7f460e07822b3700a18428803235491c2a5e1cc294b2e315683628c1e7265c61a6704ab8fd73c3a62ecdb2d2b897e3c47be6d3625b76a0889ebf13c82d4cc26148b9899b8c4df23a66139fe6bb62678eb313d54d713b5271d1d9577fd63519552fa0aeee8e329c027a3b18a56c1c5c458dee44178e127d2e3651a35b2724d939e9646f036c577b792c772623f448899ccffe08f05635cb664af09b4fd63409dd08aa708cd9640fd4d489bc3de43b509d1e3d2060fea7b940840638923c43301bad05d7d8b38a024f833441e79d87bf7313643544c1e0fe6dcf83d8ab0bc76e2435996569f9c46c95be9b06409ac573a496e1ca7ac05fd181ca00e1b9892cdd2b19944081dc705f92a65ba8c258c10854ca75792a650010e474cc83db217ec2997cc28b2a7447a435f83900d2066146844a59e90d151ab5cca9a152d7f1a51f6c6316b6b232eca26c3c63a7d44f1a3260e6d16848a5644f9dfaa91d5fb40be85105bb8b477fa0f96ae95864d62eea954af2653bff4a4754219379ae37855dbfdac45c0718094d5356904fcb128a391b9d8ccc987916be6842d72732315e8021686720618eab3193777ed90d5e2a601a9b29b7d4e3c6f60760d10da7e94337608f11678261c54deb36b7e65ac5a6536422ad6a2aa35fe7a169beaf579211d94d07c2ab74eb4a9dc99a5f25b065fb8824a0c2abc418f867a484c226888b96fd25ab52eade28f25befe6beedf7cab829674619007474ee161aa58c1f1c46ecc1012cbc0ee4c0e31d7b6683356230acf49bf6f4a4e9b5f92cdaedc2813733f2634dd7d1256b07082c9f22ebaacbc855000328c3664250c6e9a0b3000d59f36a661084e612fd5e1fd580aadebea724f91d500c4efb41291fd62702f5706a29ec67dfe5174031083f2e09f406bb633d78d4589e998386c16c259298cb289b570968383f87f249a05fd006405484cede4a29b6623d342b0d3ccfd6683c0e2014690b48a1de95aa21bf2b40bad562c343020772613b108279c190a7c23b11ead9bb5cf9c1c6add4dead401a37e74b2394e2c27fdf19d1c1d6fe4ea6564e6ca17164a4a1e09d2acf05818866bd18bb36788ba346b28b3230eea11b74f9a3558c4584ed9c903e1479208872c1e72f903e60211cc6c6aa9b62d8fdcf5cc33184171006a6c08e179d2905e86a8a8528c121eb9884c99c6dbd0b3e1299504cbc3f5d507ce33fe6c845d5125c0fb8e997d40c744826cdc3227fe0b1526f9cec602876e39bffec4950ee40e30d3488ce380e5193c2b222302c403d63d2eed3b38fcae5919ea949dc6a3cb27a07d5f10cba8e6c7dd89814b7d29cbec346d65181143f31c25befa9725a38ac73b7d884f368096e513ca9b80b8fd55ccec86afb68e6719c093d5e7898a443d7e043a44c50de83ca2df99a2b7a5e0ebefa60fe23fa3e0e83abd562d1d47be198b1410726f292d8164f18dc379a4954742873187d34b04463768d806c9c512ca1dbc3cc58478ec318882bd6a4dada5afd7670e5c44a32f7ff1137f3a107d81a286b0aaafe346bdd606467082879525ecba4114eb052e65e07874dc874ee36e2fb7b9ef40e8bbbe3e578cf730e417c594b4d92c4847c16d0a7731cf80cfb1c0a8f2110a8cd77b89b4a9065ff8e74d15c03461629489fe0209c429393eb6f8d4fc1762a7ee8e46177b01c6a196c40e1bb0e0259aca1d3a2eb4e34cdcab3bd6a3cd2e653d0ebde8e9441aec58ef27949cff570096edaf81a29032ca71811eddc22118a6553bc75b8aaaa791858bbace8eacd030958cd59b3728d800565bc81d461e7c29a3ce1b8e8ae745afcdabf862664f4f9debf3085ae354852081cf38e63f90912d764223485f67bd09ba28b846d2c2200aceec5d267b8423df87d59950784d701a40c7647615edf4e55b8910b1140e91b408ca0623762106f23e82910932220dfa2da06ea179ab4fe4537c9cc48540a1d185ab1eb0bfd3d4aca0b56db4dce8d8b8998a63605bcd4f9d1532b4fdfdd1ea2df5760a50d80b9dca01ff0af6eab58ab18d5fc10184fa3e6b2098dc95f3d5e0c22bbe33e223d19d85b8b7e3c39f297467cafc99bc95b0463c7a2e3d86c60f5b23e9a5472b1bc0292c6ee5e41fae43e61e2a29c09165f409ffb6e8c93591fd0da5b914c9fd39b6d7b4e83688af1e37644989295a0a90fe10a889654a89d4aac48fb4668102f64a8ba5a91bd842b23e8a10f2dcc6c2614d9aa9c676fdbd8a71c0e0b9b6bbe7c2bedac3bf0bb491a03c88e7750d7d5a73e9b5acf66b0faaddc6d5bb87249be62524224c2f3f2ec4159fe6a683051a09d79adc2e05b7412b1475bc5252fa4c58c79450cc3cb4f0850999eb8acb20f061be2999433d1ddbff9755b3eb47064c900f16e81285099a80e24e2430d3400d6e0d1ba51fbfe7748f7b30e214c5e437740584424265f7dd36e53796aaeefd75e0fcfa9bd63003c96e8453dd7fba9487cedc3cc201c3e1833200dca743b6de0c6895e10998ea696fff21602aa3e9925e38e35a0c43393082fb728184d1854e14934b7d117b44bb3b755e15d1e5985fd433b0470e2dee3ea32f1316600a4683cd1a9b339e447341a763a97f68f4726e7fe95256cdf0d5175aa86c900d068cb1e078087c0f6eee06f8c7c522040f5d13d8ea4f9a3d584ab5c67aeddc4059031ff33387d48fcd17d9272f96c66227c8d28b23203f9c304092796c716e82f911b14b9cccb9fbd0212fa869cea310ced9f84f4ab7acc72e263d0721c105eab8887058fd1257c6807d3eb4b372312299d0726a6d29c1d8e3f5ede34cab96add36a17c4edde2e0a5818c53668470a8ee4e999620f22241d39c9bd658e89199e01a00c6f8ed5e6db821a76a1ca7095e31d2cffb1163b74b0dea072e92e8ea1178b979e9acffa91edcb3e6bf4b3b752b2ad171a1c1a1b47d565681dc6aa5cf74e34f412196d2fcfeab370c7a0304bb6219a3ee3e74b5829bce4d764ac1564cdc563f9d6e60702d82d74fcad7ebdf0e01797d08806d14fb02a77a50d67a5839671d1a9df0f127373eb5148fdbd6275759055272bb8f2f8bd65cd41f8ae4b1caf78e374e97b9ee75738ad0c426dfd6dfec78a4fd2989337f49cc2c92ea9a87098d1daa659497e066b4780e11aa715a98317495175238ec51d78a7bc9849ac95c96f3a80ebd6d35cb64a4477820359b02c16041bc03db1fcd7a692d78ec79ce806f18325615311893ce252cfbf4e4b61df79f6052d3a45ac2e7ec0efd59ac5949bd31f51e188a68a5cdc2820d58d333ef13d5f95ca1f47200405a32cb7556d8ba31448d51c443d969da1413b784fec1ffa05c768b2504b7d51b69de9a28c93898a1eb18916b531e92e0f4a9a7fc8cd1bf2e82e839cd7dffd1f2a67e09f26ac1125e7c006aa9135d5b61daf02c6dd59c67f2c2a378e8532b7b20577fa140f5ef16e7fcfb5346500541cbed24cfa378bc288514b151a5c12eb0e57c4f0b498ea3bffb32adee64d46007369f2d0e3a572eb5ff2a864a7646bb3ea5fe403ebbea9d45e83ec915e4fe9a6577c0627871d76bb0dd613f5aa295c92d23795ace3c94d081b6c5bc3a0cedd138f521d0eadf4fb7d9e18e998e5e4ed11e6b98b6efa07714df87e8984098bb71a2da50d4b734dd59492e798e60e22fd18bd74b6e2d4c8564e66e97c6c988ee969667efba1200d4402463f9e3978c4b42b73b193d09bfae1d4a0b96aa171232d84a8d605027e1af6934d9a499f48ca1d50ec473b8a512587970806e92509b801969afe61579425902a02d8759b8a5cbfcd24edc6fef0144ff503dd7f620a551d87582f23ddc806c1a95214d9f8553047887f7f3014a37e3b5a492c720be0b6fb3c78f9d9b49d9d8d23d63a8f9b03df531d343b0d69c607750ba8f9f3ada48845c61c6dc6b15ab551c124fd143f117d33c215c76073f6c5549955fb7a35104766a600fffcaf241ced4269978ec709ba0630bbb979c76080ecdb38e35ab672fdb462a65ab5578668e09083118d4bcfa62844f2943ee18854d1a9f0da2705a7e802c8f8257eb8a2df1fa2576bd897b214313de3b61a45cbbff6caf93353eb4dd1b3535327f86d915a78cebe996dcba88f0555f29a2b36c1e5908b359958d1e3ee8e7d6491dcf5e9641c05e699e119f1b5e6b36a016a771426e9a17c0aa3be45aac02011e99b75b466799fc2ccba9e104fb2f951eeb2743853dffdd5b646811c8c9e7bb4edc0a9108609bc7f88a5519e7670748a7a28719ee1167939452b54fc7966b0f2e5f20d15ac6189710449d234388d5d93c7fe7edeccf225168ef594a689797842a0ccbfa728a96a23cf73be886d09dc4187fa7fdeac867e3202636110f7d8ec41e5e289bc184116e42693c224a4e58e63a2425ffab9310ab33464160d452ed66f55de6d4f99e7908a17437573bf7c39e28e43d316b17b3520a461c8636061e9bd6ad6d34879485088b805ad38f85e58877d8e7c03eab7c135a03a6457f2d63ef36873dc343a6ae76b1bb6effbfa6988b952029f36007557412b8d853bb373043e16ee86a731fd85fc3044d301511a169b8ef20837159824182d01a1f0e1be0eeeb62521ad8f70849678f290feb1840030f43d52bf15447d1dc7c90d7622774ca2bc344d91d5fc47de1f1712ae3d21be171ced15c8173a3bf72ec2bac349bf127b0ae15be68c1cd3d3d880e01b7c943e9d96032d38505e06623dace7114a2a6ba074bdf0b32c8eb290babeaef1a1511e2c3c1d2ceef32aec632d266f6f3ae46be5b973743509f5344ab200d631006eca462989c7a1142ed66d6a5abbb08a6cf3f06e15a510ea8273b669b9c143aefa6a1dace5f5f4919aace2a2b88628f97c2f3e63e36d52c3e2a85194290e344c10085eacef1cedf50423c0cd2a1619120d1c9284e17a3a7d0f61fb7dfdfa7bafc2832db7fd470c7286ae050b61a0d3998cfbfdd3de8fe101cfeafd7028b4fac627bf9f073c6bb12f39f52b56e058d36f2cba8cb41a9709d5d2392edc94e9f17e284d78752886d5b3e3a10bbe9caef9f304eca3f173c8a9cb352093e0bee535f07a2f6db765314b1b7588da16d64f3044e46748be0de84cd30593b6e78a79a7d22a73a6014b5d3cd1955283ced7c4d2310f83f4c2bf954b53999faa1ee10a3969947b604fa0decd5adb5094fe69e839df0af51d15525cff0ba27b27b83f244c1a9b99c6a158363151ec194cd282365c49a60914ce69440a8da952225494fe14198d489804f275c9e0e14d5c1746d214a6f412f9134e25e58c1c6afa17f1c9b26beb6b1f0707d5ea770a577d67c03021b42779e1165f7834899ec89e3ce54cca7c80a78c5a7175dd56186ed07f90de870431bb715a88efa6bf886e91e131f24244d7c4796a7645c63875a21310bb360f2ecfb995e7a397a09a30e7ee5fb3757386cbfd7f98617071937db730375eac91d6ca4dff53d1e59f45d51648baf0998e9111e7d5dd4df503a659291a3014db939eff1688da613eba351743e29f62072770e571f1b592a1703cdf01ce1694573c293f1bb501270dedf8b48d4a84a3df4b7bdebc620c461e86bd87c426998b9c0fce0d3524f0e2528595220f79295833979927b0ce89869c91186f40207a6dce9925d24f9714ab422050cc5f0d9087fd62d9e62f6597f893e9cdff893dcec346612a655d25b98cf1dd91e3ca8c2348a227eb66b2b072e5b2b5f6475b2d5468ac04f61872a3407cbf17b9145151838929eb74363731bd08b492b5949f76f9758eca109a050293948a55fe64ec787e8ceb5317cc780608d69b62a4df84afd1dfa64485b74e17cc94f8115816bec7a825ac05864b8c534af0912680768c7183a4fddc3a262045d4029f80226eb3a2dd8abf89f866fa79f577fb02abb18ac150c2b04a967e1753e5e1031a4f45d9ca957b8f2d08043190c106d18654ec099a4f09b8b226b46cc4eed9c6e6ecde7c6c01d2c27c63bf06ac94400b3b6fc3f5e6be0571a1144f7671b128bdc8c0fe259c7f1a59a98d400bde030805236ee42df887538b0021fdaac0a7d5027eb3c93d0bd8fd03b0a39497d43d0b8a7040ef2c4255dfa1bac162fb39e55eef2c86042aa0de59dcbadcf9e041646d82001fbdcdad32d4e46fe9a04ed227d97652ec3fd49a73fafedb5fd7f34fa32c795e11c192dd3b8ba345613988b6164463feb5041201003eff1f53adaa56788e0bd6b8eb7a611409199d9e40ee56689a714f532dfc65dec036a8da609df4eb08bfc4a21e7e4de672dcf33392b1f1325588dbaf99caa8a79f45d852fbb26c1af84eb7e0c420ba50cc856df1412829711bfad72328c069b7eb1292a8c4660e843e31787396f260b24d5c5e5fd45ed6709239c2ece02d136faee3e945fa2d81b93414075ceea0767731b535768e925c3778f0360f4e9c43d92af28958285ed2a74bc7720a9a3db155a701fcfac7df6163feb8762ca6a207b84cfb8ac5b11d26c413c725872045068f2f165f16ef654ba1c466c7b16aa54e46bb6a69ba554bd3afdaa741d835386e9265996d99aac9d9b1a35d8283719fbc9c928ea34830f115abc30354c604eaa999bd5768d58241caf7c2ca0694da2cd0cc846f9566393f624571790966fbe78082201c592fbc04c02c5066d04ef7994eb28d52fb1b1514502259a21825d1d65345554de97beda8a48b8906e771f0564f9e95643f5d1b35fe8b3957072dd20c41045a6fcb9b957925d9f15571429efc0119a352a59667fac7c8037a491cf3bb745afd00a0ed95cb5461424c8b8cde42cdae64de386138a3fa854c86f02c135808f3619921ac25b375431573fa42f68042ecb1021b9a5eefef21093f1e6f20b16fe454d9f23936839f849873c6af3ebc31b108ffc8176e5a739f4bd5daf13d736489a5599605493e8fb9b0679e150b9db1adfbf60850cdd742f41c24b140c4898d84ff3f2781847f04e119e8273eaf51b49d32752c38f0ad8cda2bd52c7a49259aed8ed2c64b99f9290acc9395f40bd9d7470a19980dcd0dd032b2cba8874b5833e3f088d6f96f9d8044d2e44d6f34e9df5bdb8b179ae8cfcce89e79155a9fea2cbb4fffe316d89d3776fd4cbccaa5c268ead0cb46014e3a8a8bca70e61497acc6b8310ce35692bf638be374bb1b365e4e86d28e28f1c32b42f3fc80eda63f5cc9e85f21c7cbb37e268a6bba91c6dca029757d87fed079d08984a0bbcc6a4b489e147df75b136bb502321799ecfd1da335ce33c97e262eba4a06572f9782136069fa006d4c5a33103871968906c3348ed78d3f025b59e13b70440d218fbaa96e8c8bd6d8b1441200b10648ac77305b5a0645cd2036ae8eebf760089de8d480a3c3106baea753d1c6f76039db4ea53b77e3c2ecf85e8766013eb8985d4404006533c0ea8ba0213004ef3518ba5b3861c8240110f46935f67082f9371a1dee67a73002d85f88326a91e5625192f55016c411d4e99882119e600a27c0051b427f27cc1bce45e6a601459def4e4972f252818b1a85574badadf8fed808ce1663ae37f8ee26725fe90c5391314bf3280983d5eb31205906d52ecdc8eeff9a09fc6008e10a254a4a400f3736214606ff2a0aa7ce5a1f0d79834d166fad3c368726db03a8ff1d8ff26b1277957c3e9de5189eecf4914bdbf54595dfa9b512b7aeb15f286d9a46a3a561188bcb05637f16b7eed74cffabe0b2f4c46bed7c6e8cd7dabc15142d71778860424b74a0e9c75376513216c9bac59aac20741f51f63ed2cff097eb5dfd698306287d7ce25dd02d8b8837e2cca3c1db0cc3355bf696fcf301866e812c84631e545d596b8ae35872ed978f9684c13a30d88a497c9775fe1a5d8130fb38e722ecc3809f5418047a103d7e0be4df4d9a00f9af2d0c794c4ea95b5b4ec026402bfcf6cc9906033be7169b2f1d96c3ccde69ccb7a5f34c5fbd7e9a6fbc5a67c19af8f3b63e9081318d1cc148f3c46c0644861bec34df2a7538d2c14b48c7e472f434610799fa546d6567ab09064bd51a9951d0020b7f5c8966463cae044f7911b8661a0716da320ac52a44f5d25e358bd4b15b3c99c0cda78c1544b8e8b0a5e443bab21dc79752ffd8a75205c98c249c040a8378cde75c345db310fcd4262e09403af4c66c9e03068fadb2bb4b20f623803251d11d1925569fab6a8042410be4096484cc1a2248f5ec33142ba426309bd81b5f6775be507e0dd3faf7156139d5088a358110e6bb181abf8ce3bf5ed479814ef07258d669d3384d9bafd2dc29dbdb351830d4bfde1a766297eb8f0e62f195468ad576d6444b5e7cfbaf7fb114d6b99e35cc77b144c7a50cb092230f9052d273094fd9a422beb7c935d00caee75ee0e7b04d32d5b8aa86d32f1ff778d433ee9f650bbed0a8086798f8d6c01c0751bfd252c6921c52d30e6cf1f9f84adac4e474c2b202063eb41b58578c046452381b4f101813945243ba22a333fa1f83fae8ef057b0a55644f88a34edcd400af8be20c12906ceaecbe79e57dd0287997c999a20058843a5d53e624108a393a303de50ad2ff84e24567aa8bb75d4873b55222f0a6582747d440c463cb17c909c4b1ed925b240f926833fea8f45c3f6892cbce53362043e7bec62f6caac42f04ed830feee2367a104f1598e65dc0dd5699fc1d2420aeefbbb1693c031bc2567e2783fae561ad6e0b2eb97b3c6274075979377b0b86e061250b0e1c85238b51e328f738d9d44310268442ef85a073f9fe37977f2440d41b8040b07c8d43e45298a8cd7b5f529bbc6f4a12e12194bc09d8d6d393fd9c74487584750e5042d795c149966ee6097ee29467edb4c59c4033c9c0c15ecae43c48048213b1116ef3880b73c2850801c1338e1901ac09ed5d4e9d19e2e123cd5fbd9b8f4a2cca38748da5d8f3b9f60b5bf4c749453008922086cb4abeb841a53de18217927d03c4b629bce52c09367defadfe9f5e321d933f6092974474dda4a660f6d76de389a379422ffa3e023da8b6a2c629095d1c8968533257f152980f68034f297b96a111838cf39cd53c47f942c292ab626596ce5b68c8a82228dd2ab8fb8ef2a204750f20d1a273bac497b421c9707846c80769fc0035c82acd289ae895895ccef879da8bee92e14b8da03f258222106badc2404fa10a67376ec7b5220f9c3cb362b1c9d4a0f9c9b85101e590f9d7f1924185256785e86105f903e74818a2acdd6dfeac7a71aa06f4e4172a752cd3dbb41e6b0457c22a358dbc347cc580d58230d3cfae6dbab3bfeb47e466fb0a08cc1eaa8f56d4247b285a60d1e83015abbbc6d516bc1add5893e38a2bf696bf1551f08253ed08bfef12fd5a057eeb0d9891daf13047e2f752c3a954a27c6713c366140eee92cb7db29f0b551070ed68466eda856232807aeda6c9576ba0520992ada2bd8ea403694f1bb82e7854f40f8e519412b1aa607c2aed00a1a415445a428da8a87fdc50f8556b45f491326b3eaf7acea7747ac514a7b505ce5c762cbdf8ef44dc0506503b26d42f4fa0eee5703326135ae2d998e8a953da72f75ab91e3ec3bf89de990e98e4af8c59de84a1fc93db35fa9c82c9a2df6d534c06549e5f1a69f6b43502f08d65c44d7485c37f33a6edd7bc0742d82b280a01015a39152a43a55589693bb5864e23b08b4cd58d5b02aac6cf2f1e60b897dc363234504de74144b276279ae7a837aa11dd5314afc76ac781139828da94e474f7200885dc0cb928d57656f83321f6257922b341990ba8ce938184f48dfd625f9be5044061e8b1f68be419fb8d10dab8381472ff840c97bc3b747e7e218e9480a8aa77a0402638ee64b50d1c029b85347d3fc6d8ef76f0cd94872a0cb9ad6b253ea86fcd519199905c6c5f352015bc38a99521e327c8240f8cede8358fce404138d7670f5ec7ccc67d1460021156f919187399585ce01fed32db4fd6c4a199a8de09cce993883fc8be3db193edae7be7deef4c2d85e61acb1f61ee58b2cc34ee4288139c1914b6962651bc22378ec68985e1d55173fc70b1cb61c6d54ee7352aebf93dc3ec69258a93e2f15639557a358b83092b63b3d0c9075d472620c3cd9de98ac538638cfac250d2a832c36e46f79c7ab0a42277029d8407304524bdb145d970d182fbf9bf8df9acf0ffe74f86e9d7667562bddd56dc77685716c709ab7141fff177861cb18ad52e5b8f329749b117247c26fecd9d014ce4004993b419422ac915692c820dcd4012ca69e147c0a1909e9ab0f156892345fe8314439ff85f02e4dacbd6f395806d54d0594484618297f556f5be14c5c6d12cd10c9c87d584bba7394b536e0ac2cfe342a265db7a18f945a708b75a244f9453b7afca969cd7536d852627d63116577a63e5f8f53ad02de048b2bf7070c53ca97ecbed2ca9ad4c4daa505877d2ec0147f77e374c96b3eca1d5663769ad9e71293bae7c2541b6176b557c15372bf9e8e795addb3c3b3bfea44ffb937d3813f2211fac3caf763f614011b7231a3244ef472016b7d6723e26683a7448dd03b96b580d44b03a29743ebff76282873274c5102613081120498dfc44470dff5047154aad08edf077c8fa26250b8513ad00450ab0d84e0ae8f771554e00819bc4fd153684281716f1bfc93cf51fbd27ef8244a43fb45966e9b766611c229eaaec219823c2090d8710f1eb2090a23fc427f7acdc61f415ab86687364a602feb6af6fc8dc47c7989946a02055cb186934f497f04e06360f4fa5294a99412ec86b566cb53149009eb67953fcfa9999edc124d2cc287465da746c43b9effe464cd6f4b51c98f8395c00c92bd3dc9ed99d1416ad57a5a4a47c8d8dc3179105fe23af4429a3e6075c633181c18c07ff4fbe0264df6263e95e518f923fc93e86f2377111fcd4bd80a0dd09a3d985a9fe65cfd12c777cca039c2c3ffba120f9aafac6576c6921ae763795da2fe9d51d4a219285a12bd20bc6652eb8c25c0048b48139bc11b6e2fb65ac0e7d360ebe4609000d3395e61e3e0d8897af8462e596b75a28345a00371c2111312fbe34adaad125e75a49cc620588fde8c2a6c03054916f47ba5d3ffa878a127b4f772dc2fe851ed7041627e3a078039542cb89a2f73c9ed5ad484639b27f887dc235c2561cdcac2c20e99845d7d1ab4761e9c1aed19c6fbeb23d4394ae3bfacc13ed2176a2ba4684106b3153187c773e9dcf2be4a98bf8fdd63b25ecea843f3d9ebcd3e15f961581f4e69850a75551d3883e8993459db64638e3d6444d7986f45fcf18d2b011990f020bf51a78808b812866fff339a5e6ca0d0b3d47f3af89e56df7ec9774f847fa6b8b3c1e15b44690b50d4ca563edae422c350f4889167df019616753fda91bdfc4700f02cdf1249767ea4a3a12c82fcf50681364e69663246be37df4ad1afe6998d24081913434b1742eb3ed4aa34054931d6672cbb1963d16ec929c84f2fcb243d21d4d1241a802db4f5d9df145f9609fe7e7f9588dac1a5419f3d94727727b9c3becbb47989187c0321089acc41134fd183e5dd2cc669e06d036132d086120b3a4d27c2f919da0c044af35e9647a868d9c8b10ee358ca784899419d7e6509a615e921ad28022fd9e75fd278bd68927abfd64aae8f81f0a3a187dedb115edc325d8813040ca9622520933fae4e160541f717462bd34161645c85329e98954766b10fef9e866f4790e031e1eaf90c0b5507b8514dee36d4b68116f095bb7523af69aac48c41e2c7bc1159f1431a6afa984db0f94897f1389ad75469a44a9cbd44104f61f6d7304e77f1dbfce71e35dbb405ae2d9b76582f41485d2d423b5322fdaaf52325109361310fca53aa5108a7c3712cb0e43cbef287bb6691cbf458fbb5a7d6075c13ca79c43ebe1d6b767df198f1627451507e99bb16712f4af79e8a5574d9ed5e97a767d6ac6117626f5660875ac2cbee60d54d8e8f2e37d4102f82dd948629f09443a6fc404e3c6644874d79fbb4e859ee4c06b9caf433d7842ff0a3a2a435b2b4af7b606f3810fd298b9a2d97642770f47980c7e80f5beb59d0b5ca7a07ad21f25674d04c2552072cd034c0635a3df391db53c270908ad59457f718107115c2193572199a777054ef710961d64dadf3f44bfecccc12a8aafe073085098527d93f7032c45203dda1f2f6fa313ac1a0c336900f043ded733c4b54279b68c30809f6537b0c4fa7d9041160967842e7b7a98deaeb8a08e7d28ae3775dec32472f4b82cd1c2ad902a5dc2d6815f7fe024134b7b3c0e4727a214976ad1df75ec1ba749d6c2b4a78d15d423542ea56f1fede80815051b703a29e23bf87f90627f9f320e64aaa5d94adbbce98648303211f1510803e6f95bcd9f17b18e8ac82ab5bcc629ec47e98c0088ab1aa10d28d952f07a045cfb30ce1627e991784fc37bff54c6c95f7922b8ea4eb164f466a6006957684969fa1546662ad8b43d6e70b4f79f55f1689647605e00dba379f37952ca94bee682768962a03ca9a7a552f3cd86d523da4a09f973b3bd98b6dd05e1c6b50616eb1d2d846aaffb7091515c882c3526a7739893907df363cc55c7bc3915b3dd6e0bf8fffd626266052bd63bfb7a1c927aa793ee542ebe60b2eb9104c4d12903953269b539325925e9e516ec5ccef9456d6d0c5ec04d06ea541ebcfdc66a095bb034e934f046bb20c1dd7e1f96b72ea51f9b7e97270ddfce983a519c513f7d13f642083c7f53d346db430b56e05f642a1cac74109d7cd326975dc6f4ea14c41fa8e11fa73b5ad60a631bcf3b71b0403ef68bacb388fad9a50c58225d79909f2505a9ebe372c50d7077730fe93263dd709c8414ed6313514409f6a338e02e85243f0a5a064a913d8458ec35f2ba75a6163540798d4b1fe2b0f851b28ae12fbb022f7095245ebaf70d08e80d6c7f72a12cdf4566375606af479e01ff4402e9c924146a3ccbaf5f19ac2c4c4a58fa753396658c435c8c06239e12d98b4ca370ab78f528d9f1f40965de9d7b0762a774e4e061e3d71188acb5382fc7fbb2585d5a16ee5ad61b2f4ddb221f319492710b44a7641353f0984eae35b79bfb828f29216ad978e605b24cffc86d380a7e9dd3e46351918d3f499b04ae2b43f5b2e47397ff5e0191c954fb02710cef75015a09e348f5bc6dc2f095db172e8a78f737c77551aaefdcb278f8a5e71f8b14f7d57af1597ce26cffbeeece632b838f028fdf7c469dd605ceff52063f28ab4f46026379e5249a270612e5c7b367327eea3ce909bb25f13262736b5f8041064f346372a761dcc6e63af8ae2aa906d7faceec34d139f1b39add12f4d91dc7614e76ffee681b2dd3f9e8b6163a6b450b75d91fc5a3ffa93754541a5feac3fa60bc1b945fac070d24651e4662716845ecdd4cdf0c469f6952cc5e29fbc23543e83049ec307c1bef711fbe9dd52334de40abec1a786d74aa5f6717acc5fd93b6741e915d5ec24a3f64e13498a22cfd0350cb83521fa38fbde3393b544a9f474b5341473bff7288c3440eba4f63b404bbc1d9829fcf75def1de5559892d868db80d5ffcf2dcf05346a9c55524d23b17c30b7cdc4d4021a24186f43d929fa34553c9c5e3957535acb199e9d8cdfdcb6a89c71c9fa4148f90bbb353e68c10ed6f7d293103b056a438bd5dd1f2c9e6d40ed908ec54d693a7a3da2861ff5e00e9484adbbb03fd755c3484a55f5851c3653f3ecc3cc8836a28bc9b5346cb6a8f28103a7904038e55a473dc7fd3354e43adf2b856314769ebb2cbced51dc1a124252fb7a735e3ebe614a1f6798fa9e05b4c76df64778a5d102a794b850fdf74a00637eaeb6987d4be2be86b6d8bc7409754f9c20048deb29298360267d43662a9d293abf4ed72f83ee321cb0168b44d145d107f126b1f0d09ff4bcb91d38f171b8eabbe367f4805eb98c7e1612004f422e81c061e0a253c07b8c976b45d8b9e0ad5dbf185f482ab51b622866d90482cb285bb6df468dd8d4de8f6137d7993e1d30e4263061cd7ba01e5001cc5bb79703d35166a4d61cf0d3f8d71c4a632cd57f9c34f90660ab8e9027fdc800a4570e56904d10a237371fbb544549603811213ed6d5dccef0fef161164e30097e1d219ddfafe9b52cf80f3306331af9a71666e5badfd8f08ef29a4367f446cd222e7971094130726a64a3f3b6f968ea22036f7dd13231b44bcd9219e29e79e08d210c41aefa60885f505a8a837d4c142b7fa83f291d6b754698845936ce377e7ca4e2b780b6ec1f4518723b0f55ba0e08e604ff5fbf3fcbc0d392bab52173c273d07f8cf7ec0b497c13dcccf368927b4f9a43d5b33a9853bb295a3c60523cd7ad526765d1bda09e822aa579943b4f4dfcd0f9e57343bd8e0a046a450716b3ef380117e6612a197b5193fe7311ad01f5980b3a2d2402cebae285a7b26d8eed208017fd395345b2b98ad7425fd9e5702f47b456ceb45ae3d54ea6d2a3a2d9b5d17df110ac66479d870c3ddfe75d852b43e0289427a616aea845a4e1d7cdae88a39633af13e0042a2afe85d83671e8dcb13bfa1b1311e50b45801e28d6d7c929e3e68be580d687fec14ec6539b749a4346e3eabcaf5acaca5bdaff621cc05e604121a753727710d3008b253b3355927ec0b52f51c896e138bc46012d7f1803b0315be7d405156277b42147e0e6419eb85a73ccad2eb4d6bd19b21cabec393ef6630c5bdd418fbaa922de70804dc0ef19d740354eb9432fe9e7cd763e90f069bc03d79e1f625686fdb52a7eaee21575bdce5e79096f95196faa255e59f1dfe37ee82aba39056eddabef0093e0cfe5467e52c1351cd030529973a1e88e29e43b83a6d6413184edf755dce789faac938aaebf09441e74bdd7b040a3f030b9f6f45eae9ae0eaa8b6e9a35ec2192caf7887c580d1981e19d5b2401910ef4ba6dc7beb6aa15bf5f90c235443122b3bc7aac80840bd72ad7c5a73e408e7d276ae8271d6ea298f8f57673d16783b7bbae9c6b1cd1fa72e38beb91a4835a7feb3380858990e46f161005629641ab237f12946593404d161c9f3bcf3c8ce3570142fa4939d7ac18f325e3bbe07a887484bb3170afbae3d918d494550016cf24a114f885927df2e25586043b9e96a5b7bdbcbbabc78803ee24bd7b4568b097f2c09301076cae1505d38f3bd7f01deddf882f9a6df16223f7c0b7939e932636c2f210b141da44585567de680d16d3133793a4904feb424c2016ed8332e4c364d9e6b1fb48621c38178dbb8e30360d0248060288ddd5adb29f1ec5668db92841c9c859537b52c05bfcadddffd45755dff5a30283938ed626c8f0b0994556a56413b03f50c8be2d86c0e3189a90665d7190458930f93c16750a0f64eb8b452bb9410ffb90230dfa9dea4c8a5aeb8f13630c7f3cf96995e8605df4ab527bc2ea03be834f26b680cf28d23d583b117b0a5209af2bdf2aa7dbc27d4d659462003b1c21005c0a334aebf3ca84e560fbf53993b2500942dabb4a5fc45a5314c14a0d2029241111bff1a99b131a05094d990060a4413e882bee5aef456a24469d88b68b89ebe66fdda67f0794b992847498b92aac130673c1e34c675849fd86102d21ea8b9f01468b70b148dbf13e156e8e9404641402fa810d505b30cc4a4a070ff9b1f4eeba4974c10412dd9b42cef4164c2008296c2608b73dac01090b81757591d97b9f0e0b7a8bedd6e0ed6af85741c1248ab613add39c383527568c3cbb926b4fde41f0517a9e9e2f09217b6fb2b79452ca9402cb04a504fa040fda5f7d67302fe5f93d3ce41e3944e6799f9f2f8d8093e7cf97a1e83ba1e879e7feeb2545863c9fc7c9ac4954f56a8eb9bd5edcc91ccbc6058167dbc95c087c42903bcacac1f33edfb12870bb4c5f64bdb4c1b1e6b368c88d95a3e759dfd9fa1669e7e7bb786ddcaf3c3c7876dfcaaf9d106424fa3b12f82639fc00c51451aa30196d2c1c5cdede7a9beb5da1b8ca62fd9d9e505cd9559e2c1b23c8df8babdc8d207758a44fffb2b0b8caf529d244aa3f822c99f2f6148baeafbf05c9df5fa7fcbd6d0295b7ed59eff584f469f8e25921d28bde60b5d5a07f7fd5855c08e670bdcfbbdee7bdcaa20f7b8a641fc9f52ceebb900bedd3975344a2dfffbdc5200cd750caad0771f4bceb7bdef5ae8ff3097b7c42fb1f7d185f438b91be7f9e5fb122f07a1ad3939f4ff87db34268a3184ab9f52680f12b78ef4634e68b1086f47d679e10e97b6f071781395cf807237dd81da024a5040e50aaac072849b1b17dfda649b94709f24a29cfce7b1e36afde5b7dcfc3e61d5b52e6def5f5d881415e7d7b25c85c87574a99c3627dcb7d15cb98ed3bdc55eef1e909891d267754189930b94bc12448ee91081988728f4348652119a2d0cba20cc912cd903b821c3aeafc1dcc21685cf15a842b3b93e5fff84495e5d3ef0b02875cff1c89f7f414cb3d3e313d9d004a9d9fdc23143023942532728f4fb4c83d3e41e341eef109922728e099e3a635f53d51a5e5a4079e18c6225680d126c696d18993fc82e51e9d30e518e091eb2fd286f9bce879f1b880d1b23fbcef95696e56bf86bcce20789dfb47bc3f955c09c50293dcf93d69aeca3bb67c820b96cb9bac136c36c1a7f2095f76137c96777c90fd67cec27167d9fff3305c05e20927aaec493373b28499a1b4cd647aa4c3b72f5a0688322c11ae08c3410cc9f2633fad86f21a03cf7f4a8ed389da509d966c9c8d0d27b59ad239793f005932e49b62890cceba608e94b5c84c25809b0370d531d7c340f58b7045d8ebe554963ff09025d72eba2e9e062eeef1f7f6b129ce967b6caa1a9bc43445f58fb1094a6a6c927ec3db1e23cead6fae0883c57e3ee58434cd60b1ed25651b9b0b8b8db06dc003ae00cfdb97c00385ad695452957b4c32271b20f79864895caba5d473b362c4b9b33afe62b8b9e2d57144015780e7fa236cc00372487e4d32de215e4ba417adafe3ac0a18b835069a0bd45a636c71abaadcb0a16e68950f77a23654a772f727e31c743f0dddc9c948e9dce775b5f67725165f302bfe7450b65814726bed0bf947cd5c91662afe644ae57f6f43fef74a990b452a23dbce7e40465e61eac639a09ba5513cd98641e0b89594527253ceed7db33c2ad4ff78c794565e978e8c499c8c48d4c8a8a26c9abaa155b4aa4ae765dc54dcaefb2973c59f4c4519148b304abfabb99b1ecca7ff490fe4533ae459098630dfd552613eeb5590dfc24ad90b45ca0ac5ee2df7cdb2d1fdca0bdd29774e666eb693148dd73f9e277a58fcc9dec7e450ebebe4bec232f75c0caa2b06e5faab0f1272f1d5c5d79ff17d3ce1f7ae56f871a1e8fd7ceec3a2f7f63d4f28f9c6b35fc67f93d967ecf8a04e1d3ad92bcaa252903c579884a22cdec813237d8fb4713befee7af163854561772fd4fdabe726910b4b2c72138bdfe7f3b966493a06891797fd1d20d85fed10e1bb1972982a49c931b2920e79feaac5648bc5fa2250caf5eb98e983396620964b691919b2487395d288cb665d717777979387274186873142a040a5e9470c5bb030e1834c950b0e16413411f383cb911d52005a2133440e40ee918c12649218c12a0e95b7e7269184656f763dba77bd17f6683aea704fa0ed5ddf4386de3dfda3eea93cfa7ee224ffef59dfc277026d2f5eb973dfe73fa9e9e8c5cbdfbc3ff2f7bcf75fe123d8bb7092eb6158c704da1c27094da0ed77de07efe0241a13687b4f4afa09b4fd0bdc739be1f7ad9045bffb23bafaa4fbf457f868fe87933cc804da1c5f9c54c204ea7052d35137c341244e9a394e62bdc4234ca0ed935adf13887b17423cdf463de59bd10cdb88eb09939a74bc70921bdddf799ed045d8a3e9c8c3ddf7c8c0881d7cb13b4da06dfb0caa5cdc23a949026d54df7e1b75b847fd3612ddc9151ab13ef93e672a48f65ab66c190175d89f801bd5ffd95c4a536e4cc638856123276fb947313ce0e0d197028b1819a498ca20207d607e2f3924fec4ee77bf842e6f9804ffe9383a12baec5f14264e9612e903944c9308afafa8866133234b122899a659ee09054a018d00a314814f7ec0275d1a44a2a868dbc6314a868a4028dc0b8c97303ce455ee31cc540d70c2401581f192eb0b4945e5fa32aae7f2b7218136f27e6f9cd4a40327b991bfc781214c3c8119c2fced8f264e02e246132799c08de4aff0d20a1f18cd07e246f2392a698b46874b4254ee7cd97484939ac010fcfb8ffc1b2735d94fea09846de39f3ea728d43866c83885043c028c12187800f2b381c981851ff0898250acdc23982b609ec62f58c62f53be3ce5d7cd3d7e51b2e4b5f4dc4754a65eeba45d296d4a29ed9e5d69b79cf49372c7e49c03fff629e7d396dd32b7cc3ded5b1ad2ffe89cb427a5e00ad7e8400edc28edae3b66ae5d670d2c25c6f229eed92bf798dc3dc7fcd9c6657777777777b774c2a3df945399be94ae00d19f07e5c1e5ed5ffa9ca1fcb67572b6e3dc662a876aadb3f6dcde821bb802b9faa0ad74caea5e946008c8a7723bc22548ae4fbdaeb376dbecb66d9bddead3979fdb6ddb3ecf1e0eec529283c9d1af769625768f443fb95a25b9ab97489ea421d7bdf8a2944a19d2fcf261b5be2d84b9bceb12959e66b32c82d9b359deb009e32cd3e7d14549168e1967961b6a96297ee11ccc77d6cb3998df5664dad31bcc6f5610d75f7c79f3eb42cfafeea0fbb9fde6e26b39f4cad1f17c5b0802717df27cdbbdc16be69ed70baf7bf5dc59edfe057a67371a8adc831ce881abeec1da59ebd4521c9b97215daab2b322a0f414649645a024c56956633a9965ea348b33cb1d6e7dcec17cd0e93a930b9ef95ad1def9a8248249a0f9bd72cadfd36bb7cbdd7b57b7bbf77a9775bfdb3a6a8918c8222761b8284769d7d1aeebc2ced4eb3617e276df5236b1b4d6dacacaf1bdc5dcc8052ac36ce879a092dbbdd73d92f73db8fdb6d9ef40cb7db70a416f559da3f4abff61ffae06ce9e54f65eec9e7aef350b478729d73d7def914b55f6f02ad3708837e79c9c4b205c66f5674863360b049a44c5d58b76c56d7652c73205a00aa6832ab82ea59c4b8420912b5fd699003a9f86dc1c407f922bf95482a2922295dbc5cb014265bc96225cff1ea54cc9df065ebfec499ecf1931cbf3bf294e1de6df59a9f27c4e2afb2be7259c04c935a22bf71a1772b370d8bc0300b769781adc1d5e278b4ab9bf43ef270359bf2bc3afd5c886ef86610fceb9b5c7847f979a687d4ca5a418833b9f011ec7d91767b9afdc77398e7b6a69bde06ebfd454ed7f100b6aadb5d65aeb07552b67ec0abde0d5f00c82dbc6d1cafd0bd6db3637133c73cf6123ab495738c65d6aaabf5ad5caadaafd5ab9ca6df38707c3d3409b173ec852ca8ebd27839c5caf0f56bcc3f37c133ccfc746a6dc2265f61cf2e75bfac3c440c6384259e9cb2904f6e32365f78b23dc7309f0ea9fe6b87b1df3dca5a68aa9947d1201f363b32bffc9eb1fee76e267f173d7405d6f3083a46c7e8ce98a41797e90908ca2192ea09878353cefdcf93ddebd4d3dd75d6a92f33d9092c8256e5d4f80c7ba4d3d6be9675f84853113dc172c46ff2595f3bbf5979aa64bb982172372bb5bc3f36ac7e36ed3faac1a32dbffac8cb970c52fd7afa1099eeddb7723dbd390bedda5a6f9db5fcf51dd414bdfb15ff36fe1d9bbd4e41201f2873cdf3616042cdd36ce5bb6bc6511afabd62c65cf3116ae7f901cb2380cc544e04e80ebdf6ae66477281e9db7bfed37b299937b64c38600b947364ff947ca62295c31f6230bf22c3f2a43bd2095253662bfd2b047364e4680bc91c5f2a6e0f95d6a6aff6ed56e9965f6daa56c49e9366456b2ee64bcf61fe612a5eca016f703b7b8fe548a9e2bbe5ec39d544ad8cb6d95568074403a2f7ef793e14bcaa663f1cbb2d65a6b1d07ebd86af19469ac745d91e6986f7fb270d0b75894b5561c775a91530452c1cdaddeefe5c31269844449d3e44996628b3837ccc0020d30d815b268355476a626cda866cae9c6d929d57cb1639a2a3e8a04068b4d2d9e1659ab955cd55cc9550bb7bd728f6a8480d1d2bc4075430a56a298612452031237462d64ab06060d6c0a5adc90821caa36725ab8026631844e9a2ab67873fa547e851d10699aa8dc69a0b24f77e7bcd338a519d2d2db8b37e7194c375be994a31a36947280b33ef2fb732098024e13388c71820a22b5ce1b15eabca92255431146fddd01304a45700063c509ca943674684cd49a4061068a0c3c406d5a500519625763be4851f9c85eee11cd152ecac8fbc4f494a04f239a25f9858629bfa4cc5acbb21cf7f91cd1043963457e716ca6a0347853ba5bafee421e353d37cba32611652a72fd0a8b425894b12c16619ec451b8855b069b4f9926912c2a1292c99c26d0c34419848929cfa7715b944190fc7d963b8bfe729689929a1c474d208ff2b13ba03febcc679ee433a3c963cbd6caf759965bb61809048977749d2cff84ed47a671dba88d2a167bcc93c86772b23f15b72c5b4ebe21876e9c91ea1eba71464ccb3afb98c1d36d39b9b3d85af254b324074b9d19b08861349acf4d22252842ac89f2e2830ab240319a4d9edc2c81b3a52a89d17cd82452eac1c816467a8803a70d1546b3891f3268c9e20d0e2a3461a494430a5f842011851c2f3a18cd179a44181c71051152beac90c51aa309a64a881d84e41007093946d388931c105ecb3a35e5540a4669eef10c972cca2410a89e76c62ea594864cffcb22534a613748067526cb19a83b8bfcfc90a1f4a70c38459c334cbe806036334ee48ce1c02f677c38c3243671850a2a539c3863e60c0c5c9c0accc942c48920b6ecf0e5d2e1e836cd94214269862e638a1861a5c9941846336230a85814c952059322a844c1a40e38e6c7cc68060a2cdefe29e104470927374a388591827e53fc2818e6a353ea08129e7e48aeed394515993a63191f8af017351a6a3f34221a14b52c478cb54d480a121574e07282279e3ac045072955fc58d3c3134e744f461a4cad4d0d001f2d035a4b01a569c98a2751c6025a3731c50435c7814eed021a550c35315b6a3e6a31684fbed4d60480c60343ed8605d0c0d43ada0bd65acb516badb549e6d474a865510bd393009acf40eb363ee56f7c684fd41a0b3aad45cd862466d0b85f6129062159ac001243511935a30b678a6081c18d940b8a11d7d26ba4d0bc4b8fbe42cd4ccd4bad0791227a0833829912d56d96a65ad072c3111e82a8cae13e39e22ed8a8e2429228a46e58820509e81c5106cd922e41c4e0664d112a80aa751720da14354ba686009a2c008d4badad90c19146d4d48a6854d45c10608746c651d0522dd4064093b252eb2cbc507102275968b8220552d638611145992d3ebabcc1c289923a53b0e92da238124e55a7875a9176a2a72851bba276e5041fd09c0920352738352b6a4234166a1d270ca255188d6983ca467ed482a8722826876aad4f896c4d9e93326a297d5bab9b20b956cb24d75afbf5fa598588158b96424d06063423446a9ea59170c2a9b80a401880fa164578063e68a993840f383410c900902b3dcc8132c50526168830f282e64b5a8c16430e4053525353e3683e0880e658dccc973e62cc12b45a2f11a5444d01b40e68676a2e1a9b21b2a8c9a9c5a8a86d408341635283ba52bb34233aaa8adad398f0aa20b52ab534496a95e601adaf7029563f2cd1a6891b14cca9d2c6a7c9155a9288728592a7aa2c2e8638d2502815b1c589599337545af0c144448c152a43ea6059811b2bd95a21908034341ad0fa8d3739d268d2ec50dbe2a43644c3c2ab9873c6a3351617c2854a47e1296837338f424e5785f131bd825a990bb4ccb88089eeeeee6eefeeeeee6eef972127650405fdc84f02494ab76dce6dce39e79cdb86c54a6d8541bb2d6533beeeedd3974f8149e4bd2c61f55eb3386edb7cb6615b69d7e35155f69f3ae4d0c44e556be887a1c31567e4f9a247b9780b5e83177b9cd1809d9f8ffdcce027fe296b28f08b62415c31a8d3318942d8cff721ba039e9f7f631201c97e3e0a9328003f01b09235d454f22f3e062f1665d16d70ef57deea63514ed89eae7861fc7c0f4628faccabb32c7a54ac88cbbde85156c7f4eded46c3fb9243df5b1858ac98c796376c3f984f48830225a0902465331e174e3e4a99bf2bf4a9aec5020a6113480800e10813c85f83d0814c20ff9fd099dc090ec30f31b8c08218e8a52a88eaa7bdcc1b720372c8bad0c4126d72e77ecf5a77c7306877f5313ef6a247813fdfa71c8e053fedcbf94ec526cfbf61f1078aecc786b8625090908c22af92988d94cd50f892a46ca937f05f856e8394f9c7f139b1d08170de524a29250f51286f140529738f31b9d24dc8525e251fe9ba5226c9021ee0e2719368a9698b3a923726d10d96d0e73d8d16bdadef3923adb72dcc79486384b00453c993120a0b4819517fcffaf64ae80e6e6e59910574c8a12f06729bb548dbb7bc64eec51bb925bfd68e0b9e1ece0766dff517849c0f16b9cc4919c78385662c733f99dbfe7385226585620b7f58879471ab304608239c443246c4d654390e96d6033ed276999486eceeeea6ddddcdd16d16f1ec6f76d65ab1e4aeb4a9bc2dac75fb9893dbbdd871f2f7f9e37e0bc5fa9a9a42b8cdc6a25c91f319adb05893bb9ac3ad5e7150985244c82cd7e7f224eade06d0099b0c229b28ca48d5f3b8f945d9ca20da796bedc3fe47d63dbd6f77c296401cf7ac2f84853f212781b81f217bf4bf9abdefb04ccab80eff8ac35bdd3057a714df775dfdee0492ddb6fa9810dd0bc9289a715fbe2490f7d2fbaf4a4ac5f367ca3dae81ca0191f3a8f4ac9765352a19b211040000031500002010080544429148349c2b82b47d14800a87983e5a4895074371388ce21808820084611804411004011800421000318e889e07087096b0dd99a91011fb19661021d233b82963716cc38af17c28008e81cec1c8ff4c45eebab3585d7742a609951a49893e877738406461864488be2172356b181122d8cc13c2c9c1c5919d83cb41f18d781aa5b0cc8610c5992f880b07fbe71871cd6c462cc721d6d20cca44447d26583ec23196981967210787205cb3d114f0f3672e4144ac662ac5e638588d38fb2affba11967f4695908e436cc74cb2b8d81854137234e6581e193c7326df690ed1314c0ad1213cc5a8c8d65a73cbeffd617db8774576525b4634cc705b42cbcd1ca900db3a435d641b44bc01da7983301327d173f0d5ba669190971b298ea76da46543eca21979cbc8eb2caf249383c50de63435d23929e58823cd1b8bb49a8113a03b1277976602494433ed7056481cb171a61d9444729c30c5669f261c37beb891ac365271a0aae4a6a13d5c18197994264067ee3456c381b11c6e505c8f2bf3442b6323a10648e3ab1572869cc44ae0626de5f1f8c126c3cb08d14c5844be8d40409f0daf7cdf38b3258de70084f2acc82448edcc9028f44620e3337994c3e69e5d49f66832240ac76ec373106b5e5074142180cfd84900e99ec9aa006b3f9345d01ba3bf212237435de66d246c83e51c503b66060f22a263a62af71ce1822c77129343faa8015ff72ad083731c53217d2e7c829711475a0ef237443840bdd1bf0d3707b3728c9633832c966c406343cd1c02693d2bac080278f6a97438fc6d432cf60cf684d01c6377674a45e91c825cce2456840c32c3070924bbcc880ed55389cd520d92f07585248fb4484cc9896f24b03ef322ffda35f77fa21d23a4439d15456f30f95e70474f3bd35886aba83e949592f6c39969a1712d49c153beb6b34e43857f63eea3dfb36035373af9eee4e672a1381b40521841f92cc5c4269b68a5e1e1f50058592a1cf364c1175780f4e222267589758b1d1172f1ba43c19f17b03398bccee0486b0863b30f649c719244c4231d4012b4b651d7c960d8248d4d0cad917802e34e298440b0b3dcb74347d457d39c11b51ff1ae65d9b2178b3a424ce114e91bb9db089b3b336a09e47c064f3eb82122ea0cbf3c33c788f60c5332e5461d8efc1bd2df68e2b8d850d828fbc69843d4467c98e9a78d55ebbff0a73b4f3c11b1c0334f92dc365c69b4259e839636c4e266aeaa8cc867125f02113c330711c93d5315d5dd10ab339328b1b6018da3b90d4b0e048e96e1185b6ce641c2e518570ef0b7669a2522a9b3b611210e662e297f3d36833011f16fe62cbb1ce1c2318235338c22646a6643c8c0119679c6424424e40c9e8910ceccd54b88e86731e5d139c4ea66ae8c88d066868a681b1ee7106df4cc7988c8e6cca6e4b5e10b8eb170674c29a1cdccc48a057104123d737d4404cc36ad324f9c916bd1b3371147daf4e46823e969dbe4dda532279044b493c3512151238d69912a34793eea05a2a5a6e43b6cd7b408036a21f3e03ba3e697662844846166ba8878436ccdcc17e2651b3f8eaf6f64710c27c790e54c79513a87d89719c11391a059c792d3862b8da6c4e7a0c58658fc4cd5a48c1b440e328e626e407183bf8d2e1cac37707354b281c301fd46f337e8021911370515290373e4d46e5818e42cdab120b11ae0eb1d6eba077cc5685c8366ef336e795b036438e518fb2569fd13d88123d058b1114d10ba40a4c3f26487667293a051d5f193d65f2b8b0ce393b3ee82a4cc68c239f76abfe5a03b786406995dca0cf2a946f38c1acd7c206530b13b53097d479e8c88f3a84c4bdb30657dfbe691b015fb84a150f26aded060aa0e193d923e15c1ebb8b0e7629ae43821cf45b951172e53cc30074cdd8a9e85fe47ac2cbda7e954acd8262a2abee0b20c94cdd9f173442a322d203d1fa0cdc79c3dc83207bd0044219a8d194faf7c58098bd09ad568754b3c505cf3d4c30314ee074eed91fd4bb4c2d192bfdc96dcd6d58bebed043c556bd0ad5e25a096e9a667fdb0f2f2b8fd2c5a80a3cfa1d7266fadfef3c22b70ee1997ffb81730f061d0c19a6c3e943605045f79cdbb2e58303c8a6ae18fb8f4d47d798697c83362a0ea40f0dba7211e1de1cc113afb110969ade12f8b4ef9273858ba011a70594dda0c18129419326c2e7f610831fdbb26aef25f88df87ff11bafc145d705e9a9e46b92f207759fb7f3dc6d98002a0d96c081fd7c748d24a4be0ed7eaeb72a0a4fecd03b82c2bc9cba05fe023347a809916f09e6f5b34255d24423cbaec85f9e9aef30a3462e2b56d133aa3bc75f51aaa6f4a7d7a0172801f663fc25690470ea9efd1ee40d328c4cf616908ca1d37cec7f84d6b699ac7bf701d6091b94351374e1cef6ffb97231f48461f6c0cf901a41ee7765e92161700d84246bc2b105dbc44439b8a99787e1e63ee66339dd0bc47c9ba2d488fbdd5aa7d6460c70cbe12228eac35043c7d5aaacc30f7b0ff1548b75ea90c9df97ef46d92d0d9941c5930493ee94e4e21554d3b21e3acd4e4b57a86b5d29835a79b3fbed14d885859a815d4eeca86bf2f451cda44f21052710ab643a2e4b7590d53e50721d3e7004a0da3589ade8d8c44070da03c4cc3a4141dbe464a7d1b2b1e29e47e4e50cdaafea7e993a259bd35e7946a3f4a2b650879894baa08b61389a5d6a4a3d65c1c45ead7eb52ba5935c522875607292c50a81e1efe042541695810f550d2e578fc1bf5d825a3db4e850dd01d3725b4a6786cd257e84c21cbcea38381d29c8f1474521d579ea21aea764628abc365896860d9a4b8776c0809be1e133777d22ded197d94bd3328c2a21f345179ae1f81b3b0ff6414667faa0f4958b16bbee026b2ba1749b0d2d6a56c8dac85e7661a545ad1a09a3c9a327c4a23d21edc5b4a6bca3e0a92d6407562a2d0ff464e9bd5ce94311702513bdc6ad6d6aefa80c639a206596bf0c2ae25407ca6ab3f1abcc12f98597b7337d79e4cf9a42bd23cd4b995f0122d7218c8cb00f611ad3280d3b32e4ae01182bf285162f48d8b5f6d5e63e4786a334bc19ef46f539491568d014b733570b46cec57eb8c642c5cff8bcaf64bfa30cefcd2f4f496f82c0750d3ec0c9d13a87a77409722e79e0f789f3c066f46961eee7b837b3e51cb44f1847330b43a92f4926eb431bd32e077989b198aa705cb754b5342cfd798fa9ae52332ad8b858cfc25bc7aa44d66669fb80287d7a142cac268312cd46a731599a02788680afbbd74faab939c7ccf4713372ef3f274a79784a56c448b133f9ed92dc4f82c197f1d32ad2feccb94b6e34d407a8af18672452e8b5eab6442205f0dbeed9210a2501137b71550f635868753fcf8331039f561f7d2cf2bcaba9af1342146a4f377be8ebdf67aecc1e6097ad72cf6ad680ffc7185663559e6dde72eb885669a4cb694c96054efe35dc254b653f9187fa25dd729c2c25c7d1851a47b9f39e5fcd335f92be8c913b1aa6378a0b8953534961765834ddad8248dc5ea641b313bf566f3b29fd2998cf1c87c48fed56c43dab953d0978223e19bf505c5d046fb718709bc082f9e15aace00595945356931c187b9675ba296a50f981959a3cb9c11283647e2e1981e0055312ef1c0fac7ae0ac187e883950fb708f38a022e46fe3867d29fc75ce585f8ed16fcf49af3a0753477455474f0061b23269babab615905836ce1125003249e85ac04bd604105b0d7bb84f10080c14e4023cd296fa96d87ec4c224ed8b483916c4edc3d58c5aff682da2676f164dcc70adac854544dadeacc1f60ca0ec64446b565f4ac758533dc59d8db3c0c0851e72464ec6be59d9c758c440ead98d7e087cb34eb37d70d626f746ab9008e5f4e39a4652417f27b769960947c23c6ef9965980b36f7b9ad2c02bf53e96f7f02406706dd3f6576960140cf6e65183df3e44bc7ece2a933aece8390cc59e8072e16e2ce75f0bbab640d7ea59f77e70fb2604cfd8109e9835b149db65e50c0079eae1e2721fa5290eb866a9fea4b7e6cb87c2c0609888e5c10a4f03f49b1a8420e481f9fdf8a789d65e9b4dd3beea03f17371ba8a39905effff78cd9cca332084668c284fa8e847bcf385d6f1c9c0704bcc3e1c6c6ecc61e1cb1d42cfad49c2aba0245ab17c5b7b068557c169e632a54810d24c85fd0ff663070c0f64808357ed3cfdaaa12ea90ee85dc9d940cdc82d07e10320a560a7ae4a9d38ce220b7726c176d5ad42ebe0b3a408b9aad2d65593d44ad4554708d90ff141113551578960269527a40e5e182a1d88df58a69e3254b54aa7f0548ef4c4fdeec3ee88e56fb9490084f83f68ff0da1ae44f2394e917bfef2014c4f4577f4a4a28ae2cdcc9eb6352e1af656eee3353ac9787aeac67ec59cc5f612c48fb148e254256644f581729cfb718c2dab8915b54a1d483f7fd74638d77b340d7117a5ebbd17b88eea113f49cc3d61255a4433d091514a027586ee10ed161a207bc01adfdd89183c4922f16e576dacb410a6615ffc67ee1072ba28e4841ae41404d3a841f5232b32b6eacb13acde303507acc786baa1f55b41445c8c1fc7ba10068815e1d933687544cf47a5d5fc7edfeb0a6d48f20dde6cda453d1a9b53ac593e01962fb27cca61eb1925520018a5668497db2e8423441711069045eb3dc9b9aa7a9525afec0248138a6aca329d848745f534cdd716dd6c2a137a8f2390fcda328f1235597ed31ffd7dbec0278b9cfb7f9d5a2fb24ea061dbe08082894de1c36188889cb85078cb5df5bd1a7c5c89c65caaa72315321f6a80bcef86522ca9f4488e0e5d0991c5bb5b3320dfa2f8c9f81f5a7fa13814ed455ff4153cea38a78a31479de365be88326a263aa67fe3bc0156fa19552c00da1ea1216e976ac33318fc7ad05b45c5ba4aab22d2de3112f0b9eb45056e279e0da1848b247daae6a0887bf15a553aa2c9461117589d14525570e08224d2561af9a1c0561cd8d8cd4614fabad61625f7e5eba89631b3ab9ac2758dfb89312d8e867852388dd878bd366a8eab32b0f2ce4be7a400285ffee0c69739db235dd8b8888d5919690e1774d1a79fae993d959d280a4e2553b490a83e8e86b1a26612c87006112c4fa67fda2bd183eb5a962acae807ae66059cf5d52c8eb5c302880a29b719a5ada1da3689a18b23794b6c438425cb894a0fa2b417157915d576c1c08418c41b82e622eaa4823e2d95bde890ed537b6d0d8f44f2a313cb89b30653a14c0b676f89ae343dbd67a4446fc9e0683379f40ccbccc0926f4b6c78d854aa75a5698d4ae0f31a8fd48d9a3eb82496995c6274dfedbb0e8e899ae04a823106cb315b329401f544a089c26ca2248c2046ba01c47a350cd1e1f5f801a73ff6632f91e7be66d3375e35d971c31d83191cb8539278e1cbfc4978395764c9696dfd0f7468b6bf9df06adbbdca7795472bd119d2be1a667a47fb91d7a08896e33d420139c5063cba28f9c5f57e37eab5541e2fb9a952a284169a3e49ce54c7e06215c20637a7d5b94ea8a95106995096ea83f4c8180445b3fe9e426d8920a23bbb7443abcf0518c57c746189965941b8741492e009a9bbba5e85f75a9c1da497bedd0779cea1f94f59661245872116b0c3240c7f0c28dce80db161330b14038afec436ac2b81545f20d55e9fa1b404269372abd4180c7ca1685adfbf83bcb20295972f1d1352212c393e0ea120d223700fd86873010b647adb2e904067f87e72770531560a8ed977a8e5d06906eccbac9300b232ad7a057802a8cac703dd777a248dfb2b8ed9bca2cc5ea3e82ae4d0dc0d87b4eed6cd545a602563a072a348e5a66b47881cecba5708aceb46413156f2262a7eabd4a4a68f99d5e465160430296486f755ee5971a65fb362d48300b9d0a48333591b58259e6e38d871597f272ba7d63b858292687703914698cbc486ae1e38330185d616722125ef51a7a81eeb27045225b009d9b2eb511c0d94124ef18701185c91c399c449123655cf6ee3b6f1dcb5e2aa3c203846cf7cee1e30baa23dcfc56134ea50e89e567cf2c4e07f4bcfb674940757e26956bfb7a811abf803522b225054c44aaeeec98b5f5de82c377a975d480232f15c0991c6ffd30c3b77f3faa6935fde2488adb106c743f2a133f5c45746519c7502373153c1e18bb428a638d6946e7b544f983320565b2710f57f7b349d8aa7b10c29339df2771e07a81857252ed5198e1e56822d84df5dc6212d02e76a87f0b9b6a5f2f2705c32905e4c8dbb9c3562b1483f55eb3352d1a9af3f1b9b9ebbeb205b809ba8e1712901325d486ab1088a797e595d063a16b598e900cf7220c5aebc48daa667c30bb654900ae8626afd641951b07c6dd4f6411042d39086eae014dcae8952c1cf03c7b5cbd8630da58c2fc0dcc137ef28d8bd943a794c243b36effbd9bae696c1929b6d838da2650738a6897052ff0734e98c2e2211c424eb44ae82354b306aa75e783330c004933e55e79c66f9ff9b4d8eb6adb8bf957f89c8e664890dbae31e6ef7886508ebf3b33fa1c78298db9a5cc59d18547a5c005cba4ebf5b9a31929260988341da39b3f6035d6346e8cafeeb30ebb55a836927f54057095c32739bf1e6fa332eb257977bdd95f42e810b680a2c1ae445ecd5d24591c3086191c207d7c29bb9d57e6fe7f9a5797634c1eb338e9010fd2fceba5c52650316a1d0a995aea3ce871172e1f59557dccba85aed00a70842919b8d9406353625c30d41e8875eab2f1053c9edd107ecb901bc8f849e5439e0c6c0b7fa9e87a5425caa55618dd4f01152a8993cf6da0f669f53db979eca801242d2f874f30454ca3f41c58cfc1eb3ea265df52a3603add21629b6f420567c14bb792a969a4efff0d37d12b2290628d9bf0ef97f5c70bd55c13b2ad472db14f7ae3e893ebe9998d82285d8246fa59958529351bc58ac4f9b55dd95af97ac21b015feef5ef1e3a22c3386bb7e2ea4132d56b2693134572284d3635be66339657fb127d96c90158c22af1dce2e696a02b3a4f812d9ba574e656eeca85ae630e6f15e69a7cbe9e010ab7864965d15534d9a53bdc76cfd38b2e8c816f668806cd773faa13dcd7ea3731cee2e03b484012cc7e941cda2a538ab0c585a926d2f0bab71cf25530e70a631d7860991953888286fb9cb597e587e70d793954c7c13010ab3c9726cf3e2b087e9c619b956d334130c2aef71bd13ee9a806bff0364a8071ab4832455bec3a1027705a15aa4a919b406fc9da0ebfc67a89b6f063f5b282c5e235c8b7403d3a3f3b047b6ac0ace3c9cc8cec42af3e61ef06703de861bff39c56c8d987204977da346fe065bbe0304c1ae6033bbd41a651183fec6ed55f56aa6938694dce86b28f5bb86ecb769e15a5c1fcf9c571d828d8a5b4937fc0a33da9cfb045cbf3306f7ea5d951c1805e152f5ec358bd69b0bf082ae98988781e6d9b1d3596751a56a5440919a7906b1b81e72024660cabb97ccfe670d95b40a28c5f7d9f388a41a8def465d651abc236a18c4cb4949ee703f7cdb15d87ae238bda52fbabcceefa36c006d136ae0af0a48e2fc806006cbc3eb795f342c8d7ab3cbca83735924c44e0b7018b9a05e91712b5662ab2f2b5ccd5635c35c58e6f0307f5536c1677283fa912f845ccf61782b4416ad584211f108a043cde0f8c7b60635261b594d1272cda6e46453f36df86283e6f649806f31e365e193f8374d431721ddc3498fba8187c3b98f52e0444de4e6728a5f269a9b299c99c1ba8e83654268cee2e8ff130f5b066c83667c735d72ec4d15d87b42468a8dbd8ca7f2b90a1f33e917a806ac4a10037ac7f68c52cd05651f791c49941ce3b4b3bfbc9588e3f1e9430968a51614ba8208084b5388ed8e43d5542a923a33736b5bd56ab66ae2dd74975f038a7f06a2103cfe4951d10861668a2e504fa8458c0ec5ee3365188f4369bd7e2f3a7eed3609f8fc1d18bd477228fcf0bf49579444b73522919e55c3935e9bd193344d782d237da1a82c11f708f6160dc9a65c28325899814997f8106e0019c28e3c4009492a8f7c296a2fb14bf3acce3f135f84c29a0d8002fe78543dfc4813932ee0d70bbd271c8f853d98c63d31b34a4da1d08420264d536ec2508ba4bb5723903d49d87ae0f5e75c160a76a66c1d5a6a6a6bda4ca50cf3eefef9d21816e5451da157c323847b4e2cdf2a454e3c65e30301bb115efea5f1e1eb997b230b7d7822b83f19fd03c601373759dfe42e76118d3204f1e2b62f0eab25baa5b4e04049c574cd4a1ac217e24d98ab86c61b0e2725fe06af2f1e6c68482eb99492e2d57c3a7f47c0151480db14aa56ceffcff693b9b585c0ad4251bed21644d4ad98dd335b250d71378298c842453bc92a11c3cbc579e71ab42ff5caae14614e768fb59434b08e1a748dd9695f6850583e0e695c0b720d2c90c4a26b3c60d17e9868f4e28e1e9dda5de672982854331a1cd2e7cd354239381ac75a06bf56bfc0bb0513f3f1ff7f24cb8bd5cb65f9a1d1c3a2eb8f8a3900d02d50d56ea19cb07dede18c7db21b5ee6eb39592edee7f98441b008268dd75d7d1a657e848690f0984d964109e2d0ab246692fe69ad119d566cf0e911f85d4da6b8e1090e07e3408535619b651b6391b41cc262b1fc74446d9c1ddf08379300f7e338c7a6a906084182aa3a1d5047061fc7b93a20457f9907d374c67338d810c1269d13bdabf82e632897c76a986d4e2f5eb72e31309830e10157f332b9e9373c772e6ea533606735c41932d90caaa044fa9e667812ba67caeef801465f1061b41e6873e460702c1f293b0f11e615be6cb56f309c040247ad9720967efe39fe59a3c098c9866f926e5ee28b7dd2d3779b9ce553d859dbeb96611157252ee2a8e5e38af5ead72c913605fbe0e205102c9d1c4b1c34f319c571eb57c91bc5340200ab412ab16144fe7a0c618e940895732fdf587841d20e906a585d96041d75654e809ca63152308eab23f892987b1680b835d6eb67811aba3f23f9dabf7e75572a014282ac7124372e86a07e5355447e9174d7d18da5f62417f66ed9f388cc69d6db6c2c7f941c45e84b8f1f02443b92f6731a1b7284b66a5994fb33087ea167d247ac4e71690531135f6b5632c409349a7cc5f26d8922c895e11a4d431af643acf44b73c13d545ebb585425e48888c1a824abc3d54a023eaee87066c9b911ea873131c29376118a22e11279fa29d39f8c7b292f22b877fdcc41761ee2c3f5f9fbd9dc1f14b8554c9229bfd08532cabf267e3e0d3185c2883ba7bdd22e6c722ef24850f4ee89eac3be9fd6daac7e7f7e39e96aca0a45216af7548af071fed672f1e650444e502aeb18d800fdd6afcd342eeb0c264f752a1c31e793e70991abb851571ce2c7c5a5808897e11f8ff99aa31e5c5c31e62c4f0e442a5e9c7d05209a3aaa562bc7c7ce71e31f06e78a111d5a583b2bd5411980831bbee6cf55a806bb91ef7fb3d7c74358f4f8a39670a1076ab7a92b9644861fa041f66fe1ce261650a611f47711abeba4faab15dfc640c4d1347c81bbf81ba0726fd7f31eb5ea84c8b0e0f06c915ea1a1692e7a6d0d4df8223016c02c71791d8dc452c4d40782059cf7e2b545de311949193079e22347aa1efd3f060f40cbf0ee89e842e831be539d7686a01a8190c9ae32c7421e8102335b465702b97c5b7a950cadb2ac1b8f32fba2161990f66edefa2d8a3e9884a1ec0d8eda1e40739c6666202a681ff45b8926820ffe86a41009645d07530681c88307a2d39a557fe46c77ccde6690b0398c36c851ebc559e84d53d5e33ce93153c2b433e2869c97465a4296afa1b1763c8a3c190461c69fea0ef2b3d8ac2885adb330501058d86c30b8257a7c5f85d8ac25628eca1bf1e0505f2d24e4b4e24ff08757e98764298ded2b536bcf0542622c039bca5d20551b7f63ad7f6307887603d6b8d670c669195842809674be192de2c283b8dee93dea003e7bf2afa499d8655a7c6c472549d7e0a34ab819f39c4afd905dd2679f38667f9dae903bbdffcfeca7b8b9648d50e3fe72f572c13eff4d554e3889d8f415bd055251e1a94525880469287a5b45ddd4d4bed87d3888ffcce940320d17e460c2eae81d1769edd1768825577f7c78e1ce725749d3f9058570d064e7bb6db3853c8b4ce55e9b65aac2a5958086eb9478b89c8dc219f1937cf844de5899b831eb178df72b849bebcfae8d3eea446e200ddb37687ac9afa34483a4a893ee301b59c10a534a56d2563249332a01f95a1a7d34f7c03043c280c74908f365d2178747d49f733b908487909f312f8adf759ca71ef0dd4384fea12dd5860d169735c66d54875333bf466af3684360cc1178184b31f6320195a8610c77296f674c03f26d3365317204499fc5158b81fe24b770368648be9fc5bad27d68f888f68d30b1d9d9ee124ea506ca2de01e328c05f6bf61e5510bf9c50a9d37ad84111830b94e6e0816b7c8604475eaed37c0dc4bae8d6642d6f421b3fdb9fc88732b1fbe44e72523582ea86804b07ec0261fc0439ef1c9e68ca93db3efd3fa9586be0061a8094c3f46c0c2089a00726ab976eeaf1224e230e88b27d1ed50bd1d013394cde03315c6326a36a8a451f2663f3c31a15859cf2793e05f084c1290d97a5e9e0bfbcbe01ebcdd5a31dd4a049d052a285e497c751cab2c0958b89a51a36a050ec76f0eb66a0df18d4a400ed6cc3114b8eada7a178595f0a77e105d8fb3d69da482dc402869ed6dffddf98f0b3af8effe557377409cccacafee28e96c2ca9ce7507d4e2926d96c015d061a25770f049b0145bc1bc02dd6e60131df550d7d8862461526b18e0567707fcc30fe509ef7a2e3329cb48abcc067d4c4360fd325f7ee0f6a8aa72d60d233b70bb315ec9c99b6c1009838d2b7063afee9ebcd6d50ab71388d6637869575de32ac5e60463b44fc408ae279dd6d49be0e7513ba05e5a7f424767be69e5745fe85d73af877733cabd9d502b550d5c4c65223ee397d00fc7ff18c68f418256f193bb655de6661d064079591522db77d0d178d4bd157305e9776440240a5e6c13cc884b96fb97f80531a5dd442f9459da27f65d746f63cf8812dbbdc18231b5622b4ca54a8fd4237808374d7f9aa8623686fd2eb99639dd65f3fab52e2afe10b664c4a7b079b0c92197396d8f2d1f9f5818d8b2db75a2cb4599b9aa5659b0301266b1fd5ad8bc01212803fcebf687f09a56160f40016936c25865646786fc619a95b35cca58531da0044ebd1d2ea0446d1aaa7f624e7086546d6f8d84487bda1fca1785883dc86d7f359dab00a6accd7cb009b240409dd36b120d299676f9b37d91101bc7de1fd0539c059dff5ff0dabe7e880052dea9101e65185bdd616ade81572831531b901aa2363252d9c9f409ff952b2ca23a82c29bf7fac4a3f81681b86c8211f9319aa32e67fdf5cad0e24b1d6908c1ae7aa8d77fcea45c9e211ea0dd6c315aaf5b93adff20b65132df3ad500f367a34fb359062421d9619ea4a254cbcb72f1ccc79f47f6aea2295d43e4353b47b5eb7527205aa93df6696c28c63d8edea6413ba60a4b4952e794b0654a64697c2ebd43609d80cee287b7539e33290c1dca03665c6d3bde36f1dbe3106c154a92cd7726215d1611f3ac6cdafafae7fe0a555800bdce402404efe89a718211ae7a3f3c08386021e6e2b773d90ee48cdb98b338c38d60c517a8b7777da0a2067a75347d82c01782728ab6364c9f02f5f023694794ba0e04189f28bf03a958efe29d30b07bcab030930fbb7d2d69c9ee7a81eab860c086786a8931f308cbd49e84ef6e265c1447b62a62a805aeb3bc354492adac09b898bc87c7dbc0c8f550a4c33b5e24e90a50ef2b0e563a083bfc1ff6e784f67e408b232223958e3dd05cd1a5f2c60f5a442618a504043cc6484b9889f0307ddb6b36ec30a119303ec76a1c1d66deb3e1e7bad923b27c15c8310eb892bb9cf277466bdf81a4b872c1de33d2960698b2a487e064c152b1db5041e8109b02026a336471cdfb7fee15908041c4b289a233f8dc178276d60122ee094abb27169e8f85aaa7c2d1bcb4d12587340733bc181eaaca79cf703d30c212e1c817d2e67cd619c4df763c10c1b04b5e4fbce37671da8f606344fba87ab551e35d1a1692d799a51b61aa8035a9d1c99e7961dcaa0e10cc98531dc469df4a000b84ad912496b71118a7e7dcae00a01bc4c4c3620201cbd80cec4152a81f0bfd864cb8b844dc9c07c6d3d56769693052ed39e8cf165c389e6246ac8ed8cecdd0a34dc600cae71018d010e4048ea05ca7d6ea420a40d0368fc416e27c1a0061bda029aa44e14e6b02039d3e39c0632823caa3bfa116c4183741b2f94754a8d505c6986d7ed28aef7301bed05e751aa80e28d41c31a3a5e89e2be3281c5190775da42133fac3c726fad16b7058ae3fb500e36a43d39632f960cc9bf9856d5d06f9efe23fdb640676b1d1ed07df4315910e10a7ce7ae9ceb12fda92c9c61aa51d118d811f78a22ab062d303ebd1a6b75b13db822916270bcf507394ed0d49111f233603cf9086ed434a3b4122079819d99a1934fa55ccc03cc021daba3fd13a3c9035d9ad064fcf33e45a6d8d68a9f762491bcf9bf9a2caa470269a3821bec432f6d9b23b9c354fa9a6ee46c8a180fdbe6aa5f1c68120681dfe5125eba1e7d356a516b6ed0e354831e7bd98e87656ce4cffc31e603e7292215d1ffec6c7ca5d0a1d98cc03ac9aef4fb8c8647e95faed890259571e938cd3ecfb6fb13b9ae2254e852b3967d07bfeb0dcc7579e5dd62dd05ac000a821401db46959cea0b58f7b1dcaa9a24792a23212141c8b11d4c7932c8be7a395d25a435029d8851a830327b19c4eb62f9d6a1747aba2847b7a6a341c153312d1800d8cdabdb4d0f18ffd1cbd5eb562cfbb28eb479c9b9fd7324b40843dc6b700c671ccc24a597f88939973c750ce32af8961c87aa231bac4a3232ddb7f680fbb7397bf850de52edb66cc652e91636ea8dc498cb2b4741d771beccacb6fd04a77c4278bb8a0919737a72e84cc4409a5337e04c18029d7c2858743f75bd6370b69e3612696c825690b079609ea40e0352e8d60d7c72892fa11341053051164f8395f87cd593e2ffbc753cb4bc7b62918d80a3256486fc58619b4cc7425be6b6cf26c66cdac0afe0c02262d53ef5c133c3595bfb0ea5dd9c5230b13963da25b14dd5d0a8c5048e586117ae9066e370065954cb1397612243279e6752c121da3262a7d869c386f9d34d3b7784654b87c3e760122ff308606376c72d26eaac760029ba561c2658b84a86a4b32df3d4baae870b01930650f65de60652badd08153feb1950c5469e840678e058992bf2a8811769d1304a84a5140a719cc4a61f344492f9499de48888f698b678521e9cd9b43f2e0ef6f416a85cde42bbed5339b1a9aa273efb0045a00c6d053672f312a508a23388f337fdef20b31983038e27d0662099ad177bc2f144620911b74505215008ac66646175912a3bf0ae4a788eacc0c2ea80a85b18117ccd8e4bbde51f5a34170a0989ecb352ff4cba3b54ab825823e4b794f4672b6eabed28ce3edb533ccccda382d03ae26c1d6b2591a374fed14b84334b709820a1ef9ba6ddf65d4fc180f8257f56d486343eb5ccb640e2a1836cdcc75d83ff7fcf6ad5ff8c6706777d132ffeea82154b603808372a3645e367f583982744a225cd159a48f1e34b9260ea66a43efff0ac2cf0eca6c3a0c87890205485455268d095083f228e8d3a9eb46a51abc8910da507f92597aad9adcf7bc411cb2d99c1225357f646b34cbe0291a139d22c8e6d3f6ad88233a53dc4b551019f714dd14ad8921e5194b662679e5cc24ab5c83b43b7843267035193236d2f7cefd081be328cd26fa28df5644c94cc9ff7dc1438f3d5317e6708dd8e6b1cfc0a0d2231728a1b809c74cf2c0cd1b92b076a25ebb86dbda1058cf25a9ddb2a7baf7d7c1ac1f2e47c143a7460d36f9002064c6165c20e2f2854d0631a9c9bc2e216855837adc7a20a2e2767947711dccb28f21e593625cf7e6af63a4a1606235fe62fce5a7c53a531e1de0910e62c9d9c3a92921662934010b54496d6831bd0c08322238a51fbb3313d5f4690152d9c5567feed56dfa27909559262c6a2948fb72a75240e1008a5e9a8763d65e83aa46e259fa4028b196122807e7591007e47b692c9f875c2903a20dc55e9a8a3f311f618239afb1c58fc3afb48b9e2ca1bd22036aaf9f284604bc5040629f50698f69aee27aa9f832bb032628d84fdfec5837b19d624def97008f12d43074f8cdf3cad2a142f9a5aaab36b08ba64cc1a312cad4ac9f343415721522232e23288b81f2c812991d68f1b2d284103281044e2002139880d64f422788900926ac04093941040c0982422df87d860f04635daa1091cd15c1211e1a1a5b45dae5500763a1fe853cdf15b8c2064e1d79fcfb589687a842016baea7aafbb2e19a46918bd2e5603e120a2c5b7f46ef07a214166f9895d457fbe8d643c8cddda06f9ea4613ef482b7b0bd991114f7033150b2398f55ab9e3466099272c4c36aa55d2b2513d7bb3792942f12cfa68b0aa96364f676655cafa54b3bf8b215181a01657a440defb6e824e420cac0c23c98baecc5cfcde4560004cd0989cde3c0536c5e57b84a91f1827239f3e9fa3ce0b8229ff6efed457c36869e9b68415ae6e56c304b6c2d3f70765e1663aaf4757ca649ad54ca46c8b8ce81a60f757c24d5f1ec76cc13ef6c3b1036e0bf8bdb2ed3d4f55097685070991e4f6744fd2234353433fc6b1b3bcf4063391416470e81cb8a8e1d98cce309c805b9960c8e61e0a1883d41d2ca4cd1baec0e53d02b486f47d264aa66f5280de4e78ba132a0574bd1abb88f89f6222bc4063e0a9a6f4b77631f7406bf1ba44e22a6786f948eabb7bf855a8c7acfeb2d6eea729c9d792edbea96a5c451ffa52665538d6c923dbd3f48e041f964d2a5e48a6470bb26e463db66ecbc177dc70d639dfc225771fe7f1f56c0e47f289fa89d3f69c86eff669f6ddf8eecfb8a9fbd8711c59120856944d78c09dbfc9dfc12ff302317885c600d55b0482c5de75f3a544f39dfce22dc9ee702e69af2691c855698841b7d5160ca45339905ae43239a1a11d13c47896885e249dfd82cd0fcb3c9ccfffbe0f08dc4b82f21a12fbbe2e0e9750d6670e4a5e6ae8071e25af0d12680e662270a00aed20d1ffe08405ac6298d62cdaa85571186e964ad591d3bd05cf43bfd4beecfa13d0aa3f274bfbced28fa7a86fe03f28df14721d3c7ba6c72a5216317310f5e7e8790eacb39e0aa62e60df5aaaf4a3f10842d0f4e4d11d228fa2cdd0f55dcc69741607840680ea3b5a551dd6b62b872e9aa31462290dee01ca4b138a0b155bf5d31d6acf52358da40c8b04671e693579d3c862967e46da84398215bbd7e5b2582003cf6ce2683f03c2b77104346816fc44c9c49829179f207051d619b1770456e4b525b25dc867c9e8219486a2c40a58a8602f77f3a1c4a0f8efbd3fb80660fc29ca9e80ee2fed8020d81115c9d034196320654f4b81523c0a2151018638203831dac49c1351173a36c29f39a84ad5a5052309861d58fb406c0d43e5d4c33aadd86aada4bec1bd083a4df754cc259e25ae957da2c9762efe30c8e564c33da3e99b4e9aa9b2417c3fdd9118d888c7620c534a632e5b54fa3ff10e93e8d84cc16fec72124df714e0018a928e9f5c8f7ec3f9f8a04a1a86033d5bc47bdfbb42abc65a9db220598b96d31ba79b52c6307d82b5f07e8c6fb64db1dd3bdf60bd06a5e9f406480b2ccb238ca9eb90c66beaf119eba242becaf45881af0eb5632c2d62b1628032c9dbce3bbd158e05d1d8ddfb205b55d144f631f6a709de24a7c38ab795d2bf7375c676e0cbf0fcb6795f9e9610c3baebc2a11d967fcbd53bbe2be68e453b7bed20be91e154e6743d7eb323d6d37d68a12e80728d490c4cef85ba9bd8ac813227da0c72e5c299350ae4009ead31409081459d0b31bc2c642c9b918c3b4af9245c69d305ef7f9084796ef0b48008efeecbb7639f729a322e8a2f6bbd6e42ce4be95be50ae149e6a5c306a9a7236d3578cff3fa6b1c0d55ebaf90699ce35f488ccc8d95c28baf28d1a28e57dbc3eba823acda217e117158ac15cc286b815ba3704eca21a24e7865d05a2744ecd474075426e335ceaff57f2501da27beed22b35b0b80a690a97fd142f582d735e553ff4187dc1f6a80991498ee572977f8a3b201e57070f39e2e020aea1531620cb37368861d28f1e12c4a3390d5bac8ec29487cca9f7873a05f39cf9c51a393076bab0baf2112ee3e90db4c93adf77517942867b6ec487e6a84f4500af3e0f1362c491850ea65bcd2682883212c766c734a31ac951702f8f70c0635baa33be439db3ad0e305c27f2d5aa8a232e480922fc1107cd32b567cfc2137ee34e480b4dd3239d4f129f07091ce27cf990be96e889e721974f10842319ef118612f73e67f8151c4326c31bc6fb780987a069e14dc13b248706e798a9145ed3170d7c58ca45ec26d70a7c281954339c2396574c2ef2bd21215e434c64e3735480e1da715f766cc0630cb6acd27d6ab9b3092510df30d95645cd266abc034849103dd65b6b59a53307c467df5a0bd896f27f4104a8f0165b74ed81be94bc1d4a78ea1c5238101a1fac7c3eb79fa902dbb33876711c7103c035251341e7953f63d921d594414d6114bf297c12a4495aa88aed8b143d1ce664a3aca4021afcf56d5b300c2d9d0ed5e4301a0e93387650f815380897a21c5f388c072d7c9e3438581485e5b004edec2f6538b22bd085f932d7c1f1a920387ef885c559e8cce65b950217c5bb48f94270bc119b64021b6d94d56d6defbdf7965b4a29a50c51076606010709b8f73c3bcf3aff14bd49ff76c36972f0265f9cc33de8f0832081b3e39c1ccc1973f271d4a0ba7ad0dde460cfe78932dd72399fedb7db2d97aae769d6f1a3e7e31adfaa4baf885b7136ea572d1f77ef3daddcbc7ab066cedb635d3f5510c8d583aedf2e8aa2b7bb7ad0e1fe968f5375c6475f645a7155dea7159ed451d50213520fba7e9fabebdd649295d3ca1d80f827e61e74d8e8b4729e9d56b2cdd5e5d0f83590aebb8ed70c7a06f7c5b08e7f877f279870c5d20e0d6b71fd444f0b84efb5b2653f3f5a4830bad7ed909028e47e9010f941c2bde018bd58d988477e90a85e4c786161f3f383647c5ddaad90490c398487a78b5196222b2368ba15322919f938068d60a082906988b87556d354f7ba6e854c537d8a6975d25d9ad371ce794e3664a6162952a624cef9d619a7a9ee5390e9eb6f537d21515249412e491a793852b15b618a30ae13384ed4308a51c442c4d208618aa410c21266fa772b4491d66bba15a28052648e4ffea88e21e007dafeb318eedbdd07240ac6275f4803fb3c3c7cc7c3b3dbf554fc7df6fda94e492afeb6264cb615219c3a16aa08ebf8de17ba77044642154c6676ac030a580d0dc817f4ac6d94b10e2a90c14f071f540003bb8e81ae5b61092b3d772b2c51a5afbace0ba2907381640558af888bb764b563935a73ea6db1d82929a1c455a2887765865706a63744cb580abb3065bb2c623c32457a46bca4b07664a67a266a24b940216989272469c44e4a0b49c28c48db01b9c23b010aaf8a92dd57b047c4849e0a1fbb1472ec82401d69ea9140210289c40e0c698887b696d6a54a055e14293d2c263b32233082f47e640ded18da9011e30793d111126feb08048f68ccae6d9863851e0d2a419af09ac27a3086386df186f0e8e988fa32a3cb8a5d171225786024420829d38362a177220b4f85191327bb27495820c2b3d1b51b73d5f381238a868b16bc1e462678288c44f44c38d37b52e395d8b253c14aafc8939ed7d8aeec042720bca31ebc2faa1ed8d3aec90b13469ce0cd1861c49b6aeb8510d3c3c18507e28c67228a7049ef8510e98ba7f6a3d743c75509bc361840529c20e2a8b742c487dd11b61d9a17312c4953b4c87a238e601182448b47958f1b5232785a54f05e1425f56a14113591e13d7de9a540db11a91204059e182444c076625cbc30563d22a9dd10275e158bb5104a3b2b218a7850ccf0b0c0ec4cd09296028fac4c88d80e4a919dd719ac22533c2f357828b8c042b4d463413462e7a4859d9930595232ad372325afa410658c498823bc32437827b87a255c8198e22579ea4d352da5d8054120f54e0041c4bb52665705cccec816b62b4c51784098c438b2e332a497c5d5db71a5656a17f6b41b367d48d1f50189b7e50311cf4899dd0a6076c22d5e57c04489c1a4e808ef6c082f8b6b77e5aa7734c563e169d742d3ee4c8a21245e13e110518f4799dd123054b6d4b8a214a505139e942356437a47b8965cedd4a6743c85359148c1b3d286c47bd246d4532a13056617658bd5959e8d283d2e263b8eecb60ce1d1e0eab5b8eac998da89799ad2d443916207a60524aa16887863caf05280f9b0a5d775a557220a8f09139e92233b2f437665b8b4ae785053379e7648d2b1a967948e294a38938474a688c7a505b3b5ddd990459616ac4c89d2573bf666cc90f59bf38f66c27a7e9d73ce6f8621886504f9a4c4285ce264808864b244ec4c862a4993714aca649892c8188d615b22c3a5af3e6797cbc3a34509e5e98da982748e697264f2f4c69028cd52de97b447956e36b425519c82b4376c1076423ffe514949b039255d9e1e1b89221e7d814ef73ff4495f7dd013c668195325fb9ae431615a6b4d965d2855798aa22896268ce098a5711c47d33cc70cb19d676643639bd273f897d9c24e1445515cda25f6c86c543d977d6c35270dca8186a6265fb62536122659efc9da7004e77f721e31bdb6266438d5b9059d7371be75c63f676eb7918c89a4fdd24977694ef784b7429ad4d71403a6b5a2ce13a6bcdba09816fa053f5d2eddf1d8af0d1ba4ba34316a62b0f4f537d6c5b4388969ffecbceb5835b876d66d706dd809105c23eb3cacabaf2356b7c135a8be9ec135186b278264d882659c7abdb2153e9c6066e284185436ca0a604c5fdaa814097689eabc0b154e0cd243f2054b8d9c2ea7dbe574bbdcbdbf01db91c601484155e39fcd3797d3d975977b4eb438ef8d72c86f62723f1005665d6e53344d9483054852939f5f96748b76b8f677ffda9f05deae6ff9af7a57527d8e17e30c1a7c0c6035f074a013d0eff7a0a4abcffd2e435c94744f7856ccf345cbc5d813916eac1ca9aeea7d7db9d17937d2b2dbe097189d47e728d5e936f805442fbb0d0ed9faaacb60463be29df5bdf7ee5f73b9bbc7d3722ae18a288b4d0da438f8875a6bef1da29d05876634740f4a891e131e9e31ad797e7e7a4bc6f147fc81d464565484298c08c4022a1c99e062ebcc12592f8a94273ff9de9b73ce39e76b9be068327542e985f1e40ce0226c855c2f6c0031ba34097d38e2a262478e165caeb68890b802333b5262e48c97942dc3610605a6ab880cad4306930ca95c0cf5b2dd62583125572cc86ef7de8bf1c51817e0b60a459160364cacdbbf68075b64d82ddac1b290c562e9eaf6abcca052060b979542659f50b149b42c1231fb640a153237c8416e8b0a02aa20e0686f7e5b8388d0b048920c2049ffb1d0edab56766cfcbe5c284a42c362a7040e21d820431fba08f86fc72807b98ed5350464dae7b68ac0049a1519a89869dda290cbdb5a82317e45549151e07ed16db76f5107884c63136086d23a6badb3ce59eb9cb3d63aeb9be4b6f6fca3dbc77d750bd4ad2a842f87615a7cafbd6065c7eaf6c76e855533721e7ee12885882971e11a860d41ea900305991c2ab40c4568306314abf49cf38eb07ed3adb0eaa88708e1c38377a4756cd7b35b1e2c947bc6ecded1a54358b67d34991656dd48b3ba7af8f9ed00397f3ca3504755c7752bd4b165c62bd5751aa88ebeb70dea50b2e9809a1512173128b6333bb41a2340f4889122051d445ac7082e52593bb79c1b36d05a6f2734687def9d044dd359a430471034adec56984347bf752bcce164764db164546c8132134c7265484a15232e35a6b20cb9924503fbd6033d722c51c978adf58e0f31ca0d1f2e5000d3840693504815838592985891184b4c94c9dd0aa95e6421790787306bcac945981a4d3a102552ceb0daaad81896422932345076a670ba155285e867b7c229b61c3b399eece4f8d9e121c4614638b56426941adbb1faf9d9a91ac79f32be395741c027e69162ac4b48ad56f5ef50a5ab46c51f812e530b54bee61f02099ac60299aff17f1f7d0ce8bcd7ed571f7db6adf3056414513756f4918b4f7ace39ababb87774f058b14c18e514d5822bdd7ae7f4fd986d6cb33db6afb81e6463687b6c175571e729cde9f603fde63527631f3b359c7664ec34e59c73ce17ef34959956272c014b0845026b67c4b53e8b7bf60e89f4fed833d55fa6c5a6f0894cf83446f884763590a2752c7c32ebf8cfdcbd1300339aa1d66f27ef9397bc97bc22caa124cd3f35493eb73b26d2dbc9df3af6c9b7a5fdddafbd6449962ae941d7ed5b55a754c9f24bdf8e8b74f5e9bb7cfbe7dedb72d2d429cb2fcbbf522e9988d4a78bacefbf5d60c11b76c76e154f3fcf7c5a52455fa328fa399ca17c144539a0e8dfa893bcfd7e1e1f0c7ef06f3ff765b0a10127feddfd3b46a926ffe62b1ff5d90c561f0b4ce065e983c14f49be8849723433b1eca448b97d9edf0558fc7d318947df386a4d9224be3c1c6fddb16db3f8f66af8fd36b09f5ffc15e78c5fd439e76cd5dc0f63b053225dcf1583ce753dfacc3984d049e8249c71d6ad70867006940902fed7bf357973a45628a3abe3bf62de9991beb88362b72bc9db71556a8534829993cf6964a5370123b83db540fa7613a738c30870cfbba716286740c9df28508754c731084b8077cded57e79affe5e63def08b8f3d76810d76890bef627fe4639c8413befa048efa7bcca2ceffb88a4fd6ac0061b3229dabe21f7683246570c2cd32cf3fcf46334a8544ffcd3a2fe1da434aba7cfe84b778ed2956743c68083cbf3c319c8bfa0eb3731ea800dee8933fa00dbf78f1bdff89bfb6a7c13c218f61c5ad7c226b4ae3fd53a0b90a4e3a3407cf1adc31d7de71e3371ce7a6f2ed4af4f247d7add206cf58a39bec665922445c85a48f49d5dab2615df11bc84954c7b8e2fd4cb37c7377de579f3016dbfbd99cf31025abffedbedc5db8b7f5347fc7ba16e3ef9a8affc0df94da14efa3ce8fa28fa84cc8ba5e4f0175eb2fced788aa208c2cd14c5fde7b9c51bc629bdbae2a5be5f04dcaf164d510cbafd3d4bf14b8ccb1b0abcfd798a28872bfa6e3fd698e6799e8f934e6c71e769be8999685e3c71aa89956e02ce172fce5f1587062fdda839754daffeab9e349fd2bcf81ca4a2aa93feb901ffd4d89ca269e2a5f36fb73f73e7e3fc8dcf344d75bd25ced9a86b5aa3ae2389722869d2d4673b0d0d4d1a64f61b4ebabdf8db771fe72b1ffb36f9fadcdb3ccf53bd7f9ab6df3da22608e38b2afa22aaeaf0bfbf7f03feace127e753fcfdd757fe78aaa63e4ff1b192898db09288722835e73edbc7711c552edab11c978baf165043dda75ba18bb1de82a907a05be18ab27ef69bc72893b0c949f8024df8224bf86249f822789e65339c79927ffee59cabab071263f54cff62a3f33cc31948cd81d4f9cdbf80f1c5cf2f7e8e629b277d6668d7e6dae0dbe7f0cde3dcdfa82beeb73febb7e63e5733cef872f15e4cae29388720a44f42bfbe153f3fd148344d1f9f671c9f675d7cf2c95fc94c661225710d49921c4320779f54d7abeae49e678cf15ff24fb31b389b1feb7478e49824b1b5e3887bfcb70bf58b72c0f657a18e3b643555538bc10273183f0ed2aa2fab3e8c026bc6e75f84ea4c607cfe1f8cbc5447521d330aa401dea0ab5e15a7b98ef360fc60547fd49fdc23026ffcc43e201a74556d390a3c6de768879b8e31f6f12151f5a3d232dfb1e4e2d6d9e2f216c26d13b71ac26d2b7b08b74d846e6f5b27410b6dcc55303fbfb5695fef05ea9edfdad15a9db546813b07e5f4715f747b347ddc7f90aab80f6e40a008b41bd82e925fde7befbd570238dac8aa18638c813ce7ac9c73ce1a058a5a6bad817be3167b6f11056a51148199af96ad5f8e8630a240ac7d4f45a2c0dbcd374b511cdf547582baa8ae5d3471b79b416754fffd48d0a1d7c7efd0eb5a05f6c41e06aeaebaac5fdd018b246821139f2186aefeab5a6042aaabdedcdf41d6b0c7314d75f8e6cfbfee21ab8e248fbb87c6c8aafbeb1ebaf7c998a6b53ffcabbe79fb268e4f836580edc1d847c7663080fde1ef2076fde6d53eb107893d3f032c10faf81760816e8f5f011958a0f4f127c002d13c7e200b54f3f81160816c1eff8f05ca3d7e0d190e6081ccc78f81053a1fbf010a6081c4c74f000b943574907d2cd0781560f4601918f96870f3ab2db2809bc738df0d231f0d707e34c04969703e47be8d6f84b4935fe31b41d749f5068e3ae61e1a7b80fbe6ddbbdfac37fde61ca0038b8126456fb87e1f4d733f458be15a0cf7b94f8b9bdff2e3f86eeedbe0518ba9dfd43e0fc4df6f799a5ed4830f447554b5efb6759cce06b66375bce14c02a0d9f303cd8e4ddc6deb16059ad9470216ca18c3c063378559615a647dd53fadab1bc6bcd75eadb3bb66de7b516b6fce32cb22bbf7669cb3c27ace62e68bb3de7aaba5dd7bb7288593e59cb3c8c1baf6de9b8f543f38e77c24853f987e28097f14654c0631df890363dd0ab7befa7abbddc2eaf75e9cad700b4ab8d5229fd95a3484db465a0c78dc32ea586bac6b7df575146a79f55577e11651cfe5dcad706ba8e7f0af89f932f68b5cf6861626a25c312147960f29f012b1a24b982a4131aadb65ba91adce701e62fc32868f15705219bf3e827379c895610a91e211bfa2ee981675f7de7bfb4081468a8a0b314455485e82a809995aa1cac51512434688cc14a4aeb28fb6bbf7de7befbd6fd40d227522cac6571791d291b5175a535ad09ab103c40c1af86944a3299339e7376a10717a114685940d4dcea860448d58d14203ce398e7fc042251cd9f862822badc6194e012e289404c9b80ce9d08073cef9d0eb2d3ec4e0a444511447ed448a13133db2ce0c19c19ab16f54c629698267fce48a8c9f97b92746221968c599a9ab252928271080eae2e408c5092fca5c651f639b7895c45bb8dc0bef0162acfab2f7dee7a8420d1a5986b4c8c810b322782486890c242c1fe3990bccb01ad232f262232221094c8c5a1932445bf099680667a5dce5d2c04f231a394c814e218facf868d2630d6c4b06591515264cb010630bef71445366a7220be2ee41433f8d683d9afaeebb2c53eddc452ae72e28ae64d10846e55832c60576a54badc84c9b9929844a6789ca3c877f3ad549ee881685a56c112f25524e54304382c58829154b5694119224a4c7150bc848e1b4bdd37299c815f317510a3b60cd47991145229248ec88c182151330c686c3dc9828118bc86dec86b64cd47c00b1d1f5348589dda2c2142a20d64ef82ae3430400d82ece6b482c2b84b0c7980e1b4d5f419a52889db8c2ca574a706b586014cd60a18595132a40e263868824606384f82a07177395029d1212311ce211b5a4e2890792158c2b636386ac2d41c3488919fff881143a012285d3122b5f3ebe64806d065ada10d49119f22e5d2e524da8b4653921d302aaf1a4b444a82482c80955a3d2a3042b6233a058005281938e2f2a5e8468cd08318a60a2bc307de9620b23e6553465e3445a093eb8c2a899d91434169552172ca9ac94bb88a2fd9a0ac1c8990828352429361624b63c9d184a333bbab170b1a0baa894bb586d008a97940a5c7424b58d5a828851597104f6e1f7136c35b5abf2179c203fc88890d12487520b2c2d265acabac8149de02dc8a1288ae2a847b4a89baa9f1b51535662b817fd7b0ab652ca6297cbc191213fb6886122c5c282dd104ba1c9922c5a4d3e88d058b028ccc42aca4950ac85184e4eb0985142c38a4a18168f1c17297e35e79c739e53dd9af69ccd9b733beaf4830ca21e552bbc78592143d210233ada8a7864c13a91d639e7699a7e59828c3535301a2e80ac095141491991362b5946565660bf456a970444cd0a1031685d405738b240a5c8d252e16acb4d4b8ee8e2c60632ce14d14ae147530821afaf232e50184bbac2f12566b9586ce5131a56da5995cb46a330162e4e0885601c00860b89303c60b030a34577195710da115cc32ae305052d33a89cf94184224b04170a2b1ad8ea4cc4f2f9890f8715902e8abea8fbe6f505e5b616846e80c04a09cc04e1be8ec08531c6012f965a1faf27205940acb25107b2038863558b6b88f9eedb05d5e574efed5aea32ca39672e36aee1de7b8fa459197972ce395790eb8b2449b234b9a44c2e2733ca7c235e595e535c555e4fdc4bcd6be865e635c58b8948ee2449b519b5344d7529d792906b48b865662b6debcad91ba54cd24fda22da1a22da6a02452459d8a4303462a38c13321f3cf440d125b6c6f62387065c081771ed1d9a676a7b64f1f353801364b0203a132535e4d2e01ab335f6044aecea87c98b7ea27ea2c69f33b71313499d2fe99894772bd4a2ba580d2a6b31691d49b93c3c64d7e767f5c1409815c639d73f676e975546005652332cd5a4d9ad306bd8d71f142c0a79b0097984f0a9d87992f2340c524839d9bf48c5ae9d26e99aee8049c9711cc7714cb96f452acc62ea3add0ab37808b3a0fafadb23fec7fff81f2288534cfdd6de8bc3e38831f0bed6aac5503c2b8e78f9d8cda71e83067b7df4d1a0c1ac81a6c6a863344833d43443fddf571a515ff1f27b6f1e35b94bd1dca568f27344f939a2e4adc491b71267a6278dbe0ff6e563a6270d5a73b3b1f7ea6bedadb9f76deefd9cbe7fa3f5fd7bef102c08161604231bfb72ba4bd7de6befb557df6befb5375f7bf5c559ef9df1dd38ebbdf516f9389e7c24c9d234cdb334cf13bde16e385cc617773b51b4c30d979e38b4434a5343535393f1adc9418136ba0a50e0cd051d1c1d9d8caf4e875c735a9fcd698376b87ddfe0dce0e0647c71b65a817e1274fda278f36d05c18786e11e8c06f10f0de21f0e0bd20c6b86708675d30cd5686874706fad73ce17f89ad2fcb3399d1ce21f560bf275ad926c38270d1aa419e61e1c277238ec438be1eb04c31a3019eeb91f22873063464d2d2dedec4c8a94be92c3a1f661a02d01ad8e5ac56379096778802436432df1edfe4046959163f68732689a65306bb8b1e51efcd8e836b4a1d1f1afe4985b19bc0d6d9c2c06fcb76136b2187619867bae5a06f1af0ce21fc60de10c43b51bdbcd8c05b2a3cd5006fe1de9aa7bceab693ffba0d21537438ba2e9634bef93f7f6b58dfa09c6ba7d32cb02919fc7e74bdb88546f5fbe11e91dcba59e79f4751bbdb8378e5fbc64026dfefdc0fcfb85fabd18e7f304eadf417aa31db45d85fadeba9c4972bfb971c7781c83807b849dbe3f63aceddd2870d42850ab3e8b61a340516702fcc52f22d55144815ba87314a8ad1dc9b22c4d922c3de0eacf4fee21cb6dea74e6077c9f24b9f7ef20fcdbdc5aeb8df556c711055a6b9628feecf380df334d4fd403fee2075ccc68d66f6a6cc79b7e20098066d79f81666feb3a41f7ed03afaa556d8e6807b29ba81dd1ad96b76f7515a16b9c2299260eed20766b31f0b69f4ab9ba9acf47e06debe5afda5a6bad352f9975efbd17a3758c1f638c31f78d2dd072ce9a07b5d69b6d8bab1815c25ba5e393274739e0fd7c73e43f76cedf77fed8cffd18059e6fbea9e61e2fd5b534ea789775acaeb69775ad9a60ac7bd8551d9b972c62e63733076b6676efbdf74280f76c669639608cb1ce28879c33feea7b38d45a8bd7e93aed2d72185f8b448140aceea1218b415775e875ac6a591c987b71c37b2f6e08e6aa2b6e8bfd611c8e4ff739be0ace2bfbabd25a6733ec289f54aacbb11c85715bf03a16e91ee78b2af89c3f1f879e8f433db8a0657b7a8017b4ec0fff0d09fbc35f53f3b683d8b3ce7edd0b6980e3d3d9aace04703ee73fc0f99cdf9fa3a220c7563fc8c97914e81ee78534b8c0e1abd2f26ffed6bcf917b22c507efc17beb2860a6a7ecdfdbc31ba59ca1a721e8fb7b7f9b6a7706f6d069c1b231b75d575fc15f846db735fe7b3765fd5e00c78bd90f61537ecf87137ab0ebd31baa0d52f8c75fca56fbd603626ee299bc15afc3b4a7cfc39bed1f6888fe3834ad71d2f7d5f94c3de776768fa60e42a2ed3f2ac38a7accbd614b7846bc219e15ae01f2e4dd81efc39be7284edc18f6bb2194a22fbc32d70867208ff4c944a658932a9442a8f4a12a59105ba79fce5080b649435ec2111655119c2021159a0f1f19720ca0fe59005da1967c4b4962b3afe72092f00e7f19b69cee3729ea646bdf18d30f61072ce67e31b41d745f5468eba9dd81e26f6879dd81fc6d966f800f8f790aedfa11bdf2ecbf9f6131bdf8652e3db51687c5b4aeadb5370be4de5e6db5534eadb641603fed3b79b580cf84ddf46c1b79d580c7b6c2bd94b2c06261603cef16d277657b13d98ef2a362dcb7b6c0f8d695fafefb1aca12c77b9cb5d728ef1e62a8fa1aab51cea3847e5b0e965e237039952a8842c582731548870000000050317000018100a0944e24812c4301c4dba0314000b68ae3e5c4a28918b44911848410c833108c34008c300060064003148398ad40ca42093a9db75f300474dc53e72c18c0e6e3ad66788aae16a7d26c0cb8fb8c46904711c1f07f012eba95d9dbc1fa1997613f73090513b74dc2fb7a862c8ec0663305b5ca80bdfb9c9a42adad1b7b2b4214131af3df910bccefc5982bc5a231b408a447590d0d89969634481700d2dfd6e427b4c40c3229a6c83a22ac01afa669028ad111a525520d2cc30c3c77423721a3b9077f8c22f9e167891ae8e084fe99856a2cd4b69929ed0333d3a619a54bde833c40e56f622b6a80e5cff052e2cb1953242111695c892c437fc971f5e8a60606369141f8a1848479b10dca9e41b73204c1236fa41d54bf754848dc5ba1c3a5fac8bccece93e067d7e3817f658f18717bd27a55d04655dc17cc0ed1d77161ae78b8cb77bd1a1b1554c7d82bc8dfbd6f701765ea0083d45f458696812ce275ac0876ae06f6b5898d76284141cf3b3f8626dd2629b8eb9e103995e6ea6480378f800f1a53437adf105ca31d90f6270b432950f510843481e77eb1c6768b87a596e7e26f8d772cfdb93831f047cc2097f0164931abf026efe219d12d5e0adafead8fb2eb5a157f194068f91d65ab7b6fa307e6df5543890c6fb8badd992f01e4463ce602121f6c31397251e85fcad3a0e7669bad68dd0fe13f2081d09f40f901644318690d4b0261c6803774d6e3c69ddd2a0674d9b36b0e2c6877c27f27190d004bc04248b5b5606476902c2cb5524722eee349c9bd2def709156def718c2b8aeb2739f357ebf6103fdd60886cd434876b36536af5b0232396f6bac86077a53f74b7f6a1106234f689a9f21a0e01350b07a664af4094b473e68ab9838c1d15c21b9e8238a87d713deb2807d569beae5a32103057bb224577b4813bc45160fd6cf45a8101fbc5d0d489c0969a8601f570460924549dd8a88211ad9438ab29a9a17370bf672a55868ad9b987c4e7a2490936f438c4f9eb0c514f49b3de6fb4fbb11759245dbb192eb4f6e66c60c4664098c79f736693ea5aa34f276d690f3967653d8f63b6603dd01908db722219b0970dc9bab3e783322897b5b4fe0cf927d3dd95df2efc19d34ec7016004ecca5d69d427ba67857c185eeccfcc0bd1fb2d69a8b3a1123835b49d83be4a0ef507ee877a3b175ec9f2103b91c70d919ff02f81529bfdc905cc4ac920b971fb44c556e74d0638c1959a35d4c35b1dd35e55068e9eeea1ec00e8b423d3e88b23987aa34fa62db3a9e7e856d6e92c73aac6119521e6e8870bd423c265f17ce5fbeed492658b984d0c74d182c139742bf34f5e076690d17b98a15eeee9d588680b23873041f30b0e819887bbadf19547dfbc1f5d9e4754c68ec846e2927c05b4a2e02ba045a96621d531493228ac68c78e8558e41863652f3972c35503662de3feae8121e4a2fc2efa2a74f8a83e62ffd535763aec0be2f143a123179604dc7def083375548b6a72e9e3a20fab616c60148e0c1a60be0ee92106692848c0580063fc218043fdc9e8ae576ce3663c01e2b51052100472b4db5a91c6b617ae54ccc6cd3924d0ba38c4243d601598065f01b562add7fac23f734370cda378879a190de5bc83b5844681954be3c487336551013940a3a199032e3cc8515c20061c37fcfeb763a82fcf78e0ae18fb0a181e4662fa44e578e7358bbcb2af8009bf31d44a679aecc3278fd257c001f01f2a1ce1f6b94d1093fee3643f3aca34131642cae8c56f91966920a769301ab21757a2b4c5fcb46d834c24de3b550722fdc5bff031b583b720e9dc57c0cd2ed371034e7d74aa686404e0557b2ae6f6479b7b7075f45cf54e405f89e2ce3afd9e2f6e02571c15b4249c68699b06db9a91008c5d9e844f27edc681da40ee752ab64049120672d5c9d10495f31dae045be7375c1888753792b06370d32b08f7a0478c1edae63f1a28ff36744114af0e69c53a12fd0970402440508adf2f308386d442fb55fea5856ae55b2c6f56fc34cf9e0097811d43de2b6aa8d13115b042852c4c037627a4ff39dd084235fe80e4894fe91d4ea4ba9129111f6808d8fe9ef91f54b2002c88a72f3b8b8d76875a2b810d439ab2c0952828741c445e91066a778770b6939f75beb123c602224c848b553f208fe2599e666626e72b7df84f6e6566c5700f8518d82c500dc6a9ce7799fd87bd9470991f3c477c3a4f1613a0fd85d77cf811dd12b8a3cad334565491590a6abebf4cdf3a280d991aa942d13e8f06fcaed714bc69dc4cea4fea07f9c312dd73074187d312c50a74bb07310d5fbac6b73be14ef597a47846cbf004263fd262569c338da1bbac7334831b2437f018a7249b0710ac554ff6794b728cdbb6f1f92af98b6149f6e5ed1aa47010faeacdb03fcfbad7e2f293f9919d55729587fe949a76496b1aaa408ed5c6d7474873ab48045eca81a22d9271fdc8436f2491c328136ada86848b57a879af8dc0f5cd95af1006670bda69839e2226163eb273e225187c0a867bc3c34a7cf4b702c44b97d2e9866397618474271e3e2d57087df238923b26ec87226654ddaa804586343ca7c0298e89de33dd99cafa5299291fed2df849e490867b7960dfa1c31578bc9ee8dd0d9be806b252ec1f815c570bec20d013336ed30c9867521c34f760c86a012fdb92c23d2be9976198dccd418d86b334236c8af1db0d41f91cfa87b14f5529ac8a02595d6322ebb9f85df215e59986a6f899b59391e88a8ba4fd22f36e12cff222e568f367a7aa0f8b39be4da4f6f1ffdb2e62d35cf0493f2c5cb96c59773d38ceeaf2d5e6d498a2b57bdeb8f7b505919f0f53260392a80d837333033f17295e1b4289230076df365a066b48bed67f25b5c5dd1b23f54bea9329ae774c10b013a13a52360744a6c565b1fb9a8bb3c1f5011d1c0e4ba74227d2e4329d90953ffd89017786b4eaf9d23b4dea48238bcb8a6bbdc234a5687ade056143658b5521b2dfa6c4b76235162bd8775a3b5990ed2bff3004abe081a3e465cc17948370704922d55eb803a23573bd1f9fc17eac911c5c96b0caeb30e0f49c0a9f69d55fc0753cc16a7bc8236a8f04db3d1a41cb8e752f6b1bfdda9997b304d5bca7eb226658927144d9ebb17b781b29ae01712dcf27671b6496abb0ba776d88198c585c8ad6f259aa72f95fe899fe918fbf8b62e5aa1f0b73c95912beaeb193253f88b003dc57a30d2cb5d68b032ca4443d28fdf8206aa8ac74dfe437f204d708b104d09b6091510f422f51aae698ae5a7ea84b54f6c050289900a3e95789d6c21c224367556f040de815029ec0970617bf8aae7b7907a120f61e433932c405b6a70b4bb69b76836a79a1816e1fe45177d673d807ccfa3b0154899b4fcb16f9d2eef4b44d1a9f26a3723d95c8ab70091a85f7b1fa00b74e4b7fa21adb2122eb82b91ec2dbb3a3bbebdf55c998d6773efed5b7ec25263711da0f8128a04bff9893b8828448d5886ff01fc0bd7b071bd84e770715bb30e402b91a91252cee307ae3b37c28d22223b468ed7a56459f11924ac4535584d1c7b919905fa276999eb4c4298f761881a5aea2886775cb6750bd129807de405c8717401db818ef5a39b4e2bf95099d97c9fe248047bebe2c40cb42ecb7c58a0d90e4d6ce6bd8f889747dc92e5ed19c4b1fa98a366c279677302d37299bf8b50e4b084230b5bf157e8caa6d987860d64618deb95e55c9d8ae34037dd746265904eaa17361dffacf2e59ceeee708f10667bfebf677508233dc1d57894dcac5b083cf37817e61cdd0802f97f7253beb2a54c8d5a69692184e82149f9802aa10348a2318aef83bc68f37ebc9dd6f6630412f08f39a18c0a817a3ab8c2a8da615209630bc81cb68d340fb1e746d5def4549f9666a0af879eb1c2d11b8c4a9d3d66d72f55ce958cee7429d4ea6b623374489294202b8f546d00e007ae3121d0b7e89c39a733ca9409d1f078a3e4c41fb9fd8243204fa485ec171d59035c5c399ee38ccc7642f089b3feace63e752ab3e0e4550d3deb8866491998b623f579a9de2eda239424d2ff1eb21da9ba2f53a363a2fe906c388cdaed2c52fc20728f319666d69ed4892041a93ae02ba9d76ec51ad38ae90af0f6f402b7cda0f0dd66fddaf1dfd3a538da2038787be43df2f5ec883f6ce8ce07c8d9d5f83df2291b3774c545c52ffcda49792ec2500989915568581fa04dd4c92b609122395612658990d659ee0c30a5a65aaa690e41d79f6a68d844228f500a4ad8cdfcb182f2ff273e64d8a1722cf50bec38defceaee76b7a77b4f4ca198ba1a81962c1d50f6c33bb93816fa1f3d602f82853a75376b16cf324a43b400de4bf6b2105b11051cfc93b8f57fb312c77feefcfccdc8c516d429d4529bad67dc8c89c816e944b8a9a4a94b9ff6bb4b217bbaf674b2c0c1de62dc9cf96a375356d4500101104bd392722cfa540e13910560c6c41296e497d54286037f6170d2a9eefedb7a995d5f8035c01c7e3072cffab65cd54f547772642d7b71d7ac79fdeacfa7a94681f2371498797ddf5a42c321ac70856d3edb6211b7dda7b12fe05210ca8f6143b8329e09c6a35f9ba418bb4905d1f4f0beb42b5a80adcec3143e151149627ff7ddb29b8f611b9bb418d71d71c4719f3177c09fd638a723b74ac5dbc90f89abf30635921a789dd12d88f398996ef4ea4f24780193557cbfe1e75d32abd6f08a5a7e773e34cd67876a83d9b9317ddc9f7c6cffdff8bab617cf4b8c3a9dc1eb73aba822ce7cbc76b4efbabcaf6145ae6c798165708d4be6d52952acea379f637ab3b8347c5da231e4af180bb76bf925ffea0dd049893ded05bc81017f1759e0b34e3e516c01cd33158300af4fb6b4eeab825984bf183a286ece037fafeb054aeac6118c20f0f4099fbe17dbfed120756747f153d64159d5545d7a8a25753d18528157dfe7da238d2325e3e0f05ab231403f5ecd2382b02c4a188876ddad9be2c02ea2758fa65c5d8130fd85c27eed76a5314b80d9decd09896405c6305309caee58e451ef0ea5839b547ec0e3579cf9c9d22af8bf7a1dde9440d01abeb98aa5006157914358e3e25ed40fad7596b8311057a5fdb38182cfad4464b206214e3f5af10ed68d23156d1e492126d3cd34c73d51016c9ddfbfd9f6259008134047ea484acf38579afb376bd365a0a1cc0c6eebba1ed74b9bce34b86a84e4df3af327b64a0d578acce9b8635271a761752cacfade845807bdc093f5c5c342fdb26a3a09171fc84048e77aa0bdc0552e41d1f2a4aec7db7dc0a0a332d7d81f87fb75c7800f1dbd6376392fb6d7a4b261587b706a9053aecc77b8a5127ee131d09758a843f7dc1df6082e912dae676540e675de057f3a940fa4cb541d5681ec067cd9080b0d2795ee745f7e976a87a9efe0d76316ed6f6cc52cbb1a839155055bb135db6c2e37b6eb9c42536ee7fbbcf3c336bfeb26f88adbd42cf7088154a44ebacc0a2efddf2d2efbbd90cd02f4894b9537f92fa6c9bcb8cc368b5830ec7cfe25303146c51360aa4121a44b0a63dfbdf5aa96a0eca9431d424585db1c385e6a276c9aa5345618738ca8de8781a4f74d7b16651690cbcd89e88ea89d2b0cd9cac71d8d4b860ce49e01180541b76d955f273d241f07f40018a387be0e9196a1aeb537282c77b5c6acf8765cd40a2e7f536e75b18d543894998d12cf2ad79056c15c68c5a113770f206cda4145096c59d1164ff981d97e9e7a74975cee077ae31c29ba4967ef17c8ebd4cb39641fdc2cc7d0cbd4f66ea4a5b60b23495f2b70c0c58edc8f2270a6f8b779408e18291b9c006f069f23440032f5c2b8089dd6cc8119e27b55249c072dbf76469de695654ea1233d068b3a775ee8630bd08626ce36daaa672518bbf0e2510e6c0c9a8d8c898a209e3a4f6eb3b11da9b34968f3acefa0604cd093e2588f0963e9290aa597e94b1083e587e79d67d43e6409197ab711effeac96499b09fec6aa25b22de4ad35c8665db8897c062b62c2d2866a268fa3fea58c212714ba90b90d156d3405ea64103b6db325af6966d3a162232b1fc54b64a362f88481a04206bee1c3924fb3877790ca52b7f239ab69d7c990b27060c016e10b70117c9b3d90dee9ab79b44fc8efef85e338c4247f7246d38d60f7effbdf78a8a395b0ad43b2d2a38df056a9131340ab22f64b1e9f68ed6a66fc2b7d901f4ab8b116f3b43372cde6c261326eb43974ea83de2a35624b6b7e077195a3a23056b05aa67a42d1af3b8751937b656b066b7ee78540c4f53e4ef7d01a8850cc96af02c06a253ffbc5b3dfa5c5cdb1b19356e4d13474605705295b24af56ab09026f282c040447744e08e7e3f93db9d2ff7ba205794d31fbfdc7c4cd4cf2f0952be41a51dda466c7181c631e74bddffcc33dda05398d21f9d8244b7c9ab3aede717cd827e3f45bfe5e06cb13d5a7aba09b86ed5ba3762910ff9f51ee4f3b313d0ac7c7afee32762da4cc327b67d6af705091ed13999d9a9eeaef7cd8d7779c840afff1ab74627615007e4901f8302eb86969542859df50b2c7c5d21b389d8933bac8b7be3aa40bd2ac9983c3866963a2a7f7fbf10c774c01976c5bac03f70a36736b268daae4c3c3f5ee66cfba755ab5ddf383b5227dac557f123aeef6502d47107ae6f6d8e176c917b7ddfb6c84a841872efe155e2fce278636f02b61e17242d8361f853db23d22d2cd862a84de2c6d391174145fd4b7851f99a8f87d34bfda8d995484ad96ec935b00fe8f43277292f88471f565cbb12aeb463a1fcc57a294847db63bdaeb6a6d1af291830e62e83508cd3caa7f054069c71d2c6984c0676a8677237d9dd9d0bba26d0cefd7e4ecd9e651234dd19629aab6799b3c24c95dea818560903946db9364072db8b9af49ec41663401feb52de97b9affcc281f95ff4292651cef14299448db14820c2c31dea2827d1102b74f0586cf8c15b91a189ad8f6963e21cbcb144b9e06d8c21c51772a1e65426ddd58cd70a6f5eec2b730de10eabdec7c588a82b6d0b1c6243e2c5d83710ad591caaa5b80a3e5b6c78a85d5d9171cfdcf4bc1801a104d061334b5fd278e2d0231b47a29335639ab6d16ccacd43eebd37f03a6a0fd0a497b78728c66cb5d8c05015b002dd679d4b6ecc1b1be57484d63a2bbe3330a21c5fe0b753e9ea80eb341f87aed22c8a8cdcc5d8ef1aa2b1a9ccb3533f5a055870dbc1c5a7c5a1ced8a780548b4b04d576009f9dd846c8711ec99c893cff5e0aff94ce19945a633879854b9de3978465664b806f40438f654d66d4906a642d003386ab92c98e400c474e0b9ac84d9b13c102d3c3c66b2b6ec5accb635d2afa3fcfe618f24fc8c953f8f1b0f8a246fd85ad3a00d4aeeb6ac915a89539c8f02c9d8823834fb303604cee3b9a9265cf346243fd9da2039bfd3a58a33225c19152fbc2da08972809bc1958d2c5fd816e995c75df242fa26678b2b05e5bbab589ee9426b874af714f7eb1c16f86d57606ed504dd4b523b2e101aa525c1c516ebe5002f25e96adf9cbbd0ec429684c5cbeb3079ebc893b6a5fe887bcf27509ba63f2949af6632fae6993bccc9f5f3baa976b7862bde340989f7c4616480a05be8122c8d31881a8cac52a5106a7673e34e89fb763e555b09c4144987c8b654669c5c24ed8fae69ee806ef8e140a9c630031b2806cf33f6b88cf722a8ad78963cc022e51f9716333f516d52b6152efd926b8ad1d8ba7a1c28def59e9bab70ae2d4b25d330de77de0c34308d9641f0d9857fbb92b2466960621078154144e6c83a4493015c8cc94996b4e8f397bd6655ee3f496aa3023744c911b861a019fd04b8f95ed04e605abae63c7c16d3e20820f049e8eb6a1b1236c710eb094aa21ebb3c570c86eee385b5490f230719e781ddcd8df6e73088202b96519ee2be7dd5ca73df1ae4b45cee3e059da5e1f9ce7ba325dba2dee3ba68c86721253fac6c594d4f15cc8950d5dd581fa9b00aa8adbc14a7e2878260e21abe8321f4a32dd57c9abb75cf2bc31fb1f32953321bf6dd96bc9ebc6c579024226a4e1e80852909057cd5dc493ea61d70a8430020b900d736b009c3261003c67def0d9843b86a2e4adfae24ca1134264d730ddbc314456067fc2f7c6c86e400d54e9474abeab69c763462d91254fa1f7e9e2cefafa5a933a29277d59d0c302e8b1f4dac23b70a0ea68ec29d8c8b2e03c6cba65c09ef36f734e03e259743fb223b7a9cc7f9f178542b27432b8708229a25c2cfdd3097decb30b9104dfdef068a0f64bb6f40e0f5e2e845aa0f131efe0e8026bcc936762667b121b07e894628b7088d87b90d00958181774c2dd1225d324c69db625801f846d5037ed7f1dd6cba6464a03883e0ed863fd746fa11a91b987dcfa7319ef66b91f0a30cb6a48fb3c61be83876e063dc2fe0a88604077ad5d72722d4362e902ea1d6ca5c446b7337b52bf04d4f619e85a56e45aa24d7c5410becd53437cbc034e61881aab8ff180529920bedfbf9f9bc5ec52f14b63f07e3a401f625908af98e435cc724311e72e8ab8594b840fcb712234de8a763285eed2208ad237f26b90f725a4517442262bd196ece29b3846925c7a3f6f51bec5034fd4842b232e8f8160dbfe7adffee2f20506728f57f1d6fba31bf34074877199512cad23276a992b987088370c779f1f202c6a88fa738c9b995289e1063ad82de5acd674cdfa0de7e168ad991c03d04e242263fd7c6462f1a127366171b1ea65047d839cc6fa4e5c7c577070d2426c82f5a90de92eae3750e5b89f564cca48742cbe822b29f3fe76b7f3f97180cbe96a12777c18f94dc073cc081a3fc34471dc7459b8105a1e766d94115ce21876142225803e2edc10afdf0696e7f7d645d6b07a6e52cb379a836703d291d065d63a735f662746ffaa0b378d61be3ab450cb93373086f222071766f018a03931c24d933e34a5f95d510bc8cbef1aa05473673a750205981c0b4e0bc80d09ca41d4a22ec90ce785c7ec252c53a0afdab88389e06429784a8ff2f1833c10d8f705634c736405a91e0c7541a4db37730425992efe4aa2180df555550950451f7c42ede3a8e8ad1d5c33fcac63b619c0ca080535e0663e1d7274eaefaac624e526621219723a45d1d54258c4fc23dd8178113fa72e29f72edae28d663f8279d36a4ef1fda77bba4fb794e1f4c088f5228cbe13262e0913f0501e88214be809175ae16a23571f9b575158cef2525730c8bd49d12903428711350bc986eb9eb6c2e80f73469d221596f18b9801fa8a75bf1de4afc01d56ded5d9524b330dcc2ad0a22ad90097cb03b8b1131f33471dedd4e7115065b5c2e62d0248b81b3df919432d226d9edb78c43f8539391a9dc39aa04e9de016200f8e7c7162f62b1dc2d019a3226420a5f36dea026949249e814d41f4c75a5e7201ba153c0a68fd36f780220ca700da159e1b7b6061a91f9e87342398d3709a6af3fedf757d1acea52ca378b1af105d78e171e6b1ce28d2f904581ea82ac325dae6521c9acd7589259717d32cdb4f2c7688c635b20cb0fe7f828faa28454308323405aa0060b4b255496665332ca0ffa0e240ea8a0321a0701ace322b1be2a1ef065d852b670ccd7d5703220ffa4b9d69219f704bc29e51a736dc7912d9c355099f3024952627f8f41e67d0278bae2c0cf63f51b6551e11cc512f803472df9306b708a7a4a813ac0463ab7b4ea0581ac8563d39fbfcc7c092fe682202780e60f9a802c329938bce7f1b40fb8b3f21727f6bcd547e9574facb3e6820f73dee7d6b69ff807d1e0d504453b6a9b3c38d8f29a87dd4f48869584c38f30cd5db575d4451aa307f8d83b2a7e5ba5ea35d9fb1193f5e128c0d0442f0a69a3806b33ffed120a95b232da26b2b7537c844094dff1ffd370f6f3452d2860f957a903e9eacb9e9ea29d7a3b026a9f98dab5b07acf2b8564f233a31c5607d0e0e0340c75b263959af551da5fce2de8e0108526828842f8a35bb3b0c37cc016f6e21c4635eb78265dd7dfe09add3cb051316c120384a2cef4c641970bc621e824fd8a4798ce3ef3edb01a1443a202ce49a4c8330fef41f3e375c9e43a434f06eef07adcc0188ab4080a91194f754c2db4e882d6fc629788193ade617a2ddc0697a661ac0b0485fe9ad4a5f899c30b7cc7d9eea4bd53e02a32dc246cb2abb90888d83ceee53cb40a3678556d718d9a285d0eb5f641180e3f9de5b23d9c39a920a5d96fd78bbb1bb32fb65c1ec4c2a6574d819c56c3b4c2765cf3832868b7394612097029190101f9458b32a36df26f5168984eee2732a6a9436f9869a056843f2aa806a9a8b574ffe1184828380e726b953dc95732239a4e40ae2c88374bf9a6fbb019be53cb9aaef78a221d329963cdfa7ca4f4052aff42b820fd4eb0444a82e9910e821af4bbd273f40b79546cdc085fe982c5064c078c38aaf8ded8b54f86dbdaf27d294040493e9d8652910e718827bbc69b5b25dd5f8858b6635bbe7900d488925ec6f1c39f1d38d469f5f990e1370994f701bc23d128fcf96da4d0ec9e6f83c1abc07a5ef0a57479d41d945bd015a4f1443403f7fc9bc0272a343bb5832be24c9188879fafca4a2857b1e7a4e4b92c7cb97e5610c5547d2abeac07b49bed4d8fe95aa83218341b2743bbff33ee8882b3ad3f62940a8405a4b824310b96b77c999096a7646776a541d7523e430686c7b84a995fb45327e04f2b231befd5dea2e95249a9ad3abf46daeebd724ede623adcc488ef0867f29c2c2ab1f8d60461bcc3b96aa0c81dd54cc2689914d98bb1c79008e82899c150664bafe7c85552fac92d50d230d48f611a59de72056029ce4b8371fd6551ddcaf088fee25c2aa3e204f8d13088f4732aeddbced3e220ec49b25f928057924d5da67a61967933528598a779439e0b21a55f7947a06dafa442d4fc5e43fc245dbde919aae61ce3a96108d6b2c70003768f31beb71b0ddf8a6d28f392e5ca0dd1621a8674d57089a0b9cd909030c435256fa0b24e85433787d0ba6a981d1c2814aee5762aa0d811d14b5be549ea241015495ab83894755a97fa3f8b5d9a8d16652558d3b45f4cca96da643f588198fd535683a395e139cc6d9b2bb6557468f2880c23e7ac7c6cc0c893612b55f71eefbc2c837d0c1599bcdbc51238878a5a23bc09e54a20015feb02f35930135b0289837986132759d661478063c69c9c4eb474c3d394c97843415636072865a639c0c5d880f945a6349038988209bda1f1702b3e1b30e02091a35afd34a7977747e8d345d1d360322692e543ff33a32f38b4a9b3a1eaad3b1f08be813d8408491d9e08919e292a078dba6fad369585b43f5165af15ed02d8e86605a03ef5f99b0c7d92394f703fb29bccaf2ca0496b6a35acede4711e00509f863fa9f8c216be7c6643808346eaae81273bc89d10f1cd31692f6e9ab974c1063dfac945a04a626811677044857b3ce1513845163f1a21c57b1b3a1edbfed2dadcf26eb3ba89282e9f56c5b207a34fd35aa0156748289d3768c5f06fd39a53963c84b54880ffada98f2c57e2ede12693d35c7da3569a35869402b000ad955aaef2064e6a21ab8ddaad79193deb1e29b3e11e1deb13e3a44851524f8812433f67bc44fafd16fb1c585e9cbbcd80b75ce0ae31a44e839fd93bf7fdee1065cb63236cf189478018c3ab0f10142c174e0971986357d1c080737708e3bf96eb791758e53cca5ecae497264753ad17c8bf37824cc8b1cbf6cd6f1c89ec570d95228c0eff877ca5cf193d4df5bcfbe4854925d6c9b080b7f954faeee45ff568ef3a7f9c3bc8aee81f96368dc0a15c1d8d75463c1750b0a2b1b5b6a8b68510ffdccc95563e10018d9adde0981fa7e85e5caa0cf2e7257dbf163c74a0a6d0899c96a26d0f4329546e552b33599e7e91988a395d6b488d6a94c58542c0d882b4c4475676c6b09510222fd5ab1b15006ae85a655fb8521f4cd42070aab9dc2862f525bead8190830bb9fbbbaa7e879bf2fe44cd68db110a9c28a11f6e8cd979b51c19f0a66213238855c50a2022d5a72c36ce21c5aace10361f5c4b333890da0d2040df2ea7d934805aa1dbcc453a136f353bf1a909bcdf83eb1eef18b7a6b8f345317b0d86bb99acc2c57a93a8a575b6c515b88aa454f93ff40adff2639a009bc930389ef631d215f304fb901c07d3df3a13714cb4afbde160bbcdf9b9a446b686c67cee4e5cd08e59ece417bfd986331b2c0bc441b1845bb8260a3b91b2b1cbd922eadbdc5982c3a76a3a09946f1dd8624afbe24aaf156a7a5a488a75f03f7892d8df09887402ac70b3198964698360013f4e17adf9e70fada40b0f5b3258177665cb2d0baabedcb06079b5380b5f4228cad250d49669d95b53f66d05621bab7901cbf5f30ea46abfd4d263274d87316ab60f454ab3791cb28636754817b1e54aa328a24c0f537ded1396f01931875410fb62443f8c375f87ca7c9d2cd85b53f56aec63be022ddd24688bd70e4b7bb9883ec1104d8962fe3c87ee314b4bdf8f575e32b5488cc1623d311fe737a3305b28c99005b6b64d870d56c94b6d7d44627516b4cbbf6f1f01f04f4be877dbbd46e99a29e79a01680eeb46de61b059ad22d54d35695fcdee16db50cec2e4e03c3f6ce07c9b4c40651774e41b08bee9932d05e537d4f65646b3a39c57453a6f9df3cf1ededd11dfca0bee136c020887db6089213d07a5f29e79f700042651b7d204cdcdb289b94330bfe9e1daaf37ce820dc45a7f41f01812c950ebe55469e169725b707f0b4f62debd2ec837c206fa5a4d0832013c922d8fd635faff68f38639b9fcedf10b9e7f918bce0b6891fe5e5637fd486198977dc1ebc5baad18e9d56ff80af917f7f90e0b9ed8d28a2bceba9a6b87f9844538d9dcd78386f26fb8cbcfc7a4b09ecab59b0945272c0405ccf71f90364543440a8003fbdc4b93b08ba6b69abac8c7beaeef9f2264e6b9650d82af858c43d94d21367d18b7166d594a0777c4aeb8462e7122284d40c030398d1e1c4ecf7d205aba39ad945eca7a2237c3672afb9ce03d6ca05ce39a15943b3620f84c14ebcf4a0331748265a37a724fb736541b1fdb4ac99df17fe4d3e972afcefd630547cbe411f387803581c7b1553f9d78c58076fa480f726243a54ec12a03a09729ec40aa351fec651968e5a8a8d08cfd01a1a71cf325bc4d6df72e53b75c5964202c9ed168dc0b0a2477ed367015b110ec6e93946fb115a97c0b216a462d703b62a9186b5b1c908d719566f78357e32e2aea3a22eeea9946fa0196ed813af1325f6484808f662a6b4d8e78b52d2ce8f14309bc7dda6e08a8df0ed20d218f845f568ced1036fa6e7055bf48d718d65b9713729dc3f0021b31041bc0622d8f83708d6e5657d62b3a1bb4e187661f91d68baf89f5afec6327bbaf864f904754b7f8700d6b9dd00769813db221594fc1ae81e46eaf586d1b55711025cf26ed0022c5a36a39dbdd448ed297c7a6da98c7965755db6392fb2aaa7cc481e602afd092015eb5a351375cf0b1a71c36c29c4991ede3ba4478d5b226ed567dd0652b68c6eb0735e2e8f9dc8f238c4d366878863d16e21a978d5fb5353f6989924d29f9cdcaed65819cf2025e2db251119e90240fd8579eeb80b6ce3a878c48e2279c9b055c5f50fde2ccbfa2b93345912bb48a55a380e7a591e24b8fb384ad9434489969f0cdbd63c61a6a65ad5ec4faeb52806574865795f273e08874ea1154fbacd62894fbbae7f3450d13e0718da5444cd975528f9bd9b09c24517907fcc52984afad424798f507ea75e849e38328cb7cce6a0642ef47f10688df17ef20cbb84020831913b60aa541f783a0541eac5cc16f6ed75239d6bf6d87b847167d34702b3dab31957b00c0660474c77c62502950192a0a63f201eb03c8e574b4d91a8572e412c95c9618c13a9990ea03a4a59190c4a3ab6a9f301b6a93374010deee37c59993952708b22308a41719716fbdd84ba01c4b98ee568fe0defd72a678b71f1ab4f2a982399d5418fecbedade0c3066064a99bb9d24bcf1b138472fc97d4b8e3170c471daf3e621896f67d49faab0115088948826a5c71bdb9abc815ad840112037795be927803e9e126bf0bef21bc7dde4fd7afa16937cedbdde811b990bfe29727ce22d41dc6b1bedbd81d8d61add25097f1863d9c0d72562f47061f9f72f4e421100e181496f80f9e00eef3c2bb9e3d4de30523d76ca885076cf29c96f91243d061fbdaa148fe7b81be2228ab0ee10eb7536e4dd4f5002e4157e940bd0eda01d5c3b2c75756071ff7b62f6449422b0da3efbd7446975f38696a2599707aae57aeceacaa08a40b976e4605ca2274798c826ff8e20b9755cea7bcb4e887e8b3c0e48a410fd49747d6acea6844f153ba8c04f01e9201dcd875f431e61cf7d15dd9d216c58109ec390ef98d8fd9c95743ac3e182bf34d306f9bdf0d45ad754e61f8ae477478eaa7be04773f94a502197f8f7529169719cd0f98652afbc58e3173f05a38fc46473e472a27ff5867885d723c9fdd0db0b6e5114dc6efb4147414beef73b018b4fd4e91465061207cb3c3a2d21846af5e0f8106b2f93d6065a77d64dcde827927c702420811b48e6cbad737017765d8556b87ec2af55b1e02d90f64fee4e542690c606f1da299c1f737841181e2048d9002c9d35cc39388ec5ea31e903b782bebbda364343ac9331697f444ac8d3e11a0b21f8f0a10c682e2f220a3d70fc063abb9a1cf416c6d4f245fb6dfbab2158e0f02063d71eceafe0b469bef0588caac27e0e5a15eb1938db27bdee18bef5644579890e5c6a0f19724a3045bb971a3ac2bdbe6164c9c41d86586c806b61172348028da363c66ed529c4cd328601706599c2b2b45b54817f4bb160a3805b4245b47c5a4ec82a9a3ec54561181509e9de12e50506869ab98256d74419540832a1a161e9196108e4226425f806b936ca5a7b8add9e091239bb6ff447852223889aa325190e0b65e80efaa7aa8d332e16c8b4844754904a6e18bd688ecad025424496ef0f333aaa98da8610c1f009ff4b74df46cfc2785d8968055ce53ac7bd38228b8f7930cb68fbb8971d6369626a24b455c8d24ae561721cc33159b15d99527ec559290d713146e6c60e1d14371db8a0f245343f5969f92d528417fab78ea0d622c288ad4048be20867ecf2c328bf7d45f02f1bb047a0e7a1a38a9094acf512d9ecaf2ceb1448223d236545a841496bd8b2f5289e5ace8207637e12ccd8ec7bc09e8ed12cfe3691630a706223cf5923ce6369f1177c85a6d37bdc1846a94617c3138011cf002060cf48dfdb3894cb97bfd6b022e4a69c801c9c3198a6727e8e4bdec592daf84391f113901aa20b2350c685bf4a621048b5e030ad2a5e3deb2cdcc55a5586836e76bffbb0f32f369444329be46b50c989de12f346b2e5624fcbbc95b4cc98de217551361914f9b913a841835897d6a23d50af08989e71d39b520d131115738501116d557292d9e767a73425accf917f59716ffdc996391ed97f738680a699478dfd395162f13406036834ad8617d9369ba6db9775a298a44ca292d5ee1feeeaeb33b8bebe430e3358bfcc19a3d40987fafd5a979e9c3fe256f007495c0ae04e8cf567fc1eea64fad210339d51bf118bd79f485e31dbac5379bcd15d31a7f9d1b6c303858bfbac52b90cb7cc2dd8c68c7b273249d90bb23dd0565805e3f077ea8c58fb54e1ec95accac65b1f8cb93b6d65ebac59f519091dec22d91422e3e89d016675ce3c957ec4bf12926c1311073908471abe5a40ba649ce749dc9a7e8ce04bec9cc50d77bf8516f297bcd29da35d14c00fee0e2874d504d3626f1f163d1d5fbc5728f6070a9829a836d0c0d47fa9ef81333ca19c710ff7331fe7e14dae315339106e2afc498b34f3dc4ca9989591c0b50a3c7dd634f0f79c17444b7076b3d3a37c057881ca74b5f554a1eca82abfc468689ac0828196fe5431244b620effe5a2ad11b3609ac9b1a3a160ec5af1beebad47be2bbae2956928ad3b70535d58bc11d303c7a3a4697a201b29d44f8849817c827f49f9b64035fd657aa22d7c097ddbe325e606d54c49c5d78e18167f00aecdca3a16ca5bf52572f76662588ec869d526e233f6637344550c544d0ea0eef46734a44795cbd8d076840be6e3673e319775ca95e4f351d2115c99e73ef451ac3e913f6ddab6a9e638b8addbce145bfa595795d0d2a221dc8c2d403e63e0be85e7b095b26f100d85a9ab5e1d3abafa5532f3e71e0074c74e7a100787eab61a5917a8bd859b49743ce12fd925bcd28e230e95527bc646b69ea4775c6939f66e6f5a2078081bd34a92e15b08bf08ec73b5f31c5c33bbe184d076669d73f31b6ab54cad0c69abddcf60b3ca9aef79509480266576218c4bd4b50185f1d7feb093ff3f8a1481fe312822142ea7b06c190be48bb6020f2016759666bb5981664756fa0d1dd3265d966874571e4915e9d805589b3b6512de8e344b58772685be5119ea43c50b1878e700e86c24c59a081b845f86b4e67c6b9ec02c5b99967b395e9a7025b4876daed71b0cb30d90916208e2ab924786d2e8be5e6db5ca6d34dc2c84e97f159390645dfba4c6f0c931f7df0785bc051e4c0e1456d62604bc515ba2840a8bc5b5e2213be376523f0ab7f7c42ca60977de3955b2ac38de9fcaa4882de54893ad903d04a4b6a424fb86a767147c9f596319a9a667a562b6bf5fdae1cdab680406d9e7390473a074bcdf809119608864a922af4ae4183545dfbb190716922a3a2747709f0c1d009cc7ad4cc6deef1435b0e90232e5f8f5b548bfcc3b496cd36e8e9aa6d740729dd5a47f597df37bd8765092b76ccbe7796d07a3809d3a84a329fe41765b32ae667ef1a561d2edfe4b8ee21d3e6f2f878c5e24185f0ae91a1f185f70b22ee69e4adf6fa35235d0a8eade09bdfb52899e524b30d39879d20b0b2228d15792ddce9b6e03a354ecd8560f3ff1c62091cd76809bd3c2f6221dd181b8094a37a74c66af73c001187d6bea4f729665be251eb49a70f64759ee2045c25e7dc39c8ad576a67cfe76deafe0717423b92832b4ab75601efbfdc35af0b5911ae8328e9c5208c08f9f7cca05fd20a862b4bfa1a84114598391aa64c21161a19d71b09f4f3a5efd5d887975dacd07ee25d5d7462058e2dbc7f90ccee1e967bdd057a44ad1c0d89c7b8d22013f1e9ccffd4cce086d11fe3230a41dbc5d0cfdce68ac6338a283bd758f905417f68be9ea1fc8d089c4ecc6ed8bd48925bf790526e4b69b01746b9ef640e01aeeafbc8e5c9827523e5a485bed843e6ea035af8ce226f75c8736aeb3d9c8f03ea0c4554deabd49d10a73a9a4c38d8fdaa00ea9dc3b7ed9e0146bc858f1f3c3673e3f0e1413b1560006e722b156164f00313b5c45aaae0c9a9b7ec3d92b20961fbfef1b9f5462b85c2db9db6ebf3f1dd4af5b378a62f7a23f8daa8d907fdce4bf1cd0c7871e121afba90781205039f34e0203411d40eb873382a6c2995c777789621c01a15cf798916bc4c81fca241dc82fbb719845ebb732acf938dd450d72ada5ce9b838e0eb37089fe4f814abc9ad2f09e44c2298048eaf6e6f8b954d1416b82f4e0d4db50ba96dce267db46902be740079b9dec5fcd940921edecf3ca5fb3f689cc022cebcbdec8cd940991eeea1551f6d9284778c14dd8967e2244d6ee3c82c34e7c3de02d1b6220ae17f2d9a08ee1ed8786098f930ec7dd88792a804bd17bb99fc2e27ee46298b381309dca7028be9a6bfe663816a687d67e673014df7f1a704a6e0706592cb16de211692d486ec71abba9a06dc6a9a75f75580394bd701b1ed205e0d9a00c06a974dd29b03b133e01ef63a1bf2b9496b2abc85a877e3371b51c0346978a04e6eef50a20cb235c0b413ff2e0a478b2293a921b5b6f967deab062eef48f333cec36e51ca7a67476bcce1454251c1bd0bfcaea4997834f5debe8960b558720a428edbe9c14ccb9135e19ed6335c3f41f8d4f0c4d545c1d59a836f11f95c4a6bbaab9c2e82a422785f930fd8c936dbb5b9c09667c497b2c2d2c218083d439327f8fdddb60a859e9e93e17b462fe04646621f4dfe2ef27e4833e2d98a7dcd07b4493239f86791b88b417f90ba802996d596c9ef7623d1ca3a83f72e305d58bb2075545ddef981acb4a0a2c0fcc77c7bf048ed58231c0119754ad1842397a5291fd189d3ea72b80c6351476cddcd0352cf767ec1119a32e9514a404349bc566127a5e520377245ce1d0aaeb29f9387e6401eb59d2f9202e5be36d46899900fa4926e1f96f30cdf13d47466834079adc3295bc6c7172448f30f1b5580269cc91f4c3840fffa5166a7acc605fab865a0a1f3e5df93bd6185148257560f2ecda3d83182685b9ce0c8c28841fda05f3c8252de5f5255fc5aa2e6d3a140c18ea6a4e67a99ffac94b481e76ef1862466d1e5bd9a678627be9101bf4d7f7bd2f64f575f324547bb173767fd690573c27b511e1c8e330e3250e5ac5b771f421cd063509877a9209b4964b7d69b1eaef2e545da2b64ea8fcc6b973a790e9a8bb87f9667b65bc855d3e8d6275265d10b6595d9910b77555af2d1a8db7cbb4a0095d24f8d61e60e21286734e5826cc9e95cb42d075397bfe8b6e479ebbce4f6edad02f9e1615440a66260d8851d26a3220ceda0258b55877778cd59b9b3c727d777b8072edc59a931664816710931bab7817807ac85838422517640a151eaaff4bb3b3f2db0b63b513d5bddb6cc249de95430a8a6839589755092bb673d8329c14394d2ac20132fe3b95b17b0ee3c2d22de70b7dc661d2a02dc1ec80c2acd1e98f5d3fb00e2f277883520bfac986752a9501785e854ce06ae3b7d2f146054eedd2148071e084526377849ef7980ce24912580cb9b81e4b0bd2909dd0577a69fba12d3cc332f0f8f112064a977ce43d2812f6d11ae918f1d92ae9a145b5fa06f25b6829114f505dede4fd7ac0e29b2e7b1aef6731f3311e06cd0b175ca4b31143cee1f7bb93aa4cfe4c1f0aa625c082482929b01599f0f4c4aadd0d8afbc3d00b963cf74dbf0c3b45788d32c366dbbff7ecc37fa9377aeb18b3e72793299e57d71281fc16c5cd9d71410504dcd3f0e42d87b3ecbf3b7f0cdaf005cb6cf5796c411d637e2f16a411ca06dfcbb15bf4be091b57d17b12b0bdb2c0a4510d199a4f7645058b3c3c9ec494ad064f3a05cc2ea3a49489879a91d7eec431f5b4db536838e20148e7d68391c4ac04e200dfdd887ae48b62850f52f716b8501bf55617ad503ed3a449d285d6e41c2670f36b5fd429dd53d195cf97f88ed0db68085b78ad2329a919f0d486caf5e522705effc913e2fbcf314eec0d25be7b991e4dd9e91fd86f70bde6112c460008e62d936ef543f424259c36580ff9507fa093a85fd09bbab4a7f4566a0c145d38d0f9b4be5dc350ea98ebee3ca0226972f9da5bd8b59e9644ad8de0fc06048b59914566b28049547b24c89a7450bfbf7d746186b794f35f4d8616e2e866c4863d39cc81fcc170fdf4f324a942e9f6c2ce1980292d3fa1e9496034d2a01a75f63fa8547300278044cb08c36c25e773502c58bddb318496b9b7a03e5a4c602f479732997111650cc95ded5213aa7e68dcc129f2eb2d4a62d2b5247f3197ed8a7ac3f434e2f980ab6304062d27772c519e2a8c66336d69461eb955cd67ed4ffd9f0176c35455653178c1e1ed6f45cd92e32d95df3b4992a2f9a29918abbb5a87df84705b6ec330992e98bb57a7171295ab04cb79e7016136953de23e918492aafd52baba2b5f416430411481bc200b266a881b7852b66b777f9478a12fa11d13899c2d70fc02f633909c81b767c661a5deb1637b4a956f1e0f1d21ea38a12f34f52fd4f5c52a596ba451f7e3b99dad0eb13467c4ac2426fbbcf94762d48ea6526378725bd65ccd31814f584e9295e766143798aef9d1735ae20b0734c8316d9dbf8a23bd97c5e8397a75bc9e786cbbd5a02bb06d95608e6dbf2bfb67a23c1a711908806ccfe3ff93dedc9fc893fde5e96efeaf98c581efdf8e44657a79bd051e8701f80d26bb1d1e58da901957d65be80badf088822c0517d01934360ade9f1e0a84800ef9c4331394122d12653cf8d102d39672e448e9347e89780153805c6c9a306bfc94be4dbd32ba70b8a8ebb261bd9906e0fc68cff774b7ee27236580381917209adc4815085fc5ead9654431fbc6f6a32f64f41470f8e636551c40186d04058b254b7ae382402284233c7e9f27cd2390c98a39936be48b3874378a92a837f49e0c4d2e105f5358851a116427e23ae74a6df69a5783161e481b7d3eec05d650273e7cd91ba54272e115e110517f33323f2d4b51c324795e5b47f6fbc4bec04561d04cc792e04d54991604de278f135ae00ead0f44a36d74bec0edc6ec6c3850862f205342115cf3547fb2e72c311dd8908e89975598243d5a7b94f5a433a4ca3b00bf4b60f1b56aa0a3c3ffd49d072bce1d89b835e8eb71ca8176dc4b8cb3abf6662e44dec9231e5d732027a16d74a204b67b88af9b81156cf8c2bf1e146eadd10b29e191a6423473d38dc70a01e8e11f53aa7c09c946a022aad21585e30141a3707a9754076360f0fd5ca4d883532674dbeaa218c67ecdbbd30679203e3333349314040046e1d198419d25e6bd8dc45ff788441300010ef732d7f4c2f9de2bd7c6ee78f83ccb66236f12a80dfd84ddd5ada68c9681329654a520642054b05630520e71d4a032acbb23c6936f679c76d26eff5721f7ff9cb5ffef2d74c00642c25499224cbaeebbaaee3008973722b913ecf4d1b574ba0b22cb9b2237548922449afe23297b9cc652e2bc000669c322dcbb22c398ee3388e244992dcbad43ecfbb4ddbb812a82ccbb224754892244903cc98329dd1d9b66ddbb6d1d2c7cdcbae2cb5d9fdcff38e4626ddad8e4aa0b22ccb92c63380f741c02e974bac15772cd319d148e9ec5257bfedb12eebb2ae91461eed576795a8ab33aeceb63ad3eaecd6d901bc0320000140727f027e90e025a023c127f72be055a3809a1a6f5a63635d93ea18eb7fb5ea587fbbb81fb711bb9a95c776e5ce23d1c650a8a20cb6a73d0f0463626464f2685d3468dc68ad635ec35291d2b23ccb52f4652b8a3260ba715fbead0dccbad9f49bd36bd9bf7a164d09b38c514aab2542824c4963b67a168dfb09aec34e97000d9915486dc0b3cce195c70a8113744860088857cfa2c93aa06508e64fa65ff37c10cc23727f005644274f9f4c93398ef3d1f4b2278f13a8ec91c119963f949474ff8322b12241457ef2e82d589120b0223d140b1458868bdc10212232232223e204952b3009446c4024e8950411490441c4959281109d222144e164ca0a788c8865e1040e7870831e90cc6e966071a2c7921eb60b2c43453e86e0418787123c30266605ee8266c03dc049f024a193c4cd902c2d8c91700d011a32c510211f3a9c74b3c961337948ad1b5b7bdb305e15b1681c4891d7106a51c1a9425af9885cb9bf2cdd4d0dbfa07fb2cdb24fafa496ac85a32c611478b445b9df6699750d51d714754d25b59674303afceddbb8bb0d08b637bd8ab85978fa9d339abb6631de366eb5f219db756b82b59b3bc6d6863633fbdb77b7f94ce8cf61335dfb726c9f15614ce0b81111dd94b99ad6755aa77523ebbeed5e086baadc04ee44ede3bae7badf4459d6be13b5aefb6ef4de9c73ce599435ed29a594d20eca9af6dddded76276bdabbbbd7a1ac695f6bb53f59d3dedaab699df8537e0a2985fc917ecb24714c2177df9f4de949574ad69ea4759ef6f747d6349792b5f105b13c027f51f78eec1a1b0da3c023de4870cb9f593cc775b6394b983b9788bb737ff80c7c8739a3ff12316b9cc764f53bd9ac19f80f2e836983e43e780fce83ef4c1b3bdc1daf715fae83e7e03acec371f01b9c9c364e77c77bdc47ee77d7c9844b291075b94d1b75a4cf53ddd77de59309975220ea729b36ea489f7732e1520a44dd93099768267d236ed3549754b3965baa38ade02c7f6f583767dc4a2073bf6865ff048fac4c8eaec0b83dcfaf05563e83feaa7a9e83e0364170ab9f4db5a975f31955f3bcd23fc17d3d9b556dc09ef4b3c1cd9a1c47b22fd621dd7d27988ac0719a55739af54ef0e838b95a29cdba334a293d41a3bd331b2abbbb7ba874f75aeb48491cd6ce714ad901de604cd8b2fd14b2f69c884306134c9f7e3654dcbc1c33f55bcc91caf655d98a1d6b56156514c18e136a24e7ea2e150afabd31d4b18de73e47d97e73d9bed7bb65fb9482997efe5787b55bad9f8d76810d039bb8f219f75e7bed53da699fffd531b3f56b63ffde12056cf68dd3aead3e2947aa3f2770c52ab61498fa22106530c1de0eda5eede775f7bae06180f4799da6699a0663dfb29a351f0aa59452da1f05bea0a0a1a19f1fda0ec5ddbdd25aabfdb1f6521c9cab8d538a1428b8fe9d5fefdd9e1b53c8f747cf8dc454de3e472a95ca9b78c3d9459aa671edf5d8992acfcff244565aa86b4c1aa9fc42ffbe8b238efa95b6577bb58d1b75de472a61d30905a6542d2b979711871561e68c7d0f61ac183367e80b18311ca867f4db259b15c2545655da5a011a69f4ac89bac896cdeaff216027bad936011366640a2352ea8bcada49efb2ce2c755d21789c312142b208690869086998ddb843e6649733ea2a8d3336c3982f02a917302f4d1a5900e3c565e5a4b10531562daa54258d2e784b1a5f107a9734c240d448e30ab99371c58d44bf26f182c6348cbf54173b452a208c2a6ff7e953d7b4f1f2f4e9accb96a7efb26943f5947bfb5ca8ca9df7914ab8739db8b0df9e62beebd0fe0d617c64ef477c25fb5315bef2e2fbd10494fdbbc5041443faf1c423fbfb8a074ce9abcb898a17fc30ff12da8f0947541017d38f9debc56ff9858d414b8e09c7ceb5ca2ff451a3d89c0147ad3913caf0a2c1841508a96bced07709e96cced05f855dce19fa2da1937386be2a05a2c25338d2713ec91488916231b612a3dfd0b5d717775fe4421beb5cb16983e33a57f6ef5cf48a200c1d25a9413aa7adb106b176fa142e9b33f4450c4c872fd7ebd0c57f955fbc0e5b7290eca4167b24847f2fceff6088ee31fbda8f3606a4d79c712fb570f008fa83b169e37b9b43f58528eec797d9cb9872852fe0f125e5cafe315cd3867d1bbe3cd89a36ecbb2a7be1cc5d69f432cb2f4064ff0d8c8d5a415436c6ace2745366ac2feaab698cac61da5fbaac2eb3b6aba6aedb92e70b9d2adb9a3654f565c4c81124ac9e4553427d514aabf52161d6ab67d1b8cc889059e395111e23384e1a79b511236646805097111e233b312338465e30c242ac0b5b80e1773098274c0921e491456bdcefbefde1c0218fdf7be90cff300a991a34e0d619b35995ce69adb576364d558254d284951b3af0ea4b2a251325a480359dbdc9f46d0c66cd764dd9a70ca767982c7f8fc1ac699c66f9d39f2c2fa21f56fcbd1f46b63ed81d3b6487ec901db24376886c96d758937fcf306b341e93e5ef1ad92cd26b683b3ec37fdb305eadfcb59dec5a5019966c89c7925d53e28104fb5cdf8e37f424fb9f7e9c5c73c6bf43d34ce371ea3901a1ed68a58ef6a3bd34971551b16bc58f7a433e7c3c79a76b3e1ea6674587fc72bc2c1ed190d73011f50c7f5396d98d2dca69653fe9e064ffd325b3975c41d9bf45d3ac59fe3f043c8e76b27f0d4b3b73c67f06cd197f3bda79224cdfc6a00eba3ca06eecdc96954321ecef5f0e568b540c6b26e5fe709cc0f6095e5aad569ea20ad484d4cd28d4da228c09d503994e9ff15c3c17cfc573f15cb8b0c5165bf4773d67755babb5f6de7bb1e0b11bcb6c569658f07782667dba580c6c970b752a4d9424f194c94a7f0577ac6bbe3cbf0a6561e9e6d500bb566b376fe5b55aea13d3f770f8adcd5a79b8ab96a48961ed19fda10b8fe0bdddec9a91a5f4c3e174abd3469d3339b62f07cd756e3318c5f088ff8648952159e8400813010b01e380cb1f6eb83c61a511231317a1010b4304c1891022404a1f1e1f278a0421824f6bf3c1e1e6135a64ed6b091e1853649661e4092b5b7909165d8cb6ab675e564ada1d59ccc1620ef66828e6e049c58e592bdd94a15dbdda6a6db7d76a6dbd4544cdeab166291aed8d6d555baf66eb56b9b106012d09e2c2b10ac5346dabdcc8d6aeebbeebc2b1ee541d9ebad38563eda9aeaef3ea47b2b53454c5ca10d18c2c67193b4eee37dde4d850d0c9544da5bc69caf3bef3429f660df969568f0e6443f3e85dd6f572dd573a812a17981034555637952a567aba064d03a1699f21344034f32951d7e0401659df744eeb34cfac6b38a0ae4149720929445221ab9056f27c5a4e5a529e9e9202297dca21797e07ba06ddace05996f994e440d7dca7f242ab257802d17899554cbb7cc6a8633ea39ff43ccff3bcf923ceaba75e5f9d0997489f775caa042acbb204491d92244994cb4ea3d7ebf56ab56e3a6f71dedabca579ebf65f1a5e0883035356929ee7799e4703661a1a9ace655c89f405266f04f6d5bdbc1695278e291044a15028d27b2892e79d4ea79317d2c0fbbca7efc18ec860477c3cfa1c0c082773581c77646c7aef49a18c2298f4261229fc30c7d1cfc60bf1bbf7d8f3727c3fdad7cbbe62af579e3276b4506f0a4dd3344d0357315cb5d5565b3951dcc21ad06d89d4d31ccb0d8ae38e0cbe4c2ab8103031ae337a4e8c1ffda65f7ea491298df8e3ea0137ac0cd17f82fa01b3c6956c31c471c330c411bf10c7558e11c78711475618fa87ee3f1ed4351e53326b1cca64fd84373b9459a3fa7e8f326b9e982ad5c3c0508dc2509ba7abc29a3de6d455a1678fa9c2ce1e6b5548b34799524c06cc798011cbba010c387d7749309fb51c3e8c3853a977f9969654aa252576d4d33183b858e842a14b69d675f5c0aa1f7d083e2a873de87bef129e865c42d38f4b8873d8a3f42e6129873d307e979094c31e38f5a3c772b7fc965bc43185dc127e7915b6a8c291451d0c332a1cbd4aee3f85a3f7207716b1b1beaacc92b674c57a46e0af2ddfd0fd6f0c46978a9001960e2bfb23d9cabc95c4d308351a8d46a3d1c80698666c227d287164e5162f4de248c421cd9452aad39e43aa81bd8bc459e60d3b76b17db3af96ccbd975e7ae9a5975e8e5ee6f65c4559e3c4d2ce3b81c73b9b4d1bdc3715b73b2be528c566dd94195fb4bfafa5aa1742814be0af7cbfdbb6b7618efbdb9348a21d7764eb821fd9fee8821a642b8e232330cd251b3ccda24fc5d1ce9c795b6e1bc6be5a693fc6c8a32d4d1e164d45dfb661bc5a3d4b02797b3abe2096b71f4758d4511ac590bfd0a4050a345f7165affd8ddec2fb753b4dc9a3af52a60dfaa39f3e18fd285546c9a32da70f525354af2bbcc6079b33fa9f943ee59b22f77f4352e4fe4dfc5e083c8e7646931653727f41aa578c4c4553d10f81c753ccc7d12996fb3fb26b36fb375b0ed659f1d09e6c8164fbe9e41e75d1e8af6c3dfa22eb84bcc081bc099f266053f4e4d1f3acb56293e9232190b008199990885a652615459a5c51042931d8a06412ede881c8141efcdc8045084041f8b96288176868c2477bf9c0020e7082c0c147089e15dca067892a888c18c20e26151c82c0524514509228c9c201f3e70764469758eef7a857ebf66ae5ac57db387b6756dbb8d145915aada8727edc6bdca8f35e1be7514ae9a4b3527a2fc73dd7c2c9fddd4d4eeef7703e9ddc4f229bd5add78fcf909f1750f9b25165dbb53774aa62e534444fe389e8343bc928b9a3c3b343f6b848fa1dfd38ae0c025a12545694102a862a51452822541654116a76d342cdbaa7ddeeb57255a3339c99b6785f6e9f54a6c8f35d4689b821cfd1cfa75b2891e777396d700da3584c1bdbfb0e0522cfa7aef915386215a4858a161e2aa0d495940f30ca8f5feed7bcfee8d2a2e22a48eebf54e47eeb2d3c727f17d2bc8d7e5401e5fe3a4a5df1948fdcdf1b182537edd8ebf57abd5ead56abd56a61d3e9840253da8de1e6ad5c487390dc980acca30444ba42f261bb769982602a308fdc7f4b404dba42f2d1ae1faf9f32a82ccbb22cc9d7cfe9757a9d5ea7d7e9757aa1bc3953296faa6688426df96c5abc7969ee66ae513e1c2f07dd0f3a1fea68f645c9fdde5e0e68b6dd0f3a1f6645adc621cbd7cf4976929d6427d9497692e5fe4ee5c1705d70466c526c91fb395a8e96e47ecc7591fb4b9c11b99f6e5294413129b2a1b22ccbb22449922449ef85f7b2919a1297080ddb2849d294c8fd2e9746bc44a099923bd4f5e3f553966559962fb41b446e21aa4dd90de25aa1a159510b74511c70869a8da8196a869aa166b9411004411004c118dee6c28f7ec51d697215ad0ba0cb6bb8ca57d04faccc3da504e5fe2e86d78202cda34bab1e614597d69c0182e98f2eaddc2e310df8e0018fd8059f6f4108cf2bc61346ec805f07cc0236c28291a0fc9203e21050aa40a10265491e2d9423507e4422442154d0040e142c80d80108c88e218e04a10b29ae80e1489156a42c626a9c0d778009c1e96e19107b4f885084dcbff593269e04951da594d64a2bada10c82742b0cc9bd4299fb27772dbfc9f5bd7eb5a57d4dd3344dd3aca6695dedd56a4ebddac66ddca8db469df78d3aaf7a5ff5bc1a8ef4a686a49b5abf541f7f36b54ea1764da1ee699e0632653233192296952cb9be0ab95b8506caf57d0574085d628286804eaeef3a684e7d5b811b54a7abecd8a132adec708d3bc8dc4fdbbd564bdbbd566b6fb5f76adab671dae546b7f346d7fb3eef924adf2d95bc8bafc9e4dd53ade148535334545e6db1da0dc715aadc1055e586e0d00d471ab97e2ae5dd50b5c22cf77d9aafa68d2bc8f2159dc60858f16fa10e4f96e8273e9ea2a1aa7a586bedd5342734682206822db306041c2886ff8d126ac88017f0dfe044278f13088cc2494e1e414a436373f2431e6964d1096113bac51df9b9baf219df5c6d18133c030da5a3dfb53b6eeafe34f43c3f0cccd2012e6e252e77dddd9345a24994b45c49645d9e73a6bb89e7fce836a9bbde93cc5e265542fa90640756eed17ebabbafa6596b378e1b75def77d258c4d27140a85025329550b6c090c4ab0bcc0c4bc78217d35e842263e9fe41469f2e3eeda16cb72f2b135e1c191ac8c40b059348c31892d41c35812a5980d70ac0716234fa83c614eba4039c102745245ca0914958742a150aa9624ae96262dacaeb878346462c50b9328304c943051e28507030303f30206931f181e0d614c7a80272e422b61188a5486477d0995254cc8f87220531a020060b3682a4f981322146c16852999e1d110a6e4c8003caae487004a4c2d74a1925847dd53cd3ac2149394798cd9666adaa0b97183d3469eb024afcec1861664d0581012e6f039c092f89064074c8315d349744842e6203c5c1a86d5ea57311f83098f2530f4a004cb0d4daeb868a11db1438623aa207a614a8e0e9058c00d7ec0696205f9430f7dbb7aadfe2427d75a6b1540aeb5d65a9b14e11180263f30d45a6badf579e0539eb0263eb2d7b8d184070f19566a10a990217422e799c4106309196024f1c20b1bd41013eb99012646eabc74c1c3058b95575143cb0d1db36c28a46af53586145cf38435b1c19bb490035e4159419ea2d4605292839590a1f4130389490f1d3e2634e4e071814367c5861113397039a0615b32840dda122062b84e88e45a6badb55a772224d75a6bad158a5c6badb556eb4a8ee4bae3805aeb0f0d15a6e4956badb5d6db44965bf2843d9141b679c29accf2481313406c64653ab7206012604bc4d05539824e2aa42d812dd13d7e80c75902017f0df86629ba812101454729a5737eed9a0fa5d46b0f0ca6e3ee5e2dec26288b67838a32d8ac2da4c023be82b3602a3888c7058c573fa3dd6cefec549c3a04d580c305b56ad8b48041c302e722e9c10c168a186a0bc5e03774cc12023aa0e0718504899baa63f5ac9b272c48873cd2c43a20660db0a096a685c582c290f4000a8a4448d3348d1b0115e1386ed451244864db058704c80b8ea407a73d10c7708261485a9019a2722578600c122179942965400264ebe97eaf487490e454243164b059569cb4e04c3071040963e2f5c26af52c26644c6c914779c29898e591466bc16033589256026ae75e57bd3be7746fdb66731ce52cc9fe0070e066d10fc709a811b8384256a13ecd1e8114f7e1d09edbba26c668e4a34ab23ffa720c2057ec218cac8573eb9a171bd6dc440fef66c50a938be866d3494f51d0a66d62d2de1e94837e826e10544406bd7a852101fa110444104f115d18047b4efb9f707777a7fd84d312a6843ea24c097dd04f0113ae5c803c614766b92b75da8bb52ffb16052efbdae2c6be421a00111de9c9dcebc08082565d58a2effd64ef5b7e00ed2cb1b384572291be237d87ff0b471376f90fbbfc8df670e73d89f4f73ba1fb128ce93d7184b1a1b9248e302631073d42c6e238b62096bd9fb905b15c7ad268c22e1ffe12f6fe46079941a8b4adb716a964333300000d5317000020140e88434990c3589c46457714000b5e78465a58369b4aa391589443410a63208e618420800820841064cc54d501011e8afdd1e7a2cb7042620fd9d785530530e926d711d86ce5fa4d7d3000f68df28ac2e5255c7a2d59bc4f802cd7d26c83b6a5dd7363e1ae310e8e18360d62953f691dc0b5417c40c2afaeafe75d2a509b0d4893738c9e4e60827a992a10a3312faa3804435459220c0f45df2c9654356faf0afe857fe815c70713e031c92df60922ab826e6d3f49e0197363a12dea1ef131e8b33597ea186213ac73e7d77fc785f5a4294ca6e95117fbf8fac5a542f2fc881ee3e661f2064c586e0a8fab3fdf39a70497d03baf5f1253a48d6e11b7fcd5e6e2e8d513614715451bd45e7a5e9e9335220f92cbe703f2d1658c18ee4f372a8deace2f1c5035d6201aa32476d9b74c60a3d5b2e4717bba54b62b77eca41d795100c24f466b86dedfcb3b543b417aaa6413affb8910abdaaa1b76a1080941127404a0682068852b3b9b351a8561de616820d993a6366e7968e2b13bb3c0def5a158a72a6270a00add7050e8809b1610f1e725af1146837cd0fa5978b5149a59c04fd99d08313902ddf414efcd480333b55ff10f8ff1424510a947088a177065b3fa3aa77a8262aeff0161818c5ab3674b3984cc1cb719233de85a30699b1cf07ab890f9fc72780eb0ddf539d2408dd0eac812b9bd0785baa4a535cb2f5f01465e5011bd1cf66065dd3bc4226d96e6d79559ec9b807ca2e1906ad8de6003a738fbb670c457c883a5a948c29c756aa7f2d8564b431a884678678c283423e3027a16a41ed0fc75d2525c9e4b8f7a497d44e22b2df93e58ebc5d591cc8c29345b158f5d2410f41ebcf1a7ede63ac2c023b65642ff218e37253ddababcb87aae94eb9318ab45b43abf6668690917cc5f2300e462d71a420e3de21f001daeb864ed749d146df19ddb42ec84f9c44a67b9c283ce55af157e1d95169f1776cb310cd157da80b7c706fb0282f95b3ad032e6bfd9321b432423df15338bd731498b9b0066f989af110d285db485a0687a96937d229ee8c29443a564643ad078df68665876c78263f1e80b32c5bbe90d50ac662bc9c064d706f105c1c048a29a892abdb63507519bb4a362a6820a858a69c3ddbac0ea3d97d4b6eea2af48a3818c57114a17563d931ae0031e984a48d565afb17eec018aa12f86a2acaa5940d2aeef20d6b29883969214f0e19111b081fc12d108dbf24aef1d789166aadb100a825d93ac0a5c7eabdafbe47a9583e4baa7b2b2f5f0aab03b08bd21e6be7fc59230aa0f83364409025831110e52a1f73961b58eca0baf88d920674718f30d87eed494236e371f3b8933a3eefedad4c88f41aebd3d3d9b2459a02dc65e0c3729a91c9ed64a1788cd593bf0f8db4a28c2d8335e0aac67f8a013a658d51e99547e9ff70114e1eb6f2df872f8ee94652e433f6479ce304e71ad0a77f6ded3bf51045e0d2c0f4a544f7a3ce41663b89972c96bfbac9b3ba75dbb8fe39a1f5d35ef368090a716afd27d2756cfe1c72b1bc92c53b43604ebf05a1106277a71a8e0d38d60608655e29ba8fd2b3adfd5c68510cc4b4d72424b9ec7e54239c38da5af3823563a9ba3e34d65ef1210cd4ccd69dfb3432e7de7abf1b926097f66061c68da647f4fa6facc98094974d68a36e9ace387faef923901d305704131bb06025ea1e349206c49cd6a04cdc6383758b0ad2f90724a56840ac6339567d603252bff1a04e7ea61bd59cc856bbc8256e26052cc27e5531928124e3dc0d7969829d37d95112baef911d26946514f3413a5f3ac18cff926b67b8458794ab04c9efbb5004a31653497530f8392c1ce65c877e82b4a0746f7e65e61f0f4604bfabc6aae77ac8f0a6ba9a7a64c23a3846df46d8a99b87c3def4196d2a27084b2f520612256bf0de77850d5879356e83ba2286a06200ac6a94c8bcb3aab951d71ca6b50d2d9e7a1118d8bbad533b8e28e2869d46b680996e413cdc3bb86f65c81548272d3e82e275eac11ea21b336ec876019889117fe5e5b55f85733c558b8566f9a9c4c4ff034827392709dd679820039c13945edc77803b7cb0355feec87314e6170253c9c6a064c914899ed13027e04426e44e40ca2dc4f5b412b72f6ee65aa1ce61e2448100632236881a099ac7f213470984c0f924488342c6fa99e4491fef05408e82cbfe06735865032ca8340c1c77fa9aaf01e75e64ebd393d75d7548493e4a1beeac98cd01253c425a541dda7a3a9e18d2935ad0a53c6250018427d823d7a0352ac899bbc02a8fc603d361133a2e1a2eee2c51a995d30276388554d7e84a9065d6381c551106dbd808509b6f9f2ce78d4a28b80e97455e7511741e40751595462cde5be9af8c232fb4eec3d1898f5bde12b5cd8975edb4a1aee3ca8ce4e7c333576b13a4805d5e40aaf57cab7002123c5b0977a31b01523b6e520f8891135080271e98db58a50d853e4d716eb42a28ba7ccc27a31f5e7dcf8a261bc9d83c81073c767cbe778f4d5bcfbcddf50bd50a94a31a2dbb5809bb88ce2db7d23d587f8297a800d7c8a00bde8b7c883f21c0b4abea1306b2cc2046cb0157db5108cf411f793ccfd6a2090750491b666d1bef1bdd5c94c3cde9f72126d3365d470823b170b3c179d1ff92b28da3774ef9b7b7854243143e3e786aab9c022f9e5644e025cc635a5e34e8f89b570a24266685e68be11821d13193379d2aaadbff1b6ae99b934f923628f23dbbd4332df49b008eb0b7b8ea02760810119635e3126ef9abc92fd3d55d308d15b293d8a6467f27d01c6e9ab5f7e4d0eb3d746e231e12daf1542e7d72f18271a2759a284fceb8d1ba07af45f36c45b1f4a34824911416ee6abe1971b0af8d3cfc1a2dca0c427fd0e14a51e857ce097a3e2519b129ff438768bd8673bef7873c4a5512d0c9778bfc9f54f1b21f72cf251a4f07e78605fc2e51d63a46215cc5d7d0af0c981aa32b062b20cb95cfbc1d0335d31d4f0474d8a7cd4e74011ee51e0a7be8e8b721acaf8cd9f43c5a895221ff5392a821b4af8d5afe3e24f8f129ff2e3488af2f56b6bc8509c5abfe4ba3e3de5107d192692b22da4ce6bd017772e18372da94fc543e4f08084c138cbee402d56aee4ae492aab787616c6e1dfef01cfa56692ad514a5160bac5c5dea1dd3fe521ed6f8d82b2c0d3aff812ff9b3f257dcf1b69d4a2cc5f82d6e5a971f615ef3e02ac10cef12d2e94276847cc31a9101eb2c5b560ae79906a5ca236c3151df29f6261ff2d2ecad685a14274fe99cf58333470158f0253b761fb47771260d87ebffe09edf211ffcb1bdee4c9e60971a6addf4e3f024fac6f8bc63408ca05f72a12e0234c5d697e520b73b6d2d97635d3b15ca8331c1a25cc0e5c2e118c27ccb73e152f62a4b104ada459359d7fd5baa27c1eef20c8c435c34760824343e68f4c149fc70b10c7846a7a0c6ca6003fabb43bfd9898367a6ac39205f244c4655c8d78f2e9a284927b110673538a96e8b0a4454a14a7e95fdd16d095eaaaac8b6ab3f299264742a0fd2a8870f0bfd46538d9ca78a358b6a015c9943fea1378a04369979229d881f4391d094b6e9cb79c09aec4ac77c2730e22fa8b82dff34fcee1b43b8773e14467caf7d9055a0a23e805e878afdffb286891cdf1e32ea5e497bed3cf5112198b8212e4279247ea2368ce97f5cd714c909c3c380685f767238ac8069e603ec2f32351a38a0570c814e96424b36d0b4314ea8b1bce880ccbe0f8cc8bfe22e98177bf60c44ffb02d49c7bf1e70b9c7b1c54415d133956081eec140b5fac82787c0a2b7cd2c2072068a1b1f94a7667208a656839cbd9b4449cd172ee509b96d7eb99091c0f556e144e76d24f4557538791b87d6967be3cb67092af80dde07c50546e14ec988f449be5ced7aecdbd08ba9183262f48ca3c74608f777e9261efebfe88a47432e173362d36cce8a76d23082a65b36692f2699f29f8e6cd673aac9fec79ea2b0a7904b0b26dd0cf585e65272878b678b63091e9c6022d121353482a6fb907bcdb33a8dec65395a0fec5a18f4a43c9f57941f7a0118ce8572a9fa2acb9d15fe0e0fb78523e5aa31e2d4ffb6dc9617ed28bf13b5f376d4bd236ef7bc21fbf69a31cbb1ab053d077ff56460ed1778ebc12bf5a9bcb9feafec330a65785e0ce2bc1f76a93da721c61951d74134b15b3dea213f6470fc985ca04ee42397c3b4303a98dbb9d9dc10d8dae1e3275d3eaf7ad25ee2028425f812ff469bb1ddd45895e6faa8312d993052931361473b7203d5edaa662f85892156ca692dcd50636b4ad506bfed6324d6889f506986586b1a21ad8986d53296964725286fe36e20f3bac64c8d63cc3792f1b8c37003618b79c75265f323e4a6156ff195deadcfa7add390dbdcb35dd6e7a4278711ae4a16ed3731eedc935d7b9fa2b976d1e0a8423361ab8cd1df4bd3a9cd53877d6b8a30ed99b9e840a94383565731ca8e0ad25766bc2bf41623c9aeb6d29b03fdceb1af2e22cde2c5aa1779abfa24a6fd099c02886c43d446036312790091a094d6e5bf793814b33f8d79ab529e9abbdc87c943d94da181d7dfd62603a5b3107cbdefb728f6cc4b590252a13848648aa9f6441f59f9b68b45b085cf87a88cf4fb696dbd0a10191ec366dfe4d7f1ff6b6cd4e30ec7595d8e31ca806361312de772bca523a3bbd549ac2f9c781b8b9e07a943b94988029a58144ea95c96ebc13a124dc88c29c9850b7546ef2b54e6e7321cd24a2805df5356a0197a0dfc0fe866a05273a4e2a1744470e00eda87cce49d6d0b4cf2866350a451bdd50330913123dd65ab01dae38244032623070ac8082f074d59f071a7117731c59954aeb98d7b495ef9a965f41d435c9cc3d6cd3de6f2a8da1a655c5e9dfd5b2574804faf689f5992f449d6971c5203be70ee0b86243f9d4310e58c4f7a8f9b0260145d942612298ecb3ca843c54e3ce2735eadce152e5ab7eb659b96321ed18994468f55d4e1419f90e86901929733c8f75ccf1375d2356dd1bc4c04c0b8802cacd901608d9d75c343686b3169e2658cf8b2bd6a254bdb2b48e9589138b2bc6d4823b2d3e79a14b6c0946b2ba878b2b4ec621f77184c665674bf5ab6232cdc2d6edb78a6a0866a10d8c536184e32f2c4c7529540afae4c1dcdd7d2f1d5ce0b495daf5fad81e13d100b546a16453b8645804a013b448f72d993501309bfb5ea20b1586d2754b560a45b251d1bf4ab4038d0f1fef030557cc4c14f0496c7c096e828f6d0cfa18a5801df274cffc123da137b21db72f43be538d124f8bbebc9158313cd523368f14f5caeb82f38f8106c1f9b11f0b15b530e4d4d0fd4f8954124f06b94aca2e6aa7947e7d7b40b0d83e398272250cff8d8973e93d22031f3b9355ab9226b7278c1c75a5b0f3d1c346b091feacfce36968e8fab317c10734a5388b3b19136056509c5b660c947020370fcbf73267b6b167d79aed6494b81c1ebeef92536507bb96a2ecd828740889721f6ce0ee2bd51d6c144597c9a04128ada21c3cec78b89bc7c3cc2368edf8228e4c95a2a7d7c8adbf5afb1652ecb3e518d71e59dff03ab9aa790a7ac6dab1017662ca846f44274c915d93bdf87e9601efafc685519c57b38fa993f91ecc42afe7e3812227f4b2479a52a2506b5df7dc44518d9a9682e8970521c321561f6cc122023991bc9e87121453e06216ff71730ea0d9d7f97e8b2f9be228dedef8ad7c3683306c73a95da6e6491636a4d121e6f4c593290363f843c74b757c755c7ec63898d245bb4971eabe60a9c7931cc762bf11b9117a23a4f37da42606ad5e63a1001e83c4690cb297470deb2391249f2f0fcb0faeabde1fc039b0e0548c5e123e9f1d35add64f8e7ebf5b60c233694f49d4fc6792f92aa4cf0e06bf8bd3d6f32a610b0db6df2b3c737be26134ae90b79758f9950a10448131cca970c287ac2cbc545cf926345088dbc9682ba001eab64598b5539eabe58eada09e426e433e40011e125ab8a3cba2c54f046c28237f498819d7f54cde7773d3322248e2d1da4d13ecfc648b25ae8712315db971b46e14758c2d54a80ab0df11c0b955d3e8d663f6fa007ca2817fda1751454e58a2fdb5ca3e9856886780b3f52bc26defb533b44c2b8e200f9d50e8a375978c95f6358cd5e602fd5cb9a2c48b92b5286629eb8252f479a4873c8e3f049b9d07949ae2a90b3eab8495c5b7d3d41ee1f6e5ca8750dfdad2c1b6bb82d854012c7c862c3ae85afac748ae3c664d5487f81f9713058df04195444dc5573bb5b75a619f5e94d415e3960c2f61621aa5fea699d041099cd939762d72a7b14147bb329cd83af1274281122d94119f4b7c1e26875df7539fba7ca76369ca45c9a59b1cbb7e933b5a4c70931a835bb07ae21d763459652b42711af3e736d8bbaaaece8049177ca8051317a1d412064716a3662c0c5c8a517530e35c1cf8c81e8eec6183e3f81c9474c2a477db553d013d6000ed72f74777c92dd16573c39766ea5a241d2adac3869a977bd0d0be1120ff27d8fda4d6240a9f338ca59d1c0044deeaede384450ed0040a0c9f1cc54ed00d0713989790e4ed0456e3d832e18a10d7dce1e0da1a3861749bdbd3458cc15446a2c7c3b0e09ff6fed2e749dfde93bd71d9335991258aa888f958934c010e68849cda461c6ed37062cedb925757a684017121d671dbb5377017c9f0bea69218880c1983f92646b15a1955824e1e68c395b890dba5ce6d9788afa0380a0d617bd6b92fbb27b922edf56ac880ac07a119f44f5acf5e540b830e629d706b3e762ac71ac90d4f68c4d9b842ed4587f551e3d314dbab93188b1c5164f395f8f7976568834a5924c1abd78376486f4783e7597260b77157151b7de8af822eb047941c781f0311bdee500746cc2c1974a547201cfe600f0bd35c081f8e9bb13e7a0ea2ef12beec3f697f2a27e896a82165ca85607848d4cf16c499f333cc7c1df82ca62fed482ae3a6a8c77c67dd42ac85e304a597ba1233e55b3dc3c64eaa2c458fbb723d8406a96f5430993495ad6144771f3a660c3a4a6f3c3adcd5a6ca9ebc49a660c76eb6cd03aa5524a9eac310570f238cc67556f5999b24f2fb716a6c0bb7449d40a794ab10a8ec56c441d1b6ce926565e2a2aec40dd34884f7a70922384886f05087baaac0785867d08bda4ca1d1a7d58ccd20592d7c28f6b1ea3ab8ddd2eee4ae964538beb19920bc8caee935e0af84e4608870c02ba6f6b52166f47ed7fa1b45a339dcb4f5b423178a2bc8dc253ed8974824ec6bcb610ee12b8e5f752452f31d6771531625663d541c95845e846e8de8157fddb45a8bcde9d6aad2939b6991dd876a5237818aa365fb78a692454fa80fb49c981eefb4e7fc46d49c105651c55a6a5902bc73c1fd461c3f66ffdb64b06a8d6a00bdcf7ae1337cd1f05edf0fc8e728e1a675fdcee3a208c2c00207cdf682557326a0da53e357ee6e88bf91b31259cf75fc066ccb5a414adbf93ba2192b91b122de055422f04ee7a6f990844f2dc4b0d22fbaabfe5b0c0fd66840dcd4dedf5c0e52254326335e6f29806f296de936a366084a265ab37efdbda95921c1a4ac252bb3c521b147fc9db6bab739f163f64cb480dcaaf28b31f9d7316b4f5ddd96caf50cbfb41107fad09081edc020d769e71ba54871ec088fb9da8b8f117133a4c291ce31cd98b7cd2ccf2a383952ef1eb199520216ef094a087fdef6af7fd5260e94676b0093f8572416db3336d84ac9d4cdadfde5032b1a20b74ba8410cf479abef2a27ddaa180af8b3c4e1e87fe7954d0ab919d3a0d51cca10bdae1f86b2629a052b82eb482c715c7a0c843dbca247764cbabf732c3777169bf9ce21626a20a31bfd30cd1ca2b0d9e634932e99030bf248dd5238b070bc00e40d1925be6bb528f5b49a9f8ce009dfd4537aada92e86a498d64d7367ad4e8aba915368823ac59ca6ca15ae27dc314c299095be9f46f1962a0e9300cad90546def388e0b88ec46149e1f8d4e0f5f403946efb88f4a8fec7a766867a9b3405a78f59fb04030eedf543e6f3a2591067b4c6c651cdab1c1794f55d24f26304628e5046e188cffdd401d31bcd0754d6b22c93ad4f2a34819a277d8ca0d6e949bb5074f612d0355e4ecf683eec35b6a24a6c1f749f030d22c026c588280f4a3effd872505896afae1ad9dc896614ef2b2fe8df0f23050790c06c5b2b4e8159200d908113692f5553b4cfc978cb7e3f83125bbf22b332d431202b0bce700deed00e939c27a280a5325429ed8038542e56997f5506be99f39ae27d9f24017e3326ff69f8f59737e71369329121116a8b7c45af7ffa16e7b128cce9eea0b3a80d17580ebc7a524e533a7d375967ad25b477b8b9bf7f756147cfef28c8ee8d014b4c874137a724e18731612bb50640f2596ad5490006776f4d67a84eda401808f65e5acd549f6c65c230cd4073754d34ac8aef722b2230d6bef31e99c35fe5e4d59a4a5b408e5bbdefa2a7559e4718508a6c07982169dad8eaca07b5cbbfa8d6a04a15b25cee49f75f93ac0631cdd9fff8aad5386b3b20d9fea453bd8e31ea3ec1892f28bd29d9718b60fd1fd1a649cdfb2d61125fcb800ac44acc87720c2cf65d9bdea4495e4900b8dc2e56c4ff0c30cc3b375b747c6d894ab19c459245b06e2d49526a120f1d95c9f454a143a3d485142208e7e4f0e7431b135277c9d6b9f3ed8309ec9c544dd7503a90c39c64e9d90fd8289b5fdefdb321c6e1d114c9c451a87dc223c37882a9592fc4042c961afe8937d18a18a0d9b5f1568b1d43cfc47c927200ccef07227f3d67db5ed0321567df70739c9e16acabadc0ce8a6d989875475bed6b13545e2291d3a7b9633b29b6b93a0683bb17dd76522ba11606ed12913e339ba02ef769809dcb9005584e25e4a69b9cb70fc9904f15e93b804cc7f3b8142d183836dfc78d1ec4c81182c893ba5ef6184f6d46fa3e5d83a0f25fc274a96cbac37d93627f9527eb397d624f59cc032021678e457567a1ba5a1d83a7f6a9dcadc98170ac182bd986610e7a68f040d82c219980e74f0409bc91e4621f173301ba577b305bb4a2799fe0f9064b0a2e4d1346438f61baf21ba188bd2cf75feeeb8b267c7399c6c6d5597ded3d1f2d1347c8c402bd3e1bc2966b6d714afec798cfa5ab9b586bf61230a937e45b04d7282cfe1884b9bf35643c6479beb9210487ba407fd519dc158b91970b201d6cdd97032a0c0796f017d0f7e2402e8bb06a5ef4cc7c0772d1a9830db51c6dd146911b1fe3d5f5ca77f41dc47d712e1278b81753c4fd4104b0294fd8d2124bf9ff9025eb5af357d6afee8ad6212667bfb0877208fb9b364798e1cca417f1f08407f24a9026ba23c0376bde4983314837caa006430e140d87a057cbb938e007ec13989ef56100d79d8a611e3db0f579a538a9a5469368c4cf25843c81e061732bd4e08a9a78779077a191c4bbd489612059e7b6fb12dfa5b1c0a73cfbf601b7bd152584b4255916141c4210b6f3ac3327b68747c8546cc2da79286e444f12e83440d689226dde22c67be897333179d7e97d863550651935258a68d5ea01647f4b2b3317eefb2bea8ab6a9e40ff39f3cc388255806ad9345b4bc1e06951b94f9508e9a9b47271636a52704ba4c934cf7808f76a36a0186092865da37a8f333efc927c2dbf10a1d9419ee129db8646c714318aff81b73fb1cf707610cd07f84417b802fc51ae6844c98f5be482d11e2fda0e4c3f4c4ff131e388795fd77a731e314799438b27da7ae9f91d5de9c86f7e1a12467b83d9014138c63c2ee8612952e60ed4e91f5afe216a1b169f76c367680d07ba0deb49eb094731b3c118fd790768d1672e58f6958fbd227f8ee5fec79d34d22ff02f7147511d377b52b9ee753a50647c82aa244c0441ea9429c6977c62b1ca5e71002a486a68b3d438eb5ff4a697794c3474e0e0005bd4e457ca5ead718203e7c11588e62165cc7262897fbe75ee235597d18771fb64e3986d00218448a33137074062bac336263449679282e5b9296fe07b1745b1d78f2e9928eeee8b0fb334ee055a3c3f4b2c4b459c6f7057f5a2687857e1626dbed395039fbeae72240a08ebd10d50802daa1e3c2e443af46d54e8ec9fa23832dc409fef68738d5e12cad37722bd95242801253071bf658f0d037f626c414597b17f8efc8eaefd219438f6ae4aab8468cc927c0dabcb6c5e1c36d821574bbc6d08d5ed12df451bb52ac92c5c1c4c350e96c9b74d23fa3b635a24075ca93354eacf242d0cb98becbf45c97cac667512b1929959dbefaf108d40bb6be129e6ec93a37efa9a817afd66eb66e12121dcb382f219307378a1c112b1db5828eac91a3337a09303de23b1a1385ca57a068081914605c013b85a3b9bc05ae47500ede31362ec0bc57e03d69342b5bfb00ba98bcbc8d86e0d0dccf40525e5422e95c94c5f885e5694a8e634df6e3135d77bedb00f43ef3c2a27b75b2e0dada4b105895f29b435b39cd2cc79c84548143fe3504d49f49c1ab43e14249ec8a355c1100d41739e02877b03f7f90de24f2229c2b581ed0ad9b74a796a109a1b08171b49599b0bb38e92cecc3a4b5dae95430643bf130c4966c2444d42b75c77d718d7519b58508b969892788a770040cad8b0a1218393c899866aa03662609a78f7dddec43aff5b13bb601c97a591ed9a601a0d636e3e75e8bae71e434b5a8742ef0dfb6e713d613448769c03db44add37eac89f21a42f04b98e22cf1f119bce39a20117cc12459f1e78b731c6d7c2b39c183c70cca9d5c6e9a442eda0562f5b4f41938a823ef3d21842462e6bec3938f3e4110572e1993225980238c77a2bd78c71e1b22e86930a2ee79e6b630740c790f921c8083a4a928d15fd9d92f23c9b9243ffe6d8975805a9ab143b97037954925d651eeafd3935a9d6401ae70688a7e97fb898062abe5eeba7e93bb25e859e153a581f479098b48298651b6b986583e9b17fe76d83c128fe11665587be608e0f11a72c1374c192817d3900aa88d0f97df7217737c9818239ed65051623f58b48ed02dd5d7f39bb7f8663d2abf6d8d545eeec3a33c6adbc27d8a02e46c590588565e3838092772e320075827410f0e8bf53ad07c1533fba136f731fd0c84c3fd52afb34d0df8a967fbd4e26f9e88572e9d4e39abcfc1d93211356c0b77c7f95700e00e938452b07f7d5a9147cf0ba5df2a3c1777a01e948a6934e655e012bfd4ddc5b5011b0b912bfd01ffd53a01b285b595688631fdded16b427290e501d9f7a74f7d7d8f728a4bd7039e779f4a943352bceb94003a157be2fcdf471c1c41d7da221b06e69c86f7441f757682b4107c774f0283ce378f4b5c644885f142c88d06b35046225815c461b07b8568b97916fe1dc641df65acd41f0125ea9775731554facc3a40cfbbad5347897288df7d58c3b0ad3e3d7a2666142516c2f462e9e60923842331c377b6a99904faa92b1356610d10f7b7da8c6f2a413da41682285fbedbfe4d9566054f1605811b791333f863f2add7e427fd3b39b6e110cb50992fa81e74d38ac821633d01cd28bdf50b87f014d852e39b392e74b32ec6bcf73894b00ef6f7347fb068155905a8fa778e55217a7ea138c6ee60c1548205cd849db31447dcd23aa52160ea753e72539bf0cd5cf7eff5923be9802beb0c27a90dbf5e36ef3106a5eb86107a6d2059e1bb81e533cf75c79b34af50400ad28f9195d42067099b8e70f22273b563d56e8a10b6ea0ae98b1a8f0bf6dfe5ca75f534ddd39ea416704d3b21238fe63ec5cbdc472fa150ae1ca57d630542c0430c74be12c12720821c82a54c300e81c32a48eb27a3abc2fa52277044765cf3c025abc60be465e0a4450c3c10bb4a2aad71f68f2e697e57585d3893f0df3d352c11c7b9bd9b72328865873c7a14708c466a1cec8f3a149c300f457a1ee9ae7ae98f830842ed3087c86b302967080889403073fa9803a26a292e26eb7e69686b9438ca418fe1b7cc60f03846eee7e4b7bc93f79a2f82690f7c7c0aa4d223ba7fe91ede2bc67a4b059e41b52d6940ab7d4056732a26558311531575f94e1848666280ff4855e26e566e451ce9e7e7866448aea79f7e3a4687dff5a1e03be47dfba609ddfe251df1ab3f183cfe51d07b96a535dc06edbb751cdea233be548aab7644cd37adfe12f23f879d3ca2619edcde9aa6ed291b87964a0e668a17136d1a277c787e9ae43a8c7d4362e30812f431d58919380ab36b762390162c7daaac6613ce29f06f97a089c1daf0e3bb778aebe35fe3a9bbf414b8d650760450df7d2ba6c9fbfd689fd58921d9abc6f6773b2e9aef83b6ba6d630fe05ffc23c208107fccfab591d3f1010341647396c9665a148275da3192f0a0eb13cd3b95ca0d5697f2f2063fb9a0c5cecc1dc5141f829199245862a2fceada38a72f02f64c77c1d62ca4fe6e39b06904c8628f6cb9350248ad51e25e88b49d22752f5b65fd23fc903c85a606f86ed40a604a1796aa33d628b0816bf1b1202266b8f4c55bfa44d6dc364030a494feee7d4747907532f8336b994a984e7a3e8a8456ad86bbe1425ecb304f5a4a9f944cd000048b4ebebffcf83fc3974b57fc1e3f995c72c82bcd4f85258a25a8212dcb21d30c2727caf15714ed5973f1d9b2b8aaa640484a9c3c3a184524f6d81bd7f250b8766df3b44b17ee0cf6286dde61f59437ce686f67f01a386394a5b32de8df1dbe8a37501c47f376ab9bdac511fd399019c48d00aef0cf43b8583a328d05731a05365ccc313c7117e87b7286be8017a8a6717c8405e7137aa2d6d69e3090d271e47b082a79d91994d1978c3ae5ab8c30fc72cb91ace49aade1e43b922540d76bf0c1b4165231342e53ca77cf1e13cdad70f703a758d7479074790dcb601da7eb34b7b06448b120ece89986cb0e0320b085ccee4a1c29d21025fbd690ed27a00efe3b5e8d935a731f5a83c8249422d3245899c603474e586291a25bd8bbcf66391dcc72151eb0c4308a2fe686f831c0a43baa026718a9849e572295fb2e61089c7169639dde1246133497b3daa6cb468a4174368685db1eda901b2c34943e31fb231c9a9164bd9e1dacbf67536fa862e42661bd7a87459904c062661c646489e8a18fd1097b119c8f9394679fc693ae212cb482e80783f3fd36c62fdfbf5a16e6453816e0ebc08ba76e68685fdefe4ac7621d3af338576ee159252a0de75579cca46ebc40ecc05a8912e9f8fe0193b813b8827c2dd406aee4d2832b7054543d3f88369daeeeae71f50feecaef0ff60de99ba75158006a0a4f6fa0d399b22d72e4c688d64a1f5515471b7d848bfbb7fdf21c6f218f95896988ce315fe5188cb2ae85e0175bcb2db0f788b86bcf37bac74b87ed6cd156f2954d5937e2196f50ac1d2218bf7b1a2603234a804ead775412411e0133e75af035d6c1a0ecd20e9bd00a5ad32aba313450f2f0bafec5cf2541241d904201689b36b425823d8da783b1c2bde25fd325b0458b1f24a6a4fbc5ddc0d64c003305ccf481c5fdba6c7a80abf1e54c77af2fddb79d6ee474ee81ec0e185e491a1495542ad7a021f131fce4bb61094d0b8b7cf96873dcf3c754f1c98da9cedcc3a8a6d3c36e0e4b9215dca8c327f56665fa130b89bdf60ab4041fdd5b1c186d0628d93ce211c5c217f395c2339f3c61e1efe58cb0fa7f3fe4f2971b26189d874ebc6043776c44fbf33831b83b477b1b29f2800c0b319e3c197f134d76f4e36ae1f410c91ff9817a9aaa83f0af3b154a5085c445344f6cd0d717379c4712ee634581a1bbd21f678c0b1ae34c5008aab6727fcbeb1b493f16287fb6a89eec787c313e7a92bbb428fc6e0c4f948336e33e73a24583cac3f1d8a0b699e87f6fcc34be56cbce6d611ea05b8a5872980b046ee86665193684dd8fc28be7c6b11c4f04897f26f9e8b79fd0f3e0aa3ee590ff74e97d3eb60b8d94cdea5a8045915250cc013f0fdc50127f58b01f834e9f3e8fac003dab919126a09555a63b12ae2df032323dd4431a5830bac46bd637f049a0680fdc6b93956ac33f93b600cc6827e8e5da15132d7a93734f6ffec5e70d3f61223b34bb1026957db0f8e0e2ba80e26821a5d897f5d95e0a1c0e38a3438a7582819a8bba62fb61649171251312472651592f145184c3470322eb32c53f7d3fdf898482858131e781d82745d283259376c0542c968880741c7d4f0fce648e86b09f44925d0c0e078c54e30d3066cfb5eb95ca60daea3943cb4a3233783d7703ccbb40d059fcd91724daa2410169cd7370231b3dccca3a20c705de319db616fd187a3e5d5c4885640a7aef68d356ebd1b4033fe1cfbf315dc7108c26bf83c0cd4df0271a67485dea22273efd719cba494201fe623f5bb94b9527fcf14670a9928689a1c21c023f096e17cc456f803549c45d80ab3babda843ea6494899d657e66c58bd197cd97cd8f9144fae604a59535abc4efc5e6393fb69bd152695e9fc0dcd543f1d0fc5c0667ce7a9ea568414ece7fa7c5693119dca6a36c326d8345cad4b0d80b8a4526b610ef1e1d1425c3323e60178c26278c9d10631b78bad8698a0366a36d46860a88da59d1fb010e39b61ec2e971c9c4d374c36f347954afaa8399a8f755591f8ba8d319d96fd188fa1f358bb098822d00f44d93ac72677c37d918887192dc10d35ca369f943a70906567630729d312dec8ff7b23d8ed4aef0edf6002635b138335c1bf0df37cc7b37d71b9f2462928d238ab63e6dcd8f3cc343f7a5cfa480c5c371a81595803504cac0c99ae6ba46a6fdd4ea376e123d34c2ea841e7870b19d55cdf0abdc9bbae06d51a7b746b540e316eb58ea77f670672c3bf13802d5846e8fca4b330e614a574815862d3532e59e581107abce22569c44d2aa30e7f2fbfe6b23deb7a9def828513795af40aca1f1886727e43892a60fb0072a97896b3ea699484cbb94a08200d3664a70576a745e7c34b1d18b900904c7805738fd9f01641a3a5a2e8c69a75ee9382d8e26a749c21e408eeab75c92881f82a0e18aae230bb1edd5e09f8a2c46c2f6b61a34320096669c645760a3b4329b259a7eee6868065da68ae28224d251dc290e86a944bc2bf55ac79c65e318d756f3a0538705924452dc76e81fc73975e95d0a034a966b5952a1ac2592d6f6311e8329b932f129fb5d5a8d2365bfdfbb754abfbb3a0df0f8ecc4dfd52de0db8ac4c0699ab6a93c21e46f9bf81ce7f5f18fb1243642be507e27b9716aff2c3439763511a311be693190a758efa9341b9d07ac6c2a6c12c5d173b155d088b8a3556d29237225d6aa401e5369804bf2787ad2f2e152687bc92aa39e707ecd4fe030cb63ab4961bea2642515a75d5469afe9b0aa83f4d0a26d88e2f28a8eec6fd398b3754315e5c4c3ad98760b6c106d1514da83da83d9826c7500b680a36e3c3e1e8bbdd3e14039a0a356b1d92d68b8df60c1c5008cf2bace0277bc161017381475359ed54fec4b7c528a522429e395645a0bd4f435a356973cd3ff918b71e8f0fca2327677727240a40bc66d5ff00b44738e347da074d038ac588c803543bcb55078d7769ffdc11c2963ae4a1f9d2bcaa40fde0a29d247e31af1e8e36cbfc50cd0718e4db1ab402c4623140f46d4926baa7b02ab66605a1f2399cb17b05655254d125e2d612514dd22500d61495f57c67922624f9e654a31cabd1148541bd44642e799fd4afd57f5f768581e2c7a6a61bd374a299640ddc9fc7d8896110ee81b117bf4cd532b26a1c1f926c87d1c71483ae65811418fadc341f293957abfec1a4f01b6e0e6e614979884be99f12241899af43528f7310b34031b3e85f8c5b62f81276cf6ae7f7b5c38a86fa097290403d1a7c0ef37752525a9dd0750d4464525a670ed2c8f53b0fd954d26aa21eafb9d4bc9be367142d350acdc78f99859c01adcb72a00d734e2bb403fc6ff2349ae538c823d6023ea8b7af8022ca8c11b3f45615f8f04ca712b0b5a3f4bddccbc9e33d08afb9e1bd22e0b1f123fa8edfa01bb8a322e8a27486d72a4898aefa887b0172549bdb703f55f9692ddac3608104e5d3439b2a3affcb4e2deb6763c21c30ae458463181ea3a6b592214a5c86b545384cbdd60822251be329d80e2e6b75b3147404bf061ffbcb89a41495afb9d3fc3f30fc4ecf2822f823097d5d46a01d4eb25cb452616f1de4d574ac890ac822b285e63c52c79349a45d26accf8abed4d9cb11c1d089ff265eaadbebd3fad6b81fa2d619bd734d615e524f62c51f4165b29324b151bc6638a583b88252c5e050aed5308ca74a758d8a682b69930788ad9ff942ce77193d6250870c78f45ccf8e9f8561af9f5fbee5eb26a5765fa4d6f81f0e790c08906aa897c7bec392410ed763ea16e06a7aca999606331976ed02bd02cc5d209ad498b8142461c7a42c7848f19849a7d74fee8092729b58872f43de117b9d23397ce7044715544ecb8f4c47c20899dd2f76a2694c754a278a5b712bd35e95b4dd86e143ac45a936ce2e1843f127e10a87cf4b60e8135c4f5c04dd8d5867be0e5b0cf1aaf8fb89e3b27092e69f010b0fc0f3d8780d8593f66a2cb44e4401f256187f213ee0bc4a693bae8bf1ee7a09fd89cbabc6c17837bcea13a7ef983441c485465f2a60dd144595f65db97229875c83b57838cbb13b39c5c98759c0865ca4c9e91c751f41abe0f817b84495eead0364e643e6305525fd0a938835149ce857db08c489b55baff68945fa6fef427872bccd16b445d210e43aa9171ab68f5f7ede5cf6014825beb0ff63093102340f9b09d7c1ff9963510037089843232489a3b23145cb4434cb36c4b7103b419c50e4a1347b4bb658f02f32a26b50ba6cbb0c489f9ccfb83f41714b9832f5aadf8aec06ca096a88120245506f36cec01fd6e858a203c51c56560afd0ed1b3996cc0132f968d1cf1cd7da2b3e84d911fa58fb93c49f06e0d6c69ab074c747e82112a4bd41f8de0c82a25c5f02b09416c03ba44036e3db9e82498bbb50bdb2e13d78f6d46e9f281323c2dc1a24e7ff9682a32d4c860308681b08a1dcf21f68d42240788de483bad240e5c570a66d5523b93f6896b2a4152bf0b0c9ab64d929374a568a05cb270cceb68425f4e912c4b179d8d67a0b5ebbf8a93d9ca45747a3004e75e5e7abe4d58a931d977215d2461075519f8c1f3d9dc006f7e31046887d902b8410573954807ca18b6685d6dc3c8ade060cfac98277db151681309c29c4d8da814c61b0531886b1390c49482faed31243f7c3fe58f41b5994187674597616f829865b2de3bdfc98dc1243b2ebe0c91677a00aefa3dc753e332dbdd79745a0f57d1423906259524e457611a62c8c7422bacb6191f789dd7ae18d586bb3ce382d92bf024d9af1ec9bfd2b68f40c0830e9f6ba85a6816f0ea1c0f71d908fb41cdb292677cc96390aba17c5ec465558998d252483999a9b72bc0ab20768e44f9d0e005a7a2e03a3138e585e0522024ed4d657562951e5dc840ce63cbd0af5cd49a657d1ab2a39c03046d325afc21730df622364857f8b39c45ad9ab900542c4650c360f89a58f22afc2a4f1862dc6a949d734c6ad6c2d431869f52a4487de936319a00236e9a73fc4c50a8f94b349694bc4565d5d43b09137ddd1fcf52d3010856ccb6899d55e9bc70af14e6785ab43b7403b5c814ee4925e7c7510ab482bb0b652a77d2152388239b362eb48897a9d804f77b1e75bc3b047760b0f9ae2619ea7fd1e084923c1d57cbefa745a18431ced5bcd458039203a93f1399b1b219b68fb3a422251ee91695d58e3ca4de2412f85dcd3405eead43090b032075a5f49357ea1bdf38367ca07472091b30341caccde89b53be2ad6e984de09baa752d7a2a6071b0e9d27a6ee83a01d1a199c97d52891bd5c9d01c7746745092f61354d11c77e23ab7aa951ffaa2cd7ef436013812e5a611b9e1a26a81ac3dacb099c571c3363b5a3f5fe56d912d247d521b5192b2db9f80ba9ae018d44119cc2bc28f721df864c49b23ded11a6ac1d4b7ea04a4061a43a8b624c8ac5aadb7e8a001bf102853fba36a0f7187f79d64c3cf0bbdef25b36a12066bf08ef4c9423473fd466b30ba007cabf3db712935ae73ee7f270b147f60eb4f8d7e282c2f0140a95273cfae469b35efd97b6232447f9e9b470569b348c98f2b28a904a35e86e73d09ca6b4ee3959a9ca0a0fd0135a832549a3aef58ba33f6eae4688ed929a688141c20a681eecec6d161f027cfd77a185ed4f9dd1751ae153854b73a48e7979565c6bd80886c791d15f3c3b6127863dea84ccdc3d48439b46eaffca5ebf55d32124184d1a5a3d195e373ae3b6a61cc916632621812e5dc77e421a0e6f05e2109766307d688aaeae0c6720773849be33e02bbbf2fe8f85c464df940e586e921f08336f2ff868289a88fda9143ab0afd20bac730d03d8446f353e403492e87e0eaddace99ac3a4246651f9917ca16494c4c138b78dc82d6d671e607068b4266f2db7bd33d58c7ed43e5fedfdfb37b6de531b42528a85d0277b8de6ee70c0dca52f3049280cc3cbf6696f51983657331ad8905a63ad63a4044beabb35cd5dd4c708cd0febae3f8063c35539f2104cfd614841431770301f5b06c696def9092fc2b198f902061542326f84f32fdae7c55984b7f805f13b113f2d730d7142ace0195d783bdd3aaba28a328edb3f6ac445684b86c5f5e562f887d60a966c31e854f9c8b29fe4793f1e0b055544ff080cee7b624c0bccc3b4bf275d9e16dd6267b7b6896538de296533b0416d591bc5e5a243cf830bcca03dea144dc16177bc0ba1d1035226369eb4f0e3a1f0ffcdbff472ee1e8999b9afa61958988895219d9d59537b4fd52c3528f3c0679ad1eb88bdfba5f61cf20870aa8b3e45274983064edcfd6a4016b81962271a41af75006faceafca90de0f2525f1c4e1c53c73b56fc85a248309449e0ac85162e441d7044cce8c9088988c88da123020c50e1c4791101db44c223629e87724ed5958385b4bf8b497947dd891cd64ee16404205993fffe4369cea80ba754dcaf04845dde591b1825376da05f31ca93e24486af5dc3ccce8bc735c4ba19ae4ffb4cc239e9cc61d08584f76fd2928a14a91bd6415d2c7056b9fa93080a4bdd6b1cf1274a68ef677762f74d5bd583bcef180c48666d3c95b482b946021feac92360db1e64adc623049a6e4329e1f155b09767adbfa7a375dc6c488a9986095d5030e1ebc0dc21c51010dbd1d70c4453c058208847cb65fa632a1613264753947294cfa2f5717c1260146ed0d7a20e0bb1c7b9719e633212fe7a6e65f6212ef24d1723f6a103c3faa0f5f621ef2fb581d5c0241f80e5eba50ecc6d70eb439712c681373fc84877c2a119907da5324e970c7c20cc1ecca0f268a2c18cde08400f10a1a2f309aee0ee3c0e7f52ee3ce5c7d5e1d425614a4cf18a8044f7f0449587890c28ac4747830fc1d53d4c2a96a224bda297c88754b60c7b58374d3c1f3e51dd344df8b73e5c81f7bf741f32213fbcad9e17dafbeb26648036a6d7caa66af14624e2681ffa819bf4a0f9d3e757749ce233ce9c451e77084b36f8d91fa4f90f1742ed31742cd662a5dff5ba091f90acd90c74495a610110771a3c65df40c694a98f48e94a35a4934cdd32b0ba2c7bb3b251a4af4357c69c28229a2e87acd74bab1a39dc33b369c956b3205d3a25e0fabee9e5c4c40b9b6b793da403c06b41b3a3b5955b0a0dd0a0d7a7917aedd99044c80ad0ab353f36002c73c5511e9ea3f447c40b499c0f91884f44011f24513e0402bec8057c108c701117fa23b6b80b84d012fb57d3385df336e53bc287b22534fa55085116fca5d1ab24f4e0cc6a47863104794395a9d6445f53381b025ff12aa7d1c6d4601a8f21653aea8cb6494b5682c0674d43ccff96b76567317c952d223a54e394c2d1ab516eb99be9bf531534db036ace368d5c4094594d8b012d876c2ed19dc0e920731f54c4363c640655028f68a015b287ea00f3c8f0a9e9821e4f50afdb4a72c77cf1e35e41661c1a6b2f6690afea7c29e8c34fd12fdab8ce55c71f479bc1d3eaf33a734bb3442491c8eeeeee1d16099208ca082f5dbb0b1c0b57da6e4bf5b9b4daf7245b9fbadfae3d95d249b136563776e7c6ae4ee9c017ac0a4f8e2d43e027a35bf9d993c593212704f184d693a2ed841e9a5085295bdbf55505b46f1492448108149a40e10814a06cbc35556bdf8d499a8a15678a50087d20241deec8363cde6c4e8f9f7447d5ca361c541583239d9e0e838ba72e69e3b97061a5c5c3262dd65aed7fc49b53c66a8dd1567d23e68d877668bb1be33ebd5a26f454fac343add5f2389d7670d35727d5dd0bd59a4aa5a22a1555a9a8cac8bec11fc1a6dc7b51b154453a550fd88bbed18e5f543b5085c1f811778cd3bf8d077e0c55abc53bdab3f28efef0e83bbac3a6a7439e0dea9274f568629c6aac1dbf8358ab765e34976fa59bfe82ea4e073c3d55a98a40157dc530daa95a37f4a7e38d477469617971157afb58f16db6d9d4df60ac3eb35e984a1bbdadf4d15e208dee97ba5fb8b7354a4faf7174d397935515d9f4d80d1983231d06a7fdc4bdfd82337199c575f7a958d4256d642f1933efa2bbeb427757bba0a98b456bea52d12d74f7b2e990c642b55321e5f2e3665349cf5ed246667537b7642fbcdaf31d2a9fecdfc668a0f02693ce30eb93bd7696b4418764ccfc2de9ee66ac5999b5e7335ed56f248eae361bed379beee0ae3cb4ad9d0e491b74c88b813d54fc60c4c3a43bbcf6cc3e7b5eb503559a6a47c552ed48554bb5035fa07ae5c72507f845fc6284f70767dea1902823adb53a212bc9af23b5d63a459612c4e4867c9487e42610c102eee08e199531e63e19ce058f4377e4b85de941506fda73d259374b075e76add50596056c8c617866537d577617e784016f5837847207004a4c82d7304201ec3b635f19fb460e66634c8b9cde38d322f7946c8ca594f1614c846db71c98dbf7c68ad17923ce8c750b4eb49c80e284099c4c712285932a3881c209134e84ba962bb2931638910229a574c204d2c914e9440ae9a40ad20914d20913d28990b51662123af1c1300c66d0498f131f9cb4a0d62aa3d65a2b113a60b1c9111dee16f20984d42625a0b7494e0966cb6bb54288bce1801a6badb5d69a84fc52945b5a8e6a52e4e48626474012e80380a145daab6c8d0b822fe4d60411a2861a7ee4fda365cbd7f0e36eecda31076b2ea5b794c61aa91244c86b569e0124c7625beedc7b21be72300f15bf71345e4cbcc6bd17d3bdb06dbc98c880dfbae198773e193873cc4d21e6b09a5b636bf0d530576b30a6618ed6e04cc3dcacc158c39cacc19ad53252ddb48d933538d3362ed6600c5fc493344b2a31266e383619df06497be3708d86b7edca8d4b38c317f3e4b28c69d1f6bba5cc622df68d37062d58987289b46938c3a80c76f01203c68b938b8b16175856545ab030e51269d370865d5be9944fc858800fb2680246184d4151469964431d2786700208278c38f102275a1a0652662591882647a484f0b9e0bd262aa027685282263956dc28a47c103ea2a23d44a4842849966590e8061609d62f80514e395f535e505e4d5e432fa097105e476cb52fd78b6777dcab05bbbbd88b055f394f48a9b55eec89274cb00ccb9e2842e3418633dcc4c88ea7aeee3e110318888e3cc1f304eb8995135a1800c658db9cfc382165672160ed02278ec026a460f336f164081110443600c07e4144419a78d2040a36ecd9ef16b0223321a589204c3c61420b4c4881625b6bed1124ee11221bc5f60008db732244850b72f77ea490878c182a9287880461828809269d740a090a3d8472a8c910133429a5acd602514aa9bd3ea51e6bad1d6a0db1b4212240524e59efbd46f9e50a7909234a9073cbb7906412792c80010627ac65425a81074ca41582300c3dd206490491031208b9c00933644128082351ecc0880b783083c903205eadb8e1000d43cf6d69396a0922ec16a16c0af4cec72823adb56a21c75b6badb5d65a2b949c3d79590228289f203791b39095c8974e19950052e2b5ef7e414a0ced0e459d7041c8dddc4f4e498287dddc37741e04f4a6f9b7e9db2b50772eb6cc1267ce392fb74f3a3a2a750780fd3ab8ad7df6ee5983006cfa20b61ecf17230b491ee61736d45a44c11a1bb5e566e3b2a5e9013abc247e32f1704278e34ccc7e1ad8720035c03564ac617fc7d26e2cc608790d0c1101f055ccc1c00921e57d3290be9acb690f861c38ca2d75dc6f63eefd4c4edb970b9261181120b510f2b806202dfb478b0b1b6eb8e136a7844f4a92d439c3380a4344963abee7dfc23d19886d8cbb774b1f23a078ca786d851889e2fd1ac57b317452257a8c6c258a6ca1214ae4c418ab4dc228892773ce990413a08b25e19344cfc4322c4ba2b563124566122b950c67388aee929eb4600d6bd865c26407311162f2b31f131fec6eca4b2641608cb1b631e981490a484458df10615cc26889d712434b18b1037e7fefbdf79e029e4c029e8c4e0a7e820d7596586da883b510a444151bea30890286042860099fad7200ed85d6b3bb96174bb436931e763700f9049c34c7043410a1238414ecc0881f88de1218f03004932b38014910254848d9f89ddef09e3101f3e156c122a93ad0262610f2a4edf564e237ed2763a3271331cf9361914d20b21c21193b7a343054fef055568ef53c9a8ce577e8d1602efcae1ecd6db9f579343622d9f12e5e8b1e4d75a18f4d50a04703c48e7ff179f468e463cea38171286546fab133ecda4ae7f1bd1829c6ef8e915dc2e05e7035c0ee19d1c11f3a18a585eb62112e709ddca1934d9843a874f3a806f88c803f44894590ba4c8b3adef098b05f72071f76fc2da20af1270a1223f8830e3b9e1ad1840c4f060201036255b0f6fc7b3510f3992ecce7c9ccf88c0565809f0ccd99206f1d1592739626cd71a7cd7b28d343bdbf1c2fa25eca4f665ea3f33204c921871cf0c9601b96618c6119ceb659da66281d6bd7b608c468a592e92593c6d854ba4fe6da63bccd3c433ede34eddb168163095fbca45c7a3ede72e05dd22cf2b12dc7ccaecd4c67612a1deeac5fe97797489ad321dff88057c0df909b10d7b56096a35aea4c28e3017e32ba151883fc8290e8d92fe8882102ec177484919d8d7ee8b211e45639dede90093d1afbec067a03bdb4e61fb67c96d12be09cbaa337ec18e3edf5db91e7c948288479b2d10acfa3995836c2f2afd85dfd6987bdd9dd69c73d764cdf587df58a13381df294b29ec20d47ad9c0e591e6e368f4228c4007090524a0949f002e6182125c8ee34ed8822fb3d664026d035d03d5d432d24a147332fbb8c67cb164325c850769427eac7516eb5a5cb082a3f1bbae8a37c236ab258ac56abcb56f2908adca2e8d1d0cf2160c9c7e7c9c07325c8f25db6da70d215e454a8d004134c30a18f80e21ada8e350e3f63ad0330eb23f2b376cc75264de3349296b1a69d54d29dd647e42e04426f9bb40d97347db31d6f39e7211cfa08973e421fa163654b16a8e9c0888991119e96b545ad56ac6f15b5845a38101111111111111111111111111111111111111111111111111111111111111111111111f9f068257a82ae30147405a0202b4c31addc0517c75c095e83b2b5b33409b2c292255548b2e53515172f6d2d175cb7b5dc458b8ba2d356b47521282a2ada5a5ce4975ce81b966b34d056cb4a6bb3fcdd85162c2b2c545ab460c182050b162693c9a495302a7285222b20d1b49348efee6eb55a3a8956ab55d4e281a2c307bc029772c02b9eaf04f2b2e12b3d19d3e3c9986ed2ed035e41355dd195cd73c0440209249040c24a29a59473ce3927a594d25a6bb5d6de8b44d4484499234746022da499f0f6ccbd37d248654ae74d9d5a41fd621148a4520996b4c66d385e42996de6bc25cee6edd2e9e680b74b74c8de3e5f556e7a0e2dcee2753bcda072d37ba8e81e2df47616b7b7fe462ce5d397b8acdf93d13a0979d39d76981d6fbf219f6a07e372d01c3207ec70e459b05a5b0ed9f22398385b9456e09236c2c808f260912ff9eae2cf4f8cd14a969452d639273532a274421e9e2abb2724a48dc8da880cff301d519e6aaaa36a2a718c960acc721df171a95a73aa8aa876848698b85e40aa1d20cca242d0d68cf354bf396bb594dacf798cfb316777f842ec207ce420e4417d688f8ca148e43512727ca7d292d2810680975799b3ce8a84fcab553db99b09cd58713072dd77f4880ff6e61020eee16a44c81d15ba48f20d768a6d36d4e4f5a42853b130adda49428196dc25444c3a8449553d1091eb214db20489901093a126af38739f14c599abda111a8222c4a4896ae7aa5857b5e3caaa1da33b053f192ddac3863c1b9eaea40dd2a18e585f4d0810dbcce92272475d3a08f9e69e628b6998424d77386fc33ab5bafa86a49a1ae1680f0d821a893333081cdfa45a0893a68ae89f5c5d4682d041c810de1e09fda149e20c509cc9523af0054c3fc168539f38d3d1255b5ebea027146d797a8422d9f2704be9c017f4bc4ead3676d236bb0ed8dfab6f44b5a474e00b4ab459a9a63db5fab66d54525a49af14569803f5a44fad524474522c2a3508a10629a5146a9a464b3e32c69eb4bda3406c6baff2da969e6e4b754a27ce40db522b271f37981764fa2e73e578d9f3e7d1c8db9782281705faae14b4ed25a550bbe45ef6db6cb4d3d7a3a9b7df389822f28bc296bf971c96bdcbaee9726dfb4b5b1b5e3bdc3f8fe6dbfe869c3f4fc61e72578b285bfab1456cfbebdab647e585a35684492291a48dac2795421368b6a77452ab149114eba588a456299d142ba5935aa5745e5c41281da57482e41049b14a472b56694a4a2749e9288ad211cf7ed92e409c72eca3486177f5c7a5a545d9f49673f1f364e85d80367de65672640cc5d93b1720547a9e0c3dcad9f4302bc8f45de96853179e6e65b5e933ae53016285e5a2085a9462a5745e5c5ac44eb0d4e2cca996f2b945da1c42082175812182464a298410c21b3183e921cb77f4674bfa739d9052ca390f79f4b4fc90adad393286dee25b6d552cd58eaaa562a976e454b1543baa966a474a154bde68d3638d24dfd45328aced62bf202a48d9386a3543a9b9284b893356c397519c39ca4fe2cc3c6db2e55744d1eb2879529f4dfff0e9c756284f45f93547dac02e1943dfd59c52cdd9599f5ad875ef4bd845c2ae0dbb2eedf0d0a6c7dc4ad10fcd2e75f792bdecf5f4634f3f9b9e8a363d8c0a32fd8a14363dd6e5d73ef1b0a5aaa5dad13722dd408b91c658b718552c5591a832b2dd7bbfc518b7bb65556a91ba5caa1dd54ed501b6514a6f3afb6929a5df6c9558f64cdbb6edbe4bba37cbe0f611ecbb695a46af695a49cb6ca5bf576ef85ed36e268f33ecea6efb30b58c31c62fc398da7b0c53ac146bc339e3a75612db638c4bf8259b33cea52ec59ab4f42b6db478497734e3d25bb0c8b0cf4a73f5d9587734dc4dc3299d944e4a87b248d146a1c2931d80fd82a8e043859e0d77d82f680a454ac7e6b66d1bedb2d78f20db360aef21d7a9ea96ddbbc0109161746bdff2ed9c73aa8cdc904cb7767bb6cfdb7fc46de271abb763a5e374b4b47ddad393364cabdcaa76e00b2a1105b51cb018adaa15adadd6aa8c44fb1eadb5d6aa8cc41855aca8eac1be68adc5af31c31966553b2a9a15a4b092b25e7252f891426b0a417601f60b92829094f29bfcb6c96f72abf7927b7ba35b0e9c7d67c6b1617b8a88a47ffb6ddbe352ab1411396ffdb66d45e47ab7bbc0106183d939edb3bd497796a4b16f9a86adcdf48fb931ada25d2171b6de7bb2cbef549e5e55e49d6ecbddf8b0e159550b53b5acb5d2b56ba66990180d52b5a3da51ed440c4e2c44614a1498c4d77a652d3ab269ec17545474b40db05f5051927def93775e49eba52956f67a71dcc7c274f9637fd274cf827b32e6ea6db54ba56bcaf5928321225bfd63ee7a798cc31abbc96ef743d24bfb6a2bd51dfc949a458a95c80a4cfdc0a758f558d3eec2590e7574d176db6c48f7edfb974aa5b20dc72d5d85cbb75ce99823c54b132c19619c610a3f45e466fb0836bcf31eb35825a50355f816aa6c5f49b1b69d7a3bee7e4c08e1762cadc5b1e196ec6faa87dd954810a6562922a9229747972ac597aebb146b977e31c4fac676f8f3f7de7b4918474de5506f3160549fb6c7976eedad7d6a1567e2ef5344a0cafe5a6bbb7729d68e27e5cd66e5569756a27de9f1beb412a37de9a4bbdd77251eeeb55f29c592f6b337ccda5b6b37924d11d9f6f8250ee64b8ee5937381945a653b0cd975e8c522331d6e362fdb070f3bec5ab4ab890577c2fe36de4cef7ec00d9ea43b88a396d2812aabb1af3a7c336c3ae4b26f267dc25ebaed4a3cecda6545f7f6254ea3ab0d03f21a7d3b527ab390ad56b5eefc54ed4c1ff28a2b763c851b876a270ae16053a9aaf5decb93897fb4e7c9c46b25b97b79bd3c9a175b8ab4ad976478aa63dd4fc0a44d089b97ec18e3124749184698d564510eb97765423fc8ddcb48f6ca98642fc8a976322084322671464a1bd8e3b31ea0ac88ec073ba278ec4c3dfdbc3a47dd3ee8eac518a31d6fb94c0a57773d9a921945d492cc28de23d4bcda0e584e98ad228ace9e2359cfa6cf5ad969a6bb8c087d35105e9a93655996ad322271a67e3bc530faa2a73971463b3d0d1267ec293e3d251267b05358f3a48df9ec32fb96d52c8b1a87374ec86b84c0c718e27a4b3d28e922e4f9e783e42709d0122126434d5e4f8aa01849399aa2da61b5728c90e7634dac8f3e48e29238830931196a1267306df324ce14512871463ea674a0e5eea1ce625c606473fb05412127b5d22916b44f46fe5e97a0dc597ac456fb68de8bc5a99a9675d96d46035dfd86a4f3274281ea23d4a0adf97a34df2a2d90a531c9dd291376fcd5605c10690a523aa9556aa552495961a148a552a9542a551578b03054375a2b94f1f347e868a094d249e7df8e556ac8a98a6cf8a962c10963bc73d217e13f6380852329ad528a486a15e1869f31c58aa2872874088a32e564b4e531f4c1639e6260b63e6a2ab2c9e747561aa718988d10420c9b1b8e18d65a5be73c9d300e6c0cdb38298e4411c4c658f6eccc361c51eb2be48ebeee865cf70d357d19d59c3833299429b4884aa14791c77dd7838a2c6362958f9c0c1c55df0a196b517a241192170211254d3e37648c977340444953bcf1ea06c41f9d851cdf994c3e32464fc933a24c3e404371261e6e53122671a622f9312d31557b23c6c8bf8733ed6d36d9314de333ded157c67a342fdf0e43a1c0fbc21d46b3e40bcb6170517ed1170ba441e333667c86b661c65f26cb0f1072a9632952f9a955c40285c588454a5e912e49bf21df004963c6230ceee59ae527ce74ef8725499ca94be20c002ef10e2b458f06c66591c927cebc5cde8424cefcf2a69f38c35dde9424cee8cbd50ebbc345f733687cc67bd0f88c19a771750fd4699c86b681c6512fbf1c07e3b5dad2e4c352640160658c180d70d147999ee5e7d1c4cbb300491b345a58eec205177761468dc175383bc6b1d564c4b8105618f173c36840bd31833b451adc29ead30c334ee33d669cc6679cc6b1bf60d8b1c360e7b817519ac3301adc0cae567d23a2a43c0c2e633d19b95d73140a478be89388927fe1e82ba2e46156f464e4697099918c919fc13d19f3e2d866f25142eee8cbe59dc967bbfc491b269f3f79eaf23efd491ba793c9475e69c26434f9c4e05e44c5380caec3d9308e371c30e69cd3e65d5fe04b8c18af5cc7811da3c561702c6eb90eb5b1671b8e16d85f701d6d9db8eea5711d1dd2e202cb0a4b114b110b13d7e53944c9e4c392a4cb1acd268eaba7fc0d07441baf469e8cf68319c1c481893043a0426f7ed35e7a0edb492210225e04da4bb7db493db693f48dd881c8692bb216b91751f7de67d8df16533a1cd8f43764bd52680afc64743384f63c9eef25438c997f67f14d7758e635deb1fcb058b0c6651e638c34320f999579c8463006f598f7403d46e628548da32a07f3cb712c1ad24295772b45ae9517445eac5e047991135133566f932188cc6b1ceb2a725757b77e02a80be0138879004420448f1a0fc04520a34ff20238ea0278aca14f529faa9e410047bd8700f4933111f51ec7186a18975eb94ee37412727d874d383b56ee97eb38508a7a460f10b2cb518f79c7f2e3c25d8c5acef2c39284058865c94a918b4f172b4532f488bfa6186122f7a81f653c461c7146cf8c20e2cc9d7124ce00e02bac3d7f32fdc5399e38c37d5e1f46c6e74a11abae50c7a9893147bd47cc51a8c768dd23008f798cb621e601d03835917be5604c79a5684f969f192e6923e630607472c788c1f15caec30100572f65ad5c29e2ec58ea11515386889a2cefb2d1799eccb497c19da644d43c0c773ae24e1c77328aa819f58f9ecc8ce1b81c1933ab15b9c57ff46872a48d18d42b2b9f040f357f2319336bcef60eb3ae8cd9b65712bc19a39ced737bce7eae93fb45777448e68cf6e489c17574080caea33c2fb82e6f17175c3723883ddfc275790816ae9b43ac70ddb70ad77d884eebb2285c0682887a1e56e064c4f19c8c381ed70f8da47b739671abc1e213d0a3d14c7f2f3effbdf862fcc6f2b3677cd92d3b9f82f6bc5c91eaaaae6aabf6542295558bd41e2a4f35a2697a866bcfcfe89131730340484a0d366fdfdb93d1660335f36ae063b81813857e464f7cf7f26ae578a2a6eb16a5564de56d9a8a1c73948efb2e03db0120f468b4cf18eec998d7965c0fc7a3a3c8ddc968cf9f8c725871467b3d49391d9da6bc08f262f582489c897a6a77ab2186fbaeea6b2167a42708ee08e7c321893355f788a8a955191e4dccc1ae01eeaa399e2733af8b7237399e3d5fb708a38c93548fe77b104e128cd726f06446c5911169fc90bd68ec66f1977787e254b8cc47c6c8ff5ca02cf4b2a12d51fbe5d84e75475d389b116733d6c9998ea786c10dbe9c5fe23a1c1ca5cd66fb3de5ee6d8bfc640f160f631db90e733a09395e3be4ba101c6d78d3713634e5c7fc8ceb38b033a6711d8abadedb705c6ee26c4688b31961c40f5bed6dd145b51232eca80b5e4a892575e5a83b723450d7c5f42a7b6949a2c010d1de6c808ee225797b001a0bb9cb7b2640ca0344959617325035a6ad74ce1c8c5d40c81d76edd8e1a1bcc243b8097e3d893347f8c88ef308c225ec112471461e42b821741dc13e184936c4537b5cf508dc5e3a4e0ddcf4761b4acffac918a8bbdb43db906fd2383550773f26deb8f9cbd543eba28fb2ebfaec887f768c5868c7b9d924211f3be4eec661edfe86cc38ec6a0b797607c31ec02b081c654e5414995208e5d353e4a9b51419be7cc5933c2f9f1da7e654f56906ecd97b60cf30dd037bf64c3b21cfcbbf88286d94bbb7e43220106768883372027146861e17c8c0db284ab91b5173d6b7df9e4675854f36f112bf9f7407a2d6396244284aaf1785c2c8a842616464a180028a0b85c5a0c8a0c8f02f4608add02ffdd22ffdd22fdd04dd04dd044d45a6aff1f2d2080339f2108778a8e5c45feed5c408af71af2667ce209c08f0e1d42e0ee68430a0463ee382ac608dbc1006d4cc3f4dc3413e5bbd06ff72417090cf2ee43558e7c8673ac8ea35f0424e0199231faf7138c819e47bc8c78c5bc19aec18b78235d831174408036ab2af600d3e8e9cf6b9e138cd30af9d02f2508b0007b912c0a9d1b40884e8810f2f82ec91fe0a11c20703763dac810942f6f1aed855dfd40004f13500c17c96211e6ea719e4e1af0cf30f8261822e4c11644c9a07f7124ca3f762d6fec67ecece5ed381518865bf15da9348241e98c418639dcd615f8f518e74a875137756976ad59d3dddda3b7daa9374d2a58c888aa84dd79f6cd51088a8d8c9d89d0c18e33d61afa7f39d8c1d51dba2665604bf88276ac5b39f92121cd9dd2dc16a5f25438cecefa76448908da53d2aced4c31091ab842122d7df3fda3769b42fe688313b9e76c4d7c37dd211773c900de0ecb76fc4faa8e36faaa6476daae511a0b6977ede1951f2345f7fa7dd72c463c76fb3798e7878baa77bd4b69f57a37487da6fbf1b1be027e35e2c659238638176c0fbb6dc74407a77ad87434342793e085f6111d501330861a6734471715f3d6b3dada7403e3890b73938daf6b7489e2a1132b4e9ab8985c974192f7eb6d59d6603ffe90e73418410f21a2170de76c8d752697b1ba682d9b86f48ad45c60012e0801e17a0e10341583df4c43d57e2a6f968d865d1e8e3bdedea23b6ed804a8414ed2d061d374714d45474541da7843fa455694b25392cd94f490e4876575292032b6ef436ec0e4321e408fdecf793c0b5e721094af33440ee86e33435c7d93f6e07dcf04d6c8b417738a7f9fe70e29e105228eb1643fd7b0dfa635a7b79bbc50063e870b6ad11510f6e5a63c2f9dee511c53eec9d7cef519ae9fb9e8cacdce5a6121c886ca9e486293b4654fd89a270f6ec9ea4017ed1bec20091e5df0d8511227773cffb79fa68b0cfdf9bfaec58cd740779d8d778ab7d8587597035d9f2720992fdf6bd941d633f252b26fb07dcda3b6b31ca9d20b47062f8daadd5b20d87b5277bb9937db59abd586212c8dc31dc5b7d821afedec04efe744f57ef40516badbcbd0522fb286d085fa9c035e24c275f23886d6fb7d0c37efb6d81c8ce4e5fa74a4445a87386c770c26fa50d64eefb1f73d317b7a505efb99d42520d4f66d3381f7077367b4675861dbbdaebb2fa99cd799a7f7fa779fb3a8fdad8b3895dd43ccdc3899a356e5b5a58b21fae81e7e7a7eee44ff053ef406d786be1afbce9e069076ac343ed036eaa6fe0a3ae217540b2f57e415a686d5cb27910c8dc3e4a7bfa80d8210e6be1bb7baa87b7a7dca99ea2f63d566bc4197a9a870fb5517b4a5dc309b8468d887a415a586d9afd82a62cd94a5649b6693f252bd7c6517b8fa606f8f764e4e549ca4bfd1ea635e2a0a7303e19ee13fd3bd1a3f67c3d51bd03b5a746edb96b3c9977d78bf73636c2db3c5db3432d310ffaa781bc77b50efb79ab6fea8d8c500b582947fdb16dd107dcf7b461ef1be703c680c98dc7bcd540a0d43bea6d6a4424b09c8264e3df1c3fe086734bbd03858a2898e38465b7efbd6b5703f9d352e780afef8465dc09d33b50fb6ad4aeba4644c11f70e7888478192f23b61fe4b123ca47203b284dedd1c43877805f9cb9918f340b59f051da3ce625f7fe228a0302712884ae275b7e0ac164cb4f2b92d4dbe392a4bfb1d7917707e2b1c5a58efe07d4271df2f858dfe0c7731dd6318f1f8f31f551da1d784fa6c3a73ce2f127100804dec7cd06637f1b8f7aab81401ef44f77f251dfccff805bdfc4ff809bea99c307dc3ee08e3a83d306f3f6c8fcb5c2887cf2c9271feaed40ed7d5d6074907d40793c2fcfcd5738ebc663fe9e6e3aea2fa697f092db311fe34c849b8e980df54dfd7de4baa9a3fe028940e0a63754df1f704f49a76045914d3f7dc019af6931bec3f82077f4f0dddcbaab4181140103031818c0c0a00370c3f8e00384103e8803c68718a38d3e18194959250e181f269d3e50181f6485f121c3db44181f6cc47153b0e5780f85d26678354172e8c981f2b0e2c53135bacdf086bc18edb846f8a6dc6c48df5e904bd75cde6f4429a57c892b69da56b7e54aa41bf142920d5d3d1adabd5cfdb0a687b4d3cc058688a90d6dfba4bd7d06ed2f75d5dbadd1d6a3b11bbebb5cd7b29f4cf69242e5e5e29a73a5b5d29a9a32711d7f45861bbfa34cfcf4acb4545c9994ed9156e102e836a10ab84231bcd2caa4d0253d3d3d3d3dd7b4a3fd7469955714728e565a2f5b6a159793dcadb4b0104f750124f526257675a757783a979556ce596945b1d21272715d1ebd041821a70d3f6a7ac4a3e92d64b8b577dacef4881ed1237a448f5e102f1b62974bdaa043241ed28288335853200a44818032cd6a66b39bb5645a5add65a9578a9ae48e85756ac953794cfeca5b99b1d0974d754787ec995f2c9a85054f3ff8061816c0ecfcec58ffa68f85f5c11a0b532463404f26523d5d3c3343328fb48e1c69b56ec613e35d8092aca48d3af5ac9aea0cc9cb11ca73e4de1fc0b4605a30ad674f778013ce1f9eb2b87a5c43928733477aa9cd8ddbb5e7809fdd6aefb175b407eea8eb9e6a1ff00a58035324d3213af37932714e4d573e994f930cca63dd317ba2a3e48efa644fb01fe38c6cb446f08b33aa685551a40a561255e4cc29ad1598cb027d692a49f2a3b13b424e2f913bfac4e521b7b4b4b4b4d05794021f0c3a3a227214a4091552a828da43fb29f9d92a54ec800a1b50c133e5a4824545900d75f20b9a42ca7e5354210a5b25a2a66832c592a0297830450cee143a53acb64add52771ed845c010beb73d28c59114539062488a25508a24f21203fb08c6529412ea7921122f46e048f0d332820eb0d0d25aa03c8f443ac62fd1acd9bb3da503555977b0a656291d9b5568257dc26b177337ce4879755237e3e69b92b1544a47bb54169c89db78e07d2db4f035a55389a4300264b5176730ac3becda243d3e236d9ba69b8d8a155b5e48acea41d5da14e3724405a456a8044ef4863dead48c8c000000004315002030100a08c442b148940582a4891f14800b8d964e64521ac9d324c75114650c33c0104508010320023233238c1200db9e7305003b0a107ac13c1bfe47ba93678d60bfef8324fac006ad68009b395987c2dd5ee8c50581328c6df62b364a202aa0dfd2fca3b89bba652d788dd0e6267b7620bee5d832fe770d91fca3cb496904568ae0ad9ee96a8ef5f640342c132e3938bc901588ec91adb3b640ccbb9e7fb7d0aa315cca98c5032a15dc98e38351551455893d40d660a037bf5f1e377e307e0c6549994aa45228c0b340d08a2f364e89c2018a8fa1f4f77b5e2ab70fb80fb804c56a344ebd6b2b186d7c6f072f73888cd5b40b799741987b0da129bc8898338896c5486700aa1b181ed103bb1bc8861cd21388055e7a1e5a0296c9165a51303ed8a483053f350b8a3d270b410418b93133bed265da279e97444e46fcaba654b08ddfa526138f9cdf786e86bb2acabfe00663a0c036b0cf4838d37ae66cc153cf49cfc72295dae15a6448e243889c4c4357160bc52ba42082310d354362023b824c8dd8351d50736108988e8c1a0157f4e3d479508d325432936282097faa84ff9f09f5bd85e7981f770561e2c9578f8586cf51090aff7a3685e8612b463b87f8300c9e6c119aea0b074809efb4445d8893bb11ba87a60ad5c760b10c892b3627818b19a22cb094355758b7416539080e0af9b81bcaf1721e909d4edf48786c37acae74781009c19f2ba52442a79a0001ccb045c6dc77008c13eb83114b9359ce3d2065ac3e903cafe0736ce47ec71d1cc04af02568ca4d28137e6a672ddcb339dc5e11884e9145ab6b6b1474ee514be3c2007ccec84d10391f61318fab6563788ee308714d889eeb3dee500057d4478ab9b58fdf0be93829744a56af60acabf4b06ea5112cdf3b970dbf84727ac7aaf832c74fbbc375e607c9482797ead0718375f040692db9737cd23b60a2e0171c55c734b4950d0e9bd8edd5df79d6a67ac160b3d84960cf734127babe854273aba7fa2df2bdac21f4fb39cceabfa4422a2a4bc52c37358cb5641ac8fb36bbdce42fa77f66685a91a2e59ddbf10f504e72a7dfd1bceb67ce3e95f8bfc17c2b8aeb692b9bea06defea225a5fb1559b2672f732559f74aaa7c26735016c6df9d1a509815c5dd28a27b62be82a5a933a5d627de62bf522ca2a748e8e393d053e1700ad5e43a2511b0f346aedf7c838b20fb14c9ccb53a6b3d29dbecaebebb702c3be65aa4dfaf220c035290547eb83512d41d2ddd129421da3e6c12823051e88851eb9cb283070f92c5faa36e78f0045597e770f4e9b2632187cd99c651f8f6af335cb4b4cb2c197507249cc6d3c6cea4a3f17221d72a53538e6c6c7c954da9286a413abe66fd95f665b5c69e69345388c5873bac01e666c2ff2754f0f97674013da91c9eee4609200d3b4ba6594ba8d8767ba0e2238d43f2b42a5144a18ffff765c21b5320a8337cb3e88a8f01fe2316c1f882c81980b0bbb330c819b765ad5b964f3d3d0dedde144b9136a7968f252ffb877d062758f46cf478529dcb444251cf4adef55cff26d901c1022dd9efcc91b20f47d0980ae0bb7201a16ff6436b198c2925fb1ac36cb9bfa60f0e41e70bee87273ed4fd56c4238ea0c24686478683bdfa0396b0a1d54221c5bf488c56f5fce88de8eceb44c78cfcf08067a9aba7340bfc20f513777371320314bf8f2303eac3041ea9f21e269790eba96365fc3e329640e1b9adff52c6bd0276b89b841c34fbbcae5f363fc038ef7312c0014b69126015b99bfb6020ba0f2668e9b8865a4a010c701a0205de9fcd1a68f07dba6d93e44603452d234d8f6288db9dd5193841048e5b5e8d176fb1f4a9ac47a570322bc4d714a655c9d719a2b0509c6fa593a67cf07dbc7e19be7068bd0df5d1bbcfe1e9d8f4576c2ac762d4561931e4b785fd5d10af4732a6ff945ed37e9848fcb8d5a5a6eea2081e0698a7f74bfe247154f05b946b922f15e8045b55ef8f88cdd59ecd10e35e6915ceab84298bb632670022fb70e581adf304cb425853e6c02ed46122417883bb73e73a480b59a60273610b9d3adcf67349caeb0693646252548e9a9fdea17f27f54ed0407cfb7bf7cc8cfdcdfa4f8719640dc30b1102e313da6cf4705de60994422ea5003ea8f60fdff20832a12bd10b8f093a4eec839ab5498518cf47c61f6e407d89e28fad9a40231b44fc8abba1607b4fc971b91af946c9c7c11a9ad8cdcfae517c874856cc2a9bbb32aa0257925a0167a0c9e7f18b91520707c9cc98e15278806d4a1fe63cc79a491dc47d64fa5424af6d11ab52ab8d17ddf6dea4bb80dcd1e1e4565982f1baa827626586120b60c737d8dd5215b344e63f6d14ad9753040dc39374a5045d4e4c0eca4d4d76f91b4e58009c88a7d06b09edbd6e9d5f0fd789352fe10794ec1629dc9c044fdc27a9f6639c26a1cb3499114a31dcdab05351350b1238572db0b3c046742c20b681eb6f162848630885d7483b01f852e818e588f53fa45b32c981ec02172d121ec39e0ce635b8d697c1c82e676378524a4d49a18f2f95993a165a870d2d101246a94b6d7013f6d2e4034eeff059cdf2ef4cfced2cdc445b6fad7c566e597e0a8c455713f1d3dba299f6c3b4610e233bb4300fc4db59d50560560a93e4444950c76ed2db8ca25a202f650a7aadde8128348c2ad7e816c5aa0d84b921591a87a1e6b840d4c1a37c33c5ed85f9a6b8b0c9f940426da35aeb251f53ecc597f08e827580bc036926cfc531b67ad0811e284b8b638a9a31a80848a1969933f2d04de58c1dc847f5c17cf9d9b8aab1e8738b2909b3527bed07db56c2874debf1244e0f749d8fc5a9347c2a428fa4a49f48d107f9a23e5b07ca14ba6dc16c69caffb4a88e6778de406b44f0636c0df507f8b12938c1ebe49803ce9d239c33f06153800685f26b213be4afa448195a1430beafeafc449666558b3274b78ca7a06d94fa2c97726e1b59e81bf689947f7c31b1d3d9a92f1733e67fda2786ef9fe0fa5e9180138e84fd14d118853d9b6f7b2d135a069fc396339255d25ab309a5c8e03b580efe757f70f81b74f6fa00d289c509eb9e5a9fe5388434d38ea608b09485abae4c32b8fd68feca8ecf4565b47a4057ad1cf7cbef6527ec3e6da01f05384dc8b66928119e8a30419747c85038bc10121fe57eb030cccfb05b71e072fa6bbcb597c4f6d80e14f4f00a8e8a2cdbe636f486a7bb5240e4f708e7983d29e4f6bd9b2728bd4809800ca38716d134cd76f26b2295d37a32ed89a7c410ab42e9c9321aa5c96861fe48058e82192a95c880058a04edcfeb7cefbde7e441b5e56d63a9a5a4ee2569ab182292b8346e226d0cd1c076fa4fba6cc02a5f1cafe6d770cdcfe293a76278095a89ac2d069c7935db1fa42a378bbefb1cbf8d8896f6d198d20ba9e478515efca2af86a2b3d853da5e1618080d1d01450482be157ee25f1a9e3264b29d2026e629936d813250a7ac2596b304b6c62d2eb27a87b388f57fbb1bb2416f4c1005670e4fffa3ffd3db6ef4ed8a497a2969d7a64a5a2d2923de6245788bdd20bc5f510d64872c281d867e0b095a9142d3f5bc1377af709923de74c115fa798cabce9d5e35190a57fe6441cb9e1bba9882217f028605e86b6681a97a5dd870fda028a912ea94eee689e42e4b06637a0d50a4b2d4fe4590a49c1f7661114aa863557c16322aa15a81a2b5b919666bedb47073748e3c3a4596a529ffa6bbd46258d6b37d7619eb7ca7a0970bbb8f9e260f0106559b499db55d4c4c36f790395568bcd730b837b189580bf8b9c46d37531e1546c6f16d128c7744a8dd7e15137e40dd86351532149d2a2ec3680e8c349d7f3304bf762042acc2c4e6619ed98826399a6375f9fcda30630f2a8cfa4904bc46800fadf0ae18903f192d205f01f88233977874af31ba8c8039126a89bc2faa86c595234d9d58f465595bd9f4fdade0f748c1968b25bb41bd7a58b291e5a90665bd6feaecb6d320f18eec23071707b78389887f4f4639e6f0cd8fa33f88688572d9e8896d3114bf55a8fad315c4f847f1464d1ffe417de2afd7a9151cbe56d2790a7aae5d109e036598436a33230ce7e91479b9dc3e4c99ea2d2245c38b241d22df43236198a005b11735e962ea25422b3b4bb6796a203a17d3ad16ab1ec37f743c36fdfcc2fc9e6ee206e0acd6c2c0c7412f4ea14102fcc0794acf870bdfee803a53d2695813b245370f87fa180fdad3aa46ec062ac687b8ee8d008016d27d90a8bb3e882bd81b3fa282c0e51c08ac4282bfd39d2229d59988be679e3480d26dd46d2741a74c72ea0a8eefaa1f51376eec2bb4fd562f97700bc5fab008eaac1473aa44dffcca093651d3a20ed2180be3643eb204a8514f9053616dc5318bb17e0acc3fb36965ca642e98d1beda89468b75a4f0f0e52853c4f5ae65b3c4f196c302e80112220d1ebf9b8a496417cb2db6aa3f5a991c266cc800f6abac9eecd791e373dc9b231018adf1078948def581b4055261d88d51b6fadff24a57c9c650253f7a0c90487cc4d9304b6c95960e266f8150fcef4e67807194c13f755c87e7d59b3f854f27ec0be19551d4dce250da0d580e51167e13f7b885bcee2bb92f0afd07e56273c79ae3651eb8e1249e42c8b1bd4e08c913552f9f1028649a3f14e85889973d41755e57b70f4f0938c6cbc6e971a49a8845b2f6f678bd7649459cade59247b5d7d63433e1b5a7265ca682c678ebf0cc89d70187c274a330cf420c7351bf57c8ba0116a1abf69ac6476cb2fbf1cb52246f2fd0c8259eba816f6b41c8cf79a9c03891d874390a93bb9883807beed2c3a468f136e39b4a38584e2a8f6eda251a5ed7553a52126ac737d0ce2f11eba2f146f6deeff8aceb22cca5cbf4d9c616eb66831dc08acc7984b5debfed6d988ef16e901fa8d24d243e4f9ccce6514ce144f27c383bbae8dcf8b89208f4eba3a16e9abc41c059f32b26c009351e7c7b324ab6b693465aa401d3655f5269da5cac62b1820c46695410081785b6e863329398429a19d60ce58597db7d1465a24945cebff3d064a6c701d09d4ff5fee3b3c04c031a4950f0eeb4bd90826de01afc03e9861c96f2a1c92a17c4cadd4767eb441f2ccf7644126ef22f83e2fc6635c5d72841553ebad9cab7e9e9d75ae62318d7604c9a998606345b67c680f0e7815a1e8d7e544010dde76c21ea5c123cfa1e6329f1eed10584fc4a808f6e2d0ab4f1db331387756bf03f071af1175514b7e3612ed9036f28f1affc032fc44c73b79f12c1364febe4269208264858407775e34aa3dae9731928fb7fbfccaa99113856a9d3051c4bf26e066e353254b972afe71513c06903307b481647653e90323a9cc2d702160216ea3c3b97aa93d245bbcfe4ba9cf8c980ead36c9b78b62803ec33fedc0e2619c240ec400b9898ba15794239a1b8bb85549eac0cfe48c2bfd230e0c5fd02f22d2aa3b6244e0ad3e63964826ea8e00590303deefd87457c16d3d42593923811ead551ea7a723200263c1760929d0b9457ae5ac6ec0332b4aa052d2c03027ca5919b3d1ea32582ffcbdcea01a390e9407709704a0f9bf60ca1c1d70d0212007d100a7307ca1ff594cffaa8059157f5a02b542493966f935b30e0ba09f459b3a964efae7fb3e6ef1bae1c8e7fd54eb7caf187abe8840b2023c289152c841da8048e2f58ae0a94ce32811aa9379c17fde878350dfdd98e305e39289f25b05f043d05eba47fa1570df29a0e438a18d9c6e8d47bda475a8ebdcb350b81908ba298523f23c79df768dcb7fe0b566a7f863279226ec44255825f847607995108f7499d817526d4f072845f0e59fd16e7c00bb6348d1668bfac84263ff0156831c507fec00fcd9ac4cb7b9419240dcd243859ef9fb1bf6a58af448bd0ef16aeeecb98970d9bbc47cf16689273f5162d6e557c309bc21adb4f51d5311eabf11030fca7ada6723ade7eecb3e134d8b87747a581441c319d7fbf9c2ddd94902f8f6b5b1eb0cc9cc492036e529d987b82e17ca0a519ba37a01206126d0016b256a2ac4cdb31b40bece7851e096205667f971a40c034e21a6c1e762a8f9577eb0df10217cc1042624cadb45661aa926f5e7021f0fbe97a78c304441cc47780a2534c8d87e3dc7a83aef49b7a3611a2714481488899fdab1b80861af50ed9a00f296e30fbd54807d77082600b907f157267eea39513edf4aea06ce27820d178381e0f6e0d7d5aae29ebadb5a2ad068b444dcb698d586f0aa72af99c942c0096b8d2b736879f1af88a03f69c4e23af5867ed8a41b254428201ffa1a1634f9c1c74cede91445ca9a09bfda7d840352e6f5d0bbd249a7cae22efa8d5f18e5f4e5c8c5ca8460868f12815ca6693cabb380b4334bc5e3ba9649ff2c1d318b00f6c8506b468babe9f2d9a573ae1a279668b082c2ba6d7741181d58f4b515a368fac8efe614fbf0ac8dc2bdc87d5e10cacb2f13f7ec730c82b2eadc5d59b29368711a50b205dc266d65fe30f962bbe9e3d660f5b584861571485ad33451de5f16fcb00e043c21257dfa48ac5cab175bf58d834fc8c53ba86d0a6c8778459ee4f735dabdc3d511236c5ff0473cacf117ff2d9615bcf2b8e3c877aabf67b53db9b56d9c869b2f5515e3bd91a0729486cb533459fdb125a553a69480b87fbdccf79866b27a72ddbb63e969f4a31035165edf8b5df80d16e4427cb0f61c980bdc13aef494888cf61e124994612c4427395111d6739608a79564648bfeed5f54df817d215323ec910ea2306f8094574421636051159938a405d7967a7dc2dda60cba9590574b882f0dd9176d0bd550ee1398626eb4129a4e444f9d9bb45e4443e0e314b52e1f47e51362fef4a3cc02e679bdcd1c8892b09df0a9a904d503f8855b9fa113d59999646aedf1cf915941447bef55e60e4d5bd8f9b7a0ab9449bc229851c73836ec00089ce086661de82a9b11a9690e8a1a1630504872e1d8836ee36c199d45011bb73540d15d6f688a6a25c7472a84f13647befbe0460e4fec3436fd0c71305016d7f89ac52c33b98df5e9e550262176be40a453f06939ffac9e547c0690e8ce25a4dd9e1b42925cb09d38a9332c297ae016fbe9dac10150647f4f9e19e007310868be3b914550b53ffe75e945910e52b37eadd9448478e5e8067f2320d1977a1fd6954d5fc7177c8e804ff3cc5db9164572dc65baa51d88736d7cd8ef5e44c84b3d96a1dff1ea75ee8806d7dcbbacf87d6f441ed6f8df7e410a509d51881f366e161badb27041d477ed1080a2977e41009db9faab5e3d02c03c1f5d7a75bed9b35182f6e68f158a291335cf3efc2b34e44fdabc6d780d3575b010e9c3bbd2c4d3ef0662a11b8cd9d42aa316fcde21b206c5d3522bbdfbf002fab3e9ad59be94999efaf77ea2b9a313500a0bb32ba77c7448ee7ed36d622ba5146c30e447f11f85b29e8c52006e289bb3625b089209b6f5eab53d5b6c32c2d3d6dc05daa934e8cd756931913913e64a455d6f6cb773d157c8135c6bfab290d7a1c3719162774a52a1cddffdbe898257d37b535d054dd9162c85e76255fd5d680a77687bb877dc957d55ae0a9dde0f6645ff645bd36786a77f83dd9957d51af0197da1d6e0fbb655f556bc1a77687dfcb6ec957d55ab0d49cfde39bb210dc3c4915eb85230ed47e1b0b78bc8a3934c026a3492846e0f0824fc2f854ccbcbf066e36308febb194ef33390ee3d581231c1bd0b6ae4a1d0769897303b0eb7f1eb94c82735bb464f82fbea6615b45c2bfedd941761d1fccdea5a261ec26c75959374db340acc162a9c0e8640d8c942851163bcb3e92a84073815818582ddaacdc02cf16b3b653a8b01f8097561a77521c5918d4b49fde3805fc89474c1692471fed9fc60e12e2a05ec22ad891120c34858dab08e0902541680bdd53960e004fb001b69f1ebf30f5d9cbcd449ddfa0cba2b31c09f4084b7f0c81c135b44823922fb395efe8f5c8d0e343caeb848052177bc7f115271054632d9ed40714797fe7366f9d070aab8c7d33fa676f935ba6ebe67381bd0cd8f032f4ae829e28e94b7dbee938976135191c4ecd675e3520699aa0e32f1c870b38a77e8c35c000ff6b0e287b58228706af610dc6444029763855cfa31678d91f623c720cdde83666b9c0a3310b5ccc96c5c7d5e0f3d52554a7124e84a3f6745038bc1a288a00b113162d1f162372401818696f8a9731026b52cc0b6334cb1737a832b9ab01fd8032655de11053f2aad3d8e5aef512ac14f1952140a14a7c6c78eee8be8845afa02d0591609d10387e51febf9b15c0c2f11e0f74541cbe465ed9275d81fd0b63b396615d88c40b7aa5c64f5671756a9aba5bdc49a4ede61ef598ad59f1629785387ed366f52d47225fc84e6b2876373e544acf4ff3938f6832b44ba02df8d5e94ceb91088560adb78ee5ac48f5bc21ba1953e9e4311da20414965df06ca4b81953542d5573d853d75b4a9073ce723ce4bd8d31e8b68df684bac819fcb56896b4a03f9254ffc7b9e4e582c1d80049ad3aee868f373e1c4458dfeb485fe1ceeaad4c89954636a81206f6d7ce22f68643b5aa3dbee171717d348247e5d47506ad3833099718ba79c2d6d3fe7edb07e49dcac46385200eaa54e8e2f61b646068a28b5fe82735e13d75030e9abaab07f6905f20c1b27dd138b9b29967f37ee6b0051dba09c5ce50b37d95ad009faaeef1cc319f9021c303bdc28bea4d6824bba5291c1d2075c3ba699378616996cbf01a96c558e5e592b1a6e50815d1920db9866c6a8b74db7572c620afafc72ee161318a25424a3305d2c78068a9ba90becf01c6d9d69e60c192170e0b6ae516ad1131b88f127d85b28297e347cb9c0adaee5d0bd2f46d98d33265c9f13dfa62a7b5e1849438998ec319e211004e5a0b94ff6e88d8c791418ed5b957b63ca9e8141f446fe75d953a7ce35ddcb614bcba87abf4eb3515e4488d7f31b99b0c84d32bd87742c8a2bc7991ee85ea0082698e64040219426e1bf093ae35bf24d12bd223768fe5905d510fcc3361ab122359a11608ec40adc67e1239b1ec624b6e129a3f60232a6989a120928f418d546ddf4d866775d85f9ac1b4454227a6223465bf4a70dfc0448393cd806ef017eac20abdc4b4667c8f195362d5cdc837296eb7513214be6d9acc1153c19b58965074f23977c576f7272d07ac8e08f763d3899d77d09c23cd8d0af7cdca2d7d943fbedcf984523ad8d1043c2abaf41f22f9c1e70ad6d706cf8ef25a6673cb54265e15fc9c9ece5e9ec75550bd2ef9654a83f2847af9e2cf3bc5d41a1d3cf44489fe0895ca1fe531912e1486395c4a99229ee5d5511ae39382388c6aa118580b783a316da830763d99b839bf8fe065d6e9e6bf2304698240bfa3190d399a11a53724114b47f77f99ed5606e124fbd8440908e75667baff8e6e4924790d047fa2ad0088ff963033f10b488dfde095f2bc4feebdfd65c855fa1010c9ce79197eff8ad4f7fa6c8013e348750a88207ffc6c590b80beb9d9447c192a1df023793092f420864530a835920be1a035d72ce43d2c55cbac17aafb83fe6c15486cb129491b9482688df4311d948d0d0e67b58baa1a47fa0e9e67ddf1a4fc13c87dd4eb2b2dab33491fcffc717c84e7a94bb600ed62c6d5cba72b2ba3a64944f8046e627a64f458e4c2282764d83ca602437f0612197a06b245b56384c92d296476b901abeedb12792829294b208e5c5452c4f80266677a10ab32c829669a7fe13f65db3974f7911fa31efe03719a32727f75707ea9a003ea374241f126466b47438a9f7ad7a4f17286705d603091c956a4d1f44e8ce90e21d214a6a48176874321624f7398b3c3bf4d91da83c1350bc1dd4741613839b0ee401c2419eb76f27a3003cb4dde525d6672d8162effa6c19790d4a7dfe18d7c9562f642a294ae576cf5cee279200ab362be856f68fa7c54747eb7906b3d400cbac44dd11ad9f5c185596bf71ff9a42ab5362ffeacbb4b080a1b6e0f0f154084e57c0e58b08adbc72aef8f6dae74b4873e2f10396eaab963939217fb435545f68aeb5dc6a44011659ad2abaa69bfca0e0c674dc9317e64f558cac29802a0b4d538538e3d34ffd7413debd9effda2d3b8ab0e35fffd2207bcb939869ba2f75ca71e824d5fe2ad40c7966361e7597fdc0160724e941a561a59ebab9e207a82fffc18f06ced6acae456caa8706044936cb4666ab531778d6cf0df57b8929599086b9163234391b4d851ff5dcb0e8ec55cdf81a2f06ea99e10e31ac77c69c271e6e43f1329c5a25fd82430e52a6492f4dbcd76f054b18d4fa9c81b21bc2b36e2561afa1974a44d8e86c712160314513284d8916e73495624eda06b0e1708d82f1e28df2779737c862e0844ef498d4ec6e2cf648934d821df884e318d3ca4b000b0614c8a31c37af78aefcb01fe0bf0b70a1c5874db12165d50a061d80252be30489b4e0a399bdcb4f427b28848070c086357a76fffbf13c37a61a720094415946e955fbba63488eb171a74002a63e4e88b040440a3f2db81d3ade7b5c868f1fd6708c17174e622e8f669581d004548dd470b4344f7af6fe7ace3cc9eaa0be0b687767096dcccdef8aba9e1ac31564970f854ab1ea60e411bc00662748222969f728510d01f56fb96b8864e87ef2930b37d6660e07fde3263a2721b4bb46bef729af0ae2d771ef74aae3745583dd18fcd4f4673a5ebc3fd82a5832f268473fea6643badb3556024158893b73ee80d2bcfa20eab5c8cca90230e0fe9dac4cad9d7fc3e10309a6590809584659375367a06bdb102a9a44311c7138cefaa9d7e31c1e5c234fd60b14d3b2bc9d813ae2ad33440d43379a9c894e10b9485ae365ffb9083945875f2a7175f40a23c753be76180da6a055587cd0bb6db57ac41e433c0da885bf9b881739a477ec0d2648a46195a2634d952db5dc40d963c414023b4c260d9593c64540c9c3882dc6af513068a4446458218901492ca5886d2736bf012737da846216cccf05caa8ce624e0dc3c884744a4a0e47d9312cd3b493f90737e0d3fb08579b65e9ac46afdf71f5ca3c624b6a15e1b38f2909e3739c8620f372b06a5e94285de1b0ae0385de3891a5748ea91189cf35b462514c4856295bcb516f2d1178f3d188ccd2b1bf4468f00b2c84e9d7a55788e50ad995e40629a8cef6342c2c9036839b5061e6c1cc53442834d651fc9e7d1c26f72a16487a044be4ca3085eaf63eb29489be6a3380a671e6d274f3f4a9569c9a717c69c6e1e11352f2e5d06c7e8a712faca2cda693c488f68f10af1711843ef966077dbd9eca9daaad860d34ffdc23d01b7232c8303950533257c2ea119704d3862d90cac4860a7ac3588347db13c1b083ce904b656d96ccd53e5d14ee89c93fc8e3e2f84dad94961445acef135aacf5669de2e4ad5bb5f491d1babab3e0c863904e1bf8c6e0cad5ec867b66ed0ca7eefe090602ac1d2c6657eb111bf079e7e5c39b1672bfd3b6b365d925577a6c1a4008630f0d04c82ed33c547c4e973de699615aab6b4d768e8b750bf13f0a5ded828eb0facf39deee19ce1cd59a787c2336f060c5d4b026420042d392625c472af5e7b97050a35c30d97f9df7b1587011724a4b42e68e9359cad49c700d3202214043265be8e819a28f22a4925be0542e706907d3068b370cc20611e9ba51b31c37c79e0f9c176fb3bb150f7fc947cccc278b517b713484ca49cf9b4b3d3d62df2d19579e98188cd76d46407b24dd6e4de9a64701d861ff6773917f6a5f6420c20553d5e9eb15ec77aafc9e05444ab8964d9be06a903a9ba29bfd9c2b1d5557159fbf9c90d2f60c0f4cfa825cf648f8f9dfcae79818403dce4250f8edaeb189059967103c3b1135e7a164e987f543b3e580d5253e8955e6b2d292f111eabd1edfd2daa8af512631a29d6eb050180b3e090d7cbf291f1b0dd26735cc28fb6dc1e9b2324ff818be6e9702864cb0b687a1686988fb803d13f52ce9fc9604cb0b47f57e6f0a95f1b08ced6905f8b5e150329ddee0d640b2f4e122c0e6ccae4569e1e730c0f109b4c1021c41a1462362673814c868fe9ee171cde6a165e1eed4e775e21251c809d67fbf84b983b7162cb9a31dff9c721723a1729c03d36366aa981392fd1af50e262ee06f9de9c88d5fbb3d2f6a6cb4626fb218ca739bd5a34ed26c7f80411fcb8438b7d3c7247a70c3e31e59f137b52ab98cfc144b8282ed228a5ca15c3b24c8f99e662db14e53cb3d7799aa0d8c86a20c4d38ab5bb502fd668536090586b2c62cd90470c7ca484fb5f52d0f9ec49cfc42dd1d9536a43d43246e7fbc78eb31d863a2f32fb2b4241e2f5a06764a744eb73520d14a872470fda3532401d8e6c14488df9f4efc1f54f200a454a4ee815b3f91d88bcabb85b7a3881ad5b86b4b1a4d22c030375154696451705d2b714a2fea5814242410a0dbf80fe53570f51680a62c1a49e7d9b8fa3c7965f10ae35838003621913e954d337d794a5b62aa11c7ffd2c33fd7caab51dacb8846a9add4aa969899bd376ae0ac9c59646a2636e68f733f929cba23e40dd989d2fbc6300576f290d6efbbe41133de3e93ca81909935c9856259cf62897c6186f743ce72dde3b6a812f1472ae22a002de43dea2ac7a61bd35892a48a336221d1500cf1e87fc0a95c3918c5050c9ef78e119af8b95f12da22aba616765db800e99ad2a22c6e85b08e2250efcb3f56097ecaa1438bc4fe432073423f68179e92856b950225af8f161a6cc457b0908aca8d5474261f791983830794aec7d9ed68bca7ba55ff66a199db492c57fbbd9f64cbb715456497f851ddd29013aff2c933482f459e2ebd45deb9a90d2b86807e9c9889c1d573565f0ead17b67468919c4192454507553306b0cd53244f112d4c4480da590ae80e87625eb166b0548383e6b591c041fd5f722110bb07c6ea80922ea392e400b37a53ce646596149905690d860bb771df76e3d53c06a8ba51accf468807503f34d012588a27c2b1c651e39427e18962677228450e6cacc5d61bd9d0085761f9eac08a95e33bce4c384848bcc7ddbaa4fec82568232b90cf0862bfbac55818e24c14e22ceb2c7b2d535e13cb302b12c92297749878ee625ac824a1e92db37c005ea75e1bab812b0bc95c6e75808ca24cab992e72d433009ed1d05942075e45b6838f29c78a76665f37b0c4f2f19e97dec1420b3c23f0c12ce42a8fa66ea73415c1f1cf60e345424cdd98e8526d9a0406ae3e291942845436322509c1df29e62020598e8f30dd1642f28297c5b98a8fa1a429786992b8b93adf13369b54e9b15aa86c2738f0b9020ae6632be9ef953f248fa5a0bd02293cfbd2fc7e2baffa8ddaf8e1e4053c7568f9d4aafbfe92b08a02ab2cd0938768da11e4acbf2091592dfbbf51b291c1c45fe0c3904d311d11e4f95efcf1ee1ecf5333dcce3ce99b75de9e8f071e88e74c4089630d97be03a4787dec3da684fe113ae21185f4566e688bcb0e465bddf1a82744e0bcadc225c6ad0055cedbc0afcc6155d042464eb65ed08fc2d46b8da2ed83b89a904fa3c983a8334df850b0cf8320af959965bac06d9d7c86e409aa331debaa705fa90399942e8980a5fe18e1ec7d34280e7592555d62920e60e19140235934f7c9e7f0414b2ddda8ef3a3cb279e3942bf0400804375404124744150328fc4f1e82a745b99b43bf7a07ada6b921bd3e1d6a5e0baa2168cef7b013e2c584ca70b8e5a7c05064590c12f507c8e24e42253504b1384c17fa871656218511a75d80e8dbad1c7ac5b0f2fd56f514f85d918897273af029976c5d678f0f7299d993b0e00a7c8d871b51833c893f17cf2bdac1cb8974f95c48b75ff083602f62738021d03c9e5a1962d871bcdc2804d09c2d5a2c0c40e288245122fdb2b5ae67e35832dd91329b5d4e5efaa47bca9b115b2ab16f64275993bf6b5f95d782519a2857347ab3c26f48e8bdd21fe9271ae6fe1b641cc6c90806b5ccd15dd3c30ce143e1d536bc92c15bf7a10d3948a5868ab9914f98af638c42a26914b41f84c687bbb819edd14b44cf420e5951f1aa77b62316a4a609fd42dbe55e6c4a8a1dc60b0c986f09aa552581613e6be6440b455a88f6d115502edfe7239a60e711def024403e6795471c6efe36a06ca2ead6d822bb5423b48273a580f85f62f3582281519d9cbd7df22393d4a09e9092610770b9a45ea188e0e54234589a5d44e78f803013503d054cd8e69c1c57987e97194856ca787f9bd0761387794e4618737910ccc9da06b5ab235a7165c188f62957e0e14bed3107577661ae063525763a7fe7686bf75739a2c2366318340e67986a057b5e3cb44e2c284da229fdf6c38f1db53795e1774908499d5c285d6d366a52b8b8198202a5c2b38f977064e48cc4c40aa2779c04c7242fdc0e0bd8d061527c7ae7d1970d80a0e77d0791fd0275f263dee441ba0b2814e9af3495c0c6ee86800df2625795357cfa65671955ae1e1bbcbe2a6415a3586cc93714a6e0b246edc7b4ade37442bb0f0565ea9e1940cdce0c18ff78330493d2738491c693b618fabfd26796a3f482d91ebfe27603db9de01dd36db1868ffb02a94f69a3c7e0ce468338f5105d91f20a7e0898432282dfa2ee38510af94cb38b007fe81cdcc50823f2889dcb054fd84600a2df408fbdcf255389546677632888c6e9b83b87ed055065c6ea2a5d1cfb115fe129d93468e954eb35c816c08b16bba21bc68bfb36e569c993145fff62feb5aeef809654b99cfe2b07f92d56bb1ab3d480f5dfd7169f3e6097fa7765e242b9a9eda40c191e275bfcfe2249261c23a348116eeaf4aa9f0234756eb0106df31703d2d6f9d6179ca07d8582c17ed5ee25c7f2b4810e8cdf535d6b98aab79906cf960834d2dd162c1e81df796a5e292f64c878cbe754d3d0aa333976151e7e15735b59c256f9ac546becdf8bd95b1b4053f5f3b3a1b9857ae9d100a9d43f9d1ac3e6ede2b26402b997a41ff28a52efd3b4b5ae76f29b6b33314873b8c507524e2ee84a99f952121e20f432e749686717e797afab11a38619bf6950c5759f3e895da3a6b3e99c8fb638b7e64c12f804e2308889e6842a72fddb011766c6e32f1d4cc7ca121d0cba2326dde3778447a895e0a95e932af67bd9c867b31b8e44d0727c287253b1d24b8ca186e7b427f1b8d330d16c6993fea1995c676f1b9f53463c7172fc9b53e0c2aec9b8ae2aea5a9b4142ed3407a82e746deec733e96b6f8396325bb0f462600a7b4efc19746a8f112d04b3ef2dd83b539c3377e4c2fa311c076a2c3d3435d1062d09d3e36f55dc43ff1a71a57dbf73b8fe9395e7f13edc46370d9093b9edc7f10fc63309f23b466c05277bf5ae50c5b4957e90c2cc5159942420b206d2fd330fcc610191e038ea941c31bb0b6c2a3b7bc5ec1717f9334a18d27ab264cfc26c238912c67c1538dde2a93a64c1882a586320f1fca8795c433b0b2fe39830aeac6029edbb0b1fa2598c8041065466241c84c76f8454279004e7981750bd2b96144e3237ea61f46e441cce51beb88a88a44fe94795fa9c932d9ec148b1062e06c1e6485d5fd37f8f72bc83815cbe576cc87867a21fff951e004786108866eb7471fe28c6c110b61ac219ee03e6ee406c7d605b1627f743646097c27d2f97ae82cfdb994c5427efe1903e53d74ac31e39bd822eec6681583c1155503b2705427d6a1084f9f3ff445045bcf2468700538ba7685e18c24bae286e529301dbe69f14975a6f10b818e1e0f5c55aa1507522c0c675f98d3809004a9d8f0863f2184aa1acaeacae025da4011962a75db59a2d329f39352dbfa52fdb895242cb43f5df3c83709f8b193516527010c2cb222955f1e9a878911ccdb9d13f21a168ea45f8f851ad6023815c76ac2922be1f1c905a7d16a50432991e0733de549a09c004044aac35763b19f28c6e3cd8ca7087da5f74403655778f0f6c6fbad0b2529122e58d2a03c735b8cea371aadf128c60e0f7646dd26edaa5c24a6545b0cab02b7abc2b1107676b853562550455fe224b5fe298ad30a50d8d8c17f3f07e7204b422738715f86671f1f1c7f202ade093d42f491119a19d43f8d186f202552372dc6c08a3478815f49d85721defcddcf5dfcda8edbc50d6e339cf757a8977fd773176fc02f3b8f839ddd15a5206971d1bed9676dd45364994366d3bb25411a1458a50151b9b762da0c377a2860fe24be87e58c6f082b1f5716997e8e50c94490f6da952a3dbc44953c2cf53bbfc9ef8ffdd49bc30c175913216aa8481757bb9ca07b418e7103987697790c5c99b473c5ad6b4101215333872716050b13244d11315b0e0f3d99810ef00fd902faacb4e2c8951d549e8a9fda8fc1cd0f6026db0fd42f7f31aa53bf322a51bae70824ece27ef4cdc7a2082fef340734c7dab2b32419b9fbcf8c475acea1492045ce7d961a01e3a3506a0879fceff495adee22b3730d934f290cbeeb10e12c53205cfb7851c439485d4d7f0bb88bfe0f1395a7ca079806d7e7bd81462296c01bf0ffdf33a0e0555dc7b74afe6f788d18daf7838a9e9702c0521ee2baefc73b0d556e8b1e4a7a29a503abf7bae6f2f34bbe4d9d5996ced90097d3b26d90b2a4dd45c5b14f5bf6ca4129e2a259a5223f3891c9c4e8811989f84dd32c401b8cf87bba99235259d6d9a97069cbea99b2e5294112816cd18c6dd9df2745371a6e566422a303a7607c6a1d86b792ccc266b96cf4bc367b32fbedca964d5e2efb39d01c28f103580c5068e112bd5659c16ed884a6b7d7c54ece02320c55169c452af28ea1b76245a18766ac85d4f2f60e79d948d753bc661f5ac0ec688c1366c3b062aa4699b1bd527886cf70049c5484e7a8baaaf7a1010d06652aa7ab68dc72ff7ef64957af2e01e0da48dc8e94940593cf9a71f2b8011b44578b56d3fee99531803f34a2a0dbf535a1b4630294ef72a56629759f08ca1d5ea139254053dae5fa1a30bfaa268a8143b9d304a813a8985c7467ad9c8807cd00f96aa8101e130800c2e677c72af3cfcd4b1008757eb437fa09a49ecee41018e1f498e38a37283cb8cce4ca262999a76a5a195f22b93e1e68464f294285de77350a0355699b1f48269abf9df14e740ec4bcba2d49f568f4d4309b93d28e2489906cabb3ca8e77b437c983bef87f87c86b78dcffc1958a390e9fee473788ba93c10435001f3da5f57207f929c1be644c3b5025bd823b81c985457a0e81d849db89089f6cc06a2daa10d8e7279b286c3d4f97c45f409b534b271fc3298c5bdc3b9cfbabd987d6f011e010a7a39b8bdad3cd6e1ebd01b4f4638f9ff75233d2e56755d2193effb81fb7451ac49841d3ea3e8e1d1a82482b846e74078ed60670e286101a6538e48eb34d9bc1af99c7d3c01e2af2932e94aba33eb51de1c0c41d3b123729848ef534b15151daf575d570df883657d4c4a40db1090229d48685f97534a5b021ff795526c1732dd93a75b942bbe739f159729c27e1466a4626e6bce9f1edd6d8f60469009b37e543b4896dc8ce674e76767be8700de6f81f771e91a1e5ac60f6a54f78a55167d8fdfa178f5b510d3e411f55ba3685835b210edde40dc7a10eb6cf4d724065762c8f326212da7a339e0dc1c9ca43bc14006e4e9b5d979e23cf1ca9d78d31b49dca11eaf9ee7ec44bd465a2edb3360f0b1a2c995fc0444fe8ff196a6288fd2a8aa7807bf78a3953701ab3603b75073cac1224fd8d24a132c96504da294443ecafa8c8a72e3c37eaa5573aa83e971a1f481c7583473af3d381470edcb3f3ffe5aa4e27ffb131825031c61bb1cc5c9a696aed0bedc461625b6c41d786d02eb589fb66579923b0dc48af7b8fc8f7bacd6666959eccd36aa11dfdcb51add02fb0f47ebfd82360d02075f217dac7c0b02667b187cadfab69c2666f316a3c54049a49498480ea76afe0ca6719520b2e84fbdfbcdff6558b59040474220418879793694030a193037002d835d26d9513e012ceab3203d48b1349a8f9ab2071f9434f2c7bc242499566d6141fe9da7964f8172f921d3f1f05501c578572d6c394e2f5838e3b88964b2940fd3b49d13983f97b153205021bd0f6b3c3244a51e7e9e2aae98f2284004c5100cd3fc66948a96e8a2074e757b33004b603a02938f11417cd0a4225066b86f53f233230c4618450781044ee8baf2ac86814a0b5322ac4dff5cffe06529391a05818e1f1c9192f809821460dbd620a62214cc357ecba108d938235e2020a7bc40e6394d99490f271ab7496ae049eaf86cf0fbf1a5f7c6ac8c83ac38496c563ffcd5aa7afc9b7a935fb4290f315fdb2080d99fd157566ffc56ad85243598cc93e5bc0cd441ee56421c91b1c13ca2d39cd4ee2e9892e0f2204e98ae380b1c94f1c560b2b18e736ec29989db86f46a45c021e046341e9d5fbfeb6a86624ffce396fed159c79c10a4c38f881d9930b99919520158260cecc77b9369e38a7100162042dce3ace8484d2f493ed508c4c3a93e6da02e03dc6f0f35bcfe0ca3a4b5c76c5ee93f1bc4134c430037f2d3c267dd9261f96dd6db1ef67d1a7b106aa5268069dc34789b78eed724c22224e8b9ea32423ecd0ad581f18602dab299d85c9493eea60d3820dafe0ef25b4d7aee54891280ab8c29a58b32b7d1ac5808ad6cdec57400ef68cf8b83093710a0e6bc686edc4cc7e61edcfe2584f0f8b4c716f66bf49db0ea718216072f10edf7ab66808cb8e99ce5a02de1ef19f90973f08fb447f6e31b8489a82ed89ed9f9c4be803e9d1e93f4e2794e07202213f237acef1323a274e7871f20c6f97dce1c84bb057002b9e182ce1e963f7e22ea88c0dc4bb05d10edadfa024ce15c3cf4b42020a0224b71b5f1c2319fa1e15ce256edc976c05c306581287e89dcd2e81b667004cbb547f9784390386049ef40ccb0fa3e063cc708ca4ad45ea17c3706841904436809c9dda3f174a6ef07d7ba39bfa76ccae9830109b89e852e70e34513720c3164717862f737924356949054ea617b6429f8db54014c0e1b63cfb7693b71f4f6421c84591abeb16ec35d192c2e4c245400e16f9b5687c7e6045081489da1d7f3207a55b7609afcbb024ffb943e02e389c3011a338e377145cdf6e01aee8c1217597f457f5398d0da196728364b256c421bfcd6f406955a12bc06d066aefc095df3fe8c6a659903b9628cffea40d5175327d0f06c7d8fa635e3930183c0bbd3a40ba172104f223d66bcd018656d31eb5051d59a7870fea514a97074f369874b6a28b7a457c0f9bb0aa9f6021ba621ae5e34726d9b77cfc98824e088cefffe53b33537791a69b2a9c1fdde8efea6dbfddb68eaf9b712eb9a31b73dbfedfced21eed293d84e675d814d7dca1bc40fb10e195c3abe25f82f6c335aed1068f3beb58c6943f2d432cf22b4507677e090f6e6be4057bef09ce76bcc2f0a95ad3f7e3ce21d845da5ef986fb4ca4be925100fedaab4aed7ff841ad7e1d828a9f782faeb4223652a05fed61506b95dd347cf1724058cc16024560ebff17dce3c8f9e18ae44b1c181cf2c6d172bc47778c1fdf1272a8281d7e99a8ad69998c2b6715326b93bc9b7bf1e43969463ded7b43aace019228678443f3535a80428c5f26f9606d5f9046565e12be8a602751f042fd52d20281e64e50216c355ef7cdae0d9c14564216009f83e2fa022e8ca918ba30543faf78554410b7d4ef3fe221d7f3cbe2c5d33c241e735440b718d5f646e41a229c1bade073192da9063a77f5a94902b9a238e0f492e48493578e9d3e1a3b378e0970a5f28e48565783f1f13566c7cbecc1ac778f082619111d8158bc8ae0988e576420d520941377b1d3cb21c6f382987d163a8eb859412f3536c806392cf5aab478f74372877966933525de7ba9199f4903d0f0a7090b49bddcc7344d8bbcbce921a49b54211b18efb9e5366ffac4d9fee1da11c526ad52d95afd39f296f29116c465ba8ed465e405336f555b31ac33413ee008a73c9aed84a08ccdb57e35354163aaa2280e28a0bcfe9afbbfd93ad0383443387451592be0a264e505bc17fec3d7309ce2e263cc4fd55534469e1d64c76d0718fe365e17c6624a4f77daf0944d8295673812bc08f0915817c250fc8de87ca4acbdbc10223b815aefa8a02c9262b97baee0558f386b2d0c3b20c806f9a1d8d930cf6c5953a46fab283983e8caae059f0a27080fbcb3f128370422d0139e84c0d1bd24337924b02c17cb1f97c617f3d1deaa202369f37d547fe0af8fa3e38cf4775f1dd193051f2c84b9a532617a5225562dc7b85121f8163637be6f23162e6ac4f11f1607fc67b3c39b6769f969e08675ce85f04a2c9b4f0f0f6fe5711158e8c2b660a6eb67a2b7664f2a185876a9aef0294205ea146ecc09d130ce6dd97350876e5082b9e35f218018cb90ebe96e6f6018899d1eef4907ddbd252f4e3acc3113acf6217964bde81d6b2d2ff36d796c63d30082385b0f8a8e818710c6855b7f208771f11166fd2cfe237fb21f3f2e5f46c5ef30cd700238793c0dc9702de45d8e25de94361879115dd49162c49cad22d75f8721df500c44b5e8cea9750f7feda1079b4d701112d4323d3661467b9444e21a38d35a0d9fe6e41522dd3338c9d1caaf106998dab225bced60e071b254aa5216352b6f979f132d34be7f05bff9f541cc8448907b680cf1b80e0c06aa5c37844ee5019b8f44ada564a852ea8a4defb26438e99f522c044fa07428e3f5a5175a437e467e4e2a9818f2839393235be5e51e122dae40983da4564be7e93287abd055fd7da7df520740ed3cd3b6659a85a89d8e37898f99a54605cf877c4f4acddaf79e278407ec5ddf3b00cb23a25595f0ce818b1a0323ffde0529badeecbe96ce8ba22c5c93cbbad58cef7afa44833966c39a3abdad41bc875a9fc49d655aa861b1a448f272cb6247a688a9adaeebf6260d5e5eff598ed3513757f3d76526759d0db7329242f3651e58a0cfabeea6119c62301a73c89ee74bcc513871c5bd41ef299e73c068370d39cbed709e2649a4f18b2adb37135fe061c8e62507653c1289db93a19928525dcbdc33ac1fba600ab47a2e64e80aa9e4b14227adab9e18a6d0461b5c61df27b71c150aa537e6f376763b308c1848e68b2f20e1503ed73130968950326957a87fdeb973850d6740d09758fd1bc101711818f03ae5c0b2d2ef41045387882890855c961166a4ccaf0da1479fbe1b717e07f957c68cc280286c8de4b15afabaa7b1513cd2af004d00c5a63e9803f937279f58cf43888b568c5d8142f87fd62dba63de7626ecdebf7150f1ef383291dd5530ef36cbb1d1cbec3d81a2e343e85967fe37230a14f591499628d8f8986b9dfd6c191f869e99db9b8f427f46a17db38e49f73315e928c1a728dfae0c8e15771299fde7337c94f085779074289f6a86ff0573a9296c995ea393f627bf3ccafda1b0efd620fb220984098bd6534d9203d8b7746641fb9900f32e2c687c08022006658e3f88f51ce02f45f30f00b8af1248401242d1c25413bdd8ca1fa87ae195b2af4f7416345f02146b9546bc1834ce41fb26f308a9104fbd91f90bade5a784092edc8dfd0a7261ac21105297bae1e6df4e9616dc3cf199587cee24f84780ced60b6392f729ab27b78016c9841003c0320e313602c8b9e4dd88b47cee08a0ccb891b2cf1b3a7199a34473e35e26f0b6971cce79b327a63860a95c104f1bc499af531f7634f288d5305f25474f835bd35f25b7269b98de6ac842b2d77df0fc4e91073eb9c9a2bef9d0ba0fd097a0e214bcbb1f5e0eb87821441f0cd825ad6d010149510fea5e1a720676eee377582d36a74b7c89ddb0d1596dc55950d92bd62b9000f4fc8235cd102bf92c0e2ad23e11201f729f22fc357be040c0028610280f7b1339081d98db77dd9db8186d5d0dff70fd1a04dd2cffd131ec6454c9638f7047b1b85040d22b973c889c67c83f7b2b1d5c42c4f4d953051baddeb8dc3d545556b576b56dc0fe4602a6b004ed636cbcb056943f787c96d4f62ea3daee3a98a605848b05ead10dce4b92d8c1c841444e12e8d9a27d00a73c31b7eda101220d7ff4dd255332c17f034000f3b156dd958f75f41e1ab0648d706dbaee84e8f554e310f2a1e5dc31bd4bbf74e9ed825190d5977a8488f3cfb537159de1c46d34b8078b8563a465987d1a489b7518b280913770229b106248b346407ea470022ecf8976bcba98897f14678007a9ad4efbb5cdc1680fe8699a0415e4d0ecb5a6b3515ac04ba9ec1b7c88009d66e8daf17e4f6281aa81b9700a15430214bf77cd711677bd8a00355ee58fc221379ebe6ed623a379871dc1663d3fa6f03aa49cb635ebb554702a3f5b4cb65a1374064de165a48ae73e1a244bb9a7ab3e5548aad5acc7dbbd85d2ca4cf133ebb52f912dc7e83564523e838738977b33737966738478bff6c97d666591168ee766929bf7f9f764c3747a5520b1e5527885f13b6d2c74b4f0867ef4684a25ee2bffa99f2112bb2294eb200cb929a2fcd0a67fb4e2888961ec36182e5a5c64b53e19fed99524858c490a22e85884f352805644352c923ce1895a3c2ab18ef01f4b84c5fc875685066db077ad3f5632c28247925d80d51bd303859896b9eed0dfbde9135da0d2a17da01a2ac8c1c43ce73cc2c7b739d48588094eb6e9add1030e2852f3f128cfd75e290165bc6553bb46716e355b6cd5d2b627cf1e2a4b6bd36a2dc263b6f4a2d2f6ec5127049208f445d4a9bddd3e4cc640a25f22849892c7a5537ea845492f994ba797594e55d4a5e4e8bd48a7c775db10c0637b606b200bf8cf79adf02cb84d91c1cc36a8d501be50c21bbea538a999e48482e606f1438a15b7a0d31b7b7c374be7f4eac1f383fad5e694640ba0e7e6400c55f955ebf11a33a2c1a09d2611589d135b1babc8fd92027820642b4b5437a8ac6f34a96171db6da0f789729fe46092d4036beba011745c380fa598f2879be223fb9374cb7d170598c29c354b325ae865b1e9956c0b139d8dbc201c3f9d07b50c68f108a0fe2235bb69b112145abc520d63884daf9a32a0884d8f6f519540cbbdea3b590c68e66ee75435dd5aa3cc178d8b2a32e2e0e0cee3c1ab67c2db6197c214b63e057816fd0798665de6ae7921613d06eba645bee8f278d6eb41a2e86abb994c935daa23cee92245700762890bc2a51b4b4c1236def33da258e82fc424c49614bafdc1a07ca92b37fe0f198bea2a60f49fda58e29e8d627388d75d009717c5fa497b84f3361c280350273b11d9212a134f37caa2a7e3fb17cb48ded46e71eecbb8da06a68f890701d9981dbcec8760396c951984bbe097b598bdc414223600e59345f70e802e6f20ac80b008dc052be5db9a2699abe0f9240e8b821e18f8ea8c9c8bc013dc05fa9a9c2b548b98d2f946108d2ec8f8aa0f64f6e2d5a85affe94846a8f118c1d6ed78d582022d6ec00b0e4fb5084bb45481d1e1f2b7a14740c823921c1b0d78caa70dbd65c379e5420a3047dd3768e192e222cbbd1093789ab8a4b834d6edbb2c8a27b958261b210c8a1530f12eb0d2670b9008d8792105c4489bc7b6ab1b9c10ab29d392acc55dd533ca02c0a71ddbbf7652926bb7ee23b6e85f76d311c48e15a64a46fea0804884cb04a7a431650f6ac4e150c3852e8677f3a7ac2f62d6b4c24d1d4e9be2d2bb07662fd440d30db4adb5880ab2ba7c27bc07700d9cb279720d0505c6634e6e7f82b5fdf6240507ef72d098936e30a731e011eb8b88865f1d9c3d3ec87e6bb05909d255acf88050b537f6b20ffc42506f3bfc08d01572077d18b7a09e766ff80ff39392567dfeade0b682df0a462f4aa6a6d7ae375e50c0913c7d65906715dd146118bdcae2ca25cb2c6dbeededb268bdfdf20eceb396d48dc2479b56a88591bfbf0cfe79c50f3b91572f83cbf62ef85ee04aca48b3b23a7dedd8ed0c80b59d1b4450db29ac14bd192959a9fe4ba24385040b8c0a894c55e1c77bdba6970a23c2652a7499aff52d812ed886e882aafdb37ea7c94c9f1f6e4043f4aad315146cd19ecff1dffc1a833dd40b96a8de03f3e6119a5951b34d0c54188649f60d1499b4b815496f8f60f882c49594a247d02a0f877405d3e17ae1d2fab900af136ebe4354f789f0cfc0a8a80c2e916045e00cc7097b02ba1ac6a7643c17a09d0bb8558482040063bd14a75ed92604963abf13489d649aaace56bac656fb85d5c2f0114e5f75eff9ccc3f3d10089632eb1e8c9695572f2a778b87d0b5c146ea2b2314a081b85955cb38ecdb583057d0b0ca03fbb4f90fbf80c80a4c62d5e0e32cf9401cc91166a43517de18d8910b26961c6abbe1cc6886e07ed034a3e3b27dcc6bb6662ef135e81b6f468e6f59261e59adf2e7b3dd9e52f7678099f2a02edf35949a88e334bf2347966e22d70bf574e10eb0c6d433249050ed9edcf55b3292bd012384ac0fa20f825401d0cf937e2932fa8f33069bc4228d456769982f974ecf47487ad564878d6eb2ea2549749ecbacb81eb9c6714182c9202ff8b0a3460171f82313040c354be2ecdbb98498e80b53ef04f976e637b9d96e2a017fb3b203a7a640773ccec6c2b7cd232e562a3a0949a61622964915b0a8860e1271d9a637980616d3949d8c8023062801a4b38273e9fd385761d56a08541e8294f45d180eaabdbbd040ff3403c85b08d0584a33c484b2aa2d778d0c18041af0e014cb302208ca721513d55010c2ec74525fe5364e4fdbfb66d0877de21d2cbbdfd9cebdfb3623756224110397c8101d09f94b025054da333aa67ec735a4939e65dc09f0118b0401d64301df5d437cda2b2dbadb1b97fbcbfe68411931b3dc38eed03d087f8c01aca14fe5ce5010700d08428c36cae22db81c0544c1c5b2f4f98cd60e89b16ab37c0020ea788deb040e633ac977f5051bc55684b13c774cdb5147bb66be3303ffc27c88056b44d503a8b49430f224360cfc3f4c4a860965a7505a446634a42c36d1e4d210fe28bf5fd004da664fcde5b681fecfad032bf4efb07c14cf12602fd0e004c2267b751627439c93331647613009927fc608817db4bd2c286a96c226001468b2871c8ffef5c650f25a0484a16b29ebc2a7355b5d65f3bb144f2742acf29d5f79e42d2d708918b32043bd105c2abfc8afb30e056d52b62d797bfed574e62d7219a7b51813d6e6972a615d5e7e08f3bf71f3ea7d9147957006fef083ffe227197f13fe5b9e2cafe2ea442708836c76646c7ef30c04901813d8832916c83c2b96a78e8f5012352146826cb534e47ef756af2865b3855d622656161d4a3fdee0f43a25a1523b28430bb41ef5f3e5c63dc8d644baa888d65ee0bb5544373007f230db4474b3d6c3849ca68c0360b6aa255d0a1227df669d56f051bf07d78aa2541e2ed06fca322913be11b2dfe333b1d99df99c12d6484ccf46ba7528090a5895779400895a89433ff9cd835b95e1b2fa5f95f65b05077aaf99b4b9fdac64564d2650d368c915e086213bdd35c89bbec7e451f2ef0227fef6a0e26949d4ee97475579762b634b19031efa03581de0a4b6a27d8ffc7226c1649c7709c09f08d6424c707d01b3244b0173b18c361cb2128f30b2021facc4bd98d8eafd4789115276e5b290306a671203827d2e11caa27183021011dcab7182f37de10bfe8d1df6a473180d9e21c1088e5f17d5f09ecc334c7c380a5e2599b5abc58e687331682741cd86abcfcbefe643918bd3a0d0c1fe86d8e41d1836600e08abd0c6d7763eb0db3801eb53b76392ca704d5f702e99f76e57b510fdc6d78f7f48e5ac47a82e05c77921c44eb174a51918898181fe0e08e1a506596cb79d90d0bb16638aebbc2b9a8329a577758ff1e6debe7fe64717aec5b910a0a3816340d63d0b47ef018ec2a2a4d256c381b9af166393fe83b6c56ab2bde986e7407c5d144df3029c99a691d7075dd6d4204b3c79c0676a99d0576a80dffd4eed32270f9fb74f8c584d8bae557a3d9f3ad6a777d605c47b60f65d0928f5d16095d60e6e9c128475c1aeed34ba024f9f4fa6c096fb0cc473fb69f5806b8c9e34e407a7be78692edb97e645ab42bb7cda57c409fe94a07335a0df863e30343a01feb24ec0d249cbeea5de2ed241ce127db5bb4463fc607f800a01c54743a0b4dfc6bcf24db9306ad3744e35eb475df385bf39b9b3bcae6f06e9f45598a7a10a4c1de5ee52ef020028124d99ea1f3bcffdfe8efb278fd8791ec69d2b49001c3dc920d29d24e95a71b07b14de568dbfa40a3b29077c599664dfa8736db43b100d779488248976e903860bf89cd70d719a21f65bf7d30bbcfddc9a4b0c10942050571c03dae20cbdd0951008ed07fd2db5161262c90593cf992090792eca1d69100e69995dba8229394aa0c083c9451c98ce81b344b206e415b1f378dbf24e0999c9056ab10129ae2b1e6a61c3a34f80c024b5f0f35bee0704edb53da6c02d42798d7ae69d89116a6ee970af0d9ba449c9b5d486625ea274ca027f30c18f13a32579aca3c049a05ca567cb7d353f1ff1fb6d9935743884d64135498ffd48ca8691bc7375ee5544f44cdf551cd28c17f974d74941aa19ac55d2dcdeea41c5028ce36a8a45f563bf369b6728a637adc3f1b5ecc486a4e888dc882e1042ee4482f570435357074515c5fe27118dd744d5d6304ad154890477222a24faca2e15e617c01245d83562f37bb73b465eaaa704f5f022135ab375bcf9e3bc10dce406bb01643d31428d0373f1dd8a2a95e73cd46bd10bb661c087f4064d32f9aba1236668ab4719e97de6d134889341612b67e994840a271bca96c13fde8a61ff427988fa9d114efa08f8f4d65a08c85346dc3733a06be556434c782e590b278147d853558cefb8106382f730ee26d872184155f8e71ca3f052f543f4709f777df7af33befbff3cdbbf9de4ad213f4d8381b58f7964fe4d3233fce45fdc221c47aef663d1475f8d5e2105e3b57fe5e1a9ea2814d484b0fd427241e14172599e0f4eebbdf7be3bdb7beffc67bdebdf5cdefdef3ce9bef7eeb9df7def8fe1bef7837de1e7f6d9c47c6d709fc3a6bb97586911fe1ae21b988a19ca6c9778e9bfda10504c558d4e9f4d98ee04f827af56a53056732c59326d6b49c4ac39f67085c784ee736a267eacb0cb3d279034352acd9194e6e82b3051f2001e4c66a9f1cba6774cb11e77c461f5374c2a98fdc6f23b36559db5cdfe30d0695156ada1fec4adfe9082a98e1d6a0a31b51c3908aa686b7ceaa3e140530e55cf3420a100a838516054b55ccaabb1a72b9eab5704b2ad3073c203318b157166b14bd182a2d84286cdc46ff2bc8b9d06bb75c4518fbe2b3a7117256e5172513fc61265f16ce39a7a0893cc3a88a847bf0dc81d6bcd3cfa26a24eb45e2d33de13bd2a3e7e82381333018f8d00bea99fac0c6ef8296db2185faf09ea405879f54436ec2563abc629979829d77c14624847f8b8f5b86c28373d0c2d06c99030dcbaa8905a577cb935b1ad6f50bd379b33b42e3e9606e705486b3dc156632c2843b0fb2b18518ee03235224e3a4f73ca733a7cadddd416bf31bb093cd0ff2f4c8e3e07348c3ba7b603b6e74c7c0c604400eeb68b9767b6b1c0b41e3e481e35b944b91398192a1007cf17fba728551cc0c743364dfdd69fa0707e2837805d931216eee47d77b50dead461279996857aecc2f2c63911763b522725cb1f70b2d38d72263988de33268a0b075b131a0581e05c444e893f1b6933024d7d2efb905407f90dbfcf34cbe37e7855c4bd1703ad592c9527db26800377efdd7e8e286405af6a02d1c260c8d50dad12bf1ec2c617a0ad7a7e94a08ee0faa441a01212364c47187a8b2595632b28d8541b1893ec8417ba9b5248fb00fab71753561b5bcbd372c5a26b11075a4f6898ddd83ae68760ba3cbedb022e5e2bee3267cb440262fc0be50bf8d674e1be3859182d4e62c84e96edc9a028eb7cfa88158086096979e910a7c313c34cc1d3980f6fb1438a416a4a927c389e63ba4b159ee3efca1f799c5f9bbb43b8c41df4e1268edc49ccba173a0816ddcdcdf16edf9a85e5edfd84a5aeac2b74afd08c1c5c2399c3a96511a12309c9e01834e7e758c7ec8bc9c47e5e6d98167b92498e5b064f5bec9d441e1ac925199ff30f4eb02e66f4aad2b3362b7f7be19509442d057922b30130b3522752004cac7cfabcc6cf225ea094cd9e7b0cdc1b24c37796383decf148c3ba2487945716850453f962edda41d59c8e19f7466bfd32a6ad14a88928f41a0c7528be32d22a89a29ab0a685c830ca7ac000b0d8b44e9953db2dd51a609fad05b5b50ff7caec2d82b32437f5f7fcc7107c1b44b80d197519f320b1303212e3222c8f9487e825d7549e004d0fa337e81ed9062c8ce941f2461b1e279d51b0db58aa5f075d1e6c9f39efd2a0aa715f268c0a26b343962ecd0f56005f58a506fcdbdcbd9ef7474aefe5e27cd4117a62e3b8a802f622b61945c140101a84d89fafa566fa694cb50a79421707508390b6321b8c7bb9553aa6a4ac5dbcc578ade566b90fde50af3a0fe7d8259c3b43dbae640b2051efccbaefac8225a11e0a56ac05c83dc9e8fc6ec0c98217141ddff90b809bb2c741228b0161398855bf3f69600db9df1c6917125099e6d8c2e51dc29916ded49282da8cae64965bac2f602b9a6934b571d828f8d93b79547e72a3ba557a0f3ccf2cf4ee18179592f374a6eb75b62c653b6fb037e44bf11c47667a0549325ba38e065ce34b2118e71a65b188d42af95d69e53b2965b4c2d3735e21c037a8604bac805d9983c92cdde8545e2c9e4b0557454238bcd7c0de141703559cfafe3b9bede0a6fadd5cd6ccd958e229c6b00e5c74dcaa5364a279d60642a3a2386dd2103825ec5f841db1c390a1b5c9acf54d46c0f3c317455a8f314c4e6893a88867dc327b6c75bf2b9227894b31fae8080eb5a0f0246dea59e111a29dee4ae5cc597a0ae0c7455fd6cbcdbd7d4aca3df443dba0465121ec0c0ffaf80a11e586305a95b109b736e91e264058e2467ce9e1fed9882e83221df8933806f894cb4dcd2e48425d2019ce248b52d1199f2a29289a559d461979274c92bc8b0f58716e3b21dd7d16a75000b94e788b3925fa9015fd2cb5db790545a7eceaddcc6677ab3f8745f8ac265e0b35147ad2b25b0d585b01a77dfa3ed649d4cf82a1557abde7c9670e5407c79543e0095c2b9706a8f4cb3887536674954afad762d5d3721c492eaac756b5aa8e03abb20eb635f0985e83a33f97d3068a5f379cb4fd56b8ff3a4c93b558797b02264f889343c84403c5b82ef859fba6df576b2ca0c2912d31fe927bc6517440f1a4ba92792d12d1e6a8d6dad697b675a737f30decac52af5341334f7ba6419aa676095a1075c30bdcea9682ac152234deb77a2bc338bb6f6632e691429bb3b5ac740dbc43edbc974a46947b2d13d0810a3e59a6473a563597097760744099f568f8b30c5b105bce594468cf82b484ad73b38bd9d9ddbb22aa91dc37b0d5b760b37a670c7f9ce565f9bf73eaceb9220d8af565d8b0c30d3d9c1727d32550805126ba3b16017d4d6ef0a8b9102b31a90bbe4168aac82196514b08dff6c878ded886af4a188e6d6fcb5eada08a232004171bc9d3eb238dc5225875c0d2cae1a1ba0ef7dec14a2e0373918670f0f58f8c337b34e8398d13eb9d539c0c7b740ad9f4e7dd098ad3dcb8db6d3290262a87bc8697afc45f5fd64e83447ec1951e68d433f54f08f44adbd52aa303cbe3c33120aec1f263df33b72c95bf323290caf058b28c5992f8c56f8b7262ffccf83435b2c5e930f9be80f0a5c161848282f2f70b50e3767b5b8dcaed49a193ea6368efbc5bd8066f012298ae702b6d682ef466ff046a0c71b5de6ae5c3b08bf820743fa41f1d23ae0d85848b516ace413da9157c9ded888ebbd8e3f3fcfe574844ebfdfb545817aa4e8e7c257d9208226b528df568bf04c59f91a49a88804f8cc49f4c9cd57007a2adbdb8c9cbd11d2ac0b0766a92eaaeafd46587b5952fe234579c6e4148c6bfa24672a103af40d010ddcce6709fcd82ac730910d39c112f09681f35d628a72250a357af5329c8b344df4859649b8ec6aeaf07eb9b74a658eeb44978006609c089962adefd81c257121ba7830b1cb6c5e3dc5ecb29a4090a8348ee6509a4930b3cc01588444d0ba5e0f286b871c70b54718b17cd42f4f6acd740a3ec23773b424d08e39f4a490f43ff13444f4edc4d981a47c55d31aba1ce24bef477a3be71cceec4f2ab66ffe26bfca061367c6fe3be0421243a6fffddfc936c74bd2f4752d1547e0b38ffdca04042f06f560e1180f061ceb813bceb14ddd8a3dbb6727af0d3f90915a220b8328241632459097d68cbf42c56c89202f85e5bdc0f122b0e84efb036ec6dfddd880dd00bb4230ff46749d2f9141926a2e31106ce7c872315f22a0ff0743c93a486e4788e06038ba6ea4067b197c4b3970003ded19ba2b9fc2cc1c4c982e2e544b27415173a7a6814e1bc85d4a35e574250e6880369ac8c5ec7d4f50bc4b6ae3b3104d05cbf8d55393c3ba2ca64b0b42fb338a14d42e695166031ead534b89d16719b3cea2cfda6f83e155983fd7104781e114343940f9bc2b9f294d051a01416a212dea38f05241eff77ca9b6537a0b4442fdedc408fa8a3001e9839a676cafe23397cfc7335ff283ad9fcf0bd64932fafede0ed370f9584191346316909301b503d41cb52df0b49b7cfc7352859395a99bae48275b4f8fb537c6c00fd351fc2ef2b40ac0573809c712ec31e032b95c01f1792f31f958bb0feafa8e3cac4dd5c9e716c3d30a71ff415768a53d099e87ad4bc8e8d0fcf50ec07051fb3b2d588299ded937254d714585431b50e751bf7f9905a73b92612ecb5c45cb6527c4bab07c71ad31ae045bafab5e8d352b30ae32b094cc6516550e70a2b0632533facf49a1a87b512b40b0f247313106b135a89ebda544508a94fc8bd12546bd8cc830837a5f631785323e61fa2e894bd79cf075685d02062a1c5117087f45b1f105cc67922dfbf3120a661584649533fdd2d534c4545817c3228644b28befdb7853ec1b5c99b321777579974cff8b9e54b44755990ed2957cbd78fad556cdf0ec2c0730d5086f938ef3a384dcd991639b7657a95094300eb5be83be504719f48ac476677b518c4af391009dd032e5ce0a1a1786881ef492a258650832020401ea218fa4f95a82cf83924cea01ff843bdef34f98bfef47cd646742da4a046daa4b00ff148958686348c1d89af62aa03bce70c347f6dfa9daa779a2872abbc492842e96982ec3e6a0daa4621d26cda91672324c44802f092eb324165280dae3a1b5a16ace28fc2bdbfe69b587d9c0d53f221645345c9ed95226894ecd8ec898b64222de2e6b6cac0285be08639ae8763dbde14a910911434af20e993c074a25da03d9105262784fd2ef2e755522eecb48c43b4aec5c4a0288c590b3c4a21be1737a5ad8dcb6ce0b026afd3cdcf92ee1b047ce5276135e30dd0469530814cf4c49897482e16e2b865428e97f612035899d9a3d806583f159407fc58cc769d82e5f557a95184e6eea40b1c4cc62a09e2c124df844794a29f046c8a05944f3ee9a2b932f014a0f46dc7322b130b618602e515c396dc07e2950f4de1864164904955b41f3c0c4344a22693f8de0ac6adc95e212b5933089c84fb844e68eb5d72072a8dea978d2bd6c722cade7161d15e3fe8695a93cb063afadfc3c62efa5d768853e8c9047cb455022069bbc48de499c8055e0f66a18040b7183ced8af3dd938f9badcea3073c0a4a692117b643eec8e055e81c55f12551030ce6a3a3fd47050d2c6d38107fecdbdc3a75df81f0a9af4d781854aba5547a35103c09926415b4ab22de5ae95742369774b5b13431e01b12d13c9fd2e8fac65a6763c7e6fc2f55ee24b47ed293ef7ed5bdb3b050515921051118f47baa3f3623eb1d6f142e78b8ff777a55a67cb78bfa55345e789d3dbb6dd27690dd3172da969c6b55cb77638aa665e53d638cb594c3cafe9e28d332eafc67d7833197816783640ac24be02820542cd676333c35a799e6bf081feb99702ab89e7354b66dc078de79ee7cdcc18f9723ef73c6ff529795e8df779de8a06cbe77d5ecd974194e7d17835fe793dc0d54743a383433dd6f7b1b6782ccffb70bc137cde8dcf78ac6ff59e67e5f3bccf06cad7c1e77d34accf0bc1e779349f9782a781e7ad3e1b08ccac6a2cf03250f26160b3f290f03e8fe6f3bc6f157a1f0be7c6c8c7c323b2f166445831f16abe8ff5b3c2f93e6fc70712af05d64f4ed20aea73967f05f0582b1dd6cce7ad7c26088f039e7fde6ab5dc738fe57d1e093c9baf04cffb66be6fe579415e8f67e3f1f8beefa331f28a7c1febf37c5660ce063624783d7e70f00de1cd7c1b782c6fe5f907f5511c8fe59fa5713af37458b9100878eec48b6fe5d178ac1aaf87b7e2f27d36b09527b4f2be8f453f1e373ade8ce7d5f8b07e42f0f13e1c1a9bcff33cafc6f360def7696045e3dd7cac159537e43b3d6fc5e3dde47c9e4d8df739f940f840f83e58f9ca3d1f1ecdf7793d58ad15ebdbe0fb3e8fc663d5bc1b11bc9c99cf9bf12c48e2799ee775f02dd5e4dc7c1ecd8647cde7a5e0d978deeaf3589e92f7c1a7e4f3e0cbf96abe196fe57ddee7c1bc0fbe9caf86e5ad66569e97c45b793ade8dc7c3f3bc251e081fceaac6b361799fe7c13c8f26830f88c7fabc1996a7e4cbf1561f08de6af57dac8fe529f93e8f830fc7bbf16c589fe77930ef830fa766e5b16c589ee725a15e90126c865626cc782befc66305f16e6e6039533a00c123c2f2be8fe6ab79af85814f8e92e77ddee7791e8fd7b8e92bc0e0c1a102099caae0c50d312a08614304522a07100065036ab84c48065cca0b055eaa8ee1515ec0201125043fa5e0c50a1c50d58813262f20c4a1543df051e225488f926a81134936d043842442703c241bf0ec40322b9304127a8783333a40e09931f1bdd430007e519f124ce8f27a238419e51102d5e08777b9a08acf667a70ca9ac17de554a05a22c8eaae638893c3a3876781f7c4fb19a4b87b54ee5d39a972ba1d16cf5b8103dcbdca5b429ed019bcd856f338239582d08c37c490e38ec08c2a33be480114303a267815bca1411850f4166219990543456356f80003242240e145ed0b090bb8722b1004191f63a0704d1c1a505efcc0a0a70632181040ce624c0d53b88059bccfe9032190b03240100c1b8099c0970c70c132c366cc1827100191cd468f0f0c84ec58a541c40d18049117e469f581262a9023034b0c31822b3482510b2b21784e029e3236858627de888108004c801de09e3b0284e784e001eefdf0b883a0a5ca573d9cb83b51f71a31ee1ee61b459a233a3345ee6e7a2b042e74066ffebdabd993b70ee14b085fdc292cff6d63237488ea6d6d76a4e1eb3de1eed85b2098e1364beb29cfdbc3c7aa835516552c1daa584c342550afe60431b40c503533c3a386840cc4c0f2f2c3d28087124ed004a08ac57493018e074166a8ba5181b5c20a849c4dc3c326c946880667b52408ce8c04aa6ca8a8024225869b242b244cc0238daa993255333c38b040158f3d248baa9b20167c354b667a6680b001839c0d8690819543950f213e84ac785c3d36415c65649831c146a86668e5633564a344d5aae9a7870687e689aa550e0d07ac57cecfca63f160bd7092664ca8518186031a1c1a2b3c80f0007213e426831a155676c840c3c1ea8b0c3361aa46b0c22a6938c2072c180f1f342e96b7e25919b1a0786840f3d548a9e9a0c66906030b3c9a9cd50c8bc76ab5fa56352b9bd50d8e07ac550d949a1f3ea0705c353435deaa866584d5c3f26c36587510c34d08334e5459f0440c2e1a5607364732b0d290614848bdcc1d1ec000190a880123cc6993ea610b5251132649f07d83043d455a3e6c6aa6acf0e454468c32c80881071f2e433829228d0b8cf185160d48c0123d2009429c9b079d4f500238cca840195d4801c30b2c505961eac9e984a41040d020033057c0900004bce042f88107f88660411a685c29abcadc9102145099a24106ae0b7ce18503b468c0140ae061871b42165490f2e4b43486185e4c000b2e4a1ac8c0185f74a14516065002071a6628011c17a880165980598096aa1f97905c3de068838d34c0e8e20a2ba268a2071d7e0c59a03245ca12d212d88eabc71862808184113d68f991051496929096781b6c8c214604bcb8c28a2814d0840f3b68a982c2129325b024467e767eb8585ea60d36d08800185e747185150a68c2871e76d0410b07c22a549af48075d0c1f4c487056778b025071ca6a4f80c40071c763a286ecaaceef8ec98a1e35b818d13ac037c4d7c4b7840dfcfcac70373785626ac6806217c1e781dd470b0dac00bc2d2c0cbe0e6821f3f562d960f1e16b47a70c063831c0f705c37353634351f0dcdccccca731a2d382348a0aae6a6e68665810d929a9bd5073c6431380db1d1a1c1b11162f1f0e1b178d084acdcaac7cac5223343115534167c3537ac578f0f841050851385c7c87a5910c367a48319282c8f66e801554c988e5c337c2c1c16d34e0aaa3a2043096262f06660ae109cc4f0b13858f1989e10a219132a504543130294160c7c14d1b03a21861aaa36b891c203080f20589230306d340e0aaa86f6cd09331f5a3537ab1e3338d60d39c460c3b280e603cf47500c1e18c04d0916d06233c710167c39334e5c354a584f7eca17192e98d1c149c22181860aab841a253254e1e504190ec043062d1f2d3d44a60070519573d68c98a107900f2f8a07ab25351b208901a746090d8e8d05ab244b27ac3ec0494241154ecd520f291ba50e9092706e9478488d43555469c07a21c1a1815dd08306270769080b06a8c2b15102818643d5ccaa35e3d1a4408333b384a604a2a29cf3a6888d120d4ecd063d8e6e446039dd6800c4031238c8a0041b139e449110038e0d5813d42328c912540d0f560d0733b015921b1eac22d449cd4d0d0eab67e6094bc88937d3c14ac84c063e3736e24a09d29220363819dc2489c1c829c7888d0e4b0496083407a82ac102d6d18c093320dc88c0f2c0c7cfea0319aac01e6f555bf1b056332d0c61e1872cf04d0002c46cf125010750220030b8a0d3b8da8b3316a8ac3015058a1414130a4a30a1201d12462042595e7451e60d1178a009d1084338d8a0a6cb04c258c200292035817d80018f1c1a2ec0580105134b2841002f5d9080a253a40734a2089954993ae4a0adf00609b408b1cb071e6c8d68094e0d133d6c49728301324d30015382440320393836351e74d08029a8fcf8e484c2521212ccc8cfce0f97053d5849ec70430d3fd43882450210700018a44449822408101605c4c411858809929b1a1a0500c08a050c40881f706841004924c1c39313d192223c3c3a562bf8e660c14149f0b9c1f2c0b7811935bc0c7c17f82c7053011e657c14f8c6f8c07c0a5825e0c3b1ac78227821d87c30e3418f0d3e0d3e0c3c20ae0b667e7c2eafc5b2e0cb99c1f96e6c6cbe9a9a998fe5ad3c8fc6719050792bc817d766b3e7472cda7bfec54e337ab1dde6d3940f065d2dad67c5a776ad53753281070c16ee5e848a4f112a344a44abf84ca99db42cdc7d7b2b831018d13b5713f4019b7238675c8403a3bbd2f171a2bdd564624244eb267ffc1bcfdecaa05531590b7776ec29cf0b8474f720ffa9320567e785a3a3e3027501e785d302a530faa22d504a69972dee14d6c2e06220a585c1094e613814b66b4f528a33db379bcd94eeda9333fd3771d7928910264cdcdda707039b161037dca9b6fba451aaeddfb6bb833c40a6803c717710cc38672413aecd92f5b158bd4fbcab4cee5ec27d32c43b34cfd0dd77bec4527fb193bb93a0f304ac75010ead0bb45ca065270c14dcbf379cdcbf35cc38c343e3233f245fd0922222e03327d8803b1721aadc479113f700d0c5bd48cb7de586bb8fee8ee3bea3b3db66f9a1b660eda40969e2003e94883ae143899ed019c70ac58ece975887da823bfabcb604471ddaa9c3f171e2df44100152b2a513968ec21396a4c2a958136542447774ee9360eda42560e707091d1aa23f3a7488ea9334c1919a40f7ad25383ae981f363a13b1afdd152d991e24db759feded5c371c1dd61f056cbc675064f6db3ad6767fb5df3d63de28f409bacf594e76562f90881105a5c2bd2c861e06875822436d0800f51acd1411343ab0c9411a4011247e44045ccc86a8b1d6c47f4b070548687d7aa005a0861c3c9ea8933784458e15075f484052c4b8c4183072bf3091d8a01da12397680aca078a065041d5e8ee081048fd512235a4ab4f1811e7e763cd68a8a09e18d9601a46879d5b1a2c1c1891dde98c2c9111cc8f185e00a2a26a62594386a89e0134387115930a0c41247436c7c533081010a48515983014298f119c1812e52983144083eb032be18b864b0421c2d08914a238caf160615c38831547405e0e263b2a18e383430c1901807c07c3f0d30430b29d907b210238a2f0314785144260017015079c2aba300463cc0880694e4308212de06d048120016447409d738c2fb228812a2a0b28221ba300008ef893047a4ba082344152639783e00600a007c986244170fd0e2e5a290b1e1657746905c168f8a096e742d44e1028e86ed150da024001145bc914381f48a64c023c1c881a9030846af870f4df665800e3c30e56e022eaa7ca082a22b1f14217332337c61b303074ebc06981c8b2f68bc500e61a060e2c807300271003f6570414711771b04f05484040d8a188264c4cb0a9021a484131fa8babd5c8a871780c4680104544c77a12d8610a305306a3060803b08313451a29b03881b2dee2b287421e8e2099af328e3861b6514a0e5ac32621ce1a48e320ca040033498e2c006ff92a30c11398009e30643d9108608cabc00a60d9bd10717072160a38c68470d529860068f80056694593a8008221061b50f63ca2803b241a5c54509262ca109a30c10229a70c00b312dc0f0041777a860025f4680f23f3f5380b9238d00d8c03187163e30c045710717551c993ff0c08411633c7147137334118e61c376010825eee0c100696021002adaa0828723eed869a8202782b9f3021640dcb182104e34e4d083152440c9e18e266080293333e2001f292d778c50032d9c21b3a08043963b70a490020a260e50a68a8a6dc71b7170608e1fd08040094820eda800019ca84df508338082d10e2b34f00429820bb40268a16247971f700210e38a11c0104266070d5d9c715484833113b082c90e9c963065a44186174d961cd901b500211b1d251c59c00e43761c79c3080e170d4a78a20d237674d00500634c60082d8690e365875f60d320811164807d21421d1dd8220408b0901297270fea90c0035a402069a2234a122075284086a11fbb2a53882185471d4220208ca51e9ce8a4ca32ac3a5a00c11143724448808304eaa8a32271e5091ea030001852c85147d29814a8e8fc4029620611d4f1d2414c08506c81a18e2136ea706511a5873762380bf030830e1494c929a08e2cc098f154061d663861dea1c1174578110177f73bc2a0c3011de0608c15130e90d270bf0172d381bbff99f76bb2d6dc5d049b10dc67c8dcc8414408f540e0aee48712d19c4fb1040ad35589122125499420517244891125414a8094fc28f151a244484828891012a123424684828480847e847c849424114a92240992244792184912940428c94f129f244a900821498204099223488c2009420284e407890f122547848e243982e4c89123468e041d013af273c4e7881223424692184162e488112346828c0019f931e26344499050509220244147828c0405050105fd04f904290112024a028404e8089011a0202020a01f201f20253f423f497e90fc1cf931f213f403f4f3f3e3f3a3c447c827890f129f233e467c827c807c7e7c7c7c743ed6e6a633589eb76797b519236a9870f7216fd900a1e33d718df2d059152441f405346546df766ba67fdbf45afa96a45b9f2585ed6b439d1d6b0ef3d6618973ad3caf76f7296fd530719a2577c6b45af372772a2d1a3141dc753cdc7d6adc7dcbe19ea9b8e17ea9d098e1ee435a3462dc87d0dcdd0377efc0dd3970f72577968bb5dac05d03770cdc2ff08e9879a24503c4dd83788b260885e9f19e98466b98ee1353bc432643d4dd7fb8b78c803f3cfaf7ee88bb93de9af1d965ed33165b331cb86b5bae74b464a5ba96246b2985b12c7fad761679a6a7c5dd57f0166bcb8f58fcd1073c02faf0081dd1b49eda8f8fcf919f4aa301018d497e6a4878b005f5ef9a457097b7b6b6cf3dd896ab6813c482f26d4953d3d9aeb30cbcb56272f71fde6289e04e562a5c7e9ee9b2125b2b9abb6be0ad151bee0ec45b2b31ce44f7e3b5f9f5b53a83232e7bc4a7daa1daead0fb241da24fa32379e29ba63b5f06ee5e7aeb8bc2dd5dde5a0971cf61b2969f0ad3ddb66235aba833283e5598abd90267390ff78c734612aa6d2825ce49ee0ee4ee5d77b7e22daf0ebf3b9d83e37f92958ace72469ad15be9c574b6a363d69cbb270cb6c9537fd48c8e4f9255bc7177eafed1dcdd7acb7bb9bbcee02e6b36eeee798d679cf3d35be9f8379ffbf3be5f3e6996966e5bf3a6b66bf7ae65c6355add9dc6dd67dc9d95837b3bb83bce5b7ee43a833dbb5c33cd923d62d63d344b6e7de61e9a257baecde53af23c699a95c708929b986be5799256b7eed1d7d69b4862bc7bcc73eb33833d64bd78ecd1f935941e77efe12d1fc1dd31f096b7dcc7276dad51fd7b47a3b486e9c57657119364a5bb8ccb2fdd7dc572f70fe78628727c8baaaa98af7074d892f302144ea1f0a8dfe4b43cba840db4c8a9dbe42ce1d467729c95e3d4593936ab2459a0907d9435248b4743c879c1a947bd260700364071a5498c480482b428161437435933437ce53dc9f12bab19d2468b0c317cf4637952e4380c38cf4acd9555ec86c562b1e85773c362ada88d0df5565656de6ac5fab258f1e80df59666282b0bd5b757d486daac6cb2d4d80cd93435389a21b819d6102b30d87c144733e4ca6ae6f3a88d37f325c767acd8d01b4a73727c6648de2c4a3384eaa3349425e44a90188e867a0c67c8952cb11bfa09b91224463364af94bc98cd2a7633c40a8b7a43ae6489d50c1962c573ea23f6d10748800b58065a44c41843440c2d44c61823c6d722a2811691315a569cc48894d1d232468c166e457d8b1891315a2b4a848cd6473d9a440ecd6a87b2680d25424608a5a144d0681111d32242a645adf838d902470cce10a81b6ac5b7c0a1565ccc8c022d2d36d02200d0a29b21e66a6786c819ad1b6ac5e188cd60a065019c0a58a045436f2c60238488192d221568d550221568d1d01a21442ed02272812ddeb76a2287489830887c41e4019f8b30339443bd288787908ff2e0b162d1aad5cd8d1616e571539483c3836648074935940657336489dd4769862cb15bd12601b56702a20b10a6111042eeee4f85cb1fd6d875b4e3ade2c6a4a6ba08c5559bfbfb03133fe87e5002abcc7e0812fac0c64dd79b0f5dc09aab49b39a42354529d11bf301caf5b6176f1f680f26284ffd1906139f0a061bb70e4d1deadde3f2bfad423df5b0a507a61e806c9963dbf2e9d3b66c71eb161b7c8b92fb96d6c9831bee3c6c71eb93a2b699f6230f5c78a0b983630debfef519ee20469f3a749631d46ccc55d476a44d3b74a155ee0e36ed30b3557387241de470777024f1a566fdacc30f3a4cf17ca63bda78ebaeb48adb8eb756f3d4a1055e5b6d399891c3971c56c8c16797f7e773dfa7225f9754247e8dfffbd65c4f1bddf8be6ee229754f6d7198e3ac9d7f5ea1dae210c6dd56a17038dd2d557e4dfffe8eb4b9666c96bfdf56cdc762a56030d9dbaa8943cbf353e1300020c8549f4d5b85226a1a72d2f9c4b49bcdfe0d776325227a2c568abe1e8b9532eb2b0d010541c1fb2ed197d069d6bf6d5dc9c762a5949cc25d253f536133d4d5b2ee7a34d6571aeb13c986f64bfd6759d10d5d6e70e10626f72a54e80bdf107403cbab50a1a3a553a8804d465363aeb99bc347fb7e899b6030590e5f1b30f0da76adedda9bae4a24598d6cd03978df062577b72108580319f72fb1aea14b0d37cf7dad86204bf28f1903408c3bbe31009000301dcc9802d1a7c9001084eec8fb4443981776a2818b8374e7332e75e8eed65de4926435a2c149cde51fcb24dea2c11ddcf8daacd6b20526735a4c771add452ed5b2c4d65d6fbadef4db8c4322a23d0317f719cc199cfc8423896ffaf36d8733fcb09b3ccb50d7f06f44435f5a19514c579be8ebc77be2224d562a9c4db366acc312e71adeffb9264328434b6392ee13d35be97d1d21d11c269f46f7498b818a6b6330f18e012a8616b833fed6a1708054d75c951870fc5cbdedaaaa2a26af5ac1f0c5dcedd3962b05c78bc5d72d18b6c0e024ffaee4d630d8b8839f6b1be34c7734ba5fc75ee002ee646ca9eaed69da9634e3da0b53eee04e7dc1e705bfb674410d7074c186a7d17ea4afd9ecd4daca5c98a2515d5dd0715c4f1638ca7ab37cf1baed68cf2b96a45946122b8d24be6581b2d91b2d4a161d7770bcaf5b50e3474a56bbefe34d73986ce18b3b384bd2d5a6197d2cffb9d682e96ea3a2b1168e1ce4f96c16117dae85b9bf3098cc3c711833611183a58bbb873fd66c334f2cb470bfc6a2e46095199620bbfe18eee0d83d80aca1f98f0b3f6a5c8ad217ebfa392fe144cc345a6dab6452cd5b87a46d95ac8dff9f6bf7b1e05cdba1e04e651a06930de97091ae3a5c84e4e724c92aeaa8a0b23f86baeab8e84c92aca152d5d1dbb6b9dc1c3771575a8e0977b0b45417d996624d5f4f6549ba5fd3dd3fc9e98e96b0530e6af7b8a89c0f88a9ea707770572b62adabde3d8e6227bdfb273098ac867136a9c2b8efd7545d7cf7b830e62d2aaa1ee07902213bf7a9036e35f613eebb855d822603d2ee972516b50dee0e6a2877d048ebe40c6420dcc19d1d699a15bf25737dfab7cd53235f93a4cd699b93aeccb10392b8927f9fc0dff54a18f088b7aed8d0e37405ea0a35ddf0f156938bae39d9486273ea459cb5a526b5780e2cc6737ff17607cf6bf7c5e2d611e9fe62217730639bab0638d6dced028e76ec212f1497a21b8f15476228fab77d7db8fbe3ea2ec3b83b92b74a9d6b4b62fdb9963a3731d7d7ba7407c9129355ee4f23a7dc4132086e8bf1374e37d692e2a21cccb8867359d9c21ddcbabff85e4bc997a2e08ea42f927c7d6b8e7ececa162b4e7664ee2fd5565bbb01f7f17724ad611263cb84351dd4d62a599b9b026cd76abb733a509489669c7473011c497c1bc1c12d7eadf6f471755b1ab59101c97a6d896d5c6ca6bb833625b0360738bb7fcbcd5a35291c9c7dc6e2ec5adb6cb62bfdd2d6707030ef36bee56ab39a90fb58fb1cfc725bd90e7de9e830f7f7ab7077306a244712bf99df7631495fe3d719ddd035376e317201c72864dd78d3bfc9c620f45651632c8abae674dd2213ee600e5ff1fc8c8d88767c7c902439527bd23b5728fa8a7dc6463a43e247d9f7cb6bb32dd1c61d1c6dddb996eec88ba94eac450bf3bb8eb4aa71ac34a5dc5f5a2b1686e7e25babae2d4b4bb5ad38572877f09e36dbb562b571b75d5bc37426061c9f245fbc558ccd4cf76d73254fad6f627daa6dd79a6625716ee35990500d1c7916d11a0ea988855cc0f13ec5b44ab58db5c299575ac3611016c2b0708413798b852755e6f84b77b53c81fe6ddb6cb9abe6136f55e91255e506e64a9e628d55b1719e1e9cae9b7ec625cd98fec5146f2a6a682a4c78a562666ada9bce7f734f5482b83b4856d126796b4a18bf27c65074fcbfc9a6709942abfa9ea3c5542bcc6149d3ac75052e67ce58a4ba48254d5cc6b9154c7717755fdabcc28eb45dab821a0ebe2e32feb5755f2cd28daf0a5f1cdc6d15741a3ba92074632ab4c0adadeecbdf964ea560aacb93556fdddf2fb1a833a5a34fd62927b6bfc9a6884811e3a0686d98ac54574a56fa46de9252e5ee3edea75fab346329534b526c7ef764d5350a13be7f8b366f4deb675cfbac43b5a5b66b71ba9651aa485a256da5aa94e21b45098bddbfa31b5faaad136f4571d076eda5745f8b4d0a858a16942ad7ba9ad6d43d85f2249be3459c4d7ddef6cde18ba33bfb773a23b6f94daaed93558a7ec646549ff86fddb76e7d976462fe625cc6398a6f4b96837e5d49f21cff264a8d1593b88c73a4ddf7d4ba5a662c7e5498fb3b2ee17495e174dd9fb148241baa3ded7be2b26e4c65cdfbb75cd26e5bb25e5b1b97a2461b4a6d11abd4cda59c805566db8e9fc5f075bd89f934a58278d41c6232dedadea2987077b2eab0eaf32946cd1cac328b128a6a6dbb73355350c3c479a442d727c3fd3a852df74ce17686292891189b2950f714727f5b5062c21f4f129b389b218e3c69246969f9a1ccf2a48564cdfdfdbcabe447bdf8e2ad5463250ac390ac3fdaf0c9aa49acc3709f98243fdc3adb1a4ec4645178ed589ffc8c8d461ceacf581cef87175bdbe7d15673bc5ffed63b4d646e5b84d3b5e93336d217db7dd26cde178be1ef4a65b3f9bfabe69756a6cf5db794d1c5f6c55bc95af4a2cd26597719ef3ade5acac85a667c6fba2ac16032184c866b77e9888bb79c6eeeee94de9893d2689eb31fb1089bb59ca81873326b2ed644c6df34eb8d529e8be97d6ad252d64b8bbcd5546489b79844e0ee60c67496b1d18cda448b24d6bc754ff1a6494c61987460eeef786b3ec5ffdc7e1a7d5d3f8b3f7e0e93a4f8a666a2b67c9ad476edd3688933c5f9b1f0dcd613356e4dbcf52488df5663b3c422e5b9f88effa315ed2945b47bdcee9f10fd49d628bb7f52feb63f6251ef1e473bf5132107abcc9eb4de56cdd0ac7f73c35b286ce12870d1d9da6713855b88cbb5fcfc81b750701292a4c5a1e0c3c12a331dee13ff88c570bf7e5d6f50afebcd44daf7a99e4866e2fde2c557e7baeb51dd75dcf7c446b58fd2fbbe156dad76ea9a915ebcd809069311edd01750d0a993b1953a2e39e132ceedbf64dd6fcb7587195f5b86e1b525ae5d9b3f57f378db36176e6b65bc1687c2be35578a533f62a3a7ed6ad397f57e596ff8e439e212d7eed263b13ad4d5f2a9ac6d6b5c86bb86e3b54fdedced10e949da898f48b28af9c7fb3a639b7fdfa552d4edc7897627695d2d8d2039ef5bd38a9fcfbcf55d1a123f4a10d550294ec1604054f78fb67e9b1d37c639fc5db776ca4f2bcfbb4ff3a66bb8efe76a2ae51febdf889086c48fa245abafcd5bf74442e790f851461f20dd8f7729560528882e113aa7b82fc0dd67de4a4ac147d29e3e3f20100fae5dcbf36415f5f8d766b5aeb75da132495651ffd7884a712ae39c3fd4d592ace7162bd18ece50294e213902a474d3f516de74552ac5a9d186b66b77d5143a43f30906dbfa772561b052d47d89c57de2adb138daf0880af75b690d231d117104e5a0d626de47addc5f9a77cd365b1a91f9d2565214f296110d2da3a19d9b314d876eab65e08d197d4f803b923c6b5abf288ebade46f056d12d0a12e3c2c1d9ceeb28fa1a97e84b67f6f9dcf5daaca695daaea5aa37f19eb5735c3223c96a34a395e27d2dbd67cc88bbc7564e9ec0575bfa79163eaeee19b55d8babbbb12af1969329275f932fe439fe7edb7896676975b5acb74df3effad946b56d725b4da83b588a76d712efb3de4a6fb613e7dbbed9ecf837598bc80d77d0ac79df5a6229222edcefc75a443c10c1d870709f988e19d3d7aea225cd73a4755bfaca4ff597ea9a2bebd3e8ae9614dc55260b604285e21ca6bae62a1deb2de6ee27788bc937f484fb498ad76a2c52bd6da92dbdd85e1b03b7a5e3677cf12da74f5d6f434d96b8e1f96dfef3ca922d3bb3529c1aef4be9f3770debe4cf51fd9fe4ee4fde5a22e4a0be89faa475fcadffbcf2f9a52c99f9e22049567b137735b51d2bd5f6d614602db83b146fc1485022c61dbcd83c2b1d6f7d1ade2ffacabf2b49f5e351c916771f6b941228778fe22da1152c212445488abb4b794b88d54af2c5c1f1c7fffb348ab18dee485aa5e16c527d9f2affbef5e6a7c2364ad6f3d6260a8e6f9a3893a4cded489ad51a9bf46f5b9f38bfedd6918eb7d29d7de22fedc69b666cb339d6cfb65be9b5b4eadc936a4bb5bdc9c0f1f3537d6d2dff26a3e0b823f5892dd5679366aaed6dd31d8dbead66bcb5584913fff854f6c5cfafe92eff79858ebfc5377548ad24b3939638d7ea6eac9fc94ab7a52f5eab6fb2243ae0c554e3444c6eaaeb4dc95b48c880e3efcf66fe7ded93b419fddb4ec15b48743b63d33c351e9150f0e29bb639baabb74d7748b2ead09bc3f96fae88b78ec0e160fe4dda9a266dadb58e70d9dd4a33aed9ece7a7c29a3e49abb666f7fdd1d2bbef8945b182e0ad23a63b78adcde6277767e2ad233a75d7119bfa73c622fddba6bf71f999c6c4f89719ef6ab7bdf626da5b8e8eb69ae795cf4dde32a2fbafd15c49f1478a77c637f1569ecf2d23146f8d798c8cd4b482bee05dc3745773145f5aa9b63b92acf9efdbe8eb495abd89f5f33da9aeb920d3835a4eef3f8d3e55bd51bd6b76dcdd5a528c81c2584a5f2da02e0efe7ed22cb1489b8ca6285945cc04a403d201779a8ca6680d533a7eb6fac7f1b5fe6cea7e5bd006be0e1d5f9ff43e95b5d1bfc9801cdcf7357d1ac59bea9bacf54386fefe2c5e2cf25c7c977e8e703abe783f7fed33fd9bacf533d5fab171a789d5d4b486294eac24ad549bf5961f0a1cef6b7a2dddb15dbb4f9ce96ddb1cc523c51b7cd0ead03fb5b65f624d75cdd1cf3cdef211736d997fff489f4637be22a5ba08fd9bccc786f1777eaa6be9d3c61f2d15ce269dcdfe2673f7286ff9f038881371fef2d4178b9424edd6f473a59fffb6e9ebb38a4fa3e3bf289e9a6e7c6f15697e13539c88c97bd2bfc9425d2d898668bb5a1211ede81011916435fad2ee7b46c16044365bbbd888ea4a654343a538f5b6aaf597433a5cb46d6999a484fb66b39f238a9118d3c61f4b710a28e82c7fdbf0bfb6f58f444c769367d3f8518674b86897ab156dedefd340355a20132dd00ce22d100aac32fb7d8aaf7b90e6afe95bf30e9f2abffedce281c3ef9bfad42d9e2dc02a33bd5feb1aee8ccd277f87e36f5b5ab2c563c326cf5afb91e736ded73c42a5a86bbde600abcc7635bbf10d2fb63a3f55f8349b454d3bf53dc31733aec160fb49b242cda6d07f29f756af2c79eb1793bbb6f4c5c12b83d6cbc67d466dd7fe2d4777289321aac364885e6b7b9a0969b83b8db74cc040cb040ab44c70c2dd73bc654297d98cd630dda13a94fe68f5b5545bba2d5db2cb35d79b48aba57b47ab0282e01467ad3ef776e4d85183d6c249c58dec409955d45ffb51a455254249901c311204f4e3b323424b47053a6cb8fbb55bff48e9c697ea2e9e39dcddc5a373f1b04061502968b36659c64975d7a3b16252ebdd3ff9ad2b135ed2381133e9dd3fd9ba77bae9aa24bbe9aaa49b3029759f499364ec49d366d9f8bbda3edb6c3132ea3fe9e8e222a31ac6194a9c9282f2649647e36f5d99969eca6eb1de25bd7565d2f506b5b565dafd1328a33dca0fa53f36eafa4ae295a62be5d1d695c968bfd4688fde92e67fd2d6bd93ce98a4556d8fa49865563aca6127a0202858368ed8086aacafe449269005c955248cb625264f2852e5ee1b937a7cb740c95d4574740e75cf53f359311dbf76edc664fd6cb33af74e2468b7494041e33d697e53d722102868f7b8d9ce0e7dd1978e4e0d9b408d9075acf456f26ff4e2fb26a6b51c6692a2ab4dd869bc274edad8e92e49e51ff353ed699f4f52b94fc258e6849dc687c542f8b2816bc81c209410d79027dcbd52215c4366aec7245c43a82be112a20638ee6a0097902dee4fe5123282349ca94c9364357209e171510bb8bb8c24ab5118829a242bedd43b931c71e9a2477f1b298d2b849ccf3187c91a140a6372cf05821b2e10c2708120c505c209eeeefac0899fd0f501d535044105eede82bb3c50729d41b396491051b38a61ee2f521417063cdcbdd1fdbbfe559a1ea6687c6c8d4607b45d9c7374dfcfd12fb1140535edd437f1d65c44b3c48f15082c398478a2bb47ab81d2bb5b477c51f0665ee8eeb190c2131b984f73ef2400d70646ae25aff5ac700581c39dc25c419800ffd4f526c260e3dfdcd38fd86916a3e243d89af76dd32564c594569e59a495ea1ff10df1be36f464b042df76aef4335efa113b151dbdcd323ade0a83d1ca331b794a90b6d6b49dedb30cb7aee4d79eaac8e776ae0cc2e80c661c45184bae0c82dc9da4d1ec85c146d2de4498bb57c5dda3e2ee2cf7a698200e3536e0c2e0f6f303fef0884f358022eefe85069f0ad5055c4de7ee4fdc05a4c77de3ebeeade0eea9e0ee4db97b52a2b87b50dc3d590a9e1418d7055b67f0c5fb5a7fe809b92e00a23338dec7899516becddb6d43ee4ec57dc77d8abb7ed8e03a83a117e59e8223c0e56ac23dca13b336d9dcbdbacbe57e96b4f16dde341bd448000355ee5e8a535b8754f35971ed2eb95a3eae95d09acfea6af913771fc1bdb817daccdb2d71cf89b7e3ee55dce5a3f503faf06c8cb3f7e4c4cac26b72f794506042e1090a4b4a2724118d6f9a366f0d2b75cd0df57092f4b223f79860d9e97254e7d6f98a896f89b3626fb61554989212058a4c2a2a0528a726a62728289d908474645414eb01795e654c286147a708092388406488903265ca9471afe6e2411d6f739745578e1cee3e2bc39543c5cc95c34395bb6b7c5fae1c3c9bcd6cd79563e4ca31e2fea230261ed203aa08c3a38027c68533e4c2298263e34e8728059f1c6d7831cdb8e6bab962df9712a7dc3d0477ddc41035d622d7cd1077d7d526f0c92a56994999a538b5349b42b359d43fd66a98e7c79a37dd551cf6f1f1f9d1c1f86644c17de2fdb6aa7724799656eb26a329bb455bd6bb1bb1be310aea6d6beecc9bb6db96a75abae42fa619d78e041d39822487b38deaf29442c1d98cce704fb35b2d59cf2b4b14c4afa7fd48f393642df5eb77e5196fcde18b8b82a16048b227270a8a74a62b2677d5ac9f9b99b5a9fe6c469231ea5ab9bb9364a5bf4489bb3e30ee6ed34fae6f0b09c6bfe9d3a45b9f569654d1f5b55c9e19ee603e6b74c992f15d5e025c1e97252479d6a8b6d4bbb93c1fedf2dcf585e27231e393e3adb77bd23059e4f22dbe8496f5f3d6d4e553eebe1257b0f2326470edda325d9ce75ad8094e4b4e513129654c777f5b356b6564ee5aaa8c92b75553c81d6a34194dd11d4defc78956764714eea5a82341e786a5a81b9742b3cc4ae7d31d44ee3a68bcb86869bb77c5bd277574e16ec711ee66fd1b85d9e1811d357504c1615cf458ac94ad42edd7a1944c7c2a182c1cdff5ee71eede91111d117077f2e9cfd7aa76f78abc98103a865c6730bf49fe2d74d2c4c1f1af50706b2bf9f50ad0707a9242c1572b885a41518f0aa070f7883c26cee2c15944b83b0bde9a6301423e3fa00ff8c373b1b612fe985fbf26eb9f9924cf39384841993398b42d7b7a446d4b184c93e4e9b1bc9c0a2081c5fd8a192850230547ee0e8a4e1e75f74270f74070f73ef8115f9bb707fe800e38d82008cbd3c0fb310615364e10c46fdbe6daa34dccfa6247078769969d90beb40e0959e84b67f7b870470bc35daed7d240adf567dbb5344c98d07c8a2c276686c8e1e3be421c6ab8fb8c4f1c42b86f7cf77d29fd4731a9247056771de9bf140567335ac3bac45b9f228d516d8ffeb66dce4f858da63e271b6fad61f19ef8de1c5413149a71ed6b96fcd1dc62adddd750eeae2088c56bb5cdb98a543f55d574bc75ac98ac4fc354d75ccde4e79b36eba5558a3559a9b6a00d7cfa7ac9285929b85fdbdbb654dffa2559f3977f33df9655663f62a3dc3bcda6ccbec452b3aa2da63746c126b466a550f06ddee3cd6feed747e36f6d757ea8d9147c69cdd2faa4c8b3232f1629aea2bd675962916a2c6afde22649d256413a7a3282c14292fc1c0c66ea2955a6d84c92f6c5836d27d5186baa6314145f555791de250aea7d6d15dfd49f737f35140a62b26af31f47bead5599e13056329a4da1af2dbd6ddb7d1eda7ddbdd91e2ad7acc3877cd17c54a6f1def8bfa47ca44b18c1b3b196d8d95621b3bd96c11c6b25dd1f850fa898d2dee2ea4c9efbaa59af6ad544f7ed42c0cf7db661e14ee5e06adf16dafe93d8308d9e5a77a7aede7f1ff7a600d2abc071cd668c1dda570ed2e1dfd275d0103050e94e1ee4264516fab50484f50e08e9ce987c1f489777a47feaea5ddd6f6a41eefd39dddadb46675a894ae3681e3efee533d7dddcf55bc4f14bc553c77f4dbead6609559ede93e93ae36cda6d01d6d47ee9bcd929a6a4bd632dbbf6dfad2e68f14631b8dd1d2bead9a58acaea5ed733b3c175f1dba7525a1a0c8a4dca5110a3bbdad4261a7a88d9dd0b8b983466838b117178586bbbb9d8df5955e6a764618bbb1fef9e715f2a63f279d4184bb8fb03380b8834e660061465518a43b859981731f9728a8e9c52335cbaa7b9a711205bfb49ba4597d5fd34a5f9ffafe7d8d6f22cd275681825a9fbbe66a9a958e389bb48412b4cd55fa4514d418dfe8ecb1d8bf7fdb4b14fc7147abb453d38bcd93cefecc7b46f1b6f46f522838fe6eac1fa5c7bfd8da8c465ba1cadf4bf7b47d51692b69248564f4636d1a8da2488ca1c61f97c6871a3f8fff240d8a4ce9eebeef539161e2eee5799fce32fe9bec021fa8b7fc26ddb7da5ca968a5da8824ab11a8b5ae363551908e4be0f81b43510a8e4fc75c739f292569266dd76a5df7db6a188ff4733bf7d4a1a03e6fcd6fe26cbef68fb45393e48bda96b486a9fe9d586d4feee81aae385db70ecde3676bea9062d88924ed965a62d251bb5b9b3096e1232cb35d7bdbd869bc503676fad2ca6c176729fb7c1a492c75a18cf9a99eb09394d1e228d8eec85c917e57268c651b5359a88d9da0b87f642071a730bce9920d06d35bf797025c82a87ac4ac3d0cdcddc60322e5f4b424337282824238156bc297024e5cbf0567e1c5b3d7a08c72408c12429c0def02ef87bbc7b47e72bf2697c450f06d77e36b5baaf9ac2018238cbb78cfbce96b6b4b8ea1e47519e366b631296534ab619ab1b65476a495fe489e2259452d811b56534c4086b7956ba9ded51c5975597335930405fd58bdd3b8762dcf0496b4c2f0228c29408a37adfd4873ef440bb0450070c060a2cd7fdf666a6df5df3f0127fecd4816befe9acf5af3597924e0c4dd69388203457498782e77f75a4460f070a73b728b9fb38f59b72eed262b89b1d953ea9a136b8d270e34c8eaee55b8bb8d09dc6b687571e4ee25707738de7077121ce1ee23707711b87b08dc1d04eefe017777c3dd3de0ee1db069c3ddd970f735dc9d03eebe017757c3ddd340c3a1dc9fbe58c27bfcb8fbf596177638f9a32dc92aa544620c75b135d26fee7654ec2f7692da18478db91af5507f5eb1f784c17626cefb7e58f3be162643c2b5abb31010e8c3431b79dee6fd830707ffc1b3fd070f15770d7ec088f01fb0eb3f6042fe03e6f21f4a52e03f945cc07f28c9c27f28a9c17f28a9f90f252998e0456812770cdcbf8cd63f286580af312952ddfed346c78b456a6229373860d5fbbe25454a774dbac101f57dd32c2badee3ad28c6bb746c9aa429531e35fa14b729856e98ebe27233d0f38708fc3bd1708ee9e917b344cdcfd0cf70854c1e3ee6490b6d6ecb63a24318803c2384eacbb1e45f984a0bed001474e5fd8e9db58be17dc6dd75e3b2505064e29b67d410131ee1218430c77a698cce96985a5a82725b2ee8b674d509c42a413625252484de151544c0a1494702ad634c309bd95451cee3e81307a7ed728b8c8828bbb87611650ee1a976b7913efd3e83df38977748936d2d526184c4689a8ae965988e0fe17dbb098c361e3dfb4bdb646e90e4b65c2028c27764a716afc5defef2a5e6c4437be3a14e7232cb2c06cd76a6d4b982845c5162c8e6c960603d3054c162c78b83b0f9836dc292c85268af7f83a833c6629ea2e0633030346c7dd41aa6dbaa42ff2efad37f1da26302c77bf620d0d5c01066ffaa2b0178501e1ee5764b9e2e614866f39faba02c9f58f749ff88a0caef0ea2d475f14766d78f36fb38e35dc893a6f59e1fa26fede6d9babd9536211a49db76d6d3d3d306dee7da3bb66932e193fbfcd66f3a6f7cfa7b2fa7eed475aab08e3e33d6b9f095005972ab63b856dddbf287d51d8eea4c1607adfbfd82cb191ed5a0d456770eb9ee7f753d96d4b9b4dddc3607fed4dac65f8bbbecd9b0a379cc27edb1a2abae01b0644418590bb3eafbc266a1aa27b57baa335008d0640a00154f094a22ebfed5a5bddba01413885e1da5d92a23378d3f5764f18ac013e8508dc7d36db6f24c49d8807a9796b0ad9e342b28af83480bb13ee3bb8bb047c90020823e00ff8c343569c2dd5fd0880e13efe3e4b1eb3fe0dc6d3a37b9c05250a58140288e20577a7b011effcb97b9c0e0c1632c0044e77a538aafbbbdded60c0e83d3db4533300c8b954e27deb0d2a0c1720820508b1002d0b204b51c7d3538aba1eb3e74bbba97efc05302de0c8dd297df9f7a3802874067f579ba5aade3be5fece18e1c2a5f27a312162427774a3d5d9b5678245e5a75d7b264a29bd545aa0bad1866f1b3fc489356f7a13772d696c686e689480c164889a10012644f43e19eeda93e148e21915acce6099e1ac236a3cd87aa44c988859d3822060c8dd7fc497c2ee53d83de287f2d4c8d79409130abe6dfc2e3cb87b166f4131bb2ac8968c625031233a4475a38dc15b4f40b1cbfb7e8f59cb9effbc693d9ac4a2b8ef538dfb3e55b8ef53d1ced02c459dd6a11e2d8e028385214ea84ff22c4f312482c1c22f6baee5c7e15cb4db96b41bca51294efdf9670a66294e69fd524fbe949136cbba89e86b28e32492ac46643582c17060b0108ba1fe31bc3f8ef6165ab386a686c16ae46b188c48b424ed8432cd1896c16022505d69cd676ca8e6b312dd7455ba528ed7d69b5179de301c8013707b51a37ae1e2c57430e6c5c74db3cb1c3477d982ee2a49e98d75b9d96c4de35b97200ede335b0184316dd7c60470d3dbd2ec0d67530042eed84900eee0f823167d782b890d24b105bc67a6d8e6b4b5e54a521a266994b466794b220a1c2976fafcb7bb04258970bf0ee363b8c57a1f092ee163b119dff65991b081556663ae39f30cc7fb3ac4234efc9b88cbb5ccbf6b2e34cb7c2b1a5f43b9edd1e6c65b69e1f8fa241aebdf62f80428301859436c0b4722a5b1fe2dacb99a476839027470866fb919ddd15eff353a33ed9ed5307dcdfe6be18cc6b4aea491d69514ff6b2159ff6be17df2bf16beaeb4ff5aa84f2b6335ed26fa4f5afaaf85b9bfba5a1a51151a01b55f1b411dac320bf7eb22e070f7712c22ccbe367caafae4c6a40e8bb8e91a16211416e1c31dd3c270bc98a6ed48e2d186445081ad5022b63811d1c4658e5d2d9fa8de3dce8e5cbedc4a77b418971b18e392fbcb25c8c54543a8e1ae87a062081b6c94217caa4c08322489c59d798a22a64fda5b8ee63069c4449510500e0aa1e360106a5c4ca33bf1da5cee9dbe043123eb79e5c7207c6e0dc2495acd7fc993ea2e011126f70171b04ce046175709b680238d9bae4a44444430d8dbbc4353db7459c37d5f03059d4e24283dca13a0a093e8476c4434247e145cbb4b4661f82316737f433d2e7d7ea5fcb7fd849d48f24caabb8e22b0c03d0c75b5dc55f1c730040a3a91fc9c24c6b45083208c0face0031670438d0f4c39f11da009971b413c70fb9870afbbe29ebc0325f81bb37156848836809865beb17173630d1110600039aca1658d57541c1c78c2cbd7450049b87f481cf1443bb6d1665a4c6748fc28a178a50caf949988bec625dbb5d8b64f1c9a65be8566199ae51902059d365daaa180a7128b1e0a3863272f0d39dcc7fa9f2b0d2adc69d2d07994fba46103eed748ee9f11ee5f11eecef28f087777171a55dccfd8e28c1e1a70a2011132f025032406ba7c42b87f410001c2fdfbc1073239b8c8e0dc45e6dd4566e6ee34b1fa4a35572877e7c21dc92cb3d2925966254c4a9175ac1fe5fe6dd9e57d91c6fa514d1be328a9f08da24650800bcca84093017ca00d3098c0c0d0140c6860b09102c725c5183dd02bb07c378e293ca0610d2a46f04103131142b6f4000a028928a2e03141102182885181268e5e204316002668d0b82c40c5dd25e0aa401811c0c14586072e0a387189c9c16b8a18a38c7f01b8c1fdb3a106291911119111209f9f21a020285b5f6c445f42a71124270d5ac219c6d767889d3e19be18aadc9d4836247e14d704bc78ab0d9db7dab079ab8d29de6a83c85b6d88e0ad3640f0561b38de6223053c685c0ad0c05d0aa871d79738dcf5650d777d8980bbbe60e1ae2f5ddcf58506777dc15f66eefad2e4ae2f4adcf585c75d5f3e70d7171a7725e0042d6ff5e8400320ad2e7c70571732b8ab8bed2d3450e02d34d0f0161a61780b0d2fbc85c614de42030a6fa1b184b7d038c2dd737eb47244e0c05d22e4b88bc81cee220202771149c35d442ee02e2263b88b0818771159c25d44827017112dee22a2dd4564741711990f9b1f3ebc24e12e2f3eb8cbcb0ceef2a2739797ea2e2f327779297297971e7779e1c05d5e78f8d0e007064940b92b0914dc95041377254182bb92e0804683327c78ab0ccf5b63e2f0d6980d786b0c196f8d11c35b63acf0d69825bc3586086f8dc9c15b6376de1a73bd35a6e6ad312a786bcc93b7c624796b4c136f8d31e2ad3125786b0c116f8d01e2ad3135de22e30e6f9151026f91c186b7c820e32d32c4788b0c08fcc0b1e0021b9c1a1c771f373d6c7ea0e2ae1f9adcf503cc5d3f7ce0ae1f56eef2410e77e771817320c50a774971c25d5274709714ed2e2954dc25458abba43c71979498bba480ee9232c45d5282b84bcae7ae282570579435dc15858cbba288e1ae280e70579428dc15c500ee8ae2c55d516e7057949cbba260774519dd1545051f1ccce0f0c8e96141109b1c8f0e30a893f1b9e0eedee42724b92bcc94eb8b39dcdfe67dc51fb10833820412a80f652284eed7b51a9e218104122813223ade9f49e2ac2b5850b06a6019b18a583196112f663b63edba220c34ba98a3842ebab80f57172238065c8811834507b8d06274db56cae79a71ed7573737383736bd16e673be64a318d791740600ad4b6e6a3241470c51404780013ae33788373f178e98d391666d4e0801cdc1da0e4eefae6f07db2451deedb8a96347548ae2dc02ce998ad59b6c586229fea994c2c23493f620b0f3e215a88d1628b535892f690d0c2c83d8bed54eb6a7e4f59e8780f0e58b8804589850ca6e90b576509c06208163614c6003066b83b13ee2e840060a608b9d5750515da96eb1547781659b058fdb90456c8e112f2e3fe9382bbffb87b9fe7ee59e1c40a20ee5598e1aec4b6631524858df775153e54c841615967900a11dc3da21d1df74feb72ee1fd5a9f397b5fb97b56b0a21787062cd32a530364592298248b1e4c97129012290424cc65af74e528071299830cfbbed06ee4a8015526491624a8a97bbfb2480948287bb1c4e6657baf3b67769072e3be8a2e8e20e4314b9286ecee4c5e4c504f6e48a02e68e6b770927957ba7dc3b8de38f632cf74e204eac343b7e595aba9b517d331e5f679328586566b3350aee7465a2e0f83b72df173fef68b6d26bb5a5d2fd3e4bfab499d6330aea9dd76eebec7c717e93beaca897c68b739809635989735294b64c58b6b4b1937e92666d9644fa124b8d6f84343e48415da9ad66140c359218db9ca69fdbdbe65caed7d2bf89543f557e29ea82a2062c509441b05917122073461920f8fc861351e8b78d3f66fc2b7489f95be3ac89652e279c30c1dd89d05d69c6e56b5d5fac40f4b1d873d32abb8f05e7a4f1c7fff15a517fd4d21835fecd67d1f8fbda26526afc5d6dded5fc2828a3d4d69869ebf368fc1c1e2f521373b89a98c0521355b8bb26eb792d7875b509bcd8d27363270a0af98bb73d3155add14a6be46b7c44617f2d494bbc372ea220b6349ffb6dbb5aea13efa4f011a5959a27bd6f62184edb9c4fbca3bb52ac291ef3537d0e2b5170fcf0f329bebe7656bf46fff6f456f185f363b1b4845c426d890a798a425a813ac59464340a4916438a3a81221551991494260ade37f12a024268962c747cca848866c9427774707e2c9f6b14d4983a5170bc2f297bceccdaf4fa9ced72fdd1ead98cea97aed5f08f357f594467b313abe002a575d723fa1aa1e86ba8b43729b336bdad32a194d216280c4629beb3d98c22512234e34d65b7587fa475eb7d625ac37adb7b7a49819727dc1d2c5284c2461bbe74ed33cdb5fe91ed5a5a1def9b3a24246d8be2c5f5ee715dca707707778f3381cec6a53777b2dd3fd93daed6dcdf5978f18ce634dde6e39e8afe486b78f7385d246329f6f66f455be28bc527f38f575361a7dd3f198fc6a5187642620ddbd519a4c160a5a87321a1c4dd88468d8fcbb5fc2892b4b977923aa28b0edc750412473041091d919202719582bb214247b41e42858e4849511b2d423b021f87a9c41abee9aab475b6e78f24d6a1e63faea43811935a8fb7c2340994823773a671edee1d1dfa9aac49149c01059db3998c96f5ce3426f1a558dc395ab3b31ddd8f77e945241bca3869f637fdf9770d6b7beb4dd46fbb4ff5240fd5a2ddda8ad68c0d7d0dd71d990ea5b589ce886443e5df760df3e870110579aea5513054ed5afadae696e83de94b8982b39914ddd9ba1f4b1daa29a89b5e36bce93e4db2ee5c7bb4647474452c5bc2323dfe3b7d599dc437a566506043076e55d33f47ecc4e4f201c98e0f457c00e2438d0fde430a7ae0400f6ae0d0723731dd1941a787ee8c40e96b040a33317d8da0435fb44a0f50f4f00385ede8543a7a7839f87d573e737503135baa70d7becb2101ae2d24349909718fc6c50398a75d203594e161067707c31d2cb001217af738fab35fbf66f96df5d6a619ddd13c2cecf8f0183a08f958c086a11c44e042aea53f399c39380e70b88f17d3a8642a974a8d0a8d0a152a43a84c711b2136204d4e0062d0b2fd525e00ffe1863774063349d6d065c31160beebae5fe3f28c18ae1a7cdb180080709c0b003a3560b87be92e1a9870f7292951a0c8a4be284f0887c0970254115aa8d0b24593f535ad51678c3e8e3467fcc69bd9f7a9468ba318cdf042ee85a919cc19c219a4cc80c20c4b6638e23e92584906387cb64f7316e52e1982f0bf388f565f2cbe926490c960c2b86b8d5e7c5f2fc2b4eec39acf5a8a3a5cbb4ba6bde99acf58ac14a74a71caa8c8fd8b7d4e9ab87f44ee1f936f6889fb0753e2fe09ad5e0042040bb8bbf7020d9e05de38e10255e4baa0c3dd3f9a65824bb0c60f2070f7950a669413150052260e776731318026a02eb8224c1277f79af830e569cb0d488071770f871084b09367869c70f78f09a9491b3814c12105eebec28007312418820b1832dc9da68820e6f0ca80dde1c4dd3f364ed043963a86582389bbb38c98d5107193809534dcdd2b8306d405151dae9cc113824de06c86f73e31deb4ca4c4bcda6503b03a576bdcdc007416bc648b21a3181b3ffa4d969eef36be0d7b4aea43563bb4c6edd69cdd88b170467354cff045cbb4b4639d29eb4bfcd68dde2932436a260fe5db3d9aa0cbcd59619d7f29b7a36d3373f553e357d5c2d6fae4f626ce230d5eb76fedbb6b9dff8d257bd5014b46152bfe83e49f2acfdf819ef6ea5176b2be0e7d98cd6bdf474bc76df3aa346d49ab19acf1858f359b1d62ffea54b50310ad25de99bbb7dbe7624056b3e6320b566a5b3a29906afdd74972b15984441fdb656cd18b5664cca1368cd4ac15dab610a5ab3826feeb609b99a6b18142d493b4d3a3e1253cc488aea280adaaecd5bd399506d9f333adaa78dff60b554747cbd7bdcebfcf74fa0e3bf7661c5b25995390c71ff84a4e013531a71779137dfdd5a5fbb8ccb2f31b9a3ed5d6da214f78fba7f2110200937457e20b0be0f582998cde8786dedf3c0fdebc0fde3c0fddbe0d3e0c3c0fd03e2fe5de0fefd70ff5cee5fcbfdf3f1f570ff78b87f39ee1f8efb77e3fed9b87f35ee1f8dfb37e3feb1dcbf95fbf7b97f9efbe7ee5e1977ef0e77cf0e77af0e778f0e776f05ee9e0adcbd3952e03e135170b375f84d57772a97cdc18aee60cd68ac5f771d475ba1fe932eb6a0c6dbb49634cbcfcd66a3599e345a5f588a825893fadc153bd1d96c4697ec984097d012a809f4bf16ea5026d4b43bdc4f2ed931edd611c92a864c28dd4ff6f450fbdafaccb3d98c8234fada31edd6a158ace6d991b992b6bc67e6d9b78a33b296595f6c4dbb69252b9dcd66345b8df110d3ee6ca986d0dfa759627146d6a7597efb5fa331fa54fee51aeb8063ca0881918a139c9e98e8d3938cc288d02aa30fe30b3a7722b4ca94f1c988e44d78a80a32a826240a23a24b51c7c333bac4224420442d0e8220d544e8db6a4f29ea60b08bcb128b6148db00ed023430f445b3a1da28b428523a83958a0a047d138b9a5efb54544a7caa10573f87ab2fa5f200058d5ec69fcaeeeaa46319ec71cd841cb6a9cee0c562e80a5f08c7f15a5c7e7de6374f291d933a2f1458183a3d2d4945214509914e88453dc9a4c210c60219701c4932c6c21716b4b83bf8e2ad3cf95d2c38b170e4ee3a167a58f8e14e892885218530589531aa7051c5892a48be3885f550b1c0dda76899b27346c23d219935e75c2bd4e11466d69ca378afd0868faf2b94fe4fba50485b934cb366997e5c95bdde8dbbb1be544c05262af018ef6b291720ab8899f41bbd867297702266dadfb4b193fe4f92dad80927e21c66dae7d328859dc65a548bf447edb659223d49b34b1b3b1d91b47aa18c9f34de9782528aba28a02b8a8ebb67904584e29292b9a08e9891b27973b74b51178eb74e1175000244fd00d3f485475794912813a23ec8c0dd6bdc15e5c39dea46cbf35fa3af17a52ffaa2e16f7c53f84261f8b60360ddf534022835707e2ce42533add99b868aa233b8c56a9658440206f1883f6291e7c92a6a1e97531d2e27375c4e6efc109ffc8798e43f4424fe43f471bfa15905009cb99ca8b8fbcd5d4d22f8e26ada4d4e9a6c98c48478337171a7b0f0bfc674730ac3b5cb64c49dc29ed4f1640b779a0385e5c7659c0b6b35fc04cadd29ec890714a6516083c2340a3a77170a471416ba96c868d752170adb355c1aaabb8e757fb9c4722531ee14a644ba2bb1dc3f1feeae82bb4ee0e2ee221671e6f9d2ee137cc0dd364b9d7f7f892f2cdc98dca779adb7c318597862e8ffda5359db04c2085244169e042200461819bef0a28b1f8e44c085bbb320e03d2048001cb085a74516eece6324c66d97863f62232ddb49e9fc4eaea22feed78eb5a8c8c7b5feaf75119bc2155322c685c26a196a4b29acf639a4b0ff5a4ce71416f51415854485081112c45db1a018902b16c4c31cbe7a286cfc1e1dbe6c088395a2cee5440eca84091327147002c629cce5a40627599c48710fc97acb322849ae2676347183c29a68e16aa280260668a26587c274284861422eb66f73ae4578dbf1c352d411bd41c4868bc80c0a13dd1d0c16ee5e773d8aed7a943f3f96f0e22bbea9759b09184c644c8edc1d28280a509425539640c1c2b83bcebfc2c54ee29b523a9943f95006dc8079111e1137873d2eeede105fd6abfb5108772f0820b6bbf783bb4340ffd77cb835f754d6e6eef5b08507edeeed409255f47418ddfd017ae7e0f9adc7e1eee1e0ee05c0ddbbc1861a001084064f8622ee5f1924248f76524242058395b2d156f342a9968c89d6c42ff5236ed2ff49fba57e6327283811ef8cc9aaf353d90b05e9e6b3088a949971ce483a33b65f2a2bbd943e49339f5251bbb3c8a6f45bd421656c7ed451ae4f1a914631b3cc4af7cd27a3dc3b9965561a5f36be1492ac4641543058294e01ad0144c6bd1475375d9576c64d506fcbf566f4e2c54e3e404041b91a352ee97ebc55ebcfe3186b793f61dcfd69614861eefdd490cf261df16d7f4977b40170e0b3c0470d26e758733567966ef45573f95d3d6eac725c3d5bfc0577b77161df2f5b7edd05964105fe803e3c3ce3bb402e20ee896e7cc11cdc71d85d200ff7a8f8a854f18706c21ee69c42ce8c880800000000f3100030402c1c90474452d18cbe6d011480016bbc70944e1b8bd32cc9410a294488314604000040404434480318328d1812587d7ed5910f139feaf04c03fd453ed280880f93f30b5645e6f41e5f33eeebf6264831936ff7ce8516281ab4900432f715f67088022fb8dd30bd3a8c11e1fe44ad83c479357b98c7bee4c69ef32544ed8c03bf7c3ca089f8c830f1b23a08c921ae2ce5b41b79e9462ebba8f77a0c3c48718ffc98ee446f1c44e7cc86afa37e7c8198aacdc4611a18360db9a80d47d41017b028a7ed4af6f2eb946b5b17524f96bcc75616fea5ec8875a2b7b8389cec7207945d3a63703314f35e880547134c0502007598381919304b245aaa5c20861e3fa15406cf538b295ea059d88f827be7eaf9fb05d57e45a99728d5de47db8cfa023f865d8b2b3bfbe4934c5057cabb13a4dbe8af7c3009aee06141ece203955fae46369ffddf5a54d21271e16625361b7c61fd8c0ae82899b022accc7dac0e14c2f89c4b8b0338edd8f8a4bbae1afd1de46c09382cf37b31992447d2579a1cd483521386fc1c21fc93357020291194e5a8651fecdd80b251e0b12b057a8b86e0953ec3f8b808dd103bc09fd6621f438838a1088112efa9d31a835f838636f8a885f63063021595b0c7a0d8c83d95e630fb9cea9f262babe8758290c9613d7e1ce3baec8ce8d956f7aeb5c73c30200e0f0611d7319b521b9f51fffab6c73e4e25e23f809d1194528adfe565a61fd0b0f6c1941ad1963bed39a793b8b3e2165ab07623304e763d979f3fdde3a68b18b3215fbdcb47a27b15805668582bb07eb3176fa762866b0c9b2ee3718c30538bce95722d405f786ddee957e168348b6bff1c9c039df7f1a0f27b13d0434ef474ba9cda56ce93209f801c60c0e581629dcc457bcd599bc5b6fc4e70cf26c968cfe56098147ca7d64b86b943333e93955b471e5007c5febe9f4b33d62f6a38ca3f8ccf4dee45e5a9c30ec62cb7b5ec779779685df4359dfb44eafe4c44eba4597572fccee8a89936ff6f13f071863e15e8689b10a4f0a39603355028d80fe81238db0b90dafdcdade8f7a23d0d0935182e39014ccab0fb801f07ca182940166322057321be16b7a2477b68518f98ec6f53dc1e7a07c6bae794a6d5f64d4ce3c47487f5a4c09e1c8a192f35ee2f7db3979d5c09070b34f272d388b7db9d608cf95a7b5efef30238f661f7cc07ea7fb27659265fee6e0f251f7c25fd0023021cd45e727ee0fc570eddfa93134ffed58877463a38bfb789c47cd648b8e5ff8ae6b806165593cac6765738e58dff8a1e1829061eeae81b66c0ef05b929e2f6073848e8b9455a7e4ccde71819826cab9d2df92c861f50121f748693542e146bd25e3f251f30e4a3c1c7fc32cd3839f40d51fa9ad40bed8bafd5f49eb47ffac498f02b1c0327580e173256fee14ebb7753e2ac9f40c1b2939634967251b0c29bf6fd356ce2db6f87f2af75a7b73a5ca4465848aff1046102c42bede43fbd239d8fd3b8f5d525e214a6d2f6c80687359312296d981a4de593eb94e354bfb431cf48cb03d74e73dc7235242c6b102a62bdea0d5cf1da997e250967eda5147b3915a0ce3696ae95c6088d786e0144efe9ea6f802ccc406ab06e01a4f5cad60e08fdcb69c19888fdcac85b0f48d95e020c7da04af68fafe371129d33fc478d1bc05d7825ed88201d49421c0aafc636081bcf52852d00e2d3013ddb6a41bf970a13c0269aa3a09c06ab5cc94688e1786b1a1fb9b7f8c8fdab90a54ba964bb337c94eeac1fa66888569dcbc76304326f7d58a14b1177ccf274e2c61e94e942b3ff4ea20c88e28eb9fde8c89a2666c31edf8b9e948d315f7d3e3bbf86bda06506616ccefcf6e978494d86372455478deb289f20de51bde409f6f96b701961df493273c80147e00175469c3a74bd366f2ac9c602e79b44ef7758e81f9ffcd25ffa0ad85b75db6d8d78ebbde8f7f3a0b546fa957ca4d55965ffcdbb01e0341d24f8b26de73920effd4d157ea67fbc04f7d01ff863693ce0114fc603cfd50f4bf896dbede5bf91ed3a77dc21da92b5339ebc8e3d47acf799042bf9a8fb6558fbe3ff6523a2addf77b077281b1fa9ec1578ff5471246e541bde0d9f7f50a26e125cf92ee0f9f0a7fc601f1bdcf470c2c031db9de4db9b493fc753a87f1e71e38345ec1f62edc1c6a0fd92fbf36c0624f235f83a9ddd032ceeaa0d183de4ad87dc8689fee8f7a969f6a211cd46e02c1eb27f1ebf5d471adae23225700d8f618970a115ed16878757fec360d0a2e470ce90de7fcfda611dcee425d71a8205845f966e5d7708c9c67670a0354b7ff8ec1f01a8acc9c7043cee56daadc339a4f48f00a318c5ef539460af60ec9f17598ad6355cb1e637bbfcb2e354bca0f707339bb0b8b6bf8dae32c1bf153dae5676c223ec664c3651b52160128719cac70bc5e28a8a1fefb0a3605522e8232162ba07dda2217a33752f7f98b3b7dcdab4d8b379e73a685d564ad2a707a3070f5a41cd2772cbbf700b68ea3d4126d79697871886149b12c700194a3166197ccd2902a0922a0a4d3853190cbd5e7daa3d5acba6f0d5c4ad1f5e865bde7fa1c065df0ddb6fbb69c04b91054847d45c55d3d95ea6464d947ebc1f988f07f5855f980dbcd96c580cf7c695fc3078117cd84d58fe5c4a6782da668b9cf94087fc62298c44d74e800282c792a03056b67365a71b6e6469c5c86ea8c9d3974fd8b788dd69290078783867a50e2d3f38b3ff00298163507efef05cb66eb4029f2da86fea69c6d3193514b5a523de9f850675bf69333d0f340ede61b26e8545e249811d044dca67922b2202edf391866f787f79876215a282db848f0a6f966000fe9f59d4062e39239c1f3e7fb0f5c5434eb8f24c2140d17022ebc198f21c0c9e770157d0b5984193b8c7f7755209791fc10aacb83bda2c1a230eae3aeeb53f06406ddd091b406f224602bd8ec02f597b262172e590ff1ff63b7c189ac37ea0c3d02cf4b78c472ad600f031b884b57cdff97922ae6de9fe1c24dcb4c0763197ba4a4fd046d7fb2bd9d2e37744e392e943c93f5d9ccdc7119ea979c38f60fd43b8c4f4e288dcfe753527e07acf3114a98573736cf5f92be14c2a496e4dd7ddd7eda022f79873968b17de2a0c79c902073990d4fd393eed29c4abe1ff05a780c21062b7ea2a1fe627bedaff0895be3f5e9f6f0f8dd7fdbd40f1eadcb1428cc0395d6d66acb929409785df17721d13b2e533084da35e37fea83d7b5c97c1befcdaefd216f7e0ace087ee93d5332b8961fe9073add42c886d2e3ce7efc53291fac3f3ef65acbd785a2b93f70b6c649e259d6c99b5aa5afa80f508300ababad5d7b32dbfc5dc363a4ded7f5cfd43ccb9b1b5d0c02348b232305ee7b7d161905f2c0d829effea21dc8cbd2e920621d775166efeb097c2fcb495374bb08d0733e79b1b585b3c1b81577c5faa6fa15fd73a218637729363caf93e99e341a7f7dfbc4fb0702ebd992f6d072dbc42ca80f8e63438725a028679e9520f5ef53f5a51ae687807c799856858a9be2e16d707c52b794cf9d4265ae417c5999227daddd3abb7259f72120bde23675fd9fb4c5cd1f244f5dbb2ff05e622814ae77c748643d3f9c5b8fc8c5cba2d333fcab4e39059bd902fa55e9f1ecff27e0fa71bb02a466dd66b6493cac9be46b13ed8df509f6177b80a2bd84c4b1bf7b365c28460f69fac236ae9c9cd92be273b001231439810af37d67e1d82ae205ea122740da4ef872743f4eae865a687c8d2b18f6811afb1007a7cacfa89084b7a252dd26b3ca7d83674d6fd37d79a5b63cbbbb3c04f3a4622291287a647e71d313567899376866de42385af8da22954ea177748832a9ca8484673be8d5dcecbe7c10ec8e72cd913ea0cf0bbf382feb2e20d738720473f053210a8d306cfcf19d23cc474d1cf27d9bb2409df5e91fa6c2f417021f48ad5fd64a1308a60152f73b2bcfe0ed9845dd08f9a867de9694d701be3e7aa2e45176ecc644d1f83ad3cc0306d7cc27e768aa2b3ca5a49df7e81a6125c46de727421b8660583ae698083ece0b2a28ab9c07d474b8124fc408bf139fbe57133315ffed141687b7b0be04b165dfc1f5dc800b9f832a8a3900d96ab86223f56dfaa820515491ae85ef8f715944a90d9d5a022ba0a2650634a1420b8648a7dcb0e61c543f35ce63271540e6a89b0bdccfb7a08473403db4f01230eb969f8f2c089082300e771b615808401b11a0d3dd679bc32dc256bf086abcfb8bc6f051450b5206c0d0aa7e32d17fcd5590d7f37f241499c07e01500b8fe4c57c04a3617ef6a18f32a583f667dab116e4ab3d254e56e42fbdbf3a180bc2259efb103e4348fecd0ea7acda7c316258e51f60ec01cb087cf3f0e3ca95dda71551d204dca61eac6b52a105b7811b4976df0e4f6b4c68615516a37131e1ea7019da74cbb36051cbe8c3071f51dbf753858fa37be3ed2321f2438ed55a0a4b971c5d18912f1820f773042780239cea7006f0c53d1f2cc9ee7c4280bb97676461b8b4da4b93966c03afada44b0d7837b5443ad8bff8bb8bce56ecdc4ca033a0f190807803950342c5e78435cbac019354863146bb0fe8fe529f64175f8f888e9611db6e57f945739f971024493996399ec6a6523364a61d671c5dadde37e82cc9d446d77bf040a02c768117f2626a3b98d985506f9dd6eb91b1ff9654c7a19f450597c04a9cb42d9d10dc25382530ced66b268356d80eb279668ae16ec758398c0c6dff40f0c6414467f14f49d707c4140231c0a7cdf3977d9bb98898657117576788078cd8c103ff55027ba57654d3830c4fb3df19bac1c84657091e61344db6348041b8b943e6a52888501df8f40a1ccfc3934e4654f6224e9db06cb382e8000088386e57ad77d53522ef595b8bb7bc9d4d02b144da5a2638104c44021a76f452052e8e49829970578e8ad211291d7b7c2be9c22fd50036c0e5ca27df8fd9a0185b141201584a2f5b713c9abc049a92ed77934a4a04bf89b325c1c6f4c1230d94d18fcfad81e46d1660ef8c6134836182057176695fc03e321183a1066548b5b8403801574dea7e91edcb4954451cff214bb74a4540f5a6e03cd9ec4429de794ab630479380797efa1558e7560cecc6293436914ea96afbf98b111289be50f9cc557fa9ff36b467de91494f2a0b1f5b074e50a274895b0b0b8f53571e180e7de77e7e9e26e1885198fb045e3191e5452647a4b4b46991c590b9bfc807e9a7ddba1040f18bad3ffd383b8f2c109a75102de9fcc4f1e14f2f9f223dcf7e5f7905c7e9f9d18f63558caddc1e277c7e14c0a1794a0ccb65842b057b411e2e4249ab44ebc82ebec08df6900a146e6ba2a3f12640b1f752b3185c19466bd11efe8a98a099d41cd7620e0a83b8e92f47db9a7b228b626e0f4f6dc74b001773da40cff2b0316683d8807553fc0d79a41490ec376bc3473ae75a7a3492cdbc9424c40b6829745b042bcd763d805e070a878bec0c74c28a25adb2f23514d287a9717145bea193916de2e915097d27d7450f60b98e8d2cdd0a129a83b0994826cb60eba72684a88b38b9bccf0e9c18aef84cc748c091f8f1f2cee60322bba8d7e516ac9b42df2b35a282eeec57dfe126fa255c390efb6d88f53135b2e69808843c67eb2fcbc18bc147ad8d00262d6e6d3d1180cc1e963cfe1f12d2f99c30e39c9294823e6d4cf2d7ceb5426a782409e922abe390ac437ad5ce1241c89d40def9c148ffd6ccf291c2fdc8c688d03acebdec8251bcc27d2ecd60325fa5a4588f27df9be329a1bb0e517000281a295e622f00df1d5c9b49c600d7416c075a6c49681529621e06cb3f01f8a5b5254841ef6bb783a8241563bfd5f0f73fe9ddd8943f487f2770f1faa0292240001a96104212a72a6140a31a9e9e9ef8c50618229d7cdcb42ef51733215b11b1531bd8731b3fa71d9d2fc2dc6c521f66696aba54f3ddc8f78b8b95d85284ac9d08f5fbd79654c5867a6ea45fc18bc67ef2e93d2162eb463cc63ae8482cdbaa84c8935c29decdcc23c14b12cab295cf0ac51ae71f6177f130add161d2c41719a53c9605e862c3100caa7b21b29d9339213e4cb1c94c7412ae6d54a189c97398754313757bb3e91765f5dd5e473bb1552aeb3c44a1a434cb2c429761fca9fcb354018958edace1cd742d9bcb773ad652717683c5dc5dc5fbf931820ac39b833eec181ca687552707af069fa0cd944c18bb22b122068a0d8c9d4423653dfe110da88ddd2a27e64c42cae091875d96bd4e000ae415cbb066b30a0b1e830541b6425c18a96d4c8d0b764c5650e94736b6aeaf5156a0bd4322a61adb855a4e24656c55ed671bd2807d006f562fb5fea3159acfb58b034b2381517fcda07bc368e4ea6b08e47d650ea7b96a884aafc276ca0d655ef7dcc9862f00fd2335a4c30db6d2b0dce60848fb56029c899e0c1bbeb4237663b09d840adead76cbb9b04a045b19bbb9ab1c62f7680f6a9e6098ae1171a86a604eeb6888c53bf00f544e8bac7dd05f16b632847b84bdf0bffa619147104c0885ef11460b1bd00b4ffaf89be9f1a47d3070e7a61eec9b515ed3c33912d7790b02ca741fc1b9abad19dc8eb65db52b13733441fbb68036429e9ab35743839f42620a1ea093d603705a4966d8266fadd3001599ce6e43547d2c4737ff1a35d13243b34d265bbcff58d55757e0cc5c9a86585719f5d7446828518fbac1cb5ac8ded926d4287a72a0d2201b10d3d7a8b359c70e3a6003d4f26ca31cf55c8ee949caaa649e9b89d0b0521eb497d23c6ee2b56ef17c9d27d4156f7f1020245f3c33f9dc81532d93863fa8506309431e383b21dd85c127493d9ec14b95b6f9ad2645c5cfdbdc4603e07a0dbb9551d876b7d614d34aa3c0a92a6be59a1a15b52b23d747efef0b3c8c83f51539e3715457b6063cb3bae6815d234b9e99fd463f43c5e00ef479ef24a2c8482476db06ee287e36bf8cbe459aaf27f6d5384a7dec272fba8c48e73edebddc05b2c452ed3ff070b6ea7a2eaa22cf8abb0eaa4f0676eb2b0f9ce567388cbb721fff5ec0920ca6beacf06a5bf8764650ada51e0d76a76c0a1b32c1867338d6409f60b116e2a595dd857e590db1d20d4cfe5ff2652af48df948857745ff1a404bc2e3b692a9999106d4df5279cba05da07fa6a1e6bc75b3135daf6a694709df5242cead25d78365bfa7f5fc93a2e0d7bdcb49e03fcfd7a19d093e80774f3ec639b9fe1bd362932d316bf3ee1105879d5c9780c9eeb2f2352a7521cf271f66b3de6d19666c54f68cadaa080d41a7679d5aa0d86006301ce35290688c89f071e3e66c77a2b0306d5508b58d5f0911c08badfcdd44c9a29a35e4dc96d0b168ff2c8e44ab91604392134e3565c53e14166ef226ef9ae7bd8f6c7fcd3b47d81bce1b35ce39b36ecfcb255124c803e534129ecb0fe520ae71d6ec0f970b9326e7d9269ec9f49b7115926efbccb9353953376b535cffb95b722b907a60122ed47bc2981f4771eddde77060100d674f06cf85316de55237a2876b2ee42ff875e5c4ac438e3333eae98ee428dfad9f9318d1c7c36bbdff00fa2143af1e48f04e9f3c56ba6856b818687df469cbef16f75b1038fc0fa6f3a08502de4fa10012d005b1907126844ce75da93b1ccf7422eca4d507c966aa523465c2cbfae22a0234a5fbe71e6aa8a0dde467f98b5e9047b4ba763461e6aff2b9fd8d0cc7696cde3cb66b8c79b9cc8f1b09cdc0e5ba468ab36b463ff7ac5a1fbb56de20a7289e261526a3da96b43af24aa17d620b28cd8feda4a53a9419099ffd56b5e0271873c544774288de8e5abf39ccb07a4bc577328aeca239d7a567a26f67aae07eab93763cfeb62fb9adaece9e6da4cf6ac12429ef54e830115d72c4ec43c0b9f13349acf1748036047c3ae76708be97a1fc6bfc55bcabbd3acdb6ac0b76e063af5136416517dc4982009963459c2c68d1fd8d471eecfb6993eb7664fadc4146c99acae96ebff0db53d5d9629e4931da40be8b6ebfcbb512142621c8fcd6070d8882c94f382bcf1ff3d1dc45e3af7e50143b9040e3f133a3d0d8c37fe96967270eea6f2e36a9aaf1d72b34e670fde9d1d93792c2c7c174d3e09ecf3a0f7f9a3f285bd5bffc8b8651deaa4f72614faf7420d47b19c5e1413ccab47d46119020cfe20d1a051267de54c5d53aa580c5ce680b54c4b487780fe29aaefb9526a826b16357612678f9de4e180f33e2d9f9d9dddb0351605e231b18f077ed3538004920d1e66095a722dc1b8a71d3de851faf87b0651c6d2348c01494fa45387554d9bfae076ff53018356924fc07fe0f053d83e8e588ad84bbca8d936e7033ddbff1ffb7694b107dc6c1df4ab15069a76a021b26e229f668968131d6c0036fa5f52a6e64d39b4f41d686eef8b0cd13780ec006c2d51b614db2b9a016d00700d1c1a57324236f42316f302dbc7ed6e9ae5bafd7198ebed49ff1a38cbffce0ad0fe9cd8e11a3d5f2eef9feaa826db9d58de1d492df5f2188da4d41fb3325dfd88a6058d5a0b469b7971d9df8ff3bd811d3cd2247073d7524fd3be6d32173cc841d2f61d0c6371c85c6ba66e66ae79ab1bb43d674d758e6a11e91b3925c36e24beda922f3f446f27c8d1721b96e8bb06009db77f7d8a15c5bdd233cb78e1f2cb7ab2c0be2c7026f34ed9c60848c1146e5667251a28ad598724b80a7f33a1b4d2a4c9d79e4a624d08752ac2a0ffadc41f3e79b93383989489730aafa6163b5939cac80a04a582d71df1d79b2d95900ef99ac212b250aab023038b145f3807907581c113cb21f710c19f3e6c631e9079902e1693dece5756a41137781fdb15f25b377e098a24b44f034c3567b43887e2d8f4f3d0f1e568bdc5f136e9af62848cdd0676dc439a35a37eb0a4900b412d54c6b1600db6669ead4d81cd7e8a7d3aabfbd773f12e0098c5b11c63e41ef33c9dcc364a15e71e73dbb59797265d80b005b7885edd5c8dc2b6c6ba7ce60edea56c6fb8688a37b47f03c201a6e8ee789e16e3d9809880622c7e1128de11c8501e0447de7c5336ac3af5e5487821432c74805731fb81dda238f2b5f3cb7b063c275bccfdda8f729c4d04beecf96b872b6808f4e1f536f24dabd720e6a2570845ee947f68d49085b725e75bb3f02ad6b35868fafd3d4ea52cf9a48eb71de06b7359c04ce73a4ccb0b30a2bf047656d6ed9b49fe2a4ae342b97e9bb3ac86bfe8334f7bd87a3e12ea39617703561eb1dc28a5d3c50e978740c515268472bfbd301034d914e3438af0368963e4d95137aa97ac3e7c6d19f71a116c3753304b17b29fa1d02ae553402919f5007c5ccd6b5f22e73bd5fe66815aa3f87e93ceacd4ae838677f1a7ee30c58be1e6161f0a10fa381969ed0eb7a64f87b10ce89f3581f27266c1d15c57fb3964a6ab6f05b279a1bc6127fa1c86a3aa4cc88dbd6d531cfd6a511c31e43c7afd01e2049834733489f7c06934a0d35011ef107a605e98d807745a180e87a801787b9ccb6ec41d4753238bf498f0a16dd3005d10f30f2e4ad46c803989b10e629d4f845a1fe5a53233e0190f151f68c1649c0b8f32cbc8bf765ca8d46620221e111471001d74d280480241b3ed16394da144e6388597528a96945fc014f406385d45b5a168654562eb5446feb29f1bbb75ba05126904d007921e5703bef5c4b756847338371127c61f67b19c4ce5ac549d237b1903c2f2fbe43de22afcd1e6d02e54406d4206b4a8fc7a6889f23e1b79b940a5896d6aaac5692fbab2c93d2b16ed6c41ccd6fb0a84bf08b35e69a40cb4f07b7c12053f1666a04b0cf5ce880096d453705eec62c474063fa96bbe213d9725e19fd9ebd28b60ac19032b6368593d54a6ae40dd68457429fae18c63e405ce29da3860d9afd1dcdfdf64f46587c6b4090c7631b8c8551eb141d5de4a34e910521998e25707db0b54a501e97562fd9d5100bda7f7032496396cc7943b03686a559a7beb67a3b9311c38fd0f3d13f358938c72708d815c6108c4aa32285697ed1fb0efb1d03ae034e7893f574ebfc74055690815894cd6c54adc50f51a1633b74f741697b052eb2f327f3c30f5e3707ce680d74e95f376511005129c6e4155f6c02666e48dc99fe7d62529d7a5ea68ddf8fd13edf89719132d0d8457fdb6936ed1706ba901e4f262c1bb4926b5fa1b7ac911f2055de2d5286ca749cf5b1213626c8986d002d62f3d1463cb9434309e4e32a30482270c7586aa3c3c893355d06478affe84e9a1ac7625125bb8f6737b29786942867be43edd4cd4081d55604d75a7d55b2d33163f0c7d0d521ca2b2cf8b7d51c0ab51dcf1fe54f8fdb6e2a6115e20cef382c7993f939a43c66482388ce23d1cc208fc7fe2360a64b59b67babf359709f04ca8fee18607cacd256e88f9d77afae3d1d5eb6def3dc32bdf99e672707d80530523cac61c969cfabda263a9dfca878a699785e3404acaa49171ccf0013c5f2c55bd73a58b46398052250644b5c8009bfa6bcaba512b89771611f8429f389e3dbf5370430e8b09c88a51443c787413b07b03d24059db1a6db73378350778b44fd6eef9d50dfdf2f8ee825afaec9d2fddf885eff0b0f10f29164993e13f216eee8effd9cd7a17a9d7525744842c895a1faa8a8b5864e8e60cc766e171b4609a5743bbb844f6429969e1b0f5d5e8cb51116bbd097d25996ca4a3441250f2e3184ce79418df4d3aaeb54d1fcebcd0fc1190350069708bb4911967e9d12295d31485c144f2d739e8234ffd9e244878b093fa87ebe6718fc9ce94d7bdf9e517f3cf43f719a32f56502cc9cd13d5b0978279888fb9bfc3ad79a2ee7d497464a6489f07507b7c4fed4ed17e57fb5327b06ca9a9faa3713fbdb43b48bed16edea940b345cffc849bb6bccd789121f15ae479b27f0b7e059edc35e336f5b375dfa5b2526dec144839f24553eee2ee5f92c07f435a6100371cf50141ab7039c5fdade7bb42ef7fa0de47e1013058b9b88a9a742e2cd0d11d41885441beb935e16bb7f84b27d696504d39e2c225178ee5f11700c88774f8d4beddd11b1113e5927f93b0dab21f0ed5b2779d1974a6f440780d6788a9bd8c6cba1ead60277d0c890263351c6212d3fe951f181fddcc36ccfed33802504f61004eff8e73941d459c6ae1604422edd6cb6759953c95d77f412643fd265685bb7b7c4d6d5fae9aca3141d6b185156fc1186ef65c410085b3503ab1203a8714ec277a177fb52ad253f041264358a1503a4bd73bc1707ff12f893bcd6b4954fe49c125cdebd4722d1950fd4b4b45dc4f047fc0feae3588eca4ae91da6e3c857cc77d8dd4488163bb5c923e479d37e30c22082b34dfb15a5d61576fec970b49e42dcf0e425cc33ed0acdb9811010ea612f6b9d479e8c456b8263ee1f03123aeeb793dd17cc14a81ee6479c2ee5c8f24ca81a0c5eda7e57cc6b863d39aa4ad2f28a1e4001d3fdc5d10eabd0323803a8eadb8efb57727ea573a82fd8cfff82c8dc8f2ff4d949775a1e1701a25e480fb0a4e33db5154e5a197d42a4942b745374820939f7a5ecca721897c45e3f73f8e334b5a1b5a3ea388e5ac03e696fc931bbe0863da9a506f9c710609e26a3200d88fee5688630728e873031d7a2654c5790b52bf01b3ccef39d5683b856362f918b8cc4ec73ba17ddd14a45639c060e6a2558153c646c5dcc5c8d62eb4c35173677780f40e64932a4502ac91edd971bd4256a23ab13c95ac314193b179442c99323c2296dcf44a4696c9da18267bf5605fc94e716df6317b9e3a64087802d61583984486b50ea5e87e4315edcf2d7e95a6db1ebf1f019c3752a1fde153a8a65e1c2e748af6bf56d190188a20b5d9ab1d86140e1394a562f9c0b5014b7041110491dbe0ac594330d763e0ec10a4050d853e0ec6483fa158d24c1074e691d40dce1ad50be9393212c81c908d11630217be8ca1f36d5642d5962a79d6478ce344c42c1f4321837718148705a5c8cc328b081214fd6b77618f10001356ae33fc2f467fcfc1825f9c5023a070b571550d53baef7da9e9feb935d4c06c03819e18fdeab24e82e0d462a34050ffb98808020f5b0351f23ccd3962deb6cb0c7794584a008f28bbc09e8440abe81fc54c6dc1cbac67e51921c55bf5e55a92bf372272c4f34754e9c2eeb3d5714534bc27a466b886bff8d802256a03211498cd6f5807cace33403fa6546b336dfa4197c1110100f2d68f0b4f8196596dfee821ec17c16515ced37b8c0299931c60ef5f27813038769786a794c8db0d2970a9e293d99a6865c534bb18bc360701accfb0c83fd7099ed1f3c74a6eb8c8624c14a629aa2b6926f832170b078a5304debe84edb75a744aa2903579bcb549f6cae842f7d828620b7c8a97caa8a4b3e28d5dc3abde384b993eaff15fbf4e8043f03a9096948786e6c7afe65c24ef4538ace74763cc7c2c0826cfdd9577e7f9042e9891492ecfa972c75081c4fca4708dcdd7e0f1c364f70e5c8f5bbc37b509257c127749f1540aafad67630c8daf27e30eded8fb9e5390e7c79cc4b1d48d8e973ad0574a3749c233a9dfdf992be63b45bfe134768a9f22ddea046f9b07f4628e6c33119dd093f02a39f5ee0e8bd85945ecaf037f9139ae1636722c04a91be43be2933707a3a2c6f006347706532a21cb290f4ebc5bcbe296a9d9cd9df061b8abc911c4a4e822e1f8eb4108189dabf47cdc88af3bcf81e0c848675df06ffcbbadd26b30dbf9cab80fd6dff83d0722b112dc16bcc420cf89a893b3f4f7521a18ec172c0e8a8c748574871b2830f8d24fda77552cb9f88e8ba9660ec266f1d8549081236e0f5edbba6a867c31dc051474625a01d6e5f407742f87255ce3ea78c2a8512bace0b1f4238eb8f38eadd53e712fdffd706d71129fcf15d6ea0a383fe662e4e3b3359f1ca3aafdb4902da0d9331f0df7e797b3ba56b6bc8be41c865bbf9c155a3885b03e9709757ee058a339eb48ba9a888a67372aec0e0b03c14fbd0122c6611af291b1a3235db641d1b20bba5f5c32ba5eb23bba196e61971c341b20a85850e62676062350ff83203235f4ffbb8ccf52dccccb593f7cb74d64bf91eb04dabb73dda5ec7dba5d316ca0f8498bb2ae3269f7b8154f5889116bfdeabbb1607fb3f70f8a422b3669db3d2505c934c3a65ebd002bdc61301843435f16fed5bc54ef3d191df487b500c124074b1d344da6fe3077cbc03c860f7ff6f42008dcfa9f44bff73d01ccdcd4d0a6d83a9883e0aaab6477bf5ad8d2710a6e436ac77e070ba9b61dca6b45e1afa591ad0f136b1a07416e79b59edb33d6300236a6d5cd9ba9668981e0610cc7969286d9f9eb2c87eac183a6e1bf6e33905f78c82a716e67b1d0ec240942432c18282f86fc75675f96d707c117e6f62354e7ef1c7aa53eba1bee0131101b3d7a40e6ae18a32bf09904c55a3e51b34b2a9a5024df658a3fdbd7407067ccad0e83ef69070995b4218585370eb2ce8200af0604d7faef0ef5e665242361dcc082014bc5482cd98bd1aa48e2eccb12701474dc98bdf50926f7d01b0b7907226038b340fc0a969ab1e593d0f8461ab8417a38130ebc319c1805fac6fcb3343506c0207f0a395906eb75271efba53d762490462f5b8c73727e0148ab691aefe5321c1c182ed4148ee95a6a6dc2055d7878258422775f6825658093d6151707bebea2df310f829dfc881d07e785336a0dad8528aecd862eb3e7c4ae12b482ebd0c6424a0d5e929ff172ec90de217c54ef61f5a01dc0c91f0a0faeea1bb87754fe5b9025dfca0b0e9a93d6d48074778be120fd7b8da68311747ceb11c9406f1fc31154a0d246a1cabdfc6ae12d39b1783246c6c914994172660768623f46ea4d65ce0a0bc3a79d974e50d25b945bc9baec61148736ea31e3354fdca815c61800c7834108d3e6cc2bc12bb2808c69366b609374765fe30c75f40abf08f5188d28e3e9e90a515b3ec843af8264ee570a2fd69171eb29c2052525987a9fc338272b749729e42b147e6ef76b7a8cc7975a52264053f6b3ee3ea0b295d269f1ace681f6144f8578407112b9aac62dd76224886cc4e97408c4fdf4c54ad4c998861186d2b3aa110da91f3662354b19a024363316018b58727f02d0a10756719c32446d83d22cb72cf67eb3d412fbb49a30a9755057017a7bcf60d55c60b5493ed5fd0af205fe74b5bf9971b9bd9e0fec257a8378771bc1f5b162acff224f9a3c3e4ec2df767d87d70a45e61b023cd1358b6baf7611a2f5cc662c112f627ae454ea6093a30650a73ced5e0efbce14c6a5c795d6dfa3945c7a60b584c10732899a4510ec103eb4529c22cfc07999323fea7baf4d4c9da6a598c9ef38e8ea5879c6dcc3f9c6af7955c820ba5aec2d12fb8360077a946752ca9da42986d1a6d62f62d1b2b89f479e829d8b99952d18110c532b6b3cf2274a11ac4a46286f01690f9852f0689eb3200d97700e83ed19ebeadfb66cb95c3392547f8097cfbae2cd33b8d028e25e517eaf59d77b11731d6b2edd738f8cf17db86da169cc59d101c138722984c506beb10cdaa957b49c8d319c77de7c4db9c7f919f1ca32f0a39e477cfd471115bd0362ce518fe5da9d9c5506969717b1c1901282721cbd39c16f58855e06a3b27a0ed96ada7f43b96e44828f19dcba012f50293d3d6bc1e0d94102b4360a32a0480c120079cea417236ec960c92c0b5891b1c0c214e892ecb21d70964e3643f718c67079aa3723a5551f96d4181a15607d14dd1123a6ccd07253f422c8cd7e65d5cd822dbb45d759f4414d1a4c31330ccc78c83a8cf9580627c5daebe2291461aaf7b4da15dc17bec28f7ca8db09b1965aa79e45a82273e4a4ae91c1ae01c351a6df030f664daad1c2695abc20648eafca2ef7c1dda744a07b938afaae9e75aa82fd2fc21c01adfa0a3731c805b598b73698ba60dce8ca14fd0546ad3914ad594590b58ddb04b858dbb8110e52a03312254d74aa3bc54a67dce2a7c86a46c30e1626feb383ce2f4059b302fdc23e0f39679e15f60136c6f5ad3640e03dd720ec365df9df3c5eefe776a15facfe7aa9ec7e0ba8da26d5fc2a98edc549b5a08814074b559c015ebf9eac8db8e8e32bccc1c47e3a4fea2b0d543cf89e07df70c33c100b399cf948733620f8f37f6e0d476f60b636c0f340beec8a4172a2b683b89422a3a95c04e0b2b7a67754bc37c6587a970e9078a38345b54b78eadf29976441ba7e037bedab102b8186a86478ec3638d73bd2415c7d32832276d500fa6fbe31a32fa77d3df7e376588969e832edbfac18c41467dacf03e5b7494eaebb587f59ea0f750b26f2e139aabaec8cc061aeaf43f28831feb075d6e4b90e2e2531c1e5106679fe7d87c88f11cdb08adc81d0f83c3222f800995e2890ecdfeb71ebf5589847ea6044b3cc11a84f0474dc9e4879f997d4b540b0079099f752f008956ac1f33fd3eb066de54aea5a0aa84deadf7ff809b4094aa67538a3ee04017449211a6520d349774a1aa2a6ce43480b2ccf1b5193bfca6db3aa3051818f801f54730ae1e3fc499842643f02744dbbeae3a68b2b90e57e82438832286a1294278270f373d49dc36d38609a90161fe0afd865b96f94507160fbdee88ed911ac1032542434c782b3197c80fae3551dd49f012b51b4218348895627aa767aa28fb14ff1e7f6078a9f74cc77337cb1cc7b02b5434caf2a7d42ca8bc9e9e162b37610fec1214babbf255319ef21d3ded6ec431b735687d28885aecfe8ab3707fdff90383be78765d23fea5e3e198f7b6ec2e201d27904e811824710b5b8fb543c3d95dadb75f67383736bbabc533bf10c4ec0ef841baee2fa031eb9ee0ba44e00168cf9c2bab26e0d5f044eb895a7b86057ece3c6ec87160ce9de83e19b0dd6028139e71269ef4bc47ce5c8d954a58e52af03c9444d6a2d630dddcbc8b89ef52c129491aff6d8bfe0b60cfb1c1bd7f1413fcc60e32f9d35e9fa02887d211a68bf69e87419188a79732606e9aedfb4f86b8cb629a05b2256274de2a00447b75cee916c999044c751c4e7ffb32e644cab21d6dd7063cf1cff257128c2b094b6ddd08e4ed6184790d5e009ddfd1d715fcef2c3f06bd13fbc3cd49be21b067673dce397683ed5b3575a3e5eed21e6859f8a1f81102898e57d72815e10fb3ddac771cf67b8c2ab5c9b575487dd742cfa1ba3613e1db97efbad6321a724701f54bf239e3a1dd3b3903e7300f85e783f43035e07d9e2631a5b7d43a1e0297b44c758414c5ec7a4b23c2930c661fbadc9bc9d40bf56df9a6666fd46053f1107cf95a113c0f05a6c6e65f5a72e02cb55f13fff0a8febb6531384c9612af4c701df67727f30fa0543334c031335a179b0db5e19dcad9ca86a490939b99b65905da6540809667e23162127cfbfaf867edec71a71c503adc6e0fce2e81bcdf6ff18179151621e8190313b9c82f22f7b49220195099d30092795c1548bdbf8e6e1d15a77495d6dec05eead2fe58e3f3b16de1c16fc2be42b53ec28b7b77a5ac323a0fccdda3be499a680067970073563f1a28760f65f478266ef40c821943739f5981d700dd1a7499cfdeaf362a0e9a5e91da92ad5d488f83c10e19a6b5713ee5df641cb7f0d8f3881ccc4bfc2c7fe8df38048e83f88c1b44c3308df6d1b81e0e3d78a84338aad012b67c34cbc132fdbe2ae0caeda43ff37e79a7b6c89047bc7cec1c7948e96d26e72a2fe9c8c05c6ea066a935b1e7d076256cfe2baf7b05fc518539ef3bb3f66da0ea7e2b3b6233c037e604107ec3a8d3c008848454ddc664d88dc739783f1bcb6e7e0d30690f43901cf91d9f831aa380097bd400181957f129f336db2d4c7f954214c8cb29afc2eb189e66bf4b5fff03efb080b3c57c17a1fc053a401a584b4add0e33b34c8056591cac4fbfb107187a3b0ed1b8c4f029b594e53ca25c9b82184b1108ea7de521cd51db82fa635336b06412f8758ae6cfa08ad8501c7e516750d30bc3e81db0713159d5c9f931aa06bca99378097a6a481bd03f3e27f93c66014f030df87b390c05754484c0fe9b446ab7e4461e217814199ad666e00d2d631978135584f1634c97b3682979f7d2f9cc785b6e3e0b4ad51c714d10b584b4bfeb7a2423fb8eb839b76001f701107e3dfdfde1acf95a47106e427a6d1ada9e07520ced70f4fdc08f7e292b1bc394e8a3dbf4ccfc6a223c8cf7be4f36abece5d35b8bb033a323c5b0e83d0f7a2bc5ee173c0b4f4f331d4b519157d55359b19a0711ecacebaf84ef05965ed8dc02b09ba67fdbf249908f6b9401dcce05e063458cc9ac44364712323fad3de6b41d5ab31ccd12b025e14280b3ce0741b507764a0cdad3a16296e263b5e448cb63be8bca15cabaa97417a114bd3186190a3c3266ad897b70a6dbd806795b0fcc8d719ef2dec642eb511dc34f8473b2ee0de7da20293ad47c24085422470e5db665084fc461699c8a4a6f85118f300430069cbc37c1731ec217e9d98a96fb2297572638d772bc0431c2cb8e3ced4b8590dc62c61e3ddd968b920d261d7878d169b5669b5c565012c5e1686b6449f5ca0d8f37213a231e84d8a9f37609fcbd4c5a2118cdaa3d90d4873447adef531325f87d02954cd0ae8fa30d37385729d78cb6c82954fb0aa2b02224e76f330d881e79572a11615b9cd6f03a7922246ec72fab04f6e3816d93c2a9977d439ab03a2b6b13e18a888a361bed122f28caf4aeee5ef9da6c2a81528c320f6409e6d2f26de88f4e74151e57d6c3f889813d411b6ba02c278814be1c096ae4f2e1f852d2e41c95f86303a225488b6899c79221550084c0ecdce55572ecaeec0b2f7991c29b5020648b09a005673c9bbb572fd63a16ef9bf07669dabbb509d061b3da2fcc3575e89d68a606167fd62847e7a3a23e0f41d3583def749a78b21b4404226aef2b05b287ef126e2daa4e5f21e7be88e9b83ecc34d4e84e9f81c556bcf97898ee96eaed577103b0051bab1d0e277d6c0d487ddb6b88395d266cf4154e591e07bb3f67b50f18de5464b2f0e257b7ae41b3bcc2a5b6f86c41728191714bd48d6a57dc52fb99d4c9a739297164091baec1365c992619c3957e5c3301a15558960fa3ea01fe523e504150ebe24ec6e38cd4fde5f46e3374bdf21c9841daf71eb93dbccaee1fdceb1651f04f2439f96a43ec3e5720802a975f4bee9a9d6b25cf29d7ad54dd7b75c37742553ab93ac0128ad7ef7c0c651e7deff8b24f106b32bb99e6ca6252f9e4dc1dd37a698a3615e46f1c01db9573549cb8fa5c574d7f1b49f9c275f102920a55032ff04a22fb9df5f0559c9f4cca6adecc3f299fd815838890d703b37f28a107e3239b5537655049566600e4e36d5ad14bee46d978ae22d6b8f6e6ddc4552743e48271042c1e0e642ce649a1827b20c806679a2b9ab0f543e1bd6a3f59dcdf6bf3f7f51e6cc54a11832387f8d18957cc7434c06d3b24ff7b90ac2f70023de0207aa45c083fd0b53f72258cd25fc0d5a91e998f58dd715f95930d1206b7f2c166b7ec9dd5ef07b1085099474dd25e9ea09b954e327efe4d57cd4870addf14c51a52ddf78152e2bcd4d43f6ebae0574282ef72fecfe0a19bbb48627c73780dfd16deb1989721d80aa9bfa92d645be0861fe0515c9c4005d54dcc125af115bebc0f0b4170b7fc5295ca3805e7b9f68e50fb4cc14145c60103fb697400cfb8d6ec80318d027c632adffd850c89937476ee05c0943e6f9c89597e97db6251d4a97a97c4ba11996517edbf2879a9363d05b1b3437fd507ca9a990a1631b5c539d847abf157f07c44300c434193c8fb9d0429a53cdc2df1e9093552ca8e0524686731f775b0edcb37708a76320b5a344f4e13083c7c755a2e169ac5af52180ddcadc8cff5541e5a7da370b0934b0c54de4d41c84e94bfe59f0181bd3cc4654c9db564bc5fe5492094d86006127e3f71312ffa4f80d2e16b37dd3ce52c98f35b60f57662302100fd76b70749883b39030322668655654473ca00f1a5801bf09d0bf26c91712dc5497389a17b2ccd36238d886f0266998856a7a26187557e9fcb9d035f1f2430c02a4d4e0bdab19a94e56d3dbc827cc9d585e5397785f0382bcc2031d9842aee1912f121f4b2058d5153420bfd5769da38b5a7b11857cb73991cfcbc87ec13c5022a773e584ddd1a526dda60e68c3c5a99170503f80554b2f15e7729b2bf5f12dc634ba5905470a283d12cf1a5bd3c1c724caa7ffcb4c83a8ca627d72c76659f07c1e9a468228b5c3996610a6b423d2d7285fc4d6c4586ba2602c5a5357f2e967532d4f46d7862978989b069fe207a59da105f419413237536c3e47d5c31be390465e97ebab39f35f3ce4cf202dc412f9f9b3b2180e597418b36f318a892552a945a31d1a882e7c0cd206003862dc693dd7b37c8cd89955cffcf511262293cfbdae0c5580fd0266c196c0960d2835c23d125a15c89472a56fcc102609c569a8a39ca464b4e58aea630cc10d9b935a6140d5366654f2c01489029e82ecec49b8d0dca0d1ce187b69ac8e119a999fab7cc3cecbceb39ddb50847cfb476763ca41bc090c4d82a54781692c23e13d29e29ce45c67685accd6c360faf6b07257839d6e95bbca006d81bcbf3f9b1a0644a396098401195f0ab3e382c44a7e968f41e447965b3bf3c4268527540b213812d60501c10a0669ba928593d8924c0ba4b00396f56dce06ed9919bf1881e647c80296740e600671d41ee8de3130f61474eaee1ecde15ba9933384d84a3a18da15df533d37241e95838e85046d74d9355d31aa05c51a25ed0540fb9d0eb24db8175f9c34ff5f258d4c084e73b4f3d44aaae180cfa115b06cbd035f929bb680a727b3620284299fcb011a33b95756e9fdc47e6dd711fe516372dc81f9b4525d2ceec644810821d4904f4828090146e1aa54add8c13392db3ef98f9f707f9c02e53fcadcb4128f5500c9bd6a0e584f4c366670e65df0696bb507c5b88f32e5e6225aaff75d1b7963050df6a74bdba4c3ee482802fa86d31104c858147ee0cfe04c7279157ea7ee38d58afb0fffcc6757abe528d201800e3e853f8b14cfb8bf9904a107ad92b2784e72c7de181dcf28f42cd0c2fa3d1c6f5952dbe00f7531965391ad450cdfb5c7846768fa4943dcc9e8d373b3cc2319de128b6a0c8e0c6b8e68eae2b09ab94c6e058fcb89af8fcb6aea03b8c5f011513646cf3f3f9144307466042da53a87436dd1bfd84a5561f39a14cc48cbd8dcdf138344511922b7221a1da61a75c214b087f39e9b84abe0b029d831d7ad6e3569ab2634d2b9c7148c07894b5f2b30e0494b5f4f4fd54374fc0bf2a9d79171a456781ea17e66d8924667cf2c458037e477ea8b1626373660032cfb41437dd3c16426642ab106f50e0653623cac915010f52390a25ed591ac9a0955fcc19d1e525a4053e0120b953a421c5f63b133205eb220312001350c64d0bcb89e2f3145678f39a69072b56b6818c20d8199a701c0237537dfc7cf3e07e889aa2298b18813e7a7b46ebde8b2180c6bc898b77925952e47dfac5ab7f46714f7ee38bec5225b457f8eb86589a32d65f1bcb2ecd665e1d52d04f586a2d72864df38b78646716283870ee81c92810d08c75ddf906862c08e0473b2a5f5d728e51d518d4255ab49ff44494489ad2c042516d6fe1622ab78619cab2f03ae98340d8af81328094160b4c6685bc022fd96b2f128b9791c89088fa3fb0a14bd988c428fc05c8b885510e326685d46ed7176fa4311b19f2e6671d310e10d88bc6900ed5cbe594362cbcba98a8578d0c16fed30c466693428a0e97760ccaab71b0189178390eb9c5a556d1bf46b4eb44fe7dc8d8c112240bf827413d853e7028ed3a3b2d6df0abeb7a3e9c2bf2dd091bd4dc8852c27d5065c4bc9d5c3f554505bfad41b87c699c30f3e47583525fc47572eb3a02123f61350d1a4e854906dc7ad4f3e479d1f94bd6045b7f001e161d7cec5fbbf84ebcc54f7c34f154faab8b22f3644c84d3d70cb8469a713182e60a76d09c45615fd143f1ba57ea613f0d21e0092d4816a57e79951dd443ea8e4c3b1c9769ff08da372e75d11697c055728d82d5c5b143afcbbf9687a4f1dfbff33a0c6411607ac71c3949311fee8f4577dcf1e3bb41aef5a5e67e44b9af1a0751bcef68d4b32f765e8d1c3ad26b103eb2b94c508b042c5b02c18c4dc4522868dc6a799c53c6b51abddf818ba0a89a376c4158fe6844160f82996304ae58d1168f6522f45d52438f23d7a99a73b45a5b0b3ff7dc914567d1f664c32a83f488e820346f849cff560adef1f273f283ccce726ee5dda8faa45a757973c7ec472751853dc562f7ffcb332e8659482da27f1c643522a4643506e9d9ebb8f7c0956e364a4e1345993245e43125f0040f5b3d53e67d7e83b87dbf1399ed428d04a105419f00e88411535d1cb312e473c856dbc502bd381ae4f91bb14231f677bb909155a2bab73c5080ec822309063dcf11b70349bb935ac7b372037e4552476b3b41d13b2d216520cd7a4f00eadca5e32915d4083cef205ddbe10e5c1f87348667e36a6f7b3c16d154244dbd74a8ea3626c025d24285b1fe82780988c23b3e9ade162498653d938f423c004a24b457dc5871aa291648f91097b4b86ffe5b98eeaf0bce2cc4845a846a0a741324b22119259a3a937695150b070edd691597c14b234110a7c5b82eecbbfe27dc3f9fbb1866fc133ae382533f3832c35304c1db1e63f5a1e5fbba5823572ff11a84109dcbd5e97e3c104e175ab258ffee170bb0182848036815a5d51a5fbac2c3c6a41ac1ca5a1885c3c60333f5fcf96ae03e9ffd479dd9fcd631c54ceb9f41aea4361f30fee1e76809ee9faf31fcc55f86e59d72040dc42f6566e2f45fe62eba1691ba86722f2a1692660631f33fc658dcead1a90968ea4f5e974a38986f43a060b401f1d7bd33565fc825eda32857295c8876ce5b48525a71159d28b15952bd6cd46927c028499edc0c71c6fd2bf42acae39ff9f88f45390f3b78ccfec3802200fda30d57c4b5a35bbfd3c44bde0ff91a08a7057f6602ede684e9d386526920d5b8285a4d756756266c683ea2b8c1295fc79572cd14555be0e02ca061b83bc1700d413e11b9638b279f43ee5dba4c9dcb7bc80f2a9f8199f68adf54516201db113fc2c82b86285031d6e7aa0e601292211685077fa1e99d90f0d410a6c3f0d21b6dcc503bd485742cdff40fae189e2d8cecf752d55140180e2bee3eeb9d03d97388a61dd20bd6e85887bcd249aef40c16ff57b34bb060e851c5f2cde8ebb476e05baae1988a7e8193fa667d0425cd904ba3cd0f68efda7af0e56d6d0de85eef4c1eb61b16cad24e0f51d1e5fc7b9ed94e14730df039fe5d2d5f2c9bb9cd226dfa2d55a449a18f959f1f3c5712ea02716bb43b47546cb56f43a93229436bcdf7416d5b33bc457c2b380ab07d3895265156f5e609e0d72e4b745bb8877d552096a36c9edc0b3be568a187ffb83a23354c2b3f065e6e81e5c00cc68c4673de493513fc136fb9dcef48211df39f6c3c16a6f6044f66648ad850f0e0d6f0aa20fb320ceb84eed296e0df36f42ab246fd894a0f717c54c268dd8358c0129746334df233e5802b7dd9f326dd93586c986c92cff2dccd2005cb30d9afd99c22fe7b121f421d172200520aff31b64e94e7af0c9ffb199d74997ffd0b44d65acc49d39852472ec0c99385cc74c1c121c5720956561042ce482573b9e9498c3e7f15920ee8dfc3496460bc5c8631402c093e0db82977eb3c4bfac4940a1fc0c61f222a785d3db02ed5d9866f1488cf04f57d6e9cac5ba0344dfa1b36677ddd30137714c4edfc5a0f55536fd00e763bc2a188533a2d282386836505c618b4dadea28f2cf1fb591e6025c4bf53afc5761bc8fb7863742b91bf13681603278f1fd0f1557d910ff670ccc03c8abef16d505e8ae9ee0a4c690d01824394a66e00395599eec82a79e53e4323a72e762890f022c67802494ba6ed5bd1f0efebb37605fccce9f9e13a13f705856c39af3640f8228fa1e61ec9f2332c1eecf83164d4fe0fe817d359ae7acadf795c0be30924ebff2c37dc1e1a52e566943074531798d41352035892418bd81b48dac687abd9780b2f30b421eecd57ba75d9142c5c95859e731ed0c74a8678fdbab6fb0202f106cab0e85d8bdd16f4013fecf7550c6d5426bd116712be009ee44b815403f559f5721c683b99cadbeffccfbb1032c206681606543e72e704aaf60f6e1ff5eb3113f7366c7f65151e1d555d0261cf7d832e2bcc3a260519a4f12ef46ce36b4aa883094c45b0c38555a0f194ebf8ce4239bbb27c76c1655b4117a80f2c633d48236ee7f76e8c4edbae887dc6e1c34b2c4df45caa1a4e68d25e103dfc6ceeac475bcaeea266d47e13f6a6a6d85fcfa3af9c0d76461ab78b4591b9385a51ef87c86a49a9df1aa17407e2df1b184d23223e9d647fd07509f48d10d175ef2a03d6aa8364eff81d25a1bfa3e1194990ccd4bdb4d9b033d4524a21e5f83e681d67848d258add97e6b9730bd778b2981dfc4ffde80665844c2c46e12a1c3d301808330f34908cacd38a1ae2015bba174b0b5e9c89ae508578cda45ed2710d2f9d3c09687f765621cc4bb1f3255b7f463066aa270b5c23b0e5b40da726763650f4230a5acb696f3d3b787ca41797ac0c3979753ed644243d44ddc82abc311921fc6eea6a6fdf0c25360eb07c8b8d96fdfeaa8c7737ef8fad80f85633d8b9f71aff6b8992f24f9f8ca72ea77a9e1897d5a715f8fdc63808e19717a149fdb19d7281b524c4863a9d1a6f15e6cef0614094590ff17e21d1ebeee1e3c362ddefab7a68934b33f2efc8f33c12938508ebfc0264678dfd5715ab4996e0a5e745075a35a1c49994ddfec15e4b9fe987dcf17c32c4a6ae68105a4cc9d3372db41388c9c5506ca5847cc239f7e88afe9a4e0048e2a30b79d352997fe9e97c70e57c41597141e340183b1ec6fc826f7b364ba6bea2253b10081d54c30803fca6ee031c3890c4abf1418752fdae819fc4b99595a764c4c33f3374a3640571165a9e13120dc1dec0d6f9a2019a438e4629c53b49f72a77c2ec4bb561bf2a13cece5f68cecab0cebcd111ea7866f1b1abf9db8b20273c3ff45a32767d5431a2d45dafb6cc14a882b0e12e4ffa3975c9716638e0b718007d53006611d768a5517116af5f745b0d7de262839a5ec9a073a39cb450e0f53cae67279261179fb388de4a0274e29733de78067cd05beaae7b443200b4bbecafeb23360c49d61ccf46ffef765e58f1a2433b22946287da5187dffc088dc57fe709ce1e1d5a37c2ab925e6f2291a213cfe59ce726cefe2ee963d4f01a1bf8bc3bcac009b8a7cd22f5acaee2a79ee6e4a8cd220e2792acc53045a656abac5f92e15df2ee709372b667dbd08722137f587b9858511b882e387fb510577372edcc6315c4f10ab08ced682eb5ce8fb756e4cb4d7239bf731cbb40e49ff166db1d9550499d01bdc1b88e613123a2234d0614ae3d09bcad3513eb2b10b19bd1edb14595e419427471bf5570647e11122608d748fe0f65ef394331d850f8e2b01444d83c2fcf86b0d98173dc5879864b5755d8fb9d941f0ffa32f11c478e84791ce0d303830f68e65c22d03326762ad3f68f5d12a65c86c6e75737be1f4b25c207d017524e843ed9335b7d4cf4ab29016a5abcbddb8cf61643c44abfe453d5b38c379d02ea16d3ad4de2af64fa9e9809f9ec9d3187e2501663f11a0bf14b05c691f68c5cb848503983989084121addccb11b8da698ae04bc00bfb579117133bbc44b5a011ac3365ebd9e03e3fe3281bf38aac50ff0e1777f37efb3473339d57f73c0d7c2ece95b5212546f19aebedb4488f51e8c4cdde36cc39a5dbb5c7821c5b8e8b801f8bb71232f5c25b3bf47836fdb11f5c61dc050effb54ade56f076b5347b0e0f47abce3894ea19d034fe32c8728f05139dcd9c78fdb1f66b213bc34c369becdf8aadad6320c5b3cb42f729fa86731c911850b3c1e3f988bae585c77ac409f4c4db50aed1a2fe03c0fedf34ffebb973ae0c95da225e6189f580634df0f8ae99af478537db738b5397d284254e3171ae48e01b14123219a2a6f007b3b1ca88a74f524f64088099ecc5f75a637890037b940d5ef8d8e675f4c152ae6d822583d5b610c58364b596ca071d03999fe20343babfed2d6f2427a2fe74a9283b8faa63a7401902f457948ea36e16a59312400424b19304fdf89fbd6799a39e5c313127603ee232624b54a2342a4502824c76e660762e4f9670e0017142ee26ddcf3739c7baa1bf45b72f86fff4f4aee8380ed4c4f40e3c1cdfdd9369002e77638a57cab032503471ef437f59445b98c0aa47627e3db815bea20b7a575ea657e493a79acf12767010f0ea19dd2245f97a4fb90de8664f167f0cddfdff016da7966c7d1cd3abc760b67fb1129ab929d7a10af23f6bfcacbe372231182b8bbe2b27db1316ab86f6e5aaaee4725cd03692bb6cee4c88ed98dd423054a6097f47b6960592511cbaf43cd1ca7d33ca9cff923d125146a11b61cf4e64a6c2457a339a2e301a98cf19de57849213eddbc1fcd2b8ae16ce9ebf613d73fc2e704c4b6dc6dab463fcc595f8cf6b333eb179294580683cb658a6336fca0e3d3a004cc51c9040ad50625a8ee3e8cb8b6c353fb1b5214b3f6b68648a291c065f49363e20557ff838602960dd9014d6ecbee53bb22d5bacbf0daa792867e2c7e10f9e196472240782bd3cfc81e6d2aa744cf4947e3dfd3d63745ae39889140efd06985649ef4eda4070059abffb920a446557c618e9cd540fb356f1cd5da9d6ed274059f2a3931e0e5541a71cc5c8851b3804739fae8693d48351e320bed4e38f1760c1f54a835b7554486f443f474fcbb6bcb23d6ae5ce9bdbeb70e0cf6a70a581bf1ce39db46988df9c4be23d1831cb9ee23c430140d32b1460679094fdaf1e297a03e84ee25a114f5b6a27542155c78229c2540c5f77c6d3e48e79ed2a997ee553152b4d62dc38d74c9a2bba1bd8f4c0a03cc263bf1d3474033eec2f4a7b2c5643530c199c4228a179759f42efa211aa65ecb6ff64cf85ff380c471761e371d04a0bd0c031382201388d3b0b27bb68fcbafe38d631896c9f5e50d88d1f5b809ec141df8c219a9aa14215208725f1ef012d14e7ac7f3968b11b02aa583698f53d8c0b091d7ae3924674008e650d50a8468f2ec3e78206c3ee8904e1c180a8840b89784be3e3193b3366acc1c134cc6bc0fee9a9d24d596a1f4113de6e04a0d29ee0fcadc02f4f4d639ac3bb5a519598c7a7bcb834ef7c9c9fda0ed2f2ebb0711f52271b84e6e5ae8f49fb443a66c2b41d69a096311c623b69896e75169fb91a66ce3e10495e4974566d727fbf152547e9680371cb64d7c2ab8f8ba1a0600540301b310efbf73108edc774edda143579035e29f4b98cbb4a24fdde3b0823f1078632db31e5985a41ba896de3dfc576d04ae9cb25347660bf09fdd3f0b8d444eb7665e9dcb703e0cd0a749380b15872bf61e2aaa91749a0f28226f141b90310abcfe2fd96c1fb8ead7056aafd1d978c3add1750b9bdaef2eb2b0fc894b11d576adaf1dacc0c3514d9dd9d4926ee4dccd75b9ec235ffaa2419d3b8db8928d6c420dfa2138d1b98db499f19e7a5d5e24b6420eafc65ba74dc6eebc2d21e41867c9c61d256af1539cb8619fe74576da5d428b1ed2a2cebe60175a00c9f44da128f01f5b119899fb2143bb9278b6e07a4de1297c18a77920808305365ae324b5cc0ba4b5200d06a0257aef6ce29e6bad82e97558af3d69ffe6f575b34c9dc7742a4e953a6093dc3a76788d3c5d3440eb14bff6595c80ec513cd6256c9b7a7ddc940650439fca782064a737691ef51f72b2631a72773b29fbe9c2ebf52c1b2b8bbef634a50d20722588261644e709036eed163dba9d2ce427a3f68bb20b82efc7bfc7dd19d30216b89693ae85bb435c15a01ca7dd85c7bc68ead41e67337abd92b1aa23e4ac1e2b9bd1109bc7bdb3cc03f3050dc780e3dc7bfd57aa5fb3823019ed2593e8c610314f2ea6dd64cf189782c269e0dfd942a7ace5a9f776c33dd6eb52584cd325c4fb58e5cfc646eb683fada58eee886aab8a702d22873c67830309d6fca7ca2cfc6e057a5ba0ee3e8f6377789ae9c2dfe763abb41750d3012359f5c1d45ca45288f192b8d09c2810dbe9e13e68dce26061c6df012e784374d25eb3733df35d9429e190f3d0f9cb5fbb842ecdad199df87fce50e64302f36fc9eb4e6001a4cf2f2b69893f22fce2855321c2ffe3f44ca81e9a742d37386823884cf82ce83e830916d46552a6287ad4e5c655fb5639453780696d1293be20caa09e0718a80d333218983f7d0066ce2069c51beff485d27d5d2e0582235ce27319560fe2bf4979100d4c46b44b468e8b2ca790e35a2d909eb456311b7572abbf8343b402e4b379b80d7ab9bbb60e9b88bab88625e691c77d085a9084c4f161480da38d469db7feb49ed5cc65898f1d23305ed30a162ea28d0823e7544a843f5322d565f3a89edae3d5159fd31fbbb9e828d46d61b3302f62d31e696dd17991eafd6c649eceeca0fcc282c75f8948f5ec436a42146065359c0250c179857be2275cae697b1486a34028e3e5a082912f187d5331e4224f29dc35ec8cf0c397d72472cc006d00bd160e03c43b52919f57a19b9eab6e1bf17f3fce4ce4c172eb6687f5233a08512aedcfa735cb66967dec0757367dcfc71c1060bade939d24295f8069d5e766d4d64fb3e8f68fa031b56e6c290392ea9424815f3a6fdc26c08392a05ea68f6bf6bab7dc1f9d19070a9807f8ed6349dabcaf791a67f7f9a021f12299178442bb15c3449877a7b1508588f4948622aa0d98d76df81fb9a26d1bfcaca37ed896d409aed197c8f32fa92218523f96dc24bca6419f5598e35308389928f671742d9e67ca71d2e15f4404beb20434862ead87c4166434875d57316730efd63302c0665894d7f49685aaa87ea6457387501c40b856a41247118f3a876470bfac5edc597d19525cbb44aa075deb606601a3637b30d8e0e8543f900c64fe89d30de3c83357d28b5ae8863b0e7bce8c0e31c9e8a061ea4b802ef09a3c6b902a22e9d35d7f348491ab89de2239e1a18073fa681132db2c9b464c126fa9bb7b87c267a930d6a89d2019a0d793898ac20605170a35722c554aae9d69943484a0a7a70f466d45c0ec2c714f2dc922906fd4a68eeb3a4ed1bac8acc6ec2c1cda0b5aaeb0401840d79b26f44fd0768b9a33d43974fad8378645989a4db03607b1b27d47fbd622d4cbb4142bf86a229e38f41be19721cd685c4fc15634113959e00af431e208754d778fb7b5db8f1d1410888daacaa478b0dff29ef610233e74da791f3ebff268fa2767fdbb582c817e79837b70d253e9520a39856faa8fe8fbf7de9ee07fe99ba7e3dcbcb4452f170f041d6b0be9d67c4ebff8b6b261239dff23329d1e09c8352bef25e91f6d4d36ecda8bb0369a3810a0a3d57855a1a33751fdf3f8d5ce1d806fde29f158e7349340dc471e5e5ac6de6550a93b643007b138d778c17c2eea191154f5c6f13b0534b823e921aab9fc902446170585cb5fded702dde50a9149e520bfe34dd2544e21cbba02b92993580e8d3f17774399627198cb9cfd265af605c88993c279eeebd122c00c8d78d488ac9740b522c305fb6f3c53fb30f3ce757b576808ace3126280a59444eaa77ee18cdd62f62d644a579e4e499339743274745f1ce73040ea97bd3b68bdb35283e6b254c18ac27cb3d79507480a99a6d8ea2eb9930623dfb081ba1b9aa02c7b16e9623dac752841154a160d47f19674157251b6a552b71d241134c6aedb260110ab71539652d52046251377a1bbc8048d3b287495702ad7e2483a8cecc7fa6ef881fe60064f3be181b6b948eb59cc38338025e20d0cde8422a3ef89b66880b49a959a615cc302c38df4fa29105dc5eb6d84144d3ce639f436d83b041ffca42c96c31661f3a6bc4068e7a9f219368b66476f4894eca4b69367f523d423598ab088c73dbfb8d99f5bcc1cc02808dd6868f100a7f93be6a38e41da609e86f396051526e4792866d35818379fd52da904bdffbe6c1d2a14163e6f20c055ca7932e15ac356ee9886bb4343af7bdd238498925894f259199a9d85d626d12279698b12bb9845f1cd0ba962a6ca731ced2ff6a924114ba2eb661af588c7ae48c728c3536ea64f30695ad97b5b4fd5333c5854aeab8775da0651fcdb2ea23e72cf53636ea5db154f507b0a44c9ba3051a5175bbe21d142862135358ea3b1f4fa03544e8708bb1158cb91386d4a5621c08e3fe43ff82072461bd0a42ac2f64169d74f4da815d168c51754f224c25157cd4441006384708c0563225f68d54efabd222e0b4a862e2ce068db0c1a5a65c2e1296ac2f4880079c814e555e9a792c5e7a869a7e81027b0c14196123d3df25ffdc26d281061e1e0e097175f9183de51f2a8210f16d74967cfe73001fb81591c3a452db8400b5c2f562e838c820c95b78f440fdc72420568f049881f8eb7670c1e4a57bf8a663f0433dcbd17359589660fb681a5c06bca9d102ef17607a44ce0c7c69632f81b8f47fd03611c61f1c3969cc9d4bc8c7228ee507fc18707c9f559f9804c3a5af6e0b94cc20a09b11496d18a329b17b80d989e8577bda2d78ec011aaab0a487e663e55700728485d0d1cd8e6f0d4315f20b4c538f5b859a34f09ff21647cd4365f406a05fed5ecf5d814a9e7c9c7c66f32f3ad86d2b23b50de8f7a0386e00623f557b40deb76ec0200f5b85d892ac9acec40f2ff2c9d962c77d8657a666165c325635300d51f9a59f826f4877843e7c8b8a33110d3ff6d18d71fd91a9cb59dbffaf7122dac081c9aeaff4bf8cd5f1ce91667d6ff3700f4d2c9c22e8c2c2a64e8919303c4461e1f853c6d3fb80d7fff6ae345f0bcc3f2945187fd404c44bdc381adec03ae621225df03760db5c2e86a7d028370e4fc56cb08145cc688da4aed3da3344dfa05dab5f6d839e188f21a805ff8a6cd95be7f91c3a86517e9482f1aeb4c7d11ca29c61eb6ec0ce6ce3397b2dd7d611dacfe86d9ce33fa8ced947e270d938598ee378e2fc8bdb7bb782d2ca7d36d7b5f7e7ba9aebfe6d90b30be1a4e72448a2d43d3a4b83d0dcc97c114b610d335782926b25b8af39c5fccf09651b908d064a42ccb9d7feb55ad6a55bfdc68dc70cf7789f4afbe97d03135d3ec345df0d44caf3ab42c90e31c441f10e544e3eb04c1537d53aeac441a392a2ce66c8732be5a1c0c3c3232378740b6af61cf90fc35485964183d3b989ad99b562e6cfeca0fa57172d6f906d31f72ef4fe3dccb00849432b0a2c16fbd9c1b99e0db2e0e9eb827b276f03d02ccdece913cb1148b0eee74a294b900741cb8911a238c095d8693c319430729286b4ae2f3bf1cdeb8c8bdf5c9206107ddcb3c2001b03f713ee23ee11e46dc8b36b1856184204964940a48da0467de0453d2d311ca01ec2bcf1ad1bae5900d18b79035f186d4480581900f3a69cedd93409b5ee4e8ca480deb4c7855aaa84c67980b08bf66c96f27811c85cde06189301db748e47e02075858802eabe46f8af10e3c749ed463a96a6065f8d806e01a25a8c9aa356ccfc767036155bfb1c4525672c67a6b6ae4e6138ef1e90de311a3960dbcae5b366516a4866701fdec2f1659dab7e5cb1b5ab9c2c39ad1c23b6f1229c35600c7303b01a5f3dc418a1f195d44dc0e336e198d62cb308bcda50c495e1b828a78abdf83ce88352b5384603ba6e2df66bdf199c9be6b9cc087be14fcd8b70cb81630fd10d307370342b8f619284cb60cb8fdb898227fc6b02e906ecbc09757efadd50a0d1ed17641c27dcf4e289efbe2ddcfb0f1395f68d56b3f18940aad59928255f9154d297d4e9c58741a0de06e9df01897276c11fca2e8abf977c0134f5e04253dc613f9a9b182d850a9aaad36139ec5ae56648e15bbb35d21facd9cea369d8cc9cd8b2f76ce255d4d62c3c5f78df15d83086a0dce96d64b5a1f7c65ab85b965726c46ee3b6b8be2c86e6c2bdcaa4281d0add4a8e6910b59285ac67b262d89bb0c62efae35ada987a104e4180407c0f37bcecc6965c8408464dc99399b89a706ce1d083f3ac75583f1e4d98313481dd2578e58570dc3ac69b13b8fc6052d79f082f12abda8716d1a942cbab552f5f62c19944b1ba57b3081f013ba1da4830e89e5eb71da5e3780babfbbe5179541e87703d9f9ad199a063b2c917798f02424feb55e9152f1fa2bd144cb7ad594e27cc9f6257cc730a07ae9c7804af04e11b4e73481ae617855fceef33c47b53bc63834ae5c3c5f59a210210163ff4f12c108534f6f6e05717adcb786313797e4e6b79be408ff28c6dd9a2bf9cea26132999247447af5cbfb8c798e3767974e709c7bf2418b92b341dd50980faa4c739cf6f89e994bba45f9dfac396600d39ed199f7611f8f59330a525ae393c376fb52824db11011c0523c9ed1e1aec1fc6a20d3c7eb3becb0e4d07240c9d3c84a2d0c14e0a4c24eefbd7ff6ee9e2b5611e2d8bc77cc2d7afd4a933f7a98e4a65ddd81227f747bf913827bb4410db64f97051d32598f969f6565d84654455091a07d24748f4d27934691973e256613296a851357040c52847fa9c3846d3112700b7494358fccfbb757cb20b00f9d21e7192a64535978f3b7906eb88ad261aa61e693532c9c5efb426c3e3380998753c0471601b4ef1b7dff20ceb19cd8143d949ad36a0b0bfb7da2ec066190069995d596385d16e1747005ee1e39be8d61743686c8da3f3a4e0c92cbc40d917327806379f7ebe408b140498ed0f5d6d5decbdb16778d9271106b3ef40de4a7c16879f4f08b218102127f74e8b3e11e65011f2b81e443ed3a79856846641e0030eb5fe44540022a290202a73f432ea95fbed17eb5086fbf976e771d0ec969ff918039e9182668da0415294d7d1e1723f3a311a1222417f48702b9569a2d775101c3d8a7c2b65225b3f6ec9bf11b54d48dab9e04252636dd1addc584247a235b1685ec50083a82c99a697aaeb3898633d3c70e225946dca65155a62ff19fe816ffd3415df9f296fb0c6cd187ecb2ee9170797835551d304ae95617ef456fff5aff54443762974fa40902c0f759be1c8ca343e3cf05ab2e1023483f9afc162577e1b661e0ef92cbc7f873ae387dec1651b082f4dff3d9c16a68f212551fde9b32b82ac9090cd37af15813284eb0551de913a320bfbab92d519d8bcac88d38354eac8f9a0d1f14480113aead8e231b56aa7206e1188099f3b8ccf92fb6aa6d55ad2dd8dece8cf28316ccd78a103ab643d6986ce86438fcbd79c1831814a3067cda4149a677143292c1eb664fe953328f4d2f6a75a4c877bbc5b208383f8b47b4799849a8ad019300ad3d5e6dc35e8a15369fa3c6b7f05c18abb4ec16219baf191c0732238a65d72c2eece8de203d5c480f2cc3072ee3b04fce04d33869618366b8043431c70ec2d8951f96f5cd0f3467bd04d630d6e19fcf4e125f65e302add4d5e25a0d2c6cfce104124921ba08c299e9abb6b5d98f00cde04246d629461d4178661c9fc62d3367415ba7a77e3dbc7c83acb10fc56214a8f27f4d822262c2f3714c93cdb3aea4247f73b2cea746601594f9ff21b69e6ccc0b5ba696c2c086f49b50c64425565dd4696fdd3d71e8de2ef8ea66e15b6d7900456ebac906f07813b741de873c3696ebf51c7fbf7880d3b039cf9bcc22f7c6c94af71e668bda636c041c497a9e71a8cc76854939f00a80d448c82c3d429e89c0d4a5e240d0f36202f6debc8ffb7a92326a6892f20112b7328448e8b5eb279b260476257d702b0d5ab1a7415dea2a1e000a432a2466db9d40a07475176bd25f087510529650416f250eda8d8c0de35e51fc487ab9abf35169ad2240e8dc21139bf17fdb5f88d2b28774760f0041b3e6a2607e492778eb1a96c195c5527eed557a787545164c13e4524958c22da27ff3bc62dbae1fa33fdc2c7e51968a441582ceeeb895b051df03da6b8daf322b0c3cb0799556b8164405cc04eb615dfed2ec452da99b26b9ad8dbc9504aca0ad5b834a2fb7f9a828824c53794ec2eea6afe0816c80c2f1d26e66801d473d568d5b333ed731c00cb3f5159b3a7f1bcf6c246c3c91b1b4f354765fe82851a226b68930196155d8ff1d60c71a2fb08d9c7c3d5a3dac6be63459d743b2471b5c3fc4d4762e678614d241ba95bece12a599eeca3fa3e69277ca8a8018d74f00d3a7734d0a559e6e07a1a705add129af825c02b61e20a047e5001c50c7686fb3a5b4439005a8dbd8611dc99035362aab297904dd6ab1c9a55790684e7deec6f9669ec59e784463b7945f5a5d9c171826a3470e282612dfbc083f1e07fbac920ed7f320a249b0d1b60a9312a79e0bbdc9e13f26a2d1b628164fe1af321426d6d1149d31c493029df8a68232c589aaadf84ac0ca07686c15f3007670ce135c6b4f6b0672d84029f073026260f3a28b953d7196a46735ce2adb1f58834f9492eee5080d8abaf4ac68eb917e65f71df26f25dbc644fb180f0614c3e67e99c7c1aff7fdcb06b03bcde7ecf277572beba459c6f6bb60607d14139405cb3d9416ea4811149d5c383087be42cebf717c585c6df0d74f6b643a16b6215cfd166fd53b33c1c91497eff3bb7d463e429bf024e0b3cefa810cfcfd8fe67609b77998697463a77e9ae29f524e21d033bc84f96463e2675d9c70cd5af52c6987fbfa16720574c80c96639192800360fff4dcff0a680d447cc349a58e78c5809d0e1bdecaeb322349ee71a7f07c4fc622c4b910934da51e6c56175a1a2ee7680b8c26e2cdd458bf4b8e7dafb0d480a89612ea8676d514c476e820c196f03062b18021bd760f8c5e5b2666d1925a9a2031df872e5a229d44022092ff96ed2fa9824162c1c677224a0c6f1604d3e7a26a62e620af8e424bc603c990739d39ab9c22d65d4d755cb4920acda30b662205fdeab138ec639d471bab7e8203a329ac0bc375ffdc3c3fe791d6dca21d4806d38e646a0c101b82e1bd078038cf65b321dc4af1ef5ac85c0a57f17cfe6cb808c87abcafc5cf699af48a41fc64d8780f7a2e74b44e860d2f743d39d8c8b56ea68e337085b66754e869ea1e85849e27990931c3f68fefa84e45960d00d41b06da260cd87d65421776064b621087e6d98fe7b534210c7027be93490408fa0b51b88c8e0326bdedf2672bc0de17e795f4ebbe00db444b7291a35ade60df48f8be8354eecc88b4088885538a8bfd29cde0f81eaf624591dc4627cdfd04a27acc08c30954d76ed1a9504e35d9d5e0da19d9a5e187369a93f60f564f3233dfde4b49e3d6ab148e485f05f0d885f6fe2b6376f9d67ce449d271f896018621788968ee47e4e274264c52b79b444fd9600170b8acaf9d87ffec521ecd446a30baad7e8f758f279608ed8bc39242b9db34f6ffc19498703445a6cbc28bc1f3c9ee66248a6d7e6a93ba6baaf9a79a4e1cc3cb14909656d6f22dbf307c87e3777a2088f4693b59c3fb399aa33cca8ecc6046432dc958022625680cabb678a93c8e221872104ec32de279f30ad637bff7bab23134329c1dbe41d84d3053ea2723ff00c9c04a7584528e25e13a0f20faa48581fbb11f4e8081acce0b8b20c51f9049793766b55f8637ae5b53da3b784158f9958d72da76f92d1f6ac788a7a0430d98849f4c3572f8c210c2183420c796880f4adae42b9412a989ebfb901b181a66eb7033ee16f063b7a2060e1dc64446a80c2c22405626ce8510f663052fef7e44f5d46d4f3378bd2d2860e71073344ef8354e8fcd2fb561222f58b41f977f2bcbd4bfaacf66bcc6b274284fc51f55ab0d80f0c565d347cc0cac9cb331be740cc3041bd87f2b06d1857a61479e6e6151ef1ed4c1330c0e6bf185b528c03c82142ed3a3d6edd5a2660a17b66beae81f3df5914602b32e24f8a1a67b6c41430da36b27d0123c63b140f0ffc9e72d1f3d4ec0d796d74f7f8a3627176ae95f42d45f06636fb8f45d3508eeccb987a53d31bf201d331d7249ac25913290790a32947de6638f9cf01758bf71905cf0eec524fd17aabc2107de89efd46319a6bbee2a2e331964ef82fc967009b8d3e280db38c71797d8b03f7cf95fe4b970ca6ef8443a3f16be22e337a28bc483710f6825dcabfb29893e55c791d55c02d8f2ad10e40b185ff3f6d845fce751f93b0d9976eb174be3f24b8b1a6c9f83fda4f20f03056ff8fac7b4562fbe41e26e0eeccf980dcf8f1a0cdd3d25f78141d7e49e00141068fc63d142708a9b1736556621c6c0ec2693f367f4dbbcecd8deeffc398c202fcffc30ac75f6b327fc2dceb8683fef33482eadc77304adc8749159b7491e1e119e6dfe7ce9353f41a90d3327f92780d81cfa833c666187d07a28d3cdeabe6882e969af7a622cc809101ebb5a69b0c9e454a888678a572972050e41373d3e5f2cc8411d681ea5f18edc332d052fda78bf32158ac66df8e9127b954fd9e06e01a5ac0ee8d1eb3d28e1add3fd35ee9af3742e00f70de2f1dabbfbb02aaffb3f8f31583ad496130a36ae9aff37c5b08274844b61ef534729fde860d45ce2c9cd859f24a3d2b1b69f91f43b8fdbbf25e6aa4b49fb81f8a6635109b692486ff9b9f3ae20a37e2f054151a0c6b24b2d1f1c6312a617fe8cc0ebb089bdcc61f6aa2e3c5bf8ec2eb1ef47135abf75620b4a30c77b4310ed959e6a26294cd6eecaac4fec10920dff8f5ac755e2f2d4a78120f63b5ba919e01e2a5c3e9c89f903f90bb0ea60eaf3b789471454e82988871bffb227ff309d515c4f680fbffafdf61b38ec6cf2e9cb11d114e06edc33af03eef5221bd9f7317f47fe44db378720487aebd04632dfee0cecb5d406bb9e371444a59f39e077cf52b68d07fa2d61a25e0f9f1f3b25e3d659d317fdc85e2817a9713fc806d98cd17321b41765c8773c7f7df74d913349fc0493de8bd5f96b020477f6cff60144c56632ec92b6f560dfac2c818dde4056730deb906be530f7659824ee04fe21c74408ce6518f5a4ddc2dfddedcd48d6190f88f6ef91ffd373956c94d32720be0b0cccbb239c7ce2cd0a325e5c019c47a392be3b40a6eca482c1087641ecbe25c99da1a38c90cc06a5277bed9812d1f6203c6220b02c656f3b8f93c2edb3b1374b433ca5ee487054b5cfc7c56e912d7df504a8a810f2d3fb1f573ca523c7cd22520c6bebfb48addaf2ce762f90796a8f8fd19a52baebee12c95af201f567e62f3e72dd53c9afbdfa0db3fef247a41128d57920404ec95e30bf2f86ec7ac7aa208a7e6f30eedb9f77b8c1517333cef0d2a1efe933b867fe7fd9156d5f82580817becb9cb953f1b286849de139f52a94e9e532c1690a8f285f6b6bd6cf5daeee005a90e973f149d6c64666d95089dce6e60f64a71ab091f09255d3de31b5f7d4de0c3d824079002ad8f71ff73c43b147abe90e61de33b4a1f39351f95f3590146f41c784f58af229e75edd7740325c15c0326bf72df10e34552d7e81fba2b9f8bafda56327877f31c0e9b1129a85aec6e8c1b81c8ceff275557c762eaff5fe2533e77143f202a06ce755b8de3bb854e69d9aa9a7655a9fbb186540bfcff635ddb94dbcf035dfbe3dfb6328509eb810757d77d7f68ff9ee389dbee7d166666f447e3d9b4791e4b9a233e7af586607ef0fd7e9d3af3965cbbff426047f201e1fbf7511de162ce2060fe97430b7d8150512da9874be654e01f1d123d5d3fd43f9946e8ec14bcc2410353c305082bf6c1bbd02e425b1482ab7340f4ff01e1c01a5e7724003b1378695c73def73684c1c3fa8f01822286ad1026911f94578048c3d8e1c0e79e6d4365a6c814af243a08393909cf3e1f88ebba3e640db08fdc382d06398cbf20f06131293808f51d2a3eb893623802110a7f7cf60a7cc751ad08a9c1d1ca73a66700073ce4548b1c84861e70101cbc1145797cd01f4887e1400250561c86930d3f39ba197668b10b1e54f5eb8d05cb1b4bbc4cdb9b2a96720a069ee6a3cc752e90f9f4f86c872d696901fcec783f7e5df38558f0871b22a8adefbfb2f6857e09b90e7e9504dff94a825a5610ee5c4a78f0a067844e901716f8b90b18a54b610e4c483c3da6f6c1eae68968940386a448bb128d00d37671b958afc53f5df4b2ed5de154d73f14e8bf02edb44a66388d440ae1dacb50571b63913f4420de17ddea9d0c91994eb7478b43a8b1143eaeee80f15781b94ccfd868f31b1773ea6a22d6cad2622ec370b91c08b27772191f77df01c5bfd954dfbc0c1196efd023da4652c061b17b04d9a1a276aa87403e125997db41715797414ca16b504faf2a21b718b08fa15aa63d7ca13b19ee6ffdafcecb20244d5221e54991ba6832d900f7b1107030ff0fc04b6cd1935604413e10f73d2aa20cd507a9c4ce0e7bcaef7f3f7a8f6b291dd77493de3d17b0f8fcf1567b443d1f470e280162c99fe346b7b717bbea02c5e85839c0f8e1ac1d727b69f8628cbcb3bf1874f9008db553acc415c3d318f3c89e981118fef8bb47913b3aafdf03d613df4dd2e9e8640c22556f5055357dccbb5e69619313267f75493ec5e125c0a00a137d746f39695840df850f92682ff2d7bc16f49f4ecce747425108fc5b9466826b8897869b19c6d2516f8bff8e97dfb893843de3e1277ecbaa7892458e5eb4e803ae620643134a1048af246d0c62243000304b140f51ed01606925a051803a2fa80bf4800c1d6ddba86dcd356d47c9954bc7b22391ffed9d80c8f4cf07504aaeecdefbb8beeb5406c6863d7559fef2d724011aea10b410d3c5f558cbc6cc7cdd0845cd299edb74c264d139e343ff0a0894e2b920d09f4c15e53a8bf9e884d1c1f24547270c179d10733b6542595408a17c4ea1500224b0c6791810010e9948674ca4132090a3e38007d80fc1c726e080165a0d90408e0a0c50210516162001054c00859580154e60071760c21a939a540a25c0c77210c038c41e8d9368e84c692442b928e7008c040e9d206000c663aaa1d3a731d53f85428c063ed6531fd538a1d0034690400150271080994ca7040c004d754a2299262000eb7f4a80082984c0a13f7342001805001540600e9082c28d02a2f0a400854fa6942bc0c6014f38146fca67014e38f4a250a8a2463ca1d49a5740f500431ac0c61e4e9ec74b501ad0c6102154782cd108151b4b5ec078b9006a81050aa82081084040e7010e68400e03525880025038c18412107000120c80334201083000018810420000b0c9089a10518da0c9152bb04123840a0f24d0a052dd3032041d4e2c14273a70920319bce1c40d1c2108b9210032b8918010508238042788434e5f4f54aaaaba6244960d543f1c1184020a00c2151c20aa9052021c201410c20f707e20658145555d618496aa082da9d3a84b95aaf2d228a7022c070239a527959e545515ab2a5185e30320692891648c8a0caeaa0f3ce1c4182f17c0e9e18b4d6c34c9a4aa6c3001c14692aab2b9a492aab2f14165473692545e1e8b95aab2a1c46612495227530f7850997848383ca880f343a6b289640755553db1e60b48caa82a1b1d54674a2947917c508fc6532192a7d1234d19386972704e5f68e1c40e7aec604265250738e0a3aa6c1e71c4860d925313294b55d9dc00914610f194dbaab2b1410d10418491441641c40689aa3a3d9a51c9d425352a89297da90c4f5d937a12c9a7ca9f4c564a23534d8dd2a8787c26992fb6f429d493a92a1b4512a92a342713969f2a5565b304223f55c084a92a9b380ea92a1b43aaaab2a1811d8508a9acf0d41aaaca4605363390811732a82a9b180c22081e54e0e11348555550f0305d18005255367f3c8f97d05841135d903a794f55d9f8d147559d7c485e2c6924b26273829f9fe188aab2e1638f9f47b3d8cfcf9086836bde9696a8eca8aa6cf4b0c9a3aaf0a85a20c71d76e08c443675d8b0808eaab299438eaab289c3068e7f2c541a9aea29302a781275d38efd92182fa61d92ff15d38e25f998c83c8f4f12c9074b0f95946987a59932e5cc992f3c758d69474c8ff7ac99265318d43f9633a5d1cef38c9ce4633a95c4a44ea61e1e4660509664d2f224eaa69daab2798344f2c1724a95c4d82f89a92a9b25dc68a20d36d650438a14d8a461df7e498c0d1a2320917c764adec54b0ae5cf2545a5aa6ccee031954e5850a7a73cdfe5ebc834e6e7e483f23f85299946a3d288c75279928feac8fac840428dce3c1792d75149543fcca719a57cea4b911a95c4388fa37c66b03981195555fd94a8b09a43fdc253d7e0781538ae810aa5c394292dcd94296ec5636543465599600c11cc78a06263ccd460a64bc553d7b431a85843d7006b7835b81a5a0dac86aab211836bf8d36b61a14742878401c6c934b229c11724b0f1a28b1188800b2bb6b0428baaaa40b0c9a2b2232c42c03270810cb82ba88954c50a2bacb0c28aaa0a4b1a91a92a1b2afe4a55d978600afb34904852d82791a2b83490485448242a556503854d074a3eb7aa6c9ea8aa0a041b272a3bdad24455d930f124d3ce935a1a1c1c08808303149b2558530272121189cafdf8e90823aa9218146b69a64c61696030ddd21694e996b658a0f45c4ea6d2880786d273791edf59e35d9ec74b9e720b831d854e4fed9629534edfe383e23195def2984a27d3e934512894258dc8c06081fa6346674a3e584a3ed76ea925c8d1a54eeee5c79aaaa44ede63b7fc5853151860602d32f6893e3f8fc6411f3b62cda786296cde7e6a745263fd4f5565536955d9b88d19077d589a109947c81442468f1572a65cd3fd930a64ca54a81e54f449d51f831af550418d7afc4c89d4c333428d71218c1a939510ca090d70c49400474c081640bd359570c21c91a552a19a154e9806802101104168a0872f8b5455e52914992faef43d349876703000080e06c2441f2f5a7068e06233a6089c1d3d426b707680a84c55556da0c2e9b283aa429d5c7001a74b195595531aad2939972b3344318397aab21153553661aaca06cc97aab2c1808d97aab2a1a1aa6c76ba5455c580aaa252e16ca94085a3e5920a47cb1a58185045a02a41e268e1a2c2d1824385a3a501158e9627154e96412a9c2c27a870b24451e164e9a1c2c922a6c2c9a2800a074b26154e0b6254382d8ca0c269c10a10b680993fa99118d3cec97a952ed6aba050a751952a4ff214983ff9a47cb0a446259f9c92fb99110a5581112807e54f9d5445a9d388e6784e89e6780eca452b80ce805e851cd4fffd296df1d3fb69bef7de7bcf39e79c73ceb9d65a6badb5d618638c31c61863666666665e6cb1c5165b6cb1c5165b6c3129a594524a29638c31c61863eceeeeeeee861042082184f0bdf7de7bef3de79c73ce39e75a6badb5d65a638c31c618638c99999979adb5d65a6b2d96524a29a59432c618638c31c6eeeeeeee6e082184104208df7befbdf7de73ce39e79c73aeb5d65a6bad35c618638c31c69899999979b18c0d9f6b8cbbf06cb101e7853baaaa3abd0b75747ae156382fd850e1bcb0a5c27901860ae78558e1bcc04285f3424e85f3020915ce0b20543816a8a4aa2a2905b0ff24d38e2599b4a41e85a2a2c644fa2a3fa4120d624aa6d1ac2a1b19aaca668b8d960a65dac962bd8a4d0cd58f7331edfc97d48867c4a5aa6cb0cccae64a55554e6caad858b1a9525595a36c9d6066ca64fa02c6456bfebfa04ea61d2b5ea5c7078d09551a394fe964aa3ed407f54fd5dc91c562bfa4c67b50a7ff71520ec9ad18923f983f79e1f9d4169b182e7085062b3b33f485862f5f666872117385862ea11aa8749932e5537ec67b4ca71e2e696038e361bc847934a630241f6bea1261e0f9d4963326d29746674a1366b1820a4eaaaa7283f4c5f383865028efb199c00a9597463ca353458d2c0fcad59c5e0f5565038294aa1a33032a84c684820186e8e329b7a81e94fdaaaadcb089525557aaca068a8d4d55a1dc0458b327931a14cabd8852ee65cde83e8f0fea892699545595729e93090b95f85060ac5789ef515ec57a95f9a5aa2a3b328d799f1dbc8786ea4f5555e5e0a0cd6196d90226746596d9c285ca962f134c687e111386ca4e962d601c65eb57393886a5aaaea44c24300efa54d5134a2649994860f889504f8f4f9922f93d76424d99424764ecb3a6d080e34218a9118d8c4328292f5455354385e3425555382ecc0001d409840a9dbe0704c281182d406002a78541aa6aca14500f150ea8860a07a45355219e510eea040ad95f33ba1eea41d551ea7944a9d1894a55994e5fe6a78a98d1951295934fad26354f7ac083aa229d4eb39a48242a34fc0000365e54303c89801318544efac8c3491d4eb698400eea04b2fea71f10d1a307453c7287163f88a1010658408c54fb21cd1d8ce0f1802756f8a1ca140c948488011b3f086002282c02c58927362048184514e0c749061be0020c1a6488e1864436c053c80c605451d86ca017b1c01195cf151bb0518026f2033e28c3073ea4b803871596e0e243156a6eca68c00b01f0a14281068e045051011f5cf022c67d22924a7ab0440063d4404962470f7404208c3ae69840ece109109040d347984b7ab0810455b0986004253de814c107163cdc8434b0033421988149e4a20136ae1832040598c0a281226c6af00489175ea0012f7d3cf2a65031030d24a006b3013eb021033c305204141458c8f2030f6634ece101a4e741034a2461268e61e2010b002a00440b6496e061042d32b94988d1f34388b323006a78e8f2e3c5135954008a3b5ef83909808b0b5f68c13f1648c4003fa49a103f5250a8a2070e943859f3821e5e8f0c1938b32604511c710618ab8b35674a306585303d50630dcbc11d0d28801c96a8d9644b0cce0658e0a0e68e291f9803071b28434d1494842bde60115013b209d3022643f4a851a18f344ab8704599343d880105b858c28703a459411c86e8c083b7910689338ce8e08e1d17d2840193420d5ac0b0449a9c2a1b1883cb2681f83862a3492224e011890f1a7d6cb2a30604387c80f0e2438082e78c8f0c2200c4102f6484e073001a0af901b5020ba41011c10c26c70f6a2a8c2c4138e2822ab048f9bc800f31291e4c4851f1d9a1104aee20211502240af000192f38800aa40645c2f8f90205b5c5212870801d293f502410bc01e5142608144c64f971439a3d504f769840f5100ae1b4870c1a78e4120ee34445a905484821091227ba02295b58a10490530b9b9cacc82212303bf86013391070021384ec30077334e0411045766802056c68d2011bd0ec5003142e301512c6ecf0802f20104755c8083d90a04c5638296ed0b3c60a1138c01c6084e92122e7912b571c61a5c74b02c4f8e183eca3e78439dc00e20e1c7e302d728507f6e041801ea6327c983be8e0bc60e2818290c6cd105298260a532680e68a34a6022422850edcf08185d20ca034c0874bdc90521757d8800a4c82f8a1b4830c5f0cc9e4cc074a15002385d6c2c989d24d03cc9023093944e071011a4dbc0af0d1c373451804b401852235e0b92508a30555687884873560cc142b500f2069f2e4014f54a24693ec703788a10e4bea2041f1041b208876b88094031a39ccd1840842902430c70660082f3c343d789247064c76026854a0030d34f9e1060c344788f981199a36d080515385242a8c59681800813838e0c50eb8e8d0c8234e62114c0440873320712df40840d0e1074672b4d87083d2610b1127fcc1031a2774206164822a778c116174481825445a5c096404468e2102d8634a24a3940d4c1044105a6614831a878821c60e19a300c4718687316430430483334560f2060cca88b660001674932b5a44681e8fd6648a0044ef111058c0430a41444e8ef8830c086a0c70468f01a4100909741e39e3811d4e5006070b40e28c4bd204190f900190332cb0e1032351ae70e0528209158ee8108278e5a086e8a08d322cb94ce8f0834542413871c91ca1070a4ec08708d7017f0452891321bcf03a60800aca6c608dbf1a27fc01d892467e882c441440123b70f03b83fcb1130645c29b3086cf20569cb0621579c307874011a025c303365c370e19637f26095fd6196bec95b692ea811c75d802b0f07cb082288b1a8343c6d45025864dea08ce5883c58a33acf63811051a328a88a34ef9e1cb64848b3e6a14f6453bd24817d48f49c08420c921825ae180147ec04805087d3418f90011550928e740040f7e5446382677fce183339c085ec7142b2c38814320de813bde1b17f0618de3804202940726927804e4000402a64ce630c3839786081ba87862e60d079099a24224213063c48e182023246086992f5d8c51228b2f929859000cc8cc3c529083900d0a31040d00429184ce28811148a69c2181d00696108109393c720a69e911e26711376e0819808907f2e20132326088163108b001991f3250821b30d6ac81a347065251c4b460660681642006285774baf410460600a005cf4d228e9303204090896924c027072dae182de460023a72d0e18543b44cb1084f0e6e932e3734f04226872a8f9e17cc80fac1010f4312e008ce971b7098e2072d6d5099410f3838d3620a3eb62c1c2840847380225f74b981121b1c1a4141ce0b37c471850c60e8a80115372cd18295352a1080256e1893c41552b8500111dce000107cb05243264294c1810c68941b5ac0a24c0a0e8042f7a0b3813242a8d162c95a002bd34509124c72c21d349429810b1cd041821617b0219146de188171821e3698808a0b0448e3c41836ac7110e8a29253243658c1c191475a50210e1b0680c9086bb45079350c72870cead0c24da00611fc9148046285366a30ad3192a4aac2821aa437e089eaa1a9014a1734502d0748c8f801c30a93c861c612643ea00901200941238790b178a02250420e8020b3b698440d03e8dc31975ce1860d6a4c79610c0b645859ea906293311d00210259bca000798cb9218617f4186207670c04f4d8e48537c800c444d29387a580012e20a60d1b84f1c79a14f010c3812f901062c6042ec46060063d40c019581a11a380285248428d1d50616a408809254b24c121cc09b68c010129e6f0218c0f554cc085115980c26411299186e4028130386c0020073b7a52005308253e7490332a18125492432363e0400318540b25e4808c0f228081c1044b9880073e0618109410c10a54d8d1f9f2071737f8350659c0972cd01000002bd860c79791034e3c6c98407e712834a1025024767ca91010421e35c441c2400bdc30248c381e0f1890828a13c6b809dc8101336b1019610e313c1898c00d4e5010458c06bc4c7200105402e50af502070d0e10050c010a5e92c804120cec0093901731544060061270e1072f0d982948a488157c808647b858811f30f8c10d34a000053d4830c2071040431064048283019030020d5cb82861802d6ba441030244c0850f2052746027110504e18342ac9060478c4b464b4c2145d8510362239042ec7861a78a127784b1481676470062a4b04ca9228b2e82bcb1820beee0e1842e5c68d2c600242659ba94281d8a3851c9a94ba4c1e60617b064922e36a79111d73bc0a58f05f874d173050a5caa5824030be8411907e0526fb0c82272f4e00a171708d9a204450d9e192cf1a14b2421304366063a2c0c6516e96280199ef0c2897879a104339439e1063a9c90099519741031e208125ac0870c3b4083442aa404830c6cb0918584297c583214e12f4853882670c880011406c0820d5810c88042063600021e539cd8c20823acc51677a0b698418404ce40430b1cb6f470d3050ec2b43ab6604193821d58c1018b2d234052870d7df8c0450b212708841213eef8d1e2c5246be838440bd772cae1924ec00936d0f2425d21923970e8428b14218c90e40a191690e5057cfc3101046c6146162c4a50c30a99110a59443896f4f193a3489656c54f76741102cb267e58e1c8203dd860b9c30b451c80a505576089428d34d288522a014b8809488e00a10c1d2c2bc06083d4620947a6247cc8253510a401130e09d8c00506e004319188c10e8b6c42c49319660c4952c06cdecc5162022ca8f0a403571c5963e527062140e34a1a2d3cb103111928b902c40efab8e9020328aecc908045024904122f570e40c602d6a4b1440c5610a96111331d78a1b212461c3c6776eea8aca471648a18448bca0a95397efa10202a2b22a0992402aeaaaa04e2061d293c515555b858573012435555e14980145cb496348d545555e58916a19c3df6782a0421a2e2f8410544041095e830c7114ec4604315834e75811de0e0e440a950279069f42492aa7ab283aa7a02890e9c50c2831b24e28406380c28408593020f52586401154e0a6e54554522f99472a67ccf4f2a852a5268820329005155d50a395358604185147248214c5555554e89c78764aa39395f1af9d43fa19c87c7b4e64539a533a6d3c907f5251369855b1ab5e00205724ea69c1ddfc94999cea890f3a951697473509f323d7d343f8f66052cbc960f0e2dacb5960f0e2ae4904c674a7e32e3001f7260c1cc143339674af575a0a69c520a3522e5f8c97bb0d03095a0c10a25157e4e7e725125fd2637014021070057e0246004a8132807d50fe9c7493da8d1151c54d59347aaea892337a870f0395d9f528afadcb7953a6b3e27e854550e0b279050e1982089990a0445704c4813df042f55856342ac2a1c13722a1344c031a1e2f1293d4e0992543dce8353c2223825e851556abe8453821a23a82a9c120a8053c212550e4e093da9ea2717559c12bc9871a73ff3a9d208a78407e09490003366724afe249389fef8e9e4674a55f5c49042aa2e4f20e08903dc519dcae0204004ef71e12745832785a0bee48852de93432251993205e70007a8aa079c4888509a040d8872068e018ed0020747013823a061ad002c808f69c129c012380558a2aa2ad49aa5030ca038c27680c70d14b03d4fb0b4a1011efc718017cce41148213c58f1820d2689010e29b060c4024d88710a40232b08010bc000810e487ee822820c2051410f60f8827a8290174e552ce1839a03070b3d2d18623e2b8db03e78521488448b0d57a4b0000547941d5830a38c494ee805bce0840f10b0c08b2bc85ca09484103fe06206053d501e3c11a57e11dd30c007bac0410b01e83005060684403004b4240e401820882f42c605974401011606d043420116071821a40f9b1f72269923cd151a30f2020d3f68629015f27001021640418a2d78a012013d0af9621306e88d15c20e7b4c998233802b700620028e0022499d4c948a179866ca94aa824184722f556ae9074505c573a634aaa51f94ff38e96d498dc9924ca61ae2f1298542d6547a52e8e7940ab1e6234af5bc0d7d6a8df384806a3e76cb94290198c2ca183800c8c4420007004fe00080071c00d85055392f582027678a1933392994f7e43c81c11340fe00618d2a890a07041070a4c0c1536bb0796223e589949b1b9c1b35a8e0dc88706e4cc8712a38510ea9aa0a84f2a13851b8b0acf9e044b9555589a970a28c0022915e0dca7358d89293837a1415fb8f12a546644435e555a038112645f241f1808d49957e544b5478cc7fb1a902a6aa2a0954384f0ec1790245054aa1fecc191f140b2817ede03c095569a61317546850cef3831fa04179aaf422356850cef32753cd61314e05e2f9d4962d2493c90bca678b179e4f6d2179c93442fda4d0a03c0cc964f2f224bbe5ab5f9eaa8a8c7d50679c84c52b6ad4e51f0bea7b7e52a84d7caa6a8448463d1e6220075a2d9c3191cc947c4269d098b1a4d1a9859f4753554ff67892071e653429801a3f315063d06aa1a4c674a222948b32c1418e08e55a323904cc9429acf9c0103a7d0f9a6a22913c65b200191858f3b1c01ae7c14403289493d0b8a4a7aa461530133a9942664e20546ac4d38227775495e996b6542124c0921a2a30422577fce953a3108fe9745da89fa69231aaeaff84a5f4684ca90a750291dc0743542010a88e4a4f4af99fcea0489f660ae9d33c2934a5aaaa1128890125825032475599c999221a819220aad4c92442e5984ca9298f3a9d4022948be078a28237aaca927e64835741996e694b979d1ff75255a61dd4db904344fdb897ff1b42fd788f2463940294b98b0c03991656f82ec3fbefbc3184c1a4ce3bb2a58feba30fc86c30c6672b5dd54a48ef1ff365abefcacabc36c3fb82c9edfe3a7dd632edfa2e987c99c678d743968bad1f53fa3bc8efb8368e96f63197e5becea5b58b29743ee6b34c5607a3749612ca3de69390edba7d976c71493da67d86904167ed1bb5318f4919767bfef65a87178f2923bc6c19575e1d63b7602ed9eebebb46b7fcf51dd331e79cddabeed1d6a01d53c2f55c73955f7c095f1d935fed77515e0ba16b8f05f3b947ef7b77dbb2ee8e8e79977c6ec1f66e731fdb1cb3d2d5da2ff7b0d9357b724c5ba13f76ab17840bf28b6332637551d9ec9f63bc704c76575cb265f3556b5dc1b4b6af7494ed6d0e4615cc6ef7ecb57defa874ffc6b4bc1e8dbd58f3c6fc6e4cebb045daf8b2789fda36a6bbcfac6b4dfe92145a36a68416527b57f5065bf535e63387ccdc2cbd34be1af3be05635b8c6dfdaf2998f61fbbee1442c9f845144c766b6cba1aacd01b8b694ccaaf3e946efd7b7a1b8df995996cfc5e5cef689f319b33f96e2baccd3e0b4f30db6ab5516bfd45fa183463b2babe5eaeeefbf22d63b28d9535f6a0b4ab3a19b3a1b3ef99d96e6bb909e673bcae5a6f0cf68d6f8c593d36575b6cd145c74f8c593b56d6287d2f63ad2e8c2925376d909b5973d7c098eeeaed1b2fab4dfa6a2598ed3278ebd3ead66acfbe98f0a98cccaa5cebb66512cc7edb8cd95d12da87ecc5b44cb6c81a93cf167b179352791fb247a15d2f2e3782c9cc0d3d562b61e5b69c08265d0b5a192d74363e7ab998b7c55eed1abcd6b2df622ee7dedd77e9fb1ae36b31dd37c9ceeddf7bc1cf62c258e1bba69039d9d062315f85f241f7bf98cb664330dfad27d99215727fad57ccb51e6d2c36e9a07d4fad98b6417abf6137b375140413b66be15dd72e086ffdc0f4afd1b98eb6b5fe58c57ccbdd257fb1768d36a7623ec7ac5cf4b54a5d6bee81b9646bef1b7d91f57f9c627675f7fef2f8afb96d29e6dff6fc5bd3774f5f47317bd115e1b59056b60fc56c4a6b8cd6b2e516841d98ce645d0e3677cfd2c127a6b3f69b5db6ad7d76d08919f9b5cad42d63fdff9a988df9417ef6b670b1c7c48cecd6db564266d69e5b62d265ed5af1c15f7631a7c47ca6b43246a3b3cd4696c494b542e78c9b35242695cc2d8417dae6cc717b72c4e4afbf6863f2b943e6f6c488c9a47b7c97ae6776b9d59e70605e77b91f7c8f3dc9ec7d52c4741eb9426b217411cafb8488799b5d0d99ad4bbbb67f32c45c0fdd955d7f45d99c7d22c46cb53ebd0fbe5ceeac3f0962bebfadafc1bb96fbe54f8098b197f4e5523a3777e50f26abeeff68b596366eb9c95c4fe582344a7aad645193f9ea8235be73685db59dc96c525aeaff2cbb31db98ccea5ac7d860accc2a6d76c96476bfadbb9163fb6a66c98c9172a55f6b3b771db24a265db5f9dad5b2da2621f3c174f88ed95a1b73cbf5314ae685cb7e7bee32191d7b6c9219e36bd065b4965eaf8e49321f8c1d2dc7d58bb537d68359a93387b759e8cb31331e4cd6ce51deda6cbbb0de48262ff620b3cd9ea34bfe0e26fd77ef83af46ef0a1f92699d5bd436f38bcdadea60c2af9575632eda2b2dcdc164675dcb7ecf3d2f4a7130a5b7abd15bede7daf34766bf8deeb2badcf165eec8fc1ba585f455e776186f30613b499975b390dd161b996d2badb7b5d3afb1b50da65c145206a98d2b767d3598dd6eac94fdbb6dcf3d46e662ce2474ca68748ddf2213beb367df36b5ffcf29325764b5fa5acf79ff6d894cea2eae651fd77fb73644267b2e5d7eb3da6cb9c687ccf8ae8c8fddf5da6d6b6cc864ce312ee8ccaca596310da6a38db9d8d8abb7c2665cc8bcd7bd85ac35dbeabbcc844cc72e5783ab356d7ee6194c5665a4ccdf4296c1a4b1dd05b9be15dd65e6184c76bb7b59f962f4ba160f329b3326e563da8bb17a05992f725ded2d3b737e3f90d9e4a25052c797594b1f0653be6c5b992ffbcffd01996b316594216c4bdab67f4c589d57db95b5da3cd217ccf7cdbdf51d5d302b6dabad5bb9ee7a47fd98565af71abdef21cbea7d4c5b19ecefdab0296cc9c77cf4c67fdc98465f778ff9d86bcd57c24afd9b1eb3b26efecf638317b23ca6b7b33b7ff2e9a31cf1988b5da5f7d125dfebb760b27a615cb67163dcf68ec92fad94f07964e7cf8e09a3c7a78f56cb5c755dc77cd8f2ad64b8a263c1b48edeeb18bbe5fc1ad23157652da35bb78fc13bc7acafd2675eeb7deda81cf3df6d943a0bfdcae85e1c933dfaf0c6faec8d0fc77c4fdd9b2de756ca154c191d6b17be66198daf82f99e6d9152f8363eb56fccaf52ca4a97af2723746e4c09a5adf5b2babe366f6dcc7557bbf546615cef6c4cfe6a5783acdeffebd81af35628edd7165b5d8eb21af35e767ca3b7a5d6d114cc05995c0d3617a393bd96a8198b4e182e3aa831cd2fb41805935679a17ddd7ec927ef0bc934266c6fdd527fbc648551f229856a68ccfaf4d9fb3f764b8f2e6ae6319dae44a5e229853a83414e4ec08b654bd96cc125e15b0f3e595d6416255bd1197ba2e6af25538844f22abf966828618111d5636a609c9831adadd25b9392ca7f8c3d547a52f34f8fa93ed6099345b27752c65cb7b137a3dfafb9e56531382163daf8345ae68e5178176b5b7a6782d9d631bfb858a491fd6aa2e6d2934290bd77c5c918f32d29595a1759ac6e99899aa10d3dcf8b7afc74e6316331e6fd287bd1c590515fedfa8431e53fb622dbfb2047fb6f04c664b7de6dff6bf66f0859f3e9e119b512cccba05ddd8dc5762b9ff373f245732cb2642d4a17d9f1628cd5e8aebeda167bca1cf409a5786ef7f08c9a0c4e4830d78dccc168ffb2abb12d275e4cb7a2ffeb46e9aab2c9276a7eeca48be9ac7ddc2e1d5348ab2f51f36209ab3819c15cdd96efd3d5ee75ee3651b39a2fb5d38339a5c030f7298512c174ef99abd4c5f7a47cef36865c4c57affd16a3647ddf75173587483e3c8bb798ce5957ff5db4ccf9991a534f88e4c313975bc3da99519508b5989536acedd6271db5bda8a81965b28edf29856ad0491693656de71e7b8de9f7a5a2e6106ac4180bc6a274925fb77b9259942bda670e763f2a9bd2f724eab2779c846036bfcbd7fda53b08eb0d9952f18a09d7bff7edf0baab564a53ea492756ccea9e6b5e575bb79d976d06c1bc4f422beb8231f28bee76813b65423838f9c05c6e5b75cb28ed15edad201089f475c19d32211b9c5431d7a25e63ecca56b46f7def94095d2754c848ddf5dbeee58b9aed28f43d3fa92e5101817e4ade76ca84a6130f4ce6d04af84eb27475b14f31238dbe98e4fbf245bbae1329267bdda86c1c17bdbd6e7512c5bc2f32c7ccba5b504c6b3db65569ebea2c593ae9c083d2b15c8e3574ed227caf25abf2b16bf5c6f5908a9adfe94ba52d629c3c3199bdecc647db5ec7ebb627f5f884594e4c77977d0ea1b3c68c6f8b9a1f18e698e2b9cd024e9a98ad63636d753b269d332ad6bd8040a8d347b8532674c60913f33ea7efdcadcaae470865059c2c316ddfd8eed6651b8becbde744097692c494ababac4e32dbedb1a3a81985c4b4ffff6a8b6dedba943a51f363c1c911b345f8df7af98db7b1f6b6345253f231954291a7383162beafeacdd461b4d59d9e298dd048271c9855d2189f64d23de9d8d71abbe54cc9bf80402010aac7c4bd5326649d14316bbdadc5266f5c8bd91bbd987640a093694b1d95dc0b0864baa55128f2e0848819e58d8d3d8dcc1e6dd8a2661f1d2c5f74220c4e869835ae78692fc88fdb8d923a116242dbf1babbd5b51b6b64414c6f8edac590a5ff7bcec19d32a11c9c00c15f65a495c5d67c5942333fb06f74b1f5be1fd62597990da8f161da479bfb5abd5ed8bca91810c8747a1e4aa61208c4e353823b6542b487c94ef293cdb11d64c76a63c834c0d8623142261b3e07e5634e5ab752ae7eaf9dfdc79aa8999d523d627898fcdccbb9ab6bf973921dbc667e66e55a9decd7587cf8d48a9a59d3b266c218af33b4ff97a37b4cd45c32f1f0f08ceadb90291592eca999fcf459b7cb7ac5e59a1335db928fe914fa7136a499775d291bb39155fa28b7ea6b1e6ef199313af6a8b5d0eda2f0a5a839d5630aa5501f92fc62f3f894b8a4a66cba2aa50f6d7cd4ba755b507359a6decbad7ecaacded37cfc5032adfef03a786d961da637e7e8cbeb9c7396dd66e999cfaf6dced5766e0f56dade4e99d017d3e4081bfce70dbed8d8c9a6a534dd5bab366d97c617193b63e1993036c71a6c0c61fbe86e0a3549f3dbadbd62430be1cbb84826136d5ad0cc466babad695bc6f8a53f1da633bccce037a7f6f9b56e3421e51a99d3e79e2e4b5b134deaebfa6aad57ac972d646f9e992dcab8e47bb0bafb8e5e5173a88520963b5d7354b61b5f84feec36332a3f61abaf5d6dbfb4b5bb98a839643f15b2de23f90276feba563efdc562472699a8d97a4f03d5f92ea474b547e1c3ea2e8b9a19163a2f33166393cc646cd75ed44cf2d399f71ec6d1e74b5b25744e5a26997c56d47c7d68a8c74f6ae2e95332c5739b99f956a32efa8dcf3d6b7f45cd2fc57323109af04608bd59bae08d5ea17c3b65423564603a95ccdaf577a9fd5b9da839b5e64321e82286ea938baa039303bf76b23d081d638fc5d66fb9a3d657bb7eadf5fbbef46375b07cd191ee94eae1c16142ebb6bd4abfdafb18dbb8e6b59c5e8d894b066ef07a9dd7d8d131c8ac75517364ec134af1dc98a58c6cdda25c2db696cbf55a2e3af86f9da36519fbbee5a04fe85127ef71bc63c36cf6cb247bf9feabdd1aa6b733aecfb51b19744745cd8e797c4a8bcc84bd5cb537766b76f565a2e6d4c944fd0163a674cf687d92325ed575bcffa5e1191ffaf6a5786eeb1143996d86ebb573bfeabd2c4318ebd15147217d7a596ba2e6c605cc5afe5675f0add53edac678862ff331d90cc67b7db9af1dad35498e1898ac9dd17ae3b36b21b317355bd3142fd339b6a8736ec106dd36b5d6b4240d53d6e8f85d7cf7a56cd02eb9332d74efd5785bc768a97ba266520fcf2864ffe42254ec2ed3c9185decb822a4d44567ac025c2685f0f273430b9d638f2254c319e683ff9ac2b8e8571a592d7d9a0f99784ca71e66536498cc6f73fca2cbb6d86a276aee319d2caf2d932b5dccc255213ba30d45a654632dd3b1eb6adb3ebfea525a51f3974ca41aaaa394a7508c39cbfc48ed7f437fed16cb266a8e55de4e9910c3321d8471518eae296d703532f3be2669f36f071d5cab8a9a19a2e40c571ee3c5de5a90a193123ed9daa3902d7cacbfba63f93c8c503e5767311f520fcfe8f1f8945a8ae7aeb132dd63c620fb5efddc5a0c048a0f456195b9dab2e4eae8a3927db3a89919c687a26ea74c685199f12d8deedc7d90995d2c6a8e7f325939f9185ac531dc2913ea18267df63dd61eb3effaa57f81e9dee99332b27592dd6ef79a42920b0c53b27f6c2375c8374a0b5f98f09f5bb03ed96f2fa354d41c6a0c21d74fd3b6586036067dd5a65d9db2b55ed42c42f9f4844ea60b994b055c7bf0b9d6da726fc96ec9ee527fcf32b44f634351b31ad3e94c886798322b64f7dabd5ed97bee9ca839644bceac75c264d1a1a26345278c1aff226d90f3c5e6a03bf7dc63cdbe1535879a3bc6e96cbd4d527a7bb9a3b4814020d0833b654219e819a57bd956adf05afbfc899a4f3106389ffc662384f272ad0dada83972bf699b7bbee87b86d0519942b979256cf90c99c76e4eb614d366850cde66edbd5a47e113352ffed4e844864cca8767bd9d322132ccc9b89a64cdd149660c1d37e91252ca8dc62b216417046a3d3cafebd8ccdf5276e7d041a007814e2914962f3aec8d5973b17e774146dd3d336d61b8e8d41f0302d54f030241e7c585496f84ce3abdd239bcf145cd21c630c573dbdb291382201e932944b2a75008002dbc5b485db57c9965b654d4dc9c85c70bd26ecfb5cacb9d45cd3a61b2e8449f5069d4ec94ea697f3255321498afca5adb55f7b8bd74a7639f8f4e181dfa7ca2db2913ea32018eac259411c696cdb57ef03188270d89049a8d3659ab5b0ae3edc7de5708b1c17575c270d1e1a93580402d4ddc29137222451432e68dd663bbd1c9bfd13633c1741cdfc24a23b3d2f5e3316637b62a7c189bf576eb12358b522335eee76768965aa28831e97b4db2eb960efe7d100482d2891051c298ceebb6ad37bef62d99c1980c1b7bebb1eb1c73465909267c8db22be9651e1fb5f68bc97c2f6bd7ecb76b2d3612ccee8fb6be77965eccca56bb177284cc295d74b51744e96256b6f1ddcbd1716bc6d808a66b8cdfb3b1d26f16522782195963cd22954e765b8d713139c2fbdac6974bb2c6758bf92fc6b6125af68ebfc950488bf957d6b7deec79fba5adc7313da26431b935e777cd2e06ab6c8bc5acbd16bffa7a45c65f97b4234a08a63b7dee6fad6d0bdebaae98f732cb3c367fdfef7eb7e858a9238a159335fba674b10541302ba54c1b7b47d9ad8d97a859274c161dd0e9532310e84fa65aa241c70a080402c529a27c6042fbeccb05578ccf455bc584dfee9cf13ba6edfca2e6d49f69b10251a89831ca06eb95b07a7d8fe59b238a076674cf6d7d12327a66847272449962ae552573492d3bf8bf588a69f9b273f1596c74318351ccc81cbdcfb239870ead1335931c04ea1ba24031ab74ce76b95cb15667b703f3c14557af4ae1aacc5c7c62be58bb3ee8f439e6ad4527e6fb2bbfeba2ecdd5bdc2d8e284d4c162f8b5c9975cc7eb9cae088c2c4b4523216257ceb4e2e8e4b4c6b9bbbbd2af428ebb32b311d5c1d9d6bec4acaf03989b9e06aeedeb5e5f8fd8dc4ac72f565d5724b7aed635e41942326d3e63e5229ad75d6fd8b9a2193bc7137de2913a2218a11b3d5f620bf83f7c626e9153508f44ea99e32513830d963b69994edbdd7ad63db47e74f3e2813a9ead0f6d10945f643438c44bd2406040281402012c9ad804020907c234a1173d52a9f7cc8226c0afd89982ea3fd6ff4dddb8da17ccb4d04518698cf3a66adcbafed217f0dfdd0d06227530a0c0a14ea4155d1b1a2a3658240a9350f0239a9228a10535abaae93d26d7bd5d520668390dd4b9dcbc5cfb128404cfb9e5f7cca17564a57ff602efa96f575b449feefb5431bec9d32a113944da6b45c617374f8add2076319289a4cc9cdda6bfb65846e19743093c9a837a3553274542ed658c364725b0e9f377718e5df17f55f81ec03e592c997193aa3543e080402d5f076ca8442502c992f99357f0b769531d25ac95c4f65bb4e1bbc00141fccf5fe3ac2f7daad8b7b899acf9446289fc613668142c98cae2dec8e4e632ffa5ad4fca91f2a533e3da52d8f0a9449e63bd84ded837ca9537b2599df0e5b5ceff65a86eb7b3057848ec5f8d8b30ce3731e4cafcfbf36d61c3b868e4532635d4b7abbdb1ba15ba6526807f3f137f8ec8ab151b7f64232fb999be9e5f7b7bd5e1dccf7f8ad3f53f8a4eb7a73306563f85ecad6a85b1407932dc6b2df5bebfd17bd8f4c5f7d1d5d573e5f12beebc85cd4c5eb586bcb1ef7ba379892462ba9bbaf65db76bf9159d74bfbdae505a33ff936981e9b418f6f357decdfd760ae2a59934fd658db83d432322db35faf6c2edffad8769119e5adece0737f1994cc2a326d65dc9c595d2d996f4d64bac7a06b315ac7a27caf884cc8d22e6aad7dd7b6d5ea21d3176c2be3655a97a5ab1a32d952f8ce9975cdb49fd2605acb545acacf3a7aa5b7906961bded2fad8df01ba384cc774eefabaf42c8fe169dc1bc8edffb828b7a837c5d06d3f66d7d3bae5afbb2ea31988bd5179daddc7c9059697dc85e59bcef7da520b3410923bfc8a06cd2bd0c643a28ab6c923675b021943098fe6e7bca2c7ce7607b1290d9e2a3ac46476d7cca6cfc635adbeebd75c166f9b2e30ba6ec47a9a5ecd1455f6bd105b345e69abd37b2dbb6b2e8c77cb9ba456a2d5dcdceb18fc968b5b6fd3df9b0b96b3ea6738cdfd962fe77bff51eb3637db6aab496d55a5beb31ad7dcdc26a2d4366a1c33c2675e6e88dd16629eb86784c682b5b951d7cb005937d735e5f5bd674bd07ef98ef923a491df4f6dab5cf8e19df8bf59d9f3bedee57c75cd12bb7f6ce2464cf1e0b6664d862cb47f9391ad7a3633629d7ead6de61856fbd392694d0dab7dd9a6385d6c931fb46ee682f7c2e8e297d79836e2de9d046dbe0988dfe33e6f7dee275b4ad60da86cdddfbbe36a3d49b0a26b7dbb0dae56ad3ebb2bd315f6b669645bad6622d3637e6f5c8e45fc7ed565ed8da98dd2a5cad52761bfce81a1bd335638b2dbfcec17796ad31e36de69a844fbbe9ba568d5961dbcb90c19a82099f8d2c614708a933a360c2f89edd5dd649a66cb134e6bbd445b8e4bdd655ae684c0be557c79675de56a567cc4b5b3395fd6cadbb7e82699d844ebeb3d432cba419f3a37f8cd5d24ae1bb8bcb984d76a5f2b51be37b102e5173e949a1fa6924bfd04f6a74f29ec850c898cf95bd65ac1764123a928f71804031c1f4d6ce989b64e7bc7a8c79198d7d3f5ee9ae5bedc598cd7f1973edc5762f531ac6b4b1b2fade2e85dff84ce96d9f298d7eaeb0f8241028604c689fff5bd8aafcf6ab4b309fba64d252eec7ee8a7f4a8598bfe7278c6394131840f9625679996c92978ddc373e09a6db2a1d6c175ab8245be8c56c7efb9bcb7779496f4bd49c064d843380d2c5e478ede55e8ec5cb6cfc11cca68c3eefe8a0adefc916c194f76d8decc1668e0ea1a8390d9ad0f527851c8f99e1b1277b40e16246fa5a946fa54be8fadd2de682aebf3d58e3ad1536abc56c1b65af1623d32c266c97e3bbdf2517b39758ccaf9457bdf1beeaf07d0cc1645af9d76d06d949e6e015d33665d03565d9fadae6ac98d55763f5fdfb7db3ba24c94b271a62337442091410ccc678b5bcec32b6f55afdc084ecb26796ed4aaf7ebd8ad92d367bac95c2e54d97a83964b231072854ccf6587b6bb8a8a37c1bf5c07c964f2eeaaedb179df61413527635babdef9a65bf44cd7fc6fee9791e33419162b65febda1aeb63af25df2826bdd55dc9104a17e3650fc5fcaf4e46b71ef37f318ace8c4ef042e9c0a495d95babac456b69fb272683ddac95ec9a6bf73f27e693ff626dfadce3781d8b9a49fe2954080dc98786e1a273a634920fa5895961bc2ebd2ed6e84b7f26a65bf91e65d7fdd977975d62feb2cc4f3e6fde52325562b25faf2fa494d21bedf32426a38cd9ad0d3224a674d755c9de6d5ddcd88f98eda36dce2383d1395ed288299ffeadcf52367dcb9103932d487935b74a1932eb226637badef57af21b46f68898d1b5dbefb73e6fffdc10b3c656db5d969fabf5c6a2e633a510ff193bfdb958d119a3634567061d2b3a299f2e20d0e9fe97b7a61d100804022d666f4d25f7989f5c014588499b94ce3e675fb4aedd0b2588e9bada58a95f671757bd5080988e5fb6b3913a7776e9fdc1bcde226cc922dd643aed6ff5fa37bbd6aec9f4ebb55fb294f67194994c77cc5ede77ac3ef91a93f9eca5b2fb2b7c561fbc6432a4f65de998b2d60c5a32237b96ed776b3442f92a99905d7fd15277dd51f87c309d74ae5d76dfcafab55132bd4a577f55cbda75b0dd49269514d2c8206397fdda9564ca0ad9df65b09db167b707d359bed1238bb07173ecf260b65b66e5aa1ca9b7ca379259fb71e5f5a4b44ce3ba3b9830c27821b3ede073bc2e24b3d996feb4dd514afb7530e3ffea2863b3fbabd2cfc1648f512e0a9f41c7ae3f0ee68d4c593b96153a76e53f32bdc1f87ec505fd76cb7764c6cb6fd1b820730619fc1bccca2e7bcca5b3e6fadd373229dbe8357aafb5a25b6f83099dbbd8ecbb96bbd65b83493f5afbfcdfe87ab732321d32c9f4febf5c64fe8d55b60ae9ed5a2fb58accff6a9da5163afa2ea33691c97ab9bdd4c5ea6c9b5b44267dcb517aadbf766b95f690d91d5dbbc77ecb3a95d690c9626d7f6fd251ba58b43498cda9c38670415bc87454bedff7f7add6d6b3844ce6b6edc3c5f4dd3f676730db5227ed8a6d9773d7ac0c2695ab5f6bbf8b3577ccc66032a7cfaff446ffb2673bc874dade7db7debfedc52ac8b4d4becbe2add1c57a692033566ff1d15857dbaeb4c2604ae7a8d7d81ca5cc415a0199f6dfde4baf5d8bebf3fac7ac1e19dee78e55e8acac2f98ac5a689d8bbd7cbdc7ea82d9d5e975cad8737cbfab1ff33e28b9caf57edd4561ed63367f5442ca5c75513aac7c4c78dbfdf8225b8cadeaea1e733976a19591f5a5fc5cd563d6f85c5b682d7c94bbd53c66df0b9b335bbbf0989149968bf22f67655bb505b376bfe59a4b68bbc17bc76cee6aedda2af36bffed98962eb9dc467fb7b87d1df3c2286564f43b56d9960593de4a5dbaca9a9f324bc7a48fadcbefedda5db2ce31dd73f23547e97ff457e598f6593e87cf96572b691c93beaf325af72fefaa0dc7fc1a29a5ebdebade656f05b3db6f8514d6fb1a42a782b92c85abbe8f8dbe31a36d6f21f4e8fef032eac65cf0d5d77271bcbf20a36d4cb7ba3e7e6e4cd998d1d266a95d8bb9d95d63d6aeaed5a6d54647d95563b66aeb82ffae73ce3505135e091d649161ad2d5130697477ccfe63d69bfb34a6e3753fd20bfbc696168db9e2720c3e19eb19b3f6bb0f29cbea27980ddb72d937d25e91523763caf7d21d3226eb374b2f6376bdf2d7fb4a9dbb6e27634abbd65bfcebbefaafdd04b397db66594a3bc674f131beef5f42d9558cd96a63bd1c7bc818f70b63726cf19774b4b949d8c0980eff797dcfa4735dac04d3b5f7f6bdc8b5baf75fcc78e37d6e596c906364124c489b25f4f897d696cbbd98ac59638bc1dbd5b576bb98acd9666385f57584f44730b9594709615d4f5ab62298eee3edf637bee8f27231256defaaa3f242e6e41633b6f3a50ddabafcbb6a31ef5df1fd9297ad563d8b699f654ed6c8ccab848ec594eb2d6c2f36296d423099db5a8e659450de25a5cd15d351ea64a54dbecaec8dd2c68a5917b5d61b840c9be5286d4030ed6d6e0beb5dd5d9f7a4cd0766334aeb5bed2bdfd89eb4a9623a95ee9fe4759b75ce491b2a265fb75c658bb2e5df9ab4f1c0a4adfa72ceb107af7d4cda4c319d5e291b6516decb77491b2926b4bfac74b1ba6aad5dd2268ad9be63bdeeb2bf4fba8f36504ce9b435cbe465df8bd268d381c96cf33bd76ebfa8d368f3c4a434327edf1e7b7749a38d1393a1a4cc725dfdcbad479b2666b3aeced96f90ab7c196d9898ef647bb3ec36c88fca68b3c45ced3a6c767631476b8c364accbaae37a5ddb0618b31da2431d9436e46696cefcf31da2031bdb165f9246df5cad5d1e688c9167d0e9943f9abad8e36464c67cfd03ae6e68cd6176d38309d59e72073ee2da6b4459b22e6ea27e3ebb8aadff62cda1031d7b7aecb4577f6cfb26833c494fc6ead8fd67af9498e36424c6f9479bcb43e4b6f73b40962feed0b63ebc7ed41c8d10688e9607b7bef4a7769ac2bfe60567a57b3f01b3607afed4d264b6a2b4b471f4ac86c6b32a38ddf4f5f4a6fedb53399f2497fd47685b5a1ac8dc974faeedfa67e9dacd6fb92f98dfaffa2ef19bbd0db92c96c75b71a6cf532bb6457325d7c9429bcce94798ced834939727db0b95b1785b12999d7d9b22d421ae353177b9279ab8b94995df7df7add924c28255b77b5283b5eeeeec1a4965d65cacf8309efbf6fc62e57273d92e9ec8fa3eb0b1f7c09773025bf93abb2a430ba072199efd1bd5619b3f64fea60f6755f9bec55393af9723065eccaff16b76d973d1c4cd8ea958d2dbe1fb93e3219572b6bfc48bff93932a96dfdef7a4358eb7237988da12ff6cc6fa5be5c23934268af5fb6567683cd06932f63eba5f4d88f3dd760baf5056bafc86daf5ccdc86427bb39bd5edbeb5ebdc8846e311957adf625745791795b8db6457aefadcd2632695c2eaf7baec2bfd02232d9d977ed3d6fac36670f99f65bad8bf15b94ef7543e6f7e57fe676fb18751acc87d5c2e6dabd5adfcb42a6b5dc913e496ff5c6242113d2d8b7297b8fcf1f6730bf3578bbb6d8ba326e194c86355e7b7fd5665f753198ed5a65b23bfe62fa6d9059affcead119f576ac0932177dcc3d731b5d48a9c1e4761fc628f9b9d8dcccc86c945a669f33baac7cbbc8640d32daf8b91599b1a333d7adeb4d64c24b2bb56d1f5c0ddd250a0462d9d230e6420a22f375b3f2ebb76575b98e2050e890c9bd2057daf65e199d7d513373e3c62d0dfb22c5905969fb9254aecbde627c5133c947a70703029d1e0502bd3d3d4a86a4d06042ca96450729745d9f5da2661e7ebc23a59049d77f95911b2e0aa18c64482164b6bdd6ddcbfc4a681db2d642ca0c26abb73de41bddda6b5b930173ebfe9bbc5052d6f17918a1e433434a0c9af4d69a475a237cd68ace169d305c747abc0b08d4e33c2110082586944126ad6bf9bdae55575ddb9269442279e84fa6fb54b22e241215100885c6641281214510c790b9968cc5e8626c2c25bfea1874cdd67fcbf247a9117b7ee226a40432d76b86ce36b6f6e22fab63e5d15413a90a08f4da045260303d2e17fd5977ff366350d4fc4c274c96e6b65326e4640b2980ccf50b5ae6cf19b21b5f133587fed4c3f8530e6590f2c774e96cbbca373a2f7717046a8c5a704879c164f2bdaef636eada4bf92e98fefc2594b055b65c76f563b28ef139f9eef55fccb58fb9246b8ba9859252be4ff998f67dfbc7f69e84ee42b9c794eedcde0a5fd463b6aeec565bb25d8bcd751ef3ab8397322aadf46fadf198d5b5f7926b7dd1f23b6cc1ecb5985d4b9d85aeab75c8d69072c7a4f7bf6d83cdad76dd82764cf891a38d2f6da3b5c53a66abb5be4519bad8b6aec782593d5649dbe3272375efe898ffccdd5b5e2d7a7b3ec77c5c5d64eddca27db7e598bcb8d2787b450add611c93c5a7fc6243db683bb7159d1421058ef9f5be6525bb4cbe57b856305f7bdd2a47e956b5df570553b606a1658ef25f8fd4a5bc31d77acdccd66552aeeb1c7363d6c75abffbb6ff5577ada8f975404a1b93bd5b4ef28bd0c947dd1610480a1bd3a96dc7da5deaea5bcbae3159faa5abab43b6d643a6c6a4f23d77afcb5f20100854472998edbef195bf5cbc1daf0f4fc83909420a0a66337b1fd7efc8ee5b978a9aa90da17c318a4a8c69cc4badb3f07ebc4fdacb64dc29134a494163c2a6f13db7b5ad7fefc633267d77e9e5eb95b1f71845cd3cdf6aa49c604afec520ece5baa31933c27e1546bfbf449d464a1953cab7a474266373f03e1bc6a70b19cfdd73ecddabd439e9504831c17c8b42cb98a57c19b4718d94312695cbb66fffd15152c4e8079d4777a9376bedd252e58a08e55a44281769d1b1a253e50a1352c298b7d2f890e373f89cd206c6acf73d16df99b9551fdf124c1bfb398472c9dabeba1735bf3595765c94f2c5bcb46bb5ed31f37706e597d4981653db3b654254a490607e7bbb545efbce1d7f8b9abd98b0b98f8ed98b9197a4b18b096d7cf7f565b40e657c23985646f7ebb5b3b2bf322782e9fed8aaacdb7334763366500a17135afb5a7469efbb7dbb6e316bb32fb7e86acafecd3dee9d32a125458bf978c5f6b6ea153587a80faa99dde283942ca6b4ebc6c6ccdec7624a27ff5ff6bb96b2583f04d3dad8343ec6eff2a2ec57cc4b3dc65bdd5b2ba67dcddceb0b6d6cce2808e65749d9ad972ee60859fcc08ceeb55edefc70dd455f15335a76e372da9695d1462aa6fd1b7bfdbd4bc6159bf3c05c5fddde28977cfa4ea798cbc9f6be2a371799af26c5e40bbdad6cb557bdcfa3988cfdf76d4d4a68ad8d0cc594fdfe591bddebf8daedc0941046e61ddf3f31bd5bb456c2ea31dab7960d529c98b4b97a9f74b12dd920fb26a67fb3afd9ea647df651cbc4a4f7c64ad932b4ed2bd625e6aff82a7c67f0dfadafc47cd8d7c5b55e5749e5a349ccdaeffe9a6ccf58572466bd0cddda25abeb7ef58819af37a4f07a7cd9371a312f74166f84912bc7efe6c084544648977b5772f785454cf997ca7e777d8b123e11b331d82ecbe8167c27eb10335ec6cb6db4abd528e51362d2c824b35f8eb429a341ccba6a6449df2f270588d96fbfc9ead67bf82e6d3f982c5a16ffc17ab9716c6c93195fbfda587b8f615bafc9bc8cb1c6b7717df78f9cc974d7dd7ed99ef7952e3226f33a1aefa356b64be65f56dbbefb7bed3ab32553feba8d42cb0bd686b792e9eb32eccbba5b321fccbe8cc66eabc56897e345c9b491ef85b035b92a7b5c2799f5f1655e959b3f6cef899a5912f7efb446c7ec62ceb27b30d9b27f592f79edcb482f0fe6b7fdf6fb0fb646d9692493aedb76c9d56bad7b9fef60b68cf0b92574cb95ed2b994e37f4683cf5c38373790dc994ce99d25e16da66b737b4984e982cd79fcb6b3a98d13a06dbbd8ff0b6b79b83f9b841da2a64ed0c196c1ccc5ae9f3ba8f9964dada7b645e0817f3d5a873cd3eca6e1c99bedc63f746e8eddddc60f243ebba995f76ebe52b6a7e616e1ab1ffd62e2b2fb3f6abb35fbd07cccfcf007ace5d726383e8fac1c79a948b5d636b35e37bbbde7ffaacae5af945d5ad80ac8850aec58a0e7ccca4264c952b3f2713cf77d109c3456756f9d1290c28361b376e6a30237f5d4d52a730aef54dd4dca41b4626c30b6b74d6ddaecdbe0b193ea625a413860bb5a11f9d523a61b8e88040ab7571b3c87ccfbdbd0f36c9901b7b378accd8cd6573efe1e2d6cd3789ccc71894ef427ece256411ae2f6e1099d6d575e18bd6dd76e5ab874cf816df58dbbaf0baa4cf90099b859172b3152394f06930efb2ed46d7a2c7e6fed542267496ab6d155ee6dc5f96556e0899cf5e061f6ccfd1cad8a533985e19aceb59dac890fd653023ebf796bd2ea96d861e83d93c3a75d0f66b965e4943ece40437834c16a9a58d51b85875db5290e910f2a5907553781fbb37814c7fad7d5bd6fae5d62ebb81c18cd5bd5cd2c6f7cb41afeb8b8e159d85011d2f5c749ad4c3333a7da9348258dc00329b65f9cb256d8bfd63ff633ae7a28377b19496c5485f30bbbf996db735e982e9dcbdbd18f6d7e5a29373dcf831f9b548657c8e310bb96d1f13b6ab8b3ea5123e2f5bf998d7debae85b90b95ec76483e3668f79dd5fb3afde17a384cfddb8d1634a0a99d9adcf2dc9de2db9c64d1eb3c5589b310b2373ccadd6b1d2180a6ef098b22d87f5db365669848e191a372d986c1b7b7f1ffc7e4c3edfd1b102998c9b3b6665be55b2db4f63edf856388c1b3b66bb776e57577bad831c4b7053c77c52aeb79eb25c94b6942c98af3a7b665dca97ec36fb3a61b8e87c018140a0c521467dcf4fea9d1e8de944821b3a265dd23dc6ccfd655b8dbd39e67fe5089d3dd8dea14b13dcc831af8d5fe9bbfdb7aec5bc8c9b3826fc05ebbaf2c2fb6e5ccd63dcc03199c1c7a8b5efad60d6fa927d477ac370d16123b851c18c52be5dceb67233e37f63bedfe864c7e85df935b73824821b372663dc96d7aff236abdc4ea610b31b6eda98f1216cfecec2e8ec7ee33ff9a04a93710473c3c6b46eddf8be551bebf38e718dc9eea530b62bd97bcaee6ed498cf32c7fdcdbdd83f5b4ff995af25d38e4e182e3aa65b6a56dca460c6061bc21bdb7b5617a36174ace84c9d305c7440a0c5216620b841c19475518ff61f4776eedd34e66295a35ff658dbabeca26653aa8a1b3466adfeece56e0dbe6bdfcf98b79dac705106add36f7882f9de32d9e87b489d294b139221962833a6b3afa5b5ffd86ab4323a1239e678953157abb61b93defeb9cb54d46cba25e9e50b151d2f5c744cb70402a1483d3c239f1b32663774e9fc775d51f7706382f9dcadb5b967ef7609357033c6fcbe8e36ad30d2471b4651f3f78cd4b89f1b31e6758eedcd6165b9ac9a2f3db7296ec29890416f165f7cb69f8c4dd41ca1b801633aafeebe2bebb317ba5f51337ce2a6041f693769ad37ca44cd5f4c28fb69b5bf9ed2d8d68a9a4ba61109263bbbb491f53feae47351f3a74a7ee631276ebc9870c97731b67f7039bd1f4a3dea4f5d4ce9f8c5d56a4bcabd6e654bdc8c6056c75adef5ced6b8f8c5478960ae5a6bab94aecbcb71bc8b2571c3c574b0f9b28dbe6a2b83d12512375b4c47e35bdc98cbaf51728b9ab59891b218e1d386aebadb96c5641e57b5d2551a1b737f2c66959419d76770b97574c5236e4230e183fe625b1d1f7be9251fd315d331bd7e2b6cdba283578a9a7f1e4d0972e0c68ad916f3e66c6bcd4ddfad22104c762cdb73965df7f5dc7244dc7c6036ee67ed3ad72dd6db1eaa8ab97a41d6ebabbc2eca2aa9987ed9ed6e5c9b31b49e428d489f1a89d484527fc60373f9a2d1de0a5f95cec6db86b8996256c92a57e7cf643a4931bf9b6d1bab940fef73296a16a15c14c5e4e824b54bb62763b37f06c57cce3d4697c7d6ac5577a2e6c572a74cc80910371d98b0596b59b2aeebc25f9723eae689e9cb757bf597a5b132af123a313bc2161b7b8bde77ec748da90a08647f8d89b298ba69a2bae6b4df457ae1756fce9db96162f2c3e5acdd4b79b1d54fd4760b08040281408ecdb85962b6ebdad7f76ad155c9a4a87914fad00f0f4e824c3eb6532654c38d1233be8fbebc97a3d0b2f331494a6e9298cc42d6d1d26a59a4b25dd41c27b94162525aebbff5b559d4dc30b9396256a62d32646ecb63854cd4ec40201d2b3aa9d38b6acabb84e1020231db29137292c98d1193418e915dfb9efa3baea2e6c621c7b0de70c0fdba0a9957b7d7565bb3103745cc08639431562a2b6b28a3a8b9197243c4acde0c7e6c143e67ef16352f7eeb275b12f353ade858d1d1b1a263b7e858d1018140201609b919623683aebda68bf2bb6f9ba8d9969ec774baa1c720a7d67c7b9e3237424cd6ed597d6feb5e5976b909625ac8515207e1bf1a9dbaa8b967b801623685afd78a504ad9607ca26e59a2fc607a85d2e9afc79ca899b931e49d32a1196513189993bc1a3bc85a72741c19848ddefa4ed47c4aa14aa3c674c264d1e96245878c4e182e3aadc1d3cb23a26832fbbe686577635f6f75349e72168892c994efb4ef925156e7be2e267345bfee5dcb9c8d0cae7bc9acf4be7e0fbab45e25bf25f3b5afebd8da7fffb5be9209d7b3aeb976a1f5c18c8f2bc7d85e3adf0a2d2513b2fdc5cc5ebcf43db3934ccb1cf5c51cb95a16bf4a32a1b46cc5866fbdbeb7b507d3294bbeccda8bfd24ad3c98dfd7d76a8b55ba5ac61ac9ec5ef59b83f6aef81aac3b988f5d5c30d6e68f50be0ac96c4faebe74c94ba333577530ef62b05fb455be6c96d51ccc185937cb6437cbcd4a8a83595f948eefb77c642ee8b63bb206ed73155247e62f777e9b72d71ad7a33798bfa85bcdeae565ed95dec86c79dd93ef5de5c71add06f3bdd7c6ee5dd7d9ed923598abbbb6c8ce18746c2b2353bef8da72b5e372b77c91c9a2954cda75fde17b322a329f639145d9ecd95dcf6222937d59c991b295ccd71b91e9af1baee8d8fdda2efb9049dd9d17a4cc94d12a0d9915d26829f55b6d9551421acc162d7c7cd97e73950e1632a96df7416ff1427ef61232dd85d79737a42eb6d566305fab5ee97d8eb1678b3d194cb7fc36e9bd38c228d78bc16c57ed5ab0b116dfaadf06994cd7627fd798afebdf0499904afe4a7b59871e6d20f319b3cbdaacfb8590c260baabedb0420bf9b1372013caea5c59fd5b6bdbc7fe98f6befb929f8c8b6933f682495d5e17179556c2b6d705b3bd4568e582f0bdb3eac7a4de9036f7fdaed9d13e6665f53eebff0f57adcdc7ec25adad913df9d539c77bccd7fcf97e5c1c97e3c57acc6fd7ed7f9bd3b6aae7316d744cf282f6d516d95f3c66f36a97ed656d838e6fb760be6a1bbc145eb91a7df4de3165e37bbd295f6bdd5f6bc7ac954a69e57b2ea5bcd4d631d9c178edaaff185f6ece82e9e447c99c7d649636ea744c0b1d5cfdac7ad7b5a2cf311d6366b8bc51c6abad28c7e4d61cacbd5a5c6f7d5d71ccbeef55e71afcd71a850ec784d0e38a8b25bb1aa9952b988bc558e1620c7e758d970a666dab635bc9643fdb54be31ef92eed68dd2d1dbd68b6e4cf7cbf9b7cbd66396d5c5536b782cb4d84e9950294a1bd3b6da32d208ef57faadd998d0b687f47d63cc357ae11a93b9efbaf836ad2c99aa3163d707973306a97d90dd14cc76472333a3b6d5da0ea260321bfb5dadb0f66df5c134a6577859b6adee2edae84363aebef05db85c6d8be97b674cf9ef1633bfc5de37ba134cc9b032e5d6b8bee8cf9931297b5ca3acb7dd870d5b19f35dd977adafbe029315a8d48c8ae54251200e06410c84000c9c975909a001c314003038241a0e0663a1348f74ed071480034e66608e38282a12484381281c0a04811800601886410084411086614888d8299637b7d2027b78e4dedf876b74bb3db4b8235ce4a2572c0ec5c3b5b4544231818af692c732e7d8f19ce2c15bd7d50261928179e1fa6152f184b9b74e29389acb06eca10ea293a1aa23fc626ac6a190dd217e8dde89bcbaaf35981a3aa2950ce98dac0942cfb9547b9611d0f325d9431d598941674b87df12c4b28854aec006c8a9450f556f5a9ce872ac1a624a1f9648feb812c4f6eda0bd1ec650a53384d14212635569291419efa2146129e08201ae220cb8b324277a689b549afff33602681417072165fd52814dd0037ed68297eeb33c081386ca0255de5f8bbdf0e623aa32a4daf6b29122758e55b5a338f835cbdd58e8e452c6cd614ec63d8a51faddd5d4c6c427ae5b7b871a943d11352202e2a7ffc92f9111040fa114ac1dfc95af52fb77b9c248a343a0dd344032f4e616b85161d44af35bade24e88fb295a099e0fa9536003c5f82847e702ce6c034740f844f20086ede7c7a9cacdabbe94dec26e5c884f4c6d2ebd470513d1d21d3ad57e162a33f029ffe98937895c99edc8111337a6ae485db1cb382ed20b5567824093e33dc08c22aea77d1ec622b118123258369e735094af685fa03ea2f64bf4847e183152167a29e8d2282e17fd234ad5d78a23fdefd6c7f8b62e9e8f6a98c696a31572cbc36a907aefe10a21e995768ffae9340b555651ad49b3cc9a452b5a53196091a7c60d914f7ae9df308a58b9300c6f5588cbcac21cd036f9bc7d8ae258138103f04137999b0b253c3b95ef0d0d4bbd75d9e5a2347a43904f6521fb26d4562ba068353d769be9c4c7869810d5bc71cba5e5ebea2c1ea26bda410117ad9aa835c80c35030a5971d06d79c5723552d59950099b85824186b5f2399cccb9df2214cfb3c4b36ae24b20c550830267dbff3cf6ed3a76ee855829c0af75a85e7962add7d4dbc59974ababf690b6f2a45034ec0f48e58895dd1e6d0f8306aac8ffbc9562322a933c2d9fc26a978e12114911cc075183a06408fc4674989a80127f57bc4ed092fadf6f98280b375c0c3233776887c01c0d108ad92d4da41e4cd5b440a35635fdc018a1b3417dda828b70b34a685d3e9f0325c8301ba214448714fbb50544038ef309da1f3bfe0d2c285a4f826c33b40a95e066374a51e4ec02718aecb96080767d481801c408e138f69a59b7e91baf70d6a9dd11f8035f5af79fc5b637e9501560f21530c2f5ba721b875c1e64359789c084883291b43eb50dd64422655240555493f1b87de0cc47befb20ca4ed32c86138c4b07ae5fb840b8fbc20d8ad327ce21dc3c8bc3f13b2a288ad419f865dfda1c2f04754cb049a6bd3265c643be2763f54aa3314b8e53bdcdc5d2a4b7ac799c74835b4827261952938b31d61bc9594a3f1794b80f73fbdd85551c4f834a30b18ad1630923b3bbdf467676be39d3cf281cc44a9534b738355cfa2b0f18a6269d7a8f51c0580a986b814cabfdc063f7e646e1e8e242828e0a1e91ff5bddc7c7b2418d79e04025b1c0a92029b2e682198f814142a120890717a6c80bec39aa52d5fa46339f75199f342e6655632ca16730408459b5b6b2686516425d410402d58de8bfa6c820ba22564d0b3c75d75840c093d7006c914fee577f6c54a8c317b1b65f37cd33131a6ae0458b9636460ebf62af063da5146facf1ab12f0cbe80455dc8b60495fe10b7295837a6f9c687c512074d9f3f97222c9085488beadf1e1e2b63f647c8353832646ac739fca7aa51e883acb47bf2690e8c9a93cf1ec34a61cf42bd536984a57b200fc48ce88337fa20fd5d1980cd7bc03d3eaac6ebbc870099d70991244e9b5dd70b6e3e57ea244a40bd805d14dce094f390153909874746ce5160d5705bde6685bfcecf601c3ccb6980ad688e14dcf708ddcd0f22e054644ec0f76e06254a4a242fb166de4ac1e88fdedaae4d27cd0d5874c8f2ad781f2051d7597376b3ab414cabc40f82afd415b792e6648c046995206b886728b767dacd3783bebc537f44e7ee8709a94369fe33a133a9fc25377b459b69bd12b70528afdc2424db55594318340784c53fc310035e63871e57c1891134157c80e63977aa7e0054986b23082da49a545fbf4319f664728dcbdc89dd7d12f0f77d42320653ea7fb02a8a9863e5f6901c3175ea97600db51c8d80e58def1cb028d8d58d799541dd56000fc40b2c1d9e83ccba98123abee86d12ca77f622a0c6cab006b86553e0222e99dac87ebbe11727a06987dc64c591863f25fbd1074f8ae8e6ca3bee2450b38e33fd52a7d4258280582aca2c40493a7aa40446ac655647da15baa647e92ce6783a28f109a5fb9b6d0486113fc870510c60ad5003efd8c70d90f4abef03ac2a0f036a71b42dd7dd1a67214aa61145ee820e8c1d09828aa1504d52e884ae5076dbf95f653ce8617096d5fbae7905a713d832108b20842cb403eab0e1bc655c30c98ad6bc8251e91dc7924cca71da821ebd3b29f0e9291187366790f6781b109d4e17b51f2d69c6a1f2a1574a2bbfd39ed9fd2a98a7536a0f36334aaec81db682ccfc08b3d17c1a93dda73eaa64e9dc5778821285261c55c9eee559adc2a8b12af3cc941744a5cb95d9f8a23807ca8d6ec035e04508c5e030b12e0d140570b411c0a7cab820d3c282b7f00f81f0e3d4ce25b8822b3b8ad66117f1925ebb361acec09b1e2553d6a1848c9f83f6df1842e91a630fa4f78cc5ea10dedc8cf9dd0870faef2a34c4fa6666486c41d815c45e0f8fc621c92cb138b542f217de1b2e8dbe4d4957259cab92edd447c045137952cdd00922bb23084601036e8452291de25b0797ad1148ab7a29858306f251a59c781e8de026644eec3de34272ecb8310f75c15a43034ba8e781026750064506f18c71c94d03f11ab37b35bebe8cab04c12c1bf99433da3c09e11a2fc1dfe0581a911a2afe05b2cb966a5b5a380989b88b2900e93ca20d9c195c3488565e3c42e39ab63873112755c2933fc64256232a53db9de210e1e95daaf44f083428d89b744ec859c231357220472657539fe8f54594b2123703be2719879e9680edd653c66df2a31808f19aacacc2146f0bf61ecb4f030c4f9a62f684f80684ca1c204eaeb5f5406b385ce1ed4c4e2f0544e8260a00af63423e444c43565e61d08488ee20987064428e08737161af8c405db9ec3a3662f805209636081e14d24208e7e3b3a2ed1b0877dd7069a7aa039896362671ead7bb8d6717345ed5401263c58974b65693b54e6536debe7973cfb45a9c8e507d222a68ff4c412750901a19370ab65fd56b306f6917d578bb928b1f45a1958bc02d10a667e8e812efa16c8637cb54385f13ad41b653723d2fa4d4b023a87d2102863642f49d17a62f055c14d9de2b67a4c51f61c8c9f5d4a4314903e68424c767c9204c0ad7cff4a585842c12a20f89fb28b345ce68ef675c0c56a298bf7981ba2cf3f6b17b3001ac5132ea8094410352968d0b9ba714c7ae176eb0b256e54b67af58b3665ad71c3e4a9d232f6b856292c6a15e991077fc10247f68de442049658fe88e6971d6042e1fba85351d60e7c37b6113d93695689c848bf1ecaf30fc27851099daa217c70d78760b985d84f9a22343e460c07471336b6e9008d08e6713132f747bc1338141946c2d6540e46da54c75fa49258e53d8c380b37110bbf48d5359994eabff96ebc86e886d4638ade6fde5b9db0e763040dc1440acc3c0f5764918ab764c47f4c3e843fb469409b11ed065c13ade64a5e163a6dd689491322b35044a2eba8df236c64af954999b82ab987386abb36e17b7e8c888c1a7537414881305a477aa69e1ee4fb02a704af82ca2065500940065541a880c22051e12710544240bb6aa292104d58891d846f048998225c1294041e62945845d422a488a04415318ab01032896f046bc4884849f822a4115844adc4d888d59fa875428c3b9367ae01df0fe0e32c1bc1b7a0c1109bfc6956bab49aa0b20819f6ccb4ca6090fb1cc724fd11165c81547c1c415ccb51005c6eb3f2027eafd05743a3061b50abaae0bfd4c03c0abbcd0612c6f22c4b0c67ae99a438461d86179966688a899beeedd3eb0c92e824929e47d866894715ed44703a5296de4328e080e56a06bec4a1468c6aba872e4720885704231b7be38722e8cda0636a3493cbfcd34f99ae47b00a125200bd5795be50caeb29cba860dc9a9457cadfe15b879ae0149880f28c02d47af74699f27934f8348c83eba6168752225a7d254b7772c0a1334114b6aa2097858279518eb8ca661723550b28536cb51a529c2725b4164a25a11965159ade3592f9b895130d7373ec9b13389a704bbccb93a443e624f45025c3752cf6c6de1e18665596180aeb91d00ccf0287ca205e32f01bdde80ab30c927889ca7306716793a2d4b1ed56c9a54fc2e4102ef4513a7c1cd2b314551cc08d5a257badf2879af1c00f25ce51beb769f182918db6d75c556450e65a386ba169dcf054d0c85090ff4458ad8db023b9a53c909b8ea5f15b1c24a796a682095ac5911603bd57eae65cea52b1a4832109fbdcb537bf6e13508ecd7468ba3de1975947f0754bb85f9a96966886d028834c043120e428b511846be2b20b61467a1ff997151c9d2e8b9e42de011899dfe1afc380fdb96240ab5c96127c52848726511c49e7b881282480faa10a00a1cae2e6b08fa671a4d0cae058ddd9830d72593f12d34898769a63c0ed0f347683578fa3e0d41b3d8150edf06a835002820ec0198af417199307bc991107f51f3fcca7fb930b8cb20d5f78936727fb19c0c0a82802698a2fefc447c0f1e01ce38a03156440a62c61cc6f3adead54579ea94755564741578b04fbc009a46ebda2e5ba88e838e3f7bba507ece62aa5e5038150a1cadd4388d2a545db3741dc0f1d71726050a992c02807df9a88769dee0019a362d0686215b1165b222f3ac0b2580389a8274663b0684575b01a9b903fba61f9984aa112150f0154a369a48b54cc07a55110daa33aac8f8dc81f5db14ccc767149d70c27299a46ba988a7c501a0361eecd6e2de947d00f819109d5c73ab4479dd81ecb22367109ca41fe135f726a37c9af4e1b778328ce1e992a76d3da5b8982eb2162ff6490b30d8272e9baf85ad17d9c32063b5b043ce44c725b44df9b25a3f4aa28f54051175738040e0ecb1f39dbd4773bab3b438cb7062a0a3e4e6c7cab501572341c023ac083bb7d7e4ae40a670580e69c4df9fbbd98a0838ac7ef4da942948f188103616d7b85a5347a4825b1a24b73794303d00d783adbdf8732476e8bc1078fed6d110fd5b6233e4e02a639b6a9bc617a8de07f1fd464d6ef454619a05be572c8e7ee0c539e0665d055ce0d655c51b98f5f95ec85b00ceab32cac808c2e9d203dc525899ca46c0fd251bbc0d8f2736e4f4f24a4f53efb8e20d9920655966a3f8a75dd5a004333ce237639ddb4ab6447b8b189bd0f4fded19a49d348c33eab9918e2665dd0fa3c30e52f289957373a0695205817b480de59408f16480f5f18dfaa54259b0743bd61e15e5ab8572cecb5057af582eb75651851a747b7400460d7cbb08b3f825ca7ce629b370a0b34fcd331359777c0b3e3bfe67065ed027fc009ca758bddb9b761b4a9f48f8ed6190c2967050c96044f945b3ca29e9c4b7a1e4ffa2b6875249d34292105247b32ab6a582af121690a44ed898eecaf3697ae68f47c76a501b91236144d50249f9a66426ace1304ba694d6f492fbda1e945f7528c1b459160e44614c88eda514eb22093d42a539e473e410191552ba7e2727f910eecb9bcf4afb2b3393dae77c3eeb0c1281ff922d6b7e825d884d546cfe6634b056ba4f9b34e30e53685767f66b81f574220c5a94908c9014573ccc82fcf8c65b63fc92e1c8b9174e657c5bc024fab99835933a1019f76c4944bc28bb98fdfb93287277750c79f891ea1b13fe526ff1047f8699d32473b0a26efc418c07139a9a7ee58479f699e52a33f6a933f45127872a534a9960c482fb5a092a449cad47c4ba4cf97d1518ec62536676a21a6f56792f1a2ca0cfe1a3b396763e928e74234a1669b4bf514cff9cbe9da9eccb37f895d3e8731e1a831a5985acfcde57a0d4ffab374692fcfc33781659b4be3f4ce95cbe75a9c3cb3a5149557c34141b3e1a5fe94ceede569ed51b2b473292ebfc372c25c0b4b0fb5a09bcce3da9d2c67e7525c7e87e584b1164c8f94cba1e535db9cd4b378c95f4e877b425a7b4c59ea9c8bcbef5a39396603d3655e0ee784b8769796d339bbe045c28b5c5f097b6a47a8b4a1d2a422f5948fe213a07370a9bb3a67321da995ca242d29a0bc148300c965a5f62a9fc9e971cd5f49a77b2a2fff8becc09c8b4bef6a399bd3e3da5959d22045ca93a209612f4178e2507d8226408afd517675aec5f1bbb4f200cfb1f7b3fca51f77ef4631389802fd652c660cda4079a9cb2ffd0ec62cd0bcace54b9de55d256320d9e2a8374bd9f6f234dc974aa7c60ec9b6906b15eb38a49c6bfac8fa90599ab4daf7b0d2f0f292c38dbec92111d5d2238be9a24fae9c1f5ca8848bbc44cabcd57b39bc6c34d66d7d27f8e8195df1b9c72851a7465ed82175391b33244db3e402690b35243a4a44f33020b5ab55d89b51f7f9b1f166c156b51ca053eeeeed584b1152112fbc20c4566b5b80817d0ff543a0a1713124178246fc6f47f8080a1fb56ee839364ebab89471d9d5646e2dbc1e28ee7e785c4dfc1a266db8823db55a4dd71a84897f16db8e32b90cc1d4c8b82e87d0e047d24cfb6e091847fa97f3a2ef574cd0077d6ba8b3e17e747533781e5ec914b86acbf00453f7a61b68fe32bff6b9b58076328902525f56479493df6ee0a18d6ff66f688152544c32ca9804185a6eeb1fd074d3257f78958ba4f49ef057eb9f5aefe6b2af493f3028e65ec82d003362d1673e31c451e5e864027ebfbb32c6cbf6f3d988806c6468dd43c3f88b50964e542b8bb9faf1890be2f64fb61155ff508b88e706d6ba6f205a122033991f2cf6c440bcd6f02dc98925ce27466b40a6fbae68fb8c7fb07eb84adb9787ffd6276fd1f51800a1ae778a5a3cf64fa0495a0112537d2014091625293e229fafb4d25533b16987a09a0cd3f7d4fe6e73bc1be8bb80ac00fb76f058d4f68042b9ec553d6be6824fd5ad951374d348634ce5020904f0dd4e92b5c959f04df2db751d5c00c401fd2c2740637309d98ba57ed9487dbcfd428eae91412d2736195c2c73fe01a1113a2aef680456b7a932011d04052db2b004dccc2cfd13ff327339deaf3a7a7f2fd1302500ce4880d0883c67211c690f40c52ccc69ad44761cd0f997295943c2aecc3b1e29b7a114bcaa52a0f7eb451a30d849e85a4fef901021e07f0c8b03c66505746cadd6f046de81a60adf1cf76a752b000364bf2db98968da1a454e788e684cf1f98efea3c02ee017946a26a2951245143946012b68c0d2574c45d171ca69af4eb61bb1855a059213a5e9ca2c8bb4a89f1b983edddd0b3b844cfb2bcb6f918a1f835ac8ef973bec4a07998ee50fb7c7a41b7c19f8812aa516174f7b341e18609e630820ca34558c2a0818856924394ba40943202568be10d6227b8a1c1865a0efeb13e16481db4fc4f5006fb0b94be92884f99a2fb4fe019d22189caed25bc6fb4abf33cf83586fe91f61f6bf115ef293a861a22491324582200949790dec74ff10bf79d5a662fe97350aeb48acb20fe0ba59652b7640bc498aec0c7df5ae9f8d55a23af7ea81ac542f2c6e4464a817e479e9e70398ce4986f50d314e45c135868dda753a1959516c6223b98dde64ce1f4c907e714b0474f1e47cc18005b9c96f9d9681c6a65a643e9fe7115ed0405d44a3b01900d349390f8a22e7e341eb30774a2d599db78ea5d27b3af19ad3a9a2f87a50c9ed7cfa8eec94cca8dfcdd702524ddd55c0363d78422fd528810ba4c71ac91d8a9a876620905d8e8ba9e0f3bb1fa37c6408c08584455ecc87a8fc31b75090a4b6a7e8426c72cf8d55d0f87f1e100974c0eaca43368814f86051b7d6cd9e463f433a071541c15f27290066ef59d84624b7896ad043d6375f92402b0d08813bd5ec460c4004843b0f88119829554452f721de7526cee928e9099b030c581c71cd37a11cb1aa11a71f740a7405db19d2902153603217edc23c0a64244c313f42b652850299c43ea453f4203668ee8ca3c3bc0449b2341aca0f3dd0d196f9e63f426be74289741bd454cee7d025de9a80efce932542606beb7c1ad313a2661c30679b418a5cc4ecccc69fcb884a80be11649aaa0233a608d2b14207b0e73875183b033c05bd450bbfc5aa6c3d7bdf7af91f91725d3d95d50710216591828db5545d85a77246fc38f5b3aeee41eae679a1eec193adacd372fe2443a856ca267ecf9425c66e4382ea0c216a6642e435c9df639eea23aa102c655f1aa93c10ea3c2988117eaa3eaa539420f60670f2df4999ffbcf57f8908ea803940002b66bb6a82ae844e496b1e25a3532d0e4813a14337cc7613b4c54f4e64322a8ca0014b0d8c19dce859380917036048de5f55dbfe7b3e25e394326aa73f39ae97c12d5460a8e265ce5a59be23a3682bad30f858c60a983aa65f14103b3b774dece1646a3d43a8a742fa096e95326db68d60fee719b5e2bc0887e4210aea9f267f9f168935f48a97ba8c35189bc56f1553125136aaa603dd837d925237ce3f3339f04814d41a3a772d8a4fb89eb2e84337d531600f2ba57a6ab13f4e5079e6e470f15827412089738684cb028a04f67b89d12cc2f13186aa06244d88185fe0681f590e20e38e9f848e14b0826563df04206c8852a4ba6e716d1420583e1e1f487e4dfa343e8821ae57f143f1b6075204e7d2e99df31b7c52baed4096c0393afbe67c835f94ae5d5025091e71f810c5ade745f9d1b14d3826d599f2f986c620fc21fa4d161e79441a4ccbdbc39bfb43e722bb6efc91c2b9dbd6487be10e7e10047770d1f3a335d4e05821946353b7c89798f7446779a4ef9925cb235437ebc188e96715d13376eb3297e96781377a7fb517be6b76814701e48c1b7ee2c5dc0a37007907bf5607ed706e7ed2461dcdccf24087dfa933ed195aa5636fbe9595bcdced4b75fb0a237e4e62eefb70e23b6dbe7f0766b93551fc25edc00437c4a65a3979ad398ae5ab4daae40ee5f7a33abb920b74bb6ee449711e8dd9371f13362a9ca3557374f2e867852ea7bab479f996134fcca7351cc34ecad90ed43942f7190cda8bdc10419be39906efe29ec5682fe734cf75fb7d065662e79bb36afcd75300035681b79bc58d171d96f29bac42bc924d9c1ddc04e6dd81dd527ad5439f051ad06335d1e39925f15f8c387d9a5969000f5d6bb08ba65d3db4f27f18f27853cdd07e9dc8f6a0d28341e558928d832d6d7c94cb03d0080625a1673d3462404e2a5ab7a10eccdececdb36c1b1f7146463b2a2390ce8f147d07cac61b7f5e0feaf7f9e219f4ad5f337a3309556761d343c8207fc03a7ad76185721da64703004630fcb3107debc8b5918c4928c539b4b285e7c8700a6f9bbec94cd638af952025f804c92bb38b887afbec6e71a84c57783d8b6b72c4f13ec7edb82fc25fbc451f576ab77ca033c124c7f776f35b2c39191ee1e6c0ad40a4c0b6c08ad9de6e2b33307bc61b302f351b3824f9131df4f3e23839a44af2efe9b2dea7747c08bf95c913ad27537fa63b6f6a40c7a9f8d32ae07891ed931f92f4c47966e2902c4f7775a53a26e9e178a6d242bfb757f4527d11c4d3d4b36daef572af17bfd54167bc11d7ff3db2b31e06dae18f9698985ae5a765cba0759afdeae6466cf5ceb90f3c0bca042004d0aa9272344039ed5a15ef26a5ff2efbcbb2a6b4dc04f93b90155a37658c3578172c569d3f83f74b103351870976532a1e41b542b8a782b5ffaf4d15bd8adb9ab26a387ee9cbbf78790778395965e4701623e5483d3459732ff1c203ae7983886d205d8c814fcc34c839377858c49df8ca58c3a328d5ab73a30b27242c858dde7e186bcfdd646403895f657120eab3be38605039730ad9ab88df329d5cbe8e9cc54035ff454906c0cd1e20bc3c151a10bfeab64f350d5e9222cf6afcd6fca3730b13273ea2bb52dfce016d64a9d5fdbce3a04a26dedea07a34da7b97cde5735603b46fb195733e617bf4fa2d9ec0fbb6d3028c72a38912d84b420fd062770bd508a8d0fd7bb50b150d68db9b84706b2b9f6db882f0e67847d6c9997cbb6e2abd6956d25e1bf9489e9a6588ee9af771224cb447e7673ecfa314179a8d6f17326515684a38231449d827af6e544ed3892e8469d0126bde6fc367e38584997627b774556efab2036064daaf12bf5907d73fcb064e138db2f3a82cad950810bb9a8064ec679dbe414364a9ccf6a8436e1f15d9a020c2203db261c4bb5e665566b5be1910ad48c945a0f75e4c1c3640aa1424a88308806a6e1938a758eb43310bceb5eec048a34ec5f8b3e2a2bb11f4fc60282c467dc41b3183a62a4c3d61001ab63f52087740459e7fdf9e542f2d321519f56b4d551a33706972569732f1e32cdd42d444243929db4f261b3dec54d8f6502ecd420e083391178e8001018b1e9b4f431c32e02dfbe21e2c1a2313cbd43a6d5890ed00d00030cd4af153f8830f231aa4108cd46188b31d0859155f5480dc4dbc54753395b4bb3d60adf40144c126d8948f048d3a7d4069675586158b9d8213e32c63cea7ebe8fbe83ff643278fdf3130a1194a10e42161e63161f043c1d6c602a8799ec0e03a817e7e8801b9b92e642da8ea8dc85ac917c7b37ae6b59a55e302b05a38f09ee83f02d64712aefd100b63c72755dd731e07b941d8803cf66e30fef884b47b60fa054818c030e80d809a0e6e25fcd7245d69a91d3b98df65b3a1d09f495c3a545eba51dad2cd72e9dc36c1dccf40f954ef4dbb63ca5f1a978f51dbb3cede5b40869c43c596af9508535c85ea425ad32b115e17191b5e44a94a80baa62f0111836046a3bfa7eb168d6cef8d74b2f717b1d7ad3a1bf7fd73d89e19bb396e5955196b75015342dcdff75c7665ad98bd0e9eb96b7506da3240be35df538975534d3a8ec8d1cf93b7ca47e5965a2d205a0bb32700645ec691867ade075c1add3fa313746246646daf6d6f7dc01a47727900e851aaf6eb68f745d2ffece927f5b663f9f2817c06014b1bb248189938be115a8c3ef17dcfdf520874feab420df29e700e1ab83e5667fd8418c91cc031d7e0e8bc1bb512e23d559a0f568c328849eec5835ba0f2579643185cb8ef21ffc9f664721dbe0b2edf2e179fb0460a50597826d7034fce66204e8fd79468e697ab2f9720a1a79684f582e62fb3d5c0c5dc7dfe4074b6a458f97bcd1664cc904dc81021c858e2248bd49061a87cdc68fcb9981cba3b03c66ae3b7a0295ba465c8d8f445dc7d242473c1784238abdb1f1fc32893748b08c3a9887839d60514c78aa19b28826b044d813c408623d1dc6e9068f10ec7955682e0ca33844821001b00c5d0ad788269fdfcee2d8549d0966cd3ba19bb673e201ff203d61a56c132717e3fae6c96f9f366188a9cfcabddd49e2b06bac149d9631b049ad3aeea8da359e4c97bdcfcd6935b5b2ad82a0dcd0f5a2b19f1af5f60e0e742b3e0058f17c69f95c1b827386d77f73224b3d48e4e260f4f1a794559fd6c8624a07be0c467cef467377aed776254821535856f91d0d628fdb7d44b98abcbb49ca1ea67565c88bc84598c37e4a513785dc48333a20e4ed21849bb3d3d6edc90d8f6cb05654513a04fcd6384b1e75f330c52aa3e37bc631be3f1367586f7bd675eec9b00a11b7e63394915687e0b9d94d4505ef52430c0458cd69aa7bf04dc036efd11d9de13728218af394e20201f05171bfd72fa3c25067493b9ae581f1d0c5861b8083310df3aec067ec52a99074c69fa35643d65b72ec7e836e849347deb4fc70de6f1e6387d39dc5707798f9fe34184fdf97f893d9d5b19ec5461a60c531ca0293a44510ce9a8ad0fcc5c3076757991579d9464a06a63b29c2608e69564f79777e6b1fbebc93c27bc1f4c50082038a529b0daa860a9932bb87c14f82b6495cc4ad5c31578dc2117350622547f49e396caa59b00301fe9d04a98cd165fe542be27977d4bafdf818df98e93f8151de1a2cb7d2183d27f7390545e9716ff5e3ddbb3ad0bd72e29e61fbc04827d46c61bc9f8716687c177cd20dc8d12265ed0b3f36bc9497778960c3497956376d977dc0ba16927008c7d74d02e8dcb36336722aef55e7a109a152452ffa657960c6bd0449420228ff47a7932cb3a5f4d75f182478aff14cfa5c2a0d0c970995828d568faf2a1ab85fccaad34151ca06b0195bb627faca0806afbc0752849854a48214a1ad033753947b437f1e12fa3bb888271694d59145e62d8b9108ac5c1d64a59e5d781461b7215bfccc5705a96962a8a3281aa930b840ffec3626cc90e6208d882257f2added717c9a874984cf4dffd65b4e70a6c01bd3257963323283c10f3202b164889a31d4adaa269d84f3d9404f55e96c6e922f89e100e5b3b9fcd6ac884b123fc2ec89c23ac4d27a36fab8c4b378a35f95e5a38034fa949c1f332a73dccc1235c6eea0c4a6201516318a5bde845887fae4767b4427d22d971cd5399f7fba2fb8c554e15e85b2458abae95d25f76c056f37381b9295b7f196b01ed1de14518bfacab57b25cfe34d6f09ceb13ab8bfbcb0c954c35d86724ba7dfef277c254dbc55a05bb2b5c7f582be12e95e9571fbd2fab27ba40ee22d2f19d2b530bfb9ded0e9b4ebf27009da96e3b9bdba6d7d6de66793175bbdaea5688d6b3530514bb856f745f335046a85645228ce721afd923acd20bbe0797a4bb31ca37fa48e59a82e38afbed28ce3d81fc9710eaa03d7a7af98e934f623799a8374c2f3e91567398d7e499d66905df03cbda5590e8e53ff9224457fa0313f0739ac6b00ce50bfa4fe205976678773d2c428aba87721647485e0fdcb6538464ff04926bce6f5877294e18d5268eac1e51ace3faea13ac43377388bd8b43f3bf2f33ad62f3d733dac394750b61638b16695261d5fe427d9df4b5afbe4c481b742cee8bc30fc8437416c2d08e92425f9dc60eeb011fb7b585cf5fdabbf3662994f52b2ce0cd100ffd742664344c962a867f4f3d09e128e91d68b04ffbc54cab0fe3b562cac470b6ca9b107f429e86ffbb2333eed41f0460e5fdb512025e310b28930b3881ec575ad975ca6bc0ee9f34f5b7ef46eeb590c9d5a99a331fa9b02cf229a4fe775134f9a2ad0330d1b2ccba0695652fe29a01203e3a20698d87276b93f0771f92838413bd301a9e515f20c0b914951786761beecaf29528aa1d8241a824c47003cd08b9bcd838184745688b08de3b91f81de903b95f4071652b3a42260ce14f816807e1f367ded033d37de60d63ebf283d93a57a80ce95114410a906111ddb4ce2a470004969e82879d49f179b353559637c389869032dd59b6ca8ebcb790144224d5727e116243b3ca730f4ba44704d4d357143dce916f6c6aa559d745f9f8086be3302e93bada21716af83859626173f3b265d6072faeedbb72c14fe6fd1c026adbcfc76138ba9c841f27c636ecab6ca184eeda4991cf38c99b9c1e6eeca04072a71b680b16df19d40a24b4c99815d8aa087a9ae168091e69fb774c28e860d535e77eb6786629da2b28d571d227b9eaf447e079ea74728c0be85eaf6e1217b346b13bfe75e9df81d5d63edf3715d20d5fe4c46f9d3947b0586ddbfca5c3a91d1687f6293c80655e8b47166be3cb94205635d76ba4687fc385c7ed64b8dc93cdc49070c3fbb5679f4c549d1cb9a9bc6fbf2726d90b23fae6625f4e2f16ac82b094a24bb4921d3595bf13f89c877729261ce7d06780c81c8f6ce67affb5ca834b9a03a51787f81e764b92689da13e8ca20e420b1cb70b9398225b86971f1d72a384fccca1d895011366d81fdc713f1096890bf0d837cc730ff908b9729fbe5d3d01dd3abf5a702df58d1c74b7eaa79e9d6cc55141a83d305702a6ddf080703a7b49b6397832c3c23959605146e27a2bf8b0d7085f05cd6fa8cc4b57304355d52d4b21290ebbb6b91e3d668c7425ecca02355b9bfcbc4ba10ef01b767d87b790b8feeda3f2334a9d188e15c7a10df5aada37d9bd4c2e69ff5d0f2679b2405b1e0198518d98af24720b80a6ae00f8ef9f929d177f6b507627b07dc36fef300858d7bbff3bbd9e7ed2375bb81e16028ff0cca56f42ca73637ef885ecaa3c0fca9e99a3736634fa5df0932e2e81cd7a097733ded755da6d5b8a417429ee75efc02d51cf6a0de944f6d3a62da26b27e8fdd2096ef8b45b5d84734be6f7f81d41ebc025af23ef4233be11d61f9743f12abdbef33d585f331597eda2109c7fec4f7fe21ebbfe5533b0e0440efd5033c19a62aefcb7a13ac23d4be3ed04a37af7aa34626a1f7d559e08738ba352484a528df2902f531a2452eea983994defa5b28a434ce375f8823797b2da74e47aa4e342045debab2e7974e3e235c336fb7d97bfbc80e7cff4dad89639b5f73f348f0661037f2c45225298339fda2ede6a80d7c105ec8c7c4847c1e630207222d2d6ef389b3f278c09b91adfe1ca01d23a0c1f8e8dcf97427b905d0c43e999ef3b49925060b943770ec330e2d9c77d15ea0801c71d0901600fb147aac33ac97446ca6f646542ae6f07b8700eeba9652e4efbf3d876e205f2de7d9e8f78dba48044372ea06937027aef78aebe92c26a3a792891fd04f10c771a84efabcfebb418a8a39f341ac840689923adaa6bdf547b6cb5903d27f2000528b694ac3f81cd94b773e79c27d9f083f471cbe815cbf6bb27bf93fd31c0f9d6ae25e4d17c653022a771d3078f6b5bb8b2b8ca725a36537af616af11b06523a4c39e0971dc85e06cf6aa235ad883b6d2825890d8087f01aa53cae166f29805bd071e4c4e762f39e4f1eef280682c5208a75d127d470d2e6fbe8ae33d22ef763a18d85e36be0f42c61b66cd32ac21f06fc06ac495bbcc5939766bf56bbb4c2016d06a4d69f1b77d4534e15af56fbc39cbc017af6d7170850b2f58db8bde322e0226699e359ec180fe2f9390597bdfd54659716fba066d09a827c6b17a8680d6e55c0fd295e846f2e30f78bffa59ca582857a89e6b301e893bfda3b0b757ce6e43053458f89ab1b023c969e5bf469b642fbc72a0c3c9702c1df64cdbd3bb91ba2b243557ac27e7b4b6533a1bcbf489df62c4452bfb66aba0ec242becf4375dfe6cae7faab15bdf52e4e4b5abb96333a9378469e47b0cc30ccb9b94b2e60e73d95438278c373f9f1b43141ce0006bc673f6392a7d3edc131c9c1e3f9718b0cc26a200ae005b4face43402c9365db98fe1378a25b0cb0fa954c962760db87dbb3e91b882b0e84fa719d5fca6d5a732e275f0919e934c41909da58dcff0430c77a18de544d6503cb78b06ec58d6c0a06a2f90f9c371f7bc65ca90e38140d7bc5d0645ce0ee8e6aaadae1cede5f2f5cd1901193987a0dd7933d875d685ba05a3f08df40481e945027dc0bb10baec1ee0726228cbdeb640968d68623488df116ccab198dbbe810cd637fc9df9cd7d97806a8a5196495749651932e1e4809332e402e8ede6ffa45fb752b54be3685b329ed2a0abedf9aaad29fd5b488d837de830a5a314c24de2cb8d5bcab760509cfdcb8beacba20ac3cb45403f8d50eedd1ff1f9a55d68f1fd2c6ef1f9a91bbfe284970bc2771bb5f7700245b42f8fef6f91b366d46e977a40fcf07d0f95f5cde67539c261ed1e609628f3b711e27eb9a80e3e44a094deb37264db34c9043654e6ae7e0bdbb762e74e52f9b6b28cecc3c6e9bcde2a1745b281bc2068d336d0d46d157c63d84db12cd06877e1ba8a2ff717658d4edce6926c6d516b7cff3c600ecaef2a064f44fd2b285c1f4040beea35e6b1b824d74f9da83d181e27acb2bcfde1bd26dbe2f12282cac8e32d5db553e777af177ed78408b3feab63d2a3de824b0a6a801ce309f94c1f8a72ded9a4ef60cc5b428cf84ecf8fc5652c5145bd18b01ff95e876cee2d7bc99107e33fec0395a03c1b285a6af342526d9eb281c183a97a3629728ed8294ce68579ee629f3390ad6f92b7554b3194acf072b6e4f66b39082428e47e10c8f0411fcde9680869e7ac3a2995396a3ab698b63b8a21444cdf2cd8d0abbdd07e10a2fd8559aaebe93bbbd65aa33137809ca9cb9b4824b9a3cb4b51aa24097768aa62b21262d93f5ad03410d2b4f5b126ed5bae4bd52a7da09685a7817201c5eb75b8a9487e02ac526ad9a6de9677bf2d034131676777bd6de7cc16970e918e47249f3d6d8f9ca9cfa457e645fe99e4dedce8d728a0f76aa437a56e03483ad5e943250230736ca6ae06b1f0025183bb80f3fe48da0eac25a13d12b0786eb94324269c5d39c0345f153f298843a090be5e4955f82b4b115fac469cd93438a513de16a7dc345d6a061b09ab9e9c5d430b312d0fa2e0c34d4305347d6c015e531be7eff71fdf5033a3015741c7db7d51aa34ab4e0ed0ef4934b675bd52db0ffc4892260c4964f315e54bbf0d2e5e711edf9d7f501d3f2830d75817e31cfbf46129912130dafb1e68583cb49567177314640760a0f98490b8cf83b6e88d89507f2c6d2ea4c8d2c98b5c023a4a844529315406a70d08ee001e201e8ab6cda96aa2df54ba61b2e0d5f81fc9f4eb22158932c8381ce96f2e319e32060b3ed25c817542ddc59e91d07d3c4437dec450b0233c2d6bad17defd0b233332ca884ab05c15173cdee53251832d86120e4997781164862f79fdcf4771c84e389073836a14b03b92d9c2ebaeabe2fcba31b6f083a97b8c5f60b5d631fce116450afc542aca6b5e762a98299de01a5db0c27d043ae30c19f7429f8993122af94f69d71676700bb4df6c9e4b2a373ce61742a3d4df606d4df3911d8e0614621ecc300d3bbb7726602da866521e72ed354caa9843a0013a3b04c98ab70061ec5718913603d44b33173d6c8155cb251f8a1db3b8eba17c71890c886dcb06f3413b0207a12814759a43fc4d67ebb498463ad2def00968aa0b90082fbe7b8b9b77fec344e859a47216fda3f1bbf3760aa8bb7f78933268801a411dd509dbb0c7f5661617fd37798e3e734d2b35264f870ada57e44bd6973d56d1587dd629e29c68bb9ffd668a5c3477fdb4d301abbce8cb4f27f691adf232394de18467c19d7e4888b91bd90ff2af40d53e7aa3e135990081477426ddfa3f20dbc6b028e3c44f70a3a60b0a6d001303333333333333333333375d46fbdad7db190955292e5e18daf419a1dcd24539229a524c64c7cf845daf01769c39d99b584d09c3c100b120bcf0a2851cb2baf768d900de792313d759b637aafe1ac57b14932f1948c41af868312a5264f1c35d2459f86f329592a5de65eb3708286938a99ed8f331c4ded990ef793a6c3ec0f339cbd04d5a0ceabf32f2ec3416cd275ba6499fbd09041ef7459d10aab6821e2ee4e5e1354ab1cf818c359a4d7da98cc7f92580cc74c4ab41bff1edd2786e17c31fa6ec2f6b254309c46f69cd810226c63f7f185935f9f5df00cfafefc91353b2f1c46654defa7a6798547d674887c74e1709b545e0ab296044d47d6ceee900bc798aff1b6d35b82f2d0f0b185932443099ad7cb6ab4ad8553be7c3319d4eef2b859385a8fc61b954933495e202404081f58384893b382bab022b2095cc7158e25bdd22f263935af38b286036921d25bf89e0f2b9c654b92e992fe4c4989dbb89144aa50bd7a251d16542229a8f167a518e0830ac7395f53a1d71029813b8ea5c0c7144e299458162ade4b581933f02185f3658966cc195b634245021f51380969311e171be386190ae7dc52d320c3f629d11cc116376c980a7c3ce1181783a991b33be170621293709bb4be7235b27687818f269c4acaebb45159926836b2960267c2316c935fd2269afa19818f259c6a9477e909325a85587c28e1702ac57d13b3040b5ac0cefb3e9270d624a709a36269d0ef23e170319fea159f3d25df473897c6d5a4c9af94f28c118ea6a4933d2c2899f4453867bc6eb509a164ee9708072dcb27b5c62a4b490ee11433252546eb26845392e14466464d0f6dedcae0230807f9ae69d5d25bcb3240385e4cd1a92aa6cdb6fdc161e3766c8abac507072d776246bfeda307471593d74eb7aa0525fec1839356fef22a33212bdf1d3e82b58f1d9c9226c984ce1335fa24710b3e747072bbd4b629b5b5db9e83636d9f168d3f52776d1c9c566f4fbe24d359d6c90d4e77faf664b53e366d6c701294523e57974bf825c238f8a8c149d2202c6e7f9e33f8a0c1b12d25290861216492c7e46306e7d294f164f0b56c5f195943816f21b2c58d3404d49941012c66e80b3e64705251b4499bf24eb8e53f627092c418755d2dda76df1f30388999b4063d222bbca55f718a2529a5c426b9e260ca4b90b271f5334e5a71f81a93f469d2a87e29ac38c99ad484122728694c721595182fbba225a9e270bd6a1bd46249e162a93868ca56db6bb25d3a0b15c7bbd3dfb7909fe25c42cf29318fba50d11427259cd053bdd8a45b97e2643a6b72283925fa894871fe8cd31663cca429c9284e4aec4d4a5b10a5945b44710c279c2c3d96a4c84986e2206fcf6773cefc251d284e921c4ad854259bfa4987c7278e494dcc551725c3463670f40d1ade372410e2e189b3b5aea76528717ebe4e1cc346d125e5f6c6f4958807274e6a61cbe4d3325153b989839abe98fa7343ae22366c201cde2a810e88a4c04313c7cb1eb779514d9b5847d6b4701b478b1b3916901cc7054242b4701b2e810e88208f4c9c4fdb9510272ebd241b03332880c5043c30917ccd91971b4dd6bb056ec0e312d77fa9322bb5a4ed222933397c478db4c441fa6f08a5338556897b5462d93ea537d49496886c71232464068d193466d098711812328302397c8703520d1b3a0c8ec28312fe8e98c9c798863a32c70e1ced3189d36f5e8958d1384a2549e29c7d2363e613b46c7a366ca06410b0c5024242424274e4c0231210f080c4f162e97f7793471c47062f4996d5dd927c479c772d7d656f7cb1ad112765d25a10a1c4117135234e526532dd2679cc756511c7fcaa6462c8137f9398220ebb29ead24af4ddb43c12710c27af5d4d696d93a30722ce226342672452b2e0718883f68d292ce79892ea8eac215ec1c310c75d8df937fcf2c5e5377678d205d282a7e05188838feb858e293bed087132c94dd69bca95e4268dacd90de29829ffe6bf4ca1f4565c070f419cc57b839e16656206111d3c0271dedecb5aa566797703e2a04a082d225fd36c4ffe703c4b7dba339ca0ba591c3cfc70ee7a0d25f7ce6fdcb00fa73e498aa3e1a265ee150d1e7c3887daf6bc2497c624ad65227b38a8f28add25af9cdc4de2a18783ce12fc2b7e9ad6b58cacb125c1230fa7ab56bd686da1f7e491351d060f07256ecc7b0ddd65aab4c3191012727687f3b68685b313ce4d4a164aa00322012884871dce7e629416a14a8c66721629d3c1e75187539233a579e934b2668603e14187a3958dde06216c4f0cb31e3ce670fe5d13734aba5c9aa99b83871c4efd23be8205a5aaee8daca1c5c1230ea72d25bbc9184dfcbfcdc8daba8d76c01613583278c0e158954e34bbcb174c6a3368e4c1e30dc73cabd1d6ce0f6fd1cee0e186d3650d32c80d6a5265bd361cdb82d56bbc60412dc98693123436e836c9e53259d670b0bb9284cad57e91ab1a0e4a1235414bc494a92a0d671d796752ede5eb17cd173cd070f212ffd2e4652f789ce1d4a34dae2a67849ca4190ea3b4aeca9f6538088d77d2454edc982adb8207198ea5a46042ccaff4e58b083cc67012b127b67ab38cd5ab010f311ccd55837f499a9135bc814718ce63d9276eb0245d3cd1c85a0d3cc0708a0d37a5bbedc89ac88d3e16787ce1d8e9afb2e2e9223cbc603ac933ffc98b49490200267874e1ec7eb9a361e7c424e722246446160c98111232230b2c668484980b80223cb870b4cf5259841495369a3574d43801cfc882013b6ae8a89181b6e13516202212121212a2809010ef9010118f2d1c7e4dfabe7c5f0be7303133595d4ef352164e276d189915efc336848593caa58c49caf7787e85b38d92bb43df32c6d558e130ba4dd07f8de93d57e124fc55bea87519ea5c2a1c4bd893fe37a54c4a3785c3c5598a5765d249964ae124df896abbbfff2a89c2a93d93a53169e229d10285c3a7263deb1fa16e944f38ec66c98a7ada3259e784b3a5c85462ac1a3d9a70d2d35c9ac4bf623563c239ecd52f5b2d8f259c2e9e58e5889c76df3d947090cf70afe92edebaea803311c313f4c12309c7d07d77e2d60609c760250675b257bcb78f70d61151a74573e7097a18e1242f9b248d12db248dbb08273bf5b5285ab1922811e120533a512f65e34d673c8670b24c299a8989ff1eb287104ec964f5b1248545d423084713844c11a6ff073c80709057a2f58428bfb8491e3f38fa8dcc9b2a5808a53fde1613c0c00466d098111252f6e0e183739897a445a6aa8af5f7e0a0528e90b94ee44e8973e438c68307a7582762b26b714a4346081e3b389b7cf27f5f52b3ad8b0ece95af43334f8e77d1efd8c24f702c0a1e39388a2849eb97bc17a72e143c7070cc99e1a4b314e3eceb1b9cb574ac44897e51f7b5c1412551319724bc5b542b13c12678d4e0984b0ecfa86d6155a58e1de538e04183c3ca9a0a42ec5254563d66702c372bf9bbf615caa480870c4e615555a349f76ee5086ae8d081446e34bbc0230627a9dd72d4c993edcc9a1678c0e098a4ff8c1ab3e8f9a847d66cdcb88143e4462f0b04f08ad3c63a13a5f2dae5762000579cae4e4c826bec52c2e53a1d08a015a7ec2a5d139e51373810002bce9a92e81757e264bd1b5913b9d16a0301ace220da35ce7beed733aa8ac39ad4d49d2659b9d9236b2515a79c6e7253e8f6545171126c749af62f29eaaf91b51c22373a692080539c446b9ec855d1f2232e4c71b63661767b4fcba5386729f935d3f59135529ceb5d2d45bda0d51dc54910269dce6df1cde5e06204357e8b938100447152a2b425795b63a5141a590bc52989e3ab39d498a416b9d1068a837cb52475e691ebdd46820c0602f8c441a998368dcd7a48931a597315ac270e5ae35f8cbe54a29a143492b413873be126a5f7cb2a16ae40009cf87364e74f6e7f930da443c4cd6664c180a2a304a9c68eb221086013e7d0a695e9629228b1a11b04a08983d03cf916a22c83003271becda3b264cc959f2937060160e29825c5d29854b2bc375de224db898e1e216627463a0206995b065e18014debf0bded53253ab2a6233ad048cee045116af0820865cf7425a62397dbe2468d901743d0ffbfca43a5f4af92042f8470125eeeedc492492a684138264128d147652539a302610d2f7e70327d77a755f2173e389fe0f7e29742b69de4c8aa1e5ef4e098b2f9f27a99895de9c20b1e24ed09ea4426a5ab1b5974c38b1d1cf3a9bf12c776445f2c0bc20b1d9cf2cf9c6017932a8db332b0838b1b5d202cca80f0220727eb4bd26b494a3431310478818383882fa9fafef135ea1b9ce3378ead497569a3f4c206c7db92a1c205939478a5458d111d2127a87c5183826d7bc8cfddf4ddd1202be9634fd83cd29b012242c6e6d5ce2675aee0850cb8543249ea47fcdc15c1c218bf88c16164f04d92097f67bebf80c149a84edd3e8d9a529202f28a5388b018b26295ad096601e28a53b0381afa7305405a71d8da5395ded24a885ec4860d2e7e5971ecd4bb1464a9f9b95e57716c3741e6db0abfb07e17441527217e949f7caaded7a21000490567a97f490e61a645020415a62fa5629a1436483febd41320a738d66f64f59b8a27396ebc087702c41465ecf712c4d505734b8e1b9c0029c549e5495b25e88b2646a40610529ce7526ef76dff7e79a50c20a338a66082c9246fd0acc12424242484458c9b00228aa3996e5edf08b539758e0124142751e4d2a8a5b95808f70b20a038e91fe52676c80c2dff27ceb6aa5faaae496e9a3d715256258412ae39abc74e1cee5faf342f49b2e7ca89a3a820d366eb5900d9c43155991215956731df9a386a92f23bff04ada0994c9c6bb49e25b97e4b9e6903e5702d7c04c7298889f29a74ca44efca8cac71a14324a1952d6ed8a01112c2209738c57d0db978525c2f294b9cffa4d0de95372971f29540dc642c0b17d37117397698ad190974408403209438bae59374f6fede78aa05ca91030108e890102b804ce224785cc6346196c44999a89151a77a23ac8d84490c4da2a82dcfec8d2c0df30308248e49dc24757abecc5b37110079c4294e12f4c444f192527464ad04ee3ad201208e387aa92e3bdd6762e78cc50ea4671a00d288937097922cb761d74eeb0510461ca36bdad9369562ecd0228e9fa5ee6f644a17d5375a005144ba32a9142625292a2840390c48228e9b49bef479923893d2580041044aa5df3f4d579dcb0a821ce2a084b1103539fef5e2c81a6615400cd15a12e7d29b63070e198014e26c4912f4ca96e946cdccc882015e2347165868014288fe92a4caae66d384236b498be8406a0db04b800ce2f45f5682d028276b5d4204718c3f7aa4a5e5a54e8b1b395e8b1b3916d0458e2c4242d2042c10722081405d14bb5392b5c680783795ac64b27f405fc5c514993aa32c7ef8aa9259969c6279f47d7864ff79ffc891d18c0fdbcde630bd64b1eedd83deedfb61971e0c72fe17c5648913ee3cf4725244e54d52df5df0d0ec9c7946ef60d0533959af4b4551175d7663c7160e28870369812076388b34794de9a4ed9b53070201a40ec7d426f969cdd05e264e87630655223e4c5e853ee5f01d0ef001c81c0e7227d57c643c7e544e0091c3d9d3fb7db54fefc67138ac9b2489b1f45bca5a82c329c620ffaac413f388fc86c38d58aee0b1b115aa8e2e1c026e38a99432d765650ce7a7361ce3854bb523464c86860d9852766aa98472201c9e03078d19342e80026481bc3a1580aca15441339646b9241796524cd6de27c4a2854b420510359ca4d6acdc573c0d27b30d3288c6c9cb68d17030d9f24d08ab2daf19e40ca712a326bd4c571022fd018819ceea6342f8c6b60ca753ff96acc24959a5428683c9221b83a5565eccc7700c1d1753d5e55326f36238aa8c90565731a524fc6138a9cf7b9149c560385a999d3a498e39b7ec0ba7d39275394a1c377228202404c78d1c0de28593b87fe12246293983cc8e1a5c00e9c2616366126f82f849290cc28573c7ade5f72b212f07b285b38fd0b1154ad04a723572ec28964007440600a285e39c2469499364dd46516980b0a6e4d6144e8fde73a465810264013b14a01cc812e8800816205838a8cb3ec9d4db528ca104b9c2414da892e4096ad6e492158e31faae8929a5aa324b154ef2bd49b8924a2a9c4cd7c72da9532666a67092bfba7563775238fcaaba49625611d2aa289cdc9257ca134cb098432ea070acd324c8f83831266f9e7012e39a38aa311b4fb64e389eae95983f159f0d9b7052520c1b262c67fb52056f00c2847385b3937f92dd7b97967092a694a4c56283a7669570581157da6c4f775b2509c7127621ba049d419a1209876bb98d214479d87c849398752a47894912b6a1114e27c6f71151529db4398e2eb2c8e13870a01c1a0029c2e17ce3f7b66b8a004284639778ed8a7183925fb288810ce130f2d4cc9c24ff6de68570d8703a3553a94bd1618a8e0bb07a8e1be900204138adc6eb568dc164a93010cea56f7bdedabc2c363f3889fccfa0c27d49b9f7c1f12471641ea1b57346ecc171b69420ec4d12544ecb758884847897e5c1496f8af94e0a6f4ac9ba12e880480d901d9c43347f6b529563626e55021d101101880e4e69faec3efb4fae248100240727176196955f936421c6c149923f3b95b5c45ecf6f705236ca525b84a9db24236b667e0103b1c129caa5dc7449a8ae7cd7e0a4c457b5553b716450d2e0943664bacbc9eee96606470f3d7972084da9dc0291c1692e899794f21e2fd546d60c0126f00bb00189c1f95445cd2047a624e3775910181cd574dd92e9ca3849fd78c5392f7aa9dff8f1559d2b0e3a5e554787975ae5225ab4c25d7199b124236baa12e880080a3e58712a7153e388b08b297f68396ee4c8513e5671dad172c16cd6aad457c5f14bb854350bd20415a7e2f8af49bf68afff93222aced65b157f7cd36ed23e4e714cbdf184ea3e416e693e4c910a0d951a1fa540ad49eb980da3bff90f521c64fa3069adb55733f5318a93ce2651762909affd73da62023bf810c547288e2b239f9aae4bdd87cc141d497d80e2e8f69ac2497bf2f9db9f3899b4556a474f5aa6b4274efd6e3f42bb4e9cc5ec4226e1e327a912278e625535d2c41f15ffdcc4c9f4c5afae114acc851f9a388949fe15b959348d4c1b397c64e2a0dd3ba48950a6265416c1c4c9ff0495315cdc2e335de2eca2b7c1948c8f0d62193e2c713625b3267992ae7492578963efd58813111535895cf8a0c4c9dd2e575c34ff98c431e912f308f313fb94f4073e24717c9953a6fdc78491211d5c201c223a6a70488831537c44e2285a2efa7cec913533adc10724be5df3bf2fa91c59eb2d6ea86d31819090da507c3ce2bca677346ef6fa4ec487234e52cc1e97fbc7cec2bbc3c347234e9bccf435d332f4e60b1f8c38c5b90fa54a965c8ce1c85aef8c2c18e0858f451ca4669e9d9b08252551c4d944dd95a87b41c58cd8f09188d38dc6787b6532f535ef0311c71d214ee58fd1c89a8e1b3844762011df71630b3bf47188936492681ba38fda499246d6bac8b1c318e25c234b3ccd64b7818f429caaa47ebdb45a4aba87a346097410e2749e277fa12fa7423a72d4f8b2bd411cefabe26a9251419ccbdc64a934397b26d3858f409c2dc63c4168ee96e630a9f00188f3a672cbd51759d5a31959603103a7f0f1879320735d67cea0a4922d3368cc7015d4b800a2404808143efc70acbc17cf2b5c462bab4364ebf0d18773878985ff1a2f51914817397694e185e0830f07793f6292ead0fea18ac3c71e8e2e9e29c6fe5162263d1cd62f870ceafccd52521e4e7ac23bb4a5df70920c1e4e222227879fa0adeb1d4e6ee27eef42f4f555ec70909669aba3c4f773ad0ee74e13f292ed9c94753a1c2e2b978c4fcd2d253587a3c6db932ebda46897c8e1945b5312b9cd3531a9e42730e38f389c565456aef8acc8f7e170329b9337732c8530e1379cbdc452cf927a49828a1b4ed2988be5698cb572b6e1d807ab385cda26ed730f158d51c53993cc99349e8ad8f8a6e298df21767d7275f38a8a839c8d7abd3c2d97524e710af79139315cbeeb668ae37ccd99b867e948018ac00c1a5d38044674a006a8276094e23c27e742a6119ee12c18a438f7c820b3bf09c6280e26fb5b839ad3cbb768cc280acca031a3263083c68c92c00c1a332a023368cc282c66d09851109841012c66b40186280e62d7347e478966f587e2d45a6ea14c1067b9e44171125759c33fd3d6ebfec46937d4ee9934af1edb13a7b1a00495efce2d21eac439b449aee9da544e9839711ed3d52ec2369b38e88ce6259c096d1a524d1cb5a468410513cdc4a97f6f734b10a7e2164c1c4fa39f59ec1bd728769163c716a7b7a80181193466d098816b87807189534ce2ecb4b5f74e2225b871a3015deca8916ed8f00cb4a7a0c60242424a70e3c6484888150286254e51fddbdab4ff7c4e9538c98cef71bf689278254780418983bb6c989d4ba5c54223c098c42949296595a4d5e62e248953efa9b513cc826fec23714ae24dfb8cbf8ef70a89e35f95da7c5d49b39c1e71907e5a2aa920eeb49e8e38555217d4ff66cc4a9648146034e26c9d7d9b64d1ecf2b5076030e21c632fea2c4c9c24bc058c459c7ced339670ebc85a81a18863e68d954bd1aa165a1b301271aaad932bf3c698850de5cdc88201a5c040c4c9c56b636c7985b1b200e310270b3ad5c42d6d95843bb26688637e56263d7fa92419cc32051885c894d64b6d29c997e0c68d11117fb7d135eee8008310e7952de5a5e264a9243bb256a800631007e52e56928749ab004310c7a07e167ec653db47236bb8c504688ceca8a1011d366c2060068d193368cca031430bdfa1a38b9365251067d1194f0661a374b303c429e5c85319d467be942c028c3f1c67dfd2ccaf697d29f9e124e3595bd4102747d47d38fcac9cbff75a3cc92405187c38b8e50e15b7ea3d9cdc924c1ba3f866d68c1e2ca48000461e8e772ae643feb6c3b57c020c3c9c4caa85eefffd0e075d957636cd4996a46d87d36cced6139692aa2fa9c3f1f49f64eafc4fa8d2d2e11434298bb1c934f3dd399c2a5de61342ad72389da047895063b1b448e390984e7a9bf822389cbae74fa957ad586b79c3d1ab665d355838c12b6e38658e35653ad486f3e811254eadbc462b361cc62c66afa8d7800893a974cec49243425061a8e11913277985af8ca4e1b06f2a85bc269935d175a0e19c7f332a1bf394b8fe194e522ae5ada3e5af6637c32949b778614bbe31612fc349d224fe981853ca24dc6438064b529fc99ab12bd3633897a5f989be0b1abb440cc77193a4b1f2fdbb2a09c3d9dd4c9f4aa50286b3dbf7cfc8f2b6caa44195a4241334d35e38c6311384d69a9f73b10bc74ce94e550e2529d31d1738b8704c9ae63e665316d36de160562a7429d57c25f55a38c9198358673a25df48b37092d791d3a7d5193b2c1c744ff4bdb726d3ae7092be4793582322b3712b1cff4fc6a421b7fd4ca60ae73d935955b7637254385f4953bb5ca94f56740a67eb1d212cb56f45b948e13c3a47447d8c853ba370b0332568ea1235a5100a2715ca246d49de6750e5134e4a496243bbafcde273c2418afba6696f597b309a708c7e79922a93e1a465599424c060c249f690a2eeaeefa43659c2c9dac26810b75bbf91120e2ac5b2e65237a7514ec259bb8452dfa474fb72483809279695d8fc4999fc239ce2fda57c5fc652a289114e41ac89c1f636968f49114e2a4853dd61314b1c11110efaf289b9e23335363b84936449ccd3e9a2104e51cbf63266d6248e1984d3fbc9a5e7d6994a1202e13027a5d22b4b5329e97e70123a645bfc0491b2fbe0f425675b1c35b3650f8e515fff4249a76495180f4e7287506969ff0e5e2da1437da8df6f644d73ecc0815dc0d0c1e1f6ede4aa82d999520e4e55e28ed237d93d49120e0ef2be33abd91b9c4a840c9baff2998c221b9cb46c1aa12ea89794c4d4e07832c5204e49dd6ad1a5c1496c924a6d85111a9722028c199caa4c2931ab9daadc911060c8e01484a9d793a482002306c7fe4de2d3d4ee9a9a2c1460c0e0202aecec99ce7c06fd2b4e494b4d2ea51cca4626bc70c5496bc3c8a4793ebec45b71903b37fbebcef03bdac185160c48c20b5630666a1d6bbd9b5f0552544e18b1a27ad6578517eea4cb348d7d2a761ffd1527ece42c1d15a9c8b768e256f47d8a7b4cdea43589b5dcb429f497d3d6642996b026a53025e9a22f8c50b20991421363562f29a348bbd4446b4eb73a89021d7527dcf8ca55098782937d9428139574507c6286ae34e27fc3e413eadbbdb79adcb2aaf1049be14ca894625ab774e22d31532cd1d46bd59cd8040d6629b7e9e126de6452cff2abc837d1445289c1c4d40ae55e268c493b342ec5542fed4c17353189fb71202dae0c07d20273dce014bcc0c42968cf659c515fe22459895527eb84fd9696389b583e49a534c24d8d65ce850d749538dd86da1cdf8e4df9a6c4c1a45342e5c7daaf6812c7afb46549148b652ae9800be0e0a25d0121212238b8686f2cbc90c4315c98f94c19f3f505a3f02212c73fb792296667266187c44186f2ad3325c49f591e711031296a34ceb64dbb138484983bc139e185230e2a3e97622f7b4de6ed45238e2242591665256d2835238ea7f5b6bb6377a5398d190ee062e402a9460e5e2ce2941743ae28edf36fb28a389d18f38c9b722da8331147fffa2ee525d9e6ed11714a72429546cf93a4920f7192ccd42fcff5ed06a9210e7ac4fdac66d057cf14e220ff9292364a8883e613a52fef8338e8b7144b57de3cbea1208e1ab2b4873c2526995c20cebfdf23366b6238ab00710c97695edb2b32347a80177f38d5fc26b9c28e924c6e3f9c526ba9d0abff9522f7e1947adfdd5e5f167d3e1c7c830a7f53da4d78d76be4c82277bcd8c331e8f82641ec6445553d9c9429ef2b95462f9a46021d10d9e2451e4e41fac966354a9518453c1c4cc5d4cc68aa4d457364ed0e470d938292fd2eab67fa0b3b9cbd84c97422338952621d4ed9a397440ba74ecb4487638c9b53dbed9a17bd9163870eefbdc0cde178726b3c5d7286e9725350e3590ec72ab12a681d2f111dc6e1d459f13b2aea6fb85568bc80c351540813174f8c96692180c6880ed400733890166f38c4e82b41e486d3c9bc4c1553dcc6fa369cf3bff45538bb6c97b47143648d022fd870558ca66b194b9990088be0c51a8ed6a757642c41cba46e642dc78d1dc971e8d8a2c6045ea821ede31b4f0922c459501a0e6db2453d8b419f3ab2868683580aa5257ed49576475e9c0151e26851e1f6e636837eb6195bc94a96132bc371adf2986c8c51c62491e1e05e5256d16dfd93e2184e2aec5750b2bc522c59319c2ae5a48dfb97f284aec2709252fa7559bba44fc8c170deb08d63d2bedc46d3174e4910bb95be37a344d90bc724c97c9b26255f847863c71635ce92012fba702cf13586d314f4e9ef5c38e6bacce5d05b5b71c08b2d1ca4c6189fa7d6f2ff480bc758e59be5c459382657b558b2fb6fcd8985532c29fa99085d528be60a275d921879b15be9c6c40ae7d88a69f1ad2bd7e6fda20a471925e88a2773d38726c70d916cc00b2a1c3e53d093a14774a8176d07175af0025e4ce1b8d1f7dffc3eb4942422396e88a000e528a60f782185638afa6934f78d4d0bb40bbc88c2497e89a95749ee6fedc85a2a2bc00b289c2b573a93bfe79edf99e08484e0d0228de0c5130e63b79b256e2cd444271cdb4a504a764ba3be54e945134e595244acf6eba9e9997050cac44f1347c7edb44b3809b769e45b73a6a431251c637b2853c982c56653128e9779a9e45df0d12b23e1b026decbe8bb536284473868dcdf3a31ef0121217a8290103dc1814186268e7ef2bc84c9d3a73667e2fcb5eb23c435579e970258cc30509081896316cfb8bf8b232c4f645ce26c72dec9be94f2fa5d6c46160ca031838616690329054608199638e94c32596e8fff7d46160cc8e1366c201a33b2c06246a2838c4a1cbfc5c4a5692c132ca7449260594ee511a3e398c00c1a3370dcc8a1800484846016644ce2e46155fabf7a7e2b5c08081992386566879d98effb786544e2b88b1dea7476236b5aa4149895018983b41c97cba47b02ca04198f38096e216ec25376e64409321c711a25574a57b9bf4afe8320a311e712534ae72165469cfb64bf2bcdf6a529b38893ca209454de9792eea588639c8a2a757a4936ce449cbafb2e885bff9efc21e26cf25cca99727d8893989a4e97d2ce13a631c4a94d5eb3e5b1bad3650f6414e29832ce29b3f4a3c5a2c640904188d3ffa824e60a4aeacb92411cc45c5f5383beec8b88031982385ff69b98b435abb21d8853503188fcab09265a8a0c409c5c843241dbc49a129d8c3f9c37e5187d39c4095ab11f0ecada4ef36c9f4971f2818c3e9ccddaff43347d7d669d20830f47f5af92ca4bc54456f6702eed18d5120b42255b0fc724dcfec9603994a4a93c1c4fa8ab643a453c9cb469c92785b81b3dd21dc8b8c349ac989891b75f66f232ec70f04d296c3cb945e8ce7538995465956944c3be47875392f3253d4ddc55d3399c549053259ab81c8eb36a1e5f412e45ae71389a481913b53f49520bafb8371a453f7dc3292cf32641ca9cac3371c329b3c956b2ff2879196dc3312ef5a68ad17b92201b4ea19af57972cd7ce46b385dbd55086d2a6a38c7952064894b11d3571a8e1a67d30911254b4c130d0719533e79b7c4661deb0c27b14c8cb94513eebf34c3f9640837dd16553f746538f8ef85efd9d43e4924c34133c585d29745ce3e869358d61c8d75cac2956238f8c609ab63b1309c8229e137264f8d0a170c27cbcb77625dd2392af685f36e2ae9cd24d7ebcd0b478b5ef13fbd6493af75e1685eea4e46e8baba950bc7b5caab1062bf2e865b3806afb79343673398182d9c33de09ca4a36f94a9fb270b44a429a12fe46a7695838ee9f4cdac4ccae70f0d573bbca2272adb7c241f9de86cd9792acf9aa70fafe0a172534e8659b0a2561edfb43e8770a074bd1f439732605b14ae1b8df3ab7a382bcdd320ac774172625d7ca4d820585d39d0942a65daab4583de1245e9692bba377c229efcafc8bd46c7fb70987dfb2cc58bb98703419f72646b64b3828a1d7246b8db3aeaf120eaab486909fbfce6d9370d271775b2d2d2557868483a7a693c7247dd1368fac2d41c6110ebae74797d831379919e124556b9ccd678651528463ecab9a667655e68b08c7fe9099de372acd6f4338a50dfaa1312cce8e2684b3ae6e0abb319eb2a00cc2d1929f778fbb40385fe8c6f238694f473f389bfa8bf12bde33c9cc0727b9cf4aa8122ebc95d583832e25053dcd190b35f3e060b124b9442d71834c6f07e7cb619250f3a60e8eb14f8c9b22b71c1c665325295c4e6c0fad0c1c1cc4cf5a4d45fbcc08c9b8c131aec96b17831c6d92a80c1b9c54aacc2477f9f92829193538ccc8502ab49c09ca743268703e39329a88d57a4553c60c4e4968c59dbcb9727b284306a72e25e554ca6fe1ef95118393a4192db3e9d905b764c0e024899b3bad73946872fc8a738ad21c9a45c615870b9379f2e816f15d2b8e416d08717e1b9692c98ac3fe8afe9e3529c6e2559c3699b0d63d69f396ab8a9398b8172c8aa57c99a4e224c6aceb464baeb91a2a4e426498ac71d3a54ad2a7388fddabbac664753a531cb35af8d6cb58298e963a33a665436fc7a438ea89b541adeb511c4bb64a3a4c8f7c6c44714ae2b785c9929b42484371bacdf5cb0cd99b4ea0380993924edde4b2dea74f5ce1be35c363f4c451e4a9b968ce3c77ea4e1ceda466691f99132731480b5a3dde4d9cc4c49859ffdd2c8da58993885413611a4a93ee32711283de2466960c264ec24293bd8d387fb92e714c32acf50521d457652c718c5e62261d25fb465f9538cfc97222cb77b4e80b254ee9a2996962124784661267936b4d42ee87660a2571badc5227d76c8e8a4a244eb2093bb14f5394dc1e24ce56d1fb2f993caaf17cc471f308934dcc93bbcf1c714a62de56d079af79b4469c94e89bd79b42582a39469cabc646a511f22b457511871993963fe36a34311571d44c49a6123642880825e264a33b573bb4e92531224ec2fe5655d01ee218d4fc7b43cc581032431ca49cdd09b929edeaa510a73549f34cb2d110278510c7dfba8b2973adb2290de2f85517f5e4bbfc79928238692a4946863510679362e7093aa46ce600713451a4ae9a70fde15c69dffa5b1b3f9cc410f6a11a1bc3ffd587a38b6b7c4b29edc24cf870ae19396d9a24e1b3ec3d9c6450e36137a735e37a38b7cc26d1269cc995bd3c9c2b8911769ad1d2eb858793cc9a9ab33317f7a4dce19843e6c58ba893278c7638a90dd77292d26b0931ea703e31ce46db645ad1e8706b8941e7a8e3060e5c27c498c3c94e570a112b8d11d26d420c399cf2c46b460df6ab5eaaa1e30605e2703eade3dda2844b42d50e18814b408b1a212122367068512324040e073525897c2bd13a5b2c428c379c4451211774e6d448512b21861b4e5284ce8d6918a30d96b491258c1839f71662b0e13cde270993dcc6ebbb2db670400d1c37b2702e4c50630b0c8ceca8a181909020c458c3e952aaa4bd36a96469a486933863961a32a94d299e1962a4e1a046eb9209214f2b4631d0704c32932e19d6ec528644ac9ce19475b56bce94ca3d160a882186194e5b7946ebaa97247f6fa78618653886cc995e362bd37ab4430c329ce40dc2e4eebf5e347f0c079b91a264b6135ba42c86e349c2c8594aa37542993887186138a94971459d244ff0354981f716341070968618603897245ef4c6d2656714e30bc712ea64f19411b24723629a6a6c71218617cea675764bc42de913d22246178ec1b404a5e45c50d2c08c2cb0d8e10cb8e4c24195bebe0da73526497e0ba7101d55155731cbef5a388cd8e512379eb2707893136b7fdb9aa413162eb124cae467bd7756861857b035c98fae8cea68054f96d514547e8fd2ae0ab5898c8ca9ee376aa9400c2a1c6c2e78ac6db838969d425a66c4aeaae9b7434210190931a47050b2c4fe561aa53156a2704cb393d2a2fad92641e130ca2ff5bb49fa2b944f90410c2714463b54deca628bac65319a709e4ddfa1ff330553310731987034993ce5662e654fd3c85adbb8f13588b18493ab5d0cead2c73f88a18463bfc89a559bcae8598c241cc3352d7598a5285947c2d1cc5fcba42f69332759c438c231c8ca7c4ae8eac60e91111d5ce0480dc430c26943f799a749166cb46c254868c7057228403f10a30827d51873fd3b318870526ec1bbfd6a6449328483a951ba1ebea67b5ade253184709035e3727e6a3656384610ce765ea2ea4f5ff8d48170b024e93129fda82695f283738c67eec9b3d9654e1f1c34d8585dcc95d836f6e0642ad5eeb98ecf9b37b26636060f2cc4b888b1030b312788a183d36c8d3a3d95da5d64ceb89841e36c08317270de939424995ceb1934150307e715994a7909ef1962dce068d2d7f6865349b6b6c1e1eb4e5ea68d2747c9d7e01444551825294b96bfa7c131492e2ab8a9f8d7219fc139646626139654bf2e83c349f25b3fd48c10261d230667532794d2a6c22f093b060c8e279349516ea52687c92b8e2d222d659320579c4c091926ba09a5e4caad389ed22c594d8559715e35a52ea55cb7bb078bd4a8942fc6f260241006428140200c0a0588577700b3130000000c1693c542d17048b08cf3031400054a422c503a24221e1e14148a83a1712018080402023118100885c28060201c0e09c77090cc0729e7cd69e0f8f0113eee74023d2d5f93a5e47ddef1385575b7cb5dec920c32981657051a1400b93aa4ad2ee160c8214936fab78152cad097af5a32ef30a8f155bdd83f132052432707353326a83d60b62f7f6acfbcc5dfb8a8b5d46536e42f7f19695105a360171209495ed2bf1da28a82a0f2f2eafa67ff5d0432b7fe8e0aa81e675bc5b4ab2418e046c954480a5af8954ffc3454bc110363e7d638c969504e2647ad421ae58204566bdc265bb693173027532a685f71dde7952a61d679a496e582dd1f4122626893ef1da2939ef7b64f59b8aa59d14db8284fa61648de288cbb6ee6610c11852f398c984159adffb9ce55c01669a1a45653752654128aac70972dd7bf21a677c5b05db81692b88ca59843360fa08d5e1349fd95ff23917bf709f285419ad63dac57da1497c0b548bbb5ff65e2695df0d2b670ffd2853bc6d0ab25e0c51a555649fe8ba37200172a4e116d0714106b07ad2e2ef2f1fa99ac0f11b915f385f2dca4546e3348741f426aa7bdfbc0c7327639dd452fcd395c1c1ce6d4702ded3127888f0cccea71346e66abe822f7a49d0de4fc74f9b1e2b2108bee05f090ebbd63d7a4debfc75718a7d2124ed3d080960ad1cfa39e4ca2883167646aeedf068ec87cca4cb6063d14c1e85568606243fad6507d06a042b5280e5922f6d9415364d342cd2887e4881a96506f51c0b227a33248216914aa83da05b607950375093500750d150dd427511052f084cad2358b281a02d43ad48651975243a05241ad43f143c140f12214d4124135545a281c286850b4056e64723c286fa877a891509f2da83ecf2900d7508f5a14f025aa524e0bea5e285d06b5a14ea0b0a150822888f22b4816055c0a0e0a180a034aaa1a1547bb22676616a5217502b50b150a6ab0410185dcada00a30aaef94351918a18e3e5d3cbd7afa3dbd3e8d789a7a4af4b4fb84c3a2501d37c9289c1fa52315056a1b8a060a2014050a355178de14b08978597e4001335ca02cdb624f2846d54905200a4a8b6c5201d54733983b1186ba1554b6e438ab4e0d55c0f2bb233c9b03a50d7512a120295d86354aa1aca0cea1eea19640e5827a439187c2812205858782870202050525180a1f0a35a0e2984e48472594151410142d140a287929aaaf196cdde268ea419502d6908d915251eb59d342f7b8a116a0b6a126881a2815026a20141c1410347530d844516bbc8ff001884cfb65b1ec806cc304bc4a5071817d35e5f366f70bb872751a959c108bada5d22d8c45f5484087b7df77cbe6c21ca41cb9f0c69dde7c4b2c14d61892e4922104224ee0d7355dd088e61abd701b504fd5ae7d9e194529e04d4520fc51d6dd58d54e5cc58d5fd3e95a859e813cc45f00be4db28d19c80a274a1565de3eb9efa3c61a73805ba683a83b4114ed7042ac503e9e7d2f9687ab445186cfa6896013c6e4595ac844871f0dd50888914ab0abc4a089812fc9de1b3c3d5e0a3a97544d0afe5f88b361eb4a397f947a15a94c8b51ce5b1fadd73b83d0dfe1ba7d49a89c8bafb27033b8e617d1f93fe46072d973be8b66189162dcb411a2c575b8838f201b59ae745036fe71f75226db1279b6b5abf9de50d99c118e5302d7c96183361feb485c8eaa17bd1cb5e72e347b9f128610b0fc877400e6e8c69da0f8f891bb95bd556351d9cb3932d8c894ce887d2a5e93a4f438083aab25657ead900ea0412b4e8c303b6f05ecbcf83c95052c0437fd1854956de60bf8396604a2d81971169a538c7b0cfe0abb80acb61b4113c7f529d1c2974cbb481e003039045d14602021150fa56a2cb4d50700e87ed6f2873dc53fe4a132ca31caccf130fb6378fd788bf8ad8f6276342ee069ae9154ab9d46a6e6ca13a3f98d9094d6d51b89c4c4c2173c188202771ccd34c070982ef1107de7adbffd416b190a885755f4b1bc8a234b99bbac524729c1aedfa8398051fca16e05383259c42039b8d783111c0af3b04847505a99152bda88efe0b67da39110995bcc93385319786c4cff7b252c326cde01ecb28bdb440f12490cb10c8166f893864433a25a1bc1e833e2421c417c486e7e9b568c5244b807d221a93aa49b30032eda9943e246b0a4e032044342e8a31a1ac36085caadab256ca59923f1c40f7718431476f1aea96da17c1e7ce88b89f3d229d232e7e32427329d4f44865965ac0f4cb248964c6cbf4a1b09196194b6f4c65527647875e2b916bc26a020fc515ccfe60295cd22b543b31c201f9c38e7af7495ff5954aebd835142897548443a463e909e54d2b111a1513a3d56efb7770f5adbadad682a81de8b738a8cbc770b5f5a73dd8fb359c0f22aa63e6d1fb2956a5ebdfeadd13ddecf5576643e65d0021ac321649a78d3a57d755c64a0fbac9ff2c3bff8cc6a0f0e21071fbc8f5ae30675067eafbf846c63b89bf8301750224ba5aa9892b34293f1bbc5dba93ba3fbebdd7bdec472efb9b870f77cb61f6a90d0352c7743752602f9db768be7bdd325d6a5d065a50b97bb756f7799fb381d5a2ba95c2875c4ea923f64756bf5652be5aa1ffbece7aa9f443b8019ed78d148e0fdbf96d4688a119a045123d7b010014d61937193f1c23898310de32869ccddbba432238c552763ee267c2d4c8c66327adfaffd0433a1247935a36e3efe002212dd34dea6b57b96273b6477b67de538c13a6c48769eaad644e3ab37998be83f12ccde405144a7fc7c02d25a717993dc83a1f8005d1c44722f7091e0ef6be7c6bd3c6636df0b56fdeffb49c5694e699d521a113d75f0f057c567526cbac2c490a3e82fc863281b397e8f3dab99a693a2e4412475f1ee41eb57547e7865299c30fc5940b8a6ee61f75b1a88ca0ecdcf1f86126c59037c083aacecefbd19ed4f58e44296cded12440f0405e7388c5494fc6be119c8c4b1d361fb2a5e45a84e0c746b75a8d2450b1db4a1e891655b1aaf8d2cea6164709e474794dc1c5e7dcc4ae980b194e82f2a7f8708e6a01b94ff24d369429707a5c8f021f59107f4b38c3954bc9162f5e8be8857ee798bb291a50d712b9cebe61e0b683908cc723d11d5a8bd86f785ee6f44918e053ca5210e2136529887ec40418689656d76dc4dc0570ca6cfc2bfb5887aefab5eaf73fc25f7ac94b2bab7c7cad341a6ee5e9fcafd4808ceb9153fdcf33b66a02309783a28814f35764e576bdaa01b174a83dfe2aa9920ae569482ae684941674cfe75b873fdff08ce4e5cc8f843069231198313821be213570a1f6ebe036af6904c9da403c3644d32b0622311b7b08b755f654460ce2090b2c840519b422afd369e040dd678700933f1e392f997c384a33a0cca83a0466f6de2a553d088b9003da915f397b20f1b33ee793b4464bc1d8153dd7043da9a30eb3011fb1e3ac68468e032d86810c660501fccacacd45f0683e20f9657e67c968ba06580b80f668097a5cd4d5b4e2e60f13d586f04c57ee46e70307ee104c1f97b0e6d182f188263bd52403f488142ff92c68c80553196846088f7549a0180188006002100078019406606b4b45de6b40a30020cb8fba03b817c8077faef3aa9c037008b01905600a3cf20f865528d000252883c286e10041541e504ab0407048311800b50eb4f3184b95ed993387d381b2d14867295e1c3ce90bb106070c6e0cc665fe8c9016d901af4d380bc01960194010687c1e460182d364034f83678f1204910e6296c441e8c8a556f3cbb75561606afc8c103517c3fc9e33d0f4d4d2077ac8e9e50f084ee29803b901705d0bf0621fb604c8d509add22501ab419e06bb0f945f235264c30a605153e5bf4c37fd7e57eeaa445803b832ddc8d8ec6591fe1c109d662d403966210b4a70872001f564d04082670cf2e3e2d8d276692bfecc0a69e2334ce9cca513169e53d2e2482410370a0564a369759efd270dc6afb0e086f765cf25c6a0eb5c7709ebe3b4fb08169038d89868df1b909e62c692122f9f985aec8533af038b548bf9a100829b04ce01de50267a746f1d38488e8a164627db3c08f13023aedb8114eafe8e7f1eb5f27d436c50ceddb6b0e3be67e7c7a5291b287ec33f20cb0a9b1a9abff6a13070c7d6676600827718b6347a74e1583369f49161ba897dbebbb25553adf65f5cbb4302f5f5d274b451c7c61cb9f0835e305dcdaf155a6f29ad87a93ba633c084fb7c474cc3bba611ae06994dc2162047f09b62035788d0361bbb56e00410351bbfae358b6353899ad5c50b151d7de71e9aa40258035e7292844292cbc03e4208812aac049e967ce5a6b08fa0a0c075869afa1ac2eeb8e22443ebb0c965d3e295c424ee36026ccf78ee4fe31198844dc3e80b7e1181ca62d013c363688e05dbf0778ad5092178af6bbc456e0e420421880c8360330a2b3cf987f0aebfe28e3421cc6fd9bf26be88f5681b4e298cfa6c76ec89fce03659a227a8c62daa68f9149adddf498c55b8ebf4f8dcfb52ef5870cf710b519d478315b52e21147048d4665bf4083d3a5febeb949cfa8b0d28f8ba739285545034a332cac1720b53e747908494683a614062800285bda0911f49cc9417aa60272eb5268011569a11a1874ee7434805eea0be9824161f4e9f956a5ca104c32cafaecfb69c2d237a9d207dc110580e7a82d5d1016f01e3f5169bc0bdef57a8d539c35c34f483b844b1b99ccdc1a6ca4c9244530a32197329c4e42bad546b594d311216a8c9a73e78ffedc775a7873080c9574e28e831aafe85e1bf3410392b13c8ab0a695f507ee4a12415383038a5ca6c8a05549ca00e7ff904623876f2e96c12fa2deef4c98f67206c3c90aec4df61389734dc018ed6564fa5439189d070fbd63ef0571c85a54dc35302a5a0fb513c2ec62d9a02f5bb8e036fe278f7b0a4ac8f1831d3ee6bf35472c4983bf5b6bf474f5293c621c16381c47ad436e34650f9094e43572b05d7105da52a4ce2c765bc203fcc4dd14c0a5e12c7436678fdbd9c6cc006f5b1da87d04e1887bfe85d20efde9953f20663e19c14586d51e1e5c2423a36570e822684a10a74edbf1e7f9644986e7202e6310be1b3d3067cd2f2f408615faf2bc428ba52a06e0bb4e1bdd7ba17868850e0fe7d2eba2146390583a05690a450054906c300a9d0ee0c69ec92388f06a7884c9c68f7a269e06fe81c4253b0e3f80e41e338d35340a85738c4cae189bb0565c606d73856f26a7d90db87f0619ff0e956bb871989b20728f576b0108a4449e5ee8508a259cf346c14b20b813a601b9e0218b469793a6ef2a52094a49db06507cb3ec04ccb65464f7c066de444cbd545d78fc142439d0ecbb832c2288c55310cd7659ece369a8f569d2cb613f1436b68f858b0ae5938e1385a6e18b033c153260b5100b2492e628a8bca8d982429131293159cf764334ee47dcb4e0617d9e62f9925259bbcc42b9f049d32cba68d2ae2f9c5c96b288dca6d91a8f64c5fd2ca5a0192e44747a7f45c4a1d18a8bd57f6a7f19376a7d5204d95ef544720a63ab0e9f14e4bdcdbd931016b126d96d48058f54defe4ab50652d21eced65f1c632172873ce1313c2a2d410eb0b973d8f97258d058671994e66645bc9137c9047d19af66d613ca05976c47250130637d4c6ad2b4490b8c725d9d53469da301aa96b21bafe1c8512b4b501b9e3fc4b711e780e3304ec1875b1e422b4617f6c3813c5897330d27c5a635d0378c02db1ad020e25afce008eab4802a9b1f18c6e97f52c2d10f4408b8abc2c66d4966aa37525cd40c5403ad6fb5554d0d3864d7978a62b34189cdecad4df78b6f46b3d780c07cf4d22972f85450135d9e162f9a33f47c1c2f554a463a03ab8eb2d279003a4cbf5fbc4eed4727ca390da80b92188a491d8f20101c6ddad73cd1ad77f243c7362de75961bc3275024e0c6829d1ea809feaf60570152c5059039f0da12b093da1773975002fc8e5f6dfa5321568c358ae4bd111699044171474138de56bae5b8b4410cce48ef87126b5281cae058f46d1a29eb1a57d9c1b7d8aec61ab0405bd57a3741c723eb68049a2d8bfa0f46a20f46712887c3e285dc4cb9632a39f000417c12c62be325ac270af0d99112b6a2056a0597baf27bc91301f5ed393d42f8735208213bc7eaaab2371a9d5dc22f43f2d067ef229aa431a1620267b576890c43ca5483a3332f0326f506cd41eb56113d9559eb1e28121a9c001e1cc6262631f72f26e49c440d5ae21eb4d50371164e4f77d09199e09ccfcf16b47f0108330ca2e9f11db243416ea16565c57b242ec5d9869ed0e8c1baf5daa97edd8950a870b780b28e68fffd3a9f54a3b99804d5b89c4e7dc9c3081bbefe9ebfd8ca810377c099a0bc1f05d024a4d72e930afbcbfaf00e6fe848990c79e2fb23cdd51c2eb6d530293ba19603952eb91b1d933632a094416ef1ba7ef4fb43afb4a7eef472cef3a3f68a1901be51d7a88eb62887567124c162402ecd6dfebd9827d95f062162b98de5ca19dda5f98403d2f6e1384a67b1e5e0491f61701f1dbeb645eebdf1fad1435dbfe859d4d21dfe41ceefa93ec319d23d9312c27d0848064c7b09c088ae6366586ca75a2559036b5bfe70193a91cb69e7ce7a6d1d1e3ee0953a76f17658fc539f9f8d36deb173533b3f952c5be78f4351a6e8a5ee3b9bf4b2cbfd52b1c48dc825c910b3252d160c3eaa198a7b6ce258d4c73a6ffa75c08db6ee6540546b2c7a543a08f1252db80a5257e53776316b04a512eefa50ceb974d1ef5c327ab988c49874e4fdcd7dc881c3b4077c860dcaf45520a22ded2f47c09bc2385c4ebc7e40cb1e9b1ba241f7c542ebdbc5da267fd16639f603fc0de8f8212b3c8ab0e76719bcdcc121b3c726d88b5ddaffe5d3cde001bcb0d0ea528740c520a98d58a9e85c65e4669c7b59609fce565068cb0e73b4477ccf5af0888c959f44a8b1cb6bb216c0c285a815a8382ca0510a69ea851764339f69cd1854f13a1d6c04dfce5367d026d9ddc10610523e0e60800856666d054b03c85808fa04ed77a0789ffcbfd6259547ed7c3d14d5f72bb00ead4172161d5a212744378804a82a83103104fdaacc960c91fdf18b5ec2eaad67fd572d36cfef8834463dc9bbd311da158bd0c027224ae09869633c3224aea1f47502dfb017b2bd6b1188c3de087bdd51b947d34ef34b3985f73c2442185e6c2cb00f267084570ba80308db6da9c857c635a8dae1b4209e810c44644da01dadff717f28d50ee0630515bbf0092f9ff9167c6df0cf3c1cf77be65a800439d9d6703c2839168bd60f4649e2979f5894f4fe84ae680fc3d4274463291dea745949e2b5a03f77c1ccda0eb337768d3d0e352e8c013b6ff6c8f5cd509b4215c0a7d6a4503f648daf9ce402404dac4de3cf173e20a70c51ec43e9d9670ac45a6010adc81d1e8cf243ed08a58cdfd9b615a8293a2946c91ebe1c949a47ff5561f5c26241a927f18260f0b2c94741ac5b5318cbcabef72c09f4bda85d45017a3a2a748182d060ccd9648567518fb2651b76ce714ec96d31b0e2eaa69a2eb9654ff8e506fa71f414cf41ee4e177cc7ad5a66086f7d19c86764251ae75609ae00b37b6a4765ba7005dca4f16302e2175b65ff37bbda7b813919c98da27c57bb901546352fd33c50f6e1d2ac8c182e4c8919f81677c110f128d02afe9bdd52a9c9e274c47f5f11fe586bc1b8952b4e1d14130a67b49ddf66a7fdf7b2c2a0a8169a18be545df81dbf8223821c5c0a6c9a78655cb2116f9ac463faa3676941c069718b5652f8d59b8b0721303c544b36a21a3093463ec014b3d4dc558c0f1c40913d46ca6d6cb265e1ab95ef7ece6952a0070a2bc008ae0688e939df62896fa5346d21578b25e884d9fb1457cc175f03807b1a5c3ea301b3150326a8b9c03186f50a6a5c380fc79e46d199700568929c4960cd7a98efe92ff58a47d7331e7d835c223cb9d58765f1e8db13e67b5bf188f54b46a01648a838403f139c6bb181e813ceccdea0223e3faf30b4cf53d55e5631c8018c4f1972fcab182c0d2eb25920d71708190ac4b94ddb884ea6828bb14b666989f15b5496960c3fc5668ae3e6b5af09e980d915ef8d911f2e42bc65c544dfebc6e420572b4b1164c6270dba54b61c3c6089c0a605bfa75902ec64b166bf75e5f3419e327a54e94fdf2391ec9877d3a345377a20e96cde2090c076ec0afa0ddae35d67016431118fdf433dae573a10ae2fb364e8ec3127912629b2398b6a5a91022cdef2078b37ee1fc98d1d42e954b98faf1b8f455574b0522c00e1551adba281868932148ee8f9cd1d30ccc9598ad81e88e8fc1ea7717e28eada21739d9a832ece0e76a297691f9b6f9b81fbae3acb839b9f041276507d9d73e2130a9acd907550b00dafbd1d981016fe214bde7bea2e8a116be31d8526357eb96cee54c3e82ee5dd5e34a0bba612c14a9048a93fcd26751aca82de6d843b985e073bc7d16f7eb04e64cad38da5c751457833a30234e5f862293814947a245621c45b186438fa70aae124608a15bc6dd5b0402d1209bc0b1ae51292d03daceca0fdb6970a97246a23022d511351232235e8e5890c88cc435da4d8ea7306d3101d44555f87dc3ddbb72805d534d2d1232e0aaa66bb108a3420a85a8b0d0aced40961fbd5c4891d805323a4d60a0ff605227b01955bd4d5e88b0e89187753f5289d28c0e4edba7dac33a3c8adfb8a0b4874a888abe797ca6fb7fb86141403e48852c1295b47a90e4e0ec2db18df7d6328c9f382e1e60889d9257032f108b4fe1fae9268dfb00f10672b8fc2c5c47f25da613492768e6b1a3f317a8258bbbdc8fb5a699e61b39577ab4a288db1e1c53397e16ff1778273669ec16147081df47a1f90d4478c8e127c6a0573589f47f615736e5083c4904ed10d4ffaab2320f6ab9f3cf3534ede14cd91f8118c81d166526e66623a415d4a3d823619936cf694dfe1a65c4cf7abb8978bb6913d487f6090c2783b5cd5e4effb98f682f7453faff6bf3373a8124d79540d14b3148b59343dcafd393dead3a9490d855e88fda2e8bede526c86b96a7fb4d96d9a94d1f1ea4d9edbe553058fb7ea2c73663b4b5553a7688d542349582de0ea5055f37c0a919ec0c0c718494bd38e419e2dd467c28828908691210a1c398e812dd20c4c444243429ae344c02086abea2dc19c62e64b0660c7cec55733c2fd742820e4443ca8615927106ec63b0b97b372df562dc9b69d01702cd8d7e0b1079f0b49fd3aacd5ac841698c1eb69096c9c379c8607d7dc691b479ec8ba2ebdae5c2454eefa3d91a013440b493e4d0f8c8159d00c8e4a7f0753149bee26d9ae8ccdb99c8f4d006d2031bbbc2aaefd6a72a40fc5512a1da29270df5aba9478e29c78b08397fdb2d46f81077c817e4b9489d99339f8255ec13c20f2de1ab44d6998d43830e048b00aa033d028d809f00470f39b1f6a46268ada2e268081c92bd0f0deb10cb70f312ccc8752d682dda0459ee6727a7bd55d9ee7bc856d625417f43a3590dd648423488646ab5cff83aa9a0d918cbc7908b3ed2adb90ba2883d506e3fc20e883bfcce2e6b464aa1fd9d14c57435df633dbd75b91a100b4f72551fa7a47af6a583740758af1c91325156447b626b74365a53b5f937c03f8602c8595e7fe5bd679edeaa43135068eb54661bfb701cf0c5f93bd92847d9980b5c3a534ca16f8e09ec5346d99672a64f9554dda8543e8bf9cbe863c6f42ac4501a17a9d28d9a552d99569a073258d4724a490ac6a5eff1cb3a89c15b1490a567b9ba14f0c17e61505d80cf1fb2f850828a09726fb321c1a1dcb795a165141286c35b6ac0310d891c9591d9d7770062f4732f749e8788fe6dcada9930c4b76b123941869d4bb252365aa478a98bddbcead03c3e29e61121e0f1ed61396c8e0106c02f18cc154a32e2e7ee1fce32eb73916f57868722a000519b3aa550c02bb0610bfb7dcdd18aad8ec5687494c92155ec6a8bfc33fb0979cb7c23fc5cb5d9556284eeaabde1f7ba9c6a6930c4c7df2a288151cea0d95bbd37420f316d4d84bda02ed1dc4ff3b2a8676578f378f9ea14dd89312930e5ca1ec96482b96c968386f4d338b6043e5f6a5fbf4df1153703860361c8c7784ab30c293627c2faa695d63c170d980c2309f17836f1b39cb84f486d593eb037189e23330359fb2ec940ab169a5e02eb6e889210788feff1a9e6f31fff481a39b249563c918eea19d345eaa180a4a2d4da934b91b36824ed1d6c4866e2d97247a711b6283756181189585405385a85a51ba5ca8cc3cc6af989873e85093a525a121e0d244d158e2242a7a85bcf1070019a2958a0a58add3dd53d6ec8d667020ab9d07bfde6796e884d4220e62c9a7b1ffa87b70070212d313cba777ca091dbc985b06d88c27fa8d34bc950a94aa34167b823dc51070b73a083f8a61104ff04c24cec24910e793b1e3399a0e9a792d1fce5101cb4859d54d7713eded17dc98dcb40d521db46c6de15b1debb0ee84dab5281dd6b045c0e38085b7358b7db1abb3b1bc82c41f2f17b22388acccb0fe0a8c82cef10c4c8eaf9575b9600aa717148e17805b17a2643f33841d99f4d5d3e56910c64c0c933a03abc356b065c6aadebf08c2cefa396cb3bc84fab79324b6e9f1f8ce1f9c696bd8694b98b3d7b4994a01cbd6255069d8407344eee029baa31ef59b3c0e610fd2dc33dd428685d5d6e7878d193d8a82befac6ecdf320bd0499db169105acdd0fa4d5e1dbe3c55e23a3ed677067dfc88235e84686168ffcd9017d1d93fb37210469f300220a9b0ca7cf1a20d963678f949895862de81b06f1d3dc45958363194b9b8fcb64a488c164417ba15a2ec4aa010602c0732d68bee394401f20cd86612b579cc6c1481853ff1e9a00f13bff307e75d0ca124cf1ed7c17019dc3f0cb47bc3a0d6ba9dbcab70ddab5d3d90b7fbbac868973cfd89b2c88c36aa77e43d83392caf359e38398861e4a00948a29d8daa71a4b3bff4a65427894d09cd70f82e6310163d2ef43906722d06b60297f50ef427b5af030a1d1ee92c667e485927a89c9b92c3a8f914acd08eaa40c837493abb915786ecaa6cf667fd21121e6e1988dd03cdc4b081e96118684758492108c70248c5038151c6af33d041843bb4807ce2cc794a54349667a5df76af7ee5424df7122eea24f9e677b0b566f8a203a1e90637cf27dc2bb7903509ca4992980b865ab615d73fe72c85b708da54e5ac0cd3e65bb39dd05cd063416a7513acfa0d3283aab70b2bdc49a4e75061f36b1bdf1b426db2f62a92986f8aeec4ddadce98bd5e3b4395f94eda2ecd9924d38c6eac139561659092181b66e5bc0490e6bdd3482ca2f6df8a995e41b21429e49ab3c7511b4c88da7d8788dfca8005950673bcd370b874de8addf80b89af2a51253a9880f149ba7229ac76a5935ba49bdc805e6206f08861ae45eb2981678ee52e307f8097a5cf59bf2145401c01b1a028a922a854ba80109e6e924b2a722bacdec4b8e2d7430a99d02c3780b068919eab9fb980b425fbbd358727bf5a40cbf867ed0abaa2356912390bee28434c5420454a8184ea6999a0833883357f14d753512114ef80a4c2b9f530f2ce8a640090881186f4188d711058cd4ffff3715cb863ea8e14011ff1a74ab7995a30dec5c316ac8446afc206141fc4a7b23bf519f5eb5e9d5f38ca3758c6c5e32878a512d5bb1a74fe86a7769c7a34d70d95995a89ad05413872d0bdd6b2939a8e7b287fc4e014b9f929a51124cc1e788f04c357849f065ca836562a6aca6249661ed0f42998a464970cce5f703f64c82693fe24fd526160a8277421c3fa86146bf87fd16401749aa504c1c4de7e07a30f8a39a8da67c244b0c88e247232428219495470b08747bb10d42c222919bd24bbabe478df7e6e86c1607139db9466ed0b2de98ad1fd9da94a516f3e0af479b30f01014450fc2455454362bc2c0ec6de2b37001cbb8b5819887eb9f4244ea298cefad682ee85cc91145c8b01059a56631a0ae98b58bfe08de85de6e8bd4cfc64561cd55e03b40bd1460fb69334803dca2020afe63a63640b2a4d001ffffffffffffffff7fc1daf6ada61ac4c894a4f4840945a26a76534a49a694b28fe6859d2da7029c23a411d267fbce3d082e0ab70a0c0a9b8911266194a9d5512de15b5a8449507a357478ca7b165284616f4f7c4b121d22741261aa52a97c54c922c260a7bde5e4b152823a0f61f8987fc15a43986488cf65b5fb3929b310264187ebb809da316b2384b976ebfe4eac0661dcf79363ed4df8b8980e4198c4d2751f7425258927e14247204cbeabd5f92e3767eb0e4098b28ce988ac7e0f1d7f304993baf54949aaff543f98424e654f22323deab95de8e8834992d27f3f4e90f7b9b3850e3e98ee2ebf8853622925dddc8e3d98ebab4f0ab57441d534a2a507937e4bd59ede3ac7b6d6131d79304996f2bc5c4cbe946d192b3af060ca761e848c9cec6dc71d0ca3259b506d535db58ee8197630e5db6ad10fab55624645471dccd152b415bf0a293ae860f66c3a96b29b34f512cd82c73b8a8e391877de4b3b54758ef0921574c8e13ae260e5d5faf324a59f6ca0aa4f74c0c1e067a5a4f0299a6372de60ae1f7de91f56d4cc6de870833989e1b1f5b1c289f9db604e6f16ec8387c706637fb59959eb9d94eb1accdf772995643626680b183ad460d4cffda9d29e74f157313ad260d219e22be8e912619655071accf76e3907257bfcda07018e37e30c107c114607bee88005120844123bce60ce0e1a1fbb9228d5d78856411d66084107eed92be5d8ad9738cc28a3830c468f27badec5cfe73d99f1397274c718cc16a4b5a76849aad32d28870e3178e14c5ae7dddf894a18cca7a66a2ceebba9f4c1e09f8eb0504af6787fc16cad65eed7a73c7bdc0ba9865272f8d7d855aea30b26f9bb63c9ef38de1a56fd1887e9d0c10593a0f7dea424d654fb5b30775532edfba816ccfdd6256855077d25990593d2ab607eef2e527e2c182b94f89472aa916ee257306e0513f782e7b8cf8f15cc3eda72bcd0e92a98b25f50a9b29824c252337450c194aa43ee7cfabd0d11193aa660f4d3d1ff1494c6812fc0f8a2c6d0218576c475127d1410eba0f3be89b7a23c50d84ff49895e4eafa04d463dcfd2e4ef8a46e93e7f2496264d804e4f55d59cd042ebb28b18a59f376772ce1fc1b0db57cf2f6564a784fb864fb96325a064931a12309e6be522949d2e3888e65b407b23a90604e274794f6578f6092ea7485cc8f270f1b093a8c608ed17d4298dd6913fa11b53a8313061d4530e6c5d5f370b989a18308e690b39dc73ddf2941fa868e2118ae63774ef276f26793112d3d4d7408c1b4a7fd4bea585797c900a1e013117404c17432cfd25e4a61be8380602cf14decf493fa64ae5b858e1f985267dae6e98a9b5d6a42870fcc9ded82e89febf84c8d06bd30eafa65594b913f8b9b4083172699e17ad66795c64d68ecc2a493e0c9726e87a343c04ca0a10bf3e8549383d858faa05c1853f3a4b030e2cea48d062e4cdd61a34b0efb2dcc6969c54357ba1b93b685c9cd84130fea5d2e8db53005b9ba7f6f528dec244aa0410b7314f9eeb8154abd476fe06454408f406316e67f13ff2fa7912c4ca64b6c88bbb4198d5818f4436e78ce932afc0b0b538a32eae2af1befbea2cf2e32c6fbe4010d57184e6cb93d4b63d9b5d40aa38fc92e9b2709da3cce0a93b070ab3bd135d5b55598565e453b9c7a0f175485e172f3b442cea9308738c1a42494d434c9448579940939a2ab941cb2e414e6349ed6948ac514a60f1f4b49bf7f639f2f8561439cca3f1b614245a430edefc9614cdd44b59c51184b52a792d0beeba92ba23056c5f48ab585c2248fe8a44b49f13fd40c0a635f0cd3b2237a7bea4f98a409fae6c4325e66f484613d9c27d9e9ffba279d30670fcaff65748c2d35270c323b634dc4e2a9a56cc2a05f4faa144b4b3251d684c13ea528ba24b982754e26cc2e9f4a4c344bf92a8f09a3e79c27a860be255b7a09939c2ce327e46409932477b23d0fb312e6f061ccdb3e67cae72961ee6a91f339dc93305576ca29a792306bc936614b9023612e3593b4e2f476d61412462b498ea6dbe26c75fc08c38686126c46d9666e8e308c7e09656129abfd35a22ee5a76f79324698c2e8f29283091f2dc945987487df5933cdd65129c220d5432e96b475edc9449852f98c275951e5a94684d1545e4be73f9a12d621cca1f27353653684b9f48caa696ec95b5f21ccf65a9e657b1bf2530861ac36795d94990e26cb41984adab8303af59d8ea1204c3d5772f4d8268130a96f159d3c9785271310a6170feaa398f707939eb8ce1f25d5e9f1fc60aa9274de5cb33cd6671f4ea2e7a97c30a8746b6a4d0eefc1305af5b5f3d383a9d3479977adf2606e4f2b713c3eb787f0605251353a7fde01a523bf75745b763025cbe964892e23bce33a98db93eda7aaa0c4d1131d8c27a7b5cfff59e304d51c4c226e3fa5bade9d6b7230a8b86c085d495279eb3898c229b94b573a9d2f7d7030874f734bb2f5a8ceea0da611fa3abd93dc9f25c80d864f9f73925c1a27e46a83f9b6530cdd2394b43f618349f6d0273c8c5f8850adc15cc283853f25a906f3e67fb231494beb527ea09106a3558a7a615292795930d04043baa6222fda98badd67704c92e5822513fa4f3683f93dc9e6e2234ef4538d68d128c39b236a3dca43ff6430f5094f3bb12d4f2ccc53a03186e5c47eb5aace99a3d1c00641430ca6fb71533ae9490e8d028d30983be9c9398e99d8f97f3018bff533f489de13687c214908a1272bc9f60e99818617b89492ece9953c2e991c63a5e47212dc353b4bcaba60b094240b4a9cb15026e702da4912b682ed5b3065d7bdd07f37b1dec446190d2d18b3637b4405bd2575b60b34b260eeffa0b3f4535de58c05837ce927a9a5f76b562dd0b8825174ba7a7ff9bd5e1b1034ac60d6fb18f2a7d62509bf88486278a0510563dc75ae3f51b451cb081a5430ca69cd577eda8c34048d299853896f95518286144c6b7931de5252e5f161e00b30cc17607c6101309640230ae6b461266459f2246ed5808dc740126840c1d47af77aabb9f7571d197c6a1288943ed07882d972cefcfd4a97e9222798464b9824f9a60553e588ea075260e3068d261874568ba5f35d12ec438309e611baae32aa74c9f89760fa4f2d0f1e461a4a308cfaa97473311a49301a48307e9fa89ce4c5cab4444444442e30822faaa040e308348c608505348a60b2e049ed8367f9a93c114c3a5c0ef7ea9097f46808c6124ef89764e194d7470826f939a5f3a6e4f31f04530962a4e8126a54740c0493c9e50b93730c955f7e2005366040e30726cf2642ac97e8dcedd2f081e1e754fa7a5637f97a61124d2c4194ef8c0713e585294f3e59ae4b29954fd98529c74ccfe96bc99a5117a6d8b9427ae9248f342f176613c5a4d071e473fc24b83087ea52269c901dc200b7306deced28c14f34a97f5b987c4cd78d9df4935faf85614f8997cde3cd0851222262a3f40861005a985fe3fd439ae8502545850166614aaf544a494a34662c1b9558290c200b637c8df8f825be418618381043066298510608104b14068885e13a590cbd99a16b550211113050f0051826a0c0ff05fccdf817e0e8305e61005818438c0a3f7a55a9332c960703bcc234a264cb922c8647ddd760005798827de815ab2ca5ef7f800d066885b93ab8899d7f56984ebea4a3b4a75661d0f1e2659bbf785f160d065085d1535ab2a4d36980541835b6e4d1974c4f3ce50d320296077334b17f314205551be1c19c9f4aaeef50a94451ef60160b969277653bdd693b984f1253729ed1296e9eb38f3a183d868c3c1db70f3a1844a498aac939367e01c2c71c8c5ed5975ad3a3dfad363022223936081f723029b9173a9d8e22e45c3272dc200355a5f107526063031f7130bdbd78ce6d117ec0c154bd21cee449552384fc06c3094b928c2825c5b9edc30dc650395736fb102faa6e83f154123c89a53d7f2a668329554ce55bf913aa3e35f8588329495290ab5522567efb5083a99320cf44a94d9fd309e3230d268b93cdc35c7bdb0f34983ac9b94dbe6061bfc42b838f339892fcf94e2f986630a8bf2911b1d79b8c31f2a30ca6cfbefbf4e95bd265561f642891187c8ca14460f0210663a7ecfeff498ef0eed147180c3ade7592aee575aafc008329a8ebb4aa612599881f5f48b4f27e4ae6bea32b5a840f2fe047173eb860ba15a1e399ec5a52098d685d60045f1c181f5b48e892f2896b269b5813317c68c194293267abd42815da187d03471967d8880119648ca12602a6c147164c27e5d6cb0ff36ba5c682d1441351b264fc7105d3a5c9bf1e5633a4988c688d314623bc7101fbb082415a124a0a27287d54c164275b584917199ff44105b30942dc96588b9524f5630ac6d39d2b5e3839eacb924344e44a0a1f52a8c7ae82be34eb88368e7c44c1187e1fd4e8fe934f8a65213ea0604efa3a29e1640b15d6673f7c3cc15c39fec9f6217f4b0e6a1c3e9c6050426d849aed174b761e3e9a602af9bf4e559674693e138cae737289fae4d17d4b1f4b3056c8ed85f720bd42c7820f25989572afb636a25583c6513e92607abd51ebaffe41da67442b89f08104935e37113149f824bb5c107c1cc15872d8cfa54fa76ce50bba0c2bd6c087114cb29975ca39fd051818858f22982b8b96515af10aa41b3e8860f2f0f5b1e1630826155392e93fb73a1d8b883c0e32d0d98710cce99fd2c79a240e313a046747f8088249bb969d7025749fbc13101f403008719d72efa71f189428793cdbcb97b6cf870f4ca7847b923acd4e54bb17a6bf573dd3ec99d59917663f312e876917e64bb13ba920530978e8c26052ec0ad282147de9cd85e156c45a7f0aef26453d7061521b5627f6a4bde4f72d4c4a34934b8f96106261a5c2c316a6d1962584164fdfb8bf00e38b303af0854802005878d4c2e06954f44ef2040b045f84d1818267a2f0a085c9049d724e4df474f929ea310b53e99c93e487125e560a030cbf31867ac8c29447bec493f3071374ec110b73e849629fa699245d3fa295b030899e2da785cb7e498ebfc2bcb29f4d87911fb63d5718a45eb8ab987a51d4ff2fc8c00538f005185f64805b61ce1545890f7ad45f80f145181d288188c8611c3c5861ce29af7ded69932c9f5661b85092b4f52699c8e7ab8a2a4c66f1df4cf2d23c52611065bdee1b7a5498ca2b972067654918a15398fff39aa443d54992200944444c61366d1f43a7f830e9e788965b99a5c0a31426b126a2a2090b8b14a6ca5797a44c4bd1a35f1ea330a8b4a42e7774f5a44daae0210ac42595628685fa49d9300b85729234b97584b034081ea0306c297793d26cb6fdd4fa82c7274ca394f0ba31a663e4670b1e9ed8cf47a8a464ffd87aa9c4cc071e9d30c8755c96799277799ec18313e66afb207b46c96a0f1e9b30ba7c3efd57a2ac08b5268ca76a9f522aad15fa66c274aae41c637205134b1413264f51bdb43cccf7c453f0b8843946e6760e4fde37264b183e09f73032f54a9843ac5e6c9c10254ad9f14e279dca491846d6f96913f737c6e22109e37cde9724fec6b5c7ac8cc6718248184c38153afc2f48984dde9224b1baa4bb6c5a2c783cc2bca1c3ad7bedb7e5bc230c7a3def4982c93a7fb5110655a6a4f43621469884ab9c1115d7744aea22cc25b62725469d28153c14616c99d19acbd09fa64e84f942a924f2fc74493537c10311a60ea5fe63895a34213b84516479ae9f4d37d152c510c693916b2dea62f12984e19289dfb72bb94a8e9ac18310e613e7da565a2a8e92833c0661ae4e9ff7fc5dbbf534a265b7678011820e7c11820e5800ace021880493d546784c4bb9dc2b7804a2ecc003101e7f309b96937ac99320aeccf48349124d921f562ce7e6e4c6196886471f4c2aabd2c754fb45c7235a37cc7078f0c16cd9fa7d4bd212f5eb81c71e0c624384d0173ffec9ab871ecc79679f42ad524c8747b4c8f0cd83f1477f1615ab2c85c5ca0b3cf060d4cb5de27429614a850d1e77302529dae4947e34a255061966e4c03578d8c1b417ae9ed54e2749504d0d1e75309874822af9a4242cc8fd821b62ec1b3ce860b89c427dbfbcdab527c1b5c71c3ce4502223f08883c1bf93a0723033554a64c3030e1e6fb0bb2aad4d904b39e584cbc30da6be156136ba010211911b64b4c16462d6ad9b563af1f488561964e0b0001a3cd8605e1371d23fe453a9ebb10693f49674d8a8550de69c448c49ba5aba59a3123cd2603e216fa16efabbe38d06f3e9f777cf6c57caca19cc56e2976a879cfed3ac081e6630e7522fbade64ff90bf0c66fba0b166c2278329492b26e80e7fb2fb670cc651eaca934a9fc3e92e06735e92aa7ab6a38d523d0efc220c0c9471c3230c660bbdd60c133098c49ab413e344bf608e0bdb3eaaa41691534484081e5e30e9dead10b648e2d10503787001011e5bb0f18587166c3cc0230b8907166c5cc10a1e55708007156c30c0630a0af090c2023ca290e3061f83067840c146033c9e60e3011e4ec8d10406783001011e4bb091000f251cc0230909f040820d0f8f23847818e1c3a30805f020420d8f21d808f11082c95a4f7a12b2e46852a4038f2098a4e705d3d5713988fd8607108ca74d5cae8a4bf13f1f5132ccb8a1011a78fcc03c52c7d327dd4620228270a4870fcc734af8af98cf883f9d8e5e18466e87fb9fb99342e78539f55d6a48d7ebdcc6c881830259e8c60d7260043a7661d6fecba3d639268b98820e5d98f3f78c5b124f9ef4940b833ce563c26551554a1e17263923b733e696e5e4b730e7f076b2ab7633efb185491ef527a59c949dd95a0bd3e68aba4bd2c51354d8418b8210f9f9f41acf8d68ed143a667134e89045a23e4b5ef1b2a0d37c33e888459da43f8bbe57d24a28830e589846e909757d7f1e2c7e85b97410ff5713a54bb4ae30086996bf9753fda3d90a83aeb499cb96040f156485e9931062a3e53eeae7566138418d94bf2df79c63aac03aeb3d493756b18e541884fa0e27523f548a860a8328d145f3d55abd730a738776095993c37a25e9308579bd2b55d29e3ac90e8e60042222ff171831438c138cd16f0111911cbde82885d9dc53f44f82520f1da430999c6cdc3f49599416f3d0310a93fcea1039a51485c9a4247d50e1aaf21e33ab1bfc1a133a4261348b22e71b4afa789503c7a71c37100a307580c26cc924d5c934dfe4dc56d2f109837035d3124ab77cbfeb095392f1b11d6d7a2dcf4975c22ca255169eede1c39c305b08e16b9265790cf9a31074e00b11911c39c01011112903f843c7264c6243081d53673fa2f239d0031d9a30e99ca4e7d4a64bd405c7db28a365a0863b326158136d62e9e94b511b699830987cfaf1612978d2df20a38c1c8f432f61f0f4248d49e9f1a292a8250c4a495379a652877974a4a31285bcb414b6939f508e0e4a18632f49b3b7fd6fb28e6889c19330a69e1cab249355cf82235a39ce193910051d9230cf8d8e234d8f5b6726e8888449deaa9343b4081dcec006196640c224b576fcf0493e95e47124f508537fb8ffa4e289122607031d8e309b8ff4183aa9967c6a84496ba99179aae324695878bbf6fcd67fc6602a55f1fe9292a6a76f31984c3f674feaee51f40e83f9d409b7236f3098d2fb8ca5e5efb8327fc12409637ae5c2980a232f9862c83a1da4c9254577bb60ae8e1142b6c75b2571c1e4bdfe295f0af22eb905b3579292a4ec935269542d9864cb2bb2f55d9dd25930082576d6f4bc56a6050b063375bbf93f3a27a52b1847c7e7cf355ac1949456e8576a5530bc5d9864aa51c11c94988a554f313d9e82c93d681345acb943009182793e5a7a5039e51027140593e7f4f87e5279d2412898ffc2525e7d47035c04902718fdce04213f6f37007182b95e94144e928334c1248c300bb794da43da39468a4198600e972f2975d05982714dd6d1b6a54db6de4aa8d24413fb241527c1e01e3eac2a5a4a26854830e91227874dd1114cf2c3e79ef017a552ca08e6120f25fa256162c24911cc31f37d6dcfb23beb67d80d102298046d52b7deba7e978660b6a4f36eae89eff974211894ce30f9fa92f24e6f100cfa4bc8ca878c31690182b94dcd82be24efa9f87e6012a6bde1da83f8c0e021e7c45ead17c61033a6a266c60be39ddc2edd33673a55bb308cd03fd5d176aaf7a40b53e6f8274f594f12940be3db6986ac786279bc71614e62e456a986ca6ea2b73058f6c68ffdbafc96b630281d77846ee911da83b530ac9afa3149bfb3f3460ba388b0aa30a2c62d9d8ca8cec29c26c9739e265998d494cd78bbdc79b13069930fb28359a758725898525ef74e5afac207f71506add559bb3d4e5fef0ae3da57ca8b55d5688549c3c43c196b6a4c8cac30a51c2ae53c44c74f921aab307ca585cdcdd15fb3aac23c2327543249eecb3aa60229a673f4f31f1526b5d649164b5e7226750a7369ef98b0976dbf630a83fcce27e7b694c230ca344d7c2b15b498235a5a17a8410a2e4cffe3a834374f8ec294bf7766a14d8e373aa2304593f34fbcb71b62b4198f23a9120ae37aa7dc97f67fdd6441619e312929c1b3a738ef3e612cb73841ffae930a560d4f98aadf2de7cffe096a74c224cdead3a42931257f6a70c2b0253d4b14d9ebf994d4d8c469a244528d4ca07396a96f2575500313c68b272ea684d2392b4f70d4b844d6b084d1774d292b79b6cd4b39586b54c294cff34fa87ad9b84f9712352671cea34b92d3237a811198246a44c2985b6a7916ae4cbebd06240c761f4a344d5a55a5a850e311e6f393ccd4f25f8df838c2e897e761d34ff887bb11460b4a2935a6a48c30ed8c878ab1e139afba08b3d87fe810a322cc21a4f7294949fffd5f228c5a6dfb22564498d724f1b5e4b5f8b8dc214c6b9f3abeb4aa5a9219c2a4a5754dae871a0b15c258fe223b959cc27eb408618a7731fa6249d94f3c0853ec9c37bf54490d41984635c5244bf741cb1fc97146196cc61965987106086a04c238bf7b79dfc9c3afad0108e325b1fe6dc6624972fc07a3e8b513912608ed9cade107e308134c7cd0a1e4ccd40753ae3d39b7867bbaa7f960b6cb9d6c4daccb4927f760f6ed284a1071e27f146be8c164d2af3c2fe5f150230fa6ee9cf3c3c9192bbd6fd5a1061e8c267fd031babbe5297e871a77309c0c31d915cf945fd80ee6f31b3952fffdb2b53a9873fc93caffe470f724235abc861a7430497174dbdd44a7cdd2885685e161ccc1248cefa7924c3041462a0793955a4fc25efef01aded5888359f5d4b8992eddd58083d1920569e25c8959d1dfd57883e945c929c775bb44e5abe1069397fc501375d406638a6ea5a446a5125a6683313c4c4aa3d3ef46ff35983c3b9d98e8e4e33bf2420d3598b388680daffdf417a7e13dbd1d65e5a4a0c1b4a3820eb26cf694eccf60ba9445597d9520f34433988292e4b89d5b77654888e5bdf86825329832745fb85ca5a724f93118743e4934a99a952ae5c5604e92ac26bbc975188c25c84c5d914b6697dd81c1f8a1ad2c9fd0cc3b551b6a7cc160d2bff5a94c936f7d2f98732fe95d9243ee96ac0be63856f9ebb76235d4e082d1848cb6d87b3d19f7196a6cc11826c5c595ec4b7d590ba6a4829e606e29ec77b250ca25c9e432374b96e5ab8105f39a78d9f5b2bfa6ac47861a5730a8f8741f537fbf6a5bc17479a5e6b3fda3685905e3a9bafa31b1256534b030d4a082d9e4dd855512de94245502337a8c0b8888dc7833cce831d2136a4cc1a02ee3c54ae5d3785f0ae6e46e626b3b59ce698f42a7ef45a5ba1287824157e542cbefe8eca12718df534a2654d79b748f130ca33f76f487c50a5737c1f4ebe2394ac5e24b9809e6f81216daba4b087997600aa74d9e3e69f9a754259844f7c3aa754e8a154d8249feedcd13364582b15cce6455ff39a5cc23983ea628e192340fffa5114c72cf52b789563fa35204737ff0a04bcf44308ed24feda252d8d786606a2bf9a0b673496e5284600abf3dad2caa52888260b0cb26b8291ba5210482b1a28d5e2b4950e1ffc06cf295ba134689dbac860fcc27bf27889272744b82bd3069da28513cc70b6305957f17a61c4fad9c989393587561ee74229bf5be269d960b532cdd494e62165c987cfca3b5c5935b18e6f38285314b2945df1606f9be9cea738afae0a31606333dd9246dba4ed22e2d4c679f25a857b22cfd631606db39f9c35bece0264916a64aa224a53ba59cc6a4b130cb89d3a5df3f2ccc1657be4a99ff0a735759d261d29624e8387fb8c21cf369f7e7cff4848b76e1a315466f11cb29f7092b4c5762d99e430951959509c428a3590c1fab30965c7f69534a235a6080718324068e378107ecce48138c81decbc8b104b8c2872acc2687cad29fdfed4d38a265e3ec2315e6d3baa3fa3e16db3f235f8481011f438c3350819f850dffa0a75398c4e56c41aef7e9ff20539853bf9f32e5d9a475f69ef0510a839acc7a68cba7925c2105fba384970fa382430c1b55da828f51184f3d8cf819a5bd8479440b0c1fa23095ce282588b0f1d1ea88960572f0110a932c73ea39948bc8523f4061505a82998fec75733ba9c2c7274c52cd57c9755017a935e1c313e6ce90190d7957663989a9e2a3139e97289f5192a5d4dd233e38612c41585aef961bcf95c3c2c726cca6a264e7ac75c26e55868b0f4d98fd94523ba993144d4b69848f4c18c34c5fcfa43d7f193161102647c74f5f6773f925cc259bfceac5d612a670a28dce58748c316e80c38c4a18a465d1b6aa33a7ae82e3c718a30b143e2891ec14a35e9754b2923309e3b5bd5404f1210953b83c16a77be5d54f9130da9f8e9734f793feec885652f7010973c9561e4acb4d3af9f8116691134ee73fbbcf159bc287234c1b5ebe255e0aaed553e1a31185111f8b28910f4554216e624eb893b4e1de222262348e302e161f8938251fb177693c6b1bd192011938928888162b3e10e1c9cbbf24a9a9f40f515ed591711ba2bcd4416e7af6a51089124fc7bb6cebc10f42243d679b9e7cbaa3083e06810e42bdd4633f43fd108439c6cec20969b2cfb64098cf94cc18cb4e76150408c3e9b3b43872c29b29f9835a31535bd62a9c2459874977ea6be342c1871fcc1fca4a7a3b9d4e905e1fcc7d33da3d7ed976447c30c89f51d99eb64d7417868f3d18adc2c99cceed430fe60ecac27b7b0e628401f58114d8d841c82d4ceaf7dca49ab19c64ef476a50467ba092625b983d6a5d9ae0ba9b6adb1b426a61b292459f2494561b3dc9a28579ce723095ad47cdae112d1411a980c81f4266610a7f4af98b3a65617e53d9b2a4b8204a9055482c4cf94a2c8d9adc1a426061ae0aea4eb614a542cbf58a2d9cac899924271be20a9328256de55abeb69f642b4c97735049105f92841056184ef57e0a95157659648490559856ad52cea7a454753f4142882a8c16224edb270f4af6ba52610c1196432ba9937fd4235a050e21a830a5c88a25c4ee9818ea0d9218669071e314a6bb4ea3ab124388294c7a6e4a6e543ca2658618fe38105619424a616e532a888f9dc7b2a46d90916e8861e3cfb8f134781c6721a430e9b42cb173fbd500055e06629861690819854907a579254f68955c9510220a836953e23e09534444048961c6cb60c40621a1486aaf8db4b424509845ee5b2bf6c204f33f6152398cd9be78189f8f270c1ed7315466a713e6d58dd16aba93cc4b73c2d827e7123f7729db4e6ec268ff29ffd60535612ad124415d86d225f665c214d5fd42ef72929f649830d656101e6fa54b187ecdfa749493f733b3446aa724217410cd13c7e7b8125209d3d7e6c5cea92476273672942aa33d40029b4008254c6652f427d5d2d0df47542761ea0a71399a509df97b44ab8c1049e029ac49254cbf7846b442502261f6f8d919fa450d8440c26c62dcbdabdc0811d3234c82f4527671438eb0cf24cf49cdc7eb819046983a4efe4995d629df87119bce7711864f1e4adf7618eff028c29c336f17f79599fe26c2207fd49a98719bbd154498f36a86d598f010c6109692a9b8945ec7378429345b94ecb9fb4ded429846cf887117096112f3c9e329395ba9f106614a25d674572f47574a1026a59360d28d055d154e42026158bd202374b460b27e96e0075260430077080184d12f089d4b3ed3bd7fb27308f983a93a7ccb7786129b37c40f66fd90163d7f56d2392c88903e98947fc79bee0abab3a61b62941c8f1005c848373490630c217c3049b20513642f69442bd9aaaaaaaaa47857005e08d98341fe828dc8a8a454a79422440f460d9d517296d7ecc75014217930c8e9da93555b1d45cd89103c984d9294855039acc70e22816222e40e864ffd6582b29ff7940bb1c3973ce5189627b7c812132175403fcfc949591e997a6144081d0ce7962c85559d7f9768089983f1baf4ddc99d24662784089183298975727d3dbbd1f90e217130675513eb66c74d12ad1d42e060d6516bf2bb7f2bf2f406d3295137f973dd60186592243c96f4b0f7cd21a40d06cba5a2ddc929d5796183595c4f3ac984b8793743d660109fd54f84e5a8a70d7341881a0caf6aeb3ed28418ed7f2005365610920653baa8ef615cb48cccd160507d25447da666083983f17432dddea5548710331856e365e7c652ebfe66b40742ca60127e57b9f45a4e525a158490c11c8486689c5092204ace066f230521633085eb28738fab188c97ef66a287c1d8b39e2f76bb6f554e02216030f98a90f539f379f9f305bfea2c4797fa944b8778c118aab2466a977039bc5d305685cb9f55eeec20a48088c87e22840b668f53e99d2a878fd43010b205e397588a7572e9d8fc46d44688164cbde3563a9f73860c6ce00c42b2602cad7dab35d163150eaf1b2158307a8aff96693dc93f7805732c2d71a25a4b4f05252683102b184374ce3dfa28a5727c8c902a1877749ea062373eab09150c5e39dd54b38429dd4fc1f0164a46cd2d947c926c0c42a460166962394a84be5130ea9fe04957f09b99110c42a060aa0f13e32edb04bb31758a11f2048396cac552c1f166dc782718ec334477b0ff8a4fa5902698ed44d99bcf3295a4f94398903eb77825e7c32529214b30278bdbd962bf4bd4625a1ea204a32925cededbf478990483ada528a1bed46949428241e65625a9c352dd3378f4264844c8110c9eebb3ca5d30955337448811ac9c4e652a8414c1a0be6c438e1cf378417f811839ae6c541e428860ba946ad22e88d2592d22225f08198241e752234b09937bcf179fd773e81e5d164282605a114b23d62516428060f4dc2a2ab337ce83e807a65c2b0bca736a4979eb19427c60be18efad7029bd30f65b8774d13103082fcca3a490ffd1ff7e27681746f9ebe02587105fe6d185614bacbb7a13e335d53180e4c2ecf9ad92fc25579b1939ce701b2fb821c61e105c18434e9e2ce9e46c80dcc2601fefe4cef3d3f6a609406c611095217fdd4d7eb58db100520b93fca9eda49b574ffa314e9b154068611a0f5339dd6813543608209ddc13f17902882c0c66e7694c454b712c4cb12788f87776655b16781c645ce002144062e008c1182d8611119101b2c121406061ba132d5b392f496e4a90571874b77b988f67cfc7415c614c533a75ea6cf6275c405a61f4b426bffe4d320f2b082b0c6f97e4ceee55722d02905598e6c4662895c28a18f35f703300518549f989d696f74ab23bc9f022032415067d4a2721ab2f367288711a302387182ff81188542f4050619027bdae8a9e9347791c8cdd2f905398f7649de7e422105398f39c8a7d0b3247ff0909404a619ad53091a17320a4f0cdb39e308e02b71259a74e681193bb358088c2248d5e631c31d019fe38d40cf46588884c012414c9793372eb2f5a308d68bd67523a0001854999f6a58f936f4a5f595f84818130ea4a09209f30990ed1a6db664d8d50d69e274c715775c6ffed8439fd42fbc6ab73a5ce0973d0a233672a6813466d13624e89aca4f6b1268cfa27ce8441df69717d4bb227264c58a6b457d609209730bcc5106192785a3d9896307b529245bda036dbca2e8054c29c7358f98dab5d476b440b475a008412c6ce3a9fc451b2c792a4500032098305fb2cc2430802104918d6b4898e9eff9ba41dd1da178044c22444d6961c4eead0146d00020990479845c8888e95b63d3247183e28c1e4e82f96e30bd208dd928c2b418e52919719234cea434dd0bb25c9d7e58da7c12fc2a0538cee0bdab8d1c8c6005184414ff46c2ede9ed7e28856810148228cee398d5a1c2585de685c00418441abcdc44fe5fdb1191ec27ca99314bbcb452ea9ac0062086386a598a173bcbf7c17c26027c9395e095979da11c27482b00f254a8e1ab90dc2ec39de9d78955fea910119382ea005104198d7d382f212f40409842645b7aab65c5e5e6a6e337e3a6ab6f25a667c8e0208836849fa94e7e89c251405903f18cc2d7aee93a44991cb81f8c19493ac5ee27f4ebcd79800d207f37d7ae750e92e2995ddc2f1390e02207c30f969cdcfbe37d96ddc8339975b4a9fdb4ebe7c7a309cbadb7e92aa630895f200240f0617578d2dbdec652a081e4c424c124aae7f8f27852077309557e8f057b205fd41c50ea6917e27c9afefa4ed4e40ea6050d91931b3e8609274ae24c7de2b07afe760be2472b1928a96b58db22d80c8c1281e6c334e922a8eef7130a75373e9e46e372d4946b4ccb0c1dfb0510aa140a40a2070307749c9dd544a81bcc1f85d82e90a9971eacf1b642c1540dc606e93e4bad7cadd9877643d90ba206d3096a8649dad55a37232a2555f84d1016c02081b8c96e4ad2b86aadc8fa500b206a385351344895592d8a906935029ec84934ec6749804903418afd4766e4bb50926090de630c2ed648f3b1df3cf60d879ad1326577bd2de0c9e24c4a993cce3653099783ab1f3956345880c268ec5a8f451c632511c0c4641108441104322cb05f3120000001018938582c1783826d5057f1480034a36264e323422342616180d46e2a130180e8442e150180c088582288a813894c87890ec04f64e6744f1281f7f4440079df5fa56c4029dcd91f8cec1826433e3e1b17891a4f29c7d76d902aa8c1b8743432e591ade0f0256d4bcc0b03cb557a467c5748d0066e290f9b58ce807cccb5bf0dc38bc206017ea853212228d35782d4d514d2394ced2fe0e90679227e88cf3f3f7503c099b500faf130b09ac4affd61d425c8ab1f3122ac78ce2217c7fafac07726447dca9862ee79b3c78c12a1b718c4bfcd02de0a1d3b5723a9f2397c56d60c3f7f5edd2c6f2db8b276688f22c9167cf3613836af38670345e114b85670ec50f5a92bdbf64196e0bda9e24eeda6f76b3f05b3553be6dd67aa9733baf40129a538c53b412181bdfbc2186fe01713576f63ea8b6c59d17e82972b6bbc0250f3019f79387ee76288892e289d3793677047cda414f4619d8c192a807051ef841266650da3cae93d41664fb190b81809c1e5fd29eaed0589f393f3ab95336680a67a147bc327ad6062e98bb2330b087e12e7013dc78886fe80b5ba882870a0ab679a6810c826704026ada98df4907c4428f6595775acd4e76bfe93878cc3f065101775944e7359c336d397ddaf2da186e065665e4aee8511a3a1577d805bdc5eb45e695c8332d07cbd74847d6bdc267cd784bc2039322e2056411e03ad87d6738bad4be4fb9bb3a452f8bb46014235060e8183e6e43bc92779bb9331fb981fd3042843cde1787bb648640c14e1cc992e803c96c087431e885db63b2468df0b9dda7fc84c604a1f3a4a3f2aece529a908ef9bfb3325460dca9428e19d8e165fdb4c8e439044cc3c90206d18a21e3ed624df0178315d15705311d75113693750d81602b61c4cd135c19e64fbd9a1b6168856852c70e009a52e60fde69bd3c5340c09be31043d74e7332b03a01aef398c0c2304b940dfaac23456620b4713d9588116d5e0d37dc805e3ba255b55ec39ed77e28fac13e2f39d615b9e96124c029bcc1bfb352bba8bfc66db0e2757bd8ad79c189c697f4929d292f05298622029917b5aa3bc5c3b521ddd75f1f8fe9d3cff7de95623a7d53672aed744d887e7991142c76abd3570ed81078f275461762f1856d55548b3fab00e18c2d4881ede46c4021f1c08b3f26b1fbc56ad77663fb31d91cb980039a44e0da1952b88494531d9c9e1e9182cbd7a873af010310dde0245856bd6775bcaa684cad72cd02aa65166819cb0239ae2c5084057ce5601a5fba6938a8d7ebaaa7ce1214014e35aabe6c2a333aeb9bd05bc4ccba0d68115fde854abb027eaf72e92614ad0dae3144bb7b6851831ca55c6484c5370e36156a18159ddd902160edb4ed7e710c56006c46cd84b15c424880ea6e32eb7cb77b89d7b11620ecbfcbf8609423b62ed4ad2aa94a4558fa0947c5b31e54d4187f6b10a9b99cd7164dc7340e81ece15ecd15543874cca05fae6c7609346e17d590a871d01b33cf44fed400aa602d75ba775764d42d77cad02aa55ca9575e64502d736415063f12afb65b1aba80c4db559e2439d6e5e0786ef2d4ff1449ffa2e8a5cf2ea9e3d6d9d106853ec40d5e5f89aec88a11801875f3e0920bd828cb6a271c305eb5c41d4cc08b268834c018d1335d715952b9141a1ee65c2e39f05ac2a29d146acc41cde469bf6d4cc1e9d94745142d0f42702011b8b070744a8b9dac87c085cf7cdb25e4485bf847c33b7817fc821503f9791b8f85cc2387087d8c42d6c0c5bab13e60cc948873863163df74f5540dab050acb0ca8ec0d2f89b889a27480099fb397d9b4c02c5fe100a8c2ebc9c5f7f42728ca841b7c07b22eb748bea3324dd7c56114a1494781342ce25adf41c3702520cf6040c5b9da6b8f57989c74b1d999067c766924b2b03cbcb09c1df27e561d6e34083d06bfa3b360a37b63b8f570dda0713046e378110505b450d0ffc43c09f127303e9a1d69fd46309681e9b848f361877b57c421d58c90edaa78246928b0958a2d58d4bb09e66021ea808556854048489956ad82999c9383474cbce81b2218e11605a76aa350b0f1a4f031d9de5c9a9d90ab3b62b563145c7488b757652cf8ae07870ba36f1e43d714cacf4dc9f966c57fac67ffb3fb9f7c319b736e80ae55131b09865d4a0e22b1fbcd0e13a193fe3639d705f733822b9d8fbeb5553f838a94c8cf28d1bcbcae44495b79f4e7d43868b95ff54558a36323325022fdbb44d2926a86a8990870af0a51f0246574b0cc320972d860e059d98484e8c8aaacde1eac1fd7d8c7cff32a15657bb663ca15b569b43c1a65866f0299fc58789466b036ed0503310e5e56a5490d2cf798930301c8f0b9c11708ce3d156fa16ef1a51739f0c0b7e13c16dfc428569f486b706d8e83105940c8170815946931d8258e14a6938cd7954c6c0509b7d3e3736b7f3e416a3f43fc664d977061c076c365a31861eed46b229cf55f01ef291747e5206bf13244742e66d8c924d0629c9e918b8ff22b149611a9f51a486c3b971ff62bfe0b3592daa8c52d4233b3708ef79a6961ede6345c1420c7d2b155b8746a4c6e8d139781c8acdb8d65008c075f7e592b28fd47b11e2eada0f719ca2379501ba28347943d0d80aff5bcbf3b6bf6cf74363c255ce03f57b5aac090642389b2080b273246db5434bca8d51199d0f932092c97a0f54a8c23907e9f2b51db9d299f3e26410dfb488fb45c0ca235614a57d2b4194829259bd3471d438efc98a14ecad3e8540729650e23ad2d932614917c0949dd55e59ce82703899f7756cc3dcd38783a2a024199960cb1cef4925074e339ea0280d8381b02134ed29c1f57ba06036cb25a688af5bebff927862a948d8977ad84b30a59620c222ca43dae7877e4ec8fa1a8f0023c23dd258c0547180abd4ee31a2be35e8fcdeeed800ac4e1f09a911b6899ed4aa07b653c4ae82786faf2e13da51e201309166237a40b4fcae22e57b8ca0306051897b0a51ecae62d8fb07ce14655ae09c87ebc076dfd3ccaa8137a54c482a8368402a61106689876261286afdda2b0c716a88a238e6c0edd075272d543300bbaf5416b4a9c8a503c33c4dde01c66468726a6d0ae20421bd35c334faefe47019f85d9d2a773549960da2d7dcfede59a9c9626f7a4a886f7fb98d9695c445b6d499e4d68f4c34dd6e99d1024275514411bae86b4681e1b90e02d1f04ea8eed9ae670e3e18445a511016505916f73f768461a3554bf98e383bf08d89eb6c6128385dc751d262428333459ca7e97e80fe989b20744d219f61a06a13a06216e4b9a21761fd72022126d0cc359714af861b004ce2e0d5a2a0d1924425c11f9834f1221081f418d39b7acce4da84cf00eb40c78db62f6f53e13231e03832c984d11182e5dc1c878818ec39626ba6dc66c80421726f3d6596c912e11a86d8adf6f816647802a0f819f765cf80de098f5731098a435d1e269d63e70744091f4eb27927029f48abfb3d9b79e7ede87e61e4f228bd5ce405561d0678a68d03a210387dbbaa6769ee1f9f4e846eaeb165ed59e3ee248afd3143c6c1abc837c3c77fa3bd4921837824c3197b1c7b33e57889ad317811961b2ec7b574066cdd9126cf5d6c9f3127b6d9b79ad2f32415b7eaeb500510c6fd84fef3823c43403b9580b12858af1515e59521c5917ca8110c73b828e25238ce53837179441d65355c8264c5f5b9b0f99524eaba8465d5c67529f82bb31152cd2c84444b2c4bd449280cce479fad3fbca28af42baf7f7521579e9c4ed235e8b3458a0282e46ed5fa816d580b62926563868cdfe8145d1013f5b497ce3d4d9ff602b7102c5083f05b2d8845fdd70bb70e38bed6abbb4c698d11f96b8b413175820bce871629fdefc3f18380af0b97ce15d973b74db6a0ba7229099387bf616741b12a4d210fb1da04463f96ca92aa033306e9eedf8788e08316c3806120242c507caf6cb33d405b77aeb9a4cbbce23234f50a9b8ff5793953b2146e0e380a54254c53bca7a36e126b5d0f2acaeb1b27e1916178ba1172384892355ad8efcca5007275d64d9755164674147a261d46af2fb8e9c7f02c508a42f848c2de70f6a68b108d988c53c8be00f36f051f441c3e36b0c2f7b07e397c12011f90c13976f563e6d23288f1b3c6f4679cf95171d1ec654028093a074240378e40afff6aefb0290ba08b747ceb5b8b6876e1e2270ba05c145572966b7b7035594c04dfe7809dd866c827e66e94aab1b98a3669b9069fe63f93d2b76c0b0966490136b93789281800446ccb1e52c8662545af7fd92cc20ddffda7f0eaa1401c2d2e39acfadc50494f9b7979b001c97b3c6b2dd12520736b8734f249696c8baa08575df0d6163aec9873a42a309f1fc1c9e74f99449ec804914bd6818a5b0d42774876a282351f5502e4393f26957d58755091e95cde9556a0b966a8e957e6ad74c81a816c5f4b5a232bc36761bb537ca304dd586730cad101ac2bafb67ca235819492818c8e1a8add3c49c1a2aca63405704dabcbd39d13992b5fd01711dc62c6d5cc69106c41fe130943c2a8f48b2ae65987099470655958026486e3ec4bf0cebfd9c05a16cf212af4d5838ca8d7e7da89ef85c488358dea661007ca6e9d6bb53bf698495d6394a1fc091942aae0569158727975f3b6941c361cea55ccebb8fa986f5b427a6c77f3c23647db8fb88601dcf1a9df047a2ee2a3b0c0725768130ce25cc45fa3dc13c53d42ab7c271181341b2d4962556d85187624306154d54fb23699af9052484e407009ea042807f058357804ed8caa90e62969feab7626832dff727314fe3d4e2322dff86348992a45ec35c6a679022682ca42038e6bd53543335dc059c09249318e095dbfc006d729fe3ebc20d9ac5c472fb1c7bc146ce9b092a13fefb8f705ac30dbd8233230c23a9aa770dc96c1573455f91379545a8ff5b1af0722c4bb3726b8ecf68040de72765fdde9afd33090555aac71b504204eeab7e7f892de5def96190540695ded4f7864d40adb0eca922a159fdc17ce4f25212169311b9394850497a42c000c137cc2b0db29e8f6e1e15eb2652a27f565b5c436532a0f2b51425fe627d595db22919b960e291ef0bbc3ed1f391ad82e75f621cd796f22be093054b7ca0f0249aafd87ae354728d4194a8763f6e7e9854c595dc4b1fc66c67856e6812f7ebac86e67a56f2232fca7c0e9d836ab1d102810643026c1cfa04d6c5c74ae17a6965a8435e8a2d8a2d3210fa1fc8eea17f866e51166ff226d2ddbdc9c04bd7f9726ecc2e8875d0ea311ecc95641a989554ab790f39fd1acf9b7dc82044bf3d4e9ad72ac547b650931cda7cae046016d4e26b2f653b7c3c7a1d67ec855ca2e8df22b593ecaf576e6f6a799c1a5f163c1c07f7b9a51b2324d5321479aae8da3fa5e87f9e296be64896fe9b28344aee66d8379543913d05cd5188e85089d7fb8b914d85c31a9352d4df54deb75164686742ef2e1c16df4e40cf420e5c4ee78d241eaeca2dc48be2c3fa652b150bcf2c6622859ef79ed05c00ffaeaa18a724dc40430e9a2e35ec4ae882cf6876d1321314262dc9d649501cfe9c43d3f4a9696ef1095020f9062f7e160f5b56f44c76631b8abfd313ec4f4d0661109fc46d378f6c5a15917a3d767edee15b2492470607f8815f68299b8cbc75fc8ae7f8856e9a1b559bea70e0d31c415094a6c851e0bea8133147e2ad676f49b0654b5d1d75fefe4828459d8024533b040e6132d7dfab964485e95294d05e57e26b2e883ad3789c74e9391fe5afb65a3203937ff12f1e1bd9f9d4e6e2b0f31642f6cfc3d2d8f07e9b59f45c0ee84c0405116eb766dad89ad7c1c0462569c09e210316d75c575a3bc14b6a7d545a087c0b46ce4f073832c619c0037bbaed864d34b399bd86ab1b5f8287bea865a6552e99e9f451ed70af122389fe7d4e8ff8d78a16345b691db49614b52e812842b75face39630fb2c983509be5aa22a867d29b0b274918ca4b2fd640bab3487397dccb4608dd08f4a9dc3192e8a84eda38c68eb497496e12cab7879f9462890ed8cf5795bf2a4eeb1d3cb8407afd795f7d17b1320a66e9133e491cdbb86374abfcfdd1909c51216112c8a1fb412b61470aa8cedfac460dfe12e718f1add7e8fad780353e213e5cd673cf0b5e91dbc90d53462df60b6ef37333d8133fe0951e101f4ae4285515cb9db2d3859b0f663b72130cdb14bd5c140766907b4c30ae0fa5153977a6eda730a4f6cd17748618872f00ef906a95fb0927b63d747d8642f76070f5adc8a1120ac1a763b32778158f256487220ccc7964f5cea4509425c03b9e8731b6ab644a42f6f3598e8859b3c33f2107561ac879ba0dcd63933ed2eb669de00d569fdbb28f11b1370beb99f4d7a499fb86592def9f994263dc1a36195037d9a1949e8aa9905e2ddbf14d9adfdb780b645c6805133ca93c69024245674c2896d23beb0c865c4ae571c43c865d0746bfa04eb96192e9da10edd24411aa1a66119be74a2ce84ce3b6bc21a12bbd63a0402532c5f7b37d1b731551388d8a0e08b474fa3f53fa3a671cc6e6a62a0d5706c5535adb58730361654f3e3132b60d145f94d0e138c3b33f3dee3534300edc9189bb1f02a60c640e84c3dd10cc80d8031b4c57112e6167daaba1080a2e50f9c1c66cee04921006241f788e6fd03fc6e92a8ce2a224258dfb3b40be86a9bcc0249008e5b01e2f3cb02168e110b06935aa8ba010346e4fbb5b5e03af9773b3448bb2ea10c57ee7a6bedb6b53d6323345b2ddfe9a20f6e4404e963b0e300b74dd26467c5978df4549ffd1286c0aebb5e2475bbe027330ed27b014a47516b4fe52d71711002455fd6e115b1b073fae2e73ece52f177d5c45ebfbd4db4a641a57a33b9df236b9409048186361339cd35b92fccec2662d1b9c48335cd4fab51848231fa675d6ded31b34042c859fccd3148d8867e9e0fb8beac60519e33e2cd13562009cd68f56d6e73b0a8d9ee95a9b1ce31bd3859fa0955451e217da80dfdd0f8b156450f5949ba15955ab97b689bc1d4c841e1b5e00fbab441645b2d9d2dff15472fa5de4c6494a041e61c18530b781b069f34973d76b8b293dfb38f54d18814c8588021427a85818c2142b59842cea4739586722fc34d9ba37ef909a6507619047c9a65b8ca53e18fa54a66f92b59c109015c159853620d26be3042ac61934b98e5392596c60a8a9bdce9414d4f661f611338bd08724f83041f6595e6e486120cb89dd5a71ff674681796e5ed922a9d321791d97b0adb9419f5cff546cab4450d4ab745812c86e79a7423f15f3648acf9198eaa8878f89509050d30cf0041a179cd8655d39148779e1bbc5d0413f17823d43533755c23b7f7f6a2da47ead5c03aad7678e68d28115284f74a54271bad4d2a3e78511792ce586620ae17a25984118e11ed0e293f867efa55f6d2317d51df77c421aa72939f47785b6e12ab9838c308d27ac20cbc04be0898c70c79845d6d2ad288d270738acf2da92ebb559f1a58266974132a3d8ef71e03b8dc11c4c63dddc243aa4c4416248a4e7745bb9cb23180c7df698d24b9c86c49e4962ce8eccb552e65117850b36153d3baa905c075884125b13ca1301cf7cc0606c7640879164a735212dce8a106a6da8384ec4d9fd18610f408e9e57a0346d435a43bf8c1962acb29401ef1c51350736f4e8961bce8fb38853c8384f32cfe62afbe324dce266cedb4a5d5e9e02b13bdd591205449fc72d1570bfb32ecd6cd71e75fd774fd9cce14d404e8c1336efc572a1329dd32901472da2d7165686ed918571c482ed3bd82257fabd48c1be9f1255e44cb2717626f01f1a273e741cf82e39eb77d108ca3fd6067996a77125b14681661769a27003b4f331aaa5a5a95941b6bd5330002e73937e218008f67fd1b84beaaed73d3caed27f136589c5312bc5e09b4642103e5f7b8f7d9191ea51c757da3198dea3b0c5ec6e695577fa598942bdc67fd00574499e6d103d45e0c6489e1a294aa9de0c87507816b33cc4660f1f4dde6b0fd0d494dd2f5e575f760156d2bdc24e9ef67b43e28bc51a9488a2148bac30c1e0f2fad0909b5913b58b95c89c2bd3a49767fdbe3347cb19b328c4635c334fa438c467e212dcb2faab771e4a1e16c3405762411a4de0371c169bdd0d4573d1efd9f4f4b3d4849580f2630a4d83f3e6b91ef0c592ddb6e42813869e43c141cd35e638a1b8564de494e0e8dbeb8144a966580f606e78972181a1c12d151df8fde93ab51f3204f56449de6be905f174d366fdc71d989dd08361c60a2dcba705fb8389d2a9d5881d12481f7a4a35ce18128a9226e2bae84607eb20ec9a0ac10ff84c0f0c14eccd57dd452b1582a8d5ff4a566ad9b788d7c20a36dfb112cb7c6cf5a193d0998bd184bd555e2ead734a447648c394d8bf8b5010f2f4fd5403542337cd40bcff0f5a39f8cf1459a4d44e5057ae5c76902a5c651203a09a3898ff46716aadefabe742a1b16cc789f6ba8ca312e4ff04f0a1a0dba3756c8c00229ec01dcd689d67983f7889e70cc38985d5d0b05e6eb74ad949202610c824be858a7afc40444991998d14bd8fe927e1dd959fb3bb2a1c16ca7044087af04c01992290408d81f18b61bdac9e636251aa91acf51077353d928c732a615c33d73ea3e49f7dadb4ba25537fe301e74201c2a455b0b8094a59f6cd15e1dd7671c9dba433d9bb110764221bb295ef344c18ed7e52f015d1ed254594accc1d981e29fe6df2008bb5e0d2c39cd13e9edaaaa5f86c1f4f2a9027492580f1a58ec6827ed451afa63f576b419e4048b0a9a29343c80fce85d1ee8ff615ee82b1771e1e69def6ed870beccdbd36e0b800555522cb752c02e556b6ca1a093580f9f7055a627f660ba9c725ecbd575f9935a2eb7727691c744f08781486ef11b4b290508cae811719c07a4dc03d82265a700b0018be902ae082c77a12264e323ae352da9e7daf662126e5227c51ab180a228c27ef0c270cd12982bd6eccaeabd4f9a5a77c2f1ff7a16ebe1aabecd42f871c72f537b9929d1d9b118047f0826bafb88cd6d9f8649649d132158e933476ea47758881d50e2a0d0460cb6e0d82268859b62458de2fde40223352c629c546fd3b47603df2be08b4b5f60247fa866463440d804d997a3dc196d2234ef13c49f591a3756c88651227fd6d4ddcafefc2220a3ee3a64df60683caf972475c8a2d2222e5db8696b6c91d61454594406c4f51806e476011edd00e4a964a9fa58e7d7c098ea4145d61d6f37b3c2e24689a0f6ab28ef947efcad209dc0a50f5dd9f897a0399fb102c978a5af76e166a281730868330214fcf817eeebd00cace6c3679182b7c5e24b01df3562448840ac9dac4435903f8483937e9d08b4dfe78fcff5b1df250fa40fa98c2eeb434aeb10d9bc09942f79234ee2c8dacf36e85a1786758bb52f547d4f0bac0fc34b8b27572f4cddea3576a443a3c5cce755a5e18732b69ebd14a5df1b04d72ad5099f0b42c8f6708030940e75e1d03cdfd8e9874b1a4bf29f963a3c0902b1045cd9b8b281e8b5b9354c270f1c5c739b6c6128f806c8e1323a687ac272284a1126a2e5d4eaceab6993619514a5c9c88bec718a32d264268dca10239b58f4155ed12546b4dc23f3a90b3df2c97656d9f14f9d23e360a002e73c20043ca9c0cb630b8d351a27d454116c2350961238c41e72b98b6300c6fb475d267d92ea70677610189202e8a6802b6048e931616a0086ac89b8090992af4390e2f4f0e8bc964028acfc34051018f7ce79e1afa4dfefb0875103ac6512c8dd2a367b0d2a024343b2fc01f02f9f13689df9a7a5f4503bb2fc5f5aa19a9002d9038838a8a94e9863f419e6e6b357bf4794e4914acd207ca09b6863194d412138add0b963f582d930d345e7f7edd6fb246fb117897e498c68bad335a0f475ff30ae02699e269279743e5c4e19be41ba9ec0c249a5e71f95d7d90b78fd53326df1402bc7c49be220ff099fbf6e773989d71aa85d813585d962661a36dc5099d6dd442685d2a958f581fa10b8d578beecb8449e4256766eb36ff71ebc4e9c03e5aabdad3639c524b798680a0df7760cdcd2af5b7763e20dbd3bd690a8164ee52dde7e86505c84150892f77bbf2568ef207e5551ecb65f0952351e25b6721b670373e6c4f1f4467f3cba3aba1e3ad123ae029ba9218782a0990fbfbc82d68fc89544f58a5b1804146e3623a823a591f089815671ee4f54541a579adb307d6ca8a2a2e88a663ffd5a74a5c08cc101604fcb6e3250aab42df8472f01cff67f5a3671f10b51fb8caeec14e46cbdf5eaa554429b61e7cf57c159126e8c8ea8a1f3c5a9842489934fe1078debddf9c2152bccd70a966878f1a04c7d33d8b02246249350c16d8640e10f222954968afb94ee9a3ec7c6678efa741e359abd57311e1bb4dbf4c4a4f9480aa25efb90bb14309c1c3d4037813bfbdb51f9fabe9f408480350268bfc34e22f4a7009620a83186746bfd99260509b2f1315113c853a2a545c8c03fd3c7ea18eea0fd6119c2476356ebba0ee4c462dcb70c3af3cf279f221e9901a3d605f313f4fa1cd1d7a19a803414b9ff31b7918f4e5c85886c5790d2e1f83cb78d5cf94682b3baa6f94422e0016a7ffb327094b73e8ff20ff6075987b34c510fe139e81456e110809a4a048aeb0508eb3af51c7533785fc33f166c681aaa85d9fe14c0d5b8333e450c842b7a8801338cbda091300dd20c4092a8f36a320e7bd84665ea44a325b5817acf213888d7e4e9f0b486a94eb37ee75166570f4be11dcb25647253671be33daa52e0d534c4adade0929d0d9fe6c12337662a269bbe1d1cc30b5e6105e0c5f3d05509106e40bf024235586194a91f7a47404ab281110d748ab524b5747a947a4d448424036d4672833c285ca3cae9e9a9d9455227206744e70a8bb746724167e02486a41c9c4702bc07114a7e080879666c110449e5f872a93bfa3494ad89395c0a6c53cc175e0f1dabaee65ac2aed7e5388d812410384935bb069bd2d162f695d061830a8c040db563644b859e189491ddc62a997b32c2e54300d531552e68d7bf9461f1f1b098fb29bb42c03e69d122d9413091be04d2930efb625520dfeede4cc93a64929221fc8dba323e7b661b95bc960da50e102ccffdd31d451a11b411435eb07499382248633dabae2192b24961235778dd049e8439ad848f828e6b5b2fc861d0beeda6396af7916d46908d8890a6a36da71243c7fad17a051f266dbe770ae44f457bd5b310eeadf501566d89c217e600d7d3aae9da196d23f234a96f594029d416e45b78370accd7bd165ba2e6b186f3a8285334b506124099b44f6176c9e89161ca6a8ebd5524fe7e939c3b1ed418ef658968d72b99c4501b4a2d1853c081675aebd4d27d94248906a8e4d4f9ebf0ea051e55fedd270e76ca6c805c3a6055ee55cd4d1571fb4101d0f20af820c6277953717c8bb83e54794b3b26e9080f4598cce26687a58e8756e2299610c05b707108cd5e9fdc88a5bf02689b4272ce62e90f15bd48de613812712630e342b18f984b46ce08be96c2995c26067501d493b00056994d99e0f7ae18a3d09a538c57343796e363bb9a8dcc58d5919783b2c290a6005eb0a2f0c6f023af5f2113111d3f3f73fca8d28d9f97cd0585c24f501b17a660aabe68cd546f09f1e841942cbfc5c810226b6202ea7f3d50fc122bf082918cc573e11cda1eaa1e041d016b6741a77503c28ae377b5aceef6c31b1035930e8cf7d60382028942024943506153977f75b84a6fa113b4cc408120e13097eaff016cd36aa0c1aa443b6dfbc8b92e642b560f131f74d942050361f2c313f605aca291e32eb18b49d77366ce2433d55a77eb22c9b785fd52477d8ddaf6560ddc9ab95885032514865237682f7a45444c526bb4f0b65c8612d21d4925d9109fbf26f9fdca4e1a81daf630999bfed9fe3286c0dcf01faf78fddef7a9cbb4f895c8692d254b2bac320f819447ec4ec145ea1d4ab19e8f9af591c35cd6b21496ee488c3720bfbc42a7a813e5be0ba2a14e180372493094203a6994abea0f670639a1158ab69394d9aaadce4a9046eb57fac67be8bcef1947509ef20610ab458cc757525323ec13275fb94c09f2893e70dcd6119a9c25e2427d59536a062f4b36cda4d110e06b1d992a150754b37256c63a9e38d8e67cc5d0425b27b265f2fd1a21d06c382b4c13486f7254d92fff602610db56a678fbee62a7464ebc65079cc1695cdc82cc56cbb0e71be0eb48dd154890140cc01336c7267098a554dd7f89386628d3246900f4af865c2193f44dba82004a87b17ffb58dd0d656d1c1fd1d69ea25e2c4dcb90eeeb4de1293ad25a1962d0cebeb352a7205b8781222d7dc7c10c8922298d80f764de12f17c0cd536869ae01367c3fec0239262bc9a98c5b4a2a2a266add7b4698696da469326abc519db0aaa28d78fac71fd94a2181f3f50e23425b44ef74d0354efee583d36506ceb65fe7c7c1ebcc2ec9aea458a369d02c006046faa22b85e42984b3fc363d8f22378b4111615273e68748ebfc08d3ff9bffc911cac72587ac500cc6317f5f98d7a2ca4ef66e2a7be8926016c3c03eb943393e44b2f6cacb05ac559b2c1b75fd4084c56ed79ae4e75678ed12cbf62ca32611df9a029bcac9956bdc8d4854cca1a73be2af4926eb131054fbe82cf187938039ab105341a72243f03862d37909944ecd7a87d72b9438fced3db66d0ddbf7e602897a64a83a98cfe59662cbcf6aced813d181aa8cc674a904e1872784ef75204fd43110c95e1b9689a580fc34c6e406d60640926431a1394c314bebb9efd0dfe936f65313b8358a1c9fdcc8fc66b5fc3faa8ae07ba12215015effbee97be2a2296936b83f64e33133dc0fbb6e488d477cad39c74503dbf378fdc341d47618f34efc044042cef49841d238540cf34d76d74358a098cd9d5835a8e1e4c443b3c005b01da08b00434a87ae9354b14793afe82f0de94f6e15a3e134b57015be529c7dd377c1af9ca85560735f7f32f08cccb424d80ff5c5e832d458589e3402c023ca338c8e982987679d57fe2962f5c5e43c7b5b2a92dcc95fcf3f87fd68aa2b23a471493305e7b3e759661895ae91eca0a4d40f003a5a36a1e8f24e8ced48a3ee52ac113350c09c30432af361f630faf1e4b8775b0b05efe386944bff1ef3ce8a167670d3e5ca8526e543e42647f6e202192efaefc0d2816d350a0382c08ec6cdda8741a74e36cf7bb5406230806c56a74d247194634a6a6651f16caf6ec120406c6e6d1c07031fd86370e9506c14fff7189d3c26991d53b6faea50dd5dab160f1040dc38bcd9baed85a8d342a37b8e29ae3aa9cd1152b1923c2dd496399d853bdead26eb74051a613dec60eef54e1a6445103cf01fc248d3d926593aa2769e5972252c7deb718c74ee1ca737609892ea9171b774e605a8c34aeace53b6d2f0448a40e11e5a211aaef5c7403178253b766f9988bfc3daadbfc6fbab19ee0d1bb6b80573e042b8f33ea79434466f327e20effda9fc38146060fa8e78402d4fbaadceea47166d1b329de5e05dc35677d3792790952dc7710838f6549a9859cfa415558066ba72ce3f12743318268f2da84910e2f1ae50d2da1ee812e7fa75fb1bdcd42a48792c8a3c92cb6e822edbdb444936fb47d9a415e9ba1d9d8d8df688f2d888f567b35baaeb027876818c992bffe8bbc6c2d99af688b0d0eb0605afefd087c71f0fa242a9fd5dd4db66c9df8a8920f962495ad4bab7663dee34c425bd14a725a24ffae78f5db6b68f37aa2386f7ae7f5eadd8bd0e075bd299c098bb7174faf695e8de7259d7c3dc102cbfe16cc48aff7791d7d9d57507d7a41e145ac575c5eacf7ea4f913e90045ec747d8aa373cf65aec6b6f5278b9ffbe8edc92eb939b7cf561d90963f0d624ffb62a488b96d7bc5eae78f1df2b69af4d2aa954495c5e7bf592ecc538af1cbdf062beae7fa4f33d7aaf3cbcb8ead5c70be9f73a36c98020f2781dc183573da2dd50303daf2f7bcd2f92ff6742244f9d0ae9d6fa3a9a6a23b26bf575dc78b073fb7a608bcf1f6873f00b3f851d4f374ac9d8bf6672b6ce2bd6bc2e7921b85746af6778f5ea65cb8bf9bd327a61caab33afefcdebe8b839939e7a1df76307e3e14d5d61e554bc8065466504987725047b2e1c156a987c23e55ae368a1024330e9d9d766c537daf31765151b8cfc4984f13a96a840ddf82ce30142f519537f36bb2a17525b918b46c18940305ff477345908593d3057b508654b9ece6648548a7b11b1687b93c5abd94efbcc19191373d443394b93013091c81ee64f7d221509c5f878feea45cb53892047d17a150b2af27fb3a06f1a1e4eda0c533a125358e6497952b9e6497b8ee349298b48ec26aa43a89e0255e91c7990452a8a7879acc1feec4767e69858db89056c31aafb55f017c3bdd56e9279ad40c48dc941a0148dc8f5e9e2e28526ef26e94c0ca7104374e9ad1ed6f12148d1b1013d05e1904516db5838d3bfe711291cddf81a1c29542407e1edf063f5b88968b09c26310e8ba307a4fda71eed18ab03d27d1aff34b59ba0759ecc4741d36fea56183763a70f889d63f6e0aa678abf5b0fcc7fc304d80e0131f2f3d833c70093f511db4bd437f75005d0dbcacfa4d2324de76bbc08150c7d3e7d79154e6aaf7a254c87cfd2596c367c978e3a728dc55700c5aa151f381691f44f6ddb262eb94597b074794d23d748e8c4ca2106b75d83260df287267b8c39a78d20afaa2859001c1aff8c3a3a582b913229509e9e9662e568c4ce8ce9b4334c92d72f1561cc884f18614877c32920ca00aeef57e1cb2ecd63f79367b2c31f1cf628a966266d88754ca7d1abd1ddbdac7792d1b3034753a4a764a6a8dc38a126162d0be8dea54e857787a560c46d618ff63a379e726552d7b5900854c941e3c5623e6ecaa3bae07a78dadd99bb2a408372f88b500038328ce8703c1c074d01a941d087d6c641a9d83c0c91d9e775c9ce30f5f3385807d492a7d67c5628f7072eab3d21e4cfd7589b5277cb3c3711d44c3891ce597fd5a9777e69f8e4bc278be201f8590f965aed19abdc9dab2338d5b3a825403e6eb9ccc4a9ff16138fa7b328f430f15b5a47d84a8820f37bf227b59776481ede844e8e39135600a33c2555afc70fc89bbcef8422a7e07bd875b8e9438e618900898a019ab0c084d09f7046361d24545ebe6b795911519a1148388af2663c8c0fef86a786d8085f0b81336f9dabbaa8e336c979d521e2980bd594c7e349b00b5a1cac2b91b6e662213ca9079f183a3cb89ec6a1857c0320b241238d7c18e6b760b52819a8d092f96b5193983040bdbce799ff40f36e3dd8c4edc4012482fc4efaa44b4e10f3eb423e77b4f102ae59c59b0d3760f0140a0730333333333333333333d32c7bbbc3fe17feee44b65c801caa26bc2033333373aaa73303afd8afdf5a6b3bbfe01dbc3394127b0db90dc10d7cba8bad1a5d2089fbb6ab6dc5189773813c1ecd7b7afc6b3d96b640c8eb2496c2c72b997e2d904435e5e7949dbd9fcf02313baa37fff8c73137850542ef67d20b3b2edd952b10af62a6d9fd690572e5cc122fcd39eefe55207725f91f9456a54f1a2a1063bf2b58de13aff4990229e78f9bc3f4a80f6d9402593a65ebd8f13e38cf4781309a47e933e37a43fc8002c155653a5559e7b8793c8110391b7941baf2cc740249ebcbe3ba46f8d8d804a2682cbfd8c9670239f87f9fe5c80e79d212c8975fb72eada2046299e6b2198ba665d64802f9ae4772fb56116bb7156a20813c5dd9724e9bbed22d116a1c819c437bf730e6a189cb8f20d43002693d87de17e93eeb941a452077fd387d2b6f22f896631ed63b1f02b1ba3ffe47353dcc03490824cdb1f92ecf0581182a57fa98f361fd0f0602295aaca2078df90139a36ba7638f8278f73e2065ec4b939b3646adaa07041f8f23961ff180183dcc94a1f4db0131c798728e621d1d10548327b9501e634339207be8144a4b2ce3a58503b26c14d7faab0bd9c3dc806ce723ad1e688f1a3620555e0f795d9e542f59a306848fd12f6b31f578ac6ad08038d7e1ab2f645ef741b320dbdb8885f655efff210b72d8d85d88546241feeee1da46fb8a317360411ea7f614d69dc7f942f30ab2d75d4b45c6d5b0cf15a46e599ff7e453d5d15690378a77764c2b2b88b3bfe1526c69861ebe0a72d8559bdfdce3b0965505d1471f2e594c7277e3231584d759bb1e8f9229112a4859d4b37b4c218d53904a7b26eac7937a6898821c63c5581b3cfe488568948218aefc5c368c597a9b06298857c94731e5d2b61e6e688c82289d3ce5e0ef83acd8838628c8a398eeb0d4d60d2ed20805e97326531ff5f03dbc070d5010a353ee3cd2f08c67368d4f10ceed7a94b345258f320d4f10d3adf457eeef4e3d9d20f5eceac68593062748ffa1f1e381846bfbb809628f7fd831597ab49ac8d2af8768970952bcb8e5595fc74f4f175b343051a6ee6ce7e31265bc6dabb4d4556c6be7dcf6389a8fb2a7695882f49ac14726a6c7ef1d1a9520cbe745a64f7950829cf28fe26d9bc4e6cc684c82b0a98779c3be5e3d5f3424418e7a29b652cab1311909b2c764215ab687449f3ff6c155770a08683c82305e9ee9b734f4dfef08f258ee59ec10f16f3f1a41cad1d49a267b30821833b747e279f3d3348b206565dd983ee426ad2882189b730f23e5335e9a13418895a8f0314404b172fe518ad9e8107d494dd69c479f7b661b557302340c411ee7ed74bc92b14e69214863791c1396e11ded13826879b859f7f120083ff451c8beeabd987a0882e4e371fde7e18687ae8120c798344cbee59b110b0d4090c75d31c86deffad8741a7f20b4dbae85595d960a711268f881b03e5829ffc165d56f68f481984286652bf977e99a061f8851aabf2f69d8fe386534f640ca213efc14b4a3a10752ca6ca676e8e481d83125d1d5a439cd7d34f0404c56e69a9bc79de33134eeb0e7cbd947375984a06107f27bea817ece090ded69d481a03f0c3eb81e590ed16906d0a003b147e2e7dad952e5a58c31f841630e44f3619b8fcec35d5ca12107626eebad54413566e94d75001a712066f37fd7d4a3500234e040487fe9141fa3a51ee60d64ebe4ba6d496a366e2054bfa5e99dbac90ab581783fd8bf5b2b8d4e3e1bd67a17954ffdb5f951dd9cf966fe0334d64052cfd8bc8d1935d04843e50083061ac8234d1fe6c92bbdfc511a041a6728aef5052bbd8d86194811feab65ad29033125ab14f5b1460642767f5a28bd688c818d91d48cfab0a9b3d37cf7dbb3e7d21003a1c62e8a25fdfca3580268848138da9a6c3c5f87580606f220bdc3c36f1ef5a844e30b044fc91c1ed5213e8fa2e105f238a67d8feb525ea142a30ba48fd1de3c3a445e3f5c205f1a8bd64e493577ce5440630be46aef7cd7101baae98b010d2d902dbb564cf7a3b422791608627da2e279c6023977ca1433fbb87b30e71508af3dea6b1d0b93e27b81823e2b90346590eaa1898c7eca5e40a30a64cd638ffdcc95fc3dc7a00239db4b584cb64e8168d7ff63f360953673291053bcd4b710f7f1fb2c17d08802d1c771b37d347930a00105e2597476fa8f213f1fba16d07802315910ed1e5bd6e8bacac2a0e10452a8efb94f6a8e976c02d9b242f438f69536060d26107dd803bd731fbd04628fc6cae569bb1f97128851655f6ee6db473fae53402309a4e821bfd1b17bf369132081987632d8adec6d4c513d018d23102f7bb81ef558534afa19238c2eb65040c30864b71ffd55d8a0a6f2e9b01d348a407499ee8a9363042270a2a0418487e3766b266a75e7c8713bc810230060a03104a246f7bff8e3d4c12e4743088669cada96b2ba85081a41d023ca42bbc3b3c6a3b66253ac8b5ba56ae1d52068008154afb9e37a6bce686a061a3f2068b6a7fca6dafe838b860f085f3ecabdb63ebcedd0e801693b672caf289947f9ff828655b97743788ff72cd0d8c1d1311f5966ee573d7e61cbda72347440c8b8def1a278a4402307e488fda8ba1b7f3cee130744bb4e1f7a627e14b37503820f36a776c7d8d947130d1b70dfe1251a6ba3d6d743098b9cadae2c950b346a403e8f17ae745d54b73b30d0a00141572745bedf638fe3071c0990616ac62c486de1a5c382845298210b42df8ad66c8e8b3c4b2c889e63cca5f76baf6bcf800521f32873eeff0f1eef7e05796813ae797efca31c72853a7da969c08c56905a3d78a81efc335841568ffb1e9ae7337d9bb10a72eb571e778e0566a8825819ad2d6f8c0b1f196f82aa1c39749f3023158433cba6c9f20f437f382a881b533434fda019a720e6e023cf3fd48a96c29b610a72282fd14f9adb828fac19a520a5d0d27bf7a94d3c268319a4204a6e56e906b51eccc522316314e4513bbbba73cb192f470e4fcc10453ea88b1e328e0846185ed485827093e9e3ba707269b6c10c5090d273b6680ecb91e9a38bad8bc18c4f90d33cdc3e347c0f6ebbc10c4f105aab2ae46f7eec98c9667482982aed47cb87496f3b9c2045d7114ff5a3b8ce238560c626c8ef6eab321e2bdbbc5dccd004d13fcbcc044973f8a185f5aacf1f830952d6700f3a9df6c30d5e82ec79dcfad66df33c34c312a4fd610fb6afaa4a102c3ec746c7fd876acea004a1c264875a0fd3b9e71e306312ca9bbadc898c47866457566ff2e18f742273ba982109a2545d2cbb5cc13fe6448254fadb3dd111769b870471c63be35554c3db7f0431d648f448654790edc7a31e7f5ca6bce3a311e4fe909ed96a33821ce5433256cc8b2056a8a097f663cb7d5411840d3dbccbe14490aecb479772caa7a58a08e2a4c7a8f0710cef340f41f0d0a3b0da9fc7717f1882f8f25b2e1e7f60f94210e463ccca342321c81d3da351411b04d9fa2cc694b3208816e671a3ad57e52b10e43c1ef930b65dca1aa70204a9e3d975ecf156beff40f2a1b7f64d4833fc40cab1362d33a5b49a9d3e90f220c3060d5b492c6bca4030830fe494347e1c97310f538a99b107a2dcf720d47f94c447f10c3d903fe6514c5bd987273f74461e4892b7a1fc2653d6bd66e08164d2e39443e7dcf71dcdb883f252d6e5d1e5526933aaf94efbd7871dc86983d7271f6dd6b64d1f31a30ee44c7d5d9bc2faf0c7310008c40c3a9047d3a95356ad2cfe3855cc980349622eb57efc23982107528ebde5e39c72c2e40b63461c4852a7a23115f2eb553d33e0401ea9f7d094b17b3ef733de40ec3beb30df963794c70da4d4b360f2f9e94aac196d204626fff35ce1738e73061b881afb47d6fea1975bd70ccc580329e54e7b0d21ad2be20c3594cd480339f3e3be4ff2d2788606c2c60eb5153b8a86196720a7b3fd70e17c3cb465861988d1d22b8f69c9c7adcb406a8b397cfc87ce20c36a6359f11a69a3a51f43a8c4add514983106f27850d2c33c9a67bffb19622086c91c7fc662f80221d0b1634618885953e231348fe4e76680a1f4977c28b3f1c08c2f10c76f5fe35df70221f330e551dbdd5d48dd542bed6aacdc7d98e1d775f3c69cc105e2e738aa75ed31554f5b2055ba982236fcfca6390733b440103b4bdac914f88fa1a38c1959205e956dca145fa173dec5968e14f8ef2063031708810e15ccc0025bb142459b28e9788709b00c15bc03cc7ba0eccb50c13f74985f41e715c82517adebc7ed6275ec20430caa27c3c9086387e60c2b90ef5ed593890fcc54ad02f1c2c58bae381563ee5081987236ba32c5986f3953205c0c9e87b5bf763ecc4b819c5cf23d956b51206a9a5f8fd933502096f547911ffab8b67b3c81b03a2e9ac7172790fdd267e7e18f2aaafa26907e34fa3efeb730817c1a2e78887509e4f199bf4bfcad04e2fa5ece19bb7d2cee25815815d37e5ad97d351909849eb5e93cf60f97c27904c26cda8c9b53f6ccc3a411883152a3295a5f7634452854dc4d2210354a5abe5afb564943206cbc60df7918c38f3b42209abf5685f2ea81faa82090f3cfbea3e61008e4bbd06b3eaf3e981f46c6f1a28b3174a43160c60f88f9e1ecc79df29d0f13ecf8145091b1238c3170860f083f1e6ef28e8a5f99355de40e32c498d183193c2096e465064ff7d83d7c07a48fa987bb51d9e394e60c1d90d6a27cb86dbd3e0e9919392094bfe5d0ae1b0d15e542ccc0013957a5e07b79bc01314fbb7da7df67d88024f3ab1d46db9d5103728a1f7bbcd93e8306c49c119aef5d9f05592de9f8a03e7eafc74316444f7e7f212e0f3aca1f0b920f2a7cf6b59aaa29bbd85a3206000b42886af759cc3cc24881be82f4bbb3393b85f45ed51578ca9f4da9fca3790f9c05e710b79faf9b0c37f2407c75971f9fae48cf748110e808e3061ec8192bcc85ce146b7503380e37ee400c6f5e1d9db237ec407a6f114faee3bfad951762b88e148ce12c600bdca803d1bab7b5fab73cfb78d081a4f963e71f65fef1d09a03c1360ff3c7e3110bff961cd0d43a8ff35237b374cbefa431a5d0e24050758d6183878fb407df8003795a3cb26cd3e60af171e30d44f9cd2dfb43931ea96e20e7471b4b33ab142cb636907b1cfcae87eb581b6347190de81b6c20aa861fca95e65bedf4c61a14ebe1bd0ff5f2fc316ea8c1b0724d730b35b93b7b6f198b5ef12b46fbd36037d040d254efa69daef47255000e40e0ffe4c831831b67208f879abdf23ed60716aee0b1001c8080172908030c177c114606fe1fc06606a27d86ceea622903317dde1cb422f7543b6420691ebacaf8a0d3a6d9c640a6b8aab8776b6b7bd8feb456a86e5c0ca4f3b0e9c4e2dda55b37c240ba4c4f3e1edc4c64fe71030ca4f67e8b973206effd715f20e88f328385a9aa05cb156e788154296e90b855db7876156e748118ab25ef9962d3e53c178823d3dfa75127b487d942b9fe639a966a37b4408ea125f7a5c3ae7dec4616b03c6e317f7b1cbf8105bda44bd25d5a367c503dcee8b270e30ac4b09f964973361f5e77c30ae41efd70e3e445f5c1843a6e54811456bdb37b2a966c26638c27c32b70830aa4b4f49196f1c24a7fdc9802396ef77e503eb4fe6c6f48c1f3d6cbcd1abf41dc880239ce6eaeb03f28f134df8002d93bc4b847cf669dec1b4f205c6cae8ffe9129debe7302395e7cd439658abe8f6d33dc6802a93f5ace15d6c304a2efda86ad559740c8cb9d1ff22f0f73484a207dea65e817ffbc2a2581a4d203891fed76ef8f6dc20d24102f88c7b021fcbbb3d3831b4720070fbafe5f29c55ceac7f0ffe235c0841b46207b7a78f2f3686df18a40c88a71467b3c28ad6811819cb6e29647db48f1f1b831045232d94efdf79174b26e08819852c5ac9765fd3afeb8110442c6ce3b96073f1e0f422520dc0002616c635854b6ff78e41f90e2ed8f07fb12ea61b71b3e20e6970e9f32e67b40b6ffe1ef858c1f64866ef000352deda8cbb4abe0fe83fdd9cce8d4ded80131fe78d53c7f7959fe8e704307ba67cff960f32607c4aa105121b3be7e7e714068b3caa13fee61fc51f4c60d48a216cb62f0ef710f7436c20d1b90c721937712cd91ae9e1a10c5c7a1f53ecab97d941cc20d1a102e8759e4fbdc8aa4b320e9efa6309d475ee99a3e820d599072d0dcfbd1471f22be58902e78f4e82cd60f6cc082f895bdf93faa45f6fb0aa2575fb296f008d5141a6cb882103f48d13bd58bde4c2b08e9e9c7e17c7c5fd124561052328ee447cb680bdd2ac8e1f61b3b8fe62bc4b2a10ac27dfaf8c7c39f8da4a92057aabdee306d217cb6810a729cafbac87934f7cec62948f7365969d7fa2e37364c41aef0b1b0eddc11efb214844b5b8d939b349655c008630c7f0d3029087b29cd5d570e1af3c02ed60b315c870e1d778110e8c861631424114bd6263fa88c2f4e4ec08628c87d93ee5a3141185f9c8d50903f2ac5f8f6368c0a8002b3b0a92af30cd90a2fef700f99a1094c104605c408e302397298208c2ff24860e3cbcd797c55d31f4f9082d658cedccfe0c34c2758d55ed1e8bae104313f84857f7e59546d13c40f5339a9c7ae0992871e8ae7714ae129a799e0ce2b2643b5233da2c332b6472afbcbd7c404f947ad9ea36bc536dff312e4b04ee15bb35c660a36146c58826c2f22216ffb793c288d60a312e4517a0f3e4347dd543f3e40051b942084e64197875d4aa1c52e6c4c82b09ea1b2343f15d021041b9220f6f83466cffe3ed231777c5b2488d1cfe236c6bccf94b2528344ea29377612f1b1d39ab9b9f53cfad6c5560abcf8a25960e311a47dfd1fb567854cf5700449ca7e64dd99f6429c8d20e5d894792bd3c4271f3eb0c108b267ded7ee7dc6f02e32a0a35c606311640bb7a2eb17df3f5a4590a2f58fdb2beffc4474051b8920c78c1fef8c57f6ed1eda3610414ca9e787d172986f5c8720bc6b2abdcf0c41aa3b8ff61e868f5d3f6c1482f43b9a5a513dc3e76c8310241ff458f2f051ae3cdc0641ce1e6692aaa88aa1231d250862ca1936bd2aedae37660c2f5280170852de9ecd3af27144e4c70e2f03025f380d6c0082b0637b239661175b6f02ac3f10b4530a3d789f0e96930a6cf881fcdac30c2e15a79b0eadc0021880810a4040c968c16bf58150db1fe6f9f2f03f8d08b0c107c226b9d7d23ef7c178f640d6a0691f2cfcfc3dc6861ec85e39259b3efa98f36a230f84fdf1f94a6ad4ab6a6ce081946cc3e957f45f6c8fbe03793ff3879f3fbe876876208c25ed88cd73126cd481e0939da95e953f0cee18fe45035a0936e84088cdcb23f3b8c770816740c71c4879c77b72b3fa47f3c88110b739fb2878d66afa8803295e69e68166af89dfe140480b95f7f34d4f63be8158a3797de203cb958336dc4078fb91bfc694b2b3b636a0ed5e2d2f3365922d61d5a2b2a9dcc3a35d670379d019c7bdefb6b10652f4e7a1584e8f77d364430dc4eb3c7e8f37379726d94803c93d567a1ec76a030d8451698f5e7741828d3390ff572ab3d6b55bb81460c30cc44e31f22d98d4a6efcb05cc11b05106a246bb92c8986228cd6c9081649e95f7b1e279b81e63205518dfd49e8c0c1b622059cc83bc98c6ef7de36120c5b0e983d1a8c140fe921193cd7e81b0215396e9c9fc619b5e20d6fc690e6b3988f6f8228cd305e24eaa5450154dc92a965eb0c105e2f8efa8f72879aa5333b740ce3c3d8efd0bd5c30e6a813c7ef7e07d3a666616881b7ae54b3d9615061b58206f8f2c683cabd91e557c05e246e7ba71f930f161aa60c30a494e654bbfa2651548a9a2dc458fbd20d8a002712e3eabf6775daf933905f238e7f1d18f432c58c4b8226347181908830c30249023870b769031861736b0210582261f4c54f620f9b26379801b60230a84f9b1db5d4e4da51fc70614482e9e4723719fc63c26030c5731d8780279a8993a7fcc3a223a81741d9f3f4ced8fe9c6025e88b180ac1564ade0c860a30904f71ec5fc41f89be5980d261043e6a98e79e0e1ef4d55d8580279984245f591de0f7d90d5c08612489552793eb3525c4cb19104a268997ccb74ca9f920d2414af7afc996cfe873fa80c1b4720879afbf0edc903397268c18611c839bddedfe738912a478e31d40a368a40cc83f471fba83c45b041047248b1cab36cf3f3967701192e20c39e6063087c7c597cd5956af86e647ad3cb5829ca820d219055a35ec5cb294ef84e43b0110472343dfb78335a3ee81ebb60030804d31ee6d3cd16333fdcc60fc8f903ad3c0a5bf7c1751f903c3e6476f3e8012998b8b9be468f2d65367840bc965fdff69178da9c1d10f63d5ab24b345cf0a103721e26cdbb3b8d3fdad9460ec8316abe9a732c6e5c36705057d46cd4adcaf7289d99885e28b5acdd803cd6fd518e6539c50aae0d1b103f57f2abcb1f366ac0c954d5889ca787bd65a4f4445cd4cae162830644afb78b0f6a91974436d49805e1b3c48f92a5f3ab546d0d59904df24393b7cd8962a4604717554b821ab120a51ebf7cfa1fa987fcc16aa8010bd2e51ce723e2aff10a42768c32fe95ade10a4a42cc53a644c45ee4432f6a5faad488a85a41d2e4e3ebdc66d77d790d56907f2b66c6ddec2a48e375c1b30fb3e5f6ad0af2c4489afc69fdc0c2a9206916f350f1f93d980715e4ee142225aa9f7a29354e41f4a8e615224353903b59ce9a7a7a9029a54b411e7ff8981faac7588a3548a15579c95d5c5d5b946b9ae52ecd2d056a8c82d079ad072e139f79dce5a08628881a3d5d6e8f29e38ffb5010e47330fdfc9a3fe7020ab27488df6896aaf109c2e51e5d6dcad7e389ca126a788254271bb23aa56b748278e51b3d19938f7dce1a9c205516b50c659e036a6c8258b92ceb53e8c6e5144d1036a64a3f15fb262cac9109b28d7c0f320f2f334c9052d84f96437dcc2e41fab0993277bc66962044541e7b5efd289762b34ad4e399fb62c68c1284ce0ead8bad15418d4910938796f6518e99efdb4c12e456d5f31eecdd841a9120e5e1bc8f52bc4743823c0eb7a0d152cafdfec550e311f8d0c7e3f881e651c311e4e1e5a43dd278a937741a41ce3d1efbc6876a129d98508311e43cacdc9f347c49a8b108f2d8ddc77f67c33ed5a408724ce907bd49f28286950872cce3cec3fe9135104188bff7778d3dd639adc621c8b7997f74ebd16e79ae610862678cb9d62d6f59b66b1482649747ae977b3c6a1082fc692e7cce103eb8ba0741fce15d18f34c6b088224f7e9b747a31a81208f26acf7524e6df13728811a80208fc7034f1f73ab68f47f2045a5e8396fccd5f003d135fcb8c664cdc3af35fa40f661e8e581eba53c29d5e003692d6ae6610ad526ebe931d4d8839652d2b6c150430fa4d3d60af328e99c7b1ec8175a3aac7db09cdeec420d3c9063646a35b4d557ec8ce1a4851a7720dd677f0f36a5fc6e8b1d08de7b296f878e293d531dc81b2d899ae7ac061d8872797c29552ed8054b8d3910f3567cdc7bdbe3e07e0d3990ae2bf3f87cb4661d2e55a81107a2c71c239d2b343be70107a2450f32ab5c452fca2bd47803295ef0419cc798877a3b156ab881fca943ce2dd666f4f151a8d106d28e6b6c1e44a95fa8c10642f99474ddef5ff8516b200f2f43c5f0c3907ef50935d4405cb33cb458af4903317fa6ebd1d7d8a68f820652dae861fd503635ce40100dcd11d579e0f75a0d339053cef859a3767f1834652025f1618cdd75cfb1c93ad4200329f6fca7d7a46f51cb1a03f1ce65bc4ebe738faa1a62208864d6597526841a612059ce0793d04edef1d5000339c5ac717757430f3e58e30b64a99847ffdad6f002f9f2284da9018658360952fc0be91e8f5436452609f230585d88fd4482547df9a3989722bd870439d487131efcc287318f20a6f44fe9b22f2a174710264cdfc71e2f8f2f6f04d187716ea422834bcd08c2ce061fb5e6cf19ee2288f967ef9f26bed2aa08c2e7b01ae4c207fa0313414c4944d5a5f507222282985391a79a51babe4390f3bf627c9c7d53aaca1004bf934b3d531582b86751eee62db6868a10a4cb4a316ac37996cf2008a399467d328220cc6e8f62064be99d409045fd63da685a3a0b20c8391d6befef3f10936bb25cf1f981947ae4791883db07f2f83b55c3e50379e3af58cc21d77bf5443e4a58f440f00f8fc99c83849e79200f35768f7e3d33aa880782c7e960d57721fbdd8198c34fcb69ecca1c3b90f3388f5a79faf1ca5607527b676bf6cff9c2559c5cea90d99903c1fb7d6cfb192a6588b49003612edee5e7de1fe6b3681107a2547ab9f9caaa1bfe2eb674bc09768471ecca1a0ea42d9f58dfa459af2c262dde401ef8a5b3d7706ae106f2b887c9c7438d1bfef325b30dc438e3e3f0b962bcfad01db46003597374e7c7d1b9e3470960ade07678a1438c31b0b45803318facffc71abe166a20a61caaf1d44b630a3fd24068f96af5bad01653d040728d1e7d7be93390d2f969e6d1545a98813c1e8af4a8f34285b129a0451988239b69da429eacbe263041185f9081e463f71e074fb93110a6479dc962fce0444b2c3190d7233b59da4ffae947175b675eec2003c340f6b5f7d90e69693b060662d2944abfaf3bedd9185f2046e98ef47be95146ec0552cccf12a1167681a03d7ddeba52f93d1068c105b25b8f5266cbb7403ae98e52a5be96473db440fe14522b6db806a988045a64811c3f307f0b1763f2f328f38016587834a2b17eb0b99a9a362daec09cb5ab7d46dc6c988d5eea3e59f5e9fd61b4b002295a73e5971e5f000e40408c1448c0050c18630ccf408e1c39a4a04515c8315d2c9dde78d629c3022da8408ed1de0e6909afb7bbd842dd2990ed3ba85d7eb9c58abad8622990eec7a3b4ed695a14c8a3fbd016755a0b680105d2990f5c42f30fac457e02b134debf6abecccdf12eb6b434a085134879d463b3fad9f93c32bbd8c27b68d104d26c7a893c53f5da942eb6b032a00513c8e1afe246fb71c7983e38a0c51288da9d339a7b8d664e8ea10462dc54a85ef5dcab314920b6b959fa581ffd27ff8016482077c836f5718c1fa0c511083fccd9ad53454620e9271f4d3fd1a208848dd0307d251381f8837c5d0faee9801643206bc81e9e7cf2d1ece74220f5cfb78b8aa5e74c09012d82404cb6911b7d2a108839b77b32b45f9431c66be08b2fbca0408e1c5afce00186172c00638c327608400b1f34a99e9e292a566dae59a5bb1a44756c2aa58b6580163d2047bf2df78acb5fa276b19502e50139e6a1c7f3d135cb1f3ce8bd03b29e8b5cfe8928bf3931a0850ec829e3cf7aacbe6e3532c098801639206c88c5b5a560e947ebd0a166c6f022051886173ac48880163820b8f63063760ade805c5227d6b731d98da5850d889d271d3cec7ece030f4a408b1a90e2b53c48a754f0cbd18206c4ccbcea7799664194fa9c721f25ed20e39005f9a287a4c45470c4829462671f6b282ff5d78b73b0caf8c23d2081073c200563380b72e4305890c22f2a79deffd4fc385e41c8fea91ffa60cc871ebb827749156df16e59efb668bd4df5d988edd18af2771ee70ea63dda9215ad9cd6a9dbcdbde47cb48fbdce2a48b11e7d4226a30a829dc7fc507e31f94d3852410c6e23e1b53f0f70a0821c4c2ce7d8f571f3f3b05310abb23a7cc6d6ed64e13005793574a7d6a51e06cba314a40c7e123ea26e27d3250e529053cad339ceca5ce841cca8011ca320de59d6c7f768bd634441d23cb2f03c08d1ce7b8582bc61d3e2059de82c220e50981ddb5631626ab116bbb69a7367fa031c9f20ce4fa6fc3f0c3dfec508c38c71f5001c9e20a6bf8b1ec5f2f09375db099225cfadefbae9284e10d407193a67a9a6b8dd268872379947e11c630c36000e4d90b463b61adbf874ea23470e1d9c635c26c8e3f8a311fdf17bcee30b26c83df27cdbdae14b10d3f6f02c5e2fbdd468098284bf7dfd48e3357795b046c366b6b552c2dda35ad443741e9f1284b7eac1649e1f5e1e665e84e1014d018e4910b3846b580ea9f55bb100872412f10fd5171b1c91207c7e65c9f48338fd2c24881e69d93efe0f77ab7b04d1838fadf6e2c5fc147304a9ea07fd15dd6d046933bed34df744f7cf08727d7fd2f30a8de531bf8306381641b2f851ce1b51d96b7be8a8c72f0e4510b3a5dd987bec8e32c8f0d5513ec6c37cce00472208e5f2b1c7b29f41730811a4e896d94962b358e5380e41b4241f34e8860d41dad91ef940a6db87d3168274712a4235c45df5303808516c887dbc5cd9d75859774e9b7d3a8a5a0a2f01c72008395f953215f53f353bca70c031107008821832d3558c4932c2f862033972e8282e1c8120de7df661fcfa419bfc3800411ece878639cd6c1a1f8e3ff01e7cb7e647fae3fc80894c657894c7bb6d287bf7b1a76d18471f08f17a5ad64945a3453972e4c89123878ef51d65f8e2e003b136e6a98fc7993de0121f1923db211b595a6ac9b2a3a5bc3f4c0fa4fe64d6831cb78bad1578f13807345b355e00471ec892b956dcc7232501071ec8953ac5783f1fe7202f8e3b902bc98fc7079647de9683c30ec4541b73da7a8b775fe3a80379a4791c3d7ffe30d6331c7420c8c9584a9ad258ff287320e47a92e81832a6efb81cf24a119f5d358c03d1531e9dad6d4a316e0a1c08d2799ff2c7e3b08bdf1bb4f796330dab89edfe1065ee973be5e138dc404c6fb3f17d1caa5e934dc0d106d27e5946a88f827bb21f609411c61a2a171b4873977ae4e371b8e9e6e4c8a1a3ae70ac81d4bb933de89012973b6a20c9696ca8ad283d80230dc489dc98ed473490075a193ddd65a9ce401ef4a847fe66ef425ebad83a61bc8e1d615499811061a9e6c37f07e6c05106f28fd43ef8d042a3db82ef62070e3290a4c7a3a8cc1cede133ed058e31905c43e8d5864f97428f2b307088815029cae4f9b87afc68d6807fb1811c391efe3bbac0110652beb0142ed6c55cf2708081347e165f69f12f64ed165ea211ae35d755f1d63a0e2f9087953e3eb7183bc6fb8cb1c33360f50feb0229f430c60e9baa47fe55185ee0e00269373c5b204ee8599a7069b73869c17a4949a90338b240921ee58fd35dc779293f405820c50e6beb316e8f4e2ec47105d205350f4d8dcd8b3e7058811433f3d8936d3ef9a165f5c05105624c365bd757213cf628f01450250e2a183571a731a6e536f76a9a631ad9395780630aa41f756ca850bb4280430a84ba1ce536aa3fb4f4c1110542c5fa5454b37140815872a9f5c3feb06e185e98e178027918971e1937a694f732021c4e20858af99f87d7b5bfaa0e1c4d20688cce77dd23f76198190713484956fe522a362ab3811d6488816309c4ff0e3eb00d6de936b7830c317028819091fe43950d061e0c0c7000471288217ec93bea42162f10021d15c08104f278fc033f911fab66f77400c711c871f724dab234023946c6d82e15e228021dfac7a92af62602b9bc2a7f3aafa495c63104e20f35f328a5a3fe38c72104f2505cab3f0ffee3e9cf388260f098f9076f3fb48c2f7e8718cb388040b2bfaebc3deee8a183050f86160370fc803cac6c1fc7d0e3bb20c333805d90e162a00bc830c4e10334b32bd66e252e43352d8afb289fed0751990ccf408e1c5d90e1628481a307e4ec69fd3ef6ece371965b0c1c3c20b555caede6ead6f45eec80986b73f6d83ea896993a20c7944ad263793f7a640e08e71fa56e638789ed7040d6782be72db71edf2a44018e1b9037af99b90fef3b010e1b1036c54fce66f58b30c2285fe0a801393275764235ed9406070d889f3963f251786fcc82f4b292a3a9ee2a26fb862c8e329b69f352330ff13abd14158d14bf18db11c60372e448c18e304e8e1c3eb8110ba24f68f7a065f47d24df8005615b34fce8737684a1038c1dbfb5e3c1e057907cb8be6b3e14d1d5cf1524b1943d53856c4564ad206a6a77d6d37079b0176378c135c6175e90514622e0062b88ee9d63cae389ce3b6005375641bafff21f4bcd3bfe5441eada641bef72375241f24f7b3a95ba662a0f549077a7ab2a5849aae614e4ec51fa6ad3e0a9a9320531d8fe28374aa19b649d49c8d6785bf7a0cdc7c163a58b3bda0d521043061f4545e885197f14045fd16e8fd79f21be28c8631f77b556eed1a120aad89f8bea65ca5041410e8dedc1b5d64f90ffdd524aeedf9eedea62d713e4d823e21addd3adb54e9043975e5ecd3fb139dde00429bb5bd38546cff2a36c8214633e6bd4bfc1d0045953fac18f5cededa27e231304eb5099fe7914233776a35a75adcdff918f4b10339f55bea46143f2b70439c614da3c9c0ff305b71245d46ddab7975b77ad540f7df4630dd779a7a604e9f262866bde7302372641181d0d7fd9ef6ad6bf210952ece81836742cad8faba3b22241368f694d4a76eb7f50086e40e276abae756d1771af77abb8abe918ad2fd2479072f861d00f3fdc1144bb0e5a979d9223c7b9e14623883fbe9cb6b0594690d24c93798c1ec6f87c6311e4d1d460e6eedf5004d94222e2338ef7cd8f1b89208519db941e6b6ceeef0622c8977ed429e73c4c9d1e872067a6ecdd1e4755851f86205b871f87ea515b084267ca1d77232404f92ea69dea6c96e73e064150d7f6a1b7e51e550f1704daa2f21e6ed3ad66f6173ae5d7cb3d812074858f4f4376c4af8696700310844a29d463b8876633f10937fe40c850775d7731633287c20d3f90528aefc3eadce957a32bb03e106e2bf8303c6a7c2068c6f656ef3e5d8d970637f640aacd73c993d8d77ef5801b7a208f8f47055f78d145039ab0d0571ea9e5a05d814eb9942b39c9b2a97acd1fffc26b4ad9372b5c39c5cd97b5b762efe0a20a6ab5a4454b96b9aa4b45868ff3a38f373f57e4820ae4e8db90b9b6f2002ea6404c3e4e6935e4fb0779a540bc19ebeb41ee20e0220a64f5af0e691f3767cf81c2b12629d9eeed71b19217abe6e255ccc51308d569791d3dd4cfb9e4c209c470318e87be938b26946df2b5e996eede59719d5b172d9359f041ef5c30c1d49d7edb43ac25e06209e5f80b779e21ba01174a20b96ff07185fc73e4c89143471838e0220924ed1e07bdcb951ee4c80512c89ed3624a0f7b24e23b174720d9f59d56b694fa2fcc8511c89fef4f3ea79c2e2f068d8b4088cde1e1629f7e7ecf02ae015c1081dce15efff3f0a5477a2e86a05ca9d78bcab89de5d77c52cb93192e8440d058fa837dab4fdb7f47183ab8ae2ec0059bc729323c53f8c07030c07031c6d031c69be0f13a8e3f9d27b80082511da3e2699e628851c60f889f5f611d0f30bc386662b88003056800173e20e5dcf88b3e776e5972d103d26bfcc05793e6d83996082e7840f8ff3caef00c173b2057fef140738a9dae6e73a1035246cfdec6fdec71ec6570910382688ca9476e3fbee8c1e7020704adf2d7edbcb9b801f946bbf230dcc5d81f37011736200fbbe57353ce99927e0dc86556e9c7597e704103b2a55999a8d2c890a92d6671f9783c1e8b885afd78fc175bc8a28ab7f5cc2a11ad3bcffccb9da9be9297b7452c48656231a58ff6610f43b0388fc295c792cf4c0c5bbc822c153d743853ed4e5ba7610b5790cc0762be3f4c1d5a652bc8c9c7d1339715802181316cc10a62b67f6cfc4e796f1c0c2f4a0ac23056ab2075fe285f69f5a0b25c0c0e5ba882a8e97fb12296328f3ca9207de8b1f9308ffd3aa76b07c991c377fc16a8206e3e8deb31bf176c710a52c8ad9baecb182f96a620680791fbbbfcd53fac14e4d4f38e596aad9f551b6c410a425a071dfd097b991e5f94f1080361b0c5288857e7ab55d53635b5282a472888299365ccaf99ed20438c2d4041d0f861a818635c53b35c068a608b4f10d5f3c7d7f971223ce80992065faf13c47026e76515718298111ffe2defd2c3cc169b20ff4a4bbb4ec60b992d43055f5e860a086982a097924554e6e4e3ae63b0452648df837993b28cf7f14f5b6082a8e9e73598ba89f9580df42548799ca52e64757a8eef16962027fbfeb99aff3c3cd1b6a80429acfb30c4a728ae67a304b6a004b1c4433f7efe51d4b04c82d096c7e31fc5edb1872d254128fd28715dc12d224190dc743978b0982cc32d204112ab50e5a145206cf108f230e5611addf89939ea168e20d7664a27755537e96dd10882f8561693a9fd71ec2e0c5b3082a49771c5ce53cac3b4590429670ef53edc1fb39c2248666b3daecc3ecc636d8b4490349fdfce97670b4410defc34f538050d339d43103386f5503c74b41cb521c8b943f89f8fdb2d0a41caf959a9e3e5ac94b72d0841ac142f95cff641906a3bc6bc9ec57a34b12008f3a1c37c3c5cb708846a51251a73163ec7eacef7d11680207c9f86f04b3955f20fc4982ea1da83d91c743f90d38b8679e5e1fce47d2059cfe64f75e7d9663e90479bfd2ffa677abc7b20c514d66d22d50329733bfde353ca21cd03e1f2770e9f72070f241f766ed3cf3dbaf3ce1d089d69fadeeae46277ec40da9fa83c2a4f1d4821bc370f2e4786eee940dacc7931bac7c3d079cc81947359fb707c9807992207428d8f45d75b938ff38f03799452e61f5c0d07b2f7607c46f524777d032957920a73bf917eba819847e3be949c5cfcda406ab78e73971f2d78cf06f2e6c568d62333f561bf06d266f30cb359e252bd1ac83e0e1e4d457f186bfa349087b92da1ef3ed88c1e0de4d7313fef5859b97386d3b66f6975cc40d45a59d5d5eb99bf0c44b1d3e8bb7cd5e33cc840683fbbcd31de663ec6404af7d1d33c0e3e4e31622079d4868aedc240d2f9147af439eb540e06c2ff28e5a19c2697ca1788f3df99e287e97cb617c811abb2f539f2c4ee023163aeebc8c505f2c6241aedc739b3b75b20c6907db933a905f2f8c795acbda5939e5920ccebfac057d5653c2c905b3adad786d58cdd15487d793afcc03f5d570f2b907f947c9cda4769a3871e5520b407b7ec331544a607154a59c45e2a8f3305a267d74a55f3a6f99102d13a77f97810bddb9e28a8dfb6e9a1030582e496e69bf8c4f64f20f8c02f5df5a8626e9d40fe1fdd4d7898072e1735f4600239ba0f43329fe9af6509a494cfd53cfa9440de0f9ac7bd5f1f3a4f02692a6e8f6791408cdb3f4ac13336bf7904d2e6d9b0f659c7dd3202b13fc47670ab08a48ad59643b32de5c8108154674143646708a4bc1e5234fc4220ea48fb67fa78a4ef0781e441af3e2d4020c6f879dd3be739f51f106b3d985aca398ce63e2056f264791c2dd8c6bb0784b058152b45b6dae6012986acd11fcdde23ef801c3cae776387a51cd501f936868f6fbf6167cd01516b3e8f43e6c8ff8983d7d2451f1f9f3720c8851fbbe5616c77b16c610382854f2671f153ee61658b1a10243ea84e678c1963650b1a902c7ad01026eec34c9905494e93d7c94ca5b9c882e89aa1eec6df7cf25890ae73ce6d16b9911a16a4ea4e8bfff115e4f89ad28f55e357fc5c41d84c29858ef6a8d1f356e4316b389b4ab1821443c3874b692653e4ab20fb60e2877ea171e957057963b0a031e7910af238e554eb372a88317692492da71e1fa720afa7ab203d2cf5e8610a627a1ee5e17c8fe75c978298d3a27a476fdd3093823cfcc1b7854d9907568e82b4fe63cda52956c75014c48a37cd3e5e28c85a496e5372bbb81e2888ffe36495befb04413de3d39de7615d3c411e64c6764c35e6f14e10ed7bfa637cf1fca839410a1b1f33c46713c45d971f555813a4b2f471dfc9953813c41e8d686ff5402ff79820e6e8d96ed62532f7b80461734af1e36d2c41faf4fff79fa90429ff789c3de7d15ee70b2508f222f5b7d1e42c3e09527af6eca16d5e695d12448f399d79ae2341b6791f7986dd3fad21418a9eb5e41a3a43cc8f20eb8bf82865eb08e2b58591e8f7a8691b41cee1769bd3f25e5746104c35268fdb64f9d54510d536d4674a7b16464510d7a4475749adfac5449073f2fa514dc88820554c71c6f5920fcee343903d6dca1a4732fa756c08d2468fdb3707938c1b17826c17e55b39c486d2981084f4e89cead1fa402c1e04d17f5ec3560c3b5bb120c89efc55a2250e04597b53d8f20cdaeb03827c2978dd6afc9cbeff03c9c7c1ccd673268d3ffc405a911f4d1ac34ffde803f1e5ea637fb0758fe10379a02f9e3ba79bcc9b3d10f3b62c594a0df38d1e88b9935fba596d8c66f340941e7f8a678e88de8b07c29d0589def01d8879a2fc623fa6fe69079245573bcb61db471d48b1de3eb955fca8071d08e599e6f52f5e8f3990722fc7c88fe36a5c0e24531f47adb294c7731c489f477f5821265a453810f3f8c703cbe14cbcc71bc8956254db64d943353790efd3424fe6d175b6da40ec98e39e553fb66103395459f8c1c6b46dd1ae81f47db9625d68aa1e470da44a0d3b1d6fcb2c0d8ff240a3b2557fd8bcf9e30ca40857cfac1b1bb5310329059f294df58cb59481a4f3fd038ffd71ba0a19b2908a8f816891bb566ebf297431903ad60f7a942ff9833c0c641f5f4ed9136b5f59c140ce5176dd077df6b9ea1708aeb2d69b64325e552f10362d7d6431abb7a9da05f2b8ff8c7e568d29a95c20da89a779cc4c994eb740ea49ed7bab540b04890ca139c6b95748b340c82c73295636cbf78105d2e40fe7626327d9942b1073451fa5f2ed71658a158812abf147f7f9d3ea5520a73c8befefe1b2d95420e4e8d94b4474e57a0ae4cd6ef29e950259f2f79266578be98c02d953ddabe9e6b80b428178dbe3f1502a7f1c36e613cca318de62a5d009c4f051bb3949a7f8964d20e730177f95bc1f5dc9045267e58c9dec9346cf12887e29eff129811cf34f53fd7893c7ac63ad692c0f35482049fce02be484f7384720a61fe60f161723906aa335fedb4520fb259b08848cbc92acd4aeb28740ea114b3f8ee619550b8160a571d9d773d29e8340728d3b157fd81a6e0602395c0f4a3ff503d2df8c64e5983e8ad60704ab9adfbd8f7e7d0f483deeacd5a5f280a89747797e7cee803ccc3bbdaf2012400784cb7253499fb21091003920cd854e6ff1f2fb7848001c106f3bbca774c107d321016e60ec81f4d0520f23800d88a2fd3e36f951e7b021016a409e9ab1986a5d735f48001a90b32735e6a1f5d83a84b320fa20a2b7c7e30e7d1f5910ed538633edc482b43eb098ca3ec46d1e16c4d0e9f0bc5163ae98579066bd93c6dd15c44f5154255e33eade0a728ffa52fccdd7d69c15e46c3f27357a29457b15e47198290d92bba5b52ac831661ef43078d8773415a438d3f0d2e371a8540f15e458b22b623eb2a2de2988ad49a4bd93a62077bcc8d8e09b3d3eaf14e4c145767ed9780fcd2305c9c38bdb56ea417f78a32067d7b476387111ed4441d6a0deb9599bdcff50107b2ea7e81bd5f7634041ca64fe1ab6f20952f6db541f16ec87ba27489d3e886cf8485bf44e105f2ffedaad4ee69b13446bcfca435f374194c9b1cc3dda1f79aa0992ea554be5b09e8765268851348f433efaa50c62823c8a95152d638bb678099269b56db4cd2c4116f3f1f87cbc49e36a5609a2feff7824221ee6995182144f3ec307d2098d9aa8542ec5c2d148241409c441712814089e301f931308001838200bc562b160441286791400035a2a1e3436241e24241018141a128984e16018140685c2403018100a0803819040f896083d3fdc3bf8dffb8480378b1dd1d7614c1076c6920f927bb3c798c778e376955f365202c99c28b5524207da1f73814b014b2be95b1a98e594f71186de9e6707db6d5e20d2e2ad5dea4a1117ab962c94c6bfaa98c1b7a4def1bded74c16e72997c43bf5bb47fb83262beb324ee42e28d606d6ee8ddbd1146f9dbd050d847a023c58fa69dd7a645efbcd524a4977f38ee06f1b8618802766c2f9d1bda5adf70784e0bb90dddaaaa4592c1021eca9668ddfe2a914e865f49b39639a675b52efb295137353264cdafccb6cece4e10c6cf659f9f964f6fb6764e0cc534549306cb493f1000e85184ab7456133412dc9b259a61de2af41f69e036706f245c71bb446847cb57b12ab9f0b9c457537d51a29f9dee22bfa77a395f6b5c25382a6fb3a2c87638c0687e9e06994e3ace6d25175951794a97c43fcc84cb33f761160a2e2b1be594bfe76298ad091a7a5f652acdce98abbab4a7a81535b6f26b788dd46425925ae941a6422bf1e195d2f4ac5c7f8a64b210d8db39f373cc2a7caf55b8f2b1de10611bbd0f6327a797cf194fff17d5f3ba9a2446ccf95c71bab83a68264b355d6e1fe44abe94de96fb20f4dc9c8e7e86bc045c93f2008e07bb5bdc491b5957f0180a04ddfff73555d8c669505810791a3f4db52c0e4cfea286ad40751d7f22f8f40e8bb133e9f8a116859f476aa0138217550db93ef53a12138c43f761bfa384eab1b53a74851ee276bc1ebbdfac6f384fd9c592e90ba89042c73e1c162c0cea14c56d63408749602522d39e6d33b8722ccd8eb3808a643eaa64ffa1c5f979dcd4d7ac42a2d941530ef49efd019407a5e8d08ec325f4abfa579ed28546dfe49e7cbd1d530cdca26be0c8a7a7ead10576a0e2fe834ef9c3c56c5537d28f6868394feb8dc7bc78af94fe15088918b866389266f299210359507623ee600ca5dde9f22e31fed866e625bd1ea334867714e5c8e7c257e0f9818d79c4be017d2d157f95a13b335484183103829c61ff8b5ceef86bbfb68cc374f534642245ee2c46dec4184c009d02a6f98d9ef65a8f568e18af8f5dcc7b650d816c21723fe62e27bf44c9dde6f01d60d8283bbfd6267a12b3d75461583c3f4f9b2229c8ee3b45e46518de002e74c63d58731dffb0b679245d834c3ae5ad73f3d520df8df45b56e1d257d6e437905f628c4119e22157358e9b8581d90dd69a8701bb17c6561378b52aaaa90f5b5afe501a2afe475d6dee971dcf3e48270ef26d850b40762ba10759b1fb22dcc6c5bfeda94d38542da0c0e1a86aa5cdb4236822641a86857f15f8b2f47c5388970f14482e343364af7ba4a78e098b61017413cc0d65f8749bbc0a9e4b32f06e9d926ce545821cd43ff7111eb0621e450b88aa95afe493c6bb538efbcc3cabfc5c03141b15acb72a2fc233499d241125f0abcfd9be6f39f7871193fcdea7c15ade95169aa295014413ca59792d8f062f12866cb9fdaecbb07ca4dadc3001939223330c3aaa856ffd5577aaa602944c563ba377a1b8f8376f526446254444c5960867b63c493362d621bfd567e629a833c32bb8e737cbca9d456e932cf2161be2bbe9ede83b81b90e39e1e7fbfaac9b6a53d5843571b61f0a9c896bfcb0731919172b919b5747c5da7bb94709ccaf459edbbe4dee4b5717298b2a08798438acff20e56c7ba74f302fb246931fdf1aac9012337919fa799182e923017334f004555ae73c4a0ed5a57d48aeca5379064efce97d917e86dc4018e2244e85287f464ee4e9f361fbff40309c43b652080b6edcae3ac3b77ca529e6959eb1af8c5e811c235719fb24bfb247e49a3c01bc88c5a732b61fdfda47dfb52df66dc07117a58094b043297c11f7d1c5b16b0061a6842bfe53ae320780a2fba0021aa609a7ec09397d3eec7f12c5847628af095e2b494329d67618aefe41bf569f6c1efd1e05bbf7133065133595210d5252954c18ec859b8bfbf93e3f2feb51bd324edfa4905089852173e8147ba0731ab445320edd8252a64c8b90f0af7133450325a8a9444579705c7cb98fbae06bc41d8deba27b7fccafab27c8056e42ddb7ec07e805bf5ee3a05ea443562447ca543b8019da00880094a7d7e5785e9d67e194ff3ec4258302052ff7695af5ebe385f879801b431255aa7c9167e116019c521aa41c5f185ca5f2360a62bb9f5306c879f7f8ccd6086fce9c672d7971b14fce2b7a146dd8e8d78eb5a4ec56d45121809ba1e470ca1d40d7320aa7c8099223d5504cd7e0982a7f1e55a802c67ffa15bd1b36ded0e683bf12d782b7e33873df84ce31be6e9f9373792a664a155bf4ffc42853ac21511a55a45475f204f03ee2127c562617ef621f093fb3d6442333ff605c8d17fd2daf99092cf23d4e8384492d24f474755c92aecfdf891378fecfdff53eccafdd93723a4f822929611b40b28ed388129386dd821abf478c782aba2972566ba0ce9c22784b88514c3142eaa70060118e700af9dd0653184633425aa0b7f56c98fccc77d015962bfe79a8e4334c755c14a170b540e26e3801692296d69003d1ebeb0a4556a1284fa787c008c795588b70cce9f4f278f6beac0343215472336a84da9d664ff7502835aa1130f2b739b11bb6682b7f9992f0a97dc08d9b6407c22b64d6418ab951182d87e575724b781d96274014fbd61546a2e739caa8391b1c694d9da68b3b14863a487cacbe8112e1cc9cee4ca0e4cd2c48beb3f3b986f69984e6f24e1b7719522e9a951f2c5740b4165b14876bfbc82298ee5775065209285c04d19f31b9069738630b1160bb4e2783ccfcf04c500407dc281e36531dcd308dbc6da69beb5e70d398fb05d8e0060bf2b1e643d2d1f4841339f9c50e05bad1ccbc505edf02b2cbb99360f83b827bc504a4df2c50ba0569667471168ce5599afa65e1591408be4f536656cd6074df5239be32f868e0df1ba61382fabc3c27ae0b8073408b95e37c1c19f4d9c4914821b7dc078a5f94a3db299fc5fbcd086b7a66000558b914560fcf6d3dd45aa66071f70bd58eed43a11daede9fa7eb6474464d77311991877547861101ab4f7ffa299d92ed62ed99a438e29bd29d40cdcec921ddd0e37613b35026b1fa7195f1564697938ea9e1f661744d57308b6eaf44b7bbb3864936ec26511eb56b8f8ff0cb0911f6ee72d12c693a60b2ec570319f027b48874cd78610ad12a34c0e2962d9b2710a6d1bfc19ab8231fe3c169439d030e1edd7e40235d4542c60d6b8e2a4b64837657fb5f6af52fe6d2fa32e87d2a20a13ad2418904021e118a6257f69b5105890759b67b0520b438481a3d8c1784897b49187c8acb8104adc980c3772193a6d109e53c1ff8c20d9131eaadceb6d55dbd06e49df6d47eb354576a42de6e3f84224bfb0ab62ba88d896817909038da8aca5321e475ea3467d4d3920bee6be2e5ba97322046910dbdac3cac0ab1ae779f3d0a79ef04e6d84f29a9783838eac0d241112441cbb1de567ef9385723fa3a08b7a2e2dfad854e401b8dae14628f23b709ed47740c42d7cec87eafc7514396a78e48d2a3dffdcdf7f23c7cae7fc81650aaf5fd0733423a5270c0aa8d8594b089b94b024c66b0491c0db038858503ac1c7778160d2790c4fe5f5a2233ec0dc7f4f5bf31431f668894530efcc60eba97396698b402191d6663f289e0385d2fa864df14277e0154fee9b783d0e5f8e47ad6ad01541a498a14b3b9b1b46065609da28bc581199a068a47198e2be633152da36581a4897558314798d849a150206e88efd5f8c70581f3ec3f9e407aead4072cb79ba4160174cd0f1fa56b041e244da15d81e06fbcf217cbb8027370a0ffb553a18eb3e881ce031c6ff559207363617d67aecb06d802e50bd6cf69efb2b0d1f35d0d37b3a7923458d9fdd8b7b0b16328af456656814be419136c92245179f7f6aa8c951258d4ee2a3e1473314c0d818583f426df39b414c47dd0252d4b333719c2b98b58fd0dd425dd111e1bf495deb7fa53607244ca619a3f353ea31faf4d691343792f9cb46193bbd668e8d631c98b746b4630e0403b0e6dbb83232f29712ab9b7fce7d35885f32d86886eb0207cc84dbe34517cd9121ddc3807c1a8d4eb206acc94d91bab3acc6415c1325ae841190f2e6a7d27637061f736c298cd2ca75181ba75e78fd03db9e1102fe20fec1c6a6159f074cdb198802b7e4326883cf6d34f9f24589a6284aff5927a48b7d344316a9a199da30819c84df622e9e611087e9785372da65ddf9d332210068901d9150a64c33f5f2acb1bbb27122ebe918ee732e3603d4d46cc5a5c30ac04333c6ec0df0e45d24f9a45bd326b19b91e74c7de46a7b0a02be6aae8c78708098e1f60519058c7528796c78a93f224b28ab1400ac2e8e6f65a88e576162433dd2311df7eeebe21c95bfab17edf66f16bb41e2148646e908b2a98a9cb0ac17f27ff7da60b6a58c6ce1a889bad6ec4cd4d57ebcb7d9bfbb4f56018c6ae4c80475292a82c3cc1255ba789adf9c520edbc04031f2a975a7778e8dc03f9ead09d1e4054903bff52af36c6e026a869ef04f5e00b4c00e26de70bec024ba2cab051c378eb345dded8591cc96455cb657c6d357dc90d626789ed778e34228e7af48c1023b591aa822836b136ca958266aef64275c6230047c67bb30ac2b7151a35ff934c72d04ee949216902188cfb455f98d631ff0961ba1a45a1f6dd3cf5777b35e46e13fa571e2cea6b57f5451cb7a0312bc1bcdfc483b5759402c74cbb57799a460c5d916a4f077b32a45d4c6d4c23920ac73c3dba1aa5a5f20a4dbc2d93f6f78b66bf62ec3cad44484f4bf7c167d979f7c73918bb1a0b53cfab51e5105a11e8821e16a438c99b83a996e3e88cacc5fd4bf18b6e4a13399888cbec931f1406c0af3bb4efc6e42d5077df362a11c2a7ec4c164e33e436634a8b19094c2897412f7c8554ff3d74fb5321cf975ff96511e808574b0f1db7948ea899cefd07766b31976d7f1a7b4408ebd2498176a1933177196566933217623602fd704a09b227a0994c6511b97f861c26b24856642c4d1ae32f753ff845994a364849bf47fca87ad893852daa23881ede0b10b8db1b8e3a8b700e90b14c7a1c6a6273cb0053a5052d2cc7884fba579d520db2ce14b7731b9fed7502a37ac7eff6cbe92937e46ef84f9d3e42d422a3174e09901fe4491fd13ef1f8028d9a1fca93ba780aff0578e8f90f4e16e2028d2489bf8ac91d989fb2302c3b7d802a802a85e277d4959a844563e1514eb9dc1d5b8148e7a3b4add4ce72fed429f839969a7ec3a6de36596de3542bd8d319b0048336ebcdc4b96870fe499fb07a6f8ce79aa9df5c223eb1ceed533b5490fcadf949a4eb55b79eb90537e672896115b757f89c0df5ae24e02cb1eac32b2d6f4efdf106ef67d67bb9b1f5ca49257448d5b35429d83e9b71ed6278d2c0104c85c55a1daeac71463fc012d798e3114f9f4fa8bf71c17b281f9c1f63817c9d6da53382142f43625f509b183f449e607529f279b8524df39340759d05c327957fc23ab98129eb2494811f79a61126890c3a7f0d46dd2558678f1861ee5c89f0647eee42c3d3257c4eccc1e03f7ae3c719b1a541d406f0c8235a9d6b46042cae4223c3feae2ca000c2ae2e03aaaf2b6b7da9a9e043e4f1373af8cb72878e4d6019240c22ca6ae9ae42e5120d422ad0aadb6d17b066bf036b0b6ad544da0d3b95c964663cabd29cf0012afcc0c10e2f786c115bfe734734140151ab336dd782fec169854a91dd1eae54e11bd6194599153b73b6be0eca9d1aaf66e484441c804ae7badfc0ee96c0de13553fc000d9c066e63a35636c5122277046670d105dc65136111ddd015658af5790d3786fcf832d81e3a18d74dcc3dfc6b11094d8f4b10fd11cc7674445900035a4fba594abd1406cd79f11839362f18e015da130e1793feeed3e61c3451db05b20d1951ac1072d78a11626d1fa073a74d02b85c831a2efc0b994cdfbad1b5ef4238668b2328472030106f0dd394ed1d4f7ee7e263b17deeeb982018554cdb58d21d54ee3c46eee344b6f474c6c82f2adb5f2253e13038896016b10841bafb5ab164cdc39b5590e36246326636f6675d33b9bbc633e21b5761f53319f90d1ee3b30c0f189eb9a5cb8484afde520a05a4ab604a2a68f7cda3900d43f85670550a211518c4f08d1b2e61d200e63bab03f251a4111b1a4ea4d11264222ee5431ee0030a6ea3e4dbde9e025d5367aafbcae48cf7314d7e0f80fb378dda69cc63322f32790d05e83df64e9cf859e420b6e68004754ed76364b0485b44841e1a0399e0d38788577ab9a806d43096fa54de9ddc6d183d1f8378ac598681484ad4c716dd2896171eedc2e7477ca32f8ee30bcfc01a6af70177e12b218f9cc38ba31aa3fc7f38efb174af72fe0cc9ed3c33b5aa7fefbfad726ce8a5c1e23362b95174081a3ddf74e13c55e1d72fd86e1320a6f87ecbdec29350fdc59ec2a069f2d1ba88f2988557990b8619360e7cf47d729d603aa5b6225c466828c804b63ee8c3bf347407d9ec7e75c972ca44903644dc086235dcdb49b44b4111179a548a0302a4f46b61e65b6675879779c6a3770047b8356354095be3c9ef1678143a8b9ba43fd02ba6cba93d85d2776652e09bf66c7884039c5729e5d716858a8aa3c190a1f5e9d9c75befabb889b4e342024e9caee94828c32370042c7112acca2116d8c3c672ce55618db81c50b2ffaa89fcf422c75d2dd3ff0531f6237e1caff2c8f82418195274d0d4534c5249eab857a809ad263e917788d4018d6176fb94c44ee87002f9d05514fb1549d9275dbbdf16f2404565a1080cf2831e35ba495e555a969b64f7fd1a0448408ed40a4ae135d983c14aa8ab8ee40f3d7a8b20c453f035d11db925262a151f04c57196b75d84448880bb31c755284f5ca3c1fde4d347f80ef8f68042e140936be9da80d6f4758fa4a3d7e732b90a543b0e4eef3e19fb8a295e009079aec589ee43b2232e26793795e8cc3545da9c7d1f8a0dfe26972078c8b98456187173fdda1d8eafd6656df87a2f838d3b875951c8871c33dfedcdbe1c2df8227049a056f5890fe62560dc1444d827d354e23a24c60a369a2824ecb9a5b503b0336685c5c6a99b86d86cc59304f2b3a37e6d5ca8c50c22421cc5e062412fa321c0fdcf8993f9eb4e7d2e8fa80e84b9040522240a4c084d57423c061d20e675e8d8e26fdd64ac18eab85df121da8a7fb2ceb3672a7db2bccbc91440608cc29b04ae5fad5960a5d454e8c83e92a841aa271a8a80f8a09d4d92fa86978a49839ed232a99f15dfcfe969453ce54acf76db9039b2deb4ffb7e8084fd9351012c227ccde2a91715349974d7cf195c153d94104f88a85ea3c8529010db5493c4548238d85186c2cad10fe3662fa9410bc33ce8f86b2b570763701cd04a0b96922b6073928e543511eac0d09f109e2b722073c00fa8db62271db0c4a16d241c334a8c0056d1e750834a2f59fc8194a85f089247bba652335daf4ad0badaa5feb0c3e2cbb4b3d46126468c18e87a486e350f35a454af7c05c087f57ac9a4f5941239f2bfd5df5829f381981305d1f0faff1df7e3d232b694622e1c2dbc8ceceada7d9504bd0adc68cdf186511aeffc8efdc125ee945c94a4916d8947815750523a21abd33f6b4fbbca24cf27f324721012ec3a21cbca2673e96c5e84f025d08fa8f7b782d71bad90a24705de68653232e2e01397537d83bb39df5354aaac6e59a9c32db4e7f2ca525deea9b736adb32028aa57038a6cae72d0fa38fae0096ef58d10d39c995b254362fa7152aa8312138a3703d923c97d8ad64b8b3eaa1315d2c79f4ac0486d07fdb523b8cce88af28a949f9e993988f149b147f8488b80b5e69650de96d4894902b3deaa333a77eca68e24a87d9e86ed5afec9219aa61d65581d61f9e1d35f4e8c98aaa7db16253ea4d2e7d88f86900563f0f40780e7ded512a2bd5cffc2e51740856a0d1ec7a17921ce2d6cf057e36e5d7f45d1735de4f7c15c7900e194ef8ce4279f61511c0ed20d6ed5174570242a3c3114548e1c0a4db227ef4f0473d2c4f5f88a0a5b46cdd3f0968866ebe5d2e2c0d7c276cd248a71521dc4ec7c207b946fdf0353710ca712136d2078847671501668ae6385c9efeb7b39558fc0bab9f5462ea44d55d557eab4bbe87a2e72ad50d4adfd6f44cde8b40dbe24711faae93cfe69381054bae528ae8f073cc54a9f678493770ce83875325d26b4cd11a28dcc6d16581a64e1a9231c913deba892d1297f0ab099520917b2162ab5ce3d29c94ec6c62bd92b6d19c6a89347cf1b22983f011bcf1912b387255122387fba2e6565550d47f82de4ac7c2881d992a99fdbf99a0a6e98721b7282f026d169e237ac23b0273eea2d9e4597d7247a26066de027f419f483fc056a258767d63f76c49dcc879af66007b3b83a3a098dddee189c13646a1027941660e26c7d0495c04ee2e6a7e3a5bba463e8e0bcd3718238276abad9ae8adb614d7d136ae16f4061d971ae213e21c07f2c7a9354c0549df34fe8c403c5325297c2c1e394cb7d83cf3df59ce3feb22b1a884a6f4b564913e818a9f5126072d943a97ef6a38980aa937227f1e81b03c50b0c79c6026ab53c14ae30f0f3af1b0e89ad294db8c712dfc5b01753229ccbd362004f9e6b72320a8b3594a54f511a14c2a3f5bb0c3615baaf57184ac8f2ed8838ee073f6059719cc4a38c1e9949d28d4f2b907a35f1e4d1d98e3222d499078d6b680e3bab244731b4d814cf09c246ba32a56ad98924ee837e2968d23900830e08455099bab9121302aacb7879d531dadbde465dd0bbc9007fc3880bdc492c3679a118c2e55617b6d2e82bdf31ca28db83dd48280e587bd45fe0f1b9406b6d9594cb16ab3f1e185444c649908605b6aadd81ac08daba5134dba13e0a26f92243ea5279f33c97d2e11c30a7a292b88ca2e90a0f7c49871abc43132218ee52442b1e1e4e79c99c35f545b5f57c5532848dc86ca028a5c965ef89fb76687e86f187f7b399e0a9864b66f5531806d19c84eafb0c19f076bd68685c58391e129270977acf7788ad09c26d1eef68bbebab3065d8c80433d0f4d26186dbc0223a9c1d27abcc516324dff97b4052838331f77fd947ee8f1914fa90768cf780a98b1bc330b4de3d39bfcb83ab2c7737734d851cb052ad1d146cf24f41ba832bec69ba89fb3c16f1d049ff06333ab18e04e3de87de58e1f0444af961f9b8f1fc6e7e60cf079265ab5f64624a5f4dee9fad26e6c9de63fcbabffa95dd4117408525372ff8c4027eb78097e598ffa0289829234d4a035730c46ba1b232de52a0e2e41f60bffe91018d5175d6d2e15c1bea8cf286fd58ddc107d11712345341b1917957a235af1fa771f2cb44800d39df859e8545cd620cebde30a37697812d4667732e36b35ad54c71c91dacc3b42a618e30525a27dd2f4789d25092a91bd5fc03f38098ada9ffc76a28e666bfd032bc9c271aa687d821695cc35e43c68dc64c7ac7f075f68167ae76c37b6dc5bb46d1021ae3a4809fe505dd7f2fd78b7a242bf9605ad738bde3dd72f81b2b1b849a6c694d6a6b0e76215af486911d601cc24b3e9a986f8ee1c625470f22297388e3ef4fb8e5d3bbde14f0310af47f527f07486d7207f341b2968dcdf9fee5e433e520afa74033bd854d3d5fedc906e260cc1e5045126192a108afe89cf5799ca490ee2872ca4b599dc7be0a0eb21e1baa66eb4bfe64e92c2f7b92e83bec61446822ba3513cb7e882a90c2c66776d630cfcc538a2a788178be90d6582df6cde091d5b59bd573f03493d9779f7434f77e602c75482cfb3d4653885f6c2c67f2a7df63b5c494b4893c6efd7fdf0675114eed2a4994440f2f62d00749b4f31295ee17e38f23cde8f512a14a618bd6791f743fc542821851391ad20b39bd996006474ce78f7e7bde178fa1dcd937f65ca2b15e211943b7d09f034bf1db5469e8bd4321903ba8ed8fe2581af7fc62c82a8bf8539d39a73082c8e897c12bf2a85a5a096c93c32f039e2190f6e7894aa6bd007b154023a94507fee0309226f8d21a044b666f1121c7de1a0eb4b6327f49f983921c9d038a3a516efe6f3ec8f7e726af4fab1127dd257a52ef6ad7fa840f105f4fb7e6e6b7f1f5acdf3402a4d98fbef584b674f6e9bcd211c7d7cf5ec3dc80312666351fa566ec6549ad6df9151e056ce5df55d8080d371d370e5bc9422c5baa8333de3a810c25173399b3c407634830ee3be2c27fdb751197daeebc445ebe90008a0118b6a40515b5249275da6574fd30005958fcbf783befbff77568b78210e7aa01f50b01f766a52e6604f55ddfbed1b3a0f64051c60152319c45f98b909dc5a961d1c225a4bb24a3d716c85423cf6865f5a45920e992ac5bbf4747a9d35d5ed2a133d1359d8ac0ecf9d1f150e0ab0c357c4f49c4d7c49373d04dda4165a3f6348e519dd47fab0eaa5290a7833bba420aba5c64a95fdc2abf50bfaa89e7541530a5515cfbd8a1819474f7f92b66686258290a409e612c120e725e8c6ed39762ff3e4d0bd4f1487708ec4682fa9a7db08c74e1830ba1efebd7604defcc14e5fd45901f29c267b495893a4618282a81a4c09037e55a15f25018fe8fba50a28752fe57db7a32dcabe08caae315db34f89350f4a149dde6fc9d8ccd52f8e837a1c3ced10f415b6932dfc35001ee8db836bd1caeae6c497abc0285fcd85c43d0dc3b9648256b96cc649e8f44b9c754e9961dd10944dbbdcef24ac0ab2a5e2d79ed1c5eab13bb0176175e217b15e395865795bc5af07ac64b2a2ff9bc6a21afc52756e4959a17ebfc751c8d338034fbd7b1575f7c30c0378bc7fbbe298864c926c401255e82e1ba615df7b36e9c654af30d4401c21a9f6ff9818058112db094fe1ec2bd3006ac737d2fa586666df20beb7c7d04d587e21c9464844af945f5a1789652834a1644e9604bc9ea00f50c145109050ca6a26d551028e083d91b1f23eade04816b2f83a8b2a412836a35a280b897ce7794a7ee9ffa3f850ed5a240d166511f8bff41610fd53d147fa88a0e14edca7cf25b34285aa07282c20eaa2e4501e120406c207c8a662ade278a9fda7cd2f149fb27ef9ffc3f15fa043da889094c4fbd4e828579abdcac65a2425aceefc10f0feecf594bc25d82435f794d1d74f1cac1a902fa8c4381dbe63dcd7bd31034c68c429ec707269743819779eaad6e64c0fdd57a458fc92aa98910b01ffa9d276dcad889e0163a9a4dd594afb849c6050a010606793538b4c56fbcba0d57dea5f18b50504df5e53e7123a307e7bb0074d3024931513f229afa88ca14b69504eb8c9764b389c5da9e27697fe03c7cd3cde704fe4e697030880571b4df925237e31a824285b048af45d1e254a7a570ca803e884f9386a08351eda0c3d05fb83cbf65801accd66d9482ede3e033c0b42bac8c1b989c03814a1d3fb132ebc31e3de2dcb861b9a1c241268dfdeaa2c403dc38548ef9c463bab6ff13de84a08a89966a67aa6810408fcab5997dff264c23ae6c253ff008f5ea418416258aea646cb3efb0f1769e284219da08f92cabb708cbf5848eee795a2c202312bc79520888bbc18307cd25f00f85e7ab1a4c99272036e06e7b638d32d1918e7fc5e759704511e604f938e22a4eac32668830d613673be08451681e7795986b2467ca4b8463192b09494926cf7b0892051ee23103f4ad234ea22ad2c297811f347fa9fbba1df41d5c0897ae699eecbc842268597923bff0841a7d835ec51bab45e7eca69409c5bba0506dd555c19c5db7bb891f0722c211ece968207cf3ac8cabce1e54e373c66a43b04184f0fc7e3ea0a374d0b4d6a9c1c465f0dba2d4dbdbc06b583056c953ba05f0882dd7e319c42616220c3b771efe31407f22fce860926080f2a78d770834346ebd2a5a18acbd99f0829b903b3b45df30504bf3fc0788a59810e80d06d590febaa7993ebb527d2b31d192a28028cbbb5ea07808b126459e4c9c041aec71718924c0630a56e25305474ab3383b51fb5638b85a093de653904e1c468a31140d88578b78a55bd9a322a6be1e7225b33f7acda8a84fd7358a0c91fbae63050396649731d600b59b260f268518ab3102cb551a1822212771156b4ba2822d364849e0cd8294445da897877c353fc51ab50f81fcacc3a0d3548cd6601cf05cf70d59393762401617721ffc41e3f272316d22e46f81d1e35e8d94aa4d851f63643e265070aef06e80ce3b61a0e7cf9ef2371466588311582d6682391cadca362d7060d261a377842188527491291efe9ec1076b8128cada07ee48868363f63b60ae108576c34442efc52a3ea7f99bbb76eb173a02ada3fa16170e6baeb5d102bc5e020e22cbe3a130a42b917b84db6945c4731d8fd9573c3b97ab5284c1e7c0979495c7e1d855810494782637076ffb453d30523d8b262624bee38181286073b4eeb49ad0d58314b4194df07df9c702ec096ca84391ddf50866508741443196e1f52b0a8e27eca4b17128c8b0206e0f753fed16848cf10e1cbdd487be62f15e54878053cd2a294fa7ea23da307ab2804ef88b85ccba95d9461b24b38d037b4be380017bbcc5557bfcb629eca5f6d83604640f6eab91539a60d62c237893f092b124849cf3362dbc5f8225c4f213f1230ef3805c1a5d5d0379e0349a45c4917a73572c15037944c3bc399ee1c8560c8fd503996656716ae780e0914de1584115524fd2b65782ea42332f6b0426bbe3605c5db8dcf3bca85282bbc1de2541cd1c535791361b8c430ca8d738a512603c9ff6f0a26cea0885d9b2ec536e0987db0f0edff20f37f12c78a158581cb680fa32b328597a26306d9a180d42f0d6749d87dfb75131db56a077d5b81243121ee7bc1cfc3c9000a949ba44a1b1a3ae1b42e5e0f5fe47fdb05e3cd703aa4ac18b8c93b91c3d2a60fe47a979b16f246122d52283bc922551bb0cc13b1dcec1a20c3ff665e17030a5af107e7ce1ec00452a30c2135b0ad3904995e448a27956fcdca0376684b4212122ad2d21c40547a2356d9e0f8620e8c67ddf8513999f6182869cb56041978872c7d6898542264ca796053fbb1bac57da9979b636dae81d4d370ead84bb11ac5a2a477b9fe7358fcc1d59171c35b2e5a9271a495fc61696554962004a11cd9bdce69c413f78bf6fc7b0762e42071cb374e616cc79c97986b45e81a95b88e58c39b85eeb920970822710cbe369a77fda7c1cb95f4ce236b9cbafe6ceb18c054e5ef9baf87cc947a964f76d92256cc30b269312c8f627f731d64fd8273d3b1a95a3382c6a856f62d8115263add0b42b1ce3c638391f0915e42b476f39f5cae1e7243bd40bfc23365205a2ce2d29187426f0fb737662be0a7261a69cc7f4ee543b5dad61f2c69dfe29df8477accb13b95567bfd30041485ea98591f55a855c77acf392a1d2ac7683be203af3851fcec7c05d47dd88dd78b0643d10bef40c49d1e40e82534e7a2150ecaab146889214b8cc0ff50f9e0f144ea3e8f41146327e9d11338eef12bf6074d777e33c068b0d821c6b077c8d77084b1418253fe9c5445312147d873a244c32f961a9c7a2448868f98e631579040ef5cc90fb6778c142e12f2a1e85c38c83205a3a2fb3970a4a7c54ae3ade662023e5ecacb0e919a980ba9b260ccb53b08ab60e2ae51bfc1c10129cb8390777fa927a321900c87f1291f6f4ee048be846426e84ffee9bd4c5b6d1f903c6e5d2ae1c193385b69d1594a38d9b2dbebec6179e0289f5393fae88c79fe437d6014d0d26cccff3fd1d7926986a4c247663de0761a12a3f87024143aa41ddf2e93e3bc0a4ebc34fa3b8557c5da2019f8e56c394155a019ffd6fb2a297590e6c2de041ae00dd610e2c295cb9206596849cba59e4ffe31c0bad472c894d7aa2e0d964f8e385b5b120d7a764a6bde9a6af1f37fde9775e401b20814b2d69ae30f907c29ea02529a8306b8dbf1aea6bd79f791325375bfe36374c55f141a1b62478857a8ccb948b407299acaa38b4c41bf3295e1025064b2643f13c8ac2f5a631b328e5fe46a2ead6bb40df6efec5270b78b461b14c8da546a7bacce25be23335a9ab30aaca5eaf11e4939f9dc9926d76aac34be26b9a549c6ab0c8c2cb7754300f8ed39c200917748c440e2e0d5975af23c3e368b7af760b9d325d562d91327fcbe1fa213ccb7d6f738db539fece53b4c3fcb4172db504a8720fb9226fe15225a4f53b7ad8b4e7db936866ce93dc3f023f20371e74c74721a570012a76ca653650ae26e9cfcff033fc0c3fc3cff033b00dc2f7fcf63fef1f3b255936e6761d4a9c9b649229a594349d6cc01ae92a518070699ad96902110632063206e8a8df773663cd7f074a2134cc590c3cde703439d1bd4e2bb496bc871b4ef62749bb977c4db53978b4419bef8fd7fd930dc7decba4d2cb99b6e5d9630d871f0d67de15e3a18683a6b7a45cee9d81471a4ec2780962e3eb5b2f083dd07090d7a92649625f6d726864f038c3d94b96eb11af994f30790e12c48881a346f230c3694b95d89cf1ab84914719703b25e4650a327ba345071e6470e46f6c93de4e76788ce1184c3229eba7a2a99811c3713ef3977597b4f187e1b449e764b92c49b866c1502915ce74693ec964fdc2d9a46adbf4ebda68591a1e5e38f9da9b496ac394a89c47178e1b4e30213faf964db8860717f8cc3196645cb34d533398b85027d7edb185a39fb68a6c13939ad1c22996d2d512d7c4799a85b3de69559714ed259960e120c4767ef5945ce12832ce7c540a266dfe56389e283e325deeab704c4af69331c8f1d32454388dac97d61c279d964ce194154799a04a96fc2e8563bcac105fd393778fc2b1f56ac4f78fa58fa07014978b19cbf29c89fa13ce5f67d2c5d3169662ee84a3578bcac52871a279138ea1a4f813355b6192890907e9a2b4f55c6909c7bf349b371b21464909a7934bd782e9ae5752128e7a2679a620249c548a3471eb949c5b1fe1a004cbe0a22e6d84830c35267dbb568493a04a45c813fafd4c887098d3e12b32e58d500ee134329849169460620be1242b4db60d96959b0bc2d9a26d97a6501d25209c3d4cd8f40a2d2f0dfde0e83abaf144d4e24a3c7c7012c636b3ba3faef25e9c64dad31283e64beef2e2242541254b9b239420ddc54964d2b12e0e5fa6c49015e5e2985d83b4b618aa840e1727d9d2ac28b1424db6cc2dce656bd1d425a91b94d8e2acf3953cec7e2fe9b538f895f8a1ccad448ba3d86997f6ba8fb967714ab57982b26d657192a4b02b57d9c6e29826cae42b65720513581c946a7d97db9265a977495a1cad6bbae224c65fef5392f012bdd28ab36d9828abb12bea841507f9b8af507731bfafe29474f55cbe8b163c551ca368cd1d5a59b4965271ceabf45fbe79948a50710a4b267b6b49903b4f711e5d3569e29b7f766a8a934aad763156f68df9521cfbdc548c0acd1a6f529c6be36f4ccf5e74cd288eb15db284b59c6a9614c5f9c3622e6136ff8c341427cf1793f4a5337934509c2d26656342e3ed55f889837a93434c9c9e38c528fb5633eac449ccefdf94a7e7fc729c38a78595e6f80d16d6dbc449d59728d3f45b539e268e6ea2d564bd9889f3f56acca52b7ea8ea30719273a3f2d4252555c9250e6229fd55e6b168bf254eb14d2c21574f492a5e89d377d5c9341a4a9cd3ae33bf44757f651227b954d42471cc1a774f30db344b2d12076569bde47d8a9e689038ee9cb79dcc3de29464c75458d2923f33479c4daadb93a562b14d1a71de9f8d966f348d1a31e2f8a164122f4f9ed1f0228ea6762a946b65d18d228eee71b1d6e28893c5441c94ace86942dfce34449cb4f4892bf237995e05e310273bb143c45b28434708971dc0304442f7869dedd05acf582b844936e3625c8a0b8fad0c2b1563180d06210e3e3af3953449c59c248c419cbe749c5ca92c99c6de24018620ce1bf6e5629693dbdc610462af9331a97e2be9372c80018872e4c91042ef6a753976e040018c3fa8494bac2aa7b556564aaf926f4568550b0f861fce5b2629214d0cea018c3e1cd305d1fef9101f9651b5b2d4f6ca666e594b4ce2b48eb987735849ed19e787a18783df9d788df6397ad330f270aad821635dfa11769615e0c0c349869755a9d5a9d0228c3b9c7c339512b24dceda0cc30ea72fe182ca345e1d8ee9b4629bf20b1d8c162e6626512db1ce20effdb171eb42b9fd3f8783d41c9d173e4c3c49d20d6e6820468c1bdcc891430ea72c6abbf154ff4b19051871388c189927f496e0704c7b256e4931fd86f3cc99e4a6bd59de24dd70aaf78b69c3b1b4090d3ba1574d746c30261311f1b294abc2abe4e4a5cb5af1f5326b38d589dfa2c44d42e6b960a8e170b359e3db4a30d270ca4c61c3c5ec5fa7250c349c945d9d10f35f991b867186d3c64b4d796242e425d101c30cc75496d74eae71c65a829e86e18051862d97c746cd189b3494d63869b38961fa92030c321ce35e2c67b43171ac1ac369358634d9d69772ff3490f13970d4dd486c78c003cbc6b50bea0f409c820aa2622f579296d38f3f1c2bc923bf72fcc3cf67217cf8e1183b949c58924c7d21f7e124297917d1a5e6c349871c2d4998bf2e710ffadee54dd1b27f7a38ab6a4ad157f904bdec471e0e223a2bbb45ce1e7ce0e118834a6295f4f4ebffe30e6715d1694a2a4b3b615a207cd8e1144a26d5a4be44fb8a7dd4e194c9d3ee67641264d60bc2071d4ed962da132785cdcef4c71c8e5e272fbb3567840f39f071a2a9e9e695bcdab382b6d65e13e4de861f71b0180587d36ba90d8b23fb78c3d1d3d47b7d849d20946e38e9930493fc46491b0e9ba44da222f36c38695026b85826d1fcf51ace224dc5f484b3379151c3299f246a122da9a808b1051f693899f60d42f484f6bb24349cfef2c9af98dfca323ec3316826f1131a2d663848ab8ecd5fede625e8a30ca669ae0a964ad3b4ada48deaca07190e328810df27e68f311c35ac6fccde202dbffc218683d457d3cc76b9330f83c5b804c3d904db1eada27fe124f466c9f687178e6ea6c404eb0a27fba90b073d7b5e61a6529baa3fb8704c31e35a4ac7d7e4928f2d9c62496e4ad84a62503f7f68e10e8b15dce562d0ced8e5e1e0230b27293447beabc9a6ce040bc7efcaa16de76f42eb57389e92644997fe46ab3f2b9c35c5047542ee3546598593bcb07262bbf74185c368123406933e97335bc3c7144e4ad37aceba4ef49748e1746d92a59d3545e1743195ae3cf985c2c933dc06fbdba4d7d28f271c84922449dc985e0c1f4e38c898a4a4aaa81343ef2a7c34e1181773c4985a0cbba50f26e4c712f6071f4a386919bdda96622dbeb3c047124ec9a46f09e2e652aea075e003090793fceae40425bc9f78dfa0310319364266ec08b9c1114cb2c15243c6bbb2a9b55df69758a6ff0c1d5fe058239c35a7b926b9ff4b8ade8c3519f8818f229ce428e174976c2705798268c8d8114203878e321d4c84e3c69790a6458f369d3f86705c17ef754b267e08e17c72c656d355927c72e923082725e9cb70b5bc64823e8080122e854d7d9ea224c2c70f0e9b32496295f651f6f3171f3e3849b3b7aa4b772f8e9a66f4d586b36ee0c18bf3283bed3771a476571ebb0882872e30462e1017874a4a6f52fab25dce78dcc2d024acf5dfa9dfa0331eb6b018b5b018478b93c75f9c8bfeaaab9b199bb33044be32e533ca55b5bbc5503a73c4a9cb431607258edca8788232c1e4c3589c74e5c6bccc26581c64895362ff6cce6432f78a937029ebe6c4902a67523678b8e294643651474fac4b52da8a34633ed1fc79b0e2185b1a2b8c652ef058857ad2c8b62062831eaa4853996546b1d02315c7d12074e6fc85eb3931a838df683b41da788ac3baa8f72c25f4f47da6389698bbba5e64ee4a713cbbb698a2aa4656c991e2d41b2ec79fba68f7f26e14077dd972b9ec668d5772278ad39f98aa67e8192f2d14a75493ae366fdecaffdd81e2649bb12d5a8506b3f13e71106ec265dc147aa554779e3849e23325f1ebbb63fabb4e1ce424ad7e72ae058dff71e26c82a78bf092cb6f43779b386950971637efa18953d093448b964abe5159c9c4292ea3c5093ac4c3c4c162ae92b1fb7497384957c2f867bfffdf7d9638b856b86bd829e9b9c1abc42996c5c9a725d6fbec78943887da8a6abf9a2a29cb794ce29c266ca7fd326cdcb0214347885a5d093c2471b88b1f4a526e2978ad1789534c7a275c839a89f51d240e4a491d3afcccf4db8bf7886f6b941cf51a94e788638ea814175f83759daeb847235095974fc50b31ea63c4d9a41c7a7e6b6ab4c92fc2121db2aed3d3c4f1157112dcb43e84acfc6692127130f5253208a5f716c543c45994f07aef25e610550ca6d172c865caa19943c958e14ade919b214eb25f3eb18fb15349ac10079384c96e505af98207210e17946c2bf2821293707b0c0269328aaf5fefd9050f411c6d4f5968953fd92499409cae3293e68d8e7faa1e8038aeff99f6dad259929cb3e0f187539f9874d60966a6645bfc705e112a06515277bb8face0d187534cf2e8d3795149c5e9c187838ca1524e634c3df6703ad9b812269378e8e12476ca6cc9115b265e6478e4e194fe827f8a2e8d5f490f3c9cc71dce9b4fb45f2d5f34b876388f3a9c64cf897b17378e3e4b03c7070f3a1c5352d1f3820c7bcce194641dd127a27ae37df490c3493dfe84cd0a190f78c4e124a7d7c26748cf0c5f060d64a6c3030ea74b3ad612114d5c260e13164346e41287b9186dc454bc1b53472c71f8ac502a6497384193229538451dfb3069c1ed4d2294884ce22053dbd6871a452461314a1089838a68aa9598311148588c47588c1ca80411471cb4726494dda6110719d3ac9af8907e23150025228c38a6cfedcd6ac6d034892ce230a3ff36c92b1b4afc238ac033cc8565d3168d150b5a1b4eda6e5d791049c43955e3ab4997f2ebed88388cf0cbd8279b5bb658c944e4102779dd67f304552bf61ae214e47987eabf184b2b91429cacc7df35af28a1e28910c70cc22df9eb6d89fb06718a316dc4e56efd4a0be2b0493cb1224ca574b21509c4499624572c650b4a8e13409ca27505132fa3d549d2c81f8e9d229732890c722ffbe13033ca6437e1a4c6fe46a40f67cbe921a39b341f4ea152542a49532589ece17c924ce14c4f87799bf470d228a6e234c8913c9c2e09e5fb3d7aefc7040f2731f14d34c952913b609aadb343b5b22c5c56ed70ca5825ed472f7538062dc14c859acb39a1c33147358a92c5391c4e9850797262440e270bd17dd1d3a3c4a823c6e15c323a33ac058763aca85d9b2925ca2411df70d05c52963fe9c45d5311dd70dc5ccddb4d6a99826d386ca86a8acd777a4e8cb0e124e442eac92a665df22944d6708abf92b4a5927693d40ed570bcb85757e2dbedc64fc3419558ada8ab092177349c6a6c84bae7cfe799ce700c26dac450316d866312d3dd2da7455f539132588c926470f38790ddae203206319c20120630e017225e38896749ccea49bb60342d99cdd4326b19aedd2c7e5e5e4a594f63225c387ea9f5896bca4db20b26912d9ca48b4109d26aa3c7a55a3866dd2c13adeef4c61366e1544ae725a5e726d48d8553579013735ebe4a51bcc229e82df3add60011b1c2318692e315b382e99a316260a40aa7de9326f68a3ed14b898510a1c2512c3545ef5245d58d91291cac42ecbb645e911091c249f7f5a9369d7da6718c44e12409424f29ad29d65e1781c2e992b8ed3379476485651421f28493ffc9d8705192573263c44083224e38c7a9dfb5f355156f310891269c7b556ef36610baefa1469870bc185bb77c336cf64b0a91259ce45a3b49a6d3d223f58b28e1185432b1e26b92144b393442240907b3f46f49457b9369d6420409074d57f2759eec8cb53541e408cc488b79be971911239cfd4a90d39ab1e5e50822454026d9d2b3a462ea678408c6552c77b335a5191c22433826b9bdf4fb6b1b1f82af814660be8808e18b48102e0284f37e48bb0dea2fd6c617f9c149b86c628b78932bc652c407b76c98930f4d954bac5b5eb73f7a7150c2050bb55bb7126378715c51416e58261d77e21e3e767196b7a83f96f75d2d881fba38c9ec33a52f9f204e9df8918be36f2ebb58d9dd60273642d08c94a6820f5c9cf7c49b2fd33ee14c4a7ddc02992d2955fcec69e0a811a2c38ce0c31607a1f74b9b0c26d8ade573e038147cd4e22cfa4a65c92ce1a20cb78164cc483b500d5dc1072d8e17b7b4649fec85533f6671781f8ba1f46978c6211fb2b8e294757b5d95626131ca072cce23329bb8e4b64156828f577cb8023f5aa1770613ed8c62950f569cba33b7ffbf8ab35ccc9c90dd93a7e4aa38be897e2ae6afe46b522a4e92e978493a6afd320815c76ff9926b7f7286864f71eeb5b1b63d298f5c6c8a8365e94bea2449259131a538988dd2215e4b9b4a0d290eba24a9ba2eedbc85cb28ce16e236b687298a936f58e8b812e227ba501cc36d7a5ef6cc2364509ca498f54a189d2bdfd4270e327c5d8a8f2ccfdb3d71d0e3633a279e96a9e6a31347dfff4c0bdf95e4783f38614c566aaee1d5434df674ba5d1f9b38a5e6536be2946dfdc28c3413e793653426a1946dc935260e9a76222ead0453499738baf89b2e3953d6d792071f9638a978a31575468ec7ef361e051f9538881227aae6af786ab7267c50e2182757d23bbbaa82e56312673d2b59ae94f887240ee2465f6f36e5cb4c23713a619e6bf716426dea03121f8f38a879893e31e89d3271c4b1ed94f78fd2d7ad498d38ee58bcb6da3393c266c449760b564264aead4adf224e27444fca7935677aa488d3b9cace2825f5918893dc6341683b2949310621e22093e558cb54f626080f71909d2f57c24f269d9e218ea937a6429c828a2f374a9c10a7375b73eb12eb4cc643f818c4c9f794aaf8a3b913c4f1bd2edf9218a3862a05e254f289320a11c092409904f0ea3c4a4909021e150337863920edd0f1458e020400072d01050440478e4f5ff88d1c381250011d2138900202b005000400000000c000244081056400003a74740c020800ed401ea30001403b90dfc811e30000184003127003870c1a24b443870c190b004000187080c3975882d5c95f688e433871ca617ea3626d219b385fbc2ef9b45fd20e436000a289834aca2e64de352b00c9c479cc4c883275a25c7e1e72031937727c0eca3a709838983079d352de845ce21cda26fa26f1379b128658e26031af63971fe7bd8754e2a05f7776654f438f8a1267178d5df162ea4a00328993a0b4ee775645838e326ed028434924a8be74ce580b991189c39b1c2773ce6a534e67acdda0711ed8c01790389af86c17a13b32678f38f708bd21f4b454b8e68b1a218e388eaa9ebc1d4233d668ccd0216306364270b800a411279f9defd3277dc86d860e1934500a99c10d1a662324460c1046e0a8b1889358a344cd0bd76b2ae274d6e29b24cd24b3a966acb10d42fc8bdc61232411c724b26d945252de782522929517664eeb3bc461af74ba86958b973733d6b6c6d730c431768cde147c76cf52e36b2848213064870d429c10090019c4e955a4da262554dea09463860e1a82389d1cedec0da154e56fc61a6b200e2fba474f0aa3d484cc8c3543e38b1a0888b7c18d1a35bec60140fe80213bfc468d2f64c84800881f708023f50165c84000081f1a25c5a42ee9a533d69c7d7156e373c8b04188cbf090f43874b0edb011823a6c84ecc8b1207bd8f13a6a7c0d0380e8014376d098a103c9909100903c1c93b28ca25577a5064a108207a84102040f684a4d6f8b555163062dff5af1cb972461a3207738e5e93841083fd92284300062877389da77429650d7d6c8005287d3f5a9bc951b42367c1b884609de6f8cc0bcdfb09108428753c62097b2bb994d5c41e6908aa8866acc56592b650a0e10399c54de4c73668bba246b6880c4e16473d9cbd449226a35d681e3868c3210389c4a53b0ffb98a19179037e86e5dc9e233a55c3963447449d5511604e286b3c811956bb60569c3416d4c5946530a990477c61a0d74e80310369c049917b62e49a36432d26c80ace168a6fcbc444bbd248b1a8e2996df2add9a90a1a403d1b0349cc44a9a2fbf60d725050736081a0e16cac752c916f97ecf584b6f030639c3c93c2d99b4564a52da790e128098e124e48e0a3aaec15ffa15a001a40c674d3129952e9dc9797a1bc8d04186d39f4a1d624b775a6d3903640c074d8bdfefea0688188e49ba2caa7e441a021286c3959d24c99d8d392f08738080e174adaaf94f8ad476807ce198c48fff26bddfd831821831cc40bc5040baa0205c308666b08aaa9a29a5b8dd368e71d4481c00d9c2c9fd3537ac47338068e12c2236a9b2586aacccc241acea4cc6909939c3c2d9db339b55953c04902b20881510a40a074285f3c927b65ed53c8552ac2de62da7a6c568991d8048e11894863341a31eb0e1811cbfc3033f831c2f02bc1cbf23e40520513859dc7658e5be94c3af0008144e42a3daecd5a5a5993ce1142f7576359d9f986c271c3e4f2bc72f9e702a37e17872bd64baa076ba3b261ccbe44fcacb6d092741c925eb7e089db1b9196ba70510251c4dc8a815266ac94926130049c2498987d8ff2db16f924090700a9a4c8ca7736d172e1fb223c7e3c801c8110e6ea3b26165fadd82ff1c8811a3868cff04c408c7d896ad7498e4f88447e002193a0029c25947fb57500b9d257950002182dafb23b24af211a61a204328bb9b56fa295100220490202ca66519642ccd459750d9a54e2b4613198000e130a652e3f34f69b6fc0707516729d357ff4365101f1cc44c2beb89b6a05e8af4e2acf35b4163366da62a115e182b5959556ab1768d9a4a6813756ebbc44a12d9c5a7b97354183525c67591a6940f954f7c4f915c1c7575356aa8888bc359ed499964ea16e7b43649a78b2c95e4d41607d54a61ba527c4b76d5e2ac9a4f5027ba49df8268714c79cb5e4f2df99b91599ce3efe4b7d87b268b83924d44cddc26794e38128b76dfc3cac4adf0c1a296b72c3296b9b2585967ce9a553a5d9716465e710a3256f8a6b799aba8191322ae38a6aeb74d82106219cc569c4d5a1295c48f93eec6244458716a8bd992cc264ce3b6c82a8e9535cebea464e965a9e2fc9a4e9b5cb5793536426ae00522a93889a17194ce32296bc4196bfd850eeb40041525a57adb9de264a527294d722999521e8288298e5da19b232765de126e20528ac365d2fdd3b8cd585b0d44487192f6c3d751401019852996d4a6ca1222a28884e240a1914f64c413e71211a797d29c1de944a9f1daaeb1269e56d539fabafdf6196b344029881143c70d229c3856c9ba8b7e258f327f13c756f3912629f90801871ea833d75196a3288ac12808622086d2b5747313502088441c0b0603f238cf75f103134001ca22c17020108c83a180401c8a610cc4301002310cc2200803518851b0836a3dd9adf0a04fc5096d9ee3b92ca713a4ab03cad2658f18bc9f56a00a7b9b08e52d2f40002e5dacc10a3d5b8be140edf780822a76605c439525de350163c0259c5175384c1468453863f464b6973fcb2a464fe4c8baa0184e4f10e86681975b552e0a154eae782dfcd1eaa476926329a78be5a80efc6798b71b401868448348c9565b10cd8c885ea5daf45b059df4d1457c8adfa498d05f60d427e38669a5cb664e12a3efe2342edcfa533e0c54e52e22e91c67755a01de834de37abb108ad4df4e284437f5a2bda27c8ae4816277b638ed4811a844eccfb0e5b82ba58c504c030d07d156d6de20285d92dbf26cbb0ce8a77defbefb94e959100effaa8a486a41795af3aa22159933bc7cf0699451082da286ea666b526c0168ec7b28322319a80e7b4ac8ad62bb957ce02f45139584d023299a5de7ac38bf329c1ca2a4c895cb0dae543e23f94e9623dce88234bce1a7dd938e28513ce3089a9b82a8590893e1f2ec161858b3858e7d300bbfa5970826db00dc56e93072c11d722c32fdddda96154f24c95b720cc1ad2df1d121cbc0ec2a2ec34d156af1dc3ea4f234c52fefc8d4788115310ba1f1980d5d7b2cf03b46256049167b812ac061b98845c771834eaa586b9fb597f828101c322dd9626acb68229f03cd18d1abefddab2b719fe48de722b281cbd3182dc556d7288a48d23ff9730c04a0339b47fb7749ed3f9306760f78119a2016c6927ae4a5f0b3f65cd4d555ea31a445210699c48855bea200e12d54b7d64915ba91810739af10323d348bbdc0d0a2ce11c07fcb6f604b135234cdacdaad20e916ff1abba0223e845530c1b705d11fefe0334c5df6340873eeb2de735801db7f9292438e574e206135a8248f6ad987acae279299f500b449ce537f4334e4ce3e6443fabe9bb40c62ff6ea9ec5120423244ea55800055e90f003860493bedcbcf3550a33a5d08be9a5f8f213c836c72497fac052a218d578b2fa0972097b5ec6408dc846c5d087d17565492ab031d33e359fe90d0cfe990253a8f6e11f0150d229cf1162ca739e814e2289ef09ec35814a028855fc640a18d08f166807e690e6d3ee499d73640668f9627c286bfc37ef8fb601fe8f438324360dd917530e225340beb729f1157fc83ca6891bb1e560353cf76926ed4f21c26f8812057c31d69e6b197450f32ac3e3f0c7fcab37e90860fca061e4ecb3738bd2ca7ea2e8b3a03d0e6919d716185618a7e5d53ddf4fa6bb36bf1b62db23a2918583dacce5b10ae3315c1d8ec4538f42bc1286aef02bf30e86f7db846d11b203dea22f1c6d998ccca4a50125aa89e390b6066b19929d7e8a1824fc3cf3ea6a8aba515bf83babeebd30d423b351d0c7e7e136d1104d4a789b4fbb9ea1e025686ca8d170db7c88a9e2d104fd0205fe76052dffd1adc0456b32869b2116cbcef56e5ac70b899d78b46e07661830df978949ca2ffa21b8f09517c1c4ff3ad9d9067021003527ac9eafbbce05eb2344a7ae3599b60c9424bf55a6ca86791f07f48fe51ae2a16ffc2c46e7f99b8ba90811609aae6eb91fa8d5e35f8dc65613524c95487caf3bc76c602d798ac66469ee8a6e50312e7cd7da07014053507dc481678acad02906015e1c08e4aa2e1f70c9c180a520374df7d11cf9c55a7cebdc9257f7e2222dbf779a920a416296466e238e611fa6709d7b308ff53f31072abff7d25debe6075ca8d8c7958425f189e3d65aa9eae3c0598bd5decc3a6199b0f9baed3962e49c538ceb40d75892e1c01e17c2d1eb5c6826dfc69db31285a73c4f60ae5e5ce48b0aca4c69ff514913364507a218a08506509a783dd30c730e3898162de29de54f003e565728c7dfb6a2bccdb969b50e40a63df1ea432aa4d6b27629c45bbfe7a9a1346f1dc95ef79925488d33b2802059022fa73b6c89f0c66c8d3f3621466bf30817f08e118808cd2bb416e411e81159092ca664d73b6c3cb30b2232c1e0e27160c6938c13376ffae3b176f145b3cef8ecd286ed103b3d69fcff6babdd4e950adbde081ab76cb225123ee132db502c528be0d598d1f06b29bc2a586229e1414cfd16471b58dc4982b3dd06704f039c50e31b65109996812391f6d6a19604e9a9fb5cdad960817504e89172eb185fde1fd2701cb1b65fa516b2ac089bf04e49728fa80db3bf6eea6be249c2ac7eae1eb100d57877694b58c4a60e83cdcc2ac102da7b854797e7ffa99a504ca2d0f4429896907832ac9f5b8ce33818a872a5db8033fa9ee95a5a365ba88b97f7ac924daa667c7f05f4521716b764d0b30901cad65814c71e16f47ace9601bfbe347afdf3cfff3cfca189d2d1d94f2f548b8d6859169e4eb4519a802a76df6424aa35489b85af852339cb9c0a1e3f1eef25384c0f9851ebcb70336cdae72d7fd19cd840c517745803b137a76811408db68cd60a7327738337f688f11e0b6fdd9f43ff93af534e16853aa84944c3025de49184eac7de611ed99894f9cb210990bfc506dc0b4575804bbeaf4e84bc30ca8ecdc2f6f7b561c3aedd6928d835deed93989ef522c59da791653eb1762196f5d0c170beb9b0342703213229bead81dee2af8b9738078c57746c9eee77f34188c7b42e69782b9222730976ce1b4e2b2a1672e4cbb8429a80faf696c43a092835ca6f7da068b604a5caeaef1aed66e0064cd1076cfcd6da7d70b4819570430ec895f0c3150096d2b92351eb5915881c7109ae3ca15230849c4f303ea6cf374093d3042bf175c97d5bb6108069277c71012136e8e110952bc4cb11479f6db03efd664270e5c0d0fd81aabbb92626c0fdef8754c22f23f8c81c4b4ad9f32beafbc6c44e04ee261b06cc233ccac037d317134a9a120d4b66d418cd4202761e2a1d06b69d220b4ab28c506f8b093ed18e142bf15a213f99e97b3a5de0097df185c452dd950920b41d6307966aacf3b81309b7b9889741446970560cc93b2488b4201369d0de54cdaafb2fe4209530aecb7dc694f746e0548c7245f90e4f6d26901aeda0405a3ba4c6631d45e5e30891d7b4950ff53660849b72c8382988702bb8eabb5386f22ccf0e881f6ecc813c57bb484950e8c371705e513a7ea76a83631fe5f8c5042427f18ddd1059b065c8b1cec60232680acfcb362485d0622cc184d617d030a76e20e91b7e29927d42a0b9889a7193720554aa6e95a086e2f213b07b153a366f121c005fa65813ad9103a454f0d6d098ce062a8f531caa09d3741ba6b9eef7be90b513e8b186008fbcffb3e06fe02023a27516b309942b261766dc050ba2fc117845d4597b61f9af9986d88d38dda76739df3dcf071af816c5ea3bbcee3a526baf412a8483c5ec28eaadb0eb0c5f81cbfdc01ccc48218aabca942fa0d846fa40410b621ce57393c726dbc7df1b68d3f02f2e84db0648149a98a3a7009244d128714cc11fa050efdec20d85de634e6aafe50e7da8ea13cee23b5f649f170e771b83c8214372bda04d137a2577ab86ed4703aa1e32ba412a744d4f0d1c734a1a2b2b12a71d33dfdc10d37da8dce8818fc7491522284ca9298cbe4203d2c31a07ab999c29180ca1b43d96819f7febbb11bb2d7007868efbf9156cc90149f9c53b445cae68f464097f879b0a49f958c2c8425a5678a11206bd8cf82b7f1c0760c0f913be6460efb8244d6310335197d25e06addd317e6840b5f0f9c89700a02f9be20eda04b140fc06e6c71c5d587bed34e0007764d6e58507df0cab801090c2f578fbf9e58447ce3d2513038cfa3cfaf4d022acb74339a98a7ee0227ca70382e52bf571913f544a1d65d3b99dc702864fe18bf198e6a6870c86018e83e0786ae0fc060da19687f48196fff14d7dbb5ab405379cd2e33d45bc1adad4ee7e05b0f75c624fc9bb93b8c4c085049edc743b079e6f2c8dcecc9362da0313d03c5d7b09f7ba652ed2152ec47cbaa1b1655d5e278db121a5d476ebc3a2a9bc40fd709a456a92c191f5c654249c7a871a1f9fe2cb86457c8d3d7a9bb2cd88097cddf271df9b0c130014daa081aa448a4313ce171aa08a2e8935aff93d3686c5f849d08c9d6567cf55c87eaafc3c27dbd26012f9812e70706aab5441fc98e300e1994a87e0194c6be02baaabe8ad4d459d42e8908e92bb3c9c066c56ac2e3bc8595a79f8817708c8c7f1750ec30beb4bb8a8bf3dc672de35669aa0068f12a295b6a1ad2a12fbf1a9ac2c540139c3d4e8c248566a26df81281c0a375a50bd3d3fd165fa0ef5330392ea767f012babec33a193a413d35842c93462dadb8a9186381a11c81f7010a0487182e72b16e5f6a31883434063179c3729a1b2b2de6f995f9d52d34fc1a5e12fefb0095643ef7b6afdfe08fbd0a34adbc13666b1326b0a160bd98363b68941af17068b82900a327377fad601548050ffa0eb7e49c504aeff0693b8f42c23be0d5bb42ce60e6dadab5eb6a4bc6a1f2f218c758da431a64efb7e10fecb075f2873d37c715818c58a6312bac9327ecf3ce4413382bb113ece60dbd6959b41b2e60b1e8825451a3618d31eb69819b47fa363782ea65a2d88337380cd0f319841e27cce2dd6d2b6acf1542ce71e31f03e88ed0b60c149efa65dd8abb7a08c5a39ba3e169e1217be28a5c335b0f751b3664b46394b1f0074d62598104929ebaf49296d31b10f2e157ecef2e6bf4c0fdc802c9f566d8e1aee6ae90141e6eba97d6b01022f64aad4dd12c70b056fc715653d341e8895ab25523bdd78ab8c308a921ef85b873abbb9d74d6430069bddd2fbd9a142b16a0c92350d8cfb90fe6f89c10baad417583a1dc9b89965f18da64f5411156a39996b7d8ace5b3aa653643f7429c82cef0eb67a739c7429ad236f0d8f1948f0726700204aa1f1945176ca255d61f42979585dc0708dc686da5c98682e217050a691626e486c115b348a8a83308ebaf5901e2df0e20833c94123bf46c394939f725e37b33d5083f0a0de23f8d64f113b0eee6494a565e80b8eeb1dd421b86f4088b839b3a6a73dfb4a50d92dc69a9fb4f70c5f3fd9ab008f29b27c92d05e957ece98e1fac9b7e741dd74438d4afc91c392d764e83b41d006ad3dbfbf2239b304b0004fd016b6d697e93467793aa50a183e12b6d479da9962bcdbd384a3286d63fbf0e7dedc987ae5ac0ba3bfd6416dd52112c84065cfa69af2af0e2e87764488509064ea447561560cc9941c377612758fd73a5c712b511192ab160cfdaf0e4c6b9a12f3d3001f2a49cc39070b64fa5b054c3b8fe3d5d6c4ae1d00b75337721ff29d473cde5554e236223be6ca0a74a74234b8a295002b1f4696fe5a407455507f0da285cbac2b116dad360840c26a72f90b74b578857edd7f3e5aeca175ccbc75594cb492ad411204a4e3a7402fe43dc7ec8f301b4b01ac4a07a0ee72e39a07129a1c1aac0ce099d09bae58453f1d7985b81c0f26ddb92d53b53040ee42801b1e90ab682ac48b3461ba39c1191aaa13c7d943acd8907ab0c8aa90815a88b7df700fda8d78b3ac0022aee1b1530e9e167c38ba2aa3521749a769bf26cf0600d54c21adcea764261945c3da63ccf8c23095b29e196642419ef35f5267e3859987dc545d36780d7e74e86e72b1546f91b7c7a7453ebf5c5e52ce45d01668d14d0369e16d5bfeed590ff0f7d74106b7a7e9a053f3b4007370867105d493e3c6d013b270361420a33e930a12b0809e67fa767ce8827574b87f0fe685e22c25db8ebdf2e77cbc4302e26bd2f12ce144d43db9533673f1b63fc69a010875518eba17cfab71f97ffefda2a1b735b49546604a69dd4e9433ae043cb192333875b93cdc5510f9b1a37d2db2eb866ea06fa758e40bfeaf131f089a2b939d791da382a83a898e7a1e478cf61cf8b5959237fece6028ed22522f05c01b879d2648220a3361b4d55e6dd614d8488434710d547d29a6906409ee201b81cfaa247dda0e0385a8c51506511a17849f2e3907a8c8a673cf4cbd0238dd0dce7d23e2e6d198e90e296c91ad42770e44680dbc83c6741c5251051a9a327398cb20f026012f16cf0c5c8aab836e2d802a579926d86db8a6be794ea74543cfc066ded5930dd12f54ba08ea03826508bef4fb3bf5de3c9a8ed56d02c5c41be18c5d063c8e2277fc7bfb9a9c7cac131c5f68e87ca9c07f1a8428e7530417929876d28c71d870fa132d8496321f9b64d9af23d4b45d8666e7e7079c29e701c9b3c1fe64bc012d746534f6df17bb7a4417f25301044a6d4a25652edd1448724577fbe0eb86882745a19dce7187faec033538271ed12f3cd38d299afd045576a61e7bc60d5bb4404eb1bd178a7f33f9fbeb28137f657a471e866c9c55217f87df5135dcf388012e20971a1f32a4ec3d2e4a3032a63b6ba1c311226c5821c4f731900cab8a773b3e6fd405b555b1f7380f7472f7899f574349282453e8d5a058c9abe1f7f9f4806bb69cfa162be42cc72776c94a685ffc51b54d5848c68e06bb0c59b22495e10b745520a25bb8285e0345e6f0e2447da03e1c118089a4f42927210d6187c11252a36a7228bb81a2530584e9d3dc5d14e458e2268470c16671f85b55bc7b69c48196d88466122b46f53a79aa6b87fb30822cbc5255ef46e5f33370fa791a155b4b944de26fd2253d830b1ad8d81c21a2e0cc3f42e948f369812d2426389556e6e24066d12e1c570a36cee02d5917320195c05ce4d32c5558aa328e16d761f2a973e5bcd0ed1f6444891d9d9e3cc3d5aa56e7b0f1e745864e672505aeb3eceb07ca70d49bc20990a90dc8481d58e382050b29962b59e63e4687b0cde1d4e27535c6ff9ca35cbc92e36b22f6e103515be5225e4b9503b4d8de333d5bc0745b7833d7fab2f5868965059015fa9fe53a60be6e954e6f6937de30f8a8d191c835d9db40a5ce57d8daa1924bc64e3281f3d8344a0343495b96dddde983d0ad179aa188a5c98c9d6fdf022d659fe839ebdf84c30ffeaf40f359181263b7b5f96f22f4088bb6e3ed69ec1bd01bfca2f831e053e08ec9f8bca05b52d595b013a390b79274f9394d28b3bae67576453b0ccd3e166f44d863f7cc01c5e4cf0a03143df54dd3bc85af2249b6462640765aca4d46d05614017ba9bf0e7f880198048174ab5b1fbd190484b018a4148bc2b2d601d4140742bf55e3d4b2b604c0162eeefdd3c0969d9621ae20b607ab11d71481e33df9bc6491b4e4c52b5ec2dc4733915ce77b28cbd16ef030caf6713731f91fe046001e65426cffee98124d1267a80c10bb7091c846b7687b50311f5074290e0469ddddc5da774a4543a0b0d790b9a1ec84e608aaeffe28d7ea7c24a4f487468a6d08bf8775215150eb021a9a04174d468929e1673dfbf13c75a8efa5cf442f23fa36b5cae02", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x0802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x4dcb50595177a3177648411a42aca0f54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x0802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcd5c1f6df63bc97f4a8ce37f14a50ca74e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3274dc1bb854565c3b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b": "0xb25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3d6e4cff6e22a77dc02f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38": "0x02f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195089a0705a664955c36175726180b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b": "0xb25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d6611bd6b7ff46a3617572618002f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38": "0x02f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x0802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x0802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a3802f40601439cb3765ef8e2a0a5770a78fdda8ea3675f0d4262ceac46fe9b8a38b25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4bb25ca9a71a8570a05814e75eee9eab0757d2c98e91b24c1fa2e3eb75f7b26d4b", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/cumulus/parachains/chain-specs/people-rococo.json b/cumulus/parachains/chain-specs/people-rococo.json new file mode 100644 index 0000000000000000000000000000000000000000..b2819157152174cd803e55cc7ede8fd38a626e08 --- /dev/null +++ b/cumulus/parachains/chain-specs/people-rococo.json @@ -0,0 +1,82 @@ +{ + "name": "Rococo People", + "id": "people-rococo", + "chainType": "Live", + "bootNodes": [ + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", + "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", + "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", + "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG", + "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "ROC" + }, + "relay_chain": "rococo", + "para_id": 1004, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xec030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x103a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6414a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x50cd2d03000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000829e74677a0a0600", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da906d8c927c500fb243f4fa0e582580063d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94aa395db903df66bca3cae2ae6fc520890cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da985e234ae3ae91b863f593daffa88b7d73a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c641": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9db37657b6aa638db66fa3f62d0b342374a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x419c3470656f706c652d726f636f636f", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd00589c94050e1587ca155110706893746882cf922889735072198891f28771aa672811c53dc6b561ec80209cefd7e1c08c10358c03293a2bbc096774384133ba88b782d67cef477a2f69e3fbfdf8b9b14f5a2364134208d97bcb2d031418d5147b14daa4f0bb892a10ecea3ed537da5546e2ab31869ae8997dff36222f4c6b75f747372bd00bd3f3f2bbfdb03debae0ed86146b731fa3eb43fe42ddaf1f93d8cc25ed5c43ce47962f47fad3f41f3ea3ff84fd85c7401c03f21c57bcd77f8429e276cea09c7e1cd9fe0f1f9fd05799e00f2848edb7c5f03799ea0a9279a8f46b789f9889ee6279a7a8af1719b9f68ae7df427603d5dd7b97ae26ec19f603d5d558af7d3f59cc77cfb9e833c4f64f5949df9d3a69eeaad1ec7e16cdde627be4d3d4979ae9eb2733fd97cfe89f89b7a9a99398e7a82c7ea093b9d3fd1cf7a82f0a779d26538ee5a3d49f9ac9e603ded78760e9efb493b574f52e627799ddff013f71bea6966e639ea29c72deb39f53493f31c568edff093f51bea290783393f613eaa1425997a92798e1c27c9e4c851df23cb9eb323cb6686d11c36343190b318cb68e03573c811951a5f363123bea8c5412eb3724ca9d1d8d05c741423330c8393b372c4681c0da6d9c4587c7136a2320794396ce0854300b8c9233a9f43cc0d190e38c8d84c88e9e8fcc4d7d1c16868ea4bdcf592168000fc943d0001a096357abd8e965d47964a3fd19770285da31cdceb036013f3003c879f689e838ece4ff33a37e83c879f3089030e93bebe2473bcc438e08043007eb21e807ac2018718f81c6eb861c74fda7760afd7c9f98e1e34395ce7279beb944a00f80df5547a8f1baef353f61e3f71d5c76564d8e6f538ecd8f11e3939277d478feff829e63baee3701d7e1a5d870704e739fc347a0ecfc77be4701d7ea2798f9faeeb947070ea71748ef3133dcefb691ee7379048ef91534f3bea69c77b9c9473d24fda49ef27f91b5efa897be90100879face3504f00c0e1b59e00f09c9c977e82af3f61d7a19e021080fba827523d912e23739c7abab939a99e702e2373d24f394eaaa71bd26f7ed26acfeabcfb780e3d341cdf514f37f574f31dd7b4d79fb2d77ac2d1e3f5a75995589d771ccfe1a7ac46599df71cea2900558aa7337f433d65d967ea69e6f386cf9fb04ac4aca76c1ea79eb26bd8b59f68156275de717eca517f589df7523de190e300a8a75a4ff537d0fba8a71e7fada7fd821435941106066648c114136d42145530610d6370c1134d303d41df6353afd579a7f5f47a9abaabf3cea32ed4d9e7ecb0394e3d916a7e534f2820e8000c5dbccca0064b0082c906f58226a680822148410d5eb030d9fc14536b56e7dda69e6aea429d1d6a57eafd6954b9d579d7512fa8b3a7f909562456e79da69e7254299ee98998cfd4d3e832550a20a627e071d4538ce8a37a426d81e50d2c5bb4c14618c860824b05303a90010a5ed0c610d4607a027ef4135783accefba89e4475a18e006a17a4f727ab02599df7add6409dfd896b92d579d7ea6956294aa627aa67f5e4430fb68801076c3c018c0d4c5895e2a627788881861d84310528d60006d3137ceba7ab9656e7ddaaa7aa2ed4f9513b20bd3fc9fad5798f95833afb3d0a0ae9f08f30f3b9a067b8f46ab8f4b87841cf00c5c6d91faf2e971e7d44b6815e986d7ff0b5fdc187f44a77b1b9052dc46d0a8a54ef656f0a8a2abae31d78fe06b7de141459560c333e2e1760f40c97b6462ef177037a615ad26b7ff051aff7af096e534356625f2b449e8fad906dbea46bc5685c5a2e7535fdfe7a535b88d1335c7a8ff4f5d2f85e0a0a296b3033c32d101ec24308a10f888127ac747771c3ded413437ddee1471f9d0e672452aaf4a69c48a3bb51c3e7d09b72424bcfd1fa88bc83ea8e6fe4e7559291d8efb1dfa30c0d0d0d35c72e4ac7c70af4f3bedd59fdfea4bcd1c5940fe17b9f4f83af5e8ce207b4bcd369cd3c1aeb2c0f5cf5d7276e534e70d19b7262a83b1ecd2f406faa89329a379f9ae6d768bc739dcff14e75fe0c6f3e57557d3eedf57c5bf559d1a007e413b9145f84ebea6ffb61bbfa6e3ecba54eeb276b876a795def8117ed6277f141b0adefe623dfc373ee066b776af88b76efeb15ed964b51fa7ab75cea94e8eb5df5e55287445fef81905a15e8d4f015ddbe6887d3f1f36916ddaee8f66e476243498160c347ba5c7a3572e9dd7af0059325a455337dfd6817827e9ffb9845dd08fa41fabe5b11d92f36d36d143f247383b68080db54135d04807bdd95ba092bbac43b7c1d78e75921637f6cf7b0c63b40a0988b03b77f5faccb2e97deddaf8f55c0dc1dee7dbfd85cde79fb7a3f47cb3bdbfceeeaf96e8a58e1b23fb6e7db0dc6c3bfc3cde7d57d314556c0854bdb7c48974bf0356ffcdd625d2ef5307c1b10a8e61e7e6398dbcc7fcc7551ef1599fbf5b1b4e3de352010febdf718be871f846708217c878ff957732519e1c7f3ab914b7bae1d1fd69fbf24ccf5d1dd00dc5c1eb8e5127cdb90284343434c34e41df82e2ef17b18528d4bfc0270dd4cf3dfe3bbabf9b3663e6d8677f835bcf39a8fc33bd007be877b3852283cfa09eaeaf7ab5f2519798f7f8f35e835acf1d5ee317c8f8cb9bd3e3aaef9f3e29deea60716dc7ba73503e1de8fbcffc01a1fe5d4c6daae90f70e7eb9d4bd86df4f4877e0e089c07a4dd61e8dadad10270f4c03590d05c76842ed51d81b944afbd25034d10a71f2861a4801fdcedfed481c7a5b116ea8df35ded9e6ef56e40df57b0f2ff5c1f587ebed81e2c757987337a6c80a525cda5e2eed77f3895cdaf7f0e2f0dc20aebb5a9e9bd2f217ef6c8a89a196d778e7f1aca99b69f9395a1fdd5b7e7927bed37a7ff10efc7e79e77d1f7987bfe778677bbf5b117ef73a7eb722f1fc1e5e1fb2febcb707eabdc7c4be1b709be2c28c7e430d3f47fbf5d1bda1e64f9e06d2dd86e96ecfef814bb721ddfdb19f4f5bfa48c0fdecb7bb37d4dd9ecfbd292e76d0afdf7933f2861ad6ee0df57eee7684eb7848a1c48e15e88569f8a51daaf7bb42ba785291ac63051a35fc9ed140badb30ddedf93d70e936a4bb3ff693b5a58f08dcfbebdd01ecdf06f486df0d886b5871b8f4dec33ddcc33ddcc34b974bfbf5c1f5e70df5f640c1c767ccb704ee7db9b45f6d2e0ff7be5facbef152575c90f5a8a2c7097a90a07ea1aa51c350d1a856a85ca86b542fd42ad033d42f150cf50a950af507954bf5c1bbb8144a854a0545439d82ba51876816fa066d43658286a94d5427a80daa149a06c501bd02cd029d02258362a163d026d02fd40af40bd4049405540bb408d4085405740b140bff429140cba04bb81bb40b4d0265c2dfa04a7819681b34456b40d9a05e502ee816f406340b4a04da05ad42d9405140c1f02c50355c0b148d73b9128e84278156e1659c8bd3e05ebc8ddfe0611c07afc18f7034fc8b93e162bc890ffd8a4b41c5e011e6693813bff228e810fe043d021502e5428340c3f8948fe130a057502de816da03ea026a8502e1583c8b17815ea132a066a060502d740534069f01e5c1c778aa56a13ba825a82438985b515150537021502ff0e082070d785cc1430b1e31e021031e30e0b1021e2fe0e1021e2de03185c7143c4890b3861c30395cc8d9424e1a395f72c4c8c9420e1672b49073851c22e4f82027093967e42021a78c9c540e13728a90a3849c25e468c91942ce183947c8690269083950c87142ce14483020f920870aa420e4fc20270c521b486f90dc20b181e405921aa42ee03481c304ce10290cce133850e0a40007053827c031014e0970a8e09000670a1c2970a6e044414a03a70a1c2a4860207d01a70a690d243590da20a5818406d219486c90cc402a03890c2430a43190a640a2026909a42790a4407202c90ba909a42e2426909440d2423a024907a41d90c0200981c4039217a41b90b6905c407a018904a42948544852dcb8419a7213863474f306a904376d20357193869b366ed0f0b43c233c2e6ed8b831c3cd186ece7053841b23dc08e1268c1b2fb217642dd0b05013660605374f6c6edc9ce0a6043726d8a8d85210d9e09145544314c37685c60b599637868e2b363f8871628425d3c2060a12192428d8443103064f0173a3c6043951d0b1858e2f3aaca023063ad2d0b1051d5ad0c1051d59d0e1051d6a8c9630f2c1283512c24809a33146501899317ac28809a3268c9c30ea321ac2c808a32d233246618c8e30fac188cba88c111146481889316242146634246ac32808a3228c80308ac22809a22388b8888ca043063a66a0430b4e71182c040e026f6120f00fd807ac857bc03ce01d3018ac03ce017fc15918077c03f682bb601b700d2619241434686c2b88619051d0b860fbb29921f304cd15324dd0c060f3b29db1a181a30d38d8a06d414716d90d66664063050d1659173431a8a09811c24d173252e82043c6444c0968d46023036c0bf606d6068d16b034d8a00147193459c89a88990247981c3dc8b1831c3ac8c4b0b9603b017b99a181cd1633586c9ed848300ac30d0e388a9920cc6cd151850d135b165b0930356c5668a6c821635b834c16191cd40cd530217303111264bc90e9828605a319645e6c57d8a280230d3455c8309161d9ac80430d1a17465cd058a17941b6058e36322e36286c54d8a680e30d1928321bc84cc1d6b03db161b1b120e6099215dc0eb82a705c7053e0b07052e068a0bda1b581c301e705f705f704070527858b8273822301370507468c94c8829c34e4b491a3868d0d231d6c6f8c6e6033069b356cc260238691173ab0a0e30a231c7025e0a88cb28cbea85ba85a909192838648859a463d022d830d0968ba90314186035113747421a381cc104d0bb62a6435907102071a685640a3824d0bdb143124b019a299020e1fe478818e35e0d0425386182adb941c2e804ca03101a90ccd0b9a1ada19b42ff0140d8cb6065d8326861931a41548576c5298f9825281630dd742a392f3044a031c6ed033b636d42c3c06345e5030130b93cb34c23cc224639631cf9848985e6612a612463b986b4c30cc2fc8d820a78b8e36c426a61768a8a049c10c1066523adec8a9820e36e800b3a1807406290ada0d667eb059c1c30a1d63a8718294034d063253f0c04243836c06990eb630d90fb22b335bcc316ea86869cc2264596430c8b66440c0a48079e9c97cb0809b35645864312049c9b46432c87a905d91f18084841b36dca861448377041e5bc026c44441e262260b0684191e606cd860407386ad053339d8b48899b2bde0c689cd043466d8a8d0b0417262bb82d405e90bcc0ad819f10b16041b2fc8b06146073152c8b861d306e9079913326198e9415c419cc256850d14380b3651d0cec8b182cd1b36458881a2668d1b29510a38926033864c0e5e051d671079a155d8a8a1553113c68c16121068a8a0a94082a156d0356cd2709383192b345fd8b6b021c216031d6cccb420aba29e40471b5b151931e0c8810e346851d85440c550f3851c346cbe20b1f02b642ac8ac6850a821838d1b3ad4a05de161a852e84803c542e60d19307498613aa1660c332ed0a8b8f982ea808e41871b346b905e2213376bc87c21cf8831410d188ee54d381172b4e05d6e84991d50327c8b98064d15484990d1c165b049814307333ed864a0a30c39ac50187c08320cf522c120bf20d79057c831e3460c7208320c0986d4811c432e412641a6a40fa4126410a416c904c9451a41e64066915fc822c81fc82df2081208b20cd90349042904498614432241ee4076913cb8098394229d904d4826e2192219e21ad10b110c395d885f88698855a8a2885888568868c42bc433a297488258021e2a8855a209620a6215918a888248e5c60b71e8868b084574223e11a5dcd00072016e01a601bb00a5c06306375fb851032ee1260ba3c12b60273013d8083c83e70637819fc045e02d580bc6724304a6024be1c6064c8549c027e029b8043da660336e6af0de786d786a60286ec6b80103578189071e1a5e192f090f09afca3bc133c153c22bc1a3b2c423a3023f16783b58f2ba406204227042c4f3e2f9400243446008041e20c491208c34a0c80f0c50c08fd2ebf2b8e8a033000110200728d61bf3f5be111530864a9e14b101244a14a104092448e683473451820449f6e307cd19473059f2810d9068a2b01d58c281264928092a428913239e2c79a2a48912223e68a06004922438d0a4081326492c20c808254c9824b1801c68cc0042f304264c8a308209932216e00424823ad044099326ac12251c409244109220243ab04409087468baec13264b8a0852720412254a3880030d1336090e34598224044e947c0008c912253aa889120f84a008214eb38455624493249a202902c912255a89114d92704050930f24618411bd83460947246184922378d094b14f42a0e407c99327215062a449124b9e3c5112014a938405c11225da89921f274950e008a541c22251f201251f78c0104728e940078a1822071a32f6c98f114a903082cb3e096aa2c48912249a2c39cd119a2ce10089c6086bc492274d9638098aa20449089828a14411489c283982c9920f987e8c4882890987a608fbe486668c2d02090e0d11d603488a082ae2882282940411433304112c6152c492a0254d96e4a011e303462889a204074d6a9d24b1008d268c7da26449d01227492011f48122646884b04d9258125484930f1c81242809264f902c51a28b70f281ceb145b13f4624c164034894041571c41226454011ed53c26403450435f90007900419a1c4c992239a2c4182c4e6d70dac2f645831a31735373534d40f056d2aab0d561b9e12cf8a3796858375737383036f8c70374a5e399923f3ca95920773642963841132cb18a594124a18638c3a76dfca652919729472f73d2957cac72be59352c68d4f4ade5d694996cc72df4ab97261bc78e3b817dc6569edb2dce5985d90e38541eebae606573200628c71c6382bc81516a3acaeb791d96229e3c6c87059c6dd65f883848f25476ba5f59e9432b3a4dcdd2e165d2ca384f1b2e1c850eeca28e25d867b8e0c198310ce4964c6dd8da944cc577c2b12e910bd2136bba21811d4c16f2bc9bb558cbb722e5f1c39be1899a38c31eeae48324b695910e6cccccccc70dcc65d5e8e897004af27dfbc1662efb29677236bd7324b29e556717d6ce2eee2d8658e92011c63640030f3e5e392f28aa2b516f2087b7001912bc9bc8c2333c5e51899996fb86179e3b338b27c184bb9cb524a8e10c376e305e1b6c16d8390318cf9ba2e78c118e115d9b26294978452621bb63133cfb817c36841e6c8168c178c91637cfbde7bcc1b7919eec2f7f6e2bdae09b71df062182d8e7c45c83133847b71c4a494d7cce8b2208c912164f838760f2f33f6229423692312ed42cb5ac8d62e5b9079322ff3c6da2eb422bc2e78619c73c9c852328b7877151079489900044418a18c9161e427a58c50f2caf836eeca8d79076fd1873871640f0cf92d47b91c772533afc58f594a9642a494707b5262d8688461cc8c31bc2eecba609417c717993196d6942f5e923116b1e831f3e0c8cb5664c81b31de8d2b97374666b97cbd77805853535363ed3ec81784d785718c50fa88cc30322fc496e5c27dfbe432af5cb931323f8ebc1b24eae01819eecadd85fb645c86fb769723c3b8cc9057328c524ab9f0ba2e8617dce60531eeaaaa2ac68b594688415e69c1c8bcbb707763e47d1ce38571ce323327779939c2eb5a299937c68d303e2865b42cf82294303e296364e618218c9121f385491e19a3a4b9ae186394d2ba2cc9a39d1e06a019411a1e7ad0c0f79eb42c6b24a3949694b500920054d267c567bd917549193796922f19df8b52b225e536802bc22b4a68c5287765252384108311c2b8979498484a8e1c9999b96268c9c872772f29b9be51c34099e5103849c2034a3c50653b00255094304112a5080ff000b203e840124c9074a0c9920e742009263631c71145c9114d962069b2a408248ce0000792a83b1ca1c48992239c248164c7000450b2c44950000ac084c9921f2002c0e1474987520e1d72e0904300020024e608264b9030024910930f14c1012547e8f0b0b3234a122008cad93100012e438001202180929f22986c0049079a2c41e24911489c003d74828a70a2a489920e38b9415b022879f2448913253c908411489c28819284114a9c2029e2c91146284132809f1d0310400923943ce1c1e1c092030cc0000620c0114a902411a444089c18d1c34911414f70887962f32540124938b9f931a2880d306192849300948a50e2e426044a7e90044551d2248714a4a40336324f829e2c89a2a40a32a288274d8a701284246889073c904429c712600907943851a2e270528412264690628e68a24409258a40a2a40345384922e8c9db01782067952871c201244e92082ac2882498302982b40350e28125414d3ee0a406e140d007764069a204e2d801206992c492274e9440411245091248829a2c71a20412239e2cf10092a0284a70a03451024bda1678853221212121a10b85121242a1ae15121212da92a01e3612128a1085414eb2423109ea0909b1104c827a42284e22f4504242302659a12ac90aa15070a24ea8984448682659140a4b827a2821144c22f48484504242285495645142282121d41312e2248bca92a0de4bb242425712d45b2121142aa2ac24420ff536c9a22a944c827a9b04f550288649168542a1629245a13809eaa15e9245bd2445a215deee3e0f3c296203679deb3e98e7629dab2369cc33c33ad7650a0b1bddf1d0f2a415f25a1ea3385cba0e55d0c3bf0effa5aff3dfdf769d64912ceb9be2a24a6f8a0b2a7a864bd72bfa1aab385cba2c2af7c77eb7b83ff6f53e9464e8c57c3de7db7c328b6e979f930e4175a73db3bed6f71aedac432ea874dc1fdb10cadfa8758cbee65fd7377a1da3efaadd75519e7f135e136a1765adce4fa82dd5be1b914c7b36a37651fe6e44e6a6617f1b5014294d3031d4401d08d8f046987eb25db8a8628ade3f4135563b6d6a5d949e416cb58bd2f3da56ed721b92f5b3f7c0f5d1ea4f3624fb56a05377b0b39ef5e73abfbb2eec3db08751f34522cc0de2e29f70dc1d909658d2e8aea66510de91e78177725afad0f2dca594df5493d971bb3273e58c0dbe34a7d88831f51f84b8196dfdf2d625df56f5271cec2e485b58a874b75edaaae9e19deefac1ba751f7827678cb6bebcc3a3bb2d63b924dfc33893a965c673425bdc35bcb3a91974e1b77039de81b73ec33bf196758d77f8d6d787557f160cb76157948a5c1242ca134b58e1892e559e388215d2302dd479a29b90032aca50430d6910c3b45087bf1ca37129d652e97d21cfb24ec7f5b937b5851a1db58e938f7c206f8c865a77251f78a0a9d3fad16bad98aa6a5c9255eb4d6d8146c73ac3a5b7a9de1e28f9b88db9a4307033382be4355fdb628de6d7d4008c166b54c1452ec53ce19e1678e534bc91f9c0e01807bda92d593463d19bfa41135dea4df9200745829c00d31d6a4bd5dd07506084de8f1e181c1c426f6a0760fa8dd19b02a38bd6a137a50333de462453c297064ad27b2219982b1a4848d45d5016ac50834577415c6031b3d05d1009b220bb7b22851de4f0832edd3dd1d2a5f7d7d0501c1aea0dd3fbd773f45e564118535a9ed1ef7304f43a7e462a63eddedb8cc073ed86b0a5e390f8d792762892117946c7cb33ba7b82ea7879a00d13260ced50cd728b10da68dab4e519cd9447bf8dc89b1f29941caf06b191371f4e067a6124aca422eff044de610d9a2d86960ee296b5e343790d8f6ada90d2e6a0d97b947c9c8629e40d75b6428464bddfed61df0ef1a315b26ff054ef553d618cb13fe2b7d8db434f1843d81db663edb876420d85bb034e5ebf63331937356d53615869a05143b9aad7c336ecb8bb4a09ee7dbe2d098edf2e81c7789b0f501c1ada2f518b44a34a635f0f4f0d347acf2f7b3dc4a1de4f4b83db9045c3baaeebaaf8bae0bb2e5912bd576688b0ccded41656f40cce5cc87380293c80c289361ce18d364c474829821334b0820c6fd8c0b48f5c7a91dfc355edded5af9531914bb50b82fd8e7d8677a635ad2cab9d651d9ee39decf0d3a2afe7db88ece8c825ec5610ececf3f13e287adb936e977847870ea7dfe17178e7d5aeba3cfc84ef55741ba397028264bf99e628b4bb7aaf953171f3c12578ebe79255f99176af81debea241f06d3e5b65e5b8c4dacc1b8777def03aac10d8d047674fca609707c2addd1b6a18bf144aec7da445b8de43b8df33d8006f68095fc2bd77b1e338feb54515cdd76696f430e424e6f28e3c8d5d6a791f5d74ed4dc9600a19c8608b0cc0402dd2ae3a3f6e41c0738db0fe708f535a6ed422fde1c7cb2d0886cdcd70fd81e777872c65104577382d6ba6a53dcabd5b7c0f2f7cc7b5bcbcf67ad89687f5bd1eb8482c3280defdf10e6f3061fc8ed11c04183130a3f9f02b243bf6b801211f1fb7237cf8488174069fbd0c661acc0eb56bf06f1b921d4efa9e75f0ef950637235c97b85e55503cd3f525a078a6aafa12d5ad13717d898b88ebd6e3832c984399496e7769f897cd9faa4361dd8b2d8bd488039c7cf6eaf1a3c7fcd59857d489ebd52515ca4cf2bb42ac57552833551fad1053b04b499f09a35e0029a03b5467918536c97a5ea345b29ea2c36dc83b0eed1dfd6132af7d3b6bb4c9fcf61e888376a88e791f8dce51edbb01f1eaa87e80eb2e0e0dbd8bdec373f4b62cbe34ed4d5de9420caa681fbda92cc0c4400b9268dbacedd226254d1125cd4d6edb66647b1652bada8c644254d589ab9e50a6ac4261fd992496299656e77b5ad653256f61551592d58951f593d0f5aa4eb9a9fa949bb02a74d52937c90ac51405989e4099e4e5ad8a67fd91004a9e34a310779db828b292e6a5fc7b953465455d6928bf9b10b22ed4b9842abeac30370ab7292a5f9a533348f5f6a5f5c03bcc3b0c471722c2c44d60bbd04ccdf69a1a9b6fd5f69a8d6a9facc5877d3fa3d476f246c4690913690836711658e2e6da97e07ef3b70d7173ad9e44e7249df36d16103dc7477409d1478fa127d163ced193e8dc77b380cc6ddec339e869741ed7e849740dd5f3d9276b92bee668ccb79f4636f5c47de34197389db673af67130ebac4e9a4ad69444fa73fe7317409fa25fe25727e125a02e7389ee3a79a9f687e9a39574fa221448ff96e447097a9279469f41ebea1a7671a55492d20928fa94b708fb9824bdbf4a6aef8d273371feb9236c95adea245b296585069f877851b96b69b056c1e738d6e17d1256c2ebab5594026a69e44cff15d21163869e7f193e838bedb1032dfeac9e69a88ae09075d42bbcc79d025646ef31c748993769b5bb443b5dc88d02e5397d82e73b90d6173d16be869bb4c155a42c7797c7b452d6073515d42fbe8733722b4ea84ccb58faa50668af946853213b72b44d60e48639922ba56853293767e0c1d9da3db333dda1179bf1eb70970f5a47dab5088ee45557f98c85baf1ef35d21b37640da7a56bb773e7c0f3791af9e13328fa9a76722d5256a3dddfc14731ec7e8758d3a21731ed7414f35d7712ccac0c28a9eaf2ac195de2554a94a70a5abcb8e7d8596de26f43b82eb6a6209aa500183051a58ac81851b57a85cb1e24a179a2629e9aa46b0aefe3a6b4b8dde342c35ba3a6ceedd8a6c1a0890928a59f062a8ab5b5df514699292e42f4a92b54857c7faeadd8aec70c610867a2b820029a9ae965045741ef5f44c275a79703f9d470e578570be7d4715c2f1c23af71c4df47b0c9d7253cc4974ca4dda475468f41cbfa942da73c454a1d147df2e434f28934d854254bd005240ff74fb76e83655c550bfcb6f3f530d9d72d3e93a39313a4db94948fb7699eba0279a2aa469cff193108ee7a8536e127da64eb949a60ae1a8536ed22a1431d509059862ae7dab4e8cba4d51d146bf1a14fb1ddb8e6c13fa550f704be84e62c9d24cb64b1f75a52f4dbbe8f10b791e6f6d13107d740ef23831baa84e89a95e544799e2ab2aeb8f55258032716c123d0a71d7e90d0d71513635345449f2a2437193e8d53b25baaa2429fa7b97a421cfa82eb15d7bfcf57a78f5739b008f3026d176ed701b42bba89e44d7449b26a4037531d78314b4d1f059956a99a5408d86cf52e0652e065ed4aa6b8fdb11f8ec71fbe1035cbf4783b8b63e33de863c203d80de2dbbec50fdaecbed48f6eef5cf7b7665fcd765e743b9f90075db11f857e5e6333fabb72c027a61de76ecdbb98b7c986429a53cc49e814a9bf4f5dc8d48769b6f242aac347c46daa898d2f097a65dbbb09b21db26d2dedb311a91725e8423e5ecd85e966ddbb16d7487486e3b8eed108d6448a41d6c06898423924e73d288d3de9be101dbe04122d12d0692b6ebf8f6b9515217bc43bafce4647e6e9e1d48cb6c436e9e5d6e46e0b34f1c1ab3b60367db9c14d172ae712f32fa752d47bbc6d50e68b4d11d1548a8497fb4d334fa1a87447a0fefa0db9a0fe910876e6b2d27e992b4bde6dba3151df27ebd07dad0eef5cdf61b6a6343b76b745069854bf23414b2c125798c6e9476a78ea1dda971d0ee24a2dd09b601db6879698577d88c96876cf00e76f9596919dd9e33aba422f03f8fb7bedb0f5517bcb35d7e6edb76defea8880a2ea8d04205172abc50a1c6ce6cca4dd7feecf5f44c6bb97d4e6ddbdec353b3b08b8e5df46a0bef882e3f45229128cb469ca639299265f3d7b32cfb55bba9d15127adc8e836d7239168fbfb565f175c92ff69166509a1bfbed6a19f524ad7f4ae8cd49b4a85d19335fabca83ea5f6de7b38db8eece6b3a9125c699d7eb5dac22579de82c03aab1d083801a76ff3d12e14a5df4fdfa1b8e9f40b9d6450c51a4326fa21880618aa1832bd4a81e2a6ffaa5d9caf805efffa6e0f3e8a3da3f09376511afb6e41941a7b0f6719859db4a2e5ab2ddc854bf2b2d35abe2c2de5b9cbcb221f182db97f5bfe75f104fc326e43d0ff701be23fd5bfd257a9de1e421045621cc0d0d534dc53d9d2f05123826a2b281ed6ae48777202e57a5583e2ab77b13ba1965d14201e7d7db723923703ac4958a15cd5a2f2bb7f578078c82b5cda540c1a9ee136a464050e560050c50d557c50e931a536fc93421bfe0dbde14f3c1a3e4a4ec307ed687825701a9e09a9e191b869f8236c1a3e494dc3634047c31341d3f043e4687821661a1e089986ff0147c33320a6e17bb886f761d4f041440dcfc3d6f040b486d761367c296b781f58c3ff6a781cabe16baa869f910dcfc586d760c35f43b861251979cd7fcd8f9beaf7855b91f7fb94afe8ca571dac3a3ebcdc53b819294191fdbe9b112c4ca3ac775d29e2a637959ad238417c74a806efe0e0e0e0409c1c9c4fa8e1e0bc87392e2727e7f3691ccdc9396f3e39232a1a511198ec4ac7df7c7fbd1eacd7e589bee3f3045f1afe8676dca4ef0a01d2a3dff4b882231d48778ff41ede153280e6cf4cbbe97105571d4877dcd7b3cd08e91df7f69a3f771b7280defd71a1f815ed86a0baa2a87e94f411ed6ece9fd97684bbfa752255bd2aaa1fbd68f7febe7d04d7b82a49e53900db1c205f69dae623fa886e73747b5e6ec81ddfb163c70eb983c779ecf88e1d3c76ecc0b9e83822d17c5adcd0ee35e930e7a2e7888034ce3bd2f9d536049ef41ae7a21a9b1d22b8c60e914824a23b58544946747cc7757c47c73b6c6e64a668ee9055443b76cc1d3be8d4767c472519a9f97ccde7270e75121be737a4990a8cd1770be275954d9b8f6ef3d1e788cee3d0ab679643f41d4f502db2bcbcde21a2db9bcf34f2ba7a0fe3d0ed778c446fde036fe8b60db5cce0527c0dadb270295e0795546877ea8c76a7ee2a304e4bf3d169ead50697e247f55de152cc419370b03bcb8be5a5e3af366668128ebbabc0a8c0e878cb0cde9154646812ee7527ab747c958577468f9f500dde8989c9b26f4670e84c0ddec91e273373c9ce9b4f761e8377de398ee3be5b0f739f9b010c42ad7b5a3477b8f9707f743b866ebfed884e7395c7e052fcc4b0873d625ffa89eaead11e9e9f8b812cfd63a84a1bcdd39baa924af5ce1414acd1385dace81dbd2914b8d129e0d238bda91474d11387667dee9cda6e57ed4e7dfdd18eeb774aa7dc44b547eb895e28332d1be0ba3ea746e99a1e8dafae877d6a1a86fd20674549efdba82619b95e9dc805d7e8f8f7ead5eed4efa87e9bcfa341dcefd67644677ef4f9519554b8341f3fadcd080e3db74fd6de56e4fdfadb8a547fdb5f55974baf06716bef2e37361fd1e76ea3da09f5e815edaeaefee8469d14d97ebd088e6f5fdeb96ae7a4c8f59817e17e7dab5d751c75b9b47d7927a67640afb75fb55b2e55e74654ab404215dde8b68802bd9ef31bddd6e6fc66a4d4b3928c64c79e55a80697e2b1df74971b1d9f59c13b587d576e2ec055bfc0052ec0bdfeb1be1d6fdd24e17eae6f77700de67293847b8f577d16fd00d7415c5715cb949b4e0cc61525643164128a6a88e18221989608c10965f2f1288098a00062f2e25d52e11db806efbcb6a2b73b08a6994bc773193da01a3d980b27a047f7b4e8f8c745e531ead38213501f7fd3e30aee59e9edeeeaf8c83ba4ea58a6dc44bfa4aa0ec1d1737302e8e31fad793bbc4b7ab33abccbad484cc3c76d887a7ab80d717ade84e85139a8f3fe36216abda0cefb6e42d0ba50e73d8ba81d2c8036de15dec10e6b0f2cb877a5e3f77ac7fe2af647afecf540b89dcda765546a908ab2c7d7be9b4f56b1705c55bfae67d9c5db915267ef19d55e51abaa9e5159abec53fbb53d3ba4d7353adf03aff988ed27dddee4f722dabd206e969f3b577b743b464946e0b1432c03d47af15b3bf9f957b17087547eeea6d5eed45ac6d12937715f4e00574f42a32f1b20fb69d900f0dc9152f77046f78fce6b94cfd1358da817dae7a35bed62bf6bf4caa8dc86547f8734fba4d77b60763dbe076274fba2db4c83b85fcbf3e6235fd12e88e57b58f6b8827b577afb5de9ed21c4288dab296690a5c43b1878d2051838da937a532600834be046ef90d59b2ac197de4dc52ebfd100c7bda91270e93d37d93ad32e5a74ca4d966659f5645d688733863064b2ac6da02b03a59e4f7b57ba206e68048786c772bd8799eee623c368a34b0d4f1dcff4a2536ebab46bca4dd5abdac5045cf5745dc8fab20138bad1f19335a6fb8bae69b723b0f9b102c13061c284e90ed5f0165d3640b7a92ca67410f7157844a7617d5776f3e10a6409314a722586b91a0adecc2d82db14952e5af6a648d0a53766c2af8f6ebb347f423a451bb176a88e87b47b900b03e182f0ce1ee76d819b0f57c33b5da9f91d1020bcc3cd0fc23c1f1a1a1a32f17b98e726fe0fbc03c40a813dc2e1c068fe0cefbc26ed15ba24bddb9045a3f7136a918ba8492ba4566d3e552cadf28179d6440229d59372b987683f5a215b777fc0330bb80e480f0751a3f940d6683e0fbcf35efbd0db45a651c68bf5aa1004d3011c9746f35180dbad01a88eb1bec7001f2c5c0012b865f2a2563b6f40a0fa672b9050ef3bfedbfa84304519bda929ba88530c358cf36d40a07ab9d4c546755527dc7c246b953c8d445e5717ef9086bcae5ed5ed77495faf8f587f38d6623ae0e7d11e59bcb743e65240ebd873c4153eca8a0f4f1ac28787d59ffc0fac7ef10e77a5750ff730eabdc7652e05b8399a028d0804ead58b4bf1f17304049050c77d1deb06e63284109e1f2384112a991a9deba303d2f0b37657bff7febe97c28b0fc7dde13494820b8e0fdee974d08177e2e1cf3b5bc32fef6cdd951af22cebc4182fdef9e15f5c7a7f5fe689ac03bf8fb5bb7a61cc7269dfc55eded99414577a1f79d6b4df8d87f0635cde61204cd112c51a51a4f182f084c03d28dc7301f75e0b381de65b0127ea4d45c1a5df57c445c7573c3bc6d090693fd4f3fb398abc537def44efe7ae10d1e7446917d1dd1f5aed26103791593beef9778d7642adbd16754f847a3e3efbae10eca2fd21aadd3cfc768ceefed86aa71d6e3f0a64850913a60d1dafd10e35e4757c0f4fba9dd19f58a1bcae5e84bb3adc8cc473ed50cddf9e7f146e43aa8b3e29a948bf8d88f6dd1fef3db087350af4c2f47511256d44ac63b57b8d3da3ddebecb3a2ddebeadbe376245e7b0fcfc500b7a928b4747cf5b993b5dd886c9fb58b3d2143b95a74517d5b91d8d5e7dc4d543b548bae5128576b9fb47b3dbf2b44ab9d7626427af7c7fc7c0f9c147ebe9bcf9b223e2220f481953c3289e012ac2ac5127311c532e5a6984fcdfa1c554f98e284357a424d5e8956402b5cc56b59ab343c116f4a7d54b804b3bf4bf94ebb8497d7e1de928f91a36f7fa2cb43da030beeaa41b0378bf6c082db9a550b3ef6567faeba59efb46ac9aeb62257fd8197bd55be0eb723b2617d535c663d3c645eb5d3baaa381af3aa074ea8551ac7d568743b86e2a8ce511cdf6d88465fe3a8ac18ca7da353ebeb1c8dac737dd2b70db17e7dbe7d0c459a4637d28b75de334a676a280e97dea91ea2a2ac40b063845408a236ded4805d72c6187fbaeaf042b1b26a876a2bc64f869648f2851dfb9cda2d0bb3300bb330ec1afdc9accdb2b06bd635ac87b72cbbc56dd7de8dbe61f21dc7cc980dab5d9068636e66ef46d6fc76d1563b6d62585dab7695ec37e24a0a4e40a303d09b724216275ce98782de5413a8f41c5d24feb6f5fd555511ea94657f53aa67f122fad3619717ea8a2e564558855b6256173be6576ced4df176a846cf44a24c24cade0333da3d2bf467fbf6a31decec5c96551f651f55a20a0439da09f5cffbc67df40ec7b9ebf1cb3c33f2b10b82dc67e8b20e57ab981ac48d5de762b82a23aadd135487a30671675574ee31b51b89aaec5368cba5b719999dd50ebbaafc0c9764157a288e2d1b7419aa2acbc9b48b962e55bab8d104e84d75e1c28c3a6df2dbdf948d37c940b037b90d41b5f5d9dd56f8aadaf65bb56dd5b655db7eabba67e527c2aede6755c1eab0ca566f7a64c175dbf7bbc96ddbe4ac9b76f98e45dfaa9faa766f321cbd3a57896a10f691568360cfdaa1325aaf196d638b8b25b0b184327a8e223f889b095d340ebd292630014c17a037c5842d3d77468b8837c55ad890fe009ddafaf672fcb36af776dfbdbafc6af3b17cacefb6af8979579dbbf9ebcb3cdcadc7d0651d6bbbc6a5ec229d4e7e54b7aa55350876f54e9e7fd11e5870b30671579548557f2ac6eaf58bcf56fd79affe5ec9aa716976e42a62a983d18b5ca215f9b998965dd3a29537a5e5107f795cbbb76d446e9e3df20edcaeb2cd8f56b4cd87379ff82450e6dd63de95e6466f5e4393e3328ff99cdb21edb8469d32983d5691c1d185fa2dce7bcc381b7747b6da718f9e533b7eb48b569adf5fed82e2a87bf58dfe986b57a14e420fc2a84577d5837ede859aa1557d83dfea72295ad15d95bde36cafac6759eda295ae1ec4cd994c9641092594fc0765329977fca5baaeecbb3c59b6d5bcbb68de7139a08e1cf1a1659d2b69c7ab6707027992f0b8cdaf835e44fa4c09c78bfcc52ab18ad7031f6ecc563afb0d1d6d4446e7fac3bfb968943d09d4c9797735e9cb3ca4d3fad7f3a8ddc5f03c32b8c1d1b5ecf24020677c1eb5e32f221d905fdd2672b9a1bcc3a24870493e08c78f6315446ef83854fba3d5fc0ddd26d1ee373556e152751e205047dea676574ded2e1db5e3686ac7896a5793a3763533b5bb4cedde386a57ea98da9538ceba36eebdf7acfa733d3b17576055903b56a3b95623be76963daa3fdbad67dacbb22c08eb5aadd7c5715d0dcfae0eff71df6af782645fe72a097b75eb40204fbd7c120975baf572c3bbd777353dde713e7e437d55a08e7c771fd7d5a32edc2fb52ed4e96abadb2f9dbdfa4d8cbd5df48ebfb455a5ecedd5b5ad76af8af5edf27105d947d7acda05c5cebe74f52a35355c6fabd35dbdd52e4876f6eab14a5c41b492d124477049fea2d2aa71056f87eae18821095dfabd29248c51469506d29b4ac2974602189d436f8a4b186cb4d6f0d796256c6903f4a69620a5956046977a534ae8a2959046cf7ea7d2b1b2646e511c9f9af6e51ded5cccafdabd6ed677d16624eb6bdb8888def6bcdefba3c23b9bcf8b3472e9fadc7ce69c938a3ee755b9eb71bec7ebf127bb505fcbd7f574fcc137c5eb21fecdebdd553beeeca3da7133957ebfae1af9ef5db1fef0b35fef6477af76bf68c7f565a14e42b35ba2df67503f2bdd59a73fd785fac5ad086c8d88f9aa7417c440b0bbd7c3713b426341d1bbecb1768f4a5be77f8aeabbac0671cf99e778acd727edb806718f82c89e8e1a5f25e9b855e4fa3b72fd9d08de899f7f535e0fefb37635b72e1a55517cb71eafd8d55428123db86d44aeffc4dabdeb7a87734b469f0ea641b8bf1dba37a5af5b1b11d1e72dfa4e43b5e7a0319fa1dc47d67ba088082ee190a1a4ecf3dba3e3c0f14653bc1eba177927fefdd5095bc36ebdb37ed3230b2e885bab3fd8afcf77dae79b4f86c6aaf1dfdf16048e8beaa3188e4aca6e9df6c0820b82fdde05c116ddbaf69b1e587041dc23ed1c5d2e718fa1914bdc75eb1b5d2e8d6ab768b4f56a23f2fe23aadbe57644ebf76af704d5d7b76b278277b6b5bf295e0fdbdae3e6a351691a2397aa67f4e252758c2e9782705d2c32fbaa6f8ab78355c93c1c3fe00206172db874b1974b0baade14172ac4e00206a9347a3f7b5362a400eb4d892145dc8510c275e2853179b110ee8fc77c1d9a5fb35407de4162efa3f9cba5b9b9470d9b80a8802184105ef0d137c46d8a0b2dacd033a929f40c9760d5622ee12684bb4007e27a2b0e973809624879449000cac45f02fe0958a3508089cf5588df5f01f62bda01c4884377efdc0141c94ba8570b5f7a5dd248bb53c74bf80518d35587760955e23bac210efddd2c8043bf27f0f09860141f2325555f29532ed335e532595fda090dc143a6eb18852288c9221579575ad6aa0a617d7b08ce745555051ab5ac58aeef190de43db02f6ad1853addd592b90acf15914e89a6784887e04c119e54246b5881468d257ecf58a8c3fcb07801844158010318e6ed70058f157307fdfa0d35acdd1b82df85df331e0ce68b82db27b8c7a5b730873d53a0d7385c7a3f3dd46305bc9ebc90c77ac755565da803bf50275aaf0ecf970fc902d8ab0f71ddba2eeb10d8ab7af2421eab873ad0c910d5af7ae2a00e7c3c04d6540375640533d39aac775cc33555744d9176a5a60d04328cc17c1d3b2659c3c773ef27ac5dbcac58aac78dc83696293779010f372358c7da09d11062cdaf6b87ea24d0bb4256c83643e62a7298404f5e63f5e71dbb0ec0e88e9dbcfd211f7435d086d19e659ff271575d1ea3fbb91888d77430466b13931a33bcf6b719d18e7d46ed6d44ac4f8c6a8f9b91ebefafeefe781d57ec93768c41b91a08b661a8f716ed821eaa7afc85b94570f1ca96965f8c711fa5244854ba4677f2bdf7aab7affabe0755bdaf7df08e74b20faa8c20697ec5dfc7755787573bf87ee05f8c552e8d88242f8d963970a3abc30369b95c5ae6804b77405a9684208277ba1a207264778b46cb97607c5b0350614022f0ef7df3819f23e8eaa7259610ba84b0217cb858edf83f57ede0f75d55557589e66f9f7798e15f558dbcf3e571b899541637baa2b239ded9eaf28d36b4a1770bb33aec2bfac2f46e43abc3be872bca434cf43b4330971a73f4edfb991ade99f751e29deab3a2a8cee8f68ba27ace991d5ea3a4ebd8b7353a3e43a3e3b1cfc5407c38e03695654b579f59bae8f8edf3d22a1a3f27ea55b49387c7302ae7d3e47c6b7334ff4332625ddeb2449691cbb244d6bc3eb1ecdb5856875cc78efdc86355fb856958566d47e2b3fa23dfc53f417575b91d89c7b4ff5447e2adda55d72af69d9856d111850cc5ee009e49cae850ec0ea0fa33f5408d6615e8043f1ad1356574bb4301412358fdd91e7d7ac480dbdecd43de8cc4cf57b5db3e2b69defa5649b35ed9adcb3272591756bbeaf3f327fbacddebaebaf62c6574fca49d7612f625617588fcfcacda3cf659777f54a01706fb67edb26b74beabd7d7ebc843e743eff71713bd17010922f450196b52cce59d9d4420f348c92ec50bf889d14d314fd6a086d1fdb20111b723704d3ce572c306a04ed0d711200bc6013d7d1d6e4678d4d6e793a24ea80affd0d3d77923d2a481848a68cda32dde1a80c2a2af21a82b0d94e489449dd095ae7675e8aebf06a0c2a8fe6e5d5a76d5eead69625a46d784d135f156845fd5ac13ea1b50e9eaccd133dc8ec4c3da5d0de172a9aa1ddc8e4420d83ff072dbaa1fa8e9a09aaeeaee0ff8f3fe1e29c9481782e673c5d590176c78a1451719b37f55704b636f9a080a8ededdddddddddfdfa38b9b8c0a123effea8ac57fb6c8558f5b2fe6ed5f7ac9ffc429e35f1b93a92f53e3b326bb63fd8b2e81b6afeee0f66265a560e078ec598af0aee7dce7d3ae022670337fa7d7dcc486bcb371f6f406867faaed11f26c27f7f735f0db84d7541a5b3f35f185c3a6bed7342ed6d44ae63ff99cfb293b6ddce947bb3b4b8fb637479a6a28f68c7efb8217cd1103ef71ec8519211bee87c51a5242359f3b3e67ab33f205b67c8b32678a6a38baced48d6da6f5608bcdc8e90f607bc566fde50ef46e40d6189790f739027a6c251edaeaefec3cfdef11708541859d6c333f2792bc2cf7e44fb037eb43fe050c7fd019b70718889deb0b9d5d1777f60dbe8228c76b0e745f341558b36ea4434d2dec323eda24d3b46bbd7538759b16767babd2b63acca71a9aab5659d11b1f582f9aab0c119ba0a4ef6a66c0066f7c70dcc10c27df5c9ad5d945ea66fe8d5136cdbb6699fdfb66ddbbe3e369632e79c73ce39bf3ee61b188661d7ad631886615f1f181a2ccbb22ccbb2c6407963fa9abfdbfe901797e49ffcd237c4c596f2db0af1e115a2c6c456880f5c213ccc0d0167599f2378082184102e84d5b55488db940dbab47cac2a4aa4b2ce5b8c4623d1b76b9f1f8d46a3111a73ceecd8af5b9f73cee984b9bbbbbb6fd9d2f687f54785b8eab2bbdab2aead10bef56d856c5b676ac1e503995064033d36100785bbe3de65d6664a58d54e5bce848674575339af44d6b336c93aa18ee78ecfe4a1a1656d92f163edb25f59673648b5ecec336a5c7556974b5dec4ac22d835a2665e5b743c5d899f2dba1fa84af62fea66cc04636d55cbb1e6dad18f94df5a66c30d42b46738d5caa2aecb21adea21d13ada5943e117bf204b388f51823b5e22decd879f3a92e28afad48ad4fe6ef8ffd09e27a75acc62b46dac53fecf37aefc55fefbdf72bf2aff7de2fc6f8e28bffb8a27144499129293ed21f7aacbfc9da8b0cf4c23809d2942892a4af6b6fd1eefa8c87587502b46086863a08475f873048ebeb8812197502a480e6fd111f5475865127d9832a23481a3b0f2005f4db1f3162755787f86d887c5523bd2efa34fe7312eb753d8b0d283fe1e7db247d639826fc9ca8be5ec7bf20856c1d72053aed21dd863402c1671d08c7fabcb8747db7219665d5e5d25521ede0a170cd240e31d1dd7520cb0d26c234940bfb89e7cf5bf5a25a775dd57a771de892d20413ed044ad5ef50ac7e826aeb3dd0a2d52fbabdb4abb20af4dce818c41d4f2af2523efc8a3eebd59942e1ebdcf21750079f456fed1ffd79d5a290afdabdefad1781dd3d41f590ed587f60fdd9f3fedd0c7367fa0d7bbf3b5c02f2c65c82e3ad065efa3fe6fbfae8b825b815c26898a322d7af5fd87eb12ff6ecbb34a31c7641797d5daf7e3eadba96bd9ed98134f6f7aecf4cab2a23d7671d617fdba38283af589d17edae63cf6a87eaec1d7b0fe43933ecbafa1da356b53af0655c1d7848ec8751c8ab03bf6b002a8c7eab03bf01a8307a57073e962d465f0a38588463ba6dd11f5d511c2eed453bf9778b925e057a5d559c3186863a87aec298b20a09b81f791cba93dbdb4a568813208d658f5a2efc7db756550a143f4cd5a7c898a8f0ce4cc733697920ddcd74b75b78c09aa4547f53b8ee51d942c79ff61cd43983153dc0a20759be0c0d99aabf590d451b9840064dc42008262950fc30bd4f9131eda540c126eb5312607aafe89404984e5e0cf17e1202eac4bf2e272f86b05ed5530fd489b7eac98b21aabf7a3ad489af2a90853aa9d47bb75d160d519081250c53a801169cd04306dc9a502a282308575c2186971c98c1b4a67d0764880f0d991e15dec1a16315e698c57c53705087660ae98a64f1310a35fcd342c7ae8ab76a93aa76d599c02fc0b4acd53b58ab43ba84137425df0975f70ee599a0ac68abc92ea14acb7722e8caaa4d368d965513044849b5ac5db810c20e670ca176516ac0a5065d3496d35e6814264c182b4c1896a11d25d8a28a2b4e6eb882032e43414a6c1a8d656887338630040501525251867609554cfb4e042d9bd8344c581dda344cfbeed4b276ef7077e836c5451a1df41acb108d15be70c20b4343a6ada7bd50932bb29002136e0c0d99ae3a24a58b7f9d12fb48e1799bc00e67881166023158b0c20cdae0449830a699f662caed22c41044f08eb56874fc7c3e4a3a00e9f89245bbdf8745379e88b743fc105c8a174208c8f3ed8264bbdc082ea6db2f1ddfbd21435187ba2c1124414289286f489a614ac9f1cef5fd578855245e7e4a3ae57723221f2f9f0aaacfc73ba3b69bcfca188e4befd8abdaa15ace5049bb47a1bceea0bc8ef193f72d4791cc7edee1e10cea382b4a2ab269f49ca4b95b114b8d9e75df0e1db7acaeeaf142d1be1de461a44cb48eef611570ef355cdacf7069ab57f5a73afcf636c73b407a8f5120fd6ca67a767d37a0ab71f607c7a5bd45bf3fb66e8d5c3b8d8b6d98f2eba3bbc040c175572ac53bd6e3af317e71e19debf1dd5546c74f2db2f40dbd292da4e809b5909276808bf0d7af31521710f257bdb83c015c29c87355619df878a536c0c95fa9ab0a97e29570b2928af0d050c30b0d0df54fed723494d8a58a917d238c9695893ce9876d795d76505bf17ad88ed7e603cf3da1f644d42cda5d553a5e555e0fdcf157154d74fc65857760c7cf07f9411153121fcb140598b0bf96f4b545497c92fca6a86c21c91a813561df94164cf4a6b418ea22b0e561cb2af48515221f7f75813cf3f19717c8f34cf1d717c8237afca5c6ebe17afcac4e920741190d8224b48892e49f894d9392647dc075289e69fecf24a910da2fda8140095d3df684fc164651876a9210d949d663c3263d603bc9aa426cd5890dd306f6d8dcaf7b18b6469d78a6594955051a85692c537830617762d4061314cf845dca353434945527ac3061c270ef56c4fbc36810de2f30bd3d842a9464eb0af37d3ed47b0698fbd7048c91cabf6749591dde9252ce8ae2bc2c2253211f7d805e988eaf2a4e8cf475647ec27cbf56c8ab5a1ca167b8047fad10f8bdf2e6fee0ba5cbae07b5fc0fd94f0c1c3ebb03aac0e1fe4aae0617508e1250e972abe224aba32a6faab4e8a7fcd4d8a30a82ead8ac3a5aac66b7feceb48d909931f57088fdee30a7915e88561b93f16c5ef3931573eae90ea7bb9421e0f5c92561a08ecb425340d8779b2cbfbb042be3faecbebc03b1c972e2c6eb4d1ebc58ce6617f94b80bd7f14024eaf472174ec208231401d91ffbf9aa856c49d10fe6e42bdcd668851b6d7831038c2cb00aa4b2861a5cc6d082afb88186172d60bc2a6098fce0635ee15e8d6fdf4a20ccc925be82db4f66b831f6abdda94608a168b1ccf5c157401849456403bd30dda9768c8c040e3efac0c30ccf9ae275e898c697ee826cca0544c418df93efc9c7cc4430cb17c5b01713ed5171c17c527628de6d4e438ef1a295aa621a57f076d8cb3fba3556892b783bbcc95636eeaf9557ccf5a1852e294b83a28ae2c0ced8b5f85825566d46b28e722312690d9798711e433920dafa888abe51eb5181d77807fe22e24de1cfcd87ebd5022eabf3d2e6a325734c862eebcce6faac8706579b8f3cc71515b1e788aff77706a7754c3408a57c7c9c022e763b689d0ffd2c23f587a9ac6fe243ffbcf3814014ccf5c13b7cb852e0ba1d9aef83772a8b639dbd6a8c40565f425d593e4eae5b75df0e9615ad0c4bdc4eeb079990eaabe7a8921a37c31673d5667040862e25d0820c30b0d084a9ab691e8318b2a8a089299a08b305137f8fd0ccbcb3958cf0d5ae8607dee17e0fb2a9e6ce08dd05a1c209aa60020ea2e8620d53c735db800c68b8410f9e30832a84316960085c5821055daa904508a6c58296e6c7223463c107cdbf200f2cc30cc268610853f842858937d53ddcc35cdf9481bb7a5358c8b2a58bdd31d11a9ea5b4a4b42af8285fd2f8c348b7e132ff3df927394af9a00fbfefdb7a2eedb330230b6960e1072d6910ec78585595ac58c658174e89f36990f2f9f151aeaece5cddea2a42a6914be7fa38042c5c6901f4a6be80e93922f2782ebaf1b13a22e1ad1775964b95e4a0eef64cb4e677a866c90f8c14c85470af5f10afdf15d8f8a2c5972aaec0451a793d93a3e635a4eb38cdcd8fe8dc45d73e45a3774e4b73b7e1e20a56fa5a1abec60a591a1ec747c3adc8f651ed9868d57684bb48aba4598be0a855f4d1b5986fb4c3f17d034897790fcf4a13d198efe680d724d1636a0f2c3851b50531cf5d44bba016bdda82d03e8a7977b528a69264ced58e7404c763be1dd491646a9118eedb413dbaa8c3f12ae672fbe135e91cedb8cfda030b8eab4cb48ef9e808e931f5877499fa63738e89d6dc47b41355265a93ea72a9e69acdf737b48b4d73b81979e7ce5b119b1ab95453afa32e97681eb944533bd9bdce91e3d973d04ea8b3cfb715195dfbd428ec2c888ebb96a44530be2d886e74ee1aec1cb55ba233ce82189daba2dac907c1cebac825eda3ba5c9aa933979f790f6774a676af334dcbb46797d77ed1a0d75976a682231979cdfd35f751fd999554e48d6a97f5fc972fad436fea0b183d591bd19f79ec93b72259739fc7303a392a7ac7ae30a5b1cf11ed827af4e51d1c97f9320fce473235266647d78e439775b4ed23ecd54514ca6bee712bc2cd1d3b56bb79eea2b71d59eeb97335de867055f4118db9463badb5c3adc8e831b5077294db129d2b32aa1a97b6b7a8feccbf4eab4cb49e15fb726dd1aea6b78798680de515c9d1ef36aca161c62e3c7d50211f9fdc8523e2837a318fcd67f3700302d573be770503bca38076236af1a58b56a27c948f227f9252a28c92694f7b4685c62a31466bfb61fbbd1e18a9f04e95102c0ff79888cb419e788575e4a316ce041ca3e18508e649b24296e7ad0ef7c5a4c450e1a2f00e362feb434073f9282bc40a2e78877f31c149d192e6dd4341cbee5d99ef9e161d7ba96fecd6e60364e43d6b894bdcadcf47b32fef9066cd6af7fa6d3e3458f5c125ee136ada630d8a767f70983614e55485d35ebd632fddb6c155adb2623848a5657dad587c7c480aef78b92ef6c25ed80b7b612f5d30f0c5e6653ca254d5e6433f5f1667b40275ac442e60897b77d18a976a42818bc7ba921abddcf210f1b2bfab5f0f63978606b77fdc7ce889783dcccb5b60856497bfc0ee58973ede5d3c37bce384c87c97c75a1dec32b728dc12f6497777c01e7934f61b7434761fa3c69e319177ccb4cf8c3bf65959578f1b7a5c56ccb22c8bb5c3b0ffcc7a8cbbe02c392aed41990b97e46fa05c06ebc84febaa825541854ab412ad5069f9688577acf3f8b87ccc12b5c454d4228211b7442e62172d2decf14dfa3a5a8956a215f14ac6447b87121b3bd0a8b15ab31de18e5528af1ff31e9ef993c23bd9e5df142a51204f1c823ab2b62104efbcdacd3feef213fba90faaf50dae872b8d532c1aa5605670496af14ab4c23af22c05777dc773b22abc638577722e2f2877d8bcbec175cb658848d06bee3f710a97e4835e4729fb64cb9fde1003b824e74ad13c6fe192bc0fca29d6915c34cd3bf6f2aa3c2a5c92cfe89b3254eb50cbf7401eb47bcd63d6285c92d87fd2711ee534c43bbffc93327a77e9d0f15d1ddfcd07e73d4c69b4225ee1927cdca2401e22a08efcc561f535ee78cebb688509ec5178e7466e476c8ebdbab9fce90dad906df96b3362730ccaebac76da716cf3a0db3994c3a1242a7f436d68c4e2e33a68c7d1d0ee55d1f23968a7cdd0365a5e863ab170d0181af49aab4070499e082845b5e37af3d2f23f401e1f553256bb77a5eeb8beb812975eecc283016086ea73695402f2e87875ed0b79683e6d74d0b73af03431bb3a1dd6908c2a5dba7b5a5256acb0d1d59f15bca38026e3046e6c19a32b6d79672f8e4bf2dc651e2dcadda30ad75525b854dda241d3a26fab43acbf3ff818a112417f58581e0eea54aff1c2c9d0951abafabb2e0b4219ba94833c40a04ef59a9aaa92b10295defe62acdea5cde070691f5dba7ab6d9adbf27a50e333232323215caeb99badb1069a5e17fac8b625c434a4bc47177ba5ab8aa458b96596aa845dbd24b4ded556920dd01e98e898f264189fd0e34eaf7ce1d1ed9fe2a94d7964883b4abc2a50a892378677bf559554c705d4913d1b7854bd581409e97629deaafa9e12a589d4dc06d7fef8b94aefedce09d57f76f8dc706efc457e72e78e709b1bcb3d5d8bdbbf7a5460b8eade8aa7ac7036685a974556d099ed78c2c56c6b85206176b46c3eadab605e1615ad66e3d9ae0aa60456f0a8d2f9d80ae3655852a4cdd5ba3abcf4b8396ecea7aa54657bf361f5831b04f46c85b3ba17e327f5f56c8767596b2bb3bfae3a15fc5000edabdc6f1b75d54bb6262be292a4ca9debd2f5ad0d80226c53bdd7619a30a17dee95e155dbd5202ea54af5e15bc8303c70c97e4398a25c747d4c9ab2ea241afb77a44152520cfb53af0d51b564f027972d4bdaa63e0a242c0575f1f55fde12c23ce61c37cdab9aea6b7e6cb3dcf244a91e5a9813af34d2849a127909eef160b3d2f30ddb2d1d38db5ac47a7d046ef1abda92950e9f9b62b46edd5c825eb536a16ed78f0b92a0f123e962b2b11a948d5d7635fb5136a4bf399df2b6626258536baf3a1e7a50f73679e013fbc06f2c4d579d76aa75d56ed66b8bdeaecc275353dcfa8de59f3fa16ab9fcbdd72f96185c09e078277e0e735ad1a3feb7233bc73bda136cce5b91900f3858c28ba4030cd9f184c5def6a4afd525d600af2bc3458872b987acd5ba3f9184c35c7008d96fdbe301438edddfb82bdb3b1dec590e063cbf705ea6c3edc1788d5ae86fbd2fcbe547f6aac90d7400f4cbcf5ee79a1b1c72fbc639dd5582146b0c7eb96c6250b82f1c1642ab74e4609aaf49241058ceede4c069529b3f73536fd62fa1da7f3d17b6d7976adf6b97ae0e21fc7b73d203fe4e1b891e1151ef6b2709b92821a8dfdd7e71bb269a3af77311dd6ef6f7990803ad7dfdfe7c31e76cb02d2b1797b401a09c8edc31ef677cbc222dc1e122b840aef6c5f7f56962c4f10a873fd7af7aaf4f5ac4891814b5fef560d7d6d1726ac24f488cd07ab1880311a97aecb2090e77a5990be38c81bc33ead77ab465fe723b142605f67c23bf00ba6af1216edd767ae9f77e0afeb4078c7faf50bf268f5fae260f5ea7cf4a5435f5f1f57fd7957aa37e6fef2f26490f2c2a5e70f98460ba8a337e5a504516eca8b1355f58685030c97a80a58944216c3656a4b5f16dca6ced072c60dfa4d4d5bfa56c069bda93362a0cd0cbbac4a46c86f3751c4d28547cdea3c9e9e0b4bf5a8f15183f31a9cfbe8f981015c52c2a80a89420bc6e08428822086a123f0208423aca1095b48c3175500c30b3830d8a00aa9790d4e0de4e981151955c4d8c20426c6d0010ab64b14ac5843ca165174e1054dc0e10b6df43840cf0f9b62004fcd3b831626a42883146f30b185430110c06c69c10f90600230b42fa46282d4d4d4f000e4383517e4f1f143cd4b34bda933a8d474b96020d3cba3adcefb7eb43c40de5e88eb9d0f3a768cde20d9517b40dca6bca045c7f4a6d400a3776303d769fd348e7776acbfd7745605c2a57726413a08971e0f5c9a81c83a108b06b1de3be4d158e7fd5d66cb0db490ea32458a1a260ef224800a2c608218274802196798acd41758d0a3b6525e08425b6dfdbcc3d3d64b90279aac03a91d93205a15b8ed4e6badc43aef92fae0a8b690a78675defb1ab80e483f2041788ae95d639ecbf43ec33c9b7aa61b32706b80de5414cee81ebda92880d15d4d775abffab3298b251932568fcf22b239b072858b2cd0092131fe5b7ca421afdfb95faceab28ee4f8451b3918cac1941c5481587c69e6a45cb7e2eb4e6abdfdb688a5fa9aa2096707c0b768738fe6ef5803704d1b5ce703b30f3d15e370896bb8c49f8155fb4d10b8f522840f0ce09d2e0810cc419adfad9786e7d7401e2259aaacceca9a2babb35cacce76e4212ebdfd1219ba31d4f0176f3e5c06371f19203c8cb80ac8e6c3830f5c82b3b7e6f5b0e35dd57b3d89f56ed91ff09dacb1d91eb2ef492b04fb1e67855cdf07f1e17f8f0af3f8c03af038f0c0fde86e7805c23b9687f3a18a862f4686e60c60800f0cf0a18701b08d1c3a5e1b9515331abd2b6d343c0d0e1cef611f98671980e3f0d38713d59907e1f12e4837009d8e76bccdc69ac7cb1592f3f8eaf5b0e3f117efe090fec7d7ac909bc7da31f1a1e3817029c8157808b2656dac338fdaf1e81c5a647fc0efa00d783bc0e35020b8044fa2427009fe7408d6e99b8a81fd015f437fd81ff02f0c57f3c8a58a1afa523ed0e488d1879d9ac7ef5657bf21c8e3e3d0a6ce2fe4a1e77ff41aba5cd2f1f6418964696375b67b50225f0cadce76a544be98b23adb93524ae48b2aabb3cdb4ab691db4e3a1de2a343c0dcd41bbab672810e6b9825081dbb66470c454959614972e6f0b97525c1ad5a7854bf043dc7bf7b4f044d3fb12e9f3c2251f80f8e08395d5d92babb3fd05175f64d1a1599b8f95dbac5bf547f45867faa1c12538a988c62d5915a8ba1138df97e765b9343c1043f00e067e108288243ef8e043962c59b4644965e1122feb9decfdf6949a7c27e30ab1daa7ba455f6f34029d9a774b5ac532e5708861c0e9d0b1cbeaac97eb0bb7cb43244b69772c9fa843f770093e2d50079ee3b42c172dcbe57a5a20cf9aba7b4c68c8a5e14b35d402799e97abba16eb9d0ede888061fee705f2bc21a8037f55b5e374b4553b1ddd3d2f6dfd3d3cad67f42ddc1fd62f1af7872557c73ac461418eb6beb5f5ee4d69185f9775aa2765884bf00ca83efc26085cf7bc34fc4363280dde5983773a205e78e70bef74ef8a1abcf3c0f04e07bb74f7b4343c10de19823c44bef0b23afb6575b6ab213556677b210f912fc0acce76f7ac441eeac743bd3d84246a0757ced2f19a0bb818e5bdb142b8e179a86e1b455890ea4d69014c775ca5e31f98f8a725f2948e3cd4f1b5d1b17a57f3974a40babbfaf57209b3ac34cae7fadc3eb74afa617b3b91ededa8d668f7d60e699784eba79db3ef06ad4f8a6a48836467d631da311a6dbdabdad534bf80b9e0baa725fe6981520b67f1c25c1f93046f5dca7bf55d216cb22225c173649d08d9aa281497498a14286eda2d4017d3f2500031ed0ea0933180fc4b0317b924dfd530113899802937597559abe89aa076fd319462ae8f6ec1ccacacf9d17bc30e28307e3fb342def739967bdb037f7fc45734d24b63ea2488db1d62bf3a037972ac4e3c0f3c7e747c577343c777dc8e9d59c853823af2dabb6bbee33afbeece6ea9e3d11d8f9658b65f1d5f823c42401df96bd68eebae940901afab4bed7234ec5284368b5e9dd91f90db12fc45b52dc15bf4dab8a52dc12f93215c733373053a6d7579082e4921b82461c0c1f32faeb698cbf3ad80f3c246efa530d45b83b8777f94e65e0a77f39ce0362586351e16eefd034d823009f2a54910687d618422fb075e835a8cd6af73f5782d6281b3be18bd3a32cf65e20b0a14d955fd8997f5e29804ebe50d759bea960b4a0760c090e28a176082f00253c771eff45e882090279a505bac010540988118be68d962da6f077824c40d0d0d3161f282bf5156fefac4ca0fbe6725c2b5ae69d10a9cfc96de4f7c69969455ecddaa4a2ac2b242613e632e57d59728f707bff8601262b42a29adcb8a914bd56e65c828a5752b5ad2925ad462ac62b0c38caa0b36d2a8e8ee0ff8e48b4a90916baaaa9232a311420863acc860c9b1763146b833a62184f292259410f2c65db8bc31c68a12d9ae2c18b904b92e0ddf7b6368f1bdf79e7c516b182b2cb082bb960f1d63ac2a58c52ac60857eec68541600823c752325b34c608258c10c208ab5781b163589694325651c6186524638e163ed227afe33e30eb13b935dee1b8bc4f8ce5624694d656efa27c35baa24e5e5bd62fca6cf47c1aa4576a0a34bae2ca71293e42ab6a5c8a9625659cd15ef5a29ce9a8b14eecb4bec5030c60f39a967ddbae8d3e49b958550b577bd60c6294912debdbe1b494f2599694324629659492cee59dab76f1baa0aca28c31cac805c6a841def68a89bf62ec8df1bdf7208c31c2f736d66e26d2ceaa5db4ac85f1c907237cf0bdf7de8b0f3ee171844771cc1acd67e61899f98dc1cc8f277cfcdee3faea0f277b102bd698082e5d8f57a43db0989fd6e6332577fea3d8b5cf6af3d1a2fcbb2c6d93efe1aa9acf828134a3cb1c11d99e13d2cd4733230bd47893f2da18830497b0f3a53cbfa23db0e0e6b5f954d2da8040b5843c17eb3c8c6a5adc7c64855dd4369ad19b7a02d79b7a42961ee36d4144c8c1dacdcc58d540218dde9419a99aded413acf44cf3767109153766b9b4b046be5eb54690fcd0d05090841042f80ee163e6472805263ec44c1943064c7c3823882377111e1a1a1a0a622f6f0a4c8234bc6010ec2238b420830a2641fa67bc3f7edfe6f707253ec2f86d187d62ac4c7c889932460cfaa1f83d12cce51d6df49e849b0fe4d2e3c750c8ec15f6a8d517951e68093577a896f261cfb0778488223eb8146fe9d0b1864bf117c5e1523c973afe79e1ae775ac773cc7352028a9bde14af00eff1daa9ca1ed80935bffae585dbf868d18b4b40a40c946045196d9031c688ebed66aeeddaf71aef447e35f277b33e5fddc866bc89fd49faa2c0c16dfbd1edf704eec7baecee3dcf3b58369af2d111f6dde6ab4470e9c5b66a0f945544e1e74c0d0eeff046694694ef0f0addcc841acedf13b867c9c3eb00848720bc233aec5e776f8ab743565165a8d1dd7d78195f1ade870ebb65356e3ef147648c97dfa846239764ccba6795acda05b1acdda2b13ee0f63020cca72defc817b5ea917744af6a07e5e6f324adaaaa92af1e379f6a4b0f8c01a82da8942af404c2508f92902984666884410009831440304020140b87042249d3851f14000eaeb64e549fc9c3200862c818438c2106c000000008008c08400280909472ec3dcb0541a351c5e9f1f382433c8cfd52287d14e469b0231421427113060c2e53664d48af2ad3f15df08fb84562fd71a9ea695837f376dd5b9adcb0b9d36143519f1bee42400d232ac2fabe144c076cd273b3d7cff872378ddd8b4676d160b86f163bd3e06e1abc59b372c4bf20a0cc8e34e0bb6769d0c044285319e111b701663bdf923f48cab9c96b6415a8f8f7c41fed1d2c8d4f47d30013eb678ae856302085ae753c009522b3b211b8f6ae2a3f62fe5483806d8d0bdcfa8533c66d6da1171899e8abe5f937751f26d7a992e0ac972a2d02a941a24d3f0ead71b9603033ff775d4953942469ba9d9e41a6e3eb5294c71838800ca3dbc952307b2b5d89a7f3308e8b19c2db6cc4673d91ebd9019e3e86d1d26fa126ac9c25746eb14d7b06239a7be84bdd509d3272fb692369ec6bcef5679981a27f78b4a109e8766474f52eb67cd1908eb84d3aff3b81cecb1b3ee5d644e47bd9c5662ba899ed4faa8a2054ab0d39b951ce7ef5ff64273fcf46cc5ba1ddc0cd3920aac7060527a3affadf7c18d0753347a36a1d6f1d7d5a056a5680e01e537225dcbb3c6341d2d7ac305916587ecbf3ee12aef9ee4c337750b1c5e2d1867f6b0a8c2a7c58d5acf1b2e6aa95403cc5b41f4fabaa3e080418765dc8ba44206bafbebd4d616f39df4fc18f4de9bc9ec0654b2acf24e1c97ee7c2f2634eaeb15aa0b16eb35c3e7e1b02a59fb7b2a8bc227bf2fd3f68765be8722b5bb3e41404dbc9267ed1b32acb712516b32394f0eacf76f4addee3a5c3858e18dcd886b7ffb370a306a488fc5b8ebb418bae1fcc6d2736a904ecdd6b1bb22777eda81047ef954d5b988c00185ada35895fbd5580b1f0b8a0a5eeda3eb1cc38bca304fa740ae8256f29e7116c8e0a8c908fec61c67ad0a2e3be6fa78ad297c58f0dd2f18d42583601a9297d08a15450107b46e449f3e7c57f2d4e98babe3444c93ce3e9aa8e2f2f17f4ec102fa184544ccba244dcba90d7dc514af4281e2494f38ae5dbf2e4a94dc26468a1dda71112927662d9c4bbc7bb001c8946675dfbc07160f15c699ec722b9e71414b3f9d706da490f8fd71883310926dfd6ace1dfd3718b58c953dc35646edec366e34ec2d647a0c0417993a22feaebb01a041f7c1594c84d8f6eb93307f2106172c6a9586095387e74fc0d9b74ea6c68657f8fd379e1b8140a4aaaab7c397f0abf146a128b9e611abcfce2efe680f4b625a2eab5d63d52f0a6a7beb215454606815deac54054646e68ede8833c36bfbbc61179d6c8ce4a150a4da2a9a47b70300bae52bb6a41c420508dcdf84a79a497d6d59d36affcedc07d7c9f1702d0d3e7557929a67f208a2f230e688669621a16a9e347fe0a54f70d08e19c6f7e7f9f364c472e7a2ca943a58f899d39b5f35c3eb1a15f27cc7837562e5ed2aaeb6b83953469a7227a380af515cbde27224d94acdeb0276b37c26f92a1da63deb47138f2705292857d7c7c2f24b4443a15da801744ca998592890973c06e3c25b34603d8f60bb60a06d03aa8845b24c2befc46274c0157c657452d42cfe80debc9bd6fdc2ca095740b856255c3d9dbfe9b8666c4f7d9d56449bffc8eea2234f10cfa0213ac88ce79b55bdcd751f5d1c65ebeccc0bf32e8d97ea6d68cb9d0049d8219c3bd41cce50131c0f2008e99d84b34dc5a9e017d068bc11b0b4900a028346e0a3b1a6ddac101d02033e20496d4dc2ced94b8c078ccb38b1f97b5708edf8109b505538f1983a47e86dd61f5353869d30e07577064d2753178cbce5408ba847831dcca26162fa41632d60366ef9ee4a708790812a08d853ccd41b52661b8bccff02392830f577b74e0c2a2ee3be8b0f0ee1e814246cd4d3105ae048560ed3b841aa93b6374cc96149ebb57b4c55a8ef5359ec2d3640959ab63241272241c8baac40134cf1d716e2e245f2cf7ccc0cf24c041725a2ebcd1c042b006d4a3148a9689f3b159236e7cfce2eac8cbaa39a69772e6d4e8a97f54a9b56ce5a964e8b1d004e69f8d0569264363e1992a2404d0e218ae041995676a4858bd1945a1dbe14e8150f4bb60afaee924f9869045833a85b7528c56b68964dcd15529445c0a0f9495091a063d53a27b44ce98266d9351afda161c13e4920ec76d6ba60a74a95fe73b0b6bb38dcb65fe965725ba1c8d7fd874ab67c746f7e8222e9ec17abb20baf4f89c9754caa5c61d8348345bcd04e10c4888b5c1bcc8b8e40834501cc4bf8398f8a866cadadc6efdb706535954d848e4f330998850b8456fdca15db0fc734c532ad8143cfa565c7b96d4a6e22b9ba929e3dffdaae2eff1c917c7fa4c6865ebb39eb311b6486e6243d06e1b35af773a2cb3d08e549caa243316e12e28357ba1c8c3cf77c0cd668b3ac2590397ba22e1422c8c64f487502b638b0701d519a8dadb4040b4ea29b4b0f775c44b8bbe55a26b60fc4a2c1c69b3f8bff0d80e3536e77fc59d0fdc45ef520e4fafaca1586e23115600b2be3d3b564dfbf7c2d3584f088d5027d55937929e3b55ea62a2f433aebc0186f6e03526da4736acfca27221e22d8c8e0aadbed1be86aaca35b2d6cda4a971ab54747ad843e0a66c211460a8f25ace08a6cc726f33f0099c3ad07334db2169f3d66b1c18966e03686d63c244571f518e1a09415337b661d2810fde2865cd55aea3f65620ea8c329e3434cf1189d143a8ce6b93106d9f353b1b1976bd0dab2961928e8487baacc5af3ba651f45dfe8a1366a7d99f5175d920b6c7c3cf9e8240841e2a005c0f5331a1639b9e8d8e4ef6f1079ba28b23a6a2ecf170807051fb60bc0fd6eb2239a4e036b7bb4cd48b48e93bba09b6283a029b2ae8fa7bb21e30d8a21183e39aeb1716fab7c7aeab3dcaf6de28c025f7dc0f454a8eabaf88c7fb04a49d82e9d8e8b4317533598227a8f3c6e1ead44ad3175bbd13751bfe61f42a52c5636fb0a02fd9593e310a2e86df1caeec95bdf51a900abb66627a907d5d6cb0ba1075cb7c3a41e9a7b75c7686f8677fbffc53bbc93cef61acda432968a51d12ebf776c883528f10b585e08f3cedb54c2f81fb435bb8725beace73cb739345dca4d0562f98055e2e77782b28221be11b302f3939d06d479b9252c9c9715414fe4dcba9493393aa9b1e4c2a35c1949e1ee9c4708f02b1e03cad2c05d44f37319631b41bd592a35b842b994e6aaecad92319a4f4a8bf38bf4f177ccadfc4dcf44f236740de984b39558fb183cbf553cc8c509eb1810bc0f637eb7f74b621797cda3605e983b2dfacf8e81b02574eee443d7b253c505175e8a3302332894a313fb92aa399c0dc7419d06a86e59af44958d35f1252fa73dcf88f6473505244d5b041f96c70f00da24ac3d3fddd0a58417f7941f28221a4189dfe8c94849d844f64626775acc77aadd2e869d4d5718ddd0dbc58d436aa2a972a810f32410269b005d1123c1249deebd46e5fddaaea4a4878759a5bedd238ea6e63b919e2df4ede9d288849aa8a0eee96765f626b2b4e3dfee6fe8e8f9c56bcf3a9bf00c8e75085e08b1bf3a80a8e2ba4cea99debc56127a95bcb50939107009d96c4f705ef37c73686e9ef080f24009326cbbdff51d9838b1ff31cad1642bfce9e4a65129e53bce788147524569be3e7b16c0ad75667eaccd842b8903cb78a9d014f941904685686571d4a896eafbaea6ca657436a40825b000b9c6460887e04231c84ed182b86e04e60bc6f14049b46122268d42849b234e731f668823d74f113f5067f5579f9d51c899b2738a75f7677ecf1194a075bb96bfa356b88fff6c4db399bc18178550584342a550ee07c062ec8907a1422c9dc4826124e57fb816bcdd4c7aa5925194ea7a50e34d4844e174ad4f7f97401d049cfd7fc44d3d6b609025e01376559c3549f6dfa2b9a3c72d36a55b764310605c7e7c740c98daf76463388b921d64701ca08a381fe71d202f5639af9fbb4934babeacb13894b3ecc1ec612abf95107f5eecdf9f2478be33d110baa86b23be90113eab207aef6e932981133d91d2617f5c110c4aecfb794b9b5965910c6e3fad404ebf1f045981e0495e892a030919e0d00d9aeef4120f0b82bfb33afc4e2058f93698c689b73eeecbc6433b77377b2c11e7347b0e4752ae6db674afbab88adbd39c201412030860017dbb6b8d07508d261c791702d5112c9d8edc95720bd9db88e25f2ecaf71f890da92b6be4dfec419a455cd0298350dcf4ef5f42b2a3d749e17667e77f7e8e687140d83f7ac00ea83b7dcc5e47b50b39ad32485fd6deaf9702cbb2210fd63c4a633ba180140c0036643facde3d260c287747236a6deee4556b86e537097a0b864d03b64bbc0024bc146165242d47240ba9860c29a0e2420b52a985ac2e4c3a330f5bfee633846fd91f96147e23dc4b2eb853ca9f823c78d1a77f7e5796660f88016f719a357e91cb080244a89ddcaac5d50cea5c1a98d8324754f01b5e193a3665b6adf93f99993c454003f58aebd8ab158a27f269a9a0042b0814a4bbdd0a7e2f40a87f5080a6e68501512c00062283ec98cf0c0b14a31864fdaf82dbcc4877d9f5dd2673c058f185f860c5af810b2e05c100e4bf3482008a01f0818ded03501ac82b8a0380a46551f0cf22a074428d36e702b71c5a7eef46649443fd0d6500e75a685b009a5955c0ed79a4fdffac95778a80325b6f69c251d6e9050b8e02a9369884249b31b836748c4bea9aaf56cb35b6f5e9c70f92971fc84fa8a5d118cc4d4af0dbacae88d060df185391851a62798bfb72f82e4090266574d1621e70ea80384333b603cff977b924addff4b7e45b7059bed720f9997acc74da83a4d2ea1e049052c16b05b5e27de83d21676000a80d47a6f1b02abc5aea2b858f451c7996fa2f1f198e8c9489d49064b6a960f0517163f5d4906d1e10d79191fbcee8265830d9f19917475dc9cfd3f6f8343cdc830f23fa6bbab11dec5e2267c0f416f9577f4452daaec76b7b9d497ead3b65133c2771f1912dd9c4a2c15055042c761c6d9d388f4a50f84432da7cca7d7d4a03026d5f063ab4162aafdac1236d85c79d405e941c6a254dc746dc9f8c6a7cb56d20138237f491ea380147ea60c8e4eefca365ad0395b2180ea821a9d527fe694b1117381a2b7211227b5c1da64f4b04c317aa1c8de20dcb7364d77d8afe3042643ed96e8aedaa483ea689bf52eb2a19316d021518d8b4b10f30ba925749c6ebaca5b6830e9a32063b8ea1ea8b023ceb23232ed9aef7b7d02884967fd09edc127d291853f2549565035ad10c59620ff46d42c2f7cdbbd9628b4282b071203b217ce39ec36f358c133d254c0ded8dd1458a31cedbade2e48e9d5d18b441434bfa3825149ec4140e9fb9a517703ac675fc2c8ff7c55cfce1efdf4bfd4eddb20b1a95bfa76f6936b34a0ec23734a53d414e46e2f41c695c1200afc4c154d6ac14a6ad6ff57e62ab2a08f7c2572042bf90d224e0a0aa8cef28dbcdc07c11d9e7e1c1815cfa6e5849198025018ab5a0c17c9f7fa846438572ddda798e3011d83503c66d6f4706c0479f2dede0645cfce98641b042d4d8652b18a6781503d16d7ca6300db0247f88e5b62d30d1a9e28b8a0a1778e4cd00bc878e96cb233b32b2c97f2decc2a98b30d1d21bc5a4025d367b92d20d018e94d6e6b1668304e5035555c5866ca18226d43691b808cc4fa46da6a77f29cecf64ac1139a30396336c49e836f56386277d1c714456542a968d72ec2eab6d7aed3a3ede782ccea736669f4d993c1ca287756099b764952c147d0b59ea6031afb4bec678a122b7567c63120a397166f2548a367ea665448e8f69abba9f7bd8efa83a0fcf25cd2e9956434c700ed3d0b624773f319dc39ec4c95537c0dfd2e2c919d4f6fb18b832269cdcbe8e2a0943121a35f8644342e0047926c1358703382f3f90d4391273611f7296da4684e929491ef47d8469028568576251b0092dde62f4025c3c3c18255c2cac8b1b2882f3ccedcc4d61dfed7b353a3513b92ebf8853a4132fa4b76eff1e37f240d842e46b5bc9ad60efa86f0e82f7b9c7f3b1099560d971e33e52a0fe720167fb8e888b77677885742d4fd7c0a9b69b1d6e8b718f3437431259a873cbe57fcb6760e1789997efcc143bd0aa33a8493f80b6839a7495b24663929d2c2c04b3e643471bef21dbb2951629f20fad87761d7248973db0a186716ea443d4ac6d340d79a47c10552e296c308e24b3170498b17503df71d07e31dec7c75279070fc712fdff4a07d293f5dab21281d8c244bae2824822d090bad070dc9a6cd852a8373260d046cd66dd288e8164a2bdae11cf709f9147559e67cd43a0bf794a2fd829d883f7bc2140f30492cae6254a7571d8ab375a764af2cbca745bfc651c679e76d5c6adbd221dfc1cac6e53d9af008ab63893aa9f88051a9516e711a853ee2593f1cdaeebc0133cc3ca29786c5f43fdb248fa7a602b6cd43db61927f8d985e089a91e770278e6d400c645f27277774a80f7bb39c8af7569ac747b0073384ffebf7dc1925f9b9f419d18dcebe70d7f41bd9a56a158ca136cae8a96bfeb71314b15f97ce76c483629988b541e3c04b19f620bb00af8803faa4aeb219aa293825874f8de6a1797d4a106a77f46b47e49f4e5150fa9998ad247ef9365df810b3a91821aa07f52db590560c258d8b1b06dab69cdcfe25ef63876dc02de20e618111772d8b53de6c5ffc7a40f68e5ce4458731eb38c38d075d14828a9386559a2b3d94d520fb588c1d45034071be1a3bc9ac7ec32e98ffa4d60688cc3df0522a99a4f839c0aa69a29497fd8cd429b9ff97f338a1d78b6d671efa2cfc4da8888be7657c5d75fd64b0d615ca0534548f8b653cbc2850800761777b0ccc95ead11e00b55f41721872bbd05ffc03e56e2d4c5577a63649de687258ac5b1e07a3e62c4b12e837761102b4afb4ef8d40a9f1b20f157898290a75ea6a44913850e31fe91ab1bda050aa47c40f2026bc0582c2071a20b5608be5636e0ad66043806e5b04eedb2f70a34d02f4c0f6222be97ab59e2234019dc2600d096e9eb3b9afe714d568c250af584604025122c4642e9744e83a45f6f8a07d98c5579703d851ff570ff2ba2f81afc7fe059cd441ddaaca94a5ef980d2241fa57c9a913101dbf4af304a4783859343440e5e36fca48d01c7d2beb52890e6fe3de68b09de3632a5f7bd5a43246346becb3d378a312e3c7f3285a4b8607118e9267b6ec5569520cd8ec47d701634afb43aa12e869f9d970a34babed2acc3b8031abced39cbd880b9c29ba5f68122253e3cf9a6cd1b67fc447ea40152388c804c9d22fab77d97c7c6008442ce7ed72044e27d2b94ab28728e6ef4629002ce09a28a8a88a8bb505e30c08e981294ff1891195d42ad91cf1e70e1ef4f023ea9b662e373e825214f6801ed6c55a58f7897046b0530c0b4f11f333da8d687f0443585a1b68c0b0017f7dde66a6b1f5aced71effbb656d867da74974d806e9ff4eaafc4713ac797198759b04756211bd68c005166c54361b9e64ba7a98892dcd83c049a48618654253553d2b940d13072fae113dd13023be75c5905683e455ab56838f8f614caad13e308debd8879cc83aadad422b925ed77d74664c2a3956d52c4aaef10345e8f17e86b0759ffb7edc8bdd1ba3a6193007e774f374874a79e8e27b6b7b5e0c7a4df3aea0dc3aeb42b6a77407bb80ec09994faecb90b6efbc62a2101a9923bd868f606b6d3e0f5053b4957bb25e28d5dbc3e82f93bc107cb2faffbb184595acd853e8e5ff2478555aa4fc83d19b9102e0b5a982be5547a3f6254e0ab2cedee911ae08ca28a5b0337a40fb39c684ef3ceb40e3979d2179c97946c47d60afb1ef7322b018f2f94b310fc59617a3a0a1527df2b856eb5cd8171c58b911399b73c01064de891a42f71cfcc702d0d84b61f5f56585a826cabedb946b9136f0722cb22b5ac7ef71e4cc137a7b921a2c6a9c03e398ea8be62d17cc083c75af9b10374ec2c9c3344f76dee141d743362f410a61d529a0ad3fb1d318759d3704d713f3052ca9b57ffe17a60eec40c20659c6a62e4aaa55bdc50fba55bfdb3cc3b425887079c4954b7c50c6b0aa8a73daa0b68985ea8dec97f841f44742b46ef351161b712c73d192ecd3c12f7b5b7fda135b2dfb8544c7974660aa455522b463a6aaa2a448e4d51787d165753723d034aac44e18021f791eb46e5878dcda114397d242e2ec5c2351a6682ad05cc23cb63062daca02d4debd2b3712a8cd85f1e636882864027dca1251eba32b50e5c47d87a59cd5aeedc10852746f334e497369298e49ec9448b33d325261c584f8a0f0a9946888042738b2a8752de175109bec7e80648e70f35d7053e3c5cafb10279741505a716dfefbd0508f4bfda3613d61a92d98f7804c6410d490a42886695568444dea08962cf5ed97b9d6981f419fb9fa80b8470800372e200d0cbd6c7318e089314e03466dfb9882f83548c692cb94743cd5dd9ca0a2034da9770f4b13441abc9e78b93f6748b07a49159d52a2b9311830c8946c075b056aa6f1e93042b60fa5e5b23d76cb8c594432672f17a3e04942132d349ff08685793b942c4dccf8e261e83e8214a851b10f570350febe17874db56463c9adf6cca6c0d91599cc14705b123fac49adfaa631d6945dfbff1fc90498f4bdc5dc876d4537c9bab480d031ba0c77b99bfe2697db7ef99cf8e7b9d3ff8f8a327106a9bd1c9f63e7afe94a8e72b825aa01bd26d3ea5a7dcd0649892aa9e248a2f7720d00a3037e321d8d28ab7e480310a6b325338717a856f0d52098a84851a31d8c14591f97459c4399bf03876a16cee50339ae4cd26269096611ae39e6fd92f37c80277e472ac375c15d705a2c64e6cf4de405edd5d6b0675a0c13045664af9d4439c30c41399d14ea5c55ed6b6653b1337f066997285cf68fccc85e451e077cbb155ea65736f807821ddd856f388c6404c85a843004e57a9f2fc496fd30f290a8a550333b7c58faa1e1b17bf7b0e44bda01440ef3a260e32dff521dde4a7494174e8603406ea7cbae726a6916ac9b78fcecdc1631680b96d1331cf41517a3cb6b0b4a7750050e1dfb6c818e3e8c32128171510a23b8079168a476ec4f88c3a0d467e2c2566cc1b7b2c30da1e7c0f9b10c5d283cebc87ad74d2cb89640d3cb114595303c38b86476a3985bc2085eb1ace313f6169d6eac402cc40267ad5f0207522a7fabcc828959a0f2d778a6625651b16b24a8f5421a5f6e03d4af3ae8a59fa295d91422a71ffc5c963e338d1612e4ab8094c24ceff7bc09aefc4f3cf665e67d4f12301466af275a2b5ac4986f3674b0e6eae33f2de3cc8767ab8e3e35811a51f90f6bd7396b764ba09272fadbc7230009408c61ab656b7fa1917ab8c1aae87a21cb1c65ea82e8d3f818ae9132baf70f557d54961371a173f9352fb62a8fb70238ddd0e2c8af783bcd16ff9f954a7c08d869ff82199dcae1a6567297f171eb1397c23ae25211d3ddb5984938a6de5048c4beb20b29bb4adc43daa84ea0632365972dac30b2ac399a5a5b40a80b754755f3fe82e944d83b96ec20cf0f8a020e3fdd69b93e349e85748d541dad4d7d9ea020ecf30887b3e449abe8868e5a3a5ad9a22fb0756c066a55f1d8847f2b6c1d90cc373d576b13e93fa90a455d4f93962c558d659e249288bf56ae3515fd81184d54b92bf5f2cf1ed4f2b37ce7b83128ffc63aa66f1f49f8861f3c1d098f50e7544f26d98832b713d48787c377d353ff601ffcbed4194843c606007f17c99ab5d87191a63e3f64121484842415b9904c483b771c12b8959fba0576b5aa768e7731a754c55c5fc831a5b1c53d717550cd05531bb2483c0dbeb14bf7a446c34f3fdb02d7a4164322c58d938eb9fb5dbb125292e5f3bfab4719cc374a2c4e43750f56e434d6cc1f29454899f2886c9e591c274d2606f27874fcabe0ae0c10e94cc4e2ddd613b22ceb0ee0ee2784c5e03586148463ea042acf2f6c3292e2e7880ad1c41d871696112170d7608765a45140a61348e886734fa5f3ac0cba678021d5df8abbec1af943787d9d97cb0e7d3d05300c5e2f01f498c734fe276d31cde2fc6f7dd146e30b48fa77eb64bbc424035dac5e88ea0bf1b3fb4e141d34e3e0ea692f5108603814f5c169bc8e3cd80cc13622191635f782e49622a0bc840d08c5dd0dfed08456af4eaaad1c3ccebb686856d21f711ccd05b4393bd55e06274c88771cda4d767ec88010e802610b508f6244000160781e0416cacbef43afa4deb0f463ba8ca488685d513f02a5f6e82b0a90b0aa113595b16d2383f4824697fe4b904c9088b60a53fa6eb633f7664c6e8f0c7364913c1fd5127c34531cd2568effb23a8fc2d970b0219b3b01842306cac3e1f6c02bcff4a81704188786f646ba15c04d12dfb9afc2a202006d43415703fc40ffa4c9081852d1b5f3ddb896fc26a4609b5034e06c17bc541790b5f8b72e8a641ea41079cb919c41ac8ae69224146ef380121925c4f0cd3004e7a72bfdf626ba9ff8fb37f95a9675207e285f8ff555fae0c144b87f629a9f4f0e6f37c55cc6a16b3f5d6887d56775af57f127623eba821616e1eff7d76d2adc34e698aad2b71b0c632e2cfede482b8087582f1a89f50ddf988ca111caefa687e3d93f3c7e0bc7fb42658bc7af7e6158ef054104a1662ff80da11a2a3e1eddab4a11c952cdc17e7a9d9e76e3359d367827e3b531e2def25ee38f74d8f0429ab6d81a7c55168e8bfcbb06d68f5a079bed203bb43eebcab38365e1b0447082a048debbf3a8f7b42034603a00524097e6c678a00e4fab55d359dff0448323601f6bb1d258f4ec7d8adb0a50aac932c7339a184acac203e3d4939a76cef1df993e2a371d4630ce7d0907913a4f4dfd32c50d83fffc3f0b2b433717b53281690325c514ba95e1ab6b4f518ed98ed4495d9ef885d8ab59c903886288d0d618b983d40cde66e83bb3bd3023995c9768e7b4660e44f6c75e2aebb8fb522e1d1f6b604ecda2db8c6123004f72d1c2a4c74411704efb5b7d992c3b7b0a8e74264436a163c8e61d5aa5b3904ee8a7325f8163f19cdbed1d11a04b2ac22748f1856c12d070efaf2a84169a57cd2f53f8ffd20afcf408db01d2f1d5faf311bc628d2dfddd0de40cdcaf73b00ee78fcb0019c4277e366e69f1093a9ef803570467abc4defe741d69cf29498ee15414f12d480adf0f05a519f90567e738256941bfaff05d2d81516d2b9f27b7e031d05df5059481d2c8dbbe88c674a7e5b35d41529444a768e9d26012b0db47b45215003562474d327417944becae649d544746a3dc1312e878caab79ca3d07eee57ee290f7c655c683416f7526ae5e937cb45c708fa6b7eec099498093b6181f276b0d9063e4fd169b6032ac643d8647a22a918af6a582a1daec3cb104357dd15f4f09741a7c7d1f0bd86b0425d8a79fae3ce4ade074dc09e77f4f9717162204769c09cab4dd1faa893a2c9b654b91c507a804f41375d715e730318dc1ed1823a432c41a1aca4528e84fe76bed92b125f430d07eb46b4132fdd2d46ab8e441983e19ab8614bb30925c6c68bd136f4ac1706f6dcec41b003b5cb7a39b060997d7bd6e77c200636a9f4c7d55bc8c7357cff4064d13b28c1c37cd6e6a4c440c14608c1521598b10c2996f5b80482308397677308db105f55fe1719c118d938246752ea3de07894b0bd0c8a92aff98c6c90018b9007168fd2606db7b31c6dc765cd47018d225b708a5c6b0056df20bceaeaec636896bc8f85495f66cc819a6498bc0b40f3892e7ade5bf90c93f3c5881b24854a731d70e06ff53fb66274ba232a5e2bc9031a4796df9b3ab073a7635ee3f092f07b836ffc6cf101825f5950c8b6f986131978d705d15455346fc29aaaf86e9f301f271610b3fefe9775180e5cdfae55bc73dc735e496906e1c4d56c9ac3752d310dab8bc2d4b79235c97eed6721768c0a0dfd161bbd35073dd821fa0f90e01fd74cc45063b2ea32723969f1436bc3d6b93159087a600157b0cfc1f371c7720fc612a9e0a636dd3c80d50d137ca7523c7a1f6ed1d8fd2c8590ea491f8cb39f412cd042f28e377cc6377cc6bca499782a884fc2609ffc5fd21910fe20a32a5a0a896644e6fd39abd2de3d38615bdb82ad94610fefe824758023c364824df2962d838447b0055ae73981533602d208e049cb434b702e882135644736fa7453f42999f772afd6577441162972cd67f82149e709bfbd3c46372e31c3c42e4813cfbe1bea0f7777e517f71ce64aa3d9e5387b3e671407c2a7bc3472a14474fb42fd1f84ff012a02a45c523d3f936d91d916fecfce39d862dbd2b0bb6150f1956527903bc32bd1ae2ae0284be3c6c385dfb00fa1dcbe6c34ed95b9b9142771f636ccddc3155a716fec1605af81b2942708c60c30b236e4eb897d93944a066f9ce45bec0f95dcc7de9563815152bf1638db7104825688d3df7d530357235ebda4d445f965b3d9b516bccd3755b2b0d778108873f2f9dc1a184c3bd6edd26c02d77c355437609f248ba4a4b0caa6b19690e9fb39b4b70658e60d0a9316ea600cfd9fdc8d087c54c35805890fc286f745e7b39708410b8be855ab6207bd8cbe9273d134f4ca63770f9df33430d200a0113db8950bd2d4b6bf2055c7a417b9c5d809f43fae2513eeb67589e17afa847815cfb73108bdf2aee2307b59df6d153783eb45a990de3062b9c4035d00b58af72dbc06ecaab2a447661db9d80132c77897a4a857e3b582a4b508c0799b4357978a9fa2e0f78ea40ecf4e423a0f63dfaac01f2853da5df677ea41b79405696d37f95818ca92b8180721ccd20644412362088c20ab28d4e2a0d2d71b4a9bf38ae34665a274d2b2c81b2d224a6fcd129e0ac007533bbfac3c11d1972e14cbf8fa7a03cea429626ba1b5ea592514b6e853ed64c7218e02ddb3def7928f5ff87aaee123b756c117e22e288c36ec7d983f2ed2c834be6c300d152e50093746fa236f2e03b7811b12aa7d0501102b01909f4c9f4e734276b960c4d14b4a2051f3ebfa459b6239de88eb424e705ce7920ca3e2b69e7be5e1f0a7348d85b4742ad066f04ba90ebea662b13c044803cca68609e0645545221154a9eb6049de2fac749f712e30c50180b23114c9d3cb5b5e734dcb6e62defe19273098fd316ce7e2b9783f0ba6c6aca27e23d820d8b97ab6ff0972f4855dd023971aa1e271c96b9ae3bf3ed347bf944a6bb1a739111f10dcc9786cfe5c6010764219c43d041cf57425af1d89c177abc06ff65fda43120fad9ae1c1b286762ffae128d19496e7ffbc5bce0c6fa05838d17f7d001623c85ecc2003449a06d8744bf55b2e37a95630797ea152d1db7dc58c97a3a133ef105ef7ba05075daa9d52e2fe95b7422c05c3f4ee72c844c9aa70b2b54dff8397535eeb3ec8f203af9d14832483e634d0f068ed28cff42fd12c02da32806720a7976dee300e1c503a8634cfb2e1303a68400f1f5ed9a1ae5bbaae89494d0ab15245ae9290199782327c400393e1bc27659fa9a29a7f2368f482883fc16735c6ddc4c05a7e2dc623027ad3ec7a185057869aa126107807cf63e1902ef7c7d3a0745b2d10ff0323ab88374a20b6e7a90cbe0d342bd5d9e968207acc90c2cb9551750d28b04ba22c3308ebc01f2403d87750e4f067b8c89fa7d740b424fdb3f5c3f213929918d8e7e178b4272ecfd81b9c69e265db26a2c90027ab2572a89331cdba05537ce1d9241ecc41c4bc9c53a6de44381fb09a5482a1e13ebdd908861d4bca2bee3798b39265b9f287f45ba6eb39a12b10208581f264b9f352819773ba389ba0dbe7dd41325fd8bea229b3067412944f483dda4a8e538ed6bf9d46152c8bc2def51e1fc8161af2d747cd0fb9f77e1ff4e7ee1310b6613d6e3dfb512a00929f11b275d91f1cb52e43600bb9d613910ddfa658884679000c8faef6714320f9bb3e3f81397f8cce49a35231c6f03cc64a6e965a3e0a0df838fe27d5be452ef0da4f64d144204454efd8dbaa88ef2cb64760d1e636f4bf0509456d46a1913ba48a018753653c36882f6eb6d489fe2750602234f3a7f6f9eee61e5485c24b4ef76602b2a78a7d3e681c1b980f74f6f13b43cebb57e0a67c404979344cb27beddc993bf2ebe08a692832ea02b20f7f0e51b1c4353e502820c1f7ae7c4356559636d590a6a77b2d7edb3ec8bae85735591d07040718cc8f141a20dbe82c2f7085a49f1ed0658f6f0e43fae1fb601a2d36565d15fc6d73957d6de7b525986e6f28281ebe04b438b7618a60abb440b1e790791a9c48774b4740dfd2bb1015d76c368c20d9db71e3f3ef18b659e062ebb51acd27e19709500dabfe1818ebbd21d3667e9b4cc324dacab49dc0cf707ac8e12626db1bbab600b19ee6007c5bb6e5864538fb1a4e7ab952e6f13206200d45786443bec8d76911c84f1e456a18cf7067d1f85d6d7a63f87681d5d9f324bf7e2a52e6e69dadbae3004c5ff85623445e84ded291229f7e9e41a2bfeafd00f04f21534e0a71728a82841226a1e2ef2298d8dc4b5e8b0bad2c45292e0f8ba022ff5ce63e9cb5310c32bb27bfe6d6e57ff107c77aa80dca329dde3692b434bd781ca585ccf20d738e1ae16ddafe7177b49457b08211fc6216b7becb7056787750d77865141003fad81edd83fa36eb48d0180ba9ab77598fac45d702716c5ea4766b523df339bbb569526784ae6dca30258816eea1500593ee63800a4c815e19d2a4c6e92073cd8c9ee5e01c022a1f6ebdca89974aed0bd5ea3a9d8407a335c1383798f0e63fe4ad9b99873e03a4a4a9aa32eac0541372ec5baf80f384fac00278ffa7d2adbb75f72a11ad0cb1b5e82df1b9d038d7295d68f568f61a5d16beefd9886676881c4d18912c61fe7e4b2851e41919d22e93b337f12ec39d813cab8dc692aef19386c75356e342eb7b8d4437ae867137f261cd0ea8f002c2e3be3512f869092c666e6557dfcb28198bbdccad9b0e9e6ab68001942082e54e569883813248b868db5fb4728c57e9572db43cd73770370b193815b64bf2a625d72f34488b4a5f251a57e609ccaf555b987394765cafa868430c2f9214c6413b01f4f7ccdfa47e90e9e4cdfcceb450d58f48d178630547802a47647f0a2ae21ee32a72bb9a3e50a444e00bafbad5ab298de1e9f164f0239cc60a59e2ace1ab0ec41c52b1b3d57b8b427f9578a81cf618e4f5dff3e80fb581790544588000047097181a388ae2aa2b73dffca918098332405b3137aea006945c048023b9c0e6242604be09fb9b3956438ff0df10ea38e416a7e08b4d08f40a17a8b1fdebbd4122347879abedec11247ebecfc85a577e3d66aca5aca1cfcabaec7fe66ca27ce6e40eb464b53901e6111389cd4cb8f7af535721821dce19a581d4acb3af1abde5d421c8ae05d040ae514f5837b29ac52fda6f3203a8f5ec27689ea5a443d563048b138391fd700b3e214a3ec3a3da2f9fd4b78c9f096f33a0d67e88187d54a3a1186a65a36d023710b64914ee1abafe1ff4bf746489c0b8f2a48e245767cf1a4ac9b960cd628ff509e014d3f81254e5c62bb6b600e319157772727fe85987c36072dad3c87cf4f98882d10558e00cde9c18de4bae532bcc153d8ede372c32106cc4652e9c2bf0527b6a27014ac406251684c67bf6bb212ecfb6a6a5546e289f21f5b0fb1694bd3101c36e2dc391cdd72fc946e462b7379cc94d5125b464aae1ea89d905f76d716a7f0edc1be839803e85563ab33c599400c642d6a0329a8fda4b991a7ca03fdc23514d85e1e6435363386519a8e0414c9d16889502f69c8b4cc05f753a28e9f8c3d4409ceeee497e6db8e38635e7158501cc0a09710817c9854953ebb05f8007141fb16cf4940fb5d6cb2032430f543b9da8e066f5cb1150a1801d4ad60de2b0a8f3a1be37d8a8a64e2642e28312eefc5d1f347c278a4a1db9293a477a6b3e8094813b791cb53f4d357231c324be97365f09e6a3dabbb9867a10e605d52bc72d07a22c9a8eceec855371d9c1f96f7764462f4f3116cca443e9a217622332ac2f99fc00014c4b640e3e680e369168cc31425ecda2fb1d7fb3178c03337078c645dda12fa4a68578d99ae3cc5560f35281bab3dc855f5cc8645c004c95c102b63da7d15b5c9a6a3e2dfb23cfb4caefb6c5affe0bcad0d618019bc1e907604401190d68aded083b5bda7f1863268fdceb5abe28efcf7ddc2820074d84aa91e3896a79d1baa489253ef139a2abec3282d198d3eabb899ed25b18510e4ccca8d66521aa84284ac721e6a4931b52818f302cf8b6d44477b878c800c4b0e70020dfd56c701b53b9e9ef7622e7f3290b35708b1fe0b4d554e3c448520a2a73bce660e9825efe3c769a14c2ec2ec7e736dc7b6fa375136c0b7211cdc08d376f6959751592a898e1a7e036a33aaab519d049dd30a3a0ade0b0a33458dae3e005e9030843813327f824944d29c2244b17db7c9929eb099f7eb159b14c5b5bd9d86cc873ef3e0e6727393b582323f6fef8399ccbf3e078a339761c6035fca8c9f8298d14026bced0f84279cf97d07cbd90a37598583f8759e0be43d5b9310cb1629f66690145e2a7e1aa530d2d20468e8a8a504b7efeb1a0df93c78d97320c43d6cbf780dfcbf09cfd2ee04c8b019f6a12e1222b4b470cbc9c06cffc0df7e2f34094722e50764a380f12a2cd63de84164987dfe9382a46711f0f03a2d3386e8fa0135b7decf1024249c9c042edc29eca6d26be061389800b58c4063f5fa649e0b7e8e4f175074106672bdb02b40411be0edc4260987662348cb26dc83c1851576212d25580cbce1b1f3370785a0393d5e391a09abda1f656388294b1caf87be478547850ab2b81d6abebc71c1f7fd63cf078b9c4cc27c7bb2dd4563ab7d2f0e066ee712085b2d103d80bbdda08560ed23a8fa790b6b99d09947f257ecd3c4674c782e3c93946f6e6c440bac6b9ea17f10f1f3aad0bf29eee51df9de0eea815490f7ce0315e06d288c0fdd5d7931317bdcecfa4b900dc8c118b8fbc856d7141ef86c06f91604cb07018ad3648c46e77d871640cbe252e7072af653ca7284877031893b38400f72307bb6d07742e704c04d1b6c3ba058ff1c16cd70f0ed0d29728c377befbc17b969f986c8a31971ab45c415ee25b7b82eb51c03c59ba41769bfdf59d246d533db428c9d4897eaae6adebac45d7b19e12402168e6c5576933d7b94bfb72b3205e4e875c3f7c3947484ae6721588a86c9d3319da0b2bc7cc9c2e00de69b162e5b9691432e530043c25cea963805a95a06c9100d60fb6a7ca2ff2ce73c4024f76faf0a56f29fe6d1f8ef9ca90280c131a6351cddd9932b953fc8bfb69e015dd9bedc8a440783c4648354fa4c971178a558d77a10b6adf5d9106dc9e9985c6d32647c6ec5e4585110df02d9a2cec2e64facccf65f714c9cc0ab67fe2ea498b49d36335524c7dc5f1dd8df59853c658193449511053badd08dd88912d969490a1641726a1621359b5a6cb2a65370817b41de9dab922a6882b71398fc656852ee38123d9d13dd2447f06b9a12ecd98ef0b211a4c9522375b97c4812758359d9292b2b4222c25059b42626325d8aa867b1ed77d5f29a6ec815d3a018c515f6d8ff688338f6941ec7cdc75c0b65b479e87edc9f08fce634740fb3c72efcaf25394bf8018e18c4c150a076ad3fb560f400009fde9f7d043a6dd0fbf38c0497c6745fba0a4ff48188810101876b5333191bd08c5d32f15353fe2cbb18ccbfeef616dcb4c2f3a67b439224905a819d1a96957e815d5eebe30796d920c40343c4ea44e98985d9e21fe80c7e42946d76daaccee88ad985cf780f204e2858c952b59cd511e98f9e468e88de4f531def884c0a7846679852a8feb1a998552f90abb8056e116c8e7b5992374a1a13d8e3ffcf4331fcbd829e742b42d549572a37fda8d5997275a7eddff68e81fc715c4e50714ff3f04699321afb744b015676e66fbb498cb83467ba62c05b883a188c6b8a7ccba1a166da4cddae4b26939f54aa30adbcaa59c8a56ac734a33c74a6bfbfb0e9b88b728303cd4fae497b1704f6c9cfc4c5c8542062bb5b89b1c35c54049e0e473c4078707b20d5588a722f4af059cb51fc29b3b2b9adfa287a9a6e23d275ab8a40e2d935a468846160f47a46358d70ea34a121e054ce34a63d08f5cff536d4c733e4e52a7e50d58af37e5157218f7fa2f8dc95e70cc15a89f833d3ffade9076c285c4ea75abb1aa8cb973190ce28071751900a9f4dff921623b42286553a309b322d018105416f725f1ba2e7b7e49b09a8485f0f37c3352dbcdb31d406bf877f84a701d3669e2d0d74cd1044f7017e9f8822bdadf58fcdaede45cb0f140efe2c17b1187171181709f54b9e37615bc964d3774950ed3a28d58b4a3404e806a941b8eb9ded541c12a1f131993b3b0b7d3178960e9491371b207e78aa4ae9bd10b21264f8f770f85bf05051ad126951909868ce779d04bfc4e9a80cfc954c139bdf2a3295c208a0e319f0856b818c2e43da10bc81ccc995b320e29f6aad1bab9d2b7ac88b2d19d1ee1a591065a311b73b3672c8123458aeee3349ee800bf15c88af04073910bbfb1c20be817aa88d1112030ba21e8a5a2e2eb2948c6a49a00605c7b5d302e8c4ebc591bde9ffd9d6d9ad135b19f1601dee90e5c7ba1902cc301682a7b88505e1fe2138aa6637013419453b0bc218f6f631601c952e57696b89681704c270695cf688cbcb04341b38b743c51648cfbad86c2d271aefc94e2e5bb9d1d591001b60ec1e3f7ce04603a54c9af857c385411d53f40e978cc0abf12682230a62acfd71f5cc4c91439cd1637258b72261d370a9a678a9c3383e2f86c404f4f2024ece1a7f40b4deaddbe02f58443e0889a14a2cde759bb448432a34587ff2e49ad1a95481a528a66044de8592dd8e814b697dfd69199c93793a2411ad2840487a4feccfb37a350219461e090e98bff7b296bf02c0fec2f2db8847b2511b0b2fecb881a53117f3893d54655cc6325897f5d00ec8307335b11c9af156810ec29f97adeb96f1737432a5c7c7864948c27522f2664006a05fb84f5cb926c45d39e8b833cbb3b01bd156985acb49ae8bcb11e65bdae8878e159ad5727ed5cbe9678294af087324345513801927ba6f81f736ec389f734741b16bac5f07385d76238b83b237d0c820939d20d412beb910f97660bc79a1b4dbae02f9459e93111e3ea62dd6f19e12f0767359ee9aa29b0e10f5213392a34faa17d904c1d84034d1795d6033b73f19e17ef206bdb0134322cdc339f4ff97c8d2569c09b279d9bb7d05553bcdc7abcd94cb99414f7846948a85193b5c7ce5732e8e55b4fd60aceddb93d68003704a5583517732936199a5757f1184fc97a461cca5e95aa8a1cd90413cd5b6031f4477235991f07d6269b987040f8dc30e08a1e05f7c282c4829c88f0e77da31a801f576ed29cfcf830b9116a3e5b62e07845adc88c972138cce1df5aff48aeae702a5140144fa6fc90fb5b4bca3a8c8df08bf686b13efe4090952d6e4d307781fb5adcd03fd75e80dd88eb07d782f77f05a4df3edb9f6d84ed7d7665c92bc0af00d1a909a27021ef09c916c10921ad60dc7d7b5426ab2e8254ea84f2443313af51ee3d73977b1bb9c7a5b997b4ffc1c0eb2f552f399cc4402b40c80e071ecac96ca122dc44e75a0cc79831c02f09cf02b4599dfdc5609608b3b28b3f1b2cb2a6b24fe80eabcfa8da5354f9b384b1b1ed5ca6e80be37f8262a3d0b170f334714cb9c7fb30822e97beee69d723b7a162fa122f14a5a1682038d733c459b69934ab0824bf3f73611faf3b0a4834b8580797d348f12ff996b6758bc5bc1fafa4609820db21d75692ea9aefb222dace686da78d1f887c90e0f8461c8e0361be4d1b2555fb094a37d4d885f9b2d08f5d881d7bf1a1d2847e444a596823569aacc4d4a82ac346412537e4e8a98244e12862e43f55d086910930f47271f0171b2481d75b3dae44d8c8b287b5b8de7b1cb3a96f98fec086098ef11a520cfa4a3b483a2fcb9d5c44994fa035603850f0d0c415857872ff7e9f00b2053ae614e5377914fa84e41bd7815929b34540e5b62cd89ae169228094bb4a987bcc088fb8642aefa211d62c4dbe8dbfdbff505bbe722c4c8e050ce834921e19707f982ae967660578bdba522c06961b8723103e530e6bbcb2782875335e61a4bc687803c4cf782c21affcc24d6562d24837c0fd367efb8b09c9c1a1b09b902c706e370a62b9d80f354ba0fc0e38565f790fb633ebecf2978fe15380fc0e0b86f7f0d28e21551cdff6a760793bd43e1dc90c4c60390b143790b64849daf6563258dee27d3b1c9e954be8500a37c750c46029d9fab814c2272a79dfe19933dc2f7d0700df4e9fb617429443b5f10afa88cad86a67dc5649b3d90f27deb80e625c666538afb36a501932c3f25cf289aab5436c6059650fcb38a6d2fec0ddab506d5065d041de0af635aa1d9c560e171ff3b0a4f8103f3378d5db07edef26e7c450a5a343d60f98b0331804a0a6df1ac526f400f7ea6ca010b0aaa95a9afccc6c6dfa5923c7f9f872b86c2d83513447b2884a80a0c396157240b89857aac3d505e4d6b01d173c10c7a1dbaccae34e50e3da18e88e8251d41c5950be74ef3adb005c5cb5940f6bd22fd42976da55078ec59c8d4dc23b3dc0981ff6b9057404fc3761aabb786b8e46797c412688efcebfbb3856f1e446581f0cb08367711a7a56d8f2fdb511488bfb7a6c8336376e0769b44a3bac6a45444abfa1644e9e2252b88c7444594580b728b75684410ef616590fd63df6d5a2f8861808e3a553add2edd99f401084899255ab91f4aadf65971273ec9f34c606652555523bddfad4b3a546825c82f915acf5430d14104d0eba0c5ae111c7740b113abda1e731f155beea3eb2edaa7a6077ee540e00c10b76ef05ca30c710f3438192b1661d58272b2c90290532cd3ef1d80c082b44bbbb30cc3271ec3b39d78385268939449adb55c82b74042d91d9558c32b4cee1f65a03854ec4dc4427cfc36a1c06ff7cfbcfe15bcaf893f91414e6708a9ce12166a2f47d32e6e79b274276da3afdfef813b75517c67b2781fce34808802a89f20050da0234b353691713e926bebd0cf89d994092e9354b511b781227ac724143a5358baf6eca04930fa9428b7f49be29112562ff446949425920931fdf100ea7517e831ba969ca56d05b692c12ca2ed3715674b1ac1d03c8ac0ab20cd500b338e8119792b4644490faf0b2fc7b0e1a01fb9ac8c3c33a121876399a057813e87df3c8315c726f77062c5d786a92c100d82dad25bf634e313add33e813d71d5385eed0570487834816908a50821015a52ee1b0036d5681753722fbd8e28e03c4b7333eabc1a2f239b71105c76efdb3177f54c51910a1ce1f44dae4e9175315c90c657baa3d714c0ccf8505a292650ec42440ccaa17b3da3d51ff030574e8e9214b8f0ca9e7bab0b04adf3870f013fced4fdae97fe18e2482067c2089e974544ce3fc77202aa1ac3d43198b61b4fe0b164955f812c8806046b9b8f21b29a0552d43e90628dbedf64296303b70761a9cccb8866eea660e3a39c373e154ba3cb88c18a7033c204c695795f148a811d51bb044ae8744b3aa3ae683e6b7e3a07dac928f27f68698f60ea61d7d28090f9f51ad1fceb228b8e3890dc90f7321784259e57a06a148acb6cd098566240a752e8534ee01a162f962b9b5f391f3e66ce96b15dab7389f3e1366a0a951101c7382d3fa6df2584809f8bcd788e82e42c3316a4679124489eeec76e2e55b62c56e2192861131eee29f06232054660f32b8ae87b7c16ae545db615ae4df5443dfa31e8eb83128bfc5d8187738177b76464f65d9a13b871b63b475eb26a45310d4304c95d151a75ad14e501db5e829bc09b45b39f88ea76d2b858ef09b9319d7536b1a83a4508422da2ff4aad5622a81cb03dda86ebae4e29984db40eb3630782f266c14530157121cb6b6969ac7a27902ae26f193ad6c263c7ff0d6cf3ca3a63bb6c50870dac2ba7d2ef0b34c8002804473a0503842a0f9e20ed6c999b0fbf85ef67cb625bd581ce99842d2e8b5c0e2ead4f6ae4a32e3952350086eb859d1c702b49e30c09e9af3cbcf0bcd36e94a1f47f7f274a68734c2d3e488217c3ae968fd35e198856afe670849c9fd3f00b1148103430b9d971f818de86e68ae773e741fe29349547a543a09e1402edf057cab3b99df5a8bd044d8aa366e75d2d82ae52d12ef2349279a3ff2dcfe9f345cc9d124c9e8fb42069236ad5925ce1cdd4d0f08b9fecb900088af01b16c82ad97b456cd6c92ed73f0897dd3a077762d774509a0b36f4ce0b1bc5d48082d6619f455af83070f114600f7d09f66191008bc6826b672bd988fbdf88a2ac10bc212f0d700a26cd7b5e56fcbabb6849ed43dbcad4b8964c7048196eb6de6e0bc537ccb4041ec95721597e21cbdebd26b8e58b737d67ba0f0b4d9a51868b3d4984d7c57517594578aa95ea96d49bbcc171ea0a0f4bdfdfe96894b0507cc649890c4741cd394a0ef46323d3955acbc91aecf0acdeb122a98c3ba00d4da581786893a3a8f1d59fd52b213043df222fdccd472d4fa986f231fd8c03c43d9b1c238df0a1ccf24bd9b77a51547cd5a083982ee1f070eac8cefef33698491d78bd66521dcf2a0d29c4ffdc02b2c67cfa3375edfd62d5fa4327b49a70c7db1cbd3ac0f579cbecec55b183805ae9ba5df84867abe0b138a671596552bee0490ba102a45be42aca447ea5f78c2676b7c6a98969b23f4d3741bdce2a3103dad0f4ab4c83b61d68cb2cbb5339d8b7b20a58a4cbbd136deef667c33eb3f4f99cf520dad6c213c3875a05b9a9b8d30d3013cda0a510a320a46dc23d62fd227202368fb9f9c968c28465fac41d02a7a9c22a8e2765fc58ed952aaab8a08bcdd7b92f005b2cf3e1fd70fc0a05de96271f52f4ddf1a029146633de3e2265c05e4af88185b35d1875f006eb426209d97fd179df4516dd06d94f1fd61f869bb1dc18670eaf84e246ecdcf1b22e3e4cfedaf6a405faaf1efd7b0e82928f39c316b425b0c9e88aedec386a48ef9c14b413e4f08bb15ec8a6bef2a29c4fbff619a8182e1a34a69c55ce4bff16a4412848bc943122fde32c08caa860c51403e2855a840aa1fc70d072203edf4bbc73f1e1eb9804efd0ff09f91aa34a770fcc4d377036762ad0d70f1cb0711bcb728270ab7b8d1120255889b0603a6670b5cc551d71a8269a8f071338d7dccb578aab46b8e97f053eccb7d2666acee55f0d1b72f46fd9c330ab1b97f05862425372ed7ef6ee34a1b3ef7fc0d172d5e63c014406e8b02d002051091be2371655a2d05420194dc096f58903c28c84c534d9c2b03ad0b207b4d8df97c7cc9c3b4e36afcad43d60ebc6c47ee00e06f26b2983988d2b6ceeba7a1d96f09c734b5b6da11de6e4fc243c8e85d7a6064bce1ebba14f84baaf6d7cfd7bdc8273be7ce88aafb0c96d6f73ac4f996bbb1dc2c2e28b2002e1ba2c8b130a837ce6cde514eda6b58533ba971730ab2f913dc3e41c061afe4076fbdd9e3bbb0503f0b1c7bffdb7059250fc4e8847de73112b9c6ac9fe998c113906dc56dadc555e72ce6d06d937e338ca9f188c6a7be286f00bf4c23e4c263e9f72c0f1290d28e9c9655c38a3a3d3f8f2729060654f3b131d54acbafb84a7fb70077d79905c223eb3e93d559df8ebf2425cadb5dc58b3f911fbf9107bd6bad2c4f231d7022e1bc532b729d1ff062a4590110be71a4bc4a79f876f97a5a7f2eb1b41ef0fe66442b882f9300cd292ce071f39ef337af58e3ef0be28dd47df3b1285ef4d688fc0495592c4e0a0a5074bee190e84eff1834126b75d6bb970049099c99f25e00454395058f94e3e55f39d05ce4be3354c005ea48e7f1573e65f3f9cf4e5bbdd46dd1de0a8b49f6daa1bb019552344bc5b6a6689156065a204e70467fac54031b951ae81d0529fdbb53a2860a71150cfb88124981d4c3be2346f4b332b4439ce9ea3d6b3756cc43a746045bf1b1327fb9a1986df82f9af9d34d1c6750c7211f0cddfcc9ea88ecc703bc54b1fdba15b7e2c8984d5f01811bfc1da5e3acbd9c6911b45855313a5ad7e2f0b37ae3758e6527eaae749cd69e5cc4c5b4ac4bb4c4dadf02430d208ed71c99626f0dbb45f0d548f759b84b710eb3469ba7205175091d4da132a75bfbeefa080d0ca988bbb79d7d2855f1fe3265a0681939e0839bd8fad9d209e351771aee05fa7a2514e5d965c13bd689c0c30eb47269826ec5ada2cee33c2cc9dfd99af82a157808acafe87f34d397973156828543e51d23d797ea68c52c5f639d862ff26c97c7da369bbe8c6ae87f824aa4a2160e598f7c7a191e2f194af6d2d54c0ead9de2e04c136783e2896695ccf30d11372cd410020d0ef7bd380b44a90d21ae6a39d4ca20623e57d9b2c16b7bc470073098cc00f3da5814d0a3c5255ae73b9aa81456fcb052220411dd5083a0439e739bddbddfa13e5b367602e221940f90370e8aef3f2b814c7c377db2a043865d8e680049a7425b7d4044f77e87b5bfc0332db5c2cd03e0d226bb010f1825f9c1b7001066ab384b09b700b87fea622a130c30e491472137d06ad2b173d12b84c2169166443307589a1b20f28e3212779382dc6dac99c4b884658a6712caba8f1c0d501068764942bc61fb32adffd023b6b850b0b65e7188008585c136c732587b0b4d8875447298a1cd47a0c2a37cf910b0c6c2dabcca19c45b5168a07ce5273f35a96c4cbaa8851a6c73d941d0272fdb83ad9315031af6f7884985fce215c6706f9bd981902339da2f9072447c7547a4e32267be86d4f4f96686e3cc9bc403ad1e58b286e1933466964ea8d61483498bbab5e525238da0d8a273674f407544bd627dce7d04a29e0379038d138a132b37a0be63bbe7672f9669c8571f1ce8dd68860b2ea1d8f3b03bc6e92a5a23036ca65dadd34e2ec55878e3649ee90aa320ad5b8dd4e8a2d5d570f7fcc4fcac89fa1277d1d9076b077c9c2919482203c57e0aa49b91cb9b932f6484ffe180a858e8233455c9ff6744cd99428898bc043c869dc665713faf42130d5c90fb6278a94cee6e7ac205cde5662e8c6fc3f6312660f1c3c6ed37a706766c86ff0dc5fbe7d27aeb5d87a51f9643205b7930f0218cea402c5851b946047be2e6469f27526413d61e036b6d850c71034e6c0268683419d0d2afeb044c7fcd946dca7073b17664954603c626815e38d4707e173795f2bde9cf119d82c8d222f97eb4e392c1af7fb22b1a8836f8c796f4b5fc6cfa3295fda34fdefac9dbe0d78fdaa9de16e65bbe5bb0b1b76799e4e9379639b62f0fb074e191922cc0e1d744ac084d8dd0008c1424e2285064864bc6ad36911abc6683e1b63e04eebbdc2277ee69964556bca9f59003437a1fa0c7027c46b57c1b082920a724a238f64becc2f8a060e695c3105b04e6790532b7b311001e0b15d38d8f4452013065ab7110d1f8473cda2c0ce9b80b52e3170c102d11cd5e476e7efdfd5f8b5ed6edde3ebd8c8004b87d37674a7b2bfce8e813910fa760c1f62fff217483b219378efd3df7dd329fd3523d99f2d296ed09034ec5a4d03744f9eb490d856b064c8bf52ac3f35828d8cbeec5c89a7031a39dd5a1482a8003531aacef72794cc0ceefd3fe70a1cfd6727513c496125eb4fdb1622d5f8b57aec72e6a5dd340046544aa344dddf1dd8a193961e3841f200144c9ec47f3d6bfcf8fb3505e7cc5a67d218245e29ed7d2369a855bf8fdcf4a3a9dbbe7a1ece919a9c9e947a8ad8eb83b25de20f29848ed8a1f525adcba0200db33812b06f0c0eb246e0c20e898ef00e468e9d7f8cc82e5da9f9314eaddf63e73a3073f7c9a81d32ff5813cd070b92922bcae241769a0cd3b09d837408a4bec8ee098410b2dac310211ecfcbe6bda9389315c419ff82df712a17c851c3950f6e08dc4bd5bf33b99f9b2123ca73c06b861288b1a9a7d7108744f366112b9f7bf3ceb8eed18a4a6537d0d72079a0100842161f7f148022b8a298e3871c370a530f78415cbbe63f5cc0525774f0cce8fb7464b107265367270f574ab159281919d358e45caba1f19b9fcc8f0812b92f3ff4741c51d8571275cfe7ec75490971b3d79681b9a165a415fdcfe6b9fb0fbb98615c841d842a7f3ac89a69efff63ab29b276ccd252f4856b1e1b7231352cd110f573380e80c820ed42357011088923162fc84a021e6124efac3b05426f2e8c0b39e201b7d4aae58d6930a2174ff71f0599cbd87c73542d9db3f4afec310086d88601f4d4eb26ba61fa0a7e258486f88ec827d55acd4aa4b773e6902a6b6d81ac088184b5c2ca6b43411a7654988e59bc7793a53425ab15b4dc387ff58d149354e022b00125d85a8a472a9fc23b6a9c2a318bbf74bd81e8e04f13f1eddab8ab8605dbac3909a55f3c10308b30c50d8f6ca89fc0b16aaf933ab9cd7987c39f633c822cce5c0821bb5e88ed28dbc25745d2289c436b60ea52568c9c24ce14cf2927bc9225b53f7c438ffb9fd615f740131e87311ccd7a00d1dbc82bb19b5fdf4183e4e35587e4aecb5155d96a17426f66f16eb160c2d106f3f2453c0b0fab3ac636ccf5936364fb9bfa0eb50c6b94721f301fcee8ccaadc8bbd66b3f3842f6111a3cef56f5f172887e934d1c4fdb324035c33e1660e78921a49d89016317e4c8088ac0187b06714430662552c6765b0b1f2cce2be2ce42067fb6b153970b5d3e1057f05eafa8de234e9e044b0092012b3b24ffda534eb98b1a4b00bca4d8485dc5cb0f0b0c47c1bb5115051a654dd4caf7ada5a4d45b307a87b82064b1d4e307196a22430297987d373ddbbc15e8acc580bc5278d4aacb977e2058c87239d12ac12e37da31aafd8929d979d1b8c49a552df0c42c63096854217a430522b71411a618b8975497c64a2a54fba235336f5e6453f3af50d39a404f7440be279e0329cb3c203c7ec916e39e450efb75f66961b26342a3690bee685694a67d3ed4504fdc231e95ff4483f7bc0a872892ef6dc3d57a4bf6a9154ac9bbe1a318159204a3d378f5f1bf0bf5d4b9647496128c4369dc673279a051844678b1affe38f6fbce5f4260f8ce5ba4e25a944ba836ff54982913b8af9a507255ecda8ea36d6689f24e8ded54bc6c733f9932fdc0da41c1406e976b7e1c20c06c4bceab9c114303a7fc74a5fdbe2a00987b382ba13d3bc4266a80abfe44cafdaf28cee9ebf7d49617f51a95075b5074b918b29565d49f999cb1bcf3876fb4921d2aa881cbfbb0619d59ce33066ffac21d16009b38556ebd1771c2890749587cdf2b1ec6f9fa9a195bdf074a54dd341f6b3e1abedf58d20c8bee841a1a540f28ef47d7940ff9fa9633d7bba566d7bb33329624c6317e6722dcbe230ca2562024a61f97541f294ec9905a2db72aeb30791f08cd84b2b5849de8c59ab583da920a96ea4336e70a88edac0bd2621dd729c6a0d8320a2189ef0a35d2fa0757c81b8c853cd3f0c13409da79b1770bc3fbeae51de82e01e83f27f019d4a6fe015c35d3ddb8dc0089ee7561c261757cf4d338792363f27b57acca01e58a4007419223bcbeec1f695acb8fc0e49070b5a9b53b4133d7062a45f5a92371963f0cf23bfc2cacf40e0484264fcb412919b2b0d7e036ee1219ac29ed36abec241ad80a4befe552bf911ccea824dca043c3863680b95fcfe664672b5887bd5ff5c908663ccc9af3e36c84e982e3e7a2b34ba82140a6cf53902ef56a7a5039ae5d56c42ab733f1acae26b32e128ae3c22440c60f22c431969dcdf1763de399a559203fe9a0a6a9af7a70669cb8a439b8f13f7002a031e744586a34b3d796e1d48bf7ba4a34ba22591fb3f831eaeceba49355281c224db34c08e25248d62d835352632e51550bbb5c695c575eff5083be4ce66c9e6b682b57679cbdc6329cb183d25803afe3fdb5bc344381e11c8642a6a142b1337d19a5fd064d6eee3db0d842d6f8f52715938c37526265015bb120703292e07c628c73e580db55431a8b97844e81179f17ad2ad4b764c1059bb178c111b95a5880ed93310c89d339eff32bd9c3b9ee2bcf069d3be1c8b92c79b3dd5bbc0bb1b62885bbb549cb488bce8c99fbd90e80c300ae343d5ae75b116c42fe33c081f39936784f6cefcc5ed0750880b4170d35f49571f59bfb33ef6ced194040c47c2f6f5d2c99c4ff25e2b8c8e0a049610cf11261b9dbe1b15c8df946a2530655943900acb949cc6654abe792862039a75352279409751bc8ea20a5fe476cd8be0cca35ba29c3da39e44b85301b41c0beb0b06ce9318a6311fd1ff4ec9b83f0f90f652e87e35cd789c0e4377abe063235c576dc8bb79f39f3cc978c67780ed65a6ba17344980d2fc0504814bc70b7a8e0d810fb342e42f5fbf9bacca588ac7fe5c3d15e915929695ad9f2586af9d1c3dabe6e9e471ca2c8bc5f397688ac75a00c068120b890a9cc7ad08d315f71da0c49db07600128ec260e88de39ae3cb35ff9404cc31e9b502ad9762a7c938438be0f685653c7aaa28c45da9fa0a828396dc3ad3215b6001a098c128be168da5ab10cfc752758b2d94b0c8b7d0f50cf47852b01552037bd3848791383e4fc4c15b74ab8edc90461fe85308134fc5df66a597611153d121fd437863316106c7523a3a982a94d3fb05669534593c232639b530756a40eef87a299b749576e8a94e18facd6472e7e318d4f53dab346173df052cef715d1a90872a9ab64398f9badffe000c05ca6bb21319c0b863c076cfbfc133464df3fabc031afeceec8b9c036a78786058e4bf323358235649c156315d9d045ae6d6517b66649260e59ad06289800872a9d58cf601a380afcacb2970c00effa0b091b669eb2679b3f0a9fe1a08ce02c56acb2ca1a58cf360ebb4e3380659f65102c52d8041424676be0f5346b39ab3425622d06f3950867212d7f8b408e263a01353c4d382d9e5cf7244f6632dbc5e3f426fd11531a8d1c9704b1a9883a44403ed4a8865ceed14e80083ced90d057a1401f43f614482518d251dec905d8941bab203f88f36f19dea2b456c6f1a2a8b0c8d8ca10fe0ac4b4c3a5275972056e1fb6f22c3fa4ccad3f8a9824062e89a3729470ef93cc276162252c51c2300e31eb85b3a96bd41da553fae0e3ee09c238a4c09d688df977edffb3c371fea9a5a20e152349ac71aada3fba3c4b067cb4992740e85488b752bf81f0083850c077ce89dd20d1fd60aadf5f9c7d0f1036cc70079448d8c737d680ef438f20eca5f81058f53a1d704ade43a43b60632861740c0706bb6ea6b06d6d2208eff3797926bb40669fac7a3e291ac25de596de02149745db43d9e1072e5a819a295c449f1f4c5a5a90bc688112e1dd38e052af9340103be299b81a330eb9a85c4ae4e5578c530a7ca7829833ad023a3652caa47b7062b38b6eaa837a46cc6184d15b004096748ce8c9e1c49cb313f2df640226b9c38a0467cae0a9f07899eb45ecd349d85249ff6d0f4f16727db4f106845ac977e72b4957d4bac11f3bb49b3ee05f502d04f85ff723af97055c8003e878ff23bd09b12c40e74ca5e46269cfa02299fea64ee37a2e55651015899a782f573d6e5ebbeca6f19ad3bb0dac2f4be741138398dd286a7e662b1506b04d61afd5d0a4b83e23d5a34a4aac772539fa7f28961adb27f4bcb3d620b595c4264db858474e35af47895601adc75ed414b4a77c46ab89fbf2223de75077dec815ee173f6106b0e91f1dcf9c848990d6d99eeb80b19f729acb1d081d4b7db8842c5606de0a13608ee930989af36b216c845f13450c772083ee52ab7a6eedb3ac5233852606dfbac01750182b0bfcac931f7c1ce2141f0746a59331b91067d05be5dcaea36785266b8fd0f9afdbd7e5bb9c71f96c60ca70538038c4aa026d9e8b7eed86b714963dc4f37c93ff734c3f2aee3629792316e20113da51b1cbdbfc78638f73444354eeca6e2e6ff8ff9cd21675713bcccd37f83fc7f4a3ea2e3e73c28d1ece393d5175f7d99c7cc3afb38426eae2fe3027dff07d9ed31a95b931cc851b7f39e334a3eeeead941762270280d3135577bdcdc947d6448664c8fd940aceb922a6c950df7bdfd5361dd833895f93b9e2f36c34c3250c5dd9efc29de13369e87e7ef48022c65cbf3228daf07f1af7cbc44efe3d49acef90d71a3d42b94b035788f0c809ed4769b477874f9842559410ca1f1ce8be435e6bf4d035c8030c5cc86caa0b1a3138a6a197bca41119962109056c193f0c0d2b3cbcff27b4f9f5740e82e8b5840d306087d93b9fc8684a0171bcc1aac4a7dbef8e97710339b9f66616961b57095b02b6887409f16d8601315c38680afc29435266db75e04193e80d859f765d14f55ef2920e281ae5ed2bfd66c27352b888779f6478df62b9dd46ebdc139dc8cb472cbb220ff8b7b9fc6cf2e8d8e1ccbaadd24c1520de6fa5b2fbda0fe2a5389f5ee71ee9556e371b2c3c52dd7877e3077ae3b1cdd65df54ab7c84ede27fc696715c7f5ff4bf19f3a36a6ad671a15de84e1a3f9710b97877dfdc3653d2ecde0c5855d4a9d6fefe7d20200435300e8b1e39419507e2fcda9e112035e53c62ce281fc20a2767593ff14b9534c849e790300eb5046e761d581f174982e526a831ba22ee9af471e521256f4ef3805cba1350530c48bb36e8f0b82e4342b2b9de17718b5cf3e815833ccd8d5e3ab11c335645c3b342d146cb78b33c303781e158561728e431272caa86578165df501d7fd33d14f0407be543c729cba361b15c7e791d49c63d05092ff13e7e709ab016cad681a82f7da007eb5e637b11c430e7990a91ef760a1355ce175bfc76e594787a62ca0f9fc6a52e296cf13477cc5fa2804979620a79a381417f5acf56c759b4143554698a8616cc7009abf216fc0e53957c1aabfd0970164ad0bf8ddee33f9dd193a8c0724ce393d21d2881db93f34b562253d6793cc7ddca1f3ae3420d838d74fab8869d6c94599076e60c2429d46813e38b1fd62b6dfe30199eeb3e3887a398e1558dafd941c221dade4ce72d3fd8b2c07ea44452ba015333fb1185a5c77606773733aa4cfa5b8058235812230ce5106f6f098c59ac5d4b321ae92c852dacbe3c552dccc6cb46363b2f6137693f8b05b1f6862ac7b6ccc947d7f815df492c3f235dbbd329ed984ce076a7112644b16cd81688aa26cae710363e51b3fc9e6fd6cd6f605f54d8577e37b0921fbbde2b5ac0203ffe82a10d290f37fb829f6d111e2d6f9e236eaa726d356f419dd379d1bacb982d4037ed707083e077fe2a04827ef9f6aa50ff3a2b340d70a3baf22eb063bbe06acdd8d2aa0168b7924bf13b75aac4e4237d94207a0905b85177bf8f0e736ad93f21c76ff2881fa9f6e042a7c4728b3af0bfc352662b3cc5a543dfbdc785b5f77762b291c95ca291eb2a9b2cd19a87c302a6322b1747b753f1d0039fed66477dcc7da0015b2e7c16b3135a2ac2963703c4a19c041d626ca77a9a9788b5d255e1705f725f2078f14cb54bae3a80633625114ffc4b912cf3bff7125966c2d92b6da9ca920473e61292c8ffe12132d127036e09b6c165ef10cf79258c0005837c7a40158c2aa18a3ac5d634d0d5023d493e1f83de3a762890d2871f62555a95ee8a290d50ad1664b022a28b0a659c999147e3b33d9cde66702f38ca35a6b415015464eb58747cc5e168713b64b537068908867abf79233214a0f2b3a0b440ebbe37c461571e112650d21848a10aea3041c5d8a8f5a24b11cbbf2a26511bf5306922f315e31deaabb088923fbf0c85e49e5bea53bd552af289a4a499e34bd4288183bd1676970cc7f95ddff2cbb2a40786485a4068730002824ee94c8aca972838cf65e025936764aa4df7dbd0c22c69babf9ee7e979530f22f2f8e36548ec40f762e0382ea0c8129c5d429c1d35b279aa36389b055bada36409736f353abd2bbfd7ff425f21995ae3a8c7bb60ca6e09c7ad7c9f6d9960c9504b858e3c1a8df0968ddd632f54895b8e269fcd51736773fe9e530fd09b1ed3b4cc39597f662f313f75600d868c8271b02f503c5bfd98851210d118a54bb5a6f1139cdcba36b83079cb212a3f269b277aad8048cb195c26261f45e9d0a23055cf7b34c5197eae2235889cf596381b5b3bb8e440eff765079cc26299ca384efeb69fad84e71682c3f128207085ee55b0bb6bfaa999a27895461d824f87514244918d4a0a22f3368d4c55663b13dce125d4960e6fa3a060a2fca7ea8c6e7039e863aec80675ebcb29739f1456a128ca9f59f3a0459cdf7ab7cd40569c32c40429177fe73ce512ab61cb26de9283ac08e102db1c5fa30fd372ef66415784b29177d64aed2d84a383984b12ad8ee970a4a58406db55e5a52b3fff6e30a246572430307dd0f49ce984f77c33c5b918148667a4201c0a58c6798c233dd98847f3321ff317128688c172ea042eae1fd125ff22969a2400ecab696d65b24d2fbc45b0db609ba5f69bac4c1b68764e8f592817571c8301380fc9c9020f528ba8167ee4117c3e33d07d22165ae266a58703faa4454fe92a89e3a996228e8a5b56f302f2c4536b310582b1336746e10a780225116fb9533b8d303693770334857a0bdbf1358c0003d34aec8d1053830ceaece534d49e55108b1d07870fc98896006ea1563080d3542d93818e00d4de105d1493b6395b90a71e25ee6ecfcb7ff208710a1679aa96c04cd45aa3d1172d439c5ead8d4156bc20a17a993e38aa99e59b01989bd9a439e4960e9a6f897109a8b3ea4f6261f38b272242f3a4ef9297ae402328d1570d7e6008b265585fcf5dd23ce06c4c26128472dc5243cb2583648eb7e1740a3b9e9abac0c065ca75631407c315da21d961ae3d51b5335f9fecbf716e288faf17632cc0612cc67218bdd2b145a03dabb92885720dc15a703e2e9be1ec6c506c7811bdcba9b86cb58fe19830def9bcd9ed5646082f999cafb9a96b4003f22cc0b140504150069c1187c5adc450aa25e3c704f1e90f542e25ecac31cc43c615f50db7bef2df796322599024208980892089bf6b5975d1c83eb98748e4c2281dd1f8ce390e9fe2883459c8ad89905e95b4839ec92c9a9d329b73e25b90fa379b5fa76bdd88c9c9dbf70c547eee37de027a9e7e1970504e5378cc312d0c2e22dee23db22a518a6e2db452c73f6da0b97f88c73cddb9cb68c40cd573c6d1379d3533c916f9103a597f85431aff5ab578f5e9f104c78495851c355f2984bcf40e7d23b0fa97422babbdbb15ddf4b0fcc2ff7f1ec552fa45f5de4893c1591e7e355be70896f90415985e0d39c001117e093a83b25d41b3128eb4d95f181017cf2a4741e667e08c22fe93db02f7e2f3698affb78e9cea76b069596f8b421cc4bdfd24b1f2b51bbc28cdf0129d92467030a8a84f48df3c6c1a8ccdb37eac9efbc21299ea538ebc36641f97efa8531e48cf0e9721efe9dcb4fb6916e5b8923529ebaf49a9564c90f500f01b9d121c283a4c5a4099fa06c29de8f293a77c26ba690de46b6abeb71b9fcc28f8b5d029a27e4b08eda30123ce24b7ed9ed107dbb39c2cdd93be7ae07097eebbcbdfb42ee7121b9e8112548753de4d6799b73b4ebd13c0c8e3ecedba75d45328d5c5d0ff935cf172a111f03fa79e662978094df8da10bd0be23621b61b03df3fac8c6b46f326e5c3945d2a5631e120649f072639a0935ec22df1e7691e6e193113eed4c714c876cf10735faaa04608b551ca14647c203046af490a224ebb06de1f6ec2bd993406ccb4da8e01c2327a99c2446e62f2cc1c7af8a6d4b4c858a611886f11732f1bbed9ab3a739163a16449ba2d491819823fae69873925ad1b0e42b47c63c8846d10c441da59823926af78595fb9a78294e1b0596a28e1f628e1892220df9c3526c168525f894cf0891d70106e30f51c76e8eace3227506438ebe79fc8c83cabe0c7afccb27ed0b91881fae132746947e3af7d2a3fef4faf303a23ff9f9a8f162c2ce19707e594e8838c9f92d458e460c450d17c94be77e0802012396ce430f7ca2cef916e290332f59dd7407336f2f2f7d24bd8f11833186f465496ab189e024c8909325aaf45e9619987ce419a64cbf5d30940b6062e737536c3747e48f5fc89827412f8c3b1b12f498de5f0f153fb8e3e1e32fae999bb16b01b34b800f2c76139b8d945f5c708ee0e7b3a7042391637a12a47fc9c4b30d1fd1630c3da73e7e485c9e04fdb831f36794be4d4e09fecbb76519578a8ee8bf56f249f2105e2e397769248d7ca446a7be65add5e051e3e977448da74b3381a87f44047aff23a2cf53cfb88d3ab8898dd37cebae299a3285dbe99ab797b9e649ed0b63d72d6076017ebb1ed3437e1e0578fa1d5180a7be556e9766a3c62d451ee057e27aeca3d7d7637e92562c7aa8b8f9cb5f7a5cd33bbcc238c6d3313ec32e9f4ebd94bfbef8f3a329fb3eb018abd08229377a297dc4450d31cad5eeeea5c12eec628e5dd4d01fc3463830c7bc048e4830d7755d980c37f145346c81470c92247f0ba5865c6466af3f5102d5b0beac2439e79451881783d7b52392a186a4bf487c0a39183eb15fee25f0f23ae29367430a0c8b118bd7151bc528f710cdb10bfb8ac09c3dc4be17fee894df88524a29a5946619c74ce6c89c335c31d4d07fca12c817b3cc199a08f91c829e90a109198cfcc6cbb14565d0b1fd541bb62dc935c303c0504914905bd4b0fe9c4eaace045319a586fddc4b13dd029b932f7e67a8d1b7724d426e886dbb18862a5d3a558018867ee2f4e28b8d9c5f11d3d9c3f9bdd8f037a4378e210a31305223c71f5006f77228d4658d21e8433a06a09eba2c321069320889427d3a0623cfdd8efda273367250a72f65b06db0b1ea5992a48cccc675be456f761207d88f5f545da1f2b58dd27c1b95e2b1d57a54643db30fc7e8e261257948ba3c3ac9bbdcc5bb68b7234a1c15f781238b4f1c92cdd9639216678f3cb1089f549c6f184572f698c3a819323ca42817cf7c86179bb4899de4459f54a35c3efe5a3c15155f5971ee76acf8363f12acbe73ffd87923df8e870fd148c559bc905bafe2ade23ef48a3762905554be1a7d1a45c49ad869ad5acab771ae753b3891dbc841dd80e83ed91ee36fec3a04f8d8c211739167bf986350c31e7af878ec04f8f4d2bedf3efb1b824ff3930c3280b2d7d10f93975f486a568d98eb01d3a086dcaff34b2810a1b53d30a59c8d47490735fa7aa981d85807cb27ba1f2777c93929a51c0d1b52d4a731f3309a6599f3d8cfdcc7532f7422c2fa33f3a8f3e53e3e5eedb3bb1dfd91c25df2b1c3b065ad36b173ad510ba655c221a1ecfc3183fcce9b5f974e18257ddac4ad4665415950abd56a6543da8d164489f6e3d93b1be253e6ecd4432d05438a4b3e3a5542a2f4abd28784f9481ffe49f2d37d0541db9453a8b17565415a8a94057916b42e9305354afab0897dba3b89546b16f42c7fe49084d2239d3cfbf513ca26231b2da858928f2e7b3eb2588a3a6515eacce153e83a7c0aa5cfb373286f903c8ef4e193ff9081202b25312887c53f4088b41a0aced218591a7e5e159b283a1bd34fec86feca5f0608116e921e708099376f18e45285c1c6b087d8af738c9a38ab6342b139d8d7e3546d0c3be3a0de70a30a432a7d385553e54d1e06d9316f1a61909d6aae8494a42e121e58c527c651f275f679339d4464b41fcf8f74e3d89c5010f98d0214391f4e950ae8bd31af7aa3a68a4dec94d64a22b987e0dbf8512786617eb163dd02dab14f89f893c1975f11d37918b535ec1b4f1997a748d399e1c4b6a5b0037af61c29993ccf1c1b52d479f321a54c1ac9bc64cfaee1d1f3fc42b0bfd06523e978d0975fb31864e71cd430feb09e7f280523e84ffa28eb2e8dbb909ab62fd8bb39da374a20fca5f7764057ceb6a56c4cd2f33b427ea4daa79918e1c1b961280cd44a426407c88ff8137d584672703668a12869bd0c0e27f3267a6c2521b22381c81f3f3e2c239dd338415094b4564438080369f28384f53a31c703547b92510c8649333d30c85f29e2001904d989e66199e6ebdea8254b732dcd3c82854adb6c654030e362272b66d39b3d0cf235a53791b07a6090594f6634d6c57d83cda5738c91c4261e03376f1d975b24bfe63804e4a42ccba4f327836a0f6b7c98d5f85d9474d4f4903429955f7fdb32cfa69f91428d1eca00b10bb62d852b40cf2ef3363c463595e72ab2151c74407145a7038a1b735d5dc3cf6f05280735c0c8d046b5a4f1ebd393dd05e891630c48325a7ed4ad09d5ce0e36aa3f18bea2863dc03cebd0b134fbac1a7926ab51574fa3b049c5872696d5d7605ea54e224d6f6b7afa4c9f2e54966a45f5e15cedfcced5c6b0afb4239b4a3bb2a9da09957ad1a4d019e4d943965f1f915456c70c5297c69b380c72c4a2264413a2ed306adef0545d7ee5509c9b2051c78402062d50adba4861f1d835b18b50293b6d3af1a5f540b2b4a02a2447674651835f9f33581df326e6e0949d464d9c98b2f33c71528ca418e15398828467674f41c2d26c147a80a305615fd68333854c9d99336fa28e3983598319c5c499389fc2923d6049c58e87fc76c9b3344024151fc64ed49a37d39500835f9f50ac8ecc48ccc1aa8e8b2a3aa00bbb7e7c9825e1c97abc0c0983ec9991a8233bb239d8b9099912faf3a9d1c38cc5926a17ac9e7d8bdc854d5f973ba4b021bb9bed20bfcc67a535f30fb62d85292a99dffe890fb35f6da63e9c2a2d634d554a8c21c89cc37e5d720cca5a9fe052fbbc7c7b40f1c026761ec610f2f057047b887f37b30ab62d8522c9c9221f7247acc8963c7b6554d662137b5f81a3fa306bc2376169f659d40467ad15a5b2259992ac952179f60c49d6c3a84cc966adac256a65a1e3aca5894bd3d28a23eff193adcbafe6586290dd478c2ab94f1d3ee2f3bcce62d470933cd71d3b7a04be7d08e75bdc3a91afcf9d46f9d026f69ed52e61fdfad4591accd74369c5afcb565c6d56f85083e2997df5e1f4590151c35f9fa66212378666aeedc650afe1f75b17793493dd4525b5893967f7e1872010c0176f7373faf84081e3ca16cd4f8798d8a4b6c7fe70808ce3336f17944c64def4993f4e386f02b529680e3168fafe661329542f87e7bce91357cf1e4e1f3eb5a3e6cff4993ecf2ebb1ea3efafd43983ecd8657a6fa64d4364649e5df3c99882c1b1657e8b5dfc1c0635a48c22b1899d4764a8a31f45b13432bd53a36f0fa9d1972606c7ef7ab6fd2d112ffd27003e2e4dec8edd1e9b0735fa0e6af41652a3b70e6af4ce418dde38a8d177694aad53a3b714357adfa0726d831abd736af4ae418dde34a8d17b062d2f32b8c67199f1b451a4360da96dea826b19cce076695c7e7da3c175142f5cc70086dba5a981eb1b1bb886410dae5fe05c43e171adfab876810dae5b50e29a0537b80e7203b71b0e5caf00e47669fac431c832df4faa896b15e4c0750a00c035101c5c3f11c3b5133a708d821c5cff90e1fa043b70ebdb0e32397488c101801c4c200e37dc28d9f83caf61430d302f3466905c64b48c5856546a8aa89311edc6c8af190bdaf8a7720cfa1067bca2cb3865f4dd31659c317edc9308b4bbb106db2ecdc0b6c130b854ca392f8c12d9a1ed6c8cb6752352ed68e7fcfaeafcfae2dcecc0913f551b43aa9b0a573b760ea3b2932a832b1d73652feee031b2a308cb901165f0f3174660f52ca3518b0c971617179711cb8a8b8b4a757171497111752e2e2e38685a58df52ba5bca962ddb433016f1d1c9b6a5d42899723bba3bf52bf33b34e54525a783f1500d2ba51cdde9a11a52cad56ec4ce69a8d139302a2da580291d8faa92b2c2ccdddcdc0c6e2c2b2c2a952545d4b1b0b0b05c73864a94cd8b4bd67b092bf9f191f3c2a8a4b439d2bd309ad16c5dd37438c971cd91d336ae134991a8398a96eb36855b4f244444e4d7537684fc7af5422ef2eb2aab15b9b2d21c57184a1094a12028fc03a80749921e244a5aac9b981371e24d54f18f935fef263f508088e061a68799e76191c844e66e29e725afab395e91bba59cf3c2248635474cceebc2304a334d6a5a73d4309a659ab66d5c27bbae3976dac6715d2712a554596b73ac51c4c594142e5615152eaeb0481696e6c8b2c2ed7a21dd96166e65c8e0d6852449a4e64852d956b8c82259b8e88d3a1ef1e9888b5e4bc72372d193d1f1885f5da48b4b7374a1a2179b162a7ab1093df0dc62705d8728366ea21280bb2895388a942586c25c86e620148141afb05872ada24eab07d70a0b70469231140135d67ea1094f682c2bcc9481cb305751094796305758d0b021056d820d20ab0f5c821971cfa89936358886192da333d8ccb7571f8cc01949c6400409ae8f1cd280be3b9c47e6ca0c3308f308d6851e5309479430351d6e2f7e7b840ba8572f5dfe1503466d79e921f8b389bc6c212f3be765df749097fde3a5f310a39ad5a6a0af3d34a2574f9ad55ccfa5d817ca7cd0873548c65f5f0361b0bdaf1076905e71108abca05717d0b5c3015dab3484d7916b057405e13a0243e45a459dd6ceb5e2802ea72d2a0ea34ac91c879a152f35d64bc95b54d9da12fef250b3e2dbb90342e6e75737a6bd4d502312714702e8949f9cd7f519a1b1f60b4d70e225fdede4b7018dd5a2ea2b24f9d8285a3323c30383d1a1e874210428597879610d539a9155fa5231fcf5d25d4ad91ffb7c505a2f5da2a0463af7d2496c44a9f90bb2d92bc990d2f8654131c25fbf2c282ba985120c0f173f25ce62065c705e18cd4e1cf8387e7108f15d5a70fa8243883c2f4da0f3609c08ce23c15c60f346a40c7cfbc888cce32ae55ad2d9eea38b92055ab0911223095190742105173710c30e74d0c045931b4421df0435b42e6527d1df8ee34fdc2ccc6c17cc10852840b0220a8b09353b64a4870a57e0410bc420869a08468d340221394c07829e10841c2b5e9002c24b17524e60324512a6a0420b32745a4051b224a5515a4ca16b54f7cbea42c8c5c5f621a7056bd3a24816805fd6132e9ef82449f9653d696dd7830caa0f4de253b856d860f814e3c6711ee3b6f976dcb665decbb615113df3c87a995fd613d55fce719cf3c09c731fdf4db93f4e10c7a8a1ee04adc93cac33d795c582eca1bc7adac4a78b0983ecf5176b62a75e8fb13bb05f3d7d31e153035d59eca9bb58b0a7bfc09ec04eb0a80b682f6c053bae9fd4cf730c40287f3c4b279e5da658008cc90093e2d97b8c0884a5a2fc168bd1c63fca060a921d78e915a851195dc274753614c863c43a1b99798fb1a8ccd9fb8b1d42ca302cc6cc03426be8b7cd506a740ed2407896a6bde7a695c3ed43a49b1409675abe811a85a5b0548a87c86a14939e9f969367cfa0f80c35e1169692daf60332d0b8b67085966b0b1c90424f17a690841d0cc142004bd01942162a38830d722086912a46926b0b44ea0d442c860a54c4a841ca2f2b0a11b42f6ce1e5a1f4707ac81e5e97947332d7366d26db63da26f68a50a27f4808306ff9ed8490a954fded8488a9540de6d113fd76422caba6fb4601739018292d8cc682ee70e205562c015212031e2c17a99e27b808834815573cd9c2682cbe2b091cac60b5051cb8e0070e1186c03042135320398218617c218c2de49210490c544e6cb1faf1c4c711849a5091832a7462e0f384a9a4bb228c2984c17a0ce33344b141b6048e61ac8658fc45a9f433d7162980f1a589251e2ec7300cc34aec44b21733304ea2fc80f2c587a53058a8e2f051a8def051a8def82854fa28546d7ccf42d5fb28549d856a8d8fbe651c0b551b3efaa6712c546be058a8c23c0bd5978fbe751c0b551a1f7d13712c5467702c54492c545d64acd00f29dd968fbeb1702c54471f7d1b712c54593efad6c2b1505df9e89b0c8e85aaca47df5c3816aaf5a36f248e856aca47df66702c54451f7da3c1b150ed3efaf6c2b150e53efa06c3b150ddb48fbed9c0b150cd3efa568363a1ea1c0b558c63a17a7136b8127783bb81c3010750d47e9a4e5f7a2c424a2925f8fdd10bc8d659ccd4c6ec6a63a4b075186a7466dedde541e3e30ab8daa81fd4d7b7519793cfdac3e1861ba5cfbc6d53ed1cb0353488daf86d72ed7deffd8671edf96f946bafc66fd9a671edd5f030bf715c7b2fbf755c7b347e1371edcdf82d856b8ff45be5da73f94d856b4fc6b73ce69da003a9d4b6b77436f4a337fa6dc4b5c7f25b0bd7deca6f32b8f6547e73e1daabbf91b8f6527e9bc1b527fa8d06d75ef7db0bd71ef71b0cd7def65b0d5c7bda6f3670ed65bfd5e0daa3bf39d71ef69bb77d5c7bf3371b5c7bf2b712d75eff76836b8f7f03b9f6620e2650d45f0de66d8d00e40499f2c9f4cb22c9970c86007f00fe5e4086524ab922e805b44b8f3de30635afecee6ed92690b1a3eceed28e72cad41659f22023d3c3cc0c7797f8d431ceef27738a5a481629637dc9b9bbbbbbbbbb1b45325d19b3d544d74d7dd9f562b7720f3513ada4e535b10bdbddf52236ab95b070edc5bcae954d53e1364eead46ec7fa6a41d4553a74edfcee882e2d7429dcd0ee6e73432a2b2ca396955ecc295d42d0ba564c589731d44cb4929673a89968252db7296ca1466f297ab19118204d1a33f885468461a2f5abe465616c64116577a1019212fef8f1ebd6c6043118f47cf53ccfd5547c38a35b7e7995d2d58758aa25e3b32006af80a5b690c5ad7b55648c49b3f234b2582c5693b29634a2926d649655a716e16b35812e9d098551d98f6bd53ad7aa75ae55eb5cabd6b956ad73ad5a274b5d2b9d6c3599c0a29493524ad9952e89d4bb88b0690767e7d91ba7731875f15c4564111cda0487d552e07a75a47691e6e915837ca5bae7cd4834e3ae32259db53a5365acec86519c90ac75b1b2d6c5ca5a172b6b5dac8bc5e9d455a6e25a4e8c18e4ddeeeedeca204b1f06eb6c72e13861d49c43507ecca1203f7eaf9b20bf57ce75d338134867551fb60ee927501674a414b62d85d7aa8fd4107c062f0edae121465d3f7a05f60a047b0582517e57aa57d373d2b56a283215ccd64f5caa4e7dd8aa4b357d1804a333980c06938181818129952825d52a5ba6c9b6a5ed2315a6837c08d311457d817cd83b171236f14fe578065d3b4b338718953161f2f3e3c409948b857d99eae2b98c5c4598bd77f844845117928b35ba58ffa46617cf2ab40e8c4fabf887510c854dec3ce4a47f4071f2a10ffd32941f431f7a07e156fc18080caed21eb58ece0603c35b8c71376e5cf0617eba967909c4c1a7ae749b9e0d36f2336fc8e6d4db36eff284f086980010347e7e0c00c346a621611ef3789861f0fa64b607e5039b78477f3dc0f8e4b8528a5e340fdc2edc67f27094a9305c15352c3da3604862a892053230ac8659c2bc3019584b13357a585242128c34126af42354c14385118a3082f97ed2456a7422d4e843a8d15708bdaad18300849ea246dfa58111491919b451588eb870fa163129831b8589c02d171b02c46bb6a69bd1219bf7934be6af0f863444f37e429a6193ec21f3f22b3148b967b0e144559bd6732cd5a19bc3dddd25916a0de97afcdcbf6b7c1138e40260e67099302fa840ea051cb41deb9bc5163734d08dadb4cb55920e751bdd5582c17a927ca782ed524a2f42b697d2a3e2956b2ad4e824d40696951a462cdd8ef58519bdecb67034bc98f1924472d9a53143c60b8d96ee9770777761b816aa3538606b264f9bbcc0520357c30615af81533d6e8cac299fc74955fd80fc7a7fa17636382f2ebf2857fab0bbbb6fecde9035ed8e12eeee6e28e966e2289f4297bede1ebd0849e3c6ec7701e0032c8dd5038df5eb39e8bc4f00709d458ddebbbbbb1bd3524a1c5c27a9b1f6f0b0509d1fa350a3cb8f26a8f2a3b350eda74215aafc5d861a9d856a7c16aa7b757bc1851d521d6238870e51861341ebd7db0be7eadbdb0b357a46e4e876ac77941a3da4fb75a42b23eac106aaac519a4ea3b61c72f8ecf08595cf912f88542b9648156bb6a8c5ad615161dc55743a7c87030a24599d0e058a54c8c104b6626738644bb990293acf1448b23220d905d9ba894cb51e9a1b7e473d53d528d22e4947666786a5f5342a881eade78360905d63a231e153a87141e3c2f6cc1e1914449bd829ad954472eff92036a6cf509bb8b157c4d5b63e5c22d0b03e9c5ac2c6c5dddd8ecca7f6fdbadc0bc64b7c0aa610fc50b2542c954aa552c59e565ce2d384c562b1582c954aa592aa96aa1a35dcf3becf868dd20d79e34673bca142c436d9a09892404040aca8024152c4893a7127f2b0582c96647d9265e3b3512addb871c30d388012049b235865e84c65917a1ca88900417712a956daa4029136c8534028c9251026812e0934259094402d814a91fd0621371079761c7670e0a28784043b1e2017bd7016797653c723fe5c6d0e32871c9a630efd4d1d06d953de86149575da1482a46ce67959be4b26f78554fbc25ae91792ae2ff439755eb6c09fad1b2d37887abb7b33dcc07538b4fc7a76a53030a4bd18400d6d94acb1e602325af47c480283e7434a82f1d20b03a8ef0bc1a99acc6ba33467cd03426b9c98a9a10e84d66459a6791d7871c0d664cec4b26a42403d049907e0652ae5fb42c497fd1890f1c5d249d4c11ce3a92512fc49f00fa1be6d37a921155af20c86ce3383b1f3ccdedde5bb3443a887edd8e5854bbcbc1cfbf6baae2292e0971f7516ea3e305d49836df72465b30ef67077bf0824e227c1ece1462f095e977277bdc328632af5ebdb4f4c317d376e6b6e0acca787848d7c0cf3e28273045372d3434288989abe5dedd126fbde9ac96d5e5cd3e593fb2e95bf38b05ddcf45e1ad039e743741fe7436c6e238a7ac40665be4dde478d8f1a967e04f5cdfbb139e7fde07cdba4d9d0a9476cd06fcdee0e1c5d02e1e37b6bb6127cbbf466d723004f7ddb4e947d3ef016e9755ddddce64d413d09fa91d218339e566cdba829e414974b0f89cb6de6f30b11535f5c53068221804c71c4cc1791443f75f07dd464be65de8fcc9d98e288994fa277b818081fcff1670d4b14d14f097e2ca6529894986f93935360b271a8d8368cb8adb1c2d8bea286fea58f356a2ccdfeb53b77701ee8e16030967eaa64302ced931ad6178525f80f0783b1047e80a5b17aa0490f412b6c26ac11d7aa4a2fc90f89f9d23d69c44b7fbb7a8df51caf2ab690524a29998b326564a17acd116b51a3cbcfeac6ac5329b9a59452ba77493977a51c8d5866badb8badb7a8d1574a2925e51929657e7b0bb973ce4ceeee6aff81142d542d2d2d2d2d2d1bca8026f74833aaca12eb63519ab3f6c5953664f3304ac1791865d079185df0220fe313a28f765fe53ed2f6691f187de26a63ce1057d343975f8ab5ac0ef9d4bb00113e46c42e034dfaa9270403865842bd1f7c5804301884eca19b9969fe9650827efb08f6d45f120c720716e8bb9b6a22ec9bd80a6dc966a864ded2c86d94063e142df63aa53332266f462665cc28764dce14ca6834d33c52a5d8356546ea91e1ccafcb84a0f73b43259371d526765a4958f4892b1ce617e2b06a5408d6c4de044f0a580d454f9c41d4b9e2d9b56f8933d418b3a5d938daf2292d94650cedee526c77776977376d537777a6615768147bda28daa6f5662dc668927209b92de3a6adcc71428dde04dadddd536e8c1c8d5846edc5136a777793bcc6136af4eeeecdbe3d3ef542e4eee49cdccb243fb0891dc9b6615ef6f4b1bf7ebee4975e97619768ab1bf66172d3da43eeb3ef0724cffe43f6dba8ccc30aa546baae71ad69a5d1a45c1d1931dab6bfea28e3b80e891ded93042e3629bdbcfe2b0804743be4f78318b5bbbe8b76526ef77cea5adc11114c2fcc209fa0161f61420ae2125850a07c9a41729111a5942d239615959a22eab84dcb28764dd91c67906888d8c3e6e8a3b9c3d9289f68cca0d66f5b0ae5138d76b92636d724bb971e3dd0d9509a691b2712a55415160ef3b68665d4e2e2429a0133e38586a83fb931d2a5c7114cd9ccb1bdd02ba59472e79cf39b73cec92365fb9c2ca5cb19a5942cd8b614a6ac4840c8d917b394715e1eb7b5175f461f12c8dbf0598b0b3a713139e79c72a7cef3d4994218a511d176b49d94d510b6066266e6d5afaf63ebd7fa5c971d2e103750267ab129024342e4963bfd92524a29a39452ca29e592df6c08a99d6c5681c5920b88c6d278346d0a3c8dd25a6deae193e6c3e014f6a48561419ec7b06e07037e6c3c3ccd9da0359b6bad46694d341f3e69415a18f6a4a5a1e5c527d6c4ae79d105bb03bbd6a451326d0ae2534c3198863dc5132cc8ce325a937813a378f6a86201b0cb346adba478f6e88298dab66de354c754a336536743638a4f7417949b0784d66c1e5db0a827b60b9b9796c9cc2a9e8178353c89d498578197de294290d9529cb2da52cf7e9560d6944645e496f3c268a66d5c274aa92a2b2ca316192ea419345e607c74527205f3426306c945860d35607466cb4746cb886565b69e5da586bf304ba7552a3545a4f3ec9d7b34a2ea026256c76d9af7cd882a8c05a46518ebd9e9678314bfa84355364a2e4f7d3e5bf93c3bd601d1f2d90a0d25196f438aba1b83861b2d9fb5a460c695aba5a9e197a3b7cfd4d2486f2108e1fb635fb992aba5b1414a395361d89868b592ca1e3b4a654e02dbf2f269e3a8390c557af4f66c7072fe8b20a18f69b362f86b7e2f36718cbfbef0fa6c70726c7072bec8d9d85d179faecf062787c17d1b9c9b57b10dcecddbe008b92e1b1c1d06f76d707618dc979c0d8e901c46d5b7c1c1e153fc8f3638377cf2f1fbdcedaad8e0a818dc0fab0d0e8e0dce0d838bcd605b1e6a7de9c590f482097313c3482cc9717cdaf99118f4c1f592cabe91443a4d06b9a824cad5e7747e59519c44d9c2ab8256bf2c2f9c4461c2d58429a5d740a8eceede39e79c3d243bb5313b7bca39274b29574a1e2edf73065c04b2344a184562e23fa013999ee98f526e76f2ec8d85ca7fc518a713465d3a6d6ac2a7e93b2b1ed6d5c3ad9ce76b49cb5f4b1a95edb0893ddb59f2d94e4aa751db109bd87fec0459ddf0e43c0b7976222dfeb6a030767e7a327e3914e6ebdce293cbc5844d4bb225cf9e654418d5398d43b9c5275273e456bf36a8922bbad798b5a6c8a6146bcaa705d79bbfa8d1e9259a2ea5fc62f7f9d4dd3db4f3abb5a24b81851acec8c002a05ba874ccf735452e15cd0000004040008315002028140c8904a3d158220cc30f14000c7fa04a5e4a9e4b234912c3288861180621428c310410800c02244684510ab0cd44bcb75494e6a395323f1242c8d26ea29b70a0c2de50d2c2451415b9436ba1c4ff149fc08987f1c758633c93341aeb21c2a3dc5602a2f67bf5c9cc85b071a453bd802191c9eec3e5ab20e1c760bc5d921fba6cd754c0119fadb44bf87f17e0409731985af1f9ca06fc5ea0f7713aecf3f8d1bd8db12002e628381f4636a93e605fdbb45b749efc4f5d487db562ff2745b19e946331e34a2a907b7ac1f29013eeef62dcbf3151f36f912e20aa5e785a016af046d5d7f226dc209b1d7974b61492a1c53c57d56134cad353085de2b1118884fe9839aa32f61c1d76dd12c6464f21a5a7fb4a45f9c96de13e7728e0f11804096954e5d41d3a07f0bfd96e6e946946f188438ddf2e9ecb0c612037bf4a75e207b992277418640fa738130e52645381db1ce285d699f865f8f00e6c43423c41d2e186fca1de0f11a87718075bc90076d0e22e2ad98cba4f0b7d7dfa6a91ce3a06b24eba040b83a8af0aad2d830f77565a044b63c65ce71f82e3e69821acd613497f32be6a1cb5210e2d7e1387060902e915846c32759c16cb06fade99ce7e031edf366f9316338814b9c27ba9f723607fffdb20d7697e806d03ff8281bf6e17667e0e326ebc784471c9162c13af3e7018b69b6855ed0010356c0ef32c606299fc62a456ee31dc6325199eb0a096cc031e4501dc42a8393a9e53019550eb00bff222475e1f7d8a2933ded122d671aabd2af02062be6a23eacc0e35bb67707c42746240d8e69062120a490556fa5d1dc77827b0f3e764b969cc389915f91aa071f30650155f18444642c3e749857c37cab1e3675f50a85de48104258d2c9b0ba97bb00d894f3dd657f4eae283299614ec45fe4e75eecd88bc9f89a22a750f5b4493d8b7a8f9265d7b5494aae87cf04b597f934a9297b2ba075e9f7c22add728e85c6ead5deb7455ba9c50dfdd2703099e3f1ba7c2bc0e68aec0ca1dce2aa5173c9fa41183eb22e0519523743e24e5d9938bcfe920716d28de2ff9b5a82c6df67e5b446cb52b8815614681401a96c769b05f24a809823411f42eea8f2cc5cee05d3e7c9a9d750991e97c8b0d10e0c06f9e0fc35dd602337e102b91d57f639ab8f5db125692b816afc4b6d57b3603fee17e2ca48160ecf4e9a96b6e54e823fa8da293dea0691557ab1e68e3ab0491f8cc45bdedbadd704aeccc82218b145a97b8244a54af3513c8fbb91ee012d60ae8ff51267cedc61d30389f0fcc14f691efce63ea41b19e0ad61583edda3398d6bad30dd635ce744170e2c1503c9201484edc9d07f5ecdd51bfe7c6e4d3934ccdd829e125f5c48a420c743302dd52372b436b1c25b3a300e9febcad736805179dfbcea107b259bffb0a36ddb77acd4cde707d8d6db7afd7296d7f549280201cdb8f31b982bb302d5d2a7cd61678df09e5563849448741e3800c670eff6fcbc50caae9381186f76357448b30aad2babad6ba030e22ad5c375a1c72d2f6934067a32891c352c1009285b5362035beebdc36c5f1dced529a39f318909ecb978926bb0b87e37751543f914671727259cf340ef4b8a1be4c4c5c06911fbd8b850490367cb4f723a04c62500b76630eb1b1bc4624f343bfc985add251f35d35551372aa9e91e43ad82a0ad1764c2a0db68267d6ae1023f0a24ddb35dd3aa8d22760d856284b592b95f98ebc62483ec421e3b45c00d1076f5a1097efa45da4b7fe61e4fcb362672c8ecd664fc2d10600e4933c65520ecc624421ba5f87406805f596844b8810f87a051b44d7741ee51f03b4fa34f1dcbfdfff601778d553997a900b22419296875fdd2734c7464e12002c39c91035ab1f10e8524ea9c6d974b2d1cc875dc579a7d521b009e9470b28e2fa1e3305184522dcf3c70efd24c1b81caba63e81c066fdd00f68bd7ef57d609d2ee85ed2e208689579668a23a2c6c0d62d0d976f11ca350a74d57045e5dbd81b35d90fc10fca92b6ce38b96d71390ffc6e4073fb5ee22f3bac238a8d4ec520c0c570637d5024742acb1562e100fa7ed96b3b9bd8186ba96ed068cbbfc415aa5f5f13405003de21c1e1f2f003e175231877e910982f2166bec802894176834cc3db0d25a388e4c16473603596314c07ab191dc92f2d7dd070d00119b2ebf1b1721bb7f6b04e50d07ebdd53905694181252501cf1e3db22856cc9315e9bbe574fe839dc105ecb9218516b9685c105e0b31e18070d08c6569339e8239c054200334c219a6420599cb300cecde8f46ae123aaafb47a08c752cdceb03c7f2580cea2cb40fd64c2866b46e798bee9399474ee9588fd0a6b826435e90a2d49d0e522c604025a19cca08e63940f61bb424effa8c1152eacb31f3d84ff1094e29e78572f699485b206135a6ce2969537f238451d28708ab1f349eaccff0eb5b5eb5cc674113fc82c8e6db9979fac6ad477e0d3d9c036b56753f40bcc2b3fe8098844e64dd2ed4a28473ae56edad5627450aaa2464f62da83ca8c3f60a73e5391d4f14e06f076feba0ba4262a89daff6b8ef06ba65e71127dce11d281a5d8f92a6ef78dd181e625022ce9e46f65821d623218e3158e0e2af89c101d73b02a9b300ed9a4f507fb57e798e54680f305f8f120a2373857d98c7346a6bb50bdbb839f0aab8c8c8cdbfcf807eb48dc590fc7abc6e1daadee0b0ed64d6659a0fee01a799d2619fce2f64fe0881b8d93c45e2ed8ff4b55e4a200e97c3983b8c76b239620728b162b6936d12625c26555d51715c18accb8fae0cc40fcc96f4022e0bc369c32a0347660a27521418309e3c4429e431854c3cc54f060594191773627918b9665b973982bc93b98cebc2072d742ecbd6af104321d7b676dab9d8b82dcf3f4404b490a44f36ebb487337389425ff42047e1c72bdc227ff005e7c83dc3727676fb1d2a50d1022b0626969e16373af3107311a69ab722cb1981eb11a1320230e961a098dcd83900ac04514973fa515e1c096c31816fb12942ba88c58a968c6ff900628ad64033e64f4effe9c895579a6c99cb4112fabb4bea3b12689bd481c20aa62065dcdfc0d0519f6214a58dc37edd62058bfedd568b56294afa9d7a3c2b865f2bbc1d4acabe8aa100039b374f3f04fd89c1f15d00b2f552dbde0eecba303b3da2006b1eb28e125338e598f14db68a7e9e1008a9dffb1937d32668f718299e1b33ce8606a19a8d8b51580a0f2090c180b9667b7332236fc0fee0f8b4cb6be1ef2e480cf422a2bdadf45aad820e26b37cb97bdb66fa0165cf69338797228e242a2abe26bff7f135bb60585c233674b00ad3a3a0e07167cd107cbe95d1128f3f0f94fb1a8436049d25f6dcc4b7fc325885ded05d3a37e3110d21c2bf14f86bc4e96cee25fb16ec8867872f12f96f575c8a7d3658a0c110ec61a9dd8cb874c534310bafa274ef15ac5bdb614fb59cf5513641475ea3abea30d8b0a14a4381dd51bbae58c766972989a1b09fe14999bc2c4b2a282235d3f83eda30879d7d368b91586d3847491fcc4ec840b8676627dc0675305a1bf53d1bca8f3ce9079e8871277606eda56b281bb6204f672628ce7075314904737773cdf4eb09c6411509f48a6bafd1fcac97c70cb607e343d392a868e293323828e4c826500ef442bbd03ed21fe56854293ec6c776a27cfea44663ec8bb9f2685847cc959e8dea9fc6550cf949e5f3008696c5f60d77b5cf707ac5bd35013932f4eff05838d58748272fdf3fb988699563cebe43fdccd7500fc21124e66cb4203af4e869649c3c41b23d6c7f553b674b3d274c977599f63922c11dcd21a8f5905247674bd6d9dddc2eb879819a27788e0d30a895909d69eaf784d84fded8e932d41853447d1d3320578d01280d4fd8c276b06b1bb9c27f537ecd87c07f0d2736f0c27730c4dee48feeaebb5eab4ef74bd0973557ec39bd5c3c7d69d1916c7cd3959fc5cb370cefdc70ea097173184fae39ddf9020111abcec2280f51ba0879dd782eea52cf6d6d662a81ef76007a8d2a7e1451f84d781755c6932b1066ff2f9f85fb9481ecbcaea3a5bf9f845d192ec676838ac86ffd4e062c361479a7d5ce99aa3bf026e66a5a6299bd5a39e9820c57be41b9d04ed2bd42bf5e139aef88f9d93483086d85dbc9ec2abe45cc8e18f58959193784920cf7ccd3c079844284082e30da0cc8213244dd5f9644d27a88713481f8f533d309c9abcac91371598e4226a99b8e05cfc165af81e140046592536fc61a51bf76090e26341df987e3c68593b5efb05316a69a49d370274a5d29bc93db1112dc106e01a3f032a105a00fa8a3c14a99504577a182ca48efbf48e6c6006545990344864ecac66a95d075523652ab84d649b2512d24944e940d6a95d0745236d061f24d38c1a0635b39dadec8e43b0c52bd29da5c9dbb9550fb1e641df4338afaa65354956c5be61f8aa03f9b4e78c058137343b1269a116cbb313d6a03e037f4cb9a5b795e33afe4929dd961fc7300489a269c514fb7b262ad3e946edb07fabd5e83ace982ea445966cbd5cc48b3c6032455cd6b894df179dbc7c37f3fcacc443278c231b57987ab8702d7dfa3e50457b4694daba4fd310c83423e83b42a6330eaa88808ae4f6bb41ba9be022d5702a96d1b9c694e8d5d0aa0316780fcf8625e202d38acabfe5ba1526802397f48f8d6992ae167ff680dd3c39b6c74ddc1edd033b8df471e2378d88910dc0b1598ef8c2f46d1be2053610f7cbc48939266666a9fee0d7e95bc32d43fb69bebed33c0d73879e603ef8cb6189a268557ddf11eac865d4d2ae5e7f9d895f5ebfec79ec3786b87738760319b33c1f43b7d1ce6c4009edc5d4169d0a798d0d859fa6e6a8f73c1b2a1a78b1e41013b60561302fb56fbe59eb919a539ab1d72081e2df8ae420ce04151ae4b94b416f57400bc136b73fa9fe52d08e8e028777e4dcdc7ccd71d9567dc6efc6eac232d49f31fc6dc280fbdaf6833f76310b87034339fad45334d8bcbb600cf5c4c7d84d19c9ca39aed488648320b5f325ae4d5a8d79dfe59c96bceb8600cf8e5d0581006d8b551656665cf37e6347dd760276846b6ef3818a5ede8d0568c065e1fcf66832f068dfbc1636bda46d3f0dc39be2419a0110f415603b878b3abbd010e8426fdea51c968e6e3314f6d7f4ee237c18d5ce4f27fd9786729d7dabd1d089392de2f0753b2b6d29d63e8d1b9dd362bfd6a5f8709be8c80b086b675874608a2e884de08379e6e3d733f58e2850fe92219444227cca4cfdc9d8f2061e23428cd12ba4a976e292cf4aa18b25053979fdacf464f743fe355905208259a7678497ee2c64613fe2fe7f613bc2aeeca51176b877d10766a895b5d9ea9a86045f3acf462587a242519ec4096606d3b59b62411be7ca3a49d23c207bf871db38fa7c438bd4ba80edfdfa476aedbbbe16f49807f5efef9d2e05dc515826cdaf8bdbc449a71e6e80880d7b3e5fdfbb63e1f1b7a82c6b3b737ce40fb511f1058fb66f71774412a5b93a7c0eb5d766c5f36c94dd63eb929a5daf2a1345d3154da51521efdcaa24157000d7d151ebd4488da1d1de5e6b9987dd7560fb8d8583bd6a4dc31c41a6538c7c69c49ac8d4f4748640e9b7267d3afdc47d6fa204176d8287383f491a2d7a3e563c9b12ed3989c505fbbfac6f1de4d34cd3ff00b75b68e1fd6049cbb5b176511bcac83b7932c018e3198a47c1a5023e17cbc16c77b6ad3b2db1f413c487345619974028a279b2ee7777b8e9472014b4bb0d3a6c0312b1f73ad94843ffbc255690afd768b4be524fce975b77e4b9c3f43ece17bb09ec88665fa17026add080cbeeafe00bdca2532aefb6a7fc7153b1f8c130e0d0fa282669c653bb28807d3eb2f2488e81b37d03ebbf7e09a934f435a8755b1855d7fce954d2fd771b494fac0f29df6041bd79c75629bd7372a6b6c1eb636aea57c2a2fd18e3b091fb31fc168729647b10fe498a64c94c73a3b187d1ff07dc7bafa73176aac832ea409086e458163831067a75761f2df90cbbeb01e7867d0b9f82a3897fa97a0395d3bd07702ec2727c85b542e223cc074ff78c90a85f854e88942efa312ba2576e7e4059a162d35d4c097f183490995ce5d5b0fb25f6d23a63f88f96d81850397fa43c18d9b425d2ff3327a31c0fb42674314f71f2475553306a4659bd88ae7b4d9e8276710481f3e32744fb5be9f9be2f78015800e33063a3218d7be8e6542839e1827ece5237269dfdf1b413acc2b260eaf8542501609c08a5286fb373d09e0d65250ec20ba005e449aa9da5f12596ecd984cf84e8b40e1f307779edaabec7a1d0a1ac7c4961d35295d9bc0f477c0921ebf537ad5bd055e395d15a1e9efd0e46c8114503be58ff9190046104e492c80aaed452c414e3b5d6234954d1ba5d2e58cdbf323570074251cd8877223c9f3dc2ca156e8d8d5727b14afa90442eaee5c563175fbcc6a115d8edbdbd91765c001f76791f91ce6609638d36589cb888c5726cee6dd721d01600272a4700da80472d0f7b82f7a10c7672e22448f087633971a71c99bf83935f019f3caef3ab0abc0cdb58ad6ef7fda7bcef278cf2f0b1f68fe9c427c4ba5d9cb1e14330b2340d85e31d1964a2b09f371a0f2c2616a490db6ea0ff199e84411a5f71fdda7e91148b7269b842427d718a395b628e4fdbdb80bf2f8dbc7c1a90f19464fa916a86816020d369674c1039199c9e0f0569682602b8526f509f01cc12731b8063905cbd6c72a3bc26bf81cd612a61a434a591b37985dde74ad23e1d4829c0f09a1c9af31f50fb7932ef67de184afc6560c7e2751264999845806f4be6423a504860a5cdc85fb9194b0f1f7b817c67dfd3e0ac39eb8ec9168e75298dc583745436a5626a6e7409348d4283cbacc3ae1ca2fbc4dd8041e39377645914931b7dd7c3ef89971240d11e90dd0316b4c5f619ff299e32e3bca52c0d3be354247750c153aaf59277ddbdc6a4bd488957ac1ceaf0877dffde46ee31f5e197acba8064194abc10580636b6e80dd53405c09fae65c5753efe8c82e471c0d41cfe6efaa9cf0e6c9c6217c7b02ba8a98d8581141d34155c56375fdedf96448e45d11d2af377425b80a6e1685016ea06df2524640a108f632c512c902d9947abfde21c48fb8df6c415af640ef343a03eae88422bc54a767180c6185e00d7961f5ad687c8aa963858cde581078fdbaa690ca99febc210d2e2ca6854b993da2d0175349add81dbaf616d582d53da51cc3986dc3e351f6de7d0d633cb07cf1dc0da6d93705026437f1a964b42eab42f838ae36ae4d6f2d114210f00afe79465f5e48fd545cc57a5a54ae9e30eb548d2a3c266c9837a0e03399595cfdf4bcd3b18d9bf5377853be6425410d3fd930806c68f51b0a888c7b6d853d1eb280af7475b24983f26854cd090cf08c816535557794410e57047690bcf287bd553196dbd98daebff0d48cb81c38b7eca0d9d00221fc05353f433a07a04ee2f3f63f48316b0be1b3425202e3f51ddcb38df6e531c3adc1e15e9cdc619bbf19947cbcb736f04863ed88a8d031acd166cb4268506a90f222f071f5131773d542dc70e94c9f128a9cf3c35c27cd04f5b1424fcf1f36f29969c824600f6086a49a71d7fadba4c839484ba66d9f880170602cb185b68dacbc15b81a376f9b6c97ebd75f26452899aade3541ae39597f07ffc3648986a246a21b43d55c12481939f26905f5b12cc1fa610f09198d53d4d5cbf672f1ff013ecdf7388033b83d1f0942cbb8783acc4047f351b70454a32b958e647e8d15f1f984aba722cb2207bd29332e5d785ad52daea982865156a98f9fa31bed63a37b96045ac95d62cf59cc6507bcd8cb5f191a823cb633ae95754892c6578f3fb933ccb1402bc3dccd42be74c8873ffa9dab1b668597da45912cb9eaadaa239bc63af62e10897ed44205ff00a2b2539b59fea018cd6163ffe5c5b88cd00f477121530f5cff7ee560e4b4c0ec13790a675845ca370be000c76a2f9609b106e453cf7a013989bb6dd7228a2f4470779433360c1b201916741a72bb997b985c58c8d7da98cb5ef02bfc2284e8c89d532153e518a2d05f83c5febb0f9efc79043dba514695c2ddaa57c455dc992e18af6b8a147b5508dc2c44812b9e5219bbabfdce85b139ac1a5d62b98d18b6eff5052ed3a11921e5f40a93c984680e2c5cf19913c73bcaa2bf17568214ec1a8d3dce1e31d29276fa19066de4b5ab9b39e0c24d2bd3ac674d925a718992e05c72ec0a028fb5492816c9c4962b456efa79f3f958eca4c9a291950b5af01d44ef04df23d90a4b8a3e557e0488b9c6c887901b4293bfc51d42c985e5924057d9d6f3001f2e23c18ada11bba573ac9dda1fabf1142bdbc5a94f9beadfc97dadca99096e1b2cb04966f329b7f130a0e46424d89d7e64752e1008ad1a0da6f6eea2337daea8587f40c316e63f060ff7bdcdbef7262e3ff80c0ce12733844089fdd39f06c418745e71974c68e2265c737d3858d924190f616731cc79216ccd456a9b879b8f38f9cde109c0ff8066d6025cb88b7e47c0ce53f6ada887687774da510bdcac2977c1cffd372da84ecf48f090bde346c3e546974c0ba97a604efb92d604d2739a55c6273bbd20acc83767ad56a78e73fe50798a6582427928a2717b1a15f60ce99580647575918515017d7cf9726bd8e76c8892ee9661f7a6d3c36a7052dbe0f2e84b72af16605353db6fddd65221541905b0b285eb5650a724463228835d79e20fc0f0ccd5dfa6d975336007e88048320989265d0cf5e4f63ec7864e33d469e3108aea2eea5d8c2af909d8a2db38133af5a58a0e9d4fba9df1e5ab019973d83110e75b950f05965f3882776ee18b4481fc450f7048ed5d8fcd9ae3d3f80eabdcc6e926f8769b6946487f16b6c8c6b7b3e1ee83d7c4738796c3729b2d1d76a09649db661ae365e73cb7139bf6e15700dcd9e8e5b3c443019c8d1dc17cb9acecc4c7cbce3247d3ff9b72a9a2f6002a3f4bb4a09bbf776f175bdb22758cdf05ead861e6fdba3caeb0b52c907f0bccdeecec02322fc65efde44cfb9ff3a1bff16b73a4e8105cd64b05cabdca601828dd37e095727e1ae1a267a923113d91b6d2e856129a0251d4fa4c965d59153351b123a867d5e74b03a42a28d7719c51b95b82ac427ea0c8f6d7411c834514d61d090f0d5ab38dd005ee2c5c21c76e5e2cec7063bb1f177bad2802f9f370521c43891313a9b057e7235868c8f7f046b284722e3b8c9e2bd3a8afa37ba96f45eab1971afab513723d16012b5540ce09a46391cf062f08df205614dd3216a342cc80c41aa9787346ae8dc990af7e69469e476815a40e5da17e2a73da17612f22d8b941b8af1748004839548114cd16c0dc7ce959d3abb9711d03a274f6eb9d9dc09b30663945401cd3804eb302b934953a5b4628e55df737814fd1acc441939bb1e45ac9d66ec0ad3324d69b6c54f631b0a4b05a167b7ec1398aa6c543fed46891e33f407718c8ed2b6701de9a1e7f104d525440020b5b96f9b01b2a09276fe64ba1ef2a75092f59f1bb430a37373a638c6cd696f0fa20a042d3eaa8af373e4154f1189cfdc2979ef853c1ab5fc60b662a66b3ff06cef3986b88aff7f26558ba7e082c519c078b2013152571f007f814db37e58afac6ee6417d38cfe704dc1688d196c2151e906fa9f5c0693c4bedd0a1569fc40a93b3c1acbc94d6c55d8ffffc5ea180848aa90fc0c7384c3e78b5792660a726c8b663b2b14755cc2f8e00e316e9c98ad3999062c11b8dd45d6c54e3e3bb384bf9ec5f218cf019728b453687199ca6611df011500b9924c0367aabdca798136148dbc2962cb89162b5356399cab49437f56f8886328c55b2763d9bcb5eb680a4bc7b818ee17a275d3170bdc55d8f554ff296961449e5b04688e96697fa5c9ee0ae5f2208753cc715daced55ea320e32b528baa4b4d6f4677906e6aad7f5464d4f5b12f901dfc08f5d4eef11230ca5d637193e372006e0c6307f006ff0cec824d652978482a4ae54529f6927549256251aaf9bab89c31aa0d291e59e459b728144bde1d916fe188985fc8babce3a5efd23e6068d0f339440552a4faf3a4d17f259b5d7476d340a1da467d97b96a7b5bcf9c8eea7b5963f0d05f7bec3756109e2690e96067cec899dd0fb022f5434cf1370c5cd66012732911f24f29790e060d2eb05fa336ae315bb57b5101b7d3be0e28a13f98cce9b60c612adb84ead8823a5963e7168fbb33e69980339695fdda00b28a3858b5fd697112805656eee588ce8d8e62d08e72c3f85ad4469d4850a63fecfca2aada58ec46a68bf2b36b3a503894e6404ec52d8eeca51486becab20a3f134369977c9035044cb9d01a5d2f752999fb5797794f7a8b83319b71449c6e5511d1cca2a4da32857af1afba19994c6c23f1d344e595ed59440889cecf1e52865d7e7b66c9e43847ce19a1f182254db3785334ab70346c8852d03c2ec9258cb5d34fbc10b31605e7a261533cabf921974e1e4cc8caf855cd5161fafc85f03086885bd9b8f5785037b557aac379707f36670a39b68d8d27d38938d2364822db7578b81f65957c6dc66f2c1528092e51b7ec4bdd69878bf843e40432e2317d572b9931b75e76d0a7307252fe97f186f2ce597eec19d3d4361055c93549f7738b1825a1438e83c040fd4939139adf18566f2891d75442abe667f701d1e0fdee7765d757e57f827c65ba698cfc4090dba005673117c9da250409b3f56d9384da5ee655539c0c2487137bbac48098ed49d0672b2e73e37c72a5e31e388d8c9841833262c6cfc4c88f0814e2b424d0396bc76ed7683cc3a8461f053086c334b03000db96a39de560e48e6d7fa616a270b666fb60a88e271146025c91380490286557cf7181481561f4bc831d30e87c0581fb6e8a9fbfe4cc998a127e40c02581e6b84d55c4165c82629c4c8452186cac5273ca13527e3713358d1fe4714fac7a35f63132379bd224f6fdc6a133386a19e7614963713c65622d9b5cbac6ad06a460853fca804d3edf7e27e820ac3b50d954fded30856157a552af142855a528ce4b0536e9b8863cfcfb70b97e856842b6370f2851f84d01b8e4484f83a4098d13494f31d8ad02ab276edb0926167fe9647615e1500a879d9b5329f6dc4e00ee37df4207a4d5b80c8ee5080e177d80a4f8a6bbbe504218dc783c9b38b44a093c43ccea7d9a66058a4b9cab4dc884758b0229328515bc349981f4d1513de354e86be48c592c71904e728cc788e342881ec0bc212b1b9b7bfc046c37f9a2ba48ec462be26fee7b482acea415a30217eacdad90171d72b3dfba825b9a2ba7e35209399e00fd78693cd19b93c4437748307b7a574f055ac36599532047b044c4716d91a5681aef9480ba7649a23ba6379d96d4b6ca69067fa749f3797f1c12f34ae5c3362a23d0ba7a31b138a945e68f702d2d90ad1ccc98eceab8db06352737c6cf8a18763c51b1f65636591c8e9a9739c2fe1ed80694a5ca5f50bbca01ecf24b26ddfbc11c5d05a8f8d3fa9519e8cfea2fc85848923f2fef927ec86b601ca03607154a7bcb99bb1d5a21f1baf38064027f2c2e47aed6a040d4957d8496c0902216b118e8534e89a42f3d544e399809f0561cda3451abebcb3102ba6f5370844cb37616472f3b01e8bea9327b6ea2710f35b840174ad6e0a41df5caff335a3169a8470d979befc31d2afcc416f6c0b0228aec02e43532c88840be00c65e2558ab9a2829a378268e2ca0f5d8f0e11389f616084bf10d91a9009e0eb24716c7d934913543fcfdc09750712668b4e51f981a36cc19b2e13b1605e01a16b2f6b8ba81c324efb2cf74c0f4e08bddac58e08090196197448c06c4e769fa2275050006c39d91134eae9c3c9102fface502fd2ec880318f694753272157d736edc02dfcc125d5b2c6d6192096f30926df75a306dadc9e330a908c7e112d41a583d65f356da2e74d4a4d534b23103f0d9405094d6cf134fcfc6ba89a4fed466f60f3e943ce02797adccd3315b364d757cc9a0b2fb1d8a57fb575a67ca5c27311c95a89bd71de6672c18b88897249f55092c451a5a05703da0d70eaaf26e0262c009200ff200665f836be341a08bc6be912806bd087611594d74adfad301cb32c8742a67acaa43f6fe8e748ec8c9680d7ecad67c5a3a617b3b814fbeb800636e74a648d71bd70f6879583824a5bfa70a3b3578c9e6b4a9f0706aef7b8ecfb404bc5003aac670b1daec31b16abdd62061e87e8fc6360a482ca32ea5e1a015539ba29b1545338a0a8be3a728c363ca2840b7036f2a136279331ba9511a240cff0513c5dd3d7129a728ee5287a2d571071568112641276df7eb926dc3ee3e518918b33793e94726ab16fe433a74b642050c7561c6949b9fde6fa21e7dfb81b181af6e10832eaf4dee43c338e607cf9833ebcfb836d43e18d6f0177d8fd6633383d41b6403ee6ce0eccc033785ddbc89714e2991eb3066dcb6b24e87df4eecf2ed4cd09c354939983574ec41d97f218ab8106e64639f21390bdcd9c8eaa831fc7b5293f1ac2d1e37ddd9156844e1b9bde17bf2cd1911c7a4a15d982011eb263522484efe4fbbda14d7a1d0346e04d758557e7661c277c5ffed557f8284efaf8fdbc41087f326b0130c345d0021fc5c94dfe8e53d2de99385893d1953761c410f3a95599e4a593bec9bc60bf81101570a0efabba303de44c6a79d8338e11f150f65ca18cb09e44005e58f33a2c1e7bd4ed94f4ce0ee6e017da0c93b01638ae3895b2c58ec76d81e48e966fd5802f7ef3ca101f327bedff5b05879a1a20ce9cfb7db0a7c6caae100475cc8c2831179111c5fa7a29b310f85cbf80ecd9f2f4162864471122ba5d0741c3aa0d2e29f5d6b2c1efddb2b0452e559337cb53caa9157d229d4e415670b7c2dc7441f23dbacfea41103780a1f0aad3656ac47d26b37dde193a55980bf146da2819566e6e0185a99cba27db3e38e60abc58e78b476827f5b010a1fa83e97763e88214fa9c09de078b647ffe9e8ac244894ac3b75186c5d43e9b7239ce3a3b05015c506c08612b80defe8940c4f181c06eb1d891dc4f43ce23a70ea928c905b7378e63ae534998847f23f6ee8d35069b0dc4ce8fce91b69fbb82c337f96c779c24343cefef16f646668688d2766cccafe0d2958b775c7f9d7d7bd5c2ebd9c467845c7574cb459fe95472c8c56144f2da65622cac2168e775274548bff19bd8d8db375c5e361f6d19d2862a3297028a22ae47ba1577e6cc5e45d8ac681d768f3efe79cba3957be9f501533858858f8d8743760acb8333e77014d8959b809cd1054aa3bc62ccf13aca973ccfe126bd9c10e596be11dbb7ddaee697cb8350341220d1b36e185f8e85c78fedf9d1db18488a4ebefa082a1bf47f9b698dc6a55c061c5c14d8265bf4b547c113da9f321940366f2868406c7a5261d1fc9bb9b3f8ac2c9147ac5dfd328a2bc0f5a5d0470a96776836dd926b1f205b4a9a5a9d5ead0b570b3eff2390c711ae8e3c4fa1ee7e324eb64f1976a045929ef6924b8faca955a818e11a1671fdf2e499c88f974e9957aa9f746fd46c3ccc1c6dd937c75cffa88867006cebd6fd052659c2643353ddf4c0a7ab2e7a584cf2d3ff5f92af337dafbc1fbd9db623a3e5d9025acfaa2bccb649fe26769480006eb35eaf399b1660b8a7308ae01974fc248bf722fdfe16bbd5920b763b350c53a2acd1acdd5eb2ce6c2f0587c20bd66f3eece235609bd48dc5e6806b73b1d8064986730be3b1adefef5ad8dc084ee70fcd6434850bac3f94fda1f54388331e66ce138ab76debea01a45b9b0cfd8c521af3576b09eed505ce3a9ef88e24d11defefb6e479b193f744f478ffe751f725743f91a534e5206afe8d69ff7a6c4a7926d87c0052b126390eab435b3484af490112de74b8e3b34bfa12d97b17948aa3b1cf123c81bd124d3c68afc6b3960c1d14dad7bcc3e405e2a587e9d163a44e71433051b985244fb6a11e6b84b2f598072e8e057cbc5f62523cee0c9b93c6568fad0a1a9237511ecf036ddb1bf9f9cc8b4205dac84cf9391b304f8cd1ebbc94944dfe89fac1bba013d0e9f79e6586e437e0223d676d838484bf1edfb759c6419b35b7e7a25feb8a5542ac7e90342213125cfc1b144be2b31168b20f3b405084226655b63c47ba916de27b2a6fe6b147e1b35fece45ff13a57bd00e80d49584aa751e1947ea2ccf5421b3cf34e095e6e3114917cc5f80ab79cf0869f58349be42e02d4d64f10c6054f28237b4e29c495c5fcd5f3d406999d7f25bc6a733a62588818158c372ff5123f1dcae75183d4f7d071e1774a243765583033da85c4b92d8b0f914205ac7d965d97563d2fe2d3e1f610ace3d8b3541ccd5f7ea194d051a962038fe6d1f3c11853fd098c4aad2c331708133443e2876676ea05bcb7ff4cbd1bbde8c34fadfb8ff7155bd0f45eb2baafb9f64db94f9812b3d6ffcd9b88bf181ef33618f72f25a0bad0fbd27ad12eafe4a86a17fb44b90998d34a19b13e9825d2b9de82421ccec94f0dcb374474f478e50a7cc216f7e905cee8d788889fedd64018ea40114a0d7d0c6e366714ad3ac4f9f4c1dd61a13161ce1c0fd616d880535ed7f931a96ceaa135d9d4a6b38277a4dcb05d835db105105d00eeee78881a59b6e530726cccda1d4975228e01892c045bd430acbc83871d9bbfe2ad29fd09874483a8591922521e42c935355975a83c395c9008b7b235370cdd1873fe6556454aca8033d9eff85dbd96d7368002e18d61a81d12e85a069c9b5c57ea0dcafecb3cbf5972aebbd6270cb67489875bd1730150177ae455ce91e90d071d0f1cc35c165a3dc0a4fe92aeb10cee46bdc4a640f5dbc59362fe32fd905bd6cfe2563d356733682ac5e01ea5f5d60c1cf556003cf07474c1d1bea3a65430452503c08d05a563d89062af1d7402bb774cc5635c1e50583f8e2ce0ea06b1c2643a8c017b68468f5205b23f5eaa60947998fea7aeac0a576999f8bc251674d4b3799350a6fd6b12d2768e7a97824568defe26222afa1facfa515c2419c82d8942e50bd71054259dba631b4a6996872b42d02a60ab7a5a55619cc3b82319ec19d4fcaff1283647d35696282e3344aed218357a6ac84342536038cf3560e1a09874363b9047f8c51d29762378a31cb278ed39cc645e75ceab41e43aaeee786593fcc9685b255b31d535dd0f90d9a6a5ce9f86d94d630b924ea5f6f8851f463cb763b82dbf0ddc03025b9658cbb2955cd4813d388e16afcddb03c689c37740729a1a4ad47c7c64650e14bb69f2145267bdf8506581c3ccedc927c854e77a93397d5157075cca79ca16585623575da93d98d452e6584670f67905ef8ffa000184cea9bb38a820daacf094ae179d1d422346f46005381fc07ae703b39f6786a0299a9c49bb2518af882576d1cef2d331ac250837a0b856fa499e453abac513388b0eaac48ed382aec95556160d706662e2ac24a8220890a4c9fd2e7500fd222cbdbdeb7d9112380679b84f9a427f6a3f5ef54d7f3531edb07c32306a4c9f0b600183e843e910fa72a8cec1d2c059774d255065800fcaba6d2853f3e3980596ee8f37a4c7954cecabf7bc9a654bebbdd3be92d0df06934c155e0c9a12f817d5f9d99409b20c4ddb816237f105340fb5ad01714ddb9eb20a40d494ad838ab182e1e0668641931e7a00fd2c5121fa00ad7f079c2bf5bf4db8c4f0a562d0a1b4205f9a30a75c169de6b14a5a014cf0ea2a0aca1e0e809dc7d65316b06a09a5c3af095d868dcf0bff8b85f6073b7048ac41ff9a7b5cf0b1cfd70fa3154a3cb27716fe61f4f0401a66feb9fdb7a7dd04fe18146b520929965dc3e5cc72509ddffd81c1750288fd231cd9d97167a922fe9d24c56759781076067ed9071921c8e4d00846acbea36e14e09a4c49754583bfc3d01d80cea8d3edac187a6cd55ac7561638ee70490ca42bf807aa9e8305dab5cb90eb23ed2f3d0c4865fcc7d7ca0985def80c20a6eef09d067625d17354d27e37dc8fd29e28b8f445fbd216a981fc179e8f3bc722c604901782b7c339e761b94d0930045894e1eedb25706f0e43b549227085cc9ffafa4b86295bfe1950fb18b8ab9435219a1e6e2f4076b729237e307caf25d8583615e6649efb977e492ea62d00e3ec2e6718559ebd5ecc2b038fbc62fa7af80b4b4ff2a8106d275a0b184737c43e0232800032f01b86c99a5902c6b00a588266063d5889645c1e22ffd2f8911311630660ff5a6df460ab5fd570381436f6f2a1d89948a74024c36dcdad661aedbe961acc894818e4717e08b8280c35dca9aa9cb1a363304e81bb62e4746d82f6de046b812dabb3bbdc8586b5ee5570d461c6c2a11b16e707343df881471d7abc06d826016264aa03f6c22b81d4ff80ed8783efb25f1ffef80fd42ac632311b1218806527324d066259601dcae47f49aceb9077cb71404138faaff352f04b5d34b7487848568c3c2d9175eb2f6c2d4c7a1aa1ba2f17305205fe4bb8053c5c2a98be465e457e7fa2d1fd6681a9a1019af04c6eb9f78c4112690f5e90e2744e0427acdff4b906edb088a41f36e6b00ab651f17b4987b50c4923fbd908f94b0b4484db848b100256bb6400df05416a5dc65a8601e625e5775d8062ae9a2559ad39df1af684d3de9f1f38cab33eb904ff745e5152c5eaae8b5b474395b33fb9fb1b95b7b27c33b08974d709e8a2474c6ec54dcf52c5e0e54da16f2cb5e84280c4cf44c5ece85bb495d68807a113fc92148b4aa71934e63bfb9d11fba96f94459808b13f0eaa5572eb3672591a7a525e5bbf61518c5f12ca2c220b2a754559d527707849986e0f4a4b4e2c496edd03cf3221667b96e6fa79eb7609b7241e7fb3ac34c0346564098c6c2a39ce6ab74b61014e7ae60bb93cf23242541c5488cc882d3e739b5cfede65bfa4a4a908737d64e8bc20e24c122d2961258012a3fd40cf48d095cadff3cea316e9606d7bd34b55c61fe95193b0388b6350e366e83fb1b08f0ed28a32e2980d25737b68b13d984d40ba519284445961e0ef023cd31532870250cf7129e59119c805f64c8e556d49bc858ad8a811b02c39ace0171584597bbc3794d154e336626972b2cab6468cf98dc9fe3b495185bc6acbeeb9ad3b57068f0b21447e0ba766a855ef4369ae78f3fee32cdcd12fcb51c5d14f6746b44d25a380c815386fa7860d366ef8a66184451e00c16ce7e0417997811516afa3b62621f2812c35a44317cb00a7ed69b54384d20830add687f7115b02cfabb065ac6c2b140c10c0d64101d367437e2b0836a0139597ac8678a7c5ac0c7fe5ae4b103f4587d56908e3e706ed05aaa282c8034ddfe185296cb28199691b300139535b7354052f965ce15ce1f5bdbf234da7fbd5f6caa01a2feebe98b05f6f8c78c8cb926e93e62d41b62bfc0900b4211b8bb63016c5c70137868c0d0fc3b92d1853dfacf83dec97113c0e88e5d753b163aeca16ec125d34a2895e84812a669e4b38e348f2d2e6f6788098e1f61f7ada5cd4968bce4b7d7c9cd22194092fd4856a1c3b8700b24758964f34424ff037a7cd391a9af2f99ed12fd82af513e958309075b51184123b9d350437774f844860e3854feaa4104e4ba8f7132ae8728c71d7cdf42105778bbcc3dc998b4a6de50f2d3b58bd5811e7439a582e44a8ac55107d57eea4c1710314e572872ec9d02282ca28a02a70102f110d4c2e8012488142408c9f39cec14a61674817099ae9c82996155ac64def76a16ef5eef292ff4bf65c60b16cbef457fa714d070f2e797dbfcf9b870f455b69bdf19e270e069c0b498d195c614f8bb0312e2a6f4a9aaee5c5393403b8195dbf48582c627c4083050583a8421590b05f09f1e02c8ef63d2bbc60f9e01e1461812c872ce039789e12324bb5737600fc0935665aa846b7561c438ca042d8e2948542060bb67fc0305959810c8a53580ab83564dca85f13516bd8c806e2b34888db28012acd0bb7f3215ba38409835d4016c7fb6d403acd03836b5e21e7a76f8fa691890f8caeb19a70a0491296005813e6018b8e00794541fba17a384b57e1cf9208e09f404d53db31fe36ee0efdfabbe50e2256cf30b186194e4057db4c29e0294317e5e9c2fa94315d4403aae70ea0e1819d8852f543fa914695735fd54eb1e792db5d8fc7bee98a49732fa4ffd283506f0cb1c82d85bd343125f99b0d92791664e406d438ae7013e69403bbe2012d9760b26e4971526996275af0a07d123f3d6dd50d32d84a1f858bd5537ac751771203c55de7a49a43bfa9b591caf477a5b4ab6cd1dee1aa96df908266931af0a9dedec5684c7bdb6e2b10c6272584bb1ce81abbff3a9557ffad083d80e334fef5fb1651ddeffa87152edee7fe5246859da2114f47cfb85904ba4921d4a44cb225fe036ccd86fafb96ad9c4c5764ea295067a87a8c786e9d032a0b95272d3d03074c7d9f41fa0c7127bb9d0365117d23c045b31c64878b98915f44c29f3e83237c607a99b6724ae6b280f4a54ec4ab7a4cce8f2a3656ee6b1b7a60e1f24374f9ff2d7373258434d754a1597d4ae0cb309717fccb2229110df8e5bf1a82ce24369b9549eb23465f6838283c0d4d0e482287454ae9c2430cab0b664d8748885c4704a734e926365f915ecc20cf5dfd413c170c1e810886262e07080c1710fdb850b139a244a77226f47d1ae5484f8b34c357ad152ea0a05101b0ab05aa268bfde8917c508827ecbe5cfa8815e29bd9d806829626b76c5a4d81db1c8845c7fb138f34179a3af810e29084dfbdae0cbb6ca07e14bea2e377ebf6ed508d21b54783669d2f6714f25c5a25a70b6b70481aa94b2b0bc03b574699609033b9c06280e3a62fdcd49e91a34e11076a758a66ffe9851588667a5514073ac3f064eb094cb9ea58b95eaaa5d5d408d6a727b36af2ab644ec76026da64ec49a38ec2737def2e20eeb43db99976891934b90749d8ad5608e11eb70b331a1671f76e63834065421881d9d5f043de19b7f9ce5b3218e61d38a87c2e2acdb96e7319b56b7c35a95d3199554827bb3f825ae984d2caffb80f625deb64c317969c2a2ea0140e7767a4d398ee1004048f54d534b7847801d97a0fa6a885bdd8f6de87b69049db647ea7e715540e8be2e9972207c9e7d8d387daf5f218270f8e91b826fb782493107e12ed60e32aef9db74f0be0566ffc60219f526b9c8d83e4adccd95e9438169dd85339459cca85e7127092aeaba033ebae6ace3c785b9d6f035bbe635c7fcb8dfd97df5a9c46d1a433607985fe622213220245682322c987adbf4bcde89d187ceedd9bf05187398425fd2a638d79e9cbaa603d60935987be9ac13be05e0d87d81dcb957f6840cb874c5f4d369a2200de9f5f5d22d61401a9d2ede69e37ee8dbdec83f8ef6f25bf4f3986b73525f972c70fec773dbd71006108bec2a2a4117985bb6b7a50c285376076f27b921f1089d14f73b4db4ba3dbbfafe47e8b420b6a5f3b96c5a5a47a40e73548a35ee306539e16d3e953b77dc3042e6e92cc0ff3513f7dda56b2a8e3234056875238d413ba4ae686374f8b7c74a4e55fae30e7b1c5da710f74ff14af1506c1a7faca12c84443784db832c8f563ab68ed442fb8e8f63f527fc95304a5d9c76db8d2c84910ca3dac78183c92012506003d4dba6a1c4a9a6fa38c0a65f6a95a16cb6f0edcbf554a9fa50c776fcd30d8b59d24cec127b1a4eeb30e12d9443ddf2221497cbac1c96eeed145f372228506242c322cf5ca4e4b5262197764e88a597d217949b8ee25918b84ba335071e0b4ca0f5ad2c01a50179cce12417936e6ea7e6acffdc4f135d2509c40562c41de00a4f2a300676b0faf8ec3b3147d3c2da536bafa72a3ba5aec7a9707dc7212d1ddfd4db9f2f10f5605a152693127616d63e2b9fac991e6cd01acdcfd7267ad816b90ceb83c9952b5a97eb5f5b569c98781a4e3a75b12df8836d3210d66e61df8ede8cf686f8654cb665ecb51e4aa0f8512a7d6d6597610ebb224ed0e7ca1db31a802dc5f0b73d80e6a4ade1f2d90d5e40fdb10953ddfc1170244846970c74c43d5100da72be3784e05c24da52cf5495b41a61945d18709e442de6645ac2aeb0f42a8997905b9c100648a73056d6109006dcfeb08170f7849595574bb267b8a27db6126f7f81d64ac3d4caec7f20ae3138585a1c1c06228502bc852026c2680ddfad9334a72649ed670dac5b5dd24648b6bda39a9f6ae6999ff12e44ff235470c21c5f693460f791594953fe74a64ddcf66cf1a7529805de4abe906a5d73840c4d03fb219f1a5309cc72d4927acdea1c71c01cc78a43cf26584bdbfee0eb94f5e0b1a1f12adb2ddeebb9a836bc5e96bc0562f3308ae918e22d05ab65366bdb1806bdc32fde328236a9e20291e980a79762d639d72c5fc469264effe97ed6c6e571d2c9d8168764d81c53ef45c1a610fda46983c61d9ff180a0abe19bc5de7ca5185d802d591004af9667624ad2d6065bd32a67eefe661f8282df339ab63005c227b378e1bec73b5590bcc2703148d50f12f61e068af404cf0852008e1198d16e31b312ab659705e80ea9b1394ef133401c4e782a8537605342a1dfa300e049f6129547a4045fb652ccaf8938da8044fb7e36d3336cdf55a6a70e4940e822ef85bc2f6257b0b75b412aad99d694462e4c2201c0c3b6e7b192234d031058d3251983d99d642779048a9b3d44cc5a820e9c4809111e50d6b2bd65e1ad442636148cef609da3cf8b038ea1faa2bc39510246eb5f72625b39a934d6a9dfbebbf408595c85846ae1f35422a03b30b492b3c3cf451f5a30424811abcb360d2c7d0a1b2b4656b71fa0e9c193c0c5d44e1113f5068c11201a4aac8bba9bcd7e31eb74549213bf06b5affef1a1589944cf84e3889a0f430de34215ffcadd39c7a4ba01c3fd70a465db505414b466aa6630182aec4dee43ef640380e9af26fed3a5920c00e6b2e6b6c875a68254be987d2e2b4cb53c58c2df2486b379101be484979d025216c1f1198b3f0c4ade5ffa6bae89df34e3667ea89229516b4221842a85f26e6ffdc931b438050ffd301ae41ffd2f6e18b2ab35e64271eb2f655e0b5c7cb42bf0a2adccbd0e0612d6833ddf7c1fb0677ffe12655d7447203ef72eba54c4f9d46ae16070e14b950c7ae3b7f6adf71659993f80ea52eed23b4edcac2f2b60e7a2138f474c23e9dbb68be4b9a7fa3b60b1adf1da12973883a89c38b12a46bbbbaf422758026b983ea83a74f82854c69295243bd233a88b76e8e06581a72e40e7baba820d6e6b2ea705bb8b9dc587a5887d99c516e7ff56368578a7bdbdfc5dcfa1f1fe6044d4746d89192548cd437ff0d6c2c941773301045afb76d3c137d3d986675ec33ffb05c05db67f8b3ed998b2c11dd0da51d42819e192d8f152ea8eb3acbb11d8af6d2d668a4134ee99196a5130409feff57f65f692ba8046c2e8f54ca841caae60bd6634ea4e478bb9cffa53adbfd5fd55dddfd5fdadde9f6afeaee64fb5fe56eb6f757f57f7f7502bff062082e3b2f6275ebeb6785bcf71e66fd5fea8fe8f6a7faafea7ea7f55ff57f53f55ffa3da3faafba3fa1fba9aec17c861ba09edeff134976cd48567ddf32b9eec1fbef932eef994771b675ad833a5f49007e9e00a2c201f190c4f6497eea288e8ab6fa9abcd7903416360c5e2534e20da7640cdc0b117e9f97da60fc412cb23b07a17ecad55b68e4eb81408b4f78d1789e80ad6b32ee363ee077c88e1610c713b8c1cedf393463120f036c76ac06df4e726cf9268f95b80bad28ba6bd4b37a4297947e49b3268da080bdc32c1e5f44aac8fa1ee130213af42320399fd8818b0b121afb301aa845b0f66ca1adec5c5df676ffbbea9077732710d770d8efa3dc4bfc19ce7758b7551964c327578918471414208d0c33d61a8c26a4af4a195dd0dbfc4674a0ab8ad1106f3b80e0229eb2d8b762b2758e106ab837175acb365a9ec75c7ae10b05d8f311c6b01ee882728093ff4b3fe7b542f63910a8a7e63475ea70409e76049040667ddddd2584028408cac8c881b68fa56383ecb229e3d5b87b55e2a1b72a003d35c997328011155069ac19823f52b831d61bbcd1181e3c52b6122be198e118ba822052f07b6885e48dea888879c929276e24e77b6d43252facd81070bd7de1a8dc7effb346b9fd090eb12662ae060042a4bfe6d6c186587966bceecd523eae18bfa8d88536a4bf8631fc55883849d221ccf8128d68e7b8892ea6aa829090ddbb01a283c1ddcfa27a964e38c7714edbb657e1622a3c66f24e98fc3477eef78c385569f6dcb529456499b836573a2b97051fcb9c1bfc2047204757da22577a2141d35302835205852361211dafd8d871e75683811539fd4443051f95963f8566911b430f8021e5338d71d82aad44db1a6eb02a5c49fd3927e7c4ccc9447deffd8b490e7fdc6850e8088d5e724aa1d8551ee48099db43acbcb6ee5807e03edb1cec198773fba20eedfc67801260c9b4d4ff6e19d5eb911af10088f780034afea438233b9959c9066af92afbbd0d189888cc6dc976f18327e026423eabdbccf0153dca3ba15df497fc65b502ad1e9bf761823224e84d0838e1fcf5125835ef80f189b661786455b5f8c757b0b176be473d31027b80c07461069b10f3417fc79950f940bbf11a594bcf7bace2efa5b635ac9179822c2d7396bfffe32861446b8b5349e3da2bcef04a0ea8af384317c275afacef886026bb72336e41509641c52e62b7c7b996bd2b541ecbd78746fa5981cbf4a18e04ec7b5c85d3b511918c4bc3a27018f19bc8d067e0a90fb0a938174efb0c372c0103ff29320dc4db6d9358613ae6ff315a1d5248da00d32ad9325dc1872870f327083c922a47a042d35b2dc116c62a2dc8fe37c2bb261566832d86a0ce2cf0d3e1f81173f22ec545f95006002ddd24d9bb92bf8da949f12a7eafc829dacff1d2f3eb7b238f2036097a64288b8d888b039363c6fd61af9b2615b035ae9fb4d3ad11b119e0c622d629aeac247f00b9a8e39e9c94b46df4273c4c11b8cbc1f1e3daae0fda5957650a208e46b69e6b74d61b04a93d9806f813aa6245d5d43f9549a8f9cf2364080be466ba3051c8c5ade01ba5997f370d9e1e4d2f64d7cbfd50dfba11c051135b484ff38c8c23b1ffebe4fcd62eeda1b27563e6ec2a2ad2118351cb8e6c863d1f6c6260a3d5163614ce4a1aba804abcee453d182f35659c0e928230edfa408b994d29b53bb0eb9e6066b4144b5de2eeea6f5d07173228253071a1b75f1aeed248e656b7d0cd69cfb13ad4b30a1072e9e1092b0bf094d1dfa090aa017a9d8a06bf90c00e0315c20849acc69732f3180e2351229a635261fe59c9d42c92762dff6e76585b5ff6af50c3947e97f548d45b14cfa1a9cfb44e34138a7dd28ce398feef25f4bb06cda034b82ea82684ddf84b569f99ba2ddebee9e6721cb33bcf6d921798748081e08562d3b70be594b1da023b883eb3b98c6adad261283fa52200cbb87232a5fc3cf707f97e8063cdfa0f2971cabfc12d66de55d181ceca32dde17a71be3a687ca33e3749732d8c8b6facd72528a99e3153cf9ef25b7ad4af558a6a4a181c021c43781322010a3584f2c6f648d411fca23f01f93588f3fce54f257e0889a6eda52190431893a203d3077ba1defae531357263d8234b0389fe0213e2b74305da37963615cbab1a6039e3ed3af0793c1065bef2c6d9186288382c979c62831756cda37d0868e6729a0d98124aa648f36b8afa1a752d2c4be214f803a607b30289745d1cacb0fd070fdd1da021d163badaeba093bc70785a3d8b20964864d3bb792ada4da3e8e65766677436fbe4d20b1f00eaddc1bcc4d697c04c50e0117c1431f6142609802f7b1695ed7284ab397577f3895b8d28681043e4fad235e87ddf7ca5a74a4cb056c2e62bae924e705b6ae96c054da9326d223b68ec5f74e2244185ea3fd182e0f3414650d18300fe90ba11f310e5544af51eafdcd6bda7c3731021d87a1f891b66823f32aa095ba9d7c39c8ff53784f6055e34ef0f278d43ed5e88842710ab3bac7a97b1a81e5f4ec563b81e85002cc396a7afbcf052824d8e842ea49fc6e850effc0e6136c4efeaf3d3a1c89f9968c35c5e13d86ae25b1e3df3c1fcc40d649bb572648303b0835187c624a9148a5467ba198c6f14ae0aee7321fbaf5d4d002d829a45fe6f27d78c5f627883f899dceb552b383ef00cd31a4b6b080dc05dac6349881a3e22ce38787d270bc2427b9af353c00a608b358b6349dc3f50142fecf449a5c220fae201d7ce2daa5ff4429057977630ca1792ab89cfd89068fd502237c74cedbf2413728bcd969cb94eed0411fd6a51edef4dcdecc59b184263fa2fb135cdacbc89c490c74b79d44538fdc366865fff6e84bbacd1bcf4c6d9e2ec00a539102b1815eae4343c8b1a1a0775b8d2cafc9701e6c8bc19d4dc8cd4444d770c40ba1cba2d9894ca16a2523d00b6c18b61b4b7f61837d15d311ecf0aae2b1aa369a45c8104cc704189fc04fa54c0704aa18365b0c0d66aa9611027456302308431a20ce84941e5701916be4efe6f8008d13ee2860a21283023b02f63714625260d10ed5d381af0233dbae8596ad805ebb9d0ec8e240d8e078f26c3edf8bc50e9d34d06c69b3a22988b4eac9c92fed9924eb18960c1f0ee882de9e560fab92fbf2681411740281238dc5fb0a1f16335c98475f892d72f3297a8804b7510d3dd5b3b37ce29461071e9e52d7bea0ccfaa1c010af346b6df3b68401aa515b468611ab154a255e90471e575eab929154ad08d40c5034138071c528a19840bca24c2d97dc5e512c23a39afbfb5a116eb1fc87e1c9ad5a09b2b85f5c3e560058560c33b1104e0c4d5a492a259a0064a1f4f50fa9fa1014ac5bce1f2c49e20a94a20630766691f25bf364167693b95ea62855f8de458e3fce72095b7cdf518acb335294830850cc866ac317c25a75f22f973fb9a978fec33900819b630ff3cd256391bf132554e3aa4856bfaf95d718296b116961683e109d12fd9a20c683a2388952da0d37bd76da18e6e7cd21b28f9c8469a60b511f25f42e84fb79071141e275123a65cc11ea43b0306d1a3b3fea4a60ecc9dabf015fcbb6f54a06e58758d1a30db19a79216ac7856d5ef9d17c9f33d47a06e0089bfcb25fd2dcef03b0178e34140341229f3a615ff488f4c387bae4c81c63ca30d354a8354ab16eaa0556e349af19e2ec76b0904203c52d8c2a4aa8fb4631f0160bcd4582bc7f8c2707a7d49d070130b9d2771ecc4e0787f80648216a48fdd26e288d75647243820373687cb94f9713380b9c5b669e418b21fb928e923070d9dcf70a9599fe035bab7420c0796865a0cbcbb78331f3123694f117983ee1be3cd0f201eec3bddc3e8c92d39eed6d7c865721a166651dca51df5eee406e52e72789d8eddb69ed16056bdaefc09a80a371388e42148f7ebf68f2bf54680bf8417ada9fd70e3d85e528d908cfe9a4cfd10a11be4f5091ea5d0df86984024db481af465527fd56f2a066b5ac22df221956363fe4f56d17c30749546e71341025613d38e06b81990b74b2da1d2eab8d296c97eeca02520d58b6b86b324347d5916cef43327d991ddf232bf44946c120c384e8e5daf68e855ab2e4af373ed805210a20415d20e02514e407785435fedada89680417c54c0cf3d7afbde5000d26f18cf22c951f3d86fbf3d8b0acf6afb38225954a1ae5107a6b2c1eb5270883fbc3196940b359c944a6a3553414253f1d09177fb457ed4596421aa5c0b0f3fd0ae6fb88b8df7dd61643628ba5791ba53220c3129aea212d8f0a921ccd09d2c400573e8c6cf29d6e72ba773841c4b33a60df66212121eb85ee780bc4efe938f463c2a3efc48b8b48db29d3178a3435d231e881b8338c1f9dc0afb77cfa243c1eeb1467b23d95e03f969375a94e6bad1330053bf174da0ccb35b1ab8c268c8809a8c26754a7e708276522636d097a318acd46007d19dd172e8ab367ab4e15ab93b0408397a34e4d4e92a42a96ebdf76dd98a54fcbe16877b0ef434623d6ec3a7f3f8cb09000b82e258910094e3c690956b60a49523124538ea5a76f38093a0995238d6e753276e32ba4afaa8dce2426cfeeb203b452d11451456066dbb985a1db9aca84765a46e86e944ba7402900745bf3fa449283544627f9dace0bbdfa6b4830bf0adef25cab16e02d03cb325dbdd6602c0b6a99a096e4b5b50a6e89e096f7aab53518145b26c42d2b584173c898a8aa32ac4574c346e2b261e6e054633a273b5ff15da692f6e1df790a1da7aa36f1df71ea6ea7ec76eab517dfae531edde54dd9372df03ec6ce11ec1b035a91ea8b59304c69351d0ace94b6454ce207751017007522e0831d055e6810d9aa453917d3a8a8006890a8bd8999c191c7eb0eb72a308c8a270d84a32aa3ed6c32112c1f009ab32bcd4d4251d80b6ac9f746417aa991dd6def2da59452ca7208230824096579c3307cace241cf27cabb4af600d95113f06bfd35bb65d44dd25a5fdea57193aed2e5f118c398cbbcfa289ebefdbae800a32bdea2998fb31374a12b22a3f64cc3c7a2cbcedb18bb4048e957e641bdb82b22670c6e0f3d263babac83985ff10c4ff04c4921f3c9b75928857b34de3c87f7f9475a0eba38332f29d3a0bf5684b628cc9b74452859de9fe49632f5bf8ed110aa74b761cca22882372f999942e2d4f862c7d85aabafb6d7de33f3b4df8bf3d2c55decd8c30ef68bacbdbaf50f587b3d517350ce396b12f291d27a3e41bfd619fff6debbea5c6bd672d362e38c390bb4fbe6ac33c6386789c89e33c6171c4108c9ce2149821fd11d64cf591987b3107c367242fe3c6207411004f3a864ccba5937bc32b4fc6278fd927ab6f7e909385fdea57193ae12eff25c2852a6b7dcdb20678473cd4542c7a807155733d0e3acd27a7afb3447c3f8642fce7ad3b292d2fa3e6bad43145b6bfda64259d12ca4ac28384b4df02f94d67594db8d19a1781c82303795d763681a6edcdd1bc516c4e7333553d3bcd99ad6b4644643bfdda2d06b18e64b86677e2bcbe60704008304cc63ecf9310a84fde8ef8733a5097011cd679e8632cf2846c16828f619d763c75fd9f546516c77b20e7212308c9ae0e26e12fb245e710b77713967d1b5dee1488e26d8f0e276dd68ccebc7440da2e97dd085b276b09a60bfbabe39dbaa832ece3369f50a44d597d3c24ce33856199aba8a109c60be5f5cd6bad65a4d40a6b71ce24154acc77e7178f3e86ca5bc3e8615ec56b0cf150beb539bf44fcad0d3b2b423a6c83f82faac1fa2a8581fa46003c615d8e8d7ef5005b35ae2bd62726ce79c73267a7b822097e63e7b46d3dc6717cfd1ebf5302ccd9cf55c55927e2f19de4a0de2b3cfbc5e11d9655eb3d3623e3b531cd16b34454e47c4c4124521f4818a2a3a69a2f28b677d0c82dc6bf44446437466f3d64802fd27b68332f2f6a2d771448dee78a6b91f5dc58f3504a39f29d18717278a687a827ed2354902fd4559677dfe6388a6ee41d74b4fcd07b353998200714584b3d02f8adcd33c8cf975da595edce8e1ac3ccd3b348ea35b5907231aba114ec34f3ddc284afaf254fe6112000c92302699b3f6de7be72c6d6549dfb066102719f92dfef68da61a4591fb5b46497cd1241ef78526585458375c80c14a0b06991f187186e81ca990a4cd1a2f3420e95186c992c90396460e0f227964a400cb18387ae230a9930686921a8c1680be984e1b6c94f1dacdb0d542bf103a4f8cf07072e546a76124bdaebd86c1f49ac3eb394cf7823e4272c0f1baa1c50b2bb4ccf0a577078dd50b342fe87890c32480afbf17549ec3d7df8ba8edcb36ae83b84510a84317bb59b1ca0dfea621d2705bd71fc28b2bb5a7e16bb4cc32ca34fc5bbd505c11d66fb9757e4be2de0b1ef80513edc5ce050d2ed6c461d203861971918b30a30babb77dfdb990d22fbcb45ead87d64d6bad6ca047140d525370a094bcc8a5364f2daee4f061c5035a65c881d1888bb36648a42e5c643402094e054839d3f5d24f92c74e1a8dc078a32e5c3cc62e788f31c6583385200a858a52dc5f32b9324220a45c6821d5c3befd91967af51aa2a18b9ed1300cf1d6b716bad745173dcd274417d12b96224adead518cb3eebed1cd5eb2f07401f112c390a53e70c22a3a2bc278e1393153450955d391322a447953834a172f3576b6b450f92169f9b53063414f6bd6cefb2c49fc40a4c0e9d291c669a74bcd02d8602383c6942d3384d161379909ec12419e1a53d8bc60e70e972ec51ddd785395c3852c51a0c4d1a54448928442882c0b311986186c357892a5e33e65fefa6be1f4b8886ce1032946de3a73b560b2fbebaf450e1f6ea3b34cddd0c2a2461c366dea16254e37aec0dc1053a66ea161e30ac61738bd15bab4fcca06694b14972e29503a9a74d56b09b904c3baf1911216eed8f8edd0a3238f2e359f450db1c58c700d0178565094f44839a347b7510eb0afcfac8622ad06e8ebb5c64075daac08e341c5c9160860eddcc0f1c1d5274e972d17a8311d571bc0e60069e2c3020c9c0fb080c191f126c60a3cac7e9c9869928e7080d9cad1c2c6951ab8e852f3b5952368968ed059d3b442a77dc96bcd02eab5ed359ad75a67d30c4114cabefe58f030c33ea396d0a7428f424808a82fd6fa8811c64d2f7023f26fc512528cd0c3d7df8a15df018e16993e5f7d56f88720c87d9afb5aa635ab823b8bb8b848035ce04c2dd17872a34a979a3f9599dc68c045b58a8ba63835dc78eab5d179eee2425c1cc65eab3cc61e3ac63827e16a319a9a6f6b95fadb25f9ebe675f3eb6f97e37fbefe76bba46cc39af7de3b46b943b0cd98293467b0c67041d1a5e66f2f71d190153b6b64f871830526baed35e4fabdb746c9a4b27e76a7b10aa595ab687b2d2f0e08eb3190461df07b32f3d9836e51079e6f3042c7dfac95e453dc23cda6aa6f8fd6a721f60cf9fa85c49216123b7a27891d541757ffb6f57b86bff83e760feed19f7e83d804fd9d81edae6680fd6ae11dd288bdbceebdb7fa7dfaeae0d6d573759c127979f56aaddafeb541c62a94eedd5f272dce7a63bdc150cca288af157728c6c618299bd16665cdf673cbb71bbef6f60566270cec4b23e9ddd7cf2ee9af7340bade21c9838ebf6ef22ac8155480afad60574535a5aa87e5ebe12eac067be12ecc85ede02fccc55bd2d1b4c41bf26473d65fbf19eb662d7f1de72c9ce62f7f5d6b5ddccdbe3d659a6f380bc0388e63ec3edda8dbbb55d777b3be7c595a5b5cb8b7bb5abbdd6eb7bb5b5c5d5f6062afd7ebf57abbdd6eb7db5950abd56a02b87d81953b330977977c595a5b5c3e9fcfe7f3f1783c1e8f87b97af7a9d7ebf57a37eaf66e95ef6685d8e7f3f97c3c1e8fc7ebf57abd5ecff79402f8f1d92cab65b7b8ba7c3e9fcfc7e3f1783c1ee6fae202bbbb9b74797709f4f97c3e9f8fc7e3f1783ccc85b93017e6c25c98eb040a0200000210000b2cf0f3c4d79eb6e7854b4f368a049fcfe7f3e13da29072e42de882afbf1e2e78d448f088226b2476705d5cfd9f1f2f99af03e57cc5dd6eb75be139b0b7bbd8130f229f0af1d87d4a24804f43bc4dab2fe33be27baf7b70ff5e34a3297fbefe7624bde51a71357345d8b7dbed56e8206082035744fde5f8faeb95774b39f1a7a3ead3aaa523e93fad5df54bc44d6b9faae72f18ce5aa35b517b065d5c78371ae61a091e3baff7cc58cd1f0eac1f0eaab74e2e40ca942a3f1c433e305a16a260fd786d70007191339732f6f58723c7e3221347d20c1c2b6080c518613ace82afbf1b636e58f99408ec51d9e47220cc78124226478d1aaeaa98324c4b2fcc80470b539850865c43ae0b0566e4596ac1ce1d25ab71c9901950a074ec708185a54eae0b58f5a0e7dc85fcfabb11c58e15126212e316992174e3e985501e613aee468e4f89349f1f2fea67a32ab759c3024d0ba19cd7f0c23e7ffdf1c0b40869e5b4c464ad1c8e61f3ac7e93e962c446d41be0eb4f09ccbc6109afefb5aeb393a21651a098c7440f5d0cd1125efb46ab59c2c4b18b9f687886a1469892d487def4aba115727051d6c12b7093b4f96db49d3f5e3b4beb36dfdb9cecfd6a3c7de9a351f5d8c9db8f9724b0e0675efa964de0e6365bad36a20f38fa99cde636ff39539a577f40071f6d12b0dfc18daeede6b51fbfb9ed4c4b789b6f59046a5e86615abe0d057ac0d1d3bc1cbdf49b088e7e444760c1d350a0999f189de6b3d169349ad7127034f4e6b69466ebe0b733bdf90f7a4190f2864ea083dbd0131d7c444f906e2bcff486a230bf035a435314e693a68d947970ed3634adc034cbd2838b1e5cf420965ef3b1e641146d5ebb8936af791045b174db4dac798972e0e046573c39d46a62ad56f39858136128813e789b976158baf5f2a7d4f5539a5286b671b4590f7f35781fd2cedb9996f0b7db975eaf080f6ed6bc7420134dcb2fbd837b38ebc57540d3f26bfe73d6bcde2220efd0014dc3afa12907bff98f039df50e95684a3aadfaeddccee14c51986f73d266b37988a6a3dbfc9c59d9038e6c36db8f97e7cd6b676a7ecd31cd4f947e735ae9372ffd86d68bab39cd7fd0fb35db88fa40363f51f3d26de805424a2012352fbd86cabc44656efe8886650d518289cd4b53c156cff9e2ce6af38ca9fd4aa54cb34d81bda240246a4553e44761f5e74a147b11c4a5e49b1767bde7642b5a9432257f766598cca38e6df17645dccaa01b76f2cbfcb756111cc8180522813d7b46d32ca5f5a38f9d1983942909766f55634073817d957071e067bf2400ffc4758095b16abdb5d65aa22f71717a6b6f6badc5da8deed6282e3e71d190042d2ea20582602e2754dbe0e2ee19e20e5dff396d674ca87633d45885527ba5bc7bf7ba6ab150823c1bf6618afc28aa16b851123522bad1ad2ea2f572593a5f8c6e1608820e0041edfa13a0870ed6ae998c4c9dc34f6da495a7110e5efb393f9c29631ebc83df6a3feeb633075d1c8803ddc389bb43a07738eb1d1a2d693bebc77044b8e8e6350e7ebd7696a7d1c5814e3b39b838d0676708262e0e74f21c81848a01e81ccef00e8115178d00ddc672b6abcde4cafc61edfc70a61c5df4f2242f8e66d372065a0ba6668a237a8ba6c8ef7f42be733088a6fee09992ffe1cceccca0cc4127c732357f4453e4340c4394f4cb436bad99aca8b5e2ad75081010c8e5422522a575ad9be5ab5eb1704221bef6b45ad0ba32029138217f35f3350ddda2161d8148d8fc163dfa11052241fa383a89a6c88f22f7a41ffd18ca42192a92e31e47df635a7de199dec2d0c1101d613f737071756fd71bbd587fbe19487fc57f6d18f1355395d651e4bed65a8d2a0637ad753982d00a28845ee3c8304f29adeb140408938c0fec26c6b8af4753b2948a752347e4a8d27a88c739bc5e10a819a8a818a421aa0fc42d1ae10ed91c98d7979d12a46eb7088415f1e14c997f652a14ab2f7ad015a11dbbc7629ebfcac4188a82fc500c51bf3822630d51ca3cbf4d43b845d6878744d147d431bd895ead8f173d2dd2c02ed503889e2e400807a24c4388a21645d13f84a932d915456b4551d462884ad9296254d5185e5c288a52b667cf0ad6f6f9e14c096620e590db7451d1f5994371c4d558bba38b32d925e1e2aed7ae97358b46647e8c3c51540c441745cf58a2e8a28f4082e8a2f8949e00beccb58b6eb1ae08998b2e7a4545d25a1445d57a59fef1cc1f1345b18a6ee623d1c66430aa1984a06260fd460bf1445f2b9a81186ba7fd1be66255cadc8ff80a7dccafec83a02a3a694a581acef190fd9913392eb239c6d86d678887acecacb828e74299562cd2f1e821882b224551bb3ee6a163271a1d8f2489a2c87dccb1ec03db102c383e96ad876aac6ca3235c9c754cbbe00cd2f7dbeeeed076d1dc1fd894a48f262e6e848b6b53a655cd6fff10a6ac2078cfebfba2bbbe94ed19c96e021709dda1ed6158a6254aa61fce94237091a5718736b93d5f9569ddfabdddc41541faf61148d86e63be875ccbb33448f484fc57a862705d9fb1f3c399f2662ce5d66d07718baa6e7bee16d550e8b7b9adcf4ac51863ac77d55a8f57e39b73ce59a31e62477e45b1a5d6a05b99446bad75adb53629addf2f35bcb8b366b9cedaf219ab50d70fc037a3f7b8afbf19494f43c757f0f53703ec474cea21f90937dda507c1dfa88e472fefe2c07dc27d8b821fbbf946d399cdd3d06bbef7de687a42c6453f8e655efacf693beb1d2a6b284dcc0fcad013d6f383e809123cc17bfa45f4c475f0a227aceb07f5886a8ba21a4d2fefb7c730c6387c52e7b384cf283e57f3d3123eeb1304e667f484f58c62cf32c0debffe6284bd886eebdbab95c926e0fb5c020f610741db7b6ba9de669086d2e565590760d2554aba34ac069797af0fdc37e9e2b206d112b11876239ca7ca300c0357bb83102a5356ae3b543f44d27587eaa7217866245f322437bcae64cafa5627f447b6f0d0903d02e4c8d2a7662eb2f535c4db1217d5170fc1820243f45576f8fa8b428ada7d4a54a41cefdc28452f1c46d84095e3d79f91329f9ae492306653974fc927a2e55393e45df994347b5c7d6a923ca87c4a9a48aa3e35c9a9269f92a611259f9aa48f239f922690219f9ae48f209f92e6928f4f4d326ac7a7a44983f7a9493acdf8943487bcf8d424831859f129693e857d6a9235f27c4a9a3bea7c6a923ae27c4a9a4a4addf5a949da50f329694a717d6a924c663e254da5ad4f4d52c8974f493387d6a726a982964f497346d6a7269904eb53521f29e2fbd4348b4c798c31c664382524847bc19a66905b0d6ad68ad760ca947cb2bc590daa951560ac06156b856ffbd8ad975bd681d5238a588ce59cd1d43ab62658715b4b44ab88d490af215d175f8c67fa4bb61d881583ad731ec1b06260bd42e92e42ba4bce36ab25f9b301400baa79f89063a25981eb1b1018003c5e59683b700001b60004678d31853a73b608311306899615e89459daa2a2270a8f910f0328838486293742fac0f1f1e28c982e204466e81511d75861e981854c8c2b2e5480cdd290334cbce4e142e496a0ca93242a343d78556c70bd8881d4e3899838438060b00043e7a9491c2342d03a2af866e87205ca0d949315832575264f0b1c2d867c608b4f2be98e6f0f0f214e3713325a6c3df50873c327025eeeecd8c9f0c28b1c2a00a9e052f53082d41bf3436a04284778b04982254def0988006a586012890afbb2e342e38d902d2d780ce17227ce8d204e35199c1041c325c8903a56153c29f1c930e78a85204e688d3dd5c586498e241a6efa54917a41c30f2530a658011224c780c569cf173d4d58be54c044e94e8d3331c2623439d1c8335e98c050b50b83235b13f503c70d73d4c8f00348d9076a0c16a41a2c7e531748b304a4860a3d785f3e6c3f5298d8707af3ab4164ce8c282c4b3698c08061f2a0268992376faef458c1cb404c0e356cbcb0f8c0b2e4e423b418d364e44acd1a2f2174e0c8d384098e28ab1e403ad0a86cb0b96af2e68853f5ed418367cd169e0c493c9ccc232a7dae9c141953458c95057e60be2a5a4f60786147f863810b4a9d1964766bea0c50608e20d9b90a5ba32308870c78dc90a409efc81031111822478c0c1c577cecc90180146c61b16b01c91b3563a0ae683102064e17177e389c82441003122b44a8d850e687062acc50e707866906176ec8ac920b1735204db9d335801bc02cddd0c4890c2a3c20e4d6152266d0b4d922830905a68af8f05a1227860d2dc447992b3d295871f800d9e20425cc1a293a862805b940a8aee92347060c7b7cd810f341932b366b96ba967ed408809593155ad4a1c3c3d33566f7658d95196780d088553b45ba9c6112a535c34707aa254c5376469658990162d0bb5146c91a2fbd23472dbc96909800b182438e8cd9518c1e59a6587871b1726601bb28499870cc30a6480ca71f2c5798c4f07bb3e2eb488dc1cb578e1b5c4c6cda2405c1dacb071d4f5857769878f51410a629c88f3a5decc4b130740c3aa96610d73d7bb4a0c0586dc170a34bcd87eaf2dbc9174d30e04002830d3668745a46eceb0fea4a96b6b9042e7233088a27e409abc585f22109fda0a8b410d23132949107bffea09e3e25ca546315bad9812eea80fba1839eab370751ebae5bf4d48c5d17cf10cc42785fc17cd5b26dbee6b1deb0b0b05c981090baf70d4e5cbf379f2341a6d39c54b1b3e20e0f1d341c45e5b8a212f3244b9d354f5a38fb384d899b8c13548c2ca29154afb60744e2663ce545339eb2fad11fbd457fc4183fe59c97acd55a6bf426611937d657e3fc23341a91b3130e31673e954e171788bcd1c29125a5890b14179f284d75f27cce4e489f679ff97cce19c80c089d4d86cfa67dfd35f530f7d698564ff1d57bfbb6d6ba7e09a214e330c6d8c443f54cb3d6a58e691c96a88fc57cef1d81876ad6fad16571067c7cc474b9da91064e1b1b535d3a9a08e182064896cf9e81c0f039dff039673ee0edebafa9c58f5f7f3fc0eeedd5a9c245dbfa6cd6de5b3789cb5691808b40bfbeb33d1d0978e8e2225097cf127808858dd3279008d9b56b1485d0b3aeea464b9608f15076104d617b0e4f15532258394e5ed8e942cfae2285eda087680adb514801ccfe848bb45fbf51b828fb5d00dee2b2e78dba382bf5d677150f89b08144c8bef1d0bd4f78e87a8e8ababd7bb79c31ac19d8abab3ed2a2de5ed26d4ef32a33a1a2749907f6770d05db1e653e7ad5d56c32dbabd9aab7beadafb63978f00e8c74b0c97c1cc799d59281bd75dbdd89e1d61757570aecb211749bdb50146a0e7ad5c9dc04900836fff11f1485ed36afbad13da89f6ffe53f3db8f9b18cf8a876e2750f69b1fe9467ca6b82e820ad247a7b9082a4a9779cc53986082e6a19b88f9cc3b3062a26662bbcd4b20d91c241db4a140da3de0a15ad46bb44407eb1cd0dded5c7aeb3f2750464b543cc4c377090f59a79d150fa5304104f64d300184aa031d84aab3b9092680448839cd69280aa3c7bcea4037012402cdb76f140599d3bcea6cee81d77cfb58db0e9e251eb25e3b81ae837ea4cb677a9fdeca40a83a249a9589307accafcc84cc698e4d901e436f1206c23e73f2f2de7aec04c268091135f190f510c58127d0454b10e121eb150f1de952b01de51b568df5f6d1bb4ae4614ec63987b8b87aa54893364ee260c132c74bd618ae395632e0910ac2420c32a382b05cce191f0d99e18954140d30726ad42f50766ab8f36535678d2e7b4df3a4d39cfff9bce773ce7c489c900f1a1f66f485b05757d95789d1d2e4a3744bf386f256e2a2103704539dc7347418638cb187d820b7810de5eceb8f49cda725085c487efd316dbdfefa63f2f2292e1fb912a18cd5061b5288694aa284853c79d6a01823c7870e26aa5e179f9ac13c076b8cf59558c407cac807486f0379c2470804d3942fbffe98a46cd8455fbd7600c853d30aefe16d597f6a9151b521d0dbfc8542e8374043e4f6301909515e9009aabca1ac5f82c0059161121224c4d4f45abd8d479c47eb3974111c59f404f6d053ed60b6ae6bcd40f414ffdebf4534b59e7da3a91dcf543bb8d42399687c6af6d0faeb66d65f7cad8be0e8b3c74e13b03e6f640e538a5e27206a1909adddfa09ec213882b20e40081c59198913d84f60c7aecf13d9f32db2baeb5646b4e10809e52f390be572d94b18e632981e524901f56765c585f1079225589cc0d0b3c2488d1a7834e179bd418d2d2845cc3ca97043d6141e4f30280589c18d9618f250098b21cd569bab1f7bf00bad9bbd9280d7008c39495598743992450716304ff68ca1a1c47775e78e0556a2c0274a2ae0da2223ab4c922837626071a2d4c34c0c345155bcc623284a2bc63c6955b1726201de962d2f242150595f4f1cb0f6dedb890e51e7a4a9dc52e088416c6449b24298183147494c98bce1b245851a52db051004411004b5110d4f524fa0e2a831f2a1c70ea7343ef8b4b0cac23faf15e0958d88162e2b5943b69a4050416acb8f06272b5499a307fcad90448e943d556156defc2865d965dde9b274656565cf2953faf29bda040c0fd5f05622a12252dd08a550ed42aa24918ca285687d80d75a6b4d8654334c59ca52e487a5c377246b88138b2b45ace8f021df8ce1d0427da384c50d3735b4a4629021e5c5880c1bc248258dd6ca71195b66c92aecf7be3253f59b431d2cf10901aa4ca2dd6ed78234a3e50ed52f6fc64c69be910382525010860a9c3b4c7d98a01cb54064861aee50e5d871a32a4f19a9806f76488adcb09129015382200882e00d2cadbba1c5e88ab94fd4132a2b186cce845ded00b3f19041840a0e932e4a7868e8a620bdd83060c45ee8b864947bc583078259701c20bf6b15ae2a5dbf3dbf01fb2da5bc6ccabdf7de7bef2fbe302781e1490f297c86fc10a353f3dbf30d4fbf371a2b346f9498e8d1f1a48b72db7c9003f623e70e6641b735854b0c2c7c4d7e3078d1e2454a942d3b84b8a113bad103b280d364a8eb586d375b644a823009433245053757ded07942230f9ea7302f6cf8d2a3abc8dc38444028b876c152e1ce132b378490a9000c293d5076eac81023c7c8ed89cda1e8200882200882e09124adc5d32ac90ac7832008821634c12d1889f3d402950d25703c70e1e98a912e3cb6f470d190cc1172529ab9624d75e34dd71e275188c82841650369075495561f2bb5ab176da81e04c11ae612bc42220604cbb2dc12ed0a9e2892f9c845efc99e3366681861a24bcdff7292af5890b1e0758d1b6a88f3f502520d5eba265ad7e9f25a5fb5e8f15a67a50e3fec84499243f5e59b0a617842e5498b2518bd3959ca15ec955d15d29ad4fa4793447abc9cec8058b9f2a59472c70b972462b8a4acd69056744626b872e7ec1b89903657b8b0c0074e9516973c3abc2ce1e203894ebf413207ce6b277b1dd494f586c75c11d616853aef00a7d49e351aeaac68e1a13a454bb13c58e85daf1aa4b0fbbcfb3a4ef363f28ad828de2719621909bbdb91c818e3a66a97d0702e356a6b0635df1855529f06f9f58cf597b44b3e25b307a5f6329f287db4328dd68a2b3f48812691ff84768c5db3f0f4da8f7ecc24e863ce27096e046c9fcf68903dc0f52ddb48e5fd20ab41fd2bfed58e650ec868793396b1017b370783681a3b81accfbceac69346237d86a23073b2f4aa436174998b10f3124581e63197a1284c8540f398a350f331768e27cd671e02cd676e7d36731205b22ef3239d0ad2692890458128303acd43189de6d6a221c45c86aaa079cc55d47cf423dd91aeca38540c4207b27ea4234f208ba60881f43234a14455a828c9235ded54311542cd69a88aa9d408cd6934a7a129a060448487b063d2d18a8742b4e2b42cd1f22e204c89a02e602aab4ee6a48fb2f348479e47ba0fc8d3ee4897a12a5484000f6127010f6127d18a4d8087b0a700a1ea4ac043d84d00a1ea660e42d5c9484fed8eac5f326b9f2cef69e9e9ce9c3c693e8ef9cc2e9e26dccf4e3bad0f0f61bfb21421d07c86aa5031f3196a7b78083b0d05a1ea9066344cf3aa9b6137c2b46bdde8ea74e469755566d15bc520dc1804ca6eab2ea651a08c7e8092101e81373c62830d4236e442bdc6e64c6a36a5f66d33a9eb5fa0ec479fd1f474afd29ecea504c06334f72a06168bce13e6d0ea3619eb3c3a8fa63326cce0d6197fce196734c5f86a9b4330c4433ae730c4f5e68be60b76c916d3d1746ea6936dd65dba4b77e92edda5bb7497a6a3dde8ae30678b2bdeba86d27ace39e79c73ce39e7dbede796cb3067aceb94d671ad55d71a5e9c99339873ce19db8b31c65a6b4d27e7ec2a1ea31ca694ec4962c31ea0a7ca901e142cbb2f18d63c79117981481f223a28bcdf4e5e1132257ba230c618638c31c618635cb59cb2fe9eec7e4ebe30c6b8568cf1b7d97bef5daba77befbd6bf574efbd77d571cafa73c2f5a38afafa5181695d77ad7b6bbdb5d63ae31de0ce18caee09efb51daa207fa7c45f7f544d20b67dfd5121fdfefaa3e2a164f30d163d814f33884d999aa6ae40d68fdebdca8cd6dbcdebcbcbce97571898bd17e39c35ce59ebbdc14ceabd41300c45300c4531161b311663b1712449d9489232d96c461b41cea660fd759a6fca5f2f4b2d7f6fd5c55dbfbc4be3eae05d1ab769e92699e132a386cb8c57d796ed59294b657b56ca4ea9b2516176176691ec2eccd2b04926b85aee97bf5aaa991117777db4f7629cb3b617e39cb5de59ef0d82612882a128c662e348c6469294c966339a6c46a39565ad66b3fddc6e1c3874e8e0e14c3d15f2e09a2988bfb88bbb19352fee6217c1dfac8a819e32357356cd80c35fd74b35838a00273b767e4dd4f8ae683a0cd060605783815b670c06a6bb8c1a4f985e2d8d07dc3ae3abf19417ef402b687268c230a673a82f185213e58842e49ef60d357ec8500a5b9144979abf42d45e41c9958e3d66b03c29694142449425963918b1c24e83271986687971b6b4aa8199f4b211f61559ba693818c18571b8a464a621cbef45e1a1101711fd9424e59c33511aa225594bb43830329f2be346e8a230e7255b9fb3874bce7c7632e71c864439253b179ac2f21a2ba429b9b3aff7acbf26523f5ad2ce638cf1dd332012faf3e714bf15a61c657b82a921524eca2d62a3dc06535661ce28b741bd5ddc119dc3451cdcdcd494c745310ed2943566a3361aa42688038d1281f0234f6c58b226ac8def499608d0f842f2525524499492a6264c49745848e1998a62a3a6a41820cb901d696c783921396e8aea06c248ab969d042c490ca9262cc2ec69f864888b6e77c8ba14921838accf94f676fb40a7ac1fbefea4781f7efd49257d7a73cc33233466cc91a4281f1f7ecee297509f20842e0e778f70bdd67e33039c9da8544c98ea721222649100000080007316000020100a084422a128c91351fa0114800c739e4856502815c722b11c47411807410cc3300618620c40c618641483263980e2ef2ea7df66b9e000f9c3c74e60db73764a6f0fc6930320041a794a62dc04b89c1af739dc358063a7c4c40986f98a5f95831879f28f09bc53a37b3414457f4c34ba6618e33de9074b8c59524c5f7a204b8a8a31143ba309210e2275bf64bc97220e4232f6c94f5136c514347e478609d57bb78bdf9a40f0bd7c691669b668c40d95a0d3d44c616c73ea7172d18a33bdb5479671d9d7dfe8aa4fb3feba7cf438a7b0087664edf97504ef3b45df2e45ac1b4b139d591a1e48c1e5b0b172e43bfd40a0ae757e0e2b195211e724573f6ebb19098874a84528db011767560c253e898f4d4ba1f2308a6351fc438880beae3aa08a3cbba5687c2caefd005a4b332714651f8676e165dfcc947a29ce12474d853a0900ed317668bbf99f021f8a6f45d96205db7af88db93d1e971fced1a4e427764ebad8054d6c14271633a51f6a26991b268a5691bda1a9351524f4301f3364f95ef584c7e4d6fa2b2584f12ff86b4deabfa2a0a5929037c1114423673fed8cee8d72ef31f45ac538932e7c90999ae80d1d3d6b9b17994e6f47b498619f0a2253292695fe1fe4df3822444c80620e0bb5f0cfb655ad45ee1f7aa50e96b943d9f2f6318fcf78d45fcb681fe02cf309a9fae9481bdb4055527df36c543d8eb4c8fcb6898651fbeac94810aa23d67e71795572ca75c7967521d7ca725a0df0028ac2c6f963509fd0fe1003b6e5c65b95f42990147f1c06443dd3f0e4a83148c5bb291dba812bd82853ed0e31b7fd386ee87d0303b476c9975d7ea7cc04b0dc8c786c7f30f78dafe05ace3f71c1a525b078c345d3718bf21b25f8a872444d378bf822d032ecb2c40bc21f87a891f7266922edaf9cdaf4be51b83249b4df772296a5e662e8e8ddbc1b0aec5c5f8d8d62f0aa2013bd02c618eed49885661d1389c1a521dd56868b146ac612506272252f3289d442f1f962d2b008811be2a4d065138ca6964a12ed467b2f994c1445177badbba11771614d5ccb4b56496aaa80605724b05d5209187f5ff759f148bbc73c746e47c42973bb33e6e2dbc3e7c191294f9ffe6593fb1f743fc74465454e41e4f17e133204c7b3ea40194306d39f630fbb4afb5fee0f3888be9ad2df9a11cb9dd5b59fcbcf3838eed6d34bad2d508613436f756a7706e1d83e769580615fa198d2c9e78293fdb81518e9d080666d2fdfcff409f1a0d0a7a80a72616de0001c7a798460eba49a205f36883f1fc2cc53dc5282a09af3787601e77b7f7c19ab0fb434c530ae4bf116bf7be9210bf50972aca85e7a8ee58ca7cf1c162943ae4a7b7d917c98465cb1893595efa1158fd210d289e1034b371f1f9cde37d3c8e53828557111048f3ea72bd9692823619d4152437b252e8f295cef92b8bea1cc5fc4022ea75bf5361272d58516e93c06c986d36de3a75b0ce925074ad3d97c22ec49c7b6f5341d0389ba0832a8a2e50f2567003399d218e683961a1a36fbc424964acc0b7c404cc92da855688eab3c1b50e5eeb912e47ba23373a18ec3f12615b06bb40fcc419d495788d8cdbe2c459b8d0ed121f909e9d6ac6abc9a43402f16596dcb402e4d91541509ab14f24cc3548873cdf22ed2da821fa08936198feac6d934847afc9e9398021fd94685fac21b77eba0a3e585ab8617e5f568dc41d18170de2030d1eb8994972f06eb1f0a765452a238db31663deab2bebe0df477a78c8d7393a38186056f2087c18fec57f387714e86690bb07c717d1a1ec86972bc4ae743c4b062dc6e2c8bebe0c724962782b23bcf65956fa1f88398adf42e8b873e463093741cda3a00d0aad298881d07e044c01f3331fbe4632d1ebdd65ab228c0fd1b3e651a004974045b6825cee6015b68e35c9b97131729447fbc568f13e961d5c747dc8cb377df45d64527e4589d2e22176c765e187dc99bd1b1eceb8a4f75a667d0670d64195421f9c266e168a907d65d7e9f1eef82b585b7f4e94dfc90479ba39d7da162eb3b8a2535a04636f447f222d11f31ee2395439051f81695b433ac0418b90e1c787da3dea8f3e602b5c56bee05efed45a922be5df0a742120da05c543c505ccdfaf07a8624cef28056413d69fed1a4b9425619518e3d383bd748b3a4900ac48952107ecadb8f44468d38071a845933a7cc5b15662d14210bd90f6c76eaf0a72198ac295be6079c31b249d60bcafe623e8d2ebd8d9e69d2bba29196be84404df8193d6b953c71ea08265e43229688c8598caea0476b353c89280642c74f4691b0c7735bbdfe5e2b344d753fdde5338d04965a738d3fdd0b2fe72100d73a8955361c6fcea797639523fedb6469ce05fbfc0ad61d121d75cecd6c052d3ad26e86eecb61aa485a62b3ce2bc16a6f1b98eff7317f0e7e8c82a552d59f52f9ff5845730aa7d31658c42307e2421e61f97da622ffb9fe148811ccfa86f1c71a8736829010e6bdc8d1f0f9404a7283f742b66815f473a5587a35ca9bcd72b03cb31a743309fa7758cfedb3505e5009eecdc3ab6d5f9e2d2015f913a230c8fab8214ae7bce859656059405279fe970920a795260c82ed2a6aa00f920e1e8e4f967f825412c888443a387d50b5dd47df4d38d9ea1b117aa1b0c1e75f3b18e070522d3018214eaf7e11871d1e92ad4c4ebff69506da7be5ea99702a45bf5c46cb3ad47a457b1599dcc09a28be3129c45e78cdcf36f5c21545218a9e07a158b217c7bce07455b71657080d8f68af02be6fd28fbe52f7e3613aa3280fd182bfacac23975107c13f5461ac1291e5bc1eadf33d8308735b6a5c530401b25da7a117534f99d413fe3ae784c34ee84c6f31101d10980db24a5de758a215957677747a292764dd83d8d3ecca2003c9cd32b73f02f2a7d2ee6b2f5bd412990d8da391f51d0c93bc76a3550dbd919d8fddbe0c1680b1626f106a01f1b44ae97683574685c692ab6cb6dbdebfb1cca31b99daeec44e3ae8d734c4719d8e390d07125b157cacdf59ee372b1920ff95c5c9693cb8de3bd8c3d8017125a597674abfa614072361d9ba3e9f528ab48c69e33188d6fe27c8c1538ac3b51911429f8382fc69c06f7e949001887264c89815e7e3ceb4c60c4345780e071b34052f1f7803493b1ab8ff01de48f58557b74cd40e96a5485ab07ce13040aab10e0175499fc0f58508feafe8de099c10c201f1c2df03294ecf5284931ccf3b39c6986dee8875f18b1fd13a48b3d7020bd44e78a3e0594703540cce0fed4538fb5dca74691f154f75128d6a2f42e2522d4de194684855a75bab6901d0d3642728ce46cfd0a46d013476739c4bb754f42e2a4a9ad1329e4afefd8ed0178b3c05105b0754e3e87567545788cf7d608f5556c1bd342968d9bc99619e4772e13ef85c0fb205acc09a12bbca256f82089c175b5009778d214b532d04b3666444fafe8c4464a6d7f8e4331e8cf7b89df4fdce9604007763bb6930da963a210d83405170a3ccf1e4eafa6162a6a2a1f1652c196a6d3908f7c77d9c361fae066b6c205566826d5b51b0c6c7c4a01faa358a85c13b2014dafc0000497145a1dc86758b25122319e837491dbf9f04ee9e9c2586ebb5b4a29b7ff38852666c957ab4d0c953bd2ed881ac92ef3f4c01a4baf207114bd50e4d810665a37d60dfe0a8aac6b9038e461ad716da68c5277379f8c9b5821bb1fa6a25da6a279387100f002796062117ee6ddd14b15c0f8e27070a91ec1ec992a6f84cd792b02b33168a0f792179e44ee544b01694d7b552b9956b897163cb859e2d4d0a2bbac6881e4050b036ba87b583c2305b8c12599454a54ab3c46fe0411feb350801f09f142aa11819ef7961fd9d6bbb39753535ffad3cb80c38ad554cf62e1f3f075f61addecb47ce78d201e09b91ef8e8f34322b7ec8dcb5a086784415ee5b17cce78bd79da08fa798d795b80ab3738ba6574f9b435d625ea7caef83c8fbecb68bf880d1c8b72f5adb3326e4bd957e2c0d94914483949ecc1f7f993c7c0c88fcb90c035259c0160db0f8919ab6e701be517feea148717913f690f219e26e72fde1cadbe1f403e8c4686b51224580d77c253424996fe53a91e577ecc93c57134fb919cc019ed57717e36561d46fd2964da6ad72b60b545fc81d1c0769ebe08785a671280dd1bb47f175486d989cbd3169d4cff0286b761d4484da557fd1b5b1eca367ea20aae0e9cb2c31013948a206f4bf7c578e885c220be8c740aa295f67b94d939f4be7960516584b0eddeb040a47168fbc339e617b6f2c84480772dd8df07427ae46ff43047afd8ac0e90ca00e45f277a8b167c36e413640ff3c1b4b9ea708536f85ec255cd4e1540f27128f67608c5bbbbaaf7ce71e496912ca0a75ebd05cdece70619c33755532d794c078b9e5a5507d424112f9ed01f95415a446df8140f22c0ba217103b8049d52ca4e3568f1c663073a1ac7a50ea80d5843713f6ac3ed2352d98834d8dd29f3864eab1ac6989199968d30ce37c4e8b630d56ccc4222a33ad0a32aab8d7b8328826f46386709aa1459dc6354336e80c5fb158304374c2c9a814c70e5763cef26a7f91e332b2fd462802068adbbb807a7edb027482d2ed9303dcdc1164c36316dbdd65c8a7278992c9cdfce7bed4da465f8d1181e433825edede6c9236a5a09479918ba51e2c372f9385d8d4649ef0a898069d499af8ed1c00c9cbc2bfd11a16496c31ba5a375c0ac1fae1f1ecc6e242603036186b36d559cda7716953ddb19f1895c9dd590f1bb309ac6189155e7385aa999048fd334f66621d4f6dddedc4cdd27c90edd926d8cfe74ce68258b64536aad7299627b6af429cf828df6e648517bc9209b8d554b5685f9d33b52877d05fb5a1477d6c39035678b5a56555e2c08af91b6540b1bc56b9b0b5a49251b30a7f9b8e22376215ba151f90e3ac556b700aa6a4394e8a0b760e324d4756017d8ad5bcecbf99e04fce1faa36578b334322f907e564e0524e6b1714d45be2bf5fce469129ef5e0b5ef0f74f5dde0d3afc87636dccdb43dd480696332a4e64b12f44ddac7e471de4f0614ba53c27078216d88c97daec94593e1bd3678bba71397cf50ccd518bb5ffa6dead24757c4d97f67220f50fb3f1d2110affaa5c83df442a3b712ad37471fbd593d8df9d2ec05b28c277d1f3aa8e921631b61c0b0b25a6a9ba1843040ceae761739dc03083d74b0b8231bd4ae1310c5c0c8962877e3095cb2523e9a68d04813b7e4560a850a163da32883b83602b139347599cb162de01fd6230ff544470eccb5b9e1d15d706584d1a46524da1813452b064af8a8ade1a11419b2cda5171225812b8487060828f889ddb268fe51c8182d2ca5c2a667c413047d7e5338861d8eef849a4a330b5a49637075386872228fd4cc71643feeb8abfc70b8889aff5450e84fcf376fdb235a7d1ccea9a6ca9edaee990bfbe88d093182024450b553bbb4458600679a2244155c463385f62cdca794ebd438855ded6f8386dcd3bd45094afddc5c48e13b5b86fc1f072f144baf26d921f87857784d2ccdb118454334213a7bfa0b0a42bfc76ce4a26f1d1dd1e2573737ae5ea53084c2345aef627df05f0d8415c721946aa6cc00769098ecfe501b5d16e18ce1fcb93c0eafbed238da830ac6fe6506cce011871f882c9c72dcb4a840f51be3c653725a22a68b2e4319525e5cad5e497417c09d5b8147ed8dccfca79eb261a647b644514f7f6e9c29722bd6d1005eaf64bc166898f63440eb11316e109dfae09cb2933a1ccf30120abd0110974341c3c474519fbcfadf9d824d89b24b86c76a03497f1f981122d9a211079bc2ce792c85369da0815f20ab09071c1ed1da87edfc3359b057eba49ce1186269909e90fe8a06a235b6c9bc67797b9de57f3873a91c1fc9c4267fbc1714d9ab2c63ff823857ad127e8aaf366a480757ee1ea571c6d70cc4304764cf9976d10847bf54babf56137fa67a793751b5967e6291fdca1a83fac687957984a8d950e2faf4d76c7f3d144a7341f727935472022489c5b81ba3ae366a820ccb2e9fe3ea2e8308cf481539b5888c1e8be23785686ebe7598a60c67897790bd332b709491d9769903c4ca2a66c1df6db4aec75ece191f5ca0a48c8bfbe919c53fd2ea12f0e72bb2666e7a64d251a5d4a09027fffa43d125666c05ca0741223fd462fbb8d620e3859326b3e6ceedbd56049c7e17bf088894a7d776f0f8f15486192716fbfe865c0ad2f76b00bf49efb1729a0f1c674311f6438e84156ecc8d9a2da91887deb90966079075af0b814a2d80370d41f656c8dfa5c8fa34ec62d4cc71e9bd132d4917bc448fce2982a7fa4e26fd4117b045733e6411ca3412c35ca4096a102dc2346e297711a594fc601776628272bc2363fc690476e9813f26795be655a31e87c029a0008a1a8d0af8a84f5bccc54671c146b82e1c7a207a5c03de0678e4f5801dfa037a804f150b7a5a0dac2a2fea1bbb761c785828b62194ac88c88eca325025a69ccd049512b446ec11fa75d973ef3c042ae46e1009bf5a5a3083db2e2f7628ac1a56b230d8bbce6351b03dc622b1367212ccd01b77a92db63a3825acc56d4a0e488494abb9169d33d9d137f70457a21bcdde30bac8cec0e97973ed2f2ab3bbd6b77022804ea5f0482e29795370e57d6be30dae88d622cba002bbd3e01a9ac0ad41396a777fd52a080cecf4d84f70930d93a50eefb4cd8fa1844c33794d8216beacb222aeee7d29719ecbbed4ecd2669acd71e3224687d7977dc30130cb06ffba7ba9c9b7d7fb6caa8f7cdd4449e8efb94f7b5f29222ddc787f26a86fba5dc6f141af128acfe319aca566840a142a85a1f4f17a22bdaf2bdb8d7e5956b592dc456f4e5b3705d4b0be7ba5e8a5734cb67e1ba9616ee6265152d68d7cfd2bd962c9cc5ea2a5a20fa92ef035b8b5ad8f476b2f8bebafa954716fd09fa8021621d8a95fb6190e5d86d5dadde2b494e6bd7566f5422c2ed821c086ecec216d0a0647df5e97003c2ed401ba2789c262dce1588957d8c1914a4a3038adc9272d40c4cf69728a4a072771d0722ddc4b1aca0b0f237e34829d2ee621e487768e80eb0be62f46310b58e6295b752817cb90a62ab8b7924cc4d2a64b407594f02e2ae3a455689239fc44c05b9e8a143c63a1125a26900e293943213ab852b2b7feb0a4250dcb8ce0341ae4791c835606d12085db51c627d3ec4cf4a026d457b97897215efaf02296e9caee723e95a842583d75680be5248724e9039888d09296e49fd8fe25731b652a4b5cba0e700c4d74bc4727ad72adeb56487cc46dbea623c906e71e89dc84c8f1052d42ac750c7a62d365a3b28addc35161a4ed756e6461ca2458c045757abb73391dc915cb98e6392dd9038d37b274ffd2938a96c7d4412c9154323dabf544c25cd799899a75c884bfdd891eab0fae61472b814377bfc753699ad46db2704f7211d98e6d84fe8716f3373c096ce19b8e8a7587d01faec481a9b99998619de0ca64377bcc24fe392add762de46c199bd2ecee11de231f60a5cee2324096d0287bf718765dd8b3f8cf7b7d0f136f3c3197acc575d9935438dc0ed10969803e3fedc5dff83e939472c805beff347baecd5c1548ea0bbd670e0d00638dc7db5c2b0a6d94f1cba4346a4450c5ffbc26c0880fa9d9e5805150670b0a7b2f52477ef52a8f4165bc08e40edc7cf629b342bcb80b7df291a45630084fad17521e5c84a868688b1cc01221d8e2508aca724302e06b18b660c2a6870811b96d05ba1fa466b0dfabff42a8340609514905740e6b844e376d46e1ee5818f038bfc31ea391b4e4bd06834115e900958cc51271948b0b3284c3355f47cc4f212b3ff9c08f94517fda806c78615762d042eb4c93650fc9be2843ae16a6a32a11f302110f19a8241455231747f361b762555be84d278b7c237ea19020744aaa167e44d3337e0d3600b939a900085b8bf6fb1afd61abb57c2b633af6d095b8b18e051eac7545e113809eac6b036202588df0e8112d4d689225766e71eb128513da2caa4e401748bf97b8d1717d9a036a7beef059b404f84ce1085f2c74539d90ea629fd02f246c8ccf57d3c513dee7e8de55d9544e0956128a80047f2df293fa7af7d31a41f8e1406ba450056e2161bb6362123a648d9e7347ddbd53e0cdfbab165c7b43eb9e91d65e0b5a162bf92784dacd066fb452fa909ecdd0132250833152b91aec156e9dc0f92dce7565f7f69706ba0063bdc406da1c36a55be43c10219c569fecb29684f95b8ab4a166e69a3a773b8b148cc06fca3bda3fe9670851960bb38b9af0feb8155545e98498193a6ae63bb87d6b7fc338b1be47d4489604c6b3a43520b90fe58f2fe4e81aaa392d6b7ee9a83a4ccea0be264978f340199c8b702a73cae22400878d854323475276e6e22d759770097e9340889ab4420ea9c5112017d0866d9ff01b8432afd8ad71086f38793d44b750023c2138397fe459464d49ff76a168ee2420d1e80e98ed0188a0731bfb4fd65f438d41db803c1723017babac7d05386ff4afa5b96ca7f3658fe6d07c4bab1b9979d203270f70029f2e85dc5c4e1a427e57b84dc7cd23674e35aafe15f9ff2947eaa9a08a185a98ae2d1613599c2f220e0310aac479058b7101d769a8b6f37e2faf6346b3ad0fba620b12dbd238f36c664ec052168ecc63cf7f935c3ad2254a07bebaf87a6f1179e43f0ac825c6470513f4370eeb5df1388aaaddf2bee2207a68a1d102b870e2b970f7a5d6932fe552c09f907796d0d945b155d5002636d3566cba6ade7a2df8280ad5ca9ccc1d53a441d7355cb68d5419cd86704f2b989beb255c69f6013a20f50a5a4cd5fadcd28f1f04aa043eda75887c5997d3662567e44ac69712f68b58936328585094a0f21bce9e3c110f9645b55bc8dd001d553d8320f3dcd5aaf93d9e210f141162711c30805760d8a97bb333eb4d0fee46dbbe06bf282acaac574dc64c37ce8ac17db356e33d6190bd9ef6d2639525926c7cd6e8989cac3333b64851139b99aa4ccfcfd7fad91ff8d584010fa0f9d9e165772da3a535bfad7c3fa6bcb5a4d0dade9683df38e674e9ee8f0884bc0ca91a919ebe62c6808799599e976a355a557e253a826782cfb41ee72def1700aca685869a846a93bf106e031bffd098ba1d1cc0c10998c6ced42b31b4acd513564d3eadb374dd3b0744ed0b432e754cd7385dc5e48eb37f9d32b99d8ab7a38731415db47978180c56fc1e38b823afd72072feb067700c9bddb0bf3c7b3a0079d0c0037e57723d50133494a8ba9cc5fa2349193ce84b6fb79e8790779ec95458b1ff9a15e805f0eb7aa53e0fe86fc5a067e2fb724b298f5d279e9c16267faa39e2c7dc19578f611a8a5a7b48b38e25be7610ddc1d07b031257aa58e18b0fadf94513e3fd66488c6c1c3a6a965807dcc784c6e83e68492dcd301531b7a67a1a42caa462d3b4e4741de99190395196cb87cfaaf1ea6adc1d866ec2125eb82fe9dc0a62a23cb0853cc517b12da51b77594e44b973c3210dc2bdeba5c2d9cafb2f2efa2575f73a1d3a6590cf4bd5af3aff09611e5f50a95f0a501dbe1ab60941528bd022d449ea62c72d62d324f2eec2906814b9254474368ceb8e6ce84407878a50407f2a6d3d0fb9d825da3180111dca6964840dae005ecea185a69cd2ac4056f4f8819b0aba2309abf029b5049fe3ca46e0f0d837facc0cee3975a72b945ecaf1e9a2455040f1a4dcccbba94ce22e93e82ca7c7ff2465b1ba033458a0ad719ff734ff596e8d9ec1252d033071c88b8739a56a34c0addfbe94ea52f1dd267739886c7edfc9766e975e947a2d4eb8bee4be350b01ea56824714c07c1abbb9b92d9a5f4d3bc91265d14a29c656739256a61b07055e7a734b690ccebc7fd8d11e055666b986640022714e675b30900b9f6a939085fba680cf6bfac1538d292e137274296a3db95d82849267936b10c7eb380675f938e4740ab021875b0a907f3c811feaafb438cb90528ed42290f07a1b3aae5365db2810d312163e3cf60e8321fa07d488c17204090dab9de014883a0fd3787d7a367e526e7cf3f290fc5b38a3c573fae7d6065287a4a6d2763df908edec326a7f7e6bbf79e91cad26021f43ec49a342c32d95945558c561416a253d639e06fed36154a0f400c42c01ad8f220ccd69d2a70c9834fe4300808a80c4f5be14247a2d0325211489e7ca5d1e9f0317d229d9110477357512f1276bd8a05f1d0c99184388f2ec2b184f737975dd854917835bef8441940c248a030773d0534b3b6ae7fd7aecac6600010ec04096e75281f6b8c408e40e4a721482b0f355d76e2ff714645b7532a5ade0d8251e60e23280d183b43203d297964ca6d34b7ea76220f934f0ade0f80620d14bcf649e559163030fec94779ba8d0b85b9bc14f8505dfc8394873d2b294fcb445bf73074d7743c0d271deaca6fe8800acfc4a9d231540520a1ad00271dc5f939aa31efc41e8f654e5c18043e958caeb221a14865a8c6f9a651a764459ff3bb5f37b3d3c17416f9b5af5f1f0ca406224d27a656e80e01281f673a096cbe259d25c04e46b399c452193c4e4f173b206be5802a9790efb028538785e24e63043b26441f923c09a4895f25818fdc9b4f2b7589ab91e5aeba47f39aa2ba35ffefc52829e49a476625e515b5de21ee889a900105567543a977eb6ceaf3652a4dcbfdba5a9996d5f70d2b5dab68d2f51172dae4761864cdca440bd357dd441e6e0729c64607ffa883ea688a60c372bd8700fcf7d966d517f4e2fd70c6190a5f9b7f350d5f5f2c0c7d5c4374cd9603f9b3ec2bfe9619cd59a7b5d7cb825ea22305b683da39e905478f39148f420edcbeef776f6425d23712aefa609d6e2d769c99398d69706876884d4b459dd507ab718dce87dc6345e3a212c118a46be2cec927247a2bab2fda70a5973c07669612f442dd76c230b0b79baad4ca31663a505147253946eb550c1892a9e0794f4fa0a703dc4b7f55c628cef31fe4877259af99b2bff2d27adb18905269e04ac042f4eb469bdcece9e53d646a0754178601fcaced80c9dcbe87189d113694ec83d828441d1d04ec3804d44368286636d31b424f9adc3ccc9bab844d5bd77a475edcce7beba7eb06e82ac17a80ebea583ad68b44342c813e2f1089d4eb9fa0a6cd5963bcb65b49e3daa7e2e11b543d056e304d46ffe15ab53293435382aed68c228f6a1dd20b9d126c8d62766b39cdd2ba858e0f9b5e69fc5ac3f40f914c2fad136dec5719a415daf14beb1c2821589f5960ac52ccfd62ec6e74e3c1c4b1e4a9af1a4bb0e4d74bebdcc8540d018abc22d74b886f10483165c5638c280ad76bbf8bc35caec18cf606f66ece44cb2ffc75a558f75c7ed248400a1138eb6f772e54ffeb4fa2ba8255c5e715b669020d8538adbc395760ac0938ffb0b4d56581406b2928842da5ab5d8efaba851bd16bac74ca723f06d9bec4d82a2463a0344b02786b4695d35647308d7fd755f7363303347e8268daea983cec464a75e301777b96b5d54dc8f7f65ca82cc8a621c50ef3c5177c6d75ce7e5579797a4d6431dfafe730d70720ee906d4a5f6d756b40da1455d7f1dd21d0bc38210274eb6eafbbf00093b6ba9dd8406b3fb7a1b4f7e1e91fd091fcb92aa2c14f209f1ffec761dca86e753da0ba411f9f3f346b63a92e08d2f7438c35891cc955af0b095381ff81d3324eddf7bf6a14d1b7e679f25ac19249dd6143e603e7bb2ef4452ea21175ba1fcbe8bafcdf9ed4d92e10754b049cdd9000c0bf66c7ffef532bb02176fcec89a8b55e48c3ef51c6da11f6627b599c8ed40206665aeb436d81644117ea2c9bc25885f042ee1d802266a20671809c1b1c9c767655228da859c825bdcc5b8bc45c6b865b37d75ee763029574f75c363fe6b055382b8f4041dbf1f086bd0504f336e2a55e42c8108db88d80ff1fe1a948d98f403225994431606b87fdc1ea58aaa41e76809576e5159139b33026405b47eebc0af9243416b25aa05ade0a91cf7bbbb5925d6b3e5ca389f58eb6f4d734fe1866cccefdaeb1c34dd8b721ba8c9ee75731a212542ec3da7e3a6fedb85e43c6e1abe4bc418ee11db5350f2c42562caebe2595b7216c718775f015d7544448bdc58f633e4b385ee174ff1d10de268c5d484a7c8726c0188986f1337fda4cb707a901b7d27747bf62c6c678e8d82492127ad680ca1d1caa4ad6f6f4194c381097270f798c1d679e859b114a4f18ea15230605400d1b04a8b765944fb2b3d8a864c93691a9cd406a97221bd7a5731773f9d678ae729b4b3816961e9ddfe4d98c5e3173d9666ce5c22dd940cf433c83e2fe26e737f1aacbd9cc4da33a4af3bf323f9503244601f9fbb121b25e6566971d8b2f5c906a456a88ae3959ab10c3c2a82316def00d597c7e2e68a2e8982f2ccb7ab6c171679dda062ef453018bb0b191f194d0530e29b6665066c60c314f538dec90e8702c3aea463f0f63ff0ada1187b90a20ad10c8c6ae09712602bca45342f03630d08c758fc6ad4bc9cbbbcdfa07f04a8a02d8a1cefcc1245a2d645dbd744d2efd873b8811d3ff4fc5bf124f58351a403bf4a514ae73f3b622284077bc31debf185639cdf72725c92df689ab4c523898f0fd22259c97171f52ed6e25b1cad255681a3ce93697780fcee9bc1ea5fd70da55146be44d413ff9a9658311a1e4224060ccd2cc48c8fded46a926eb8db27f04099cdfe9a803b31434df94411b159a50d45bb3d6d9eab5dc140c04d93bafbeb52ca4819c3cd7f45a9d459c8d0d10bc6ce8169d1d96ba43e29fe2904e29b29a1b42e32f8e56a41cf4ee37f6e22c7177639c25c7669190d797c12efa5668b133aa3ef0819f5e29e5b5cc3839d3ad45dd6fa0574b4626040291163ded43e0d4a873b4cb5d9650fa3778f14b56f175f6e329f4a0c62ca72b8edf48da7f7608a6607215217c353873d131bf32a49f7b66878869bb76a18c2a0ebc029bc072d8a6eaea8a85192b55ef66627762db208581404237a1a6b0204972d58d4d491fb3d308388e45bd96c0528e91f6712479c93391f826e59ff54c9a3da649afc614f9622af260b3c7e09078784d47d9210ff666019a93e797a9de1cedac5b2e6b44d2eeb638d24e46db7db974e7d03a081a2914316625dd5ea5d7334092ac2226b8fc603e053b123a1d223f757ed532f92cbc11e8f77a79acdfab1dcb30f17e8379d3cafc2542859f66df23490353cc23c06d430c49d0571080f27ddf554f57b4eec0f721013fdbb785fedc656885f62eb3e65166b24aee6847499d1d5396ab14b0712425d66fffdccfd7894fe24c9ffd8aa8da15932015d4d7daebf1ac3b7f74a0c076b6cea2fdd956b34e00d14eb8c326150c4c8c89c5f424298b78b359ba76d471f11a3c2d5043c2e6f4af0c454951fde632c22717e9500d3c5ba6d8a5f3207ab28b1bd87cf2c3ffe65b77a2f89c534fd45351d9f77786706ec98cd3809999289d43e405e036f50af94e5601424865575da1aed88d2d52d54378806cf641f997042260007d10c4878178c1ed9876cc4082a9d3743af30ba422360504d237eddff616483a883045c0f12ee6e2a764e339120d1e82c6bb38755bb80c6af44d16a5e85a8a4ccd62eb8456f604af964219afb2b02fb38192c87c3568f912ca308c198f4a183937918ae01d7a5b46dbe5aac2d73a5f4548fd6e5c165673759c2f5ffd33d930993558fd9a46f66cfd41eb574d44437930a1842c42744cc69d8160728813a154102a4da53c90583045364cabe0015437d520c7c12d4965ff8ce53266bccb0c0d8fd5b4f3bbb9c07a144743f35aa40b1f6503c73c99193e5bafcc1586cc319d928a9c599d30553b6c4e130ad236d9f2f0736331dc9d43b65786cb7759c97094ff4da985bb683641e29e4d4d67b236d191807fe01c3366704251aba308411a0809f8a3038e5b4468dfcc7219af76a1a6e1d409fd49200197210fdb2a94024e63db3d16a923461d95d027992ff103b9d069c75b5b44d6701089131656f6df0cad6c7244a3f92e608efb5228c0ea2e2681ebc40d5b8e42883736b60ec6f8cc62d54b302a20130b84b75e7d9441c69763f4f2dfb040f2283bb2e922a27d13806364f9a26a66e022576b90a2c297802ec7d4b17d508b40f216d30dc421e044f9ebe491dc2982d140667afa542f17e603b15f0471d91bd95f3d04c201452f1101fb329728819fb2410fa7e774d527e24675d9443b419e51e142a22d3eec4afa30b2e97be0892571029c4bbf0c318146a8538ff69d53490d31a9c2ffbd7489ee722e1bbac45d3b24eac010b607b758783ed2f9916d264c2ad51bb473c0846c0b8bdc2f2e09598ee380d57b6938dc955a4da9a2a6ef7d102b1d8fedf554087a15504cd1642974f837665f44b84fc36251680e5c9688d579cb90721d747438668bd60156528dd45c251eb765370a3e764deace2d20de0fcab5234523df4e79838f01ac2ab7b79647db9991249c61cebedded09880b6933e243b92113ae01c02cfaf4089fcc740fb0c13d0c1896fb72a307534d2e1438c15cca5f3b9c82a5508a7e41dcde04b7cef94f0f904f1866dd6d64976d82dd232cd39170415031b0bb5fe2fb175cdfaa48c2b761e62e89982ebe63177f8c3491873d7eca634d2a3187d23427bb9d0dd977e0b7fc039d32cc680f9bf70528ba984b91892a5bfc7d83a4cb058eeb4c77df9ba2302bb1615cd8d117de6de2a6d423f8dcd0e7e81a59c26145cd39cc7a869764804d397c93898655ecbb46a3c2d86c87ff516eba878bbdb234219fbe34e90c8ceaf5d0ef7e8ba5d4331ce8f2e41eca678728469cfd7039478e9f0d07a45475ea7881ff287157165f9189a7068a9d2cfe2ef4d6ca61d1d04e51e1f2e6cb12ebbe5577bec50f1ba7a77c94e83ae982eb286aa57d7c2ff02182939af6267dc4ab33b19f3d24572f8d8aa0f5405db39bd6040d74b9e8ac97b98b109418092a8e73432e064d34b1b8a077071c9d83542a424f7a71f6910025132c4e5fe2364c613ad65a59abe765975b2448d60389d2e98e26a477d154f7d9571481454aa751d73a79cf573b54305fb3dc1cb50fe8bb0e04c96d1690414412616737a64f575bd00c6fb23f646893db8659f3c7b037fccb4ae50f57b1f1ace372fe2781a77bcb06c90ad061fd0646aed950a1e9b0cd3453b0197f9c5ade316e44da72b4aa266c8f4bf414aee888682e36a1bf2ef77b967a0973a9f91dd7d0c6c51bebee72e9a9ebdf089edc791b336da0f2384c7ff4ad354352010f3ea871a4bd56713666f09871e64f3c8ef9bdd9093678b14a4afd199a081e53b96638d9fe0fc2962b7b31f39a75148f9f26a2f999a79d98f3519686ed90bda85d9b484d5a0722095a2d2c4759f1db09bc2e9e244a32e713ac57b571e84eae817d8f8445c020b60efc59a37a08c01b76f198276c668ad02a86810a374558fa0164611d99baf159f5f9f4d63e920610440758d4fc93971e94f9b4692697f0db5d72148771ba0a1782944bf5fc8994744f9729fd1de4bdab10d4d3b5cb438fae9d23d4b14d55f551a80ca124c88158334f63e3325230ba542c88d484210669b855b67ea5262cb6650a8d760d6999460c948d982bc2877bfdf8491d2c38e43cd27a03574d8eb053d296fb0e3b8fa53e359fed445398bd8f00e9ca59957a9fb6b25a86c032e9626b81161e9ebd66764b2a0bc32f8f8042b3c23736ccedc18428675f7fed16878b0c2d374c3c7cad94ca3146d498973ba3b176726da11ea5417be3ede071e5d65abfb84d90ce0cbf2a3d26b68d4ddebaaffe26d1cb430690d68bf3b50e9d39973a04f421a54d7355eb5d526a87554c9c0ff1acd62bcbb2390338e239abb0b04a5802396e4a56f7435c1e69bc848126f2a3cf7861bcdaee923d63801f44db3e44de87aaa420af42a0e3880c88cba3567eaef8f88f3f72723abaa1b304129906575126adb9264c91c34bc7246639ebe992de93e0b1582239be92f4bb3e2d65c5c6c4dd514e41363cfb0aaab16a075d1acde05aa0e4cc1b94f07eca7e5e919096c73f0fd32ef659e3900070ad3223dbb31691912f693f00030bc863751bf1dc9f891dc195c11a0f21722057bdf692f22fa80b6aae647d1acbecc4d76c431d81199e0fe3dc77d292091e0b549f22626fb55cb6a73c7b47f259bfefd4a78871f3f345c23b8967015852c44fc2054ee73e050e5c8e34b49ed3a7946f621e4b8b6a209411d5f429054af95bdd125f97dbd433ed15925c37c8841f8e694df50160b8a003926eaeee169c3dbfc04cd5bbc47003083325a838245f669b5535c1a02f771bbe0c1e2589812a485b0d67618e8152670764f0e9dc858f8d0ce9ce644e7225e7962c94a6676a73519a3194a14b73612295b47c891395da891de276bdde90fe9a3a34510bba297397a885c84c4b57089a67325662caa4109086c688e30ac3ffee424bad42849ca8fe980a01923fb2c9cef6929bd48edb87930205803ada1f0e32eb54ec65cc1321cf0d93518432186a326fa26efb45c5d74db36e98e88ddec83ab563187653dfb1a60a03cb55e61a0112a21425515cf2ed61b21a71bc62a78bd58ff7c64c59b1404796bfe92af40a2883d76e24919c9fba30f355044a960f1e858630dcd0414874fae50a2d958d811593f0326c2adb9854f1a7740cb44ab2e2894683f21a93f3a232507b19160fc33d90f05d94e0dc106c962365d1923e14071e00c3140e6a4f90a7ce3c237ba5f758f3fd792268a5d7e02a359154aad12c0250a2ff4d45ca8f8cd8a010954046e19e8c0a0850dc38d9c4064295e9e358f3532d42f9b2558dd12e014ed92d46da876e8b770e3736931c996412c812426db6b666d8ed010c54fcf31fd3ea927426e1f1c92a6c641f21e9b95792375a75dbc88431460150fa9eb3fb105d907aa30411c5db0d04f18f65cb02a3c04221dfc4b15d66e194f398c128a611890af710288daba2c11cf67f3e8a33040519edf9e24d6a3c4e49e425479aa56298dc896e92d5d22f6430624e9e46ff4d4094000d09f3f3846b8804d24ec0d6b9f61a8affc9ac0e1f2359b8f324cb74f10d84b0b7f3a47ab9e8c2e421dec8fe18e968093ade618fe3c86e53674dc82aaef3ae5492edd6334b4307b9d2b54a8828f3917de662c06de4d484b4c563f04426e610d611a8441aab1cfe91e11206d8162655ee7adae65ad4f9ceb5007277c0b3afb7ac15bac024c716d9712a69191e64676a826fc1929c12bd42d831a7d481b0256d1d3d9fc8787611279be72d96acbbe85315e46990b53c6b84885d8a800d7b62d44955773ffc650420ab3f663beb91c45259119d72e8a65f5d71c6794f2604dce9b1725c352f72bdec952773894c11dab841f5211bea825ebfae254969814cb2f34d272cbb51ea6a200a643a7a7c0e4b0946fd0246051106c0931d2d2299e77b21eacc76c80c040b781f301a3103c2e98b229ca6cb1dafa64cf6851815e3f6df388ad53196bb5607a5c6a763e9439ca4837329b9d997cc4ec3e3c147ba55e9bf3d9cb2034973b46844080b846b5cbd129a079ae30f0390b41808a17429a6c2b2fc2534664292a9969456470b6a76f768d4de45eca512b7017dd3aba09e45afbe385768ade35faac8b34d8320f59cd3973dc9618a9b4ce42e01dced8b6092162d2d05b6e611050f09157e0834eb1e748ab861e6f751f3d0a7cce1d69dcd343014d857af71f9730925fc755ef2d6638417e66785fa45c981e76dc2582dd9967409643119912959e4f30c6682483cb483fcbfb231400166cb34b9d3eb68525f873ae3a8a44df87f2ad684d72177bdbbd52834e439c96019b6de97fa9ca92ea1864cb2d229d1da3f3e5b271f93eddddac4c696e3bb1f09c6002c662b3f7a5585e34e4618cbe892aa65addbbff306a5138fa2c8d08020c40f47cc6cd0e8169e10a27c489daf5e08d7b2bdf94d37c5b7b74fd111bd7130a93490d0acb1fedb85528117c928da16c02b66adea6d51d072a363875d3a77982a4bd5484345d2d89c6c5eeadb497f51131fbbac6a72d4ed81a830cc76977ebcc63e5ae933ad55edfded3775b6b2d1af27b4112d819f9ba67cf88beb1e37e76a5c71a0c99907821684b47f4bfb629877e085e9fae2e4e777d02d3b562ed57aadc13e7f3d07c6cc65d96fd606e8cecff0fe79c7f19f09d0f1809cfc5da4313a636a3b4e4865dfc7e937beab82bbc8c772a3e341bce5899f1b4c90b0be656e82f52b17cb4337346950379d1d5aa3d612fe1661ae6b790ba077be253b0328473ab36fc196e2fd7f1333cca83cdd122ef82608503edcedccd4f9e382008bc69ae3ea127e1f1b08288c3df4614b4034c4011ad875fb034023d95f91e25289bddc718b1cc19f2ba93f613650b9d8af0dd8644b6b10a31c79d1f5f33c1b9cc9d867af231645f731f713eb701b75a870aede061918f7128fd45ef054f7c008dde0520578eb9360089fd3eabd29254b27ea21acb45460cc877dd2a6bf81e206691b64b619792d761cd8d0ae3167fc7a9014925bdbbead841cec4673efcf6653a450400ba38821440ade82b52acf712b075e33f29fa1154cc41dbfc7c0e15c5cb2579a202fc3eb2a45c02220dcbe4377c09fe33f8a5bce5913ece14a2f04364a4d280724a86c1630c44d280a6598078d31a95f7f06c2469579b4a6b524c93fc177c72232c3d5d5f66dbbfa161b670c382155eb40146ae1a29e5376696213f2a830c8ea917db9c0378ee49d50826172b9901a31c937f0c472006debe53dc722c1f32b77f3a293ee50e2371d31b0d2fa226c9878f1054d6e6bf380ea38e8c3ea3587a3008585065a0fd8240bf0502a550049c5823917299bca74ee528e93b12c3822b23d388a676f462533bce04a7ad2b5322aee684d5568e4abf7fb8712eb55ca1c5a19e308acac967a9b013d69b7c088ef408bf38dc6e11aae433847ef0c70d0a94f173015b63b6affb530f2722fb3a3590aa6f88b04b261a166264eeb6c240301956a49cf327694f7bc164d584956eb4c46339eaf7df111a60182e1c805cc7117b865e333252c346fe00c4380157bdc57bd1305307105c43bcba029592930c6f98bf7eccc0831bf073b752e734748625293a1740e4c27c547638421eafef3ba1c90fb68dc1f3307a26bd224d390971d1c95a9db320daeab2d5423fc840de4e79483d75e153c0d77231d8d9986afefaa2263816e72167764b9a9cecfd21e293526806fec4e24c07856c4b4013c48895abba6ea1cebf69f760b29f4565c27cb362d6a9afaa13fa4de627304ed90fb81741d8ec1d93757ccfd55721fa6e773f1bc289042f27c0946a1e3e561e4803b03d03450072b883fa7b113bc42a260782864e37c4d682e8c4476791451f65d29f536b739224a05c633f697410495b2b1062205ce5a81b760581417f97005dc3f3af90ae982fd9c1a3b2c5ab6a4f8e4e0d6d84fac3c93bbfd61b74d43019e384e08422c44259c3083ce0a96b9f3039655073017c91c45dc6cad87ab76e8d1210a407ed93908b1f647ea956be3ee1c661148641d0a32d0022c781f952bbe406f005b4305fe900876f24ed8dcbe23dfcc3a4294f05f487b56cc12151b43a04ea23655566ef62d77adda957cdc78bfc1061b0ff36851ce8acffd0b5871b7388c37e7d72537c20fe3905e3904a68c7ddaf9513f3eb7f4a4b7a1d0904d1f464a77e901fd30dd7081839ae61629d28f5182e33ab9914838bd3d128f3ca11861d9c42d73cf87155b39ce8631bfa950a2474afefaff5879b3fe17ea4923308ffe975110bf94b1ffff195a38930ed301e7564094854b6e0e2787f95b7722cec4a941b38b222c26ea31e29609ac887b6b0ec28e7333db62ab117dc51e137f1080881cec0d5209fa954d08ab7d3fa35964960e4b7cd809a8bb43de7b104ab6bf0afbee6c8bad49de40602e9f6616321d6e406c6fe4a670212f0bf34dd508c8379f08120da81013dfb0056f894cdd95a1c22a9fe16af2de9654f7c244abc2bd4d282a5a10219a5d9ceab32f7555c438e267a7a3ef42161e4f61e1abfe28d90fead0e71fbfe7bbba10d043feb1a50667cc398c6e77ba8ebd12d0e2aabeedffa216e3f201721b8ddb00b54d4c41b91562a2f4824fa0742b27dcc92d1ad8b045e2448cff9f5bf4e9849d7182769bff6340a5817cb8a2f09a58a56b9e9e54b82255c257849805e9df0651fc50a4097b027f53181d2e262644497cbaf2112aba4296f4bcd791819eab19922e148b0a57ed0fd5a9835bb1a81f4d7ab01fdf1dc2b67ff3d8404fc93520683949ae7a20f882cc9f63f07237757c6048b7315a05b544eccfb2ca7a209939d96575c51101e921547e0eecf94fc5c325f76f37a9d471c77d6e64ad1e8ba5a91098a222d1d5c8e1132a63ba35060605420a0d28a8863bbe0f09d0af6ce41e4aa0e3bb14bcaae9d2204eb284f4cc83c891ccaa7d5358036e962cf84fa59168440fff090861a9ee3fb7eb97e6e344731506b46ac5c94cd16a79de0d74c0a7bb1ded40ae59123940084e658f34939883c30d41a3e517288ac3f7624dce435f94b2fd1c1714c28c47ded5c936dc32318aa07e9b6a0712334e6114a1be84a2a3a7c0c500c0d6968548765b4ac8a1e8b7e5f9857cec59b7d83ecc89e66232fe708adc13f8074b2c05b430df24a7de6a8398fbf32853d4f5a8a8250f8904acbcd18dee62940bee0bc51ce360de2adb18c10a52d862a04c7441cfef79bd22367a27522e52726a784b2456c1cb6a72bfaa1d1bd6c536573beb6a204d60f7696d0a87dd2a90341c4695f6095e25cc42ac48e83e1655bfdaf106eca983d5d1c8c1aeacb3211bcfce48d982d110816cca21b92d4698bc88c7f9cea36740f0fc34533c3239999ae4a93022ab8e33d3298a0a15802edcfc9787689b29b029de87e2c572bfc4c6f7a14130a873dc4c30e3e14085ff3b354815a50976110e5412c08e3c2e0252e78fe129e47b940b1346eb5620ea18fe1f1b55c9b0caa0f2fb149031e35e82e2830463bd46a564793ca1073acaeebee024295d160f5469db7cac4b00ab8cb07edad841924a64267b4cd524d9e3b20172883155e14388ab923966b1dcbb44eddb09153ae4efae5815cb378974b34c48214300c4f37b18f26f848f195995118b0d9ccece6accb74bb32802cb7b3128a559d26bf24669aa3f9952af20848c3259ad233beefe3b578b42699a8b2e317ef5e50d466b98580fbf6f1e4cc3a51269130f4530dd0a30a825a5ddc58ed50b74c18c12a0e5c754bb6963642a2e1daec1937317a2cd17b0710bc7f056844b974ac7bd0ee82431c2224c8f2de5e4f805f316ee7e230b3e31339b28c65f90b05f5503cacd915904103388ea825744fb7948fad91dc4d2b6898bd32379def9a67f8431ae274be5f8e0ab151a4ffc54b651687c0dc80bdc330581c996938262e6d7de631c2326eea1af322cc2ffa87a595c5aa1155ab809df42c359d16741c0e1f1f6e6a75fcbf6435b871b90247d9f1d58d260a06fa14ed29f940e8729e4c12fd06f9a40711e8e8d2e4e5c4fb7d9b9e0c53ecf41e959e083095c4334308888c14d2801a2d69642747ffd23506d96d8fb3a73088b46a81f4fcf880f19135918381cb5044cbb1922208366e9b18bbc030c9b74a80b72d6dc594f298da86ee1d3b5597aa9ee4cb633213dabf898dfae499279803aeb96adbfcf88bc5f27c69d923c5722a643f047046ad5216b67be29b5055585026f420b31daa4fc123b40e4a41058daec47892530d2826d7932faa377f58f2d44cdf678dcad8f051c27b6fb90d20c529ece6d890b182f6c2951ac01a556143556133a6f5ca0df7ccad95ac879564fc175978d05801105b9a6d7aefc4b7b69691abc5c4ef85761feec920d007aaab7a513d8026891dba86ab82a26c67ff0a902a84e3bba9a5dd3044ebf4eae8d80c917fe70f78fe09704457e74884143ae988880b027f45c8391afd65f35fd48e578b19b234fd06773badf70f7e7fbdf6c934458c210c663e3490f311fa2099f81d2890a3e771d97da2bf8e6fcdc84027318a1c6de06d7788f9814a5f436bc2e66e6e6525b12d1b9cee8287bcd7a67cdfb9c4a223b859beb6488724d61434dd912486894b596cb5432d698c7f3acca966fc93987186b806c1689c7137928281fa887c59daf389f0e35a861af45e45e46fdcde8f9caeea700ab433a00bc95140b8413762e50a294328aa7204e2b6be7bb3a8c0bb2c8c35968a9cb30e5835eaafe50553e98ce9c7c3d0dc2aa3ea682f501ab3d39d5b3795655dd9c6f6f3b266e65446202cfb5332763838caa72131d11ca97cafab6304eb7a51aa3a07fe12745cde8cd74fdbc0abcc1d9654ea74f2e0ab31f95d4650e90ac97f6c29fc9e9a45344c7fca9769ee90370d0548b85fbf31559eae596709bc02d10d4df3f4129423cb03d268a7429832ca75692c42fc5e8fb6e3364dbb469dfea90e145eb0fe4fd10590578c75a358832670572bf83281b70530929bc3cb6a50c983f0a4d98320e58310b065c5bbb8a19ee2bb2fa0cfe8ddb746f26806d6842789b9ac63a82a76e13225c4371a099a86a3b405112fc6c3f80b83fa0db136800782e3fc5115cba43aeb8607d384a61a42b02451952d3bf2e0e6cd97461d439ab8700fb418c127f18a34d737c9b49ffbd2affcd16fc8340c7e211b47f945cf748e13e83f99faa00d04a2d0c76ef3e809f13de473dbbf63064e1d780b882e62ab2f64302f327188805c3fb5354c88e57af91037644ba592392e9acd2488fb42edd5f12c2aa279a7ecfcd8b22e25cd5d3bd473e5860acc34e007ad8ff954e79f978cf06b77c83d6ee46add9b87c3d674886c559387c3b461b971038308f6c93be8475c422879a1d54614d881463335ba238cc0201956aa2a0e2ca824f9df7238cf09b819af5899469bc8007991f95c2cc21c0f9b88f3ec68a0af9175bbe5219570955cbd4ba6a98f38f6efc4382eb631ffb083f4e2e30447bb329d1257f38b61cc55bbdc9b7dd80807b41e0ba9988f2d45466e76d607c81bf89ca20bbdf26a5dd0f5b22be0a35b2b43b840996b3b976c3573ce70bd78de5b9f404d6a1d4812e0d74144bd05788a410fdb0773245cc35c7f26164cc2a1dd24a475a72811e4bc1fc48da345a3e93605a26734ce0aa27d366db44598b673434734fe516380fad9a0044f2acf8ee40645dbda179d962970095d04e253e64ac9789805650fd4603f60318a8c0ff51832aa1ff6860692704a6adcf8ca207f47c019a27f54355408ed0ab030058358e3550042b4dd3d43253c8f312eb8a0c4f0b2ba1832d67cd819a73acf24c9519686680c68b7b0e946c95bcc7d29c0c10256504e0d162e7bccce40cef72e3261bb7adea8816216932a524a59432330446044404233658e800352cefaec4e6c9da87664c919181eb89ae71a05db569e5d1425d27bb93155a6081995e89d357cddbd20767473622c0acc4bcaa7c673c1869775afdc389aa681fef7df0697bd08765e6765c9af9a97b897bb9813399718a0ba333bbf03514821b13e668e5611da47d9618c046bdf7de7bc72ae5180bedbde8e3d0f41dd74694daa42650a5c364733116fd647becb20c0fcc104627b798e65588047b4c305868c6916af40a822345cb5c07c6b3f3773f7986b5f74953d8830cdc95ac2be92367c59760624322a9a3baea75f84e85814b16f4de332d1983bc87a34b3cc18e31e543b0f25190298b14a9fa219146d984724498f14449f50960cc655a32f1d43321f171667964c25dd300782216b86a564b5e15ca6ad743a544fa884a51f55eacefe6b8fc421ab32e81486540d5e80e4f1e8faffbf985d571dcc61c4cf0a9b4a99131fb2869bab291d5269440c51916cc282b6e06ac8b4085bcf71e0a73b420de7b0da1465e865037bf8e11d98e1d63ed672747e1bdf75326bfa7cd7a587b9c49d79a62c85630ab587ca9da052316971562b454d4f125e37929ef65dac787e3eec3eaa9bc2245b836d8c16a3b513374c589e2895ca0ca1fe6ae94b84ea4c82db810232af2019020213f243a44625e43282ea24c26efd76711dfcea8241c6092f1e64942185dc3b8a4bdfbfa94f809f4928c4a70247d28787869d4ecc70a9db6252c56dcd7f45c27ef944c9a14590916354e068a76143346b6d70695db220143b5c169bdf72ec75da9656a737bf0c9f3762dbd9a77fff12cd4ba39e74c9cc735b46d6dc6c78d6cad89829b72245963e2630ccb2c80d6ce050f462a2e271a1c4f185408a49867ce2b110a53075cf5ee73593d6e3be692b57151ea7d389529619d64785d2654581a3f22082bad1773864968890acb7270b034a84e31f6defbd1cd9adb1a0172ce393f61ce32e7fcc867a79c338539daa888121f633df28cc4308f7d90c95b91e63910352cb5967458fa63abdb814c061757bdf7de59a872dc7bef47ce392752122d6b0428a359332584b8d688787c0a52568e3f5713d11c772a73cee5a3110c7452198373ce6b27086b765030eb88a6340f353a31425b201f3e39742634ddcbc555db6c9b025a08ebd8ea40c448c1a7d52e3652c6adda0c06f3a60ea0fecfafd112636ae88d82395af9e5f6deb5928f60a30c8121b1153b1e684e93ec24a6296b84955953f9b3a150353b6b1fa651023926d54b082c131b05aeac9c0d488e1d5ff40e2b83ea153be39c73bec18731c39a61d00d3730a0ffed9bcbed7fec9676fb96edf552499c73ce3987327f3f1e2dbafb5da1d8d34161378375d3d635a580fae82a1a337283ea51231f8ca91f9b00efb32c0ddc131f498a0972a5f7ae410c26044d0934371d298bb8f551985e9a33029f64d21acad1c531e21636826be8ef22b09a11ecebc767426df6de7bd8c83fcd627ac97bef5da33aae0ea0f7df862d2919ddbd03c53c49195c52406e5c50970b4810d6194a0b1b1a4a33446e26a4e60208a66c57684b56cbe9424e854459f653e542e2ee2fc88e0a137674d0a3393822bbea9a3525696dafa86e3a7d3fdcd73863d15132eddda971c5b45160694d971484a570b0a8aeaac87b585afb1d8d5f9c3749ce4e225d9a2fe2c7287808e54ee5f8088e5f98316ddeeb871207257ba472b078d18daf9dac2990485c8814092b64255247c6c4cda5a42ca42917a8ce627c927a5591ae34aca3a219aa0f70cfba9cbbbb7d3d30ac009b2d962731e4549d5249ce570abbe708107cde2866c2de7befd9a8fc662bab63bf16aca27ddcbe470781b014bac1026f5e44632c2c572c545078275040655587fa2cf6dc231a20525289d97bcfbe31af234c78583d2050a81cd1cca4303aa30b61a787d132edbdf729ccd1ca725c25ba7bf5390d923bb236a1f6b86af2dcc29c579d2eb63c9af62be7cb849714fc2504b4bae1b71e34eeb33398929d9db148db310be49c0fa15ca0ec54f43982e356e33ca119c10464c74cc14675c523e100b1b0bcade9944d8c03ea4058e1da967522752315b223982b284be4f525e90227243ed4e84c95c41d01681f083c09d8b420f2f1b593db43338c63c04aca87ca0fdb0a38119c101b819732e3b25c33ab628319626f8792e901d934a6b3450616c7ec7d66e4ee702c70735a3b6178763e06ca429af2ea24e7f9e28a73cea3608e56d6408a96f3c7d8fc795ed1dcb1d938ef8e4157ce399f007632b7f0700e845db0a6a54a1e4d92d55d8f15d99405c42973a702a9002a2eb29059c92ce546d564a80457e96a1f3a19cf4b79da876ac1662e64aceec9d515059272bcbed959dbe2b038dcba9e8daea450353beb7ee48668ea084155773fc3b59a7e06ce4e003cb5a24a3d73573a2e5509319fb2950f487456ba1df8a88c73ceffcc6f99139a2b9c1e11575677753f22696b28863ab80fd960ae5771ce396f6bce72fce46f3e37e7b4cf8b948dedb21b0899d9920e9585ad74ecf2ee1dab7377b1aa65f5093274ede8d60c6f25ca823e463855946d404356459b73722f4d0b721aba7c047bba3b25a0f7de7b6ff4fb87d82cc7cc049d8ce7a55a12c9207e2184924102133e25481dc912c948ba57a466ed4389fbc5c1203c411f0023313d91a7a265c8c30fd09351df09b51d2808de2f042403cebe50d49425b5e15b9a69a3a650ea5ec69a279e5834a8be579eaab8a83569737199fdecfcfdfa1cd8aff7deafd3dfaf772cd71b8527e79cc32c618ed6e5c68b730b29fc35fd7d51eb59b6c0169cb124f9f432d651dcb8df8e6f2837ff5849eb9ac3323d9f3ab81be79c7b29190358b1ddd461e564605e405ed65ac8e8765ce674e2da346af8dea3f2bdf8b7cb1d0da33bb75199f713c09a2e8c1325a6248fcc86cb021a12ddd355ae248c8d864e32fb7176be41a62ca39fe12d15b8c599b2f56b16fb0463816e8e6dc245c00dc2baa65acb17a5b3b2e450194c5a728a4d71612d838a08ce92965111d20770297655ae9e243cab5a50adad0ba5d13efeed267e0b276062255eb8f47eba2ebc4bbab4a720ab1160461a1177756b2f665b57cee8dd6b8628c6d9d9c4f9f0d377a53516d4212e44946a161782a59995d428bd219485916632b9917ab39238f3209324267563f4b494be3319206074664551ce54a35462b060f89ea88acee4a864e0abb12a069e94cd4e1ed513b37465927ef0bacaba5042c3e05bae2dc1a1f89aab7a637e4e136d5814079a4a3cb1869a7b5231a70c0c9e0f3fca6a0bdb13a64dc21cadcc719755722dfad076c5979021c0d618b13eec5c563afccb4b9f099d21d54099f68169a34618fea0e45e43a81e7cba3bd0b9fd60fa23288891b98401a1f23322c70e8408e4c804c7a7d95936e946f72ad7de21d05b0673b432b04c68d560c8ab873479180709b7c9de7beff29151e3c28e89aa1992d5bca3754870b45a54d2beaa2a70223e1488e6cdb3e65825e4a139b5c656a29542041d9184d7250ef973f6bcd0b218da4f015ff9e87722121caddcdd51a9c9122d4b37161538a314745810ea66deb86eea545c65994f2737ecde47888a5016ef1ad4c239e754e38f8afc5193bf9fec7befbdffb8840d2fdde89420bc58805e70a2494d2baa6402c94e69c032f602f5f3e2c47991ee1899a0b0d42425bd2bbd15129a0a6919793b682f8acf9a469ed6c69479ae282f24b8e328f70124db17c29941cfcbb2cd4aa7f4bae309b183c26cb9f2e50677a2946da2e2324ec94c40b05078554cb44d5434cc1c0c75b6544366a8cd64f094a8be611ece198dc6917a2deae568a732b52edb68fb732f477baf6539d830cb9c4b79efbde79cf6f9715b467d01c7b48ff7be7682a74bad360c2fa9cc88c98a28b4b30e12763837407061216f128e829930472b3fe523affcd950a88a999e9345914dc9a81bc3ba4a066145078381de7ba92b0c44dd011aa0014a665f082c2625bb0adca8216d54c25454e0e1dcfd4dd9e940e3515b92a36ad28b6049df9a83e33813b21146e10981c6196e24492953269ab6ac5a5022e8285b420572bec5dc1d8bd2230102e7dce8c284e922bd0e355e8197b393ea8f604fb416aca27da0ba8f9b2dc17215913051a20acb67a44f685458c94d39255f4ebc273cc86289eee19c732fe32c73cef9b1f7de3b16f3d781d5acb171f049830112b419922db58fbb116cc99444392d6590269ccae49c73ce2f049dbc5a4a6705689672a65070017565916c11f1e954c5a958defd2d71397ac69aaccaa51a0aabb3b80f552d5b712af30b8273cea9513feab49e65a9ea52e325a9107c52a1d8f8ce5cf7594b60460dd47befdd28c71de80d35f9ce9ca4512e7a3c137e9a9a4d1f0ad2aafa223b859b1e3a25ae959d1dbca1a1b8ecd104cb4c3c6724e11db18ed07833f0f4561782278a4f8c04316d61427a2752a55525689501bec7cef372b46a4cb9a693f1bc94cc628cb994b15619cbdd7f7ce49cf31ce32c7361079ccf4e136014f5a8670947c433c4031291af122d6126486e3eef879e0b472ed627b41094144a69757a784e48152f79125e119a73da87735e3b05450c7791030a76f1f355b08b1b7612ec388e6dff85712cb43de03fffd8dbc5382616ebdb830bfc77e3d77fd78ea777f1fd28783de2ff82db138bf0edc23f16ba5f588ca3e4e1fb5fd82ec64df7fb331031b8f8ddf277e50fff0e1a60dbc3fff8cb5eaec894afd8dadd32344eadd88edd72b18306b9e862f0630bccde9e40e1bf5ffe6eb2c20ffe16fe59d4c4bf117f6f1725107fef41fcfdf7c0ab00f9bb3f86ba0bf0df150a8b28788b71d32dff084421cdc147f816f2c627da450cf5fff605db825dc176ff1b57b033a81142c0f09fa639c4df4318fb1fdbc5137888a3fe6ff85ffb6fe1ffcfdb15ec60f01a81e85220ba2caed2fff885defe7b9a43843fff9718ff07c01388175f0211c2c78088f187c4e18f50c450138bdf8258dc204620b61e7833f856ae373e86bbc8c1ddfdd7c8f82ecd01bef82ddcc1073047fffd67bc7611c3ef7077ed627d77b8677c0de284dbfd4537c3757fee2f6644653139cd7563d7f63fa8e1a681dfa56d0fe097bff00bbf504368cb195f68138b19c3ff01dc3160f87f63a3aebbfc7d070db7f0173b68b87fbf1eda19d4d41e83d86ee07afc2c846ecbe1fe9ea18878bb3cae5deca0e1967ffd135ad871d42d80ff34fde3a8bb80f6a69bdca4cbf266e86e328eba145c0b7c7b26f7bfdba89e718feae6e06ea37b3277dedc3d80bb8de2a131eff91efe377b27e06ea37dd03bda97bc33dc6d94afed8ef62defe0edfef6a67beffc133dde1eec9d771eb43d8aeffdbe49ff39cd3f0adedb0dda1e9da6e9ff83fd07dfd776eda22ba42900bc5ff75bd01c849ffef17f5dd78d80db3b51f86508f5edc105eefb5fe46be2be32b7c7f0bfe07d8945f7457effb274bab63de63ff42f4afdba1ade2ebef7f06bd8c5f79e8b6fd27e6d7b38ff61eff6848bcf3ffddf8f01e17ddfefd2ddfbe29bbce92f24d23487e1776d0ff70bff4c7b402cbaf74b7f8d0cfafd3487f77bf8e107beaffb87106c8c1f5448bc98890f7833c47f43edfee2060904efd77e5184bf7f4e747f4d0cfe9708fc42ce3d4dd3b6eb7edeb6af0c77405bb876878ddb43fe5dfe16dfe2b734052cec09bfd9de00c3316df70e1d36ee2fcfdf6f78e3b7c386ff2f4afff30f0683bfd311080237306043dddb290438feb4847b56d40c9c9ded71190fa16063555ce4b9e6bc94d6801abc9c583aa142b13c5301cc750985854a125251bd90b3821212af38a48a921fa2a263138a0453d087b5f9e2f474340f465d3d2b9c482ccfb666352061578ba35677f4f4c86e7e229a1edea0c4b33322159267b2a42402abef0aa22dc544a625c2fdda74ef2da44eb9cca48ca54c6658bf455dc8785ecad33e9f2bc5c636e55ae7b1f97519bb905dc92e65d7b28ba78bc96176bcd221f1240d92a9038efaa824ad19b69c3100b10380a001b31800000300140482304d04b5930f1480032db6b89c984820128c47c38040100483014040000c000000402010180483c1006240a3235603c60ea8fc12d4e9f3af33d994c8099dae6215a9f45bf5b954f82bab25c864df6b1c6627021c5d61b971215704759209b98b980b8ac06f989abd1d3dada8c647a7870b989a0d1460cb6043960901f06bf5a74fed05a2a2ede49a5edaefdb506bf3dd6b17c14b5a6daf86f20dda3e7b3b3dc3988bc1acf90bb875feb0e1fd14e2529d0f7836265984a0051ac5b80df304b74d4f79a1ec00e5acd610d81adb5b03876b86b241256e8d347e23ed5d53e8ab6f5ac4eff9b76d472d727ddab574ece5b3adae9e2c59d87dac7b72499bf2d5ee82834fec449e0e1482f69d91998f0057568d9e16931445989d5a26544fb7a3dec2737f8ec21385560442ee69204da4bf674270387d602755c982c3ae80a6c2305df712f864612b2bcaad9fe1a04749c52554cc0e575fa8a8dd37e7ee5616b4676dc5742d1e33bcc24d4d24dc3781e4899fcbea7853bcbd6701a742a1b69a90c3c92fbc5f5b38ab509716b71cbb12e891f28abb23d06155d6adef22dd0ccc725d492b0e0deb1646c6f3da47396f3cb6c7aa8f019a0d0ead45d2cece028e91f54e0ed5b613cd90ce0844a0582872aa60d4e77344114fe12527ea51a5b74c9a247b75313920c447da18dbbd381aee933333357b7b56bb00080670275ed6edfa4dcf3e3837753ffbc9373922aea598d14e2ac33695ae5d68419c791c7d2a2b44802d166500d5190f9df1bc761fbc226b245c449b059b4d3ba2a82b25a83932f90be5ba9c09c01083724419ae3422ccfc0bef2e5d835b9e0d44bede5c6cc0ddd16f0b7886a8b324155dae4852f06dc0ba8791f5bb2f0a8a7b0a1bcf866d5e25abbd262e1724e9b50079b35cb8bb7db174ecac8b03c01acfbbd643bafdb19f1d75c98dadc3a8b33cfafb71f0e64f36030bd9458fd9bf4b077c7c7b2a56d99e438392fbda847970725dd104f0af1d47ba614bedfd06681c3c48518ff9e097e4ba5c1880b6a15adb819485081b1fe10e06dda21f75a6e36cd94d2e980d8c82707d74ed28a68b2a712045695e7f43d6a9e129bc36b1d29c7cec85cdc6e46a08a82310dbdf30d3a24e0fec88f399a2b17a11f46a7a83c061c2a150b637fa34bda248af2e0a45144bc771c39836e5c0838985b4120bf05aa88b8e403fcd55b7a3c052434af2d9ae286f822af23362a15710d66f5f2be43ed072f52596aaf2a98dd3acba45d5eff7939deb5e2c83621a11f74feed5276e2cfebdb92bccb767bfe33fb1a4241c7f881fb95b7b4b9169df22e9eb09c9f56f78012af3e54f57c53930ec76e003d8674d871da2f83b0b4872f090e34ec8d69879a4e402e9e36d1f5e0d5feda090defbc40e2fc413907e7ea9a5474a8c5eac603280dadc059abf4309be3e5dfe7dada60d62fe9e40e57293009dd32dc5ba53d9913691ee7ea5b95c1b429c12ba8b1cc8fc58e0c55956a60f0229d9ab3d0539400a7b0dea3ea8867fd711926d0681af9c333e6b287557bee59c11a59dd10a77bdde564b0a65f88cda4280ed703c6561013f312b9e13ad43e602c47c6bcec5bcc59d0715984a7b09aea38daaaacab91e06c6f5669494c31be20d65ebaca0764c4043d91af1406d341127653d5f91caa8b15a356a285b921b5202bbfc2334095b0227ce120003848d85741661424980b055d1178c4db80ee89da795ca7dbc8e76f1fabc18823824c2cdbec100bcc4782574e4d21d9096246d14198255da3ccd6b402bceb3310b5d2022253fe528bea776d4721349149479456b61e7f049726d129dfaa5b2a0f224f1835df26986f82c4d6dcf0e84ff9a085768379d76aa610d26112e27f465c9d99c3d92e056214a3f5b688737b3aa9d9cf53beb329f254fcfae87e3c895ce823256a650533f0dd84936323cc70641d0d1a557c57fb059233519f7aca4b1110519c12368dcad50e64e8d3f643882b628b0406a5b227d7554c89d7abc68890270d2914caa9a7afcf3b50a64eac57b98868cc410d8bc99562e83620cfd28435e7b7d7d4d8348b4f0d897d76a8894ece4ae9b70c86f6818c3b714a0d65a59b34bf70ef31027bcbfaae54c0234628901183468bad05fe7ed4200799e7519cce87a5036c639d5e8f03a826b20b913640a801c637230c7e26a79c3553546c29da7447666e8e3aeffc4db0c5c447bd5a46b1d216709aaa3adc500ad4cca796d5523605512645e5b4c6bc6c4c8726d59f3c7da3a7e8ab6b736517101510d22d2704ba7c5bf389433724deeef2f74acbca1c4cacf9218722cb5112b4bc1fefe475ff4080cae8de4bdeead4d092dceaaee8698e17fbe8bd8e732c4edaabab0f7476bad78021d9fec89f5e675ad5d6477b2f37991b189576547e79de8403ab72275a0b528e63e1a8d04720c9e8a7c89430d65083da2cd0e7512620d055af3085aabccbecc199d4f8870661bfaaf454f729e21b471747cdea22884b5146890d5abebe153a035608025a9c126c0beb0b12b932d06b476f56f608db7e8e1656c5a30e826f55ff9617303fa85d0aa43e98f405f194d2ecbc9db5f436162d41bf21f428c681e7cdf2481843c23380743ac8d794d63ea9e9179b66b46d8b6f9f4465e5e19efb5e24f2f6b6cfc47732a807c52258871f0ad1a5d2fa3d3ad19ffcf652c1fdfa65f888564d7a19c6e36616d4433ad63b8bd561c72117ad24fc74b9aa4b112f8d9ad0cf8538c4e0dab970f4d782a37a67b3f19d06625b2f14c214ca680cc26f3113421c23d0b8aff8c089676c3744673d35c81917f8b7dcf95c926ca84ce602170fec7d5d290f0073d058e1a7544b44f2e97d036960812572e124d124b50ccff88d8eca0acc193ba5451f333dc40c77d002fc6618a3c1009c4b5710849812310cd75c14d39937ef19c7a648449dd10530d159d2cf9c11be2863bf242db198a3d54d1db158345cbe8667a96d1ecef1d32cd258a9a896991ed0822c388922f049d1cd4611d34b87af7af30c03d6c7aa1026cb5b14f94ca01158efe85a51bb7813eccf7ce318161c6881cb203d0a389a794a1c5ad0b117fbadda72bd86e4ebec478a97a226dc5fee4d9824a2d67db6fee29f3a374fddd5ac71cb8aaac3faafbc4681e9417ac367b8a1e18b15adcf61f755a486c34ecea6ba754a8a2b5d348f5052842b72eaf675437b025b94a11deefc8844878572b8c6b89897e2483b83189396676d52b485c8652e1b8fe33c92dad76f1e9f42b0167a8d6cc2895d782974acddaf543293eace73ee3d76d93b2206501c68834f2431fa9bf676d1c92c0bdcaf48ac535a3eb6ee727ea98241cd193d5618a26f2886884a9cb821a0999215033bf503c3e0a6a413af5fc1da8265b39054bd144dfaa58627cc6b53450d25c2f207229b84ca5d5bda13b9adb6c41791117f7906f0c8e0f7d4b58bb20cadeba25cc63795fb26b15bf3ce3f3d5cf37696addf7ec4364b724b95ec9da2c3e6a7df28d84e43ba02e19cdf7b2729400a2f6062409f59ada4bdf5360d0708450007a08618bebb0ab20455227447b7aea6b13b912596e64cadbf115f3b5454827994157323889604dba96889aeb1ef2a25d6175f2386c80498d3d0aebe06244a481fb51065d7ee825ac1f4d23198ca2fe81d4639582bc338806dc07e05117dffdf340b2a2352a3a97151e38d9eb828782c31d81dd380a6f427da46313089e1c1fde26b48a24a1740a7f87a504e4ed52481cd4fab05a7a9f8118bbd85eecb060869668536bf7e09b08607222026d0ecbebb9136d344fcc8739a757b36c1ce2debde693a13c38ef384039e9515022496e2fc8cc52e7b5d710a15e3e47429808bcebaa975be4fc6563404106a520f1f429c371dbc7cab662fdd7a766253f35e305acbdbb906786dd0aa3d85903fbe52011ab7eed9aa9292fbbafb1a178a3053f6956a3fb8ea22ae61aaa1adaf60525a6229c4b91187d7d45c0c134ad3ac0172ac3eb365f82dfce800c68e5099a878b4d9c04f4d1c62440d00cbd6990f81c2229bf5150a186a00c73e4a1f99022fb555ee01d30fe72b224fed847ebea40ae5b46745f6a6d59b65e0fde6b86d838c92f588abafbb41f25c2f7037a2ec49ed7660b7389897770b769de83e31e08104402d3ca52bfac3448e2208ac9f1e5b73f44f55dcac9cbc7052f169c3b53a2034629c735dd85bc5476b6ea7511da47fe890cd1ac00b69990c6757ed4a2c463ff629758be71422fddfbc6d41532da735b20c4061cb146fa0fe0d88a405cbc25d44ec2cdf808d601b0d3d8fd93361687bbdbe15372d8c77e2722b9c121a4094591a3490681db23621ff7b0d968011b1a65349205f3b2c44bbf98070a72531ba97b0fa7937e119e0b734b8d0e4ebcf5728bcd86e21c51c403f2770aa99ad63716f10c43d4db53288a92e8b391572b8d572379e5dbc6f353345311786791fa0a68a2aec98ca1494037930419ea008d40039a461afddfa08fabadc7700e1ba3fe90ad7ecb8db186b529865a628ec34c70e72601e4b3dca210af9b651b7953d7469144a3efec5426ccffb1920a5c15b56c32cfdd0d3689abe9ee0e20213e6cedb5ecf10b4b513604d24f4e43ea9ca02bfcf1e7e2fb43dc91e6c7c4ee32e96c93613e430f61026f06bc52aa311b58c118becaf937705e8f235789f0842d74a9ed9115ab09793f95a958e034bc60b50065a717d226d50ff18f3769b774ac5ba04019369b6fcc6a5f308d97daf62db3a59530d9868035205aada94ca9b2ac0cb9e84cc05a1453e539596cfdd493ac8d4b9861f37656e6124e9ba31adbdf9e74fccca4b3b7ce93e08641266994375d902cb9234d0c17ba556929b84a0db33b66bc7495e5b042fd6f733e15dcd2a98da24e3f7c41c9dd8747aa59f53b4e4cd3c29ed3d2a3064d0e4bb8f5bbbf876d3f9aef6f6903aa3624924c7bc35e279aed7f9fc2f6a6a5cb22f62388b0b7ffc929bdc58dcc6a53ba345250e399cdb76c087e8110fe24517aaa871e9d38ec232acc71ece7254b579686e6a2c547b780d5714fc29206a241b271fe57e5f86037330887f648792249dc1fb1c211838d10f53d302d5c6930fcbe7c3d45b65db6addf8c6d6e24f8c8241b8fd0b612ad79ae8e44ab41ab5bf91dd26e9d36255d665565fd9b4fe2d829c4285a00294af38ccc07661552697ee70412123fc1761bdd2df15bee708876003dda83c77460474a616f56e8f3189c26d03a62100f1d44a6585d2a1badd52f52fa0c0284089500f8887162c5c5934cfd9040e70e48c9e532e23c26c8946e82cfe5a33767526787aa04291f7e420dc3737e77b1dac4155938aaafdc35de270c5501df23a812c45a19b4b9934b911dc84e6469ad34b3d7b48b419c1951a8a3338f3c4a42cf08771045fd2385e761710e583992552eaf9c581c7731abd35efbd285b59be6b08808f7df111dad01686a0b91f39b8c0cb2c802f3d832d0897e01fb88c37274cb6ff052bd2dea87fd7ea79b642f260363b667d04b776e5ac588515f3c204305c9f68141e6d7a8573daf8053910b5f45e75f49f66e964f36f0aed26cfa2633abbfda3eea35c05890b423babd6301a9e367e038c0d06b9226678f25f7e772ba1d7474896ff161df81c5822bdbcd77a2a984b860911737de916bec0922322d646b8db6a1349f1fa38e084c2d6ac176d24a1463efcf0ace93296619d08dc9ee0beab3652c2e30f8da53bbb083437654115d04ed0eeb6870983182a605cddf90b16d6ba4f55ef709e7dd7716381a6f27c77b6f44dc28bcd8ac7e15a4d82721bcbcec6ef3a1a41cd4a3ae52c65b77a4811b02bafb570d8007594fbacca49e38bebe8b10d041d4a628a3ed7b221f10058430437ac0c62708ff065d146f5fb11ce450f9fd9ae40e6a0f3648d7d4d02d42d9c8528c45e0d553a76cd5d8b7ea9814c2e6331406a81a140af7d99226e39f323024ae27c79c7a1f134769df026a824d4fae9e0f0c6a1185c13263cc1bb990832837ff61ce0afc725fb1b80d67fd18087443ac3638efd68ef517b69464e20548b7063aee10a801cc2792f46f7c13273e0c968adc9e58a4df404cf373c304a99be6d8e1a13de259b1eb85c6e1f5430822b6872c9365f44372b8702e5847b45df3258d7f21cb57ba96bb489c4a79ad469a326f4cb30762a4006b0a09094e4f97371eaf49fe8e86bde1c27c5e1bf31590ca65c2a54503ebc7dc670cb23fed4e8b062a79b875af370c259d8136f2695a18c2c5df0ef689e966c0d69e5e0ec9b0737929c40db012ce1e5e878a8e3c0ddde5068e72fe3f2264451284392044685f87428cab51a666998f2282b837099894fa0724380979fa96f309369f91fc76033b5f9dceea004a61bd99d06641a0ac63d04fa79a76ca623fe10973546ec0b9458bf4b65b7817b3434eb3a14c6c0f6f0c22e52d3b4b67188edf19f38694e4a04ad3b1b8225eb38ba94fa4e1d0ef97d6e2e2bdf2c340a8c7e8dbc61f6c27a18469224f0ea3911c56f1c835e35d34773bdd1aa4c3b78c5703cbaa7a42e0996926ed6d66fe317c2b4943dcd687be6fe83a00d4da18e234934f2401bd08f053376408885c803ee9d9118b75114e3eacc2c284412087989bb0db88e0546a00572a2cc80e92cb85b0db3ee131b3368ee26f3b657c935c240032f144d4336b43165ccd79203f806c908be5cc45c3878be997a925a949e2f72275ca38c756064623206a0a0cb7cb3854d1081feb40ff95a34c9fb55a28ee71024f88dc1030c0d44e5ea7cf18483753d23fa42b265d1c255cf5350a52405ceadcde78e964f64e44572803fa5812cd5b98f9bf5b98be6a8426b43e9ec2b6f1c48ef6381e1252f3181d51c33bb8e41b4020c102281004a7db3321d28a677790d01091304ca112199b0772ecd7ba1da774ae2358494dd7ea71d011cb655f5319deaf409cdeb225893d30888a409a7c79fe3afba4b2b5e19eda3e046969319668964f1b645c6cf3d313ea2baf94eb969ea01ceacb0c44342d6d194af349e32bf107580b7f5dd9f93ecd7d94b81a459f5d12ee51d151b30c055208e9a46f3945cb32a66b8c08e245484a298072623e4a122f5f4e26fc58d6abfda479fb523edc82904546e654898ebc40efb1229b944aee71fb8c9e10fc1b19b7e9ef3594f621d12b1fa998b9a6b3523bb89de6392af258789358734b297bd0216c17ddbfde124a183b5a7122291a0867d61ad6ef4b52c7f2d3b41c8ec2705361a31dd5f45ae1d8799de1c9082e5f71ff128ae1850b4789c18ebb821c604b8b4f2c5333f1d8dd6347b4648a03ccd5a6cbafa5823ed83469c8329198e2735446439d99ee7189ac5cd597d48987245456496b786ab124ceb3a12625f4a2e30939d73ed5e324792c554b1b2f7b86497f73eb5dd9570c9747bb1aac521ae19774cf46e87e7880642e6bdd4be65f782b9338ce1c73999b7357890f68d132c5594ba506ed18098edbb6f3e34d485c0626613e9acf588686fd648ae32eb9eb79168fc8fb0e273620740d621b7304035f43583e71d28c880c829d3982bef9583628779188f3c3e08e0b9c19d9bdca4db20f32009f9894c1e4dd34fb2faff158596636b2c7e53f4b030d6d57195edb194261451e9b2d90c0d8d18b3ac3ae42f28ddf7cb14ff2af60d698ec6534c2d623f13b9e5491e6b55053cbe3d90767cd7f64504395128b753f2bcd4e97634d923b27400b0ea3d2233b667cc24842c83e5c4654bdd04a35ad825514972c9111378d591a29a07a4d0407914604b906faabd05e10bf2c8d88eb4b44c0fc28b1a9b808c2e8d6d698b1a96ed639ae23c5f7fee60c464bfb613e37188ec32ac583f26fe02e8805c4cec9748af287633cf9ff51b12508c9a5dce85712be04a3694f3bb588e4c31e5b179145ef89eede879c4326b2c7aaf39a2f3c578ac65d1b948cc26d84bad64c827063950815062b18a23d57d8d48b91b8bc3ef1a6afd6827875ad510f002fad643c59d837912ea5a30394d8293077d2b47122da9f92c15d681d48358e951f32f867dca5036d10518f5cae24308201b8816e5d4c758c44e9687e48b6cc4284d1e1b43d34b0f22de9f31d88bccc01326e753e7b22e30d8a22cf30ce83dc8f73123170d83e1125b012628c983b3d6062a02789a683569d8bf648c653cb92d4e2be7bc2b6ad9d8798114fc79f18ffb8215585047ced164e4d65d18832786bdf95086b0ebe21f54a508ac01a5a9379c37d6d619f4405a05529ee834b5e85b746a460c370de6c3f877f8624cd4044a58b477d906259af4ec0ba922b26d6a301c6644090e799136759f678cf457ae44f6c719adfaaa84821a743775d49e6d107cd5bd1e488f0bd7568a256977741dd4d579e6544239167e90c1bc2c0164eeb9c72772ab08a7827ab477d8b1c98f90d2bece3d4d1b7e15f249c692df9d91db92c2177e66fd1acd804b8382571967b1b9713a49a1f1648ae6f0a6671711715ad3cd1b1156ee4e6a8b9b25751516627e2812b08746b6e2aaae6a2c3f3901c2af3a2501d945aa6a411e3aa433077875a3bad8e39920f5cf2f6620ec7d2bd90b0e0304a1ea5506d9afdbbac7b390ab68791b87e108da282fb885c1348569047acf07540810dd731bd54eaf6cb490f8d1b385b78598dd2c0323d93a88bc88356caff7ef6c83f97d5caf1f219509a53fbd5bb470eb906ed44d1ebc0f36d364623f6810d04ae66ecf5035ec908ec2ce9b4bc4d2e1e4d2547dd2412b3f6ecafb1db6832039b2b4a74304e8aa8d751321307bb58f78fbd8128f237894104c70733f896d010df702f0008ec7648c3ce99d4898f1d10a3f1311950f40f1d7520227f4b4e2235194aa124eff989b894db20c3105a39bd114f2ce0463cdb1207cae95cb454975823105da9893b5766df0467c6c0fab4a35d20042388e0f4d59a87ad2bca26607102f2084521c8470db5b1aec0aab1c810bd0d9bc0164aaf937c95e7ae43f7bcca7021e465f67e48a6d5c2e722877e18baadecc2749834f974021c9c3ab205d718a6b2d64c72b1edc2786f2fcd4bc3f2b65646721d46ba99005f7151d18a30d521490f225b182345742df5d2b7595349bba9c2954cfb8d4cccfeb3e92d6dee82138044f24301e8a440178ec2af2e4d400b31a6e5c7d06450b446a4ca989626627a8c56bf43a7615c8ce6a5336b1ce9eb1f8342bade53e516ab1614cc203bc5a5172cc8b66650431dcf294d3089fe71ed82b9c55be72957d70dfde2531f1d8e88e407934a319be7f699a12dd3dd9711198c9d329fa524309f9081862c66cfa6886f9829fb16d6f36b6af25af7cb480e8945917ac497c070b2364c68f74f24e9c94c4ebe5df57c1f7c5c280eff202ec202ac170555632dc5858960e63841e7d905d30465dd06fe5a13f45a7b81932d5ffac8003f45fcef7843d1ac7b49855b067a9dd91e4e5c0eae8c128909d4a65a6c274f31b4288e5c5b04889870aa3ecf739f97eccbcb5739ee43a98aef561232cec9a617acc621d33d8e19c75bfd9c3fcaa444c5b108e1bb8e48b387ebe39cdd286267ab1913411fb5204732564d4214e0fb4716cfe3fe1bf17ffa893ec4d8600ab3df947777ad468e008d5bfa2c6d299708144d11e0057902200fa412ba34a093cce0c62395f6eb988732d429904483c28759ff362e6045005cbf2288e7b3f01003db62f64d323b672e8929d8939e8b7d5d730d71eb9903aa7128125cfe8443b5d05e9f80740eac43ad0e0e0c8443ea9430f78286898ece7fb232757c1cba414622272b399d6bf3e649785a91e5c264fc75ed6f3221262969093c0119c397e35df7a69142f8d63541ab0c8f50799a78c83cf9443d9f4e73523b8270f236478da8c558f8b645726ad0beaa8e4e976b5c65308e34eebd2092aa36218f3c3e261026523171899a9441b52110f20cf778d9e231a4d8a7b8341d2bfb275eccc159da6661dd8eac40318799abc28b98e58462de3209ec6427605f3bd990c35f205f7ddcfa3ffeda0b7970af446b90efc5e9280cc7f1a558c2c83bb1644cf7b464dd60eccc6ea0fceb4e5ee287503224dcf54f3302d6f8698188eecda83367fc3c6f646faa2216d7ba7d473c610dab9b5aa08d3b56e3a74a3eed257442c4b438f270b1cb615941c01a0e348c213e09f37e70d9833b2ed8367a667bda1c3aa986a9c2f8c493a3635fcc5c0163b94f5e732c72998c81e5ee05bd5b1120bee581d7b0894113a13ae4b914a1d03906dd60138bf75d78a1a8e3360f09e698f870d48a5ac2138a61c9bc64901183b2d8455c35b3a191044349fc5dc14a6ba39a40557175395dafb63b1176db8c829fc59e584252daabd86400bf04246885188c35454378461b0266263d18e265437592737242fe58e865572fca7dcf0ea8bc7cd4cb37ac5668b7f5825ff90e676b6ba8467a5f783f6c288d9a67067f3c525356c8863cc8429b2d1fc340ab5802d5e30eb61ae8f0080a83188fc1c60edecfb8e077ee38596add0e9cd11c085b337541114bbdc781c859f88e2ae9e8740d157d60f978b968c6f19d31d1dcdf14ca4b0a00032d155c0e7ccfe312c799d6813e0f19f9417c8a2acc2b1211d536840b29fbfde5093c224356871ccd1fb1eda4506de514aea9bd001a030ed99c70c81fb2487e75fe381593785fdc651c4e660dc7c87200c772f3e7e92cffe9a123a5c7243b30178ebeeb23ecfe8232e5872dd0b398b04d0327bec0a8210469af2bf580c7f63ebc8c31f42d4f4afd8edffc3e145b7d6a8e0098b611390c83808c4b94a7c0a76cbc878aa08d7bf424c81124b22c0dc772f02623487523f10ba2b18c7f2af056c9bba09812eadb90272322d90d1796769baae372864083191e05b2fe48b813a08383380d06b4ee536de5c5767dc3c2b8fd0fd7d051767387135d6f3819a8ecb375b22ba6b7889459b060bd0d1639198f21e34259862ea4c687216fd04f842ede65682a45709a7dc7c5d0e6e088827031541475d5a84e67932dcbef42e0a76a5473cc2b9ef1200f0fdeeea106993602d56a7cc5f0f572a77f8c6a1c4282dd3c3830b9705e31011cf6034af3229053305c19a50d9901edf415b0165fca292e9d0b929171c0d74a4019e6c9e6ccfc4f8cfcf9b223e392827937102c008c03cdc0962da928d23f6c5e128087a9211d8f71b9189e1dab7df4ff469a442eadeceede9964ac08b8081909efe296fe7459be5d48bf450bcb7ae9c62ffdd25e7ac81991144095dfd23a66340270dc431de3e5059fa87ff781079674b98cc168b9fd25c660ac906e3318fdd67506a37f741d83d118c6c7911ef1c59ef450d7883d34440fb926fa17f2f590002e7bf1b09b805437a2288410c72bf868d53c15a5f8ea1fb4c2387feda8b4aba355ffa8cd637d8c30baf5ac8a7127b60f4e25c558555524619ceaeb4af390baf4bd52aa24abb1fa5a79c5cd53417c5af94663b1bcdde91f99be55adcad2cef297cbd43b6f166caddb3cffa2ba30df3c16c96a9e1729c54dab8e94e58e2359ebe8ab11fde6910f9ba7799ae7c7bff8ec9e0ca9578038644432de2856d49c73be09999cd32909610f7fd4d3163ef440467e4f25eee119a7af8ae5b349710f7f1d474fa66bcca7dc25635a3607d53fb238eafb3bd82ccf6733322a7bd3d6decc6ae9e4c838039217874208e7cb97e91af0e50ff962f9e916b51efa4fb862e1cb339591a75309299e91139f56fe3d0521fc7e26d335fa67f7c5f2ef2f1999fa7a2b8c43fd4c0b16ecd5e2808f6375d40c7f460494bda118d1cccab438eacf7f9782f77d4dab97cd8c58863fd3313a57f824ff3d35d335e03f7b31ac7886e2998ef19efa198a92319d9bb1463d42ce0695572054a819791223157a97a3bc7eb34c512854c867508e923eeca96dadf5b017a71ab5f6841a55140a35aa5a5aa850a656da5f4d75a59343413a253a4b51f449d7a0be512218ada3a0d8a742197e8cf8dac1ae88d1629c5e91d2a976b2a2287c62a19e3ea15aa3421415caf16babea5791d486b498f7a57697165bdda50da1bca996121e626f620542a1b80fcb7521432814fa247bfd36baf1fd4292fb5426ad4a7e57144afbab776af54ed58a6a47a1d89d0652dda7d1480a855d91fbed8fa2cbd1523bd81599dac9f0258542a1c48a2f37034a8c54c87d5c88fbb80f8562c485096d4b5a8fd089b2dd6f44db4ba3adc767d89b166937bf2fcf5a5876709d1c176a40224ef547e466b2bbb5f8266b9c7e8db7205d237e63414ddf267c79963db99130fdab35afda53482f481ba5f4bd413ffd557dfc4bb69e0c5f7eb4f48471dad3ae8c26465393d89a2adc82b4c03d3ccb8f0d9f5ad49c5cecf094e22b88fea643a801e91aed69bb787818ffe55e4dbf619cf6d55756475c29b97c0b12290f98e2ab2bfdd73ed2df01bbc0145f3dde09d325d56d2d18d0fe6240ad01c9dd9ec5b56fbaa3ef0bab0bb9c8f9564b8c339f7e10daf46d7c5bd948a5a514b72093aa6941faf6f0cb895e56ffac0e06445b10fa156e413a86500bd235ec8d6d3d350b6841588b7e198390510b69535ca815b5295388a4b42c93a94971176a52da14212ed48abc516952faff2282cd8a56d4a8b4294d4a9b4261a229b54969456d4a9be23b2a919026a549694551123529f659735c77d41d95a8496952889a942685a84969539a149f4f6691e68456dfb17764911c27132328874c266691e8eea47a38fa7a63bcc1a26d0db69984aaadc921f3c74e23b695a07dcae59b7a971a6fd8dd947d08bbed53dfafdda7cf24f99af1e2f41f91a1bb1c327fa6116bdf27135da3fd847e499f45f27cc6688a6031ef23c06840d08460344a1f314bdb31637f8ceca1c73cc77dedb7e84010be1ce233e2fbcf1f6fedeae1d72c92e38faec5e1a38e2bcd884b9c2ffeb6c5af6d8af70dc81dc39f463c620aff65be0ba6f3c964c29d38169c0bb0e7cf9f0c3f5a1ca3a77edefe9a56b23fba6a5e7127233904048dc6815a118cc64bf93dafde811a4d036a3e05a36953b098f7cfaa96d55d27134c28b7f9d70e2674413b31ce4f6b5b92ac5c22e2c9c4483e69406d0a46d32e602d84aa3b6934206831ef271376f4f0e9e8eb5f72894cbfba423018f6e50857ed0246c398fd1701d68209e5f8f03d081a0df3c944fc0a4f26f01272c864621671278e0577e24e26131d0336164c9a4ff12718955a2f7fe23900b77e2253f86a6f2ecd5936e24afc099fe1f686865d26eaf2b6359d1c8a7a4fd29ec219fd11b93116ad3550b3e8ea472d9ef0d5ade1ee968653fde829ca1dba33c169ae52541b49e240140b57d235e2c3eafe44865f1f553165fa4751df6d47cb5f32bf07f43b6e976a7c6af18da6c6c5dbd24afb6e239ec4fd09f72732f52ed1a5d5341a225e4eef0253cd9bbf0b6b71b4c7717c756effdedabac0571ff1292ed795f4ad697117505d0634ec4ada93e4e6f2f1e1ed6771ebb778e9bbbcdcabba5cd6461c06be97d60a7625cce35bdd952b19fd05ab87358f300ef5f16bf5a3a75dbd565defb5a3f611775712abfe6740cf018e85dcd7b916b5e6f8b63e64ee4a5c8957f8eae1f0b5a366ea4886df465c89f3276a16e04a4c6d8a62c546131b425aeadbf0194193a9b10d25acc5b10295b021c467d81b4851f6a6e9fcd21d3dbcf329f5103e1f0aa1c3a7cd6a8b433e1f8a7d9e4f8eee3a39fe94be0d1f7f6ae307da3f223b8b0bd9f01902298b2d4116f7962fe1cbb3fd167c2f1b42327c770cad3b74776f6fdfa909bf67dbf8c1f13f2243a714e542367c2e1b43f235a98bfe6c6d43489eef288aa2a88671e0fbe54599a2288aa22e7529e3031a7bcbcfa7ac9fea597369f12f103e84ef42367cba0684350e7de08165e3b2212453bf62591c2b0f310e7cfba3957b351563fcd8025f2616d114e1cf15cae7d7af1a5f6dbd31fc95c99c7ac197a91bfefc0a5f32567b536d3481d762e230f15baed5c461e2b76b397198f83f5830fafc1ab76171ac3cfd79fd6b5ae98e9ee5da5fb92b8e5dc861608cd64e8b894f39b9282739fee8e3a5a8583b8dc6fa6931ef4d8e77f27b8b74ab4854451b4a08c90dd28b88621cd92d49d28612d15e1495fcdefa69342f498b796f325916fbc9ef6d28f184e4fe48946d94d17e7c3e7974d98e278428c37f496c2861bfbe24f17a49f27b17b2a1840d21d9626235b198e4f73694e818b045c8a577a0129d6bc368496eb19606e99d730e5f51bec431be44f7433828dc127b13afb3378d993b5dd2c7eb5fef8b7c162d7670c1c3bbbd31c297d5f832457c0920c3a7d4b91fb91f8d463dbc5d9cfa4764c74aa3d110cdc551e9142fd7838e530f4b727c1e704bf88f0ff1257e581396dda34454b0e0b70ff99150b0da4748fbe922144ac79054485e4d884f2f41a148dc3e57fb44f9734adcacf669342d4287db9029060acae91a155da3bc828c82853367a4b6daf8cc56ca269ea1184531f7a870ef0739054a51320a94a2663a06c550d0610928e8206446866294bb1e9e01923678ef3d1919d1cdc8ccc8880f42f8646664b808df5319052b88972d9ff151468ed1b9f8d1d16aab9f573e75382a6637e4c7792bae226dd5943f6d654e56f966911e02df8f5ba20d7121cd27b71e6aff55da7aa0b44cf12fff49aa56a009b3180a5fd287f52d97590f495f622155ff7e25f67859623b7a3f2309cb27bdab984454611cd24b12c689ffb27c189348b092482412a9f57c89656549cf921a183b467c39ab0569405a90d6b384d482905a90d6b3a4052191685a5a70cb93f06572f9c697f5824bf8922d063e4b0bbe94b5c6b2f2f359a3a1fee1eb4526223e9b1247d599515f3d112cf8923e84da8bfc9cc7bcafb294dfb3a3b76ef5eda3c9d18f6e751d0c011c4652966c3d2dc89092922448484990904824d291242b2bf8da51912049d245ee92b9646488e408922445242449484790242942422291904476243e3b129f1d390294e3b3234fe423f1ea24f95ab9aa24aef4a8b7ef87f725990c61b324493e7c96f71f5d1f7860517847cd23c7978cbd569e7defac1f5d247cc9e468ffaa1a49d7b0300ef5230ae3bc7f79841b89c43d9c7ab891246924cebec36e34a2f0d5ce659cfef9f329ed1a6f88b07cbc2c47f896df189df531b2bc1f3d75444aff2e2b61e931565b30d4b35ce93114f5fe586ee9fb52a3f725165932b9f8ea310d1f2641920489e52432d0f8863821cdc77b2290ecb07f8c7fbd1f91bdfcf833cea7dede595f3226e1bbcc300e6552e2cbe1bc67413c8c03ff65e614f418bb9afae1e5d13daac33f787ef0fce0f9c1f38387edfca079e131ad799cfdcbd5173c6c87f1fc603c3fd80ee3f9c1c3785ed829acf6d78bf683e707cf0f9e1f3c3f7828af6640db63e296b40cb1db6832d8f7551fe24b466e14be3b76ee5da6188751575f6e3f539f7ee3b87f99e21e9e2f87673a469391e1cf5090c9c898613233329ecc8c0cf866d857c732a4b87e8dcbf8927ebd14de3059add20cd5f40af9b91e269423fe217aa88986e821d7443d3484bb2c6f9d8e2ea4eeb3c2b5a151f129424de430f16a22b7b68926be24e927cbc318ca7c466b59621c9fcf5a0ff590b5a31e1aa24a393ed5303b40e02253d78e2e658771a89fcfe8abf3ab4b6465e529be4a39c26b89585fe3cf4a80f42cdf628c5fd328aca30b9fc8cad7388dd55ef8d48dcf6c0448cf62dd154add585d2216553d86fa389f591cf3a9bfb1f72a65ea59aebdb27acc7cea6957964bfa958b4413958c4fc4fa786f98c3744cbfbdd5615cfc0b223b1fd34dd4434f5242d98b152cc92c320b5ac14fa6adbe2b9c15ad0d9e21768ec83939e5437c795176ffec8dce2b7fc9cbcad09421beaa2cbdfc16abe5650c84df7263562e633110fbc003abe2f9d139eb5f8e12e358ef7e5a6ecee88aa2d0ae45518aa21445298a52144588a8a8751465977db2cbd16251848884a2140945291222128a52d47ca20845e918ee8d68318d368b5a8c06cbcd64b2ace6d362a4578bd1e81bd4b45ebffdb6667c231a69f4f6999d1f49f8329946f8e1cbaab0118db566c91bd4c338a729cff9125bd495a4dcac5d3245518aa21445298a5214a58146be52449d7332522787be4cacf1ab459da3d6cccc4cd619f5a34395ef55a1863a3992aaac535f7d15b29594b555f2ad95ef9b955556a62cddbaa90ed5a13a5487a4b0f2bdf71ace15dfbd67efbd9711b6495529a594524a1cfaac92b23e7d42f16815a5efb27aa5227cefd69bac5fbdad6a84ef6d552bdb24956fad7c97d457fcede9c5cc497c4598d90bb71d9918e9e4ec54a14ae5d5a24a5489ea501d22e9e4f84e15aa54488ef45588548b48f4aa44fe63e43f487fb59dd1b5e81b914c995aa63a5487ea501daa43d2c7aa522727be3805e7a21e32cab917e5c7071d25e7b3d151cae893f83d70aa678ee41c7d1249f73a22e3581dd5e8a9e92cb8733912d6258ddc65912cea28be76d4f8234ca154d7568a2f6aca48a1748cd28b5704adc0274805538254f02428054ca8aad65ae1acb5aaba421959fe256366a663d41fe9e4542f5361558d46950556327275ae6281f5dddb3bd3312aa520be269e9999e9180e57abe114dcddf339276cf0458fb13973e6cc2184ed65bccd4bc69c7356f957c55407cecc942a4c426b714e38679c3a3993cda74f269d12e38d9468ce19e79c3fffebc87e8d2d9afbaa1a555565fd28c627d54c5a59c197cb240b4bccaec5146328a54a7279c513b439d8860c9359908f2629489267320b4a0112ade9e4b4ec227c7743274ba793c362ac5470deb3cc5c6ee7fc5d2dca5773f18a11fe55638cf2e3e85b409fdf6244dfba4ae9538b5afa95d25a2b253d6579c797c5e25efa111d8ddc9fe5aedc11be2abcc344725b2935b197eac8279093c118d4c961ef980ceb96e1c5e191d397e5079d8c29233b84b05e273d3b3c33333343db311992dd51219d1c469f383a8552a1f814904bce77208da67786543cfacabf771acdcabb7ceb3287b148a40ab32bf15523dd9974d26ba742a15028140a65a78a912a3bd1c94a94af06ab14650164064595201f45a0a862854e8e6c2fbf0ac9292b95dc1e94af12315789729392b5b05ff9b498f85236f7892fdd48bb5f1e471fadf9124729f125b3fcf9a4cb1cc398892566f5fdabbb835d91e3a478872947ea218e4b0787c257bb9f97ffc8eddfb7af435d43c687113e7c0d758c864bcd88f528454f37ef89442f7488185ba9b5b4d137775921bdf50d5f3d5a8b1ea2879aa827b6eab678667a4a65fbd83ec6a622dba9b596451d4fd207796a53c357b76eadb5d65a8b2deae4386c2ad1ba0bc92e8ad687bf9a487a1365f8317a8c313e6b638cf18ab9fd05737bc768f0d0c35f8cc67ffb6634eeb76fddeed5a39d5e3cfcc8e201206513c926cab1ea088067d102105b6d2d060000ded3431d03beb82f60be875a28b7165ff970ebe3bbf2f792feaff53d5c97e7e18e7e87db124b50f68265d98038f92b51bf3e730f5b7c788aaefd92277aa24ed3fd49ba8ecda341e8b0dd800e151c54ec03fbb5caee6b5f591de6fd8906b5de1fd8af6e00179ba20a46728f96610e3f72e4073a6268c206169881c99005d477db000db00554246411403c821e94b4b732f321c775a2bb29b57e7dcb67545f6be982ffaa7b82b5ea9c772ffbf364eb9f68fd1355ff346bf578e454f079b40cbf5e389a505838f51d8efccb7d64f6aa392be82a364dbc3f82b2c9f12480039bf6cc0ec1a249142770d0248a133e36a3f904ab7e9b405625430ec1d78b53df3d1de5ea6bf5cede545fdf3df57c554eae7ed67bd1bfa8bfe05ffdf5df3dd1137582f34f3d1f8f9c6c9f6debf9aaf5a95c6121b2c58cc1788a5ef0a79352876a28074ad1a7285aca6e0a3224c971564a29bca14a99decb3e3dd95c73b2bdaaa727866f50f9e4f00d2a3783d1fe54e1f68de59a666f677859931f595bfacfe6884ff1357ac7180cfb8cc1a89ede11befaa2ff1e4a9f61ef45ffa23f7fda1bf4fd2b8523f129477cf750c7f6239a62c5949dc9843589ee2d1397eefe8c8aec7dc4856846f89cc94281595e5012d41304032ee4cf2ca84749a6320bea310294030742be60b202a2a8e2065520810321555a90fb59bfec218a14984ce18413596043115292404a932903ffd22405fe8065d974106f8ccd60efb98cc3745c93a047a71b9d3d77b67c463f630d5bee59a73509584c11d2e6e9902f605d84660ce66aefee7f029ce62e688e35c61873de82f7de830fbe1e2e06579dfbabbad65cb46fcfb9e7bc3d79ce590ee3ae9a1d744e0e945dc0fd75f2b71cedfd46d6713d822a20ece18517594ef6f0e753c81e32a8058bbd7cfddeffae1d0c08fdefbdc742e4d97715f3e14b46233f529ff2d2c617fc7e87dfd05d9075b9dcda7ade55cc0007292deb0703c45c1626694121ab552a9556a85092d57236826ab5fc1f4cc9f420bcd5e50731a3f14ca522bc785172981736b09cc3bc7098a603cbe6e8fcdc83899d93ab55ea7e43682699e9617294f921c728a5132c0963e10b46e692a9f0f503698552f87a211a4d09b3984c2b984d36d98c3fe3f5b25f1ca8902139296da1aee10d3faf89f7a275e23995226ca030c2341afbef7f68341595814390d3946fdb54d7002ce6bdc9645940e0c8eeb298f735263f69001d569619c82e88cbdec327617e55aa54912480cc89f4070515322421d4eac473c2217c10bef71eb402ac690f2a4188151159ba844782db1c9e638f56a54a952a24682ca8c2738d0ed7e7eb271119d26ae73499f0f59e92effe51d74d39a59cf2610bcb56a581546bc485571f467600c86e87ecac16462344c9885a2a52ad921046bcafccc9736205c4046030dab3508109c673af7f00b2063ae5e8b2f20563ddd1656d874c96d2089694f6568b8a6f658721e2cd1eb2933b645a6466f6c964879d0ce5ae0766580982734ec9dbeadf7b2f09d67b4850bf0210f8f0e1c3874df594d20b6d9e8db36936d4557addc6c65eeb52966324bfffb27cd88175ca31c2796f659659be1e3e056165f956964fbd6799f98c96391c752d47407b9aaffecb82efdee15310eee1e89e7bd19f0da403d27ce4f796c87a53f2cbac28026ef36aada5f7e413cacf7dbe9d341aea1fee21611a132c9fd66673df9ce4cb14816643e12e3a82a9a938d9a4cc0e481da04387b20917618cf6e6c1e73a78333c0725a331fffd6434a87f2c068594c588475030c286119fe9c01c07237cf13d0823b49c0cec1120d373b202db0474eebd7fefc1f72084f055e15f83d1def83f177d98418634879617fc4a12d81540a652cdce55234e56cb4ec1c96a72a624aedbb973401ef060ec865245cb0a9c7393a2f663cbd6e8e33ba3517d74981aff59973118ef47b7198cf7d57506e3bdbd8ec1785f2f7d4fbd9fef9d7c55b6f340dd814d80d045090d5650461e902158ee99c561c439f7ce888f07e4092c0c3dc80dfae0213fec8056ae7039230099a20c2b0062a445817a4dbc172d125c8a1db22e97db45690aeb41a47db125466407a3e3338e83987240d418f777be44f143a319c254bdda430f13e7ac38b1fa877012e788393290dd4376456e382e2f19b920480c6ec9104887e86efa256df90fca52e941f7d2461b44b437124ac779d8f21cf080964aee6d72cef138223b77ce39e784e59c93ff248e4f5f75ce06e1240b0fa4bdf16f25938bcab2c0441698a84b904c410151f1f2840ca98a1dc2cb112c13edc7199aa00308f35e8eac20e1b927d82a5e9a5082a2296b104208e11391e1bfcfcf45e9e029888798042fafa109779d5b07f80bc261dcaa5439d2f94ba6eee75e77f783ef97c711b9dde5d12517af549aff4a2e4a26170ed39a78efe5fb28dfb38a597460f55f92c1bc9cfc5c2b995cb43b0ea075b91cef3d1c872d29208c11cb26251c88105d4e46314a63d1e85e9058b1c62d1335d9fbd7b407618cb8c7eba14a952a2f3bdca0f05276ee97f46ee62ac660ec28d5b4072d27a565cdecde7d331a9816d3bed48ad02c98d1285f302f435cb2dc507a25592d2fe538000e9a44710ec3c6d04296c82f160756f4b204560616074b9df0dcdbc18ae614224690ac28b750bb81bbfcbd57e3f0dde571447e5df38334df62dabff74204ebbd7791818310f770391250a54a95fc5cc6b11ca65d1642101247464be46b47ddc184a49c451cc6b99c455ec4ac63c077f73dbcd7fcc972c864a26338202080000104082040fc00620788246af8d4f0a9e153a3a706901a36086250b0e74dca18a1b76fed3bfd2eb6074947bd3f95f7c6a56e6d97561707cbd5d7671602ce7d85ab775fd90a9f18c072f53858ae70f5b07af815961f5f275fea6b5abf4e9e778729cb9a162f7b3ab4fd09d0ce0f203b54880a4da06fd0488bb9cc63a0d0278c4219f2b3446e15c8883402060a85420103140295cc0819321ca61f7311f7cc4cc934536afdc492d5927142f73877465422878995c861a273aebda9d1d46fd0612c63e0bfdb31fcdf857ebb6f1dea18aebde3230d5fa64ed2b08462796b75a80fd039b8559dece011974e3f28967f7bf62076127601a827462139f644cc9c14227db1ad605dfd1399bc599f763fd1fb17631d6a4df90ed01226404b98302029b8186b5174f2037424c913404c9858ddf148922780ea50258af1c7c8133f458ed4a12a94dd4eecc957ff004519a38cdf3fedc7f4538780de40dbf70ed5d8ca4dba264d89aa605ded03050b47fe8bb3d2579f4e8929141fe201d933753dd3195dc267440eb322c639f15544ae24add659252b4633504c2c16d4b3935910144f72672aa3c36ad9af3ee3c6cadb3b3f1ca692924e594d3a5fca39e59c73ca6a56557b5e594c49c9bec36850d0c6ee39fbf268dd235b514deafe495fb577c26aabe71a9f73da9bf934d6784fdf461a4d03b5066464fe9482954d8cc6d5f6d6ef61e5743d5bf8990dc18df522e85ad5d2d7386d944a49e9cb18a5a42839e39d9088b2f1ab91146aeb0a0bcd940405ca52579e966a0f3d7c8dcf1beb86e5e957f7a6142f2505ad2abe6d458b81df4eda4397ff2a2f11fcf43650a369278fc61d6456e3744a31a5685b7a666f5828258565adac7c8ddb38b0f81a1f493151525830b320288832eb4c77a8f1da2f591c9e4fd60a2c6ce48166161404841b4e4b355e6a0a567f8bdb79876a79746ef1d4a5dedb45f6a6846b13abb2f579b8507aa97fe3f360d91b96335837661614740396b7eee8296be16ec3e8b65011c3a4cc82825ac0a2f670e3ef7059eccd0a6ea1292cd4871a2f05054b6616d4f32457b7f35c8997d4c2deb0b89d49b789aab0e2f7f0626f7466f1b485194cb7b30f2c2c8fce264a8a153ffe0b1bf18c82d56f79747e71163018f07768793f45e8a9c6eb3b0e03916030e05336585d741272182287817faa81d5f29204872c31a4ed0d9ce953ea564f71b43791871d7efe0e9487cba365fa3b5c7ca278deab67f5f39b8befdc5cd0eaab0ae31a65b43791def67118f8f2b6108781df3e3c227dea43a5ee7cea186b8de278ba9e5b5c965976b92170a5cb320bcb2b4f65cac46854a36adfb217f5b24f54a4399922bd7c7a4fa491ade829479d3f298bc2a720a89f354e59d6d75abf9fc5def030311a2606e3a5329ff1689374c696a3bfec5fd55fd469642b2af66451ed9d4c2c16e4849f7cb90c326473c8d41732f5f576cb267ffe94773ea5145f455cf2a38e899dbbf0055041bebcbcbce07ee2f2ccde10d1725dfee5c539e0028475f54e36c2675cfd24bb17c9febde3c467d0b4cdd53dd9bf9ff80cf75d59deed4de949f4a54b2fd7c5a5c5c5559797967f717171f9538b8b7ba9ee5bfee576e680cf700f23e307170f438033588e7aff7f51bdbf8c0c9f61adbc7fcd027c46e9fd85e880c3ec10c219077c46cb7bcbfbcb77769be325bfe0ab0897af692ef7020ed38289c01c30dd2edd6ee230fe2cb7576e2f69ffd1ed24b7ed6d230ee35f6f17e91f8759c261fce7f537a279ef4c3258fe7e012238d083d1681fd9a7646f2e4e55d1b2fbdca94033f871405ed0066984a706be3359d00c92b8bb577102113628e109d11c6cda0c2dc0c2144750034f155118429c0104d2a8bca0470279811333884b9040867403f1c9dd530c72788317a248d286a1277840224890343861042890b0e9f74177773b10e8d5dfaeddcb9bca60780b1f8b3dcd2c48066c0ef9725d68380a2977f00215c4410c6d008211ea70c591288cbc000e405014f121f84003273d6148031592a0c299e0d164074782b0e00c5e58a284c3ee6e394cf3e7824cd965f6eeb268aa04a4f0f4f898c112883041060d698062862a72108735d80881c8199e010e701200f92a7df69732c8411273c83b0c01ca147e0087330021063697951b178834e94290272ee8b1c266023ec369acf644301a9ddb33e067068b220875c8adbe2cc16a5d9a60b0ae179d84507677078716afc5f07befe1cb7dc4ce95187cd0e51e2f3be765683c4f32109905f150c9d4ab23a28226fa616381c28a9199390c0ca020450c993198ab735b1c5ee3ed6f514199cf2001cc38cc07fa838b527d16e3176d21f4bb35a83882bf8baee1df4f44a371c19a8bd62d9949a18239e4862f8bc9e522af748c7e1734940f6683013e8305ed5c919bb9912024e4e0223a698205b12006f992913b0809a226b96d5c73227cc655ea80cfb856727f07adc127773f077c8613c91d847cd56e6da56b64ea062a4036cfba1b1f7c6059f9b18b9d7ed62fdb87f3f7046539160b4aa227b3a024900022b3a01830c9414938c9979583926032bfe0600c8664272bf581f5f972f50a417032a50748fc8062b3430b86baa003222957a8c08695210a4560e0833ab081ca166c7e08928214b041053a08c30b6cfcdd0bf402f159cde670dfa064c901122c3c703088f60e6381d909e0b0678c31c6986c31ec99cb12168b42063e5124c104481432f0c93e334eb380fbb079f9dd93fbf7360feffce0320dc6bdcd7bb9ed986cdec71dcbe67dc32468994809dec3bb236ddebb9d407bf39705961f969f1ffcc0073e60e9014b0fdce5bdc7d8632cbb7b6560c8f15b6d34c83de0776e36c74b86f82a22c7469f842ecd9db7d66484aecd49cd0986fc9e4acbdeb87bf6fcb5f9ecdf3d4dff933c45ff13f4f75ebb71edbaa7ad3dd7ac24b4b84ba34376aec117d555447638c601763b97ee578bb0b99453ca11cbd2678cbabfb667cd796bcfb9d65c6bcefcdb7b63ce3136535d4564c660b4d9cf0ad709ba0761bb17f5d494b1616bad7db7e69a3721ccbd0b7e840e613c5153b68bfe1eca47bfe516e34e35b0ae920c2c8f40b3994116cb0c3a27b16b381532e6f6d23a3c9b60b5cc82581085cc5ed24973bb130a16fb930b2cffa6727b1a5f84b5ba3090e353e6524af9fa56970e10e083830ff6adfddbdd933797e3f2fcd3941136e7dc35e7dee978d859f7ccb2fadebdfa757bf714f10dea4fb0ffe4fa398fee1ccd1ff3770f708c3596af23bb154159a71496bb6b6fb86f9edd1b6e64f7667114c0df9d1a7336dfef699e24fb533c3de63c72b2bbe533d8c367ed734caba335f7a85f6775b497c98eff491d0dbb864be87482c4c1c89061140d3e83ef82ff209659e7751fa18cd24643ed65011394d4771b662bc06c2c46417d6653e3b4be8dbda616d37ed60684a90da0594b2c1a6b5b1cf0dfd79be3fe08caa6beec27016627501f3e21d6a7b3f6fc2e409e5fe312f3e82ab3de4b080761778dc3367564f9568df0cad663d1f9845a7acade584eb05866366e436d658da6d9b06a3d7b134f2ec2e75c5ff94dc53fcd93c73fc9e85c770e5beb6dc29fde93bf8e0f66439bbdc1c870834591e3bbbd51801cdfd920a8883f3a8b43264bdc3a9e6bd22938d0f904cb7dcb872f3b4bfcd1b50928032e7a120103dcc36f0013811123e821438c43dffd684e61bd170207f5f37750993e6d9b23e6f96d71b0284ee0a089035814277020854c292a4feaad0ef90d52d4430ade1385548e937f9dde499e22a478e4e448c58e0f63b33924d5727cea9b0d82ca540838e5c7bf28c03ac78e01a72da8644d977541e9d08c000000006315000028100a060483e16822483116fd140011849c4a6e4e96c8e328886198420811420c3000400404404666860d02b6065954ed60331946cf4c776ccb64d19ec4c226bafbb0250f0d1692a4e187151be176a2dede5f21312bbc5c58ad4de08954d1c2095c88e0dad015b85508a86d97e07558a90efd92dc9386b48837218a52c3f07e42a722af13640a16e8c4745ee13a0532424953f705205df60b62f48800a69e3bc3b523da58ca5e64eba65f35c0c84afdf2d07305a4daa70b888a737a08f0e51e0d02aa9d22c174eb49e79525829f3de5152f1af063e2835fbfd4d4c7d93692d44c30446a95a360e07d88ca5c1e8a87f6a7b8c9a5cadf81d5c404551a82f2a767c4437e9bc77d5ca8511b1c4afd7119e07653d3696bd547a4b40cec705e242deb994685b78a138c81443198658730f4202152b6142f85fb60a547bdc06539da68f55a4e6ab522f8a19091ba8a690b3ebbc064ae34fa6b02d9a251ab42cbc0779d13656a549918a6d7a3877c43166ec9569491de524aceca6b07f51d57e67eb353bfd55438e2ce0403fd2cdeac16a311f0862e7b36bf7206616aada4c1f721021699fedeb1da01258a0f425955bccdb063336e659974680b250f4f84874322474c7efc93c7a592a284f77a5cd355b10d4e57912990188b838386759490b799950d590ae0c130aaebbe7a042045e31b929c21c489a399126f69d544bc1af79edce7ac311f3b16ba79f1675adb97571b7520e397c0ebe08fac7c4cc915752f3aea59e300318eaea60ae3048dd88c4cf205d1fdbe55f1304b90b1cb2ecdd136757e47ceb9af07a94c8c162b91e78c23b4ffc4cb254e503e256f9e49fb0f452c8aa3c35c1b6a4dc5fc027882dbb7246ac797881c209a6f8735e4a6ce43e551a99b446618d819a1bbc7512aea5e78d8fdcf6ba0b7a5ae9e79f7a68a6fcb8dfba5717c631e5337e33a2b04a18a76bcba6f5d1f827283cc2f2d81324752664749b30852da59b0d25093283cd5f5511fe54525819ef7c0cdc1b9240e1d35a653ca764b7cf29ec6d995a0ebbe97e18454ff9cd0ac028493e6e0234adf14a816eb84159be28708ebc64539ea39938798f01617b9292392a892ffed75ad5f4db0456540847469995397f4aa5635d0afdb68f413126ff794e6a54151e68304c4618d1c9a61e159ea3df35681fca6825b86e868e14448eb4b9f37614218bdb8078159dc98e8f285009d110d827f181d2097a8ad3ca830930368d53e78913e880e0c1b25f5675a606a2b0d6fa7012a8e56725264451a25c261c717cc855ad59a49ea70f0299a23a72469af8df18235801712895a87bcb735e5c298242f67e70960bee2cb5c4f6ae90c291d07c839da2fb844320279ad3027d7576eb9fc41f6d001f396aa360622c07ed7a374682ac10edc7eb66806c2bf2d419f1c245295fa19993653900bf1472cf312f43d760ea91c0a11c304c8d58e2fed285cff195eacfe8a9d8014f8fa7cad32cdc8c87cdfb706c64d31e5e95979b3d0dce10c5f7628630a918073a73625cc0b00e2af13e020b3f332049d691416a4d1ae05511423bb90f6ff2e54525f7d565d126641d4dd63bea5060ce0667acd10cb53a594c07ddc603ad500699f9a6eed2696c7c69a017c42ffcf2b29836466d1c9f6514c558070e75ae202e6c087c0311438a4fd2a11e42e30040ebbbd723b00a1729961c4abaa638e2c8a8e64f34429e1c038d80d509209a1fd6a0c009ece2479dd19aa9781de4a05515feb52386d2d11318868835a2e957a5baa9aa00ce5f54af3ef85812379aa88b1b84ab08bef70de8e31a99a7023a579e2b981dcf742ed9070503a43d95cf34ad017b0941b889c953354fb85da3ac6f6747f74b8275134855a20fee9f35cddfe25ac9763db1e329601177a34659a44b5f51229714fbb90b501c79e80cfc36e231d941fd4646f2d2e22ee4d27c801529ae62d1209c8522844349ad2acc58b2e9c151cc2f2bf892ccaa6a4af44f316e2298925399e0de77489c1372d3973b8643fdf5f28b093cd05ff4b4a7f4e37604e2b9f9738e67eab5fff89e7a5df5c0cfe1913d0323976104031e9750052e495194f2735c10d0c5b767a45bef03e6a04facd611eabe5720f42c6d7e64b523b56f3f70758f417d5457b653a9d2e11bccb91a958af7e64e7206aa64e3972b51028a370c617d1991d8416f0dbd26ba6a02123cdff5a5fc36abd5d78e95d39f55c95aad1570c9ec7dccc8db60eebd90b4cb816abb6342d4317007ae24e59ec3cbd1c27052e30e4f59ae807c5d25162b4b8c32d6dfc8c1eacb0cf497af7d10a36ff803e15a4b33801d96ffff0676f114464caa3ac973dd2bc7fe4045f98d00ff216426fe5761c1894ba362e4f95ce8a6ce9d3e8f1449453e9745a3b6050a7a58bfa13bc479bbdf280db96a9e04cee5ce2457e4845e81e1532d1cb0328ea9b6c0cbdd481b493877c92fad59fc52c44a3d4181f1ff40a25365251a1c8148ca9d26b700bc9038aed116136ccdb89b8c4b1095de4cb0009322d1ab6bd9891efda11206785d50f88e3557f248af21ea9188c204e8d0d7462f4a63f05f4bc0472a8c4cce0aa39ef09392be1e77a564bd47365b8a2f5bec2f8ead50436989c9e1bfc62e8f49f5dfce8c3a41a75eb112331feb77419ff09b1e02a9aa143bd22fcab53d5a6c4f70819ec35e2b90de7215f84915b7ad03e3ffd6e7d30a15e2a53688c673483e54a8950285fb1b63b228f9044af97b84a078145acb25f32804a3d4c83116d11917345958dc546350667bdb4bbbce547313a2e23d9abeddac5e168900ea23a82c0a36b1a9713c0736af2100fe53d3b52d5b2923e3e8056b4fd3f14cf0d8f7c6f00c32a381d346d0d0a9ced5d4888005e1254aa47234bc90f77969f575c5c3c89f80ddef8811bd28d917182bf593f1505e6151270c08f78ce39390ecb1f11b4b2b5d374632c20868432e0161b2526f17568fb444ec77c0c40cb9a89f69c9c4cba68efb8105daf50bc383a8ace97b8b6b4f2e68a91e560cc784332ebf1f6116d710c8a378a21e154c0c1bcb03980d94dc799e8019a16a4e28edfa55fd199aae88ab38b19a4c9edb85d51f885968cb4bf182376f7c064dbaa00831f049a64d07654612940fd4f4aa47109c052c419c066882be7d8a70520676fe98a06593e110ba8602857f910f3ae21d539d0874220dbbe716032df929a315c0081d3c0578366de4fa2cdda1ce1ca08698b3dc85b2a8157ce5a8b0e31826662b0f4a8aa9117c1979d0e9ca251c80617fda484fb7942589691ba0f40b4648186824965b7e1bb1da47caa0dc7dc547528b8800610a9bf924f746faced7a0cd36f22b1be6e6ad2834d4cff72edb9b2214a0c2bcd7129470d1afa325ffc9b8e5fe59c559e68ca056076e10ddc5a75f4411e77fde3305cc7c99a6b698e76c413843cfe584b2712066a4bf05617e70dd33183d44b3ace1bc88314baf0fed4092b225424ea35061ef14bcee7ea80ea95cedf7c4e6cdada52f9b1c07c0f13440b35e8eac8f87812032d6a209ba557cbe707756ffe0e1d74e2125f701b0d85b71aea9f51abb3eb9115e3d9d57a7570b4cc4b28931950e9be4eb8847b46098b8f0179f29046c747fd713ddbd17d1e4ea8fd3687a3d5c55407079f04b0214e33d90bae046be40db9987958a21f3750fb679b24a173550328841a55be2acf2510dc4591307486035402d88683d9558cdc5f2c2eebc15edbb0c1367098bda0090d06cedcb0c81a1f00d560d61aaf24935e9019c55e96c688fff5e8088f0de58381ed2ea3224889f5867823259113047c4fd91e2a1b34bd5b4445102201bf61d03cd3dfe422f25371bf3d0cd724a381b6e6a948be1f0ff7f670ba2f0a71cf0c8a8e39ce1de76ed4ab1ff9e297fc5bfdda3323a85d00c6cc0b058020d30a7af8a0643f265053890a65b48c0fb10af44e6d616d33d25d1d93c64564d9c403b8926cffae40fd6f05bacd554c5b85a5fe5621d4f8949079afea6b9bb7e5a9145eb7a709e3dd12aa7fc7939520c424654638ba54e744be27f07526102c80339927c84d45f1031a52b7b9c3ee409f3509160a59e602699ff6baeb2ae97f7354a307fb2545245b8752a436c0330f69202a450724bd363d7003de5619dcc76b528301f81f6e487985a1885ffdcd6a18ee66131d3b6b645911116c8edebf4575c14558f896da216ed8eb2d2f935a0bc8771d4acb07fc2f62a7fd8fc6fdfe68132b6b1dc37d0b578e8897bd1d525567d4479eb19248051dd40682fd2e119a0d1265979d5b0bff84560023d25c7e584b6b8421301b90ba8bb19d88439555f0850214cc2b9a85efa088ba76528c2d48ee9b65f13b37fa49330200c0910eee58c442d147f18b13097342f227f29a61d1107bda90fe84500e8128e24ead3383397a2a101f6907568810ebca773eba5b733ddc1cecece904d6b97a9eafd0d1ef5a0c2a2a1d9aec6929ff4bbf73821fb4f85cc977abb2e898893bf7e8a03f101bcd121bd368d06d24bc82217fe2079669d800be3de07ab23faf06039628e69253e3fc7b309c9553837dae062cc79fd9a97b4d4b2f23b005d172c60b93d1dda4d33798ba4dbf6cdf15886b50330fb6c12fc173a825092250669c07e79fd848d10447859f74d3383b1973536d98d477cc3431d76876f694973d500cecd9ae52356d2102c2e351e54b3827b499021c0e1bf19a0484893a72f156156d27b271eda8892e45ce65d38d5a4a9696cc6c680d41ef0faf16d4d0ec363217ffcb2cc860955658b2e5bfa7180ffaa5337b9343b1495dcd25c5f035f448b823ca684edad2d682c90af0dee0bf54de567c99674571dada4947b1e3f02ec0d3d6213c806c2ad72f0f8996b3c095041288c38108f355b1260e94197ddef6fd1a87d1d1d86f4a9ff21aa2ab21c951f22e9ef117d3d11c59d008c9e4d3f5224078c98392c6633280f2f4f6e1f9e9ddb621d409ae8249e452caa16b55851f33c9745bdb944b9885c1005aa5808ae1f4d6929792782909c0f91af27107e71e19e0539063d76bc5e8a8109931b73a8d5e23e36fa3e1ca1a6bc759af070c1b791f6dd28b7db9b8583454ef98621ff9f9fae4747ffcc6d0e201554b4c53a11d0a1e808ecaa0f3f2ad6c799be39b70513dc49d4c388a11840a30dc03fa0f92cbec86ff63350d8de79ae61a21f99548bb803ea1a6d3f6c37340c9bc80ae1ff5a2e02d9c8d6ff4ea89a58267d37fa986c72d109000f45ae1fc608aa22870c459e98d4ea331bfca80af57f5a6647b4bb852ab73a6a92c9f2a8418f2059e0177c9fde323c9a231a6e03669ebbf8b7dc64424b787fdbe212feab97553c1d13abcf3af8ca80e21cdcdd709fce41fdb0b69e4ad6d3fb948bf769fcad14dfaf3b4cb3004de2a30f046524a6abb8c936f28ed81bf7b702ee90eee4443a6c15a0296dc8b097330dc1486f08f547482070c38165972ef8126b653ae270c3751645c437712cf9ff1834cdb87aae26f36a1dbff3bfa0ecc76e56621bbc9469a0194651aa99b60470c85dc9a2dcf3eb68e2ba4e5e98ac5efb16dcac8cbef41dc1f9b9e4a4d2e0fc739173de75bdf627a8bad8bdb0134ec134127b53e6e9e665abc654bb8aceb9f7faa82918d64e166012c7acc3025cc64216b90b9cf20c988a4b4057ff8f03c17fbe671e4cbe38106e4e93c6ae9ecbab8d34a2ea87535fd9d84416b2c22e64116766b817004f453e5d13d88cb811e04f07bbb0e8e878a09d5a985eb43509da24eb17f8344a96df5194c277c6a7955a5f8bb14635ea7d5dd9d86fe9d6e3c66772a477082d2fe27cb8a7d5c74d0372e7f15a1379da4dd25cf2cd9050ee8b0c2793181034d2f9997ed11a34368f4b615211fefd5063618b51f801ce766b4fdf0f05a5a9d6d274099493cf8d610dd1a67a0ec64406351aadd5f623594eadafb832eee0cdc01c45da6fb90cd068f4bd4de8f93384940324e38d7dfbeb741e2e50fe51b0a685c51c6cdc64978fd0ca4c10b3c66a5bc8e2b00a936550b9c7ce031a919095987ebc4a6866763989bdca0e46fc561dab494586b8c53b2bfebe4e67c10b0a77612075084771826146b29e35dbaf5baa91e4d9e573224d2931e563f15bbdb24e8c62bb897b540443eb48bc430f43a9481cb8971985d99f58b93a6175d09999ed7084a95e0021f651fc8fdc641d68ba643687e83622057ce497e69fad3eb71e95a50455583843a016a0357c78884253a2d88fcfb7ace404b2865d07a5828cede47991b01f0d899f373250bf10de1f31821db67cacd18eff5c0ef3aca7801ecc62148010051a05ec17bf3f6e0754e676c8a64362a983e89d2598299c5e917513747d057b0d12c673135636a34d24cd88c63a9545e6c70b0f3352204b809e9e577e66d68da81b97740344b7dd0aaf59a83e8441945153be0ad27895cba207c4dff4becee546f95e1ca7a4505e48703aec42e76d45d854618db053037940f772ce20a6eb51f325b9f3cde1ba09fa648f93ab8211a9bd46bb6030456dc6c200e4b13bf35947d08819b29f7fd752a2faa007df9ed9c974785dc24e67d9adcf0b9a34b73ef5ddc0e50bcee01da9828177a745329a0848d812450d7a01bf2046bafa798f784c5782b1edd8760afa8c017177788f5dbf77a055b5ce1d25a01f25e78b1eb4e8b7fe997264b0c590305315a6a0a50420ca562a2df25dc8fcd196f9b3a6c36b1535081939c1cc4cf6e4a6229b52ebb03b50ddf9c81dd42de1c867b7636a084333946395120ea918974b44282e99a473efb515d86c1e11a01adee82cff50c379ce8861cf4d845c1e60d89b141c3ebfc103721baef6867cd20c9272131789d38d8c5ee17d30691dbc341468958705392f1afde692f028686bd65c3486e9a51ad9f9fd04d4f959c83e4b0eb0eceee1a3e8e9d8cd4406f850088954b839c84591a2b198186121f48229c7783c34e33a323bcc0e48388fcfd1ef23a7dbb77be4581c1265b73890525b9a67565d2c177ae932b8b01b48305f185220ba445e9d1c3e816e836ef4dcf56ff07728773a456b64956553fa9be601d3a681f8870955067ed1f6d1c3d96d375480ac9defd723a2108f5df7230d12ea3224c7eccfdcfaa0d256bedc8e91e129a134ea77b22dd1e535c1eed0025424c3522280d965c4d247f4272d4eed01f3140e23b92ccf20901976f2103285badf66fcde67b4d4d7a0488ebc79db646c4723cd4871bb51c275ab478722e16bf931b2f6ad08d8997b08114457c23c5da6e8f2657d0154cdf8d3bb293e7056dcfbcd8083992535a3dc2732f17ad502114efc9f3ea9c4114f34e7295a592a1864ac58c9c7d599459440e5c2503f0c57e13080d6c3e48f8422408d66156bfb2b686b72eccaba094a1eb2c84c6367c28385a34f406e6556463391ce4a4710f0179ebbab3ca9e62973b6f66c2c1a5c68d64a917a4aad1f857748de735ee4309b13e683a6535a9ea60612100446f83f4722910d1b0b974ac03c75a88495d2dea881f4698b19e4488b422740a8277b3f28c5874b6c10266c6812586654c8f42a2ff84e1636114ece025bca5f22000d39af5bcae831b524ce99d88782081765028a32eaee3ecbe17e0044195337f57628845be3c9ff5dbd1052211b2f572742fd3f3a9871c892710559ad30905967054b4db870de95584b6f063e13604f89454a0edd7f7b9d407f589aceea36730462b2c50a37780c1a7ca1409bb6fe6c2749cdab6980a57d2c57c17c0eaba5843fd6d197062e8f48915efea66f6edd742e1b95f99281bc09c9b9196d9e087ada0b7d1372532fc0e77d58b75ba9574644e78c82e4522098f1ad98b6fe115fafe9536a9e50b1196df4a0f270036cc243371cecb18d0c462675cee580f0e7e28a9fb3d42c52eee2084099b98b89d27a7d8b5faade0fd5b01a19328c931b20aa42b6c788eafb2534fff6a4d2ef671e8d15b4e3a230960e45ce30506bedb8c489cf0bd25af23e6f8d4dca348d1092755fc904b59679538ace66b12b20fa4599724f84d2a58c6b94b10afa33f4453b0b352a164575b6a70619855186d08634c969aac5402ac1fc9faee1f0d2ed9f415ef07baf81980e9035b81b5f5ad7baa77f66025b6306fcc8487417bb19d913f77660042c8b1446c2d5fa3b8bfdd5dd6f6714f2e455c5a50c5a9b69f42ba2112a63a42ad209ba05c152838b50c22ab5725db2e7e0e247c7a53f4b64622217050078db09fc5e54cb24e8a9c9d00d1d81cac08083956f8d1e7aa1c5c4d3df84641a9c30275309163f72f7efa86a71c1b15635daf3a699ae8c13143abac85e5b68b6a6d0466c22c16bfa867ec0f5ab92a1f8399156122e99c14fd2eaecda45f8ba415d44ee3f40663f7e6ad6dec98bb224d80493c03c03ef4b484c178f4c4633ea9bb60cefceb30fec95a24aec1baa098c030897436e2f8800b80133a1582fc9d7119b6198eb89619dcdfa4ba731fde0908f0452e2b47015a2428d935af79576eb0bbffa0a70ace145552685c47daf2a9f04e1385721cfc90265ed668160e1b931794c1486a5162ffb0b9c3b2cd04961b6d3c508df601edfedb324c1318e14e350e8bd25bbf268a25756134ab369417be25e2946a9bbc82dffe705288f0c6ddb84d19447885a44149b222f50862f730b36ff21da21825091c6e860fd63761bcd64383968ad5d7a4a85daf554d526fd26e4d50ce6c8a3a45d305104288723663b342717f23902bfe7b17a9204b0c069433764b4bc141f099e8445bcac45cb8bc2cceba50a652ec2901a2a62b31f3e2f1a464e368f185454b2fa67a1fb296c757fb7bc37d6e5c23c4f18042313c5b269b79b08cde7f147e04888c849a33c671e300c669526cc72b9b9efa92648154299a9890195879fc3e2749776b7304fd979a6e2f19611df7dfa329ee1088bfdd347d9f2316f895826e01f31e28e7d14652f9a34afcb2497c6d9aaf3b3aa678b2cc70e959823ea45cf4b3e19115693f9e805e9d5059e491f5908b6124f58ccf28882a25f3779fd89f25ba0f39519613ca74bbffcfd8a8a6483b1c2159203e31cf2ed18467255ceaad3c26f54470f15ecfc4eefa37ff511ef6876db901acf9ac0da76b12dd496b4f915fec4de4711ca5c545e2d4528dc690ad410aa253699a534246729f67ad4e85239f857c11a89c150e3c6ae5147d352a47a5e180a6dbbd8a6b2aced9a8342527ebffb6dcee90a7adb7a17c4a304ceb2da6af2ce52bb7e16265072404df0e54eb2782e3aa68058472e3aade90f220fca547182cdd09c7db54f5e9365005a41d3f69bd3b1fcc3dd08942cddf245d5d76c0994c0abd158996a20553870a851b8a990514fc516c0dc02119027d8235b954b0fdbf31a59044fb8bfd463c894835429a66555b1a2c4f4b30091f689524aaa876d38f21b07be5da2985bb362e4433e1d955aae58ed24edbdd82e0dab1692ac4ea99f678415759ce3ae6fb30ed6766abd547608f94c967c59082f88535eedbfd92aadd12b3acf10c1a43558aa885f26f1e56e100c265f8890e858adcb537e26e4b963ecd100b3f57b40e04279528deafa004d1ca4c89278580b62ae1e0e1043ce3f1a49aea5f202672aa3bf874c21910811d79004358131d021d084cf2a0d22e79b6d0369ed7342687b68b1a02f6570c11e30febe1de5f3aa39f59c731ee47686b3b124f9b812738fa6f47135f6a6d71b039b5cedc5dbc275011b31e0f3d0335c2cdfec446ec07e3f0516eede25b93a78c3dacab1d5d381d8756b3c051a4b9641f267d7b7b8b6b1c6a63fde476e345a7f02426a6f02b8cb30ca318a43c254f37012edb8cc748018eec3e4375b01a0d41cfaddc8e57b1f466c782d22a65de1cbfa0876d9e102a5c61fbad1c5a1cabf6be3c683c67a2b4b4a29e91770e2273536f30c32c4d13c0a9c78a9a35abf1803a43e656dce3ed3f4823975483f47ee840b9ab0d6cc46c222469ec70473565a5f3a6ed2864efe422b951c61c1345837cb8474b5d8fcb8ee86f566ed12cab49f88fc36a4464583e8f9f269498b5d79bf26f6cf6d151ef44b6aaaee96e7162aac23e0e5f45453834ef9faf822d1ae4e057f1bf59f3aad3329b01b0461208e20fa487fc571e0f685d1cab238d3bb65c76fbec8b5f2611afe8339f259a24b1741863577ea62e0d9070b945731d4b968b52f19fcb522df2a8bb5bd2741398e423b31f7f0be01b3d3ee7cfe3c407529771536cf31c94fd249b57c07f9bd9fdfa59351189f90a52aa7fbf06bb89f863bff191b708fec947b8ae6ac28d68280abbbd9e4723eb2020b2293c117bc37355e1e3019a1bca9e89ad38bd35711638d27c97482e91fee7f575a4d2fc100b6f41d6446c549c0f96aafd221aee344c143889dc74f046ebe2cfab7d7d7883fcc66c9c04484d0f388e179768c9453e4320177f97b2cb50313cef765e0a1a65ef0d88b9e0458a545311a1cb030e4c3fa225303ae399e205dc27de6442c75880653b60f39e131fdb019daddc344db03317be01d2aafdc590981368c41c8cc549d454ed4682f9b30c9a9aa695cb036ecc3b8d06d7e62a32d0cfd0233a3fb352a0f1c95c55cf33e80640398383d3d2c52fcbd49c10972012850fb9c607106b454a79c30aea56e7718e573125414230333a5889be3c7d9a29e05d1cda118188b087d70321e8ec2d26a8cbc7c79babd3f6ad94d5cd2c109639f3e54af08c93c7496436e87f97b86d2b05b29d885c26f2b8766b19bd1bf3981eb60488221a830ae04e9d960ca3bcc66cf4715bc19a8cab95d6ba7c74dc4a2f1e85073d10b666e7429da277974cf59279765c2083e23e3052cf4e4a38f80e21f7264a4e665c270a027872bcecface7c568e94c1fb958886f189a817bd0250e329d145f85680b3faf11cf2174583b356c87b7e8c36e35993ce1784e360deb68e0f0df356ceb7188401126e674465466a101fa811a29d3088ced9bdf873b076c68493033b8523ad44676dec8876582a186d64b3a99b5033baed0a275c3d245c3b050a864531eae49c86601df062c3c1452fd030c82744c6f13be39f15c2f2bd0ab8148171db33bb300c414ebd2f903d229d96158eb0f1c62dbc4a739f079ee1d59a0ab7e0f4826799d57aaedb88801ea35220e7ea6c3c2b8f434a50811340c65952db94e42ed918aed95a785c95b8c0ea6fb6184e9082d1650deac0976575ac63dc910d3b33941f4a2b5932075b384aa9068052c2f184afb2ed13c4d088eaa06821a47d4361f89ccfc2a61232c3a64116e1342b77eb57a9f69c6c5ddc32429210fd852e46236e4aeac6c01442e4c5cc44aafeaabcfacb093848996ecb881f0fcdf681c059e2259ed264799b5445399920a3f74984ae50deec42f095fc591094aab66eaa902edd052002fce4117c9253436f3cde1842469eeb513114fa0175d8378538355a4d80603600e7ad62717a4b9e08787c650c03337ecbb340b95b7c27beb49771880d91bddeba69a5a797b2bd825faf283364cb93131aa270805f8ab8dfd4872b9c5bc2c06907292e3c968ce29cfbd334e3b21507f2182a54b986f770844fad98e3556cb61abae00969ac7cd25e4471cf922eade0371fc246b0aed66b02a20a9708f7434f7b736877b749a56e8de1dab303d46a4fdeebeb513a2734b4f9b094ded59c308864ffbaf68b57e635a92dc4442851bd0bdcf014a2bd43442f2111b8d96110fd9c90581de5e99c901db09740afd6e9311438c2c0db650400f800f4308f95c60f7a37c674649ad80556e04011a07111ff403b82ca4c5d55c4b809c6116e6a3e7d96724fd8bdba4087adab4a9d7f4680157381f24e01be8f3d015619f0c4464020054bae7cfc7701ac455200ba7351359972532e7a1fc0c7253e550f70889869b2ad89d1c59e3dafa9317a71fac237f053b4f0756e82c328f99af550fc192d3e36321dc8326835454fd1ce3e381a773a5a75e544e66a29a93b24bbd2e334ca26939d2252c01e78c6850306ee3a1af6c9a9476637ef9b8a737a7a9ae6d709ae1183dc48a50a192dde29dc85363d83814f8392adba40117b5fc5f5b0e078bb939b37fa510ce19a8607fe784d41a8d50f86f386ff980a61b7a02c3230ffd7232d68e1dccf0ff5ae1c2625a575bdb2645dc7fe305c018dad164163a47a983de7799c43ebaaf104395185d53f34bda33d98ac8d45e68e071ae99d7aa024920d89eed4f87302da61fba02e4e017765c77a1738c0e04d4cb4cfa61d3ecbe2b0bff936038648c9b6205673a6df50d4c5d6a3e3875366784bce7a353796661e9979e8ba6f7693108ccd229d5a10ebad447656831c08ac6f7ac903044da917ef2c5916d22ab77336fe3eeecede29385ca09701abcf81e73a67ebc9a518412b3c5200690991809751b651d1d759aaee2c1c4e6368c871b80f5c31efe07a3b63aaefcddd9a71bda1987af8b1817852a032e3698eb72b8c8c1a200cdd5fee626be8a8c085ce537a1031febfc8fb33bf58bae52f9619842b443c59a7645a2d3feb6ef2573754a0ac7265308395c19ada24198e232c7d2858e5753d6df3fe9fc1e210d2c9513f49deef90d76e296ee4aa220c2574fe0cdc5a423ba6e6b646b970ae088be0b7b735c699a522a8dc61d16a50431e94c1cfd48ef1585bbdcee4b6909a480d50a09b2e9d5337e9e6b9b0b4c53c0f87c7d657cba273aa90223da4d24cad83cc5bb226335f5feaeee8f0ce02501a199f922593c2ebe08f1d88ff4220e369bb22d13c5afba48f6afd40a1bb764059c9b29ec294e11b809b8468a6f87ea3ac60201e91c5922c0906e4db917052d67a55850cde2d05c989986234b96c92cfa0d7256edb54c1011b47c3893ad7ad7059f99c546a3616be489fd7274f82188bff009e794641622c7a29020c7739f88881be76ed1cf009358ab2ba12a9cd8defa5124db447167f6f7050ae61007536aea917a1a0eaffc016b74a713edd0a71d79378a1a65eaea838832e9c3094a6fb183aabc8ed883ddb9389ada939e31aa27cca83d9d6870e3d71717eb3180b0120dd7adbba6f62aa803a18b2736b0016ecc9d4f561dd191436112b0ad377ae3467d48a6d83067fccbb1312dbda9cebc6f9df7722d9b0a72d776c741cb51c76039b1322b433466a140a52512c81deffe4eb2526a9a1674d0d85e38472b906aa4558725554529e31c4e862e3988d68cefbae04dced810dbad29e635d558324f0d320c66c84949bdf0d86f71d2611cfcedb75e586f4aa469964643d99f5b1da7eebe436344d5ed1e522747b51edfb750e8954dcb8dba040255f5f2fd948fa8a046397e56b6193584f0de62c2ffb53d057620a7d75e7a96de243bd676fe583e4052be45c0f50f856ea9902f4c0e1f9edcbf55b11eb7eeb76bfeaa1dbfdd0537f885315256e86107123e28b25c4900d7dbe6ade52a60a1a1fa124f071c64df96f58ae12c2d19b9ed0bc40577743e8eac4e95704a97799d82103ada31708e82eb2a4d2ac5fd77ee29651ad6d07ff2bdbb9d04b108e86157777ce5709d112dd76ff3d8d4a627af9a3822a7aca05f07109be82a305665940ea39a7b259bbcb780241892b9904d604a8c082ea1c633a7f94632de7038801500ef41780930e988c3de0eedae22734a7fe9d8377a5ba26ed40df42608b831522df5a1cec55d68bbd4e94adb8741831a990424e95ed85e1e50ed1ae365023e28506f1a38279268f09dc33f4b543c3d102caf3ea96fc3d3883598308b6e4bc3533c3e05c651e3cccc5459e040de93672a8e5f8cbbf8499649a46006bd8b4ef83d32773e03ea73e3e40462ad679a6e765f4c12dbc627ca743cc158a362fc789333b22c395c961a971f60f1c72ebfb6b0b0b38805c7b4d288c59637d54e42828723d2425650abc2d62308864245f3b8542e0a3a3f38d83a79535853044566184c13cfefc72b80bdd664fffcc152c0cafab8b7ae4bb628b10793adc4483238131795497d220131e3513fad1f124f4a42bbecaf4e20e96d9bf35c541763670ecfcabfae33242aaa27ced560d764e106b0c7d4772df239552912cfa91f73faa5db5d780908268570f5094ab937a615cfc4e1b0b5ed5c66e813fc8bea628bc61c578f4a016b2fbb348032a163b982c12ea487ae983dbf2b1d9cef08fc21ced7e345ecd915f8614c6c5b5fbe7b9c01ca4bdce3dac83d810a76eec2a4821e32206a6b040ed3047f7054f96759714364f9f8306d673691835b982b2d815d280c59fb57e493f9b480321e8336daae21ac777a8661d8e1bf4cd9df52f554e8db1ca37d280c594eec47938e1c9508ad687a733051baabec346e2cb11996ea45b55e1d38137e66849e32d521ca158bca1050884fbe95fd754df77ab05e89c2788a169256b717297256d05dec2d67a009facbc0a243620bd79763e1bb53b2989668ec30586eebf414859b1dd3ac677e885305eaab567e0935eaa3d513d7782ef9f5843a2036911504fed567c219a23a5329283d52a77d616ee077abea2989eba177c06c2c40ea07b31179fce2cb7e07e9303e1da5303521f933bfba236f8d97cf59439d4a20eadce904944fdf02cc85b420a97943153bd857ba68150ecdd0b20a5537b6e72ac82c1eedf08284a5bfbf868c6778102876b2f6aa1b229fb0dc26b95bd70591139507e1cb9054f974042fbd8556eb039215e38d01d84ea0a483c6df26ae8e65865e2a0cbe9f23f38358bc1126c99f12832272b97c08b533ae52f84d3079eba763dc36a366a5d0ff14f4337aec6894808b85b9b8b0b17b03b51052700050774491918244a080d5a9c785dbfa6314480fee658509031248d901a84841cd84b61faaf17c6417a06c2ef5de49d197dc267cd0fd9302dde2bd0340e60159da96ca7ed50a08b985ce70022445983f39d4d9d7f0861b422d3f528c51f40c4ca5291d4803d261a252ab2d8f1f34a5d60522c243560373831a4cab4c8f889d8810ec5843985cf33ccb828b9c5bc7443472be0770bb7dd901efecb18eecc22a82f17ed49305c6a5aadb64311b181f43469388a225c8f0843908378ee2c586d1f049c37eaeb7fb0cd77021fe588b8e09ef80d27764c98170792dd6f96588b00b158844474811a36400ebc458bbf9a4a2d7f617069851b2363e1c74f85de74c3c232a58747b5e53cc6b70d2f105f36e770d2bd640632c49c3b7490d25d841b0bb59ab5c66b13dafb0936a4c6f92508836d493c2712b8f8baf9a78324e19e7da6f96644c665b8e76189cbd325910b5f201945ab4c1442c2c5c2e36b8c65ac012373b9805ec29dd883a17e6653e8e52eefdf001619570ead1a0c93fd0146eccce9b2d27a192256ab01a5d5b7282f710b405664c15f949859837dbf220bec83f784e85f2c010ddc8e9a07ecb0cc780f05fc83e1fdc6ae17127651dd3a23a408bb8e472aa9b942d638f99780784d0c6c9f644694cb0005203547677cc8519f6185822c7e9ac52834390849cbf17606610f604aa120416de757e567ad1cde29c174386f070cde15caff5894a19f0c7e4a7914337a36efb1fc228210e419a00da123b0ec98a9d0dfda2417b048ef8e218fd0d11ccbce850378a95cbdc9913318283e4669a50ee87eea4fc9f43b8f34a03cf84d506d7585f902a2e61594bc4240cd9ddc6a5501a5302b60104a315c2d895300cd27a5ebf1e7d59d1102eab9bf92d17289b6c3583c0a11d7ed8a67cb0e03a373179511e2e7ef1ec6ba53c3c4fe3e5d014dd36de4bd28a08e3493c1e11428094f9605fcd2ee3d92d456db453646cc34e3939056e86a56038e8d669c392f108e40f64f38a3d4bd4967af8135e6fc6f57949b314bfe91ba0ce28a8dbdab0ddaa1b32c5f960d9d295a60e77339495c6bb6e5283125e3840ab7e78297bca7728273cc4e6b3525c58f6c518638d5986d12a4b9e4b9efe04cbbaee60d673fd53e31762be8e2c10d2032e722f2d6055195511aa009e850ec61a3f89964ef687ff92a21f6b56f143058787304ccd30ca3cf1cc7010f1e110cbc2cf4ba1e6f11330104fbac81090eae20e21042f544cc929b18aa0126b6636c5d7efbd0050fdcebe73e25f181cb409cf5503b4915ecefa3ec500778051d5cfb44b05db2746d81635083f551cce3a5dd2953be5c206fcbcaa7be21ce1caea4af603e1bec117cd9c70e7d3400509a8f1e2a75bfbae329e92c9fab360d0a135447fc8e70c0a04fd791f1840d14f237f2a116eea6393649d7ae770f032b3eb3f0e1d77d826336227d9806432ca6b1800fb5c8da1bd99a09f828f7edc0930261515aad0d10881987568f7c09399d8d09aae50dc56a0e960ed7f2d8916b6cf702b11be8df566f213fc2846eb2858024e606947085cbfb66e38a0913484aa3b84734a51da1b3d5eb8c84a880fd8d5322acaa845abaa5dd7faf794ceb10a30081010b0e2149c2bf28de9702c1baee8e5e663a0e3fa880c60b0c9b54601817fc5e7109a625777ccf205ed963588a8af7c3ac4d0164bc9429b1017fe55257359065d5bd706277e2e9bc48bdb670f4144a6be6a083dc331db1b3e334266cc6e6c0a83fd28e4c447fc7da51e03a5ebb522462d5388cf3de81eb18c17508ade910659d213a2f2896cf4d37b3090d3ae81beae1ea5421fba506b70bc27ae7d381cfcb23e3c9c8ed444546a7c58924679e2bf47d21b45b7efc6c51b0435c7a61bcaa9bf7143a12c24601caf1bdc196a1a6e742e2169c72fa19be579d1c2a638d76b007c354e0932704abe4ce1113079023cc499759a0320a0ff6991056d22985eb1336e7c1fed96285c3f69c162aee62414fd7fef7f97cf047001feca948f3a7b9f5c1de3449dca03294b004c663f79a2995c8a1a62516b40f9051ac2b14caa3a2d5da14f307fb738b4507c28e186570a39c2055b5dbcff5a920ec0c08a920596962107631d7eb5b358e3c3d49b40743d85471470b7b925126757452a8befcbc36035cebd84ba452accbed39a17b79cbd9d710ee0b01fc2ad1c8fd37d73f3e12b44ffbb30c6becadf540637faeb1ea3626c3d18eda69e10a53b4cfb59de4d86ed2b18f3b56f04c9ce5ecfd8ef691595c426e2be2f1876f471f0005874ac3b4da9858877712931f6d04d638c207f0b0af4286bd1148b571a0f51bf1e0ddc1446054de84b9db21a112682cf72a126e3887db053fbc1d2585c12416c225d67b1de25a2d5f34cec2d98665e0e202b2d6315ced99093561da5f5d58376e3d8fa4b01ebf4d20f3faaf387250f631f0a469f0cb3e3a7e2c341e034718f2d3fd3da8058b2734d89061c6a4ca2733f6caef2c52b6a0b67127fad0e0c83bae84adc67b5f6c7f0c8d1fc67fc07a53c9797726838097ca12e1d18dd334e301790af80af0d3f13c13454c18deb2b27822af7a156cd42bc36fd4f732f2982f3c833d7f2bff4e89b50c0207df45b5a326df5a7c4b534014a057fc1e438e3c2e0086a41bd5885059fe96e4573ca2440798c7e5c6905b751ec6169b4d1376832f3c4159682ea124ea6a48ad05c2f3505f0b4d7cfab25ff2e6664ef440f90c2dbaaf7046cd3e1366491a55e284f16ff153d54af0fd1fb348d39e98aba8e80343e03f3f11e132279388d8c4c2661c627a150feec4eab9132052dcd152f1c063fc4d0b22b3f176a4b1c00798de9018c1e39a4cb7d9e49f428243c6683ea0588ccac9600b21ed3cf7f23f7007e871c3ff8c4b90c309d099306026744d130f0673881955f88484e02863ac722b749a458e9a1d91298f33679901a4656b3ff4c25968ae71a5c1540ba7981fe8401da6bab8d635e2b97987bf1535e14b8abeb128a81357f6d6bdd89dc4bd3e47c8eee97533c4218a04fc87512abacc80dcb06d36027cb955da10fad3cdd487f8acd31b640bdf49a916cbf641968cfed4546787f7dbf53538c3ec1a858c4761577e5f822275283a3b70496407d1bc42c4e2f3f8bd0e45c543de0c0dd4c180d2cec505d1809faec61c1b3eae3c963ec4e07aa5efdaa82b7608adf30fe2a24379c99bf53a300dda1df14b1e2a7a29f40c5ff3f897993347cefb12715a8706800d8aa100ebd903c3ca7a107992261d173ce618b7566443c0a3b90632809122d57ffb2c2d61fb2431a2deb0f616ff6c50c501bd265d6c6666fe2f64c3a38efc60a2061d4d5086395aec77fe0b488457300b5fa36b3a2caeb3e19a574c4466e25171a743557cebcef21a88c865dc26d3ebc408b9742224d0837a14f6a84bd1e5ca3d68ca92e488afa9a1380b5fd781f65c4a74d508d304859dc485e42b373c3849261898695d37999561b31e1fe6b5b70bd63b25c421149e3f4c3b4322088b7ffea391bce729b42d3e94f400467bf89c7966a57e4b0fff0ca9091404763bc6efdc269fa37905e283766a98b0ae320cb24d757b1b364f1f0096de9468b794c8448ae68dfa2cf89a7fcbd8e5f78a64e887a4d336512aa2d7b543a0027746f680da37c2662215c61ed8e890443c3151bd10131c08797ab6b008880d48410bd54642e2303eb17d6e2f691a930d32c2fccdf5146f35f6a95c8709e29ef5f67b776f5c407c2bc3e65948437ee9072a88a5201c9a337af2a7081714bb83028ca90aab0fa72c319010cf919307779336ac23e87b7c692bdcec4e719a8b8f0006528bc416a64fa0ff8779e97616bdb6901e987a09e4a94a6d15a082c0b7d40aed421625f6840c3e317b11173b2d1bebe20650305d90e9d9660732a18a8b1aa935234d8d0b035c23b52dde7e76bbb00c218d5b60dbcaef834a02d0fd1db42554f40499e0ee24886c1499b5497f93d6daa395ec09dcbcfcfc38afbf9b1009f9a66a7cd3c28965be0a7524f64b680f988506421497ef6030f7e9409d4a8f9717a9358e34f1b41324f425b51ad41209ea6360532384fdb2b7fef80abfa984e2827aa1e93aed4eb5378eacca47265731aa18fc91c58a97fa36c0f80979138dd433813e106302b74a4f7efdbdf4d707f8240790e5267efac07185ca0240e35fb50105dac6610224ec8371209149cbe0605b0be4d8e409fbe089d5c8ac1e872fb2710120c9bd8089ce5ea4b8a361e669d34d8e9c893a2ed3711f363018f5e5a680abb9b17e29d9aca8f79906dd86990ca6316f2b83f2ba60c1c5a4a56a2efbd5c81b1b26be304b8cf28726ecf9988b743f85be49353099f42a05305d6ac5c09a54a50f9a1f8a83c3e96379b441148ca54e85e641278d15fef379490c70a81267dc39b1357100411b4720a1c577ba6179d1c6aab8aed1a641f8318db34e0c31d0070a2dffa979e3f4ad04e31e1c7ecb93978dc1bd55ca295659c3fa8dd7e6979cdbe42346be229ce66f3bd3a0686b93927cdbf61b9495dc8bfcb150d9bdbc93873861cb842f40911e1b773958a73f269f582f0e898f88069f449bd311879f8d2f9f88f802892b2ab50912526b09416281107b62baebcaf6d72eabc991f8b266f9539cd9c18c24253ac8869b436b3e127f4b751a8f1e5dc80de6bc24b9dc1ee4e4570eb6b4d475292941271a035f8f248e8024f16206610dcd3499972ca74b126fca781abd55fe0ff700e1e6e2e93b4310ef72a8308da6f9e2e060cf3f0e3956b8672a4b6de74e54b22a9b417e5ddcf1b971400dad2eea65737886cf8bde21a42cf3ba0ebb2932a807f470f10dfce22d5f09b25ffcf62b6be32f6ed447226dfeba34ad53fdc5271e53c64119b441f6b82101d820d3fcb7ca46fda406be2c014c511aacaf5689f91c72e2707704d9f8806dab268d409434955fcde65cedd0f2422d1c87fdcd69caf2095ffc447cf92f18e451665512be96f31b30d530449c208fdada23468a9c4d06d814a207459e6a30c5adf80fa81a90fb6465cfc9d8b832d989f20dad36565f9b684507a1a73ec01d98106bbdae3b47bcad2280978e5394898da30701321764768aab0c4e9dc68920020fb37fe5eea849199866076d35505a97bb232feaf560e4a5879a0befa530ee98b4ba82e70c980ba44124d4991a5d318275fc87799487d2d1a912a651c666c8980bc69c0d7c953b6d2d810a469e4d7debcf040fd07b7c5e6b76e2c103ba90849364ab99cc09c59f84930b856a032dc5e2a8d70d5a3d9b7ec4a63aebe44b47bf29d7e686e3da437f9b7565b854592296c896a8b9b0aa79ef169e3cc17d42137edaa566a99e175c44be797ca32e5c0382e314691875416aede237f55e12199005867195417876266c01f06970809ead01f8cefc7d77477f4d6b53c97e6fe0c6ffa2faf59ef11e4fd724986685d455ba7a9de786641ccad0eb06cf46d7818ad441e23c3a2cdc99b1a72959601559d32c47c088944e35fbcfa7de09d397392c810fea77b647f3a60388e9c66e8bb982a99596f595d6b8976cc9915d953d99767acaab41a80cbdf3473cca7b102c48f17cc3f9979f896d33a046218a022f0d130f3e929c7378714ddc0ee71545a49aa8a0280a867aea5a98ab2244764285626d45e9407abecd2a0af92919829b218e7e7f02fbf9995fc86971432678dbe92b35b47e2bdfcd03809695712f2f18546054129071d5005fb328e0bd4beda86cb5100d7eb085e89b7f516d036746811b28b58236076614004fca5328518b4a8b8d3a569851500bf7c82017f08dee8b78588fd8ee377667112feeb10300827b20934abd5c8aa7c012117df8e58c00329736565f8d7f4fa10768e819951ab48a2e869fa6c5e6d7ceb3aac8ca1652e62db72080670c050a6ff3b82629ccef433e9a04cf9f55dc534f61b1f509be91de84426b8636dcb7a07bf4889db1fde76c670143862cb89f7f910a8b91438f83ec4a72f0d475a36d37485803c9734a1f7dbc8d558d003ded64c1f27f612303b1a6fc8960f5bb0968b711e02c3778ca7b3a726b41e89bec47a0865293a7792dda9d772224fe38d6bdc3bda60e19ed14b320a399133269d2e70db0057c4482e8cae0299938bee9dd63a44999a57b808492eba6f6a14192b90789bc6610a753dabb882e25c7a697de60b18b28c6943f9c1fd3ed558f8bd6710b3a043c2bcd6ab409168086f557924073bcb69ea434049df74badcdd01cf32b6d19d4277da3bde859b62cedc8aedf021c3c0a2564ea55cbd402980f63dc1476c2bb642708f1ec04d4a99d00be87b43f775371fb0918d055154bcfe50c211013a6d8091d4347f4d9557674e47315a0c375770fdcb4c43c9a09f834fd586a42636b4e2045a74643aa9d772966890730ce97edbc2bc00508e0ce9b00c0cd79e7ed7f6f2b02029e37eeff7ce88a450e082c0ab4a93c5268d68034e0ece4796f0e0933ccdd96adc007dfc9e679a311034853f44e3c15bd85be4b16217fb6a2f75cb9cf2d7acf2e17bd455ff40e27305893d13646eebee5125180a21c8d29f4086dbbb89a3da0b32c4f96e3648e43f034c24678af53cc96775221fcf352e350d1cf5fd61403a6d76d0001af337c39ff53233f07f2edc3517215ee03befcbcaa7153eff3dc0fb83b42bb17d194c8b4ef1b1c945dfafcf666b0ed7b1cc1bbbd94b7bdff1c85b8ede861e2f24250b0ead8cf2911deefa2509dc7a0b2594900227dbe5cfc3fb083078ec0549dafcd7fbc4adaa3ca2a64336f7ed7dd476e5f724ecedc54aa77fbf1204f7286c0d909b6cb6282a04ad74063df51c27a3210c5fb508d2412d08f2f6cddfba38a22230b81e876177f236baa29c1859f560ca7e1d7c49c87d0548756efec0352016b1e6a470c9c72fc841984d39911999e2d0fa704034c6951793e33fe2b49653e342dcdf3b614405850fb1de0688a49c826054e9f0c876c2f7e1d3663b2124b6a76de8426baa97297d9bf141ac57c3bd092a0bc8c55760058a6d71c44c80d0cc7aa0150b96daeb7aa77ce00153550115dc2461180365b18ac6f3be37dba7e41d9f810755c5d01f715a9823a4e4b01eb3e05d634d0726ac3d9e27e28dd37e54f350b2831eabe095a0dee0a04e6b1f004083d5ab7a3e8a777655120e3c6c8d6ddafaccd4299db579fc42827c581e5bf599658b0462108b181f00036c7ff6498baf0fc2c15dcc4e9a7cbf9fb02e0580c170403022a777e12a0d5799c359741ce460668cddd875522c5fb9bb70140c95ff564c0fff932a8e4cae9b9c362bad7776e973566bcc5181a47117451eee1dbda71bafd120b22ce7c019a2d997b4583726ddfb6b259aeec99379e1413463081268dc64a4d2d9a81790cab59acedb2a299681624680c318df086e88c32c4ea77b7c50a0a92492f8bb5a3fcf299c6918c50931201c97750adbded1aeeff6facc4d0ca7f86ec46f0310e10e7e506ca976b61b66a73c33f7987eb6cec0f528acca6f29e4ea0b432a9207e1f92e7860ae8f4aab3396220aaa64b8df7a204571b3028eb03ce136bed40c97078ccf070ea2bb42470c840821e07ab195c9869200536d71d423483eb5a0b357a5230f93cdf3883bee0831b9fe205784d765fe00198505aa4e4cd36557b8e320c2c8cc45a393ab7087782978f4e5d346545c64854d546c103ffc4fb675b7e48b47b2c7ccbfe270508b2aaddd1556db008da1fae8012d007fdcb9b5b511bcfbd7b3cccaa0da30ef4acf59a20a40b4fab3643a3bf4cf20756fb609dd4b70606e9959caddaaff2d58622f4d56ed12451c1beda66175b22d12ab9384196bfda37debfdae8eb006b77fe1e7b154fd34550f3d152cf3d3daa0ac6e0bb3619f541efb5a3b0f549654ad53df39891f55970e93f1f1faab4984f3e6ed034aa54b3d3605089be4a60eb5eab950d6bcb94dde9815010bb00434287b57dd8d9c6f86abe26b8c43ed253a77fb9b5ea95910655c27cf0dbeef592953b24d2460bddd0aa32a8599b636903c88d9ff64b04b7e8d94e43ae8ce99fdf816848754b827a5b5b835b9b36cb60e2c6190ea4c89bb93650f102fdf961eeb7825069dca65c7262a5bb4fd4079cc0de5bf4af2e5ad738cd426bc5d9a0d9b8ce5619cd10d96a0454d121a4fac19ed7882402850c369c413a3d4c1f63ca832802527396f99850eb3a6c6f8d2881e34f6daa16e9be511106845a1fb0b7c2226b5bbecc9b89f6e3597b00eb9afc7c184d3d044efded4546022c272f289c746a15238541226ba4012841be4e25421afd200404a8c0e041bc1e0316ff87b788bb0ab8cddfaf4592e7c1807fdb0600a0787fb5e600f94f1234e53e7c1331beb277a52bf14683c054e1d7f6934a1d3d63831dff6268302a4d83ff5e65ea655032c85429c50e5663c92c1260a505bf9729b07bae4de3949f6d911954b9b225765c7c94c3ba7b2eb6fbdca35ee1f8b72f9f4c3d03ccf5b5ef9485051ce665aaf83bb09c282005bfdae6e08888dc13c8644087ca490b2279c526bccba5ce27b70acb757f55e5f27352abdf6271cbdf654c6edb3b28285de610284c67ecd78926f3666c13b26a76ffa10f186a7a06e7f1398bfdb3eff8681ccc68fb9ab5d2b86e69226689d1ea99a95ae17a1b1733103ef7fb2e52eb3010f1d2b9c4dec99ba0acdbba9faef3da9a9b5774a673104ec8ef3004e2242996bf84aee124710c56d52cf95a1c2715b69866a92e4bf64372389194d9ffe0487a4ac57ea16a2c8fee863bc8478585aa674b533649052fab8b22524e641e85f2d3a8958cc7c6591dfe99e5735d174f7b26a64813f6889e9e15d72ce661ff5dddbf55d3158de5f99abbf4073a0d49695ff26ee6ecf51a1694a14c2f8474dba99162afe981783238711323d544036c41d9117bc948fec4997b1827477c6f11d4d153b3cee373b8bd475444ecac3b16ee41bbaf7414a6bb895dbbf6801e50a08562af8edcf230c5d68068803f3398fcd7b9be29b61c38c58e758a6ddcd56e03f3b66c630b18facb9ba79c63c422cfbd9d7a4e56449fc7e48207af1145fe9126a1de97c19780cdc17befbefa47e722bc4b662e73b010065401141f70fdd6ed124ea06ef0e2f77e881a47bd79473cd9271f62ad15b7049b00938b324d4e67d1692906b8dbc1acf48427e920c13f195511b59ed227799ff066b6d0af0cd25b10eb95b0d677240ef78dd699bd4ec5f91b8fd56d695fa7933ebbddce01c09b1f6d1c7ee8f429c867877970039dac5b866efb8cdb4a0093e3dd594cc2ea72316fc1fe6cb12e1d027ee0f2390077343a7ea580769607b8123774cc1309aa85218863dbb8e27ae3ab48c5cae01b3f6721f23cc6d5fe545c1713cefdd592bbaa79ac0c5f535ccfe492f4966a7f2d40711d01e0ccdab661a383ca44d063f404fc0612674c96e90f13b4bd39926d5a9bdc4d9bce2afd76f933995227ba0916e9b599482e875a3f183bb3620ccf968d663aad691b9bccbcc43a96a0816780176451cb042b120f136fee60cef5cc9612b5d1bf04dcd63f10efeb8743abb89ee674abf1d6348ac3114fb078d1253adc1321ab2abb786d7af7b012859cffd5a1144aa29ddbee2ac6b64b3345d22226a6afb02494182c112ae2891ba964fa6dbe118e2d1481e5a6855b3127e9c4d5d6c3e8c1aeb6c6b93415d22b5a840d877ce7c8b2a3df652eefe271b50afeb19fe2d916340b67cccec4027e261854b96163c71c3bc2b9440c097556357004b5d85200e39e26e954ccfb15169a3805cfb205b7480a705d75ffcdaac5711d56ba776afa204cad1e2ce4693064dc1357ef063fecea7dbd5b66b16f145dafd56cd51d7714310e03b40f739e00fa102a6704280d0b3ddf269ce2680d0b7c41eea0fa9a53242a445a17d9b3780fbc6cba744e5eefd02e462534f3b53e8a7409283724e7bee9520c35108eb6f2b62ac18fc0e33621aee8b68fb0a0d4bf1575e6812afba5db2396f9b27b7016d487505a726972c7b3c7d48b981d72c1947110c127dd562ae628ab9198821980d7b06c7ecac75921579741ed89fd0aa5d567b1004ef40d0980c8610e7852c1e69f422814ac4ba995c5c45482fe1728a0d3dccec5a48b27060f4d13b822855e3af1d409f2c0e3894acb6bf467acffeb212be1854dd0495f7f592de8a406b040d24de8f59999db4d413f149053a77b0ca8dd694eea36dee92aaa3b993312a9b75e195e141ca5bda148932a6a6baf51d0b02ee6799a5513c76f9eea22156ff4111de7b270cae2c4c412874c05867d2351bbb0a43ce6c6388d81e3504a7c439fc874069ae9e8b1c86c812f52314a079c47a99e2d8749b7422e0af3e06f6d470f97429a3b11c3fcfa8cc15ca5cea514285fc9530682a1b0896db341556d34008c54f9dfdd8ce550efb117ce636355e700a2024c6371674e7a556bdc0a88c01a2cb1f5dd14a8d91de7e2968aa4c067f6708af54dd1258340712fd66ffef1a6bebe7cc8f6c6e2c2a638b97837ca73722e04b16d3c5620b2b315a51e59bd3bea7cf31ca392c2baddc4f98b5b5218834079f382e750a43e6f742c8db3bbbeec9396bc415e3d11ea19ebb123b04cf74d694bd06f3f71bee25f16cca65717289b65ee8cc46934c2158c51a63b7802d1f2890585401974ab8627b71b5f13ddd91445457691255237ea609bda97e309fd8fdc3cd48d62b45b1dde51d8ef0a6018f5fa2157946b917db96d217e43c33ca47837403b6a14282f46b994e8e68542088508f1f9826883b3bbbe8ab81e41ae2fa5fd9e2c77d659663124eae3f1f4969b2a7d6cd86edca02b6d5c139cd5785c5734958a42bf3bb558d40bcf3ca91af1ebe31dcef497a94ac51e9ffeadf5dfe15c10f9c707f0fc900300f20211cb8e82b76ef4039a0a6c5ff6bd93bea5f0d926a4291cbdab62ebd66c8b411b5036d101567fb6171791c7be0efb13c9621e5b1574ac39642d9e48b018fe3fb276cf4ee668f4bcec4ef9dde347a88f656859df22b0dc8b61d0a2bd80a9661456ebfcdd425f9708ea761d0d1a6fce2a8fee1f5e3190ea05d48ec629ed24ae062376f958a4e25ba6d9c2e881274dbc4489fae3a008afa7012d1231f3dea8e7b9bbb4b38d0d11ea27504d55db7521354a1f336f1e11e44cc8c1c0833ec80a54c7eba7f927df2b638fd7207a716998cfb11aea7d86d1352ee0ae334534cd79244ab4ea85a89d7c7e6ba49ddaf4703520f0da61413a2f72d665307556a7f23cd1e6e59d73df2148ea6c83331b86d067f89114110ef5d3b194156b8da7fe9b453fd99bde919fc3bc41cd242e7a6b9774ee22344f3c1a77a9eb4c65e634e390e96e994b56f25f5b69386e655a2f95d90780c5428e148c9f82cdf740364d63a9c837619068b6384210232f02b208b1c5357a75616da22e41e25cee23e7188c8baa5b846ca592e2ccb9383a11594dca668f3bb2faae163d653e24672636fcb143b2cc69c506990334fe95c08721385667d9782007148a620282d0b34a4408f6d21406e2905b6449839e6423f9181f625ee4a3947f0526cdf5a298105e13df91b92e685f8b07a47935814837091fb4fc22254e0eaf157cb04ed0e5dc1f3d184f0465bb914b48625576061780d9f5cc1afd22d648c0cbefcda862cec70a12af1c1fe488830d1759c89ca3fb35e72088c85db564634a5bfa53ba6ccdb7050106e17f533c35bcf1aba13af435a133c92a9b5141b4774839d6b114732bedc547fcebeec17cf1ae991b40d6d782bf9de4109528cac13d6f5da1a946a96c2c8e21eeddf72bb3f99ca501da20dfcde441576152370f80232651a75240c47e681d96f61e61c07f03a502a5b52eefd449b2cecc1651120dc007a291edbfdf43b65735d3a0e95128653eba885ae335c0ea831a2583f428ec40053727db8666e74946157bd3a9a7916819c256da863edd49c7e752f1ed5fc788a7d08ffe4a6434e4f3b541c3a152dd54b24ab079aa00194e5eb39566a1f8bbba0c5cc356d477d99ff3d561f5d5d090c01a6327ad297ad6903a4ede881633b7e6a4cfa67e72bec310ec3ccef9d43abb46a7beb332a2057a57b7d438dce7d73385f4c826837516b16213209d01aaef993991d097d0d78dcdcac79079f127f2c517d1d9e7b380034ddd256631b6395813dceba4c4c0807a20fae2f6d149edac4ad3b87732addc45f3a2bb2f4f242925f732aca4827f0b1ef5a48b548da9609d2724f123a1f7a0ebdf485d9c8d86833f1383a0aa6ef0dea3b126afe2746d34b8e20f2334b3102003561eda08480c13d8449ff4f8fd1517a7d8b3b2f3338d1a0b93091a3f82ba9850afe9c27a742fc3e850d4bf95bab10815e6e0ae02fea171f6c836ea7fce1e99719bad933518b9bdf215d200f665aa260d737b974d0125bf8190483c4ac31688de5c5b20b22da81a0891e568fbf1cb8043545d2035c6210894d0778e0baefec140be096acffd74fe682f0e79f0bfd76ccbef99c718ffe54d062da97075b3d4661053f06d5350c60b362b502646180ce48c9b3197f11125be88d1e13ecb782ebfd9e500098066bf5c3cf1b3b4554a4d8096e226a53182f355509f533dc60d4ec455e82e40654764af27d9a19a5ebdd58612ab04d88241f5d16315f97bc4e6a7b6c9d2b194ffa4ca885e0217c1a719172ff532c8f21dd8eb6fff9578590ecd6f7a20f0686aeeb75ca384bb9cbed3e8375db4a359ed7be34e1655c395ea4882a04ff1064506d82f33719bd7e8cec70ae6cce7d701d3c6e7b03bba09398d2db2cb4fc1bdda3f76e6dd0dbef9a8dc584332b14bbe6d4fea54e899e6b6fec4ccec1fbe8af8ca6c24fd0f3101f524a0201615c94a7cd4cb97f0779989a29ffca60822b3100398d0fecad0c59640012a58f399388f8f410224575964d60f10322134acb971ec130b22712cb714ba8851072737cf6cceb53938f7f94f9ba0f9567339faae9e2227a1cffe28aa2bab06cfad86ee65459133ac60692b6573284b7a522243726b570927555f60b8cd4f80847940d311678a91f18d68276aedbb6e2d756e7c9c7929b4dd80e95e5834ce96039013ce8ee7536d2c37358f52717928d613f12423c866381f0cdf9de1c4cf003a26f6043939ef32afb6b9e2d98643d5886b45419e12bbcce23051331972c25997f9c4e380c2e87c228cfd70a65c6c92e902f0f8338c0ceb4a6819850efe9a8f591d4a2e72995d2a78a4319e490d7674915cb727bbe4759889a54e0b21569b4b0d6d762c997bc85be0c9f688533bb89309cbba9c758382fee1bdf2a7d02aba7bc94214ac401f7730af7b88371da42479b0214bb25c98901494d28da9a058b325779755e721ca0f82b4eedc4996cefea0a561faf962ed1796ec70bcb39f59f484b7b8172c7d8e8a43983b4bb0b82b192e7d9bfffca0ffc8163587a6bb2a4e2dd8373d5ec66bf23838e6dcfa46b878840b698565250e6e5494c701d487ec2a56589cc11cb05da225841037403217a6ace05b54cc804db032333573f5a018ce91e871a3c682afcf4dbb86ff51805634faaaaadce14fe04bce15d3d6badb84707381859be36d2c950b7c6cdbee3fa48101a799549e9295930d171fe94cfb3cab6a374b6bf6fdd3085874adc1f5bd3613cbecb41bd8cf6b51657cb05fe0d60b3f32c6083c4c2daa58c36fbd193d7f58d9f1392f630fb166b249a6495c22165da2084cc7ad4d42f6d84511a6880439d4af35003100155b5eacf31b222fbd7dc6c4287075a730047060ab75b0905986296c61c3f8228ac83654bfaa7f0e51569e474d499013d537a2cba9a940f3259a55ba50aba30e6e0374c07827ab97a65528c0a2b12448977afd83ecf4a4c4bcf6ff01034be945a725d92135e8c8157ab1ba4e83accc256cd6875553a7339611e5eb198d7ab2680310b439ff8e0e30c38d3ce1616b96093ead99393806da19d3a6355f468736ac882ef90218ea05540ed4ce9b2a3132ae4da9336bc286031e8e47782cedf2d645ff675e09c7ce468df6d8e237807bd6e831de4b1c4fa01d3dc6b9ec9ebb613865b3b0c2da5633afdaa7d02d306034901955b26de448e508b2c165ae69888388c4a99f008f4b50ae80168d4b42af22c870fa811ee346c61684cf93bb2661478b88fa631d7a8cd727621b7a713054bc21581ab559bcebd85bdcc45000e37bf0c32e9b7f440b0b832b93cf9b89023dc6973f894632c3cfeb3f83ca238402ea93fc241b187a550f9c4f77c30687102e3392c432300c518b6505ce79eb2f5831bf3c32f11ab5243acc91c833e75db53c98354e878e0322785d8622f60adb206e4c45a3f477a8b4a0c718cac8c9e9c69bcec13889d505894b5ba0c32926ea5994ae014351e173b7176e59cca9b5e85c94d3153d7bd1789f53e2b975a3001af8c399a85c29f4188f7ed0560d9f1b3d6a1bd2cc19cf1c42857f8c8b05a5d95d93a2ba2c3f0ffcec0e4ca001c92ded910a76037de92db80245eefb83d665005cc34b9cc11b76f0b6245a776134ab6c83e6e52b34121d5a53b22170d9f1ca7bd5ebbd88a354b8834b233a907d730a424c3b8be4b01b7f94eda4f4e0f545181411ada892fbcfe438f764fe56588a42beefc87b3dc8abb949d420cc9b8db7af92bbbd921ec797de301b038a2498e6e05c863695cce7b8d2cf8bdf581b893366bd62c0aca1bc34b35dd6f3bac75922797a869ef66b77cd401a032f8ff6067435c9d097e3579a13a707b28d70599a854e8ea602ed054972a2af99e0d6ee325b9a83cbbeeb101a86095d691e8390af022d1020a9c60bab1a1eb06e69f1dddd0f2478a8d7c5ca7cf5c2e35c48e994d309d2f4836f83a0957e852d50474e448f51b078f87a34cffdc43c51bfd9a95c2aa8e9e445690ecab74b74d076ad9a47105c5d27aba9948bf6a08cc33357a291c7eb4bfc9082fd574154093690da74bc598b4343b343e228e10d3830023d5a390f1e0b6f762e492cba76be6dc9293e9c7e83238b0e0bf9818a2e47759260f81a916b7bfe6bce751c01e9e9158eaa0429cd5f5b1dda6f141bdbfc57d1f1ea8206b35d3a6502dc28d43f8f271eb2115163b15ada9c501f685c7ad1c7a155608131a5d4e23ea739f49ec32ad2aaab3dd9c5579acdf85df2e2dcb252531b7494385651ca62df95364b3ba2d085c8b24c390bda368177e3fe8d18eaf8108a7b205977aa4ab5e7a83b212dd33b3b2133ef2c77489906c6bbacd95cc5b37ea24d91bb2aae676b4483180957f85eaaf71653c7a8ec0b24126f19410978578d8cd05094a8b522336519f2c1d9fbebac509421296886fbf03d541e2da28787c6bb2f57a4268751c9cfd13d6d15e1b8b7941af2e18c9c58079b8206d1986b2a2f028c987f3b3cbd2c6f428b01dcb470071658b45ff804d458d2b4d264a4d4397e8f7427d3e02b0c19ead8b62e56295140ccf8043731d53dd1b5f2e05d9cb8b949522266a5d20d8fcbaa11336df9bb1e259e9f235edd7edf406fddd071a76f3f7f8a19dedeaff2bf8139cf1346250db7efb46e89867426ad42cdc322e9fe67456ad3040cdf058ea267bfe1865b1e2a3395da21c0f02c5976148ed44fb2ae1a1262d0a5a10378ce51aada075e7a3e8294784b3355847ad2fc09ad1d954e02f5908bf0b7cba6903fee1e36486399766c91be3abf33c379cce58a358cedc4e1ca0874ed09570f879d5ed21e96095c9c21ba32c0f6ee70f41e9660e5d1bcbee26fa8cd34cf20f59d9d987cd47484bf8208a662376234217a3139fa84bcbb85bf305badde501a083d186598ac2503126cb4222a6c1894190678f8409e9150575ab07607dc1ba2f82d76c45bc60f6da0a7af3a854deb53c6a16fc7bac4a9a00c97f0c34fbdca5d8f17e030c3a834e4a9c6be0f8f7d4a495623f1a6f8435713dcd242a1fee29ad23070c76102582d2d0e913f9dd63dd23bea8b89d1e1c6b06767c66a9ce4880e6cceaff18818c6688cf34d482c41d81b38bd1220e74aca3ec8c90e1efd4dfd984c7910e85406c5edae7051a5475cbe0305b41b3e0a78a73754e56150392150959d3d0bcac633a7bfcfd33286544ec95a9239787d18a6e3d9008ec9c4b57d91272db9fda74af74f722cc96f44d8df8ef6edccb4c85955f7c1c6c1abf21a8fd3b34d4d2a934d3f8df22aad337a6e7f271c1a4626901032a64a49b0edcff353f4b4a8762828586ef5345fe40f03ff1a84077be271e05f75dab719d533abcbf4de45810853edeb11e2e9446d7b7f9f37059045695a699e0f0f6a0fef58a72e70ed0b06e471f98ee3aa8d10b0152b5f3c3dc1f789ae316eb62a19a1c09d6b6725303d9e07ea2e634357a894f72fec75cf4bae5be11dcda56074daad6401e9b69afc57f4ed2e9e15dfe0f69797be339d1bcb3099160161230bcd1c77e58d1764b68aa10bf068cb4d6363b0dc02166f7565c92afdfb0424326eb364a934ad041bcad6ddedb912fc1080983ffe48e07f618c0d020fa507b1bfeb95db6cc1ff2afc82f5d31a00b895996b5ffced4dac2e00876b98f28477278f1e179f556ac277fc1d446abc49e4ea223bf64da34cbdb5b9ac1a9f5bfa7ee81ce93df1228abe1412cc5bbdb574c778d97d8f4284439b17af728c6f29b166b5c6d55dde13cb64004094912d21e1c47a3a4bb677e416af0854205d800a27f2d52d5e6e6486315b621c4ebcc94aaa31c4e7f90000331a9ef5fcc99b2717c0e46f85c31d18bf7628d571f302af8113aa3a1272207ba6179c2b5b9f1cbd655ef32eb0a75a1296341d49f61c83c0bc21300e25bec5a4c314dd6d5050606b9bc6ee5d5af9ba317b1c178f38a83192ba67b33cc62e0c6fecf6b068fa015262d275a197bc0edd350162ba3dddc9d56792f828d7938fa41ef877b7341b246b7b91df6f8d4bee9e5aafa0484651d2747fecaf9cc2abb26eb78e8beb0a80c24837b2474862bab872b4c6a6662cbfc555cc689d2b04179f0e7482a77e4b909db4f37c8cdbec0c183b5d86e44d546a02ee7e3a607bfab16bb9d4b12fb20539aec69b85e3b49263530d8957e8807b8bcc344afcba5834fadb98bfa47bee3f23a7e4d053dcc8c2ee6a853e20a70fa8292d8b08a874411dcb8faf48cbd2a4a3f0417ae66da310317dcb9e0775d9495352dd3b8df608daae1aa21767920a6acaa0a2d040efb7f4887c0e62938153a9f2adab2d7788c0c58d2509cc48d7fdb4cbb964ffb1bf1b76afd01ac165e02c3469f47ebd8c418c8403a92e518e5959a02f873e9604531bbd2c71ab64ebc4d605dbeafbf6e75880d6f1a42f01ca83094d722696afdd7a3b1eb36d62bf6e397b484483e98259b6770c733b77e4f3a715ac9f1daf339d37c228fe1f39cf5be4934990861a677abcf10b11b2d87d7c57011d84c8536f97b0ae3d834d232cc724cfc3443f50707cbaeba6c61fddf7d69271475f286100daeca0971a8e5ec6b47d566a5315076d3bf70d2373ea9c5531995764bedbb52e306e9f2ab5576e58550ea0469e998a78c803b7622b457b07cb93f7b4fd78c38ac191739998bd3f64d5126027bce500e70efa0107e335768cab91f00a8a2899cae11c96f080a9dba577d3e26799931a228629921191cade3350122dbca29d01da807bda58f2985bacf09f8893f93adbfff0622157623cf51f18f67fed87f771352737d1fd9bb0a091bacd8817fe5e5b4d64dff0325536598999c07b9541b3dccb8797440c7eefa4ac5e3112c72acad9e85cbb9bb35fd141f3f02287fa2978b38d5460307ebf7ae0593b806a5b6a30dcfe5d13fcb90ed3e9b018424a9bb72782f7b131d2ca67658533dd2bcdb98574dcb4fdf196709821d61d40ec4a31e1ce30e3c4c46602e0f6c6c3ad2fe1bd912cc25be9da7b8fcf853913e9256a77851b09548c895891507ec1db4a35bbcd77ac988cb0c11bc6e40ee726af89503d080e0bbe0410c7500393589887c85ff0b20894982aa7f99ed8f45f294bc6a5220fe829de190f9c4979b6a215392f878327d82035c28ddfe45fab84f49e7ac0253ac79b46eabbadccd2500b8ae6cba6cbe8a98450b0efc83af3b7562b148d5a46c3c135131551d8b5ec982c317451823a4e432814b1b4f804a81fb9692c492729c81f024fc83962efdfe2526540a0ee06acac03daf4ea955930d2324a071666d56a4be23367f069047a0cb40e966a1a9408a68444da13ae93e385bbc9e189ac1e95086b8cb1e0ba88e21804514ca3f7cac888339df044e31c17fc1c5ab4b97de80d57d63e698448b2b7947b4b29a59449cab7096c09b6086eb97d3e30b7dcba3232c8d2db8170e311f08f0888a22fc1e4d7af9f9aeb2076a25fedbd2963d3cd1c8556351b4639be3d091043dfbe82121198b20b6fff7521fd57cb3e3e3e39cd71d23b2e95612d956153c62613c6819129633830c6f8c688f1c02be1dd82f4df155ad5df6a44e955fb95f0cab7f38010421b1bd9aa18c76e5b5572ec78eb341b25322e81e426cfb4209909d971c9efeb524c4ae6e44a5ad571dfb9b994cc16a45f26c6775ae7dd80ca90a733e3ed28b4abd02ad3b72759a1555127aeda654e31da7b4e478e0f60be9dc723fa2187ef201813131383b5a765198f6933e5683a38d3b52c8ca7e4268fd15299c3cfbca4a5327f8f75a4b22b4c1eb2ed706f6125f4c430617a60181d39100a65e99848e643b69de6da65a264e8d2af434c48759b2761ccb8e6faa4439698661f8ee558b31e8ee550937087e557a3d1d2accf6897262577efe54e0d564b49e84f892cf280302922744bcd8dfb9ca819b1c0ce10a053cf5c317380d23a396d75aa51c43c6ccc29cdc53791d0a69419a5b9f886ee9c51660e7da333a3f4cd8b6d4491d2aa59654e699d2934a90c91bbf923f32577720baad1e74fab66783f1f1ddeef8161a4db973a75fa488f2e975842ca77d8a93c1c3804e5e8e1b34aff723891a13c1f3e547837ed71ebaccdd9cb35c44647a93c746c8d0c7f9a1392ad7a55f8c8e05f153e5cde7afa1d7e193372078d52d099acae770a6e469cbc78c364f5623b9778d39e3202f3248ee016924849e07ae647aeb7708f5cc7b616de86b3d15b5dbf39b0062ec041113c3459c1c76795136f1a3b4a420c28a2004a62c9f5442c4062083093e71a62a3218c11898a275624c9f4e9efaf4442fdf29b7bc8fe1d348242cd0d49a8854a865b36992879faa551b8230bf5ead5651678c59a19235387904a73f04aab8a821e0e1c01491b690eee905182c94f0fd232056e695507ab7c9720e10ee873a3dd25c1180f847068d23091a97750081a3d38c2d3a2e442b7da92a9055d874251e76ef04a37148243145eb93564ff2bbf240835c200238e15b071c6aa1d7a893a26dcc0050e33c86209273f58b543a376b8a57d234342a3530e393a9c1275a051ffd40a61d0f3f1ee660188a177d0081ab5cefb015097a356c151f4dd4e22900f59bec30fbd40372098f6e91ff8d3510039f926f2e10af4014208dbc8435ba011d4a096e6da4b2ac81d34825ba051abb4bcf932492ae85a20eadc4df60df486f9e814067236542f8c50c28b2eba80a18855977910042fae90e20d1f1c118355b714df5368f0ed40869070a41d85ee6e180b10551f630f427ce4818d8ff119f9f8f83cc132f8aee63b1b28460480e283157cc1c3146e6621da21cbb7c3764ac6cd48a6c30d47195dfa08a68c32da3e41ab524466fb64329dc9cfed5d35b65a0b2dacb5d63a67bdd556af1ea755dd6e757e7d247553f59bdbf34b7b18dd88a488a48a481f9f9f3e37249f86ac99b1c618638c31c61863ac33c638e79cf2c978433d5297edb323c147f954184fa5dafce9e55e54ab689c4c3ed5cee44f4db483145f5dfae72da78a347ef221f45964043f1f3e842fb74ebee9714351d84533ce2939a01ecac18be5a2c314938c50db210a8c115249e9ec71048f3a46716411498102a21da6989a4ef9885400e63b8aef28bc7c076129b7a86144a4cf0b1fbf23c14b8fdb7b9c7c3810498cbe5958420d9736ee85fd392aea94b89266ad1bfbed964378b37e92061f877d92e0afcfd73166d9b6d3fd98963d4ebac903f4e3b5610f47e2adcbeed6b130e98b9de18723e33fcc3e4e3a56f22d9f15814b27eccba1e9daad454e8f1c1e38e7e113720a6c8b4aa78c3c727afcd001085c450768442d72984288272fb051060f44146802c7153100910328b2107a928574d0a09840b0849a8840b078308160f9eb50df3ad8f0fdedb65d879f2c4478088bb258ea5851ee50a81ab7d91942832db8dc80ca900c76d028002282881a8c18a2018af4c100fd440c12e5447367881817e5ddb5286bcf69d5ebc10de976a286ef46c1b76354cde9063284040bc7b74b1d7cbedda21c75f2f2b20bcae872b96f0b6ba172bc2e3cb873ceba7a7e91f48f2c9da68b0c5f3a94d6bef7707082285c4051b4040abce020a70a183420f2790306520835d942880930547489e2c412259230b261064746f8784005473ca4d1061050ca20c38c2a4c4c212a32438c26bca0a009146da8e182a21760f0060f50ce78011235a4814343279e1a3cf8900c584ef004061d60e181248800c20c9868830b1654b152d0022f60e8e1091ce880890d8851c30d6670710326928032048c263b08aac2091d74b0d10206135fd890210b355ec0a4491421342152a34b124058e248ca13a026da88220728b016055c74e0461443c451c61313942dcef8e282a12bcaa0d2c50d5c7ce1a5091f6440c5055918470c514508568091056b011c4d0c0114c30fc248830b0d5d28f1040737c050e21d3131f4831b4560f03044126f085598f889a28a268af8e961129d72d5a13e73a49aec23a49c734e09dbce296384dd65c4187b8bed3876ecd8b1638cdd4ee439b7f7628c2f664a8ecfa53f39ada9f5d32929754937acd788f2348223931839c9047a996402b5012384d121125883cab27196f984e3663f0ea19fd5a16e73bc817e236a08196228245aaeb928b1fc4edaa9f2a975a922f0edcf4f15812f5d00bffdc382c4975a00fed9b46c69654e4a299df9a97d3af4248fb83c1f1d747973c9ad92973f6f32becb5a1e0ee6046b596b3b01d04862ac9ebf6e32bf53dfb495821cbd81e8a3b6820627a318e18cdba9a839181dbe2d316d1c7d0f7d3b2409cd2077d7b4e1e958efe627ea548fae6939be9b3fa5efe6cf0ebebb79e8ddcdc7db8d4d60aee6cf19b923fd7c3be98947127a3e26e98b6f5216dffe9ef5d03b5215df03c3c8e64af6e14039b10af4ca6e2db870329a531ca0812e38e8018734be1c0143076270e50d2e8c88e38a35f27b8e7acfdf7b6edff3f7801bbefc23bae1e777fc23b261cbfbbccd0e10082184163072c4c891cb83dc3d321e9ed02ae8468eb40ef459f9e4f0681db8824e42ab6a6cba1f281d5ce294fb6cba06aa9a954586a80721965d56b516922ca944b2a42d454428db6b12c19fe7b42a00fe9c47ab62fc798f56b93fd5c99f0319d22acd9f9380a9bfa759105e57967b1bbfbfb47e19cc6a102807208ee4411be67900bed341362702f8b57e69d545d08236b75ad0001c6b410470d7826c3c0057d308e002b08937b433c8c53ef64563abd57f181a0f87620f870b02685ee83fec8469a72d72d74e9eda60311be4d2570ea691234673fea511e3c17afce801316c0809453d77800c0d0076d9c2e3a5924824120a297021494a2291489c0e6e5a128944b2c0095b2ccb22919c44cae191b369b04bdf50af188f69fb82c9c015941cd1785135a889a990c48b65656ee3250c72b95806fa38c9dc0658911c97bfeb9a2e372b4b7eeecf25c633ebd63179bba11ece7b733a6d4adfa37452da3d6166846c95bcbac8d123cc97968b7a561631467caf47cdcaa26178b3b939f8fc7b8b413831082d4b27bc306a610be3b9d72dc6133d6ad61619fad5228e39c6186b0abb2c2b25a57262bf1363ed5dedc20263c74c98960e8c076f36dc968addcdd22247edca02cb54540df6a85d5832fc47544396dbf1eba6a3b90ed26fd39c0ef9ca4a369f6a8d9194f9f48c94cd79a9f5e9315ad55a6bc2250c7bb6309e58a555fd9aeea334c60e3f9e9244ad095fc7374563a51473e9554b61976563a53e7d564c7dfa4b1a1373ef858ea277cb7be3290d9e7a63553dd5d48472ce4a539765238473c2e93df1279c52d6ba7521c8200068b8b2e4e8dda95516b7ca63383d1f2df32f00dfde99fe390100f0cf5fd7ed831f0e904cd7d975462d4565acb5ce187b5af854916b718dda8bde516a3df1299dd4efbca0c3bfb022340fb77befad987c380fe28e31d61a6badf1c21b2434a5bd511f7397c30325e4e4a3d7c4076f8fd8e5ba2273b2e8a5b491b08cdc513801f46d35f09e968221478c273e06e4d74f7b1fa119d7154df4444d1cb5153080f92ec10850625b060035d730c20d8c4769adb4d2f8da78f2cdb7bdf148356fdd6afe6ea257adcbf97803bbbbbd1bc6e0bef7f8b4d57b4f47f6835d1e7e156efc9c9b95de556879d8afb32222185c718348bf00832aff886020e5bb0734bda8e65aa8e81f9190d023e01f511530cfb58aceaa6325e04d4438b29b8ee6e196c87573baa16a9ab3990d79fa12f81d44c1d5d65bee6bd5754d117a1f7ffd4a26984bed3d1c4c623c3d7d5d780ce39612e3adc33024d6a5f5ebda2cf4f9eefa34c107b67597c758f4d22febf1b2975b1b51952d2fdd1255f1f2d23186f9a55dc734223efe727bdd4a2c887599929710ec77eb1aeb2e5762b1d659bf1a92243efeda3ce0e32fbf36162ec7896eedc3b1ac43b77406216787dcd9cfde55144de1a50a11dc2519fa2555c9d0f185555821030f2c6d9041c5109629193a8ef2437e4556868aac14f1afc80a957f6eff1559e9e1e9bf222b3e6f2770610eb9ff113191254600c026f95d89e2e83bace40638ec3f2c54cc9003141c8e88ffb0105dff88708812dded53498ff1efd4a85fa971cfc44f0f267c8e50a75e378f376d0425537e6570a3892e3264f103c586a01b140c9d71c30c466489239bc0d6a072a415afa95aa9ff681d11a2003172021352bcf4277fbc945f5e563a5d4acf893ad207a195f41a39247b2544fe9cb1213fec88f44148fa20b47ad891dec2050643abf69d8e3ad1e511f7ba3cdde9e99c734eda2539457fc10bb27ce72fb8f2ed36ad823cf861216281cf2ac68a1cf345b6fc6117b03cc68becf6d2de2a9eb29fdce52a847aa4493ec9296a4156ca3f01b94f2c161c08d5208c1a5eac00a7082c26d801cc0e4270638502141dca90d2c60db414110235547083910d9e008263d5328efbb28d22d3fc238ac18cfa4496c9224b46fd238a6109fa934fff8862f8e1e909b2cc3fa2187e725e30459e2189ee94d2a78cb2b91863cb29d3e26e6dda6a5db83559e975716bf1ab4667dfd62ebed8cc3a2699c258a5be6851d8981619badc6c381c32e91fd112460fbdb3914b14516a69357d63e57b362f21e53b54187baf565518f3d0a716b9b38f4fbdd9ac8214b4cd798bb53976091880bad9d87a5c89188fcd893a960a98ef767c42d96cef57574c89376f7bb0d46edf661d0850398a37efeba5d9b8ccc2c2d77efe23a29285ad6b70af89875087e740b7399ebef8ae46a5040c1f63f0312ab1e5a30b5cf0918a0fa7aa3d22ec2d60e70cbd992730893014f0fdb6c090c12b26f1e4a35b6d04051169e4f0c2184ec8e800280b386c80a58b20564dc57711397ce72072c7715187d21065053da747d4e915f42151e76931740690cf0aba05a20ef5f1f1f189b2828e42ab2c4a1a2131f4d1a545e30759a5100928dc47fb020d602006125dec408a9cef2188ca78c2c5d0115b56ed5cb715e8441a5afebbbb9be3628272c73d4cd2aaee79797205e51be132c14b8f6becf8689d3c2e508b18625cb1e23b1d536690c1941a18e1c411341c518316047d9f7c32a7c3e68df11d8fefee87942e4f444450114d8a28e3dbb9a02182de78986db8a7a3872ddfdbe4f6de76e2d21c746c027345328a3733c8dd2564d45d4246cc2851873a940efda2725569559c55cc27e6120fd5f8eee63b1b25de40a3874efa82b406090dd218bfd3efbd57ebd6820babae94b50965fa2081b9a29bc542b9bf231991b4b4ea39c9e8f9b87f7fb27f47327ae8242e5127491058b81cf500871ee060059de425ea2040064f481107166c407962059d6444da723a3ae33e1b481d5a2dcb8c91bbda90fad3b28ded3625908f523993e00faadb49bec54e393d9cabcf07f48e39b8cf26c3912f173804973714bba6010e618e22bf9e13562f8fd2b015461421c03c375e96b7e575c9f1ddf37284ade80d484d97ee9f9b8b11234700e42972872fa00cff79c3e7d8853cc7cee42d6b017047b4a90fc2369bbe696f1dcf08e6d7c799286102a2217eb0e40087291c00c50f0e617c41c50e6d60da0e18cf69efd1eedced6b8cfb6ccc389a31aea6dbcb5aa155dd334a15991e4415554ff1d3ef75592b51361b91fe412623a68d8b37d37bc4d9e38d4493652fcb5acb8c28f6b28270035a9665954c5ac658b2f58ef1eb575ed85e185f5b70c1f69222655e17bdb0bd30beb8c8c1822d70351ddb8b8c145ad5809f7eeb755d570997a782c8cf3c859dc9e34fa1b9f903a9a48c98fcf29cbf3f641536149a93b92277cfc8c81193067f347845057985a8a3830c26a0c0018c2174e0c56aba08a2ce1029e0b881106854d96186d57414a6ab403a62f2bcb5909a6265f287c12bdad86268550a8af1135a65fdfaf5828b143228be86d850a1b9e9d7e5d81af9c2570acd4d8c3d46cb61e0783843dfc09fe6e60f1715f679fd6e2cccbf0e5397b525696e3a0aadba7cda920ed9c811ac02b10292e4a5ad051928b4185a91dca4657f671c4591d225276926bf7106518490424232849881a76479ca5ecd593aaf0c2008944394d6990ea7b40a89e9900aac321d0e4db79785c2f414a6ab1045cb4f4f625996756d2434374dda8d29c30619fedc085f6f439aab80f4ec10ab403c52f2bcb580a558951c62474c4eda5a68401557865626df893b6dd2485ed2b2cb17a5e53b3d6448abaccbaeb2f6561dfce1f256cfa191007a705f06f2bbb2c6d00fe0df152f465f5313a7050050466239dc6c9adb9058378992b71c52f977eb6c97c429857047578287fee08e8e042c307968bd75e8d5f26b4192e5128e80dd720a47c801556690d2aaaeabb410f59ed23a7045bd7da0de515a055f05530039ea990669783ba8631aace1e1503769100872d4e1517654f6bbb5146f1f3a8a960cab6985a803a9bc1bead696885c9871b6ae7d9375d56aadad16b69e22594f65d65398bdf5d6aa5d9729d5ab39add7e6c0395e7bab7c32588e1cd50bc482340c5b9efb47f444cb5f196c6db5aea5a57086599655af93ac161f6242de1b5f6b49ebbfa4eb248fd1e09f5a25b3754c5ec6a167188f0c2962421e0deec433c6afc424edfaad96bf68a1b516429b48b25c463b9d885c12e4d9e9180d7e09438ca742376175b3aec8d0efb5317e82d89ec6c8d6abe31ef8244752376e06d315641a5ca591c5782a170ffd4e39a16da9651d13222a95145248298404c8de5ed8b5a42c6ac9212604023ae0f2b8f7ef71953403109e419ab063528695dcf292a9442259b7844db0fcf20bc3304672ab09799ae546accdd7e1933408d45ccfe3284983bf42ab303f8a4a9e0fcba977280a530077d0290f697838473a3c759a048a88a76eb72ec31f9e1ec1191ece054686cf077c8a6558095f2b5bbe6b2a867286b85a75196d8c3262515a597eecd0a6b4d65a6b8d14153b3a905601e937c2833b66bc7b667c7c627c7c5d7cf4204f6748bc7923a03cfaeaa92c8fde3de0a3cec71d1f0d50ea48af42bc79235c8fdecf07c9b71f40a0c8d1bb1fbfb301191275e09478137560d1ccd6a1b2ccd6e553918dd93a9b4d5b9751eedc77cfcbf3d2f5989a4bf9ac4f8d87a401c0af96a37fe24d479d9e126f3a142ae79f29d6e60c8b60112c82453d0516c593cf9e127502e028772ea7eb293da64cf908146fda69bcb3187b348a3a01d8fa687bf12647a36cf2a8c52fde8e764b8b60fcb4bf0e85f29f9fd4f65ea462f470bae46814ddc62d2d6a69ae1d85a3b6f3e3f65f5c2daa0635af1c1e3918cae16358119a476d18eb2f0f07623cfde5e1b4df98716e39354ec7f48e9bd66a405996b94c0482272dc600ee688fd1a2968713c6e3dafbe8f2bedcb475a8521fe53e92da15bf78381d8db4c85d7bf9f6f6d2aa2fcf07fc5374d4f3116730c5418aef640ddf0eadd759eb9cf7b22ca594526a6564aa5629b37ba7937cfacc4894562a294de12aebadc1524a69bb38c6b767d1a8abdf45a32eaef1edf307902a3194124229258450424825508e360010b29b33fa74b959efb9493a2594d267c6904f7a7792b24f524a3ae7b39171b0a008eb31ba359dd2b6cd598f16e458803ebd3525f0dbad9d6eed0fead404ede796adfb576badd75a2bfd9dd8f95a6bad77d6042476da39e7dc3245b5aafbf1834edb99dae66884b26559d65a1ff1dfec172c6b6dd441653e643de4ce31aaa655f6daa865eda586142bf79d20bdad40b132e5fda93febed70eb2e7f458a94a0ebbffcc2de341694c0af9656639cd12792c8e3bbf7e5a753d7ac457dd789f091e41dcd479277a48f1e9f8a7accdef5c7ecd1eb8b5b67593e2d1e1e3d6f99888ec710feb9f37c449fae02079e0fe9d3e4598f56a54c5b11ee4d6eda74348779c9a7ffc07c7a49b3cd618e59fb3a1170b4fd2c0bf033968f6df96eb2b9e89df496dd1349bbecdaa594dacc18b9e79cbddd17e18b595b1aa17cc96bab32fe9bfd027c5167a76fa6cfcc87ac87dc61d82aebd5a7c7ba047e77867161e547ca298afeaa92df77358f688aa1efe4e7b4672df5427e184f0e29f9d45c5f2fc85dce237a820af5ee79d4b4fee7359b8e229befcd9bf31a16ded02fe9ef8170bc646189fde94ae8f747874f8b3ce0e7d6f3c28479b9d530ef74aa9d729c3004bbd7e5411a4346ee727eda7873adaa388461c50b3e1e1a4f02ca9dcf7c596915dd5244a6678949ed92d88a3415b720fd51e70046d37b0069d53b6106303f21151a74f8eec7cf4944c3cfecde969f5c22a49386283f29dd5245faa90be9a758099486286fc273ada95f9b6d0efa7baeb914749882db9c734ecfa6e447248596a73eab57db29a1ef63c1211b633c52e8bb077c7b46a1ea934d642973c4bec0c89da40f55efa5f6d1e03e9b0e1e7df4dbd7e9145eeea455623a99bfaec65afe32d6d12a7975f28b40afde5a97f331e7c790232824118113201fa65419cad2a555ed24895bb12297f7d66d4f1dbb4e17cbb9ba10ac278609b3a3a3de7ebdb12225787af92d72d12ddfd64639f3c9e222b09d426eeb6a684c94dcd97cd4b1713d0518f9600f7caac3da7aa00e053c9c08634ba71a8f1c1e6c381dad3ab25a70db07046ec311fa36145fde7aff0bc09bc9a277b44aefa40feb52ea70840e42440721b8438423b20d1c813a67dbb17e22540dbe1ded528b6f476f5d7d6f87f5f77674edd2a6b9b8e94815e5de58f0af3e7b5e0be9b667a10786791696d0b7aec47ecf03de6ef0e14caf5a4780c98300f54a501c8638884d73abfc889ea0d277d6ad6342bb935b8ead0357b4cb9331de89f1508c0426df0dbde1c34721a510d2a2ef48df5810f8da0757fb573ac0b78eb5be7a37473800dc51b54b8cdc3d2dffb86879462a5cf14d240518088d40550ee4dadb21d72de0dbadfd2e01078023ec783bda5f17df3001df16de74f9bb0428e0db3b1d9f8087d3571859852e01df28a4a0854b84181bf7d9dc87a175b3d07a69dd1712eb5609d62d7f34c318beac2ccbb26a31cbab66f9d5aa0563b5f6de0c67d82272b5aa657eadd60f311eb751235fda184fbde28889f755077fb9d55241faefbbf53ab50e9f5a0b33e16e9607cbfe11e190e5add68f5d996b49f54a0d647cccfe5da9818bcfde6e5c73d4fb4b7ee59aa9b556ce708633e9bae5113bfc88edf5eb31d86afdd65a7aadf57bb2f5f697b4cea143ec17aeb56638c324b79c74cd89b7f770ae8de42492db5691787a6098eeb2e2e5bb1df0dd9ae10cc367e5a46290e515832c5dcae7d7a76126a584504208a5ec4661cd510fc7895c1d6e18854189513ae965d93aa594d3319ef824c6cd9d6aad6e411372be3b09e597f35507ae3a8394ca2ba565eb94d775af6b4248e9059d560a9f464980ac0bc1d61981a3b5d6c83ef9a285a151f1bd94ecf8deeb64cad629637c31cae732f6107e3aaab9383d7aa76653d949ecd8b2b5e9bc5cfae5d2259d774e29dfc729b50b4b4d737d7a9863a7654408e1c32988f7a307c4f874aab1d1f1439aa3d1010d1d12ac1ba2e48c42b973dc3f3b24decc7cc95d5e1cd237d187cc906573f13d21e2d0ce90232824c97163684b172f470f4c4eab22153c72a48d683c9d28c4bb89bec61afffae8ca7735353bf1267a8c31ca97ccd5e8b0166565b421faf54ec747bff915e9c4212327a8a004e8a3bf28f41d7e2c5bbe7b5cb04c8971ca1ed1231a1f1f981ed6724b42dfc4215fb2f42ee7e3ebf2afcbb19a169d4713c9bd05558f75f3f708245b9a711f0f0aa9bcb4a4eda102dd6c4f4a29a5b3c785aee6ed906da739e9cf2d0d4873d2af36f2d72dc996c2904c890c91e001a1b4ca87d6e995ce5b495fc147fbd11c8f1f4fe429b71f16e38e0a65ea9da44be4db8a495fdeffa03a7025bd311e2ab1a44a20d53aeab639d297ec54badc13a0cbb89497f192dfc68d0921797bdf27e31b7ad3d6c937f92d69321e7343d649a17b5d62bc4b72fa923f4fe1b475b6fac48a4097718911897199ad2b798c29c686dcbda16ca7a4c107d2aa92e37ab1e67f35ff18ef6c974dfe5ec91be329d56cdaded3d97937d533ae551e7af6fb3070e54fde3938799267cf0f9782903ec6a4a43fc6abccd62579192f69dda9b4c9e64aa6aee4cf653424a52dc9e9b35bef678c56f29d580a42f2b619e55cced71e5f3dd390b467c7b4ae1dcb9ec5dc90ab47af5eaf367276a80357d54b5b1192671f72e3635c0419727524ed31deb59f3cd6ea36c6bf9a4cd524311e5349f397f15b922969257f254d041b72c94b9b6d2ecaec1193d996b51491ec3137e63d1c1919ad2b39e626ad2bf9c92116a4e431be134f5a8c96226272929bb6d7dce9b39d9835d2bb340f585b92d39ffe7506bd922036c34512341d3b92cce7f42c8b24cf5816e6497e6d7c1abb2f78691eb7cefa6442e51f160486797badeb3dd2da96e4f81ee91f40623d7324d6e395573af56bddfab55b8f25dd14732ac9c8ecc49d96d1ba93d619f9e83126ad2bc1472f69ddcec7ccdfcde29d7eaf77d3afcf686d0b4e5e1c615633dee4a7ae9e3059cd6c6feba25f1b260ed80979df537f7ad45e0c22fdf28b2feb499850f968bdc7f24e04bf24c763de451bb71ecb3b3ae336ced0ad07db826498f1272e3c61e23434343496bfa780ae044f7dc61f6604e53b3d4333333333333333333333333383dade53c0cc96e30df46c4586de19793a434343434343434383a2a1a1a1a1a1a1a1416d4f361a7761667bf2de4d9c99999999999999a1d922dd2e7f8fa1bfcbf1db97a4fe92c864004064cb6198efac5fd4841a264c98203cf5c688585b678366bcb120179ed12a891491975f45942a0fdd6a79584510591e2ed940c7e43d509f12c93eb68f8f8f0fdd5245a847a7252c815fb724f5e9b624fedc6407a14c6f4757fabdf9ea5283d63b26541ebaec89de89e091646eb79e17e683b277da2601701de5f77ae04b6dc3fe9e8e0e6e101b91f64fdbd6433d3a1e4010b97d6a1db665de188f1118e0aae42523a7adc9c95b60b23af9a558901e18e6a5e529927b7352488a447292946e21e989de65be247ed0084efede1a396d4d98f8ac9ab4e042182aced80110de184298d5c99f30599db627315ecab69ee8d231754c6342e553d6a35f9ba7ec5684fbea35e8e4319a0bd067280ad08f1313bcb06af2c2acf2f6936375da6438f913179e30f152a954bad34f5c3c7101870a1b1861c50ebe1862cbaae44c8264600221b4a087235e88c1ea743a9d92905b96877edab215b91d7a8e3a32d0ac9eb42003cdea890ba752a9542a954aa59852a9542a954a0e438f55697b42822cacec10451a39f881ca2ac64dfec4852721f05205126dfc8045113f5895dc85d3f624bb13274e7e7aac4efec3ad4eeee407b53af98f5d9ddc891318ecaa09098c8f0f6ad5a4c2e1e3b33ab91318b855931887cfeae430f4589db627258f99218d2bb8f450c50daeacba43e5beb13b58012305693c31860cab864ca438e9c2c91059e0a881d12aeb1817c017f2d0cf7718135101e6a36468420d2412359048f8cc104488cfcfe338e9e555e82489493679731249abd9ba3089e45b90b4ea597b2f6bcea49cfd5e882d8c6b2e6f24cdc606a49cbd66c7624396bf8d4940880d55bc9bf7efddd8bf577665b79966574c0c86e10bcb6e596c31bfd584651963128944220959b6d22b6a8052447da250f53d6ae9cbef552d85af656b7d58c5524468cdf3d4dcb48e6db57ae25b9696ca18bef4b2ac676edd5e24ebd5bab0e4f70f8b2d591e35eb75659eb996659965a985af5b1743f967598ee995b2af5a7b5def2cbffc69290c5fea298bda57adbdac7dd7b5515be9bcb5babdb0075b9665617e69975f0dda2b0be6a96ed5cda9c41193960b798f31c62c648e39e61673ab61af9ab545e9492c300d7e76dda6b9f7186738c3f0bd96c516628c31aae6c2f0f20b0b8277f8f213c31bc622db08c9f1d76f09631a7c8c6ded8a15c9f1f7aaf659d0b220b417c699f61e03f1f3f7df951fe0b83617c3ec3dd3de93302139b02239de6eb174996430cbb22c6b66e65e39ae1a6c7fbedb912388eb00949df1e75987e6c1cef232c734204076ae776f48865a0cffd2fe5b6b7c724e540d0ac3b68afd783810e3f9f170a897d1349fae5febe2edbab47bb71ecd51af0fc62c2887e7d876e20ded211a097d431da5e5a95f5ee421200db3ed063f9e8ff7a0147dcdb2ecdeeb413357b3b6b0dacb70861b6641b1b8148a0d7f33aedac9ad46f219cd49125966a76fc5e2614133bed3982d01f9925f0bcf687075d282661ce5349a13187c85da7e7c35b30d89373493d186942a69db698e02b981957700c23f8c87246373789470d52e2c5080fe96fe61d921068a930f9a71881d7df588fdc3b2430d6077bf99751ff8bb0d698eee0031b9a974ed3d99dc72d3c98431ceb28b3196b117c653afc57ead998b51da5bd1686f45ea9ed1caa4759854adf61ae6ebde1e240ccc77ef8cc725f7d6e52385a91862c307ada2dcedb828c6f33e6a41d85fc578e047edba92bb1d286f473d1f77c237631674e3ea6e167a67ed7c3956d72d5a6801b9d8701355838267e8d051f3917b92479d9c78d31eb72e898e256c381dcd1999544e3a27465d6a3c1177e46c9ac39aa57028deb467da0a6d83b20af42adba0d0e50514906b87501e37211409a1b476c127a010bc13e83b7846cef9944dd8748c615aa9a454d61868c546470a88a1a738c3f07b3e30efe2d531126fe808d9abb5d64b66d865adb5d6a5c127b9a5f158bf3421cf48d459e11d1dbd236bad666d61352665589c3d7e5cd872899900a9a0723ef24b8ccc80dc01a06da8948cd784492c9daa1908000000c314002020100c084422a148342819c6e60314000d8da6486a401909032588611832c8184300010410002200323333b30e0c5666e570c2dfa48ac63ebab7a4ef10384b1181a5cc5dac50dc36e9a8b4c12c1e924a1756f5176266ce496c82e5889725edb7b9c7700aef442bd2d59b163a940808f197b68e6c113dbae8bb1466615d4a90feb2122a263198c5a99471e948af3082862bd08186ca8e95380d90b02f69d6e1233ec95fd05c483628ef46c4c94ec297f49dcd5925459d82852aaa5a56e55a516e616426c8ecc03d938b3453b08fedcc20c2aca27a332189b7bd9c6cea04c60c389e15db93298ddb5256f644a030dcac40bd27a3ca0791f96a8461a669ef032d7ba3c482b7523a9770b2fd1d2e4ca9a161cbd65f0ca58885186d80aaf5f248c714ffec287cdf8c98dc1ec9daf79274aff6ecdb5bc7dd56643fe45306a8ad6a7a140980712cad291f049d4f28d093ff96843df513fb3eb85c9105991a75bb1b79203004781ae3106ff228d13a3637aee8e843fb6800e7408cec9dd2e6e571f3cbb321a8e7159af20c74b3f1a418670fe42e656c41c6842bf94dec4e6531e1a45c82fde8110c4ce024f4a4f1afae2fa60ea15c945ab9cf5a6a5e7cdc9bce85bb4084f681b2780fcd7cec6c98701d13f3a694d3a7ab3826744045176ddaaf02e6cb78d317db0473672840e17e8255929f523d5fa1587aa8c9f62ccfe114a7441df411154f6f4b11053bb3f2f235ae5a0701dc79e734f6c0ea0b82e4432ec71f9df26793298baecb7fa30071cc8e70107c0872ca0cfd3d1dd2ffc16901f1c995be9b53eaa4f945ba4c5afc4a9c9c58ecf3a11bc0f10c9a33009942682fa569494e0f1fbb09c9efee46c4cd4a3dec54d68c7b36c50563f9387fb01c5e8e42c80b2188830dc8441f46d6213f7cafa91404542dfe73cc51731cacc04a85d3c0273edaa477885b2ac5c85662bac33806e8181545d54b5cc3fd6916e374b3057889d5e5008037e51cec8c0c84941f200d3167674d5b59b3de58d952f5e0356235a60ff40e7cbc72d5655d8c9380730825b881365f9734274446a11526fa90ec54cb5ac1f518dc2d33d1669ef512a9b23188e5202fe88e8e633845157e240a69ee3477f369247444db129004cd73a1112dc8a82fe284b983cb1688de19d4c8790c46b4f1a9889420e3e0e19a8101f991dbbec14c76f84f65b8d8fc82ea54d1511167346a4b8960deca5182f5a88530e0a5629bbfc6ba9238f6bf87f023e6c2373d21a169e1f0e8703165504874abbcf74f9d49114681e6180b03208e6325eb1ab54efa5bec99eaa7e18f4491c05c5df942606e75479913d0b3d0981e9dc277d4eef7b7579a583b90fb01270b41a97c25332171aa163038ac4c2f591b73fbe768d4065e231fbf2d32f38213cd40d1100891fd90bc2cf3fc16d46e77f84fa25a78753ede3ed647eef3da6898a7a19cdc0e5c186ac097e5da552838be6f0e736b9955363807a7a102873b3ee7758bdab8b762fee71b516b8715745ff37b55de59f78a66fb23a871dd760feab6230371202f2e0f091a4ac0cb15118a18db4f7f718467d4636fc6af5c8610da42d820f1e94b000369ee2306429e249562d5b048f252584e438a1e13a56b33b7a4229fedf7f93b6293f1a0908fca92236a6589f541500940beb79dcdb23af7490d4663ae31c791ad460a19298659ae3181df76bef060858c1aa7abb8a1161588eeecd28af88224a200c2dcb511fc3eeba4a775f2e33c807d8d23b7f3b34105b33c7bc1347aa231e941b49caaedaadd64e72b53a2420d14fe6c2c8615efca3b5e119eaa115b00bb03eace7a49e69f3b7c2db6350e0e49889316b77444895bcc4ac81a10b52901916bcb793c5abb63a55ddef6ef621ecf9455909b2377f6d99c5fa3cbfc259deee37e13685cd3f7cf259d3406a8e6ae4e9580e548a80dc783fdd7af2bee7d34aa132a0907ba22ea48af8811ea6ba684bab71491400d53011abff47336516f40dc851500fb8e34fa3ab68c7b353936584457deba4d2fb49644e9b88fb7ee7de35fc2b0ab91e2ad34901ff3739afefd721b6eb504bcceab5c607e37793f80fdfe892244eb68424dedf1b68b03454966e534c6f5e9f5b0ae8466c4f3e6bb7d407354385ef51097576e9fceab5b7abbc7feec141405be7af8150a70c5452fc17eae50aa3f3b518dc4d26dc168c30fb7e1023566197bdacb334ef5ea958bedf0183149f4bfbe81eb98b6279cb30d303fa8dbe2e9442e7cd3209fd3640e9a3712d1d3399a723e1d66e41580e91ea57529fa4e9ce6a9845a0bd94d26b79129b2fe1d857d93047a7a6666ae26a4d5725dcdeecdf801f052c30184bfee8bb7c8ff1f67504958407475f43b1bd066d58ea341877c9bb44c0aae46e2a966bfc3518cac4cb54663cd5290ddd21542152e35f1bc8dbe0c642aa3c042ec15f0ba3b8d0e52e982f1b54a8fcb6c524d17bee0304e8dea0e4321f6f845986acb50d253bddbb2f88525fb7111333a05152cdad4b2f5f57f3fcf57268eb627ed0fa685ff822a1497885b43562e61fd96f4a6256e193053d82913c1fb1e0f23f19d43c1628ec8e3376c2f81f98162e6d1f5c14ebccfff9c7ec9f492eae2fd56712307a3d4976c51487fc0fd116fcf27cbc01c4d45d23ceee306e9bb1989ce645f81eb233fd9ff99bd64bd89f7dae6d794103440e25163039b86eec15117e180a191c2215611599c85765edce2d41e211e10d8aff7c0758b701cb1e270cf44761c9371ade55c0a9173d9c5049a075a3df828ea6b8347dfa30232066ab0472f3f61c99997532fb1cb2c6bd441d4f823306f07963f9e6c6e09e677637188b5e237da7b6e51d67731d64a43e36688eaa2d7461b085a1da701b60fee1af6fd714fcecb099b5777177fba854184f3b7e9699709a83aa85e96f8a31f212a3ad8674886ff591f4d3884792539e1a02ffd6699fc9d9d9ea95f79cf29e52308b2d7ab509dc20a426c709bda617cfd35b787fb3319f0cc47f1fb5fd2730cd873947090dc353000ee521e02c23fab260b1e9f7ccddc0f3682189f224a5da3dc895632993c38d5405afc480420f2c079b074725f3f59923fbe205ba54a83ba50da2c7977b935405bb902c30b52e4416c9a17b0ae3da4eb2973ade2d63f382c038d4529eff068eed51879d745d7c8944f286683e88b5470e61bdb63d01eaab4282ca668ac21cb01ad1175e6a5c07d12c5c703e42ded7066e5ba868a0561f86a8412a9ee7f1e8e746ca3ec48aa7dc50b8ebc2a57f08a68c6b3a5b471899ff5674e665c87a375ca4aa3a5fcc515b766f1a661626263e6b02efe04f846c7c50c4a8a5811df5e5f8cf317b4b2a73f0857f7e95e1093bd072ed1752687448347dd5b4110289f36a9c0aca65ee569ea8d063ecd911ae0730f744765a80997a907e702fde821e5d166a3d09cbee980db0a3ff6e1d7a4b780cb4d5e0eb1eeaf6e93a2f95f5384a32884d334ea8701ca89e9adcd2f4699b983763c0696359532deb0f460dfa87a52744857c1464e8a7e7ab21aebed4288a98dbaa4dfb54a0adbb851970dcd70ccbea4e8149612e848d145c0d24a9b863be7299081c1f87b4b8a5e398d3d779e4dfb3e5007b48eb132213bee30fa0b406252f426a018cc3a7d123dfc1bc60ffb792584112f93a2ad3f5f38ba2296b9c637708481a7663688e185903033295a8214cc5706ea87d16bd2a571084e872b7431e81f880936be4ff5b5139d99d249d1e63341bc93b358555db61a80b2e94708aa94e40acf4f4ce14cc8aa279c44f828dbbde5df410822e6995d06e5f22db3d279b4877a7ea8fc0d520a87afa7c46d5ff7f0ddb2c385c7759351ee29a650f88a31b47aac215f83ee352d542ab940674862e5e4bf81d7a41fe11c160b66792d87ff456f969eba1e683fc4b3c190907ea4d0548a1c04424326eada7ebed28fb5d1fb9055031564bdaca00417183a9de2f03ec0831226255e85392a7edae722827d5f63ef00efc46cfc2ef9871d7994d3e624bbd3f7c6b15e03a9ff2650fdbe0aa7f2dc1a812eceeb4e0b31503e32d4c8ca660831e1aaa99bc826dd91961fd47cf00cea109b7efad13a5378df3ea9fd8714d9477ad1c3d28f70a289e1108688ac26492ffd782c9924af19a61f89711b514d7238e8b68d97eafd06fa5f44feefe1311a3c2e92ed678f5cc650fe71298a24c49e1a8f35baa1ff241126d9ea7443dfc894db2fb4678eb63a3f584c312a8b6fa78dd5b1df839c3fd2230fe60c94f693518223a072754c3f5ab4f90d95fc464d18e2b91fd612935acef0dccb078323a9a22023be3d4c11e128324a5e97291595d65d9eb231829c593612c2bc47ef6021909abfebe4088befb56ea06dea13b0aac8d5502442f7cb8baad1c9a0cad542e8ef3ec6860c98ab245743b3a588d3526bcf7b4e712eb51ce4ddaf4cb296075647888f6c381becd6567d9e069deed991d18a49e00659d33f90c593b825ee38ed3c6b0ba42ea14efb9554343ac142795566c8fd8431564020a997ed8a255056f288f1ad22f5b042eb4b5773f7fbc7e1950ea841212a9c95dd60f2a350de4042fbc5b366c4e7a7979b8e20e2fafe6c175f22a4f44f11f27320e08af7653dfd6066b52c6a65f77c9e2862de86a4d2ab1a6ac64cc154a314e10c5a4e7dd0326857a541d046ab71b7559e13118581df67a481c6dd28c964c9d145d56819c4515ead5bd44e8c00191724ccf4e5620676378a23a14e9aa9821dfe44e754f532b3e2d5326584d657eab7dcb7ab2971f128ecc2aa891de42be6037ceac3716a7737ef2d44ad7dfad00b4b2387e49e83c701a9d83488899199cf478790213ff4099a917a82a74861ce318b8d9a54a4feb906a82ed85f35dd223ef22b94e6aec4856a9c61664c69e9e8768b3341440c877bba362dd2bcd0a1d4efeab1e8838d6ebd7ee821310c104701a55ff8182628d84053f082ee1ba0cc164be09a8a94079d7dc7fab909cff9d5f6a4f2d2e8d32fb9de32ea4d287983cf230989479bf0495439c7c0e449ec61f4953dce0de51422fcc7f51d26a2e0c7684c54d91d6c737237b55ac558fbf32a08804312c73109d5e21a4fc99222f21091ab16e3f7f06a0be0598ab5a2522a37d44b79ec2099c50e71d319b881427cc4ccdbe88b5835e0fccd18de40b8320c357737a8ec6d4e59d3d75bd47843d5701c329681cd7a9f90cc8b282cc484158e275585942e207a0b02f4803008afadac67729b1bc2c1882ec781f19092daf008e50bf8197abc60357ef2cc23bdbfe65ecebcc278b86a91928b08362ed8493486a3d539264e830c734a5e1869456969bf2241dd70273b28aba845794a95327a112025107caeda73e4c8bb93a5d18e15eef1696f3d772c8463f5f0e80561c545cbd5b5bbbd1a7fb91e09b24e5b3216cb14ca2c72f6c316a2048ead9f5763d82b48fbfdf145c5446251c37aea91bfa257035d1217dffc5e204949b36443a248f6cb276c28ddc9690b74338d945517625958f4b6014a9e5389b5de87568ae0e7c8fe075c2d326ee3fa11f95e49d8f827bb1d13f23291f9f724a3b980a79e53feb24222bd41d396e5eaffe90d8a80239e4b288d722f3d766f026540cb8871caeaa237cd2fa87be816a5a396e066218442394a71cc69149f7af7c249ce105f2e228e9fe560a3f176d213e5282ac0ec68efb4e8735207f1f797e20fd709f9f64f46415fea3ff7a9b7b6e198938615cbc418457bff62f63e35f3686e420eed6de140d03b2584712721105a3dca85907ee1aaaa089a46854288ec35568e4a09e80277317d7215b4642c12c1903f35f3423a33c73e6381dd07943fbdcae84ee3be7216097804c70230f06bde026ddd24f138005ad5435795edeac0edcb001d754bbad55d7961d97be9918fe169722d08368d08666f2492f0d4d231705dc6e8645820f2fa90a5b6434113cab7f09a589199c14f82e0347471abc719b7aa52e65bcb058fedb092d3687de43c6578afb723afa9797bfa8e3f297fcbc91537c83d37ecc6451e14ccff7cdf151ca9f759c5b22775e06d755c76c4caa1aba646868447836f86ab3cb6232e7c8ca84f30a2f33bd9d2c6d3123ab8fbb063346e60f66d50d2889e4cbfdd4e1dd8712b35f775b97645691761a76eaac07e028d017088a408d70356781322d15d29bb4e5ad80ddda373f853abff36d67a115d94d8f54e161fbce7f8af34b542373e77dca18a83a510222377756c95a1cb6016b860d578ab6202cd9e1a44eea9d5f88e06c8b4f01f4ecabac1a608ed336365e431680c7a893130ee5eae6e1dd000e9e066220a44647c25174c151b0b13b4aa660740a1899c875757144a8b3f94d3a0dbce9a6b07c8a913ac90d275c20dbe3b25a3bc9aaba79600efbff0b35baf421196e6fb1cd6c9c682955e1470c9ebf4498848eca6170ba3837f970ebf9a6bce3a28ec2311956ede925202a89b310ddd44f9cb35dc7bea3ae778edb55d4f6b68ef781dde4b6b58200f8b2b90d539706548d5957e8bb86434a61e96baf5551ddae9efbd0397e90b13697e519921ac64ec4e539b519dae6c6ccd781dadbed14c25add8e88e9ea8b69de4a92a7098a79427c10e0a97064b3b78d09f5f696bfccdd1202456b7d3aa4687290833416bfe917f58f2fad81d296032950ceecbafb23db75039d5d4d6e5dc35c0986ae0674a7c8b404c9f1cf8234ce161597d36325104459940f7b0f5529f6c2ff27600a3a854a7d701659e9504ebf1fe7ac5d10837ce0a8d0d962e637bb906a74a1c355d6d91e7806ccb4f4c7bac94252220272439ddca50050f739fb74c466b0ff1e6874de36ff5dffb719908ba4e2b1ed81dacb35a5e863ce5c5fd317deea8b29f6d4423bcb8ba45e81e59f697bad4ceca77f7d663393833374e4d57601af3f441de77cba5b2b53eadcab056cf4b072f35ab425e6066466c979a8584759b599774a467d0e517647b066bfff2cf247b2b6fd6805a76a218b16a0b131aa6aaabe3d9920eccd858d5b6b72a6a1dbb0d00aa1939f5eb5d3c7652a29c4e7504c115cda07c65b9dee58d97124655a0aa0e5ce5ae0c0ed0adb3eea1afbe6d75d85f0400ab3d8c747318b26e9fe97a412c5c55b85c9f5f66fa657a20da5b5d676dc7115bb9256f097bd508e4ef2ae22106b5c041ad3a991d20fe9e2217049294c46ad2d3b011fc9d0e2e803ecf708c609338cb6a2b8e8a9a6798fdb743c40f4c1f3a062a5830d577fb51665713014b5a5d6067c1f3d772591c6af2d6d56171943124744e2ef39281c6d651c10691492592e323e5e3bf792db929f44dae2c6fe1c238532a7cc60cd15b6ee71c2e487f4c0c0d5351276962edeac154d276a40a8cba7705db28cb61a50c3ec0c2c725446fccd6a41d6dcb64faac1e9a5aee8ed504cb0348464761d564deaf69736a327cbe8aadb275760f700b410954f09c4ed531830e8b807e4f566dc86fda548714c5c9d92a53943b2a42fd8f290633141e717f90eb552c559d923ba38b5bbcfaa4803d34494b181d7c2643a4808699cc31d055bdb74781d48899105fec9cb3c88ebb3644f7505421b645d0082e785b469e134f5e43517985826ca4a1e75d04295572062863a434da19e9dc2a51639060388a6ce66175403ac17083a530b088c83db8b1a29aaf612f92440128de5f39a5bbe0b5918f1811d138781534d84a4b5d55e25714602f29397453fdff3597b1e8d41a33244391b964568bcffd245c69f52e0f00b45c0359e2e22e777296e6dd7ef9086c29a16403a037d87073a45b638da8f32b35e84c97083428b14ec030b16270261a8fd3721c5bf1d36a621377c0879ae1345d5d107cddcbe260bf7d57343b4f7a1260ec245d21a9d08cd06ac9f93f1bb8e0f56b61b4c36e2ebb46b9b9f6db68659ac7195ce1fc22740ce23209dcacb4a8b1a0aee4fa5a2931dd2315c0e854ffd37905f28efff4eb5c9191d1db0e723d515ea480112bc97b6967482693a7c4f8be8e57e54f815d0185d5dbec7bafea52a23f9b6a21e9f82863f5ea52e3ba8ca7e34c4a2686f80b1190e117e317aee55f887729a2fa3e9424be6fb2e360628a5dc4d2cc1b105b1a1a89e7f1f50508a39bbd42549b9a5303a7891f6d3842b61a99ecbe53542f13935d21d81c3df1322fdcd92bf8ba939c0a84c459a3f40cc500df12abf12dbbaaf52c0f90cc885fa55c57cf12df695a30106f40d87d425038ccaaa0878245f44cee84c6908cc99c896982157c8f27f6b6a105f7f1fe75a91282e3c53846aa76f943b17f8aa767e8555bee7ef0123063f9ddd1ca15091a72590a800eddbca6dcd19f6ec75ce28e0eefdbc121bd45a16f3bac9312b14d1c3a059e1aec730887a3b05bac1ba592a48cd3a970facb61b575ab779c0a11552cda76ec991d1d10a74994eff3c100b82814ae38f4fa65a6702d01926b9ef6b6c931261c6cd6009d9a127d3ca86689adb4b4b2372c7c68e53482ccb39f432ffff0fcafea92181cde6eb7c1d011c81bb7e2a25c119603bf9c3255a93672db133f6e1ef49c585572b861e13ca4eea4be3dbdd282130c854c96dfea2122269c0ee129a796dc3760f819f56183aacc6cf10e329cf22c12ae683001efd100e60e51b2caa420fc5190fdfbb9bd1a7226b3ce64752fa07e5a8ee888518542cbb9a900260756130483d3450626d48c0f09fb0dcbb10a25175cc2336f370938b2e960910c23281346d7dab8ce6de0fad2b8d56a78322058845943a5954fa8f0dc3ba9e8bd111aac47cbb2be628514d82b3c53f76600a3f9fad0803c7918bf1597646dfd1eec6424bd1a630060fd5cb85d49f80ea7e0e6e81c8cf5aa51b7ca5ac323b8ce39493e0f97985dc329499176769dd3222c7ef41fce9b36a59aa329124c6a8f36ad82f2ad4a8c573216df0c90bf1896ce6086cd745b7654d07a1cc8b6153b808a28add5e64adea018771274b3405a4905d4d8f8738524b14033ec01f60f0de8644251bf7f97b001132a59a464aa79fd913cbb667d6fc6ea9d7855303f3a5a6d4a740de31e27833e30db4ffa095d67f373115ef2bc05a0df0d7da808cc184ca20a0445d97cbd07e7ba7daaed07390b9d469be9ea28d64dcdf95ba274738a723ab0511f6d76aa9197473b4c592a4d8c407777913ae9f8cad0842df2da313291bb137c0e450f498d0404e3533308578c501bbe60bc529b0bed20a7b164cc89be6e701ca1f1472335e0ba9d0300bc11c79cbc036fbc471961e0fa5e7f1a0fdd245e6b0594bb42fa5c1d66b17cfc02a8cccd81bb81272f762ffdff7b79a481c2a88fbbd027ecde406cb0b1ae9168391bde557ee193fe5c5c7caa8f9fa1d4949c24d39890d2abaff3ebca8be5e64e5ff9955051e4ae105dbee8a996a8d109990701e71cadf878412f50ed49f1af75afcece60a840eda809c6f00825978e625070024332192db7ba75522cd4ba0fe4709e403d9a74319ca7d6186e4ef05bf511536ae8c63a7a63b31532246ab10308bdd6a44784cc98b9ad8ef770e30224e16d8a2897d4858b137a87437e5c3d50dc765bfd2c7766fcbd77e742870af00158b5acf0b523dd590eed5c1249d3a3e2bfc86e767f0ff0ff425e41bcb1f0997290c176bc3a85a292f9bc14c19cf449e331da951b6e6b2e8c26a4387d2a59e33d0cd9371241440015492deab16cf3a4497805b77a158146c2fb9684f5d4d4fb4e7a8e36bcb6ff61249af4879277824f3c337688ee4fe8191899ce34ed48f03c684a7b2a4dafd27ba8b09b679fbf58974786ee81d00d1d26d009615129425a258548f721530c59423102c43ced9d13a79e5f8e48216b00ac21557c72b05282da89c9ca9ccbaef5f3653ff4fc9b0473d58567a0597e1a3afb931fe5b0f6ee7bdbf6c85e870b1da3caae9a751868deb481daa6c731c93eacaf62d6797f02406ae4f5ae585bafaeb265720b646d627a5425e76de7242b275d1255d39c952c52a6043352beb8f6a9963e658fa5c582ae223d3fc2be5eb2d4822699782176582712c5b00cfcec25173493d22335f752a82f584723c1319288f35672eef192a5e0cc11a968273358909551000cdfc3e01b1686200b37952da485ae425df7671450b51f2763439b0914f729642090dd31b85f697140039c98a106bb1cd54fc2b1f19be42685ec96fc95a055c2db8aecaa21feec4fa21a1b9109753d1ae9a4abd1003bbf0c5baed435ff6f766eb40c29964add805759948b5e4e47e2d471ef51ab347515b97822b0a1f11dac3f7173fee354a4640921e6710fb8488fc7e6890df99be68ed63dde778f1c94490d2e9312fd3dd08dcba94e17581d936d310f3115a088b0a86ae22a6664352223ca2b9f4bc9f898415e4314821b4663a2fa90f8db15f1a9f2e1ced666cd6b866c7ee02f447f3390c670c4c32a48a461ceb080faf0f430f8860a66267dc211299013f016ee1b5919b328e43d2bf2427665d8acbc820c89adbc992202b5f8aa8bb37dddabfb37de492087b1d3290e8852cd386d6372cb48622e06e5d61ffb40a8a28d9f4d122c3f55d1646c2133ae7db1c6aa6f4b27425a4bae6b835dd1c2785b9df9fc1c8aae878ebff4837d800bb802a08a03ee5644066540cc17fb6d4b22c1857a1852de1c8c2d215e158c4c12c0665e6911daf707108a374c0e5d0c034aae25d54591769b3df7128664f09cf2a3d41b68a9c2b368825fae7007f5fb7a4eb6dc8ed60b9f9d83eb41a70efea48610167ddd4008358be7a0b4fb440cc46879a7771c84b29b3aa868df3c9acbda356b0961af425e975e11f36078698e5c290af4e437222a7f4229947555f8d1161a70ac1b7e26c34bfe17def7eac68e4fe7c8dbe2f2ea7c63210c534411ed5079ec58b54d505df2b8308978fe7b853431602fcdd703890e65e0315698bbefb52489d1b3c47114a32105c02e7d9e24a7ed81991067c0852783842b5605434f058e9096b8e0e5a1a3aa0aa670a785ba4faa631547b97eb90af7290d13196bfbfe1796d22c6c95874ce9233075009b5e0e525a28b68191bfa531c624f3f778a06f529a951c08085ec827f8d66433b37519fc2d6915ce2148cb4a5af2341dc25b9110f10dc91459cb82c5e8a5cf478c5fe2f6c87c5acc154b6d47112c910e7a282da8f81b78d3b20c42e583559cbcd85d27f902ae4a2127020cb45b5ecb09fe1153715b66bbddf9f78660b5e258499f37ccec2b01495f4a49eb19afa990e912e0130ce4d2f30535bb5d6d4bb1b743bda5759bf1b8d1c99dced6ad6a71da16dea071999ad75d90af16d145dcc70b5b4e9660b39c1acbae26bd6f463a5e7e02386464a1d6cb69d83c6deb5ca93b514130269f3d5e51defe6fc993ed9ae94e32e9b0771bf27321e30b9bb0b56bc471658b54bd2fb2d3a3cf6fec2189e549c588a4e0b60d5080d86cc744383e945b8709d94f172db52fb56383b4ce135c39dbf75ed034e7d0fc686b997bdbf48583bd6fd05220c77882e7016bd88ef42b49c7e2b267ffefb95c4a5b2c9c49462655b5b10f780e393d1fd0d61191d13137d57a458f97587270dc9764a14ea36537ea9af602d0420b76e9bc8f59a3fa18ddb3dd88c0901ec8fa5132fe3eff4b4ef7ac63c0cebe354bf27bf9027e0ae800091bfd65908b74cbb38721d21b343cb9d1a2cb94cf5dabbe41db98d1da050194eb5bec7105fb98d1790b6c7ca1255e93b3c84233cc13bb938f529371bf0b474e330f62b3602c9e979292c83abad83ff3969c703549131f202eff81809b9b815f50e06a5680c9bbc4100b91046b33de2b32cb9211cdb096519085b2322b16a52586071de2da16f322a165842943f40124ffa613762d4a2544bb89219f421391c1b15dfcb06b605b068bf71e955b200d6e4a785c15dc029fc8507963296074b8cd55510450bbea3a8bb1c15ed4df0c0ecc52eda7d4890c03942c1e028211fc5379f389751ea02809d2128deae95f68d90c7ffb21ff6a8bb0b91ab91ab5043c1c177dd82b5ec3dbad7158d0ae46a490f2898c2b8afde38d65441e6b02cb2684227c135ce6ee662ad188dcb23b492501bfa7d62203895463fcb348e752c7bc29e8ec8de5bfbcd166723c326a4d505712dfe21333adc4a6a523ce063371d0d96f6f4df42b3e5c2b9cbc10fb339d5042db2750a4aa04b3a2ea4ce5fa7ba1fd0bb3d5101a55ede355015c90a1436e80d0d892b9abf1b0f00d828ce22bba7abb2b87c8da979e20e710a14a904151d1f699e366b4e703e3a9f0b934ea3e99e3813b828c68838b51bb42052fde84b58abf6aa917ed571522aeb1e3418d7981e8919280125ea3780003d51d2fe742be7e5b85fe7cd29310ab2c32d2eee166ceaa475d5478e39921d5069c9088b402be157769284b5b66ea97f9fad2dc40d6bcf2b4bbcfa2304bf293320daa1d45a44b57a0983c4b6ba51b088efce836128ab893f2e5d243ceb49394f2655947852d4c1f3127e2d66dc1363a196cfc73f53f5940fa2136690ef2ca2ee6234fbabe45533cc8b80e8745f8aa1e9042692d06b86992bd41c8c41f61d61306f610845ca67198e059aeb143cce283e0236f0181df37f6b9f831f1a5716ec0d139b9c0ce253bb113affc643efde93cb68b1c5543002f8df5479ab2f034436fcde89ca1661ead9bf44616e1ea5e9cbc8437d4910767401169d0ebd842f2aa213797ac1c019bad92a378fa725b140e8a97f9dee63b249121121fe6c8b86c12d005c0933996c6a1964678ded36d216fefdc67a9fca049af4d606a1f1ec362cbce5f943d963fa82aab046d8e53eb432c5a5aa7f5bdd0fd5aeac484369aa88cf31a5c301119ecbc0f6eaec3c26a3839fe69ace6f595ddc5790f7bb8c472e74cb73f8b5aa51fd031408b4759a768828f6940f7b638d5270656680981a11f15534b3362aba2addac6a39b76e4858083bfd2384ee412c7f1cc7a67e228e84e1768e99c9f393d07f2a0d32aa1e58738eb6386e0837dc7ad003a24ca37eb2b186bf8f7fcfecff316b5d5e563dcef65c0c626adb6998a7f35d01cb712d15858c98eef3f9a4093291b68a499c8f8f6b006508f1ec8564b4b1ba11e9ee1236864bb0a4f0e1b843977e77908d7a66fc834b29d3e8e4bb3d5ef32393087d586b11bc19648f1ab6411da298e30b1425b922315c3570b38f4bec1b01ff832817ba70ee7bd157d86a4b7934835bcc8c39858fc17adfb9a83a063834492ed2600644c712db1642dacec5b2006808e62c53ff6a04494f4dc078488690eec062837684e386d911a718c551c406f9d3de7e680f6f6bfaf88de291d02183d245abb02fac5bace3c968c4bab0305283cbaca17d586fb0bf4c34b245165daa90d2a8ffedf6026ae89455f60fce8c55ff7ca409013dac21a16a4400678b99223dca0a9d08871865020d5733de69b63ce970037aa3a04d356eca5dfe4dc9d9ccb9442f0e9728bf6cdcfe7ede6308bbae84d6b76ee18dbeb64bf207b3313a0aeffbfdf71e92e1ad2c3f600c4c14c35eefe480f03bc8009d92700bc2265cacb58987d0bd8fb79130e70db3b9ab9801727dbbda2374456a9002f0331d9f0efb4e902b41031a7c4174951a4c912b30c8b227e8d8683c60458bd259289db6202902557d68d9d61da0980cd624346910811e40a07961dc6bb0619b627257e96a23fc4c121a77f9d505d8494c669b5f5c78b507ed83173e5ca544941e7c230e211a04011d50748eac08900eba3b60ec4ca4e63591d645d908180681c3454074d1c82a8952b172c2f0f16e5c48f4a1422d99e42b5415d70dc2307e17068df796d8d8ecd16edb6a3b4c89fd5b034018f741c4999351ca0cf23fbc5361544155e732070c8fbc5670e766ad9e3b95c76ca727630d958ca98de6ef91d96f4fea52088ab0faf2d61d80c163d016b59e9d3a582ba12f1e484372ad4cdba0add11acb42d113f32d204bc9a4cbdd09b4c1bcf80cc9a08affb9b9a8e8f12b28d12574806cabcffe7db5fc58fed63bf39729830a36531d3db670f9832591ccd28cdc3131281042829082af4d06356d58fc1dd25c52d5fb29004762803b0d913431cd492c75f70074ed40253b907f5df2437b73139a0b3154f3dc2349eba99fdcc059c73f36d4a196d854ad8d02b38ff139127afbd0d52f0f0b25690f39e53f778768e255d13ee948b8132b58b82cf9eb8feefcc19d3cc9208841898164b65fa1365ef2420190549c332640dcc38faab8c4cd20fa207f28efbb966f5577128f254fb8964f1a60b4d7966a7b8676b7819f5da5a6934a8fe2d02c702e6296ff1f46e7a7a9c3e8cfcc3e0edfc788549cebf170a499e4beaa578536dc27ed1a03dc5a58bceb2850aa22c474ff614fb857161d3807650da6a412aa2473435eace241cbedda30705089b84e2aa30913fd26395854f21c48e86990b43defa984991e6499d891b0903fccf2bc08d1f7e89e3ac1279e1ef93c0d6f1e96d69bcc63e4f440b124a8413284413903583afcc789e492964b99e3241081329527d7b0e2cc7b78ef9e2e08dce814847ae1a0e11591d8e33b00feb8e40841584821cbdd16c8128c0b5756264923d14b61f2a12832b9347a5493ff0ba1ec2e15f18ad416dbd3ab06e7422a8ed9c3a5640647511d17678a39b4a2aeb862e577130d7a3720c19a0444c91ae0300245467cec7f85e8a56c790fcf1b6463721d7741b834340f8de73fb37fbef468522bd84ef5178120484c85fea622e8310bb9fffbb88d62338290bee4d26bed033d14acc15a580767a50ae527907ee1d14126d49b6f802e7bf2c7a8e7fbf0e0a6eb73de4661d633852c68b2fdfa958521f486ce721dceabc7fd894c1182032c1be742b88838bad0af20590d2420d85ea95d6297f7b3ae912f620ad7696476886d2abe04e15ab0be2f4705391bf93f87da3201bc58cb0a915bf2d6a417cbf17f1c76804f8f027eebe569116196167b88471ad557e3312cef060a03eaedc84586da9a8ac277e3388d4d9224f50c403ef3302c54848d9618a24d1a2829490664215beb282e7d78af6c0431325746324756ef813a3834b56f7a1265c9f6d7efb44f62c360e7f4a1825e9a1f8c1a4473425719b0c615a9362ca0267fc30066a65297d3c3d23b4973925c28c5b36882c62b819f4b52717f4293bdf1de55afe5c7147c85f924a4041add2ae6b6ac130c69eb2d28800befe2790c698a654eb97cab313a8d230a36229d43cfdb803846874829388599f1f8c60f8ea6a3ef1d3d70dc325390412453a15ac0e213370a1a256463d8431aeaa4203a37b6424a969c8fc55a0b166fa084d125b334ce5abbfda4b979531cbb9c794e5cb89c4756dbdef1803e5a6d39f6b5e73bba01d8c02029c67451593a78e2be4203e567df9b958cc987313b7120428341de00397f40109487d259f62f6cfdd4913efcea270dc380687ad235fa1ae2e30e5eaf1ea355fa79cbd3e0baa490deda0152dfc3f99258da2572f51c8ac737c02d1cc2a85564878a4062d7201fd522d9690a17a08f63afde073d5e06c1285dce3e48e9c6681e32cc36b31bdc0e840a6a411816223dc65aa38b435684e4aa1620948b7cb6e18ed7bab4e8cc3e18684ba1759fadbd9b377888a8290016a3b11ec052dfb95a8f6456db1fb40777d1147bbbedba5da9f0e9e24136e436ec20f5f8977cd9b69377141b1a3a9be84bf70ca0be6f80bdf7fc01e885da2dad29f102f82b1c6d7334c58955d7522aa65b0376b5813c4802e65954e578eff6a6b2bdc76e347c5bff11cafb6444f2506f1942dc17f5bd665151e879e251eb6e01b49ad8680f8018035be43d72d7cfc01ee0e4cc3febba10ed3705264f6285c21233804b666c8a2e8191f91c1670112fdcb1fba502e98483cc4848c7d2a71bc469969e0846e05192db4ff40aca1612aa90307c446afac8640b950319a963c5c26bb63b47f2970ebc7061da0de564574b6c35bf082cfcea2ff42faa021f40ef9d99cacdb1a40dd1327b9230eac409863422d80930fb0f5f721a573c6d468480fc65f69e94cbb8ab0a6d6f2a409f2d197a17e754135319f987b028d9410330f18452e78934ed92225c4f842d149d97ed27097d0aa4a527732c12ec199621153771feb70d2103ce9d0e4ad0af21a395dddb6e4cd7507e1d87a12dfb8e7afae5c2226f9b51e8237c8be9e5fe7d5aba193a439359fd61838a0f2e1950b18dd9965654352e4c6bba4e3083d2e52d580939b3cea351189a20d4cfc793780ffdf2a156bc20373d0145270322ea175b420fd4c061845960b34beb682d729fc530724efffb6b591d0290704b931fb60a1a2de3edd4faa867f0b0bd049640502c0eb4a4f9d787bff11b1925f7fa7d30b43972402799f1bf31e3489c0b8fc3fa2d0f5b465b88c946fb3caec234d8562848f41075e50a55cc18d441108d8017ddea6c6aa7aea64061134846c6e39a3670b4573894491de004a1321965a70957a0b71ce3e870c797a803f60e2218bd59489ae09081224ac35f7de06975430265da192925556634129a757d48e7a20ed1ca55ee463e8fdc7b122cea162200d950d829f5f02a6bb1adbb83d0401452cbbd0160a3d888975dbce12481198e73798629b376244b700f98e8375c0dc82205cc4da2ba95c393571d5f708ace8072062c1e3aa03aa2f046d49ab4e95bffbbef690022d0587290bbfb46ee9fadd501303237356618da47e8f2514dc2dfb41fdfc554d27d18a81a89a41424b8eca124e549a0eaf9946c480ffa310e66d18fb0227186728a83a02d0649534d6a513ef45386041375dff39e78d8a12f72cedea40b27cde48c2f38613c4ab92fabe8306956cc3e661a772e5e353739c4bc6ed08baa918503b07d86b98b10b3ece83a928804b0081fc381af54efd1ca17c984424ce287edb69c8c94a7aa1908ac0d879387be0f3b6e95e3099258a1d1a92279f014ccb950634bf16d577bcf700e859b55fd961a67ed9986454809ea6f4a26ba0273633168f046a2798111f6524d8c28ddff5e1b1faa95ac0449c2340e66ab3e186fe568efd3e3c20240dade0867dec8e9d614339f6cafb872a2bdfb444e665e43526651df55e3312690303a2af717fae543fd000a2589778bbbfa8b2a2ec88af679f7b7e60c997d654b6e70cb50f3c6f5cb8f9875a83b343238c642edaedd08efbb67d7e721f1ba2a680ffe0760006079905701a100ee53e298c7e4247bafa5defb90ddf9e0d011d7316351bd4a357286b9561f3b1a9d0f733ff9f23758ddfaeee19c97163234fb6502791e912eeb79cbae41db362e3adacadcc72bb0c7a2e1150116f94586860f6096822fb4c3e52990f7a6d2c160df0fcde19cea0f3c48ad8eba29fe4d41c38804fcaac8f7013178676fca562ad251029553340b5c0dd6f01a6ce2943d9f1f79488a5141af0d79e18b380ffdf95ec36b2eb097a514f1851cc75a91ce26393b11a4ffd199a1e647e646c9a103b0e5e62237e4c45f176c6ba59b458703950a20f334a5cd907f2e8aa0420439fe32335e998771bb3b752bc98d228025aecb2538a2a94fa5dec404af7603213dd33fea05b8408d52a4b8e9fc50758549b4d14cc618386009047a813326519b824d4b6e9f2513ef073c6b64908c2017a80e2817d702296d45764fb5102c15d8873d2700acb834e68b2cda5a3d00dff1bd49e62156412b07075fe1f190f249a6aae8084865e1d7b8a597e47e16e9d40c8d79761439c284a3067dc7e3cd4ca1daffd5afc166c19aa567c23560f2fe125855bf68c91853bb21072442f02f5df15e7172417a99ed872b85ff334997b782926172d32724f2aa797db600de997422a23cb794f0ccc30a001753a8fba09f0ce49ba7c3a08b5aeedf4c1761f4ad44c03faccd8f61b607bdf23fb695ee49a6db31fea0f801b4281a8e5828b59ee92856e51f5dfa1301bc22614e09353394c076abd2074ba0b25d741941767921eca315b86231ddfc9400c9a4e4b6774a1059d8df6a32a1c37dca593046e72bd277a095246c067ed2062b94bb15233b7edc1d301574a216748fb379f689e0c05a91de45a26f3588fc3d8e99d8953c76f0a6403d08240f4e2ad1f08c0189f2d0b7466fd136ca7b749d72e4fea4dfb1a56ae7326d351ee0c8a94d0a063703a886361cdcd186282622b1c66b7d780ff25349541728e12e0a664a0c2c165de0042928dbb807da0f066f729f05491abe93e1d1e204f90d4149a530feac35ac55a7d2faf2e5c810a379675e0aa2bbc4d1fa6da2b415850c8da01455f4d1f63c7a3c27c5920b7fe03b531214533ecff9e05906c6491e25bde93cca75405d4dfb346a56e46427b793574e3b4b314406a59dd8256579f3ec2a460504e0d8d11def2cc468263f14e216d098b562495c89ca50c2921e36411cf8c91dc093f29c8b41501ba1f25f33d851e2c26fc957853c13ca5dc99c4132af98a204c23ea7fb51e8eb8fb8f009d258a0a2f70b93b6b600195ce0b51c4099003ad23c5eda229dd11c928993b01a228961295626410c1ef212c45dd0fbf0522d700adeb601f1a454ee704f059584082aae2f05d78543cec67d6d04582ba1ae149b3705b21f29a4eeab3f6fa707123afd8985f7df2c1e86c7e21ad64c6eccd642d94e4126224b54f12349ed31c0d9d2d686e1374ddcd0cdfc4e81aa9d2c11d2820b4807cfb8333b0c8e31debab6499f773194a0beb3e587955517f969ce9b550b312794ac85fdf64c1268c33e513227109c45500b83c52bea9a156f5c464453f28f4b8c626456a3451bb1e0bf06897de69d2f1aa12b0ac3dfbc76e824e89cab15330d8e6c94bd1a469e0f909a112d3065f96055a241b444ba811f86ef91e52b561b17bb98209c25dad600c9072872f8f44c17a4c6b1aeb7ee7b373e075618695ef53b766c77c77b8418495e65fb86b7e3cfd3f59281516dd8d494d5f4bbb9fd6c3e60d46a8f5b532e0fbde39ab314aa3787792ba428c09962962e05bc7aa94f25f6940259ab09227e56f65806f42713622394c04d6537a36e9d0bf28aac9a298737e3b28d499c36efd68b13f3a1921ad9121311aa93932532ba58da09f2e80250dd54501713709839f8e7ee86120fc7b06738692b260bccc1c4634e36339a4a34b3099d80c8bc6d085687f304562334e569426faad87966313ba44730143dd186a9c89c01194688f67f4672c113c0eb03381a5b9135b519299c3b819c667c91c98088ab0f92578e45f3413a97d66599587c6fcb2f697a42c3e55f250705c2bd9dd648469e419a938b9f890393cd0847328c457075abecf69ad9d25eb67ce55ab22274924c18a1768240e672771e86be186b1e07e44f7bb7bb324cc3b89b825322497ea8bd0e5d34e0f720d8b34fb8201943c779d9582bbf18e3f30f7d126b77727713836024f0ad921aabf9bc1c76b9d6dae4863794482bc5980ce7e7baa6de2b015336572a8ee46de54dd88f2e02355466d729486c5611e95968f68494b445ccef4eba61ed823c7fab2053e4841a2671f32fa5418d1bfe1d10229e69d22563553d7fceabfc2012711a59f813390bb564b5140faad52d1250dc7bcabd8ca33143c05ff16bb631b67d3bf495919c6d09b953db9d1a916921e996084171de1ad19f2d2e01de1318fe4a93f65c03e6c896401f5aa5d31618117b1b6c8688dacd7236537588e07e4b4c2facaef7cbea6b6eb4561423f87a2efe8913c169f13b4dc1f6c3bce367280eb2d35b9605a8110cd27ab22d78b4a731f0256610f66b5d30d4290a83d31745221d0c93c1ca607a725e194f3c79335330bc74f79983ccb73c7b99c6a5c23360f32a7d31c94ab001fa074052ec50cb9df7fdc92760764010fe1c4d292732abb982f5728dd642503c53d18ed876a889b80f71fc8e888380c668d6fcc02543e53a05f8ad72decc47d585583a3e2b1d71b90763b7026452f10fe484448dd4a8c6142370f1a5dd9c8798bd7a5918917840ef22e33cae663129ced63f4a190ea558d0d2a0d2b3abb24ee79f1ba36e2388a928c06cd88bf9f134f72656cf370cd751b6088ad289be96e8d60693e77b710702b93c53c2920ca5f3ce8601542554257c901577bb7ebd1fc7b1ed334221b951820d36947a4633215513ac9a43b2004b6bb11517d4104444e5eae236eed0abfe5f3464385cce9e68a83ae6e01d6d7a0a8cb7df786d53645a731be0451b1497986c53054326452a922200d06062026ab85ef5095c90c4b2a15f5b2a6ba97865cbbc4bc1da92c65c28839144258d2e92a565a3c5550c739e799e774e3f10bdbfa4c2316e7feefd4d48f5e8678f4726ab050d4bde14170c9f0df8dd85f97c8c65c97bed8cfd7da7eaf4c4a0a4f9a033275f99bc53e19f7e873107a072cd35ca3bad9acc9e37785fcabec99a4f0c30e68820fd395bf932dddc1ae900acfbf7bae5a5ba644e2c28945fe7b3f5ba49bd6fb881e3f97ce7a1f7d2dddc02cbdb3919818ed0530ec6623cac25f17f0c28be2628aa8910449016790420aca7aef739f06a78fe4ece42bd76c64090f73433ff5cd2c8884a48cce5e6493e1c4b4101455b1161d887a1b55c105abcbe2a031108568b974010fbb5fc7d569c308f745ed2911c384ebee4faebafd93c0e8548117243280fa0dc4fbfc423b846e402d294aecb4cb4f0ccdb800bbf3b89500352a6555561d30a09c4d490dc356622c26fd04d0261a9d023f941f72b7b397cf587d50717e47d084ac8600d66cd8e70111826e82e8b2f0643b485f45bec4528871e03242d79ffd76ce8f06c86e1863ca3ed80695788294a04b3c9b96ce1a359a240d902b122fc4c5f45804deb2f66c334fff5379c57a76ba4560b15c7fe736a0bded404a08d858409094ef272984b0126098b9eeb928900113bf1a092cc7fcd4b0eedef006c7e551cc88d16ad03182b2fb44903cb9b75ee91084c9ceb77deeebc93b9369e12dfe5e594b152ad9b56947f761ee18037963a4422e290dde267986e8bd287670d81b0422a11c92702aedd020d3d8ef7431288eef50e85b818eb11783e222d663508edc2ec67ad097a92be1647dbc77653a42f69d520726370a865e01944e552f7b8f02f38f04e38652b8448363630e6f7bbd2390d97e5fcf8644b7260b26a44ee4c5dcb6b099414babe6b0ae6c82b06b42109068591dbeda028fb8205bdb5f8dbb5a16dc48b2bb153e61564d1dfaaf561101fb848064fb529fb188975ec27ae851e9a0d095cbd543cc56ed07a705ccde3d587491560f9baa5c4ce152a2f4795e80a5431a4dcb4c1bb56598640b2df6856381e2e82f48d817e1a67c2c346e213ee19af94f91afca6ae3d8b6345a680b0c9f0a0f325c96fb24bd2a102c6e4b64bd3cb874675629164f9222de5121317e24beaa4ab441ac667bf2c8ff5a72be55bf96aaf4f12b87fcd0cc9f4069fcccc4a459195f6536e160d1d75621a75eddfc488ac96b548f204cecc2361025ac3a5a2b0a04c479d0b7748cdb8de61136ab80e83cc85dee25a108aa7541d73123c47e4590a6bc5050ff141044bec60b2a67201e509bd1d931530550b2cb9728fd99f8fa085233bf959b09ff013faa1104155b03bd5b2c7de5977bc4472c927f6be9ede1cf38dfe64b83e1195f180a7fdf8b242c6a4ce311094f8a6063428cee2dbf500bf88e09fd33610cba48e6dbb718c3d82db8f010cc4c08b75ece2ffd8792759c1ada0998e35b4fd3a240997d1fd3483a370c14e6879f9ea84b22b9debc4fa34103b83d37011363897887bbe37adcc177725d210bf30c9eb7eb532d01bfb334a7014fc6e341c15335708084dfc16738dfdacb4f86fa84053630f80887dbbd6fa485df016e742cc7233b07e55827dd6d04473a1c96cfe4514a4d6a0156f064cf7129d336c2f4bb4680b20cbd63d615664cdc9af24ffefd9c418d30b1c06180280d957eb42346ce1f077d97f90bcec9b908944c30cd68b2f10823cc864dca274f67e2a403e474e4247bb570268a3f6b3858c93521df6aa770f8940fe77c29dbaac46cfba9b4d40f578ebd1e41ab7f72401bcf5316e09dbf6073e86cbe5c5084a0589eaec7f08c1e92af7f43364d130fe1cbbda4309a5a78764e425a870d64632f45390f5d0bc4dce921bff462c64e75e2bb452b06804d4156676117c9294f517aca6e16bc792232d2c6a717182ff7b406623bc30b1c97de3dae8e46d03e709bb4898d21d728bbcae6ccdc99e9cd54788b3019596e238a506cc831a4842ceae06d0d84bb784961ca3b9d54283491e25a57c360a6c9cde3986f9cd5e8c01f42b67f2d2fc2bd9fc49fa0ff9c51d31701f9b66fd84bae6b240066c59e4b8cbee00e0e7b4159a42af3df301dc35761a6ca14c9ac62d438612f8fa1cd42544fdeda111f9ab791d3660bb281533603463a740e876abd9690a747e471902e1080023b4e1b20876d616cf9d107223d9ccae364fb0cbb91e4f8bfe3a891820477b437c22447adb0012a34eb1e2d817efde6c51bed6d6a98da2f5d38db6c90ecdd31dbafa892ab9bbde04a57837ffc926d4fad2afae8917d8e560c5bc7ccfe48ac450605ccf8485fd9b1ce1e76c6d08465e58d94d9dbfcddd39c7888ac80a717572641bde958a5a2c99f501b0a9c3ecad7150748e5a51229eac1751671f71d90fce7d98d87ae3abd7b8d11cbc23c75a60053929fa5b6e1f0f4af07227703f31ea0f72e7e4240cab99f0a0d6cec76ea0551a7cfb2f1d75b540a678f8ad9a36e7d097cab474ccc66115d606c5f7f16f6755f4c4158e490efff54e31d1be593b6e67fb705c5171c00af59feff7e87006e629b5f832ebc5979272fa380d20a2c060413f8a179c0a2bed72f77f12a14f1dd9eec94c7a75a442666e7faeecec37b039ee12a2bfba918ab14884c971037280c1a646c08e2b67f82fef4ccdc05633ad8ed5591f3fa3a2b9095100adaa16acf0a57ffbc4a3aa6ebc7ca128ebb0b6811a2853068f07c42746a9814b9cece39150a47b70b73410941fda6c4a9fc62f04768611fcc7b92a4ae63a34c22e3057495c9ff79be43ca897b81d00dcd8db30644529b43e59b1610791485846545db3b3a9be69466db5fc9c70530ad2c8fd50c7dd0bd3e11d03ec2506fd2b15f9cfe5c653c9d33f66e8b8d898310f687c799c79029232d9574e885e12e51595d45215ccc32b753a9aa05d9b6acf373d2c10b1b02b21b17063d4c3222ad2422d9d82d66cb11e1d4ae6c693e035e6f374693acda0e3a2ea18d6c260522c36c4cabdb4474b8d96f7fabb10d4f56758057f8dcb3f3c5c36db15a2c27345e519988b666506dc2b7ea87f3335078ca699716426db8c22702fa68fda40dc1426c5efb383878fd6b2432889a9d180ed60ee0d2a31b4e1a1ca20cab0440f298d1e1bf69b2a8e232a33c56a6c9d7bb6e71e72c1098f741ddd50114417fad011598224975ba6cc1783885a81772e651082e20c31e91861951ba177b4bc9e4d9574454ebc7d54a95217a55f59d2c22ac31a662a0c313d78eb60833d76f0754c9d2b171d64442851f09ef227b76578a1b2a8c3efdfcd825d157410b4c1e607346fcb89e62960ba44880be0b1104a7b48659a5fbbfa6d606e5b05ac9083bd84cbc66bde74c992f96ada2bb11bc3ae642e1f09a15841c30513bad5394484cb6792e021f4b89aac60a24ba6125106738b83ce6cb64754c12c9e4ace58a49b2d1641eb00b7d61d2e719cd8d8483edbe5b16f999e13a3878c90cd4437c041716bcfc1718beb4812d3b8c7fe1182fa571a3013ffe7402f71893eebcc5b5aa6fffc49dc47bc1ae786f51e75baabd7046efc443a98eea84288299cfb460211008b6b473c46f2ee8058a1d2c741254147a0e2eadeba92d1888074484a01e066ebade0f3196eeb405016a2449d60d9b43a862c839bc375552051c57cd64ec08b776cee38e3842338d9d8e7279488b21292ab006ec250b828ea4da842367b4fe33342c5be9b11a0b3361344a3154133984596330331cbd2eb25108c227deb1c2bd62ac2f3e541282ebea756842c114b51195f4fae48316afa20b80a7af0b1e666ad3e50e0aa87d3bb17e8a4cba469f9a1af913accfeb4cd8b0bb5c934b89e4a7260cff9b261dc88956e4b85e10b1dfa1b4d471768b7eeb5c9af05fa5e1541089e7fa00207a4c529576f686aca110408d1065c10caff5434cbe02b4481852370cf034c3ae595861db033d798f70dd1be16b4f0948d3e653b4567caf8424beefaf01be0ba278fe6de2a4c9d1f402e867d6ce667370846d124fe90f5237d6549a83dbbd40bc13782a81336a414ba64f79c92923c2f0d77dda41668a885de4e53683db1786c5d7dec5e03291f99924d70ede3ae319974fc2ca3959b9a16243e43a88e98217f853b74b64b5e599e92f65352457092bb4a6498dfcb2682717cff6ee87811d4e538fb09690fbac35e274e52dae266175f137da9da536a669fd7df49340bc0b1b9e879a90e7250a4aaaf5f13c68329546313448144cd2946173ab46e5d0259e4a073125da0842bc4e50e8dfe47ff167845952c926d18a0034d3755dd9189878fa14bf2e24dadc883f2929bf9e9e9e411b4b28400cc0c4f71f4dfe95a2fa1e30a0eb4184f8a7b211f76e33b585afee9a3b7bbf3d39302ccfae6c75887ff974c6da9a136fb4f0a5162cf113aa85c8062d199197113be47ff16ddf9efbd2b6094537b7e45a0ebf7e350b5b65d2f3fed1985944574be21c44f5eda5d669a61cf1218da8fa2534e591f1ff666e2df10469b62882cfb4d80ff36b32bb0e78c1f37a2b03d5334198d792239460eedc7bdb3c5bb129d4c3892ae58dcc11ab5823b2c8f95bc8abb446045044bc38f79bf67d29657db519b839fba3a73dcaaa28fdb55133086671b10cc8e24a3215af4c6a8b39d15c69cb5e2d5b0c92d6c7199b3c160cb70b9fe454ccddee645c98c4cad5def1d49efe1faf3a5117f6e97e0664a0de4c9e94e43f05748bf7251c8c107c9bca8cc9b96451d6c3f971d229dc33900c96e385b8c80761e953718f2369b3ad74d9ef60f8c83cd63ab001c611f37510a2f4d45e110d1e3fe9f4dd9e2d4a54a4ccb5bcd2944e8e85d917a34164c2d3a78ac4cdefc5d6dcc23a4098b1087b9879434f6ba7d2e2a4a846f1934c79a059bb57c69f2324b70c7dd1f17e101b7aca8e755c342e36fc3076327307faba016e56d4ac32a271186f03cc05fdcaf41ae361e11af9a5c319c0f0014f42fd9d16bd481bfd890353d2bff5991f0bf6e4e342937cf233203951d234479b4d05421d5343c9a4531105e9d3095622f1da216a4a4e34ed0c9689b18b087feeb58a2d388ecafc1ac66f8b4ccd7e0097fc76d7f2e147b4a06d9a0335bbfe3f2d7b68c7f4f8dafb7700d6f22d7505326eacddb190f73a9588d81aadb6aafba7b7921cca06aa309925d2fc75d22222b25760ca227b214954c7427a3b876226610c5a530070375c9e23b984738234fe41646141585c4e054af37ce988368dc18b1f44ac1f44fabb9d36caee9ee3ec03d5bc12f1a4efccd31202b80de2f9717a60c0eaa79c0261d1a03aa4a89457c91be618a8e3438ccb8de2c76ad7deb1c1683b4e97d2737cf6dfa86274d919c4650632580da072d657de43d6da7d5379b783393cd3f2e316707e7e6469af35b66f7ddce213f39d7e32172b2c78a306b4a39498ba47535c60833b16bd5a54c9d901b9a7e8ffe3ca93536aa866e48eea747ca9937f9bf84c592b6b69a02bc1735a2fa48df58e2f9bb41d10ed8e998dbc089c98f7ef4961e2b623f9af2ec0d9e41a58ff91460413b3aa8308f132cd6d7b68d0e79ce256db474716bb30c0277d13c54724bbf7a49af3934932614ed2d8aa8af1ddbca3ee8380e28e90eda168e44442c2d23b26138e4331531be20e2186697ec6a4ef356db248d572484e1c2517c165a08527fc83878b8e9706bfb84eb42e4a72f8fa29d9020fcd5f8f31a3b9974f9d479f6adcf042e4b01a967397f240682254b228ec28b6530a16dd69550aaa64b5dff84638dbb5d67b7aae8574675428a1653f491ab138a410e1df87fa2ce308240a60f8cdd99d13045a42c4ac2148b732119f044b766b15413d99eb4e57178c30e53358a6b29c963adcf1c4cd7eb9712236fa49a78b376a84460d7579da4fa4814648e5cee310fa8a5413469767ef4d0da1c7ec6f528fdf776deb904f291e374a78361a5a156c088d3043b3b13296c295a40435e8a151d188594e29b799b9799f7b2924755a82dcd3f938e1ec256bc3cda1583aa32d6d17a57f0428c9993e55043a6087d7c08ab5e44f218ff8f2155805f0fa51d9ca5a52daaee528f05325e8b113a9002d61a717ee61ef6f85a34b5ed153fccd1f03e22f7bf54b2844d191459307c30e411afebece84eb8abe4413a4b708913aef055de6a073df2231acbe8336b88b974f9e42765dc49a4719c3393689e856d439f339571220ee1c36822a8a7976f0b52ac22185561e2314e6e789fa8a79dcdef2223914551b639ead02286c93e70a8ffb02303bd8ee72691127440636eca8bf8a670cdf88ea509318b2be97020b8b35d250fdfa67bc290538cde7fd5d406f2c74c50511f617eb3032d251d1175c4f02a395a61572eae21aaafc48be858eab353d071f08689d4a73c89460852a1f95793cd37f31ed8e9ee99f1babc102632748f533b89c129256fcf39d695f2096cab31b42578f7066886174626ceb32289bee2fed8e3471cf9eb1275bd959cfa1b7929ea100fd7c8bbfa5c5248db81f37b1460e2c8fe8e7e95df31b94aa03ab5cbb53725b8fc43d793a19fb255b797a93cbab67fb53a01528ea1db7c22b364156e0e9cc7a9f7c65c5d444df75bb9edb1e2081f66400084ed5fb44ecc928b4c9636abcf2b76195c8ad78847be2c52a6207b24358e54d9cadfe5e95f48c2dddf25a2f3822bb8083844adef0de3dd85549995e7376e22aa83379188e38e2649900e155f94e3b0b43f8d61d7cd376b18cb4cbba7461a2bd4f8d49eff8b7bec7e1bf3bc1df6419ba7c10289c62a11f89f7ef7e592a529544376586ee2e568c2e1f0882eb5933b2df8683328aabf936d298096d61556c5e7151df069f311905278cd84d9895b92152c50bc6d8223f2c00f2942de0c1519b94d728fe2d61773e44deb1f62828ac3666cb61b47caa3f63fe83ed68c7b491a3fa4b0a05cc64e18be9f88954f3ac5bf012f3057da8627b39d2ff0227dd013a2e7ca247fa17616d13365d851900f0ff1f85ad995fea3cc428e2911992aff7c20d5802fbb1243e4dfc0ba26180c957dcda125cbe41b7ee95bb3d4c067f78e50e7d70702e782b11c62e8f0799f51d88cb0744923f3be69fb7f9ad44cb4527144fab7f1861643912a8e48a6c067652366d82a3991b76bf83bb14783c227b2710e305f4cd50a2b318aa88192c95ae280564a1b1104a90a97e67ea69d463f5f2145c924015776545998319ebab9220622d231928bb6017f60b31385b19e05880c13f14d276d363eb87b8a8c9a48356ffef9858b99c05ae61cb216579884c0ba63e9c5439c80ebec227ad02f1914eb31d72e14982ca723378b4ba3e1a6674a6198da5a7a969f149358fb9086b0ba15836ee6bdffdcf89c694aea44248a16e2a6d004f0d2b0d07390d6471f0dc0ce7c0dac36cbdbbf80333bcfa6ce40ffcb0714f9955ffa120e5d5e3eb09f625fa4d4e811eaf60631160ec4f61bcd23cbbaeaa6a3ee2e9850750ca74e45160076b59dfd674011d250a0cd4caaa2408d512f08074f9ae182f52330213ba27feab2f07f94ab44d5a195c736e5cff08bafa0b89df8b1e3a62152e13349ee8742578489f5249da7908bd6f96873054cc2f137cb55e821f7befccbc7283e030e34556cb10a84e0958476609ed44c3e718e24bf1a4c10f612a166872f36eab2d9d6df7f99670876e05ee684d142895defb7dd876ea2a230321601015c79a4162100b7f2de159b1c8da62802cd3f122820d871ec3d9105db26d9aef166cd084f8a51234a628e5831d590832a189f4f9ffb49dc7e50ab5b4c12e2287a9acff016cbd6b85c706846295726383a0eba1eb76b069bd1b3f56b0dafd1b24f9156cbdb1dfa9ce18952d94a1644b79f833707fac44be1ff48cc2d25609d4c16250fb530aafe5d9c4948656b0021f328bf5557d0848cb45a6bfd360fa4c8e4ffc0a97d0c1ebc2a08c63f0aff27402ed01a23437dbf08d0a9ece82f79606d1d30170ebfec6bba1def8b7852af355840a363ca158ed62b90eb68f56088ae9dca8bc5144a2d1158360a6631ac9489025f53bbcb43deb0cd8b425228f73d66af8c2726ce52fe0633a60cc200ea914ee93aefd96e21f1a242ae5402a8039912c5bc70ee7e5aaf2e95a27a787e4a2a343112166473eddec26e914ff9db7efbcc8c53b376a17c74b1b4287e8a6cd48402260135923fd2e6bb90404f87f06047ec27512914457068925e4eac13da0de4bb416520b3bd41ee6638ec79034aa40add5828d640b68f0ee3dd2873b9504e1f73613d2ea70a3baab2e1c2419a3e7f08387ae1886c3092aa2b21b266ebf0531eb60fbb74abe215ce627b01183bd85f0b13401c391c2bf57fd3a41eb2fd83a1feb25cc2e124c531ddc80522672254385c656af1db6d43696fbc53e80c26e3b5e041b0f238b959274895326a75dadb05d80059f26bdb51fa6e4560b69560d841525a1c403ba107108cb288c96df9219c7c57d08b38c337e4f0cb5f098b3ac24c3cfa38f3e5554a85ef2c174149d3c3754a98632e63c937c1980d766ea406e10c0c98312a94b1c1fc8e2248437b067772f37f9bd22e6f88098958c243bc4922b93321a212ee64ea8281ca250849e4ed864c9b2c8d0b629fce9f392c65f9c310a1aa14b14c7cf4ca9218c1a675ba5e72637aaf0b2d38aa380ae066560ae21695ab4db4dca7535245929f565b9ec82d1e0172feabcfafc8dec131922ce6693f685df9f8641e8625b4e81470932663b6b013353073719171eae1c20271cf938ec13e20c51699691b86d9f58e0ff1268a79cf42d775722257adf1b0eb50c4057a39a1bc1a88061d83a40e22c15d6f11cb9fec7da03024486034a1b6ba15b30141e3507dd39201b2134a7bf647a272fc68a0ad96f2db0ae1e18a2d042ad19ab5dac72b1c2c868359b971e91edeec0d13d3d876f3603a7a1a1717d2465ca1eb8cfa85e60a769adf6421de8b7ffdbf626a14d921a09d9524a2965d009a509520ad93def943dd71e0882a0d65a6bbded40d3bcfc89d92b90ebd3699abe3e3dcfb967faf9ebe305a540e904f3fa642674f85673ad69dbb681e430b969343976206ff22bee1e6f1a77e632d332bd5141d295966373ec52bf366e207fef0deb0cee83e61989e659cbb4949bb29b667c46e531ddc9bded24cab8163f8a52c1f3d3c9b5b8c3d73a74ed5b287e8e0ac5936f348728803a4e62e832e23eb974d4490c43d76ec3d0bba12019d7e1c9b7fe30f4ed247e283f89de5882f92897eff9f703358e4087e728808e9af9134fa77103f9cf519e87e23e859ecb8c326ed0dec87938fee01c35fee83cf4930824f44620e18c6be9346137138e5bf54d204f20e74de7d0b5a7f3e7a1a753a9544a67d75a6bdf2cd7d2261f34cdfc937fe38fec9a8eecdae7d3bffc239f50a810c887a006b5ae919189f1d041afe1624ece394a3cc1fc93d3cae938b936524e05a7e1888356fad93b79aec5d04111b5554e07e828975c0eeda1e3a02891723a70d413e7d8e789d321e92ac717d255e794d3a1c7cee5f823732390f0b3febd0212cea0b39d2d4875d326626e6934ff813d730d3be6990759813ae9016b9aa73897f11855064157a56a6c4efa4f40f35d7670dca07fdfe78d1018f21de8de68e98ae376ce3908c63c364890cf9e393f819c83e30fceb7f187ca411efba06b6efc5431aa144a2626068542a1322a8761186e3d68710779dd759ed7781f0de73a679db36bcfae47ce6d52b41bc237acaef017f359b1c4a671637d1dbb3878ee0ecec396728d46c6b3ab66fce4f9b77922ca3b517b9ecf0c05c58cc0ff04338ffadcf3bc13b77fd7751d384260c8a3b47bde0e5d3a04643c971c05bccf3b947fee897b85f71ce326d0b9c658eb301c692030e443d7e3e6b95f09863c389a20c687a38c155efbe7de0e3d19477932ee8d3f641c1c7fccb8a7c5fd8d3cf765c66ec6a59f54de0c4dcaaf7be3ce2a552a95ea7ceb52dde77997f2beaedbb66debbaaeebba8d07cfe77bdadb3c70dc431ef450dcfe2bfc8742e971c78001e3c58b9a1a1a9a9919952a959291898941a1c2f07402c1efd3daf3ba8ee372de369349d36464c68dbf9338a8ecc68cd3dc90f114cdb8b1ef5ce533a29438aa7163af3d46a73cc6654429716246d4d54179d78dfbe4df4a8f3b7be8386a284aff44cc4f9e67073d0f7b8e3d6f721018e2799f57edfab5c7f9f6bf2b203d8f55cd7008be65a66dcbdcc7e6d935a52cbdf161e3c3a61621896a4bf98c679a6e03c518f7c42fc43994f12fc63fc738153a075540f9c941f7c46df39ef6b4b7fdf5481333ea408d274fbb8cb8419df2934eedce75e75e8c77aec5ede3b563dc043cfff0d6af350dcabb71d3a87072b0f36fdc34a851c7e973f07dfce7da7154ad53dec58c29d7e38f949fc61fe107c4e653361fba749a1c72db4ccedbb67dbe7ddbf76ddfa752a954dff77ddf97e434ee217f7250dc360f7a276e1fdf7de3f6ffdc7394e8efd5d4c4ec548c28e90a35eed47f28afc6f31acf43d584de390a356efcdeb8b383de899887e20662f3dab39fc0efb3f90ffc3e1b2036d2c8878d8a7a2d1421215484846bfbb66726722a0479eb9a89de5caf9b6e90bf8e597107e1b13c18c9d97620024588c78ea4e8c9d12974199799f11847a938cfa2375d1bbdf6d4d093202f7af2b5a8c957afb1a1716fdcb69186a399f16d860bb205d982040932c3f1d89919dfc61f291ecb637f669474259372e92a544a268c41a1c2300cc3d3e974da76c0b8088a5211942618638ca7e3a2273f8f75909d170ebe1025bdc1186bade98ac74aabf43c5545331746187aead333a5cf7c9b40f5263b493992485289b23621274d2e91132d4c4445e8e0a4ca094990105111231be52a705959d065a970a077f693219142571915b12a18c5b0894364d4049bb4ca2bc9a8ca7c49a13a34a9ee5c23bb0101f1c87eea4de6b2deecfbb3e3fad49b5ae3b124bacab4d05576a2c18f5d12a70280e8881fa3a417c68cf0a44075c75a6cc906bdb1d7cf679efdd49d4c281b027a1590de58d2675aa88e12d5f142756a04e836b7fd5befbd53aa3a1cf47ef5fbd5b1277495391631232d0bd2b420ba839132a16c4966944929924dc988b227d92b3bca903e730d576ca989cba8c787ea6cacca67d85211235f45c79a68a28e9f97d113aa435fd851150c09d5d9f3070ba23ad3312554870a6143445407cbbc479c556481aee6880109a1ab0c07bd31a08d017de6d812f6c5450468ab220f0b42e80aa31836512f23fc60934a2f7269baa6111e16844cd7ec99465ad03428bcb08ebde82ad3b21f4dcc8ed0d5005c474cb92c1574cea960f3ecdd987123ae37d825c763f33cca7a83b1246ca9de98b0c9a45da026464c1ac65ed90fb6842561be89d7b157f6931dc95c58128f1e3e560041906d9c3ff5467583ded8d2679e05511d3a3321b025aa23a6ea4de656f47a93f9b66f1e131f533df63de65ade9853ca1b2b62bf229ef152e2e03983ae329f35c368306cfed09d9ad7045242759a18f1e4b32ce833dff3497d31f99db32650644d84f83d977c96b965edf9fa6cd22c88eaec2c4475b61ea23a3b454475f68c574275a8952a59d014bfb3259f51f99d35791d3faf19a9ba63af539b6cfc9129d94021ad0683b636acb5768ade36260c12a7ba9704cd63e74860c78441de905f25141263f7d52a27c7860db04b0b303521999e989e98a4509d6d8ac2f4323da1e14930571227c7a0b7a989a989c9a89a906c0a2622da5ec90195a34b1c9fd4baee94d5b2c05acc5a8b611886d5ba5959693725cea4f44da03a954e2b6d7767b5964e2b6ba594d25bc52765b35aa5c0e3cbaf889ebce49144124275884cd797ba5aad56abd5e7ac740eda7958b03e2fd8a40d0eba7a92048220427a8c1c1d499aae59c4e70768851df83872513b972475d2416ff9daf1001e394439ab194256499d73cefaee33a02e18acb536e730aca951b5800a012f01286733d2d9a91d1230ca52e8718f5a3d325dd95ab724c261c20372ea8e7b8ea76aac1619d8c8b22c8301b33b26a99152f5041b366ca4f0d4c808cb9be9d20410a434c636b02400f90c7903f3b9e1041bd65aebda0696e2868aafbe0b20b97cf59d8097be64a38a7247038f8d3d94b0e1265c4ba7cb1b5806ba526836509bfa744e3a634529a5f4ce2a6b67dfba58a00587192ba9f1ec42e99c73ca8904ac691a1675fde2e32f7376a43dc14604ea636e820f9bc752aeb0c94da03a266d075d5509d095ddb68b84430d66ac569ef39207d5c97245b5bca1beaa3b3936ea0e0e79433d052c57d4ca155d51973bc8e7b76fa7b5ae4b83136c64d6a4695aae42204f330f0ba20942724c262e366923310b8b9255abb5d6b2b028e50dc5b5ca363d25e7a473ce2971649d6312cce96301caa93bd9f8809ca31e52ce26b3c891d297d9e3f3809c146413fd25034af496475fab6ce2041fd6ab6740de9059d20927e000ba2d2ce140031b367048a0c7076aad15bb76c68a26c9bc86aeac6b063760251c7618a0ac3b0a59014b2a5118b61b17d000e48535bd0861c50705fa52290c4b32d185ea6707e10f9286c72864d6a083a52456d554a4e121c412a1058b8e2e6861a4c4a2a225420b16f672185c77a8cbf90c000214221615b30d58db9452ce793b2a4ad6e649c9858b13412c19a68b93239674fc0288f52208f939a86ea0a51178b003176b6382255d4f05d04fde90bf0022445992963339e88debb44552de8bdd8bdd6badb5b5d6fa025d69a5b2de8bdd8bdd6badb553d7d49cb486e9730b4f1121256ab556ea8371da4ae99c736a9a86691aa6697648ee9bbf2f819037d4dd53292d912e109748ee5c9f31758190f7f574ab3a98ae7a32eb734b2429b1ccf7070312c6946595ea0ec63e976572c98da09564731e95b58d4256c0326ded2606b5de20596f24ce90c4e99138d39176c86cb607f4544aead769b64eedeb2b2aa1b7e9e7a9f7236fcca7de19c91b280b488cb905e628eb8d072483deac95948ea39e8eedb5f6fa549dc925e8777eaa2bd046a9e7b5acb79a73b26049db6b6dc54a48547f9561eaadd5abbb10ebd8cc4197242e960a56e076bc4755bcaf7a33afd2266df645fa2999dce0e825931a9048a4974c6ac8e2ab8fbc518924ceb40065d5a17a73451d9a2c9317305a3c71b1a692cbc59ace4257841247f8b8589312b97ceace64cd1a458a124d5cace913a8eec8974ba8ee50304b7c2850dda14334e8a75317ed015f345471b1a6d31fa5ba435fafa524a43d917efa7c51a5e93348680e81a0eed4a17a337de8a7efa83b0a801204116b7a8f15e850dd91acea53ab0b071da2e3d0cfea332f9dd6894d935896655794d82b429f15e033aa83cfb20cbbb68e9c11614d12531ef5b2c88a2c7e7b5914854ab631d0a0a5db97455620a568e0b04939e7a42798206badf601ad14ac24ba377c41d311491005a22e4ae4cb14a4a3a12542cc9fe9a3e405c82b85d58c973652ab9aa66974caaedb439e8687c7cadcf47b7e926c14820216a63612092d477be9c55edada8b615c154d1d47a522b6bdf4ce77d690a9505b293396849663c65032474563594b851bd7c1a0a9d3d496e5725468e938aabc37094dc78c33ec6693b5f75e2ccb328cb1a699a4dcb64dd3c7ba3d24f390a08978aceb70868da7859eae43738a5269a2b362ccb5b4b15a2c4547b082a7e2083a18683aeebc6d39e78e6b696f6638c3b4abd94ab529730e0c271b95940439eae359637a0eed83362aeb9ce39c27983e8ffc44e1b6ac387fb39c0af8b1c499bedd39ef9d755239e7b45c485759bcb439b69726f065911555685e166d31657a8a7afd8dffda223d670f18e69bac93566f51110475e7e54d752394a27c7527be7a120e89b3445792fef4c8edd34183125dc9fb534780bafe8e29d055c54157b57a38032d3d7c4133d15b123d209b18765dfc61d21c0526ffb075f99a633478dc6192afa5aeb811693bee9c975a0f1be6b38e1986f9dd7457bf7bc84f89239752580c705aba3965596525c68c031cc344038c5a5552f48c996901a354554ca7a05a6bdd7288033b735619516bad5a145b2a496f7cda426ffc523c69d11b534a69aa6bcffd666704682230ffb5975c8efa33dfa037bef5af4dcef2a42b39c502fc86c5df4c798adffd2de72992e03ecfe5aead95e3e8949ac4913e459515bdf1b46e7aedbea659939e5aba9cc233471a1d738e33ff04927fc80ff9cdee213fc53d7f5b1a1ddbbec42f87fc46816420297bb1cf24eef690c7bfc9bc99344efc15cc21956635187ffc69abd8abc5c5a8c0b224329c6f80b1bf580a304b2b96829a61185eb2415a6953f34e3be78c6b0a0c9b13e3ba239d91376be05cfbc494c4d9e2deccb70c5bc11a169a098b698bb2e539f311b9719ca6ab1ae8c95d18ac2692bc9942ee60c7630dbda14e7d53193c6dc1ec625e31b3b8663cc5274cd512de9c613c4d4567190e5ef781368073ce224ed9f4422e261f292bb615c35c5e9772d2ceb230da738be0f1ba7eb474cab2220932bfd755a7ac619d27755d9ebaaea792d321fef50db33e368cb53ee4a5675267a26559318fa92e5a025580b2b2f1d680b2a4787de8354257f787ded02b94aa7f7d240ef5182bb4f47d7de8bcf7de7baf143b59fd3a350129d13c03d229764005713a66e744612ed90d0b52056258d3556f5257ec3535f19a4be82a8b8b6929ac6978d63855e1e2f04bc79ac9a4e1e9a2abfcca389c69a62de7cda4e16c03c6febebdf5eff53a3191fe1ef2d838a7963489d09b544aa7a6e6a3672a55ef356dd9464e01523718d771cb2464117e3760a78600388cb3d65b6aba9056ae256cb962418a81a492a2eedc7befc6753925699eca2912e706aee776704a4bc5903b9a6be374f9aef15b001ac62e5511f4748feb3653a7751dce3aec76b6ab741e80126134c689ad537afaf3f6c81b994f9fd3ed90bc81f9dc9ccad5c552f0d5a5acb4c3e394382cba82c9b5b6b846b5aa40b270d8c206fdd6963425baf24257d42d87c28bc7c60e48e250cf3268e95b53a212e77a95b49bd48e5213290b8b24c81cf31d754753d2344dbc92043cf8a9ac719d27f5d735a5baa3897f3525d55fbf9ad2531f39f055c7f8d5b79b8d53aec05a6ba5d855db29654432ce321e2d67d96af3cde4d84d230f088064d3a44c7ca06926d3096280200f908c310f0f900c2403c92653e8034dd3344dd3348de7f5a9cb9ac642660187523b4fcee982b0ed4f4e856c9c190bd3083d00b3a7fc3413137a2a7cce39e7dfc88d3226e798cfb9cebd6fe48408792fe71ce69953089e4e200882600f54d0c1193976312cc41493580f20a12b0d81b1a66d9ef213e7339f7bfe7ddc09623cea1377f85df77dde851e236ef0db1ce53868b8390eea85de07240c3790d7eef90637d4e6e1f86373d4f883730f480824fccd674e9c2aa74ea79c73ce323232324300f9aff3aeeb68504e0324c4210e3bfc9ceff0c3907362a9102bb351089d4f78207352b90af49487339b77be8928d7a2d7753e8373e88d7b04f64ff0e263dcdb50bea146080cf998f1f3cf396fc579061db5891ce89b73a3ee36c72030e47968578221ff8d268821a4f36d7f1ce8a851c60dd6b9f107e8a8f1c78c778e837e220f4869ba195537ee54189e4ea753b775a7adeb646464643ccff33c8f07dd98b5cea7779da6699a46396e48e79a87528f1b377dce43efc48dbfd3319fa3be5d82ca39e8a74efba773083ee8dc0f1efae0788249afa6719c77e2e75aac9dde3cd4f344fab21bad8ecda78f9ad45808cfe4ac004203e2f61419f1e32b5e15ae8f36836f11baba5785ebd81d3790eb6302a4dfd2815c1f1aaecf0ec098ad729487d93dcf599c91913657c7d2e619d79ce7397b97fd1bbbcf67c4bdb974957359fc543bcfb8e739d370ee8d9b4685cd3f1a6ed4a1fd63e13d979f02a5d79e3795cf7c2affc61f2ae7c61f32ee01d1aa6e9471e9a95026e6840a43d3c94d27d329a04b100487f8008428456c010296268c005d08a28a0c4dbce0aa01f865f9a8f86660f96d0212a8fc9784ff1c272108ff4d09faef8918fefb3ce70926fef314b8d667a3a6a6a6c62512a0f81acfc1b56a62b8dce1c7a8f91831621c81f9184e50f1318e7ef0311c05ae15a3c5b5c01755b4fc0b7f00d77a81639e668ba7f99c8606741a1a251a902411e6a554440e2c1a28aa7c0982cf172286e8a97284a759c0eff0c101fcae79700aa30741700a253c5845c983ee00ae05faf77ddfe72caef53dd7a2c182e5b3e79cdd6606d5a9f1ec39493a7c8ec93d7ce41739e79c693c671839e79c6378ce9e73ce39e79cb4c5bbbbe3e05a1e23468c1831bc015c2b068c241f3c0c6700d7828169686868687c015c8bc671fe4516ffc24fe05a2fbeeffbbeeffbbecf15c0b5be1a2c337c8ddfe05a35d82e81586278d013c0b5401b5c2b5fd981166fdd6de4151fbca559fc962fd0adfb90415ef6337204c9db2b54bc3dd917fc9e465487c6adcf27527eca5b0782c913444764e0c2022f3e7061882855e43045165458d665ddc19018618418ba5851050f5858347104682906299064616d49e4927b0ebda599e2696868687c876bd1f8e7a9cff177e588ff5c876b7d20088220088220e808e05a60fd6d6323e79caae58a92cf8ef3151e3ebb095c2b1f806b5924ff192b5cfc8c1b806bcda8542a95ca73542adff2aa20e47cf60270ad8cadb58ead1522de7a0ed7b25e836ba96ca4a2187dca6970add46bc9775de79ddbcce8a20cf15d8745e73dbadc759d633194041792b002072e9d900e3e35ec004396243a0f5f4701782de1b3e370adfc7d9efa1c7faf28fff90caef53911bc19d2048b5d78ebfac95b4ff9ead5838493b74e00ae65c12ce1440e86be5451d405f4f22050155a78700243109524b04024983ce803e05aa0df70ad4e4609215ec6575c4b06879e0ac330f49cd071a83fdce1c328577c36c0671700d7ca01e05ad601c0b5421b31517c8cdb70ad181cfee98a3fb9539dec27b739f9c973e8cef471b95c31609db4504112c5480b4b5d64609d5cd69d05d8a0840f0e3f50f22588258df027eb845d22c6ef1926bc3de1e6b76c12c50ddeba75aca318e2ada79066f0d61d298ab7be0ae2ade74811e5ade3a092c35b97c1b5ac91cd6797dbc667d9c4c66f79f4b905177c643082961f8c907bf5d9733ebbcc2e5f9f7397cfee307c761c51c4f07905201011610b134e68b08195a388c1671fb9564612b9d609c5049747b9732dd412537cad5cbebadb54cfa9528a7cedaa52fd6a95c2e5eba9522554d0ebc9f73c1244e5ab92af56bcd193baa794af2eab14a1c80727448e2c0962ad82b82107341c51fa210b5997b42083245d9e28ea81354407518ee0021741d4400516d60948c2b8208b1640b82006d69e422278614b9218f07045124cbcf0a1dbd06370ad10dbbc7599f3d65af718bcb5d661702d9bc49fa4e4f0277fc1b54e18054e00e1440e497000c416acad1f04b73ce8b8eefc88028a1e7a40420720acb04029353ce8355c0b9cf9cf6597ff9c866b7d48fc0e3fd77cce7909167c5ee2a50496cf3ec3b5b2eb54a10421fc60032d9c70c2dafabb4ecb778eebce901b9a2062a506480429e1024b0928be7395a7b85645227a6dc5cf6b97e15a3acb93e7384f71ee2b8edb7283e74e1ccdc2790fce7d709573c9590183e73e8e56c16116e7f38773c939e6f4733e30f202115e4cb4e4a0046b4b221c47d8608a1141495831e252c08396233a001551a203e7b2ee10e0092c86c2f8d04509285839bfc32d3fbcb55b9a78eb312926bfc3277ed7dc6401a345089f45ca5747e59cb3670fb95696f15d161ebef313d7ea40aec519bde7f289f7fce35a1ea6228bdf7ea3e2b7cd5733a84ef6cd736cd4e0b713b6cd7d70dbe641aa80f25bd0e6d3c7c8b64da0df1cd79d0b20b900032d8a38e2081eb0362250f9e188072282ba8881b535d1b61555a1c3779d6baed50d5152458a107200650940c8c0da3ae9ad0294889030c413469e308265a36c79eb1ed7b2aae7c03ce71dd7e268866240e4089f20602882b5f5d7af58bef6d004104992a4f8c10c589509225f9de35a3553a1446b0ba2f1a6231768575e735fcdc8d1dc46135cbc76c26b3d68dea3ee6096a6713958e286303cf842850b581a163f74b1030c9410e5c0dafa35975b12bd268d8a78ada8892504f0d635ae65b111bfc3af5f5d8339821335f52d4d2a615ceb5657fd4257d553f87b47cc559764a0ad0da0b5e1a76f30cc53d002815a9e825e9e3ae82202bbf80dd21b7e83200882e0067b9e821b047a0a0661f161f119e0f7b7f42d7d4bdf57e5a97f4fe88e2d628b7cdfeb9b49bfbffded6f7f3d4fbffd013dfdf637f4f4634233a16f7eeba37a335d2fbd9ea2b74ed2f2d4f513ba53abd42a5a7339d25487df7aebadb7ee79aab7067aaab71e7aaa99c81a4cc23cf56a134fcb37f1bc3c758f0b92478ffcf6b6b73da3a7eebd3c29de14afca5397dbeb79ea79de0cb2c60c61beab3f5dfde9ea4fd775dd13ba5385aa50d771c1927f7752fcee7697e577b7bb9ea7ddee809e76bb1b7ada11d516b8da02e7bfb94a84ab44382f4f9de37205476473421cc7719b037aca056d9135b684799abf28d19be93b6b79a5ece5a9672e60b6df79e719e677de39e78cf434e9a9cb9d7b9ee69d819ee69d879e6627b28693304f376ab451a38d1a6ddbb63da13b740a9db24979ea5bcf36a9fcdee614bfb72ebfb7bdf53cddf606f474dbdbd0d38d88fa60a23e9852bf4d4226219390c91483c96432994c263a834667d0647e6b3fda8fb6b5a5a7aeb9a8a6699aa66960640d30619e62eac2d485a90be32378e3f9e437de1863bc71cf53bc31d053bcf1d0536c25b392ed2c294bca92b24c876c673bdb5996653beb799aed0ce869b6b3a1a71911146c42c136f6c25e18754c88a71bc3300cc330a4a73bc81a7387307f836ed00dba774a95a77e9fd09db964894b089788dc57e8de2015c81a60c23cb55fa6cbbaacb5536c95a76e9fd09d6984de4cb72ffcdb6efb73e5f7b6dbf63cb5db023db5db0e3db544219035b68479ea41aa92d212911fa16af4d4ebabd68af4475fb5b4640d30d24998a75f9eba54a253aa3c75fa648a149c3038b2c6f43dc13cf5a99463e5e96f4ae4a9cb4d7f84e8a63d4fa9cfcf2f4fef4f578154c09535a6d33d7b9ecea0a72ee79e43488f59576c417f4f224f7dbaac78ea42e40d4971f541772c4b3eada137b30b98a7d453d473a8a7401d53a736686a0a5be4a7a7a8e893972a3fbd1ed51d0ba463818e7e7a4c11bd2dd04faf5f9cd840c3570c14e4a40619fedeeb6e3323e73ab141d20fe0ab5b2c99d0c1dbd6cd4864287ad79f9f6ea2819ef2d34d32e84d8d7ebac905bda9d04fd7a6e84d7f7eba4a09bda9eba7abbad07b26fd749592def3f5d333147adbe9f909bd67d04f5745a18dfc7415143a48dda94bf8ba4c5d1e84f057a7fa75095ce7e1e3e2ba246795af4bd66585def6535bf49634689510f4b6aa20687a72bd157a2963884f15b490938888868826188a455212ae570859a5a1a1a1fbc5c808c342772ccdae1819596a6384adbc5eb3ba9c55db824dd8cbcf0654b36bdbba585ab21bbfe642774b7a6346cc60e4a56f425467e7225ffd0afdc6b908d5d9d9955d54a7e7024daf3ede1148e849e9b66fad4dc55cd1d86fdfb0689a747d240e0ddb3ee63bfc5df35bbe6c4022bde56b078f2c735d77aa7dc91b2f3ef31b7485a8ce8ccf88b2cfe4fd74ccb034eea02bcc53da619336ab9713a5839e5e7f5b1e1b0ca2b73e69b53515d295b5f38192e48d03bc8076dca4ba93233556ba4b6fadf5d963c73cf2d68fcc1d3de86a8e34d4e5f3a0b6c70ebaca9137d631ce01aa39936b794adede7befcd3c21bab21e445774f6d891c303071a93b387b8637e8f1d3ce69cd485deba4422a7d84215fab9b3a3ded8972f415ff71df4c6bacd9cfe0e87427b40d9683d246f9dda30ec58a089382e9b7569b357afaf2340a1d65ab3ead7bf68f99238d5332b6632f3803c1d6c26c218f27480b1e4ab57183e902fa8c87cc32012c0926a28136b86ecf82249e254cfc68d87bee65819499c0a836854922f3aa177289442923815e8ae8c5646489d304827ec41aeaaefd59317c092bc917925f2c21ebc236110d5f16c203b23ac00b610fa2ac308860096244e75b7425b91fb9138351b3ba399ed017dbd3e5f7d9b7e9e76d751a856c613baaa325ea30c23d4123d7dcb305abdb22d366f93db17bdf113d55aabadc9b625dad4d095f44dd27be7bd956bb9f41a0b48eb823061f296bc256fb15a4a29a5893a1d6344b16d53d64a6bd6554a2a5466abd7618ac66fb54e3be9c579e95d0bf4ce33321dc04bef58a07796b6d23b25e64335d8a86f747a3407ad3447b54040b5e6b0d75a9be362f7de9b03cb7c7c300cc3b08ccb917df5cdcb5db4748cf4d5a7fa50a257529a40bfe5ab95bb606e02d96f9a9cb4da8b65b81b31176cd266cb2c484fdd73e9db17ad25e5fc085078915d5af34d4e4a3b93c9b739e72aa42bec14d750526ba7bdd6e9db7167e0370f18519d3a00eb30b05bd1c7145148beda693731638ed989b7ba85d1da73ec3e5e75e73a761f47750773ec3e9092a8c0a8aa073e8c903734b7a135003eb4bc2d415b57f5c01369a67b3ec724d7b5d3e9d3357943f369721a4295c650bd61804bc633c04543882200fb14370df1e9d063c76820dd914650bdc157368da0a429f486810403a9ee4c168c240c60ee79e530705d8f3e88ea0d764ff461546fb06f56f4561da1a6d05426fdb621e2de8648e28056f4c659a2d183b344c3256f708e9d460bf286e6384b347e6800611f4d7c18d59dc9c22e6b6a4ad2e9a5553df0807e4b28401b489237fcb14f008cb6be2514a0c79eb72ef434390e17a9432388ae308d1e70bce0e0f0d87180e48dcd55471ea0d183144d6804511d0f30919e918c181e6fa30c9f0e106de30a9ed1173db91c2ffeb42471b06fa62b4736448f5ffccda825a8fb209a0aa02de01596e48d1751fc8681b4458c1e3cf6cde234517dc340b24212a756df364419b775a1b7eae8310c27a48e0d118571246f481d9b22b9c2be614c79183178ec307ae40d93c34012552f8983dda64825e5b163b721a23aaa2852d5020c2489c4e5b1c3407aecd414be645ea651e592385bf87aec97430186457aec991bc063d708f077e8b16fcfe8b16f38534e750400295edcd6859e2e89331d4749e2e0178f032471f05e29e100c91b9d63c751c216889f99b2527adc8d2b28491cec6e85e688240eee94b89bc7d8b789e8b1aa07fa478fbc11e3f114a3c7ee83a8ee50c73e49b09238d873ccf86d4ce2394ea57c34a12b1c681f46a30f22bac2ae8d7bf5d8514b34f52da7103d766983c71f320b5246553e6c5c13ae09d7846b426108fd9605cd92c3b1a4b7e733df3b4275e61c2d5da97aa05f1cc91babb79dd15b6b335ddd23df17e9ed4db22d1cd42bf2d631a7ee15a13a3476a34163630a74659d8e38503fa83b2e6fac5b6c77ea37ef67f47cdc334257d64f32d8db167a7b3edecf14bd1fcfe7e5cf97d7257130d7c2d46e20b20afd157a1deb0fb0f33d9f7a63dd7aa3c7a0b7e7f3d6472cf4f67c3c1faa438dfc1cc9211f89174475b05bf7947842544708aa83f9d012a2b739e453df5e93f78cbc279a6fef45b33d299404d828e9ea4e1842573615177a890a039cb8b83929414f97abbcc49852f20c1483526afd7a0c4a299d61012a236daa2ba6f0064a295531d1d329a59472793315d1f23ce24b26bfd1222e84781e8a42f7d4a728abf8cde6294a2a7ebb798a528adfb04aa7b44f5f8e7b0417494b97a2a56f771483b89c0efafb022fc5cf3868e9578cf29b69cb5ce7e90cbbd6563aa764a2c2a2a5d3cc177f9b7356d75ebeacb54a151174366938c3b8db759f3d1db704be33b24bd88cb47d59c4c50c7cb4e90a20e80d4c12cd82dfab17972d7585e71af4f418314a90343c2a8bfafc2474ae60d2a9b4c9398bb0c5a4dcac5c98968c1c34f5296f581939e8296f540bc82b8238da7245064010810a4be23ce50d2a23073de58d2923075db1c4136718538be1db755bc853ffc156fd0a3d5f162141445754062dfd05616c88215baae1c37244476103cd29318396eebd2c5a2afa8ddf88968ebd2c5a0a0213db3cc27e2f8b969464b156ca1bea3f9bcd4ac60ddab74ab10dd5915f5773524a29a5958a41e86ace1d3ec5300cc32890f0a5d3ea7dd155e71a2a65b2d0605b4232e7d442e99b3a5bbea4945256592b3d0092cde2cb4f9793165991c5dc7c8ab06062166951647e734eaf548a3a661042a7badea5f41cf6e59437d227c598ccbc406f927e3a6640924210e28521a31d868a98e201310cf1831794bc72f0c21020389101080b1f2a374cd1c3892310e1dac20b11a6b0a64bd16a485abac6845e495725d12a1e6855107acf983e038623456872e40211481069c0134655bce0830c4c8e58f34b103d54820043e585002c5162440f8838a20a2cace95494005d497ffab2288b1f5e95032d73aa0bc3c1841f6440e4495603adf56019e8ec0846457f3a6022347efd4c49c9f002ec8324ca949438c294748614039db14ca14d576441b002fb01b7803da1412170d1f88515815df9651117383062cb9994c96196bd2ce24207bf6da815db6b6b7d840e53352798600308ad5f167121c2bebcf0105c8e927041c292747e59c4050be6840b182c8bd67ab00c3a3b9265d19f0e50347e6150b466250bd2de67401afbad756ac62a15ea1a1b498423059dd25b68f17bc5031bee369c08a76c18213d467aec33e8714ae9f1bbf0c9f5d80afdf4e902a980ba246b4c97950b18bcd46260e4053d5554a10206ac2e5ea24431c222888929587bfabcf409a4842d2d604184293e7c29026bcfa19b25a4b84204111980e08545c413362021018a151f50d0b3c396243f58a20561587b1e85f93d9196f07b1a2d49302f5dca94b4f1d273ae4cd1c10c5c8860790206560a756705223700e18915234148c1925980900108095c60b8618a8c32042ca8304183234b7461499f44a6eb07268405aa83b9fcf2b28b978e6316e1a56b29e472b968207d26511d2f442e97ab8874c903d30e7f7d35e3af6f13701cfdc5f7fa96528efefa0a4780f25756f99b74b7f412c4df49e46f4f91bf7b1af981f2f7bb414afefa9e420ce9dcd9e4afd17cf23775af4fa42b35fcad3293b4fcddd3cbdf25d7952fa8df9488f54d7b8a1839f2d729900ffe52247f2f55f25788bf4bfe3a25ca21fc4d9bd0f8a6464fa894a3bf4e91baf8db5ddf34896af97be912144607f8cf7570ad0f09463c08822018c341108a250f66900bb0c641f00b9ed9a864eb178b233daa18a31101000002b3150020281c0e87c422c15894a479b0f7011400117c9c426250188ac35196c4308a20448c310400420000c41018a921991a0404043fe76dd4933329d57a75cbbe7a92bc60b4b911826b1aa1dfafed6b34c34301295b18051791d54a7a8f3d8aad94c7c2157758223ef369306911c20625d0f4e6bdf4683f26ca2e8b483ea443e13a22f2274cc00e791cd9503eb20e5508354bee3763c54c142dcdf12e70f7d6087fb8f972eb84db144c7dde92bea4227d740b7a1888098e3a1d0baee376d45b07551d4b8da7a53de273a7e44c5bbc533871201f95068da609b04a3305d4b6d7510be34062afe3f5e0a83b7ada30fc693e6773f8de5cce4566780124f1ef5754d6b328476e5414b22ef5d4157b44faf88922d4cb9f0ce4b9a344086189e0b5ac2b83563c9d06cffe391e01172f7a0c4f10d64a1c243bb807a66725e0227d1a97caaa2b2aa26fb32bb9c62fd63611386bd0560a86f4ede8141067d71ffe415a6476c4f9b2a1a4cd4fc8cb43f22816237052aa39aef3e8b4a65bf410f1617f9c263b6e5d147fea316802f0c811f1e9524e07db51cfc29d27e886d2c736e8e36be648e434f30d38078872c9793c74df549fbaaef13686c7b7e37819be2713b6c48022debf8e9cc314b9f7aef20bed82a2da1dbdb34696ea89c8cd65d40f613b6fd1c0f5d8abeef4e2890305dd04629bf0ee5890a20859ffed25801918409ee700a0254c45319c54f38607c093f9098a2ccef9585c74b9fe8ad3cf91fdbadb85edd32c440e57aa046c9aaef9f76b42cb33576e0512bbbc11ec1deae0a90e7690757d172d5c3b5a437622e421a14b553e93c6b097fd98e4cec9adb6d8c60488f6d7a9923fb16682a1b04a40df66d54b928a7755419a314da22402896916c9c85d5107e567ba69f83fe21f39f552afe87766343afd17eaef153e6dd9cf01445bf8ff19d4fc52c72d161a8d5cdd093be95253245b73dcbbb5373973e33dd94316e4f09b52868777dd7b62c0c793a114fca5054304beafa3185c591181a961b3378fd9dc5a67dbe9c25c50b28435811168c9f33a4760925ce10238c16829afdee41cf1a3b27837da073d0f9f6faba80206521da564464e9e5b6f136ff78e2d42bcf575672d57959c93ea682945008000c432a6069151185296b6f5ffae2bc4f9b5e4292459e9091e26238c0337dc139962e72d31c668c35fedca0b65ed7820c7e2a6fee9f2e87b7405ad49f05aa0a63024d8c639b2ae8d3c23df6f8111ca45a335a15abf07f6b7c0f38a889aa16cab78b155e09c23ff28cd2fdc01a006887b67d309d661f11c35ac83e9fc9c311867330d359122150fb3ccaa082866aeb616f1130de3b02cc04a4ab715632e8959d83450b83d0ed9c1a3d995cf0733f81dcb09fa5c2b5db637ee8f54d0cdcc89568a5894debcbe6c4a70f99295ded74df19f81febf9751dd29f4fe1b5975568af7d734d0a3587e2ba472d63659b15ff7bbf4a8bec958aff26af20a3e39d2973ef50d3d767245be1f2fc27d10dcc074c5d2073e35988f83d5793adb3afbfa52043c21e2e6510c45972be5589865228d82f938cc4d85c1bcdb8b9931b9df0c118735bb01f88f162280ff725819a469acf22c2c212f28a33ee79527384d81b6cc38601d78cfa08e73cc636489b2a464d98ab35f5f82380b22d47f37b4b396a3cd64c6266d2c5d6b1d9453266acee6954c6e21381cc4779e2d0bd0d56a63a17d497a659022fe59a026ef248a5ac5b5801ed63299465234e6b3741939827fa233345d41183634395deb36734e4eb78a0d58ea9f55501e150c0e0502a8e065aacca3449aba643b7793b3febecb4fe504597ea46d154debbb3b0973b7def774fc9279dc43642786d3c5d18d2da246998e7ea8e2f17f559ba8c5e2b7c12d516fd9ec6f9c379cb8b2111b453a6b1ad65279d21d22e673123be3f79efaa82f774601e398da44ae5f6b7a41d2812a2a70736deb6d1954ce7be83630295660c4c56638a3a8f04c9abf19ca174d5486dc8bac13353242805429bad6e96a715d929f5ef509604569b271c66d2c8bf43fde2b31ecd4313137595eab06b0057ee0b592d30ae6bcd61abf37666681e982d2c2e1190d9f2cb727664bb7ed166bed2a14fa32916d041a0a93f8d285d5a2474dfe700778d8e9912ce5580b27e338cb3358d83bdbd21a3d1848fbc01b1890e5760394095f577b86389027349ffd54509af2432b25ea92b8c6293b8ca92bebb7fa1d5a8f5eb0d955d8f51ea8c034aba30e41ef2f4c70556ac9a5af0dcf9613aaaca3b996d44a2d5043b3410bd5693bd7542c4ebee33879836ac31f68ad416899c594c9c0baccc9d67123c9fd01b40be1c4a40c5e5e383f7a920b1621c101c782a1003fbf53ac7029f7b396ad72abccc3133affbaca4b313332e6dc5ba366265ae1961495d8d444be6ac9b97e1a68cb8a1a896866ad2bac6440b8a5c12711ec8d1d4d34144b40e5a0bc46c5eab3566b96fa6f59221ec913ee1048624f57b088f3909d87ad419dbaf689381ba9aa7c965e16225c5280f1e38fc794f4dbaf025f84082bcd40cf9ae47c00acd4f0cac009345f2c8066a70838d9452285ea13536d8d3b89d7fb654c7cae4addd0cd5c142a3b088e425da25076a5dc0763647e25b7731b5a9bcbe4da428f12d092b894a127f3d3598d1b81febeb6c3ba426c10a13d8b1af77f155c9bff3feb94796c6beb58c9edae75dd2065e5e358ec281d4d82954a919f4cccb47a2e01613618e6c932bc729e71442fa3009081ad012931d63a6fcc083f34d6c8196c33231b231e80cf10a8f481bab72eea2b2827d8b51a853d8159c3b3fe203c388c0055c94d428d63996bc94431c2aef2601cc611c3031ac3019871492453e58c6b0170bade606548308bbb438c5d0712a5e35a910f104e18ac474e3264989eeab73ed6cb512c12721ee4cd4a5a5ccee477cfddf3982fb5fb876e1aa09523834e7f99fc1d08650ab8aa0c2ba3f3daf6ad0adb3f2054b4a1b8b764e77298934b1c140f47968c3a71064d2a5156ff38bc1dde67216afa4c240eee104f904a004b2e518d4aff9ead29e395fa4105570e01d6fc4085250280892cf0bd771fff4f5b3e6481702d1cf9577e13282f54e42ab1f9102fb8a7d259cac18b476120d05b7c292963c102c91fbfea6490fd846f04667472db6b4a8003f83c9cfb8538d6cb1d9281ba62d198dddae84c4682bc515c5359c2f8c609b940bf5de6395030df7aef6ad789e1bec08b4e493c70ad1498db711c9a7f747ce931e6f8fe01ac07b1cddb07ec83dddb30437e66195ab3b52a56b1cd057134f6af62133c068b3a64bb72fe0680eb9da695d5ee577e53a9f9d17ff722d3168aec7cba37ea7b5989d494f573814fb5f0a3c022de6f48d0ccdca1356707af9d7a7322a18e745bfb0517f95d6400c598117d616faca5aeb2731a8753fcfb827f39d6a8bcc0a009270f559bf89e7f2869fe232744627012b85df66c7acaa5395729298be0f9164eca377bdd343b048af082fac13dc9c2d35163234ddd6e0f1fdbe97f558195cfa3b5edbb9528a5ca3a904cd18323bc0d19a3cc2205128a2249157ace0382c495f5f8ea4926f805a84598a394c16b7a0fd2a0f0b453d177354708fdc4eeae28b535e80eeaeb8e807392d54323ea0f5495e29b6f831ae41749d5bd482ec31d341a883a78843f01d75494fd7e45ae36571f57f8b9acfca24a6766e19ae8ce4bc8d3415147151f8c7fc992c46b1f964597f65d614a8e08dab651983e3f6347093e8a6a14b8637b91b43c047baf0d018c231c1eb3d2b49aecb08235850b367d7c95b7d8cfa9ed6d20d04269348b8ca301ff27f5c367ac2c7524484ddd39026f02c3ffc27c49e8be9747a3468ec986a8b7abe6b58facce028a1399c681e603fc9ba09470091c3e18c25ff3eb6e3accdcc49eb44d45a25cb67616b17e7f6b8be117a299b588c5b747b5f49e976328204ae12f2af75f607bcc8b0f60be3e3af5fedcd087ba7266cb48040d60f8fd5d8b5d467f350cf11b8f7a71632cc5ec845cd771576cb1ed0a374856d3b277899c4d6e3a43481e5ffad68d05612e313915ec969eb11634a1b31c26fab0a74d388b2a92b32c7d33231667cba425cd8388fae5467f6b8d9667c641edcca238b362a96371a86d553b9b4c1a5f744db93e7b932d61171716ffdcc3691a1c5599d11d9725bc52991125790a04aa46eec777427003bdb87bbcbfb3c7f8c946bce0fc409c87a26ea8f06ba26629320b2b8aa2a1ebeeef9461b32815d074b9dc0c79f0026fc6f1338e957727f52e27a279bea965f20467b9430e29589225131655cbacdb7af462c2890070b49a647e696125ff9eb860e768e8cbdc88a25cede28ccb849729ba0dea27791fb614bb82ada8eacdee5ef110347fd8f35deb4ff92caaf6826fa15f268d59d36760ac32def2a7fb289050802094180255a0d10384de47fd7ed6d8eb81fb7d42a7bcb5c6ee1fdb37fbb8261f8de97174d9b4ae597bc1858d027f28beafda68cd2ec67646e419eb3156676fdb84d8b6e3a9308e02099044fdd4fc99e2e47481c54f78cf34e7501ff6d49b977a6cefcf67a31197f51c4a369f8d47f13f12d05ddc85b0c182ad5ab3340039104301a44195022bf9d5d36bb11be066dc87176f013b7c40f983d1d25a34cfee3d15c20d863acc71740482130e13e39e0689c2100c6753844ed7d8222a1070a8f3d230da225e4ac5ef30137bfe7bec2f2719cec6dadab6ec47fcab2ebcd597c5cf991cdb71178e81e7858ee32f92fa18f1cec1a878cd8467b09584c427290b422602f058c542396a4f2156ab7e2f3441a87f56a2b3b49e4c6aeaea6d0d6735fe65ca19b85ad1c51eb2ef26a8c6d1e8757c81ff5b92b0d3cad40afb252f656584ee30f9946bc672806a2c86268b64e087e440733e70084e9d7ac1decc418c07635bdf0c19230566770d8a3fe7f507ffa908adac62d795a5356ce64685ec4dbb662321303c4c5bd5a17211ce49a3ac9a61b1edb2b0dcf11a3b1ee7e31257480ffb8facff16790da9b1161ccaf70b582b0dfedbf9a202bfdc24b9f421a665435b1f87e4e4f487953671a6c520fc1c2886710aa15c275cdcb1151ccc0ac2c80f124677dc2095392f1cf135cbe39fdc11832bd044298e336911a0345ae56210603506f3c83ae07c12f2c5c057fb85b63780e8e4428ced512b4bc50327d9a8929acc36a63cdb9f714cefd2d4d48675419030c0d7183f3c2e89db50dc48e891c466ba40882f2bc7884500bfc2896fbcc0dea26d58ec085e49d4cdc6e2034a4d6dd3786bed597b52a2c63b6e3b29bc2423ab0811d2227ff862013ebc1077e6acdb702c9aebf59833a36eae241fbe38f4f0534e7c5450c0d60705964f3fe6aaa4124282f97576c896702240fe2b67bc309d2868d886bd73b8942a774e3f73f312f0c41237537f323d0c5e8a80044a389c248e38c975cb1157c2997ca9adfd622756c8ee9314d8d13e62fcfb720389e893ca2022e1a4f98c15f92c2c6105232453a208522a569560110992aac4cbf566d8de4ae3f9f2287546326cf8933c301bfbad1eac9b3110b12f8fa3c691ef3b6e783e3e2a0fb68bf4c5f7be215e5c242b658af4a74094454267b17ab1b10ea111197d1da0aba645b515b6608334055db41d86e4b103199813d3c5dd36c1d1e86b249042d84ffd15c2628d620fe06656d39240e8d4c1ad038af5dcb79779c21f3df0a8deafdd8075acc67270bc77706e75ca36a52381027f69207f0b7a1f7c6eee52fa39003d47e8c35305e8a3e11a4f176e5ccef6db5afb8aef3cdde05d922722d6b076f6695819de4b7cf264d12f2db563bf304646670aba6751c7dc71fc611c5a90ed085859f46e1e8b7f6fd3220fe02e7e4b268b1e012f0d5e8f49622b151cff6f1388d0481fbaabf71c39ce67b992f13bdd45f9d74301810432106307e3a63fc84cd2f7773b921250e871f37f8a6089158a5264cf8da101db26d251b09cd4a463e306561e4649c6f6b7a82815c27521b1a32c24529a0c01b3c0a019de904475fc6eb48ed5d5e571be44411f1d1be0c2a9bb60b809b156f03412a2525af6a222843aa275d5a2930356eb34d3d6b702bfbd6ec690863a529f25d264762e5e9c8f355a10e93a28c7d6c29b1485864cfae6b877d16dc453486d7de53f2ef17c3b2e59a6cdedf95c58346dbedd8c4a5373fa6bb4a753b0c1d7e0998b055979499b5aaa94c138361fbc5923bc2656cef8d39075353603d20e1b3aaea06d6723acf13b63f83f078daab23d952bb652018eb30ec67f74a80c6264cf86e8d223111be89d4111d495f733c000d6c16e3503c2154ec8f0792a5265674e6b7986d44d551429e2fc54e709a79bec1057cc3efb0a1eadee3464889de58672252a1c8bf865d35c8bb191bce0a00bc124789aa117e0366e864f5580fbee9f0662c4df240f7853e3cca60851d95706efd930b8122572e5d2ec99aa125d7ef21549933f11c02be7e159c1797d7290be2916cf2f86ea22815de8fb43bca76844e490d7083366b883e03c78165db7c84846761778e04754177b0de500cde3dd844ee7d24f59b0df397e52467a191021a9964cfd77be0abe31468678af3d7b2c61df8c2800dfedd74ec860ecaf1ef60c30766e0e6768ce70b89111c348ea3aa4be35345db63701c8abe4ef2278b0e0f70e7e5482d7bd00ef09448625907680d4609d25db46f63e297f94d63ef8d49e656692e7fa1e8786d3a821ccdd8a8b426f876d08aa8242f03b537d39224432214cdd4d1fb5b41034d0e07d3aa8c919209f6fc1ba531a9f86e5a87953fb6b464a31c49ec1623cc264447e098a041c2e1aea59276e354e96fb48fd5c57503981b4bbf8dc95c3ebd8775a63f3f2748c1646b9e86091eebf63882365cdb9d0977f7c3daee0668ae130dbab710490048f89e3576f36be01b6a33c103e9b9db12e1596427b7c6f0dcb93dda0cc0612d00c456ae545afdc20710a220a11a87442d90004084baab15f4d8470b7fb68a03d68c862fb70e2b0cf85f38f132df3dfeae8f60fda7a4e71743fba6e83433a68d7237deda36c648cad9e6c964eadebc0b77966525f81d8fe96bc1b7303febfcbef43d45d52b0021ad08f153c50e51896462e66febd2fdc9802217e204a211fb6c949fed3ef41bda6f049a7969bb3f4e049e7ddd450a1b6e9c0b03cce86a4add338e51eb20cf67c093760850d2002b8d644b4c3427728102cdb67bacf236c01ac903a94056bcc8e0917bb7f0ed6142d01f03314aa303530398c3e171993c58521d639627bba9cbbd07071aed4739d229dfe34726f5c6b1e3125d66b9cfb5eea8cf4213d20447486b46de0300277f2d4dcf3c11cfcc0b1fa76a1420b42331ae161f41add8ca4ed9efd6b5e9c586197e08291e06f27f32b166b78a5557e3026dd32815e1d5d2aedba35c7f096dfdc714846cd9837700ab34cdae3b885cef1eeda78af936cb6b6e932fb7d4d177dbad5b2e4e37fa6b8b186617c9b4706c254047111c10481275a4b6236667ab9846d700c6e4ce1ecea5fa02987cbbc62bf703a29a42b508065ba5c1643d7b058016509f8a820a696a7209e2c4eb9ad50ebdc212793e60eeecb2bf0ba967fe64d303aa89869e104fc26480192b0352e262c9de4b20ec28af4e9c9ee422464a7bc4316174789637321d3a68cc2dc8816f24d73f1ac16e30c367fce961f4bcebb8a2fdd0d2266e2c6bd7accd60d09aa4e2f29b1e772614df412456a4c79290f55118384a4aa3f2968716a9708cd909b1ff10e97a889d17d85b0ee29575803a67e34fab925678700e24f99f87c32dea79533f1ab02e50823f0e92c298eafeb7f3b208f09816ade6f85aca52836e0cc57262ec6b3d7b7412b948a4fba9b1d7eff6dacc59766a12c93b12f341c4bd426e0cf88095c01601f87e12bed054677a976852daf4bd6571507bb9766aa0237117aed6b8ce04da09924061b0e14b445d00f34b5f99c26935ec2f29a7f08661699990a79325c1a0dd50161069e0b738be6df9ad6bbc88136f3306d9a84a9c31fe945a9900f03a386a5bae4411052872ddb16fba94058cb3afe5f8c65c0f6751309bef7786a399c334333098f3015012f0552e49a568f4f535502aa644ab1673ae49eb44989675190d20a72daad328115fba001a1bd68d979c99acee5bb6ca8e4942b7f3ad8dbc3ab5c7f6d77526a65243b79d6adedff9a6286d9b61bc5d2652270ba4efeb47167df34444d601500c0d08057c2ef974497855f637e4e6e4b0cab20f455afa8c7038d6125278d0a096e44cdb9757d6e8196323880b6082913776dcacaa8dc249b0cd29a3bc338c40d773227d6de20d5f14f00a8eff484ac725d85b79ef309b729d8115274bf1b69cc6f6c47af7feaf8bb023a56f92a4af5e45b7f0f574b34a26b9a82c9cacb61e7421ff5403f987aa3b098759275a66f1f63dd493af9b6640976bd59c90773a4cc9f7d86c79af7a87683a55a5fa33316bf8efebae5a950ea2544ea3c15949d6faa623539826fc3b631e2b7d923410cd7c13297180391cbad3ab9eb79d87ab4d110032845d398fdd064b8925e17b14cc6d9af8c62951fe8b722291f17c78dd696984b345a446a9c803605ab11ff2078a9d4ae6a12af9889dd1c5362159a6cb0defecc3f667f6c4e29b8e9b4e5d59a3602ab0c299ea87acc3804350125797b8df2981de4a875e91342d102534444bec8542069330d626fe75bb2337dce1a268519294061ac5ed2b4018825affb618c9732869764f583231911bc6001e98b28642fdc741a807c5697ddabee5ac1e84d21aeeeb3f620eb314a3cd53e4d0a3357827f0d293664ff45cfcc6b46a966cd1246d9da5ead210996c4c7d5568192d4dbd435c9d4f2c135953d41adf70b55d81298e29d522650ca82c49c05941653599d94b3b08871407450846685c1a4c57d38ace2d618187137a1ab6b3be888182c57cc656dd3c0b296b9994254593a428de0a7e2b35cd64cd3fb242ed5adae7cddbfa7d8135ab18641a76edb755277277d0180f68b58c92eb340ab4ef06e5ddc1b930908c658cb8f61b0ee5dd348e75209376a25c9ce4eaf8f8fbd63bec70c4fe9351e276575e1241e54c9070b8db56040232944b1816b9420645f60ce067f15392ddc48828a5895fa25116b782b66517d38753e80df163d93ad5c3b648b852bb85251ad9adea97dba05b41b66f4f9e564348a8cf818b9454891192020c0f3409a692c89f2d994a688190a539020588330cefa0da0eec2915605946ec1153620bf7219ded937c3929d600621f054ce11c929a53975c2cc241de59c5b18b529dae350b9247fe4489862b92db22737353a262c79d1e6f2b768669de495e238316d370e20ad3a961ca682610d2d6a4118de7a8ec1fc68a824c97e2f4329ea52b5d4a25dae8943366d7ed80ea798b8fa9196a47e090d750ce6e9e4e7743a3cf422984eb9751d45b924098359571c985903d3c80c357801db7503c53775ae93d7065f3f7c31a8ac20b4d8d318c717c28210308712dcff2552c969b8ca9ebf01a2fdb99a184ac690bd326316bae12d2bc9a203249fc6715e8be4774314a5382056c5777785dfa7197bb7199e72bbf05498123034846722e17e6ed00a169b58c3b41cc7076e0d129576032f36754a1e143a0c15330063adf852b580781647d0da5de79239231956343973159944f5d152922dfc40b26445ade8ea76f535c5766d2931f9f0c4b38fa3cd870fc320214b2af68a415f3e90c9284bca272225e2eceddd781269071dae8d8bae9f32ee4d834638a398bd897562cd8b75065ea1238488101e98f4cdd7446d05f5041aa4c91c7c5016c8ece010c58fcd7b0e5c284dcab5f8ed78bade879e7ca14f676a0c75d4406e7468f7fbb0ceab808428b81f77597799bfe8d2d236faa23ef8aa2c6c41e12d640b35cc9cd9a399c009ef783313d66ec29edc55fde4f296db300238330b6978c23f7e33736bed58628d8c25fee2812bdf413ff331e0518b827e92bb1c77d269f4aff501da095b267d1e68dfb0fb808ba1c425b4bd13934cdee0fc6cc6734370e54a1d80db27e5d179571d4194c79c7dadf3c204fe825c2afa7a720b9d0e27b983e3c9403a59eae701dffb06f16ad813ce4d7c9c5393d7c1e7f682b3903d7092b30a57ca9b6b9c7073fbfb5817901c9477130751b0f01122b121c790495ecd1bf1a44209f8206ea287a1c117c66578c1345033566e657f56a01bc43fd541db87c3ad0ec2ba2e0d108c2b33108c79cc972f71e851b13a080806a9b679c24f2cfdd69142150c75d210a2af257f159b0a36ff1456ec89034e9cde2799a4b4ff702ffee13b3af0fa4bd178af53ba25ed9460042455880302c79d0f2a24971e381c965ac71606203a617589ee575052d819b41c54b1c1ce5e7ba9fd210a072990b57798bbae8e9ea42b90a8df577beb688b2efdbd0863ef00882a0dcb2da897ce9bd176bad212bd35063b546ff4693aabcad2c28787f08b3342294ad711c6fa57a70d43fdf80256f2df688c829e84bc0e2e2a87f1ec52991a0472c364d4aa26aa848119e63e8039ca4c2cd744935c4b320c7abe87c423adc555b7edc1e57c59790f40ddf6bc34747be641bf3d751bbd9e347154a686025d4a4cef21863478185a7a18feef132aa06db5d05b9a8b3088c1ecd627788881118b9b1bfb96670a2d4ff2636cb17d6dc78416c0d56b300af2a9bd77deb8d0da5a860b8204f7757fdb7a4f23de773b7afb1c7d83e9b937efeeaf6bbd897d74b12203f998e539bf0f62e19270f5375c2ebab53b69f820653a7f4227545e4e07e8f1c709a5a3ece5f5bd40d1721500003b2ce8179974f9f86d1bd25f3cd75abf482a77e2fc0e487d91d07170ad126130a1f811c2d952b701bade7abae3c73f7baa027daf5e2e103f8445e24c6b285f395a70bada847bd131d497adadb98d99428a209dc58347192f9797ad446a00b1ff0697b1ee7f5d3f4bc737907c616a087feee69ad3848864e6b9c4df0375e4517180128f1f891390029e04506e44428e8103cd4df5cc9d5c1b41546fda51dc655a9e05e1ebb14f480eb145c298385ead5d58c0bb65b976d65677f31b575097666914769e1b32229cf24ca77f3d181991fc453ebe1efbd0e4e38e1ad0a3d384babb208d23ef65a7bb3aab3750452ba9a330ffe5b1831db1d70e6acbf7374cf5d2562ebe3443f683ffe4026b4d81ae3efe0bf602cf5ddb48efe9d92e99ef7aad12641c3e2177e06923230ac05c882f1576383cea863faeca0848fc5bc400d5b7476d53228e808055ef5c4c8900ff64b8b788bcdb3358061c02dec97f9ecfd0783e0464e5ae47722a178f445c1fd32f7e7073d4f5d3f9d0a2231e9716c20b03b497a735305103434822bbbc5c71ab1014845c7ec1982a54d75253ca13648d635d4e89e9bd00b34ca4c96862d9e2cbb12466544fec149d62fab477ccc5a594fce7774b8666fa194952f1852805a45fbb7e4907cb71a0529c85a5130ab5cc5d23ad05c3f6aafa24edf0c113edeebcb7158192b36d1859b9f75dbad33fb285eba648dfb471d42105cf633eae9f6595d2083099b19c015def42253be5e92401656c9ac02c0fe4bb45a840ed9b0a459b211971dd0dff41957a1c8dd73a69447fbbed188496b3282ff05d96675e12c541df91b50ea24c1b9d81e826baedf191da81f956c271277f05b3a05bbbe7d625911a9c7c30282afd3b22b15df262dd09099342e9438f91790faba9d2d81a88f3ebb9f4929b114b8280d21f7159bc5bb3048aa257a4033ba986e36cf2968b7b09a3adb2359de8a18f55eb752e7ea59e4d2b65f3cb5d250923a5efb7a56cc29e24da3323aae926b75ba144f1155374af4a59f41fa7f1b654f9a07675e33297dfd80732e6a16a1a516522948a86a3cb1980d68201b492cf632116ed2aff880155c4b510ac1c28d690af814cd0489598e884e9c74d6336f804a0b2be3d7432100d74b56759198d182602e9da6b22e8d91893b5a86b0c2675f130d99c0b131553f62ad4fe6b19e3194e4abba1e8c644937135bc1ebfa38c33114de290aaa2512f44c28369f0be598224d874783c130df6feae83f3cdf5d4529b641622ae619714c0c0f19a4c9fb51970a84950a41758b001eb1c1859df1f2d3ef83df0325648e035ca1260e64f4b8a6171a6264bf839cc39d6144e90c3f6ec405444455d52573d31d09c7158847e01fdbb2f87a83d345a478992063186aba8d703a35e39ecc092f313d59b92b164956cb51b27cb901c4caf1350c84681f2753a5e181b14d8e807122329640777f249497e88f8f2d473d682eeeab9c6a061f502415da528ad19f47a825ad042264ab1a9ee35093ff63fb26391dd20cddaeaad0a2978c130a3029dcde3621cbc4471747f5300836de220f448efbb29c895e833c25350f9814ce5da39c05c0ee5ffe8fa434141648b772319ca4605dcfbdb9595916bee1d131141194330e09f20229a3d748da4917fc18d4ced20464d1d9543d4fb8c2f88e23ad1022838db91f2ef2806dc4dfb2aec32905d90b25c2af7c838a5daa839b3fb3dcb2839eb4dba12f5952a77b91205f9f6bc9d33078fb220f7a159304eb795d3dcc9720b9b13df52d6cb4fcc936b0e200530855c7da7f83f02d7c949b470e4c21cfccb14bb08535d22e3fa24af8bc000b9be891fdfadc80017e9fb7d94a395756d43699831e387c349a4628dfdc40a5020e0be515d466d9c00a43cdda81f7d8dd1768a5ec0c90ea03c7d4c5b06cc90ab75bc45e33e6ee19cc52ce80d42f924b1062184bfe4103411223e6f4b0bbb4d675db021d57c01788d5b18a6d07d485d9ccf12f9b6c563ea15d37c296e48051dd8411f23798077b87d1bd51833a4f9880ea8336ecd0f671f08082d9462f5da4fdbf8557f81b42fa59cfbce5574e00a513defc548d042d4f503959069afceb38caee86eec683317f5338655da429ac29418ac33f2d27d8e0871c4c8e1fbcabfe180a5f931402f6e1dc5bb4d18e5753a0e3dfe56120356d280710239d336af0cc46be9d27b00a46c96b12638f0ec847f67b7f0d31261532c683e393fdd0ad73c3fb5d519781224d97699c058943120d233825c30bb36e8bc13f785361762db41aab6343fdf9248feda00950496794993331abe36d16d79d05d40a8cb3bd8cc49faec5f7780d0f4906427f227e82f916ff56872913a23fb85cfbc8c294ab45510ff8481650f417bdf637d9f775db16e1ba542202bb18fc76f4ff22fa4596943d7ce2987aa00ba43334174ff536ed453273382e1e159f381ba41ac56da9f90ce2653a36814e33a02bee3fa5f556f23e7b90658e140ae1f87d172dce796943d2b31d6cf9858eb80695136065f95733b303ce89329f689123f6419d56bcc4958e752e74ed0143a77b4c4fa4c9de4bf1e147b06afe70df13d731c020f599b8f88bf5100f9a8fe86ce26e6acc73f2ead38c33e415ea996c36acb5f9b3f45238d1443e79747a62839d34c6623f3738d867906b2123ac203dd6d2bf220f424ba3a74c0e11afc41a23a770b5932a2fc482d09be99d8f089878a891269b58515c56204d82ffc9f7e1e4d8ba25cdfc8e4a19c6ebbbb48c4514bc4188ddb8bf21d0716dbe3861456562460130ec0ea4436eb5008898d36fef6f4097da451771427b219650bb8399b672bbbe4bb300a4e2879f6448027f408a2b441ffd8d34bd68a74a110f97f5fcc54a9cdd8acb69a906cca9ee46a4990db309bfa7368bbb48e656a5652fb20bb487ab350251f0242bf2ce1e8370554831d46898c3030c025967710557baf5d2bfbd97d6815a3df622549748226cdfd169be93304abb41f4467e1e04d7156370151114ffbf785f7ea94903cfcf7d4d76e52fff178dfe1b564354276054a87e44ca0f3a864d0e25d8b44122bd0c902f7517f60fa7e524b4e39fb962a4ce24d8472523e3c79085e6a0c81f7504a6277a815495d94f8b9d53a1b278cc67de805e89cbc48014ee0f0a6285e20c5e341a644e3520d5f6b50cfea9fc435e83ba08c6aedded312a64ade8200281d1efb4bab954639a4ea46f5eb6b83d67b4bcd9419cfb1258723b33ae5e50abbdf98de3eff3cfa325ebf2dadd6b5c88173783e80131a3af7c65c77cc29c7e3dcd6dc53c90dbcf442c045cfc099388f4c2848f50c306df644b7e228226f966821d064e35eec5ea4f5264f6f1b1f017326cc2c65145db612d1ea56f8a074410d158073504d8420b2f354eba0da4a0c18fe1b893225afcd6ccb6a10204226a1ffa235b40a5485d2b6e0209d50e8520f5d18e1125f5404d91a4a2163a179b6a6c2831411247fcf0f86c06df98b6ee602b404ecdd789db201cc944805a7a0f6da962ccdd9e44529219414445ed3a2c0c7d59dbbac1708f06995a705d6bff72402bae25003986057e7ae59de1c8329e9f9263433aeb549b87b4403d3a9225abee3b8874ebb17cafb45c11cf044bcf47f3da465306a37e122fdf9f6671ec9ff34c4237f959be6af54efa3c2773fd32b0f574da1b94f38ecf1fe1766ccac29ed13b4632de4e84230933b7ebdd6c52e4987d12df869e1a55a6ed369863a98000354c9507577e0941b218b2e4caa7e002ae56484945ed2ff810dbb973b58e7619d7c1d27bd92b1eebf89d26a8c497e5a7bb6c24821c9df2170495d9fef104cb39a346a4011ab9d6a86f69954dbee7984b8426e97dd5882752078f4830574ea0ca3946d66821ce0784c9691526ee64ae19fe06abf67d275845d5e2ba38ab237f9f5a4b7f881ac125918222336433f41085be54171d95aa0bb1df82684f22839fcabfb57797b70df9d43e88f808677e87c4467a6e8b86976609d1abf90dc0e8f27c492558b49ecf2f48d9b51c2d69b5509e217b2c973337e243bda6f1b2336872dbdd9ee06d032b19461e8fa1c4b05037d4ded4a50923a7ca3a52cf4f6f2b4378782435deadebd1c939de6fdf06ab666cc5f45e27aa3b27d4779d03f876a6e018b9f5482ef9fc2eb18d7b1796bae028b5cd0455d0e40a8c108389f894b9ec4f1426ebbc6465767054e505cb70c6b9927ce153c1054f9d30dbf29c14065bc1d6b82bf5148b26dd412a7e851a9e206a2484ccc2b2097883607078f01bee90f01436972f96d7a7171bccd71f10ad47d504e16f1a90128d0d619bd4a800a99b18813360c59c9d63cf5090af213382eeb391ece18a294919d993fcc2b5497dafbb05b856f832c562f1347d00a3c348e7885514e1a162b5864e69d7dc63925f058a7f3297a0b5a8ea402549c52790152a8fef182f5f737eacbfd673a6942683032a28de7999cad7a0de131a85c1ac2171f40c6fc649b07892ee01951e19054584988213b8e1fcbaf790f8494f6cb88adf7d85919d42319355350c8800e84918f2f434fe41b363a8ea074c7dfca968f4408a65cb6d427d93be110888e41e6ddb123d23b60f87ed20d34920727ce93e27e01867ebb70c985988d0c8f7175a9f44e1a0e67a31fa9e34fc6134b30237bd8ae50ceaab7e1aeba370243bb890ab83b132f37fda19ee14ca0048cdec27e6314b9db6a0e6929f0b23f59a64d3912713322cea2dfb292c14ef7c4eb5988983151d0cea99b109a6071192fbf93e05953b094f8b58fe4b56988a1bdacaaa97e1f2856e79c3c8774740ce589d11f4049ae02300c2cb35138af4f30d17f362f64a1f04c1c824a5a74c3734c843713dd500a9330c819e9be5dc816970884826d84f869be27de05217185a1712257ec58159de20034132989b7c3243ffd42914bfef6c8b0f94e49a8518af4df7e8d36fa5acc23fa2a4d84805852cdaf432c0eef3d79e870e9d9458bbb3ccb2536d040421310ff6a41228c0d1bdeed200d2849328197402acaaa155086c938b2c48f410bda25e1a0fa05f485854cc66c4c95b843d7603afaaa2ff03a622603723db684d2b1d13ab4024fe4b1a05d202c82690c771d1f7def65b4d1f4a20b1dcc94e5a1ad145113536d880b3a385a36c00eaa24fd6622f2f9f148736f60113031c29d505c705b902075ac76baea8b0f9f98bdefc48c10040ce8de2231ca82d3bad54b3c9c38c841ae7ff278755432bfc482a96177aa9a47bf111de3f16d7c932aff30416805a423775ed095cc3205d61789d6e368a95436e2d43708b7962f981247ac7d29ec997d519a925d8f5929414805f63b66a143969527f259585f62ed70995e9ff33f65af29394108e49aafc39f2e6019a7baa744e6b83b4a6546a53633dc43c473ec226b727d583fb226626a31d8184208647357694f6db25193d57bc1159d68d8cee41cd1823797fc742efe634ec39ccde5dd8c6f48a1a7c2ac66ad75ef18b9c10a3e2937d0b1a546308e8278e4f074c538f1d8390be3599a842ebe3f88e38a6b1ecf0262003ed7cb0c0ce4c112ca579733b890183bf60742e857861255fe313cba3a304b81c64034d529ecb440829c1700d8443ae78d153fbc73e17d11b17f8e983beef18c9f63fd4a2b55a4e9f87f3411b5ac987d1d84a47a31582b183b8087bb1900878a4a6d0eb20b948e567cf2346db3b3c1358c6dbaadd7fc9864fc06e30a340726fd788746ae240f2d37c277290a35b894141555705889729efcd4a3671f05d9f7006a90e49ba2d49c7e23a62e8e067937488aa0acb90ea8c0a033267cc70990122e106abf3ae05fbe2b6a2b8166abe852918467e5baf8a5c05ce9b06500152caedb24dc5288980886bdfa42194c7dc64c78581a4a297ad93881447fe142854bfbb00a19c38192f22d7066c6f530aa7ac9471bfc32db1c810cff2cdf141af1adbddc6d14f4e43b3187cede5fe1e3440b1f0e6ce1a336173edadb5988e77628baddb80364d5a32f84eeb760eb20bf3b2e8f12889d52f78e9dc6b6d1f6cd402131d640c0fdc2aeaaf7c31ac587f5f6e915770709cc0c66a707b6e76924c6b1940b25e9787200db73408ad601896f6cfddb964aa92523584e8782ec6279fae865dceb73e22b1a643d64a12253b200acd6cda1649c5006ef6887ed60a2083a7da0b212ee348ced5ed0a0c38868eaf5a2288986747d7fd7d48a8b25dcf7a180210b204fe96ef7c16b0aa86c4a73f0a1bc252652fe3d1cf0c17b9be37d1ed2ff91f81a292c988f893f00dce3875e071c36477a870e21e7958f527a8c0f7443108c010bf2ab683e884d24f61213edcd7887a2c5479263a26db22edf2cbc724fb0ee391356321c3530e7a4802acb708e44eb4aa2aba9e4b9448de2be45d963dfd275abbcd021689c89a68133fa2726f350654edf8c5ff93b22dd508d25ff8d99740074ea92ce5b579f1dae7e3fa88ca3508fbba89de06e48b1da1e526d525eda04d303f72948022ce3cf981d62f52c9551645b9698988586700c0fe238423bfc2d7c4d53cb8243db2782583ddc3e89dffe249803f65d44c1b9292f23b37a7fcc783a1447a266f067327eef5a7604d22ffe28435437c131f409571b589ede04deb768d6642781485cde5c3d7ae08775e9867466bbcf811bd8e184795ecf8815fc38a5810809c6741c0d2b24c197e3984865efe29e56ea97fe174b414316757ffe018ecd44f7d1af3b836cde256feb079e7112c1e3b481ab86888b1f9871c393b4f4da08f371a84ebd143141e729903333aafef834bdd0e5483093de640b920085f22884a862bec525ba86d2b5a457256a80e9e45d1f072621e26d629250717bccc06c6bc8498c2b99d9344bcec00061ae0c5c023fb936d536b74f6187545f76047cdf4558257bbd746cf46f69fa9365eab8bd86e4c25e3fb84e4acc5ba1d39ffe2c4a68dfcec7aa5e95a9b997c6480a0afcfc58647ef5aeed1bec2d5536f762cbdf018d3d0c81fd16f1d766cedd082bce9ef689586ec8d4ffefbb1f0c9adfeef5fa4aa57e1b6940b503d39313001a3e34246745f8edb960112e0430058bd3f57398142c663e82878e96ad2562142c08ef5477c009a34d0234009f4ebf71b5fcb627d8afc436ffe664d74560fd133ab4fc6b11deeedcb8650c71d42e2c8534bda0bd7b63f767a2046884804bc5fcae7607c9cfa0aadbd5342156ca5ff281412765760a87f33fd69cc27a9c12a4b77b47a0f7b78b85adf0eccb1f1d9081dd75027e4efbf026df56bc6495aa8ff88b39df2a5e4f98e30ae4312800b09f570b717845497002c5aa71ad90d2ab0c313124143062412f86efb806da40d1b1347bfbc5738288866238bc81e3059f5e314c999ffe5d20bf6aa594a8d7015e0d93e05850adbe5bd22aab4097f035aba4b64e978cd75643906597888ee80874298ffe45768261810ca4a91b7091447329086aab3ed590d1f27481d03768bef9b2bf90073268da611c4100ea6b811062195a99af1b7b0bacf3f8d64251f3e85c9f8fb597775f52ffeb75691aa53453fd03c37af2bbbe7e75cc868848bb87ff24b852edeff18aee6a56a5a0fea88bf148e7437ca0152080cb6c0ead22db133422a3efc98ea8ad7fdffdcc7dcb2a7c2e07bce021152d64d18a3050a9f29ee857ec8edd89b4e29e54e67dc899c29b263e1912e0448b9ec35523e91bcb4c0c0f9273637e4636e03e8d9dec83558fef51aa66b1154e95abd2b4b9446570a7ed08822610297a5245e0a3e217596f7828bf1568f0b8ff579c0a826de0e775069b18f5216303c5f8ac07394df6fa473c975c658087ae7bf22e8fd54bd53897e8e7da54dae21c3808228543c6ea8e7cc1ee55fec39461b33cc67b2eb1d685b0df4f386ec1b7309798857ccfc97339bf12688b14b486a2b8cb8212d838015a03783ede907f8f6c6c984cc92afe481055cbc6b455d86d71fa345253af9b29088cb65e8e0e70ebc18837110244948fb24c10966cd758c2996c4a0f6545362c491465dd45ddd200fcaa4fd5269f1f93625d838ad36e996ec6a9fbb561dd5c123b9c24967207298fe3b077f469f0c63908bc03f6a46f21d7a863595114867d1c1b7edbdc03a79b13cb179cee83783404ec2ba59036bf869be8bbc696eb582cd38542141ce56250049a62dce0517f5692845c909f47ac1be4ef2ee59e313cbf83943e4bb79ebc94d0f24ac1d3855ef18188a1432ccc0ec4a75ff20cc600a959532b8b2e3373ff7f0d3ad012cbc64495e4f82346fff50903fe2f7faf94978101e77b401a56819c65d8f554fa9b9c654dfa4da6c8eab94306458b07b4cd6fb2bc85dda16dd6d091252b9d434f4707076688eded140832ca950f5c670031281038e70fbdbde3f2055f59ad1de86ec700232ebaecf4ea1ee74ec91c40a87d257e6587e55ed3eca53ca0e382c24ede865cd0c4f29228d8cc7d5a956c16d7ac662e949b9c9b75b7cf38f01b738d01da30b9634d1c255ffa5eed2224ac67facab0bd35055c00f48af6809544d56975b8ec14be3259e51213216bf785f3efe1508a3ac684de9dde768cdaeedc2da543bc4097df6940de8de11975547c9504365f9170c4cff664a11c6072a44b0551ff44d0154fc17223ff0ee22b981c3a792d866e18da3afb56894e1842442c5a14b1d72851096472e46d8f0fcc28ffae47b80c7ab9cddbb72d6f7ce03fddbfc576928d70030030221bb913d305830210d9240520ace4d887056d5fc355beebaa1b3d21314d3735ee8bd3d3e5dc992d3299877c9183fd7eeb6c79a9fdfcdf0009063689cf734fee88ba067e2aa03d7a69e518f1da3fde991e6b9691466fd5b8722a3a8c850e4e1195c1ec16c300a7d6d6a0e067bb265dbc94dc9705366f49bcfc64412111ed336e8bb2dca0728e86be28a60627412d6f4bceb672f953da55e22f2e131c491b766cb9ccf5478ef9d2408c8fc41db5d2936bc6a7ef26b7141c6547104abde83d2005e0ca2ee12cc656116e8452fd0cc6cb487dffdfe77623e94b57e04f29f180e4de62db5e856d32e65b9fba0f368d9d5a90ca7b37856c4185cae31f4b1b44fe6cce3971c710dc941d1b01733e00cb6e409269b502cb9618e2749163133ec80e9e740cd315c8476cd5677e983c0b989b7d2b575cf55da041735ba911f5a6c2a5361ff7ac569674a62a33716c721d7ebe42c4e7feb218b3f9932132b55d5d7433ad56bbdd29dc0d61f7f009ed555e5d84aead55b00dce765f7d8454672c58aef745574e0fe4ca9b9542806b852345b4f28545d5fcd556c49027ab5501edc6e13e487997225dafae3735fd85582a3d6e28fc303a6fc2ef1a0ec12484702e2aea5fd8d377c52fb6f62ae20219d65979afc4be37919d832f8860da43220629fb3723059e05d3e1b53706136d18a7f0c4862b547e786424d34fabe140fa39fef3b4f235887db77f1bb16bdb8903d11a261bb6ea3c3c1d816948064941cfec1c73c65d9a8569f775c8d72a14f17a4f67eb804127704c73ec01d5dca02edb0ec8eb9dd46a1615ef83f98235035b4d91ec3fec1f70dbcf8337e8310e8a247b2ba65562904dbb10275767f4ea97339dfa619de4204938a6550ddb76e8904a0f961efc906efd19647602c714252eb1c39184821e5662fa85cc5e1c3e0b1f06e5dccc58e8fd071fd5d9899f1b02e83f7885822726cb92efe154b379f8207cc45a3692406b20c734a6beebd60d0b92f252507fd30e0532b252046fcbab8a74597fec17ee44a7aa201eab62040fff41786a641a7ff2e85faa80b4bbba95213c0ff94039fa062753725d147a16351262a7e621118f7fc354afc12fb01d1c0da684575462f49f25eb3b0e2b1ebcf9aedd946ab220726fc2e778eada2ef5f30f9430f6b5135e81dde6ace74daaca967e17957f403cf0eee2157446d3efdb37e690265490e90afc1a049dafb32372a7b9fc9dd46360743ecc9920ca8c66449f507dbc83c8dd5f2703186f5e9fe140527270b7094252be19c3e9de509d4dca778ec634bffe02bab938a7fc1637670f181ab8afcbdacffe37bb14f16f1b1addc5b681d360021fb8da25ef7eddba8ef9516acfeb7ca445177267d3c4ff469c62af9c0a183c63cc2322199d1a19fb453c714774a702453ac851a465af871074f5a09d1eb07a75b0aa67cb808281a332e6feb4baffe98db8a1e4d5872fc67a18b64b3cee01cbf0f127f7e8599fa535881b9d3624e20c33bd30967588982e21710c4311ed6343aa6143cc79911cf4289008635c8ed045950f262bc018ffc99d09078581b40c98823a18d25a4ff74215223634b653bb5a15f871370f9e79ecd8928b0b5f3e3636c9e1842f8b2d7b72f1e197c7c636799cf065b1614f1e3e3cf2d8d9248f179e6c6cec8d2ea9485905dccdf2d77479088a00192aa51c10c06eaf0be709a1b0df4ba35c735593dbfd53c5dc5a188288358253acf656b4b81b99263cf19175aafa333e28647cd0e248ea1c41d551fce8dc3118954b149f95f35cf36d16d97ece072fe9ecfffca9f2407a58624e29a8bf391f81f0947eaccc2f994424210da4fc0d8aee92af89468c2e49f5ad7cd118e2e171de5e101b8661ed058df73e147dd6a6fb6c2bd496333f3f61a31aadf952078ef99f22a3fa9e2a16b13037a3f8c61ac17ef63e0d683d2dcc0c7197d2438ba32bfb472c666c8f060e683d2fd38d57035a2ffe7fbac156c299719858210201ad0b3d7225120b7e40727703fa6e4ba3fbc338402eabc025f4fa7f103f3eacafbae095d1c7befa6f5ea6ef0942eb646103805e1b0e115a278a37ea1a2614e3139ec231cc83c10cee214bc147c815874f10fd32029df445ac539be109cba42844d9b62e757d7033e017a13107675ba7a52ec65c096afc406c6d89c4e95397f8466a34a03ce4e405ddd5f961827b1b51482772b33f72fa6e36feee7a59aacf815d56cc503ef8c9e8b179f6c383a3019a6a645e59b5c3cafaf175d3a5acf4583a02cd435418fbe67afae3dd58f99fb78455667f9d2e704d221684b49ef4bddbc23d3aafe8ed2d904f9a75618304a80adca3ab8952a4909f8bd1a00ce25fe22f44fde9ca646b023c46d056e4a08b0e9d41babe960e84b9e0c623f3736633707fb33c0312b59b61c5b6aa0bc883f451c777c91df9a37ad768f3af7b523a457ac6591881c2f04e3f6c3c55fe29d271fb0c82ef1f8360c0e06b76f35168660fbd383c9bb180358e36958fc14252944075e0104ea7486f41d85a1424c98beae6a35c1fdb1dd1418eb280df688c72786a331e4d6ceb648d679cc87b444b052bdc7c94175538800e5a604346e0e3681cd1010405a91f684cdf96b0526c0ad36967be9a5085d76295c4a47bb5975a80ab38811a96126eb650c2ea2a636a4e9e660b6523ca61f79b72c8a66e8b15371f8514f74a47bf8c955fe917c69792bcfb309df229faf65f6241ffd04c3cc283f427c8b2d22b5dc56b618471cf5d20d168081430ebfb592fe2605b1a81f8a86f20908c3dcca8b5f2adfc184214d5357bdb5392a9502253192031eec9e165eb1509d7ee27cbc666387c397b3792137dc9baca9069d4386606c6ac4e160279133c8232fb62b90d7c4212940412903cf21a4ca5f1a2aa0eac97231e0b12e14e08930835e25702ae8e2d6a5090ad0774ecfed7aa1236da5416ea4fef8ba165be040d538fc89266a053d2e32c79487d55fa2895b752bfecac059fa82ea6c0f9e28c889a2ba81f60d6e38846f6240f1be9cbddbc77edd9adffb7abf7e79b83781f849384074575924b5469d2ab088905fc0629254626296c015a6b141f01ce06b2e000c1377985f337d03643de5a26b980b8e9ea20143b1eccdd01aa2454163d06a4e92f809d5a7576958315e63f436c492d18658923bcbe8b2409446d17818a3c510d8b606b00065443c261c96b299df66121d25c5860253b8b17f47349ec1c5944b2369f86fb761bfd8e0f944c68d8a0f2f078f6fc0cc520a6ae29f999c2f55e5c02cf7fef4093213a30297408cf21c13c6180a61019d40f7c10337035aaab7a25407d0cb26e04a4f05220512225625421491074db3f29a42a75499d76c9a07001a1ca8dc8d527906423ffc7411fab2f9f8bc8db7c54d9a7f503d70be1c5349b03a1e61348abb6a6ed7a10400a9f6e0b44d6c897fc0fb271141ed15086b8b069315e1a7276ecb4d14d160a8cb8f4500287f1bcaee742fa4ad36f128bfd78dfff526840023fe3ee07ab9b4efde7251a51c237e2fa42488b774be76c51975a378c96faee2c2a666b683b923531b10dd81e9b72e99b099e467183ee0a6da703644633166a0e439deaabbd60244202ec448ff3d5e55580676ba6a953905c7fbc9878fdcd5b81d30d0937fa1d4499027810ad05d8fb2664592c54ee62ca1f06fff692d633216b02529b3feac71a2978fd47fbc4ba1ff6b0f73b7c1ce3e92d8e8c46b05d72e9cf896c65152fa6d9d0567c1214a001d98cd7f9c72c6dd445de195eeb33403f9af240db42531ec2fa9cc286597ec34df21d26653f33eec686e4dbd3ea952a731c898b7d25e7e240892b680b22f26e82911a6a23d27b2a13dd15b982bf97fc82e0ae4006023d10fa87d2b66f485586fd35011b5dd93c2c1b965d040926cab2cd78ff13d4f4370070208d32190b9865e94235077e46d821f85025f3042d5db6878d784a2035ae94bc97e2f7dfa0c460ee80a7a87213be1e821cdf3e23d433abad8edd07ddbe0257dcf5eec608b3967c62f2eb74d73596ea0884a72699e25be76f4fff462db061724aea24566912da8a9969401ed66b2a98f8a97c0fec0047b0fbd6fc52034c8223492e15ac2f898a5c0019f6f1c084ffc0753d2c1ac77ca10b1ab7e43384e41927d8f5914b805e0fac86bba8de002e79c468fdfa85013d7426b992025adf15c79155afdefa89885ed775e08bad7973a8813a4affcb4d89d4232b19a42d94b5521f6e1407d44765438d130c7514b0f1518826fb02925d118585c609cc070cae5ccf140cd1ecaaa773ae07bae4af6806625863d1530d1abf5106db8db5f002794208fc0e4a2c559eb6b3c09aff1acd390134f3033685f362337cb81189fc889a896d2424da5d380d05568fa29d76f54a285917cd956fe635e8b0f5f53fa4f838f770bf14902203dc468b54fc691e8f22033e52e238f715da40f9d59a7147ed391f3cea96bc4ce9b114e2b16b5d346f6694bfd25aa5b9fc775c0c6d3b720f56cabe31bfee3daa5107a56f8fb9ccf32621d19f3b3d1e319f917a8b7763319517261f684e85a8d3558852b74254db15a2448190466f0ebae2788ec2c38f2b5eaa7ff4a9e70c83105f182a78dacb24b70d4fe58cea9d33176f8c91333351ce654cae68d94709a6bf2552c0559c078d8896d33e953340a19f0a188821fdd82147d692b3065fcbd1680fe0d5a7087cc299b3834e2c26bdea609022870140d8340a89a2ed142aa0b90e182e862edf231d2c26856f4613445d8ab0d4f882abee169e446e1cbf46cd0bd7eb27ae67c619396718aaca508dd90b4cd7a175dc97aaaee7e8c6a8eb25b50a7c4629d46ebade73dbc41061a9c7b0bba41bb96e52bfd051dd090e3ad88ba122c51e721223e5fe7ac901994be75c23fa7f4e66acc92f1d47b88cd829ba3fb055bb891e964a976eedb39b8027892478c49bcfd51a164af735984d8047f8e79b2b2cdf22492db86690545307f1a06fe4f0cf1410160d5254e8cbe6efea0d80750d98b7425e1ae3b9427cabdc66d933a332ecff45576dd202e917f63bd5a5054bfc9173cbeb4dafc80cb9493e459a60786efb1bc2fa272cfc8b0669fa9b4420a2e084db0d9e0b0c2ee732509b900d180a462d7efbad8d6b0ad966d66bb9b36e5205f0521498c86d6b6ac54e20ae194fe04624762ae9d78dbbf777a236041b7dbd05533b1495e69592fb62b66de55340a31233f7ed15eeca08be920c9ff0114210f5a1a92d4accf665cb62662f7352c12e731e1496c31224e2e4e7e5be8c6c79ec3b44bf84e1f0c623b36dc643ddc34a49174de5e37742161a00f0c7bf3b808a75cdba8e2556791481527e76580a4d6dd9936c8bbaacaaba12d565ffce6ec84bcc5f482fa82947be54516a05ebcd201b662574bb92264fcc75662f3644ab3cd653a442b54112749f62099d0d5f176f52697e79e589018c7bce2717e637b064f73c2f54b05a39a86303d9d7ce19e7dcfd42201cf1dc1ba77e9f8f6b3ae063057d24da3378dad94ca6f490c2bd43e616615915ec4a066562ed62f64a53ac9535c696608bc5b6ac51ab67748212f97b2b26d447c0b501edf35947d150c2a40b34dab605bd64e46112d6b72a82ccffe5aafd38bbdf74163c1d7b22bf16d0943fd67ef2759737ae904b1a5cf2767d5ca5abe3b646c69ef04ff3c61001229d34b0fc2b6f3556f445558541ea7e3951d8c85423d53fdfe6be11a623ea2ec909749f242f8f3e92678ae1458aa28c821d0beaf2bb76891a2daf9afabcc8d2baa4724d0c1aefd3c95ec6e111306aaf2b54a794b3ee25e7f9a4e91815c5f288bee0a1621b36e9f52999057e8bf1e64e744c64a356d24c6308868d982b32144202f19ffd40044304dd9eb1c21cf845304f49ebaddbc730c6aaffa83fb538daab0f1aca27e577a39660d2915b61a0afc6bc262d76a1110338bf1a1fdb7e7a54e13ed1d2f082025692b1649f66c98550c03f4132c9439a8269ed9547f6b5bb234d05ac404d460b787b2ee1a41675c7cd69e25158983fb37146ab0e292dc70422a3bceff498acbb9261cee1769ede865a65ed75273f71fad255f0ee75149ff188452896667492ef42d9397de441bedf36c15e6103e321a2b0839499a851cd900aba4dcd8c225666562e6ba7d996c166f2d43c1256fbcf16ae1cc5caeabb7b17b684b7ec5e5a75f5f5e87c373b45827e954abd9508fcb8b92da0f840cf43d9b483d5127246ec927c9b347132a631ac27dfdbfd4ee3912bb285e1bb432e2247e736ba51cf152b947b4796c5562fa56738a16c8a2885b2370a2c68bae0210d80ab9300e31139c3aaed47b9fd2dc37aac0e94f462d6d9c448f64b22e62edcae1d8f0abaaec4e19dd91cf853f40ff3b2a8d9666a8839926d853e50dd7d76b1937d9db060226dda09529b29d3d32c14ac0b1c24d2e49ec407cbc79f58723740e0ce59cf7b0d3db8d264d64bdfc8040ddb8bab7b653e755a4f019e4faab3f0a1e530826b21921e8b41823fec634108b86045b19f7ef23c6ea7d5f8631fb4dbf4924bc0b57f176cffda09a99caeec6fe9525b7f64f9209c01038f515a70aa2be42cac71d6b3d07de16ecc18eafb501cabf9c0bb78497273def49ba034d211953656147ab5ff0d846b04be0ec4460d551050aa8a3a10be1f26950ae718d4f4fd306e71ba234ff3be17c71dbefadeb3d058285f74d0f5879bbf0479761e4b39529c057d1960645dd934a721c67388e261e4457e2b8a27018792682684bbedbdf7de52ca2465fb0c2e0d0c0d36f0cec8a74d878e10a3c6277f9b0e1d01c697446ed974e808151c65d3a6434718f95290efa64347f8d4bc70046d88c81098519b0a35a18c7d371d2212b4cf55d015e418e4182a1edb76ecd6524a314e0286a12856f15d349a7d790c55e62b1e5bb4a540acfd948ae413ff6c7f91fcd1126b98664d2abb8935d176e64dfa90d9a2ed885cbf7402e1214989bd6b49298df5de1dbf090e24db9f34c5cd93cd92b5271539ec9e7246827a80481e5c95eda71b89ca4937da20ec2c3705e8862fcedf83d9279b351d8abffca3dca49c703ee504e4544ede2d5de77df503c5da0f18fc5e75426bbf18421aef437b827deb0796d4fbeec5f82fe94366874094063f0b2ed73ff10c8e8df15fdcc906b1f13b14374db6198de9725582a8004dc16dfca6dbc6b8146b63f8051cbb6eb14f90880ea4511a7f4f7f392a40375b86401fcc36d9acf945f197bf94db141c95afca76e7c989351faaed951fd1e9d6fba3573d6a4be7905d6dca9622430c7ac3da96bb52eeaa370646b8d64c7bb8cdad5d67f665bffad8a7e6db97cdf1c15df65e31e88d1f787097cd9a358a4f19c45fb68714db007c40b0554b2029c83c5833e5d6fdcf9cefadcb295c5916b67fa5e657f6b02ffb5e9eee247bcbac3afab26fa94e877243b76d8ac6408836b67d18f2eafcf014c3119f5fd934064240a1b2bcb22c6c4a69cefb3b8841dbcdefed776b7884d144df30d5ecd65d177b9f19241034230c451259fa71e3c8f54da40f72c6b3e9b9cae069c5fa8a8ae4a9944a555518b79232a958542c2c2ca915140bcb29b3b0b098584a240b0b0bc98a711c2bc69e078260288e23a9ea5b026fb6d65a6badb50e47388a18638c3116ab78abb8eb03f1b31065bbe9576d3a72d7f7589baac8e079e6aeefc1d4cdcd6a85510ae72495fb3e05e5093efb73c145d95f2a687f4f004add56d8674ab7bf3f5344fbc3b233b5dbdf83157682400e3bb14f90c3749fcbd0917dba3cd91f0af639837d38810e8006ca04407305b1bfa7d4055a8e6cfc330fb1bf3755a12e60ba5d9af8eb73c17d2e3877b934311da5801c769a8e524d96f800c6ff3e754be11c764463948e62ad943321a13093cd545381e9a73403930a52379bc3ac7173c1b9e02cccaea66e2136ddaa290bd3910bae6a971ba5f9de94c592fdfde772454ddd52374a7324d6c41aa5f97059ca519aef566bb961a05aa66ee0fe52e08a88d27c1f832ba530c6d8defa3d1c570c45e7247b7f621dd6e9a058b18aa76a34d6c2f6f7ff7014acc33a5cafc54378c861b9aaba2265c433d2995366b6fd83288d25917bbcf67ea5fb3ddcb4474b52dd209f38877355a7d2eefd4c3b53918b1759e5afea71137cff981b8dd5ccb0137fc18a1c36c54d9c24856b1808d734fe71172a289b760e93722f684d1610f0c15248957d25eb3e957d3f44064a917f9fa69dc9c89a2e5c20618a36a23899420899bfe9c89a43aae0812e0ddd11424eb042e66fdaf99b8afcdb2629d754549a76eef2575991cf6cda6dcf3233af8c32703bfc91b21065ef49ee24777ddfc3dd411f755b72a7879d5b927ec4798293c3d18dee7e45d1f344f772e4e96107ff7d154ed5c4cd56cc0e6f3b1c6da7d7510d41612c46d022d44c800e9cd8a10fa4185162e433b6fb415fe1b3e802d04d79847fd23d284df87f3eb8473bb210e5fbb11065d6fdba593ebc7dcbd8ce5de1fb466d1f2272f8748f43d87468d6b3ab35329bb26bcfcf408f744bfb679486888cc2affaf0478f1c9b906b36e1684cb5c37f1a3b7df83528ad83b58e4aebdcb819a3e29a9ab135e82bfcd0859b1b16589899f953cc0815628c72eda0ca2f92cfdfe1cfb8307e79d4fd8bb577f250280d52ef511a145988b2cba6ef5dcf73273e75683674e379133bbcf7deeb3d8c35655e0646cbfcd54a60de83b1b6e4bd8b17f91c67238fc3500f23f3311ffe68b3e67ff82e391a83d111a3dbe1fd18ad43675fe1a3b40e19ad23675fe1bf0e2218ad23c8bec2c7dab5654613898528dfa75ec98ac16eefebf6cad3867d4b1d4069649897799897f93f7500591327e7aff08bb24b46c78aec2b0412dbd957f82c48e499c3a8141df862ec8546b92616c7837d8e3d4164a8910432d4d0ed734c3256ec5d1d40330eb34cecd0d301e4b01327e7e1e41ce63d8dddf14f9c3374c60f7f6ce9e7d13fc318079447f8ae8342693c8fc4794269c21f71cca03cb00f36e230b108b97fd09d9ff529528adcf9f608bae7e5ed89deff10d4dcb121b171722149b49ba4e9c6dabed4b6f7f2b33d95cff65e0caa3a9ec5f65e35a3b1d3f64e15cff654b4ed919e9ae38cbec2f7bc21a8a903c85fe18748ecff1354cd36b82be989413456829dfe1487c4dc16ff14755b3c4db32d3ed116df14658b4f53a2288aa817c5af093205b145510790289e48a7d9e9f4208da9fef4218db1fce961fe24f3a7d69f4e6fc40eb5d8a797d130baa5774e2800b580a465e68b9619a01d3e91a7519af02db903e467f615bec6c11804a5095f558e334a13bea7bf7d6770720138a2316f3643c9b0bd8968c7b673db31daa1e230f15bb4d001f46dd4c776944643b901381a63d0d1d88893c3c9dd001ce1c7c969115b83d2fcf0740f8e020a29a4d0e330b164794fa56cf1c37fb1c697791adbd198e78d254b01ad8ff91a605ea63c81ccc37c4c79022641641ee64f10f3ada7326bca943c451040d67a5a95c1c494b42a6bc576b5bc67ad15c902d27aba83b80ce62ff9c343edf127ffff3f49d4301ab42fef5b9a85287f9bfe0a994aada45e5e5e500072578882939d24384fdc153ec6a3388e226914ef288ef7ded84efcd38e4d355e9212597c92122b28c8226a0505383a4fe3e4f09f3839fc3a805a3bfc91a53c3f556e874f4f95183b7c14804e3a5c40cd7166c111e7098e0e2787f5cb7b3affd5a91f571ea595b4b0a7c17d4b3fb2b4b48ca813a926a826a805242d20519d502ca59ddb8eed85a459399551005a2153ac6f8fe3686a7d0f47a132e551c4634acd90bb4228ee0adf0cacc344cfe3feb4a7dd7bb510f7bd0f863486ff96e70e3fbbfa6997a77c5a0fd762f7e0fb7c4d6942af7c4a13e67678eb3dda2119b61dfeb9c38d1de2e476f82b10a13ae214ca23fc70ca0eff5c01891db6806487afc38968841d7ef805858945a0979444be4f4a229fe36c87ffe2fd8a917cdf0bda46f2a89f66d23f3b23fb1e7f1c835005d198b8c31f6734566b826a829ec761a50fff693f739829bce15aac8dd88ebe42d4df1f511a13b92bfcabb1ce5de1b738c9a8f71ef557e7e76184a138cc2b95b41ef5e1639dc3f2871f63839a3c8a626fecf04d1adc58e7580b914f5ac6163bfc9527fd389e18b7efa7c4f27411c087c65e3e74f9957d11cbf33ee97b78f82aa27dfe6c87cf727a31093a6212288ff03d19ec13057a8a4bc04ba0aef0f1bb682eb0eea1b6e85387182b10d9e1b3e873059f1dbe4a9f978b9daac9b92bac097257f82521f25913b4c347b5fea45954ca26fd129947d10eb36ee9ebafef4d9ae4aeef4b3a93638be4e96fdf6499a00dee4b7a324131fca7094a917d8a626cfc62108d911b3f06c2411be39c1734b6b2f1fff96dac9a811a4af6f7f40db2579e3ab4f07f87d584a334e13f10b2ffa90368877f8e330200ed5007108da576f83a82680ceff057a0d1d8b8c35fa1c7612cfc54ca8e221a6bd9e1ffe4dfe18f3ee18fb5f0c759e8fd16f18f3fcedcac2cd08d34230eb3390ceff0c72571d47618db559b1370b04f12386b42947d8eb53888f639b3cf71b6c3f1b6c3116782e1934631b68b49093f5614338a1de9cc74683a3e0efb3e7c9d9ac3c0272db1cf18159f7dc6aa14d9e78c12fbd4e949629f37fb8ced76687bf6a9f3b3c31e4e67c252075a060e0f5971206c988228ad8f83a030d055fb5ed1e6ae9db109f904fd5e272df6bcab1fbbcb5d4efaa079e40f25e12bf97ec42490b43fde6b020a43b196b84669fcebff084bf156b2825819f8752b014b21408c3dc8a778db2ede7cae09d7c4d7389576db45dc6d8c22dec4dc48674e97d1fe9e8528b372a836cb1e593158d0021bc4d90d9aaac827f6f99044cbde9f226e7f6fb2d1178d5c35d19644bcf9b8e4ac99a3c8be9a50580c1bd4e5c20585c5ecdc054763f8bf7701b266e974d1edef7409fabea441938e6183c228b5e6aa5257fdcf687f4f2d2e29589e3b60dcc607c03f3ea9bd2d8ae393f48a88c6c0ff3edcc199baed4f49e97d00b712d3b310e503f81ecb1e6ed2345056fad1496e2ff90366834f69188661e85d4a63ce42944532fcf12395748c147f8565ccce5ddf8f4e8ad9c54871d76744699890e3c861a76894838ac39484537c00a5a21c46a2118dd10bfaebbec9b6a24b4d91b2a592aca061b2619e7de628bad887c6c4ff3efc5e5c82c2c49abb7c688cc2c427a8ebfb13f3d8f6877d7aa8fbc43edb253e416194ba4ed714b1e630971bd4f5fd17e628ca5144693edb16bb87bacf985dcc8ed27cef397c28cdf74f84fc7da85328a2345f0dcbd385647f7f86fbfbc0d255a33446146d975109c6685b16a28c2ac1d4680c00db621f5bdb769774a69e7c826749b72d08635459209a8cb6f7348644eed2a60e0b457bbf12f83ddc151a21d7e4f87befe245c647b88a9be37b6faad1188fed792fef8104f1fe063773f7fb28b518879e378bad3c4a6657046ae699d54164e5efcbac4175507f5f860d2ad306fdd90c0e859935795e9ef5f751a0bd8074d074f8e830e2b01e7c00b7e9efeba8edb8d158cc3e65e2d85746095b47cfeb8b130522fbfecb0c1741ecfb79664d93579e602d67fb9e5a7eacde5689416fa43cdcfe9e7423ab321098939691e2aefb2d99a27d4d3b6bcae4eceb7ece3bd084f9fb2987b17cfefb2d87a9fefefd19107458cbcae5efab9050d8cb17d4756b2f4ef6fd53f5b36f6a06fb7e8ae5e3a28188336818dc65d2d77d02e8f69501118b4ab7f4cf6817a5b98fd22fcae3fe49af405df75b9a077dd99dcb9592d69bb412989779e27926199dbbee8ea994c9b9ebcae476d63451b939184d0365adfa749be0d874a88825fbcae41c766586f62993dbf7048780d0b3efcb48b14e80b5d6aabf91a492e93e4d54f67d991d8d8db353a6686c42a643b3a37d622298adc2d158e9efbbec688cf4f75fb8a03091c85d381a2361220fa8ebfef9d264dfffc1be7fbaac317261ba55aaf82b46b2099bb458c55a3116f10ecf6b2fa9a5f38fbf6edec93feebadf47c99d5c73970a472a41c6c861a70a2733c561e70e1e6e87527c00ad9d4c910ad712891cf612e4220f28ec4588baee19e20f462ef4cc8bd08c94972087cdac415df7afcc4e6627b3a3343328e95ca3349754a67294e6fe1321d7ca1da5b9a472c687d2dc97d989341afb887680bc9443d438642f601afbb27caed8f725e864f9ecfb234b5399a6b20f7f1ffe3e999d257ff887f187bd0f3116311631267fc814659bbb6ed1beacffb492179857c2c2ff859e1796de8c918c1bfbbe27534469ee7fe5d76ac194323bfbbaffb5be6e25ad52081032d657ada425b3dbf755366bbefc7dd5cd9aadbff78b948ec6be709fb9b6efb734f6a95993f5f7b38dc6c0bf3f041033d9cb9f2f41fbfe55e1f605290d90c634fb0a777dcc63c6e29a0721bd652d2d38dba02e97736a4090073aab2ffb20e836fbb246951ad931f2e9b330fc5675eecc6dd604ff0a4169ec9fa2bd4364b1eddf59d5c14457081ac369501afbe0e3609f5f6e886d73d6f46e405fa2aef2b06f3f20bcb1f2d8c82bb96df1ee1b426e9f1f88836de3c898a8ea784ea03cd2a0312f0e4a63df564919914fafcab6efe540d0cb5d2fe7daa797dbd6be23414d1fe86b56753640790c41dd080ac35f835855397db6ed1b428c71e4fad6b4b28f8eb1cfda13460e946607239785b6b08d917e20d3a11acfa6d974c80924f650edc93e6b86767581a58a4c876a4774a866b4fd6f725cf0d1446746241805511a56d6bc256b467bb093d11e084961c41438ae90ed309251d9fdf2ed8b48fe7dd620a96d3a8484b665361d3a3265d7b44063f4490550998787a83226f6c71ba47499dfb8cb6bdce550e4df34280746dc6fcf6cd7ed7ee6aa432d57eef24fad28c9675e5db7d55692c3c4c8a9245876d94f4c64ffca43c32dade93399cfb6efa082efe40ff27a16f448acc3fd3e1790ba148a23e9f3921293a9441ac5d003edb783cf3ee9614d9262f46f09b7ee7c7fc91fbe3f50e35080cb4a3f7a631c39071ac31ffef7a5af2551ddd5a27ae4b41e7963d3df00d5a9462a0f7ffa2c19ae0ee02d75b8efaea331f0bd87ba5de783dc606981c2a879ced548d5f19fcac3ff672c6993db40d2d75ce73f55c79f541efee0ff805e52929f923554592e8500213395d7e6366bdedcfea0ed4f2acfd5c56d7fb1b445f13f5d6783b47562819e541dfb850b288d3f583ed9fe28a25c5f674d2ba33f83cb681c1b9758932cbe3a9283598cd46135d4f4ebbe613d7d80a1ee7027ac2f7edd61bda0674b160c748bff836ef187ec50eb138733ef4a85ff905df53d5279dcf76ef94c46ffd1a4805c491a40fba267bdf7deeb152cd9a79486d2f8d49a958b537f79d5a432ff3386c5a4b2b3b4fd69d5f1f2852337a02eba6bf9c2111d501e61500d0997ddf7072af9a386db8660ff70d9a74dbfd297c706111b1c61d7bf1466bf923fc66dff6a6fd75dc9ba713906b82bc90bd2f51cda04df9452a323616cf75ff9ffd0911a6c7fd05324d7a643478e6cd4a6434778c81833a54f7ba0c25061766d4c10210c6ac2270cd4cb1b4560420843fcdc60907980ca20a2710185072e2037153202911bed9678c2101344a4a943810d1af2216357e5a70dd2154aace1f3059225bc0144dd40e245141df44c91041fbc6024c9c82419a67ab34072efbd9507bb6522573c04939cc8b5d25bb518e9cce9a29ba574b5ac444346a46c3a64c467b7b0e950135174b0edf750672c7af52acdf229adfa159d7a945e794fa3deb5f7df0f557d2034c6f2f5ab8dc68c682cf5f53d88c656bebef5a131d4d7b73b1af3fc5bdefd51dafb158d52e9d4b368d5af34cb8b3a58e996ffb46a5bb1ede31109db3e481adaf6c326b67d91fcc1b63f92c81fc13f472b5a231258e29fa4a197f14fb2894b72ed42fa93fc815e917f96b8d856b7945eb36cda6cf5e05ebdca876ab798fe1469db7e8b4ecd284dfeab577094c6347b29d9654664db3fa934104a533fa5ab8dd2d45fd1d588d2d447690fa234f53d6d7db4dd9d970bfb599b66dbbe499fa920b6fd923eeb49d1d8f549faac535438b68b6197b06bec1a622298685b0fdce757feacc465b8ed0b910b4b25a15153b6296e6b42194d68028713c6a125aa60b14692dc1a3f9be6d6e8d9279ee5d62062939be67647bb960b18e9cc8931f6ac08b20df9dc60562b4de4ffdf35f673ecbf7d97fd151fbcf67e5bd38918b96e7acc70b32c6c9be304dcf670c537879dadddf2b413d9be2a49ae6f1fe7fdc0c7dec7de4729d59a8b4cbf962bb19699442229494b1fd3638c31c618e3954dab26e1928e7bcc67dde126b98bbab6e622fb706dff8a71b5f41045aabac4b7f3751799fe4853a99b3a6d17371075c8751211a65314d95a6bed531f954ddd158cdbb64779a69a58dba72f6f4a29a5279b091546ae0f7ed8565b95444ca3f79223e9056e8297b88bc2f0cd5da471092b8cefbd347be5b93271edc9bf23154ba1b52c2cb0d65a7bb1cfa66006e977f2ac4591566ef7e50a290d4869407a4f4fe4faf4adb5d686faed8b521d2a0df4fbf69e9c585102b4a988739778dbf4b62b0a06b93e6d69b17fdab63c2db0238dd3df5aed22be7eadb5d6b2e80fd9b4fc2999fe19f7d68d372d7f4a06f769ffd6bff7de5acb4bbf3cebb3fc876cd73f25fb9f019a58d45db4fe905dcb217bacfaa7e4fa67e4fab40251875827d5463a73662a566badbdd65a6b512fa0d65a6b2f087e1e0bd0f6c625647c4bd05d193cd572fd6bc12872fd4c02ebe948ae0fda72b576f4f1897befbdf7de8b31c6f8de8b31c6f80b512d2d52bdf7cabcb5d6b694228bea85a554b1725cece4fbbcf7bc5f2163b81b15b39203950344e1db29d8b728587bbf5e8a6d0a2adc5b3d9d8784c6ae903b975a6b29755d2aa8c8d5e58399973da5807a9d54c01863ace3e26a227770e9e244aeaf7befd561d251227fa08044fd4006b0ae1073c4bdf7defb91eebd9ec9a462b9b064ca191558a0f9be1ce50c0f71064dc2f0323031d75533050a942036d07828da19d17870417ec15dfe2db24793fcb272a969a1850b80ab46be2de4ce0580b7c1eb82bb3c87858bd1e62df6469c03d8f79e45ca60824d5190ebcdbdf7de8b2f4eb8aa714d81e241be2dc06c2ab7b0a454f7c65a6b4dd2d54d766ce97224d747912835f2c9bcf7e6dc344a6ae47b7346cf2b91d5b6c27ebf711cfd92e148aebcac5e62644894c6688e14306d837b7ce91877ad9a05f165625a302c144834b488853c03c48380e553291ee55160e44aa9cbf3582f2e784503eee08162235b1672c7f2a85f59c79a1630c6174763335b7e0b9e61dd42eedc6721773c4b2da566584bfe000380045a1304754158481f7ea2ba1dbbc3c6aa52a895530ba4b15424d71bd27558b59f4fe4ce2d5d98c8f533896223636c226f4838248cda65fc7507932836b2b5341c12692ce95cc018c725e4fad5dd5dee5248314831aa57b71649ae7f6fb54b10abdb8bbd0f0cc59144964cf9845a49a9585a562e2fac1899178a1182116a09b1845e845c845642af8556422d422c422aa194d08a902e855684504227a12c64122a730899844a42a4104928c78c104968141285665010128542211452100a8552504148051769bc55b502ef03ebe781b43ec54f9dc8d5c3f8e299f5011945823e258a0001e5d5a643456c5e1032cba643458e1042123e2772a9e86b821237f8d250028c1be4d4a6434a586153a2887724ab985084216091c46e8b2466d0c5174b9250f2d920af6c3a9484112324c1f3d132b8e9504f15cf0999dc74a8a708e01119b5e9504f0f80d0b3832d72b8e9504f0d782905738f6d9f2b2480396716912a6a2b32762f448e1469894190cb51142c33a0add448818a0ca0161938911a3262a18d41c40a0d6a2a5a0b7a50b9146dc911a719ec56c01a1a9226329841522e0308454ba2c734c6ec441be288922d0fe10899bb99a22c411a9a956aa0c4f8a488046b8c1012c10c92720da6d4800a297f71444883a2710928464e9422894f08171ab184129e0c6aa0113d44e0a0dc07d650a9ddfd04e7fd8009bb65a0c369d07690ed0c33a89851250f1531844491bfe806c700f30b3d3570837985371d42a208fb5ced1354018caf8b9c371d42c207dbdb2191039bc8329b0e2171059882fc9b0e2141c423ca316f8049b24b557248ec4048669fb8b171da6ebab57f43d1265dd31fa426955d52a633e7e7b341525719684d96e7f997cebc512757cdb6259d3379e6ddca8a6704be475b791aa86ca5ac41c98ad75375881c56bd59c9bdb7fa74a20dfa6c836f5fe0eda4c1cf9ae5fd1099ddb1bc12c81019b52f918705322a63921f07fb02ff85300962bff442980421dfbe1026414c4fbe102641fc732904887d8142980429bd97427a944a5b0a1152635fe09b4aab3b6b64b664794f654164c922bd35815eb248254b01f6c9afa1f454662a595ec983bbc0ff615c22ff3ead8e1a595307256af0c411d4a812050632f06d91356f28c21bb736aa58c2061b32f0ad0efc92ae344b2bbdfdd25b107cd7b5665fe0931a3f21b38290eff5b641f0ed0e1fe1238f66a55cdc0b1132f02d11ad818f8ccae81ab32ea490828f36c8a281ca78a87d8de5095a9d7d81cf6201313d0d542696e76ad7607a25a6d2eeec0b7cf0ab93aa5337f84e73d8ade22ceb8a4c653b968c0184201995e93c512407441849725a1d2eb22f302c4f7c837c5a9d27fb7c36cd01c43704fb881bc71ddcc583bb6a0e94c6621a5c56752090271a221b41ed69967ff8436434c7f292a500efc10fe23df8fe602984899741c00f4b216f5ff685d8213b4056a428f9962719957d25685f162481a053190542ae7888bb051ae9cc09f6f06acda097fc415e41631f7f61dc837ddc853dcf0812e4937ce2dac6f8f1cd612746022701ffc707003efe6c8f91a031baf1e7af0f7f2de3f7079ddcf9ca1e30cba3a40f188cc34f8c1fa88f5dd79654c760d2555287b7ff0b45dbddb32062280b2af65983c551eeda7a6d0ab63f3d491bdbd23892e96791c54d87b0e8024c020b35b070620a2c8c60c1ea33088a29570389cf49147f51294ec2151531d8202803c15f812008826099c37059183cfea2fb5cedf853d18409acfba8d0a2883bf3a888c2c7cac0c861b00605445c30038b14516b726a6e726080b32f0a52c1a393bd800874f39a7080cba84d85aabcb1cf9a2eaafca0b5e9900f194d8c2718b2e02efaaf4db3cffaa73f4b869dc21e6bad359be6b8bbf4e96fada53487062babb12fbd51b06e91b0b6ba66dc4559a84e29a5b4c6734a18e4996befa5f723025795d29513ac75c213c5f02dfec08f04129ab10a6530c1b68f5316bf68dd2b49ee79a40cd804fbbea7c32d7309aeaa64d1627c6b6e1cde07da1ae47aad5fbfb5feb5de57b5efcf5acf7a25c59752a96eafd8b2e119b97ae00fd692315ceb7ebd3a0cf40d7a1b8fb652f72ed0ca5561f46b8fd3fa630513d03f478aed2e6cbf01e6f1c084eda423d9200b6eb6fc559fb4bfd6fea892fdfd7753c2fe3e7f572cb1bf2b743b9f8b4a3e5d352e5aa1e413dc04d8676a7b56b0607b7f3d253ddb53b2dbde0adc18e7e06cdd07efe7244ef6fd1513fb56e1c6be4370ddd96c7f5f9040300915d0a0dcb9d2a00a25461e2af879423ec13a44851bdb676a1ca3286aeb5e7b7c321c740936802fd6f07d8d7d855f71a83cb4dae3e60b4e019a24ffec30fcb31ad9e1c3103ee80648e100771852a230a494e29212f2efa68d3f3e9585e37f38d058dde15380c6bcfa63bad1980076f815a826485fe167215023ecf06965c20edfd3359426fccf234b174983fcf8ef9faef18a61a9439e612fc6ef5fa0b1f1e9efd4074bef9538d020f8bd3207fbfa7e88eccc3bac336bbe2bc75d83e357d699e7596df2fd58d2d50811357c72c20f73f6f7aa2db2fd132c69ac6f0b9420536191ed9f20087edf0cae1330f1f74a17ab8652c8109957be7d7d22911cbefeb6df527dbbab58929ee421b221b293ee4ebc416ff380c3498ff6577437e99d147b48800dffc3fef877e34d4919ec8f650d3d4d209694569d1cc4ae3a37884deb8fef5a077fd2879a154378b262087fc8be640c5e52ea32c1f6bf37866bc117f57d3084e1051aab3c94e6fbef77a85989a0afefbf564ba522c9ef87a822db27c525f27941ffecf72098f36af5fffde7da5f0e250eeefa3e707f35685432d299b1054976f71624def7780186194c341a13c0b6d5e52e4d73c2176008cb1ee0d537eeb2ac1a484f3e599e8009f8a432afacc9aa817cd293ca13909e2c57f665ffbe00430f771dc0c30f4b1d5832803b7c70876507ce1f76f834a89231843b2c5932843ab0642875b82f963361e9da33b8cbbe4069ec933410c7b1efd9dfa13c72b0ffcdb1660ffbb2605eb9ecb6b4870b37eeaa7fde9876de1376be7a6b7a94c4a29e66d1afb19a45573db297e0ea3e2997fd9df451ed8fd44ff7597806b3363143c6937d9f851acf71610743edfb2f388cc6000aec1474188d411444fb3e290665e8b62bb17154d95e8267de94278c410caed8d429dd1d19e90ccdab5cbff77bf3aa14f558522d9637c8f4c75a9dba7f6d18f94783d87043150a85524a4b75562a915f676e5221f8cc66b3206c435118d9f6a9958208fb24ed5a93228abd555c8994e267dbbfa14353e4b6adc1f426a7c65d54077197c5a4516c7916d0f3b008f9ccdb754d8ebb9efe5923d2b2bfbfec83be1a87c895a6aa22933392e6c15d3be4700177e11f461b6e18cb20e2631f844ca1623aada43edf4ceb7118aa6443369d56523eab154df7c9a4e9cef786521964cab5c1646d48847cd6d9d5416e709729bf3dbdadb12f12a577e0c1614fa65025533ed558b3d2eccb7eceb6db94735764994f2a250787916fff02a42621c9bf8714892204146d20f1a20a3864760813a2181cb183219081451732fb7566bfd2ec7faf5941589e893fcb0f91b5681610d5ab4a2155a6fa2132fb95a7d26a8f66d1b5a6abedc6a2a94ca5a9cc23eb6c8422e7a8c8c8f4cf9a8a7215d12816c967fefbf7ed65d977d9d77dfb2f73cdb2a58fbb73f608459ee10577dd57919173f97eadc1e83f7a7d181033777fd7c1534ddc07f09e395f90047a93eb5d8fb28071ef783d8ab1777afb6a9fb15e8371bce0f73d58defb9f0669a634f4da3fefbd77c68b5077104aed7aefdd21b5eb85a36efcb7bc5f71dda727acaa483e2fc618dbfb7ddf7d1ee4b024398c525a6718822db52c2ec8e7ddf5eefb2477c56cf06df9bcfbfb90143a2c0c3f7dedfe1e9fbe59df0fd95f79d6fd53b2f77f46f67eac967ae9b4c0064b8c7fc809ea9f92ff8c7c4be2d15b5aafbdb5de2a449f2defdbfb3561f9227bd8ab5330beafc37d92bbaae7b73cdffbf3fef7b495ca418a657bb80f3d18e3499f55b76a9f5ea66e13ab844460933d4469a8577df0944b73f69cd9202907560a8b299f50a3ca7442e55185061a2a151aaa110d954a95fa71e5c7159694aa3ceb4e8d7f1d36961475ca6898d040030d3494805a616951a560627c665ffeac566db7db55a91214141425caed76a3d1684992e4505454797860c8e52e20c5e572d9d7d00b95873f08ceccfcfc3471f2c3d2b2a24ac1c4c4c0f8cc6bad56c92aa5f09a4b4ab5abf2a9baabb26a61098ae240395506455929fd47078557e14c6edb512c5b9cb427b1d19200e16a129338e6604dbff1541d87a2a8488acaf3a3fb49e2b70b541d87022887cb49092fbebd50751c0a974b8a2110a5f39f24b799aae350e0a498f9a13f04a6a29418e9fb108da96a7d7086c6c0afef620186191c4622bffe0e3456fafa3cf8e04363a6aff961a7af5f9ba4beae7cfd4a54a338ace56b151a73f9fa3e7318ebeb3b8fc35a6fd6a323f7a94ea03afef2678d438a9b9588680a11951d509d4a850a0f760502ca01e95840752a14285eecfa4e0635abcd0c1b0e76fdfae3669dcd922cd9f5eb1254a7fefc34b1ebffe0260ef6e5cf01aa23a44a11760ffbf2ff18a8ce0d516850e3e6bb501f05aae34235eb57aad3b2e0cd71db1f68fbbbcd4daff9cbdffb2cbe20890c47b1944fa6d40aaa8565f523eb29b9d3727990fce1ed16ebc88dd423af1dbd946ec485a585880a111195d54a0a084a4b797a1320960a54a1a0aacd9a6edb9ecad9ba6aaf3cd561a39eaa364a55524ab392a2a7acfd67567f9a549f25b4284ce2888390ddae4a8924f5b8612c4f6f229e348846092f7e17de05d07fdce549fce5ef48bcc92d4f5fb2fd094ad3b2a5d7288dd33fbd16c4aa2823f4a8914f7cc6b6b4d65aa9573a4db06bf5105ca01e308dd2d807b10dfc6c36375d66b726414f787ca0d88086723a221f879d60cd61e767c4e77ba3c7cb132f41a918db52d2a65ef650a9948cbf7a1fa871ec4c695255631f4a63c24bf01237577c70131c849f380c03e11fbcc34bf010d66128380a969271126cc33f46f78c91ce9c3966dbfe582feb5ed3cd4d929109677262ca99a03cc1b33a3245c1b32794986ee2ec8933f6690a7ac2c83e4d4f4c3ad36edbbfd557470e3b4dab2a34b0914f1823236b5299fd239b8b363032ae996ed8fd3342f2847e91fdcf5ccbd384a3414ab6b75a1a5112660b96a01f1dcc16305b984c37fbb23faa91edc3e8acb93ab22ffbab158c6e7504a3db96b6b2dbd696536cfbe7ca1adb7ead304647a58dd2ac8eac0983a32ffb40b320dab6303e1b660bd216f98481d962dbd72919501afbab2337b1cd65d6e3527349e2303a04c5946d734bee0e89115647db864629a8edd3c50749947dbafc6ceb62dbf674b9b9e0b67d7c6bce61278c91ce61674a064f6c2d95fbd2a8b4d118cbb6ff9292018df9db37d198e7989d7e463a73f298edfbdf1b94d292e4ae270bd5e684f1d7a861ac477ea4c0bd3cfd17fd80f7e1f2d8a47d5bf2876fd37fa3e9c74a72d1e09bf4f7a6277fd8cdf2a0c6ffd93d8a45f20fb6c9494ad1a9ac3bfb21115c571c908a8cf1e4e14ea7206c270fd70468e7c4adb5d649270f773ad11ed84e1eae09d0143627768528d79ef1923fec9f4eaa5d0e5f15259fd5fbdac322bec802c4613606cba21f7000afe4c13ecb8d38ecac3aeb3e0ef3ed44044da9473c8ef29a9b2eabba0aa5da966c77253cf8e0e48ecf0f0e63c980df7baffcc11f5b27ede71fb27165012a2baeee7444ee5a39b3ada41f777da0da5a6d093f2df18b4564fc399c8049cbbf9439e0604d560d2fcf7a56296488eca55cbdd5768f9656b72c6f37cbaa243dc9ad87f99692f435c9a75ef5beb166294122f2597d7ad890f3001a4c3992bd942e8b770fdbc3350c33fce0fce5ef38c7551fc739ce4a719717eddca5aa227bbfabb5de27c0189d74b50d37f8b04e80276836270fe0953cf8e02efface926c1509b00b166dd8120b95afdbb80f0607f28915e812aaee2f60bab861e4ed690f34e0689c94116b298b2c6bedc6eb23c3389e67ab8fbacb320ac7f2985d4241821e548c62a5df6f5c1c0b2367197579cbb1c6f4a6314e6bda5326cbc7bf801eaded5a23a058a6c83bbfc2bae098eeeaa917f3df2af3bd6fbd75833a6861e4fc91a72624a212a1491bd943536082d83093d98c95ebe878f2f9aca60b410216c1cc919c95ebea58594a08d1e19ebc75a773b37eb16bb5ae4302a0ea3dee3546764fbd176e7719afb786dfbe36b81fc6cff203640a1b2176badad01d78e20633d2583d81f37bcf0c9b1e6cbfbbb58efdffa957df96ae5bf1be9cc49c730da2d4fab6a66947fa78e525554338795de7fc764ad1d75dd568340d8a5b24595900220cc642b4baad6b5534744f269a3f39133cbf1eb2e374ae32f1a06f782bb2530da27f84a58bea563461741a31699e5c1b7d139ec9c99b1e89726fef2075f7023132e3833ac00238825436550a0a6821ee0a00c2e94d0625cd9417edd5e4b5eb85713878d9ab654b4ddfad192cab36ed06e522b7179ab95b4a618b98bf632971c3b77b9943a8e95745d3807230a283326c85a48d16b19f99124c9d2366d925e619ff48e9b7e6dd40ea07207ce5dad97b1292245cf3f758471eac8a6894d131b20b2f5a4cc93314f9224f94eee90f8de31da50a10747e8acd97a169c740477045c8ba638a06203c586c846a5826c95c090de85a8cb66078e8688c6c8e731d3393a4ae36f23f4bdbf4d90c3fc532fb82327d190e5cc4c85990bf7a259d37e2b67fba2d9f3d5c373ec72ecc01cbb1cbb2e32f8a78d0e3c79d8cecb45107ae5e875c3416c8cd2a7aac7e47b87931d413b703b4aaa2f16996ebb6368eb1d4e28e51185510f0e6cb2d50f91d9e81c66a138cc44e4b0924c85410da3bea03aa92ea860d42f2a50f5a2b6a0b2a036a92ba85cd42d2a939a45bda22aa9555415d429aa14158afa4445529da828a84dd4256a919a4445a212a9465422ea1035882127300109461002107ce07bf041043f94400810755685a83cb5884aab47d49eaa44f5a94c5423f548add514d49f1a45b5552a6a926a45bd552cea92f7cf21b266d5e2fd7376d6acb8f7cf29b266ede2fd738c728e64b5d9f6f7429f1d04dae4d8d9d768ad2ded115bec1553fc9560a03df0321de0c00e3a9c1bc8410319c0c005beae405be06b173ae62b17ba025f71bafcba85a6c0572d748eaf4c340e5f97e899af59e8097cc542a3f0f50a2d81af379dc257253a025fadd02a7cad42dff03589767d558186c0572af4ebeb14fa015f6d5ac75729b403be46a15b5fa1d0367cfdd12b7c7d4237e06b0a340b5f91e81abed634cd57273403be1ed13bbea24007f96a44f3f8da845ec0572674cdd725340d5f7d740b5f8b68057c554203e06b121ac8d71e6df315099d80af47e89baf443402bed234ce5723f401be16a1615f89d0337ce5d1e6d721b40c5f85d0395f83d006f83ad301f8213a860742c7fe041a8617a275de04fac79740bbf024d03efe072d801f81de7911e8017c08f40bef8326c0834017e07bd03dfe035af6aff75b6ddaa45ec1dad7adc240f3f0350ced81af2fd01df8ea4473e0ab0bf40e5fc1d03a7cfd429f5f81f406be7aa173f8da02ad81af2cd019f8da4463e06174ccd715e80b7ced42975fb9d016f88ad339be6ea12bf0550b3df39589a6c0d7251a85af59681cbe62a153f87a859ec0d79b56e1ab122d81af5668d7d72a7404be26d1afaf2ad0377ca542ebf83a8586c0579b6e7d95423fe06b147a85af5068077cfdd12c7c7d42dbf035059ae62b12dd80af35bde3ab13ba86af47348faf28d00cf86a44d77c6d4207f9ca846ee1eb127a015f7d3400be16d1347c5542db7c4d422be06b8fbef98a8406f2f5088df395884ec0579a867d354223e06b11dafc4a843ec0571e9df375083dc357217400be06a165f83ad3b11fa20df040689d3f818ee1856817de041a862f8116c09340fff81ff4007e04dac78b4013e043a077de07dde341a05ff81ef4fe0fe802fc6bd993faf5ea7197bf7cc8f275e608dafe2a2af269a37bd1368f9af398f1e0d13c6c82ac493dc7ce9ad4730459f3df3f47ce9a32ef9f832887ce9a301fb45dccb1db6e4305e5f325c9c963b679780f7c079e03ef6f6364cd1ddedfe6c89a3abcffcdcc9ae7fbdfd0acb981f7bff1b1660eef7f53b3a606deffc666cd0cbcffcdcd9a1878ff1b9c352ff0fe3740d6b4c0fbdf0459b302ef7f93b32605deff46674d1cdeff86c89a1378ff9b9d3525f0fe3745d68cc0fbdf1859f386f7bf39b22604de1f6766cd07bc3f0ecd9a0e787f1c1f6bdaf0fe38356b36e0fd716cd6ace1fd716ed664c0fbe3e0ac19e4fd7180acb980f7c709b2260def8f93b3a602de1f47674d20ef8f4364cd04bc3fcece9a08787f9c226b1ee0fd718cac39c3fbe31c595386f787cdac6980f787d1ac19c3fbc37cac09c3fbc36ad6fcf1fe309b357dbc3fec66cd9df787e1acf9c2fbc380ac5980f787055953f6feb09c35f7fbc374d6ecf1fe30226b12e0fd613b6b0ee0fd6145d614c0fbc38cace9c2fbc38eaca9f3fee6cc9ab1f73769d60cc0fb9b3ed6cc797fb3664df3fd4d9b3561ef6fdeac89f3fe26ce9a37ef6f0259d3e6fdcd206b02e0fdcd9c355b787f5367cd9af73789acc9e3fdcd9d3577bcbf59644d9af7378dacc9c2fb9b47d65cc19aad9267881a878c09f9458032fdfe39346bea78ff1c1f6bbede3fa7664dd7fbe7d8aca9c2fbe7dcac99c2fbe7e0ac89c2fbe7005973e6fd7382ac99e3fd7372d62cdf3f4767cd98f77f49f27203ffadcc5b6b6dcc7661910f637faca283ac1ea614428588b0c2265bbdd5415abe550aa16e48216b219f076da3b321b2d9d914d918d91cddcc6e68373e37b51bdbcded06770374137493bbd1dd10ddec6e8a6e8c6e8e706638341c1f9c1a8e0de78683c301c209c2c9e1e870887076384538463847b0198c06f381d56036d80d868301c1826039980e4604dbc18a6046b0237366d24c1fb366dacc9b893381cc203367ea4c227367169946e6510e2dc727a79663cbb9e5e072807282727239a456d27a5de6ccec8bd4307b74120ffa05e701fd02d401fd12c401fd92db41bfe874d02f44a77ed96d40bf14e5a05f8c34a05f8e32a059330c6816ed029ae56301cdaa5540b36c14d0ac1b0e9a859b8066014940b38222a059b91b344b0701cd227a8066ed1ca05945366896510334eba806dd9a3140b7684174cb6701ba55a341b76c0ad0ad1b10ddc22540b78010a05b4107d0addc0cbaa59341b7880ca05bbb1874ab0806dd32faa15b473e34cc6c47c3d05ed0303e05d030359986b16d0d73eba1617004d0304003d0304102d0303917348c4e47c310c534cc2e001a2647c3981a06a6637074cc8d8eb1d13100d0312de8981a1d83b32f7f1e3a66878e09b22f7f1a1dc3828e5941c710d997bf0e1df3d2312e1d73a482964941cba0a06566b44c0e2d536a9918cdd3fa218098c9625e6b1e9e225e46a9cd48fa9835288e9365cc8ed2b88d0ea6a32d384c4763e4fbe7cc680c66fb7f0ef2f9bab170a3344e4344691c46bfbc7e91d12f56bfd4dc953a7add62da8801c2f61779ccb67f68a33b73aa9c01a06d1f03c9e661dbfe2eb717e41d386ff55eeb5b2bcd0ab2fad6b756add64ab75ad0028c87300512474cf2ea4f9391c9482c5ae91c22fb128b6462605662ce9a323139183127e65a90e90e9c356d74feb2d981b389b2036713c55dde45b64eaa4e73974c0cccdb954c0cccaa6e158fbb6c79e4ae54951527f94c1dd9e81ec0fad687e4035ebea5640569a1387043d6f243642dcd0ad2fad5af4a21f5c8122666b2d60f918daa2cb2fd1307bde0b6fbc7ec3e0a2e87c85d6ea3eb615b3495b57cc4ec9cd98a939c3ab226ccaffee88f604a1b9dbffc5b20751b9dcd1455934cf76983ab6b2a6bb1f44c94e114d997ff8b9e893846db6d74db45677ff965d15436d6f2efd346b7fd6d76d67c710144c612368eb0c51399bf4d9135799869814515435da4a10499bf8dce86680c232c86a49c013e359b6aaa47b3e9d39eb62f636f1b4cba5842b9e9101344a8778d5d57bbba6e369ba140566b8522801b9c37d4f424c73e29144ba5b460a9b5f5ac3fa5a12cded8f6af7d708918db7e5e2d79b2ed3f13dbb6ef62d2836dbf460bdcb69fd3638bd9b60f43916d1f8814dbbe0d388cb1ede7b0850eb67d1e9ed8f67fc03d991109a2e1743e49b61de2a209db0e7571838d71929ac34051193081062cb0adb1031e5c3184c81b49d228428c151cd1c14f153fa54bbad6bf923b4e2241310510bcf0411c3c50a30c19a5c093202409a47aa8e42b757751ce3aa5664600010014010315003028140e8ac542d1581206ba24fa14800c7da44c6c50980ba41cc77118848c31c410831030000044808488b641009c66a17cd0058ca0b8ec8e7f9d0f79b268a9f89fb5c6ab9c8aec42077a8acba80a7b02af8594706b618c35bd57b0aa645f2323ac814a2d17e7f671094ad99239ea052550b656b19b9c2d2aa6e34f70dad7d190f7e5b33ac403bef216d660792a0557ecd48aca1f43eb1865c45ce1e10082dada4a649522966806d85d55d5ae0baa3451f8d017d7fcc8dee4599935a99ebab00f6756917c334fb45f0496d9f12016ccf8cc6c3ce12c039e39a0074f5c9523aaa2895717699d36cc63d8fd8107018645ffbaf3531f2264337e29688914c44806701b25d9c9a46d6903068ae003da732c976f23185a91cdb4c1afc1621ee378fda30767551a5b0696bc32d01e94266487e0e1da2561bd6bd48111a0463fa685bca0e53a0f794066d08e18f42c50648d3251665e8777cbd140a5ceddb194bc50d346837d17a468fd8d802a6fef79ca119de5dbef55dbbb5d4bc96476b07fe2816ef6a2afe4d8aad98bbac851c3b2b01192f71264b35bcb43ab8588b4a082834750edb4306f0b0479f90951aa96d60524eb4dd9f8e59c94613b40a0fefdac368c296b776f7c74c7888807809cae287c96564f241e2aac35543fe6be631b081ed5b2bc0785a3d427bcb768e34861ae3e7439de16e8952c5f6853720911b287dcc1b865ea4a5e370f45853395bdc291908f27433931c90d19003eda27fd584d65ec4d2f432e55ae0210c5ad2355e9b71697d8b50bd9b1c6838f28cd61bd7b2a8d5b2804f6becfbdf7cbe2901a588851656db3526c353519a6aa97f1c0a553b2d754f167755b0639c19b4ef13917d37918530a56d6c10e1bea50f652ed7c0eea35eecf322e8e8c10c453e3b4f1d1e7bd3fbde4096ef9c90999baf6e94bc34cca0dfc5c8b445e9b432c9e5424ea273c426faba0994511d6814ef4656e6973264993934095b8493a5754615488449c9c641602443adbebaa2c74feeefb62e712594fd6ffa97c0db380323c8057e969359a8a9fcb8510d9a0d6469265a59b3c8aef64560357deaddbb1e891bec4cc50ec59ffb3dad55c1cf54aa55525be7f536b5f7f185d964ce3b6393d5d904af4b1f56b954a4caf386a8ecb2681dabe441095de4ff2f23b6db95923937c83a7cf6b45c272d1b3a6494c66a23da71a7bb91fa70af6dd4893750383a205b150c059af44addd2b96515c6d78acc75c24868276336ab0702d5ef2fd27dbd386d4d774fd54f36d66f515e74346708ca78676241511ad27df647d735da764432520031c8eb80fae47d6b41a51d5c2a0c0fc0325a1dc7b1da85289c02c05347009e532bbe7a96c905a7fee7f2393633e5d2a43fd02d52b8bedbbe1509cca13c65b80c4a499e250e6b191d9fe13981a854be9954ca79a8ebd7d914d2ff748c88c7d894f8f7ce9f8a85393f6fc78a00735f720a23286240c825d5cce88b27d2fa61de4fc422fe877cf03a76b1c276fc95ec2fc4b48344840f6977895082c2080838a420915bb827363826db934f742eb9097a400070293a62f85b6497dffe0fed68ee874be17ffab086f343223ec9a8ef1239bfe20adadd966f8311d07ea85bfb92e2211bad6812517f06a0cf259755627689af6158f9aa9feba22bdf567419c54dc9e5ee1bde3f2a1dbec864eaf0a8cb3b9748cd1de93bb68666d64620bc75a54b5d37a99dfaabef87b1cbf70ac32aff07021e2bfb2bad894e57e488817ab40f9230e313326ddb87a7ee7b82a0e0f3a5a1a68949eeab27fb4af19bb086a5a9fa4a69259e276645db40d8205fb1d50f52a8d4aca67d977ee18ecf3f9ccbd202e982ef46a6f63dd1655bbd097491fa5a613913d2d704a193b6232179d17699c938e0003706c993bc8bfb842693b46c33d39bab9fd6bc856325f544bcc60949b007a8c7ba4d5b63ee4402c1e5c961a957c84ccd7dbe62201fc24f99a56447a8af8799334c708dac3f15db5f2541ce77cbbd536729d4b0ef1d73cf60b5a9f9bc3276f1d0303c09a75d74bb4565294d894d17ca07903d5aa15171bfdd91e97104929439aca228a2604e0e6534497145e23ac2dd3a1e95dc1544cbe3ec303a825597014a4a5e80fd1815e39d8876200a7b7d2da90952852d14d5d39115d0a04251064cb4f5aefe456a23ddafe2e843abc28047bd3f2af1cbd49fdd65276a4e73e1243a959b78fe4d31309dd1c2ccd0f0a035569b32275969fe415f47e363004d49f35b976894e14085aeb9e637b94cc7eb7244b9b5875f760f574a320f4b0bef8096ddc1c33fb17faa071d01c6a371c51050cbd5a1964afaecc6c020134288da0d056491251351c84a61016398f43ebba88ec4d6f6fe4a19c1ad3f76f758abf53e1718a4f3ce4b51e39e53f6e99e4e969c5b7bea66999d304925aed19d08c9914558b8cba0fd26dda01c8127f17038a7d3be226d5b22e51fd947eca4b939f366818ccf3c3f465e8b9284865def0277cc9c7bc517056d151609dce681226904e3a3d1d84d8a12e9e1818e32181897e0fa175ae862aed1ebf379841d2b2598745289bc85c19ce7209a77018636e080bb54cf881b8d9112a58d9ea117ebc4c76955040d2f2451faf5786a4dde671a0688a96d5f9205be52d8aac94c594f8ee1edf8f4093edffd28c5fd4de177fe0cbe6f7f266bd10735ef00b2f0db88b6aece2e9ba6c992eef3b17d2910b56e2d224b8e8e12d1ee1968db7e5ad6ca1deb5e09b5a3a931695a3c50f67d9be59deb52c944b16bc8da53962d17f583c002cabffca63bd42b55dc13faeb4662baa6b45a2132f32f49c529259e7923418be06ca4aabb0a2c4557cc12afbafcad35285b453c134a9f4252a8a878a0f3c6577a7bc6e53e8362b388795965c4577abf84655f64e95979d0a6da5827951e90f23a8286093baf4c818bcd664e056f036424788ddc8e63be768ca25067320896c2b9140fe72055bcf8b9ba0759c0864962742dd27910260a6cd6c57b97d21c7cc6fc981ee4dd076aa9f37fe6aca3a8078f3305a1278bbfd86535a4ec2a1dcf4628b792595e37ac11dd7e6d14650a628b3846dcec7263c50d5e65de6b7d5d575c2f6f585691d0663e83dac4b0aba215dce658904eb165008018238fd835ebd447e7c267dbc489de63d4f5e752973780461e2d42f9f651f8940b27a33bdfd23db1eead93c40c3e7a4be6c46f845960995f41b80bf77bb66c8a65b7bcaad2586bd542d6a0b3c96eeb507072a083789ac73f8f6d06e9bb0ce132321dfb5ad6bdc1b7ad3be9d4bfc201f9a95ebf92dfa5bb7bd897ce36edb3bb3c917ecabb5629dbd47feea5b96f137ecbd65779af4853d35d657f357b4bf66e31ef78adfb46efa93533f1233f39323e71d1abf24d9b0b3a7547f583a785db947d0fb99543ff2165fa6cae177e505a58f768767abd2f82d7fa16a43d9fd5b9430f2be9fa0ea31b5ffe22a0ead93ef2bcb08ba3fb70ac66ed3eb553e624b6b5dc34ecd96e62c77323663b7606439632d7ae916ed3484efcc24be0c9aa278c8ca1a871408b6f06df868f844ee19cfd83ea80dd81e3d6bb4cda96d400730bbcb15ac8c9c40e8ee401c2befcdfb2883abb22b2c0f5f945ca3f10c1cd30d80b52e338a5445a6dcd0e286c738b8e39a28818969e02cbceefaa453e5f3ee615cff54925096fab76e7cb1769d3a0dc08c838cc6703641ac0f5156c82b35c104dc8854280b606cb57476615af035fd124bb71ee9753f2aac6eb514ad7a2a11ec00ca666f97be86a84b5b83c982fd538c419ad4e38441503c18180a2624691dc14ca375f5290eb0e6bc936d54b5113d55fb16775c0c2ab29eee97397c2bb85273fa13bbf444c31d390b1e434df46c770d650fbb569dd39538600c9c0495def36832587cf3c6cb4300a1bfeaf1b3f54e993a09125e1933098bcfeb8debb085fe693c1c119a132fdb504cd4260c3f2e1dad8eed864d1c30c919844442ff0b79ffb69cae448c6e5fb26fca114068eea9387767a05d3a52e4d75ef08f58b24213049631711262abb1cb638acac70ebf1d79d71c210ff2a39e52d425624db51e6093bbf34482d22548454811d8f889d54093ee035752430ec8e0eb5688e6a23d38be3e22a0b4c8fdee0bea96f1c4f1de8705cd22cdcc7b48303052d0e2c53b1cfc4b09c487828ab90da0df93c42ff564a1672b57244a04a41a632197175b001210221bb34fec8a6c1b6e2f8e7b16e5c4e7995b90d57c1a306360ecc29d5b31f79dd8056c624061c0185430660f9d7442c507406d2e88819716056a0406b3cf7198e07aa19f70f6bfb5032acfdc52f52f0f0c46339873e0dba34d6dad3b8971eb2a82f1d74af5219f3930f19db9330a8ab522758cb2d74a14a4720ccff95a8994121da3fb448eb1879153a79e643cbcfeb512c392ce3f6f5902479a84b7a513d02d14333cda25a0f08dce34faf3792733df1578b81c6eb225e65f03c3377ded650dd590cd3a8d2afe13768ea52db8d1ada0057444a09d24ceaa5d094607ef7dc4959ebd7f9050523bb57062d3a6a6729118bb9554dc7c0744baabcac8103e65f386192174a9be60956e1ca8d494286b5020f4b0cd9584acc3660ccc663da472dd104b55625d8c66bc7028f9d6e22ce92f024fc7baaac6d54ae31779643f979f4572d084a6afd999dfec808add6132d2b193e8cddd5d999a78b65889c74bdb472ab1e6aaf3a22034ee903108897712c7343c3a21f4129ae4188b9779dbc72e1bb5fa8dbee516bf6c2881be9ef8fa1c2d38fdd05d6a8f86680b7cb5551176df98e3ce7c27b23f8e760c65d72bb20bdff235321698edcddd6ec96ca8e10f7b719889fb9a21ff311a9cd8e2eff36bb0ae56a99ed64bb064cbe1a5497874593171977f25e77736f3fad71cd0e4ce79489786a58f788bad2fb77186b4ed556794a2569a3ac5a0d432165683bf2363b09fb24e0f141f542d5b04e6599d8f1052a2af6263f53d244752f05261ca2b1764283724620d4149a98e72e855a5eb426057378d69b3a76af2aadbef1a20a800f1c2f455a759755ebf499a26e908a94b159d68d065bdcf5c0b2dab4b5ef5941e8ed503bd46356947ff6315a44b1950b07872d4b648087d2034d6b47d8d81c6d271c06329d64ee311f1d556207e03d86236a737bc55ef9d2449f633ae5fcc2895d8f17e006d4b446dba74d958617f98ee6974f910bd007e41fec3e315d224616de9fc90db444eae01f1632d0da983f168aaf28ca11fc5b11fb8e6b31f749f0ea4d5f55ee3f0839c036b8886e126dde28a7f63da323d4e74041866b8a5d7dc82c755b2c6564ff693cfb8944eb2ca11b8ccecfb2eab5d8af8f61351e9f8519319e4e5d63f0f9c629e0685715dbe0b3a1a7ff8aba2e55dcfddb796aac5aca18b702d7648f07f432af531b67dcff0f814ee1106431113f66dcbaeb2576523209164434f20a5a27e470b9fd0f64f1f2cb13af04e7631a18eb8b7d043f7aec64a0d2eb871ddb1246b245aa8f2706752ebd59b9b1093c2c2db58b47cfb15394f127b1fcc471baa6557b729bdb7af310c44c4760dd164bfab80643465ba77a65d56c7aa64e759c472d909bd5ed6b17af8c1b9a8ad46b3d042df14189c0e7f4b00da3eb3a7cc4d8d6af9106f79570121d70e946e52ccd70d757c085d31e49f16dca40256697f0c05fe9a109cafde96d3209dc881bc0cb7b79329612c75291d8c0bc517f3494dcf2a351c0c6d357e2cf7d9c1bdb97b50f315bb2e47767c4aad78d3e49bcfa537e96edc4f81bb717634d013201ec17cb4066fc0621d1e942cb366fc22dfb795a071f0b79211730c0c3f3f5df089e6bd111e858ed32977e70bbf3d074db0ff71030e388efe83fc2ad33da3ac46c5a3a382f88828ada5c425a5b2c170b1a74b451d64cf27e52272500f9c442c94c840c842a72eb22474bd20fce678f212dfe8928359c101d10acf017605e4d778144813fc06400a4117bf78bc09da67302462e8b6c811fc1705d68c5150d68ebe04806190375bf5f62254734f059467ca9c70954e482ab27b2ac63bed132ab48a07f09e5e0404b5c5e2d370d0b37bb15fb46d083c7340447591be7c227a5c8efa23547f8a24941f8c73788999f29bdc5b700a8f38412f86bd3414d5b3ab9e2056b628c3d13be7a20ab69c1c4682ad08a3468db5a99346b87f72c7153a2483c9cfa85ffae760da7f6ac56271a8903e0c041ab93e08fd00f67555286fbe10ed357575119aa7afe64dba8616d23ebef18a897c5137e881883bec9a993525e4dda7f64861f71df774768a7c28205aa486cde0e79076346963c5e52874b245443990499bb82bc3882bfaad6e584e6b6d6e1f598da39036599bc075fd2eadf2bba59b1d76113be69f4db89a0d58950bd1ab0a516a79faa9fc863cf7dde3f264d48c5c5832e7d4a55a5b033e327a6683dc571508a754f5ee6f74a9d662c26edd0f9d455da4a8aab6c768a516b467dcedd030bd54b4fad0b699deb3f50da8e97aa8ea2e88d894fd5404f766480e38ea23c4a463d045b474783dbea75c161dcb18cf177c45b1fa889b94308073a82da3002c2766db63d4b828b0d26256f866dd54e41413921ff29f20de2300aed502914421e206736a889809713347960caf2ffcbd0f3bcac69f00a93150f0201042e705220d8d75217ee940b730b5aa025f99f8ba9cea5557461e8dfac6309f132fcac8fea0ee87ab9b1aa87cf009dd01326c9391d012b816eb9c0cab7f510ceac432a1483f4382b561eec140c2119f00be9387fb07b3787c1ffce305343007256aa59d014265d88914acc4c676af91bfab17c39c21525c2d5f2023aeb39909080d4b4db8ec0338ff7a0936c51e6677c4e673ede51536a129e3069bb9e2585d473832dd5dfb71368126b1e7d55e14e0079a2e0f5a1110183ac2ef928fa303a752a4739d26d62ecf94001421756551d3ef03c1b4ff779ca358f5b60821ecc34634d87f169b4ff402cd64c3af23307035e12391c97552aa7fba831388dfa8a2c9598740379b18c7e3aaee80e57de2f40e71e9505cdace4e88c0f466cecfea8b98c53066bbdcb9292075f862f2885b8795b851af805f498de4f8e9943f583a801709280a4d063a83cd7d258f4369092b03662e013a03cca9606e018c82e4fc995ee5de70c1d836ec5c164339d709c1f907b29b603ca67fad0378e80efca17af0d28c1ca46904cdc2aff5195cfe5dda31cafa406bd7bb32dba45647ea04f607627822fa11f039b718e57370e49afd429529de2cc9c1056b55395002a6035f1f886855b3cdba479fcdd0f558d60bab54b0aa46122e42fd6ec7ceb598409601603e3c9aeb4725fea4bea997848e4d663858d1349b7e90a5c659e7f88f9de9bba78b4923af5e50efa53cc496b7ea0d6276a9cd3a68e3da43dabbcb1558fff3438df7c599b60277ad43fc17f88ddaea382008f21fca2cdb3b83d4c605e60a9d3c928c3f85760c9de22149132bd93e56fdaa887c341cfc87c427620c2878008801f1609df924b4a580136c19bcd80e0476809075025d0173380513220c494f983bfa2c114648686bbb90f907698604b6a72a186294007684a096e68849a11ea8d21f7708c05430e3c043830597056eebe54a374424b3ad5a3b19447574eb1a96cd71809a6b288d8bfe6c0c1137385d10775bccb31a31c194552ca8614a99515e3abd52dc70d24f0a9ab768d30840b4eb2ff200b638031117198a66bf681e8393d8043567f0e69ba0e7bd806187739ff00012a4690d019cdaacee43d3f377124b6ee9181ebfd39c4bfa7943a0869b77de6f921567efa0f49049c187eca74e900f3d047bb666ca3c728a089965fb91884023b1950d29d2c779a67746fc98fa219a2e992020758746cec88fedc796ca4d8ed078c001495a968af1c037c6b0dd4acc7f06b808b284201e3d587a2de34174c26d88cd744b46a9a2952ab3be2fe05ab7eecc46506c5ec9bb7031c00ac942f903f3f4eb7091740651365e47805199da3ec30d27f5e23102a8e6a07e6dea135639d7ee3c6ab372e6166a2438b4a545dd565c0305da584594b5baa05aa73117e81f3c38b3d723d9cf88fe59f8796272f0bacdd23d3b49da243279bb0646a4f8323bde901a03f63e5828ebcd90343e28f7edbbae9a4bff7e7658e4f6097508d8cebac2e1e51e79abe59d29efa15e4a9aece93f49e42467cbf6564018da6cc550bb73149827e6fe640a656645c889f740833b96a05d0f7711c68027a8a04e84c266f1d1ea863304f9e6352d4463d891f5fff65e3076425c17129902a121ebfc01ba8a0c8fb9f61016012c34b5bd7667065c2a37d1a5fd8188dd86599cfb8dfa2b481a0a5322bb4c3705088f808ef05a2929e3a91e94c8a4c4ef4e49ef0ab80f2a5c2fa5115be9391cee8c396de20b657eb0130eda75146c15695a900b522e0a0f94c2648887fe3592b2f3807fce9a308502bc9bde9f1b8c3065023b67e1f118a6f9663e5b2d0383b07201ea01352c4d88f5ee44efcbf2f095c5d44979866ce4bc123e46905c253add8e3b7e18629b11f8918ac9c78cb4615a38a6729540ac8a639e3840285db335499326e902951d5623ce46040655792f47fd0da673390ebb732486f761b1e03af89ee635715758733e463cf13a6dc6210c026ebffd278188e888eabc2f7a6adaa0df9c351e2693000bf9d676915472b973533f1634c4eba31ac7dda2d80929e8c740b698013bf6a324a0bd8a0a1ca210793589210ad2c27c93e9ebca56148a10c0e63a4b0ce919b68470ddd65e0eb55a189c9a5bd843a30e00f53b439120c18abb1aa2f290b64e4789d1e313f551a905bb726deb7148268e80b6e0c098328f03ccae1e21582fcc540ba0737a43aa12b60a82bb03d0895b769439e085a505cad64f20c21a2f034ec8664e6fc9e9196a4d9b24a3a2e1228a6892f89a7467d9c20c2ba1a0773f1264ad8d3c0770f0400bff4bb4abfeb30dcb73ce3c2b2ba42530a2682ae7df185e9259b5c991f0f6c3b30dee56849869ff557bd1706ae919368e2f053662265d4d7c56851c9a6914bed2c8739864fe6852b70a011dd710a04530bf9e83b407273e2ab777df3e71ac5909962db637ff267865783e34789cd165dca042da95ac1e5fa4e3082aa327a9c703a3d047f25fd2796b3d09016e460091b0dcebf99ba60010f27a7a106b61294fdff31a9065482d522dae4b15ad660a4f21d2fbd0900231e25aa74d5b5ef50c7f48c7dcc16d5a9f9752dd846da8b50ea9f3f6a6503b375eec22e3fc4d552d2ee06b45ba060132943011a3f2e6b267ec9099085b64875f77c18e3e80efa37f05f1093ea14a56a4652765c0e118a30cc8fc549d945474c9efcdc9d781be1f584822162a0cac60d94bdabfe133f619bb22e579ad3588ba7e97b43441936caf7d3b5d0114e79a66210b5cf1c8e8fe0fa91c1f7a6273bd9d52dd6f66b3c4300d860f70653ca32ca8a5011284dd7dbd43580bb55f1700d65a9c829848425a31d2ed3d1336028302ab2bfa15ce7b6e5a2c110fcbc0d32b32dc89ab8d5e4dc64b94aa2a2788a5118cc91174758263c036585dd7da9851288a9eea8bf2fbfe114d4f1205543eefc94bfcbe4191f701b436e27edbc920dd51024e1329173dbfe95d57e8e628b53bf99eb020cf4818b1fa8c74e63b91c6ec036e5a1293deb092bcfeb2326fb5be734d6dc1662ec1850c120ec1daa7d4907f04c01eee68b6b13e65d7762f4ebd1fd42b2526be643f4c1e3b26bc51fc617d0059563514259e99ba3a583b4082e5f782ae357e731a2db7c856a83906fa24d7b648da0538dcfd507cec9945071a1a4333ff14c1e0cb258ed5f00f148ba010940f3de085d98e592f9f68e62dd12b0e3e81c412bee61eb3042030727829c984ac8e3356b5fb6a1fb23c92c039ec73bc2757abc24a64ea8eede30515710cc090fdd4fd262885d90369e1880b233434e3289305c6a6acd1d9e9c8b44a8ee6fd12c9473ec1642f6e1cf2a70928bf8c022879cae4ea2504e1c2b5ff4f0e9b7fb9acfac80f6aae3f38e3fbd0ed795fe826e71a735351df1774d350cbcdf265d8150bc4aa8119491e69fee9706ca519caa902c6b6415ce779eb5bb8f70a26a19ab91e33fff780d384f4383f931dbede65da63eb391b033b2fe8b19be9c83d7b1289c952f311369d9284415db17807d1c5e907aba5738e0868321f19d9fdcf210c1906127a772406fd9dcd02ececd75e0011613f37f56bbacc64eb6eeffc3c4ccf0bbf763655a9901d82a232d885171b324e5c4967edb7b176ecbd63d665a1a894fc648b7fbe082c044fdd923649697828d491a61856c4b39ec28d9018cd7fa07169633a27c0a5ec5076ca1cf0c37001e09f49e08f813dd2e18d8735a6282e25f7647899636b2268b1334c3ea3ccba24fd72e5da41d7fd4d8feca17caea75376fce1da046f89e0bb88cf4f2382bbb65e3b8d35be45f9dad3e19807eab56fa66bcb4e271d5235ae0948af23d137e32cf59240c885830342c39a78e03ec917c3e173b558f30990132ce2a13ad8ceebe1328864238ab5873c153cd35b057eae1006af23c2f441274d2c7585d35128c6044ea9aa48d7c6ff8b7525b99fef89e15637526e9b89083deb631233e650e20ec3fd46558bee5a67f1210dcf2c94be0b0e6bb9e8f713a94799cfde6b700877f1eb6e98bf23c7bed52ac210cd044816075eb6f3a53d3b2a9ef07a9498aa7e41ce87c1f2ae44e92220d9baf5abc0a033dd8cec05b703e9321f244105d0064aa1fb5a7d0719ff4d9f7bd38515616dea31a639d125231c679f34483c7b5df77322e99c49f6d86d7121c8c10e62f7693e91e230882ee482f61c29b0dbe4033306e96edd2cc2919366b9738de272eb2b59769f624cb4794aa8813b1b781fb2ff0b5c385b65e02eef90c1afc796068ea3b10c7739cdc4bdb2bb10206170771629f00422d7704fd9491234c6dde10b217b6b797eb966e3de2fe626ccd8347d4f9aa9ae1aea083cb41ba1f679471bd4539f01d0bc0f8e7fa8da14499bc9afa9d5392e65713d8576e129bfdcb1a59a35baa23cb1ed9a8d5a924085beae5f098d1500e9e2ebba542107437749dd411c1bc408fd2acada4f220bc8c120834c3a1b815a33005fe91e4f079bd795785d1c6471e5acec414372136a4a8b359392dd8e0c9c49bdf58b7c1dc91778d4ffa2616ab2889e349756c883a18412cac8234574cef1b2ee307b4b1e4a626d4e27421dc22841d14e7699bc4df2baf22914283168aa4d07e5f4118c44986ea3c0e0812a77aa32e6d05c4f3781abd10a7e69af7969d3f115b612de31da77f57c7b8539889ba168cfbc896dc08608cb686844c62b33751ae06e7af241da951d4c1e1c491d5895c5116e466d58cc15df03b924d7d2df31c51d6843dbd79667ea4b9d5cc0e6906160e0162900cc989f874cb1d68d20a5600d6272357058c8d3b6b56fff9f9d871f0a3e941c0ee5f91344f678996ccb640a9765979967b373cfec851c3d641862a2375fbfeefe25bd441a14cef00c160c7e54ce24fd125c756c6f8a77108e886838d1c3a8d6a54bf00308c5f794c05d7c6c527e763b072e6bb60e884b59e71842485fddebc38ad8d9e09e029db67ed0001face11a34a8e85a25052c3c46539694f5b6dd76bb9f761b5c08b7c6ac73750703f6447a5e378d53999ed4c36db00b5a1bc34954f74e43e3cc6b9c6a1c589ab4f28d6ed59e6a563fb369957de1e1bd51ff8b7c217e0e630c20868ab7d424c5f30c5cf649c5128dfa1d9724695a366df40ce0f50ccd5bbac564114b76ff3a3b14a5e56264171e64382f9ca355988577cf408c4fb622eb87aa9ac4ae1c7e6fbef1bde286292da8edbec4ece1ba325124d54260edd7d926150fb9334a6922376d4cc8ad16c0efdee58e1ac9292f9715bb6e7837b637afbff150a6f43784501c384af21a902642e630518d1c808f2e21c81ff5ca32bd9d37d45567f22effa8552e3847d2b8bd4b9e90c02820c0e9f8a64f6cdd68af9f0609cd3f045efd916e46dc8b235b16c132b63ac639ebef1b61e750729b6e8e0be7382419a11ffc85fe4bc1f680ea2a3b034505d08bc85c46f1afbf91a8c891343061f2eb4892df5a81239ce65b5937b642cc8c544cd3c48f4fc74895e7787f94fb1cf1136ecc9ce989ba7680186a850f02fdcce907e81e4f3077edf40162533b444d1337458c87bc806744a6ed21de2b4c8f6fa8d4e485fde30c7c675948e2cf452056e9604ee0979746908e69c50b92c4e874fda2d98b48f24135e5cc959c563b2d7e51d96144482c00336ce4077d262f9470459a45507384e94b338756e422dc432f7bd222604303eea22aab44092d7bd406013b236c5658929132178e7c0a5f7eaeca26497acb37f8ab3be6b0a083625370088b6b66c1427cd8655166b747abe48c651ebcb2b9c1bd532a9f7264350583540e47893520f678df040703c2a8e00f5eb9968d884ceeaa94eba7e9979c269a9e12356828263547a7072203fcba7100ed53b0d268b20d32e494bfd15277b2e9195c5611f34fb2a933260551e57b5d80281139024b2bc68ef040539d2e6e56925ca5b56651d17d3fc9867c1bd34c45592900332ce6bc645b6b9217631ebf7c693ea2b2f22253cbc8ec2c9913c0a7b84ddca63b1f0859efadd035efa708151ff9eb6da261ac5650bc88437ad4921987ba20860038b823c7ed0921f96582e9b4beeae1c6aee2bb4d41c3b321fc047ba3c3a61925f53476dcca6bf3cce1132030e679da384db97aff13368653ee09f0684060e97fb8b4b76c7a591b317d5f52da588efa1d1e6537a4f2dbc2bf4e0e85530020c536a86c65638c9a232ce801b6048d90ada09535a62749bbb3c4cb4e6b77751054329c08dc7439f8e5b364761fdcdcbe27534f7dfc59c816264025606cd597954cce172da9c5b18a2684b11e902286e93a9788cca239e6fe937577d919c035b706f554a6fc47dc48fe6bd7eed90535e49ccf2195e9f11e2ed8bbb067b0a709d2fc188ddea14ede51942c069df011935d883a36af3c0e349df875dadc67c32203d052e857e46fc8c33f5a8683444c046d1ddb4b11baa51d45a32c7bb4ff4fcc2e4afb8f1b33cbf85623c1a11bc0b918d9baeef669c6f4ed63f805cfd47b4491bf0a01b2e9374f007f00efd02df92772d3d902cd681108698e266588343610949488017d4d9db071ed8b39159010d497ec55d9614a9f7f60896a95d4b865552620237639ce425578d7d21111151f164601863fd5eb63c293431028613aa2809b1115d2ff4d78333ac654bdcc13b25af3d431fc751e9c6dda885ec009dc04aa55612e451212c1ac5d6424db0c206a1259441aea70486baf62d80fa9d88d2c12362655a3d07b1203b8dbf8f4083fae1404ff4e17b15bd88a739a9f5608e271b38f193632f46b8f9a348280630941e024ef52a564beea87aace10c7f00f5fac2f144f652d7e38a2beb7963d649d6a14bf004ae7c25f87798472786cf50e949b1bef1986e1d7e2dd2ca16edd695e99fa8ffcf7e6d3efe9ae9d262e0e731047c4972c395dd3afd00ea3b0bc812c53505d5d3f879a123166f0b2fe70642fa70b1a2e11165caa2376216f0b95920b5c1f5dd5e679dcd5f79a93b55d895bc0755b55b82c7a2688b73bd83307f1deda239ac00180c16ca5287069f9aca670945c235767e110c1f404bada730ca24ac42c6e2f518ac9868240ea5f61ae4dda66517a8b9894da15e39170a551a037a9d7a872942cee8b4ce10376557e3e8b7820a461c4ca7ac34aa8ad46caea86dd75a682504988d7a1bd80f2ffe8681463da7e8310419d6b4019c74a7198b8d9691d43cf81689a0baa0fab56d2bb5fb2551dcd866bb74c41e1519d2a3cd196b5e48bb86447d3494a593c0dceb3554781173670582b54e7ba601502f36405e66d5dfbc97782219fe606e51fd90276c6b440209bbb4f23650584721c7aadfd6dae5ada241f8a140f6c194e600f05149a03be34b5275bbb7343eefc75d8ce7819feaec0917b1f4952cc48136f536e8f148787cc0dd27f607f348881f21750aae3417ee1dfba4568bf35463028a0d00ee566b4f4c130baf8265a8540da26bacf7dea63dd28b1c3d291572af3025f123d24fb9845fc586cbe242c3db045fd7035d7c37ed740e2fd807be0861e29b9c8fd6b2c81368e9df30af877db8ba1c5bd8d8577937d0856f40da31304c5cf270abda649b65e9815d799328c0c96b07263532b1e00984c31f39ff071afad30b57da28b14ad92eaeb5980f7a1fa54202427e35ea4e4da580d8618ef3c579a73ef9226bb19c3ab58ff2b1e537618f9f008bd07abb1cd59276dada3819d1b29c74e26d8460adba11a14675f8b4751daa2d5095d78d762aec88931ac87463eb96615c4ec67c1a2307edab1f0672208b2a1f38f311b45a600532248f1fbb6743fe0cc06085f9887ed939ed951878df8d93b7adfa276422df8cbe593cb0f1941777ac9602dbe9589a1e7fc3e5038760e5e6222ead4463de2ffa7c7db77cd10882fda563cd405a278669176544a2e7eda594191017760d6a0af84472210159fb39e5ff6369e9e456aa71ca4eccbf163d8d2e52211cb76ea7fd4944b53a02d3c4d075455ea79ed9abf481d8e87b3a83fabac8f9087ef8e2098f424de3529bc496c9d01adc0061887b824e1a45ea2ca8550a2f8d3d6d751ea7261b21310d51bbac31c7871295a1c44856314a15b0225e4db67d2dd68a1a82c0ae42886809b48ea77307c23e9523dd343a19f4670f2d69260ec775c703d4279fed6cd402f57b15d2f3bb4191168af23a95f6c897be8b82cd87f1400303c3715ef8400cac5caa9d8a567b7b75e20cf62e3483dd412c57fbb4c215d1df65d94aeb90c4c85e2924a7c33a171767be4731cbd9b0a3aa4c82412b9908b1ff73c04739d10ca0474b41532d4d4f5727d3b5da9629544d580ebf974300d06287ba409638e362da790ff50faebc3d3d30d64068f0fb0bb43cfac2c98e097c832a15ec9d6e49045a570d47b5e9ee35e201f8cb0b50aa1df70a762d36564b1c18a00df5d6431fda453e0189c6e3673db4a39f07700bc4650b5cb8a02362b52f1cfb2003a4690eb918b48f84bd207132a63930dbdd8d285f027100e5d9d12992ebba732df536c96e503878cd1b838818aa23ac8d3b30fd53951548190a26425c66e350568c4c88789227d2d2c7422aa8b27b6389d6a7e9e2642b7217602ebd62f7c95361ea8167f89940a84a3248da1029b0b4334f704a056bf478f7b1d5d190a9113a6f0fc8327c43f8a46e62fda9527d2132dad2dafc0120b90b3ca80eaed51d26d110911b605098289bcecabd15d4a06bb1a97bdbf94b7455b40c58173ca3a61729e07e2d7a21e03710b88c0e3e47980ecb1bcb8aef1c512b7c8bd8c5b8c842907a3f3e2db9ab53b227a3749cd7ce04c7777b18b85a1c6f255f0f422d14ea7e744dece1d4ab20ca54fb85ada24767dcb21c4c24d7518ca35d26c07f9886336140448bd5c67e9d47e44741c62f5c15282ae3541b14bed9f79c40b11d08b77feefbaa1d5505073888a48092e7677b1af78c179007dd9adb9e77a6f3e079c019333799010eee8a5663bc37b4ba51885d2145e0bec7e2d1f47f06c2191209ddf47b9af681565a6ab3101f488792ef1c7237a9985ea49d216cf164078cb8a440e63e81db62d5b10e9c7b387a538633ab74b14096b130f2883915de6382b9c190cdc00588b48b995da2b9b76bcf5a260de124f32856fb8a85d6d25e525dbec1c1a2c4a10e1913f8c0b48432188b7b6a90a97f91f3ce62915ff0b5d61858a07b16166f2d3a203514f0d9879e27ad6cf86da55e2729004a5ae1da69c37f50d32758305473f86e71e7a2012938b1e5a6f4e60f5953d0d7e03d3267c37413e321f463e3c5d48c429d4bfeb610b29d47c8656882d1f1d890e49f120f2cf86fec0b89a6f734c0c69526078381efa82a33dd166814e2572b0d3efba8bb664a7f3c697ae7fcaa3691358a84f9a3bb31438809e3d4f54a5629245d133fd97647bfefa3066816cc2c100d2b53cf556366f5b3c736435fb4ffa676695044562a8b58c39af1e3e7176c0eb9527dc5b853a41d7857bdb3fb6a4afc27054078fab458a1d3261d9f35428de743ce1acfd2203b81a0abc2c3872c9d0759345f393798d93711078146ba976ba8a8de258276671bd80e337f4eaff6107f61e0b74850e0a36bec04bb87598c3464121264b23f09464cc3420045114d92e4763bcdf66daff6d968a95acf443232a5874df0fea7520f42176d074251077bff344ce582ad878a5b09f944073cf05423f6a4b8e94c505f52f6b448935c3c8f914d13527e5866eba78de0cff7682035b276ea2c04f126e81f3c1bc1a038afeb0b8d49d5e7e2fbc0ceedb0ae8cad1c4a4a3c22b51e28d334d4729064c87221c17679949ebccb881c6d813facf74cb146657d366886b3b8c4743612f8c7ca9a448f507ed1930d6a42e014735cb8264afec0ff9d8a0081c20f1dbf00cf57e5ad6611d13a68a959aa50b210a4167b28c4e145483ef28e20e8815beb3007216f83dc50943f5162add3486def72b7439ca6d54d20d0bdda0f3d6a1b74060515af10d51149f68c1df5b431abc9e42804472bbfa52419b151673d60228b7aee560ee3ed6eecdb404ef1e87993ac45632213954069784a8d9155e3c5911a1fd890a0a0209d94e9f3654dc9c01671508e64dea934b0ba0f13af876103e80b98d8f8e6fcd36409479a23c8e43d4d3a04624d107c291de2394378d8b81f92dbfa060db3bdea95dc2f09fc8f343aff221353cae3c896e48dc36a885c967bb7e3a555d2ad3603ecef9a9c75412fe816c704661ed403bd9fa0caaa944151d1a8c9f89d6e406ea38a2fd8c9cace2d775f078ab70ae8d4d351b7633eb97abc2cd1b06c0affa963057c600f0f0dc35d8f2cf30b151207b4546fca1bcb555465e74db5ddbd7e532cf2a4070d9a2502cbc674e65ae5e652249fb549e9590db5e8c3ac27d4a840013a1853856b7ed58078aac3716a8b8b505635209e6020fbd2274b54afde0487d0a843c6194f38ede7e47280ca4e2592e9eb22683b25ed7a8ece13b98411e7802716002438b11060f6ac36ee506db2294d06c45fdcaafe99227816b81d356b94c65dec03d651d9e147ec154a8a0d9f8b912e90d674d6c58d390be7a0a210559c474ee126f4c558f4f41ebabf6f51ce1a3a807b6ad5e38eb48804a04d74782fe5517743f5949943a253e3fc24c90f9a6fadb14daa22eb148059483359bf7f3688075951c833aabbe4bb6caf98f6c87d84c218314cc7b68641e1742830d2b829a2afd073783cb203b265d26904b613c12d2c035743adb13293067ed4b6e8b947c7aace4365545f4e2171130b2c2c9b803c82743ca9af1c31d9036c4c66908237053f7ff74629bfe03b375763d640d0fbec537ed6d0a997fb82ac060cc4da3e4419228f675e11f942079a61ce5f2bb2968f620213c08b33f55e6b4d95d097d51a29a755d9f0db7a9bc4c46c4248d2a77280be8e0a55ae0a0a6b12e2519e897739aeff14f691b50a23f2f94522309c55e0cc078acaf3b78ab797f1fb6b7d9f797c87ee452796cdad10a1ab8485b556075b2b425e0c9f0633b4c52476dee4672ec830166f985c9a792222a5cf02bf327aebe2f9891425e537d471db3c15af259e502511cde8be92456cc26f9a07767ebdcf2413c50ec9220f93ac42fafcff215a4d1a27a916ff49f76105ff4f58e319f9e2bf191164a4bed6ddf01704c7d6fa65ae873e41ceda096ed771e5f2ca1f154e2b6856438ec28649769f57d65c85e0e364ba9b7e8e5e1935446dea34b3d64e5449d995195149ccc47c5a0c06a292a72844bbda8f5bf77c7bf6988ab330633bb62963b49830db7a29b0daf36fd61928f6bcd260d74d906b1eba7f1872c04453c9de0815c82eb73ae9023979a4213130672298ab23f28f43a1c4184e21e2a33a2ab9cdd263987b65d09954e453d7d08e7a660484bc9113e1585aea823acc2f533928de7225ec1ec1aa9c904cb61cd51e69c0276de539c54a9b247f4d278efd439d0e4f76fe0a27de02a2f7b154bc0cb454fa0440b3bc0007ef1817ff45a544a0af6f4ac65c9dadc136f73b7f1bb44ba9538855ba7c7dd09d63742464ceb6957f1f3ec6fed42288c1d0079f0cb1b7cbfbaac5007efd204866266d39bfb9ed5251ee5d97c16b246ad1ad2c47c1c13a6c9a188a10db411ae4b64346de6722d428d0c26f2d42258718ce5f765a5a2796ee58650a0c16b6be89ab3af37fbd09e37d055b45ed659d92fdf3a0b48483deae1b93db73a980c9fde563ed05a6c91ca7a451f8ce441bb705591855fe73ce8b5edb1f495befbaeb81923936bfc4ecc1a65e629475afdb328d1a1a7f24047d53f08c0f5b35f3a437f37fc57457d78cc59ec7a65236f33a317598c83d8874fc6ee3cdd8a8ef7b1b9a34740d6b6da4d93188130b13b3ffb7dd9a178c6a22e324b2779e21e8517ac5f38c889143da128807e0e857f188cd37b151d17b787ba765b537df8acbfdaac396846abb4156b729bb857930df7047dcc4faf68e8675346aaebf665666a2c36eb4f6b9861a624cf50fe14cce76c11b5a0333050f0714d8d267378e8024147d97a15523a93d9063b92bca193e409ab546da2bba99a177aa35a1df3876e87d2749d76180857125de5a340043217fc0e7ea1f8ae1e6a25edbf3904f4c2952e61bc2b245098007df38d255ea9998acad12f3aed99f221c6d869a16e0c8996afec3b223a50b884be6b7c55c15780b1ae21a9836cd943fbdf6e9dd227c684302df7e55643ac5648bd750eb98d85927c5465ebc9dbf720f6acc2a064f8e659ce18a2c7a8b774a7cd8d8e4b5b50737a876105b5d8e0fa34ed5b9543814ab9d986cb20bb148fabaaad563241d21fcba89b6a8ff66c5fc0bf2890ed833308657d05de53ab50d61056fa92bd4edf9df1e33b1d894ba0bb687dc1b7e0f1ebe941853f608a69913828f525c55a24e33609cab9a30b9c4b10b909a7232fde842625381643e5cb9fde572586d7195c190f5145fa8bb852e4319b157db3b3ad5422ab23ddc5ac45a659e55617c106c4c751c3fc9ddd70dda0628d4323d56316a0ea184c4e57e8a556b717f591279a01e9e259e39ff1b177c77bcaecea8cf00cef0737278e0d45fdf360ac878f97600337fcd562a8c57ecb01a4a6c0cb117b28a8eeafcb85f229cb7f1ead995c88fc57a9df55ff6eac8ef645f765293407de00a325ffa31953f83e691560b2f0fe7a3131491e7460bacb679fb6377c377de67c55834f1821cb64fa85efccf1b57de7bf7f8a1d0226c4e74bb4a734e669edae162a2b8e3b588942da6dd8d9320724451cc7b9d21a90d8b909b2fe0b76ebebbc2e282de5d1f1afa7840f55e35c097b9ec30865c82e4147717696c743aeb9c39d37eca6fec3a85e7b9c486b0bd0c976bb29ac4fdb4887732de4b323b93fdbedba555612b0f5d11d9a1217cdbe62a15721ba04a0c17d9ed513bd8520e8dfdbf9255e8c3a849cdd537e809c06e495d3f1ac767f073efccac0fe2cb38de87123af3d81c857be794e641d626c590e350b7b13482ce11ab799130dec3da82c597a9369c09515ba1123e52b0f207145de1b4aa5e4e42223a7e0cd3647c7f5a5708bcb05970cb95a16173b0e77bd0c788f744b094699a3679b7058a668d4cda31e9418450ae24dd817aa3d354f56bf43c92e066633ab2594b9df544d4ea66ea344c29262d5e49c33d6b31925def484af5780d957270a52e46e59cddfbb3a075a2cf221ef14740e0d5cfe8d87a2c20370f4266722f2686caffc4554ddd6547292131032b5b89eaaab9786704c39525860c7e108fb871601ef28d43657b43ac499396563a09cd7f2d3a4f8994f4aa97a6e80c5878f81ee3b5de4a7a5e0b704ab525e47f1d5266e764a0943ca7a6745c6bf7f07979590d299c5572d064105ebc538809b756b30541187a0ceb47cf04e0b98ca42c3ca94297fa05cd82f1cbce341dfd14c43afad03475b0b3142dc474c603434afad90acba7bd7867e521ff2c3c4f80ed18098ae3d9ed6bbc882e174cd0c5a38136014200682f9dd17059a4080bc23a36696db10c1a0ddacebab29b66a0afde3c10b7751f01ee5cbb63f0e0ec63a16b52abab0f0e7035bd24c7e35d092a43a03ee35019a14adf32e7f1995bc00d2a982ce2153a39f068fdd4038e750cffb5fa32a885ac2f6e74df05b53fb55947e5110d02cbde46ff87a8624b71262835cfbf816b48e226916e610919706f303932458eee8c195502954f6ebc700f2b133ae702fe74ca2e244a4357f276cb510725565c1909a48a0dbff3fefe4056320fd67b2d3cbf4e5937a29f1dfe80150851d9e4c5fb0fa4ad3073768f2d7cf757ff9df7e108e2001885798c2ca024b0c0c372a7932c13d9aa46412fe4a9e2d9b3610fc1f566c59f18e367507dc6d8009b8f5ccfd97a76516170e4dc99db8e878ff537e1a659ddd44290cbbe14207ad0f8178249197bb56bda9ca0e6f40dc8e8424a100c7713677ef74dd1b9327e896300b1e7efe35e008aea78f2ab31f90a79b3cae15e651bf77116c0b991de467a365daad74daa48690011c05b7d77ee774de047174815de8c069335b6251746721a2d40e80426833e3ae1ea1f68380251fde836ab8d74ae395b70e54c0d45c340b46d1501dc6e62196ebce033c6e06d006cb1f52c5656d0641c88e30fa083e33d0be1e72811d37a1a7717456c22a43af4ac48848d08471743ea576d6202d21d06920c735954c163ffb89e8047cfc98fb48e966a3900bb07769c139422ccefd104b70bd5a9a0797a95c68dca077a2800097735a98b179041ef6b53a1c341163edb53a4ced08dc89d6afb286646a9a2df259f793e04311144848f2885aa7467060dcbdd5604233fa96a043df547be10fa12990e91527b170f5ad45d7dffbf2f02545edefc8558b9651b94e5d9475e06d025bf9f4c0f1d5c29d78d9e307cbe755f93444257467b1754c0f810791e2801ada38c87a1c1da543b852ad1303d02be43e11aa74a516e3213d1b2dd94345eae098ad90362bdb7d4d3a1a7acf5d8a644f8f47e17e529afb144d084f306e00f3a2fe3494058ab5e7d70a67fa054881ca81b2f4a52b1a7b3e923ca2c691054717782b775799483f5f293c756a4da646e1b1ab04a6d8ab5b3dfeb26728294abd82c1da26747e62b5ea03bbf3d3cd3c91e89e5a29775c3024162bdd4fd57b293359a1a18ac8fc8f5408174a1699247030029a68305ca3ce4d6a512542445fc1411253ae6ac57628cfc4703630509ac6747718d7a1a4b310f57ff100b086bac5f62f9d6a45f304e1005a236a6f7d033b546a20b3fd3d07a80657e15d865268fc60568ba7c4116e9018951e55dacf1aa567ef38a23aeadde6c02d4515bbb490e13247c3387d2878467e929f1a0e72ecb8a772ccfb800191bd4fa8ccacada5cf7451a55150b51ab9ead7f5b35dcd627fe15e9126f74c75f6510d850523995b9edd22b5ab6d8bac2739bc11fd184554ae8bee1f28f4b663e3f739b8bb4e90cea4281401ad740b1c7c816a50d2803ba3fbc17a7f75832aad76bf1cf766105f5d3081535824c48981afb293dbd58c1423ae624a03daa24bac94bb65ad5ab2f112d7e060e957890c5af8cbd0d414519734c2c026c591b8c2199ec3f44239cef5130aba751d6e30ac6349cdb8946668c984cab7ced9fa0ad5902d8586f49436c41777ce8fd654d64f9129b8cb7f6937fa05c7ed58d192fcb0848fe69a6cde2c057e2325c1e6ff7dc3e5dcddc4441c460e81aa0343688e79b650411e21f715214a3a3fb80224481584148ecfca6cb5c6d0c6b28476ee6c64bf6e4621714105127cc719f2bbabb005bbc3c5905ddaa5fb4bbfe118726555d4bc899c792653e00563eafac02e4d83a5c0c671e0d748010518f3701d74902590ed3106cd5c2c065bb30336137b821e20a30724d1a506e809b8961031bb0a890c6bf8353e2676a8f2b93653ff11affd650122786ff49232cca019e7a162b6f4a5dadf5c63f8691594cd02714bf63a2b63db05dbd01fb03fc3e4cb6b5c84b93fd898825a7f968cff109273493fcca188a639402d40e2409120e16f58e92f37185dd34db706d26a713fd89adc1b94d1eb266def9befb184560033a5de8a905e9dae6c9106c5eec050e2843847e1133e96c0428001c798845a4ee3550273da30e24b3d44cb8a118e587056413d1713aa00eba32967aaaafe5ca72214359799750f23df629f0f04f5764d2b5c28e0e69c89aea593f40669735e8541db715528b86ef9870de6359c6df127f2f95b4ecf2a0a3f3e0465ad3bedc112f0a92782c908083aea9bf7e015fb076db95adabf6c5a7f99482a5e0f88b369406a9d1561acc7299ee00a11dde8a2ccc15ce41d7a5e4838b821bd038e62f650092d180d93145921b7ef647cd02a216c795258716e28231ac26b65963344aca604fe1f433b57bdaf4df4262b3c84adc90ac7c12d8ec2929ef1a4a583bdfafe1078ded6b327b27833665e5ee535462bb7c74dbc173e921175e9e8c95bd118857750229c4bd16f49d3bbb3e50cbb111427d63d45bff20f1ccb18d221c3e8d40492ffe57a57f40a1c824106919e0a12a7ad6ec642fb0a5a9726425b21c430dafe3b5b7f9188891dfce39a3263239f297a4857edcce260acc96932c64e26b133b18b92f1a4c776f60310e18a3d40d70d8292dc9f3c4d14bc3aba6efb8041338c62b4fbcbc03a24d716a188b0a36a31aa6f3752cc57216e2cf08a45a8f567b7ea58046af2cb80e27d1117eea4bd7cfa731afb94765e36116363460bf074ebb54b2920e0c9184d3aaf61b42f3388238f6bb2f7928e5a149504067f10d82aacbedb614a5a7942f3a1a6ce99d4bd57d1f780ffc4a1b94dad7a1c015ff0816921eab0381971a562c8aec43f12f3428c7eba9ddbb6702a6f163fa4ebbc5377b3f77c6cd463606f75c498d8af8e189ebdb16245aa1db140424e2f273265995525321c7bd445a031861da3f98ca9037a81dda284592a45c261afa9589168972710dbd1936855961915221b0c8bbd6aa6640ab91b5a38d4470871967dd0648949191795d352851959740303a5a4425292c481648e41f68bb419566448b79e30341b11a133181f39211b0a381c1130ede31db073596857fd91b3eaf5cb450257644580ec6086704c5036304b1823901194118a0dc90e660a6224001aaf85a722cf1b9faec355e9056f3b6cdb306c13b73daced706c8bb1f33629875ed8b663d836665b78dbe0d91e6fe36b9204bdf06dc3b14ddcf6b0b6c3b0f79b64a1d7c333ef9ebc78d2eb47e9b3e10669f925e42b21430f0d7047a27c6952346806776f0f559ff0f05b0c2ea37d0aef809e659a8b356a17bb889a0c5158a803b0789de2866a8f6761a74a95855f126cbe7ca20b104ad44307756b8ba41e475bd854006e2069e7cdf890bc1ce9f8f092e6fc16d0871ca703e450a41f4a7e9ab8213852fe75741e34b3964a509d119b783ffe88dae66c7db2d4e626432263d76adc076b178ff2586d84e254ed33731bc7b6c311e283ab15ae2fcf68b3a8995b53ea6ce9476c31d321e0417b85b1a728e952754866d39e890848c6c8b355a2a1f4522294f4c3b17c55715c8d4eb30a38eff0f53bf60b23e320df2974530f48dc0741165cb3bc5b9ce98549f4d4d23bcd9e2a515f04e5c947b1a5330e0bf35d06e91c6f017c572bbde1b7904f62976ef158988f3248b3f12dc4d7dad22b5c0bf059ec96b640691f0f4d2eac17ff8de492d35bd7563864fa2867e934c785f9d8318de35da0af6a4d6bf916ec8bdcd23bdf85f9a4dba76b40da0707a6a1d3a475568e17363cb4eef20e13f97cd1357d1b5da6ce673aa675dc05f441cd6955be85fa2277e934cf85f9acc3a76d91f649e62b1c64fa4058e9cae60b1752ec2104a5a2d83eef68da37ecdc866132299b10c62687aa0597c0cd9bbde35bda19ac8e8a10707d1acfd47e8aa898d6ec65e5035e56ddd5a26776b4f1aa32b3cc9e6d3ce4995d464d079df40fb527cc0d3a4ea4143754fa3593d6c4fabf2bbd20e435e0940e6b0e8ad66400e9068d7df22310ceb3451e02e08a5af53efbcca7d66ce6a79f53041eb7f42c155feb48c73b158cc6c64e569e743541af88579b255cc91ec103193995a2ec96879d3a78eca91b1133bbea91c7637fa898484c471cd010e8e79c5c3933548a80c37eb5c82cc66447683c67eaa0de60b52267868a11f1d94f6a36f2d0e105c43674245a976b76fd8804c3b20b9aa99846e68e06b795113c8c616f345b625ad05df594596145d26e40a0d5e970ca89a868992d96e5ebecbf473f6d12c61280888bbb84881bcb9270d0a86af20218156366b087175f212a5313752d97b5d1ac776a4c1fe5298b6f7089d6d076a890ef7e3f5e3f4b07ffa77471adcff9e8e6a8bbce0d15db3f1df2ffe2c0c0d8a1021c99b7b89e946e7e085062a0489cf5c2acd58416e4658740c98d285a564576798ebe02b8d44bbd14f95cf3f416a6b0bb1e32f18094e33e5f7597113119d918a743743bb0504e365258c0332f4af11780fa7e258569a6cf3d71561c521381be53216340dfd3434c5b4ccdfd714467c38d72490e528cf87eacfa0accf5b8f39a60f03fbc327b7820a77d10b591824472597e768662acfd33704fcbd1110658cb18e9ac7ad6665206a727bdb691f07d4119922c155f33bb667dc02a6a3271a7fb36cd7a86321b7465ad715953def74c9ea6162539af63ddf1ece81b58ec66044bab4c1d7169cd46b33ada4bfd76d576a9f223595766c4ce2073f45f231b15e3865e5f38e54f3a1427c16a21383b4e9553319f86c6916d87f214a31edd061410b341c4dcdeb7e5ade51677eb8c34b24fa8cb4db5ec63fc7bd0f265cdafd1a5ba6765d030d5608eb47999b626928a3091316755d2a86a4008080c472dcf48bb37053783d76a5396c9ff53f9285ac4e8f916b29683ab1ad2a57621701c5d81a0e6a10eda467973f0d01f6e584f8fc2531c390c357b206b8ae5f29f3836da08d1696b756975e970423d377926bc8a21f4c9a73c7609b3749667ce36007fb92f58106bab95b526fce37262fa8fe8dd8b03ec34fd369bd2f6c2cef94c31590c0845401dbb7b4d2fc49284a21a1d227baa1c7c22e060da007e30012ec7adc1a4b886b33819cd68db91c4f28b5ad0d0393288849568ae911ed420a8120df4ac25e3cb5c1a781236fcada7b7d3646b3fab89f99836e2d6b1a56f79ada98db996e54a4f83d37dd88e1fd22e08739c7f05e688c63d420090d27e1c8822b2a53ec26a5f6c26be08c9c3ddcd4c8017433a3b6044204a2357ae131a4b2b03742c9be9ff20b37237cdb22b5a225bd211d9ddd51f10769a77f4e73c820ea0f37f37e8fe5077a970cbb6721b935d27e345e0aa0352d3b59cdfd48d22956808c42512c917fb24c3cf87ddf630668042e1969ac188647b87f607d283228a2cc277bcd19ed9132f1b51ad0a3b22b261963985ee9df9124f5e3d17c388c40f656336f9abcb8bc2adbf596748290fb2ce9a8191b518dd6efa5af03b640bc060bc7c7e61d42e68b75b5041d881904e396958151114f3277d6eb91613d46aa800be476e17a02e346524ecdc08c357a0efa96d3e1567a58d7fa85b0ba39bb23b37e357b738082706178fb36e23cf072d904b25c9b7cfd775049e222ae60037d960ceaf68d2f089e451d031636e0f22ef5b3dd85be8d045213946d5790fd73cbe0bab2d27a9b3a283f3120fa6cdd2d4a9f5e394e69650f53cd5f8d2d2928a60164a6e0fb24af5242ddf286528715b4a3de2424ed90856bca94c83f7112336f99089204087be22b8937e946c5a8435650415db496796bba95cd04f631f6dfce1e0e01ce88fbecaef0b8faba0fcf781b36ecd1fbeb1b0ff14433ccbb12413d2a7a6acfcf723d6652f9a656d6fc49ac33fe80b320491fcc3c7e6c07b583ca7caa51c4487e04dacecd9830e8e8291131c5ec37913c8c71b242ce396487478e6ba435ed06144948e4ebdc99a5b476aee681017d7213d5f871f263b7e6653673320101fc5b3b31639906cfc016d3d7f430c96f7c848fc7e3589a043de681e02f3bbdb5e2da27e66aaba912f06d695eb4695cb8027cd23f0cce647a0c94c1952e627d0761981d15773d3dd607513d409e7190509f133db2ee74d854f6cac3acdb6e388b3d4f13502351ad37b85a59a405d744db3f09666ae07ea84b4c36b9f0822ea2214d77a9acc642c50d739e91901c98c322f206f9b9c5f2550d7806733085af2bae05fd2f32219898299c28bf043be0252ef3a9ee56272a3e96db40d55d5a1af3bced7e5689b9903ea4ecf7d2cd3991e880227ba955b3880627f3375f1d4885cdb92f2bcf3c90425ada1c81ce810797161c1138dd272b483af1b918fa61fecb452d2a9ef14a91de8d8576af86075a135690ca5933b575f7382482478300c2b9a67c4d8837720f7324e958fbdc1e41e2ff16edaa562a7e8d10d84b6a22c2c7da02af692723fbf3d5dbf55c05c9e13bc025aae6f3377c6de5920788621dc2d8ae7c2770f86ad788b123d5304e3e41047d1b4fc693d49d14bae7617fb41b1af5a5336727226030e82ff76ad00b0e7e60bd366e0ede5d6dd2243b3980e2da44b4041a3624edbe14fa7a8219b942c834cfe02d041422896f9036e1570de396a1b734a7a9d5287f4df0d0e3e5e425f2fa4f419987bbcd074f970d4147d3e41b4f3cb6fec223ca06d83a72e12e18f69fc879365c3e26eaf35d179b44204ca79b3803970c5d6def246878fc2095107d13bc1daaf860059bddcfcdbf503cf2d35ae031443ce816937994a778d49fb865597067a4c10c9495b247b847944dd7fb92aa3384d61431dadd1aa2d8ae2d1364dbc4c279bb35fc10f076f5a33c6697d7655854c490665783c734d838d3ccfe8b2179ca814bbfa341fc46efd929f5cbe4610bbcad30bca0ea899b1a0676f1656baaac54eba07279dc8193f84f957725d808063aa3fb18dc3bd3f4cca749b78d3cf48a2e3e6a8d262af5c12574973b34ecb5390e3887c06be84ed00bfa540fd61f122eab23d9e5ccd2f68d59d9d9199ed12a8439726899eb25faafcb12091c9d76b64773806289db76902ae488fac1bd9fe01ac69c83efee9dce2a8b8d042c3989e4cf6841edaf3dfa9f588d3f787dfa290d60b370867df87cc93c828d4fa969195caddebdd1c0cbd59d15f238bbbc3eccb0dc7520863944d0b1b4c833528f32c07fbb07a671a6fe25cfbec0110eabfc2416c5875f93b3a533a20f39be3047738630b2d95afc398dffa7070f65b20034f14660eb7974947c80c4b8e270cd304f91b5b9c00ff3dd5d5ae785df03847f4b0e8c30f6af5310829b70b7b72bb2a1d7dbb222eef80e2b083d2c90a68023472b95f8edf07b79b8007332ab59ca2a158dd2c7e651b418260a7da4ba607eaaa22bbe07d61f2c9cd87d7dd986212c3462cf3eb0f4f3235b2ee0890af78feea5aed4fb869f5e32287341dcc6996143c67261fe90a091715043ad5ed96e0bfdd45f6d0e69a97d571564419a4d43768c76c91aa8cf028621f507d719df91e82bde8e559a472371da23bd47f1dcf4e2b0ce8106cb1bd8f7beccaaff892cdcd5fd1b1bc3d8c6ec1a4ff77895c447a2858f73297cfa580b7c728fb65eb3c72b3c54a529cd87c1311b88ca8d0b17a4c52c1ac86971cdf715cf2ea40d1f893e46480947ae2662490639bf0da2d29e3c5412cc680f500ae7b786ad61308a03f73efeedb4e8f7691eaeed45b507512dbb964c9498befdce335c0f9496047d92be2babc198c37066c8905c4bd0ed4f11137fe78f18510d13c3ae5057db10109db78c5f5ae680bec817d86810f29b2f569ee430f702d9dfc2cad0e7b9e89608342b5a015bb698975f35f75a39a18958b06d7d9ba9ba20d17795531189aa5d83cc4ec90442614d9881baab351330535a85749bd4beb59666236b46fb9a22d58662e3f7a00883728d9e3bc34abdcb010349d11c8048253540e59fcea91f458227de7c9a622082c3533d89104bc2b407c03b9e3362ff24343bf1d4f0aca672d8a3e2920a9f4ebbe01cf9380f17ec59e2c82bba8aa6c47ee71db178e199eba3d67abad00ea9ac0c2b087d7e91b4bcd047a73bdd71488ff959d9a9be4e7843d55cd94d0ab6d01adb3247554b456f864d54549185c3694bc592d193043b45afdace2f8d082aed1fe0359080aaa0a381a4c4b43716d4951bb2f836cd48fdbeed3f4e063d0327d6e3c7fb3c4c1dc6c77e5b33ebcddd9b107c300cb83543997b5cabc245b8da8e02b80443cabe44a9ae76419046885815a762c9a9740b11c4062fd86afcf2f75d7b6869117f90d4f2c2330bfa0e0948a34e6004a278bab40b89ec563bf8c301b94a8e57634f60a52646a0d59f21642b8cd1465c5c1eae6e3fcc49bc2acc5f371aff3d7b3ee825e0015fd4542a4e8cd48065fcadc32107ab5c2dcc5d63d97b2795c0f0fafc30b6e8febb23214a2c397bf039a8be44ea8eef06c3786a7ed0b458e94a3023dfdea746fe5dddb1fbfb1d6044fd0ab986d49bc11dc6035c9e128cce3e99ed549d76524e877c0499b21adb3983c2ef57e5bc79c3db2eb8b3e8c0f66b8631742b7249273bea92a9be3ccadcade1845092474b32f36b876240acc139998f7cb30031ada49171f97c2f6089a076a3f66570c3351ab8d81a26bb5d85835a71ade9848ede617dfe3c1e688db3a95d2fc39d99b33e13dac131cafe3cf84a8332d365ab1df51fecc6436f1426926774e9edae2d12e78b10eb495c201e1ae5c2cee5b4d629d06ab7ab193b60fa1ab44034e5f0b9a55457279d48cb6a6bdfe60394983bbed5f4462027c7fa4e8a06ff4ccd2256564955b2433f942becfca0c2442dd7d7e3cb0f586f925fd4508803a653577a3effb64c72297e081e806054baaa422431c70bf5a2cd646e458700b2ce5eea219769ac9481a6a9af9204eee967da60d8afd925bc89eed4b6ecba301c249d0f9c2ce697c9cb2bd31320343c24beac4df9243adc9e7fbfdfbe53703ff8e03a8fa31fc0163e718c404446085731f5234f1062aee0a8896ffd25352f197e1c9a9bf754e1928fa93b7d538edaf09b48a528d9dc03a04b0a8b44e9fa6533394977519dfd011ae3fca8cd39e2c67ce413e7e3dc0c3ae400cc547f11931cfd538b92b91f88894011779f156a3d59049a2933cd61c160c6f94fa4e785a6ec7aca2c24bb2a8da837e364404866d3a9110572d860e9e944d087b9611bde9c5a577eff40592cc8245263f49ad273f82721d25e5d0bc9d1ec7818d3bf48b084488901db8bd8565ea2ea893ad06687a9701927e17d153a15a5c0d5ef97f65315126b1adbaff8e620ac4eedb21454eb17e6164a94698451d40c1c3f822cbe9c83bcfd31c05409db7814cc3594cfd1df110a77391f1f97f329dd15df25c70acfd894150e9cfaefda397e8465d90e0726aa78fd31a99c0a4511a0a68f11eb2b493bac2bb6c931e016a9b23477bf28d939897e28938507b6a321b4c30ebdbb7638eafa8cb1d9f670f465492abd7099ebcf4e2235731b5dc734112808b472beb960ec152b1fce9ef8e0aa4b6ed0548caa54ec6d1f4b0a7291d4137ce6428e5a7232bad8b674abc73af37d406f6100a44fa1c2654a99bd679d859304575cb9f051a8ef4e4031ab46f2f4c802fb3cbd63d3c4dbbdcb7aca1920abae9423efde37dcf0fe877bd4db51243e71ce15af399951ee62acb024910a335eb3e5cdd947dd98559b63f4c4c5146784a554b6f65f7b0b4245d5351d7fda52836b4b0d814edd4d284b500ae137d49f08cec9a2aa80d26199e6f2adcdbf6edb5f1271eccc6db5e8c87542ab41f1283aaf8ad9e206b6d251562733f13c5d2207d29eab364f3db2b02a256030ba1a07c92bcdb18ff050684ddecb591bf77adfb192f0368f1f73ab7b7af63ccaa5fe45bb64e4620bd6ba4a26ea8d0ee2e6b49212d65370a79ae01d799b1831ab372e15e16bf0183acf0cf4b00a7bf6e9aa80fa6551596de18b16469973f5019c6a0445f2dd7d46277f82e6d24b52f6c3774a75ed98e77d5f94bd8efca55b2f4862d9c388eb8b3372f446bb1cc96917fadd982563105edaa6cce7b2e1b0ffb9f7061b77303e3668446ea8da544339262351b9c814b774de879946e943070a59d9a525dc1a4e2239e495c85a222954f8b96fa04b94de56b34832b841149f9c23ef5a310c7ff705c45e422b09c1d0b79d3ee19a7c69af6366aa86d92bd283a38921557c4b44d4847b3c3ca009c69087a1755e603d12f5bd062a3e6e9c09542e0b3362be2ddae7f5f2c3d1e5c8218aea20fcb17dae09b4ddddade5325c4a4eb65f6460852314db51dfc40f9417c7c79aa098d165378537384f3cf15a55861732db992e19c485cdbc6dfb9df79c7dd32c740d1046ee79247da22261157c5c3b8e8629b5993e2cebf553dc9640649be5d15f29d12c777450d2ec6b7dc15c7b5f153b2e9d6c1eb34ecb54e6d82a420a41712465bf54ab9e4ebae936ce3216ffa217a59b34b127dfc00bf34e5b46a2a67bfb6dd22cc8150dad184b86475e716a7c2a68a0818a1ff8a75d7b42b33e9762e372cd15b3717177449cfd884169dcc1767f37a6ea425bba1acdfa82c0cb7a94bfc55834c93d12dea050aea6fe22f1e0efa174a956eaeae6b931a5506561a3675da5fd013dba751bfd8c05f973247e3885932661917ba1da3ed2f2e7deb2ff2f6bcf4d777b4b22c80c5b14d5141cb986308de217e752e49ff300dc4b076d231002b1683dca1fdc16ffab7921e2dc22d3f0cd5b02e3a37250bda2b51b55537e10e6032072e0247d40318861812c3e2a58394f1a34104eb141121a12553e0d01eff35bfe57a894a45d75a9e2209e508646838410cf03811f214d149c7c0a7c4f7bdf5ecc1e77cf38f6d5c43fb0bbe6c433c60f33988ea1e1d607c9e2a8fb86064e5e4d8a526979e693b25c6f0c77dd154c9788a54ac657da748bca0589295eca61660c7edc683a140c30ec0432bba210478abd4c95d169b8edf786d156ecd1872e60b23841ef3233a0c4b8237e64199d366bdb09e7fa09ed323415384bf916ae148d80ab4fec794eb9a4f96824419f0200e9b17d422f728390d1c6c2f97ffa14f7da29179eb2dafb3ba2f3dc45f3e4ab5ad132206d73d70fa02ea83fef19c6ac85f0439636262d5d01c9535c9e11b0a80787e5c34100f0f018362639b60030b5789f64a31ef5cc64f9dce2ce031116f4107484bae82e2edb01997bc6148b42b520c80fc2268c85411c137fcfad2f69688e3b522c3776b03b70994c096ac908d5ef35083806217f4642a4e19c211ac6d72806155344242b716519feb9280cec34d33f8b30e56747cde6ac4ed7199090a634c9fe2c017a291fe68fc1badfd4ff6d0a9d7dda99b18ceead6f13e8d9849676ce1aaba4ca17623458e5fc1d873b42c736db52e1caf8dd5551071f18b230a9871584c9a8d5f152808340a3c4e1533019e47f9711ae8aa783da7d55cecc0c02d83b6ec054e1f1555bbae91ba23091cd720fef81f1ae59e93b78041f2ca8d139ce888c5a3577b921746bf29d4bd28bd5310cd09cefd42fa7d752a23b5533ecbac80d259658d522a1e48c67d4865dbe4c151676c26774d1e22bb3148e50b19f0a946a56f26d6a1965379fc64ab7d82d560bad5c3f2cb3000fe1964d6f3913ac7d3b76f668d3f10f6e41c511d57b83167321505aba9a8a874083efd7f20325c02162861405c395991f0688c2cf3041a7ba7c2e112676483d1e717c6ec467be3ee1335bc8f33b42a3cbd021b09edfcb7c6135226dadd52e37275d43e37ad9cdadb5df576cbde6165e393cc2ca8d1d27857ec564069a23e502bd4e8af10d414e43c039a53f27458201a56a503d983704ac5419466d4d085bd40fe85bec7c13a7f94fba896f014a22b88696a36d49caf69563f205e836268023400298c0e1c450138a909852f101f43c05091958b2e0b467294454d12b2f7de5b4a29654a32053007aa0724073a74fccb2e2957d24836858abd8c49373ed481bdfff3d2d08d6f9becf8a456f5f7f4f4f4f4f4e4c891832da4ca966559d6430b5a594cbae4ca7dbe8f0f0156b5f0f22d4ccbb22c1d5ba7e3a755b5920b9c0b3e3f7ec9b22c0b66d9b37c4e63daa4f9379d4c283768785a4a73f1b9ad8bba263ea6bdd25b998000a2858740300842814ae013d009fc61d2c2cbb28f3f4d3db007898f855636f39d5c65d9b34c9147644fab2c247e0357343f3f33599a167ed26c90a755485ac5b0a739332afd984f418bc7a7553fad4a7da4304a6e944c648f4472e3db2cb343b227258f0ccdd9f92d6c2dd443a887a4677b47e08ac64e0b04ecb972614cc226767a978f49d78c49d6f63fb3cd2023f36fbd7db9621f4ff2c8236ce2126b7ca343fc9ce622121fb982ab2953dec29e98ef244fe9608f44014f8e1c30922787653b1ddff239ae63e32bb3f14d6d313039e495e7235772756d1d3cdd4eaeb09e174db65ecdcee037d9bbe434b2cd8d91a921ff557165680a9e21274a869cf0b81c11ba315592c9d1adfdf8c760ab52259913ea643bae7eca7614ca4574a307f9cd8e8fdf3c7eb3c346a38ed67c573b548d3532a23b8c98399592ccf94d0dea5d5abe667b5cf39edb76bcc7713c5a85002e8eb8c2d8ccdef6ae077528b9dae1fc261ac955ab4ad694fa6759de4b1bf32f633b54ca5aac4b92e7c6c7667a52a9d4a7aa606f4bc95820ef539fb2509b8181ecdccff1d24b02abec9bbe8b64dc18cf88f92e364105f35d7cc28d510a3756a1b463330245ea87789d1d302ff3a5c7ae6ce6bb6864c43ef8620c69564fa898a78932ab2754cc53d81ccfd297ba79c4c0c0c0c0d8d4dbf743527f7ab0ca7b318331302c294ece7a42fdcc7ef8d1bbcf7636a9ff01ded2fb60eaf164be647f7837f5a7a3e6a21bb52a65bb920523be53c1dced76928132a70d87b9f7261803c3921a4013ee776c3cafc33de672a4768e74a3233517dfc39c915a553a7dfcb7d9182ebfc7c16ccc1ccbc6cc711b730dd56cd8b9a9942dfd090658a4f2d0139fdb4524a41c50b9f1bd0a0f3de9b9b1ca4bbd94b569ee5907a3b9587a784bb62b41f7aea904f3d1c848ab9e45596bbfb4c1589be64ad6c1a84c59eaf3d013a0db399291238151a5559d1f59496a15e45028d3a9b99967d99aec78bb19157957c63a95231eaf895f5d6c577f6efc16bbc3a6ac83d1aa0eaeb08e8f42f168a919f9d8e3c9529b7372e337d9f170e594d68a42fd739ccdcc56b48351ea5f4a2855539f7a4e7dd6aaf7a9d4c356c97cea59fb91da6696d0acb9186fb81d9cc5ae30aa673adeb51fff1ef40a10f3f11d07ec430a56cd7ca4f7a5fa90f90e755f9e53fff2339b3397fa988d5900a997d17153cf6da9df61baa97f9b26f3390db375301fd3428a66088a926bfaf756eefbbf98a9bae0820b2ec8165a78176868248b4c2a9582da0da64fd91fde95b1ddfbd290528cfd3791b46ae6e34f9fbed9f1f16f7c343a42b231db8e1a2b2e6cefcaaa461628040789804b2e84c18ddfe1c0236efcce06aee02a2606ae62e02ae63bd48d5a90bf3490e663de6766521b6c2ef5a9ff0b5770058d401e19cbcda56c47242b6d7f83e95fa9f4d5c6c72a49ad4aa9116dccf69268b6a7c6cc3ee64b1f3fa655a68f8fb564331f632d166a877223ac25e376d42c81b9f85df49b1a1bcde09af85d5cc387a342cf0e9b064f01a2d113002b817bc4efa051ea294d7d573df541fe3e987299ac849a724b7648e98bbc6bfa216ed45cfc22ef3a951863be4a73a8b7e534173fb5c5d05c7c990d678a05126f8ce59a8b29b86a958c23794d7c1616976f79478a414a6d3a7edb50ddbb39646c346a2e8cfa5e87f6d947a37814915aa5e3e3c72aad7a389e1bc76fd048733ccdc54b33f21b0ff29af854c676b46453463a2c5c35f7583ce38d5a25f36e48fd7b68148f5a85c43e38f52f86531bfce1dd18dbc13c85d9f8e630426d1d0fd9d83ad4cbc681e9b2199d58e0eac6af5b91774d36a6b9f8a88f969b8baf6d30d80479c46c47a7d0e59912f88e6d9d25406073ef5b42e9c35bc3b4001c1ce8f1743b9c9b055120f07a381839c109a61664e31eef37ee41ad0cc8e734b4540bc2752c9783707341fac29f5a908a991610645ef897c6800b5af0b1acb32ccbb2c7946a41a8a5fcaeab14ff7db49707aec7278f80314d27796e47efa3459af31eb887c7cfe9297ec3138d8c9090923ada51f883ad518f34e73dcd390ff7704fba3734925edd0efe2cc075a28d495ee39fd352d4ae91fc5cf76925ad923fddfec57b0d9b666360dc6b7063587dcf76d03d464bb9873f0cbeba6e03a3943346092585d9cff59fd93c9d2aca88529a01c5c4a01e0965b9cff5ef8c4eb9ded18b5da9d81a357ea601d1b0ae56ed145f3ba356d49732ec4aa5eed06b65617187504a29a5432f7544e4a782ac23814d506a97c518e451c86f5e154b0b12efcc0a869ca86fe28fd70cb5cafaee7c882806c51ff70844a7c757a56fa2930c2948bc968d4161d4675dca7731e869312806c5a01874db87e2fbcbef7c2842894e62500c8a4e22948d277d9309587646b586a47801adf26f92bd75611766875c0f2fcb83a6b9d7158199e5ae71e8434238d8d3dce6467b059a5671b25eca104e86ff83b4d0daf068eed1501c5c1b24ad88b0c2104b58638a3558604510a438e328ca910c484105134f62e004149c50c114523839c2c251183610e38b229e1062d1003a02152450810a1329b6308e521005082db0a2071820028d221b4069e2823298e038800014b4b0428a1c1c61052cc8c01123c450c2119a60c4185af80d3e100515fc80c88b235c410819a801069e64e0e6023710b263c5154850a22707681ce109da40144c1c15610b2420299962c7870a461cb9c20a92a4f045141e1882020a4ccc6007660c194d09fa620a477aa420c30645c628020d1664c1082788828381f724035684eca8a04a155a7ce18222809080420846f04289a01bf4e00c2a08b121490a1a98218524a4c0c79f90400c9a60c4092648c30a1660a0ab544188295338620889a320271420032020c98213a010410b2c80b1648c212a28210a37a8016321064b9e8481d48409a00889c265083d52b4800b19f064e14ddc48e30a5c1c9da00a602ca1c3cf186828843890106fe203952a4f60228a165cd1c40e3d457c51e58c22b2f04106de93f7e4bee7f7f43d6a8b28605c1e7a0195dbbd25b7df85cb435086b0835111f8f5b4c31118fc98337cf7e1e1d0dc13f25e8e26f54612f5bd9d59521f4f4f73ddf284cac3ef238402a40b370ebccbcfdafb31301cd80706e3cf47b795d0f79c3f24b022148c43cab167ca58622e9165f8cc325a300426f420054250820ed270b233c44f110c90f881104aac50c3548610d59348239e2165193e5f2621ce39e79c641881c8c6110af55ceaf210142837bb3c04e589dba18a4c69c8e911d24cb34c5889a5a562ed715a18a5af87d8477711fa598ccbcccccccccc8c3d33f3f5fcf27c7a6666fe5eb5911e626666666666666666666666666666666666666666666666204c8c8c26eaf402452848485eab15d06ae537ade4c6f3453d6b35b8fccb90d3db38fd0bcae59efe861d72fad3e5cbb73b59181a4ed7c69fae8dc9af4b16e6f4fcf22e36ec90973ffdf56283f03dd956d2c2c2a3840756cc8484e425eb511724ea834b685c90a8ef8588d83133bc02df773d74b90a517cf8366b5a04c6345dbd020321a4ee9f754dbbec09cc96028997f2056480fed049f4d315d5fa8e4688eb40bf338734dd14429a5695fa6d4cdf1ff3a596e756b53ce62d2d09d8b96eb3dbf2397d99eae9eb4e66633b3d6b2c9fd3eefe41f8ba8579f921fef205c2fab2b1c7d3b673a34b7d58bf25c759aa65b9f997b40d661bbdb20b4d6ad40e867e18e24b9c56b1ca55f0716815b459f6f2ae1a604c43b38c6a5ac964d26a4659584c47a624988cae69058fca7dcf9710fb88f71aea2ea24bca7d3add5574df14783b9f1faf0573c95c20bc7e2ea0e61a2687090416049d69d96b7674504d4c604587daf8137dbca6e7b6a363c388da9980809a4973dd375652fc7922426955e42cc6300149b91d99dc8e5544a1db5d248af099805ad599983cd1d0c4a455ddf503371a7c8a54effb2f261790899164395670df67ac82ffde7b3172dfcb91fbfed997a3e7be172cee7b48ef4dda8fa7e384048aa4178b56938d8cd2394da51b19ddda055110638acedc62c8d0c15019ddba680b2558d1c17064747be1055358e5c8e806c31449fce8cc8d7530986c6e39d09f3f2d1023f0d1a1967a4d4c46b389b9b4ece86098167774b04b0b92ba306e4de8cbed3229b990608b0b1884dbdfbddc529113a80ff7688b28fa10bd15f7782dda0ff8b62d88ebe7c21abb7e2e2616e01ecd4731beb400fbe06c8b317e167c7c63d17ec05a652c19acd4eddcd764c808239db9c144a11314002d5cc34c266e95655dd783cff2ce019600befc98996bf62e6b3bf3bd30dcdddd71fcb93ff89ceb1a7f4ede7b2fa7399bbee9c13f6b95e5f33d4a2b5ef3afc73608a185d17e88f6d6efdcf7b48d3bbbfe5d2fb70e08ecba28b42c67e3313a84f006941a1f4a1bcea1c3a7ff8376e401df3b1da2abc02cea8ada6533f84dd63510075a8893bd2a6ee4fc39e99b2ca85554bc02bc1bbb0a0c8656bdeb8462ec273f5765346e34daecd2ebc9f7a5b8759762ec8e182651a86ece869bcfc3067e172337bb6d2cebe563d666c3f1e0e07cf836333e3b63cf12c26e20bc117630bb42ed7f3cb61f73664995f2c9e8487c2ec418b336cdc5a743b5b3318af1dd7bb02b9dae1a215d59af94dfb2b551df50a06be24b29a54f46010b0821b2830e39c8d037f5630cef37d5be1eaf895fa365afc12010774e49e7e7b49c6ef96599a63df628a574e5f24b26dfb99629fbd2d675d1cd7e669fe35a66b3b78af15ddbbb906d2e6d4e6a41e0d3ff7141eb679478ea3fe4075d46c87f907fc47e5a02f8f6d37ebceeb634629bf5d867d9bbf6237bcfc204e121764689fade7ffef53b45ba1a776edc9a8a2700ec7beb9e938b3dddba1e2ec68dd98cbbdf6d17fffa1b638cf7e777d4da204fe0c6b72cf936ec6c73be4c46aebaad0c5e7285e232ebda71776c25a83b766c505c56aeaecfde755dd7679bf55dad27201e8f51c7578040803c72e363a54cae2af69d5c590fc453b22cb33aeca0c30e3208bee90c5edb5b4df19b67a46be2472537be0edba1f8ca94bed2463702a14685487ae215d4cde31a1d1fad233abe9348746034b3e4cb205af8d84c825ad54f51505770630bdf49246e04924f5cb9ba74bc2dbbb2cf5ad01117d53abdbd5b12a3b20eebc8bf7e7e2cf21bec2795680493d847bf8d1cf681ddf972a310fbe03b4dcf5a03aad7c4d79eb51c4a6fb240605fb2d4afcf6cb655cfaecf69ec69d45edb4fbad51ec0b46144e5a358a5b9f9f104753e5ff6e2721648a8e2c61b89aacc10a5cccf992f839458a5551dcdfae6da8c4061fd10af0305fdd67e744cd74c5473f3dfd37e302711f79838b4aa8b443932cc60037df621c30c3634376d4e73134945c5c4246f9a040d4d1184343fc422f106107e883ce010c1b9c1050f386d44bfec973fba378a352ac7e7d326969c51e206d5c4fa6859d835d03e44791116bf9dc875c38bcac32574a11de2960ee911a03a8a8a791af9e4743925a5db9cd361bcb0cede8531c6cc1fbabf08e58d1d096eb4957b5c0fb3d89581e0421d4bfa80f20c4afa219094d29fd2f2a5a5937cfa7383710b44475a1e9cb6710ebedce8a42fb749e597e4cbb7343a351c2116aa0fc8759b05bef7756d06166c972659938ed6a33d5dc218e17597266dd5a0be87f74913d477bb2c631f7c5bbb42bbc2a5094b12d0772e3cbdd87896f52e2cd3b40cbbac1dd467e306a4724687f0ce243289df994426492293c824d46a4008f7e807c10ff4c21f01aba02d6243ebe186663da9f1e4f2722363f6d1325fc67f394d3bdf270df3fd5b268e257298ee631fecd2f2723198988c521b2b1b0e1c2b75d26a18723ab9b4c4a46a70b419d029bd421658d24ccc3e260ef76a43e1a82c3651c74b8c5f23a5b5a276dcee05bdb903b6d2a6924c121d2f3db3717c11045b88ecadbca655bd6aae41f0b4263dd02009000db8de7cfd705db78460e19a959580756f77777f7c95a0f408e994f0c26d87a7e9108d6c982136dc62c0890e838dbfd50f4210111229841042f9cce482ee50ce77c489c8894e09e30b8edf58cfd5fc86c2617d67b37a44f04d6b57f7a2b8fd9adc7e4b8eb8fd96d7d0604a18fda528a8574cf4a03cf57fbdf29b57e435fdb4a2be88bb5daf8060f715d9a0beef5e117661ab6073fc724d2f412f4a6856b830a159e1b26408bfb73b8c91254b962c597264d8d4bab29acbcd6d51b8004d6abde732832bf352d75ad98af2d254d26af88045b9a4f17531964bcbd07051c2a5498ddea4e17bdc0385aa35da2ec618a3bfe7e6803c1144ed49bc3dc0306e95774bf6196ea273d030343c181a20bfbcf1de7ebe44d0a48cc5f3e1cd7970f205cd87776d6ce4a404e01eadf9f02e0ef7e8b7a2b0e846f349de0a021640cf67c20d6a3fb80842eaca1fbfc180b8a69fd25a7fac28b5085c00fd98a63001840395f5630c96a08dae0189d913403f403f4cfa067b02c8faa9100826c982ac2a5c4ee8ee73494bd91ee71297524a972e3129a5844010b2db18ddddad229670518550362bb7b1173da635148aeeb4c3686da70ab7776d6c855a796313d70d242621945042391381ebffa6294a133c124a93fada6f24817cda8f272c41fb66a4905b503bb9c27856d8118c070bd5ff7d12953eab089726b02a700bdba041e52c8e8092c511403d591ce1d31a0c0ed6f3cda1573ad7d776b943e2a49b912170742c3b9f4aa66f6d3c383af4afcdc81054873e0f8e8ef51884d1a54f6bb55c9626b129fddd7620a03f37d6b13699e3bef9808a57f6b41be6ed80c98779dd0e8976ca0fc1fcca302cff69306c7198555a1a446e4dd4f7cc0f7b3631e195657fcdcfb0ecc232138fc5432fcdb22c6332a7bc1976318950ca1727004d41b42d2eb028e7844cbccb6a9b28a9fb562854adf029f01289e62044d3e3e123183be25a511e8f6b16142ab055734f2a269798a692bcd865d10ceb585b6569354c607bda612b8b36a421bed7d5ec4bca77d87bd79f9d273ef8d0c6d56ce0d224820179de7befbd07dd9bd8b8fda47cef512924819af2d3950aca713420376e3cfaa806aa4af5aca7b946027b8ca47deff9349206247ddcc675e832744d8ecfedf7914afac691f4f054d8c3a898868592cec33ee053e18e6caae03cd18deb362635a7061322254248708ed84823f18aff30a1fe1393341afab2708fae561412e8e7dab8d44a13d5c6adadf238e9cb018728b593404ff48d33f19f25357b9425a17069c2a509961914f9a4d6a44b2cb9864c7221178232295a96949209f8cf9900a251629472526b522724b1ac7871db8ad256fcc689bca62d289a45850b35a7594feab32ccb5ae2657c169d4bbcd7c49c3e08b2d22a1b3134a739513b1ce781d4bac498d29ca402d7f45b9b5c835fe897447e238d64524b315825a3c01c91a402fb16b75d887bc0371349ffdc89a48bdb4860686e22b9fdee62734d23b7bb39c4ed9fab1e40bf34f21b0f836b7a0d365199e48d84dbd2e876ad56022ef4d28a5f2d3c5caf5726f94d734dfcbe7e486a553317bfb3ebe51af26202ed94efb2b499d4f7ed42dca335a9ef3b17ba33684aa1d695455883bf7caad1105fda0e4e38211c12935cd87befbdf722134c4a7825c618e39228ddddddfd3121a1f7dcf67628fce5c2f7b72e49c0d854a81c1d55796adcf76c576fb46dd42a1bde2305769c56315fff7a9aef1f21a4093b17f64b5b0298063c1df94f475a9e4c673e0fd431227f080a1fea4c3b8410261df9ad93d3dd40b6ae81c0efc74c3f709096f985ce6be887f607bba9e9776a6e55b31f0f02282c592309bdb22488246c29c139435638b9f325a4b693df3f6376830abf93cfdecda7ddb7bbf9d4b28d22d47ef9afeb972f2d8d6b1c754ff7340f0f5051a84cc0deebeeee97846366f60714f54bdbbd2b9b5c6f63837d7cee035ccf9fe3379815d235ef1f154246ed788a0e18dfb0cefb392d4a77ae65f930af85e2a13386c818e33e8d6bc6633e6f534873ef2f2c6a975db63c9871d8a6cb6e0c8fb9d1e6e83f0b9b3b3d8d7c8797853a0e01e875237187304aa1592194524a99c113975589921b96652f8bf1fb46db3900ca55c5650835346ac4308861ded8c3d846a9dbb9c172a3ed2a70e13f86a50e885b9a37dace860b4b4597ada5039b1454eeb6bc00ecb5bf2e41eed14f43107a5b03c2dc85dce388bb1ca99026be2eb906c479d4405882a101081820f4b23fd8836d3083ccd0b7b62f0814922e862e861b5fb8afe6beed3e1cf769593ddd87d685d296ca62dfb495b677df6f98991f85928f6df2fd238e2b7f6e4462f0e74f082e31c5f8f05d5c507bfa47185f46f7cf697a093d319cbb1cec62682ec4be9334db75518a3d7d08316b43b113e16e3b846f634b84c321a50c296e7b67ed58d02864591fb72e0695be43d1e0e761c94620afe98f110a0d581575c05cbf0e3e02ddbeacfc18e4f4ca8d63d0f51d60b1031a85ac864264a67ff765f775a62d7b6bf38bb16a6649cddef496f6e3d1d7d66535c63e2eaca4d5ebdd5ad2b0cbf4960febfb2ff6717d635ffaec31aafdc84c26abbdbb5ddb7d6fbda701893ffd2391d375f823c22b378efe11b6e949327c48720f8a411fe8a116410afcaa482c92e94028bbfe4568cc41edf758bfc7da5f0bb81f1c5fe0b8525035206fe3e112caa27652480ab10f9b4b2825a3b88462eeb33782b88719b54bdd08ba29239b2b85b8474ba11b41dc8fbc220d212dd210d2e2fdf0783d2a94d5afa31fc1a8ef42cbca4759baf1b8bec5c0f90d3b17f39d45dba1626e7c1a98f373735c08da8704caa34a3da8a72b9ed07402c6349daf6e3fdbf43dd782735f8f1791d7f4182684faaee93b78e9bf9e7e9cae6d9aa3b6fb9eab66f5667cd33d2d7d3abd8bcb9f5aba8756ab755be92badc64bbaac65a92d2a2a7ab7b475d05fac6215e32e86e15cec996f30cc6fbaa77bbacae1dcd7e335af2763a19b3dd65c7398b51d856251b9b8fdaf83b0509fad1da576ddd33d7e837adad59edb287b83d2f862bb97e4d29915307a09dd21ef5d2886eafffe4ee4bae185f7e3808d22f48594c8e9c288739fcd7ddc7d7f1feabe77bd5fb3653e8ba9a46598aaab92c88d2f9f947fd96a791b02dfa928c221cad16d2a8cdc7e1bc0b8ed58e4f71e2c11e0c2172eacb970c3c172a145df6d8d5a6fb21d8ef525dbd97099ed1eb397656b4370e5cb969052230ccb31b3a416791347b74314e0362a6088dbec4694fa1e0b309beb3ce5632c99a93f4a51a8d7d111b1b9fc3e0526e4c2c76c3bf953b26476a69799f923731b717b8210c2f87e7aef11c92ecc822143c8b6cb2e43082133430861528c36aba7e69c3198417be28f3fbf8b913fb2853f991f22694ebc8cb99b9b9b9b39323fe6677eccfc985f6302a332d3a936f75e73a2323fae2c8fbbdbbdbdbd9d1fb34801f3d78eaa0d5877bbb7771b617c09a141b5336abf1f7777aeb523770da96a6654a89951bbec3252d5ccd0caa8ee5a19d55d732209b5cbaa564675d7c8a89a13ee5c047e3db1bbbbbb7bbbbb5b96c3281bfab767ec0f26088c1bc360840c193264082133648e514249e14f3869d4985029a51905d29c80af314173026a4e40eda83243186345bde6448514c208b15a5960842dcccccc0c2384b6eb6f2102fb71fda0f2e7386b4a788f882eaa74817499a88b295d0ca18b2e36291059215283e888488cfb9e720f1e5e130e24197940e812d241cf25b48594db654cb405d165a22da0dc8e23da628a7b5d26dac289fb9e015278bab812822219448511111974fd2d6f56d80209bb4cb40595af72e16522294833a84c2465cabd4c240508f7c6ed2222068010728c31be25c008238454fea3334ed8d2258411c22860bc8335bbf9f02dad06f9d6538d86f996edfc27a4164e5b2de50ae7478d861865a4ee45a4f3f277f252c83eaea7597c2e699f3d7f97a03f18a13fd732ecb2e894517baec48bf44d6a39fe0defe1cff79ee5f91e5f3ef66e8839666420619431d627da118f969f6198bde00d2feabb176daefb4dbeb677e96d9620608646e54106dd213c54bbab68eb64d055c43e626ebf0c7ae1be2b2efd8e25087e1646a88507b5736125745d58ddbe8ab8470715dde6c0bb1d8cd0ed177904c53592eeccfefaeb7774620ea5c71efb1d9dd836bdf1c5f92deac4ccc2de7db6d3219e93a8748d177541c9109508000010003315002028140a87840291483418e7aaaa0714800e7e984664541b0984518ec33008639031c818030801801003668464303500dc51d1b7daf6b50b168ceaaf5c2c7986f73ba0f5ad9c0fb40b801e38d07cbb65e6e8e1caf86586f080e2f876959628abfffcc3e30d8670634fe48155b85524f6a85a8278fb3e0dbf0955b3fdbc991720c923773eec09600622878f7cea7cdcaf7c79fcaa43b725aebc132e163e98eb42c75daff0319d41bba936c2f01fec6f1a6f6bb815b82b663d23ec1c85e36c9fcf1d7b14c9a1cb66fe15e903638988e036a6254d01bf09c2b53c628d7f4c6455296cac9100dcb7ce6b23216632e0244214379440e688d3844d0c2e6fc35eba7883cf13cdea6217678c1047ef3647bec421c37b881327a7a6c12d42db7c5c838a1cbce28094d5a97d6c1a86a0f4b14a56885321ff6e488af1a5520b5a57040f64c1b744dc7475e9bcccda980f4fe4fe1f39816e951d4f00facb170b9c21fd1d49a64bbf41ac1074c8726fd716ca4b9145ed6a99bdb5e2dcbbc6827677f1d7a3a55061945060a0d967f5b9e07e5d0b6bffb9ff14d7dd87ccd072622a389a1c7e06f2caf8007e4016660792f92b9bd5c6ea5a399fb1d80e14d441f1cab64bba29573f72c3e6d64846890dda81e08246ee3c7ac25849254fe0865656adfff2ffe05058f7befa06e231ed2a08894db7853210abcc031d3964ff41fba7d30a53519d38504f8370b5c872145d7b227ccaec1bf25c73d936afe825ea201d95a233ab5b63987c9b6687d45ca41eea3c40f3d4923e0d2a907de466554a36c8350c20a017627472daf27823052f41e6be36c7186700a1f184bcf65c6ab2d5f263654a3493e97485841294fd440588d64271c43d248128509ee3779e5cf349e83eb2462755b9fb91697bb781b446a3f2477e1caa6def15122d8a00df91c9c5167c1e4e7ca0c9820a7bf389caa088c85d7adb950081ea63fddc9af6155477f1233183efccd66f82b3960f8ef96f4ba82f431eca6c6b1683cd29309c9c320f649316fb2628db92dd5652d139fefebe7b69972cfb5d2836249bd34901e742a021b656bcb7d5dfcca54658e85d5e0a0a4ad0b3b2ca82067875d6c37009d3f406a8791f4cb8a642252f8a5df0ce8b070af30a895a00ee2a3d783e6c37ba12b5d7fd5af105e55bcd1438b1ba02b6a899132ec91499ea758818d4b46c9bd7e66e39942253ba83017bb4eedd2a2eac8aaa9ef4c96e86c93caedcf18e70eeda9b76a1b015b0259d145345ff37bd73f8422d5941c7533968217406181e73eac56af2803c75521e83022e4b2836042fba54b3155f08adad48cae71ca232608f97eb84cc7429cecb8e2e736d82e64d8719e5113aad4698f28b40491fe76f91728575eaad1db907d7f6524e438d134cfd77ff9aa097e64fa65d234e05e01042ff66d1a19126c3f8ef93b4ca630386414087a74c89915e8105a0d8c8dc85abcd118c322c74f5ff101fcd1ee79a5e9d1793f0eb8efdc7490c6c667392a8669874f6b5621048db2d09682b1bcfe47d26cb8c286aa1fb2d875ef87cf7fbd0dd02705be6cb974471fe4dd005be4896d7c2d3a6d752a8c63661163d0dd9a45079a89b595876de91a26612050ebfa180b99f0afb23389019db91eefa011d38b605e9e253c7bd129a12395e3429aa34e0d1b0299fd2fb86100a42edce96fdcaa02d465663443ed9334048156b13093495717650550daa313dcb4c2fb8a180c1133243f96a83a59451d9a38e73e6cbd06a2625ff4ac988e3bdf6242c0363cabb2421732f05663473579d005ddb998b67708359dbb893b9948d15e9f130dcac5cb355325f58371a3a1ed92e12bf31c1ff3ac1af0e4481fc0f6c54d4aa402b90638e3d6a4dc55bc0fa724e3d598dfbce9faf85efa87558019e0849059f780e3c411ccbae613bdf5ea9701020c1aa8eddc8ad5a44edfbf214255f538c26e07a0e29f66be996304c151df8af605584dac3e0a25688ec8c63f22752d93dae987d5c3bc725a1e9cc4b9620bcc74f5a067d8623b0287edac224c5415640beaa8890e0c018f5c5891c6766d793be8576cb6743be0d4f5b0f10e9f8b2303b04d3bb1560c923d4e43766e702fec02103daefb09db17d7787d9ebeedf684a90eb4d3975fe532b2c0d1409883f7784793fe66425f4cefdcd14f5b6f4086a62e8b77bee001eed2164832ac6ce918fbde5edcb8e7161687b04c6a23431b9f7d7461f026474676d6672d8acf695bcb09f793e7aae5d36433bda2cce3e5cca877fe73027139cd8cf3d30f0e23bb596b4a38ccfa18581f898e80908a5ecc711f6af601233266a494553664d2c53f872ba4daf212480277e32f4f20212f2254df56386afb008cbd60ca04beeb6e187c7d842fb3ed05c97371d9af9e7b104586ea81caea415b503354c37c275075d57718de50efdd2bacac3fb0397173e34fe626a0f381c1641c0ac7533c93dcbde47546a5c488281aa2190517dd33c154ccd7866c9c6af797fcac57165619ec71c631681aba03254eb3d3f2b9a925c9d18467b7f6e500d7cd90aae23224fb7ea230d316e19bf6bf81d7e2a3ac22a0741632adb2ef98b05b2145f0d79badb580ede983ccc570e4f317f2b0331f09b783849419c50344629f7181c12dba8a4fdbe9a39f0f270e38d197e784e1ba500ffcac55aeeff071fdd772a21cd14e2ef4ad0aabc8b1ad45d0ef3b46c6ae8ccb86eff49be331e5fe20259b0f71bdf1cdd9022daacf84dba7d04fc2cfdf5ae1743d242c84a0475e28a77a6dc9d0e4e8a753361585ac5dbea730bd1bc09baad9dfe14e7ce740dd3d4227bc5d20fb6634264ba1ccbdc86e5720910062b95801d2e466fbeef56d5ab24e342b694971eec99b2462c57ba7e225c497c1c1ddc2741c713bcb0fbd51d7aecc32648fce2dfa23b16b258bf330c3db0421dc9198fd5bded925a2b9b7fb7ed9b57724ff02158779440f81c50ce6641455588dd6c749186677e679b24c838a650673a679ec5345b912c997902fcb574e760ac1c19eab8aa1d5ce5451ad030a206a3b18009cfd5a7933858dee11d0f86d5a5aac8c39e2fb9782c2a64688e77fd01b391598a17bbeb26550c96f2ab1d93a1fb3a04cf9587bd0bf96af39dcf508ed7a9d3b2c8a2436c2d788035a4e38acd6f1e3ae7b7984b4cbd0c4380c0ec4838658bf89afa9b3599fc135b6303c390edf2b60c8b68247eaac334c486bdae45925be77957f09d7a7a19a861f93d8b8ffda864104a7e475eadbf987d406c94d562df599feb0f5bc44a0941c0f27cb2208c18b7f857012130e051225d36bd54cbfe535e044345840c3bfbefc598f7c8cb05ba54dc4bda1283ba14652553bd8ac863a5170efa2575e5a0c5dc6e10daff2848754df2df51e088520896539d80b71a740cc993109d808e703dd2cfa1be80e6648cd0fcfc6680a1c664b28559bed2d6a9b53b255eb82fa96547f314728c20c53ad1ae9cf5600b768a394b8bdfacaf9cb397611557a766f4ba2ef3a1fff90cca9365d16126e87b6b4b4dc8b2fa3930f966a8ee094553b16b1a097584a85c0907a73b997e6eb39337c8d0dcd0c546884e33fa39387ed82e2923478e4eff702a8c01bb09a91085fa100f6680ebc45850fded89d31628fdc96119d4bfbfa88f9dc91277d3923bddbe383b4bb52c35dc17c1c1d2bea5860f1b41849adeb26f541637a6509a72431bdc938f9ee05f7ed82633f65d44a0f94a7ddd661083191f7ffbdbd8141d3191df97b8a56c9ac9629c9db195cf1db3515f651b28e311f8dec60205ddb871811bc636a34a89d702b90b12539309a741e0de711998ff9cacbe960fbffa384d1f91bbaa0f50baf5227b7f590ce19b56cf0799eec539513ef631b4a166bc81658b82a4901d13038c7f40f3780af4be5fc32b8e5a22f291dee6618d565b3773158540694b924a9bcaa6e03db23dcafc88dd8545a58c92cd9fec0c5a699ecfca959dc89044c4883fca7bc1da65ea5c4e85652ff7f7dcf60e1180acc8ed81726435b607fee95b5eca74c2335aab0e3a2de5a0e351928dc44c16ca086ed4832a89215acb4dfc24cc202a756ba3400678dcff557d8894fd7b7a3d14bdc1d581aee272275e7089a6c8bfc8fe484be8ead188ceb34a3cd112f19a2b04878fa646920d52338a443e72fe1fc7a52671a5232eb5fb6243292307979a5ffe09bf3beec28f14cc8e05f852e8a57c92d23303f90dd30e5b88b62f7a02ef48622ff11c3f5ff697f65c11e405029b0d4d30271dd763484b00e37decddc78378618b97ba38aea36dc01b9c2dbb8f9f711ab3217a4073ff73ccd176ab43bf0ab1065f8abaf63041155da1b6c1b7e421cf36603a7e30f2802d65471a59c600a63e4095b95e5caa6911095fab385b183ff9ae011dbb362dd8734161b3aafb5a39c2cabef57305ded0424035996ed346416e167db49f92715f5e4da28ddcd41e5fb2a78d539d1c27801a444c8c5bf161f32e8e56aa230c3652c6c0ccbc5c5a41cce8735d6211f1e5b478cc2543e64f99e582296d4007de4cb023cbb96377d6d1acdaaba0596aab4fad8ae33a211343841f81565d92c9810ba47e8eb2d3c04f14f93e69cd396b87e447cdc5feb452e6137208a39176c59deefd411b7825825a3c99a541569ba233cd2a8e4abd114a2caf0e9e82c4470f1dea8eea46a635863c1859eca482f74bb372193db7c66776365f9bf129b938b91a39ae242b3da5897a8c1b9c09f9030f27a5d607755b62d1bca00f66c595d718db1a397dbe7a746524dd2975305078f4b01baff012f6912239a858989dc21f3f2375036683166b0217e494c9c58b80621c34f052dd4973724b30cab726b8fd600619fcd9acc865f06f8f064df9045e6b7627d8be5d90e8b781cc5834e44836e7ef0a55bbf481a82b7d0287a3b8e61329050bdbf3db8a0168101e4f1c37a09b4d3cf0fa54658fbe0c5455065fb2a97347bf8bac5f40bb70413cf0f635fa7b51282d9e007b1967d08ae4d5b18aafdbec06c8908594716508183162a9ee79e174c2575760ef01569c3f74c7aadad6ed3707d8160220367701b0ed7deb7bb75f7100fd2a2753a90599dbfcef3f88804f5d2a3c178a0a1cee51e0fc991e5abf0207807787ed3a36dcf39ae39d29c936d42da74416e64a7dca7bada8ab3bec30406dc28863880c9b62fbd0067921d0261bfb323b2c060a999fa760d03c0cdf99b056fb37f6b111d611e9434a9d9a4b8dd7dd58a930f592fec62a458d748d4a52c1ebc5020eb471401f003cbef62fa0827f4f3f7a26e83cda4f248eaf2a8f5681b6975107359909ef9dc32898b4ac6445522ed8479d0d60857dbb05eb41d9a520300c624b30ee27ed0a2d9d5fce460c4ad98a1afdd1aa475a3032e86d70813c8352b03627f41a246bfe4f8184603058f36019153044a54262b18055012a4704073c88b445eb4fc698c93c2afcbd4dbdb0ae903804aa67cd2ccbb718b9cfd5d561c9f5220cd19af446bde6bf5e079368d9777cf80980f413a47698774093fb1b4bf2be87a5618baa149767401d0a4b8f52bc4c6f515ef6c2f37f660cb06cf0890b98441570c43adf427be57fd9ab36292d86a0c1559bc664266eda0f2af95d902e75bb7872d89a2f6232a3e401523467e4a88fa1bde8cd6538acba5ddca3b5a3f6685687db040e82c90ff8c79fd649851ddcd6e4a2d3b5bc941c250210242ff5694a488d0000c0f809128dfe57449f7938d21435399eb6b8b9b79535d09a30ffc388760a01e58bf984154ce9fadcf1b486bc63025211a4651b8c151fa279b3893ca5789221046f71fcb549c3c31f47efc69631150f88b414c0773353ca5e8619ebb355ef7124c1255f09c6773df200c3666540075529b35d5a0e25754127a80f8a72210874729d359eceae4bd8051061e81e397c4ef6ab6e278239deeb3a13a75cadbcc15d053a81b55b4a5deb9b06aff561814e66f7e46a63826a879e78cfa850d53f9686eb27b5cc1801b1fc73725ceaacf58574033c36ad15bd79cd4a0f7800615175f4dc9c1bd72c1ea3984c94a9b19c1281d17e2db5c96a49ae837f3feda49a32d72727bbdf174fa5b034ae453f2c35cade2c0b9c25121cc1c96484e604fb3aaf7eabf136651b9c27673ddc15f0c357745e8e741a6eec4268a4e535a843562d631326f44113b3187fa02cd93c45f370a160b9c9f1c254e11097d66987afea4db0428c4058af71af5e36311444731f4767e3fc7e11ab0a66a909c94f8fe4a915b2b0541f70677260bc3e92a470c760b8a08d21d465fd8c56b4ede54c187990c1b8294542a344f7855d85401d156bb5151262ca705aa1ae07b7b780183a543429d50a595a4a7599cc2a75d6f929329a87e626d965f047685eb44a8b855a21afe006daf2fd104a4e5d0f86b63305a543680995793262ad0a9a1f107148d22bb1aa4c02d7e99d693b9ac497e72d97b0b675145da9551457fbe36c2e137675c5f8cfaf287a17ca61a7db00a6473ae59191036bf32389fd63acbbf4773880abdc9ad41694aab22fe4d0ee19084a28704075e9890fee7ae8153d58cc8cae61e4d2910fe0aa14a2b87d91afdf7ffcf4533a14252d7eaba480d5491b17689747f3d7aa92de7d21668e00ed9ee2792bed1270bbb424fe327244bab32ee904ada4219e6d8ed17b6516a116eebec022517375c16a8f420719f5dcd1b67b3b97a1deddda2dacd3ec01355b3ee28caa8cbc565edc1bf58e57671d1cde711990df809edc4794fcbc8dc7a61ff0224438fa80c69d8958935ba86e4b60181e0b6d2bd92ae55ae873ffb276139a973bff42b1abaaac3668c36e2301ae369859d4f7b8dddf95f38017d94b70bf0b022f4184a2d136586103011ec81d2b6f9534bce0f20844286d8d907eeb5165741c8deafc20f34155e8b46b5997946dd7427558de417e7cc2de3b42c9e2d18e3e4bf07b313fc74edbcd921283734c88012bcf21184178453d243d07599b64a71f555b9f58b70f8866dcd32702030252a24cef4c01197ac9e6d15acc43d5632003ca296f481a43192976b5c07e1677b7eca8369034338295774bb4085e183481ba52db3be65903503b5bc01d59ece176932252b8167ec0ca25626be6afaeb388441d03baa2ea21d6221cd6ed4fa2923d40924939d4957743e05515cbdf74e5a9928c7754f0a52e6d45e30e47ae686bd90c0b17a80f69654119ec31056e30e44405c1fb91ad48ed8c7e600a2dff78be934ff1f853c40880b356fb4977fbf6861b808121e206505302b5a1680a11cbd565444c5903ae2edd848c7463f07d3aa29d3627276c8d0dbc54fbcd9c628586df98b8b4dba88824bcac2cf2dad90e7796681af776f14d9f89e833011c6010516170208d8794489911aa31bcc6e6cecf6f8518986ca10cc87cfa4d61d959fa3699d061eeb6868208e1841391834b1ddef1f457d266d9a5a27f56b4a49209ee98708c435d727ad23cc542ac59d3b3113acef09fc06aa76df88a960d4fd046a6e8138231c8059791fa142179dc53ed69bfa55d9c129c42334bc99bfccbdf72e59e77314f4476becec41662ee5c9938f3b9907b792d06726a1960570ef75ce42a76d6405d0c20fb16c3c426caa15b9d64671d1f303133365ae864eedb161438f37ea037421eabecfe0248632dd300c445fff3208b5654b30bd613566ebbdc600d4734730d3778799329a43458783b9fb3b054d4bc907d0c6bb0fda1f8ae3a44b14435c49b2ff713f2486874728836355f9863996720b551ed60fcbcceac134e0de9517df7a8b5a39e1581b03abc2dddd49b534232d4539a8cfa439983c2526bbc58f87ef485655761c52790a46692d5ec491496ea59423df39d69ed79886a6059adf7abb9c72bc9ebc274ae1b415340f4798bde1707d2c982c46c6f03a6eb1873e0b1b783c06614218a687552ff342d7fde76c1957a0612fa4763d014f9f6f7b2648a0e08cefdd5c74c6a36d3e2c641a477d5644eb6b2bab2f016be754268f08f887b99729c4f2467cbcaee04516af42b8f3ada51742af4117ccf2beae92f341ee915adf36f7a3582bc5b364c6d7fac935df01d0bab5ba404a7236416a3efd3ab8591592d43333751c6ce3c8366fecf7e3bc327809e7b45d6525d1c5a8dc6ddeecaa0c1439b7bb740e943caed79c0286144a14a76b5d7e67c01cd906e26bd35c930fc72dff964ed99d913cbafff4410f5e90077542676f19c71c97c033d5a31cfac9579b1fd51fd5859800965eaff89ad96d3819305fb8c4a060d2f5a128f64d2bc832d8c2175fc57f8cf615b114b90bbee8628f0928b9e58bbcc1099a7388a6702c91ac19b30becffb430eeb8356405a943e0e92b4d15e71f23251614d0442503bb73612328dc4a368cb89f123bd9e9d2b0105218ae5c13b54ea753b8a861d9fe7eefb194e85d2b2df5ea7f249227cf92a47fb223f00eabbb39838c5cc9a2d08c22d42987c85504cc78aae0215f83b541a845c4ddfcbd1e607ba24bb63da8ecf5a78125eb6a4da270db4855fd088759d57d9527ab83afdd49e1f6569e3299faa8de811adf9524a7dd6b7cc653d3d0532da5bab1348696dad4e985972f8fda08e6806543324afa4ada16d60c61a766019b57cae110c5963cd19c41fe57c9ce44212b172261155c1152a0f6a02e0ae443942d0e6b3fb3f1b12a43e4267bc8ff7e629a6226791c01cb8f959c515fd65a3dad57cd707923fed337297a69f0521ed59b9fa412760f0c56bda76606dbf0d228fe9792fc6ec82470ebc3a8eabce75415591ab65bf234e5caf3c5e16e438e2120dacca8a7ea583dee81db833a1eaf928d326c5130809b05a07ed55bb26c8baffa7e3de6857520e352ad78ad0f4896f96face034f7c4133dc41646cd39156bb4019389d3a6dc09a802248bdf1baf7e58763197057083204c3cb786eaa80b9a44036e989535865d3df2692fb6b99486df03c58d42e77e4f905a55c01a6298f4b7d3b228ec467a8157a255ccb9d89577f7f94d3323552a67c54dce78983d188c9662b666f71bb353c13d073a3103adc73ad29689a29563b31005b6a0e6d547026822b32a817268f734dec515246d4a4ff03ab1aaa3259586756b84d1d9b374c56c01c994f8bb12056b8c8902b01ac148d82730eb74dc3087a7d5d520ecaea8e885d9631e9a386589a14da1b0ff761f4aee6d845b75ecb4d0e88ce992d45e6be77e720f3a4fc91c61fad67302f45ac5e3df42f707749e626c487f68e036a8b7328b2871e0bf41670d5e86e1ac42e70d3d014aa0d99ae2a41dc8388983e7e08130d0c35d8897b4f51e07d32e06b0e7eceab60245d2276b826e269f6150a5d7842cb29342103c46d7873e395a8db529c2a16b3041ad01f53877e4e0845c0ba0a235f764f170984010107a1d7d43c4255814ded44cd0ba6711f454645c8598cd166ea760ce1e57c898f24185b26ed468d4195db21af54a228df4bab41ee27acaa7054ca1305b1a95215fcca90551a302a811d20976e8224650fdec65394db2a4f85b86ba32c47bc5c2c2a0830f8f235d601b166024576a982a986416886a33d90d99cbf715a653516ced62acb16104be9fd43d3cd0a34ba54a3e26357eac719c4dd0221d29f5354934b04672627d9475da716d06f5f1d4e7ef0c5b76dc2d482ae2df688db47478e9c261f8d76af1315bdaed222c85780fe306690abb195b35ea24e80aff60f9e8119b9516013ae78c2fe40d4d83eb8ca35557a70863fb7c6a23da03161396f7728da552d69da82d0e3d84c405e10658ef4d7f093e97978947a15b0b0615b789b9ea56a85f16422465614e4fa8eccfbfe3f55a709f16abd09cbc05a3f40b4af67ec7f359e8f70ed58ba6341d73c4c5cc318d46fa983db8e4abb8542cacf0bb8044cc21ae205702ad57e140208c08da02d7ffb036a300befe527c8c9867184ce9c6275da8ba7d2437b107b6df6923358b1d58366f3095c9fcba00de9087d3dc0fe00a9ae012e45eb7ed4e8c3c10cf43a4040bf459ad5084a0a6c7a3db844068d1ca1cdc05e1a24cfc800a166674462677ee853410649e7034d3c35d0adeb8e5aa1621bde421ad540d2311a29bdc3062529a0ede6a82a3bd48e90066791e73dd443b7e0a66c06be6d6300b76f243f869977f45260e35d73dcfe2ffb91fb4f374fa71c23b1ecba9b9fc0295fe9c40aac710cf08753775f311ca14a596039387c23061fce023d25323a9e415654a7f5f8c5010b560f72ca1a719cc5190f52dad9af1b38650b9a0b439af971a4d3684de19cb2769c0d031b6c908145ab5cb674cc1900fbadc47fdbeecc6c87c1117183d0436059a932761e6289dce82a1d8cad03549f6e0d2f4860f6399640d9a586daa5ea28f13661198fdc48eb317edfcf5a4cd2d0d55231d3fd93cd72db280e7a96bf2862437ce4e9b05fc19cabc4417dba80e67a403e2386ddb95ec1bb8d00b054d325eaaf559c9b417681e5a0764de3e82251b4bb1d91eb4d0c27235b5c50846b391b5da43ed401aa0dfc413ef58a3f00f03e695bc99c25d58eef8e279bd50924f63ae5574fbefca0a9c3b73dd624f68e2dd1b50eb1447c98b683b09532db61c00ac576b010b51c5f08fbda39e6bece6fed87365a87ce3b2a4820bdb2c0a412255c51f2d922505b399f60ec15b131cc2308a62dc82adabb2285287a408c7c0a9f2356c770785e8dee1ee2a2f81ab13a760ee7e005f4baaaaede1f77af2d7d15d6f4c28e2097d09b3bc22b2f81fa3c8cbecbdbd5e8ad49de461e73ba2a3b5fdc1a7a39848cbc590c092b8f28da0bcd9d444ace994349c87f5ca5a63714f45992046651c99fa56c9056bac52284ae8a55d788ae222cda46585a99d641c2fcb6f3a06bc49f072ba4693e74f65365b0bfc97feac4fcac3c3598ae482b79ddcb33ad52b9e17be5b9ecd97e1c8e73a36bf676b53b2a018baa15e8eb64cca24259be0402fe9f8ffee2e4d90c8a15ed4ce8e871ce5543d81f78642fe14eee73f1b770f5c01a2849a3fbf787f47a16e32e3c10a5cfb5d378d0443027a3f9b70761f370d151d4a8cdd14ebf5b9d1e638bdd622d064d7ce41bdd3b394043e9e6e8677525c6896e85aa20e877adb375194ce82f885f03330c9915a1b433955cb9873543b2a6da02b473d41633fb4b83f62a1344242c1e5ee34ef48870538b40666373aa0211dc7321ea37213218765b7ed7e63e2e6f1e658f9a05e50f02cf6c170c350c54f0f8dd23a6d83347868371fdf52e20e09840c372b4c36df7a9e6828424958a3ca07e98d8135214768e6ab8deb5fa18f14ee8492925fd6d5a87e2ae19007e97b9ac2f2dca1d6ff3a4de6b483a9a8641398717a970dbc1c040e53fb2522154bd5496b5b0b530e2368e7bfe69b20c71187f5027bec4cef18488954cca6731eb4f470bf725d9ddd23e1d042ca79c0c609a49971097229ed6902a0d1d80d16582467928f2292a6dd51e1f9e2a3b1522e1533f1e45f55fca1a42cda598b01759c94fe0b96ef435a83874ecb628b3f2267ba2b703666d415d68f78feb4963b00e1affce1a8c54d91fc39f615da35c6f54f8886c4e8876b02981321aff1345ee9301d9e59bb500b3cdc79201d763032a81476c76f6a16ba4ae09da1065134b6e9519a065920b21ea5697f9db5837e5b342acb4c47b9bcbbc08a1fb0c125bb8c8f1a941f94415868b31c1f88e5af82d7e9479eb4a3185888f1e92307d9977760bf4451bb3502aaaa6da18fb9baf22bcec95065356f4ef911407fa0092dba43542c5e86eb56ab4b1b47a5feca9f1ecc58cf9d6f69b899c65403e367ca86ad042d29c2ace41167d8127c2c6e093951f6f64a5d2d154e68b33d277c975468f15aa5e4a219520f8f62fae66b29f8aa267784b292b2def3af8ab71cfc89f3b61ff3c482d3ccac11e64cd03907ccb8383ea4d7cf049945fa9817bef0288ce81ca8e97c180cba1295d0438e2b64d586af9535a412addab19a7197f4d00c049151a6e9682f1393271fd7d128d2c14447e048de1c795a56f0ed9c7659f24113b55369d6920d2867b44388ff1a6c05a32b630aa74364a57d1242bb4886d7ca7d5efadbda570629a129ac77adab64cff0904adbf67a1b08af6947534b189a94b6555063d237b5d51fac7771f56ba8b70da42f7c19d53aa314cc6074a319ea1439538cc464a82d13108b2a40fc5fe21d1af19fed8434b1c1ab3c99b61a6ababa861e7cbba96763ac539294fad31436ff039637efc59657d60f347e39fe178696fa3f224c6abe2aa6d237a48d99825d22580fe1c90275751e4c8baa0802681c22445c23791248c6d6366560b507ffe2dac325674a85ebaeb9b754356b83ff61fd078829948c5908f26958d03125a888d42e8328e9adfe44dd9e7842d47da4c82bee2c6ce20428c0e961ec9a6af662dc0f9797d6870675ea6d4330ac9389f22eb2f1dc979b80d5588e837df8c11c391806d9099a792d9da9f7b4ef5358017d1874f3cea16900c1f8fa2d95eedf91db1178486efc00ca93e3efb0dfccfe16a947a5949ca8254f3f46e2cbdadd26fe1f321ad3c65bcbc4647fe8bd392103cea5cc346d5fe588d4ed68602af884428efe36234848a19a721dc49fecd49996f1352ef84d58f0c2cb333d0bf97db94fc007a2df223b7a03005370eea85615988e17ac544b1cac1998eb1bca25a2f56177d2e1f2fe695936359d591151516d98ab37fa4f8550976761aecf886bfc7b165ffdc5cff08f08d45cce270d5a61dc4aca7e495c794fde898aa8710e7755193fdcd37e7f5a646122afb534bd26955e072eaa47a819289b39f9b95ba15e37de504d16edb23043c141c0e2cedbd4b0a728298c54477b0153f4667aa14b81113102c159c0184a8a01ba14b4cf580cda3101096420704596cb1452793973b71afd21d2b5c2228da2f5176c2bc46a39ffb37a908dce066bc33f065d28710c074fd71cae059b5827393456757f226d2898b35a825a1ffa4499493b7963bfe6f82e86a22a97728bda05641b541ed20d5c7f4dbd2fb78fb10cf07676e57032d08673840371648a49d9fa82ced56293e5e60d7fc92c18d5bf1316924ffced084753489cba4b27f2d2f7b881a56df6929e3202a9172d4cd24507777673b5cb95b0b9dd7e250acd684b7da50dd6e75e2d92a776b69fee7647aa7dd9a8bb7a35eea50b18e35eae5b86e452c6739ca4915ccb200786d4dd078cf4cd75b97eb42881cba07fff17d6f756914dd7d1a2b74be959e51029655208213707e3abd6dde4eb2828303130157ab4cd980aac22e4515ebbb6790eb1e849e6fb5e2b2148c3a11acb310806dd93b5b40989a077b428a6c3c7bd88b0cade413404332801d52b14e73a85589d9f84f4ac0b7667f9b22e8b740f548b3e72d7050034b2259e995e21c5783b7d72df32854e206aba06175898ca799581f1b4584e58ebe1afdcaf648ff282a46dd5e63f29e5d4da0c475ccabe945f0d9453af89a9efb4e007cad632022a4b18fe8ecff6007f10107a87e6c6e5c8d116f3739db4cb0b1d8b86fba4a19027ebe79103abe77d546e7d622e0f3860a285b1cd8fbb3b4fee2201a30cbfe94caf91a26c1ce153eb94d610cf2a53dfd7d071a7defa6a32b0a0cbb1d5881908761044803fb125f5ce730071ac8c71775b2b015cde9c284cfdf06aac35bcfe3c44305104c654fab99a3504744362a6a69619068aae8e9502a0576cffeeaac52a238e789d4b14bedc6e3c7df987e1e8e652004665b79ec687fa568753e98b3a447971d0f98697e960d7c50cf041b52851d77798369bf4cf7a9b0fb6cadc035344430f4b90455f4e79e64a3c6bdc4f05a7be49ffb06cde3525514e4091c057314f9f46aca94d5a44592e4987437949de4684ed393ed668935609bd570e41fbbf8ac3c005b1d1901340bf896561a88ee4a9d0ffaa5b11061c8791d68537ab0119650116b09df266127dee809c644d4775d5851d3c75823e62f04c405b68509659a26d06e1c4d0669f767c732d42f7f8b156fbf7654c522e5870ab922f6770939cf551a638990547155e8405699da53c77c9b585f5090c18263acd98d19c680752bf9edb321622c88eab75e75ab8f5b49f384f3e0b8884be0c63b6dc9e51015c3c85ee618b4cdd7b599816483829156dbbb7d45238c7d8dc9bccb6b3f911af070f932aa690c80ee5494f7e17a188494b6be1c0ec03a09698e44f713f3aaa9df873c768bfaf5df78d3537f57631913f36fac1009831ea0f615d1ebc2cbfd625d2b8cddd4eafd5586fc29a50e98b8814421846b8685c8071c910287a3d4d33547a218e03db6b051623c5b92cf5b879aaea981b0b86fe23f3cc57839f1fe4fb5fb585da3d9f15919577b4ce0fb0b7eb944d3b73478d484826e8f3797469e48b51dcb3e389e26a73dcfc4b96e04dc3e56f0cae9451b0505b7b5259926dcc64837ca54f5aab4d27dfc58a9e854120a7463a4ee40eab931a4ea6e26482f08212da7b4b9a4f8ffba2b1902edd8229f4bee6b38d6361d95e55a618b351da1081b53d318fec372b3ea7fa06c43180395d722ffd5ede1024d4c2b686ea07b886562e255d25129c2c074d54fb3b7f6c18afbb0c81f7bbafc769ef0a6cb7abd41eb624f3e8bb0a567898a94903a3915e53a5aebd60a7a0fd615af88d8049205383e9724c29285b4556975cd230647e7db88f31e167cefd6960a66150d80e6651c93e86dfeb32aaa3285a23a66f72bd0a3dec7135796e09561171beeddc5b6baba1c246f207300129fb90f0d836fbbd74355254f0b5c957c5c0f567d64e7e5edbfe9b29e908089fe6c2a37e26939e7b7b514cf2163108eefde260e493738d4016774841f68d73ee8522365b5fb36d9ab0ad7b5b7ce88887b756a733aa01e7fcac1166b98eb5acf80c369f02b321a9584ac6bb4c3ffeb065557c1f1a733d4b3324731b334a71626d25530ecf28cc9f5fc50c1ac4ce76c93346c3ecdb075d41c2878942b5870007ddb856334268c0bfc4a405a0f2089d716178d9c1ae4db1b20050ec98c9d5ef02dc95ea98a27589fbbcfff4cc32498682f241e3085dc6eb76a25f64bda9c398f7db9cd788cfe01b9f33d452d6c9dce962dff03ae0b46ab2d07de1716c74d832e23bdb2b8371425a5071f3d56364c4903b08f3c02bf2b708a5596f2da970e2748993468fe57b838b886a7c76eb6b97b3e731ebbe59a61af036b584f02b05ad107c491961b200a60366d8ff9212b3b4fa02f4d4ba89361f4a3496d3dd59c5fa8fb7e6783315cb8729ffa13fc18e01584fb70b7f364b01e77baf98f6b3ffacb51ae22d0be1773ef54a172b8f4108481fe21c9e38c3b8828b4df34319bd00bcc67bdd4d6efa1acdf2f28b42d8ef9645760ba730c79f3e5099878e7f10958393db337af9fe6f414fd0161205c0b3cb2fc488c0f7e3a4952dbf3f003825b7b749f8cef71e4f67ab1f929f9c295b771a409224ac7e9a8026f01a74235a6d9af362a709c03d4dbe10cacc57275fca0e76628ef2c2f12cf0c31382c53640cad91182c92a068d65a6073eb50e8e2ad67b776016f0b289e81816e2be608be81ab27ae6f81d88f8531d8983226ba3aca3778c30153e7662c8198a1b12c5760e2b900e483f006823ba0fec47f33dd63ef2e3a41b1e344eb0c15ae5bce3b98493e1ba8760e7b6af03edeac1aa6b840174270de43fbcc33336414c1f2a466d6f4c17c9f8366a3430742c3c3b008f047e1a9ab78d5acb20c5be3561510239045423c1f76430707bb563e170c208393f7373682ec878a456f7f00cd0dd468727f22f7b0aef1c1f585c8436e98f88829ca21c642dc4ff0ea35bc58e0b67fafd2b05302b8460df79c7087e34f18f0fc0922544f9c0cf510b69528e9cc50e98b6413e4662a1934b6d5d1a54c650a3293201341c3c4b7f5aca22483d01e6106ac3434ef6b69b3190624184a0ed6f8eebcaa8410a021c289ebb7f39f8fd81a22baa54791eaaaa9b95b8a71c29c2a0138e72254d1ed3698b388f32620828a0253a1b2ce1154079e43722813611eb3c966f9a54d78ace1faa51e18e71e9208c3ac5b43f9e4028b19a8da83a009a8ae6f694f57092b3c1547cc75bdbc36915a18602f69b9b270962704d6bf536187a8528199fcbd10c05a872e213cda3f62d08346dd5a854fe7794098a914228d6e5689659de4b967b262066bb44feff7912650e6e9b4559845d0ee74c5b7221d1dbcd3486c18b076c4eb2082f751f0b59308a79fa6907ba55b3fb509e301a5f2706405e7ff5ac772662a51dc20a9752e653ab8a1f39af8396985ed9c33af200805185c90ddeeeb3f272962c410ca243ca422392d14f1ce80f4b8f22f2194734eec50b1c56964dfcdcf3aee487c1dac91dc995552ad77288ab859d989b6e203aae9c7bb4f9498b2941b11f4bfef99f429c7bbf3a4b52fd176a9f865b25e6c8311cc744325062b85eea3673e1006321050522b0ce5fa2bed32a4130300dad4c17956051c6481f0cdb7e7e1c9456babe8fa43eddf0f8bf61191732dcf302eb1ae1682534d9bab2818f952c623430fbaf10cf29e5a8fdb8bf410b30388bfecba13916fe0d7aec24f81ae410b0b405bb5dd359be0f51d251573b0959a1c18d73f463c63756fec61b8f501e4ea9a4675a83e4a6e4e7dfbe68c09b5234191908462f9ac760f3143dc308c83ea1678a43e3a459765e202963769a5f8432daef6a166b0abf17e307816d57e9d51532b67b65d1bb59bc4bcf939e102be0f7843e431155a0a68aca4c8f951639fba2db9bdf66a22f66088572d3e577474c869fa11f457f89e90320fab5e3ce4e39339eb693605852499fabde7dd5814943a87cf1f5e9c79f7c5dc9651b96626575a01ab03538d75925c5d865392937a0ca95ddc3708f1968ad6aadbfa728c770972a06f540be0f0e1f3d786af47e71c6164b99738bf359b44d60f91798504c416796a3be728ef6a5cc768c782803d95f8348ebf78d253b51f0ceeff55cd302c8a036b064eb51418262a40f4d5a7265f71133efee432c21211ae54f3661d74309aca34e63c23a7b4ebb96c2d383d49fc52ed990c497bbdff666c432afc53987125b473ce50330cf9b7912ed75ccc19ac728d123d0307cf147c38da212abdee469233e9d9abdc665459bae53a8cf5d141865db21e96701376c5511fac94a23c1256b06b3b4a33cde0fb0c9b3e4cd6b3f40683ba5608bbd6805863691639123ac8fa18c76d12845d6b7dd1b4342bf6ebc27fdf1afb475d71f9b478ffb7d288bac8f3b410376f093720749ee97a8b13d9e7f6574a50ab79b656bbd3d69af797c1d78ed152d85273b6536f81b24ed04b7a16d2b1fcf11d05745caaebb0355b8e743f909f5c7873a38ff96c70cdeb05bf7a599f5d5071254fae287a64984d043c83af9a4f0e74d50b94cbbe99c66a8218cdf778a25c2bd465b961bb8a33147d05b8a45cdf2db35b858c2c86d92f1d567e88a6e97719b9008519e8abbf343939977fd509201ff2de3c6d62a643640097d59bd22c755845f9f855edc53b49f5c3d41fe3f63347d9fc3ac4e5d2d6becd18027cff5b87a464258029ba7152a45eda1613129da1fa2b5e96b378c9f27143c049fade2af71e8785c9d3281301ae6336c4e61a09c9638ff7dedd3095e4009f048a216250ef1ba2f4735a4005daae0021da46912114a6b47befc906d11242dd2e1ce847337bde48d0826ba21601127e78e8834e6820d29b1e5dfa20f4845aa501f4f199ac818c62716e44aa8b2901510e4c4bbf33f6df606607595248670cc5516fefa8a9e033c2ca904e0733d0d4a4f298109794c667a986daa0188f386c65fe3fe31d2c8bd8ea875ee1a583baac4849827b6bca4428737156e75578493bdaccd82f03fb6bcb7d9c291b850ed26ccb01f86d26787107296b0f893c93be1854c2dec20a29bf3d29a29064456ea9dcea256ad05194dcba422d013bced17ec86f4beef0f6a1a642a765027cfef6855c080d80e8607f0abfce1b1d04a61c91e40ec29f056f1a476e59bc929c05cab90b5d25b6b522fe051cfb639e43203a44129eb18fa3beaa917c500a56cdc0a0d6f8058ffb6df435c646c1940b41cdcd6e785fa7c833367032ed6a4194031d5cd1595f8164a88489fe5802ac13b6babf032c69f8105693967f81d534ca925a17498ccbf89d59f7206e7a3184130140a32570ca165bbcb01a99c32b203471995239f8424014bd6558b17d7e079189701887ec2e60102f98a2202848372fdd780322cccb96d579419c0b69554aaedaf1906badb76074e66f0902dc53674108f1c6e18ffc62be75368215d31f7ab69fa59a4931c88286fc9881119dbf51b1451a6a7493e958996371d1427f97a1bd684d32d737ae0870562844b741d31c2811b93dccbe8163a569dec84d50d9415e6ea44604dd3a7eba53357aa529240413d5c547a75a11b2fa487ecc0efddb88712bf55e362d3b146f258ec39e415338f459da4db3ee3a1e8732c1a913240b74073fe815e787f25600d46d8a5142fd20f26232ff592ba0c32fa29393b340a6d6763a51da4ed71c40229570263b9bcdb5de72f22d500744b3383903d2fc5c302e7b733348d58b0eb31ea5bccaa60051cc8de1fca5f2e380b3b62cf542a651e3eae2720816e457e5c3713bf7d95d703e2675f68188549b333b735deaba022e931af568528569364c8ba7c9644d66d460ad7a9aca26b8bdb7ee9e425852fc04bde010247a816e316abccbec4cc4e0180784d834c06536567b93ba199c1848a21d6953742b369b92836308740925d030b269c376236632de904d63be6b73a40c8b9114edb2d310f82277be923e6ac4289712d5351227ae298616b131521801ac60d935b366259ac9de51f692fda00f0626e91125f5561a7391ed0b9eec85ff85025ed15e40da412342cac45e51a3575dc059ebd262e9a93331003c565e272b5c0d9995182a4c679b12bc1c581009453b43869773eb6d9d4c6557e5d025331b409dee60f28356b15622b8a423a3e9a496b5d2f72c0a57bae99f6af44318116d23c429084a036a411930942cf0d9843a71e8c1e49e74fea78e6e0173a637ea37b8091faa2a2ae33798a2161065f1c70ba4410623b573a4bd63006410dbadc67500530eabb8d59f65a1dd8d689e26a9cd9e5248981adca6e8cf9ef7aeeba6e47b25f9e85390047a2063b6960be53f534e029a2e7251d0c3ba03eea7f4f39e136814b18ac00ba02b3e78b69a589c4afb2d72187ec789c66396707fefbeb1b92a6d4e4c03900ba49ec5abae778ef6f77bb18f6d223f87aa4e8fa6b85e737a571e2272c8e24f5d2eb2e01cff36bce8bd01588ea5477b35bc30bdf4f4be8cf40df486f54b6f7c47d8b548e24cf71817fe6751f3f7f723ce9defe478cbf0cde3c31cee8873940b3c8e9da2980b2d7d9f03edff8717f0936246350153aa76174e1eee2e405e2ee35b7d753263905f00a0e18a6837dc2d2006e99c1117f03fcb6c8cc89579a95d992f8c4a620b04781ce84a1f7742ddb8470da7a4d0b84b48b33b20de9e7be5640efc16f2d681acd4e7b136768e6baa50e7a07757ad1af47d791a7f267aa79a0de0680ae97e95b563644e154c137751313aece2abab6829a0e8ffc693e707c753e05a48dee661ad8744976f2587380f56a5f2a908b7a119b9130251f7083d6457545a1c299075a55c15341f2a92b62dde62e2fd56c5118e309334df8d658ca4f47fa2e1b0ce68167655a0fc014383ad238cd679010c72d225ce104a0e26bc10ae710a4f97b72ae29adbc0af6e1a005c58698a2962c22deb29e0052f904e7b2829574f2a61f3750542dce1ada42026dd7b703ca3c491e48aabd03c374ec920ab4dec7aa9b9c6d56b5710dba7668db1c26f57938125433b6ee4fdb49ec7818b6099a6e79961425c559bd0259ffa0ca550db55135cb49747334d4cb2816ffcf8c52e3616497b941397ff2c8198865623cfca67420181dc1641913b50fbed625525b127081c1ce5cb35c19501753d7114ccb03be351e91dcfee89be4915efc4bba8a158008f20e80fee0275d1ad1da8107f21e15cb4b6d12453e6ec2249842c141bb24093f64dbab423620e2d184d62d30cd892444ecee54bd80516ad911c91141e8d08bf303b3ab52ac408fa88c41aec0d0990a14aa9372490e30a3a7c43326646495938065c29e003cb44195816a056845143ea015c27fa95a48ea60eac251e9238e6904ecb300880e841ef59e5015fa8c60dac65de4cec138d6af3509597bfb33aa4f4e2a6d05a12066e3891ff65dddad4b082875f35a88cd82b126718b0082558e14db98760569e8651af86e08ff21ba7b27943960d3f2a0ae1e01fb40ea9b7dd80c9b2144c6d1d5a84764517535051fc6db0d841ceca5d984274aa3d06c78490d874cd27a838721b69b8cb6630c204c171142f44bac0c8099c9c53301f34555bb59af6d24fc740c144fa016d8fe63c56c2237d5de805524c51d665af21c6ea9ceb0d05900203bbb84ff69bc02e6ad7c98914980d22f01f89db710716e8e73496d414976c3aa16ad4634d09405ad6deda2e21712c6742d9febc8611ff4a503ed0193f2d763f454819cb724f5882c8998506164be52c70152fe71560351838aeb70c2cd161156721692bbb2d69578e601764c40ffd804bc320f0edf58c681b564dc065a1bd1d11bc5a56afde8c8a2ee1a33cddf49a4950c0f113965363e6abd13f56ab579f798b889af9f6c795e7f92d3f6bbf4fa23da05212dc971a2e83ea7c081ba01fd8d9f92051616953c2cb27d2c3f20378e3fd25cdd05342b7b55985d08bc794414760207563b516eeb66a04a58fad6b6d5fba9636635dd6f4826b5d5df5a8284ee4607e5829487b7d5827167c3961714cc0ee23ea7538383e867a2f715d3fe30abe132b09408b3fd35c0dc0626c5092be6bc41cce7650ea89b3d8852507c1e246f4140729c471ae4e604cb87197e4398f8003883295a1f2ebecf2a015f01e7039cd01a57a01a580b51f6b7b005669a1651b200093eb022938c3e1462bfcfb51ed402d7bc850daacae61325dc551a9f71de2542c0bb8a20d9170d05b3fa4ac35e37f07548c5baa17bae1da32f98699199d709d5d129a2572c92113fa1d1db7baf08bc2aa3285168406bd95a2652756f78cf9b782973267cce048cb73d85fced80a0192f44354eb41eb7f386cabc2fb9a72380e13bb2ab0cba88aba90487d1475575acb746d3a1dcbc3f7d2341f4dd0d89e960f34961592146419541d56516d1422a0b553a13e63d8b90b5e9453c4c0dac5fcae6ef0ca92711eefbe4fab7c9850e2e4ac1cad9d441396ee128d544aae513ac92f25d8d595e94dd2f5a188dcd0c53c6203b2e177af895aa3c148e118af0e1a38483c94848d9067199eac2eb7925d4d35d576ad3a31c405f819249246840ea6c25706e2753e6273f1d72d6a5742f6c74d8fe5c22d100c496c51795ad51d70478f4fd03bcd80dc9e2e2c782e3422a2ac6ee75055b2f722860b420c80fb221ea71b6a6aa7625748a1b4ff522e08309a0bc484523e2fe9062b235323080c3c4eb3131a08b882c48612da64608bb401957fd434912af654d54b68a51142b0ad57956f4fe9cfb4b1b4e16f82759b530aabbf66da38a2f0ae790873f05ab0899f39a94c29a4291b2d2a0f65e0687b5a1dbb06512aa55882fa626971ca48fc1194388ae7c46fa87aa020b5f48a506aad0412b3ed1043c30871de7ed596d969f373645beb1352db6e843b6b2f530b30987ddc658b08847410ee99579d68ddb7f7c0a3cf1a5715d8729ed293a63c362a97ee6f7764f95be05c84dc822ec939aca2b00e8255e13d8a58590d6ecd7a4f6cc2ce9bca793a2069ed2cfd8beb4de9a32fa3d41b60376267dd8d74800bcff63d00436f4dfd7d8a959f88e80044dbf71301eba6f43fe68e2052b6a7a34a1a940ff3384c217ec123c01ddd11a6a9a085bc8e7b55d70810fafd7515971ba5df27d45b5a0fc9b013d743a84bebba62d156ce9daef3191589400d208b147b5ea7b463d025513f8b2856f26c7e299320fe93b145c643e0376ef19783fa2495e8cd9ee4a87e53286ba4b8690443f93045656b2fee0cde9746f3cd5295328d5b51e7696d1bf56e57ace5f0f8a25ffcc8f417e527eb4303d8fd8fed8b03e6e926220c2372f8187dfe453116f30cc0a841b93f55b6d2057b8d4882510827e17609684f5c600046d5e2de6120cca361b0b4df969b483733581a57a306658c51782700f203c3430746262951c7a0b86601cbbd05fb8051882596262f42a58ac83e158790fe1096fe68704035f560ecfc13f2e5a2b706a01e813729807e5fe69ed2245ec794e8a94de33a81651d09416a2a7490971a9e5c585a1c2abfe45b4c605f93fc2c6acb0188ac7a13e719446c9d8873ef2acb66034e729ecd46084b5f317fe70c3efcc51abf505e217b9c34ae5c68d4ca6b35f14914acb740504679fdd75e3c334a87277eb0b44d8241a76d0dc7a37a267d8a95c247e61eb95823c97451e329da57bc1b438968a5ca1958c6cff66befd7d3724802b558111722e6c8168b51cdb7c7f88bf2e0da7a5b2ba50d17c03e2c72edda554d5fc0f90edcf4baefa40ba9e8b1b4deec694db0340dc22bfa464373ee5a726b3f54a1302d1ee537da6fdd17c4d46a7278be6b47d80b3db0f4dc668238598896a336f2a11fd19e29c163d3ce4f1a44e79cb1493b7d2c369d23570167f3ad7a4c5711f4433b77661ff5538ec736aa1fbdf539567a68edac3189627468a1116db3de747c7145d1b18295066289cfdaa365c49ac04adb3c34cc52585c592eb25889ddea793b4e5238d646a1f9477678fe4b9d983e8ba83be5a99521c70128ca254f42570fdbc756d9de6a9f9503afbab8bf682be891a290d6d590aa51f6909ce5fb61b030e1d12b98f8c45981c16705f42933abf66d30ce827a86add23d7414e1a34fa6be143ddd136beec0acd5ea6bc97521158dd928c958833d71abf48903ab29058049b2d26f3778905613753596e7dea923fc82e05b3f0ae942aa5aa5f9dc0448f47057f4505db894e1ddebc0a359e159dc861637f5b44adfce5e68645683391a942df9482a008032d99a65366bf1bcea3a1901c18ec660982f0c0070cc6b4dd17c87f364fcafc4097a4384fe5d639c9bacfa5fb341756a26b953ab13e3edef6b492e21f5e6c499578435456c9c9b427cf6adfab0d8fedf66fcbfb310faabc21e0ede85a2522472d2066fb90f8efa8d1da1bfe2415325c6fb1218fbe00eb55f12f6f37ed51be894d0c292492fd2b0d12ae1c282a1aabf01bd32b56dfbe96c7319b4de5e5eb0fd34e86f524662ef3a7874f04b0a5933d6ebf650e0c5b886aa48056bff268dfbec9ad0ad0341aa66e8c2565de95228cf74529540c7191dd13223426c651a765b8fcd59ac1a06816e7d0875792e7749ed2328d1c58ceea97481b964fbfe9daf0c6e8c688377705e87bd45376fd06d577360c847e5bcb508f9e2dc85adedc9ebf6975704910864b121f715f07a14d3b358a920d98651d5183a95fd44c59b96e7ff6c038af012c4515f0937f1ccfee63c3220b46c1b86931f34363cac26657a02ac181898225fdbb3aa69ab52b0ac8a9103bff9b7fb38f976184ecf28e465dd6c126df61ca1b902cfa947cf44477fc5a1312e26ecfad0da038aa1b991764e740c9f12ca1a91c74042a8bc3632bb0190c9321b5324f0e8c1bd53168791ee2454e126854d683f19f6f9bc2c4e5b4d532866fae2526b6c81e429825b5bcfb006848f9a5077b5cc2407ae10d285f91de80f723e1a2a82c066cfb8fc3053ca1521112237e480ce852559f5c5d6cd722e7ddf991881f87e5f0d989e6704f0bbfb29b508f2c217cc816d52af093e05dda309c81f919206e58fe0df7c7126f0e696a0eff3e10d13564da2fb8684b5b1313040fb03c640cc34322c5d61ed7d1b7598669d85ce805505bf47e65cda1eaaef755a2db74003b2f2a85ae38c9ca3661c9d41e9ad29823f34c80913d693a1fa3e4ea8fb400c2593bd003a3e40966a559a598268667be5b10d8bcac0cbcf0491c15a203c7f09402c67dfecbcb0094692a3701af880d434188a6ba4fe441133d2e7e4c673758b73cbe5cee56703e6be53ee977816c1df9b87406df03300d5f7dfe31d7f5426d46a70f37e1aacd9c5e805ae1236c903e003b254626a3b4dae47af246c62e209e060e5482a593d35ab16eeb6b62f5afbe749db8014b7b27c60752a8be6b398d5b4d1838a48a1760d1fa05397f7721cc57351850dc775ada51ca4253880bc6167219db77816e38f0a7cf1f24e44525abde8c7ad12355ccad70b9ad3c39078bc0b816f82af16e055f4ee2d1892c2167494ebe628e96a356199dc6fed586481f91484bf46a3ec80a4a9cf8359957438040ba5a9b5714ccec9e88852a95a911e7bdc693ce9dd296690a5bbed7dd3136d825dcae76c0c9e355ad04e2270b84ae2fb3b423ae69594759a64c97407a99726c41de08f71896ecd1f320ef8de948c95b65e9aeff1fb3ce832e4a026d8f6ff809896d523a4a702fff03db8318b3928c142e05c3e66bc7fc770baaa9cc0b265e36b50e37412235c1f87a91fc4abf769f4ca328b44a9416ff98bb9f4c13f7b1d8d82377b13bc60b06c42b3d6d5c99cf4b09dcbcd7dc4cba5b07a1af9f3e48690ea9ce3fc21ff507c6fadae696b09d5a991e240106d2eab2cbf3221689a3783090a14e8391444b3cf4755119697f614955598769ee5e2a77feab3575da506f3d1af59b214e7449452db1592543e13f82dfd6f604319de316fa03a3182f4d9d90645a4b2eccba629a753505149958e151b0275585049629efe3272c561b98f0e1b54f688a3ead007722c9595ef6fa9a4c9a889b37c746208bd12a59cbfd95d2d96f8dc041522be0b98b93bcb1c52aa26611929c29311db643ba9c5af9f1eb0451480d40f19fd05d01101f3f9b266db08982d21bbec2cec183c6ff53a022b60f76c5d242f5e84aa86fe4d56884eacdd036b90b50fbee990da955dc63f23c353cf2b87641a4c68b90b7064412b8449a3c8280e48dee3c430de024e67f8bce8804ed194377cf9226aeebc6b20a84596f56a74cb2027760ddbb8960197775ffea6b0e6be7a6f88c4280f9883e5342f57b7bccfee8f216944451d5bcf8d8f15994725ebc255324f3cfbf7650bc5b3a4fa27c7897b93ad579d06735429199bc3699f4e394d998056db22073ed428a788e92f4d8f5a1303bb6c9c7e39ae1c5506d82b70a1defe0694f4519998b4bb2d322d9472248006951884a8f93ded002b31d188594a5710203b70748d894108d57045cfc61d44c4a661d60d208c79c641ffed37c8729ba20ce1763f08a0930062f920ea359943b23a53031954f736229ed74732504e7190285618081b594ae10608fee0f2143999ebd017b30667d348491d133ab0dc0017581f7a0727aeb3b4f01d1a75bff8cc5ee1e8a739818b386d8bcbaf84e11de2206b0bd78157647a686a7ba3ea0cada4e30fb4b4b390af9e82483c21bb92a4bc518aa4f2ffa6fe3061d9164a60ccec1e4f3ae73c95bf9abefadc939c9b4cc0a9ba1629ed90dfd7eccb06e845f4cb6dcda3fcd66120ef6342e84c238eb22f233bf0e1f86ce85576084e0735a06bb355bcc4dd9da631f5163da3e1473bd994260fa3ea781dc38aad3631974cedfaeed15ec12930eebc611992db394508a296086cfc961d6623723454e9eab66d75aad55d2a1200e6c2ee06514170e37c493819ae195bc49c768fe73959cfb86bdaa4506fe22ac251c2d7d6d7522ccb0c5c17584ef5bea2c3b99b727dabf499fd8b8b21e325f96c45e03b7f10c2866c860661c60fb04614e02dc7cf07b79d853f8603f9e62486614e89db444140f091ce9e4f7a42bdf9d424c747131bc6dc929277f9f6796190a824a0452e60ef592d850d0e0a0ed4fe98e0f99fed6640e0d2a3a4a86aa1f25552f538727a509a57d5198ba263c2febb5aeafb63f9a6712b26ec328ddaf1a92f3a1547ec4c9ea64f15896ed037b81152805352ab4578ae1213e256ab8a9316a263877611135a148848f615e1e62d42ae157cd0f886a765e7272d1fc86a810d3556bbe32eb5126840024fea506620d6a78e95a45f3440d1ff5a97641445d0995ed768be26d4c0d6e01e2ede0caa12ed95e311b5cfef27a075eb495b7c567971d349e71ba912a5b01894e540072444409873fe74d744e2f93070bd9a95c227df758c48ca800b80ff06bd199a3129c6ce56086b5b0c8ba1f03618aa2ea9ef98db4218df7222e43dd6db4e27855d0efdc5166eee403e0d5fbc8ca90ba8fca047c7aeb24677df7c4c9cafe10bafe78ed16c01abfbd67c1ead5d9395465b0fd4165be812f403c0d4853e3442741c9b3440bdcc03e454380be6199e0caaccd5dd6eb09e5c8ab02ba3d444ba2e81cb6c545917c4fa89a719cfd612d8bf2358c18075aefd3a16c0319da0de48c81c574cc358506acfbf193b14be01d2517064f160d095ed710107b7f9d745a0ae2a7d10cc8fa688bbd4f5e8da1162c04409d434b4911d17f42ecd4efc240d82fe7fba6650912268d73da34608d089b0589267f04e721b6f4ad549989b4a65af6b98b57920b7eebfd66f9f26b7121fe653c9a75473a1bc88aa7e8eb224a47a72110e86ec42cb858c45bb74e6c42e4db227d7931128ec80e0e5bc66c386c96906ab369345ad01a62711f6360ac538ad093f7c20a07d653eee8e453632feead3fa3c6781b56d5f59c86ce34793cc9b146f636143ec31ba158baace669b044874f8ce6d369d57dc4cd31905e1ea3e5e0ff3a66667c95904a591260d9748b9bf1d87f2b59dacf9ea3e142a2592d05a535635e6c9c34e4af62f369561a46bd226e0c040ede42704acae9ea59d0c07618fc0b8bbdf66b2cb2769d52482500fea5bad930291745cc1cd6381c4a4035a9ba6845c40d619a7038e329ba5d511b528a6db0080ad1754fb3781e563903aaab56f03a79cd2e13810c6628515003392ce9ee11e870152f1be74c6de0547387eeecdbd4ac01a88d41ff7f1dfef7bc9d88808b0ff5148db7bef2db79432252903eb07580897084fa381eea20b995074ae81653420bb90097d46703e6d741ebdc859979c7ccef376e0d72103a8e7bccc396f07a9f9a6699e79a1cb67de1d0fea36cc00ea8d786116665f1197d28ffbd46f64d9a1eaddf0cd31ff6cec327e8c1c5c55c471e3a7038938b0ccfb65ce107a20d1c67496266e185b32441c0c28f9e8b064b9afd4cdbcec7cb4977edcf82cac3b6249e28677a55dc4a33fe91d8b27f24a2b6e847e69e5938106afd44db9fe38f4297b02092554b1450ba0100499e943faa441a188112031840e38910499e940fa14b558820a80d081116890640432d385f4698646881b4879020927412090999b476a2941eb86d10788bb0fc9fbe3bef092607e0769bd09c03c91e8d231cf9b747e444440576c30a3d1d6223d72da880789c33cabb6da6f623e224de99aa6594cfbe296c90cfba2630104c3b59cf4e98a828c260a24e2661361101e0c530a12c320f7651e12556e4e4a83a46ca02848e99b946e5b4aa979488362186436c1b08976d5a30645a74fa14f73692e9d67cd95841b6a3cddf63b04a418943d3f1db0ef5aa4f44db64baf1d0135cd2c1836c50e8641be0c72cffeb417839a2bbcae98e77ae58a076b2b30cce58a2586d98fb3db673e4aeff841abfe42596b62bf7ca1110ea241228fa6e2cbf490a7a2f4860611c1e9ecda1531c9cf19adf855b431258b7eb486aebea268d014fa065dd9cd30e3eb8bd6fcfc4441401c8b5c6ead75cd935773de3acb83f38c9093fbe236b7dee8c49cfdbef530aebb6130dab8a760359c5f28196c8fc42f3e46f0caa827475d5cedbb2f86b8e1f489b9f588b04b6c4ee7d28f773e0226f3b88a613682731a99b1ceaacd3aebe8b769a785b111cc8c7c36fb6d7644d3491d91dd14fb64b8c1c3f8843a67e408123659491303f7c5c02073c7b4729f562d7664f49b174627395421029148376e006540590e59968d9050f2314b3ee413a19f0b1fd85f8dcde10637869f99a952cdd95d3006645f4d12afc2dd92dc5bb909c5b4d89c5fa7b5573ec64513572479978f4d08e108943a33298230cc1a3621106db473b286c196dc2769b2f8f617475c1d32fba873952f3f23f2474ff62c407f3ad95a2c4627371acd309b00e989642e490c5aaf7a32c81e0cb20c6e19a4558bb6628e79cbbbbbbb1c915a2c46abec2ef998dede5f3801d213699f797606a30bf52673945a1b33696300f32c8e73214472c27c70e54b19c32f0cc360ef705900c9ce19e382cee516f0ec1a98736216c332dfb2392567ebccb2cc96200558922dc270455b8cb78562288632b74fdc52c8673ee41a56ebd5d1fe98ab316b4924966fb7b662ced857fa31bf7af59917a242bf3b8aa71bf8a54f31fcd2ee129d44b25125c6b00a8a26fa643ebd5168e4bb767fe1e4e47f2ec5c5a47f667d348fd4f273292ef6a544970b4a7b581f72ab15c4a6d8ead179769ea24314d2acac5656bf27212adfe6d2639c2539a51f1faf7484602df9023bc6c996946e65ebd945d243d26595b2b39f944dfae9753f52d064482291b2a02ca85f9866945e2eae740e117143c9d3c3a6d8dd9483ba7543ae97309853fa41bfbd3f6629c41c42244b8938e88c537cc8383c8cf34488cc51437ca19da77005894222d840a28dee901be2bc44fa30f8fdf030c82e852b535c131c87b9f5da336f64b58df3f9859953d62e83dcc22fdb67957027839ae69ae6d3397ef7b399477a4b64d6e69cd296c82ffd917a70c31760660df5696971098dbc3cf212c9cb961e3c3488f271ca479e5592f7de2a2631975e84f4d6757009fb7ed0af5ebf23b3e5a5c785204b96599665d96ccfd239bfd28f9f1f73f1c322a41ff4a9d3ef0883f31bdd1f288004e9c1203b531a838e1849259511b8e172d8bd812bcf0d4b3131f4f8500209c220fbe863b723bc4f7adece79e79ca3af47e8ef2f2cb9946e58603e111943d3eeeab3bb629f05e6c78fc8f48671e9a611f33ba2589c37264947df11839d0b2290c807af1e467c75cc26dd3ab2ac52d25b2f24d1d9f4b3937449fd957ef4408281acb981431c1864215bdc1765c2b6d52df34d7e988b5c577a911b3f811b4f44de78cc9d93ced18e87fcacd76e07c67913b8f1618f27d2f98d0fbb2fbca1d978cec398e73c1c3df7e23919cf05e0b97b7ad1f9e62fbc00bc7cf1a218d9f1e0bfa3975fd8f9d4421fc0b36fb49ff0b4fae6cdd7681032c5b9e6852eaf39c726debe5093df73abb3c608ca6cd6acc1e7a6e5ace54ed7dad46bae69d1e586c75c7af68346f64dca031249f3f6c27b04c9cff9854deab9b24572d6ce29bf3dd26ba4166b67f6b9f8704798fd308fdf64108b1d0338e34982395550a1cb5f1f4967e9dc8e4c7e2d559c7a27a548aa7c72ca747991d731d09044e2d1cdbe1862e01776acb1f6f81571f9d1a2f3842c9e7e2aa82b6300e2864d2385390636d53b4a7192eff8721cfa3499d2f9c9292ea17025cf2b2d12712b753ef3dd17caef7cf3e835568f07772f451be53aafe4a3f39087be30e49dcffc160a85bcb0fbce9b21fec679ed5b7398c7a14f07d9050ffec9611e635a7524743ec6c7160f7c7ee338ce6bd7b08e4811f6cea977df04d8a973dd8eea5a7ccdbb6e86cf32e8da0e46a694972f99e800948f1f35cd2bfd8868deeda06eb7ee34e7308d5ff374905d98e2cfe73cd9c5f7f0d6a5752de2e097d36e99e56ef34c27ab14360cf34867a42c5871e563d8c60dd041f4d4b988a37be9d2addc62e319d4539fe9c14fdf4147ec76c8682312a1d315f0d327cb9ee1339e7dc60266e8d977b8a1670fd1a05d3c7b61067af0b18363fbd4d19348e987029e7d87c8faf8957c28e0d91510bbf81829d68450093c5f9d458785c5a729d3312f3685355035506c2af190f19b2adaa86e63a0b0a95b3f6ce2a200b1299312131433854d9c058b470383d557bc1da48ab7c3fceb894231afafdeadb84295a05f51852b395f81e4b478c549c4a15ad1e99395a9ae12d4a728b3eaf97af395e5fab0ea645fb852f31b0f97029f05a6eaacf8b029b2beba8aab4f51e6f503444ab50a8ba4fa7a93f3d563f37c5571f235ba74be3a3665387f323cfe4a3ac8a740d146f5ae81ea97ca82ea2d5ef21e0cd6b006eaabd3579fa657a08823fe8dc64a18c652c3a71828969412961c36d1afcec2e4f5d559769815820f397b0304177c48fa90a5e6eb0f823e6459f29565f59545e72b0bebab63b2eab8583c6c0a6fd41d363508bef6123a5f6bcfd79aea3dfa54bdc5b35eec173e515fc0cd0b8e516dd165cb32cd8b080980cb322e13855c3e5e71b1107410a64f21b7d694d9305de4211a23b42df3be68ad87bcc83058f02d687dcbbcb46d9a83d661de7a0f36d1b71ec45a1bf3d65abb69598b8bcd3c76208adaa24b0cda64f75b14f7461cdd7d7f474209454241794318ecbebb9740dc43124eb8c19ad002668e93c930f9b1a544942f7d6c2d11c4771f5b4a4cc9628b2e6175b95c9810212e974b88cbe56a12d49a52513d80cc8ed56212c38408019be286d105e4322599f3ab29ea841248954a45552a1c547dd2716fa85c5575430837acaa6d652fdbf44ec4d1d387de04bd80a8143e45e6978d2b894fa5619e295f29755105600027781e8d94b86155813754b4521c17efbc740cc5c245ad75ce39abdd81437dfbbcf4adbe2e283de6f5fed28597375e76641e3685bc7ac9a6169b38e49e9999577da23e5d4e8f9d0df4b3fd12c6d74b69d9a797d862187370b224e57643866fbfe1e65df12527b7b0a95d0966e6c9e056e557f231e757f2ad04ed3dda61da6fb4737c025128540f4028940cb350a86f8f2e3dafd8a28d99238b2fe4b0003fe9e38c4c7430e288fc859c5c9eed2e53ce80ffbb51c2c95396a5f8d3854d9dc453df4a7071a03e843a1021559ebc08e48064f5f1c9534aa9445536515693ae93240a84b9f19402e9174a1d86fa0dea4e1da41b0b12aeec6ad01c601d731e34cf9cfaed130fbe69d63ae6f6f3b22f7ad8778345d976708e51c9cd27550cc210e286f1a77dfc55318a1002103080620457e4c8b0162d16980007539244610432eccd6a1eac0b2e501fe3177272a3ebe0217d9aa1f5d0b1f8f31408fb42179c7ea14bcb1d01655fec97cf850f63e48b1b1b86b1d309b1c1d6106536979d0e4262bf80f40b494142a8ab5fd853a5209a73ce7d482a8a3b1ba40d1d10cd33cff68bf5cc7eb15f28e736ac37b4def089858542a17a60c262d37c8e2d26a03c877589b64aa2091f569d24743eacadca537d3eac4e24a59cdb0f89752efb9064290a048466fb6048fd2281745effd3ace7d48c4ce6958264b3a65f6cbfc49c6601b1801aa841942aca346b0ad7ebaa4f718910dca0891e2250410513199e21411445008116426871822019f67a5373a4caeb245a09f9022b3445b32a50e59529b65a29ada91a820a85fdd939558af33d862c79e94c7e765a3f3d3872089e1d5613ec980f0f998356cfb6b53df8c53f3295c34fea2755931af281abc3da7296f02932b6edb089b2f79039da5354d54aa552a954aaa66a2a05841bd6141336b5539c9afa9f8aea24927c5853e1d693b251f24ce917a99370f3992ce26a9541c80e48d49123d1860a648b97e85a8154570d9f10228f8eaa23da30c1ab32e1136df10b94274f9c38e9f93006d19e9d9f44b074a8cbe5aaae1d702a254151c7b278301fd7eb07480a9320a03ed51ccc8a9d56125914da0936293a7af82ae7a60757073931c8a572dd485450d0111edcb0ba362036b5875c2e577545966b88ab4f3daaeb555df5d3b1821b5617abba6aaa4f3937ab9c154be7593a1fd6179b426ee77b10e5d9eb930a4514eab84dbba15a3f241790a11fe5583026d8a24b485532064a3c4bd1a7205c86366bd6e072485f2f99e389127a02cada2c8cb53e2db8217dd117147e55f902908a15687424730a809cb029249172b8c909621c186429bb4ad938489c170e3f1da49b04844152078084d1356f40493a9c9483cb322553956483ebd32fad73ab4e8355a775525547aeaa0ef6c9540cd60dad4ed5618a63b7572906ced2b4f2646e091c6ed4d242bd003eb69268f2dbc7560f54dd834d06110735810c02936294ac3de286d4674bb2a17a355d87353b04d305544925258982713e3e3efd02ba0f89e40096f140941f3675b523c8aa90a201b169d29cef9796915e4882c9d11e16e0001f7d7a30d1063bf59139fa2bc00180f07c72962151a1fd8454fa966241ca36a9d790994a77777777338c0184a4c36855909d11aefc88b8528e8036ee8e42528b6f349b03e1329c75393936d9d1e6ddf4adf351801f304f3deb76506cdbe4731c773f607e5acf5c3eb5de020056a0753bea84110b307df358880598ae7923f9c274e95d001071ad4f8ec1d9924b82460734524a2925b394b24554315a59848d46af6b2d67735a5a7e445b8cc115899859c42296df55e11899bbbb3b7294cea3e5850aaa497ac8d19112e80f2c72103a486ca2d8c3938343837a3dd9d151d94801f2e95992534303e5c5da51a20a92e2c407c6856823ba4fcf929c5903e5c5da69559014273eab255cf303a5c5924aa604b9423b577e30a11d6649d2d3a9e804a11dc88e45845073429118ea5989c5505018e4f9c9570e6ec8d93c91afdbdffc42d2e46c38159f3a20ce864d4d391bcee6d93ba06efa016116e393a4fdc957088545b159b79ecbdbeba1bfb56d638e21f285b6a19dabf24d2b3f867670adb5d6f6cf2f4432049bbf8f59ccdbdb923ebe6efb04bac81c32c8173a743bccc0dfaf48b4d13930d8ed32c81c31bea079dfef398f3fa4833b3df247366d5a8c1285ab7ebf7e5f93ed844d395fa65b275c6e7ed5250a8660e1e686362d92a5277f3a142c4cb91c4591e7062cf8d8f910bd54020514b46777cf6ee284133c45284210085250852a3cf1c42acb410115a81045143fb592666af57a337af1ede1c7cdd9c4d521f1b8bb5316147a7a5619102b800214aaa842c74e21055a6841051541b46777cf6e828382285132148433ef43a5e47da86c9ee7f6e74365b30213ccb9844929a534b4044a29a5744e1d4deba4734e3a97085a0549d60fe69c76360dead3b5b749f6b3fdd273431a445dcd4e4df0ec4141414141151514b46261e2c9939beda656c93c9bbc81801c3020487ae286f4f5b25166d6e9126d746def6984efee8de4e486f4b5a9d8145a90b22c3737abe88b5f5a4a29e576231a42d4585c9a5a6b4f8e0b6171d92389c1d93c5352d9b54993966cc284db4b3f7aa6396e8b469863c9f37553da725e8a0416503eb6b070f2a1c582c9db3eb14d90eae699a3943669a623279d3cf3f474cf699f87fab07658ae687e78f6bbbae168f6546233e5863c6d5237bc6253a4e1540f37b425ea114aca875b7530a8e4fa879c32e2869cea48238d4d7bb68c91c73bced81c67e2a472b3a289139a8fad27a9207c6c5d71c46b3cbd713eb6b0b081e2634b08519ed6918c314e0665376d540cf61d692dfea5cce3db12e7eec226ce59c22fedf17ea1afd47dcc844126382d263952b8200a27aa9831a21584114d380084d301ce4a22b08066e7b5c4942b64686289193061841f4820029e1a6f495860411147bc204a15a828b2c0a2c609408852e3248bccde2357c8010b092240a1042a1ef0840b7810a4848a971628d43fef949809eb4881c9902a7058a0640708384e6a08fa01a2678b272449695ddce47ccfe480035c588106403538820332ed2f1c20110426fc90c4113c906997377dda38f0832da438420a142664da654e9f587ea0041438c841cdeb0899f6174a2ec9871b927e8a119647dcea75d699c9233b066053ce596be7dc84f2e699d571480475555d5c572de527250eae8e948d31e78675c5b3f5b0295cc002228ef6d5112f5dc75da9b0d4c0c6186d149183f3ead56ab55af90ab4ab95afa2eb027d1859917d8096bcabeb0dce6bc90f13a09d564f9cdc0dd7c4c7466cc4b5b082eb33b8b800a3a3476cc2a6e6c3d82a45ffe9b391f0fac95d06671c6d3424da9ce643ae7a3c44b421fd7af246e2883844b35609390fab730c4eccbb06607e473d1a8d46a351fd42243af0a05f994b1271489d66c2e08ac1226038c370ee768cbad53cddc3e064f5a97df865fab07c0040c4c57c340306c37b7e7ab7189cac23dc50a69af5336cd4a76c94c89bc9c42bb1f3d72b9992a95e45ce2b5597d3632b8a9eef1b9f97de4ba637ceec46f5c96bc0d4b76a04c39b497e86d2033f3dca1c230fa510cf354f7cbcfc74522b86d9f76957e5990c847c614a61721fff106d4cbfaa1b36aa534a1ad535a89f4ef44cd9ed10f97459c3a6904f9749228ecea74b0f4821648ecca7df40e246309cc5a7f7e0130b3e1d089b567c7a1016c25d36693edda790a444282498100cfd77008088ebb19ba1fdc59733ea79e9307c58f09e5c4fba48a5846f96484d7a46041021924626b937e69c58f5fa853c84490983d2fbb5c22f9c6cd1658bf38676e001f83961f899809ff1a5a50515dcf6ea3d6878fd74208cc31036d128744a98c7af3198304f737255c6271108c70737ac1b0fadbed271b15c3c94049ef200706fb4e144936eea06e9de2557e78186a0412f7849839a09038ac40c38954ad1140e2e140e2e570c12c2c3940e9140644208e24b08154202c1e568b12c8615742569c0821b3ad599cdc4a4d4bf6bad1197a5b3ec026ebefa160320a5ec165e53e0b0dcd0299bc7874d217d390182dac3f3617552035503d503084b1296d4d71a066d544fc346098d1a066924e9a651c360a591fa29e0dc28895d0d54d00d635e3a3ae6d5de0c158335260a9bf8b361679f919294062b06c788f1a2005428812115e3450d140d561222a0c1ea536ceca3c16296dae0f4c872f3d5779482cc48bd52108ac46e419779c878e9fcc5bcfa145bc2f8f9ea2f4a2f5ec4bcf8f442cc4f8c1418503024090214186c625e314130d0c05013c6bc60507df51798ac5113b2d4a4d814c6a8a941c3a66ed2c487dcd7b09991fa1d4a36947898c1d32fd54b1e65a857b2a165864fbfd418505c1925f6e93fccc8d070195e895d86ff302343fd7e0d549f4aec5ea3a64f25fe4a35509fe1ec3368b87f48a6d390a1e1486438fd4a4cc323797703d73f8614378c917407101a19acaf3e67e87cfd92b861cc8cd41b914fe386c1eaa10a0c67e07c8d79451bd55f38e1862c3ad4c31939335c349cc687847e251b68f020c36978949131e385647e251ba6cff0191f0f32d40619df0c57bf54a7f1cde0993c9b8bbbf9b0ea6021a509cec8abd3c02616af0e844d2b28faada07aa61444868c4f25a86764566efaa5526f6565a77b85d5236fa5b5c2d3c3a0a679fbc85b61b129aee8782b3b0c56676f85c5207bfdeddad375adb854d8634131585d250848256805d5a75210ba12b43223b3e262b0faab05a885299c3433b1527d25a84f33bc3a0b8a4d2d78f515179b64e41c1a39fd52bd1bb1c0f018ce5e0c16485ed88247f2195e94a111f362b03a8d9cae9746ce5796ed622999002b273db2d9c5a6276c6a11c4bcfa14c3ea97ead6863786c56075e981335b4d131ae6d8e36ee6acf3a1d2f9c08d617d75193c358c79f950e9e8985a6c62f7a16a1d71dba313aa9d8fdd0c3e541c9bc218d757e7689a791faa56eb8884b81b0daf148486531a3e2333c32b0599e17386cfc8cc0fc974191f4c1019323eb05faaabf870c117ce09715fb88e66169c763c62380c96148baa5faa73b703080b0b1db822af51d32fd5a75723d5527654098518f818f362d3cc57777d6c3561f3b1d504cdf3501faa159bf80be5fb50ad9ec90e8b1bc37ca876188cef43d5e2f9f9d86a62f31f5b4c44f90de37ca858acc327961a7ea94c98c96ab584c1a8e2cf876a15e3922b5ea34a160f98185ea8b1462e3df0495e7c185ec85e01d6f357441bedb0298c79b500806c6e963059b189abc22692b33146af34a9e76434c9c8f3475b855e11c714e968abf6505b7d2c3fd1463b5d89745611c7a8a2e897bd220e18cf7123960eb6e8127ea86fdf384dd527ec0bb5ea67bbe87315b724be3dec642c09b53256b4d14e3dea39b9dcf29b97136db47b53dc100650cb4dc421bd270d3e8401f4ede5f8112e7bb802f4ed2c2d3695e25c015a096261c1b86909fa7619ffa1e60f0e821bd254c80677c6af517e5cfc94b8116053c93238fa7652c441bf1d87c98a38607cd3d4b31046f2c76590e5d36e29bbbba594524a97739bde0ed4d28f5d3b0fb41642506e7442b5e44523b82cdfda5f882b9d3b06683f9d52f0a9a73d56bd01fc0cf5a2eca891f07da87456ccab5a77189cdcb17b55894040eaed5b1e2d5ab7856f0a0ae5dbb5a08883bd37d537ab5fdac32d35459cf2eda36df1cdb5f4501be2dbb51ba740540a0da253d8445d7adc9cf3209b734838df3e1ba4ebbe282b475d6c7ad2523a518497af50dec8956422a523a0060142c81c72874440ea79cc98d5455f7c8a218d4281a88b4e114df48a08b66d2b9cf8f6e9dd7cf685371e0c5dd8387a0590ab8d6099eca36ff709bff10f3e2c3c28498f3c3dd87ce4e9618967af24bf17def45b85d2e3c238ce620a149ce7cf083f9542b4d1bed9183cb782df348ebd951f3c30dae2b54e7e1bc7b1a769f15b173485dbb42c1443212a25da68ef5e7a9187c608bf31c7c96e86fafcb57fa95403e721b7a1f3ede3e1e3c188209b7721e7a1734e86f328e3dd7ed9c08d21c50d29504781625471e5f36fce79f8f22f11077d420dfe2092a83e7c7b88e3dbabf0eda10180a28d195c170a64fb45038a38648c36b49b88e3c5f74bf79cc1530b3e4195a6a6aa122b80bec38af33da718c287dcbf9c68e0c35af3ed92f261b5a9aa7a5373be7db2bce97617910fe639524b84ac45029743c5711c17b25a46a5d775d4c5ba7113558f228f42d1e9d3fda8cba5f32e9b841b521e9e4a796cd824bbeb42225128e4b1137d3a601fa24a186caa62d08641f9f4864f522645559dcbcea34a6ed8e4456a436f3c8a4373543ce6b48a476ffaa5fd7a54d52f9df2c00dad4d91c9b3e3acc9cd53192a377530e87cb2a085096f380b445df9c32fd47d60784096df20c1dc0629bf50b7f64a9efb813bbd93b0896b8d3d1dd5935b0f3671383dd8447978faa5080984b1ba98879125c3d78341da36fc4245a29004e386be70f4d9b661907ed8364f31185f781f63e1cba167b7036b9b5682f3e2e3e95097061017f39071a44ce500030382ee24d2bddc74f4dd4ff485e04e2734005971ea426e605171ea453616effa8a7c156f3288619fe471f2748a9c70f9a73bfb394d19faa3ffd852f27afb617f13ea00d06bac496131a869526a92e7354dcbf953a422c52bde611e76a2849f301366710ffbb08b5f99929b1ddddd9ea4e15374e2a3b94ca29aafb95cdd684bb49514496852e4685363f2dace6bcec9e61c3685acc56bed83d7ba781d641733ccef2fc2707764307c6816ba683bb36ccb98cbba2242ce1eb3304573e99ca3b56bce377c0ab9c6360c6a1e6124b945d4b84ce8e31a7ed1bc5d6a5e6918d43814839a8ba05c7985888373cd251756ab827c4163b945b4a1714e28695e73ced1a86bd635e71633d161d3e63baf69be71be79d775675947fb883050880a226b2db5de7908a3a1ce5ab7224ba26e29a594d26c87659d5a4aadc88a2c75eb226ae5cd887a0b0d391585bce89a87b9ec324cd3442291cf105ff495ae131139e622c7fc3af7d6c8f5445dc7696e35d7d19b967998c42ce6539fd8a647170be302527ac3a67b360d406e10127343061d4b602e1273b29c3736cac9cec537cd036727929436fd284c68763b326abfc92f527a497a7be88b3335ffcd24790cd3b9218cf422dcbbb0cb7367dbabf30c0b79c872a19138e531ef3a0f7577b2f3665e668e65aea3376f162112817c12d96e8785a1c2650f61e2f569b911a965a562c526a3c87919848fad1e9f9e2d765b1021ddddb2874480c11683928741f6f6994b563b2a9c1b46829249640d831c812146726090e593db837b84c12ecd5a9afd754f492995f449949ee682ec10c22e8bd8acf5195d5cea0da04e04fbec906e23fcf46b1c308a51a61fb795a8777b691eb9475c8a439f7af00bbb7dd1220cb24f9bc5ed115a87e913f74d7ee9eddb213f1ca2307b3a8655ee471f9d015365c94e9c5c8d515c5b0c2b8676121877d0efefc8e4527ba9bf1ff48f3028a5f30da2b8fcd0c88745481ff21aa2cc8aef207295d0578a2ff2441ab85e3d3bad78342b0e84cbac7c46dceba17bed0d7d1c83b7643df3c86046049c8fad214079988fad2130f98d39eb95aecbd2fd5c900143af6b419148e52bd9230c669e89bcf81f45e0d7eb21177911eead831dbad76fe6f6233158fd7ab2a7b5c40575725a5f5deac81d9b79cdbeb06556cfb8bf3ef9c4f110ae77b7e36eaf11a9c55dd824f2ea222ffae659979dc6dcedb09be8deeb33c4bf5f49c5895cb7aee25c68e44337e4226fe643db10e40bdbccacf4c988b055a7beddd1a54103ec9857cb9c6976db9c3ae745666797de0cb1db61b7deec66928e791152497e13f3a8f458bd05a401bcd183c67cd45d8089410795ee22e5c914a2bccac7d6145c3f838b5b8db01df261a94af9956af4d1c9b229720c52f758af942b3f7a09f312e6348c413f3f7aadcc953dc6afc447e844b59e98d272028bdf44d3ab08e886249106b2c69c02f2afcecddc3ae740d8a4797b10215c29fb7ed0cf3cfb465ac5b45aa565b0ba0cd48b903e73ee76645f096390d208743b1201ec1a83750da261f870c3d14f4a676a83e1c36d67bf1cdaa0caa8b42d5665392d15cd0c000048000314002028100c88c442c170381ceac2e41e14000b8fa24876521949a3208861882963082100180020000220202223d900c623d18bd438286bb7800451b09ffe1457277da1aea0af312e5ffa10be0f477ddcb8ac61534a6516264d7cb132b1f51b44366e1e4652caee1341bc1dc08279b7cd33a22008adfd028be02463756f260aa7a1c9efd2244f8e75c9dc0e207403bc9139439d6545d800e07a1e8081c099851635b8862ef550d8c68ebc28fe99710c9ae5e6f0c1bad8d3d82d35e8472deb33f6ed45543c2c95c1ab6b48faf521dbe7ebc4d41fb3d31e3ab8610ced78ced135fc98a31fbad36d45ddb7d6f2fb22d22996680a8a5416d53e7ef36949be6cc22294ce693109750fa818157b63ee7d40ec7385fff5020bac54ddda6eb62e5805da801659affdf59762bc20f32c86a917022c14300fb549c8b1c1f6b00b96b97bb742a0343fe9b2d37e6c4ca095339a3d2a0bbb85d70bb3cc2b042e7e4658e022c8cfe9dad0e8a50ea82c4f2013e85c4c629d1d798705cdac737e4fe84600528593cc7872833c94c9e498e6c37cff0d1c1c51da44d464678888e3b3bee049ecd5a00b460e67487d094ca6ef7cef01d65b248ee8f409e063ea75caa451ea202998e258b85b0e4f292ec3dd191a52e95f33242bf998e200843dfedf63d9fb0ce125cf7125b4ce1ebb294c49cb2daeef21fc00e3114186c8ccbe4039eb23355512c908351410948a61c572c91cef6c6e53ac5d3b083227209c8600c283838aceaa8bacb307ff466ef376838b69971574258d3fc4c11374e8aac62d235d13f84e50c26962bcf15820ca56b7d693c22b6ed06cfd80c2c3c6a60d3f740b769e2befa9efded24060c4db5c2adcd927d15371bc162b36ec406939d0864cdfadca9980e1694a87576320591575d3a2398c9c598b58ec0bb0546c86457cdc360eaa74175688c7dad458e4cf513ab0c13730d442a19943156bf637db9847c45acc94af7c3c96319d165b9c636edce4324beaed70bf796a8173d2b55f3b66f4e90c08c7b47558b6159b920ffc307c4efd5e9998969b81a7cbe75c689b4154e3e90cf0aad9b65bdf5213d531dc57af60acdb15217598ae7999e10008023d34dc52708b00f30582471fb44be428e4b3faa80af51a96d515256838fc9f0f871c7fa10af1140a9176467214877b8ced8b6716911cf4258e090caaed442fec0189dfb4608d547638f5f289f2008c156b6c45ff9a361affe699efa2c8de5a013f9622cf6da4a66ddf3d1dfe630b4dc03e112309dc54f794fa85a8b4b28039aec8dbb67c50fedd74dbb83e1e766d99cc83c4c45384a4ed6e31db680d7db5d92849760258a43047f4c97f51f08fd4407add42c5ae0bf8e1e77107dda3b6e9bcc41691d701f1757054f5f5b51d2fe3eb38388feb567fb6a5d6245a28dd33adabf43e215c47358ac7fa4c06e23962d0c70c312d750b5b8f83b6c0d91b560c00be0c667daba1210bdbc8a8c580c947bed25445a116de1fc9325f29b46f8a6a802180ff0678c8ed6dd290e4ac6aa27d95a4f24a104a8057336ab298910167e8a6dd17b59932a6df505508a01af4380b0cd0cc0b058c9456b2d88f3eb527248e3eb5e03dfe3649089019797bdd23c27b64dff8947479615c83518a5450240e28786041b77bc153f48dbb7ac74081751805d91c2ca38fdc3581877eb5622dbf838eb3012c80628921c3e45b020291895a3e66544539d07dfbf5958bbf37a76c1ab95c865ccabe08c7d0b295ea149542263c466b7b5264c53871959f7a5f564059ae5176efae262a2383cd6b6ae7d5f46882d72e02d0be0e612186b8b6f2d870511a2ae60844080326153a84a4f0251020a2835f68eb359afb4005afd408dd99780966ff9725b7624f22c3f04550a65ce487ea7357cab112dc614752664cbcb6672303398343d5ce87bc15d69f9ee458192e3403e467fa97b1d91fd2505edfcfb8016f8d7418bd3a02e189aa7ec8ae9ceb51b042f8a3782aafa8ca18166b43ae00dfdbc97fd20f0b9a960e844c2039189d4e75217458c4327e099b6bbeea796ff7f91b988804c968e2cb0383610d9710f0338209bf1212ddb64bbacc3f38774b3c8c5e30c51c254402a25b0d8d369ec41733187992e46e70780b5df90f1e262e034859f5a31734ed274b32cd42a719b2cd694047c50313343e575f938ac83de221c1833953415ad1fc95d38c95a2d11b1ca9c89a8b1bd2f2bade6869c5646d5f762d64a9f3aa27bfb22c1abca163731313c89a05bc1869f4e72008c31f60a84ef6baea3744c79f959ce729758eeb29922ad5616b8a0fcf43d939a637bdceb94fff4395b139837e82d47323dfca4c9ad7eb0c724854359aa058c486ca0e0f47225106498a8b547148baba0abf473ad2186ba90688f61c280f49bff8e0280f2b35f5141fd8538cbeafb1d7f8ea032d6fcfeee01ac1f3f735fba0fc1eeca6d36c831fc05f3d3c2ef6441f62c1d39c1b95232213fac6dfcac49c65d1bf1db2278f7dfefac4f81a0c9fe7cae145ac225463d3d726bb3b2b32b2ef71c21cb2fe0964056ab1089378ac129e91859ea0367b577a4cf6f3ad6e8ea77c168b0d641dc3dcbbcb8681e734ab13f28dbe2afdee33f337a27774ad2f3d6046f694ae7a17e0a09226f95087e687326c5011aa8bb3285113cf83f9dedb57771fdf0c2599e666dfc5b682876b99c28358fe4cb84e7fd521a84930bd3b425c029a0d902c2dedf89719fca6feb9d7cef3257bcfe7a3c45f6d58ea32332d0e15d2ce92d4627784f0a675e0dc5bd2afd003ee6bd946c1426fc778b06c23766303e719472cbdb74443c574e07d71e57bd4518625452597a65cf500845416bb6e38369bdc1b244c6c6fb1f94f177f0c39ea52a6992fe59e6850953b95d35d187ec75dc12cd2f092ae27ffdab374ccfc3db7624077b16e228915122b56591eb1ecc39f74c063c3aa7c9589653bea80bdd6e7583fb50c1a9ba898fe5ebf7c3b63f97e306581eecd895ec5be2071e8d15aa0a8fd5ca8b5ea76eae5a5d7fd3bc633c8362d26cf46a2a54b251effb26d0b4e44e4201bd4c50487a178a3325019391a259d51504e199ac9a4509160dd69241d41249845135e35731e3704bfab60cb7133fd0d176303881d65976b152f7edb7eb7470718641cc2e13acfc2c7173feff2d9fc1e7193618d168f9bb6318d421605d032a2f87e435ad0422e3f29c639331defc61481976b646b0b3ab00d3107203ffe669b666e40e7893e11e8b52c849e0935504b11efeed9a49a8ee73962fe450b1e02f6c02347815a481299213b4c7af813003c1a0cd694a218f35a3d1b89597c66e6f4d970475dd1c204413d2312d2f2aeb2db8554ebac1f6bf2d7833948cf62e0512a007804bb98dd78a18f38d51ced89e1f6290b00540df8386024e3c2c1f665eb7d2d4bf846d792edb34ee9b2e10843ce838bf3728f23033ec4598033bc3f8c0a8b59b5e0b6d41b6931ec91b9f7cf0f341253a1c8d87b459df09747a931194dd372e6132011a82ec2d422e80f21b51de0d01fdc20db5193ae19aaabf82e84b5906ed052238d192d20a427ec3526c9b26608e549e7c7a34b535716003acd184184b0eef6aae60b21951dd6734b626d771ed9434fb6238819165556f8480333b6788ef63af3085c4b96a34b6476ab9620a6522f03d99bca7af420d59283df7235dc0641951c11f90c8b1e09f87328bbc92e25bf8c6c6f94cad21b136aae60248e46350ba1d4e4854f996e88f6647706fbf869e9228dbd6bc4d7985b9b765459997dd2bc7bdc9582a0200ab6b25d4a85fced924dda9f3b282bfc8db14d3d26d39110f8b86759691a427b4e536e0f589c2c1804c0772d4e0907a8106044928280e98c4866f78f8efa1b28a050220d11c937af10675d589f4e00a384663824215dcb9d09e80a0901c544f3134f10c900106cdadd78cac8fc7e3875d1396e22e9a122fb5a857dbc480221284239de7d17b36319af6f52133c3278e8300e61038f3450c013d0afec567803e2eefe5b2d90c93ba4a14f47eba675d154c47dadc3f813afa8ec84645331321e39426ac3a5e3e863e179d2bb78a4f15a6fd86eb6b6746faa9ca471ef061cee83203603d61bb3fc6deb1a9e742ff40efb917ce4807639addec55b4ad9f21ba4a018947f52adb111cd63909097b19a478885d314f61f181fb41d21315842b04b54ef7130c24afd48d2785f1931a5a41a14315584a524994945de5013ed3110a3fb5e091f030266c435cb54389e7ffe2d788ee51504a0cd2acfc47a174054d02db4f6281f8a76500799c267f7cba214ba9bb7e8f2a5aa7beef937e4af624db32b7c7c8766da6473273cbc0805a8881751888a81bc3a958fd0862cf03daa9537f0c8b34a99cdf88c46472c3ef01d26db7da2db458c4abe42800b7d607a2f6ce26d914a6ec1722888920113930bc3972a2bfc1e3e4ad7ade91a59f49751eec79068b54b80b3393071e00bd45115e19ae544d3961e1f3ff93a7260b1301beaa2eb3a0d2d15be424c45478b04556ad714eb06496f5f337acd152aa58138d57ea559d8ae64b99548e566d1884da36339119e9b66aaf71e0751bb9599a33fd4139210d6c27d5602cda2371f67467e94a31e78d2e385420a30147f51a6bb779c3a0d7a7e466aa0e6b1dc53ae0d888a61c3cfda5dd8b7e24f1f1511c409332a46d0c0d528167a122412bfe57d25af9a0db96691ce6cdfbe0d3d681d012d7f56d21d2b8010b5e07d8ae05b79f163510c831bc04e7566e37bcbee7450f3fe42bdcaebd45fbded28d3effd00009884247fc06e0bb2e72a4a4bdd1a43abe7d67b038f6edf381463f967041c9e786ecb7d84fb936d023b10a4efcc172446afc555dc87df6d048c7ef5075448301a7f082ed129eb5c7607d7275971a46ea5e682957598dac1ed33c37c2e9115aec0a588c0019818a14b1183a6018126459257d2bf9834451d92c75c972d1afb857b3b25b42349941ee665b6856aba7e5254251e05201e5f7b17ea3fbd6a8fb6c2fd648d9f5f2fb42a7e3611ce4f6de0bf081fa2768ed7eab82806ad17c59ad91ea72958cf84f212ddb480f7d7a850409f13a9bc7d362ef5ee5f5c3f8ae3c9198859b0671378ba0061e9a4d0e5c8058d7ce5c7c8392ec173da72eb14393bc7100f871141a7cd85861bf11c71ebf260a9e7b229a15c2a1ed6b67f0c99f9f3d4c0610439862dca8dd7388a805b6afca1009a11ae1957677ae86532c86ead4dc4e6d73a1d697f57f892cbd48fcd9d1ec00da41872e1e79ab62a7febaa01314d96095ae06c3de3939a7771e3c0d3d35a6e15b82724ad4581c73bdacf74d3067d5e74f9d13eab77b8288d3dcd823933f4c291b74a365c97bae0e71f5c242cb5077683056398a13a395f7f1355885082979629e0b60b1e3402a02d145c00447893eb50ea27f88368d60e3b085e42a912dca5eca884ec53a4759befaf6343bbbca52ef14205a3318493ca39dc4497a4b1f49af371baddbb8eece908814033d756a1ce4eb4b93dd389462fcfcbdacf5d55eb0c054ea02a2465c98684ba687de71bd28922de66d6f9aeb8708652d4a9ef5ba2a484ab6357ac0fc935fbd7d08809d42366e7e2ef92611e43ded2d131eaba3448873f3c356af1192f0e057f736b3ae67e7ad0d69f7c1f0d94b6af6949a2cc9284d22be478ebc4b8c3c33a436406b579cfd5a8e41238ea228ac20f5c542a781a6254bfe8ce992c5c6179b4a0be039a9325dd86cbcbca140831d128a042e3c103e39b04aafd4b10db073d199d5f3d7af51bf505b26a6c01c8a5e624a7e7240b6704de10b4c6fe36a8ca7b85fb90dec7919740611d6ccb3faa3ae08f1704c85ec007f57987eeeb9af87fc6b5146eae44076b19e0ae0b69120cce81fdd301c5536b2a69fc367c4a84d0ca6a05bb25c79fee0cf7201507d55d6a5492f82b9428987b2a1075b3d3a09b71e6863ce94a16d4d95ea211562872162d05aca4f278b53cbf06531cf7ffc0f1594a6fc402431b258734bbf292a5fbd8556f6d52c9411b4b892e49346d52660959d944209f965bec6e178401e4b0e7b681f10f9bff6b8f9c70d85e658fecb2627342314e79df88ac38a349ff2ec6181c967e7baa4dea5ba0818494ca18f7194e2e1247bf96645384ad09115676c491e0421d2d2ecbe06ba6650a3d0de2e6b1b153e569b3ecdb06a283670e730b65337f8850e1d7708e02fe51c8818122a0d6ecbbc51c30908d636b7350a4435a269344e97761e9c04934b2cd713fb5ad2f9d4bf36a35de6a289a915e212e7a2b69869c6b90ce6a6ed5add2c1909c682774286287bc951f1563dc16dd7e1b707335a1e18430b6866a83e124da46ce4c23c82e8f9c30eb420cd3d4a793a3a3d8be08a224f4ea36b5c482c57c62bf45a6095886c72f0c871738606b38264a55ebc845af1045629bd510185e13978b3c49fa29ac096be260f66e1c524946e93cde8d28ec9f4ce8498d11acdc0784cc01b8e12ecd7c81564ed70da401f9481a59de0d0fb441ab534dfef266aa37479c1fcf3613a3d38f36f212a956e59fc3c39ef0b3e57d5e00ae62b3f88b35bf70d9cf0dc76dd31588473bf0a260199859f3e0113cfe44851653633c141cb2c435210bb6a2f86707176883b95d4765471d6cb12ce1c6057560451b2bf8665333640ab3f04c90aa7b10e8bb11eb640555a2ba2e1e15060b264c3f54aa9d53049b32661a7db50bf98ae8b88179842b041e17219a1652b5cad15e81db626aa9dc20394ec49f5e0ab53fce005f87dc147c166cb4d7242bd61e6aa3e4a2c94bc179cd4c9d53feb37eb6d18f43cdc3d4a15e32e22fa918b63392795288504d76759df6e73c508ae2107d425d29155a91f332a148150b32d7a32292a72926897595174d49987612615bf2cb5ef12cd36c1d4b5c126b419b224eceece1012d2a926261c532dbcb1dd241c7c20a8c183adcac81686019d981474bad63bace8b9ef4dd9da7eaed74ad21c1c32b90c067119bf18c08b28a1b5498ab2b31f365456aacfe315bc4ea0600900b89eef2e69b515293ce9ce8faa7d4fd1f0c00bdf85145be595a63f9055b004cebd65ddeee265f76d1b285acacd959bd0df88cdae5407db756a594872c96094953549b96b243937207ad45b91a4fcc038679e1d28582410041e6cff5401a0edd6e64065196e5e461206777530abe9905f100eb183e5c254d06a5d24d4462fc3ff2af2dba7939d03830e9425adf3becfbf28c0f1806a9061319c0aa918fd89a9f286810fa6b6ea6aea188de3b21c4a7b51d1f416c48b630b99431829a807feea380d7c682f1bd86852b55cb3f8250edb3c9ede8810a70590c03db824fe744b63fab841d7a482d2c6507162113a6156168303eb40d3e640da8d874350ce6e63c173acc27b896cc3deeed5300e7bcea43b6c3515b6574e918a5c874cf5193693eb868654332744eefd4eed6f32b10a4f8c2c14c92068e4d49a60efa0a7653d8f3ee05cfd04547d1cdf97af5b4fa9a185d6e0caa8e2e7a45372e5f2f9e564f13abcb8d41d5d745a7c89b8425397a0043feb1168d740f6ded3dbaf74f9e4b41362db3fcbcc6aa465c6e96acf3ef6ae123cd356d21021277d25b88eb71d711f36c0e6be20435237c415b34c51ad2c5cb90c6b0cb465bcd9c2a78973706c24b5765c4d8e1c5de1e9b3641ba4a4b8bcc668fa1975176c1d09a0a3ad0aae2ce828e6c5a0fc88d010accf9c0c3716c97ff780142550179dd7418341da1483cdf878a70d0b0ecd95f32f2e4e5491eb45fa1f10e0b284aedc671c934b36e3ac5372d7f2f9e5e4f17afdb8d41d7d34daff846e4efd5d3ebebe275e7061316513fcfa48b5e516fcaf82df47c55b274df38b0c9d2c6b3b959f1b4fa34b1ba7483c9b8d66fcde27dfb3b94955f850c2009ee63c0ef40f1ef6033ba67423aebd84244d5fbf605c990fe8aad39a75a517edfbebd2cfa1bb4c6041228ba8d447d350608784f38a23bdb8ff17dfb885e4bb13bc6d1ccdfb73fc1041edb76b2485b4d5c720674557b6e176d7b679be0441afc18ed1fa4ced73c81287dcc34e326fb2bfd22b97f3b324e63b0f88976f3e04adeb71f797e11e03d2075ffd6fd4bfb9e3bdb142767d7e54e9a6ce018f7495a1825407f16336619880bd1b88443dcf6defe3b589025fac4814d9636964d0ad40c216e60a259adf87b5d32375d390c0a4b0fa0ba77c05e8c2487cb84035c15f47165213ea0633530a86927a18ac12f186fda3b73480324411006d81c8055dfd579555c9fdc3b6f1a93c2540a0f32f40df83cfbaeb16841ca02e8900270dd6137d8048f5e66ee629b048213c5c5a670e8329e7e8003c50a8f43f0438309e19d1224cf6e3ed8ff239c460480e20611cc146025b0e14292a183be026f0abf8bf79a5e4b87878ee8cf6be8084c3e7b4f5dd56dda184996f819cb788faeeb177313f4b474a1070de097d9e883e6bcb0cada311b6f244172aab7fe014a385eb5cfe670952c54e43e350cce9b1dbca2e80e37c67f0545ec40a539c1e7d79e4535ae78be1cf85980a09f901447c3d6e50659b32d5eede00612706b30635f2e90fc340d611936c6cf99b07dee06c02c3398c1d1452d8d5778f65921a5a657f6695aa0a5bb5de9ce2f3c91425c4af05a24860a98a28fed0ae9c7b249e0b1107bd61b69dc0cd1b6703a256eb5b427e9227111825f5a59953088f6dfa7dff4cd32b9b1479eea70ed45e02ac3e7cf0a3304c37dcb9374f0fbeb32cc8fe7d066df50369c4de8c67d8e72dff0431d27e9db59ea1c6b050ecb30233d85dce855ace95f033774857c306d74993c28cc638afcf6830592434cf7752f2cbf44f041d49eff06e7c51f18063a0486f3921e3156cb65f349960130bdee3b3ec3878d3e3644b7bb454717f5b96817eb2a5b9daa2a4d7a80d45404bd1f9138ac15e76d04efd47f0ee5dc5964ebe9ac3e8ba86ad10d817afb0ce962f99803d710f31ec4e6a7bc3adab0604fe1c19433abdafbf27a2f628cf27fef56fe50307da0f4942b39bc8c2b3a0eb6a55d3c40cefb0ecd3ea49083ccb43a133121d36e499873502c17407582f0be91e51cdada72d33d420fae47a9dee3c79996b957e22a4e361b2d34cfbcc3524434cd8d9bb60c65b3be1abaced5613856f6be070128b33a7cfebe0f9d28a0d737796cfbccacaa46d98c061c6a8dcbbc05b52469bd57135f69783de605a0c7c841168b03a1523b835757e26d4d7055dd38080a6d0201d38a1d41b0b665d6eb50eb0e1d63fbdd3d9bc04cb1fe8331c990032536bd94658fead6f9c734e219d0f3880c41e12107eaf4d44c1f042a8bea2a81478077653e3efec504bc2b640a22e3ed2a0096471007c7f7f2a2e89751a3d674b06e0c876b152264cd89daaf6803d88544ad4981b2304cd3038d44ed495cc519dbc6eb1aee77bf403b94f272708fb07b0488992f2c7491ed57b06f4682b4db84c544ed0fe5926a25dcf647048c7e5bdddfac3753a1891520be4b43595c8e8aae694fa0022225f60db798f57f9e78e7d67066b8f227aa3c06443fc46253a8bd92bfb58153700c48a39a4ca0ba9684f99842d184f14eabb4e5385afc3d7096034d57c5dd8c01695f92a9a36399177f667087f06785540c487bf3256f73e7369e4216064d83f27add3ba5cfe7943acfdc68a1278690ad3d3fb037b0906023bc695f3f6de3b759d3ce3091a1d1960c8916515d82a0df190b7e4876b3b4e3c7eebc4451ac5a3562efefe18cbabbed112b308713d19e29975716bebdcb7c4b9eda99e39eb6afb22a78182832e1f8f393b3f38a198fb6a7fdaaab794e6c0bcf497ab78d1454c198e03a1259730e024bbcb030164059c92c94d9b4ca517daa81badda6f1e45952adb802eac6a24f46a20f60bab4b7db7f197e9c5fb34b5918cfbad5f09c0885fa2c5554f9409dd86ef12f427565c0c62db110046a37812282305fbc442f57ab0e4d1077a6a3eac75618c6602d98d889d434ab210fefc3fe4f301b94154a65552223e989571f371734864a90a6832d99c0edd82bdacfee67ca3e66f9e5a8359c02f85e96d3098d56019470aadcae8cbd82e90a6d3d849d686e180a3349597142fc5d47201d9fc2e96fb2c7f8858bce5c7593ee1319808f7a9514ec9939fdc561811fbf3103649353f2d629d3d1ad834e5c1a8c1fb28da7a38567a5154d761534feb33d374af3d224113ce7452abdffd20a5ec380b873e2952ea546204a505fa8fdb238382efbc1c1b182e73319243349d89154f11f7f613a65f88b9377457c662cf8a9fda3914151c6904219f5de68ece9525bdecb50c9313ff1c9bbf5317b18bba8eb69726708b1038fea143a44b38ecd09130acc67941e4f214eb9c146654d67e62f80800127c127d88658b0a92d35899c83f4855a19b3b113eae0dd7753a975a244b2a3bdec023f7069154008fb7b00e481d3b5374f7bfd0a30f382a7c3c6f3af4ba58765f3a00f926dfdf54ef59c3089d6d5f5a2010d27ec86f65efc634bebc1a3e9f655ec9e05d7dbf462307648f08c194e6a1a8d78f7afca374fd11101c6d4b3c2a1bd844e0588872ae8beb9707709b6aef82864f42c1772118da2c82cb3d69f0690c4a257b29119fbb6b5712e9d8a0d77991191a25d2d230e896ece0caae50afc118206f3a35c5152d9b022abedb2faac869592e0c32766f512c5f2efd1b7bc89169dde99192796f9a7c24c4c011dc93f942c4fca19d2a801a75b03b3b9d096b7cb80a0538b32f931499f9169e4450f51c56a0d7f657c1b7a27546f7be77a17bc6b7ed6a68d4f3ed10fefcb61f9851736eda4bb98c53fb313f8c9988898397f947ba389ff7b9c95335e10b3c8c5fc89269d7a58caca77a978f9b5b24101b3343a3f18c640372c1102d1eb10ab50b342ced9ed332e64f1367fefdfc15e0ad5dbc48f693d822d6fa4ea596273d7da734b25b1032aa48be5830206b61c4e825f55ab204558dbc6838124ba248c3d3da326d1f65cb34f0426bb67535b631340e6ba0e104d9723727583a6c8b93bcd0c8c534bbff837e99b140c94ad63c72dbf4a53b322a06cb484be2a4c013f6822ba32ec9776671134191019e70575de8add90511a9c5aa6c31d10e2996e163c219550bdc1cc7b6195a23318fd4f3be92766a7f213224a10de2d56fb9ea02d7c40360cb6e5804a8018835868cc58658511d093e6fc38c634314ad7ed4d9a8e162daa495120117db49040216ea4ca0a7cdc79842d40f0065239fcbd277f32b7b9d888727033b4aff790a86806637bd787849e1174ebb75aa203a27de40ea3ec4ed0dff7cf1af9b1838ba31d133938ddaac1321d13f5cbca017425a9d05b03c0e12b6ebe8429370843f0a9dfa6d1a120268af8c58e4ffbfba7dc936daead485c40fe9fc16555a50978d61f181f75071560c7f8fc854d4410f1b987818309760d2ff9f51e537509ae142744cbcf14fa2ed475bdd165d6e266879dbc34686818894e02eb271402b4626a362fbc750f540ac94a14a0811e19551fbe03bc95864f067ca74d1c0b909b153efae3daa7906c65085f3490b0e4cd0fb17756e60fee587f472adc543f6c988bdeb567bbf04df16a2a9dcee07aebcc0974301edec8e9ad709becdd0d6680ce700daa3fd50376e2e95c16c4f2b301bc44fc0fca221b07d8ab8a5f1100a098321f4e1bd05d8ffcb67e6d044cdc2d00fe7e07309321e8836af97c28692825599820fbd2fa9a52365fc6d80cff7828685432c2690ebf5e089e7565399c4dc7158bf6e6241d39b8cf880c3734e224892358914d522e6284331863169df29465a7a2748b5b793de38f65784de904e1c0853a2a184910fc2f4efcdf3746c66116cb6ea473966bb2c95e0a32a64dc42d02b4fda37fc731d7477dc9a0632f8b445a173b042187556682a8dbee0917d2c6beab57a390a2e185aa489cefb82e2718608d46f86ceb2abf20f874d4cfe6183065841c447a7734a1920bf6f5a22f20ba03aa839183c6738989760193d2122a14978096c6a5f80b51e52bdd2745d8c399325da0876096d9401d05186a9b80e1a3b4bc4449b877d6d27b222256e953510b50ea611e65a1f2a620f8739581e9bbcdf79949d1d31fd360f1e3cd38489e13b1b07a25db889e1e256df2ebccf586862478d9f575834e095e9c0f37b8af8f8f0ff48542c4219f3296309910061f9c0a4c0d0be9924337af2c8983daa55d971511b294e1f225103dce15f59017169e822e41dccf41432ac3e6f0af5c94deaf16339c0f6b04c031c21f64a121cc204e55a01ac144a0134324d122cc083c21426970bb0857b547b214f523b8a9506ba854e6553c3912dd0ff126a09233d0bfa2f581f1d7d06e2677ac7b260d46fee7b26183af40609256e0b5bcf45971213040137f5c6a2fa7447f17695d8f1c38d85eb4f3b05c94de823a1dc79ab82a183e3133b04ba268d8821d22ec76abe72d3fe7bd0725c2e051cfa85268c6c4a2c3cb65f23b50dc88d5cb49e9d47c57af15991d0497ff9ef00759b766007cf2c47346a64bc5cbbeaa33c22b9a74a6164fa668fd241ffadb4a078c78be7c4beed1bd8229732864e3d4f642aa0bd98cd8f09505b7226aa84b979230b90167661cbda2aaf55dbf5c96617caa18cbd34e2d0cd24e23eba1e09690173723b979f76f2d5b092bec0a8924fcc2fe2e868901b0962fc0b3e3ce8a73224d4ee0a60e61e8f813cd4f64f426bc90155f3a95d09cd81c5808ed06f123837742785ce6f6200895208453518356d38f14c4ac404641633d0339c085afbc3554185be895cfc86ec3536a31d021959f6c45b027d002ad9348abe27c622de6e2fd750c5db9a809186e94b04384acd7086221757869497f36039c7337f2dd7e480490b800b01ff8ff6532bbca9057d4fc5aa80c31ad9944d45189553295cc15ed977847d0201192c2b313d54b47d7ca3770e38ef46b2e93fcc61d85d1d5da656f74256b64c9b974c544678b952e216f45211db31d8744ae44a410cbe5033ffe8f58168260f3b7f440800d6dc89195c3bdad41005cd6bf7a9ceb0af41cb48b3c4a3adbfa91279244817c83b17597c2201bd6c3d44159258f2626cc82ef0fa4b0ba517a88e70f6784be12c41ceed67ca0fbe7d5be89403680e484f07ec495d99086310c09aab826f7aaa6e8b4ac8cd97de6ddf873eba326479e0181cc8f653c147f6ce3fdaf1213460bcb0fc5af3c80775330ddb192b09d0616f85688b7f9918e2329ffa2f17fe6c819f050a9aa194a0c1ff1851c06570bc7ff3d8caff3ed8802c521bda3a60b968a18a68e2b0524f614e278805711bda9a251054e797fd313d748c17e90f72ed26ea1d076c0010cd957e7bb181e6908a6a6415e6eabe59b12402756312801a8d9e2276cce3a081c782afa1f1e80a2e5ca6a9638748f1f61ca3fca92dd0fa1539469afcaf586673b62a2251bc4e804adc720e945676af95184f646c33401c3ce16814b208ff46eb406e50501805724af97619be1ec2e10a7794d363a5f31c443684a29f16e954778d92c18e5548c8ab8db678cbe9b525f0f9039b90ccb1e3347e730ae117faf3314cce029dcd0658dc9aaecaa2a49e7e176126561a1d9b6ce871e5ac5dabef8da6b8ecdc38cb4bc7351310adbf65c730c46b580a83239df1056dba8bc1bd3b9e35ecd65c4a02dbb240011019fd0e8be81f4c7e942b88117de3c6a5489a5a2c99417b2e3741d9e972db19bda404a361616c9be7fbf9f9df08feab18a2df679bba7818c6614cd059051ec509cd5cd501ce44ca669f3a42c82b7ddb93a1c79c4120e44521da9429565f694f771152d768bde7678e5ae94db15f7b3e8e95ea21a23f3562c892da1daaea1ed233a1254199a69d783e539c054eaeff3a9bfceca020bea949ec6c3aae276664a868967eccabea0f06488e85faba31af06fefb064decf4bd56120f2cf4b55cacd1226bcc52f16d3b857bec244cd259da4b503b7b56a0566f89a80121404880d7c72f858717f53aad6c18fc3df6ff9316b3c680478927114725b8a7d300ca992034b3fe5b813c01ae548b3fddbc0920464e97389632d4a7fba2f26aa38824c8a2d441385a593698f510e4de5d097bd50de1742262063f9c4151ba2e6c557775b71112ed09a968beffd01ba2fd425fa62fe1c0acbb7bca1f178c7f38838a089eb12e1d5147efa186db855ddea8656149c8bf43432358c785e9518c089f2d1eeb9d7454870a34b6cb0c0ad549c695c734c16be87fc958a7ed548f5ac04c80191474ba1fe22d1ee9c3d64fcdd1104e33119ff687729441e1fcd7ea132f9f04eef07f211e528e8449f104b00324fc1ef06be85c5bb8ea61265ce6994abb30a8970ca776d7225d3725b63f1f435ecfa76fa11f8cb5c9d8207a862350c2a057eadf471c39a1ecf9fc35cd7c2a0dde35ad830031da0ae808bf3716efe29fea86b424ff8a2448034acbc60ca89d2f5f80c93cbdb92540fb2a6e2573279f58de14997d75161f4b3e11e1cbdcc1fda280c77684f81a368f5b13b5e35b1d8849941324fb29a4138d40cc4ec53c99076b67ab34d90a8b9f3b7d21da1c169937c5b43b6933314692fbffde005eec00b18d24dcba65379433c4739b15447e659d3f2e85dbd332d2d552bda4f1a3a6c9c94eab8536d3c190a6b42315213da085df9cc5b4531f2869533b52c5057ba09803c7a5b0a869f7568cb4b5153edba0c8cbceafdb4530a50134a76afd103f56ad3c7ece46325a6c563cb711f674b38b6aff9feb2e3e8104c40fe7b05e3b99007853a65e18bdcd4633299efe91f6030ba3df6f246019d7a3b3933e1e614fdbb40a41642fe5d2169cac26d2cd5cac207e92cb352c0072686f0d050fbd6f3d4738f529b7d94ac3a61e3a65176b245e1b907d06f5113bc52475af675ca50806ab3b21f92d382aa8b82572e9528ff2d7e1a7f231500fdbe1c1e03b11748b9de76a939a21c509f0572825cf6fabb2bd0464f743e4a62419df79be40e90a43f29818a5ebea588323b2a5b9906502f592456e6b256841d82b00a1f7414c6bfcc07b4839dc5aa3fc3b8ce8128eb4f42064bb2c3768cc4e1a304095c8ca84dbe99e6a99475f7f5caf49726e5720c7bbf0f7f3377c73a03cb7f330705a08cd59f08fc46016afee71c67b460df4ae5aef44305ab7ef581e1edfde8b57928aa40f13865a32e50056c71a0474f84b408a50043f07b97202251bb8914414eea06e440780b5c30edc4db31561f38f38f0aae0941a9e849d5bc8b1d583434f4faa4843ba536d031390be6e0e5bf09b0cf599172f73409e6c3591b7df5e3240e760894ad186b6ac3f81deb2ac309e110e5b7f027ee56442ebe76cff7b081cc8921ba7f4aaf38bcba9a56d23b4bc0302b8d3a15a95129a5bed3dba2c784a255f7dba59b3e63bb4dac7b1d46a92582aeaa24999206503b651c0aff07041b263fe694064117590e3662d92fef10bf41b78e2dc7fb7836379c2030042bf93f8d0c3690000a34c17228faa90e82fd218afab17f377dbfa2e0e147818f3ced621d42acab265d945db043aebfc09824d499a6a511f42ee3007a0a396143c71aabdfca6abc9317ee13c73bead65be28a9666b10f9e93d6e0c58162daea354ee853e99fc4d085e317b88230820f26544d678c98703b439f868c274de3cbdaa95acc087fda6de486bac44a886e11fd6eca4a3f083f41e2df6a289b59c137f6d9e0951e40502e5b3afab569556c0921bb15772218c9401dc0d33da9cd0280a03e8249d160d02b0dc8aa27923b4f641900a3f6df7bade2c64b327e89eecd8ee4511d75937f9298078a07ddd1f17ce18a43c7dc8c7ed1528e7ad647d698db52a7b83660e156137af4114575fdd8512e1aabc71cf6d3fa0e1fd7a1e5425ca55ce429ce2e14299546ec8f23ad59ec7091b7b3931c61cd37419cb92061209f4c657b884217652aea0662f0bad055c1e037bdca1b7fdbe80c2641f5bd72fd8b0d67b11e6ffb879b94e3243949a82c63dfaa67f88f24ff827c43cbc43ff9cd12f8e9891d63242c233e13dcd23b80da25dce994de34bbf04ae26737c8143a32d63d7f2d73395f57421f664a2dba8fbc51a92d069cc80987c15fcbbb0403bbcd78520675ed362707012b94277d9bdc6ac454cb9f0bc97aadb598a4bc638777eeb42beddb8842dd1319d7a891e8a7ce264ad7bf59631d067840da0b73ebe3589a0108968b898f8e9d88755b42ee8c5ff2523aceeed1b1191143bbbd6fd9abb074e55d91e9e5988832e90e767dc1367d305084a9c141a621a22d07b5f78fefb2f7ee09df0688d66571f0160c6ed52441a7d1c4c0b00df60b3809ec247b7429111bf5f4001421f5813d1ee75d046e0d5265110b9ae087758ceb1faccb0bb531c286231c6aab08de0ce832654fd4978c28fe9e10c630342ad964f857a14aecf05a3add0847bbb50f469f14a17dbbc720d65ebd900ecbc8c5df6a2f79a5b58ed8c635461c85b5f7c5b18bc313292b649844acc0fef8107439379d5ca11b909f501e83f17b969ea31741c1ebce2f6c5cb7c20eeede5820f7ea8118f61d4076a45da0795d3543412256ce7fc8790a7399df672edd19722b28076898ddb415f9a4709c9eeb3720d5dfe6d4d3b0a58789fc3770d770198370acfde4601184acf244713aefca2cd12a2cd45353f498ec1e2bcc4ee045814de622f504542a567ce245b765ba58ce10965520d52c83fa65942d6f9256aac130ce8c0250331fddcd28221144d65afa829b7d4ec953ce70db559ada694c736b724ead51e9d10a112fe9fccfe081bb33d7a390fe5f1d3d2995b1740d46c6ce9672c20540df6eb7f6e036060d854d56ab9adcb9ffc9cc39271bc5447017390c5fb1f5f51f41d36b5bb0a1162449761c5ebf4e93f51074d1b2dfeba296561b29fe28fd9cd046ba1e3407b412be0a836bc5369d6319cd8647431ec378f5455e4f0ee280b9e5e9a4e350ee3c651d7a796f38656fb2ca40404c1133c4582cb360f4197f0e4946cb28c16495c021236b8548c36eeb048d789fc94c64f8874eefb442bfec03eeef2a0354a75801c515813a59c59b16555e76e8749de39e38257896c496c74c9d7ab328092d681624ca549c0b06502be7e0ce410ef341f58525ee99fb49c5815cf6170e6d3173f519820cf0984bf2b070572e7341ced83e104a50da74dd1ae6f0da8683a0494387c9f5020d82484d669e31155bbd6bb8ad2496cb96d9600e9e725eb4ee3030dd33929cc2c440f6c2994a5c0c17d5eb5c03711142c9ef636ecc73e1fe73509fc6cb84f917e86cae351aadcbf6ace9f795840e34589174617e1ee17bb607e4f25c49ffec5b649394bdb95eee9bc32ecef2e65ea2a9833233c2c1344ffb0c130ad6b7101595c19f49b22e2d61fb49c4a15ba088961db240ef8ddf14a10bea33e7933781007e31b91160e1087c116031d0a6c38c5a4e142c54f268d630ce37c0bd310adea09e548f56f91006da77be7f48bae7930fb4a4d08b72b12feec934af5a7687a6d1bfcaf8d00e7477d91cf3a7f25710c7bdfd1df3b44cad83394eebc0c7cc8b76528b06ea339cfc112eeab88b02a7ac64ff62962b303b02a14563b3630b917b8ae7fade4c56108eb409aa4cb63f052585b3a870120e24c814b6b604d81a6c810e940f7194e6ea015b3b521ef3f23e6cb2c6a95baf0c7fddd03d2c8fd162a1770e6258524fd1c318a61ad0ec5862acc6c2421fc14b2b0e39bc1c7a8bf309e1981d6f7f770440eab3b6123f27c7dec3efce88f81de4a214e1569ec965850a8be4e51e551d4050c8be3d7aeac390c9bc2095972ee8bb9ad9ce74ebbf5a3de0c904f78047b305dc2b74596443d5d5c8d3e937aaa6bdd958820543e9713ef7a780b78c669e99e0a59917d958fe2bcb58fdfe94adbf5c88a602c68506a1beda175650a76ba91510e385fc6b576c1082c0d5879e7f83c410fe1f3adf228ad6ab99a686b6f10b3c6b6f1045c20e2cb1b770008719dff5ffeb6cd28103740ba1c90f8d84fa0081ade4ed8e1852267111a0e73160e00b08e6ad1f605410e6e2f48897f18c094232742de774932ca85ca2d55d234628659a3320c8ee1e3493b72dd570764510562464de2d56a2c3eee89c2d5e7b72337bf5aa2ef5e6aa84940eda08faa372091cfea90bd693e78005e38b5df7d988b9ecce94e6950d863365d600b48ecc946ec95e772288987b6db5dac78dad6f8972c404518bb0057db592f929e9b7688574434f0c63a6bf41f2fd4f043dd309e39dc3677bc952062377e86165250a92cfad30994b65ee8a6225e72e3ca8ef54a9dc642463ca4e3b209d1c0f463d6c44e8677b6bc890a85c3f255e07a3a577a495715b8a5627c8bdc7825cceec654c6081d7d0375c72a43918db0b4a84d653524a8201bdb912f02b1783b77e3049bf077a5f30806880c7e13be731821e2ff035d951590a05efad1dcac65ea605c30de44a5736d8811cb76e0bef967a1d95bb3101f49d929909636b34e7ef75a123cf210dd16d749f8ccbb44b8fb973dab2783007c37e4a95cb723a6149dcc0b9c2dc546ba04ad41def762f27d8abf93d020352c41c3f4961fdec3be225513e4201f0dc2841a07e67d480320cb5b7bc2ce048ae963142e4839158bafdaa3654dce5abb25e637fb13823ed0cb487e737c49d70e8f230e38f2a02831850f7877929a0f281c8b6f41ee944d42aaada439655b8f37d960304bb18b60bfee6342bb155344f7dabcd7dbcf2bc80ff011f00536a2fa750ce48de2cec3e6586f727f13e4d7d6fb16b4fed85f0d31cf2116a185341cdac81c244097aa757f05123efbb5869d999e1196826c5b0328a3a3bf42f637c5ff4d735246e0e44fcb643d420fe70e41a59d632cc4c2edbabf6f871d54cb5d73130e0ab32f8a37218783edc729795b3b1da03b59950a9edc8be5c2faa115872c2a8a1db17f6638a92b2a567f3a21edaba81dd957b3ed930b9a65948fd6173e54750252c3e396516cdda52ba71ce896afd8b3be812d2195f3e597f167360ffb1c7d8b9cfa87a819e2a0a5ae168f9f3f21d0b0d3080f99f6e5ba62d3851c363d41ed2451016f1e2301d02a3c4d7201c1901fc4e9357aac9f95d412742eb480c2d8fac6bcc12e7864a01026e9a2c21b447e75c285d2e29a8151400dd0d00570505269092e285381a51da02ea4cbb0f9f7dbfdb73ba2fdf669446438fee2c838bd495f3b894e229a36789fbaea25a9788ba448f134669c8024446d94734a9c3eb7f98d0abd1c7f673beb78bb61be5ae6f8fa4cf3888248fa91556cb63ad011542bb91bf4e871d771652ade7f99d1e258a5b2f62334870eb663e8d035547a93b718668b53d22d19fcde1e9eef7bd3febc421badafd59f66a65d4037f583aad62d301c91fddc5159412bc53492dbec4c63463083cb5d15c85b7e232e9404ce477ab5f5fc6e381ea0a1b0ff5740f404f82fe73c43a36147118c49a437a59f5fe91b692dfa5825532030efebbea96a42b4afa97e4d39376aa85797db01594f6af18f3718dc5fe5000b6a5a945dda7ee300509b1170e232f7c31716376199936c0e3bd86493e4d543f546853bfae327f280d93c39659762895ac28e66fdf9a64a094a86f6887693651e9029447c4b91ba886de2843114056a7462e397ec843dd1f2663da82b8787602843c4a96aed66a4decbba67ba0d866d5af5f47b31c282cd735eab73a5cc386e0c0bc70d91150fcaea529dec512faecd9fb3f9b2c05a20653d8b95d5e93c867d6b60d72434a7ef71be0517c1e2b5a70609b7065b5832796c879ea2b306804c1b5103d484a75e91b728e345e5e1328c4454d300e479e484f617b68b69b072967b67ad212d78b941efae6ab6085aa0cb913265eac76ba7b46d94d21c3c543c0b02e9d6870ea819db7e2c2061cc7194c1a6286883174e35ab7285fbcc5593fd8f4535acb108c92f4a271fb0bd116007ccb8d2d2368c96dac822429c498d20c7bde3d6dba912bbd09b7008a2bba6b84991440b81542b742002373624260c60ec83dc621d2cdc87704e44d52d139612c4003ddba3d0c138e097d67b40e43cd1b2513f8b0911d474865cbb08ef83bb5a940a45c51ccc3c02a4671f682c820198a0aeabd16b5df3fcac28d354f39adcac720ce5dbee34bd45ef5884e23fafeb982fa6519e98747019ee2fafc4877a7cec79395f1366424b3cabcebcc44654d7149161bab9cf770172d5bbc94583b9e4652852a75456f94464774a45736fda3c10724df23a00b0e26aed3c74b69ccdd65dda04b1449b27781d420b9558ea174a198588e0e693b1a1317c151bbd7ad9f1d9be43333e70501a3d03bf0464c52a1ad8d27ab2aca89b68a02271600c5d650e961d10e296a8677870310807a6e4792ea6e9e573d844b0daa26d7957c9974ebdfa3fa9fdbe48b0ee271d3134a7d933e8935447e12963b3e47fcb7074d82f95f179498b0055ccc1216eecccde72c3fd5283482ea3ee9e0f957f2800facb881cbffc360af1a87b48c6c105d238b2f779d90816f7b8a4580f91d9d6e5e1e9ea4d14a82409043cf3778a363649b3184ec50b3772474d8d65888202becfa5b34d3b4619b217b1afc6bbabb51a4f4d0891e0184e906697af71f0bbdaca11fae9040681b873840c59ed390fe3c625992fb670e9ea524cdd956a12370826d8f487833bc8f1430345171e6c9da8c235b4156e9f0fb02f8ed440d26e1f707c3687ef7376e586c7cb3a1078b808c4e054233e9663e0874acdf6f469520bec20a03730d90d29eff07ddcaaf2ff04d851ea7098957ec55cbc5524bf4381cc7de1ac2e409ecea216b9cd12eff140a8f4f6289b2d01861d91250f04c953911115b94bb65d002d1882895a2bc2c8f8491d4d515e7ec7e6d21ba2ac86e0c466efa28215ef44b1ac481e4b07c04779c0824b5b12bf07cbeda5ee3dc689ffb610ddeb36722075ad51fc6cecb058d9769c765ef90a60f5f77aae56ecd6e5056a8679128cc755bc671cad9e55d61effe1bcb7a5eb51d60a7b76b9d162a0e4fd019fb175c341b8cfa8dbbf42c1673719372a4c15cfb06d771fa0761cfa4e9a03370a4419289bac85010ffbe4f6969535e9a94953665a54d6969a7545a299766caa5917269a65cda299746caa595f2d2a62c337778503838542d99c22bc72cd14206daa0bb783e30c9214c043f206e54a0e52b3fcdca4ffb71cc627c22e37490891d64720799ca5c820c1e87713bc8e40e6542cad10f350e8d29e9a55d594248c69832e33f063d1ea10b3d0bc8b2528988c781e9a47d6272841cc0e2b7f631e8158583d74faa640a47c7407139ac9dbc1d6e101e0c26d5f4de251ce4ae596d5c2ccf1f0362179fd0905b40d774cde0892d2a865cab1814db536ae00ced53497d2cd043deaa8cd2437350d84eb88897c0fb8f42aec90ed6dd5fc8ed5e505b41b1675569071a921388a11e884f2bcad0c6186703b24cb09f60774d99ae16e7a3fb12b7e9c2ce814dd25a416e254e07071d8f51ec8c1c5f2c167410ce906620d4706f500002000b5e4cfe8b2e8144dc742ec90931badcde6115a05576f4a4c3d3403f92008c5b390ebac4827b85ba6dd01479cb09f72971c0c0030279329e0eafb89001d8a790843ba4d72f1ce3f652056c8017341e2bdd13765294b1280afc3f8ee1006db231741637b61c6af9a13379717235d58b8f978182ed4e20f0de5d86a179b9d6a330f20468dbc5091cfb9aca30b358708d89add386cd71734017a6936cdda1a27bf088895312bfc27d83ec11839b6009c930029b03863f5fa828d367c754661ae708c0492bfdec3defc21278fc1100d5aab204c8c1843645f61ab973aad3077be27f497f71d6dd03bbb51bcdbf2b4274d5fd7534c3dd1bcd785d3dde86ae8d4cb9dd7eb39b3be0d22cb76db8708f82a43c8311289f9edde471c6b82f89cea60ac018823f58f7f4d97c50d1f44d90f5902138a858c629827ea37ebf105673257b2848f9377e13cc4b19c97a00a7cbc1c17c3dc2a02dde8099c7f00464216111e911c3bb746c2e6c8f2c7df36a3fc400f23d7cb9045deaced1d7d8bde5febdaaf17368441be83b14bc32773d6b4f5d9e72f478f759feb7e543868ea42d5d251f1ad4d138da2a795bdc38640368c2606aadab56655623b50a3fee2e57596dae5727a06577e18efba41ce3a43caa55b98a7c8e6c53da522818e50faea046dc7520cb17f2e862489d26f48554026abd04435fe6ebd816190b260310670703717fbcba6fb579d923d275367e8ba266730e407311289547a99a85e6193bbbad99bafcc082bf07a473ec4735a47d8325cb9bdeb1ec0232a2bb23f3902b9721326156a06deb9a3554db707856437a57c21fe6bbc42ebd6d3bd83eb27981d4f6d14c327651929da3e16c84de245694465f86d9cb492ca5b41cbc18aedf576c30f74c628ba35bdb7286e8fbc63501b5cbdd8de58adc8115492b2365e2b4149a01317996185bfd1a3ab5d70ce25c3004f3ae10d349f88e7e57ee1bd6b3d5164277dbe17adde4d4d0e3c3c59196071ca8377f6e4e04346743803a09617a63ed1e2a2948ba3599980df8cf9c2c9cb82f47cd742c5c401730283c7931a3d067504d122549ad5d84b8ee82480d57625d3b96c4b98e2d2c9ca67ed2e57a8c9ea8e1f6a55462e05f848a822dd04b15126e45ff5649fc10e657c382a8cc04c81ea01b0cbe9710192116391da265137eebc90d75383fc6f557fcfc80805685966fa5e668d395b583a9acbe8294ca35687cbe3f90bd678485601eb02c65faa9b6b7baf76ab1ddc9805467f732161fe7c8a3ac9542ec60f22f4911dc472d87a557790b9732bf85f0bd56e867f2ce864b18fa61143592dfdb58d91adbe487946425c2828a1b1dc252783702ea00ecf743878788f80aa67a95a06300d5898c07a05a9dd53238f0d2c4d34ea6fecb5cd2eae1b0bc486be33d8197cbc4b7a2cde3731fcbc34f2a61da801aa262971d544dccc404f9e4cb041749478229d849671230195668478bd06179b94ed9a687cb07d577742a50e7d42c943351e432d471132d89fc0d92a7c8c87c2cd9a8ff11d4e2bb7f55b9c443df88d24776a64577b61d5983fe92aa32f6aa26c2729709956135203e2e5e2b64d38e3c098512993412d23f3daa08e41b7d0de8c21c27f568eb84d93eff29467a72b2247d68c242434a9c34d65a876aafc6e963c8c9ce609e11afcf7aba2243d0e83dedc9d944755ef317aae62f2996b23c2c389662b45f7d663438ce96f9b71138a4872d16a773c4909fb92d7909b93b9a063464207bfea619bb7474034d55a6e3e262a99f458f2dc29f2e477d44cc954e521e672d4187e1248c3b652a33855531d5e82bc180345fd0179bcd90fa5ad4d2f2bf6b622736a585adb85f013cbe5d3d6f705499108d998cf0d7977a70a201393b459220823bc988bda9c3446892c890f43c82c7d114df3520e70011591743064fdeb8930fdea13ffbf882285e6963825c88ee706b68d6287fd3268eb73e3073e3839c4dd260e43c5a2750528fd8009ab575dc4982bed801aa29099594ef230940728e40b9158c6f9be12f2b73c8445d7abcfcdcce7a380c7894838706aeee6ff3fa2410c9cb6a072feccef0f3f83d222d2151da800750497b97e31cec27a6f06653d659cba2ab04aa781dad8fa322b9f4be0aaa68cd149694d9a285d14b973261430abdc910f3681e668ceef50e5e589082113c5ae3b90738617bd4cde8986730fd344ab8296b1a5054848f9ef6141bfb1bde25c2f36a2569a4129ec1c679a532c9d282280eaa2ec91bf2b6ec798ef400d72e115f0c9d0c640ab70f697143088efb9f38f700d8bb8b211575c9efea41451208237b7364b50e915091bdffd882e2012200c8687c6ce76b7f942a517436962c0811bc7e52ce7232a43097e7b600617c073b151154d5da45dc9b3b61b81e0cbd5ae4c2bd8a62b3cff3b7e106e17103bf6769e1be90054743fdea8e2bcdba6fd471431f1a829254f1963f67d2672517247a1f4c857cf9b1552e7f4e78782eb55f1b80a4c6b5bfa0df6c1edc871788e2859ac83efc845e3129fe109dddb8313ec59dbb23ec193af48fc764345502a86628273e2e63d193363be764e0e8d5b3b1d0688467b057a5132a134967440aeae397a476977e0a5440b54fb16d1e800b60c031fef9fabda165f5d5da4f4659c3724d99346261d04051bd2b7aaf8c8c23834451ad6961a1a2abeae11ecbb48705ade3062bc519c265685d7ecce04856bd1c3c0a3e9c8871b831030b53cf9260855a6935057bb0bc405694ddc5ec32e3c91dc64091db98c075dfa0d94926b32ceda8c4707e48402657d406d81116c2c54891bd803e50e11511d4a5c51d1335c87284c1a6b6bd758eecfa38ca05b892b203ee701b812af1bcdc8396338920e7532588637b924993011993609c5145c9df230b2a2745302c17bc3b17db2fe2a2410a0f880267284c2fea49004d847137ddcfc3d8c54c38d24ea13bd212f94122d8526b10d54c360ba773c3560f71847b62cd889ae19e896369a4a5964839a959e74daa1e59c46e9f213c9580d626febac6c4863cb4c9e5294af9904ffd469038caaed08f259e2c0c71ca0e288aaa2db23571e878a6aa8c1479dd1be41e3168fbfc991662a6e46fe479cdf0c58ae955e6724325a4cf606a7519dc241a3262896569adcecfb23127ae9093690c0e1008c83c2e046bbee66459a4979ff164eecd98bb3bb74e150d3df9a51cdf9e026165daf5fecb512aaac80098f8f43e77074bc0296b03cd98e4e0d1b2af354e34f5b326d728ab770e9149c2ab2179c9bc99886de2ef9c5d745724ac2e975d1c20f6b113157ca5cf31b4aee92688724495c1f271a0367f791e4cc29d58c96e7c7055336ce770f57d3a9fa9278e8240f51c0adf9a311aaf2641279e7bc69714d0ce8ec36f6be7e57797561b915984ddbe338418e76bb6080e7b3d98bff954920e21672645973124e9a32997729a293e1b075f9c7b589dcd5c3a6d2dddf5c2411af9fc8313d48fb954bb17a3d0fcda6d09ab720ff0f1dcd6d4d03b7e9abd1b82e954606b9443ec3667b06f382b1a7d9af49826485a82601dfeb2cfca216128650b444ba59a3aaa070a11cea2bfc72be9d630d15ee1342528eec4965500d5196efd5eb18daaccb4f9eb3572d488208b123638e9eac5398f0e24c65dab674875dcc44a70716188d9c416a4b7f6e70af50201bd10e3c9d45c8029688b5a6ad20e9f92f39bd1ca65b26470eae0d705d10ab317857182605d036974e69aa259d16d04a0fe033105f627df3f1af5d97b374afa1e6e42d23081a9106bb5cee9bee3171dbd3fe5b0969c2428d68f33b7c79432ad5b871bb31a24fd6e02ae2f52230d0c1c59e9d117a412b9b7e9f4a68f50fd610139976a733565de5d5a26bd4655a7a40b673489ebb83041366a92b37855d2c203c7884406810314593e83b8acb924a28809d34ca8857da7615660dcdc11b9e0e89d177a5b4b57a6fec8cdfe4419d09ad593a20e9adedd39e6cb64a055ba5f8bc7fa406b9ebfc8468d807322dd3301f624e97cde96b7a53ba0dbae8cec9e3fb9882e22929b4608d193da837c45cc9d073af2639317f9e126ad4b963290f06139fab57bf2e1d331316770bfa83cfe462b7bf9bdb62b0550a104e31171f82a427bc66fc1e012dba81d3d877cf69b80cffac4f7aea30a6e3bba1fe7750c6b9177d1c4aee96bbb10667c2f9e02579b28e9fe1ace2b4b56f822b202fba79ae5491e1b4e249a44518d6b220428d73b27ecb798ec5f980648cf85d7a56e0e99948834b381fa0fd06bc4bfd36f57a2c24cae9d9b87499e1be7287cbed48a0a8ebf7725a6608fd59b31bd9256565c2c92c7506acc5faab0b08ca6f42cb2d5b6e7a494526213600537d5f10c339644b105aa195968205c7fe0d596171a853588b51993d12fdce9a6749b6307f3d456cbc421c6aade70e0cabaf07f98fafad088cca494d047270ee2eaaeb72c2dcfadfccbd6a5c9334392cd9d064368eb7ea5710f860300b05b351d5a14745992693d7620890c6e97b0b39911d0c8ae2ee1b0cf634821d207e72681f61850be101589525838fb794fbb4f21030fd5321e90bd3d7b692ba51d724f8e8f5951a15cdc0f23af7c6c56d8720b1b7d8f42450c792dce287ef70d9d17896c32ea60af5081d856a5ef33781db09ea19f2e5ecd67e58e9d0cf357639d1f3cb8039cc636dce4f2650da55fb347c2f4c475dd45b3ed1c10d82e4f217db994c46fc413bdf0f35c6b38185a2b57f8ca82a28d1819016efc1c980762be743d0f5004696cd02ad0fc5303667e3aefffdc57746e1ec7192937cc4e5fc40830cea9e696590d45f90d92de2b03b3b46a75e0ba1c24bcf2ced566ef2eb1f1b3be008455f0042d6fae68153b695c9ea28767db8ab178eda5efa44f147e01ad13b9c8df94ab437e04291a000310052049986e80ea0e8de30378ecd8246689edf58caca0c0d41624a4f9af123a21dc7729ff0316dcb2bff6da7ec34f4826422562e7550cc3a718e5dac8c955c608f35acb08d9c137659dc3337c8aaaaee28ba74eb23c2427e3ae72cf063496c9ebe68a48a36ee2caab9bba08b1c22b6c05eff2612d7a7d53f4588a2e02b7e2ec011c0361875c586d83ea14a02903646d92203cbe99d55ccf80957e1843615032260b187b03758c60eaf4944b5710bd13611e84757f7f3940b2e355084eb09a25dc3145e59d071ea72ac8fcaca7f1d308ee18eaecc4bf924cfc3296e8e02e3ce37c18768084dc7a47d0daf276d6a8fc41051d9977acc9d260bc3f987d487a6ca8d269dbd16c4744f1dbd6c8ec825494fc47adb976a0dc259c22df70287b1760729e47c275584abdfbb63222ac577870adcaf13c3f85bc1a80103b192a908f868cd28b51055d23eaff9907e4535c2dd85e98657fbd9c4d0e77f36a93c20a3a794637bbe99b74685f8c248840df799f4c3890e0c1eba6eedf2be66e721717b98990c51b4cca8b81f45db5088a1a5d554dd1b2c890ae9b8ae1c44cf25cef0a90ee15d9dd5fb8819de18da0b0b168eb42b84bca88df00690ae2c0f44fd0c62bb94f9ea6433801028cc8765aa8136bcbeb1c7a7a244e49bfae12258dfe45ea224f4fad48cf9dba8ce330aad8f38d90345cf93fb5a0f02dc80bc6ca6561c4b25106f1ba907cfea3207dcf280207c058e2e40a56add487bcca41900e26a5b7e493eb90ee53f98db0e65a461e644d74c73a682d267820e7b438b89adecc1deda2bf0589745e961f16f0838cc327b17b09da1b6225472de3667775def060060bcca25500e0421f7252ae494f7541af1c451336e9063a9c5f7a47693a1596bb8033b26b4f1a7577321f0e26028437de04a49530fa2866017a16467577207bcef84a238c93b9659d3105c4dafc419173b29a462c0d74e39d508497cfc3a4bae39bc48f85edde74fb8c8c7eaf1e7f9790b8ab46555d14f42d095ea97888e6bee33d99a3b6662e2ffb9355882b14f30377e77a50d68ffb54f73f804636c437128235abe5ca8070e1fb29c55c31204b219b23d7bce4cc68501a49931cd00689dac1ba772edfe6d0d49680161c5fcc6ef2ccbf10b4fab63d6aebddbaaa909ab4728f5e6c3482dca349e35faee1bb21181ebdd76a5bf4be371f96025d2fec479b722aa2f41a6dede48220ac9943a3d3671c0d504f09d6c87a310075763855982d9894a5f8994c520933ff43bf4b2bbc9cf04e8eae60626a720473255ec70429f3b8384a51224a75025ab52885b42fc68b2c8892c820a6fb24d8ea15fb1455060328c2c5b729a1c0b33760538549b2bcc3f9887fdcedf517bb3d247e9f81c24ddc1be0bfe5eee7fe0a3d45ca50b98005ff960ea0fda076369925ce714e378b9640b8b79c442327ad0893c2984e0844ae1fdf90d438b8c64263d28fc479e94adb4979980ff5b0aba13ee2195c575006d84499940a3a10b404b907d08d3c8c1dab2e4996f9659226fda28772e26a5081026d0cbf848acc525baa641386a8e4d2ad32aeea879fe5dcec273db573c816454e219ce02bf0dfe92c46cfc9cd1c2343238bb69cf4cfce53d6d913907cc5e26ce8dc5ea81b107da5279b2f0c9a26088a9f7d3b02c8048632ffe0eb49591f7423eaaee2e0a6fc685c812c0f068574817c753b3804dde28c26f8ae5e45674abadeb07b61838c5efab469f429aec4e95066e94246c8e6aa102d731f42c684803fb0c69cc2c0b833f2ee35ef32a100ed7cb66b94f2881534002e440a16e6127fd185cb6f3da64ea18515cc2397cd1b0b456c8a4e162f4d83910ea53c0b79d9b62d869397cc546d2da0b61eb432645ca557c4ee182f91b0f4253b651597a329f77339afc96a952f7732f2f17f12d1deefd448807d2c779ab52af44a6e943f342e1fc399a7332628251ed6cd4b33514cb92b9dafc67781986783114de1ba59ae8c4f898ae475ee2e5329c610d3a481ef47c6a0fe29b4c0c4b5d99146f08e900a16a010faac8df65bdc96dd13b7b4732d11ca2e12d279184858967b0e5ac336327f6b010f1556720e9b3218479990a9d03b6cbafbbed563ff15c4e31db2a07f5f5925da132c205aea1475779222466f17341b700d37597e979b59be2f3a1872b19264096fa80f05d04e066454de2582fe3f3486c1d6a76edce69130883b145c541212ff005d8ea1363d0856e6d85b1f7d83aa20bd1c2f33c30f3a0b7e771b4d2082be98852ce628bb1953c0dd743143f7275f1ea4aa67bef77b961c8a5d1652d307413c7b34a0d3946ee3a2ec6595a05ecf093ebdebb262fef40a7efa0832da9a0cc979340a051d06a8ba543d80e8c5bb7971edbac955a03c07938a7c53e29274d03f11d9d275c15bfe3ff12dcf5193fc8d01bacd5701e423224c0acf057a955907cc78337869f608dd2fbad173a01ef8fa0708c80b2b8b44c3104f5d8dfdf9b0a357f6c4de0eb8d5cf66c296e775fa44531cc5869ec9d067c4074cec9063a6c08ee1d8a29dfdd699ad2fd365fba7434bd7d96411387921bdc6189eaea02e5b73beaac8a7c7f0e01f990ec0ec5b00388ce7ba62a8ba798d2161bd161c4815cb1059e60955da5ddab8a5c4f7334619605aae03a805ed0c1d71aedefc9e4d462101fae2ee7a45b9bcf4c7a0265ddb94c3ec74280fe22df0ee81bb28a857550555f1aa85fdba24ab2015334d332108844ce214f337b4134a7d17623c24607d5774c64eb7e924f1320feb79377969735106ca8612c356109e7cb7120f07660db1a2750527c925d510438321ebe0ec8a6d373c1c5e08547079ef6180c48ebef4bee1967a2a6d82cc88c0e686ea5dd3dc255be85d49bd65727eeebfb81b7bb5b84cc0dab804dbfa49c53ff9290380cef0b9f2ed21834ef87393e508ce2b11e2f14ce40ec5230f69d707d55334e921201fde90dcd4ab8561efc0aa5cabdf027017d7d7e0c8cd7048507616e74a529c1e75f1935416ddefda135e8f6a0d14a6bb9ef29ad20a7cfa4e4373ad36a23b8ac94ed741dda5c2fb50b65fb2806d579b3609c143299fcf40e92f87ad4cdefac1c43b8d130d59b904aa58af60de6b6a85af0c73ad42366509671228302c5ece850ba26aadc72da6ae7db7665a7d718f052d0e84f0d93627707fd701a8118466245057ecc0202481f1bce1eb2e05b8e2ae7a5c68dc00102b1d18a0c906ee5b0dcb7fef1d6cf5f5fcf89f688549fd3c3a888905fbeda8ca6a88c99a98cc27034cd40b81871742f2e67c35280f6b90f833fd343eb0c2dbc5d59e31c5a5681a1a7d1d01cc593be51f7d764df6072d345cc785109f36f66f2ffb6330569ef1b3b5c152936dab5460db7e1634c27d1a93d1b48b33fcff6e0ceb842bd5baaf91c3346e4ef267707cb92f957fc9916396140b98e6f36ad52925e318799a1c9e114b60c25a19c43a6c5365fef119531204cc7b95acc0b0c5a08316b377b3add66bde3501848fcaeaebcaac2715ae1adb8f2480aa80c5ff0758124e97088eac11cadf00e49b53435f124a59a98c107d36e8ab1e773124fe3e3bbf630b00b9bf39d281d0b31fd71125daa39eda71fbb056f76c60a58c3e7ba78a1c5ea74cc60993ac3e19b786bd0319cdcade6626e2163f28d005fe9965095a1f91132c5e78af7873140cf499b5bdaf51e33d69ed700b97d0522a17b9295a444559f462f43a5c3077a0b64ab21ff03a143198f402680544e703baa01e855a305c554d0799844aea95440c83734bf4e1d03c451ed1724d670b1fd8c66af9357610486260c6db7b055471c12314e3043166067660d9220965e1bdc5b264c3305ebd8cf29f0af27b31356d2c38a80909697befbde59652a62465960806085f08dc9111216fe6d1dcd4fef9401e261dbb30484056713872ef1a7c617d4bbed046e76fe6c8642404333d12a37e9910657f08d47c8de392f5ebd5ef3d81c795187d6b02b99ebc6d1bbd25166030e0fed3a33e9bdebd0aa1dcd3f0d0c4baef5732a79df385e6a65f3426a45fdec768705e3b343c3440fa45439a1c1a9d16e9a4d9a1e181c1f61dfded3fae524ac5ddba4c63d657bbfe348868b884742fed34e65120a4eb57526b57bfef6bb5b6da6aabed668ecccc608047f9a3a3f6b884b0dad5d3a27f8758a39f4b2fc09f8dafaba1a645afc126fb0b6cc905785c89f9604500c5ea47bdc9a35ec2984e3e5811307980d56190156ffad49b5ec29cc099216fcd04cdc8da00fe92caf902b26ecc9adc35b967ee7c3204be1763958a3b7af18409789455889d5e26adae00bb9a3680bf492ae7010d734a028ee5d39bc299a02f02a64785333254bffa28ffd0e834ec250848f68a13e4800ae3f900fb8047974cbaa41e4673431423ca47845c4cc8d70c135274f99a312245ff2f77487a426592ce22739d47bd18e0b1ebb93d251ecc2de18e2cfd59a7e9542af19ca4141287a33d6782b6e78e5c2e19ed6ab823970b09f6eca4735a97cc95c475e4b996b4d4b4d8b4dcdc161c6b1294fd6f4c8248a5924b36ba642e2332ae23300d2968cc52a7959837abfca121c5b6526425865762362b45568aacc4fad542eca78e645f91e595a1bc125b01f2568ce495a0ce66456865a75f92fe90959e1522414c269459f6d74a15653623932e36fb4b8047ba6ca63f754fee9efb13d8f59b94e5fbcc4f10b2d53fc44d81ecaf439a1cfa5b178e3a56622d1ee10f1c65c833e461aae4132e10f3a4ac49f3d3b5bbf6b4a1c1d9d1b9366ce9e644e128aff0acf4acc4b8a3f9fdd8fffea4094cbf3b5b500777c42da1d1e51f0257af7a1bf74655e888abb5862b405686c4b65009dcaa14150d4eb7fcbddbe1d751198cb23ffe624ab56a580c98f4b80a4cc388c6ac5b5d7f689bc8f467a0488b3c3abb09cf0dfe14a031c9fe3a9ed881358cbfa53e3dfd1a65158a64f77711e997abc066c0270345fa354a2199fd564eb0792b399d37d4e04b0b7090c34eef4790a27fe79259aaa27ad921657f1a3387b98408a1315b020b29bde612d2af215c6ea1267a4626678e206134374be821f0b812bb97731dc125db685e5efe7c078e9ebf9087792d47f362bfc828c95177d3e1b4e85d4d8b5ec3861b6672e85787dc92a3166534da39b9df4473c373020f22f7df921530e742d262872215f0e8b90b7998993b7221c142b336122434d4af3e327311b98a82a05ee620a7f777217119b992b88e5c4bfae5b5d4b4d8f4ebc6f4fe2d38fd6275d8d9704b5a448534387d44434d0e0d182514820916f0e94d41a6901581d49bde147a00f5a9973028938f53c88ac0e94dde24f480e94f2f614cff03669a22800abba094697e67329d66456e535373332b3a79279f53cad3cfef8e1c66fad99d93e7e3e449294e8b536746f6d319796b3e2aec7ebc35519ffad4d81de5f9a850763876329a1c6f39cd8db7fcbbdab5d2cd6825d6551aebd7e892e51d77c9fa85f2525e6762f2c5e0a9fa62baaf5ddfa5877e5f5d7ab2bf673ba4c9c101696e5a7410e06e31f1aec3ee8967831b85024f20f6961702dc1d8e2db9abc95336862359cac66053802c656208ca2b25dddddd4d733367515750cecc2989629268273b114fd632cdcdcca3fca9d12fd285666fe5adbe181a9c16576431cb0f8770238972b27f8782f2c5d447f962e8b33cd61753bb146ffc415352ba120bf08b945c1512860a5d41def23fe1521eb9a3ce04d9bf93c113d2a2bf0c5f8ccb25dbc129c5e0c4e4340c3b8cc6cc5bfeb77f7c50426efa3dd4a837bd6c480b3ec81f598b4c436a245988c03f2c238d24b94fa637855e50f615e051febc70c89f1a0d4bbdffa4c09598b75c8a2b3c2d8235bae56fc11b72802bb1169d862f4a9a7ebf3833236bd1080ac4defa7609f1d1959202823876cc2a720f38e327edf02aaac8f84b3fb9d7febb2de22f4f15ae76c688ec66e29412783def59d8f0d342e4e99700b2ff0a907ef58ccc5b2ba41a3c5222b4f06478f041b009d293ee2e73182a9c91b964d93583d72f1534f1d3a513601e8771f245430a2906215f346634390e3b0921a2210577e43466ddef4e9bf8c95ec3b10ae958e8793bd2654a96fe19bc0d0a53eeb9ca3d87ea132a9e942e723e0d347c31da07c093fd129b90305478c24d9052c83eaec45662aafb5814c5d64a6b8527fbb73e199a576252ba6e2061a8b0abf196ff09bb76909dcba34b48e68e244b0ab8a3ccc91f1a453490346c46466396dd25934232598732327713b1da7f06895092161999a913364eb884b2eb887402c73504ba825c32979116bda5255c028f2bb19558e6f969ad789286333a38e6b1ec2e3cd24483d330543813e42dcf140acd4cae805d6a4e3f763c2e350d9b31d22e379e4b0d2acb1959088465fe22f0f82208ac3e3d2f82566ac0e148b2780d2e3c598a612a3323b3210631fd8ccca550900b8d25cbef04205332024360a75f2c18336664d2250581180e4730b2fc09ea583c16016449693531c23438343774265930e003e6a702fe805040117f06144917effd15e00364644a23f86428925085581ea5cc08412d0dda5c6d0bc0755d9e74988591ff59beec5ae617532aa24502381c0dc01bc0178352006f7e32dbf7874218bb1c25945acf52976c74c9666443fdaaffcd0cb5587b668c04cd6688fa55c399a0167d46a84597b9ce186971e6c8ccac45379d6666277066c804ce04cdc8baa7b921c06743bb411e67eee661660e1cbb9b5147ed69f9627a6546677fa41276d3895654ca64c589d5542b14560a8b16323c78b77006151a00c005406cad7435b8601180ab45b690a464e91792595b7f46d630193f33413347668a669230e187e8466849762924945dc604591ea51092ec79944246d9858eb23b5193fd6da534373438fdf223c82afc907aca80884eaa4894aca47a2975968eb0ffa892fb35e79c620e7fb0bbf398d98b85e1cdc9f377f27cafa2932a63ea27fbdf7ea564f95d2cbec5fb17e337fcc82c481f2da95efb8ee356dcd316b96dd35e53bd16fa9fc0716655f8c5ac42d96d5f8cbfe98be1c1dff4ddc99bf393317de98b51b178f9c5ac3ee5658b29af4395ef2ac451f3ea57618f16539eb698f29de9db9e04caac52514a298944a2abd57f207df99154e0a863f5f4fbb8a7e0ea39fa2412a5f4bf932f8607efe97728afbf98ad05c882fb14d66af5ee28dd0c1c7fe4d5bff80e2dafc60fe495a3fceabbe99d40148e058e3ae4927cfad5e9bbd59f42395da77770f52770ec8eac56a07fcc8a07d3d74fa6fbefeb27e33dfd942fa6f4ddb7fa6e058eab4f06ffe9e927637ad37773ca26ad7e05ce3cd359c5a8c213700598e306d0061458c3c32915f82aa28b96175df872e2c3f5aa0ea61fd2cba2419bee24084cff4493503c04f6a7ef6fa36d9aa669b9c11311e0feab7d609b293fb7ed3ef7a377f203e6361010c9267cb6a575d6a9dde9c2055635bde1c406bb0b178114846aadee4d29a55e6badb5d65a6badb5d25a6badb50e81e9d77f0a832598daa41e8887498548e097fe334feca0f46e6b6ddf909c7ba69f13635c7a1daadc2c0f4b7fb50b7eaf81a72702b46d5fcce9b72fe69b537b2234bcd3f4ef48a86f70c6a04216e9745b4852c0fe2af97ef7f6c5907ab0babf4e73a0e979effdf47e2641c41dd4a1df5fd3401d766859fb1d5a26853af4c7b8dfebef81dd0de574ddbb6960ee1bde77265f0c0ff3b9ef5678b3f40ee2d740d3cb6fc7d5622ea99bb9fbf881dcd3fbb97979fbedbbf95b28a76b7b07bb23d9bf7e313c70efdffdf6fefea02ad5020b7854c9af22bae8f9b3a7c549e94b21a42a3fb84d3868b105dc3f7a272310e54b151d577612290a2329fbcb7c4b4dd71d8eed60e7eeedee3db18d0cb9ce547d9c83e97b28fe7356fade374e3755b77768d1a5d1bd54b6430fdfc177b075288f53d6350ecb218346299b39ddf217af34c22a692413a5915f8c5523c0fda3b8cd19339652d97e32a6aa94d923f30e2e5dd29716a9366f0aa29084bae816fdeeea2e444aa78b86cdee278b6cd137c03d40a6237d5214a88b4c555afc23baeba96e70f450f5f2be2c0981b5b7fea3b7620c5bb8f50c2fc0defe5709414872294ba0209c2c4d3f10021080a20084a29078501a4252028f7446eac176c4ddb98ae584c106cbd082450a0b65a53a596192429d4cb844fabc8edbae662bf59e41f0d442a0f230d601ce011e6f86e9644fa7f4ebe3149d5fe98e99db085dd7918a541f803396641e657fd935f4c80c499103c984827a88cbef804819364eaa75ba73e8bb1ca7ce6b208be28c53e7a679ec109dac26af45daa24cb5004b267ee0c963964cfc10cb92891f6eb2fc2d4b266c9664c9840d925cb364c2668afc65c984cd509eef5900032add8374d93173ffcc9f658f7469714b45dc9b923f4976a58b8f5f22b23f054932c9e220135cf697788a04658aa2f3957c4bd5b45a7ff0570f5557aa78a2b7e8abbcb5fd0c256b7e7d99c397fe21304e115db4bca095d25b29ad94ca9c52f964b48c250bfddadd54a0a63b9265be8661d20b3c2d337632fd1f71a8481e5fe48dc268d665fd1abd9d3c9f7efdf122faf583843c56a14c8de4b11e09da90e038d5306fcb16fc5ee20fb437dea2bfca4aa4acb7eed4d9f3e2b6c6cadcdbc7ec8ef699b336dda23f7a9bca43b7362dd29baab13524003beb55a39aa41e758b8e3549a6dfdd4897f994f2482171329d7541641aa3cf51fab6c6dae0f44b7beafdccf79ef75f38c32ff4f13d29fc0153c39147bf90d0ef6bad5d6bbdb5d62e92307549a6b6c65bf4692d6a510af9aa5448913ea5b5d2efda93ebbb56ffab54d414f58b567abdba715eed2ac89aa1e7d5a15ac31d3ef20656641eb896bc8abd6af2a6cce60b2cfb27afdaafa0969224527fb53738997e9da1d72225e50e436b53d322fd9511b0ccde7f3fae72d878ffc5708991698cd44aba581f240b7dfa281604997e38aef26a95a50c474b44a62b6985c87495698d64a1efdd008fb6c6abf42dd743ab66dfd6342c25c8dee8f0f4ab66fa76c86889644a6d2c530b2453fab62753eb93e96b15ff10a1945670be87535d0f8a8ad20b4a3928e5a09483520d4a3528bd004b02745245c9119e13e8a40a75d80f18674dfb33d5be16fab0f7b5f75143d6b4ff03e6ca210d7c5f2501c380215d2410c309d95f0b4156c82ea3902cfe2f86ee50d79b1698ca88023a7a4c1b18d2482e993537b467cb8b1953c0680943a7925f6392cadce5ab1ab66206a3c69546542d6ee24ee5743a7acef30315f3bbee9bbbbbbbe703e7cac94ea24896faaae9aa3d244b4df97e59d8f7f55dd45addddaab458bfffa5abb793b4b7f7c847b7eaf70082e98f12030ecba15b15876cd5af72094f909162fd0e4c80a421c5fa2808504d1933a6ab7efd0f54e996172dd67710468bf5b5d0458bf553bec55abb8f0aecffe2b7d6afb6462bd2798bb42bcd9f4eaab0b85a4889f8a444b2dfe4d4a17ac447ba9c644971604322f324f6cd4e39a7a259f693d83d1565ff3bbb455bcf46e4cb39159d8a4e8cfc099d54196bf8c9f6450f77aabd18bf1ef7eac29a736d7ba51d5ced6b242092ccfa536ae9534b29f540ec2dfa1d075e6fd11b1b3721643bd61c1b2d1ee543b636c7612a9b6e59acb2c9b6e6c8b0c8770b79a079240565fba3ca06552359ec37480a6ad13e2884b55e29922cf6c125e0112405d130932efd1600604fb61dae14693fa6f464fb2c6cacfd14fb2cecbf0500007cb21d49410db2c8b406da5748e780ef010ee8129509a9d34929a532853472207bcf29544aa294442989921125234a46948c281951859fa652b2d44adbba6bdc6f285502b2ae8fc351d7903bf23c0f15e484235ea531cb3e06fdd273caa9b58ee7813648cc07e261641bc0c89c405360a022f0fc99138bcdb7b1f9e3ccd169b1ce58ff7d1b9b394fe01f59ce9ca9e37d31b7b333b27de16254c93625f7ccf1d6fd626c7d23ddaa5f97e4fa5c38b6b48862055269e10442011e25119004a2c1808e1d7848a239d3629d993572c8ff9f4490adfa4870020a790201917070e489234f48e912e30fa49a815f8c2e60df53d6bfdb2abf404a39a7bbbb7b1d1a1aba45dfd43979825c52e3a3fa386c0e79abfe78e9ac99371305b255bf2291eb908e64a9df9d3cc1094338b6fe1cba58357deec5d85533c7563a34f444aef5ebbba8aa6aed163098679e5d73024257df860a853b45ae57c85d80809c416941ad3638373912a6eee41e3e5ad46a54a01ab6bec3e60fb2559f043f5990ebdb0fa78d64a9df79daf4ec60561e2db6b458bb555d9c40014ba02222d282ac1d5654e01dfa5573dc205c023c6b6e50049d245916e10444799c5916e1044ac8945e1858e4e1e1f1960d355acce12dfa16e7a8a14a8531edeeee9e3f3f3f0e9bb23994e9cba12099836672f0386cfedcda3568d7851e902dfadc0f99ca568882201f92857e7732d4043c7f26cffcf97b3156a97efedc8bb11c2f8f7e7d8d8f524aeb4b9f7ea0c292dc9dbb4b9741b43b3f99cfa2bbcf39e79c618da69fa35f3232e7e04f811d7af4ab5bf28a0adc925dcaa0dce22a2ee6cb0004ee1f55b6f0527a38f0e936285149a50402340514edb6ad22719842743de1dc9e621f29da3a38906ef3eaf0b20d7ba01d7082064ef0e41aea309f96601205390163c64b8d140e330aa28e082948ff7432d2294541e785817a4b24a9f3c080e99fba8ee200fb63ea1dbd098e6e6405b054220c4bf267a944186a9abb9ef7a6aabdbd4f43d2e3e0f2fd64aeb56f55298fbf18eba327a56f18049da47f436debf216defbdbd66d67be3d281dbd3ce5a6f9a0c97eddf6daf36dfbfa54fb7068af7d9a5d7243aba28a2ac430757836d720370fc37d8341b8f0031e0c12f7eb67e3becdf75938aabd35574c91909d777fbbcea314fbd09d8deebd0e0437955253b9bd192df68b11e77edc2addfdd65fccb48141807fa4dcf1724fbf0678f96ee00d7f621ff0035122cb1248894fae214b202538f9f64b7a61277f96404a96e4ce5b9775a35ffbfa92d69e9f027ee4fa0c90b96df46bac7efbb4870b8c3f646e80fd0ec7ae56cc3514813aa9d25e7f7d4aebcc9245df7ea5974ac9223b0a6af727ebbed49ede1743b14549c35b0516d2dd4db5f296f4bb6738aa9cfa67f72210114531c3210a221c88661293884fdb7451e88ed8c4881123468c183162c430aaf5296d6923a308193a327264e46c34d8845c9ecb53a4480f7c664f0f8acc9e2245ba048de3117951f44ddf78443defb440b4f8f8244124091f175c203ae46c137279da7eb5b6e903c9d93664137279361a6c43b62132b0420f5a30852180c88251500e54d084256445bca0061ec5c7c40c287ed4cc8cd0c4096c9890630498051c31450bb6308223b8200298b1658a239208534021b7fc994268069dd3385d82ce99372ed0517b6a951193a12323474611d335df41d95d29e51969ae55abd609104380172f46d01de9664be8a83734064e8c9b1839a529f8779d7c114248d68488654d889bacfd64cee3be1bad4c29bdf9fef5b074d9fefefd62aedc6101f4355655b5e8af892f2dfa0d6bdc6fc8c66ddcd6b1196ef0d8406a03912edafb770ca4cbf6de43b27fb7813becccf49910414fbe4f77cc1b72f430730d675a6c9c162fccf49a92c61f798621307dfa35fae5a2457a2fc6321f55a531e091c5fd7d8dc5853844fba910cb70ffecd990284d01d75a6ba55fc853f6902e7dbd1ed265e6f930c020dbf6cea3878f167770d8ace956d3efbcca501ebfc314a3349244fad5441eb650815fbac5596f5f0215f1e4ef97ed2ce8efa2818a70721fe5296b7889c0c111e2060721942849716a020c2960c09850541efdeee3d375fe3494d365bf417fdbf5e94c62fa4c1f0a051e53d906336048294b50a082cc981184d96be0f614bcb476ad554ea2cbdd49546714d3255fabe09ccd28a6ab52ed89c0f1c70e4d829a156a6c95e1dd66f5c1113398cd70ec91a7ebcc7622ba3d67e644b88eeb18b9ce2c25a57a02ffdb70430e1d2ad97fd2ef59bfa4eb743220f1fccf803aa9a242414a2b85e12e64290c1277bb087c865cb4bce041a50f90181499dd8d4337cec4a1e0a458a07d9d80f39c2fc4942133edc12d4d378ab8a41eacea20488184526f6f8669bbfb7c6f7aa3842147a430323f7cc3244214c94b083124cb2584d88191e5123735797e3125116099ab872d2dce2fc2da8f17ab7ae4cdb92e74701bd2e28fcc75e10e2d73614a8b53d5e2148abad9b2499eb9d3e2ab66ec06cb6cffc52d38b64d0e6d422755a0c091a2489640520ce912640904c54eeee417737283fb976409344392c769823c5f12214ba01990ec5fcc490deedfaebb7c3799e6ac295d0f6b9e8ac4251296f84b584a0f832510a754600af82aa0d8e2b4200c70861078e671e670e68480faa494d2931b204e428025e773c81ce23a80c83fb9019e619d4aec2801a76eb76ece3938c311fe1cf736dc8360c8fa2bc30ac4a9ab83b9678df793e15150c4d5eb50e5ffed8bf9908512e2a819e551c21e2dae9eb6b87aefb34166194216caa368cfbdf62cd40934c1e0c9abc0693d15a87a4ef52b8e4422d1958a5bd52f66e52916ca732b8ec55abda3f87333d795a97aabcfc68a9347c21c5aac6e7255ca73208b67812d1e45867ff10baefec5b75f81f751409973e8176dd136cc267c201e260c9170497d66d29cf3a324fadff75112edb42f8607eacd597abf74f5a6d3cb164faf4395fd8b99a7a72d9ebeeb50280e859a5c2aa34c1e953aadde14a2de0434f9ce643e0af49fa81797a8378941bdfd627840fd8aefb4933759f128d48a90a2cca72caa87c19337812bfe049afc0453ef20ea55e0042202446485c7e2e51138a2bf046b288179a030426f46c4043b95fb4b9730d7b7d91db270d8ef97b13ec6979268a440a6ffe2b2053dddfc3927d8dd78442dd22a9b67a7755a7429607ff7779c588d147806e17dbbebc21f35796c9dce691c28f0787164deb65abbbbb906474abd494f2086a10c66a9841888e4ae34652dfdb8333c964ad245664dc341bf7b19dafd8f3cc191e6f99347e71b1260ba6ad8ed04efbdf0fe008f37abbc7ae1e8e5ea799ee7e5e0bde751253c8f02d9eefeebae8723c6aafb12f3959da669eefff5ffc81ec881638fccfd054717e3cc9df71ef72373200fcf9bfd0b8e2d1af702bfe8344a043c528f5e4f12806e5bc8c2317f7b99f9db0f7fed8725c074b9163a94cdfb20a5df86649abbf6726c21fec14e297d9aca74cedf40ff6d48defe02d9fe6ebfd160ca9879fbfe62b66d08acf4f4f1e3a7e0c4608d6e511b4ae08bb7e8bfd4a895457a1d3eed491b38f2c8dde7fdc81da8c3cbf5b77ab5496b0972322d8247556e29768a38b9ee4ec136da2d1851600fcfe84f49ebf01278c494a6726b9af652a3dfa760a4d73a1258a35bcdfde47a765dc8b241e6ee6564ee4296f73ad4d7c091c7cce06d51dbfea57fe40dd4e1e5ebaf81b97f744d8d80a9a42d8aae7218943e95d6bea6b9b7682d1e842576255d5742e1e0247012281c520f28a552522ae9bdf7fe4b8f5d165b9c54e5468f3a5c581b8e3c7c52208dd3eead54896a6da53f54890ad4e2ac774ab9ddd0a3d391482badb4d49150eb56b0d68bdc0a76f6dba694a342ad15278167ecccd899a18219b1192a98b13363c746656a60f69c2a2dce96523a95524a29bf9320ad2a2dda29a5ece94e9802e8b3cf59aa5406f69f36c84c5b063e3285a5276152ca19529ff31ad12d1865a94496405b18923b6c049e485abc7986b3680778d46c349ba13cd211cfd01b359bacdd34ecce349b7e51cd46b3c93d9114f5abc226926ef587f31a2991c213a270c4fbe9fdf47e4601cb96d37e1e575f1ec1e367cb22b148210e31cfeeb3d6b396b3e2056577edbf8aedfa2d60bed7dd20ab13e77b21ab0b7188b9fbaeeb42b9d10e420be90fbaf7eebd7befe496e52acf00047e021e3f6baceffd7188dfffee85e3bd9de69493d7f49c74e95efb8e1b5b0bf0ef3ad677c3bfe39e0b457791c3c31982a02a98edd42dd702a3c55b2fdd52736c4de07bdfbf20aa5abb525b7fd24a6b7b0ea8bc42ddfdb3cefaf2566d0bb5bf209d1afd5a27a5af7594baceac93cad4e9dd7dbdae9456ea31587105dc3f7962393773d2233c7f0ae2ab704f1855b63fa5dfc99616a707e1f92ffd1465caf7b0c51278c438d52feaa966a754724ce926a223d582544acab06d7bdae992d1deb9fef490d2e992f5475ac14db2481a56972c4f1b8a89773ccf0078566578e9c98926e27ee35133d749f91fc8361c7fd4cdcecc85f737b07e773d8756eb3af6de6b5fd3b4ab59549094b74aa6e10fbaf9b3e937f63ff9c1175fbf1763950ad3fe1fee7e250b7d0f174181d04995d17bb2935ec0248f0dcc80517a95aa87b29433298d7a4a0fc4c394422448dd24d26f40d3344dd3be3045b2802c7a1c8683b7fcefc558f5432d792e39029b884f17e924d3c3a1a54514bda74f22dffcb9d12a825380ebdb2ed26d3e2d0a817598afa3650bef5b220dbb2733c032d7b07d5abcd6d6edda1e79ccac437d1d3a5a2fc863e6da455ad442add6deef22b3bed56c6c5bc8da5e87faf3372ebc51ff472789d19e4813699f7e7592168b34ac8fbae50fa3a54514ff8960ec6d84023cf64df6bf3dfdb240454179be0c6b4a095383947e1e39ac14b691abe6d164d522569d45fdaa3f9320f19f462da7ff75236fa30da81c96836ccd274ae23018aa57e1171d3c5cc0b416fa0bd97dc8ee41646fa312606a94bdbd2765484a29a5ec809d4d195da3693387d99f8bf17f36d4dee479a8e74c7e858927a70c49c5514ebdc9772afa96bea56fbf9339dcf78464ef39efb9d47328d4a7ee4f2688f0c9f34b9f059f2c95902510168872077a73061b5ccaa40fbd295d44951004c7d4cbd0a2054bc5c4e4498f3a51ad58bd4af52bc071a53a0167667d0d40a1645e815a815a61626262b262f535c0e457843db43cda2f8e06f9d228abd5cca793152bbe1bb771509f8dce2ad4db9fc106a39ef4b45fa890b56366d4f74073ea779472ea39ee5326dfa6af01a937097b9819f5a7cf062a4c852bde942e5a38063c764e003cfba352612cfd6a9df6a81ea950d6b48782fd99b5477d0da06f85fae543f4c8e9c341dffee97b98f9148ea7473d35d2af91fbcd7beda9ac5ff8c3318988e5fbde7bffdde0ac1077219a90accd7083493a709f922e2b5efb668174393151fd09f7276f028eaa93700538f3f73520f55ceab9efda3bc926e0c87de7b4a899fc4bebf4106b517b6e44752c9d003cc93dcb67a36b7953ba4c19d4f398d924ec9b1651e198fa14e90bc5407374e818aa736c62634a07f5da7fa8f7be1b29d400bc295d6ce8d0382d6acf63e6beb16028069f2104786c9bac69df40faa58da5df5efbe6e9970ea5ff70dc9fdf7d374aa16c71867da7f44abadca5215b16d98e26d9fe276173e62dededf5a48bf72d3fd8cad019befd7af1130647fa43d69e04de976e9b7ef96b1afd41b6b427010ab2f65f3862ca82ac3db5912c5ad8855cddc2de99c9a7338749983bcbf53b967ded459eff22634dd3ee0da5bc76f645469966afb141b9c1ce7191b9f8c99de24998f51b18f92f1a26618cb215e2b059d4adfedb17778d1097b6426679bc321ff278f3d838336c1c1417e099a5108e0092475a24468bfc7c2e92fb69cc69cc43b1c51a26d4cc72c061c6083cbf0ba41ca8348fd36539c9148d8800000200002314000028140a078442d170381eeabae41e14800b8b984676549a09b320875114640c4204294300002030022040188940006561ca06619e3e86dedf1f3151fb10db6297be6ae06a411fde88dbfca7a6fdc18212b2c60a603ae25def70fb27ce1ca3a97cf76772b9d5e3c0cc44fa4c62dbc0721a5255108b4fddd815167f0b9fbe15f2fc7050fccf39bd9f09bc55270197d1ecc643bcb64f94c25b98beabe6fd8129697fb8c74a85861138f73a9f0b006b42ce8d886de629e886d54b1dd13e6cc9fc6207e025fb71ef42445838a5840950335d876374f50a6db03872d976d88221a13aaeadb3b6f4057f8d2e1be5c78ca07aab2843137621112970ea64a0edfbe56a4d01f9d7c8ba3e5acad9aa8a2d9218019d607d0bc2dbe3964120dac101814330188954e5913502cda3ca16de0f824d1e97fa8aa54e6e62402e473663447f55212b8247be729ce1999a488d29b9df1a06050cf0096fb15312e9f321b3c10059fb5de31316fb65c17c84c6dfee6e4bb953e25c93ade158bbdeafa81d89b87dbe6de3563bbc99e6be2a39987261a142d34251e4d464820c8603c0abc6c4490cfdb479665f742c3cc630d9218ac77553484d47712013e920e80deb715ad0104bd4a142b197b9c050748b4003e0edc3554b4628681efc72211b18acd2fc453d1765e7808eaf9be5ad422955bbb1e35e893b181c6c529ac12c2258abbdae6983c03fcce43275f501eeace5693a35c91873d0516976fd35425cb3b4a0bd019abcafb11f55903e35e6c330ada8017f8fac2e23a2de4eda8c1f805742fbb109a303a4366b21c458672be271239ee98ffb3cf2a7b35d880c48b2c44f1ebca887eabbb559af04bea1a934865c02ec6e0a9fc5a5921c3bc85032d9b57f4da4a39ce3e53b2af2e5b48aef973215c07efa10c031741980e7e683429a0e0bbdeea0e01d5b67bf65b0c678e4f6bc986e89ffd317df248574c3c35527701fee37afbcb1bd6b69d61f637413ff371ff1bf9084f61bee11f5c137e4fc6cd7faa5877603ba6c6425e5f047ba355769a87a8246df0de3fb730e9f8d153d0e11ad30761066dd1680548fb9b272adc7fb696ae33d94bea930ada820d3fa3e3edade21604414d82f382e9b855601bd87e98a2fad00e7bb67b92fa68b97c40b8b03f0672d2f34e88cc0af4c32e9e13d13603378b3ac0e3733a002f3beeb110cc236b502fe3e121fe06f7790a92ff7d159520eafb0a06c8d9322d9b4210fa61ce7ace9b8e8fcec5e421d8230c5054477728f2baf53be50e0b77a437433fd8c36dd67205063e4b0f5b306a26aff233601da39d5038bb5fe3d298e90e76ecd44a3b621eaf4c29b96182baed661dfd6fd33d218f5d7cca85cc5ef9709177747b2a4902c79cba34a5d1b508da107291f4bed75dc427c6e4367fa5ac9242c2fb500fc880d8558fbc9efc12a3922d9bbd04a777fd2b21f132a208527de62913873118132496a9079604ac11243d0bea4386b2d3655b4f56be1cbc97125a86b3bbfe65171462ef66405b654b78ec25981960834907ea8c1f9c1e293639b8b686472356ed12397b81e9d65802450ba633098763aeafe0d04700c4ff4185672dcb0dcebfc6e8ba7dd4ee583cce51e05cd4492f2cfa48cf6ecf76aeec996a6f3e89d4cbe493e90c52392787c1811e4c2f2926dde212ad502d61a2260b969573ca0efe97ab5b769ced047c881c1999a0e099b19ea548f9f773616cebf626a50670d08fdd000cc0092b985b86cde97d50ad1fb01c678050a78c8f605dc731b6b06b3c0114150349cb178f8a921df123e61b34f8bcde9caeb22b0434676dd022a094c6060f1f04cdf2319c62a8e80d783717d94059dafdb8c8afc61cf03610c3a5c6cefb4b13ed877167311a3ac44f6d62ce6967a6680e465c2895b818512fd0bec2ef2a911026ac421ef12a5616f3e614ec9a1881d1d7b3b6dc0a9d0f03960c9dd4ae5059aa5542d5521f19a15b5fad0098d603356dee08340163f1aefb3881dd6cc387a6d3c645b100785171df082155df1a080a518a7dc6fe580b7e02d557653081a2d5bf8cb5a9d7a2d2e130c001ed94c01bea5c5da835532bb0f621416e8e341a31866d8404e6a98f6e5dba294b23ccac04b2aa0ec56432066bd7945e46223be2c6e3aa62cf8ee73f855dad861048c881702b5c6b27f2c022554785b55886a4c8b668bf5d6acf8a7d274a19558f7207443dc3fae16ece4046389dae937f2ee45afe03b20c91d6f4451bba459de0aa9e7a7c71fa9f066dabc79327d16606cf07928f9368ffc6695214878c17b4fa0220abd727bd16345443fe5bc4c50406c44d7a6f67cddd4808ec7564d77749b492213ae6a712f6fca83d1d299729be1d363f8f4126afbe5c14e5c372b4218edcc5b5997a32e88129aa3a38fa3865a3a41fb41eaf77421a3ae64d6b54cba96715732e85666ddcabc0b197745065dc9d495ccbb9079d72943d285c42f90894f56e553851f3319752f83ee65de950cba94599732ef86ccbb914177327427f32e64dcb5ccba9069b75aa6b44be593ca7cbd775f8850c4ca6895cf4bc8027ba9a02280dea363351382c4ca4af47c01ba1f755c06676f2a8aade460247d96219b2e4330cbed61fbced02519db11747a5936903095c1d3e3a5394929589df40d37648f4c8f9db9f657fe84979c98ca7a8162b25881a5e880672e6aed4695b62f8bc6da3e53854bb9c1a6dc07a5add1a25030f4534d2dee00df3a726f59ec87e40a01f1c710e2b88c364454561564c362b1f635f387f7fffed078f5e2ed4342566405bc872d08020332c2cfe08fc362cc422aa299ad3794e8cef3c915a1f76d23fdd4641b9bf626abe750f9b550de2337c49eb47661ecc873434f312ed5692227effffd29ba7e639d6676aac48a3de8a7b75699ee9dcc140fe619ec833a51fef5059fe28d7e552463f893e943ccb07834b13fd95b67daaf2e7bd5cfd0dc6a2ba165a65d3fde0702eec5521bb7fa9b9a5b49e6e6ee5f5e191658646ee8810bccee5bec935020ac6261cde7f648c37b93d69245b9d4a235f50231c3ff9cb840b6e0e496a51410e188727ce01ee0af15247733f45f376f95311c99084086f7678a796f6dd53776ad1e4c2a400b6308ce8e40a6b1ddaa5f3dc4383c38ca4dede04c5b10c631979362907dd471d5172e802d80b0185492aa65fdb2d728b64591f429eafb953a41209f1ab2eaf12d1ec15767da4340bf551f2b5a10c5550487d88b3ac53a60a6002ec16d212cddeba31aea5a484575712b999f2c42308a960b1f827965e56986f18a246196febc047c4b8446c567281a8d75ee73842d0f5fc4021bd4165f6301cb9405bdefd9614cd30ce1c6ca2802b47ddf74b8ec62d31452fbc049b5aacfbc39ea7ccad63d0380820cbfda66aee1dae93e1a964b184f6cce4b36072c4951cbae1a73e7f3efda965fb45e6c3fbd4b4fb9f4dd063e67f6a7565a4ceba2e7610b12b6d59a8549e46e6d9e3318ba3370c46cb290285644eea1d61ad68d523991513b1288c83d12109d8e152d2936163aa839c5f0b99e6ddcfb69dd2936860c28f7b276cb84d770e8b5495485b2f4e894ea0af47dcad87e6f8fe94a8e5e6427cd76dd2c8416ce96f2cb3962d2ebccfcf0f7ba9f44f83787f3afea22bae3506953765e9ec2180e9f490420a0135f6bc1f8c406387896290ffb44188987d018eaed38fdf156ff0b43b1fc7379d9e75c6b391bb55e6250b8f179f26e99eb932002c97c4431ba6762f8bd51bd7732dbc25bba300d10b5e4773549cc5edd6cccc0c5a267d745519cfc06e16ae8c46d6527ccfe3a832823ea8f6006c22131840d306c967b577e772143d0ddf49750d4b41f6322cdc728eb60dc7a463738d5fcdaa8798d1b1bbc8ab2ce3b8349f9d55fd53de515d6e94c3e3747f81c08df05cd22458a577d64fc791b13d9291372d650bedad40bb1c514d02feb5c28fcce75b79d646095d9100b97fd9f73025d618ea067c13afd89b5eb32f55d50e538b6f0d520d6d4a53fce3326c2944ba89d3a7b5a278e124b0f7fb88d60eb1fd24ca9ebe8f5281d337121540420a038972e06a3af0463c2a0d34119629e0699cb163847b2aaa873ebb8a389f5f2fe11cf57c0b0226849e594a3d5467a9b698f71e4c468d50c2a21d7a08d2be4dd978b599165394595b41c0541cda85bae8a2cd755f81ef72ef688dfab176d46c28aed092b03a2a62d49b31ac3bfc7b22684f946f1925e3a9f8d174bdb26cd210353678d8e247c4a031eba05b5c54fb31958e7c49d2026b748bae2138a5c0f5501f349f796ed100f12c3dbd654e844fb1b721403a3033ab381c012fd5ca26d2f1b3ad617b5203f84739db1ab1c501116a1f3c3088895b87ce40d486c72d973f41bfd04226c5064406aa1d723735f91c87d1389ff918aaa0bead7fb5afefba6f78ce1d1a21bdffc43d54dbc9a934c0745ca03a7672603fa0ce78ce9cbdd3d0b0764161a29e71f785b072492105affe1343d2a64a1ae5ed58c4b5b3ce990df69290400d9865a0f543fdb362550fdcfdac7b8984b19b0fcb45eceda6416a981af2191f882a6724b40b4a853c2a54ddc8527f6b8e270511a0b5c3f1090a4104703947bf9f4f18a019ed57c6a80d3bc12f23442a40c877e0656df3d5d51e769004bc426f5c763f154049e99de330249924332b2059216c44c83f00c615dee0fbf92ab6c2c3b970f0f90decc86b0151178fff9087e6731076c8fb1c28ac04370dc539f68a293b5e31f100cd3b93268e8b3853316cf5a83009e2d066143126ed877bb297fae73a744bf55cede894c6f707332c591488e34e5a68ed89e88227465527bd799bc5d254494a84acf2cc61995002e468309f886c838aa3624a583f16f66f59ff83be8512c974bd850650158c345dcd70110ec19426e2eb97a9ee34fe5aa09bc33f6a4749a5fd5865b363fac35e5f47ef93449710c461cc3a2d654c8a3271328ef5f456ddcc90922d8584c6a42265bdd49589e57e1a470a13a7f37df82be4240d1356ae9a5eee45c2570d360d972ce1d52f782ec37f7d72d5ca4e5fd8384c6e1c7f4cd098a8471398d6a5adabbae069175b34b6ea20ac9a36a3b2b17d66c4301a41db031daf3dc4a09c91748f56dde65e75c4b3bc4557c2777e35cf534654bfb8a724d48d2a7c2409a85c6baabc586bac6b2b734ed81478b0ac3c76468d2c2606da6a06c1cc3a8516bbde822025e7595e98b7054d878bd7b7f8083d32d56d509d4050bc5b4be905a4c101e9bab4386f2b808eb8561b2b1028c4130d2910a6641c40d41a64b0b68c46a08f3a04ee7ae34aea1924746eb91884a3793518d8d2c9da157466146e7229a427f1c3602b8be7e9fb912823d8da6f7e1c24690802deca1545150c3e88614f56ba338b7872356a0ed6e9a70cbacbc77e61667daf43fb5e37e18cec3d3ce72c2e212d08d219dc0c40d80e3b836c9cb68a0b3fc22fcc7be840d588439c489831331e50717269cfe3ff218abba1b8079bfd8048959caa493133fe983bfae6117e1b66d8a6f3385e3c1e563c27f9c215f63cb6401e8cf3067c74d095780cc40ee97ebb8aad0fe3a14813227fd34298064a0e0c6d2cb3d792f8032d995f30a2e34695b836a3832a246d15f3a8c32ee46237294a7524a7b5c7191dc145f1352eef86f82769c2074dc7c08f5f902dcd46a5377a78c7783a21e3992514b7df4a55899b3af2e7c11edd0dedbdf0cb74333e509f0ac2854932f5ba7b3dc448ebb8883fa18c2c833911f72a9e8eb9b209eab803d46e49318805616204f7bb479f9156dd99c23e06b5fce948ff3a075efa7cc2770f9743221feb8ae35c55d269a59952c194dcb6acb59a0362279d39f4cb8fa9e24746a8325e1141b7b558a5a393c619e43fedbef22e81522dcbc00c055b610cd86cd433a133705d2becb1abff7c112cd25543f04898eabc66d5a623135310186d15a3ac9b2a793e3dec4f96c742cb4412f4f5deacddecdb9d1a8549f37c815ddf065206879b4ea1d8b884dd1950ec44c747a4341b5e23278bfce79451a1e11988bf1e5f10ac4f45ce45196cb500951e82a54c37886ed205e95e2ba15796428979c4a7b5778794dc3a292fe48b12485c2ac23bd4fed2d706e00a323a46b68056c5a04c64dbb4a301a45c1ca001c0b11894a3cef232938b52a424332b2f5f4b64a84df8df9f4cc824c063d372d8d822856753e7d7aae9ed16348d447628e1874ddbb1add54648983ee908d85afa620fd7e46462ef6b15e0a6d096976e287ad56d5db6e6cd198a66dd8caf4995ad470a6e5b0dd36e9e3b538c727d90daaa90518c461a6d5009bc4fcba3cfb4f16cf04920ba66bd5a338a1fe44f594eb737add3d51919d0cb1f1f88e05e4f674b5d9ab91745144206e73902260684a024344f9900953ba58ab7d74d0a182dc813917fbd2a3a35777eb0a4523f1f82c339dc6967afd6fd3538ebf5d272bcd35824b6b16b83f03c1b085196d23cac8127d0cc6314ecd081b39fc711831011b203c9c6d01c826ec0aa07114074b6216df2642459f2f58d9032e2848604bf3af1c14cab3fd08c07f65b1d7fe0c48ac68b207421fd908e64c3ebac1b4ba977305053a78707896021c1673dc3217236184740ce96d8c25b05817004df022b466a0a90d4680561fc7f9240055ab247b5c673e5592451be77217f7c4e374bd26e3bc5a4fd8d270a3ee64cff756e7ac2086ccd18139ff4c909d485a27a4912d3c2b6112aa65a49b958f784df4a7613dfdcbe5e83ae00753a661a4a963c3c21094de51c0d5ba58837abd716f3ae93be3ca0eaa81e74cbf23d924fdf63e85a88f94f4a3c0aa38f8645cbed3a8b87e8b2eb1c95f868eb3ef85dd49bac58371c362b26d5a482392dc1f44a3007481c748b8eee217df0553b99d10103d3c3299cf2a43744d0f5062aa821888ac472a812494a36e1683a199a5e55c11359284fcfbc455390057003e41a8b70df90618cb87af3343e579e895bcb2c2cd5a48e6907eab9271dfaa3b79be1505e130f280a1d7c097f29b0bb7abd4e4178b67305c7ed1c4a07ff3e6ed13f8d88902c6bebb58a54fe4f1e1a9cb3b5c99a76de4491ac09e2c7d61357bd138f1224624d83ce09febfbc75387ca9673618fa1ce4853cd353df6de934ba1e0470ec2c4dba8208ff35032ddd5bc90fc1bf4095c3231814ad1d785fcc4a5ceb90ce8798a36f9cd768478dfa0dcf11c6823005277294ef5ab3ad70dc44e1000ce05bec995e2ed5ba4f4857bdc4610e4475094914e57925d2a5076c5fb2df645bd345484d906c8fe8650ec931b7a8c725608bf2d1407988957e82cf5646e0d32df634035f064893da43e3a4ca80b92e80bf24241f6e6f25eddd2ef72d8dfade10e5b0a08102cc435a0b330f5d9f220453743a2768a1dfc79d37de28ca626336a681661753a34b5292093f97789645488092cc091ab886fdf0f2fbf3074e55d1151b5d671099d2d38eb87f630ca9a5a380afe6be49cb71570b501838603ca35e01e0802d1e2286076e5688c1d52c02da260afac4e9114eac6bceb967a140afb4c378b793e60b3a7ff88fe0c927d22794862c8eb670b2e4a62a754196e0f48cbbd508a1d824b3eb9f874ea0cf0e9308276dc6da39a3933b6261bbb3807b45ece3949f4d556830fa30cb834b923e0e2d78e5ad7002f52c42f96b41d16229c1ef64879c3e52a80188d42bcee505c0cf9afbb70728490e95ab59dc05396f4ba5a5d75d690d4386fe67a1f1d209d20dada15efeefac76fb67a3d03af49c45af93a7290ee9935bf9b0b8b9971a2d21b6f7a3e97ecaf2e49d5cb488596d208ad5a019d5fc03ef40cc34627cadfb96fda55ddf54adcbde7eda3f344c97c63cfbabb1c4bf5b846395abc4554bec0faba9c2e13c02c6fb081675046913399898cc72d86b96d1ff2caeff813093424028248b2ac563061a058ab3ca8499a7a15219d11c8696618ff616ae4e83d1b2fe1402e31b6a3e1941c6b71b84fc3b19fa920e187326e1d62afedcbe15306cc7304170bd4fe41af3fa5c4efc3c9dcc69bcb161854511a1b7cd07e2e3466e6588f8968c74966e163be81f03e28da0bd811854f2efa0d3e0ae2f1a2c1670c9ccb8aa22af172ad9ce1a3a8a4f7d82890cd22af0314bf38a96715a04c9b61f00d7f89ad5713db0206d5cffa6c56c21213c2ac27c00f532e1cafc94099d6133797b76c5f4fae0bf2a18508296a38ca19a4e9615de6b606398f5836ed1e2ba3df2694e3f12df796a4f5f4686ab0a2d8ea5f822fa71864e9d4ae5c3f3a8f7ea451a43154fe7ac87772541e2a43c4f6826d789674799807514eecc2cc6f4a99ec9f56739b9b4502507a7e704ecb0dc3f9d3862628d615e608ad014d635f45b480b01070caa12af40c1227c35ed4bcf352eea7ad4a1b5486982d2e71cfee7b57f50408a0e4dc150195f23fc3713cad395e4b3e12abbc9958e48d0b9b767cd42d6af84ba69f78e583ffd666665d6a7a1d7d0491eca53031193735ae842a6807607347768b8a3fedc48bf0369ad68b5447d9b6493fabb493954d1aca755ffbc5f5493d1c913a1914d264e8a002a1a05441dbc6935335cdc9891354d45c79ea4b2a28cdc832acc9001221d04faaf7e654bb3cd6c6f596a77f66f8d5723f1eb704b0970fb5c570cdca8984cc71d8bfc65999e9b9ea1ce86b54d9fbdb80698714312411532fa108a12d5113ff8e896c32d14be706623d8645b2d2e58c02360892a77c3ac6bbab349babb86702d0e32b7606dd0bdc49d9b4b1d5e0774bfbada12b003fcae69c971d9ccdc1fd7001253522fe8d7c915ec643bc29c1c7a657fb48f9b7f4a9014c186dd605f2ef0a22aa82cb4f55ddabf84fd0a5607a2bec7939ca55fffb20df4cf8cc5e2e2337797a874aa7ec9a59bf851adccea683ef37d415499a60aab794db6db8322c154908db71522754936f9a26109f89e6ee2e47f10f69cb504312158ab314bd90feabbeab379cdede16dc906228d5436801b55b6c756e675894235124dacada25be2c6e70fd74c01c903e944900796c36f084f9d4331147daeda19a9ae197c2e5613956fd25574bbd9e04d591a5dacfeaf02df28335d1214fb80eca11b97ba21d1956e120a9fb8feff9092a33586d364d35adb065dbd568006880d6fe968c83b1f514dc2ba616d2c5aa9fa69e6e2df4a48110db0d8e9890c9d6fc501ebb344bc141a3de22e0a8a234d8d522bdba7c1d506901f62486515e3f34e9bebd77d0cba57bacc99599672bbd32935b3f66dd58ee83191cb83d48876571d0921ca2c8ccfbe35b9b6f9e09319a40c140a75aa69c2bf63d8d6648204f9154e1c552193e5df1476cfa1f515d2eace24d2a2e6e48fcce037c587d890e97ccf0c3fb86864674de854fe7ec6bc44dfbec8c3b323b71290e9fe9bbb77502e4765351490480f0f96f9b8248d6f434e72ce871964343ce39c2d1919c64a8c397cf6671d648576d0d010ec0099359dd57ea86fc9c0097fc9ca941f98963ad10e95034c85823a90afa4e36da312113c60798a071c77fcc0e900302d86514095f7f2f5d3410ce1cc26b700d59632440fd141e8ebd1c709cd3d371e9c653bbd332a54150bc300298d4b53f96cfeba6fecfed92958788e5139103876c7360b05d2b8b3b3eba6c6cf0f412b057f8f8cd8fff50fad97ba201a5f5732939fdc7b9d9cb4b87440c3e658a3ac08435ba40a1f4580251d210b6b9538675e2cc288f6e582e1f2e210d0bea757eafcf10354c8921d4c7105b87315fc80c599db104d081021000e4fc532e54d462384bf00e2724338cd3819bcf07ee1e0676358961bec830750a3656d1648d31d12024e7ad3ecf4e744240a5b0a89d2540221bdf41baa3e5ee75aa6ed1518209ab6420e2761634ededc2dab777c625b3c09649c510a4d50414bf65c875ad26d94deb284819ced9fd099597384da8b380b3377291ccb6e64d27c6ec41845fd7cf6e9d0a4d709c38d2551ab8533aca3c66c86702a967d8be689a5a84b9b734290a1fe9f34620dd2597815f4a24f330b1761b1347865d307eb7c8c0ed2b65825aa9536e06fc9ca48583d50a138ad54f4b65ca252c052c252c46ddeaeddda98f2fa1efc8cdcd2a2bd06d309f4a61696aee944656d814c330f241e2c502bd51f1fa14b1524a16ebfc71b4599b92057ccb0cea7e275feee4d1e5938612254b69cc08f062c0880e87250c6c8f65a8b6f12dd73e4a91ffe1dcb031221091f24c1e594492f5f565a82416f2c88cc335a0060626820b90921fe0f028525357766c67d76c2eb5833a50c3a82c01fc487b74a5ec5d7cc6346f282b13f3bfe1da52109f2ba624cceb2775392dada66238dbc93d99fe2df60a37148af2e6acad92d30e0f459efb1477365b53451367defcfae8ee1bf323d203575e7cac4f64e5f0862f4ae47a924d225d7c37d1a40aaa47153b9a29c5dc68d665d6fd48dd309148142244f47feb489b9f492ca4c9e260aad4f8ef9fff57a2998f6397dca3fc718e1b475e80010872bc9ba3127d5628335ac8cf7dce25ab53a3357761606ff4f6459ce479e506548ce3ea3ad28246adc0be20477e6120f1ba7737a72f57c2ce34ef8ea9650d98be426c7b08c30912776e93b6a7e0a6398f7da0d61d5f5b80e765382fdf571a1b9d814d8838ca0f702e6b5ec95d7c2e628e5035beb0485f28105d07c4b3acdae7466653f21fbabcba7a358c9b575707970487a5c859d01f7b6e4120a7df64426d8243fa1f3d9b45d7d854d2c5cc9deea17fa3eeb5ab7afa91a178ecd8ec3d4974723fdc177f093b0e346beb15a905be1e2f1c3643c719c4feb7f4828025357b2e5c2509e80f22770cd78a28f497ba90f66ddc3d62f1930ae9a110a34e8e2d95a04e8ddee0eed2123625433a76bf8c504a6ca3d25854d06da1cf7eb8bfe626a0e761c6bdd707f1217e08ec588818c4d8c67d265269905c4265dc8cffe26b9cd73117a786631d856da0af8faf868498b392f77aa7df18aaabcc5bf56610fd0a131536ab0902c69af82905f018c33953dabf1c8f7e34d6ecd8bb512d81b0711fa261a6c156b1aa93c362d07e0d6b100c4cd3c8ed4514a9b23a9ef5b084fd9fb4ba23c295b0cb49f6a9cb3ac51ee7f1ded2d2b14e3c1a3e800c19578654d1c3f11756f246a835a878a95d595a3468ff7309d8f5f9f79c3926562e132e9d85e998a394341a3bc079603cbf20c14e68dce1fe8e57aac78bbda9531f9acb55553aacaadb589beddca31b153b924685e64c868520bcb2d8ecfef635ebc5def42554525886d3f625d8bc7d60765efd231aa60c690ccc240896f14a69b5a4d15d80d49b879e6b70b73993f6ab9b7860f5c9338513e6e93afd19827b43c52e1c253ab09ec7072d0bf6079f8fe02c3fe17cf6dab2d8a20313e636dd92ca812a0803d3e52bac7c192d03928a8b0ddf1f4bb991705a0f6c553422ae375dcbe4a5dea003eca02b491a8fc66d02f135a275abea79f8467a6bb55180b4183066ef1399324e58820ace4d12c761f7ddb23d65003666c6adc9d3d07484a46a20e7c12a7654cb6a3ebf01db5ac3c85185715ab32c3d526a3487bbe321f0216ae964e5e46263a44733b51938df82b6d8abd819ad3f2021319c22fa90909a69c641dfecd6150c96055d02c870306318536f7a0e23376323d8570311e50d47a67cd3498d848ea2a16be892da5ce6f00475d933c7ea6aeaab8ab41153286774c18b1fb497c8226df6c5c98b3fc33b1e43aa0f9ffc3abee8de018eacfd22838927095c4ef3a33c05c5314d14914959d75e11ad195027d76015c84278c78f913d1360b7c4b8a767b9c0ccd42610aedc82d4c26a6886c7aa14a767fcaa41bc7ed1562e706606077acc28b8a2ec981b4dab72efcd69543da6e726bfffa94d498c4d84cc0d451cfd917737c6961dd9d473def6af4eaba7b7b32354b16c99cdd0e30ae5d315ee02f45d6eba9abe063cf6bf05e2c60496b27b622ecb88f8fabc9f65241ea7ce70c2dc27855d70859da2a39ea67f2a5f90ce74e88f9254ae2f8fecdd2ab4265446581ca401a89705312422766bf6e89de035fdaea69b6b72b15efa65d324848cd123eb56c8cc41167672c423f4eeea7d0e78a208e0d1d452a5a4d00ef677c26b594b81709f03dabed04900e7cb6a75a7f152f4b8f8cad51646bbd8ef14244704d58db2d187d0ff56f96c54c61154ec8266573716fa50110fdb74a3d12689c55c4e57d5fb3fbeb8f7a8cffb84acf0cc9366accbf1242745bb2683cd7845c2242ece7a210a472c45dcbc706f21aea75ec0d4e9c320dfd4078aa1d7c4a8f0c4f4fe292e86621853a7d46aede8ceb4c45be7b1d1d36a99fb2dd8c6c5f63fedddf341b5e10d516a0b7feac7cde8c815d3173f82634afe515b61ec1922e71aad1165f5bf8498eb09725a6486fc9c202448cb44f0ca51a383c1130bd26e2f9a94c544b7d29e6f45d72da373767dfe8cab817f345c314b4c849b022bd276e5d23125bd6312cc492ff11fb525f51b2c755da2c609fa109bfc2068b5e54ea0d0dd35ccf9fff5d0829880ceaf233b7349cd918c6688e4bfd434a3a0bb53d2bab5dbb9780c26d8a52d928b26b850cee844ec92c7ca5136d634c37e0566a40ccf62c7b725e00cace0cada64ef7d881b12c798e804954a3b135c6f9790c289852f23041d6bdb00a695e019a43891ddd9d7fc5b4a3c611ee1a7b0e3eb9a7370c00fb69e4ed7d910960221ab0e9f98a51ef9121a1588e0a2f31df5552a3c3d092ca504b4c2698a3a2ccad89b9ad762cbd1e578c1a6ad9d8e419f4bb6fb04053a295d2e5869174bb26e265b3934ff3da7c77b5facfc6df843bb19cbd258ba3fa485fc3d21c57c621ca166590333fceb26ff6c16c07521174e1ad744eefd11bee29280c273ac38a72f95e7ef0d9d34d8e8fb5ca3c94353d30067bdc568d94ebf51a788decbd528c9b42c3e7194f99e1f58baa0e6d6ea3fb2af9a16886a239d982375bb9527c68f55153300e1e28d8a0176c006acd68bf451aae0c614e2059230ee6a100572ae24481082fc1c8235f60310849533d04b8d472d7b0a4757a7cf7a5f2d3713db348d03e6e536533bd27c51e2b4ca4e3eda666932d4456f29109abfbf8c9f624c513e24c05f5824302d1f2c02f67c68940516f92ad60b400e177bab01a018aa8eef309070ca69c634eb4142fbed5375a4010435e240520ad652f51e1c11ec4a541b5dcf8d724889156259da3c45deb9cdfd0f0e8f5a6fc26140c48fbe12f6ef00530fd093fcb44f39c4dbc1d82933dcac810e8ddce93edd4709ad552d599eda6858fb4b59c40e42cf474d6d83226f539a8e9410fdcca9b5ecefc41ed32b74341623d15e1a9bf702b154663c2eec0e1e6e7457eea408ed959c13d61d85fc423a69f4fbcc6fe2023d41ace61511314bbf78f39998841eab4b4fc97fa2cc7a693818097e51d4f3a43c990a2277134b1116bff011a564f295afb2528d8e522868449b232662c639139c540bda032f77bac3ee46b1cc60ef2153c38fe2be423a81ee061e9f470611c1671be0d839983952d213e1e14e78dd19dd0392a18845750ff36d1330f9802f0cf23620760a60ae3804070ab9137eb747f44ade44a2a4918f996e7c989898426f1c6be2cd39455ed8b9f1f00cddfd3f70440f06e2fe5f8830eba1e13e12d1df6906780f183b67a6da4448fcdc08afbbdae845ce82e2f78d85d36b7434e0f83c52c14a8abd70d8c3908af8d07dc20b0f09d7778b1ab1e62111ab404cc4931be177cf881e2d30960fed8bc30dc55eb82ee851c589fb4c75d952892e9eeb3d3465ca789cf60c13306951eb27b8910f0bb1835b1121ed8d49e8d390ed1bbf058fd2478fb6745fa2596d311d5ce7a842910cb6d5f2e24f59c523e7d64fd1bea0d311db36e455e48af1e8f893e9e7e949101be5fb37fe9b801e9a47b83ceda46e573cd46b7a265130c26f49e5ced426b69d1bb7f228b112d35a7401620bdde85e04ada131f0a7421e93a35b8d95277c5ff0694249011d21ff09e6f0a682527b65b340168bc01dd30d9edf41e210834bae6f32208fcde8796cce3082ee55c5cc028dc66a9d3b128c0d5a571ffa4bd0d8f6ba1836a11af6c099d26b90f0d3cf260b9076bd027ae59fc645b0479423d06911db66b2c359c653abec60f8194f55b1fd8be42b776f02ef083e7528299823d4bf07ffa0d40a98f7f1e1c2993037135dc95c8b6b090e1950b681b346da051280cf439e400d99eee0ab0163e63ca2726e8612d7ca13906e43a0f541ab2dc090c4832ee94041638273a03f6d308c068fdd8ee38e5b5efef636138f0c658233f521d0bd9edea902c1f4ca98715b6e9c6c55cc5b2a65754be18c7a28d26b9720ef301395ce68c376bb0fa023eec8831c2391a45d7c0c02daa7ee7bea88d9ac744627ca1b9016f759b1107c000571a9470916251296ce945d36faa6f95fc07446d1167fedfa234c677a5b72143f027f6a24e1f8720d4dd64eaf3d01a7115c5e95edd1a7ea5de73c91d81398d3a6e5d00b38ecc3c54d93c46fa81dd16fbdb0a03bed8cbeccb5a0b6e09040cc9cc16c3babe156f2d9ed92f0ca0cd21313dbe9997d24ee0db7b9816c0d88d2a52ed2c08906fbf1f51196f7510baa538b483260fab4c454f45ed2d9cd466f8429aa8fb39d967b7aa7ba83a4ad879b4e34afa0942c46f555ca8f8be966a62be33f2bd7a34a59cee4b37d93bd6885509756186ab423a3023e2d51e820a5aea733b77fa6bf064e6bea27f572e1d60cd8573ff5eadc9ac37ce013d72b5b847fb93ed9d9ea95970b56b619bf32c75dceb28c7866221b1e6658cead69786340887a72072fbeacbd2b189832ccca85034a1599092b8067f92510b4740310ea3559280daf9834e01794fb22dd5224d49570db28d81ef5488caf75db8183939844646fc4cab394c48f1ae76b9b7743b47ef6490665eb9806ab9b67f85d2c6fb48b87177e1499250969cf55ad3e1df7ab6dd6183c7e227e0d491c28b68d6b9aed77548017480f2dec1c1d26acf2c423525b3fb232ad5e7fbc153d2e5f51d01804db709e9f4296c3b014e0f787d6b9cf28d2ae8693214ed6b95f5286001bc3063412abd49eb06b92e5302ea9d6ffd848e06890183e0319c7d004835a3412473ef129c926f6c94ca750a7b88c49a3593ee65de535977a87f00e55592245722831c59b707818ce704eaf4ce0e9899ab0ef929e8004fcabd464408c81720b50307360547f53467ef7fe8c91a08d5c7d21390c3f7829498ad181dbbe9145e159fc72deb03b15f6b15b669d407a785b75c6e34b88996abb87f0eed73c791c7be187efd2429440c656fdf7ef191300b42e0289925ee01d00c9f72a501d49a1038a4a89947ec32b153995e920abf2e837f12515f90ae5bd5ff9454b94909090d789e4a520562031b5fbd875d39e266a1ed5e6a854201692c310823924b8da41ad2f827516825afe0cd681026a6be6189b71cd6d3cd98d8b9baf7aac6d66f2158f2346170a8a48d21ca606ec98412dacb15637855ccb2643607aa3609cf00f5b45aafe3ff681bb0a009f27a8bcc64b79dad4f31d531a6092ddaa81749b961ce26fe143c386bc811bb06b7859600cc80a56a5d9bf3396897b6545c0041291e8991b881f134b5a74f86b805ab3953984869b77b585a114f494511adc2b09d3b622bc4cb3153becdea2ce66d93031218020618419e6c4ed87310536993119e5eeccd7c062b6ac0610d789eb44f74919fa2b7ae0f9ede1483dbfc6b5012722a5fda2f04f1432d188d5578a0d44331e0a7a3552c6f4d00b47ea293061a9161518f0634b4970f63306ec40485bcf86fba2452678afbefbcce399c9ac62994bee8fc5ea9869925dd2c561451486c249b67fb3cba01eedc69bdd4c9191266dc90b5a929f24a9ae59c868998fc4ede95e30c29a0d51c1f63548a496629a50c3a4690ad72b665f8c34da99422aadb2238bbd9b3b40371f02b026458ee5f598ce6d0963f129dfaf8fc091cc5a99131641b723cf111ca5a38c7f04255cdba924cc021521e5f5ec1a6c250f8da1fcedbeecea82279244e547044ed05807a1a4903395023dcd3dcf7d3184e83d89b7fef509d479290d2a5e7c2880ddcaa97cf93e3938f8512586b061acbdb07e586b8432f5bc7c9287600af72c0bea0c12515f9d3255ae399b251bbc7a34cd06c6b61c4d9f7739ad331800f61244a90dc27e04dd4aa618827350ca3141b5239381aebb2604fb48758c1cc7e22d9dbd0f04e0043ab6ff71a686cced3968e14262b064080bd11080ffbdb2092c9906309147105303fa821bb7a5e60a400926a095c48c8ced56d8f9870adf2b00199b4e4a2ca46fbc060801238262400ad0cf40c089a2c9ba6305e75d209e628fe5e545b69862f491198760957522fb3860ea07c7cce95827495c2211bce842ce170add4f1796c105ffd81d88ba5197c4bc88a17168eb7a628867e7dccb9c543d6712fa4fa0c80405952001606b23302cff7b0f11582de20385ad4ce92267d84efc17bd524b72247e28ecb692ffb0614c0f98c97fb3b503b77ea9af2905b32d41566b8801fdaa2a039b1c2821a6f5117a8813121bb8bc8611f57365e2e05a620f3e6eaa6b17daddd4d7fb1d1f903ea51409685b02b3fead865ea1db371889a1681f9c267b8d4d264c1f31cd17efff1f2c6e61866114fdcaa9024e24f1b52ee54aba1529460706e58255c53174e47c5565f48bd028fb6dcba73fdbfe179830ebc817ff10ad66d0dd470018e47bff90955a47c923381e4343ca010c4ff521cbc846c09aa5a1338b392f2ab6438388bc2572e62b513c8607620d4cf35df590f4936edbe432d6cf2852552dad0d69cfe94b2c5d8adbce80242157da952d715f4c9ab6f88e948ca22b662ecc04d80d36cdde2777a10422639476ce0ece564d7e1d5a3f9d3f2f108d2a7e0bf4077190a25aab42181bc5de4b5114ce1470c81bd4e73088be0195615cbfb23c950ef164d85e9b21e875e7f092ac8112db238775365e91af1b30d78e6fb091881a42a8e3a02907e1077acbf6d8d4af5ace8a60bb66135dd8d15857a81718f0633b9d61b74e96a57da7219e995445f37ce7ba724ea212f27157573e201ddeb83b5ed2802bd9a715cfd661667994d0a1dcad01c735a220b26ca9e9b143dc507c641a10ad5179d56fe35a41caa93c68d1813c1b14bb4e0feaab75baeb28fb13a9850ab061839c0800a3d8a8967f0215734f19475e609223c3a1a44474155251ea6e47790887f8beccfb8379652a0b794cbaaa8208ea428029f1473e05abe35e237e17def9fe8923310a2a575d71b481f16a04e79b17f223b724a2aa837b285e87c6b78454f210ed1c6f83abf4616b27a803de34a2389c2c14122a87cfc7756368728a78260d1639afacbf52cd35224f8adacac730ed294601945f51e2262530239184be9a7d2fc29807e60ab878847642363cd203831ebe3888e48a0fe01063e582e519f8fd2b324a5705330ec42a091c1e3ff85e65b5dbe2b55ddfe09133d1212da3aac6a6a2b0987f44db813e80bc4e60db56f52d64c9d3cc5e003b4478082692b20f4ce29100a478270db4fa0b5a7554a87d80b317f71fe825296cfdac49b1bd7c187d62407824715c7de8799f11669183fd0e0fa3faa5ae0b2207f92adaf6637207cb60fef23f7b0d43102d68f2bb7212a2c5b20ca57d29b901f1bd58ff69701fc47e0391e58867a442466db14ea746672857646fc90227c7e5adc9439388eba953449a9132a5dd300c776bdafd8b749046e2182e9d1799d80320100ee42d96021a17e758bc0e86a9559009d20d40865c222e1d752b6c486b9899d34d6f4f210531928269b57cec6a2c0c02e585c079db90fc1bcf66a557457b38e4232d9cad40ff5c91ec26b7a90fb0c891cc166d82bc2e562ce1dc001b24fdcfd156078002bd82b7a9aebd82c262522ae55f8895e6b9a6a825b2d1274d41bd952785e98a2e7d3d12bf62ae99b9a9f86139842909e6345353276592cc79ab80691a4c4f28e2273f2c62d5fce1c6103715679e425e0ec4437203782391ec5a00d862c8b67637228d1c84f7e43188b055c1284fa6c90c392adb4f02ad52b6b449f7de21469aafc0ff9cc18706ad55702c5291fb56ed5a74cd14a4d2f4ef68c1aeec0fc90a55c5789732dcac29cef8850ba22ab15259ee31124f0251544d8cecf21063459891ca8f254b9670d133d8364b4c0948509b3ff676c27e616865360a9b6415641238b9b04e5d5470040ffcb92bec4440cf4422c3dcd0f9c3c4b52461a2a638335b1638e5c7605bf4cbfeef565db3f685cdb6df280ff162f147e59e62c175a720860798fa72c7da306bd8a2f539d4c89dae99d7b6db12d1cd681d83018a96bba4555859229f92766bf3a201f89bc537c441b2c648e3247394045d55a800aad3e0d3722948255513baec15733069f59eb4e8da94c829c9209c8a5de7fb15df462c8685143d016b0ed34aadeff976c8a036f4d62684ce4578e6dc59de08fe6772d4949f1099a8456d0cb8a853475dfc3b8664515287412a18020296124667a3d0a929f2b403772347fdbd5028e12b815a8d306ecf2d3de2d18c6a54c53a738b0ede9aff18b3af41f228e8105b1721f46a203f1dd3954b30f00dc410ba82c532aef1c8c1f5362b35fb137519aaaa0727c1a14df14dc42a073cf3262c58161b60f7fde56288156281b1369274df6570292e21550baa2bf4c6210277b00d26e986e017021899f9a66afea54c8d278d24af9b4621e01bc52b4212f276bb41ea5e053a1059d75284ed3e2079185cd82076f7b75a8578ec5a02eb4245b9bd8301e78dda880ed5806096342f28bae5f70366537c124a134ddcf7ccc950c2089a20923a18a56eb41200f81b19b078b01e99dcd2c2d3b8943dfe55f613edb4a017c79d1746cbd522fd08d5190488204a5e4255708b39df9da5006b839fd641a39f3533a57673ce9c48256e6bf365b6e7b106bef490181479391d643a04ff2363ee21e69821d6485a56899605b636978828a5f23e7525bd0a0ee78ca109d3809471a865543058692618ab9c7dbfe2eddef55a8b9db58b93d9a564f0b583d386fbb95faccf8d21121ca176edd4f33e6af053395f094dbc0d61b524ed13594f421beb6237116b7be422115f7757adf6b75c9e6a7ef5005ae604d28471a7a61da1f9ea4f9a57f4dfcbf3fc75799733664d7e898e9fc9b0421057ba97f9512b7751e4757d9120b730ba4b9f38914767bef421eef6e7466e3b2f926c6b3bdc80debd9c95947f8c75c51676bbe2644f0627f689996dac3fd8dcd4a42f77f1662a80260e09323fa412074d064977ba4242879bde219c3388c74836c3780c41d89123349a27f38b70de43cc3a3e62a33044f7e91d0da2bf19f7e04d044155ac4fa087212bc129b5e69e3a4c34cc77055a39316001c87fd32fc187277c0b7db7c86bd3648ca218d2263771ad3ddc6a0676bbf6f11135607436962b22debdd1c39c06b5e6e1ae0801e9df3d783c245aee76052f470f8a33fd62c3b3cc8f37cab02238a73d0f41d649790188b9474695b9d1d5e5ab85efe065fa0a4b69a4311ab23f49110ecb1707a5ac7da0b547889c3d69111ad976eed5a104f689b98f83e0d9f310a51dfa02043306f49f003a76d4da647339792d2ecf161f1331af9152db3f08bcdde602a0717610d2554c6d8afc09ed081396832b16223932b183393121f9bfa35897a91b664f1702ed0bd7e698e4075fb1fe53677cd024fa135f5809a361ae89db64e491b74a4e5f19e8f3c22cc465794317eb6e39cbe3511ecbffeb7f4e06121a05b959379bd211d47e532c8919e6c8afee48776b40fd5b711a9b22cd476e88d933b8215e0b45179a8dfb1cf30e6e770afc352d7c4abc20f0c3d5879f63e15574e9da446a666612840d993ac3b07a5894fe43a7a3c196b66e1e686e5928c22a4bd0ced21d917a77a1ff688fad29888e9fcc28cd21180080dcacd13d0bd93b50db661108302837e8a7a186595d81f72895d59564e68692059d62eacbfab3ff40f839798cc0d6f3302197c061c6f110b058928da94b5c007e5ba094115b4606aeb057551394d1e1c21606ab54558c6ace50648fef0dcb7aefa81471cf753198d25c2c2ed3378a280c1f0fd14fa3191524e55fd434612a3eb29f13a362f12071471acd15b865554949840aa52eeaec6b4a3a7906c71e5538101a8b7bd88c34a52f876b2f387b850650d3bfb36c748d28a2e422f4e021ffcfb2b3a00f3d448cb2c591b3afd73cc29158ad29123737c61033df827f0860ae42b66b0e433b36646b8ee1b9c913b89d3198ad40437752413a74ec79c853440025c0c0602475360770c4ee418b14a79be4a24b4c130b50b050b959952e87f4ac25681b98b9d5c0210a02108b877b3450801384c07f3472f0a09f582003c4ec69e7423576cbc46269b2e5b391a8359977261bd5060109b8824ec96c7ec4df195dd15d581afd067dfcbf7b658ead7813062e34940b852dfb0769550d8ad32415db2caa0bb30f0d413288317ae6d8c4d9b18f08cb0c9840486e1a879046f81160de082be7f327e70be2b0cb85cba2fb100dc6f185dc79369d61e9c8e7212f1d5ec59f3a1cdb8ad486e23e8ac74cc146efcd210f7345a6dfc0666fa986f5cf354b2e7aca1363a2122506f73cf16bb7661089dfacab693e0001b895a00260e9c0fcdb0b5af6d8dbe80744570ac8b238a428f8709be6aac23bd195ddc4b73d5265749ab98f205a09fc3774db5d9b9b47bb6304c7533c17c0ef3bf4a713dc1331e30aa4a0e3bfa9d2aa6481e447a45d161de1b3026f3c0b6b7a8ccc1b6b6e5d9b08d761982e3ea0bd8a8e5a268c369074f4f808d8867e0099c66322973ff04b68688d453500c018c4eba4532bf79ba8ed4b81c5d9f4e5694e1147784a72b95e76cb942a7feb4fe32cacb729a32ba2745e20c35a21f3c7e9a84bde5e2f1bcd905f011bd5532199e82fe9bb1592a49a0a58103f56030a41cc43423b1fd1d73e2249ea6ad042a165a82e4925f48f1c71ba041681069a6d9777ba0c6d2012dcd841a9e4b47361d717a1ead06cc471dc5399eddfc681d567793d5eeb7332bb5453b69f98a341bcd49984d2bf808e7d1b539ebc55104ef30465053fc89f7a134aba2ee0077a28f9d08ca69296dbfba3694c21af5e97ef494d06fb18d1327a6a56d014c601ad324a1261a99bd0ec4501339cf74ac2490e6efa40d355e4a796245619fcda571644e1acc9fa5efb23ce02c20a6cb1aff6c8fe001037ebd9e3f9957e793d5257b3c891f4f79208003b13e23833b1882a0b8b50a221bb90964e41b0de346eaf8dfcfc01173d6d60906510ae439cab6fcfb6f2d0427dfdb4ac6ffada674427a1dc4e9a5339edca01640d963baa742e584e8bc824973e779141869afaca51853e60b85c803dbcfa5bfb7609adff8f1b55b0c5aa7ae2274eb660482b094a49eb5719ab4000940a30623770c4a365ca581488c30557b8e10c67dce01cc7b8e1008738e116c738c11907b8c00d175ce11e075ce31e275ce0d4b1d3a577b1145e02a308c2c22e0103d86109d0183b00ffa6a475bdfe4a2c8987eb988f75338786594b48829e12523289eb0bd72a6d3819d7060a5a184c18243150047eb1c4fa299a6bc435c9b3b93d58c61c323dd7244476b4859c4e81e68afb8cf2b39c16b9932b7a3a1c91840a2fba7914ec70e7f3118fb69b949971c4785396d6b54c4e99b45fdf4494a4f15a26a5382dd737090569bb9ec996f29de6eb98d81d1ce32587f6b87ee9e25745d177111d51591464c55dcc86b9a49f833f2516e84380944455c112582d2de3ca52837501f229087ab18ec473fce8e69d32797aa5dc0c2351008031dcb506a2fb7d17eed850157efb2f16c62c273fe6f79a4944358922e6e3872abfeb9ab57a3589b0778dec85bfa4c0a06f1c9b3cdc2d33c42593cb165929860b753f32f7f78496cbf4bb8bd0a526761d8d6caaced04283c4fdbbd49b34af8750f9b8e604237422a94e9a9193141424aad33c43b713493ac608e46459fbe031f264727a180c1a3192b898e859f9c99337a1ac04751757c682d9f744b3c9e923bb8941833bff002488f16831adcd2a02010bdcdbc783f976b05dcc974f0429e3956e60f7b8ae1f5437b63882fa45ad7fd8025d3756b25ca9ea5a45318493f194a2609197fea49a8c6bb5bc8099e07326253209cd38d82b9a5d58fda85d9e9cfc1fdb196409e7abc3213bf2698682dccc6a7ff39e9d8772edee1ab4f6fe534fd890f611aace802be785acbdced6bb79c5f9ca23e9a0237d5d16d476c551739212252173321ef565f29222231f669fc3fdf69210b0c377eb4b5540e1accc7b2c7f116269cf18a4a8044657c7cb8f0c1d08fd6c090661a53f02be8a79bea84caa0d0fc94361f30d16f0b899b0f7a5dbbf0a744d1ae8aeb07a02034d428c03f59039a7c0d0ae8e82d3cc0ee123a1aad13391696f31c3a5486354e847a1842a05020a064de6f5e854c6f7505df2193001b67871aa6ab2d8762dc692ca071450bf1384031e8d9a81a06d8780b94871a80a6fbb9822a15ec4e6960f09add2d593f9111e9fe8d417cd65509aac909c7497484f5aac765ad1f274b32941323c209f04f395a9d0f9e02edb1e373eaeabf97495ad28dfb6e75a8b13334f783be9fa7b0a7f7f0adc31b2baa1409ca81661ad8bf4d81c68df97276c0459aa65257315f33a862301149c679d12807f821494dfa26479a71566badb18160df9088002118408b34af3080a724106af521936683dc39369f6de0bff3ebefcb0c60b080f2c18807aae65d0fd9f176c40089892fae81d1e924b5a90097d6aabe96fb258f9f6905a6dea25a5a4be45ba4bd4a38f707431435e5f94a34f6d2d2ae12428c42c6387c24cd8eda2d495632e44900073cfade3ca5a2cf6c492cc76923ab4005379924d99bee5e950d60ef1c5e11aad5200b11cbe6a4ce8e3af94cac3de7ae63be3e58251b08db2195ac0018082d36dfef636f8fce14f2012dd0ee94f609cd70a3c33cd1751d6dfa49327f9bf4fb50c27b83911ad4ae1dc5982ac81c6ea2673e66d180960324204b7bde53aef01c662def9714493a4286173a43ee1fbfaa8afbd509b89baed8a8723cd329768c3bf48689a957158a6f204a191c711552b3fe9fc9fd067d552bcafa0003052fdfbd1638f2a62f15550e64590b74818fb445005a511afab71b41a7b2536b6820246e62c5e0f5812cbcf32262360a21efff8a946599bf3c5c5bd6e2e8518c484bec41691554ecab448d3c1c264cc5221a95ba55e9ccf8576f490d8b5910f90deba44f02b28200d6f409384a371fda872466146e7e713c7a5cae465c8ee1594b5aead3e04237f182e827bcae9434a8ec7e9a0a51522b499e94f19ddcd05da0a0a45386db8935650c0c96a2d3104d0575af5c223050d5051564843dc03e312b9215d41a92f23d11802b35e87969eb8b8728f220a520fe07f8fad2b51385f28965f48a29c2f710b5f962117717d1d51aedf0268002c4a5d44b919cec9208337f4768f17178942adb42e4b9868452901435e1488b2cfa539d3ec4d941f1205a0d2d8d43bc4334c4aac8b7481405797c5afba26fcb836a24bf8d83e8906460b7777923a4cffd4d70e4b9785ddddfa10e04084f4bad1777a1828dfea45b0f21296a53ab46430a3677cf5a0db0495d0da294a37ee0a14085921bc33496b95952920a81dcce9b890772b4f51ea3298cc8a4136c19d0ebd2f5d201186a72321c5d6f0b879a5f08fcb9758ea1dce3c4f5474a4ffc28209e341e9b5d9b2a0c00fbfe57a9893bd9977d9f4026cea8ad56c03b05e4166bd23259394ded07ede8491011fe1382a4e48efc5eede8c59a7d85adf370b12911b1dac34af9b88b3e2b12d7238a9fb32f388e9e78d4bd1d3197b5e68cac730acc1583980ae7ae40c036caf45a6ae3f0367a762e2b10763e518bae291d41db555c9ef4516b22aca92e23cb893588a056ba1862eb0a6389667e5694caffe0585e66e4860e5b2a401f4edca6eb10414bfb2ce01a209ee32f5ab26ac9103ba19d0437af0eae964abe0addcefb5f887dc50c0b7be40575f55df87658cd64c4e2d35655adc3eeb177f75d4d182be37de37ce122efabf00851f54210af0af3d9c4bd9419f948816ae904cdf1a4d4bc7cd490bc0db8d9f3b305bd9b85e72b549c0ba874586b800a3031eb9a31e3596df8cfd62b8196ba13d3b8fdfb763f986ad2e0090434e364cd8f9d80cf80f9dac156828a5e39ea4b8b0f9846c6793a27d1cc92f805cb1c784e9dbf97de42cd065ad21698341eec4cd8401694c89a1c1fbc34563c89bfb52a8caa433161a10cd7907930ba193c5a57bab762ff92575bb4a9164855ae23b06be5c629e4b639e4be41b5bdb320cc30e37d82de00b5bc0fccb7accaf44e6bffffd30e6cb053697ef5b86bdbf34060b34ac78c243c6719d5be4a55007f662af24e2aaf4581bc933ee40e12fec4a2946be2c343b057d959120ea07f476dcd2d0a4f23e192e23426ee83656997a16e151b503f51538089f2208a3fc0731124c3d8ed5e97de742e426c2c057d4593c469e7df59c8ea2ffcd014ac640af8463b69dcff0607ca200dca7c183f67e139ca965e8a4e2c24c286022bb8406e1d5fc895ce5f3273e742476553b402abd96b314b53be7f6fac7cd28be42fe03e119d6737d1fca763cab183d8f691775f41e6fe055a9ab2411a6591696321a9a0c0979a10dbdc20e3fce4ba95440a26c1420bb47206ff19dd0b6207191e7b7084c607ebf2d06983af8d8fa030a7fc583a2a5a3a987f77fa356476909170bc1440b1fb0d768c7d3e3ff75d6a6374138563b146f7625f8916e19c346214b0d84f7ddfbf648c2bb394540fc3efb478e68c4f4eabd6e1b24a5f9c13a92539d81b3e52d3c69cc5f766ff8f4ebb48a578dd056c4d668d1920f559c3e15c4cb45a0718d0558d3f48f9791f3bf588967ad03a2c17516459c6c21d006ec3565618e8b533672f2f54a90139faf63fa762f10fd5aebe8ea28d65f60270c355e3887991e11b411b224e9226ea92751c3fddeef8f8f2e06e928a219e035d750fcc4bcf87c3df4cb0c86c46aa63d604b15a32bdad4f1a61c6f51474756729cdca6450cef7ec34ed450fbf20dd5d61bbe147538e286cd82a4073aa7491720e6187834ccf1f3bd2083dcbb46d7aad43026996f3c42f85bd27c4b508013cc70a382c805a7721007a844ba169a5baa6c381e5a5bd916201aaf7166ce942065bfbea99e5cd40c6cb07b98a9df1f943888ba85e7b375090b0c0a7539004bf0875591d203c4c1eb114c112a0a2648f6458cf431c4c61cd8613569515c90bcce21c3c894c2e99a1218e3c0a359fd482af95b77ee66a1366c946e89f6c509d15f0ef1234f67bf22801242125ff883febed457ec199a1905e5152cf92e63370b8f88486f308cd7d567941fcf6526b488ec8d8974963a0e2525b32ff1ef0e6171b04e50d4cf4ecb80e55f79671dbd769a6cd60841a9baa41cd3a9e80593b6e6917e25dc0831907efd286ce4161882721cb31eb7b2b8d39c4de5f00f446b6ae41f35c4179ed66219d9a872144e2217a5eb200a522ec2c4b0efafe890a6716e412c675065cec789ecb584fe736c8a491bc827fee51e3e432249e8949cbb79553ba78ca3d0bc7ecc64380c7c6c7003d4d81de5c670a974a17f1edaa7938cbd7de998d77c11d5e65477cdbf990fd4816b3e03a6870ae44b44e077f956c6957b64e6240efe44653aaf8bd7624099e64acec7c607caca0e926f765c9111cdd031831f10accb9721246b0ba48750b25de56c61ffffef6b8d29954abd5cb4eccfd4dd47ba124323c61b06192fd8f81aa4dc2145900824b4df4764130518d985bfecccd22a8f9e168e3cd2fcba356351f1ba558bdf658c6acd85d96ded29b062eecd7c37d4c5921cbb45692537f9d8c793696669138a9914c7c8af8bf6ba03abf607a6347dc4380e3c76ddabdadf9014ed13427f473641688a52cc7ff75510f1a1724435c0773f33c216bdbb71efa3fddd4481c8dd0d57a4094b40b16b60c404be9b69f9425e98d4218ee4644dd5ac41757e79adf21324cda8b1afd07052d0cadff0a343416c2af026c1a876394c4667836d0ac079fe04684adc65a471e2862868f225df0580a3a60ab4f3696faf6df5b4730aee07339db3390327c112cf8a0cbe0e2b7a2a20596e998248d42ce226390b9e3fc9aefb52f64c9dc94dffc682c1feee047b62e2404e8da5c476605b5a53b8f9e398d53d3c9137051acc1329df85a055b2bac07cf07635b9cc4fd6f5c8bdafb383ba7d83ad5064608ef01d3e214bd7c2c8aa4c5aac82afffd57285fa1ab7e72b969781d69d4480520dc93037ea87efbc27804e59cdce9bad26a2571c87ab2b59ea76eaeb05a4805cbb0407df88d8148f9d12c6167ca0612702e1ca2bce22734b97f8aef7dff7da289a6c0cd28536e3a13804ddc199ee1ac6027693a420f543c238718e353c847f9840b5686ddc4c8ff8675ddd5deb183b822e56f4fd576a7d872f66a3cb8ed5a7fd7937ce6bb67d2a7cc091bceabed1fd1d8b84f803bee409286f2debb2fa4ed71d2db21d2989d9f1fc2629a2c4d4a2d97827d5ccbaa5cab2f3a07387b1896e01f3b1d4ad5d91a0a880efd947b98943d16624cf3996de7254f6d8a53d84c20a3b8badf707238f5ec3996cf745ad2d866bc53851715bec7930947b6dedd1e6d9355df782b1b41a4cd020d8407356a406e5152e363e61527b903d76b29fb5e58bc9640f1b33573f8a7775bad75131b6a6de98bc5371902bd2847d84eae4bdd71703ecd9f15d42657016eb251c907b57ff043e3588f3917c183edface0220d487ace0878936b733006aebd33f27b97ecbf53ebce248e6df108c63d40c21359b60137c9e6b95d3eb3fe1a33a996a4a547798e1f6908de2a1268440a045ef5f4a6e42cf435a6453c63ffd971a2884498c8fbb4ed925cca4e4a732c09708b2a3e1701a48f7d5dd424f32d3aa07c1ee93a4619ea92d9dc2d0b7b44b7b52c9ad9ef1c4871bc11ae9e66389cf2e2ff0e31c456fabbc93c512cc32f1c80097c4f962326c10c55aa64912aebcb5f93270716892c75bf29c6908f54517e82b0e524d1d5dfde979e231d2099bd2c3d4940111f8992c620cbf38224741f6e844bd2df6a640d1457f6f4c570143ac60d7b9221a9a1e9ced2f0ed3e7c682c65485da8db6bcef8fd13842446b9478e1ff8d0933ab98f1faba0bb9c0cdc096a4f2a24a5213d7eaa132c4249d58be054b473ac26e3e5ca1ab2653793d1a0f6861de89fcaa37606a33d9c09ea5c073eb5546afcc56ab2f54fdbd813e356ae7d13f369ee2b7ac45506ea1e9fda4e768b4a30c81ee79a864a2d2ae2e45217695c76c178a14ebec1d4b767b1adb1511dd6e29dc1a00accd2453b7ff7d89161919e0309ebc8f394e4a98e48870b137747f65c8a383da4446a2d0b0fbef4054dbd55e49640834af6d49b19c89a27bb2e591243e2fe63a20afe62ed473c4e9f81c2e458e4993d59f1b32f36712e3d3e61d2371ecc453207ae58b60a9189f6cd26d7b2cbf150c155cf2c09ae347f4151e80d93f7c2d3f43f17b18fc3050e525b6652e64212fbda06baa6ccb970629c383585dc6675cb9f657826e1df21095b2a5df3a16321cad82031dfda07707638a0d3a4980edc494bfad54d2311788ad4ebb665f256e5665ca28da69442e68f1b752218dc7fcdb0fcb9eb60af808a06c1fd6991d11b039285e2748bea75456f7764b84850597588c626d1b93599d3705cd80fb7b61c1652ddda4ec8750f7a0d3ff23f46171ea6e402ec2d80282f43261414b8866afe8f0bf63621f0e7225ca453e8ac4f623d63344687cf9822650741cd3bc3bbc7f8523aa18b4ab3ea29d7f2efb9cfdda5b18672ac40ae60c61b5818674bd5517456f9429396ecafcd0e77ae3c7ffc799b85ae9a5b82087983389908418ba3330f90f7fedbb9384ae9354f56ca64536bda19c9fdbf82006ffc405a81b0e7e3017e986688eb7a86e7485be020945e83f4cda2e8e8d0f4d3a2ff659fb7a3162e7cda1e59af9dd1e535dfc2d43dac90560b76acdf11cb6042cd6694bf83c9ac5e6715c4bed23865dfc307a9396a7149fb3188bbc07ac353d1282392bfeeb4357ed05c7c0d6e6f840820683b6e49bb50a3725810673e2edec7e93d7159614497a34a036262276928be63993c350ded7624b3546fe2fd4110256581a86ce9330301fef727e2a519474c1543627a90f6e091baf1ec72a1f1526ffe70c9913719e87f7a687f3338c801dcefa1009376ca2d9216a08361fd220a53959e9c95a471e3d182777088073d764819afc8de4be54bf62104029d7b99170a644e9a772db4cac16e016239674868e12059aba542288b0e304b18b0e4eb0fe9f8d1f81129a234b54b216d4d0ab1351e64ce403e9606253beb32fcbcfe445f5c35f85fc2ee6c8a2912a89028a85d6d76ba44e9624d2d796456d3618ac28b468cbb68279b98707b19fff79231e14e73dc0fb337c51079133b118ac118a220f5f0202ab9aab6310f100dd3f7ce5dccd13bb1ca02c3efa033a21814829e27abc5bce782fe3a77a842ec78a6c72cad583a984b7e4d8f53938e2c7cb61f616b1b743369f9458b94841d86969826060afb84f817d511a942862ee4d061563460ca57a5ac2527a44002578003072d3df1bd87348f65a3f6c0d1348565fb63fa9bf1344a85fd447c2712a147976e92c16c7bcb96391408c28f4a306636d55a1940014ff18459cb03078946b50daf71f8c6eaf5e2d0903abad836e70ae750050d39126642f5015860287370a01c3b41cb3bbb2ca328cec9fb98b46c2959f18da3e84ffeed188eacf027b2ea736a09cc85ecb15b6e61df5294f40367e7a3db522bcbfa60aa3607749bc535cd8dcda9830068cd83b3bb4e05022056669835c3008b8d06eb2907fed57e98ba6a697d6cfaa1e60497b854b60fe1eee3eaad9fcaf97c040b9aa052204a4730541fe5fc8448c03977abf071eb3b1b8b9eac8664bc339b2a005053b10d3204ba226f9ab25489229adb1156e6627017fc5944e4e5fb70c0babef84c5e0c2738655975c5b1319925d736e90e5bfa29c38c1d0b5a7b9b4cdd45d5892d7abfd6773787e8c87c62ca3f643ee5dd12e0f9e30dd934c5ac2004801fdd2ee845a888412a5eaabc404aa48a39f219078263544dee93ff52bc5c3f66aa58e9a8950377e5fdb09ccafaa0b564e5713f6d91a1725374037aa7faa94d92d55041311f07e9accd3a02ee2e0feb258dc9c5e4948c7cc09f4ca21295594001af4d01b4b19ec508acd43cd3fd4957c620e97272b820d46068cac26513a8b452e4572b5c69f3e8d6b0bc365c6793c2c9e904749afd394e4cc72459590ea91720618a57d8025823c074d0e30436e41e8f2c0b3b6a687a9aeff9d94af570244c324eb5a4cbf71f20e48e5146f65a986694b08412de1474673051457a90fcf7bbdc26875662b372619a9fc23361dc46d6e386c97b423b6bbb4da753bf649338ea6bead2a322224bb758a27d3ce5f224512faf6ca895dbffed6270eba980fc186bdb5d1550099fad1523367ffbdb2bf1424a903fe85135f32e06a064f31b6a87d612530c61d6cfebeac20db7edb977f1052d94cf261c103cf67cde0f8b80ac7f17747afa702740374aecc157bed312b15f23a174062c7520580986260527aebab49743d9bd1320089643d7e64400886a7bcc95246d5b44323fde311685128db52cd975d856dbf795a4d08412a40f892143a121de612858c9e72256846a4c1a687245f02097a8a27f892062a90e762ab17b0a1d9313bd21b509a1715216e3461176b3e3409fc83aa1de0d105b4d82b07759950380f834093ceb836e671294eddb379324dc4839c0dc5d37d10601b777412c5edcfc584e537da260e0e70dffd7f28d24f3f9ef06944ba23eaf09650e3ac4344c30e78f0db4376ec89782f8cb3fe1c837daa21925271f48568badbba6dd726c8d221c9a25dc4fdf02a49993d81e34d04f84bee380192a2d7aca5412a860371b137266352ce673a467a8faafe72794d717c0621c6ecf3424c745c4f61ed4e22044c594e0dae1842d70031423ba40cd631276575dae84dfc916c6745706798a7b89caed18b67da8dc095b3a89f1d3c82a6d41c39f76e8d6a2811452a4429f96993648efeeefee38c29fd74ab4424d87aa10def95f24f304d7748011a39189001ab18159ca4cf556ade9fa324b87f57cd648403c410f315e3e242208defac8aefacb61b8cd49e36b4340420c27838b7d31a315de632b8bb9a2d771c66649d7abf86413f9586e74e3c3347ad3654429e406bad0df48ba9468252e85fb3d274c6b8625d4ca51fcc464bb37883e4e42162a3c793c24d0f75aecb4ed14598e6001403a2c625f78b0bf589b1c4d998eab95fd4e880d2173a4cca4c1de95acd88654d56c0f8731e86e468c479a86de83e4666c7f4c96f051ee74e72da3873846dbb676a7310d055583aab9fd05b86d8e429cbda8a5124059cdf372a1826b8103a8467c68e72543eec084439537f0a7c3711fe1998d03be816aa59f6c82eacadc590e0bcc6bc8c4f1ec339f24a86cf44e84dfbe1216dfca763001dd486d409c34d416044c2e8e03d27f36b0fe4c93353af0420ab3d9c7d42b53b17d5f969d3959c60895233a3c8366be72294992c7112c8acd7eb4426dfb0a39b091fcdd32c45d330ede59e02cbf5599d767706a72a04250035e19f8295f9954f5ba83254032d121ef6b48dd8749ad378d2bd61e8320460c7a646e3ecfa59e767862b88b2f932e2ac35df60041a90d03c7cae9978b9f03603b388298e92d9a81d8009b1d26da2aeb235ac4a3d39c995d347d6e7f06c0713d19063abfafb096722de42b2a3a9f919ff38b24cfa8cd2a2b03e2a1f9a1348dc2bf93cf38084851b54262b061cc4b1ee60e84ad71e7f1ba1e41214e8481835deb2dbce3644d51bd8c0b755e4216ce951d4ce108261c924cb9df342346e7f0501c03a00d8c8a87436cea9b9ad5b30396eaae561579de0a2729984948dbe38cb06fc886243a54d7bb77e3cabc18170ea3305fc0d477ec658bd0feb69aeb8515dc0b52d7a62fda8a2f32d0c207c1a71159706ebd8f4f053979ea138d7d9f8daec0f1ca466b251a812d21d748146eed58718a0913a23fc9ef5d1863ae926b0d90f22e363269e2cf6f206e320e885efad1289f58edbed3169592f8b273425f1b3a419afb9b1bcdc475225eb8925d3377436793e6f6644cf92cce3a0c8e3c5e530b1a9059ec4c26af61d092a9caadc4475b99c80033a68815122cccd4a9213c78f38a8f50b6d0d0fd28d1aa99fa79c1465dff00c4c201d1aeb57e7e75fc15a076c76922c4a63ec743a12cc2365da42f9400925169488c11b74c41d2451815c796c3ad29fc0692ac9fd0bbab06068591b5ba78e805af73b8f461e537526932f5591dc89542f9d98b4fc1077cde6593ba1a1e68b07c340543a1d54c1716ffc841b7fcf40fc6d3d801166020a7cd1f71141ca40f41ab7e4a825272b284148e974752bd38d89f0f4d55efd3bd829b6345549f4a605a92e0eb226cdb87682d17e260340ac583b092e2b95ee6fa1163759a7ec56551aa72ade8b22052b8d494ef3efa85c4e5fa156f33d260faa6755b83ce1686d6a97ec8c6dc9e421f89148fd3d24a6c25c62517521707f1092101d77a8428939ad868c1cc183bcf65032b68f997ce483245a6eb0a04bcdbd3357e1567b6ae2bfe932a8994f706d58f48694d2f3b1018e7310ab165fd446e894b6724a10cd870302a45c7b7767a46835a2af63b5088b7ea88b8a9886be021ebd87fa2ee89206e7a658471cebc8f20b8767da5d4444dd3bdee6e11168be68725ec2f8dd31dcf3da98bc6e17ee7ba17b0c0a94f4c6efbdd3377bd26363be14a6e640a0a3763631385222d17a2905e049243792a83a96db67eb0519a8d96f4272b3b6d266a44320b3f5925a6a4c819f5aa5dc63e57995298f2f67867c2db3e0f18e3b7ab4598136689d0cd40e1965fb5bb39ba0607677d19f3c8fac49e803dcebf684ed1a0f5977844bb799fff215ad06e57b079d601f841b8eadc8fccd79170060e50db356dfee309276e35277920af1de1ff22960a430b70617a5caa883c505d860ae1805a9c58ac7852535ab7466978e31cae711ce24389ec98540b68279a4cb22f0feaa82d3419032e072201b02b384ffe1a45a812c3eb95701df2ceadb779e10361bc74bf0f016792dcd9985102c2ee642313f859db0ea47f8e265bf0e72b1377c89b5c2d71471930cceb326200d9e51aecbe7a9d0e66a82031072c505e6631c366c82901969067596d369cd5b00859e2c2e08c49da5f7c8d8f3ba0e09dae23860c1898c281a1c4f7cb720013d65b0cc512df244d853c21e92a78af14d44b6a9de79a94a37b148b3ea6400740a0134e16b2e69c18ea71cc29c2255fb5f7a64c0355cbc9d9f576381b163184f7d123d3f6eb8be99579360781a2c66ca14e2ddd46c5785971b4016fb4a98fc5462e1ca99df5eb756d82b0cf480a81f20cc0455def31de3eda9f0b11cc26b1cc19d195f24f68348edd363ca8fc32e73ee92427691496ec88fa6f06708fbf3335193fecbe6fec43faf593d671d7049eee307e9084f871c25384ed8b63b5cf9fbf54264ae67f0f18c7d9d409412c31d77bee2eec05c33c9bbdae563ba4667f904f0f242d724fda02f679998395e3d4de91619b053c73d8ab3cb7fa2e144e085a237e1a9c636d5a8300318b9b3896317306d065a8768feae459f7497847b0dcfdb0f339d8d363c80734eb73218877b992ec1bc6dceed024b1eebbf7dd682c3702d58f79e62c1d43fdbed240dec20a09a127dcd51b1c604594e9ea9d07b395408c25cfbc284b003ab10429557c5749ff6e075fba641d517d2547003f5ffc58743c19df5ddb2820c7ac34c9289f4d126de174bcd0589b17d4141eebb6dce6c746cbf35bfade3bebcc4d0a07bd05b055ccc798d0aba4625422a5d71ec928e49e9626b7eabaf7988c81d4648abd47d0ffe0522833b7f620ad32e96b61e7b3e576b6d3a0d95085bec9ab176835306284f28627143ee895016cae02da3c86715a301c156d3a197b0e3dc9dc7bee56f83bc29ab3b697df5e183ac56f7a2511d7cb00e71cc6351bc1c5460e07f77bbac830816001a0a57244d3329dbec06eb396b25fe590f2d2fe5f4c63c6105c77c7d9ad9b8888086db2f7967b07bd061e076d07a9fbd00547929d3299ec14ca4ed92993c9f7608b0d048381601f2c73c9f71f0b6d849503b2402cd6bdb702ab48d57831f9a652a99a7cdf7db2f09eae4ce823cb9048de934cbee7c4cb59d87509490bb3300bbb2ef97e83826873898b58560e8bc5cac9f7dd4641977c4197544d2a95c263bda7d52f664f32f6644ff62493eff168afb5607b3f1d1d3c1dbe1da32f87fb609ea8d31f172aaae7e3f9bc30533ef27bcd9427885ec5c9f72da7eff5e5b4e883f5e81e6cc9a25871eaabe634a9eb97a3f3a713d360700cc7704c8321d1429810d5a51d3e9d1c9837d21923cda9afb05ad2627574e94ad7434321518cc1f2ed7af2b52ef9def6b5f1d37189d59277ea510f5df1443055ee6f4c488b69316f041d1cf23d477a2d4f10edcb70952f15c3552d7daf7055a449e5f7c2374d2abd1e3be35693c6efd524fcfb2fa74ba3df7f3adfcec7d324ec71e3cd55554bdfab8e6ee88a4855eebd963aba17c55a55379a86caf7e0e85d9d788ab27faf5aeae628fbbd9885ca90bdfca6a9ecccb9c83cefabbf2bb7791841971edd7723d8d2524bdfab47f79ae6b57c1e0bc45207a3e3e383a726cda6756a17aca73b8cd4d5a3fb197834867a74af65bd53efbfd7bd48fb5ef5257a2d2e5daaa8eab5b4e4fb8aaa1593a52fa747f77b91dfab45f7f56d4394f6c00f5e1ef54ecd70937562b90cac2ce6ecbbf7dd03ef7dcf50df3dfcec7b9685ca106c37346ee41bc28a5504921fe96290402c692eb54c02f107109a7f742f4a48422d5a8c8040da49485d623421de57cffb788439cbddbdd924cea3ded8997edf773fef7a17889aefa89d689a663f356bad5df1e1f06ebed137acbe69d2e76a9c69025f5b8a694a82ee5c22bc7f1f590282dfe7dbe11d24bf7f648def236d1469527db79af403b39ae47d3ce847c4f70cfc76e0671ef69e79efc46a3f92ed4fb27df70e46f865187b0fc18fe5bbf7ddc35f067ae0177add3dcf7b671ee08e1004596bdbb90401f8c570116716c59e29f6314ee23d104daa17a2493fc21bd3f4793d01a649c5eb3b156312a1f8f0839fe167e1985dc5679c390cc91ff8f8e0bb8fc7f7ecdcb7037bcf4833d4e449863c6bcae0ca5d0ede4ca19fd9db51b3d6224a3e7bcdb4e336cfbba0a66d0d866409828cc96cb435bbb68225131604bd7f5e28731accbe4cd0b0d281e5eccfdcfdb7a3b53244a445a918b1ec99d651ef7d399eb373769ab2eefb7068199d243c674731778c71c761ae047364093e16c27be6ddfb91fdce0821dbb82dc38d6a91901e653dd32a6e669aa810a6a96b9a8eb31fcbbdf73d7c18d1a28a83418d5441c5adbd27b4721589881d4e0e54de3c4b4c42c935652040e94399525fcac873927746d5deca24719fff61ce592ae5d922ad2ff5e58ab45f4e2be4f9054c928d16e5f91cb304441db1e84a8f27641fa6b69197a83de80a3d5115caddd9a2e44c8b44322dea1a678aea67cd53656b9e86cd517dc77a276f2f3b97f444bd3b96cff7f3b273dc98630e71b7b2af3049f478ce028474a5796f1cf5a5c2bc8865cbd04c3522b45332ad9a53a5cab55e0b64203753eff3a324a5a133542543a44b34488feed763e6ec18df7b9f6376dbec9c46dacc9ee338aec2dcec711cc7611ad9f6787377df9d2bda1dd1cd73a6d47eb5cd4bd4dae1d89ad23429ab332d6a3104f15e451b241bd6af0c6b51ebb4a8be45a69fdf8e16b973705c4d10678bb8e397edaa2f6468c9dccbfac2ddc85c8dcccdc85cef64ce2473dcbd2e35c77974a43098a2fa70a44ecc5165c00f6e3047f595c2cc32cc2fe4fa2945ae3408f4895ceb0b664a7d494f90ebe9c755148b060d512edb55ebcbebad5d5f5fe82abf9a04a32bca6a8d41a79ece509a928a8787a1301f18572da9d03e0f8921d1555f5a541f442c69905c83dc4f9025cb506d790da30bddcc5d985150a888ce144a9bec2cedd91283889a37b36d0b42268133de38f2521a84e3344e3bfd70705ad6d9464fb069da464ff3dbf3bdd9b6dd6dde793321e08b483f6f1f45d4eaba1d387bdf578620099cbb3391319e9fe3fdbdcfe67c93e69cf79e8e7d6ad1bdfd927dce5b6d461ae69c9e8906c299bdd666cd894eac25b24422739fe3f60c1f889a8d6dadb5d65abb911a890426db5a6badb5b6d65a6badd5964b684d889963c2bed65ab32c3b096c0491bd890fdc87ea883611822032b289ecd5be49eac37cf6811e6600ca10e4729ee39e917306609eda19c4fc78d0cc69992be9eec8c474a5ded0c44d0e15e0d083203e506fbbbbbbbbbbbbadb527818d202cd944257bc091259d5847f4216826b4f69650df7934357a2db445eb71ce6b325d9a938e1e9026d17fbe9c3d3d8f40629a7ccc14daa345f43334d08d5c636a564558b55297d82296f42546489e9fa4d7284b4e1cafbb860a336d8f0e21121292939084587c2c24a190c9496e42428642a19291e6501552636a0caaaa12a9a9ea040909edec6285b35aad70588c8c8c8c8c8c8c8442a1874223231f4b686484e4a1938446463e3232126331d23c12eb51034c0d525dd6100a91590c4d4c4c0c4d0b1008040281402323231f1901813e96111028f491874640a08340a01bd08d4b0a3bb0d71446465a2dc6ce2cc6ce5dd69209c984a8f87c3e9fcfe70302810e027d567c2ca0cf67e4a08f803e9f7f3e1f22347f88ccf4a0480dcd0f4020f2d5f37abd7a5688a2288aa2f8f97cfef9882a3e968f2882fe39e8438aa248b338f64b9b20d3a73a06d528f87cc8156367152b9cd56a85037edff77ddf278ae245f1fb6411bfef73f11f91fcbe0f46f307d371e9e10983184313131343137a9ee7799ef77ddfe7891fcbe779e2bf8b9e178e347bb4864593405fae165dc2473b8363e7eeb6dc96db728570dbb66ddbb6799e77cfdb3e59bc6dfbeefdf3c8926edb466364a80c323d0d9da143280d3c8f7cf5bc5eaf9e4ed3344dd3b46ddbbe6dda27cba6691ed5b41e9ab51edac206147da127376c63676e85b35aad70326badb5d66a9a76cd7eb268d66a6449adb5ae8933a9c0a39363058dc6d41286554b4357b4562826a6eb3b7cefbdf75e6bedf53e167b2fbdb7c6ad61a365a707da391b3b773c7828a209ac8e2ad56c4a1aab56ab69774f2ff4fb63b9a4d6dd980cb95e9482d8c7b05679333d4a4bae316c13c3056229ca33bc50abd59a97c39a0d7115621dc3b2463f4fa34b18b6327f98227aad554b06d06e4b8a0c0557ae2a1efd0e30c91a315c20625809131b913c638400039456e1068a28b45aad56abd56ab55a2d28a080028a28a288228a28a288228a28a288220a28a080020a28a0609dc195c5dcaf628baaa689757a33a5ce16d10885268d4c96759a7af6fbabcebe7b9169b35b0d3a99beb5d66ac41c56a35ac8f625cef477ae501caa623f4d4f8825109c9b7e2c5454b2a304e51aa25594515a64c7a9626f63b4962f792788f5f7d969cbb4d48af3d629ca4c2c8c1ce6acbbbf1e439c76d31796a29b2865d8cab4da9b616de33aef0343bbca36771f152b40232c5a84485c94989cbc80316ab56d46fe3b185e7c114dea78f854b52a54e21a5555ef5ca9d54555aaca4557c295268aaeac552f15e4c4bd343155c2554cc7bc12e4fb70855dd8d5249c2ecd90e19d87d193f1625ad4d2a37b917a296d88cbce84393c0c653cec5c2e972b4479d8c562618c879d4aa55285e4c3eee6261c1f76a8300cc33fec666642180fb9582c16be78d8c184270f3997cbe50adf21c7c313963ce4542a952a74f190bbb909491e72a8300c430fb99999b0c5c32d168bc542160f3998301c79b8b95c2e57087ab8f1f0842b1e6eaa50a50a553cdc6ea8cabd093f0f375418830ac387e136136a2e97cbc5136a3ca1f8f04834697bf823a6a953a954e17d744953d5d1bd09bf63ae68372854f8d9a8504385dfc3fa70268c85b1301686e7b49930c4b130d460c250c68723cc81a7f45a5c328a88218a2be672b962f97e8c01929deaa65375aa4e7593ef3f7eef50dd4c87ea501daa9bc9f73060782f621d4c2c16eb60f2fd8b93ce8473713c21e7e23817c8b9389e7c7fc2b9e054dc4da852a96ef2bd09c916e250dc4cc8a1380e05a266f27d490b8dc516e360c22dc66db1180793ef5d8c60d0e6da783617b7b95c3cf99e6445a662536d379b6a536daaed26df873e45c49b4b5c64436d331b6a436da86d660c4f436108bebcb8785c2e174fbe17c7ee5b8baeeb54372a95ea26df7763e7ae8ef51f8baaa166349486d250f5da48c562c52eaa722f8a35e5c97827cfc56b99295e10ec12922fc642be177920965a4c8b693d5ddae179bb95e9129691c34b3ba3c34b4b93c1db9826890edeca60ae601e9497960619bc15324d310edeb67409a74e2f298bea69d2d93057308be586d83491385dc2a71c1d1e5793462acc152c2363859b6982a1ea520683ad602c586ba6492640982bd805bb0481c834bd38f80ad3a58cc5aa425832ac21197c7d69d2c9c1d714cc952c27a7aa2083af41a6a964a74b592a154bd516f0add3a4d0c1b718e64a96351932f8864d938b83ef225dcab49bdc38a41348c284b992c9c834a1669a5aa4ba746130223015083af8aec15cc95cfa06197cc74c138b836f972e5d16ab83b01a061c39787372e2d0334d2b5e5dbaa9142cb5938ae13449c515e6caada9c942064f5dd3f439784ad3a53bfb54e49e6e32181e3c15c25cb9259d9926f1a085c1680c780ad324eea04b0b3278ba82b9725d8074c9b2eae89ed5c302c17360aed81cfa324df4e091f811d35461e20bbc8fbeaf814206bf63aed81aaa726f809fd1257beaa1639af041b0d4218315067e362ce452b38cb9626550a6c91ebcd6a50a034fe78a75a12af720f8161f0e709545d8857974205d3990d82583c42e149227dfc720bd96990205313c4eb170ea388553ac7cbf8204477cc232f8747cc2272c93ef55c0f84c60d805660283dd7f5e7827ac1c96098b9593efc5922e94a56a4259168916cdd3361021064547f4ddf6b663076ce41ff36d4347eecf8acff99826fa3be743d09bffc4d7716a5e7d8f9f8723f86f2c9fbb8b63a9436b05090a1d6123d7ebb84bd01650fdad0d227bd09d12a9623747ee5dd775e238ef755d37d29952297df73c77202a9d0fc92350f23c786e2ce121e679f13b104d5543f2584a2083dfd8b903c77246d64a1a59a3795adb745a5b329167a6432ed53451bc779e2ce128d58152525556d1504a8370263019af93911adf2cf9e8bc79d01c4d8688f325288748932ec68b4889ccb468acb743ccd359bbc939ebbb7e79118a8f5a6a71162fb591d97a0b92057967a55a7b562b6d64ede58d1a75b41f8e1ad7ab63296619be74e2c664c944f634905896a28f085108869a27597e606aa39d9f356870b9f73147508b303991e81534b8818c68adb5d65aeb856beffdbdf7de6bada571efbdf7da7b7fafbdf7e240bcf7f7de7beffd423751ca1963042adb4ba0337da65ded710a9e5c8a72cd143ad9de8aec67d81a94aa7021aa237a4a670aa57364bf65fbc936b2ad9f9803d1d6dbcf406911b5efe163beaa176c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c3622b9a21c1f931fc7ff3f39b989c949481e0a7d64e420d03f9f8be2bfef9ef76dbba6dddadf7b3aa7c9bc36a8e22ac47aeb25d7151c43555ac3317405afa8ca2a8676e18a39a2529464aa8376aac9f0e69c132ba15547f5e2555f6bade88a8657e114d1db71c514d1161605ab01c6085372da71ea50b39393f14e9352b95ebbc1b026cd9a1d9c5c8f759a44318ff7591d0d640003f8022816a80005c6099012880004fe80184638a001270c7851c40214609200183e58104042848b1e3c0e10da51a223c7102338580861802040b905103f14e043001537564ce20056d828913e1f04d0438d00805e48430500293bf03063e3b49148871c64741ac800062e60810a506002128800041e6084031ac0802216a08004f8604100113d781c60878e1c43e010c2004164207e2800016eac980660a344f2a1460f0208000d1500a4cce061879148871c64a0608c31c6b8310f1f0b0ac6328e7219286449312e8232d28c8bb49c007bb9a02043064b6bbd30c678878f0593c72771ca0eb138b03596c6ce581d744619e18fc72e2e2711c4a05e46308e2d3431313134a2d72b478adac3a3b3851fe7c5d839860e3434ab22b85a2c239c9ca87240a184b86066880c0c3ac3c059ad56383278786253bc9c5ad24042b283d26add44412707278a50a8088bb173498c214388ec80b5523d313292224fa7204dc808812941e71638abd50a67ccc98171a125b6e385cfe74513131343f3d5aae60814e7a608123aaf802144484c0c5444524e7c1fea456cc666cf54034c1097b986cee1c9cd8d4b0a3bb0d714b6ada5b1681a8dac46ca2c326b6ea8cc183bcbcc928d0cbcb44330f0d2ce5ca0c777111668f27ddb324b4654e0a53d51e0a57d994037100974bfe2ccd2121178597320f0b2ea3ca04fde14c6887ea19a258a72c0cbba6ac0cbca6240379d29a2fb156696a86a012fab1005bcac3209689237bdf1d12e766689ba585e760c012f6b0b11dd94a7471799251ae3f1b26f0ef0b27176f4c8bb6174bcec26d44c138bd42c352ac7cb2632c4cb56e168d0bb6784e87ebbcc52ab0cf0b28304f1b261727fde3740bca471e8992615af596ad70f2f29ac002fe90e015ed258f3dc689a59ead8ca4b5a637a498b0ce0545561a84ab5d1a733d304ce5245955ed218d24b9af2a1bdd7e83a0364d607e0e5ec11400f34fa1c982bf5267cf78f98264ee5e58401e0e57ca5dcc72c55571dd519fd1d73a5f25095fad1cb59b3c3cb1b3ccc98a51aaba3aa639aba8b5eeaf01cfa184555a6293b86d59110aa52dff8fdd087a36dd492d6ca0089810b144157708e05c8b2b31115705160022410ba825f1220cb5ea296b01601b2d420403e802c4318c708b26c1ce3005403500c40cdd0154c53045976c62d0b5029409500d50d5dc1427c90357bb190a58600f295eb8920eb290f5dc97a7a9065d76cc583c60e40633b680c86ae64383a6050b594c5e4400d81c2819aa12b198d1033aa5aca5a0c40965a10aaacbaa12b991020c8b2efab8e7e7015c04500b279e8cabd41969d6f85a12bf7d619ba7257481339808ed980299124d207b2c64caeb7e50ab1afca43576c0f55a9ef4125005245835421cb900800b2145366903cb9bec66ac9ae5074c5e25015582dd9182174c5d250957a1ec81dc81119139165eb4096213107b2146128a4905cdf69ad1cba5263ad562c671cc7711cc71e4b3e16721c514e1e851cc78fe3d822479ac7d64d8d45412707278ada06141492be344d7bc5f8ffffe3387e1cefe26319ff181f1f63fcff7f488c91e60f21b203d64af5c4389231c6cee40ae3e015c67971727272727272f2ffff09c9c7f29393f1fff893939f9c9cbc18693e196d10db848c1562616c097e12c7d0c4c4c4d0c030313131313131393939f9c98949e863393131f94ffe13b2a4d984343131c98131d26c9203e3826d89ed78e1e4848431767e815bb010dc825bb0101724242424242424262626373121f96431212139b9c94f4c48121292958b916692baaa35f5083837452a124c4cc82ccbb22c2b098542a15028f8dc5877c0f7989db29715e48fe5be7b9a268ffb9e96d51d7fa3edd13a619aa64e695b39ba30ec54541e15425a84ada67dbbf690e3fade6b736feecdf5c37a2dbbe65df3ae816feb1d7cf7affb978655de21cf9a34c064ef1a095699de8ef4b3de1b6b46b14169f67ddabdd1a27adae9c60fcd12dd99f66ad3e47daeccb2d3c68fa5c91b9ad6a345a1186aef97db3bfcc237f9e2e3a17d2395a05924cbeddf4f3e16ed21a9840f3e7c3b46d98a3e10bb086eee3af0fb899982df63ef60aae0dfb1675a44a47d4055f0b7b17b3053f0bbb187cc147c6fecd44cc1d75e90c2f87858929e388e43bd9c5a76ea0e37721c5777ea4edda93bdc8b8f47465aad45dd68cf0529b3130db247e45e52978c2cb97fa4467ada77256cce9e4498b377d7638b1ab17390afbb5e5ec6dcfdc94db5f0ff589abc91a9a8bbf7ae1b05139fd5829613ae1785d55a6badb5d65a6bad271f8e4a5d2a494f25f74e8930a62ff485bed01790be740652978f473f23cb8decde8df66217c4b26f320f0011f3b88d4a2821e2942d452ef958b8f74d93c29ea6c9837bbffca1e240685ef1faec544b9dd23ee2f67d64ac699829f8a0b14e3155f0578c2a461aa44578863e64a969a2b8915f9ffba84b8b8f473fac3b9dea54dd9926af4e5179602caa4b4dc453814c708b06e9866adba59b689769e2daa55dda85e3c05233f9583cd22bb98bdf0ef0dc0faf23699026e989bab834a9a4ef016b8b589e8c6f59531343175916abeba1a39587c264fc9e93ecc1f3469cbbb1eed4292a1a2a4f8dd9170bd322548b74d452ede920b8d36aadff2abe8d5abbd76fd334e78c496392eaeb8a8fa56a15bc7690d64a33e865afdefc321f3c2c7641cc5ad34473127427d74a7e583c3cd27e2a414526883b33e5344d1d69822cc84b864b1e89fbfc41c62f67cc397f67293b652898a5ba53479814843f11ba81e84499b5c0ce6aebc8850bcc129d169826ed24249f0ffd8668256cee2711e6aef8e0c702be49b1ee78201a64f602d323fc191b43327e4903df5a6badb5d7da7b8d5a4bf688da7f5359c6b348a846a4b933199ffa2663ad462e6f4acc095d9d8c67117c7bbcf74bf09d11f13ab2d40ebe8be171634693a9b2993123b28dd90a660a7e38662ca02a3063861a95d0b247ae74d45226a447f82cad25ba5eb08c51b04cc643747e4d13cef836668b4c53d55854852c8f40f19e4498bd6befc21b37bad7e8c61b356277bbe3b2b016a681df7685123ac95d832e6d0dfa77966c0de6084f53f6751f395b14fe7b3859dd58d6c8f45606bf264423cee05b96a689e259ddb775eae3d1fd2395a0393c85c8d2e3c8f2e61f1bd9443a1d47bcccb8684f4c4dc38210b9a9b1f19d62f1ae914a7c43c8f0bc31cbe088a3af8dd9290bd222fcefa547f80f85c4d7ac532da2640fb931b2203305df8ed9890bf45a8e1fe0bb6b20be47f6106d4c71a9215648c6d701912e408be387e3d78214d168c9a797560e9e8c6f88267f7864e38064bbb89b8cb512ed21e9015b795c4d2ad1f01ba74975a73d0d249a84508e5f776a89468fb495c80ab5be93f13b1f3cf07ae3cddd0884661e0fd55276ea113ed6448c394cdf17e16ff6e341c94a2a41b3468a2dc2da6dd65e8fb39db08170c10aaf294c210753005fc1d7fa6d022bda93ebfb968a4f88dd4eb66d37d06959bf4cd1a5d33b4de26e29ac8eecf1f7508e88535b5a5a8e38c2d237d9415a54d696156b9a6ebded846cff7d44d483f7be77d483b75eabcd52afe6c89ef51ff4d452b261665987d4229c16c4162034a7c64abbcaf705b6e534c9d3d99926708a690ac5876175b5c8721fcb76ba9dceecda4e0f44777d59971eb95ab22096b5e5a5cfedbb39d6541dd2a35ed5b1ab0ba7491bb9bd9ad4d53b8c8748e869ebed357fde39f743ae7ac5e1883f80505b569e6cc9388865e599775a8fd5355d84eae4e0dc78352df280a5434a945aa445b6a6668a7d17c41fd5da579726fd386db7af299826efe0bd3b96205952f1f2b4c8d25a6395ac3d5410cb8ef1a4789a5452f64a146b5209a5abda538bd456936e2db51373647f03ed896c1f846c5fd33a6fdb453a48935eb6dbbcd5539a6cdfb1eeb1f7464a849a608e0e8324f411ed3bdcb05e8dcd6a18cceab4a88774b7afa969e26e5f77304ddeed762d89b0fb46e9b792eddaa85d3ba92c9df69e668abd4e93dea994180ab5c86eefa7b2c8e29776caa165cdd620a25f7bf4f88f921d9525ea21c414e2b52d9f41d7769260c696f840937ec8d98f32237bb7224868d68f787afc0b31ce928f3aca0e1b40005816092077b4931ce2de9ee380a84800f131edb98e1b2d672d47da16d5c81c59a3d2bef372b799b3d65a6badfdac49434b76bc4493ca50ce3e90655928e3b88d7bc76d24a84559f6954fc7cc019991a804ab592c8b2254342300000083160000300c0c064402b12c89e254b2f614000d7a96485a5a3a9849b310c6320c308831050c200600000c0800800c910800da65b97ee9fa3ed4a97364e3b207781ae23fc4881d8c58c27ea39c90363cb05fc4084fdff51ad850a6261b434209235d18c4a9c0aa1797c293946b501d8f4825d7dbe0e69f42064ecc7bd5988d38633baa808c46e3dda91481388342bd29eb3b0d11c4b7b0620e84fe9909ce23dab8184ff9941ae3d5c3c70d5848e23f1ac996d8736658c42bb8806499ce8af78ef3a753560477228010e38288375107ac24a70b8ffa16106d5e7ab56389542ea7ea5249df4f3face3aeb13895bb5c97680ccc7d4d981cdfbff708bf911b6e0945c961d96acfa2de0fc6c4d906c02689310f8094c768545b8c32d4cbe5a9ccc7057bcd3d9eb81d0a4f08b9a5c2e22e7634bcff24f2a0624484028ea82fa8155d0f1686c017b43e1611f9f1157af1f84e763df3bb9a5eccb4038c67ab8f0e3f1722f437db62a01fafde16f4923c759f5a29baa38eeefb4499759b113ec6072481140943e231450a38af05651f64460e03afc8c997823f6557fd754b864c97a62338efcea299553c2727f4069601fed1cf2b565006faf358a045f860ffba338d4609bc25d593584a276b1184c24a5f07c22462a359b549b9876720ecd2ec939b062a5c16fd94207dc732c89d920aefd8b034af89b74a28bcf7fef0c5cc6a032530295acd3e119711105993a56f1e9672517a104dbd1b3180fc77540a7e76ed345d9f2c9664f5c76a6db251fb5080418bd74eb8e1ee9112cb34ca9b2feb3cfe5e79efeee4ee2f0695790fb4e088125f5132e46e4e6def12eed2d1f4ce45228f6696059688fd13b44bce8c0401222e4b37729a623827ee206808388d3be8bef192523dc21e22dded6fe0367daf418963184d5ac2c5906c74f5a93695eb3af412aeb40eee02faaa13c13918904e6c7b410a42abee64f8e3fdc8e019096579466042f9842dcb4e516ad6375b3f0b2daf3f9c9e0b9f5843cd4bffec18fe8539136b21a0f115f92539ab7a7e75b82651becab5d67aa3242cf56bf2246d3832b24f7ab8a92423b8ff338d4abe2005690f4539b548be76d207b0a6375268091b4bae8513861294504f1fed6bcb508acaba9583497043190863dcfdac5de76197a5e7aeaf5a0b7ad88ecae8033df369b94489c392f8a0129e8b63c904bde96c15fcf090ccd3b8e3df5bfe749ee142c3dc2c6135bd0ffcdcf6513f897ad979a2c47b1531e0b74d1a9dac393b4f72c82e675fabc98e83608c42fd552b4995a1ba1fea525d3670725417fb2e773644b4de41bf92a1777cd0a8f78a7e6c82502f8f0ef612c21801363a6e54e42b50df657078d48a39ab2e066caed824fc79dbfcd5609006ddc4c17c0036e333a2de165e5a4dfc5c018e3dbf2de16517683c95ea73bf8f019cd3013292e91c5618b87a06e0bb578a826e9364064f6a8a7dd1631e7fe63aa67bc43cc2e1bcaf976049cfea01ee7d72bb2318ab1162dbeffdd6bfd5479bc8879b73182131200be2abcefe494e040314ed1ec8e8f9ac262d8fb11075e842523401e3fd171aeb0873328883700ab299c4822cd4288e4cd4325f728aad72a828d49322dddd66065fd42216b5b28dae17c3d6e2cd08719499843cabe8fca777ef76227998c2cbf29e8e322387473e65f72ee08a8d0032cbaccb4a9d5fa9d4dfb8fd9e29ab231a82dee68869877a87141b9a070bfcc2c74075747cc851fe1134e770886ac3ca0e814c2af0902eb472fca2994d59ef35a492c1cd5628f907d3b87ddca8b61e8c91b708a92ac567666fd82faa312e342b74de5676edc1708f3304d1cb636c4b8b00ceb9b4d6441fd798b5943844875890581fd436a1dccee55b0d6ae315c8662ea89f3cc823540d6e6b3e3d04fb124d374014dec0327398b197fb05728193fc40a679cce1d1f1199f0b95a87bc2a6e91b29ba278e29c739504ec8ffb0d63e065caba119d72b7f195ceb12c35520228aff31e0e675131595bd4b898361049c09a44ae486122252aa35ed8c96722e93ef4132298e23d08511815b36221360e0025dd9220ee009ef9a32d6916185104f64b8a85fdefe692b293d87dfd7f8702f7adfd1c0149de98fe0a8fbed77190744dd3c24477a7de1e58bb0fc035f893a4a5f9a0bb771886d525480de772a78c9dc05b1400c5675c6563564793a0ba66517580735002616e6ec8175c6aa181402265e82ca8206c13a846a318fb16f1f6006eb33d531ae7e07802d49d1c23a28e2827a40c3ac4d07b946fa99ef9dd2a3c8376cd7b36859c4e4767d707537387d8c7ba3eb4ccb91ff0d69ed701e57e03a7e0439d15bec9c078fb5f649faa32526adf7cb28ba7b415b76d2312569b60ea094f2212c21f95a0883b1a9b32d874521b7a75f16a2602811e4bbd4275bc82b82aedadc0687452ac62d0f60ee5f8addb033897abe059167fb82975f331edfc253c2691617ce57bcf377446d5645403205594164a884746ae4b527ad2a324d0ab1852b9712627dd89e905a98e0116331f10b0c5020a2e9e88ae8dc3cbdb40e7324cad5248bfa70b1edf1316862e586b1f5669c458f5898ebd3ea3c625663251d0e10e6e910f05fb992417f7edebe13e0d5b0871537277f7353f5dc8e339e43a0b18fc5447f869ac4e30cb4ce4ad2ffe5fd5dad85c34730f7e2483c7fb5c1febe68a3b21d85cb671ab26d1b6f3b83ec8d00d07706e28298e2227d154e20822e27ce9e82d439baf39c921860e56e65bd445ac6e6f28b9d40ca77549c2f9ea793d8ea843d2d879017bd768846db1b720ab08eff385ee12f2cedd33ac20f9364c4a272ac4a41734544d216da14580bc85b4b8518478e01d740fc356c502b21a26d7d04f3da79e1f7a80af009aa3155787dce9d22ea79c144fb1c34eee63bb2fcf1e6d6f2e9fa1c1a15d06209ab346c54383fda5913dfa75dd55d6b086b1674f24cc77f546e30cb8b3594903bde76163d8e7d6eecb0ddb4103802aae6e40bd9a94fe7a16ea24b53a8485000990631ebbbbd95063a02377ab7865846145e797fc16bf883d7ea61b1f470722ee2c3de60f4e1677a059afca0074f995998eb44eb27443afd98e5485b946f1bf0a56c2892a3ecfd7cd5d0cf523a634bdb62ede49e3733e12c42ceeb687ab0465beeecbe0c65db9ce01847e3aa1a6732d4a1f1c17e6eaa4b472ee74f47c04a9b341c310fd9f6fd6a79f31e43d8ece38aab667ec43b76b5f58e3329062f6acb588a0a532b7ffe0c833b84d58764ff68f8350247139e64d41fa7b6c8760c775ae0fd5acb642d455647db70dc342bb1f268b30e9d008eeb14ed4bafe597fab6d32578fab8ce27cd8077cccfd6bf9ea614e70789aa83177affa172d1aaeec66e263db215cd249ba519cd57f51766990b967c157401856190b73a30c74bdac20584a7e24b58c06fa431f7e1220e5c755ce8f04398e8f9e9445ace59e0114c02f6ea65e696042bc53316f2f57e5066e8737a8321bbe0f7d0a1846e4c7c3c248210c49a031af6d8378c7ae4799ee7e1218658bb045fcaee62b415ac4890c8e6e4b3438a73840106a6973d4d0254f13d2dcb553a0cfecc6d97ac6836659b9eb69cbda44f198d80415019aea019e4c3b42ea817262cc1a029e3f3b8464b2f04db55c265f96ac8420d0d9c55529237b80b04d1a7572f10e97999cba78748ea6dda0894456f3bda33325121e6e0ae80f0a3f89812b568a6e6dc8f51772ca46b54942bc7de02ef07598e5d2efb35e6d8f14fb2d8b11be2415865adc9f4ce2cac3ed9ee623ce675e6497eb957a215305ab9cfa0ad568347e79b12a308b2b11a33d53f61efdee82063ec5ba3f00c7491a9b55b72bcebd35f3c2bf7a9a0d2be99a8fdf3f20e2577dfc6a992228a763f457b3442704054744bb7469c81125d8b3269162efb9934111cec97ae4667f8ad2954312e1ed7af6243431dfba4e419261449c91e68f4b133c46f3668d3c63837c940cc0176384207f48f47551c12395fe3292e7c090da3ade65784f92e7f3ebbbd33623127e0cf27460fc178dba404c4de197fde44205c1ffefc7e29ca88db3098ede5970c4f3965291e1e2b252f70589a71c7346d9d075bff7652a0f1f4a05d81b3084f380c39a9c40595d0d175ec51fe05b742163b1d01e2139920a850d2560eec64c242400f2a7532063b35ed2e1f7b496c7049b96b6728052cc2f615f954900a25b35d3981da89b2bded3a07dec917f468c98574f7fb00c1594efacd2561ab90ae83c6a9413d136177b5e6b62e1427862596a265a9440a1b5cab00e001b4fff22992c9d5efa7f5be59b32b6484b35ea9d8a5bda2682b03483c75e01d2f5cc1ebb8e37cbb9f3df6480d60870dbded165e879f6793d6e598140192c3948d833361b2cf3328c85f371b3a02873ab7f7b16d791b6d8a3022decb911a75fb1b334fcf92806aa14b50ff5c6a97b9de5103fdd7418ad1e5a3d40b4bc6e868a014e3464add47171c5e23666c91b1582f7540453492b391cc2dd473b51c08827610690860c62c3a189185e1b433572e365557f4aea50e4057262c458c98b9971ac70abb6a69f8fe3ebb89a8c773e681760e25c5b49951b406df8b400053f6053c7dda04a7972d1d6fb8f2038de04b51277830b3228ea936a91dd4e2b0c1fdd9dd897f48ec19e412d15becd3d1e429ef5fac2972d9ee9fa371cc5128d0c4b67ef73b458101f9bf2ebfd833e4fc2e1bb6cb1a670f196161625cdd89eec2f22ade14468049a21b39b3b59e6a19581bc6be3cb1a81834f24bdcc0eda2323d488521cf3a25100d96f0bada94d9a0a1123bd62888543cf38f9396bdef3b3041b4173aefe5801aad2c3f3fdbce15f26190babfe49c1e19e1ee600447ce900f4232a71eb263ca9003c799e37b379202b05f3d872146267db114af54bb1cc93b0b7c492318439dc58d96bd1df54758d0adf7af523d5b4e54a2b701f192683db273787fe734f8381b6a6944b6294331562fc3273b1a9dfae3dca71d355a81f6f1991b0fde185b2bd316e571d1e88c6c12aba4ad7afe5e681924fc154384b44a9fc7f3fc1cb6f3ba247593ccccdaca652ce6c01703a14e68afac5fba8a68feabe134bec61541459c23205e6b367a1b88f83962bab0d57bc7e3031f70af5ce165edd60cdcfbead932dec329dc000d65c6bd6a6e2510758617d91613340d3c3277d761b15bbb36008f6b3b04cae1f4ec80c97ec3ca75bd8115eaf503407a4e1984aa862a8919439cbde0e4b1a4dcb99746936db29b5a3599efee61f9c3f7aeee19de324e765fe02194ac027f7003418caf95bc0a1a97fb310004660b8f7eae3a9f6752ff21b254d9653a4e728bb297e7638c6a0fbfcef3422b7e0f8361d3c573f5d4d91d723c7a79c10ce8be9d775bf56a6c93b1f95bebd2c126a5d7ca119724b24453a08934de19049cdea48d24284452294513b8b78b7aaf73eaa0059b6d33db523a1fef6abf9497dcfb34cdff1ff452d1d6503ae990c16bd2e942915777499157d79b22af5e9ad02968e9ab3b56feca1bffc894277172d1b2f90731aa73d1e1783640aa11bd1ef79952648c1352c71144f1aab05f85cfddcf4b9916c8ab4e05df47bdf74df4e818ac522589edfee6756de53effc03242dcb5ad0ad379d6df410f55340c91002050f9a898fad5c4c1fa081a8cb3a483cf6810eb5badcd78a6ac8412568f66839d1e5881026ce727f69d876d0dc31d964163eaccc5bdf3380c752ddda8a0a32d0de634815767866dd7554330e2a56e21297c810828b818fb079f1a231c6c290f2e9f2a0d745c48024a7e0fefd5b7f448cb4f085d513ed2969b1040242e26b8e871fd7d7f7a39b8ee5cafbc58b34dc2ef9db9b691f342931c0194b8c0c17b8a2971460a79ab2e5a4a9cff0be40d72dfb072b28242d99ab4e99789ea05db9b72b65d6ad0db3263b287f4d6d54da74fa3de076f794a9a01ad68a3642e011ff6af95ac7dc7213e8c9dc479797a3c156301de42e8f91ce12e633d6d531506972e17f1e2174095f8394e4b8a7e2c3491b1fe0c8033d665a0a29449bca86c5770085334302df95b76a5c86211b147db9d272991436322ca0a890a7e757d920c3b2aac37f636d1b5e06bea45fb9655086f02493fa495b9bf3dd3118123e6c61e1a92134f114727881f8423a528ac18c31e6b00d4947092a786d2a5e9e2b8435ec5ff659e8a5a1370abd8e0051f9c8f34b61a149673122de63b1c2883db19e94eb9e6651ecc1b49a84d2c92ea7fcd7ba908438c995249ca204bd607d58987843281e08c0eaabf1a448db101bc18c5a176206b1d9986fb198ca4315c8684da4357466d6d2967bc2fc416bfa50d8b70232e788af6110c8a98fd62b0c2404df4202a8d04837af7156e145b6052b9aca61ec90325341abc5b53b703b661dd94c3eab341054c02137206fe16f9758cd556efc279d88ef4a74fb79bda18adf6e9dd7705b4f752b7e869ffd2900b737b3bccfc5fe709c19c5f54915be33b971c31280632c99b3c623351a77f0e2be01c3a8487fd764e36ecbb2b6c84c0bd750ab55fba7d6835a460602bc1574085fb2ceecde44b9c383f0fbe908114eb3a6a3a934e1ec432eff96601845175a6b66d353d39ab81a3e4f61ed48a0b6531734a146fb7da81375384566bf7c061735d20d51342ea90c5f725f22affc712155fa1b8e208b0e3c5397ce662a054d4b8d3546a468116f70f54cc74b3651444a9a78533e8c5935cdb7851b7832dbf113578a59703b344f11ec5328d87f4af788c2070c47286d26ceccd9dfc6083dc97d74f63f85e231c051f02d1ac63d55961d6585b5cb959e53980302391c0d60b828a9d6d546ca7bbef9e9143426205786109ed4e27ecc4a1d82a34db36120e6df8d667c362341930a640eef922cf0273bbdc13ba2fea76abc90be49c16f2cd6477228c90611c935c02dd16518601954f2006b630a515a530e8ab0c2134b3df79804da6947cc7ec76011242f88f86720c5901d9f462dfbf19d4831999a7549a9b3819542cd280ad02795215c389be311d9b75c4bf0e13deca57664896d585839265d4eff4ef36191545b35074fd792066981e156b98d94bf4e2f8f347d1a158b4376b1f4f4c6e60f01f8b8be619c17829b2d469367a444e78e4cb85f400e7883e74f7d515777433a8695830abb1e80f06ccacabc3c1d87ceb87b034a1c751ae0cb5de354403941e33224a731b219ff5ee2188054196ae3156a98f0d91b6155d5e72609c9d6a6590114af1d9af2ee4b66e419860e85c8bd900670022bb170a915f18ccbe2d7c3f2b53cd309f15f9cc2c2a061c4d6d9b7c5d6592a678c62cfd547e588cce1b1bfeb7b9a7292ff88e53a666272b7d11e8ebc48436d59a9038153d4594862c730016cad6bb96ab70f74cca8f8bad235f357d1ed2044b918a2c29cd510e2f60f954c2bbe1ed893d1fd4ebc49b0f7a4ed0eb03b727f47ea8f7096f3eec3941cf0f6e4ed0fb815e3a20bc0dde9ed8e303bd27dc7ea0f7043d3fbc3d61cf877a9e70fb41cf09f4fce0f6c4de0feafd686e9a69addaf818793faee86ce4bdd4b467f175301556edb28cb9e207e46215a78c0bd84bab4cd1c340c01eb1eaf719226ce0fe77646255004a7b8fc801ff1024bd5cfd55bb5b932d7672e969dce9130c37e676c3d33bf238e169ee977d41b936f6c80ae8db69cd6231d7f8cb35b4658e498665ea31b2cab43068fc0e95ac02c88a6ad4d3374f4406e3a6939a33a4d2ad687189f246b74860d5d8b91a1591ad568755fc6bd68506028f00b0d261faf262d4e27614be47a0be88eb44f0594682bc44d8a71196ff5b4ea1b91e689710e9efe7ef339fdcffe7332354a107a8ae520d93d37cd8445365cc0f509501bdc729af89e5966a450228142371a04a95c3db83984104c78c8f3561866003517f2075b94b2b7baa8fc466354d1f9391583a2c99f52dd3b30919570c4ac876bef4b59d0c939d7c91fc0396737d61e9bfea2f097eb0fe53fd97c4623888b3902a2dc380308555e9360c2213a84aeec3209881a97c3b2ca2098ccad661229dc054601b16c12944a58de110a74255d80c076112566f3c3c854f1cd9be50fce048cfd08f8f8328609a6ec0c6302877370b781b378ce9c86005f8914746568bdff8e1c005e8808a2b2e42070a0e70014d6871c50d34a0820397d0113a0e5c800e68b8e32634a1c2811bd0371971c415072e1ce0c2cd6610270e5cb8c08513b766461cb8e08203475c7107fa7c893456fbd1966570ed8539f0cc52b0b4cdc671fbb6215a9634dc78974766b62122a7587319ec7f5c90d14bc4e31450b323e5bb613d9bd9c8d957fb4473e1c048c16a62b1d402891501a13630473378e40224d9f2c32e35115be33f8d4bc7c1819ae30de16e361fa2be0c663fb741d7d7344cbaa165662165b20bb93e370d6196d05bdb17abeddf4237e176a9888e6b475780f3726e6d1fa4bce13228fa49f75b8c9108ca8c44af0d25e40ce391e6d0ceb0b60c5a84bdf18e2b1a27b9a961e6adadb1ea38877f3a2d8b985750f403c5c44e74bb4344c81db19500fc1cb79b476ae1df4237c1ed72111d77455602f173dc6e1e6978c36530fa09f74b8ca3488a9904af0a25e424c72325b1060b6c19349a77631dae689c7453abcc234a69d5715efe349a2c62af41d16b58068527d160c7551a4eb8dde20e46d0651db6d12d0a3926b96f66e0911c37705083a57432f6b5e53a4e687f17f998d7808cfac686cb6031136e6a997948d94ac9323a0979ab558525e424e39162c96938c82e66c2ed5a311d67c4af03f1f2dbe87fa9770c8c108fcc14722b57e2d03be84031428e73404d3eaf0c26656e9edf043a3592a14b11b7f9b0a805f43c7256cdff9b22b9a46630056a304dfd0aad97bcf287b89f30c24f7d0380f1b9f0bcc54adf063fb448122e4dee807e1401a6963c301723eb1c2d409cfa012e1b79c0e552d5f1c073f108100d5d9eb21070cf2843e33b0483bb2acdd127c693115cd991116697741921801c8c8868efd2a093407f876b759580bd8c9c71e258928e5013e02434578bfcf37b8d41e298a180db0e6f9bd02890d17e601f01479f2f6d55cc913c32823aac1148639cdf086f2177c8fe7c058536fa48c582c99b6edd9d8e7667d99c46193278b50551afb6cd0113886513e718d3f03480dc5ef03d690656c170e208267afde4c4f6719685819e80bdc44077a7fd37c2e873f220a48cc13715cd48e16f40624a075d4b12845f33a0eb93b12809f46aba140e51385e74250d0f55e4eee1eb3352165d5785272d42ef810c4933a2017868626041391cfe5b950b457ef61af4eee241923531a29bae99c0103e68867c79b17e931b606142e7e8fa6cdc8742081de0deeb3000727b9f9aeeda4c148504444a93f757d047bdcd0d6affb538a48ce22de322f8004a53fc6cbb2e48f476448dec40e96c0f9b90a209a131c89a47b3782e7c2f44fa2d1b8848d4feca40910e461be1854dc5a21a418d15a234b380c56dc001d0535182b929b42b1f6cb8ad1dd5ab7759d8b6e203908f6250229fd6f042f9f4e0309c4213d0645044dbd5bc4b05c960e044a3d78406454708467aaa09514ae371dc8b142e4cfd91f32f2ec7255394f9b44703d4ef1fe9c19084a0831422dca616545f0fc8b93f36434d2571c8fed5f063f1c0cb437bf0a376aa7927e31d09a6f50890bb737838749ac09ebe9bd75ad30993bfc9259bfcef6db7c9d755d2e372f218f8f341a96c404a3a10a70886f2962aa3565f874bc75b72a2867007e88a4324af5669b489e9ddaf79ae3789c0e82578f40ccf84f5cf4b01f3a9048bc6f600e5c43cc9837a22f7c0c22ca5da8fb0f3fcd335c4553987373e586e1921787c3db6d3c7c7b75b5bee7797979bfd7ff2571c2a2b1ac77362bed66f0c2bce36b9d7eb4bb3c83cb77e315ca890f196b0318d92a1bfd05c1794adb61bf13490eb52dceeb1bebd3f09e1fe153e35bc3560548d6260f686b1f5ec845793669541ca271144bc0c5464b087f5825ca44d25ba1f1246e34d8c87996c15e22e4b9a70a84d262150c84206d367782daa7f861998b4b01156adaa61d6385c6037d94a8580affd0c11e294e51429ffe0813727f6e127020514469b29676720c8a0cf25ff5918835d649205980f8c6b555b1f0e9eb5c399ad908db918a70d0fcc1005b5f5212cf6e37f282e8a457fa4e5f90d3092a71018ae568909e9398d98137516c1485856ffe098f8b8d5693c567aab7d34c1dcdb7d9b4fb4abacdc988eb7b76f364745a76d91b87ac375684afc34b91016b37f99d4d666cb157fc148f8994e3f5c2451672fee2e2e467dc0f2bfb6895e51dd31dc213959776d0b88d2bfdbce464eddf208c55fce4473d3397cf6b741f18a9ad872c79a7f3429368427b7fe51c4b8abab9fc22b1dabe3b5401da2fffe0d0a44a02988bf505ee73e51483fef73f33e532b3ad02e3f3b5025d9c8e271d259d4b533c8efa891bd92ef1d60949271badd8f3e5bcd147ddb46f27406c657aa3e213da71173548b0e0797c51c827cddc466c6f380a340c7dfac23562687f74ac7e9783df8a869a834da4c09ac08188f56c794e8534396c35ea5fc71caefdaf18da1b6b62fd69673d11b374cd7911e23f25444c822f14686143d9725bd574405847dd402469692bf4cd04c004eab6d1d9065a3d9c01687ccb4212a68e299c33822515cfed758f795ea105e06e45cf439ee091b35bb0b269ae522e1ff9cc54fc37c90035cbb910b3bcce07e8216e35be0279fa439bf998eacf9d24978938f02bf465846921756d9b85d6f870fac67eba64d0179fa37a340c472454ea1284c8dc273b9c9f14b06d4b8555c3fe9284cef2d86094687d172452cc85725f9299a3038130b18fdb173413070de1b76b9a60ac14da194443ec33ed16c8d4958eb8d38d7bb8bdcf65c06cccf8db3a975284d321dd9398fc24d19e4aacadd8ec3766390f85c6966c05fa5abe928119703bb71ab291ce4bcbb3c869462a36894c7f69fcf6002f9ba324d3ce24862a75014a63601e4da93197502f2dd284e0c8e37b724b309c6c96586742a1c7c1498677d147f976d84511cceab51e715f510be04c449fc79e41d3b675d308c22bf5c958c35aa8a9c76316b3aed5931a9023fb28530dd5cf3a4b5b22398a9b70f914e8eea824b6b951668f12347753eb0501aeebc460bc117a138c19e81f8bf0996dd2fecb05842a0aec3f5fb5de45a0c1569a2838b04ed6d50e5528ff510974337869e6b26dfb88508b7257442cedf22a464478e96f31307a4842616139f85a02aa5a70a6a9b9c1f243c6da52e00016a5b6701b707c0f9ba16e10c4767185a92380f9c8134c73d975e9b160f845ce5c7b8b63936d6b0c4784aa368da19c2b596c7b69cf75204266309162c9a10ce04e780399b7ac394cc27527f17b4ef87a51f0e86e619699d7886a0121fb891cf407681631bb039b4d331fcf18025d52a409abd7a38857ca312c864829227a78bb4d55b0375e85b0042708ab25e32fa436a81ce51ea0952ef0e477f953f158a4ef66508395de8e14f8180cc74eceb54d7500a59974064c2798a0bcc0c7b8d4648c32037dc70cc9cc00c6bd6434dad971db8440677cd74425e8c213780ca926ab0a11be48b401c88794ae8fea96121eee2108a343a50434e8d84093a352e20a3e557f9a038c092f2550ed281115cc39f8940e1886af9897979345d8ce4dd2662ea45892f059da42c7e359c488d028719020684ff3e81763a79bf240b4202c6415037030120bab99a5859fd6380dcbfb4508c92394c6420bb95c2dbb0621e48d8f1618c9b31d4519663938162cc7767aa4555429785d624ec241c58ec4d2f67981ad416090227493b5569de965ebd242006ed02bc04b80539344f4ab064782556b899ec9e4ab70414926171a739d7671da08d8f5ce1d87dd2eb46af49105ea49b821511dd0a5788702f5811c17dc18a48b7892b43488f237a1dd123127a8f235ea19b882b0add225616b88bb0b2c02d72c5826e912b26dc84805e47e841448fb4f409d4ab60dc98a6cb5fa8c6de142172b10f452583dfde8ff6e787195191576aae1adc53d6aa376d511a48a4822a1a3d1ac50636d461a521245e4c04d71cc5f5fc71d88bcf241df82a76b89598e5a70d27f891ac03f6540c37ce1d0cfcff062802b719eb63ee24ca86a3c3386c0baa6dd1ba87d9ed12260486562a38a4bf6df09e1e9143757c10d606f8c7bd5701bf02a13f5f04718121a401ee8a629357a23204ec4044c0b5b546911bc5e3e756818efe40dc516813309b65df818ff95de818da2e45d4525ad5836ec721933106122bba2fbc79599213d32293d76c4b3166bb8a7cb60f2a01ee02ea80cea6783552c8cef89f354360510b255c36db1c10d5c29de3b039225ad3349e995538d6485f451ed5539fe3d85377990278a2458a7d56102fbb9c282208525614740a518736ce3ce705c1ddd24140a15fcc838377d36fe3581f82797322f2fab8c73681bc70f3d0a9274af408b4c9ce555d56cb010323becc3fe7a54e1829559e251836fd72e55653cbed580a701f60c475157d52230970809adcf584e6f3c2562b27466b09d6bf967b3ee33922854066274f5b4ff11dab4f6b7806a8d1243fa6513b7f8ff873de6f432081cbce49e7c4c41480be2940f5c336a4610e76f26a425857341d4174a3c2f4d3446e5053bbee756fc9ccda449aac4da463c52c6d5665a21ad566d0c860ac4d4ecfca4c1aac4cd3c9da64d3f16649c7d14cc9f9d0786b93e517af4f41e7b2f66ab863fb719c05f802c67a8a830cd8d2633f15f2b04984c204e362dfe0586d19415c9bc9e9684de29c766f957b8117a7eebacce960100c6b1da0977a071e84c6f7630a115b743b5c02b98aa0104eccc7dccdeee6b31a82c51e5dfd2cd3945dfbe502e6089f445a68d9e0792e54b2936931aa7ac13059362e289736723df729c86213cc93e50729d8117bef962af8364de91994e7f6df6491fd2b02f91d695f903cf1e11e2ad5e9e57ea62894443543fdc0b572e9d93e28ce64b9e6a25aba6edcb1802677466a2ec7a9089f6aa6e29a71b2933da67dc48bc97b938a56216500a364b232f334a76461fe24c71942390c8e0f5d54ba4c75c0183d26e35160bc53a29c0f0dce37d5b7f143091a6b800453fdec7f23f0dfe2e85f1c5d02a6faebbfb35f0953bdf807d1f63fe3fd0f4eaf83a95efbbf4a45e9cd8315da62f409447df5f1673a9984a995f42be8d1c0146d77181b56b168efb7c6eb07ff87602af679faaf29d4336630d587ff1e19ff22e2ff740a53faef0ff106536be5a765228429eaf32308f48348f3b748a383292afa73a2083045cb3c9204153875fe1cde9230118c3838c154dff957fb8bc354fffc9308ff83d8fcdf45a531e9c10da17f24608a12fc8ae8fe1241f81178ec608a227fd7f3ab0add194719973f859c3fc7118f30455567a1281630651489056bda0e565d317a99cbf4aa131f81a951e220cd2064848c20b624758a1eab81db61d99161e9e0132efd4f03532ea10d04b90d492ddb8801a6561620508429f0d397e315a012a628bd68f4d79b0253c01c17669a778b4abcaa9ea0c2947f3bcf0c53b4907883dfeaa5db91f07b0d5c43b40e54430e0badc11402ef3a6d4c374a1132aa13de8d12471a17f00c2c854211f36a6a3cd2b4f55bde54bd1b5158c37debe5bbf878ffaaa0fd3070462204c305ccc3320e481b96b6ead7744b5469c8455c0317a1070737bc08c2f4fb09433fa8c6acfc02e50ba333f2f17dd8f3204a94ac0dc55a1ed7915abe812133c0caeb82d20efe12b80c602fc93e790e1960a0e463c9e172f0df68fc5a14c3ef0b663ab4d6d8a9d2c7a61a9ae53cc564d07ed7b11458d596025737b178eb86779814a8288fa4a6cff4a6b662584701bd51e3a1d116fd5c835de3ca2a55433ace9f8d728ba3d45ab8506f6b3b25c754670f93eb9a82b7b69cd25d75f77825a4ff9942b79d1c80e23a9af37a203adcd11262d1a0e45f3d082aad90f33bfdea87468f7b5468d0d0ca251a7da892fe75f8e9840aa4d4ff22c70603c87f3e1c5bfa2ca1f95bc68102fd165ed819c2d1e026a9b559fa07c7d82b14526b60bb1d778bbd999545693af7c56287e51f91f69e5fc73a8ebdb36bf41799da02f103bb430cf80c3f3ebd19e0a22494cf927ba82142c1095760ca51981fab0078a3bd511f2ad8a3387c7c710810b8a3131a02cce1a8a8966edf71614279fe54f0bcffbd828261f9c7370484ef3c415e71a981f56abbad37e65ce6dc46f993831d09d542c9f10c507bf558fff6a230bac89f567ed0b8025c685966bc6d07f775d674b78d8544d54b5f4001ae0ef8deaadd83487b642bb512db835cef28e2bfed66f8f10af34cb7e7e639887fd198e5d95537564f0c27297b056ee26b76a1465c2e77b9edc7d88a94913a36291182599db016ec6dec02c5ac1245b4b969a142e44580ab3d47f6626515e244c13aab79daa1c125019e2d9dba53a3e42c776b4c42819565a19d4e1fb02b77bf79964da85477d8b2ecf1d62efa317699230148648178cdb69b8c29c11c39ede49d06abf4e64a8e806fe23b0acb7c22405a4235a3c4c5e59c9db0417f06a844d98cbe731c45480f399f39d94462cfb5fd1a85b91f38c51faa117f232ad06f216f28baa390004c393a09d0a349f13950137ff5d0febc3d71c1c57207fe9dd5a75f06548d921f8c392c08813295c598d5acec2d97ca43a222a07489fe4fdbafbcd9018c8979d63ed6602be8bbfc38fc55146413fb7babb3e7b0528c0eaf5557d390251a03228a71638e3010c4eb3633c90e912edec48b48be23922bd4009e3f08d1e94114e8814e22d7cb0e552d2d51aacac854df30de2d4a2664e0227c70a0c677af0f83d73f584faad663a61d6c9e1ae3f6180b8c07f3a00769c02433e866c24dc9dccf8d5a33dc082163e659eb92c06bc351dde8b149e971bbc3483d8d2d1b6a050c23555e333391220285a348d37934acebda43f0d5b95c80ad6861c5e3c950433dde3ede9619d3a721ab48cf215368440360fb7a184c40865848ad5c7fd364decd82adc46204c045c908311784beae33950ee3ff6b480dd34c236a8abba0b9d6d7c7e17f73eda817f3094ae8d8268972016c5bae0335823bc4847903628c584e3f9d449040f39a4586781b2c88f7b2342cc847f0c0c56860a46269890ef02086c82b275112f564707ce393d4d790e6eed556d1a84996c558cf6a529cadd55edf5a9fc3bc55d79cca3c785f52aff354700b29f6808ac830677b9abda5f6a0eebf7677c6eabfa5711bd6c352cea80d97c013199d23bae911e064fb1a7439807c022e561c5260f3f3f6888f4b2a26b81b47467a44297f11deb7b4deef0982de1bfb7effe7a10019d3a8a87b5e7289681bcc527da6615d3df09f2d8c14e8dc8a9784c88e546a7361844615c8ce0f8ad640502fa140de0e7cba29334a31a890e760172141fd078b74c79cc776ffd3f64a711bcd1c388813e01049b19907cbfec632e889951b6ea08264b39234894f51115054f8ec8b0f2d10847e70ffb2ddb55b7f6beffe43a5a8cc7a17723f12d5d0f6a9b555bcb990b286e0f36bf1def423ef95795005a29741618507902cfcc1ea86eb80232ace4269433ccb9633ab0d03eda416b7006c1b2bebd3c85e59d30bee0d1ebd32f11378241f33bb5a91b3019c19983c482cba58c4847389f985361d84da340e780c1f6cc828ff53cb517d639ac713669f375f513c0c094723083e8b2c0ec0a6eeb18199c99dcc312f5eb53bbaac1dfa04ec47a774562bca0da1754a9b6c3b16d635d635057cd08c0463832ce2ee72dddc2f137896358b85bd98c00e1d8500a6a1487dda25f293743acceb236252e765ec7900a80ba1728604e2f4cc41e3b45fa67309cf676fec499f7b4e41d10d1a551751494eb99367d8b8c3406f34d98bd7db9b5c07aa5a3ae01cc8f767f4254764abf3aa7d692940a3f3948c7a869f81471477a1668ec8b0d3629fd1a2d377036395eacbedc691350614149b99603c2a71bc5100941fe189778aeb8b0463aff81e5837a262f6f86eacd39561265436301d36bf5046d1728608e900311bc33921015fb3f14dc269a04cd25d800e92a7067102ff2dd1e3112d1f76dc76528fa21bb8010e7c14a05529638858f814084db78a418ed04d60dd90bf959645593a343e0814e01e7eaa6591ce65a72011f4f947ff42830d5cc9c39f90377a7226ff32e4a204622a14f3ed07a0e7aa95a76eb8b61cdbd7723a00ead4c4e9279b14e5ac7f72d59eb8ee32ff4284865550b23e652958b3f6377c06e4e9649f0e4b2377da6c949b993f16d8b2584863c306a626c04acab33f6e4bf0356794aec123485653b21ddd01e137ce465593200fed868f3d0e3180086ef51ad5045d1f74c102e93c64dfd8a9a594815f1237662a7db9aa16c85a1b115d72a73a1cd2a53d676b688ef6114b096d8ffa8f74197a3c55035d21da0d2de89009a3f24b1c2926fbf413413a02296c5d5ae86538fcc9f84c7d91235e2aeafb4b4f5f411607bc6cbcb441720dafc495e6b7bb6226a0a22012630be2a134f04605ad1599553cc7f828501aa5bc31351c312c3cc6274100b1d147410c3acb3317820f980eba537a3a977a2cfd980f5d956117b2cc9cda459f9a66e802c8624ae5cedea90117d7a041d2be7239e2494ff0b10fedda05158cac2595cd28af3f70e9f13eb1de37e745ebca53ad92e90684878921062f61a4bb04cccadbe2f01080bd17eb4438f515add5968079846ca719564bd4d50588af8d4fe465a1c3cb178fa192a85b2a60c87a34a1450e99a1a04541214a2ea437b3a73d17b50967b088f728d8a6afff25e373046866babdadc421a69fbadcb6548410ab5e84bd3adad23c7fc6c0b815743628ed323bcfdc6264db0dabacee8c1f35fcb501130ef65758ffaa45fb4d9f5b4a2e86a07cc384a142174b9513de407acfa2f2ed07946a0de05083c7e060332e634345431ff65bbab6b398ac0f74c3e10a2009cf102e2035f63e0a798b4219eed733717af975a9ed91c009137e1c8c859a56ee1b0a67c149d6bec26b962e8c1ef3a9c2a6c81e2d241084182d280dc931ecd44a3c8af1bc1f84194ed50d070002f4dd6792e3d0d0e9fcae7f66f47b1ce5abec492080d15578f265bfbfa0419b5c22db2f383cde8611bb8bef2359a3e865b1f468bcfcca4fba5ab85952f3f2047bdcd8c6188d853d11c020ca088c53956dd957daa9a247fe57ef7d14c8dd1119f2e0f0fe5ac447618be9cbcf0885cde71148b800aee48214275be755d1a98e4a40d82b6e3a140c6a29058b32f4a2daf75476dcc7cd247c3186824ead9be23d93c382a9ba5beb50329c88e090f70c1f08857c338faa2f0784d0ffe15e6d1289430805e159162609b772ed8050f057de39dd16db6197a870267f6b413daa828d766e937e827e11ceb40dd2320869dbf30934b793cb5553b46382f8842caa27e1e90929e972554a3476ed0014754caef249a4f30e53d61ed84f3a01eae75308180139726a0d1113084f9dc4d1f5d2e57edaf8542183fecad8948d9c38b044cafb9479476e40eef6641b8714b40bbf3f6948e6425e6495c45d16e6eddbc405ea1945806e276aff7771215950d1ab486d8d961e9bbd72d5f7d591336400adbb862b9e6f0bc5e888fe897561f5670c6515d9476c6ebf85a9fdd1c5ce9f174149e596cb0be10b3090389f3ca9724177fac903dddf5449d0e909796d1f80b849bad90830cf3ae49f1c7d38c3fdc49ad7121416b05bb72047c088ca7cc28188624fa5788d049503df78b159e2f804be31e8b80d9f45241e6aaa69dd37d5d738fcf3a8e3bf07c243e541461599fac07c542ea3f29453545166d2284ef35339e087c33fc59bbe34d5d834a659365de4a5147625347512f755bbcfeca029a1db776755a115b28da3c395460a2954408cd7c28fcb17ec2b6c4ea2a78936e7001852015a11bd391cc6e5f9f0095bb0810a928ae4c9ea1eb40363d612dc53d199705a1ef56c85011340a55281bbf89d03b11d3f0d66c9e5e742a1a76defc96d54569c4dfa988fb8505934a5d2fd3e827ccaa62161d5dc7ee1c6ac226659764326b13572c007a200280f92fe98b48b630c30d743039066340b8fbcef2228639b1dd44a0e2023281f9ade58e42fb1ebd82077d593129116a086d102b599d1c2c4639a5c483060b821ea8a0faa6432e03f5adbead8b88e4c45ca3fa9f7570eee5f685d19fcedc5d6f0c84c4652336307f993ebc36409407a780682ee35fb823cb3579f8f9eee8771aeda989518e5b51f5e732dc7163a16308ec164d832e0470b936080d6bf2a371202eaf2288f80177a749e632a6229c970284c034a0b1eb4478c90949e9c6f251bceaf75e2b992f35fdcfc3c7f54ff649786aa89c40828772bac05f5585dafa542692ae10ff02321b8b418dfd32761785835aaef9f25396e6e55b024393f2a30a09e349bee01615edd82834d8bdb2e842e1da99f861324e9c26949bc737dfcd87f5a87b8294e93e03679bc63c8e68bf346edf417c68b2a4e0140c4db04e648422544473ba0e1b9182fbe35dda0910019ca340c57e40119226cb3653e4eb2b5531aec3080576629c133179771556d4dc402f8570c1bb38af2b610a838fad03a8b7b4e5611f1e1d4b0cfac654354eff29b12751d9c95e33394be033b899768d692f260a577ba251d8245baf52a1627fc2769275bc114b53655b433344ba44e62acf5ccc9d97933886962a88795093bf923bab98aa51dace48852d8cb086ebcac46ee5a2e781846808d83b945f5030b99d3d56a35f9497fcf29939b398bd0c99f21184ac8c3cb03a84d880273572429c71783ef29bc03aa7f2d1640a170a0d32ed659f005fc05b0d1be85e94526fbfcec849bdaecf0b823ee7d56217fb071426707ec725470f8ea75df4eb26702f30b1e9fc951e3011fe9301c7e33e4f3961e1cc8b8af09b27ecd3ec03a57c21f9a8375116fce79b2860cb0d225c6cb22a78374f484f7d6764b63d5f0796d455c5cd57bc25c8710c0a8cb80b4fd2744b23d6ee9c74851b8dfbbb141a3cb624ea6ab8cc45991b4291e9e78580d13204594b903810be642d09b853ee1702aea4a2430ca01509c842cc81a5586e52680b1992381666ec0cf0d1bf5bbc457a58cf07fb687ef30d50b1dec400bfecd01ec4e50d823e3e84545ea51d8a1a189cbb742ac0bcb5c9678fe0f5f4e562ba7fdefab45ff55ea19d0ebdd1212c1158fa158c5c8998296a2ffa5fe492509d1dc385e77898537c4fa978c6446e9833cb830600393911e86dc9685ed2437b9e6c8af4735f4b6fa792225f309d6e68a2940098dc2f35e0975c71ecd3682f4fa2a1e52a6ff0b4ac3648f12fa4bd81e1baf00776b42585214550116b01f2c3132903db38e06a6e9c67e2063ff4347d31e0165c60864a6078d798ec6d6362103c12fc50bcc9b8cd832209394e3bc5920163ca0595b97b6d69724adb4d2a71f0e3b129b9c2c9d5d264829a1d71b7e2dd13c2468092bc744186b4895f62d48c3810aa29574ecbddfe325488f3a807b37a1b853abf13b27b1e21fc17336bb1f223c395f7a716f30d8924108f69c80774430a9a11392c8d6c7adb618f6514bbc7ecccd828b10859549d1077f4d19aabc853a0042d78a37f573d67b2a1a64e59a3a30ec1a73f2f9aaf6cf19464821ffab4db1d58072f8edada1d5fb1ba21486a7c00dc2c7bb293f62855cf6d3a80685951a7dbcc6bb4825658c64b0900d1ab7047a4098c619d908d681ddda3803ad43ca437813c03677de139538a33fa852541d91199e79b542839cb761641af6f70e146b90685f9b8c204581cd4904c155256d499276b6278a7a20305c6338025066fe6ad75d73571c5a8e4aff9a8291892c7600acbeaa3afbdc7ae6adf600d53d27a613e2811bf58a5f41d2d6078415bab599af6ffbc3413bb4f24853028109c2a7d1d53edb204926c5faf47af2564f0c2667a18096e946295ab017db65cc0782740c5f68e07eeb645c602f057128ace7d507b9486a76aeab8c0b58557ca23c96c8e05f6f7599834bacd84730917a0977463f69c70cad35411feeca4debda78133835831be060cca767d1a23cc3e3e789d78c6fe8603d68169d9daf2267e2d7af6f2f082d137f3e9f42fe569aadfb44f0540aabfd262abf63e6536db6e7295d026fa7869da30ae397ce5b19d01e1887fc42b5175e97dd7c01a56021b0afd8ba3838e29e51f770ebafea84e95b4b08566c32429d96538202eb9c787ee5440aae158e84b7aac3e19419281e3c0b0f2461d1fc18c415f28d92767074cd88fe4b5ed10d35b613bcc867efa0d2eacbac5e981a3abd1b4a920e956977a6285b5fe81e8abb137fd980f645cf358e0cb20d55c47909b75cc1b6f28b0c33220e49f821b0634d1f7e6c0e8dbfc00a8af33e01666ff7e32ccefabc5c043c316fb7251e5b759850eeb0f4d6023da2d00e5cb4da91635e42649f6a684b5ab153142a808c0e33f55a008d01229a280e7b1eb19ac08600d4cb65ce12dc9d67da1f42611d44c53077dbc936e88c4cc39b0d44349ddd70d7ba381608e27a85c432742bb020a4488c76011738b87a2ca8190915d50377a6a9b9f2fc1739659811a2525e88ea182de14a670b68b1aae1a359b69f709818627cf3cb25eb1c03ab012cd6cb42662419da89e859a49102ea0a4e675c301856a50371cbfad100ab67c038f1b54ee0cea520fe05869b66b7cd37cd32d1ad0299526e0ef46e13238400ac40830760f9fdc3fdee970fbb46f299f46986430d57a1a0895605c5ad84b1641875d906d984ecbdc996724b99924c0123067a064906618d3bbc32eae4d34ff245cd75b9dc1385e8d628afcb777b2dc676faa2f96a705ed1c999fd7c8983de810e3846fb598f4498f9538708ede4ce79444a19c69d1cb4979abb772af9ae925a75221556808fe5433ff228333ef429323e9437a90f5554520d4e1fb57e9daad68f81bd4ac558bc193262784d79bc98944fbf7240a4872a20e03dfde9573ee2d09f5f8fc456c4c14172e4761b6783867d96651b76daece946d366b9cd6e9bd5365bda6cb6d9ba59ba594c686e566edd3764ebbeadb463c50ed521825e3fb7674b9745c7d73fca9fef7d8f39270775937eb8ba937e18c39da1c7f367a9dbb2aa5d9acd9bedc41d20262a30775c0d2a9bf634e46a9045577ea1cba5435c6e77317bbaf108af4a7fba983525992e66b920ee6276d3c1ace637dac56cc9a874319bf5d44642a7604273c9bc9895975fbc2a61d6ef105e95be47ff70175e953eda50e600f2c6001fca1a1d3ef429410a300ff0491d7f99ff8106f85e87cf0bf2b94c013ef697144664fb2022704d1fae481db964fb12e723cb4e61e8a8b247a2002fad2afbd3cbecc1dbfd4af7e09544e40da58ecb14e08b3bfd32bfd64ee50b3b0ff585fff1a773e79fa250bdad4a40de8d6ffdfaf5697d4a3722d295784ef61288bfd9b0c8a59239f999ad9257434cef0877823d2738f9eea856cbb1d5d8f4e5a5589d456ee8288223482104f95af76cecd56090d88dbd3fe6d31e7547e929dd3557d617b7a2071846b222087825bf5effe24d932ee47123106d548f74bc6925bda4d22074218fb0c6302a8c37d1034c822b630aae8c2e8842c39c8fd81db9c70ba4cb3d5e10ddb041d0e305d08d2f230dca2951a906e30d373f9e28afc1083418bf5f990f142858c44d738cd37c8b185732bd66ca4aa552bc9a86d50885d3b6cc64ca366dd3368dd3a0681a9765daa6699ab66926cd64d2b4ed64fa2ddbb4379d382de33015f7010123a6d5c8954a25ada471ac7916392ee3be725fb9afdab7e45ecb38d363167bafae6d5ac6d735f7df3c3b656eca327fd3e90b4ffe598d5b8c264eba8c528ba55237d36d25cdb137d9c955f7ccfd31cea46dda66e2b48de37ee334971f435e4b84e214243d6d74a313e449f28889c33cd3b4d3f61add4c76be5bbea957d9b82ccb5031ba9329a5120395752753966948b1c5493116699ca6699f90cd2dbd181737ad765babdb348edb348ee3384e8b2ded156391a6b9bcb02d42d9324ecbb8d7366d8b18e7503293c671a66cdbb62ddbb22dcbb6cd74dab253663a9d368ed3b4dfdc6257db66c4662cd27ed3366e45c6d337653256386dd32cdf6b6a224b3476a9b5eba2bfe6b6bf47338dd2126a8471d065eacbe7c165ea172550367325586b846a280b613f6b66ad5d7765df2839b6a947cf8f75e412dc5feeeeeeeeeeeeeeeeeeeeeed3eb0c3fd08f385168d6a2a2c1e27dfc968e9921e3e34a4ae563a0be3bc5983e3e0df171d061071e5ae6fb8d0b12bf197ed8f81a6fe3a3a1c1e8df8f06e3e340a4c6c79c1c9b1c9b1cea2ebf3faadc74f4ebdb1bbfd9eca5b5f1737ec962cfd9d29b4cdfd9d3a35033ac8cf7ec8c67b15e0d1b041134395e6be7d1d7b2adc5de78956d791a56f52c96c66b36decfd62461e3699496d085b1c88866df345dd287adf31f760d6efff40b7ded6badb7ebfa470bc1ab7d61d7681ff68ffd42f0d6b07c378d258808689f7141b67767d96ad4b8613b4868c886f524393ff00bb3246d4a24d6b075ab516f8bed20956d21db43b6891aa4efd92e6a19f4576c2335d8511aeca49e82b24e63bda6411bbff19711cc3ace910691584fd22ff437eb4a6c88d7bffba7f445f1a98f8ea24be5fbd060186f4bcbbc925272fdd3405774612c3ae2e216754c51348a475f5355a78c30d3329ebfd0af3c3d03eaf8e1352bc5d6e95972e9f426d39fb83967366fb51249023548ff475b473299ba372544cec4793ac68f2ea5d4832e3d7de83f973ed3faf4c17fcf6b9fd327b5ef0bbff4994a5a1007c4f4a5d7b8ad138a1ea563faa7b40c76336b7ade60498c5cea72e4d2caa6af9bc9a776b5ce984ca6afa65a6b759bda4f72dcda4f6a3fa9fde4d69f2cd34f984735fdc60539994cd99f3e33994cb67611787369fdd06b28eda3cef4da87a7d7ba0e6ad0b47de58298be162a7d0f3548bbbbbbbbbbbbbb29e7a383f4972af6fe51857d40c0eb7ffa598f7852940603dcaa35f459b8556de8af70abded04771abbe5a9855e3e97bcca2f1f4531db3504f396e552338f4336e551cfa935bf5489c4f7f6356f7f42bb3eadb78fbcd05a192badcc9a12fa9f4e7b8674e218567564185675e6185676eb59e7986199e19000078e60004e09905208067eed1e399ef7d6697eb9907308067a681061fcfeca38667aec18667b6e1997ffcb881004070003fec9bc333dff0cc04786620cf8c03f839fc8bd54af6c667b6f418f627bbfd8a3dbdc9ae7c0c6b9a61532acbf22d56f5356a586be373b061c4398263e47563535369a62445413a322a221a120a02fa69c283273c7fa2e791cea28b3b988fbf10c5a13bbdacc69d89d320117964fb808077fb2daba76ecbb05931ec75eab60c9b589d7362d367fa80a0673ba813875fe63c8265301e108a3ba70e3c25a60c5a10e9c499473a46856158f6f29cefdb893b2a7cd835dc875d44cbd088346530a4f81375c0acf91107dca2371186592fb5f3329ac2830f3e86e4322de437b1358d44980ee9ce6723f406e371196eecc5f39ed77594a7d6aef3bcf025916f39581c2c104b007b83fd616db061adb9f36bb03e2c0d7600d665afed61056003600160433a74e7cf605b7605ab824dc186b409e5017de2ce6fd01261c18672893b5f870d250eeefc950d6513777e0b36944edcf930369451dcf93b6c289bdcf92ed8504a71e7bf604319843b1f061b4a2aee7c1e36061b4a2beefc181bca2777be8c0de51577fe8c0da51677be0c36945cdcf92c1b4a26dcf9396c28bf08a594508211ce0bdcf96fc359c49defd303777e917f79dfc6cbb8e375dd8eb5f54e23b185bd8cdcf9d303dca23f3f20b861966604b728105007eebc5104b76850d006eefc397f5e806706eeccc0e0161d1a0a630ab33029dca24444504862d6f605b768519113a230ebc4046e5123232feee4825bf4e8680947cc3269c12d8a84b4c59d2f8d9815e3e7cb2b661677be2c6296cacf974fb8459322cc4c82c2ac9415dca253aae056a5a1825bb52608dcaa36337e7a3f5f06318be5a7eaa72081c608b786083653dcf9520a6ed59b26dcaaaf08138513dcaa382d3f7b9845e3e74b1e66c9f81b27af1f5c233d90505c1b3f5f2ad104a35a1127c2cc9d25b8455f11662e61566d4d9c08335feee04a1ddcc9c49dafd1b02bbc0c4b03f5d5766fc3d6d7eccbdfb01a0b2ce8d0b15ab5d0020ccc8e1d2eb8f0c20b30c0c083470c31c4c4c8c8ccccc820032b870b8eef5be15b6c0d16ab7acfb2fc0ceb7dcace30d918bf624d7fb22bbfd9d363762bd9ec6d58f9376ce9e30efd499f4e201658c05e3f3a74fc00ad5640412db4102404032334b463c710910b2e1015bdf04291110c30181df1e0718414430c48516262a224c9c84c99f99929777ea591e165a861d9e4b87179e1f89e5593c3c6e5e6e2787d5fedfffc8873c3c6cf9f38b16571fe6f3c8e8d97bc61c3ecd27f1b7297beb59f0d572e7d1cd6c586352efd1c36b4973ecb86392e7d19ec8c0d5797be8c0d5db8f4636c18c3a51f830d65b8f479d81000973e0c3664272efd176cc8515cfa2ed8909b5cfa3b6cc8525cfa30f45bb0215371e9af6ca8c3866cc5a5cf820df9c9a5b4b39557f451326c6753bca2bf82f57845bf866de115fd16fbbca2afb22ebca2cf62415ed1f7ac0e5ed19f6177f098e115fd1876065ed137d91ebca2bf6269f88103afe86356871d7845bf6479e88157f45fac0fbca22c2577835f57a2e934b8a4c1b9037e4c0a3f3ff2304b859f1f9d601667021e01c768b1e5432f1adc6983045715f8d0eb4efa1ad5a4fc42292995a055c92fbd7c156c0ad9a6da8ceef42259e353762211f67219a9233ff9d9679f655c0ef2b71dee06f9db977df645222671c9d6fd7c5903669d7ecad366e3c5a4947f929ea3fa70d47442c80c874d175b7c4b4f9f48cb6810196f26c4b695a200993606ed658f25f37c8c5c98cb3d51905caddb342a698cb2c300df7a2c281a59a685a43b01df7a2c9c798ecb8065da08fc321f070e954a45e5869ea3ddb0359d7e99cfe14cc7e3f900a9fc8948cc88634cee7cfa386e3afabaccda3a26ca21c9a46586f097f926d47b41ddcfc76f769de77d3174c1cb3d5f2cb91a377394a6b0080e7da9de6f581c39647ca842f85e0ca933947d28a7dc194a30ee7c09e432eef2df8792e85fc7797ce2f4cbfc960fbd1a1f765227c717d68ae30bbbef0b3bfb85de7f37beaf3ec501ddc6651a27c7a6e5b389452ffa729988037e99affa42d0e6cea7f1853fa342151f04afc99d1fe3e5c48b09afe66bf3274e1377fe9c3c3577c68913274e2c0ac38aa7c28be2ce0f1f7b6e0c3b892b891f3fb3918749dc69307e13b94c0c01bfc4cf2a2ac6f8ea9f28afbb4bec1322aa80dcec9356b5d1af0ab50cbbb2af1a4fdcb961e449d55aeb0c0d7af581998d388cd7b9205eab971e8b38545ec2e571dd09377e58398c1b3fece2066efcd08300bfc4a71f0f0dfa374da59f9c8f0834b835184b567583f6c517afa24a26c3b861cc7834e8ee59fdeaeed53b5efdd031aa1bea9d61bcdf830f40c4306ebf4b835bfc973a1da32afd4b5562231207cc8a4df02a7ed47ed3beadc1b02f9624c2c48f4c628c31cb6292d88aaf08137feb2437fe4f34bd0e37fb4cd602455c9c7cce66807b0b147171f1155b52dceca393be617cf1cfcdbe210c748508e117090a70cfd775e03eee339b5580e63ae5325084c6554411573771234fa22b6d0f1dc3503041ea41127991ad7328c9228b2c8eb2c8e2288b232eb8b8428b370d4483f3dd86656c93fc1eed577e0d1f1f496e1310bc9af2fb8157335d793a1f6f82ddf308a1ddd433f7f09789ea7c8830f3575aa7e7ca5743d9d6e1d59220217a35dfaa650d4bf1ccececd001e74ca552dba3507f42fd09f527faa7d4cbd4cb5437832e95d25aa7757450ef6d94fab0b5b8f3bb28b57141527f6aa293c60149a5521faa3da9254dbf7ae3ba6db33eb63fc95a3d27510a86f9018a480da21ef59a8c4d760c2aa262924c777713e1a0e9c24642c40d9e6ad7f9a056ac5f8d8525c66b9d922e8c3ed187572b5bd78de4a863622081ffeec3e3d53bf7fc5f0ee2ac441f268a4ea21431a85fe61f7945432b3adf75ddc51f786f498d444a12b3ba70e553bfa156b646824a4225a192501f6611f59d655ea15442c88bfa204e17857afe64ea2bea5342ea974afde94ba5dc63ccd4770aa391cdf28c4ff4711e1df709bdc9457dfc191704f5271e663110d4271f2544bca8afeaf4e94f5fc84632524b9a8888888759f4ce7724efa93f5dca6b711e956db33e50af534fafa5b6cdc6db4af06a7ed7806176749cde6a835f207885c2bc4d211117f5491919cc427df7326ce497d3d63d0b0b0a75fa30bb11755a9d4e1fc4e99e3eda3a3c3e40f7d542d14849067743f733362fea216dc676b242d48bface65dc3bef26ca5f7ee336460d1ef54b6cf5939a3b679486c249b873e56b222d3abeccc298c56ed4325d913a4ff268a44de73c1ecaf21dea1f699d470949a84f3d770cea4b52b1adb36407bba71f346198f93de63713ef5dbc1621eed3e0ecf93e30ba19a33a3d11d931aad317e30b7be84ef7e998d601a25fe6c79b7e993fb90829fa18d1d1d15909f272bd4b42178162102ee582a45efac49f08947af92b2b5f2864e525102f146609d1ddd3cfef27dc3afd54f9f96ed331318c5aa675889885faf95dd431ddebb48e4774a7d0d4949532b6153b6fb7f253c7d366772bbff22bbff22178b594759e06e707462704fbecfd13922222816eb3f1c6291d533f55fdede7c7a48e51d56fe52322bde7731e1530860e94058257f3a9d5e2ced6711e978946daa3699da4249d126f3ecf8c6932bdbc26eefb8157f3b778630fb34a5f0dd9278178a9a46147631777efa5a70024fa4dfdb68fec7e75777777777777777777777777f757a3dbc65fe66c24773a55d160f166c85849a9c440752713b769a50cb375e382c4ee96b7d1e296887fcbb78dcb74ed726a48eaee8eb963d43de9d56ae36b0dcd967edbe8b7d4dae56c2a6b43d62451c3735c49cbd49e8ec912ef8b3b7bbab648d9f35fe83e2d1f76cf7f217835cfc67ba3e5392e88f7f2370e88f7d27bcfab36543458fc08927e992f994589c4dbe2598fed6ef26fb1f5caf651d1b00dd4419e6d21db434dd4456dd4478dd4516c27f514a7f11ab7717fb911ebd6fa114ff275cf169d7ff7c89b8eb1f11b4618e45018dcf96d7d68f087291f65cefe322e87ee69d0c773ba4767d6e052faed39f35d0903a1947e363322e9c6304cbe5adae3d1020b2cb4f0bcdebc6ef3af22b9fd5aec64d05940328b6fdc6198f92e5dcb8bd61d3b3c4244f4c5a044425f988f18c02dfa448499f47d5000b7280f7880844bbf0766dd78fa09e0166dd2c408973e0f0860225cfa3b302b7bfa06a0af430edca241414e2e7d1c98b53dfd1bb84585e8ff60d6e9e9fbe01edc95a77f198a4bbfc703400097fe0ccc8af1f465685dfa33cc5279fa31c85cfa3c98957afa2ec070e9ef60d68c283097fe8a5b344a120b97fe0bb76812fd1cdca253e85b6e551a1dccf2589ebe0bb354df5273e3d2af11273841b461e2041f90b14406c44da149e198e076c0f5a820a1929345819b428612329650c951c9d1a46440dc14dc141c131c138e890a129a144d0ac74493a2497177cfc19c42a6c2e655aa20a18284bc75fb2e0763615b414e4d0a33f34b4a1d9b8c25ba1839a28e50c9e9e287db7569c3dac2aa615a883b78c2f08e66d9404399c0dd7d02fe2baf2ebe63ce7d034c62920952d626a840ca8f35d099bae8c2bffddfdf7f83599cb1208644c4be7d018d5da2952fbbdb22e59a07f68743309dfb5a8c5a68a994ca758fceefecceeffc429a590a617f65940a594995bc52aa6db57671c4c6b1a0b258a79d6faa14ebd9857b6a48cae7745781bbbbfb279330a3bc2b4256da5b56d86319fafd3b5c867e2e9f4bb4572ec05eff1e8d6153daa053991853a9f241bf6bf36830ce7011e5861d0f23495b80a0f3cb3d494646e8e6e59e2428b5055d5fee497a422d386d8ab74bc30f1cb698b323e927898adb5f3baff32ef7242db95b377b7400448927b0b802c708e7082a7ce273fc1adff361c291c86ae4c1e183ce7319e2729f4641a3016c0a07a67b6184cb16d4bb510946788f1f3434183f220e78efd5128451c58d1f3f21441a1a1a9a0db8da252fbb5cb6901ae8c228c1bdfdb59372ae8ccb3d61d0dcec724f184db2a45e41145c45acc22573e373fc1adfeb016f6455e182268af083274db0c2933826aaa04942090aa2100507441ab880c755c4e5b6884b6b0de0b858b0848d4b933939e07169130c1c9786d9e08809367473db4a4880e4d2ea921110ed649bdba8c40e914b2b2581e4d2b4cd6d8c41122497b64519600088bb400d725ca6cded0b374c905cda69731ba120310221575b150d5c5a57821d9786dadcf21348c8018fabad0a8a4b8bb1b98d2e2882c6a5a96c6e2312453eaef8022410b9b495cd6de471428e4b93b1b98d492881c6a5cd58020837341e0d1a97a6eac8aa9f9f1cd0b8341625a020041e1695ca894be584c6a5b574e49626433747483d485c1a8d2549a0e0a37272a4ad2a7e8b11ae1623685c6e55f1a37169353a720d6992bfe2841a97db08851348f082c6a5a9e213903c8128090eaf549b5bd50e5a12aa1b8a6efc5421b8f1695cd2247fe6033f3de07169b1075a0c21cd23374eb7d1065d4c01e46aab72e2d26a6c6ee3103e80c4a5d9d8dcc6245cf8b8b41b9bdbc985133c2eed9b670717c8b1f103509aecec58d23de10946aeb62a18b8341c9b5b22e25011685c2e9b5b2298074940392effb644b418e1f28f3b867073c1f1d9c78103070e1c537ac71cbcc3e4435b18a171b56557914844b7f681e92ee5f4e9ee52ba942ea54be9eeee729352c494524a39a794524a29a594524a29a594524ad9794d3892d4f574efb6bfa5c4989358d62dbb5bb66cd95dbbb6ecae5d5b76d7ae2dbb6bd79652c62143b61edd5956cab212f6dedd2f9faf3be6d225ed9ae4ca9a73e576faa2144b4d3897f8a1cb8be3e4d13c7a07bfc0a37938e926f48d773701abb199f335e7f47ec57ed1404a17256fccdcdcdc1ce43b78f0685c0d5bdb2f1cd25f4be91d5c3033b3e4366aa3c6a28b1a8b366aa3aca46d1cb778dea6993ad5755ae92431efe208cce7275febba3802a595e808b8c5f3b04e759dcf482546e57cb77756e0facfebd12505526611db27b7781ed6a9ae63712c7dd860bff4e6b6dac82bf9d436afe463563ae8f2eae26793a53733c7ccc4d80686e898ea79f487e818ecbde80d21597034d3cd8033fd229f5fe3ea457b271f0de117f921eac608c58e90cfd0a07cd9034f9fe19524e202c12bf98312ba70e6ca283d021058e101dc02e31066612fbb4efe8cf3aca06cb9c2951978e212318cb1971d73c17fcfeb2e16b1e7ef7ae0150f455db8dd0cfbf8b126dac49b6f000701d132333f30805b3b4418ec31ec3b0f9bf91d503762ce8a1ff2102e26bbf932cb98a852d920363fced7c442c88aa0201704797d9a75920663ff341823f7e18be9437b4f1fb2c4ebfe276bb29ced88d08511e9ea609e57fa3ea993d5af87fcaa2049bff055819206dd39f4219fe242a5275d164a235286fbf8cea4637c09a59e73637c89533f963ebe3cd2314898b5026669ff9a0de254b2e1f68551da549b0663f6ba719d25fd6223c3b9900ff58b8e9436f57b09314bfbf85e85d7bff11e0bea7b6532a82312dccba08ea942dc2724a7c1ae059d3fdf5026b9f10ba30b65129ad59d8ec998d48f724983510e354844c6a18e42bc8a42682fe91821fe3abed3314cbc090fe2847da5cfe42e93341873a49206e37743e8426924944b7294f814d950f825fe16d58b0e0a4219882f9774cc3e0e491defa7eb89411d23c4713ebc14978aa884acd8a98ad62945220040000000b31500003014080643a2d16090c67130b90714800d76a64462481d4ac45912e42008828c41c6184008000018428c99119aaa0100b17a19c30de784ce1de75c2aae63595034f96b9a98db8a37f70e589ee884ce1d16416f5b86cdd6e92d3ea389047734990b47ae65e677ab79623ca6bb157fa30299d913fa3198d0378812c9af7e5f21f5e5d8b6f293ba1e3363e3e5df849a1d4f622b6950ee41b30e76e1b42ce9d0c4f00cc85d94a9b426f7e622700547c4bbf597d074bcc91079e16f9fd5fe847556c4d8038fb7f2a59fe45258210f853285d27116e84e782f30fd042824b463acf2beee87bfd2b43677dcffccc401472f3f39e0d59f36e1485bceac5d4fd373bc2a69d53e419e958a8bff08326a376b206e71f976395d4004f41ba4cc8433277f7aa5629ac1b9371ab766bb149d9c3b2f19889b8c03b5e838105dfb1af83a3b5cc7e084c9e7d40d5127a8aab10d04b945ef3783a65ceec77504642e3446ac97e37dfbe593db736eaaf5b40c6f3821f3b610ab0bc4a17c6fb32ea8d10981e4fb35ad78c0afb8b3c8d4275a5e58676b406838479c2e422949cd5e85fd14754c2c266b1af3d96979742e0a1f854682318b55e15b7b1519f0420a31e46f8848298373fad5cf434090b3980489e0bddc8db88e03da028591c20ec58cea3673d97861ac06e813f8fccec9166e8fe0ebba982c443674f4fa4248e139ab1f8329a2204ad5545800c864bc7c525a8e4a38232206d16597641ee54af6476842875feca01a83de7a43337abb34c0b01bf3419a3102c12bcd441217af20af7808fc12a599586eab9864a96953d7bd53f63e9526c91fbaa0ab31c39a4923b51dba8c4028914b532d2630619f2ef01bde4262c3c3431e8ce20827165d5e7e6235d66085afccb53967806bfd6de9ebbcca7d56786cf459454044bf75fb3f405dfb9c5e949d511873ec286c75f795bee90f6634e6b7e97f911cb040c8aa781121b13b7075a1f6ed81627b6c1387aa0e58c8fe684a15f6e7053b58b5293714adc2b7ac46a0f15c526dd15e1a84c6a2bd7118dc6bb7b52b09b7eabb30b574fa4cc518a4bf1e3e8fe0dbedc6ef107841946b250429319b7c4de4810b16c412222189b5cbed97769a83d24723658c1463b4f69ab7b96ad02f18f0dd5cd9ec209ba4dabda98e19bd46d25302d5db7c7e705202c6207ad7fbd5d9e2c85e0e472608a0673d0b53e78117278b427a8ff253e1ed459257501899a98252d6d42d60dafc7fc73c5b01181220003f6be26a86fb2b5ad1d0e8aea2ed1e3abdd69bc4172a8add71139fdb43ef9196df8c22be5a911aa21fd458d92e99cd454a845ac1aff452a0254a74bd54758e48152a3c2e43805f58632823a4899ed904195fbbc189155ca3ef1a26acde39c391d65a29814fc1a22a6194892b26f91440d0cae745a6c5ef379dbed54f522eaa1b268409f916dc483536ce6741bb99c749591d3067c34c58dc37048a4d5132c2fe67196ebb10c34bb94cc6d52b4f4adc72f0ad24383fb11a3f7b1ae8251c48aaa12f4d464610d266f3bf898bb678e4dbbde90d553c7338a98f59c8d5bda5525679ea98ec63ab4e8fe8bece513979c9d03743499e54df2e2fa9ac60147ecd6b69c3c4fd067450dbb9fa8a829c44cb752ab0c8897f79ed09bebd37b158bd2090f666fe3ddb7964058fb2276a4ef460818d27059371c42171689bbee56041a2735679d0c6e98cd917ae8013988c0a422c093380ab0bb26f92424b0eb3e4581915be054341c532ebb60237f3e1bbc23dbff0c6c8879bfd140c91d48b110d9e928a1615095c9783c43c5bb7e25b1b24dadee994a509effb2c1e01e09d92272ae2360978a8ac9692478e0d3f02c50a7865fb11bc993ef38797879178fd20e45e2a32f4db5c7ce3dcbe816fe30291db940b57bea347dc8136280851444dee0eeeb658087494caf7e8d1c205d5db4eb7e7b19ce7901f203e886346e043b83640b0107646751ff2af6d1f8ff91e2c183d85f641216ae7b2d1b7191e098d1b184cfe9343182a383cc9a73bb7ef3f8544428c80e9a2ea70be82f70a87c37ebc0ed27adbfb08ed02a16fa874a17c9f49022bd3c9f02fc292819eb66718911df86fa31aea69a4121179cc7b5f3d2ca36c2a2a0bf8aa783ec21aa904c1c845a372267cbdb0605e10d6d5352d624401996ecaa1723d21862dd08efc21339939227a64e31cc1d6b6e7139fb83b260748ca8d21bc7c771165e580942913c31be79d6ac849bb8c90e9658c937539ac11d29beeab794ebaf67dfc9064090022d602ec2c085bd52a3bf4d44132fc15b2e382d85fdbfce65b06fb18b892de73918d6cff9e33e6cf35b410801ad0d5f2bca9d5005e51cf40a20c19111751734d17d312efa30c49bd258264ca4442f071c256f444129174a1040f81eae3c8a3c9d5a3a146e47de4cbecd44304c7ddd82132365c0e63cdec92b3d4870bc5f8de40fb1844f50de9d3a1f21088155eafbd481b3a99a83d7cbdd7c22bdce840bb654a0fb088ff07c4feef29f431e3677a2807c4787ccfabfdd20a6a8fe2c94053fec028c5590fd6c8757832e68d54e9067578f0be8beeaccc4069524ffc960cc9374cff32f3902a36c6d3efe9aee47cc93edb1dc653edebf14cff3f30b579d508b7f050b4b28532d18f793da68f4b81a5f72d6363b0a1a75b1b2fc7a1b863c2ad163b7042d04223ecc101162c3561ba6760febd8d566b4aaf3a5ae104a3ee9801ccd476e31df2259f0b81d06fb0f0d2795b7e853ee1f43a158b272ecf00edc13b0f75b4dff8b357e3858a127d0e7a6d09ff27c94328236ba042078661116e74d2680798b1e07289c6c92003d94e9a6287ae5f8fd5d3be1b0bb57d659879b382800c64127777884a6121a5a3531bc1de1c6184459874f0112847ef9f48592ebc61e873139cfdc3b64b1c342de09b81473f2c8090687cdcac5e1fc6eae09124dac924eb38601a49357ddf35bb05e8767c9dc8696ca750015848e67887e187d0405d6f07e99361af77c9d3e584082adca77ba8c0235f6b68cb0771f907fc95679957219ab6fc18a6aa0128ef60ef7ba114368ae10819bc8454cc1d40e936cf8677744f1301b103e3506221e5703134f2326c239b0f2127c76632612e0a386c17198a39d636ba7c015f7716ece21234fce12c2330a03787362e7425d3e64d1e2a2c61f2ff388c4e7b89b8e6f8d7cfbc9473a3a0422e3a2472f08248234bc510d09a3c96d8208d226a9e6b942d7ca1f637b66b9e21d1e452f56505793c677039c0f5b6b95d49468105ae685ec4af97afd267b249785eca4d3c58cbb5b77732b91c437070faa6a5acbd4e70f8efb2b41b4fc4ad8981fb318f84912cb25d01119b348d6747ca6a01f46bc202c9c29fdd9d38adf2eccb452adc51d9587bad22185559808665d15743fa94aca21a9a10a51df2d37d07fe21b521c83e38a387c5cbee9b186be985c4bcd3744e52bdef7d9c6cac40df877fd215876265dda81b6c6796fd242ffec0067262693677c1e5cf766fcdf71b4dbc0c6eeab2495d31ecafa1e45ca63c480ed85f6924522c31882bf31bfa90636a0c134b1ee7ffd24092bd41ac6f2b91e88903b0fc6c84aa600bb92d1c8654d4ff90b8bc92413b3fd20689f6d7805fa1138661ceab323bc887f914ead25ce5517637ec76e9f170875f09048b67ad1dff897f8d2d11a4f12143e52732e3c2d4871d30f9d38cdac0d3ddb4f6697df893f7609d79901e1ac168ac112e781e1ec0d771628d9307519cd30feea178331cc10bb6621cd3142520cc35455102ebc9eb86ce0b129fab9fdfbf6455af62acb336b129d7e2c771be671b87567640a196dbbeedba5d988d7699e2fd20d32bedfc7f88868178644ad073092a8ff1a4a6c26ca4932ee4bb4fce4aa58d74b8edbe4917f1149543abf13579d1f0d81907e07e19472c599cebe4fdd9364cb2f049413c029cf716966ab235e9268ed5861d30d4a2ecf114cae0ec96e66e69bbc2d8775ebc3bbb95ec70db7f9303eb968fc6c34ef65850a1948fad81e14ff808eb5fab734d3f899f959f9908f245c19c29b428403d9042220ee96b71148f48f3c1f13de49a7742a986d9efa0fd9bf52b0c264c9e6c9ea134708888e1c5024b3c19d4673f53219569415367cb56f33149b762f9683cec64d655f81a86526f4fc42c74917cc60498156d24c592b40b8188ee53f43fa375f3cccbb5e6b43dc131527394a665c8fffc7de90aa0441c5b9f3122551ed8d1442de618d2c3d2a798090476c180a33b0368eaa70fb2cc722f6d4c923ad26227b65e99cfcdf59613b22b44dc5e8718b9d9f814d1ff07eb4adc050b887ecafada8cf64fa28cdb79ac0bcb0d2e25b47086428b578b85d9eeb4aba190823fcbd46f28800923e6c2d0462b87c23630f9b3056cf41c42087e1ba12cadc3bc3ea98e2e3b87329ae02de30111325aca0fb7dcac393149ef3462bd4a8dbbbcfce28fde44e83f1c01d043f4d24adb1b19471501d05e5e162d3258068241c205293de40b351e3ce20bd764734c02199ba2833e4d498640b1377af13e4910569d41214bbc4e29e394a1289b6e6fad1e1443c2cca08471c5ded05c999765dc632fbc7c5cab4af63391cdb7404112b2564b8b92fc66af735192051cccc859b04caf2cc22c66f75238f9006e41c1a7e7911850865f28e9ef51460edabec74f1fdd45b4c4603bb0bed050b36ed68650fab98e9da810ab608677542c129dabe490026a52f25e8e17fa047e60bda8d7d9ee47717993e0e6c06add125dcb2830257b34eb53d2b9ee3a337e082f60653cdbd9431a89bf71c783eedff2cf95800607c563c5c878c46896b2e297d4aaeaa62b0d34982585b31600cce12dde18bb8dea6e831ccecc88046f914573d8e412cd7967bc2ceb74c2b910fa7c209aae293b1da3bd7963ad15919d7f52dd47ac9391bfd7b98757aad2f2ed530533ebbe4a1572e8359b389d3cf5cbd433eae4c2db84519f3d635a944c016d433770095217f13c299b041899fdd149bc1d7aad51eab334d0ced2ede1047dd62432209330f511be0dabab4077a68171d05506f5299da5758a964e2b08c3d00d28fea0756a2645cdb46f7aa0f406b237f1817e2f1435fd45142b85579f3ecef4b4fad9d3a09eee4a19156c67068da9313d154f312f3f4f6e546901b835850618a8f67e3f67dc69f5d7166e996ee8bd12aae6f5856a780f5b71efd0a185f18acd6eac330f69ade13f9e2d747f4ad1b9ca293a995bf4eb62548d51567a76d4b18170528e0cab7ec49d6961b0e4ec7dc4479c5480173d98395479283499cd2a82311f4ea030e875f3374ac26f5c920ced5fe71c7fa3733d850a4e866a2980a83c048eea322ba56993aa773f026bbfe2fc21d397a87a98770a5040a3c0835a614af460aab3dc230a2d42bc8c81ec353d1bf9e81f52a9f10668035d7701036be2ee3baa948d151d41dc25c01bfb9a991e95d592e95331851e74bbf8c06750274db5f7265eed723493e8fec664c31949235c99d12f352d8cdf03f825da02d8fe22f040c459bc17898cd782dc5b3e16222f55b7ad2ca7d53dc03588c636532a0f5a45cfdc689eab84771cf50e6ba96b603785b554045547eb6cd7e11a231e9324d47fc4e0eed3728e1c7c692f0497e65a8ae80de2d0e8b545c3f02265c8972ae5f0ca143c804be25b1cd9d48f8ee9db40bf04d666712ab06338f08a9f58ae660a74331f9e690001d1c49ab1374b02544870c3230a4659b9e5ec12186b8fca88995eaaae73943491325b8b8592988fb86846520559d32aa53abd28e9a502c58be212a9b9eb3b4a4720fef4be0f9bf6fc26212e9c7cee75608818a0fdd6b57794594c8e4a089b2190248d49d03d44fc5ab20143474992b40fab915d3ef545e5739a421ed9296698d4533165c658e295061af1d0407f6391a47a095e23706c16bf3242b68ec701061a322cc34fc57c099924ac6620c08d2b684a663337965aed8a19b87873c0b072ced7ea0bda2e117114c63b8e46be326b119f3baa22f806da686292861cd09bc47010aba6e1ab0a996a280c409daab82857c7e6e5c34ba5cdfb465d376a7617d227b01ff4272938336e81040aab318f06daba685b494a221595b3686245d5be85c264a35b8556312c84a08f1b8bb0615f5901e9d72263efee40cded591b9c803a69fd37f09c654f834ae455ea1460baf50e55a7650443f7d6ba52d20e372a505ab59474fada53a912a87ff66a161a3ec965ed84b84c64cc0b155724e61e24b92dec1e13ae091129a186e82964a3bdc78ad4f4ca60498368bfb8a80054c87dde7c618b0ce768ba43e3a62ac3bb08bdcb67cb4ecf1faa0a84449b1051315ebc11ad19af8b736bbcaeec17b17eb365d05b47de44334977b2666d6190f2e0eeb479fc393bfd54baf8eddbf04f13b61022ddf0b65f33b111b16e76ae9e2167792314cc99de293fb40feb874ac1f41987113260b65ea88e72038f6be8059430cd7ee6a5d7005d4f1ed37f482c25ebcc4fc7a2c4d57a04b5573053f8a123413b7d499f9a768b1a040b3c43901725e41613eb3f289646876ad293dd69eb568acff2b8798e1a337e99a6ded8ccb21c6a7c248e0942281b76ad2acb17bb30a0b353b3313e176c15cb5b619eb7ca345cdeb940e5e25902d0f3f70a5e3f3aa2a892bb3a0a2d11df62dfcf22d60fad60fa563f579ada11c7285fcf71e9f4990c0668bce3d8ce1074613a9b5a73f8e76cd856b5a8bb72dd84c84675fcb9aa9162d411886b9e8945e5475cff954e94fa19573dccbe826ac05c1457cde23c68e96486298ecf845bf890571cc7b9081ff03f962ea9a9767937ec7cc75c478de7b76cb27d2fef2cfe06a077522ecafd42c1122a57997e2690612bad232342a51c2e52ffec91ffd3b53b1c7be433d0835922cd098f9cd0f0e03d0e1cd9578b17035f50689816c0e1a79a93cdefc622ab5d9742d0c940fe8218dff8110ac27e3d3fb311e5aba98ce88bea9b526e7613d74ea796368df4116df4475b52bff529841f660934776853ca5f85210822392af9db46bf78f5d331fd5fd35f52fa2ad37b5cfa07d1cbdce14ba1d758d363f9e7801ebe63eb70a8fac28343d8e310327265056e8b6776869cef04a8a6a8870cbc7e2769a8bc3c055571cb943ff762a0d00fa98fb8767a793bd24db2a18ba3842dbcff10d7a126ceaddfafd0a964fc8bc0f9c4ed3e875e826282f1a53892ce701427d19ae982e1dc249eafa909d730c8cee095691e35cd1ada716b9defdea7e84d69e61dec22fb446b54d88c88c241bc5e7d17f966ea9564d7dd9ec07d58df73fde4b25456238fbdeab72226ff7e5aa17765554643d465653345188edc633c740f829bd68419aa3674066c003982d81d1c68d67e9c6dc4cdee2eee375ef30710856cd53b609165918a4df236cd0d349a604595a0a24414c0efacea0263dea96a871a4fafdb537ce8d0cce03392eb32c5c4cc821bebee61332c8b455a9198390054292d057476ad37f042ccf5baf9f202191a51994af3000025d9c69bf8c892656f19855fb1e26f824a56211fbddc258f2363450e02d5f1072b200e3b7eeac8432a6948dd15915f32c484af529b37ee2ffd394540e6abdc54df7f17180a4c06625f448ba3017fde33006196deda9901f347e29649ea460b5e95b73e8d458d4adc40691c89ed61f7c83b7898b6c839f46192b53260770f1c8c81eaba83015b8f7050d23d0758da16645d040425be221b8bd1fe5987f2f21e90fc0e09b5447d88578b56e22d9b0197b5c23a832326690b202a62e9f059ba53662584270ac19e8d6bbd1934693ad93904b1650951f40da2871ac4f9deecbf2dfe724219f456da6fd450b980e955581c759fd2c8489c9aeb777d8cc4565f1163fa61451daba5741bd6e27381645b483f43cc4a18af847519e34f6808d3cb9d2f59fa6001335eac689dc895821cfbd4c04e5e2cb4cae493038edd7a42e24cd88e6c8447b2f7eec9d3163c7cc5b6c4d29308dc5c491488ea1aa9c89fd0e7b50eae4fdb101079eac14f69932c30a8dd96494bf58c866978c0de125f4d489cc016d13e67d293e6f2f2136865203a6e4a1c2b445d5ee634b0a7e31c945a3e4d465a1917e00b26498e41d34c3e135cc31b357ae1806b766b6fd2988e78570f4080690cc253b2d535f2a00ca2d2d1d7bb708524d2c3233e7e83960c91f92b0411348d431438a4335fa478c85ee5eaf85532bee58f18f3273e4a0655cca75d870f27844da42a363ebd9f2ca2e864e602a07a2086fdbb63412a1119d6e095a3ba9e83a1b435d928394d5fe00875b1bd844ccfb1ac97b3082a3d1e3b88a674a2c72635fb86569a55cd5bfde48194b295fa18aa1b96af086af0e084dadc01f726ea788ae740fae038166194fd38b86874f98bc60f0f4fc16ab8c29011301d0e92d1557028b5070fbb34fb9858b258b09b48fb77c4034dd0ca64972a309813d2fba5b21cc3dcf79e8d24e63e7713ec1d3de7973fbf2e924e94ae92b88c5491019131336ae062fbd00c82942a91b267735c6a95c70fc9ce7cc05e31e0e264a04df4ffef6974e64f4fed4561f9938354876db318fb306411669f4ee9dac474132da0ced009c4dfdb07383c2f1e73cfdd09738dca0adff0acb1a3e79e1cc1880b57028df28548c03a1e23fa91ecba064644fd41da6d3b06e16a8abe0dd19918b93e8d3ee049d664b9eccd9331d83386bcdc5224f02ffe11e0f8491509cd49c088f34c3652ea369ec1e31899a1a53d9339e849dab9145a2dbac0e01911295ac3568b76fafa931061c9921f5ce104eb0662ef8f3948e92e604c51d3e378bde4298ac5b7261fda6e8f917940f80853be1e68761267df7f99abff0f4ad1a8c9407a9a4e792d789b0b948dc5289e0b6bee2c785729524c4ab591cb3ec1c7018c1084781af33872b4b1d632b2b872a27f2e947201fe3cdcf0984e5d82848e77d90e7d8768085eaf1381651acac3613dcff3789e7a685a851cff480c8bcafdb93e242dceab96a981fb46f102f502539411d6d3dab1a437571217224d119df3f0ab65da551401bfab038fef71ed5986ffe3c8eafb3141accbbc28ccac6fb78dd34304e936ab502cc6808af593039d5100e721e592c68a54290639ef20a725eab8441a6edd674ce36a0c701861a1a6a5afb6ed6207fe7baf2162839d482548f8729c26f0af9a23e52f0b09458b508f1cc85c00b13f29249a142fc2dbad70e5619e2ec14378418f95679b4326936b8ad396d90aad17f8a5f6934abcc8d9461c11f312c8ad4f988afc64656d75606f315d88c20c6126adc4ab340e3bdb8ecad91ac0786c65a0815b590df13cb965938f71fc41fd867265f329fef4adb8bb7d86eebcedb9697e5b6e07cbabdfdcbb2056327ad643cbb72ec0c67ca7d63fc22ba146448a9ddff88273ef25b979a744e9dd3185587bdfe56425e9550f8fba8b65853eb6407db73c8455d64c985c15aed7579718e329d1189902d665a05a4c62dc9230b6200d8dd99057523ca68fcd8706611e6dd21f03a45be36f64a0af1ac83e40a521aec1f809a8e0908aa545560b1e3a61196e250e2342c8ab50491d06bce37d5500099f876ec2f61ad47c3534175be853e029b9fdd17f75c3f8e10c0feca9e0780a97c4abb3d34d6f192227511f8368a8300c0e93065f46f005a3e023e7a4ffa6acd5fa24a8f346ddc77b098285f33029112898a8bb3840969c1f6eb2f54c5023c3a60c6356572166b37ed62dd4845cb25138a5704ff16681f6cb7ee63f3c0e370f321210ba07834aa4d97ca9cbf1f1c5b98f710c930f1782f789ee06e8c1f6a087e7cf9ef82419e636f0f9754f5bcbfb124b0e5c80ad83b752672924f7b94d543713e5ab973ee9c82bc1ee5c210339a146be4106f1659e581ec648a414c6204a091cc08635462ef2c64897ad94369d2307086f83653dc0a665d924d038b76ec092ac6bcc06aad23c52d8ddc8d1448aba97ee8d6621e03fd41d19e876dd552f64774270cc57e736ebaaf85f0ab60850c93659120b4d1745421b808fb4ad571b2ee9c64ef4beaf58af426271fc9a379f22a1e10831c5c584981613894e0770b172625fc0915512f557c6b1027e69fe615e592495ba8d8ebcf7fe18bb6d84bda0450cd2546e47188452c3a816f8a5ce234c88a52704bc8d819349f9793f262310554b2c9142fdcc82d039d13c44116b0de7ac99910dafa0b42881791ea21303ff894003787b79551db64c1ba3730e91f590ffb3cb14bccde477c81519293b56df9eff1165220faaabe1f923220c4594b4844f3c3b70a4f3b19945257e07616227800f1ba15fecd50e275024aa3586ec86a260f31f088069066834b8d050347e9d45c0721bcc49264b457e49136566402e9566fd5abe4a5a915882537f057c0b7e2933999a65506dd4a445b20cd68f9298112e83b0c589451534a2904b5fd01e53955658ee6e87772632778117b725183b007356eda974c5681e3a465866d46ba60102f3a29e1090bb94194b32701035296e10b3277de25677225d5415bc2a67704c4e5fccec724504f79889d3d19af2ea089e3f6cd260a3009001f258f8698a40755b2c4f70431d4b350899c89155611d41840dda4bb0ff534f501ba2c39c7e3e79f940a76677b4729b3216ccf0cf3d414d2ca79a98b3346b97dd567281c38358dbfb4f02d7b135a1ca2ebbfd6a86881eea3955f2086ab78264bcc513d484d66d947f67d2bae0196103a162d20dc23a38317de300af6a3f816b9b94c558db8a2f1eff32f97d3af17ebc457ea7d0a01c5c2b0f5c3bdf16b76f83ee49d01e244cd525ee997c96a37a69ebc8c6c909645bdf4f113de9ed17e2d620a700961916f7119ed104b23dd407a5a2f01efa4f2f9cc6245a7a35095b6c676266fb9498023d864001ebca47e4188e4512c215dd94681bc3ee5ce7ae181bfd5d47cfc6ce76ae5d3d28169aaee738a761e6afc8eda2936eaaf7a1ba6278868db72f654943301c08507d5704692d727c26b156c4e8007e9940dd109609f3890f3aa8eec4c57d61f34bf01ed1d150d0188f7b81d80321a0cda2931c94b806d7014c87bd2d0267a1084e45fe21b185c10b684171cd182c1681b984cee980b14fea9c67f0cc081f00cadce6281f146c83472720d2fb1b3a301b0b954a9c9b8b89d5303a374abcc0c787c8285c409c06c3b52a4a99699996a8ce3853199c547d1cf946d25c22361c470c1d0eda62bf5ca210ba3c2be4dca9b8f578e1c297a1bab7efc8951845a2e9444163ca23681bd29d59258de6ed1e95afa5e1fbe947f94b69487e06d2fa2a2e811f21f9286a6d2d6752c280eae4c67f8cc4d531099c38e11e69b0f56d57c2db9477ce480ae83111d08425183337ba2f06694f13e43fcfd485676976e297daa2c11fba8c1f87309ecb0a1bae4419964fa2447f8e0ec517ddf7845139e38e40c2802b94cf76850eb4a240943e90bb74fa12e58cde5eec2d4470e165ff4193f9dcc04e687b4f335b580c27cb040afb4cd25ee029fa9a1930360f22f591772064bbd8f15db23c5e27ce0bdbf7df7469971a80572a9186e46427f827829a9cfb16a6ddc25a7aa92eb77514574bc94a6384f9ce2ba65044c55aaa7f3b8986f542f905c32dc21fbddb5d232a2f541a286ed1a8e7dfeecd19d1b08d55a959909b1947c68842e84bfd820b1d12b782b81065385962b92ab238e4cad9a81a62ea22b55120b0833bf8b3813a677e9679a61561c931783789956ce845a989de527ae0621f2eebec1252273c1fc8441e8242f5f07c52815444115124db02b6b3f348873d43ed6d8818b071ba1886414ac0761a7b17a5e1520dae6b40c7414b4445872b73fad929ffa514eaf41ecc18b27a12aed41db38171e81520cf3814c5e77e9765440924e711fc95ff02584434b43edc7473e8c6a09247eb8c066d296da596c81c228730d32c714745d19a60011bd80fe532a98143f4be5210b104eaaa9b11075d0994033599808e12545bb70db8f905449f17fb5b43ec6cec4a3eb90f6ad47a1573709210cdd609406198ec9f25b81f4e46194a50f3fc983e0b222dcf3ef243436ea27cf4d94475905d138dd805ce0c02d35b105140f763921cf673ee3dc6b354571b08ee291de40e4a866a660e42f5961312653910e67781547f0c64f5446ccd23390bdad8628098979a23ed52d303e6012e4e12711bcf94f5de5597a930ac6ca3c9cb43824f34243f66f678590a705c79c049071795aca2b8a17dc784e42aee1440996ba7d10d84cb5c0fbab880b815685e5fd044dcb709e16e0624f3238d04c901bd4e528c40226eaa0e0ee7ab314dc4353a062fa969e86302188e5bf7807bc78634bf16f398498f77fbd8791b09a8676d0372d590d13b1f7bf64257495cdcfb605de11d5e2fb5ad55870c76c80a7f60788b035583f112afc86eef3924c84bf3f7fde37f42019577d5e5d36da6c2a28b3642724f64817e11bf47079aa650db1b2e5237d75ab9092d45c7cb30fe7a65453f620e3e336f7b94ff233b795ae1f6ee02a68115bac5b9a3bcb63f34108b5a011c90d60f502257608808650bbde578f5f58de096390613b4deac601d20cc3eeaaf899fd3ac0d1f5d4a5eb2814e6901c51eb250dc6e145c01caffd88984b3e3f967c344b917975e3126301789cdd9810a6779624484625951600181984e44d427e4409c380a0ae1199961795dccd7b60fdf15942131995f2115a4b4b806196cbc900c52b211827c6b2e87ec3932f2685ac8f8d2d151d32a9487583103f588268403b74b1520d95d26cc0922aab559b63f7230b76ebb59b407e6bacd4fcb4f6263b02a0466ddae1092e943bd2c03d207959113102e6c1a3f0516dabe9fe1644e1a1ce0ef2f820150fd24e801d21f0bfb8f8d63340d9222dd3e43b6aa31934d21539f15f026cca2e073de7c67bb0d4106f7637f541938de66de44c3cec84a255e2ded900e19a8668a9f1a93eb13f276dfa9fabb18064a769cf4595bd7692eed6b2bc89915d0aa794c19fdd800060c0cd0ac471ba71e45cc49e2875c8123078d52015b746a80a5d502cc30ea85ac71a73d63ad63cba6fd9c1941f4d0ff47f81cfae199214ce28f47010c0212bf3c0ac11092f6e755108610893f6f85b004098c254e28c56013b2d4380d990258a08c7871ccd020a9cf4d249f0f141634734a8eeeab61f5b613e7ff8c3265bf0aa076bfec8fe3609c07ff2cec6b4f67cb48f2b56773b44ef06085f312b9c179188ae30114c683501c0fa1733e84e278089df32114c743e89c0fa1381e42e77c08c5f1582266a380c0bc442760c07fd9beba7098bc215021c8d98dd7442bcbd4e40f46cbb6c50cb3d4e2b7860a279296114b9dbcfd4677ab4d178352f31b6d9334ee4c9f104f47ad80aa1fcc132bde529b0ae10b56d0cd1089554d3b67b0e438dad3df3176feb5cd4f8ce9defc7ecaa0f9679d1543d069df95ba25f590eedb85b2d97a578b49605296700ea812198b5de94a8d098ee056882b4f6c67440dd166ef4e4b0214a42303efc2103edb429bfd024a857ffbbe24948d70ad9b5a4bde1829db06dfe62128d8005c7744f2abb5f43cacf587b42e5a0730992d87f6ab787e6d383551195c03515328869acd39ba2e4602205d096c28452c64a25066265c7fde81aef806ef9feae748f36ad9e021c8151789301713b0737eb262ff8d4b5625f96e112ada22258d7a8239a2fd770fa865c4da3ed2d68645d0b64e61f7bd61b6adcb2a608b04f6a4feda218319cfe838c7d2b9e2afbfc5d118ca2a430865ce057e47413de5ce0f0ab11b7b1c59e4b93044568d61ad11611eee4c2aa302334cc66c5bc20f5522304d2c795afb6c02693253f389d59a6080665692446ba6d574679e54e14e39f2d4e61c8994c5e8e2e033abf313c5ac3af81433f4f61998d9489bba6ad0ec0967f0992d9aa0b27bce5dd42b6b6b62a2218694d6efd8925b5bbb63a5ab3c63040d55a6475a0cdea4e3f614f545e86e1bcda76577ce0abc3a65ecf7f1eac3b512c2f5a94fd36a6c637045cc2deb62c1dec5b281e19be809cb607d24dbfb4712ae6f4df0cc3ad9e12d1f402667416b64e02a71d9d6ce533572087034033e101beefa88406d0000e608591c94cade442808ea0de09d8231e5227d83ecfc80271a2f1a63c4302c62133bb7d2588b6c880c8630bba1cfa785f30302b4a32b3aed7b48493c17583e53a7c164abc8d2c839af41e0c428a7a44cfd63621383683c6d61af73746d5c15a2a7167d0309a9f584b302b034f95b4b402e8f8be45ba612eeb79ad2f0a9facc8e0b740c6311220428c3d6aa50814e85efc1a072797ec97d4a7abbc05a95287d71b16b26499f5ded74643035953379175e486c0fadf832e56a10e741a790cba19c537bb840c5ec75ef6ace127408889dbd5a8c6764e00510c0329dea001d4e3f0040390c6b4647e91dc1b3dafd16679c3d087e631808b697c2c88a60662dd95899576da6254abf43d2139e25957a9a4dbd1faeb03ee582bc166b281e0eaede0d07f726082147bd38a0f9776d05cfec0eec59411e84e2a75b618fdae3763bf772f4a28f5106779550589d19118196cb454911558465ff437b94134cbc3717731532fdbc8dc223986b0477a15e2ae8d65e12787d67c899f81948c7496a8c7ae14ade05ae24ffd5564112ebd40288ce313034a949ca12502a25fe9e467fbaa6ac6af30eb4a461dd0d10aa2c3b2ca313167c754e75821daad0021113a29700882a002f1571cb8641e6a3bca4f8b9dc95d8c8d6ab3d3e5028313daf1ef1d5ad0d0a55dd7b8c131c27ab13e76db9fce7deaa769d6d196fa51372a653ed2427d7a8315438530297dba89d3ca83127951668fe4a4177e4aa36e9a90a6a3abe4877ed806800856b024a2f7d263d86e1b388bdc2ae05e566bf198803bd058645705266381c121ae081cedb6719d59780ae192404cc8e4870d6cb472760a677592ddb09379e3844621d8087e39f8dca1da7dd591007fa5f1495304aa3260d926f02b88dfaf0bb8e077d16febef7d6dad2633a5e046ebe716f8588ff95823be493a386634a49dae01a643ea77a9d2ad59ce412f0086e219be21cf964aa39da691ad1a076475363140b3499178c2ebdd3cbf80eb9c3581e21e802c37acaf1281f2d32f28660fccaf6d40354ca5c884d8de7148ab9202236e3abc4cb853fcb726152ea3181449daf59e55a18c992addeaf9e762d095e4fac683c39c6487fcf5e6709f4e30422494ec0baf89d23b9be394f29c5d0c4cb74804a17d44bde001d47d41bfb0db688cf27e0105fa38b5161c8216fd2cb7d88e539f4fecb9965a6adb72c179ed746a4647fe050fd21ea3ad7d9dd5cb0361fd44d582622e05b02d492433e9811d94637e91c52951bb31bd63830e4e002c180d512e4f039441be2b4bb5b2b82f32a05ae90e469e63a6b5e3db83dcf817ce09bf9bdb50c1104988dac8538a06b1a856a75b849982e19cdc31b18a10026896574b9eecf32ba9cdc35b65a3e8a02962796090e80bf13f5f9055aa8b450f95a6861fff777436b310905340c326e293e3c9187d59194f7809175c5ad22a2128622077506916ae0cc1034fc46083ff026081c2603840c8ef9410d9cf181ccb36cac1e290af08866872ff4768c74cee57a9e27873170a80453d0c7c0ea8b14a6845ab56e9e5b82d7491242533326b80629b079e68ffdf8367deb80157681c82d46b861159c8a539129bc0f9a8426374dc2c088514b6c58c116a239c8a5eda25091e372b810173d66e50efdf9d649c40d3531801ac24a282b404fc3953bfa21695a8256b01b7f58b3ccb13166373580b95554bae9debc8b1c063c424f5a93553f9995dc2fffc10e4ca80903a108b945538e72a452240fe2a57e5400f480e02dbe0c4f3e4ac288c5fd1168b8328a65c30ffd144ed49b31818ed8158120090579bb81ace44e0a11fe559f2a8ad2bcd58f81c206bd6ab278021ba74a3ded83ac83a2aa5f76b85e61f70af51edf4f9142965b732384154ca34b7975b91beaf649e1787bc19a220bd3af35714d99ac5bbc186cd3fa625e01a98524fad65379d3afa499ec30dc71b25ad93ef748763be9aa52d575143bf73ac18bb58e91043266b1ffe90293f32a45fa7cb3089f59349a6ff1ed8a0102d57b30fb18cd9e361a779c44c38e5f1423d4d637a0040f31bafb656ab345e43435198cee2118148fb56b2f6125efa00207e00fcf801295fb123f5191f70bc30dd0f2cbf0e0e022d3859c0472b95a514622b948b395920f93898dbe73e1cc60cb785883d07d483afc388dd224b487e9184140366c5e565a25a4b3ef2bfb958216f6c02b61099add5909350621e00e91e3c7914408831f9a30aba0ca3f5aaa0daa321a3f1a2f4cabc6628dab418329e4f8935ff43c7553956969325252b28bdfb808677b1cf2deb7ee88890e171b34888afb9e24aa143133bf6e17834eddc77bc4f1f5f70374ec93af4834c8888452c8604506cce688867010e9a48295b2af51eb6a108bfcd0ef12b84931cb398dd61c2c538176b25526f5c9f6ca5d08fb4b6711465124d954cd258f46f5fd6fbb2723bf2653029f20c890b4c2f6801bc526bebf93668a883cd36a993bf66a856d8a9b5a1af3c04883401e8a45abc7be6d71ecc38bca85c320d11f3633bf0f3d907d228e6715a46ecfbebbc56e26952ed82f03a1db99aabcd606bb4035ab10ab492890cb2cdf19f8650deecd600332e791318b8e0f3df7db85163860aeb6bca8d8c5ec1ce83dea72db6892d6a429034585f1c1fead2b9ec5e5ca67ef079668daff8bd7e43ec026a695a644ac587da646c60272fd1b88849f0bade0c7d3165a1548101c757a70a30cf777f45acc30f593fda7133daacc1e0824a9c283b014e5e85728e33b0bef06570c9a7a183db385990691bbe6f8db92bc05d1d2b58a968ea38f581a32acb763032194eb52fb17841a1a9c576d91e258e2df816881715582c31287206845780c5eb648e21e609209782e0a231eedde28883db3b4ddf720db0edf283ce290bda60cb3a268cebbbdb9ce236cf2250e3ca0fdde38cd6aeaf08f447faf1cfe5b754de38bbf44d6cf313798fbcdc9452603f09464817c9aba0726c21fe8649cc00dda16a7350f8e169af708df3e7676884c1f6693c9eb064d71a9684adaa75ad8fcd8e2ab2f4d9d8f5c7a34ea4d4cae45e240a4220d1830d60d43e9c0f09d37fb632a54023bd6f98764e303e391e34ae7c248842bc6c1356090403d603285ba022946e258ba1e5b6582541fa7d64c3dbbe67ad7d01ae4749045516c3f22f40b57a4b508a0ed062d522c456b6e6cf5c2f887d0b8b6e16e4a38d016a6998eca2eaabdbee7e03b724c6fe10a87c1742f0c7f82cf01e3bf1788c000f77c6ca95ea5359811370c92b6c70be38793511417856ac730eb41049c0c937b8cf65216a3696473877df7b9d7b89361567a534ae53792f830783be99a950b59ac23e4514a23c9f48c8119df4bfd763210384bde00ea8909714e7cc14d7ec962c24be04819c751105ee0a43841bf9b51866d4781b8bd020bcce038ac280bc52dba09869b7180e88cca84926f1b5a3da8b0373ea179a2190e191723c444100b9e9b8888113404a6e866f0508c951c3bb293622d4cc0aab87ea063a82de24c5345b27cd4c2b8fde54e45e1fe9075d2bb628650f9601a887145800f5651c9f19b5cd478b7e15d0c8ba671f4c1dd3f6813f54e8354ed0a47a059e184a786454de44d979be48e42ecf2e8c175501dd4d87fd79410f2409d1a364025e9106a1424cf0d622039bca4f9168f987c17ecb726fbdc707f3366c533cfa9d1c1f39ae6fd4f72cbd76f2b298da3099310d36aae3439b61498d9ab0613ce2b1ebc6a98d1b14170c8ed02217766e329bb21c78ad4a1f1cbd3f3058c957fdd6523601be310b09d4c80829381801bcf4ee01216f23e703f5c92d34081d1937bf546a623fd1bf63e804b263611876e36493a70f4f31307ea807d6ee689eb71cf72e4c8c17a6b94883b7c995951c373c779d30f860e562896d282969cc6848903fcd5c42a888931f4715694c622161dd3f87e4d8e40f2fc0f896e9a677621c4445537e2def6b09a5b3968ea71be97273c62414b418f41e008118c091986c47ef76420f1b446e09ce0a10b0a0d633271c4e4b09ebd7c9ff67834879589591e9d3134383e904bfb58a250d1d80d552e152d3b14a7cf06bc3520d75b68200201154060d15ec1d361ff35508badb89cb3a68a941608a4c6f8b47892f446627c1ad55fc8e95375188ad0423e7abdfc6c5e0316c024a45b7b4926a1c32428257741c9ccc5dd7345f30db1fa3192b5a60ac718d9996fa0989daca9571a17d48c4dbdbc9a12f836f48b2bfb795398ab5b0111f6bec863118509477e4504c224cf11f37738519b6aaf5b3addaaee87e959693dc737f8f334c9f640b0a4b588021e96acec6c14a84cae88b7ea51783bc0f4c1a172087e14baa4abb51978d9beaed55fe71f8af67d6947d63ac2cb90379031b0a9e3868b68126b89b24855aa7190c02b95f3e97aa6f10392ff37a27bb3020691acd0cd93ad6a81b8d4b61ac7c883542ba274cedf2f959dfb65deda83c03d6259e1e382867b83480134c7885e2296f36e1074745b2383739d85ea56460923ecbe909c7f13249559f7620f5091dffc7c9561eb56d06867891c782b7726db0b93ad790dc45cb735e7bcc91bffcf681d7a2d1f89f166eb3ed1dd64c9b5fad2d4be14138f95abf1860b6943411e14f7a0c7fb1a54aee7d55aa938b76b4638d0519703a9053ccfcb9e8bfd27a791d5c3d9c7246043b5b20d3492d317f9c1558b3ef6e1894fe41b1f37100661bb94334cd07e64fdbde4df3148cae8e8de900360ab0c60b5ceb8a6eb135dfdf05cdce05b613106cbef8415dd73158e3cd8715dcb0da27211a5cad1b32c557278ca1ea2ac8144dfa52c919922ed6c93dc5d8129f7a49d333e5b14ece90dc74c59411eaf2031dc37e351422c606f433ff3d9df019bc118927cb69e65995c43dbac1580dc1861a476dcb92037c63ce257808ac057d0b153cfabb3ad322bef771eec2737ad3267fa92a80419b9b43ede033b1a9f532d5e734e7f4028d2eaade1b71f04f9bb754aace43bb2bc7210abbed1def5d8149aafc919409db52ea731ebeb68908b70c04a2111a8a7e7a1cf0055ae60c060695f6e970e78f4283872143d6888f78db8fa3e87ecb9b3b7dc235ca687bc86a434941ceabd2b8bece2e7bc10bcdb6ec9fb6c24e77e496ba10e9dae643bfeb45b60f2f61efd142105c9098043b47cd3437ca2bbfeaaa394666e5e6ed289babc3b49b0c3e5eec85631f0cf6514bcb97863dc6cd95c8f689ee7531191c5efdeba071f7c686af33e4be2431dab273672be30968d32273bb53185db927e1e5e2edfc59296c68fea325d1423dc09d8b26a3e6cfa66d7547b5360299c7bde4cdfb73263d4ad634ac66337eced89b57aaaa042de68b2d0891eb2fd980accf4012fbe272f044b4ddeb2871d82c2f0d2105bc210f16c5108216862f6a0d176df2b9eb4a39eab3977168a32ba00c6b06e96a4992c0ca01b087fdea97dab816f8a946dea83aa22a75a79c3904a317fd366e9b4818d908c2e50d2a3ce0754b763902a531142eac31cb3c0367c633ee8652d23bb371a5cc3c8f8cdc2d047639a757fdaa340feccb84ad2ea413341ad1052a4eb1226a7a1e913cdc3ba785c56ab295eff52b20330bd465bdad3bfc4a17e298d1a0b1020a5f3abd61fe4d0188b42574a3ec3dcd4740556176fb9f5091600551949cf5a6d8170b010f88a4700c1aeb5867c327ce6db0a6d2d9af66c2408794162a790db354a5770c8f625459f2bbb5d8692431ffee0b62e3ac39b83680b30e8298a3de0b380b1e1074c3798f116ca27b522b8213ece85710b288741a26798cb969948483af661ca0c899e0d4fcb24f3d42c557d285ca48b04c53fa488929c3338c3b543f3a2be6fe2e9896a64540d6607783f72f46c388a015a6212c48d411496a2781f5aa28e927452ea3c82b9a99ed2005a14a5c509b4a11c3d4b63006882cbd100a1094984b8a45454e8d43ed2726459b123796e6de1ae414ab69b06814c1c6df9d0fa62af671a053514fe149b92339409bed78cfa0121b0dbc2227c6e817cbdcd22aaede0a2ea475674296f462b87a7269e61508d6b783f0a3ce6488ec878b5eb86c0f7ccfa818ca680d6263f5d8d9155ca34b9125d2616ca34ade033d65827d34c4bca82172d0a61452aa3f69d4f9c9e0de500da6882fb46e0226c99b1316cbd17562414c1624250a8c2aff3a7273d16961696e9f80a825b54bc65199d8164b23a28f07095231d082d2f55ed0b68aa5b82c94ac804dcd2013a4fe9466040d05d6d0e0edd61807507ea0bf15464222251570b9dce46dc83fa891fb146ce5d9189ffb585d4416da4b27f6abb3c68367985ce18a067b0584cb4dfc3d9ba983bc642a76248b59cf31b97c1446f0a8afba6dbd14626bf7b6577f7963249290328043a0480046413db729207f92685c102ed37e51f745b61d8f3fb61a7fd8a951d3041deb6e53aa892032a539650825cda96e340750314d6daa12448a56d391236909274440d68600439836db90cd02256586b65483180c10b888842ba20849084d222fd6f1142a76464423481c8e414646d3657d7754fd4731beb7fe2cab5cc74ef77f2a5e9fc5d2c4658b9d8280fd82e312ad143242222412222c1efd5fc2599457707171f9d5b007dfa8d813e5fcd67031f8659d91674b6d26139ab0a43ecd5de12f8f071ea3144c379be4c32cf1cd79bb3cd798491e2f4a6abfce3735126a224d73e0be540b15518ba484abf1c96d36f913ebfa47a737978430876dd076ad0035e67613b17bdeb72cecc4a722d1b99f9c51938fdcf7ca4d4a1a85f2e9a287f578f6d7991be1987ef05b205e410240bc815904f4815904ec8233205240a48214823b2097902920959238338f5209790262095904948204824e411d208f903598424420e217d38059142c8223208d903c9035902720752879d13d60a234d475c3ba6171b5e749c809c7ebcf078e171a2bdec40517422018a222cfe7fd0f831f9ffffffffffffffffffffffffffffffffffffffa7d168341a8d46a3d168341a8d46a3d16834da3f8d46a3d1fe69341aed9f46a3fdd368341ac6dc8fa18b8bd893b9cbc69f8f62c4a0e1cd3c03ecf86506b2d4e9efe833462d8d11a4566a2d9ebc8e2b8ccac5ae51a8171a2fdca9935863022854ee4a12bfbc882bf460eea5f4e32119268d20b57bbab5dec4b8b68be128ce48852971851e90775cb54237f736b8f58ae3b80b079a326204a9e58b6dc57614b1b8fabcee137be8eb8d335c144531acc13f7bac678fc1d9e3d9e3d9e3d9e3d9e399793a9d4e279bd96370f678f678f678f678f67876ae56ab950dfed9e36ff678f678f678f678d68a898989919999e1dc34cf166eb5feda163a7b3c7b3c7b3c7b3ca3314df36cb5686854a8a8b1c136367fad0d8a7ff678f678f678a6029d3d9e3d9ed5d4d8a0a8cbb562c5072c300b167f2d0b14ffecf1cc069da1ae87526badb6f67bda207bff3b9c65dc971ab4cee6d7bdde75ae6b1c6633779d7587f5874f3dea6b2b8833678d647cddc7a6c15eb8511440a5c64f5ca1e779b7fa9d8f675c6d7f0e147bf00d6fc735badf7adccd7fa210b2f3635bf198b1d8c56c5dd0e40fefaf50afbce372e5f82bbf3fb6adb5d6fa8d2c0bf46beb8ab46ebaf7de7bc9a16d3f76efcd516badb5d65a6badb5d65a6badb5d65a6badb5d65a6badb5d64a12914424114944f6a8b5d65a6b257b3c1793408f0ebcd8c3db6ec8fbe16428d2d43444068d139a7ee8bde24d3174ddab11d3961ef66ad494c3b6c92e9fc0df30ff97442cd6a93535645b5e33468599f3bc11680b42af3cbc76f53be92afd5cf07fdafb37d54aadf53f5117dd36b3e895579c1bde4fdf35c2fdf3c50edba2bd5e9dbc42c77217e90ae6eeb84853ce157a5707a757ae3b2e736f23d2012b5087e2e8b2c9f2847a49a95622ad088a142c981899196e9e2d1a15353668eedab5e2031668cd0d3ae27d753a5ed12cd6f8191e9891b97ca3f3fef33a6f08ef7c0dfc89f54bee5a05eebac626f4cccaf3e6edda6583a2e39f6ffb408c89c01f119f076a6bbd1e6c8c65faf36a2876b8c33d5caf13472c1b5d3e2f06e8fe1d1b1eafb97f66e541bfa66b8c4d9a0e7a5fabebeeecda33bbfe78d7e04cd75aa6eb30a66b11a6eb91d5b54b8aae378aaec955d7a5aaeb53aa6b5488cd97ee9920a8c077e66c819797d4a96b55d9f58aec1ac5ee3a854bd7acb16b18b1eb98b06b19ddf50cd835ffba36bdaecfaeeb16d7350dee5a458d4df89995079bb66b97cd5f5b5174c427ae37997f1ce08444f9a2fcff6723c8e6cc5fa4ff9f0671519a9a70660a01901fb4938ff781d84887ad48ca915294c444cae853541b7dfe71fe7c5a1e6ce28fe74f3e43f4dba3152ef47e113069455654dd016d351da6d68eec22f45dd7ab51d15b799ea1ead0b8e8056dbf8382cd864253b581478047549b2dc7aa0d85775dce798daad3aae8e5ab55bdab8b17e0c25fc719f8ba575d98b76adfd5857912044c0e4ce27c9ca00504c404c4040404dacb7da0d76590e823f2886eb57cc40474c3e307fa7fa0ef81b5166f7ca2684fc25acb755f3924de6c2924defae55d4c2f6f4040444444abb10ace1c0f67b35d15d9f75e185c6af7b92ee3235d25e22a51ae44cf02b9adb5f8f33c2fff5c14e888104ddc11ece364adbd5c8de3475e202222d241021d40404040404038337996085ba2ff7b6f0eb8cbdc6dea747c4817bd402f037bb9ac83a3183fd05512baec51d420a8437f1fb7d6fbc06f632217a2510c89c09b6bb8b67a5b80cad9b6bcf471c382e3382ee70fd2aeb9b0242a7b9443a55019548ea0042a732865250ee50de54fc9a3b4a1acc15a4b54d250ee28759433943e658e52865204650c250ce50ba50b250e6b2d50d94279a30c4169a30441f981928532966d57c40413ff997bc1892d5a9a6882a6c2a8bdf21d173d2e8860696272f1f91deee7773a211307058ff4fb2579756323d65aecddfe8a37cbb4c2859e6c187b375ccb18ff90254df01097884d5a32998cc6b2905d2a8f3d507f99d343e0d0375473cd7eb75ccb471c97ff2873d68617456119cd5acb652e9f680f1ced7f6bb0364787d91f9f1f999ef241484826241b1a4a6d0dab718add6163aef338d3f2781d4fc6f89372d290bd6b403cc69dc73d10de1deaee1077876eed8bc80d82ffcfe96e4f8fefab03f2fc23d6f038c8b1d8f39ddbcb423b64f0f92630d771d8258913203f20d377f473bb8e8be279ff8fbb1ac771dcfb9a7c4bbe1a10a7d82771093b8f7bec0d7543dcd0f3f8bff85e18f090c96432990c67cc75b21fda28eff8cc4f6c98cbdc630f8f4d34195960d2ef678b7d48ffd9469b0b19e3c71ec723a33532ef6ce7f60b714f76a7e80ed4df8ba5940f7a08b49ff9e25d344e40214609b6e5b017293a84d6f2fd1c5c2b24ab423604a18963b10e638a260d868330b70e93b9954d769a3463fb9d9ca39f41624a93604c2f957ac95cce1bf3dc712f2f3dac25d612ce98632d55181573f9742acb9294b9c95ce91991b06c582a73eb97a7b7ab031b91fa4d524a97a85c1d18057e44b22fd5b5158665569e1460d7f1d11e585d158a54b0362567987a56181df7e2a62fb8130561d8955f1dfcfa06fadaaf178f64bdb9bc32890dd1dffcba157b5036ec8b59d095a6d68c44dde94e079c53bab5dd19c6380d77e4d9382415eacdf392c986714239e9a2d6e6a5a48b0afd0e172be2b289c20944ee0e0f4770e182058b0be3849ec384392a12975444b964df25bcb204f02af47aa42b8d8dadb92a6a85fdff3f6ee593336bedb837f3c980311a2664892946142eabad22533b98bffc9f50a8d34b9922557be582624c21b242181d03ce70f36cd1d45aebadb5d66a833ac2abf5d672e67a703d72b64196e7decac3d92735db7a792c0e1cf71303cedc7bf75e1c9c50ea664538d182176e5ed85e0a7c6cad844710eec71e7b357262c8c7091b883821021938119b610b135b6ab0838633b2512a30dae11226433a221b15e4640b0d4fb6e0e88ad87acb13fbbb62031dac25b4b820898d87286283bd1a6931eabf13430b102fa864d068a225c88dedd2ab9116212d3b4480f2a1a5056ec816b134edbd0427b45551382128e888b20ce99eecb057a32c34d9d6bd1a65c921284b8e9dc5060ce05ea12e6a390bbbdc0117c43e1dc121d9285090637bbd1a6159a2c446f56a8445872d05c8052c3e1ed04ef56ad4848573b25f7a356a4aa26b628bbd1a35217119bf0c1a27ec15d72cd8b6bd1414141414142493c9aac8aac86427a01310107a037e7c87d42a3be888b2054b12509242206454c4071e80e4800a1623612d06b5d397b90e779eb7e3053548a2d40224821822024cec5092e1071d8620e1a9372452dfd4b9783182131ca2609a80e86f24acc140eb7a861c2672d8c7612087690ee338cc0bade5a1388e5c1c4fdbbf30e45f786acd417ddafe7127ec798c7fcb35ec3fb47f039d45976b838ed6e22bfc5a6bd3b0ae7338d7ac107bcba21f99376dcb2b27d22bb7f9c41863eefda0433189375697e798dc1f142fdca82ff518eb70e68451a9558a98f0626c6253d75cd4d43717040bc5e75d95831086402e8e4d433327bc21b470f10abd1f7c78a8639ebcb6138a42782f42152a6a6a6a6a6a549ce68591ff39ec39976bc58a152b56b84ef3f98f40425861b8045c169120ce40fb2da1630098102346ef3684162d5ab46811824d8d8bfffaaaaffaaaaffaaa2eaa0a9ad6ee094d308106066abd9641680820fca008e007124828a184124a2881840b80b0468cdec313c41e9e00ec9f008432c49e53468f9864db1921014ca0e1aafbc716404cb22db703c035700d5c03d7c035f000f00a57170239406873ce29d45a13707bf8de456b3640788010b3f8a020205c80d853534880bd113157210c816c2bfeff7ffeaa8050abd0f18f7b262b9f95a73b71bcb8e9a39877e6e7755c3320f4cc8f77a6f7755c858e7b260e8fa7e3208829e058ec464cc1c6623c1def70031ae000073c0063fc80ff5aeb0f18a188fecc75de8772bc624157aeeb729db3d893bd172ee5d158dcc3d9e68409928e73c9b13ef08679f476c84f3d34699c67f3c5546f7462d0c1a1137b69b9e8a5b2947433693658fc86c73393a55ffe6aaa3897af786cd8b891f2a4e0334a284999e2536fae9962c64cb9992e753cac1c1b7653f447de656f8afa156297a569ce983666f80d8f8786df48795afc4495a035b449881eb95a70c55c3eae1dcf6de7fa59ea30d44c93a82d2e6f99290e6acee5a7994aa9afa58bc60c907966ce34a9e6f0f04195a04dd05a882a418f5c2db862ae1d2e9f478f8e5cd023f4e8083d72fdf43bfe8ffdae11c9e37e94e593fd411eb68e5a4f5a369a1c3438687e68642b55eae5d33442ad272d1b4d0e1a1c34329a9f55cba66ad9522ddb4bcbf6b56cba65a311d22714eac1f0854ea7507330c594caa28aa982501d511da99e54d472141c860343617cb555b6daa15259524caa982a08d511d513d5110a556ca58aa95431554c1503a28aa96cfd72b36b148ac7b8dd8bd883ea4218a906c30e25a45128b107f331c7b62f30f973e9294c1e64427385cc2fe0f5152eb0bb0bf752a9143822b174c05a4add52567050beda5be6a674d3a48bfa6cbba494546f524a56849794d41423524b57e7550412fd7250a95f219f1189a5839495d42d1c917e441247a43d228d48231212f5464ac5b91c9ba9d245b335398f0ddb2f2f29b1a7e32e288b31cde4daa49d1c34692cd3060f287ec3e30141f7c4134ff0f0b0c205760ff99be48db4423295464a5bd6ca2615b5dce2a07c656d79d42d3f914e7334e2baf238227548568c206f57e7554445ef4be885a3dedcd75069e4a72cfa9dd70c6593570c65edeabc5cb0e17418c9f442327d24d3aac34826906422997679d42f37974a0ea9c3727e96cc3753293e3862a99694e946450975a649f586ff78dd19fb61b4552cae124dc09de34f337968d24ef3bca99bc5b14c424e1363de5539992452bd612df58b6d1d9623c444c5b99c9b393f3932660e8e1c1b39b18b5e3eb3cdf44a7aab37e952aa64a55f9eba591152292912a95bbfdc9926fd6cd888442efdb0963cad7a333c5f48efba984a15cc54432204c8506c5cc9985bcbf0d17cfda8373ff546e605a40af5d70df53543a5a15f241a1421273f4c9870242dc1905e0f4da68726c36b85919a34190eca7017181307e749c131152bc2a76471f0edea7c4b175ba93adf948a5ed8ed98e977bedb1ba93b60905af43be08f77d5ae0e78e4a2b949d50193e0a07c6326b09642e6b6c3335f28f345c4cb76325f474a568417948af34d79ad60e9eabc9c54f4bea2f4cbbf53a95f5e9aaf980ba6dbefbc66a8ae2038fa8fdf79b1505d49aa0b2d47ae8e8ba61a0c0749bf42fae57fc23890d550bf7c9fb7febaa15fee72a649606d3c5f3ebcca86c9dc92cc1149e6ca755dae5161366db7306169b45731a319d37fc64c4533a562a6b7775d1e63a64cefba3c85891267e074ef4c9770b0612b960963cec09833a7d89d32b78bb2ccae55842c1c8bf1bc99a26b93c6e2212b85b96b509c3131305c46555dc7c4c070303d3257b20c93cc8d25ae80fb00d5358befc6eb9d892fcaea7ae785176599952792eba8e4cb9b8db5533303000000331800000c06a240102549940459c8e90314000d50823e5c5446280e06427140240c82280aa22088611804811008621008e414731aab0021584b9210048262f9067817a2d203140a893c639c555e2f93ed5994ab38cc5592502a35e8fa25ea40f5f1b84bffa63e91bc7a561da3f30e9e874334dc56378884fa700a03cc347642bfbc7484e684a392eccef5d23633d611b97b1ec36e1f990e24a0111e726f438a00bbe5a45d4c041233003f66a6915bb1bea568108bbd2065e9f09399e0cd7ade479a0f92989814d2b443929b4612d73f120db729d2b59004a5f960fdb6189c3eb610ce3ebae293d96d8566f2b2059c05cfb936ce282dc6518ef1a21a157edc4e4ad092521f0e7ea53e6ed1eea8d177b3f0f47c937f7428092292fe7378a9ad64637f6c841a18ec91c93357028bf328916d602c149f040d9d7e27e337d5fc3dc8bcc997af68d159a4f87b7d2395197dd6965ea745a558e4afbabdd20a436f18ba15089e04a47a5ab64ae2f5d17a49cc41c951b552e7ded292f7ca928770c965113c565fd18d5434c7116828391431b708438ae13aac08afe252b7fba5807bd205929587ecb375b55825b50d26a2306283c6cbe4c9bfebe2b78afa7b4b67ec0d9ded7fe634f4c1659673ac23af32299d2ecb35db023c66b50773ff23833cbae45b82c93826bc0a1bf25963f6d6289a398e230b94e486544e50c4a0c43d43c9b7376a0682d7f508e91e6371b7fef3c3dd15b7381542ac7d50845bf00441f1abb05019f2ed19d2118e39cb2076da2ea62c5093ba5f84120c4ae06bb3cfdf08c28134d653122a2f1c2890c9afb6ee7c92b475a7976d6ad52e932d2c7785d4b35a1961fbee9518efa151c5ffc31e25e8da211f17e0e3e11e1bec01a01e08e840b6f0a6e7ebea63f85e925122725129a4b6338c706ed6a2e510642cbc7f708f7f366c7489176d59487492692da691c52fdc2b117c61f9c6a50402a8de949e8b375b58b2b7c70afa66ed6b42a90c595f4bccf99e741316daea5f3cd5eb4933c3db04a28314a80f08b7636bc24dadbaf1f407102d3740df26ee3866e57c97ba22b4c5bde776f0b78dfd0e83208f45b2dc0693c3b8890cc6fcab7b6dbb5aaeb81f4e1e118c5abdd8693d70f480394c890ef3fc95faf19deea639d590ae93f60d3df507cf5c804d9c46401b4909e23cab50a12f7154d38beeff77440277c527b7807cf2f867e5531e7813ce3b851a45b19fc218c168a59eb2593ed0f3f29405500b50e42a2b80d09380b30f41fe669b23d2d5ff75601454acb5cd2718e485b0622ae513a17b5dda09933054b879c51d0423ca97a1d4e4bd29af27f3d4151e4825e455631baa235cb8cf6bc3f451617138ea93844bd5505e26dc62d1dbb980440b43ef183d2936026325af15ca1bb2bbad9b840c77490769c918da5ddfe1340a5c6030fe957bacb95a90e28d1c477492f763e895e7c0c73f1d4aeec80686b6244a94d5d16671e60267dcfaffe084c5566b15968b6d178bcf351b120ab76d06606f001956122e3bda11fdac95848e065dd2bc5ca00fb293f3c74ab308fcfd1a80cc910862bb5bd49440df1fde3a28c4d7f8d8ceeb67c4048f5eb9f2a02fd2bdd6f6e56ff586a37858e0c417c2fa2066ce7b2ba7332c1acf7bb3de85451dfa1bd0ccc2d28cf062567ff01417f5c5dbffc8a8f8344056566ac63704a23be616669bdae1077a83c6fe52bdcfe1290ee2ac7920336ff94a993e9854d371ea34798a3b7af269bc8282d9e306b50066de89af2c731e8e606cf072d5029d66a9644fa4772cd010c99b317c5a30a86d6591a570ca82372d6f0c91f457e3b548551d46d7656c5411d3e5c71b9a30d4c172aab995c7b899c9160f40051dd18b8cc2b5058f62a498be917e740443a67b08c6be49c256c25bda09cc1a1bcf661ca79cd979e883a3f20aa9da948e8a5cbfa5221521f98037949b4fe3334a087efad14363f33e899b87e6c3329e4cbf502c87ad6310e5184f9d2815e9cc18a21f117b3a060f5ad3a40bc1ce595e8b27a499b6e219c210e7b674d9b850ba0b664d56aa629c165cb7c50a66018e80f3718e46a254a99da815bf4d362b90a2113baee7585dc4fd5058194428ec0b7aa7dde0565c98c11f3a1f4bb51b1e97a49998415ff737c4a400e3881e99eba3ce7555a585297573cdf4c5d7c7b63d5d541635c79d1cab1b6b82b183e12553ce39ac52fc68fbd87765410a6bb83220baa41cb07ca82bee8c44896c4d382fa186759195323681f931cae2242f82c8549b005d1a53a0900ee0bc3370076d2f7f65f44d74d24234f2e5a50864f4b7899858e49dcac6f80bf874be71cadd452ff3ca068933e30b2b3bd974c0a43ce281e27350ee1807e15b84c51e00703308f28ac74db3e1efc08d9afc6b7225291aefa1ca03aed1f03b0aa3cba9aac8aa2e28ddd529f5be3229f39c8c0d430e92755ec665889aeab9f9465b0ba15e5d7165af2bfbfcb4058c651f6f6b73d410de49af1d3efa8688f9fa3ca91fc349cd13b7f9dd7342bc84ba688d90a6ebe225723892ba40d5a0d44b3ce2cec50121ead4cc7d44c56691483bfb86b28707b93a9c6dbfbd0d76f8b4164992cbe108da6299fbd6e378fc5063cf1f475b912bdfcaf423e214758c3f5f58bdaff2e56b665267fd5e5e78d6269666618e646040a7c9a5a601bbc097069feca9a066079680198bab18199cffa8620539fa6b3d6e745f952c03d23c4b298c561073b895833760b806a98267db4fac04f0a8d913fb3d508d6a56bb4eafd5791d2b32c2c6ec86704e0a078b072770ef745cb853dffce944ef7b8e476d819d38a3b755bd58b827a71b2cda08ecb937a2bc97dc739cf8dd095bb880ea5cbdea33ba5a6ad74d2304cd76e85a30c5e2db01fea03e16d18611008569d75ea96143eb225248a450af84f9e4edf932413e122ca89660fa14b38f73dcbaedbe72a051b47b667be90968187cb33c7c4c5a53a5f04f6e2163502be412270d1a72d800fa4894f745ed1d084581b5d0502637f29cdbe00e7ebc9c4c03c95a78cd71bd3a32ff2b3cc9b3b6e1b839231e155c832901341e64f964e510eb2d1c248661b23a7507efa6957b269aade6bf52f779645bd46d83e3badd8af494044eb724dd80848032023d15a237c9685aebd8e5b9b04e5984327a26935d592be4f6831110b338b4edc3e359a2c27c6fcc69629a8d21204a32f83d30b3f647361ba38e467f44b321652a446a9ab3c260a333b81f9875a5c540649f0f7d417c257e56dc1aabddcde9a59608bc3c9bcc984679c5af99b833d7c91cb8e294cf1589c7103ae32802df33d59a3d6f61abe121e0a90a467023806e120040b9edd7d752fbd7de3e9f643dbd686401f75ca2eb0472bb9d7d6115c6468403f708d8f61c3652c6ea436cf4740533dd7270e1f6b68123cca5211d97cb33ad7d61e13bce6d6f3370d8cc73dbb62f143ee3bc69dbf6c40a1ed38872b23b4f2ef95091fd1a7969b1bbe1119fbcdbd82d14b6e3dc36ec0b83ed3c7f8d7da1f01dcf6f5b366c792cdb268eb3c35ffc893ff883f19b22c48025920f896da1e095575d3e83635279ecca5c2f75f90a71d233ffbbfa0e6905ce51ee2058c7b3f2932413cc4a109f91386f18c38e6f6f03da33389c17638cce45a5c6abbfbcec09e4dc4e738fe232a9624a18a6e7b05acc7879c11a5448174acb059754565f91272eb48d94ae1e8591784ba79d4acd81bf77bfe9442d66a3508c8ef56e051b4faa4f700241ea4504b59aaf08f6c432ea34426e6b9be2f5aeb649cdf6d6cb83e86b959cb95ab75cffe40dbac14fd24fa4f23752f427ff53c819426f641b3c1d339c9344fa042a536ee5525a6a51907e47f2149fca06008dc478ba64466c05e8f551674aa6b9f7fc28580fc9e2a6c0143c99e29534be149054bcc9283ed8128d7964e26cc8d4b68390cbc801d5c3bb4fe231edc47bd65b282b53fcd8aedb1ba90748832d210d21554f96614ff19fb5587fdb723741b719966acc55d524ce57d6eb8426b67689a6b43f8243ea50bccb3fb7dad7024c9a614652166abf866a3ff98df45fabfdeb10a8859513a94120948839e9ce1066b6a68bb4248447eaf2dee68e35ba245d09db99441fe89215bd587a2a93da925a30f93e24bc631c7939017ffaf9e9ce1d276312ea5d5c2df8e6c2bc7d7e623626b5fb61bcef850b65d72be8fd196a192ac1d043f1218b3801f56aa167119c51b1c2ee1003f0153d21b87e2e04f5ce86063fcb7eb920652224c720de4d30e740e509876207fee16e46cdf80c59dd24e08b71395f32004f4f17cfa0270c0c4d340a2cd9b7c7a01f114daa8606c3414357fc4c757b3d86c1aab7783d82656ff2c0fbc8a1c574389771e8503ddc7b8f87a3c2b8059ca7c7785d6c2efcff8a6ef1be82c8c216741a35f55aadbc00a6dc4b6f0d43882818ca7d79b8d0e965e7e7e0d24fe7663b8bf30a1872e77310fc5a13351d1b78e93628645560d6e29a9ab4231e0d040878ab12481c377b1c5162c431dff58b0f2958420610ab2f71bc1a26a5b5826fb39ad2fd4a519f08a00d1428b9d79c4cab425d8340f12492a16e266349ec27eb0cd2af5df22a912613c258bad15dab2623071ae01354ad7a966a086785b16c015a8e58c8287bc0d89357296ac82ac1be522a132d67ae13a6a5daef55b4926eea8c56cfefd1cee447b945f109cbb151f9c1f6c9b7413b8a3399b487e64b4e398abc9a86051fb411945a677b49141ddd6ddd4ffc1f0431ba20d35c17aa9af1d820e5acede90902ee96296287edc96ab9e262da28cca1ff80e5ce2517b3f4a51c812ae7620beee7697e3e4b35a8661832224fe64d8e311f1ba76ae3d3607f98e545f1dcc7cfa7695683c6664fd6e48d0511916691f3212c52e2db2af04215a88114cd32ade9ddaa2d9acc91dedffbc66ad5c26d23d5872e280e28f97bfbd6d2ffb19fc7acb38e867894f44bea386a3d87919f289fb16aa5fc490c08852536508f854321e0aad97728425a4bd4035c6f23d565148010eaf59beb197bca36151a48a0fd4489b2e7023e6a98b9b2edddde9bfbcd2cba606ee2ef7089b917a768fb1239fdd14f96964a802ae5046ac6a14fd23350fd58c96d69f23a85aa3659d0fc9c9a5424d302efa91eec69b20b7595f19bc7851b8f6022fbc316c5bd7059cdfeb44ed46adbac1ea4fea011e5a065c0316844ff2fe26d18d2034594c7992df5347c6847c2fdb4952303cc456a5c82adbd308a1f042ed34cbd4fd3eb330f039a209ab1f4abe4af09680f1a49fed9f2b2c2a458346a9b1555e412f7ea891cbe7e42f25d6f919388ce87e5002dc4178144361ee53f5813b576bb7ec0c081fdcd9cb7f5c793bd8a892986eeef64c976a2f811b12837d35a27bba185dfb822a7aea2929e90eecece985edca1eed85fa464926040bdae66962a45bcc8f04e6b0ce2c49f1106d461f747c16c68e2cc0d3800872023fbbd88af6ad91bdccccca39a643803a18fcde9d0f0be59663badd56672c22f509412db1b8244444601100416e2c20c1b31e6f72eac7d1ab17a0e8809b32fa988e16471dde85290bf6822d071952e1b02c7592807c49fcfba0ee28b151233e00822a2ffcb099fedba1cd1b65a158ca5ceee120eb0cdb0029cf96e50c77a04d535cea4e527ffca3dbf22c2e537805b28d2575b09706072b4c447d54800e099d665b13ed5b5c10cd0337d7da127007ade9e8c5a8f832df64c924c960b1a32c4accc28510596eb2d176a15b74a1a048ec7d451059f7bfb88c21e0f1822d285cd74712d41b4fe093c6e854a2e54c77b35c4f134ed8a1958d8641fe5366eb1c5916279b10321e60c3a4a22cf73bc10422e17b2c888c8d8b817a20ebb12b8b671a772eceafd33641974fe986a4771ee6ca0d5d0da2d70aa9663f15d34f59a35dfc538eb54f3c8c594e61a9307a51b4acf6376201f82a16d08b8b8dccfd8186a995c1f47e5abe900fe29270c79447d53fc8616760f571c2eff9e17a01234624d7919d25cbc2fe7807773d3ebf0ae5dbda1c396d5575055b23d1c6d958a2af51b5dd3a70813a15a60f1011cee34f06120286d9f107d0bef7a4746536aac79854833f2ae995f3b771489b862fd7fec44e33025e419620b67f7ea73db6d6205b87f77e1c06202ca3b44b5aeb77adb7c87cebdda6867226e4ed1026742348c8ac24830fbdaf96e7ba2c2913878d684522719a7dcfad64083a1280e74a6dd22292d39ab96fd0ee52db55a0e44de3183094155789b44406a0ef78c5bff68ffce6e458a9836a9fee97a14b0fadc7f63831e4e4f8417556c3674ca72396112edc67ec01f2e7766ab25b4321802c5cfa665ad39a19b8ed7aaa22adbeb31c94f174b383e4622e5cb771597af85f3feb1a6ef442029e0525603e16b7f0a8650d30d19b414fd135ee496046e1de71d837c0cbc6780cebe2f7e8cb384a5c782bd00676606d70bc6900c6a2d646c3b3e1a58727cc305ab53118c9685c5640bb5e60cc5068e1020f0c35d91a75eb4e0fd562783400802bcc3942bb6a9a1c1f7a565262ed4e6e6e5acd9c4e94dbeb91c0742dce9e79f15d116ba531d356d0581e3bc1c1cbff616936b60962894ac37c30f564f2f43ac49853a5ccb939313bc6878f8a57b4a7e9616dfc87563127c1ad854c4d40cbe29ab43eccd9844bbd159a51fb368dacdeac79f74053bb09fac2e2af382668257a5090afae19d8b35e106122d709deda30a01ba35f2f4a228f6e7a0ec1d443571056383978db7951b4295c724e3a70ef23c90bdc0d526b7b3d9a3c00639de4daffefcb76fcbcb664f5028b742da527b5e9d4cf914e71969f8ae24db53d81ccc1ae5771e6b016fb7142894da0fa3243b14dc6c9ef28a058da5f6fd576b19dbd2bae967b516b3456127b1f1a4d08fc04c890d44bc692ab1d55f20db88b08e8ab47919d6b5f7a539d4dca6e6dda08099e057c35c1aa441c781a897d2e0232b206d3e76f6957d62978ad87b8f96eb56e1e86ad823914a90e8360fe868d0306e8d2144e402958b8e06a7013485bf0b3e11d2ba2245ab56b329e84ba7cb98baff26eb7e5b534721f8e60426b91ae1383a0ea3384e3f81ca7f464c64d3812e88ddaa2eff1d4f8fe87eb31b50d78d2ad1ef90665590254219c7fc1bd355f7b1ed2c8bf719a98d54dfab47d07e7bf14bc4144ebd31261b11ca8eb8e4c048507b03f78b1bdc34a8f725454f8d5f2a49477f2f467f40f99faef9687f7f28820aa23948e623d3729c0143ba9738522594e6eac7c625772734c739520fd5fa6c6906b2fd4770a1602ecd065af5a12348a48b72b89badc0742d0505650a3f00181695ff0a98b44aac4cc166624d6656a4c99c3493d9e8db328a8c16781a08f2d0fa4087b2bac6b2f0029c00b0fb5a698fcf5b1f292b31a6cd9b77dc9ff2753324753d73338541c405918850bf4bb382d0e68ed3a4008210cd1dd2c82911a27573d28753a9bddecfb326a2efe9ab27edf01e801fe5926e5c5076c484b82af326b011fc78400bf64030093cab43406f34902959edd27665274e34ffe6be343ab6e0815c508ffe2791bb5a271d3b1a3c7a84017d3d435c8206759d4747c3f98d9f942e0fc585282d5894b8cebc30090a1947b4bade3e7e4e6e0ad70c4607632f1bff20109eb8249c768a1923d0fe62822fd8f8c1af8a272c6c1bf40eec30f981999950aa21defe5dd24efcedcf30088a5d81fde6b85d0c19327a77d5653c464d7d7542c6d4ba9ce1eccb65f877247d762ac0c44b4956299815dc4380c16b25b6ceeef47b6e66951cbb5631a9b11933f037de41d131524ea896cc982fa2e66a0da03981d0f08bf00e379b16c3551dd0803f81fb812aad1dd2e5346664885b7d6a1302c193e831140b93328a6b287c0cda0326eafe69650b1bc3ad420601017314ae4c1478bdd1ab3af960920373fbf9110c81588b74c17dcb1700ad51bcfe0fcc06f25a09755fe15f60352500afb68520c429c45edf4d0cff1f0d7e690bd81a13a4fc8ff94bf702c658b7e21506e90199f3a010ca1937714a8b51d4650a070449bad1e57bbb487a511c920385ca90c4769b7a5ef71c4f58bc4233328609bd9168d823773273ce32c03b0c441c0b363c43e648ae2099d9a27ac8d9303ea16fda7815da2fdf448e076f0edd473db674f72410f9c5357dfbc837eda40cd3aec5403a718eba78d7ca7d1d75eb5f02f64288c74fb4d67305d6e6a3ce1b6ebd844a890606fadea8bf0488987a2853c129b61e4a94c94f2fb0d429fb01ffac29edaa9d880bf06391c3a70d49dc2311d1ef206fdf0f950f49cb5f0a228393e3f047817d38bec884a4bbe2b5181f88065ebb6e0a3352b4f665cd0df700b4fad073a4dd1d3c5538b613d802119355e4cccc9cc558db1cebbe16554abd2ad8b40881af5d5ade2e88f3afc86c071019970144e535389ad93f28adfde816e1fa9d11e583c633a449d5834156e95b6a4adba111767de39ea0abe8f16ec595c245d202f9ebe1a7a35d06273adc1c11e45347e963496e5342530e3e1dab4f756745713d013070d26e4494e031b816337530a5a51f13c0f242ad72a51475b266b8652862d0cfe635371aa5b686784b081b0227577788a674b59f7a2bf1d3d3ab0bf01214c142857e153fdc291ad016b0ab0b168a90a707c13c60bf0fd982563d3703628b0151fb8dd58958108d0b1f2d4af235d524c562ab175c75ad91044a76e1008400a61feeb213746fbee81b19b8dfe27671b8f155a798ccb4419893fe008ad5f9e7cf1d213a7e6b4265fc3ea8168195a0e3aa9cb1d1f48e8ce88edee0cc4a93871e0fd0ff0e7118862fcf98b700d0a9859d80790d17b9855570a95e915f4274329cc0f0728068285cf283a69ff2238a0968353770d442602cfb90be1c3f2d45ce9177756581732c535189df32facfcb47615c028230e5b9e67d1ed9040ace331d1cb332fb65e541fa3c4bc42aa639270d63279a827b6767638146d2c231a699577c71d9793d16355e0cb92a635993e3569275104e6a8e1686fd9642a84096591c9088b10adaf2b99093756a4a070813c17af78c6220d7447b7b21ab1f0c08625c17e6a82d951d3619dde5c5c535da0dc4c3321200c13d2ba1aec8d78d716c8521342c79b1c838a45fa788fc488a8c06a5cf074d0cee62382a91d7117de49311e38f999572d7218fc094b14aa0dd9c9400e41aa65e22dd67d319c58feb9e22652673f1f1156aa86b8903d5d29f4d17b61b858b0d510b2f9cfdb692b547da921c3f88cbf89fc9362dbc3dd093deb8a5709885c69653682dec1b67dfd3007b9b9e4e87ef2788fe5e1d2f78aac2eb3fefb33c0697912bb0d674060c8b20dae139afbc3a5e286fe5d8c5157d622c520fdc7230f796c425515cd8ae273f7066153176a969fb5df6c825f1e884f2a232110a5748f6444c00cb3e9c85f7ef584088506f605966591c3256b52d7815963b0f5e05b1d5676b73656263c49b8f91e070c817fe2751ce97968d8f353c0b170f60092c5c0e6de03c2f709a6f1f9a062a1028e9a9ee8d608557afa89b7811a01652de5b3400f83cdd219c855a63c2a7e279a364bb5d154ccef8c301714b0d3a0c6b309d1ee4731357afca75e08174f9eff56c161ec2b0fec12cfbf5fa86502ab303417b2ce286dc11e27814e38d5f3310fd1a174aecf4fef271e4c77061bf928d921336ca62163a8fcfd19a63b9841f0b2b32621dec21d0b44a29e7b46ca25f8cec3ffb38862dbae44f8f181f6995344393437fd0c59b635ec95c94fc9ac26494a3687a741e210a390403ba11622636f690f89586d14e17fac40fa1ff2da18c62d1b6ae21539c905026f094dee7e4ecf877d176403b30a89c75177e7b936d131ea44de539f4459e624e0f742947bc61ae6f066f0af2a97ff647bd67c4686c802ca4a3a9df23f6c608493c0547f5a2d7e5599a83af6131f5a7962d934b7c59640288f60e4bd4db64a6135c08ca35bf4a80354a69d7f58943a7a7b294da872ed23e534832f3af3e7321de1fcd408e2667b25fbc4ea49ab57c4f8929a57c9e6cabc98c4e06d9284a1ada32ee84cfd44441a22765f3316bf3370e1f2abbea2294066b50551e275eaa744bb68754670a241047d23824696a057000e6ce55b704b624fe5c89d45c8b340a7b347545a7e04905bfa5a23b6618db0cc58ce1349667884a92a703b694146267dcbb71c2301ac24224c18c12cce4385dfa922fc7acc9b4cd0fea653356c79bc96199d34b8e954793ef40d4c76765f8b0bee2b3621dd8b7cda90c7b0eabe58f31e9fba8f9ce2447c27e3c231dce3c94b483f742c5b1853c026320c9c846b6a8089e8b89b6d82d727f0f6ddc942c7c37f50c15137d00102b32b5984758a849f90bbaf3389c26b21b22ae8a891ff078151b1ae6211e6f94be036acf9828f78ab52362ad9e0b497e8dfa33b98907d398da90d0fd213fbce1afbfa9a12576761941450a6b31a7ca61585f14e3bab026ae0587262ea3fd4a9a79783cc3f316f1707bd01920981053fb2e21364709f4307654203e19d1b1fdf8b659f614bbb2a454e26456adafeafce5aea990982cc9e972c48478aa35663c929c67a2c662f2b6c35bab60a220b95e1087bd83a26a67eb8353aa6dbf938d89073c5ca7719c6946eb8aae6f4a3282e4a37105a4139abe6c4d1efe947c1547bece6ed1edb9b747749323283505bb5146e1b7c53ade2f18e7ba451cd3505ca4e4376e5da686196cbaac5d7905a7eb081bc8a8b02476b28b49fc1abe6da14c434c0b2bc624ae3067cd3442adc0f17f0855f4b2e5e104f723331d509ee5e04f945530ceb4c4c3a7ab9c4a7a72e3128ac8a93f75173b7b7417370b9b0a8a1b1a658e019815926688dad2ccd28ac6d962a0f4cd9e53f063c4c9b7c2a146c161ec086ecea98f4e5b153c7b04a7f9edfb66069bc152efcb9c40122a452b7d820c9d3398dca61705bb4f6053a8f83baffccdbb4d649326b3c4652d748efe4fa8352a2d8e2aa2ec4deced996ab7460bb5152a585dd9d7532ccb77087ccea52c4bea80ba60cac8667f187f444ed5eb3ada9bcb0e102cdfdbbef6d68408695a5a5bbaf74f0b0c8a398249711cba0bbca3b4dcf4efe2db0d7b179e1902e34f1f63bdae2ba40b9c20c01664a324280c616e1a51598c16e19aef313fe31808754f8c829d169b8c1299ccab531ec87b1760a4876ffeaee349c4c3a60bb165c425f3cceabe374189b4f4d2c92942f793bba7f3f0aa413cc6eaaefd4b582126a566f9fb480dec650fbf0c8423c839354541daff86818f476ac8a033270d9a19c80fd85fa3545c42ee4894aed652c2e8bcf0dbf70ec3771c2eb65ec098056ef81d58e614073ab6e58bedd34e68be3557d3bd574ed2394cbc611f9912dec2de88ee3183ecb87a3a246fb8f1610472dcc8bf6b4508b56c9459d805f94f8ae231b9c0044ad828e198a5f832de732437c80a8bd59c7f6a5bbb90dab7eb46a2ab61c44392f713ebc8559489fd2da5015e7014679059e3418c8d83910c72c06e28481382645c78b808bac8784b171df5d7fecef826b0c96048bf77b5ea429f42520f18d28e0732340f4ce40789903c8ce32176e2ef55c52185887c0e23b0e9945b11d6ea88b17c2102e3606c3acc3d8d87fedb68e72a134cefd234d3a69180719859e05490a1ecc32c4176597649d1aa0993641ec41a64cff006b9668c119f1f8e1cc6dd66e701862a78eb72e8e939c84aa7607d7702141039cdc086d700477a3a2d708da70d912b948e514843e39497d7fc17d3591dd6b784f958795cf41b3094f64bcc58dfbfce9ca080680a16c499d045f3f6419a048a7f7c2faca08909693c42ba1929b82bccec21d0efb5abc1891a5e57def898cc22afdafc2bbdafcc67cff26a70a2a78672c2d351f746a810b3bc6773bdaac64ff65921deb9d1f03c645b2beb999b0dcd47dd66453c732321f5b1fbaca8776e4e683e669b95f5ee037f156ec689167580685107444be1bc9b82458b2a205ad401d15259dba9a2aa4614523544a17fb390efe8eb20d1b20e8a967460c9f1be59d6f529324d8fbad998aedfa25c07aae45496b51c2cf424ef94e06ae9882e2063f7c03fb55e76e386fe7745c24dfb6e6c9d431614fb0cb343a06c241a239ae700f55cced171c227ce556810bd21a945976996f702964b0fce6e99da73c472431256a8aae8f1d47ba1f5537fb64ab51e0140e8b4e57456d267e10bd94d200ecd542099646ad911ea43dd6150e15eb950412f1710c70bf435869817ccab6f9539e48ec781d12e22404b49317437c0ba9ed59991249d7c5f2f07e764e0ceaea07f60a37a5ef2f14c670db2ab30cc2be5efb6f9afcbe3a158c3e8171b406e1fba955653e6f1d844303fee4658193c3abf98d0ba712d092d3adfe8bbd3fbeafc95fc47087dcb73af17d9cc00d612b5f6c44edaa95ba242c577d3cc0e562bb13a881d12d7f0a049f22b15b820ade6dec2030e97e41a39fbf92c899e5e16d4c4616d30fd38a0bd964fd0187559b6c82367091a616cfbd1e6f9388b191b6a0a95e69c9b1a19b8a703c16fbc217b517d6e188777a16c6aba132370adeb7d356096d9500e521849b65f3a9f513ddbe1ca3d0c11b9283635dda919b8b6eb751d10db6c543e2e4f54ae7f1e7799a11727a2dffba1f6b2fea911b836eb7d1d30db6c541e2ecd54dedbe3ae799cc7b85a6e33c145efed78936d36940f6553d09d98816b597bd73775e920dcc2d29a2c3f33ae66187a2e08f35af6bba06f6a06acfd5a7d4b517dfca0d93562927b5f51637b9b877730f1347cb2cbd0e7da4e7e98c9721ad705715acb4363ad37da553cf5d6ab64641c10ed383e8a10ad64e4f3b0b0535b784ee3aa13c86758f9ff34e47d7d173fa6cc5e0620f2eb795e5fe0ec7c70b4ae6163d403619e91b40bc221a9dbc68d4c37180328540bcd9403bdfed67b90ad32951d133f7a9ea3ffa2ad546f2417e747a3a00648ceb7be812628b70b522fb04d98557fcfaaeeb279df53fdd0727e887d279d88b4d484244d9ff3c85efc323233c6d85f7d5ca1be9a5841f5474f351d2a6e583fe025e58485b8e948e7f112e58da71540dca5e2c4b8ac1fa4e7c00288fa719d7829eb07aa42553ffc9de0d4d91445c99485d71e87521e8c336350e0e275f61a2940ae2ca6ebf0236c8489023cae7f5d8898901e6a03a0f213ea87f3beb06d917b620837dd34e2606fecfae3782f45864015016eebe0b49e2d42a6f70bf228a669fd09ece89a682b195daa029fe842fb9a7a6c6cbad62db5a55185b720470648f3a87de888f4c42825a9a5211dcefbb6cb7bc88d820081b5baea1a44c4e0888bf759e091c3df893ece3a5c811fa3e82d4b7e4be741ec7a6724ce7462baeae7ee014bef72704a00c179186994c8180358b38763b8e703a81372c74f49f22c2a81c588c220d84b86776e681395a47f349d0a30956fc571e25ce16348227b95cd4512de95a445da0d206d07c10dd47c0eb6548b640be271091ea33ca04a76a668c8b5814a087ac7c68d9ab3a2b40c4bc3ab999270c28c12374fefacadddb80ac1e4eb279377fbef4f75797d15a52824a3cf260df928fda94d43379dacb61445544617b28f0b176b928f2d5a3d634b8687a2ee73cc1c1a781e9bf66db4cf6f04baf24171a77a5ebb9f7b14fa6fd8b8aaf509a4b5973e9dd05d576c3c22c2bedddbed345d6520a3642363c859d65363a921f3a1dbac88a7913b98d9d76d56d46b6e24541fb2cd0af5ce66c2f3b1bb59596fdd6c783eca3e2be39d9b0d9b8fdd6c81a98e7063df19e61342f517b276fed6457c5cbc83246fa9db88ccc93bc9f34edd8860ce6867860e1a453d103864946f424420139e07498ea96a445ee179c87398aa44f04a371e770b8b50bf0aec1428b11a52f1080ec9df90b521058f541099b6fe46219989f7f3309228a4b623b28b17228f2cd52b82ad6c04b44c4e56c1f82cf9462ef3b0e451a4b644b05b862095e5341f18e79390b59e12885499b624ae93ba30fece284ad556c810542b579b18a566e6aef564fb6218f8dcacda3e42b8c455c502510b76c8a1f4369d97546cfbcea791d0ddb53918207e6cdf6cac4fa727fd40b227e90902922d82aa63482269d5151c2dd03dbfcb19dfba5d00b4c0419367110b278a45149dcf36cd90ba9399d291e48e564e78118cd6a7dc98efedb23158d0e4cd46e12d9fb9c7887cf1af05e36bd64a03a8eced37139b49985f91826593fc65806d648db68f7522d25465d54e73ab7e0ce6253cf77c668cb16841700ac1a3f0fc7cdccdd03a4a3992fc9f2358bd022dcb41de1f80bb901a8c4b713fb87467fbab170f87180d3190bd7d15268bc4c4fd8beeada53492c7e3e686842a3e59445f1960bc5fa6381c298e4505d82b054b21193742f320609162aca63bb87e634f3102c2340617d67ffb681c6987c733e678a8745e8ba0ccb07ec36239e835ea771671d17a222d578c43fc3a24865b2d81155abc9a3a02a0595e4d171b725ce04dda7ff00bb67e9e97884b885fbb53750c828c152070cc24140d3de1f14b93464fa040a813427d2d9c0ad923478fbdec98d0bf2be7b7c7f09b1b94a9ccc173adba2fd42392d50572e7fabf06f765d432073b5be698b21214d4de628a04ff86dc0ea7c3f7cb8e0719f1db410af49fff04d201dc9803daa3128b5a20d0b2d21c969fb7b69f1cc2a8a2eba30a1db1866607b8aa819e381549ea87ce28d490e25787b36da3407705a34a972bf773ab83100748ab4aab0866fc823e19993b98889e0a9c5ff7d23097add8806265f4d29295bda59449a6c3089508d008d65aabd55aff8aad6979562cae08997ecd0f78ab618944847c374aefbd3df3de9bef15cb6dd36c7f65612ad6ad36dffeadae897415059754bc34d9df56b16b4fa5e2505d49a14f6dad19499794ddbbb0fd9ba67df7acb3ce3aebac95a66ad6e639ab1695e6c34c70f9d1f88eb5385b6bb3c94481697ce7a31169b4adbead4fad66cbd653ee9fb906658232e13ecb9a7da7bcdfaddbf5ec5cf798ff69d6c664af81e0597a99bef7e50627ad5fc112c6ec69d4ed6c4d53b5886862f9002f0f516366f72be9d7159128321fe7b3fc6211fbe82cba83ef75cdfa631ae455a46e304351f6a9852175e7c83028a121a34d46bbbb55e0514afb29a594524a29a5b5ad146dda1e815b2dbc5a9b36a53f6895a694c2aa3ce1596b86c54188596bd3a66034bd60a8ad33aa65bef37a79f6f6d2ef4c302721a821276a37cade8e64826b437bb92fa3a82118871445bb9bbf28cfc6e439fe4e0e73a12dca873632b26f60eccb9136d96bd3dcd64ab5244db3768ac65599228647dfdab0c773461de7e9f951bf69307e1b2570f923fb787380bfae3b54dc12c60dd302fde6d17ece98188dc606ecfd5e43aeb8d6a7229ef907a1da7ddf9f8406cae3c5a8ca5ee2415d1b4e8e518d8005f03db1869691497a3f494f847d24f8b04a0679dfe492b343e0fe0b27e9a04f2541b1099318d27b273512ae277687c069734be6be0916e48d48373189f4969e1e976b1cff592c8cb7e4cb04058978b513354d1c755642389c6f310049a8eff529688267fa30c04079366e0a3591cb7e22b7b70cb3a49e84f24cd8b521a23974684e95143cbd041b327822fb5bffeee1bfeae191680bbc7efb5a57b1cef985d7bde375b785f9d8ef7ec511318ddf73f7c699b2c24ab5e95862e6a530fb9af32bd29cf3e73b93ec0bbbc7effb3e7feb037d3b036a833b39cf264429cc9bb618984e2c37243238b1dc8ca272797d23da86b297774bf2b6f890c11e40a1f07bf2d13f06c66868aa92a6d7303721caf341798e7fd41019441e84941d7c12cbfe38b62a7e964edad40a55b16946d823f7bb3abd04ca9b5b5a6bb5f5865f8fd6c65acd03d5266e5afca6e3bcfb3a1c5d15ef1fb1dd6f2009dddc420b7e5b5863685e7521edadbd1e633a67becf77aa6d56ab360555ada9d42758e3816a1363549c0a3c96b6dd9c1b4d4b56893f029850419155deb040a6343dd679e4a9d417d80aec40bc79b7f07eff103ad0a8613e8a46141db60814c174c4c549952dc838c019434848d9228d29293841a7e50014cc6581fd5134c2202df1e5e612c60aeb26f5938cd54b3d3d4568411811056d820997ad490fd40ea4c03815f4e4fee17a1bd6c561d1ce654f14b9eca69635c9fe3f4488271cb06e189ed71d44b3955edfe9d6c2b6d5c54a44155e539ecd861be9cdcf6ee12a03d91f67fa36dcc8cd494a56d2929ef6d99e4eabdd361549c53aa7f0b68da4f5ab166aa4a77d5a68496ffbb4b092defdb490921ef769a1935ef76da1f5a7bf81286420d7a761b9ca381fa91be975dea6b25f89d4405f3b3e8c11656e56353646338630cd8cd0c87d370120f7e31cdfcfa385704dd387838fd341543737373737b29b9b9ba89b192fbed08433d92c4a369bcd767861d18eb7219059f7000206c81410299a13e5a33b35c99ebab028e846040246fbf01d2c9c1c2f02c1a27d3065205eb4c83e73052787d87423ba6444e113c93c310c1201e0c1f3c0828404171bcb18a55e4c3c2a3373603da854190042170c7a00fe1e84ae2b3a047f1ea1cb4b0369f2514a145db2996cc4322231228d58c6d858c62895c3c4777e808134c6688c881f63ab31b68db11e54c41e547056947b6f47d20e27762ce138c179e108e1fcd0c271835503c4191a8dbaad668713333b96709ce0bc707ec011f29d1f6624f9506234c15f611101ee58c2191a8d46a3cc793259cdd3caf372e048e1c0c6541248494ed87ce9a31ccb67e19aa5961328db6a6c4cd524518354e3444d928d8da9968d291c36a66c4c11816d4cd52c657f9c9613d872cafe4f35352b4f26933dc960b3a62a429797f6e1892e99ace6490798233caadf8545a127f3ab262719c8be3c417df1313eeb6666236a5e26594dd31402c48b0cba38f90e901654d1945f2a59eb8b8d2f36a03895cc53c9a84a162353c954b22aba8717261fa36e5496c95635ab9a272f2cc2f92e2ce2f15c587413061dd5d0cf81330b43ea08a3d80a6eb06a8073ca24d9609af108554cdbd24a6866252573848c91cc0a646237a4585252444849594909c992b23f4ed31314d314e2f2a27db8bed860ca342290974c267b92c1b237b9aaf0f2724d4945cd9e9c56a18ba94674c966b270ea69f65473d3854538ef85453c389ccfc16306855f3c70c658c99e6442db8e3004d814193e4af928f3d43d5c4d2ea62eb2cb4b15503299ece949e68935ea071848578890d2835dd211dee010c8104d284528071b8296b6bc10ec73e99235e99845750f9718edc355832cf28c08e4250bc38e5be9c8e1f4d4458d5911111212204ba68b714d5388aacbf4b1faa6ec9f23bc89f21cbf99c96632a6a69b10c8524d08844b928d1048cc690a01b2a50b2d4fbe03440927b27f0da71f523f6633ca03c486a21f534494e7870d908842f29d1f6020657f70766546043793c2cd7ac8fe31dc2c87ec2fc3cd84b22c74317599425c30b862a5ea824520af996c76133593657f560e1ce6c8540c5d329ae3f49f4347932cfbdfe8b00975dc74f63450c717e2ec0037f166e6e38d075f1716ddbc779343c74d1716e9784e87786f40126ea25a4fb2d9d3938ca9c9e94996f3dddcdce4983dc9644c4d4eb2a830a4662a5919b874c9664f32195393934ce7bbe9c1dddc70e1e7e32cea49367b7a923135393dc966514fb2d9d3938ca9c9e949368b92c9664f32195393936c162593cd9e6432a62627199edd4491a092c9664f32195393936cd3ac0c99e3d5f2084891e122e3c48c931927334e64b8c870699c7247a88e51a3a60faf387892fb6d0ebd53a57cf4cf11a33c373abedf5af11d1c04c50acd7cbf25a267b022b0454466bc0d84c098ea57c6c24bf3d4291e9b44e7f4d72d6d9dd092fbab12bd63937c74d5cb7cafbeed52d0d1511774f47d932771bccdf77b3da77badad2d22346f4ff3f69a0c33db772b6ad3877b3e32bfb5ecaf4d219d7d06cf314c30b956b165bfb3ed1efedad723da5bcd5634dc4f0002715ba040f98add63a9fd56a9a67ddef6d9904815eb96c207ce50c19ecbef9b42ee975c76d0da193cdfaeaa1868bee2a6591392b0dd258531a72666caddcab2d9bcced726c434acef09f67f6f0b6ff6c2268bbea2ef3dffecfddad3baa430b46df3114a18345f479c68877ca4d67649562c3920cbb68650fd2114898694b59f29c2164ba676a8ad10140bc5478a4413ed90e7d06f6d3604b8f278efb0ee41bfc15807a175725478d4dace532cef73f6efe5c41611ed3dad04aab6d20034007a478feea373fcdb87bf0e9eec54fb8dea3852ff738b2d229fb5ffac7db51ea8de3e3d62ed7790b6c243417e1244978424e8b8c961d3c271835563a306cd8ccc0ac7a8c0cfebb8bb69b6526f90fe1077a7e27b0c5c02b9d239b1802a5005aa4015a80255a00a54812a5035efa50155dfa7f240950a54ad4015cd0d950a07a8ba6193e346c787e20e1c1e2d1c37707870a3058031e78300e8f400c147102eaba6cd9f010297f3b96d0748c2bd1c0fca43fb6ef64210800afc62542a55cc070e60358ef507e69193a3a2b553d10a44a552a976ea070be27d69a5582aaaea410d4000be4ed5a968ed54b4f2a8542a550f01820811a10006c02fdfa11f340018a83ac08f1f433218a3c21f8e897930e6bd18954aa5023f12013d5f6c8cf2d8587fffa5e3739f03a08e5ace07f7af456a7ae7cd1ef66187fe6e5ea702bd8ff6e038866361364ea323bc2fc7e99b1af36a3ffb728ef64e770db0042d5ff186acbe018439a74e0072b8072fca638568cefca121a258acc90d0f59109c1da1f61ca5770b6b4012b67b2dc7fdbd36e89d39c4e287290f0e6f0d5a43e70b971727c70d3c6c3cc03828eb860f6c34d1a9d1a407083231a32b5890bc2b58624858aec490342c9a688dc0f076a1e763e5c221c4fc8a75030dcdea59345800ae321a924245d50497550a1523222a528c88a80c0d00a46f7b8032049b37dc424b4439289c68857e7c97f2cc205f5f9e9eaf7a04f8be20d77b9b5067fa98d313626797b4fd0c145cd2d738ae0748c2d6207689607101aee857d011ab6ff763cf4b9eef2a30f48ac0f74226493c30b446f4865cbed0be2cec854b1b032137a865e9c10497368b4ede426be4e37c13c65cff83ac85f665833cc76d964dcbc95d780de007f8aaf3d89785659fa57df9f08007ce0e107ae804c0e6839cd1a5ba3e801878f49ba03b0ee3c36019d873e85390049f34a1074e515a047bd2be01ecd36779101272e67bd676496168b3d6b644d4b7dd7f3bc7bfc5727eebf867ff3ac5eb5d949880d13425264659d3346f9bd7ddfb35195e890d2c7038c36d75677713ec572170896f749016117fee65780dfcf9dadb7410f0b5a7f1d71eb3727410550c3568b91cb3f69e2a9cd9439cbd5047ee4255feec8507c85d78933d4f0a43bf2f9cafec859389dc8506c8ad2bcedcdac49a6b76297556e0b2bb100097637657cafee30d4bd95fe7be7f027460cadd7799c3c6f7a56af2805f6a41bcffd2ab387ee2adc9e55893cbeb9e153085d8f7f7b4f76fed4b57d993bd3c40875b48db8726965ab78ff2fe3cd2e54ef4692de12bcaf059fbb9e5064d0021c7d061d2c8f64bd1d3c2d9d94ed4aee74d6c11118b70ae0012b79207b66b4b4d74cd70e6d1e5d2617aea7cf342c90330250f0ccd493ec09bed250095d0019b846ddafd23cb142c019edf1429d39f29ba5f77b7afa0fadcc00d431506afdd6379e1956f4153530e4842de4c90144b5224e55e92022997548a2b525069c02462e98b18794e2ef7d217a83c1f0240d8dfc35183951b03e5948a9a03945973c0c1d362b530e0119a3d4345c4a691716fd417ce8a1386758171f16059eeb7831880f38bb1f2c1a45c1b1810580926341ff6724286870486aa3455564e983c283252776607344f38326ac87051e39b61418d953a45cde724863b2e373a15e068551b303fd83831724225e6e48913d8fc2c201af78c1b4c031d647c13a8264eb3a909130eab153c8484f8794d6e132e425f114e168f0a8f2656401e26519007df94f95c6ea5263eccff722b3569f2611d5ca9f94f59b06c70b93864f18ce665c1071f8e1ab89801ab567eca7c1ca028e1d0043f51c2e19585d0f9a4726ea5a059935250172d59b2e3a801b75290521095ecffcd294220d838c387eb0b7a32451b20665c72f9cdb4e45e9a61e91951eea59950eed7722fcd72c861ac9153c08659d35704c60813f91b60b9b4b1612adbb0fa336003f6c7315fb8956e8811e14964e90bd397a5dc4b5f6239f7d217a4dc4b5f8872c9eaa52f40e455eea52f41a1185f2f79f194e77f5ec8727997bc589a41e02df792175b72e7ae9bbdf66ed66a2cae1be10417eb35aee3344de33ccff362f07515afe34450c56e342440e16a5f250a2784073d053d9941495e80018912eefd7275acda077adcc6d3d3f3332868fcc04fb3d67e9ef7435cf33c2b812db00c5c780106097490ce3784e1ab01a679c1a35c38c2e50c7a0a82d95c16eb7e0fe571a1e7da9ffe2e80e2d3273e79b7856c7f7ef7932d0c3efa0b2e804280939bc1249a73528102030d2f20f19dce9bd7af00ffd123c290114ef01d3bfa38b2fcc5f2723fa7f79ff7fd98c6bbdf610c35e4eecbe2072c5ef09d1d17e60fed420b2b0c81bfa64b83053b8d8feec2387e8f354160a2a4ef11c177ec37cfd31cbfbfcdda2314e18fcb68c71532fd89d382b3586ebfdae06d9ef3f4d31fa50e4e15694541023899d6055877b98630c119c70e6a030b2a1ea9dfe5232dde20a50ba8bfac625943eeef2a9635e4f961eda2be0d51f071c59630b98f2069a8b286567af19095ccbc891868a8ac894790f41442b98a18e82984f24fb662d9e59e42e889f7bd539b7ad2ebf5b242f67bfd03e3e4991bc4c9437c0651ad5f7b7c3fb7f69988a47fdbda1a7ec6a1955acff585e06ffa98ce62515a6bd5706e6b232bb19952d14fe0cd7abf199b22c6de8a083aab67467dec230d0b63ccc2345e7dd0be5bb4e375f568e117ee485c597b67d1dc6cc32e63c809ae1ea03b35a3cd666e730b4700a55c79c8f67373f5f858c43d27626ad33de8d74cc1362c1eef27125776651bd678a11dc49f7e8ee699407318d3f8884277d1e5ea52c172a982e5cd05187ca45f83cb167e512e15b2231c18f1812651dc008465cb0b94b8f202a5a62a48fa2ec0702fc6edc28d2d30f7ddcff7708225f86bdf5a88c4952b57f1cd98f25c724819f8337d1ae8bb401f8697e69d0f3809244fc7edb96f2adba9060478ce71ebe1969cc81aba72c7851d45920c845ea4935aa8ed40e405813f7f9eaf094b428350f5aa84d7381f12f1855c3e0c4660faf3bf32f0d74154f9ba7a46d79829fd49d2bf948aa5122d1b81e93b0a3399fee7547ccad3a4ff3503bf942a90acf8c1063b48a30a4942d1125d0c457dc9324502ed006545952f2cc04116242039407443942a39c4e00c192965653e4b4aeb80072a6224788202131c91338b24b6e8220c26b660411454bcc6d1801612a9afd70c2e838ff94a585948f798d47fba28437d253713a08a04e871be8313346106cfb5a77bccd7c0cc2d22d6c5efe6cf3dbed322e4f9f3c7f9783e6b7ee34c0a82da4d29a5deddddf4e53790ea5010e703ec0663c448b162febdd2b128a31917a758521759d31394145218232c598c90fa47ecc3e94bc3fa51c7d513d3128bc5eaadadbd6252d96f72fb6afbd21a71f7add3d0c671dbdb269ba50bcfd441c822f98e16df69bbe43b32cbe43bad144588c7497fee6e35a0d253d995a210e57b67df15cbc77964fb6f450488279bf7ef8a40a3f38467e6104408b2ffe8f21d1bd9ff471041d9bf678846ad511d82524a1dd4a856d4fd10b746de894646d4294ff7f37b7edb53229b783f7bbf3c1de663672929bc9e006e25133cc937722b998028d3a422de03911d10d96fe338236ed5f75abfebdec59f56fdb1fb1ffff90c68f27f5834477b171f88fc9e2acae033ff7b0634f9893f532c59e0ef10016ed2924daa867817b6ecaf4073342bcac080268b883f270091ef3565cdc7bf7b175b4562bee5c3fdf7dc7f3fc94e3cc240d1fda2ee9b6c59b14890bf57c4137f2639df1f882c7ba9c87d8fb628759e1b224dd334a52ea850a41468dc4bca4b86cae5b492358d6e4648bed34a5056b2f6348beff80a8a72f9c5884131effd10a746bed34a484364ed2916dfd191b5bf2fefb9077afcde17be6262c496cffd98bf221dba3fc9ebd3894a30806525f4c8be17b67c384a646492dc4f927baf0a4c91284f6b7b8fc6284ff7f88dc4c43c16971cf9781f837f494c93dd37b9853084493acf09feac2d11edc08c34447085d015a406f41a4111609c20cb124a50a40684030f5f94800269498a2648cd4709b094e089171c018321527b2f8c095ff3a57af0e9abc4d6067ed226fa7433d601915e177aa1f6df039148adad7b1fefbbdfbe137f8e36d1a7fb4ffcd1be13e78f4876f34573b44b73ea5f71be2807e4feb1c06b9cb25df7b68b9b2417e250fb1b57e0ce1afdaed8aaffc37d1587d01c6d88875ce86ad5ef1e88ec6955b1a502c7fdfd5a459ffb9d88698ec6894d3ff273c2abee519fe6d40a349d6a719b30104b52424c423ba4a0e92a01d1b4c518452871858b1e666490730733309ef0c20495d71323ea5f2e5aba64fa57094a52ee27722bfd3095bd9898dcaadc200b8aec4939f84bafa3a9d1fafe22bec3aad9b17139af96dacf5289164eaf37703ca6b981238c61e6235a4d92c3f2cb37f2101a63035cb6ac880b49421b3e2bb4c0aa7956eb4b2535dfa7791aee6938d17261e719430df9de7c5be1914be43e7def86f6cbf9343f84ce1c03172aa999b65a1d05ff8f28109ca1040e4dcc080109b4840e82a68c76b0c3972ea4fba5455a37eea5236d8938301d75b0c259134ea227133683421f0758d13d546016d9ffd3c26942a0871a0c1439c862f81721c37ade9c503eaf0dcb2f2bfd209535fa5b089467c8c5f4d14d5995c6f654bd56cabd689e4a3f34e5f9ed4f3ca491cb2f973679be84a0e4f2070f331b3c6d1508820a173580810e4ec0a0842d3778e142a50827843c271c0f99ce289902750efd9a2af8e6b2d654c15b2e2b16dd4fad82cd2d16410176d904f17dfa2bfab493b991cb984cff86f3d53d2850f7a03f6de81b0d854c2e3b4c97cb0ee381007f98ab05082e5a8479e2054790f461a03c2950710226322ac0a4a00c927e911588c07cfe72cd5e8cbf67affa778d9b23cf0992e05f06bf14e0feda7d565509f46934d1661416d0a691f6d17c3a68646c46131fe4561af29255b99584c2b8c11877b680476ea5212372975ba90724be79054d7f38722b210565566ea51ea660a13424f3915b494a5196c9add4430e499fef6080958fcc189211d19c420ef371d2c0da49e26391201c807e88f2640bd80d6540d181b4220edd85872f8446c045120da490557481e6ccb29f3a0b91cb914a2e3b694a2ebb0904b96c2929b99cb0a11925973376821160889a86c0c1054b90e577008b0e3e50d961cad215b2ec5152258a1456c4c408c309928a65cba8409172833286a09a8e4817cb9ef5b88049163d2461040e3d4c2e455f42f045135464643987a0e430440e96d0d0c2062c643991f20c02087646133b1471c40d3a6082eca26c1095840d49b8204c03a22821c5173ef890821d90f35d9407e88620bee872049358961dbe24b8a8c2022e3a385541ce7781f27cb0c50b26b6f84094317240cea781f2cc90518696a822c0189d809c5b84510231b40031860b8620e74f23ca03b4c40b968c88e20a26ac3c41cea43cc43534bc7917e03eb8e615045ef74eadd4b310f756e2b57a7737389b4eea64d36ca5d48bab33f88161939fe7c9f0f9247249306c72fbbeb03b673e2876e76460855b2aa81efc155462d31cf057f87e4710477a96814820d2bdffc2a0ef7110f83b88700c3d45768750dad73c0f88824d76e100be2f3ca261ea171e5d9dab93fd7570c61ccd3777bfa0f77de151e7db119ae757d3eeedf2c31465d85ec9cd6e87bcc639aaf368db9e48cddd7b3e3bb17da62776e76c205b2e576febec716fc3ee28869edc1d42f7134c817e479f0bcb8ac24c07a640c384a175f3c2238fe3dcbdb3aa2bdef679376d7609f60adf36918229581b92f0c4af7369bffc3622b512a9e2f5b15ecf9b46034696b491258d711a4f39c1d573ad6d28643d2314698105e5ea25a6a6a7aab16c1e4b9e3f1ecd583a9aa669b72231c1eadf5ffb6d8bac495d27b87ae60bead2aa6df65eedded2c64579baaf7fb5961dc1c7bfd98a43ae73e1b512dc508861a5a18812f29d86420c2dbd71c9ddcfd3f2bfdf3d4fcbb937b23de701f978cffd1c75e2cf0f100d4b2bc0a0eaa61396663edc7731cfbdafbe9bcaeddf85de736fe4fb4e5cd2fdf7e2f74b62be47b51263da47f5a0f833d29c7e15ebde31b7c7fadec5960adc776f84fb4e5c72e4b3faee97c43cf74696acbefb26cb1e16540fb63c266cb938f35c28f3340f44e219b3304dfc99464331232bd2ccc8e00e56ce21b1351f88ac6124b6a65824c8c67f93325fced89ca239fdddc770dfadc49f239fd5d3883f734673fa571f23aec49fd924fe3c27fecc256e896beae6eacb3995fb65c26de90b5bf3ef7fef85ad29b654d8de7b239bd89adfe4931658c8d00191cc1ff9e023fa3fb07e2afa782f23fe384c74a3a1d2619e1185cda6286c36c50ab79a70b311d6089be6546ee6535d496134ef4d34a7ff7f8e7cbcc7ff73e433f31eec00a48ccf77e483ff137f6844c7e28fcf884d2629e34b1e8b115bf481c84d96fb4b5f5ab5a85824a8696c5bf2a6dcdfba829bfcd5e33ff289f9ef3ff1e7a8a5c2f7abf7f97ef5893edf63f1870e893446ecc26a4473fab9d0a849ee4b3ac4899cd1924ee4c49611d583ad22dc33a0494e5ccafd46b4a920c2104288a5d78ba44b34a7ff07e664f735700f8aad2f692cf7ef18c24d36352d69d10105534c01d5894b6a7879442aacc2684e7fab556486bd224fa4339ad3df3dd78fa3095c80a0a4dc2ec86535aa4315c67d2cd7a1dcbf430a6eb2656492dd4fb27b3a4567b99bfcc4964ff72d178b6c496449a7727fa703fcddbf11f56f46fd9b50ff36d47da94de5feb2e5629dda5eb9bf3ed518cde9df421c5b70931d397359634b3ef66b6195d19cf67ac09fcbfa945b6aaabf3ef5d7d926450364fd46516ff5fb431a3c9be328f7bfbdd84925b9379cf0f7aa5f3d7e2b963726a9c592a5eaa415be37066390c552959fbfb21337afa5c2f7aa67e17b16c06f2a0ef146e2ca9bc8da8aac30e404f023ea7c8bc2a988496898e4c57febd3a7e20ffd2adeae6f4520f23dc497e53d5ecceaa1398efd59eefdb4964167b5f4d27b29bd97d639e9bd18575aeb6f3d4257cd87182d9e5077492d76b125585d8cf270e2cc48acd0add9826967675958de7e890a2e79ccbd34c517d9abdf8c16b8ceef8c289842779129cb2b27f859d857de4012fe0a5cb2f234810e758ff9a0fd06af06ec5f7113cbfea5ee31bf9158210f8bb8e7c42ee639f367776f86e5be14ec623ee0cff32dd27c1b9b6f89ac514cd2468471dc3a4fa8e290114e18320257b5cb9a426c6ee943e89d3e2a55652b7ef718fd4e21a1efe872f95e172ad1a6d8d2bef30859138798616d9849a9b7fb1605d2d190bb6fddddad241fb97f3fdfdf78ba0542e3cecc53890497890a61f98b448214e4b2274f2c51b96c2e79f6129daff97d92e77ff331519e58a0f26c51fbad9fe646f265db1e0faef93df37fdcc0a8d32ff59b27707ca77e3dd3e9f7a3c7cfa871552010d173ce8994450b5c624ddb30beaa0f60174b1a9aaef3bce9f97b768e0ba3eb6ad751ef6657bfbb0705a2c9c7fe616306b8ebffc8d5d301a6a2d8ea9981727b81c49610782185c695d703a09c9a8019d124072d9787e6f2f3c7f9783e6b7eedec721dbc3d7f77ed4adbddff3e79fd03ebe499bdbd6b78701e1d72eae421fe29c1a46cd8f24449c9092633d8522c2ac88a263018dc7f438a97352bf1f9acf97de794d7dd65dcc8010e62aa12441153a80009262dc6962e4f042134851528d2a7f8cc9ebb837490f6f7fc94c722252155031d7cc8c10ca8082759d210620ca230366cf136cfdea9dffe55ff3a9f3569e054478606b833155b44fca98b2d2265cd5e7ed9452422642422641aca53812acc90428c2d1f20c1580225984cd9c1891dbe203d2997e3055ec0050d29aea4a1040639bfef05f28d82bfbf1fcfe0b5e6e3f80c6fc03d524c5914a795f8c76419ead7946270684e285218fc6f8a34f83893e6d2449ab12916c9fc1097f95c2c16c6973a2b4fd1e7ccacf113fcefe7fc1e4401fcef4b24aeaf00b8954ae0e4555596a166ec3b33d32c43553a828cdc4a470ce572222995202a4f24ca53f3fe5346796abce32f6753f69f42626b5e427d306ca9fefb160a313f7f3654126fa920f3336f44c608cdd7109be6dc52034079f52f13d2844f73e617a1abf73e85d5d3b74572ed42d52a6c81fffdeab7b005fecc5bd87d4c88df9b604b859997791666441991055538b3fde98945322f660d898c3891eefb8ffe98f5845d75826d1bc35203e5678ed64cc11b0ae111dabdd6759d26969b667fb38e7dfcace898c0afa3e3e29ef27062dfcda57dfd6dd3be7e7dda2ec033576d0ad6beba3b88827644fbcd841946dc1102fc36cf3081727d8dfb9cf24c3043fc4ff077f9f7f86f634c8f3229fd39df892e31e90a49da6bee4968904a4283e5c4a79e0eb5d67e1ff1df44c0dbfb86c495fd7fc3f1913e8e8f54478796a0ca3add83be57330577b9d449a1a172fd2452e1f65e839a5816992d16b8f74416ee37cdb1ab5cea649d1ecd138394ceb462922df092239feeef2ff19efb25dd5fa9ec84971cf92fe1defb25f7bbafdf22a2b55839ca0b3500830b281078110328481a831824b8c10bc06062820a493f090d355330cea54eb66f847bef8ddcef5a5654817befbbdf44ffd2e585b608b5d452ab5324bb5816d142ad2c9291b86c58749fbb7f47a9f94a42839075300473ce5c36150b9874ba7a7efe3834ebac4f77058e7173ce893e3eba56606ac16705d7341d8c3156655c552550d65af071b95c38761081cb22f9883f12d70d27dc34879610969384a05ca8f4ec57453dcb90a29101000000001315000018140c8784429140289ee869a8fc14800d7c9e4a6a4c9bcac35912e3300a21630c200400000c00100011229a511000639641d0832fe6f7dec9024c1683bb49cc533adffa2cfab54687740fbc290e900e9c8c289b0e94e9de0e48291c333440bc8c60ce5c851ee59f20e146a9ba7da879ab0427b6a03b8f9808a5eaec3e1eb3c49c23be61473706f845fb20d156c847d009d5fc1e434e9baa4f13c7ab82e12dc61de1292e38e8cdfcb3983fcdc4f743bbad3f151185bbcedd9752f4787fb3383f33dd7a00bddddc5731140b877f3376433bec277d1fb7b4f533ef82336337746664fc3ff18e10a8236f008717a20ebaf70f74f2062d014b3933c8683ce65ccc97bfff7e41831fb8c8459b03f494bef570e9ed0e0f575de953bc3920084574ef95083989e460527dbb8075d09aeb60b2d30ec77456e160092b1a8ac74b48c18a8cf0f52607a46bce249f65d08a28402c8151b22cac438ebf560c746f49bfb1281047fb0203b50c8ccedce563bc81dbb7c10f5fbd6c32b35a9cdd95f03606a5415c3c54401b1ee2ea0f62a3da2432df8224d9ab55741da1c5b46404fb1b68a999e571c65f1e0c7a5833c391ddbc6712ae530db0857ae8b333af98a1033e6176a319b12b62a1c8eff63182303b2d386173a2d8d53b51f760866b183020f17137eaa719eead6ce34f53a7decb84b8d0b6e3e77b86bbd4adf872418995f1515fc654ced9f06b069c632b1c5dfb47348ea9aad26ca290d533cb58dbbef3ab20642a0f6b89a78b864928fd988245155fe8ea2b03d199c28d4438d29c76e180534c3b45072e125ef0b975b0967ef642f9a3cef8cbbbd07e1b5028f829d37777fc827c0b0a1423fa16ae53a204724f1252438c78e3fd7c15ec8b634a611ad81ebb7a558d2565adce52413a77511f7b03bac06833a5c2d045ea81359f531692d480945206f4aad60f70bc9dfbb780535f5746dd6fa6ea365effcf8193371e39a8a026f3f250a0e280bf358ca8f56546999a56d698fc858ba94ee25b27d4f7b69204c115d15f78d9f7ea4c10329513f594c4fdca7a948c9e4c6e3abbf5076bf9147ca100e770db455aa7526a0923a7f2fe88c038ca49b360fb3fa22a4893353127de8823ee9433f1693c8e10ba94d6bace30c57b0da7579c5a0c1f80e497c17dbc9b5ad2c195dfe7d9d4cd85956b928f147c78138869bcd43fc7079f462eac0752cc3a70599666e47c57a46a0c4d90dd98c10d494a6ec597590904a8268e77a54a88f928390ebdc21cb91ce5ae3396aea057e69b629fa49a467aecc941215f41e358edd57008545596ae9cc667784f4dc869e3b1a948a8e6dd4a8453a7d8d61c831c1035183ba96e8033b387bf74872d93490d072130ba4f4f743d98b3320a25ea7fe06da78b302ea368524d3a19da73287cf22ed51804af2cae26774602ef67631b4f4327586fb702c45b0586f7ba4abfc8289d71f800de5dd71aad71b76be5cdda819b3f4716fc49631298f56caf539adfa6fa85ed5be16868d612f1e8c71ab6e7da0a747d7a95f630303dbf64478511605d7bf163c2f259cacd8c0ceb867153e42d5c79cec7aec8e0064289992e538a3641eb2f994f1398599952494c0d8fcd198daa0a9a644a578d6368536dbfcd6191ca7dd10e634c1c80241861ea7e01696f41adc468f217e4fc48951b8578a553658cf56310560085827ed9eaac673c911da01b55e1fe34caa670bc12981747c8754f0661168144b1a2b8eed1979f309255244cdd7a44c7531056e975e8b33d5f7984542283667817cb4abe80c6290820c6cbff388789e23b74d9b3593f173770419fe9887341ef53ef9f561cb30f0f0ffcf9d4238ff4f7005f303daeaa0b12d74c0ffcaf9a3a7047e8d14e32b13254892e4134eaa3ba9e2087a997605ce1381344f3e112685e503c7654133b9916e2673c3d7169bab3bf0a7cbf131ba01d6d3d70bf386ad140f06d925da6d7ddaff0d411d3520c94a7d19f6b59e92541c307ec1b8562281502fcd25574997565558bb93ba341f85d32b2889e29ddb9448fab76bd3098e23ca380b7715ff95b3405cb199a911bbbcefd19ce79e109effcb90d58651080363edf0d583e4184f34161e78daefeccde7a110c4bd5701334912d06321a54a9d56d79f04bf9aa38064e193291a43a804ad1047aae852598fd925346d2408a974068b0b37042f429eba8b011397764bad31d16f598253f3ced1419e1f0912abcb033da9d2ea2deb9c8ada47a3c898cd440f6a4fd59825912de7bc0c1685339023bf1399cc1ea22d82dfe60a0f69fe9747347fa70e314b7d3c049a45d8c6cd368727aa19960275c021f150ff4eceda82a0ebc7d5a1a4034e02b29a7bfdb37cb6b60a92e6032f1bd21c2e5e4e17eba157fe31d694df01418dcc2ef1a735da1376b8dd2946822670a1294d1e66334ea580ba0a19d3d72b33ff14b91dfe58a4886ea2911f1a7a576a012d812404819a419f912c9a02d6e435e5d1d14859cb67245dc0e6d3f42df70645f25c86a91f2254eeefa22b20d56430f3ccbe78608859cad588145e9a5a5a8104d4a0851098e72dbe9dd0daadf0fe5554ffe6e9dd4b750fa47887140dab30d83d31bc97c60c4699d76f6fd91dbc88c85d2a42135bd4322c6c7db98b8f162c85866c9f4f4817d35ce361c9df3b9e8fd77496274350bbc608c95c64ae27ad42f78d36848102228d7fe5293bd07cccfbbbe2be2fb9088c58a1efeb52a55adf055718f3580d2ab8d538841c2abd9c8a0d99370dde22267bbff7251afd4383951aed4d41ed29b804699ed5e75700ca901ebf0e391e7bae887b18357c9ebd37e622de32ea979f918337ed5a2cd62a3fe17f0bbcb4aba72053de62403f70b3477e93c12017592878941a0597f50a8fa7f9858761a6728f40939433c678016bb667fe3606f2f1e0437d2f40d925a3b6820dff249d198a32c91ea4fe8c87c0c9de4d81e1ff7a1ebe87b88512fc041e4fa37c681e58b452417903e1e052d1644b49341aab2a4c413ab8d45bbf067a5047ae9e17fd3216d1db51d73c8b3e4714e5b1407a1c294c47fe39140ce0f1fa8f72764feb5a0b12bf223580fd6936b93e0af1c10b22658898d475875856abc65cd099dfc5ed53f544b304b4dde85e8461b9394af3a2bafd8fbe7ed061b30acd5773df4d616cecd8c40d01cd0544248cf8f0b9dfc9ed95e70810a5e17ce2495c47e348c8a9f067ed6d37c06b5e832f30d8dbe996f006b291ab09230f6e2450c11648b9277270af809afe3b155d7f5d11630b6659946581ca041a61892dad7f1db807edc3cbccdce164233d3a1306617c01db15a3da534d5c615088490eb057eec5f89a11ab5e6b6ffb5c45d6b97ffbbcd2cc69b7d04860106bb0a2c893861546a64444bb30efd160763b79f460e39a714757f5174e21de54a28bf7f5fbdb9584855d8eb094cce3ce6ad63f297563ce093758be695fb87b3a0679ec91a7915a85a88a479f23d01681834afa75a93798dd1ce4edc828d58d2995d0bcb27fc45c5ccf68ecc87e505ddf179d405bec48d8b4dfcf574aa59ff23378f08e1c802d54a4643c6be79651fad3b87eb81627cda374f75de9ff65d774fd88c8002dee0374fdaf31913502c7139206403c832441c657c522cc20be0bcf33e12242cb278cdbd5bf97fd493d845e43e44f2ea94a3e25577bc4d5ad7ecbdf668d87057f76e745b22d9532d0403646b9835a3650036bc94882c2ec848affe1ad98d918c750727b2f5f23fed996a89964ef00f7c8bfd5e4823f8752424f815a838659f05e19cdbcdd7506cbf2fc06f34a4c4765a7cb481d945c379d1023e80fcff8994d27a512aae0eba610e516e336709d48a55935b0678353880e1a96430e0dbd079ec297896594b9d943b3f91830ba6d73b1fa221c4f6cdcfc7af50261ab6de5fe6d352fe71ca427741f21c056fb500199e4f5491064e99b0473d956c0f8c916e06470797f6a6aa85b5981739951d2c9050ee0ae507026510cd0f6054373a51c576c513bcba1147e3d7f67846bb2c249868eb892911417bbf63d06e21c3e75a4a820a0771b3e81e14e9c0718a6c8668efb4b44e5b48c78faff81a00f0464638620cc517cd795454633634067ccc11bf4c761af223d6550c990c5e63124bfe4351a32adb044dc9bfcaea1cc1ddfa05a7614cf37ebf7677dd1ba781855dedcf56e5ece2caae5d5c063bd65a575db9ad5fd4089cb6fea29c3835c9977a7a460e0ce8190ec6e30f8e4cb4756055977781c6b6597a79fd479aca2017db961214f0f115148189e1d2be096c5c31a404a522e0a6fe17950073cbe15b31ea62331d416c5c9294e7e4c84b35db69e55e08f01befe21d24d4e6619b0131428f4e6c525611f8d1ed5790bffc89f60c30f4e3e6b9cf053a14f925fa57dfc20baa3111476cd7625bcfb02b7e8c6b788b74ea39e4e4b92c2481301350d5fde330179da6f4c62a55f259188267a1c8d42310b54b79794a16a29cad43e601d1358e40195f4298834466acf335a6de76661802a4915654601a00ce5a1474ac753209acc2f2919967f1648c79ba0d8fbe00c3e9ffa3389e9fff8cdb4272386ad84f0878defe9bec0517bf0bfaf6a12ded6e5e519a1c4e5a979a0c7423d088ee55e10953b1c9c1eb26b412b87d7fb15618704d2851c567e722bedd0ea14762aa9a7c132413dbc6b7fe5b9448e24790601edd6715baa75bdd6002d2dff126149e530ff2c819d4e447e545508c4f4f81d0f7f13f2d49b66f1fac31f66a792e74e001f76b626ec9682a9ed5b92c0c485bcdc05cc197ec9bdd53c27aff034cff780b6dc1ff162226a02c32d57213654f122f229317dbdcc2d3e5ab4dcc9e51ad85df1fda932519f1fce93d5310c2fcf137554e85ac00e414b06a8506880e2df70f8051f75d9009f78de808b92615452b1508fbd19498e4e1a014320d8982fc4fb16c06db3371318d6db2ae6cb58df20ca47b566bc48fae66a32bf01cc052f37af7c14c9f8ea61131d15f044d531cea053361d0b7dc19a2f5b7f0cc4a09bb48dc92b5c20983c161e3324a12a112caf9e07651583d22debf31148e2f853c027fee1c8c81f3be5cd0807f98240bd30f063d7a6618ed985512e5a92e3adc11776d5021457c47fb45c0eb483882b06cb96d531be3ab3e3d25f7ce34b90b6f28b8705e8a29b7e492fb9680d92cf74103137c720e59fd63ae76ee56b82e2824ba8d5074b17d83c71d6d04789132a90052237ebea34c048e0ef54f0f15ec0df54b1a1749243555c1928a8d2f94fad75568be338f9ed747c50e736376a8faea39db475775973350fd1ed1f3288005559d608efcb6006bc8c2f80c724d5cf37efcfe29dd2f09c29b4f0143956d177ca7f394c3c487f317d57d6c7f98d6013e7ff89d4170bb87d3c0cc92fe32ae9cf226a3df9e693b4e9709981a0d6c9c349837f9658331027d73001f856774a229917976c2fafa1a8cdd86ce49679a023676fca03e6990f49745a42ca128e7787819b6474494fc2d328cec38b9dc26effd852709b2ea01bfdb89865b07a886dbf7fc59eec8a38427770fa14d8514dbd3c1c1d5cb27ff72e42e74d67ff513b74ec5459e49abfeb4e1340912c4f715fc1bbaeffba2431aa360b4bb188bb54633bc2712913163f6bafa471f85f10bcc08103293ca8cb4263103d9dd669d70b36f0947fab412abff3f517a50e86ca5b6eb5b7366c000c8189747ac67f1558f0ffac7c0a73962d6ed9f511d482b81772111be213c7806557008f208ac345b2cdaba058d733559a4dc53e326fb34db0a687035e103389ab1014bdfc454ee248fbc2beee45bb79a04cb4ab5ba87b8aeb61a9f176992e9b086b5022ea467a1c7fde8bf3653af867bd1c565f738421d5d1054ead55496b32c8dac0b86d46170cec098ab8606a70796ad19e2725ecdb06b0fa5024424f2635ffaf1e5abfbe3f0e41e509bd5b135b24062f9dc2347e7c1cca315c828b477de7d3d270d319e219429999ff3332c3b90b06dfc8386e59b19095c88194341c5007f57d12366a47099a7bb6d70a0a6129a8d725a2828a840bfc0a502b32156ebb8d176a3334122413049ddc0fff64ee16cecf6d8b49ef42fdf08efd14a1abd4871b4858d7aaaf3badb05dd6e888c154341388a1755de7fb1494a9a7124491625584b8ec5cff14f899054772349897fd31f33e661bb731b82b56fe5f75c8c422f456fc711b94ade2450189118b4bf23411b162d6735ecfcf01acd3d0ac325419ae8ee8272f914579aa82462d94bf3310d2f200057beb64850e257acc99d366f30a1998c4876f38e77227ac514ddfda233b8a4a8cca14303bbe9eef3bc9e8e36e6a4ea6f3547aaed4bb466e632360fe791e1e040c79cd958ec7221622ebf131c92c0daab57a7b00c6086a364832558b60c2accdd80266a253151e1edc053b11a96c4dc7b96c414641fb734186d91b323b9bcf2b4c9e1cd4f464ad8f7151e523bfd837f502c197c22d98171b8d8f2b33de7747dac543d365fb1becd10449dd4f5ad3979a62a8151f11e74bbf96d684b6cf798f3d79609de521d2cec7c764347799b82e432ec7ea262f957f5b66131c620b9cd21015b12c82f80c13f212f165a4b78ab71e900ebe8d415da08170bda9ee670409bb63e696b672e9374d893cf4c7bb8ec7b6cb1839c416b5663e6b3a5246a15627163e997f732e926de0b365499c0441257380d8294fe5c7871c540d97667856898b3c723a23c05612a25001d35d61d8ab31ef7ef946a3ecb9e70dff308f60e5e6fa0033d379a99ed7a29692effb16788c24b3e982a4116a937948ea92d9f74b4a77dfe63f179abcea7a237821bf7ac73e6674ea040646b25ad1f6d2fcc7eb0896e090a9ba46f0038a6674eb1f0b5360cb5d351a9854c8787c7a3932b40b1551af460a525fb3c0887286cf81cf45e12641f903196686ec90cdcc537782c6a6b59fe04b1b8931cf66349f96313d7ceb5110f2b14f68c5b4800110d6a88e9205a881697b697fae3d04572157d3e8ebaa5f71054d68208c43a4b1322ba2dae1f513e35f3934182a188705a85abfd0e7239669f0a6a0fddc6ed1892abf86cae13848722978905e472cb31b43bf9a8eb520ff5bd5b0c592431e8eaa1856f8d97086946604ea71798b758a275375c43578e40d50675abfd476c605f7383fa4df21d7d92142e881424b39e1db17b5fb8e30ef5043b7d1872e60d015fa45dfe63a59583ede657143855c75b69ef9ad2403d33eea52d1a2ff4b973e183226d3044bc076ec4ec654c941e2d400b35384bcf4f54dee43384829e9d6966ef5a9a017889b703b9225e0afa897d56bf703f15459e2dd2f96ea9d5644aa97090f43eee2e293c136f962ca8854126aca9a8088b372220d2b5ac3d41aff870be921d782a4c81be6444f611d3b2b48bf2ccce3a2ffead5b2520b5b8ee12f7451284cf837dd84f5c1afc048dd97368b6095ff40a29a6c2d5c058c15f4a548fcbee8606e01e9c8c80c41867565d033423ef3ac320aa3ac370d7c0c783217ac8ed4a7a8adf2233dffea1f70a6101217c28f46dfb5be202072a18150eb6643585ec178fcb944358f1917894ebdf745df77f909d8cc224c82bec24c00dbd3e9953e0e25bf95c0eac5d07acb943d145fd7cf517fa8789e60e28f96620b9b2ddc54c6b2e3542cb20ba8b8aadcf6b6294300bc9d6af906e210fe80bb29994901049f4b7ac9d888037b87693295f1fb7bdfb7ac5a6afb2b202170a38b8a43d24f984493f00af5a9a03f91f275f334314d4166c395e4da48d7e09ce78414a34d2e1807d0f0a3170405c83a9da78f7ae9fc6d1e42ad428c82716bd5aaf9f363d81ed8d18713f76df11d430506f081d9eb20603f6d0854c30f8845742a652cf1c6ef4e99637f4f6b8891b8d00e741bc65b10451f54c3c49afe951a77dc8f0fb26907432c94fa1a9c795328e0df5f3355ea49c553a54070945823a180b2153e9b51048270ad5fc3969f2171a3998880c5332d4c71bc8829327b425d697d6f249c98012e7a3941ba790feb8e94af2e417d3930d7c1d48fe646e7d79e9105a6f885172b6d552cb329bb7afa19fb9a0e562feb6c9e6c44abffd32d8fdfb96cb317457680949cac750e2b243063f2004348f45605f805f61d008d07b0bbc47bebf869bee4197b9e5fac63e101db54cf3272e6b1eabcbb24598dd8e0ff6034ed61ec2691c0e716780446eb81718b76e7a883ff1770e6c24c399cc63270b7ea51dd544bd3903f0c1584dcd2b463cb4bf8792c8a76bfb454c1ebeeff1d09a11435e133b1cd9ff78470707e2631fc5652a861094c7ab146e8c5877de93fe9456a3583a0aa7d46699ddcd9004af07637c6aad4d184bf47038abad96f4b78b8f3ac02a3a622290e9fa847bcb49211095850e7e8ce4dc81da70e9107086cb5422aff52be094b9bf0d5866a701464ec372307c43e352f8c9ed4db01839bf533f05fe43b804b8cc7b40c7b097ff9a00997665dc5064f974b8c08ca406b6d73b7b42692359f64e9e06ba93e9bb82869a050c498bbe684c865a6586524d27017fc9be403a72d0f3c79821dda96b368a1b0daa8f8b7dcf55d1d0860429fda23ab136c94afbf4134e5d18aa5b0e34f1367c02261fe1a4c9ab1995c54a41278a36729f9cc1782c46c4f24fed434a5b8478c8965798457d26d53b68b2ba0f28285ce3f46fa61a2c9c1ba07fd8efa3d9213926aa704cb51006952891e591a9353f87fe301f07e385fee7160e068447c8782dedb637925d8b45fe87505b21a9124131fd51482af49d950a5af5d613c1841620dde13a8137d02c35e6174833b98db21ec32a3cca4889d51d0638628ee2c003004a1fc2f0b8b8f83bbf4d53062aec353101bec38f9c0f83c01463420209a7944137099ddb1a5893d1cdde512cc18113cb1c3a0fb52362ab17a745c73702c56696fbc7e40023493ccae8d2146042562ace5a85c00c2a51c21014d6582828250b948c266d6e8568cdd02f57f4c4d56b23e00b63b43c65260b6022491b8fc6254ac071f03db7c8fa4749a59978208724cc4e2061bf3368d3e49cab6fa1fad7bd257b58c349f50df760e9aa0cd02590fcbe5e39733a73a3c42f0383b0d9b48b5e7ae049fcda3bfe21aa180447344cad52df124882d586cb77a6a842c431b979a6da708fde7abbf30a52aeaa953795f1630cc878e7ab8564b80366e64644a6062e5f51973b348990bbbbec7364bb53abba9f0a9e4f80546a20dc03e276d81fd789c8b8c1b0ba7c5bf00de128c6672a15d7de91834c45318bb5d800c58f0d5d6471a4c14f1c7c18ade9bda27272b9c95c3673fe7d1c5b441b9ad4bb9d2920cbc611e6cdb9a376efdf76412f0d789bb32374c3b102918fe72bfd2b401c411c800d37430ff72d28b73c8afd3b9563772873ba272526e6a9cc6c8b9e2c71090ced306d01889fc2a50433575f7654af31d9e7fa4674a538a559414fee98f249003c86adbee5de8e50fe79c083a793e1c9c1d3d519835f86221cb554fe35e34ee6def8430afba2ab01b230780282c5a0d313c2f5eb3dc3bd008d7ebab492d3d1d2a93f27269383ace5855e3c51505ba75ad9046e6580d00089500ff02565994f83d87ab8ac8735cd747138cf4959159d54f1b4d3a03325cfd6fabbb79732abecacf7ddf6c54b111098aa4303b7db163d1e6cda2d9a10859431eaba259566d843a8e373caab394614099bba2f95bf630f792dde34606e0a5cb34baae823dd1ff9fd9551f5d91b891f34acc8ba54a1e71eba93d1afbf4c9b2b16f6f0b72f6491f5408a22c0e46398d6acc0d6bcea34985eeff6b1c6fe10ae23fa9c120df1aae04486a5035aeee90d1a160a058c6824bac6645b886f2d732423c98557790566075a18147573d532fe7da92af1facd14dfe0c3a08b4646f1ef949b0b3efb60a7809adeae929d677c383449cb2be4879247fef11c077d48476949c7d0ddde259973201abdbca69360e33cde39bbf05892c933468d613b7112edf1d42f49ea44e96163cffa42a278e123befbb9eab9408271a44cfc28dc10042713e973bfb24e8324c874b7007ede2f4fc00cdabd68d0a65fc214b4be5d7274b1f4423f1d60dbf2be1062b7b35fcab57a2f099c21248cc2606d128a456057f4b13ec0285e7dec0ef72c2f684d3b595b463c9b077c7c2199f06c46afaa215eec045f7a06ca9d82fd39f6ff8812ddd0647e48d98781f00e5987cb3597ae543f8297136794df6be7ae05b452f7054515bdf284d027d76214a49abef068cd19d299674811fa9dca0d1fb4caf3f4c3e51f58af67812676e0a6629f5bdc2c52cc912351cc1e5a81932e4221b31ad77cc6e2a800e38046721ca929cf297bcc6084f722553d8e4b75054f6813808652ee440e8b6e72ba820b8e730190fce946eec20840999e40878670b3be52f946dcb03d63d15b19dfecdb31fce85ad6268d66dde0adb42c8d47844464cfb4c11a0d65aa2c78ec71e7ffff8f8e55316ed6d1ce9a0002ae013b3b70c7d5492d4c7344353811572d1f6977d6348babf82560f6dcd62154e8c2440b7efd8b5b7c5cd85b34d06c561c085aec33b95ffbe7caf7505d18769732e76757a77d628f748badc861dd614d9265422dc4dd5e5fc603f6c7f82d20039478833f3682468527b3303d76c18a5783be15286b01b7714a15ccf51cdbbc98b1253b957e3c03a71c7f855b985ea57b2b65334f6414cc0b0ffbda1c4c901ec057a568cb02fae8a084d7657c2860d73edf6b8265b0631e2d7f4630d361a7b47c8d1257bcd290e2eb1568168d8be14c9fea5a4bc049b8b154f5ba779efc3a49bf26eef10ddc5e51aaa862039347084e2c95aafe835a21583a2309e1d329806315badf377fb15a1a363a1b943c9e9b0d2c9cd51fdeff2fdf36fa38cdf4408db922f116770d7470453a29ff39413580c630da4969ff79b629c0cd343f0b0722d4907a27ebd07fa636347ae96b42cd1ae2f88e7803fd97ac9705bfbcb0eac7247a652eda9819017cd21b76e00025299be1fa008ad38e3b83260444d2a20d095aa43b2aab37faced168f2a4b918036915c17f246161dd4b7e856fe2101c6bdcce328f68bd477bc6d6dc7dcb382b1ea5f5ff9da2f2d7ea9f2f3e091c9ee3853945a6ddd976a795e94792d9afc4036d07f21763ab3b1b928e0e42313e89b8ff3f2e6baff18cebdc2d122370162e65d7fc8e1bee66122fd27be0759b9b65372c45e062a278db8a2c395578605570b879b0fc130006f053e72a21467a33b0fabf19edb12e36dcdf77c35875b03c75b94cd924154f1bdcd1f3106f1ebe83e6578b850b9e2b53711fdaa3883fe21962c442c35ce9ea300114cba3f02b1702c76c424195403e1e6f9eb19c8a990eec2b158e65a4f4008c15fe5a4adc9497db392c30434255df4e25b8c1f5ae2501f943f9a863033302dff94ecba74d78ab524d1cec2d98bd5cf6fd7f767fd100fd16f2f16d2b11f5febcb5a068859f666eb5626665579134e10f42b0e0ea73d0f4ff0ea0884676da560dedc5d999b97b05d1acfab1d8cfac88d280e7849d2dcdc5b4971614a5467b0c93d8ec6885c9f5e67a05f2526ec9a7061b61ec04781facb52a664f2dde64b88526ba61ebff61cdc8b5006b85fb28beda959d2763d281a5bf48116989452eebaec15bc19cdcca086d0b64953f50b3cf4d84c3eab2955b561a5cee93d4e29d6caa4244e967e0a939834c72c8de53b4d6025e77526423aff6b7585cdabc0742fbd6f30bd22b53083a949a511dde0f22ba8a810a8a051aabded20ae8173c40568eabb3c13be27feed5387e0c5b5bc40e101f2b35b0a605e54b7c58754c55ce0963cba3c09cd4bd56a225f24b9aa31ad8337036c0771dba8f88a0265a18d2efc09e6048dba0483301ad48db715f6251bf2a022a5e214d57c365d0bcc4771d8c32b222743f11c9bea417d3676cb2e73adda07c55e0bf64e38aeb6752649110eaa1d33d7475291a6f5dd01a3b7991272c92ba57799c7be0363abdc5e02998b0ea724d6a11f78c44c91f6b1e98a697f2f86d0629bca5d867a71bd0ce73c285c21b122052042757d9b52866172c12558f4c3f25ab75f1a4415ed6c5c71713898bcce2b0b89820d78465934e1b7819f8805ed3d217c572958ed5780cda630afe6287ed7ae1455546771611cde116262fcf42bd040b11a9f2acace0c52501c1c3a2f05a6970476d91de87352b95852d71962d91292b303dd2f5985697d168cf4c939b1d6fa93ceaff4b8dfb1a5cea8c30702f6451d8cb1dc9557ac6e939021409f5abb8599cad33af313ac3d037be9783a98f0c0d281a1282139e5530c9a305588ba9d2e1fa0694fd605249c982ed2075e310dd132aca2525ad9a6e70817e2bba0624c4d15be367ccceb96fc7b6c9dd191c29900c7eaddbd3305cc76f730956974686594d12b42b5d57b41948fbb0d89554cd42a58159aee36737e329e72eeb8a7ea1ff2a733c6dd0cbdfc7a5a381844612462fc45d8181356c166cd79cb1a62b0b5b93ead7ab5fca81034114b6848c75cffc671c02298d7093e1d9ae72f3b09ebcc819fa0b517d2c29a7794253a9041007b6ad59ab5b69e4dc770d76a5519b4576aefc0f548fabe16f66e3abc9e5bad8652671d2527a1ee393ae6fefc10ed56ad6599a3c818e26385abb4fe85f7772f8930ab51c514b7bdc77dbe35c25c93221a440ded93de58cedc6167062d76d7f08b937102643acf06b614d123678855319e76a46a603bbcedc57fdc87b2290f3fd7c01731464dc1f953ba899ed9ddd3249a13c9355f8a9b9728f2719ca04563b4c10408d26e22f5533f5444aad051613f393ecee7aaee05ca07ba59d1f4c16791c4b2dd84e925d3b739417de7658b6184f60cc1d9cb207178a1553b831178a7d4d4dc9ec611ab34b20017a99efca3bc6f5f74f76b986f82723b855874f26e5e5e78c4222ad2ae847c51367bf8be431d3b8f78bdece0df61bd22f8643718580aeff73e5c776f851db7917f3688e18bf705c29e41e4ecaef41959ad4334a57234b8ab0c306a2063b8472464648830808e43781a52e9f474a6ef66939e3f7cf694b88170c5bcd97af64ac63cf7383c6005a1cb05fffac868e61117a59925f04c7247b8d736a738e7c955d9f5f1af73fce8ad52197a4b825a64f2fdcceb79b4a8ea02c42a9a7068573bb07d17296b562ca63f612bcecbc7be0685fea56cbc588686eca8e40b80260d2b6f1edcb8eabba273e9c8ca54aa058fe071caf119677054e16bf05b97201c47d074315f43380f4dbba638bef08606c3bcb35bde6225eba0844dd9fbf6490a180981fdbbdf519bbe1134cb1a838a6a9df95b58d5e56d9cf9221706b510444d4588943f2b45e5dc35cc73a5846d10aa351329f10f3b73873efa2dced455fe5a04b42e368598285fb5ba63fc0ea79f9cac49159b628a9c2d73b5a237e90679cc7c093a0bf22345f114c5559bb1bf9a68f44be95ede54ecf91c5e5f70db5aed0b7e7f31332a549fc0fd583b8d2cabd822604abe56341086f7f1569a908f42f43c230349d772574b1dd257656c545de394a465aa18921e39fe82aed1c85194aee5f92a6be7abac4102136cfbb9f6e3d4d8e02d4eead942467f7ed5a03c2fd2f694a113795e30357d7155e737149251af759016c2838b226f5fa4adc2064d49acd364fb2d6b5a16afda3985a3e4a5aaacd93a89277ec92c807e69981e475bb8f1844fd42a3be08d06b92a05ac2977a41e16523ec2061f75faba698dc6a935dad058f2b9b4ea385b76e93e2dc28a24e1c809e35d797d1a0efd9de3d639d4fef082d83e5821a5535bad91c9cc0259bace71d4f47126204de75486921937b323209ab81ca7839e8f911fb946a78d25a831be03afcdd092074400d00541c410d89bee3c8fb7ab6bbbd5b56c0d0862f4fcc223e5a2c0e7e56e5cf756a0fca81ea5c9097b9f295152cc62329a471b4d5cd4f3f08be638b87decd1c189f2abf1ee4b9a3212bd5ffbb9ff84a2ec77a360f1b85ba8ba83204e12ad66338e26b75e61b6468c981b6676fc8b7a6e25d5ccd9eea1ee00df2246eb968ae4c946020f8699f0d0f09a36f1568f5b29b4553e3623f2b4662e4ab44eeb9d128fdee6483a6e471d7ad680abf3bdfa355f6c81d1b67d6b8f37d14dd6359c487c37f440013a72d9e31145edffc820ca02533f6b058ac89f827a289aab16996be65b99ec80f634b514ca85478371d2553f65bcb014e0c9f25fcb9e3697ac0b0883e462b9579ef93b90f3abb5d4713296243038296a2073a0ee5ff3898b8b24009e0f4100805dd0a15475ac7679aaf3675e1ab176f39ec777dbac50f9845309f0f1356bac8d6e6f66f93d58b07eb52d08f3c9b9be11de7401965b243275df7722e3e33383016a70cb5eb0119589a3a29bffbd0c70062d3f38fd3c265ee26fb8ad6b2346f04da6f2bd827c94d6dff43c57da2fc516c1d73089119646507f3d572a02101f5c43ac8ee48eb54a1b62fd16606ac1bb652c1763445379393c6685cd96e10ba9d0cdf96be3d1b0c55fd654d51daf7a36d1dfa80be594907ce61588a1896ee3fe2a70f9f3beadcf003e4166b25635ced15b8e8cc5c12e1e60e7c6e06afb61e3e10fdb4be6ef293f1f852c07e7a94eeb4ef0f3e8214a57d0dbd08cd79b756bd88b80f3e17f722048c858786d67327ec6f368c27733d88ffd25df52ef50f41e39d10fd56d52c8fd9e2c8c6501d4431625f91f1b9095a14858b33f3b45d8f97ef81099f98a6c38d31144c04a6606d2d74625a24bca489a8af26ef4e44054c5cdec74eb22d6e1dd8f67cbb96f465a9aa50d960dc000245d89c064419cdff474e4d49155baeedb926f33396c60b56e6ea041289bd53473b861ac942725307a68de63ae2160d0f3e7697febfc1cabb882bee229055170945795388d768f148bc153e4b8b92750857cc6de47f7bb1abb490ed2330b58c6c8d707b139a49c7521a61792738860ead70151194223e64fbc6cbdef2bf7850071ea62479463c85b7142c9c026a9fa293ce5b04f1054f9fa046e58630d8502cea56dbb486e98a229d80045cc40b3a52f0264ed6ae1271e759601813a449835a6737aa6b892a3678f839b9cd400eb9c1c52a1b7369088eaacee8c5256ef2da10b3eb9eb582ef96c6398f32ae5896a2bed881c7e548193424ed6904f59c95cc9830eaa4e0c47ef97f507b686d276aefdcf3dff9366245a3494523f3562822533171f46ad33db0185dd2c90986258e07eb940e0fe308e6169488d383b5f569b22e01c98c0626473c0e70520ecfc1a2eb436f162a6c3c31afa1485d1af92bcca248c2e934ab929e63c94452fb8f4603b9957df74e37a406c6f656b79bc636666218fca0318cb63b2eeeff26bbf026c27f95a32c18a7c0fcae43b8b32ff5218ff271e90626731b8ae3f5b8d761e6146e931b0fcd21c51c7d3a91a671b2e18d57007f50d482941cee4939204c8337c07e0ab5b245f03344b8e411a8100e53431a755ee90fee5c255c5411d14947bd747f7faafc6f214437eb157d4bc2301e4e271846c1c43347b648ca05d4cfb051e0d4029551d5a77a93b1b19139b461894e541f5daaaa73c2d81e8c69093168eb21aef29fb87d239346f7072b06b58f2358fb1c4d818d6c0a7d237c548a712d7c2b990d0a3fa3925d2c38925932e3470e10d5e9addfe96dcf137a11ebd3606750fc4999d029b34918c909c3645646f995d9b0c47ee07fe64c587c86f4b192d074209f8cd7083b9053ac3c038cad904949b55d8c6ccf55bbcba360233ee02868655dc0db00dcd5b883c32bb82e9c2f835ac765b85134c5f97759cce9c7efb8ee2bd1e8eb16a08c3351d9ec75601bc68056dea4a36ae825da871f40f23c99339b40ac02bb8f402d36eeba23531c8f55038313320a0b487febe8066eed7c22aeaa6945b68843b1fcbfc8273e3e378c4c9044898931aa8f2c0c322c1dc0112430f4d53b48d86cb0169651a20e6e61288b9c45d60edfa07f04cbc749eb51013172ecfed071d36a7843ea1641b3899e8cffb1cb6c9d3889cb33c8ca6c07c14290571c3561f6672dbc12229c5feebc575764f995459738c46c51982149adf38fa3bc09908610045d53d18e909e3109d8e257bda2186d0b3d7a8f7275bcf8cd6aa05c3b3ebf7a108fd58f0364e365f0624ea47d51de497941c55198145808dca52fbc8f54c5b80ac77449fd6c3018a1dfb23d22837eaa91a3dfd4057a61add86ee855fe2bb6b1caff7d66d18ae85dfdec5475338e2eda506780b293c7bf2667bf64272211960a1922f299609da4c7b9c01d537c88c4a786b46b3927e9e72b32fc4a51d923115ffe31576d2e2dd55cfcab77eb0447624a5aad6a998f1b3ebdec58c043491c9dce689c08efa5fcde22743368456b1526cbecb8fb3e4435e8f36539a3645051acaffa84f26e1177369e587c46a0b63fe3d9380506a2d9bac7227bbcecd915fd2fb115830fabf3a360de046e50160f256f0053a070b3cf242ce21fe34ed1a8fd78b848b3e960880025b983ae3018fb81f952b92d4e29b7b8dd77ed2d91e664e5e680810cb5859e742504cbe8c94811d40e376ef0ce49774c1082318b819065fcb337a6bdc42aebe98c383a71250d5741a41ee738750e308f788edb1e1401f2a5b418699dcc8dbc7c71a039b3c538e0a0e7ec1115a7f0c33481da10b91ddd42d0712405b93ae99c62e87cc4aa0a489a130e0aaa31a50ecd4d01d565f8b83da00cfbfc99b620c66e7a4e90bf36c922bcb35e3a326a66658dcacf60d8ccaaf113d1bf30bdf8196d9510b35ab8e2a31ac50ef9b896b41350d1f98a02ed8639c2cd6615375d0c0436e451595dc98a82723e5f688eeca5de9179b989843503fd84da0114fcb286dbc4ccca5c57d9a1b8768fee69f400464a324c40140970118869c8f85f96f86664b31708d2653087c3edb1c37148a0984dbb14c47a41ff722bc735d8fc98741cc2e2bf5b774a99f7392363f4cefea6eca66d9b054fc4e604b9c263554d0c2b28499ad1a29c8b9e91993d6a2884c84ba8bc4de7d6081af4298e680ae15eea24bc68456d46f8f98b7a84f4dbaa3ea39ba23028486c1e04781e3a16dfa9f99aa88a18be6113d7c8d29b50b2c2697702e40f7acf147c31214e631c6dea38cd163fa33fbe0d6dc9bd0d0db79f2999676f39681c444bda2a9d8e1fe52cb693e465710a71058c7ef6d1ce320846f8a88887f1392032a04b90c4d025caf0bfd442b5552ace188f8ee4ca27ccc3a78a5277c77561ad174856c1597972cda10fd754a835b99cd60eb7ee91df1d7a842424cb535289c8ea58ab78a19a8a69995f14512d42f847ea579483ca40f5bb1bcd73751a4761a0b3b98b6fda3d378a5a85f5a669d5bd838d50ed0d498921b5335d0e7a8ba44c62ecfcd4442988d2256c37b5831e62efb0105c660eb5cf8e45790841b2481f49f4324d5fc432e75de7961ab724adb72bad771ccc21d20070db150a9d900af129d5d0c7d941ef7f352f41290eb39d0125ee24dde7c740cf6a186787fb6ee1cabd4a1fe1444b7ad8ee0e26948ab618e8a2b0e0a92d5dede325660ad85d28e5afc5f141808c9ed5ba829dd21d1756021784ca0b83690e53ce043f72e2e39106d2f207bd7a28830ecfc766c705310689959f4b62b323eae5e96bc77ffacaf02d21fe81a778ac1fde76746f9cace6777172246f568be3de6687f31db6561af4f29053e300da08be74d944d5658d85b8487f05ae5cd513d19f23a8585acbb50cf2605a4035d2283d32564b3766aec1e8c6aca0efbed8eb1688ab35e54d8b6e765825ba9642cd18a235d05cf6b8248005e18a60be8c9d205171dc326b0c423b1ec4d69b90dc0d75387f21c39600a54c58c00de0cfd00d911ea53a43812c849476b053c1c35018f0bca24660ae1318cf8920aa7551f325923860c2dc37660a54ed2c0cff071047a3c1579b190d17781d4dbed1a41a69cc0055a8ee0bbb1ec2dd2038fe8ee6f1a2b86b712eb4e4fff8ea21f3003a6e1ebf9184a234cab6e73da88a66697b24baa6cc4ead258541e7faf6f17dcb545cb3e6e76933b152cf32113748cae82f4a9b08a6bf10b5473d03b3ad639330713ae212d56077c1fc9ca7296a0766870b6050b7b523c2cc6471f7b05309c131b905a40c5f4c870e1e4cf93b5a0d6700a5a3d63362ac673d4ccd119a89c5e638b3fa24f87002c2ade104a09b15f3ca725baf4b82e21beaaabc1081e7a6a0050acf73d5aab312a543da9f9fd7318601f39951e3a9dcb26d27616817332d6585681d4fc23521c7da465ccbca9068cc31ce45e08f506395046e1cd21072d3629156ec2321b56066f38c5268380a20a36d0fa624198876ef406bf0d4d7527ee95aa41c2c1a5109cf1c1b4177da3c32a3df9bee4e78ae79832c9d8b0388ce3d3fdf1e22400742b515d618f861b8fac663ebfb7ee7c28b186e7ddd0ca566d07857b632b6ca15224c2c7f3c33bf6e96b50e7589ea5c2799c5f5821748cf2436d62ca0d75e1fd34368b7bc14d8ff287c0dc1282a6c5624edf355f2dec64afa2066ea41e192a410e11ba3b0f43ce0868b8b61c87cf7212058a911f5d64107e7f4aa048f99dbcb4d4040e055443f90c6d45b7ca47635c6b054ebc9e1f6413bf08f1d6a50680dfc3150219b8b917b8e9e1d43903b75bc7f1316dc660973c0601ca772d69dfc9271633d200cee1cf80ed3d66162bad208a96411d48ae0950594e779079a156a3a846aa0cb7eed30493fcd0680d4d980c756f1ff24a74752a6df9e83c8cf34cccc28dd27db40a04bc1cb673d830a3abdbaa243985ed68937e1b05837889814fa55ca75b8e13559d81dc35fe85f4685280aeccb6a003b994a47f3b1696431fdbb38c2df83255cc9d6d120ecfa1da8122b9731af03c94107baf6a36ca29ec1e25dd1dbff656504cd401f7a4ff1c022c548b7226a30cb90b14c0f0b8c408723ca99fbece37f3bd67696deec0c5883fc30e30ca4a9fda6f85ddf08786042ed207108aa09cbbc2a3d18e2800bd38db2ce3d7651d2790625d21b7cd59554286f989f3b6c500d03c0b11b17e4497e65833ed7f3e908f42e3881a849cd6479eefed562c0b03afd425ca51469ff5550e61bb35a0ccab49f4cdcb7ad0b94aece764538c1613dcf951999b022235afd64a5999a33258a34144e5c999e87f67a6a7240aa1baa3791105a981b811c517f884c48033257f270cfa7d2a2b2adc6375cb5f77485a409d4a864f1ce95e1e4587d369942a569f4a3323bfc978ce134a3198703861a7be71c4840bc8c950e2d69eb4959c288e3d6b5181ed36f33ba80437195eb0445be529900585d36163865e929d9e414259903a1983fa6c5196ce1b61ab255c16739d73608b799987649c6693f85a789a2a45bbc84a3b57be741dbf4924f78148edd91ce8363b21a66ff22938a6ef0776870112a3a764ff238cfa3e8aa38347b27f7f9bb5454e478c911f1491bac906d035bb579b839a2959259aa334799fc580aed535e0a8859df86d77b9f98a2e7075b57ecd1d4fdc51ece36562014658caf76ea4016f7806b292eaac883d30ee4880a62c7e201854a162c1ab8d76956ce9b7effc029ce26533859d39f42616aebac8755240558fa2704f52e42f6d07d74fc59c98159a393a47cb7a8cca2709d13695c188a8c259bf4ca65e8f54288286e14f65666e0a97630f29c3f1e7e9ee8094bd2185ca28f52f7ca47d7f2f051327d3ccc17e9b287e3fa2f55afaf139ae3445534298085f6e33b23c5bc145123406e3533a496918e183b3988756543171c39daba59f96c1daa165a3dbfb9cb64ad89e32208680cc2080d9c7a49dca0924e641fcdaa8fcdb6cfa1c5ac668444892741dc0f0dcabaad1f1789c0996c6e49cf9cd474daa31a5d05b6a946b1333d07e223e84a828308cd07a94efb2c7270347e8698ad8375b1dd24f69d048f70e867b53bed342d1cb8677e683fcbe5e1cd24ccd8b42f6637c2d29ac92abee64aa94e1cd5b497bf190c5e0ea6a39c74f74cebb94ee2d910d76c03019b95681e8c0d52875b411822c92a8c746447d6b5512ea6aed94741eddb560122e39d3e442760f78fd20d2554c64fea67396c133b8717ae9778d04c34584f6fb34663be910c58bf7967d450be19e6fb9770a71b56ce2b8e363a2c39a21efa364ae931af6f8bdd79354f0c56af4e4b9e6e7fc757a09253c7d5c5ffc235d8e091efcffb268993ca2e415686fa0f52ffd11502001393d6241d81e07ebe04786b12d644aab296362840e33a8a0f74a83a2050135d3654dbf8d453ebbcc11aaf2b2c60d22d54333f34d848e2c02e2140078e10ef0d605875a3a9079292c8a2fa95e4d2a43ea6e7fd80e67c72d3e48f459b8b497b03df8c7b90ab59848e58ec8abf26263d9c95e81272e989b59ee39704d502aaea20df0eaccc011b2b5e1e5b1ca26d9a66af7844b84dab1ee22e23d76518c0b781726218f21d659bbcf86a89c65bd3c41b6cc5014762d1263fe5d75bd284c440b31d0d66641a3a3a7260dc1ea784e6bb332f090646b17dbd7f97a3280ca77ea159dba89d49ca6f61c7650b61da6a4119e042f82598f283f6e145c1d82238c3df85803d0c92da473c0e802098d8a2ab7a8a50c7f6ca729bc020457a79b74eef4ef714e572022dc7044470e84beb53435397322362e445375072897229c772c998a57667143465364690a99c0277b7a320eaf5886038d57e8313132f0742db50711aee2ab229c291901b62713accab4dd61a93e6004b1c3c9298e3e8aa16256fa1dc10b595d0e58cc1a85c22130a74460c18e464111d528f6d0256385f507f6c1c631cb03ef249b9b61a194027bde4a7c4620a61e9b1597e3e028a2f935b892b3c2da7b42d4005520fea761bb4a55da0d8f2eb7bfc622d2e1d5ccb12fa3f9bc4df6bfb6a4c9fe4fed8644151b8087e93a40c6ce341b87da8d025c92e3d3d583f11144ecf095c1fab6123664e19a62e8bd8b08b5bd77876b080ffa8efa1ca3288c9cc1a37e9d277264a9a4d49ac8fbcb229cc563798e1890431f8b7e053c5e452ba74c362fdd45dcdbadd7bca48ff6aa274350f9c004f01e4545b6a778fa0d5f0af160009200f8ca90a429a506c56724480580f5d57d298bbb61b85682ff5c04ad1a5b7c3e03ba2b3adef8d7e4335fd2a8addcf5c553d3a854118943b9a5e01f6c99bc949afd15af1ec2e6709f86995261e6933948b3b650cafee29b9eb45a2a4d4b36905ba97693e05c346d1d582ece2fd59243ad01fb773ca67517e9396426a79122fb23a42eb7a4c0f9c82877409a34a05b876f9c67bae5e125897f5b679d992ddaad788410ff7800d1dd1fb63d7b1b0aa82ded2158c443d43ec175fce886dca97baf54bf5e54f1684398a1cf0c7c7bcd73d284433526ef9c9f3ee88f8ecfbcae49d41666dac8c562b028832eeb6ed8e8183fdba5459b189afd580a1e838a2d2709d28bb1220d9ac673a76546992b77b15b3ef8bcdcf1250ef975261536d6228b4cfbf8ee16669079ff4e6cd7d70ed251bc4de656a22d93c9216d2a0734bd1e299fd5ceb59570bcb68870e16d5dd1ce00e36bb281d531f68c5f42ea378faf9b774bfeb3a1084cfcfe7de52fea8e191cbfe36074db8ce0063f3807cba285d55bfe669236403bfcbe8fd83387280f1632c685f48d54f90691b84908f5f59f8e54908545078d090e63b99dc35ed25cff329287c9079434c58e64d54de03ff28fa751ab4f7dc68c161426c774e85db523a3c849d65236e5af5eca90ef92dd9bda0011dcecd151088fa540294757339c110b3e4b9bcfaca904ae997189c85bd2858b3c8616e51758a84326c846e0a3c2b8a804e2c935c4ce581db1d3101faa9e1d4455e8d50e235cbf5f6b513256244cb7a9a5b43eda8832de2ef1ddd2526a3ae8a749eda1402cc6df0919041da45b79fb794cf08b5a094b9e84f2d06148f696bb192b671da94958b09bccafd820dadb948dc5417cdf75102ea7e0bf38e5409744ac462a11db06ee42ef1db56a290ca45e1922f54ffcc620bd381b6d1e76f8fe2420d3d9693eee56e19053d25746f69fd3946c92157502dd193924a9f894bf190e30152c06f1632faf0e4db3b305a218ced8b0e466f7f23739811f459dc03b6fa519791030ebc1408fda258a0a12d65119c46eeabf1a9edb7e7d5482864a3bf16d73bcfcc966381b4a212c86bca0938b40d00b7a5a5a4165b5d398971d6136861d76192e04d0a8e02937b96054a28f74a2824004d7fe40b703f7e1f8d7907a1a676bf47ab575cae243448c03865741ecf61931892d39618687b6bc54d3c8e0190d60b47448e9ba0a144f2d0fba3abeee436f4ecc293c84b29a5e61e67bddcb230bf2db31ccbb57a18d3ece9e090fe1d44ad44c23efa46cc86b50994a7ea549a4a0312a55758d9d6011e444b25254a3e457ae815c75f73adfcdb54b89399950866f67b95e3d035278aeb0621071eee00cb12e06bf1fe182604a5fb87af9572a21dc696c199e26b0bd6a4e046e3a735f630e330984c9a521fefe154d482646b8d873a0022ee3e0619a3b0b9a9956618c7eb3eaed6f355cb1d337a433007c706b5bd9561e77d592f6175fd7a0466fccde393660661a341d2ca22b94bfcaeac5d87d6365eab1d3d0a22d46886d4a5a644a55acec12f4eb6da936a43913925e3aadbf180754f35b5cd9f047693ab320b6779b9b66e7b45f0d0c6156c87cb5f2094b2a24936644451b9ca2549cf8e322e08a23745a46eca82004d93ce6618002c3e59091eb403ce56ac21039d5580bb2b62d093bb0bff0b5a52eef24654c52f2fce9d801acc7a5540217a0c74d400d08eab4077701d12d69f332e5e6ed0c3af3147b83dfa72b5df2fa103b8160f72a2cc4548d1160ff4098c96798ee4d7e99349aa3e01b98c7009a88ab22ff7b4573a5acdb73e3ff993a33f82e3fe07d6fc6157650ee0a15cb66048b81432cd1117a66d02cd2f085f1bf121d8f2dda9e928da8000fd37a22ccedbb8bf927d66d1b0da3287bab74b660cff309644aed518c7814599bb00797893dcb90b8921de163801c55314682d441b80716f606755b64a545985764823eb36de76f8215a96567287ef9d57f601b4f69727eb6c8b619a679fae2df7360a3845d2b6a266828e1d9aaf8ff0d8e856a49a644d2d7e1513e49a3464fe4214b70bbebc124aa7e948ff13412356266b631ae7951cf533a8cc8cb4d38b913a82b860841d924e6224ca0108808abcf133235d7d898b4246864b59624f9723a1fcd164565310e655e3b5144c87f07249bee88e51612244efecb1f16b6c35998adcecd4cbd8bf2e5737cdd5931e4d6624f36a10ba9cf8e2b8cd8ec4d8f7210509db507ebb0894bb62e61b38f80b95df7fa0767efe3880f9588cae0abaeaace4a91a7cf0b83b73b3e5875b68aa707d7adedab8efd89967e541c707b238a4f8e91cb342b2942a681f66a13b45a6919d01c10b961c877ab9fc252b39ba0b7cc6fec7fe791614b9deed10913963674511a13639c4eff17c7db7bed515bc9b477cb3f7a7f64ab98c7bf2e4d72a15332890dd23b5627212bbb220d013d5cac7300e155e785b70576279ddb86f763313fc2e42edc303a09f63f70130e5c31f9c94f8634143ba047c3dae7fb3c94c78e69256e37de3ad5fe3ca0ff254fdf6bc657173faa8d11fab3fd890c1bbb41aada379ff4977e6006a59d0f38f7e4ce4ae4954914c2972387eff27f861eacc0ae44e680e612390a66010d322003af75cebef4f2eeefbfc5532cbacf0cedccf06ef3ddcd4643ff5024ba088cc8d9d125c5ba4c391f55c2429931869f84a6a12a8c6a5ef921627b30dd433c01037f5dd958b23c049474c50a85881a575b7e7727d85dcb555b22db3018631467337215329051344fcdb058801ea36ca3fd0c16fa6416c7ce46bd8419bf1520c7268c1fea881d2311f195f9fbb81a173316514ce3c36ca30bb2ae030d239d6f841c1fdf47a63591118312843e8f1951a76bf83ea07004ab3c95852aa98a0a8ae5c17335ac3a390d4c9b5b81cdf5a7a1b4bc753992ecd4f6522bacf29ec6e895d7fd8bc889aefcf56bd41722b49aa3dceb80acf1afa94c80c25c05988de22ed29161492357ed970cbea2df1b14b6b1edc0e747bd8201b7b30913ce29f058cccce327dab25356613a15590ad4888c184caf10c203510e7e8515c2bdadb258a48dd8ca337195f2cdbdd61c461a6cee004bb88fbc4ec35f89e145e5c39b013a16d1c4688c6a9dba465bc78771d822dcb5066d11cf74a4e7ab914acf81606bb5c05ab6c8a341e55b06d1a4d62bc46e3a624df5be541b313052b5992796580ec8820880515885d19867265483385d0a42e26770127b8f1761c68924529f66017799ddd6cd52d50eb69e7e7d2a20eae9ca428a3120f2c5702f5a98573730d46f1c91836ba71439fa1d072e4697266232fbe6d24775eca5cd58beff1e001926173c0e16528f6aa723cb527b8b146306017cc83c3f17dce5e75632064af4c4f1914f1c3209b0cf5a1a4476c4e7845da8fa4bc2618955aa0796a7fc00b20625b9bbd907807371e144f2cb8e1d8c18223acb900759a2ec50a3205711aca5830b33224debebfb7fbf2ac0d0b4db3b862367016bbba195d77e1aed099d8db89143373616de0c3cc77c615e9091136177628e8a7285e218d868817f4b63a2c0df275dc5404c172e155d5e4a56661748abb06274801310f7be7d2201d2ee388694d73a783810da0be45e9a474a1dcac0694bb8eabd8a8e131e8db5d10929c2494f3f48af84a4c49c1afa17087ad753ac013318e4e6f01a79c848f1b9801ddb49c7332a076a2c0564bd2354fe63c2ad5e04eacb311c278ee134f97dac44b03999021bc249a4ae6646bb03dfa7ce6d4560282f1de35fa14a740fa4a70473c74d1cf179c873055ab1320ec6181d0897d67e7ab08e950c31c9d8193da106d28995bbef03f12c7fa170ab65d2819c44df0d0ed3edc045a1b74a517f17a04561f9fe8af8187b04ce3bc25c619f20fb923d1a93a4eda3a8166a1247ba394d20b51e2d1587091fb50977f700005736012aa73eef965d2a62dfc105306e34fe5c64ca52e629953f8364ef997f521cc4ff1c9432e19027edac95d7b080224bbc9f152d730d3d1601a6fd35f2867a91d031f71a8298b6c41fcb198b6ea6d629c91e1e0cfc36fac04e31367c970c5e157f5a83aae62809cd02f4449515a35ee500dbff1b364a909bff00e17f3eb37cbf50f68b988bc244412adf0d20cc84861c48faba74e1d7bd8fdc90ebf18ce38e87c1a6ea418529c7817583efcc5562412af13ae846357fe20b4d414fd057a4249b2f62a433db8b6ee4e629529f367935656fa9b5bbc3fc78906a64db9a69f7e662b9b1f426abb025b41766dc20adc8a51def8461d07f2c1051f4d650af2224a43acb1b7df52a7da527afec5859a2d169a5c2b7031d6ba31093a797f46a2473f72c501e69b853dfbb817efbad8c52af371b1b58052cced33e44b60c68ffa98ab01a286088e5aa228384055aac39a1dd386468dba0241156c352cf02e2b6088b650106da6d4c2219b1e5020545f8407d5c56836a311625cca7316d22709987cc850b5368bdc9cda1f6fab3ad5fba6f74607c0e85c09694bf116c43e1d1c01c82426949157b4864a5e812d63848119d36f4110ef4f299476c390dab46e2796b6b02eb26ad942154904c4b44f75b4ee4240a6d9aae4afc8bb9a26e4b4bd47bbd9a75ea9ef090ebaa05298b0886fcf8844be4b128a6ae37ef75b9df6bbfbc5874914f1763642b784b210ce5a3ac12ec176c9064b2d439c450220bac74862eef1c22ae5f01b4bb9a75223d5a4f7c21d0e9bb1c0372e146ac6cc5c7e54a10f0f343d6e42544f9c0ba2e2a4af5ee6773ea0fcdb9c88ff4dabba9f02c825ca9af1562cd8b78faa8ac20c64eea41ad85a541885a81a54a74a1fb518fbca10290d193d0167b1f245998d6c14a26c6c66a7a1754554dde4734950d6a578016afa1c5a6e64b0132a91d0c6b62cfe0b2db2288ea25810c29da7ea2243a5e1ab0d3a1b8741b7e82f9574ea47d920e9075c7e94c8a314735b76c8722f7e290933479d2193cae0528ad5f2cb41999ba0929a452a93a01e94445d452ad375e98682fd7db4f84f38d9f8dea01ffc903f7387631593e390a11ceb26a893b438469cc5dff5df2efc163edd8acb732df95c8aceb123791b8960537471cc24884b59360cfc0805939cc5c3467d8a091fb2a9eebda3900b2a412c652148c4fc190b7c7e612abde10ca00bf81d81a5c85024f3b71b78365ce11c93b1024ab1539b6298bc16e58162587de1612d7ec74b17d88eb20ca376605b67a6765ed87bdba58c9adf48adc63069e5ce454a6c5d3787d111d84762f60efceacf525b2a1ef7db1e92eec4ce257a9daebf232b4089f7d26eaaa698f8ce6866089019cf46e42ee905a2b8861b47fab1a8e0e2bff71dc96ab87de4c6b4e0c0ba7e1de34b7169b3e051b5bd8fc075e7a35ad1159e3e80d765fdb6f0671bb7c00156fdf4b317239a393e8e43e86cbf96c40a6140545b9a78181fa7170a61a573785a7eb8d69f998a882ff953cb6b2ebcfbde2c8165dfdfb7149fe1fc5f9743499685c3dfb5fcdcbbfc67936eb21aa9414d776c6a8970f4e53ae5e80da747a169ac287ef5b497cfeaef2d24266d172ce60c58f12f03ad755e3b3eebea0c6d15ce92eebb5898ca5385131f4a0b26e9a785bb6ba278a37faba36096d14de6e3bf59891bda6488b27824efed62559e257243b38f80735e3b75827ec6660de4c6daa2cb0e2992e5a4bbcd9d5d0ed31324ebee41314751db91a054d053b9a66e8dced92f3904854cde148a642d3eb5195d6a443d548a6fac7158fcdc3fd3b0c9156bf10e86394f160603a53ffb157c39f50f2bd61dd7a5090557e5172de18bb0347a226b41901965ccb307ab0e21ac5e19a7eb3a672e47af58a452fdba737006d5036fd172eb762bb8a446f6ea1dba9a1cf53837edfbcaa9f95cfb05ea2e351b7ff12bf6637e6c475d5869702cd195a1d1dbd036af1abbdcc791131144631bbd714c13756c57a2e2193cc0ef739f6f67ca68789e4a60335987a5ca446908209322f67626c7802d0a5a0c557086318a52d37886645c2dc093fcfb945937069ff7480a862c66bb68f5790cc61d89c068c63b327a9588fccb09ad6d067dd48a8f864ee6a35b58329768a539f6633622d11537a32f3d06bf86c5ea38111836fc619d16b415e324d2d129415dba9947972fa0588ad10c07105d384ce4aac86068e38844096cfcc5d0edb1a60897794ac08b51d7b6236355b95575b87f0c3c0db5c456cc8040492404a2241228f401424761d01f041d21d00d00dd86df2af85463aabaafe6cf167c4aac4ad80a6e67af65054a0b9edddb3bcf1eab023bc8255b6d3775f58cd51362c7c67d21ee94c023a6a1a9b398500edf2a64e8b80042191b953bd502338161a08e283a021000a5654de6c34e05b23c09eed0cc506da2af22b3ff43944e78dafa41adee71772a5a167092e693d1df0f65261c385a76e34e654bf7d9f2395c1964354d8fe9a3eaf9750fa1bafd2f382fd71960d8327a91fd81c0d4121637b482503519a945021759d5fe8708af584315908704cabdcef1c99daa22649d6a70dd9bd9a863c0b021b0195d7248df0779f4b141c5d867e4a3699bd6925c44ac53b769b2eb750a38ef992e476379ba2adf3a250f0e3105b585d23af5cace68e1a5042031c38692ce926386ba920a1f972a67d2aa78ea7b818f4bb1bc35d052357831db0d7a3449bee47797e5db798b870a97e2f98481fc2441dcc6a5be8906b144bc2b2c43d72ef9899b1b22f7702b1c521cdbb02a9314c084e2cac3b41f524547b6e0101c6ea78115c6903aea42d80448a64ff951ca574d23776718d6ff1d52a1a7310fc58b9e4abacaa0ca983da400bd922681b1e2f4b826d98c93128e051c521c5a9585c82377e353fd36f30f292b6c63614dc5894f94b457fadbe88531a8a56c97353d1fe68d98f215368f31f7c72b562640121425666a88bacc4f74e40df7300a9a9d6811e0ea3df682a22aba109f50e9b8c7ad8eb8694b76e4481bca1ebafb02d76ec1e27fa010e7e1036f88e13ad6d68684342dde15a136da7fb31531a60546de0a466956b7a20b304284f280b756363f82d29bbd4e73ba003952fd1c7f207570c4e60297e671bfa073f81c7b89370495156a3aff350b969700e34da802ea5915e148ed7619e830c4044cdeda650139b0026aee629dff42eb287187983d4452b431cef16174dbcb52699cb9012bbe92e2132b8299c318b464913762944a1ab9d2d3aac7c657bc5f25b9a5278d7ccf480926be7f801d83e43fa59bb4a524fd6564169aec547aca6e3c0218bcbc348b2b1b3321ae72e1f7dc7e5ce2255a0d52a1abd8f896ac68ab54f9d327274daf49cc1693531f56b6cd74026a11dc79728a372b19c497d0274f5f754aa0387b2c6ff8fe956fc1584b63a06d727a82873e83a7bca8c8b40f1e8043d1a32d41a4fca6c9292a09a4154154a9e84feb8cbf22cc9393135ead9f4aa7c051b3539dc98916783aa1f35a539a8ffb412926a9741d959abbaff6052667641517774d699fb522ec58a16da25f7c17f58b0e322cd7c37af618efe486506054a1dabbb256abb4e7cec60965d1dd08fd6d71980dd2844f0470dfaa432815d5f79c3d722abd2bee5d70f33bacd63ec6f38dfdd6f354ac2e3ad98f291e3df901095c1d571f63fc330edc6d48fc7e86af57d1bdd6060771ab5b35d89a459b25c97abf0abf2e17e22ef9108daa3c67673296d02963e5c541e52071c42fd889c406d570a7cb596b752eaf84fb5e003a609cad7231d97c53056ad33d2c6e5c663cc12de18b9b64eb1967fef9480a702c895db83978e3f55ed984b396988660ff83e228a1433f15564aa3e30edbf12298bbffdf89ed6e6e6d887e56e58123f163e79fea2d91a8e717cc3d99e44df962bedee4075ad1c67ab063dc951857e3cf89c832e84c3a4916a48702c861689986400da884104a5964ea0c17f42a080dd44003fa63ca7e5c308cc29a0e9db8a0fde659b2f4452f03de86d241d4b42d19f025b5b9b67e0698de4f50f8076f826029e628fee80e9b4e0c0447d3d3812008817992593ccba82a32d065ccb3d1110a904ff7510a05d3a38926adeddd6dcb2da59449ca7907b107f80796ac2004334bab407e83159a664f29263ae0295090982861252634264518eb88d9ce99f592257629d6a07306f58bc74786c796800809a1dc5c5c0c01a16caddc0bdf945a98840bac2838d04b10efe9eb21cf10c552b2435127e5c85312e5052ca52a2b4162e452b9ea328f92158ad8ac33a8a15c6133754ad57d82885779b25f865028aaea6d39ca539eb2668d3bafc19cb316f3043a98d1ae3da55cb0e28293109df794724149d7827eed29e542d1047b4a1d7da9e1fb2ac6186312e817d65a6b2d0d6b7177adc5322cc6d85afbb2be4327f65a6dad75ce396badb5ce2cd57ddc9fb328bac348d84e624ad3e5fb78b408b027d9e3eeecb1e96f9f5a6bddfd9d07de01d894e401f31e77bbfba5238c5ef206fdaa83378f1e77c37e7bdb290d2e28a5d456fcf82f69f1d329a3d61a7bfa8a8345d3efb2cf9e4f634fa736a80633eb60b1306afdb27c9f032cd823e03c67dd343c363fdce27ac55d667d600b74854158b358ac89bfceab369bd0095d0cea88e2d7a2f932b6170cbdce0b8bf55fc6aceff7f79fb7b3f77d60484196f7f242adb3c6b806dbdb79accf4b0e52ce4f59f9332b3f25cb0fe4ffbc49bde5015d3278e97e35608d8dadd5c792d1d6c6864e3eaf5e71fe5a61d07581302ef875d592369bb0bb39bdee3b7b3f9d2f144556a86be8591f193480622f844f487a24e913c26f16ebf3f22b875a772e6b696cc55d667d20d0948f4f0e114dff63dd19f3bc32e67d177a6fc9f203fbdeb893bae7e5118886c7bc6e9a8aaff5a9b10983b077a44f67f14803c8c70a1f266b63f2cd9dfaf676ac2ed43539df883a14542ce352c1d836a6cc838ecc1177c4f01fe766021df06336e80cfbdd9c3c327c68003500b43165fea1f670d0373835342f1b1d9b50145d2d5d414e7730ae87330b070e1b55e60511d491392f689a470f6c5ce9cf35a9b7b0f6461cac3028871d993d005f60eebcf086ef111efc30a88ed5f794b531653dc0e1b14bda40d21d48c37b6c87416f49652b6eba9fa6f7502636a52fce66157d9fa23d41a000a53b70ad5d4e339bd2a21ba53f339d53f4e71223bb9bb1bbc7615027c6ee12f46197af7defe7fbdab6c3be3fc7ec2b5adb33ee12dca5ceecb2cb9ddf258c22bbd61769d8150458eccab4c3ae5776e579dab887fb53ef29a564655324f463f4b548977c61077784fb9662c7185727cbfb25d00de449beb953be8e76fe3b4316907ff3cbb761906b5aed2e4caadc80b02e4c716d2a7386c3879985c89cf1d46949307dfc1063d04b6686a108b44577decff86bbe516cb0d099eefc57ca9c3177fe055cb214c5fb753f7f423cb3482ecb6fe4b21c64c6c85f5e30c3fe981ae4bfd4feccb971b8d094f5f3c7ffd676fe71ceac159dcd514277a49094593129e43b34211286d659cd2c426cb44993e2ff81160c77f9546150be79dacd08cb81ffe66f48a10d946898fc9b7f88861fc36afe4b96b56ae7ef4821fa43ca0c2978718b84d0a4783f44139a52658038a209a1e00d4193a21f850a04119a146f03255a29ee79bf5c2aefa199b2fc4ffefef0b1bffcd78aeb9d6f97caba489e9568a720f3f5a57da1e64babb4b36ddaf96fa2f0e46ac5779bbdbbfc3dad668c9a2e6d51e954f5456a27278d8641386695c16c3ee7067d3fbf4dd4fc59c0ce7f9fbc87e6f35fa7b756cc992df2fc2f9177f1198e1bc573be5f3cdf2bd74a36ca3ffd2a19ab2daab36a85ad21d999043bbf0d1901a76112c847608b98d065c555dcfca13b7f1d227f0d59714decfc739252ec9ccb09a3fda9caf2bb8d4aaee107dddbab2ab3569ee342fb2effb3c9e9d25a4100fcb2e6c311060935f2df00491b32bf5379cc29acd122ad6dfc366bcf707acc4eed9252d9763e7241e7bf79d225cfcef9bf66bc8049cf2878d0bd63fe7c9f1f479d79aecaf2e75606f37f7a9cdb73d96ae79b6b563346b6452ecb9fb3d619d4a43d226d91c7f27fa335ca32add1e7854160fe6e9aac11ee39867bcaf2b3be14f3d750a16d11d5ce6f8d2c129de19f1f69e76f7d385aa31aad1d92b668c6c86f63a56b7e9b35cb5a41608ad9debf8e2c20fae6ce3729a5b7d6b0960b4d7f92e57b631ebdff403a76fb017467d231eb73d9e36e6b9f7cb422f97096b0b3b5ed757777af4e7196d0f4a7bbbf3ce6eeee3cb61d93b7b5015d1dae3b3b524a4fb4f9025588027d6683946c553677d7ed9703f9dd532ac91517ddacb47e9bbeff9075ece46e09283b53d19fad5b417f7b4a09310283f6f694126264a4c33da58cbed825cf56d2774f29a32d829258e1ec2935a40452499a20e16e2d49c7efa62dec06fdefa6b3c7e650de30fa439b66bae91eb77dba9f58520805daac324a7e373d34f2b05bc7cc6d2559ae53eb6634fd6aeb9745d3b73ec3a9530d68ad497b7e59666ecfbf5483f940ee845a0be22ee3ae1363d9e630a866efbe740eb21f165d3dc6fa983e1dea9c1a5810632f89296435d55aed77b956ef62dc749b3808b7f558f78e5962d5cf566b77c05df8e5a0ebd30fbc77cc12d8ed80c3a06a473a35a827d41dde3be66b598fc826f77a7a3dbd9e6cc0d880b9179c33eee776de1f08dae4349d817716edb561ebbee80c3ac36e4bc10dbf2bfa1e7daebae78335c24d491072fbf109d569c04967d0b984d319087402d4469d7524eda14e69d0c86668ec1a2fdfbc2a9fba2461c706c9115a5fbff52dfa1f4b2c5b35febdbe0f2cc10eff1b4bb0bb862c77ec1158ff7db9637f375ff325f8f5c3917e24eb5b35640fbb5f412fd28b5a444234ec46857d3f49ad271652ac2882ca0a8995133c8850ea824911284b436c943ed1900abf95d4b6c1571191dddf54806e3dce57c1edd6e3d42ebcb9f91aef69bdae33188e9c874960d2721ef641ceeb8c7e3406e12092d16ff970448a17549c6a359acec35e4a0e0a3ee773725ee773e4bc4e4e8e9c87d1cf217d72be14734858fd49c35165742a00421459428614aaa0024451922d61b490c2c869c858f3e078f3337440f8f5cb9bd7016e307c902ced063ffc3ab2c6b22c4b160544d82408c1d837ac561dc190457e5fab4661e3e05050ea0037ce837ff35f1dbfefc2a09b0ffabe9a9a0f6bc81170be65b7dd3404ff66042bebf5f5f55f056f46f06b70c21b38dffa908c93f7e03c38d6af6107adc7e9b1c3c719c5afe38d8f84dd60dd3c0d6f8824ecfba1fd9125dd389f1fa8e28c74eb005f1bc401ffe671c6b93309bb916b6b9c3b64451d198138e008fb57cdb7465815c7b93f9b7ea236ea744697d4b62386c912562c60a102320415e6098b2d44948089c245a529516591215d8cc7e87bcec5381956ce5995d4412307e787b62d9ce06bde33c1d39cafb1c179fa1ee53dafa7ef57bce746d3fc7733c23ab8b9f9b95f23ac83d70834f773d3a7d2c59eb73da5a8e0b0c551c727b66eb0be3eeb2b594ef12b59be76ebbff939c87a701ca18e0e862009eb00fcef6b80a40e1a3b24cbfa25d00d7ecd7f380f8e382f8e3d3c86f375dce131faae2774b579cf95512529115292f75456183401e9b5f166f42b1ea37ee431a3d1896ef4dffd69d36751313ef31badf21ef075aaacd76e75e6442ea335a7dab486f39a4f6dfadf0b730b171c6058424288275410f2414399a8232a5c31515942b4e63dada7ef49bad441633b92c7483722f22b1ee5479f2bbcffd190457acde68d9ec4baa298c863d46b1ea3cf82fe9d81323b40f1a40a22a92f341a45992090ae42b0a5490c34fad4f62169df7e9441152535a71aa8d0e28a3163b22c3521d18204e6a8ce3000250a172cb52d989c6874fe10858c135024b12a3242a3ef352f722bd16504c9364facf5c8e9b1fc36db6aed574bbec76899a75b5b9a5dcc2468c54d29adb58a63742cf36072cebfb8624c626b6fa9e7bd4fafad0df0d805b2379b18f29c33ecbb531e3dec7e52837d4069f729ed924520edbebee1daf55dbb92e5d07ee2815b6bad55e3fffc892a890bbe7e687fa23328698f5c86f9e3a42d3263e02f9a3f5e657f3c555555557d15eb09e7a7e93a608c31f8d8e23063f83148da231087b8855df8234b181a63fc36e4c077256dd48c81bf6584a657fe74e5352ff29ecf08bf2379529d69b77994fc9edd3555e8d29f3c8a3f8135ec20bf475a9cc7700d8378d8301ec36f6f84d09e611f9c103ef8738aaa2a67058e42ad3f21850fc26f7d8b3ca125047e8bd4210e909402db1f127dab53150a638c31c618638c31c618638c31c618638c319df1aaedf93e7f66529d0dd1f083e310cd46cd9f236bc3f669635b0596c067e7399d2c3dcff33ccffbbeef53129174df961d92be24ac6960c1409ffc6f965612cba5490da0927f9da2a5c473d65a14ffbf3a8e34b6fdedd26cfb67fff7a73cfbfb5824e891d6077a6467b2143b7c494b965fc93246e447cd769f6d73e66b79d1def722a8755562ffabaaaab96a55af6ccd16d5dafca9b53abb3d55043346556797ac505566ff7ef9a55883516bb923adb5f656a8d70f1d9b3fbe9d8ace70b242510decdbf724e64f256b9519c356b25211a1ebfb5f5d79cd8bfcc893dc363585a3aaca59d1249a64a34f5051b79913618709f356448f7ca07c806ce00021840a44a0cbbced672d7e9dcd5bacf20cd1ec3b2eaace2ccdfe514767bdc0fca9e4769212cda6104734197bc2c82f9ac918264b9eed2069c952d41ff94353a04c92640da1ce28d194f91be590946c0fa1ce262d67455598319ce585a651a8135561fbdfe8a0e72ea953e737c9c7a3472e5d55fbfe474118bd1fab33fcacff20bf479ee07df63e9fd03d266174d2f038ebc5711f84fb40f76ddca7210efc37e8d9be0ff4fdfd1d75c6fa9cc17b9543a5bbf758b336c91ca0095576fd296c7e9883cd0fa7156c923f27099236aaecbed005aab24bbfc6dc3cc419837e0f7e855107ff7b90cc3eefbc936e20678def93670bb7672801a76192a7cae6bbaaa8672141c2ec9c3da782dc3efa2410d5003ffe9af9e3246dc6c08f693eb65f3f6ff3664295d99035396b113f0ec91d3639e8fbd8f3c896177d6fbe68ff584075a673d65a147f5eedfc1d740fbeefba69c84117c4c37bd6a64f1ec3452c8a74b5b1ae331b55865f8bfa2bfe32b6bda19ac61863fc40558689348d429d3c7673d3feb1084c5aae328a44e3c00f027e20fc3666b0906887f2825840892b217e482104353cd11443133543b6113cf970a2098a682aea97e0702001163a0441e6c6e4c98f7057314c43aee410456e882e70f8cf3e4eccb0767d759ee7796499c9fcc264992d49abfff219a5cd1878661656ae7c4cc9c294c292254dac0c293962696969092a3c547905d13aaeb33da6f3d58a637886a27c2103161eb810c1444ab63561f142610b2c541055010226647417ba14f7ce4e0e0cbadcd9898933ef6072a44411d40a52e46821cc1f3a9b6ea332ed2ac5a4cbaebb46142552d8c814f9220b6d661bff1a670f5a9a6ab41982951ed1cfda203cf630c16315b7a3b544e346b06f29e9bdb537fb7e0d83e68c51be30dd5487cfcef9c9eb77d4d98ba447f9c8ee1d756669f54df01e9af310b65d52dc10499586d9f5bf89f3444f29229e76cf9422c2b6ebd32fde4345c3f86cd244f1e8881ed1a31d7acaeae7ac2b4f9dd121a6ac529f26a4d8f5cb39c553713346a547d4569f1ed12407f0d0a07e0f71d317668cfaf6a9257960223346dd37eca07624253263f5edc625f8c6b8c26e548aabaa4f7338aa272f1e4196a435622ae0c4a49961797e4ff83c30ab49dd22a7c73efd4a41d316c923e38b6fb4ac58ccdfe8635a0491e8ff98166fb88849b77e7f2bf779503ae6d1606178cc69e6a9330868e15234444b0f4d470670e2c4890c390cc982051fda37cf0d235717cc158ae99c3c62adb5d6ea76ce396db53ee7b573ce39bb56b6619c31e6d79c73b6d6a3c9a1ef767455695a3f2789a63f411212ddbddfe59ccb2865afa325d97deda6c7cab98c128b528a85fe704bf4abccc71983acc0bd57771da54b945233d6dea96fee7cd42d9867ce1e27cc37dd404ebf52abad9d404f9b399bda01db5613cc2eb5cda6da3b6ace68d0baeda89b95e57976cf568ca802778508e7c4e304f782972ba5996d1643e585ced953ca0b13db4b0f49730a3dc5ccac23663bbb213997996565caa86be2e659b9810856a296ad4031ba55509d1517966c1345980633492a135637f39461c1b3cea05622e7b24953c2ca65739759d39004d212381a7a788109db6b3067d78f283252908081418b7b4a21d1c244dbec2985044a007b4a2511696db5ce59b4a215edf3f8643bad33b1daeeeeeeee73cb2e5f42e0b0ab171576fd1d71126103903329700133840e534424408af2e48b29a258294d3bd09060feb81041767d7b416e4aca09a28623483d67123124a1c06fcc4c1a01ba40ed5a3f07d1c2ae3f6a275cc6a701841920947605bdc75dc929ca04b30a34027e7062d7078013929b1f8ee8643aa6898abbfdd2866568536400133bf0e16a0a20637e5439e26ea33009e3eeee41d1db6e9b27e534d3c32908835cc6c3ae9fe3844528c67c48b2eb4f10065551861feeee8e1306d919bb7c19608a895da7a4ecfa376150155947493019c96204091c08397c990207868b0d3c5028db1fa8a968fb37c0dddd15462fe901ccaeaf30a87ef6da449326dcddfde22efb25796626cbdc91a5b609839cc7a7ea2480cb985d5fec41cbae5ca4ecfa3561507d216a2c74213273c31150247099428b2c8234e9c28b0c4ea6d04c1af70c7ed8fe598b1f9b524f6c7f3dcf108144080d4e84407db181a48632669c62009224363f862640e05c016e97b1e54a045243f8bf7f8cc77d36132b6c7872a4061ea410f5ace209c9354377a11e5ae0b030367d1a4e52fb579a822922835dc2e0a14c0176d5c2a4658b5dff635b82d8f55b6150e5d900d5921441a83441b182033fb4287d29019515513c3db96a85e04b478bcfc30ddb63538a2b8a1cd142d395992310a86224852b511c71060bda93272de2ae6fc303ab5aadd602add65a6b0d832c8581d9250c71973294d8e1cc2d885b2ecb96ed523c30ed90b4dd4764da3996dce52beb1064b1ebcf104cd9f5739df55812042a89254388929a54f17277f7c1a2c5aed9ddad93ed31186d6f6d7777772d6678650a195898b08cd9558ccd7f1286a6d233b30211ec526747872248e820450b57aa3c567ff25419ad4bec7ab36badb57a54f4dc747a09c3c32e614471d9ee3828d98e43131ccce480dbce33e4a3c8131059c86831820b5aa92bd8618811449aba28c284e63940f00fc1bdc976272ddb61dbdddd49908a270b5df2500ac4ded9e5cb062f764d62d71b90d8358ac9aeef20115ff8e0c329cb114b68a51e82a109243834a1a2e40a0a9a0dead424079afc95c70c8f0c4a65a3848b88ed9f9dd45082ed2fda4064bb0d65ac7cd9ceb3197045cf1f6a9bf34667380bdb5f17cd9f0db63fe581cea86dcafc7f283511db7fd224b63fee5817638c71e7790fe497046a1ba2b5de9f3ee120d2effa215af8b7d64871b7e67daaccff5dafc39fb586149292c3f52d52481412cae4ed27add5d2a22661dfdbfc102d46c23e120791eb697e88066391b00984dffa1cfa6d482129acd7cf2273080dd126ad868455921e55991329ba4d8523c55519a572bd94f0f5e708f10bcd1a9222901734e9391592572654597d1b7600fe2d3f702c816e0b3e1eefc5f8812a0942db7b3c963076f79fe78541f56fbddeb3bc30c823e9531e496a3ba37f636af3a8e86af3a754b7a96b29550734cc95fe5d52281a456df449577dae9fbeb9d3fd043bb01eebbea45ff5f7c07b7c5316a064008160b3a2e1696ea1e1695bb0cb59e562b6032a0f100d1f1845e0aeaf4a963a68745047a8d5e61bf4f1d7a08f75d0d8992cabed46fc75ac9fcdfd67b6fae68e100a3382a872a349b1dffd9cfecff357fea2ffccfe3efe3e1563d14c8db12d06db02e065bf8e53e62f2b0002c419c3595e680dcc50450c0d4c41ac00c4032f59007175e44c910e34ff993189c77929ab043cfe3e5361cad46861376618568852a3f1f8fbf87fccbfda37774aa754daf52d16f8af5fadff3e56ebabd0ffadf72331fb76a54bafe1ea6c2a4195821333559440e2834603a0436e4a92333400a9e136456db6acb528fec7623ca5d758ef398fd150cc42977e1343ab5c565582dd6095db9e5255a0c83da5aa186dd668e45554ccf794e67ca6f339ffbd8b4a7f6449ad5823d27659e9467e842e67175537c2ff482ae66137f047c2c2a7d3c19cad5023e68fcfe8118ef8b4ea1b6b8c300e7c7f35efdbc1706e9ad3a4d76a48bf7dfa47a1dbdcb68381e78481f7b4fd31c0c0b379535e176f0a4c0a40f66eceb379921f1d55289ced9a9241d31fc73008fc425e704118047ed911408d67bbb2aab226ab0aac6cf3a7860923460c00bc08003ae113af5d10a32193cbe9e8c0309069315555b0cf0a46814c36800c32803100170d434545412c03b022bd40cfe6643c47bb78cc7f8a4edd7aaf78ce739ef39ce76e94e7e8979c90e2dc65fb32007141e01718e0975a6bad17a4c211c0658301e2b698e972bbf842b4c318ed290669cfa42db79b137354ee26888e7173de489f6854cecd9f2b3e7385092346cc255d367182f00985828a41809bab55b1ddb381b8cf4a031e8f1a1515211d29f158124de2c9440530c0d52142aec70e2b218f99608003803a3ebbb99bcb8123049df00908306aff3300781344cbb8b96b3378c9784ed3b33500c865f39eeefd5db8f983dfdfe5e4b3cf8c6703719e0dc4793610e7d9409c67837d56369893712bcf8138cf6d7f10077ef199e7e4d93cf06c3664c80883ecdde0da7cc565fe5bbadcdc9c3797a33c27efc9635e2908832a0d0a429f0dc00dc220bcc1ed6165ef5a18acf1c59b1baf997b75290839b0605ff25ae5c47a7ce6f3d2331001609e3f4e9eede63cdbf6bfb94bc667e0171007e210505b40131a4dafc5455e78e1a6e8a6851b166e56b8215ab24464d55aabc55d573d8cc242bd1deec41d4eb9f58a4b464053adb5d6d6ea3a5a6b6c05ca568ba1aa0d43551b86aa36af0675adad532e1926a529b54655285cbbb7c35d777d5ca56aab935b5b2b8883aa3588bed65a6b6bc5165b9b4fc902435ddc754baaadd509d32555071aa7eae9e8d87474aec559d4daa1e8e818593af37ee22d2ea33e3468aff0d3ad4ff5a93ed5a7fa849d94f4d5876bb59d1393d20f4b842c712625caa4349994ea4d0a9752bae4c687a69f3d4a8d782c4a8db06e6a5f94c08b2eb8d8e245024dffeaa5050ea5599c3183c51556bcac34fd5715165c0b2a70a7cb7499f84042a2a2a949b4f7efa87396c188900abee11bbee11bbed52a7a387b030d152c4b188a0a96252af75ebbc29177f3907c06860923460c1932663c1cbe79b8ed97046daf252a3eac6dc82e51916a6a8232f2742a3eaa471cdcd030554f67696949676969c936eaa239a348481489569276b1c212be9d59f281e9cc92521390a6a57c66690a4a69d3184a8b505a448a5c14238042cc134e345145295d62620925c280494204543ab55a2c422937415a4342a89b2041ae800aa16e82d038b161721384a9e876adf5b086ceae9f453cc6aaccc7825d491e23ed2938e5d13a6009fa4c472c1285e8bc8ea84cc097119ae20ee7e766efb48e68ffd77e7d7915f122e235c40bf7f2a2e9bf847805a1e9bfbad400f1d2d9af1f5e3e4c00bea634fdf9eae1c545d37f49bdb6bcb468fa3af6aa875ad50d3f90d0c286aa0815909c45a9316592a0dae39e526382d8e09e5263ce885dd50fae02906e862d7ee02d4300e99c502d1e20e4c70c543c2c4d214386e220d04145111ea89aa042852c7e744382f8d169714493e14c13932a64c0a8f00347b5909f9676a8c588c90f63aa90c1891fb98519a61050d8c834f9a1c9174d6af0d9a55844c38f8e093712e18b92205352362545acce22a1ed9e52464091a1bd2744f9b56589ee4638d9548b5aca085bd1376b338f81a74c154b4c21851209ae90c018204835223a3cc12283f2747503255a159a5d580081131a0ad6690a2dfb06f4b3d40b484c3414ea1646b4ee3329348160e248161a0a7609155a474edf20e7215a47c23ee8decb1debbd6791b97b8fcc19b496fc6efa923a1ea3be4129d62e934299149a59829cc18186823dfa3ce61b504be977d3150a5d49504ac895bb5491da76f72bc5377768c6cfa33863f8df5b8b183205102b9cd86b9bf45095bb2e672993c4c817652ee88d0a48014b8d96c95c6550689bbd310873206e2d02d5216efaf58828986842dea3e0454bccd0a4d41fa27d74f446d2c272e44cc272646f84e5f03ca09aed4fda1d73947e50e88bef4769deb97e58a5458e1421b4c89941882ee8acea4217e128a54165949a07a435078a2e75adfed1233230b9daf3cb9e525f9ceefc6cc36357c76be70f7f7a3796b1dd7da763773b76f724a0508a3b041ffc281e5718ad93d02851a4be206dd99e5245e4f6e77a4a963436fd7a29cd7ace189346cd4aea9ba614638c6b6a30a5e3d39bd798abec3ecb050deb9e4ee16a351aac2371d05b95dd6fbd111c6cf8e2348317426c3144a50524669e7871a28413355a1d59be367e0aac115dda8d5fdf347ed708a379178ce6edebff78f0c77662302a524dcd7b1854f31fbd2166b3bab2fb2fd7e8be0eed6e9c36642d4235f81ea852a89dd87779dcd06490eb812a9e2df0432bb63aa3575c76ff6eb93f6994c7be2534ca8827523822e48a20485860ac80450b255d9e780101edfedf8fddffa2e8f243ca443836c0038fdd6f35e9fb3842d8d183023a7280900111c2ee35cbbf1aac8f2cbb2fc1f3678dfe1d0d833ab1097d81be0e4647dadf89790f7e5ae5721a204550b9eacff979e67c737e7dacf13d6b71ccfe6f7f03a1deef151004edd6fc9fb7974b9d73c60102900dd0075d9fb2486fb44674997389c91e76dea05d7514a2552c59314543e1b3f242133429f6bf2f633c7bd26a059a312a98295221584a62a60622b4fa3e75d601285254c9002497e405adfec7ec131c277a729172daa5272139b289e862cf2c7b4a1111b58b98da3f7b4a1561b4ad723746788d548523236e34db3e52e2319b8b5978d293d703f98bb4b2b2f7c3a7e3fdd2c60ec37751e9fc5f17d6a0effa2ee4c0b7eb1dc9fc319a3f47e68f8ba4463386af306320a16dbe7a5f5ab0ad586333aebe0d59dfc318681a5253d24596ba3e502d2dd8253d2a6f764971656e8d3a7c38f0bde305dc179dd6a854cdf6553de914aa11000000009315002020100a07856291481227aa9a861f14800b69943e725c30170963498ee32808a220c8186200418400620c3184a922b202c90be5ef034f2f72867c98f93393cf48c4607a2fa2e896a3b33748241a6ba538653857628fe64d1c93b487c103a791a87fe7fa2b89dabef157c48643fd5ab512e95aeb0d8b7a000e8b0cbde65b443bfad3c05e45022d58665c0621d6d5202839f51cc6860d42423f8fa3246e9eaa39080b31b1901cb53bfa24914d9159da4aa50b6c9698934ffbe44f9be64e3b4fdf9c6f547b6e18e6d89c2889a37192ecce29854cf362a3b134eaa50c4480e86cf84822b23b6fe5901a1feb4fc6746e97df54f99b3a1276d170a32c51d0cbaf2583570c3f6e3d053faec866a67ecb3b40bf6166b490a12029d9cac8d1b14a6a85389ab4ade44885e426eadf3127b329e02690c1b3db1f2a58ccd5070ba437f76127164e23ad4921183ddc963f19e7f1805088a0752d17a9c1a67d70b842689c189345ce77364f79b2aaf99e963d8d04c166097bc3912020a37155454df2325c2892465bdd00dc9075a554eb6388a1e98c602fde7a7b3a926fbdca0f97e56b6c5fdac185c792636a71cd8b3c49d3ce64361c0a864f9a520025406414d27dbf269accceda145e35ac6c078aa4d6f87424a98071b12c8a95babbd407ed6b013975f198be117569ab6b404959c88698999f298a5034d271b4fa7bf8c43bcbe85ec97714861794c122837ab123d87e11c8ddf71fca9503b1367006473f0a1137689a92034a06d7d12c4a1d037f37fe8bcd7c492a7faef7cb956e248abf8deed32a7f11cd7b36c549a4ab400d9e1a2acb5549a160e89b7157139208ac670d824e87eb9f4829d542f5749b3d98e3dc8aaa040ef69535a410239598684336785b8840ccb27aae78ac996002e5f1f7949699d49b8a167d1fd16e40160e9c87fac8383670fa54fc02eefbb09c3ec59b0d1c5294e319ca4f5b15c68e5639559b2db1647f7b9ac83cb83652154993ba36819783f56891165951e8d8f2cc560c4f3912800513690c53714b3a6fed8d4c187e8a616210a87709f8d9fde05aed278d7ec4f0a41f0e7f143833151c9240d8c86f8f0f83734c0da97dac0f8359fefc411cbac1908c06757765dc4e6f280d47f8f62ae1c0a2baf44b01f2b006db683e84c106f137469e6385c7d7fd8dcc4db86e7e108ee31c243874946c0cb228fb10eb48a82b60f2c39d174281a9313326817f9db558dacbcd6a72844546d2a5a5be6b530062fa3f8367d4676661ca99f4bc5ef2bc12d89e24050550d44a8702e15deb799d61b6f3cdf09e517f0239383e1bfefa0d6799c537f09c9650dff7327b16e870afee98beccd7059939c0b88d501aa77a47bff5d0c0f6c581597d7358e23d474d04ce5f9e01d907d02c2d48aa13a68d99ecb4a692a55bfc8477f041c35cc113660924408746069c205d3daf85be70ad14e884f6fb9fbac61fa430680233ccef36dea605c794969d79d1cf9b542bf036186ab53f4d27f2009cfb6ca7175fe8266d16d6fc0de9818d57ef9b7f03db37b0979737085bced34e9f80c2d9de8ddd32a50bdd549c34f60fe1dce82bf553bd2dedd70fe597f95e84efabbdeaf6118b27c35154c68766c731ddaa6b5b17cea1b3828e2d60ebf61fcdce53064282332403c7722a49a14705c2e225a0e6b46d10aa32bb98d74cef0a89f01eb7f3e5681772fd061b7a719c72be7c4bb952ca5f61ffbea4843488d2cfbdb8a4e3aea5672f45ef8f8f007e1c64f44be8b89187e0bed1bb3d7024223a7134b6593972456a8ea66d4a74d4ccb238a95f49d8121b344578611b60bcc66716f878195bdc684fd878e8f95e22e778b00e820a87640f430fd3a9373662cc8546a1e432aadc7ada41174f199a7731945dda5d1861ccbe90e9f141a63af523d03fba51f194df802f67ad61b6c6c97a6e6673523b7550de8e2822cf5397ff53e3b70e88bfafdb6ddc52575db38c786cdf04564445cf53579a9d0c051780751039ca3f2520858fc94378490d3a6651929a499bc4ed064cc57fd6058ce038fa18f184ea2538f786ba3e4f54c0cebdac298898f711f9850744646207d9f39f3f7bc1192ce8c0f0abd7edc620429cc41bc2cff568bd0485092c2dd6944e817431c6cfcb87bf4742c429e6432f427913290299fa57a7828a6f8bfaba84b39d9966d576b1a18fad846263cd19315a5c519013eb2dd1c2631f70677a7c17e34eeae58e8f85774a6729dd4277fef2bc07a26cc9b7ac5236af4c57160f64abeca685fb39ce96aec11d12bc691f9b39c2d70878b85a151b9fdc365c860de8819dad70a9cf9c8705c2b1d78565d114036d0984c8bc6db68f6d8b7169dd462bc0523240bb725e15dd76319f7da346a91659fdfe7936ecf3126944216e42126936216efb23d22305d12c2d22f316e2d913227323c453069161df6f39eeb2211223f9109e60317b409b46edb28845596718ad9c819be960dd28f9c2a40eedc0c1ba0a14ec81bece12e731ddd078479145713f07ad12fdca030d4c0022ad6715c9b67883b04204c419c1e19036bab9008eef70ce1b9103af127424877881ed7dc7578ee613b44e6bdedfd07fa6cfef39f9ea7dead6438df767bbd887fd6c8d65399ab162c78e0606f155f35406a0d9f16b382b5ee8cc4d8b0e3403ff7e6fed4ba79c3808d6a4496cf793023e8458379956befd3a4b1ee3a160c73918a77a090942065a94034e4321c10a53ce4079b4ebca211856fffe6ebb8691856845e603872ad8f471775827a97a4756582cb01daff892bfab75b28a7065fcc55a0555e2e3b28e57cfafc3e88b5e41695d9952ee11c7b5ca977c6110d066bcd12d7f2e4209adeee594a1b67352fc05fe7517f63196d80a87ea59254c3d491b3861b65bc8321e7f68e115393bf8b2d2419c6a1ac345e33f45d9224b9799abd115f5e3e3fdf72bc3286da1b47a1dad48ef3bf14784db93723164dc34c3d7c7c7bf09ec66bf54da9bf6d80f2e3ee64e265cce271b7ac46cc1253e8bae4d6d8ac08529ab5a1e64ef915cd0e19eeddc0c4fa6cc7f52179d5547e132166e079c5511dfdc5df088e6532388f36d27b57ffe4b06336b6148013f11803f1d7a1c4df9e611916f61965aa4d1f7900533482a18eb6cfeb0bee46b4399a87a28884c22a7bb99efaf0b67e9bd73ee3f67e9bd9b5331242367974ffaffd078050a8faf0533dde2f28f613c7a3a1b7082ff7d9943a2bc27eaed54c1ce93c3eb253cdd4a590735ab0ccf2c97aa5398ea2e217dbb28ec98c50b9bd40f2d500f0d3136338e959e04b06108b0e4eadac236222abdf2790463f4dee9201483620f89976c8b6e34ab129834c60f724d9554aa9c763e24a7e2a8d9e06183699a706420b2a482e90044a4495090387d927b314120058af9117e5d2e0045e7e5931614f19bbe9c4338a8ac287169e4265f8288dd288af38eb8c2bdf70294e89d345b14c5a00fcd07228ef5c88411a5dca7a205b23a0f4a05996bf608bbbec13d338c60281f8b7e9b4e6cb0b1c363f44976253bbe548ea685259009b0d56a1e7cfebbcccb072267dffbaf49fe92583293b58353b4a6a655c48a425682aa0fa8394f443fa6ab403d852a7499e790478ba0927f2761b17b42888531af3a637dc2cf0fae1f1133f1f440ea5dbeef1cf2b613639e3af7db6b5ecd09d2c3fa06e11d35c7f511e870191b6a5e410dfeaad9f36478e77efdd669aba5e04d4d616f3256cce9f6833fc69bd8a5f69c50eae2b31fe11c1ff9572f9ebf25309266fd847c81b5dc8832ce351b7dfd416e84a7c4e20e5ab4a563e0358201ba2ddeec9824fd1d286fa423c4e1c993457b46e8cd233093f436151cf08b5125a180db225b532c7c6d91f0ada430dff07ec95cafdf27d4357dd4e2825f8642cf6845aa8a39b0fd7555ed7f122bd3995d934716ce45143b4bbe3100c33fa0cccb8da29b12fe45b05c58e1487d425d9cfbfd1f5a819122090e7d53bf518d044d88cd068260357f935d6637fab00099460813354aa85b20820314b920aa035c2bc9790dab149538f4142d66e99854ae3a5094c7b75360b172dc2959791e56d5a029545fe9dfc63ea9dd61dd88bcc861fbb4e64e8a9319d43293e4d3757f093643955520f329ba7113be765474d0b88522a6cc6de26f9a22ade2db7e611f5351b3c5d1cefbed8f594dacd72f8afb30c6eadd8ab94120c2d3a7a3e7d14a0602ca009614473784094fe031b74d5edf297db5fdd18fade48927b02a98a4c58880a1a7c3162aaaf52ba53344c0c53c4c1561090e9c7d0888a12d182224a76b5c20ef9a66604a25e44b0b8507e8c5a2d381edbc7e0ec6854756959e833efd0c855b81603e4fc80ce3a223abc698a1193fb7e1dc23d83e5aded78ef448d2bbb36747a6fb5b1a8ebe83ce7350ddd042ce9989eaf9c19ea39f17538f1778667f1986f96afc8a9a0ee8671901f47bd249c7a823d37b72d2cfa5f0ba215c641eaf1bf711893ae3cf245c560eb0c89762142f05305fa983c0cc9fab61e026828bd8b86adaf890112043129e499fcbe1dc11ac18d1de83a57979108e982d6a7afe0023be13ad09e613b8d6e79f2166e58091e908018ac4a4feb45462effd968645e7c15f791ffd3bbf79f64023fcfc455c12196030dbedb0eab41eee2fb3431de70abf2ce0cd4b09745ea60e2eaeb5d5ca97bdb418ff6f0e43b30b37c4432e7cb917ade146b13086a95cb0c0dadc710cd6605b40693b6bebb81599161b50fa93925b54029ed7645f4baee88ad1f820545036bfbbead249109f8ef99c3181647b16556870495736714bf64fb65ce2e498539bf98230ac3396447014f0b5ab391a7b2f9e3175c6c83297872af403654fd80c64b334a100334b37b8acd8cf188222033ec0d8af942e07577a63b1fa757f5098596027da00112cf4492551482951a03bf6a893641462e28c42929e82b24f81532ee39aecca63e8491708128d9dffc52e7bbf44f5e9806f47932c3919364067439bff47dba4756a56405c1c29d93581e73a084a748582032fc37f5329f8cd99a8f312ddac5cb4a988cb0730abac9a398101eb4ef66a1c1f15532a87d1d840ea9ee1ce303a8acd3d68d43e480b33619f5378f750fd905201097bab7adf9d1a1229b452229c9455972a8bf672c8432835de23a69c0a93eaa02f0711575d1426cc092fbcbd37d1bd75cabbeaf3ab0edeb897e8f680117903f9ecc7002efa5a72b83af6fc8deaa0febdfb4d5306ac2488ebd41a28f058c4830facd4d36b43f9b63dc77ed7d14475887e92a223ff4ad058874308595519b297a658ec2313c7a851a07449d87437d154e8692d70861dbbb7ad61fa6616bc49b6ccc1a99df871b04016a4fb2e32359af5dcc8febbf2b6c099f6ed925eb3239345f31d4fe7236206c961c0706f6edb703a53ea5a4de1169c88e427b6cfd38883127fd98d693ee9ec3119d2d39b6123f3e28397f732c61b3b79cc597f105f7a8e7119abcf5529386df15f5395b8c0253a54d0556ae817bd8006418fb30a9c6f541fa1805305a217b812a794eaaeed94501003042067579d479d5359a84c1c779c436c82eea9384b662540d5c64209ca04b267ff006a6a3a310c5151bf7844f96af0cdf573a566bea1605084169eceb495d7db7710be596ad938bccbe6155c0ff6686a256efcb34c4fe2fc1f8e51c5790f197ca23483248a63289467ce6d46dfff93bcca8e8380f47c601ea39574d57ff9e4768790ac151fe9a9d67620141d627131ab0118f2a9681cc3482ef0369c2fc64c1cec66cdba1864b2316f0e124e9fef27c387a303f5f018281d465fed3bad7c335e03a456a00bcc503e2bf079dab21b5b374a76ca69d11897ded2924c317b22885101e785ab81c9acfd7f1663e43f0bfc068962db2a4bfb855a66d5c77c2383ad4263c15acf680ea2ec5c163c82736c199b4d599d38c38710b9e5f08802f04e02840dfebc48bb0725aa668cd493107c287b1105372dc4bd7e787e6ad48fa7475f96ef4ea7487b3b0c2cbe97683ee586fc6e05924b383fd328a37fe865975b1c16e5521382e612bdd67947099ace81baf1363859e05b550681dd3f7aa36e414e04d094f10ed76f7d7e12f3da26394d0e2d1fd4495022acdc62f786d7fb8199a9c7036ad55b24ce5fad9c8dcb484c4283576d801f0f006b6978a2a79b33c7e03efaa8445edd05c0912f7c12e40373e461e8b8664f800d4d8dc5c5861ba363422902124bf0c5da8276117dd480ba547d0dbd8d1153474ecc28c3bb97fe7c8cdcdbd01e95868c43eb55c850295c42c28dca11edbe4848658ad0390f6c012c3b3cf6336d08a4b5241da5bb4bd1e093101ab4762d41dd6518e28071c62be837e7b058977ed4321ae19e616995c79ce08ca269a6e84ce8692c0e18df6dc7ee86e088f3dc971a207f8b06a849c6bd1e971864e6c7729c374ce0a7083acfc0351ad527f1626eb80b95958fd58cd6fb6dd72f374beafd5acc70269aa4522d808ab443985f7e77c564b347a17213c95a40cd14674e664bbfbcfd0b049eb0632d3405a4274b5a9231c2abc79d6d15388a27fedbf0df226dc8f28d4249b75b681840b4d14e2dc12371dcfb056346b538b3ab428f74a552bb53c861e44fba46a0924f215179d01ef80ccd92e146059846bfb916aca35e2478a5561331640c8def32fa1d55ebe228a2cb87308ed360c272a225de1d4cc85852006dc823cd365c54b1fd5b795ed911f8b089f50bf210f47a733aa45fba375507aa8766e9641cb5d8c6f7680ec05be278246f1dcc0726bcd640b202df5195b1ed0eb9882821ad414a3c94f1bd8f33b7e67ae87e7f42bbc3d034e3f34042c5823002a269eb5643ca4b129c08ed45b1655d898d0b4e24a75a954406e284b733914a2e13aeda018f230e7b28c48a44158648917fc83c2fa4b5e6f65f20477f8239f5906fbc3c29224e05fd532c3b84209ff10789dd67caf0f5c0e7209823daf7bd77f69989064e5bfe911f39e19b87dd298507b877a8bd3353dc40ef301c42ccf579253de4a5649949ebd8b2484483673d70f93c685f1b75e0b20ac4d974daaaf1db509a35fc94d30c278ac44f364debca4c78a99c128909cc17ecf1549aa992e1086fa89032a18189d2490c386a4fce747d65e8d93f527c41007204afcb5ef10fa96def2266fac6d2cc9c774d8564c94e18053de66d419513ee89afcb499d93dba8d77ac182244f2fff543367c99d518e981190f0f99e7bf459f2a965c222d6652e2336b4e46f78cecbaa33521084db9ed54548161fe0242b46f9a84a6037a76868c7d3022826145889175d0bc97046f4c6bc84bc63e563334eb024079a9846f80392b97f69f5e096eb3bc43f35e29c60e99b42c72e604cce1959f8afc00e6f55799f17de7d677e214deff3f02b949ec23c64068eed6a882fab98d98fc9fb78e90ebe83c79ac7eebeff25550792a801c9ff6d82fb5d8706de64d87a4c91fb91829b9b34a86543fe0cf43e7a8fe50253e78ec7c101dbc1cc4f4e20c161740448ab2dacadc31663d1c4508e096c900cf5d47a73e66be1fce885a1f8d24a4256091779b7be8fe05b57e1aacecf080dbe8b37bd35db44526768993ee3301b319f23a2d60114e82d34605aa73ac007765ade5d3249950148ed3737747f6a078aad255c4a4bf9d1f59ea8ee9b02b97ba39bfb48fb20a27ef8ea034a08c28c00483f49afe9d007c33007e172e142ee8021c7745b119487e30db059430d97e629a76100b7715703c7f8bd1e655dc62fad4e58f5fac685d94a19b270c62a68d8643f77e5ff0c81b522fa607e04579279977e6e002aa4b35e73bd1b6111ce77048023a4dd6c10929ef2e27af038b9530f994e480019757d19345f561a5a5ffcc09b0c4ba0beb629df12994c03020604293feb8e209b1eecc1efbcdea4b78c5cc46ae79133e6beb3930e0bc09148793c68848d00ac2cf45fef68724fd5b36e7d98b7376a0a4602806e4da2cf7dacef61725ac9f4627bb19297494787d68991f2c5e18d1f1b94701c2ed67955057c065baff2572e21f937d43adcd0dbf6644a945f44eb8a3e4fdeb4be15c5048044beeb73f03b10331604a840f423747512f4f2222d65bcace010ce3d72035d417c5b6a52b9b83b7196d6596d0cf8e14e0c21595c41cf21690b026639bbb9926135de1e2466321aa6777003f1a93e3df9184b846ff3d92092f4868298665f1a062666a186efc748966292a386ed3bce73d87b383b1a9275472b6a2491aeb7e5b3dd453a96afd6b00be6085da740437590c86be9be4b2a7b3e466b52ad128dc6a49094ad36b63e772caad0208dba55181b7d3c6bccce4ed6fe885fac85ac9f2885427407f5a3bb578c53e2437a999df21db1875eb813b386073832ab789c89025881191fd40212da9c2c176637c95d842edc187838972d9f3260f11907b6398f5d8a3c5917d86bd782b97ea42ca20f68daf10e3a3f67a1cd039530624e88481313deb20c2456ef2e37060c68a052c2e5f8ec260ffec51e8bbb99ce2326f9e88409ce11dc9f93657f358d45bbd9b97b7e182d718598a3e25a25c1993cc008d9d2a6b9a82d40d6cb3acb2f2fc2347ed0c249fc257e19ce1fa2335c1c519245da948655ec6844360482af9ef916a497f532a38fc95cd15ca6d236297e75b288b64133fc5521329fec0ff9322fbf3b5c3ae77489b749b510c1b74cf0cb70baf54d33f17446b44d8905d9e7a7b217c91d1c874af8feedf9c3cc797e4079142b24a4de0ccc0757620c6285ec42dc9fe423974b91daad21f348a675fd5a77060c65ba48c7cdae2fa168393cdb2d97714ad44fb8c69a865742421c945ec8561a42ed4a6fb96a691692ddf937ca1396c1b374d7d8d578ddcc2d7ce1c50ad1230117ede4b875e607fbd4725dc1a900787f28a0bfecddca8e6d2e6277385e86585700fe6b187160d4312e31abc68cac41d85f226adf0404a8b30fb90f3f9fd3260df4a515b14afeb20344f6a05d05f3b815125c5240b26f8134aacdcf3e5c0bda54593a1104c5f5d8d44bbf12a5850187bd2af9d82b360432e59438521fe2d690f4e01e7361825921c8bebbe818c5e4abe6b68e6b14d067419805cadab43e45e90c826afe311cd647fddc4c8c8eafe723e3b3a561b8d05ed963f6d7382ebb16fc4151773b5e115a80013cb6ac4ab48fb5392e5519699472a3dbfd2b54b595585cf9cdf136943d03aed92d0e7eb78a8aae9af3f0a103277b0f238447dbbc56d41ed0e43a8ac79c2abd6566093c03ac247470658c4144bc825b46abc034a6c0b1cb9dc00e094a02ea2e66644469de3476b8c77ab0e68882d9208ec3e9be69f15c97072837776ae8d3f08de347105b69fb9b81b6d5ad707a5da7a8832fca2a0a3d447c35836f94b4b93199b80d73160e358ae0530204ac1051a9a2fa0def7917106c269abf6a2340a3318480842ef018dad0126f990bc7a84142596500c7ae397a1a10b0773b495e9db781552ecefe533ea0ed3b3edbdc0705b7e09eb3be8b90805793f1ee211a370eca364913512333b597b36d963830185dd5e8cf84f415fe04155ea7d515dbb1a516e6077aca9b5bc4851cc6389950b7d7267835d65ee9f27d1513ae30345c596d46f327cb2f403cffadd13a101c256790e6924f8f0b25e6a2e15c390504aa31416acae0e727e8162ad111cdcb6cf5c7f7e8eadf4d61c6dd178f0a658e1f27538247904edd464874c6555e615e2d54c34e71471ce42568e4786b495aa3ded77511abb5b1bbcfe2c5f4be76930fbc4cdd51d4cf3a23aefa8ca530e247c3e123fb68fb15a1f4a6fa5139d2342302591a51ddd65078952dacefee7ad04db85c033d31ef3366b296e839108ba086ba1e466bbf065f2e177891b6a0e8714476c51f73bbbf652b3a51592e12140d262afe57f0e67322186506a0d0a965db6dca951b2c13ab75250e87a9cb67c024e26878061d4c19c03dde7ede37d865d8504561cd1adf4db6bdb41d83e65e7238ae335411c1c548badf82cb5253982a3493ade15079deec74974c22823f8dc8f2aa5a692950c735b4346d97fb613117846f27c240996ff6305c7e2da9cc49dbc0db812e1bf58453124d9d0075a37cfd26e3d71de016231546a1bd61f4d246315b30d576405501789ee5e2d20d75b7705f3726204729f39bb09743b314d0692d000296c4d2e796e0af5120f374bcbbbf99cb2547b64de5a5ae685945fc95b7873ac1b4897d86e3348373a4d2aa7dd2b0ff2019d06be6ad8370c419be3b0634b76c0a9a4dc1b6785679889e172473d5c57a5d602327d659af83f8c1aa6ec03a31f08d4a1fe3f169a3283d67ae4ad5853584f1b61ee73824ac4ae12582ab1744229ce6b98ec6e36931896836af4e13e3b216dbaad4e5887afe8be944d6fa56695fd1999a4e1734f0bab5e4cc359a713b7aa5432faadad48b1790433d1c371d20a54c8351abe59b0cef1aadd39efed7708904a5602c1d6bee66c263d057a16061bbbecacd2bc2962c4121d5c9738cf05adb37b2e44741f1011944d69d361161c37d48873787d4a99521a935e3cb5e0e8990eeb9d0246022a507f2f1ca68e12b8e92fd60f3e34067cf259f76239526d6644126a3036183f101693ca4e50ff3633e3d281dabd140102899b2f984d0bf63fde2830b14e51d8cce8b59367ad7600b9bd88f8d185a8d3810b0d3c91017817cf7f4a84e461b8da9f1965bac27898b8b93d87321d6af41309dd467db4ee6d537b23daf9b9f3b07db64c2045942723df657cbc96ef3d8399670eb4a32285de4067dcef228b3c493b50cac33b7079239f4c8250256b1a71c05855c8f77ef4b375eef464444ec83e4ef2238db99cad100b15f6e6e583650db5ecb7134d416a0151f1626002bd0926885ffa027acf9ba7f1fa456eecd06878b4c07e6012ac165b0a12a6c3cf9ae9f34e2a487d08f7f4d4dec4dde43d99e2e3c66dcc1f7dc8f80317b65fbab6687fa1701e2be7b85b09f7ca4239a66205518622c38b1c86a1602086fcc19456f49544589451989de95e58acde1bab8c384c117ab0ffb858cf15fc8b6fff090f9c26cd0090c6717fa2f7917bb33340ed22d5dc0c351c662e329d71d900487bf189ca9f3728d55aaff51652ef155c773cd7e8830a68c0947bf218e0b6b5b36c1e44d7dd540b7e373d7e0b1e1de095aa3587aec71405d71c4d6caf1403dbb8ddfd26ddce6b35d6e0eb966ee859c872ccbc9a5091491f40f3c428aea607a8c83f50b43e59f6cb222f852d82e91d438d61039c745afd9f1b19672669144cf99e69c8f5d35d62f7076ccb39bc0f90c67023126bbdf432aeb48cf35498942f7ede1845222325826adda63011042eff129e7a9234f14bc75ff124a90fa956735a986ceb62f4219456d1b34df8625df82c4ac042939e7958d89ecd3c7ff7a072791dbd47fbeaf77d127b907d51e207fc58ed185573fb91bcbcfe4af6e1479d73bd04bacb0b6ed3e01ba05b923d28b48a5b32c4edb44faa70f91872bea5036a24d88875bb7d19882e1b4a64067ca1779c0761d0cfa374aa15bb59f34e5d728a635df23d45b16765bf35871b284452d44908331466a46569a4042fdd55b039c89fc6b71e32d1c31cee62d3ca2b80dd595260448ea211ebcaf0467b15ec768cf838be82499af0f1e4820f15d185cd07f140cfab4fa2c3af4f6c2e308c442a8988c42335c1e24ffbc294f78b02def95c281f6ddba54da8fda36a718f56e326735050a2f34560d2acc38d9ec835d8121258cf44036f710595216695dbcda592774946ddf61047464e1dff1289e626d76d8641244418ce70dfc27b8dbec1589e80b8b446bddd82524fa869f9bbd12926e5455c4bb88f681485081a8db14e42bb06a38a472c497388092c3f7ebea125aeb5fbd89f572e5880a6172b114dda7a5d32bd335f70d84fad5fea3822f355dc66c3fadca2907690391dc15bce293a851bf6694c3a9de5e69b98402674f1fb5887febd09a300d700d3ff77d2cbc213663e425ce3b7fece61e2297c1efdf79b88cf70d61388427ae74507c9f55cd4d1a91a7763b05669befbce4c54bb1c88b67eb4469750a3df5cd061418bd70907b923745994e072e986f6acb28385b7e40f3834dda9a33112c8d385429742bd9eea73079d7548854fe0775746c167524fc00e4328ed2d838b9cf32ce91512bca1a3b11a23efeae9c67b95e743709471d154cca30737a2d00d7c9d26a8070d29614008c889c312db6c139a6a99c60f08496423a754e6e4d31aa31e6ec6f6f561826be7bd5acbd387e25262180ee5739b97b5791299699855d9797b08880c58c300dcb98496009c1128e5c14de61f9637550788e51afbe9e1d7eefa9bb91363ef12a37dd8d9c7902587d373a3a705d303c6e82c87f7654da78661a21d5a2084173e200e6f00f17869fa170d076ba01ed764dea9581b3825844363a897c4242fc2016c3a0b8eb4dbdd1ffbc23a46da1b774d1aa188aabd33ba11894fa9a32abbfb54569a1ded6cb3263340f74ef85d01e3b155787c756ea1121368e3d529650af1900c13090fc5ec08a47a996e13d53c898fdcc7cea5509e3841deb502bbaab542a645a7df227e4cd07bc25c9313f87651776d962b32a3739c047b632ed170c16e9ba8dc7e9866650f2002de2d17289591a01ca3a6108652bfd65532d6ea90c18353021431b984efe05b10e6223e428cdf0f9c73efde2745ed07599def1367593c308c0eeb170fd7769045a2f02a5f07faff65e79674a0a298a824a1c88a5e5226513dc7d06edfafecbc62d62869f1df0276596a440c80669e43b910841524aed45f43ba9f067411cfa1078fd3f51f47fdfa786859f19031e2ead8f4377196dd4422c5a42ccdbd527d7fa41aaa8bb600128ed050ac46c31e13c2daf844d99cfc97527f2040bcef4cdbc4cc8f0c44f538ba8241856d847543fd008ce1889ce7da320071c1449724999af4dd974ca5b1cb8311603f95a8f1f3a32f9530d7ed8e5d906f369a286d944167c02230f487261d1734987f48468694fb02fc8f70a7fe2dd44aa86d9732d2268d51904310c0640ab421c5e942aad54a6fa02bcfdb5c1397f2d2221a3779b16fdc5c90acc4e962f2023810b14b89b5b4181e3301199ea6f9a04a762d0b05bf955822bfac6a84368c29608e46a402027052ef04d29701c0d561635e733d75da5efae1a0ea1270bb3259f0f9f6666d5f025457749e932136a79a6825e86ffdf3587408a7b9a64ee794cb141fd1525692490e4bb947c4b70e25e4a1343ba61d0b5f454b137d18ee0b7e94c08cf98e9be69077a51d5c1e0dd6bb9ebc14af811829f975d56e26d3d660ff80a3267a80fb418baf1a44a5d93f0463f38249e8cb1643be3c234fa9d29dc9a5b946b65d595a2a431d4a00af1898a9bfbbdfec627f8d7df4409b3f68050405a07588b0dda3ce5a00d2eb428cdc739cf97140b8e62c21e5a2c9b025bdc185ad219db6c8540551a3c4a034f41f0893aa0619ad44725db40610ed869ad2ae121429b9c417af2ad62dc9cc181683e7a31dc1716b089bb8e8b9adf0c9c001ca38b77cf76e312155ab9a750d210758d6ed8f84fe44b7c1f341cab0c9a22769e67160e0a3682bf896f0e7b44f7252825630e8d43928c5e8e83a661551428c44eb2b121d26b907a8753e3f73a739149318955b6fc5028931fc4e8cf16fd19d4e52dcedcff9f0ae952055549143e2eea8f8c68214b2335163be106d762b07e677932b4dbd466943a0e1bc607b6432601130cb24be54ec30008d067a5ac4816520397055977e851c8e1aa1c25dd3a25222cdcb10aeccd44e30a64ebc938379daa0b32bf552daaed59c697207852606aa50e13988213b1289334d833c88e7678539fa82224392a7271c2983cc9888156349a5672ac215ca70a5860fb6325525f316286c181fec1a010095a061e82b9797176f46b7a8cc1e43768991e57cf04755da719ce96a8680503cc190afe33622c1abe20b31d25d07004d8341f810dc022e4164f644490eb41889379dda3bb24479971f64537c736da7532294a2f46a2527cedd7dbdee3013f94249632e8f082fa43fc442caef35ad850ca14c5dfd654d7a53fb71d6a0c5b8145f46664d55dd43013af4c0d562a1b9fcca1f32dc6fb542a36698559daeec8843dadc34758b7dc4de4b1c9a13a078322f383d374e0652b6c14e88d43dd49e3c414229133ad5b37b0841c0ef263297240d23554deb442453102fd74123d3497c281f49d19be4e9d390b431e39970c55fdb2a56f1dad162c888039df4cdaa95bbff0fd8a2baa4e249c2dd6f258c52784e6a1ae48a23f64dd146f1f17d7f9b6d5cc13f7e57ab868393099e835e08d6b4755473db88ac7a21e0a7dbf5d98de39c64af3233e3eca9022f4c42bcf668145d67410c2efe26e6ffec0e780a76ae16460b2cf353775f9c02cc49ec8f82d1ad1e3489e1d0992c017d5b5577f81bc69d1f9c7107d1401a3b329c497c8a2330771958578e4988248ae7f05aae185bc640697892c7ed58939589419b95489e99eb99190f2a697cd1efa16d3ec03654d273bb93177d8d5bd268f60c71d46d9e36d76c841f648bc89cf0dcfce6c1add8ab9275646f3384b6c73b5e6b9f7e446047b4b64f24ff8e065365354946d49131dbf1326d61b4110002a050c76c9c76162f23e99eab496c06ae15adb87d426dd6c4b3b012ef5efad0cdf37543ba1d6de97f2782c980bbeb614116a618dbb84ccc2d61e9260c68fa242e7e1e0e4663d88c4b1c4d72865610b6de10e45ef8d14f3d07f56020b16ca61df5638fe332971051f22788d03c5d5adac14887b96529dac3e5a1e401da0056f4410aaea6a4d3945e01ca6bdae7d5c46b37a797c4406ac54fca87a13ade97a7899970848b927bf20c0e5244b6430eb8ba21482d079ccb6c1b77a843056e75bc88638f89f5445914ccc243a48564bf87df451a6fba6fbce4ad1dd4f0f7d07d68824f084c71abbba90c6f104c3b82aa0a9f5d2405c37002bd63589f499f363ee891a2e1268e3f5ff9bf7e6632d768ec6a10c3e4776d3f7e4e29ab1a0046fe5b80377616294ef79efab626ece69526f9923d745b578e8ebb00940f00bf608e301e9bf89fe3388ed6ae653e64346ece39adb2470de0ffbb4128814caf62029bc5ce212cdc3d41c76a4677d5414eee509582e639759a2bccdfbf3c4e81b78824352fe7f2d593bacddb4e190dc23a4fdff45bc90e91247b2f335b9551d8dcf010fa3f45954a6024795636a229b60b49563f52300ec532e009148dc105c26ec3302bcb4cc8162e2b7171a00ec99987394a045714921d7ead2821d6f86801e1491f9bd3dfea2c42f164f41cd4110b376356f179b2ca8f145136dc020228c24b41688cee24c3f9b36aa60660bee18ee6c262d49d0cda634a07d5c69b01218ed3c3423cffe9611d3f64d8cd1b1b37551cf7b8cedda1253bbfa55e3fe99b43ed8af6e3671e9d601ab9c1b0d0c35694118fbe9c5c499406008dd977402ab2fae60c67b22d8dec570a216cf5d467b303703e82eb3206c7129c0de1943878cff57f3b656dd71cb4cdaf2fdc0d2bbb022f1fb7a82544a68c0e7d3a452694ac90efd60286c4f0df771f0e43664d3877f2e7d11f8b4c9f6c5f97cc8f6054ad00731801a13b4a2b942e010aa69af07f560d8251e0468892f3773dcd0c49d8b31220d4e06aa927e06d2431b6476391b7424707570a3ae892c9481d941fb0bdb53f53364f84bfb6d707b83f2a8f6ba2550c9160bf7952c7c096208c54acd13c39547113a2fac17911ab0bc41c5af3c69e548b7492c4ba67bb7d69bea4824d5b0a9bf67ea63237a4a9c786652abbd02962bf4566ef321db30ecb998c846211fffccc5b28ec3e2b0286fbb3db37c6cc4ed6af9047a60bf56967b51ff8ad8eae4c289f58348915b38e88165f42bd28a2448bb82c90d79d6cf62bb407971535a7a5239cd34bba7604b648164d2c9ae059ed3af874324d004e0d42ae8a68ff151246356c7c29f8ca4b5c3e96fcae5a9287812ba4e626518a51d2cc591e90e22c04c804ea201c7f9252068e3446c327a46dd47f62a593f1f54da8c67441bae9f7eb4b54cb22b452ceb1050fb9fb096c187f583660e4e1d4e3a5880fb4c14438c4d6dc53716d195ff6daa179e8d40d341ee1064f4dfbea8a29ec0e9647d414cc7d5b5ae29f8996a1d03de4dd0b512b087871c0c215f71369ae0fc2c346a3e43081aed73e6094e8945bf600645fe6e5fb0c168118297bbbc492e1384784d596607aa930f66cce4e338b8736a01a111b15690451e85fdab40967de757515d3261e3a7c5c7e4cd4bfc1bc97ebae9c209f0ed1e823ce9069314e0cfac6376750554439113e74ceff774b5a9a8fb87ad91a39b006b987c24842a759c79a27b1814d2ee3f5ab79479300541d420f317e6a44b622349c55f7ef4f6cb2c545c7b61d13b854bd645d3d2c928ca3e809db3a780a2d4686285cf4d51809ff4eccb9fed861e9372c47aeae3c53b70086e60cce15ea4922933e0c1c858618eca5e0e7a2e207c159d157ac4c959796b892a1c19d9432e5413fe6269d77490859a4642edbfd21460cb6a0ae0aeaa977f0952009aaba9fb22b196f6d243a250d85a8ad30984d30dbef7b781cf0e3b64cd760477e15f295294b2db9f2edff2e2c3f82c968452b38c6a5957d213dd652da7dd960e35b924bce8d08e4082f83de2437244d431522054b0ff2b637c7722c80595c3038e55fb00550690ebd9f7bd677f3343327f00fcf260d3195b07a7c8b99d6f50d334e6e621dc9d8dc4efa805504a13059d9df71190b37e3944c10965ef1c85bd735cefd45e8ad458effb69dc41fcf4369bfbada74f3c68474083b2581b06c05b10deb3ead9be058ed762135b6bf965fba3bf5f059f2371eaabfaf32b7f86275c04e19aa8a6f143df60f49507173b83a2527cfe0537490e2318ad6670cda904f4f9f1f6d0c07b33d68aae1befb01aa5c6614782f147471e5deb7eefeff64c0b3e92b08a30861eb2405c92c0bf1284a70bf79106cf3f654666e818a47a3dd71833314da38e70a03609627f00fc123e52de2864dbc0a63d8c31a2b12a42c5acc1bea82d725f18dfc6a4ab0d9d75416fa6331071aee2523899989347f4f12a5d5d7aa3d2d4423fabd2f18319a167a861bfc6cbd4a20401d5c99864cdb2e696135dd1ba9318c2150f41b43ae62ddd24e22e067867163f369eeb571944a9184363f79be639e3a2f4b68931ac474c9e5e43cea0e101426e9eca945c27cd6e17dc28b617875095d1e55b1d27c862702050247b1c7c51816e5e1cf0321b0f5653a9ef3b38b5204ad35379c118226a18acefd0e83ce0f38a8bd180691b090341922ab62bfe407f11a8c68d7326e315170dd1632f35cc2752c5a9b3078c6ced8c8b0ad41817845a9b07153f307abed3758c3ebb13bc87b2c36e896db32e1e459c3b862ec8200f9c350a2a8c44811c51e1181a81821062ec24607e803602102877e0d2722f55d2ebe8746d65b45496b463567bea8e0faf64a8fdab32aa57aaf942ceeaec90dcac563f4ba6a8432fad5be0adf33456f376c34ccc2d1d128276baa56b062bd70f5758acd32e220fe001f2dd1983c688abc71aad91b062304d09a3737753c2836689e946004eea5b33cc34e018c34b18ad317839cd689096afbd260666c4684837a0e0a482de6e5325bc1cb55f31700cd90d3481fe028e1c71dd559082a701437a16dc8178c1a09cf18701e20ae471d62a2014c76ef14e635614efcaa6c7ac3ad9bfd82ca4a84e8a41dcb1b80f9e3fbacd2356972a1f21a6641cdba7c21caf5c8704be19e9a65d3b7d3f2becb812c588fcbc700ded737d2017baede27c670c69c7b14a16ef7eb1adf019bc483dad213a75a277f6d24e493085ad8a9f2731d2884c62caec6b4d9a176365826b2c3fc40c23d8c4365a0649e7d322cfe46587ae39443918cabcc7d21f68bf153489cb7a95cfc960a4cec699fc1c8ce0e65c81887d4434ca34f309135fd489ff2410af75a68a8ea5a11f8ab88fcccee6a6b0ec04e80c03364da28ffcc2c6a8a33b76ee35f2de0bbaa41545e4c927f41fd4aabe184751974615338244a146c9cd1e5c7389845c9308d6e8e257e6519ccc804dd14692afc0ce17e639b722ed2bc5d7dbb7e9df8d9ef1dbb1f8bc0168ff3ac142d91c42485871ac69dbe15725b9b3300703b0d236ee0c0aae8856c3027746c0c315919bb822242cae2838b3aa70b189c047fea357cf8f9a8365e0cbc1bead232e7434d8f28e1aff35da5c730dd7cf7f2ff5bb6e5020a5de2dde7661901088113a5265078da55cc8f09c82994e45f9fa43a75fa490424755849cf172d489545ab333bb56792acc53b71e1928a31c0ba3a09c1ab42c2fe16e52f8769d9b275829f75454d0b5002ad86048e4627edb8f13b6e128657ca94aa02acd93293ecfbab6fa0e3be484a7b80b47892c48b0625fef49875b3fe6b5d5999b5cf90c0b8573f1b9ef284dda2b04a6c7823a4fbf4175ee1df504477cf42f3a9aa6aab527f222e811a15e413326d16b146306489a9a3052b87050f820a23b431165e476cd9d204eb5eb0957028cf46d13eba3c749079e209a63b0611fad54a99f7786d56011bd50c4958b6bbef95d4156cb61794220f58279c3511c46448251824d33406c008f084d3333138b93629e86c009889c4865b7e99bb81feeef285b12ae4831cf83d5d872c3032181c9e65f159bd3b30ef1624593ed68c62a91670b48d020d4f85b538afea69f2734e65a24f8674f9702f81a01f29841001f146127ad540416c9c70c92fd0dea750be4e845598b30c995c71252f64c4abbcf316bac59190d544cac9edf9463dfa3a52f060b0dd993b979dfb6ee815833cba385a263699d1936d83760b46c1ece744fb866e12fe3207f1d8fb6e308962284867c0753e13732b5f8d63633f793e38f9c7385c86a88493036e6bd4372e9fc532e74e3454fc9e03160c2a2c51ff9d8bce3452ed2974b6e4aa9e694ff93f470ca0c505212505277423459ee71f8fc41c992caa0478bb53a24bc8b54186c0f1fca41d9a23e7f7caca650862ac0bb2c9306d59ede2f4ac2266b9da13bf9cace42b04857708fef9e840c69db896417ebee2436168846dbdaf8898020b5b83085a9719c9482c254cd5e72f6e0e5b88277b723ccbb6e306d1d4eab68ec68a467bd4c02603349e45ec74f4c9a1a1ab43725c793c0eb8f2a4d5d79d660150b3d32b555573608cb0ef448ab4c3a6b629fb5ec7b3901c5fe0948f61891bd3360317058054c90933797c8f150fb8c2e7d38ef0849710f46719b18582755bc5f346b679982595d8e2e44c00b029a1b796e23e663c47838f0b7ad1431c5ba6d2dc575988caaee31b2e06fcf29e97a70decfcd355464c0436fa67c60b2a4ddc9b565fe8d2cc63fcb5f073b74835e46592977c7aa1d06f0bc805b34d6dd3266886cafcb0a986f5d7deb90de58293728ede046f1e2758f227aa8b186c4740b02730f163133e856ff1589cbf1169c5b154f4103aadbea7d31883c30a3fd99172b981146bba8e7db0234061ef8e51b0d55a6b59197f528114ecfa9b705d873445743771a0559b37be76bbc1e9f8d2bec79a8e921a4642a6fc0770f1d5e0765af7e1d55dfa57c0c383dcce2c132f496553d8abfd68f68d58dba9fe545f4b9ee49035eb6d6a8fc5c2fe2968d7ac5dd7301faaeb39303bda4b651ed73dd086bd1b8ee657171fa778d597ff7cd1c10b1d092bd4d5d07ed888c5afa2c50def5b5eb55b78105c9b96e1e6c04ee772ea3fd50e8137b7916b49a1cdc41078bb3f160a28108908018f15c69423054ee5cfe77dd02bf09f61d0c012896a8dc5cf4749dee91b274e71b46244d4d54d2213d8b8b66052d3d56ded578d8136b0ba0d1277d6c662edf00f202dd60e975f2598334b3407604bcf75f3ab66d502d3bbf422dfecee4cde8f1d2c638a839d02845d05f8b5749faf020df7884f16d3a76d1c3a08b7cc93566ed6ba09d09366bf5ebe16463a26f63e29ef6cf8b8b17b41bf7834e10880cb816de61d401cc97e7a1286c7d98f6c8731cefe7203df173958392d87d044a154d80f647745351a89f322712d0f820e0f05838039165612b432631e60118af5e8ca42a8c5027ee24d6345f1323263890d30dd56e156c3259954ef1c80fb5850def6767c87b0fd4d78187ffcdc94e8af8c2ee8207b12bc82276892054811e663de44e88baa95800d19080b7fd0320617d4e012fb5889bbd8771d91f871cc5300dd599f0874a6280817cacd8fd6a152f23fa83ed4a3bc7d1702ca4dc916e3b7f93939b1869e832cf102655f2c3c187d31c585f24e8a21b15140e3091524af8fc0208eba6d0b8a3c356a5b0c2d83da23d5327ae8d9c74bcf95feb8415f00c1f69a5af339d49f20188124f5d04829ff550dcfed130dd64f14a3a05de1fd1bdd01559d21da3744b457a6fa9df2a1d20731091cf47238559a3c10bef30f7169f2bc31a383a8997f9577998e95b98009303d704001ef6560964c0c327f93a5bf326e671196de36950a2858312bd03934b64b952aa301b6c8cd238102d8e5240a2662e1a088e40f0114e322bc296170a1243d0af222054b3a4a93799f2238c3e53dc35142148da550e98ee0b3100de5069c26c3c4b44b844a96ea2eb661ac7d442c65a7e9ff553ae82c2734c652fde366f935d33a95b07c642f7a8f5e582bb992ddbc8089b9b5b3f33bb99966c89ace500494b631e32a14021e957d58acb7dfe6eee8f331eae0540b0e1311ee33a547ebcde826e41c368545f3554c6d79d90a444bdf6a5844ca97dcf41e8a4819fef2e14c0eed36d84e867b63e7d54ab81fcfb65d7e76d9b121210023aab3182c14f360cc7f0909e95dcf58c82932a261dd594dd8354afde9cafe3d957ee1c4ff3051670ece86425d08eb7ea06187a5c2070d2ff57b2e00ef9bcb1a077eb94fd015f05fea6b01a20f202cf66c8ae04ca16914804a30564f87d91e78826026246c90e07b24379785ba557cb7e503af61e5b69f4f9a79a86ad3afffe5c44e6021792022131f42a58038915e514ee9a84c0007766fa1c83761cec1b47eb053e5b4eabcdef1fc459fa512da7c0488505112d961fd9c17d2c0bc8da86cc60ef1ae62c0a3a0be059f26ba4f33295270f4e3987f441dd2260a7dc76163538fea201fab4a7cdc60c6e847a7a3e43e72727de90297b7605d58d6b900c107958f4b7ec83c74c368f6fbf432de99fe6ba24920c341eccd3b6d764fad36554ec23b5ff6a7f7e91a525c2783c5de831bf6c3d2d66434e4e3582fe45a7485127c8acee2d9fcc57449e35d8916c1e25ca89949d5bc183f94645948e63462bc25794b12229b660c704dbf9c8bda421c7e5e6899664c406f81f0bdb83aa9da790809e31835f0882c7c10515583563e249309e8251eb85c2cd63c31064621e7fb3ab487ecc7aae61bd2d591ed6eec3455b9ac08857179c6b08aa61f62825299d6dc327fe570412298e7d6615ed11639506b9d7c32c2288c1678f17bef2acabcb9b9bc62afc110bcf792b84c8c689565d3ab14c43d00716b6d0a16ada38438f9b825ed0096a6b7123747fbf05bcaf9f7b61ab6eddeb9383391b2134948e3cc70d2a23274ca586c8d792ab5c9eb14ff6c9e31cc0060b9e4ea41cc56d6231a4f46906c33be43beeb2f9cd35f88c01cbe4348a8ca428fa59b003f47f9149f2a90430ab39bfe15106a58788055664832804fd2c276d6601a05380f18f57153ed53a5230c8c58cb1bc5031a788c4ad26e2b61d49e188bdbc0692f42c648302bc1175df304e161a8fcedead11b2f1a49950973a2dbb45a57d43940d106605bb545dfc40913edcd6dbc9cd9032278ff7796db737f50b0278771a2e08d9b97d8ed052eef4859c613ee5a081b6856760ee355433581cb47b48faa010cfa73c04da25b2f2321a7d8876ffba43898b493838b041e5f36440cd5fdacf36edf41228985cec62b2938198596767c9c27da21eec49f2fa4d66533a1b88c822aa9b316ec4c6bdf1166f162f16b584d18732454542cc835e87164441eb2ee030558b16c34d21fb1da234ec97072ad85ac38840298a310a9e0765f493cd9d324756839365052c0200fb22596c1bacc9bf87174a722929a4556fc31a5e4451e5951806c02ccc1c751f99540e6c74f854b862c53d12124350f4dc780c25733ff973848347e46c344167b8c62dda87ad28313707acb7ed11d25f32d3be6f9bbc87cca3d77dfc0663a023f0b01c8e0769d7a3d86a3e6d72c83c2c80b168b250483f936958ded6e0e6c3f47c214379dac902e9d0aa3be463e9d286b08453a1d30fc814be040dd7020bcc4179b50d645dab0fa493a3f17910d11e06be937d28273eab556d6006969be328313e6213a9b2c74d8901886c41b038685144edf088a45314618903a6876348c2d63a28108f90313ed1dc82cb3f4555e35f065d4b55b099a3493305a1a8cebedaa1d5f9d5302c36a7bf274fcd4d1383059402ee2c94cea23b07b425be858491cf828b33e11802308b311dce650322347194159a99ec67e834e8f044dcb16c815d552f6ac7b1cdb9e94b1fa06e82c81090c0140fa44fdd320226bfcdeb6075f32b3445b7578f6d705f15faf0f5b19ae505cec01958d9524b6e6883acb61e8d1da1df6c3199534a5eb1101d527687362b615c25d1a2a0e4f4365c94528aa77cb813162c8bf239374a5cc4acce8c59ee9c1da2b97196de0f1054cd62ac186845c35b389dac13bd6039a7b084a39db83536a00b427288df0a6f89409bdd940a4e516b3d9c61abd0754d63c80c0f50f2146bf470661582345ed2427e0d8beee6bf857320657e85e506384494dc518e42313e0f93a79baf4e4264f215e4ddb568c79a8206721616eb53ba75aa87123c5b7adf1ea53fe89d0aef5ad9a6d2cda4bb3520324f96cc12909283e80eda37fd7c3ddb873ee71bd4153b0f34f69a9ec4f27483a81bc5fdc9c85c630b9e8074ce826402c5d60f910e0723f4552ea559544ad8269c125c8261fd18498a9cdfacaef4346d5c34e0fcb6161446aed6de500836ccbbd122192a972730008912d7b8eb718d87756f7dd0dcf0bd3e488148196da4a2d603ce9dce355ae223bb61bfc28e6ee04c7f3953400799d07b03dd7560953390e09920f604cc25b39ead04c8c45eb1a7ae88ca8e673997b00acbc542de28ddaa510a5b0e577455c63b7926e5c5c67a2484266dcdee598341c553f36f0ad423d1759ef0ce8522350b68e5892b832b7c82807fc93e4bb68fffeac14331bf2ab62cb1a3b82fb1710c227127c5120bcbc0221a8bdc004acde736edd034acdee21608dc7baa0397bf1a4f9bc4a7cfa18164580e088b0ef9cfc02cb2e1efbba08cfa2c18815439592e95e00e8e33ef3182e1940c7fcac29e4a2c7f14a98eae7f1808fbaaf904d834518262f48c50315b3dd10a6d5dbb4a639adc48f175d30ff3addc78a469c898ccb1e662a5fe4a423cf116ede743c6c973c1efb7c89202399381f65017571638d33dea9b9a3edd33321a891dd72e9bb451e9507dfb46b36c956893cc65da45e62e3446b64ea699d04df7718f43690a2517e535d2a70d9e8b38b2b103ed97fff74353d8aea07f4edb2d88908fd6885ccc64629f8f40ad3a5bab8aea223b73d57d28078c4204495d5ed4f7833424a996c1a2223747f9f0380bc308bdf8dbd6c4833b839f9e0e4440e4f348556e5a3dcd6dfaa43fd840be405ad2184f0f901f283d6f7d1c75031fc987eb82afefad8b83651ac681be897f0959f67f0e7a3314dd286953760680e8d03f411e927278e4990a78b204bd910e0bc8bbd758ba0a07af34aaad43dab8e7f4c064c1d0655bc464ff4bc742103d7948af16e06e85d6da738e47c8465e35489650111bb1e982d57ba90d95bd324066962dea4b47ceced2079c6d31e8c5716b5ea61357858a75077ed3cf86a13fb2a29f6f02128de214064ffb381198008a33ba557cfd6122361af335f401a308dc2e908cf5be32fc56014a2312c188f48704ea1c1dd07a68cec4c0e82b323d0380722396824a41005b71057ea51379496a52c96ed9f8c63af46a0985859f34bd448a715dcab7393f7b515ed3a6b493f7c04e316298de22615e6bdcf3757846bdcca29ca5180df1999d1cafea2095db7452ac791ce9e9dc4fd0d07896deba7c38736dd1c73c46bf130798e627e384d910eeaa7ad35d01070db47cb430a2a5cb2ad0e5514ce9ac25f696f96fb83e3ae0d2a5c3134c50f8af28d46fefacdfb9c3fc1d3ea705ccf10c7cfd92a1ed5d2bb81d18db1d8c8c30f7a3fa5e7f0a2fee27aef5802ff2709a89e096820c657be694c172123a05f1f7ed429f1a93441699f22a6f736ce60dcacd14e03e947c71413a45830aabc26db482c96b4d41548020bb3000e569b148559381a8912e9ce51de8c1099734fc78314777d21e1382f122f97d901752dc33c4be674a1420cb0aa6075f0cb39da12af01a2e00a841cc01a222f948f84900599a76bb6ed4adc810659c61870161f7ecc180f54f2f8c4282c56a05ae50802fbdded1ada7bf86d833f7813d2f417ed8fdf6f649d5b76d518ec3b9c822ac52be1d7b2823deb09a967847f8fce9a1cffdb87ffdc38cdf2f0d09c45a6cc8ec81fe205268f38f06728d1426a705e274cdcfba8767fb8767ff489726910df18d0a8dae13bc60efac8ffa5b8a421b03927f982e92e66b832bccfe8086da8f961c2d84f4d15c15cf74ba4c4ff12c49f6618e7db8b0e28b2af5fdfd7b66ced9ae28e02c2acd07bbb4cf489b06f6c507ff87ce361f223522cfbeee093c0e850c4fc3be9e4b6129e4e8c26f1d3ec16de26d0412ec81e448ee97d93bb10242623ed522e669950c8d411a8be4c6e2cfb0e8850c3d219e85ff283dbc95e02f94ba05891578ba21318b761e109ca0a5e4c2ea6bfc8905c4597d7f413f97ce451122284b748358870ff35554a4d2b784c4868a26cd61365566fad8344a1d0eb27619f32c29ada26a82005264a800eba5760d44ee9834e3fb8cdcbee6a12584fbc2e89d053ee2310edcfa409f7ea603cabcd64f3d54976fc2a57ab59d09c1ae43d24d794a714289e42abe047d49217430ce755eafd0439dc8f65a2a668d2fada7409fbb34c6c429d780dc94d9841993924b4ea855d431aeb48af76c997cb282c315860ee9acd5d4388f964254efe3dc5bf72f7707cce47e2333d3ee8b9b980a188d2cc32a1247b23d40314ed38a0ab00a56eb91c784740a33e7123361c199abfafdde786042f45a887e808d6a228a74905fb729cb7c8274e2aefc2be5a4f3708eedacb4474ca59c928e2f6107be47bdb5a0de5b3c1ba95a08828526e3bce5b2eaa4009a9111c92af7c04080c2ecde7e95c13e855aa869ec4313be9c813595e81a283d397452a38aae0cea4e206d927ba0928e3f804ddebb1f67cad646604ccb55472dc4b7b9f97441765158fdfe6102b2e89613b333e40776c07faaed418ad35ea9c360b80af15de88991b80125d5d6943e65713e968b12e98395261ec81981892ce851b1343141d25c1bed1fb7743a825bdcf424d8e7eb848f50a33c95eb02e30cafff4fabfdbc3be9879ba2ec2929fea101313375bbf04c1842cf0e8eaf5ff59c927d3a2b7f37189301c91e0652857cc465f472c1cf60adc0735255fc9d82fa816946912730f3029401c1f145a5f8c842bcce152d7658d8ffd97465faa173522cc79af59598cd18f5d9ddc8e044a09734a74f0f7ccde4332e0e45c35a451e64f23aae862169d7dd383044201c773843eea94f81b809052c364df59e57304532f11e78f52f83f581d9e0281a3a7b39efa652b126fb73b6dd9e7e90d35de5f9f922e5e43d099c93a5d8a51fecc17b1be51309c25d8d0707e084e8662804b0b414f3bc3950db13a6112ebb3840439bfcdbec674b170caddc828e3fced0505afeacf0e962f890c15ab6c50adb361866a1c341455f420045106dbd76fb71b0862640a19c4dd73695c9fb91c08bcaf4a05d7e32517f517aaacf45fa02704bd5919ca6f026b541c1fcebf6daec8efd0042b024945610c7bd932721edff9a3446ab021b9671b3bbd12da3fbc02b23fa08f3c0213884a0bed872a0a147208f1ec70eea06aada4ac2bb71576b2e0b151830a21d3856a1121e5fa23f42f6827c9ce43a994515a2ffe81dbc74039a6a0a7f5e3050329b0e95b460827971d20547d2efcef14afcfe05cd811c46d7a9dc459d19b3e4db76fee879c3a20de88740104ca9b26a0895005477170c22abbc37965d438b0c1bdce52b229970020412fb6206c33387bdc591ecb6b6f79632259902cc07ac071f0838282759e4d0e554496228a5784ce04a2e186c069bcd60323098cc0c368369b213065b0106537225aa0b46dffd61ee3eeb9893244c06461f2633f30e36a32f301d1f3fba88524a290c06f38e60305c173ee79ceed3dd7dce39e79cb3cb29d590f33ccff3be7a9de779de6ce679b5ab335a9581c1cd44f0e1b2f9fd48528eae376d018e0abedd796350d967b3d96cb71a3f1de97775f28cb1838624fff696b2e477266c36a3cd68b459cd6c5643a33ea3c9d0d952cd12ed66faf4e9b3d90ce9091fb23072cb990cf305c61d4606a693f1ce3bef64606030de7557615aa65d305dc2dc7e187ef57f302b5cc0d01c3e9f297cf0f92ab3cb69114f05c1ad3cbd54e43b4454861a0aa89f6aadb5569ffab5d65a6badb5d6dab532edbafed3cfadb50eed10f514f9dc429c0f53fa513affb32e2e19d6c3c0663513868f67831e6ccfecd66e970191ecb2feaa30edfade5fb5fe8c1ceac35cca33fdf56b516d42a77430fc70eb7b4fcbbc9b7b11cff03829c12159aa372702ae60d1e456261d0cab1be3a8df8d4e489692e64a8e71d40f63f06e5cefe6b29591a4202524b7fe4a09eed695d1addf2b9f1b6ca9bac5601c53c69d5f3170fbab651cf3cb0c5ceb2d779bab4fe32182d1adcd4486a3fa29c9705419174394eab4fd2b8cdb97036fc1ecaacf39fb3b27656a869a61caee7d46d2da95f337ed6a277bd24abef133d99f8beee58cb476f9dfd07098d272ca8eaca9dd239050d4eeee7ea3754e7b7fa2777b777b2b7d21d5a10120f51491c86ad76ccd09d3321b1c8d4241691317b70c6d20024a9966bbd07f8ad4c474297577173d9fa47881f9ac76d1b96200b7eb036ed2b6cbba0ca65ff4290c9d350ad32e9aa295683673d90bac66469bd5d066b4198d36ab99d5cc68339a8ceb6160fec2d06a606e6078369bcd5a7c6cc83264e7f3f7fc55c623999999999979b2ed813243eac26ad7a7aeb0fd258b47ab83ade1635691b44e8137d87efaded17a2cbdefca63eafc88e2e387ce8f283f4451147fe8f888e243e787ce8f1e3735392624ed748c67f60809128575bd66da7e40f99836205050413a3675a288629428ac8ef5775bc0cc689496e3ddbc9b9c8e9673cbc9a1dddc6e683995d2726a70403a36020945b7eebe312e873ea5dd6e29236e9fd7829a196dc665a429886a15cff33c1b35654df7f77593e5d7dd9f47a750c18de0f368ad393cca39797493e6dcf0a04e69ed6f26c26c465f6936e7ac5d25598a13e7a06c8f0d5673d3dd7cd49fc3a3bbe14179748c3a75faf1b8c96997f3513f0fda7dc3a369de23d0128e829820a463e5fc16a15dfef50a5bd2268e95dcd4a3a949841b3e3a3aea74982c2ed81b9712b9646ae697f36bda75f37d7f81f9a477353735371da3a43b75ea9ea2b49abfa133356b6ee647ce3c12e645c6d3cb65a61c679a5fc32fffaf660a2335e0652758e0e09635f5044efb65baeebd23bbebba23bd81dcf932b3eebcfa24612e27ebba354bb4568aa2d3fd32a588f40409ae93a595b94050982ad3947a4f2f407f56f3f3fd65fc674dc72659fef75366b684cc4ca6c6e727d3351ee9d269978c8cf72d398aeb6f8149d3ef4f9dac4e8c963e42c7e6cbc8f0c7ba8c2425caa53249979d64c1736b17a09d1ce81a2dc6e1bca3de5a6b0dc6e1efed53641cfe3b58bcee2f3a0b90ed7ff196414734256a4e41a2c2c88e7e149840c204cf5d22ea7a70196909196e7f0b484c3b2c476cdfd22f233501754b8e7225b774a33bcb262a5a62242694eefc5632dbe84e214d3850fbdcc93f6dada4a889da355f4cc196df6552288524fc946734b1af05cccb2b9c7eeeb124e192c0bafd9cbb6c935c3657e0d7fc179466583a72e79773c72949ae053c775659d872eedc1d135c36e74e94093e6d73a7896e08c5044a4f38ecf438f1f2820b26908aa0a418d5e677d1744f872fbcf8609845bcbc2c81420c2d18b0b8a588b454c50c484b50dc521497706109892496841045d2239778788105822513082dd520c5520b466c8b1643d855143b2801a5071b5e465262081d909430baa5d82129a1025114919408125f7479242562409a41955b8a517850ece744b7c4b28ac8c1ae80f096d815d11696c665a419d4704bb10696e532d20c56b825dfc4234944dd52e4db4d0e0ff15d6015a21298441629f4d4703be2061aba28b170c2829d235c3ca1c482db8c147bb40c6360b2840a4b5288a1082502380205a69d232e56c0841530b841799099d548414ae276615c464a02caf5cb484928cd10129a95da8e1c3972e4c89139e791eaee3738a54568115a69115aa476b4082dd2cd398b78dfe7b9d28c1b6af5a0d4fcdaf38e5666ca3c83f6478980fcf29eba200d7dfcae77c10ef4c02ff491de4ead58a5ba201892ac7ef57f2c96a74227e9cfc995be928f4f4301524aa7535a038a524aa98dfab556a7947e33a594524a29e50a5ca63fb498a90d5b838a2dcc5d57697821e83a168f35afdf3c05df0c771faa5d270354c508c1900259bb7ae601c64ab7625a9f87c49595eee767c5839d2b7a7dc65088430c193358337376b485460c1ca8bb534a29154560a7979f8d1addb2e1d25ae1f03bbb904e5e071f00bcee4b79d4a3a4ad91c567e37635a52aa202933a6800d48a85bbf3dce04fcc0e1a00ed3298cb58331bd7495b438bcfc6f500ed20b4df17c2be8eb52e77ac83cb0f52bf595ba5750205b5a78252432920da4e69755a6b4781bafe521d985aadac5662c898c162a1d14a4949b85eea073bb8755117fd8f622b69a226223da0816468e885a53ed50e0a214e4c53bbbaeb7e35c57635c6290647938bb2a58dcdc6a763dda7ba7e9a1ccfd45026cc11fa38e3d651e8c6a7462197b731d67c6a3f42346a404d866a3c57e946b94b97e9e2bc1a086e533f15118ac953b5b1d9fcd818b1097a40d75adec676a33ad6b9cce5fb2f9596cd9c5c74e2263b64c94d7a44226fa49d8210f0d30489cbe6c7652024f92b06c252bb9a632034c1ae4efa982b38460a7ef583d004c762a060572f752c06d7ae2b7886e5fb63ac88a9826758df1ff3144305cfc4f8fe98293e660acfd8ef8f718a9122c96520e062a288b101cfc434c540112325e6099e517d7f0c8e67bea499009e87aae181d5c42f63b8b8fd31b9d6217efd72947c84c3ed0701e7b2a3a1db449492e0f6db1a6e3f8c22b73f8691db2fe3c8ed67d970fb67dc70fb59806ebb8f3733acc61b1ac69b111cfda46e60601cfdaaf12686f1261c6f88b058211e58cdc6cf18857866351b2f23868dfa76e480e580782b2964832cf948073e12bafd3738708e7eb0fe75196f7e46266f7c664e2e8b19391ddd7ea727b6283c31a618027e62701d1923e2c11ec5e06ad178e4532908387f7970836da75bfe2c0a01361db9514794bf7ae6a4c34907910e253a8a461d465fe878bafd3aa6b0db5ca6e34847d3338951ac8e27b79b26374493a3895272190d951d1aab0588d72375dcde053138954d8f8d0a3c03a5888b02d38090340b606303a1a941c0b58c34508df39134391b2c4deef6dbd86c766c78688a689c688c5a7699989a9a9e9ea06c6c4eb79f65b50096bb62f9b1f123ae76b4aa7aff8129cbf23354bd31541949224191a4dbdfb3b05c90bc4a48d8d2dad8587c58ab1d7d53d5fac4e040c05519ee6428381f5308087dfcc0195ac3ab101cc08b07e458690b8d1aef0963c56585c6c83586c2ea1196709dd47ba410ef53a4edde63a46055a3f7a95147f5de4a27f51d29e49b258a525491a0aa3570869dd77336c758293be0dd454e988398dd467d44d21bffd0a17fbbd4f7f3804d174ce59143b32eba152bf9940ce94f9b18a48412829890ea1f61f5e08738135cf62bffeff60a8c21d710e3185783a947087ec9ccccccdc1cc4042140425cc8b5efb35614ff5d2ed8dfd92dbf0417168e656da9f6bc003d8f6cd11f52f3dedf049775648b923d765e3e6dfef2ffefbd074921299def53a4904fd579b4235bf5551ed9aa642b02dea75ea7f33eb2451d06cbc2c47ede9ec5f7fc52c220f22f553db6dd077cbf1a3ed81832b75ffcb5e60fb97303fcdeca2872b85281a91aa7acbd51aecf9fea90bf94ea9354e30f1f3dfc8a2e9bd994df3f622bc3902c3b1218389d76d1f79a3a2189ce8a093ebc9fc633a9bbf2cd37526cd787b34e25e76a3461f9726d856cf5587d4b2774b54a05820c5e2048bddfcf9e177cd57fe3bc55359643ae178e347ff56a8e1ba0b7147b68e104064ed51e08d8ceaf7bba12aa523fe75c11dd95fa707aa89a284e0fb1bb73cc4f8db3ae1de953442dfe7927cf5c5a8baa2ab6b21459fef29b43348c18cc1c3eb37da629b165332867b3d95098dd303db933f531337339e472131dbfc2ec04e78d3172cd8e5c8351a32a27592c37cb5f869cfb46e807327bfa00e2b24ab67a7cdfb742a8df3f82cb3eb24bf0d70eeed549746aadf53d52c847baea975f255bf4b96629497339f943a75dfd2c4cec740eefc82c4796bfff07cb133b3f8a260ca0cc2c6351964363190cc6b27f96597fcdf974a42cb75e617e1fdcb8208b0ba95b7d6917c8dd75b3ab2e2b9c6fd77477afd5ab5777f9e8f2b2fac0e54e0f99b89125e239245982b05c4257e62f99d9ab3bb333333b1f390f7db79ffd3e1ce380d82ffcbed07e76f64c1f374229a51488cb3e1f589ad8b980a9c4cc4c839120c217033b6f977f164da2f99ae91e6792fb97432ec7be99bfdce576fecae50bd6f0c19673050c238a6764aeff24e2194a0493eb3f5ca717ecd09b9ed734283911e4f5f439e7a4369c56dffb0a6703dd7faf72a2964d19886c365bd1516c5e46a241d3bc4171cb6974ddcb315d71cb99c414e59633ca759f3855ca9b9ee779ded39a8fee244bd1e98737021945b03bb69221bd59a58f09f07664d44680fa278a0a549425252225487aa6cfe4717a4abab10be3602702e497b2739627b6f4a46e280a4b7f7ee949b41b621cde51efef4433efdd6f37b0dddd007d62dbba1329e19916b0cc89f0180071bd744f3a21f5dd9ff0bdf73ade7f7f42f7a93fc1fbeffbcbaf555f27f5dd771f428a64d7e9defb8f9cf9cbdf23857410449676a875a4180a614b8ef2c338a02c256560662f80e96ede5508a4bc72b3fa2ea77f35caf42b8671f897de350017007602f8ab3f41f5e1f797b079c2eac13f217cd5f7b756afd34a910f007ff52180bf2243507d48e6c0b524f3bdf12687c708f3977b23df09f30203e285951c07a03f150148aa8a2aa47aeb0e631cce309ef9dedf002c7b00ec5a1a37b0f396b01a3e8cc0331edf7e78866fd7bfa3accb5a297f97a7c85604540fbe4ef8ab4fa5481dd583a410156987d45224c941aacbd5bd43f99723c30acbe3236f82b04d9610bebbca0ffcd7ecc1b50e82063738529be44bbb3a5248f5d62a79e45bd13b2d6c2543162776c2c8a992780eb0b723c7ee5614acdc4939d6aa3fe45672bac0d47f1c728c65f6e73fcb567e3e8c65e1cfa7b14ca534c7f79e565621c8feaa179cab4a8afedab19464852c37cf21494729a594524a29a5b4c3b5bb3bf54a19e9891786024d767a6e7f5da50e9b50fcdc86c2766754d1a96048099e8c7698d2025194a30334fd2054c50c2af4100df102a7fa001a7a8e927cc027862d26d0a409a0238692a801926a853eea6ff24515a4a01b102ac8a4e0a805229c13280c81448fa418917a2dd9907404952a4d8ea0400c54dcd00293140071824943576827de7d9f1ac58f1462717a7cb3fba9e4c5910f132740486698c28115a8788208173d7491a4071580d409cbf5c2b2a1d8527eb8fd5fc80a037019498a0c17c66524293ba1119ea2c30c434db840872a82b849aed399c2053454b104d7851128dbdd5dbd800d113222caa7862a4dfcc8e0a34c9c481534c5004a0e8ed8c289babac245dfdd6f60401114a2a0c10b4e8272f0db131058e148062d28410a422688b183865ba7b6a0e236e36148042f24fd400912e2bb9130c509262790c1920d2df8acf8ae19ec26839d4a31e85bfa2eb7150ace0caab0804a16465d88200823f80915348c8005513091a087d40ef67e1705aee4c8f3d9fd82c95eec44f181265b0e5ea8b0040821ea1d8264044e9a88c2852ea44851a394525a29a50e3493f0e19be946faf1430d58dc0006447cf1841a25517161845117511c61220317b0135334a9e2071f5e106282bac0049eca66b3d9946af46d8d1a96a84062a74705163e2b3ef3109bcd664ba2b18c6b4aed3b300c56404587156c71240625c8a187179ed06087a2179450a74b9e7821bcf4ad03b86062450f476e5dc02065852daddf7eaeb94f9916708288273a283d3949a2568a4c7fb87409bdf47378200d415142053c465ea8d177cd4c241862cf25b24b715097d2cfe141bbfb6802a1e2a5f4739c7edf31d9eca62e7d4bdf4529a554453f954aa522cbd4275e4a29ab65b336435ff8cf69ad34f5cd148d416b4c4bbf2f1035d89286942c6b6883961f1c9ba4c64e6b017d5d18c774b2b583962da79524211c72875cd2064eb258ac9015d28e56fa6dc14cbb3fa75dd77564f7aa7ad947bfbcb250d00fc3d0a692d832bc338466b51e84929f0add32a6b0259db556dadae17e49a86923bc93d59fa47ffb535b3fef661c93741b9747161f678c326ec35099d2d9f3dbd54e36e51a4ed3ff275b49fa432e6d209e9c42016502f24bd9b75bbfbb74e9f2ecff6a9deafd4350bd93279cd0648baaba104ef8c8165dbdea57affa14c9b513c0fffe84f03fff42ffd490e82f7fd51c58b0fffb59d43eedd340f527516d9f5bbfc71cf2579d45feaaf57526cdbc35a7a65c23cc5f95fe1c6ad90da26934933a1685e9d63a9fdc5abd2e56d6dcd4f0f6d787b9ccfbe61501f9a5f46cb7fb3ac6bafc5c50ea51ef69a744f359cec835b41e1d4b8d15aa5ddd9d4f96caedbe452bd5f1ef7e2775542ad2f2b38e4a359f76ac4a562712c996d5a9f68c67baeb64cb7bf055df559fdbe19af4ef0f0124d9fdcb996ab463cd81edcfe994943adceda6ccc60ab5ee695da774bbaf51ad560f151982ea8554dd572a5d5275aab96e76df5fddfbf8b5be6fd632171b8d47c7eaed5e049c4f37bb5df755a78e951fd5b152e676df7dadb98ed1ef86b865d815ddb252e9efdee532f76c2aa66f48ed7bfadc63a7f3287dbac4332c9756a2fb5d5e51a24beb1e40c6d60e5f27965ff7de0ba93e453d35d24621490aa004932ab58ef4d1a37e1d7bb4abbb6412543d3ad6a363ad1db3fbfa37f951148033582c161a2c2d2d2dac19325a5a62d8969616182d2bab9696961515162038e79c733a3333337f524090794e9e73c89c25b0b8002547859d0fced9dd2b1cfba0bbaf7056883ea097d6da759df751efa3df4741ae3f5f2ae5b3c2091f5485a20a0c713a300cc355c832855dadacac7060c0e0072d8eb530eccacac68861adb576ded404c1715e90822b218c9506402170556e106680789964a796d6f3fb8f03b9ac467fc1aee6771838d651403dec3e6deba796b9cd5fbdd44c1d5b81ede443b04abf84b9a967e16b34aa92cfd817433054855f6ad575a93064161a3046062b15c5cb48dc40b8390f1235653dbeaa15cb9496154eea57561f5018a10cd07d7aae97ea541f533735ad48958ded1b6f6c8c63e79655c99d438c637e6aac487640c10d172b63e56283004b314baf24022cbd922ecb574721179ccd8fbf80fcd536361b9ea6278c98ee8dd9d3cca95d36d89226479383a289ea58b7f2741c317953f8dc1f4d0ed4717bb979e3ebc63898c28682fac80e886af772bb5d764083064f1627eb238c6b638c39f9ab7143ed8a218911c594c48abed8133123f759c5e06270fe8a718ac9c540c550a1c915a5624431254c31381b1bd936361b9bef5c1b5b1983bb4a3680aa8f0d20718513bef838e07f2d4feb8e68cac7176471c1fab84a8d42375c6e2cdd12013f08e0f19939cd72516214e338d291a4e3890e252a5d4481ba4d93739a9cd3e46a7e6ed344394dce5f35b61a9e9a9e1a221fa9e3e67d550a0e5485aa31b614038a05c5866248624cb7634b4489c1c5e0629c629e62a0624436c61b1b1ba0bf11bc4cb8261f9b211e7ff5b7f81809fac8abf448d89289b8d988b8bdbcac7054ef23a75254c01867c7e282ed7a6d8c37cae6af9b2b32ba1d25b54b1c79be46c32f164fa2581b9bcd8e4d4f0c2e06d731afa9634e1d53aad978e6fbfe1ba5999aa96985b4b1c5e06c6c3136b6189c8d2d4604f2f0cc9e664ef5fb4ec57a90e7ab0108c00aa7fe8a086c49934b8d42ada4275196dac5c44ca8d8a07e7c9a6c6c363c3eed6a2246845a3a1b4803d5b2968e752d3e7a6a146abd38dad8fcd5ad1e221ddc90b4e189c19615d78273591deadcf5b13b1f27f3bc759c77aec6b9322f8c71ba50eb53a350cbdb71328ed912ff8afef291639afad531b825a9ef2a49bffad8ba6001421f6ddc3a0a899f12491aa3f5978f356e1d855a9e8ee4e7afff0e3a0843803bf53b074755b1fd93f642e637ceefebeb7cb57e8a14420af9ba9deca55ec2235ba9b906dac0961397c4338e9b4d48d8f2bbfe4c6ee063fe89842d39ea32d949edea6fd005b6eca4a48e3d618aeb607091c1011299d9e72e4a9b8895a2a0dae6b59c6c3da03efd10baffea5773605b3aded3ef282038c7794e8429b3b142addf473beef63b4f8f568fef21c0b58f0c41e82381f8ab27cdbcfdbed4516e6ba786622b8f76b968b392957ae8e4f0f0e9ee4800b20c05fa31ba2505fa11c24892d25ecf7103dc9163b0ac86e838738cc1e4d2b147fddaed3c9e63b2740535aed121207f8155b896bb4014a85d211621bdd500eed40a84850d9bb0fdfdb40f959da637108180d7e692bfb0b014e8e6faa2dcd25572156a04dfe69c2328d9f2fbbeefef6c1cf5deb23e9716b6fcfc23cb2094bc4981dff0f88f8a3704aff2f30ba05bfefda811422e397cc337d8527c115d6c207ae1744bd166728e19ac5db92ac472114b9885c95028382f90745151d4a5451d2b690d8c60da2573bb765c9fbb3aa3b72e6c69c5276c498bbec8650d03bffa9b04f4d634a977601c13e7af166cf9851427441035bae50c2a1bc888202433c8c505c6a55de1778c9defec1cfd2d31d82b6cd9402575d9fc197209ba2e35786cc962894d44856a605df0dcb2c6085e35d9a974674d8efe10c9fad7bceb6be6a5ddf09d4eca386c0afb2d49d85274c1551e0a15a5449f85af7dcf2fed63393dcff33ccfdd9bb32369184448e768ca359836b7813bdeb74fe7f8e91cfde217b6b4220d6cd93eee4e3e29e9b67d6ed074cbf6a144aad8d20ab1b58b57b49e38779a38dc747262b29cb91c59cea85bdaabe4b58e744a8412a13f21a55bb8dc808885afd5a761e7e0c116826e611edd901a6159bcb065384327db07de155d8ed8f92e2a36918b9e5b4edbe4e91cfd2d1f2c0db236d872da40275b4e9bdbba0670d6f8a59cb9db6d02eb4e66e6394960d1dc2b194488c8644b86bafd41e63394dd62e8960c359b58cc5dd775d42b4ec8d37647138404115780fafe2f9c391ac059eb179bb0e5e4a13279e62d7b2777773a5636352204816d1bb7e6f387e08332bae5e7cfc2d7e8c36058e1e739fa6b08717415901bea1713d922f1f6492ea57e2920b7d42fbee5cc835eb4584a329f85af75cf2f252c477f4a0b0bf3425d25b07f5352d892de86759179b370623962795e46ca62e7d20057739fafd431558c1d342c81e08e9f270eceb0897297ef8e1994748c937458ba7cfd9f74acb5e3e7c9e59796e2ee1da3b4eb9f17bf834e01ecaffdce38b8d2eeeeeef93a362d6b461620ed498f68b0bbbb9d64c6c154c47703504610fd2c836abb6de31922ba0292298e4a6f0000005db52de98ef5b3a5fdde7d9becb9faa09b13078673ce39e79c73cec9d3d68ca3560a8026e2990e6e7f000675eed977489d730bb074c568d8d56f6333d1c5ef68f779dd4769f7341c135d16835f5ed751dafdecba2a4646401adf8eb03d7b76d3ee20dd9d21d4ddd3dbf6575f7e2b5fda0b63da219757df07761e3e4896e2f5a779f9f50dc97276e87a15d98de5049a3fd367f24451c9758ceb9438a983cd58479662d775fc8daa510342eee7a951037e3f578743cbbef54f2e690245b192376ac084ebddd2031af0ae47964378b6afd0d92b9cdacd55f7dc31fda210b11344c4cecf652276782effea32112a78715597895021575a0850a1f2f4c4c4f4e4899111235181a4db0720442611a8db36b7df47ff8fe6a6cfc78ed79db61e6c397b88f00c9d8167f8ce9e8e3577dc7593a763df67ad287e4bdf12fdbe02dc7903268383697c7001fae7af5896fa3c24d6031febd3aeb1920bc8e1050ca13d1da35f790cb994e4f15bf45738b36193c9f2bbddcf1e7d8523d3ae8edbebfc397af001db95f4e9760f76d83d48e9a4dcb4233dd8b1423c5f4df5e5a792e958bffa3ffb7dcb9b4b1db9e41a7de23084164886d062e8692cd2f344024a102931a4c40d93865cd0a4c1ee5cfe17d8885c2683b4eb89294724b7b3040ad362d17e0515151454b544eda8b7c4c377c5d315b822a5274b2648895fcf17d6a00291806c8b16a117c4154bec8a2e0d892b565c4144af00fac221605cd1b364adf86204c9700aade894603123e534a30a8b29b4e2c9678591154a6a44c16245901536582b789ee6e7040d26a42496d0a56715d1925485d1ada2870bc438c496f4aa90d82a8c1011a930aaa287a8068d1a6cb442fa4485e797d4858502a427243726e4e208c4c4568ad68f3b1793accbbf88efb28402d06341fc6e0f5441900b3ee4406486154850976c5871164d5220ea8288a163269a89d205f3260b5410115fa4020551e9c3a10857e1c863a189e9878a0c80804195145240e105149060f242d0112b2598b8418b80cd6a42563885931580fc32e7b441cd0953bb5667a15392b87a775d85c99030ed6ab9f5142237bac64af0f2331197bba98873f26119eed22eb8fc2ca5cb425cfe0a877ed7d5b84d279e991445b764a51b389e13ecbe9bddcfdab57ee7010e9604d6ad6439e47a2b9c94bb8b257a6b60defeb089af60f9f94e289791a458ba349409bda4cb4852105d7e18cf2e3fa5e110b6eff75d952feec7b051c661dac54f6b5515fb912c16215bd2d049ca38dc457e5dabeb2a0d1b5f7e1f3d97437a0b533e5124dd28826ef9d2556775ac7c7949c064b5cbc722f66b58e6b5e03443d4d10f4b94d4f86f58f643d4604985254c0c31d59873ada8d95e2b7d2720bff8c7629f4c7f66c1440b1aa8931ccb875953dba7a094a8048dd84531656a68000040006315002020100a8644a2b158248b1271301f14800f72a0426e5e190aa44190e3280a62c6186308000000008608ccd0cc8c0000004160518bfff3604a1ea1244f5da6d377257cf10079f11a5438b80d317129d8a7d92f2cbf20aeebcf3003e9fd9ec253d0d08735344599f389b135ff021a5dbb4beb69f345b4d757e709fd91c0c42dc9d17f8a782ec7ee195ed3552426cbaeb97cb1b8e308ab930d1f4146f19157a8da88c3b94179e70f83903be22852834f71700f306deb5173cc3ff0712e63be591a746e16249fedcd96610b9414a1a7c2b5028a1989f9c052caa02bfdb638f30ca80db416012fdc0b5026f15e0998a308e9a55e8ce54059f37d4c87c6a8a8cbe74c488e4b12f32373ffa3e7736b976c02c9c6080eb3c74b770545bbcd0167e38724c1a11ac4e47cc3a6566f2a5c2fb64b7df2819d97dac91c6d1968d952e46c2353aad454cbaacb7370a8b7736c05cb7ae08587aa99e67461962f554b85d4eba654087f6087d9eef8d19da35e01054706ee6dddaf4d712473657d7119155af2b18177006108b6d60824436ec20884f10cdf8a5aa9dcb5539013bc63bfc7ceb3f110de7bc83d3f72ab3955ea66823bbf511796960ef3ae91e460fd012fae6a8874c20823545b067775769d44d25164d627ac4418bb6f67fa204cf56ab3105b7aace8912d3d3c1f54e4eeb242017e58c81f882841d18574b8edde6df49d485c93013d108d5c0a1ed230c705d26193c84f5515b34a0f7ce273f8fb698234e7bff8d78a09d8b501ce098985b29a26280855516312a80896485f8f16ea716ff5ce247e374f014400cbc1521f4f00a6976c872b896df063066d11fe939a9977bd0103d3bc137f8fa809d1cf33b923764728daf4da2d2c463d908e18d9bfcd061f3b3feeedd936f42462caf73da9c2b7bcf8a055592e185d4f441ba451ede36bdccaa57ae43239103ec5230d37e80e6e5bf739f6c27d8baec04341e42dd67e0050f626eaf23b183f27f24735865d1def39d014b52db93eb8ec20eee8091d6e45600dbd89202f0190d571d8b3102c667fe41d4f4a7770418b0ac32c10d0233fd648989203f845c67c7d63fe3ea9678ee20cd71892b71611595890b5ed29f95f4e5b096a00b3465aaea76169677c16c093959df404888d9db1662f3db21924af72298c4b1f2b1efaf5973dcdcc72c88cb00d0114f1fffbce3909c061603ed2732cbe3f8ac17e914c1d8f99185145d68a8a976af592cd5dd5b8f893e655613397a775ce94801b495eb1b9838e965177f68c90d6cef9ae6389887313517b0f62b493f6a586982198f81251377271f1f21e93a28f948df1a0ca13631bb3cab8841ab49304b128b7ca793deb7500239bb99130223a394dc569f534d538ffca9c3fa0e3d8e72402d6caacd00e21906bba2fa61ce74b5059f74b516f54496f2080818cd4f343351b1b825de4652ab6a8b43ff6ee93de26485e3b92462f62d8ff17fce7414ff2ce6c31924f70e5aad58bb689882f85ad69910159dc02bfb7fc13d8f218a85499b0c9a6364bbe43bc45f61a8d13c18dfc0274706d6605635770e81fccb37b4a34d4fb479d748faa8f4c304b5875466d07774a2d4a9d7945d6bda05f4a4e1779f10e514b1e6aaba3af9db1f53bebe64ef6bc9f7708f77f41924c73821b299f4fc60c92066efeff7971c1a69676ddebb9723a8747f216175c0f346c2d7685c8e60fdb10f3f482f89221470324c21f6a0b801ca8924d1c8a0af16bea1be08a990b012ed74bcd09e62825646fbc72fc7cb7c9b0888e98068a20e79c094386152f4c942a052e4bed4c26330e2812514ead556549ae081ba9b272799c29dbf06731f518372a66c928cafd68a573db687927e9fde264af4e8a8112ceb61324f728d95ce6835f3b430a6afc1779b1e609b05c54efc5dc62d75dcae9a9883aeb1353e2bad99453f66ac0f4fad529e62b6a1a192be8a04224ddc93bba880492081a7182f4934ec1a87ce6f28c7def22bf394052a82455a1ec4a52581dab24f130f92db1d18d0358eae6f7e13b6acc43e4358d90fff5bf9ec055260130041e9a3013656b4f719039fe2a14b1c4abeeca1fed2e53279a03d031062904e59bee97b4b059f1a2956db3b7f2b3b26da40b21c832a25b683ff0d9c57099a38066c2b6663a7499369d949e288534fc27bde08957b9f06f53a565a8377be7537b84d35f747e73db7bcf2e841286dc8f237f7fc6e810a0efc334edaabb1080a0b0cfd660a181b26f6c9c41f582cb2f1ec5d25dfdb3198987db61bb428f08416b93c06ab9a94a952ea8419ba3617e7b7e3de787d1dd4e2e8c440d250f98b752a100d695ecb6857ccf6bc1e97be2405b0f617961a4f07a59829708cf1b588ba6001d9f45ef453f6005098c3c3c5246f50eaa8fdb12280557c12a2ac8d5480f366def9137769dd450f57707f3544e83367b23f66e7ef90211f925e022694107fb650806db0d2ff8289596d1435a5d6ba6d63120886ac2a1926f4ab7c361b1e2deb1ef4235c41d014d18cb539a693ff689e13085d48491a75f898eee9e09443a82559b3665acba6031483bd06762efc8bfc98bc246aadeec58d6935eae9afc5baeb229bcd230a5ca6eb902c669e8e4fa6033948fab743aa031b0db1a1cd2fbfd09307d79dc0c473f2365ff7891eee08c29b7c9df2b63b4cc8c4e85bf026965a39d98060881e288661dcb406e13bda416798e1875cd09065c095d5175b578b561be07a4bc11c2e3c3a91bef00d634f52e5ac0801c97ac59ff1857c174d03d8ae316f93d831a2faf4ffd9b79577852aafec723c013ef65e2fc59dacc98a99b38d2a5ae2e07024556c06e069c1290716d7bd25c6ec61bc602b0298f04bae8ea7dfeba0d38137f543123cf629513883263b69c3ff68064f45c1c7f702c3069726cc84cb70fe877acc71c7b024fa01c1284af5fede2fe634d00af37515949b8ff26423623b1a3c4126bb4ebf59582e401c122e2980d3bd4c61af6c32d10037ddbc863e313a6fee887a755b6f9fdbfa2aea4ecce9894592226728feadcc95ffe069fd845a06a2c4146b116f580ac642dc068d9c6efa10fa801bf7f8ec39e752e8b50c33dc2a03b2691b85bac13eca32693214eac2e8af8db619c85b6da28b2d1c359672720f0842b31ebac639a1725f94951e2663da29dd36d81c39617206b83b85dd54dafe100fbaeb484162e8ac45c6b1633d568c5133079570891fad2169e12bcdc54dae56e45e4d52b425b21991d4dbb18813754a3de65a7a7ca2fcec8a926d2fea927c048ce673e98a88014a4468062fe489b8c13c8918aa5e1e648514e1af900f383d725c3a3df04fb0dd1dc15eb0c92e4b532081c2a597ac00460fb2394f157ddf4347f1b410c6e63b437542eb51962ad51afa6dfcd6654827e59b5712ade3ab27197134e5d11206b6ec2cca1743b123535b6bd2ac8cb14e42e7e5c9136993fc5b109157912279b1810beaf16edd6915acbe4f0df3cde776bc2aee62cf7841f96707133790a27e5ad6939cda025ff3c64ee81265a32377f156363880493bf2c4d68d23ffd88900fc9f3d7b96cb7721a61c76dfcf9e12772e0d3f5c6a70610d2d6b58179b98b29af61dcf9239c4d3ffd7d7edd9ee62733d9bfd091fc4a02b42e2fa191129450c33245fa7ed735fef977fca0eceb78b45f0cbbb96fd2d0e4277da5e91c2c4c98d9818e9ea59cb1dc4703860d055ec2d14f0bfe136c8f11684a47aed2157ab06d4cc6f8fc127696f4afcfae5f7809bc562be21c9f1e62733abff1b377d3774c8d49f57e95626486f87102054cf47b2a61f59c790431f8520cd3648af7ecb051c37be27ae5e9a5f86372d70acf636aa7f4bcd4a98100c8272226bc405ac7859178b67aba786f9e175c710c7159b6b4d1e9f6f78c3d5f20adcb354e497a04110bda1f2d47d1eb8c2b8d3cc43ee4b5ab3f0211a2df5dd2d03004d10a90ca1c80ddbb5862aee10d339eb875f2cd230ef061f39edf39dfa50cc6204c75299013ad9dfd18f76958de04f877b6b39fe045313af0a57effc0952c790ffd175c010bc2e59c5f3edea85b940b2c7239715ac24fc4ed9f1cfa05da5e950905ebed2f3b1983405e27b31d74b20e0823f8b8ab165cc0fbad8fd4b713fb39c08e43124f6073163cdf5c91bd331178c74194bb18b1ed7c4d05e3212a9162bf6e431b82970c216850549bc22387710703e77044ee262631e258b40c265e73c4fdca3047c2d8f5fb241f85bd125fc2bfc77a423f6d82068a682b7ad3c894b519a8c3e41434d04184a28a34594df42997a0015d4183869bd98edc797b247616a379c4bb75ece80d47eda233fa638cddd5803ce542401503279619711f25dc41a2d8fdc71cb410077316900ad1cdf11c53db16e6e313f1591ee1f147eee1a1ec13d089fc690e0a319988839a58a6bd16a6d67c46a9d66b7e1bd7fb3f31df66000ed5999381d9bae3ef939fb70843e71b507e2a24ae951c99deb969166f3c9d7458ebfb764e9a86517c2ff8b5f45f837eaa749614d5f66ee2f1203eba02c0680103b48dc1641ce1ef6a640ed0bfe2f42f33f4366eff81ec6b93bc7397905ea3bab913d43969d99262f7b88904a1f544b93c6235ec4b6516d544804eeac6b3d6e9c00e30735d460e00394d21b09f8c5cd5167c5a8be9176b7322680dea9b6478324d50cc4f78d1cd69e187c7d8325ee35a1b43bd62470e75832ef6db3262411cf505b5de3c6c1950c75f7108387a84aa5026b50622d645be3cb33d6cc1718318dc6f9b75f0df878fb1f1e763aee2d12b79038b9537dd0925aa41140b116491bcdeb9db9038edfdba3952c80f3ca367780b58cf9ba4047f5c3f1c69e04e8db74b941a2318424e1f1b5fffdd7f103d9ec36cf549b2711caa6ba2d306cebf56ad5b8b4b4509159bbafca263ebfbf3d7ff7557e7435e6a6c36315f680173ff85054939ec043711944fe2890d792af6c1fda4ba9d4ad0298cac28784ff3d848c50ddf05fae8bb122066bb60fdea8c608f83cb4fe1a4ca21bd20991625ca408a5fb1e8e702a52a6094a96972cd161cbc8eee5cbbd10875aabb1d7a5284c9ef011f026e487cfd627e8268e6df2569802a41107592b0e18da369b65f45ab01a1c2584a319fd10c9d64a95ff6179e4798488f1df00297b43a6f846ff96aaea3a7bf4f507cacaaba73fab0f5b7527788cf6e35c93cef3af6da8f0803e6add85cf55f2cd6ca9d6605ba3bfb6519c1666b4d569a75c2cf3c59482666a83c127e5ac0b30b67cbe40c8d71deaa17d394b1d3bf493081a3962e0e218d9a5001c3e4244ab737f6efc9776c406b65ad05caeaa9c8f54b1ebc89201fad5f5af62293c0a2e57bcec31d86af25c334f297206cdc02dd9354456fc95bb289bd4f2615aa0299fac58b7588ea8e7e377bdc1053103fb575fbddc9d464969d2fe00f11a434b792d134fe704e817754740d04c4838bc41a57cf81192ce283abe1cad2a0e9adbb4ffb8cf70e8349b3a25ae4ded993805070fbb9d7f3c615264f43c6a15ddf419346ef4c79375d16e9c68de8d28c219d26fab28c7e9bbb4cbf834f4b4674e134ae270d772bf67accd858b075575c5e3182c3ee984addcbe7dcf04f0ad95408478cc0a184a858b52a64ab88a91257d622c83f4b92f04fa460f77ac9ef8cb990cb188056e826e78816e120207140ca789bd267abff4291a659a9768f5a0f6c8f0f942dfeeac1636e67a7bd43387c54a1a269c13d61035c88729c912e6103c41da5e1d3872e423340812b15155f884e5fd3604e675670f90d038d9d0be45aaab4a59db54ee65279839311e5bbb6c7d354ff26230b9cff26f87cac639dc661e955f94b8c7694b9e6b8dbffc65a8972b27edb60946c9f63b60974c47c61b4ff30d0fc7bbec802c5b3d071996205060d00e9bda20742e0e453ec11423f7ae4da71c205c930939edc41aa6d0aba981fe18453608d5fce806ef3d107863ef9c27f3d8bb00ef64885a31643edead7527b682ea06261625cc1098c91fe51a0f3018926bd97e1ee84160efb99cb73b06d2f15be5d80625648874c3bd8ce565efe1918e0a621ae04bc6708f3520065797ea11fa10051f8ac2406fb4d8663c187a4f3277b0e11b082a7c8b66079e1697a5d8a5e96c4c91e88f3b1a9e66c9eeae4f39acd194e499a44c53800f0e0a7d3a400ea5c815d1a934fb8122005f1f232230fc0126b84de37623ade35f29cc6969dfd64dadf6d0641267761b05da3717a4f008df0a9b1001f72f20c800c02bbe66d717cabfc1f80e0e03d8029a3ec8c219264b09ea1fb12d7a898796409b0221dce14926bc3950167429663c3c3c1d599d95e3333f1d28263f9d2bcfa650a555ae91c904b2efb1d31b39143e7eac2f13023c83267d67e7ae7028aa5f07f65d7805659bfe59f268446329d2bfc2b1095bc511eb52319130a15dd92785ecc394a865974f7ff38d39a7d5da0b89805eabd1c3b5581a4c7a5c8b37683c394bf93b5846c16f3b757d5e0e6aacc9a42cb69ec0235e07fac60ca06b852b21ad81cb5e5ac42afdf04b8844c1317c9b9c197b67c4d80467c27dfb564e8b63713be4098d0021ee3e65b1b1d3e10a6aa53b19cad5547890634caf1fd16e6295e092f83f1a782473cbad7229b076b985015aa82edc83a5d42afff25d0c6a6ee5a92d8106e1952e9321d2e434e05775f79bfd85234227f2199cc40975bdd8f04e4772fd5aaf8e21fb0858d73a1433c41b3293cbca3d1f7109a66995f8958af02deab0caaa5f2098e22259197e9cfb9bc22cc6bde94821c7bc6c071934f0a0133740fa2c246ae2614673b618a740c4be8c3deaea167d567a6e8d1ef8bf15a6618b40e9e94f59b2706f32f02b711eb7cdfc03b0849a5a5c15f80023a6368a96bb890d32e5b4310c7827d4eb98c64fe3e554e2d16fee1c6ffa0819057df9612b1bb0e007a7dcababd61fb74cae817c72eae97646bcbf8e00ee54655597dab5cb0a80ac9928a1e155b28c32c3d9d19080b314df96f37c0caecc0d03fc1d4563a25242c28da2564418870d2048e48213fcc55af9d5c0ace108e9aa60174137f462149e5241a5a9480d2364b164549fe5070dc1f04d3935daad720bf5ba114d9c79385a936ac3b4785cf527fcd4a6a4fb974feb1b035efe411192629415c6dfa37aa4c4bcb2d4ff0dd807691d294effcbe87afe45a1736ee89dfbf1ca8e008cf38f2529ec3fc45b06231385f69d719f7c12aa4ee1a5e6e0beba1e91560b3cfc009c7963b8e52e34f5780c2762a4018f49ad45dcf27a171ed5316570e222bd7854bbd103f20eb1ab99818b0c615106c4181344c153d74c5249645a6b2b6fa6e442cbd9275a125975ef05e5294bd043436a0d50bbd6c103b16d1dd383708abb31af5a44b448eee00181098408cab68c0eb37665a94fa683547537fcb1ad7471d1a45375efaf22b248a5d10e39a2f23486c01cbc017dddfbb498d93e02bb48bfaf7955c816962603a3c74a9cf7baa82e77c016aee36f6259fd2d36fd93948384d86349b81511124235748d6d9fca9f1409dfc999eaa86f94a1270b8a2192b88b604073617627562a2c0dcfdf227bdf032be38717d9b42b4386bd3bf7475d78ca028142a033505bc2f428c9aa4d4d291ba5607f8471731c173c9a5fda7302a0e4e48cb059d8b73d18b97d06ebc56a82e9b5b60e4bee86899a61bedb81f7808e12d586391d025fb1ad51ef606a8da2b5021c05a97ee40a08a1c564036f9d5f7bbcde42fa6c14f6b4d2673de913b3aba43021d42d935be8ba51f72ce04254c10430d8b92cbe0d499c1c735ab2daa2fa9acf7bb3ce9787fa0f3627266e85c142a73e31ff9ce44b5a31349a69efdc149508e147313bff4484bd0432ee1c863b22e307c66c41bfc44bbc3ad77cbc88726098e46b3995bccf927dd3da36d47572a6f8b4124059edab096c68c37c326df0fd7befd3060765bf72fb059c4c28cfbe6e18ae40e4b764505f6ce1f7e3b3b40adce52253e32083dacd60ed7c7d8d2d55466dc967b8409da6775ac6b627575e079d8eb54a56b28ec4508e5769d7311f67e8f9a73d1db114610ef386f5c073eb008007748bcac011e42ad89f1a9de8364c16a72ac610ac8262a0534501b9b3a56aeb8ec24cd0264ad32d61168d91a0ef1e2fe9e9cc3ca7124305145bfff865e8bea092808096ac88d21a416ec3fceb28260cf34bd80f69e15b1413932f49733a45132662551607818252c594014c9be4e41d0cdb44fb40f0dfdc7ca9e39610016c36195d3b43214d6f2c61c0117be1af858ab764be561444276f5de49a2194eaa5b456bcf649b628b9c332488be23920455bdb3109bd7029a23eaa0bb788377d08d0682a3902cc6ac0716430a94048a2494f2f7fadd0b21ef25b1768f60641726e46c2839457e9d48c39aed5a141d0db5f0427c5702fc671b05d1190b652afffa26fee42c088441e37973ef7c7c053dacb8085fde60508449464a7c1abe2b5d2b2e994cf6d2bbfdf5e63b54c0a64ccc13dbdb41d11828580e1017cb27c40950e86062279fccee91059c64f1c698d6f3aa0f4ad95ffc75d831a1f1f2d0268f6240b5a5674b718e76e531e272853ffe49e7d6dfee2dc01ee7e5ac8aba3e3b1de0980ace0bb6ab1404e24574aaf53bc727fd6825d2ca5d5d6a5a179cb1da453d233e879b6b8005755911adba6de425bfbe0dd31eb0eaec5b4d65c125af604496acf8d75f5c196dbd53693b525cbbca8ba9159f42f262db4de696454e2da4fad5bdef7ae6e984c014d3bec1b24466055bf79bbfed73f8298b6cb5b8ebb622e020380b10bbcf1e66617e4613a7b4f59fd93a061c72ba248db75b735ca696cab0e32cf9420bc3319db116ece8942fca2a7831a1dc7dc1998adf86f4862e8b84f5efb2500e9918e57e27a6fca0bc323983353e60ac94b74d5dcb4271b6eb665392a8d73adb563a7b4cc149e32951f1c96527c4c2f4aae3484d2c394d22880b675d503802857a91ab7702c71234538ab86cabc67ebc4e01c4d13c7c667e9191c9c16df360f1834ceb01812745849f610a2253cba1834ceadc43892c0124ae791a7aea6c5293dc4f07878d7537cb719dd03f0a9dd3ad45881a8662d09cad7103a2a7bb93bc7941ef4d3269d1883888ca831f98f9fa5e7e1958387fface78a11943f8d5df473d1f5d6298bf9c0c1fb6ab9fe2507d12d0ca69faf9bc733827a228755ce5e26c3e471c2bb216c67cf42b8e671d18e832b216d33e5c6785efd9b991f2d9ea4e149b9a178623f872dd82910799ca38e2e564bb372af984e307480c5721ef1533336e8424748dd0b87c3a07a86a83588fd4258cc1042ec03ad9983b7385b5f2db6f1131c9c8605254e67d9bb393cccad189451e83b1c9539cec518eb456ceb13af7084d20c49de4cd65b098aae00e83d0ff21fa208fd35ff65cc0898574e2bcb756c4178d3c16cb27f8ed5f23d7691efbeb596223aa99333f6a0643ca436e42a7462c0557712bf606cb4a5ee4c57c29aad319e91663d3bfc402d514935fff2dc784feb56fc7262653976f06c3a5edf0559ea5816e5c67a91add24a82a669efd36b5376cb5f75b012e81e3e425c278192f638e35da20196e9328904338985176756d6076667d8f930917380b1bf4dee05bdc0345937c16101a7792c4b1e8227842243b7ae4be793f82828e6dd4ffde04e4d6c56f070a3b4d182748f8ecf2a3804a0ef83526be9d9276c8e67c1bc6d295727882f5a3214220349c9fa3d95b2a0ddbeb8b90f2e32931a654dbdfe2cf9293ca58f1a345f2f3039be0368f2bb15f312c2a5fd99da81e6221f063f4787f69b9db3be045bf5d856dde3c6c0e3c65804cf65cbd28b061a8817fa5427baa029297dbfe11dcdec5622ea04d01d964a353e8664463f57f95a86b92a730638669d81e12a6600af4c06c5c6d27811e0acaab28b2d5a3af3354eaacf6971a4509e5d2dd17e542096917cf8fd611473c988cb341b7e9e4aafb2852d4a51ed369cf0a2b0a4232725a8f713e7fb06d7de5cac825992633ab11900a2a2b624f3dfbfed446d834b9a5f70f503c95e6f58070c59aa9231b441143519f5e5b6f3ca6df033767dcce02a614b5624892085c8c24c6db439f5ab0151ca03fc96cf3d0f43879abec3112b3b00041d503a1abaa84e707d026bf200a387b00874b98a8e486dad34aa703c4e67584e5969aafc7ea20063a2522c373f7bfdb5445901749edc66e157e0880b732c078bcffef47a932bf2cb92799b8076758e18f34cff1ef9f6db627bfe748fcc9892afb0812a87d11e61dc78b07cd242c42f9ffe271edd60c07b4caab865ac074382fd74c6aba32f9c887f967d1faa7ecf953be480a3d88051e791bd1251db8eb0df0ea767f6ec0cf989245df7c64d86cd80645d0c36682ad8d980917b11629f8fa5775717359f3e8154c24da72be80f02313f1cd1449961a5d116bc3f94d3209a5d35d90e2bcd287fa1a146ffd48d6dabb45929504840ccc5109778a0b0beb06867bb0ae84b5a607471a7b24bc4b98900cb288abd1ba30656a0aae9efef30758e2b6cbd13d5d6bedabe252ccd021f47851340bae6d020c1d33f029a115cf0c94a92256cd587c6c298a93d99805b861a0e4c3c696c2738d30dde41304e90d4abd9ce62ecff8aff7f57307710e67660931b5cdea8d9e63a04caf43b511a54d059eac02a4a9dd0e6bc5990d31eb8aa9df84d875e7b8012596dafe1e003a6567cae9c0060eca8122c7ced08d430111c947663eb1c02d606edca252c4913e2597ba1fd683681669a50e58af27602c8d3ac2a274f84211b3ea961fd6f6effc45820291dd0024527a680d590e66015ac126028dc999f839b64d6a76ca5e0e8731f6e87543f2738fc783d780b4abe117675ee0b96ab6ace7af93e68b309c618ed6a42d91225c60c8f66abd7a7ba05d4248c80c32cd59b2cb0bc3d75519490b024acedafa60ed089a8d66b94087871862d955c7d1cc22ccffb12a304435b1917d642f5bb2b882c8ead78bdde7e12051e042568f2176e7cdf148ce8e7d329c16f1e1be3ab3c7c27a3416aaf3685890ad22e25bcb371ab16cc2d6864e3d3620a24357806724170bbe0d2c83682a0ee106280c17825506fdd19411e453ea01b0517f484edd2a91c4520089ee7706dbfc962ecf7d955cb104e119f4acab2e31ac3cb51b5a65fca332742aeaf439753b754c94350dc7f70766ebaa7ff0d4cb41242091efcf3c75991ccf64f1c8be6dd2835da9a96cb74971c5d815789bb45cf0caca8963cbb03c31e35b0774c065d77a2c4a61d74227a7913ffeed4426281a8d2b521207d05246281fa7266076e6c0ec48365a0c5d222f6d5132857ac529548fcd83d95230c08b04dd9bf84aeaebbc68d173b9858ee46474ed409eb64126739cff8b2fd11e1a4260d60c6535e8abba801a5768861618b54357e38e80998aa59fc1b10fa0c845c2822abfaa59afc14ed19539ef0548e47e173eaac5b8a5007174a1afa80b9fc827e3d6c2dc26f177f10d60c8676bb05588eddd2988b986fa8b4cb1544c35addaab5b38f17894993f1b9fd7c4458362dbf6854f111708fad433caaf3eecb7a3cbd6b286034450926e03a6b0588fa4842ab25347615d218335aa339414fd540b4a62000923fa11634fa2a3454d4ba597621563b0e66f628a8825bf44f713f56cb5251bc83c48a297b23680edd6de01f2599a146228132305673b5d55731047ba5d21089a785a1707a0001411295f30d51ae953217666bb7a1044dbe7749da8ac3c0a8f0277768625c13771517ca157d808b5df83dbb9bec7f8d12c54271801f6af817e78889acfa8686a28fa572002708850504305e26454a1000479d43ffb8dabc8406f94fc76074fef8fa72959a05697a496f028778100feee3a587c74d3738b9a591a7f739548eb49d5a54e52e05a2ab60444392515653d81c1faeb4393cde16ddd4c4bdd651eec06196d1d5677c5f2d883f63b111367349497a6db3c0d3961b785871dd73f0ef4eb64b2532a4bb7b30e5443a45fb93896c6bcc55429f43d33b43fc3450d1a2b153037db8fa2a259e4b13380bd587b7dc398f59e8895c150bd4c3bb68ee5be9137a7c0417f1700fa534bfe2c5d2982ee922a3075bffc89cd9a19420d432d1ddfccb13640f31271a7c5a93c3027062b22c8cf049de2162e4f5c4f04910aaccc6caf4c91a8435a0b2233c78a483f12eac0564118d6ccef42512684f3591d008df66ee88d2d9733bbe55280155942d0b13b5edd03797f1b7e1375c633d7b0f4e6e36cea2003c085adc54f0ed1c8aff3edbd69d86f39577b0539f7f31ea44aa827f34f0ec81e58d89d59ab376d72d222029039ba2a923d943a2b6d79fd473da01b538bd6308e22fc30b101b21f5ece1a6c7341faef87d674e75b61b77d5b4c1af68e59702ecb391a7036cc5a0d9fa10cb02f35570537bf30010267486a3e6510f086c8fdb2f986f7a484cc31932c594cc29596935309fe7f21644b67faed86d86b42abe5f567deb8569c43f04d1c30cd586e33dfcb41d15a38d7eb766b5623c23c39b33a320e06df576778c539e2f2be39c8e65baf3e9d0d498589f2a548d04c5402077edeb914989ec24b1c25ac8e9ac3aa0b55d149fe885d5a4fca0a132fa346e47d49318bf6e0bea4e09e9fbdca7e5291bdf48671905211e7b1441c153f567051f99e2c67d8d92213ddf17a5262f59661bde9f16e6217e3fc24e32543d5fea945d8f509fd48e33e5e6a8d10ae0b11450085c22cfb48ebbf54cde3c29b8b85dd4e7ef5408d8cc3b6f8c5ebe51beeaeb2bd9bb92726f901e42d150a8b9f7d6e62dc4ee950546fd1d3b044eede02e052b976ac5f335555cfacde2c2fbbc574b089c1b73a2d676ac0c2a1fc6a5dc5d4e7e91446350f1aa604eda717b43345be772f5d99bfeba3305cc30fe4bfd8e4113ef2751440b7a5570a156b02cf6dec1b4829d2ee55b442d7fea504f36121e3fdfff8bc81f4769ab214e49981d37bf3c24ebff4bd37e737b3c21a831b19b87b4922d94de928b8311500993a9c65fe31656d0dba2b9c82edbd3cce6d3c453d3e8fa75b0bb948c1cdc651589b81e649ea1c86aed198b312713dc94dad8c15c25a85a1cbe39aa0418a4b8ab70a30d7aac22371dcc8e3591e8cf9185668f8c37d1cada179ae8198566a099d8ca90d6bd2a3da5baca5cedb9dee6ec94cc6be2d0399e5a9bf0bca51cc081a3b26dd772dc9a4d7768d41912a232c2ac90bad993123c0c71f643b9dd34d1911a950b9d2ff5b2c812d6c16def7ad6cea44af1d065196a8f13be6632f3ba1b55da13152019941be3843dc62b363e74dc45c66d9270ba8769a685613ea1acfdbd74b10fdd773cf32324f3f910aab586bc5da92eeacc5aea210e08b87ce130d50700be109368f955cbc28ab5fb9d3728601f16b0b934ad407c592b5a7ac480ef89b51366c3696968cb2b5404704f95c09ba815321cddbe177a4b2daedec6b393e8a034dd55b707b92605a57f41fca258edd6bca76c78c2b17f7141a29415362359c7f12047670bc4b85161984aec9a39f476bec27d605739ac026ca7816bae6406ea090a91e64a62be19304378c5846ce9f30190aa303678dbeccb18985ca540b08cc06ec1800dfbdc9d02d40ce3642911ec56ad73add16bb1149ab04b34ada42c5a437f3b568a15de99229dacb2ef80557bb45687791a94de59d2f2860ab9650931ea7a2fc4a487c188ff5a5163fbfec4f4b084685ceb3f38f0b200dc677108c0ec70600e714284386986c1bb93587f007af7a477ee29859d930b27cf5f639c6b6e6eb92632f24c59cc98e2cc21a736300eaeff4bc9857627309424f21f8006c5a304c008ffda2ab81b7ad4e6db063b805c77d4e6fc09807452788fd0819ad17a938270d038f17d48d4a1555a96c28a40868de6e66be168ec7b85dbff1c65c8165354b27d37ee3b58abbbc4161efb51d4f6142218df6afa68463957a182c9e19b9c0d4606f8132645f90f8024a51d0348c725915539161991d47373ef29177b2e23b432966445b9e17613af6c160d88b1371f43bdc4dca879103857343462168b0547800fd8032f41e999b5eaebe149017b89bb00cebd129b5ebf84cdfe24bda241ffdfab24f3b76420c09d22133d582a12c03192dc1df4ed78ef5373843c96246c774744168c5a5f7195c5bcf9f60015178358709070054ca009700291daf0c1bc40f182f53e690886ccd0256fa55a1ee47ab8cc9a43522d281579f84926247f1ac3c165bba35c4ceff178b252e6c4536729f08801e6ee578bd4624482e23eb90dc3ec724ead253c6405af6cc8502fd9ca7c18da112667d037968c4073638352a35e6479a805aa3a8062765be4cd3717464a2282299e01b318e5565afe2772b2f7dc85efbf5e6043e0a466c160fa2b5bdb01bae7c62883b9d8359a1c37c6b0a9a0b5a6688c4b3ab1c6e1618589d79152d1a9872e667ac9170caaa68c6bbefae67a89ac588d6bab9827a54be3901249966b9a21d041cb2099c087e15eb3a9394cc13d929a25d2c52eefdf561c12a37b15a94c09b3188a4508aae58bb083fd37014c7764e02e72cd2b9ac1b7fba4757336837cd054a8d1608bf55e618f08b20edf52d44a0b43c2e78a4248971d2a6f576a261d69079bb04ce048d2bcae59ecd12f0585980219a8be69d5c4e95506f2f8aa8897c48597b865461cdc468da0741bfee1bae16f17ad959a4f4e2bb89fc28dff74b0e86aa4879c3b58825ad89ac0a5bee4789c849a13f0b16113b531fa8c08d99bea204e9be3a8194fb9cbd9f56520e87079d935f458affd015b3d253937fca68c917212caeeb953571d240ee484d23e79f04e704e189263d27af0458b05967104bf778b630c39d82902370075447f928412b9279f321dd1e12366098b4ac6cfdc7cded2fa89e7c0c4b9396cb12925a26173937250aa1a9505c300e98061a74d95f71f36dff3aba8a3bf95e218f4bce692ce4e905fda15553d2d6b02671652c379f939c9d8a6f5a7824205208d582a322e2b574d20455425cec31e647844efcb652226cf50f1764ccd2af0275de686eb3fcde2270a1abbea774d31db78312d48167ca7c985331f5ec32f15bf9c9b244a77d9168eeb4249799c1f9bac3368a580a8820358fc69eb59de929d26964f17642cc266dd0b71ec19500897b4f24f73ae5121ed5243ce678b422e963990429d7a9df18939bf1a9189921cc4ec49ae90b3a083a2e4b88ec7498bc3a0d075988769ca631ba530be1762d3e307bbaa48ea1d661d2987f3ef10a352ad30c10f72dfff737fe9b04c32ab0127e5b088891b93ef3a4f7a68aa1b9b6cdaa3452ff42bb924ce8878c333ca48e83efef0540ab108818a9c6ec030299167c2f4d58f1463ced8a18a91ec5f537661c388c346e1222709c1cd3c428916828b92ae4a5ae35cd7238ceb9d47e28f9b9e07d4ffa2a14b7733173f1fecb2d13476632a49fdc9e66c4f5c39d7efd466af31c22e6f2618f2a6282bf60e70b54d28f46080295ef581d3428305c3c59f45c57cd8be961d21f323f26d7e50410fb657ffdbbc0ecc09097c620f1877843dc0aa1c5f5b5013a87b84193bde03ec24c771ef760dd17a6b7f4481e8d45cbff7cf46f7e24af07a8549b8dfa3c4ca74a68bbf2bc67640ab0c0bc723fac2ae9cd7f1f258c84d25d06fe3472b74782bbc8cddf631dc2dae898290a1e70a618014b72fb71bbcb8680e8c78ef2caede450906dccc44651f97de4c8f1f0ba64d9726c0e46308f20b86e963b1b9e271a05a673202b9f3d1e984ddd00f4dd997de06afa6da28629016e4bcb43370cd8b76954274771bf976e24375b0ae8523649db063babe52017782c4c47e7923facae914fdd2318364392177ca873adc2f09d7dd1dfe5fb5d648b6b55b74ca73bfba8ad52311fd9acb34f217c5c11d999238a0fbe2098f37edf900b0b02971c5d3e694c51f55be12ad1f9dc315d0c97f0a58e0609deccc6b9831f33696b419f6e147eec0b1ce6ec36d4b00c77a44c3591c0ec6162b7093736dc61a78cb851b96d7c774d5bd11e9c8c9e66d98c2a022db584eb546108dcbaf663ee0a3b23a93f04a150d5ac9e1008a1088b2f3caab979f9119ce3943315f0bdfa318d41e618d3fe8738469bc575080177e9f81cc3413247dcb24a668ef99acf9875ab2d4702c9a9f713573b776e3d3fcaaa08487b1d001a52a7192e25fd829a9729ac45f3e8559bfedfa4996121664888763f769ff15970e7b46f5d0bebfe65760c399568f8036945b31c9a36944ddbbd7dca358ac96d9c51ecf00bbdac9428a4c3b64e5ecde8c4e5947c7477b1ed413927b39ce3b0c0a6846a6b69086271a88ac697dc42f2248ce187714498de529a8382a37e13e00f4015d0c00482a106110ce1fa2c39e75dbe305af2477bd3d52a6d308a221f2b0a7330881f0d3c42560c83a1ad80250856d7be3662c219b2d897b209d45ad923c94b844720560b5f611877be0f048f1c5683c12ffa97f396a2d2d67d3a41fe5d56b7d749bc1ae62824be02519370d67b693fa8bc18266ce586800dc9c518dfbc20bd5e274b7c10c1b04306fb0f3d46bf675ccf526d1cd4f06361f45f9ea7de3555c3ada04b4adcf3041f16610eadfa0652ecc688213ad77d1954ddbad74bf34095fa93ce84528d340b82bd024f64b17d26d4e28e6fd6d89082dc899395a2047aa4a6d52c62bf3d123172621f9dfe380f2718e87d8ae53f9a7dc30041cf607a396e24e3201ba004c03e23d7d15ef1d19b576515e59cc285fc9d0537b1cad7e66673cc852abff7e2352700676e6a5c40ab8f22985c66d0713b251d7619d33d31ad0c8f02eb35c79f45c6ec28801ea8283e60eba2c74d456609b9c0650799a4c820ab1f2051c303e1741dff1c8ee91bc3390f4ffbdb62d117978372ca01898cd666d605a80b72e8b17035ed52176f72a1c4a3a049e4e6d3eb2197f7fdce1575edf702779626c422611f968dc31f7092539704ca2a61ce7101ccbf513a26eec7d6a3b09f480fe8309560f19a7249b2ce84366121a96c8ec60c904368bf28c9361d13dfcd7cc142380de901933b031dafbfb54784e660baeea300893d638d88e31f0f883ad5e519606620b0291ef970d10cdd5711c0bcfe450177a0bf2941c5cbccf9178900450fd726d711f2cc4c05eeedc0264ee9b8863868faed5440d7bc5d02fedb6c0454c4b0752c9ae463995123d39a4fcaeeeebd320f4ea4f40b6a261cc71e49c5bd9c4866644b46244e9cc97932dc6431468f94ee9ecc521fa5c433a1748c2ab8fffcd7859b3308d3a428b52fd48578e84fd2c356d6f607fdf5ea8b273ebacaa732102f2e3e149467df61d2c3595949398ee396411fdde8e34db323c439ad791c97fb88b6aab5886a28d4ece32f3afdb41df26f0a3c1263a9a6b715890492d0d6b6361e28da1a00f4eeb6e477f1c4ee035d85ebeed5defe1c6554d26b122f0664ba5b230cb9d5cafb8de9634245599afc9519d4b8af6905ab4afa3d2287b5a681f088e9e45d5454ee64fdca87b5369f117fd689726ac8afb93ef82794e2535f9e8d8480320b828dc5627452ef536b63b7502c8c7ea3a0b3770ae0c23028616f3b668251d287a0079412003e5cd7a71fb2e0a7e4c086bf7aec7257bafc2a700429df56a7c9a91093d93cfb12979a6a3af2870f42d4de7e67b13d5a40c601e5d1bb77daf66512c23c0c69bdc79d49651562ab699e878e835a29e9708f3604b33cfdfa76335278b1e2217912b8152bba10e540c73ff00b7eb372eb454b7339bb726f874c4d7ae4dbc1dfb76315f4ec632f85058bcf49da3c1dc9048abf0bcefe5a00138d25f4092e089db5b67d87941765c83f8027a957e17086da9608d057529e23aed536b2440be04e4e119b401bb225affa861a21a96f29186d68cda1f0350807cd9611ae1a08082d8064db0b0fa416313c84d678b5dd792e64656478cce775371a44eb0d7e08f4dcab1d5f412aea256474b22ab0e87aa064899389c82e55aaa1857906f485cb4c3779ad20e4e0edc362e513e9a5a2f1b6403d2078f77220ea2a1d6423b3eee9810794d2d196eb2c28eb35f42008c416dc84ce57258d1fca9247fae3cebc50d65f61d740870bfe8a9d65170350dd833c7c6ef6e5f485ed04bf97231a25a5ecb65b328362b14fe81d7994d56b50ba30a0f0ef172c25c9b76efd367e2820056d192e11599fda0f890871f6e1efc4643a70d86b908e646a0eda98e3dd5f3850a876d7239b2e15ab632a0eea10b48ddf8e869a1bf6d495797b7518a5b0a297b7f454b4bfcdec4d24f04bc039d22e9102119548cd8dec999e7f7d69e690fc35bdb8575ff66d07d22ef0df3c94a8d8a1417420f10cc68f67ea7e92ff028b241fbe8b49be434902ad661c2520f0d97b159b5d1c9f7ef0190057694f49ad42fa9a469470820324bfa7420a69333eca41e86a03fa980825337548ca15d0e061b0f2860d58a86c99fe95aaf26be9a66a82edb3b0ca286b955b2e301097bfaf290b46213100d043107b1cb76c48eecaa1aac26da76c1666c4f95490f0de95a466d4e2851654d7d2cd04e561d1e1b9eca4f2fc4985624985066cdff60b099b362f28d5dffcb4498ec2dc402d532476332a6a7478beb9c40e79e262239d9ea98e145510fbb5f044d9c5ee8f33d2a9c30eebe217ab5659cc5758c41836e054f62d98c17c08f8ce10e2a4d59b1b026740054af24d7d6f2a3b1330e9d0410b0a1f7a1fc44592f0cab840799e2a2943991b3867fa21e4c9f8cfb43b6fe05905433d7b3229f5b1b6869a360147352935a52aba30474daf5fdd66e434589c1fb65e9fe58983a92c5f1ab1d2dcecb8b3322c357d76dc5e5c9069e7dd3777c159ce85fd9143456ab8639f68d984c89329f5f217a56952e2ca176bfefd9878d542e3b6814b0716e4bab84538344a228bc0990afe2b555a54ae2254d1014726892a189258551309d564c00278661de1546024dca669ecde5ab4e9c411a810a58bb67df2c32bfb121f20be2048a66c2ddf2c925ff1a86fd4fa29f88b4074e3a3a49fa594e346af4a88af10ee334bff2011f0920b9d7d05c067915bebb020c0985aa7e2911bd9591d43d30102246bfc60f77368eab731e65931d9cb9ab967d40099448f40914905da46d0749b6a7ca9d781fbae895714bcbd54288b5290beff4caed2c7192a219b905ea1f9b83a4da18138dca625cb0aea239ab1244411efd96ae1f841a586874bb7e7ee7b0020b2b8b5b9087b2e871c0d02b850056928ec392ed379f9ea469ad2751fdf90e837c9489e4632eba0de9013e3d10cbccc99ba6f71e30675ec9adf0073c7969eb804650823b39ba1252b44ad7f1ecc527f9f42c59acd0bd1821f949fe5d80d3efcce18618bb51f941ff02c4d0518f273f1d2d118572894a430bd73038534da5c21a483f649be836c0ad072349265cf39b37d05198bc8a0a2f87a512232ca23ef83690c9465232a9a3a8a7f2afcf3d1d0a4af43f4d64e4137f9c013ba959cc00c23077d02a2b1d4fe3816ff7ef47f5ebf4b7e82962f0965e87275302b9126b235f3f6f8347f6aa2b4d711a70a50d7c9adb29f3f45d7e3b16539d381f75ccb0592c5a5c4e3d579dda4bdcc4dac771bc98493e266cb7b783ae2f64395a3b5d1384ef1c15319fe70567cc47763fa60116d7714b59be89a6237525587fd5449f017fa623fc198c15a0f06bb58f21fb9b945ee89979ee0344d3e553309d3337f6e97b0a68ba42dc350ba22ba52b7c059fbbbf8371f654cd043b5e152044c45224ba58201039b98ce42c8c320938472836e4e2260d1326eb3a30da0bfcb6b60572f79b944302bcfd0669b4ca65c4a02aa03e70bb78285b79bb22017a41edd2f421baddca72d5601b604d244061fdbc9f6e39be1fb5cb4b8d5334734706f7018b2c759a38ee3e3ff68526a08f085af5252838e6221ebe85ba6f808e65b23e02b119e729d82faba2d618d019e83b64ca4af7cebbfd0e96280bb42e5149113a135c4ffd7632dff52118036c12c818da6c836376ddfca4a1ce95dbe3aefac48609e2f2230feea42db3d5477e1f136cd5e3248b04a0ce71f1ad5e62731788c56b1f2d3253858a8c5bb49e6214b1c52e1a10fc6220180ca3fe3e6f33fc4efe57f8012fff1a5a8a4f414fa9249c15a424686c0ffc8128cceae318c2277096012b654e12e433bc7c2ca72fccf1e03bda751babca01d9c296651b641126cff08282c8cd873e8753b680f2c6fbd620ad5047737d4892acce891eaae3ce4a2652c554773a49f3fcf3681b18f4cc3974847e724fa7aaa17acdd153a444367d6acf40b2f5c4740170d302ee004b657fe7ce19cc5cbccd10f03af0ee6c475ee4ffac96d683da92f443175ad33ec967b61a1a109af959d32c7a22ad30fa659d0c34bdad8852b76c2d55c410c81104d0a468515840ba7ce3a77bd254ee19dd2c988e0df5aa03712915d3a02d5b2fc0e7638aaca55da941c74d7331f5427a604ddd7e16063fa0fd5f2b8d16a314796b1d892034311d7a3368fee4f3ed436cfbe0663e02c6478b8e77b9ef10e32c2f2bf6ce87d48ce17ae2af5d44452a7bed5de90590f5230b9ee543d317a0747047480c0c66667137fca0cdea23b267d565c454feb097b8e40b20c3063676a45b33430266537890b2575303690a330a360b5961460b3923d7c60c8c29af2f8a179bc096cbefa3a055eb45e6719d40c9218adf9b8acf85224b9f78f394957c973649a620d72b02b2dc49a4b01a67b825becc2cd4ba4d55b1fc12f08595a264aeeb247deded9ee2dc5a336e138ac33629898984b96b1e722323cbef4f5065a441770c14d4a634798b635ff68be2b5e423cb3cc1fed3876f90583f9b7010dba7e7cb32539e19118043018346e395aee61c0b66d5bfbeaa7c248505a5daf78d8ab602d57e10ba0c65af8a796d923f6cec70fa4a30f686780ab3bbe1167ae4364f1fe5f761093521c05ca6f6cc4ac573ff94146f88e7e6e2ba137192ae967b6b7b507e8a6ee861fef98c20b19090f0ca38bbe5af0cc5afaa2170cf5974959707892db9f62e9555523b87310d79061ac26ce2da33a98f0f4bf38df8e8dfc4edf186d0b5cf1c8bbc547e9d4f288e67aa5ff782e8dd21411a2ada5a422c343102282635165912d528722b3e0f4f284437f6eb166e065b03ce98d428d8e27285450294dfa5e0837a6bc1a4f18e28225a14da8ad60bfcf8e7a2189347bd84569291fba05e58fefefeaf43ba2686a30a1fd7d5721e8b4218d8470fd8912da159b7a790936620e6e73b5e37c9c9a30d50ffc38e66d32b40ab61af89f153064273e2fa8868d78b3fc4ebc07d953074eaa5766b4179d1eaea952718c268385a8be0cd0ad982afe052a267b888624933e03e9d04d9156e5ccfd9f9b24cde73e9ff65217391130f73bf3cd19deca94bfb8c3702db912a29c1d50e04fe95fb9289968d81f5b9f2b2f2019549689cd246be5e3bc79bcb41de2030818fc80052de4c11e141fac40bf7ec8d3c90a05a36429e8e72f431e46360310e22bdaa474b1f1589a40632b833034c602cef1321a4d45ecd5fa0780de31c97c9ded147fae298c1c941e068c70bb3c667ba4199aa0b73e08fe8ff786be6c57f03d91d0f66858c8db411885c5ee5a04c0470cb73dc4902516b88d7f58d7d41d858f844541d239ec2181e3b4ed41726bdedf0688b72e19aab338f44af717704e29f21c38dae8b5b1c6b15a8e7b3c94589054d6aa29a9b9475f9a695b218b1709142ea7e2975557d3769bf2ccaa5787dc2dff1caad72c1afa62fe55befdebbba133fd17533f1637fb7d6fc2a3a0d293eb2907f9721ca6d3746a6c1662c618db360bf8c133824cc1a0d0d2b9eaea93537e0be08c83a5c200199305e9fb8c5c889e0573dd29225c4dd26e1ae0f721afcbc8d3912c9359c1faa067413ec0543939180bc57958009b35c0f211ef059a4ced64bd0a0f3dcb4ac74c5b809032eeabdcdd9575b17cd5f1b7f9edb291eee813d66df5e51236ec2deb51fe72f30b064b23f0b3632093a8fe3531642fc82ba1f3d307fcbfced24e4ebeac6d5c632a521b6053368181ba80bed68c1e99379355dccb9730137d17e59d91bcc6246f047596d7897dee5047440625719529f674c4f0fc2266aa8d48928416015563198ca77e7bba569c58c17d5d564c0bd8c5f97185ac92a376573252aa4c0ee448dae8f240d783b6b7b937c8ff5990603783035085ba045227a9621888b0f08d9fc106e3c5d9e24734ecc7461030df20004a8830e4455f3af7dd9ab9c5c4f1fb5efe6e437a43a1b96bd4dca86f7468bc3fb081553c60ebd78312f287aca4d78f1530300d5d85c3610fb6f5b6cbd431faf6ba3e6475f33734bf1b0323d76da86853da656fe7a292ce246e7b1a24b9ff58d27869c86dc85d9b367790e602056da31e75f938934e86246b5c741cf0c1b37cc980bf1f60b12c5c1b4cb482398c01a0760b7c6d29426fa18cba5be47ceb3452bb14d5864ac05b610a7f08129247b39c9e3891cc4abc0f67f3beaa731f6377687f92bbf7a3e0b76a5f729d5b9cd127ee2a248c6a628fe2482aefd9cdf6dfee0a4a81855f75b1db2c35a6944a440e29d5a388dc064916458b6a3da220e66e52c4703379024a8c52007601dbb2b63a829b22d68296fb59938b898eb4c2fdeb1211378f1b4eea28862e5169ed9f56876f680d0687654e1d1b02f99194f65baeea4a7723a8385557340194cdda276ddfe1847060ebf8b16ceda1818d59245060bfc8cb68f08237ab1b13ca035043cfb5021a0d569112d54809ce47ae09f12e70c7635cd73833539527057e86b1c9579f6e1fef6a4c311ae958e27a4d8cd94825316929b027816ed00ffc2e143cbe252786b7ae9b2286a178ba61b2a497f9002d728538be8698ba8168b252ae4e62b51692d0561512aad818358f67480655290493e5427a6499ae30049aee74832db53addd77738b2c84e1a0347d903b5552c628ea44cc0b7abe763b652b1f49ea841b78a06bf38555decbc3af4ccb7daa2034ceccc76aa60372a83793aeec257114477256fe55993a45809b80bfa8db2b236626014fe6d0ecdb6fa568ecf6a20719cfc2e6bce2670571990119cceae2670d71371951dd28c7005327304545637393622744decaa5df5aebd9144702b9f2c1948cbf292ef11da0cd3f592e0789887a4c53b572f6d8635efa9dc70197ec7ca4dcb2d89056a2d4c81fedd0b5f35180f33753778e7934a18ac28ca3c0dc6ae6da2079ad7ab2fdf0c46a06d419511e8cb8fd9faf645d4c7d1c07237e266485a7ac96e0cfb42108b37253d1c7fd12c4909a040efebc103775284f1badba0c10f7f43597abd116d5a6f8f797595465146ddaaaf2b7da7cc46f2f9e3c2c8c527af44ab5b472a00c1016bf8cd1b62e10f249aa02c3d2d36aa34ba28df41c1a726784ce470c97eb80b58c3ddce7e43c96ece6062c04210d50aa46cf7f039649b03d056bfabe0dc5905f0de2b7a7426c245021ba89a0f9da9f1cff69525998dd30dba979c9741960e3ae15771657612c215d5045f00f322b7caed4c67d9038745b711e5e62ee90cd70927e2e7a1e1cc0953524670eb180b509fc653a2db0b7ef2375673fd6ece0237927f2e7f5a6a3ac9d8c2351f3e5be7eaddd8260d502870e6c9d612a7bf5b7b3941a0e28e5d69cb32d9d0e5242085535999c51d3034f91b32d19cd19a86aa730ad55081702785334c0b4b62d52521637503453d36dcedf131e481450475dd8ee13dd49bd64ee3befb631925e803536835a63f9b2a1a0314ab455773b609600325070036e935e126a5aba058b03e7c65bcc61c76afa4c9e949a07497b9c9e7514faa8a9d9132827c01005c99acca2ecd947fb3d4bd55d263ca13a66be24f7f70c41489c5a714e365dabc04fe3a4c7fd938d9925d8385500d9136e4a12c6679250769e382241714c0932b9971de06108683f342a3279c64621a08fae6c499866cf207b3b4ce5c20ec589a2cd52e2838dea2243393713e8aa42f2e6d9feec4bd0182c7e5a48822e5892469323391db9b40ee90341ddc75144964c798eb688f58c9886c27e1ef4b0846bacc9f113ab63547bcc3c492166f9aac3ede75e99b6226c606b0ade668e24e02019f46286d9956e7cc716f87887df7f35064d1fed9414576a23048ad13fd5e8ac898e396dcd698e4f03968dcc9caa9fa6d5f1e14a6058ae38d2e1d0c7129402423d892b12cafa9cea896c60e5ab0bcb67f9e45df1a11aa81263364c340608abc48eda98d867e9352c525cffe8cc30730c5349cca179de9b87e1386300a314aab1edb1318645ad252ef3fd6e5650ab073b3741acd229bcc0e47e4227b206a3301ee8f25fc67796cab7d9348305cd0ba81aa941424abf682ce07f051d094c34a23ad325c02d89c45808053a62b5058a0e17005b6b8de5e384e37670b1830366efe7abb55fd3c5ae6616c4f7eb9ff9d0ee03c0138b6092af1fd92247580388497f91913496bd12f8112702a0d51dfb85102748c4cd5f74a029cb3a640d5fe41bf6aab419ef40340a01c348dee1dfc1ea344b8821d209d022493aec330b9b10f16680e60a048db2d881fd348630543514674f7cd36994549b325ad8ab2a0448cc9b34aa8f4472fc320c1f90b5b8e315cfe4f25aeeb0c965bbd75e8216399e704e225ce6d32fde1255f4dc9979cbcd059599a935dabc70569db9ccc95b615e2537d06d0f3f12ab6397b55e7b410dbc82fe26c81924b281374e351e08193420179312dafdd93fada4ddf08a2f6f3667ae597cfb58653eb06d91e3977019bdaec0112cee0063199902408edf5e4d0cf4c50079b9f9c16d73d9973098b5b75618dd581a85b71166c67c59ee77198bf70eaa29f05a87deb3ad6f526199d88fdc8767517a0c77a929619569b6b85b47da56e59263d5e65531173f07d1681ccf1b01d158016e5cafa2ec139de684437e2a627a6ce7cfeb320853f570a48094c0ca56df54f7d366137f897808849f14c484a53a3a8ddcce3d07f8782d62b069342f3712409ce6bbd8069aa999b91530b48f4b7db251ffd827672054588fb2ea2521469a2894f5d84ff6ce58b862482bf7cb2819c5363e67b230cea835d6a1668226764744901af2d5c65b9c69db433b884a44f153f114bcea8eec56a79ea72ed21bc215151c8b641b4812b1912f19fc83fba171266cf4238432621b4cfe54e43aa6d47f1b08226cd673943ca077bea00151cdfefc3122c161b473c4ce3a3b3d3fe0b31cd33ee1063878aac9365c8c6503966494cb670b511cbf37b484c45049599ffe431107193556ccfb55b6b222dd937b138a5794e1ed27a60878195e3c53b284497d9c147a14d6615b049a04f107bb6b69188eafcd9d2b317c51690eb8b4aa08416033473c7025a70656aabd66bae2c4fd16c68a624cea9022683dff0b4f4288f4a040ba13c30fde361e23d267c4797171e713876b1c8fb4931530795946a6a14fe1ab28bf97d3c5b482e8f29bb61d270a4e99a096964076f6154e51ea1e7a75ecb48c4be1787e71cedcae09043b6c5196b42d91963aeb5e83edaa3298f76a9960f4226eea6d2ffccb58febd9ea3be89c549fa9353442db6062a25308a6aa412ad292c6f35ba48aca931083904731192a22c35c46620cb8fffea9064db9132cdd43a529f72b9bbd5d550943975cb00cc678dbd9189e6ef12914eca902fe88e7ff85ada24fc331bc1faad3a4fa25261323e0371da32074ccb4b7fcab6caba424e2f912e4652dcea88ae795f54168a4007ada6614781a1a6d7cb8e767cd0c67f7c9e5a6634bdf914fbf80d75ec8c4ac75504338c44b5780dcc58e8f1a25572eba6895e8cfab3458dfd0718521085c18d40d3a12c1c528773ce59c36df7bde41bbdf4185caba012d8f23ace0dfbefabb66d4c693e7eabd03e4f6e3a6725d297725878ab9adf0f78edaec7074ca08f3eb583199d2555856345bfcaa7b330153df5fe124ed3f27c234589a84815c9c5d836db844f9661a2dea0b789b85c248095bf6406dc5fd20bb84495c7f2b4ab0510f9c8e084c5836bedb4675b885e4a08e4c2225e089ceb8e2f9fec8dffaf4b2b364e5d33d2025d6bfb43e5192f984bcb4405fdcef9caadbf8e0ec6ade3503939e64d5e066f488e60652d7448a568ddc3ad652f4d39fa5c19957f9b7b98f70531c445e845959853276de566a5c4905ed55e43090fd94da7d9fe0266804c2ae16e63074f273d67de84c3c4b2f190d2a0928cc953b5814da968150f993aa2105f04bf6c8edeffc89a51b2731a59e81d2aaf23e108546d0caaa1a007becbdb7ed4ee8670da562bd0c6847174ea9b0189196022e6b437b045b051d05f59b7dd3a02295665634a8ec2a4076049810107268e62e216871fe1375a29d7f3006c75b4b8db35f52055efaea0d52f67b3d2a661809c2b0f8eeda1c72c85b45a2a5ea91e85194c695489f568f3f152fefa7ffa1041b6220b8142c8529f989b2e38c8ab4baf44211c8e9da20e30aa20f3611d14fe01df037809f436ec61481e3ea1cfb22520ce45bf854edee6db9c6ad84949dcd8103439f03288249ad702963fb2d1048d6cc08f2a5281807c9c0349e98efbc5565182b64fd2e026ecdeeed67d2f5cde039a33af2899dd227c0cb9dfd17f29e44edacc09fe0d1eefaad5c6c64a215c9df37c8c042922d6f2600c8b3b66e357b706345883e66ecbadad669c347eaa70aaf398c90413c7865619d97271e31b06ecb22df44e8d82ce5ccf7d11d18e521cc32eae69d177edbba2f0ce7e2110c3d2d60f2cb55604cff2753f036d9ff7f6d6241afc72ef86d51b5f2f33d63274a3405201a41dcbf0bb9f5d578e07752bbbe2ee1ce4eb13435fb920eacbaf83e2b33596ca877d4adf8506c0d7a68dbd8b74b767d07ec92c52e1d43374402a79f19114e80b830ed9db818a6792b8976c394f6aa08a804b48da521a6d94d824cab988d34f3da008153be364c8353ee1906867590e033a8b38f640cab7df59ea6f7ddc46a08c5b5cd43f52136e15be2eeaff33945df4cd9540e3e9cd8c683be83d08985c58307eb899979a33d06b8011ea3a3f4c2242e69156120480bf9b42b9d73a12ccf4ea813e57609836c43208d80e2897cfc97144f1f934a8eaff887f92f8d9a52efd134b9fbc4b4054b7fe912394e18ecc0f50cf5973acfcea4e4591b72dbe9296dae172c6aba649dcadb7b51277387d17a3fbd647ac104da27d0984a1c316a6bbf153009a7e37adc0f48a783b511690c2f4ba41cefecfac941e90fa2e0556fef09d8fac9d35cc4d2920c918a6ad35ab7508613def434a502c12694b1af3e9fad895d92b47d1e5199108b7e0abf09d9835dd82fa85d970a0b385a79ead7488fbf6df79c4e5dabed728e20bb23cc4dd2a0e67c1565d17ff529a944d8314519c8cb5e92ecbc699f0f07593f232f9a9ec975422627bd4ce91388c3898da673ab0fe8b963468a41e6b45057019321430f004d52182b13373e9643655460173f5ccc50f872eda06990dd53eac573cf4ad0b1715cad9fc684128a0d0563ec7ba82fbe6843cce930c56a152a83343c51605893da18670a75491d0c3242dbdcd141a59025c5500ff7185a93d41ef359e3663280ab59ceb8b85aa8170bc500de0ce4865b5089716e02ea0a78d07e93b0852a4fcad6f87c45aae5ae852b7b72b833799533b19da11d154f15594f0afd705200c6263e374b3eda46f98b8650490be6cc048fdd108037e7a280c805abf76b6a1711592e4c727147e1e2f4e5bc713d75bf9753e2f256a25bed8dedf3f356644ca2bdf4b811cfcc1d47348b5c51c9240fbfe46432c6568a3e2d82707b4c8cdbff99bc5edcc7d48143b1a32409c40943b35b0c373bd0ae36b83e99659ce7a498868079524063530a8e1cb428e3caff6d398a9eda206d4fbbd98dff292dcb3eb619b6d5bffdba3820023b37456dfc2d09527fe55ca5de23c3124e314579bc59cafc371125e26299685a696b613bc127e3ab31d1a9c8fc9e84c9d967632e1a46f19efae3ec8badd441ae92d49615fc2e316d17ba648aed1d697acde3ccfe1f897fed0900cb9aa14a0bda047084fac7cd32fcdd480f6a16a85871e4f5753e2e72f8f1248a6a9730f7c323e54987722b27b1211ff0051c7b7a561551cce7a5b50d7ea2115b131eddb5b6c6ea920aa8bb1fae61f180d50fd0e583213bb19cd5291118248c6c2495fd5b81a298eeaa0c23efe6e5d5f5e0f9efc14a49f28a2a424f3516f1c6fa50fa3779be080b9c08a4691ff2115a42f7b9259ba27d600c57a7476874dfc19727914e4ac7741b41565f75fc9a29f1a30544bbbd20629c1c7e7b1188253a677f1c88cfcea51b3ec02b4fee1966ea557ee25179595ceb12e65cd149df33b8dbe695f85fce2b0d0252b66110a0feb70e26f508608f6afad581b7a545810e60dbb937ad4bb39d2db9a83fe25423fcd3d482a9185d56f31c5a52f7cd12f1549206d9f7e6fe518b05603f396ad409573e30bf289de092370dc7306c7fd47b40847103ea599e785c305656a5030a83a4defa726f4295590baf838550fcf3ad71a22814db7d1a2c40ad7aef798904caa451a8b6f0dbe81fc290f978748dd7315deb0646e3e605d80022fcf91866ffda3a566c9a72f0f4c58f2f09d97bef2db794522699025d076f0783073c7d84aa1eef197d34c60d3950a9c168799ea8c1e8e436b999df326e1899ba890c6ed84718cf511b711e7f1a44e9dc28673826fa16e58cf408b0a44d3ae8a7732a7be467dc5421f6da17c62d3e7ac6c4f3d1313ee27efa8c40aae8533f948e504bcac53e8a4c474b12a7597336354bf237936c2e62666cd3190675c3c5dcde94f752656bdbb639a70a6f9e6b4a29a55ea9a3a62ae46c5ec6d9f9cdcb6ef3b9719b0ff55a6dcbd9dd73529f35c75c511870cae6d9e41113ba1da81f39ec812918eebe78f46194cee5216cd55139564a293b5655a5f314abcfc6306173e7248a9398a469d47d86c59999596878decabd9f129fa74a94c424a9a3a3344835ce8d1aa4a98ff38da3be515adff35cc7166377e75d6b47639292a457d791a9a35253e3744bd3354aaa5ef7c5a4d3c625f5cc92128f72469fe7c39d9473e9363d7c7439d4234fc7b9e5386b933c49c6d02439e3c97bc495332c4e2d0d675131c7b4d350f58d4b31e94852550d49749dd76e47777eea1e70f35de71db7756c555c3b2e3bb269b57ae7d55ae738dfb6deacdfbc4565997319d771d76dcec3fbceb56e47b76d99d6b9f5cead9f1cdbbaa77357ebec4d6ba723e870dbb1bab3754b94c9d42b9df3e3ded9f9e6e1bca6fabbd745b6fc56a04f3fee53df1ede30f59c5742a459a1d052625c6ff5ead3bb3a7dea984bfd56a071c90ffdb0310c6a879bb97699dceda6bc9766713e7d4ecad16e87f5ce4fd76737bdbb7ef3a75ef1183e1288b8d6df268c7f7da556947347fdaa6e9ea23aafddc93bdeeab4d25aac662a1ad231496bb5169b5eebbcf1d3af4fc76acaab749e1a1b62ab4d72f40cb300aa731e5ffcab73d66218866118e6d2b118a3f5686df4e19726686226cfae1d94ec10f4ecd251f2610374707e43a900d3ac9d67f779f6b08b1847a4e2f901920596414db91e4cfc79f1e8337db2c7a6676f1cc6c15dfca194d266d5be7ea25765db4fa7ce79787f8a5de7a8edc85a9faeb9ecbae6b88af2467987721dd2aa34948eaeb5abfcc9511ec30973945bbff956859ccd73ae69df9b73c7712a24a209d52b50a6aa74eb7a838da2721a0b27519c44417da36e3e763b34acc1cc3698f9e09cbf6a5b019953da5d2bd9e64317cca557e7e1b960df0af431c76ad57cbac720fdfef69140c475a9719520c873d3d74daaea8c427d9994d8c76522244dcf444b28dd67a225a2a076b85ccb102ebb0f97daad1ae4d8a29f538ad52f478312c5746ffc46a9a0fe798af2eb6890bdf8b1f4c3111fa6981c50264df840f6a01ecdb496e38711dc50058a385910902c888784363202173720aac1124b3ca162b10ccbb01c649e756b31cc6218860525f986228809ac1b05073c805f2b26e2a21f26d874c0f0038888837a76fdc0d1e14584d266d9154810ceb8031d927c8486282140b6131d789a0cf9d1727a5861b2a2c5060320cc2d1ab0c3123c523cc9628c29738b0d18b9608a2041929c204a92c64499752ffb5698fd6e3418577286eb1844d3da0d9a1c584813822b241d649f5d391ca94040054dc1821c259076b0050e49d8140c890857952637b4a04748921c1d6a10fa759a5ae43084143949d1631c5888d5485f7a78e23eb47fb9ce95f36a8a4dafce32fd359e20bf1bbba30a727c86e830b5cc1198597614dd3276e51a8b2c4ffd453e22e2d85167897bd592e446e7a2291bc3b458ced00a7eb862c678b57a1d2fb020633ab6588a881d5f30b3bfe645491848301c6d7098dd41cefcb8d4e56eaa875b9f5d3848f11cd731d1a368f962762683fb2e1c86b267170e49c2c041e683cd680855446f5a1bcf2e1c80fe3ebb70d8f9d02b72cd39e75c619939e7b430d8e8d0c739e59c92bb08d0161637a4443e7a740ae49408e3984e9550a1ec39ce291fc63c4b295db694cd371487eadcb9d0adfaf9a881bae086263d517a9ab0b50ef8f1a3022488aa9ce153832a45fd01cf0f3c3814f9a0074d384c5cb1cfe052200510152739f0c921c9129ac50d901d76a092c30f82501123d2dc70439f71030edf6ec9d8b11144e3d3a38c1f63159e6558241902020a2658f80085fa409b477c051848a2d0810c2e84ec907d3a1adc41bd81edd9826435570e40b43cbb72c851b25e684c646dcf04e209ec13a38c4d1ac9726c966d5996b120cbb18e591be404dfce7dda369fe8c2658312f7ecb2c18adf9e5d361465add10086a79ccdc99c6599958c127a541019574a29659c73da403279e9310639e59c73460799871ac054888de98d1350fc74dbd530934a338d6601077806638c2bdc3cf1d4e774adabe1a1cdcabfa1718eb131e3e8b88d5963ceac6395cef6ee668ecc19116e65aa0333f0186dd00d53bf792cf923fc076e5c5668ff6ce2d7b44b27d18583f4ecb261e806cf2e1ca64fbd788cc36255888ba7e9e567d30f02efc31e3838386fe3cdd308bf691e763fc30c1e8855de52c79865a48c9491723acbd00fed87f6b113f8e917ceefda8fe1cd4777b24df9b222e3865e8674c3d5cf4c6ff7aa347de83560dee09567bdfb7253432bf0b82a441f7a72c62b67e4e2861c195bfb4dbd6042b8e14d0d7143663ac328fb8105eacce4575e475f19c345b911280e117de2ceebc6337ec2b8d374b9dbe375e34ecc79c59d577c0556f536e6ed90b53e1cd970e5ad338ce5c6d822778c5f7763b3314c3f40fafb4a070c58c20d9a1943f715dc909b48fbd2a0f4588884561a21a59452cad850a49452cad83c90b1ad90524a196543e10123a59452cad8d10a19291429a5942da59452c6ee8194524a2963f3404a2965742923d7039a949452cae852c68622a59452cad8d28a26a81f33f841132898c5aa884cf387214022538e159d2645e4a882eed4a08b9c8f3ebb562aaa50042a5241158262ad8dad421be22c66f0431b9a2229533ac2059c04858f78f091f604091ef0a0bb63f78bb41fdd461b38ad62dad0486b1c0707870a1b69238d40119c708104e49e704652a89540e2831482b2aa81c493157c151e019e20c103af8a54027e70d245eaa559369387a611158a4e3079820a41996d532f0e0e8e4d8d1e3b768cd1433065ece437572bd440e209121f6f3a46201b372ba222ede8b375706e4e4f61d85729334db15397ec9529b3181bf7c6ddaac1112a2e1e211f827e7e82688a3ca9607087f47691df47ab0cdfde32f5744ea7f1c8cfe05e33ae94dccb742f4aa1954be9e5d9d50315bf13e5d9b5e3e4c36c8aab87a5478193977976a1e0e7972069d7cece37bb4c50e4dbbb493b79b2f3217d3579d2d4ac18d69ceca6308c524a69ca7a42dfb348831df4521691477e7c767e06f9a3f62c58a697b8d70543480df6d21124393c4a2238bfe7fc3543f85b9cbf861741fa6962fa41c2cfe2fc349c7fc5f9efeb4f3ecf393f3f3f3f3f3f3f7f8fce6bce9ff1d708397a0ce76b7a823e3aff0fd86d08714f5a5c8e42c892b74a484bcdb296c928a959ecda69fa766bd4ac90bea2342bec217b144f78b18e9ce11f5bc43876b026ccc7bfd0e68cf0859648005f689d04e00bb1222f5f8809b97c21c604802fc45edf5e3324a859b3e9f94550d50c093a7244a859d38ce7f7543543849020c169d614c2f3db50d50cc1c9c919b2d3ac59c6f3b7a86a86ec0ce119c293d4acf97afe1aaa9a9e2425a59ea5664d329e3fa5aae959ea61ea611a02d4ac1984e76751d50c011a526448919ea366cd319e9f86aaa6e7a807a907c9a75993e9f9575435437c7e7e86e8346b5279feabaa19a23324c890203daf664d319e1fa5aae979f534f53409596ad60ce3f94faa1a214b42988430f9346b2e3d7fa7aae9f1f9f9116ad69cf2fc9caaa6470809921e9d664d29cfbfa96a7a747a82f404316ad604e3f935558d10a328518424356b2a3d7fa6aa119224444988520f50b3e617cf5f55353d403d457a8a1c356b02e18f909084bc9a35bd787e4c5523e425a44948134eb366524d0f4e4e4e50b36617df1374e4484dcf4eb32617cf1f55353d3ccf5d4d0f0fcbf0eb6819258b9f9988a3e3d3248a281f56a66faf54f40c77fde99956e2e4bbc977d0120fc93d52c446b94855e9bd2863d48938314747ced057c7b4ebb44ed3974ed3974ed3978eeb625fb87a15dc90bea65ba7afc6706ac0c15429441f4f6327011bfb39b1a941988f2147d4430d7a37cc50231983d024897373bac82aad708321a7f4ed3449ce54a68ee95a9b9242fa32faa6afa4ef5783dd430d76bb77c60d7be8e5453dd49e798c69622b5ab94ca7ee4274fe68601a9268813d7e2e9286e8f37391ceade9dc923faebf0853dc70155d3d34094209530d1e96444912409024209600f1f3cd39a994f28b16ab1ffbe474307b6259d5d872adae7d977e0ec35ba7ccf5a3d5a31f5b22966a9a9c9e79a55e35cda9df7bbf10f34c95c50e02da0a180861be7a5b0073eab55b81045f1debcb353887d840062966330f9dc57525b4f012063cf18bc3f0d25f34e071e0e54df178e97c657869398cb98b93078d33181970d26216b360889044079a2c83c2a23473c382188c61022642cae0210306ae0d0639b0bc0501769c48d243194c5c51861667820841138448901f4096b2225b0b0c2e5f4e4a29a7e48e9d0339270a8b3ba5fcee3896891eb93e8fe98369c389f436edf1c7c4304a29a5149b94c6da7b31ccaa38b73ca379f685d5ed476fc5ac8ce1ea011f5fdf67984fc31c4f3f1bfad4c86410c65f269d734aaf41164d35c8914ec99ad356136c1a169b7edc590f330fe9b436cb32973191525a6b95d26edbb67d2c63eeb482f2174e7b63e0ef0a20490e971b4ccdd04a97138c39e79cb572b5d67a7296319c735fadd539ce3a0fef39d7d1d652ea95e338ee2381885b552167f39a5b55c84d5cd5b48c7aa56eb128d4e0bca2316718cc2d77a81c2ed720f5300c8b58fdb02cfbc21c18bdf1d4afaf4a6816f6a53c18cc2956a9b4c2c9d08771697213274a7e7a649a403f1d66a6adf5d0fbe2525b236e18714e44dc931077db301740d38d2bd0af1ec8dd144c9c4ebac87611151f77963ae793aa0a147382d46f76f4a9b6b65b80870f6cc2a0010ec860f3b593d9b934150d4e8f12859ac54a62538dee315d2524bbba3416e09948044e671790a357c0b30bc80a7e8b39bcd05e226b574bb39b3a663a8f17e44eeb2c6525c2be266a2239d33b71a963a617ad88c0210ff25617b4f60bed50cfb0899ae8a7a3805c39a71b25015181f4e3a428ca914f2c822436c1e250b3e81774242a894b2ca5547cfd426e22ef0bfb28002ad8b30b4891e7312e757eee527fa1b4db31bb0883cc4a01c8110cf23796f3bcea1e728004602b81c1a548e892c45cfb429b79492e5d4a29611c34df332e51a51a31b8218874f2ce430b7aa78fedf6d18f391b415c7ac4e6537504f7e9786bfba8908cf96c6cb162834c6e68ad11cbb4675fdc90824f97e8528d9d8e072c886dc7cbb64d6e87ec48e7a95bce49916e576ce77a3e783e7437a7693f6e7f2e54e2529cf2ed1be7a1fdeb39b87a4e7d2cd6d2b877c5f3ae3bea8b382a1bc6656933b7da50980d793b377ef4083373decea52073dbd66d5bf76d966562dc62dc36df62c7c2e9b4511ffa03448b341855f408156a30469c8883dd70ad69b1b1f86c4a486beff53c779ddabe9540972852d59133ddaa3a7ed30aabcf6af561d5a940df61dde1f94939a7945250e78e3fead3627445f810ac82138d006d452a4f8cb288f4414a29e9d1e6ed7449ce6429809a63aaea5ba61d613d5a294f2e35f205faa248344945972a8e8c29c0cb8f1ef5e05a19233f1aa54130ce33867070706ca84872befda8818e7cc8d9224ebc4e119e0feb912a5495d4c90aa9d123358bb6e09b466930e43af626d4f56c0c13a686524f7cfbe6d3207f3c21fbba522384021d310e02bca53fcdd32ca022f7654b173746a73ed42715c536b5b85c57b87b7c9ea56496cc74279e903d3b472164e959ce74cbe3e75c8c3c3c564d6f70c697f3ab2a0fc41954e0f740acb2c96e4a0ceb6ecea002ff11d65de896fd7cd4605926361827dda92b353db65b219ef1b269bb3c23021718d1821fa0228a60a2c55b08f92c2971c51342f8c1ce3a709e7b8563dade9636849be3468329ba5223e47599b8f23bc2a5051fd569b8817afd5c5cc068a88e7d49b8d8065b95f2f576b0fd7a0c048d8a5c92404610810d19b1848a868028461899624693325a37408240c4850e43d8208708135bd090384f8052a023c616992dea5c41384bae229cd8710d91e32a22a8c846e7e8b697c44aeb17448a56abd58ab210db9415e37826151db1c0332cac98e399dbc3c682c12ccc22ea530e49cffbc2e935527a1e06dc8b18d26c70ea296020a932a8bd84d91b3439b25692527b89e91b00cf2e23543c7d7619992208d6f210e286dcf3483de5a23933aec194b51956bf70f547dc90fb6e6f6d7a7684dfa4f665dcc7b0848fd279a49e9610b128189b44343422409262c60a8408b1240a0bc628a28730baf0212208920ac53cbb829ef8d3b3cb0893a127ed71a997013cbb827c8cbc008acd36ab6394316a9d49ebddaea3d9e794de5d2bc5a695596b9173de4e6c87488282031e10b929048d58a28b178011451453c4b03aed831818115628288ccb422b476cba60d69271e5b3cb880b8eb88c08a1e08007c42e04a0c592305c4c52805861840511074694f8767b3d008871833e048bc4d85ab6702c5652e0576c637439acfca88c362fa273741bddcbc038434a4e9d446890a6a0389a9652028d470a10150ca38d09185a7058768df0a9334608910227eff2ec323284afcf2e2352b4a1cdc6d265676c73cdb74f6b1089c966d6fa6c2e2ef88840106a694e0197e806586af930c0526bf3cce3ab2c91eb10e0c2c8074c82789e20d26a26ce18022445921a30c96975185ff4c4a02988213f9650d8a14809a02078b1c517cc3140481e7c760195e081a87876013579996717912bfeae70bbf59b67b62e224fb828fbe9e932270947d1958297232121c4f890cb705c4478f8909b0871f434ba3bc2bc183513df3c2fdda7a5186c8d331de9e74faebb69ad94ab01c6215f7a2a9ed03035b8210cd10b7225672e7b1d330376cc0fe209483f552baf590cd3acd0ceac94f8b0a00641bc40074c2de9ab9ee196740dc020f18081f1800d3066e4b6b13f9dfb93a1c1e6543c21bea43ef03c4a954a7ae8b5953152bc59f2eb6e491bfbf473af892c6ef846fab6cf1d0c8b9221275c3c4a9f69115e178fd1b38b87c8c5f344bc00a6023f6f1e0c4b76cbddba6a289733e3ca59e9d402e39a2e6791b9cd38215ccdd9b54fb39e65ceaf69f69b0ddeb8c52a952a01c643fbd353d9b573b5838baddc29654f1567a5605edd7a298c257f5cdd56c72c16bf7e535691592e9e60a3099fbd4a1bdfb8443150b231d26e479cceb5e036639e7d34cbb210fb5c8acd99c596cbb8ccbbb109dcb4388cfbb2cf6b70f354831b00885c98df1c85666d0ed32c2fa159996f0efaaa599a6f7ea3592a6cade2cff1dbc6f3f321133dccf6c5c045fd45112952c2a0a24498691a6fdb9c8db25f967dd1a841ad5dcbbe88b4491caeafcc797de5deeb5345bfd056bf1efdc6200d6a2e771ad45c061b4de3fc4eb9e1eab5ad9b480d6abe455ebabeb5dfcbf2fb8b472ca3f915e366aeb96da99a214e8a6486f846af39a60a6de2910635a4d77c43e268aaa250839ae6a72257f378a439cbfa85f65e4ff355754df31c376c188bb0a65d1d3188e61ae899d9d2348ee2350dca6bce3da331bdd6f49a92d734bfb2fb42ab691f8cf6c5d0dd1552a45009c3fa169db3c885714585c38ad390f25b711a2a1f37ad95956fc5a52a7e2bd2e78c2b53557d45658106ad47eb3a50a039ed7035bf53aef52d722e3f9620178b7f1d539dee878a8f724cd539a3bea0062de6398c03738bfa2212dfa2189773cf8b3585f9d8178558c6fa5dba9ca32c2a739a13ce0f634ece0cf18376f868f0c65bdf90c0e2ad5b97aa8893a97aace05a8f426e55e09f321c59bfd65a7fa1592cc20751495472352144cee64b9f9ffd24a5947ea1f67153dad954344596810f9e9630155cbfed5e3b5bb03d86ed01beb32f5cc0f7177a1cbb1d766a736ad3b318b31833bb6d2048c36800a77a6eb85a3120739f06a5aa695bdbd66ada9052b783b7e39920292929c926c998a694b27853b3a2771719d9cbe29d62dcaa7097420738699312e8560d6f49b5a45a5adc464b8bdbf094caa505cf53eea56cb470316d924db24936c926d9240cf3ad258c1b82df9452564dcb659f08496c12e3b8df6e43887130806b8125e52c2dd0a8e134fca6b5a554bdd33e322695f296af775caca7bcc56ba85c5ab0e135dcc6d7353e17eb2d5f2b1992312ef68b40b76a788daf83be16eae28636e9d52ceb1dca196cec5fcbd42c94b767dd8e155b43d52d492467acbd44aa93676c82744b24db63de2c517f0dd56ca5fcaa42605fdb3c9444a81a1eff881a9f8f1a5aa98f65cc51107b0c621c368c9f756f3315f652731677a15b2cdea96688cfa992300e7316ca03f3ee56e027483cf56e699a0b0b34fca655436b6b8d85c5fac7b1a8668b45958404b84543d52de92c1eff0896cf470d2d1a5f5292b773db4609301593f267f5809cd35a204c608f659ac506777ae849ceb2eceb5129a59452ca959116a01622afd7ebf57ac538a7b5f7bafbab9d467ec843b3bee46a0582617dc9faaa5e10378883d10658a8e089968f1af875462b09cd6b58fd804513ad2442afd6578d21c6c14372e68518832367588c00f0e3f6e0d270169f4d184e7dd9d0b72c2a97e827bf69d150b9c4af051acee2d159589cc57da09cc6e72389166838cbe763c569b88fbb45f94520661be751c5a29ace3e7c24d1028bd3f87cd0709616b3b84bf7020c11087b7c746ec91a5ecb68a8b8c5a2e2168d1eaaa8c338a243e119b99485ac92144e1eea52832d6732289cc52ba94a7d4d1eea8b653a336299f955a6aa645495b020d84e7dc99846c553fd301df9459c1e0d66e0850a308bb2e617463a18070d9dd275747dd5d7b74b3aebabbe5a80b85e41d2d91d389ee07fb096d2281b6371b7e6a80a9d6529a08a50d06183cb5f001f00b3b08e28f4269ccec958bb648c599a64e1368d731ddd9d4ea7538742713f404b869a1c4df7234be5896fdfe64cd6b1e58399e880054068d54085a8b43287426bb1f7cd43ad8039e794dda9a0eabf1dd32e551c48b19edf9cc10d3d12bc4a67e51ad96bcf3b233e615f3d1847770f297bf6e9c3493b9e35acb821bf7acc7839ef0599340b60cba4a77cbbe700fae9c1322398b2c3b70d56461f96e1e1d24f074d839ee77972564a2b9d93528a6118e5578fa114c4ad5bce340944aedb951a21491f3ff9b563d65a9bb90bddcaea0f15a2409239c64dbabf74ea733db6d2e3ec76b47471a5eb68ce62630ec222c4a284b5f77a9e3bb85a81a0fbbd3ee9c49133ab150856d9cd032aa6b4a05503ed414aab73ee7b6246cb67cec4a141c43858c7de1a4f6e38715e326018e605715b29ac2a7f1be60edf1ec6cc1d11aeb834bf597b8bbcc7bab99ec29bb70d869ae711c05a53a7ca0d65f43aadabc08520fe74cdedf4c88106bba8594692335954a48471446f9731e0999a05c7b47be1452f7dc8468dc5c461a389c3325d8f5eb24932e13c0d530ba7af85cc65f685361502307cacb200a695813377a6cfc4993ad6560cb318666bc5b0da611df63371260e8bd04a9438c5a93b7d5cc6d5a1ef16326ef72157238a1bf2100e0cf89e711b6de01372441318c7ec1918624cfcf9e6891d9db8211fe5341437e4a11860c8006f8059f488a91c718c237e5329a5dc344de3348dd334adebb4260d0efd002d6199e82ee309b4093621ba51117de2a3870cf8de9ca0b43e1123cf580faffd2210eac30814a2dedbb952b552e23b9e7eebe28691068bdb1e46a0da1da518a558573a2510501dd2b938a82a5c54cd707a1bfb518424f705c6211907cb8b31bdfa8dc80137adeb9cc74803ca4fceadb80f1a92b8d76f5a2b5725c2145704242eca69e0bc731a4e8ef28ece9d4bfc22d039e72e741fe702eaf4d1c8988eaa196c2ca5b4d9020dbe7074431a9a4e070b9cf3b7106f108327adeee356bb4782cb08ab0278d2e8056de862612f83bc922e67acbdd7f3dcb71edd8ae0014cd00a3d1f51c775c550ab06ee08a496f52f2a71d4cbfa057790b4029aa5eca28d202ef721bf5e90a107e38848a29211f009b1056c427b5c82cdf88e5f148ae24e0fe34ee6d65dc83eebc29531bdfdb87ea3821b7a929000010427aa6841f16a62890b2fa2e021eec49db81377548858ad5895b556eeac571f21a1a3ad898d61423a99a5945232056a909962e192c2e9a5f70ce637c5325d3f0ec80de910188bf8fc00812078d482ad017c1221ca7d7991de3e00129885f98d2ff23fbcb40a98b754759cf294796f5b6f9bc6b90bdde27e8096b0cccbcb500552949ac27d6be9c38ddf9c2a9c5ebacb198ee35a8cae4b0ad84bcf9e33bad14b70436d481b3a2da198308b2a699032c160b787a730be67881488095ae4dbe9103c73e0ce322c6540a1048ae80428229669ef8fe5c532ed2ee586ce32edee5e54c5a04b1ce9522ad5608a89667e83b9cddc867e86c5e8957e56a54a95cfdc461037fb567469882ed15708de90328e10984023ada9c138fae594724ad9b3db4195d0251bfad2a950079fd4a804ae964541e9d090200800c314002028140a87c32191583c9cc8a2a87b14000c7f944672589a8983510ee3280842c618431401000000080062cc140dd52000ba06eb06df284aaa410cb617f298d2cb5c888d91d9a4b125379085312ef99d73d193476b99d9288f3a377150b699e95afba12e658fabd770e50d8faa3e9ceeca4bf145f4f56b7d3531c02ee2ae4a0b309a359f39533993432e5384bec0a79e2f3c4eca17cde9555141d7bc29f43a0a00775399117d3f854cb68008b28d5e553448c23a545da9f7aabffc9d9dd2eaaf5e6975cc7d67dc07cde7b92aed6e266e39a6ba4709d88ec885058bfad4b3530f9db2fc4652396bdff076c77859e137975dd24c863627dcdd65e67f8616fb7d02738158ccf744ca7cdbaeba2b5e5836701655f4fd96833bc8e72bac06d5b6a21f59d03745771ea3a49ad00a51e060216f3dad44c97cfc858a234d7ed642fb2e668d6b58006071619990291e3f2a1cfd2a874d50620dfe80143f4bab7cab15a957ed42f4df51b1a047536ca072f6934913024995e5e58028077936959eb98e545f93ad29c5c9d891853d3b39ce0e6fca5966942288a7be62bc5691e58bdef225ecf286af11118cdc65bddc7557b95dfb1282271cf02e9dd0d11cc9d75501c957525599edbef012961a7d3613111967650c37a024cf170cc0b4d10d1aec659a44e7e34e682579b58441cec4da99d5646c0eb30519def70ca72fdcb1e022df044da3189ce20987e4c2105dc52b66ce2a21377731d76b79957ccb6028178fe0456b3f374748c1af82bf139804d1ab5d563d4104b4c41535403445b4251a4046b982c9d8590ce5a59d96dbbb047127033b2fe4a6380011df3528fecb1213d847fa581f5aed560006d8596aeaaf166725e801b6f8dd15f17e87a7af107ccbdf1d515f47c7ab5e7967c30065a430d2ad3c012730f4bdeff9ea5be897542d29b4cd4774c60b88c6c6ef8025e7288b8e608d073103e76807a37841105d01772a153d5864e7513a1bb0b778d2b639ff67fe50df0bba5ee0916ee804667fd09ac07df60a9cfc5ffdbc808c0a5d0f46a8864d80b34efbe9fea886aa96739b895bec90cdbf996272891fb55cbe25051d9528e80ba91ff7e2cec8dfd35628dc7b94b775b81d1ca349097774b5b31d8abbcf5b1afa61b43860fab16fc74e9bace8687c2bb87545269e8a595f7c2646f4d872ed829f1cf06898718ab05897baf92f9e25ad67c127ea2daf097b49eec36b70befadb6929c4fdd3335a14349aaa6479ff313c49b731f67ece007a2f18d07021efe381aca19efecefd7a4090bfbe34e1e39af2c151f84b21ebbd7dd82d1a3cc74ef6e46469cd52ef93534d536c7dce1a333d7cc5dc4799c178d186afbd42b694f9dd86301eead700715daa4711be075dcd83b4370431059feb944ad9ac5077364207347c8930eaca27d4295b6a020a8f4838ee142648a6c1a351596453a6d47c4234b7cd0dc4d3d0e77df3ad5618038b2ff757a54dcb4fb0428ffa8d6655e8f1ecaefea00a0c16bfc57c2557a47e58ba46e18d22c6b1338e07534155118b39b98255a6c210416033d3abe5594a7572cf85aa8e093f54278d86d48857f8c10700639b7009eedf915c224798c2831a699de0e390053fe7e882a065e4aed511ed07e4a86134cb971582694e9e263f1e05aa4aa5d380d9cb1a1e38b41803582ef6a4dc449d771e33da5420531a53711f8521247200ad10c48befe30ada98add6d58535781ebaa4b0227ebce83771a9e08b3a306191c1552bf5092ed1cddd09ccb52894edb0f57e1f70b4e9fb4c4282fe77fec16686d4b5fed4d730eae9a7f04d025c9c8c6410f7e2ddf0c61242d4e7b241ed81f1a304e50501f75717ce7a46ffde89c6a1455494107f8a2513194e4d0900a9adaa743a8722fad9a1d368bde1510446a9bc80d440b868531588cd1f455a8fbfca43be0288a5563b51fef7e20e2a37f76ef2937832e9a495bc7f0e4ed2b5cb4e4af29972e6dc2bea5aae034fba6c93152f87618befc3b75722212becb97d5306484c3ded1a8eb1057aa092fae77efe97673afe8a787da9d5ae4805da238ef542c828cdff8d85a076a8efcfa17bc7b1021aa5610597bac4c7849ccab7d2703df3b07b8cb88b4ae695f4b0a9955bd741f215f3556ca483e948d46cb1d17318f1edd6df53ae6171d4803f4d209abd24a53ac4ec6e9c50b4cef25eabf6fae8367b10da238a6557f34edd38965b28fa4cb48656edd06a9dc7e15f74655a0ca9fa5f7e4d9af3b5ac537a30fe30d64b019e2e80ff8a8d11732540d907467018c34fe4e4fb0b5423e17da649c0aa6f7dd84a0381e53dbec249f103f41afaf1d3f08f21323ade1c6c540653cce37bf5fa3a30965c3b78d33543bd474106d638ecbc620eeba5cbf7e17677612af57e7e1d7a54a35edc8944f920d77b3bbd382ec3aa331d94229006309794cc4219968e29b548221a4b98ac1c6bc91dd85f6186e6a52b35bea234d1a389b0f6990c70205ece5c3e9bb9009c93c963055dbd589f23e55eb0a48981144bad47e0353ce52c1e5a3412a193060b443d9ab486f48a667feaf001933ca448f3d35b44eaea3362765ad591d4c8f43d717043f141961d38e9d0e1392d63793779f87c57ac1f9791ffc04ee34abc3e3359ce6480a9c7fcac54c272766c58a9745e0549da2f4bad81c625ad8972acc84cd90e3932151a4881f525e8cda2bf419d6159497e569ab4028b63b4b436e04e85d8b08672ec4a94b0b83903501a7f4e40681b15bcc66b143c99d7aebcdacd2cafdba7ba4bd0f43d7993de720fbebe9a1c079a95d92890c96e42544e1c74e50be17549ff4d582c2e264d033b8839754c8e42fce2033ebb827b021d4fbfbb2ea3512f8784b6378007ecad266686738d01e7e8c37355dce91ff6cb1020fbe14257b1d36c0077f7f0927fecbddc93ec417e7bf2136cfacae8196d427f6cd8f953dff39b3d57995d2d99dfe00834d08197862afc2d20fb02bfbcbcc99c80fff70b5e01d9ebeb260f5a5314a6c55780ab744cb88bfd67f298e723e8a0896f346e49f485a3266035d4ec363ce973fa3e9ccfbdf1cba11d2a838522bb83a3903e94e226978e39c266af7a4e61197b1bd53de1aed549aaf39199847f96e00b3cd13c11ae6ab145cee6593cb29b4716b366ba3fd533ba9d4dcf4e14b79f65769923d918f23b44d7c8b67b1cd7138d7104b025cf51ed52d27d29a5371d9294ea948976fb896e12f6e943bed65169346aac62b28c8238dbda3123806f4de4a034ef272acf33c8b8875802035406602786b6fe863b3965230b164663d256412cc6a1e1b66c8781ea0eee4cc5bdbbe03785d1b1577eb461b03d812686114b9effa7c0af6b864787891768e1aa63fb8e33074d7f6ee4cfd173068521d01b0e504dab812b7838e2d71bfc05860ffa66a71dc9c0fe38eee4bbb7d5c8a5236c97e730daf5ad2cf144819d1743bb5563678738bc2ec9955791c41805cb5cf490adec5e99ca84e5b9bd8cad0c8e1471b1cdb295220352b5b76b816fc7630acc88a08934c5902c2c004814bea6d909a106a7574e4e53718168ace3a03589afcb5aafd191d44987e0c57e5cf25c52a5919af11c40326c02207e83334d30d05dadf3b71d1d0e3b91b15fdf2f2ce208d173951808cb868a3cf187a9b18c106228a230f747949179aa267529e378b2e0cac48ef7b5916de8c4200c256183b6cf414356f4146ff4328330e51dd42cc859023b9735d77d73b7b92fb4f3ba28824a75948b771aedc69851ab62a6b4b2f0a9def4ed9cd39295684592ca99cda4473c591eee533b7c8d5e4e8c2df32e0da574f5fd82f5b86bd0b5ee60417ed0af90caf9fce809740713bac94aca111abb56cf14fc8dd0a203252c4fa80ee5ef24d5ae8befe230996c8c0d02a39b06c627ea674a427d1053b172977ed7051fefd56f235aae07b4b517393579b694453f8f3c1b0043a7374f6858b71064cfd52bb849d9f289255c60dd2a18dad502faf8e8ff7c2ed8f40081e22cce52386aa533cb65d568887a6a23b6b7d4cdf1d7fd9cc5b2e307acc03cde1543d3cf1db643217f9c62afd4e4006166f1ecc3869b4016ec9189030d8e8b2766a62b25892be420c6ac8e784a7d5490460ec3f3d631e5e26951096529fc7257bdcbcf41ade737590d32147ca8d4bc5d63103923f9a24eaccf22c9f424791054967b1e98405368c4f294688137468b2de8a9914b8b9e0d22be1ad48aebe605bb3239917ef197dc15a1c3b625c5a939f8fa24e2e3008f02e00000f7593f469336829797954c60e3152803318639bcf8a1397bdbe0fe56794bdb22e2036dc1f61be31ed302abcdda8ae2ef4f6f62d484fedfdedd8959b1d25e60ba5a4088b5319b4786985a5089959b00e0ff4ead7c58c50e3654506e701a2203689c7164f65162cc4dea4ed9f5020465d7088775bbbf412060580406ba71a16dda903623d1a1cdf8999dcd88aec7d2112900f05b4158267b361a6c9950e109db8161ba2f4ae4ca6e52ad0e85be8e61b1c8993e09ace3b52079e5d6b1b31f76a7627c9958f7ac68d8b676abf4b4774d1d4e918a10a3c46d541d9fc865d5903eca3c7698eeef761fa6b1c149995cf6357a7634727d46e0057817198854295b4047c5a4d1c5af3352f627f8b7af5f44a7bbed494829533226db8bfc1e8471803d2481f7102f19fe816d1ff6e50ce68e046d02e138cdfbac18c7d6e45be68f000ed550049722f6edfa3632e7f25b9e3fabe6d072901608e716f9b0e9bc96f5f252cc2e3b9cc9c74b8d9c5aa225dd91e99216479fac8b6dec74a746722e2918c7f19a8ad596f6147e894fdb426bfb189fa16c89db2ea3219e8ec00ee0dc70b2ae3bfd6e904a01dbe985da01b55285381a20d13c2e0a6f8e3e4ab47832492438520b79b8c8f36062d02f48b0d2c58da70fcf7e5f15ad1fd82672e64f856f44886d7af306bd44c8bd0c20701194d4b0fa8311e8c46ab770f312902a6cfbbdccf4981b441639f9f1133821fc6d09578f490dd9a2852a6da12e760898e083a3dd39c764001725e852f21bf726883a6a6625e89ac402322fddb31abc8cde71cb4a9b7bafc7f951a81087f10ea78493b4cef9c5c16dc5c2a640f8d92094d7e28bdfc34749d30dc2f2ddaaa77350cc112e3d5a9e3d40e2a87702c27ef93aecaa3998013f2c907b1cd6e51522b0f8a32ae742587cc400f57ea03f7d46a537c6916e2717120bc0fee888ac51a0e99518a466caeff495dbec74a8972815d8c635892ab6b4e223bed74dbfa3f3161112a9b539b6feffd5fe86159cec2807fc36abe4ccccbf46ed87b5f27fcb8ffa41207617d8b15f5900abd69c1755ed60607c282529805d4609ea78023e8b8c1d270047fe87f629ad98f270e06430519f5e4f7a12e1682034ddcf99425ca9004a8af0077ff3dc3408f713de1c68555f2b80c5d5d304f3947a35e0de5c65166dcec13731c75d8b8a96dbd3e5bf6ce15dd93b2a6652b9c64c4bfb23d0510a854f6a87e1f6f9fae438c1516b428f238f7d8e6b8cd09896c39ee2a7a7929dc3ea834b92a9467fcd56eedf86e33dc896d10eaa987715c54cae05ed31d273b6855b31c475dd47651767e921719f49aab17ae27ff8b456874b1b445cf51d9b0713314cd021759759bfcf88436c768a909b3598d229f3e135f8393e4dd981ce15dcbc658977bfee617ca2bbdf3b68576138a00a881d4ae2e7b997d0eb61fb03b7580a92e7febf94f9915fe326de5b0e2fbeffbcfb1ea8170dfbe06648d06801900c080452252ca7ee2f9ba4d4a4fb35d930ec8e57b99c37c043ff16411f852605f4752b680a947976dde60cfe2aefe4b456f4318b6fbb01b5e47baf637d41913a61f0636e6ca200a8cb4c6f76d746d000e894f62369b86ad3583873d7c2899231430e0c083c87b4916c96cf7c6709aa2656b045c14afd4937ead0eee54f9b0fca9240c48e4ab76d3f9144338c820b98fecc0c08e38ef6df4e4a7bd1c2ef212a47acdd945d8869e44f8a2fc417dc59a009313c1f630af2b4fe16d1d7bcd0afdd23257ea38f2ffd5b41c94307dcd39a45dec261f111ad57e095e07c83107edf22cdda4e4e77640bb2987ba924b6cb131e99e15e5e392a96c4e5d91873c7d06b279614c215e299211c879f50b36a3e42945e228408a528056c4d442223326d4b075fa2a01893b1c3f8ae0327dbfa743a40c1669de559cb4865914129f97b8d7c70b8f0082eeeb9c02b809b58aa5ae1148f46c59d21636763e2475ef773de46fb1f2e530efa50a33d5b659b1ed00f33b28268d1faffeff22a907e044b4a35a25d2a09b1acbd84ea0e11aaea1cad4da53f91318f46e7557d879bbf8a9717bdfe85c6ff27c758e450524b08fd907467dc0c1735da94fe040686c0cb260dede8859d3350fbff31815c9a532de275d6c9fabfeae96f1848c3c317d42814dda889632ce35de835e7a8ea34eb040c80756eb4810612c790d2da34944819e15b698b95ff979d5ca23455fbbb74c27512af2d90cb528eaa803c1b34e0baa961ec37813752f0530ddecaa7df2bb376a9f089bf41aa533f0bcc9061ddd48a02680557f5994c50d2f47bed729033440bd4ea93aa8b48e06f255b90c2504e3c7ad574a23b273a52ed6b80402c57610d07cb708dce1b43171ab852f10118728c1a95a88b091ee7c03f019e01f83d4738afb1baebf2a4a06d8eafd9bec9ba583c46f7976421563da75b476e6fee5b1842de5d6da9307f0be567b7a5ecfd0090d957ea68897b1af3497d0634860f43e19a037d29142eac1b85d270ab3356bfa92f644629f1569f4d9cc506aa73897a581d5a9ca72b19a0fde3126eab12013fa4fb6f9b4c97d38f028d1241171bdffda664c28ab4677e0973a311998b4ad8fa14813984c1e96b4953d66b09bee928593edc533a591d354ce8fca7dc668b73bd1770b5e60ec43926d10ca86cf758433f7d973ee745af0d5312da19fd5dc9f19a1aa090ecb89979d699694e4c5217ce0a0bcd6d113a9ea775e792d4509f200532c1f3b6de0fbf9352832cfef5302ad7c27be9912bf98ee126f37f62fb1b9ad691ac4832566aff76dabd762540b067be03b4554a0e99ef64f2cecd06d5c7ac34e504be5ec485755f414e120d65ab1cf9de6d4233f878a9d52d38f11e321a02b0c7536936667ba0b16283d42b881a5b8b7c6f26837c0946680286a23ef4b0cbffef27383f21a923ac7047250242f341f9b2adda3541031df550739581c5705b20a5350104bfb6dea4db6194f9b2d44edfa5f865ad6aa406efcbd825431bc5e95d7642545927c342f1640e8f8bf3bf78101df1297a04154ef4faa5e00343b3bd9c066faa74851b41995cd580c5128a0ea8901f80261b3443228409cf612144cc7d2e628ea71d9cfb213d95ddb6cb04bc8e2443eabb3020b62e2f439c86f52422b054f68a653f628e5cb653ea517d0c5d209ab8407b87364d2a0504d88906f1a0a21eea0004ecc0a986c3662f1e247ea3580c50a06f135332ef569276ab58696b0a86ba598fc1ef483e1363a9d3194370063aaf0a403237e2e13b6cba083cab6b086d6dd24cfdc0bb5c054a7dfedcae2c36bb2380ad57e351bddb8ce4571ee4f5c835b8f89e9bfb51bac361b18413a3e91f24d5ce665e09af2c16f6c4daae3b8daa98f92c60916355174d7d0d87b6480e20b611b16af5810590101ed8e80832d7a8e0e3a4001d8e6b5dfa8c35d89549b83c5475e1e72308957de12bb930c2a5a2e9300d3dc375aabb731f042449375bd04699aa9738c89cd59978fa6127c8967c7694cfdd0f920fc470e6393558bc16e705cf1bfa72d7e919cd6a9b69aee9963c6d71659031bdfb6955877d22b6b42da021e4b81ea2c3dd09a568bd1978db416e1f5d6d31d6a59cc9b52c6d77b1c6d2e03e5057a6efeb8b98f7bd4ecd9d5f20669c4b26e4642bca767e934ac8b9a15a700bd4681dccd0ac2b3a45a10ce201b9682da57560479f8325eadf5b93d85b15bfcd507617393a2feb9791333fff840685edf613327f12f54c847353ae571259a0ad15f5f4f5bdd7bb570c14e8ae417cc5f6e1eead356b85bd13a397546e12dda53876a7995f779e5993f1c990be3bc508e98098d485a06bae1cd4c56815cd69e54f05e2ebf5f2a20e84b50440f25e937270b878974f9183dc485fc62ad1f02a92392414133c5a3382b469c32eb05b6b3e901d4c21413ee038d2c79e6a6b609689da688d511da461e838d6b2983cd83d35d58950e65ead9d2464243b669256c7c1456d88e44bbdd8d0e52113db4dc639b73be10de34a74efd7b0dc8abda9ef648090df6013ce212afb1a718d2203f55ecc40144cc3353489b862937db9be37aec6b2414bc7474eba80c05e84deeddb42d3bc4ae0b0362437b72e64866f8b8f36229d5abc5b1864bd4f88be53a4caec0f8a2b71f9c6e0cbe49855498517d082ba9f0068b7d64ad925ef64d3461a20f3cbba8631fe9baa4094faa2f93005bf7839c8137d004662fd106f70592176459a0a20437aebef6312ea170d52655a7c5ced9b2c216d49a67c9383a6d0f8a52ed05b74f1663a4c2ca11a19a9fd7193e2908af7cf447ba52f00ed9e6b65b6e9d21e3209e1419f45e651f746dad11f1fcf78a01c6f87cfba60c95b48d665df8a135e983a1fed63c673ab3ac213b5f18abf079a8555794041083420b64fde1382fff5844a96a1696834a1de58abfc3f8529903fa22743a44968543443f8a83360b50540334e104a5849da6838dc4656198cac6eadef6200c0a04a9f9215812a633132c9c976f66a7493decc7c42c40d3b7e30dee55f225b0c51e625ee3a0a569d3d6b69462ed7b96b731127f739a31631ee85ff9e86d4035fd038daeab31ba39d93264cb364841f2ca086fe4ef5dfe036e2f456cd234b0c2237c002a476d3065e8eac8312b54efe78c2828308d0f0cb74409781dd5eac6102ff5f0eca5d04cb412a888385e579cfb2fa692d7844d7b1577c949003f944038fab083cc0873345264391ef426e8c3d72c8b639553e2d8132af534e1d1010330e905978bd5d00e08f0504472869976106179fddfb3f81c16b5dd53820940b7df2bc2d15a02961d8cbc08b9fb50ceb0fb8aa04b7f37c0dad1f5f85be2a091bacdde220780f0d52e3ee5f9e28044f440a820415a62b277da964a96d6cd12d66807e8ea5ce28992fad95b17aa7d5f0920e85551b5275de96b818d9b3ba50206a8357b9d7ed187d366773e8b785b7d1b3c54427430bfad0786de8c64b52af6532c082bb8bffea7f882ff08c1a85a956cee05e25113819ebeaca19003b4945c2a34228c9b023c00d99a95a4a589ce79b6a615aece9fe0d300ea878bee4c4827c86359a19fb3bc4fa0bdb4759a451dbba25446e130374b04147166a8cba4109fe06939e1a8d5245d58bd190f249f20af16ab7a862f53be55ae745678225c105c3eb3998eebcdb31d24697e88264e6ce09b27ec081f0513e53444f0e65176f6eb1a9b0e4806fe6ec9ae44094b1606c1df1b171effba8b5ab5d2c39851b99916ee9539baaf7f1584deaa480aa90dc146124ec06c4448cbd67c1a812ad69bfe00d76d2576867f0a6597223ccc112256ff2552e3f40865d2800541fccbf52e5ec95a3927300ead7ec866141bda98de672bdac71417cf7f029972f1340ee94aec85f4b2955c19c71e1325488f5f619c34f39c66b3a758d3e95db79300bf3be3bca5fbc4991a1d1db5a804ade779a60d302d6b893e5338ed758bd936a01cc28350e437b34ba8f2a44b1b711663f4af02c9c827996bb10bee61dbda3852276eb3ac805e0e8cab845d511ea2f50e0ef50104728576ae2d6e722dc3741bf6a82d0fa97571abfa5fc635c742467747e0261adf606e537abf64e9b1e31d63615b5dc3ba4505150b85768f07f25c8c001cdaf46b512d3097f958ba605036d250efc6775d32e5c585e0c8d6a5db365b3d34e37311071d6f2721743249be2b2c71e5a949fb3f26325400fa86eb2662e70a4afd5457dc09bfee52ac5a69c359a424f069382002b95cceca920d9a2a5f3a4e6ff4c071068a47ac04a59c369bbfe4cbd110ef57c0add0aeb7a33eb20a2db15305906d6920413712872610c780bba0d2665ede2072e5255fb74c0611617acb14e8a43789144dab4476056fb68f1bcb8aec2df8b5ebf8d96acb3bcc5adca5d0b3121da2077956473f1dc9aaeab92e56da2070d881340ae42d2dd7981e562d90d2c43b72669a37325837657551a525b420eaf48215dfaee8612a75e206135945f909d2d5d2f707c0caf7d110483cfa7da2b11fc7ce385d05c68554031fcc3aeeb0675eb198f460ba86875e99c96936b1d87f450d444c7798a5844793b14b4ec1705c19e6591b3c4532c170f94ecff645416cd015d7aaa65d3d41065d5cdb9adba91457cbefbaa7e95ce1cf1cb5f613490464bcf843204ca8a859376c76076dc3a6227f061b26bbaa5e65e35699c898c67ac62fc95b167fece26189ed1eabaa4223cf0c6518af57a3a77577d2c17a3945858f0f2d41988f5f357e43e68e324d50889f036903f6bd6934dc8f0c4b64add0a74b830f0b7010cf6aaba73ce9d5ffd69bf3105b95aa551981872749393678fc0b6ef2a06c93755cf37f3f20c9941aadde5b1f9b142a4f03de443219dbaf3bcda2a394efa296f202c6ae94a8e8d42c61a8a033efdcda689743cf974ec39e8381f908ebbb779bb80f73bf902f121283eab686ec0b23147d41904a477b6e19ce1d6589cf9ffe2c7feb0ecbf1de8933134a1aa16fac774690a9941b2c4862264657e200410bb00d11600a0af5b8f35a29ad688eac870f99d78a061384c66bbd477caaae8d90535e5ec58e89459e2ee680b3019c0184ebb00e30640dc0388360c88da06e545e144188f6da2a72370de6a789a43c128806b696c99d3eadb78b83f067e7b452c19d6cabbf2ab20060c0602fd966b7b2b25ab8ffb3a50e0e835c5ee78d2303404bf0191e5869d82f332d1f0126ef9b921850bd4ead2f2a9737e3cd03bec83b0ba2e6c3f97feb6ea1cc8cd8e945b6ecbb22353f83b2eb1effcc71b8c2fbbb06fbedd02b12d772db16552950edfd021826bcc9e1b76c2bea9d0b90478e9d844eaf384eac167e1bc8054fb0fc9b7f3306ae9be4e5e9ce88c05ad76508b0131a97225776a54a7d502ae947255a9a9dd76ed4ed06c8429adb70556483f224cfa978c3e671ddee76503cef30dfb813cbbe8c53af63f729652dc76e389acfa2eafe27d9e58c24bc0787f330be230f8ceb8747ebd70da675590da63a98620ef8f00c4cc7b3c638b041d1f33cca28f99b48923526b136e745c515f7b784e58a9a75deff1b345559d2ccee0b49b08e8ce9962ab123c1597e04c55b6fd0fb1c429c9c6cda0e51f177625256c13223f3944631472c9aa72ffacb656511e3022aa0428e27241f9218968dd1079f9c82f4c884e3351c7f37608c4b84397018ff675bce31b9fcac1186eb422aa51cd2443a8d583afdffcff7ac79930007c788771f6df5a2cc1c5ce4ee4fc7f4db8ff4f426c3743792f28f8c5eb6bd72ae24da45aac8cf502b4cb404768029aa230dbdb8165c663b4d287ed6ddcfbe8d9f9822324d712302e6ad55f6b10534f1bd02aa07b6c23fc87d22b092c11892d2b10c5bb6d3d2878fc91d82d49ef90714e39ed8b01bcf945517f379ec570e367ce97bde662206299a0e027d06bee47b53e3b999cfbceba60f8218c54b4d2d7ae01ce39b285c5f214eb74fb96518e0f0077a4b6567b76403692a460013564f0929a5cfa09a73b2e0b488168747a20122a79508be7d7a57ad34b4213b230b29cf9c348234009bac6d14f56a82fce31d9c97a56d80ce2a3ed7d0e294f18f6d4d8c553c88f8e0504bb93d513425769a82925c2485b1a2c4f7d8ebb2cbd8f538962525cfc449f274fcb2db3bd68f02d4b7a7752d507b2e53b12f596d361227407f5b0a40dffd8683af8c3b798e4d0090a8eb4e7a9438ae7fdf91838e91203190b932f94616950689dab10a58c70cee8e02ec9f12f77132d856c9d50cf78598d7c5577114943f23a7c5e946c18a5efb13d500e0ac5a18fa838c2320890e0563b0ce08bdf2c37886494246977a406f276de1b497078ef581c9b648c5fc5b1a92ff6daae81bf6f37b3e077d5bd64f70cdf878c5c95979fefd1d38c82f2e282d3cbb6717f4036cc4226167d4d5716324ed84522b85c85d7938047023100319ca1876cb01cea87b3ba3e5a9067b3abd65ba275888c59843a36431959c28127ee1a395b850e3c410d87db3561f608f9bc5402651542a9cbeb9a895cb326cc5b75e86e33353b77d29151ebe51acd1a799bf9a73eaf98473308d3972587215cc49b14f67c141df9c0e8fa9633eeb60bb329c39891d223f28b534cb6c10729f3b217d8e08058e0fbd6585ac424ebccf1e503889ca4c3309db2d2d20278f279391259af3f97253593d4d7722977cfcc70c42ac674656919eeabac755cb3be354033c51ce53207e30b04322cbaf0002526b8c870ebce9057a91199a116f79cdaa538ff6862feb687f5ffde571a50a71e932657e4b731ebce20955ed8ce8ff85eef52297b44dee13954b7bf1d9fa59b600811a77bea574523cd6d8e82851b78bbba843a185f077a7239e37fe8e20f30159ca5d72dde56879e58bce508dd131a59f05be0fb19ff66720bfe98ee3af13381f8827f1f87a01a38d8c4691af14dcfaa66d25bb6425db84a300311ddd7448753c748fd5a7deae10ad79550c2f4cf5870d0e9f5172c3bdef40c780d02461adf47dba0f63326c9650ebada3b7022088736ce244610503496f57dd780da6d5641ba117130bb0592bcd340dc980376c2c8c56cbc2d7cbf4145b481e189cdf6088f672018ca1362486929ee31c58870c145fb77b2a88a77c5740247d3ed0aa93995556e16a6cf54ec50c8fb439b36a5f2df4ba58fc56fe24ea5c0e1ed2d02dda7574041945de60193b7e6bef0ce54f8272daf4e0e8ca270833514ca29aa6c34eda0a7f2c3006a4ab3b206ead5657c1e28998d6c0fefd73c3e70bc3942c79d2804cfba8e9ff061aef1e3e22a0e384e88207470850f26cb56d9ef2a349d3fd1da1cadfc3053d5f3efcbfc12eb5b9418a20c10a324ad960e9ce7e3e9b80fe15c4f6cb7147fa2e5a9e088e024377b9aa1d7efb13bb6a0606f43994ff5a4536915a03b0c62efe0d72105db0e432f7843e87c147f9be90d9d0b061001f3efe909129d31ae53b4c4f0c60dfa2758613cd4ea3885af81366fdd291bd5203181a810cc692e9f2d32c2be30dde755af28fa446e9c1cdb17165755bccbcb574485014a7f771133f8780fdab2ca3b694812b90544b4853fb66f2e06d4dd9b27c3fa9fd9ad763f54ef1a2456ac801155d496a87ed31b14ca8b2e65bdb21afa4335620e4199f1c01a95d1a7f95a630d9a57b318aada9d6aa88f752c6fa74e61643517c6b74452a7ee5573c38a954bc919b75e4dc91aad4ca09ae8437285f15753bd7aa53149809cbadae0bd4358328bed8453db872b5b5e7943720d75cfb57f19c0569e66ce5e1f303491f8e70191452c0021e68093c996585c54d7e5c614337f871353e071ab7aa3ac2bc13b5d531d200665d20ad4354762192299c3ba021da4fc92a6a60ba1581537322f10a91ae87a7111a018f4b2ed53a8f3353aa00b7640a59622f54f4504e54b6b827ed46e343c9b393a18e10709051f0c565d656112531c28697cd18223517950008c2de9b2406065f66b27fdfe2f7652bc91d7784684d608d70118dfeaa2849cef3fd7eaed4d65fc1db2504cec0a17e3802b9fbc5287adcfdfcd0da3a08a36c7275bf723a5092d5a43354f1dfa91b7b81249b1ac483ba0b3bed162c8739c2194dda0e93c5403a9525f934f0ed27f33a1004451add5375c38182280ca59dabcbee0852d337f8eead25c6294a5a6f27c57ef0ef8d8d683831e1ee9f9b8d424f4d4510b39fb4f6393148416a1ea334db1dc3ed02744fb2a0850560fa51f1b3f3e22f531e83d7148ca20e31b8a3e5f89e27b86814e3f2350363afd7a7ea6881b8772720ffdebf999d935028599b8950ae780d767b4bd10e2738105e04658384350778f018fb4f38ffc915c07e88b203f7959f3d464cd3168bfc60d9e8438498d8dbcc9fd81e0f82c1dd759884d33cee890a180713ff51446f2b2a64f0872d915bbd29e59b742ccf79655081253c656b37136dcd0b750a164b8a141e73d6ed9a05fed4896643a21a92abe392baea8ce1d805748f6b9699309d10478b4c9abc35da97b7b95611b782d817dfe578b7987cb494c09d5146971ba5348098cd2d23318d1c500635d3ba46bb8764c149519be81ccea60084680329ba50e56efc8055b93d5cf66df3dcb083deec5eccf4dcdbdeebe0ad6f72231829b9b0ffa0e94f2b21b71e45ce34e06f2eae492f164ecea7ad5455b8731ab803c9dcb9f5b73bb039332575d0d8c7030eabe3823a39f40c4373805438de90777f91c14d803e1fce741635d55ff7e3347757aae47607f3a08af5cf2bda80f3426bdaf9d99bbcf67788adc8cd70e4640b16f6cfdc12965c447bb58b9270e3e29913169793187dc5bd5a94bfe3d9d67a6089ff6deabc7faf94c2fd6f893f8e1b289877b151c60013677cf8450700b708899ac17631e6fe339b5ff5d41234f6d0db81c9b7f7f0c8e22b14daeea0d0794b7bcc5d9156639c5f769bf3184a272be10ee175945a838a5744583b94c5c83b1a9bf65899de7d6a16ab06d6fbcb90dfa94ef9aca1ea36a9caebb0850cf7a49e8a3fefcf6d008df17ba28047b2c44deaee22dcb43e0a505946d4917947e936836d356e804979111bcdda26546129b86e79c23423e4adbddb4a45b59f5b41ad1ebc146c92deba371a53045e9921281b2a3b3172e32e4c3e668a8bf0d3b72dc273eb0fb85054101858f515d264378c41db07bf46490f3c1e37c782da1b7948838090217a0cb579313bbbad0ad797c6a23d3fa208a3534f03dcaa42332299fdf3c8e7ce3f05c12be52896c59f1c1f7053ae879c8fdc890ce21d58d06a954fe43e54b830df91aefdfa47def862d8c747b0ff80bc18d581ced0bfc5262640f788d9a6a548143425f73687b63000d7c1325229b858fbddf12e5724aabfd609da5ec72fd92512d906de9667ac87dea01a15f37267ae0368f52c34562456ff5ebba5c1f5e0fdfc0632c116ba1137dd23dd6e795bac56d52575d090d6995a236fa1b04541e8ed8b69e72bfaf4a0ff2801d42c7a1d03ce1b212c3e4add1720382218e4ae737a143ab290de3106702232d59cca3baa8c65ef6315cd90fbc888fb6d6a0aff6a74e345a9958f6daf6c3ac642f4f13cb4a3029b7b03ea5e0b9667a02d33449b673c6ef0a04d324b4572497c532a3754af71f70233f20e63ccb719cd8a28288b908273703b1d8728d9d95104149d7598772718e3051a6d2563b33ea956ea46d750822deba16b581d27bd1a44bc5b6fba49f20b9a834a72dde3de968e18df25185ac2680ac350049f661a2a4f4d0344a91be613d581ec162ff79581c6e9bd6dce0eebf706f92f4473521295cd090588af1a2d225d9e6958ead2176233a422b1a6dd16195a662fee2a6b448250f99121859c166982b1c7cf4ebcaad70e402a5aa5c0f9e43264d8ff8c4ede76e0abff10a12bea860651de820bc8b6030a403cfc7594396fcf9175facb89b5c62edf23b4df01e6200b131d0dc367d4be10fed366eb9a58f45eb4129caa3d9b432aeebdea940cb3a59042e0cdd4b4bc55bfb2dd112b98444d02ef79e796964af1724ce99578ebc7db2170eaff47ab6868fb480b27912548c28a07c72c317cd03383b235d075b02f5e24d43253def57fc9e5edd4211d3fe37929bab7e157bc5d24c928198deb936949ceab0d12c09c6846266bb2498c809baa06c20e6210d3464f1e19a452f9d33243111bd6c86c91e911a120c378bee41084645d8e78f192a5a8114511919868321d1bd4890d446f477db6ba46cbac89713ae33e16dc4c5a830e3c7a3c39c4a105dc245a3b9480cec167f216e45d4a09fd08406ae7ee931b56c45ac2dde56e71ff00e87bbdc445690d8eb0b17e55cb8a1e346b7e350f63e9d81f7cf720e331cc1e8ccf7a207a4118cf35b764651f7bf6cb6098421db6855b3a4ffca74f33696cd29275196e50131d34f43c3ccf1e33614c21537712625b4de8686c1faaec4f31785819e89d33d66e3cfe8077fa239590915669520a946f937ecd19bb990ee8368d95a7e2aff5787fbf979e42f6be2ba3ed2066a5e9e77791272c4fbcede04f54dfae482b1a1dcdaba7987a15f5798bf389557901702a1b869eb847d2961691f41d8d3f0ad05a458516f495d944ae2e485c696c85a3cb146368f393e8690981adea710b8f26b191c6e7471f0e7e7523d01f9d735013b50e03bf48e30bc15f1d282517e8f88b7328de6d1e5c6ef25278b7c71b0e88c6a0ba08da4b2993dd608afdf27aba945b2758d923a9219fb408d04bb24e48cd5c415d4cd096a7950d6b96bc3ab6250ce00f7868f487e961eb35441ef831043485a659fe23cbf618b2c4a2b5d38b65b1a520e1d8a36d88b7fd22f3498cba53519a67cce35e58c2f0161537bfb504c5092872f5cfa949a6c3e0c3b1390230c9acf777641044805b3bbadb472f5c1fcd445566fb6a68c46d3b4191bbed1e313088c53597cf8c065e0cb0e3b103f89bf3bf1eb4c3490621e78c8c3b9364ef146e443075b3988eaedf1f44533c0afe80fa869b127045c11413346682021d63dea89457a815acde9a2a7c84785a9a45448a92db314813045134d1902a77edea933fe14d450f3aa615712dda87c72d40aa4c8aff616012e02eb802b8533fc5d3527e0c85ab50da887254c00a84c20faad369da83e2ad950188aa4e10630263acfd0da077832fde268d07d4040b601212b01d702035f129c09ff4213e39af7e17e5432f31fb4109c32d727c4877a402e00187268111eb8a707a1a8234604beedb7087a7f60d1fb10582d4d3c4a63f782d3ff89a4a9428855251c123745681577881ea9387196d02616c87503f900c6fc1880955d02c4364a7cc16c1340bb65868fad58e2abd6b4f89afd3a8074c81e9fdbb1c5170d28f035cb3980b03e0ee09698f125d97680e890009f7464425c0db0397b5d332eef8fc1716593389800716354e5138d12d5066ba26c696a32f2d5e111c2639a7c0e8026352c8cd7acf58a915e6f4e7ce75e19789e7a7125d9992bc10493ca7e09c1a7c51dd09273e851c4e79095a37f6e1a4cada6fe4187c7b54d0061c83cbf4a0f90c0741d3107cbc442d60c8cbb1f69a4647756dedcd2b79fb18a282da56f5ba79ceb094452c878d9c5a6150bb6c0ce12c182b4c05a0f0ddc4dd5b8cd669df820fae876eb66be6b2b835eacd70862b73ce33e0dc30de6b13cb5da0ecaae57387b95497c96dbb597685db16fe17eb06b142a56a89f5425fedcf6b5b9e25a6411cb948267d181ccaa6120b5c852e6692234d97351654b26c6d2c1554427b5f39cf963aa5be1f7668484a9bb89ebd93ce8f3b3fb1b29420986c095f0fcc2d5fecc09d7ca8e8674988d5f4714b053ed944728fa15a0e10a20106d422d817a310604c3cc7b7db2e7d5d612dfd45580bac2161e8edd7b8a9c5638be4ae60577da5dfc1cbe1662cfdb4edb62f1299ef6b4aebbef028c9277a3e4033e6ece96c960b3f3d6e1df50504b9059ce820edf0872c994a465d8b47a2f4e782c5f3203ecbaeef5213cb2947b9554603380060b0a7058e44f67f085c58f9cefdaf4bff23e941d91f505c383679d780f0ae33567d49ec4644a452d2f84a64c59c4ff4430ddcd9509884c78c8813a066d429d3cfc2dd86f3bfab4400294d67552a52a66f3083674512d47385ff1978f4aeae57d8b3fc9e745f4de4d95fff56645e08bdd883436465f2595a4824c5a0987d914d057b40d299a7fe247a6b70033cfe708fb5447026ea12ea58e7eee0df8ea24e17144428ef61285c99741331a117a247ead8e5ee69150c0bf3f63d28cd317ffb3737937adb385e6c1aff2824979869adeb4bd40c322a230ba2b61b1598a929383e3936428ef681eebc404059bf142d86f43662d4f91baf9a5194989ce846d291f8b88e7602c42a6f0101177e0c23153e026b6989f196053f60dee5c1ad9b0a288fc9f842bdfd8ac354b7912762d7cb00334198a0412469e3c1554cc12822f6b8ecc02063ed455db180837878f6f959ac4e110966681ae18926653c33a9aad618be116d64098b8c8f69695a0ca2ac7a1fe4a563b8acb978a217a6fb280aeec3be9b5c61265ec58bcd6426be1b77353b34947b1d92fbe7c18bb20ff157a436ad8b91d77a51db5ee03e692e450d79c13b52a9d752fb687dbea6e3d44660814da6e593a9c0225136cc0108adae94bb3d4ee5037e110afe09c4970b88a5581ce092d025bc727b57999c8853aabf2d59b0abe0772cc95889b27de0c55d96985a4e4b02ea464579ba6aa014987432ba2b22d4019d28fd303f6a16b8e440720f4b51c503dc8a4c612c26e56224e5ada56ddffbc3918057de5e05182c4d48f77a5aa03287212bc6e4c863876c68ca2f887a3746c8fe434537a1ab0f95484367090def8df043477ec520f976981368d9545ad57776417516165a3cebe72d0579ac38420ec2078b67e0c504cc3779b2143f523fdb6ef30a8d214e716e096510af5ac28c26aea29aa9fa5827b691002f25d48f2fc9bd0386e76c1b2635ee25582067b04216d316299f898eaf9cb2ff188fa153f8265129dc6f957513050074b2ad25165bec53e7828bf28cdb73f177e86f757b9e26ac6187ce5f1bd16b504f869676b0c5b63480d8e9b907f324c6c5b3719a64a3770b2f5f96f1cfd86c8d3cc818ba9c75194baab65693744cec8ba6590abf9bbb28832c0ff54950c63f08d1588162f5f085e2a1b570b998ffab2ee046344c9017ac1694d04b8d1b143c399006f383d52792cbc5c3ed11669dfd2e81a9f1620ddba57b3994034d22da67a74538fed7e7ea42c26d7d015a45c05e94b9df734e23cb69593b44a595ce729511ae58329ca322a6f93eed704b10176050ebe6da103bb69d2a75f14e9841c8f9ab49b9362c5153867111eacd9bb7e99275771f170338816146fad39958dfd944c572dc49fab01ab86133544874b93d3866ee392901ef5d3a6b54a7ce36d7ada42c28432161f601f85a29a421962c38cc780d5608dd4c880af3b95d4dcb0aeb2297e63d8f7a29f52f2779ad08321d8b858a07e9c824db706729e723eaf0c0883fcbb7aa94e00cf1b1343902a5e567a239df9751cad29f33dbe9f8283bd029d3f739d563ad1579368c527a4de90996fca0fd973974ae5ff6b3377bea2fc5556309dfb704f8656cd1eb34c173bca2abce1d08fb34534a1e1db205fbffcd8f1269502c886720abccf48cb89ab62eb85d93ef4ec77f690c393d9f9683d8e4b78ec84f8e1745136f39936573dbb662364d606456b809fe2acba1a79c1228931d5fd8b6e02383d6e0e3c295e61a60d7fe92f05fc2c2bc700e0fa5712a30d4218f64f416fb8db38c0c6074822c671e7a47aa6b205b1c0c83930cd39f97f10bc1d56c51b1787af362d1a166f52f33a8ee1d67d0cd6cf8abe9b0001d2503d031cad50860f674800725827f0589fc75058968028c74a9176d7237e452752894cc970c6b087892ed068e1610d6282268f34e65c041446fe03b6c15bcdbd12d8d3f667f69ae144dc9d84e1ec676b17ad93b4127625db3b95257807f3636ce822a8c415e952d7ba24af4dad7b63e60139152b4c6e2890031495fa79c2ec3510f952fe810435223cdae6f076c36aa7d6ca68b6cd606cf16d606b3ec0b16ad23179d32ecc62e7d088eea309b2b48580cb49295bea6097525fd0b7e70b0b26bbcff29a75c4c24e1cb7820f1d50e73c37773cdc27525fae698aa14c9d0660e705d309f508b4fd329236a001e540de3baf3f692682b2fe8d160863a05bb693c3a19cae8b5ee19315f628b74fd316c476a93f9b96c4aea55ea5576bb57cf216f8672a261c920002a2a4d3f92ffa541f400140ca6b0ea867ac754121bd1326706164b040491a4ac5f01cbe26198a0ce37e59b7ab1df34bfbec1f57ad60d099d865c4c27df542a7761d7a2a22e8f9a77b02bf86efb4df4a7df8ec321e47893243342f69a276e08e4d1f3aceb9c7ae8d17b2560b28850bda3be3df2e1bfa52192351143de050947718827ed0f6a38cbf30fbfdae52cd4d7c3bac5694f9b17214886814ce7419350a7f4ada37ce0d2c9aba6f31d535d4d73e9e06254c43cfcc940e0b007915e6ea783a45364aa3c90fe680b562e60776978c5959d99ce7974f598543659548f005320fdee64943e903f7dac8910ae484065d7deede80b1c4855983947149c76d1d51c6d1a35b57bdd2f933563901beda3b79892a491844d71b35d93c2876e5ab6dd41776212c8a55fbed571538836e1cd31975224323685080ca36c3f33c283318c7a0fb87b82de7368615e4e2dd4521dc6f07d1fca90533ad56c1b48ff027f83d37b436554552eb7935325a2382bd788e1328d97c407a8fe37996dc52bf162b524721a9d8f58476d23f6b3f6cd4afcab782cee2db896ea319aa8c40d7c2197d974319f41beb59da1bf591d6eb2bb1d5171239875f8ab78507aebc7924e3c9d14b086c12e056232510b0aa3f8a61a6c6a9ce7ff167b7daff5b87b9798707ef791087417a74af1844300a0040110521a41071dc4a97168037aed912a262f033961b57746b35a973b6e1cda5dc73c9998448c2cf57a67c809d941adc021ee5b8458fd624a9b163cb22095cdef7a43f46d8cee3094c16a8ddabe5f382fbab1569a713ee2679d3c436065ec1e8fc0cad121952cf8c19efb4f53a73b898ebf46449adeffb12c502103a88ef94506be50f0efc9e569c6ecf019f61fa32f42e18141df0c822c5722bf8fe2320b79a1b8e82ff6a7699aff3caf8823b97265f57a2ad016f8ca60c25fff6df9fd799d835ed1423505a853a828c8a891223f6cc902c0e96e4dd968c5991246a2eff33bad00db715d96f917c07d1baf0665e5682690c331f090d9219184191870e32c89d7b2c4bd80acf50d25d8c0849c770fdc3acc56c7d25aec2c4039747a543f3c2caa1b2a285b9ce148dea2d29eb3e56e1782cf9c0c06f94c8e7b88ec0e7241762c183f1d30f959c5d101562c964954a6d584fb3137b7a7b0a2f38228e42caafad812896572c538a46cee60fe04d6ad952ec766906a2bbdda3cd09f22312ec947108f1d41915ded77c950995d546a5366cdd613d6a980a014e06a2b86dc14f931d1ef6e8943a9cf4266a9a29794304a3102c5a687ff286bf59172dd69bf227497e0668d3e63a063ca8dab91821d52e528caa9535bc71d8c3570525fd8c332dac86712438ca8e1496ab11dcbd7583cd621030ace92bd8634b92f98e3640125fae94a6199cb7198d780ed54edc47df6211cfc2035a7e0130ae0440cc3be72cde60c7cfc0315531b9e47bd4c1dd8bddd01fa90387c2a2e95ba1153041d2098645f680516b8f4c7e478a71953cb775a0755bd70395d8b65345c75c0887c90c765993c8572b758efda304b142610ad0b03a5bf912c91c905023d519edaeca6c075242c27c19d6d325d5d57101b4ef1eb901f50e7ab0f6cb4ec765385dbcb09e681a4d6e6b260f76d1cf87a728067c2f8c9aeb35c758333ac4b6823f0bb97b26abe65785ad79fe6ada11babd7240f38ca1e6bbc0fb47a103beac59f838ac2268f6cc895f23899dea2c3a4bf72ff41de51994f5ea5b1e84d96f444884fab60b39db63d8a3f3f8b13103c35610572e17c02692c35ae426cf4c47455f8a647cb36beeca2ed1c7a52c3616951fa93e9aec10c128a48247075cf7d75f434ad65a9046bd03a91ce8c33732daafdba97d13b3f4d78964fa2b94a17b9898427ff19bf12a0a1bfa79832af4479333a2a39918c166dac6c0d3a1c3c6762bb019cb674174be0b056d53e6716a002123f8a0491993c87ca730c9e05a73611df60ee2d3ecc6e1b7d78d0320f8f18f0c88eec964dd850e49038ca3f3fc69285d418232ab414980caf82e66868d9e99e9a09f413f1beb4d17b543e5ab6c55a4dc71ff70175af6c1b2b21f943382a6b49b1413343631c5dd6b8fff9d3c10dcd11fad0e4067904da4a3ac42ca70675ccf7054c2b9ac76eea655ba13a1c5b0d65cd176b73b54f0dbe3751b3815dc51ba763a682c361e37c9cfd6f8b894dcd7485d58b7c00843aefb5e392fe748a5dbbce3f95cc1af25c341cdf3b9670430d7eaa8b8af69fef728d055e9f5e319d4e5fb83461c795bf2b119f75d4a2957bfc3ddf12b452d026084275f471df4ffb8449bfb9a74b2310842a9f92c22ce166830b56496f4d1ca10a8b5195f0f32fcc76c8f66c9ea61ff8a53a83d318ad0f4c14532ab465a9f523313a90e8c53d13f46a5daf184687696cc29906f22f9f783e9334fd1b18e3f33a1e11b31db26f458a55f47516503d041d32d8344801a08ca404fce13646a62bad2e2946e2d926e44d58c79f7105e46bbe78bf4d53d9e5812be8b17c3486559df002cb2514dd57d0e464b6f02e944ba4f16d191e4c70cca2a60eb9cd0fab2f064260ff24bffa765ce4729a617ec16aed322d86cf242672b44ba8e0c2ae3c20e99d4539cec0cb5b856607e0244cf8beb4bb682b6d702a21959d71ebfd6252afc7c7df419048e40525860b770f24658ddc307901758b87aeb28357565b5bd06da9c08300ac50d25edc0eca658a416179f96a785994fc4afc099ab7c621b242a69eb1b25e8d680e32e50bffe7e6fa9e4634cbce2c2ba66fa58f45a76918f816e2f63a09615858a2cb790c77ec5acf365495440200f1676470dc632d012013dbf5c2e21f04d555bc8684533dadc0f67f459207e8c689a832f60c7cc2c6e5da09987aa8a8cc00efde700c50440b31889ed6aec077c6e42b734f3269a08b95199a48e4e4f62572fe097c1e533757e3fbcff806156f8e897776292b2560a63b80022b7cbe7248ca41724a608d768d21a4bd7a6474b4a81f079aed62221a13ef3ff006412cd323de27b79d75fa29e8e65665937577aee064e8b0acf928a988074f63996da90868c34f52d8b3d1ebfce83751084add5178e46d7d0d8bc6beb46d105de8596a0a4a3ab7090821b83b83be4e216b72dcfea793f2a92ef3b9cd6d3cd27eea3f7db2d0eabaa513920cfd05c3f6599b5f03dc698f04d83526b765abd4cdb2e9e2d26a3f2747c4229c087adedc5500072e5118e4848fbd186c54c2481f348e78a40d28a4cd8323223a4dbff4298746e42d5d2cd413468a16e50d780c99d96833ed3fe26256e931f5fa667f620397403daee333474de2a28a1837de41474faf87b30808241d2905564136ba266cc7a7b58a93bfe0d7b1d44894aa6dbd659602aa898e3f54bad2b9fb2cc105145ca8878d817d3075a74fc17085d9d485679df5441426a7ab6d4c2aa20bd49e36cd71d875ee2fcfd62d88b39f843823f15512980e15dc7eeded22eb54dace50db595c08c163dcbdf074d0be7441b8280e40b5bf0f32c801c15cd8f77cb74e15fde27d58364a09556436a4dcf3d45bbd55b48b68821b916a0acf572665062b4f270d004178aceac1ae2eec9b306b009b86f6d711aae6684799fa01ef0023360667f356ab586c0c659ddc3babd62abd1659c6ca4794f18d991732439b2bdc2587e74433bcabf94b5b26604f8526cc0bf89295a8abaaff897750c88232988a2d47ed19b9d2dbdc2323e4e995d520bc11fc439fb8865660a1e4067c6b364baa2bc35a88819c4ed6e21defe683388991f2dd76d725bdd0c2e7efb5650fc607605e5d919cf28300619ccb5b9341706007ec927854f7e903926f42a2851bc0ff5531e8d94af0155c956b54804e45b3874219812568057a6691430450306af929a795415fdde6d2cfb4668e0e6cdb4d8b8280443d2a001d01cc8c82d730765c6ac02b0b327fa6a4f22ee541f9630f4f7e3591b68ae8081813549da3aa7650caa5777fe7175cba521e52fec287d74dd2b11560578170ef4f3061c5ca491135f730eb3b1849927c79fd465f5c086f8b98e2b92444d4f4914e0fa5e100cdec3f61d90e5657de86017c013700c0515a26c0f3a1f655de048b0a047e7484d5a691ad33d9541ef17460a1a5e3ccca318009bf04abafc8aca7586a35ec78d16120c4ffa5def82bac6d208dae13433c13ab6ce8e3b804ee32fc7ce8723081271e9823209849d16aa4820aa92640c466d02a69223e58a9c09693b13af20899c8869213e2782c356198b3094d59b63c99141777a97c688a57e2d46d88b2a54b1bb4bac3f4ded09a450d9767c3056d932f742cee0e571e438e79c559ca18236162f672e5891ace68985658b78daf281bc163f8bc5812b322cddc8d9dbeb37016df4d7dd6d0033c021d0f9409be4337b1e6cd6d35c7edf29dd448f729637c7a573d44f846f25828d3cecc3fef0f541cbbbc127ebe33a29f50957bbe4da06133882063a53719dfa8f445880d4b454207e8811999064764435f3a9357c9addd1a175563c006db4468bc549b16e2346205a64c3983d58d0f96c6d1fe4284e2b4df6dff28f6178e4f779e3233d586a66bdfdc6a9fef04a3bfdc448ab2537647f3874f5c8fe01a9a0d471c55a88909667648571d501d72e54487337a19366da062e64128244af5112945ab9d16d7311ad2626e3b9a19310db46396f381e44315e6a1bcb8eb565db5e25257007891491365505ac527f31e905b2c5447bfb697679f368a12e8f504900870db0b79b1e1a50f733c62fccba62cc60685c79b598210c3e63e7674cfc386c1538c55b33e1d0dddca2a793f9ee67592ad7304dc58b6d744dc8141368d3e70881039fc9510e7cd5a10761d698bdfd866f4472d6cd0ca8ab137be7f906046b49606b619f062d3a665e66a31e3b391e99a2be9d412176254ddf34556e34614ab1a32420f4f8d3d356d86559877e276ac6445415b89b1afb394c0a9765f932e18a7dc028c6f1279da3968fee2fa72164cfffa47921d045ba901ef9a6453fc7a13752b8aee19e04cd50d99a40541c0e09beb0d203975c8360f8a6cf929c6472afbbbe0e1948250b4cc91991ef8817c7fd8187ecdcba4c234900ef8c2ac0245fe7c886fe249d61585c2c7702b345095e587206483bc2bfcd4d81233d2b9090c3dc50a98094c2a78b92fa80c1c7476719f4248b6c7e8f6088cdbc6598c29b902ed66314e25adeab514afa3e2134c6426612bca52fd07474e6283a8a773995794eb7ba1c501049a36f901fa638bf19a9b3f205fd88f7cd8ecbaecc811e4dccc97bf94d0e013e8c6913cf59e2da537827de165d3207de2bb201b95c8cf0663a02f005d2e15920c9f75fcd266beb2434978ec6aaa1617cb79545953494804e1b5ab96e1d7cf552839adab2a8b269a0501715b8035d430935ef22374351317cb4f3cc621c65dff9f555aea8206403dbcbc21886ee373edbe7c7664b812f1aa31eb5f50043b9b98a37ce19635bccf6d08c83e8a203a5f0d986334ffe0fb4eb5b5bdc89c46e84233c195305742bc4e41d3f9c59c3720eb8b70c7db586b8608575886adf175a99fe1374bf96f18503f16f2f20198e2dd07dfb989778a2a3e8d44b3b189690fc6de638b208e628c34bca2c84401d33fafdd50b02fb0568ea1f1c97e851307cd6b68b3cbb0ec6b79ea9aaa41542354c67a350a50360466f69c0ff97c3b9e0532a7e6ab2a1ca8acf5047fc826772fb054fcbd9ed1722eaeba446ac4ddd078b82eee84bb840cf2e6522d2d4b03b2a42c3ab67adfe63c3e9e28181eaf9e45c04cfa677b0d72942cb238ee02a15c50b0906b007b2a03bb1f311915f0dd25b18327a2315bba256c4538e74925c23f94a45895462b85807d965a388668c503e7186104e8a970133ae8643b74ec9974f6c7df41a65719a0fa9f9e850de4b6628a8771b16ec3f7c6210954bf8f8d99c5d9de834fa446686e39b17ae36d886e65574f1b2942f7e1a854f8c107051ff69f8fab8b444e370b7a41d3fa960368555bb18b9dea8bb23b49e41361bf159fa6bdc051f269c99f5252e272e4c95e2253d03e169dff1a0575e094ea794433f27846dde5dc989b443d07590e4ba049fe003f18e5311c712318e1e0f0ea223d38b79d966498e4c14066a48b69d1f42909a748018f1707889c43396e5614ceba271460ec00d60dd038e2c48aa886402343c479eb739d98bee7f5704d43038752d933d52a18d767de55ed4ce8634c2dc691136e4c72259080248662f77d087f2bdfeba025141d185661518ccf1efb62f706cebac8df77dfeb7829c410dfeb31111d8754967b999cdd24ece64538bc6cf4af5d36e15dba52a3d335bf6b42d025d830e8e1bc853f41392d656f5020f3088793b5608ba108ef79d796e65381fcb42a3e902c40af437a3b9c889136f041501718bf65ccf64a227dcad6e5e8837d6188a90ae9e100c7797b2755c06a759c705b940b7205dfc16738e5260f4f23503ed7a3c3b6eb9f34a1cd3679b2c42b0bdc080bbaaffcb7736ac1ce8822717201a20fa318d320d3d9803dc485fc81dc71139b2390195e9e39a39ddc86d5f19d627c2c8f8bb48b2b2626334cdba3bc23aa95bcb78cfd04a85ffacf3759b8c112384296a8b84d9a12ecd9fb2a5286e681cb69760ad21a196a2d3098940d85de076b10d0a0b6463fea28ec0960db85c9cad6947d73cbd1816af73501c8569f977bdd4396a95c9fd4036fcf318f33bf71d5c1aabf7bb1c4eee9c69a24239bf3e991694659a6ceb0a67a561194d189b1a879f9e2f7b8d918b0230b11683a813c2a0b229cf795db62f27f5b7fe16ac663daa0467de54e0f7e0a0d10a750e2a8a7daa46868f08332bf00fa710ef8812a252a08baa1f7ab202f1ea9854ed58ed282e1b0169e95e0d282256447e273419cbab6d62841ab1f39ad3d289ad97ba96663a2729edba4f9005293733def0f3c685d479e85126294c9d180e8fd46f268563deca652f6a0da68ffab46c4ca248ee28028e78548da0b25a18620c49f7ffd4dbcf0d944f137b5798e698c82859ff1e31636d20b961ae553441bca14c0da306300044a6f08b47adf3fe91acb1793f6a2b05a8d0571ba0749a29d9381a1d09b42439032de208f5e99c80332f44feec25679d4b5c09e546cf7763d71383644df8ea152f422aec22029cf15438483d671eb070e40db1370aa3a1fd1fad65d39183a7945f5b3e8cb8b412756067d714c057a4c76960dc051b6cdd141eaf7419f3b96e75039f68ccb391f47107900641873c5ce456c750fd49acdf585c6607590784743f07487c9a07356c37261b436cfaca9403e41bc0763f96f3eeb5a633dba929171b74845097d41860dc938381c809616208e965ff217a1ec056eebc899c032acb1dd45778bafaed9329ecd469b658c26eb1fe30d30a95812b3580daa95cec65b4fd322adc15f1ff72503b8024d276e326b838fbe462cd0832ff247584c6cd53c30f5a82bf5f10d7c54e439be039c8d9d175e14b64653e73167b5ba7cf8e45551a5a0559245b6c00bd07259ecef6d5d32bbe58c88d0a94501ae2eb78befc1d9d978e16ffba20dcf119c1eae9c3773a5c009093910456b890abda281df349a2d525d94b850c8c241e17412b41c4468097eba093e0e44f6f9419b9d8cd65b104ee39d7f823032a9f39595a6aa5dbb579008f0fd0f447818a6d883831cccca5a3738b80d46a29b0c80682365e22481de58ac33be125cc898325b723b6e2743cc4d5a9742c85c173dcc8ba6288b564270c0d8ea8ba0d95748754a9e2c3869506b1125ad4587628409782b777670b450aae0dadc7427a1931cb18fc32096310d690e83b15c41a70b49d48065e6d275cd3ed12db0c190897c2ffa289c85f416d3bdc71e1f79d506e5ab34a56b453d64fe1ecc01ace679051954b12d09950834609cf47869b8e47fcd8618f0004db933b4c34c0daaccfb1bb8aebe31abff037c7196f342f06a10d37c91ac2e571057ec46e1d7c0d36542008bb01e1202c4c8baafa43b427b070e0c5ce0bb4e10f427b8a843f933a86c7c0c2f33ca04ec93d4200b05014484e18bc105cec6ca2ea080e2683bfe45249bf0166469daa1e98d4ebdc5ebe3b98b535af4c2e93a4e927058e971ae83c9f14a89f676523af92ea2f6eaf90390f549e2247d4f006e813aea7fe5ca778a2aaa7476c0563d565fcf51b036a5718e63c609afb47a00640033bbc37173635cb115e79a69ef052e0d278d34c60ea1e76bb9684212d6adc9cfb8ebe5965513c855e5eceebcff63916632e0c3f0259a5e1a0b7820301cb08c29525b3b9fb810be830b6a8063ed923063dddce85d0dc9e28766a52d204d50cde79f10920191e633d778395102a4ea990f8c225518140a5d2ec9144c1eace8c0de99e1d6837317736e223627978b097f37a2fe370f7635cfda6d67d9f87cdc068f5f0045e425378475f6536111e07f8f08e2ef7fb911582bc705723ce1e707285d5b8c65b32993920cab0a4e466cff1ce5f33f45a6051859a72c1245094fc803055170f1f60295496853d085f96dd8dd750e022ec3fbd8a8249a803fd538f901a6a99860e50d5b67fd369ce10f412bc3e7f144e98daf86c99aedceaa19b4c07a1ae1b82b3636b386c58c660a0670bf06a281ab25e7b4d7bd2e39355840bfb1360d376028057306aca0b64f385c83e1d8fa63381100dede98ee974f7f5e0984b3f61e306562661e1e273689b4eb2baded4aff084c5e8e88041047dc264a66c4b589996e88052f137e3cbe3770134a5e77341f3de32ae1a79580d0c153263815532a0aa81ff0bac3e43e57ecf66282a5164d89eba1f5138632db4f82afabc505a49e2071f93b10a50f7d06fddb47e2852c52438e3ac1b4f299d421808d62915598b31e427054c22045afef29b5b20922ae21c14c39afdb3fc63d8ebb3b80c3fe5e319b804fabd03dff37032fef1ea499fff9efc3e03d837b04e09547730783244480802200843fc0501a834773ae3701a26a7881916ba6d4ad500d5ef3e82682f2d59d95b4a99a40cb209a009c609a9ee05dbb66dbb646899c9a6caf89b5cba556d86aacd50b519124ac2f2d2765d4bcb0b1b5821884d046c784020c6c5b6f86e3836f58297d65a6badf5bc4f0dd7a1c24007413a833166c1a42ff45961b41ed675dc1fa0060c56af9cd5ca761c58a9b40a5b526b4c081625062b0108442044c80742155661aa303523c3d66ab76de338fc81f77e0805f8051cd930e7d4fecfa8e852165f47a4cc9c0b050c7adc2b561e0ea0236808941a30863e69ac016eadedbaae336d679858262532994c2693c9641645adecdf65a5528eff30c71a4e7399d75c546b47725cb87d17fad81e637c16596d21f52cb2daf76178799c1390d293be44ce50c4343de94d5f7a188af229e849c3faee100e0c69a478305f3e0fd11bfd223dccc31431614e404aa72f7d11f3338deeaa1d93e941f8d3780212f3a78f215d75028174970fccc390cef2d687c60ecb77a93cd4e9742eb3a82ccb2299cbb22ccbb2cc652e3030a61be8330fd5582ecb435f18fa74dafb0ce6fd5dc88319ddcc4316c5d558f21057e36a5c8dabc1b8c098f2900bccd79d040c8993639abe33b4feca8070f7b02f630a5a3b0c9975a45b8f3287997e3b29c45cbda512a9542a91f2501e32e52223179d9454251d2a8d46a3d168b4928666515ad3344dd3344dd3348dcb6e9a375ca9c2cca139b263039349a7371ac96582dd6a16e512a369156ca385eff2278555bc59d96a1bcdba84a3cb0309dfe5ebbbb8b8b8842e2e9ecb885d5c481c973f6d175bcf25b435b429165ea0c158a037580b8ab2b3d9143664360eda6c66519d16dd14191b1b2d8e4dc3304c6e424143bcf47183834bd1681d476e432db0b0d16026c060ee1e8661b8d1365a49f5794b172daa161d72d6fa9f0eddf7fe653895892cb04062e66c48d430bdc7b13248317312607a2493996e0dcd66b36dab5b193898f90c0b7dd6588dd55075945f67aeaaaaae2ec8d6595b828efc75e75326bfce6aad0ed559a551a1cd91279397d6f33cafceeaac45c5d28298498a14b473c9742d2be37fb7d041e7889983a94c31bfaf6f18c0a426355bf6cfae052385ee5af4e6a5466f3a25288ac2a6a0dd93b36b51b1b29897546682217cd1c3f03d18bee855f81efc4e092ae3df35b1bdf3d9deb5c0ff1e06d1870f03f8df9f36f8deaa02f8df0701c72056067c3a8bb291e84a9edd4ff7caa1afd25a6bade5be9e95524a2baee349046f6c3deb7942b8a7b5d6da9142aa1d3b1fdf9d8f97af6d632b7ce1fbb3605199d682f6dd5c1d8f97ae47ef8aee5c5dcbc6bad6f6aee5248432e8b36b7576dbb66f5b75adae658ab52ca9b1017edbc6711e9600da4a0249213f0d0f9a055014adc1a1053f182e34dd992452f7a9b5edbc9cbdeede9cf3f7656e0e31359ac396e5db175dc143a2bf28415a6e8f20ddce39771dd013acafcbf4455f3ae3d02f6808d23173b62a57ccf015a3121badeaaacaf867bd8ac55639df0a43617b2f76635e9a865695cc3df4ee4165d7557d6242ea1cded3f034018e082ee28d5b856dd5c7e5351d4937075f54ec3d42515c13b1ea13ab3eb1eae31c498a1ec21d0af545522f9de4ea13ab3e335a4c568da8486855426bb8af755599d81e2e59ad406feaa626cd5cf7c2d0d0c6719ec7855e187a4e5e3f377663a951199f93b1f295af7c95b3d6ff34349b09beb2281a9ab2fc2f4bafa43982053cb0c2c4c969801c490861eacfa1497285159309f8a70d133d307344214c4d32b14f5b3cdbff5eed232367e893ce6af8772401fdd942ec87ef23b429900f953dc2a6403e4b767555d78ff02d795223978b19b93bd406318baa2eee59485b63395797cf6aa432f66572022c7db9ac66214fd3b63bf2abca5695683c05b02b79fa143eb6af3a8eaa660d5d617b5f4722b8ece1fba430fac2b6dfc34b4a29c6a18fefcb32aca545d11b8af22354c69f88d6765fd1196ff9d1c3e0f2fa6168f9d1d78741bfcbc330fa963fe9ec645f05fd2eafc2e85b9e86274b5a40bfcb03d1eff22d4ef60ff79f132fdd87ce845084fe9f1ae72cb7d994efa5f28dcee32e2f57b6556531ebb315ac9d597b73d7658c398ed39af395af662207c188984f8fcbcbd6c6998ba1334ec6b84c4c4ccccad5e343438363b8891a1bee2c2a7ab383568efbdcef859950fbb12cab4414829f47e6ffcfb3f96d3f1118d20f69683eabb2a7b6a9611c84def8a6a64d0a4132405126a032fe6e94b1692df8b52d40a1b7cf8fbbaeeb3c0fafb65a0bd93b306cbe7ca13aec8e19fdb2fd2d8d4571afc75c8aa50df04ad3b3313d7ff95899b715641159dc6f0fe8fe635b161fa555b4fdcf1036e501d61120e0238317660e9e2149f7dd7badcb541e24ec40d119ff53b44348eb14dc75d775dd4a6bba9991bf5190aeef6d0ad4a2ed9a378ee3b89c73fe1f14f0d256bcf97d7f0a00863ee0abe69884f7dfa8cad952a7e0484d934adb9bf30dabf98d3a3a34d098df87ffc54049f0afe3b74367e802ce6f86ed0938bf1ab6bf457d2afbd9404bff6f058e6e8ac6d303648841ca04472f367ab153f5c61310d17b2f223d9937b4fd4bedfafd477a3490f46af64faf8a3e3fd666296084026c01ec4a4379e0afff42519e6b446b381ef7655486fa3832a3325501bb8e80df735954ee48cf855ddff7b15a5e9a1fcba2bc9cf517c2081e8d66e3d1b67bb5edefa60be32905af74f2e98c7f5f77121f8993637aa489c5d2610bb2f8116b221b7b013c37c04c6a8e23c52f7ab3b33d082333f4c902db73af81ed39f26435c07d87491b1babf10bc7b00cc33692f67abd5eb56d07d1c6430c416feaff1b3e00a30623c8507161da2f8a30430b1eaaf8e8d4502bad95d6eabb52167856b058ac8b59232ef4792d665d5d6169e813b3b6b3582ccf0620bc40b0a27eab9545fd9b74b74f9a6f8593001d4c9c1c12cced730660e2af52c59226264e0e6745656e24939cea45142626999cec6b5b9edf6af4d2dfca261321853e0233fb56d53f25b67f446cff5623207d7e463e23f486fe467d473e2218f0b52cf9ada2e84a9e78f5d54ff5f9f019f95646b6ff164319f406cfe88cffd0aa0d1cd840436f9cee1f01d89e8bcea8a1cf6ff5ad7cf6b7fa5620bcac7b402720a42f7d89c4293d892c629e7968d3d09c5daa664911cbaefafbaabdc1dddd6fdff77d254be3c9e55fbe88491a4fde9bbe88f9d5ddb29d8585054665fc59f208a4f42f24cecb97de1b8190de44e2989ef4df8be9653c0181f99787215d75eafee561de349e8080f0a607c1449eba87e94c6f7ae9c291c945144d2ea3161004c16d7231bdfc087431995e44ddd7ee7b78b5a4919aa5919a75e4be459fa0964a2f3a2d17db1fa466a9744a81542a9548a552a95422954a25d2c84d3a342693d6a7e985201645cd1ec871c49099b32d0185c9e43401fbf52bdd560629e669d2a92968f5a137dc56f993fd5b051455a77812c37658f5a1335ea5505529e8cddda713b0de227e3a7ae9d1137d9a360e5ea876046a2e2fe9a893e853976f833e3ff4bf3efa08a1081dee937d286ce0b0dd0bd5f69a5ff121b7a2d2ef5676fd142caa48135d4c9962248b248eb861d60f62512f0071e48c207460620a9659bf8709f561a2d03416d5638809bef7d562009ce8bcef709ed98688a86bd694b7f3e9aad3715cce39e7ece7107173ef7d0feb9135b5b3c492259228a1c4aac634270362063b62683b09b484d03e6d6b8920fad1524b5320265cfbb4ad566b5797ed71143561fbb4ad7ddad5ae2cbbc46bbcc9cbf6ecfa76e5a86c0320d32720a2b7ad81ced4b733d011ea9f99c7b5cfbac3d3a2de788a3ff2775cf7df06d6d0c7b6d5f0873bc8719ce71cfe286dd87df794cee4ef484a67b80f004ce7e7c8d3bda6bcacefbdd5f11a27eb8e66798d479a609ce8b38ea1e9c334d17370a30b790bf14c9e39a99d59b6d5a133b55a1d591ddafe9d05ac09923586c7539b7e73e4e93222795ffb746c00112f2913706c5d77bca6be08c4c4955deb974f8082ba3bf204de97e0fbfbfc9f3796d07dde68818e3cfd2d2eb2fdc42491bc2f692d8f973506d31fd9959ab5d69aa233d5ba6ccbcb5a9fd4449f45aa9b82ec4bcbbe7b276099f430658e24b8a7a106349962623fc706a9e143388173857d4f305576ddb6ea9eb7a8b16133689b0cd1859983613085f97de30ec738e73ceeb049001d610120cd4c9ff4c704096ca752b6832146ce34fbb6d6d21f0940a9f7298ca5804d81b2a0c1b635bdeb58b73d81caf84fa1a40f0440464f7a3b5a9d136436cf7638b6d7a16a53352b423ef39eed3e168a3de2947ef4e288f3f22effd59f53b5168e3ada1108e95dec152fc78c236a1c12f9f2e2123dc44b2ff59ff94ba3c9088d4762e89fdca2594ca1fd197d417354e96b33e8d3d62ecc41ef4761dce75e9218c54631af71e5cc428ac81a79d2ecced66c64a0375858a08a1a9dd60b2b033b86bdc176bbc67691b029501648ec7b9ba0379dfbc83f8a5994cdda8e62a3d82ef7696be53e47f4f68c170a3a73a3a8778c950c545eec00ddd4adc2e5e518fab43ff6c76b60fe809d374bd767fbf778e97221f46ffa0ae3ca1e51d1757f76c4f67de7d15fb017b92950163aae9acfa0cfeaf369b1448b2449942a7e125c86e8cd8eeb2abd7dbc2950193decaf34da2b2eb34bb6f63e5e4057a06c97ad6e453dd70574c3be2e8bb2352d7e360582a9b6003605c2428d5d7d2ceaf63801da4205b4c56b8da07d5e978f6a3b7dadb1b3eb7572c7bc476335ebe82e2335b78dc3db636ec34231fc5abcf8e986a37859b7751f161fdc97d7f87562c91e1faff981f9c5324aef07e6f0872de15964fabc2923b51e5e3795b46dd0991fe1b6351d6d6b5e838378b224862ff669afe8f3baaecb7a0dfeec2e9ab2087d5f5e5e9ff182506897ce3eafcb656b27efe975d9daf6dad0d68297e4ba4e3d24f01f101a8f43bcc442388acbd4b7d2a2b2c2b215eab372cb678e5fb07b5f17765dd7e7043f7cbb97737915dc7471b9b41b800b1a6664d04da5f400bd971ad6f670afed615139ffcd5b0a8d93acbc5ce12c6699cb1c0d5b2dcb727683988db51c2705dc4297b0715cad9b4d4205c7e294cc6675665269acd2f8b75ae22514e35cd66a450419caa396416d4067fccf03c8883e043fffa52cdf7802027e0f8a7ca420a2658c2bd0197f6e24e2e5cca2b2fdde4dd50cc71390efc38fc4c1555ce1869973552693fb497c22b28795194db0295899ef06fadce4d8e49809d1bcacf7ebf7f0d9b9c931b35279d45dd36757b3ddec79ddbd3709c762114284fd3a1c9571147f1886892a7a439fcc2caab35d1edf431f5bc4601846e2e207466edbb6719c8fc3606f701cfe40bf97c4222931a9f4f69fabb60d460a3c446fe010ebe338288ada197635b2eb4efdcab2eb0ebbb220da160fedfa337d52da107e32091ec243acd16b6485d65a6bad75ce5a9b4cff1f0b26ac60c20a9466354c975a08a1c40fcc1c9ca48ac9a46ad8ca6586aa296b532b9b5ad9d46a5553228cab93ed2be4af26f879ccbbfa784d3d7f7fff7dff7d186fff83c39bf778f4beba58a918286a7363c55ab156acd536b4c5bbda35954aa540100457777557af90093d872814915a9489f4c6dd4b6fe82cbea36a0d7212e551abbcd09b4a033ae35f7a43ccd9f3ded0756f5be90dcd79e0b39654a1a8ead22c7a0381edef4e1c15be94ed32531b1a07b58c5d65f4c6c504ea3dee3cee4bb28e53f7e1095a9b5428950b2de3dfbd97ddbe3fc44b16bcfc7665798d57654b1ce53ef80828aa56a1322ea3a19191194719cc3e29adca28adca28cd8747fae7798c478ab216856197d55556656e2b0f4e881bed5883aaacc6c290164ef04deb08ddf7f0d29661e825612d69555995b5a458949c54271decd3b747a2a8844c9c1c110c0c93f439343d3526a6f838348c374c1c1aac492299709f339a22c9c4fb6deb8703fad14070d11bfa847e6d6cffdcd831e8285f041a8b9f625ae58149b18e8069d4526af10c077929a3e2259e82c1e0464c0507e158c907dae6ff304c903e55f1490f447c1289c3a43e4ecb8b240e1320e28f1ec7e549fa3589a37f44e28c5efc2226e90343d24822912796a7260ee94f2c2231fc48221e12476ad210d3beefa379292f1d7f377e7078e91fade5a3b5d0ad31f9753a198e18e8130b81005aa133796e3adb312dc3e88d161495677996a76436b22cc3f1e5994589445994bf0cc727fa84be202f9d04a44f2c14b4ef47a3d168275558034a59ecbd97014f6c6ca6689b5a6bb5d6721cc7e12e5e542f3ad0d0d0d0d0d0d0d058148df64f93da61c1a238a12b3434341a1a0d0d0b0d0d8dc98946a3d19bbcfd4b2d169671930dad50d9b53db758db9f24037d8a76f628b6cd3e92db4866e8994571acdf66987b9820cdbdac8cffb6b1507776e88d0d14c5e16007fc1bc9cdc84d3663591447b3322e22cf3224cf9f712c4d96999b0571332ff4c951a1e235e736936d3e3bc86bea6bd1eeb0a1e1b8548ade6c333a1383c9d4d2c2a55810e967791534a9420d9ccc4bcfdc36c36409f5ef89bb1c4963c3cd388ee3b6d936db66dbb6e17bef6561b9afcae266dcec4535f2c1c7c7e78ba137eed8c7a26262b04f0cf689a1333100a0283c05f689a137223c051553f809d662caf65117ba92e7c97a39731f1f1f11eb6046868b4765bc66a4e539c6b870cf68e8e5f2c138e635be5136aa542a15434d10ecaa9c6cef80ae68639f1876456c9f98caf6c72716c2d7de7bff87ef2b73952e976b26834aa98986a62cffe90af4c63ddbfccaaffc556bd37e39a23b3ae35cd0fc9e77adbdf6ebb80db75e3d320fdf1cb48274a66bc04c0fcf49633b85d8f60702050719b540feee4db0a88e3c656d6564e8681695c914adc1ccac19070135b3fb4c522b735f70f8a7f0bab2fd81046dff3c7c2bf329d1bf82f567c1faa760fd83d813fcf07badb579154b213845257bccdddd9ff33ecff33ccff3f142a5798c76c50ace0b2f7869af97d4d2468c6badefb120b407d19c8acb3ce6a5b52f6f6646f8c48a292e95eda8d2c5c1c361d1b3dd104367a819fa5e9be9cc63c818efc86dbbd837bcf10a30c63a1c2581983f45a8a13f11886d004a0a408244e0d2d9192209330300c026894ef1f6f5e2b7b1db7e106b32d913c62dba318eb2d1b16d76113ae3cf22860641115ae27c7be8dc9d43f2e40a142d6610430a8d83758327569aa852eb62064ccc608a772464f7e3649fa61827cb35d31533d809a1e51d086208244efce024871b3c8006153b65f43001b252050ddc2301116c90460e98c8011a49384101571341c8808b1ad460872a5588aa4409f113b9eaa86ff3d364019b02fdecfca44c657cd4a2a87e4a29ad955a8e6b810815d255a8ce7ea0ef8c0a555a9de530ab568684687566d2f15894ef5aaf9452ea3b58c795db6a65e3650eb0344605c31f22b004165cc313dc6da5336094a04dc4f7fdf304fb922aac773876ec5ebfd4a34f90890dec388ee338af7b2e73983be9383126c921e0b6e457f1e55ceb0f2c7a0dbef7de8c5fe6103129e6f7fc279833b7dd4bfabb7b76cee9bde0c618ebc0e1095e02b74d50e7b09b869792259da9cf358962cbd7cb4a42a8a5e0af6f5f9f5974f1eaea4bd4bfd6d6faf51dfbb098a1ebcd5e8298a45e566bcdf84e4a29e924092c66e45bbd4aa5d704130ff4090eb11962b3eb2dc36bfca9833ed615e0eb5fa55ba57e06144f7c30c49333e6782243a2884bc4e76285ae4fadb5e4694beb7e9dbafbf6f7ede48981eb9369308286273153183656c4c22dea4ead0fc617b2987d8a10031fb60d61db184851c50ddb56114481ace8a14121a066e6326a3da3bbf7299525e182eede0c8631659f3135db3f375963bb13283e4ef3dde28823f63e9020f75e12cc651395e6818a1c4db48b150e0dad83362b5e88c124092fb4b829101554fca05d3605a2a295848a20925081c3b603fd6d0a44456a28866424660d7ad972811ec205072158583b2d99e6052cd811659d41add23434390e1a8a90b1d182216863ec40446b4a0eac191646888041a162b59268c110ac950e4a765aa004c64487f5fac10d44e8606788571a3b4a5a3c321e8a706998034dc13ad5408922e490437b57f212da4b632b42733690e95006f909dd8205e7447b4d3821986c72680e892d56c4aa0aa0226aa0e181166d0a44841bdc103a6f0a44441b5434b8291011676416e87053202264254cb1d22ffb6e0ae473659fa61c7c5f67b3cdb626ce379caf7bb6be615d7dc3e499b3d6f734d9eace42fa53778f0404095faf78c35e376ef3ca9d2c505b36256d3600eecddb1b25f5d891f678e24dc7fdd55aaf9f2e40cdfb38f465aa00bf0aeeb7dc54ea3edd985e72536bdf2d69f2b2de3d84fee893ffd099ec25cb59f78937cba959bc6a8a45757f2211c6248e0acc4b7a11ebb9250addc16badb5d65a6badcde40bc0da2d6c7677337d6e0ba139739c085b47331e29714d1d8faed4da9005ba7e16812d3ce8fab55616202c667e6f9f2a304556d8eb3567112e779ecaf3bc6f4aa7fabe0f9cd2a9c0501586a1480b4fd52911b1545bc9ab8445ab3c25bac5226195b4d4a7b556979145c22a1989a425499274352401ab50bdb0bc986cd62fdec050c4d2c3c3c383595858f448873ef0ebdba2b2786b5d1e9e9f283c3c9647f5526db5b5034f8bcb08abea4a026e9768dd87a2c6a0ac5830a1323e9243bf206a218bdaa3aa3c2a133a823fecb5aa305802ef4b1a09eb4eb6a86e052b1364080b486822e9f6f0f0f0f0f0dc1009a9748fb8484a2ca217d33de22231c180d0c3c393dfe04102c2e9c6405f1dfab8a71e1e1e1e1e9e1d78625c5a62a213d56de9ee6fe3406a51245034e9e3aeebbc9177df7331bb243b7a9c88b0b97cb12521f367512d2618d61c85a33588fdf47b3080f2a8ab3a02d7912b581998aebfb1bc6cb98c571e55083a827f7529a1335e02de96c4190a4560b6595793fe1d37e065de41442c20b52891094487363434a099ef6d795398845ea2ed4834e92fd1473418e62c66d3824eb428f461834820b5a892692ea9494c35c927499244075d2daa64d2ff348b28043f967bebe80c9daf6945cf007dbe2b76fd935ea95c6db9a0f2bc76cd51a8c876a5403e4054a15debab866acae55369b55d412996ecefeff7598aa1fdbd17faf8748db8bdef421fde85b2bb28c0d85d7e2ef491ef096eae8b7d8a9babb23928926c0e0a1e6cae078a69fb4c71e863c36d6cac03852601e80957d6abfdd58ee7eefada4413cb1f60920330daa8358942430d5068d1c30fcb074874cc5b357662bb123bd8eeeeee31313aa65ed3af01b6b8f6f312dbbe0672428c6ddf869a458c0e9719086842b5fd4def250b3e954ac96056100651c8805c2dd8ee291df3f569f629bea00a0a04031ab69b362a9b6c7c97fb2fd32a8f4c5a561ab60890abb0ca83232bad6e416f2c59d3a033f7ef7fd725e579d904756ff99de2ec8f29ad3c4e809f08dd6500aa682d680d667979b111368aed71625f5ef393ffbe65dd67d59a4515312fb863c455f4a60a9914542237b1ef14fb863eb00fd0b2e8ccfd51149a7a792db5b69e5468df2b5ac1fef87879ffbbef8dd68997f7bbd1f65857cba22ccbca1881595e63b5a0e5fd7b33696174e6beb8231a39f57d4a63656e95d199fb1c596196c6a2447fef5fcd5d11e9d88a7ded08f6687fbbc4cb9b73ce99e32ccbcb6b5b303dfabe6539ead2bce69441ebba6f7dee5bd67ddbba584444758c31a614dbf1925993a6e853a495dc21d23f77ec2cc2a62f8a410b01e344d3378da14f749b9a68aaa3aae0a33a40b841d3fffcc57d05b2656d65ea8b5aa8974e9e4ab06fffa442e63e3f354f2a78306cef3d354f22beed7be85f8558f274c2c8df8560177c0bb14fb328c2caf63b8eb61d691645b0b68f2d8c4e2a80d4cce349850d06eec320df73e40908d73db791413cef37f20464fbfc1b48ba0b0ccfe42f9f61bf6e7c54c709aab6fff79dae533552b697b3a4ec5a6ba20d3385ae5552d3e84cad59d4f76e501e58093a827feb0d8ac24c50197f1a9a031c2084104ea75d6b94067d622cfb0774ac2cdb5e16855baf8fc42c9919fab4687c1ffec581da4079d0280ab3ac0c1af4067f1cdb7f673b134620217ba024e9a049a54f1f41a14f1f72f3db40fa75d3c7b2cac392deaa23cc80de54d297b8e20d75875c8a65513766a150138b8b0c86a6c1b9bde220b79a95798ddb133a82bf3883b6deaa558bfa9a255b357b63db8dbdc3ac4cf5d9e77f67e8dfdf8f97bec1b69797242234501858ec338b58f6ef2dcf983cb36e99c028f409eab0bd627a747d50d5b9aef2511274ddf8dbe1e359efbdf75a7bdd491ad266dba735745b8b4b5fc8c098614a0e9898038427a208a249921b9af4302a439f6e65d31b479d4012e7238b982c5fa43ecbe8302b535f34e3a0ebc3f49cfcc793f52b5e03f23df876040282ff9243d7050dfddb16e0c91125b418e28d2976cc6a7b86a860030d5a93354c2d7080064f8aa604072fcc5a8405850e6020c70c3c109ac3ac1f86ae344c64dbdbd3c778cc221e7d12c1b90ad1a79fadd7c372a37d4a046f9b69a6d4dacf5fb7d09835fd21a18ffd7de7c5e369531ba76443b11a8ce6afd9ec4583d56238b5312d6d36c58ffde9789f33018e3581dd9624826de8c3d21c6c0a546568d3b0c2283163ed3b23d5a00a6d53a02ab54a6b5798ad5764a42cf4496974a6b6f4595b3bb316c6ad56ddb1396b5d9f058bca54a67ece9936b332534c538ecb9656ff3cd2d7f9db52afb2ece0ef535a53761144cd09284fbc9104037ec00219784062c90cbea845acb0d2438f133fc8b023034d83a0317c38a20a1e14715f72a0f76ffdb2be367d376b2c8a98d2a2b2c40f376061de4b6aa2e93ef59230a200a23203a3cafec2cf5ee39bbed7fdc3de368ee1491a41e0d3c7f4e67b4a6e1bff055db81bfc6f7c629b806583ffb910fbb348f6778fec0f03b1bf0ddc61e7f007b9bfff28be9dedbaceb7aeebba8e0341741659b2bf070aa387ed8d277d2f8cd4ee685d4253202a35fb7e458f45f4e43e7cd1f770137a74efa34341449e4576489ef4bbef3e320b28509e8e67c518e3c724fd13e3db09e92850c48a4da908675781ee6df736bf47bdd1bf43419122f4291d4b556e91af6e64d47cf1665f368e2be20231e2481285055ff4f050afe470050d4b14f192829b8021030223265479f8018d98e96302743085104287394c1f7548312b193970220ed3473a93e10d1f48c163fa686590627e17b4637541b1040dd347dbdac1b78176c4a949e3063f9839a10b7a9a744f9839a31f9896a41b47bb05a8a478993e522ebecfc16d4bd11b21e0fb3eeb069fa6d3d0e17f8f030dfe89abdb8bb76fcc9d28fcbcc30f1ce9c64218abe88db831def831feee456f5e36fe2f89feeda9f6c6df11fab438fcf19530800d9294ce7ce4f61e0a79532f7dfbd304f26f9f376eab7d1cfeedb9ed3401fcf631cea95e5bf5a3e18ffcb98685d2b1b19ded283836b8edd0956dbf0140b228dbbe043eafebbaae73d11917dadb73384567c2fbdaf9b16ae7ff36d0431fdde7ddddd8cebf61967395a31cc75d2d3a8b6c9d3c73e388ed231dcfd304b6bf4fde944941d82678c010ef5541327b4ef59afa30c5267d4a9ed9a2b0cf1754f0cba2b6bff8f103d9486a65b08f95d9465cc2883c8778283c0b0844cdaa8d3574c3ac066bbc7419d3aa1ca29065950f6dccb8970a8b7d82fb8c01d1289d78ac2dde086a238e0f7c5d71ddf0c34eb7a96ccd071b9ed881d3092a577726368bc520e58594241ce852010521e19af26344cbb5aa2faeb8c0c8a1c72b40eab27858a37bf5a0aac2251686927d965eb42096bdc663673bc8b1cff287872450dee8918546b0ca333ad76c870eb435583e185594edaf34f659eecad5a4c8b1cf1d52a894f6b52ed65d5f7fd500573e6a51d6614c209199381d0dca909943a178510b03871bc3cc1199302d48b118aacd4c2af3420731937e31a536333f0cdab10291c4182dd3479a1ef3db383a83206430d3c7181bcc1caf5509aa8676fd60cccda209878f0507facc9b8ae4052fa54964b7240dd5619fbee6c10d1b2831edb8038af9551f180e548a13180cb96047a583022849d1d4c0601831c3872d491a72981f8704184fcc2f8377a4af558cc7fcba2d6640630d2a469395161f784702d0208c32d200c13be210e08dd60e6c98f7ed7849ea249c7ad791729162e5c0b4230de30d9308d83e2fd5a22baa48ddc0b4a3788559dd22f1f95846ad5e8b7875a10678e7ebd7fa20109a6a4b7776bd6285bf53327f5568fa3dbc5683de2cf569e812a4eb531fda07a5d9a73a2ab54e527a6b131f15b57e517c2dbe26bd487a91f4a23549b7e84f6b2d6a51d4a2d65a6bad49fa45f1c517452d6a518ff9c59c4bafc527fd169e4022919e441a718d025eeab16eadb5feb4d6598bfad3fabf0ff4d0871e8d44fbe248e9687c791a6a4f2489dd477a196910fcc42c8a4f7ab9a5bb3d06c117d2964452883ef593c250ebfc5fd55a6bad45f1f5277e0fcfe27fa2d65aebafa10f4da3bf8628e817ffd3242dfa10bd38864f1a45af9f348a63f692e541afd1e4096e96d7af7bd8518b0f3a82377f5e97b90d5f5215635178b68e4d81a2ececa14d37dd1c7891a16bcca3813ef3b616557fbc202dc4b9ec99a1ffab7b48b9adde4621f789d4bdbdc96b4eefb917e2fdf65607bdc96f7f00f4a67bfb5fc7bde785286c8fb7c7f8bb717b6e74a1ee4bd2ed1bae148b469d9bf2dfe0f828075e76e32847b7f13eb76d76b321ec687a93dfbf6cdd8ef733774717eab664083abad21c6351127453279b6e9ab569a0288f4aab23f8dbb7a48f5c61feaa347f9bc5766badb52f4a6f7c58281475e3d067fdb15fc51f7a9727b8b72bd27f6af04f7b79b891ba318e1265737fffbed5240b79ea5d7f8c7846b6a5376c608c2b45551a95b19f41ac465777b0edfec8faa233f659e2d09bad98baf7aaaf5790972cfb9295f6daf65d9fb7526b596a27c44777681a8e8635ac21ad36688bb32d925ee8db7bda165f14738c6c3147b5c5d168829c3d7ab18eaa3521c83e17b22dfe68f4e2cae547ab6f033719382bfda90395c2b8edb8c0e1a51dbd85d962cb16ab2892e790728f1e0785288824162231cd4b2b64c7cb1068d042c4bf46c6bb33833edf2d16dd9497b604bca9972219e335222992316ee82de5a57d17120f7969735801f147cf85278c5efc3afad1e85da87b445240072dfee8bd1f8d75538a4a81cad8cfa706ddd0a417bf72610546eff25b78c20d3bdbe5475f431446efe2f22e3abcb42ea411d75ca8db85a44008def007fefb315786d42b5b7c511c63a068f14f718b648c972dd0d88a97a2386e33d0191d981cf4e781d76bc47c4da33f7f8b62cacad817fff344d13cbb2d8aa6f835f421d288afc7d3b77e6cc5fe6804bdbcdea25b30cd51d4b43c377575bcc6e5ed9796d05df8b089f81efdb9e96c3af4c6b7a5b24397b15443912665ffaa2c8a9a5614926739f2c8d384f7884cc14bfb21eca047eff2e7a6b3e9784de9ed969f9b896656c6bee84be44d71b3ee2dc9be27861e9142c41fbd0b798aeff2405cd036dde2d31005f147dfc33fd16544bdbc673e7190ce29dae7c653dce7a673de7d6e3adb9a207a0c5713abe8036d036d21ab21c8f75e16e8cc4dd19b98c74051de72981fd9615fb761df6bef257d63c12c6a6b5919fbf66b340c8bb52cb54e5e3e74c66eac6d51f88dc9fb6333ebbc3fdbfeb9c5b6dd95bcf7e7c26e941c9b7d5447ae5dd7e59c73a643de5885a8871c5967940e55a14aab332bdbfe90296c0b445ec0019f7ed5f9add6bcb45f691ef3d8cc6b5ef3391599a5794d087ee1f770cef33ccf0ba50ea8444d364645ea18a21900000000b314000028100a8743e29050342617a4513d14000c7b90427c5c190ac4598ec3300c19630c218c10000040046086666805af5557e3128f736bf716bc4fa4c59b5e168f4fb8496ce9958ea950bee53e0366bc40deb9a1816711a7ce43d79e1c509c6955a6afcd4ee8eafb6d7e739c524a23eb3722eedae92daefddbe6704d616a77e5d85d103ebc72f683db8361810460a6f47d0a60e0f077f103a9fb7e3e1d4d3173005e8bc8d845039c2b2b98cce981a429ed47b7144f7ad8e25459e9810742910d79e292fb785f21992a52149775f8149b90e990e21354f686d5aa31cc47b3623ddfbd0c73515fda8d36af5056a14a87d1dbeadb866bf0419d5cc6b468c02824ee40bb3e2764809de11c38da541d77c4180107aa9fae82d9b42d243270379019e48b0b8985ba967ac6ff6f2f62c06cc5c730a134aba8720a06db617132ba22b9359dd2b325e4439f1400cb8bc8203c0c1f1425d8e3940ca065a5231b113a52f3c7d2eb906db717ab9a659bbfa1c00d5525ee9c6207a84f95d9b0b2d218818a488757817c69d424ed3f14e8a3ac61e5c1e629a6b3a241992c3a4c665d65b363a755120c2c2d010c6076a4cb1defb267279b2262a6b0e61597fcf3722576d18f9985f6643f9e9b82959f2376845820eff9006754e0917af5ca11442008ecbdd2a9b09e0e748e39c4590a32e5c43b93dbd381de27db3e5ec01064d726af7e619af646c52ae471550c008fff4584b7f4e165530bdab7bb984e73778c4e4d3325fa5122a768939d41855734138cf2ae1d5fa8d894bcd0fc2b51aae4c30b093c051c9225d2ac2bec51b3db5e09bbab4cd20160d44cf5fc5c157e289409bdefea281830653191cfc3cdd67ffb8a82060821f1ea49c9db70ec518c55fdc0c655701e8dcb671252e1a14f9d9ababb345262c01732997381b007848c93dedda1c1cd67d9edb548bb7d34bf2f312bf924af8e6c5dad1dd43df5921ecb1a75016cbf88db46da88db4031bf5ddf815b9117be5d9fed9e10a8a31cc3875e9d914003ac1afb8cce62cc5b058c30d066cda7a4a061c572594b71f222b69eae633f683d6092b7dc89a1cbfb1928595c4c8b69cbad35dddd5b250c222a46f8a747c019d5c6a1458c9209faa3b6d76a2cf222d49fd52463fdd75bea42d29f67c1376e0f5eaa7c8a424cd08b9b5df38dd97df687aa9e88abd2820f58dc4c7ff820ced6719ce67303b792d415c5bfcae88ee106d811cdd2aeb08b76582530e261a930722da9e983f282083a8ab133902dc91507fcaf24e3183b5f126a0426aa3342e65a3bb31a85d48729fcaa035149253c2adec922c4242205b36a8c9225f6fe7415b8264f401cff3c981b680e54334949ee04c11049db1ed7208c71d82f6598d3136d53a439a6ad73342246b154d820203d1d784d986c557d0b1a02fd109fd8d5687a13c9ec004fd0722fabf09874c3b9a5022a75bef030f30eb73eabf6dfa01cef0dc6512043d27d78585c6130e60b168ce1937924eb97f1d4a3f86b57cf3c60895e8ceee93563ef03763f4612b5d6be4b17b1510ff52b419ed1562fedea9e9d66ab114f05d54545325c69c1d74677587d2426bb7499555430ffa42c68cf4e73d7ec34d27f22bd6fed878c74ed7d8a89cd8395d80129ccf9fbef551c21398bab55e74c96dc5de305868cd588fa172a15b9a550e89d55c874f6313de4d4b73ddd0613e2a7fa271faa05f4d1b4b5df0d8b09d162eb209c13152816087da7b0d5194d2a8f483ed482b83eddeb8f9c536b82ea286e630aa8d351754febbe86017c1de4a95397ee2df1b68b9e9ed78a307ba4f5da89152e727014024efccd3716b48b8db9c87b7a0e9ff6457566ad6e64d65adc7a3a4f9f9a7e4b249f388579a5ed978cb447536989999a3f14981d83bd9ee26d3c084763aba790a0f43d980ade0781876b12b186550b3d6cc4114a29be8377330c28609a1a188384e5cbed120987463172b1d13942e71989288352f630a1abba05b3bbc36ba313ebe0cf86352166bd64724c9ad77dc6022b13c500546afef493520104fb05a79b71c083e56a9c61a4586807ba81c7c64977b02f5b571ad2a2450fe9eb0b8e4ddfc1cee95e29c18d5b0b3f1d330f2a8157c8ffebd06ebef6e53db2f4abce9db092a5e43aa0c8a539ed373157eb59b8b890420625e49ddb37ca4fa7840be7d27a70fd794755f2067bcd69d604e50d405eb92aca682f825afa51de07adafb26ee1de618e56343a15e3acd041730c61acdbf299b8272fd4eb28cc95a70f31242f84b09e99b98dafaaca8722e084130b9fa3d9fcb3384a0c07c4735754a8ad5a8b8cfbe409c435d19327c963972d44c3ec7449fe8a19eed7df8035c7f1600c1c5bf55b4d2cf7aa523ae1e6f9eb22975353649de5c6ab9dbe558bb0b798585d9efde010c4c014f311b092b323993f414e8a04ea6747929176f40904d10c70f7aec8284d7e870c14767c8bec5fc2270a1f102b3fb8373c6af98028c6b1cb7ac73d7cfcaa2f11134916b7f2db99718b723f34c15e9a1a8405a3f6cdbbdee5771d1e2613474d08e5078b56fc2154d58422b109b1fd1252532cc6ae288813a21d01d5ca76ede6f24b4339d01ffe919efae3200cf192fbb57b440b45dd0403fda13686e17b63790df5ca88143c839d1971983991824028df402ba12d30cd6fbd6edcb36095b6c01437b605ae3f687e1df34ade072b76b22e4100c017981b0cdc15e607d9d74f602584166a041d9d27438b4bb3a01ea177f3e058268b2030514f83be3f480419943980d4cc15101e400aa0f2c7f28081fa08fdd48e5c81dda3f74b7f6fa7ad96473a1976bcc613e1d9f3f347908a8bc0b2674f7080f650e6df27f681fb225bc990833e6cfc4ea148447b3215d65bf41bbe0586bd84c81363f4238103dc92c12b7d58cf711c4a53143ba54bf615a9e10dc70faffe18d55538fe7aecf2a890de745232b0a8af26dc46c04e499bb8e8b0efac587da0b3c5540dd171bf9a637c2dde4b1c96e49376312eedd9c9c7b8e0e7df4684566a337f4e6fc087fe949e5862e8851471422a17eff79c2772eec23e2ff2495c8375b2e6be28edd4789f63618f00c5f518cc268717c45a636d60ce311682c4e49effa43f51f8c67c045a406ed90975ca54896a9b5534438aca92b420817812232578227717fa7465c5438488858ffa8585e945c1d81fe9c775eaa07de5e28b55fec0467b10652b8faa9c4aa04e610bc39a968e7e749af4a80000c7ff2b7cb701833b11906f2a80082e8cf9254419d2dfca03f870c07e3ab1614a804a5a8481cb5103b7b6abe7a760fccc444756ba42a31aae47dd1a0edc3c79e9e323eb563c900a12ae0ba575c39c82fbdd0f785a9dd9e9f2d19098760d40d029c780edff25306eca1ed44ae3a8896cf4810c826143b1f857d84c6cad831a02eb27dba9fe8d62c454105015472e4d5b810052c15d60197e9ff9bf9f6cec07200c21fe03988fbaa819a1c4e85a811c056413e2c70300efea2732a786d0d44ad04a38cd1c87c569e92d8108e050307a3034f4903aac206f0aafa5b16c2b29f975e524f0a9a94c0237ec5894dc1544b5d1fa26ce1f015eb449515d11ff316c5f4375582055eaf3869523dcc2fc23b19c01769e1b77285c001a3d9bc935ddf2ec9d40e1d6fb1f95632acf59ab555eed331fc1b5f9ae61bf5042fddd3ac2c7cc7d2e881acc9c694f849050b63b2e26fb1a3b8352c61d7944d2374a28176e4babc0c8c35614bbc880fcf1216968f728424868c2d0b509135864b43d2610750629365da68691b9d2189aebad2183892b5f25df5c95a2e2a332dce2d7055cddc87d4a9b7f48afe856aa2ad592c45be9a2710e45e5891bb1bb2c175141c11058ee9de443ec6a9328a2af28969614f359e9868993073b6b4a4081efd41522f2dd344aab9e9cd02c47d76814aaaee2b7dad35a854d7106d22781baf3c7f38ec63ad90539e11cfb41288474ba3ca38797dd4ca50ab9cb2757fdb6c1657675f5eb50dcad2545f7db55a984e25b5447d95a69b703496f89fde32205a99b852df414eea1d75b6b90a33f7eed9d24891faba3f8cf8130ea1d91470992fa9d8b1c01e842b5e9f430545ed96b03b15782ddb3fa297b579337149ba2051ed1e6a22d5913b92405fdbb02515b4090d217def2511ba47bdaee8a77b22a72abc327ad90c53e78c8a1819429214ff115bfc77bad01692a1d43b1011cfe1031262d167bd84c08ba65ebd5f47cbb9e82228d5a8e61c7e51e43a59c4b4ec465510d7ba79d8a0df37c984f70e6ffa0d8681f197cc7cd4514d9fad036e648704987675b8e50b6c8a2eac9f426c5bdfa2515812c8d0e3a19a970663163c9d048dbcc339a44bad09c548ae1826b8284f96e01df0b82a062846087fbbc0d86436aa26973a356f4bd136b647ff7e28711a57f08161c6d096138ba4b27b9052a7494bf10f2ea83407c17c6b1de22440f5f1a37a7acec49eb0b2c159a64789cfe982eea8e03653830a07b2dd2fd109b20c48375ac2b574cfbc660291a243e8d2719a73494d31c087a01e679d621c30841b4b67aa63889405163e8143135db63e994510014de325adcf863abe888c1ca9e73e21085d6dfdd9307cccf58356f064381f236dae1f571c89ca0b3da06fceee81ae53916f07bfbc4dae68e054f3f2f39ca087049777059939748544217c0064cda34e57cd83343174bfbe55358dcc8314f75e431ef6cba58487c39e46e53f2af3b9fabae6bb8262441c8e909708b0dc222af7d9c92dd7eccc2456d0d5d86f7ba3a6ec3508dcda6d4a5fa08f567ebad01f3f70f2f4ce78f318fb119d181af694715dcaf3d0dfd80ef55fb9992357e38083017b30c17ab017127a82d13c0d45233fd680248d4e56d254aa8623d5ba7333d34234b236ebd9b4cd5775fcbd03e4934939ecee8bafd1556c20e7f3a28e7bd6e1edc5a6dbfe922fac9112da7732d6e642d99be331b283ca7bf5897e058c965d4206fad3ace88790affa2b2be706a99396f00b045095d25ad4f5e14ee523357749890473c844be8746e2616276b60b8a6dbdef3d22febc5a964062867c7e3f91fc07e43fb134215457b14262bacdc5ddb670473b3e40aba6d765a58bffb1e7ae1a33d44d0ce56ddd4ef81a94a71ae1c4dd671dc3427a052b64c84eccd52c2b5a0e5a0b26268bc9a1f869063a40c42584a6124d13c0060d3c92faa2f849b90d4b4deb6a8639333eb34a511bd08a4fb8cc673949cbc0326533957f5743093e26c123d5cae033c59453303e26a9e1ae8187dc14bbd81345c721dcaf2dc16decb7e728cd8d8fb3ad847fd6f29d68536d9ec930d5d9496eb3379ce80e57bb83e97bff7747c2546b3f488e0ec07f8dd5c76ff6c2c5da059aa8909d8f6506ad79bb101b27e94d7b76ce6b224340a9417e630b455a71e2ad8c3bf97b0afa0dbabc62a4231a3268757918098fcec2ab8b1671057aed29a902a747ffbcb4352a95d0686663906c7d16372302ef1f050389b209061b4d9e1735fe51a53ca6b6231eca328d9433ccd080d14595573edefb9c96dcb7262f1e58933605cf5a40097cd5fa3cdd44b4ac6a0dd0cb0293cc7af3e0f48bb45c17368c04627bd7d2b3ddb004f22267d70b967128002fc840b86617ee8aaa4c6b840d770010bd000fd51bf5ba543d02a5fee5e2f2771dc1d2b9d8aeddf0eac7af16f101018f4846ed835f41d369533d0762e6e6b5a6149f9e2c49eae7dd84a277c06cb24dc40abb463a27b46455782ca02c7bd3c0a27a2056ff5f0e8ee8fa4e7383d5169a2464db27cf1d616a860e3bb31cba9b36341320500222a708ab11c812854aefdf74a6e592ab481023983d9948ce388115c44d2c31371bd0d6d403f0fe795df86e1d6289ee7c82035eac25bc1a7b1eaa4134037d6e153576546e3ddfccd0c29fea30963b2e9a8fb9ed550cabce8400f7e11a6ba39cbcda10986f960a1cb6368205817cc167a8bcad763ee777db6dc29544208ec0c8dde285702af65b6cdf30075a38e20d080c7c3fe8a718fec76176435dad469220edd9af47666f407bde564736871ab910ea576e77d2e8a93706e8d4813e5a475fb6909fa5fac028713ad8e9ceef3afaf9167ef0ca275b899c93b83b699fb3ed00a41022e465525fafd68f811f8d3490afedaa2d97d7b50d231434b395896e05e8ae2355ce1363417600dd17eddadecf195a62de0aecd23e12bf2e4adb2147123a3d487b7aecd86c607256d714806d28909e1a0a03d0a474316dca726e14c070514d2d2f31c1fb97326560d2c99c41d534a2039eb1c53cebf4937f167410087724295facba5cb8c691d8cd762b9f76540b8d4b6179cb622150170811801494a6b2ec008ab34d77516d659c50ec768454d11adbb83ffeedc38fee2990c3594e220506032f5780db969dc1c2ddfdc9c53542b36cedf0de02f175e72d2b76721ab26c19e3cde475393471e3d0ff5fb889992591d1c5068c81d59dfdb650e0deec091daaa257de42d97c28ad87bd0820234f7ab0d07541493e441b3d16ecf5a9d622d0eaaac8b06a215f21a8cabb5bfd1828684d1eefd07f01630332f6a0c60e272accf542302bf2cc4ccb30781479d5513cb64f2101ca43dace9ce0a5833558882049f2a566a623468442bc33a12860951ddc7fb5691f8df92aa441ed94c8e3b755778e8dc73bfcdff43a65c7de9acd9336305009dcc511a8c15a35de133a7d331770545128d7d51b09bbb2b62211253724028b1ae5d8f0fd3acaf7098bcf0573cbf4d207f0f666a35feba3cebe0f2f8e696751acee839ec1bbb7f634247ddcf46fb11251b4b1b9f0973dc4547881c5c633ccba0a6afce114696cea67591a5db907ee7241c207402459f0791406193ed23b93035322baa105384a96321b356a1083f728eea6296ee8e59dfb624556ba5301fc990586f92bd50e60e91d7bbee8b59cbb15f0d03d50a4b479053ce1fc28751f89f403a76fc741139c5fad3223aa3a84f5d96d800132f3fc8c28ab929e00634d8ca877e461e7aa81abe2477be81f14702aff40850d3a9ceeae7533904cb84b3eb0d8dbc5877dbd48eb5f7471406ecc39bee93a15beedb808795c24d42b0488c28081bf8fe552cb24c82936587952e56290273e6767e43e98fc688424bb09b923b8c20192f6ecbb9aabaa89e5f6cc7f0bf73b379812ff71e9cea38feecc2b8258fb6a8aca190951d9502609533e4854d9bd09c95082ae8d7cc305fafd2ba348940233c4e3b7611f4b8e74467916dc81094a1c5ace8793cbc4f108be0af71e3813b8ed64e28f6e61457ec132e3025efafde1c72310382ba636d596d1f88e2d3519f96b4e541f52361fb6a5a6c9e0121f400772c5c7fe9e196586b1ce5c82a57163161b7ac67eadf7bc55d1488fa2a0402a4c7b8e4c2667a0b4c26d7d6ff9efacbb902357e308f6d089583cfe0995f1b0c5f2c35152a7000ab40a4e0e0ecc0be0b5aea247f7f13fc1e65eb1e72103831163757169818114d72cf2eb6f9ec67761f308407c33d50232ee21430145be5b2b416259aac21c3f01523702a4c2868bf377a8d27bda52ec2d7891c6494a2c0889a3adc10237cdab4307331d97d8d8b87ffa1028c0e44338962f892c351d545fd7465629a7cc562dae9ddcedb5d1911a784a0e2e00adb0837dc536d12f312be40241c2958dd14bc23cb99dceab5b833557a1a044bd98ea55bb07482bcdd1b7370d7c7a32a7d8a54968faa5e14fb4ca70c81ba3f10b77e70443a1f0c1f212b69be1091a609aaf96017222477c28c5c47d082cbe14080bc66132acbd1056a1aef8a4900b6f7fdbfff82957606543a5205113b131ec418db0c0614c05931f94deb4d4b3a234d8af44b93368d168db0a2000abe38048a827bfd5719a56a9b25ae10ec9cddc8831842c86fba27473f7f5b663ea836264d40cca4c735b32191224607bf705f017f2cbda94c66badefc717a16188f7ca2145dbe3b6fa3572e7bc520f151613081f0bb4b83aa9bbd4024062dfa70f381894fa3f7e0cd47258f497f37a1967acfef34aab298095017bcae664544f59af5de4e08c0652f9481ba20ecd7e560dfb6bbc375fd097b2cd90af5cbcfbaf369dccde364b2dc719fe597992e51d8d5a0d6e9c644d325974fdd8b5bd49a2a712bd1602d3b4c27880f1c53349f85046b6a6552240705bd2098d3e37a909be1ebe2920b19b1c4b8d6250cb95da0a79c6b9851a7803a90d90862c2c2d757c7b175e42da8bb8cca83aca95f25175b15d954dfd93dec057c653f62d04fad8984f17298c1ec064cb7882381a4a4c9ca2dfba0cfa22675acfa56bf91736e79c28c682f65b19f5fa5fc6283211d731d7fd6cc96db0ff03c0608918c49be8524e08298836e0a77c401067ff5cc89f28ff307b2467dccf205a2b9e10767ef74eaa028fb10e591d2b97abe6147bb978e24430e03cfaa731ebd29511101fdc47bdf73eeeda3ffa74f5412360146b5df3dd2e32de2008a3d4d6b1e4631609901a3985e8d3582dd78a6c6835fa2367038fea94b145451fda6db9656517db67931507c0ee00945d54ff49dc2fc2218334162410eba615c1b9ae105d14d706bc2cde8305df53a981d525b1723ea0da955c8169ede31b7401021ee5db7c7e812650dd0360408110e6d7671682622acdef43b25353a9ab64f8397f6d21b5deaa7a33ed71b294601b00394ef9c9236b8125d3a4bc630a06183f0daff4c2f273c18a33b732186dcec2bc4c90e70b74427a0ac42cf2b15ba0c079c6e871340948f19a68c984b4cd5fbff8331c1bcd80e6b0fd739202d14b0ca74f56a7d3c7ff6bb8367a9507cbb76f6e8359e491e9df78b255514360a81ff7f57df6d611eea9d02e4e4d5f6b10f4b0c0b8fb793fcb4ed90358246560a0d2e4782a9223acc350a33b8034990d81c2f761292aab602a001ab1cbea52ffc2712c973d4f6c52be02f473b488d6d908b11f8de57ad205ec2870db040fd88c61be8ce54eacc53dfdce9c2374728a4700f98e89599ee06172977db29f7a7760037ce4a2ccb03a89b092620abb13112eb650cb0e7bbb0eb720e8edd9c2717664cb3de2a3c8000e94988b538a5233f854e2bcfc981439a0e0ac9776589ccfe150895102fe14e575a84e67a28e1d5996a063f97159650c53ea97029d5620c45f011704ec79a1c969a1d9d014f7c42535b7602b46a739b50a6208b34ad8e3acd97071d62bba3232583b798bd2739e5039f1071395e20cf0d94323c792ba6cba7f1950aabccb97fb8d5caa10de82bf17b143e09004ce5c044a36d42206163e5e2172b7ed5a04c302ba8e28822ef76216064b7ae8ecffd84d036192816002e90ce80028686852b8c109afb0373578e818c9721ce86c01cce3b62dabb7a2c06f1320d121787c1c35155845c0c83f498f472dcb13105d147999160728bfac03a70f6b143de9753c15417a72e2dc96969c93438e7fb450242a489dd6b138865d08103ec58d8bcd4093579008a9952808bf75e5687fedf5f2fb0ba94ab6e01b4ef94d9d78fbf3c75438ef795e8fe0aa1f582224cc85a72f2976f777ed2ce3a61a3e4b85271c761746f8ef7563dda0b974ba5e6d853a5092a842cfdf43882386b0490af4658793f23e0ec80280c3820d508036da1a092c96370ae15b963741660ad6853f127ce7a7278a81818ec239870241aec0c91634a50361f18090eb0771d2a58c0ba004a88564101c596ec50704764f012dc4017bb0a77ecf7652ba7c2cd0623f570fa351081e16430877fdb7bffe97db0bacb0cde1400291560b35458cc0b4522152e8b48a900d79bb26221072d2d563a4d595f868b7a5b99a1f291ea66cf03fad622dd07d73ece60b936b4f4979e16d7a358caae8bd821e6283b48ea078596b223123596beecd3efb85b0296b257d663efcec18a89494b74657d30af213e2658f9175c1822da4adea38859f3bcd9d2edf13c26a9995de7ae343f2c5d2c3781d95cad4dab208d1936d29f8c2a7b95bb9cd0f0c082da5127b9c2caae3bf0a8b6ac6233f7d7c3e6ba23fdd24879e302328ec47bfdc203f4f4c0e849a2d4b12a5e82cc49eb0873d2bee37826dfc539d11258a261e8317df50c903c349f0487d4115c6327607f339cbf03c9344d2ea01a423ddb8afed9962a23e0969238e97310839b30d9800a6cda807726adf1b9597f2ff4c81f13ec7396fc9cee714455dc46d21fb30887bef972b40701d91d33ec81e11d1be30b8f614f91ad80e2b1ed83f98088cb75657a9d8e48fe21420d7faeeb11abffd9724f85fc6e91cf0c896e18b995e78c8115d0c10d5505a407c3220272fa6a2f17a53305b4807b7ca8a6594e517160f2f5ea4656c1db49f784aef818367bf8c5d3ac7e2736348e79af803616246831a1d720aa7ca0310946c3966aa799c5c523377eb799100d473558530bc8bc1e8a5d5058fc20df7bd3fe03165275c40cf25e8d571d7fd244bc62bc44f0963f09970b59c1d97ee6109e2a70e1400c1d091403693e5c60c83cf192de20921b9af79a3f7fc24a0af625d686925b92ffc2396f69651afe1abdad821b8aff92932f690549fe82df46c93369bde50f630437f4e2f9b7888e90f2fc23a1a3615ee3db10dc549c97bcbc4a2b48b214f4b64b6eedc12bc130c05ae27afb45cb0de6012daf6894bfcb99aac660d72a3471292fb2be2114b382832a4561d1ab487d46bdb3039339a61e4213144f7c89e91287252f9f08c8c2dbefca9d120583bea337811da52098661cbd74c8e58aad92a823269fa897122d6e89731300074b6df5628fda8388324c80486e6c657c1e197cedbdfe781dc7263bb9d7920a1ddf827143ac61b2851e333736684ca3e63b27699c30338173d5895de27a2660a416f5bfb9cd0c9c7e2d7fd16b06d1725785a38c06ffc0bfe04dd5d50c25ffc72192cf4fbbe2121e1db7b6c0819c240c55f0f5957c20231eba2ca50cd52faf865df356fd55c481cbdb4c3b4861588572aedca541e0ab53fd81b7c29309578f469a8c3465309ed60718e461795315653e27d8cb0a6a0c0f13c3f1df32688e325f18d5d282cf9d79d7d95870549a37ee628d5932f6e8a767e62374942ea749ca65a2b55b07c981f113156d4f548bfbd32af3722de517a87d98e38929201e4c09a56bbcbae4262f770fee552c29f60de6a143c0531d8fea7fd847a9dc7a3020238041a82c572ee78650ab1e4274c724c2b4bcf6dd97a19bca1cb50f90eac1ed98edb971010f044cfa43f914c0996b1e058a168457d629429327b6c6a7591471a9bb1fa23f08c095e97491af9dc4813e1fc2e876a0e9edac10dc2f618778e3fe67e65d924e4771cafcdee3826f861d1244af1a32ab1ec32aea8680848a5f74a1b3f4314aaeb980033d458f27d795b3232a0f48b8117f464e78acfd2f6c21326478f28ea6c4bb2cd223a1dc972321401247c6fcf2c6e393397691f1cc93557cea8d634585c3da38d6252bc38d4b100ce8cd9952645f448a4d2113796bb42f776396c19147c13a9e881d07fc420d1cc3cf602639455cc72a335b5f8604ba14722e4972226164138056c73ec556f12966423e9d650d8f159f1b3f4460daab28eb4ef8497b9306a4dc9d371bd0cc29e2de305aa59b1a5de93f29b9c09e7f85afd4f596cf33ab13676c3984ad4f35356cc6a83406d1dba311efa67215507ed952375e8649947d0613b184c9d255451e7949aaec134da71b3d3e2cab009b3b0884ebf57475108ce7b4e7185272e9a7511f585c66441f181f6928972135a346238779a1bad0977a1109f402c3328f9b26aa0a605f602cf1b86146598724b68800798213cb7324ba495eb2bcfd53dc1d42b7538763881c30e3f0fcd5974111d3863e443512eb4d1dd47eca44fa507a6a0db140f4799d0932d0a2a67c2fedb35c53f2e6223bfdc4dd3ec75f3a0800ba0712214a784e615197d9cdeda6a939db40c0d542a36c200a63657ac9914d1d2b4b6eabf64c4367a8112921ed874b013da5e8d8c5e4c7e992f4d9e75d5c6037facd43100b24daa195603525aa040e116a12690435106526e7761f81e0213707fb61a9d21f39b0e00ee718624abb589f5d60d50fd54588f8540009d4f9145c9c80c520bb71650ed5b09cc89d7602cbeb7248dd8f8e06ccc28a07cf1d8120c02943b7be970570dbdef173a7a51284d8083e828acddd5dcdd1e750b3146a5660d0247741520515662b7925b4469425448c442c9d13b1635300f15cbf271bf154d02f2b2c0af012b87055e53eecd2892b39207b42718c4fc2a2c68aa75c82a7ba6ca421697ee201435249283af8c8b26ff0b6176de19f0137ff9a00e02e93ec62c26a3f9c187dd4b290a345034494ed23847bcd83c10f9b62c53a461b0e208c820c4e66d51240b0225717c7df4172527bce2c5b718e02fd2964b9b16d26de4a9dfc2ff8968b63ce91edbe4e0410ea211950655c745865d99546eb6db8115717ddc38f1d791c4c4b167319e8382466fb389cb96989a61b1e75f138e74c0cd5f39006cba7d183cb08a640a589d3b5c900aaeef6b7a8e8551d3ffba533b76a2d2228c42ba68bda98c07ffce44e5df82e11ec5895a8f32f58f4dfdabd834192dac28ed4cfd00849b98789f68c9a84364e511c5e9918fdc59762b1984af36478eccdce5096b8fdc9ca3faae2002a59dcdf43545093b4fd91415fbcaeda64430cd7056da72a00ff3e3e381994335c1b39248777bd59378c1d3cf409c50cf55c30e1656836e6c09f7c449acd4f9271d7c68419259bca5d3d5806f22b4930faf984bf5ad12f191acf5b8f904e40fa04254c04d21bb20f7327b564c8889b2f23cea6a6881ec0b0acc6ef2a69e8f3cb1be6dbf72f3a7b64bf2ae9f23749497511c6f59c2eda369661611b40d7d2597278218c102c3980396d641596e9a18eb181cf28a4d5e9e1550ece883147fc4871f59d36b0504ebd13ffdf203ab017b091b4055d01500692c2a19d0d6e1545eaa056f68b9aeccc501895c79842cfb5ac4c3450b118b0f09abaffdda4cd1636b43528ce604e512d4085f4e00c8ba561e18e1e64ab00e102934ea7be539f94dcfef30d23d57561d8b283ae2e4cd50b91ef1b104f5b344469b6c59bdf1b8b631eca203d82b9a7dc7ea521b24bf0526539cad977a3c7db447b6ee63bec468e1df4141b80d5135d2fd0c1eb45a8bac603c81c703320172accd5e81623ed93405584b6adaf77705d681ce0789472a47fc5b2b8bbf9dc6e062a5bcf8d281dfd3a8ea4499dc282acef5c297fc18014d99397eb9430f130ef29413288b4ead03b127fcf3ce6a582a6af0cafd2140fa0e348e4179a8c292674e1f8c63a78325694732038bcdbb180953b969fccdf92ad745b8f3a05c25cf578c2418f8b1229c667251d6ca60051a37d6ea7960aa960dbffd98d3b57a60cf0c96c3bd1a6127c2e5cb973f7c6c2c397c14105c9b9821477eea6c1f65204436ab7478822e20c0df19073cf8353baf8fa33708fd7c7a70b39977b84cbf409cd0d694634f158f2e911340b449bdb5c324804e2f41df03cef4e2ddadcc57ae6aae6d2c6e025671ef2139adb5373cb2e7395404c22c191b79b546ab887ac3e119adcd861e7460189b9480388d9fbfce20bc321a8cb7ccd9425d5b5dc502593718aeb07ab3067ffd45e13d4552f6bf6d349b333b0d838973b99e51a26c083f606d58544d498fb66c493e6174c739a0fb45e5909f56704e0ed22e4cedf8b96b9f0dd83b28a2b17e48b3a7dc26966a90699318e7d24212f23baa36566a92a5f94738854ea23a7c5a3e029b3d6d05b773759c74effa486f2294b43730179e3e30d47b33f163c91748dbdf8514ac8340fb550792464d57d8765b92f473016c4406ea90a4faa43df43eded97c02c64cbeb379e0bc7fc7ff941722e428c16d24e354e61ec01451ee2c52accf1eb107c160deddf72490247164142be22ce2b6eedc3459f6b8d3ffc3c36b5f8ac67ed47ec8c4bc36943adf6230e41c6827e62dbc89331b8cf1fa5c4756e70f8f0e60562c8981501614dc3504c5982ccf05a5c96d01c6b28253bd2db094e534084f69756634035b72ee7bfadab4fd734c9bba174d936fd529c852b5dfcf8e73032523369ae76c5fd3895f6649f14e4a0af4cf35d93c00918a41d776c80ea244eb186a99c7946f40174e16eafba46759261045895d4fa72c986e8891a26689a65913442516c8cab85dbcfda134bfac2e4079ca88300c334bd5caac02d72593d16c080a18c1f3d1227297a52b59454e61f2a6949b47f6cb006fa0796bc0d62c6ca837e8749c4260f3b01f9d3f5c583060c95bc1c265b6a02d5a38e497547a0d99db041b2f249fc50438ad349784433af1d1842a32bf449b5fc95915e3aa1e7d3c112959ab00c166eaf961a514bb0d4ef871411416157826b655549b5510f8ab6f49753b4edd103a11b706e289d638da220e4f49ba56f23712e5664d2e123b4089290196b6ccadf3224cc4669b4f0b35f314e925d309c3b2f50828f653b495600bc85ba011363d7eb95748d25f5dc67f3d6ae2bb840996e9f464671b9aaa80e5c38bcb50e470957e5812be6b0922f1bb83f5e12be7b65ad997d6874853d8cdee2b269738755a8271944b303889b15e7ad88e3136c845cb580a16adae88c0a1f212b6d7647b2158a3f403a4ea37f02d5d052d3a5c3a5e90ae242a00ccb65b570af4326bc5093a0ff894a2b1abde3d753680c1c1cbd62ecc249ce2a492d6bbbff89200c57c61091dea34ad771ced97f54c7feb3117a66b10970d585cd483eec1a6b5d1a925ab942aaaa401fd29507994e523aaabf6055567449c3e950d640ac526d9b0cab6f68855fa4148a7a4a64121c11795b025cee2b29fa233d0bb1cd68b44e5fa1e14394e7e99f471c9c29811bfc752cd8b26c194b37329040541f22a41b60414da7d0b17569f7e34d568d5de73fb839baa3bc13bf0a1fba5c67599101a6754fc04e3cd64b492b20e8d90bfcb5b3d38bb8c3ab49d66d0fdf1636cee750ff3c43eae78aca0539a7dff0220c5d18419a3dd909ced5cd61cf5b3f4bb1736b9eea58f98030d0f6542f320aecedd5f9dcc0523db8970ed416b0893d7d2bb5331ea5c714a47066073430a677f92f101717cadb7025f29aeda9944117ca6bcf8978de205a91c0ac603b9a12bce2aa2b366b3b20e273d2d7ff06abe867b4f4baf112a88c3f486cdd1d6cd3a101aa9cc49de17070d59547f0a2a52f413352bf57ee75ceca122d6038997c8b621347f7663659be7ffaeb907b76bf624ee2dbbce2b446f52365b747fefb989298e4ff8b796c58e3bf11c1c063981061ead4b541bae1bde8db442fc3b53f97ae5106136a86a09e1a10a8d76eaad8e0674d76d61510f4b26bff22c3498114c3a6a8cff1101f1b4ef8329fe29a9b65fc044630f72f640156b9a3832b6de3f3b2daf8a38dbb7f69b3340384276dd9d6663d5640eed6dd409cb9ab9bd789feaf2b11234e68425b33fe12cf0543799f6dc2e2264ea5ccb2d4c687a6feab3a33ccff970328bfcc5120654f0d738723baca802d3aa4d4edbe538e301398de66a608e95c44ff1ed48926dab2ac4fa2acefa2c32e2dc3fe45b914a88e0f6211bdf8b8f3ccdd2a3155930d7c9ae7ac146afb33d9737384a357e2a8e0cf588c9f4735908da8e3612c5acd3fb1a5e420bb07d05ba56d26743da329b50ab454f0c67e29dbfe0d92edf0e46f04fcbce4c03397eb851dd8fec5480092f5361cf7e07f205fa4922ef407c79920fd8132c6d2a2d055621461dc498f0b1ec86e7de7e37b99955c9c6ab1795da65646e1711e109fbd54a9c1968f3755ab40b836b1a13acb08508c275061cd39809d4853f78186564366749832877cb47e9536d8ea440983d0ebda7ac2e1b87fce9822a4147c30c3b614eac11e7293a7b3f1636c167e0ce6ee9a41ad01cad75de15356fb87115a19a795073a79ecde0dd09b440b634e109b2d7b89ec2c2c5c7f5fa4c48259f00486bd7e6729c73e178d76b5e29be49ef181f4bfdf2f10ce7132b594b748bdc2980dd1198563f75dff59d40f133217b7b6728a7d3abbfd2634881f1aa03084126892a5b9056078fb85efbba8e4b862bdce94b98128608c6f7d8f7c437ff5052e0c1b6eeb4bc06c61ca0c20c015e72451310db8e629458ee3a79e7260df9957f118233879e0c6c857414d80ad881f5b0654f014f46da51866dec283855bdd3bc9ea60f217cdb12795f1d496177374794654fe95790250d32deb3b589cf2c28f0592004a0d93d3749cf6f608cb4f88e0d578bbd11c040ccd65892b48e069507df82c9549456c9d3275f7280ec8e7f015744681a85b9741532a0a4ad99ecd802c4da75c29225631cf02a4a78394c30a0251bd06e7ed4cc0f02d743791dcfcdf4be30665b7c365e011308624949eea2a26d2a1cc2aae3fee114a69c4029c9b820f1e246f942c312ab34161dfa54649306242f1474a7343fe967e49af5520a7e47681e8f8b213a992ad00e96a6f1c4f514887dfbde0e8256e1eaeabe51b5587ace30ed5d66af10a9cf367e05e8a7faa9f83603f954fbe2c3546ee76efdeab5fa17a141faa22686252c25658ccfea403faff8f5f4e685c19497e01137ec446154a89cc6a8a80c7ab0519281d1ecaf2b9f7775226c958db1d2409f7c8fcd8e890c012b62830f828fe9ef4116c0cc42b47c614b709c4e9e3376c1416266542ebcac720bb9c030436a0c2d2642354ca51018cf0d5710e230e0f8759d1ea758efc97f277c6c58f414dd3b29722a32b41876dacca4a71722b7fdaaa6d94e5cc809823f2c4d5894f0c2c421c742e2162d44674aa6152b74978f33dcebf01724d7cf8bb574c8872ab73fb5226003b7a6104a20dc1ce0e658e13f2d69a1e73f970bbedf4a6cfdf4b0b8bfa37e5047374fededd614bbc2705bade28307c1799286e369c520e33796bd556d0a421dda0763dd4d9445a3cc7a3c45441164fceb0f282d42dec9d249d82d09ddd554fd081ae2de2c75155497ba937039ef87c4b531bd506edb1e2830df951dc29312644e433dcc167142b76514297d631076a911e9468a29893b430b7866704a4ef1e18e4d812510a9226b00554cca6c1eb00747ab9fae13d7a61d80f740b01997113148fcd1524bd435d7952c5cf6591b1957a78afac3d3bcd6a6a3dcd5b39951588b2a70937af8156e6ec88c59643ec23cc1f2d4643561968e58b07babe0617748cc4bc1e1d978fcc6d539782ecd436aab726e90973c8a7e2a7966157670f1e6ec51f61bef585213fbd2737c7399b5052195c99f9a656b3d389a9a51c0f4ebc6e5ccb0b2e30ffe88df5bcea8e1c691a81ffb5cda3424e9bc6d2005f54b0a1273f910167b4009b6fea909dcc3a20f6c1bd7c3e7edc3b0b38bd301414c78bc4ec94b76f975dfd9aa6f4cbeb6e3205942aa3b41fae2ef847cabf324b27214a3fc5a7af1a831bf8ceae0e95f54730c2a877f9dc266349bf5c607066de332ed2d1fd12e80b7ab06df73809cdc52bef594ec86cff3b1bbd97594309096154375d795781a2957ae3358cc50e3520a75489161365fe5000a280835025a825459908451f7cca08f09beb69dc4a817b2c3b0d23f6185761ddefe6564c3da2562fb624bdb6d1d52332da5f3ecf3c08c8360fcf571e4b5eef073f8607430c68e8266c202c61d793d8a6493306c9a5ac29bc457e69a02f9542459cdb0eff44f878b26caf13373d0462d29c5fdc07d437c8d8f652eb5608a3757891df7dd1b9c38a8453a2e270ccaa25baf0dc60dc9d6a25c12e0a6d29cd569cf5a9c6dd2e7238304d7f9e036662e716b1ccbfd80c9fd21a7f10f7b0c3de28147d4e9ce953990275bc049ff99d0ce3222351456954445e851fd242a3ddf1a051af082d5cb48ecc3468e7c75a3959aa14d01f21b43177d45c4e0820f51676387b5efca42c587b6ecd6b49c540513442c520cb711daad35864cd1fad667648b24be4c8450c39b9322211a8726c282b532a7c271793f3006aec1217655da24db21cac2ac7eba04000530f78bea89203a9b0ca1ee0ff6166e1fc1be506e25c9d3b1f9c99d5617f3c14153378e80ba1f373cc1bf3e24392a9c85cbd9f2f5fafbb1994de4f2305663a01e77162d5e3a601f755b38103b3388896974165f8f903fc820531cdd1974fee574a2e38aeccf0ad05df152829f737fe06ab4b2b1576ed2f5713a1adfc962479a732d26e986f157de6ff78c7e9da3c0fc1c6d35358471e8ceef401f1fe70632477809721b0da2a2105984fdb1659873c1433ce8925df018772448f12397d1337bd7b3db6410032e3b9518e73fd7bdd1128bdd3c6be62421a10eefb0ecc6ad6d42735148b8b0e8b9d3272ff2de2216ea5b1bbac32399c58b6b952d5ae4ed476dc8cab435126aa818474e8fdb0d5bcdceb1d08f87efbc80b7e628f4d3ebe31d45c981beb7a957155409aa1dbe507b3a529601f046de6c91837b1394632f8058b24bb187b3dd721e6b1513b1c8eddb8403bbeb12656a9068e201ac5de5074e5f2dce60f5d0ce9249ace5c9b854b9b1acb53a2cf4990394292e33a68d6d62ba94deddac32ec048408c40e2286420e20681f8ea221593f15b804c7da89265bf90387257f940f815d26f515f8dfc361f77c7e07bdced48080a020c084572fa3324e9984480a2cb678a3c2fbbb3b3b6ac83b9820cbfd6874e464edc5fe3b9b060194fc1f848718e2e511f38d866e2bdf60e0e0f4373cbbbecbf1ff3de2458612cf5e0531dd51c09a704c23e459c40720163eed8d3d2962eec634fb33e84934ed1d951295b84a3f85ce8ecf000a86e1476f8dcb5f47613b96d8e577881d85a643b2775b2ed716e3388aa7a30c579d02f0e5fa9ca72c5a2f0e5af1c6cd4e0c19e29912a02e6c2afda1d4faad128801ac3f637cbc565ef9869d2d0c21b255d0e15ed61bb9798a2ec54d18f95780442d78fb9c10d86b75dccce65abf8699c26dd2dbc6ed49f1af89776b851af33d795806cef5a932826f3b82ba6574c8c120bd9332fe13ccdf472b1611ad3a25e81129349b3f18fd4454394c13a0d5311e24e0458f760ca097da28cc02bd43a0d62a88092443d7ea42ff05fed0d1a7b9b1b346c365c397d98824779def9eb6aadfc157a92fd33de5e78d55b556b9516f8ab15fd9532b02ef2a579cafcb05af3d1cc08d6bf875e0b3314dcaf10b7314df1986d55201d0ee471ceca31805808491f2bafb33a1e332f7f703a8facda6a49713de1ba0fe4d24a332420a689b0b2f0c5bc93e1cc2616ed07d156c45a11dbb8f21bea8e30a88be5625c30053d2075f181ee5eca739d4b2ba48f1261a6bd9b5b7af71f39e3abd430da54c5905b3672466fcceb38fa40b484270233df8e6d34e205b73d3a7f58c0841c5f9272110b2c7d0813277d163aa31908b5929e4ec9822e9fa9100e6992d55b0a0a5f4cbc3bf58140ffa810f6c2e38e2443d2ac7d42c5bf0bceba1169201e66cba5a4c7c9011659ec746ed807f177ad055235dc16b044aee3a762cc194992a80f3b5699bcd62816f4614b1e8e91baccdf36d4db6a443280183bcf0d02e9e38d86585d81a208fca17c0b22ba5171c3c07aa00dd4206f777806d6e47308c65d7ab65667a70bd37f5458db1ab2d7d5e62dad7971dade9bbdb519f9ccfc5efdf2e0a5f188aa532b88fba22d1efeb66306cb1df74251c69f507fb2ae066f2fa560f963797174680cc577113b77bef45d15576a307f6f12b3ce89ec24eb0d749eaea511ab65960febffe7cb6bcba9eab9195fa560d0d4d2d6d5a0d6b1173b8f479523a158fbc39a7412eaa97fa8e5d637a7c6b47aa3c4e1e2e0e21076ebfb8260aed4c654213addf20faadc070edc4d276f9dd8dc539786aae797b64753f3bd3b121d1a853853570084fc81540db27cc66fb43d493817b4d2e3b6f9d9a25ca63a17d0f15af6bd47d1fe7e254b3a757d4d4a42e8def3e9d50067890c7586a6c6bd0b49a5c897455e6a8cd9bc8821f97bf7407c2a097f7ab35851b018b73f72d05e94259038c378853bde186aff8e622a795a49a147529da1936a02b05cc1bf0be25277c6726c79d03771380117f8f83b869d3aced65cf021764fe6e1ede268188e99f69ed381a00a36142f623427b6788a8d1d9038fed31e79e157cafc5fc376166d7a39f38d4c3350c59f3daf5681e2cbc73fcc3773f2bd86df724839277589ed865b47de0be5b320cb4887622a78adeb7af0ae12a2aac356e6d09bcea15832c087996751a16c703595b5b6f4e3a7ebcbbb84b4f5eeb68fb8a4f1c70f7473039e5db9bd591daef491e5eeb53167c2aef7878db24aa33ad0f3973e8c6701c61e4a802dea343fc8ffd4a2c80be559cf6ffeb0dd1ae77ad644f441799975aa583fa566cc793caf77295258d3b5a989c1d0b84e435d35da93d951a22aec2d200cc5a24c314deaa616022b68129ebb51b6ac04eaa74b785805aab4213c950dd462e0ab8bb7c44288c0dd260706df824200e73e3554e2f2085b06bb4d0f6e9b53d88db908c3e56bd9a93f26fbf5e4f0f29232f66a0a37edc11cb3de49b36978ed2c74a0520175542bb743558a0f259dd4566d7d7290147bac2f0449e845b379376b76271303b27f04089067588de9e41b02da639d7658eed1dcd452600e6592682e263070050900f10d558140d312d6266dc5d6aa6bbb09e3295d4ef0cf5fce07cb2915681981c188024c61f67b77125c79a895051afc068502d92e0c29bda80c3f592c8c47273be06e0a07e2a662523fdae73b5e7640dbbaf4332cbf227ca09dfd0aa3cada634a8469a05221605c398e0fdeb2088363aebad47447469b922f6016a10e9f44e2131dcd912e8fee7b470ce8c33bb1c814a6cd0aa94d93b572601f4b2658c056765bddcff83023483c537a724547f3aa86d84ee6ce562cbc69314682e9da159633dfc78cb4612bc10ad14cc171560b57409a56e2dc6cf709d768e53d173b78c597b52de9e681c7fe9e2871af1fbe2c0dc49ef4081ffe22181e0ba5642251c290580d18fdba209ad209ad4cad3487405828d9c3d1970325128617623726383db1bc4e970349e3316a033a79aaf09860340fec1b449da7de7e14174cd68194f199bb6c96173dd8ccf4e3e975948c7323aed3937467ebbaa39736e1efeba0b9ac0970e8f22c4717f203ab1d929f1e910929350b478c8c37602a827affb0504a4fea9299b398510314992a3bbd2c7cfd4ac2788b1f16b50a8348bc724f55656919024727ba27717344658616f0cda609f2ff8b1c32bddd102389c26427552e79f18dd0a54de3942552dd49dbde9e8c0b20811c036f7c04a5a79822b9ae23cea0f6fd4575b4c410dbc351a881f7bce821b9ff89b4587743548272198746a8e40a3b4d069516b887170055db8d97aa8729304e39d015f48a91033a02b46071a26ad588919cff6f7f651fc7f4be7498d0ceac815da7c98076938fac02f1359d5471585a742c07796a50f1009ebf977c29df2e374bcfe4897e578b19c06d0cb2fe16f65d0d612bf2ce69784c71d1d0c1ce5b8383b515b648f68329d38612c6265c25bcd81b0c4650360288c10b015d592e30438e6ec074dc3301df32102d60d6acb09f6f911805bcacd3ee92e52db22b26546f3e28b973ac60a29e1feb7380415379d3a04ac473ef8c6ab9e66ac9859b1c8b6b6473138cffed9e23ddea5a3f1ba40d211c37b2f45be250ba53fe06e31e25446b7c8ae84916054c138ed6a6e303a7c3af91b0ae0a9a296183d0d81fdfd8b5f84dd8875b732f23a8418112ec7d2cd95dad7ba3726c9f0dded9157e92c5f2d5729d51bd1f288677d216f63a99a0cbf8eddca42379241e533f3c4e9ea382598e5578d88260dad3446bd4d4f2a8227b09a0067fe65fc7d330c7381b974b8ae995beb3c4f0e853169333d137533aad263f9690934950564bf54f534ae25637a0b9a26802b539804172c1e6f2839fcbadc0e7041cb77b7f2e7f10b60976a05caba6018aed32e2b1768e04b0754cf0dd7b6b7eb525d2e017c6204f66701961a09f6c95345a4bf0851c1a9e969a102d09d35c622ecf2b181cdbda0b24ef4a5a2f048f86b7304e538f9498285115b7f1e544899e7c4f4f8315d733c2aefe278bb6a9d218a5cd64028c7a3491951e0b7a57709aa861530c39715e233e9b20f0e74a7ffd954cd69f27abe5be8b0b870667df0dd5cf726d6b330d9221e9779aa44ad8861ebbb13436991a376cbb4a8c2e7b8016a04ba30fb172d13fc696ff69530c480440feb27802aae1239c32f1430bd02e524245e86bba99b59c99a058fcbe229afec0fb1a3417757037d80d97b1800a30a73f22bd8340dfff4cf43aa57925145cb1e79224268bf5052f9598ce004660129d3c91f9160cec7cf6d096e6b7e33e91cacc06932d04b52343700cfaee8a07b301f6c6e687643b8ed8b9f7196b6710b156f1ceda20e49d49c0b3c425e316eae93903c2369a3715a8bfcb137c7f5976e48dd82a798eade884efff91ec13946bee0f8b39cc4364e34b9943cabeed57695e2303e1560268de6cba8a1cd6a39e8b1c6ea8895f9b2bcedc2f0b539c1afb1b29968a1c84639d716c812b1795f752f8d45d9a1dbca8bdc2c12ff9bea2aebd8004ed06dbe3029c6487b636ae1ed02d5205e59e432ca522135a91d60398aedbf41a6d3e8425236ad51f1081ae45b109ce42a1ce8baaff08e65867ac1405e182608331d15b1ae750b4517b96917493d040d8cd42d9327f510c507eef279021bde4c2762dfe16fdf84747fd0d3ef713f4efa4ea60604ed824b3624f30675755d22a0971151efe1fe12e442e5feafd997f92749bdf9b598ce827de2077178809df6f95f8fc78edca1652a2082331bdfe1a362ce979c00601906f46d260b281dc4435f999ce88b6118e0e4023815e7b2142f3e1399371249e0b931bc495a9726b92ff63daadece517a4a8ce68059e1974910ab2ba81ead24178d480a411badc82b63a22656d0b2931862a892f4c95e83a960f3324d64d6babf6df555016a021b65921290e40a7042b7efd048c9183794cf09ddc9689d4640b26f2783c9bc806230c09139063e3c0c3c30f926c3b9da45605dfefd3aa728225dfbd40b671653056d4d71a5b75ecd63be2cc82855bcac90a4c6a4263b595eb80a4d143b7c097b7528366b967289c6f85edd469e49f7953154d9a8d3e5a3718d42f3b847594731770c8f81b79110d3e500932c34c4e7cb741475d0c96c55f566ada223251bdab673d08ac57880145acff64e9c7e95f218ec83ec1337f0c3cd7ff40dd3a189a371fc8c785da9877e657580969c784b01cef460afa240e13136cabbad9e8ff130012d22f375702654575c609bf5c0d8380e1f219682dc412ea33239e43de6b6a62801d58ab5d2acb0df95d8e1e48a617c433cd68696a972f8b3dfc3c4b6eaea747d81517ff73c18573be63ff1b6cfae7c18ec2c8189af124a03ff0ea513042ff5d08230d9a785073366b9b973c961c7b981a4fe522f6451f8d177f96599d2209a1f4c08d33277cb568f30c9e16008ba90e35f53a0e9294f469415329bb3b575fbe3d5625f82a427efe3b01c5928ef9afef7cd588fcf9b933a2efedfc8c692621b5c1ce075c5611ee4ba3317b0bd477eaab9474f5c461509109f64d97190cfb547ef24b0e4a877a51468efe904dce969b04a1c8523f7e2941c1f73d99904278318917617abea0c301584418b560b059aca962e0f7689a66462f217ded793c7b5799c331239514f3a7c45524ad2c656b00057feb96b2f1fabddb892fb857c74658d9cf020399c049c5db095d80f22bb895b07932301708db0a9afb048bf8e684ad5012ac4327867ffe8e86b3763b84fc0a4be4fed358e4a6d30ae006a4b68af46019bcae4a2af6fd5feab04c42c221bf85836c61521a00196f3a7f8bc21a3944b4f12a09a0b1f6f4dddc6e22f899ff9c8e3bcde79e975bac2f345961eebd73d4dea751045cdc44ead03260134af49deb3ec009ab918e01f71e8ef7bd21bc1798d9cd21696031d368cb2d044f77003e7f4b5463827f9ffac073c8725385fa860ae9edf8af365643865ede58b2749cbae4a2b3dd92663d422339aea938a0c97dab019aecb2c26ae0efe29ab127e04110e824a768fcbfbc5b370e4b450fd1bd9be92cfb95ce3046807d4335ae28f30a843011acb00a4c71847fb4ef5a50dc2b722d13fcf85a6aa15d38420a6e81b02e9b45bc2d57b0c47a3681bc238b93a3e08cc021181df573fc22136f3671c6b403ed92d79e7b41b4a71fd3da2183614891294f472904ab041f6bb8c08d0636fd680fe21ea1f6ec1ff26065b7e9fc8088e992192763586dbb17e704d81bc8f4af7bbd5ddd7b842ae62cd9bc788e51fe620e5433e00745f90043ad8a1a9839c1258087f8266b8098f0380aa556dd1e196eb22e8d8e0e33abbd989f77e4d15e8791fa4bbdba9e50fdd9a0197a0753e4746d4b68e839d5408e2a7c7e908d8ff9f350ea628ecea46e5e190755e8478ad36c61691507f671c1e60f21e78b26663fe1ebc4a1a9e2584adbc34217aa26410c79fe462230dfe8e488dd9f86f22c4da087d270a5d23d37456f35d661996a4e418c93194eedc6f0e6487af4734e20734934f179b42ff869cb88f892ce14d2a5bcf4e972c00724868d03179d048701b96610ea7a399b9f47258d81b4b92cb1e7493662b72a81b3ebc77bf1f3a0073e1a127a517c3e41143c4a0c1c8e03843097238599842065748b0defe04514216960fc5492d09e0cfae8c02e084b8ce7e03469781099bb3030e71d11e3ae2f09ea235618edf2b0ed8ccc6a10069f81d456649052ce0a70a3167c419d54d3c18228da937bd0e7edeb65cf0759ebc90cc44bc9e1e0093f5be24e35c941fbe4838af30c436be8db867bfa0e9a204261e0176ad761890f1c751a29b54fbcabb8bc46d35535dd9318a2fa4fb5a2d476ab7f87a9d40453974c5dd1370177095b1bd954b405dc72a7694d55bb1043f882ba23b726098c32f91ef8883df47ca807bef1b8041a4a58a822da30009cb450fe59f1d2f854b75d9db3d8f0f4784ac9fbed65a0a2adc5cfbb610159e5173ccd1a4a468d2ae953f99a72b30d99620ebe24bc4cf479f34096004a69c5e898700074e3c19f2d2659a755a4127ae0cc327e900fdb6913d97a700fa7207c60c0e0f36a47ae76c4c5f8fb10432c1040f2752d44a947558f276d5ee433940f4d47500be46cda8d33f4609e6293f40c51ae26164622570b489f7baf33f918e0bbe121a63d180157b6a464598fc8355c4713d26d9e016a9750e48e94d28b66652c20f60c5f66b4b7655e3606559259b82e265882e37229a29b0570c0d5581d7932140c326953e6c210f781a6437d0a076468aef0a82eec45f59669a8a1c2294a552678ca788e6dd230cc669c9f84eeaad0348b996637c58c3e767491350956a983dcb1b145f692b33b638f51bc26318ce2239fd7d262aa042e2e726c15f6101cb3089ccbded0ac5208da5a0bb18a8f279cbe087d3ac5ea14275d2d8a47489e7f63e546f45a0c226d71cd5c0413407022bb1495f828b2d82011141465be6ccb657639061804116c1d7117162fc11b3694840f2b68be864f67fa898a0faba94d4dd256dc4da96d1171f6d69e2bcfecb2d6fe091e810701965984606b78a2a0237cbe456d30c529046c6d801e270d7189f2be1e49e0b3fbeec59820180ba7b261672e64a14e0637c90adf323e3c6315c9b6c881d5a51ceed2ae61670345304b97a02f689aebbf73270adc343a6bf69e86107823b43720dd06a2a9f35196762327487be3e20d6e5d237845b37001d569dbda656fd30231a6f7fe3c906277e476b22442aa5f4d2ab133492f4af14c9ca665d805cc6b51e17d3baf04ebb2025ad73096aa891491c6e67766fb7f1bb291351ad6209aee7d850931b74e8cc4ca9e30171e1c661b81c1bea2c1f807b7417111d8a32e13c6839b75c2235fb34dd3f06744545a94aa76817677e7c100eeadd846756d93ee5eb537358e449b56f43f834ab9e09c99ab24d476c8c16be5796720a82eb4f06a1f4f8d248a2aaf87d2b1556f24615e441f551886b2cd8ca169e0822a28c981fbdcec40e92291b1d18a0c52f26be38833b2dfb9f3fd932b4dbcb619b2fd9edd3d8ec424d8e506fdc6585e5efef66a6c3d666a9f5e01d086642bc8d422240cdab385e74770137f6f0d89278f9dba2b638f6be353014ea71e37c0c11aca5140389038bb110febf0a0365ab14da767c34bf79f5a8305e422180104ab28c2e0df02bd9a41d47a417fcc7bec175c2112c0cc3ec51f5ee139583ebc19e3cd06b21ee8207ef158235c320a00103e16e5c00382d7fbc5613c36dd11a7f9b10b42c9c3c50d6bed6893f07f1431b16e38d4e95befa06c7fa2f5943d5f09a14ede27a48e4e88e5a11ec6066e82559ae4f1b05bf42350c92eea0f74947ff6cfa869194af308f75641781ea526b9b4159a07798e4abc8b537fd34e6394ded23cf01722ac3f8258615cf854ed805873f89f06f04f9ac7b629d5691ecf02aa1f1652c29af950b45357822dc8cbe3d0558e2d742068f285348fb07b23a76e462f87a45312802370916fa96a1e842db823c1100f91f7f84f373375d8127850df20e50a5bf3d85283bfbb12d722d9db3156208d634f0a580991c26c9cddc074de832050f3d8128d2478d66ae575029c9b01f8fd4263542ac44b8e7b675d12a0f194b457508a6efc9b98ec280f412836bd9ef1954786bb247da162c4298d0b7bd3b99e9ee965c9cad08885cb2438d5747100347aec23c623df6eee5cf51a9c1d9d4587e0178afd1ef06773e6e6463ddb1e90102169367946a09aa52457e78db45a7d16a0b194bd2b799ea92659e905928985f6d40eaea83474a6de7c223cb4cd6b5018e687e43abd1a89d5dc3b3c24076bd084116a60efbd2ff909343889c270db307028f31c5336a2d926322c08e9f81e3e5b1ada949f80634c6252c12706511258caa927501b54d7ba62a9fdbf86d43de9ef49811dc17608b7a0cde764144b1b14f600e920da4375c98f79f47d56365eea361bfb488571bdeee64e95a50779a4dc488178fb78e0750c9f2f8e1feccb9aa9f91a9b6e3ec5ed979623bf92a41a075122c57f71227d490227ac1d271642d69a2848eb2836e35f3b9a9cd3a417b55cb92eaf78b65a5252744e5a8a9555b4f6dce6c1280ced1a671b12b7cb3bf3c1a5f77cde62baacc1ce3479db6c2372f6ef2c07959137a14be2a7680216a500ceec12abc90af1bfed61abf9b10efdf232c53ef87eeb7c283db45a25b50b2d0404500df4c51b7a2e61385a0b12660ac25090312f229d55e7a031576dcf6dfadb709e02f6774b5c2ba350f3ddaa556634a7104af7e6b40e7ae9219c1295f53e1aa108d8d05d0e73b2cbc8976573b6f3bc17bcd8b60d00af9ad3e11fdbddc6df3e86dfc152596f0b1510e029dbe6ed9aafa1977a6a0ebac10419567bbf51a9cfafeda1dccac967f9760dd9f9e89b7ca0669f6c8c8d518d6d7c52af7cda0afeb68d8036eec2356f5057b05868d8abf89f7a908f977af95418b7de78faba0b8d8ec6c7236fc1643bd89ebf830de21c99799622558ee886d6f43c5be9254409378e55612072976b4c8b05756710564c418bf56f6217f2854cba33d6e0b10f42bde09e4c6a2af332ac47ce3fd02ab4c4e42501e803f0bbe2e93340fe15b4f4dc0acefc5d4c9f07791712cac9eca3153800e148f8de9e0034562451cadf6c852eb761d7032992f3998e45eb3aa483226389594808af9d437f95e1b34ef91699c9230755ba4b56c89e67646d3d62f7d3eecd684f7db70a5e6bfddfbdcdf758d5e5e47d577516834a1293f7fd0b3c39f7f2f8b9f9f1484ff0c3ae8a331e6695d53ba33357c0d8778174a936af3c5c9dd78b9baee82c9be0e87468b280eed186d5eb17bd43288c5f99b11faacbee3306ec75a057bea99b9244fd18b5b40c2b6f02aede60119100b300acd26c3f0308aaefc193e43d3902b0ade096533965a23f112e861c4b9f2f2b36e24b1401bf9357c924f00fa3fb4035f0597c2f0a75150925558e1feb5af82e6ea079185d53a5e18311c4070e719a4195123cd7a5de91c8f7ac517438ed12ab9acdd04c78b88e7fff6ef892add3616d95e03a7d6cf3ce8180d7e513cc860f37c2222a5c7c0e605be09c06ff9c16c560695008bfaf9fc60c3766a77653c17945bf5fc71fa1d1b01b3d55c3b5226c20db7fcaa9f5c49323f51d9b94b85c86bd54f8ac905d96c69b2170500fa8023e200bfc80160404be401008821028de68109229df7f3b5d3e85009cb9d4ee81d2dd32eec55a52b50ba3164b4488bd735c6359d52f49b26775bd2c9800b7d1ec9e275fe20d502289d9de19d7f1a9dc2f30e6f183186f6e662810a5a00d31abab78f73c21a45caeb62ced522f934040c7c3e0457868211b5f5160a04ff3d81f691a25d45f95e069fe69a4cc22806a3696aac448c1199dc5329b4443076740666843f1628cc1ce5963bab8626d5c2edb08a1a993bc6fd21ae213925b4ce3bf79f38c66acd63fa831cb035835b65edd34c486f46d10f91aa238829bc6c0e8aa06869a9d3bc5d90d9b1bcbf5de3b1a2a18e9f7053a304611e607a2ccebeb9d3036b5aa3f00eaa450943081ee859df49abf135b1bd9a477b21b3aefbb0609296935eef57424ae2ca5f6899f8cd211506e7d3d09d925aa853ec96ad124202e1970110a65db0f04c874a275e883768e814b7a79fd363d0551eda0e1bab244fd395201871937b1d254422675cf64464f42f061140b04267da5a0fb265503a8677695f361a96f599e8a458657084c23f33a78b319a8ef1832170949a3116baa2c24402a16c879795e917aa1d7992b4123384bc42737f4c3a8c4854e3c624db6d482f0f874342cebc66cbc410b9f26b1f7a18ca9a0cbf73b124621343d12ac801b105e099ee91ba5753510090307a721df055180ecdadc32483fa914134b157f70a0314ae3ec478eb211cc0960f2cb2962c046c9a3459af7dadd47262644689206ea9075aada064eaeaace10a1148aed23484759e2b0179b919c331a669465bc30907d4341f5f15281ea436903349d28dfadc680493599e1b517dac166459f506a875374a58004317b8b495d8e04696f9fcd21b817fb0345503338aed6c3f6d804dd7c18cacc28bbddacc08bf295b72dd3560183e2507a6a477da3af329172e5e07b85e8f27e06f55f036deb83801769f5450090b7183898b61a318d2fdb14263ffaab4db7df7ff2a4c6c3e7c42efb4d20208cf97a042c05d649659d827fc0f675c621fe0802c2739f68fe16178052d9775f4896728a4dd5391cbbf2ab8b8cd69004d6f131f8c41943e279cdaa6849192fa27d7f6b1121f3de5538f04e06908ab5baafc6d20a04a0e664665fe1775ec4d2945e82436e0fcafcba95ee46e095beab37b3eb690242df8926c829e2c80104307022ea41332e4b52638b50e94daca17c0b82ebc8b858783ac78218e34658c0361b1490d82259edc839ae6db192a350f958da4de394adbbd202b1fe0c451e946918e0cd1c1876585bf0ea527aba22f1100ac5e401957ce786669a6e9b98bdcc6533afd8301b46a45c360a7e359881ebb1f4eb2056e1a3c9a941146ac681db35b95dc5fdb1077f907d38b746c88da4221154b15fe46276b5388f7b1f8952a8101be13db8bf11703a9cbe43e4aacaade56254d9bfdf4e311323d9de8ce2d51776064d0a452449610baa99e1d65eeb7374b6b10ab21e492c6778ff366e2065efa64ad58612720bb496bae4592bdee188d4839200dacf571b474df96d5be381d2e7fe1254ff88f79e1081925c1dc4a00060bffa6312064f14c21871a0f40f843b7decee75de57162c175d76073995cea00360eab8487ba0a7240f7b34967e41793da9613c21f5801fdc9e50d573261806d5228ce3e9a86101a5b8ff1f5971bd4ea1809187ddfa400ce273d49cc02eab1f72e99684394c79863be08c80f082f92d20557fcd9acb9208653711639654893e1345b2fadea8a794b8d3451afa476076b66a316fcf8af48e414f884ca5e18e7da7189917cb6d42b94a18c6c82c36c4879c48442128ecf95cf08d283213e4bd9108302267931215a7ccf01b0ae9c1fa3613b6b3ab99b5f592f406588f1f0879aeddfb15dae7f6cc1d3db1afaee14cef8a036e000827609a773586637311594a76999e31368cf701833fbf5a890925b61b687fd95e3ac587eae8615d4546b87ef652c11f3a127c97a04c95601e472ac37d7eaa2e148d04b05f7626b0f5e23ba9e2ccf58248b3e82fac7ce6073a4ec8ec6afcdebda8977495d84513a17c1610251d75adac1db2257ba7c225e26344ec55697670cd74cdb0f66bcd0dfde317347d373d0c369e373b84f899b0cf4a63c57abfcccb8bdc6ee11045b70968a5e1c0248c3bca727ae900db4bb567255cb04bf3c61dc43dd22200a7d140df0b32dd7ca97b2bf9627f188d488c140c2cb294e62b6e95dd713604b04dd414997e9435fb919972f298d6734ff15076e86d110e9933dfdf50d07124c0e59038460d74846b282e81d41893ce104ee3e1f507f90ad745a8d747e826aaa9c2e106ba9058d087de9d0f4f0fa33b1dd277cd604730b50065de21ea80ddefd768cf02231da630cc84adc24728f91beff35b4eb16af433e4ba294710c2a9f8a12146e1a587681ea037949a8f4d303e6d3359d60ba7e01599abbc4609dd849f903ecd2e9e2aa46775a24715eab1f34d981a74218d8add2fa9a7911a65e360b1cff721ca0ccc60f55eb0f48e17f6841359aa5b40c84f73629831b07869df9cea6984a28c7c17c978a01228440f49e30d4e8d6185006babfc56ca0431b2497990b74679e6720201ec4ee685f10a4d1a053e0f8f43fd7dc2dc25fda3b6aa00e314333ed3747b27007219e71cb0600240cb05f15d8ee3758f612886e585a9d30a4e28049cd5790d0f8782039ce9083e752d2d94ca441e297253837e124144a5fd28a94b3402f02e41ace7e65ea058b27774f40f6e906b51e1240a55bd3c1db1c289293eb635a0851b9c4f15ea93ae855ec41b6f579e7ede2031fb62d7dc373095658fc42ec0225b9a13f4b42fbe6a9c27ee7290175c9bf464656303b33d5a97a2674038ee64af6902eb5fedb0f1bf823065af2cb30452dab3bb65bd7dbf2ad0c1c43d9e42ee8fc980d825901ea53b42cc5c875d72861de71a2d21ba6af9fae570837d71b7ed75e5c7d7fd7c42e2a217b5c5ac560f7d816ab0cafd7325e57d0ec2c44775f72e56ce3fe2a66d79b6abcec17ae698b8c045c7f777902ea0883c11525abacfe4c3779169bc743bed5e6f9d1493b0afebbb1a878592ee37d3186e94a429534932b5a62490c9437badac13ee35974ccb4fb437f76d4395f53ed15dcbc8a25e8e64b7273ca0e6c6642f04d36068cd284a6dc2a5cc1f0d2a4892f107838b6b2b4f86356626932bf4f01f559c4c98a81aa22a4f28e657d884a55cd23b21e16622e9cf2bfaa5ee44ca18c5c15f9c8f0037e6f578f8b37e2f6b87b6eb576d27bc45abe795a88f96be03001e95214c1be0007d82a104e24843ad8b991c6f62619342b4203c4467ce8e7b1a8fc67b958a76846d6741f20b16bca9378aab0b4e16fcf9143f16b738179072842fd03b8bd925b9e3deb28dd6c31334ba8a1682ed5310f9573bd4e27e3c1572c95a5b3471d3f45b685858701d6333bc7b6d5f2a3e8debf74cabca9e11ba707fbdf0fc5965208e3a734a304641f6ca8c41e9d8b60ee5a3fbadf18d6fac773d9580bf60c72760543e598e26130b464a7c36ef1dbd6a6a5d232f57685025bb783987bdd50422518844f8ca0302864b1e5dc5f2a1f56021203c3919663fad08fd62f70139b833a72536b23b1d9410cc5106c25859614948583b1f227624ac43b8bdbdff894314c4a3ee179558eea75007ecdc5db43663f330349884a1393d2856f49a350726adc36a4b74a8a3e8996f63888a9fa60d56e9368d9dc95ec50e58597efe0d9780f308ad0b195647901e79e14e6b00f1e4f224ee94403e8e494fc379d5173d10b29dd93981e87e39e6d5d72bebd30fde44ad782801edff5da2ba60acd7d30148e1fe57a92dd5c731864a9df74ed317b889cf41ec933f61442ff461acd155a1a14116cedcd0ccd88b70630555ef2ffe0d2aace49719354dbbec6489bc3e558236f54093876e9f6a1198a81e3b9e96c3095b9cc31310371429e0b51dcbb731e20bb959aa4ac72c7151861730b3d5f19c26eb00731ba7bdec72f1a1ff3c0d6b3f040c71bfe6048cab93631a42851d3252478fd432e9a22a918826b9735db0d82c60b2939af2716f743f9f550aa47b68d0b24d05a931f9f8060e3a00aa18e36dca079e619b66b49a4d84d05c4fdb574551d965134bf20d60d5e5258293650b497ff4bc457ce5409b65e14f64c33ba9342ea0ab1ea2285902ef443ad2343d162ad54273ec4d5459e1b7e6a658ad9f1d95fc4ba4a0ba51e8a126514a4d648fa81bb793c3a1e6e40b35a9bd5b0b8a394fb1fcf38891852a3ca69152f8019ab18028c750bc90adff5eeb6c34ab4f745e621ab5c732016fea84fa8628091a185e926d9c163da2b7ead3959b258582dcffc20c3b0b09df71e787c7ba96d5f019023f19c8b547ca82a443c3cec31ae4746360fe02098ff458dfcc9b6d04b1f9ee76154af329642a838de51232d9c0188de6b50b0e75888b7518d4e14f8b969006c8fe00ad98a69c4e45948a103503ad859cd14f81f1b12a9c3108616330a06be447483053ca67938d6976a8521fbcdbe76378a4622d9bc85436c4c617d4c04ea52453a4da88c7891500a70a6dbc6212d9b74e129081bef11b19887f1e617b3987fd35350c25cfe0705447cf5a3b117267ff8c85d93f551ace9e043119d5d10f8c1de2d0a016a2469b5cba3f00747af8c4c19859124c38ec5e4c323b46282dc33f3084e96e6b2cb4b6a302148265e408f97f7d6d8a8e9004f809e5e2f2a7cefa25232acbb31545abe2658d03a017eaba84781410d29461d10868b8524dccfa8d8bb3376c0837a8ccc5224c62a3d7bf4a70c9beff6aa784bd454564935ba176398bfa98a652c7c512eb35983782372779ca78f87c1bdc8f323bf75aba83b2c8bbf2ef1e4e305acc0ef3c8cb5cdf4ea2f02529b6d719753b672c36e2f33eb9a25ce245573973ae61972df16dfc9338fa67430a2fba4840b8559ed087d7bacc98d0a14486af5f2ae316b6c83a67c3ee5214182ad79db736789d8ab40613bd083ab993bb09e0e3ab83624feb92b08cbc0d7b16530668bed56b8087e23f81dbaf85111f4a240046315f4823e1471710dd5e0c9e844aaae412c061eea958e8200e3b54d664ff104ee4c53e6cdafbec9bed14e516b943de2864bc417b95316b89e04a68774bfa8cfcf840394f5ebdcd008aae2e5ae08dacbf3dd4bfb47731e4d2871ee22ce6f3ac985f92b154fc499f1d230070281a7e37975a0534dfebdca85731a812fbdad3766d2c262ec658cfd6843268c073bd2c4d3b7f7b5f65245709eb53423b5b83a621c78eeaa5c7ab222165e66984fbc961b7aa2221dfa35071bac55318ee7405230c4aed85098c1237ce1887c540b5e34753bddd6b9aa254208334bc6dc72dc997da14344a3e73c42cfde3a935599196406c2980ac3bf3a78a6d6cd840e2ca80211a328b35935135ba5c495d8c4c9ae731f1f8ab231122808604557ea02fa52a840ccec68aed8ddd9016c0fe87ce1ceab5c0e466a845488d596829cb2d88ed581d0ccf030a08339d5b30d0aa9ac63139207642ff3c2ca11c1e77271bdc239f115c36786cd01774445eff59affe054f8f85eb0530ccae1a5967000a340a0482d13c496ccd50040a1ca39251bc4e65a1fdb795b266f63e3d3521216defbdb7947b4b29a50c060a7f095109944e4a27a5d92e63368c5c6372beb16d13db43f2805d7093d672df4020728c3eb574b398fba6b9536e746d3947149005626e66f1983fcba065d9416f1b2f9ab105e9d1bc745139a346423468cfa8cfa6a74bd4a04836a5140d2336dd60603f878ae0b029a5945227636c2c65cfa6119d426992a954063d458306ee27362521890e6c984109243f55502f8e28028c32b2508309204a7554e103921038b8f0840d62b33946098a508a42c516353022369b93e90d31b4b8c27405c9e8274651300512608881f4f3421a53f60c6683386cf0206865688339a0618339e06283f74220e0eb0022ecef5d08e4a330536ea0b7771c02f15c98c8f20926277b46a5a4d16477ef6846d0c2095aeccb01d3500c9f0b81e08c4613402e3e6d735684bda18164cb194ba3d15a60a2d162540a9572c6949ff94f06479b1e7d51e489b10425b634383a49713c23885dff829d58714d9710883d63095313bbde1502a9483d78c1325b6480831becfad6085ec0a20d33ba88818c16c828828ec8d821a80d26c488390144142a162c7090c3d191112e60d28489221e627926091139f82083234943c4eafca9b5d65ab72676adb5d65aeb2a0452551fc018a0c7ecc790194a9b42b1e9710d80367d76d5c08a4dff2a3f6cfa579535363dacc606419bdec6c8a637d86253bc0228de9cc9d60c236a0b7906f69052f4588ae185b444c452d3a63ffdab8c279b96b106ad59bd41c6143f8050960b641001b3e9c1a14dcf809b39f35927c6c0c1aef7422035bf361c8506398437db387f013a3393d34ea2317e76bdadbf6d88c10608f4071ef23c1223885db1048228e3882531a480c91bbb046648a93509951125463f6f86b2f85a68c833976b4e22317876adaf2c16ed64670094b8d8f58d3c035d30444a3f3e40d67308c3674b222a9aecd9c7c49edfb12511184cdfd09e351e5295b19bde159361581829276d3848cacc4ba44a23311e3da4bb69120e3f930a7dd1240cbcb1ed5dd81687131159bc97bb4a4b5f43495428120cf948568bf572a8d26022a4ccfc11464d1c6d28908cf611120e923cdca8a0f59a28cd34e2a32159f235a13392080c9e3dbf9e3d63f6cc6bdaf3fbf99cec09c6665fd09e74fe2071d213a4442887211d8c7638f2a15ad4f6fc9c5d2b4d74668687d8436c49fc4c2849c44ce9b167f3878a52152696d8b37965e7fcfcaec9a3e120a43d7fadd099db749b6ed36dba4d98867970cf6dc23ff367064da18d09980d6ce8bb17b4e7778436a3bdd5e8cc6c5bb2f16c3d1b0f0f4fa591b19e253f3c5b90164080df3894160347599599e09364e6a5524cc6bccf645210f0de28cabc7f32701429538c226d21ed43676694fe541a2c449db8f25d414fdaf3b3d774122434b4a9d19e38a8cacc6ddb8636a31d8aacc0a483f441ef013a448768b45d4791851cd99483f451ef0114a4d266a2d1b61dc549ab296daaa5d2a629e141c2437b7a94502474469c4ba60f0eda731e0f61231c8485bea143f9648cbca4977b5eb610c41847c280618d169488c9bf2a8dcac91b3124d98086232462f2d9f549167c5389b9444685204f1df29c39e41d73fe53cec822f42c0ca133b2c7ce350372c8cf1219ec90a2837cc6a6df51c65ec00892e693667fd2d24c2b8288ac40676813c9d2a6b5394483237b066ed994d4b4797a96c81d74669667920a3d8c2a9154848521442e4067e40920e5019ed852d2070972abe012ef4d95a993f66c3a8536cd92c7bc0c32ce28475c65ec28abcc6c852ad6d1041adb32add3e1cb9652d9922a952dcfd92a5bfe2e618b71a5135324fa929f94d60a56cede0d739d673dafd2e96d98ebae873fd08260a513bc1efe3ad00b3bd00bbf1388ca36e74a67fe4e202acca794caaa54954e15989f9ca06cf9140d423dc9496cf99590fc952bb44669572892a6a329571c4da18264347fa6cf6cf2337de693b98456b1c224ab5c69a23fb67c1d9afda07a26a79030774ea990a8562b16cbc9966779c2dd8b71d7799ffdbe4ae7873bcffb3e100c4ff674aa749e3e300c4f27142aa76c2a55e94c9d5039a7522ad58a6559ac4a272ba55aad582c1696168ba5d56a6971b95c5eeccb4ba5f3c5e50587980346078e6bab75a5539330f70b48b5984598908489232461ee1c36478e4a678e1c58d217d5f22d2dae10cfeca67a066616e8228a53dc4e62095c05f6e702523df3a0cce9096d5114ad28562a4e719ee2afce5aa53dcd723601ac7c3266a6a347284b2e324a68cf5c3a90ec791d48e80ca5a821fa424911126552032add439566c6a6fcec209e01a38e5049a825d4918e9e1f29e5e73c9efb7b9661b32c9392f61c6719b691c0d0810ca458438d2019e4918f104d6abc40040ba2d8dc4879e4446b8c5ce76c8d91e94f4320dee756c3064f45cae9b93bb985a7f034ce72f6e964c308d01d8e9f0c4fd8d921a88534702002c2e28bd8499f4e5b08243ca767395bf4febd625000f7694976792b80daca97c7020c9df9460f771cd775339480dd1eedba8ee3b8576e54c1ebee82a921a0dbf3ba6e74d157c52fee7a729207bd6b9e4ef9da46b0e7e76908f65cc1b5adddb3ce260c9dd9c60c2e81b0ce8b6a537f43299533d899e4effa1b39dadcd424a1cd4a9267b05d613f1b66adb5a9bfa987d5d76c61f71750be7892afe433f237ace6afec82e90c9baf99ffeb7342a5d2de6452cf9e3d8967cfc7ecf8e28a2d89be90b267dff3345b127991c47e0c8d00bee36cdbce71d46e76db28dd3298459e813b26c79ab6883c9354ecf9db59f7db212c93405afe610d25dd737805baf16de896c2dd784c4101dc0a74d3d3a3ae898a152a55e673535edab60b2abb86cecc2eaed8f33d7eec29ca6495d9bc4b3d83c95e652e28837191e961446a647759f19c30fa82d564de6d280caccaccdfd3d7fc0b4b17f9af6ee44220f5f2e0b883beaec8b31d3d6a6c76c831e8d27692673349776b2d0ab07d4fad75d2f4c394c4242592b5564a69bb14eaeea4acd6fb3e6f7303abb1564b09dbdb25099f96370708e7fbc69c8f044c789cedfcc8b63c27759da115265d0f0f303094a6d5b3a96c4a8f8a6e78839e684538a4418b70a8422fa574b39555b4e95db9d65a35379ecac0dc0026a9ce1084cb0a49693194eceac182a1af19e41528cc07d0033402633003e5d98f84b2b016d9ce0dc9b64279a6804d5f3f4739f1a98826f2f552525a4f475152f91d99af99fffc6bbe524a040c9643829516b17c12b0828801cde748329000e228460219450f1952627904121f30b194f29d64bb5e4a8bfa048fd233d93ebd6a1460bbfe0335032050c245ef72773dc4ef787f23ec1e85fac0d3fd5c61db179ce09cf71eac09e7bcdf15221e485f4388dc930b50806df1321c9e9d2dacc71a4388c884c0c8288d542349eeba65d10d503685815b2e492bb269d2669d3029e2e54e62cfc03d84884cc8ac7b9e84bae78eb0851653ae1b7739859a69d49c735a8eb3d6da534ec368d2100d10a1331f67193587d4712680240d1019425f9585099c2aa9ecfa7a9623382eb658b245cf6c262d2125214951aa328fe55603e9840e90a032060e4fec90d4f57044680a1b45cce0862701740687beb8d3c8fd86beb8cb84d4d35187be3e0c654c9f383c79bed3696e9825e2cb31add2e438179e6b9dfb14aa342c9f3d2e34c8f30a140eea48880c46c66878c249bf3cd4e2e70c653518ea4f87813e74b90ba6f3d1011f93c60353863ba75255f16e957cbd4ed61840d2cc16ba49933cb87bff8d40bcdbedc5254abe2eafb5eefa5c82329a3c1c8737c77d175401c4913eb673a780a4e9400ebae0b8736fa99145135e0e7321e2717c86b21ccf4e72e849ab32dc71c0e427b9496eb23951f6f222c3ed741b2a6a1dfaf02f5a07e85ea7fe547f3a4efde977d4f92e8d7a8b56bda5536769f02c7a754ff5fc4e8bb2d34f90b1d3838432b659f0eab59a0a67f254196ec9fc51e94913229bb41cb4a5072458ed7054e5828b1483ede1d8b0c248a5fd6c6cc0a00474c9b2c18213cc3786aa8cc46ca800498c36546524268146f3b0b84bd9db92888a13c4808a24ad2d898e949031534a796badc5dbb6adb5013731485994c2d03b6a80ca50ccdab34c6799ba62460d804a7868c87218ac74d2cd3249b12f9bb3c53e9bc58d3774d8b5fe0b8154dcc277904608bbdec3d40c20845473be605e5df62ce6486e24b1831976c0c5ae6ef8ec7a1c02a9af25395082c70ca41b1ca5c1013482ca882286104b8e36221f82b8e0951bf015a54dcf81b556273d54993d0363d04615146c0ad65a57e0c6ae6740ad3607511bb44d4ff10e941acc586ac30548f820830c4a92da0d3d081dbd616bad7542d9550556ec5a6badb5dacd060d7458f0e4c6c7946d91155060c2865e5ecb3a0a400b0d74a0d27800062d7620d5d81405c983da91c81059857e26e927a5b5059f8c9122489a6e44415a99f9d895296f2c463985b9372079d4cf9088933c934637554a298db2b4e236ca885499faaf966537f4552b963453cacc89e710125f96f272e79e6d8fe5f7a2652c7cd13266c11c979c979d987da8650c7f4772117b16ec87d813b1150cb58b8c8597a12c1cc5ed38335b8a305c8edb1361a1d2b06e5bb793d6f273354064087d2541e9aeb6ef2accc5e78039a845f1e161c4e760396b8311eba457bc77089115268dce20a129d9d6de6d661e3a337ba6e55aa65508600b81295dcd36a4bb3702c15723db6f15c46d94c188bfef5acef25aebac7f303ac7458de32fbade45b33044fab8db1ee5848875cd9ef0398e133ec79803b43da73efc36e280c731e6d083e1450ddea5656c86a2ac7e7769d55b74ea2d9d59fa943aeab5fcd32f78197257bd564bbd867af85a7eed743086beac2672424ddae4993d2a2d633bb42155c6be4e5ff6f534b3762e4d0d01e2cbf9fb20f278d4e92e7f471d201c1d5963535343e7cffdcebc3c8e1c7380ea88237fc79cee72bccf919fe34eec7512eb57985bdc8e73e716eb7580ec75e6ef7580b811075f5ee7be8ee2369a307f8f337f471c797c1d3ccaaa4b0cf2bccb2543a3647361d4e0850d68a4182e4062010c76a8c28ba39858e76b95a932b41efab984d6e6e53b18ec250d77233816d6786a62e18de01875c5e82b46e75734436dcb2eb62c9201884d533352136b9d07c7603edb12c08363adcfc2ddfa2cd5fa6cc592dbd5720904cc6cb90dfd5ca2649bfa3a31491f33db1e5669e8ed6b571a1cb787d95c56695e6e3fa4d2b83c34ba7dbdaea4bebdaaccd652aba7c711eafa93de89853f6127168e393bb11c201c8a430228444f62e1984302284442c4e871e819fb26eada502d6333536fdbcb4ddcb617a5f100f6e306e768cab61b37334319a192ec514a16455146958625e90cca88c5dae414999e05294f19c9c37eea1a11a68c3dd5a72d67016603d8f61b5f92873d8b510adb55d8fe8d40f0e75310ef28ab951e043b9bbb0d85696de251afbd1cfc577578aa4ff7748d1ab9eed9eb257d9cb66d1de6415a8719c52d48cbc56d144d080ff320e161461d201cfad3839c0e73985187fe7419d3aa833a7551b3fea25777e97c17cd72ac4f90b15a8d75d56baba7ce695116fe04190b4f2e5b56abc1c0f25a8df59aeae16babd7526fd122d7d22277d4650c9f74a8655db1586c4acb5898839c4edf7490f0a873da84f0a851070807fce93aa887f8c5658b3cff81dc65e053382186d23236e55565ac956d2f2a444d3969913bacca58911b653206a28e50462f94114ab583a92ae9b3b572f7461defdcbf91bb8e1665df7731880a25d03d473ae90c65f8b3ea29abdd73ff2a95b39ea4b8e15107df7b37ea00e1e07b9fd5b9ca58fbceb3d65a4ba9c599bbd52c549b99cdfda6d2d4bb7a324fd292cb94dcfaea345cc100be358a1786d657a37855c701efad361476efdde8d7e2935b075be34d95e16c5ca6641da0ed3aad83d7595df5299ad03a789cd628deb76e0f7e1b7156636bc469bdbe35caabce5dbcd79bd24777f963739fb57a6c6ec794e13ee3b610486b7519fb461cf039aa83a3e8597c647f15c49aee4edf4d4320a2aca57a0dbfe78c38adabc69cfad63b8daf9ea825fae2bcdfc4402d7aa32c5c7db58caa825242296dee1f0d753eb2bf0b5a3ae31de1b17406dfd3d5f4c8fe3c7d53076f81d4c151c416c817f1289a00be751cf0ad3107083f07a51a734e6f7d27364320293c606e954c754f73db535decc9c05107085fc71e7c9029c51157c45aa34e7d7877eb38f6ad51f4deba8ce1d48bde289ab07aeb41566f8d3a4038adabaed3f2d583e8b4aeba8c6dfb6b693105154aa0fb5ec6f2145a17652d174f90b1163148eb9d773b596d75d56d684b8bf7accb588b6e39cb83b4ce1a75586f9d65d401c261bd751d96b75cc6542b164baba565a5654cc51d8f38f5e1afbefb72faa6ca705ce69ed2b2da5419ee8e3b4413ecc1e3d88323779c7af18e99beb8cb98abca709709e16ec3fd86ab591413b8c3d1940db7d66aabb596db26c7a120eb66898456e4f9cb6a6ddd28cdc5f7f234dbd940d0dac4c4da59ed365a5b47bb7548986cdc0d4cb8dbc4e4e2262678dba6adeff69cb38909e6daa0f5470d780b017b435fb4078c07bbe960e35ee02e0f17f3b06b9d55f6f0e0e1abe49331f647cd052dc510b8f7ddd5f6a6070c863b6ec7ebd5795b17438ef9bdcfc2b85cf2ac7c2fe5e7c9fd85a09c823f0fd750af106b94b167b034d6a0c2c61a56ac51340395d8e224a53584b0929cb7245ac3c91a3fd85e8d2634b00b2b2941b26d1d085d251892d8255c186898a06e36685b185830a16df0b4d1d3c692ec7a810e4d4c4d4d44613c49b286d05e0367a66710539c5ddb96446a1ced2218a670030d342e2d835b124d31c64532c50c7ec82d5b124d21e585299260229fb6249a82054a533881a961c4da92880a33d4a0591d7cd3bba89ec9221f98240f49bfa9393b75dd5fa594d64a2915524a294305400d207f3a535d2e8c6ba575a3564a29bdb0736973578b4fc604d7d342bcd982f944ca0c2169bc7b6391196517c167641c26d9f77de0e6e922bb2b21efae82074312e8b652fab076fe67ca8927b661189eaa4d4d4dcd939e276178515426df03ff00c00e189297f4715f64f2bcfe2af3c386e4f4f8f614e11967d3688869f2d8a332287dd4e30f76b9e573913982ce40e02af3032e72e7924dd61e1ff8623cbb551a3dbb76972095e9c61a103ebd911ceb5e9f6b772339e6bdded575a3b8795eadb5d65a3b975a6bf55e6b6555d76beaf5f45a5b5ebf8aaab5d25a7d6cbda9b79f0e9fd2a04b772fa5bdbbf4ccef3c15b88bdb081b6bbddb466badb5d65a6bad95d65a6badb5d6bbe1aa823d08e6a65497a085d47bb5de481ef52c4633dcae45ddd65942590df5d34a7f6fd12a969e31aa455968f583782b9d42d15a6badb5d65a99504bd5eb523b1c4541c1d80e15d1a25019a13d35495e406364cf6f6a818b6a88e2cde3a1397bbed81f9644453d35d82730b83704ec2d49ad28cf0c60db579b0552161efcd8b31f38cfa0b4eb5d4890d8f5b761d7a2122ced7a58110d33ec3a83925da7144760c1c650ce3fc218b78ec800c3b6b7c73533c46d7f7966510c4d70d80590278a6d6f8b789a72cb88a5a936a6c70f29455e2195a4951d7b26af6c3b6bdbda6d2fad9547f6b204dbde1e67d75f45351bb67d8dcd8d0c061eb6bd9021546c7b2245b6d8f62cbc30064d8967c9b69f3d744616c1b0c3b69f4b7e5a7062048ba0a06d5f74840d9b9b1cc9354b91b391154011d21e9c2d96334b1ff09ea00d3523b058789e1ca3a10fc11c81a819c9b14a358b8696be76f33674c84dcf12d38bd18d6c4891874df6b21bfab2463edb6de8bdd9965c23d44597cbcb41ede2f2f02f2ec721762e5ac6346c0c8008dbdec82e4064852b464955989af0edd163a9c7528f1ededf9520c401d2485ab1ed8df7cdfb0c5510bbd1e5b775115586e4617f35aa0965c694b10f51686c7b895a923cb697bb2ec4e530df6438eea275e8c1bf689d97bb1c07cc8f1e2f1aa8bb8bd6a90f378db7eb2dc771bdc5f56dc469b938e650d7c5f02e1a34a11b714e2cdf595a1586200b4b765ab2349e1ba34b5550287da5d7c1f1bae57c719baf1c60c8892a17575ebdb4dc7ba57b750f0f5ed43216fea4c1cf9cf3c30c5ed433a6d23a3017bfd23a403a388ef272dc7b379611cb88c5ea24d875db08829d7d5570b9378adb83d4a31e845efc076ab1bb8eae3b78ba8e3107e6a7e7f84eecfbbaeff35e826070868f91fdbcce684ad7752761ee4e34213f3c4ef69e63ccc1318addeb51cff7469cd3c131076614bb7ad471e8c5d38b3fa9fea259eea2592eddaab1aeca39f5d66b35969ffe9db26a95532f23993fe5750ac118551d1dd91819eeae4a63475895e1fea23336df0f4a23ab0cc701518f02a5a963cd488ed5d780ee8de0d83c77ab6b4660317b9e1cab3cb058bd911cbb8237874fd21ef520f6a87a54bd0e6a96a3583167117312217d6c3fdd0c30b840623a31a3d85c9752a96418de86d214a5f4e58eb210280e56cba562a2b5150a2b6fb57a59a50ea35717757e082f4fa9f4cb593af5979754085ac6bedbf9984c063069260aa60f6cfac0e89d3e53647a164cfac0e72e00493389088205c1f03883e83d992c847f2fa9cb50ec46994ac5120233d65a17c71074ede5a92993b90d7dd1b596af9ed2b5d6f3838c829a4038424a0a21e8358df4390fcecda3f1b399f4cf26530d478743cf20eedd57bf09ca847010fe7ad1213ca54148a540780820fca5674c7cb98cc17cf0d483c4fc65d4393de6a9d363ae83fa074250ffe0424e8f91d132f66d312c9161e2604318347d82421eee03e19d9e44cc213d8b9832dced2cc06c2211148e40bc8717a71627be8eac2e5432eaa7cfcff0f60f8353a310c24f90b1101e0484836067770fc7eb8be06802ea1f3cc8e931c79546e6158a26ac56ab9c7ff5d781aacb1c477519d455ff461cd65f63cee9ac5f1d827e39083af50f34cc63b4f86b977ba0757cd439ae358e1c87790dc7c5979754085ac640d032f68196b1182d631e68191bb5cb75bc56cbf11acc5f5ec3f19af8d4e74fea4584c19143878b0e2d63397668b3c9f4593d9ffb904aa3629d63b9abe5add5338ba5caab568b4baf543b341aa597f7aad7a3f4e9f638aa51bc5f9d46d4550f72fa6ad459fd74d5a80384b31ac5fbac23de5410d5f3981a75807052289696b1cb714f9d5047485c0eaa325c1122f4c5d9d7a32d3d2001cc0ea6394e900f5ec6428d5a4aa198c207c94f8d3aa9e78763ce12d4322136379f055da26471b3f5a707a1478d3ae0e94ff43ae1eb65cc5afbcd1ce2f696cd254a166283e2b9b11625ea58dd5e48a56139cced5968e100c200fab2b7a12f3b8495e0b07b10fa965fbd6913eac1873aa9d3835a070827757a9dfcfa1c1ae884d3756819437da54f9f2190510748763a4a06b19c9dd8e93ab6afc0b31e24fc6ad4493d3c6bd4d1c9df0c45f042e8c30f75cae10dd9dce00c41415a1942924a724932c926d4525daa343b762ced58daf674c70ecfe5fa4619ea6433e48d40f03d2d6e075f9fd2e226ca5028154e90b11a8ec35cec1ea41e7c10faf09b16bbd18450b37ed2abe3d0aec3e896bf68d545ed7250b71ebadefad5a28cc55ae1d03206733997d76aae5acb6b2c5f5defd0be158ba5d5e272b13635942c3fa565ac4ad63655c61ea56faa8cfd29049996e86b09d355151f0576625b52947c97f471da73667af8d37ededa7620a38bbe5c552afd3c489a472a8a74348fba621e4929a5d0fcc4b3284bb15617ddf30ca8ed293425a6c805257bf6431a552a85e84c1274a61e416908de8656d77cbde7bae309c8d8761b8a4a421f77dcb5dba6ad96b1891a4209a184f64425550ac280a22e6c1933cb2e5364518a92d3e146519efb88a37347eef8a2c4d9de7dc3f89eeced4eecbbba75ef288275da50d6223fa4d2b89eef9d854a93bab7bac7724f32a95862107e97a1cce5a18bfebc8715e8049796b1675defbdb7b7de651fa4fef41d2b4f634e4b9e766231a97195f16eb5ac32df912cc124708a2c2fbd4f11ccff86481ede598cb2fc07f35e73cf3d48d79d1b45a9d3fd8ea29431a95df4e59dd3f73ccffb10ef44bcd350f6c2d24596e0e96ba991ef56c1eecf82b6de862238caee4f90b1fb20d993f7eef6bc13a934ad7bb24a2d6b19efed71eeed9803043ee7348adf3d38e2e4d731a7358adf289ad0dd3e48778baf634f3f8d3a32d459585fa99ebacf9f97bd5fef29ef2aef2bef2cef2cde51def56a46187d796719ca529e86a209f6dd85d4e39fb48c5d20ee33cd529410dc9c09385d7232a7b1a6ca7035303a73fa0f4ac3bd269b7e2699cac84c3b45a34ff1d06726fadc44cfa28568960952df8e8ac678f104139327963a1a746564d60c591d1979f63de5d0166520698306307051446cbea6d254e00b2a5260f1c41741f4109bff2a8c2c43203285f9864b06960d59ce4026840f3cd03a70882e2d2cab142afcba6fa388b3e5dc6fdf89edc444788110333e07cc8babc552e513786e2726ff892023820ceec93b47008971258d2a8650810d7e20c101ed0a273be0f92236ffd232048d8de4791074fe40bb8ec831da03fd8a92470dd3ba26887c6d5365e675e81b9e9c43cb8434c9337934844818f94517d12c8091679269cfbbf4a44d9e2cf26cb6585a2cf2faf038f2b9549a207dac156b95ea010cea21a54aa9521e147b64851528de029a7852831cfc70c41a58c4a6f4a268ca1a6ebc7143120c62f359cf9e3a97804ff26cf69cc25348293e3aa2a2842a1d612a5485524a413d7f26284437ef3a325465564be4d91cd2dbe967153967953ace32187384799769a5ee79aa45d39e3488fed01aeda9b352aa4485e8cc6c82b195380ac40f7b06ce5aeb49a8bb4e30ec3d5041ce9b7e5aa1afc934270561c7d2107ce6d43187f20b9b5ec5fde034946cfad4d643a3d19220e1347ad8f479a325dd22363dcafed0683425970c7068d39f6c1ade159b3e64a2d16832cc33dcf8c6d8f460128d46cb81a74b8369d37fb5861bc3a6f7986834da0c4b6c128d46a3c1fa5c21363d3ea2d16839d41e52476cfa4b8768345a0d503c2b363d1744a3d174584215c6a6df921c612b6e6c7a4b0352d82346a7c3a6a73cc4288d76035410db058e33145c45234869c018a54fe44082384e1004de1e7b83e80ca561d119d9e42691c4a6975236f5d91417a580ca96b26717ca15ba43d768d35bae5e9e8b84ce542577fe541a4e287361034e9b13d2228350e8ccecf26c274f82e88cfdfcb93cdbbc45923e5876fde6e4b50605491fadcd7ddb1c154c485c94a9d8f46ac1c2b3bdc8b3db83f1f775071fc4f30eea489bcff33cf039debb7127067614a7fbb6a11ed75d07681b71bceef39fed3acef3aee3ddeae0089f1b009182d9e1f18eb7a7bb3ef7891d67b7899528393c2182420934987a7eae93db7397804397a684be3c248f2646ba492019d24b63d68ee7d66097c8eb33533d7850a46a89898f4224d98941e9e701e438ce5c2c5b12b9d0c63f94af08a38b28f9b3e20679c5840d390f718f32b82551163d7033c82c5b12654183111657b00bb9b52511166334810516477066b960353dee9d468c28d58b5310430a6cd834dcb2e8e7ca1ab39b359b0bd379ebc6d96db35cbd146f5ee7a56a704d3ceba5b5d6ea72cde6de6a9dd55a6baddc9c63ce287a3b2c71f71d017b094002bb4e29632f531fd8b584d4aeff2a4e6df6727adb947c6d23d8e69cf7ce592586c9c7351d8123c821d80892070eb943a5aec61bd70fa4b2ea62407fc5cd89017d164bab6e596b08860ff8402ab7805ff880116cd6054ae9636604390049838f47968fcd62ede0d281df11940ff672bde3e6eb392de5cc5556bcad9545b2bd1ff28ddbb5d60ff966eb965816c9d287b53916167346cc276fd20523bf6c59f40386959caa228b9c9bc0486416900d3f3c5c2a7e923465d696453cb4d1455e6d59c4c312a7444e6d59c4831636c879cb221e8a9ac6a062288b9e299c5002c552113d510ce590430c869094008a1e1c3099c273c50e464be03085d009a4381a628a241f134c112aca8147490c7aae2091e2a74a4f0c96b03841144d454ba4d460b4446f4944e507570f2923648495cb470140c11a288062d36f2cd60f2fa4409cab3089818be50a13a05deb297e21cfc09817ab8bd79ec5ec8004174a7c9c308161d7170b4dd008a2081d86a8c1a7021d196309297c88086241ac521f9c04859d1a7916433363530735360559dd139f95f4e5babfb71945ebb8bd26b78db5d6eb90d2b6e093d287cb654b1946a0de5a2e7c40bdfdddd9261da262c49ec1a2c8a72d8b966c4ddc50067780793863005b1235c950bb8dbbf60a28b4f041a929a8c88a54530b4b4208eda00917b8ad0887269b82ac19f8c1964537bc5174c319db05efebbda52ff08eb31f7b565383b71ae459ced5ca5b96abca1fbb6a20dc3637ae0a7d98ceb8b85a5a9b6561ad54a98c3a85e0e775f8729bad74bab85e4ee1922da6fd5e5c5cbb6563ed0222b736cbc61ae3100849adb5d65a6badd59eb5d6b6406badb51d6ec0e56e8df8309d7991cfdf8b6bb760967df526656c0882b0045c356baf543b95f7077ea7d3fe4e60d5e1fe5060d5e0fef29702abf6f6b7022fde1f0bacfaee8f05ac9adb5fcbd702bac0aaebfe5c3e9896968bf8721ac1a69f2f6b7336b59b6eb47a3133942d31124e94e0a6cb0df69ffd2448b97ff55e1f0f54f09d2df50adbdec61f3acc11748cec9d7389af4aa19d770e74f7f40cb330db313757cf592b77f09c4a342c61c77cd5d14a1e49e419cc031e327da51486b185edd9b7e5eb68e94bbf1ab0b3af7cb247fee1f3595083c7f943f2d596c8b3bcb7cfbedade6e5331ced96bea019e3be7439536883728254d06c1677da2493f74f8e143654bc2b38dcadefe690668065012f006471b6a6328cde9db5994466eef51f323efd8db69e3c0f77b7ff8dc300cc350cf44a0f4abefcd9dfd692ab4bdf1870e587f3ddc0e07f7ce9eb3cab6fb8a190299a100b2268f9eb426f28c2525e9e8fb3e2ae3bd7ae3ac664695b2f759bde3ede5ed79274d95bcd3a46ac63cc0dc5eadc2000854e99d5e51af3fa5c987a1b49432a7312b25d99503ddb7ad56d9b8839f9e7d7f1ea867df3dfe7676a7759c6c3c5633e608e34ee6aa78923929629c80419012b78ccc45e19660016784934b94f18f1a26587273c8dd9645264812440cd887cc6d59648217b824aedc2a32b6e244be36e06ec8aa195ca9e28e014595ec6d596403133488724d90f19645360c715dc877cb221b9c9060c38fc4b969cf5c67803fd0d0c5b0e4d30109f660788624b7a72b6a813603c72384cb38c17604e9621895120c4561c30c41514c1093410537c46240a229a18187a786a6a77650454e6d4954450bb2a8a287a18cda92a80a25770519dc92a80a1e8c82bcda92e828071c164764e023326b4ba22319705c476b7e45cbb6b417bea21f9cec153ed64a55533453143d85e0e775d48569bdb9d671462fe9eba6cac8a3917274b3b456179d9452cac25aa9522c271174f0ada0f3b49baccad0534a7bd097e4215fa91c8ad59165ef8d529c64b58217ae52ca6d72b58e19f5aa3272abf5d6d62d65035dade06543adb5dea38b2fbe98db6c137cb9cd26a9c2a482f5e28f7234e3cb6d36c9c575d68bf10dc310087e584ff5e2cfe336fb794df0e5365b936c1caa5efc799be53eaf89dd368bb924f86ef6d624285408041f5573bdf8f3b8cd7e5e137cb9cdde9a24d79a849495ab0fedc96ed5ebf0e5bcce87bae1cb7db68613aaeb76e036bb596eeb241add287ddc08dce3a3c2077c60d7e7cd813ae61ec9a526b749e01eff0b81e05aef06a6522955b5a9a9a9f1522110ee29bcb25bf5bacb61aff381bb97f3700d75bb5cb75a5996ddaad7e1cb799d0f75c397eb6c0d2caf3ba25b605a2dd8759d17a2d0a8a4acf8543d0b952a110000000400c315003020100c07440291481ae8b15ed40314000d739a406e5c2e1788b324c841186390418618030821841862c888110d0b7598361a3f7acc2112a0f201d07f7b02e015a788463fdca936722bf0bb675d90d20e28e3fe030fe56389a218dff41effead151aeb15d36e8d464f18e16b53387d8f5437d8ccbf70f24a39c66d2296370bd53665723734d4ba98be96db9b246a4895fb6405a3aaf0f8b9e4e1843564fd35651388e2b360dc71b587242cb713c41048b11db08a5bbb5e9575f608ac9bcf01cf803bba5260ff965d1e63cab2a07fd65350d009adc987394028984d3a84262d3e69aab9881f2b5dde49aa79619b486ed678ec281da661f9610b21710a9de2b2d1a314ca8840c6102ac91777c203f0dcc8dcd2d21a82b5aef36025f15c2369fcabc51cbf72314252feaaa066d70209ec2e18e9cc7d5b18d60818288a4f1329b1c9e2d76b2ae155d45d3dfe3998e54ddddca0e6e3e4ab749d72fe47a15fde9cff17173d49357e08ac8588325c885358435f4077c3de858a1f7923aeb556c3956e1aed61a3023e2c94071729c0cb0047d19b08225314030a41cbe210d0c31b0a6cf7229abd0cfd536ccaeea05f60035985e504a130d7762aeef575f862042408cd6cb693dd457183d0760657998760484bcaff7e18008227b3d33dcc74b9a352e28e39d43e18a142bf037048902800a6b8fd2ae82b57be2ff8917c042d1e70fcc1516c6b9ad31a8130443ee178a53323253f2640398a93cde792b0a797103af8f57a3a121aa383262d5b9861f9895eb88fc0c8f306fe0b733f70dd4c123455cf2d59e7b876084be469474f7da500c0b36dbfdf991f0d8a2eb2d2cc30649d0b01ef8d7a081808bd37cfab59d6c6eadc29628689cc5d7723ba9b9f81a51f32c2d74f1944c53f5cbe3002c003731848d71f09b3196a85e9a586210a272c9ef8529ef4a855b4c59e8b53ee43868c5da2386d0c305b56e5c362730a06c0f44e02085ae41828da77faa406a0e19f19b671d5dc757a3c9150d7a59de1360ce3b132e72b1a4d6d6b56ed99af387e7e125b28a9210be6845578a921024efc982bafab784e357124def3f427cb88c0de8f8c77aa80f937574aa9038f025199d6a40d8b260e506c8f48f2b8c68a8baa852c534b5e91c88ee0c2a0cde2107c169850e99fa1226a232b7cc183bc49c9d990f56a1b37d1ceb3d31d7ea127baf16e96a22fd7e11c9008287021b2a298816ce83974dbc2912d006348434a19c049eed6fc29ba1eedfd488b40ea21178a27b619e116dd011a9b6e388e1deab61d45d51679cfef2243b64a7962145620d0eb99db7f97434ecbc62da2255d4a13d9e628d34244c82ff62611a88f19475903746cefc783e09f327b649c276400e168ed92d8c2765d17495742256b2e5932825c037c59fec9489369407e5f2fd17f4c304d2257030c976848e7497ac5a134aed415efa9524278f83e0fa022f84a6111a2bb7e4c1bd4886b83fc45ca45624ad91b92a0dfb93c06839b091e943599d2cbb05a40aedd3ac35213e04c404f2de83adc45c2c97af1cba49b710921993a0679d6ed09a215cf27151c95a90ec7938b644e14dd138410accdcd61e32b731cb70b4936a19520c60da70cc2072064d30cc76da9f745b5ba3308393c39de99aefc45411005430c45824ae6c541dec4d375137312bcc25a813d5bc8641430a005a4c8a8440585327f1e96c6fd39b0b4f7438147700a80a6033a0a98bc6a95a3e310886dfc97bd3443e811da6ee02e6435f6edbea0e7bd773d624298c116141084a3b876a12616f55e5509edc46e51d5e2194c985d77d1a6ded4c9504cf4107a1caf4ed1e7e5a0785b133ccf0a82357a00a1f125d1fa74aaaad06cf42835b3a53256a450b6ab79ff802bca3c4c495ff44e4c41f423b950a532539573a680b47ad49ad97e1a5d056a94e5af0dca8121dabc5235cccba4aa268090c409ba22a5299d295bd03d51731170ed9abda3a343b73f03bb7d4e20796841c7d85adebbab5f697f1c2b14c9624221a92a5c24a321e4d45563d17d3812aa92a89d5e9314125a944e65240e083f44c0d189108dd4e1288d14e1394622c8dc24962c284e9f21ebb13b22c2b6d8993a452d54949d285ee58482841374368d41f5a637d7920e2cf486b00d4a83c3ca9a1ce0acb7140a1a1fa3c92714fe44a006958fc9eae517d7912d14fa4417d7a4ac3e2f720c73e99c6e5f7a4d510aefa5405c480dc68e83f79d9031bbd28c912d45618d82c32fe2308d40dc80861010c3ef5f8e10c5b7c0529525057b809d7afe70995d89d7fb824999734c0041d6584e46d3e4120f7b6e3d2529781f2c506d7ecf999b5cd7fbd2974c414cba0a02811bc3ca0593e9993beca40f9f240c54afa882cd44c4c51c0cdbbd2f295fe4141ef6c848168064f7d304bf6a8cb5ef10c46bd512e9be644506eb84f2134479a109cd42e9e77a1ae3dd714e17c87ff2611f415fb1ab752c2eccad5254272764f8489ad95c41188678d0b248960e2992738df4a43229b28c812b5cfb5805bdecb54cc1718f3e82cc5ab0f780fa679ff61c14a0cf168f07b4cc47d8307fdd45b219f68372db0bddd7dbdf0966c8020c32699c9f6135c7b199942679f7521236725e11da4ed7b87ae769f409ec960f1057033e2ebed602f1e918abd445ca3bc4bb6a06fe610730398f22ccd1cbac4ec8e9a8a5fcc3e69f0de331b4ee9e798ef4a23ff19c06eab3b90190e80c1f5173987f1ef27f1fd7d0e1e6f32cdecf7f777b5aefe0d87cbec3d1e8ba34129adeb2878f2695ddb7893c0209ce207c48dbd3859f81e23919e351e99e99f897a27398f5991216251eb2bdc1097d0bda5bbe45db96fe9c04457e29acaeaacd893588d4b6b3fb6750658ca987bd8d100f6a9de7393497c52c0da050aa6564c780d7ab2d62b96c65ae2f134a8c35f63113dfc1c265bd14ee831f9c6ff5ecfb1e08ad272f1881a7a904900d5b96e70608e6b0162dc36e4d596113f818608b3671d5822aefda398ef4678a00f29b2d1a788d572d9c444ac5dea55b7690bc923f3e3ae0b810d9150f4b2f117d2c582e2ed261072d141f45676814b62001dc4583a9b3856c2ada84e021006f6f9e0eb4374916acb0296617b3bc0be9e25c9af4719d040ed5307941260d21ce2d7006a98e0c5ff295762564c991280d3f29a787386e03242330c7d22765dc9cd534cf62062d0470ae852ce75536ef2018bb313fe0c5c22e966d2cdd0633eeda0c5fa9c41258a038da50518944ce40949219fe46d3e46a3f5fe41d0b33faede50297bf83feab02daa54e311c0e17ab57901cb40030c233e218fb0663e40d97c3c489fd2f214d48de34a1eb4035a0e1584f4f9614299671b8ea505cb541be09a8e8cd1fe9700607c68db505f336426715a6ba5df9f7741e7658e55e4dc7acf0bb58ee4876e01edc850bd7a1bf3be7175d413f3d0c18ac9c9d8fcff6585463217e987070c0339d3059910d1d4b5416e985b5171763ecc8a80bc73f243d13e6e429f2192340be5364e0a38a763099ec36dba9f0520d0798575e1c373dcf2b597b7983e773b6db4f5b3fdda1d2b63f17ac8ccbcddbef8bc1784d43405fd4b417b1ea6a88c415bd05b41549c5944330d6fdc4529223e55812394b123e28d1439e46876b896f62a42a7c21afb19ecfae90feead40c07efc6e4530f6ef609a2ca5945f7c570b74ead8eb32da895296ecb5b36a123a4ef7728586b2942cf335cf6a88e8c8fcc51a9a8452f4f9d5bb8a4412756f998b695104e74351323f259420caaf8ee596b0e52fbc5510d075fc1a8440c7e2afd7d02495e2cc2fbd5594e893b59445b413a55c331fa662d18225dd49ee55e78642b86bc35f4a57064c1d36047f1e45553a97a9892eb95b3a0b15d3242a016516b8a920743c9b0be099dc9d7837d631967e4e51629a464f7bd30a5ab842eea5c95c5f7c80ac311d50b5b33161bee201dd4878f6c20bd07a09c5af730b4d0d85fd6fdf2287302c3c1f3002d20eb01d445b3887b15503a3cb9ad5ec258cc44b61ed4d5040192c88469df98de1f42c847812c91efdecb3319ce4d086e1d531069e4e3d44b6d9691edd0537255c20776ef759594b7b35a235c18ab1b7ddfdc246a60c4dd71c7693cf981cd9aa74053130a31972867043f447fdb6a8da3d0c57c8c20f2a756a9527b408e4d5b61a76f230c73e4312d1411fb9c0333590d9edb44aefd75015b7d2cb38503f6a97d224655667fa8eae34d78cabcdd3b34ad901d1ba7ed65a1a8aa017dca2ec698a188deaca9e5518fc8a1324a5acb4530f44c42c386c11b632030a5a928d1e07b8bf7089a6fc42e889e12bde61170d3c79b49f2fa5b344174c86c4bba66c42ea4523b6569a56702c22080001d933604953f57872cc3ea657dde0528148c17cc64a10f7d4a3283f41216d9d0d1080d1d50f3fc147c576d169d0642f4efa402c8d9f8b341aed0b52449b0144d3a914b7674d7a26285ed0f27d326fa31b63da3a2183e943d418a2d9e9599231e68426f80fa56530319c3bc6b58748036070962dbfdea485a561a2076d69aea95e738c39539c284ed79caeb9b2d2d80d9f8623abceea17736742dd5d0daa770ae48cc4843874f2b0d160a8428adf6123be38e8660e0f9be3609491013ea694db434f1009561044983b6d3ba48361bd9c25d9114ec2f368df75869f03450deb841c7dee4fbc43fcea82bbbae79708b1ac348109e6a2c788c77d3c7287de09ffb5e47a786eb432dc4678b9e17f8e0d515fd485d9f9d16297c2b3688192ca6952673bb0d1a9ec92d619e38a0acb16c4219c684d3618a361047f472a0641ca5753f10941eac91d11bb42624e227705e7b126800cde37e055797e9c1dd67a1050d642e5a157455a560782fc1e6b088d8f24b2bdd35bf61e7ef68afa5c09b4202a4755168df42095a5a138a812dda047a5541af5a09469a33c1665011bc9a0c99bab3bc39d330c7d1a31adbdf1d2b24361bf064ef123699c73ce90f5d9287d0b3ab00be56880957823c040e67ad802ed6f8314b2527f1c95510b1bf611a4f3a7c9c63b30eee714b6175b15cc29f7dd3001ced80c0d6d780c809428a0d5ca10749bb91d2c6003367b5e8c92f49876c518702833d1027d80fa80ff5483ea4473f9e4f5c04d8c79fe3c6102c8ee080bbb3fc81b3a12047d368d484c30a19329e91f2f4d7cd773088e0b5ac5bb8af0a223f66365de12efab009b78c64fe516549e467f5de6c3d8abd8d9d0c2092c3f4dd3859bcb9e3752018be953d46b220f059f5722555b57b2d79b465ea3f99679d20f2a098b44cdf4cf6df9bc6bd38472e92d77d1f85ceaa1bd792910c89bddc5fc786f2fa2c4fec756ed9452b52c2487dc53e7c88be36fd5bab909350e093ea0638a0ad50acaa789385f43a7b4d02a2af8fc6d615fde9806780dc29629f1db1476734e697fb8dc171babfe16bcc9c37e15e002f7061b7a4061383659ac6a6e3097a93c840b08fc70a450bd351405b1a59dffbabeb8c479cbf24cf0f3c770de1aa0461e397e04034757f483b6b2f94773612370bfc8d188a220cf8383efe6786653af082f8953f1b88cd0d4c15231b121ed4d312994c32ba7323a045b0e3a4495bc754c8d0eb6a23da1a2d9712f2261e787caf0e0f81284d098f486833c67b887d154932a6b6057e74d90532a1d0cf717c5b0fd673e1afc2b181c39d7cd0dcb77571b4337f0e7f669392cb1f5da4f38568516edef9cd393a2bc4f6a967526df81de50db391f1340b7b8125d4ea150bb0cc20a9476ee3a0215fc2e8fe4d5cbb246ca7cd31d6fc1068f8f945e4d5823842c99756a9ebb24bb5024e69ceddebbcdf51885dbf6fbf1e077d4b9da174ec0c511bf1b8b3116e0e1b31a63d10dda48000bbefe82455f3d6ca1c19a8fd39cd78e0e5e8630c30e7c66f416097c760a3c9ea309e255365ce8d3f1054975c00e39bfae0144403c0edec1acd1c3daa92876c77ed1d516b3d1d146ffe2f61a130c3d1e551b900ed1efb5dd468f02e0c66e1fe8aa47735617346cd7567c344fbf17d77e1166176fb19fb326a239539f049e748fa76349d21d3b54d71e376379b37c1b7e6a9d66dfea2e542c9d3a4b060fcb4bdb4f00e4e91f4f5f60e864eca4a9c1b366338240ace7c6b697a852faa4c5e3fe30fe2114ca22e9792e121ddf4a34f07e0e7e4a5f308e8c028421b8bd5223751e33feab22bc93255847076fef085fdcaa124793686bf633d0d7be11373710818679921610cd52aed9b8815700a324fe8d0a04061240d065a35bac2b2154b35138fd822b81617b5280d14144a67b7d72554ace49da38246e39b40769e556ccec20b97d0d4ff30e23b926c9d7e1af59cfc2926da1f2af4782423b99c92149081dfccb33a51617dba75e357957df030597bcfb2486adb849669d9c1d5c9696f964068127e74478d7c29d7da1b757848ea395516ac665e04ecf15424a476994dc52c544cd1d84265127f32fad5d41c9db91f04ea49d90d2618772f0cafe583838461457c626f3da1a31b35b1d49ee2140db83bb0424f583faec9ce3e36d6891b2d03f39b8aa47971ce7af0d1c2a928eb3b369d18aca13f936292d28095a6cee5a8a3cc4d5af6f69f85bedea69208faf31497d3b8fbe87214f3170caeb816629f9b8081d5aa36be606db4edb2e6d324e9bd7fc8ad7031612d64c60e1fb375ecd5d251ee9c238f89bd649493fecc52ec3e11b14bdf248c968e5cd8f9fc2db4596e7b78e6f227e88db1fe71d576b10758d28c95dec140c8431a5466917f83c0b8e33940e06cf253577ee5eb11dcca6abeddc1658fbc072be2ecbf7f5ef8eae28fe80f678bd627e820c88ee05dc55a95e203fad7309d8d1f4dff592f9afcfee2290db4a4435bdfaaf5ea0cbe2af57681195ee4c299dd513d1ccf8184a74f4132e480e1515c3b91c4e3f74a17aa0124474d4c9818a849867b64c7a9d0b92cd0bb9029b27ebd786987a3f7269d6561e3dbd09df3a0350e6fac0d2b5101d2eef8bbc7a388ef5e49f883b58b154e923157f998dd03b419cda1b2737e67b12cb7f7021cfcc6d0f6e1edadba8df64077aa56efcbbfda39b76f8e6c9dfc291e9d0926328d96ec46afcc4c8894c7fb7ff37feddfedffca55bd8c0e4aac42e98312ffb75653a0e2234e84fa238108d6bd72880cc502de9b33de42de11f1d20dabc1de0bea11fab7109001cef1c5f7c54dc59429c0023a1bbf267f470e0c948fdeeeec97b1f9f7205e6bfdcc4defa0f5c5a63fdc58113d0114a654be4def7c9e94243884afbafb3601ca19f85b67d232e9c3cec4d31b618f609103ba24005c5efd015bbcc70aafff47fc6b5708c47253fe42cfa17519818f02bc77d38055bb4944b4cc8d2d0cc012895f416dbec8d4edcfb0b2387a421a9167c3e60d106addc849bec6a3cecfb00f2236ffde4528830e7b3b48f9952cccec61983777be5e9fb610bf3c7f5af6fbeb9427f6fd0703fadbc2eacfd3c8043578cb77312c13757c22dfcef0ff1c606509cc456ac4b85a4a86f269227e8cac783797f4ac475931c3ae5b67724fc76fa551d085e34df442606ba45e2fcf0f5bfeac2bb3134f6474d2855a319b022a43863a38b2d3bf89a944d2dd127cf8fe88198c9260d335fb0a6d1290c1d1442c6369480feea518632d4577b4fc5f8974900847cabd13c5f1ba813ecc9b53342b5bbc6316f672c630ac7b902409cc69f91d97c12160065653280c450e432e45cce56feaa90dd76355cb414f977eeb6891c9415fa63b569b490fdbf7bd264fbf1c78730bc6ff2dcfccea54e991ee85086832e104a86ab6d58f26b9eb2a7bc387985104c30e3016aa3f9f074d40a4c02ad12495a845845236517745319c535afc2b29d6c5d134c2513d62a67c63e8de1deaa14888b07680c94906e0afae7b4b8ce18c455b17d97066faf893a1634d067397035d2a6f34250f80054c9595066e9cc54ce5393c3be4a735c6d13c4feda1ed048889c7c0053217ea788e08181f97d945044123678a6b80558331dd402e9861df4481b634c697334d036ecdf9fd358c78f0f70d46ad8b70128ff7a395212d6c7555b61e4a24d4fbb8468314ee7819db924a2ce7980837b4a88708116331bd304ccd9839a28606084eb02fcb2646ce5f5b4838ff5c2f4f338256395ab0c20e2dfd5f435020ab7d5d6530cbc08d56a5b2f45ab6af164eda10732fc4a52aa8eff5d8362484e58f8ff5898f02452c787df6063bba47c21347ae4881b39b1ef360b8ed5058c0094e31ea8b63f373e2cd041ac782697920d68f35efba842778e59e81f32e8303a7630ef4255ea85822390e90b65eef87a7badc4a226873e6b835b105e5835e10ccf5a2effb9e68b01ee94c043e7449efbab10f8ffb91c4d8b34c7e556a9d8d5b5000b2a6549e691e13778e9d44a65d90b235b0bf5dcc1267faabc27583b67985b8a2d2fc1ac43125ecf0566387e8325d9c3b0aace37d0a586dd8bb35d4523bfd8f53ab001967dc6c699a2820576ce617b594246617bb1d578dd39e92ec07ff2445d3bf54f653c22015dc104aa03b6fd6983606218d7e2b718bee04fdd0e13261ac88d9ce7b13701dbe437474efc861091bcd777e7e032b3560a02a45d4a6ca72017bae579bfd2e902e1c25e859080fa0deca522fdd95b028a7a9ebc9ceb3a07c84dfb8e7a93519e33f10f30266bd5473c11910bea6a8d98b9217b1cc470d91ad9abf438513b50c33066da525317129220ba20f084195ec5190225acb67fe5389faf5634b47115a13301904389d1eaba38683ee12a4753ff7c05381e7ec3a0d4f3504cbba9d02c4fab03948d5913a4103e42b098a978fd2b5dce3bbbd53be548d6f821de1229dfb0f9db8ede04d6f8820af94a563c0809481131d62444fa7b08c631229d8e1a76975f256550fa5ac99fead710cbacc431b1c419d7c0d5216f9ae3b7076bccd1a9582d36b7a52aa579a1118e9370287f2902fbdbe81066afb313860d760269ab1ae5e56ed470d089f7120f93a03c6af068e30ff597a69148a171cdc471e3d77b6ebcb595be2bf09a013585fafe3cc74fe70b391bf77f0e2c2c737c84f5f309a57a31348bb5a32ee3173cd85f5bdf2a3bf3345cf286e9e96cc146de9892c47c05deebb454ad00997d494b1b9e5a0af424abec81cbc3abd6b8fc54e6961bbf5d935f4cd63346e286d6cc946b4230b1cb309a40466dd0c4c0ca48900eb3a4f8bc9bc362ff6cdd1bcd4ce3370aff32ff58f2e5d559080d2282b1759a792034335f55db32bf668b481999ce5e2d74b41c67f0a9c181fbf76b258ff600df92cc139f221e5d00db6c95da6babb58267fad2d3edce264f99447b5ebb590498a966c80a351ebf818a9f723f4e37939839b6aae14e05999fe6cf2414c62ce8ac2dc66706e674fd3c639528159ff9a2e99092f448f4d021919aa281149b94d297d20757fadacc5c1693f8c797c02a29aa0256f55c697d7d585dc480aa604526350cd1974564d21500d0d66945e41e1e06969f89e68c8eecaa67a269f922674a4a2685ab2ad5b7b0614536288d565dd15ba86726a0165a80182580916f1d848ace846d4c9428d72777b479a845db3e7412ca5f7b1cce3e04af9208e34526a2eef422166fac631391defd553a83686736a8ee02e30c8ab83a8635f6a158f9e067a07d6a9620fe6a3085de3d4998ea123498d59045bff4869571e0ba36c77c56ccbd7b1d5c7c55848ccd308f0367f24e26f80d64dc5840eabefb262d066654f82193252641df7291f70192f72eff8d9a6518b89879ef23caa446eb864d3b64023a6e7cf87000bef5d3f568efe7e1fdf02bd2f9cdf97afbd75976a8418196597b61e4aacac3b440fc45568e07c756aaa2465b877dc1bac4a8a229e1917380b1b4796a99095598a9e5de535ad4dfd3034f2742244a101fb4b5db756257b04cbaed5bc65c342c00490a0c03388bb7aa02551325b8080a8332e17703d4c60c35d5779a09362ae763118e741fdb95c5fbd46ac1c76631808d091fe6da7e01aafbc92fabe16fe041c905e3f58d2b49f9f170028e1889826637184f21a0468d6e4b5c835ea783f0248085171c9f8d2ee6e95034719aa1975ca2570dba1d0bb432292f4897845d0c1bd8594ce7fd7a2c1cc15e02616070f2c98b824f28ff4c3d5075a452d9d5b68fe089c2f5e590c0162021268eb5aea9d3a4e40441448870984260a580a355fb3a0137ce2486c65b6bb28c42e1d5d000ea23a5087060769c7fc49c5f7c8d653228fe5107bfaf7a18edccefc776200a6c95e18931db26631c2635f0433c52002b7298a2c312af63f0d6cc76fb905a09c14f7a262030630a296f6a310154ca336145401acb004ff23f0126e379d04316b9c86594cf073c14af2d98b8d85b25e213432f3f2929b6643303fc37d582a00d59b0db51278a7a18e2bdb913f6fa2c6f61dd04c87d42538ead089155f08b12b75c418d344964658c73ac1f865a4972795cc85c400b9d5de106241afba43ede612bea32642ea6ad2cf3edc58a55829325504b2c63b99d783647eed4bf14ff42efe105b5053aa38cd1f91a98410663b2167de7f2c2c06585e4dde2c2938922ba0dab89b834bd775c49c4c4b387e1b7466a5763b9fd4c9984681e51fa0fad7f46f3771c1968dc58f31601348eb8345c3f91b5c899937b9ece016985e5fc36ba588e73114c653cb77059557aa8452ebe9105238f11a6452cff1001d1a53a2214313d0b3e61f22b26bb3c2cbb1ed66745b3c0a7f607a58408f9720d6c9de2419d422d4c6eb8bec628499f2fdd154d4c486869c852feea2cf12ca1e37a1f68054d6fb37ac54e4ee8a9eb8c350c01f8b81bf9331138a3ff99ef66f89d6f46626dca421fb1f549b301628b30efa792a3e408bbd4bb43cffd8bed90c45a1b58ae5430f1bc9f6454513fc272296c0527365b43a7af1fa18c181b7869735406ab8783cd254c5368db0d86f9bf3f6405c21b7fa4ddb7d9064e6eb0d80aa4a1479518fa9845b2f322180c98b1e47e8084b44ab9944f3428cbd88edf15d5429e2c43f100e2f3ce2a3fca779e0a3bc61168e6e80254ad294c51d4ba90c1b555378f7475a254c2d5ffc95b9c8c373a37ea10dec7faa513b278cd3932f921ba6f197999be0f11ab0f71a40f852890ec5245424df506c080f0bb3874b9212d465b2e9458e98152889bf662bfc83a6c9fffc3efb38fc414232f207e71102da994407fbb4f388c32652125d4a4880e8e2b40d553745cea1b9d8e6857a0d8ee03451de762a9a364a945e2877f3787f40edaf0926352a42ab9c23a8b2aa05140599a7b4399be1a96ac41267f659fecf24cbe41069683353cb4d4cb133bee9f2ce6985c4c44de8ba5d75779b3b6a519dc8c2f4df6da68dd6754a2e776c5c6bcc93741206a542c191e1c5def61f24751c27623cf3fbb700e64b86a00cf9080476e39c22432014839b5261c9d13931244ff39a7975fe17b429fde14a38e10fa937a70ecb4fedeba694204e48bce1c733dac0b7f687d8e002f10e45cc1ebf865484fc9b6eb269c33e8740cd96b57e2fcf6de630e68df511743264c8c9aa8044ffde9a541e0d2e2888182cd4d0c7f68c37bf0197e1ecaf23b97cc3ddfcdca07cdd50b3a6bb7118248d52d883e59f97d87f00b5c472fd078be282bea5604e97c7bfd5ee400ad1f5e0cfd5625759ae5f1aa832a78b4509b4aa60cdc04f0831ab5bd7a88f164a5fb6596af8e3a6bf1fb4bca89038d911baac681b480fc18e9958abddf0c4434d056cdba1b3b95629002e37498df48c1c266db8a4462f3fb435c5d0b3cc4a6c7330175eae8920740cfac7fedabbbc7518becedd64ea5e0f70f44220a2f2d3c306bf290b91623218d1ac8a0129ba207b08409b778ef6f22f7ff4ac25dbd6300d6365a1cf5cd48b6b93f44ef8ef43a0d7e5660b611cb681b77401c551435c61cdaa3c6e41ef715a41ada8382e07a2099cdd700a5c186291be1cad868d6d99c48d61b1a31e0185a00826a16f6bc0ce5411e144c8f181b91cb66b73ef864d299e58e8dc4f8e58088c4754fe9e61103d3a1eda23514978c9a15277e67c9c3e9289b5e8b9c2816d3c72c119f03a53d4f6475af8f773446533418240854eb81e344dc712560c21dac0943b84a6250aba0f705c2a969a05089ed08e312ea844a68707ae892f3ff7c576b348052bfd936059900714186ef99ed75f4d46f5e3b55ede0caa781594880366f8435cb109562cac407bb359109276ddbd5206df8d8704bcfb4109be27616616a66f019b12d80f8d22840424d6ce3c4ff680246c3715a3e19d2ecdfd17b71ab5fe3f95fa834f72af652c753d7cb70fef693044e411021b8a80ebbfad45c2c416f62117735ccc0b3da28e6b5b64204fe37ca517f2ff52bd5d0b4033c5f68bc3871f620f16b20116580ee071937164a441583f5674e1216080df00652455880390a2570ce25b3609c01cae7a65f22e22598247cfbf29156daf9fa2126bd4897026ed1ca37a97aeb4b9d865684ee6485b5afd21063011830ed7f529b1e969d9d847bd21e7e82c3bed8342b40a00e92d203f3b838281535c8251c0aeb24f76f9500ad7db1b3cc4cb440bb9099046825b31c5994fc202b6b20eb1b7886a7135d4e07d17dad839e1f60fc14071d1e02e32b055a052a18fbc2e78a143affb5de4e499a0986affd4b161c356e5fb3c27a6b96d2f81d003cec093418106a8a8b36a12e6241c1f9ba3f16301cc62ca744883e5303a4bea133380ffc48fd20b95d7bdf77f66b72f8ddb264dd508a571f85f7a9c8d2c144bc786632508eaba31ab723aabe0017250331f8a4899e1cedaa9166aa36175d2045a9d4a8fdd657b6ef1c7983691b652e907c6825d17c3a8a1c2de0396eae2346a4775454b95c0c99c3c15ec7661935cfac4b9390895931d2446290b2df48c5809fa756d25cf91bb34b1d6162f9a689c7fb5f4a0977720899cb20c35d0be9347727997b19e3d5da24ad7a398c191a628037fefe71ec02b969a4a51f6b74fe39fae5f6a4c076475d6f17399f0abe3d4a34e8cbe291a3dde3a2deb47c1df59fbc1bab5674df6b0e4b56639bac74e55dc240edf88433a559706f30c6ddc93025f2a0a8b92b0a3e81dd2936b730c71ab7f2c2a7dda0df840e4ceed0d8fc5c2412a2778cf2ab5be76bc90d7b790b4fb222c5ccf1d964bd1f407d8daca2af1ffde9bc33c2534eb740332e43f5d421027d85751d3ebe06af4761f0b2d2e767fc980bb20cbf8006531ecca165b837160585eff37de2bf47459b6a380a67f538a6510d4460f171fdf2eff1320d15c6dd9469984f6740dc64548b32cbfbf4215200edd17d0f8e1aec702826cb0255fb7703d9cfab693bfc0da7b666830967c25bd0de108bad306b07c5b6137313206eb345a2416a104e01c6a0e00fa679a8a5c2610cd2ea846aadbdab0bfb4a69b37cc5aba3f78cbe1d20b90cf08c3e4b4839c65b36e905ef7eac0b9f3d73985ea0dadb8de7b042e2e79f6e1acd6755b58a5c9e47647431cbce78046fc624be6fff4d73d1f604465b5794f132480354db84688bff18f8cc486a4f6642d2afb2f471227b722f9adb18d12cbc23b39752b18bf7c15eb5057e76a68a82e80724d0b0dd2adcebc8b3024aae0e573e0a00eaa86acbd7be5d3eff26873ab68dc5cc9495b0141390936dd05105af3e0179d038b3134da08f6ac2ea68c54281cdb70f3ac9b766011520c304e43cbe83aa0655adedc23729bb9422464e02a5794e2912d345800f67232fc8ba8fbad4fb1942352c3525b757da4a1a973715e400c056ccc3609af55d92be1d831e612cef0299f4beccfaa92623a4250b347c973df1d0647c3128ff59bd90c50e5f3ae003f245cf913eac5d5615981d757dfa9e0223f7702001221de75e3435e3a0928564c09ed21c6ebb0730d2f6bda3d605c5abe3f0febc7d9137b998a0db8656e5a522052e1ec74c25e6924f33803bfc460722de8892b047ec3abf90d62c01a241b25f2908220480781ca724becb6c3389c93ebc526c1d3c97666333c8461f62a12aff3852bc07e3fa07388c35e05387f1332bda5d8a65672a6e97bc83b5462f78ca6b5782a50459f0a2c0f71b199c1a95ceb28afa0d48b1cc172f427bb9557f680b16561e004599f0014f4665645126f9d7b707572937a9810bca1a29079d9958278dc4cfc3409bac9e461e20a4be299d327547a65395987d49e4c850b3f1a558122c0ae63bdbfef69841a5e7b19ba252d45977c81428a46781880fdba75bf0e4e32a4408a306c991dec622489f16aa7907cd63b7ded18e57816d0cf77b7e8bd053fe2cacf57a8e5194048a17345d1310258f527bfffe7ecfb1763f618d55d4e0d7cecc6ce5ddca6a4dd7ae39fd27435cb03b173f46e14982a487d288217e993995ec3fdba59d6e9fb3c66bcee1029e53d69ef52d85f1d1b8319c87fce8c6ad9ed0059986b081b9c0f32051acd9bb4154bb42f161afbf53e621dcc7d0d9524e8e35bbc921192650bba6182309558af8b739181a6cddbcd62374af8a7ed34c5ee80628877844187f2aea057a5f34f5420a3088030cb0ee20306347d0ceaac389a074b15f104792c1a8843b00a247a745d75ba3c377fe9341096b8032dfc1d08754dff803d4db24338626d99155b17b0248550b0bbab9ae7591ccbfbf46333b7db48b0aa0e4825e5cf10da95b373b41f13a65890968eadc7679c5a72ba2ec70e249404d44b277894915418b38911cfc93a9cf06d0ba2e43167dd7c42b28e1b6437b969169e35f58e3f7cec1f588c4d88db881804ff23bbc8ae87ae22568b90afc9c87f3e639959766f80f3e1b594f463e20ef1a016d1bd5d026d855ad63f00a3151fc2fb621d3c42b75dcd731b114260722e4552d05069e24536105526251972ee83f813333ecd4d125ebf636c3ef3bf2c237660d2a395f4df966e36db7d2dab305d885397f2855aa792a3943700d46f424e01d97bc5579a35f91a9834b5a54dc09adb606fcbf16ac499b70eded22bbf4c4635c9fbcae87d25d217e50a2ac9f9b2e39cd1632a52140abee06a8ca01a89734864237c9833d219c92a013cebc4abd0e2f45bc630252b1176ca27139327d9cb0ccc2a2118deafaffad900f598dee64a1e374d625facb22ae980201f31dff69dfa961eeef7eb2243ab4c4a8f5fe13bf2f594c5db8b323fab4f346389c40fd5d2f167354b6d83d996d5bb52aea1108e180bbc8a8b6a18a123b107bfa9b0cfbf5dcd76359a363f8267c740d44d0b8b580c76938394b3353aa57a5f261223ead42a8633aab552b8d83123712592a5a2c4b7ef29f7bf626f2cae5668474af38b948cb134f48cede85d9f6bc500df69e41403ad38648e28f58ffa9770987806f875ce101220cb90fbbcf54d962576e4587c25a225f5fc176d7596258b823c35218f81943333df97191084e66178e7afae6c44fc45dfc15ddc1cbb95c9c50b32b3e5edb9edbbbdd860816427a808b5cbdd4d9166c27ca46375d28ba7365a1eb2e9cfeb53d6fa8324572ef0200700353274488907b0dd0dd59f925429f69c9edd1f8d1a1d8eb16933f7241565563542c7531fa6015b8a134a69b4ddfc5c670d6317014cbd6a51903edc294a3bf8d9a8c2693c201e460235f332defbd2d116ee63f4e08d234546cb9d2575c5c801c36bc085cc9827db4e7795a1956d4a5a5c16579088a3d2384a72115c2f35294cc2bb98ab258a88656b9a9b75b2e8b409b31608d3e3776be993adaa0966e8d249af5edb60c9e6acf2dba9283669ec8772ef76a11c5ad9594b27c68c6708e06a13c66403eebdf71cba0bdbe12b04a17f200f89ae817a48a7b520074efa0298cdc8e55c79ba89b2e6cc1176fab4dad00b1e6e6bc1a9e681cb9d652e6b9ca8b8587b82e119f2f49fa9f96cf1f06aa9e51d6122265d6bfd49ea7deea9785b943aabf3281d3ba2460d926abfb22a334892061a3d0e0669c169746c751b967b8035a9e2111df0c53d5815b501e5291a5c35c339f77b5ed91dc5e052fd5b0f689026c9557606487d9b44a6ea97d901eab64ae39006851d05330f72450d8857f16f16d82eefde48227d0ede37fd0e7ea6edc1985e80a6bd11673579af81a7425be12901ea9e31933fd7ef055405f2379e5bde0c284e71219893d5a4284d3c2364572bd1314520132ac5408aced2b1f0b3da8f548c4c05d739d3e43758d04992a5aacbdcccbc15af86195be31b02b3eee685bc99f81a9bf417a330fe56643f510de3fcc8807ab996f96a1d0b42ed981d6f5cb61f3f74ec5394b529a31443c7702bcf7cc35bf0a11701d1d8a115e291430e6906057bc03d7b69d65f1f47741ab0ad9c8540f5a19f4c1b53cab9f480e810f0249f8dff2c2f8f27e10b9119653cdfd9eadd722e4727b915a384988e2330f7c93914e671f8088e9e26b701752208a9b4f2f98c0b5d4f547563350d71a3f7198c36dba7d14333b8f760442048c9f6531dac1a47e11ad6a7b068b80309a7e6b9575d97002193c7503350367708d064ef6b8b60fcda7eee360c0cb050a524a3e192a0e5b62e6bb5dd3a2e32b013d5807b02753e75cec7b691771732d91ca82643d7d024f510196e1889b152986d04632af2944245b88c218243aa9dda0c9ad94772c9ddc8c0d17bab97518d3876ffec81f4a35f01d138fe0749040c57574820ab14cbe29237398c20549cae8be14200dae87578c6c331b6f8c5711c79709289e09bcedd36511608548929610e7b6eafd3fce1c041c79adeded6bf160f024cac440e7e06617426ce369fdcbdd621c5d0656755ca5073756456ce24abe177c09ed631a68a1f60b062d912f6d6b9822fc16ef0d8f56d5dbb595470eb433a77bb7206da874c07c4c9cf331a4d0c78cbd31de1647669dc6fced4714eafa56d1e951dfc09c22453378d980ca274e8d8819e65ac0ce0dbf6e7d141335c2dc5531ff4b1a27af9119215e3e0acf8a2e7e59f182ec5e36d5124724f370e3d1855c28c2862f43846532beae6cba5233c9dc56bff6324931c29d815c6e6be712a47ea3d77d0f463f5dcb2b85c198271bbdb9914f8d7e66f3032f39aaba7c09ad79ab8161e2bc1349830098e33bd969465af1d14c89ba13c31559ce593fb48cc34e46381534f0c0e09d318aa323f49fb761c5a3d59ab8ce7d94640665fc7a0b3f9402483060277bc915b176ca86c9830c6aeb178b1b2bfc828d1a6b3284525cb97e0bd2929dac6516e2044f19b5c8d4550a7093d24ed64cb2aba36cb0d33c71944dc066009d20f5904d1384501e0457438f86250419e60305e602159fd9b28f28bb11d4b562beb8965e034d5d57dc01e00b554740889e29b79b51c29aca20704c92c49984bdf2c59cdcada50846f147d66745f8437ea7fecab767bea4d70f2cdb5f69ce6d8ef25986cc7f1d934329a7558dacb5a96ec0aec7820a4bdd30055036e5cb0c0debaf505cc9fc1b3337c8f27d2a2b18aa9f8409ce1bf220cefd7df15d86e2a26ce4ab6df389b8f404c329d3c5349aa6063760d191dcc03214470e2765ccaddb2e21820f6d814b00acf9bb215680655a0e6adce7c91768d9b4c02bedc8f8cc5f870e6b7ea92f598f8b7a5676fce6c9bf01c6fa7bd0754a6506bf6f37b403b5eba37314181154fa1fbb3599a679252345e68a14973d57ac16c19eb4ee614938f174164c9f702f2e25f9288d4b703f044b2f64043c6b5fa0e15a4492c11777cacb18fb1899567aaae2299302aa526077b988d7db2a4ea67fe3300185ba53532c051e38b89b8f12cd2831fb4945e143295ae4c14b6873d41abc4bac102c049ce0f16c8013c0771d435c5008a8d4375063504a2601e5771a73121279de6127bb38b34ddfc068d74bcd812c56a23657f1f01f4fedadbf4a6f1c74087dca6eee4cae78fbef6696b8c2685cb577af3da0d6ecec4d0a0a9dec0f8879e6937aa5228e55bba87612634c88a9ab367c212e32d15df9b301d02e5952d03b280fac03015e2261418c0536bcc7ee8a1337c5543a6991f4b0051575a7a1faf9963df6da840929bbb34e3e54f085a337b923417423a8c003b43a9965f0987ef2574425ffe444e7daa6636fdc5c36c5289de5f26008b4a22fad2ce691bfb41596b214822ecf1fe5fda07252c20ea6459edb9c218a42befaa9880f1422fced9b9efc3908ab0127089393fa969133bc60e6b64aea55a12215d88763ace481966e4b79606e191a384e2bf8fce0f8840e499ed44846fb21f5451df223a96fe2e9b6cfe450dbfdc6e3b3a8ef5b91a86fc6850d68b7a1be9d4be4dbb338840a2a993f58dd0b55d288d04fd0aebeeeaa802f07cf61ee0600eb376d2620994c78ed2c0e0cedcb632c0040340247b4bbbcf9b28cb9f4f8a97a5622d7c7d3ac36bd18c79cac567ad0d669941521a4fc570a279c61d388ac4ef2bb934dde57063dd8f21bf173d78b2dcba1de0753a87f04fee8d1a01799cbf82e4147f35979519ee1bb06903dfa93539be9377867acdf4fd15f44029f48d32e3166a449063d9ba386a7d01aa3ff362e48222836bfe0f17f293bd486a095f1efa5b119c82e55ab7bba2bde4a8b26bf6bcaa179c385d0ff8162f67891b32f0f2583906b3eb6112e022de2b3b1345c95bd83150ab3628ed376b11f82db060a375aa751f7c84f530e55626c8a388b2ebb2ce51555eb7943288605e41d2301e76e4c901ff860a38046163ace3d393ab2b3b17ad366af2ce48e8e5c6860a07d41c4972fa5374e321e14032211254a526c47bac2e6ab5abb95c306baf15342bd2267ac9cd82edaf5b197f641367ef37dbfd227496dea58a89247b7c5fc3ecbbf95429dd3f81bb3cc69cdad402a0b7845068d83ec2095a21859cb02f8b948dbaab7020de52cd4803445ced1a71b7fa307fb71b01f1b6250333b1e46e916baa4dce1e8ed90b48b241a1479e43f19376442096f7cf705dcf8f60c1dd8be5c37b759d098ee5c8a62c54bd646a2dbabf5b4611ca2ea61186bf18c44210bfc241ac874a7f23c721aa1c594fe61722f2d70b3c251b3946b10d9bb6ab8d8d72be71d759bf661215189e606ed1f223b364f0acdd29f113d701e5d34d3409197cfad0d8df0a6f41a33fd388366241aeb9056f755ec8f193d16f60dc6a76322fc2012f40d8416336e8cf40e10065a46a26710ca1ad2d07bb605f362b794b4fdee24087baf5d68d65315e42f0cad73c3ee79a61b2e2b5645de25e18725d68661a1f25c77647907eaa8abd93ab2094a12db706992ab6a08a29b4f223a373920c7c00cbf5ee2f28900a45d241ed58cb7632330812d59c81507b11541f6717c6b10fbba678ac2b1423546d40bfd1aa0ab8a69856702d39b2afa90453fee80294580f0bbd41e2c51eebf10f363cd099aa85cb3a8db35a6b2932415b1abeb69f7da46bc29285cb1eeb2fb8994ec2831d51cd592ca17e4520500eb628f502e1a9f844183419953a2f9d69f5f5d99337ff307c9da15ea53a1a947bb158ba040032752a3c56928a9a784c51a60b042d792a63651812c242a167d694019278c0bec54497f2aaf039b1cb6e3fe719839c4715a543a858190415251e9900d482190ba790f668e8e84e4ec6e6306d935a790719ec69010abd0efefe82ce9d4eaa8614703c3fade32e6035feaff195ae58a6b92b6be32cadbd8d24d037d63139a28799c97165d7ad0186a75a41440a15ec1db94136476a34e447555fec2297ac8f9a6bdeabd1dea2ef638dcda43c09661221d6f1ab92bcb3839fc2a388937ef2f69a9d49b8563d9cb348dd554a836b5fc530ee1dcad5eb72a96c47b35638e579442db98ac6d88d043934054f39378179ffb9cfccea456b7064d6a77e18191bbe565ffb06b9e1da87e5153431def9cb33021562975ba5081e2bd3724f8300c5fda69fc45c1716ab9d5f1b1247e77b6670be0d9f70e3ca793c714955e48100b7226cd34e3621bc86188a6ec4de80bf2945439bc2e8e32398a0455d34b0fa9aafd2824a6e5d3f40a28e025480e089452498a3b04dc480d2fe0573f3308759006007e0255fb31641516de0354168ee902b20c6036f22dfd07d61e3f0975f7fd31a8bbf7976940345c9751efd85d4a10e495460cc1af58f5f963ffd382836589f25ac3484bdea1a4b24edea43cb3ece2cca5a8566bd898b9fa90f5aa5e234fc0ae2a4b0947bb9f054169a280c65290f2f3f7ffd0f316027c7308192e0c835047e789fe5b747089fd4925b5f46bdec809541668377eb540afa3f7ed983cade05df565ce1a9743fd0ef1040078e3ad1e647aa2d1a3532518b3dc01f6f60cc442f1fc697bd055f56b333a37d3e8c2abb0ea69b701d04af4359b159703fa6a954c820cd86ed208fd2e2047f5b5e8288c1decb8a9d0b8f0e3b03caa02e77479fbf8bb737bfd75752b6550551d6ad00487c32225b60764023085cfde2001da76fca6cd2ed2a3ffc489be2ccf33249ce8f3d038ff61e09476f2b78518ef2c2e048b0720da077c1c30d9536124f49950824303aab7c9f1ab569fff0491fc5ec05b8333d5812f832e8d7e352e9b579b7b86aa3b48da9ee7b42e5be947e35a87d9e710c2f9f6e6678112fa0ffee0a0a3a2a0bbbdc00aff691a6490cb16df0b49fadda260ceb47adc891928e51ffb6434ff0fc776a62dcc9ffff4866d0cdf5ecb94cb4378150403c3fedec9e38e2408ac7b4f36077a47bcc34150cbbc1fa9e43e7cd083a562ab8d8a0a6a78785384c3a94855f33bba3190369ad69c5ffab049057130ab296897ef65ccb205c419e538a6f13e5aa7cd813ef895419d63fce30f615d92fa4a98de1c09e56560e91b5e8c3a30e62097ace69e4520b02fe8b8e006401097f739c3bb1d283bdda771437ab8bbc2dc120b5e75c6277799f11d350bdde27397ab2380928c43f2d594aec64b374aa56c7e3061c271b8c32d10ed3a20140011ea0486e001e25694aa2caca50048a580a5c45f0f534755a78509af8b747623914103b16ef0d4e32f3ac0336134e240c10143393f1f94ea05e1abde54f89a2c64d22484a78bc27fc0970f5122d36b216a0d3b9affd32708b14a4c40d9aaa75e7bd5067b23bc99efe8100502c1310eb012c1881c2c2ea79b012b5296f8f87e0e2886f329b287850302839be8bc73ec450e3250bd0e360b73ddf6020082019ab5e3b29054844b78aa73c6e54ded4899002060a8251af8ff53cec4aeab96991b0411c3d4e4e81acfe7f2226d8fdaf01ec182622a23f5fda8a0eb4994a184e93543e54702e733cd6cef81db517bd269f5568e0d94a646df80591a182917d810136164236c3083276caf698146a8fe8cd6805f0e14102496d744cd37d57a7157aae08694894d804b05c12266dcf1c7d5e9f9dc12bd3a74ba964b8eccd40ad2d3fba7e719446b5c46d679adc81420cec6cb92f4d3eb62ff64aaa7cd527c6e1c78e0616fa1d4303b37bcf63a398411ae716bc22787b88d7ab26bfb3b891bae34b0551fdcb8017a6f1c73d7ae8bca2ea0af1cf7410c2f02c634da1c744622b12196357e2f13ad72d14aefe990681115a99221cff7f5c4be5d55393a70f7fd04a71a1d4852ca52a63a56eb448272a374fda017e9f007e734c44dc0bfba3a65ae7be2d8c5262df86a4698270a3e84ff603becea96e4e67660019a86b58a56d965b9ee4cfa0b12fe16cdfe16b61d3b0c7f51546124ddb5ff3057e5475467fa3c81f1221b3e8d7212b74e816cc040d8fa8d1dffd95a6f0770d4d6e3ec3eb8f6d59dd09294e21e89f4139efe7954ac20a5df1fcb5ad514cabab8e08a78c5a56e2a3567cea9894d83c8390feac0df9a0e4fb225b730a8d21b8a0e45a249dd22b1e7901f927da42e1aa3c44dce6e3d6ca7fa59c8d0775b473a46cd6f7e56e44f62351f47dc82ff4afef7bc73e73fcfc31db40e8e0e09dde945f4128b510ea35dbd6456f5d23a952557462ee0b401403caaa80492e89443c4f54c8bbe3e68374920ca3e3e988689be01b0b171af548397fdef0eef88ecdc5a949590fd5ee9a24764547310a7d6181e343c6632601c69495a6b4bfe2162aa043566a376d44aa5e517e509123f9d2af9cdde2b1d9d1295128ad134fc6968af84fd61d17f39f99212e0fea2c25d54244b1417bd13d01e6b7ec700b254eb8b4b0830f2ff73a267cc10cd0e802023fbb3412afc03d9c5ecb5404fd897e7dbb574f28fe9ee61fab3f3d5c74bee1dc69a21d88c475c0e983e3bccf0167243afadb2bcf9c43a14d5edc1e2b15bca662537ea0b4001fd62cee8284d0753f8c01acabc7a1e9ca2a7c474cd1a0322c94d7a90ee2185265e6a31cc3dbee91df01480180e6c00fed4581f4af7fecc2248752951c8942096381ed3650bc52beb7426a64c911b1764494adc2507b75a738f31417a092b659ee8e2acbfa08da29319ef3fbc011eb655dd958eb01e1fd3a203f2803e68040dc41ea38dc9bb99e9093f45e365dd81dca27ab1caef932e01e175c67845563656b286372ecaac31c004afb888f9ec8ef7fa8b903fb6f426e1c8712d11efcf4393602716fe3261c746db5b3e3364473282bb9bf57031493c0472efa004e0c5bcd6d5b0e5d8761a4fe5921f31b0a7782bb183d02f0cae139ba51e3d0b088845b8998adb6f34dc861cc48a398483a83f63054f620cdcc62d433cf94a380d1c1fbe90e9d22b6cbb3f983323f1e122138b775e082a2b874ec4cc5843120e5ff94ef26e77d703a6034009ba85f5ad29af053b80d945150b877b681435c2ee53e9a55dfedb3591d76e8bbf831fa349e7dabcca1fcfdd549393b7d6492036a533c44d57451ac3f1ff7b418e207bf7c7ec7c3812290959d22d349972d23c2308ba44acf222e454d8435f05938f5e95744410f0e368866b23bc053f5a0e0202d1a609db4082da5ca672655d169bd09f8fb68504acca06248c2488808f91979036599b3a17068a3ab2089be3752bf89442c62895fa9f26fe264d42a819885b2c10750b8c8d2f70e2a14cb095c45bbc64f6f1271f4c70d6652e6f5e4d4e9bec1255a0110ec57b8c80e5cd5151049bc1f35ff3ed642d6db03300eac232014e46af16d33fd35938d0b6534a5c478e3529b323e95f5f00559ff01c86f1dc6f18ff5df1d1582452b9ca227fd00bd7c9b8f44bfaa25038139363daf9900122c2427a92d423ee249110528ea20d29d658e62ca25ba869024d6ece7a9e586f4d83fc0f2c315cec9cbe411f2f70623d58a822967e84162034a91e809dfa6bde84e8fdf91543e7ce173da2c18bb14e68a304f41d9b27179c7a243840a18d36433c09e02b5cfa63e52c54fa0a1262694fd34152a5bcdcce1b6ecdd99b2c78a6026ed4fe958429c11d7bb6fbf9ec775de16e23b30e60d600076d4e72db5863569517c96fa4530e190e1fc90090d1f22bf8cb8be7fe60e10f9b42e618829e3bef92eb819254743b75037acddaf975da329c318fafc92212d82b4a6c60e4705ffb43883eeac01492fd5b678dcd09354e83ec360d0e41d1050ef5c652923819f3c274a18f292b3a647bcbbac221707e69b28baa4d175c3647bb7ac52867e9ef35de5144b77231d585e9f20260cd3f68f06a1049d473f7342417e53cb1b6f6029197a4b45ad73a1f185ba9c7b248fd9ebd22fec2203bbbd09d389c3b778ac364932505d6b8e1fd04e6cf7042d5c4d2446a2b0bc906edba24eaab5718f431941465ab3f972921b8ee4e237893c36b843ab7a8b0f1bfb9e59f149ed4c71df98598ac817e021986ec646c7e17ee6beb5977d1750e1683785deff8eaa9708d2b9412d2a185431f13a698e37c2c89c998fa837afb369950d56b4ca896f5893f14e9d194c73ddad20918266d71552d27f61752b408f1e15ca8f1b1a7313a3686a20493384fa73eaec65a59596bf0ce6ca1bf51382bf4581d8cb784d632454e933ed6f2817c6ad57c90b9f149c6e193310542e6d9a5b7c73fb09e28e92508628e3702c765b5d7234e585f4aadfddad40f682c3662bbb4bc1ad63e6d4879d3368a4782a3be40bbfe8b1f889b2bc97fa43309678514982a30028b06cd2f2d4c8e33f2f7d69a98b9098e9f19430daa750d2c3b85b434d43eb63b35fe7781f4ad48957335e86a7dccd17e20e81dc0e02518d37279e0e0aa4a534da794c0c858a65b22c913d8e4a61c7ccfe834011acdf98b07c7b7242400c4e8b15562a55461cc8fd0f2f4820942938792be2843f9e328e400b569a5177e4189a826bd0529a84ff09cc0d6b3c45f2d9a8ffc118a87dcef5772582c2e6a6a91c99f8ff67e30d7323f7fdd45c1a35e9518a927d28cdbd528c07f9278325c674634b0218652977ed98891dc39994e07543db7684610a3d5d09760d37b60adca86dbb97a96e600a4294e264a7f2128859b810e385e8880a09b97bb6f425d82577d1221681f40d5685cd5b2bd01f0a3a97cdc2f52d4a2a048b30faaa06713b6b9db0b32153489e65402e180eb11d547814df769c7f21857c1d8a4237d843ec80b24efffb0be947824a8720d605d9c1ffa8171707da706b17fe7e3bcaa28b79e4098298e0dcf742debe8531d61e00ac2bf00a2b9ce4c1b1555a6b485538b822a316bdfc1b59c0db3d9a1e4046bdc640cd549fb039894a7f6a5aaad027fb4800295e1cdf14fc7b2a6f8f65303da038e38c8649d0db806d4c624510270fdf9bffeccd05834e20e8d297e00b046af25da3f869f203220543c34df9e735e2e39fa2f8a52628a366f2180fd6fc6d88c75a6c1e0f6417a8f425606f823ed4e068e3459a2207d4296e40748e91f02cbebaeca58fba9b7c7310c7b07b7a3ec0326f95dc60f0a66ab75aee06bfae52081059b2bc81f936247b66f44110ac43dad97ce6e8ee6548d877ba9c71ff497dcdc1b714661c1b785a1c2648e9b961b03242142d44f1eedc448c87e5504c2903c380b71562603d4d3874028da0f104fd0c5f9f9933b080fc4a0de72bec311e9c51500032ad00322c8ddd9c2a55ddc920fdcdd7e8ff334ab93d806bfa7a10d0ed86fcc86747b5120e1326a5fb5a85393a0bc8e785b99a337c3cf673288713242318b3cf7b24887ca8c1e988e4e6fa4fbb4fa6c70219c1d74a58e94e005e08ff510734d0e232927717ba41c4686d7ed1df91df107c4541056d405a280664ea1cc1756313038b00404809f08b80cbfe4ece5910234d35544dfa21ecb296c7ef55c27ae82ac81b2f25bdd44fe6fa54eb631d27527caadb23672ca381be3d6a6277b7afe0ac1a00be1e0b20f4818880b26329f6515c9042cabfb61b477a327f4feb5acb4d228ee1014a18b01aa4712bebe5f0e230cddbae58fc896930d36941541a344890f80d39209b02ab5ec915c465eaf30b5359af8e3b067560e80071f1942c9e0a7b612f8a902dac67e6c0bf6b25359213318a895b62d0398973d792d4313500594d581eecf30088124d2e955999a7de2f2eeceb18409e4c5ef1030e7632afc0b4d2b42233335337aa21cf4bd5514f4cc0d7e5eb4d3164eb7eb9c5fe1ea744a5a0100c4bb86193b76ee3359ff262bb8a70666c21906402632d48252c6295903d0646cc8437431ecbec49ef6845e914bd3df7653f83ab476ac18294d939b2970d96a83a632d9ee98be7bd57be934ad097c71debb6755fda808dc7ea4e77285b1c3f2c00b16eb05177cf855d3f01a620b1f01585c21c42653cd7c7a0c8af2bfe72b88a4166acc37db7a267b0cde69ecda9a5100a29c1d9e79dc7d36808025394f9e09dcf53a42c0be1c9bdf39c0fd3cc340c430e3401e4dee64ecd61a5100b49c4e8e29de75356060dfcab181e1045c3dac288545bbd1b84a28b96057c61673e53f5a5bc4b4cea5a5fc9e5f8fbde56c675ea30df935a9507c5c2ffda020c92491c2fc8904377a35426dc553a1345dd023cd5e8a511507fe1f4ab91f147760533d68e903d9cd6169f3871ca87005f75cfd185418570276fd008160b54a36c6a271c8c9bd759f13fec67be3f9b429300a8cdf7ca571c87570df0a9656b6a7e97d45566af943d3f6b6401d5988c7bc3f7f1fd8c2f0f806afc687a06ef393f5d5c963fe263ea47b73db556ce6715431b18fdc94f3730e32050568216ddc2f5b09e5354426988ac164ba347fb7665cc487c71d4d28dd117933ae58d13b3e321a2764585afe7b65327717038d0f0fcc5f7f56b886040ce6dd01ebf21b0976cf545f130bcaa09ade61d277b488235cf2a60c959031ec7f6611b3562c56edbe2a2843418d5348ba6f42d5c2f1f17ca87a82226e89512589fa5130f6bfc370401b211c65a319b987a5df8264bb6633aa031885161a30c6c9413e6205668f41b89d24e042a7b55a3b83fce9c8035e8826af75451a1ba42234c6f132231884ceb1cb3e8e025055c84f6e482ccfe8d58e3d28ba5800a6a15ee3c7abfae6d40de19fc86a1fc529a6fb4e58727929c1b21984dea988baaae0b04fd1ad85ad4c17be039f30879b9db89186c502fd8ccdf5e352f3511ffcd0153f5a7dd8a6f8f6c8d33e81e0d53ffd866def186fd26fd3132f65893f1e8d420d58bf9b38f9078b6e458d7e3c100101728b62a1c88c5a8672624033834db0b9922ba17c5542b782197220708a7dd13f6d46bc1095d7a43d035bdccd047c1150083ce0f8895b273230840004d21b2a3ad4adfb2d31c21214e9a1e51a8d973c175e220e3b74def2626c2d2ca67e05bbf20f2d916c34270b4ddf2c1be18a8ab81011b66a914f33b6a39c346258b46fe5bb1c8233d5c299adf265a68a4e4603c3891b99611c63c7a1e5936e80b891249e54b0188b6b963da5e474d84554d4b46967b68e21dc37fa5aaec63e28ecc0d57e6a10e6c2e001aff1d710cf57d903641d4d95bd589860472b6f737a57388464cce6b85ddfc6ed89f2a61656cad1c9e44739387c68240db5f6405f7d14bd867df09a3d8413c6d6cffa11a9546dcbfdcbcd1fc63014be2ea1d6655bfc894033e4a86d6484724c90ff41c6ee1b38267cf0e72405cc890bc4d62422cd27cb18040922fe731d04c202a1eddb730fe08d7c6735262d380ea885ae44b17a03bd7944956169f5ccbc877d8056ea74d275624b72327b60407e88663926d8c9960cc4e4e6b210cb0c03ea65221466e2bcb212492cd796a0c2b7eed9323891e453355cac9835af6e8f2c7fb6c45b94ad15a1226147919602d93f8b0746e4250d6a8bd86d717ea345e0bd81e597c5c3d97eadda31605ae56d6abe2f2a3fbbc6da034f1b950b77d425364285dab5477006f4c30a57b582585a02e34274a26e04b1653654cdc26a616cd06dfea810a0b9a0ef948feb0c2adf654d9c67710904880c2e9991173d24841f1bf08b66554c85f2e104144ee4897de600a80eb054c43f2da4a2a3cb68ab833aeb879763f0315b6b1bbf812a39ae59ab436e904ec47a7b31502868325e2680aa56422c4f5a42d43aec0b3b41ad668aa5094abb4c87940af41b955a3729e4a0fd69d3b43389aa6d931966ed9c2a7e84afd235d041313337d28aff12491ad663d6d1ee2c51f8452b44dcdecf4d2bcd974127a35fc3738efc19f328d1aea7f7f0d6668effa76e1d7f47947d10308debb1f872f706c0b0e1afd84599f95346465841ae75c10957c05d078449c743cf4af445bd926ad18a193ca182f107588a343be8ad33372259dbd2f89f11f95cc05eeed05595ef47631a88ef2458c3df7c8b9d62038f42d12f7f8891d83b1c61ba480bb9743fce4d1e159913848006a863abb82325d8199d5a120b06f6d50464d16b6208d5130d4d38be6b393683a7820ec37a6d92fa4316e86bf3bf4e0f44a6a884a405f8f4316c7107aaadd50feabb0b48aab59b0dc87f23683b43b372fc78d1a2f23c3ab922960abc409a784168d13b5aba6b77434a62c3a6db660fa64e94695f59b8876176d1a8be55fd57d0354601dc7d71020cb191f6547df403b31d8c04f4742ebb6e2bfa4fe6787fbee8fb189f861b6d9363a30b0baad8b75b5ba2aeea483161ee0ceb7f48c93b3e3b297bfdb76b58e0f1f05ad28191878986244766e51b536115a3d1fad69c40f39daa24333d67ce81b3b2f396fa1a473860b4331f89dd5d8f02a14c0671a7b126c37a676a78c9600a50e99ef1b3631d96bc9b5d9fdf53d70a1b6c659b4a59b8b291a013770650790f76a10d279c2ee9451ebc6473c42ef9e9fae0e1810c0746222bb3d253885c28e36adca277d748e36e6dd974a7c4c4b3bc490eaca5cc82596a7b5b0c60e5937a57fe6a2563ead1a789633753f1a2ddc3f2742e0cec8cca98f5625b15a0d3cfaae6ffcb172617966ed85c8d2f0b49d5f38d4232e51178bcde66cc03340b00e4435c69ec2651ebf82bb97bf589b39745be794af2fe5b65f14c89085c8044a32f28b326c6d5272d59bc9c9e6130ddb0d8e2f0d5d404a6f0a224ad62a7bdfea313b9acac598603062df1643012004795ce92e1b4446c6efcb58821b5ffd626c1af7ea0840904697d20fcba03faf3eb2432c87b19d81f0030910089a750deed58b618e1eb243cbb9be4eac9ba439fc97a2db6201999f8d1789214529efc1b5e54884e2ece2e5e7974d7fdf9ffbdf2d03e53380d9eecde1550d6f6ae4efa48740749dceb52dc38f39f9ebcb5cf013765a9c7443b4918db3c5fca337749c51a203c1d197b4a00d6c5320287c707d3c1b307c9540f1902a0f3e87e3a0fcb2b90cc98be08513a9dd161644910edaa44812f25071701c09759093db596422e607f546f28714c373f026f0a78a3c6c4eb18620882fa2d5df15fccaacdba1137d7f2d49645c12f4f5edd683f4af3609c095d9e06a07ba7d1f1c084d12d89cc514e56b163f433d95f42428ec83d9a873e73abe38518f9cce390405bfcb1347d4c71af2e611120de12d096ecb99203180062dc6ad39e2b42a9d9b7ca86db3b60cc8309d0eebf161dd6d90cc81edc8596d10c831be2774600da7ba342f26fffce7a2efd7efc7c2512ee7ff4d9ac67755174f27554ed0fefce5a10d78ae95c30d905923ba9bf83d170bd4eda15d22d26a87767a4ec0f9707a4754214fc31d5530bfaf965deca75ace598611bb42cb8123bceeb3cad2a6ca4a02a1a0d35869a26b053715d365d87e95cd3eed64b9079f508365ef8bc66a05924e55d7d45e06e6a3237504231efc4768ec1bc8124e0b55e117232893ae46f47b68dd333ddd09192c42cc8af0d0738a3eb525eb7b16ab9b2f24163760be6ac063b2003319014255723c794a98da1887b3424f8b8f65a70e50e0d9314c89aca216b66f2196e1685e3d5eb9b1481d2d8e7836707ed858e37987173b1e047a86277828fce13cf5e35179914857a765abbda9870c578d026bf401aa8d5e3ed41228420e48aba8e3001932b0ca82e859207aa249fa68ac53143820bb4801872bf67e1f21adb1bae7980d4c7445106933810851bf7c4bd0d038196c1dc340d92ecb01d8ddbd0545c7705e62053b3a940aa23d76978b60e7d8a0d649c07b23cad96915a9ba9a93a7a57486944e8cfb827eaa6a3e405f0a7cd98d58944408c8f37aef1c814825b1599cb824a21f1a7d0902ea722c855a091c7e76efc5d3c71d17837a368d873567bb9d9763de7a44274127679ca546813b38804f5125e34d7259454e8d719127a1d22655573b4071b95c090753d77601736ccae3fceb0d6116085d08e24bad0edf3aa6e17691a948c2bb333d51062084ede4accc54634382d8a5615541ace28a65e7a69a96e14af638df27a534102b703ec7cccec5b2defe7f5c92a3adeb4c54100be0b51211175ca31deab711e6269c10ac8e9f477fe24c98945b43f492d721228aec9e55117cfee905a4b4ba6fa06c8991f00456cc8b7fc86f7f5448ce6ce676e13e4321a0597954734a91506249331edadb265b45bf1e251f07e4dc25981a58617dbe0b07cee66d253db6b525f824cd8f99342da0a7f9027c155bf13c3287abccc037af7b6955a2092324a7331c0af9cc429da5a7c5e61feb5f8c5774222cc976d5bdfb4f144eeb1f8e68da22a056d4554909837a0760883890a002000403d14d25fe1ceb2bf0b8f20a9d8613990925ae28de867b04744ac6dadb5d1d2d2d226015d09b9081b0931d22d053f57754b418c828b81ced319af0b33b830c628ef828c3570aba8c41414132d6bc045cb9f64ea9d3e86202a15702b145c0cf06e85451a01a17e8f3bf2f4a56670e3e1cab1e65d98980424b49110988340a48934e7ac16b398c52c16b13b0cb3a0259d2224c14aaa11deda4dc2cb78c59861cfd7987c50cf93b423b1b3bc0b421d2766c5cbeb5116b8267c7c6660c689c7a7563a032e6a7cacd829236c21d142c20c24b8645ab6dcc47aa56e4b9c2b3847b490d8028b17ef6d2171856d2191a581c031baf9c70f9feb4020f53a3c8f0ce706f6d3b5b6e5134b7b7624983892ddc6ec693fac78d2b33fcd1ecb7eb26b3867acfaeb188c5966b72cdb58ef58bdb6d5672f6632236d28a87af484cd23bc74465129d3f0a8313afdb42174d6cffa99e15ceb4f3dc59945abebbab1e8ebc69aaf9b9bcf58f1511be24e46740d11e828d1c1819422d0f1f1c159c25a1fc9c4ad779fad1d79e1f5c3f285d91367506503d0f4c15142a70b25a685114e5aa917b5d6d7da031a249d32429fec9443a5d559470ecc3d7a93a162c4b9526bad2d2a465debb3baaaf7c94e3934ed5a548ee8ac6314e19842859d7245c6c5942bd714d29421ee141794a6a420ab01e20a0323c1fe842e4127ce008b141fb4d39aa8b4560840d504869a300144162a40220d0b94818332535409a20c348c20e3d4d50837bad274dda20a941145a48c30c28c980187a193e50675256c84a0e08c280c74a05e2ba5810d34b5d60ba91b0ab5565a7feb5d11bad61904a16badb5521c2b943465843a102707373b232128a07ed3ae35c54bef68d7a22283e90cec51fa4e30c705793a4d6d70e91fb552894f4f9c1dc7ca752d9fcd076130aba092831619568006108210822d9a5298c50e4a5288c0c7c51ebb72ad28627aa65d4b06651ad52d29579a522d5aaee8792ec57962253e7dc22122eeb46b496941db762d2936f0c02a64931abea242caa002ee8fd38907aadc718ac8f073c24fd684113ebacfc718e7637c2e3bc5dfd37bf1c59cbd67a56d7b5ba5a45807aa9cb4ad246ba116fa0d110512db77b7e5db9f9f383270efcebebfcbf7ae107086783341d10c3052564c3023600d64a0ac989c7043123758c9c72dc845b162e2a858494c250231c39715131f6d4c9121e8e9c0039915139e1368e105039a18ade2e656414e8a1e7258318941ac9e4503074208b38a1b66c50a6eaf9940a107e10930a793e796a61183b263063a897f2e46a7c5d74d76fc9ba3ba1e615c4fe676e83b6a974386877b5ede97078647aaba5ed8138f24ec8144f5cbc4a2e9cc48cff1281e913639047b9c8b632010779dd01ccb3485f2522311c52f71873ecb264de9af2d8ef9127b2219b84321a63d0e5ef738b57a142bc43845bacca80889b0d30cc723a8d29afeeae2dac7e77844e5a90c40533902ce36d26e27a8a28f47b10712bd1d7a14eade3893f110f144192b247a26c4d3431b1c90ebf41007d7633a35dd90d0f69984ce90a8ba1dfb6b8b4e651f37e77cd89736fb6aa116ab3624d659eb562feb6dadb5d6a911815538934115e9f90a8133d705812da53ca15d115b1c162de9e3c13347a9c5a88a1b508a7092c96c0d097f3a21bb3c2eca3efb6076d3b24ce548db26b3ce267482aab7491d79bda804460a631e9b4e4a87334fc739e39cd33dc61863e6e101e2d6db39e79c71e600454b6993c0f2d4d105ca0fa059730517a4059e8e729da7b313c2859804d9db0b9370931a7aac4827aa59d967db723b754de699733e4b9c99b44cc191d1134771d9c5d1f16f739fdb05825a7ea6e7a3c4c0c45de164e5f090bbc2c9ca74ea3b4d03ee0a27ab8783dc5171e7d46c04b144d11255bed82169040b869041081dac9ad4d063f54e54b3724d6a802b7ba206acde491b51035636884a92f7a0671f44250989f47090cab581821d4441c30b1a88a14509435cb7127281161d3c790205971aacb1722bf7ec93e44343ab77eb0573a794523add9ddb7474b92c6a2da594d24c2984ef6e837f94d6c75723cc94c2ab21a53372400821bc405c5290231e91524a690d0e17524a29a570fe414a2f4f5095d394524aa9d6aed5031994527a4181655c1cdc3615e6665568787971e5a38b4f6097173447ef080b81c8619d94121649f7ca9abfea9c724ee922ce4c1adbe2e966c4471750cc11d493b68410f360a438cfabaf70d2ee5703c9499891466757afa34bad70c618e38c6e1261ae9dce74ce398bd3821b5f6a8705072b3aa394d24929a574d66395d6d34ae99c129681b594a3b3945282e1365b04c62fdc97ac24828165f1ce43e63b5c83c395c75fc876ad21a2769771b820ceb9e91cce0e222efca50bc2a3f39c16aae6a5eb111fc90618c42c97afbfd752c9924c247c6d4a4c775d321d817798056f6fffa69cd7e796e71fae2c7ad798d38a5092cc70ce3ab356b3974f6eae2d5602d490d5979228318ae1b7a57e4e33529598e57e04ce39b1852739b853edeba9fdc07e9bab2e9b5bde32e988fba4d7e42fceb6258e72feba903837f8b7c15f5b5602d44e8965b9e3e3941083ab246b77797a77255967cd727758a724fe81774775be06af318f0717802167a0dce0a7450a2e2b1bd4855638175ae55c6805800bad6cdc81d78153494e3b2eb44a5d6835c201b065648c90b36563a81ec48ecd87e787fb7eb3e9885b643453ae2ea563376d8140eee1af0f2ec8027a051764e6b5a6e6d55a1fca1375dca05e8fdaa48c32deed4822f6249acfade6366351ca4b0c44bb668e39e6e81afcaa40157c52a00a0bd7035e63e3e67bf4b0682e66862090fcb6949197cc0a4a20102c5c9022aee9989494ef06274710c89d0922cb6387f7e88ae305277cb00323d6d0c28a174702881aa6d0e0830c3698f092d7f53441d04a3fa5946620daf6986c3bc485cc96a32c618cbe0c19116571b2253b2e54522a2576b969735b12d4a53bed4746a0d9a5d4b5a5aebfbef03dbae0c08884a21479d1c50f45c811831d3cf00224c67c51638b1c86c0a28c915e3181382ade28f3bec0b1037c61e068d2f98db99d1f193814a93c8240f24c103d66260808a48718b478c1459513882186082058a287304555a078a38cc64289a8dfb11a316ebe47f7888c0d690cad208f95d54c10f233414020297ad7f15bb8dc23baa100555face5c1854df4563adea40607ac749ce8ad502f4108221d3a8efacd0aa5e3e6ae930881f4a676a0c4482e9923a39b9f14296f8b14295ee8163160a4944444efd54b6bad95d2ca6dae107beed1db728ff03d8a3bf0527e81a47b646d0b29c4bf96f84951c17d4b410255d08b9b959c9af625235be27b0455770c54c1f705fe85d9f2b8642bc49e9785db81ef12a46949c7b01c8f329db4ed88f224927684fe271ebb3da615a139a61d91cf9174124e19c1ac9088f31449cb521702ad5684f4398fda5836779d6d2da02e9449afdfb1e5d2e751db117992d48edc4fac5d48bcd996bcfea1afb98ead88b31b4babc1597bfd0cf6316dac12a65a6a7ece0c9bd9316c6e2d60d93c365db0a4cf4ddac78d5ea42d5b2ca1aa0617b66b35b143bf67f9231f718c39a889b8b710f8b425303491e10443ed9cca4dc0b5733c72fc7c29bc889704758edf01b32d5c955775bc36a2543c74b6cd9a56949c72bb5e889d6168a0a804d5f6176c7b391a5bc2066477605c1c306e765cdc57c040ecc91eaf42cc36db385b741e0e16373b2e1d95f8b4fc7b9fb3ca70e9839db20cc34aeeb9aed7c37056a2d3d93592832a0dabbfcea5bdbe074f124af9dc2cbdf3d87ef89671b31b93513d2a84c1f9c175b76f07ece1db57e9baae0b078c9b9f69e71d7b99a6611454bd4cfe7a184836955d294e02553d28c42d37dcbbe4b51de3ee3c23db46245df60f08849ab4126675543c78664e3a766df1531b428a11f25cb26736158699b60a33d3465b9b5ddae671ae6ba493b4211a49db645f9a04849a842fcdc89ba64df6154b9b6983add5ccb43d2d6e961d33932b6571b3246d70cbb7e1eb69360b77487f1a91786dd92190d48654988381a4910c83db45da64cb4b5ad9979ed1c5ec12638cf1452fba90b00b4e17403002492e3623c3a7257eefbdf7cc83019c2e48745444b2bf671e9fce1e1937f3f0c0f83b31c58e4b0f06a00f0d113bd00103ae073c8e979b0355700819f38b8ee3e1708ed7b14db713bef97e9173e64493b3e2dac3eb508a73bcb8a937947a4da02ae71052158f97590c55aff5b447ae58e82bef0e247b9e4a2bae135964a1ccaf3350ec778abd964cde16ebcb40b1256b09939e7745f25bf23278fae6df2442591276b1a6cbbef657baa8bfabf336fbaa0dc97e6ae5a9cc79df4e33f85e6b6f6a74d8d0ecf8e9a87b6dc7265fb521286b6fed0ed9373a6c4e3539f7a60b89335be9571ba299ae996e350908b58996eea04a629b931a910d9e6e59894e47897a530d291bd64a7abea79c96a65f2f5de24c1f6f2ac9d8f75233e2e698638e39fa9a646bd976c280a67c9b843042d38b3a88400723c1aec7fe3614fa49a0df12268dfd6515bb7e913e9d08749a355fef2c8e1837bb7108c9f3e5669d79d7f68f0e793712c31c73cc91a58bc8ef3c38f445ad40e4e7eb26443e7301e747858e77b86e1109045233b8e5c88a971105d76342400854cdffb820cf05790da6eb5346deebe9f51829d3915c740b07589a74597a58dcae5b4cf369b521a55fef3577aec775da4af1da4e3f52a2da1192dd309b41fc84ba846486b7d27e4897acd2493f6db9f4eb355b2e9da6503a093365f5a73e5e0702c1fe3ac7cfdcc72d6b8ff6d46a47ea2dcef108e99776441e2b1d7bf07a267ffa134e19c15e8a771929e24c7a3ccd0c6f155f739b6b47ea73f64c7a51889c3b800d966970166aa037b3b14a7f7d3796769209db94366df3d18ed40c672ec8ebd7c2c198e8c2ebaa4540e889a71549fd48d9b19df6738760db0b819588c33234d4f633da9237c7d6c212773434d440348d3d02658d1d61a1ed120734743d4807a2ddc275207a4485de9690b0c4014e8b80e3418758f978291ff1fba5f1a47a484c0476843550c342e384d2b2f4239a9abd6634c618638c382bf9819f2621907827e9d18eed9e6ea567db0995ca8e9333023d000e3522d8c3f9116755539c7df43c86615996b160bcbbc9b2c7188ffd2fbbc12cec70c7e38540ec9484233b70860d4f02ceb0317904009c7d7c19016755971c9c9f050ecea81da4300a53fa23dbb2921f196661401467194b4241b1d45196e95cfac68a67d2f461c7b2f70c3ba892633a976a297be9d87196475d3a3ccfceb663e6d5d767253f324f5b4c1f5f9f2f1c842f5fbab859a170f3f9005de94bffc932c440b0690975fa1ac34038e33c29971de19a8984dbfc3a5288a738ffc4d35331cf04faec426296735e7a96475d7fcae8e925aae3c724e0aceaec8e2200f0083885217e2f45f854cea1b444af6945e25910df6791469c5d10aa3ae86de8b5198a5338f374e91736a19cb687b3bd29f33e97c69298a6af5a91d3b397b6782171e6375bfdc9663349b1dbb5f9c41e672fca3efbe8ec436be866dbcdd3f8505d38ab0e717e0967d48d34afd9529d95fcc88fe659cc221dfb4be399e0738d5c9687e19410038cf4ecef198659a4d7672fab3540104b01a3a1c1a9ebf4554adc999f3e7ca854ff9432b153253fbaf4ace447db9b9e79ba645d8f77ab15299d65b1f6aa1dc9baf4bad8d9b265d961f498c059c98fc66ec2b6845dbb4ce399e086547904556fca56b14ce3f9a039ef3a58a249bf5deb0644589aa75d6b891dda0664ba00ed5a56c404d1595fb767024c2ba05d8b8929bd449956b56b2dc1458b891b1a0bd396d2dccd1ccbb2276f630f0a2b6946aeb658d38adccbbf870d79da100cbb7b0cb3384bcc5eda4becaf8abdc42e24622ced4cda3a6b5fb436d7b1cb3f56e95acd85336ced3538c32efd85e9771bac1db3cff6592b31ab74edf6e587a5ad42414c2c9663689bdf98cef51bcb9e49bf4d6a3fb14befc27ee1fcc42c899ddf910c219053bd5e8f3d679712e71746963010ec120682fdb0ac529630ebdd5e62406aa4fcb5615dbadd32904cd960d88ffdfbfb7b26bc63f87a59c7ebf3336945ae3fcc7ad75e922febc057da4bca67a5883deb92cfac7d97983624d3b66753da7c6e917bec757b3f6dd96b36d3e176f3b8a16eb77a21f16eaedfcccc69b6d475ecaeb537f39e3633c633a1d6633825c4e0225d9845ba3df6b014ec258629ccbcf4b60c249bf4199cba5e7fffc67826d8d7bf6bcfcf4a5f5c42d1cd40b24ba8edfe667371e79a36989f9522efac12ce6f6a47b27e38bb20d4f6dab3bf2ad9a536247b6136697dae953f585bfcc6783e2a9d813468eca0d57a38b89b59c5e1c1cd5976caac70a104982ba515302d6d4b1fb4cc524ded5a4a38d1b25d4b892f500cccfded707eaf07d5877abe82474102d953624bebf4e0b1b1a7bd1e6ee6f151e24a4325b8f4f61afa70df85c02d4d2275abbaaa31c687efd893bc63d8df493809ea261c643a0a23893bf5e1209eb8533ff31b3c838384c49dfa577f79821e77ea75e01ab7eca52d884a9297a280fcf52492f4a07b79124e129fe1a0c7c275618883de5074020f71d0c341da215621eed40795eee28e3d0d4fcdddca7dc96fc8e66ef5eae96ef5ea2ada7a9b2d880a92130e7aabfb99d76c345b0c549260f841893bf5d7638052c210977e5efcaac49dfa125e70710c41542ae056ab0bc730ef56f955e92a6fdae233f8a74615358c1a73d0c152941412fa0ab815fd05de6aae4c4211561b60691091869634aa6843584adce91fb5d743d6c9b93c50d93d0b57532c02f7f99f4b04574734b4b094c4670f0da33e4104720c2a435374748490522735448a8854914eaac8c7a58aa8b575c4c52705c5fa64a71c314e1aa75d0b0da226b56ba1612533923ebca32c6a744945a9605800cff042a403cfd822053aa918c8940cc4ccd0a676ad33c674e6d95284339ad0762d33ca9831a66fbb96196032800551bf39a374534a27a594ce450731283308630655f6f97877114647920ea6a6840ec2184faf4cbacb18a58b523a29a5746e2e893d3523f118620c2ff191881de9614fde7b46e2a398ec3ae79c73ce39e71ccc0273cf84d70e66f010083eb43c7c263c13f23b90ec4c9f816cd703c9bede1cfd62dce01d9dd1d20b52163c9cf3d99c52d6d3d7f914693e65e75374ca0867dcde1c5d711830fd8923ac3228234bcf4bd8d735e4a2b4565a6badb4c2c3ecd15aeb6bec073f2fd047b4329b17d5f275968424520e9774c8c22e33984f526eb24f375a29ab1bca845d5316ba3234d42e4437d415b79065a8e581ae2d437dfd443243ed8c865a621dcf47fcd34ecf47bc63a3a58deb11efb493eb11efc3f56b7b7d7241aec799acd29cb42146ae540ff7a6e3e9896eb16d4e3aae0df6c9e6ba437dfd9cde5b48bc36b9657b793d85a548d753f3a2f4492abf24b634527f6a182ec248e9a71d11bae264a85d107aa2ca505f971a91c8c450cb2a432df1924866a82f5cca9e8ff8140ff771a1c9e4e6ba26ba244e1da58b77a4309d5d5e5aafbdf792ee75efc5ae76b35b4a52ca1ce86ce10e93b4525e97bc2e895da4d3aa0d7949dc2b9f2e0c5f534ae924694328b6ad32c87476790c953908445229b76b5ee28c93b13b6cb33adcebd8e1599834925d66b2ded15f56d60b4b9152d33ef5ec6b992d8947e6b3f70b13715e1afb96afcb790ce70bcbe7ebb20504298d611209bb9672db018bd13e4854061330bef8b62c33e9d93ed7677a484ad9544dd107218c18ea90729272725328b8f0f0c505481b0be05cd0137a51280b285b7c983319549d22c003e65bbeefaeb565a8b31e982ac6db2b8c3a3e23e31d69889f645c1144c72a7990a5eba949dee964a4bb4eb584f0461d6ff3a56dae1d75cd6f1e0fab747c3c9cd9f23b6d242cb6747c4d16a32c4e263631cd483c3dcfb4ad336d4770330252cd6f7e69ef06573e6b8745ac76e8b41f21b06a466caefda27563cd4840e889ac15b99f35afc199f4f97883f34c5f2ff5696604cd929fa9c09700e2081f90a1032aa0acaaf69349a678d3f6199cb74c2251d8512b5282ceeeb49fab047de12c2f7fafe14cea79c98aacf912346c7d91af9faf5b3afb6d1d75769a65a86bc6b2d950979b8e6b1b4bc735e77a906425227f4fd288c85bd3af2d4f23a6dfdcf49b6353934350972c7a96e99afdb539d783f48e088979b2e4e5b3539c426122425d7aeac7e6a6dbdc74d3e96db6ccde6c36a7bfd9b26b5ba6cfe4777c6e14b3b2cfd79ca41d395d48cc708a0875e933ec780dd8df6ac7867250c5caeeba666365c766b0cf963292ddf4ec26ac8359991601a127b408080961559380d0135a91fa7917b7d48f0cea18da912e37264d12eaa8910ee5851d886607a2bda406b5bd298b6109094d7aa231266d2f4c025403a13acfd7f989630dacd801e28782b708add6d00fa8386ad7a26287ee69d79ac2cb0f2f0d7b8c8687374ac97073b8ae350596d60273c01c4a35c5685921a5e165c62325c37dd903027b3c5c54999c2f3556949ed069d7d202040f357e34716ad7aa224a0384808345cd95234a50b21d785043894c19355a6454eca0b28316a4dab5b290e1e1a2dab5b27cc149b1c0a16e74d89c6a687694e21298bb3bb913109cce1aea70dfe33b2b42230dba1c6ebc8b183cea25b55fa0d14c58d45ea0990bd2c2fbd22f642517a48537d44b1cd0ef43ef3d08869d5170018598c6e097320de71739a5fbd250f6b89594130cbd69f92467185180915204d13aa588a50ed8e3e1ce60ee4e47f5c0402dd18a0c62424144cbd423fb8594e5b2823d71831194ec073e3c2dc768a804069805d85800d3f472211458b6d7c37df6ce2f1a3e21a5a1a321a1007b46601dd8336206f77d2601415cd772a24cd7762d27c63841463f27b8e8b70127b07446f5eca2a31356baa34ec7cb03b70b71c2b8f374e2ecb4c5cd28548eb4d089134d5039b5d65a4f385cdc8cea8aca51a17e608567c2eb7a1680ea61a8a83734450763dab574d0a411d0ae75458bbe273b378913c6953103c587339c1e5c244930a0c207e6bcb2a581c01c74ac5276594a43bb23c1b9e23afbaabcf7c67855de18efd986734ebca43684602e9457e511a54c5aba07bf3939651af26808931683d79c2e3bb99c0c679da63c9ae24c6f29a594875ebea6586ab959ea6470475e9ea4cda434029fbd48db8d6ef947537b51253f9a84651acfc7ac74f2e80053f1d864add15a68ed7bd1e83ad20991de6c52a575c5a8d5040f3c9d95fc9047528c9369c8237924dfd7852f2ef7bab04e1537eb4895445d7ff7aaf2c688da902b36203680e82bd800f86c9ed217a6d69aa240bca9090df1264c037c090b35b665799dda3badc87512b6bd2a7d01497a21b1ca204c98314c9f1377e6ed6d0db419ce6f1c2c6ee6d1f3a4f380406866683eef939de6f327649b832a9aec77cb5eb339b863da1c543d6d8866379be18c9dfe3d66ce08aa8165253eb09f13029738c0a7b390938e241c312b3babde3efb4899339c7d3a7b9df9cd787cdc8603c6c57e9d0702d1eedc49b7b1c754c2d962ce399cb32cf3e86c2dcea0cabde2a9412946134d4cf4745b32849eb4a40317121b859e689c162a7aed34222e8ca6384f920b423de798964f19915c343da6dd6663d1db9c66cbf4343900d3f2a45a73a90db118cc48db0ed743a849f13347a4503b2d9e8ad8c7478aa4db6ca3c7b6eba48de6f616490e849ad36648bd86b70a66909eb8203820b833a2382a82c205457850c41449198a68182aea7982050d2f45ae0754c64c91a5b1f861072a5200e1c60984a0e010451a5f7830420289221441939042799ed0a0a8c88de1ca5841d1253ac11555c8bc21c60d1560810f7af8819624a238633547124d6995286f08ef06658abea062004f8450a4f3048cf3e205456814d93c69c37509f2e4c70b033f414151ab88014fc21425403e314388a2053c11821540f8a2088b22b7e573ce39e7fc3dc19c39e7bcc436ab3ba848506963cf49ce2907133d273da2e79ca7d3e9918ad4932a4798a6521e0ecea8c79377441607c63bc385517405019efc50e4f3048934c8004012561cf12487287a9043175fb82000a04ad17befbdf75e7defbdf7de7b15db2c5281c939e77bf34d4adfa38fce290f5fa8cdfaeb7943705b452930a7f34347561b638c9107672049c7d8e0ee9ca0c20a412e883c742c908ea1d0d6a7d63754310c50f5829ab81e97cbe2a02a228140227e613e34a1bb5ce26da1f9080fc04f8040dc8cce63ce633c1c8a3df851957abe4f8e0f3edaaf5f8ff13f90c49e0df5f867c505a9f98eeaf130c49e013c0ae055213daa9c914d9cd2f1243cfb747e3ed44d894f2bf169ec248c74fb854a8b6ebe3b1e8fedd8f21bc2e6b7e40471edf3db4291c87a5ab79b9deec07962a0da15d30cb5a936211860018620a87a35343359b6f7e52a693b1b2aeef008605321003adb835202dede96b8f3be6d27c1c59d1172525c8e7884d1c9891105f8010884da90050fd068e78e51fade7b8fb60d2ee9384fbd312ec8157b4678a4877f61e251a0983ea7879b79743c0f9e1f40b5dfd1b521186ed06bb2911961cbd91e918b3b0f9b935208218410528ccb340c50f59e989c175cf8720b0d7f856ea0a90a30608236007b360077de5748f500b3b1a709959a04f6243165f711a2c2f634540e5465f7b1514aa7d8a284a8e07abcc38d4914c220f4c106a0ea65d730165c9020d7e3ee78482090ec39773fa66df63eb5bec4a5ed330a5dca3e5d67686a6cec0dea0ed2670b77507884cdc19d1b9c6d5b54db1a1e1a9c73daeab4fdc5f59989a765e79c5cc259869140953dc6a33adc81872ea8740579036917188645ecc56d71ce398704aaa874ce39e79cc3eea8255015a22f0a81bd3069761209c3300c833c6de110d2bc5e98f216e737f6ba5da41c1eaed2e1e14fb850851d3b86c11decd8af2ddbd691da935358e8a4d21c28375b49a64b28ba8e4c6754ce0f977468ebedd682ecd3a6c49181018b10390cf55504eebc50a11398854fb3e6e9b492059f78acfaf0803d33a7941ef51766b3aecbd3dbd8737fe18eb5d7b20da5b359b8638fc92dbb0a8d61a0d9d82fac822a7be14395bd69b3d6a72daef315db895dfc01465f2986b310175986cce92c56b73c4fb1fa1c20ee955dd9d8dbbe6e79de7cb2d8528c33a8fa1273c05e1c5adab5e008d3d9d4ee3fdab5e050a29df6437b8900ae73001680bbe4b9bb0dd32941099cd9fd84334ab5165f8b8aa930eebc18db6f132a721e4b082526e1c607583aadfdc93ea39cb5b7cfacb5965e92a29c00b8f5f416b38ec30873ce39e79c33e73a2a1e93c29873796cc088bc4ea5aafc09e5b6541af7846ae1b5bdc55148519230ce6d42659e449ad39226fd9c76da1a6bc076ec50868986ee2121dd91d80f4f23141ae140e38e87dacf56b52132b2c0010de1cd3966630f0fdc81a733dc8cca3a0de7e17596bc8e6fe1753c8ef21edf513a3706800eea4802addb81d8e61ae0e4ac75b8e636a132710d5f655001f1918c0b9fe5156b6fbe3abef375b91d1e1d2fd723fe80aa789e9c2637f3b83a9e07cf0f9f13686c8011793d73e36db07b9dc44cb70915186103e06dcf83b874df5b00a65d27310a845e544c9571ef09d5c26b79792861ccee7c132a51fe34603933dceb12794db2d7c939fb4d4a7f82aa790a5369dc29670bafe91fc551ea85c0072baeb70995f8fafb7a8b83f4b35f06a3b70995876bf8ca4205c00379b5d6a9a5406b8545987376e687872d704ce9d7291e100875c14f392ebc128ae27bf13dbc80fbeee020a2df1382ef3581c978dd1dc562ce6c1929ed5a657c3040bb561c61da694332a88a3987aa14106ecee9a8a3e2f11a3e4ccbcd19a5604c431c854eb9b9561c5ee0e579b70915f7ee4aa63ea57e704a7d9110887bef35092e26ad856ca4c44889019fb39bc1ede113843035c67b4f8d411fa11030b70168bab084a22be9a3f6234f718697f0aee10f3c8b0a2f3e38c8019614173c36667193ba88821f6e889205951a6c892f7801963878a0e1071f54b18af4041d6304a34a5456c1e164c601470e6fc141440326aba5119aa04b125d8488f2248937561905870c3ddff8218368a05039d10dec42509042ee54a3e71b6b48c705854fed5a6f7c0104192b6e4c21c483931ebcc850041b5c24f1460d9abe718300c831802751fc408b14405186163064f0c60f5abc41d44f88eb5a645a64c8b47ba95d8b4c987ecfc870e9762d3258fafd8626b705c70e7184c0ce3271f4bb7bb7efa867db698fe7b51670450a1b8c00b3449530261c422fc001cb0d44a8000b2845d0dda130a3acda5aad103f7cd1aad20416348a2004032b9e2ce183329ad8810830b50d37ae04e128d639c362d406911b541a6bd7724388eb86937e6e0c418c8d18a6dff67868e38b36b4b4dab8d2ed5a6dc4a07d68b58143bfbbe7c33d080fe57cfa39ed6d893d128a4fc559a5e18cc2103b9e12762a0d1fdf780925de8747357156c12a71e72888a174145ac1370487ba2788cac35be24e7c8c7ff3436fe805bda19590b8131f816ad314fc1bca40b529660d8178c90bb521ce4228e8eb566e35f11b5ac5376ac3371a1e489136888ab4618353b9ceae8a8e2f4b3fb9ee8d76d18c266f6899c3056ee8906525d48412676ce122031a5cd1c4ea8425aea481c4982d8e8c102a228a1c518a2033068d21ac621628d8010b38d4f881862c56110819bcc8218a2e6f702922018a366cf0841735c071c62a7e71a4c410390041268a3756f142e28462a114524861f403ba487fc4db724a7c51524a29a594a87b79a8c48861a3cc8d4fa6f0f1a1c25a9f103c91b3a862085360d102086564a0cc1531d0a2065b0ce1051b42b0411ae307a327c678b1730c16239fdbaec586166c680142c69d8775d8a0a2b30e136cd0608ddaaec5c6136c2431660d30845cd085184238d15a028b0e6ccc4002118484b8820837c0c1145d6e30470acb183163e0d092c589a32c4e98d133edb238d1a5df3307c0c3a7a080737a816204ce6804975916923a17275cc307224499738d2b7acec7839ef33cf377629ca5aa5cb8b3c695865230a7e3607c48bcf71efc7b3002bd86585e8a278cee7bebc710b41c7981428e19d48882c309ae10ad32aa89e0435353533584ac18394146142c4404a1c42a65c410bcf400dab5863084a6ed5a43382345832add8de1bae9d81603952497e931c0ed5d974f725dc224d9230e2abd84212ebe37eed4e7db100751493273f399fb9b07a0daa824cd5595492c992a1a090000005314000020100a878442b150341e1595cd7d14800c9aae446e4a1548510ea320c818440030c400420000c00001919992320350c961c8f7b46323658a437d7048d7617a751c422f6df7c547c7373aba99a3db1df5c635641fc64692dcbb0081f6962270a1e956eae78e3fe8862663c9c8996957cb623a127d07c93d69b94ffad0e1ad0e6f3abeddc1adc925ffe8a6ed3a919bd6278cc6e47277c544d29b8ccc4debbedc8f8e43baa11d3331331ceaef787a27dae33a61b2b0242620524990ff9b4cd530b70d22033a4e84241647a3d391907ad0762b3eb56397949983487a3450b2949d8ba6dd93115fda31ca99710cfd1d24d3a135b742c42f1ab10466ee282d7d9ad225aedaee24e383836cb73462c9196aaf46502c4759e969db5d39df2f0dc42a44cea4e90a425631b45d52d608379aee247fe61871a31dbbfc0c1dc8aa4f03a5c948dc34dc143e3bbad9e1b6e3de34247e2e695caef4dc244b6f21cba7bd358c40ee9a4d392464399cc3260e89ff5089b9ec2773e74a81b38d6cfa30163ebb9b5871c08c827d831fe1b40f3a83279d442d6f5387488e22632372b2c2798cbc530f3b6623230821666c0dbd773f61b95f882a023e77e267ff4fc68f40e9eb6e7692fef0b02474c20e06cd79e3d2301b26e39f0ea5d2af9f8cf8a5c558e46d6696f9d096831b636667c8b14f98245e535272b93888d08bb63bc48f1ddec2f18d39f0ed3948dc68c72e3f9383487a68a0a41832175a744f46fed0c62c677624fd35922d29774e714bbdc6ce296f8c27f1b9866d9c91addaed9c5a76a24f9dd3bf97a5c8f8b99a946e102aa0e41442bada72cce47108d9d196d38e5e6a17ca4b163df2580c9d3d9384976373b935e49211f552b9aeff99de8c4dad44f429fb6cf164cab6d0604390725f2b1ba3b32d2bcc6d82250e8050c504052583685e2f8b4f7b817e313972f41441734e23a51aa1b1ed941515409bdb0013d766ca1d0113c88f7d1597f6cca7d29df732824bc3c2b17b6e13989589cada0be336ff486b2853fe1f16b91404298f3e6d7dfc78f263627bc2084a980e0cf06453119f56c1f05cb001796dd9fa96a1c519f50844e7d6509ce55d8948a9e4f7829ca2072c0660329c8ccb6eecc2af1b5f640f30cdc85432e716bc21f7c050993e6b37c5b343103b6964e7ef1082e9312bec950d56094548f80d230029d1dd200e8decfa870dee12a2f656fcdb31f9dd18b374fdb5ea859c404f5528839244246579257381403f43a2d17a4504960703eb9397c8bcd894bcace5418755aac1441326a382c2e64f1405fe4be6b98010460bf73c7ab3e46bb90b6e4a193709ec176eb6918ddac026c1595dbfec32cf39e2022c68ab1119c24af3b9d913f6d40b6f7084cc5af6e1b5f5b0056e9da4a9a97a30fd06dc2ab92bf8294cd5395e717f1dfde3d57d909696c81aeb2a3363c882b7c5fb6de7b4e7429c061ff37077bd5ebb22e9875d321107c0fc0717445a512e8e5afde1ba005ef8461bbb77f8147ef4064f38d16742bf45723dbaecc43c62c2eef10c9b4aa4829acc07070d3363d77e6c2ca5e4adcd277b084a8e18647799005c2d1ac090596d916b4d01e1bdac3973b81478bfb0e64d54a563a533e9533de47aa22faf4749cb33ad935585b10be04ae7b26ef52fb868baddc2689a27ad667a88d4bbe0e1ef8e0ab0fa976cf94a6ab8a17adfcf22d6c7a235c28da27bbf82569b5a4a0ac03e7facca2cd1a305a8e4c3010e8bcfce43521d15c39d1acf7de2653a4eae69b81e4677e3d40ede0ea24adeb8766480793fb063482b684c7815d2f75c56aa0f38f16d507b71b003b9643095890fbd7a0169f0e635f4db68dfa72efb9b1b6412ba68dce68ef462e3761dd95724857254282408ad7cd95e156dd556092521263129c1e3534cd280c6b49ea5692a8c8a70b5e40f7bcc39f940dd2030f09bff76fd6a934bf12e970626278fad56e9039169ac3d29b4e9a58901e13f0ef7b47fbf094cf8be859ac1f03f7adfd612ce2a3faa96c22f135fe7eb743a85554822e10fa74083c21621984dce8f7e90250c0f9a78ab2ab70ed47ebebe3c2c0d8e51d1b4627da94320988ea081cc6d17a0680e84b47cf7826bb5f25e43d8d0828d842c2147f56225d13110b28fa6c5b4337fb30150e0b564cf31bc0e4ee44dfe4b82c85fd17571502237a2ec9bff5ba1d18dbee89d4a7342e0802e6a2c41bb667d1a7b01374632ad4f4573b6b38080f7cd2c7619002a7126c4b72deff28688e49e1c375ed5a101721b4f37bc796401cfcc3340a0940e202259ccaf1b4c093f5035b3a61cb89fadd500da352e0d1759d3d9b8bfb0a04543058729180c8aa6a8b987b9edb0d6b6b13a48127662e921a52bc7667b5773c47dc34c303e227263e0e557d255654817056b7f77a246e7091a980cdcd8d36d5be57d5ff0167e7165bcfbafb84cc51eb3cca192e2f12266a967014e8c4edec7ff39d35dba2f70023bd7f028cdb19314d3b7c2fce67ec8d872196e25246de0a46087d2217a6f63b8e6399fa8a6ecee5728e166dfc6d571833da3d0ebb8521daf24de22d6997043cdfa5f610b2d3440929b7ed8a68e28f5618c52c07a18d0dd80df75403ac642902965a1358c1d204247b21fd1deca74ae51cff1a83d989bbeea756972b63a00ca28c8b62280cdcf2f746677725dd328a1b4b02735396f9ac7888fe3ed4dc521d9b28d137d8f0b8e5a099beda70e86a132f81b822579dd33310c17d0c4140f3551ced74d752a2facb2f8d334843bf1b8bd77f63b4c89d882b6a9526b49f2951337422e13b756211ff6d60f60c0156ecdeac6b875d876deda0fa914ecf352375b1c862bfaf2f4eb9cb51fc387028795b313ad2f449c48cf3aa3e6ec50d3a188b7ecdeb47b3df0afb6f1e7a5a7cac924cdc17d87b90c5c62d92c19212103c5c933cb6074170b88aa86e9ac78f79264bb3eb09d68a9f091e28d30a0112d37e5aa451159dbb59a8a1b57738675dd7188ec865e006879411044adf573745b48979d948dc33b32ac6bd345bc36d655b094ee7544e1d758327326be8764933c36d80483a7e8b7f967005c98e36631a4cfec5ca223c9041f7e6261cf30cc54d9bc875c2d6546b22a8f222343dd9d9bbbd8ecac875d5c23d45f441986ff70d3aae9d095c46222926ccb1d4b48a3fbc1e6d93d57c6a9812e3055a919f9bebb7e779b4e2dd897ec8ae95830298105dbebe11ea0888082c6bd9142ec3ec23bdb9d1bf11a3f7b821904def3314fb47183ce265724862e67213bda0ba93dd53799b4cc3e79a5414e24b21da852d28840b0649d533820a33ba89207984440ee5446db4a3978b0c176a4b7bcd8e0b71f152581161208ff554777836c9b01f573900290e649ad20925396c4b5b0a09fe473811634cc447023a2f56ad6093fcf0c5dd2f50f8a0d4082b3d838642cfa8e45bb9abaa88a96625b3593f8b9867e63b1a20275d301f77a234c5ef5c134471e3a9bf5aed945c59b300a2e4bf4789546294aadcd7918dce82cad4e3bbb532a26f6ac1242540d5794dd46fd8633d43d3f3fed91980a3281a88333a16dc8d5afb99cde7311a46dea74cfbb485ea5ae58e015bfcee0dc0505494d7fa8f76f881516b33ee049060101b75b2076beceb20f220fe59baf07f2aea9d9b8e8cb0836314ce8373d89566541e3d4f6d599b22db24992bad492965ef89760dfbc9e2cfc41b227b2fbc381df8a5b8f5700604c9c010f15f36126e775862dc920cc76d51e79c096b1f9606a4fbe22e377553f308d6492c3f5d619afc34b61c923455a4e7a4914fca99afe97dfd97fe8905f68a57363e5aa8a17ed45d027e0414a5751345a177d7f9dc0a0f2e039c55a21eaf3308ad4389855313c938a76e60d482a328bffd11dfa667f611d49f870b69565d1de0a7d2ad4d1dcf150e09c372b9e140af62b8fba1afe508d050d24446f7aaa0ecbc1805e592ad600219d75801eb02815083feaa2a77679a3ff218da8dc9b6fcf2bf336950a0e5068a34b7e433879241e9168c526511f090a655d9fe9445a8cb17b3d682d91c8622a0f2a32ecfa7ed68640d0dd2e27cdd5e2e29d5e924c1f2fb3301375b4194103677bd59723ab0365c686c0f68c1fbc8033131ef759e1a6c4a31ddf6f4f8565bc62b281b96e430a505f6dac0c8f0732fa2e0f3729fab0413c9e4d7534d8fcba21650fc2fd9044e7e78cc74fb4b7cdbf431812a2130e51967f351511dd68e58e8c82a2f282bde7111a8ed614a32076e6896f36032b50fc5146d9d6a95e3723406c28fd036883538d2ebbbba2f74513ebc5bcd2a440e32fb49b1ff80282871db2298472ae7a68711a90f75091d499d74288cbb03ce0d40cc2faf719ca97347578988bd34ee7ea62591b5da840f5000cac4ef2b5d15166b1a6e138a4adcf7b1b1a911fbcf515acf65f08b6c2b1eb805c684eabfa33c6c947c7ff439d16732048d1588b462a20d89ea2b536c1b70dfb5f742f168408ebcd7a02afb533b3178a32a51a8844c7a1433c6597578dbbc005779cd763b882cc5c97a3e495e0d0507236c48e72ffaac43ada55c3210ee5c5a93390275983d081736ea292c3eda824462986031d62f8026dd95cf278f9ed726e6c3ce72767433f79538090f3c0fcc6c0eb14128526a0cca0a6f6815bc7eda59c967f3304b0b56ca098c9131cfd7726a25083c64dd5b91da6cc5295a15412514b56481737902a5b100659516105c452126d99511230c336816d8c05cda3210641f5ef94004080f9e027e10d123b6cfdaa2faaf62ca813b5b1b6611a3a5dd9090d4fc6e85b2c0caf6411b7ac19c1ce3897cd134ff6b392c9cb7718a91299e7f997c1dd90b58de64d827c397695f0584315743425fd632cc607912766c7744db85b7a08dd2909b75eae12d9d3bd313acab6c16be55215de4d6e314f6094a66ed722662ce394d1734cc8b1976ff5ab58776f8ac9f10bb9913f55aed0cc81973a03bb587ed993d26621ad51b5ff64065a23db8bb74d192ad416145490c33303ec05b40605a8c7a623847551103a07a2a3f2c90233e190f6d3b6e6bc8a0ad8d0462706c55c9dc5608f92849d269c4a3a69d872a4b2f6225a814f3731d649fc68e585fc519ba5e1f607617a9c25e52f4f7cfebd689c0e08e08647200a2e2711a614a0d45acef2aecd2751ab717f8de055326643efa9f011682ee2aa3c87c5e6afe15b763551d63054e689b52e76ef21100b851d26884eec1679c7cdeb96adf063e1a7ce9d357aa4fb4a0e298a71a484a94a6a66401127030f3e0c694cfb0753c14d4781c38226b12506fd15886cf7c4245340c84138a56b2c9a3e956ea5ae5c219531f27c11d26870850896661bf2bed4fa64671edd3bf545afd895f9b5d023e640b8467cdf76ac4d11ae342f0b71d325b1b2e23017f0a4a06caa5a8193f1d509114973c62b08d87492bb9ad509da4ede46e5be98d5d48a1fa749f861349b3915e445b213bb746f6476b21b695b15333571896aad0e5dfddbea58dc065252fd6eff543c3a2950a63cd1f134713892fa84328480333df9cf3c00487b1fafd1e3c0ed9dcf1cb74b483de5bdaa753b8ba558aed653077049a88545e1c9f83a998dee81af042bc0368a4cefcd46526a00be3c2885be613d900ede7ace6c7237fac2861d7bb92b0e152ecfa668a5a82ee87cf9cf67a053d606364019739dd97e9e83685a77f5efab6313a59aa43acc280cb9ca1aae22c0c9f053e91846b4d75be39a5d8dcc6f7b0cdbf8630c9b60f8bad817134335e3a7bee0130b2069ae8a913a9158b22aaa9bad19efbb5178c68c517031ad28778a49d6c5ba4bdeddc111836cd5517f0cb8f260571de026af428347a51552731cc7cd6c282a8a78d71fee19c77104648d287c6280046d56cd8a04b82752b0b70e9c3d96d13083f68433eb717331d9541ea1e18bc733a4ed178f0279e3a37d796992b66de0bf24a605814c93e82121a4a8a982f29d0b6f49a636c9b8e393e9e6a34174c9ff1de540c341d3d804d86adfd5a40f9e4de4913949345569cb5f2ed61e316fd7ba9e8ff369186e7705bcbc238d151f648e6a9d9096b709df42e7b721d65ef4fd1165c990702ce9e16a79d9341a9c4cd3b12e1e5dcc23065db41497279a9acbe0b598af03e9e43a35a2d4c81521ce3d63cb7be7607c0184d4591a3e8f2e9072e37de654c85b38e33e221e56fa74f323a7ca2f63710a99f804fd24e1b934bb770449f523baa9cf23e988c46133956718bf7caa6725001756b0ee491f377ce64b8de891a1ad4df38fde7ada572663f1ab3c805b91bf9c0fc440942347986f3395f33fd6a31230176563260d9971c8ef06790348d8485e6f0f7bdbe4bdfe04fe8dcd69d369b411284002341cf9164d2ff02dc74b145dd3afbc80916ae4435b81551066092052b66a8b87ddc99476323a8f51c58c56c2d3f71f27f05fd894e256b83d757ad33a88cb922ea2d1f2208426bdbfef1c1a91ef36fe8b9d87529e3701872ef0b651cc6ca01436712384de5e20316d3456a90265ef8e9cfe13eb155a203f4c4162aedc1c54c43d9e142aba0985b4e7ae62f65e314a5f00661a6a72060471456c1215e8c249dcebdb9ab2004f8af8b2034757d66afc94637b7fe3569b048da797bef0d4acf08a8ae0b1323d9e5d77411b36ad11082c0660e28a5817371912bdfac5a0dab9b604ae053804bdc3de9f90fc7d129d393390d363b6a92fa4fb3cd29ef9df2db15654cbeac8b9118669167a097b4a48006d6252f50e3abcc07547caf124955a1dc7bdeeeeaf70d68efa10fd956c89a15c41baa11f8821d012808309cce47cab68e4ec71bd13424c9a2e16e0a39726a2502383d3bbe5a85456d5c291a368a73507535416a0f6006ab436ec127b572130d2b8e9674fcda447d65d48e88437a85baa5b288b2a1422caa1eb31b8aae1879d7de7c5be85fc57846b62d3b8b1b684beb2d07b6d0bfee4e642b3cd3e68efad306d824932090e12401c6310660688cf0cfd63647bc59bd90242115fb6b118d16b2c4690290811d97a8b113d6b8793d817485f7d46e0d0abc15454dbf5ab243b0797fcac5371d6dea105fdc060ac5e5b5d9060e0378bd22d13be8dc6ee28055ad99281ad081e5f7791f93c76fd568036b22f1187849632f13acef8640c8cddd3e2869849efd20520c64b330177130e1137f743ec4ba95ebb114567cdd02e1d3729de108fbdbd663cd431aea003c120c3b02a6b781dee05f0187d129464e7a583a4f77e35921f97d4a87c6d254b0b662cbd626b6fac7c0b997b24350cdec6aa9c6cb8c0a5a163333322dbc46fc65cb83ba557d20fa000db103ac3dd8b7d81caa684f9d16a47e8b87adc4bfd33836b1a46f4d3b399a56eaecb6720f8b07dfd0647d5d99386759851be5217c78dfa88cd677d70a2dc4e7e85c1f14997f854d512102b7e2e106838622be1feaead849ca9a47a83e944ee5742fb9f666d6d985f53715f0eb03b71184212d9cb0d547b9b46c5ebd9ffb79bbc000b0f9590ebd56cbe210003702f03fbef4cb2d906876a3ecf9848d37da14037833cb84b8908d507011e0695f10b15052ff8aeb7352e0fa1be7581b641346f21debf82e37d14a8b7c3294f722317a9b46b0e5a93a71aec5762905583a63573afde5ac7aa6d8c916932f8f6033fdfa22abe6e143dad2af030f6cd00dbe47486316abce9cd47edadbb9e51829c41ee03b8574ac4e95a73ae519477144359e8ce8b3074f237a8d029e77c5f40fb9168711f9dfa075a2d4755b5df8fa18fd2ae214dfadbc374064a119937d11122fddca764085f25bf98cc9797f0406a9cc010523e385f5eb63bc26785e4d6d4ed240040142989241825f98f215c553b7894a72aa8387e9dd052886342c5902aaab85ce3a41a0d55402d205fb2cedd60d226d64420ce2044f9edc9bc8538922498f86fbc7599acd816f804b5acb95ad34ffce1d1d845f5561773e36ae6eacaa03e721f28762dac9519b39caf5d59bb9a85251d73a53b5b300c61311a517807990a667f50b0d82eb00d249363514cb1a5c46b1b8b86437760d4a00f0bcd3b55d2daad44e415981fda297d113c0b84557ce0bef86e8d86edb811197cc041c958be869ba32e058df2c05108dcb0545b935b5e11e8741d049a1c8f060540b1d64c72d350d9ef8d336e3de32886f61800e7bbc7eedba5aea146f0565e819d908985544265730225d577566c61a14b2a3573828e3caa830e447ca58663c2b7cbaa969a4ee95bd75f3f568db1745bb797ffa9f0c530db042d1a81e43eef5e5b6e8c27021e495829120d5f9507c9287f1c5aa9df46917200809a11e42b5f221637b0488ce32881bf0ba98e53c6fd3b3c8da699a8cfaccedf1361d3715a14bacc2759ff6c720336b8d08f827bbc3ca1499f183f1e61fa6cb4205b3b10756227539986bd193a16c7a8c315201b9cbadc7b5433ec89672fafe1d664328e19a3ed458bd46a11b42f35c6a35526d854452eed4b87b5711e558f46c887eb9bf103d08ef4111427a0af07e94bb4cf7bdb7141f715155d2fb72aa62a09f9e08a5ca3f506f14aaf9eaec8df0c877f2da579a1ce39a01e681e54d12019dbb7df78d4789eb8acd3017f3517eb8ea702e4e4ca13f23450000a076ad0f7fdd744c9eb4cc89d237b77637a3b2ca681b7777722c95dc4d427eacba76038c5e520b431482f121a41f76e7a4519f6347181b9d1bb9a906154186e1232c22629e50bbd81a490287aabcf9d2396453143647fab4e5367e1fd2f5972671818076de1d8771b0285daac4e89de816a41e71204509643790a7306354bd8b3093132f898838a82d9b7f4bc1d828495e21a185ce69dc97480ff4a8a26a3cb41924b35798bc2b373e59e37bf972befbfd12e2cc3059bc8ab2a172f3c8a66206b4f4e2c12160bea25816b03726d8f508d840f9cbe52f0eb83d4ed79f68d333947f3a89ea4c8bb8e5607f7bb2d3dfadf23a2dfd34e37f87fee55a8d2b7e428137fd36bc192809fec1b1d045564148d8359f105cd9720dbd002a46e11108be75c83b892c225ce398c848ae6e84f01e3a03da93c4e17dfbbe6c1ea53754345f482f7a0f9832bb90fd473bd1fb28e9d26e9701f0fde2db4010a6112d245c5ca819c02df5cf717a78a5c74afbe0ff7abfbdb4742cca421a63990ec38930da218a02e2cfb2dd1515137ed42b53f3a1d9c590a6c22fe99b8e8e0331249367f3e5c920a18086cc757ca6a6ad37487cc890f2434c9d977b11100bd247b8037ad7355f5d0344abe852457f8ff4bb31f01c88ede6bc16eefa9db230c95513481d934cf3ec243db4953ae2990e6fe18b60f1e3e6e7f6e2ed84d99402c910881d57dfc2e8ef3a3b5e044c14c5b09f030fee1f003ca40e85b1f16b7bb5a59f85c561e9ca62a255f251adb7b51691c39af965e751e9ff079c1caf57939b203153d87c78c903f54934317eba6167e9928c0ae53e2e3240e0b78c6f9dca10b914ef324cc38b73ff5da7c1958b04def544c69f0902289893e6802d8fec4b30076075eff7bf271c11515f51cb56d4a7bc17da036fbb138666afbe6a77d2703cfa2363a87c50c5faa449c10ea1511f7f2283db0190f39044507935b41adc98cc4aed7d8f89f89a1e8ccd45b01581523fbd5837d3df99feaef3d9f2bbdf1908ec0f1ec04c310b180221222a66d18b41e28a58b62f974a7c747221e0353038c5e2798717bd660281f423d809129c9b13ac5f9cad2eb200141b4965ada2eb371853001171dc544c0e985b95c66c736b8783c64061bcd059180747326742337ed366f637d1af8775b96dea8b7550921c336eaef22921e48945d79c901c88dd54ec26ebc85a18d55ed50861d521bc78952a8757e9ff3668b99caa95ad3312d6a953498610055ac5405ebc09ca986d752d36064f0fcb3e74880f6e9cdb0be0f194f51eaf6ae73d6ab6f56ab7756ad7f16a923cc227ec32382726d1946a101f29b7170751280627e8b014f802758d742bea3e966a740267b86cc1cc380486c11e9f65692a2ad6308d3c736383d82e6f582a48ed7ecfd6ee6424001a09e8057a5622052a0574f38fbedae859da2e08ae00de6a9b148fa903d268a76cb045d8bcf710080cca6c72661086cd2a870205666d40648eb8524d0e5e7fa43da3e6f5412fe334e6e3950f5b68854b836b3c01c69db711a9f7dc465c4645516a53042faa55edfdb547f4ece763c6c4e45a049d7071266e8ded62ff2dc6c7c3f5e6d45bb36493629fa76e5cb18240ad245e2db6442d7a3e54db9a6ee6f25b6b12e9aff57a3cb93305dde63d731677cc6aa2cfa209a29687cb016cad77698b02c6f6d63c4d78878c87c92c569fc140200153ee5d449a449af9b4de0d4f032763c724889f40a6de4a76dce9f9850029a477aa66b358c61840b1c0dd03d0e2c8385be93ade4a10fed010c5f9dc5bb415dcec11ccafd8d0224e6dd975222e347178c30195c0e7a35f44d711a3000d758e2a9b78e5d84385756eef85c3f58e15cad57da015c20e5f09ebc17b6d4c5d36c1b6a4fa6290e66365378043c85e356f0b7a57eba87ec89f36d803e0eae0043b67b8c40b4b410047d148ba88a5c7062a782d6da39dc2f6c789f919555af3b78bd5520f6d2925039f7d7722de64456d850e5dae44f629b39105019da2e2a047f39ad613d02f0faee65a93ac10850534f1f321ac476185692151fd9df3b00e4fd468541a90e90b8d8e4e1259cfae4ed57939fd485f031598c306041a39384646829bdf878e6842e4d841546c3844c9b005a457acf934289f7381a44a460ad3dcb4f888f20c9b30550c3833cfaeb694f43f005bece655ed3ab014ebcbed4d7222fddbe462612cdf90e6cbada012ddf1b05475dd43b364e47fc8c246555f48af1f506b22ecdfd3863398a88e2664e47428f88819f202a640b49b220ec9d7a0aed312309662e4ea28d2da44056e03f6ce0a903b41d3bad4db8eaf4f550892792e3fa74d2e851897da7c73b9a1133e3a45a2dcab2b5748afd954bc9e60557b44a180c4150b81d4ad3c19ce66c689d948708ed2d2d650c078ac382581905e00857bdd2843b7db4e0908b3a2ce3c9b72cafd09448b638866b8e37f9bd995ace9af13d3034c8a6274945a380e680b9eef1b78c9d06c831426eebf8362af1b797165a7bf755959db8f7e8ea9a820c11ebff9345b089dba4d237a82c4a12f732b024c39010fafccced94d4825d576544e0565501ec7192fe41392622052b0e383ed45453d1af9851180743954e27093cfa38af06e7f175d626b523fad42379c57dba7cde517b62968cb59bbdf4cacb2c67149a686a20058c729b49307cda12075485d750178e3c3071efdfff4d64a34502e7580f5d62cc71fc667359844dc3677a1df8283716f24831109e1fe57f5a0ecbdaf9cac2986ad996b6d1b176ebe56b61cb8652cdcfd76ff0db27ec55983fb881fc2bbfd2b02c54c08be19982b5c3761bf342067d31da55ddc7ba072aa5bfbf9404412c81118df651c979eb10249cbbe321565cafc08cf36650fbb03064facf1044072a13325efb105b106cabcc7b768c051a0bda91b383757466c62a51d654d257ee335564699b35207acbf672d82db39c21cdb1063e1dcaf03d504634dc89e16578a0cce1f63a983d850db7e2f86744b11c73131d10d22ffd968022dbbb8698ddce4edb923907104b8501560d4af1009477f9b6c7a3a8583819c92d6597f16b9b7daac952a58b74ca2c0537429dbbdfdadb27d5241c975342427486c05e73349078581698994d5277cd94e82a4b01dce25b645275ad61afc00118df1f59c62bc12b2788803180afc778da8f420a6475ac515d10eb1dd8be92f00ee61b2f1af839ca21891d68141b521407dc2914cf8712617b3b27086ec07697cd70818bbfc270aa9e7a52ef5f283853bb69e89dc7ab0733d956ef61aad0639ca82f9970a32488f7dae2740bb790b7cbe9788f215161a0b8e2d1b3ec7a9a31ed13c29179ba46960cc32eb7e6046a36802b483bc665a826f3e678aa5b98756dbd04eb1981b49c50d114086df79ab697f9c85a6e8664f81b1061c0b549df7abbdf1ed94d04a4b38f98123a19e403493196a46eac4c1001118555cb122ac06daa117a2ff9a2d28453ec0a5955eaea20adc3e9c1d75f88892ccca7f47fb0a41ade467c4eacc50a1c75bb0d21e18f3d266a7476389033f89d094d4d6ad4ff78cd6259e4b5c69be60a0c0cf2d434a03cf01bd95c9508c2ba13cf47a0380ff4f253fa9419de7422f0de3b16f4fcfb1eda48a610fd46e929b560294d8957cdf841c8c5e605c8d95c3ad1586ef96a10b383ac8f3abaca08d7c574c75053559b4d6a5507026bdad54bd804a80d9d10b13029bd099e65b3f5326797085a7d382788f80924eaec698ad820497f6492838497a6f0deb5e24905b955aa0761c2e34dd9263a02707e06f64dc3a22d69929ca6dc03e3bd2f46f57744fd86e884da1d280a396d6fd0cd02ace4c0b30f635c761f2a8a6704a80447cd131436e27d28ff28411fcba28746975f73a8cbc12ab596c91f8335f3524d219bec385fd8e5872636d8dba0a395414f7cc59b3a4c6a8f90b7728a130c51a00f59f6f14303ba640f9b1944ec9f3dd1293d45a3bd41237b0856d18d9b4d11ed7353045f3ecd5c087bf2b7683ce36c58c46cab17e5267e77d89e1ef3a113a62468859c9c76fe1d175500d1462fa57d6bfd1bf90f7914f2139a143ab335776083c02c22c921a707ce8e01399e343e952e364e223362af102ff43f7a235b2ec5222a0c8a86bffd8429fa86218a10e3e3601a762d16a85da7889b08f3c1b9708ab40305c8b4716eb0ea0745b69a61a1fa448dc13007b84db9bf7074775ecb57e2022ce7a9c7479a4aac1e3f37cfd40e40808d07b06f6ad0255fc642469e1783694ff713f271a9e2bb588f36f8930ec972df14c92e5a43e754fe16434f4e94bc6a2bae546920581f5b29c669288d5a38443c44de9325aac98138baa95e3e4c27a9dbe8dcd4134fc25a8e86b60916de6de6294cf3b54b83230df0223d3f6438a6d2eac0d3d801d3dc01d3dec1e88783a791034a93834cab834f83838534ee768d7338c106ccb472801cd441ba0dccf6c47b6be9b3cd1f0eee63566e94bdc78ea9bd47f7dfefd19ddcb422555dc6e0dc8228e6838a88d0c17733b4cc9960c03fa0ba57b95f5d9dfef780faeae2ab908ca438922a44d4907dd615ee100fc8df570f6af75114bbf0157b9706b292389effa0baa844a317d122b2b10bdd161a0a8a5fbd57268549aa8dcd5e7516eeae1a52743bd5d7543f2edf3b1f2d07b6357d3dd6953a396ea69b3ed6dac767460ebce52612cae0313be19ad4cda059303903f71b66c0b24fb0e87273b599bbaeef11caa98ec1db8c31f299a562bb0cb6b02022e3b47b276322ddd31325e2fe4ad8582f37dc8841f25c606f4c29f89c744bde517d388ca5c7ebc302c0d2bce8305dc78836285b855e47dcdb97fa26712a3ff24546caf4c85d873321e31d7122152a210c3678a3a65247db8a539a72306364c06746ce27d8caba7ef20e7b218b53c43d1f82efa1143b2872a08a91d4b7a87eca1e6b6bf96b98a0cb097b3ceec6d4c1360c14a83011e2d8bddc24404cdf818e0322da5739677194e3c5b323477ccc1eac4ea426ab0a0c95b37b176295303a401c6c9a9dbff0daa47ab37259692f2b5e31b05a613496b141badd551b4e68c87a809a653458445b5a66a314d9b3a6e19da42b12376ad583a595ffcf156674f3dfb542e65616d43731420f5b19b69dd9bb2e99a87c30755eb6515d37e213a4ac7c9a75fcfc057da6c04cc778b5bddc4a41adb72aea934c768d711d1f66de1cc4a3637b76931d74ff638a87d04614d604ad1d070599758c4535de1492f0e6f166ce67ff72d88724351a7ad81d79ba5c3ea3290e578e696f3933eaeb9a243b188513f502697aa00625ca221ddc154610ef9d42b21b7ac160e4353ff3208b8f696d297f97a0479c399fb4c7da697b173c2f0f7e14dc2ee336b5304a7acc26827fa61d8b4f47aaa203cb0ade900b694c37e4a04b99032ee07166f743e6605c5b428b9eac8b48acf03a1aa7d017a72572b29b026474e74d11b8a409da7d7eb60a39e13870cf8d0e596c54c8a831a1404372fde80398a12a2d3cff2163503230731e763885a478860071479d61cc1b88da63e2ff63377444ca42e0713c7de3c5afa89f6ece5426c9037a8d8ed1e0dd3b21755f835914f50979d7b5710d342ad313abbe20fa4f85945b84ff8b36151221bc38f6ea15bf94b51e736e16c7cc1046fcbd0f4b59481603a9395af1aef6279da2519ae1f342002d89fb5be62a0a634f126d38fdfb2cbd46c232f67449d468a409965e93bee41cc51e39ebb3cd1c0485b0fc80ca9a7dceb311216bb50832a80abacdb02af6be425dcbb1490ea786033d0cdf0da805a430d2e4aa381f93fee4edb84df5037fff970cc348bbe48baef41c2ddce4f0fb9e410411b2277706149c0b9aaf94b5c746986eeecb8f7ca5d2d1695433d65733d93bfdb2da74a7af7ddf3afc4d20c472e03fe5a04ef3d239672f3c2debc0f1fdaf826ed439278e04d16c1fce679c7c70286067d559acb71caff10d32b914445a494ed9e71bd01f5c756a01733570d628675649e6ec4b918a810d761f3211bc4856826d055f28e05aae28e57f0c3de00ff38bbbc5972b793d535d49f6f9fa9e7dce5a935391888e35512b91fbae600c68e1a34263310732100374cac9da1421cdc239bec9a455abdb95504a349931fce5cbc7a727e74cb3bcbe1e43ac8d15899e5422156d9cc47e626afff23351541a40d3025764921c254785675ae8d22c86d8994ef856f6204db38c719c9f2194aeda4c79875626a2c306e7e2cb352970e24d8a2d376b3bb4490d5d3e2e5d82349c4240dfac93c22b456d252cf14996a006c805d6f04c0cca98947e2229adfcac3f7d46e8ca4ab59d418a147ed2d8d028513cb47be558893599c27b8b556430aacdf597698334bc824548e52c8d212588f288a15ecc2c28308ffbbbec8f999be98457a1e6ced20960a0bfd45d2521fd7b52b6ce8867260d80a241ce8e5ba62c1271285d8edda5458df46761a881210a4d56518a9e87c917dc014a713259bd934b5a35508aba65ca32fc6016fcaaa3c81b82a4b12221c04bce8f44e5e352f71fa31fb343b178ece68fb0ae00bde7ece79ad7055ca1231c6c9696febcd9be52ad4504c38d9c1e5c0164069c938e9ed7d7bacab71f4c7ccb4da6e4dcc9c1c9dea0ff53708bbcc5d1fafafd9b057e242f58b78d2d90a49cd9711311e3477e1049bc6ed1c6a5fc128ae38f18dd02459bad2b15b5afc1cff967e17cd9e7f823c6127b2f2cc05b82c53d5d88e58b3043836818a39d039788cd917bc937428a8108bfe2c5f0b818073780883d8f576c06a20fe1d7ccc9443d9baa21114467d21c35def2d568cfe04097d84611de7a4b4879c012191be55623b33de684757f74df17326229a33d49c8f4d6305bb76504b30fce723942b1dc412b94ed07502b42069a8b0be722e2c9100bea48e19f2e8bfa4c85435ff3a33ef2826d9df17512f701c67dbd2d3f66af8e5e3004dd13da67a022fa295ac9de94f56329c3937aa936f4af2011a2cd26cdf9248ec07e1397b0027fbccb053b946b80263c1c7dd38ae3e8c091495dd532a213816ff245d24d7dec661f6c69ebed3eb02dfc51621faa71356a1bfb890a54e6006c6b86303a871e355f7f1ad8155f047e4a4133186143c28188f686d92c65e1413eda1ea764012dce95942ec6640e6a703747d6779da5741da0cd24016afd7abc3c2f2435478b3354b2f0467bbc0625e6638652a907b723f628db0ed54936ab147c62369f39b46f4abd7a3bc70bda0fc86454e99fc3d07801f221b258f8c78bfadfe00f5cd909093092159e646bb473645824396c47966b20a6f3fe9c70cb8d3d0a99b542007a174e678a1d8555445ac0685c4cf5c3b45ac18d2cf4bf39766091964fce5a68d37faff80ecee6b44a9413ed8e9d4843461643b4a494e3f62b3b6a228c7dbe699b4e6c611548e15bd6dfb06c46f1ba28f965068c5b5e4bf61b2c1c450217ff20ca37ab27f239217b5f1643fb87cf09d0064714c1852616e2111c48fb61a1a896028c086f21f91d948df5867cb849a76952662d289dc7587bbfec380e041a168dc52a4b9d3993022be4c31e1ba3c39b520ed777b5e4b3676862d8b07bab6fce1488b0ea46948fc0e0c33c6aeadb194ebd89df79a8fb1a05a34223f7f89241f492b291c3785181188a2ed35978b6df5e381ab30237c3bca4cf52ad75b899aa7ce06f59b596d07e3adcf7b6fb1cf4774eee5e854c0ac177bb0144eedb2278f513436a7eee5b00ba9034c8158d00f202950734c99ae4f138374a218ca4e21106a5886f62ce02078e8e7668057b5dec4f7fc68eb35bd5a084974d30594442066413f8e2e2cf0b4622fb9b26af96c02db2937be80c0f2786189946d03f369e5b5ca48774c43f69e9c17b5c37ae318388f6c7509db15796bc3cdee7409dd54cf4d3e968ca62eb5fa4f21be930bce947b01583737d56bdce2790079f391c4b717f998e3c1e88b5be59ed244b3af295080dfa0120fd7f384007664345e63ceb46182182d7e1f59e06f645c81444df813c4a36b21bb95a35508e46cf7226bac65996a6bf3267a281515c1618cd74b60edbb9e1253d7b2c4e436c732b4107e231df176b630503e8d83e411cadb4f1b36ad68762b04bbe7699649ccd35a25b9dd5f00758b2910055c7e6bc45f44cb5ff8ad46ac1142de34018d9b8b9225844046a7474e33aab4b6ea132d52adf528852b711d51c4f93531db149ec49d607153d4184ff248497311ade95abfbd6d3f053388ac3a8bca50c7e10dc31dbad004db776550bbe3a362895000aa2dd53b20597f0668275063cd44813cf36ec378c88fc1a4ca53eb7ed7cc1299926626ed561792f320a838a7ec7ea277ea0f21f1c9b0c7996e4cc6fc0c63ebdc6a4e51b113054afae0d010f9a2eeec241eb2451f54c2430193ea44188d6e299ac56d490c8731e9ee16c54863c9756c4d5a5cdc5fc9c9bbdbc7be0d647dc93ff1691e85e72c33b8497914ad46089bd9cb65ff08ea95e3a159c9b74478dae82dec539678255e426106fa4bc2bfe7e84a62395c4ce7e2ac7c0aea65274ceb50878654d34febd4b38c0e70777d3b84c26b68d8fd81cae1a9703e72ce52977b0f569b8f8925ce4d372995ea47ed34d8c84288f0ebbf9455c1320e5b7916ba987384f8bc450fcbed8a473ee131267afb40ba1dd494d7e8c2f831a0e02bf67afe89e3b29d5cd4ad6060ff56bd70e6be71edf54d28310f20477f2db0de26c62ba67b57c66d78ba80813e966ae8d1e13ee00a052b8464295e9790207059f8e3af2af68e5e09643e153b19e0865b4b488094703cbf2a813e0c0f890813d8a4500ca1e39ebd87831cbca58eb7df43c6581c362c67baaa5ddd3c9a2b24078ff9d29350f73a4eb10543b398dd6bfa06d5fb14a7dd479d2c61d3129713babdc1c2c97a7fc1d00a2022fd93b82c1c5beb1c0bb6f025018dc0cc9184dfad39cd682c06c8e0bd317fc5e3a95c437c1f0fa3ae34b52115505524d09978b879d0ce6b9b869e9e6d4987825e33ddd4070a04c6782592411d614d326e6ea0ea0a742a91b3c1fe597d59d7cb98295594450e1c2a4c7eb7208c29757b119e6ff930184aa71944010ea830a664c8df964b9e0531b4c42d690c09e938f9a51ec3d7c977ee2ca1aa4b8a067b9f8543e4d51da18cb987bfacc2fa67d7a6c4981b9d83d6c36571884737bb77644484046ecb1db2d80e0e1d98015e21903c2f3242cd00ae3028eb3ceecbac3d1b1e3646756940bb0d1dc42db512b1d32da8716806733949c0af7fa0c7d397bd8411ae8edec86e68ef6d9e7d04eddac1760f025b6f5ecf50e23e533802ec1f71cf71798cb5e43f38b78ca3b15757155f7eaab3b7c1b3f048e52944c40b9f356bfad73e96b82a588370f48f3b88d45e78f4ec1c15558ad58bc580fc6f77558dd11cbf09a3b45fa60e1eb9cf3e53f845605bb01a78be55ed432c63f75556b92942e43de7c174ff8c3f39021bda24ada740c00bd318c76e8741ac13f129edf1f9f91c64c200b22a7ee59363628134112b861317faa2b9b181edb239ac0203f29cc089f792c1453b23035076dbff9270fdb6e7dd29b4f7bf4c742231454185baea8cdfdf825d640a45fc7a49bd4e219a4b80bc6e8ddac7a7971418ef76fa852a8713f67da1bbb6510aca2c701e8fb4abfa9e053096874093308f7142e22e4b9431497bcdd2aab5341fd0fe1d6c007fb07b0a19aa2ed4b0e980bba993e5a6328467f003eee3818bffe91a5794d1fd9b8acf3134363f5a0b106e3a6c2f52e51b8e8532a0fdd68be2bfa70855efd24b40951821e05fbeb3701f21e21271c6a417f63f24284e3dfc86d1d408f4f626f728c37b6cbeb68f2e352ef5a8abf0164f3e4744cf8c2cfe82f7caf88fc060b399932669acbc1e6ffe912ae206daef26c3599d771431e79d0535934ca105efd3d58f78f230926b097a59e08b0133cb60be8d8256a684ac214649c4177b68dbad86aa7c5b2ae321063bb420fef0a71b2d3679398822f056c5a696bcd1807934bf6459216e740beb6256409479fc4d39bb62117c1fb79218251d4f61fcd9ba9febdd6799187d86fca1f1dd5a01d84265b77ce7fa53a1481850de829d651eff1c8e5ac1192d8221641b9fa123b288829e4a6a77ef693c241507994e561379da3da615ca46a390c34c0c6917b02104359189e266dbb52094a98edb8929cb30cc5042f4b732628f43595ce37d95b139681c0ef0bacb75192301c1068e53d527887ab3f58bbb543a180079cf3dd996795012ab607fac00701d458c8b90c1d2f728b796b6c4155255e10efb6a1b91926e167033b3a46b71e9c8c213922cb0489afa347ef6e35bd03890764eafbe7348b14d56a07acd1ce7779fbf71096f3d7864a18619024456ac1fcf80916881bb8f622c2dccd560c81f217cf8420aa17d02a9d11ca253b430586a77b6a2d911e99257c78206bc058cb2238e14bbc8c133b167be10db23ce8962d4b5b3d5a701ca95d610e52187941e842cdc52a96f0c46a3e4a6a1758460c74aa0087f19f87bf962c32dcce629bb0fe88b7b78ff28f2d99ba0f40b3d6460629aed062046ccac792fcf9ff2e4d31147013763ded58b2b3c49535bdb13bcdbae6d4ce5255edf0270cb9fd75ab312e10536c17d34a11aad27784795d866a3c79dd3ba5594bf7a77b9bc50bbfe01e7a0a3d090eb5c315c01a809e7ca7b482b4f236c40db04a2dec78dbae86a59d78047ba0a521b0c2c5e79eadbf867b029eca07012e151e784699472aabca0424339c7b0c2ddcf3188edf836cb1b71cc543d8c28f5620820207f1686c7fd609f93eea9294d84e5f3144c3cc38a57dcc80a8c51ec4b3072810bdece64d51cd89d7b639cad74e3e381ac11b1ac1e6a272ab7b356a3ba71d42936178bd0e847b38c8b845505b74f8c17d4ba46836ab6da5f67a5ba5fb6cefabf43724a11038085a739a610cb103f2206d22db665d90c46c120d4a6c2f99f58ee64c21df979217407c5fc3eb72895c280eec1d9d201fda69139e0f674d8d37b7ee826d6e82fc5825389f6a675b7274dff3076bc86aecdbf2f61dc506b530b8b4f9012e980cce35fe4eb5b4fe9b5824c188d8b505a0037362f64fc6f2fa80c32eac40f9c82aa952089652ddbb65b526a00f28fb630a0a85da85c94ac44e026d912519a23f9a35e4b886efd7f699ae6c6941d43844a150a9f0e066692610f343dcc2c10988dbf2cae2e6689a2a04a0a51900897472a0459a5298a3d351ab0ab314dd546e2c9329936f734acbc8e6f0530f01544c924664ce0e4ead9af16a921f2e90b0627e8617d3d78adfe6e80d78818b5b89076af6a4c41f7de068230c85531119bd5725f692168d7d90b4cb9d10424fd86c32916154bc9225ae84965543c91b0cc0249c9fb37bfe6ef5121716f7449e01f7fa206dc5e80d903ea09b582306e370071f05085f305651028220c169f30a1d5edd08369fd9568db387f0c89e8b2817c348274cccfae1bb5b804c0ada40f2daa09684b1dc8aff2e71f82260679104ca2c12cf57be58ae9ae1fc383a9b0f3a4b6ee313d481e500b675e83420f9afe4692866c8e8846b919be69976b02e5501416e4181aa28e90f814290099de2e4fc421960ff99f191fed5681bb84f15522dda6e95556c8bdc8c25651c8e589bd69fcf66e644cbeb967f74b36beb362217e20d6e02213f7a46fd24cc40a58c6171e5557f8f0a155226d40772320476567946483f5116967a9335a28602c6b807bee5a9ddeccf33df23b8428b7aa886106d88a29a228b88308cfba31a6df8301c005d45f802d990acd81f507311de6966aab92f7cc4551c594d43d21b844b839c02490b3a2570006838333d0c2f2639519dfb6b61e20b54d7fc32039ca701345ef55ea704a1c4cca1806edec9b34852ad0ae2e576cb68dfd892fb88c1467ffe38990551509485577499d2e7c8a62d45116fc9dcf1370fe51ef1fcb99e32ac0054f4d56a14dd41d9c7ba88f748b248d09d5a3d8375c296472a40a96dd71b783a1624415aa1cab3f4220d02d480b5a92d51ff66587dccc93ed0d4ad192c67d8f7a06c1f5d3aef0c09ebfad489d9e6b15ff8558fd60f49202a5a39a2d661ac2cb1f3e55be15311e16ba3b21777cc9f11b83c639933d27a5fe167b7b0db739003fa4aee032b6590d8d262f37e7130e49511b227125b7dac8490e165cbaad42e3b5ff83c5ca6bddee75cf60d753b22d465950f3608f974e7af026effa6ab3a13b787204ebe10dcf4d8e4cc40f48f8ab6e78a5cb09bb7cb16ea2eb5d84f0ebf23acc9fc046c85717939c30a8b9ac29f19265ed2f1bd3b8632ff5b8a6c04609b0ea011edacb87403743f37ca5f45fd28573b6c9358fdc90aab54803429298a97bbb7528511bbc9495558e8197701134045d35592ad1085fc75ef07e845680a7e5abb51931ad512d8b475a23b32699b5d0e17b7c4d312404bd7b5111e8c2c75346fc0387a8bf60ca7fc933b20ac95342df98fa3a50ef6ae309ab7c64e2f6052486f0f69933e3b94ff24dae2fe0c63520cd72aaf008b4071a5e51307e06d3925d0bd4cab6f7322edf727b618ac148ea83a07b46025d5cf5eab8b7bef834db7656d5197ba4a9dfa0b58a14bdc1edbfa950d7c713d80d2c802137b054d609c312436542f957ba6aceb8f40fdfa411de0307e1f1b215eaab61cb026043920cd8deb0469a0eebbccc886b2289a121b9e5c0722852fa6d99a8b6d98403f9fd0f75b03b8888807c776e18ae45c19fa751171a89f9d7d4555d12a140114fa6fa055efa815f4ffdc4502eb99d161b895b3c3af4acb7c1c7508d9e8ac923210496aadacbf9f935aa9697b9c548d517ab0017ef3791892578a8af1ab5ac5ef9fa8a0da51f9b255ed59d5779b52066b3ab5c52707eb110d6bb74c9f4c26a7b03bffe206d8bbab5e01f4d5f81da23e6f8e4855c224e0cd5849b3012c40e6d5b09c34003aa04885c9fa10089e1b6475ac56350a0172ebf0ea6a8159f6bf465fc0cbec67c83a16bcba0463d7c5ac9cebe45d650abdaa03f0b5ad02a50551c27e5c6af5effafb64df8b0ce2a581a15b96d0223e1ba09d2b73179260dcd8f32fb02c306e4f70a2cd54c5b980075b565cefbca98c316183bb821fdb3085b4f57053367313bb8ffcb6e1645d1fd6cc6aa09dc0750279b1799c13bd78a9ac20203ac32a1886865ff2dc211e1a0f77d7be46b140128c644b58a011b6e7cdf14681b36d9e37a68fba608ebb5c2275d66a63a2b4325522f3ef1aacd31713ce5ce915035326a9d8f6dadd150d1aba95b05e9e59c9cd326f6f6398fc923dd1bf354969cacdf03f3866768501bb17c98400ab9321fea32b8be8dc7eb1edbe014e9d5de8708308daa2ec32a7af5b176767644ccd057fe3800cfe3814e7a3c04122f4f6c632b22f5971d079f37a6496e3b82ea3058e963d8c0e9567ca25ccd5af57c6771af2857a016028b9592c59b02e4b2931010c3fc64d474d63945c81e23b828c319c433450367bbff0e376aabd7210c5ebe3139f752cfba9028bb575ec5643e3de5d959fdb58bfdc41f8932275334d65d4188111bb7998e74452a57125bc07b5bd72cc175d732e4604a4e93b9ef6b748535f58901d6bf9d82218ccef56999763b0e54cf3ea2a555559f58e1a81483e1d706b6f65d31fa6b5c4513ea48829de365db277312dc0bab673211e44abbef7af6a9837222be7be196e7ee2a28fe493be721bd9fde84426a3c4d24d2a699a49f379e830bcc69ada14a9ffeac36445e7ee0f6e895ec06be695290123725c3f2fee0759e4ed76c85cdae3b206438d01944ca3b32299401e6f9a58c9848fb8d29dbec9cfb43f217cc20c09e22f2d3aaed3708c6dd0e40f47b1eb898ea0e3911b1664a01d0b7ac42c9fabe9adf91272cce28648e7d3b2e08b3342d821ee6ee3451e2ed0f9b705c0875eb806f481952d8b92cef54b190451a1c83fe89379ec019659c2a0606e4e992c1775cf3c43c8a77e3a0ff4496362eedc0b073717c8f55f8a70385464f0e0c6853dc035939c6ee0bca91005105061108eb88927ae4f9882d44bc65c8ffb7af23f220d925dd60b1434d6686b1f1fd536e2e062ca4ef4704cfd628d302dbba037691062b7da6b5c0b4d917ae82cef166c769d389105632cb8365da8a01bbb9538af290f96619b24606c8be9fcf3aad99479c17fbb5d6e110a3240bccf540491c8c8fd34157ae1ebf8e85613ef0f9f78fe36c5113c340dc9af9ab3bd678cc412f855d274fbe7c2cbe20bdd849620e22e0261a16e3bd659230b08de3f409f4987c580f00bc800407471c51d7c11460198bd3426c6c54ca4b3ab0eb83ae8335aa82e2d0c7ad372ca6adcf0394f3975b49bffd9e0fd36cf13016f49c07b1131822c2c42c5af4e61be6908904a38b2a6f712f75a6819e01996ba05cf3ad0cf5cb9aedaa418127ba992567d521aec5911c63a2ecf5893ab10cb50d1a0e0c9b91d71dad30df3643addc0f9fb403f0ce4d9712f409aa2894bb17b34ef88a0ab56444b4b64ce9705f389925b55c5861ad1e5014b7965059c7694d46afd98d423d815eca77cb06554f0fdc317e8f490419edf1f84274a2de88450de0e4a1c8a88553fe6b695d0b2f90bf16ed058af7b5f923bb2ef9d9082a06587c49a6f64bc7e48793c5c1ef5d19ea0bd1af197d8f96f4ca695347f2eaee6c323aaab3c450d5e32d87c0f86378b0bf7b98b2fb0c2479efe142a95e417206753a640a9f44e956e3eb8f4d3bc04b11a46429bab3d995cd7f26b42860c2c624021e2720d040c85a1e8f77b7c9b4273184a7ace839258a48f69b2f8c85f8f838d523efc5ac42a0bd1fdc3b4ae63bee0ad4e1b1e9e63bf2c3a8dd4528a8e0ff72104521544a37a12a7807c8ba03fad55e7662821d09981c31f13052f0e869fcd70c291b26a1fa78fc0ceb9eee08bbf0f58f805210335b9098472b77144182ef384eb3b1c3841ae9326bc672e25d39fc63df93611836c1388205939b5dcbb57c7c01d7ab37156045cdfb9160368efc1ae14f3d319523246386bfa8d239924a3658ebb4a779826ec0bbb45d20872801603419e913d6a3149129953b6c3fa9655820cb8231f2235442cfa78f69bb7304e78bd12fb51d0569848d4f80264b38ce21791950ffa46a3d8eb295f9a7418e80ef4a173443a7bc2b0ec9ed7ad7ba9fcb59e7e692f0ba1c1713b0e409e0947fa93be56f30d79529bf4b905a3ad8777adb040f35ce90ce3d04dacee1ea7d2a05d5f9917f2429924a5eef1e5fec73fbe641643c29b1b969e243482dd707a45aacf35f191263d0696554cb9a35cc62ea6170e4c7d24a687dc4f4b8ec9c89c07f426c8f8901bd8738c625dc663f164c33b48037fe69649b9e5c5c7964846c15f88e500064d7418dfdda3fb2f837e4cf08ca23f5892e7d7ffecc6e6136a01ba3087718c90dd9c67b5ff8fffd777bb25345c5d3f2be2a8304adc7c31f69aa140284086aa7572432e383d0ab37379e45c95778db31f202cc56198c6d13b6713f63e043b40d10f67af1154935f1c9fd4d004f3b47a6dffee3ded353a14692b91ab18e91dc88662c6460ab02453fdd6822caa28d3f42acb9bfaa0786888a2ce27b5f09dcaf65f979ecbc81c6cb296cae432d1af17595b49dae51ca22f01b5dad4115a068c7ac398553064f8b77f54e97dd5f47d168c362d97925f65dd55329124fcd4b94339a3ab6656003f0572b06961850923f276432c7a91490077bd1ff56b6e557c7d0fe4cbea8285d73ce90b276973ad2c5ffaba5cde524fd61b72cadcd1079c8ebdd586a26f61674e67b179e46d3e5c663b27f98135d1605fb253169373c04c2be414995834deb1a6d7328bb87f3a7625b4913f44d37b7341ad9e4634da8474ce2293e9a95d65988311fd2434456a33cff6765044aa581139b2b6a3e09df319850fa26f40a1a71658c6ebf3301700514b062e9f41e7c4d0f995871467a4599a7e040246b8e7926aeb141a08e38d9595e7a855690e90f87245fd05b240a45137f259c56fcae794e2fe1cbe661193db6648417fdec34330676c1f68434a519505529122961562b02fc1abe64304864e1794a92298a6293285f2b541fea3eb4d16a3b03072cc17bb05753f5b7ecd03f0a1f8bffca624c055553e2dd6484add8dc7fb5bfe33239c40378a8f9cdf5fd434b7780fc891d7a5340e0ec917503c559ace04455ad49bb355ef00696b81b710c3acba57c5b836249dc64afd910465c584b09799da656deac4e4a62aee7d670191e746d9f3f41e0f281c8ba36957ba8fc1fb885667de08fb439974a939d730ed2a60bea00c785bf7eee2784b7f0a1cc6c9d69787f3561be7b1612fb1a4e062d909f0747e3f931731fa86307bba13571df53d216dfb565cb521057d3fbf971f548bd2dde6215c820df5ed709eef5ad3bbd7da6b140d1225a314e1b82c89e666bcb2f852f9a86870df1bde7a4cb81059a7396246ce7e35aa65d52ec562845705fc34c6e40808bfbc814df6cb0c4bda5087c3f260c0a8f98d5feec48ef206a4a7791fe3ff267502fce1697f21c5ce447d6e1b63b36a5fca144d97d5ba3646ba0239993f04c1a88d2c7ca8d188e1cee118291b7f1c58da7a7b3b6942b753db570f6757c7c00ce90333bc2893a75de49264c69943576b15023f174c701de2ab94af34b016bc51d673f7cb87675d16fd7846f5318d01e697e6f20bdaf6046bfb3072180373f485c4f3a2b3be6181e6a86b9555abedb8f6c539c95ddc658e813868e4dc52f525f6170f09e5edfce2d5a0f7d2f17038731a0c7571cacd6e8f34bff9a01d3497c791e9957a38ce06766ba0b673ec484c32176725bafb323b8c8e87b8c5a9cbe31c9c15f1c7bb981bef7863163e0e44d8c2b9d5c655f9577368e060176fa27110abba2c6813dc352053810c5b821fb296b4484a4207ce7438565eba598c2507a8655c1ce2bb8e569219eeabe42603df0545f9b247384c5fcdb8cf4c35b5236710a6c2f611593260e29cd8c770dd2f41e1193b14f210d2d743bef055450f7ed40aa2a72af6ef0af0ef5b1be0cb82e82ded99c8af63a4f931d17135b04d5c62a6c1d1dbb98152dc0c977f80523e024b3fe1ef28173a4b3215b1ea2aa61dae62c9108384cad90a44e2245bd3406913f2128fb3f1a6a070569f2e12cd1c13625855528841b434a6b8d853123a8b67c3f545253c5f24e0130f01d50da1009c9dad5922fbacb2ac8c0da1fea314aaeefab052b9d063f40e8f87bc685aad2759538805d469a0b64ce3602afa28a509f9a7caa888bc6aef88d9f924cd43375a9ef115db37b071bb1fd1e63e6191efd4292b78ea896e698cf045569ec88b65909e25b5a245fd66507b1d53b129ffbc1538a47a2322766c7219e5453913bdc2f68c7b91321d654a3efcd424a1103a5bae6cdd7b2fbd9b0f580d4fca49677eb7ec4a88a4cb16bf4e93bf1c79020345e6f6a072a529854d825021936bdf9cf7386064e52fe5c8bacde7d3f5565005767a4153429780bb1ba92f3fa47adb116c8b73545e47eca31e0ec29a4c5355b3bd28c04f0d3e29485b1c9f1ee2a15aec4363e1cd89d632b7c7bab39d1f1c138fcf9e38b8394f56f63c42f7bc099cb9a8c060695be1c42aaf37d8d0bd4e6a4a31d008d0d250c834558e63ec6ec285f1d81416736c257ebf690519822b4234c11ede7848702d38eb2f217701d9f83716b115bdcf120500212cbb0fe7cfb31b3f80b09975b3df5cc6958150a964f64b96a4477be561b316c5676205d2de321c2c2b7ea524b4a2ebe96e38199e576629758c42ed0bbd6e4b079158cf4030c992bdbd2927b84b74968640f244bc348f2a9e48440e572f3219617ad8115da881e67e845dcbb3446306d0f0f732dc3a1666216752c493cec57721dc8084ab5b8fcda21a5cb9f41c6adbc55796962b10573e2d43afede174e5835ca959cf901403894deabc20c84768c0f82a664b037139e3f0b34e2c16c6a0edcf5cf7a5606820c2edc26f6d002535c880f3e60072f2649dfc3006f368697d9a7d2933c9f32db4d2a229d8514aae8f8340297437f9dc91de336a36efca02da132c3ab1307e7c7468eb69e8aaef68318001a1c0b275f3ec7adb85fa2d4edbdf56f850788500dc9734801dbdadbe7d5159e576490d1d0c609ec5b23ed77262c6123968785e0b422c36573903af11c8b498884bc1352f3e907f9faf6af1324adcae81db6d601fa16437278c50745da88eb88f942d766f7270290cc47054dd8d7763012651706e64183ae75728f038fabdebdab9296f740e4b6670c6336db252180b56059fdc68e04d5a18447b6a878aad68e76346127c6842e2528b0d7e8bc831be3f2409866acf474c60667f54192cc07f581893c95211a346e1bbd87c36c47c0f575653257c83b24f120936ac2a38f4b1ec9b226effa636820f2706429b0fe136214afa0170426441a98319323e28288b7810e6c2bc214ed9c36a656b644eab5f2d042e6b829b1fb682aa45a4bb33d09efa9c556d778dde09171a2ee54607af56cae54842256af39f9576efeb36839dd059a3016a037e06c8c70fb7abdc0bbf1ce03cb95a398b0998b1a45b88368052ecb2785fb4b9b0bca76446e98de9f69073000f9eaf6bd6ca7ce0bf6bfdeb3774716463d14233ff765297642de30ce8ceb3fe612bb6c52c2fdaa985474dfea1e4ea02fccc9400650ea9a6013c40e71391d9f53fc6d191c92cd6db4a6ec2d35cdc7b6408a905f66d055585604f42e4de336f53e7604ab331880653154a1d8e45d2a93ff1f7a6f1a2352af7e10188390a05184aa51ef16b3fd70113ac1fedf749d0fd1d3992b1497fb2f909178cb770347940b3c3dc84e5c8fa97b8f901c175560efdff9d5dad14107b06b20fd67721c9e638cdff04074409ea62386ade11b212bbe1b98a481485af68ee2c159dd1aef237d232e27890dc3855067869efaf3de0c64edfa23f4b508472b119392411ef5df5e2dd49179d39a55f227be7691e418d033d83d835f410afc5a694e352696568c16aa6b005843492250c21b93f04abc78457b05cdd6215ccde9c71a106d27f7b87d2e255402ba39b75c55a48456977e7eaea9e2207bdbb0a6c60819308a5e4d4a359bcf2dd3c0c699fa85a0118dddabbcd43e35f227d2a311b3621108e80ecd311919a95d53b8ef12584ba969bffe8bda2abd72e2457610e46b76633deaabf46568ec68828416c066f3ed9bca12049b61b7b1e9ba159c0056ee20a5088e816062b851ac632221c6b8662f33ace7da11786750c37271d7e46464d60b5b5301051f90049c14fcf9e9538d3e9f29bfc478c8517a6170e96cf613ad471980d66e20d17bb6bdecc3398ffc92a3fa6f1ba6ee540e92560d4612aab4cea3ece2f6ea8da8739444718e1672b0047b44de1a8cb2648f4e3892ebffec1f836dfce9393a411191ae43237518c10f1ffd18b7b2c8b051dacd6f83d51f80a549eb60ffa3cb0420f292f2bf90c4cf157401693b4c945303ba655320c2802ff6ed2ed542611d5d7e2b59e32396ca359db40836a0303c7b54022c55205b540da08672281260289cf6306927438f8e6aacc2714360a0f752d839bf65e68cbb8a045c8ab36b791c87904aaeb579677f18abd7d96ae20ac4b689a7ce3939f736bcb8a8a40b6005f1c171163200ca38e7699b34495ac69b13726ae7106c3c7e7eaebd2a4a0895970a054e1d8f1b1ffff9af04a9ab75761cff73d6d54a7f655570eb524bae6f27c93e36353e0cc14a8998b3f6280ae1755097e8f3876c112ce31a7cd7b6c53814e51603fdde2ee16e63e412c80c991e4215ba616968a98ce9d120c7f9f8d9ccb5849ab159bfc20e4de6b22534c4cec9c74ca9e8f963f83de23aa095bd473390465c42c8db3090f62beff066ce1d9e8f17cf1230516c9733016e674b8b1f82717ac9b97a2963afc854f4e907c60c2d62f25128bd66910c130027d85b06d41eea6f61fe6353bd73ddb7434676b9e9faa4f29ac5288e77962435d3ff0413d96251e900dc6996f7df4af7cd4a7f2ec76078e832d6556091f3593bc34bf4a62214e11e57f87bd13222687b7355e847e5d457ca8dd0421d739645e0324a4080437eee963ad7cf6397a8db8630cfa1d800f7a86471c47e37bbe49733152d6d182ba0953d3216bbfdcb57b02e48204db905cd28cd974f2bc010a555c0a0b244c629c00d7874c2f7b8757f99dd48ebc555f2a2e7862ce114e5c6c706d95476a2971380d56450b99a476aea5be888cc58a80da7efb82282100127c06201f98e9d2faf88269af5e7620641fe02b0a204190d028314a81073c22448443dcc52188779e29107433afe36eb2cfae376b083d2042c9d046d705ef48608c27c220906c7cde9149f6e0090d7658a9170566a881c9568e8b9c65c45c17ecd807d8aa19f266efee0672bc6c28c6ca757fd2c483c24a0f3cc7296e649d8fff8478adb1cf9c01019f762fa6fbc30c501938d37d617cc0a16b0918c93e290ff7498dbf5e0fe90667d54785499bccf4e306026be62cde471fb456a6198edc7b4b6113996c1b470a43c07381c5ac6f42c7dfe2059bcd818aabd4208950a497e90d6115b492597d3061a79b36bf5f3075ef569ff08980c03fb6cb3bcac0675c9a5a15f04e82fe27349433ffedece1549b09c14d1507be66d75c1710124d0e9480c0eb903d25f473cac69e1e942c30c191e031d1c29aa3e27a68760b79348de7657fe83fc485990e0aa045aaeb1a085af6dc065c6c5819fec1530731e9f18f02e7334f336df929453f2183347a78ae4663e8e6da3e3515d4fd48826f4b4a2b1f4615d77ec717986b4599244d27cbda362608f6443ec3b5a983edee197c9c97a5b5b2cd832bb96d97c9506d87a3ba0cb69cac7dba8561b643bc7ec8c39a54949999927b90c6b76ab498907b9c4fa6f277934aa67dcc43abf7485ea608e815fdc17ec96086183f905b50051dc0f14f7577f92051830e872df7acf772d270aa525e51c3e4a90ab7304b6ebff881861611fcca7e06a03f15b04384d184d24568cbd92a98db68b11118021590f1993c0e746197b136bb1b95d4c0b1c132c4af185a5d3b4a6e3c8d5b7d573e0e8b2af1a010d8764f6cb3a181882ad79fb1e86e19b41ff8a9bc53b096240a97fa91b3c13684c7fb5ea11f9209cb74676cb29390f4478f7fa71454e6eaf7f58a0e8a913db381630aba2a01dbf783d2b6fb5c1c3e308af5ea46d62387b87c5764c0e0f643c8379a40f2efa7d8166645ac23d3ffe3793a3fa249564b113db1a8d4e1ed466adf918fdf11d2728d0cf9276d7d7d1e5763b7bceb9dcd9c4f1c3636f95d38829157d309a7cd9a9d1ab6b72a213d00d1aedfdb9a40dce30c2ffa7b463398106417a2e6fdeb16ceb2395154c4a1c1e4ab3cf06132cbca257c905adaec9bdcaf248e51546b2d1523c44cf09f71423addfaca9cdb3766aa36d8723d1999001339ad67a7536c2e2fedd2c49c7438ade2a3172adc35665f14e031e7f5866e68ef26ff567ec25978920b5bb02f8b7ff061b6b3f6eb278a9a8d09482f8d2c06a854d4ed39ec7a851da71c59165f967a180b90556cf1a42c97acf3a4dfb78aacf44ec986bec3ea5025af7a8d2dd923a417d73519dd7c377025a983d10dc8093b2af491c5d9473c647f84992a00203ffe588235ffe33ba57dd0e7a7447154abda245c818e549289ca293aae8bd7f1f0a041f0667684b5e909b0376a395ea4fc6800197d3574bfa4b3b6cce25d7f77092b5611d40e19f16bdc61ae4848e6038dda97a557b7ccb36a092f4bd39e76cb4d4873d629f7fdd139b9fff18f0372c4c5011a57b14c5a400ca5d506bd666a95150415552251665515746931d888454914c16877b3f0bd6619c8d960048f8a0778621f6936a17cc113d84feda776998b23f3cf73887311f43777f9e21a4333469223ae2afaa45f1aadeceededc32a59402e204ae0451043c229ae4dce29b0db516ea8c6cec1d86b93eb89464bd3d536e1d948b626909a938c4a3a7350be624098a8021f7e4ed855adb74e69a8db94ec25a1b5cef8a2cd76a6a0b8ac6529b84d42a1972c57adea01239ad5609116d959c7bf53643ad253b735b8c21096b5d70b953594853ad0aa89767090ea9051b72e97a807672bc7644bc22e45aac373786dadb74d6ce8db94e612f13d71b22ab8531e5828072732cbd2a90de9ba1253d6d0e1239374888a8911b7a73c1d4449cbd2dc688612e10aea2acb7046b4e8d604da8360a930ba5d784d095020a0bb6543a372e8d4e885749eeff32581be7c0fd9fff5a7bcd73d63ef723e79c7bb30c629c1d9b13070409d349b04c92d3cd6a48319d31d6e137543a374e3745419c9fffbb00a6b6e997971b133beb8d4d8e5a0527ddc01bd42dc2ba41d85fcf556381c160eff95e0b77204d0e09c7337a22968bddec58070411cab2c3a0791945e879981a10ff77016cc511df98b8b1417f8e8155807068ab18a9854a7520115854c6c0a01903694e163814cd86c898c636625ffb629bf8db486d1b185f7b950fd904c5363a3a14fc1cdb0cdf9818267561bf2ccf7ef9cbb28c51792d911718cb52e8670bdbb2bc5e95699a2e0936c66f7fb8f8bf5c7c6b547cb687014c8fc7b62f22b03d0fc2a786a866e7ff60c0dad2bc69d4d0ca20ae8143fb3f93ece7b8a6eb933fc7355a3ffd39aea1b181549f605bb2577ff95b329c730639b6d1f939b609b2a028c6896128bc541f04999871548447108b19187060278eaa9c8da8023656115be954af53ff3906b17334ee0ca3dad35af37606ad470f6fae79fa735e2fffdabff9df3d6f4e92fb243d09af1df8889267dad394c50390598e67e27e5e19a0338cf63aa36b8794c350e9065f5fc808c81bbca85fef51fb778afd5df81762469ff6558873357a6fd54b8c0718364c3c6344c89d6149b227760cd2aab1e7cff1ccd76f89b05bff45e5b5fc1ccfb4f8e8cff14cd1fff5f85b6ae66c6e928fa6d65aab1b86c7d330d71b4aa6582966774a0256a71900f14c50279e015ae09dda7963bc325ea81732c0201c5b51d1161b4da7b141d48b10422357683e5e2b327548cccbfb82f702e47de1e586ae906c4528658833f440d8130af3140208f5131ec34dcc5e1ac630c590839a194da670884b9014eba6b8b6ce540041abe2964a116b3214b30408408821ee06941798168c2928c0c096c4b07a8263d8a396b4d2d4906b585e20ac4b85d9898aa19c8db6a105610e2c618de1d3d002612ba431d43eb28e010833157de388aef20469086478a6b5609a0c837c20cc3f86c828d3c1b2c724a509121e693a9e844a464b4929a0f08852250eed33884486150887ba866fc30208a3402de52a430508cdac8c9e5474151902e2c01b5414504a5d985b5d942e83a8a54c0cbf869a8a8ea106aac886708040517139888b273f268ad4e024e9e93196cc0a486c69f9c0592b334163e608450b9e2a236f1be8467428e22a7c1bee18661092c2b9a1d19045b86624972181030280cb8927a41a021982103b40ee917968d8195a61a0a4ab0b710c0d200cf209739c1e91f5e6ab1c660a7b0cb58f0642b7e14e28e2268636db1a66294286aa90a87b729d0ea3409873fc8052d419ea201a4da7691e3a6d444fc962db85e6858e1b3b9e44903d85794561c27a62925197c5f62762d60a4d940434641a52e0282b641180de7befbd9b63efbdf7a5b82158ced045185535d46efac406011ba8ae3e157cbbdc57df7dda53e8a967b9ef2b2babe9f58ad67a853f981ef52b47b486de759ed3e9aadf7ee7b70aaf3bbf0361c3e88a2fb56f056e397df7abcf76e29c2449aff63c97e77e5be7afb05aa2a8dfc1bfadf33b497a15f52bacaa6589faee375055d1b613e97f7b3ba947b89905b7a2154944b37fe1e798e8f457f87c02fc1cab004a7df5f3fcf6c53fc62d667e027e8e59c4318ba098d8246641fc69309f1ef56bef9df4be0fbba7bd939d5c3b597a725de1df0e7e9dc21ffcbaaebe72a7dda7c1bde13d02fab7f6bbb5c38a5ffdb833e4e0ced039e8e206ae7893c5a769295302edd7bdfe4cf0db44fb03efb3c0ada3356c5f6e9f95854bbfa335acbe57e0f56f7f9eaf60072bc02cf08fddf0e7a99ed7f708bc0eb7b078d2af7ec33114f8cc29ec0ddc3abd2c77905cfd8aefa7bf863f68bfe2559eefa7575f4105ed49bf6effa184791edd9b47c1595cf1c578a0fa6258ed7f313ffd5b6f9d0e2bbe7b32f5db47ae90e5baaeb0f6eb4ec324f4fe07d377bf53fdcee77584be835b4e15fe693f7a76ddd11aba05439684ded7fe4742ef7b7385555fc6ac3f76ed7b27f51bb877faca2145637c1e6c9871ecf0ded11a9030054d616ddcec0930ff637feddf18feee38fef6ed86d15aa9fe798c959c5c44acfaabb983ddcccc9ff9fa2953c5c4947526ea867e594239e978314e6c314e3d86f41b314e305d149d8d5f52f8a513fb739c74a207766e25ed00936e689242d8f47a1e06c9495b86c4c4158274d6c991c32cdbf3bd9f63242e22696e615b96d72b7b8921a608034792ea61919874c8916f96ed792414ff07433221f1dca047743de7d1131db37562fbcff1d1dad1184f3fd2c1431e6101c7a32a1a7e145573e46473c43462ecf53c4c8af848c7288e2635cd12b6ef907f7ec915384f2fb321fb9797b27d7969dbdf3da97ed2b3bd199c9748b352905252f1ef3869ed6f19ad4d482226c530cb6012a040de20048ec1e7e3580693c0053c889140052e8081f65ff8187a3fc729427cfe734c54f77fbdff06a37c9787cd8d430b1b7b757ab47539dcb16289a89745139a521091cbd5e32621c2798f8840f898e2c48d4a9223223ac4d6b85860b553345a2927b767450d613be3c7ed1ad2f11912d11ac5c9397183694945daac1f2edb0e9789d6bb34d5ca7872636154f403e46aa1d8114241f3cecc61bdb9444c5c2623af5110baaf1837da15556d540c1a4b6e7ce40e117a8142c1b688904d14dd1c14a43517ca99ab33e4b5e1d13689816cbc3933a85cb47149eed6908f899726e8e6051134e3964969e5da5a31246d64ae52987ba6e31d71f50aa55c1c2f9054b44c295a1fd70628e7085088768c4a8826ee50899b53f692f0f1fe74b93759536ad480e1f220b66cc376c889b7e6048937c589112e8e9c9bf6a4555ad27299b93280b840af76ca56eb46b504256c41c4c83511b4f653e2c54f4d55152070ee09b5b6ca919728c45501e6eac07165f50ec9689730b94f482b5030f1e1c9f201a6d5a588d2b6b1b946d07a8cbd2e7ab85039aa14dfa428f7a80987d28b82a835d2e3fee8c1a1d203e2dd99e2beb9b92c92bc38449088b95f5c6f5852687c3959ad802284fb09a2b3c384882872ad9037d74bed153a238eb93bc25a70a165f5986abba0da1b4b4f905c29434d7ada339e9c9d5f66c50ca11f6f3e8c7c72d4309201daf9faba91a2470939fe735c4388c55d74dea8b62e1951d2dc682ef031b5437783c91551108da550b0f8702151c1cf710de0577f8e69cc95702921c4a50410979e586a2ea6118ca566e2a8262e265c7cc25e5273977be70f5b257b461b36fbed5d76d8dfb8e60e5a3c6cdfbd0923cae60ed0124e30b2e40e523f8e398312f4dfde7fd3a7805ffade9f8bdff4e5ba5ffa5a3c7cf0b7ffe0839f57929b886714c5329eb25976b1cf39e7f0afbb044d1df67fbdbfe319523b9ee1c4a746d693a4f67eb4f049bf7dcca17c41febf95df65e8e40b3269bad0b0167e07be1e550b5ff33d6344d65b6bae5d42b8cc2ce0adb2f7cc7eab04d9c01d24c918aa2b7ebadac0bdb3833b08f5a6bd4cb25a4cf3567558184f45e62ffaf1535087b96213d3d7ff4cf5a9fda94fad4fdd570ee899b3573768dfe9f5eb84d5fff4644bf6a7cf2dd9971ef566864798f2c77f9ea7cf1be6f424f47ee690b53f51df2bc7fe2ea03e7bd593a07e9324c95c9e1e8547203fead16c7a13f6645a6c09fbd35454baa8b5768653776930edf4a27a406250f97ac23eafaa5fd775f5bc9d01f5ebaaffeacb95c3ba7a4d34555e6f67d87934af1bac78d2eb980d43fad2e7010821e8578dfd5df8a457fde9773ed4afaaea4b7d89a22a06d29fbeaf1cccf344778ae485f292fd0b0c063e0b7a3f77fbbd0543731ccff3dcfabf94355430dfc9dc53599d75f7a93c18180fa3e1cc7311c673df39f7fa37fe0cf334df0003060caf1b8be1979c83b033581f113735899bc0e226a29fe3269dd88591ddfd6ffbbc7de687adcf9869cadf5e771710ec87d26fff1b3f7fe1dacf51fd1d20a39dd81cbf98fbbc3c7de95fac955a8309fa9fb07dfbb3b903766730617f4e7e96256730f57a7a72edd072c2a6b97ad06b044adf1f7f2ecb13cc549726947d5c3d8c33333ac3740fe4ae04fecb178ed5fe458a9e3e853d7aa1e7ffbb70e6620822f8f4cc1f047ec178ce2dc1f11798d1efd5030aab1be67cd9580eab177c2caff7ae8d3f56ff1283df802c822655be3a20ff16d4f4a8e9496ffaeecfbd2b7d4aea71efca78c9e9ffd43fc62d2c3862cd71ed406ebd77dca7a5cfb0aff44a37e729499224e94bd2f4325febd2f78184f9596ae66dfaecf7b8c24bd0704b87fc35dc72fa0fdaf793fb7eea09a74f8fa64f46fd239c96690db4e79c4b45538fa45935bdcc373deaf7ba7d6afad487aae9ced721ff1d04e09d1ffd0efea55efb75b7dbed7cdff9ee77f04ffde9571fea41e0da93d0fb26bc77f82893ea4dc6ae3e2a1ab9926ea0ea51b5f423ea555455d17606b44c570ee8c87d0036bc6e5fa266db97e84f5d7d1f2af813fc9d29ecad29acea11ada1f4fab7f75a61a7a27ded2bf8b4dc7aef5dc21f76be82df288aaa4dfeafa3db9fe06b947b5435d31a50bfaa69bde423baaa68bffb122e4b5ef272077f402928cbd2ebbd2bc02aba79befb92ab66e8bafe4ed82ada2f3deb5b593bb015fcae64e1f18fe57a5b28617445e19f562df0583d525600ffd81e0b731fe9d83994724ebf114d8f2ae9ab20291fc18f2a35284275425136948abc4189d1f3c3a3d5b6a2ab88257b9e304b80fba112f368414787aaa059a3018c02b4d6e55ab6c25604452911b7881b26958d58c2d3d326b4c97adc6eb3113246e6a20a0f2659363e80b668b8d8f1ca1ab3a05361814412f9d9392cc1b5f4340704880c10060c265f3b8c4830691559d1594b6559961c4d0ace0d12ae242b37a11e60404a40a46149596d28cb7265cace37827c0873204d414257e62b10664ea8562821d3d950966529c58f6e1cbce8d1460603e8880a90144ee29430919ded7a94b35be89c49ce058823b7ada21c352184a411997c9d708232ec7c7b1344378d8c4c967893e984056e25499254cd22123f23c134a2474d8fd50d0569377c78b8e918ea29b342b074b8dea4020e890a57124d170823886c438210915145e5dfb4d01e62f33c48efbd9b63ef44bf43b5954f58ec1bd30949f2e252b1c974e26149dad8578b36291f2c5d431bc12646889028de94c1b3096f67004c095cf1674ad8164b1bcf4d90d98b1d27b058f0dc56bcf4cefdc8b52bf4de47ac24b286a391584624c92b7bc78afb9d9f3d6922685c379480f0082e4d60c892a0baa81c39d8aece766d522313922449d524b38486d8c8ac9e5e9c51fab304d98b436667236a79b1e826620d1a0b92244992245533587664d523879a150e19156d613b926cb1015d317df22ac74a5f8b114b9b32054a4884949094b823a8153cd8acae944a354d77545324848a885b114a65f6a486b58218a7c784522393a5de7b37c732194a47be86494fac0050d4542c73c3a2319c33971049922449922449d5bc2a2164848bc8092f1b3906fb02127c1216262c1e2926317bda075a664276d858237a2102dbd08b2aa709eaa728f4c9225957d2af469449325d4ca466cb460ba11d23143f942c89184165455283c6a481482d048ba7e7213c9e65f28d81aa302c1b2c9a7c29e9ae25b226b31e1a2aedc4a3292850ea32bb112705213262da716544c6d3568ebe6ac17baffabdab2812f9195133f9e171a1a605ec92616a7aab296145f313e32ae8f77e84dd2cb81fbd4a5d96a5af862d4bcef678999363f763e63c63513352f0a8e1e9c90166dd242d29ca88b16e8cfcb22c7d4036b3fd49ea81d7d1c9acf0c09110ccfdecf5ea61ff9f08a6f6de5b6badf7d67bef03ec7e32c453eb679fbbbef28d263f33203f59929350fdec8b0ccbe4924c6532a62ca42c7bd1c83bbeb2102265713ad6eb08bc1a651c14e1008724cbddcfc39651dee1a581d78779f2b3878fb8a8807112001a2f162017250e25e4ab1910ff84edf5dfeb08bcbfe1df07c4c60bbb1f086cb09cc85096aefdfc83a3bb5f08c0cab39f3d5f19d07ff8da8144c180bc36a0fb983e3af0a471e4e338f291f371e49c8fe3c847edb5fb7d1ef5b35f29fedcc1cf70d61af8f297d9ba249aa2da4ac1e6d8aa98632ba79fbd39a4bdfa80a20a54dd1b82507df5085184baca5e615d61533576fc1c6d1832fb905c15ff0a3fc756357f6644de577155dade5725d828e2c8649514550e31f2420c860d338e56609f732ba4cf59cebdea52c4cb6d5239fd959f63aa1a742e431b451c5deb2021e68facd1daf44932841c53ed50d07053d334844dae0ecd0dc79120384c485f54223e1953d90891a2aaa99c04c292f649d26b9d1bc711963ae3a9ab756eaa69aa28ad61f6205b39ee472d692ef63a9be9408a00d98e9696649d2d019a0ad638327327d9711c5d82c6f14790fda8b40b59324aa610e100020883170020180c088844822447621090aaf61480075ac4404c40228f0e2591301805310c83300c420108022000003108c32014c74228e700040ce9986a77d63692b23d91ae6127cb2e0c31da1d93fc49996eb7695098835c7f921065852898d2567121dd20a5873307a6163e4232c88932f0b39014c173f8dd1813300712ffaefd06c9cb2342815bc488c561323152f9bed09ab99167fad1d3a887a67abd242842b0babadad7a687a028ce2f11c579b10ae43d569e0b60fde74b2f1980d073fbe99018e10d21a6dcd23eb9c4b6617db40af0cfd43f021e8da909339149951e584d5e162d23b4c9491e789923edf144a7efef0fa27fcba1274513e78b32632a5ca66277d4a840c339f16341066f31e8921b874d8ee5ce7371666dca9da0a4d5d28febfabf8123914071a527a6925a74c240c530390756aa6a7aa14f57f2f4092b65a20b0638c7e4bb66d92627eeb9522a956b16fc6c57a55bded4fb9d741347bd906e9e90c6828cdbb8460bf9fb3262634c5546d883536f4b5d51e10f4d9bbe33fed3cabcf80f649b71f265ff485d369ae4b546b540c889033f070ef4c9e387dd5858746de707459bfc0b5067675552b162151008f22fa49fbf43435cf75e40ab89374641ed3e94c752b1af1a845887e657eb8f8c83fb828070d8833aaf9f8a446cb866ff01f4d4a4a6ae8e6f3427f0cf9863301be08947e3c1059a5ba055a1dc5d385c3aed70a9f096a7fbcca5cef1389741ea07499815363d22c7453c974dc63be6d5f54e4713f34ed019b251d949058dc071460330ce617801463d643aeb079a66dd69d4b60422a8e80c1345fda2b890956154920b84ba0a68e01b2b3370122c1f3fe97e320af04113412d8c57606a207c08f114bfa4d15c6ebbc44b449e0dd140611ee89d44b07e12b8608e6260c69d0f5693efd172341865de0093af689c7586a422166538b2f63e047e651c524f50fe39b6d7848ae402ee13d8ef378c22aebda74208937dfc0489e8a28618f06d5b32340cb8a9cbcec2edfd87c7cb5ce60b096e9023eff8eac2d3bdd93520554a8ea0872f2498029f4eb1efd620f4b27b104875fc936f346eafa8cb1f20ae61d045ea1eca2e00b4d2e234006a08f88ef25609dd2c8d22a001a5bb34b663181812ab42404f4615eedea3acce5609b9d9b048555680a44d292bd424a328806e79d3b7627ff4fe25c4c9a9da06c3aef561518a16b28d34b3ea4443f9d255e91603712951cbaf6f91acaa65445895b2531072cae703713737edd9b0e5e47af05d7c69afc06a09cd62c0ff313920978cb906b8fdca4a22146eb4418b8c083f926fa9c2ece3f73b792c064cb99b501c680cd3d6e5522b55982a151e204c18c981fdfa004835bf17a8e5562f5978ab08b30b0695dc96fbd23b91409363e15481c1729fd0bf41a2ba43bd9e88c1ca645a98a0f045dded8d29be316ee883c81106e9837a2e098dca6afa39c597724d0a206de73daf1774b2afbfc4f13287614fab72955cc4a9f8f97866922f101b64b64aa14957e9b797e04fc226d06d633e52330db39bee2a2312c09946857b6bd242a2f955c5f8f5fd8a27ec991e55bf5341f8bb3d92653108c0460646f2022b34ea10753644aa0cee3781aeee2814f99b9c996aa8c5873d0843bb151976dad7c4050406f1365ebf19c0624a75ea06a499db2637e507678b57f5e9b8c4df6f42910895c35f246bf8941f6bde3dc3b639cf352af8c627593d753639cb9f03387dfcaa56c9eae32b0b3e1f266c2a281629ad5c23647121845f9cd84bc552c72247880862454202d738a6e1c7ef8d02f5302495b084705c2ff5563a5b8f773623fd7247e65da44ae9223d7a5ffe0f7dfcdadc41ae2ede955fd9919e320b2b91da44f68b79973b950900182f3b48b52be74cb675412db2327a3412fd0ff58af60186fd50f2b920523528112657704c2d0b188de882e368a14074358114bd418c1e71d2c18444caf5d4266f9cf4dc0fb493603735c123e082dd8f1d723c32261dd9a1f800f11842b1e2075857ec1bc394a34e414d157cadc7a56f9dbb2867067879754d2d90b099e7076065ef2ff8fbe349fbdda5275686a26ba6ac1b715af1ca3b1359b94299f245083827626089eb6329f795f928e9dc0f5759594bf8eef3dfd0d6dd0755f363eb81c88454f56f805825d890133b8d815e73cb8ad012b1a9c38112341f4863e787c07f6859c02e0622ec5265b816b88d8cea8d5ff58595543d8ba588321e44a8f9d55f1f34e8c617c75c29b64c41648d0d0c17bd014159ae5219626de1ad247ec4fc65d5942368af2b99eac1160ba277074d4d1a3870334f3c3805a1c6518282dd05cf290914e4718f02b3f6ec8160135aca9ddcfdee385cba330edfa92c1caad7975e378f1c8210d35d23981573887726c23af2f50062c55c1a967c80bc53cb4421500964ba27f4c8e23b47cc4188630ed7ac72630e3dc7c3f497a7eb235cffcb28eed5e3b9a5542c7e9d82584a2ba11cc23bf92b917f1c8d1fbfd0e61ed7dc8cfdd89ac30bc6b565cd1e39f25543fc458f9664dad6d6303d0c4668106023a9e069b8f6d0b36407f04017c9b302c5f43ca85ccc0da8c878968ad07015efef4b28c7d50e86d0f8e6ecec16b73ce3914341ee01004e52d88ea5203702e088884a4bb3b52ffaefe12f13f18315f2d4305f7944b1130c92478a9c3057874f676ae3f4a1918cbf80181707627cf000aa10e3c031c4a838175daaa474b5108376d1f179fb2d5c3541f227142a9fec7e502d094ff88892806e1faa31f6988cc193231429c490481618ff5b719680998f5491c31fc88965d14b63dea073b756d7b068783e9fb39565d148b42591a85c1b22c3d5748650e4464236cfecc9c0578c8709c521b0558c2a16814ed3b685eeec2013caf1892015a76d419c58d8bc61c2e0754b57a9ea404c15b6638ef8071f6e5c95dc19447f6302619f722e6e9238c3ea70dc0f0c85907da9629aa77f91ab4f166f4278c9af8443ba6b8ace6afe9689e92f284ca62e8f15e302db293938d5330a1a631bbb24b063fdc5edee2a3b38d15e102158a7bd131bab9034591b73a93c33e0e2d483a755d45e427d810afba6cf2bf8e6207cac37277ea84e6b5cb03ddcf8f8c3b2b15a360d51d1d751d270c387cbbc9a287d20ca28e71df0028e9bee7f8044672137c091e21ca6fe46c502b3f2b2052f3950344eab5bbcd3b7edf8b89dd1c873156ee151046aa9ff9d7587e44bde9fcf7ce8bc485a162af565e372f71c0b8c2c0c8ecc56643fff9b8b437220b6efbea7f75baf4d93c0c859ad9b1c5515bb562673373e5c38da2cf35bd21910c5949f24f7d67bba2d9fbbe83e75f91630003a8f5ea0893328cf7350375bc10085ea824f77a20d00897cd71770d92fe0943b4897fc17c8dadf86acc110086584f21f2d38e42c46f8050e63e546d9dba89bbc3ac4594350cf7c4023f63b8a07bedfbe80dcff111362ecd2cee90806502c2980e6867fb5cc867a45f80494018e76158a229ac16ea98f22c74a2e94740a5b3902efaf4f40e5a364e5b6bb8bef0d2a138f351c2ad8100b5a97f2c1c0a60fdccdd57e87add2ab53f05d21796dd1bdca23c2c4eeca1e3ed2c593925ed83b2629e1d9b0de4611672a8c0fe22829be81d980982b135a32fbe445d697752d9255317edb363867c341b60da9a2bbe083c445ed4a3c8d220a067a430a858341d595d6d9c2289d1837f1133de93bc06960666229cabfd0a037b4398ebd462979ea373ad1e9f525bbad442a23831c3f270de352f7b22368cb9014845fc4e635dbd1ef8b8f9058c2454801fcf8548a141fe42fdb914bb41215edb84408c794721c278be39f93ceaedd30115616ab1cc54d46a4ad19a5054e288e0a6c3c2af2ff37308411095fb48c2a93897275634e2323915f0d8eded523b1279c3fb296e538cbb35ab94f8d801c6328bcf2be2c25b4abdea458d08b3ca8b90dd314411de97d78ef275ed2608471d05275507b559d7b5707cab9776b3b7888a6d8c13455d8f783a28bcb885c4dcad933123cd575b4c3bf920c42ce4275a482847470f528172694d513829a9b7a94ae046f85a23bcc5769cb691c6bcade3b8f1486d38340837719f4592d71b1ba9e4d381085ed548c7876df73d19aa46e568b2bc84a54ca6541695709713d441dfbafa2a8279cd6828e2c6a10aa604c645d4ce08830fa08c4b458061ddd8fa7efa0f9acdcd2a2b11a50abf857840670d595097b7a971bc51584742c5635e4c8358a094204e3dc6fa24709a8bbc3fb826cf23a956867132569921a19d7ed2047ad646432edcc2852ae4c622f5d2786ee81d3bb7a551555fad546bfca8d5b1450fedd7c5fa81b1e9a502596ee68645e7689cf1849b718c9b24afa6e315bd75c7c22d580d8ac4cd424c66a1be4d11fc012dfdff552edf9f6b9f1e084bb9c5fa7fbeec48071d0dc2dcf4bc14494ad6ebd8e35ec41492a9c085f22b4703d0353a3664b4b54523418b0a75dac711d6485266305393bbfa5432221b1be9e195482394579983384f14ca7d256cc7df9125af8960ee949594078cc23524a350c4c2d442030039649e215cdf6215563aae2ae8e48fe9db37435dfda48509e49450bf27671f200544d29329f5a8252edb26228e7fb367c026fdf0b3e004f3a696baba270b4dee6337aaa9441d3db648f2f1346c4b2bb8ff451292fa3961d8e6ad3a8b473a56858ece748df067adce9a3a1485fa7bbb8caff5c9459cb6ba5679f6a0af22447725487e4f4c61e14e48f321d37700f55827830525b46c7b010d13bce0c68752eb6e675e3f2439b6d6852b508a98dc4850b122f63effd430e7ec9c889127ce415b91c20460762f86f64fe929b70b07728edc94bc9f089067e4c6036276007916cc328420ea66b36a7fae0b76893d687e508c80084855c70ef07369fec7fcf74d66b8f47c525481bd605b529101eaad7f18ba30dceeadfedbdc1b740aafd3074286aaede9baf1adf38b7ead58c7fe62bbe7832fdc577ee860aac1b34dbdc995e71418417ce80e34f95df3af0784240092b998d402d8f11de09eedd8945f21f97e37a3b4727c0b0a6d4ae8406e888022c3ed1779a5a005b617ae8d16ea75cef6fb1a8fb8abcacfae429c929d1dc2994a5b74a5a48d8b8e30d2e00036fa2505f1d5947f7b197bf340576344d48f88a090f5d31c420e2cc17a18696e538e4d83a8539b437c65f649364a6e0dc8c49cd2ed7c47c2854b25894b34c85803d1860d849b890e15b2a00ea3d9cc0b5007b10c8f95b710a766e7a354cff270aa20213f0f35219b7341f75cb85c5a7a2593eee7b711d98f5bcda60898fe28aff29e7374d0811815692d926e6307deb76329f3c855cf606c91f0e7eeb9e9a577a67b851cae11b7a83760f7e394ee632eace748f53e1cdf469f6ad35dfab24187b9e235729e4eae263f6b4c46ed7d1615035d6a10b8c9e3c74abcd5cd501ce2858167de876ea9651472738e23b862640878ac30edc0c7840f6df0d2c08a5dd0ba34b90fed16fee2842e3f758eac58409b0c7e67928b6c4f839599aa8153b5d6468b03e4f2daa74beaa2b94da14f4303d8bb123614b7052fd132cc3c58ce8f78ac1dead44b4836a03e83761f50f20763524b2358990eb369ca61169bea9426f4ced225fd394e209233cfaded8ea4be4144768478a902d4734befe11d377233a14ff8e9a55a67faddc7c60232091d661debe5bacf2ea1b15bcf4e257d812738f801556a6790c547709b92e17a31e81614ee15e84f886c60af8b229709e09f4a02d71ee90e41460f14a5dcbaa5d45d4eecca68c23e74063b7c7f36c4666084f64940a302dd3aa398f5d7ae36867e3d4c57ee6748965c05bae94a2fe021ff1d78dc0635637746118e357c540ff07e21d5560f2feab9089db9883a98738bf220c7e327357a68671825cbaf310b26366a3d338ac73b38c29785d56d01c89aee8f6a2728636008e8cfc9d2d5924a4e874322e85b04655094a4cfa009724be9c52d5465d483f42b87364e9df50d4748ca610cc33bee935cc25fe33817b13105d483cf33c8e28603a46548bb5e380e72dd7d7c4b58e7dea94aec8e7765810e44fee5d6742d2bf6d89de94f01e65f016cd8b8e6b4ba6ae7cd726625a6dfd54c8c290ee0cf66e4eb784c6f893ef8153e998be5ff9da410f86c94ba49a30105894301778cb3d30c694f734d6564434f9fa705695a7a9cab215e08d927fd07299c821dc0d53deb1c3445949fbb7497d6fe5485296c3cd76066aec8d1a9292e5686899d1fe3ab9019ac842bef6ecf8996b6384f741dcd6c18cdcb6b66586bd9394638eb11227ec831087d68ea6bf9c7d99799171efb4646aec23c093950662012be279943c4c48b7b7f5411da2ceae7d6e95f5c6f1dee4c02a0f32160e5ef8b2347562a49fa663369f3645762295734444e26e3e08498ba88f5e98066489b68e9c107549c8d212e9289f7766c4957c841e645f79199789373b45b880b7394780ba0877c4812e234ecbfeaee4f93004fece41d504630e41e4d36d4af598f1401f04e0b2a20f73360565296d5b774a4a3038ea4b8f3447576a5a1fab3ca84bdf2e669ea0ef5cd69637d7674d08e07cf45ff510916b59fcf81ae013171a67038ae021a25f12f7cad5b7eff43308386baf625373aea9ba1c27bcc568b4bbc550da0778e536838f4cb8d55b5dbdce01941fa57e61922cb4c9c70f74f59ab8150748a8c22d1b8a1830ae616a8785418d994245af56ce9f5e37c15aa545e405d172f9a2d147d639eb247927491b3d6ae1f69dd5112fbb9b8e3934835c88e7ce3857885396ab0e6c7bc9381d3635655990bd1ea770e6fa4434a135ca647c4754d4d08d5b6b9dbeb8ee6e70128fd37c22d1c1ca505e35ad8e9b563f4fb6ffa217bd11271606ff5d2a8c87fdcafba885872113fadcb0ff2e79d195fd6121cb146c30dd0e038e40e354fbe0ac6a8a4f6ee0d239731119caad3b12a1ab1bc163790b59bc12ce0f2b686956afbb05c7f017167cf959e6b1b563a431832d8c6989c8af830bf71039d1dd8ac76629a7164ad6667bd4b10ccbcbeb584e843668f9bcbe4ea5a105d1e9d2f2825542b4bc30d1007144f7f07d64749b91b95af7ce2ba1fd8b46e9e2b00c615eafd66349c6f01a6b6ee194300ac3dae81eb37919a39549b4be8e0ec24d0dea21730f75f955a23a6af81db3d4870018b3fb2cda1f6b7d04b657680021a2f207530c68a7ed9080be467782dba1f1529cee0e2bddf06e69c6c5caf0f2d83d124b4ebaacbc6868b4eb62431b8b581d1ec62e44e1b05b33180c02c9c0cecc8557b51e514d79e7d7b5b27c99d9f2e5e31a1c93f0e2b3ca3e48ae6566137c6f6382f7ead872dc1a4323378b3de05b17933ed142a98bce9bc51c70fa4590faf935e9584efdf234053ea318e8dcaa86b5b6a951d28b5eab81d416fefa3150895b31c9689cc91b9a4052598e8412eb8709d810da0f8348ede2c29616f892f49b3c91328558df566fe29400b409ddf98e8f8c8f1c03b5623889a9cf8522774fef41999c5326d2a44afc39cd39cd49dd921743b42ea2b10d5f964e1794d419516225b4da35dde434b5dd6419a723d3b4f8e600bb066657cd244e7565c7fbd16829eaa606b3c70ef0aedbdf40d8e5ebecb4b419eb9e3e4243fbccc300999b968c05a467194b1ef119b441d58df005fb2afaa58a6ed1d9cd2cd805bb37f36c2816a9a78e5c1947edcffef54ec74d5c607265bd2cb9b3dfb4a1340433e1ceed83f0666fb79eba4f2303e38337c91dd9d1fdaf5bc109afe9d7910526c8888d221fdb4df389826707acd1004a023659884b8d0d26e2cf7e6314024def33a1d98d481b1ff6bd95f74520d2753d3fa38bca859c1439fbbbc1052fd0c9fa434c8d34d8563d74c9fb55b4abf7309d025f80c0477683a13c84c0320373a3a1d9f05200e6d766e20a112f96daa9ff532e14886c28a46f5a541ce843408ada30b2b8525a8925b7e2da4f8804f21283b32e629046bd62ed897f2538551ea432b1fe36816e3244d39bbfbc30de68fa5414e50eee67f8880301b6823ca1ffd4b935db32b1ead68ba82c5694ba76a043e3f71c7d23224bb365621583ace94556261665c278111c7aaf6a622d25ad78b26036d5811acca747d07a4b1dfcff28e1718a90731ba33b48ab13e083754e73f202a3dbf1f3405422b529a3473f55fa4d4f1fd3148dad05a8589082467a21864981f09c6cfee8e913405cfdec37c582868ec2d53f8bc044d6146b1be7a32ab99a86ff74a40e69c5e734c8c1942651b11d5b91be159df8c012de676dba8b82722fc85a4fef1129fcbb607b09b65e50de4be033529fabca1f9b6074abbfa826cf67731afbcc3ffdf034a48b0c044206d8b47687a067898b3132cc477051fdde5037ca201710e8735c93eabbef110a2a90d1a54cbebe248b65a38a2b2f9b013d2db8008dcd10c2c8d24f4b9f9f05fe7fc7693130dd947af936136b527d19d07e1132801372ec40be8d5c0fe76b6e20eb28e518da281f2e55055295b840874008f289b79fc37293afc4a29112746c5911f1d73aaa15d93aaf396bf5b585fe5da0a8a4e273f42ef92754a7fffd3497829d0708d72996c12f00a69c626d740cdceb375082d7f0a773051999a89e7801f9c011ebeab6e9abcc2129fac551604492a992edf4a7e87b2cee5de25fe773a0a0054b97e58cd3198d58e7857b979642d4cd3b5121b8fe82c21e0d90edab167931b8320660358f945500b521c356d57b769b503eb4d27c24540429a06df79adb88f72ab10561849181925e0d545a444d88a34d2320c0a568aa561671fc5c0d9fdca2445472ef00150b0015a2896349081c264e4d5b82732af13addf0e9de485951b6f08dec0f5847aaec2fb4c11aa9df3ad2964da2238c762303d9c991debe1311949b915dced2300a6068b73cbc31b028e71396938a8037fa1b664fac90650dc75b65ee8b644cf9d32f942fa6961ce84498d83bb5790ed37a0c906949c77c05347804e2d25afea00821e7a58aa496d944b60bbee71be3c3144c0c9962ec3e2ed52cffb9120f9e0e3be4a2d418dbe79fe8edcb67ad9c41c481e8adf6a1fabb89123ca268a280cb5bb68a4a92f12507e2f0ea0718216ddfd7be8972610bfe52f18e69d85736037fb1da3c34c070fe375626f7b61eb6e026da6770f5ead932b81d3a7284a53081e7d99a795317bc86f41e5820305fde09f84bf098101186d38bb36ac608b33d86efe3746027b456c0ad91bd61711b87019932ab2818b33440639fb5e254ed3c7bc8aaa6cb9691fb3f3d943df6d3df6b08f5c6d106fc56cdd642801cc97bb0a5b03229f5051fa9ef5356dbe78d29cd28c9880702e6038a62db8b79f1057e520f1832f33e043b92f5d2ac997676c19766098ade7b8004c9091288a505e3f58e3b6361bd9c480614356c9cf88487f24b3e1f10fcb4f61f42e05cc26ddc1a2e58afeb34f28cf92d90aa96855ea892ded607a2e9e1cd77b5be45f127d5223e633a542a8cefd9892c8442a42d158c4a100fa59fa399c58da1356f4b5a58d268f2ef13a1ac60ba65e19fe9c88b22d46d2c6f11a18eb7b863a1a04cb352a60000f9b60f776e76f6c4b4332fd428840a01457c7df5b2695c946003dc5293d257b884d08643c0208f976e22722b0aad97a20a0f3487fa135306e3c0010320e8543d64986ecc6839531e22b59ac3558760f3e942cf14f647094a11404ba14d9765a7063ce4965ce16cd48a11b8727041f20adc4c355a8e5bcd2f79e51fe993da9c131ab5495428344957c8f98fae44ad2ad39c06b9721efe2428eb9953112cf667a3e471ec424e74ac962607ff964708c9839f2e10c4a2e435954eb42e54ff98c52c9342171cced218419ae2b09d85445d7f14237b70dd832026bf50b2f7b61a9c3ee53dc5512f7d4d67d7c981b377a9bc0df8717a98fa5855823fd9744747f96e024ebebf69308d70f4254109568b6dd715415fa9c82d0f4d637306c3df8cf0adc196787a4ded0748e569c7cfbf6a006741247c55098b355480ef8256ece0d8975d18f627090b5fcb3d941b40bc2c11f0e8872ef10c62af18b26d10783e0e2bb2c68905ed3a79b5061be2d36b6a1fe2b76a6bd1c3174bca62935f0210c7ec0e0637407ab630e8ce1ecba9e6e4c79511edb9875db797abe682b04c8fae72e172650e9bacff41a84f1e7dbd2ca4c71d28c0e992de482ae5184044810b6f8bedca187194a0e882a00d8542a13f87894aaa7a07177b9fca24e4c4a6bcc049a11bb3db7634e55c1a36fcedaae8a5784e239053a767a58594adb63e44b3f754c84a0708dda1c02305491424fd34b2a7007793d3f416e55c1876fce5aa2d70288dc102b257b542e8e44e637d84d8a983d7d7aa62054c0eb8231bdaf0a69b4bcb8a1b97e57211623565d0715786f5d4237858543742e83988a88425eeed139f307f35171a22072c04173b19d8c769875e61069e1078e1e929f44f073062863a352cacff1a12dba774771d8a51c370b3f8f1f4a8e38543c9e68738b49c7cf164d5fd8f5d760001e33e0a82cf4973bd2c6e030650bb1f021d8a950f6dc99c7dfa20a1f1919e379340fcb123ebfb0432f17429f08136d9c6eb83b6568f32eb9628f640c5ec108d117ebe2a4345e4cf32ea69484c60fce4811d2d6f5a43036c4ad131f7798390c66f672507a7c84fc768234190f84bab064e65d4424907dc910072161b13b1d1ecd1e0c221549f5136a8af44590deaf9315dc890383844c2cfa7a49f18764f1d5388ef3c608212e3a5d16d8a47914ad5982208da9f21b504ef6eff4c79c967a1f0a6b0408b3d11cb96d448751bd0a0facff996abb849d94398cbc03c6109e30ff93ebfe2148c202e79e4978c4e83d53021fc8a3fd64b4a219d9ee4ff7db0b82d9438542e4f5553011619b6f7b11ec32dc4b5290d802668e5726440fb23257afda6c7cef30188cd5a63da1c8d918550ce8a7c376e21fdae4c630568d89d82826e1553a22381c26de11d80f9e7a82c392472087770f28fbe35b41bb1bd45f6cd10d474736da20a4caa3e2c7029f8d3830537961e2a5803ad81118e248574036a4830eebad2ac1ae0cb3af46832206e7d1fe31ba59164e7098c31574d781f01750d829f06e983b820b177c71faa2e81f6eba323c60bdfccc7b1d34914c82479f4a1f98a021c4b10947f8c532ff2a16cddc7e630b35717e0440d31a40f0101bbc94965992c41cecedbdb43f221858d2d3ed408a0658365e660586a8e57fc128654629eb1ea367d50f5b0216a70c090138a8b39d8b15435665102c362c4962416851cfc61dde46834ff9b700ebd60eb64cc53909dbf23394767385d97b1bd397c0e32068255c6d2985099a591c60e4d6c63784cf013bda91f99f409e3fb79ae297b120c87ffde6dcc68ae790f4afba4ddb750bffb681bbc31dbe2528f35ee6fcccfb9c049d194d31abebfd213d9a8bc26910693637c73e387adec576302e9b72c9ce419809b3628280f477aaa622444b761c98785e055ab25ab010a49a8653b2649040329e74eaa68f6c992bd624a461057e3c3b3a184b8cfedb7a4c42f1a80bb26c1501bfd23eb88b22bb6defe38511b9a5fbde3e25c8db456078c1cf10c09ca6d6c0b5b54ef0f769c1f599259f4adcc36326e81188774a4768bf70adb1007626847b815b6d501162df16dcb3eb89b22ba412434da71ecedac2720a7e4e55955e0af153a0414d61dfc6caddde723834bec650ee4684417cf30cd8d201ce8646ac2d3dc13695843af98d9a5ebca791c4842bb3f7ac6fc11a8eaf794b944d30abe7fc7c15a9f9a591c7876ed9b51e36b5cc11046ee526184a6d407f09fb8736f0ca81f37e9552e50f09e6fec925a6d6104b588b75b788f91c6a45eae3ce0f10d332dd637a2c728e1311b225882b0f0fdb5e149badb2b1509915d2e57870af96d5e877c622bcb24969307858d20e8bee731b877bfb56f89b4046bda5ea28f7e7665aabdfa53dfe060840554a28eb7a0396a841db28799b10e05d51a6e752dc320ae4c65711b6253c42934dc9fee81e29fe0592c42ea3d41a7442be19d06e5cc7cf43f79ad32bdfa27ee2402c2ab924695784396e14da949b53380372db1d5fbb499119b5196d1184a8cdc6561f84012d5a206e3acdfd80e6b17ca745c42a496b44bc7f5ba38bd0a8e7f0b0a77147f9710606fc627b97d79aab8db10332370d92ebc1a8c41fdb71fc92a5e1dfd20a45cc13a2331d995b59b8be220c51048e98f3ed3ff1b92ff9b54994f39aa2e2027d0b39579467e1028994b18384746603c324437c30e9add73fad9dace95bd87e6880b443a1a932e024c61cb2d612ae2b872f5f024dc3a8b0f4f2827a1ff3c965e9036515eaf9deb6161514965352cdc3e1f5c1e7c3c1e40f3ed5c75b7ba5db6af45c76c83f20fc7b3c0d3583be1d518c327e3ce4942310e42cc27b05b9436203ac286af2fd0fa813b47a5c00a07416905b74012ffc613cc63e514c793989e335b81bb278ed04ec789e06841252356344a410b47d9ba34720ac9148d1ea5d8699abcc888691a38646a5268f514d3a4694f447aed25980601299859f9dd76914044c75b379a8684013d2280b624d1c39139c7e275b3cec523eb3403673c5534e93277d339b4ce44e9c6a1ec20ce317f8e6995b41275d2a19b3cd1d008106f454f7f12f38ef3a3d324534af3a6b5087c718c7468b705f01c65d1b401440a3a142246ebcaff8e7d75ac919e16646404432315cc8ea28efb75902cd0a85cc866d1984c4632b454420cc1d0ba5c48b06864042bc717e328fba3a9275c38161d4470fc3b74b9c3ec221ab8c5906934a748cdf1e7b845475962b4b7c8140e223f9a7ad2ed58742cdef18743c1415c305b37f17a46bdc22604c5100cad4ac808964656b21d9be090353ebd158900d1aa4f46fe695df1dbe1eed0c1818a0e923c36b6932401d35aaf9cda855f6862d1557a6e976f50aeedc6aa4de5c629495a4711b134c9956efd69bd04fe77f8e845639a344836c39b375371587271bf9d32d1f1dc003a9a42a6a75d7bd28eb92729bb4e03f84242a3515c214ba3319c8cd0d3084ecad2681d574ea3d12a56184b4b6b1819a9d3c2947a87381caf8e9d5aa9603a8872f83b143a9098a3ac9f26eb85449706aa827e174d55381d8b1c4570fc712c7200a28e63fdb4d5c2e5507410a93de5a57010f9d358554ce3a4a98a8ce4d2544a7639ca2e696408768d04c1761473fce458ce01441c65ffd04055483834b5043939882c69442ae963d0aa4c46b034d292ed28eef883a3143a8a14d19c084c7794f5d358858c70696a858b63310791da83e8740e227f34b5a4d3a1d451d491bf0e92455a6982c14124876f0e4547311cbe392ee228e2f03d94d0c58f33aeaada2c2572872b8e7d0751c7fc3ad2879c261589e8d19064cf91cf11741c23fe68aad2e5d0e5da6c2533bebbe07c6ee2b1b4bbfbdbb5f7bdd2ec9f7a388d3045146ad9cdd8ae1409a6f165fc7494fda681968c30d13405193a487434f2041b4771c307d45301d4f5021a3cb6725c3016163707f92f3d9aa873c316657d130201d02e06fb1aa0153432ee58041143cadd1c530eb3bcb80c4324f3c1def0f426ec6b8401349c478ff7fed2514369763280bff97b708ae99364095ac06fecf63713dbdddddd5b4a99929401ef0624072207dfcff74af95e5d235f6ae9b71c7699e1d6e2538b4f9cc3a7efd57a76508a635e3edfcf0765565746512adfe16b06ec2cb822fd1639e90da6e08aa022de5a0ed4b9d3b5e7da412557ac352670bd74cd8894d132c73655add1c26f9ca967911669c76a0ddda9a1b268fbc026abb8961ed2aba3647b75547b751f3f2773771d8a7aed41339ea1b27952da52dd8669ead6b4ea5c65e62ceb81aa23b0f1d59b99cac99fe8e3ebf550d9db30a338f550b486a07ca91ecab6500073eb33606e9d07cd7aa8eae3310f45a5ccb46ddb384ddb5a062cd4a1a35db4ad7226133743ed34ad56afde3dd82b5729a595690db3cc43b194dce6b137c861b3bbb7f364c0dc76de0c5898b54bbb50d40bf2ab37758eb2cc3eeaa1a837b3cd6c37a0b6a7dbd728dfd263ba74d40c43da759937a37932f3b6af86dbd75014c1d3906f101c07e2643863d9db5570b190f3ccb5bab948779e5d4a29a5949a115a33e79c93c56666e6b29b9eeea618535a7faa15ed101b30d06d6ee65a877910c024e6d96398839d67dfa13ae77cdbe98a6da1b8d53cd79de750b43b4fc312b8be86ca626e6a47b876ae296bce39e7e440cdd33c1e0f666e55abba9bc79107a5f4f5e251e76aae7858170f0772270a25d2f0d4396ccfbe513595922f943553e339b44fd8783d3316036a823cf104bde69ca057135095f52032e489413d2ae04a97037962b1dc496948411f2ef5cd05eae1983a84c9e0a667f292f3b0e22a0ef23a077932b03c215d5c502c4fc8133be907f9893e5e2472d96e5fc5e43e4aaee233945cc5abab8439d8c014e6b0e22a9e83c94bee83012cde82bb39c9a7c8396731c232768e15918bfd9dcbce434e025728e2d69abfee39a986a4ea1c2aa3ef63a21fe452c6d42122a906f4fa9450ac4632e932dd044a59732062cd1028b2b49e5fcf4eaa31bd48aa39bd48aa6179426471b1f0b8be86cafaf6ca7a6b2b132c4f60265dac97405b1d03690aae6863091f4e9afdf4a8db788d986be2523fa35972f6608894e99d1248050202aa01ddf089b715c8cd07203266db419ef88663b6149f56b009b1a960fbb105b101b1fdb0d56c3ed4748e9aea1ea40c0f1bcdb68394c99c7d4bc1a6c3969232b5413536e2f68267df54ac8092b37357c5412a1e12a93869047704a6b7a1122a6165225daa12796226f2c42590a63a79ae4778f6cac32db0d754365324b08ec0da04ac40a821b07e215fd82f5885205f86205fc0a01e5875ea12f9c2ce81d50bb032912fd555b9902fec1a58bba84ae40b63be83edab809589ac604817c64259c1907767ca9a7251b0c65757e7d8b6bad5ad6e18e6e2969ac15ebac4c222a9adcecf9570b714fffc909f0e5a8d9893826397ed6c32b1a6b57398c9178fc115e987f37dfcf478b07471a6c32b5fe48b44a5303bf7a02108ca17f650a1cc31e76173cddb6ba140e698cf90615b98b54b7ba88b9a3d663843068d5ed4a1a3a23e19b219b25047a8a35dac9879a8d9638654621e8a938f7933a367661818cd0b1d883b3d94f61dce6c93bc7dd469686b68c38cb2bd0866944701f551193302e7df2ee57d714e14fd7629332969538fd6eed1d741cec337c30d41e10c61d62e1285b2df6ea3a05e906f35df26e6b5a767b6a6160389d6d09a97ceccccdc1e0aeb36d2ddddd44359dad3f3d229a5b47aa85af9a6d66a3d14b5d662a2fcf9b121f350fc1ce842689bb59973a1f5ecade5409d73be83dbebd10d79299efef4d2e371c34c8dcf1c55c3d6f82ca43b3f5dbed450a4cf1da280fae0b8a3494dead339b0c64fa073d4947461cfb27bdb9b55781966dc69459e69a8b15c0977b28b4fd3f90cac459ed8457aa78c31f1a904e6900b902eec59c3b4c89803c813dba714b4dfc37efb021420653e674f800b52e63a3b02a44cc8d965a48c4826c5d975489991b3e790329ab3c7b480c30052a600de3926d0305226c321b9288f2880e40f0295a88151a24607034a77a4790a3882fb9a8bc011985e0b95288559bbb4309d9b4f22fd5469610293fef494eda78bb89f1eba9f777b7e677ffa86bd4cf9327fdae62969c5bef62f81a414cec69001bb777a32cc60c36c467950cb0bf239648f3dfb6268e71b50dbb7a336d08cf2d2436ddb7728e5cb4401fb68e620fcc4c9d4c172380a277e7a6785102b82502b7ac870a4f0d37a0f7cba82091966d06abd6cb58cbc385fb688d0d90af262d652fdf456ea459ad597ad2ba2f0d3695ff1f3733ee17c87233bb365c58f2320d0913d7704e765eb488e12b61d8bc2b4d62a1146ad350bc30a6badcda8b5d65a2596a84d8860204908f934992b9851037aab1066941948a1011fb8480169e1600835303c4d78010d6ab6a8f9c1c84e81e2a00a2d7e40842e966002078e280210238c43b8a04815530880958212b3d60afaa9b56250a8cd6e93f0da806449a85438e970a4084da14485610c263b10828dcd1636395798618322577441860e70847c13ca908931610694524ae97ccdd7eb5f3ee29c76aeaa90baaa2b918517ab47e98bfa70f4a5d157465f187d59fa2a4037410308310091673fc0101cdd017038f1ec2db0625a8889699e31d55d1c4362579f4eee26d3bdccab6b5a3d9f5ee46d8221a0e011aaabeb46231a345c626581851dfc3dbb3a47e59dcaaf0ae5c7fb1a56d711eecc34ef7ddc89811727b9ce229e80022733b243e2e4b826b6839fbe0366d4f1bac1020e1724ce2a896b9df839126168dcc0d1bd9a06b289910a6155246e2079ec11ad2c7ebc24f2d32fa645102d62f0d36fa6c50b30239c23a940939c1e3172b4c88102c5c591a344cec84a8244122039370110a20b33ba5001105242c83278864082c5139000821217a4bc821086ce932865e88c9428c20732462267f06043868e6e44630b1b5c6eecb9ac9bc426c90f2ca060e1e3011ddc8be4068b28b0688245ce8b2706e5640f35c305f952a6a7a99132b6e2c038cbeefde19f13ff3491cea193faa2c689eac9cd7b3e2b0f0a47b3792d6f62b66c5a7a95c7c00d4a7d6d5068f85f2a876b79a1bd3aaabb51a31177b2a7a4e1e5d39db2ad7cc1bab17a6f96692653bb64b6ab3d6aedbeb77aa3e4b7378a7bb0cf4ce9ed28d998379379956e4c70f286b518c671188771e2c97ae55c07a5cfc1e54211f332e7429ec79c0b310f4592524a297f1ec37cce39e7640c736666ee7af318e6ddddb487d28a615eabc5302e742838d0a1f8c41bdefb3cfb2f146f78ced94381fcf3f67a80ecce63fe619c17f2cf636e873c26f6ce63a1689d8a39b8be06da14ec6f722aa79c2303a1b8e20de507ec38190ae7c8329aec874dd6415a88bcb14cd81b3ba48b6818481776cb848ce921e48987704cabf85444cfa065d0411a8894894113d1370d032973d3392cab5fd02e689b1ea25bd02c68959411b1ec4d0be9249ebd57ac8052e88248f5472956cbb08dfbbcae6459ac17852ee849a314ab65d8c67d5e270a5d106994623351e882c0f99fd7719b966125fb51dfc1157b9592ad4f7c2a14a7f961d3aaefb0f27813573cb11d3731eee5ae6b10054c268aa2991c8d327a517402343c50cc43518f42f3288a3dd4e59326b5c94dabc5b4af86340ace4ad69d34740d9fe80f3e39eb8a5df3b4eef0c9bac8aed79c73c280bd7a466666eadc337637a5d488aa568f475b51eef0e8067d0737d3665a7a645ffd86c75c0bc51b78ac70c49d2e61b89be9947ca12ebdf60c0b450e67485fbd6b6a4a5f4376f18986d3d9aab415679d03b91457a4dfd67677dd9235ab7df5a636fbea738ebc76ef855a3b6bdab49a4ced22dd5a6babad3e278779eddd16559d1deb2c66c119a53a078aa166dfdad43eeaf2a9046c0feddbe7f34e0b751ac2158eb89d9cdcb47a1d372d0f9d733ebd8ec3300cc35aaad7ccad67d24f98d739e79c3178ad16337777379d94d26aa4563b6b6a2c26ca9d1d27ee66a9b5996be20d6f7d736d0b25e9339f21919ef418f8836158575f2e8e29bd74962c5987734816aa4f90f4d63f147950a7939b568b65dac675de07ba2151ca88545231adb0883c6ad8225fa67b832d35a4215f660d156cc897e915875dd873329dfb8593c77991a1581ffe9921fd0ceba8c5ad29f932e30527d95957c224612191040808920079d94a82449227e22949122c82607183450d162946f242d2d3487690b828921189918a640812232c921b8c91e09c899c9a245072584996e42cf1353347899fce2e1ca668f994d10a82e9de972d24359b0684db83ab847bc5cf0e2f5c93b7b278f10404ab95a49842a502d61386f870030419467464608424d84001c27468a1663e3a744400881efc80f2ba228c263e584270818430c048c20721a6c00187a574d4e8a8c97474231a5cb444e1ba2ef6b275859020575c91029607e68d1b4772268c0eec080e8661544737a271a4096c3527ae914bb5233d2f9e80e0ba6c5b3f78b57e90f226d890e2624e70f0e0623946b836925cf1dae8e26659966519e64e3079ebc88f6f79d93a528323415e3c39114e8413415dca39419c658cea4159088aa7ca4ecc452b35a33b6a86d7aaf11918f25a9d2324f25acfa1170dbda8bb8ba197f6d27cf8442deb60bcc33a3f3dd3666754ab9be5ea6639accb3caccb3ceddb40dab781b8db85b8db853cd197d289847c444091a79328aff834dde7c707ca8fcfab8593d3c2d1718df3c666069937d366aad849f748273eaf0ce80045177e8a38e894a198c3d3c9dcb4d6e659b969ad1dad16eb2c966999a6354f0dcb346de3b86ee3bacefb401f08d43c415fe779331fe876206fe686442191a8798a543c548a69c54391585a4a2d2dcdb365668a3a09a67429dca89320c943cdbf7c22954acdb3947d2d3db41aae645fcb0d4f5da49fcdfe441a402f875dd8bd169fbc169f38a7157a89521c23d6d0cb27445b52a676959352ca76336e9f3294cabff8c43ee0d350fb297521af157ab50b169656daebd5bde40bd3118773b00e4dd34081e2e3f3a2f69aa2f69adaebf57a0a720c346457ab4aaf1652a4571daa6b4733a3eab0b56a8ddf5a9de3fb691776aea9294668663c0f7931201cfe9cf025c1ab212c233c726b1cc7711c552c232296882562895822968895d2495249aa7828954e9a56e8ca4af35cb1f339f9f4997835dd13dc181c10babd3c26cfde5ecdb373f7c4fcca8dc10179fd708a6b58c53763cbb5c3d3a312f18878443c221e51a963d1a4a0dd2003639ef1ecda1cb79c67bf9a149e1da4dde0d96706c6d872edf0f48ce3388ea34aa552a954356ca6c2a6b04c6024d95465aa67ffb0299e7dc5c2849609f3a76adeccd5109691711cc771fc161a356aa8a0820d1b8ec4b3cec4248f45c2569d9ed768f5335acdd468356b46af6cbc2d9398f21a8d46a3d1683492e1c84697f1208615619e869587d1aa73c0b4cb5c8dac9611d7d87a76ce46570ac17c7125b5831aaea4c29a3b5d5c493d937ec57581248a5c1fb0a47083241079c212472e16f78cbbc5145c38cff2b2e512824b87b2260fb7aa79a4af3a57a1105794ae508850082890410619cf893e785ac5e36bae405c0dd64ba7e1416054a385853f91042ab0ac98fa134b60c3a45222d14f3481d74fcc010cd94f3c41887d220a9e5b21ccbee9540a1ab385597a859aaa8a0cbf18b568ce53cab0f8f4f992322b3e9d47159fde3c52a6e453730da46ec116d2739df781ae8744998bd5c52ea2e11c83d52dd892c4772e5e277cfb2c5d27d0f09c5542382d6d1a82e5e98a88082bd75b9c05ac4e0314538a98422e722bac86675fa3ea507a1aa0c8ad549e657acae6922f3dda52f2c57b36274917b7c25a58564c208ff265ba0ad82af932bd04f6c8256e5c7cbb0814a728b9f869c4cf32449e426c16fc0ce3a773dc94a5d91a8a6075712b9794d15c03677bc6adeca8656e58d454bf785e45664aa4457878b6a855870e8347be4c1a2d200bb8b262d0d4aef22c59110e674d6d3568cf6728dd637131d02e6cd7e163c997ee06a58e70c551fbc825653c6f677979a0487391e5c5b2922f4d5abd932bb2bc56df6e632565ea16156419a5a44cf5267d07cae7401bcbeb598cf8f66ce4da52b408a97d7093d670f28648695016cf161e66e15ee191a9e9c555c5a58bae9f92f492445954b89ac0e182c4c9a1ac89b17efa8e17df6001870bcde3bac188ddf4a271c375e3a251350d88f048852cf1aa480c992b7b84ebc665e372d1b852b0c514aae03c8c30bea071b718e3c59375effa196dedbcecd20e425159d6722df944a794b5d65a25c3d029a2a8828cebe4856b722d74b4b0620b9d76aea7dbd0b7e4a9e120e4a9bd7590315cc3a786c2f219f2a4bd53ceaeb0c65720f5a6f6d49eda537b6a8f8a4f1d53438d0d81a9f8d4ded84dbbb467d9bd26936327914710544141aa8a6340447c48bc9577d3f57071f32d1a527b442bf9d2ce60e8850d211282a93846640476838d3698118c85ad6a7c0d535cf2a5dd95e0d5d0bbe97a807841bedd2342764f6c54de0f9beaa0743e3d9d23e4c32eed21282f9afaf3e3bb453635dfa220df2e5ad5376815865c8b4b41d08d7c6997adede6bd8babc2ecd1aac3e1447eaeccf5c122b7bdbd9953e5708620fdefd9e3614375b68125954abd0c71984eb21f02fa71d8d283722872e0a70c5c8437c1542adba4c9ebd5a4890d330a0f3b3b3bec1c37ed4aa9adb55a6badd5b922b3ceeb358e3a77c2803d754e9359758273b946bc5aa588c6d0545d1ed0f7d25d09bb38c6cba436e7a43a4be48d593b7943d21153cb371b99ec4b5a69adb66f578e47bbf522e0e36b775ca5146b505276610753177bf1046fc32bb7e7368f476b2da994a1926a5dc6a5f049664514e1f2ec40c709269a68028bc835e32ab95abc80467bd9d2020a2a6ac603217468b27259e1031da8b2d87942730412ad9f8c8a943671a628e3314c4737a29145cfabf09305e79abce2578f74aaaae4f1f19c057bae8f077bae9c614b0ff186fa844b4da64d2baddcb45a8c6258f3c47e9842f9c940b1d2d414add46295665413690b27a7a58122d571615846b5ad528ee34091de509b20f48603452a84ae38aea3de5729a8c7c9931e9f976a7cfded9a670fd1fc404d16dbe6d947ad225112a97992ba8fe2d9d9089f78131be7d9e9c827debc79985a8df340a25169a5051c85e889bf70f24408e78061224e7014303830392f9d57d3877364000ad3f00f9bd7c4e11c53a59a39aaa9a39a5fa89ca8a4cf916332973e6d38c71c679029848813bf01ce31b327d7c70445bea44f550638c6fa17ae63cab91267e572b970700e5eb5cbe69253755dd7759dbc2ede37f96ccae24217f479cd69a411671cc771a4b251a954aa141ed1c662b158a9140dd729ad5359a7b04ed94e3dbb65015481761918555dd7755d07933d0c0cd73c1ae8f3465a732918ce388e22af392d0b752c563bc162b158ac9700dca853a552a95423c7711cc76540cc2e8fd6aacc419fd77528c3dceb5acb469c711cb511c4a96c542a954ad54e9aa7799aa7799a67009d005cec1cc7711c354dd3344da552a954198f98d73507c2e82bd3469c711cc75165a352a9542a02bcccd7eb956559966563e7e21d47ec65ddeb9ad33e9bd16dc419c7711c613a02749e7357ab5518027a0ad9121fb02c6a2c1541d45ef20465d60f3b5ec06cf4e02e8101cd0b263f31e1451823b231e6f705634b3acb827254d4277e2e61fd740e33024896694b60b0ad405b52d343c3e05892aae9467764c3c985d131c20e377aa09a8e6e446389102fde182d104b6a7e662f5b4b523a2f9d9ed631c20e595dc174c344c34d261a7e63c70b0ed0616287def1821c1da073c40f3a2d50e9fce8c164f2934e143a5ee8e4bc0883a598e430f9e26588034f9700a0730750942851a274ddb5b469835d976592356d6af5ab36fe76d5e361c39c4214a23c0ed4e77b1020691e0fcc35deb6dee8577df36604f0d4ba54e13150b6873a3df50cccd8852de636ab216d791b709259104131433560190879d9f2c2e6fb65cb0b167cf6b2e5c50f4b565ed02c91c2929f9f4770e00a7a72109a7477f7e42080344c9a80344aa0fc005eb694e450e730b7ce529fc5ae50c4b5355411ac5e945e43653ddb39cde001813bdcf930cc9f2e25a92e5e5d08f9f9a24ef19297569ce423130d9f77dc077ace81a0e95d1736dfb94a17345cf87c7b66c3c58feff61c148a3580dc7a3174fe7dce7d0ee2ae7ba01872e90f5871ef03af4b4f02f357fcf3cf39a73078def9f7b9f560e01cd412f22e145b5033ca5f970f0ac596503833a3fc0d45b104aeef5c7e095c0ff2cf436eaf57ef85f92bee852b1471bbb0861ac38adf908615f7421a54bcf31d1c026ba82cf15b712e5471e92650a4d33b67af874a48f9540a455228f93472caa7110c2930a4f8e629a08882df40debf390ce2e75d06240c22c8bf94503cc16f1bc8bf81fc0b43b13a0efd9b48f9d43908877e5128f2ea3b58240ac5f91bc841a1c85510e42b147171e8dfbc3ac8331087f91b2af3cdc11d6e0b04e6837c3ec8bb9006e7b0a5c7f4ceb7e79444f12cbc6c29513d0d5c8fed419aef60c9b9e6dc375b5c3879cdb91b8a3efe865628e27e1efa3ed0f3ee827c7a10980e722d143907f9e79cf450d34381409e01af77e7b7f31d0d02f93b50ec1cfb1c34e3b95859ef39ff17d2c0b9c7859acbfb9895a91fc1ec41f2a132bb263debccbe8e7487d9366018866118ad9949a4a1d14a2bad540b4759c8c395df591524eb7a1e85a28f1fb1422e36111c6ad0e2620ba1e1620711dba67f884dc3ee2a2eb2f0d37f8506ece093837785b08d7418ca76e9169f38a693642a843542d1f4dc3a81603bd86018e99876259131ad449e8c80f65b898c29397b3391314190a592b7b44c6cb6cc12585fb6ab673783f4dbd5b3c1fe769540fe76714c099cdf4c2418d205290b20dee7610524c0059173f66bf1c71e0465bbb4849223917cc555544824155228f944726e762fb4644bb6644b7381ad03f6ce0a6857516ec9371f790afb0a287a8fe2f90a187a8f32fd73f0be47990ef21510f41ee58a726705fcdea35c92ab78f62aa178c3abb8f7265025947c2a852450ac6e7df608ec14506c27cf2e02a100c3f240b1aaeaf87a5eb984d4d0ac6fdaed418071e0440bbedbba546050137c4c211fb0c089e6f4ed387012c7f546908bb6946ddbb66ddbae839c030171e7dfeb3bf84b11f5180abf700b2f387f36112141bad93fbedb3cc3d67b3c40def83db60b7b168aa6be376c67a9bce63df6d8638f3df6383e772b2d04dd380fae685f2f29a3e1e0b3ea3b3ab32f10c8259adb8be71973eb1869fcec39da814f5c1034ebad7359e6159cb19ef9f785e27c9057d1c757130cf9eaa20994f81a721b484115dcf92017a94c103ead9e67b8ddc897ac8e59762fe6a28d17eb18eaae7767043ef39983eb33bf61b6fdf01e18322304f3c5b03c9ecdb363be8333d03acd444d7e73ba2365a66f2ea54ce6a4f1773065f2621d2512484d4a2c313c24d2859d8bf1d96bf2cc5e18cf3dcf2e66a1d70ae28adb8d1832a3c9b3d72ab154f819867e7e5c51e4124f2f6e224f05855daad3643fae8d29c8b790af44be86a26753d9b52ce41fb1595f3b161eae891cda892e818b6553592c4b10f2621702f0071d3ac01f59a6e3c60d30084a6f9ce00c209654f1aac20c8c091f43f8c0063c3754a48268a2034b1c21c6155b3c3105328cb05c5b40c146090eb688c1123e8a28026589288c40c215634c2365ece007149eb0f2a28733d8bbd9b476b518f55024ea925a2cd3aa7dd18a65da66535418a52923a66d5c377ad4f39aa7c7cab46ece39e59c3e7f4a3aa7b59ae65a8ae6c7b37735299b67ff547c629ae2133bcb8813511861e18c2c14e92bc79da847e444f444d423e211f9885e229ea9bab10972a312b2524d8ebbe9f1d0c6164e4e6ba4293a29ae9431e527c527054aca4fca8b2695f2e25c4ee66e4a2da7d1a981523e97cda9d279cff20be9cd339960c1cb6d9ec153bc741ea58cc648ca7899dd18f1d2e74a0c2e5e9a8aa810416588120ec909a424464cb0a63334157976eba20a119ebdb6ca10f3336d74b1b4919cd0a4249e9db31193e72929a5948622bb78582c168bc54aa552a954ea8644a2941109b3342ced34703e8d2ff27c89708700e17c4ef892a81caf42452e11ee10cf6e039ce6a8a42db616310d15d10000005316000028180e0984424112c3699c56f914800c6d86465e5a3a1609a3a12489911cc38c61c6006008010000c0c0504d150a38d14383f0befd95ff60abc8e50bc1bac6340d766cca119590def75f6de325e18c4580a34567fd7c54785d5d3aa7e1527d029a4ee4167941b01e50541f0fad1d4c386987830b94d088f18bbb4f703e7b514dceb4111016567434a082301b9ba0a92667188d5c9ac7324dede24941b660006097f2e697154c8f5bca6cda836cabf570a3e032cecd6ec3f40ebbe9d6568a16216f6cde9a506a3b82c6d4fb413fa3811543147d4c943922cffb2d9a58668693e18c13b82f50609cfea123dba25333ab059f3af50083d5788d4de12097f9c06018baf2f940228091d51a384caedecddb015405bac19cddb89a777d606ea692d43a1dde444cf2c4d525083fe9d05d9f9d2b51a6d598dc95d2014db5b03346b92768f5bc6ea10b62926293919d58e6ab7bffe31578dd7759a5d04f14bd10edd5615ffbc3b85a53f2bd0688db7a73be77e5ddba5fa39f0b677b6fa409745e9ad1662b86af6e1fc4c090f008e9a2c1b5e10f13ebcc77a1f125b4fdef3d625f715cd8dfd636813b108e5b5de2caeaeb2246b5f2590d8d6cc22be721f85714c37e1af69a1bae6099bc4e24e57ac11a09302ef64fb5f281835b1e3a8b72ce82ffcd618c53f3949c8578e0b90bf025c727fc55d67d0313716690abe49472897cb2bf9d6cd435be997d3cbd9bbca54011c15aeb11b3c0574c13b858d99ef1fb985dc998359bcb779f91ad1aa25adc5009dd4591f37cde48609471461529b9e9378ced8b183255bd58c2572f849fd64be067c11f4a0eb57f35aa746b9e5dcf068ccb705ed80458dfcb446f7fbeb08c8ec581a82623d27bd2e5df0c81a53c3326cc3ecfd848903e707fa0a861a0dc2d2bbddcc53b2e3d6ce33822679b9f9811a9a118a556741d9e7c222c83dceba8848dd674d1d29a704bda15449e277534139a2b53ab84f76ff830231f7727148c9392a0f2e78fd2e29bcdfbc6d85ab56eca96ef486c5926fc0cd66ed3323386cd6a54ae58b1a91b9fe326d4cbb9dc5163ade50910b8f9f9625a8b9d6d0cdd58a098856d12e5ff63c51c5b5b0848f30a3686bc7c10dae0ad3e78a1f19075f82b71e36fe245ba4469a3556ac6b6c7468dfe67126d51479b9cb575cf297c25f0d388f485e5529781442e89d8311436806fa8831974b47f8c7efd4083d05248ca44bc640f27814aa02d5cfdf89aaf9ac5015a907bb95b2a6d206d9f845071cfffbc4f52a40bfe1285783c7f814e516cbe494a41634338a94d4b96aa1c2447b72826a578fb77b04ac901150799243f474b2b5bcfb52d8a84f21d6b0f25f0421f668f0b52ea09bd6958ae37e68b5907ecd5a6292bd75254cfc48a6d3c6c945c5bc4839c83c85ab28265bba38699e736ba41c7da2f880af7251cb01b8ef7addacb7ba36a868e40763d2b910f732382d75a3318b75335066ef6615e6493c1c25b7fd12a8b140dad438a4f0132ae944d17585a547e93a1e279001f0ed4aa2815152f9d3180f711fdd20dcb8c774c01509538ca11a29672dfbc07e9d7ec25c1630a487c01740f79b4f2a93f4a422ca478c7f80f13b124bd83be25eeb48971457023005318ece2bc52d5eb7aeab82a0de07c024660afaa139c32f2a5fd5b276e85281ab15718997cd088c4dd548ec5ce71e361905244327c2018249e6f36c4f56ea021f06ef18cd43dd73815a0d7b9489528634a9360aa3e479446291abfbd1ecc06f30862a48463b945732eecff2769191ab1a41de60a90e082776d110c1c460e39b9af2031004e4c8722f80de95a23c9180d74a2d41ef61317327b3d8d53f9b4bcef5244a60fcb7442d1ccc6683c2ee97499c47c88be32353d2c9fd78a31ee0ac7a016e82b2481f6b705595c25fcb64438e7f9c1a54adca071f73c22b254e050870327808a8f0014304a99eb2012c7aaa36b427ae0d07c080e4cb7e0321ef36e215add2245104951145545832ce7314562b2d075fbafda465e8de3a0ef8f16c409f19a63278d01ade4400049035b42bea80b5248dc7e3feda948d098d95e2475eed497741024647091eaf4858be64ae1930a63d0ed35586928827fa9ef95678d5db046c0f80524a4c0473eeeaa051820a498d51863aaa159fa492d1e5213143f69b124be26ce0ef8eb00b4a594167665d47a1c02340622d7badbf82e3ef748a333ecf9ed0106320350567ca35e452f1ed710a46fa30ce0caefcc70f32b5d3caf5a97f9771cbbe901a3cf64d90b483128262bb397194adadc5f11030aff8a2c6d6b46cc3d33549ad61d234c2d866ebb70f74a1658caad56b3961e994102544cfb9c39a0b6402c52d91e7649c8cc5ce68b20cb5e0825959bd50e64260e73c9531589282da58cda10d104e80814298e8b9881134213813eb3dae52d74193ec61047fa9cc9def4472941e6561357317bf60e330d3f1a404a861988eb43b8fa6c008a6521911ac7c223a904139c343d2f54c2f2c73a4a689959dca9bbb00bad31e936d518872ec362b4ca3e354f148456ee1e6b60a76b485ccd2bc1519238d760c1a14a031a040c518abf47eb9c5180cc5d5ca09b48efa423bb5aab58496dae8573649059c641d4bdcfa542099735d3c42a14e8bce02bf3cbf8dc650c12401cd10d9169be685a49a2ecf6a9071ff1e58482b921e3221a0422adf59918c78ee96ed487011234636e8387d26055829978f196792f09927fabef8135c8019dc04404166e5b09ab972dcee4e867f160a251cae198aaff0d8e933ad917a2789313b26f54c71dfb28dd4de75ba58a903135f2504d7301c44fe697bf89888b4ddacbad93e9a12e0bdf4e2cfe6d535d47a60a14e410b2c3037c5365164a2997ae1bc836132bd714cf6471b2212e673b0ca0938a2b64075e47258b68c743442619e5d033dadda922f647085d9eb26822f536320e849b90f31a3c8facd22c7d3d1a0c93b7db697b83789756946e4b0d93c2442b464ae4e0d2b052eb94079c157d9c7add31e2e5a7022a41f593b42919dba0104bc4245953882bbb4d0ebb91ab3a93b444613f3fe318b46a7d87ada81dfbdc0e7c162cc60d45649cb6ec39cf829a927a174c724569c315db5854c91e4236530c2323c672526b1bc8c5671b8bf10ce00b12c9dd8195963b2658bb27c085e35a36734726ced274dbd208fcfc1b993c2f3ba99602aa4cc86c90d339f07ee722ccbc28086da3986f09022b7dfe1530bf596712f00702fe038007af600a33b49efb226f05fc47bc9a2aa47067495c95651b7ebab25450e544d09743c1c6a6c040df5da0f9a15883e500fc46dfb83e714701034197698f6f88013e36962f755a59d4b0da98bd0ab6d8a3658f51ca2b55678611938a2f3e67bde3ffde011b0e566279a30dc53bad80bec034357746646ebd9408387f9bd3a2366d549b429d644c2be28456491e1e91803480e5bb3dc898de1f70d5a867af4b5487ba34463a650604e0d1cbe68aba268ff0d620d29c645b870292d755120b3c866c1c664b8403cc4acece108304995ab851e4b5c02d6e4e1e9ec86dea8609ca8b1bc54c43e9374bea157383bcc51839093f877335f9624cc11349888c20b9002fd64bf47ff72735c6258695f262b00f457ab0bd10c3d692d954e563aff3349430630ff57416631c0dfed3312c88111fa7190b422c12d6203fe22cb82402a87afedeedd40ab2f0e83dd3da34d0c8031161d4e76e6a725158751515e903e1181c99770963c44f291147faf97e000a450b24d62087e08cacd1c43da862e46691bca439cb91f3d50c3bac1e3fb0c2a26aef7d921ddbfaa22b3350dbc35144430bb2fb690ba4a4e6fb046f3bd9633264bf9387294b26a4365b295aa6d8813039cfa33758af6ccdfcb7fc83ce893f3f845111751e3a037e5a1d3a8f80894e1b97026a487a3f0bb4f6e427bc2988f51881b073a2110da0f143f247de0d89ea0cec2876c26908aeb8dd8615c7d448df2bdb37e47083fc159fb4ca7409380197f844e13506ab82148b4861809a2d0128c1a8a1e84c8dd575fcddd2ee6c0940713a13666eb100ffa7bcafb37134539554336ffcaac63a201d1c9c83dec7b3b0f384eb40eb67840c661e845b98a065a5c06af836cb93931010c4b5358aea984a2234e819c34bf317f44304c27769f5ceb79469f856075f690db000018878816f91735d5e368be93b903de0c844ad71c9c18b78837d12e4920a341ab5db5ec99fb6168e719f902353b05b1576c889dec6a1ec269117b3c49fa602f426193f2ddea90e7ca6ae715cc1308be32587ef40b054a6979d33b50b815584d46c3b46c03eb533c4db041eca4ca78d254970ec804d02aa42aed9382091220d2c25230e6e2005395adc7374b9723ee776191534de11aa1323fd3aa0e509a6e2fb20e05dfe5c921b9155c39a8ff07ddea1c536be61abf2b233f6b06ec6da1f0781d7cd27a1aad18958ff83214f37164351ef5ae220bdbe6b874e85720ac567de144f016f4318635f416cb93fc62d7184de1c981a0f52e2ffb8128b978033c125e0500f71ce027674e1970dcbbfcff27633f31adcb08339615635922d59f31b11e83b74f8ecb1e4534a744e1cf67a79341e89eb9be14a2ce23923c14e16c4447b3b0d43400d7dacb9d82931f351acefc5b5cc8e4b03cd308a44ca82c847dc39334f557ceefc9a68f61615d8ebb5a88fc8f56ce95662eed53ad49e30b314b6bb091958ccc2a4fd77fd56197afd75ae8306e226b2e8f830d96d0f94b23a55f5db184dc50297bb15717197394da0224ec7bb53290df30e07688d1031d5b9f53e271f86466f9a78d16ace078876492569047d921da69db792609a2eac83b7c88927c5aba0fa83b801df66a2c4cd8c83fcea03466036d4e2541f19044df85e99bf54055edb6740059a6a77aa90e30d0e62eb244f7ebec02b771c44c1632460e910d52e3b20b2bc278ae8430900dfdc9208749b5bbe55928c05f7f1810d7ecd003aa25c64121a043271895183a6d96fb74126f036f880ebb1b86bc6eca017541b68961b5e492be424157da4d2658cfc2d0f700c32d2d1952503afeba85bc7613776a4a56690fd8e0e4da741030093db16e6f7c1af004ce35871a4e11d641e2f95e1a3b7679ccf6018582393f01aa204fc023325f0fc1300b6e16f9f138e1c54b2eaa7a5498b811090c8e6761555204ff4048f3e87873a457d39f164864022ab5354e6a6c2aeb1cf35d01631caaba868d25d58b6e5525d588dafcd1d6df4955e2c1dfb89e64f1285e8bc1fa5a38ffd13404741811b42f8fbb9f1fb6051517fcc7b2dd993ed34cec0888f205cf38f045552692a1ffbf93057c03d0a68cc93ec2d72ea27a1c8a29b11645b7aa6657395ee9c548be363bb65fc4d4a3123d5b6d9e26696df58a6fb560d06ab4e4e0af2a1989948723070214f49e32412384f36979c1480e79e60e75ed217fa5a09ccef6828d29c218cbdda902201a3f409cccae95625b840f4edc122349ff2cebbc98f37235c7627b246f62c1af990b67962c4c4ed25789a495793dbdc036150ff1c77b4a5880ea962096f3cf45e8f9251e06a99fb70d48cd40367b3613e1fe5a7d76e1386c9210ac84325da04d37b9ac568e351bbc1ed7333b34e20abc026de73baf4ec62a6c2d763d1d06e2487a8b30812b41eb7412dbc6fc4378bd42e239004c8bfb1fea480e914acd71129f553803cb7239a3944fa5e411ade1cb682d3d03a073e7d3566e6c39b3639bb974ec85d9a80d3e86c6010782616cd63b97f183c9d98047989a46cfbb1528245b0571aeee427c867c952fd4ba6855150740be2a91f09bd30fc5f459c9b0be234c4cc0dd697e35051126f7812f9fb5e580a77a17eda9057b22d377fed7eb52e9b256f241e20efe96dedc7b61e0c57bdbbee4780b1285edad8b344676ea46987a282b0451ce148c778ec0aa045b6e4f7813dee0acb1b0b7cc117b6975a76024e4c1d4648be2344d76ef66f16f79161846658de860e3dfb40766c2eb499ba0755113ba20b574a114f6b82325ab353f414a3c56879faf6deb96b92d1d65db6bff670faf60f18c61b09eef64310f4850a27145ec7bba034e167e1ff5968786425acd36f9955c9e3811480a37e77dc112ee402fc64807e3c905ebf1ea8520d76765d46e4baca982a18bf205aa3ab36eb533aa8c1a389afffcd8f3e4a503240cc868bf3e4c74d49d368ea12a1eff5a2b55f233e19b0e7699a22e180bd7f34e5e501af4228734aa486d234e538dde5f638214f75222ebc16230b23f41741d1d37e92fafc51dea1723e97089ca238cf49b71db3b7615651f62cb816de8aef64325d1460d5c57441b70c906f4b07293cf2a1bd4384582952ca471c7aec86082005e306fa0106b38dd26879c40237f365a3c099af2fcab59c03de7cee11161fb2992bc5d734092640e451f67811d223580644ac8ecda074d1c5d7534e73e62e322bef2b30a68123fe308387338eec737b1ace4711158995f65799fb744c031fe041522e8d5080eb4de0800905ce35c42a35ad31cbbb77361ee45512fd364b5ba1e9f904a9fb0810e1cfab035ec0d0e399d212e1da44f98e141e5e7a07ecbec8aec47b25f2c5de9e528bf68f5db93c4fb939d3f295aebfbabc2b112e12a6db12c05c6f452586aea2b4a23f4321e5f4b92a7bbe301994653355bd842a3660a87bf81a4e18a55be40efb625a85d6f1bd4ba6df3f1c355113a1945e692cd1e12b6c33b211751af4f603a5ba52a1aa2522d68cf1cc39cde1e0cbb3361ed0a6cdf9550acd0ff44f8c77747051364fbe65486ba9686f37871772fd5938af66c939178cb505db79fcb80952b8ed723173211a9403da6ad3e54168c900cfb182b7e78fb1de61d87ad751a8fb35ae845e7cc0b12ce66281f89b7572ae2f97ed58d231888660df226b977bfece28d0a2106e3ec33a4650f82c4195c1ec28a4a9dc02d03478c7d75786a4c3a35311aad17a3f0de01f3214402aa6f343dfd97a9249ca91e220437ef1a26e68a57243ca5884229608bbea4fee6bf4c1e4f13fdf56a7a25667b546ce3f2a5054c984c8c9a5890d2df351d3d321ea7dc268a22f91e2dcea7c881a4621c8370b63dc91f82d39f2dbba3e322a3be8426f6f2f1be03fc343e3c830df675522a8cf12c4062dc44a3c40bb80c12fef3e21b4f8bece7e8152bbf232b55e43046ec794a902b172228e4421565d7b636fdd60d9d638c902bb6a570ffbe7373e496108dbb678b2dcca7428dc2f65cb81d551190e2107779192d9927e6b59a33a893982ec541d7235a98dd352649b4b7010496bb1ec0c085304c9b6157280b0e1e1d4f52ae85708e7e2b44a9bd041a8412eed326425ef50e16a5b29345e2915b310a952b9807532326f6ccdbb23402eb34b16170a85849a53ca8161307364d13f3f26819d0b53896502f34e5bd1deab512d2b772c6d95aecef28682eeaa92f6bb8d6594ced3c844589264eee164d0fd9ba3ba211579380d200c1a4cf7ca5044fc8f0e47126e017529a3af6b95ee816b625d824e484f3f93862600152f3a492d526244401c341ce02095992dc1f1fdd4e229b37e2a058507391bb792b7e2689b28835c8e2d827b8ab9b52cdf952b2be728985a7e0034a29788408ac1b990b3745205a595f7bbab626f97dcc81aad83243a11c29fcf363de12227a25e9d9a6e774c4999c7c9205040f5e61dc9cf683314397d80ff41ce4b9c87d8da4b10a15308bdde2b0c5485b7f838233d357eb31f058a1f7099ded574649e028bd730e2245085ae89ae4081f57996061d4b815d4f5f70bac1c10e7bfc783cb38ac5f039857e4ac1df6708732800491b9617303498469f42de6254811589a8c0fd273e13c05e23faa252e68b98ff067ddc272eaf41137113fd2bd675512ddb11ef67df0e4cb28ccd218ee9191acbecd81a7164eb12012e3101f28b19010cd40cba6a8fcec166fb880b7a5dd5bca0107105ce48c0e75cfe51e6267b792da620522d6c7acce639264fc65cfc4360312e4c21368e0822608a2ad3061dfc698e831d7d5e9a4dc7518182ce60caeb748f10f4dec0937fee6ecc28212032c88ccf098fe6547325829598862343901976c5dcab1547375886d2a05567d8524177d9f6ea8712a3b011e6da994350321cab7d6b6270e02bcefcd9b95d0f73a5e1bf3478e04b038f80f5ede533dc049de997cba73e1abe4209646df0202e7d634e835e7b0feacc18d3f2f7ff6b1bc608a6fb9c11ea3c75e873a7bcfbc435aef0a96e22cc5262a5a11af46b3e37e3453f6a067f5fa6e234f22e82459a40b813a6cc36d7bbb7ee86aaf95c7cf4de9dc565cdc5337b7f57646469f6d30eeb892b238a1bd493f45b1782959038f3ce584432d113e0f03e32ff7e67c8380686300abb569963f6fdbc20cbc4b03b37b828dd5ed8ef3cdeaacafd9d4ba9a4bd161139879de6ec6439e5d11ae2c50d4856594a9d80107f5d5dcc5cd58d8d87f9a36b6b89442c471133160998ad179cbfedc5d963291f04c96671dd254d0b2e5144017b37d75ff8a89de1f3c69d350055f4d477e7881c3f035e99726937d5b8787b27dfff00ff8e5bd5c95f35d5954e01fdfe1c20669e8ae0d3b53ac9d99a756c0f239e54e48408c12c01910101680269323f80f71a87ea11933d54a6c51d0e1859010a1f4b9ae75723b3ed8870cba0b562631fddca89ca2646168cbdaca24018a8d66842cd115c1a609e752eaee75c51b1a739669a0fa0549902e9f8430ca701b4763ff68637bf4061eee3407a539276caea05719e890a1ceffe57adb7e2f3cee97bdcbe18c4efc4813985e82da6107688a0d40ff8282ea34807d2a03a5cbd9f458db77089945eb0f135612e11248d83e7da7cc4aab015a5f3bbb4d65216146f10bf7a6fc96d11dadb3f319d03091e68976d56d425b11129c21731d97ffc382fcc82ce5b8cd246e79f60f8dd0d80130a7b40a970ac4f4ec7b2c0a13075b996e0e37e33e1a7ed691f8c48bd76fcd8087b1c32a01e79070220c201114474d1bf1c932133e7ce3d10811c98c2d34493c5867d7224ea90f03f97c67e8a1817317e0d36da196a190e06a762f4cfb0ee30a28e94ca706105c47101648a3d0c1eeb69bf4e0878e671c0ade2d7ba1bf4978f1a53a2eacb1fff340aeaccd213e8f0a0b141576dfbfd02e8e1f8aeff534502ffac7b6599473119a0d29ae20a43416b109cd58100616f021edb43888f7eb05602a66e62c46a51a98e2e9aa25e213d4e40a6a01ed9a85ef8a26e432103f02feaf03fc240f0ffeab81d4219d2ba0a5ed866110f93c44c89ba1ede81ee797989bbdcde9c0b17f0e816caf86408f2fafed0d14481c8e0bc0d0055ff7954b4e7f85549d642b9c43eeaf14f769ff7d4ec4a017d1b6229b81e78c3c2668c9fefdf43971001306ec8e23c0e8825b510a1de23dc157936ae0b52111314e542d639be45a8f413f8a26f64cecdb62dbe211ffebcc0a2535f67485c89fb4b335940897e7050676276f62d88f7d3d29905200e5d54671a3ed964b9ea7d8ded6ef6065ca42ee5e679ca8208915b0c8d0ae2e8e64e5456f49c13adc1f2626155cc0bdc00f031802eab50eb158d7fa5c7c0631a53c74c5647723c97bf7da5695f8cdb246ccc0b32c4e1c871502e47d078d63b5548b09de64223b9b4ccc5b83cf9399a8133377be0f479f7c66f02fa30a71d24bf581e02027a903fefe1d4f83dc433b9701cf4c1fe011cafd389692224191c67f506ad17973a8f0b081504fb3e4e0c3f709b86aa03fca8817c6f0095dbd7c04665bdab6780045a87e1edc1b09b58b0ef966dbde0ba9224964a216ee70300a385ada8cebd04fc34c7af6eaf619db5d689860f075a652b2860e023d49c8012ef0f300c0878489e9e866ef2951a866222932920305b9341f01e2098b8d54d7b93240a3958d0b0cf08eb2392c6fcba068315bcd4024cdf5f1fa514f4486ece3663120c2b7772aafebb3fff9ad6f7fb5a80792653199da0eb05545c0644a44f5bd8b3127f5f2df6532fe5759fe3b9103f8df83acd5e8246a2366af6414811af53f31ec258f5481f3c13785acc5cc66b8b83eb15bf7ca06df1be7699974ec7fa0eb8f59178205fdb72fae3c7457a5af18e7e807db1e58f8bad39380b046d80a157ab032d908eba45366761b6d836879f9a7232957015176c48510d78f1fd45d00f66367759fd26e71e8034a729b37326410f25071cf62e1c742e010fc14694cc75384af4a977042017705d822aefd6fd238436207c9c3260945b055e0b099e4bfe199939c613fcabd04d80738e14adf2978caa6c1e93fd50d9027f3e02687ede2f4c260f8ac8b7a8078a465186a67a49eb31323330a15e5595fe2e77d45f54a4cb4e1e8c145ae3b2d6c8a2dbfb4601293d0c6b8521f4f88ed86e880c9074527d5191ee366970574a844b942f044ceb692aab9f83c07c8b7d069fd0bd642c7cc1c4cc9813457d220c14f76e98a601d4bb1e523a112fc7e8f6324e4b7de0c9fe6786eb1d5caf03cc370ace868fc73294c790328b0ddedc94fe77e47d8b283bf42644f7ba112d79d9d67725ee9109904505647d40f7114a8afa44eb4ea6a425c2c271611890d3669e73486210a21e37147fa4c01131dff7d04a69626e1df56b0fda4e0fd9388c35860a0e014d42beb281da8c2632117b64cc7409fbc4bf929b3c5d1dd72aaa64d24b99b11478605c1117a7dc39c1519eba5bec10fcb1826966f080dddf34e55bba80020a2fdcdb85cf021bc318216cecc8aaefc21fc5fb2ee5c18f421cce159694a689056343081e3ee9d31828465a1a4ae45fb94c10105f7eb718326ce356ee22daba925c4e6907cce799c50f8023a858bf06229af7003f1134bd159905240079d66c322e53dab145b6e5d4d8b69b260d58df7a51b3a96e73ce9eb3b5225930f58915d55d839f4f2c63b0b6db9f1009990628be58a05b510975068784504cdb70d4a9601e1423f54535d1a040f17238bdb20d10e47b3d4a88b1c116dafd5976743099253362ce8cc922573e2f56a1d7c68bb7e3627687990b4409ed6674d44450b6956307d07a2ce9a97a9d67a14fbe98f78e589df169949ecf288843cbf5845f30577de47c9dad1f0dba81c4d37b04f2983deaaf6f7fc4ea3f57e11fe4cfc8d1e09a2cc819796bfa7465c037196ce23cbd4378b376c8911370616e54a6610a9c2c3c5d329987e329622d7f9102f9b4e1682bad410c3c5d9766ccccc0ed42649e8d504ea979b808029e9d33596731a39e989cb5d2dd536eac131933644e661bac7555a3795dfdcd6d3bda7bea05464f8076a101c0c833c2c8a535eaffcb42a6953cc2ca7fea271b4561e4fcb321b1964d90d421b659e25f56a7163a79637e232c6de5aac7c1c43cae8a33ce88c64691bd2c4d6a6ef4f7673b24601b10cc32f4a8c939d56dae5107005fa37bf087c2de0e6b1c3973ddad10d65ae1654f0c504bd6bbdb32a46d62a4625320787f1a3a00759c9216dbc393fd97440c3031ef60c8f4cc1e06357dcf6b943d0c0e15ffa2121306ecc0b6a61d618dbc3df2b4c87f23210f0d29f4e2961d9e109204905273a1d3693f0a482f58a3db6dd3501780eda0f95827e0a3e493507e720cafc9264c9ece8ba3320c38ef15bed9e2a7590d1a1f84c82e38bdd2efc0893b178e080c07295c0c9afb4a09292965921209d95548526c1eeebed1ca49afdd8f972c955209d9822426038d035d0e6b1c2a0cc22329e48af5bc085862f97734ef6937ed0632afdc50a101648078ac3f54994656a62925785befde115a27fcf70c7e40abae5c2c22f9f8f6fdeb8e4cea5773b7a0391efefa6ad717dc9ee7a9aedd896a37e052cd341d05fe6a305ae56c72287d9aa289020bc99c91585cef67c6a3d762bda2e2220facca448f04ff9c12ddac9d7d4d481c9bca55350c5f57326db4d5a423a81944ea3ea9866835c92e1bedb6db593877cf57010b1e49cc2fdd142f5b51c0c4443f1a770341e3b96defba177d5b8f61a3a0d05be8ae2f9932fdc269bbde7e69dd295b696465da6724efbf73fb5dac267579091d3644a84465a2ed15b39a0a070074f1a4c314d68ee9fc7627a46356db4bc814f3e5655d1dfdf4f3de5edc43acc13c2f489151d2b7ecac85e66353a96c8f021e9bdc565640a02c105faead9992382692d829f516290a2f5becb3c9d7ed700c9d93d95ba8415874f7d9b34601b08a46f9b7d06e63d3c03753dfe4ff555ae2fe13a670dc8ae2e8e8057da3a8c5cf18f90ee9ca90e873857abbd99d07a50c5600c04b44098a1e8cb70d44050783d3d30026cfd5540cfe4a5d36c03bf2193d00c43db3ccf93c3dbc01084e0f6e670c9a1e787e370ae305cfa07faea38a45682774c6b25b891f1557b527e48a2c1022a6ce4c151fbcdab517b0a717e4eed10fee8c648f133b9f96b1a9ad590c9f7b787b9739fb3bb8e911888ec80dcd42d85a0fc4b680334df67f041cb05200b8dd76631b92a24b23fe51eb18de906b82b3e2248a005d52f0da48326100672582bf6f42f1a25362900677bfb868f80ad16bf889cb612001537f634229d2e20ae6cf64bc134847e7dd794797d90f5de8bbc0ca6a6df539bb456f9ee97bea4d1cbaf056ff818a6894430cdd45b9c59bef51f567a140c204e5ec80ea47c849a23b69fe8ab53d60f90c4a8aabd5bd41981547ad6ff939df0e902f463e2317c721793a7dd7f5bf2e86e44bf417fdeb29a2066101f47fe343a3cc00a2a5239deacde67080abc260167e827be656f2b9a2ff66ed6c80c3060873880a68e8efee334f7afafe703ac55ebe16ab49e437b92ac941cd0798e4e21a588368c674b5ffdf4bbe5e50bcdc5c590bc447cfbfea40e9bf432b863f781d837abf39e5dfe3b0a7b612a88938345308f60469770e54d34d6e1d08e9c792db84dcc4570f5471b909a049f638fc030495d08730c05a3c383432c32128d07042598bc1f07d5ae1c85aa1cce1e2d86b493f92be41b423d9e2a611557202c8ebbe2383aac05180dcbd251056da509d1ac5892cd6231794549e3d9cec1050e6d907f216b0866cc9094b23d7f5e064ebfc37451f3c120c7f60548240f870601080e65008230d1b9a0ac9ad6e31931c194a262749da16ee28be21bc805b37afb8c21235cae9d1ef143da5de0fcb4f1ac7ecd19085be235241335113e348206f24e12ac8b40f61ae66dc3a2c22695c6a700e86d7f7519cf07350310169f5eaa64723f38fb09aef09f7209f780d61df385286f0cece4b84d170930e8630058a43d539466846d8d1bd243823ba220331de67f3ba3e7f79703e060af659ad18b66c89cbf4ec4676c348f13f981691b577dbd9d9bee69c1efff247dd170c50318a5da3d30e88e045d077d4501412307b569d149fc6a0f9096a89391a5b68ae7d1768db84dff43aa2b5ecd8cdfd64870ea8b72bd2649dfb4689d97881e2a1368f54beec059ef2558dc9e576f0d4e7f865723507a529c7531dd6e4784ca36094d6b1d5c3ee1c1a23039b2a2b4f4fc5ae0bee4d026dc910d414108e0c85323ae206b6a9b837723250fe68c0f262568b3ae75c46502c0550593194bf09b8732804813c0ab1d26ea13f0323f9663d3bcdb74b9bd9d15aaced63dbd51d0ecb4f7bfe3edeee932b3f1fc70095ebf43ffd6617f44750120c5464523422a09a6cc43f84d1831c993cc0f91ecaedfbe90144ad9905f60ab28c30a3450e8ffd256a6324d78d31fa2487b82b79f7975cefa72243659c7c6edfca064dfd505e5c23895d344746ee3c3937a921c6bd35bfc95a3e8ac7e0bb99de6451b8aafab48c5f7d341ec849599255a9df203fc53d1a61386e5bdb759ff8e39c40653bcc369e0846493c6c0fec20ec20891f6c727d6f4c4980229856b143c1dd3b705b8e65616003c00b794b29440450cea13a6c0b4b789465c57f1508968c1432504640ff2756477683121f6369b1df25aa385ca228e1ffc77f77ae8cb2c4f64114f829e98ec3a716b23c45d1eb9632e9b505d8e3e270a2a08b9279e7be458e678f1887ab1d884632b6e5b8c1d607216cb21d87d30d3792bac737ba76480de647cd387c6dfbac96b7556d9d805687f72e3db181adaef7bb113ff2b672bd111c1671afe0d9e1d0215dda9fae87ab4c52af0e61d2463688db7f618ff87095bb020d6eea92333f0a0b380449956132968b934d24a85309bec5f7fa9666a9e8bf96170cdc0c1713443315f71780e598d3a123135c91afc5926c43299e14234607e0b775a03b867214075d0872de663a70a761d67157d330630b88c574b9abd856919f91b410f2ac9753883e14417002dbbbe9be2efe04f0fec6eb8e9ecd4037bf04026c57e24aa17c261c4be0d3ee5ee0a11802cdcb3d93c931fe4440eb77fdb70131743a728c8a87f1e04fe0b9eceb9ed538f740db1b3f86ebb219ad499fac49bb4b68f3c92fa07857ca47d51ce074eccea1d52211f320950a2283cf2e3c6c99e350a8e8732a786b672ac783d82e2976679b2951fac2864e1c1fb93a7b6d858c6e7e5337c8bbaeda935bfe4cc2e60679bf960f5d591ab2029cdb4c6b034a5ea9dc2d32125d5b6efdc6075ca2f2d52d2f10571b3625794fbcc0852a5b3ab44082451c1a54f06ec5a008b0385e90e4a6ce1d3a8b2ec0cc93bc39d41e03417fc4ee85035ee5a8332aeb117cc860e57edb447b2a86ee8d29a8e16cbc10361b4a214fe8f6c64f2beb6845a84895bad9c0c1b65cbb8149570ce13b790f4f5633940f7702af7b268882af829bd6cef90a0e60f64f29a44f0ca84f6f4c718ec5fb99a3f42136e5597d05a5945d20c68e00374b34cd794554b58328d1a322ad6b1cb85edf493e0790e4b0ca80ae6f21c31b9939d46a6f5b32fb9ca7f20d1c1981c0051e179caafd7f1d1c502d433763e05013047e5ad7ff7a658987890db398d92971682079067cfa9e42b8d21618d06474f8c49493e71a9f8fb1cdabbdfcae27ad6f6673354bbbaef5075583694e8fdb2fe9f6a6c285310e7db95eada432c599c52494b5d25bec0c8aeb688693f9f827b1f7055730c26dc227d191ef0a855259ed21da6017de878a5e01740b493e9035476c0ed45db0e992aac4520f5a3dc635c81784cab68ba8439e40fc69f96b5053b3547be5073da189e05a472c1364b649bbed990ad154948a3f378db84f51c9f76626048c925a94dc91bd788135caf907958b1c78a54a4f4c39191f4367d388f82c3321bb156f3e902cea041fe62946438f2576235cb66a01f6702c23544383e012998dddf99696182d48ae919323fbed7ea5d2e34ebc0b946b4ff8704f195f48da30c0e1f6b88037ec65092ae94944f41a5ccac734b490733c37aba0c9776ed681962ccdb5e2c3ddad586c7a0f9e1187876404ecb5ea66916ae49680e968427a148a15cdef8be78e8491a3b2dbfedef58b16ab7ede5dc84c03fcfab92b75d7ebcd36365904bf89b461c98c180488ff631c15148733d86c0bb2ee32f9b9acff50c435685177c8f85c2e918b356c77a083ffcbb9b29b6313371b22942263a4fbeec193277f9da37024943e8680fd8f1e226ca849f5eb7df61ffc32d591db0e7072e24720d49673caa16539ea69b87de7088adf3bb55c7c4701b5536d802f9a69d30e00df27356dec6c4b6a3afae9c4d4d38517c2f5413e5a42fc49d4807a1332239536c444333b704495eb161feb05ee370583bfe55b5e0e8a547f65255a9ab68585f19f7e1fbb0e01b53a998d00641d5da6fde5a968267220c7ffb687d19e739a5d9e9aafeb1e0ee9faaebaeb796dd04380ca1b52c7c6967ec274bd55cf8f1198164bd74a936d94c6514afba3377bbfa8b3b6d77e97291ebf412e4de9bc1ca9b12196fdefad2faf67cca86089fa66755630f5d18822201cbaa31f3b5fa959cfdf0e62042c755d375aa9f549bd5c4a2110ea148d0476b77521fc2ef2f23e0a14d7cdf98ca8a6cb93ae2bc33d1e5eb7bf4ad06f437c815b6e5ba22b0700d0660fb6c5dca8a110f48eb7eba09ce456b72f1b8f6b480c711a87380439b6bbddbd093bf61e7687b56230bf8049e2bd1a97b421a1a12b06527ad73be33bb107a0f152c8e699f4504048f8dd67697466c2a6fc0041b5dc1a4ab99523389e39f5e3ec80ee2026b95a09490ccf9fb733f8c8344b1a15406551c305ceb0199ccb61096028ed7c85468c096761009cf3529cdc6fc057b747da9da74e170745358640cda81f081764c89035239bec19996466c892352b9bec4c19326664903523934c9023e8bfc9ad8431dd01dcb1dfbec661f06e9ce7b2d201351bb39800e4d516cb26d55495dcb1faf0588efb6ac70d6f2884ba086b5f7facd79c5e87493a59435c6f3c39d55f9e07e4ace95b13fe76c45c25519a5f0c1654427e1fc57c88a03468be2c4ee7904ceb4bdedcb14dc0b4421fbe7c5edfc135541d0318ea6f5629c9c7cd236b09312cd97c4cc3a3c64a2a8b26dce96fd796a83bfd0be7ca635f00133bf3bdd1e2cca24894000c5879f59cd2e29df3258ca7e48432c229dc580cbe44058209d57a42da4abe4ba9b3f76a61cb80e685b857b935fe337d192a93ba6093b5c229e768064493465613d803c0a7ead7673a96e6325b0c65e14a01699f3feb351d9aa1744bc720d1fd6985f19075df4b63b4482cbd20345a3340871501d8d2f779b2a37d3c40d4813f2a02285a04aafa7e1fe704964e94df12c893158f9b811329180ec27f6c16a6f431b6085bd60c3ed2f654ab3e0243569c3c86a57359510569f180588916f420258508f9ea56e958b000ce638f95dbe65f96956664ae6ee29ede53658c4b73a7fd269a28f774ee71ddf399cdb3bfa23b6a4d2fd27e24f177c3c0b786cbc5679741b746e1f526a6df0e75d8d932c92125369cd3aabf64e71c53211228193de74c54586c0e0a65ab46d8301fac05e0518263264ab834731359d1cd15449eb43c9eeebb7c250f30cc438ce0784566677aac814d997af7373a5ba9ad51988bd8e9c894f2688426e54cb2b6b9be432086146695c7d8763c25f9ea2a1ded06f0aedc3752307c342f19c39b40b8ce8cad1624a6d3ee7910b03d00983b4c750e43afca800def71e168d8e3572eaa49925898f53abf438251c65841321c82d6bab65ad9b0c67fdf6d6c60741679838a03b206120cd63350420a38d376474c17ba12a4f542267c768c303a66e2d0ee84a15b213c68f4fc97ef4cbe38457ae3c9c3146c18aea0670d5eb47c5e0b8e68803144a334702ce71ae1bbeabd02f5ea47387572c371ab32a5c800444d2b5058aa1e097fe06a167312bf90334c5d242b36ba800a8e143474a2195b84edfaefd5e22f19035903a1a6db9b5647ea08cc6540edebb1f6a15e9575cc3caa0cdc9e2e394421171821e905c5a2769bc623ad2f3020d3c9283803aa489ea6dbc10ef603b09b4c11d6831f0f88dd0a9038af80587db1ecbb2edaed02035f0f384cb7c2161f48dd6a6a7c87d6c95bd08cefe46e1d45b12aa735228c7b9b0a1397575c601e05abefcf82bd7b50ace91dd8111f31348e7fcc4605bb2edad35d30d1b99c16df469151665888c72e8a3c482d4889c09865ce8efba791e7fa5b1c4c648af2cbeb15eb3d547d4a41681eeb24ea9fe5300e9babc155943a545c7431fb32e2535d82028467a2b14938ec7856dbc5f81140517c0820672e1309b26972b11a791e2fe58b0e4e02b9836dad3cc33653ea6b785f40fad16a1d290a46c2dd6ac242acd704df6b58f3e124d1b1fb2f78ea1456f616bacbe0b501c2888891e0b25ad2aaf6d50f7580aa8fad2b000d087318556f1820cb233014bab9f56c4a67e3417e425937a0b3f4f8fdf3e11a27de067c58d734cfdf241bc2b277dc245b2c7afb805b39e06c24f345f3b58f9100a385244cb364cf2637380eb3c5cd4fb05c97972080a5539aac1f2b3fcbc6e211ca6df554f1aac8c5a742d7416ad1c6ed445b0f47374766875ad839734861b14bebab6baf4a05ca4199a32d0e65f7a097537058d95cc9bd8ecfa660aeb6ea636494136f3fc85c7f282ed3ff9e557836c67213224e7b0fe151ca3649ad59c855aec6e5e199c5508a283d6e049091ec88333a94c2b2455c350dd47ba8b794a10fe0c340f96bb33a28d894ddba2a23be27681cbb42b35eb46cf5b5f6edca1e1e792fd793af49abc89c0dd6c58e5946d782cb8ce489a21bb93a45a5c4d999f049b5923aebdaef24f030f6acae7afee23d33957a57de3bf9016ed95604fd8f468e04253d8186d4cd786c42492f0c65b0f15b53269553069f8c32369245182bb7cd8c4ed6aff3184dac1f22c00c17d9722a7b8361e7c143cce4c8c14369e59c151934131ca8327675cfc46ee11aea928c5eeff111a0e4316e00bf0fe13f2499166f2f7ae80e6500c45cd7de38b45d6df0466609134d01a740a8a26802f8b7812da4997c2b25fdf9434ddafb8c73ad579b820c05ee31d2476b03cd6c9b40128269970f6006c51559aec9db4f2b4af9cbe719bb31d0a9552f19d7ec94b246c01b668929d5dc868de23e68e68048bb997f8631c602a3b5cf3364cd5e614e1f7d55b47dce5a29b6cff1e198c25db245254e21d2dbbcff94d01be8158040308a131aff364404ada305213fd0444cf5692d0065a375ff7b8f2b7fd362ab041ecef582b1e236e957fc7be9c687d9c85b8d808f83a0e8c485ddcca5d984dcae50d04e7ecb66a06614a343222f16718a9e1208ca30eeba1c9333d9cc8b1777f029617025328f8f36bcdd692505507a1888ef8101bb2ead031dc034f8c0297dfa57bb2cdc6246954a37be032a9b04b6d08d9ac7e45599dae445f27d62caf1f4fbef100d0fc303b529271bb4e8eed0030d53c6e1240e72265a604f1101a757a0c65fd169b40277b4a49c613917be8beb239956ec3b5b0b6d2e0cb66a65e9efccb3d5e544b89d1b431b103338d60f63725d10afc36a6b83f2a0a04819d4ab64b215d21bc1fb00b315721732a9097565fb6d904798f160b92e143f46b44c0b3b3c02d6765b3ae90169db2611de880063f9903d7a403471794e1df7652bb715f7b3aef0b35cbcaf5877bf292c3f0d417cc36878191b30b977be25b596ef5f50a74d716dcc825a7530d40b591ff2748502a44dde7d0ae224a1f5f32713b7a5f3a0b338b97699f015f4f4dbcbfd94cbb00df0590f8d9a7ff106f783cfdf291f8710bbd90c03f6c41d0e1bb788df770aadb620f30969488257f4e39ffdb26c1545dfb26e2ee24a2f180b12a1b9f6643ad081e2bc1528035479d50f38ca487697c9aa61122c9d3611b9bddc184bd8446cd3ac314efd6ab26a6686b85c0c898bc5eb9cb1774470d7a539cf6fcdf16783d9c50a4b671735977fc4d6dd8e4ce099e926b0df905e595240e465c71aad33051f14e732795dd7ce32891024bdc12b5a071ef4e93ad913acf31630f87b97a82bb29de87372d149af3e5a05d72156201aa539f4faaa6a124a1ad84245e3ad90649434bdf61761142ed5c164bfd07d04136a6672e377fe70c97fe0d2d7f4120395a0691541fe0b9d7430ed48c1b36e974e7149fea2e3e780c44b74559b0f10a668afe297d6cbb54fac52d9cbb9a1999e3469180a97f748682896d5ea1c4c1921c6f21fef7f78e55ba18d1c1b64188747a6e18f18852f026848031c98900aaabf9911a14aff68277149d2d6c0cef4dd80f0bbfa8f6e1ed307e2d3370cc018c8d794d3e453884990039e276770bd1b3b4371dfe2c124a781d248d5a77373661c19e676c5d3ffde9dc51d2cd07b0817b87cd1ff669c05a23b75cad1a6e14db495e7a760cb210202ea78ee101a42f100b7344d14fa657931fac90db1c715b95f37e6a4588fb48d460029989d0b01c7a5e047ebee73a07003f23e12e1f2ae8308154741541f84960122e10062a8d1482d21d0aec6d85c4fbb8d1fb021a43127939dfa3fdf88e6a6e5457c860b8a5988d4870a6c97c7af3b0b49a99538280fa5af06735afe7549fb7fecc57eb9186a7cea7e909297a4d7566131dab33cad57735a539cb460010a81e2bd22b582335d2238f06b74e365ceac7ad105c4b3e67e8fdd4b60e690167a6ca041873fb89241cd07a0a34265b44301d3630ba1309857c62ed235772576639b2789cd3fb958bdb209206bfe958b372ca64c7d4abfa820b3b2bffb61268de7a092a1425b521921257eb7fa1e412f3556537f3cb9a476e7aa0134c2ca6d51ec0ed4d84b57d6bd2eec50ad4cba4f07eb5a3d4bbb51b15d5236e60a64c9db2a98e95c814a3ee6a3fff6d77ad7dfe49f1bad579cda55ccf1fb6d81749e6749eb8b2d09a49fa721cbddefe02abf317ddedcd64383e5b7ee84007b2cee690a060bf63470b15dbce371b96915cd6ba59588d8b8aeafb5b9eceb66dc6ada0d13ae3db84eddb885551d1b64e76e9734fa3efcb94d925aca7f5b41339022ab0087defa79b5ca86cfdc686f9f4c21ce1257f8707a31167874f6951fff65ece0b45722e160d0644520c23654a23056c0a385488df2d58e80ed9101bd4de8ef1b76789dc1934c14d9747a8dcd4d07682f007f52970505ce4be05ac9adaa58881e2f1ea07315167d3e7558824fa433bf94d4a891f13a58ea02cd9e3b526a6025a75362bbb2973188345f0172ca236f45e7eb6853d707cee19c56958a50a36377ecee19fb6039b1bec916def13bfff7ff0eb18ade0f6e13b5dfb36a4715cb1a737031071e79e764ff8267dfca87df8c0cfb984fef3f33a513b60193e92775618eee8d1a58d13c0da8a4d187acad7dce7d7e82bad5b05eacce96962c98a3610eee41340f2103f94931b375b30704c5093261776bc24bdb9317238f2ad40cad22b5e106fb08e83cf945d86541d22bb8a3bbb011f60804457fd9bd65881f2349564c95022378fa4d330364f6fa8f071433569fe754835da88f48381dc75e1393493ea97851f031854d378369c05cfcbdf50a9bc65a73c9d11c83166ad639a8cf2b0bae436b959ae54d12370e15930922d950a85eb8152a3bb0cdfe83a554850d481641cb001360f7cba9d71c888f29dcc284fc2c57dbea3fa2b75fbfa49627e217b7cc2d610a80bef4c3c846df52169d23d5784aa6841c65fde6f61fa2c3e073d0733c687ee041bbf68396771eab1011cd28dd919745b14297af429f56a19daad028aad0e65468482a742fb4b2be65018a222dae17b480be1743284023ed70df8ab8fa286254bb3adb7e188c7e22723e2b623f41bd2a75229b172d511cf1140d48f89fd40ea833935c7040241b6ff1ee500e51cffc9d7e2d8d5dd7095a875e404fcc31229d342dc88576fc461b869a55f82500868eebe1dad6c89ae0cc51351677fb9e460f8311695656e92603d177bbaeea0703f2d5bbc0df76032ccf54c09586085d8b3c7e0555cc17aa3f4a3cbf4330cfdf878eb72de0debb388ea66ae825e5587f49bfe488054839a9541ad2932ba6b9053517c93cd195ac277948cf3dfae91ac7412774076a95b28acb471895800b2d9b3b2ea47420afe12e54b8cdb5b05f7bc706286fdf6de2bbc3aa60a24eb821161fa6c30a78754d1cfb5e2379ef4edc975f2ae3885c73983e3749c6770b81a8b8ecb5e6cf4e87466abea3110e25bd78f604d9119643a85480dd8e507b5f5485d8e9cd04f411b0f6e3824876bff29551cd4b006537bbd76d0aff69df7471137144f69f14368b2c6aee80855a97773d232bf2b6c8149b95b97cab484730e1bd8b4a2603330d8551bafd55850764f3859d4c155b6b745fa2dbd3a30edaa4320731572bfc0eb2a41b4968bcebb0334307f8cd56476fb5b8c336109f2546bd2f681a7d65d492cca8ae0e807b6a6026ca3638b317a2a93b20d962c13f4a01a2b859c401e9d8ea219bed104ffb7e9ec60e26d4a8cce59c07d3c5b5f64398f05916e4f3d43c8d966a109991ea0157596c3f27fca4daba2dcf0e297afd0592868a076df5a2c622a79dfe5e11d35bd8d1cfbf4af23f8362cf50886b30f3e9e72c11056b5936880f36b87a65a240ffc47d56495d09f22f124953a92683c315bb76a7c194e5db46ae32dd5513a7de4215cf553c350211938a6805add48d033f9daf26916004a3e6c06de093a824e00e2910eb3d2defae8c79bd1680fa29d6a41426b5da8e71cfc59230aa44c762d0e66f8188f86631f076f00020441eb3a4f09f4274eb1404333be24dc08b5fa1df8478c5b517243f72e4b245710f40eac71d67019bff3d1050474c344bdbb85cb9bdca89086dd2208696fa69230aefbc9cd66b30ea06126c4c0555dac64972503044cd049774368bfe5b0b6c6da92940b01a5317a1d5c87075789e30db4f6dcf6ed6a2101f990db1e711cb57a86ae25a803838527e83c78bc7094d295fea68a5590a4f6392ad6db3984f2eab40c94a5a85b35adb99d3ec205b9cbf5cd1199a9d2d8ba04d49af942b353ca018d035496acf1e3866ad68477996a2ee4f152eec7d72dd1891c5ef97b7f9b6389443594b64f66a6c11d25bfab6538fc13ac757dcaf01bc262dc6bf118f4f930a3571056ca3f6d80841a7b670665abb915610d232da91cbcf1ddf5acc6f9f98648181d7eecdc45d231d8f1893b05fc536e2d4be4b73f17b3a5c6e9e4b59a7e77166f151ab7b177ebd977c179228f0d206e08510573ef40d198fe870a1b692d5d3895e22fdd989b530770a5d053fa58c15eb47432c75b7d44948fd96884afc0e9f85ca8aa831ef4a461a13e5f7290637691a134b4b63be963426a7347f7fbb2948ceb8f25cb5c13e01cc217d613e893a1833208d392472763e4930cf7a4e806c3dbfc0385672f53309404e1c5ba38d5f095d23cba8a3fe27195dc4bb3a5cea45c47363f3413766b9a2f40435ba086c5bd936ec1e2e242e89084967edfdd2fdd70ac06e464e29817b7e5a7a9d6d5e63457d2e99396f879284f6d33e5777f6095578057c2a480eff62a5ce970cfdca8fb4fafa16320fa0de0c5658fd4628097f97243209b97764c74f2e6f2bf17ed07980be5f15ff4b28ae179b02ab800988040065d8578c2fcc1625b7bb8db89c69ac0ba31d4b2f4bc0325bfe8424c3923b667599c9d91dc4f8b124a87b841e28177948ea10604b3a68d5ea308ca4b78d4072c469d013007256e17590202618164dd240fcb03b8753015f99daaa825709db96dc90b3ffbbc5ef746eedb6e4239403852834e18c366cc4866bfe49a70f1f136198aa86e1ef51937c8c445854d5c8a7f89851ad85c4666b8630a54633f7acbd8b4e4f8901fb6e8fc7c1ff7d9b94a708024e354d6d6aa9e0faf4f310cb3662345eb912d0cfea3e1968d4edfd493db284e62777866de3941e3c91eebcd3d278542c1e04f81694b41beeff64a5fc56ad8bcbaab2e051221c4a9ab4690a5dfa7e5d2b8f818889110e4a5cba3c28f536db4ccfb21c3535060f133529e59ee7961c37c8dcc93b6283bc84ba1e69b4bf2b19d1c916bd370163c3714140a09021576f33a8b1d402320dbe6a68ae9aade814c2324942bff350d2b6f1d808211999c4edb3ecc05ec7bcadc882da943460f2390352f7c452d6c7c3f59efbd83054ddc0fa477124433e2a233deca3d94f28dab1191ff2ea050d540f38fd75a6c207238dc6de410bd83f49765713470b93397c90e2f6f7cf1c70e1255457e623f26321818196fc2e9bd70cf09243785fde262b89ba39255dcadb4751c71a24397dd28006c194cdeaf78ae5cb809e39e9cd797e20b8c1c4adca8fcf60240baae7b9bf3b7a245b8b480e25846bd38fa4b9fd86d183960013c56c157b53cc7fd0193c0d64ba12fddbc3936958b0fd632da0f244691809535c91342c97765081e5f44735adfb2f0d91cfc5b03e0c4536ddd894a8a4e52b787c20bb193474a4bda25945b5199f09e0ae56252ff2b2044f3e1773c76c54708729db37b0aa9910b49403ba99aa865e40d2594d1bd50e14d6de8c786a75d077879232b74f69a3eb883a6c43e928b7af4433f628293dcaa567692fb6fd219ecdb46b965e84dd31ce09f99b672baf786d2f87bfcbea4f564404e4253d261964d48faff0685b6289482291dddddd3be808e007260849548ca82ca132eb714645680a1523930a112a3f3dcea8bc7a08f648a5864e4547d629b52948760a93294ae49d82e4c89415c8c0537e7a9c4d9972838c2953929692a69cb02521e99055f4ba020d5ad9f23b13240991448324243dce9288240d49c2414a2ca927090449ae297e905190442162822822b820a8516888a273a158c150a2c8010aadc71994222842f4dbe30cca0605490f2b141774f935da04a14129a0ec00a50523f43883f283c624024508c214020401294895a007c89011041218e320769083f00166022be1c1932f48a57e95229222ca0ea3ec893ddb40d8577e3871a50627929c30eaa4134b9cf8a19b5888027d30275c4ee8744d5c993df4bcdc462bbd39672576b8abec2680a061efd4374145499421c3084c311114e9064b28920c17105a868bd24090438f2137d06640c304424005a9890d60a0841037c8e106433ac63452e0325c3995fa55133c50b179d269efbd4ff6bdf7dedb832ddb4ed086b693cd831d839d6da513c9d55f7a9c21fd20c19ca854a038ecb0f648a9947cdbfa1bb07feab3f659a7e5bde5a5d34d736aadd586ec19ec2a15424b793842c738e25c7520a1dbffe1c302f7758d73ba98a8095dde94b0ead4bb0143ad74e076820e1a86d869dab75903e47164cc02c87a3528033442a9edc0ca5536b8d9e9ab6cd04ef9ecb16b30833f382db75c8dd0d7306420d5718c26842d5d56192935d18f7195c188b381321891ef0205f4bfd77f9902a38ddcfa06665ab369ed16d421834ea756573a33be72c340bfbaff9d370a1d638dfc41a60326684c20f5386382490f778f434cc8621c914a99e89931e102c29289566d116552bb19ea0a591ec20c04225d02a1a7871d105e3dce8030c512497b09a3258aa04e34e80405fb22153501d4a5013a580f3fa609570f5ff4b05bc2a887328a6882a88959134074e98a559d1a7a92a41431022b5274f084143b2c8125e1317b22c548862b6719240bd03a0ec10db19d204fc149ee072c127507937764ea64d166beae459ba97f58383aeb12b71467b4407dd48f6af90d28cee05c3e17c5191b8bcfae3823a71e0bc5197826e9f35ffed2e28c0bf31786849dc1ecd0e7c77c5dd289335abca4943361a0735bbef6ebeb9c3793cce72ec33d6d01da1810061946224239249450a4c07002c1020ca90d215d22ac4750615deaf3578030c848841c02c50482dab04437f9e41187a047d48613f4f9198829530645929a1cf2813edf1281440bd146eef0b5901b153abe08b957af1f230ec864cf979b508df12a146deabb7ae55e52053abe78da36fdf4e2a4b57edf8d1c6dea576be37ebcced58d1857e169153f7a36e25cade27f3bdad4ef6a80d1a63e186d620b3f6698c1b321a3cde6d5d838adb78ddb3aecb92083d6d4e7bcfabdd69df45cf85e77ddf46ad4d7df790cf8bed37fcf7936b87ee25a9f3e9ea34dccd1267b35b457b70bfbb5d775ef790cc8b486ca38b3697fbf5f7b36b8fe71dd75d2b3c1b27ff36c682e63eef42cf54f3cc440df3c7e9ffb67e20f65e7904f837a9b03c3adc750463f81728e8893818e2fc2965aa77f44eb7176d413801e674f8efaae0dd158bf135b3e0f8e68723c2c0f9beddd4360903de4acac4287ac22d25e2b0f2d8f3e552bccc78f79ecd3b3d1d2f74fb4a19228dae89f5d6bf98933ea46836e7b0559450fefab56c03ddc3ef74db542feed379e0373abc36b4bed02e1a1ecfabc46d9f7adf46adc1bdf820f65d3bf11a3112de59cd247941171203ff80c92063550b2653a4f625d3e019620b284901e674becf4d02e31c3d152060600441041380d3e734e2b440521d8a151b1410e1d2042050e86e8d1431074806be00259c45b6ad7ff2a7326d37981ea648a00320c109fbc60ce9ec0a0cfcf1daa7b327bc2f3e487cc88460d61eb19b11fd149ba0cb23e85749d540ad179a5d7af744736a9c51863bc035b8c31c6b3da8beb4ebd386f79e374e674e7715a77b5f36ad75530943f6a7dd30f1313e95a52eb8c4ce64f643289144d21b3483c7a517b516595547f876c87120d2459620114260a3dbd3e4d41eed4b72dd0c8e9e9139c3d132907661493727ec29c579f5fe5a4b4562b27a5b55a7babbd17e39c377cb7cb5ddd71b7bbde357ddefdbeeeeeddddd3a9bb2c2bea8a1574ca15e109b5252cc1a5ab2c523595a253a6f005431990fa055720f50ba268170c4bb8212f181aa0d77791a1d6e77dd92fbeb6ca70d4ab8cf5494f0802957fbf1428148b180305aa64865af94f4346050db0b4677830aaf5302a414d6144eb210a15734209195be96109730a1d4a103cb2c32ce7911d29a5f4a5fcb9d529e9037a9c3ed0f235a5943e67e94b90d26feb5ac6b9320991b41b60e4001da790af40923c574b8af430264cc5c89fa29daf801246932ab1a4ae78c22ad1c3112556a08410256e50624717a173a284ea71e6e48993254e64db090f2727296059816a7161917a8151c5b4909979903b01810b273c5e8c4003038740833bcc8aae345132328134ea99b2c9952744cc9200222711832d091db82472d05b8fb3247a3a971e674814c16be9f2b589a5c937db4808b5f438432205a71912236099210164c54604144c977276040d581c3124353ba2e7457e9d42832cf8c19582071ac824a8cdd87ebdb5de289a4c41005c9b28f55a6bad25ac62669328e67c98289a24f52322789934a462be89088e60a2044714618311112f54e023423c2982871f2730e2d1829cf35a1242568390223d38c9c1bc2e66a5b552258c7aadb55618bdd65a6b35328108688c7a68a8b5d65aab0e364b8f33a31e3d4cd148e20911b8e881871fa9015cc1ce0b0932a3c40d35c828b103a445117862943ea07ad203b344042fb214e58187058d14a26ca6e0e592fa544ce5af240929f4c90e8a881540bc5862c073d261672791430d5f123d414c45f0f1a08ca06b72839e85804bc1083624460002248084e00822047102825e4780536badb5d65aaba54e78f45a6badb5d67a040d7aadb5d65a6badd5d22386f45a6badb3d74a8fe8e9b5d67ac44eafb5d65a6f988d9274163dce9498620483e8051b88004d501044cb8260b00950db5fedca3682c98f262470d2e4d544e7eb7166446d1a71d4c36a4451971d358249974f819e250f37d8e13662e3602fd93f589a61efd42fa950c830f23c5791762a79f4f94114d766c4830ab7c6adf386d65abf059b37352ad6bdf5a2b56e5f7312ec1cf76d4f42c54fd7b3939e0ab6efa479f3f6376ddefcae326fc2166c1f2de56c24fc34a691b78fe6ca3e5fb2abd52a19be92aa685787c6d0315a63df86f9eaf4cd55b068636339ffa7633ae6e958a763ddeaa26e5f832d3419b5dc531ece705c54c9ba55d1ba7d5e3beab6054ab7bf85fb488543b7dc050af6d5e1ddb67ca409f5ebe55f33ede0b1763c6bdfbb1de7711ee7715e9733e7795ef71d77ed77ddf350765fc7e16b3f67eae9ec62f1b078583c2c1e8b73e8b89fd65a304788bfbe0c16db3933d7413bbe1bf79c3767ebd5ea992d221e98437f34e59cdfe34c5a7f9e5ef638da4d9db99ff9e96b6ecb3ca49ebc473e5d3dcff36ce53cafd6fc1e6ec15ab0eebdd77a3faed5abdec6187bbff16fafd6c7dec3d8efed17f3bee2968f34c1eb346ef9b47c6c0cfba0e2d549e871b684d497987469438fb32964d5d257cd8f7a6bad758e90fbcb69fd525a1ad6be6505c8ee3d8dc9e2d95befcd12f2b14bad35e63a68dfa10c3d877cd3fedf313b8cfd3ed3182bfe83d97ac5f72c1fb7ddd776f3f00239e115ead9be09e4d103abf456d05df390db388a6214ab334dfc04a5d8f3aee7610f7b1e7e96cb7bd68e2704638c593d9ee7b15ed8f3bc7047bdd9a31a6fb8eaeff16ffc271e6213f7ba4e63fc1f1829c777d4beb178505dee210424f4fe8ed9859024042553c4ba007a9c09e10aa5f4317d7ce9638a6f7eebd9c8e9f975c5383ec618e316911cf6638fb5ceaf2d2135def92e86ec7b23c7e4a1e4347f01b77c505d940a8b31461edfc6b7f46d7cdb319843470c11b7605b0b8631a6b11ebe6063dc4f90e3936e2fd0964fcba7e53337599f9002480a1dfbeab3d2df31bb94e2a8c3e87126851088f401f43893a2d675d455462a2395914a968b9579af608e1ea9b5ac570c6bdf3143c977cc90f5ca35f2472fe7cf5dfe537e16d073f5ef0b3d7eda17f31db5e38e873974dbecd31cf239ab9babf895f297695e976dcdba16ace7ae4d5debc53d8b95520a49697ab902e7dd50f9b4609c57c3e697014d6f55df027c61d1627fc5a99bf7de6cb7a702eddddfbbdddf72c773d8edb1c07dbebf636e330f01341b1263ce393bfb72e3b2dbb85dc9c354af96873396879bfd6dfb568ffdad4544866e658bd7adee5b3db625646bc17374af655a3eb2b57d0b3bf35646ff4c0bb6e2430bd4f2a11dc2e2e3cbdb2031868dd19a2af3966fddeb4e6adef299f98e3bf2fefb1d63ed5bfbad57ab47b6ec5ffbdb765bb02ec77dd56b8f85ae4f98940b2aff94fab37d4ef3506efb65edc7d8dff63ffb9ecd72e63c538133e04eb265f6c32d94dfb67abafdedb30c68fa16608f013fd0035b2f1ba331b8a3171ee6ce59701b73e12d1cc557d8d8bcb1cfa25b3e57b280163c999e3ba231f68bd6d4dfdc1177a45dbd7eb83a4d165095358c8b2139f0e9c3d3476f0579e2aafda2316c11ada99f3f1ee6cd5f45e14cdf42aefbe10b36c6bd09b4ae68d3b1d0f5ee6d118d6183c41a0934c5901a0eb45ead0d5d7ee5e10b2d1fd9eaeeebe7bef56af9b460db169aab2473551f6fa15e2f6ff948136c8b2c46be44f1a3b6148592287a7a8df68413a0de75d3b3643d57a95511aae2523aea55605d77f9ac17eaa7dfd75df71d8b97924f8e3d6f05ae4bef86e9e7575666b9583cacd7bcf16a60ee51e07e0c72af41939c3727a9f5875939bcb8426bfcdf87f5f71e0aafc8df7b2d2f3f7a2ca07838bbf72d3c9cfd7b1a538221ebd571e55a63293597fa75d4b4c37c723931c8d0f1672ef3b34296901e720fe6902f43c760487beedf67ee757ecdeae92137590867979ff573dfb35e9dfbcff8e5cffaf4e75441be0c25ca033557b53cf7f355908f59908f9fe59a37f3f3b376644b6b1cee9821ebd5a7feeddd303de639f07bff4dee27975df8736fcadccb1f7e38e48c39fc93f36a742c0c7eb5ef03bf25fce15793dcb3764caf9f03e57ecae25780df9b4c6064b9381b82f45b74f4e1c0d9704fa693f72769f2dec4e2c9fcf1873b5eb05a7ef8f4def6851f0efd7e35daf7f8bbef4dddb30023adf1f287bf0fed51bfbf3f178bc5234dc0df3d8bc57359afaff655f96afd7e35160fea674f16341da71034c488336809848649a7bdf716eda04d85c8bdf7feb065484e2821620fb1c32dc5a672454987b24fced25993a30db8233fa76bdb23edb3db3e41f0de3ff55aebed2e1798e9f75e970374bd6ddc4db73cb7f4b06e3dc60ace1e6548dde5edb276493b095dd33925e54cf2798f9e0dae4b9ea34dcd1d007aa6e9f945cf96832ebadeba09f22e44d79ad2193567a21f44c79e0d0eec39cfbbd99a313073eabc56925c494652ac484992d2841427528a9032244516a6a06c293524f5b022a5244d294d50294ea4143124452605a86698149e4d4a0d5d86e0de1be5870f6c51907c2007b14100e4d200c4d248e1d23b8ba2d3efbdf75e2b40288d14eebcf7de7b6fd29e4962a752bf8a5214058a28b41e9e401467e48f7f828c487fcad6f6d1931df8433f908cf3c3080c30ecd83b529dfe8edcb7ef342871b2b58fad9d3814f503c8e3b4c40180a78ec107a309f32fe822c28835f33b1362c49a89803e3fb7648bc11a6da4b51b4ec6c1383a771abc387aeb34687134d769b0e268dd6990e2e80e779c09a7ab5e973b0fa4389aeb3c70e2682de3d4d5544da859233baf864767f4aac903399c4e7bb4eb4f6f19e7aea6fae0a6a0ee1f8737961ef3cc29f84c5ea7b90da43ef86146a6458c0ae625c5c2a505b582e5b43f93d7696ecbf8daeae307990e6aa0845625d290e4e3a01260983302783f7afa694c4a3f7f33a7cf4f8338e6aab63057d5a5fe09fc9f6853f1bf00555740d512a8aa3257f54150559bab3af92f459b0aead09a7a7fb057fc2fc5193a3446cdaffebe7cc455fdaf7d8c3757c7fb50bf32adf162b4f1fefa8837351defabf79bfb0743da67b8aa567f64c0d006690186f627060c77877901439a22e166e122ab85f5480b187e4781e11f093b6ec9048a1c4908a86ae08faa06fec464769a2ebd747a3060ad9ac519dde9639c9f7491f53a67baf71b0c53bb7b2f41d550af4f5da6ebbaae0bbb4077e7be6ecf15727f2e91ae9b2b5ec48eb426a489f5fa3440b4a6823f3664fa0844436c186ee80f55b51e63f5351fee148efc1ab09b35f2678dfc25f66980666652a9bdef5ff06776fc7a4beeed50d1e345ec9a5fa9903f9cc07b3f02519cd17dadf9150f77804073553df087f36087aa5aafafaae9bc74075e5515d592ea4acc8f98989d793379ed40098302f28797672ca34deef3017355412240454024a0109864de5c8e63ae6a77230cb287e08f0ddc2707f90d42f0a7d7bfde9493ce6abaaf6394b29aaeec7a0b0f40a221ba392f268c21479de2cef27148a7594014d87142b486be2ccf3651e48a3aa5363ccdd5a9b93aaf57e37b5dc193f424a531e3deff81614e8def86f719af781c288e8385675a633fd29a0986d6c575d893034b5d9e3e7a529e76acefe736b8fd079a62d10456cdd5293557a79c1c20ea33666373259fb360cb58cac510abb9e5a867c36eaea36f764a96042176b1d64781f3202af913780f0c40a99ca8540915eb7c3ad6b3431deb33d445daa58bb4913e9a3748f36689c68893871a499f75494a8925129944b2746cdec4cf3f5f0bcd9b24f3c67baee8e702c52e12e9fdf73938d2e3dec7f0bde461c6713af1a83db07e06ef4b3cb90f762c0b6dd98ca8cfcc8161ceebeaddd061fff612f43e7f27593c9403b77c44d14fbf407d9640f7b78f8a2c91b5ae78b0c3b88406b5564a4a39d1ae3cec3811bbc6ad7fd8f5a9ea6e3cd21afb3938d606bb3e9d7c073b8c4bfec50e76b84a0004680b3822308179b3929102015038217e8d3dd65c5a75633e9d1f5fc52f3039b7d162faf01d1d591f6ab5eb43ad867df0c187ec03de7cc83e703ee815bc39a5b4c27be03df01e780fbc079e8427e1497811dbfebd3fed2ce25226a238a33ee57e3a0db99f1545d186fe37b1e587f603ea74166de8e4d1768a8acd39712061b80fb67dd118f6f582c17e7e2c101013d0077b052dceb0ff090971ae2ebf5a970451322a4506489020e146a4c4c0ebdef4ddefd71da87f6bd37717909b1bd9bfbbd7deeb0eec4cdd36fda9dbff7dd7bde9e321e74676c863fffef434e8a933bd3675de773c87f7dadb7bc7801b897123dc888bec5a400419115e3f308c69300bc3f76da4c188603c82828282828282828282828282828282828282828282828282828282828282828282828288fcf0233d57945c413204da89e5b96f493d8b97d7e027bf2beaddbbcc504310da459d5a41bdfce7c15ec0d04bfd4bea85a6f2685ec8011a8de6a55e60a9df3c47ea5f780e97ef5eb0b0540bacbb7c7c162b5c5a58502b56b0b0b0b0b09c4ea7133f42049d2b4c860004a3c1ba37993ecc1d0683f1180c06a3c17e9054d021abd09f0d59c5ef9a690666ba0d7263c6c71169cddbae435661b97559d79c4f6fc4145acc39e79c94524a69adb5d66aadb5f6de7b31c639bf60f2174cca29f56cd8e07c7017f016d49cf394e0d76969e52c97e43107cb79035e444a29a564a1a5d3237a34e79c13534ae9adb5da5acdda2a7f7e2ec5bc082fb2e5c74d8549df72abc2e596ce89a730dd98fe3bd003f57c2ae9aeb3e60fbc11fbf7d64b21f6cf06e9ded7bfa83ffd0c2b9ee5afa77201f5a7c781e2385670ef591ee7fbfbed7fe0e631da84f6e35c87edf1b07bc97df49e06b5616d501bf2448374fa32d41c6f522bb03aeb57fb72e1af9f73af95b52394e95f3049cc354b018b27051c519db7878e2f42fdeaf4b5b5487aad3c6aa005b32dd85c613b6b0d5f9a30df829787b2a757d9f29126546e897851a74f635a269d3e57c19e1fa25233460c401ea7e52307209f3e2a064e250daa83cad06f982355b03f5730eb97915acc2211da6285492d2477427668893209768eed2de68a8c8e90685cebb5f1968f123b5b929798a05a58b52d2126d8f7a555b2840444444c8a8c8ee64d46a2cd9bdcf269c1888a92889818b57c72eb955b3eb1ddf2a9652b3abe80c9b72e1ac3f4d2bebae433649f9ae70e07a9b1e439ecd0c6a817b9cfce91df6abc75afadb41d0f73a2a7e5b36acf9c06b5dc12b140565b2240f3862ba267ac96e3506d4fe725d83746a408f7d9123f4d326f369955326f663d943db43f5312d2431bb34b62f326ce947a3a7d8bc42661f14813647dce72751db48a2ab8cde4d5cde2cfd9126db36a3299b0c478e3eb5d7b31cbc5da61ed6016cf4ccce75b62f1fcd0d9f9965cafef0a8b47c9b7c4836fe9a77b303ba8743c103f14507a786530b00e871c142fb259b7bfc1161dee43981d5040d1c62ea9a86cfbe1b7d48121cc4fd8e2ea768796d70b10ddd266741215ec2e18e6f4eea367bbee59aefa9ee751fb99ea0e4b48c9e2b1f6592ed60e0feb2565c763b52c178bc75ac9e2e93a96aeff09d11afc2630d21aed7d6877e8183feaa8639f9dc3beedd87216cfbc79d62bee7b41fad3e3536c3be9e3ff862c1804fbe137d4f1536ba57d2cd3a377a37b6b3a8a33401743760c42a79fff29b8713516ebf8b38575f9dd4c97c519331d5759b4c12fc10c7673853f1906a2e38f750cc47ab17850477a7640d43aa599b66071a7288d7a76dc4b29a5b4765a6ba594522e6d4aad8cab1969d94d4ae9b75ef559004a0dd938e0ab436bec63fd98872fd8d84b10639f16ac7a356456ac5889ab553703d00cf6e7456bbad733d7a06f3d17629058d3bdbe53c64aad29ccf1b80e59059f627fdfc1444a69f7f42dec3f10835fd779b763f0337d5a9ac0dba377c376db873399ebae7b1ba31c8c0b14d4ae0863fcf839893fcbc7bdb3b0380377098633b98b36dc9114fa02b2732aa89052ca28a99873e24945ad46e9a554545b2d15f4722ab6fc1b935381b38d1af906d4114cacd616580beced117d52fec24562cbae3fb447c864bae4a81857c552f132d3449d5c820a2a5beca65b602db079c3f1d02e010202020202eaa73ef1d718471d054167a90526d32947c58476d802d3443ff16516676cef51be65def213c2b4c0745a603c688111c1c4f20fdf21862fc17950fb8a5a64aa4e6b6e97f40676476cd9bb0fbb25bb6497ec925dfae9b148d6b1a218a5baa8884dea33e7bd53a9ffd5fc6e5bee7298cb5c8aa3db65b6f019ecd0e5a582d1dfe867fa98fee55cc21da666bae5a10dd25d5ef24397974aa679440105145044fcd686fa59d6ede5dbd77d8d63a22feb9e36dbeba5107b9e9fbdef7e06ad72c1fbee71781c87e6785621731db20a89c39fd836082774bbe5d65591624848b1d8f642465cd245b24248db56c3131351525252525252525252525252525252525252525252525252525252525252525252525292dd8f5508ff9d8f832dac3fb1b759b49996fba9f561b824b656fe43f3cf9c0f3353e2ba977349e66a7248ec0f8724cb68a0e3d41268cbef66645ac4a8605e522c5c5a504333c46a189a61a6646806993d42c10e517d7ef8429c81bbe53cecd03ec955762a954aa58e6695a11fb3a11f497aee71e80712030cf96062c80712433e88867cc890f438e483c8908f1cfaed71c80709867cecd8a11e57ba7c7d59f438d4a3892e5fe3a11e45e4a11e3618ea21d4e5ed948719e82b18ea0134d4a304433d5c433cae0cf1a8422570838e934629a5e4354b1ffc12301cf0a004e345449be6db77d5c499b8f55aaf968f979a2b1a43b5fc2a9fb31bb8e78ada0c76b9c9c62dd85ccdafb23e8c955db67c3a698ba0793081a20aee62377da8ffb3fb6787b3e7adc07937f08c287b4d32d6650de679433fb25c33e7592373e035626fbc72bca3c25c1630b2a07bc612cbbfd3daf9b7c98ddb75b46031ce449b192d50b499cf89ec70e66826ce88b4ee760eb4e55b3e6347d226cf88d635ea07da17ada16f9a32e27093baf2c738a211ecf0880877c431e18e24f8d3f2e17620e29870451c509c217fce52407034e08eac7ef0cd7d5b33b771bb0eeb7a0c7255e6aadfa52b5c6dae80b8da9ce973a987bbe62e66838e2fc24f167138d75cd90a0443d2edc3b8400107d4ed7330cbed74cbc1aacc5687e3388e73713b2f1ac37bbb6d361ed9b73af3a67bfbf68775d99d79b3bd953891c6a84f6b388ed3dd4b6e76a00702791c235adffc05e90cd919a8480c8950129992d912222645464748b424168f8e093e7e825d7ffec49949644a2ebf11bbfca279a3921fadd1bcd98e906849f366e3f3e6e2df36996f8e2f41195fd654f0055322ea608f33a5222c1767bd64b4a13930be821d628b045f1c674419dc751cd62f585795cd2626ffd9614cb70febb41ec519331d957a7530d8e1db21fafcce8530e7ffd31b583c2c17cb05a44868ce2abbfc4a6deb35e9b4146799e50532b352e409274036d6ea69bd74663a34d6ab8a9d220c41fa1421492575b05055321bbe3f7911fb2424a3170618694d55c96c784a29b7ad7a356430c6f8d6aa526d16e85badb1f1e8c90b0ef140aa4248d71b17377c39afc6e45c881ddaa32c25b747b67675e64db549572ccd56b14bb4762ce40f711471e9e53fb4c39390ee96e05042f34988069d333fa5a79967e628cc938c37d9f42424c48dd875aefa496856346fe6cb7e52c264dedc24b2d39213d19d5546e306f71b87f1341fda23ee1567ccbcf72e6c92a481f173067c179cceb8bc0b4ddb47f6c845c280f134344fc363a0f999ea22ebd95fe84243bd0ae642734972a9b954d92e938b067d14280c9a9f2dc099e72e321725f3e6ba2c993723681b5a6871468ba7b493d0bc9979faa724f3e69ffe49366fc0a77f52326ff85397b6c18546cb4f03e3691e078ca7a1791838560fe361f01860fc6ae63308b63872757a1272a1e111309591c1f824e4228b33e6d37799d11830522efff221ed2c5e9ec587167665c030a7cbbcc6dd0b992ca57c7c5bd416285c1a34a06ac2005593ab5ca079188f83e6613ccdc3f8ed67b6edb777b13dc8b7ed5760a401efe534e68ad26f0172af6843bde7a04d022d0db44873457f06b4477345395ab4a10f03e46ab4863e0db879272116ecd01ec17c781282f948639c54e1eeaa8f54a53a09d118999e363a4f4232609c2b996f018639bdc56baf468b5a6bc5e1ee7746cec8c8fc05430b7499a7416558b6e7bc1a2bb68f01430b5381a175c1bc80a10d9262e1d2e24273a1b19cc07087f5c87712725112ee239a03b74a230555cdc5749124d6f7baff7e06ef4def0123f33dd0fdf7d87bd3e3eebfc7d1fdc771786fe234e605e604f90fb605a2312b34dfda9cf3efeda3373956a6374f1aa25e5fd71626cb7b3cd4365fc2872eb2d74b9660f373ce0963e3b05f1b875d9335abaf791cabafb1f9d5aa845f5dd0c567107cc1a058a23e6ca1c55a6276625c313f6274e6aad280d16be187cd97f09a33d9e1bdff80d503e01f50f32478c0088e129e84f7800d57d107c0af1e003f4be02aca5597bb00805f3d0e00e45f3d099f3feb1a5fd7757fc1b003b90efb7ea8e7e973fad46068816f721aa09e0df3abaff9d045c6e2616aa97791b9287199b92c69a1bd7c7d69a1b580a3ae7e9ea6ccc5047ff29f2f93cc9b173f6980688acc9b4c8364de8cf02daf5e5f75fa9807ebf377f1e26b0bed755dabcfc19935bf7a1c35bfe23118b91fc3ea6b38c741c2d77c0d8fa1e649e0393813fc0bba38ed165a7791d1c4688c9a6fd122a45de6e3cccb803f39a29ba8d48db566b5756a6604004001d315002028140c88c322b150942582a6b80714000d8196525e4a1acac35192a328882190314821020000048800ccccd0ac0032b7a4b4d2951ced8af2d34049b4ef2964eeafdb2322c860649368f636bead239ca31c09b8e6d18855bd01661a259e80c973791b8d53b6f8523b8e8e03c7576c6b0ff4525f779de13e18bc01bc1767cff09a0d30a1698023057b771a02e0b3ebb17f947d3aaad29163e5e82936d379c0729d67f65510535575aeccc58c0281edd0926d749a85ea046e90946ee7ec4be7a7258e83f6a15be3ee3682960c05b2fec14500edab09f17c4ad67d313ecae802443aa94b6e54744119d2fbd6616cd626a0595c59142094a55405735e9e2de7ad5afb4a0444e7dd74a3428d8889f188b843ad9d47de32412d4e01f1b317c42ab4b2b398bda4781324758e15e184cd435bd67ba22d2cbab87448fe52c713f381a37a6224f0c581af5c8205bb9e65dce9f6077d6f7b37e5877a15d7ae32e32783c1d6f4505f5d14751dc57f59dd23060496b824d1379582daedf81544a623f407b46bf2fbf7512b7fae15168ec867f361f76341d672b8df07e5f49bd4518cba8faa26ba0e1d50bb9cb43a3d58f51c8aa98e00bc7c4437e7e16e53117cbf7bf82711fcd16a415945a7b23e7dc20a02efe99334997e9858cf58af9f106bd891ca7f4223963e9bad465742976a6099653d56a5bb3c1206b5302ebdbe3529b1689c5e3c59d81b9a6196977b6b60ff436f24c46e723ee5d004da77f4b927b063bc6b72c90becd4d09779e0f02548c05c2f807354122045f3bfa630e74f1a537eceb309ba404f9050e27215e75110729e5c70a99ff50d8cf5809d25aab72e4725b80882b25b8b40d7f008f9ef2031a86c1fae6503960bdd815c1b812f523250d2cdc6a180dde39cfbb55567842f35706ad35c30170c0c198ac570671cdc56794ee6c2074276beb787fc07c0240aeed57d09b04073777c390a2ae39cbe75a8d799743af40e4267eaf97dba4992326577fa4e1b8a6d7ca42ea2348a5598a645c0638c1e6fd3520009544bd1d88a15a9689f0a23e425e55f0a2e8efb31334346b283d3d0e4de5955e395f651789ff9b73fcb224f4481f051ca839003a9ec03fbef37002c2006b7efc7557efb5d02104acc9c46cc9a2115ebf25adeeb1cbb59108b476f06023450e8fb988b7e81d7954af9d850a79b8ad7b36358e9c266eeef4aa49f1dc67176d487358be91abf0b86bb73803fcdd051690e11dc0fc0ebd93dd19613ff5cb555350fb07f0c5f6653b7cd8950f433ba8b1b46a1b808b4a423a198919a9186679c60f0527d84face60a8b9d46b628ceb62b44b25f3d68b1498b9cead1c78515bb618c354bd1634dc2ebcc42e9b05cd1c1ef1d7d4a4add15955bbb3a11667d336d0cb947133684eccf30063115211f6c14fc7572b75c22b04cb7053df5f6c367fc438caa3a626a947ce0f803be6cc7b38ff944abfd96aed2a28cd65eb6a2f977492add63e3bd27c6c6951e6b2610ba1208d89e3d14199de04f4ae345447b9469d22d8d1375680776b13ad5b0ef9816b459e2d7657f54fe39b65b0a62252fe8d83e6851a948eb92ac182818123aac2230bc530dc831b04daa8eb8f6607628f8bca2046562d0734de3cafdab2caa101ddcdc2411e463bbf33091db526005d78c24d45d69b7734fb8b27c136294942b12721d3a5c33a7fe12858cef8657704d65d4cb3a34136b5bdd1cae17a25d88c2cbe10ee71b1a8d02e0fbeb0352819d205ab999ee0e31b54f67b4b1d7c2400c18c5c1a8e0ae6e54408bd255932f3f19586cf1bdd9b35203796d8ef3a0fa6a62642a2d33908902d3750c6bf6706422febd6197ff2d6a7d6e8cd4a4ba3459b5708b804fdaed4b67c97faa71f05fd81689eb3954db16d052334faca706721db3a8e2d066e95c938688f2d98c98aa31d4fe7d59f5be0387ec0fe7faa0bf178b818b61a818d1678d0cc5d7dc056a4971783016c4ea59d49020c99565fb975133999d76e93c34a27f5440222024c8a5a65ad2795e3c4e419a1baf7cb21f250ee429f6b22012eeee9e89e0b4d57d43f4b72e6d268c670eefdf7b5607825b007dc4eeaad3bba8a178a5065e8093dbda2269a31d9b1b7532a8db5b334475ac4a2c89476b81d47483a5a3a5cdc20b4467324604e21fc3053a370523b5f28d72fdb81c5e4aa017348fbd64c9ce43ac4fb385fc8170f9fbc78ace6e79f9f3f3e7c57f0e0c9cf3bff75fcf8f2e1b39a3f7ffeebf8f05dc5cfebe2b048994ce62d4c6b864660c9a4ee30421a1518646687072a578c8634ada451cbdd15affefbc0f34dd424a84e191e11bc39f69a50707c03f90ece7a828b9064618ac6df8525a809bf04ee3b34b4bb31afdca6d5713bb2c2d54cd4c8996679628ea56c88859c629faeba8c3ed61ae277277982ecda73d923ae0d11b62587121d82448f50a592be2b61e277dc02cfee8ead67c6dabea5c01e5e2a8c9ab2d25fdfd020834d2a20c6af90740ede6b6c4c59578e93fd7b8cbde3c212e3e059da1a3a49a1cf608a8477758a313ca19f05a89f502fbc12429f205a247662f819d905c59bd6052540716ba69acc84094d99c2a4e926366dd660b769a371bbf1bed1aec94d836df3edc6ad86ad4d1b8dfba6f78d360d771a6c1b373796cd375bbcde4ce1bfe442e8335a4c870aec1c855fb4491fdd30591a95a13613c099ed75d38163deccdcc73972db016992e616f7ab9b6bec66316040042fab82aa9eda2a5a7ac886da1f73e0b8aeca8103eda6756c04373c0090fe23baa233e2aa3d8da10e39a45072a5ebe61f7bd6489c2a65dbcc880ad221d4bfc40c6134d1a248e1c88cc1ddd42e67d6292f62bca5901481ba100dc66ce634520e67b98a62aaecba8b55996bb412baf2e2e5af07458ecb7072646e57298a92bb3c3c28cb3116776b3e06acd2e7adc2430227f131aea9078012748c555eec178a14ad312ce090247e7c484428abc1791d294b7823b1831b73c19767af23859eba2057d9228a1aaa3e2d1377e4a7d9ba5bd8685cedcb841d3aa955b3bd61765e86f39015d4e4ff644c248215f9b5b688c0881a37566581e6d88f2dc5108c0b38628dfeeb4d6fc19b37c25d7c651146b797ade806062ae839936892530b0419465622ce07c288c9fc747e4f0a551188fea7d31208f75e516b2cca3b487a50aad953a229ca1e4043760ec8ebbdda18ff3e0dc4308a71865d4b509721674b6f4b87b9a318bac4fae151ceed2fd7fb89041385988381431494093b7982c67447f365c1849be609fb5ee2e69e0c5bd0e562a07f9f486b9f0e183a8e9fb15b4b2a12acf31ede9131afec0747389809857b25acf310e720f3168a21620b0a4ca3969fc1a4cc6188141ffedf837f8b5eb24cb96897099cd66ed99fb1f36d4f72ac4ecfefd17fd64a317b230adeb82f4832de4ce416d4e3de1977b72accb53872eb28e999166067ec932ae0e276047e3a0551357c5b1b2a1b3596e238c51a70066275d92610db9fa68b2141469948e36c95989b731c01a6bf3c29623ba16a2e8f5a342da02bae83aaa50df4735ff4a2ae5076bc4698dc907f62281998bf99b19e2681a46767dc0aadce966a7b0b34fbcb8e112b1c8636c11c39020270da0201710365560f0b27c0ad73bbcb13f8dd4e7204088cc5c4b66a7954f4c24d88b1b8c68a2b0f826a2ece56c548877f550dc2fcf0232bbffa8d214e4eaaeff1ea9db5125e9ea5e042ac54d0f5ff1b3148fd0cd07c3fbd293d35a9846c3dc15135f9c2cd3b6615ed9a14269ee7404341b9e46cc1fada7840633e536de9ea16787979404e14e06e4da273dcee02a0cce3e780ff77d3b50d9a21d7327d9f24a4019e27d203288b5d9b2cb4beddd0a032fa608ac788220609e9bcc8cfa6de66725a4c1dc5efd6a012211d1439afa582a6cfea6d3ac4349189c9836d31ef3fcd69da22bf10473a9381b3d09b3c334705e1713ac9e514788969e148d9603a0297b849958a4d442dcbd85e2ee4b09b029bc8e127acfdbd458a24fe02cc268ae1e96aae1c78d0c5472f4711fc2441e6c612d20b71e5bade8cfab4895ce1fc5dec5ff8741cca73cbf18dd14da3d31b143fc160e30817efa6fbb910a8e42b53f3f7a66b050a8759a6ffc559bce22cdf871173a8e59018269cf040624ebab15b513e9e790f33e68a1a3c3d5321bf4fad5e55113095031727e2912c0fb948aeb12e2d5874c88245cb8181141318cefc6d3833adc7009ac379ee8129e587f32538425e62ee19ff49ae48b24b5f31583fca7f00abfe626e2f82a0df5dd6a2093048813393501f7e74ee26d017be31d921e688c4dd0834e6be92e8e64ec1dc0d91500da65bcab658a348cb5d924a5e5f2e7ce18d7ba03d4550315657c020f4a78dd3c5ab816d39eedb6b4954840c66ff43a7f6e39b44f0078587bdca274f5df2d5a2ef3f696cde78699137e94155dfc3111976dab500ad9ad3a12934675d1efbe53cacad9cf3b867b3e80e65e02b27f839643a061b2732c7fca0143c0b6dbbccefe3ae41dcc828ed2401203f9378218be61b05d9aafa80787ec5828975a71cd44cc0c8b501ca95f8ae60fb29b887743fd7232d611832ccf044285371a7e29ece4cfb6aff36947d0402a2570d4eb96b81286dea6fe0e66c23da09a2e3356d8d42babc1272a925fecba810436bde9a0df804088672685c8f69e0135fe988caead21a005f469628ad49e1011b675f7650699b76adb2ed45e51f59bdb294c1b0647ab01eb051bd0464585d3d960ea0ad4d5858aeee400863fcfeba0175ae8a2750471bdc38e4a833e0c264cc0f4ba3064866cd71cf44ba400df486ffd4500b4db7ff84a28ad976b81c14f1996fb2407d46281483f8872e5c73be9d69ed773a6147ac1f877fea65d96603c175b60376ea5ad6dfb994b7ff6c1fae2f13fc4eba2feb572893a42e19d6e242d8460ad0b9d955ba6019c672d3ac5fbdeac63dbb9e8a13e89d457659248c48a907b7c4ae2efe29b0754d327e88c456d76fdc8ae08019238b0c69f2c4049ee097e52cd7cef839007680073f8350bda2e5eb8a4842c1bcc4dfbd98babb7fd5ee876f79741d953d9b71d0d174b7b1a21da4e116f78bc7685343115575c40ac99fca9d1c54794db374a4e327fd997aa23e18f898cc6b883f03e09578091a984499a1d3695762c27bb18e16a2697c9f8c3262b2e84efc330a5e91145da8d2f4f836bf553ec5071b8c096a5a437f1c732c6e349cb8cdf1846dddea05093cb8ca9ad399396bd4d4a0e3044dc1f06ff5234e6bfa12301dec29e956bd46d29c1a78e7e4661e5fb70960bd89510275d4815ab4477a05b3a1bf6d5e6c732cb98d59fdab458bf555f4de06a592b9d8f51cc33792772a7a72f1cb98f11b3c3474bcc7c069b42c3f2d14e3f5974958aef9cfbf8533f348b66ea8b1710df136a377091af237703e9a5b20e1afcb09355958ef35522c8c4085edb7513e1f88097ac2f29da72b613b3c9464f582a3438dd3f315e848586945b02ffebe81c4b64e74c625291e47be1c45084cbc3b84708594e7f40d7242d122988117b73b0b3765ad0791844957390e6094146f5c00e1ec2f0792e06a0db60b279b148bd9ce8294f1e3a4b0935e062f80f3353dd089443e849c4d41ec105842652e260bea670934c95484292e2666b3dff7cacff6c380ac858f5dd4ef670a163367a5c96579fbca473a3f54bf29a011a3ae823dd84cb8bea22a074566fc3d55287392a0ed442698903d06255e7c25780bfe228c4a14b656a5d8e257300ef30fdc27e78d1a966c3ade97250dd5871244a6fa9987617431a007171eac4c0380810389c3e6fe2848d5fa1a551148c8de1df016ee7e3c52766ceb8c2d615f03246e1de4b23dcae81534b82a744568c852a25412a40c0e946cda92c7907cab0b184d665656ecd844e8d7a9a82cf948d51f68bb4d58073ebff73aad0868629fcce88ab6b4a59807ce22f126354e511c55912ff5f053d1c1607b834cc6a3dfb5f2ef6bec91d00614268ef52545ffe7ee7d4b483306575c154693f8792ac465b84fe2b639b4870aeda672ab5b5b738d22021bf9b549a60e18993e709d9374ccf41f2883599e532dae4242a74a114b3e3e763c3fbe9efacbdb83cd3bc340262c94b15237ba1864eb3a7bdbe1f745a0b43eb47b6594c740f785ae6975f229bb52336304c48212e1d79541f695a886a652accfc2732a568563358137ed82c6498f66f7fbccbe8840909c00bee9931bd5231318f078d09dbcf36e2510ad3f4a850ddca7e2cc5de05e7463cffcfc33242d09a400b16c89f8871aa836866e34c15935dff2d6e3b1cc7b5a5112af9abc3746f20cbd0ea7a51658d144dc8299e249e65514d4d05bb2df2cf85e2ab315b0c9e55926355e49c1fe6d0139e98640f76d8bca2e34fb118782312ff9f39077d916973e609bb1e6e5e7334fa6acc24cf39fa067be0b88a0ab8d85911e270478b2a3f77b758ec4c3fc9d79df9795190b9c415c020be7a28e16c2d3c59e70d657ca31bb78e6ef5e1ba42e422cc50156b3ac567a9ed8a14e4534cfb8ee0e2536f4cc611ea8009f10dac48e768c9881cccd000f1c12735135e8d062b2fee320630369508f7b9ea237b3ba4711293818ddcedfc262ad9764a6e1095a2fa005a0990e823d16ee0cad971b8df0a1079a28f21cdcf0155c8471e2a37c64662ee677305424dd5c17bd7a2ff57cfc8c44537f2baa7bed75bc00eae7ff4e70a09464c3e281ca520104c98c75e52c0e72483bc6733c983dfd0a75c0929411a1cb6341b407b068e1169f213ed49daa75ba987a16c8769eddc91fe363421e9101948e0c28ef3eb078332fe87cc118047215c9085ead62302f1277bebc8e0a7175b7187402fcb1efa4e988cc8e3fc5ac9ba36b1e7b26ee049763753446678aa105767aade8bff45652e157cb351070a1a770222ce0c53c9efb6b075a2753890ab8d8ba66cbb9fc7d081b2b6da3c63b4d6b73c57c91f2675a6d680644624e103144d8d32d6d81cdb7866430296a98fd6b953f4d71ff1eddd1510f660fccde30ffbe33d0d1381179e75913dde115bbfa12f453d474b73e69a0812f2c6d22317344c6e8309056644a126cfd69613f45175409eef7e7b54df8e09d3429140dddacb1b972077d27c6aa9247000afe67487cca07ffc34b146f54573b6731ae5be15d61d936d46f0ef1b089a60efd061474a437c781a5a11247f66269a2bb047900223054b99458b73f7feaa2e1e63c92ca69959e6dcc848a662440012cecd0684795dd056d6918ca8ec9b7d3de673916838d5abe1968fbe3d0d927e778070f5bab82966cd2d1b6f6a7aba4bd22019425df9239a6f00b867736a28a8a140feb8bc2d045cf15920c1271e04f83874a0a2b491d8d1448834a75951eb6af8b556ff45a15f6b2c3b6fddbe7fe9755d704a3a5c68d8ad93e080fbab3a84a2df8d13c669931ce887d98858e988484315fd2ee61db525f2403e3e171f4cd33013c8ec4d0ccab096bcf2aab42fe00ec45f1ac915f48fce013e4b67add9c813694c9150440103ba21646ab4aa2af128202ca1142c6c953451ce4e520cbfa90feeb8e95e495d965731f8c9d329c611046582212ce34a909f68e9760ff866696847f05ec6d0bbb6ede2253a0d526cba58300a0ba3017751fab26b36afc9f55708b5f3896f4197c3449b27095d8f095634f0c5a9ae2e280945a8536e9f009520f629488dbfd96052321017ec5c89b17f02437957565e5929b45289c4f8ffe222f2242931491e965e6b2864a6141380179f649b1704f1cb188b57a10564b8f4625dc13f72ba77400b4e62cd3c249ced1109eb585926bf9a646c4f9e78d00baa640e20ab60b9615e7581a8ad0b1c384a250ad4182e571c18272be17a4e3c5b592e28cf50b99485851f24e367b51328561f9096ec321cd8932d52f36d3755b21ccd77ee15a2df390c7ae7b0f95fc099773e880b88d36b8e38c43b5d6b1cd366df4d11f43d3e8c39c077ae108414564397bc1738849415a9a045d88874b43b128902d74b1be1212bb776a969a89571d9308e17d7e61f913989a2c81556698451e5f8af9729d97c062813c198b8ff758d5a35eb11fbf4c0ead8162dbcc66f375365935fad2ce0980abb7bb1e50f470e977d58a5f43fd3604e308d4e1268a8cd36c6ac4020a873ac1e16bbb41c34c8d3fdb6b1204eb57d6fd412a241c8e440213b3241a284344409d3feffd7d7688326d559887705ccb3863e6336796c2a21ce984d65ebd51911967926040b68b03e67f5e4ddf168ad67c035b7f27b7c00b39db4d0a010db06ac3c721020b1019172c600158199d799637174b68fb115971ed380d1a9f8b536c8a15586af3d52650d592e76931952b2d8dcc4a7bcb8868a633a95a2609d1f590571610d0989bec446602b22bdc2922a08c4b716ce6af9798bb1a5567401267a8a5d0792c9fdcef5fdecad9ab59ba3c240a2aeeb30aad4e624d42a9f7876c620860301446bf3c06992cd6ef059c30fb665191b687df26e764377fb35990e5b9b6841e471b775d5b9aa2d2471958b9f093405c2d3dd8d15f221d213232c092c8a070a131c6a644797ebaa86c07de26ab027895f84dd4406799736c618801cc384864ec108828aef8c2eb3f4bcad4735f544ab5e7a95edeb87a29410ff7429d48e34c09e539e9d75c05a37bd623419c4b2e378ce6a090b343f18affd86902eea3ab6d1305ecfb4a747fc163718a727561590b568aa314d261c65a322721453c0c66649bce2dfc48387030790c7666d9cfd9718ee7ea3c8144e5aa9819d5f074e6d7e025add26f5bb95a021a134fb9a4153f5972d0288f718a17d26156e0cd72c15682788fd71c9d3c3bf2bf5c00d7ff5e088074e6a4627d1780d2a96c3933885dd6bcfe6c764e13850293c73c8347f7fe725907413f176e9f29296590b48ae52c96c976a1359808b41f120b9b33d00b87e262ec007a3196b5c12c72144c1e3b00c1010855b06e21a25ae394af767ed64566b49107fda10b1dc44c1f46ba5a27dd5518786535ace473f85f87ea4d910a3683af66b0d66292d812e295552ffd59d87627996923453ac7ca1eb63d2248346755d15d386bfdc7ca790ab5e54e025e2b8ce677e6614d8c9072a0e8b2d3c954840a6a7c80de8f8dd7ec76ace7d44710f33f0e444ee734321f5280d4af1f0e62972fe5969f691a22db900b7c2be25c65be2a90ea2076c6d9ff2116807511d72eb7ba4f9c029cbb3cf9b76c9d39fa3f00c1aed52c8dcffe2fbbcdfefdd906a2e92bd783275643f76d311b799b0892441cd145d52db1843f62c6bc67e301e1da3b950fdadbfb037e44c5c4affce350cab5ed1ec633a05107c1859662073329b2b605ed2883073667e54c66924d44ee56e6d44f290e8c6137018f0801e26b604c8b79d1bc85f53e35a9e85f341b0a1b9f02f6c840daf3724c21a8200bbc4920b01d3ece3d8456850eb543ee1d10a99f3bc10f58afdd0c59de3024717df983b9bf4c2c464a9550e32a812a7196b63c45ad2156080294c780016f3f928411a09e0cc3cf073f902922c83abac109e2a1329a709e3ea96bf2f516ba841690fe4560f0232e2dcec8d783c2dbc82087ca9f8278419edd3ae29b189d5230b8c4a51ccdc2e5b20ac62918ba1275f56190a8083df03ad4cef892e7be51d051d6a1f9b75ef120521366f4c31389c22dc023dac027ab0ac713861afd0c447f2db7614b3eae9f38691e089cd4bed8869dd27a452b36d280bff2f5e9cecbf6a822cd41815eee4230eff250d8b73040759c7b2cd43185246e6e09b114ca74680d424240c39155771b23e3b7c18725b27d9f11dbcc59f653520507b5c4cdb45cd245a65b7b2074a3a7aeaf85474c2ed92375e74015be9ea342fde17b12974ba25c85154254fbcc4f81bec6c84fee9d919fcbd7872edfa4d69a844830564845eea24a3ae1472529cef1beb85f1c9951ada7572e0b2d9686f3715ef1a8873c5ba39b05b9f0433d3ce118ab5038abb9d7ac9efae02607ac359b252e1fa2ece0c603c7c60be060e02601d9f77fa4a52fa8c965f5163a36a1f78cc76e6b84a17ff35738c1f11b1ad6c26b065e66c25b3c813eaf0dda2b2c2ddd67e60729e793183ae32cfb4f8d3d05851d14d5b0df4f8422306f8889954a688a687d08ba95ae3d4c1115ba776a523ddcad3c73c970003aac0887e721459344c9adc11ba5d9b2d317dc753041564600960fd23b9f0444483b1d1e0e6ad71c0b9c5a0423a71061b9aa1af93fc8090f1e9e739a026b5d138773825aca14860c61f18f0daf6e60e02fba38b92c48b3bf2f205504b13053cdc63c85b6463243f0dd84704fd91cd34699ce1f38ea5424d4c9cec2db988b45bf0c3afc66882a0f17576d5810183205d85e1a982dc5e26ec4a0167d0183cd4f61da2e1a4c36ae88ff2e6ed8e513545cb730d21ab5bf48611169a057ec0abfba98232112441eb753ec03bd101328d7b04641d0aa48b280d1bf6cdbd8a7c3a089623622ccf6d49a4b9284b2a10ac05f95580028eb0c76e8ccf2d18dcc4d76a87051b61cca024a18a33c1d93c890788a570a5a0d8bd8d7163fadd1bd02e8bb02034275bd7150e99f9f12f2bfc798954256900260b0b1f419fab9c18957837825faea5be1543bf907b34dd1659e826e431e93b47bb841735e45f5def6733db39c6a3d8d6e801e29b9d39349be856a9851aa17b82f9bf2f3ac7704606caa90dc58e94fac8eff2ad03a4101cf7b0a7dd9570fd693f12ee9c2159b70144b982b80ddee94fa52d002c7491655b0c68a70a06bb146e649313a5e31d7aec66a4d6cd731adc6e919acd20d0ab279227f6389e9530a667b2798b6afffacf890cd93d03b0b06970b0da74c16c0f7cfe45b0c8a4cc0d2654b2994c30bd01d1e0d06c75651f55a28adc8dcdea382a4c459bc3824b3499b926a8dcce43129a77f93e70f6ddb9584c7f1569863b3efc0882c5d45fc086ada0384a91c9ca7c432206f623a3dde5e14107ea6ee117dae5e4369c3c7692b75644dc11f00b75029b5bec150f21481422d7f8aa60562326b7ff8d5451dc63f3bac29c3b07809178002ce1b43fcccdaf88a02dae51ea75c90d43d099e058ad133f23690df5c23aa3eb681a293bb40dc20266b10c0466421575bb7c385fa98788905f1483a2460bcb9cb4fb84a5c0c518e10d7fb421201b309bc68380e6b4f128acbfc010683e9deb37c1353511b9033737b283b618530408a800ce4c184c2d6bd2ae7ca033af3ba4e56e4c2cfa5e7afcd7018a2ab4ecc2bda150ecb4336fe0af9ff89f5b708ae9043c7b71b33c3cbee0868cc1ba066f634ff3e872100aecf86305ba370ddb8f344baa73c708f64677eb7f78a127e3d3310f55ab75bcc2dc753ce67d1c25713d1eaa299be43f3b5e6e94e178ca6bc84dafc317334c350729d43afcc0c929060f6baac552e01626673b04f305acd4388256c24a1c268b2117ec9d92b204a6a5665d466eaebd90f5221db68d4bec798198d85ff69878657be45635d8e265ba744e3a0210b9493ee6b2a905825c2705b4b35b768b9279216377f369a3b3fd2aa98438ccc4f99aae9c441473b22073c9af7b8f4ea291a1bd6690f39aa1986a3cc0ca6d754a5cb30692ddb7aa13fee5386bd1998863d624f04cef11aa864944cce594f328ced864eea51db0740f7e76c216f3c3cf2f0b196977f5ebe78f85cc583373ffffcebf1e1cb83c74a7edef9afe3c7770d9fad1583030f9b248892de6bc21824c373fc2ea797d9856d828e9db08d84f0b41bff5466d78a77464ff81c547759f0be5b7601557b8159f2c21707fc9a07fd96c9ae11c7eca5c9ae07a79a7bd993228eca85d99d96de68d6199a5251e325a0dd37c24a2cd8c92e8ce7be8aadb128df8ef029da3aaf90e4e7005335c2cf7205161bf3d10dc961d483014b1b2f4c76650069c51694a4716abde19b3ed3e643c41ec097c8014037959109011ab6fe54db4d29983876f9907e01afe6607d27be1295b782d852f2965d72e9affe20c6c3c5ff0242704aae93b40d3fa5adcd38162e1e1a8ec62d09f2f88a60af8a0f1f8d7774e0f36a158b39ceb39a0026f089e3a97828f8fa37802c87337db0e130760673abf565bd4aac6ae0dd8d28a32affbe9258e1c3830fafc081947cfb77911b7882c41a70895c242422676aa8990a0946c53665382de6d703b5988cfc10be79153fc6bea763eab723810d56197b3a1da9278e8536580d373d36af1d82aca069fcfcb3b0ba155380bc4a4aab3ab5fb9431bc85480f648a4a91c7673594413efaa3b6490a57803b126aa87526db170c434baf8c074a10d43410140d981ea7ee7aec7524a216e3cf510ab07161da5f6edf133e50e4a5d13aa22e909e5ba43c84a22dd64848550ef96c493ab52ab4146273598178b033543f8098472e877b7765748d4e1ac205dcd02df9a1fd03780bea666829e96c168c3287c6909988affea69c04a29f1d50faf8491e9c7771f995625092cd60491521f0f61873078355fcf2f3c6c73147d57d860e9a9169ee300c0d71a745f98019ddf2063d84c029c1dcf94c9c6004fa2a236eaabd3250008b66b9036651cb6554246edf8b059128779c2d83437b62d727831e7d405ead978414bb9b2b3e1765739d8cce2388edb2c28605815267b10fc23e71e200313e100c96145bcab75616c2a2db0e233af8a1c24a34571c1d6e0145978e198d72976e90577b867dc8abcf3179b7d0f393061dd31da12d65f048d40c20a633d4b76b159423baef8391ee3e57a815f384dd880631864c1da8ed590c8ed22f25bf78521cd4c6f05f4baf022664bafb1b28180b93fa58530e61eba2f5df45c739339a4d0c36950d2ce3e8e53cbb447612a9416b72b802ff18c332e2eabafbb9ac326bdff09847c9c916928e0d5f41a760016d4d6bf25973060b8414616d6be16c5a24559e6b58865d30de29cad5d654d16cd2b43e344fd0e1f3e8a353ae017253e4cf816c8e59cb0e42f6458d1199f63fc3630db2fb41487c9af423cd279d5d7a0ae8218793b3db8a01fdfe4bffb3f504451459bc6febad9f50f2cec56f35a179c1f68f251e54ac004d998e017eeddafdba51fe22f160015b6c45d002ec260aa64c1993b06c105939911d9dfc02e107c6bfda93d68dcdf6ca44f252b9d70777da0896e8679baa45d298747f85fa9f9b9533454e1871b79b428b55bb995a8a4b9c1d765324bd274686352db9fab7881355f1e421f72b4a325ca32cca8a9b477affb2177eef50536ab6f97f578059bbcf718187fdcf3106d860e3303e8d493ca2ecadf08dc2a164bf062f4b00c95259c300d72d36b3bf316f79414c0e78c829aaf5d60c0b62966c42ef1bf7f3b0f1d0e3181371d98bfa2cefbdea593eb0a233d9f9af6e440fcb4886b5e48336ad3e58015475678905b0d54edc3bca3cfd7e0f4ffe80fc33ec7f5477f41f099d24ccfc439ead2dc3ffd080e604b1e196055faac4f006007e1584c359f28184e406aa15316e8cea21ca9ca38e3659fb00d97a2166258212347f37aa78cf535f3cd9bf8549c64155093dc8b84cbc0f3de7c25762b0c55bb2c952d52bc16a200b15189a394f0227ae99aa1ed4fb3fce930b604e2502ff9964148d2349e27f94f262a468331d44d5513fc0e332d912ba793a9bab546dfb3d804ded8620143368d6026c48ab30964405161047b0573362de0aac81aeeeaa3a9f44735302e19942716aaa2be40e58260a02f8b4a215a7a98e2c4f8c8b2156c7fac217008f818bed574b63837921ab0dc8b33cfe2603358c8bdd21431912c59312bdb1b842c114f7f78ba727f3a02aa61390d80bac19287868087a143ce47148de8e5770d707dfe24424601260718168629920b04126221bddeff586a7d1191005f4fb7c68e76d13cb503a98826f346607ae2a803f7a110b591551f0a5b6280aa33eaea0c79371666bf00c7332f5854708ce894e2b59e06d18b0386db99cc45e33246b50111523dc1328dd30e8afdef267699ae3ff97e91cc9a92189183f3fafedc89c8b64e721b9c23318aa726fff90ddc47d6d33fe5697126c61c2e59e0c6d29a64b16b3be7dd3995857bb4248d12041ab302d39cf18c108ce5406610440f9020681116d246b777f00960f022b27d992d62f9aa90608e5000b0e048ea0161e8b70c5ed7fc53d78921621e66626c9e599c5381c993fce7061a57b5a3d602cfbce0afad0e0db805634537a02d9c5ef0d52b0b9df2ca4af41c1db24e6b4c87f43e6c961f81fa6e06b482cc4474cb8bee127d3369829bddd0b0f54a4fdfa85e127297010af04498922bc52d81ef8ca9914a51d2a531d550af8de24b602231e2de9dcf0db98b1ce94146f8b6017763908d91165b1e0edf6bf5ab1ff642626452956a1da82e39ae333464796277a46b2e6f865d05e906abcc50d6ec360070a32f6a683b024f2abbe22612e306cddb5a4ccb9a1e1b8902a829c5514f84b90b5b3326559e8bc07028df740b6908f8f169c49455fc07eb2095bb1d54e7660482b66182ee3459d3da87eca39d56f57e16709f0c92f13a10183695ed1eee515cc5f053441babc6b96346a31326a833f023d238fa7bce3e1d9fea23bd7a0323ad359a12b0c7bd05f3b50ce0fb68f5c48e11b7ecca68b1f67d7af613e06fb8b8aebc9e83227733f29a37b5f09fdcf1540c1b53ffa6708e48cb7e6dfddd9b21c9ab53f172d5b8bca9b2dccc3dffe5f0869ca6b689290d0133eb73ddeafcf0ad68825eb752d57d95e1a49360b4580bfc3c6567b5ded510c08516fea0eaab0e09c6123f218d09067d18a464edef4ea238c13e054539b44a1249640d5a68288eb8a8a86868b1e8df037b261348d3f36893c11830c0e911cb6922af2823af3e04d2f2a22771805ff32480791746997eb22c479b3642e408365df8cd781661c373abc73b2dcfc167226da570d0b812c8c390243ce604d3e1c03e758f9620f966fe1f7c1c4f0d3983e48f2eb9532cbac78f6094522c2668946e94634ec89876c750d40f2430b2877f849e5b333391202a8d94ff61709d22019277b449073fb7a98c4ac74d3a524338b68efd5eb0f293682d96b2a8b2f8933d07846e53fffe7f29684c68a7832644ae21fa068f86322e852ace0eb351f5d1943c37a2e4fb32f392b22f5ae64fe1f238f5e2b7f24b9e1f0d523fa8b9d5b32df500a13759841195d23f482789c3ac582f5a271d368f117b0cf606443307558134476bd81e104197555b76988c083b1eda73554b2b6162e579942ab1b905c4285acc9d2d246ed729bc325d851077fd9fc3e12a50e9d907939b13a371e4b3c7fb2c84d61b49a07dc614fa8f5da7160380bca7e781ab8bdda29a2df78bb9ff68a873af271ff002d4bd8e8759291e8092390082411914ca1ebdcf2bac4a5009c6ff47d6486d86c9012ff4a748215ca4938a55dc52d3df2220ea2f52cc89f467dd5c4f3100dc09d2a96e0dd100e13db1291a307768886b89682485d6e9cb1010a785e9de88b5c06f3966699b2e3e7125b483df6a2a683d05c2562e099252ca3563a831f4cd7469a69ffebfadcf6f0793304ea0738298e3567c2ad3861f8835bc5ab5bd5057c0008bce118dccbca7f47f65537b4e099c62d9e5ec651420142846a7be93963578ebe7f7a4bc818804d26c28da9a8f72f1b773034b30320424cd7a7f78c16a73495429c30b70adc5276a1f8cf6ac916b6c45ac8f77a71f076af69936c02de263e09c7d24c1903d2462869bd149572b0fd2e35f830e6edc70f7e93e8adcc3547fd9d960a145d725f268d1513c654ccc7e3e254b0b42c582475a2078155070f258594f1b173c2162da8792caf737c20aa14fcfdecd522a8003b44d14ea9e32e60fdeab21912600fc013e444132afc68ba04e52bb6a517a78e18dd4ed5abde7a7e8cb545b7ec1e251d4e506480c06a972588592c288b67e888ec9d2ee534476b617f419c135c81b0527eecfae8a874ee3d6de8368e86c44d1145e263156e18eb4963d035825d7d16b9c8f772fe0a0e266e02e034d61c0b195c915b1259f56ff9206410bbf85ee10597dfe4e97d472ecbbc763ccd3826eed5ae36261bce3a04c5c29e61cba97c29169091d4499e62f1677d14b740b09ccb175aecb5785c135247812fc317026596f41ed62b14a6c1a721993c24eea8dad876d66b8baeb6a962f15a0066942bb6974951f08f697312c5b15a5197ac90611bc21b0471ffd7c4188a06aaa8910d278947fb4e89ff89461c712f8bc836ef9e841623746f9bb79516ecf43d65ac56c4d7afb88a58275e9a36bc0bb040ab66b7f36aba36db2003d030bf73a72306218b54e8803655ec794429de152a327b2a44918aca3985881118fb06ef6b4abe439c78c4d97981980ddccc1bbfcc2e38d26bad763838c52d800b6fac9e6c3a4543c3093db0f7f7d852fd85d4b31089d84f7ef98892d6ebe701ba57496d21c43b273df294256e8474bcb26a3d941ed197e6f064d8313abd7633be08e8c5695805c515fe3f7d4349371a3474df6e77f88cbf5451bff5c903418357ae86c6870a668124e7409bed28a99f15c380cc4b7223f1917c6eaa229fadaba0aca26cc14080c86bc1dde28c5166367a9b58d88b407683e3a1b3d7aa91039d1aaf80a674d93413979745550a682afa03f53031d848c2e1559d874d629b0824234e001acc877984178f54470c359b31cdb1c32353c2b4b361b63951558f6cfb36b496d0e69b24caaa1b3cf01ba80a58153c7e350bef2b1786253d6dcf8696fb8eacee01d9a857a6ba67c838fa3fae07651c5b9a85d261887cc4cfbc2d2cebdef84bc9d529cfb7608b5ee654afca1d1bd05a000324412f36e06da56e6d3a782f4f17f13d8292cba807d09cabdcd16af003c44e1e719c3ef0a1da7aa2ac278ce1f15fabc0c69c4919e4a124cf13bdf1c7286b94f090ebc65d0f8ee35ce2d1d00b129008bc63c3bde01ae4afd47fe381ba721d30463dcc149cb3956f07703199910ce45142c87d31ade415a9062ca37a7c2b470498e7d27c12167d37f9cb8eb12994d6a003e536c2222d21e5928c2aad5f586dbac261a7489e9ac6d620e62407fddbe190a8d6f6608c5111d1d73cb86e9e54418f1b7b3776b3084775c0ac455a24a8bfa5e875c63a014ab81f805fd8136dfbf1ef45051c0025559f97f6b2ba16e527d4a3cc11a08a421c0507d7cb60403a702c5c4a1d0d78991e49c26f974830669fa2557357396c749782fbba493c8868170b95dc2e38264e5acf762e158fd23886ad0d655dc81acf42be3f1cd356565187b118f4b27dd2110b141227ddaa96d575f2c27b3e993bf56409e36611a0b55071ddd041d39efda4a9536eb04255aaf543ab8070e87ae30a22518aadc25c421447508d311dc046a288efd2b120b27ace288430539cc16681e33ab03134dd828e86ea160f7be589299df70f00481b77a70a235c730e9d0bd362a1ed469765b94d5b105daf8b581c406aec6617ae05bbef074d729f14ce33ed125e1e1dda960e4d84ac83cc5af93c37a8f394fc3215fadbdedae26461d105dc493679ab67b3320e438d053c9339490b5dfbb63e4afdbe683f45c824a0b049355722d5ff679701a0952a543cb45a9ce17fe354b5aca55640d834dc9e69a4b5581c32335dea179d14378e476b900d16549429d0caaae76af5d1add6894fe1de422435ffcd8fdfcf16b8c571049b2786579ede9cdf49c0a16fbf14e120b51d0a8105a794632c2f012005cfb6ef29d23e6cc20dd53910a4cd68b4876fae46d06c3b03a3c6aa39dedbeffbf1b6a9def91abf6ca515f9a158ea9038efce815d24d3fc47ef300141a718b747b21a9d1424974830b04bf18cf5c4a68da0969c6c65177f5da00c6bc3cf48147ac0061f8f2209da2a6c9d892e9d7b0d2c31a8135d9c356d540ea3171382420ed3c4a46335c2b39833f0aa963842afb1cfed6f76ce6dbf8005fa52d7f653ddadedceb925abb5a7b47bdfab49321c81e1da090f9eca4d8abf0eb2d600548e73dbb391064bb0a1a288d01ce18244ab9c1a8ab997b4f5f6ca94b29474c9cbe24eb57878f92cc592cec7b9e1f9b44cdf89b4f6d751df3234ca5c392e82a40a47f5ce9dc9bc8cbd38516538325ec63204de1c487c622c4a12d93aae07efd98e6cbfa3711fb08dd1d6ead89d2236a2a3648c64ebb6867b78a3ef19ba0b5423eb81d57af45efd9a1be6b663237a0e5417542b0c53d67666e6fe339104265f0619767aa2e142616bb23543a01e6f33ce6d629cce6241375abd3da2a02f0f682b8022899a2d010cfcc5133405d351b395a9f524bdf5a136047ee6e93102036e2b5aca5d2015f30ed38518e123bcf9c2de686ecdf90caa4101e9c9d9225ca5b6a4b5fc8d5c0e048bd3c441c001ef24b6c04eed96ea898e3b3f128bb9e5af3167037f8f9a5e1a695b9978c125fcaa9d9fad76239a6df5c2916a0a0c42591a6990416187145c79e2b4b9e85520eff430969e1401aca7913eae7937c103951dbd321177f04dc219a847802fa27631867e2b20d8c805a39265061ac3f1fa46543288b468ce19983aa0453ad2b79a1fe5ca4ce56a15c00c47a11b662f528d770f1159c235226a610959ce5ed26a17b26cf829c32825a8975921ff8e208f515567926330cb12d2228d41721fb245d098cb11d95118c04441c157bfa95c2edbca4622ac12c8eaeed9ec088aee9a1c9ec588e6d082d9197df4d6c49ec95476de19d3a3c34b7a740294e9d5be08207e4e36134fb8b87032865ace2b00147d9d722b6c59930d87117018ffcd83479ac2ad5f69aa1e64b86a427cd88df22fa2440d089199faa11fd01f2ba876904bd3724ea0483e8d5974fc0d821fe6513dd9457fd9d95aad19fd1a984d3298bf2cb7c7e8733f819856a64a38a8c530226e773dd5d9377c4747a58b1446234fbd817aba1c98b22cc0c1b56a02ae75923d51b6e7bc834d31c00bba49c444f29348ed3583055f9cc40983bb9416295909e6a5b1612859be8422d0624017d766e98267013028cf33995c27bba2ae6065c0507dc4df778fec6d864210e5371709d1dd67057e33a08ca1c16d0e9b888a86fc3226af066c4353c6640ad5f7fb5c3fb71805ad9370fc4bd018858a9d3fa01592beede60a63f76b9fa6e34e7c9edd448c58263a4d1f139ea64309de1166facc01feb6af880b9365dee40eb37d07470d1bb238ad4da3fcd5a412c4813bcc4a3ef0dfe5031128a0f56e988efe53480243c8842e1df744d139e3a0de648ea9e291cd41d75b6a2bf3b5357303fc5c1c918a2a802759a8353dddd9532b51f047ab1801d26c624f209140cb4ac881e6799e6b504e2a95a931781cca11e80968a59e2ca5e8d7b79895158fa2603466b135e9718515c9413fd023a165af7e786103088af61a6a1eec375ed4ac6b1e5f49a07361491914b04bba917525090e332ccbe3ce3c79f9bf0344c93f611cb98876092c29a1fba7c897dc997f3d8a846f2bd9360756da26eb7a97bd5692af3cac4de4e60b586b2eaf6b64d5bbe6c956b35f8a01b00c640e952ca2764aa254ecaed621650f50374e4e81b96922608fb4b14fa90ef01370dbd17ebe8cb9cbb4ee5f81218bddf343706832e2d789f952dc7796ce8729ae3153cc87a7b33041805a8a5bed851ff40f02734ee208c499946b70023bcebd04105f3dd605cc7ca55b8f97308fe032dba9d1881f247625db24baff4264024d301e44af3b29d495eddf75fb24b334d32b4472a37c7c860ac7775723fdb5dea7689a6741e0b1b98e89c5fe7787d283349c0ca71d25c56fb9a8157f1bf27d5c5e39401988ea56c17842756a157ef6d127d269d391b7cc1e81c15aace8104c2e7f7efb4e213b7585da73e2c73e4257fb665e2e64380e46648fbdd89442dd49dc0ae3cc1a024fa60c65421892258cd828c37d21d9965ac9e6c44d573370b04a164a7351327fc87b21e7ac91a2e8a11b5bf45dec072e6843c068acfda2b06e28d023f46c933e1e2459c8ac6c926604fc6d5bb4af2fa67262b82f1da5bd1f3e3f526a9f64b960ae7e3fd2ac584c8bb91f2922f8c8cc17f8ca5f9db48773d3e7f084a4084f72262f43ef61e1d953c41f294caaee666f849a3f527eb669d7a32cd4a4669c71ecfd4836c943c6f598b073d410fdc8da234c499de823d3272f8c1f1710150192efe4f5915f0a197f0c5fde2fe55f86b4cd825b3fcb28bff43d92de893e92c98ff13c04e8374387fb6b280fb68311730f7948a95a6784a108fe2c932c6e47226ed5f037c938f9e1522ec51ce9bea3f924113cc958994fca235b72d32feab172e9921344b0b6c88445d484162d854dd60c312885f2ee22744aeb6c11d8e70c561951ae3038fccd4b8c443538fcfcea4b4e6b41e4957bf6089855f8f9e9db30ea9d21dffd7569d61c85250cf4089976e36039314a3ab5a395b0884496959ebb4b8e7761843c6a2d6d0414ff05e6afb1881de31339b2e8ec4147ca6cf71fa4d274202f7e9502f91e0ef2b8fb112926811f822d548ade0cd01e581c29914ff41f496ed2f5a31b7f6437d7efc329807b5baf6b7eebd9edf0bc1116ececd487c467945251e7788d7dffc8c58b99bb1b41b1bcd7d96361ed7bb81efb4da3a8034470faae4e897fb020b73e5bc1703b575d8476f2f451e19533c6ab66dc26c03ec64d015052e793e34d3ab387567fbbe22268c11e505e62c240e102fe8332aa092c72085b5e740144c344a1e1b8b905172905f6c40e27af03bab90c04d1b038943bd25a18e88f8a85fc429ad3ffe6b24ae157ccd4cd094e58e675cbb98eb5d255de2d748bc9d35b257393819ffa03688d61f8f3bd4a24d48d480377c829173f84ddee4fb860015e52b500b416339340afcf962c1a96f7ea62f38875a0dede5802ebd9db589a8ab31875feb9269ad0a7159c89cf87c34c57d94cc5397a8a78d53e642ea53ec442169e3d941aa68fc5be879ca890ff89844d7019148b43a032d7c05c56985661f28bf3d59d55ca13fa197db0ac783a3cb7d212c2558694e5c85245062ab2ea559721ddcaaa7dc1b4e96e18d462d8b35047fa7629aa441ba9b8a9984a60e88d80595d6e4aebfb3c800020dc79e79691456861eeff7edacbeb102c2949780f14ee39856577668cab09150f8ec06d9c7522a313a6b5937a62588b47bf98325eb343fe62dadc86f9ec92328a32072edb557ae0f3829e54bac66c4bf7d551fdeb17f6a5d509e77093251c417fec87236ab1580c76c82639d9c007e0d45681c188902628b0cca60154b6e14116e10d3887f855d4c3c26b396894ebd1132adf7286beb061807e1c3d91103354d7fd7191165df4ffa3a50fc52f80e92dafc8e9d9b806474473b1c323b386d746305b00ea0d1470b371daf4c610a33871e81bd880570f6ad51141a69cf98cbe723e033f600103dd4feb9baee0c917f84539523f3f191d15418aa973a8150e09a39addae343b911edb824ede19ba040e1b16406f6ee013dac87a5fc939bab05471be68f102ef06b7f06ecdf5097179d45bd087dca1e4a8470bc75ab7d18c145f3ccf17500ddc5bf767ed2fbe4905f70675aaa850dbb9207ca6b318d8ba3f2a77abfd20e773232f7069faba3689ab3680176b79d744d1ca487e36497f0beb03e3014d05811323b8a659a8446e9883ca69c1919b248f0bf3c3b045a1e5269936a5e9e2d9d9adc53776431b5c567b503249af3b0068ba78c5fad106915e5e08c4e09622f6fda1006deab1c8126bac64c502962cb2ce32eb58b196152b58b3cc7ad6ac63c54a562c619d75e41426e193df4a77e948dbc43f623a205c55058e4dd1877c0741000c15b557bb56c0559ec6564afa786eb959304cba48956087cb69477779764e7ea515495d0947448828b9aa5c57556040586428566016c1442c0a24f5752150e9cb0a237e270652ed4414c621000918e4c994c2f18ddeb954c20dd5491c31d3ebda01ad72008818a5631827b7ebd3cb33b6de9fea363ece230a26545f8fb89008732b02210e70db3a7c503c48bae30046f3d185bc8c86f363d7c57d16bca9fe814f5291db4d0c8c3ef1c0465f2cd47d7ab730789d6195c33da85a0c5ac423fc35b8a81427027689f2acaaeb3de8c07bbe818e0aadca744e08cec47452ef7d785885612fa88d8d400937902060a1bd7ee0de4e946de0f3f808b54c12d545dc580ddcb0320a76448f86ba28691ef30587abf17799dfa8125d077d1dd43e35a4c8348e2338b0d2d61b890c3218ff3ca8ec2549f7fbe9064db61c963d7ec937b1e14b687a94f33f5f1d7f2aa32653c781a1224d7183c56136b09dbc022a3350f1495e0d1ec0bb4913df1539501e4ba24b813ac808f72e375569a4b5fa3964771b1a5fdf58b130ee46705e7d0a04a544e4e8eff782c4bc2dcbf0b6afdb4f703b3cb4a95ecfcfdb49c6e1f2535d5b041a11ff4f0643d460e4897f4f716b51b70a162ac6690fbf21c785568ae09c34e254a48599e525032ddc13558aac92f70fcd623a9d8f169b1c61a483d466c8193671d1f87dd5d1522ceb058ac5e414422606f793c418aedb41f2315ca33f87b3860e14efa51686adf48388e57f10ed7a4616a2355f2df9896d647eecc9e349565f1ea8cb357ad14970af7b3a908722aad749f531ec912b2727f9184c37b45ea645c875386b3fd1191910f0546168b058b4ae52d163ec9e6bc050dd6cfcecaf0d8c112a1a1df6406e61d7ea4ac31aee91fa4b40e570e683420127ce71038af30147c56fe885d77a85f2a2977955fb3ba33be009a84bfc05c6e9baab89c65539b3e6f1a21d0005cd5801a233b080316263c5cd191c4243337baf0aa362ecc69635cdb55147d3ed6f60de57a1c18b134413951f064d54c1e55c3f7f3507fe6aecbab3c219359ab90a91b52daf47b08e4d5f5f4c5d39038097d3d59834301b468db25e405ccce3e80b99d8faf805b408cd140730f3f599b9480a574edb7212042e8067e9098c66f8a8df441ea3f44c44b34636fa5dcdf65935819e24d7745f5f2723621a80ee405f0511509c35720358712c60d01fe78c158ff03c47e10250d76c2461062238266d3f04ea8f96a6887f408b40d72d013d7465c261c3559537181c15d7f6e005ae2b6ea36102b2e922b2096045f4e29800dacc408d2605f79470f511c70c585aea3a93261641b74a022cd0688db96fe8b06fa87b17727aa54c47089bba99a048239c6f1b812feef67b83961b3bbcc4525f508a85742360bb5803071420882ddc15a21ee5625aa108e79f6a862e6a055450b67b5f613448e7684c0a4d28acf661d6d1f83c060ec55285f5aeaaeaefff09fabc14b079c4cfba345adf239803d92739bf8a072dd68f0c48bd63d73615ae500c8e2eacf2552b066985b3ae28d0e22f18402fe7c8d6d3b3795e42a8a20ef38fe88603680227ab281e41d0ee423bdea133c2a786a44e26c97131a96f65748d9d25bc22f074144781a4a3ab1496d2afbd02e25b0160ac9e16c38f9b739206cc89f743263f023cfdeaeef04c1616689f705ea92fcd4b25746114348108b42eab9943e10e8b0fd9910531873069ed831641d668827a0ccc32376d3432872ce0ba6c795f8a22bff81ae1022701c8e12275631f53f72b208f12198189e8c31b1b5e417fd545469f465787992553a2dafad669420ca8141e74b0701a614116a4551e7ddf96454b97bbf7539b23f986be333bd4f06e00621e8116bfdd102ff2c9e4799570d4a45bc5aff239ad900eafc80fdfadfd49722d6e5875494f4124660342a6d9125f226372a549b963c887a11316f5fdbc16b5aa9f3dbdec676e6020d8b1435d45b83c38b3bd926b26a0d6299c5d535601aae8c06d14c0b392369207280d08670d7d92092aeffbbf642aba79e502b960335acdea94ef68c066fa10f4c393aaaece9e4099c0dc3551f5319e9f8b79333ed755f8f1567b37211ba3a28ac61d76ac90f570cff881a568b52019489ce5b47fbfaa3e19ca3c43faffa34aade6b5aac254d85a1f637ca102ca9eba0982138f1c88e85cb9eedca2b525fe1315a1afe535cded21584caecadc760cecf5fa59057b29e56900d80d785600c1b23f7a4294b9b29ca0f10fd867f5e552d93545a925a247d4d7437bb7eedd5875721f5347a4761349f2116e95f4ca15f840f0366a24776c240858b0d6f40a8609cdf9d38b5b61eedfb3ece83535f505cdffb86781ce18fe3d684e09217366f5d82dc5e0afaa4553416c59b73984498cd9cb25b07c24fd179023ed855f6db94f9c490b2d25d08d29ede4823842d40cb7edc49ef8aeb2bc1b678f2d28092431242f133a6c9860d24446e6876992ce09f483a441a103ac32b88e82e9e232b28c82d197190d7aa84d729b86a6a70a249895bd0a290a4e16911a30d593fc82133cfff39c6585c01a3dbc04cf3f146ac4214c17fdc8f203762bec3de92e0acb6cc2c7ca8ce7a66e1b9f9520336c2740463525e266ad800d0fdad458f5a4174d6c472a6225152d2915fdae118461660a5553dbad3f503e4c2ce3d086ff90d3d2fd484fd396c29b1f8b12b2d13c1c4cf6718a3dd54834b50b512d0f8dcfa4beb4812dac18f4fe54057c293cc347931b840fb33e25acfb3af0d96dfef7886eb54309abd6d7e81526828c3e660d54e18bf6daec01a2838a8a21b4b447606be9fa1111b6076d3500fdcaa4c351776594c4a658a2f470503ffe40c0f6a7f612b1609bc8821a882a784aa0d7e2f9f4dd63ec6565a41b3195a085019b763a22966fde21f92a30ac029079c50ad28735ac4dca0331740b1348705070a49b2b4ca3e36a07ed6ffebf4d39776bc06efab487534c7e2cd11b067e5a0909488451fe05a62f11ca5928c5e2bf68b4f0cc156679ba1d49114968374b68d0953224a968edd661e455071436eddd1767d4b7a0392521154e65c3568ee8987a48cb7998939e5a4cc3eb220d7ece2275a47a5ac17619836c62f9f4cd9efe54526cd9844494d643d49cc36a94ad26830572584b929bea680de4cd4f967a0c38ce746f5b164cb00656000ed933160ab75b4e9bfcfa349e40f343f306401fe4e87a11c86abbe8a675422f2f6429e988cb6746a635cf6ae3428ec1ecf569eeb95695f9f669d790b911470e64e40c70f7339ffc6963d1383eac11c55f101575e2ea286ea654f16c2867111ac84b6fac030d3461ca6647f2795190a5200a4a19374ceb03b459bec42e7f3a16bc08a66a1f62cfbdff56f15ea63dd45d861f58cc2695c8db464cc475bf3aa82b1b75d19180f6af55afa9a0e454c0c212534dc8f261b72f4597bb9089462556924a147f82442d9504d5160e6894810c37b4439e03064a10974899eca554cca21499abd099dabe06df339410737729ade21246760475803f44f91b534c7e9bd4071e68e6d2a5f6561a5dc80bf22cfb9b2ac557a9e9e27dd1465e1d6736023644c23a9649f20c14fb8dbb321680019208bf3c3eab32950330a48f604e1d0087c5a6c1c446b02020104d009fc1834392003c0b0514f60d4658a056c9d2b45317040e578d4dfd2b8433d5234efc0a10459cacbc618fc32799d022e502f3b15c8f994a2e67285c959a599d368c62201bb498032898f5fed8fe2aea8f3202444f7b37a83cfc93f8ead3fef1073df103de26ac8cb02c2ec3694f28f13e10380d2a18c5e08013c15f8d8095ca4287eb803f900031c6fe193d0b24d2f947c8631d3ee51a381d5624f809c1eca176fb739f84bee9f594ee02986579590e20c9e2218c87c1a15b7d56b5218a987093945937d1708251323e98709dfe827bb048e012350de6bc511415844663b83198f03e1829e25b722bb46bdd409eba3f9edef8553106e72370b35453be7f0dd0047d4b3e953acf87d39afa4102b06b9dc70611113b72b73b12dbf8a0019b3ef0fece33e8901ffa6952cf7d78ecac0d1f1d1ea1399ac8b9d84a5c597af5cc71903fb62d5c60f61154499729ee919209b3b0d1d6f5cd84e75dfdcb44d2e731e815e3a41099d5e6c1688619b652a9de880f56b70676f131fc7e23a61fd4d4150e7fb858a84dda5283126d0e7dd968bb969dac8dfd7d303f2d2d1574403745f27dcc843821daa97b57d2e9bdbaa4575916bc8b36174e0fc0647ef6e805d6a3a5aae9e23b09a7d515eb483995d1b9fef6e909252e294f78a26092f1f33ea57fe4ef4c462662c5810217070a1013c041720a74ca1e2d803b107f38a6f5ab2a503d616f82af22528054d7707329b46048ca3818a4ceefbee81e49191124e53b9de9c9f5cc474ac03c10a1409c9652256dca760032cff0efb0e3cbd17b0041995ea808869ca49dc21a96cc5dc3947e701b0a20cbbb5f424e2cc2382cd3cf020a849f0a265bf9e018a9aa3c091135956c8aca5c19b1ad6546db60728be9c9068219086b0067f15c25bb2012071c14f41cbbd6bfbe1bf5188c802864971485a4d20d566a260c48c0a6426d86020a0da860b118c0dc1dfb0a0bcca8b63d5068ae891537000365ec38b112eb87228629abf42d9e04a58ee8089eef4a501add9985d444f05202427dc436e8f758b026da800401dc929d680baf9fec5943f8bf949f02beccfc2a0fe08070cd4a3568a17cd73e3a096114ff8d69d2628f8399eb205491267c0b7a509303945e7e456a3eeb476daf204abc338782ab8f16b2bd5bf29af52153140ae3d88315216f2f9fddc4008c5f1628210b50b14c0e715d7c764d17e3726b573ecf9b654e23be5e1780b29069270e29694b54da8806d28cbb3034506e816702a907de7debaf28e4998e0cf9677410ca7c73ef15f3eae635b91bfbd5e2b5a737af6b26b0c0438f2d468859b4b711b382a0e1a8cbc51e29ac186b0db4326db2e71801d335bf8ffcf2977a6495f72b47e1245da1abcbad8fbecefe65ad0e8cb4220cd3675df05f271d5bcb6f6f61a0a7d86649580ceec272836c1f9565673f3af34e589d000bb3df6cc4ba5c636d56b70326fb11bf023e67c0c8dc5ffcfa17cff68ac62b6325b4aafac99de9074badb39997f5c4aafe33ecf34a26857fd6b29307fc4fcddb96213f3ca236b91ef101b1a408848ec55ff6ee617cca0fd23b3a2d3c8dd415eb0c793116bf440cc0e09198ac48f2869cd0560963e491370f8997980c31e7e042c8c5fdd1750d44f565792a7a226ec516cd2252d3ed238d998a0d2c228d2216c8e30ae288824a92f96f770940594b131d6794f617e6c8dc0c7999189b163d891fa8d8fe89107fb2909c8a775fd4251094b3934625bd1ca9466491f8868123c9e5db1af70171031b85a97ef9aaa8acd6b575559b17ddb270c2586da81a0e821493eac16802a5f519dfcab066d0cbd89b117acb896e0e7363da58cf7f5d98065d8cb4b6b0af4eba0c270cc01d65f4ad957ca594864ca263be7a3e942a667483c42f433196d34c833b0c223bae95f38f2757c334a0bc84ea0dfd023f36f13c8cf5c9608c0918f5d9fd3c83aba0cd9d2d38a0f11d9818175ecea764631d73a19a01dae095568eb1402ea4c34f155a5ad3f62e54eb414d7e5851f308cb05a252eab9ff1800cba9dae2f92648f35411d9400d0ecf9e01e11c380813bf0c04beec8ee352019f1c0887f0ff497e71780799d75039fd3419aa5002f9c7ad4cce564e3509af95f4e8f74f1d8eab4d176e4a39a9684ce3bda81e5e68b8e913a43023f15c9f1a363e39488827d3489372262e9a8d71846c231551e689b2aea8f7cc4526218506898ef24fa54a1a482821c515a8f24bfb76c2369c052384d8d2f2f7ab389c35d6c7287e72285a80678875477ba4c406e226e8183184200902697c0e1af68679d33a751f6d466cf5e99010ff297ebb64cc8ea9f6030d079669628c12de40c300679af0031e0ea04338fa1ca475491ac2ab3432c8800ea33b3abfc905747a3ffd337227eb318f401d7adc4220357b7279e04664b957c0de55ec4d02a28ed7eaccac557307c2f8fa3255c9abe5a3e9ce17c94503ad3c54da14f6273dca4038d21a61ec4c4cbb6ba1ca81670ecac103800c16f8843e3a422361f86970d2536859fd622dd12c31be998fa0a3555685dc87408dc28dac423f4be3df8de8661535b4300568856b3b9606f4e63933f958396d07a8411216eb600947fe9d2ffe6b8988bef586c5077b438a687663d70a521202566fa3ab28c6a3f0d394d04a97384c61035694a68e46e505ac35868ac6372a595887ab5bab768e9217bc7fbe2b91682a314808891623fe090adcfe9910ce8fe395e5caafa52ec308aa45d22cab0813177961d8a19c0ac8f0a25cd7016591a479edae81ef683518a2c59cd8a5bad00279bf5f45803ab3fdc0fd47b24955a953696c5cabaa199c8ee7026cdc52ea6b798c218672e3eff8a49ec96500a1f5acabb28ef3f8347cbf3ae97d8b816d863855883cf274b3463ad1cb8479cdebcad143d1a53629651101a4cb06091710b28d0993a483d8022b221f0446b0d624406fc7148196c5bca8ccf4e0afc06154dd4c63799f086dce7691d7add16637fb764006034039e5b3da3a03708857e8c357f5bab9581219127495ebf3c2e4c94c48aba817524e56c6386a95eaa5c2a4644ad055adcbcb939321a1adc2ebd88fc6c8e1b06240bf6a51d009ff2c41066d18045445d27415be5da259918279c7130a00d8956beae30a84aa48840a420a11f87001158775fa0d5b0f976afd34b22aeaa0a564c8d0e8555cdf2d944c06294a95d4e1c2e465d46855a88f96922243a356bd1e2e964486294a5532a9ef9ff129e90961156149a3df2bb1c60bcfc890b5b526a9ffd396d9caceed80a819a0a32af787e49cd82e67189a4595f91716c69f0610f3b02aab39816495a6d7bc566cf313853f8efc8090be10b5acc52cec0f6cdf82ecd1811a069f912957e013aabffdde3ab7f1e313e61642074a6cdfb9dc0b4ecd0a25b1179f49b3ef29d9e4ae6a612a62712845f32b41ee0b36bf82b7cb5523afc52fca785a11dd327249ba683995d41577879b2048386d3e8d6dad75030e05de0016b70d95516e57882902c02d027408fc4538aa546a1f2a3b80779855b9ab744599e6e2560f66db8d0147607010366751de6fbb1cd5b38581d62b7ade9e0b7569179df28284b422e04dd5a835bc043cb86a0a4bbaa092fcc5100191a7f055c0d0e853522ac4fc51a9c14c1b243144ffe8ed6f585a2386ae4a9fa584997e04a9af824c47bb1def7a162c019ea8654a8ef361098d1d70cfc28dce32b853aff356ae0d5ba3e33186ce29710b6ba3d353386f65f7c0a6f3a0f42f05494a2620acaa43dd2389f09ee9a56a06542a018cc7057ebd854c13551048ab8a3ac07fa6f4ae2a9a09cc0496467578e65d6026c6982f070bfc16b7e0d10e1c8aa5dadf52b26e911c7221696937257a03b6fd8febd75878af43e4370dd374aebf43b4a0d704f05065273720a1646bedde296b1593123912488ec48a08d9b9e1f14a751718acb1020f88c101302a4081065c6144114214008b0b6652a2c09003c601c712253b74a8e004228200e289131d290a00c4ee498b972ea8393e3954707c416861915ac077c55785c7f3eda0743c300607f502ca4750e13bc1f3c132015582d723458237c20b0078f040b9523b6474b8664c902921e60498d70babb5627dab954a85f27a850586850f5c61bdb05e523a5a44582f281464987838aa69d9ac605a3d29991d5e4a66354be95033a8570a8d00307165a5e363bda4603328f430e00a4c13193105d3c1c327c4878a49ca5bf968e00a922542af007c2998d4929b1f5c89410f2e98c183a78abd548881872f65020a87f4687e542f90e0ca6aa5421216117600f980928107015c29e1c5894c083221541912816c61181f5cf1c92f32d08c70b15e5033aaa6d40082e0a195d2b142c1db1184071948f1e24209b0b4de5042c717a38ae1c5329252f250b6f40000950dcc104c0bab28291758467a98401432f4b080214670e96099099710e08b2b31a5ad8900ccf0ecf09a9c803ac22a81080f302c232b98960e549122195028c00cf9e00a0cab6866a945e423c910cc0b91cc92e833812b24a4604460563100ccac606292d4dca0802b302da2146a405c51a15c2a6f45c30a467564e5c20f504cf902a445b48261953023f4f22375f442420827b460c2082eb45e50a2d4020f302d901564264891a224964c8a65822a8622f2229302e231b05e5830a91c9592544f0c9eca078a463582ce4b2b44194972a4470b668497223c041dc50869d9a47ea47eac1670c5051d2921d50baa145e7ea44ed8b18342a1872b608e87b2a1705228150e35371c71015bb76080195cb66cb1802908b0430e4fb8c9f621ed8628363c69c2642969c90c44b19e20362db0b082a738004697366c2003187e58a831a1049616108c518502684802430c05116462565f7841012daca8628a0444a1e50926364072c02698605aea12070f6a36b481030ccceccb8821a8ede7080ccb8a228828f2720234aeb0226684080921c4c0b45827f8904004a2bc88743443d1509298909d1b1e2f1d33290408310001f050638805b660c0027670d2a408911e21a44260c60e9afc2021f2c25a35e0070a38408a248e0002072810808021908e7e8e0091f1e04005e17b23e586e3e063232583af052a353c147c26f84af042029933be107c657c5e7c0d406df135a5a6783f3c155a28a84e9829e123e113c10be10500158fefe5b9523abe1815ccf7d26a7d2c96ea4b7928cf5b354c9be02a2188eeb13673a597cc79d186b4961eaa045a97a083a9f798a347105c3d6cd04df35ba58fd3f98d23f1711b243e5ee9ed6b3db874f7aa5d3d28e0eae100578f205c3d78e86e1acd6dd66fdcc6fd458a2b754c3d533fb2bbf34e5ce8937aded5a4802008450c57436c5440ae1e32f7739e64cecdb853a8af3dd708628cc045a519bfe89e6d75fc56f41b9b0f6f69f108498c70a5c5fa793a1e8126d6778d008e7082e3f2adfee836eb1fd211fbb5b63b33f6bfd5795f4278f1f1e5cd8fc7696371408556abd0ad42438747eeee9c6e1c1a18d9f11fbfb70c5d3062a4c7481123448c0c3122c44810233c46768ce81831d2d3d353a48748cf901e213d417a787a767a747a8c14e92952a4089122438a082912a4084f919d223a458c10e9215284081122438808211284080f911d223a448c0ce919526408912143860819126408cf909d213a438c08e9115244081121438408111244088f901d213a428c04e9095224089120438208091224084f909d203a418cf0f4f014e121c2338447084f101e1e9e1d1e1d1e233b3d3b457688ec0cd911b21364876767674767c7884e8f4e111d223a437484e804d1e1d1d9d1d1d1e9a6ddeda35d2114c995f549474a77c3606030363639780e3030181cdc618e83bbbb77f70bdded4277df0001801a1ee3628a6e8fb900d003009a5c0080d21e83f158b6fde80e43cbb81bbdeed9f6230d3f2ecc338784060992eeb6010280135c3ce2e8764c735973c7f471b9bb5b60a11b26e679baf84d21ad93e40225964ba36bef4da2b3f75a201e43743708f2a0b978c8e0e211c3cae7e6dba1fef834733ab64a8fe6d10b385e58a0bb8144d10112a5e63f2e45078aadac5da0bb5768d74b1482f39d24a8038e3143f7d7c6f7823454e0ade01b3f225f90234c78a0553d6841f712495ce91d4031b4f783966e20ae6e141bdd2d7a3736bb4ce67e8629682b6b345fed11cdaba59944ba62015864a10503b6b8d921e23e3ebe63e33eee2f780c33f67e15dfd5fc45ea4445b7d93399fb9ca70793437783eddad12a31bdb725a91d3548020395022cd09d4008d5030494900223b878818f2b7c5028382308094f381144173221282e42dc9cc8b941a8cb1030141518a059230654162ac0f98102e28a90921baa5451860f27a0c82c6cfc04b5233cb80901c54406ae26e088c20999276450479a704d01831914b173e3a5506e860a6db814e0040b2c0ed40a082c84682302313861020fbe1a54c069c9922aa61072d9e033038e26ac9c608a2a847cd6f82260c50942b014e5050f48228daf09176881401a4aa8a0133be3e36189116cd8010e344b2c18e3b38de164343182284f547cf12dc912c70e48d0418dcc055e7c3b124803034b4c33b800195c7c23f880034cdc042c0145942cbc38a8684203425260240816a6f05ab08221282c70002db31738e175c0002e701125084a687900125e16610c5902238c24a42009c233e207283f1801a509303480c5d371c1b446143b15f4d059f1a2e8e0e509072e7268f2217b4052e412700026dad0cd307a4050e0e14034812c000d446fc608230a53800f193ce9d6c1175250000210151498606a3402d08196102660f129e8b6c096157c4f1061686185504b7183508a9d33be8003884f67810209480b243051934448e74820829e29b098c1151cac9786d081870c1c681085eceee1a204193888c112e101dd29f0008391a7379078c1d28dd2e2a90660645193e9c2061b6750e1ea42861331c4d1e501219000094f5c50c2c378d0e500417831c68b4dca32860dbae8e0050c5aa2112f136ab04697500e0140400701680fe848a34bd1027efc3800c5468ce08c2ee01a515c5fb8f0c291d5185d4238000c2ee0801938ec90c517730001045b5860f23b3b11f0620e1610a0e5c61b18d821421317737c310121f28821ac68a28c2ce6b8e28d2b666564c9392031c51c432880051680a20b18743184137364160041f783bcd1c10248cc614312473e045104055a6012c41c3088e145179a900ce82c6199830501d466aad1ca0c40589903060210d0c28a0574b9d24596a38d1db8e08d2356b0811cb430ca41820460617b323343810f44392890822cc61f39d4a8c012450e2d4734414146055810230926397c00430542404c18010828b0448e262c619cc102117000862342722439a0e706870b431c20848f1c43da1002b304092e6401032172f8d0f24319205002034af00026479b20fbf001167a8875e0471c312022091ad81ea225904e88e3031a702141c4088e26454288a3013df8f0d8498900194e64e248820163141511c3d352ae4b2a0e1c68e0448d6e051c00f18138e2984f50c962082d142006047810c7d008688862b3c3848900d8200e5801cc50410b227688c3678d385e176852441bb2a612c8a401870fbae81a10c705bc4803e90c38d2c0829c83840e30c1010f7477cf31061c1988810965a09098908405303132339e0e4f89f70168ef47b747a574aa54a7e33f8ecb917c1a4950b411c5b9bbb1f99cc5334cc11b5c569a03459b5a8967e237858f0bc15c670e14bbca1253910c454233198a96664f64468ee4c76f6cea8fa0adac11e9f6bc0e4206ba3ba75d2c2d8d2f58ed6d5e316777fb3c6fc639e18b3c79b4a9967e304ba30d3278506f90c695343af003265ed8e8e081d006096330c15cc22e4c3a7c804660030a2210f9a105076c5b5a7000954a0203a0b1a38c192ad9b482240ef010214700689c008a7b81110800ca12a015afbfa319a8d0024a01417628c10b106c41c117a9345a2328a307365821af91b343841e39502c38c00b68801faf7b20a1660003107880822a94b841edb9818881292a789dc2014e4e06c5872cdae061851f746033d0ad1b8244c7d44003edede074a780e54aa36662e83ef26e9619dd1dc6b744c791fcf8cce38bef313c8fba9ba65d2b9fced38eb6d98e8a39b9b2e2ee66d22e15096a74ccd7d6e617dd1dc5a5c26de97e9aa25b6789ee1c44f78d72a5bb4651e5eeae71a970dd35b5ee12ba8bba53af14aa5b846e0074bf3c275244ae5416a92d1ec3622d6dcd6dd67369dde619121fef6e5777efe86e1d8d2f2804dcc1c19f77281b34aa043d8e33b7642dd37d9bad04f2480f4b773b69172aa9f1055fb4e18b3ae0105007a76708aee5d876747486eccc5a8d87472cb26323826329883fcf1b82bb9b31bdfdcdb1b73b4330e7ed949ccf595c4eaa3ba63b498f2d33c9d93bd4dd30ad20df199de94862a7e5491ba15d9f77378f767d56748fd3c9e6feaab47c455c1f91ee26a15ddfadbb4368d7b7d48de4e9c54aefe3f09d6e1c531baf3fba8f7fcdc5b1b438ec37ddcdd3edd5ee2ea15d1e0fba1b5f30ac3bfcf2e5389d9c766f129ad7e9d53aedc6869c5757cb582c8f257e259a8b3f8e33ec6eeffe6adddda35d1e11f88239676b75b7d7acbef6def73a5dfc7acbfc37d7cffd48e6a8673a6ff65ba539cfdcb5369f4108d1dd26b4abd570793bba5b84767510c41fe9b439febcabb9dbac574bf3f4ddb5b9cfe10b7e2374f709edea25bafbd5aee6a2bb7576b4dd77b2becb349ce40cf105c3779ae9e60ded38ceee9ee9f68674b79276b54d77cbb4ab71e3db9d2ed2713a558a2f28da5c4eced972c28b73feda306777e7add1312748109b4dc8dcd121b213f64c51a74891504787088f91da2cb25364c810233d44848893089130cc09332e6f4e77afba5b854a75f7f7620024629acb952bb246bd2ce1464c4c0b55c48a151d3e47b90e9ea7dc8a164c2c87712b58b23890ca7f70201d5ebc5563e5070752d558d1e1a546e5ab1a1d56deaac9e2402867d5e8b0aa51d5a0bc5d879557635678a2891fbc1f5258b278fe43aa6605265670c2f5794c0cca6350aa9a6e1c14ca5312d329d5d8c2d2030f9f7f290f0231ed791e8b0a2a954ab15e522994b75aeea1a6a0529ee72914eab332c5f317f752569c3e6f6594b7bc856a5961b558fead584dab1a2a56642f354daa9ad6e74dab96a93e6f47b53cd59698f63cef73d49496bff81713a37af156d5dc9c72fa7ce59ea2a1d243065343c50a951eb2554d461179b2164af6523325e55e0d152b32564d534c4caf6aa8a0da95ec907d9f065a9d5a4189cff3068a91a1f95c4606957a79c192729917189a558d8f2196af9a583555ec3e5fd554b143390cb294a3645a340df47d2b18f71c8686064a6169e510e35eea8ba1f901062094c3d060c9e201a1fc07188052de32322f341ed0f7a55a3557b0bc78160782f19603c1b8175373058525071d500e5373a506cb3763830c4d0e3aa0fca5e60aca3f9f6142390c4d160f08c65f68b278402f1e4383e5071880603c8b0714e3311f8d8ca76692f8a021e5326e03cabd9a1c62b0b49cec00e4790e30fec5d0b850372a6faddc035a418b8b4a7791ad6a9aaad87d5ec5ce9b49fadc9bf14143ca3f674a3a408ce7d164d1820a0c595258b2a46ab4a002c30f5a508141e639694ff683e64546d56abdb0584ed3c4f2542aa663542df7427871c1786be579355358ae43870e1a1fab19a41797a199f198989729ac2196affc4545a3c3673c660aab66c7caf31496ab643b689a665cc6a7d4f858394b87ebf0d48fcfe6b3f96c666a56ee8355b3e4b3f9a6fc904df9e1b5ac084c4648083e5fb454fe990163444c7f62207145d6621c81841846b0218601b08881c41a44d44cb191b5ac9b3cff5056c4f48f29ad560c0af5c547841546cb6352334abc961779aa968cea93ad56eeaddc6375610697547b9e122f0c559107e3791e4c8debb3a1f964de8c92160b65c5125150ce4243c66279cb882e642a5fbd78bc5efee30400cc0c795c64ac9a293245627c4a4aa652d54c89a1c1a2c514956bd1e49a51e2f94c91ea45f5523385f5c96240c9f4f8a1e343c4441b9fb010e184159f2ce53121c888f0cdc82c21f3d21a4146e55fcba728f1c430220c19caa8a8e5fa6c5a2c57d5a4be14ca9b01c23714d35f0cea5badc420c20d99ea88eb738fa8a918c20c59bbc745d6be65881da9ee98938f948f54cdaadda36179d328f1645e4ec9da3bf53263543365071fa9f6dee193a56a3ae6a9bcd58cd1cadb61aa9c62b11ce5ac1923cf68ca0e3216ecb3694fa64a79a9540d4cd66a21799ea3dca301ab5f7c0aca539ea24175b74a05e3a81a989ac641ad685a4d2823940b71e5fb6c56ee4365b3436aca8f0f95002c76789e5a018b0be5dda4f2ecc958be429694aa632f9ef256938c0cc65f563035533cd9276bad9ce52bcf599e17842c6fc858de6243d6b2d619b214b8f0ecc9647c0a8bf5e2c3f3585eea25c6635a342a67c9c0cc2461b17cb0bcdd07cb572c19562b0686957216ea05e5949279a919a396b36a7c78351ecbf356abc99b82721f2d1fad9afe6c542c160be5ed308fe5ac1a9b15b0b8a8ec90799eafdc80a12163392b0c59cb5a46802e562b189f924a85f0d97c5e8dcbe5ad687c781ee3a53c9ff1d54c12191d3b644e2959cb734aa63aa1d5a3474c2a5533c5f31957cd0c799e8edb38333234543cd914568d0f1b550dcc8bbff4149895a76052291f29af79a9691ccf26e523c54ac192b08c18c01bb28e81c5f848f9141b94fbf052354a6c64ae83cc7316cbf396121b549337057bee4d59d550f1642a1aff6a609f4da308c086ac46d6620ce00b59cc0e19da734ac9a8ec90f98b7b49582c1f2bf77cac5c65236bf9cb94940ce623e59eb36aa6ece02355a3c44666234ba16a3e17c4c060620c800b594cd66264794386c414b297acc5408208d945f940a16a503925f37cca4ab5c2112e4fd63e54de3e54aeaa5162234bd5d8c852b0c669ffb650797f2d9781f5c9d0352b18f76a5031482aef1a274fb6fa607cb46a58354dab26d4e7c1f132b45acdcc7c543c59cbc7cc4bfb0c0d15cfe805c56a7d33a81a252bef9a299ecc837d21884165614316e3ad94f750f6649ed7c8e1427d36433e5e609cd5dd2e43c5336ad53879d7a06a863c5f790ca8f6f1e22d67394c8dcdaa3f30623aca87f4791d6b259fb725a6a570d99192e9d8f7b50362fa23d2848aa5541f52ca511f8dd31644c8509e12c01519ea073180786307aa89e5452e19fe3c268615b3638aca53a89aa69aa618a749cd14c5f8e7312a2a3b645885f4798ca7689c3c27afe5df8c0c312c7f711f543c59bb534ae639951d32cf613c3583b472568d9387a472afc6c99375cd14547f36354d29cf9eac4655931aa2e2c9520a889272961a5152de72317e3003d5a84635aa6d5a898d0c266b99b042015c543cd9e72894c7983025e528a749f92ab59a414a79cb5533452b57358a4649cabf1a272f49aae846e6d5344d4915adbce5a8192a9e932753b9aa4955d3ca9e2ca56243e5a9cf937d2e2313c209ed2b18e19aa27298ec734f89aa26e5e465e026543e928b6e3017d23c733697b34ec7dd4a7b7159c4319155c81f187f188a75e289731b014beb3c8db4cbcad40854712c71fe9b58e6ca1c7d9a397f5cbe8f694fbbea2b1746778337f5da9a8d678a9940598e003921a31a0dd26e1c261639cc86f6b7cc4e13574b5b29fad78ed0c67106d17c964e36376e313e69701c67682b6df64d33d39a669d630fdd57bf8ebb379a6b7a4193104d3d0de6d2ba78adc3f20ce94896a24f9aa9c3ee3b7dcd3337bfe6bb997370d2e09af2c61429a620d12eb48b8b66419f3428bef85f6bf6928e697da77adfc9de7c9c659d460e7ecdc791ea76638d626c497f5cc6a5bd7fab53bcc9a5fd1ccd36fbb5f492e2fc7bab3e6d6ae9983aa638267037e2d252a7bfe4ae56a98b1fce91b44ef1d4d5e9369b045703174e095c0cdd60b564395dacd36698c3eee739bae34a670eb4f7e9fd928af84ef4c3bfb8daddfd924a129c0ed7ad8b90daece293a4bdb870ba3871b29b95ee6e72de5ca7f8393ac730676f4cb714786314f484fa259f3e53f006be4dcdc6c54676d73e9c24769bf5a6708e3e1d9313773f09acd46fc007a98d7f8931fd9cc58ea78ec9d613fef5c731bd1b4e95fad7c417a993bda4d36844ed7a2da0f8f92ff9634d7c5c3acd1fc7f43174378576c4f773e538d28cfdef14ffc3b0c4b5f4c731896c5c6bbbd1bfef6431b5799de1f4c7318944f4e7aecdd41f97453ac992ca5f1ada25ea602bba4234ba3bbbc509b5743788818444dd0d66fc6fbb1617d689f337c85563a3c6a5c14a8d829ef83843bbc4557bea4a73f7f38b0eab25e5bf61b5214eb5b526d6d2da424be2c7e5a66c737f67175d7ace5e9bfb743c719ea97f5829f6c731cda7679ab3d9ea6e3053b7f956692eed759bf5ddd439a6fef771d91f9733fc9aefc671deaf7f73189e3af12fc52ffae398686680d5baefeacce189a359d10dde2a15eb63172d09acd62d0e539deff2384e1baf3a5b75d61fc744b301737dec5f739bdd1fc734ebc1fd3c525bb5d4fd714c3fb5ddccfdfcdcd88cd6d6f0cfcf38cea01fc7d3997c7c72e1934c7374c9ec47e6f3648172e193dd9d34a4b6af5f7b9ab91ff19bf83c59a08c5fe40952e63ed3d9bfed862d627d7cc575c30d3dc672da5e94c246d727718f764909039442fb5c86fff42e29596ae9929274df36135f8a774ba1e5c2a7286c342885b6b3d16cebec6bf486b856e2d987d7daa27001a5d0707e8ce72c5f4bfef839164ba241f1ffa5f0451be469e18ae2c3029433a05c00ca13dd8ec4c73d46a379c693f40feb7412eb0c97d03c96465068da86271b741e574be257ca854fba5b8b2cb040a55aa80fc5fa58ab5b430b4f9d8fe30c5de67ad2850ced7a829b389f5e69ee33f51f6f956297b99ee8743788cb9acb890ebabb6d157982944e22e06482e3f83a3c1d56df89defc56a9dbac3bb81bc7d2e6a14845c7ef36eb5826fe50ee7351ee7311c665eae4473267833ed7225c98275013168c3497f72669424ac294c7123719e26952653b4d72fddc6e92b3c7f3c75762c24433e961a2418319ffed5662cfb81bcdf993bb3c1f97618fcbf573e5ed4e7f5c7ef1718e27ce5df7474bba3f627a40dbace7cafa4ea3f9e398307e9ceb3b25617a619769ae2fd21991213cff17c7d434c3f0951019c233714b4486f0581dd337cd118827886e2aed04d14d259d20baa964844737957a787453a9088f6e2a11e1d14da5213cbaa9248447379582f0e8a6120f8f6e2ae9f0e8a692911ddd54ead9d14da5223bbaa9446447379586ece8a692901ddd540ab2a39b4a3c3bbaa9b4b3a39b4a467453a94737958ae8a61211dd541aa29b4a42745329886e2af1e8a6d28e6e2ae9e8a6d2577ba4a48612a9c414831e9e9d12dfe638346f7e4a328eb21053a3ff21fce42ec762b836c3d5e2f94a34ecbc921ad0f882949cb3cfd3895e7236e30185803a38373bfe2df37c9bd3702d674e4d34f1c7daac4f2fceee9bbadb87eec6d2dd01e8c106a4335a372f0f186407d4c1a93a7b6b383c71485b88da8534c5add29b976624fedcac41b198134f90f267769f7c9ab9fb34733f3e4f33f7b3c7b3f6c313a4f4799ab99c65aa4741f793702cb6c4e3c26524a43b877faf0d3ddb8add6dd49fde31f5c731f911daecda194d7c1ca695dafcbe38d212085f10f370a5dbda6a9152c67309cdb41655a1e3ac7428a4b8d29b9f92ec469294e1894bba9fd43126b896c470c4a5c48c8cbfceee673acbd96b9b854fe29cf09d9488ef741fbfd2ff1d0a31cdc562b87e93cd659e79e9c33ac709d4f882e374cdd04483200824ff08cbf3efcdb33d7210fb38e75886e30c6f958ab58c8deff4e32c9683c31357cb582c162bba81aba8064526e8a6798ce679ba8b8fc313576480ced7e2b05b9b638fef3bfd2dc217c4e189cb71aa96e2fb4dbabbccdde2f8b486fb4e48e25726992cefb54143e2048ac5f0b5f72649b2b65a746d9e629d46e3bc4f228d3377ed512c96af25c912cf3e37af0def6652db23fc35e0dd4cfab7cdf06e9218cf3108e3398632883114c3d00f2d494ee88010946e1c9e44954e5cd0cfcf8f4ff84d9662b1a737cf483cf314e96ce62984ab45379c13ca7396ebe35a7424cec711cd664e969c3dcd9cec067d4444c429169b790a899f31c5f7939a28619a790a89f3898e9a423b02f104297f5eb4414d7c7e7cc26ff2c3c427fc26d6568bc6d10689f3714d77e6eee7a99b91b95b75569c898ff187f692d5d299eeeb3873b555e8b538fcb99978a78e2cc5a2bf4ff479defede6eb2fbb88c74a340d63205d9a3712c877e7a4a9ff09bc462e13bc56262c63312cf3c45b168f6d786b3277397080623c659563a7b9ab919990b9f76337c7176673a27bad9e74afc97b457e409528aa3c5e1bfb83cc37324b253ce32bed4365a5bcb983efd0c8fb3de28cef52949c3bf3f3434be600fb803eae0e084ef346b9aaf8bc5669fa3b99617490f2e243780e2e7f049fcf7491b62afd4afcde13bb9f804b8a1bb6dedf2217dd4e86e6cef57f9519c3fbacd8e33ad79ae4f436ab395d8c5cf368ac3e1247336240206c1c1b9414a94eef04816420c31221647cf3cf148677e21329748e28bf5495c2e3521f5add2b1469762b15cf854e98d5e24231ac06591eac43a81c4c74cfa477c92a437e3580e4f9d4fb74113eb93e4a4392e539ddfa7e39defe27bfdaf3906d207e8ee27edeab1025f30d7cfdd1ecfc417cb31acb496b93efe4be232e74ee62ecec5bf730c5fcc3447c727ba0a9537ba5bb4e1ec455bf13bd14c73f49239b512a3808d22567a4929085711978b4817dd4d8393bcfeebf2d7fcf1fc1b56eabbd17d07ad03223e35f1e9cd3424211d75b7d192ffb911ff26d25b93d9f884df0487333afbf24b2a33118727512c26cec7cd422ab919959cf85528be3f9fa7483271985834ef4cf2b9556a6fb9b4333277713332372373e56c9cd9669e20e5ec86730e1092a4c591963a3b200f8eb5558af3e30cb1f8f546319eb83c939040f173a6b99c0dbd19c0a670d66c9da1e789cb3e719d377b7dd2ba3df2826411e7e392be204bbaefd772655d7503695710990652f15bd1b3b5d7ff36afcd7892aec3b344e30be6348576accd3e4f9c1c9c93d324629c6cc5d9345a326797e99d24981fd34c9fde9374b792ee9ee1a905d0dd35b46b2707eda5d0307437107672774bf191f0d5a003037cc1cff3469d666da2849c46ff9709a889165a701d4742e3f9b1cd66552db4d08223f971b1beaa4893ee5405525aa404900a42a13b059492754a882ba7e75a9ce3ca39a18bbafb849eb50bcce2c75a8dd6cf0c1788ebf6d806da85e344efeaf43a7139d7bf6fb351d1a7bfd3c4b9cd8ec5ffeab890e2740e739b2b15a71dc5cf617933ae36f4dd58ad630a4eb7591c2e7d5773f1fd6b9e6df53ac3eea01076e253f123394f9f798a8e7753374e171f535c76b14ec7e189f349fb729c4d6231f1ab0ee9c55ba5b87086fe77fa3be96ea0ee96c5d03d040ba2f105c9b2c9dab18707d4c1a989384f6f76c19ada05fb76c168ab5c1fd76442366b6f92389f68de99d4dd48ba9390b94bf4f4622d1abfd6890bc2f8efad1691b94b24ce270aa91851c909dda8adda207a337e25713e4e66657871de1bc924db71699ce27ca5eef639d2bb9b6b1271be122ecc3387a951b65669e9f3cc4bb74a6f13e337ca1687c959f1ecf3cc78b69be3df582cf719eff0acd2890be79de42cff384e7286cbb1cc9561d0add24a67ee95befc92065c982789a9d06e8eaf74032a00900609609062063010c38a13f8401161b5430b02302f0894518457a0ca57771081137c7841172cecf061c901688898e131c0135c7081a3831e49f42083045708e9200226022059c18a6685ee1e61bb5ca035bee0ee9b66e30cad028674f78d9476dd98d142c01d700767acf652a7fa428288efbbb1367399f39d38ff8ede1fe9beaa9ac8a14681c190fc20f19b2791da64db2f89295da46cfb25eeee350a0efe24d2d9dfc49f3585f3e6556bf5b2322206121f7fc103b9963674243f5e7f9c65db8f3371b42a20a054904a23e504eb848c45478224bcb8f6d30349bb5af0ee7ed156f7d88b36f41f6b1317cebfb574f1efe7fa1e1b67bd9f2b7562c7716ce3634782c433ee461f07fe4d7c2d4374379476b120d61a988a826449b220f7f12791d2daf5e30c7cc1ddcdf57386ba594c74b70dedfa31055f909cb99cff9b6b39b85a9ac31f471b76f3dce838bf0ac5b9b4bbb9a4ddb8426b568ed5a94e21f19aa5c513552f8fd53b3cd5089e0eafc5932ad228245229b0be564b9542795e93f081fdb547430a06af594754bd63a55209f962bef63c0ff511791ecbfb3c0fb5aaf2799fc7fa4650f2bc95c7eacf9b0151df6a6503e35eeafb52447829cffb60bc1e7cde4babbcd4877acf9bf279ded762f2f9f8bc6f95fabc1a7c9eb7fa3c1a3c123c0ff5b536a042b174782318f94468a1bc27bccf5b7d9ef7a166de97827911f2c9783f2d4ff50385c4637d5f6a0705f37ddecd0e221e0ea99d982154d2d7a9fea8f052289b94eaf350ad3280e702afbf76b95cedb597f23e0f075eeb73c1f33ed5f7a13c2f8897e3b53c99effbbe559007e4fb529fa78302634a68b5e0cdf030e153c2537d2578290fe5f597f4398c97ea8faeda695e01504db301af63e0c087f2565e8ae5cd78a825beaf1543793d28effb52fec9bcd8782acf63e9a47654d0f13e9855eb63795eccfbbe14a056decb97423979355fe979281cef25e6f35a2cef8be14be14be14301d5a8f67678abeff366522e54ea2be1fb3e6fe5a56cdecb0f2f46f5792a4f47111f5f112be6e5f36a2d19d6e7d1e0b53c0ff579298fc843e133f29df0c57cac4fe5a1bccffbbc9887c217f3b1521e4a85f2bc221ecab3f15e3c19cff38e78297c302896d74a799fe7c53c6f35c2178297fa3c55ca33f2c578a82f050f85fabed497f28c7c9f67c207e3bd78add4e7795ecc43e18361a1bc542be5795e11f77ab8d0f241bda0f250de8b97eae1bdbcc462a0f848c15b21e57ddfea63bde712412786c8f33eeff33c0fa7594db60d3b786ed48083f61aa260c38c1a68d6b08193e902063069811acd4483825e8a2204bf34cb68a528bc784289c622d110c5143792a69018964491c20e88e6093a46a2e8916364eac0a2480b667e14a181f188b4c0938308ad0b0288f81c0dd2e048018766c50753430116e63a2ebca005d6860a349751c149e0d15a0020a569aa22da5301e8467577efcc6c9e91e5acbb757a85cfd9a5aff648c77ffc3e2e5b21eee338d31b156bb6b6974577a7d02e15d0b8d15a4eaeacdd3515ace86e1cca28f8824fce10bfedc5b0368df41421324448109e1d9d28dd492e1592f005679ee26c6607ca07ea02575205b8925ab272c13d560f787029e08a4a25c36a01053ca4a2d84991203305162b025c492d791901e6841e01b8f25243ca06540a317925d31a6af5ac6050477ac0a83e70a5d5c59510a2f0f05204f5840e64587045d5e58a4ac684125c91c93517b8f2d243c7c73aa2ca5121914584981294e82115c4951d343b685038af9c568f57971e542fb47a583ea81d289fd6145750463b392b9855165750312b1352b0981d94979249c16086542fb06a5899b082594d9109412684971e2f23b06a40c9d1c3ca04d4961e54615c61614a2ae733165048c56476ac5e290f85830a4a25c990b0fa584e583e58472a117478ab18942a258342a13e140bd542bdc09c9042b198b078ec488279b1562c0fc54a0949e5a4bc5609281f3cbca8a0c2e28a8e2c7878ad523e5a423da458d043cdccbbcb1c3238011a2130438c30cadb5211442401820149115bdbc0410e10d78e160b8a0d48475dc83843042a0ca1b384123130c1021394d1010c48608b2a72c0169228f310f8f620076ea4418233c080c00e3adc10c586274847320ca9900209237851811db660800e39cc5038c1561574b06005547257baccf1031f448142c2082f1374800319c0800422d080218418c0ec861a9c201d15954106074060812588528082323a0006062ea0802980f021003970c30424c0c005bc7000962b2f162579cdb80183355820061815a00017571451801767374481e2a428c991d8cd6ba60c32c478a28922b0bc78c30c4543498e340cd628830c0f70a00214e0a201571821041629331405e1c5507477cb43c0d211521153d0119319664f644638d656697baf1695576b763739afeec55a839c57e736fb8b05820ec23309ff0f55264a348c740e19312d053d49cecb84eb374da6c7f7c59df8473b71e6fa4bb2d78a05150fbac5faf8950283a92828dba3c74c6a91584b3b94ed51536897e437caf608ff0f2d65dc14da253abb84299748e2d7ec9138c509b4abd3c85aa657fab1468bb23d121a6bb332117f48acbfc4e4853aa1bb73e1d3eb9ba3bb57fdfadec02f8ab0e46f77e24226affe21471a7390a00b1a75fed0a54a17dc258c397698155d5d5273c031c70d6230070bbafd49a438ff3687c1dc610ef3d9675be7c8c196930b9f70e440a25bc542374dd348367577aeef2452db24288e1f76f8012d8ea7386e71cce2508a23280e24dd2d8e96088e379a964b92f64ce078a2bf5a1bc286e0a8010e9deebe51c734579887b19c1c9c8373e15369ab45b2794b992c173e0902a2db23c000babd2cdd9e007ef0a1dbc3e205c0eb8187eef67eb0447b3f00a24bd0860c2698800e0c38bafbab512436072f388206dd8daa8154c282042174d94177a7ac90e28a240054208c22ddedc1600414242206f08417dded015103156eaeca078beefe9624810106400001e207dd8d1261083370a0c4125e88a0bb574c18e00def8cd81c3174f7b7460f8ab01287122f28d2dda92668ac03e03e308505dded9d51e301234a01a8a800c78636b477f623699f9cb79ca0110669341fc79aa758a9cd6d762934bc4483e230dacc9ffb6b83c025da5f1b043ef8202565e33883c05c9295d2fe872c138dcce5bfcdda2a05df06734aca9270f966f36cf66fdbdd313f9594947d58ed11f838269066b3fe3228cd66e0b536d7e59196b519e4f7b38d49fcdb6d3281bfab937e8ee6dcb5b6fb24a69846c3f86b89fd9b66aeb4b5e9a2d73b7fb496fc26eb54a9c5e57f7fcaf76f0ea7cd792c276c7a4df25b8af5ede8367b2e67c3bf450ec3239d36f75c922ed6398ea5ed45eabb3abd5a3c05fc4ba3f9cc45ef629d34d79971a5d331a6d1829c92b2794b19386f391d97a9ce62fce1572f4a92f92cdb1183e2e312e75966d86e9cb794f9b53611dc653a4ed129399de64de1e3823068b3efee7402871cc4147f9d94943941022939fd7ed3b5ba99cd66c1263cb3cdb6e42025e7ee9bfc05d7cd4b47bcfba6d90d0ce9582b49173dc91259d052b561bd657e2507b1d37a6cb9a4b948c7af89ffe0a44e2e3eb6e4d3fbee9b1e8fe3b4363bfdb30c2efee31c601086192eb8010656c4cf65a64e60d083abce56256bd8a09bac4c320de9483e2559238b168bb02ca4a465cabf7499d6105ba433697ca75f425aa63558c04b3a3b8eb21714e00540bc40872f9873edbb608aee98633c497c419c5cf894930b9f726e95de66c6f83ebd77d65c806a410f5a0083168c819fe6fba4bda4c33ce6308f6198c7829c7cd3646ac196ce854ff2539279cb696db568a9053a8d8db420a5a459e8283a8a1acddb1df162f06ed4e8b103eae0646b2fced39b3d5aca9bc9783648896203141b6ab0a186274ebc26292f860469a8c0ca0aae7437181e79cc63d2ed31757b4bdd9e12a6b91c8c694e05600c3d939087a4caee49e754667ca990b6e69aa650dccd861a9e3869c28469498986a423a3254a6620926128895010902c07c4817579c1851b1b202db0f063851a9a2eae420a289ce0c384127a903042972e5d6c69d46033b9bbe1113478496984d0dd3ce814a0414bc1165aba1bdb7acb140c718546734c73290853c0d4ed308f257523a1e08c06230e241e92062630861702cf8c1390d17d63e33a81044eb044b7fbb8833f8a7456ad5f6b433ac188c53b75450071029dee25345ed00de289f35c27bd38681c6750aebf143e41e30a5149264e20348ebac16bef7d9a46a0141a990b9fd0a0bc68b3599c17e7cdbe9b4d56474767c7c6adc50539f821cdb6e6d2e6bff96ee6e8b4e1af338f658ee29affdb1ecf9a3f99c369c346414ffc56297d9c1307439a9b7527da9bab0d71953998e9d451fff2666b6bfebb4c71269ea157ea47be5acfd32bf56b6d0e0e09326408115b5d67efcd71ae74e2208de6b42624daacb33e2e3becad2e1739683de339667ce9add29c0dbfe6365ba9e3545bef8fe3cc61d8e799a3193fce7d272bd6a9b3b5e9ce1c2eac5ff3263629968409e9c841eb7faded2f4d7c9adb69c76cf1ee9bee938ec591d4d1c86964cbb852da38ca1c1401acbb8f84808deebe61a41078e1e1a07139e5c80c43e0138256dfd2e6478e886f460accf802bc557a0453370389aab3b5a93231230a3e3203d695491971883f8a75e26a59b32350195e3478e49becad796efecdd89d9681a5db63e263260ee2a0275ebf512118227419a9a681a14ea71f5f8996ff46c6183895c664382a3a52923921c389eefedb246d4ec695ee2532bebbff36491a17193246414fdc7735dfdd77a2e38bdf14522610a8d17d63d36473e1530b36b74a73e153838e0be77bb5cec4412cfebc74920ed2dee6b52636699c8f6df4be93db2c8ef6d2d7792e6b78baa53aecafcb99debf5ea94fc7ef747fc9c7a8e9c6417090582d10168bb247458907ace8fec095ee929c8ff3d8071ae3c2cf391b823939314ce68cf37c9df423e2dfbfdde8cdb77c278aebdb5ef4e9011c808186c7709aac05fa2a74e936939698c2778ac576dfd4ed1d898182ee1edf7764f0b74ddced1905f9b07e49e5b1cf6e6f098f7f158a47b2cc3e69c4e8d1f882f7c9f171b3fb334cf1d7bf794a40f1a938d874adce6d763cc56df6fb380c0a74b7c78e90966640b9c2d0f198cdbeeb6e59bbbe784007cee886814643f34afd56edd5b9c3f0d3a954ca3ac0a541daac5ada63d0051df841b38657e41175b7ec691ac5624c18ff981f73400bf8b76aebad68de72de727280724086ee0e6b79b3c3f0d39c9fbe82c186a7058c2b68d98e4b4134c78579e6ecfd2a164f116ba000f3961387cc854ff9e971f2d3cfe62de717555c5fa836d0830629a6b617dd56cf2fbaee8f9c0a22a070a3db93c11bead4109d3a8006e2e8d1d90175c01d9c6af194d98bf7f1e3717e79c7b19cb79cf39613470342ddedd7fae0840b763270465f208a05b45840477778ed2cd777caf59df04ca4b6492c06f373ad11935cf8e4cb2f692073e1935fba1693b999f1e728d38fc3967cae1d1a6b731c67502c0633c32fceea8b22c5cd283967248ec56ce3e358ec27a463ad4c224999658ac57e389e3e6f29f399b79ce2b521a662adc4657abb9f33d5e5bfcd1cfdb97fabd436236fe44867ee9fe8cd33577bdd386f3feee648622a52c9893f383c8972659d3980e67280cd3b0e5303d81849b2015bba41f72a6b0009860db0017d4b1877bab885882d3db7ccd8e2b678a31b1431b5475b58d1602d2f1d49b2c45b3c216f95ca18f006c5d94bfa6e64c009189005ace5759b47aac3d4936e778e5eb363cd311d1960433728ba3d6240cb8cee99f8b99cb31ccefa5a68e99e798af9b5b0a014da4cac8f67562c7153f8b8f0451bfefc8c73666f33f18768de49d666e2e3328b20b220d2e0ae06fbb7398da499e636eb30dabf8de6b27fdb6c9c34df1fff6db3c7b3f66f9be1728a387f7efe878afe6db36a292669d67d7d9a39b1da1a1659baf3632c9eb41df10c0b9b6e3c670b7863b60033dad6660b98e5c70b78ca95cede69feb800a2eeee05a8004aa1cd4471cec46a6be268299e5dc185e90a2d0d8a5fa99df2d4dd2dbb22a9eb153659ac16e88a96155ceaf45dcd8a1cacb0d9cd9c8378f74d8fcb55b8816df6714c558481757f5405194ef2478ad3b9ce8e551055d143016cb402b810917fd74401318cb3a4f28ea9a800d7fd3a964eb5c8eba4028d6e2aac681047c5152a88ba7fc9146ed8113b06d234e9144bccbf5324555bf35d58e9d1149e0037442ad61926a08bee2c097892009b592e7c928207331c9e3829b6808e7d4e5d92911291579914b4c638d36a43cf52e8b414a906714c516c016b25f6718ce24914ad06314df79546739ac5e968e3e31c6b51a3c9a985020d4aa1cdbec992e2e37286eb6ed0484b8f6be9d1b74942d1c5c4a58dd3aea5892fde19622a3a14414041830206016af4dfd2f13b8d8f738fcb30f13fd7796779f36c2b2e2c976a89000e20207737288eb63a25e7dfbfcd240400e009347637e3fcf55ded9d463b739fa3394f9f61a6629d932c9fc8d20d3addc7fe65a5367bfddd48efbc96cc7d7e22e9891658e9bc394102f04b9b63ea7f9be457a14bb118d3df26e90469674e1c815268373f7d75fa4b8af57f7e7c8ef02d6d0d77a35f673b71fefddb4cfa2a14fffc7c15bae4b0af4297c8f9443e3c4198d85c8b1cd65392f3715f852e653cc71f269ffc4b4da89838a35b4a14873131056814f48409209888d252a2b8481d4a14d028e889db6c99ebe7ac512cc6a4b395899deec7b74a7178128de301cce8f2a6fbaaa5c103d0aafd9cc507d001c51f0fd00dfacd5f9bb3f1ddd725b6ec1e97fdc87df1dac72d41b3474bd88ce30c52820d9d1d754a6c69dddb94c8d2e0b5cec3a4440cb67afe9dc3646c38a663457225b144cd3390ea33571b166957124f1a749b6d354a62eaee8b378a65060863f4bfd30047e0a7d7ce7078e27e7e987cae1dca48b0b13343a28b7e248840a2a99be6b1cccdf0bcf6c727576ac9cf51a6fa38dd8f6c9c4ef692e4ac74e68e08a37b573b22cb114347342ee7496f9ee45f98f846a8d10dca8c58824e5214a7114f466c44cb775956041afdb70822ba7b1ca7db78bed6f3635911b4069bf09415d1837da9881e0d625c2b31116c74579dc5a9954b4498f1f4621222b6341311b8eebe3627063988dd668d8820ea06fd714c2e22741a147f880857f710660cb1a5dbed11113d04d9ddbba690e6a52188848a86b0a143b8c0eefe6997101cf0afd3f12dc3d07efdf9b999790a39cca656e2dbdf99a7108689c57e921ccf588cc9e7a75662dbec69e60ac0e5ce44712e4167b3fcf8099442136701920a60d3a014da9d3f860568815268ff6a74675a5209a28b20b27407410ba2a756d6bf41d44a0c041bb3fc18082e0d4aa1cdc60feb0c813062666db56897adadd21ffc348d1c76f3e33383c3eebc40f8269653c4f1e9905884c5f1a9eddff67f6d36e72c9e11008dee6e026c49d22e02d0a88d13a045c5bfe100ba18c012e00098449b07b0429633b01d3d97d6ebad52985012d7d9f16b9ecb1ab6385d1620b2f490bb8cc50adaf0b167c9580066d462ed1240966e9b6d5c14804ed76a2f3a8c46f35dcdc57166cb248056ef6a9e1fb3018aaf9bb8fc43170de64a73ae1f48f0e65aea34715fc334f7434f37f8d7f6430f507c3c752e1fd0e8066f7ce802147d20fb451f27cdf57d206a9d1d817ce8318eb449e7394c58d4103f372b162de3c4998ab4a44247c7427683b4a1aff27f6d5868581c2dd1d334a23dbd6371b438b759c7e2c2f94b0205408d066f14d7c41500287e2cc5c785d86f4e2efbbfb699ee6b2cc6f483d3fd30f9d8909604c0069442ab650f6fe4f9e26cd64318b8072d0d4aa1f540768f7346fe37f540d4830aa0141a9e95b9799fda8a9a42bb44a417dfe668ab5fc55edbffb5f1c0443b919df207c7624c3e4f1608cf270b34ce9087d6bca2c638ce5917b84c755796b8423ad11767785e21eac68579d6aed8804e521c76ede0863dda218cdd3729edb044834245e2916b07a1dd37f9e749438bf1cc51c74032758b1df64e747cecbb5762b3f69278f74a6231a61d5cdd9d1feb3083dd37cdc6c9c424c6624cd6568b74a0b50e3ddda14c871ee4d4c97260a3bbdbbdca7208a34bda8b368cd172d0d2dded38d57a45ca21c89503cd3aedda209adf429a249c373fbddbec2e2b5c1afc4cb1c5a9b6be48435a2eb9ac5ce9719c56fc459d1dc78943187dbb25e9b74abfe6397bebdbfb55702a0e51e020a5bbc1bf65f824cecdbafba6176d8877df542bf1ee95fcec5e49ee33c5018dce8fab6cf9db2467e47cdc2c3fae7265d67467ee6f9527fd446d7f4914da5545671c69539556f76e0478964b9b1fff30913627c9f59dfef1c491f8ce3cc50fabadb99636c8f64a78de241f567b148b318d5337af88cb5437cb74cad38b957e6ed65cf824ffedce2c161d355dabcb5fc54a9b6ca5b34a73b94edd6c861f4bd1e6ee5f3b65e6663f370ee30952da585b2db2b64a672fdaa0da6e1a25f9b1565aa1719ce1b5347f5348f3d3cce1a7997b277a1bca854fd8e66679cec44a9590bb3cbb2fd6c74f33278448592de571f2c9854f62b1204ee20b65fc372ae692c4e1f943745f9c8f93a2c3238527881fe929a13cbd58c5229953093f37363eb9f0492e7c72edbd8f148b65fc798eb1582e7cfa9c0d73697338717812e5c227229dfdcc6e95ee2689ff6d4486f010e1f0c4e5b148a4b39e721c6798b10d5d99d62016ebe7181accf5b10cb37143cb854fc4fa4bb4cf366b53efeb1cff0fb9b018f84a83189798b419fbe7e9c48deebe5d7483d787ab33a99480ca159c690ed34a6560a62efeb5d5e25e87278e8a50f7540af895f263bb24e352487c9d156b12315e6190a08bc2c8407763b03e4d23b05aeae5d37cb599ded23a4d9b4fb78d8fad90c7bed2d17336670be4a0bd5f85fa2df3df7633873fac7fbfb4bb252be4d74e99399f4e965e9fb4b1264cef758bdd8af79d5e67891c147f5629cdeb0c61f67e151776e338ffba602b721a24a52436f8918c88c9959230c99228c9e049809c6989899183f5498bf2008d5bb1e2e23b921fb762c56f6cecfd2a7f6dd61d142b6c897a4923a7d1e392b6bbf3458a6934c7306cb3d91c9083349a97b6861cdc679e420e131de693a3b825721a217177c7c1dd561a8de6497c0507afcd4e3487f3c599712eaddb2cceb4965cba70e1a2bb4120403c26d2190cdbfe3a9e2f74abb436c5fae4539224f8d6844b0befbe09036980bb6f7ac16962d1933ba6dd2bd97dd3a4554bcb7f738cd361cfe43785ef5484813811bfbc551ad29cad36fcf1be58b1933dc2bb57628f76af4414128b64f6c88b1a647bb1173edd5de9c40529c562b399f84d77e65e49f7474b15c8a1024c92542095d433646989da9aa683634dcf108c6bbc67c8d2cd81d484c06fb24ea1cdb524caf8d2f2929ef18ba3b571f2bf29e7460e62108b75c6700bde14da11c8411c49fbbfd656f38d8d3f1ee79083349e20258dc644a3d1b01d6dcd95eadc466937b01f269f6b876838fc366bb398d6890b3fe7b74af1dfea3bfd88e338a419d39092329f1b1bf769e460c67474da0f930fce89f3afcde23c592007afcd7d0ea7529f4d6cd268abd41f535d91e7d261440ed2684b0ede6020391b77101bc172498ef3a6521b677207858a423ac771067da658a8daa31c5aa622cb84c5ffa3cfcda3f0c925950faa982337b1e352b4470d48bdb6e0a0052cd832c6160c6ce962cb165b98d8b2c40288e826addfb06093e3372cb8c358f018691dc682db380cbb942dbf4589c76e6c261c5bacd1e0f77dc003282bbed802e30b7a2b0660e96eb08650850e06183538d3c28916ecbec977f26318edfe6dd669e459c043e213a38c283a25a802290b57d354ea3b58f4008b2b5890516e941ac516a516254a949a28505a342d7015b300afbb3153fea56e2a8eb86207bee0fd719caf2ab8780aa3bb3def9fd4fc2577354f0a177c3aa8786a0a7c13e8e24585f7ad7a8f29ce68a19ea46f3645523bcc1ee52a9f0e9e145d74f79304dcb8bdbd885f52cc80d314cecb443493b50c5e5178d1af2858dd8d6b798fe01f7f9ab9212d41344b8bd0cb0b0a1534a63f3ebb5a1288abcd14f623999b4f3fd628ae8fafd5797d6c71a1dfd2d6e020c6659e7792e4f45274c1054c75d31fc8412c5a7a9ff49fb5127bb564e9b42f6fa6b9cdafa4542b97c41745d938522541f9979c2081135438f1e44df149a0dbbb3f08e05a89f1ad5298ede9c8c1dcd33472fcf749d0af509103c97da6365a2bf1e71d10789f0c299e40f62303a4653cc39b6a69b6391f6958cbddf84d74b4f11badd9fc4e30fc548681c070a6379b6b6a25ae19e77db2c4b8f0b11be08a0b5c0912ce1324274c8efb9ae72217ba76a8562ebd1395e148fc1f123f378f6a25111daf246470788e60002edd3d1d0803e43ec20042fa0048f00014bf0924b4bc137d2171c9f9b858ee08398ec872c453e3f0247a713e51bed6a8297c9c113a7417d183226ed0ed450c203fc6b512138105880d922f02dded41e047882cddbb51879c19e76816420d21c610620c217cfa650351bf6c28d22f1b5ee8970d3afa55030ffa55030efa55030afa55430764420081ad5f2068d22f1000f50b043cfd024109fd02814cbfc668a35f639cd1af312e30c616fd1ac3897e8d91a55f6358e9d71863bfc660d2af31845ced9af1d12484f0fa8106fdfa0105fdfa618c7ebd6ee8d76ba85f2f21fd7a81fd7aa5d0af9708fd7aede8d72bd5dd31ae18ef7544807e1de1d0af235cbf8e6ce8d71152bf8e64e8d7110cfd3a82f5eb88847e1da9fa65c4837e19bda05f4667f4cbe88b981d4ad8f44b89987e2981ea571272f42b8936fa95c40afa954408fa950418fd4a0202fd4aa28a7e25b144bf9210a25f4960e9571255fa95c4ed571253fa95c493ee1ea1a563c6b503a6c54501fde2c244bfb800d12f2e57fac585f68b4b947e7199a15f5c8af48b4b4dbfb884d0dd3a7690c043040b3ce9970594fa658124fdb200d82f0bd4ac48704008fd7280aa5f0dd041bf1a40827e35a002fd6a4016dd2d038036214545bf5249f42bf5d4afd40dfd4ac9d0af1450bf5244fa9582f52b5542bf5233fd4aa1fa85ba41bf5069f40b4546bf501be8176a02fd4265d12f1414fd4215d12f5410fd42e57ea16afd42d9d02f1452bf50423b4c50c1c88ca0036646c523e65bedd0ed85dddddaa2bb19d0fd69f165d1fab0e8fe16d0fd5dd1fd59d1fd55d1fd29a0fba362050388f2031aed9ba2fb4b40f72745f717c50745f78780eeef89af89ee8f89eeef00dddf12dd9f12dd5f12dd9f01ba3f24babf23ba3f23babf22ba3f223e21babf02747f41747f40747f04e8fe06d0fd65e9fe04d0fdfdd0fdf9d0fd61e9fe02d0fdf5d0fdf1d0fd5dd981d6e113ee95c51b87a7d34b0059082000dda06de629baf0c31b3b7e786a115e3f78f717a381f1013454e0a941835cff6f1b5888bafbd381fc8c2bc54e5a260b6c8146007e08a00711e09aac9de8c1e6a3e1c1045ded51b65649d44da5d86c768454b4a494a4c92c890c322524a6a5d92cf625155acb586c47da9bebcfe6cd95c63ea4170fa94ea234f314b2b65af4a5f0e55085e2afe1cfc1951f3c5c78a179b850d33c5ce0d13c5c907959ed6006ae5f3bd076f8d11e7be9c0467b4c072e3c66c5633a08794c07996e8fe590467b2c871c488fbd7210d2edb11cda63b397952fac10e1316ce589c7f0ec170e36e86e1cbaf098d82f1c96e01082c7aa6ca9f2d45584ba3db65363f7d4dd829d4c77bf9ea600f1fdfc395b3d264ea0712c3f1142987d37e07f9b942f8a37c793bb3cfba0d850c313275f13ef99302d29d190847494fa8cbe25392bddd6568bfe8784fe8730be4fb319dd5fed2d4fd131f17755e6925ce62737419e6db571a16c138b85d88d1badc5c4c744f84717dd8d838be8f6180d466eb3f8b958ec8561ee1c57040de21bce39cce8c2ba2b152710952ca830917be2b1bfcd248c9f2ee5c7331b12ae954b3624bc7b25f9e98f6c4842d70eed5e89f819cf25383c8980f48bb46912f5863da3db61652d16c341b74a31939775b2506c9285757b0c5b5b2d7a551b3c4e64c07dd510ba4b31a437bb648b5c0b721d688fe570f876e703723edd6d45374d02c608344d9d3c532830658bf6e9eec08d4d93752b373387619b16ac38cc66f74db35d6d860bef7c3cdbdd59690d5c2141e2b70c535820997792aa221cd6c7396ceaaeaae645bb5b65c30bb7a5bb6d3fdaaad279e16cbd9be392906ca809e8ff4b6eb3387499835f2d93d0e3b2d531d9ac2ec98809987fe7d9d6fb3ad0411c3a16df696217eb9c769c3fafe7c6bf398ba9c3604c75fe381db44dcf8f415ca6b890decf3d8ebc554a736129da2b854683f239bb44734cc72a739046dd6178270e7eb5590a8d06c556b751b7d9afe53ac55ae67236c436d47d7d929244082928169b8de3eb623112439102c51ff73a87398ebd397e277a933938c58973a553f775da7162f2bfc94129b4266b89826850284e2d87f5b178adce6b9183758af597b858ed91b894ed912cdba31d90f84998246ba075bf16dfcea7d3ed3d9aa65207d2fd757982a4bb73ecfd2aa8182756344e4e77b7c0cef573bb11979676b7aba95f4ddc687cc157132ec026a426567437d957139b6eddab4908ddfd6282840c4b7437be6251f864e629f423492f537efa236bab45d98e4ba3f837dd1fe16ba990488532a64b70781289d50205dd4f0a6b696b9505656c89ea2fc1a350fe25910a653c97e0dd2b1175f6c80999bb442fdaa0a5ca44fc1f124a2a7a279ac3598b788230b14ca20cbf48ef7d27243227242ae1b92467332e833099bb4438e846f12be905ddf8e9508e2788df2a1545cfb4967e9fc41308e409b2fb26dacd8de83087d9d8d8ec0b2e84e698fa38c5e9758ee3ac4f5a9d5d629fa6d1addaebc41eddaf3aa46c8f6ad17df1222de97ec85aa6237b24be510c8fe30c32cac1e8a67b06e2719cb512efc851b4b9feeedfaf32abb63e65a60fabad2926dd4dd42f25354a80503245b4375feebec9e635030ae6d3d7f666484245e055d4ea9e52e9bc932c1224c80ec53b5c5482a25944039105ba7f360bb2f76da8f6287c72a929fc27a6970c61bc864278c99034148139c31714c14886971097fe42f80070bfeaecc7a3fb7b7d6e56cf8beecfb523777f3a3c26f0bf6de69de8adfb938981b9ffbd8ce30c5bde37ce1fabefd355f7a7eafe52dd1fcae925dbf29225753fbdb986f4668ff9d8747f9ff77597399468cf0839dade9ba4a44167977c9e4236a45c2289d6caf0636d5a5b2daa9666717c3a24164d3abe688df0ff50fea51f6bf3993485365f3b4e7cdf895626496ec9847ca2b2fc4b97e8979694762510517e4a72edbd499484eefc31680ccae487a1ac3e8914a4fb233277895ec8c938cea0204eb1582e7c12a4094f935cf8c4278b4f0e9d0b9f74787882e8a69258f4f4629daf235974a710f0e3e40c67baaf49bac91057b7f783af7e7355c40d4f563ad4467dac2b1bf056edd5e5cfd9250771adc4b8b0ce0bb4aa82072a05cf07291ed0f4a0dbe3418b9524da3a43b7b7836e4f075e0eba3d375e90c5ab67cbdfa97b285e3db9d2b109057a74ba5f45d8006b322f8a12232d71d98f8c93de5cadd772e50d7d3a7e1c2e497fd1d699cd95cec4fa217ed1ebc1a3001e2bcedde953fc6b8b5eb44740424f2f938b75deb0d258ac962b6f9037c548272d9739dadbde09c8eb88b4baed10253a3613478a0b63dd5e1ba91becec803b38e13bcdecbc84e01b9bfa23121f1769ce062fa88117af205af0053fac8ff1cf68d0edcda0db63a3db9341b717836e0f06ddde1a2f60c02bc84bdf5e3c622e73af1e6ca05f3dc8a25f3d4082041e9fac797c23340f4f8de6e14da0797850340f6f87e6e1d99a8797d33c3c1dcda381f002102f22c216917e4cd4001fdb31f4a7fce58d8ad5864e5a3b3a79810127a663e8432f3078eaaacdc759831416e86c6dba3ff698c6133e1376e0c1bc200f8ef6562af040772be917900c747b2bb039091b345438400a990eb4a78234ba3ff0023d0528e85e22633a42b2a1480989c869464c8e664964902d2d25319a0929c99c2431993d9119d18e7088ba1b07088e0a386db3c33c96b3d7864677bf605c6059b4c72c4ee7b0176c00b9b4301b6049264ee7b0d9db6033af17e678bd6083d70b6a78cc676e86a97bccf677e6b17fdbbfc0a53da684a4a494c46968687af4eb051c5ea8f202ad677706cbf198ed6f0e2e47f296d861b3d70b3e8e0409929b34ba5b847ebd90ea6e17cc70018c970b5a7a364e8fe5a78779ec5669a54ee5a502910b3474d3d4e94cf8829fc929ced92e0c3d763386c76e127063801b02dce06e6c1cf4184db5f48166998affea974d19365fd878e1b110367368b23dbd6c7c74db8a2f989f7ef6793ebd198895f6cfd43b41b78746b767826eaf0424e8f6cee8f646d0dd2911786638d1ed95e13d78e7b536d8cbcbcb0b4c9d400ee6c7bb7ca968c3bf995e9dcb3c00b42003e22b8480065420020960a1067cc1fc39f86fe2db17986ac53aeb8f9ee71cef3b217995b5d7f3faf1842f28d66f0a676df6f4e61cce6b8535be7cadf0c323a3e60b5014a78b3f8eb21a276ac8ee063fac13e77e4dac46a7bbfd69e66a5078d5b4ffb8c79230cdc458ec4573a3894213437b0c5f3067168be158ece54eb41c4ed443051e74f74b85047483b9cf45399bebc425cd66f882393c75e1b4e1a8c878a530834c65af14a2b0c448a13d0c5cc0025e3051010a4ca00b4f021160e285824c9fb0067e140daf1370a0d162b1970f2f14d0ed5111c5cb87f83261096f8a6e2f015e025e2500d17426fd0f81d86692d291cc4d1a4d247365cdf3dfec9283b93eb678c4258d46f323372ff8916bf43fe42ef80bfe6f9bd9381227699ee51f8fdc9034db84e30c6748dcf38fd70e794e8ec3322e2f8d4673b0e6b01b92661bc7d98d778e3457cb8b93abbd361a6d7e0e574be758defe7aad96a4d9e7cc35dabf8d86298de697626b6b489a2f75aaf190364e24da7dfa6f7399e30be6c2a758acda5cce86338c5f224011c1a71be6317b8794fe3693c42449e06ea4e18fc57069779ee76ea4b71f735fdeecea741bb5f1bcf4348d40f1770f1b6748afcd98e2bcfe9d614572b0ceb00c6b37368b67c6a0149a0da9fe92a7694483e2bbda6eccb81b1db1633ace1c9d4ef3524cbee85f5a7b7399e7e8df26693379d34dbfb14962528bc4247bf4b799648f94b23d7ab9cca896baaca0b5c96f952abd5cb8eed6d1c4f9444bb4974bc888c37f875eaeaf5b8cbd764cf12175baaf1d3f5e3b5caf14976e8fe988a3c5a2978e307ad2262f1d4874bf744c9e204bad2348b7c79c689e475876c1d7cc2de30b561bce5e321a78c958d1ed31b1d2265cde27cb252ceb2ee249114592974c949c0c2f9910ba3bdb117bec15d305be6093b5b8192e717d5c6d681d673be2f9d7d6dd4f4b18ff154559198b8554fccae4053383174cae2f18cf09e29413a9c3a894506926c21e069942ce8c8088000000004311002038281a8f070432c970ca6dad1b14000370c06a964a9a8ab3288a29859421c41010800100101008266100ca7d2e0008c6c615a6db84a223edb368de7b45f8b296ad0ec8b7b743458db2fcb6a5133c21b1178c302040d4c88a0ae400110f1f3aab46094618b28b026d0eca6372e391f10b9ac015cc7a8ef9ce51da15ec1e8a098b7023594823d3b91a1e23626cd12c0d508930e9fd2ac67dac30a25362b87e3d3cc6dc0c46590fce7e9230e2b911903e992c790d86e4fa1585dd83c6d9c95594b4fa31aa2d3da98eb7b140482b41871e30f399676cc35fbc358883e8f1d066057405ebea40dea04bb14ebd09f0bc3ba5e0994c1f632621ea13f0d0d0e8470edff4068e9b71505fb593b1dd01e7f151532d1b4642f7b6cb5c93c82c179ea5983942c6c485515f0c1c0a6c1399622d67f46241b92db3facf172793c578b8c4eeaf8a58c0b27e236eb704cb0053605a82a61bb6d3db18c9aa3f463d7ba3738abc6fe5d50af6469fec7523c85cdc0f95d026c2990fd9a74211a2c55098692544e2513e2bbbe2bd4790ce5c6926a2810369ffd2b65e011d429105213ddd690d064e90962cc35ae1ebecc217cfc77157deac1387f5e22d03b29b5a0c3ea03a50ad3fafd734c03518d6634a867ce67104992a46f04b295554466ad1bf1e48094188e821d0e43e1a216a8900888e40bd5f0e7e3531262d3a94c9d88cdfe793247e7676f755b6ff26fe92b235133d322596bf9bf8e230ee269f7c6b23e20edad716216f65b100dd92596fef4fc37018967455572818948c2168ee7098b964a976828cd36f86ef61c71a46a61c61f1b2e17d12b53df679a2e35bb046b3797ec4eb5bf6a39f8276e20a36918a7d12abc9c43e882982fee05914d01064d85f66d8e04583873277df0ed1be2e1be539de67cae4db8234062e5c773e6292229d958fcc50485b24cd89753ef44f0a2348941d5ed10ea5f0993f9bb2b0c1ac12a281dd94cb3c26050a8a5f15ad45c84bf54457d8b452145d53b56ae8c60b028ac67b0d49ee06678792f29e0e786b47b527c3226f671ed0cbdbd69488db71d09908318b3dc43a17908080e4c08d0fe3eddc8d4d498481c7fd00a3372a339ed0a01d713f4c71060a7e6ea826f4515ddd830abcf443985e6f8b6a0167bc42d0666698b5323615f7573eeab5c342b08059002b8331d44e3a0b0329e309505792d4b805b89139190202ccfaea94798771483ff336a93625e285fe12745f9f66e4875656b9804a5ae74fb7d47b2a8af7ec0f325fbe5bd20efde384263229ab2cfb1d42fa503d9c077e87973f57e6db34a0c1e56c407130d89804db5e0fbcbad5b8f92d72e73d2b4d6c2ea260b2666fe1710c2a1076c988621ec8d1ede875d158af48e3e73d74086e7de0760b75e5dd92f991ce45731b596c8ba26c7372c590253b4221d3bd345f1c563aad4244accbcddba734c37d3d0dde5f44d53d5721a4600160b5e5a24b2fac25f0657480757ff488906b39c8da5009fc3e1e07d25d48643945525641f4cd4f7217f1128ce6480b57441e1574a6adeffbed09bc234cf2bf260cf25283cb6f5d728453ec59f79f151b68249594dc8e076def190c5a0365a7dc961abe7bd6a55d1ab985bc9da01b04b34860ff2939799cd4a0573367cd1a0753d58275653da3de0f002f6ca63fe26f2e6690a49ea5b89f693a940a363fa0d0207f531ce33e607bb10028153ee2b1b88b399d49bf97d150ca9c3c0af376195875d0a8cfeb6a0eeb2b11f1f4c1d1e6d388b5c654ff6db0d8de994f0ffc0a9d5268e4994f2a675ef9b5569d294571fc37ccfa8971259bd15d7783a34120bf009aef0f24d55833a51974329d42502f4c3fc080fc9f091f4539efa8c1598a49170061a20ffcb229e31169d6d2d1c2d9dd34e40ca4bcaa0c32bf96b70995f98c24ce0dda60ecf4b7d149f86376fe86759577555b0b6932346b04693b6e72148e2984557c0f627af5fd40bf996f934f729b2c39bad60c69e4d91dc55e45339ddb904f30fd73627845e4f964d96af991165190725474c0f65c34fd36a89f5f58298f281c6860857d2b38d35b8e15dd02a0bc16654c981bface9a9a42c5f61698434a9ef852ccde94a1c4654151ee51e7e1bb013509499b8f0c81e7e439e9c6260936536d3e585c52f61cc5e74ab1b38afb64a44fd3266a8a77fa558b72344c2a4e8a21ae18e5bf1eb24c133d4b8437d6f701591d05280ca414252ca06e27df8bc8340ac40cc34a2eccb96c02a0687369c96a51583ca1750921464d012dea90bceab4f66585dbe0791b4024206b2707db4fc79be7146ba47c9aaeb1fcda273badec36211b8d1be84192f149527a8aba657389987d1cea825ddac5ee337ae93211289923b1431e8d1149d302aa5dd276bd0d0e28b54e5df1dbd4ba05dbe6b6ba3c03de3b7e5820db6c0f3a101fbe3b4dfd011fb42d87e699a121e8c993d822680e6c36798ac23c2f6c856429a5a7fde59ab58a03553018d16d192b66eceb8cc65f0f2bb5e4106b9651ea44486f84cbbe67782301111f131d4efad6fe6479ee5ec2541b733061c4dbbecda1fd6e60986b71d52cf85d331e5adf6eded69e6eac7bd8e644d228e98f03668223ed57ce86680efd2a76b333d418b14b0ad3e41cb316d794718ffb3ac47340949a931773ac0db9f6e085ee1f108134d812430c14741d0b18fe52c456a96cc8d3605f5c55ef9e216779be7fc33f150a6224d1869c473a7fe10c65ad3a7c32544beb09ecfea1b9ead308f53e13706a8f433b8dff289f0ee370be032db480fd62d5fcaae9bff1c760b282a1d01b942999001aef1f808239f30a1f2f0d52af82873f459989a41e42dffe40831bc2a3f772a7c96cfd386ccca01ae04aea78b658cdf4f374c39f713e7be52130aa3b04d386ccb225090fd36416a5019c05da85fd03bdfedfd43b00c58e1d80d40a02ec0a0a90cbf6bfde0ed2c9ffbbb47600d628f4ff31de72b75236b28cb4e5145360d5f3510f9af2b7de60969d31db335b10860684700e686284e2ba93c48fd0f86f3fc413f565af113249b995bcaa7f0a091de66f97378323a077c4d6df7894da54b7f31f585d7d7df2323cfc095e32bb3c93be0421700a9d6b12124a042817e72d690faa04ce158a7d1d9d8206c95c63f37520a42fbb8e91397373aefcbcbd8d5fa32802e850c8b657b489135cf943586fb4bf863e88cbb14ced91a5e52a0b84f057a6dc9ca5a5cb4865c9f3f915ee7f1487cea3daf7476d9649ad8b4ca4bb92b4357fd6ddc73bde70e1fb303f54fa2697cd8dba6e6166cfdf8fb808a7cee381f6f89bad93a40b1f9b4afe4686ce3ec49e6e62ccf9eebb967a9be70d60fa7c00a4ec0e1a2ee5b3a039ffaa7871dd4fd86c21250168c5c424defa1393a56f7e7e8786850b1dd3b9ca70cb3bf0d86deb432360df695d9c783c49d2b1f50d92fefae3c36f2032e63d6570247e9d49bc00d8e536c1da3e34a91e0519b17da186d9025344b4e7f60b042c9ed9d0d7cb28440f0dd1d7f6d2fe9f7383ce301fc4bff9645c64e1c6c2e6c7000bda855cfa481d91781cc450f59715e43768e1bab4bf13661e2ba390738bf838387baf0dfaa3e99a6820b80329cde95ee33aa6acbf0b3bfdd6cd51eae4244cf7ea3f9f62bfe118b16b9c5cca90c4f838eca67e709b77e933009bff62e035364805d1be18e37ffcccbf5e1e641c294303a42009179ab54079637fcb856c40704868be000d9e4ca68ead0af05c4c4cff378331a6618f0323846caba2ec297c9d2344dafd125aeb67802bf220011104f1a3225b79b16fa3167ec16629927149c6a6e0148dfb215354f1b87b7cb94bfc969e75c3eeda84cf3121c966511c7726baff5306ae34d75437d8869fe460d2c1f7cc98a9515e5a9b2d0a78f2084601c55532726322971ca8aa945424868d92bf6c9fd6b30b45e85216ff2f1adca7fc7d6a5f8112dfe8a5d0222dfe90ce92dd69f15dc096566830879ad40ada11c2e3407b4bb22fb3f6cd36471bb35ef41ab7626ef7a7826185e7b8c57b31b76cc3c72cc148ca1b9599953214406725d2401c4cb0523161d829e3bd5f15665fbf3ceea45c790298f31c5227e65c6321396da7686702b87e3832ef18648c4da6dd720c025850d521aa431093d4b37f88a31a857e6872463c47f9b349e06cdd7db9d63090a0d9719a47608af5a1183e1010be21e86b61b202f4044d2f68532fac0459a290385c4dbb9a7399949bc32057caf55ba4f8e8e8bf88d8aecb41311b16e4bb706a49861a7d047b084744b05b73f03152961d3a3ce7019127ada22e861d9eeae8c6a6953f9bb2dc19df92e32228a93e27dc2e38896384621101aefdf462fd1bac7810dc5aad4e0337a8444e4ed61d7bfdda06e28fcfb915b04f0c50b9b47129dc63f0283abac3ee1b43b2c8d405c5c44e6c25e18b884eb0a044a0c0b78b10fe5be66171540e025824cb7e0ea7196243a824f7559c1665c55cd319c2f5e00e846ddb93b70aa260193ac43a0dd5f33b741ab0704e44d9aff10d92b0dec709a06c44dfc68818723a56699373885b6a614258b06899ed08f80665485ad098d024506726682c9ee55197c9aa8ba03b419c2328d30755bf885d92d6d88797230858e056da1c7e9b32ebb0078e296072aadd0f13ba6c3a1a55046f44c73c00b333a34e10bd9702fb45f2643eabc5d26808b03e885992acaf8f5b7f1a0a8d204d5407f5c88a8c625bda2b072a4db1c06ee203f70bf08b973c7241dabe94b21532a0cd4225573a37e0068b57c749766c6504d25cc143319236590f63fe3182c67f4a16241b71bacd1728c9c31beddecfc1e79521e4968ea0c131ac664752a4f2cda0c59062ebfc548274270582e1faf0fddb00cc334003a634f91356916422c6f39f01db551c32c8dfdcf020a766c3cf5199237bad8fafa761a42cacfd9682a817c2aec9d0cf09e057decbf1e9f0c718b44227d68a5282c95585a0a20cfdab52ac23724a06186f5f01bfdd89cf1d339859312f71960cb1e083b351cb3cf41b304bb0af1150a040d7fd4e6f0345b4ee926cbf0ee9c362abe9c5a2b4b426a127b3129538870167d0b8e301a364ed0198c1e15f66d153928bec6b2d862cee4886939642e2c4e024014132084c9c1464001fe0a649021b442dc88354c0c98a0316130479d154c0b1e9084c6be3816c070b5e1fce639186816012becf101d6b34feb3c395ff4e36b223834838ee3941535f35d3058ddec11c25ab06a4536df9a245887c8868cea495374a33287e2eead515f0eda0d5a5b7a9d82506bfbfd46e71c0a030748c5ebf83854a112c68669773fdc52be86d960d6b729b2ea6e3c8fd2e5965bf71405e330a1ebf73e7eae7aca5e3bcb2052dfb33fd58c5aaa6d473345ae3f839637836c3f9617dead0c5fdfea8af714ac317e4a8fd9a73f3302fe7b663ac06baf8323022dbdb6c5755ef0c7e7f69ae50a8f7066bffc2c4fba6d5d6bd89efb0d4c06fb5f13fca2f24761c2a5eb7040ef1e1fbc474e2826df687056fb9cbb8a37e7d35fa649c8ecd4b3bcdf0d05766a6709b37c02bcb86e37a962a4877c43fe938536dc413a226af7f90ca9ef9eeb498b4d7acb8b2d72edf7fa0363495e15af8a1a61b98ad3396789e4dae80f2eca0eb8b2e38d1ef9556e83e558af4ebbf98edfcfe6168314ee85fbedb06443339f479d5fa0da6ffdd195c5e06e909edbca8921d68d67ce3c2c2a02911cc825150ea7776081c01f3539cc033f336e9162b9099c045c473987a233678ca5bffa02b972cabd97bcafb08fb34fe9902a0b60d00c901823fbccdf088c80867a11c728a7ae0293ccaea1f77a9390bb473d0d0b06cb234427c3e2da35721d3e31ef657c0ac6ecf5baef50f5a01463b7c7e76c8ad6b6280d57fb99ce89fa68c954848ee30803513a127d38ee02535f855b3f7c9a7c87b7b0f86a3fe738ce6792aba4ec92af00b129399797f47e671c3cb687acf4d9d05d574c6ac8d076fdd545a736fb322a8412fbb84917b8c0000e93c90767d8bed4d540bf80f2aae49edad5fbfe8dd2ace8fad021ccf02e8b1e9895e9a5a0171e4348ddfd88917179acd2713f5232982e5d6631ba8f59301470fd7deed5f5637cf7b197a2fe5396dd905d5a864e6e23ff0fdd7e1e18ec0e5e822600ae97da507887dc0478d244d88f45932d8baded02217b337bf4767c982c27a35475717c2d14e64f000da0cc7ffeb3f19ab5f40943f48802e09b6e4ceadd887ffee0f21fe8ec982dbc66f9fa61edc501642d9c48b3ed66fdd3592e083fbcf9f9df11a1987fa3afa1b572cfa582081be05bdae587beb1efa9845d60957e825163de16d0ae3f36abca28cfb21f2b6232d2b26cccf52166220d90a787c1ba82f9bc275cc38cfa192cab4a8a00365a6445b3ea1262f4a2dfe7a2d5a052482de8a4d71dbd1e0337bba0977245d297688293d5146fc612072d699bc3ce4a302db28e97c64e1ca17c5168520ba05a61bd157f1e192c0e038d1efbe1b642af271d061daa6ce5c28eb9c71c0be5c302e04482f806591016fe6c40fe99fc489b629b18af5ee597aab89501b1ddf2bb3b9ac6cfbdcad524a6ed8f7b8844e4f497fd8a24b9c2e24083d0272ab3f8c2b58105711c0cfd80d8c0cd342380ec1d147220c4dd26c3b151cdad22e7c3f2aaa7d15cc16251d510ebe221cd14be00a5ec8598d5791ffaa802d3ab1ade473ddffa3e1bec12bcca2c6c13bea4035ceade2f8e2281f51dfd163c119477277f02775f5612d87ce608c86db98e0980c99a389fbf8a782ce792ecc9d426eb73fc947b3bd18b79cfbfc439d2f60fcad36cc6b2ae3dd4989eaadbe28dcaeb65721e01653ef8214f8f5be36d0c03ea4711102b876bf5a499d5a82eca37ea5f1ab0da31291c38a912b4c867539eee8ccc7fd5214690cdb20827b065c294ae484fc2f635e15c9925c2a7b2b56f9738377007c2122c16f8562e5c5697973713c3d605d4e53481e783aebf52623b956872e1542484879f146e25f65c7e8d149574ea35573cdb8f4490913d1e7245ad85ef6b4bbe77edd6bd0cf5c7d4775ed281c21f6376a34f2d72f8a3a1973f07f58e12e45d09dc5154337faab6e70859318e1b9838b817ef79be21399b99d33b7634426409a52de05bdeced31dde136a984a983f8c87e72472ddf8e23e5b5133bbf624f35d47b9a63d31764f4b8b2e47968e487bbb2f4ba12c1b4499098d4baa41b865d48222b4514bd2e0169cf4e533013d97b613a59f7a572eff21154809c311f989e112c79aab264147eb6607317feffc730dc880836bba29653468356b8c43110a6205e1952c5bd18dcf24ad01010dadf781255ff6e163550dbee0cee2634b5a63d7e4840e1cd68a22632eed0ea2e2001b01a832bccfc19af9f9fcddafa49765b0697740f1681925bf4c61165789fcee4b5df44957d1dde0e0ede1290be071ed78161f729ac463a4a064f41c4486f5d9558752b98e27265be56b831abfb4f57f4bce89d2cf9ee75c7d598c4e8cf35f1c9ff9a95fd11a48e240242d2d5078f2bd63ed8c7e6214b929028e25cb5d68e54469c667a5cbe72b8ebbbdf3865950c89df2c72e0015dd7034155cc87fab74bfc9155d4406c5be9edc884270d8fda3521cbde145266f34402b57c16c648d267ae5ea96ef1d13278a86ad8b9eea988f2c0287509f03611fb1778761ce1cacf2f0b34998d9951b49b39b6df4feb271941ddd2fe187aed41da75b3ec7a06fa30e2acd2cc0c6d9c9ef154740b48f39167e5c0e58891939107707f62edb1a3dcc6426b9c879593e4e448e1fb6592b1a3f708e360a7ecd8b2e95893d16cb1ad9db79355317a7f37293a8f372129013f5f4d446cdd528e51e5e1e4a81b66795877ad508744a789b5edb91de771e4957f6385987261398aa3773e760bad066b8ff10900b2687fae1c7311abc57aca9990aa23e51cd64441e4a9fbf5451c186dafde17f622408813451f2970bbb27579aaa9adb9a49ce621d2a8794eaa775f483bbd60d3569758c058ff0a6fa5802cb2d2275855341eb6835297939c57e120241a089007edd2ab3a672ada869b2f98be81746ca86a634c9811cfea8780ae78b456baf72faacec942b9ccb01c06b3a7ef60096d6c9c1082ccdc8c1465318b975fe82c6c899511117bd8fcba7188a0022312369e450a61be1ab147f540992aa08e8034dd2375f50d7c84bdec2a2caca9fc4a07b19d4064848e8973c001acafeac65c2cdd3045665c79e9e976aed2203372ed36a15c4a297abe92a905c37775ca1072c8b2a5ba69f0410cebfa5112128e1265858593117313088a191b0f4db3b7e3b71942c00ec9742fa28d5f93612839beb27e9cbf33646df63273d24978c4cac2bd1e783c4acc43f6545349c9808efd76abc246010f6b0c4a111f0773d73cb3f046933a2ce00dba2836480a3ab984c45de5f3a0412674a11fff5e09862235b3db848b23ef26b292c92ebfed213ca2b5b027a1a05d3ec1a586e6350b6bb7aefac45c6f33382ae4f53b03b064764781a436bab18b806051c0a55fe9b81793a1c11896706b49f51e034a8e825cc41df0ce8a2cbb97d9e2941cf28252e89b86d643b8d8fecf9768e69174cc05baf9f5432035f8bbb0566923eb1a0e58dbbe6ce98aae8c35c666e6b62450ee2e6da1f5214e5cbf1866eb2b376a55aac950f25f7d029d1d5f5152e16554baaf979c2056698b0cb8e3b4242ddceb0854ac90bd07974169edd5f27624828dc6aeb6c0e732ae4d7e4ab6e9e6dd4150249d0861a86004be50c91876c8ec7f24341cf04e94d273ad1c6f2ad9daf02e09d78c6fe117a7410e9e2dc5b4fa260e721f2efdb66c11b42a8de32d39d64e837956c9025688f01f6ba3ac800278037e3b0c4e1b065c5069fa2145fd093ec689e380da982f29a75da844c2bf31d8af5730531e2be6ec098e04bc06275ceafbed59223428cc7a1b2207571b5abf349109d53fb5e78a3d4d37544a6ff590cb5d5d3373aa1aa787446187af53ec9db45408629bf853ebf351cc9680c63532268c9a6177a6986e24a2755a590b9459f10377f16e0754b29721dff2baf6a7bc1c9abac0f2690528ac9e903d8fcb54bccb9a117f481e75cac14397fcb604a87f904afc466cdc97f520625fdb851a555251711f0dad67198e3ff82a2c8133152d024a980446b90d152c40cc39fa912801e12fa6eb098a752d35619596e435e52c581022f97f4010ad2e023e226efdbac35f6bf588cca8841d4c505ab50fcbeeed6dcbc409f82a337a005bfe68425688308545e7eca02fc5b4cff8f3c417fc87f61d07fa4fbebdb3e51aaa6db3ef83e242d28c5fa3a96c14bbd1c42410faa3111da9d596a15b474fe7ee1cd25d8575845f38cbe0c5ebf65c6a1941ae9154a81229d3c1355f366e9854456f1072a727b7ed3f6a8b4e2476fd32881d16c75b746cf96ccd4d6ed54ec002bfb3eeaa80c6b86824c3a2a7a3f0c6fdc6b910ceeddffc2c8cc323e94772e60381c60827fba42d5ebcadfcce91b0f80efa476103aa08ac0a0ea292216e13d8af036b5e9b4d3fe1b113aa970a505df83da3ba21a1218198d2c7f02b038932caf5d2517a3e5849f148278c5235b3272d71de8eb0e2670d186185c2c8edd4f63b83937d3818dc7fc180c2ca9213269920fcfc755fe1d6b8856216e8007055f187196dc1800721834c50e9e0c01d52cf7f0f94ca2c0edcd5fede0b36e0e720e19e4b1b5342e886a501edf40b97b923ef623ceefbc77e30d477920eabeed4095e56c5f29ecc16a9b3841ea7ee01ea0dcebd491d516e22499c0ea8211cdb6c05750793b13dfbaf1545201464fba7a067b08b3851ceaa0447965129b41f7a3b22d7d82f7ef65a217358a37f1cf9e056429acfd3510d3b30f241b0fdc9a38725f9dd7bb4a3c137420aa5fa27f6a1b1a40186eece3b9032881f5c9bbc4f87fc6cb16abca43d3bfead8749be58a6a3c10e4a96050732819de17fddc14aa3a2165a8e03badb060851d4f366384fbf7b8c753ab8b91e6bf3a10c8900fde1474c856e00d75704c37468f88003af71917daead8af117cb5dc77877dc917030503910d0b3f1d02361d34b96219f57347adc7f4474733eeb402be8fcfc153c639cd6d7d9841a10dd0215d79747fea9c4d10e86e336595cb81b252ff1676164e01df388f53652b2a26083cb2a89f12c9f72fa3e5db4a05c35cd02572d5f13da3f5c2156b969a2cd6c271d90af17b56a6f1c85a0e492c0be9a8d656ce93ba7f50b3a4849b5c6f7e5a2dddc2936e790cb9f8c6c1a51a1d7ae91e9e0b8a848c8fa558e39e3017c0d21a99644d301f3066e45ddde4c08f36237b3e9cf9819b02caeda1894a989faac6ddff63bfdbc6673ce21b45a2279ef6c3d5016e90e4f815dd1f08ff074913b1b95cd5d4df2d4aac0b34579e1617a075cff23813167b558709e2106a478d5140f5a0d86184ba3d5777521118cdaac00e77cb44cae575bfb33cb10ca9de16bc4dcb7641378e9f6663fcfd7ca8a07aaeffbfbf78e7336ff119adeff55ff906f0df80d0358a5fcf7bb8c85d45c2577fedbd577bfe0a7e91485f7afff3c805f1f9d625d29fe2aa75f80c7480d7de42193808e0df74fb3122cb9281578706291106004bb6d3f0e35bb82d31aa5d054691dfdeea20094a71cfac6a4861055e43b2ce4981d6a24e614b258243ad27e26bf7ee8e44c99f861b2e70fc815b2ec7f33b719678e6253b3cd1f8a5e2cdf601103f1ba14185a1c87c0780a35b1416a3e6c3bfdfc74101d7fe5b1e72a3705dfc7e05e5ff3f25c9930aea1a1d5d0bceae2d6d7c47617140b0cfb759d182e6f59264da4867d42a6cb83fa973dcb09fd32f16359f14b74c692bf7a2d81edb079cf23ba896568cc5a33d45f49ccb4bcd6d67dfa8278f94d68ecaff19d265182f416e38cf1935608eb17a5140600eb410bc7356225716881ac9b9675e55db939def52d1e86842c734ca0f7a0ee98cc7dfd7afbc7b6e600f81da9315f6d7e38890576e2ccd5e4632dc02ecd186ff0ff76bbaebb0f4aa2eb4205973764ad9af09526c7d7ea9af6bd2c45ee620a6fd0c8261b996c6c57fcb4c03712c87721560962cae7e71a265caffd2ebaae8ca217355348535ac36a9575208d65ac6bbbc8bc303fbce1cf8c68e61c754eeef3fcad97d309f478316c6e5a1fd13d0b54eba289c7526b8850a3ad852c456b0f28a344116cd71796d19b2ce39f1cdcd9a6edf9a42095e9714c29e634578d9b21a5b2fdccebde505082e3fdd3ea2292c186c2efc869fb1322d01240dfe6dc32a040e186db9a103aad544d250b0943116a44a107dd0dc569909791972f5593b1b381135fc052b4f8b936a299174ab8386ad9f9775c0e73ac3d504c80806dc4751ec1582cf2d02b4400feb802019776f46574aaf1841c9bbb14c67003cd034b98fc79b2f00d3dd77dc52119f801da7ff9dfb8e34d617118e81295404f2621084b2a03188bded81462be797b09892579a86c9004bbb3ead95150f74517e4de04fcb50f79796635f1269d14ac49ce74c91d8b96792613716d0e6d782d81516f28de2e2d7b096699a3056036364dc80dadc3fbd4865d1f621cea98ebd080874c37bef795e812f42bbbd885992d6d4c7d85003ca1890694617bdc7082b95b5815e7badb10f3fcf891b0397b486e0708e7d6c1a22e981eca4921b277f05ca25891729429cf50f3c2d3af8757e156f9d10d354f285437e685e7d808763d30a09daa4d07b79ea71bff068dd595fd3289ffa304cbc75ad045c7de85972a0d5d97c6c2ff6b0cf46d7cf03f602b7928341636e95ece44dfe0ac072d347eecf11f1f7f66927b420272666decc5a8412864f58ee332001d2cd269ea934c75c0f0e6c0ddffe820ed1c364cf81cfe30982b44a6806587429fbe933b68413f4880f93bc3c347fb8538bf9952ed70d54b571a33ad12f1dc5f5c91fbd61f92fe06ac2305bf84354dbdfd833793df4def5e44d24c280f6605e96edae0e724787cef7137b37eb65aed2be7e9baf19debb91b5c62fdc43e5201d63f9c15fa261f8febff98f5815b0922c2eb141273bd92089f96abe4053c899a3ee81f072cd66abbe094bb7d7183be661d3516ddc41ba01d05c5b03270095cafd9e23a23710fd2ce5a397140c986fe578351f78ead63c3d8d930264e04c8eaee0bbd0cf0b9ffde3e86dc15bed42f61e8838f909ed71645e43fadd900c01646d7ae30605b07e90ee97a2fd73e2f02700f032a10b628d7fe500dd1a43ab738c4a85f4ccc929869cc6b8229f0a3504c0585dbecf240dd6f8c9d471c5a044e3c561bb012a522536df7fb2f7968667beccb3e4cc04211d5f5c3fca93cc32434e2b494bb18ba7b29b12ffb290347dcfb0ecda9e09bae747701e6cb5c28b1e33f0564150dec350e9b6153b1f99972bc33e36910a7754c93a592677cf893bd2da0e13ae887717b027fd96ae27c1dafa4317ce3b3877be5b45f617a8ca86ed11e1d222f7f7ab5692dffb2cdf7566323fbb8473a23dd5b39e3c85cf74ac118f2125c1cd1d9300f179268c48a09b94b28507bf871f8ca63bdb6a82dee29db8f93818a1d3f893810497235f23e935c5b0c315061cb2495704cdfd8d9d3bc9a018a9c3af8e53c48b9f7c878dfc9659119f2f78d3b171872a396bc5870a78ee5d2f9f8e2f57cda80f5f38ada4368303e318ab7dbdec12d501402676965a30ba15bff9fdec944d5b5c51bd64f74c4e30ef312009ed937bea5520b428940f47d895d9ecd8dbe1bfe3b61e07f516d27328c71411bfbfeb8915acf1f8bac9f17992ce1765111fa22703cc5b40f7fcbf26766b6db08470e2e1b2c38d10ed6b8a4f98aa437179df3cb6627930b8c4383c19e56932e9363f16800efeceb4c4463a2981b495907886941e895cd34b28b49d5f0b0a3e1915f1904685b4f6dce747246e154b8bcafb520eaedde0e60d8867314f5d01c4a81f132e408e8b1823f4bb88ff30839979d93f1d07dca770c7c64532a8f511e95856758c876dc1f6d02994351ff6bafe71bc133ae2bd51dd9d8c478dbff6aa021bd757042ff7795ef592838a6604a0ec6282a4ff9366815821670f3eb8bb2de34304313523166ac81119b77ed0d684df825615a63b7e50ecefd2d749f9dd2725d15cced7ef27f430cfb0624d01fe987e676a1019ba1e3c4a4fed7ed6c74c4c98f83d3e10e98ec7e4146f503c83b32d3470650b9f8e928c3c917abe4c5cde32e046fa8591e4c108c62b0abbb872a47505983b923a2ae25ef0e5c7edc54fe3258f2baec760da2269796a4703b45a22cd9bc1b034793307599b4e2cb3471c581c859aadeaa342d1d83ec4526d614fa40b4ec284238aa40921af24fafe9acf50d8cb1c1fa3e743e0a09b0e817df40ca57dee5590abea901e76ce88da9aaf352b9d07b8db425e199e9ef25038e2763b40fd187798a17fa7d908d1f871422f4a672a4d933059b179441364e0e7e56d055050c8bbd5ee5798cce89c6833b21d74fe86d47b0d555c7d3179938fb8540eb6d13e8bf6790e2d29c5d95eecc9da3ffc6c7cdb39f8fe867dec2c52219933b7333dda64f4c7d1df16833dbe16351e34d070eb79872fc0e2172c399461554d4281564f6b03b9333918bc59d7f3935be9b0d38edce7404b58a771aa98cc2414be7de65a515414103ccfd1ba2dfd39a01a2260c9c3e54660b19539ecd9df32be348906debfc7219286a02b78361f935c8cfb124c62d31b94e6c3609e83fe59fa8bb3c4ea83408f4442f242fc8daad20afd421ef6a9e0df48ce4ebcf9522491ee0b111b36c76cd3da48c6587de4facb1c7745461229144ceac51174db98f65a3ba8e0afc1bc22f436de03b38e2542ecc41116af29fc89bf94c01a372a0d0ffbe3e7ba67a78f340b80a9279db08fc70103069f09febd667a88e5b1551fc45218b6a51686a907d334ffb7d9c534656054adfe988f29c8e5005b498f0c242fec0ab27dab0997993ec4fb79dfb8c9bcd7dbc9909d4f8d268fbc17f36b0c304ebdc9a6877375872ff174cce7e67a0ad24d086611262d6894d2a8ff201aa895daf8d002935fc4b470b2dfedc2bc6ad9d855999e428625a3d0e53065c60118407aafeb0aecc15a4435667c133f0580edbf0cd1eec4635c402ea6e977f08cc80a044a459003ff81f0e164539c1e392a642a4ff94b85885f640dd8303e6dcb2eaaccaa6db184e6781ac1660c9ef1aec6f949df350290e7b18609c60d27958d6d3b7054a681a3e1c7c60089477eff00d0029a72010c665bb743ca189158dab6460130dc68a052598db0030ccaade1e239441e090269545957c932b08c2ca2270d0576d61e0a3ea0fbfd47e7c4708080a97c192602d76f37aa63096902b0c478957729f067724d2715b3fd2d4655e61146422b586ccc62337cd7d725a36f5f4466bf949077f15ad373f0b6b99be5a9f1b66b0d29410672725fc5b8fbaa1abf0acc7af2c1b319ff01f9f1efacdac225f7a4f11503306de80aaf6b6935d11c30a73c07608d401e3805dc857952bfebb4c385092c06ae8fd1d506a8f70e1a15e2bdea03664de048594f649afed2850ca0a6e8e9a7714255f390c78c64bb67878052e42026401708973310fec12d6980740b3548f496097e1fb8c750d8bf8d70a3640a54a69982a686fc961eb70b5301242a01be4286522d3f3137accdfff4269687ad63c79ddcb6e879d74e4bc60ee12730bdb5b24ecad09fdfb138b21b776df8450c98cdeb9f3f2d6082876171ab81af62750e2e07849f20c01c7709994672eb766f38c47c941d6fd09b40ec60acf6027fefc70d16b1659b7ed5b82ea5b0da447eb05c80b9cd1e760583221eedc0b9dbb077cd176bf3df49b2deafd3383d65694355e130f5071ab7e931a39e95c8db0590948e6e20682943075b7fcd338abc92b093d264228cd5a38c19c13bbdb9c09b2e00180cc9e3d6187dbeaf4bff5636efc3066969b33040196c5a158b01b31c6d8e30dc1658a2a2dbc62e3a8ef86084e221dd80fd6f8104c9f10a76bb70edecf7d7ef417f11b141e0b2a2f96317fa1ead4cde654e0c80a8d8e87fa26f3008b93253185f71077b0446b6bf574f0b71e91cc0da40310428122f1aad64774d606379b558422ca0c2b5ffcb0d0a2c15d899f6884c34a8161b12fbb601e8cf72a74f765206326965a935679ea5de902276ff1c230debe98e3832bdf78b11dd69a230fa63c9e9964d2ce35f76bf038725c0c96445535d5ca17c27fe19fe7b39cef1bb47189da01beacc6d00360112a1ed8c6214638c0290d223f2d4afa2298a53f8aac78a5b1f59e42af050aa8087edaf69d39860bd3fe2bc6baca722e31239a3b37fb02ffabfe74e1d171278d10e5e013e03279ce383340eeaa9cd1fa8f04a4bb58eb23cf7f3edd0915fd59851a8f48c018ecfb5b5025969d1af0bc73649ddecabbc9fab6e957242ec54a20108227bb1b5ef76e9d6fbc6ac0eada9bea610d2e6925ab9219f7c05a7dfd3e3b74b06b0583f70868eb8fc0488880c49c00d84e445e3ae56ac3d5bf11faa7a120780f3dccc58ba6e0605351b27d71f4e5099179a1f154727e0c5ffe9f884be0156f40de72dd375528b9960c1b2c15ef4fed9374a28115e32a134744b011cb9fb8fab19b978c6f6c2f2d32f880b23a1de8667b9b0b7a8da2d0452085c8069fba2312e89ad8affc0213da8684acf4fca5d7bbcd16c13b126797616ec4aa68829ce8381d75ade1a1feb5c9bfd6c1a536803d677923f98748dc091184c78948d99ff605ad627d4ae19dbe8697d372b912e026e049a96c896c126449a8d32baec913ca577e786d4c3ce408071ce82129e76fce8012f0fdedb0a06733c167f67cb35eba3f7458b9a3b38777c5277604a2923809b94a620024a365757da9c6386bf97eea27da70b85f92b4a515f439cf0eba63f208f422e10cff95018b754e30d869855fb8c2f71ede02fb94b0bb1191f28eecfc5fb489562f4719633a557846c4933dfc41f926dbb0b921462f1c791dd718819f4c4cb2585c3e80144c460ec81dd639bf758c7b7ef0eabcd4196a1b3d065dc242728e227985dd1be0d9880d35476ed1988072729e8c5c622825e7c863bb183f68991e2aab05cbdf4a75fa05bd6758680991572ea93dc1a3fd79d1240c51f4f230aac07ff7805b4471a0c5cdba64aed159cb4d60df6f73139a7e2d049cc503600597206a9ae06db3c4fb4d5c2d8bf18082e34de02e3c7027be42bfe4513a3d2e35b5c74eff31f0f54e50bf9bb001829091269f71dd2f198527c984f6387706c9edbbfce5828ff0baa2be822018dd7d50e4c7c8558fc0311e8e7f8f30bba088279b77925c3b54400c64e5b9f5f1cdbaf0928e2d1c8e97758cf6f28d564f9eb433505b28019c91ae0d25d4d39b4986d16528f2c7d8b36f237b31188fa2be3bc302d3f96bfad91f70667bab26148a47fcdbda4c52a7115ba08962e617706ea4c226526f4141d5637dc7717f87a95d545fc1aec45a45ede5c7fd69b1d4041c8534f73346874e352c49b384fcf36945d247d9baa6c9009a5e54123b8a2c09a7de255e4d1a8c907d82fd85f5ed3301e87cb3f582029b0a01062cd259c8ca2a561348280231f729e9030422a5e47732d214538920a888e49fd1d1c10e23ed01d276173c2b005d47b8f1d93c461b3c798294820fd154acff63085066fb3fc90bf1b52394f00f74e1e7dd096bebdde538673c92fd7b3d0f9d9f6b309ca562a839d96b717cfa1f8a1fc78c507d1adf83dfc323d50dc5dab554d257be1b0f9485323634f50c8c77f881ff57d32492dcd43f82e52de5d341507191967726659f48ca3e2d85efc8a080e3b9b0e8348fefb21474c0ff5a2b54c4acf02229ffdde221923ff61a040cb2699620c1e1dd22681543ce566cbcc8ad40b349402d03ad3096d8f70e71df507f53a7c12d08a1c3ffbde9444f1bf3f07ef2d4a44f3241ee8c24bbd95114d8c460915068736046a049453798412a8811a1fc115ffe0e28d378a1a60b811bce0e572342963b0a04aef48952fc486bc6d829a79dfd0da1593092a43ceb490e30400d098a7ba1db3ff8e6ef57478758a0c765abf4e2e00667c69553d9db78f9d75b0abd81dd4d3d9347751cc2b3f5f10aba8f75d5cd1dca256402abed18712057380cbfa1b957ccfb74c4d3d8d38f7a49cad3afe0bb24ed139e96317fb94c421bdb4c694833acccc005b1f4fd8bc3e8bcacfa83e19ea9f066a0f068cbb9ca83a44209c180039a6f1f79ae21a55c1d5e764644f1041ba104660dea11133e2247abe0baf3b43d417f8c5ab381e38c6410b1dba0c4d1dc961a7474b27143aa5b0fe42aa24a37ea86ab4386e62d62cd2f097aa789de88782f5600722db994f19e5d39560b458b0d99e51cde4d670968eb41d244af5ab0844cfd0d0c203cd7f967091ac321d3fe229197a5f7102cc6c6cb9583c65c54f3439818e07380272ce014a441feec4761d365954fdc43f2780482f8ee9d5f9b22d209c8e50e03877e8d99b9efe26f8dde6ef70d70dccadb0e5f91bf860b62f13278fa074a1e2bd5e810e1ce10881d5e422e439dc9e417f6658972f4211e349a7ba1052127ce6c4d3d933722cfd08064f975b9421a83edbb61263e508d90fbee899b80ad99e89641429dc2c3362197661490e1d94090007c3464ba29fc3422e3fa9ee5bbbc96400e2b7eb3df43c53d9bb2a32b6a2e8b033a79981eb22884b6b310b66e421a303debe4d0df88624d292e3b6a93ec6bce156f77c66a7f4052dff263ff8fb8c4c5cbae20faefec1c21ed10602a7cb41865d223b98af962af4c74a4bfc439467e854fca3b722b74d7ef2163698903f3fc49d38c9c8f0ad9762cc006a9129443c02cdbd874c0f1b63f359b79c09f7385ef32ecaa3f0ef0960d6432a308e30eb2685d8bd0dbfd6f6810956b6b0dd1dfaf5bebd2123bfb149e330963473bd80922d3b42c79be4feb3b14913e4840004adc077667a2a7039ecdd4328a9f3e9c2b9d7b126081e28013119e4f84b7d295ffe24d41db1a789c2ef19a55e8ab8d9a8e05579c408ef60ee92e101c5efa232a6c43a8813d3ae699e8c9ef33da1f456c879c22a305518c27d4837c251f23f3bb261554912d90d81767ff1f15603a58c39af4e4917f98f60d29d2b6fde06fa13fe60cbffb6fc48ed332cf3c5dabf2e41ecd184e507430746017d5fd4e5f3041d89250f3985bd1f30ff6b5a7239364c79f865467869136e191ece644dcab1a9c6afba4080d18a2244ab4b747e26d9ddc80b24d6a937d419bb339b9d55b5b8a84cd911862f5996e4dc3a50f52b360bf2da61c0ca4f43b6cf51f52822074454f1b1490f0a42db3f8f99bbe88917ccfdbe6c3d9dd7b0cf8043182668eef78d30d0f37890259d9a3f1f1aec1ebabd6c6dc42900e253ecf8e48056d291a616dd9f2d701ac008e587b9320540767bba844e12432db66127f470700790e07fb688667a032ab53b29d940f82ccf6170cbe4b1a8a379c8b95fd20248a79675038b9de80e7fbf6c223ef1a29ed993f0601ad102ea73f10b44774cc1855434c4fb5fe91abe0e948796c34a0a3bca0f282c6a5ba60ca8de28a13e795788a40f51e792844aac72d8ca8fc631f4d8c13acbc76c91c8b50602d05148b3ddf3e6b0c7614c353f9d3b56ea9aedc5504361559c07ab75062af1d63d0df1af7e83a01c70244e7ca243c002f2eb6908c3c9f264e5dd125ed5756e46cc7a8498cf3a9e7bc831cecf0c6651172ab1950148df4e7f931638a72a3fd85fce7e54b6c2f0ad0050d6a1f82410a93b8c2a697261653fa6e8375e9caec678520ae3d0bb8ee19be9daf4b380b92770e4f7a22132771e70a00635d4aa5adad081f50827193707caf3c731718fe46353bf6c6120b0f4ba6114caa8e5b21567fa27a91ed716af6f638d5197bb13722e63c4717a56b7d579aaa308fb9188a6fcee9f6a31cf1b1f1c24dc6d5f073a291efa8e8a1107f97ef3beb126d62ba109ce7be44dc0dbd3ca09214477cbf15762f5d2ee7369212f6407a8dd575c458db70ea619f0fe7950d2bf2695339bdd731ee20fbc4e8cabd2ebab74a735eb47104efb3de8194f4e45347cf57adc778b44efc9dc167d9cb01a2c358eb99b9310e65081bb5f2949ba330d0cf6ae459e67a47de560b17e15f2c57a0b545546dea4e79e6fb7fdad69d8e7fb2ce6f5c0b9aeffcfeb264d382e6f388dafc3930702e9491ff31f95939228f288013436ea3508a204d03f8d8ead2b87095adfcc7835b85eb1f2199ca947197cc20c81f1893de10ba8c2d23069288db44b3dcb1c7d9080885790e525957538d4bbefa36ef9aa05d0c3a7fcef9a732d8a39d690d514ea074f4750e456df0bae0e77b3335e9d4c491248ae5c0bd6de209b14571562067670887f001cfd1e51e30f788483174c68cadf3c6ce662366da0a9ec70c8afcda22d7ad531a2c2f6fcfa569e958fc372c7bc0dee7fa1c5b193acc03d835880d2cb6ffa5c88b170a6ac960e15f2afbb712845c2f86f51a667e82cb3c7a1b699b57388df57102707d6482d75da59d7ee2a36da26795c38eca075bf6e8711150c78a3b46ecbf8866c60597a199863b67138a3d5dcfc5dac5c6095d56facfb063b705427b9878f754ebdd3d6eeb36a2d89da494e964e1054e7bf14b09e42f4a33c08161091cbdecb8a800b4cc2f0488306faaf8979f291c0667a2a78ff9c53041d71902f286f9181df75b5393e64e1c7c8bafcf798e015c15e950400fcb281b03ccd9b6f6820c3db04922f68ced8d415149117a16d54180260325b33673b8fc2f72de96cdc0e40140468a76d7c67b45920e61af27e330b373031ca89681fad48d9e520423ff699eedf58235ef9932ae7494a57582891fdd3463acfa0b369a80f7eced2ce7d22d51abf809120f76b2b17cff192520d2f0d06b4ae3f0c9cfe2a924ca360767a62d483a3b329d7993c4b826daeba23859ee370c223b24ad210de73599ebd79eaa3132f49f019caa23bc15989ce2a1df765b709608c239c6ff848d74bbebeb59e7552c8adbe4e6bf446bc6d813e9e20cf361d92597c6a9119ccb52a7e5d9af68c0b599b642ae52829ccfdf95bd277f41f12259589e3ec9fa52081aecd78bdd7fdc40555c133a2c1670f956954a7c9609f927c222c8ed4868628aa1180d9ca13dd675cf1a833fc19ca1b5241adfd0cd9b39ce3b725ed0245bcf6186d674b42fc31d931091e23202bd11e257317a08fe0e39d880cc128cfd5579921182bb5beb27fb9b5ef0f4b2977dcfbeea377fb7f0c26988883e44673a920ddfeb11b6428169c9889308d02c2a60b81d17b7b1410138abc7932ca9a86b39700461533eee676dfb7f35a00756f2d670f4c3d4984c0dab5a16862aa5e0bdea5f9d3d3900216c07102fcbe0953f8301f54770f3a72a37ecff2b44a4a6bd16de2b2ca19aa8aeaf9981af11819b02bb11a90f929f7636f1f4fc239ce4b6c4ae804ba2e8d78f69ac54a5a38ff3e163918134615aa20235089d0d8e2f16ba5124ca4d0ea9ab59e3634866e1447bbe7e67cef4c154b2cd239d7954b1b9a6a0e3a55f68c9b68c8227cfbe2260773ea40a1b54a1c01e763aaa39579248bdb3a45c092c51b8030159ffd6d89995dcabe7d393b814baaee6add8d98575a09d5320dea0aa9a3f163782a8a99e15788c313eecfa5950db413018771d8a7dea0b6a945a55f244947e9df4143f3e65cc47bae80d05d76f4e7ac7c951679efb43effa1eff3165475ffccdfa472f7175a3a2d24e1bfb3609e6dedb7cbc47d6a0f9b4e2a05316cfbc904319af11d031301f1cfdfbe3940ba9bb8a76d8149ff6a1d8854b6faa1dcb752ffd90da8478ed1ea5f07f00c8ab70e195f37740d5ae3525d84ec635e15404a560966736e1057b25402d67182adae0fe81e57f5bc6fa53fbc97f5816f38ef09cebc0fb11d49b58f1538bb31284e5d34b90b836e755feaf303951374135f3241b7fb228e578279c1fada42f21cfaf3405799ee6a1fe58ee9bdd9dc91c5bfe56570839c2e28cf6f267f377cd0bcbbad60a726a1482f75d446cf30a36656c5bd11837e2e47f7942594f6ac206f380c7275face92c7b54b50129e7986a42c69cd682c61f726020a5da148a7783f147fdd9d62ece579d24c718b8db0730047173dcb1c692b3b9e8c46137880e1aa52f36b79401653664efee0293b720de0036d03748a80d78683ff7ef3b3dac91556aa0c1122081becfdd0342d6f07733f0165a315c620b17bc509139d2eb2fc4f292dc5cdc043528e62fe7aa5b3e216e8d5d571c4ce51b29b94ecc96b5c88bf871279f95d4b68804d6d77aa8f210042792a93e785af7acef46627e4a0b33c0c78315435b8b36f920c41c4a48c2b468deb6c06eda22600ba2f51dbeaa3b8a0dfc3658d56c7c5bf4b20d77bc3e8761549a3cfc609ef460b69d62a079fe9af56838f45465011e67e406c41672b22b17c67dc7d520b6a0fa296dd927f2a03bcb9822b441defd810941f8004cf14f1e539fa17520738c2b1b817a4e1523266c79340e58dfcdccb59457300104071b4980961f81bb47a88300db63c0fa15599bc312a17f913114110e9925b39639f4938237e969a28723f474242aa2396005ed8bcd551f383172ee55a237560fabd0463d1fdfb878a746be3f361020de810e56c4c5edc099e3a25cd469781530db37b017f13e69839d4ad0d54766cf1162a68a1ebc2a22c6dee265cb81d58ac8a79d1a128cd1e6ad80bd3a8e5b454479f4a2fb75799522d1a5e2af982e33541aa8c06c932cc667ff8884c3c2d79cbef1f732daeeca06f52e4758d18f30de453dd5e4ca10de9eb15f9796daef8a1a74abe7b93857950cbcf06d2c38260031d3bdc6a0fabc8d7390fba57eb43596b5a6765c077439739eca5e1dfa94bbdaf4480c7b0bcf2ad597085170a5897821cff037f86139b56b81b0a49182528cedf96c8347e8e5ac8c500d46d002d150b86d0ab6d7b2117587e2c848dd92ce504a0b605b0656da7e1c70aade5b0a4c8930760ebc0261e7ab0bd0f3615a58b3d4debdbbfc64bff818383b217ecc9439581232f6231a97ecd06ad794c2de66fbd151207c8e1ca4af812a764cd3c1dfb6030f2fc5f0afd80ffefd31b0cc082cdeffa47f34baaadb4a7976db964a3bc1002bc93e1974442ac37b08441e939be6b7f9599f86638a1238d0108597feaebf15d454aa316f1726eb04cc2b6321d6785ef71ddb5907752a0c71ec1039104809b2fe50b4881b546c68ec753264d58723e20a1a0b08f5e3b784cd3c8305ce3a148dd3e4c6a4db6c869458e9cc23573f0a3cfa5d6aef0b5baf50c0d8fbae7d5eca6a7e9d69d1b485e0da2762950a6d6566148f992e77ed7bbfac74a4e038e6078b2a4d6bdaf000961d840e0bdaceb5a745d7862e71125e638f23f684288d610929dd4cdfa716f9d2796468942f7ecee62a5189234080b609f3540ff21ba4fd02717a32ae29a40dc48b291babeba4f58c72291f4de7615bbda1145461704ad7847f1f630191739bcfca704e43fb87ef60fbbcef7504bd9e99e2fac38cf68ca6f527f6e6603cea6e2c6a7e9702dcfd4416234e4a0e351d85459c1cd59de276970e56406d82687aa6c38debe6cf9b8b49fb30b8460c80a50658d47ab3270e8457cc9cdba9dd4794e3109c0e1e95020a8dfccc28924ff9e70800179c9ba0281e8285e9a9d187461cefca8eff42a0e4ac0d257657577fb5d10ec4faac9db5907ac3414b4cde926f86a1c58271426aa13f073f075b49a0b88882a4f9792bcba9984162a499c06f368aa3dcfe2f9aeab9874c6e217f40ec1876f9066298cb503976f1050f81302cc280d0761ec4afb9c74c9a5565303d273f3fb39c608076d3c5c92e2721469b821157c250bc84d6d2beb928fe26800c373c96af91cd7815c6111511600e2947f6c882d374d77d8b353e0c55df12549749af853338103ba8992ce76baa7b906b38fd8c406af12f0236dc870f685f2ac68020f203663b671f623480b43bc8788a3d3e8cbdf4dd34d9b4acfb7ba19911ce338da172b0036da0cfda064c511413bdc71f1c09eb088ae44f58b6a9834ed0625d7bd87164a1aeac333bb41040ac4b4b80c17970c5b6d90445e5313c31c738423276974ed45981dea64a2f98e3a410826456bc6cadbba9a1a62fb2cdcc80287a3d9da40e71c348da65949e818f356eb8b1470614004ee3f7891f643e9422d0d0a0b46e60de70170f5a0e4b32e9395a9474fe1e1e48253c5b7b31443a23f9bd1e15c0071967f0eff22723007b8d6016d2dc0a1240817038710bb51d8369afd05b39e05948c67dbe370de45d67caf626a82bb207c06e3615e9e4755a5aa3cb7d0ca80b51c2bc8df208d610d319ee58866681383b406dad3b4c18b728aaff789837aa772bd007a6ab4e592ff829be467f0a497e70c93ab7adf0725cbae83f0e0b6afb0782ac3d534088e2e8831df1b50658ab485525f124f813a4c9e58616f11c757950dd1e25d8d0fcb436fa36f6e847cf05073fbb1bee5f1799be9af63c04f2de3c126532c7a20846e72df11e588c780b6f44e12c492de4dbb96465afc29f26d2c41c0ef2559b14ab33b42f3e8f513caac764f3761e02b86cc5de18bf0d81a9d28008adbb9eca741c6e5f035d72d913b50c834e24c40c5183d88b50f9b4aa1734c743bd41a27093602518a614c6162b821206479e2d49b129bcde252cd4617b8f9dafb26a5b338eabd7361a80bb9598d635faf5b07109faa6b1b2afefab3642d03f5c4071b3ed5931abbd555eb22717ba1714683e87e77438d93e2f1a22a6374febbd2cdec79646850f81c676d31c679f6f6d317586e02c8843ee7943b6db4f51bd8f54872b4cad60a584c1261f889d113fe7c0dfa18143adf91a573e06731df817a520c3101b3b0dc4d81847ac9ee1d82cbae8c38af1cde9c422b65e70fd5ba95c0727601bb7a98c4a67c45e684cd0a473a1b71c489378a1925394f9b588043e64948dae712dcc639a7df7e428b85e77c0691fb716bb430dd630c070394c98564ecbd71e5a1ec56612307248ff86af388f909298aba91237ffc1ce814367d3a1558cee555474d1f3128b97805bfc3644edcc481ced9e25d3f903d203b626d9ae6ef3aadb7f896d9775253670731a27a9eb989e05e471584912d6bab9ec10980d10944cca33b7513f16f330b9c7a350b93946aace2f1ce87a3d93f9d8a716a1067c0d8c16bdcab8d5f832e20c897115729eb526b81860d237a7a21b70d7409462205e7f8e840b040ff54544a65294e2d1aec7b614ffa5fe75ac66760f2822a48a33803225a2eddbfd1806a200d0f3652be8840eeeaf82da4cf161d27177b44480bb96ba765ae4d4127b6ae829b4faf7533b1c774e075a0981e22969400f0caf4659b496ca3234d99dad1534682147395a4c8ecc2647c2f563290feb477d52c0a76dc50c2498e93ae0c1460633a23e57ee2bff1d151c294b8c9d7d2063fae2fd994cc3e136a69629d5906cb0498edda54da542ef5c2e4a78598051451af4dc6c63e130c8c0d69e06649666716b346b0ed65fb4c67524c9e043f01a2c9cd1bc2070591f3d78ad94cb9cba267221745464dff2521c496b226b367847b5bedc327111253667d0622721ee9f6cea11df9ad9d509a1694a3d26f929044b6edc645d4ecf167d960176c97af0b4194b6915a1a5e9545a6ad741e8c22cf42d266d8e9278f07052627f2df03147c011c680a71a406d166fc0e18bcb24fe08ac1cec1b2c9b0c7483fdfa978c78e3eeb6ce3b9a108e45f156336c86f2decf718f0efc0ac6008aeb5e0eda5bc397e029a67ee797aff79ca8be1f90e956952cd9ab380e3e902fb86e7d424f3b9c457d5a65e5860ddb42fb66bf49f4456e68c429e89b1a77210b7727fa913074f617eb077a6a4e96158bca368c9def3d5eb6d3e538b1358238f6efea429016835b8bc55ae98abc706dd821b604483c64382de9e9a699530cf16500dd604f0a5ee0768be231608fc68ecbcb3fa3d5369e0b081ce2bcd1f6708e780b0609f52caa137e90ac89521a3eb0b92dc9f2d54cb0902561b5b83d4f5070499ab4726d31f543cb7a8e02c38e0af30c8a920bd4d4327261730b986436d508793eebd43bba58a9a075dec6528827369257affd38c0b6b67034794be4387123e23553ab64aa9c5f62f1386d4c700d7b1b982f489c2137b46edd6ad81d3d294b5370e4e9918278e6b19c81d672c406ac01be9174f050382b2e43d68c8dcc05fe81af36e0b269edbf2dd0d4b2ac432d25aff01233b871b3da05d3d867ac13e69e0f3cc78f18e2ffa8b0980c6fe0fbb736e9e6700e5a4d4f95b21fb63b6ee2719f1cc573e021e22baf7510507d5c4c8755585dfd7bc490b743b76ae4bf33859a6d0fdb07415f53f6c4abe9619cd929271394486d3c367b403f20dcacddcd6db6544876d6ac1f37eb3ef09c87f892c4a41ba5534010faaedeff05dc2bc4e09bd2d2ce5e0c24343183924fa8fb057357d93f34ad75a36f92fdcf5c437036726ea1a9dfe22ff9ecf4b74f0dd9b742b456d133ef4e78316b3901d7e7cb4d25b8c293645c4dfac88da2af62284032673010f8462572009a011831cc441d18443132e1eaec54b24085a03765c902045a98de77a6bea6a88fdc9868b4e04e2b62f4d6fb9fa0044ff9c16b2aed36eebf8031cb3c078b64e0d692aca223da7b57e28a8a62f3b9ad9bad8222beeee72ee346520283883532ff048c389ab1b1eb35c89385d89f6df0459acc7e062226dea79db57fb99bce508e9efb0a7664b9528b7d9b5507415c749d8148f344ef48b3d826cf29e62b63f557b5da336f2046ad6b043cb1d693fd8aa8f7e7f954d4573726a0843f36403d536bab2e0cccc3989b9b3175537d7d6bfa8dc352bd6106bdb75aa6dd9db0f75d4a672cc9224cadd8b1d2b951f1739f70b384479097777073237818613dd2fe02d0cfecb279b792c0484d5c9cdc218d32ebf1e82cc693c7c66d273aa3cedcf29160f7b3088325a539cb9e9d9c2f6c1bab55e7d526da8feae65a82fb7c926a22ee32269e7f6cb617c947134ab2087c4d60c55a7d1b28c4cd950b38386688ac1332a6ac826b0095016596fc00ee16f46d2705832d419f5f03366765ce4f3670a306a861565f5498c15adfbc0e72604c227f0f6600cd01fd730d872717a873f0313d3c42a599301b5a35645bb22efbe954b390836403a3d1204137b22eee5d7e87899402e1bb1dcd87395059c18d097ae17f76abdadfcdf7cf04f9ab4bd9c1fc96ba92bf44653256241cc3a12450fc19439ed1cf52cb5cae8dae5ba12d100eeca712b4c3b0200f21b2c0a6c0905a15d004fafda417d2dc91fb2ad0df9d4f865cec4818058da785b602131f60ec7f1b93ed5d59c26905ec7b8f142bfcf7f9b5f5bcc196b7ff1ec4a4979523013be48c16a513260a8dca67271f1d4893fe016a5eab1591edac7f81b0c6d62e8e11e0371bb02fede7454bde2c8ab91b0f3fb49fbb5c45c8d0261c542decd115d273d7139f4e446f15b0dfcb97209bb2450a5c6eef2569195b07206f81eeca424968e5db49937ffd4a6b9629f967bc323bc89a2262c7c1acc8eb44de00b51b609ce2f318825e58db2d6f05ae91ba265be14d46469e75d6ccabf04d6810c1cefa226adc42817d8136c2b613601aad16a2ee917c663ad6f56ebe342dbb77bd1c66a708274406bb4c76129290c0c99a9a3623e5646d23b84c5d8dc4fb1fe56703e7162f2a0ccb02df141810c72ad96c05366549bf6a1463ed23a11a77545c124d755b730351b7959dbcbd181fa8ce77f720af43a37172c508d5e27a50bf3bde858ee41132e8586d38d76057ec6cff6aa8c79c9cb4843f4371c261a4a7e4af1b9bfa55414e942ca6303b3394d8c7c2fffab4037d9432801d242e8b67a9f8ba898de7419ce8a65ac7e12c9f6adb64928e664ae255641fcc3e44cc78b62eec9c335a3b22aff7b288d65ab6478b00adad0e46ba6566f22884a3ed293d7dc342d9591ef25b9920641494597261e646728ba2597da7403b2e1c951b5b9fd75662a59a195d27f37cbc49db1361d62872d4283b9676af97b88f52825255911a78590f5d434f518769b97daa40486639efd4aca696a1cc80febd89b77d854dae61b1c50da9b314adc1819094c97c8c1c4ac11725ced44defadd591834d72e11d2dfd7915db6a2dc5c0048b9306df40ea65c5c6b75d421b50ae6ba4cc24a7f378368c50fbcf3edb13278a5915f6f576bffa5a60b678a695468c83f71874922a9677e0cb5ebbf63063feae9c8c5df39971329743c2eca11bf9c2a0fb582a375efe235e79a0930e4fc745aef857e1cf31b0a2db12c06604534c105e55e38865f8e4591e8775e2d9519724889fed5fab742f31df8e58f72b724e12e4a5af43ae354511adbf3bf8078cefa5357f3db9fe42fdaf90d8be2905c7c3048c382d9247468b419ac1528d1fd73cb7b81586e599baf7b1feb434860c63461e64420c6fe6a8f231b670bded5109c097c897e19e94a2237a70ba381a06a0c9f556c1a785f655e0a9af81d96cb791a6291961495e7ff1837713368cd0e128e483f37a0098bf3d32f2ef87ef421d821a16b9f605e465ab0587dbb47636650e4c212ac61de5de4a4e58a0fe5d83ef0ed778af02ad1624106717ca65b5b69031f35f86727b160b0f9c3ee48793d62028f6e5c6d110daafb7556db4c74dd5ed9e7e4895b00e968acf9f5fd675fd0be5e3892e1246d2d3df999d8e9e9cf93f25d965b9b398238e477cb50e805280f5c6d175ba3d61c005a53916647b912e801bc2e582e4537955fb113f94d58a3bda72fe1af211465cad6d21f02473c7f25698f2610e9ab341dc5f68c868940798247ca872c1568291808d11bc06d1e506c37995a30d67f8ce026ae4356561ff236e18a2c46d1bdddfd1aeaa8f2bd4b0d1468eb7b970d8917946a4fbf3c52c0b24fa06c218906a6547ca4131172e93236f5fb731609f6db694d7d1df56f1e514b7cdc3410a06d817440a60eef5dfe7d2b3f2c5ff18560894c2213457c3582b1d5df570852f31c24193b4ac2029581dfc0d6b15983c0261236ce5048383cd9890ed7a61da23f0e079ca879636f511d3668f012d8bbc216661f80b4cdbd8fd7c12e036f190a5cf55af167059d2e83880da7748bf9b6dda948d57e5c59abe14c07bacc4b740146bdaafb2ce77015765f15700a7eac1522ca8aa42dabe8c1a5cc8ca25a6c6deaaf90b0cca579642803f4cef329d92e67672d5b272cb0620b0e268e0687c48339d1ac4d136efac83839a8a5393438e645206fd329b65977f77be64c3bde43066c000495376aea1d8a8276cf39c1f827a6d33ae645c2f1e59e162b454810b296c64fd4ec5776629970ef2831d45cac68d315c61ea6c2a104ab058143f6310c412d80260e3f69a3a0a4d680bfed18a312b399d8bc975c0495fe096ad3305918fd570b652020aa17db3652e21dde508540a6f41070bd6bbc9a4934e9c54af68d9657f608b6d37fa19d6acf29c1f567a47f90fcc42a806d81923071667d4d6f8b02769d37c445c73598e424000ccbf2d2c9d19c0c3df0aaec768b923f0f111af4109bb7a3a1c4121acf8f727e0afe1c0c05ce7a7cc7939431bc66df149c1732b9846bc92afe95d177e924dd42c8b95a1aa4738d1c2fc7857ca9b1b8134c194e1757bb1c528e7ad0407b6846991961433e7e8f8260889b098591ef66db1145fa5d090c944bd0cce02b6975857ef114b111a509ef9e2a69522cff1232295488003cad45235925364135eac0fa36920b5201a8335f24c26da9fc6416a86b5ff42b69e2f5743d4cb7c8db4c6430fd5ac87b57ec1e312a9aef853ed0fde8496dfb527b54781dbe0b74df0119fd8d61cd5095e2be6340da843c8aaeab30252a8b4e58f0ae8774422e4248954e846d445c9690f553a786b3fab7aad733f73544fe895a67c5793dbed6456157230dec5a2011a1c72880344f429f5f07e404545040f4053ca49456ae53e1a1009f008cfa6064af55baa8ae4b4a793b339e6ddf7b354f22b01f0947b59c35a9a7860caae0fff7e71d013949725664ec6e3c707cf3eb81f6fdfe40f809e8578d37ca6ad9fbc014ac2c0807f1e011e5e4df46fe79bf112fd2733a84a67b468fdba4e8e415db9b3bd69e35c48955401cd0ede7393e626b41cbe6c75b349c79f6b271a820e1f7911505f305b31f2641f3b7f5cc34cd6d1e3d6db7cbc22737e09dc0a0ff70ae53e9e4cf6898110959af9b1584963de70791f42c86ec387169cf751390b71fc9e6ad213d4de33474afee75c06d407bc61307606812000f0733429b9669d018c47326c5e1ba5fedf83e682953c664bde1565527867e4f407f92756881c356f8bc8f663f29e96ee201e8a4052a9de324af7abb0d9591a149b0beafe7fd6fb2124054f039a617f2865dcc044602118abe546fdff8c5838182e26cf2df5224db8ee14d92603b48d0a5f42f9a2caa89fa6aad1ef81ff89a03a735ec65136927ca7f558913962cc310d202fa2bd4599bd505544396356fa201bc0d81c6f2e39edd8a13a4076d65018e846f020ec36ebb619ef7c72da5e8df9baeb04b074e109b0c4c80dca3b2d1a84041e3eb98523786219010f1719a3733319f1a71c19b75e635d4846556086a480a988445a47117efcf2adf981919a5ce167f53c423f2aecb92d5f56305cb639520eb0ebbdadd8d6f27a28392bfc457ab2c6fb29090e94b8ed37b468c45fb233d29199a764485fc3623889c25b758cbbf2e60188a984f2a24e0d5207e2912c6a378d24159e422f85580ba6474d4228d2a9898d113ed1afadc2d84a1c3016047585073f8aadd3bacdf7c9c3f80c8106c272d1d0a329f59a35c7d39e24642d60f95622dcc453487e429e7a459351016af7214555a6bb77f666a7e12e3bb777b7640823acc918c0d2bb06cf2c31ac029765db390298c8d6c2aa82dba3b6ceb6263f59ae5485c5f8c693dc916954d009af2fc1476f5f38e309590a6f843a310cc210a9fbf5f97c7034c3f65e9156a7f6514c2cfdccb9e7c5a2e5db77643dd6fea54b890d2c1c9a1c99f2abfea7634851a9eae2e1454b46eb5a2c3e1d5a6035bd64da7c7cffe0edffc66db50478c2909c9e443a920d24b2d18f24ac86aca3629ce35f3c19f64a4c5f57ecdbc819e5afc2de5fc35fcc1d047acc9d679bc2cde8e62131145546d8c6754982209b40e926b2af80b3232191736e661e5eaeaca4eb9ac82e8e3a16fe600f77f600973ce23be0a52e33527e341c610b8dc92dd414a9ddcc1e7bd1f8e64f74b2f5484820ad7d874e36248286fdb690323040e96758d57f480d92c0053d7d507042ee50fdc22d699f95479e4de9b7293f3e559f43c428ba1bbe6fa7c6c7f231e9c5d9fdf52c53f0e2300bb923ce55c56cc4f726d2e4903747573b46e9fb29e02fcb84a08cdf414e2cefc7c6fd8dd9306cce74ed788b9d816061502d8b8c3024f866d61b61c85f26e3c506f594f38c2c7b5596e39c03547325ba5b64b3a476aeb61165c27b96b318c5a90875b972d72d5cec33f7547ed349ffcb9df2cc0b6aae2e9fadf8bd3e63f91b0a5b9084ef2f2eb2239c7abcd80b3e242c9be83c2d3ce273ffd7a3fe6c49435537089c540100fd15c461c54a4b0d0f257217dddc7542ec2440402bd2e70aec997d32fba47b0220d7d66806e9fb827f1f32758cc82b4cb2af005b579be8462df143096a03dd671dbf13f6272f18b02dd6190718e0aa0ef0d2fc7a31154f122b8e49923db0ea91697ab2d2a4125df9ce2bbdbfcd0e0a494c09084f07b94a877737f249635e33e5a3a0a6e8cd093d1b1adec3edf1f6cfb00508784503772714c7f65d9f63ebafa64a5e54d11ae26b0d8298e1e0fd1e476b98798e12f7a77adacfd0faa0ef9c4a4fb1027dc5f4e1f68f88193e0e5e3bb6fe17457809d6702d3385656d095f56c46a5fcfa8eebb9f4948c63b8db048d2ee75b050e8e3e2535d4bdacca1ee06439080e05f1c92c4001b7aaaa6dde1122fe28261f4ff45e01dde5a6cde7eb3b59e4ff6edbda4372674fcb354c524b0954b90909d459bb6c7686eb031e58ed0872ecbebc6415e5ee756657eff05eff09aef9483c741037e0c8563de7348eb4ae7747fc938e8e7ef517320eb97a632efe1b133ea7522d5212b462e395d5353ecd08565fcf53dff504d635a70beff0cb796140183d9ac4e77f7c63f3f245cc6fc54f260d665c2663a7ef4cd659b5cc724da987de9485bbfb42c1af00c113c25fe77fe98960365e162d7893ad3326710958e88733ccaeedca082a924454db740bcd32a1ec9c4003e67e1c3f22a2cbf5975f1435ff7c7d4fbda18f31f4249965f224dac7a5efa115f466a2e57631eda8ffa0f3fd17e9eddff4eee1b5271187dec62a85253d89edfd39e4f3b907138c63e933c009c67d1b1a99eec22f66fbfe76d78dd94b3fe053478f221959e2e55670d38d28c04b4b874c8e3d254e343bd0d857e4202e59cf02eeff7da11350d43891d850505d37f612736917a6c3bb0fadf06217b64857974cc1f44bd21d9c026711ac4c151df17dff68dfcd33e8c65cf9b274c857159b6f983bae1ffa03f65066d9a93d4284bca5862fb1868f7bdcedc23d52318e173fe260c9d2c89f847d4b89ced24ef2f75c4753ec6aba5314054270af79ed20ac50bd3186f424d58457818ac13e9d83ac6ec83bbac20985480b58100beb265277969428c77e9d9bf994be19a8b3ef716fe2b66623e4811f7299bcd824057422203dd17aaf44fe857a540ca28bf4eac494e5409a6ac2b0dbe4c14b4bcf8e216cabc87d2fc13c9c2cf792a079396767ab0aca75923bb346fbc265d2ccb65c6517a806327ba3720259e2c19548be2a75f2ca6b9b64046c0fda77f8cf029fcee206e4f9bf99d7103655f2def3402f83cf769d3743cf7fd24f7aa91d227810337fd94d41c9f3ded6bd3c75f3b9c1f39fd1ed4f62864c4f5bc65fe483de8f1428e14b5af3c4a238ced46775136a63cd397e450dc1c70c42fc5fe323d1a6dcda2ad4b8d134cfc28fcec494c7c7c31159dd3f03dc6e0f74875ab7b56defe0fc00f8c9982bf0a039a36709b903098cbe1cb9272a93c24e529cc513cb4a92ae769cd0d5113d8441fea3bea16db5f688cdaf3c756bc92a705a4bc1a081a1b0674cb9d8b884ba7faabbe1faa4df9c9d7bf4b7fad257fc2aff70655bd78ebc16cf7613cc7dfa9e05f60c0fd69520f1d9b7b9e21872d98b11e564771ddf3c36ccdf0617e7acbb1afefd506eb71df728f9b4232a7131d73d1894b021f5d31376c0f6f2a1be7ffbff7ddcdf29fcb838cf88f83b47f579eb1cbb91ec61f07959f456434d8137f37b7a2dcdcf5fd43ce95c9ecf74a84876a62f2235c225c7b6c98f5d01241993be22744e01a9250dfb83f071fdcee02b758383d20d4bbf7646e587807acc11521135da4cb3c31ec9f86982b96df17e02cf6edfb79800c62717fda887e9cdfafa4b40f728112c625f85ea1ac83d7ef0bc64fbb586e8fcf26a0067d035a71f874a01aed9d130ad2dc7808ec6d6cd1ec300556a651f48eeb8fe829183dfd688cf42af325bff0ba9bd8219b3cad937c2f455e3cedcdac6bf4e777e45fbbdb435227f79bfbf611d9edc657e9a273d56e61c4ad551849fbf77963d825f8d2bb773987eb31f251b09475135e2e069a9f01b1ab583f0216fe81840fc60bbb57b38128c42293e510799d3517222d97e40ec37cf61cdabde7ac3a2cc739f743e47962ffa221d368cdc9cd41001f2996c37d4a91f7011506c1dd2c80fc68f85c5de70670bd9b2cb7c76ca176aa9b97b75fb2cdf98d036dab32edcd0e8d2e2c9d04b3402a46a08b2404f908cfca35e01b5c1bfb8db50c3b6b4220cdfd3309b9dc3f2c57e69285a352feeaa12293141b76385fbf1b7e7e3e95009cba03da376f227870d92705871bca666d506a6a7d294b62e8032525f63eac1cc43c7e5856e1a34b4f8cbe59c988ed2f96a4d8f9c17214739f592631e9f34b2c9efa18c41df6f3c130ee36f5832d4d71e0c7bc6ed4928b5e9689cfd914f68d99b67840f3e729a178fad8d214871f59ba62f00d255b6c7da9a4c5ce87cba198fdac328ac9cf5942f1f831a5218e3faaf4c4787d0c2bfe33cb20a63f6fa9918e0a2e66da86ff1d3706c119e436ab8dc916ed46efcbe91a1ea9f705367fa7e8cedff0002104def56fd52a9c1c2b65c9780934df55d05f64ecf7370fd7d94fa2f25aaa8a113de29c8fcf2aa52f13fa2dfc8f7e3fd2edf98bfd447fb3d5801998ed67690b82f4e4354dd88be7d1b558820b10c69ec690766fd2ba3b7dcfd494c6e96787fd9e75156a0a3f37d0c8ecb547b697d8a7b25812432d8df146d6e7c0ecc5e1f7b019f1ecb18c7464af576dbd59bbf2f9e0ee89fed9bd291ea7f12cdb0bcc0e9cb0f6c89cdd0abd3cd514a5f48e35a5cbf1b09f7770e10b1b45d507c63aadef2dd318ce4b071a7d487fabee2ffbcbfeb11f8a5d5e54a11fed87ea598e772f5268555a40c11b9caa8bcff26e815f2fca5c06cd83f50429cdc56a5fd0d5295f36606049e6c4730b7a201fef97b48c97a9ffcc6044d7594fb3872eb60f7e9501503c00b23ae33dd97d2be128966edbecfe2316b329c151083044a81bb24140b66d7aaee10d18b8fca84b915c96a091660275d9fb9868d9791bacd3e2a6300be0ef42efbccbcaeef73cfa224d60e920d5e5e91454df63c2d5bdbe1c00f99a9a5da94fa3cb4fb410e0f2203113661c9a4139db6090b1dbdd9d935f952972a51e4485e5321806bb61a49a481d93ae33cdb4b75283330cc3beaaea29acc369d3907cf71d0a0ba81e3082c7c3790ef2e4a94a0ee47e69d0a07938d41c0eaf830a9ea862f0ed2d1dbbe1cc82bbc82f8afb196b5ea18c1a74dd3c9fd457cec3c939863ac4293146c34973d147a505659aca9e6bad722fde7974f505f7a9fe8f8ef759588c85a15d9ba1dec516116455ad658f7e06d432d7590ffe28fdf99b085f5b683b96bfad6ae84665c6dab84227694780cd1c33e98e2a870c4e58fb6ca713ab77a74429455013230725d4f2d7d5ef915c2121e3ce6d31ee1bb8cddb4e860306645ff21990535c6a380f9a297a76f1a620be66e5ab1641ad60ff800274262c02272127d2a1c1813da0531c92801297c2f9b445610460fc4b0e72671c9de5906696ff40c8540c016dc40490caae101b5f0a767ae69280e6d006cf02ddde4e5325311329c1e1411ef70c5620f575810a08ec00119001c41a954fb9effdb87f2f82755a9b0c4a5a076d6107123226866b51a5ffc707c2adefe8094ad861176201f3c0971cb9535c5c650fdd6ee499434ea5528619d8a2f09b2d5a133a8bdfaecaeb333f78d95dc2c2552ce0740476ca1c996e8275a0a489aa35b1354dbcbaca11043ec884946d34c1f7b399ba4dcb3f4e8204c1523912b547ea395801c22d80d25578ceb2abb1112c5fe3781a48b68dc5b1cd73286a5599208b4a9617008237e620bedc085f477286b122eb0b3cb65323dc8ae7c676380ed2e3ba6bf1227c31fd6abd122750541434585c1bddcbe04767a83fc2463deb201e60a70309af785a7dbf76e6cc92755d408ed040a334aa7c158a9a5dc0828f04358ab7ed620ef81dbe13b758eca0ec23a36c400a88d31d8fec68a2174e17fe6c0ef33ecb550c55a721fa60e0ae008c1178e4534e5bf1df937d85d3fbbbcfe1cee40349e0da9e7b79da804af2025b4e6224697060edddb7c04eb13837870d6f1fbb973808fb5eab011a88b962fbdc0f5fa3f035b9b988c1a8f6bfecd01c0325375304ced071e230a395c4afd94cf5e0c08bffb6e6e3485b1f29fa99130e4c9428a2dd49643b372abfde16ad207c209981ab718d23444e655ff5e044ecab135bef2c01646554e258124b102bdc15ca13e4fbd94d1dabbaf83bf796d6fbc26315bfd945762b75cbac037e6d70cd3f60cc902008afd10d2388fe5c171f17fb70b97c6290011450f7e141a7c61a2406f23a603edad1646749a794197e1867cd78595699e83d99ad7b7a91b8cee0ab157f43d2ff51631ef0ff1fd503064adcd34153995a989a7a98cf7e783f2300bf7a97b5db895a093865a99d61bd3b250135d44247bfde652d338b74680cf638c7b42ced7e21a1024cc141748210334722985716917de8e4b23f6204255259acfc18133bf975cf50b09965603d40b43497d557ceb3d4dd1369408bc48fa6a902700c264341ca68af664721050c2c5b3c8735ac606ad6610efe68d3f19738b1841042ee5965cf3356f8ef55f000ae18c24974159d51418d9c524c63780fa5d0936503570c0ef2c48b6e9ea27d9538b198c452a71bd6767628307959f6ec029b66c307805dd2e1347a78b8aec45e84c728b95098f5065469fb8e10216fdcfc0ffb282ea53b568590d1d795bb6e0a546526e9b322ad20d64958ab6becb8ec574158e6c182bc3b3a4b1f8fa2631c5125f25fa5d8ad3f98e8e96f858a83eb3806f5764d485c22e1129aa0c60dec841ca6c4905a2538811feb047c61faab24739b6d8e825b736020462f5107193f591100c0ecdc08ec4574fd18dd2b26ca2deeb6428a286e5f6959b12714b04647bc58c44d02bcdfc00c3d37a9b6f85abb6671b105a71860891178328ab35f52326eb66b9aa1437579b5cf3c4516cd38df896bdb00a348f1fbd74cf3e50255d4da513d53cc4d0d36b5c9d97ac1b234a157baead7253374d3c7c71dda02980043a623ed40e88fa181a72955234775478647e4d2ee82dc8b8fbccbe769380a4de2ca957533a8eb6b89ec5d6aee3f98a4e0b989e6c5c2edc9cd566060d3404aabb8ec3b52614ac2f80b589d2a0911327a77754be1b77b03c3ba995d7e1902dc86325f03f013459e3149c7ed320990ec20dbd4e46cc881e0e86e969efd9d14d3721b04c6198a4ef4d48bab3ab3d0edd76531bf177f03dca2bc32b637c156e883935715933dbdb5308fa172d8d2d944fcb46f2db68412794eb56fda5390205d65a82d0743a4631dfecba1045b112b15bd70dbf0e029a8096dad07cb8d2d1157830f3e38d7750a4442bd7f69274ce52692a18cc53e55b25b54aa59b325bcf500127ed894f26d7d1ca14c5ef215902f7139594b4626736218b5bccf0a0cc0098a0c980b21e8541c2333d00fe3226e7cf3d46476274feefb3ff0297f4a5668073b79f0a18b10cc838994987534e19be268df70c2c01cf7ffa1a8d2472feea841b28b664684ea090def2e2b79305825bdd18db9b79ef9d55187e463875fc7f242d500f07e0e8c889fe684208b269ebb0a8ab07b1320f7eedd3bd6201af08c6a4e2cc6289704f8bc5344203bd00f9ce653789b7b9c57de809d05a529574e7fdd6d47d00a347f22cc11913d4a54a993be74fb65affe68e1f6bc24c8f80347ad262d61a14a57d03a94eee9ae3d7b5310d3383c486a36028a974d4216afb5e74acb5d94aa7da243b0ea46747e7a751b46967b61537089bd07a97afa9dd439080051c4c7adbb9159b4106d322c8db38db0e80259dced14fa4b1b9a1448f6b5a957a0444b6547412c6154dbe2d769cdad9aab6c340a25d1911a9a1839ad2297eaba8d206ce15497394d3b30a2461a29df0cc03e0cccf19c1d69d52b35eeb35bc50126eccb45c68716e39feb355042d46d642ad3d361c1da2333441dbb8afdf6d17c46139743ad8592f0839352cb07bca290a62110d0ad187b54f8180a1d7fb3cf40fcc521eca405874f2c475fb9cde2433f72a816203f66139ba923d0d8a188a6b0f335d2a7157dc90f68c1463486d62185c6ba6768c1fbe64bf73ffaa309c23963687cc0a6a20540f4884a39092645b2184055413475827033bf6696e31fd05c43f9c46b372752d3d15f45bd7608738d0f422686a1c70a2aea1588d0ffaa9df6841d105554ef9a1f9d54866659215b46b93affcf12aed0d323ed92240ae725869911f886d976ffcded7b2f6a6bddd20ab48c85224f2b72cb608daa6546a8e9a280e3d3e4d3d8bd129dc592a0baf1d241316a3c1359ab12b06c80e0916db5b701749305e21ddf68fee33dd98bf3ce8ae866467b66215230d20b0957f82cdeea65361daa79d6485b0a42430f7e4488da2d074c72e082c3c83630e252b77d70197ea1ad1ed6452939547e44bfcd9c38db789ed0aae1762af74807810d10b338b40147c3d88f0c28e6fd2eb6d2f278f0287cc90f94bf0ab9f61f21ea037ddb6f698da0c50690c6ab242f8427f8d235ce1fd285d3c10fd95ed1e54b5a74ff8dc58e1350a0cde12e29b9207e7d4ba6a909599e9aa1ae19a1b4427231f86142b1db01b382b183c4569bcbd62eee8083d0ef4e2f0d04630898cd06bfc55c6ef7eb4855e85a726467885bce31a37fd632637be88d1f2ae0f09ca2c6084dda1848f31d6910692f061d8bb71ac7b9d57ecdd3674b476061a47877f1cf5ec7db0bec668f9f4965d0a26ab2824ea91c41ee08ddbe0f29da7938030d867f9148f3fbb8a9f3c74809fba3014090a264cd225de493a6d30fc8761d26b488ee8e165d241506ec9a946ba1a79e69bf73153ae012c5f4bba628a66983996d4bded61c1b757589075e651722167e9eda52186c69cb82c1581c0288b05026de05181a8a1ebaabd089d141dc3dc1eb8298258675e2bb344a1aa6afe0000536ceaba7848744a1ecd0ae9231569b60ec9b008bc44faf8e1436d085b0243d3a3c0edbcf351e036c955fbccf8a46b672ed220d17827805a608144304d993d2e5379c7454932d85fe170671cb6e9ec0f60a299a7a2739c18652ab56db5015206906b24080b26c03e2b2349396e14a4c058db9698cac5dd8ae91f56a6f841003b2293ca98d2ba57e50f3e2ce0f1bcdb248c74e40929767560a10d128d84945a2e21930a6958fd418293611d0822aba789da33dfd99c85391875f71ab80cc00a0c254d974e24733eac4f25157beab80318ae104f4306719d55fce509ca5d1ee5a94a6cf361bb01b77c8dff80033098493719d5358aa52de40f19e4a4aba45bd4e855acd422fd33dfad4c098222a1da1d7702e5427e962d3bf7d1aac1a1216c59501d7577c0d6d04bbb2a294fe78d9a5486ee4985ecdfa096eac3cf34b8250b581114fad9c0b6c60ef6a13e4cb16210c68e394e414d54370e8494ad8b63ac21748052789c2235d4ec859f892f49434188c4a6a12362c9b0675b3e08bc8df97dacdf969bf30101ae9423cfdb46efa76e8b692acb69b449d456a3d240eb133534d20a9ad9673f9019411ae9219694275e1e667e534e78968033c5b1fb31d3158382341754d001c3649ba72463e000910003a3bf409338d3eebde296874eb65274cf30dd29a6eb0e2a0556a392b8041b75458bc68e747e0bfbaa05dd85ca6a25b65e8a5030c28a79ff025f3c0d0abcc4145536a19e205920c7e05a2d28f83f64076bc9534031e52d7115169f1f21bb7942506efa7f9a029aa51c0957de5f30ecae91cdfa9ca308ef76708b3657aa3ba2e47037a2fea867942fe382aa2668ede578574a71f67a93d0191bffeac8a56f3506960845d3fbad32210714d87c729e59ab0dafbeffe9047310055a047209ad1c62ee4fe592a506bc06aff256003d85173fc66ef080e87960f32600011dc010074006420bc0e9cd084eb24e2ae9ec8b8107b91bcede10695c14dc95e33208a673019c458ba22659afa5d48193a9ad474470893d998645aba3d81c95025121fe1c32cf8a13a36d8c33614f32653bb2dafd6506d0a6a3d2c63a9529633e94e100c9a0e93e12f986d8378bb4c86fd60971d1f2f262ca3525143dc1dc64c0df4f40811736980cb203563d1f9143290ed58933ac8a5bffd47ffab0e5a668e94ebace6947c14bb2c88acd169d4fb5fcfe4134d6ef597aef0bccece6a7985381024150beb4f79c90a2dfe34a0bf2bf05e57257a315d9e2a63baaf41ca967ef19945055c606ad0b46ad1062c4073415062e876789a88fe915c460ad2b737ec09aa8c4a46ddea3fb8e97f7321ed2c79c5609a047cf4034e27469c12481040969236f7e5b7feaadeebf3ff2d5a94fd7bebb6fa29a05640550442b66dccb666116b7fc9955b89101e3c74cb8f78ecc333d5cbcd95bdb35c8cb9f338db2d67ecd888689bb5242b19bb1046101e637cecadc50cbac7a01d33241248fa735b78db6bf7ad7356e4cc664267bef45054626ae7b23f99ea0c2bc876ef7bcbffd59f6dd2a882842bb67bad7508a1bbd1899c533b97e584c99243a687fe151028b57341a03f992a6ae6aceec9099383e54b4e4e182e39a16e3a6342595208a17a4ea11003288066ee3c60021c3291c8984825a02025270212083dd72b44808516042820258507a470020b0ea0400356306131408512da73c002d098cea44e50c0732d2501cda1fe32934443644a23116a8aa420a02570000ea16080de31d5d0e9cd98ea9f42212ef35cf3d447394e9a504802245060045409056893e9c4000294a9939248a6150660e79f1820c20902e0d093292100280070289442081d01104cf8200aef9c70133a9952b3019f08d87008de548f036a38f4a250a8a2463ba1149a6f4025811048a9012635bf334b37520c314300551e59e041e593491f2db0508115544881021340214702521e7082031a6002034a58800212700003e09030420108300011041000008400c207513e379d78b9528de04587339f360450e5e1012ba8541f3cb2250455556d024a0e6a704003386aded0f24695c32701a0c10714d0729345119c2c8a9cbe9e94c822c4104b64913242085770b08001421958ae00c0930a4766f1851755c585125aaa24b4a44ea3aa4b95d3974a23291668292848293da9f4a4aaaafa2b9c2b95991734c1248d4a0dd6428a286af082019c2a8454a81388079c74c249557d36a9a2aaaa103e96843e9a6052559f1d5476f4b1a4f2f28fa5aa3e3af85c6249ea64aa24079569878413039aaac2c01dd5070737a8aa4a0a1c0c1ca0aa2859a3aa3e9354644aa9892a335321d27ce186172e70013e70621830a28059c010acd8200f49aaea130924a4494da42c55f579c410470c99a969abead308238618b208228a18f241a23a7d9951c9d425352a8971629c0c3b158d0835ff64b2521a996a6a54a5aa3e6becf4481bbed8d2a7506f43557d1241a4aaca9c4c58d254a9aa8f1269aa800953559f430ca9aacf1b35a82a1a5455f529e4434855d951680695959d7a43557d54f01944063553c8a0aa3e820412831750f1821e18982e207f54d5c78f2f5d5a4bbf80d31187bbfa9d59ea0f73c247ea3479aaeab3c7a987e4c59246222b9f34d2fc0c4754d5270f3cd27c99d5697e8635da43f3b6a444654755f5b9e3634755bda0f252071c74cc81b3723e7254d5270e1754d5a7057054d5e78d0f0b58993a536054503d894e938efd92182f261dd2fc2b261d34d2927a4c36ccdf999244eac1d266a64c0181403b150dcf8e189e790af366462893290cea1f0b99d248e777469364a5c7742a89499d4c3c3f8cc0a02cc9a4e549749a74aaeae30689d483e5942a892989a9aa4f0a50c084122768838d284cf059c3befd92988f1a25d31724528f4e698ac2fcc97432f1984e5e52a8f95c5254aaea43821d53699eb0a44a4f77becbd791a98634a71ed4fc53989269342a8d762c9527cd511dd91e1948a81199e7429a755412d50ff36646a99efa51a44625317367a27a66f8a481465555694a547a85b80268a7a2c149c10a9c14ce54a81ea64c6166a64c49010329b4ea6346559531029408381480a2ea1a28e08502b2daa968580d287883dff06e7037b41bd80d7d43557d44c037fce9b5f4041a990021648c71328d3e210081189f30c0f8c28b2eaee0e28a2daaaa0ae1f381ca8eb4c8a251701458c0829a4855aeb8e28a2baea8aab0a4910d55f5a1e2af54d5670a29ecd3402245619f4482e2d24022512191a854d5e70954557d9c28f5dcaafa34515555089f0e5476b48589aafa2c5132e93c29f5281433832301107022f0c94789661c789c0444a26a69e6e90823aa921854333353a6b419184cb7b405b50586d27339994aa31d184acfe577a60e9ad9e5776669a6a685c16eb1a3d0e9a9dd3265ca8ea9f476c7543a994e278942a12c6964030c17a85fc3884ca9074ba9e7dabae5316ca9d3f492c69aaaa44e93c76e49634d556080a1d9059a8949a14a262bb0b907f6a4f932edf5d851b31e9c074cf1e121f4a9d1e98c9d7faaaa8f0676a8aa8f0e9f1cdaeb6933219c1322c139a1109c13f05041ca949d512a74ff94c209632a1e14ec49d5af0135e2a1821af14c32a5118967678472000b73c6642584aa4104a70165e034a002557501d45b5309c78410648101c78488c3003a88a88ad80010257c5255d54ca1bee09440aa4acf43834907a704117016b04405ab1e051081938025aaaa66934f062a40aa70122002ea04b22134380888a332e120c08b0b2ee0206084aa42634b3806a803c7004c849a5a70702ca9aa8f97aafad050551f9d2e55f5e1f299a1aa3e3254d5678b162a3823d4a0c219e10315ce08385455a580ca022b3ca0aa2a05449c11aa543805f8a4c229801c154e01c4a8700a80a6c229c0970aa700acc22940032a1c026452e110e08d0a87005c542464e248454226865424641248085bc08831e99cecac62671554996a225511a54ea32a55fed493ea498d4a3d6446280b8c405250f3e924a54e232a654a295115ce80c8803e8534a52df3f4f3249d73ce39d75a6badb5d61a638c31c61863ddddddddddccccccccccab57af5ebd7af5ead5ab57c718638c31c6082184104208a1bbbbbbbbfb7befbdf7de7bce39e79c73ceb5d65a6badb5c618638c31c6587777777737333333f35a6badb5d65a1c638c31c61823841042082184eeeeeeeeeeefbdf7de7bef39e79c73ce39d75a6badb5d61a638c31c61863ddddddddddccccccccbc38427faeb1e69d2d38e044e1a9aa6a9ede853a72a19e50510850e144a9a9706e2ea9706e20a9706e14a9706e04a9706ef0a8706edea8706ed6a8706ec018800044b04f32e95892494be97f3863227d9534a4120d6264557d64557dae7cac54a82f55ecacf2b14095667231e9d817d5f95f52a39d1197aafa5089a1fa60e0a6aa3e307c5ea8aa6aa26c9560e42845eae9923299be7c0ad5334568febfa074accc2a3c3d654ca8d268ee9850a91e8a7afb4fcf5cd408cdc862b15f3a337950a74f33495248d392e683f993979d4f6df95860ca151aace8c8d0171abe7c9121c945cc151aba846ea0d265ca944f4d3293c774e2e162e6c9944c2418c8cc305ebe8c290ca9c79abac01e18763eb5858c890403ecd9f2a51199920c65061552a8aa0a0d295547000a1a1a42a126cf27021f0854b334da199d2a6a647750f3cc09949caafa7c3e365555c30c28d24ccd5bc68482410a0b1798a969513c28fb57aacaa607a15415ca93419ad993e90c0a35bdd84f997444a98966747fa70765c3c9265555418702636715e81e35abd859457ea92afba7aab223530d7ffa1e1f260f0deded20c76c0113ba22c76ce14265cb17092624bf8809434527cb16301365eb573bb41c5c49994860daeba92a9b4b2c499948603a815515e2e19953a64476ae4fa82953423c9cec549b4c4203099be850a14ea0d488c2e610aa02e1054d0421611309a0803a8150a1d3f36882475555204de2a8aa0a368b09010c4d46a8aa295334e1a10af1a0a88c495345ccc8cce84a89caa9a756934d0e705055a4d3495613894485861e00e043060c3613a8914155f3c71e357684a1831be69f8658848f1d2441091e600c81c6061e700134526c0833783ca28704a45061882a53325032a290150c3180154c70e4260a2984984198461a00c88906428861060d32c490032342ec1c32831955151f21dc910b2c51f57021c4a701a0442d365923083facc04307159ce0124416673e5803022f0420884a459938188085058270c10b1a578a4b3801a29301d4e0060a933b80a8230067d841c70a1008294260a2cc1f617800040e22a88265014a6802444e127e78f1c307a10d5852460025f00472d9c00aae247244059ec0b281243e90d830f1420c36e0e50f4adc142c6ab00106402221b0090e1af8e191246e4c6021cb103f94c01f101220fd0f1b68a2033cc0344c3f600180058868c106277e20018c4f6e07d0e0496348bb2300677ee892860c29beb0001578bc90e634002e2e8c000c4e7301460c30448a1991060413aaf0a183263568620084e391410364d07451c5126c98b1c64043a68c292a84d9811b681a0778406084393a3913ca96185a245ee870068f295ad0710321d63853053d001770f404ce843e615af0e4089e3329fc61a60917b8186366078554400c278240801917c891480f3f4c16986182cc2339c043c7053361c09c700318303861464a1521d2e012ca203d937c4061e4007a5cd2d3c61fa1e89c09218e1e22c8780150b143a6470610089288173504d083001a0e1902e5820ba41611a1043d470f6aea8c2c454c02832cbc48f5c4c00f31a91f1690a2d2e3c3219ae0718094002a69c0153690c101d420373472469a2f37a83014414104ee480182227d008e9b539822508f9105480e66fa40d9e83c8102222480531f326c80121ef019272c4a79306105264c9ca80b4008c38a26829c5a08e5b4c5179780f1619350e64840094f18e2031ddd10f961068df800050a5600ca143894f1e1062a5c602a07a8c107098c000539aa4348e0a90465daa25961031e15a8300104d06146181e24a45072858b25acf0786180184082887ff09440c71b44e0a1c3102647b8b8a28f9d04f0615a2308e9430ece0ba61f680827f8e0082b4cd284292b94e1c28c690446acc8c11b41b050aac10d0482e0c14ca43406179160e1491143947c90e14b229f90d1a26401334e602d9ca2287d008112ccd1813944d881411b50380bf8c1b3c3459804b0808a4620d9b9659c9147151a28d96908d430650b940448a0d848400a4edc70d21dcd0631d8d1891d242aa458c107e01d3020edd0c60e7440218211240ad021c413c00baecc0e6cf6d080c9ae50a6053d2002ca101f98516609314370976141193067b2c024851a569907a0200707c8b0844b0f92505203937822003db051496bc14310420f433c22450b0e1fa07ad882440981fc50268a1e0e302241153c6a1061a448982644605c19646486944406d0c7944b46a94816308319d031a318dc50040d3474d41805400e323fa421430944322093842770c8608c288c07784143b9a24554e6f57050a60c40e4280981053dac9881a86689406c08e18c01c8f04180132e11410e2564aef0c104636ee00026c84c4ca050430232084286851504f148942b1cb89a7882c5243d7401ef1c34911cb0608d4eee133df4c0915011515c1b96e0030526f043841b814006e1240a01bcf03978400bc6088166be1b253c02b81349fe882c488c80c91d37781d4202d109833ac02f208d1e42ac4461c53602c7268a3c119e55e30a1cee1b8ad460d3c8037c596ca0b157dc4a6a0773d8614760c16da282e88b5a882235dc502586506a08d84083650b325d79a2a8a28c1a49c851a70cf1453e22c61f354a8fc02791640c0a48266042954841826e1181137af0482508fd361ed10289aa0cca3b20f103209512d3133c02d9848d26c2b4638a1516a2d0619039051e0e0e0c048166ea806202b4872798cc09cc21080aa64fe8e04107ce0c12386061c3031c11b0614a0a9774c183123a688094a0400978f832460d4d7cf105131e1c20031be41e29c04128924312290380d025213614f0482553d8a04048082740b0801d283985b4f01891c69137720819e00907f272851a1a48448b98044462c3101a28e30333d0a800870f0da4a2886981871a0ca281186eaee47401e20c0d00008c9d0f1899383b0842840df0040ce8d9018c8b460b3b90a08e1d7a7841112d1e70646787164a970f10f162c30ed51e3c3198019546073d1261c024385f72d0c10343686101951a00a1c36c303ce0c796a543059068116864045d72d0e4a383242890f2420e72702103983a20c12207275ab0a2020b04c0891c6ae8001756b8600110e410811082b072c327468cb9010d68941c5af0624c0a106082039123c41823ce68e96439a0c774694204999480070d631420c60dea38001818c0811149e0189951021f3890808a0b053013451a38a069288cc1c9e9121cace83009252da420070e04f08404345aa8b81b08c183067680d156b801048130028950810537985430c2a4aaf2821be2848014952b73c34d9736506c874a6c000406153299a3044ed8a0052805a8a40b4914b1c1ea819a80027040840d2b8c4cdc3040cead81075cbc81831b535ea8e10532ac2c7658114a0d538430812f6230c21e35e410c30b7c1ca18353030a7c84f282238400622ee1d9c356c0001810c38248ce0804cd097a88e1c0974a0ce161016288c9400d76900036b04822a601559cd001377c408581c49005943ac144873026d892060a56d011449820aa90400c25be0085c9226ae204910b0a61705600801deee03901cc219af45042d8a86044c0c90e92a4a1030d60502d28000736042102181848e004097430d3001342132070410a3a395f0211c306af02421cf0e58b320500800a38dcf1651481d3ce0a4810bf3413a06801e8923bbe540910c01e37c841ca401e6f2472861cee870c5881450935b415f0c8000f681621810e313b195821871a144411b3012f9920e0039cdc5ca15ee240040155bc2e4cf0d2814f2ac980259e84bc88c102851a50c08521bc4040a68091245ca0050d9488e1024064d0831c684081097c8880042d124043116a0c7203021c80041ab87069c2005b5470021a120002317a04b1620a1d461a50c426875811810e1a3c1839e1812882ce19d00a0639e48e1774aa3481c7198e7c61750680460acb942cbee83203385480011e3f94d0450c50584080e849962e255a4723517072ea0269f8d800039d64d2e5731a2971e7145cfe7040cf183c5c98c0250b4734e0801dac81002ed5068e3832c70eae7071c190304a376eecccd049105d2ee982071b66a8c3c230c6912e0698410a2fcc889717ca98614c0936e8a1844fa8cc90b388124b1c200f3f64b0a40d12e990120c32ac6005590ee08120960c49cc18983904943864c8800904788110f6033298a0012142d0634a145b1e7984c108030fd4961220410136da0043872d407c30c60dc2303bb6602993024bace8e0c516122ab103873f82e0a2c510130ca2c902f048a3858c4cd0d4a1081853cb69071e38034a8844cb0b55854be8d0610c2d2018a104265cc8e0802c31f023901512104609b278a10037acd83032218b08a7933fd24869240bab324f777431024b28806c310921407cb0e041462311c092071758aa70e304278852520096d013952c11c21a39585480018708861393484c58111e40320308c8382810890b0fc029423251880f8e8482848d0c93062629e88f93529a588185146ca6b832091a2b690ae9a28d2b2768c1e68e4564d0e40a1196fcf14117195071650606383208239578b98200351c80e6044ec46065911b1ce1618a172a2b67c8b14346078fca8a9966431284805159a142471a578488ca8a0865329940abaa2a83bc51c709525455153116178fc45055557618608518aec58c24555555b1012324a58f3e1e8b1920517100a1024282084e7aa063892862c0a18a21a7c280253892c4803a814c239b1b54950d25556533890d6a34d1810d18a94124122f2444d291e49000c927555591483d2529539e274d0a124220f1c30e48e2a8aa4a0529535860210548ba80444a69a78764aa52a47c69d453ff849a29342f22633a9d7a50251349855b1ab5e04205a49c4c5274a68e9494894c0a523e352a8dae14d4a74c69be8c0a5878ad1e1d5a586bad1e1d529042329129cd130f11086207167898c2039952fd1ea8494a699e260f161a641388a8504a21cd69864e535443a811e8923e940f02d0081100e0a2919c0a75028520499586742592aab281a4aa6c1e71a4d2a1e7747b4a29dad3ac6791105495141616b1a25a24c543088d28c203c8a005e2165e1045dea82a45425049a108123b3da557044dc53377141129e2a5aace7c49910b289242552562852223545212c12455e7698a6a2282f020450a99d2c88daaaa7890727a329f2a8d121141225ef0c08394d27c92c944d3ccd369dad08090aa8b14891c00112fd5690c225654a81348949a3c2ea449154208ea9442f57c91224a4d1e2924129529534838440e099c0eb9b9711140200a1b86a4c020a10629d0008b423279aeb514f246216f54558542b39a284407d40914b28f6674eb28f53ba2d4e8345d20a1105855550f32b899a42dd123870a581e1b2c2cd8800e0241c00b3cec31c8213f6cf1422499c41087155e3c720128d0380540121774f1023342a8a39221ba8840834a5ab00318bea0a430e48553164e6c52a5c4c1024f1e4748674592fe632755814bb4e070058417a0e288e2030b2558239312dc012fd4f8f1811790c1850d182875c0881e70e1010540501d4811a58e00be61002dc6d0018c01f430058607843e70c4eb440e411e3083e9880d30e0411528b040009e038cb038f088217f7c869092091d66ae20f2c80b34f4000a4254d8c305142e60821561fc4065027c1cf2e57306088e25001ffa9832a5226106241206f984844116a9aad4c944a9787966aa0a06116aa2bc54a9a534282aa81d32a5512da541cd3493f4b674c6644926530d85767a4aa12785d29c5221fbcd7a44299eb78290613d76cb5b93151202790202fca5aa2ace404dcd076df0b188223168b60f1800c287cd1f367d54950d1fa813c80512cfce08102da44c41d92db7b485876754068c2d99aa9c799d33af63ada572e6754669489f9a23fb3b3b231e7890723255fb684658a48076e081cce8da9114501ad4e4220564bffee94c1a3a3283e67b76a43ce50185e2410a08a5020b695229d83f3d8f14164615b5a5643a63e2623285d9319db0a0465c4c61b6a44c3a4fa223322392b47b8062c91e8d5415097b6cd903850a4c99f93680a490a0471a7a2051551290929a52a44839f3a59d47a126191daa501e98bca8ce30a5ffc174c262dffedbd2a8a2fecb166bbf74eaa9623f551a75b12f424d12982f2531010f0d1b11c8efdab0beb6b7b5d8d66dc89837d2c5b6dd5adcf58a9ca120366330b69e47c69e3be798318cfcfcd66a7d5a0adfe23f6c4220217cf7398c6f7e7d7fed93d122424d916c26b001816c8eb0f6bcd3f9aa6f99c819bf35e93468238684949f5bef5debf5aeea4c29c7546cc290eb19638bf67abef5728b9c9d6403869cd4b27e0c72a4743d84227f4c594f0e08b4188f61f3c582cd5c6bb075f3c6cdcf9ba3f1b95bd7bcb7353391f373ad0504ca09c385dd0b69fbd9e6703ef6ea858f899c5d0bc84a8e951c2d395672c0e484e192530504ea2ea4bb487fc6171d17d2bd3b78d9c1cb9c9f7d0b19e37c902be5bb1c5bdb784ca72b20108fe96463d41913aa29c2e6031e5fbbe5d85a8bb1766eb9c62f32eb6cb98db75bb322e71e5488c511d868212974bb2eb4fd7d6dfbebe58b172e393961b8a0619385848f36cf359f4fd7b1194b8e95bb859bb3b0c142c6f5ae3bfd0999ba3add622f6cae90f0da6ebbea6b70d66747a5f9193c20d74ff7c60eb61917745602f1cc1db89ab0b142f6bd0f4e786d6cb65a73a8641ab9b6a9425e66d5e37b8c77bd2f17796871ea6442351b2a245dff75fabbacfe5a566ca6904f197eec55ab856db1863b3652c8559b8d745177de205f5bc6260ac9ff1ee372b49943ea5ae4fcb88b0d14f2237d2d3ec61abbfa6f8a9ced97ce9842769a482050eac980403961b284403956b806100804fa6c61f3848ccdd158bd36767fd145ffd48332916a087688a72754c6642279e784c99293811c2b395c72c270c989d4bace9890191b2764e55617eb572d7f8bec3521efb290758cd439dbb499c839c77a4fcece48840299c9a1de93b35a0724b3bd6ebf46a715399348b3b61296b66142f2c706ffbe45fff9db1b75c68484b0594246f82a9bf1dd0a6343be87d828211b7dcf56ebd67d85ad2d080402350ef124361c90916f6bec36cbbcbb9d8a9c43cc92b049427673eddddfaeb5b9c54ee40c8db04142ba0ae96af73668d9fb7491330bc2e608c935d6dbeaa5732e6ad714398762d71c6163846c4ce163ad32b6e26b77ae16d914217ff2756f4d66af376fd58608e9ac65b1b20a5957be932d5ecd4c674c684c4d0f6464ec36b595b668e95fbb050442bd538ac7d68422ebbba34e1fdec8f7170545f66c6bba67bff63777f713597d3da5163aa474cef89ec8f98e9d61bd8ecdf93e45ce31274c961c53aab425cc094b7d1e302090b3346a78209765ecd85a7f27f3785fe46cbaa518060472ee4cb7345aac33265443444d27923e84f5a79bccdd8daf3991ec8b7b32fbecfaadb3c8b9f4a4507d33915d0d41dee479bbc7edb16b2d46efd8dcfa36aef03ad88d17e4d64e291e4d24d74ae1a3d54d47a35b3713b9f8793abd975dea8f564ce4fc6abbbdeaac27df5877202953a6f471dff56b45aa03c962b3b766bbd0dd63d34be47caefedcb78b6d97bb25f2616ce6e7556d83eeaf12091d5afad8795e7e6c5d0e647c0d273b7dabe1737438900c1d3257296595bdb3ed06d29d2d5f303277dfcf8d12692bad97d2ebaeddca964d22fb3dcfe8166cee4137db06b2bd3fda2f2e9644beffc9d87cceb13d7e24f22be3cb2c4ffa17be85442ea7b79b9df741d63f1f910c29846ced6b35d6161d91d32ee66c8bf1b5661136229939cf76d3d782eedc12398b52a3332d274c96343f837364cc88a4cd207ccf8cffad45e722d259caf5da189ff6bf0771a85144b6e8d7c6166f9cde68df44644fd8a237b662fb0b694444d658e1dfd9956f645adf21d299b1faf77d52dbb8a921f27dec8fef785ea65c1a853e35b906adb9b386dd60648cad1597f3eda6ef2bb3c90d23aca1817cf5ba56d99b0cfbdeda22673365e0ab414d2192fee2bba83b74d151336542773e29d4b886195cf7514388bcdfeef73f5bdd7ab3d2489aa5130df64b676880ceaf46939a1948c6e8cf7a99d9deebfa45ce21938583c88fadfec3c99ee96dd744ce4fc6fee9775c9b6a6420e36d1f21f36e8e46fa6e8d20b2d9f698bb9f5fa3ad359133697e0a152a43eaa161b8e4a4d07caa143b10d9982d7b9d83ec96df9f3190ee2dc3da95912985f8c958399f8b959c1a72ace4cc90632527d5d305043addfff2d6a403028140a0c56dc3450d0c64a415baaf153ad8d45d0b88744dfd56c675da1a5b757fc86b27c7d9e07bcf9c210481dad31913daa1c60fd9a2336f95f2b76a17757dc46ed5d78cf7fd7a93f1619dfdd5e6f3da6f6fab660fd97cd1ffd5eb298dabf59074c6e7ac763fcadeaf9587a4f0321a57a315be6b1a9dce98d0971a3ce4abec6fae6521c37e5c7bee90acbebfd868a396b58fd30e796ba5d01d6436de355fcd0be46c789fa19bd6724d1dab4ddbebe3bb5bfdba1739b7e73c3b23b6a5860ec997799defdd352f48d91c9241b81a3bdada6da5b5c921bde78db3ba767a5b652e0e79db42862f46e8ad4e5e2e90de4ed9e35b6d63909bb7405e66dd7d1c1d63b439c870c8beb0527fef99420879be21ebf3057ddabbecaa7ddd6316c8b8dc6bcc8d5f7cb7cd3c3c625620dde3b3689fe3e77edd1fa1792da73f63e2a2811a15c8e922bff5f642a6143a6deff250e38664f7f15ab728539eec3116d5a440dafbde530adf9ad1be7310cc8ee984050402814e29d4dbe92991a94181aceee35d74def86c5dac8901814ca7ffa1642a81403b3da5a73326446b4e20995a66acdba5bd226cebda900faee6ff6e4f7fffec8522bb1a36e4bceb5a7ece5fb36cc50ae69402c3eca7142ad69840568776c63a1d3f830e6faa27a252f0944231565302e9bebda53f1dad17b2d7a1d2939c433ba6d30da5e13155675d4f4e981cea7a72627fcd1af2b177e782b1f2a3b331ee186ad490b6b9adf898ebd5f5fa7d576a4820195bd5b2834ce18d8d5f1a72bebd7fadabae355f704d111ad2c16ad9bfddb7353ea3356748db16d7ea58ecf5ec793e3ea55066c8c6b8b2d5ce42677ae74ba1ca90d1d65f773aec3bdf8b6d32d111480697df5beb33fa2e7a1b91168b40369b8eb6bb207c903dd65064c8f91875f6fc7ab3d7f1dd18d2f1ac5cdb7cafb949fdb510c8ef391f6b6fcef69abb8e8140728470356a1ba4d37db11643467bfdd6e61ebd4ead37d761c86b9963f0c5d88bf1632e7266f61a3064df7b6b6b13b2bd95d9467d212d75db6bd1cba0cfeb20f342b2ea1a6d6eb552379d6f7721219cced9768eba7ecbbd10bfe642ba081febf676ef8bd04d91736f21197575d9b55cbb6f703d0f4fcf076463f3f66375719c6c751339a3795128c45ac8c76d275b0cdb7d775645ce9f85842cf682d53edbd8da68b190f639f2c7c60e99d27eade60ad9a6a32cbe696d6cb32d37951a0fc8d674d5e7e07ac6e26593c70ac9ec7491b55bb6de5a5721a95df4a98dfcced2664db6b13ba550af860ae9fcb677976b1fe3b36f22e7335f62a09a29648b0ea963ee699bfe7252c8eb9a6bdb137adb5013857cbf7c426691455eeb4231355048e6f119b37032f6e71cac7942321ba9fb7b0ed95d18d9861a2764bb3959edbfb7b9bae23c7513f036aff49dc7e866842267d8dc134aed5c58d3014999b3b1da3679797597c26616aa6182dad7ac9baf7b6babadbd9ed0a34e93a7f14e4f8975a95942424addf39eede884feaf99961a25e447b6cd6f6ce7be68579173c87e2a64274fe4d3a722066a38203f36f7eb0767d36b7b954ca4b9338aed644d12924e6f31c638176cdb71a650232eb10609f9fc96bb8ebac7ae3b17530f61cd117fe9ebb7b03276f72f7276a72f95b684a93142d2d99c7d0d327fd75985aba608e976b6b7e29df1b265ad3d89a727ccaa214272f48e8ed9cba6b5eb5291f37b6108d914b2ad4cafbbf6994391b303c30c533b970921adb3ee9f2f48ab7b7d17153bbd8040a8d3c3a7332644260849dd517666be8b5a4621e3d7404818adc7fb713dcf5eaba3d31913ba6103ecddf72d8d2cfa62c6c30fece99bd75e5fd5b9ee2972663a6964754c99fdd538ce09ef474623d7ac4f1b53c78ce384d79646674a3da6d219c99adf66b7699bafdd759e9d11a366e4fbd7fb6cb3f4f6bcb6996e694b1d95a61710c8744ba390eb8c09dd1ef9f139081b2ecbace5672bc5a87f8c4ef8bc5ac7de84df9ace98103d4918bf3d3b1de35bbbe7337eed83640767adf53abb5cacb6451e428d9a238fa4cdb1c8b6aeb6bfdeb9c8f9d9d2efbc88679ec8386e76935ccec276bdfd2deb6d3691730acd87426f8a1ae5a729aa0d4c49cee7d175fc86f0fd745d980e3b92d9b3796f7c95d7e4e6201089f4150792b4713163df56ad312ec744ce76147a9e3429cf099325274d890a0894a63499ce98902c239dc348698dec7acecb2e7276a99d3b811e2a9c74be3a3bc6bfdf5ab3d53392b7d9748c46d6f13ee78ac66e21539a5f40201008c5630ab1eb8c095991b4bf6cbc705ab7d3b5d5f56dc8940a395b808c64db0fc207973b7be3a222675bea319d42691a0e5736079b7edfe59cedfbf75eba17219dd71d339f4ea1b39235d3e5e682d6fd5b8fc7148aecb854e9ce7e84ee75f184d3d689e6b150e9d6b238ed6aea565d77899c1d0b53d2ca11525a1ff37d7859e4dc30f220d982afbdd972b521b355e49c42bde32d2139ad5b6facc21badbd2152cfcee2d5d0342333aa02df0534207db1abd5359ccdbad651e48c0a4126531a95893b480629abd1b11b1d9d17429173e94921346f43ad9dd341da57a38575c5f5cf426f22e7c5674c3c2152cf4e7cd0777a4a557290d579f3c9bcae6b23f395650cf30621e3f79ec3b5e0ea077fde755da46d326c9632919f3eb5c50a08647fc463aa20100a84e54b4e2f3ea5786ac4c041ba1b2fbb0b2e77d8fc6bc914bf966828617910c5639a7283648c3d6fca9875b3c2b944cea993894e09d820e9baf62b755dd76a76c6fb5ffc91e9a16f5d6ae7329e1ada6cf17db5c7566d8cb78861ed468e70b2fb4cdf899cd90c611ab6fbcbdf1e7c9fad5906301232afcdf56aad424bd7b4d614197e910c593b736eef82eeed14395bd3940cc845abc39e93617d8fb9596b5ad18b64fcdc8dd7517f6dc1fb56a4412e8fcebd56cb6dd626f1ec8c42f64f538482ae2359ed781db53f59ab4ebfdb025da4bf37ab4c99f58397c1332612698a50feb8c8092963ded6cf7addb368da319d78b8a7cc20a3b533426fb0ada7f33991338fe96479c920677df5dba5ace163a64be4a614e3de2267d34b63a4af7d5bcb71a88e5233856a662d1246d7f1411661df3bd71339c32a5924a43f5f6b95d1beb5328340cc9d523c366091413eb71c7be486fea26bd1c106db84dd2b5607a9b5ece6acee21f1ec8cdc4e4f899db922ef5d5fe3b3fa0dbaca1608041d8a3ea7332624c68aa4f4e1a5bc2eadb345fa44cecc0f3a146d3a6342ab8ae4d79e23fd47e1b7bf57e40cff64b2729a35d06efc74c6849c8a7494b6ae6f5dd737a9e318a46bc6b73976fe6a74abb9a73326a4010c48c6abaef71e5b77b05976af2914b9c02019bb0ca18d8db99daedb0bf2bd5f5e6b6474bd7721c6a3f7b8be19b6e502d2b1b93ab2b7fc327b3845cea17a9acd6a290daa5db1805cceddc8da41172fa4148a9c5dc944b202027dc944aa201008f474c684729822a3a3b327ad14fe6c2d569173a8f9a5762e7b51b665b6ff3bc2e75eab50e4bcf8ada9d41c3315b863328548f6140a01c0a5bdf44db6dd9ab37b6d22e7d409c6f024f31a695b97bea7ff5c1439871c3ad95cbb6bbab3eda613b23709af8dd759a6cd32ae70899ccf984e6442dc8c599cff784efb66ab5f45cea714aa2dfbdafa32668f61fb6862d9dada572fbbf9feb1c844ceb634b91d872567fb6a16db6bbf67ca226711aa87277432dd4725c74a4e18773a63425c5ca06ee374dbecad2b32ea44ce8b3f353ad96043aa6767399d31211f40f27d3578dfb6839331ac3961b2e4c0fa358040f5cd8040af7969a167f58ebcfec5d8a0bbc8d9d964e1de38d9a3ce763deae01339e784c992f33f8c503d3961b8e4844aa3108fe9645f9f523c35546045771c14aee5049174413b197e7babfdab0391b31f747abbd2cacdbdc640def58dddbbc9f7d9370a03c78e35bfe5d0dbb16bc858ff748e1132f6aeab65804848bfd967ed67d3fd9a377f48e8ff16656ef28acdecbdf143b2f37db6bafbdaebd57fd387fc3be1f5f78bb6cae6c31b3e24fb73ad6d8dac3147e38c21b6e2660f497fd2b99c71fbd5b65691f3c3c08d1e92353657e5c5b899adc9423c73c7390d845a6ef290113e485fa52fc6b6582f3ca4cf38eb7df6eaeb5ed3de21bfd2689df7abf79f594eddd821d91faebfcfb19ff3d97c817c085babb1d9c3c6e67275c816db4606d7acb32d37a343c6d96cae3657e3f6917b0eb9ec83fedc6a7a63d7dab6b41cd23e489fc7c5987b79b3767148a7b359fdbb9863fba873ce05b2f17397b9e75957bc0fb640b68fee3d3adbc6a78cdf3907876c57bb35f6185d97d59e8ed3b87943b6365f8bafb9afffcb170b24fba26f3138dfabacbe0fe36605d2adad33325bfbab7bab3a15c8be97bdbd8debce35b9b921dd678b915a6e91ebbcd3b914c8e69e9935d86cebfa78d8c9e425cdc9881b14c8bbee852f7ad39e5ebd1bd700b959b939817cccdad65ead15ae479d4d734a95646b433a4ad95bd723e546a3a5376cc8ebdeba8bae7eefc2f68d39c48dcd3013c8f7d6837442a734ce0b6997110f15c7661c9b616bdc9440f2c31b29bb775a6f3eedcd1ad2395ccfbcb277215f679adca821577c8c99bbdc6e6b8b711137249070cd1a2bbd93ab7df35a303c3df6afd8e7e9b16f6fd290eefd8a71dae860dbf7fc060df6913e64afb6f2bd2e7226531aa5b18b5df3cc1d86ba39435af67cdefadeaab5e17c10888c1b33e49b953df7945d582feb0b02a19e183765c856ebbdcfc1c5b0ae557b8a9b1148c8ae8d6e795d91f55ccf4b9a140814e234a9dee24604eef572eebdc6de7b8bd9ebb8289b6c273307821b32e4fcc88f3d6bbd56675fd7cd18f2d9ae6edec6e89d17ba7d2f2010efb4326e4220eb3fb7873c79453a1d8d2090afc2b86cff63b7ced7ae18d22f6ddcf5bab33dddc530a4ffbfc376ced119ed7d3760c8586fdb7676aee86e37f785ec08dfbb369d6db9d7e8855cf33eb35fd7b976236b5bd0b8e9422ec8fe3436389bb7d81c871b2e5e33c766d045c83c42e70efa7db03bde162f9d76c69b2d64bd1656866edeb99c2f77f301395ba51cbb5dd6d6b2f0de68d1dc6a1e1b8b8b176476b1e1baf756ebb7c2b7d1edbdc94232f762336bb45d7591bac521376eb090efba7a8cb035ea18b51704aa79e3e60a7979557f96b5f7b7591a45ce3c3b275188b9349a2267673a63421ab8f180e4eb2eabb4affbfd561744c18d15f2d9ad5f6dc5b7d4b5853961b8e498e0a60ab922df582d9ccfda5f5c3754c4e51c3e66f12df81cf66acf55d6e0c387fc9eb89942b2b5ef759a043752c80b5df4d6a063a63342bf8942bebd3f99add9bc317350c868df7a14363addcfb6d613b217cf778df9e5e7abbe13d2cd67e385acc6bf8fdde7c54d13f23de6d1eb6547993976d301c9b5eba476bdc7f7667fc3846c714217d76c6ec5c95c5b82977b8f99bdd79cc5f82237379fd33af80fae58e7c31b2524b348e77343e65873ed174ce28603b2b66befcd579da5f51d4d42d25eabb56bda1dffb58984ac9551b66875b69442eb9c889b23e4fafa1a2fd7cea9858e62716384b46c71b349eb73f12f3f10288698817153845cf6ae15a95fb6efd8574e182e39cd8c1b22a4858b975f7b216df1392772c65283f3954f0f64df79e96a8dc2eafe20d06a2d3ea1c8af95ed7a73561b41918ed90be1657a97478f91049f4f24d778d7c736639b5fa71339bb538ae7e389f49e4ed9efe5b52664111562c6cd03b91a3f165b74eb23b3583f9d484bef3bbf8cd1070281406d26143df0e144564723a4cdd9be1636b76e6d22bb2963a70f9fed77d83491efb2bd6d679cb1be769a89fceae67b9032ac6fb1d84a7c30913f61f5b7287cea16ec1dc8b620437f8efe75d80f3f3a908b63df7ed4b2da9642267266fc2e910dd963ad356ecc5e6821639648f7eb41664b9db5f75e6395c8c9ec5387fe7aba6597036997bfdaeedc5cbf0a1d03e3830369b9fa7dad196bdd2f7a189f1b4866ef7be6b73db5b45116430c9b12b9225d0e72abf3f93bd793c8e88ed9eee66ce37fae6d20a37decade3fef7f92c7f24916dd96a5bd7c9dea1fb4422216d14c6b7ecfb4521756dcb0712792be3f826648d355dd73e22dbdde9d1a77d8b7a7cebe388fc4bef73ae2d7c3dd9758d486ae19af521f7aaac51b8bee458c95919c8f1c225c7493c3ba3c78cc8674d9773e72eb31997656c1109d9df6a8ef95adfed56c61491dff8cea7ceb75ef8ae4d44de1563a395d5e5963dfa8c2122b93e6c34ba8f73c5e78cb143e457da167548fd3a566b058158882333445ec77ed1172b6dd1e7bd6c31107c6a20575bea6f9b2dbccb36a741c3d89a21bb666f45e6c58b976b2d3e376fd539489bf9f029447ab3767632b8e2b3dfb8de0002b11a2b3e84c8fae09bd4f96a8696fefccc40b2a3be98afe9d1c245ab1a9f41249dcb5fc365ad73d1eb8b687c6420fd46eb70d9671bb3965b191f4164a3ccdb628c3a7797c3271059ad8d94bf3a77f969fcc5216e323e31901c17adf12fb3eb17db8a5d7c60202b73f51fbafbe766e51685fe87116aa6e61510e89df101443e8d2f5aaf1436fbaa5dfc87bcdc985f6bf0c6f8569b20906b3fe4ac90ddeb499b5fc7e0ed433e7cefbda0a5fe97d5c887eceb989d966f8b6d3a65339f3d2474f0b5db68abcfd5e82ed41913427df4901fa9abadad8fecd1f5350f59df9cd0fab46d56eaefea9b618e727cf090ebc1a67f635fe7de420a0604ba43c67bd9330bdbac3f57a52b46f1b1435af66a63744c2f576787e2f302c9ee187cd6689d94677d20504718e26ee253875cd5b1cae2e2f6ea8bf542273e7448fbb1c286fe637ce690f1b2db228d5cedc7d91f3964ad33bafb848b9b39bae2902e425bff42c7cfde57990be47b68bdfd7d8cb26f585b20e774ebab79c287eeb106877ceb0ed6e9e865da71fd1bf23aefe76bc2465d6495b140b60a5fe3c7efcd8f94e10a24ab6f5e6b6ffdb9d66bad02792d745f2d758e4d6be97343b6c7d86adbea8dafadeb14c8f5fc7f71adf7bd750b5120a7c7f70f5a66e6ea9d7c02e99179b71a7d7973daaf0dd9226bfbb3e7725e6b5936a4d3e56d19b2a36f1b3e13c845b91b6dedacf59b979640c2daee6cd5fdc7d6ee9d6b487b1d33db6fa1638ff95243c6c5d459ee3899858e04f23e73ffbc4dfb6c8330c6f1494332f897468776b5f8aa85cc216e313e68c8b62cbe5e6ed9675d7793e27386e4e6d6a5ceeb5a8fd62dd38d4f7ccc9090f1bf68797a8593995225e9c5a70c6957b4b4b27f353a07691b8174f351ea333a6547eb5a22903ddd42afcddf1dbbef6448e6d8ceb25ecb56e7ee31a46df37dc639db9bf1d917025997ad8cf95176ae6d5110c816d9bcedc5fb6674cdad18f2cd7af936737fec3e38c390d1cdf686ed316dee1e0443def8fcbed8d9a5d62ef785bc75ad37dffd1bb40e9b1712def69769d3769542e7ba90dfd6658d1f9d735dfa960b79218bf79f6d0cc2c9deda42b60ba7b571b2fbdeb8e307a4bf7e8dddd9d85c71f5d542f25df5de6adf8a96ceebb290cdb83a7568db73b7d68785fc78d97b94b2bd42f28ddffeedb6396965cc03d2b1d6ab997dcbd6fa6b56c85beb6a5d9959bb5c745d85e4faccfe5d13b6af35562ae483ab575c6d4658db636b0ac9288cadb648273bc69549216ddf08db7faccce6bb1c859c7755beed5e67d7660e85f4052b53d7ecd10757ec276474d7e2a290cd3b99b2e884bcaee372369dd55b577b13b2fd6d4dab6dcdd1c71eeb8064de6c538eceadf548271392c13717e56f6fff35c64b4838a3f37cc658f77d7a959037ceb9d8655fcf7de5c601e9b3dfbb6befb3707e7d49c836273fe44b6b737bce2221dfe416d99f639359589b0a3e4748e78e3eedfa67ab329fa46925c4cc4cf3161f23e4a3bc9e2ffafcebdbfb45c86f5f3eeb8beee3baabc1d28708e9dcadedcd9f576ee6b4077252eb335e5e1eddb7e5a1c486973db3bd5e27bfc540916e1bb5de9e1dbbd7ad0602a51e859a369f5ca6904e78b92b84b5638e172eee0161e30964b69a718b0baeb8383264f51bafb60e5a3bbd3a080402b51910a81f0fa43fcb8bdd199f2b4311aa8727d2f03a91fea643e6da7cb43efb9e13c90faea6f1d908e1f4556d3691ddf7e19b6f3ac626d366a38964bb5eecd79ad967e49889fc77bcdc73fa0ffdc26783897ccacccd566793e78b0b079b1dc8c56837e7dab5d146387520e9f3452f75bf974887be988b1ddfb544c2c89359abaf427ed6d94ae4b5b7b567cb117ea38be640c6e7ccccbcd27799a31107f23ec79ead962b6dd898df403ae8ccb67729b3d1193f4a9abbe7af5db7f6bc9b1bf6474af979be9f1e23ad33d84c22595babd9cb97da77e7a3a652c806d29dbdaedd94677b5eaf24927bb906635ccd8bd5f691c8b96e378e2ed2eae2bc9048c69eda699765fbecd93e225963fa7cdde8f759f6d1c611e9d0a39b91767376bad8108b4d2392f18b7dffdf6d07d77d1008048221e6793b150df4800d23b2b2d69aae59e7756aeb2f22ef72f7086bb7c77767b45144fa77adce45dbd1b19bcf2611499df579e37a4e6753cb40a0670604620ef133d3d10611c973b9c58bf9dfb2f9fe1079293b8dcb7a6dbfd4c62a395672a8e478e1d27a6c0c912ec2d8d1dfaff8a0b5af06d2d5e7ee5a6cdd94d27b9d01b1a1819c2d7e7b915a8f6d41d84285c8dae27aced67ad4f2a214040281163b6143887491b2ff7a6fa5d0dacc40bada7a42469df6ff6426620ca9cd20f232fbc96e3efdc7ae73f5adc9ca5b934e5c7c011b19483619dbf8dcbecb5ddfb72d3682c8e8ce79a5f5b2ead1ef072227655fd73d8d3606b23943e8ec8cab79f36f184868dba593faf24a42c51395c70e1017d7ea66bcf8bd6ee6dcb1f6f73efc06bb55e85d83cd1f12bad74a7dc2f9ed3d5f3f7864d307834a367c48ebf6e7dbe89eab10ae2572b624d30da539a5e0971c2b395b4a3d223075f49eb0d9435667f3362f6669acf7c9402e938b1ed2ad7abf595a39d6e5d6cd43babf1c9b3b68bb99d98687b4d3f563cbd6061ba3936dee700ddb6bb1bb39e8ce6cc1c6eb39c86b3d37673536b743d6f7dedb999bcd5fc5f22507360c362f9090354717bdacfeabffbe0eb968cf6e3fef6d8bba65c370c9012d0ed8d021f9e17baf6e35d6181b8a3c274c969c2b201008f4e01cb2d5575fb370f66baffd0f2314471b39a49bccb638d979631b5b8b43b66bf7ee8a70b66afd5be4ccda071b17486659dfc9da75db3a7fb116486bdbed3ae9375fb1bd0787bc95bd76d78a0bbe2123b3d13aa7eedf63819c7d2ded8eac415eade9a84c642b90efd9ab91aef6adb166ee1f6c5420ffcec9d179a4af57bb8e7243d6bb18b239793ada6bda1448569d83f5bac7ebeffe4281bcbe165bb5ddf667ac97c899d3d89c403ead93d26ed55acad8ad2267b6019b36248cd02d65e7ec9c4eeb44ce8ba1b161435ad67f635decc1d75a6322676e938d0924b46c36c719d9d518df4525900f9f85aec5086da5f355d7289b3524adced9399feb2d93490d79bf17af9f74d5fb7ca1c8199240be5df13d5c3f5d7434a621637c5e0d1ffb74ecd62383865cb5694368a1658cf637d4ebc1e60cf9ceedbc77d2b5b831d67f429944ce6648d79c521be3ff63b4676ada2e9946a9d9b8f1c9a60ce92c33bf66dbfd606c2d72de319d6ed3a2050482ccd8736033825abbb986956d85b0bac8f945755a0181425644a8a9c54a9a9369e7bb786548582bf757162374ee0c4720e1cf38e95ab79f2fc65004d2b1e5e275cad6ec6a2f19b227bc6bb51b63e4477f0cd9f451caee9a0e7e8b360412fabb6a23af7691410b020997b3c50d6b6d6e5bc5901e5fec67479b3967340ce9cfb6da7e5d3ff6718221e39cd7dd5bcc6fb116bf908c325861b38d975be8bc90f4dd9e97ceea9135d8ba90cefe7bdbef2b5bf19b0be9d759edfb6feb9dad7d0b3969658bc266b1d667ed1f90d6de77c8eea4d039b26b21bb72834faff3752c2ecf42bad5d0dd5ea696bdbf584866e9a2f435773a61bd5748c7e2b3b5edb0d909af07a4c7eb1afa7d909bf3b542dad5eee36df6ab9010fe3387eeb1faa54f85ac8e2db7ab3db9e3b35348aecf7bda36dbbebbaa1492d6069f3b3a1a857ccbc2e8d6d6e5b0de09859cd03d377f429e97ed1392bac7b6b5fb1c37bbe8848cfd2ef5fb22d7b76013f2be6a5b6374cef9ddb003b2f9acfceba3d73a2b1392ff7e3ffff407c212d22d4bdfb491b1ba9af903410949e7fa762f5d3ee1edf640e080acb07e47cbfc3ada603f109290ced1dbdc9bf151eb763d109090909963c86dbff9eaf540384242f7d862a62e56f87f1d0846c81b9d3beefa9035df7720142163bc9751e61ea5fce23b108890ab517b9bfdd2cbcfdef540fa85eddd6cceba7b7e178a7caf237363acd25adf3a5064747799d76cbc1842eb3e91f51fadb49dbdf7baacf344ce87f05ee78ec26e8c3a1e48c61556d65a7db6934dd789ec766aefafebeb598c8e1319e163cc7e646eba5fe836918ee95b58bf56582bbbd3442eafefd65627ec67637399c8f7afdae89aff55e8cd6122adf7abf0b2752bbfd7dc0ee4733c2f83cecd566b99d3818c303efab3557697487f6d365b6f51c8d1b2b3443a7bf5d8ed9a45dacb5522df5d5a29b3af97b5f4e640fead8de1b2abce76dbc581e468efa3947b455bdbbd816cbf7fe18cde6eb57e4a64e466ee4137f9dba37612f9ecc1ee59d7b3fb28b5819ceb5db79cbb371d6c2a89acfee66acbdc3df4a691c866d7b4d6161b9dce131259d962ae45a7aedb9bf011f92e5687f5d9d975397444424b1dbfffd89cb57d8d487ae73f7b6dcfdede6344c2b758eceb7da785ed2d222f5deb9badb0ce66ed2922b9b247bf91bd3917752d11d9dc8fd919db72f7d3354464bdaeb9c55843dbccb9768864dc9c46c8bf6043d89a21f22d3befb39f91b6d85aab8164ab59b67531e6a673dc68209b9baeaee67ab5422474d6d678b9d1d866b484487b6143c66f7fc51aeb0c24640bdbd1f5189cf0d241647bf4d677a96bd6b6a53290ee7f51681bbaf722a48248db334e77747a6db66820b27a3bea68ac3306d2ab85d04d06575dad270c646c8ed43ddb6bd2672320f232e7e66d6bcd6f3ee31fd25a1b67bc0cbe09298c7ec8af1632dbf1c5f8d6c53e645f5bbddfa9d7772df2212be3ba1ab335de4a3bee21d9ce682fbfd815d608f590cf18f35afda27db53e9687640819fa7494313c64f79d90e37fbb6cd96377c8afcbad6aed7a671d7bcc0e49f94df86d52c6fe5dc75e20df5bff7cbe776f56ca581db236f72a7dd86ec2379bd121e3f7fd77f41b6d76fe1cf2b92e476f8d2f879cfdb11bbad51e65b77148d816ae46dd6b8f2e365d209b79aff726fd4a1bcf16c8d6166495326b37678d70c8f7e833db379fb9ebe21b92c6d69ef3b56eedf99c05b2b2ad6c6f856f0592d1caf31dfcb7efdb5381ec67fcb1b60617acd6b921e1f5d7d3ceea52205b3bbb66df9fedb6c850206fdfcafad95be7fb9f40fab4b6b66ebaba0de9cd7b5ec82adbaf71351b1272f7a3bde87d75c5d5269011c6ea37f2cfebbead2e81e455fdb6bef4ae215baded4e7fd0dd6bdf55433ea75ce774fb28d37549206bdb1b9fb5103274eba621d953d7132e86ffb63e1ab27d2fa697be3f43bab5f83a7e6a6ffbf666c8d696ffb496adb7a5b50cd95ebbeb71b4f01fda39024967b7da8bc5290209b936b7fbeb9c7d163219f235c8af63833c86a45def72cfb276cc197208e4e3d8de467fcbd1a7ce2090cd277784f3f973de8ec59071b5096f63ce8fbddb380ce95885ed2b4f5f96d2c66048769baf459975e7fa8cbf901e577b8fb1d92fba63ec856ced59a55ded57fa6a7317b29faf5eef2e5f7a613317d21964f3c266ae9fbfe62d648497cdd89e6debbad7fc0119f9beff65c8ed2664d642d6377db2b6dee466be380b49ebbbc7ed8e42fad56271f9b18677527a8584cbaec85c6d9336d6aa07e46def411b6bb7395f4f2b6483ce2e07e164abf9ab9031dabaee757cd96d950a79197d562d857f594f9f4252e68dbab3bbaed92a8584bef8b573d5bef5e9a290ce989b856c4dc7e28442aec9edce6e8da9e5c59e90fd2fba7e877ca33f27245f8ff3d6f6a86b94bb095999cf56d98aeefece0ec87727bb5f67a3975764425616b931ba9ae5bf6cad0f96901f6fe506ed7cf3032564dfbeff98b786b32dec0f38202f5c3e597cf1abab6e3991f3f30f9290975d657a637bca4e196ca91e9e0f90908c464817ad75fdd717f22e6d419d7aac7c7084bcf4ff5f77ad0b567a7be207463c6ff5dba20caef7ffd30745b05ecddc45f722f4c6adddd7af459d5b76fed7f601111a88d2036999ad57f6caceb9d7d1ccb79c3059724873b4632aa52199bcfc63811d8a5c8dbae8a6dfb9e6ad8b419137be9bced9b5fa7ff5fb44ae678fc58e2ffaf5a7ec09732c36061dc7d858bb86cc3dbdb656dab63f36062b0f2475d46bbfca5ca5cede8ad2897cae4d69fdcacfde399d1369e36d6df25cb5d9659f46d944460bdd4fb6a05dd127e4ee289ac8366773b6ad73916173cb443e5a1b6d93cd16addbc630910f67b735d9dd74ff60941d44d89a238bf0b9c7e75eeb668e8cbab77bff6d3532a2e840c2f7eebb35f6bdf5bde6512e910db2d89aeb9cadc616dd3d8a2512420761659371775daf46a9442e9e6e418eb5df5cd5c2283990f31dfbb6cfd2e5fc398f8203f91ce49fb3e963c6d83d8421ca0d245dd33673163ed7982da7443eb6688bb63265512669af3aaf0c36838c43fcfe886203c960acaf3d84ed457b79558922896c2f36b490df9d2b3a84512291ffba2773c6ae42da0d3206895ced2f832d36b81cbbf6629447249c4d5f75b34dca94bdea887cd68b6db365dc88ece9f555b66a6defba3f46e4f3daa6b573deafb0d1b9887cb1d16a1dcfe9ed215b5a445144ae47dfadf75a73c6dd2408c4449444648bed3a5aef748f9955fb3a0a2232f6abb5ce5529851d991d22eff39a94d236e9b297a321d2726debdef5b4cd59f66a20a7836e3dcbdd5ce18a8e06f2c6766d3933d7b3dad50a91bfe8d307235bafb3697bcd892884c89f6e5ef66ddd2de6fba2283390d79d9b90dd5edb2c339613260bd419133a4519a44576e8acb5768ca1afc88fc147ef7b8db658e75a5f4491816c74d6593dfa8c0c462b887cace78affda9b8148aeef19f5766185ac678c412fc6de7bad41d89683bc60c3ca986d2eda1805069251f89eed181dbf05ed0544fe85ddf7b1eb17b638ef1f72fa7c16b2eaec9d8ec6eb87e4c59721f3e77d48172bfbca28adddea62970f39d73b83d7518e8f31f77b48c76eb1be5f21c36e570f09eb475a3f52e6eff9e7212b4f77fd74fd84cf1a1ed2576d0fc2f7187fdfbe43f68d8c9d23436f9497b5434ef8a67f33b7d4767d2f90ebb54a1d65f5762fcb6a1dd2d9f86885ee8ccd1521a543dee6e8aad55d5cb6dfcf39e48bcdfed4577578dfbb1c32d2a78e2d7e70febd370eb960ad95ef7cf3decad60512dafec9aedaf645690ba4853736cb783a8d94231c12be3b1df3b2fdd6a37b6fc8c9a86bedba6175fa2c0b6463f89c555a9f9d0ea75b8174e6f5de2c6dcc55679b0a64b36edaca6dc155adb36e488eed6e6ccad6faf79802c9a85beeddd9b52bad8e02b9588bb7b676b5b1beef04b2b14337235ccdd2fb8cb521bfc5e9fdaeeb47bd5bb3211f5db1b5fb8d79dd9f2690abd517eba4f6bdd5cfb804f2f9730fb9dd87f4415e3a4c88286b2cc8e09cab72bc570de99e465a27c779a77bf3924042fbfc97bfef7bf6f9a721e37f6d6f6635b65becd190d5d5fba27be7adf5af3f432eca6e32bddd30da45ad19f2fd8bbdfabac5ef18b36548daed31d7b052f6b76c1d8184edd97daba7bbc5975611c85aede3a70bcefe7e5ac99094be65d6ce1ae3f7e43a86746eb6e71a9ddcacab8f8640c6d7ec437afffd6acd2808e45db03657ab33c76c371543fa7dcbe37b1652b66bd130246b6d516e867fbb6b9d6048572dfb7ff0d1f5ec75f00b696b8bd15d6556dfed825e48c70b7ab3bfde857c4a23c37761bdf5adeb5cc8eaeead7e9963cfdaa2be85accbb1e5eab38f5907a9fb80f4669ddf65d8dabb6c3a2de464771fab90b1aef339978584712dd8b4564a2f9bedb090ad2d4337ddfd15f2dfdd3f8636bab8f879403af7b0aded0a9dffea668564ee2d086d3b6d35d22ae49cad9f7b94d5e70b52216f3778effcc6a2abecd814f27df4cbf04e5eb4dd4a21195ac8ec42c79a3e9e5148cb7e39ea17ce76978550c8ae753d66b6ae3177de4f48cb2cb4ef1a6538ddbd1332d667e96bac5f3f56e98ad284f4e6223fb670ce1ab9dd281d902e7673deff6e5e7f0fa33021598d962b75d4977beb38ca12d24ef65ae77c77dd14519490f47b5d76595daeba8d10040281608851cf9326e55c140ec857a3fb7b4cbd9bef1bf1cc10d3191302454942dae5e673cd8fd79c6c3ad27532444142dafb95d9c8da5157dd8a22673226128b7284ece5dee7d7b7dcfdaf51e46cddc4854b14231cc6089f776bbd165cb07bcdbfd3a99bcf18f5e54e868c890b8f490704226322b1538a274a1112ae7a638c2e3ab7d5fa462142b2fd179da18bafbef9b107322e571fcf75214757590c455a6ee697317aa163eb81e2da73e85e638e3118d95973caeee663ed6275ccae4fe474af51e68eecc50b9b7a225b3bb8a07db4a36def3a1ec8d5b4c2c5de70b2f6ae1359d7abb4f162b5d959172317379cc8d87cb6a5abd75371b389bcb43d667aff358c77b626b255c8ee5a7fb5732eaea29b4ca4fb5e8dbb2d8494bbb5d00d26f261f48eb1bb36635dad28f60ea4d3552965f82664b1561639c3e6d73ae8b6187b6ef973d0bbdd6a5f5da9a56f2165cc1cea643a9d64c93402457789b4cfddb968298cb5ba9e96486a7b2d37fd995ee7ed55226d8b95d50b695c4ddd65205077e02607d2dfb2e5489db5eb267d8f9a222f20908738cd0d0ee462f7d6766eb10b99adef06b23547dd8493175bfd9641204ae462ca669bff7cb6da36c69b49a4db06a775b4c275db471bc87aabbbc9f7cee60d3e26899c97ce6f7abf1f7b8dd54824bb6c7a847fdda48de7bb8144da77f3b6c5f4df1b43f78864e79feeb9733f99cf0a0255d4680704aaa8d14e7bcc1169edb5d62dd8b032836ced69f66421cd46e4bfbf5b8d45cae8fb9c22e79329947a328d2123b2febacc21fbb9d88c3c2a8d42a1c7854a151ab864792934a1c78b48ff7899b3df356d74ede98c09f970a3886cb5f9b3ef5de67c75bf7093885ceeb67d5d97b10997bd88c8c5ac9bf0bddecab7d67888b4ac5f9d96b585dc973243e4bfcada9b75b58d31421b6b202f6df3ad3961e47a97331008051f0da4cfe7fcaea713be8e8b420fdc1422dbfe73efbd477fc674a226c94abce28610e9de8bcdfdd3d7d5def5a834d30b0804028518b2194886effcda3ae7bd7e7d10f95f678ccd32adf451e63290b7a38590c6f61f947ea8340e6b35792c8ea2188a610080016a9f11000d3314003030201e8e0683e18826ebda07140004416a62963a2c289308838140200c05611003411002000c02310cc42018849710a50362d32c8a41e7fe33ed8dcdf1f9e9f4ac79cf3a56dd1417a6f54b7a92557a62b1f6cce6447ef3e93eccedd4397557d9639aabe5183d243d4158e8a232bec83c9b15d381f68ba2225954975766fce96b761b4ae778da98b223c2f849fb1325728c5ca5d30406fda51ca822394faee834c2a8ae55f99483b149b9d0558a59e7aa33c58df9f8f13abc0a5df3cfb5673637f2e3d3d3e759eb1e198b3445a1515f4937990a893e5619cf27d5d823cd85fcfaf4f479a6d69269f397fe4f1dc34fa137cee3afd3d559e29e7c6c58658be8818c5220925f082f59410ee90585591efda09b2527797e7af93c6bdda38e1557991df6f1054891bf7f1d73c561d19b764738c69ad9e647d21d5d538d42207fe9f923335542002cc63df3da75a6ba41711f7655b7881d6f17a7f2b58917c1c292ba2fc6f327168682e4073dcda546c8259d4720c9ffa48b2e2346e99acaa0b0e48765dc2bafa57567bc64feb1d6ca06459e7e6afb4c6ad951d90a2b738ad1fc10df1115513de96b01bb0e7eb5abfe5e3911104f0d949e2664ab1a31a81807e8b0a8ec868eb21e114a3ed741a60bec26f1ee56af4793c5914ae257cc36dfaea2f939d7f22515e711beec4ca9f40396768c947e0f8047f0c4030c0d24d26a96505d40b1eb6835573da37c684120e228a7d97c62b82aa8751c9af2e1bb388f38a32db71410a2320522466f154e7002f2223e842d95914d6921fa7d53e4bdd9701019387413a44690de81cf04320622c387a0d542e794ad689e62e3bb394df4c741f9902d49b9cdf78794a9cb8dcc93738019f3c4e7030b86b94c326925a30768d6bce78af14bca62eae8403a217894d0b6c0c9a9a8082eef1757be21905c1ce3a64dab7ee8c685c22161fd57d02e59ebcad222be7f2312f8182d79c113dbd4deb6716f3ccfb293829d7d631ae6eeb0dafc53e49890930b8287d4053af3c7c676c5f6dfa5a664ca3bdc28c437d28857ac9eae6abb25027de350b9b1a5d39fe748a34bfee02a4cca7269ab869eeee00929795d23c46885d608829469125b6d076904b063e7206403e43d2c7f1a15a70ac179d09100c30372e228cebeecd0a3a8262648efca130a83d20035514710f64a93d27c85bbdf452827621747d580082ac9c7ddf0fdde846e03192b41ffa7a73a515d864e2a9eeea7d9afecdf1790cb55a1580fc2f390d9e1325a8d8f8c462f36e3f1851a8258f294ddf9ecf046fe2b9d15f5887e9bec8d544eb350bc5ea24e2e4bd725e21e47ffa071cab213490b4e22dc00773c3fcfca6e4157251a62408e194862a02e788e8a93b4a0c0807826a43621b10de50d28b450cb82a6052526344ce8b0a19901e54d286a42b01b420da867a169418805ed4c486c42791b8a1b106842c4847036641a43c40806e5068e7f2fffe0cb1492a3342201ff22d40744b9b76d8420da2409bd2524c8208940fb11f594871a531b649644a03f097fb2443ab6e87bb1d58c6dc40dba4a30d021b54f35b52006d06453299d9f8657aa283a4337d5e90e2ccf4fd3754da129359d3f658179e464fc9711992416102bf13ae91e9f9b274d5d6558f449aa563bba7a9ef9d872561cdb4194f70933369d6434e646e051a577d59cbb23943a5193bdebd4dd52f21f36ae88da5037e22a72691658792884733180a7293dec82d0a670cfe35544f6679a54da04ea88493f5513d906d2eceae64c13a4d59192f4123681cd26652dc6cbf53421b84e96f3751f37e5011192aad694171563724e66e4482ade466472aacf87429d986d80e2687622b5c3e0df478e4bf580cbfe00229bc48eed23e0e1e83695a34416dc05490b8e0910460d9e13c418e8faca3229e170162d9c26f4860c3eceb1d1274e7d3437aa54c3161504354f18d3d91106d6429a576ab5b2fc07924f77cd967b4415a9f9ced79c1ea6e3341e1d87e663c1b98563736aca63b2a09fc2ac24b34394c686017100fb592c7441124bc4b4f4674d767ab3852bc974572135d6ed58d409de685416415cc961ccaa46f95765c0c1adce33479037b35092cbf59df1c79470d8b88622aea4d0d51b7c46c1281d0f47147b013a379a1b0e6dfa6f8e461c8a5f5895b2dae4c06cdac3c24daadcc7e367b92f245005e043eb33d697ec1f70d4a48fb96591153b529e729186e30c0fabdf2957a990a7e9e8eb6ec27b2185e88f2a128e10d3b1a2380cf18de2ece9b80292aab55294e03b99bd69d8a77b759941ec4bc99d7af0a85901b5270ef80d8404a00716bc09f2db628fdbbd473f28c15b080453b9724d53a9fe4495322c478bdf3c57aa72a13667b01cc08916347f62efb1972d89cc9e3fe4e08e985b3708d2a2b4169258244df18c60f3f94bd410eea07d4ed412d2953dfd7e317763066256dfa1df8dd8627bd641ee6fc729ee1351b243ff40809a47983b997c29a0b6500e413e404d470dece13e5dd5e03e20e6941acb4fd26b83247b55e2c260650f275245f1cb9d14dc52a8c748a1e9ac40695369686a0286c89d56052d4c070abd57e2061e324cf034e26e582b7d52d09010266ecb0a1797d04b86308fb158db69ba240f2c70a2409d863619aad34a19b5782d784ab1c52d753bf8c0b5743cc3347d0ffc3da7f9871d2952237862ca37d0896aeed7dee52a391658a3ee540e60874085e0da216920a0ba37db17e0b07da8030d00f0695b63304ce42122ef9613048e7ab67b346103594717ff111844ebf9cb262b5a20878f2f30705b17dc7e51c00181c10e68591d45b516d479a435237b415a3c87da0dd96865975b9eccaa82bc4b8bbbb6ee353199d0894ace23ee523fe2d9130f4a55770030654ae52c5a5535b23f0ed96b4461278671829b23d69a7a5e451c7425b58181cad1c50be16775a4be374b0d6ccb70458e626f920d5524180122039b8c781050e2872efdcf582114e8b60a83b599318b544f16dc7290bbae81d48a2cee9ab097a2b0e249d2ec56e70180e2c2fc31f4c20deec1fdf897640a503fc6b10ac26a432a5b2d22a03c6077139132d7b798408a509ff97f0d17665cfdfaed13adb40e06fd8116c11a1c41a86c7a60481cfbb38335dc9abdb1e5ae5a42ee4820744bdb5366ccca30290b60f81b35bfe63e0dac40dd15e101e323a43184bd1ee7ed494b5fd10fdf92d6269243190eb118d8069fe43c12b695dedf3b45700b1b9878acebc89e9b7c187029fe32275f5c8a4c0beacd3e5265151c27714fe551280c42cb212c6d5e70f18f6123f3cc4bc2631273971b7714a56be12bbc76f78c849a50756efa5bc27ea747e3f62548fff0746adb7b40844a6e744a829e5897c7abf032ea27d318a2829207bff8113676d766a6a7dca30f684085b9d3169c9f057294d1dd3ef724fcc867e201582209634fe0670b5f4c7137481dabcc119936e39454d450cae3455ff0b48c9ebbf509cd1543313f01ac61ef50df3ee0258a833202e5d968095ad4a455e27e60f255ce7005287ba8e68df2ebbf29c1a0251c44609b3ed2b6adb2a8029e3501c37b068d73bb5c9cb2061f72fbab03281ceb6269455cab761a509a53f456ab0d7fc500a72845bf25032e24f4394a07d2f4f1e0d732edb4136828e9bf7bde408da850ed749b64500ba3f0f7242d143aa45e822c7533411d8ad4ff73c105cbf7b09c37fb4af7f681cbbaf643b8bb65969c6335d58e9677e9f6d2cdf6447480ae61a494d0528017075d68e11e5aeb1dd4e60ee16d48a84bd1d946086b9af548a46a6692078cdbd3f87ea432f127b277a70498e3727f5cea0c3701c4600aa19d02d08f0890d4d2ab44fd0aab0265ae61565bc29eb900964b70d08d745d8e4e9c9e86132c7d0445811301c5938266944d14eca753469c9f7e6fcf71d76fb88010e1843639ff786d8f91c3138d8915b18200e5ca1a96618aebe090214aed9dd5fec065d043bf0f46b0612b0b23e7d4708b6669be4b41f94de43f2c75fa6f13fdff8eae559961d15d7f20caf91cde59727fddd44f16db46e3bacd2098d717afcc1a81538bb0a2c9f4ceb69680bb1c0a652f8f151470e465aaeed461e9aa217c2211f731171b90848368f30c90909cef7be0efd4af2dbf38b8af40aabe790229c92e3a6163ab01aa1dc29ef8ad573a3f337112536c521413352d411e089b6fce5cbd1f2b45ce09cd68b429823b35719fa8751dfe1131fea8527508d23079632586aa9bd0a381df2e0f53ae24f7ba6420b4010eaf6c377d6825952295aea6867cbe847de390ee674b375b7bf5849b5773acaf82c3eca265cf9a3d06d7e6b5e9b1d6f1940e1feae53611e51d90e313d0a56d0530a394c28bb8c2275c13191ddcb7a8da429b85900d0048e4e0daa875b136429c323914fc2b67914403a0041310e441e5477a10c869633947699489664e215f3460a11e6ef72b525d6fe997d4569d6f6d5c6930a9af7a84bdcc1bd58e43c5e88aa4c170999b4dd18ff418ece23c32d00b95242e8477192ed67b21c45623293e106e1d40b5f9ea180ea67c2e562be575259fb6670f688831ce8755bfaab6d93ffe19333e0007132366bd61e37b68a7e53cce71b74f5b86e58b74160770ec388437f2826d4298c7e6d2e764231b03c32c69fd4cf24093ab89d9f39e86766176742b7c382f6f113e85918ab0dc0446ad91bead5e8d853ba2672b626122192c5acc7ae85165618b3801aaa5968c7f7e72d7c28080fb15a9b2440265f90c80686d8a11db09a80d40e4bf39ce7d88e0b721c72d35b3949e83471473e4c37b9842d0829f03b401577c443413c25fc65b9bfd4a199b9bacc6345add8f1820b287d10f86ac6b88b33a797fe4ee0331cfb9fd2f99cd0c698e3fd98b367981baecbfe554e5d0ab06729b1c8679262d4e9cf30b09cd901ad999c3c207abbd4ddc87ae5f21868c48db4f03222c1995108e57aab4cfde4e47e030cf57823f6f7ae0ecbeb96a082e32d270dff6321623a263c89d10e4ea38ea8182bebd610b480a7aeaa2410491711fed06624bbda6c52b9a5ed3e7054daff47b45cb2bdabca4d54bda5ed3e1055d2f69f092a6d7f47b41cb2b6d5ed1caabd2207604ba42b7634e256606c3cf79d3cd47160e62c831259ff424caf931b5864f8c29d03562613e82ff6654ef880ed741bdf78aa7b7fae348b8d680ab198a35a1ae836a3da8d641b93e94eb40bd1a8a35a05c0be50a50af84627da8aba05a0daa55a15c0d65d64029b8f322f292926a7d28d641b91a94eb42bd1a8a55a0ae856a0da85642b90294eba05e1f8a75a05c0de51a50af856205a85a0905fc950de9115b41d4e75d06501f03edd0f45c42a3dd01eee526bd145d1cd0825024548d6939793121ffb35020fd51460de09449065f543e709cbd96874e72eb1883d29d8f0e3ab5799a481b188cc5b7923d35acde7e860f8874357e958ce0276397bf7f8b02cec2ea07a06e49d21b88f967a47dd8085a4122047d5839cd1179991bf4fe29debc304e345d73fb68438e0d6c2744e1d0b8ede66fbde9ca70db7dd7d14adc0b4881c9f24900640aa48827472923429b19d58d744775a3da3d49d80dc249955245fb8f1bea2a0e654b424930a6b0632800c99962aafd2283e759919a94fbb757eac1d3307c9a9e75819cf270b2a7378f2fe5f27865104029553d48bab989d569622de353fff732e870a5e219de45da3739afc68157f15319a4a80fc313fe37ac578467d180c6cd3b73ac103e35b86b5e8877d0a054430d09c4dc2322d87c8f2f109e9b98205ff7fda916c1ca50b6c5363eedf6f9240e678dae85aad8f107221ea606de5f1b83296c5f1c41fad5696d065b6c7e87d613914b569df612caa3debb28266f9ae8c746ffc5bbac19287552bf22136a45564729e82bbce00324570116d02c0637527d9b963112686b9b02e0501663626d00ab2947d1467e5499288dd8edebd96a6a1468157851ee80f4fb78616e44e0371a40ff9de5cc077c263f5eee41bd3c0e2c96b96a2721957337cdfc305983ca40a73718ec2c108ef64e6e04c390dbd8a3912d9b078beec55e84b7ce360d8c52782dc7de827476bd45fc937a107c49c7cea98f58f191cb5c4b365a39efde6ce627550917a2a1d77a7ea015af8c0bc9cd904768eb28d50be200f2060b3710d919bcabd28206da11034e9f6866eaf7d368d240ba037b645c186ef9eeba41b3ba81dadc3e6628e8965bda851ce44d1602648e664ecdbfe8d53f98a53fb984f113864121e2b811dd93d0f576e232455188a830d57a6b2168258be2e7d23fe361397c0fffa0717cc08bce4f311898c120fc14370e92671a17bd62cabc3667e1f1b5bb3bf3d53780d25215284b12350001bd27ee31926f3fa9de2b26ac5b4bfa5f573cc9dc4f192db095c0fa469ba8c0392a3ed5e6cbee47fb8cbad6da98bd3a7bcc37772b2c4c8c4f251172c0251dc788989636b559ce5131092835f5c26791670dd52dd9d5184627e2e42302f51091c056ab67123a8cc4305b7dbd23c2f83ce0e427849688da6dee0dd241a4cd191bb43fcaf95a5641f6e90aec641d0c9549de7456be4415d42d7b1d34d72f85f5d340a25f3246227ec17f51a93e8547766c4eac0afbc9eca541fb3e56270b7500b35c7852356a187b6a01622a338c2db22913010e0c0277151e8192b35078ca7d080823adc17af8344cb9e80fccc911b9d7c05ce97ce6febe88675951e6e265c41a3c0afa49c8734d70a173ec0a0acbd61a0b7dda9b8dd546efeeca9ff39a59cef3010cbb02554e662cd8abcf878b63b2f4b700c576b7c39421e8031c6b60a201cff1aeac2d563b250b1baf1eff6886dec436af0bc0ede8d8a4509d7a7760040e03977cc39760393da0dd70a11e84c9abc4cc50557abb21d0ab8301c1c4af02edefcf852fa972380f92f4309103c5279ff6a0e7b534718424c668576cc4a7443bd9ad33d88eda265df1d02bae5060aad2b840deaadeca76596c2638eafe1561396352df30b9c8e0261ede5bc5a03a2800a9d7c013030bd2ee83578a521244bcc2900d616c2c1842ce7db50a8a5ec326745c5464b0f45617883391928c35956d877ce03bef1aa402d6cca4bc02d7361db3cce33332b3f0a66021c7cf7d1b8064d056d6fc44b2ca914f4e17e981036a69ab981a406b298c82c31e750538835e1ed99c74ca87a0457a600fe0041ee25a12ca894704d299eeb588706cab12932229dccbe7070f0c44740443626e4f309f17feab2e48fc5a0bd215c29f5ccc54cdf7402ab94d55a2dee814929ba489f90a1e52ee325774c6e3d960b81b8b2f6082103d4d613a7fcada10452d5d44084925f524b7d62c73f0f3ee541240e3d78d40d2f54353a41a0d69eed6faa431d2967c1b2852352877708a2c4907c584607e5d6bb6cc80d6eb1f1eef42d78e45ccc2dd6ec34ea53e8a7a8db04f0dac9a2c2aaf0dc74223b41ec19bb9b06e157444914aeadd35bdfbf4f6a4e8ed5e96cc0fe5d056db45eed43a8b75a550fe91a6a09d59f2400f1347238fe705d6f05db3712df3605b8087c248343876644613cee91d4a1941bae6cfa1763b476ef0d1b7034630f46659bb5f414ea427be06d8de758e4f9a5fff0a1220ed5886dde742dcf5845fecdb94e71ed60df0932e6f61fc299ca91f0c35d1c68255849e0be507d6bc2f3bc70e12529b6c141684497df7f6a0cfff8cf57b028dc4a6730e13048f076abab639268536505323c28c914343ff5345889030a75aaeeb0fd22b2a9cf3d643ec14d6cedec1aecdb6499e14387d7054355127bf91d71c1e56727f3ce5630a75c1adbfea9ab376f155673dbc8b56daa480383dfbb0d78b0cf9aedb2ca2a8c11a76bf2bfe78666b073b67110c4dd7e9d611e89b854ea9c2815698c91491f1638aec3cbbe2d31854fecd79705576805df2cbdfc542c94f8f46cbf39bf5aefa533c62b6eeecf77de45b243ed882dac9ab505b3752d3a4eecbfda91805320b084121694115b06d3f4f66d64b1b20d74f158005b0ed2e0c778aaa11362770d6322fd5ee04bd4436064711f3d275400df49d1b1d7165a03a563caa639b31324ee7788c88ddfb1f982b503642ac1500f787421de16316df34445421e860eb5e67d897a5c4719096c2edaf29a00552b4df489311a0ac256aa25e65fc528986030889b2ac3fe5bebbdf872274bd7ea49775396cb08202cfa9cb4257efb565d97a08d12734f4cb6cc70b23564d3c4b0c1c73a21e61d66c808700dd867495c375e39aec03a5fd5f918f39e7ddd2da3b3d6a856cba14332fd9306af81c0dc7fa2f612066c4f91c1884c44604fff8fc46929567ade5267f6214733643add5b80984f5d896762272013e3547cc7a0041f089dcd846302f486449efe3578e6ae3a6083342ffd6093a7743069405f5da6c6b68a4e6c066a65c65d11d7f146d461dc1a0101dadcf9705516bb96684824bae49e82558dcd1c2293e1f5d1d97a50a1c07feaa1cff45a1cb3a091ca7a6a1643bc27312139ff10563c9cefe450a45401afd3b0df51d240765a7961070929f31083da206bee5300901bad2a9a12e44e50837d8b69e4ae08facca7eef793155cad7f4fc1e23ed85d11456a59810a7d0b0a1f3644defa2e4e3187f0aafee7fac06284c8cde9c6bd349d2c3e24a9c023be3cf6523e2c134f979493b9a68bda081a1b21a2595af8065882de07f39b2fb404b55fc05398338def6af4d1b815369651175c5a0ab59dc252241b19afa267001ca7c055e271d290e7da7d05ed8df04cca9fc5b40318f241d27161b3271d4c8ea49a6b7b21907a9bc9140de855ade05accb041f66009a04a319fc633cba4a9055cb7fd1e697407c7e082176d0d9b48805d2c032f6ce94735a2a8bcdb59bb80f755c44e892168c7ae71789b9d026ed6c9ccf95dfc0d10ea3ff91e670282b48f408069f025be0ca385ecc2d50888432b597a4cd93d384995c25d287a31a7c70f6ffde8335bea58b745fdee3bb61fb1e4ae2eb0077869f6958a535191f53786da96a9ca20d373939a01126a70dc2365aadd23bd79e98b7e67c3f06456f9570c3ba24b46e35aa4c77ee0656d52def4eac8db0ca11d4eaa0ba33bc230fa72060b12cf85e72f31f927b6a475da7b700ab8ba7e90a4659e3bc430d70818e8e9407ebe13c28718ac44983877d019b066fd1a91a5263e2d84f30944a4be202caef610d0149991b0ec6242c90faeb779298ba6563db45172a8dc20b98971e34526ae34a66c6a890a901af01c538f56f780550e37e383b395390ac926f012e50076634c1824857aaf1281ac868c14272a4e50d2d9fbde667393f7871a0aeb9faa1a5db5db01c75c447a379e1ba9c94396097e2ef6c64b998d3b0c717fe98fca717d03bb972f2f02fe0a8e0aafeb14d194862cf974a8901b100afe6787dd8e3e4e1332e7cf170781ac3b0d8c99ff4bb37a3ba47e97edb88ee51605692dd0430b34ca74fdaa87356c182296a634b48f74b591a80e8d2354a056ef84bec8aec3f9a28ae720a06426bd2f599ad2ec5f27d1633c0f80b3ac8ac2364c402b400c14ef9ab59160be4942f1a46f6ac85ab5d202ba256f614846f7796aad6f36ad5dc8e2770688b3ec714dd606d7e22124bb1cc28015d9965df3e6120311e31667c3955d22800d748dc8ba01363c5523c3ad149a3c0ef16e6827071acf4b9440d6689ec0e76da20ddb250fbb4b30101850ee1fbbb09c92321fc1538cbfe41794f51e665899fcde20c6e62c85cd5a7e89dc011031c41fd4d2418d61883ed340c44e715f2a3c0eb2305f7fd44835c0f7590dbbca3815035cd3a3448381be9fa84cbbcf64474c8e1e829f30755f55b9bb5f3eb8fabb05c1752966e8433f42e358243765a84f3be91e6b19cecf168e2c8a9477ff9dcd8ecf50359445e7017e0428e00bfeb623ae9102647dcee36606316ea066374a6d68c63c2f4ccd70036b14ae4575f6903f7ef77ea3503314fe2d0974ea84bf495a56bc8448cc9a1024a927c36ad8c11133e39ab836cec68c7fd0998107a0a94bc404f3ada7ecf5fe45cb1b986dd7dfe672a15750db496d2b3fbf0ea75d44411b16e3943561f84393eacc62a1cb664a54182944b4a872b154da6b8a617c45b668b0f40b0c1dda071782f7be0f313cc2ce18e9778c66326d835ff4f290594810fc348af36dda4a9268c0139d84d5f3c1d700ec028763fb60782ff2ea95611bf6fd9f51e5e097d7e65e2fc02b4f568406ab4568d24ca4abb3a91c988c9fa067ba43209098c260a55145fead19a69136ef0aca15a48511c117a029e33d917ce7f05b748189879102971828a0dda20a5c8d913731247c30b461349c245efe0f1ba2fff01b2e88eb5f0be8484bb1b531cf0f9de480969ce108dba5f44a0a834f8170840a1107622d08b2b83f07666677de725f637655fbd967b4580772748dee8535f754e37a85c58be8e9b9041c0e5918e5da8c4b3642cc343308b1f1f3432c57edeaa8380be8aced1537f27f4558639f3052040c73d9fd2af5b2c8591b914d8a97c5a97bce4efc9485d5499f7df7d97dad164ddd621cab1e1263275c1e07f1fad0b52bcccb0d1f9070b98fe23e4aeb751aee563e34ac6b67de433a789a80fa7858014ba660774479c75fee7b5d3bea327e86490b30519ba65813da062d87c727d1446ee10b74d82af54b54d3fd10472a9621c8fde0ebb947b6df1b76009a2ea10da681dd318bac6792c80b85682c973015401c4b35008c469a06a3c1010ae020db70607a40ad19b6a3b28a654c560856924975e2faa37fd592e59a331ee6a478881088f805026cd2198decbeb9cc769b6a7e75530e36928ea91fb687813bb7ce2f05cc14b888aa163614af243a912ed153d3a18334c1669e1e7db071cdd7d13c340892f5a784ae904c17bad08cc6887817cf85e62952391d8d3960a00f61b00080138201c103b5250aeb5ae0bd0d3a08e8446e60b9804dd211dbce942021f63c0e1e5711cc19e20d548e7c310ba0303a0168f30b35aff0754ef08d35793dcb21c90c5c485a946f600b5a69e2e223b806bc03b03bc2fa975d09d20b451bf47c446826a183e9ec5419fc21c4222a8568d5018b712dc3336eb0e635a53317eaaf7360305f2f10f76fc3e9ecb54f898caea60981c5858c497bc68fb5ff7138d75cd5efc9a7f11b8e5579fa8e1e51a3ebd1a396b9c94a1ec43df030f80b4067f4d53d5a9bb5806c342fdceb6f1503d668bc50224d1cf5e656aa634b5e81f9b142d062fb9006045c8a5413c9f330e77b1519923b53a3faf3c6c70bb1f0a37a985b7617ef894fc2bc81a2472233f073b665c6246089587f3e62bcabb0582aa20f179d86df7a9d9d3ff22e60eb48ad680fc3d0b0e76169d81c0e4e8243c77d3f6f46dc94326f8bd27f8a87ba0f62ab7358df6c93c803c5ba92a1cecd48c8b01a812b8189714966a37fb090f359396c3270ce8a095f1fd5e33fda1c0819c13f8968fe5f3f8fc5209fe12e1bffeb4433bc2bd175028ff9959f0284254c38081031dc51e84c5d98865251135acea292cacad25648e53f2da1504deabaff161cd5f13fffdebd9aabd78f27b3acce868716c131c87030beb86a9912474fbaf892f300d9c5bf4eb7f1fbef160c9885d174e70ffea752b70d60cfa7844c783937f7223f0b6fa023d198a8ab2dadc02c1a2724091df006159cd7e1ed505c1a23801127f9b713e6580348062f7c6b871aad5a29a6a283a622adc6702142238e8495fabc60312f756f74708d716daccdb30c33a7d1973fc91cefe92a96917500a9b7030497e278d8fb673eb74a5ffbe570701050f3abe829cc423fe3f035df21024eb9402111356f037714748dcbffb7f7f8af722581b0ceb427c3721b16ecf98afaadf47a84cf19181265d738ea91b90a9b0af675685b131b4fe1daa4b0ab4a30f87177e4e564e2165b48e3181b54d088201121c69b39f1d575bac0a17216d660ba366da1b25eede9fc086449ff66642531b1ffdd5c0810c76f23f26589a767c13c5cdc3353142d751927ffacdd4e0145dc56b6d9b41e209ac539f7d49c4d933eda7ffca815a31f362ce8ae117604dc259a58e0142bd62d6c0a36c5f4933610bac6d5a27745c69a00d07e3a4fb8f092bd2592b51991e6891a0323141b40cfe68a047b513725db48c558f6ee5ff24bb8079b3fd2697be4ed42c05d2f1050c48b8e49d783ddb562700b53712a591d881b4b16eb0877dc89688a9b1fb5daf9143282dbc93e78136088b97e09ab97e4ff491d8f6e56eaf900467a9cc58e0f69be5ea7b418884162fdef1d1b38a453a97cbb820d68f9b5964dd5d77a2469fd10be0dca7910b337f26108f73956883c41f18588d2054ab33b3190ca35a383c2b317874882e4e4fbee83c71cb55a43736a4792f83cdf6f24b2d35973323c31d4ee1e7795bf80ff4c6b15c3ad29b75bee34c749a194716eeb1bb6e2cb1b027830f91db9a19c7ddedbc9fd1b902cd14edc8f6e7296ae811bba4f5d0f63e6c85c5510424227d64c08ee5a6afb9c743851928314cfdf5c1fe3155dacec4a416649725fe2e06df510de62d66ff97a21c98ac148722ed2239bc338457658b2143cce3cb620ba512db14f207502a4dd93eabf7c59ad3e2d65d96626b31a1618d6b3e3b5d05a355a6da4aa8b4e2aa510cc5b05004ab153fd3cdcf8e386480ea28cc082b133aece8d3f40e38611faaa8fef65b20aa26cd01a76453ae4946eee909730ca71f4b7d007fb707aa822b3a6553eebe1260f853b329226b85ccc342de154f25b6e7cbe38ed2247d4558c5c7a37802ef2c5ba09baa65a7297ac306157f61694412bfc25adbad8dcd80a43d86f0d21c441e501ad4fecb0351e26f1f19610f8c7196a42c5dca8c1c15828f41969d76b3639f4f982635e6517742b044828c99b540189c15c35b22d0421b46423e793ca1a0c4f28a72f06d98aa62aee603bdf62868e892ace2ef79277e6792a639dcd1391c8f7592ae16ed425f6c5c2bff01018b3b46f484f4807674d5666a8717ab292e134d17b07a49d81ff36fb32d4ef44c1052f58b7bb1feea08482d85ca7f7f56b6dfa5b97f11afdf605d470e0222d9b4412d6da39d18a4edfe75f9f2d0993a14adbf18b6b8aab223d86cad72256ea5ca46770528303e6ce58e1d766c0e99bcd5bec6d58287f7a330eb21df48972cd931cba066491229667efbaeea44598a1f5d872e3ed351bfb354b3442c0189824691fb352122ae074e7a65bc90cdab92309bd6aa0f6bcfcd7663942f22363efdde8916de6be8d4f76539b13f234fe552d020a30c40dd1a767af613c627e91155f5d332aafcecd982f502e7b5047d42a3e7f319abea4971bff34fc019238cdb10915dc4c8cff930630f273cf4e89361fea54ffc21fe5be4cbc7b0edd57d393cbd032c52573e23602629ba9eb5bc3174af23cf28a344936efabed8b0ea312f92b228617ac2d2f57a92833a5b0dd99edcfe93a8b7c9ea0170bff94bed437b1b27f147037a70e9ab15b78579c7657be614e12339de7a79ba0762a5bea37bba55d4017139b93d65778553a9a92541cf630191647f71e3f5777fa7eed03f76c04e1ba00ebacb1b79f1a7644ad1abe5377156ccc106812b5c790709251e26add36e24993d1a796a6264be44704e8ac2c408ed82a855f0e0bf52002c7351a02ecbb8b0afdfbe9bb044997e690dffb08055644005deac2d66464113017823364365b7bba01439589785ea0f68a78468044e15fe3f69b321ce6fc2251a8cdc74c3c69410d5da3b60c6f27c0b959d3ba621edfe495ef9a924df7546f1e42f7da9ceb2dc61f8ba69f0985db0b8f08822bfc5bc43835d370453ba59b5978aa517199ce761aca9e9a5418fcb44ecc6cae77f0729f37dd1198a70512d218f1ad74953d0e2cc47c0d46c579189df1510c8f635a7d7459a170bb9ec2e5dd989fb936c72a25dc0979b18cfa9c2526086fbc347aeaead47fda014f579b57fb2aab7ab4c7d43b5bd98c3c477ba07abb24c789f9d6c5c6eac5e3bf84d2dab7841367ea2f1c38cd9d0131af90226cb63f147bc6aed7125d24279283b61142f5fd2b6592e1fd483d2145d09cd51e15833bb481e7fc68fb47916a050bad8dd0e1020f9badf72dd614579c41b33821b4d301996480e4b777e0a830450e3848eccfb1bf9bc9c128f905bf795990fc854f2ca8e1759de01d19ed95c307f9818bfe4a123e47abb9097c677047bcbc3dd05f7c292f5b3d24b3fa9568c1d4996255dc3d973ffb95f9c4af74f08bd7e42f751b8d9b5faf7afd04f15e5dc7c33f889eeabff65a2f99798b5321147dad95151fb2235753a16b033d3720efaf8a4b35951c4d658ae240de5deff475a78f73527ce52d16e94bbe19ea863317ce2a50550ac869ec471aef6c440627f93b9bab76d25f5d976e0dc05db0104778d88d99d753446d8c7e615a1bba462cd4cfc4d0bd2746d28c04193670d971cf1c903bd8393bf6301e37634deab5adad8411128110d3c6f2726bc5cf961e1519f25a64fc7c8e3ff5fe78966d4d5295ac82e1974f212f3e8bf0be889020eeb1653012fab6ca406f5e8e3acbcdad47e72c31e0730cd5f3aab4c802591e35652206d263c2a1da4eb649019ad20b9e309f5571ba0c31404f5c1fd295235b66b3e72b839bee18d043be11b8fbf5f4ca82f2c079a98eaebd1e1612efc6faa657c649cba17f0da8670d3d6082a4da89a10f4cc481e2a2f31c6e47eb17c10e1250e9ab73cb98e44cb667a8fd56058eb19ab14af0dead7075c054dc34d7dfdaf524ef60919784e7a36fd44e1c41fd9d10ff6309e9ba25ece3fca9783ab5d08edec8dfc10e692f8637782e6b4a138d06354466f9ae60aa88f544178e62cdd400da53ee0adb92a205c3249f437e2b423a874ba808bd44f707c3504a36fca66066feca79069a0a9a3ff475c25bb8831a62faebc52440556963c3f3195fc02a82e77f020db90d55849e4fe03ec511b20d4bce135da03ec239871db0f4015d7096c538ef7882eac16c211fc1ec9554c27b822ea8e2f2e576f091eb7f1dbdcd4a64db602b1057c8daa3534575b2a357c918aeb0f7ca0be84ddc11ec6d87a05ad121a25a871c82925704fb66ecc901abc42b1578d8606640b7a4986d34c5b1f6093852aea6dcba012829b849ef8206bd30b464da903fa5a75528474209681fe133369c111acd0279eab4e0d1bb2984c168c1ba4c5ff86b248b7231e2273ccdca2641c8f7d927e1aff7d8a015b3609472036ec30d202e5634f9b0dfc54a0737df5bac5dbb15d362f17797ff390db0eccae5e25b0c7480f0232a4f9bee845cb12c3978170059344240c7fa18b6007012950a4ff60e7f40d7bccff628dd38e174647b6e4f3232961358af206b7d52030d35aab550f33cff964d641041b3cc044b7b8f8c3278675933504768a7ad5dffaccfdcd27725b0cea30136f60613248b3261e9a454d1583b8deb4c0d4c99f43bec6433c1b4dd09c88f196eac093de5b6850c15f763ae3ab213a840369fb63595274921e047d207d02f4189356db717ebdd616618752c9a5920a69d42ca35a93b156f5c9bfc4aee876a54d5407466e0f384baaeaa55514c90ea7185ffd7bac68b5859e36e065a859966a35e5ab5ac0f1ade5bde99e0b59d0a8b56222a049d6841004cd7520d2eb3577a281875e360c218ddb499632b38bf87e3b105ce70ec587312c8e5c1564f2ef0ae9081075d6bc19dd0e2d2d6f48aa5dce964c0fb883278f2f95636efb156fe9d2481d54d78c17891b6334e003ebab53af7323ad3243ac12858569d17a57c5ecdf25ceb756de4eec58d25541a622f33c1d55daaa0c4e0dfb2b32ad549c0080e24e8259c8123062238813f3ad39815dac2f65f6b9577df3002669b94b111ad74472c1449e65be03ce6e3dc0400961c362cf6e2aab3071cef66cf4fb9a884c2c3cde8d1d05848fd94cd3c98cbabe4efb8cd78b731e6b901bb8597ef267de1871d787af6a617aff196fe7a253d3f9b595f80813df57a2ae88eb0c8c8bd76a3c3e1e08e812cf7c2bac1464aff8be6ab2db012883d57424522588f3c513d7445ec1ba9ba8ade5498396caddeb4cc8722f4bd21a7780203ee21a365e81e253c9daf46fffc1a4bef966919b878cd8bac900bc1bc9472b883e9ac91f37ea29412d560909ba25c4492d3cf1f86725cacfd1e4af9a647ea5c1b35d1f4f6af7a43668d07b05a0732d35700506df5716d39ec84b9e6d6048c5068a4720ac3f070b7785f3069ae13ac29d75ff7dfcf6be2c579db539148bdca52727aa7b0533a9278058d4bfbc87ab4009efe9fe9866ceb49345a76613138343d850d114273d1c7461f8ad5627f24dc5c7f58e5cd67e5cec47b4b0c8bf309f3e6fe4b48f65f8ad23e764ea5e2bb9b47d72fbccc2c7eb683b61497b46f679abf8796184883b3ae4e75428b7bed04ab7e46fb981be29f19d1e7f067d1834ae5a3e733bc46fbee66fe932af1ec645fe28391ca5334005b5b5db61c3cdd089f9b02cd152b4c30e3a510c659713ef2f659c1f80b3a5c7f642b4fafeef2ba23622b3d51331a9b4d0678ff76068fd89013a6607f7b81f38aa373cda35a967bb6e3582611c0dc5a93d95704db561669fc04cb8707e61b810290a17de37f179bc6edf83a3befe42be7ecd8196073cbcd2dd8dd57e11d87a3e2a89cf485a3201974dbcf0108cb8f3c9a8970134188f10ef715383d9cfaac615d8507540cb4928a53a39116fc3c51df7ad2718af1f7098f511e22c941135cdd10bec6a64ef62bb55e05555703fecdc10ec9214167ed8d7def6da42e6f508a735442215c4976322074e10488d73f131a15978f0fc10365806ac560a4d4348700fbfc593466fb151c6f25074e0c2c22d8c5c651ea1d2efb4631edef6bcc81f42e15415faa03e9858dac793070458f2f3e0aa9869fe212fbed8a242bcff7090eb1d59deb204875c7151bb376151ac842c6729f52159cdd916593eaf45e4f812a680460d6930857049bdcc24192793a33cb09c27d9d72c9381e57c645338cfd7754a0022ceff015e2456bdcc21c2e1291aa9a2fd7028aea315f22670b27abe7712ea789f6de7b92d3f0b9f48c5f0d8cb57120e60caf2536f3630130df45e62d83dbbcb0079f54610a42a0936d6f4d5f96124e2c7a1e5112db69b0e0619da96f01593a31cf2c649912c876af83bccfc56cc996c26ab499ffcf7420e5a9845ff34f34b2053f4c445b5ee2a3daa185d69aa076aab5cf30fd4960768c0d32b4bb6b0cfbf44d5af84ae08517c9b0232ebdd41983fcebd74f780d877b83767e6dd51a1d72f7efc1de906dfbf93a763cec663ba6076bbf1fce757dd885f740416c72cc87b19de2bf6d58936e620ad94fb8dae6c15e9d8b6a39833ada0c36a1f1623cf5ac8cce1b19084985d5c4c6f4dc5336954d211dea5541de91f59c73ba5bedc7c21ff843453f7ba705490611bcf2909dfaca8c06facc978cb99a7264e5f1e74d06de274f1559f4dae0ee643ef0d63eedfb75b34836d1273d0fbae92596c6d527fe7103d2465bbdf9e2c133e16c327bcad053f708676b10181e35aa6dd8732193b32cf234f13ca637134576f6b86ef1955cfe490c216f5353d75d61faf008c56e42d4cc4daaae20e8bc1d9dad97ab3aa0e45d674128cc6721061ad58f7330e1881c63f30303a458ed8964870464e839576df6e1d4b058a47c28aa41ca642304984b460c925b7556e82bc4799beb188fdf1f19a410b8d9e1b0a997c31f1514b598d08b56bdc55d4a78c504c5dd348806c706808d6a34931375bb9631b4b2a31d62f1563d0486be78410d2b39297767ebca52d94f03cedf690a147df27a169e0ddcc91d66c21e5202b048c713dd15c68aeb540b29d18ded518f2eba6483ed21d8cdd049712bed781d35b357f8cc44f4bb9e70e3a7355fc5c91e100eed82b0382380a42494a138365bbeb4245e382eb46c1c331deb92177ab68594abb98b7aaed428be30571dfa03fc5a562a9b1dd06e718ea6364f208bcbf59e1e1a505c853855f56a224962c45b0883dc2fa01bf833e36f7a0ba9790ebdf4de5e0c527bca2ced34c70f3baa66d742817a15d8252abcf2d50f139ca70f1715a2b1f504ed59bd5de971950447759e577a0f9f2c2cc3fafb32f39e491f2f01ce548e16f8c0157de11e4cf07226cd0edeef005f56cb481f866a991173b188fe4cafb9c13aff14620ff099e7576797ec20a564f592ee7547bbd081db6d5931d7fe07b7e1763b80b34aca59aa4847319cf54a0202ce105ec8008a82a12da86a2c4bba54f05a4f1bce3448660546192d7d91e8a1037d75221c9614e5369dd8461a09f12f13c9faf96d04d200acccb357ee35506bede168960a986a1d32cc5a0176e51472dd121bb18e39b0fe3026764b5e7c9ce64fb11f92da74de5e3ed5fe80f4e11b7f532d2a2b87c82974c0d46f4b6873766078d787e10c41128d1508b31e925ead236fb3d7f8a5352f47f93b36fd101e1bde7efb4357ec1af3f418f54596abc5b7cf815d2c99964e6b91ab0354c967e3c2c87dbe00550075805f9a1690499afc7caee3d278ca7f82bcc46fdb6c4ccce8c93c37c4bb3710d78dfffe3b18734e79673cb5f3230a4dd535de816ed4360b971668fed7da7dce8050f82f0578d91bf6747ed4ca370bda490be35c2f656f5448541698ad8ffe2ccae62d6777f558f4b9ec66c34e515b22134f8a57f984c0f471091d980dd1f7179077e5b4dbad17a909b09e253aa840e664a0d897102c8c0b4a6b9a8160aa5a24286103e2aac0e8ab89d1dc47acdc3fa50b9e07623f4cdd8f87c5176c897d0dea3df56052cca021ee984a00c3760b26639ff00000000000000000044b351224c2394e0945292d123a850c6b98a32a594a44c918b8377c199894f67263e14111131624709f00bc30cea0c288b3ea07a3bb4c5ecc491a5a30b2ebae01b7cb8562d5a25f5cc577164dd58418eb7badb0396d32c47cc089622a7c7ca14d697f5494e7064ddb86102772f3247167a406453f324ddd3bd695f3890451ed0efbd9ed6592a9f9eab40167840a8453e25d4d63ba04cc5c9cc6d69d2a98c043abae0423190851d105f2268f6c709dd54e3c8dae1754047d5b194ad1e7392b8d906b2a0037a53ced8e41b9ec23238b256e07340b97dccb59944836f2890851c90d9be3524994835163c410ef62e13c8220e2825ccee3bfc6a0c56061850e66d11c8020ee84c723f924a3266c8e20d880f4946cb14fc7cd7cdc20dc84c9e2e2fbcc3c4f659b401e9665e729d6b31b96c40b8dde6fa4d1d4e9bb706c47fb894b4a7a693dca80121eb7399c7ef82a4888e1c9815f8c08d1d59a4011d4c27a52d5ee3d5a91fc8020d88e8e1c4521acfd59ac133a03b84fc5d1b2f2b5f64610684fb5b857ca6e7daa21eb80a7ce0c68d2cca80f4ca5de693aa94c8850ce8926f2ad3ccc4fb5d598c0195256e6d327d6584b31003226ffcddff95d1f68e05e9149045185029a89c5bbc94fbb204037a5b369a5f8d09d3e2179031ad84a9d198e4c68b171031690b9f3267c81b635d40ff7a9eec5ab9803ea97169f1522d68c81610a79a2c252d934cf85a40c7b59843cce677f965011d5b2d965ac67c15f35840c7a69464e88579597f05e4e697e83f26dcf2c4ad8092f7f4e193cb86f5ab808a977c7278b0a980ca17a6624c228f5cb0a780fad2ed8eac6929a053e336e6a6a09b3f1c0564495a2b4d39cb7387a1801cb3b3c9b97332aff80948d9485237c993131066e633a6e4a58a9dd4046476331fc95d13c54b4c4029bf988fe03f7e91b404449439a5315b9e2e929480de7c7b12903906cdbdd499699545024a4c5cb9f1eca01fb2474077463a152b05f3b18f11906fba4eeb5f6fb0d22902d244b52a79d584ac1e22a054e3a970b14d3c25cf101021ec887fb8dbc5ef08011592743a4d5b10d09d629acb577fc8910304d4d9a7b6cba69d76f507c8492a84bf0e61d3f23e4084e81b4f57b252d37b80f23aed9fd2649cbb7980bef7209f9397fab93b407dd49c3224993e4d923a40699e934fabecfd740e50d69f3b96f9af95e20025dbdd44ccc95356ca0d90b14d67f870c22c6c80d453fbaa6bd9c4d297450d507aceedd4e9c9674a97050d50d942caaf3fcf6fe2653103d4e64e22c9f00c89b1cb4206287d49e4d774351ad6c5029d7c9295a826cb2e1958a05663f08b1cf30a942ef9ea55c97323695c81902f652b49f6bff7a5152899de3efdc524448285158814369b05c91fe2e5ca2a50fdda711e96c476af0a74a7cd34914b05b2d243e35855872451818c8f706649fd85cb9d02a5faa9d4a5258d773105227e7570b9944d29cb4a814c37b3bab717291076296c6dca8e025d31eb9b5fb294fb230a9496606afd9ed3c53214a830c1b287fc19173450a0ee2d87547d21fb21f509544a3fe77bafed267b02591e9b3933e1b6bc4e20f355fc5cefecdc1642004e203c6d2399ba91b910d200026c02997c521c15f518e7621c5937b8580e1843104013c8ab581bf4b2c60b1fcf043a89567fc5a474e70f269071ea72feda4851737b0954996e8eecb7c94f869040470e1630004d0380080258023db9372ca67bdd6c7e25d09d3a88be5a5af09873a3cabd60c116a60070080250029d3a7ba57415b249bd49a0e432c6b5568895fd220964e5921adc94b6d4a66223081009b4d899d6689924040b4302ede13ad6864f494a7d1e814a953a57d660eea9740470044a6a30cb253457e5bc360291fae72c6d8c6229491981fa360bb39f5e62d87311a8dda4f6aa53ccda2f2a02fde5e61235c344a093e6a8c69ba4a5e2820844d0b9f836713b04d2fdd7ae652f7c55420043a0dc52bc7f9bf14e6e16021543def394cff276f40881b239c9798296baac9b10106010284bdeddd7bd9782b7824026e595959a3e44922710683539796c3da50b0200023979b653d296ff804e5a639b29cd38721a3fa0244d2e3db13e7b3fc127401f50656aa1a25f4a52d87c4067535afecb554e9b760fa81b49c1d6526e4dfa24801e501d61f388d89c07f4494c9363c8abcfca7840e769e78d244c774096da24d190f4b6fd6b07b4c51832a63b1eb173ea80d29d37c90b39bfbe890e68d97462dc94ee7131e7800e9fcbd54736f52f07545a11dd942d15378e033a04cf7b9242d81dd5c001712ab374dbc7e0252b0478034a8d68fcd89aa4861c02b80111df2e5de29a4cba0da8eaea1cdd68baaab30135593b9b4a5af394d6803ad959e13de7f4ab0654bc1829ad980efb4903da53ae948ee392846840a6d7674f932029d26740fd87eb8a26a9e467cd809e0fa9b2b652c36540a8699bba3191724d06b4da66bc12dea72a670ce8349e347d986240ce45127f657e18d02fb3592187cdfdf2c18030b59c1d3e23d988ff05e495ce6351df377df85e40c779acd413ec02326e76b0ecf1eff2b880bcac97d34c26b6bc2da0627f2655e549f7e5b480d8946f4fbbc4b471f359405f47ca4f5aeeed99c702ea640a76a52fb2a5cc5f01a5db62de11ab2aa7de0a48df0829c695895ce4ab80dc4fb26a4be6cfd55301b5adf96bf2d366b8a7802c99ab763a7eb2684b01b5b1ed1b9e354ee72820eeca734e36410125d9c486bde4f8d5fa1390759d83a5764cdda13b01dd6f1fe2c5681aedbc09a84b7de5e173facf31ce0454cf9e465f6d09a875979492f490092b0179d69fafa3ab8b2509e8385a957488637a238e047469968e1c4debef7f04e4a78ed7f293ec6c23a07b6392e9d5ee76b808682ff939279d74894d046405b71573d310f4f7b8a46d4d42408ce9d6be6a968d3708c8b0312c572eb5942d10d07b93dfd6bd92c4cf0f50226acb653e8f93aa0f10732776e19e74086a0f905bf711ffcde601aab3c629a9b12b5ad83b4027a5cb67523a40d8eb470b49cc56b0e400a5f2e57c79d6c4ff3840a867d7341b63d0b9fa06a848266524594e597f1b20c44ea2de8696fa5d0374ce95d4699c8816c334406ec81632936402cc00912d4e5b760ce31e6402c800a539c8a58b9653639063810a9f73bf6788a97c0c0b4eae746777af4044391329fe3d078f2b10a743128b923cdd695a81cecc323171b1af2eac40c6902b564b4e3789f02a10273685bff79cd7f3aa40baa4b4714fe74330a5025ddfedb392b2bc4605b274c335cf5a4ade29505225c73a9582a494630af4c611afb89020692d054a750a1a229f6371240562cd35d34cb49cdb8d0269317bb584c69e594481cc4d594424cfe46428d0492385601e27163150a0426e4ee25f7a8f7f02a91d65de2f247902a52bcc26cff14ea04e4bca3ddf59f637275022a99b8d49e914f4de04ea2c57d96c2c7151d604ba7d92df87b8a6339c09f4e76d4e2d7dabfa60029db673268f1333f3b904522b72ce0939fb4d6a09e46799598a9e9e4f56025d7e99a5e25b96072981885a96bbc486a0dd4d02f112d47c3b9aafe424819828f9ca5ad67f2e91408794eb361e2d064f81044a56964f49fb45f6ca3e0291472c95ee9c1eb7b28e40a4f73af90effe0d53602a9f3e925d3247d125946a0527435a598b16bc92e02255aa3da2783c78eac22902125ebeb340dcf7d22509364fecd4f972ee611813e95d93efb7e08d4f8a959cff749d56e0894b8549de9c2c48b772150f6dd275e238a599a10c84fa63a098f9aaaea4120c33d4e8c219f5f9804810efbbc6cb91408e4a960aeae1a56e30b0894d2fb4a09bf9624fb0754c57ed679d30fa86a5fddb86042dbec03ba7d24ea6b4e512dc907d4ec953ee9eebd1fdc032ac6af7caf211e72a807944e5f317a3e17bbe0c9037a66627667f180d07ad5eebc2af3dc01b13b57ae29b231e7ec80ecf867ba2fc6f890ab03da7bc52f6b8ca73a430744b8c9b1ff54bb259539a09279d25c49934c5e910362835e48aca47e548b03fa53b58620b6f7371c502a6d6c7552cb5ddf80dc7c423f7572d8911c372073c6acf5f1ddb1424e1bd01769c26cd80aaf391bd0c9546fc6b8d780cc62779ea526c4a4a4069458d96fc549d380b63bb9b15172d230291a10134b2f434aa53d553d03eab5dc3c969b7e51d50c28d31ba6a6227b9652cb80deb92b0de79afc924a0664bdc5fda7e01890398d786cf10fcd1a31a0d6aaf764342e7f4c181029e7c9b931e4ce9a0103f2f4e3c57031fee7e50b889464564d1779245cbc80d27ecd29a59f5bdeba803aa523df25edb91d532e20ff4ae9f318f1265b6a23f678a553a9938e84470c3d6412e34f9c9c4e8f2f4ea03dbea0808e061c0662e481785f553a6fccec19811878404c4e132e4c758eb4658c3ba094e7bbbc95a7aed7c4b083c9339959c69c114b63d27c4ae355a3d789db0331ea80b4bbb45a4aa77eb14d0c3a204b4b49929afabe569903ea3e75ab662ebd3a315088210754188f8db9e15f3e1a07b4fecf89ac5f76da64e08012b2b3ea6ae2e2a5e81bd0ad9e74fe89ffab1b74034a539d12f1aa143404cb62b401a543886261df97ef62188062b00171393da7a62fcb55de1a9079673d87ddbd7fdad5802e553a726596da8f296940a5d848a62c67342033093d6d5d964d46ed0cc80af9d5092e39047fcd804e1d6336b7090d62f2325ca5a9f274d34a06646e53bfd7082144b1c780b0142b65ee16abec510ca8d0dcf14354118f5fc380d42ef5a54f6b537f100c08953d0912fd3a6fe62fa0c2543b3ec958912ce305748778f1f73c4e8a5676019d732abd2f9e42d2d7b880dcacf61571365b40c6f30cf7249bee23a6057458a7ace1a64ae92f650131ef597a9dfea3d88505846ccc3813dfeb30897105b489a6ce6f3a896105d49f247da6315f5a98e0c832cdc5175de0d0c0c70276b80e1d5b7c7c7c7ccc21461590e27193b287578efe5301356ba737da479c98f929a0e37636a563bd4bc75e0aa855d99f984c6dd2e4a380d2b1f4797366e6b543012529becb2fc44ef5e927a0a2964548d2e3be6bda0968931b39e4547513d0156bb356ca7a9d6226204292972dfee649767909a8f0ed90682287cb1856024ae7a49fefd48913164e02c2c4e48c55fe2cb9180988f7ca9ea40613c1ff740444aec95539aec908e835399f9224f6b5958a80fc9c9794f614d6dd2602fa2b628eb64b4abeee101039f95da92f8580d60bf215295bcef1db2020e4f295587f8bbdca020195be31e6093efbb79e1fa0424a15a94df701aa54c90a77417f46d71ea0359d4ef2482a0f1021cee757661a4df10ed0a5a9498453421da0bcf25b49d05412829803e469e5fca9f6357b0ae200a5623ec9ad853740fbe4782e37f727c33640f7c432fd314d76736a80385932a71c6b134d4903e49889d7684a79c77006c88f1362bece9842f62e860c1071ad348f9a5934532c50396b6c32132e216d60818a9aaa77b5944e0e31af407ce746df71cbf14dae40098b94b172d81836d80ab4cb67f5f4dc9db4ac40fbef69d08d57eb216515e8bfba15af9394f95305ca4cd55a96bb5459b354204dac248d891f7312a302fde62221f7eacea6d429509f5255e53999424f295a595b8a1c00462950912324592f51dddcce6090021d49fac7669f63470f529e63478f53810fdc28008c51a0d45eebc4bd8888477d87a92dd26ea182cd40990a76c70460880291c2da47ca298fe954c1915ae0e0b10fd0130ac485d3f85922e654e003375c000314e8ae091b5bb74d6beb4f2066d37667da10672cc713c8486539597dada76c3b81b4be1c89a7d33597c80984797e2f1bcb6c02194e6db6b90fbf56174da03a35be6eb2cd28b1f24526501aa6bde1bf4ed6654ca03ba6867fc95a96c37b09649ae67ef19f7c6109f4558c1263696d79be54022d9392b6a095e64a241d4a203b891c7b67ff49a05c23e9bf359369f5950462e5feb7d3e22ec95c24509e2eb9c76fc7e41b030994d57b1292f4c5454a3974dc383a80f10894fa1ad9d8af21eee40d393580e10864de942d84bfd3fc19c1c13a76476a04ea7526de86dc59ea545e00831168f953e16d3188234b05aee3868e2e4c2980201b1798808d18c05804da3fa94493f14abfe59703188a40cee9d36125d6b15c782103188940a630b9a44aaf98a949850006229071ea91b73ffc87ea7a3c78dc283d1e1a38e83bb2021fb89100188740daaea9fd89eae4a4350432cea68f9abd3e2dfd4220538e84131be295d00d06215016f463da644bb16a0f02113e75ca9e4d5d96dd08c2ec0fafb2950702311ff7738410530c0b0302993d7c8ceb595cd43a7f40b97f2917f5682a5f8923abca35c000adc0076ea800861f5016c35a56d3a50f88eb8b49f5f2578cad78f0706605a0c0357014061f505be7593e87b977a95cc2d803b2344c4f27b75f7609479641187a40b5f7dc464c4933f69207a4cea658bbde38011e88be5bfa5b217fda70642156e003375200e30ee8922f55263dce9ace175c6c0a8ab75d053e70e360d801113cc94d2c539e2ed57540991c939d62caa6c44d0774109518537268f68acd01b97b216daaf18b1d437064fdfa0458d0829dc0251160c8019d746dbeb8d010e3270e488b1ec28a697a6798e080ba49966a3be98dfd0be30db7058b266c52cc8eea281d80e186ff4c68670bd538298e2c16a41ba47487f7c86160b40169a6f7acc3471c591f1f1f1f3b78d481010c369c77b38566f40893d2c3058c359875e2c70c5e39fae180a186426204cb66a171cd1b3abaf8f8d8620217c8c0052660e3c3861636ac34043e3e3e6c5c6002368ad9d1c30b017802461ad8be9f70e2a79acf373b7a78c130d080929b5dfc374676ad01acc3023a1af0f1d1031867806186fa805106947e05ab924fb21d72a38b1e3a7240c0ec0130c8800c133ecc4ef03415535504c6185069dcb34a9acf92f9c580def38931b5924e621b08c00803b29494bfb695f98a7930a08405993c69fb023abf861f7fb1bd80b85ce9d405b4ccf9c509b93d666fb980c8e961cbb3dacec4b305644c2aa5df6c8f6a26b5808effd96a3167df98c42c20b3bd6e5b3ef956dd6001dd376bf2fe62574068580f318aa678b29539008615d8ebf238e13e36436c0430aa80ee3435a76c63e69cf45428abead2bfec84cfdf803105d484b05d13ef22e57f524095256f0dc18f023a694eba2e4d856fad4001757a29ae645f87af0ac613504a043149b93d7bfeda31184e40076b3fd3ff394a07184d408e65c8a49cd5165fc791558ae81c603001a573836a7fa654276f1c5959012e1a023a2c900130038c25a0c235aa768af2fa2573018612d01e3f5efcd2513db3af06184940f85dc4361dfd3b7b55678081047438fd522d7e17a75f0f1d9e822320e532a66c9ecc7466ee05184640a5289e323d57a5b5388f1e3ad61c461190a1edd9e2dba64c2720804184e25a534a8e173778e4382cd8e15f60400b15308680d688737e2dab7bea701e853780210454c79698b2a74a9e623abac082809340870e0b7c7c8c02461090a3395fc22e9b4692e0c8223b1601030888dc2f79c6d3a518f2b8e126d81e66a607183f40674d2aa914b66749c730c0f001c243d3b685102b8974e3c8eab4b185034ac1e801c2c2ae7b9ed2e973d40fc0e001e22ee9ca39760447168f2ebcb8d1636fdcf01e5de8285b183b40fd5c885ed9213a40fc5cfe1c26a58fa67900460e9029690c41733e592956bcc861021e384067718d9ddb4d1a728c238b541a0760dc00ddb1453ee538955fdc2fc0b00152a2a6c66a4ea7c2758c1560d40091e3e5c69952f7954d36b698808d52051834408b8e5a4a9e3a859acc861636bc053934e024d80b7c7c10833103940a2f154fd578b21803430608492104891eef26858523eb060b76f420554c0459c4023d339b74498f25e25ecae690052c101a49a933612d67ebbf0279396277d0c9c9b27357a0928e2ba62e6557d0b91588bca655f753b3b99758819c781d4d636c6ba69c55203ca88d8afc563099aa02199e753345d369abb154a094ceb5a4fe93336f8e0ad4e945eab76e4fbd9c5320d384bf8b513357524953203c3e5af0f78e4176530a27eec69cc57d27057253277bbe09c936dba34044bfec97356a7afc92287a9dd41e633b6e663c90452896a4b1dc5ceb07056a4e47f333252c73ba3e814c976c73012964e109d46d5cfcece979984b2710a3f596c23293f06e7829dff1050f1dc83881da5c39f926fd4d94ffae2e27cbb22690a2ba15c2a91b0b72c291e545d5156a208b4c2072b43b4b61a526b20547561926d0b93f9dee6a0e9e56d5b2b185033e3ebc3f3e0a0fb2b80422c99b961d8f1576161c5956669640eba5e79c0c53bf4b4d0d595402a919c765249d0c5b99551694405877774cbee949a05653b2456dafaad8d9d8c201cd458e2f7aacc06f38210b49a063941df3bdb24999e1c8e2714890e37574a1818f8f1dcee3e3231208cb199ab26d317f0909e45e8e1bf7ce9dc1d347a0db9408975639768875e508844afca446b35647f21a819ccbdb992ed6299d7238b25a478eafb2b1850314d03a7234e0e323055661c88211bdb87c9c4eae2a713164b108b3ed926f8a29b89e6e64a108448e7fdfcaf2d87555168940a524ada1e971adec83238b878e2e7270c1c5def01d5dbca7635c70a1808f0f1e26e8a20b1ca52a0b44605a4f4f896439c6c373f4c86166630b07dceef8411687404778ad5c92fed3b76e0864aeae1c4f439256440b819274f136f2e6a7585108591002659272b694be35b69e8340a470e9bff7e2886e44095908027d3fdb73fe9793d31308a459c924cf97eb639980405bd29a5be645e3e73b59fc0179bfa2e23171dd641647160a9c871fd01b6165af6ec491e5c59ee046ebf00c7a4055b76549d999c286490b33f28048395d47cc1a25c69cc49155d53abcc70a50e30171eab36234b945b5f90ee88d21e45c93c9b493ac94197640472a5d3267d2754079bbc9202156d8d22d1dd0219b935c4d1993e59403851973409a4631a5ecfe7a53490ea8709a26dc6d2e0e2815aba5c22eee8eaf7040c7bd6ceff024343f29cc7803ea5f7435bd987fde1c37a0930c33b594c92466a40d283d135284a4f9a5116703d2d2f6778ae829d7bb6b702c99fa93263935202d9e7c6fd2a923b8694057e6d918b3b3684065a553b3395b3635e133a02f7e8ab19e6a35346306849f6c11cf9895520674d86c72d35ebc4ba233c8800caba497a66e273c3d06e4a98ae6497cdab70c8b0195d36b5c4e586c4cba196140566daa9cb13d6ac86d610618d09d738dc96fd2f963fa02ea2cd2693a8d79a1ce3927f5aa10638a9a636674019d3f4f34cd7129f286142ccce0024a72ce31579692398e5b40f75ffe85b59da49ed102523cb8cd57f29c9917999105c4fd77aa495a1b75251be8b15c34e0e34387f7e8b15c7c0d61061650d1e3d7f6c945f2135f01654ac98f263bb6027ae35ed8fc5878db5215d01f4de8c7b814afef5201693a6c4bfa94f72cdb2920bf32464d4a5d92458f14ce49b7eae77c270ac854495fd7a60ab2a64201292ea7d3c77bbb48ea13907be7a7be2a57c7883a01799f76e54e0631f16613d09b4763a55d2ae539c98493ceb039efc55c02b2cd35e54a8c9e3d470948fb4e79794ee46cb92420b44ddc6e8e9ca35e1009c812539e35e5e5d1fd08a8abc8ba6a7226c79311907d49965c27d539e61501391337c653aaad734e04e499083957d7dce48680ccb7e5692699764d12022267abf0d13559693408a8947ac727474c162406088813b3e9b2484a2ed1f203b4be96997a4f8ca7361fa04ff9b929cd91f0e1ea0132879847e3f39a8974f30065252ce74e3294c18c1da04ee6f45e39c9d89e4d33744063460e906aab79d2f4d377d33b9881037407c93627331bc7a35398710384efa9fb8458c1e427bd30c306286deb770dd0d16b64bf2b6bd2ba601866d000113f3492d6ad0c72beff6ae06ac60c50e19389a4e2115be2a40b1e3abae02ab453810fdc88c00c19a09277aa24bb6eaae904001ec888052aa77ce3ea51b52f2e326081f0b06be7293b5d67505f819298add2f6dcccf787235d81faa4f24d8c5b3dff093c5520a315a834ea26ff9244b74b5981d2f6f45993e79376a18c5520667deb63b3e7b826f390a10ae4ceaf978e62e3dd978c54a04a9efeae4c8d713f45062a50fff9b66f62fa93c6659c027995e7849bbcb866b20c53a0c4632be630ee11f267050119a540ac65cf089bdd2dda09870c5220e38ebea99874df9ae0c8bac105177b032bf0811b2b90318aad3278fccc532b1f8eac52517817cb646e7d59e63214c8ef0f63597a2da0a88f4fe81692a7b60d61d67a2f6478e29c44bc7edff2451c49aa13e8b8a3a9d3556eda94e1043262badb86bc15c563646c02e579415e2fcb8584980c4d2044e6e5d257345f55252313c8521e26aacdbbca8709846aca57e79a2f81d4d636d54e5b16ac23c312c8781f3c5e42fc2cf1e1838c4aa06b4dce7b9ec89e69c9410625507f991af9d47bf48b962c838c49a0eae6b654fb5cabc76d902109e486c9245b2e5d78d4311248b7606f4ae392f65f2f0312c8583a558c21216b654e08643c821893dae75db754b03b6ef0a10c47a054148b6ea62ca674223890d10874962456574c3466530b5304198c4057752a194e06f30d9a1ece83c78e1c7b01e7c1c37bf80efef8280100828c45a0cd3587d3d1359fc4d01481d239ad7c4c8940a4cd0b2623e5d814642002f1733289bc96fdb9f22305198740ac5a527172da9b2a690894f8705549b3d529936a21901e7269cd2625492edd08812a19d7914be707818e65daea17743b3f14044a24c9d94a6afda55a17640402953cc8e7df6fa76ad208640002196c3425eb8a694f671d90f107747df2d212cd9caf1b250732fc80f86cb5ca374fea537603197d406ea96ec9ce0a47d68d2eda5e20830f688939df098dafe1376c848c3d90af4f2cb237ba686d820c3d2026c449122d2338b2ae6c6ce1000b002364e4019164968bd869c6bc3e87d82964e0011d82d5afa59c5263b22040c61d50e3514ff559ace592ed80d23966d4d89e378f95ecfc41461dd09642cc345e9a92522e1d50be6677326f081fdbcd01a5d4ff06ff170df92332e4803c9da277cea3b7958c38a03c8ce48f9d381cd0bb3121960c13c29ff80de8ffb5569b9442939f64b801bda683ac5734cf534b1bd023497e47bf2df56f1b5b4cc0467542061b909a4d59c448b97e6eb7a1c5056c6c31011b1f841432d680f83897826de709f93765011b5b4ce04cb1840c35203c73a3b9c41cd3db3a9c87d1a275707180909106d4f98eada61c31f3bf0f32d080aaf0e936be3338f2468f1cdc0517ce818fca92710694b88eec3baed164cecc802e8d95977d162e2595012da68286cb69c4e63bf7061964404f4e294ea2258d7953032470c680365d5d91abc259810fdcc821430ca8cffc7b9776db3acc38b2d0c61613e8f16125071961407f7a52d29448fae5636c6861430b2dc0807cadb0b699ff2fa0e353de8a181e9ebae91a647801f16b225fe5c8a75fb26790d10584a7fc31b67ac5dd8f96410617507e65b1927826630bc8d87fb93a35490ba8ff34319b9d932378594029a13326f67b26e49830c8c00222c737792979c8ba76aa4a06645c0195964e59c5553853922dc8b002f252724c2ac7ada675645401755aaa4de4d47362f238b2cc8bddd1012aa06c3dde9ffab68ee61450254d5f930ab732418614d096b56d9a718d025a62baae7c21443f115e820c28a093dee8b2915b376df304b4c76cc63ab5cd141727a064a89c5c9d523ae7ca26a0c5a2c5bfba3f2572ca04549e8a65b504a457690996a72c240b2b015971dbda7a21c5f6781250218a8b256ded0acb05410612506b5fd1c73497868b1c0191b52b35dedd9e48d11fc830026a55577752c5dbfd241fc8280222a91c4f7f3aad3cb9870a328880ca51757b2d46fdcaa716640c0121aae99447cc132b72706459d12f6ca4fbe20219d0428b0b4cc09812640801ad396e248497d1ae8e7f81821c3a3680a3478e0e7c7c10414610ced51a7c43e385041940289af030fa292589b9f202193ff8828040860fd0c1be54d44b7152bc28a640460f907163ac491b35e4f9ca17a6870c1e20f72ce9d648da476581d90e193b409d8839753c7756e74a0c64e8002d7741b45c5c1939408633d794bb835db0fb4617ad3a64e000f11edd54a752fbf972fc6f9423818c1ba0fcee6a93ac1c8eac03830c1b20f3c638a72e6397175df4e061baa8c01764d400a56c24cd5b5eff9d8f23cb7810f3820c1a20239fa64a1f4d27d5300974e4701d838c192043f4681feb65b3dc8c3b7a78214306b1405a89ad58992e316081889d6712dde4aec7e48d18afe0634eaf65530c5714332d4689113442ea2f2fcff490631a05888bcdd1c38b13f088d10a647a9fbbd65d1d5587053e3e7474c10a64777417c96926ab196315a8e41d274d4db7236938b2aa14e045173b18f0f1c18518aa402431f5fe1766c2fc920ac4a4ab39cfa55ef5d4a8406d06fd48b182a740cbf776aca42fc4ece489420c53a072c464b8d790539e7be4e0912305c7c6160ee89183470e0edcb8f1f1f1f1f1f1714301308918a5406b652f1162a5470a845dc8fa9e8257921d4b428c51a062c60a1a92c74cb52c87175b620821862850763f1ed6e4c69175f5d1e3e3e38387162cd041821c1af8f8f81c3b9c0314e0c1c377747f7cecf05ec7f1f1c13990035cb0f70d2f78608c1bc408052275d4b1b8fa932fa238b26e14e371036b05e8e32306281027ffae35f64d48d03e814e9df34e0939eff8e609744bbe34319367f5a29d40954d7853d26de504229c1261bd72d24d21ba893de6fb206f252f8e236b478e1b550982189a4085b71cf70e2de9b220dd30d3b1818f0f29c4c8043a557bd6f38c9a2ae770646a053e7083053130814c3afe2d7c685ad0ec8d1e4e82ac1eee80730994d2d32d16f3f7e8e96358021dc5d2e95f4bc90f6254029553a50b726b31d2cda6891894389bf078271f53cee9fc438c4920536dff8b97bc53393e154312a85415b2f79952f39aa122462410d16bb5cd345acec1189040e4cf92316f2e7157bdb1418c47a0c4cfaba87fe5f10a3a02a19b1192e918d388c497f218175cb060470f4620c28964e7bbd14f698773c1c50921c6225c5953193b46d392b422349df594493d9949870e3112814ef3923fbe1662892822d049ffc54c73b3fc91ea418c432034964ea752ce1b376714886108b4c5bacaf03863a77e08dc7007c428043267af95beac4b72726e08310881b4f06173cc51a344883108d48759feb6447b4d991574b1808f8ff6157491880a02dd9dc54f935ed7754f8c40f4751bf2d5cde9a738b26e470f2f76c400c4d6a6cb2af87c2a318e2c2eb8d81b1f3db8c8f137bab7478eddb10509402862fca15275137e1e673941173bfee3a376f4f0821331fc90c797cf9fe4cca9a70f4cdc9c5939c44a11c387a4a5555952724227bf877e7dbaf467ee6dbfa2106a0c02599559a2464d4a5e5e10a8144e4f9bce9a43d206026d62fbb46f3e6dd97be858129c05a10620909ab24cd8d9afce1f72a387f3f01d7f409fcef514c76d2b7f6b7e40c55211646c4b7c76c579f401b93aafde9bd562483e311ed4e003b2522b5ee9d39c1dd4d803aae34ccafeb59da0278e2c3da033a648496c4cdb58ea3ca094e7e49eb18ba8298c0774d5c6eb98fbdd2aaf3aa871074430ffd37da17f323ba036e94b2addede36b0e0735ea80aaeefb2bc99e747a4607b4b65dd0fc3c7e5935660ea8fbd5ae90f349d369e32107c4784ed6dee39ba62d8eac38a082feef897998cc2af11e3970a8e0460f27011c9049f96a8c64d39cba546a50e30dc8895e2186aa7ef48f1bd0f3394ee6e69ced726b03cab47e86286362ced246831a6c40960a4f299f5e2c211b1c59440635d680ced86d9d16c2c64fb91ab67a31b55b2955230df58106545c7c92a5c73b37ed6740e549eaf405cd26939a0a0b76c78ebd13d430033272fe34f58c2189490e92a046194a0d32a8b1217aebb695923135c680fe1c7e34f462524a4f8ba2850a35c4800a9e62d2a723a583056d638b09d8580003da79bdd0000b5c0259881a6140ef9a68483959e53f32861a60e0ea427c7bbd72fb71a8f105648e9a25cd16c73f7b35bcc0034750a30b8838baddb1cb643edc1a5c40794fec9f74fff3133510a8b10544eecef6bb571e7c6d2da0b7638da6dcf1ad634c1610a754f5728ea91a5840294d2f9d4b1b1c593bbaf81b5e8ac70e7f1e3a4ec01ec8fa1a57406eca2415349e6f8cfb2b066a5801f9639253edc33da594a9420d2aa02cceb6eb2355630ae85d1f1fb58d18f38638d299078f8f0f2d74e4581c5a7c7c981bc01c6a4801691543d221ff8b23ab542b6a44016597d27555fcfba5a58b030054d480427d60066a3c017549983815c2a91c33ea04e46b66cfec8e249a521c593b76f849408d26a0329876abe50b47de40eb1a4c40c4fd8d7ea6966fb7bc408d2520b2bff8c9603b72f3710d25d447096a24010036a88104b44ffaba4e62aaf11c2704358e800e5b53f2ae6416d1d64ff0058ff4050fe3811a4640d84799f5320f47d60d16ece8611ca8510474b8e097cd22d5e65772ecf02f5070a38b1e444026d14a7db2df8e2e70b08e2d6a0c01dd96933abd276252325c430828a975c9d552db3a97f24c478d20d400c23185d1fb30a5498538b2cc870d2d6c6861830236b4b061638b09b0803f3e6e9042428d1fa03d6431291731837b93a8e103c48e26fb1ad39fb36937b8e0e28b5f40e6046af4007df561b5cbe254ac8e23ebc619568f1578f9400d1e6c9b32e94faaa0d7d8017aeec35aea4d2692c33a40a596982c44db39cd598f1a3940f82761e1d9eec922a4d004357080f8147a97318c86cf7c7771023c438d1ba032db27b33deb9843d606bb6d9d8e9de87aca8e1e3a503118d4a8016a3762079349cff46587e35081174b8122d5c3793809742c0e3e590068430d1a20d6c3edd3f4db77d671641d0ad4980132a4da981f66afbb94400d19a02cba5f5bf976a33880462cd09a94b8daedaea463c29155a590062c50627bebbda2a76849ff0a645e9333f53ca6db7657b4ba7f26e3a5bd15a80de29f52d57a4cf5b3a20d4f36595288af026532e5147356b5f43bab02194e89eb0a935f1ec554203ce791b33f331d521815481b7dd5f1f2f3a4740ac4686b4aeaa45c5f4a3205d22ba364b5f096023d3f2b6612d336c94a0a64a4ca18b14c66a6e819052a9d368927e2e9d54c8a026522b7afa630b13ef45020737d7b271741814849ad5dfef8dbc73f818eb13cbbc3be5fdea82710c137a4ae0b1b962bd309c45adc7dec49c1c24f1a9c40f8a46c9aaabded2466132825b3274dd76e17214613a8daf426277defc55b3281f6cf31d7bbbafcad6002a5369ace39c4ca2590417d52ed46fabcad2550327ce3336deaac6a9540a776959f589dd920a1410964cd6dae6821bda4330941631228af8f94191ec1349e342481b21c6f2f4decba12ba0974a0166844021963898ef8a6c6dcb1b2020d4820f5beb6b2a9950da9baf10874bc9dc69ef4317acc8d951f683802b5fda65a3dcb89f745821cafa30bb6b1c5041c41a311689fbda01549779cca4ec01e7052250586011a8c4068574c9525c365fe8d23abadcc3e406311e8bbee1ec96a324aaa7c7c7c7c98da020d45a03ac6942537aa468f2d0f341281fa6e8fcbe36327a2e1810622d0317b9478325adc9c7d12681ce29c2f5e851bcf39378a23cbcc109b6d98f18a16023d3a1ff34cb08f7a624d810621680c02a949e552afb696fa8817a59f4043108fbea9dccf7296c391e53bbcb8d1455f1193018d40a0cbb2e4bc283124c8c10310e8bd1ecfa0b61e93ce193ad0f803423fd6867ef0383da639d0f003da62a68c77a7a6b3c371a0d107a4e5d6b9a6d3164c6e2b1ec507444e93f47c65f6ecc9111a7b40fe7b08c154f6e0e89143057a40a90693b7133bbaecc9bb4a1568e40191372517ad8c9c72d859906e10b3b185036c20bfe1058f05d8d0c2c603d0c7478f15b851020d3c58d2a3c72c253e57a7ca1d36914374533c953f65061a7640bfc6fee50dfb3dd1eb80de19dbf99434c4f0361d10d7794d65d133a9493c07552cc74b5e7f1ac3729640430e48ef207f771ff9628a7140c8990c7973f75c061f0e88609ae3e51062de8096d85e727d7346cf796ac3050d3754c993c7b468515dac8458566b75b71f4756d680461b90a754c85cd6342f2dd160034a9588f9f9d4650d488fa16e39a59b94f90e68a801996c53c6a4b9444f511a6940bafa273579c742cca28106f467c7cdacbb978867407545cd9a6453ae609b0111432bacaf74ae0e7f1910f61673b7458227cf93011d2d8a958e6ce5cdfb185061f974300bc9272d2d06f4b6a7d7142b6973138701f111c442529f03064492a5fe452e6989cff90252728af3339f175032454f951745eeae2e2053e7754ff10fd772015d3259eed790dc0232a4289ff5a61650723df9a4ecc9fef62ca0625dfa75d8a4944c6201b9d1547998ad08298257407c881d1ea7f44fd60ae8f2fc1cc6749384d45501fdfe316575b3983c55a880d2924d51ebb094b236055457e6e06b49fe5e5a0ae8cb54922b3529d55ed18802d2fcd44fceeafc4442030a881893da60a3a72720444f8fbcbda756540f0d27a0dd2e6a2e4b5133cb75e3b0804613d02a63b1539e4d1792a7c10454aacec9ea94b73bd0c7c7ee701e3496802aef91141f924ac75441175a7c7ca8a08b1d3b0a053494800a1a1f9235c64ee93f756824019d4ae5f6c679e4a43512d0e936d76bfb36dbb54740da697dbd4cf14d85182320cfc7632c2dff9bc3a50868cb1b73e6935bff554a0454af6f58ebef954e79d3416308a88e14264bacad9cae0b01f9bd5ad53ea6bb550f0232e5bc9d4c870e04444c932f6f8c8da661fa01324730a5ac74d0b53f7d80d8f0315959d548727b80b8f01ff5481baf293c40cc9e679b97e85a753b408918dbbdcee2b2d5a20364e732d99f3a520a9fa2910344ea1ccd369999a9da7180cceafa9624b53740a63049bbf89beee8ab0d90b5671931c59fa95cd6002537af8565fa1afd900628a5693f4d633703644b8c8b61253e6f4d32d19001cad3587ed50dba1ee558a0b4ab5c54c36949ebc002a52a49f852af1726bc57a0fff593ced9b47212e30af468bcb3cdff8a89592b50235f3193ccba3eb99ac10ab4c994add3e3cd58053293f83eada6412b7e5520e43c73d2e6ec0b4f2a906e25e25fb68f35e14105523f7a9784749bbb32a740c9a8a8b66626397932c00c53a0e5e39fe7984e74248923ab01334a81b4787597179bd13646478eede13b08026690a253251ad35a8c7f810267003b07cafc0b1478df604fc1c9316314a8beff2d21b32810ef2627575636cd6f7e60462890fa7e31c72511e3e7b3033340813efd4f9a3a5e7f8ee32750a1117ff2698fe9699be189d209748a3ce953c8a9cea9c291358313c4125ecab2c45eab8dcdd80462ec74bc531fd32535bd98a1099b9109944cc24492a0167bb1c6913c4ecec004cab2886e0e3b0b714d3832f512332ca1dec9aaafed4a16bf0119b06195861995406a921b6376ec5697650625109b63676677cfaf719511664c02e5c1228953b52633ec92405df565bc10a35d4ce948a0bdc2a4d6182e8464624820ee74ed2ec9fdc8ca8f40849849fe5689b9768f235096826de4dd1c1a37d808c4e6b27cc2a27bc51432021525e5d8212995724bb70874d29434712ec54c9f53045a62e8246d9d56639b4420738c49c9135f4104d2948e6e21e39da71d0215cbb7e7bcc37d47c80c43a04c9e5a87e5330a819890325fd57d8a54a5198440aabcb66c24539e541f04fae55637d35541a04bbd9c349dca40a0f3add726a939438e9026cc00c441c28c3fa02546d2dc9bf2b8757a7e40f547b09c4f7d8ef1b3573bba38017bc0861627600ff00a70461f50b6c9629f757c061f906ef9be4c24bf71cb8c3da06f4dc754f9d5b5d2fac28c8d9da8646f45135924108742c150300804040e38770083130000001018120663b16040a2cbfb0614000551382a4c322a1e282018128904828128140c8342a12020180683c2c040181418c68c5094b51d00bf132b7ce05f34a0426f54571689a2b8c842eec6f1d3ac57be4bc2d2a47501787e8107ec18548b281b07f9c44f9688c9039bb4c48e219130cdac0863c1708b17b4fe6a21ced66262fc91ef470e25841ca2f45fabf440e6bc2c007bff6122ab6eb2dc3889471e3a88616b29f6301ed9daea51860fa26a312c0f017d7dc320848773b2aa6a08d54212b90bd1704dd45749f885a1087f8eb3f99c58c85102ade307af6d4595744c31a2eb150ab27697355faa0da198de9d225d3ea1d8419f9feb06e7d7102d3f95480c8359b199592712bdcc76203bb17f30323de7f21f5bda741190d8eb3d4aa0c41742d3308f20432c1ea03344d43df09dfabaccf4cbce169a44bd63409146b674e4b7ea24f0ee610f87fcd37f871badcb430d26c485d235b2a93b0df2b061c754f01979858b8002dae8a5eec8f045244f8d0eadf01ccd1875b4c3b60488f99ab2c596600a6dc7e541d5647b876f1032ff9bcf0c84b5f437719b32ecb5a192378f06f286d71d4c7d188c6e4ec676c5a95b57c83b89f4aca990d3f9ccd1854e87ea0d38ab1ee6ce0f24e90d114eaee30e8e5b8f1291768e36d846535cdaa3bebfdb195ba8899bf6d3819254a1c96d15562f791b3170deb43508b08b3ce02c46083678ccb4faaf5d04a12408cd5ea87d58a5933f8159985ce6f21509468c7393a93f9019fe98b2d18f2ad341ba0aef7c31f4452451e5637690ea8db4b9f7f6ead3c4a7b0a76283bcee7bbfe2f81f10ba6094107d1e51a137ef68d0b290ab475315cc7529e56b2116f9cea9ba27b849d11cddc58e010ba43b62cd61b60d42306796e9dc6f1a11c2dd9b5808b91730a6841acbc33149ad3f6e960f8a04d30527c4a012b767acf3345e1028b332f5ee4fb3f46be4d219d49e64358d90dd664c7f8fa00d14f1088752b92bf614a823f7945c8cb401d6d84ea7c16aa43610288d744e5789c9f12a4ece555f516f72e1c7d0aed5a781a666a2124f81e44979f612a4d08c75e1b85d32204a67d6fe6cdf69cb3d0bd8c4eac3679de40252e872f32bf28b856b127eab7416c792183277e58546218d318d552e21c4f303c80cd063e286ecb7cb434aca2dfaa46263d2843f9fdd4610fad54a463737fb54f115643d5cf3739813b31ae4755f9d454577e7123d3916606654a8ad8964def2fa794d1581648712f34c65de047418f274957fd2109fb8289341c70c6a2e485acc12c263e1aae50794167716e62adea1c6c5661453e15258a8f10d213c7019f19eeaa4f59f0f350e09b96dec166244d688ef31e0a09ef89c2185e64b3cb4dbc552a90b075659643daa163ae35a06191c91f39d7da644b109dfdb1d0af654cbb91b4e3974d9dcdfe2d07a2f7d4e131b9def85863121e7a5a2363ea68b0a2b367f93c57cbfe80a4fa82599b62a47884ed738c9ca95f4e1fe9a1f65bef5c4fc29b6b26f558ea267e71240401cb4a55702818ec509166bf20c94b5efd1198ed0b9910e99dd0b72c382a30df1e94e6511e762ebff2955fc28df24cfeda07d40c8c5d84f805316c6ae31211c8cf014ab847a4676f1f72069ec7d8a8e405791f9e2df500844171f8c52cc602dcacc09881aef463eed28f8de1a7f61c9c3806a746d9542f0e8327a4283625df066e74219f67134fa7763dd2442ca9286e0717a57dfbbc59f1afbcded24e4b21e04f1e2b0c5b085f1a3f69b17ba1e336c71f2abc7e3b14ed3990daccaa68685f2b13937887924a138352cc7bc6850afba9e2f7778ce5f3633604c4516c0e01d4bf18fd420e64c1db6abd31da3e90c3530bf9d8fcba2c0a410072b707c9cd34d3e1cc00b05dbc06f891d6f9b31e82cc9b312ebf077ae04198b8c305ad491cfaac362e14361af2b39700f62c86e07d16148ca4677a4327146a5f4159a8c320d72dddf20ed041cdbe84d9f1fb91bd69365099e158d8b3392fa1943f432291a48d5de24ceda13655194361bde0edba2b67478e0566dc830ec0a63a47759370a46e80866dcb653881f4e860a872125c5597886e9a26af66564cf0ce83a50e9db6fa1f90766287b094148844025e17d2eba46493b42ea74a914d28a128d6bca1a7a7e7b77f2a5a0383369f635c6d839085d25e1703b436cbaa28b04939c8b942b229822d72abd45b387a6ae7c69bae3f5765a137752a26fa628ae8637f224e7a847a0957c3e3001c43080a4657e3c042309bf61a3da26729059e767650a12e2d57ec654d3ad8300befb10022d02a26cd9c2271eed93e0052d3314a203c6da6a658135417bf075a2fa1401819c6d5e283292620f86f4b6fe3f76b70ba22bfcdef581a388deb03b41de376a96922410b5f854c996298eca8bc931221c862b901553e2035f43e45c5f290eef88eb4ec1a0343a4ec1ecff664e8a7d884337b08bc01028444982b024bf27269086d40789d1da2832a37b304ac44bfe3d69f2c1e59d8c4fc3ac9a2ff63dfb16c7bae48ea0cc652094433daa3aa87820beab380d54eb04c1871ae8ba3f8f6e446ad19153f4d159f7fd27b93f3b1043d4e96ba368a4f81aecc2ff479ff7171496a24d0678a5f6f6fa976d316ea764b4690d3e74e77dc5bd48b64494b5c6d6c486cf0b43487cdc82f536767a3e9a17846783ec87c21a7ba6149f5d8a2b880f53df77ad4c0595ee080a2bcfd16fb40026b80737786f07e39c58d7157659d1b30a49a81b0de7e98312e721155e33d942d229c367213418f572c2750c5977ad4b05ca18e7a426ddeb6ee1b61e5559d1df5136500ce84908add0152c9689e6c5a69e5030156cb85a4d3dbc580679353f1f65516a904acabd5abb5ab3a76615558ab5450d46c3409a2795f1cd19d1d8de24309b20945a2bfc11d6d19a02a3e9ec0fa02ca68cedc2a07c19053b0eab332c3f601bd27dc2d8a5483c9d0f00aa5d745c89deb4d453567b5ec21671e72fc1863c94104ca0b41a1716acbebc4aee3cb444e45ce7220856d374d0750cd54a2993c32071688554970bfe2d0503079f075b2198b6a0d54c7688221bf001824264a1cde301840feeb776f36bf03ce78983e0a827257a99e99c273f23ae40a1167ddb28aa88f0c9392c17f620c420949c30a25e81510122347019ca3dc58c07280e52006431029abcfc22847a8012fe0fa202161d4cf1dbb08d582f9c306272d4dca270db421ce3b7cdf864e0ecc17b85608c68a8f08d1b70c065337b9336ec906a3cc04e3fe3f00ddc4d6324c8fa023c77b1e1937c1a383293b777c43346407364c391a836ac686bed6c4d02097cd2659a96eaa9c8f12076a8a120822bc126f90e25273f7e57bdbd55f8b32e1976d6046b230a6f7f73374a94f4433e8a100392172c47cacb70391fec3cd41eed074b6eff3bdee91bf944899b092fc8088d19012cde8557880d6de7576aafd64e08aee653eeb08f86c3783ee8fb1788d055e478b7a96637128bc30993d33de75e21e7e5c82a282c3466e05cc323006330aa273214351a2ec5fe347192d85d82e4b928de29f6f33c7e7249bcf9380a278f534afa47d9f454ada942936a763521bd1482723a61afbdb9315ed44aad4d66a46330f736230389c1cfb1422435184512d90ad1c812c290230a910ab53143876dfb868ae1dde0584559b3e47c794d17220b959cccbaa9a861f2c140018e61af5436d750df5599b29bd166b218472f3eb58e8a06a9639006e6d5677501dd6ee3eb79e626f54e93561b6f933f49a901553d85ed74a8d54d92b5ca0dd0c6c29cd3fd8fddcc33a008704c89e9247c20085a4d2d0edc813458bece502e2e5297fab12e096e8dcca7c864ea4aeed1106030b09a62977043e09be11e2c5f6db07dd24af85401080a6ea14bf27c33703dfd956f056a789b46699c340f22350f18823abb77c191110d2b89ea3cd8c605a5646cb5aeb80667455d247f9c3cc308d230af49a0c3725494cad93cb667baf1070d7198e0a1b536c0b7997db7533bfef58b60cef9e26e114665607a33292405bd30b9212c13e2aa5e25385c6975d1c343fe36b79866d081dd8d75211ed88082985483ecb789029bd9a968024cf87a55c2231e8d54892cd743a5350d7c7d045652f6bf1092a17b60722f2d506ec2313f26c747a2ea047187a49bf3ca4128208ea60190edeb8352e579f231b655d3a89c3d1b5615f37fde25f1b963a3d1bbe08de149e47dcfb7112b443b537a02781078fb3cb73c637cb75e6dc24a9c7fbe16660c66f7534a680334e817f6605746b7845a40a5bcec79071cf00665f5f07ee34f26be2cd5d1e1a7b8403c0f0d14836a5fd5630f3cfae6a223dda5fa3b19dc112b6c80789e2b6ae053de377c0256f384a302fc20765be0bb920d585e01c810c508c0110c3ef3b0cc7c17df1a6e9b78f23552ab13dfc363bb62d21a88d345d2fdf85545da143a153e39bd260937c9551847d65960a1a888099aa9cc86d516ea0b7375745a89ff8af73d54e8762940d869ba35ad513c082a819a64a33932727465c5177fbb9b244d7716af1f65ce2d578837a359ff247f14cb3c6dd37847bf432d10e4a8134c1261ed0ae55932a731866d10fe24bc12aa1d46e6793c8de62202601db0c0e403cfc6e081bee3ff679504dd97efe0472d6fb4c02bb3ce587e420f77842986e802d37fb080aec0d147eb6739381cfd88eb640315c586e136ea6db8e5a80f1e89c461012b4043543631b5e22b898b6997f6bbb90efc9d8505b1eff2b1302d7924176f208c84b64caa0acaa6c3d470c56d06c05d314bc1be42d0f69f369308bc5dc2c08b98df491eeae98e35008078b689859c91a1c202b88482d55921c21005f6047026050268ef96e6218b8f4098260c36ca0bf20f0fe654eceaeb67b3c26cfc8e5139315165c14205f551fe93e18b962fca2cf2938f6e08d44ab6a4126cbe63668b5d4354bcdbcc66212b243083befc0811852b39f13a38fd50a09503760992f599f3448db3af2456e1b234d3c5909f3d0e15218cd099ff02ef31e3a2952e5ac3edb8a44601928cc1630bb63c85c5ce6cfd8d0f728b74b3021de0108076f50e8e34be5a14d88dba19a1e284e98a11ba31b3b5a32a8cd97832dba0b21a0acffab1ac077f32225c923e059ebb43a6c6b84b0edfea2cc2739414bfc6eb6e7253f8452a5fa9c968a854f1b99bf0c020a4c84db19931a977fdb96306bc445b255271a0524b9dde13b21e1e8a14cfa5a1a02c18cd6f0f5da50af55dbc760bddcde7edb63af96029b09b8800af35df9a2a257ec6970c187aad23180c5f4db0e6e4cd2ac5a81adecb9470f7869677bd8713557a477081ac7e93f1547412a3843f294ab1a93dd87b2eca2212e5326fcd1714982880274b5d6b41fdda7a060972b84a5b3a000d7e381239d370f263e96ed1aa2f26302f6f013e20b1bd522390ef4b3eb58294b1a99cc10121e840ee06047c3bd1e4b4d9eab9c3d7b10b37ce4d533f343262ab9417c10efc1882f914d7b22c92ac51336a3a637188984fc075430d1e12e0cde6a3556b68f838e550cfada08b31e05f94564c32b4cfa072308561a80e243b04640c13427b2ce47f7a32ecebc5118876eaa60180dd48d9057062318c2946a33cb10d03e963401e53ae4bd257e9010f0e66013e51653ff3559235dcdba28480f458a4ffb25c6a4f5f67b314c323bb2daf2833a8451f6e6315520264a621ec5572a43af9018236e1f559687b48b06c290905d97ec805663eeca972a92f3ccc3cad7eb8c3b2efc0634712974dee92bc8f1c33b98543d184335a82b8b1878634d36c4226d6a6224d77b94011c06638ea886aba22c426992d8fc9a242ae35d686ef661b45a2f7c9ac3ddb6123f18323feb48707fba2b1e48758b6b2d942a78aae6e6a0a6d7e805cd9d9fe6fdfca33d34638acd4973ce5fbb45d6f8799e03f2d29e13e8b213b7dbd579b9f9c16a58ca7ae738dc88b4f577ee68e789bed680a9f5cf70f62d927abd4acc63edbe707e32a7c6dabe4d3851d6b856e85a44fd5f604f1ae0a39be91ccfa859600fe0d4ae35a022cbb6492d443cc52a3c8f7a78d9c5786a61726e63b4718b0178b7ce8751560a8a4dd3f774afb41fba6c99a9c1283095013222da38470e30153ccb6a224e5ed0d9e82eb1f96171040822c1f07d107ffea99d8e437ddcdc8928ddf5e9608a136fc20f54bd6115519ca667d5b574a313e3e39d4e53a7bc35fbb87bc0e5b928f53dde8f4441dc39c5ac49d50ed22719094364231fffe3e621d2347c31a06d6b24488f9584a18468478c3cd473f0a0fe259203171a2e6da26f6871eb26180ba766263fb94e829a3557d41c3c67b501360102f84e2ce451ead8777b2ee77dc579c35bf6711907856f7b9ee748d2e913f26afa2264614867dd35fe08aa8b8158fe6dc06eaed1777fce2374c8de82a8131e8b6a9e8a05d5db9a186f4f6c134b2b42d2b1e43872b304474f17b121826e81b65d7a430343b1ea88159a34fe5c398e7bdddca485d625c9064006e456e9be8a3ce1ccf8fab8823f9ba3748603e88a1a24c76d685805d13e159071edb153d1672b57372ce155cf121ff9a1a4b9d268c9a7dc18506579478b51a4d3023baa0de08a5c9260e91cd44bb42b26d2952ba894886c0b615d96d9fd52ef8dd90ff1556c44812c6a38532a8b15d061f9a9fd0d44389b56b87945587b358f9d489fd41b4e5c1cc10ada30a07c1c48d7df8ca996817a828dc8a42878812f3a74c7bf76a5d68eeb620d3621dbbd2292bbdd449e3659fee953369dc377813c9e5796216d9b43bc421679665e233c5e2f4579fd46a01464af75e4e811c713af649e14c5a4910bafc15b6200502c5018ad1e1b61ded3dcd70968487695d557c1c27b7cd9302d0e2d4261424ee7853f1bd0d2583a8995da5669d6a3682592fe1e9771d5a2eab5ce3eff15e0b537d5604e6e4572cf140e1dc7ef9a6d7d8c7f4c7a3b07d9dd60af0242c10f274aa424d93b2d34681a5457252bf02addfd1f117604ef97c6c0b449ca11e5fd46b7d9d4afc85193f3d389c05056038f4fff8e827d4ac63e4b360764a05291b0ca74d20975aefa00174b595becf4577df19d25e32232c2bf8ca350254b278688738572389a5d641b45d2a59baa04fcbc5c32bf9407e325a921c25a0c74405d389ad0c6a9b5cf36d87708f2239dbae8e35aa05d17274e8fa99825d2c1b9e7ea8070a1e6582ac37c4d689c479b65c21ae50383748d48205720e519b2af27640a8f417d5f1df5e267ce1554e86d0420319b79a9d100f59a7843608efa1584a6fb1c51677002aaf8a3da483449347a37267d0f4328d96107e91a49255f20c4e2d6e147fd2f6a343893f104271726fb6635451db75c0861372a71200726167cc371f32a0a8420b70c0da302709cc7c723ca5efbb9ea5e7d723a26f3b28a6200b18586e05087260099f7f3a529b4e80a9e850b727bf714c87ae292a1773198ed845356e9addcc60fbd5624d92d8a68a89fdd566f2ef6a1c91bb09530aa1cb801c9f5675eb65a21f74aba28ddc5af102c06e09ddf142de4750c6096bf0560441f02528baf252ee295c9f4d6aa9e8bce47e12992084758ccac16a537d8b2881498be5832bc4c1d037f2f86fd88b55d98cdc4552af6797d9832c33f6efe7018e9cb8c5731ef2c77e64441e2ac5fb584f232eca72cb28aa331bbaa44cbe4830d1353793fea4204af59fc5d8485c4ecdfc20e8d8fd4b358fce49d720b05ce21185738e18c6c6aebc90f715e12db03ae7c5b986e744c7babb4315eb4c64f96534163911a6521c8e8deced5265f4644d34ec603290620dc83f2360265a5d1c4afc6005137e3f08cf8582ce23e5eea41198db0a78c50f7f8d1bf771d0ea82d4f3af21ea283aa7377402f53ea6bf4f50e7669e95dcdb2c6b19bf65569e81850b3a644934cc559999d91a67d74d341db538b9642a89608675b1ef870d1ee2ef20869151e2cd0bebe69c0986b14ddcfbac627aed2c5e83d7754d319858111bc1737a220a664238ae5795651126ab4e7cec161498085621ddbcf0e003d85753086aaf6224bb0a919f4b1133535dd9cc6bbc4d5d6625609aecdae16bec6a4f3a5b80ee586e662a4868f02b540eda76c8ad33c7cbac6899840eec90af4a434636f589d8a20dd064a19f1ba3e7e3c79ccfd5bf790e524037346ca4d5572de28f25a57cdb0a6617d86f63e2d13b9b01210fd9747e299031c996f27e2a20dd1f89aabd7cb5b70c272d5f462e8fbe6a39e316d61cda489222be27b3879ecf5bf9255711aa32bbd6cc9eb1cc34d8612e7a725d26c9236f96eef7dde65dd9655129864f2d265558f97400a5ffc928a8811e8406c8f4df887f114d2bf9c7a5b057b497598730c02532a280ae4c834374ae1cc4d48ef94b47aba6d001c8741e889d853e749f532044f0bc1260ac6bcc5c94449937a4ae7888fc1f8470de0e613b76252b4bb14d25830ba886549ae2d53ef8c37e3013a743e5110a05c4dcf8384152980f0a935ae97823741623785d26b98812e9907dc64b255a5cd7875a3224c86c615c1d65415cff7f07e39435c6b123a4d4ec8e3a65337a3346c94a98eabcd9d70eca17d3b1002bef8c41aa09f106f2112c93720151236d659371ff81c9ebaefa9f897f91e6a4475cfe8cabf9989669436349416d101726e1f325c5cbbb1714a958fd17ded779dc9f7f598124f68410cf78616f1b8b2684f9d62ed56f2b3cf27537ac39d0b52a1c90913049fcd5f24229a1c593c2596bb53dd11264e012bcc870a09076c77e312f3cb433aa06f6335c96631a285e39148cb2c2397397d27ac82416d42d890de67282d1d93be396cae4dc06499f1b52e1bfc8f9c1672c2ab021383948ffb5ccec8d4c23892d31a3b33bee82c8662fd5143bacff6470a06af5d2be82595a9e40e25265222de882d6ad2be48ceb45a99dc14e8c7abcbf5fabda99e92190e835ad4aba814d6f70641da91581a8b529787a1a0886f873a3d86c83f104bdf40939ab4069d520019c89e7927d4c35f36789d94adf4f79f95cb0025d4da63c9dcf9c6b06e356ea8783d06d38f70ff716307c9bac5c3e89ca99ae51362f6fda157c9acb246aedd4a41b255598b67cff52b9e43f5906007a2bb998edf658bb1e0312f4d6cc3aa8ca2f415c2b00b85ab017d01bb7c072d27d097463b91babe9cfd71d74a253d53cb37247a0649f52afe1e2f2f3392df6b010cd5f0a19f06dc9c3d4d2f072d42347aa4755a388059632c7da581d224154d62e1599228944842a650c0204c0f3d1457e0f74a0f9a37260500f06b0fbc56ff83f22c1071418b8dc0fd009fd209dba797c535ce40ceae620b84bedf779e4dbc381438aa1aeec2419d04d7971a2cd7fefa1aa531b6998c702f7d6de5d857950136bba86b1a9ca7e1261d1d90be241d62c4fb72d53a35422c1072615f64a4952024793935892a4122e565bc47f12388724634025c80a3240c6487bc1eb99c59634ce4ea2554e7bc5432a39953e09499de1eabf4c1e265b5330d90f76147983d35626f6ee9fce8d98ca58801fdd35ade330cca3f462ec92a457a04190a1069f9132b15792502c87f48543c99a1834a4a280c8c139dc6be06678111636c3e7c2d3883918e8706c83ce8411b21e8fba9d590e5020f546c583050f894260753cfe2921cff436cc4032bc203e01ff804a71331cc3f624def14905e9f942d969059edba10b3f6fc5c7f8bc28e00d03edcd5648e7caac2ad3282f27733339f2ac4a7062c911f795657477b19c3a5020ace391d97294149931fa91118d6a5aa01f58ebfe663cd1aabe0ab328c3e916f6a43650ee16e49ec9c585fbf831791cc6ac89547887a5e16a149041178d0075c9151c182fb25a3a6c5d2cc2048b3eeb001fec8ca81b3c2e893b62bc8ab8229845ad663bb19b597a09bafdde458a458527d4395399c52e42d53933e06ae09bc3018bdfe1a2e16dfd4ade202d81c508f41c1c94e418af28c00b0773c321c4788e4fbb632821d6514b1240ae54543e6777170be8454b8a073508c61795c9bc37b0e11ab105750c789fd276add44a265453b17187abceea71f4dd0805352145d4370ecdc3643f6bdd67036d79c60241efb411668f07565fc83cac017900017b8f6cd75b0c94b9a84d87bc0ac91892a90a7f98d51b121e7789b53fcc500814853dc66fcf640ed170a8420ced426942ce3a84b1008f69eda7d02d911cd0ff5c58e3fa62f7cb7389f035410ff827307b0732edcbafa54f759fcd91e0fcfb36547a980fe8d92c8fc08f07340ead0835c9b35597e7393b0d00db8d81e4457fa89957959202a0a3c2536d052c0d8d312893e5d9e66053cc5b3f48e4241a8632e27e2dd34b222a0f3a3cb0a7a6ae12edd41ceb387ac48635749be6c447f122a733a04161f50d108e448c712bbbfaf71f4e00bab611a63a980791050df3a2edf49084c30e25bd8eb8c5451323a505e758111250431906aace607c729bf265d6f882136ee90ea4e0d458a5736f878b7e19fc4daf4930e961845a33df455ce2e65495c85e7ed76d1575eb44821f0276d33dfcd643b0d3d9fdbc0be9e1f775b4fee18d3b0b31d4db631b8b4c6f08a85c95768144c5647c16f4b378c691aad75cb96a104403187e46d234b728e2b7a4bf95ca316add8352fe3968aa84046afd56eb9bb1d1b72d0cc0159cf5f888f1a5d1ff4d230a1cfca75021072200f4c7fc74fc8d6b0a088bc3fe715d9b3485c45ae9dc3643ddf523c54ddf3552c2537ff517c876f0efef522dd31835568d6addb73109028fd7b5d152a6fc5ff5c7cb2170805042dbbd77b42bb974e1726d6bef93bdd550d9c18a8cd80bb1aa72c89c368f2ef760bfc90ec0e11003f7329e68aaa682240b03c040624cd73d27221f1d99c6efd9e87d53bf6ffc62c65c38d534c391187c02a0b8334117550028ac47914071264641b8c3ef9ac5a2791e707f25995894afca5eb74d234ed5d74f5e08631b8227555635dbb7882e81f35fd922ae03d67740fe6a158000ccff26ffefeb7bed59f70c9303ac83858e9f825bf3433524b56b0216210e98a0ae46c461abb425b143f18c9524fc35ec28e119a3c026344c61f32b1446c11938e470ca712838aea76ba22e00b8a7d667d37f99d6cd4178205c938027185ed6bb0cb082d83dcabff6f8406ca7b63715138d10bd3658be5e8a2ec942c104fadf54b6a53a82e235256c2825ec5cdb464faae711211103cce080f798c56ecc886530eb9bcaf16c79fe79ebd41d8045f48e949e559802bab50c5642515e27612d08ee510c813f2d9341777aa19ebdbfd8b2c1bbeb07df12d534c4b29155886b41199d853fea3bc2e3c21069656281455cef078dced38e5d6186de53414ec7442017639bc4fb5a70b7fc796aa832889d00039563e3b3421f0b1552bbbb5b082fda583085d6853101c0307fd99d72cb169bcca80675d0eca471088be4be96dfd68ec232349900b026f6cc56ea5dd44732d60663af5e4e7d06954373e1106b1f0d525530db48a94c1c3ded881af44e37f69c05cfd3f95e1cb3db4b7495997fb30c6cfed22500971cdf28962bdc166a72d5e58671f6882577c249c0d85caa563a2f5c43cb599a0ca94e97c4c04db98809854de884d90ccb60da7a36a97254b06de5126bb40d1ed0b64c4b8a6ecae71fbd041bbbe068370db1cb4d6bdc90f668ebd0adce99071be56394ad91cd46f21bd984d14e2a182dc88cd4397aea5474bbf91197ee75c22a8936eaab9a686aa3880c6604612eaec1e9bbb42ee5c9952af57bb0f0accdab0e4813ca4a9391fe9605ac0664c7c4b2505b17194935184cd5427458343e46389761db10b26441e94990756414a632a1c01b194ba48f8989081b3418ce8431fecf1e5f029f40ddcecd961be07a15c1b7aa2dc067accb8b95adcfd12b962e80ac27a58bc87abc9305e9f5044ce1a02811222bf117fc6d486e4d15706001fbfb9ecae58d9a2a55a9ea4486bc17e1a8adfb5e712a46a373e136e945079721b4d0970b88acd4f6113cf9621d7161abb2bd6bb71753c516b9e28943b68c67e34b9896d3927597c54b73989f34bb3ec17bafde4e7ab57bf04980f9f8d74482b6ac90bee5205d3b78d32e46102312f479307f6c363dd3ce5fea7fa1c5ace9e53fe7da51923ae80004dea177405cc8f3c4134a045526d006a1260694f152a57ebf316d6e33b0aecc7cf7372090f3493415dfa97163faf77b3798292e7863d0cfc03ba45e4ff0710d159914897503484a6cbb268dcf6c87326642732057d9bf6974e9af39828fc6bfb17dfc67253339091ed698e1461250cb5a4b1d1bdb42c944bece523e4ba510f4f91ed75b1052efb268252da596702498ef3dffb24024df1cdab9e92ff0fc5e0384ca0c2cf06487cf38e82624ebe86e12630822042b42e094be3f039bcb93282e33bd04580dd40c617c51f4691ddfd6f62db6d3d8bb0b4d14fd95d42aff1456840f1650e020c05f66b29c9112822c1b8fcac3ac40128b9aed7f2a86b7529e1ce2008a1bf1ab95879f0826a03d51b98b54584da6d57a1e0d9da851afaaaf7159e9e02c592650627415ce5b2ef2aac16e1a5cef310a6fe2542763503293814f565277a18f05958c12cec3b88f2b44241cc755a38c5d1ad91b457456103f43a420fdfd6164ecd73cae71778a9d7a8a19932e8d7c9196fe00ae6b1350fa9be681ba341ea5172f0699d98f437b2106c01c009c1bc02068393ca3d5736107be2c142c4316bf3fa84d16f1e1600b8699bc478476ccc448f11196a54fc49e56ec2bfa36afe54e078a405816f50e5217bf02ba9080ab8a80115c1a687b3520f0abefc3d825aa172bc3563c9c40234c9722331cec016598d88a80343a218b9c6bac75cd8419d1699a90adf0781ef206fb5d9e7c6c112d656cc17f08dd11ae3bfbeb061970033a6092c425f65d2350b1607b5082a7e819c36302ea403b892613e32726d152bc4cbadd586cfb55565fd3914d74b32265c2fc05989fd21d9d95cb7699d0363157e5cf2e0e45387c841c116a9e50e1d44ce7d9c6c670bf5b4ee586c9c4a5a3205ba7800f7250d16c49ad37574655673c367765c24e6ad1202a177bb2413358c45b13ef035b2318ba5d126e31743add5be42d8c7eaf8abaaef968f5cae9fc1124265b0ca402c98689b95bccce3e8f1a4890072463f6569bb3083dccbdba5ab80610b98aaef0c6221cbde85d308e2a77ddbbc3b8e3545949972bbbb3fe117d2ca3f46f5349ecfde94296d47439748063a46b9a71c35480f5ea0dcc2344111fcb148c054fea050f55f5722766f00be53a2953a8cc742ddf73ac1811a194c5cde5fe0f76f1f7addccfb2e4ace2349691b563cbe61e8d4163aa4d41a66be6ff4aaab77a5c4f5bcd73391cbe219c85d8b0f23c3e363d8c3473f3cc7abc2e157992e37fa45a0be9061606b25d7322b32cb0670be8d50bdc84e19e5b502f36783b86d4938591f4480137bd50ddc662ea6e6ce310b4009d654e2f16ce996a2ccde414eac12d523aecc3f3394381c6c9059a49f2ec179bd87f3a7f1e8423b64f24436a1417352af167f9b97de9c2cc8a5d48c7cde029017522634a3b5619f8e6d68ce14e54bd1e312a939d9db554ee2dbb20c35ac199b252d595fc881e1eb7ee2ca44558d01f6f2a752e45ff28d848f86ab38b08f482722bcffa39c57b170ad4a7ebf01137a50b05f64db380cd98cecfcf6450995e16e60e15495f2f8b2b771639a56622636729422500ac2910a60eefafb983d8f175d9bc9267c1293b432f2697e0443b49cfc8e5e0a24d89f4901b05a28ae4aa3c9c617bad9f460f78833371de5d482ee999c7617b65d2a9ea73ecf8d25aa9f565fbeb587e6f1f26f760568a3ce78e782b13f648323d1077dd39725e2f1cd60e35a20329a8fced22b872d6ccf818856b8520faa7421d748b5428384a7655a162eca1923bf387959b72264fbf8b6a256499e1ec8604c421022ad3d79c3760b024850330333333333333333333d3a4fc35fec7f3c71692d99ff1c5c1bb9f1cac88888848005ee476c19ae3d6da667fefd199e98112e8097209d609f97b9c1059b750c879649a369be617096ba16c223b3331a48ab01b17592807f9f1ff9c3daad38385e2b986492dbbe321e6e20ae59ffdb752fd9025706185c2b677e656c718fe6918b8a8425962c8c4b0dfe38896d924704185c284c8b66fa866ea7d0ac512eb48f6ff1d810b2914369826f33cfd70edb888c2a15e2231b2a89845d8e84182bc052ea0500831c718cec71bb1992794d3c4493ffa8f20a31f2794cde336e6668f4398840a5c34a16cb7eaa13121b4e70c130ac16a4e3ef3d825147f38fda7e2292970a18462fc983b8af43853626d4ce02209e5f17046436efd78509ef3810b2414dc3663e6d16a8fa37b8442cc1fa8e67cf1a1996984c265083144d63b587a7894072e8a5068eb4e8fc910d23c7dc13fc4b07a03174428797d4c9387bda1f7f5108aa9ff238f780e9e374608e55ce2bb9a210942bd9ac2c749824028fd48353dc71cd9553f55e0e207c59d601f5c3a1f94f378902788a598acc47a50d6ce318f07d1a61fc7e28207a595fbe19ae7ab559d90055ceca09cd1a31e77f03c9c091d1d147a90799be19307f9ba15709183a2480a9f687bd71ea41a82325e023fe00207c58fee9b99affc36b45cdca01cd579360797d4639f0b1b947cf429936f7c6a510ed59d3ba7881b99495a1427f578d33f0795f56166511ee7ac13e298b2287a876f1ee6ce743a9158948761beeee3e2f7d30b8b72acff389d91123cff8a426c09f971e5e6379eae28cef6c8c3e9795a517609afe17b7b56943d2a3afd9e4d1e8fee042480061cab288ff3a0f762d2ac8af228333ff32277482ea7a2e479983ba5c6fd248da2a21c5a5f9696b9c3f6e714059ffa93ef2cee6deb83418607b00c0e5314b38f27e4f60e79248f2e453144f7181e99a3de4d3230022fc4e02045f1a22c4553d7511423b8cd78321f5f6c89a250a5e22d21af9915798e5014624efee7f7c380a2b89bcdc7b1a24defee4f145222d6aad6639051c60afeb117f8c7181920f344297c3d8dbdc7ec2de117807148274af3a31e4d0c4947dc5d4e94ccfbe6533bce8fca2e38364196f8e1a649f37898268a1dc66bc255e4817e241345f5926893927a235c148cf1646c6d8003135b5ee730651bb3f1825ca2b09e3d44f230f7d00796018606382c71558ee6c83e38ff185289a38afb8460be617eb145899249b80f5299e6610ed2171c93503f7b7d3e4412a6ca5035fbb1e6712a12659d9b0d1a33366f642051dee81026fbb8acf4258f286d74b055edb8234a35417fe0c39ca46a44d1d3ea3a24598d99794614a427f3a70d9f63cc0317511ea48ef4c39c7ea9794594836fd5d746fb688e3011c53ca731d487b9b8f688287df8ae9afa4cb35189e18231f21025f5a83e082eebbe799c21ca2943d9894c94ff750a51da1497ccbfd1c7f113421482865d4f25d1071d268328854df9f8b3cad90f544114cfd47e6a3e6dfa78980747208ab6333e360f32197344c6185ebc1860a0400c301010e5f03a216378f55a862f021c7f28efa7b6e65af9780ae3aa031c7e20c49d92ec9a30be0c30480638fa50e8db90c7193f0ed5a9f3c5961763946175c618838c0d746115e0e0432942b727fd309f0f7e680938f650f4f1b60f4a35460fe571a7b30f1bb32de2e71c79284cce79cf4f3efdac5facc07c41020e3c94c7b3ae7ce7c16fea71fb7538f5341f45c61868060332ca10e3b339ec50befeee718f3cf4307ab60e2509ff6175c8413a14dc5f5b6c727e0e8599f5dc0ef9c8a110dc471fff1b8943f1f4b7433a4bcdf6180ec57cc90371cfbb3dceb13794726c7a987f1827d2578280c30da5bc32311fdb7b68ce1c6dd001071bc6e058c31bb28cc7e73cd4bb484002f0041c6a284c9cdcd5b641e3fbd83414a737dad5eb624f2468a8046881e30cc5489949b284768d6639cc504c6b59a5517f242e1a407094a164ae21c17b985d6c2abe317090e1e6c03186d20f7b2492076319a173a88ec02186b27964efdcd66e0a93081981230c25df1ffff0c7be1f635415c00186729cd4f77bebec85f985722e79f12c3954c73cbc50d6f918d343a93d09234717f88c2145d632eb743317f0f15862ccf5d837fb5bd87f70aff629fac36fc5a1053f5e6768989b68f6025c24061c59286448e4cf1a63cca23ebcc08185c25f477ee6f1d0667a9c2b147b4473f69047ad11bd158a29f94cad63ecf430c3518572e9d89e4a4c3dfafe411ec041856244df6ce57b841d99630aa510eb9c90afe734c65e141a38a450cc231f4bb6f677981847a160ab397f1e7e64dc1539a050dc7011f9f1af6acea316e07842b1c7e32cbab63eaebeff8b2d171832380c0e27f4dbc3cf31fc6854345f6c750106191ec01a0447134aab3d21670f79c9b1f3c5d609fe578014e06042799cc7213d018e2594369596d6cce4f9f063061c4a28bd6de67a3e1fe498ef2494359c5ff6f5283768040985cd2abb497ba82971e517701ca1f82d31d446eb0f72d857c0618452498aa54e1e0ffe2b2383a308a7fbb08d51fbe1e6a9f2c87cc0418462f7f05d423c1d0e1c432878b99c26f90e29e2af814308e5181ff2209ee73cb22fa681230845338d0b5f770fc97e78b10243b502076c19388050981fa6f9d57747bafd83a24790fd095f3ecee91503870f8a65d3f5a692dfac553070f4a03c8e19dc43909007ddff173878508af03dfa9ed8f61fbf1d1452f82867635af440332f70e8a058e163cf329a7f5c367671a3036494dc02470edc90ef92101c14bc33663cf38caaf53580e306258f1a77bd9573d8801c638aa96855b5c5b5288e448e61be9973230de3cba08021830c07a00005268c0ea00005268c15dc71610b5a946e4e326af62cce3b1890e1010d70200151d86216a6f8814c676ff98c3c64b175e6e74164fefda9637194fdca281f2cd62889fd5a3591597d05ef213db87ea8b4cd7145751dd7756cc5fb9ac9d642569c2ab7ca377e32c92a9a1eabcc4788917c7755989356ed84e0b965572a16f5817f1ec79db1b7404579cf2e5f638fe257b45394bbbea657fc8729caa7a725179b91b2342cc04011618b5294c7dfe29ee2d6dbc3e416a428b9ec5804d32415b61845d143b2c7c310bede3d8e284ab5d9473a8f8744f7435190901fd5b4e5fb5b83a278ee52e2e1a4b787b14f1473acf94f8dd13cfec413e5d3a8621692879a72758213e5a89fba4dcdf5571bdff080045c6038a0c65b6ca2f03b5375136caf373d014d145e72cee9d6126b22df2213e58ee8b4e3d1c7c3ee0d2690ec762f8f2a8b6b89fd9b935ab2cb59d8e212c5fcd16adc2c19eec7b225ca031fdeb4dda69ae4d5169528078bd98b788d927f3c28511e450f3ce7911ece666912857ce7439f3da9d5494aa2ecc3b0d58d9a2ddcff489437a334567f9c87f2319028472d49b3ef69133f7f44e964637ed8ef1f744737d8c21145f3ca34dd3ed271d1168d28e47a8bff90f17d38932f76065b30a23cec931f6f8b8f5a43e88b2d95c1168b287424ad59d79069ef1480e10284c1168a287a4594cee0c94d66ff62cb052fc6d72d618b4494f52c2f7efc216553c348d8021125498710e278a59616010edc4842870fb0a307c5ec5415d97c1e94c739ab757dfb1d94277ae8d1c38f483ef04dd0a183b2488f2b73a7a76dec735012c9831883e54bc8360e0a917f9c273ebafa3bfd06251f8fcb07d91c6b4f870d8a37933f9ea892a9e95a14335b9b240fb273135a9436a6fe61906ccf62f1ec3a791c66b33566862c8a5f1bfbfd2e8e85ffe1ad6a73c3a2f0eea3da10935794cb367f725c9bcdce15e5b1a60d9b36534545d78ae2fed02244ee8f1f7568062bca32d1b679142b513f7a1525550d9356caf56726aa28abffa8a4c7d77ddaa2f161462a8a99296db34abccc83b313335051ae2bb70d9b7f4682628c01460cc008038c3252a0012c2faa3431e314e541ce7aa7fd2195cd3045412a4635861ca9f3950c98518ae2b7bfc71fbda74bc94951d0bcf1fbf0c38ca2d859125266e461988c45519a899143dc764351d2a9ca7a1fe6cdc14340310314250f27f3279dafd1ff4421756ce69c7225ca043d510eb76d1a35f4204a4c27ca3945749b94974c9671a218ddcd32fd7042b9641345d7f2d05891aa1593046668a2106f839caf87a6fcc844a93b4e8cdeafd44d3e3051f4b18a4db8fa182ce29728894cee490f6be366db12056f4ba91f5e5b89a2ead6a6ca87126c067f391f86a44ea22c32eebd9e1122a1934479c3caa47d3cf6081f9128fbd0ef7725d66f8434031225e9b164d75ffb88b2878c214bc8b9012bcc7044793c7a53f1e1cf8f83dc88828f07b14dde32aabb07238adb521e39258989c71651ec0ae9e15ac468fe49114589f3d96592d93ef2484431859a85aec827ed9188285e990fba66ee4394f34e4869eeb4d1e29a618872ebd7a6ef75ff6c6221ca21aca5aa84eb284081e922025f94014608f4308310e597709267c335242bb90f330651dcd36c791fadc743356f74717898218882fbf8bbc769e5934ef84617371e70981188629cfa5964e9f138456a0c330051f24f5a19135ab37dff8792dc6786b81e31e6cdfd504899589bd2e329cf121866f4a178e3218eb4a6f850093833f6404fcc49f1a3b6b30633f45039230fa59acfe9f121a7fc0f5d7161061e8a3e76f6ea579dcd6f17c1438cef800c66dca1e863cdb1c7a19ee60ac35fc03bc30e25f7acf98ce1d2804a61461d0a521a3b34865c93e84361061dcadda29221a7abdedb23cc984371379eeaf6d024c6f0cba158da9d7672ccc7a1ecf5a3dc30ffc3b8548d0833e050b2ac0fafd8387bfdbea110b2247e45467caae486e2e90fc347513dcec3f0b5a120415cf64713336e940d85faf5a1a7d4d49f933584196b2887ad9d4e1d0931c20c35940777d515a977db9b4f4331cb790c3a97f5f1d5f3c10c349435fdaaf9e8cd8a4833ce500cabecde18ab638538c30cc5d7682b9e26868ae053614619ca41ceb3cd5ce7cb1c321462a867d46898eff38ca108eb398f7c688ba124e3134cdc4cfbfd9f1106f387e957106680a1fcb2121123dec479c8174a1a4e3c44b488149ff242a10712274a8cb21ff4ef4279981e4136d7c7175b5198c18582477d4a06d3771f695b289f46d4d0bdf89fb8b450f4d8c99eb2aff3c09385d20f93d9ee874e175363a1bc1d7c50f231f239e615ca596fdfd47ef2e0376285728a18462a7eac4241446bb36a94b9f0615428f79e0f7ce0f92994c7c953e671cc210f4e735228e654a31d2193780ff3289427c57db6c9a94dafa050d4ccafefa17ed4d9a227945379d470fba1dd5ec609e561fefbe874e2a50fd526143572f4d9d83cb4ac9a0945f32c39a8cff4d8cd5c42592b46fa2506c990252514354f48fb799c4a4229467cc473f8a8875924947be4336fbdf52b1b1fa13ca7ab753db88c50e8fc91e67b338b50fc4df12173eb19e3498492c5cb76ce394796c8630825f3fb8ff7653ab6ba100a123ef50f2faf2014dd447e18abc7d3d1372094631e0fcaf345f883e2d8c693eff771775a3e287bf438127d30f6a0e4a7bab921980fc53d1e1452e63cf2b51fa4b49beca0e823e918b26eeaa038397a3ab748611d520e8a3d1326e70f45d2e2193828c6bc6b126288203f96ccb84149e46c4e67b3f2fd9a6183e2783eddcd19df7d1ca945b1479b6dbb79b4216f428b62322bffb1840dd911cfa21c240f75fa352f8bb2f6266f71f9185ce258147f3ce922cf5c888f3eb0288fdd0731445a8f278f5f51dacbe0aec17e9c366d5c511e0fd2cf8fc378f8c9435b516a894c3bf1c3d5ae8e15e58179fa0f374ecd8cb58ae2feace9eb0f3486b254516a57b989c92d22379d8ac27e46eae9a1b7aa86a8288874f484105b62f5d05314f6da4aa43ec6cfc64c517ccf1ee8d6c72acf5e290a2e11c1bb273e7c0e290aa1cf4635ebc7e4617014e50e3152e7d4985e3f89a21439b934fe4027e4a8a1284fe6f0b07befa41182a2387e9a6552e7fe86f313a59c3c8816597e9c73579e286adec4086e163d8e52278a96fddee3716e9c28f72824e8ad9f6708691385ace970d32ddb63c93451bc4909e5c3d0a61e57268a1e3967d28fd89dc7033151b4ec418cdf3f3e9da92e51f2e82ae9e395d7a6d712e5718fe4350f37ad7d90ab4439a684bc5182e60b1f4a89b2f7586243569b44c963226e8b9dc4648824ca5183d64d1e873c0879248a33194b4646f2b787902876fe8c1f459e28d54d8d2cad29e5a3724439eb5f567484bcf2418d287e846bc8d83dc9aa32a2d4e291f3a6747da3b888a29aa78eb9611ede2f4594be227f50f2a6fee526a23c1efaf86634879b9c2b44943b27f18166cf28e9a34394fc72c5ef636d8842ee6e550f27bbdbe985287a8fa35d5c631e27b309510e7f359993ac837b3c8872fa482b7af4394194fa2e92b8a7971b1f812072651ebb8a28208ad5c3d41731d8870cdb1fcadaf583acde7da963faa198692321fea3f041697d2848f4c804bdcededcf0a15c7d3ee17e3eb04eee1eca21b846acd21c11fee9a1b812c36bd09acf372f0fc558fdc31afb943f0c121e8ab9eb87b7fbf2b596b943b15673bcf9a16987827fae8831d5e43c12b70e058f8f938f3ebecf83181d8a1a53c7cc28a3fd933387425acb0f9a7adfb75c3994c771d3557ddca9ce3ce250b81f7a3699af8d15151cca29e483c6dadc1b8af9b987da396f37146cc3644e17ab0de58198c8cd6be8fc38c58692458df6a6ddd8b68642696f8ed6ea495b570dc5583f5a9b8759959e4c4321c7885dff7898b70b0d050dbd6eae713d4321c87f8cbf3985448b98a1e01231779ccea9117c94a16c2e6ba21f6232943b77748a3cdecb3cd1188ae147799faec72d9ea1180a9d93ef87d63eb2a10b43d1f27348a6bb67fea3c050ea5afb951ce222e62a0a5c7ce1e08310de231e25a6e2c085178a92f3c7fb5e1be9cf74a13c76223d303fd9dc3917ca9dfbc30174de18d349943a4c8a1843fc3877659228ceafd8990f3e78fe48248aa3f9d1cead7375ad9028dafa6ff8ff78983eb14714f2707b830fc2bec9fa8e286e8e9b66fdd5199534a2b89f9256a613238ae1638ac9b259d9eb2ea254f7691a561fe3ccad8872d2a88d3c1ab1210f4c4431427a249d37f7283e878842a4d8c6ea90c9f9f44314bac7390fbfc7532a374394a657829f5988e5e614a2ec753ef8810f26691e588428bc4777da2415b9e90da22c2d3977989305514837b137bb8f87a1340c44715ac2448f1ecba81310c53c941ee751c8fc8792c7d94e2913c9e30ff4435142cccaa70e91d3967d2864b8ac91ece39c3e677c28a6773ee9e17e32b76b0f45711f479c7bd939c9eba1f431ecaf8ea576a09187425e849c2652321e2cd0c043d9ad375247ecd1a875ee5074f1f7e894db0e859d1875b24777eb9a7b038d3a147ad2f47cc4bd7b7428e7de2dd73cfafb413b8772ed46d22499eb2acaa1acba3dcc2b3987758ae350f098f8ffa1bc8369e050f8f3d0f563ffa350df504cf1710e9d773adaa91b0a3945f7707a105dc7d386d2a4dd5ef3cf41c407b2a11063a22671dde27b5b4361c3c58fbd5f3bdc576a280fab3b3ae4bf64cd4943793cbc8ef3830d1a8a9f367ffce1c707ded10e689ca11c3fd4fb8d7eee2b9986194a3d9a18b6f912717b54116894a1302f1e5f152122d02043515663cbd406cb9c3b632879dbf760b2974ca75e0c4b4a484811ae0943a13dffdd7ee79187c9f832c0c0a5018662e9c5f468ddea737d90d880c617928617ca436dcd9290479bec431e6254d1e842c9835c97f968afcd2b1a5c28daf6459e77d1d71cd2d8427998cebb667c3065e5176868a1e0e93de3ece58f2a2fbcb8eb028d2c146f7f94692c22637cc3c08441b240030b059becb04c367eb16552a0718592669d3cbd9bbd085259146858a11c6afd563d779671c91e6854a12caba13e1b43ec1fc67aa04185728cfd907ae75f42fc9842b9873dc8b1246584b4ae144a6112a3dc49b2ee40230a6591d03168ecfc36238142514c647fe0f90793925137d0784269e543d77bf8e1d4ee6da0e184728e14f5495452e37d1a4d2876ee0fd266964d937a020d2614ef3cc28f72248630fe175b2be041d0584251e3241f4e6abf8e6a95f112b00a040d2514aca6f5278ec4cfb2125d0646e005001641230985e80f93b3264cf31243037c081a4828ef57dc47c89c793e038036d0384261f33a471f6f900e9e32808611cafe3f784953bd130fd32842218794a1eaf3b87ed049229487933122c758dd0fab8b1ba4811b5ddca00cdce8e20661e0461737e80237bab84173a03184f2f873480e9d0ba110225d42f87c6dfbcc96a01184d28ffcc7b3ed75cba68c14348050d2938cf3795cd2f841f9aa3ae6fcd9c3078509e14c3d5649e4a85e2c2868f4a0683fb2dded518fed47152768f0a038f135e20f82acbeb9ab679ba0b18362aaff61bab6b779fa75508820c1f7d46b6c423607c50db1b379ab31c4af3828ed7c79ba916c2a9ebb41f1c397840f4b974d592368d8a070baf9d1f92ebf2a8241192908a388a1811b5ddc78880186056e7471e3461737aa111db528a6eda079b412c143c8f00a9216e5f3e0e3c187ad1ecf45b328469cac8c5f9d2674c8a2a855f583911c34e66998d0118bc269ff20c8a6ce9d32755be8804531d54bfb44c983e8dcbea2e003c94b0d6937f1ae163a5c518cb4d3d20c9b5614d4228f63a3beac280fd28da439cb8b551422b5db64a558cebbb8548f47b24b235f6c915414ab7f98235cda810e54942685aa246f37b36a51d0718a62e639eb61f89c24e4cea30c32c65801da0d0f48206bd0618a728c26a39f79d823d5baa314c5eb714ee87e8e90a220ae1aacc3d6e6be7014855ef751e74a48ae39298a42e56b1ec6fcff3f560d0b1da128f5707b385ae5629a7f80a21035544c650e3f611d9e284c92dd1f8ffeab424727cae2319f471fd94e9c395188f7ebf1c3b332cdbf89b2fdc68c5e1621acfc9a28c5f857e7a9caa8ee67a290f53192dfb2073b3d30511493fc1d5e37b771728942c8c64cc958a2301aefe3f197e7f120e24a145cfa077974b2f3e62a25cad91bf2b8cab23a27348942f6a18fbd2773de28b124f692ac263134bcaadacb8e4894e4c607ed3ffe791f6755091d902894cb8768fcd0cc43fb11e5ae12f78c1e57b7781c513e49dbb17975230a397fb4d1cf667a201b46143598fcc9d70f26526411e590a3ab47dde03939ac88e276ce90b6f320bdfd4b4421450e91c8107fe87121a2986d3d45f2bcdf118905293844e13a25feea5f433a0f4314efa74eaf225c6c8c1da1a310e581f8c03bfbd8471bbe438872e641874ca8cdb1a6338882d47cfb8ea44d474710e5506d1a368fa440143dff3824444d8d3e3b208a11f37083590f7b5091f9435173121fdc8e8fe71cf643c1d537a4e6fb5032a97d28a6cb08f652269d7bf0a1f07f19e43322e71c727b28c5a778d2b5f5fd550fc5579bf81c232dbd99298305614820c918a30c439ed0918742bad69094944a0f1d0fa50c1f665e4f7eefb2dea1f8929a43f254c44a88ea5980026313e8b04379f8d75b926a351bc73774d4a13c1ef9a083c88fb2b35f3a74d0a1e4c30bcd63af69c91e3e87f2ff64c7bac8392086063ae4508a685a12a347e9b0712774c4a114397568db0e9942071c4aae412cea3e3444aac9d0f186e2b4779efac87bc2845f6ce556871b0a1fa2bba696e761d5a6a30de5b5abdfd8dc9f91fa2fb6bcb01b1e90c05ea1830d25bffbbc3b89b1d20a74aca1e403cf5fe795aba1283feae145787b8d4ed25096ebd1b4e69d1c5226d150fe506793a9ba8e33144e63fb20de595e1413a2c30ce51f6c9698dc4d42ca46fdd0518692e4fa119fb4a9f63b2faae0d04186929abe0f5ade5d5b7234748ca1a4529ad6cef632467488a1309927e1b3d812b8d145b9d1c50d0874c16128ce4d4eb0abea8d906b0718ca76be133377b4e30ba589af9c4b3d33ababc30b250f3146b744df1e592a848e2e94c79b3bc7bc2d73a1987ca0bf39617278b885e26fbcedf5c8839c15590f3ab450defee05ffdaeb261160a3df9ad647dec575163a1a82f9eec7baa2b1472d5868e9db7baf06185b2b9cd4deba70fed8354a194613fce1fbf4d85a26de6a1e56a0f32f89842f96fdbd22287d7eaa11d5228f520ea7bf8b1ec884231cb77d20d9bcea0030a85f908e31ae3d4f184428f4da2255b47cf39c94e287888ae6be6e31f0f755d4247134a771ef1a6c7470713ca11ad297779e6c3eeb18442a6bf2cd7f4a1ea24313a94501495d066ef93573763942106055ad09184d26b9a1c72cc6e2dddbc17ca820e241453587e960f3faf48f08b2d2fc400a304df638c414618e6c928a3039b811178a1828e2314638408c9337d6cde6b181d462844d4bc791c8c8e2294d35bd5ff4afcab933a885050331f4328bdb7c63e4df3023a8450faf1c50f7d7f3cdb11cb117404a1d4230f19e6d3637e3e20145a37b6a675c6bfeb3db0810b68a0e30701ff28808d411cc086200e6023105e20c00620feb00203d8f0c3006cf4c10b02d8e0c30a0460630fc60b02d8d0031736f250c3061e66d8b843006cd8a1c3461dbca861830e477ac8a3affa994339f6783062134ee4229743a92a6224654ff6484d1cca9e9e619ec5ffc73f120e657df79c492ba3e2736f288e84f7a9c7c8ef183714437b98f0e1b84731ad0d850f492e2bcb87d59b624351666c93968a4eee6b280f637fbfdf830f23460d658f7a97729d3c8f5c6a818d3414ae62628c8fb13a458386725a9fcab431c43bc73314f232adc7720f79dcc30c851fe69c97d62099da2a43214af871cc8d617c18271b64289ff7ff309d6fc70a7f0c05d10ff3e4c31c4b33e2850d3114a2e7c93966f2e1c7b636c250de8dd51cb9a1e0c3781494301e056f9a811178616c80a1ec63910efbc9b5f185a25c4b8fbb737d97f45e28fd7053b8f68f763fe6d185a20fb73ea9e441deb02b178a19acc73d4855dad842b9073a3ef8781ff7b767430b25dbd8db8c3cf9fe236d64a19c3e1e4f3eb797d4b70d2c94837496a995f60a85ed6c3eea8831fba6310c4ec00f6c588188cf9347154a951592bde227caa4a95016cb989896d18314932994d732c7e7fd954221daf8e7d18e6c1e4f1b85427c7fb16d2d91fe2414ca1da17b7ebd27143a0f66348f42aa324b27943efd86cdb7cd8f93d78482ccf8d00726312694ed7c28396cac9650accbe87f39f9cb25644309e5519c4df6faba8b7cf6071b49286fb764528f26377a561b6c20a12021e4a4d6d5039fc0200606052a021b4728f5504763e6538d509e7439a9bc079bcfa63539d8284249624abc4a7f6d10a11c43a69bfbf41842612aca7b2cdd99150cde6c08a18fed519eaa7d9cdcad1cd80842f1f3bfa491d63ae05b7bb00184c2fe20f27a6c9bc36bce0f8a62daa3301db5d0dcf141c135a4cfe3a1f53d2869271f5fc718b249ee3c28694ce671ca729764921d94de6b3bb28fa3df5f4407658fba4b1ffe0fd2c6700e8a2dd9623c35d6eeb638c07c1421df25b537287d460931f6048df661c306c5f49f39ce8d598b4247dfc6cee396ad9116a51eacbac7e8f4e71fcea2b8a1c487ffe3418fb263b228fa68523f6dfd78183bb50218bc055660264048512316e561aa39ed983eb02867ff417ff4090fd37945e94f346a3db4dc68ae289c448fabaafbc8a36f4561ea337aca4dafef9a15e518253e793bafa2d0e3d22c717ca8a2b89f3f69e37f8c31d754943dc73a779f1dffcea1a2e813ac07d2353db8d11850e314c53031ff3065d29c7a25a628ce6df06c5292bd3fd62845417c73269e7d648fab440d5214b2a544508db3a1529121c607c4d0c08d2e6e588d51946dee87255e762bdb7eb105032f1c0c3252c04fc6181178c08d2ec02063032830609404fca186286a84c204354051fab4ceffc357c9282305353e61353c51d60f79b8253627568d4ea4c9f4d7da6ed37a18b8d145086e78e00237ee08278ae7bde1fb31d42af2d844e14e937e0c9d48eaa335511ed5e8b8acd774d4904c144284f9684870891c4c9475d3ba4479681d7de2e609a9fc638972787b499fb35d89628c31abfa3592326b28519030613658890f32492651d05cdf614d327a8caf240a913dac23ffd4498f8c446125e6983be90fc73e834441b2452f4ed347d13fa23c139b7c38ba212e2b4794f3ffc8871ff3602268a811e5904b7e1cda734a768711c590697e3c1ec6cd228ab1de45bb2265980fa288c7b95779d211494479282f31fbb86673868a8852faa4e974cd73f699126a1ca2b81f26af26791acd1e350c51cc92adfed9e3bc7aba46210add5e3eec3069d29a3b420d4294cc35ab76b6c95aa1c620f0cb2ae9ea515867bd20bbe1810bdce8428b123504519039fb32738b70bb0351ea0cefe3601d69638a01514893343c4f66aaf387429cfb3ec9a91a7e28bbcdd9c4ddee689f5260c2b81a7d6037337e1e0f3e3cca3c9ef4d1c79a657cec410fe6365d5beabb1ef4b9cde38c3e99983c98ff7ac673074d193c549539a3694a8c22217748a4738cf8b83a4878ed60a787c9c8ca746d1d081fc387aa7f47f74987be5efba25f23a73987e3a47c69e8978908e54076dcec41d4baece2f098fbf8668303add79a87d1837453d71b087924fe39b941e9eed2503ffeb186a70dc59479f49b4525feb84b3660f5d96293cafca03594a27653c618e24c8ba786f3f538eaea66984e6aa4a19cb7eeb9e2b4346756550605081b6aa0a134de9be6e3a18f33143b2c2a25a26c8672deb4ba32c9a3ac9e65284fc6dc9dadd3aa764d86f24688d9317a60d2c3fc180ae9c5eda4e30fa2645a0cc5df1c6a99cb25d7a46128e97cd611adbc50030cc549ef618f4c273065a059e0461737ca02373c70811b5ca8f185225437acc70b658f98653a474fe43cbe8c1a5d20e586df1ec986a4a273a18cd49b3cbf4ca7b770b578e82476e7ab161e4d5ca9fcb29075fea8b63475fe0c0bf92089c7bc2c05862a05268c83418d2b9c9b3cd26d32a5795260c2a0b242613f7a5eca4d81c180d6056a54a1ec3b3974bbf8a6851a54a8ab318562acaff8da3cd1834b5fecd5ab75a186148a252a9e7c6c72ce1e17857257e6eb8fc2e226d4804221a9cc4645789e50d6d8cd22bb21af8be78462c68ad9f11e8b50a309e59022a8bd4a361faecb84f260dc6744aac7128a39228cbfe71f48bc88120ad9278f079976a3fdc61a4928e7749d2284a422df036b20a1b09a5c7d20ae1979a8ed0f6a1ca1ece1d9871a3e104fbb114a362e3daef033f7c8d4284259d5ad2dd42042c9d53dff307b1439823e84e2946fba6c88544dae10acd2f2ca8e2c2157466c3582507cc9797ceb53fa9d9e20d40042396b237b696d5a91cf0f4aa6bbe9b5eadf4799f141a1329cb8c4ee81a7e97a5098130991a67af0a034917ce85deff71be21d94246baf6234d30fd2870e8ad6a39dab128b1af7bfd8c2428d1c1425ba79fef8b1a53d4c0d1c147466df727ca4b5bdd5b8414176d3bbea347d8fd51a3628f64022664d526b3fa945a9a7267be6b4f7392e2d8a767ae1a39d55727e16fd60d5f243f441b2286c44cc11c147763d8e6251dc1cacee46a2a7cf0f1605511f8f53f74c308fea2b8a9a2272f691d9ae28dfadebfacab8e70f6a45413b7f3c981c53ac287f4c1337d7db79ec9b5514634fe78d93355594071b7762ce945351beecd116d35051569dca8d91ea2e5d9da2dc33b2333fd659131fa628db9788ffe883787ab214c50d497fc24e88e3c126455193b59bc44731f547518e1ff3fcf130746b5a1445cd831ff9ee9859fae0509443ea711e68ac979c6b405178fdbe1ef678906d73fb89c27fa71fd9e641cd4fd813856c6f9bc13ee4f18edb89826e4a8c0e9e21bbe39c286a74dbdcc3eeb0d4741305fb711e8759e692c891268af1834de22391e9f4e14c9447ed4a76b5f3d70f8389424ade8d57b73af1ec258adb71b27e92ba7ccc5ba258111a9a13e34a94c739b1b4db6fd434498992ea7e1e442989f1a8c44e6542e240281085c3a1402814080ef21e00031400000008169245428158341ca98b1f148003442c203a2e2e1c1e1a121216141216148303a1602820088301816018180e048241212944108d1fa7937670dbf9092b6dc9830ae197a4fd34807cccfaed164a7f02ced2ad4232ae9569f820d8cfb85d7f61324eb70f6442e738264b31319b2bb06881864c56b6500c5ba0efd7b6a834bb7914705ddde16761353c20a627309e8e8787a6b16ff62f9e1ce9a2b6297d88e9f0449fed68a9b7bdecbeb6ed8264b2c310008815c43922f2c486a202114762f38bd3293cf1feb0f892952c3c6018499cb690abc140a21cb952347429246af829f599bb602cd496ff9fe5a64acd092894b2bdca4ad1b8c5dd1251515a0270db7898dea08ec42a598d8e7ca0153ffd5d39b27f9216162f27dc7d1d3350bc8b83b9effef9be3bae63b06a11dff43029f139622bcc01868c55613a29176296c2227b71e309c3cca3dc5224919285bf0242c58e92e0675ae5ed87e8bcab7aad77840d3b8c7eba448957cd6df8323f294f612854ca6ef3b6724bd0f93638710c017f6a3390a4cb3712bb646ab81231db23b6d00808034c66d2206cd53a44661420250ded124a620b52138f112f7145b21ee3a11060ce2cb9675954596cba4fce7c419493116670f14e64c56a78f1e0d2b70654d6388a9d93c8bd24c43f8b0898a7ad2562ff0fd14a2ccdce116fa756a8c762901b610bd72bd443bf0208a212758708bdc84eea477d7ed56aaba2e39d27473c2d5f000f031f704bcd822938addadca8aaf9583b5eca7fa0bc9d0701ffe060c1da40150d03c28542c212df91db4eb7994f2f84b0c8528a8c04d61015d5c9d2283fea2e6107f63107e0e8a2b0bbaf85463e21952ea6dbad1bd44dd28dd43d0f375ab1c4bcfb0274ebe8cae041e72e376f5b372addba1ff3b6cf43f08300c207640dfbe8406bfdb1a43e0fa9571c45c1450d3213a79fc9af281169451900870e9c60f91c5c377ddff1b88c2e6bac286b9cb03dafe47064ed2ac0e4e5d8ab60f7ef188fe4b936e07ed63a74572affb6a08b4ad88dc75f000e48fb160355c809787ad6a10a480a2a5300da152a53c7a414bf45d2f72505725fd48baafdd10171234a478b456e19982fe26745ba7dc503319d13bac529eb1d1f0fbc82c5e6fc8f76761e8d4c7e4529f42b3df294c432df428dcd6eddc2613c2c81050c21586bbe8ec3566e9723888c8b0535ad956946fe5062664398c0e17ad21d02d657c0397a19b624dcfbe30d36e1162386bd9cfa5b75da2d0ab3ea0498b90fb23cdfbc6dccf075220c1836f79c366674c141394e6c58d55083346d6b2f8d4211690909362d59b37bf094267e0e193e0a250c2968185311a75d3e10e328ce797a66906f9064b0e3c0af0d694acfc03eb95034217f12888dd36adb6098fc2dfca133801ad40dea066906751d1408616dbc43cf8c83dfead302359841bf47bf0b06380e6606f3c9acaa415b834d6e1cd690ae101353fe7108249254d0f301916ceef704403ce63460a5f98b07c4bc0241416741079f4aaf5a835d4d6642bcce8e25e3b406e5a21a69301427ea58aa578bc780be03a41115b9c00682588842508096bb360eba0288a887a0e0548412a36510e5c52a1fa8b1ed6f74233502f97f3a3defb74c3666fc42e03e6cb0d2f1dc331ebdbd37c4480189aef7f2eddd2b22243dec5aa8f7aed1f6a4a3e1dec39a37e1d0faeebc2a52d746fdee36f93254555bd07b84114e51039a3ed88d00befb932319fb1f95509e0585246e61b9644c7f36499953444b0536ba0fbd5ba2301d8fa174cd0e57778577e461a3e3628898762f120928cc2851858dfccada5a92feead7ca623211e32eafd59f3c23ebf2224f2bf332c7ac1a898a0f0ea50b8d0b665e5e368b4d2c56c25da1b545f28a3f20a42771aec9b89816742583d075cc4f63881acd099c992bc8ae1695d7cd67039482cd96513c190c186184ea3120cd8abdbd3da2567fd2cd48626d6752ccf18ecad54719461fa43cecb607e151397266e28d0833fc75454533965a5babe7c759aca73228b90a859248ce9fc9c8d69dac223cdf5be56fa65edebd9dea7e4f6dba521bb814fcddc988556326306eae466479f28f9122d11f1d58afa66d797f093830f3bb5a3e8cd2c44023f9bf58c5e1a0ed6832915e90247752280cb77ac43f92d804506f5035f3cb5e2e29058807db4afee2ef83030de683cdba2d10854ae4b4344d64009e300935eb33569d0ac50dc9598180c15345a1c21a8ac907ee625f0fe0b6ecbc2c9b12f116dadde80803ee052f25482cef2b1600435d2d6a68dcc4b825f397019de6421d7bdcb09a5b554c209fe99b91812008fcc7ec88ec69aaa725cc73e2eaa632443bc1b7c06408915edf6378a149e12a3ef40faa8df86f26863d7b89f9cb4d0c634e80479a30d221a020530847c1808cd007956d5fab66a28cebc360a625d97d7aa2f74997c8ca6949f03a4703784c5e5c5844bfb6dcd1274936e825d287d96b3cbe16594337bcd707afe39cf96adbc610e94a7475b93260a1f88a0b6c22ee791e1af2e2f02a7845ff6b31b5ab6bcebc66d85f8b5d6cbef3bdecd3d7d169c4488c5fc76f5c2183cafb3afe0ab78874792d3ebdbcf1fa62e895158db4b10cdb8624ff025ebbbd567f7885bd18bce85e0c2fb0178417ed05e0c56e78c5792b572f63f4e27b05bd22bc4aaf00afa65785574445f21fc15329205e775eb8579e57da2bce2bf74af36a7b15c6eb0939b1b257f52af412d3d726bc1b1e4164bd085e0d5eb95798576a791d6f0ec7012ed58884f19cb236d4c040ba9286d4b3a80ec8ea85382034a68c6dd679aa5428cb0f126970b9f8362d7929e2b970837263afec4831d180862e12d278dc2d2bd714b587a4515ad3d5de9c46a9e9d15e6884a5a15265f815482a2aaa8ca38a601a996aa25f9517a49c5a6ab210697361713020d4683961c1902fec42d8f12cc2cf268d001420263ba2328a757f51635d581d8d578c6914cd7b1bbda651c527f7ae12de16c1f42823030de2992498a3d5ef14f7bd288a82172b0aa911deddc573a38be8f055049bd25cb97ad7bec6d2fbcec7f6b98691dc41942ee0b00d00050933641c388008bfb8d275d5faad4468efb9ee48b4180cdd88bb35cbcd1a25b590d8949dcadc3f04ec50cd1596569422fbb9a674b10026ea2ad4d55c1728df03c7a0a3f2d95c4be4fea42953841539e18be0353390183f03d30d132083265396f45cbba4d174d4be25ddfdb070d4adb82158e284217790bb32f5c5039654a9448e44816e56f1c3e72bc52e80e45637b8c40f35d4dcc60b10be1af006db2404529cfcb92bd3675350109cabc1f48348870e142037a9061541ae2f43b8a848365ce5223a9326da73c44f321cd56c9fde637e8d244531a53b793d46f6002ef7166e29c282e9796e74eb9b72353713b30d1b193e36da2706f94e455ec5aeff6091a792f9c75c64295d711d422be0cc4e346274e94af18751833b8cc73555c9f5a76da8f8bb753e6cb489add93a4682cbbf4d06169108d9600484b6854bda692c5d19e81328e3c3b97d49ef152c33a8ac10439951886774d98ee418ace319c6fedfa5d412ec5273195c30402180899c199a83d54a31f5e19c1698f0830d9dc00498a07c28761a3c040b37523f29c637031398234f763bb359ef0bbf4182050ba4f7d8ddf065a71a958b3d165d2c30056d58403af0156fc1c2051d89fac9365b23c60e63336385b100f4ae9f0c328d8a5bf442b07fb03c88123b39e0ed6c8b8de1d9acb13505d74608e62b9b56e50bdd8f51530590c7d605eaa0db6b54c07a277dc667d14d7fa24ddc763b0abaa847910306f11fa691301b172eab1b287f821c79a0087b0e04499f833baeb0000686ba0afb76e0b7462880c1943a96bbd7320d1718baaa57e072a34615c011e9332623c816eb92b51d56903e72cbce8cc83fd343f9a36316390f419d36619a514050bec9e5d60aedade232a2fe3b58a5b79ea5a4d1e85e358af2a089145e843cfa0c5ce509dae38236c69362328362755632b15364c0954cf7ed920863e5108cf225326a3c2e887cac2ce2d9670f3efcfcecc37f1b0481403f55a600d4c294922e2e29df00800502b00274195942949f1b02d94e9c750752b0afdc5296fa05834cc441e7044b503137990c909345858370f1aa720db01caa4cd6384f0f8742ea5b3c083b4b939384c578d761caac2b4fb11d04fac7a24a55aae95e48bd5eecc4d2d69520a76da8a6523e98acccb47f5447df1e6dc1285bb313045683c420d4206110b7c1720cc9c5b8a4b03220c260b3ded969a437a2d320a230eaa522b08434c8cd39cb037acfe64b244d29d01288dd96b8b64d8869f35a9015eb5584e6df8e168003c8010801b400740081623f1d14009384c280ea6e078403227e23d6217c53448780802820ea21128fa8481412d14e02b464ac57ef788ba26122dcbe3cef776e843612221e5a3cd55f9b62225ce85ef29303cb1c48b3e036d847d8b1bcfe042611150583ebc374ea258aea7e947802625e64a978b3c2a8b1d586bca177e112637b9c921956229a3ed5c0244fe3cc7de57aa054ba7e9d2704cea66080aef8097c973f08eb33d4ee5b8fb0ca98cf6cf9950ff6c726cc6213b4fc85b05d48a19fa22682203d829964839711b51375567b13e522ab177e486beda0027232f5ebd8a25eed989508752a1080f5296b3c6fb9b1179f241ea5ef54367a75d0bda78f9b2ab515124154ddaa7ad0b118c272a1b075a6d2abb9d1a10e630a2295bc7810d64dcad2038679a8f8eb2444cb8b41c2c9a9807c5c34a5a413e3726e564f683e2857732e9a618a8edaf69bf727f72a02b09d064081921e206655156c87128a951d63cc210abd6a02f431284599001f206e107608842581c33a207a3aa693573e99da16e08e099df6edf54eb5e35f0be23a3ac1d100309033b42f0b9c82a26ae0c99e62056885556b74a9c3559e2687b254687ceae58341764827642689a96682d3252cd4e6d284c1e3377e0298b2153ca1a3458268fb8f310e69d50108124c80ace97a81f89250f4dd32b14d2812a4c96c4047c9bcfed1c0da86fdd0163676427e8f5abf3c52734f6306c4b0402576dad7ee1e7aa5ed09901958c6ba94df51fd02ddc1500abbc22fe4044ea82b6c1ec11e674702bda3c157202dd03800d546c02d5c9f2d10af6936473e8b486e69d046ff764d29658bb3eaadd1ba8ed7e84750fc1dc9e391daf71e586dbb5fb017e1c677c7d071fcde29670b6ee1141c4dd40d706dc4af8546af6be398ac056560df5221259a334294c10f4115e37f3fc58432ce3f3fb290429b58a21efbe7975efb73a189f4d341024a4f4f8e6c871a136e18ecdb3940047332801d0dff302b449e15e2589a275574948cc34c97de16a50ca1e0f41ca0c8c070318a02b0f608028baff6c274fe09edf33e82320ca50f7afe20e3ff4eb8bd4e39166b0713db663abe0af2e074b900380cc522aabc7466056bc55005e5a5069088ed1bb3270d8258a0db34497d9c3cac667ae2a3f7d3f6456a6360c9b2af259486b9240116423b0d895a0bfbf8f80a90338ab9262c03965aae149a611f4befc9554098418906c05dfd73744b40145957732453d630c124e1a822ec06126032320208ee269d9501c0dec87c0a79c8416ef94880e2f886ca62afcf17c8f0b05b05ca3c6b89b9acd1791b9e22b31c4351700483381e6f13e6f082909afb9f4bce90987151cf7ab62732cf580362a10157ade20d1e75c3441ff2ab514cedcafce0779820d00c8eeffcfcb5363c20af1b320fc71ed1f3ec2512375aa1cb2bc48d05ae9fcdbc88be2aa2d7b8fcb42bf86ca31f0843e536870342d86672efa22c296d22ccf783ec6a68b5f06652700da3ce1d8671e38f109e4be4186618b85eb86b4316a03e762171ae067e6f15cd67a306c9b9f9f2c6f7ef0e538a3a3adee6cc2ca5f061b10361a488b8930b38053003e84e09a28131190d15c00a65d8006932cb1d0fce1d94bbab91118337deb14a16602a79f17e3d6c770580c209afb2c59fa717748f677553f1c93685aa4e0bb98ea9718852ba15a2bd80b91d983352aeee54c91dc9e5254701839664b4015f85e7fe7f36d72ee017b29fc68de5d53f26d0cd97fe0e066b452cb2d912a9cc82158d3672540dbd36260da5b0a5dccb192ec073348a1bcdb61dcaef713af1d68288c411b3ff3d0533c39c6d2533adf2ee1c240d2c344a6f1adff40c8a0044ba005f82f3cb8e005c93c55d155cb1904da962703271e460c18e9a011d75317e8ad729c2e7f30b9be2da22f9b3025236f1028030ea5939c0a28da52a72a8c043581acdbf2eeae18be1886e6602a5eeb1aee7abc118bb2e369a0c34d62c13e235385e43e7ea86e23dfd157fd284eea80bca2667a00b8c5208b378870b3cfd56305c225ec24188164e4efeb2d061c5516e5efe49ad3096a692952c2a0335386c4b26b84ccc7fec08a75e0120150c5e4697e7f5ff40d3ff55b01acf84d0e6957a37095f727c45a87cb00d7b7f996cf12f5b07f10dec373b8b2471f49a12c0304e43ca54073f229fc14743df51c9868d3867745d9ad45fca016fde20dbc0186b51fac388ccaa660029920ce2bd3edc44ccb4a955a737b6e670ad41b3c500702e03216c16bd03898d0453c144017b4090940bd9014246a089164506daf8d3d4aafc7bf467a541bb3501a1ccc178ff2fe34609bbb182b257fe260fad8a5e39154fcbc6f867d07b6f1e4eaea5950e30ed5f1ca1f67f1f3f6ddcf421596c955d2579cee155a71469492322d33ea45bb940d038427a25e403a2958e598b792b8944d1521bf3ad36dd47466d1434a1353cdafb80d84b7732d78cbd4937ee745e3370937818125fe68cf950662674d0561d0b91ab1531b1556ed2f9a56388254c92048e4d3d6a9e32e817e2e7d4e3044543011f288890a896bb6b0a1aa82fa246a5565067a86301d5978c391c7428f04495cd4fb6520e526d287ba8062a102a0255840a4035428552503ac23394aa6914592a3ba380db584a601ca87b92600a410b6a068a800a80517db13138ad40fd36aa4f569b88a34071cd281d849b390f44a110a81488ea9bd9dea13da8c246014351ee4fb5a73a28b28da02cb1a802481bcb6913cabf14d956505d281c144229609905c5851241b542d5a0ba5021a858465552e1502da80aa8082a012a06156151a8b20d933f148a14aa0555850a4195425550b5505d01459b8defdc2843411b95948a822aa8b6d786008732867a43dda11680d261085540b19250d9242566be4f01cb0d500614c047f52189aa56c144619d2a7b420fd99650a650281435141a0a1d0a81c287424021be512e45a4608b058a04150f15418543b5a0a2bf28a04f8f92e989027a8fc6cd75a1e2004729df6728f1409da866a026a8031ba5e30b1de71182b2a04e4151f7ae801cfa43b1a1ec500ea080946533ca1b9190e8f7562048c1c011db2b04a2e07beffb16bfd539c482981cde92053b5814cbfbb66af25fa0a119fb28e06b6e4aa5ef16b206f669a84068c46afe1a7e3bd868af728861410e0943d78888f22ec6474dcfba04d70c3b270b7fef7748c65eda84c53ce8cb92900ef5e17d3bac2b3c498f12ebb25a36ed3a9d6a092829f3b78d03cf01304fe94de8815611b3f451ffd818c076f2c7f598661d7a8565bb25499f663e228f1fea31f588056f6f46fb91329e0ff8339023b39aa34f0e24ab972a9ac6a933259e4a1f1af567cf80b2b35f9aa27e48532e0f2647e1f02216a30816fd251a95c3ec5c857679f5eb2735794d4f377c5a9641c53355ae8887418be40d90b0c95a9129644594dd4dddf53c1e1b39b3f8349d9dfaa6bc196b3265586f6f15b5e91a071896b430116aa4d50bf02368309c81eed9d8193cc1759f6771a3e371593e3c382c0f5067736454f0800f275188d888a0ad8ccdcfccf592c6dc84417288c67084b16ff057d6871a3b0b43247c05c352b510595ab31e1a895c90d4e317a70a9632753adce2a9a14158fd8d2fa3ef677c37050de234f6d705145f5f4ddff4d7bf1372770d7367541a12c1a48d1253b165784cc6357a1f85e36e5454148e8249005364e1ac34153dc8e5b7241818a454076ae8f619c2365ae698057d7f4edf1a1346f3e1853a6aa965a20801547be86f1141680ed5725256862bc01adc83357d77d6c06b675f5faeb85b162c4c5a15a8d45801865614442eee161b46cb0d1c36d4bee493f3eb0e2de3995b964848f1127a9b39886e64d47ee285abd4326a524c6fa1b276a96ea6d2ae73ddc6b299b5358e1a7921a1a7765122ea6c5ebd40ff6cc214eb1bd6b2bdfc65eb53ad11ded95770135596be70812b8feda00141e11aab34f2ab83060620799a34cf060d21a1a75aa5ba7d49728bb6f951296433b56034d1869daaccaa403879962220670ac35ff9b44300b0d19db9fb9610d9d4cf0dc22149f862cbce2237f5277c1d9dc40cd535ca094e521be6f95a15897851b6c50e89896acc87ee4481d38cc7661ffb4077785e75162a56e9801f698d23c655ad2139c2fe6695029accb5cf2422d4fb6cfb531722aaba4b896b7fbb2a94991952eeaad5522c16e5a6558a051537d82a6cfcce1ffb3979ef8f5d3688fdd62011c0a91573fe8bfc4fb2e2e2c632738ba8f53f1fed5d96c3700606b872aa186cc579f974e88473dada39eb06d81c39b2e0325994358bb9df2be05031a9a2e4d1998015d7a6e249e10c00aba5bc6a0e0ce80f1641d4d44c2067b5b86a52aa8a7d3700161b25a58981851d36aa14810ca493482d6860650643eded51685a226abf5acbd9cf086bbfe3fa748a4013ab739d998829a26ad60ad0f1b872218460a24b76cfb5e61b63c82484e3a476254adcaafef9812e4a5cd9c50fef27969e659c515061c3b1c98139b79144a252d14a214e269fd569e8c8cc52a359295db674f32e574d85bb52e1d65786df7d80cb26070fb06ba7b254460279f612adeeb04111a079134ee702dbd936fa601e4bf2497f127832b941fea990c0e276c0bb5d1ff3b4a172891d603e8828fd4318be63816e12e8e313605b62c32e280ca2450bd7ad3425d4f8ad808416d70d055cf8596367528f593ded14376c09e1c9c3819e8ac7bdaeb08c84c93bf00905f9c9a5f593ff27dd6c87153c1e8f7879e50e8243316ea00632ad5e4e90a0414c980401e68b2d0bd3200afd674aa3502012239a88c671ddc12ad019013637aba0e05bec9bdea054fb6e229fc34135242d2b5f660ca2342cc13f5daace680467ec8d6a7e62ce75c60f506d2cd13edc017a39f0504d4685b04811733e22024a3fe3cddc4309bd26e33fd248181dcbd215904619722b62fccb6440fb6bd0102dfa36a04dd9733c6fdf5a0dc3f9833c7608c1a25b1b8c0014bb1007a15c0c1ad435be32a3af9abe5f3b583e3cd65be38c80b4385c71a532968ba855e5fd9cd7e0a4f33232856ce7553fcd4d350f482704be21d66bbe313ea8de7a1def97b856cc413cf07cdaa1aede3b2e9d56eae360444d7647935980af410d9cc766449b8305171dba3b036f2844f88a1c02f25a34221ccbe5cf0f584d733c27fa596936c6574c28abf112f585d905966297893c2a3394089354095b9a1d4f5b826f5a4348f3e1b370fccb04d0120a51616333b3663aa1c255984f65cdff93ab5181ac48581885831d9995276f01035ad2c4a4c54b80ffa5448ed774d18be158b068c1c82c8d49e55dde582be606fc69954d08baa4eca1be96799911be1cb88a01028a26f83addb9ad3748e0faf6445f70120a20006a9f69292908203009a6d608919d4a0236ac06ecc43b6d2a99abf1cc1fe8cd27dc29516149fcd58712927826b271f19c235b9b243a70623c97031a355d7509cfd858a592f452c7026074e28236d7b961269514ef96a51b4ae70aade7636220b4b2dfe0dda307a042de4223cabf6b04e3cc99489527cd74179506f59961496176439981aef80278ecc4db79b5814a66f0a492657813c4541f680ceb0eeedcd6a48dadc3a800567925922cc19604fd5284c9482e83b2ea156a334f065f1f0c37245847744f56d914a38e8bc2a579e3192468d3bf8aa57a45e0becf9eebe5a7f19b7af876d310f794fd15169fc66246d9740a108a4554adb0128e648bd6eff0b28d5afd7a5ede49150d4c33cb92308f43da71dc4c9edba0b4c465e8fae444140f80d076e15571eb03dc2fb0c63b21364d3b074cdc4a6a42d7046bdecc177619be8d9475597d1d263e2821bd6dd03e304dde889ce1191ee6ebf73d7ddfab1e0de59dc76d5f0ea47815eb40554c853d5c67480087d26058004af0161af6074cf0db7434d834072936e4414c6c708056308e22044c6bb81d51cd0d7f9ed901681b8754d9bf44fa5cbdc07ba3be3264265b68309317cf985734459870d8af627398596413038e6b45d00d3a50b0a6d001f3ffffffffffffffd74df128beb5b6f60760954c5bca7424d89037000329a594924cc90cc080df0bdf8a4f88348b22300a0000c6b8f60d130e770d689da3878d1b3cc4d0812000118279c465b154561f53fc309c0589b775b1e3014a0290209893a8667ec5bad3277a630dafcc808c6130f15ba32d6eb6e1d602c158b1742b27f3a457fd0fcce9e35285852e95f4ee03a38d2a35f22dc373be203d409cf6203f3c101e981da4c85095de8d350870808bd205c80e4cd2c6695152744746e000a203d39edc272e9a693979b409901c184c669cbea44f9c92d3859800c1814950a626e3649604c80dcca2a1e453935412e34beb08101b987cd47549bd2122406a60345d392ae2f465c38c5998b30435723b569aff8b03940c336461d02743f85c9272fee35818eee3427eedea9924570e16a69454a733794e9d6c25af3049a52da595e89e5e728529e9e813cec4d1d1ff5698a3c941ce0a830e9654f838d52a0aa344de94198219aa306f8c704b49508e8c5462462acca2f45e7233fdf1f28d0a635c1e939fe7bdd49ea730e7dc795ebae429cc3085496f3c898e6f3ae83a456146294c2b5f762979d0d326e50b33486112b6e2bd4926f63d8dca8419a3309fd6df136fd99817916086280c962678b02b55436190a77b726e5b6780c2b025d507bb4a6945ef199f30cdfaea49bba3126678c258529c8de8534bc25c3a61ec9c66fe472539612c93d3999d9accd884f1f3a4d325e73f659f9da10973d0a22b6891632e1fcec8844954ce93fede2af9563330618a7a29ec2a29a96bac19973096249a9cbac48c03332c6110536a556caba43cf38db5ba2ecca884e14e9245a99c1cc293eec61a258c9fb28488f00c539582579831092df6d59485dd7d79634d053972dcf82f121c777898210993563ec14c47bff99ddc584b2ccc8884b14f6e97c9ba97bbb51b6b9a8519903027bbacdc1f378c0c7471a3070e10f0f8e28b0670d105175c74c145175c74c14507b8f0c2025c182c7b16e098f10893388fe7a9c295d0254798435475f4306984e95258fc783b31c2641a4afb896a5a84e12429879095c4747593224ce2a893a5a4ed5fbea44498ba04d1d12b8ba5cd12224c3f2beb5a73a192ff87305ffb6f5ff8f26c4ade10a6933ed7cce70c4b4abe1066dbddafd00961d2259bbd9aac97d73f08e35bacfa2c5ed9d27d4118b5bfe3c4362957360984295b9fa4354499091d409864d57eae715123f53f18fbe5f35b38f18329cc98ea144b787637e983f2e9c44f5ae78369754b54c7cf905f267b3028d1d45ad6996d5d0fa670a12627cb6a9f4a7930ffed651bd3c172070fc650c25ea5136ea4cde80ea6f83d29683725ff630783c9fe4952c2bc89ea60b012feda4cde4437a183f9b379ced91b13730e86ed58aa552ad663480e06931dee47cd270e06179bd1d1f7818329740e5dcd929f3ce5bcc16816ad4b2c75ab273b6e306e89636299d8e91c3b6d30299d2a6d4fff89a5773618d72fab7686d6604a41fd25e135c694520d06cf27dfe7989f732e9e06b39e162555febc906da3c164a6e37ddb4d6ab867305acc11ef69b1194c65bfa394555c0693a0167e7efdc7aa64c960b832699ede8fc1a8e1a1543c137b161683d93deaa79417562b0c836144c6c9f3a01f66513098921c448b9fa42fd4492ed332f9a6174ce1a4742949c2ee67651198d1058389eeeb7aea1e6e4c2e98941274ce7ba51731d9822949b2de8c9d97be17b560b46e13659f72146d62164c2f976ae7748805f3f88a741da5a410fe158caef961452813f4cb56309bfa3c3988b754c16cf2aa7bd01f74955ba8601284d22ce14f65f959a660f63ae5bb15cc9396450a061396637a54151df45130893e39e253db45f907057318b9a46cf6e4948bde58d31d3d74cc7882e9f39338b9feb362ef04f34926949293a868166a8249c807bd7f8f11131e134c5a5af153f8c8d3a725982f871dcd12578241b428dde9533af1839260cc51befffe1a12661cc1a89ff4a9373997944c338269af57eb545ecf2a2ac20c229845652bb1f4d55e76660cc15c5749d2fa2cf729582798210483e72835e7a76f9607c19c3f66aa7c2ab14ec7308869f5734fed0b2f1998010453e6c70ef3a1baddaa193f306669acb8a8b785193e305ccbc91f4c1254d813ba32cce881e153af653d93848abbf11866f0c0eca54f74ff52e27b2ecdd841db97732ec5cbeb0f62860e0c2a0923643ddc35748e1a66e4c0a0a5b3b4c97eeb58611e61e0284b31ccc081f183123c2e4da8f324bf8019373007258eb8a4d366c3520f336c600ebb25e8ff14a17449961935a8be92c5cae5b6b8aa986d972425845597cab9ee111fb330558c9da9b62525d62f0bf3beddab27d98a854954ebff38a56e6943b03087998579f3245499965f6138a5654fa93ab9c21442a5529d964df0eb5b61f6a4f93a663aac305f926a956f4ff7dc6715a64a49f67049ebaa309a20df1545e54b29772a0cdaec8411d1964b135498e2851435ca3a3d3e770ab3fc5c298d1b614ad2dee0c314e6f82589775bbacbb7fb2885298a52a2783c71d59d15e158307c90422fe16a4608af20c001cd5118de5ce7cbdab47cc945f8108539ebc99964611f67a51b6b48848f50984559d0517773e145047cf0018a640b42c5a4ad061f9f30dc8bcbfb07190bf2d8f48441d5f3091726421b7c74c274b28d67cd4a55c1e4983130021b32f8e084498a22e6592e4e9620d4e36313e6d46996e4524934614ef5a2b449c289a5766ac147264c92a44d890b56264f9f30617ebd244d95a454742a3f2e61ca61cb4e4cbb242b7b2f2c618e76b6a762b78d6fe7a312793831b1bed2d1081f9430d7b9e87c4ca2dfdb010f08c287244ce2eb7fd4f6755c766eac45c2e46a7d2a5d7610427b481843e66d646ac9d69f8f30a79e92e7232a8e308a8ac7779c3c3da3b3d38f4618475d32db52f139fd8c3008a1aeac047949ce86f7f8e293ac8f453c762a5879d5c6819111b4e37c28c2d4b36a51cff7c2051f893076f6ac5fc2cfc50f2644a4a64aadf9ac7f08c35e6ae8eac9318429e5a9d28f6a7af5c40b61f0120d1d425335c44c0893ae8bddbe214e7f7e0761cab0685b9757419854a9bacd0fd283f804c258f2e7dcef7f3eae2520cc5a692d895242ce34fc8349c35498102768bda7f8c19c4e9f98eb950ab90f9ce09b978245f960fa647a92be31a13dab8f3d98aea41322d43ecb5b25820f3d986499689de57249723ce5c1785f3a765b8a07934ea62e7e9f767c2cddc15882a84ba9e349924ef2763065aece6dc5ab0e7fa78591a704e960505763628f5978bd2473308bce54591b4b62c9cbc12475fe939116dd52f4c4c1fc712b7c47975b2d693898848912bbe4a094eae70dc69273ceffc17583c973aedb0673f68868f37842ebb26c307bac5ad07d274dd2ea1a4c263394a0e40d4b494ad5600c2b71594b29d360160b3fb3d6a964ce8906f39ae0ebf7397476129ec164f154577615cd60f2d3df95fe1555d5520673f80911efc96fad2783d1afcff7e45412ecba31983a49ad9347c5c4603cb13b09b2724aa724a98f3018b4949c15f79274e9a43ec06010b24d92f4288ff1b13ebe60aaf03f13b55621cdfcf082f1462f4917562262841f5db0f32d9d1244841c7d70c120674d49a7d5f3b1850f2d184c8bd6fe08599694fac65aa5171f594055b21384b8861f582829f95d9218da840e5df1710501c0e1c30ac62b4950fe3dff258f4915cc21eebf43dbdb8c2e3fa8601e8b9ef228d7b9101fe9c8d2aa8c8c8c8c7c48c1141e3ba8da796d0b1ebbf1110573b85f2549ff9c3e69d403bd0e1e614cc0033a72ec78c004c0c0e1012eba18194923231d003c7c40c1145ec3d6cb2c6c49d7138cfe7a1b2257973c7c38c174e56749ad5efd95bc09a6135ac4ce274b3a87ff860f26986fdd4725afa425983bcbb68925df12f6cd63c387128cfb497692ddc6d2c81e868f249847ad95fc795b25e79c0f24184e54ee5e5e4f316b7179e1e308e6703b72df7a4e589e8d6012ade7a42ff1a1ba6d114c3a87e7168fb29ebd2782a994e47e2569add41d22c3c7100c3afdda4d4eff023e8460f491f1aeb9b237a18f20984c1a65779f2ee98fa21b6b363ec630a718f1144ca5367a8801868d17e30b6701aee00308a60e5a4a735ede04d9ef482c8c8f1f186d77dff2b24a103df9c01c47fc69dac7d335b1eea307a6aca5bd83182162fa7df1c103c37c12a4e89f6cfa7ada81792d58ea2dc13ea5953a309aaa3da5842969ae73fec88141698f164baa903f4a0a0e4c92beca9594328d3b49c1c70d4c95848f5269b6f2eee48ae3c306e6d1175ae7943abb777c8f30fea3064631214f49c62c0c62cc73caa78559c6326461e5566bb5b23beba4b3cb1bc88885e194eefc365a6e42c65dc0c294a44ccf12d2debb3caf30c75a986b3f2d29a85f09325c61ee6c32bad48af549791ec56cd81b64b42265957c4cc89e65b0c224e59c748b5d5a85d1e74aa7acceacd8380d3254611a1d259576cfd174e5d42023158693843fc1724d32cd1c1526619428e26f25595257175c2445c6294cb2fc56fa14ce4a1eb1294c72b89b13b29e5218fed394a0c34e3c49a96f0c3248618ae127bf2a3d2e099f2c838c5198d46de459c969649a470c3244614a41b5e7d1aac9202314269d3bbf8f6a8ac5b09430c8008549db96d0963f5782e73e81cc9530299db41d166478c228bfb116a45b4aa77327cc96236eab64ed2479f20a323861502dddd0273e1d90b10993597ab6123b685f90a109b3473771b45fdda4188d212313862b29cbaa8dc9271592810993b014aecb2479a2c9a1e26105199730981c6c2ee5702ad92779820c4b984aca71be921cc3851716e0c209322a61ecd5502a7fe625d926106450c224fa5b8a1a53a71d6b0f644cc2a46372ef92998e79e507322461b2d6522a4cb6b8258b91309c257d73d254af9a0512e6ff92544ebab2ee584ec623cce5ba772167b37c6c154386238cf21f261a4a102d996534c2fca79ff43d946c8a45062344642cc20617321471001989281988c891c2c061e3108630808c42144006216c6c216310360820431001677f05739f10ebaadc0ac6d0e14985535d3ac5ab60ac1494d88fa7debd242a98ae62e26a25a71ef6f51ed3ecb46fa52c9982c9b4f3bf87c96a3a893d3032d2041129984d2841c5bbfb8c8cd8108982a94d49e3693584eadba0608e339121c54cbeabbdfc80c8130cfe97e65bfd47b647224e305b87ad4b9f633b728481021ca502912698a49cb24d251ddf584b39dec68e1c664c30c8be9d11f2c6e2c39989ff0e1c115982399ba4bb7df9bbb1e682bfb13b449460fc2443569242aec76a1b874812cca663ebd76c6899664305ff3a8a85218204c37d50a26bcf99d496bfb116460e1ccc851711f0e7c0231c5f74606424f1df7143e408e60a234627ad232da751c40886d37e3adaff751bba4811f02c4d0b5e6ba9bb2b45fb4b5196b3a84488604a5994ead579773c8f1dae0107880cc124dfc92b33f3e4c021220473524ae777132b27a973448260b88bcb392a3fe7578a8c613efd36fa2bde39db2e0f8800c15842e659dcb416e922084020f203936ce962d7841902111f18744f7ff8fb8f480fcca12eb5c81bb5bf0fcf12111e98d5939cdab3fa2a7030ca52640745cb1563b92dad659c9696b824446f81880e4c97e6267b4a5eb27b7a04223930e95222549ab897b29d88e0c05862b2e99838499f2447e406a62edf5f99ebc65a0e1c77223630785ffef8a25c4f1e934b20520363b7e99c9c3b612bc7b3302931b91ff50439a3721084c8c29cbae45756433b7ae808898549458bd939b124cffc21b03005135d7527a558a9b20c7c1819f022e415a6b10de559fb59ad67b3e80aa36915ed222eac3f7d1e286d85f994d0b98228b183568815a68f26dd4267664689e304425661faf28ef2a65ddee3892a4cb9c6acb36a6a66ec21a930da570e4a8975ddfb16820ac39be8629e3a9f079d3de414c614b1265c52fe96045f8c2f7e878ee5105398a2aa496baea56eb4fb3b6cb4e0c350734048294cc9d25ed26b72b3647ca3876b006ff4701df8821e8721a428a865b14b25a26ad1ea8498f3468f1e17320a532515f5f38963366eb68e105198fecdc3c99025896e955018c4defcdad2faa26ba030e89347654ba5599e643f61d493c774f85d38415bd45005219e3087fb12447de4b373c96274c254ba4edcab19eb929f2f72e438f9450827cc3a62c289bca472650ad984d94f4811a6d77bb37e88268cb525eb988e673a08252199c02ac32ca568053579b360167489dd9dde583076e480c0c808183b72a491114c187d3baf2429f32d991d7209a3c996ee3446b7c92dd991c346183b7e6dc76320c412065d59520993d56e282954107329943025692ea9a7123c6dcd8987183b6cf0c0c1c6e30b1c3dc45040c8244cb976744d948ae3264eff00168448c218affa9d4df62fe8512261d44ea1548e21df2dbf219030cb8d5a353739a5b7e811063ddd9722ee3ac258b7177c745f8d30f5870e93ebeb9b9732e2ca6a51116d919157ff96dea41d2b10b208639570eae2afe71c7a12a208d3a713d62ceca41abd4312614ee2dcc6e83071dbc38830b527f1477d0eb282f810a612848d65d798d452430c614e920aea7399a49b5f09298469c4595cb8ac5bad3f218cf7e123dd838c594e4306616e33af545d2782309f94c2a4fc39550aaa05c234ba5df7efed1040184464a83caa3ec35d2884fcc1e05b9552a8c8b8c7de0fa6adcfad32d2b4bd72fa80f260d266b33c3e70e13bc46ec6d3525a6c5b123bb4a42fe18413dc83f90459f249b2f36a9b5a0fa650d1c30975af932625240f26497a79ba5cd686e1210dabeb8acdeeb6db10314b27bcc985dcc17c622689fb4e364a12640763e72ca34bf24afa47bb6387183d9c075e8f3070e4d0809a5909a9837184feb79c25dc65081b10420783fa25bd375326762947468a1b42e660963fdb334bf6319e0a91832947b4c57a4ed25f5838bcd041481c8c66dba2d4c8217030676875e76acfce2987bcc1d4ede983c9d1a5da65881b8c7eba6f8349d41165f51743588942d8603c6d953d9d4711733d640da6ddb993b725d3cdb4216aa8bd4a99ac9cf2693068bd85ce9d54d090b59c776b76a97bbc4c4f4185b3245a7f06d3c5d52db76f11f9935a4288194c972c575cacb48e4a3d21a40c26498eedd6d64912c755288490c1a0b27ce951dfae103206c3c5acdeecbebc1674c46012a4ac59387d82cbf7216130eae8d918edb98783b123070442c0600e7252dd85a695678b0c215f30c8dd2c295e43a950a71c215e305df42db73ce37e5dfa2447171787902e186dd4be648f6795a6358470c1d8263a9bb86de20521c2a1c36d80c1c35bc01708d982b963badc687b215a30e892952b9ef09ddff32159308bec51275d968da71f0bcb5c682519bbcb343f8b5d521245d5aa21573049aa93e5cbf9b98fde18215630a70ee96739e43676e478142c0542aa608e2794f4b3e90dd4e393599a3e20840ac67693234e922f49e5790a2661ebf29d9fbffa0481112205f3ec8f8972e249148ca65e4eab253f91270e052e9514ab2d77439e605249688bee7bcee7db09e6fd6fd75dedf63c7713ccda6d5296a928134ca3bb22ee2a85127e2ec114de412bbdfecf5b9460fc9c7b5b94549366c324982495b2ac3d6486492a483065fb1247a72e8f60d23fbaaa44f38c60cab1db39a9f678f2b5341d7e810e1c03428a6036c93b3c9849fad28b37d6c68e1e3a2ac70e0d2182b154b5d4e554a6e3ff37f67608e9e7a8c7056f6c88108cdbbd21b446bb09e3b9b1a681902018f475ce1eeace45c50b19c3a4833c49c8ee6d080182f9b29f7e9aecf0217f4e43c80fcc317ae4c36f7887ff7808f181299bac6b52271bf55b8e14068e7c1e5908e981b904e9df39e8f026c678634746b8e86287183cfc77b80672783132c2a384f0c05821d6d62b45ab47bcb156bc9a58ceca9a054085901d74625642694b6e000411a20383ea507274181d3a28495e08c981d12ddfc4ed11eb69fcd343080ecc9a15579dfb67afe3b521e406c6fefebefd94d57e331b426c9020c7eefcadee901a982d967b7689fa7267cec2a4e4b09ce22d8e0ed3591a4064611246a9d8d14a2e1646f7a0e2a5a79c67628201041626a5bce4ac43fe0a93a7b998186fadbb557904882b8c7db26a27e95f274db408905698e693d2d329dbf939655961b69347ad3f6a2939959191b57220ab30873c256225a97b3453ed00a20a93a7f56529fd01498539b6ecfc24299cd0a982a0c2e01a5a4eca79f6794ba73089d9a2528d97767edc1426e5ed6325c92e4829cc15c6d4d3275971b4986ba430053d693ea5c70b67423e808cc2244e4a9dd60495b3e64487182ac891e38bba448088c2dca946da8e4977284c175407f1642335105018c653bc98d8ed49a9928d93d8e0e16a125041807cc2249d4e71697242c513f584b9af64934f55c98a4ee313403a612cb1e23a763cadda3127cc965644949cf674297f1326492c254c9ab9f8bddf76014413e60a15fe29494987f44e264c62fe88fab2980b2098309997b264a25916794a2e61bcd1376d4a1eadd6dd1266cfed24bdfdcb4cda209530052556afa35c6eacf5e0214662030561e0e03b20943086d9d9e88a86e9976c804cc29cb3b3764e529cd534f9008824cc71f7837eb2ea8d9900120953caa6cce819fdb29730c468278040c278b2a9505a3da8ebeb767416401e6116932e4a8de5060fff8207fe17c78f0a208e300513b6aec294387a4deb4698d3881139a1be3c662cc78517110823033632c045175c8c8cfc01841166d32b7e1ef4c995c4380059842943f3548a182501441126e983106fc27d9044980451cb779e2fbb9520104498f2aec8dd0edae4f432c821cc7349ac646aa10d8018c220fce4cad7b983d2a2df58b39123292b002984f974e86c8ec4728010c254b93d88134429417f04320893e8132cabd2c273ef37d6b46d0c104198ad2d5df29354f509da8d3d13a353001208e305dd3793269f0eb1978204104098d278a71de9a184aae9c6da1707071223f91d572c004900f983e9632f2b6f95f8962fa0cb207e30e91c7a7d943aefcf721f0ce274a720554fec2e9f0fc692929eb45392f0963d7b3099e78c38d1574af7a207e3eee893946ff8d6c9c983498afe49c97a69391fc383d1f3dd4d4bc531312c2602c81d4cf153653ea57630c813333a45094f01a40ee619119f2e3f423c3bf4012074307c12da23dc4da7550bc81c8c59d2981236f31080c8c1545ad49a6caf6500240ec692547c92848e593aa300040e26614fd2f9c4127c03881b4cca84b1581353b3deb100a40de65c1115d409cba23ede580361c3b225e6dec99ad5dc6e0d06dd16cc443dedc61a8f1e613c0b5e0de653aa638af8c9fe4abab16138ba2f40d26050d14deceddb5e992068302849a81059728a9e1440ce60724f1f6f2775b525291340cc604a7766da6b4794ec9232183e8dc99bb926194cda9e1a4ab8570ffe3501640ce6149d92787d8ff73989c1a0abc3bd097b3b337e180c167bd771deaf55163018e44f0927dd096f0b1b1440be604a1663f7fe615fbe9b000b70f470143c064646788c8ca8174ca1ea2ceb9fbe32696200200448176cb57659cb955cb632d64d8b86fa881d51d2e37924174c29dc8285ba78ffb903b205939c46f4758f5e04102d9844fd5252a5ac1040b2609eb34b1f3e0825afc3110fb61dcfa32400040be650324b29af0df926545730eb9607cfdebe9d006205732c7b8fa3e2e4588956c16c42e82487f1cb9cf5a860d2134368d1bc17d3865d1440a660b61dfd9632da3fd7470a2649256579b29f242b692323234b008982f17c4f67a5948453bd87918395000205839fe53476a98204902718762fb56999a89d4ac70926af534aa99e2749e90f08204d3057fe16a172987c41ed06594449e95269a76909e624099eeea2f74a308c4ebd5cbad7e2273d0946939763f5b47d8a224582594c7e5f139450728ae7114c95d28f952468cfa5a3110c425fda5f873f299ab4c05ec70bbcd8f13a74f80e108014c1242c4695b0fa8960b2f8163ae81d7d6715031982499789f6ab7493066753e2f556e712861365c27b548e250cae273cee291b1f55c2aca2eabff6235f4a98b46b95124595096a9a84b973749489239fda4bc2d8a654d0eb3da3a34818d59296fbedb19e134818f67fc32a478f308c6b49296ec711e6fb2e71b3d7bbc2de08e36831b98e39230c9667dd73bd5ffa7911e624a26e2cf4aa8558110695a34d84295af8b0539267cb1711c6eae49e3f9d49c9a3873099503184b93f4f7fd5f6897716c2a055dea255876a7e4e0873fc78e223b7419847889894b1a9159a0bc2bc17e71f57594abe3c1046f71274948713f2a41c10260f7372c2e8a4e795ffc1949684bc4b5e4ad7c90fe6dcdf124c7d30becc48112b415d7b3e1844f474527a3db7a13d18cfe3e9b47692e4b7ebc12497a453eedc793028315da9bbc583b1c3e5143c5b0aeee51d8cdd5572954def743b984bcee59ca4eb90eed292e4b8163a984aae20ccc373307e56d89dff3ac19383298b12e476767130b877578589351c4cc992095b637a8341e60915740825c83d7183495ec57bdc4f5ea70d461da57496306283f9bf3e852579a5a4a035185d7764baae06d3c73e215a3d9f58a7c19c74256194fef4aba2c19421d6847491afe09ec1a0cb3d2e9f6a067318bd96c120d672924f4906536d96c72531ca643906e329792d053db93a490c86331d97b3e55f5282613025134df4d2ab3d4a6030ee09bfa33fe50ba6a03f6fd42bec0573ce7725b92c51f771174c7a7e65ea72cc05e3ca5f09a624d9a2690b661346a8bd20e6cea30593c748b94bff12200b4611a3e4ca9b5141c9590260c1f09e3f760a5a54c875097005e3c99eb763a524762a9700563088cb315e4b5756112e01aa6052f2a7257d10ed1f5c0250c17ca3928e25c52998c48dc8bfa44cb0b448c1b855e6497d4d2c6e5130c8102252655b28983e548c7af32cfaa97d824990e15dea5a2798538b2839bc75ab474d30676b298f6e72f7e930c1749ab9713a5509ed598251f674bf747ec9bf2bc1344a502322f3249873b47652c2b9085b916092043bb165c92398c62ac927c5ad890a1ac13426e73eed249bf4952298b25d5cf2ccf9d02182490e25bdedf39ebc0fc124732927564c8560124aa5937d9706c19c7e634965b3132ca563184fce253bfb57d229090826b9c40df127ff03d3655fab65a96c9cf8c0e81ebff916da9d3c3d307792479e1e3dbd0f0f8c27bacae43cbcdf3b30fd8c9e9a103ad6a90383550c692667cfff790eccbef7395a4549ca6d1c9863dffba4fef873f30d4ca2e49c092588f2d2dac028a3d6c3c431095003d3c92aaa355fa12fe62cccdb2eaaeee48ba3c49485b9c416fd979e54eccb587c3a7ea2a25ec2c2f0997e2749727a452d5f61d6dcb0d16e62546ae90a53a73ef1b19c93fa2c5b61d8b83c395e3e9530b1c21ce572f34f2e9d975661d026e79c040bd9264baa3005db3e39374ecce45361fe24a592f56cb74c1e15e6b13bd52f4a4e61f6b4352aa89343f7c814462bb1949694b4b35f0a53eaa037ae24d182dea430e8bef42729e9b6247914c6ee0b91e7d99624456110d6663925c9e52c0d85392cf97fc983c270e29f53dcfc84b9e4537f6582e809f3f898f01ca62b870a76c294173bba99ca39616e534f49be3be568da84f14e3229fc45bb60bf26cc51fcbe2b77097ace84594ef47bcfe182870813c68ba6a3f4862e61923a794c4b492741da9630255149d6ca73a984a9f3b2c509951206b9a56a32b6f1b5964918acde4428e964774d8b24ccfbb3a6564aca5dc21209a3a67ae5fed8d249ac40c2a463a9b0a99f3bea551e61324ba26ccf8c0c7d7284e9e43eeb3f498d3025395faaf7645225498c30e7fcdb61393a62a74598aeedb692a7cf2f5f1126311f3a4f5cb30a2689309f20444d7810613849b01372b65de44398e410522ec799ced70d61ac647771c296143e2f84f1bb93bd8d9bd69e102625275e4e3949a2a93d0883fc757bd17ea1c45a10e693cebf732910e62d191d6374efca803048ab54de19ffc1204c7bcfe6ec07e35b6793322d874ed23e98942074484f5ae225ca07f305134dfd99fba8f5f660524a5ebeaaa02f96e4e9c13857ee61dc7c2d9b9707b3c97f8cbc4e512d1e1eca77db9ea478b93b182c47d653b414e62d6707530ecadf4e1a9dfd52ae0ee6720b2727cb8ff9ddd1c114afa717b7b34d6d37078309fa6349e969634a7230a8279362af6349618a83d9ec35d405a582de1f0e06f9b57895a652ccff0d26957b3798533bbd49523cf5f26983f1c2add3d4c7099fd9604efea12b672af4c26b30c9ff247abf5992f2558361df4344fc7cec5dd360925316a1435648cdd0d060124bb276bff8d77db13318773b8849659a152b66065376a52cc1bd6afdb33298333de5589b27f19491c1f463a2a2631a8331dc554b9820ade45e0c26a5f63b07151354e584c19ce666b782f8146983c1741f4c053d41a81cc45f309a3c1391117bc1a04e96a96cea7207b70b261b93c2cde3cf8ecb057309f179e9cc2d98925857f59df446452d98d54cd4926a47fda964c160b163c94f926bdc5830c936965353abba725730e6b95bb2a459c134a7f4c8b9bab4a882398c2a73fff7cfe96c2da86012964a05a1b7f5a38f821d39d215bb9e8249a79cabb53df24a682ce0d2420a460beaa4b7f6604aa84e6a1105e3e59ed51d9bd1ab7626144c2727952c27b1b5dc73bc83164f30982c6a4b5292ce0476e0b0a183079a164e30fd9b54aad256d34dd4a209c637152b2f871e69e133c194639b94f2a3a55c527c09c64caf4bd91e4709796ba104e38b50c2a696a9f9bd5a24c1246a253f29d16f2a5d012d906050a2f849a2a5b1b04b41010a727cb1468b2318f54b3ca54f5fe2846b0473ca8abe3aa3faf7a2695104c39bb2aeb95c292ce570ece8814430272ff91bd7ab79920cc13cfa94668ede8f757808c15452a894515addf5c62098e276746f4ce5012dc630cf9e5459675f4c4e1d1168010493a03a98fcc9414649262dbcbc1771f9964c40a0850f0c9b55ea3f8de8fdd0045af4c0942d79925b44a81affbe10c3d327761fd08207de58f820a6f4a2dea7c50e4a1554d45cb5cd5cde3e080fb3d1f5e9f2480b1d98feee64298bcbe13ea5055ae4c024ab5f8af331e6b39ae80063022f70000f1eae81179c91112968810353093a5d05abe495463b012d6e600e656b3a22325bc414d0c206a6d295d2a8e9b8f9af37d6344f8b1a185bc3628decd0376b2f20320b93345b2d2a945816e6afdc0e25fca5bc7537d6d4626132ddf3b911eb8d352cb0307577a8a0634bca13340d88bcc2dc6146778f7ed84bd7bc10718531c3538cbbd9c8eb78b4c2f4297518115ac276fcaca8639a6ad74c6b1506956a7262d9aac274d2fa563f5c4a564985b143e7a06ea63422a830f9989cbf2c652961de53984c9295b48954f7d114c6b28f3dc9e2adca844a61d0669e7354921446cfa95356ea9d922e1e85414cba524ad4eb8b2f7054606444144918385a1086180310094515dc52b66a9c6a89d2cc13a9b1dae18d4d5098838ab65fa2e534d3ed8d3530f413e6b9ff140b6129eca80977e4e8e1ed0983a56849a8674b93923203229d306953e23dd5898e2eda1e6150408413a67f319517546eacb50d1b7a88070e3030070e1b3a2420b20973ae92c2c4981b939f4413a6f794737cc424e1e45b2413a68e15930f8dffee24983085cd13ad3a86888ded1266bd5dd5519e620993ca6a9212d56f515555c2248eee9408254c96aea533c2f3fd3c38524231bef00f4c0002101819611b273209639d99ca9053119184415f32cfa7a3130945ac2cdb5a7837cb9eaa83992a49981a12a94e5972e5237aeb9637b535f7101f9e2e4e96cb6e7284b15c740eaa7230f5aa34c22499c976ffb5122ba54518610c8f7fb28abef440641126cb8aa6d749ad2a294f11a6936a42d4091e914498e5b24949a98f69e51e220ce292a4abaaeba9ac133984493eb7aec52c6208739a2408bfeaac10264167d577ed049ddb09610a7329424cc961b4874406d155ec4ad9e5c25db2738d5356e924a5b27fc84e441026f736d1d42df9858dd79103f1280601914098de3f4d4af750d3a1ae0d4400615253134a3f45f6ab6fc3fe603691de62f1a48ee7a61f8cda7f23da5bc3aca47d30a8ad7b9664f17fe773f2e0c10810e183a9c492f510a26e827664c447465ec0c9a3acc81e4c31e4549ad45a4f52af0773d2e9f3ad3f546e4bf2602e41e8122709e3a114376e6944aebcaa3e9bba8af9398b3c11b983f1c5b4fc9b16d9c1a0544d98b8fca3c29c88d4c16432f4e7e9fd5d559e0eb527e1f7f5d79c83d994ca25e9ac7e9f931c113998521227eebcb7d47e0ae340240e48ed3651affbf43b6a200207a376fa5e8e75efa711478e0f2818226f30deaae5575e69ed9f46467e870d738371b64f95888c9e1ee10e317af8dab0a481481b4c32fb4c0955fffed96783f1e5f73ac70771bedf0244d660309d2415311594aceeabc1a424e5213ce9a9a0e4246930ea4921722789dbbdaf081a4ae144d5e2de64b62c58ecb32bd9ffa593af4c103983393f8f08b516f5ef771e43c40ca60efbe9e5c2f9450746466c189b48194c499077a7c2a78890c13c2a3bf5bada85b057640c7cc82ea5ca7766433198955d9ba22da39da2ae9597af2be524fb261206939cced478929388488fd9ae0818cc1d4ee44fbb76daeabf70b85592d1f06cb7cd0ab7acbadbfd9de30583dde9dc49857e638d053892f280e6b3460c8874c1248b3a41ace752f2af04c7d9f11a181919592e18bbe48efce8fc16cc9a7d22ecd7440ba64f3773a6c453240b26254dbd774b49d13f61c17c5e197b09dfc5e5164c309a88ee0e25ac7b3c89a16db10473d0a1543cc94988efca164a3096aaf91d2bd5fbf4f304b64882b192b426e5b3ac0d1d663cb64082498518212ee8d61def7b38c2f1051f0f33c11647306d65e8f07d0bcba6c5b185114ca3de4486aabfcb7ec9346c5104b3081d6c4e50416c5d4c0483e9d4a3e63dd44cc96e3104537ffca56fe5122e3d5b08c1a0cb4eff89e7a2608b2098a458a6ed7297b7c518e6b027c90fb7b86d49eab60082418afeab1c7a52e91d0c5476646064440718688b1f18d52be9dfa5a4cc92680b1faca3fa74b6521d6ed1032b49efafa02f9eced2630b1e98553be7c839aff80f7fb1c50ecca3046d2ad23ecba5930e4ca2cdd3044dbf8fede7c0f0e67134e4dd87f20f8c2d7060d5097a72c95bfd496e908758aa59cd2e4fb5da2ce1d2f4c893f77c415bd8c0d8657263cd2d4d78708b1a18ec4b9e771fd92c4c4a1215dd3eb2308d5e93b3c79f7c178f85a9b25befbcc7c9ddc2c258723f7dd495ab1c7c85b9c5a42054ee922b4c1dc4ec36f4ad3025e93c69c5b0f01d6285298ca51c66c4c43ac15f854185d928ede1a47427aa30ca78b6ad7a52917c25392a8c7d25c9351f2fe3fb29cc5e4a3871c2c9f1ed3685b164b4e73f25e7332d8561c583f40a66420a937e3c1df6d6e446988ce29cc2564ce52d1185c1e7a3f98a8cb0b425a130a831a555447ecbfd0814261d47871a258bbf9da04f984b7c0c9594f2d8f3204f983e67abeab7bea02ca81346b9cd1a6da364cdf570c22465c8cc2d154c3f3d9b30955f90a224c9543ee5d184398838dda524a9ffe49e09e3a914b3a2a7f194b363c2bc614aea50b25fc2f029f4fa2bff2c4cb78439e8b2cdddbf9224cd2b61ce2157a7a4341d936f4a184409d5d16f84d60af7244c65fe153d9a92305c36933fe72444ebd89130ae2949b0ed24eea609240cbaa269ef2ab93d7d1e614a19a6bf6fa684561d616ab392dfcbb846234c6f732f4adc2efd8b6b3002f7cb9ded61b28b306ca7dad7aa34fd41ab08f3a951ed149ef246b4893087c8cd95b5c969438b0893d62dedd293848f760f61b41365f4feb5a9a46408e3ad594ea2c5919e4c5a08b3af7df85c254298f3493a7a124c5dfa3c0873b6a493521562395d1584c1f2469fd2d12ba8520361f630f964941c4bc2a580307e3cb169e1d289eafcc17cc2f68349b02e49990e97d3b57d302925099ab3ff7be283d16ece833c6d0f66534289f769a51e8c5ff2a8d2abe7c16841e84be27b58030f068b539327f4ccc553a971079378e292bc98dac13c9e937af749d69bd7c1a0a579a146bbe7f8d2c19c2ca9909e5e2184720ec6cb934ddf6a793eb1c8c1a02da72e5c4a8983e93cb7b429d16eb352e060f6ec29c50eaa4545f90dc6bc35eb90f7dc60be20ef4d450f963e776d30592841a9895f524e756cc0465f87cd750d6693c64bb83f7923be1acc9a1a5b275410d79d34984ae9d913448d06837f569244a7cf60f4fc27dceae5203d663079f4fecedd1f457b65305bf4b80a42c7c890f6f224ab7d1a837994b8f77e95df495e0ca68b3d517ff2ad9b6c188c268a2c3176844e520483513e668af6f0af1eab1a5f307b2a31840a9f638adc060f1cada586173c25091b9696f64537d6ba60858659c8d85a560a198b274d724b57599f1a7a5a2cfda9b1857cbe2f8939270c841a5a30979cb6c49e5b146d6e8d2c587bf22e8e90d3352a83a00616188b9e43283927cd99dc8e1e3a4a8d2b7026f2355eb65e15bebb3e4f924e122a6d05d5b082b192244ba7689dacae43831a5530e9c9bf78a547fbb64b85928a8c5c58d52cd336e22c75700a26f3383a8923e5c45a4e0d2918ad476cba8e4a493eae1105738a1555b366aa010583cb9fb77a7a724bf20c6a3cc1a8b56556e174fc9f8e62d4708249a9b3f68e4b9752ca9b601425377646c84e4a434c58d4be6edeebee55bc4dfde3ce8399ce9eda976050fd71b5d48a71504309a65cda5e9dc47a51351fa146124ca53b95b925d94ef41e09e664237717fb118c17e4c8b854a2bcaa6204b389f136795b57e7264530497bbde125b48638a9183ad488d0d786ad5b5c0c0db7727db9245af05c29eaf121144b90a253b60df1a08610cc6eda7474c374ab28098249b787cc9f50d618c35cf23c69a5e67cd0e10f640d6a00c1e4b6e39515d7f88171b7dad4b9e724499279632df97f613a6af8c0ac945e3ed63a454bc4ce5259d8ec285946438d1e98538fecaea4ef7494588307e6648250bbf2a1d971bbb1f682ff1d3aea72a43026d05dd4d841fa3ba6724d906fc2a3860e4ab1cc2d5c760f57abb459f29635354d122b65811a39603dafc47e35b1060ef4d44a8b95ceb6ba3bdb45fd8b1895aff657a0c60d4c4ab8182ddabd83104a08d4b08149c8a9d396833239eb53a30606bfd8e9de7bd49c4567618aaf7b272bfe29c5278b442b7cb8575b8b7b76c7e6eddadb97190b83aaf78d533176ad2b3460612a414f4cab5d59a7188d57ece159a26959156bce344dd6d50ecacd74fe4ac31506133e0942ccbce91d198d56983dc756887f298f5ac2010d56187fab56cfb51e19c9018d5598d4bbb676f8e0c8c8a1a18afbf6241345341aa9308d9e56497d795498b73d7dce28bbcf291e9fc2d8399fa814dbcfb69d06942e6898c2785f291e967370d32506d028c5952d9e7562567154a80faf4961ce1663949c34f226422df8306cecc86183ad1806688cc2bcbf96a54f4e4a84bf61f80e1bae83870d1e8f82e46d24ff2a5c7811014fd01085395df68b3315a4438718a1304927474b1db36479dbef6880c2642a954a6752ee2abb697cc224ad4b8d877a3493d244d0f08459ec73f88cd90b3b3f8d4e98b3921cb4f559084f8b0627cc16a4e79c25da3b9ea018343661fcfb242febe69668cbd3d084a9b3cab2f288f3ab388d4c98a27a3a794b7212d71c05343061d2c13f3e9d6e695cc2a432fa3c851da5bd731a96306527bd3f2767777a874625f24b139e262abf8a6ac7a3000539be504aec155bb3b4ebc2b274beb5b0a675e9abd3988449ce32992697f99e9f48c2fc553a72cc3e31d088844958084f5fd245a5fc3b0d342061f6b8b31aba2e56ff7b84b13e5e9eb09c73b4958c8186234cdae2bda4f07d18388e36c2e821ebb425d3e901071a8c30a8a0b4a7ae5d9ebfca224c276f98ca3ea78451521a8a30579d18ae7d26a89394050634126130296ca8562991010d44983e9fecbbebdd925f9f1ec2f0774acad029c969b2c70b1a86309c875e25399af4141bc6804621cc997b494a319efcba27848d0cc2a4ca92dd459b86204c925792cfe27c9bde991b6904c25ca2cfe7113b03c26856f91b72f477c9db1fcc95823e3949a712362d3f18dda29f9a385b29d353028d3e982f5efc8a294a62ace0cd5c8c151815a0bfd1050d3e98a4bc9404e15e6288eef0a0b10783790a0fa1424e16f139a0ad07e3a77d1515ef68e4c1ecc93de71493fa35668f42030fc6f652a1fd434933e1c309d0b883e9f74c3e531d4ab6d2b183f9737e9172621d8cf2173f9dfeead271a14107839794c75487993a4ba2310793f2ad943f9616e64b5863d0908349e83593342eb807198d389862081911fa1e7a4144030e06cd3fa53faea4eb72c340e30dc6cf9d77fa4cb6afe4dd602c4175e50bbac2497ed206c3857a29f12b9a8c6934d86012b4e8142aa2a41af569acc1fc2a9e7436a19eb6c334d460f2ec27cca92a13748434d2603c31ff93ab8cfaf547030de6a0e3dbebe66f49f38980c6198c35faf6a7ffef3bb5663028a5cf2a899e3298d6b304595a3c1a64b85464ad2a657505ab571377d5dd63a53106c3d6efb867f9f4c92406537fbefdce1f46a55218ccf622742afdc160d8911dd12b61fde47cc11cccc35b2c49124ef78271fc42cd08333bb9bb60d029fb97aa34178c25859ce7a0fcc4932d98e763afdbcd490b26614bacab9cea66e49405c39bc9a54aa4090be61cbfcf26e5887a2e5dc158b1e460e69fdff296ac603e9d4aea0b262c7895aa60d22165fd3fdac72751c164ee9621469745fba760f0b1922194249e24289182a95bc73f5ca9ae0f8a8239097aee7a395030e949af68398927c6fe045392933611b264d1167582f92e7f4f9d9c2ca809c618ddf12be245a8c804532e39ba4edacd5ccb124c677216a5fe492c1d2598f693fa0e561edc724948fc9c4bfb930c09e637294e988e600a6a434ecf6be9db08a6ced1c2645a5f04731c911d4d594f04d385192588abf5b4a5219873bee87f39551ed90bc17472e5223e9ed49304c1602244940ef618e6b5df2aa144eb7b0682514ee7ccefaacd857f603a656e75dd715a4e1f98eb552b09a1df944af6c060f288bc553c413dc803d3685339c84b4b72971d9883ce2925935b3eaa4507e633b156cebb1c18f4b429f9ed2ec71e072629ef8e4e7da289d96e606ccb3da16103b38647fd738f0d39d1a88129db6e77633ec9273f0b830ea5a4ac75417bf8b230954a5ddb36aa4549120b5332495049868a156f020b73ccc9974f2a3dc9a15798d5bc2c7cbe913b39ae30bcba7b47cb2e497b5a619aff987e42d79dcfac30a67b8d6537e9deda5518d4cea2b9abaa30cde799c5f7283dca549852acd3dc7fcd8911a1c21c4bec1c4755e814e630a7e6cbc45bd6c9142649ff74be312985f13b9d247675f89c0e290c2674740b2ab59bfe8cc298233ede84c96f422d0ac3eee8cffb21cb461d0a7307a144cf6583c2d851ae7546cac9893f61aad2a3e4f0337692ea09c35f90a6d72e059dd44e98be747ab153c97c4c4e98f486da8adfa3a4f63661926387315d3dffcb3461ee1825e770aa93099338fd1dea6a4ff8eb60c2a4628925018eeea874af04a3c15028128703e260281408516e1b831308001840200cc642b1603812e47d0014800358282246322822281a1818180e8502512814088441c130180c080342a140301c12bb0ad2f2016848b3428832491f42bb2fbe443f836c6f388a97fb44ba39a4ba9079b375ebc6a044d80bd25828e26e995ca0aa60229e212449b256e891158a57fa72d1d969f8e3175b928b2c2a44b3928207817d90f030ad8ab7a403ded65987f16fb28eb4e4b3d0058d9b829a3bdccf550d519da8cbfc221cd67342923c1299ae1460b1e50907ef00734773a328dc872607afec2b71456e571eeee9630207e0a5c1f6d01fc35bf739e6966c202cc3c8777c35ed70d49d7511c13d3e78f500c7c870cf2388f979d82a55388adc73c59b72682a51e024407c60a67bb291ad741895a76d8ad002cad52e3d8e861f421dcd17387869b552bcc6614f5dfe93481ed85a833aa35a8f5a1b299e601799ed9408d8af83ff842df1335dbc1ca343189731d81f21e7285b49b30189a53c0022e4f89c1529bc1d0fe8a6ea1b3cd905a5aa6c01a75f0fc8719bf15a6e9abfccc0a3ff0178c5ac3514de0e7eda996309be42206b39f2e22e8ce1592beb0381b8a108d471be296838f511571b8b74b0bfb45b6f252d66479863a8f300187d1232dca063ca041b138cbed574354925482649e4e545c11f62485d39660e16c8564fc53d38434e81bf524132481f3f4bd4479d936cae2a459de280e9084fb00fd2c06a196b2c1373b7fc8c9dd959641cbd9911250b14c46ff2f65c81c0280fd526ad24476e948442932ed98fcc40889b961ce44736f472027132eb6037ec0d66e6427a6c779853f809eda120a32a5fa9c6cd9d9ae83c19d3d145676c945a29d6dad7af93dda453c4b438c8b047f797e73f488323ad76669e2a00cf8c1ad5a7bc3ba305314951b472396453be1cc1d3b9c14e31bd71a4779eefdd076dbcbeb0df3b295fa7c965d386850a54c656698a7e29ec8a5fb8b82ccd38183ca5ed0cb178b9c31afe0dba687c25747b4a394420bda38f7dcd83ebf8193c0e778d295c10716c455259c2cd0e4d4924a156a716a56128e31366aaa61cd96967d1ee88b87f4ad66d5608f741bcd68b8a1dd806b7be276a8247fff48cc168729e63915e46b47a4815df4a6424aaa2de412bef775510b43a0201b67bd93c5840b275a013ae83706511756ac58e7712b77eecc9e233140945e41549d1f672429a53dcebdbbf3a0e791f30cc85ce1901d526f5e265ea1c2bcc7a1826491638ea364099696e55993857ab714e493343132c732594504751382846861a6540548ad5aacedbd7250f8e822d142eeac0dfdd980a15a5158d09f1c842c6ae083c2b525e01336a555539d85230f55891b43e83b780642605bc4d763cc4c2561af05b8a0c85da829c2e563db60bcec7606712aa761209b18ccb542b1055101bce73e5be8e715f66df735fa5ef0ab0cc24755db9f9daae00a5e7a38050b4921a21b707b6575838e51a747b01653210e0d0206f402d96ffa6d964c745f65bc5c0de062a4122184c91852910b81252a90241bdfeaf6276c83469773de563b2ef05b908b6c08a1eba40f03dbb4f9de251c47569e37eb903482604ea5c404296fbfaaaa57bba152155327bc6334a1be7804d91a21eaf6deb071ddbb750907a96cc4aa87a75d8b4a1dae69c6bd621e15f9fb633b35052ace8234fe72c177d98b9ebc0cfb7dd74479d7b5cdd8582a8c6ddfb6ba1aa8a20e63fbc1846c93f1263236d47ed4620f68e353af1d5bc39c25c60dc8100add094aa924772f0e4bf8cdd126345ebb78aa53836bffc78efcf0d2040c754950b0d7c428370b4666b0098e67fffa9e161e6e2c336c123a0d84371660c37d19a5dd32894d9af5dc0ba49d97a7eea55b01b8d29aa182ed048ad163e40081c36c745170c7f43d4ec6df6568600272bc1b2295033e56e2f47418ffbaa5ab17b4a9169e039dc040f3d303041974c66af287b514a46a40b09b9d90c0bdb4bc3ac55c447c36cf880764dbe9b7398a1d6055c6e0fff0e863115448ea2f85bf612d8b593faba586afc2bacb2b88cfa3dc6d19a30562df79933235bc1c3ac4a4474953e0db1b9d4275101bd5c817917ba22be532192742611080828993a6a60c8599a4eea70d0c029f5182a72b7eef4bbe827aa907fa243ac1cc5e55b073813331b4a43439deb4b2fed2c5993c23c4f09b0c709ee1bf2e01f886a1c3620a62bfb2c36b6ca4547c8fb38422de3d4de18a9fd017f085c61a6aec9e1a028916262e2f87ba18951623412bfb04810c23bbe22bcb90296e2774cbf25d2f7f0607f9a2c7cc40e8716899bbe5a6ce51a9bdbeccbb66d1e51f0a96eb3c2956d6902d877cd03a78fa27d60117864c058508c035ee658a150ddac98b9eb2b16f81179ca2c4036a10b5a6f0c966d100c9194766537b57de17a954acdc25078b72291768a982303f9a45662f80e169f3da380367a9cb17c19e56eafe11178cb27a0700f2cde7ba43f86622e834b2162845fcfec72fbcbfd6997b56f5e6994c6ffc44c12bcc5bda69d304c732016665023213389713358c62abe874c9d0e610289405fa95639bf18babd5a34dac2a38ebc2612e74267238049b39676d11bba4b40a13cc0638388e9678842388f4bf16d5dfe83c879b964694e57d58632354b8eaf727516eddd08832a40a67af5d63391b1db35ee928b8b0972aab24181e4c9edf59095d6f82bed1b223f3e75ee90950a38be6f70750d3ed687f3050259603aef054aac2502505a95f8507bf5602330b8ea9f65ec0add421d1007f5d74d4878f53f162fab225fe6c607ee15b2efb00f8a684e3c4ee102f626da0ca559636fd44692bc5034eed7558f8fdb27c5f7debb52993b90f3923770571a5878f12fefad7b87cdd891d69f50caf09e5a1aed78cd40d1ad29b0624e241109c498d89e349c7d1d86e6c0e0b4709fd63b1da12c0e0c64af4fd8c7bb2c90367771f64d42a8451c3554511a2b398ccc05ddfbba243cdae129884e76c935a24b906ac9293db34b9e6f87e18b75c80b7dd769b1d828e7d4ccd230536043a9a91d74918b52048951d4498537a5f696ad3e9403a81a3df5f92c9fad6e3ba2e69282e7ee9d85a7e01c5e00d7f3ccd1b04439ee8f971599636d4a7332ca7728432891b4f2545c0297f3e43897a7f4b2bdc438ecdf4b4f1d3c57f67014d141fc7f4a16ac9b595176d63a74e5dffa23707764f4ba0f51f6ba0167e37d38386fc5d9f261872b35588260296926fa2288674c6cc4e36bf00e308ac9064f21b23f3bd55a84bfacc23f791d6258bc07ea2e3ca62ed186bd79aa5b04e6c0683f7a97380ffb57dd75962197d2276aae65b5425f921e4d0b265b4a8fa29609df87b46e8087dd053ffd67f44f091abaefbe1e36efe6ffeb18b784990a1933869e96ca20711198eb2249f9e2f98ccc3f159badfd929129b3af828a82093b97cf028c3a0b6c9632628ca66ce95749dd074c7962b4d169949e2f73ca69c46cceb629df1bed3cb3f0a74b3373e8c2ab3652957d9dc08dffa8901bbb8be95bc4fcefd74a5093175625f017d42646d1b8a9b0ba37139b6136212761148e7162c3c7b77239f2c70017795ba2cf9490c36416af8221242e6fb7ef7e3c607011fb1438ce75b5e0cc50db309ac0f9b94b661526752a9eb848ecbc0a7b56608cfa91ef72a52e599a173a855c87b36829c094e822459ed78bf792c6290444c4ea39ddf492a05382cc62d1ce07d5a32704bb169daa337db15ff26bf474f8fa9fe9741a1439f39c9ad94c19530c304b0a66a1528f28537b5be458d06a3a48cf9299d62f7f50117f194cc234c28bcc96803dfb49e994ee3efaaf2bd11f6d23e833732448b12812492c3b7b66184532c3d049bc0767f252a2d117ca3ee0a1322f19ebf5993ca9e86cf5228297fcb86c5c4654e83f7994cd8d3ef6612aef693b7e9b9194c995124682fc197743a0a44891a884f4956ce7fca2bfdc1fc4a2c27920ee770d5957c92d3db73be63819264d222972a094abcc4a4ecd4c71c9eef989bfff3f2c36250ba0f804dc3f67167df597a2f206efb359ceb76eca99135238a54cf14ff6b0cb86b15c26034e75f9e62f5bbf3088869802e823b65d3af1d99c0ef94ccaded6dc6ba263e7218936d12588423738e2c7aee57e706b5d6474f363d7d1f80ed1155eae6c9a7e014b34ca9043b46ba09de2c39b0f5262c92740cf467f236f475d44c8fbd3c00cc68b5d3ccb4de2b7e34d992f4256ebae5c1c6cf5776bb9be0c91dcf9db34f46d66ebaef9956b4e1ba11a1532fe92adf20bb5b029bc295b2f8989f6096cc026b6caa54dea0f85330e6d9ef83d0e128b49ae6b4f6865853d19590bad20ebd078c2f3c39c7c70bd418b876d8b8366058e5c92bc5b09f9087973af82afda1ca37955688d3a9cef9ae8fa8e7935c6f19e43afdf80b4d863c11f1adc2ade927e1e51329a6b42dd81335ad633228e9f6b4be17f5c33cb6c4cd9ff2fae1512850b729f6aef3adb6c0482ecb49af80c2f1760dd8783e40c229a91d8ad4b7c4365666949e6f5c801e019f01002466f25c42e7059c67b7be31d5ba32a4b5aa9b016311564f078627792e9f4739a79b5dab096c44e2066007a452c37d22a03e957092b581cd624f0b40eb153a032d228af5ea952da650cc44e202b424ce8420e3021b1b4c37bd802605dfbd95889a266db33290935708985082b69b0a463348dcf129dc4736545e466cced50ab22490392dfba6af135fd0a02fc059e283310b4f11f4d2e72761dd140ee938e19078508bd8456f25045f0fb5c1cbeda1fd9a26b3606c72fae248af66401be32964a6ee9981a75901408300841493b52c6d22d0c7848fa63094ae6d9121eabf042e62a44243eb27bfdc5859abedde06ba296a6444f13639382bdc38d2bb399d1682b511be5dc6fa2570b7527edb5f5f11d3e4150398ca8088db9cd6f0ec93f67221905b4c9e793766c4b11ea3a17e66e1e79ebffb55d8eb3751095924d290d3330a3b9caab0b14f2b50dfe925ce6f0a47b416f830b82d656cdb66fbe74774b2659797878257c99b2ae3c6227403de8627a171d2b8fd1b52c6ddff50f021fa4815bc14e13525114116a704abf726cc5eb25a0b6d610c449fa8c96130d31b8c94c6591e2c89e33f8c62e9f1d1b14fe9b059eb10acb5b489a84263796932f03bbc224c9fed11eb7be54aa17b345d28d61d8ac6ecef89539571ebdb85a070b9fd8c0a193704505bee8609cb554370b819086939a3dc2f7696035c07e07508018da183f496f5810a05eae4d7649e0fae36967b6d717277fe5fb72aa4a7d0829f502cadd12dad4b4e2cb176f1e218d38aabac80b52cfb1a0ef2a44d6ac08506ded602d72b02e56718f12d40ec360b35c448f2411d6a5f32169a94b3c35dc7968b5093a012e23901971e20131a182ddd9d91485c3aca3b520f0c8182cbbde522742076fd6fd2409544e2499728010d974c88f8ff55a8e706382188dac09f59940a4fea36416f71b35d803136b6c74481d71f3cc84220e7de78452ae817600d3b5fb066607f93c11a4eb7843d5c94cbb33465131ab0dfd27958088d240659d91101147ae1e7a00cd4b7d4220711c5a864e522424707c45f5753e5e35c6e7c5801c9ac8297c1197d7bb83f22d2f5a88a87e2d04365d3a45c7fa8332437688b61b6c9a47dfb9eaa6f183bf62e86cccef9cfdec2c23d2026ae805df7134ecf048388871f44e664fcd564915c8da8dd1522da8473e290acd4b523324c712378c709f65cdb74cfd85a3805433c0f236016891f6844569dfcf9ee1e3ed678585256d19b96ce42bb98ccc72f3a7df7efa0264e6df3b0030fce270a21d237e90b34ee019526ff09848b1b063d3c6dcfa489ffac83b9fc9651b84c4ebc5b2fab1ec2c4018bc2a94d953085b694bf991af0d7240795ee8f475f6e22669ed45326ee6665fcf680cbfb1d3f8d3a8fea033b21c529c6f1b23239e3dda8b383b07325fcf57095245a584a72f255058dac238b45526fcfecf1c5a7cf803f30f371845697b9f04f241dc9cdfa7f88e0d05b798205c9934c4906ee218df5b312dbae9a4bd010fdd7acc14339b6f84d90ae00b7d5186f16a74e570be2eba3093cce7723254ac7754f6c64d441e7fffce8ae22176ccdf60cb9dfe8b67f7a27a8eed94cd138ad647c5fd22627a45755a40628dd00810a4653c0da1daf234a398e104db9203694fe54acbce84069261522120fce19e89ca55ab23f04fc20efb23c1bec6dd2cdee25790883e605ac258d14dfceca2434ac1ac8a1cf85ab045e132d606189b54112d2f5ffb0169c8e85fe454646e97ec685425f407780850327ecfad403990acd56c100c87cd11f0dabd50de54ad80d8057df5f10b983a3bc56d07db284f44f2066e5659c51f398563485cc1bba1edbb6cc9247f7f35110c02080ee0a0d453448956a7aa4e1f0ac36a24955f53dd097dda1d4da3d0681feddc719e074ddb9095d54829e2940e13df0c7a82ff8a3a1df65db7a921f454453773308bf96cf5a1487a203906424636c05e8f2761a42e640295302e63ddc1179ccc95e5ef5711309cc136ce6c513b81d1bd83ae82043543e6c64407376d83810a7265374c623d370319984a745b23d2cabf47f719653aff1996a81b81c851f0d45ce8472b98cde40ac68dc003a22ea88a1acd19ddea468aa8abaec09349f392d48f5f5c472d69ca5f33da11bee5fbd31e04cbd8eda06cd5064da69f35ea40f3bf501917a8c5998aa7a38ee61864a4d00f877331a79142babac4de2e3f73355541074d85fba2ee158876e0be8c7d4bccc2e1c9df96bc307c6fbb15bdfc0f1b712023465c1b797598a14a417946cc830e99d7670b0c6464d5717c8deac9ac12cc2965a632ea7139e0c226f305d24b1847a76a0c4a78c07824f10baa49a628ca554b7f105eb9817a981436db5d422ff66fcaadf1ccc11331029dee611a420b54a00cc86c10ff4643c8d8560a69e183d501b1f6a4162d074fa1fd19f06a106dfc4b56454ede16d162d49e3a856f9ae1f40a7f6a878435b395ab81a43eb1e2e7f6dbd571214478c7b7446284d4e0bb8369003dc28aecfada533cc32354956620a705e9bf1196c507685d3d39212390a4b2c2cd30cb2ef9de316b83f469dfe7e1283819adf7399c0260e8f2821c8065d03b7276fe29aa9af4d2116ebdd03fb74f3b034504cf599cc93855b2915a07a92d0ce28fdb3f74fb6636b3a0394d9fdc1527ea6510700644dd26fea04bacb7b194ff88360aed6d5b190937f132cb017110c53bbf4d7e56294065019552fa79ac70d7d80e8390faa705a89788ff6b3eb3526944c5f991a42446277bb5aa589423af75b1672ebab3cb928321606a1649a308b8687d5520683ddccfe9d2b3f94815d6f7cf598da51c7b4570d722b37d6f3df9253f2b759ca00d6ddfe6fcbdd75aada657889906c68e141d4d81719d6dab1e2887147b61db65ababf10aec977146ae3cef9ad55fb3d362761e6186014df8abca020c2562e0913bf08a6b19b1200f0f1a5faf64a90b3b351428caa1863096d38a9bd3f6c6896f9575aa4bb05cb3e5bec1da6fda3b6733b975a47cb10748f281a540d5b41c50d7932c8ff851f0949b033d41fd79f79c294477d335d74968305bdcecff5c0a256138d9c1b66c4cd67ac69f807127425e9c32cc1403f6477a090b02da46bd5c2352288306c0be6c36b2a6585c82f655998e4f329ad2b3cf6973cbc1271d971d3c5f2db44a251c47e15872abfa5ebd00851032faaaec0be2fbe56e8408540dc92624b6ddaa9018ef61ba953f759d4bffca05414380b91257d5065c77448048a061d2827743af5010ab61582a2abbb5fe8800f6fd28cc9d0003fecb3965e3b95fb24d96fb19059c8d6cec907140705f7fcb4c7f24927c3b032a632554c1adfbd2ff618bc866f233b8d3518c09ae49826290cf815a17b3f7b3fd69586108753ffa77b6d7e1d7f4c210e2340dba9af9bf4fc6d94258d2a55548d9f9d670ccbb0264dcc3af1b1512e1cf7498cbe255d1ec016cacf970ff0a0af2470497db50c63238e24c5133f4e9608111af9261b1486ec4493827d19a8404dce6af21b964f01f359545e3f7b506c951ae7122ca753daa47263d9d52fd5ffd791456b22e9191c893940605d2acd3c8698c219fe58c8046626586dae650fa82fdb81f965176d3186f48c4f7dff0f6477c1db63f966d6e7b284b607d94916e5cc4882970277e46598e4b124f722e659ec8a24a5e3c7e8f01b75783c31d3834c50505d1ac5d8ab4701fe99d7160598f47ef75c58ca84928b92fe8dc3781c732c2eb79aac08535ba66c4977a707752527113c3526bbd582e92f29ca9c3ab3a763ff2b3d285ba899f76adda90fe6c993e9135f8b1b88c3220810e813c35909eea626118d3dc990d8ec919b925df4d809ad3525d086f4c40a2905bf8b306d78d092e8b8b2574b9c7c8f88463a5af281051fcaf2944fa662ea1e5596724858598b9e8d0b3347b15e3555cf59c0693313ca9d6b27b3784cdd33311cec548e533aa702f02231ee4e8de67a781f5cb3bdd56dac96ca86e7ecfda9e83a1519d2f837cab9aba39f92451fae74b2e45b5bfc01c8f05cfd66834d5b215ba806e9a8f4a452a62b916c828b9314e58e7e618b3862cc2e543a5c65b6d9632f19b6b32793f6139f5aafce4aaa4b9291986eb2e185d6dcf7bba5fd13d02d49378aee770968a3cf300b708594bd5a16bcd384cbd70bb1f5f7a0ff45938307bbd47158e899e03d74f2c3eae8072708dc962a0e7c8e9c39da65aa705ee51362d270c637dd1d2cb0f38b2f9bd82356fc483cd3698d0b36b500395ae1891de62057cb2d6aad9119951ea42fbcdfb0f150331a4f246c011530f2997447661ed44e94490389f116fee03dc95acbbda496822b9fb405f53ec6d6042982a00f9f212f0a0ee5a5ca09a7328f1b52063ea64599bf40ce3f54552f0ba2a44e27539c90913b90338fad0a94642532f9ec9ae3e5692a00c3b4d2c20e615118d54145c3e1fb1c288746abb235cbafe0346264eec9ce50511a94cdad7112ab0cc9efe2041f4527f618130f63dc47626953dff9282d9340e274835fa5f4c51a5c069d3733d69e8551a8c0a13d436737324d3823863679023a9b353306dbafc32e1ef1d8e0d2e57026b6873efd5ad22d18f9be4ef5896a9027b2e300d3a2230240ad996ddd17e41df7eba607ca82d9a67b52860f0a5c803b3206780ea025d0c98c5b88670f541c0087ca4bd459c4077ebb7c5ab99683c9d14764381e256b720c70528af1385b51737192ecbf784040ce4727095bf848801f36fc52f9ee11d53a334c3af3f58caa3f31e50674b1e65b8589faa38d88c0e73610c467ec56920783cb8bc532f1d3ccff44ab84acca9b9887e5d162acffa2a1ac8fee40aa77adca10cabb0d8c43b84ddeafce1adb51007343850dc53829973c7a02690724eb91851268b45b7067a47440536c8a8bb311977e9d999414d037a7c108a6266c5d6510a9eb7c34d4529fcbc88e5112111866dd28ea0e88d038e67434b8d3171dee58b232d6a00b854a06f55a3933bc28add4f527ba2ccc3e6fe156bee292e89d546b5c7a728df0645d8c64c84c5985ec259c519489659d0f5d0283a91578cf408d0e38de09fe3c53af5f1ced27db2d5372427b67580f27862af13d0dec46eafc606ca1a6981021f28e16af55e1ebe87eaab4c44145e4a4ca40d613622150a9fa30099271f135e42747641ebe8c151dd1c9c296160b6ca0d7818c58d840262da00760c2fa9f90fe50cc0ff286b31e668f6ecf1536d69f5a45074042f7689a8da061f533611c3a2155204b9d845a643248c276df500ce28866c170ae7ba264fb9b25165de7cb9450ae32958c0961aa9775eca40ab09319758c071f5540363e99798d22442c2199b4b9d6fd420a9fe90953131127b5a708b9d60c6e2af4d456032372cab926d029297ef343c7c2d45f2cefbae7005194d0c1e3bede13bfbb91bd2c043a655a8c9dd633ff70c1c1e7b04a6544635aeff122ff54a399239b226f74014062894579004ad5af28fe6d8babfe86aba01f47481c364b1e82e2beef81ad36ae0e347083ca85fd2f9221eb3408dc644747ae8a4899d009ee7dfe32b33c9b771499d6fb49ba0abe8879578e74a7598518b3b487750612154a2b92a74b7c59683a041e5da0b638d88faff1254496df20a191c1cf4b2035004baf5909c13d6db5e51ec28f30452b1a9614f164542d02a64d0ca5310605e867f4280f012b4765289041a516f65e1fc3b75362226a83d6d2ac4ab0d7825f1d2a44ab6f335aba98b7745bbd1f1ae0ee7126d0e975d5176ddb981e045806ad4d00aa44180a6407deb3f8c8afdd2824abc800e6f6a9c8b3a4e543d13e290c569b380b21623bbda36b95a6d34f88350d9901e072d4fb5175d459b758bd826f1d0908e8b529ba340366f9e31a63f9d497e4310f78abc7eb8b354afcf5413c3abb8047945f9c57ad6abb538f9d26b25715f10935808f9ca4fb75c33ba2cd0f398590f4854fb47523d94cc721df28047a666ea29d9cd21fe4005162d6bdc0703f2a10ec1aa498fd23faf7954a75b635672ba2ff0c30e9f96d8ab465c044a453458a7113912edc672acb44bef7f17fb553062c6d08c46f8be9a2571f049677db9e2b4677d0e4b2043b6c5c29cfff3fc1949e5231e7194a0c3b59e50521b3d4e0a05810d2ecf6bfd818554dc8940b4c948a935480ca71f83a4303e35cd0c506319d2a029cce0e166aabf237695c79878ebe55aeee1b805502953aac65ef97aae79ae9956933c0e1edbe521b5200ae028126aa25f3ded48c71701f74c022ba4055b9eb1fc1899493a88f105f39a7b89a26042ed1a5514c8a0cda0256895e7992766004aff065605b60bfb1d7571215e1514f2f130479e71e944ad06d9073dfeec1dc60c91fba0ce6e56cd133038a3e15c25e364763a985b37cf684ac8fcafe702c75596a40a45d2de6aa887ecae9ea61b960f22d0513770e424a2bcf331b635bb258afbccc97df03885003bd6c473d567ecbd274cc49af7f96cb6b5548d1e8077edca5eb11acf122e6353b803bab329194b90a7bff6a2568263120946e32c118130e2f2452bb5477b594665637c23468185e85f273d321de7d5e925b5e6dace1d22a067f7b13ce115c90cd3f0ae9938a773bb6fb3abc8550c2f261ac60ccf55add0ca021f013db042c9fcc0065ebea0dd0d92a890e4d6130b1a20b6c32691a8982465196e27f42ebbd153d7c1be785240b9e93247189abdfa814cd69f076f366d516f5e037a9250404677318a3e7c621828dd6e214cbc0bfe1f1586738e183527ddd1a5ec8774975c85d0201d8a4241b816708833aeb76d958bea7a954664d21bb97735f771c8edba016937c97805dad79d059353a524dd0d303e38cd77d145321b6fd0c8adf816b8906f61d6c39c05a0aa0e3bb6dc96456105cfb76ccfa8e38156f66e39619d557ad61350d1cb55e9cb6075db7b1c2246085cb4beacbf2afb771d91770e92430489534143b6c45abd6c69c2a90fbfe856efc907d41f43a676e136cb2ce8208dbc98fd8051014e948e9d78290b80c18c446a373c248e090adee507d849261d08323d9f43145fc20b6cfe9c722d598d7cc960643e30f6ecbc0536155e824fd5320e5c819c753ca9bd60cec2c8ae3db2649a8613d28ad97087d2a0adde14e63135d9f6305db0cc2f281f02abc8e03870959a0fd966ca2dd78f275d7b23e7bdf1b1d957dfc0ee75337ab6affe2a2c08509fe653aeb388700aa917295550018ab5f2466c6f44be38acc0d0c233489d45636108912a3f2485e68fdfa3404cb27f9a050b8bceb81ec63414c2b418653d39e9b0c697bdd1e1e323b514c02bf5861ad1bcfa38ad0ec1819346a00c3a0477820047d85767a271561d7ca225021ad2ff3be424dcc42c2c31ccc0bf1494ae4901ffbbcaed1641e4ef2b9a94edc68dd1b372f699bb96d6a77c34a980bad774be9aa7f8a7c7009b6d7db9f91d8b3810e86dc5cae25b40c516b9b67f89c56ecd152b2389b6bbe96a816d4aee892b0daee0fff736c8a35eab2a53943554cd4f1552f9d558d7f7078163a3e4d83e15f266b12b6e3bdc2752a3d4036bad169af88346d76e9de774650f39efef0a50b9f0b123e4c2667ddb8bab18c0c1f472ed1c5dd9923a4555a1e95e6a05bc9d2018895451abc213548f9b447365b0eb2654a92755d04ba601e15dead247ebe82ee67e3673572b42f615a152bb62b15229f0412b91bf76862c4d7ffd91be0b66b9fa766b1ee3c13b71793ddfaf409f964731b865ad32ba6499726a57e444b3b0e545eb535b2273505898a9d1b34de13d2e8bbf2b620b43aaec6aa8dc1d48a1f6595d26ed000e7a2f0e84dd8aa558498a4d890bfa5e5adccb85ba73875039c98a8c2640582146e6bc57357cfb76184db704ca551f9d0c3b8b3b1c476987876b28aa567bab122c94a611ed59a437c4cf14422ec43361004106484e90dc11168392661e59c80622328860cf78dc9409e2ab5bde612b29026c26dc7f4ac7f209027c0a393855ba46a8938027cd9d30584f0f57f49dcce06a70280eb1b7c432f9bb2b97be4e37b55bc56b610551c8e95ffb55eb59a2b99865ad57eaaae2612de8e48dfdef517488d0742556b1655d8211ff9975385293e7ef1c8b314ecd57fabcc600326492ea62393869415ebe2a4d3e250a5ebb7a84905e28802a2abc525e67e90380ca6822e00c74bb22d07b5da6f201537aec9c9ccffaa74334f1d1dda8829857dc993b49e914ac42cbc57963a0f67547fa3d47e59420c8100f68d45e4dc710dc873b30a7854101eb110ca1e9508a7885e0d12d9cb66996c9dcda012ae18d227fe242707c20c7b7cdc415ed84c60dd7a8c2c34cb1e6c79c2673aa31f1562c7dcecaeb6a091fb67f4e10da6cdf9c30168548718554c8bc893e967da4e7af8692e294f35fdf19a77d01b87bb17b121a4e15ba2e34954b600214cff430eaf5cc317a47f8302c3bb43c8e463ba51c7079d78594cf27a8cf1627bbedb2350ce0f4bccf1307e832b193a860e9bcc166089513b1821a342193dd87e6fe4922a837ff90b730c8437b4e43e265f2359443244d5bc87116506ef867acb6fc129149654372555fca00320e1dd7aa843f900147738400cf4422c6e854d4320109ec02c96a13376643a6dd840a0d239fd6f13085314c6f8d32cc059ee40979380fbec42a4b27a4835ba3379002184a24287cb620ef5ca4dd6cb4fc063080eb95170989f0768fdab406d7215f03d4ae71e57791974eb16a07fe7076e1e4e573c070f0e0bdeaf1058132321562a44f0fd3790b6aae13dba0a882ed771ea989e2a31810744fe8ad7c7ec6ad669ff2ab663022080accae898509c9eea8a31489d76ee85546a60bedcbae4991305561202dcee1007a1b713a0a8ea817d8062a9fcd5326d846c659f970d522dcad1ff013f9ddc02e26c0f338d2e0a5c762030b0e9f3026243c5bb8bccb683e6e9e0921bc7b689e0a8294ed766fa082140123eef361ecbb5cbc6f8ef28ee18de85ac145e98cad5bb1253a35c0be9711b4cb59e1a4ef12ebfc6cb729be03bf8c9abae8eafa0189ff10fc81df4a7c6032c9ad4892a256d75d52f73d81f1fd89d1558732d6889aba834ca27af8b5c0adf50387b224eb8c535614067d22413bc10d61905e49a92a194ceae72e08feb9c420c58e71c59208dd47c3d024653504ccfbe775824c06b74ef1c7b2d992f334859287d52f68ebbfd8847816cdefdd1da01d451f03dc52a97387d9cdeeee141a7067f6259f79bf869a19c40ea3999520941b753e5bfd46c3bf08226d82fea100946c7ea0c3746a2ab9c7aeff217ccc002029793428300c62fcc83f4c7af5d89d12304d1b2d04c77107019220be23d96484f78adcd19b0154f52dc77219b1d15ebb056308fb1b1e3c7a96069ed6289daedc3856f0b95072bd04374e32235463b63bf57a2a48a9ee1e95dc455959a5ccdb5f62eddc73e157f5d1ab7aaf0921395bd84e3dbeda4f98e096d544fc5a08620d67159402e1b4cdf891f3bced898cb0cd4c099bcbf48449b239ef362adec3986ccc78c33ee8e57616f6e90ccc334fc7f114a87579992ce6effc24a7c2447274d28c615305d2048e4219af9726d014d6a14e0d08379991e891580e2968dfcf3c273a6438fd887e51f355be2cb62178a4a99985f2ad143f60e66db3fc57c08fa43ea15667d76b36c6f49c725e023d6a238cfa842574dfbffeb50a134e9ae810c97c63e42288d171860440a2060ab88d84ed028a96242894da553870177565303907230726044d5a411d5038ea8a814244fb624efa35885344f3e1121907093d9806ec64bd54541d19c222a67c834302a6e2399e425c9f8a8552b5dfe40d0fbc779029a95cc86f651baae0828ff2af88d48e97188494bfa599ad6297a27a08dfebdeada7991f16201594e66fb2de1caccf770bf8af4b255218ee4b25fcc8a5a0e4683f20c55292b43f17b344b850242957088dd53c6bb41e5bbf21602962cd06474877801f04c3fb3187b6c7da4507da652250f6ad6302d92233a8dc87acc3830dc04ff057c81b87f800a59afbb3dc32d66f83d238e0345a50b5ac32af88fc0a1bdffbd043f8feab8ae872af41311d4732d15f403aa20ddd55e7f2af48f9325b414427cc70163a8f7164b8e75f64b41d849496f7a2f034732c4a09b97bea1d0be3a60f871db76a0c0694f53ceee3cd2c50d906c4b5a5dd80a2ef39609698f5e37319d509e86159ffe3775bded746aa9424e967338ce7c09554634d9aacb04bba43c6e0a80c0ed427c212f66419d5441816f51ec31540f0d42b7bce19824830eb5c254a515231f54e7bc879862d4fd9e1295ab3c776c30c3b485c558122dad777542c572bb735d255e62b50a5f5bb6cd21c5a8748846401f20cc69f040d24d62a16c0e523232d1f683f3007c93a85fcc8150012b6c9795c75b34b642abb1d12c4ae4626147755f73ab443a0c8ac87f8986e05279c8319e9e2b22b5e7fe88e893bd35f25813a550e97b7955f634a562e72631e413d04b5c62c1cbdc2b6e2af9088d208c6cb5478fc17c5e86524a3eefc3062ac26d81a5004dd8068710e7c3409b9950c3b8c60e932c94b39ecd8dd9c2049b03b0f9c45ce7569d358e7d04f792af15d507296200167180860a3eac067138ac0f5dcf04a0ee97ab2a47585fdf74950585ea4a84c9a07bc541fdd83352ad8efde1b049c393481a7d5baf6f0248ac4fb055662adcc547493fcb368e4610c4defcd43f4aac3a780d9b3d8cb0d1077834f344b8d88d855ae06de8e27e28ac605581562be2c662f04a43a0e1837c4083abae48ed0c9bd7e81877da351bbb397f41ca66578bd72ad44c81be90cab8a18adc4b397fd8deccc2f94c116612bb20c955ab008eea87f14569bf261105c15785d89cb787ca85bc33fc15a5055dbbf173813b1237e944b6c9f2cec0283bbff1fabc8abf26b7bacb33904af163f4bfe7babb6b5d6eb20d5dacb84be1a874b5edc686b47abe4e5774c4fa081253eb09658d9edbd38d825b78c246b090b2f697ece4fc50901eaa405453a50b224c301c33f3f3f3f3f9fe2063c556c7cd8465a6dad1d4223564a2949e34199ae68446f4a29a594525251d41d247b0ca000e0c2304a60c1da1c041c0429041f8b6ceede649279ef804549bcb6f98ae22925740e9e55846675872bbef592177b97fdba71b91565d59e3bdf8c585112ee32348b3835e25945693c3ca3ce6e54510eb6399fba9f54144f33bd8acfdf818a924c261ab5f058d7d90b43c7294affde71f2db5f725fa6289b54d334bd52a5485ffb86e9e82052146eb592f1dc79d5d8e9184531539988cc8f1145318a9cd0ac854c4728caeaa77b5de6de35a51da0d04f7487278a2e740e369e118314e8e844d1d3436727cff9f9cd6da0831385133972579f6859bf8f8d195f80d49011322223069bb0f4e737347b3fd6ce84bd14995c299990bea4424472dc684d945e694625ee5ce7120e330222233bd09189f28ea86fb5db94d2b46606c8888c193744ec441e13c5d2fd2193ade7f2a8775ca29834097ffdaafb427f69896254a7dd2add65d8cd55a2f0bbabec73e707ef0e250a5a6fbfdfd4b53efd2a0f3a26518cc235cdca934ba2ac4664ce6ae5cf94a93b225152b2be7cc5f931e88044597c5ad32f72c7233a1c911f8d48469cbb49dd2b9d356647ef58c47a77f745756c37d13b14711f89b80f4444d1a5c62c847c30ad4fd4435c2ea6deb1bb5ef2eda95af6aa1d8628e7fa8936550f66214ae7aa9332252fb37a3921ca42b9e93e99327876c5d03188a2adee341e649641eb2c085796cc53215d5b881d812867bb51ea6406fd49972ae80044b7a73a6d16f5e159cf8e3fe4ff5b23fbf3a92ea677f8a1f01bf5e585d4ab647a791f4aaf36bdaadfa85b6e121f8a9db5ee78909d3d94659451ea75897a7b13510fe59439856f526ac43c143f6c6adf502a443ca8b32ed3772877cca7d3986ba1db528876287a9cbf8fc9904d22c43a14439f29f57ade9db414910e65fd36befb3acfa812e21cca6dab5f46ed7e76ad842887626a3d52489956ea9fbf230e2579591b29d38cbe17321c4aeefd416f0e327e749df88692b7187f7fcf2cc333e886f2a867db9bb98ed031d88692bce6f317d925b2a1183bb96fd23a4b5c434979f8dc682eec504369c38eeca9d3b1bdeb90be1a366a8088c8d8331441471a8adbf63aa24af4f5be888672f729f51afe85672866efd6f6fe4e2b644ca219f62c3e6ba97b65c632943733e5add041fdb81439759081d1416ae9bee118cc6afa5c8eac10d5273194dc468f792a0c25b1af7beeb9a7f5cbc1507e5d19ef57aabf890f7ea1a0637ad3e95bec37d55e286c8e36d3a7a5c9d769171acf6c11cd1791737f534d4ac72c855c288f479b4f1f73d0da188fd1b185628e3a5e4625562a7468a17cae5e07a95b3bf47c225928bb563d2d19dc0e2c14b43839a74c657cf1130a1d5728aa10524a2d43fc87ceb015cae1b429ff8f7ad37aba091d6314644e7dcdcf5a1d55282b1d42af5252bc55e8a042f1366746d17b390d6bc7140aaabb5d76bed72984df2185b21e61a3a39697b3bb45013ba050d6a85fc3ed5e8756a5276087138ab99d61f3eec7186c57fbe3a8559b903ef50145c378e139eec490504232099444e95d3c9cb80a25912807bdda525c0b2dfd539028c6dca73a5afd09219a47dc25f7d9f13aab3ab77b6a5d1d5589230a72fce7d59b1a0749234a1fa478d90da6a4a99d29614471d55e94bcee8cd9d3b421594431dbc6e9ef4da974ec217d207a279028a2d839abf7d56f49228a522b15d339780b3b2fa44f86887b21414449f557443f56386e6009248728e75731595a4a9d1d9054c3a0406288921e295aff7dbadd6f92429477b3ce9f5fa813be9990be942121442f63ce0fdb5d489f6410f7211144b185d633bda1a4d9229004420208fcc3f5625e7a6ef388c40f05d92d2942bebd0799d58792549d614ae9d32332c58782eb70217b7ab487723ef7a03553ae9887f4504c6a320ad7429e66cde4a1243dc455943413397a3c14fb7dcda5fcefd0417d87a252f70d3acb551a566c87c2ceeb1c5f7ad6ca73b80ec50d366b6b3a7428d62b4d2a94eba4fedf391844662987c2aa277d1c4a3ace463fe91f0ee5dcfa50ef293d3448de5050adba4553a37f6b9dc40dbcda9f767f966df68d51cf96a8b94cd286c2760a3befd2d896516c70d46c7c0d25d7e23de8cea354ca560dc59c426ced68694ad25032dd9cdbc5b349d1af0612349483e7534dea635ea1b6e40c651763a352c59fba939198a1704a6678deeef90449194ad24468b9fae77338213214743e9d7aadfd19e3918c81021231145726a1bd46cc068f0a4359860d9dc3690991ef0243598cfae86a1f4b76167da1a8b3eed78e9d9944375e28be10cae59bb89b3199ec42b93734e75cafbe55f75c2828fb382ba6cb2d94544b7e9469adb52d770b245a284997ef2b326925aa855928eaa0a3d27b3a58289a9aae5079a3ecf3bf4251b647f896bd798a55628572140faaa74e7bcdc78863947674968ab90ae54d9373e3a2cc2148a8509442b6ad6e8792299474b49bfa11711f84ceeb402285f2b950aa59bd6e949a2814bd359cc768aeee1d824279cbdd94c6cf330e244f28af149e2dde4b22489c507079429a2cadafe2c36a0349130a9eae39a5890fbae74b98501a21d44895a7a93d4f1d86640945595aca18b2347e569e4409c5ec36d1a3e9c15f7c100ccc8124094595b13535a40c266423414259c364b6d3254f6e52f9402286a75a36a6b6737fb64108b9af5da99223dcc74a8c500e3aaf273d67abf6b4244528dd6aaebd98094f5e4a885014a6fa468bed9531638650fe9cbda6eea95afc4608c59cb3542dfffd209463e6d341cba8396ad402a1b451e598f8c8243f306c8acf496ede3e28fde77a99bbd377f2b0a407e5f49959c7de98bf533f20e141512af59ca9a93a54ad243b7834773d53ebe573b56354b747d131e3340a3b20d141516ac8b0e283243928afd4a87327214c4918c5f3a0faf5375ef4af70503259adf2e4a7cf3d557283fb409d45e9937cf99fef75c8a2e02fa6fa641a7bf203b1aef69ce9b69c97d9bb86d05f2175ccc80410a513a6ba457a7d07559afca1b8616e4ea7d9277e28c7ec2d4df6a7f0fef0a40f0533b9ee2a5fdc89924df850d2d983e752bbad3338d94341fc9a47232b30d1435128ff3df1f49bc32787c86d6092875bd34979cfa7b987f4211e8ae9b7940c1a57630cf20f4cee504ea59961bbd474cc70062676287accd5429febcd756b485f63605287626aa1b3d427611bc54884d903133aa835e5ef0a7f2993cc640e1339601c7602079dbca138273f5247ceb7d27fe206c6d6f7c7cbeecccc95faba2e69522f3026f8f81089c1a40d451d4573b796f31e37880dc557a56cb64d385903aa01d3701f133494bd63da989b2ec5733f43f1cfa38a0fda4f7d4b99a1ecae5d869833195b779332a409194a42f8ad99bb9053392763b88f1c13311493fc0d7152a78efa9e4fc2701f60b88fc9178aaa66c3954ce3c40be5b457adc4e798348a69d2857250faa9af4b3f0ad370e19e1f39f3d84df7106f35e5aa85b406932d14847d08f33dadf65c9b68a170aeb9c43475da7f2a0b25ff18557d4e3a7ec4040ba5cfa75a6c54fa150a9b478d9a9149bd0e5f202656286e16cdab51698cf28a57ef54a5cb4e5e85c2472d546994269aeea242b17343d7adf76b68703285a2f234277516a289148a2966ab71327e6e31b2601285d28a766fad1e11feeb040a058fa56f7ccfcb6e9536264f28e88f8a5b21fab3eee484d2ebbddb3da90de91b09f942060e5e93261434081d5d7d8c3e52d34c2809619e6adcb56ab284928eefbf291badb57c2594fe3366d9307392843d77ff65be476e73cc9d20a13c9e7ff3c9584d515d8cf28b8914559f3c958c1ea1243469696ff2f495c7264628a6d359c7fb6774a1e345287e8ebad633d7b89c9c08e5549a459c84c9108a5ae76234dc0865323fc64408853dd7a7a395ecd5d4c59804a1206365b4f3e09900a1604aab9f8a4ef59847931f947b746430273e289d863ea9f5b3aa7a560f8ae5f532d596d4b14fc383e2c67b78902edf4141f4c9d951efaa5386273ab80f124c7290e88ffa29a760128609263828c1e406390b3024b2c858c0a2e4ef9b32c7f64368e615e516afa936bf7145b15e7f6adc7b9bd1f25614ed4f759dde7271bf5951da1ce66aecd75c852ab22a5f7ebb23f2221fab9fd9b5b04f5a96a8a29c2fe346c8f4f5119124152553b67af34971bf61a2a2a4c546273945b14eac68fb714d518cf9a23e868b334b51d49dc48ad02eb47bbe24a428baa688079d26630cee0241328aa21e57ea7352a94c511433287b356e5adf097586a2b0f14ecfeb333d9080a2f8224c96cc5eda5294f98982c7fcf143d43ed5aeb9124f184fa611d9a8dc24483a511e25da4e8cd9124e1447db37c9d71ad3490d7313457952a7cc8c1f6aa2a8d3490fea6f4469ab42fa24994096d65ad79a422498287d761de6d183ea9ce412c69fcdc2456689be34caa82bd50a53032460809b415289122332505dd4f8428688011e20d2859a71cc480e1a0878000044d402410004b470405a17765d241a7634a0c33eb23f6e01684424078e016c3183d3c501b41811c94102016ca1be3039bea86166180000223814ca916ce0b8c105084464dc3007d8420a5a6ca145178a000a682122723e0400003362d2c700b6302326d9c0f151002d02a0002e70289017203322020282002db648400112c4001e57b841c30a05f01823658ccc08113120205c785461063710154694080d4583001e5328dbae745342273db79e144a1ffc93a84b6514caaad24443d1681a8a06148a3a5bbad635831b8886a2c134148df57842ca180143467242ca18493668e40001e1c2a309e596d713b19339299d87b4e3e5c0a3a170808021238124a46e88fc8dd490912235648ce0503364dca06103f579a1029011783021658ce00811990102c285c7128ac23feed898dc140322f80003f60ce1a18464d44b6476b6b6d7bb34ae88dbab1213ab47128ae5e1597cce6d010f2494f5b7f81d29b6fef3490f314ae234f4dca8d4ebeeaa61668840251b5e787a1ca118b7518b16afde4ac828e06104efb5fe33dd3754d7fff42842d9b3474f2fb4c7ecdd8fcce002333c8850daf0df3ade26ef1c9542fa968c109117b96103c424e03184e209f5a39a3f3c9f543af610c269beb559fadf677fa3b341b396ddf0084251b70eb2e95b3f7ac73c8050d2d6d0585a8971935a481fc80c8379038f1f9493565a57bf14eaf66f489fd6f0f0413188b8cdccbac36675521e3d286d3e53a244bb6ba9421133c3051e3c28fd0ba567478a71f16cdc488f1d94673e3be6245260bc870e8aaaa74cc8d1f9426aa98423046178e4a09c7467c8ce61a010291478c1238c726b2d6472adfab48b1bd2221e382887921eb253c8d339bfd40cf5b8415189d06651d0f1daa326d46aa9cccaa2b041d86faefd50424a13c72416c5955a9fd9f9dc8345593b9b7ff8cd3f331d1b235e7c7cdca919e7c8a8317905bb625b515cb97711112fcda3101726ac38b65c3d546c4e5fe4d4d4c76a2d44bea86ae0dfa081343059456143a7f7f06abb37674d5451142955641aa566e24d935414ccef9390afd9e7e53b98a0224f91a6c05214c4f967b78c0d29ecdf99ff7914c52873526d25d67f3d19a81acb811a1cf802870241220e35226305135194b314324cdcc7d25c3a139884a23842bfd2cc2450149528d34c1f85d2d2519f28c928e5e7d61fe589f2ae896ccd6dd130a54e1456469de6557d36d79a1365ed35ad54d8b689c26f46e9a2758bc04413e578ad4f56a9d5d4a49c64a22843657bd02a7e7cd0608289e2adcbdacf9f13318a044a69c0d4303440945a934b9485bfad06b9d17f9eb5766289a26e7c9d2273e4c8fc9538bf508d1fa3d40d29911af5c97d448df074121349948366dc8bb63412dac967c89fdbbc79aaaba7720289e2a64c5a698dcac9234af33da33da4bdd6197444396613aef5b3cc514d376904beb79ba299aee9aef533ee84117792f25a4c6f4ee245b8f5e2a1a63d325cb88589228a4968d4f2b5b41251fafe20f46bd323a27c2a668556db1ea2685ababdeddda630d110c5f4ae3d37d3e624362f44ca6f746c1d4243c1841045cf27fb656d8cabcee30da2185e9972ad1ba1b39f8920eccd9b474db61e854da8b3e75196a4308ca220640822c6b6d301d31248201034228e040302914c91347d0713c0008be2c0682012098481904818ca7110c5301002310c02210c44610862da49a51ec70c400b078580973522c803f7d41b4a44c444a31d4e83393a13b9412818dd25cef907c107062aae8d7a97bf1e49b73482c6a9a569a54de86f5e350d4dba7566104adf4a68f135690b2cb9a5c157d9ab8f11d6601be49a344e8c50cb72c48ac1263e162360ef526f3801eb8ab3fcad636722cd15809df8c343775881ac64c61ff53c85df4ddb32d576a00b6e971d7bf1081649ba2cd13ca27711490e8828e37464a7ac3ed854fc9050fd90393c3277b34ac629a7df709519b521b30446d3fbea201a990d77a44dc0ef3754eb2a2bc64a65ea9c004b39ab986b7a5c1dd55b63e4216df6132f6abd3eb819c8d1de0209177418b33eb80261df67aed72189af51f4ad68e83ff970d0644e0643159e2c3270ec704f39e538673ebf05d038a65f763b95bfca1c0d406792dbdd84aca96da02c5f75363772103b58c6ddbc70095f70ae1bfc60b398f468479daee91409915a803633254be43286e4af23426c97e4c342a669a8a5cadb24a4e40adfb90ddd6f2143e5f52f5a2096628f4404186bf0a07551ccd1c11ad6bd25633564d105881ca6e444f6137db1ba354abb700eacb96e01708f8d2dd7285e0081df95de7b1c4f4df565a10893a8a018f32ba40b0ff341305737f6608681d42d088aa1bb80c6ebe38ddac4c2b75477250867be7128bef5efdc787521342c3ad2abc4fcf87384e3f2c62de4c4eefc6b559fdd1c1562855f25e0a846bf0d55a5bb4258fcc0d6d81afce55135992b049b2edc76cbc838b5acd08bd21594359ba30b16a043667a77fde51c3c4d60f16ca929dc3bc9843b05c8c0d14145d91959fdccef8ea7142754b8c80e667475542e6e48f8d5f7e71853372450ff08135d71c59f69e98adf496e35c836ae8c49fcee0c11f3c5eda59d6d04b84a57af36ab5baf5947f7f2448a2fdc59b49826e53e7ae11ec425becacbc03f6505cd1904861f8be054274fb9862502dd7d65a6db8712c1758082ca3443429af16f004855f52282679f96736befbd140360ba144b4f13d4d693f2b8f97080ac51f742032383c04527921aaad5cff0d60cccf44a4e345d2cfe7b51bed2e5e6a8359ba3491cf6d2405b895c3c4ba7ad6dd9dcf65df271b62c0dca739cf775962923ff54e879a26aa6648946448bb8ad3f066dc16100e0214507cd0a4309e939c77f06d10b28f95204227196664c76001cebbe56c8384085aa54d6c0522fc0902aea72fc2cdb43817d7e0c5e7715bac4e0231752b80bd366a42aeb84d7749344b3e59c005839a444c42dd23fa0b03f4f544dc681c85069e639f669b50a8327846931425c0fe62c7f854acef830e6329da9c3214376aa3ae36bf2f6405b982530318b95b5395a1250d48841f5a5ee4e41029c95e45b052f745c0741dab02e67fa83e8a1bb02d119c85a740d9f3d3d0d5fcf792450f24bb719a171e4cd4bd6a7e24c48f26946ebde7513534173dc16b94889e53a715f3eeeca031133a9afa00319125b00cef7254b21afab09f89617d0db965b7949615afc2fd33bb5c709fa2644313cab639f421710080d3ac2ffc061482b9b1a8c3da6f3de7c94794aeb8684e0aa43e0e8adc43ca1880638a4ee0e358f2108124a0e60caf3ab0ec1122a43b3a3796ac904dd8d484e39bd43b4d06d02bf1f3bc47dd28e7ea79fe4d797cc2a1cc5d88d4892792f7c218cdec2c3f465de480fd5315cd6561ec3a181c310c15b160ba0ddc04a2d3396a868f6c158d27b42375a83a19c1025b0dc039ffb60b9007d2ae1790568cc5d8024379072c0ac262f83af0e19c3a9397c84565a1db44c1127186583b3dcb956aea205f1b54504126ef0174f1b4747c462894832accbd0eb2bf3a862a74be40e63778dd8558a9e25a3ce3a067111af9271c6fe511d9fa228d0b4f69d9346ad52ac03e55007681754494cbb98f40ba85da35c99d77153adaea21cf16250911e403e69241eaa75d991a57b768b28d41203344cbfe622317b04cafd4514b987c6c47f2c8fc48a6d6ec7bda93ecb852edf11c5d46717b160591f83bbd4e762e7acae99a5348e916baf59450876720b23dd2d9afe805d8175869dec1087319d5202a12e2270e4da01f1a517269b82512f322c2502be4085f60022d7412b9ac48d6c843eb1403907e7568570c04d8a64a6d8c3c8d4308b3f3400e2af059e6b5ca454d8b019340007d55c9a0850240df6ba12153cb13f8837bd01a1d5c0d0791bf02673c3657894a1c602c080cd2d54b847e82b8ae15e663bcfde8c76f6887b978b2f3be75cb564e72d407788847d5706f48fddd05ad0238459cc8779acfd1646943371df6cddb0ea06914cb00c429803dcd1523bb0ed8071cdfe5d760a1c619d0990955443b93553d4eb716cdf1f849d3ff0d46608544106a6ad98f6cac303e2333910a1fe9f19512d7f721e9c2f3cc9bf699cb7df42d05fd9deed7fb1380a825b3c057b8a1445ea19315c777262572a9dab202a1721e1df3c34586176368d633525c83cef02d353a7bfecd1411a5bbf096d53c725f499f9eae6b91e13344919cc539c7ae357a4f9b5918e16e0dce69f3dacae147b26a0e0c3be84b41335b32bc1a1346cd32d0abd7aca2f3fe6dbd9caa2061bd377a1769f81f10d6855c2b9142f0beca3a3bc542509659f38b007cd2b4a64764832dd9bd2ba99d52534880cffd5f2e2de69b21ab13a20ffe7a9b739258a049843c2dcc1320798d2209cef2a068657aa7b0210626d2dfc5dac5ed26b7a39ede0b995c755cf37771c4cef2ee3fb46860d402ffc43d65a61792a76c03f6d90cded4ba0a8d3a7ac9d9fc244e648a3332b741d4c2ae7ffe3646529bf86727c26606e49e2fcd0ded683ab78b8be5361f377909f0027036e2af997b1311a20e89efd9e53b6a861b5d4beaa1a70f66bc857765791c7e719d5dc9eb266c880340e49d398eaa881ad1606b956a3e19d7f64744ae664f017973569ae9051d8c8ff242d596ec0966681896d77e1ac99d52920f517d5c7d831738a23d7ca5a0fab6ae0bf2b3a9045e1ccd17cb3be8ef767e08bc2c82343a19803d1dc34637bb8b98155ed26af6c0e025a5f5159b616222edc216e4ab105af66a488c685ae205b4798ae6243f806c84bd8504f8c7826f9c6bce84e0dafa189275fa26874c6ff57a0438e39db04f196daf7710be3a0070e96195f9b884a34a6195f1043d62820013b37f4e69b9df33b6f57f43e90677a8514fcf9ee03627ca9ed3a3cf7bcbe5d5185161f81ebef0fe8f7f7109ee6aae498ba859a809e2fc6f447ff551334746aaa86a04e672f43716158d4fce86d00bb32b18b032124679bef7a964e859f4349eef51829689c0868b4eb7f3b895debeae2aaba16978c197ce5b702a1f7560a453b0c6b2ea095317d40078a7cdf6915b03c12dcd46895350f61961d9c9ced0f2f6352bb078fce0a6c7f8f0a54a71ab511ab6f32823eee4999754263056493ec8c85212e90d836b92e95aa2c5071040e0fadc1a0a0af3e328a9d8be2491edc4f86020caa3a2154e23e1d8e221d3c5a6aaa72b390db03ef88ced16600d6565d5655b00d613181d944e749b654e21c5910f119e52672807207b9c98512e99f4ad5210c29a0afae6d501affcb782bec9c3b5860dd2f51aa7fb401519735c18a2e48aab613cad802e719d139465585070631d55519e48030a48f1614f3702ff4a8b5d641820cc073035dd6bd5cd6182854ba1a27144968a3ce24d23eda69b486446857dcd3473b73d64f3c00649c163f23198a13fef740bbfae284ffd68a164752de008d138001a7f164046e78aa337ac2b19accdb3367e47a681909c86c8e3646d3fae1b79429d9a6c9a8e311bb52968d019ffcc1b583d1057a948a5d0922f81488b06155cba0dd64ef504c9755bfe4e500efed5b866001025e06cc76bbda03348e419cf060b56b34b9ec1db4eb52a985b477c03ca952855db8305782b1c1e27f16108ec3eda92b835813601520102aa960004e31bb5de1aceebcb993dbdc37dba05c0fd80ad6a81403d43a220986f92496989b685dc82ac0ab282200d10f8b07b7fee140ee1de3157de9a408b93e6b580c9598be1ffe4a803eac6fcd23915c47be0922c6d8566640dd911c105326880c032e7f5f25fa9f28736212d9c9a09d10bf95bb07b66c1fba2d87984022646ecb3e3461d8cc7c79b0be3277e76f0dcc4640a24459389edf1620b8e11106d8d9cb4e090e182fdb0b8746890a72cce88dd0563e06b03011df61e5561bb9df21f2aafc09a6bd83f2d5256bc4dfdafa063852673192a18f7eb4562aaa5691dec71d6718c6ff4d7fa2409165b845ce9356ba48f3d358171684461a8435d0350c3e81ed120f73e85fbdbc2f37d35c33cc445a94aa67a5d8f155b4bcb83ec23122840557dd897d2b2d435bd1e3f947e3235def2d004eeb86cb9852aab811eace22b7c8845d3e5c15ec1990308830c2b262e025561aa118ea3d0ad10bbfbf584bdc8613818a09cd3f65de434a07afafebd5e52379ee06744eca2388b3c0b0c886392a593bb7bb82dab92cc53b82c170d352ef495150b4da961d6c76d059dc0ba9cc41751e9c68f7ff91dc959b494a0b12ad219dbb6e5a5e2b6c8ae5588df7a9e65666ce4b6252e9fd24c9306a84331b6a474d62a4e86d31d68b96540a4115c682aa7d4dd79392f9bbbb39bb26471dcf8c39435b20243c4e83d7e6fb0c5a563971bca11e50474a442942c2e5217f4057ac604b746234994b2024282864a4c261e060859db2e8994868d390462237bb481de3dc9e2f972c7d71b66585146f4bd94d10524106994465ce742588b70b632083c868f291a78a7b42258f1dfaa935b64c3dbc2e40ade16b1f9ffa2309e2fa34abd499830c227d62ba562991ad155dd9c5125ba870f9aa8d5036bbb13d729763a0a5586f36abc3371ca0d8305e68ba0f586ba5f4dec18882d1d9f41a06d0da1d5eb42396bcfe7ce448dd57191679a6828dc8edfc7d79445a7f169796dca3f1d915b57e31c7232a661d4d4a2ce7b3c26f8a37876d031f05e1ded4a4761df8ebfa4cbedbe1200953ba8a32f2f22dcc582dfdf5dbeb9cb4904a1bb4bb89dbcc64951b0139167b425c8c49b4fc63617f2f52e0ebe9ea3e59d4bd2aa5aeaf8171761ced56d9a07ebe21f0ee2acd0c398c3b25328d3d1263862ae49c8cec13dc2515b68ce3529be7c57f12633bdb26abdfc4c72796b11da172da996e282a745a8c86b1d7ca3984173a95d9a32cff16d60b61307a91a696d9832cab8350d36160c745d58493b0a8554c8221518e948d492dfd1a17b5d3e477e8aa5e62d1c014718964ce2b24085d9102b1b67605a0837fdb0c36e493d566ebb08fa73ae8affdac0bde53787bc3ee9497e8ff39f5e240da303aba2a4a4bbe17a24ba510b1968d467d8d570cae9d9c618c36825ca2f99bd275402c9c4674d740729b03eefbc0f3f1d51068748b031efec255b0f3b81ec4e01ce4c28172c6a2fda733b2b3f046b0c2931011039d9890190207f2aff3bf2943027e83b808432b700d51aea313ac08009e31d92d40637c145ca27aa34ff04f6c1ba0577f30c4550a4b4a43fa47148c107a52a56c104a8924e52ef613f0f30ab96904a2256ef29663b344a3e5ac5a1c02724aa758219a4c67c30ab14c8393ccae0d587c1c4220b7c6df2ecd568e7e6b48d281b9fb55bb2e2b22", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x103a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6414a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x103a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6414a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0x6dd12b3ae7975bb95f841f4505bc193c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3198f821b775e1d1ad0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b": "0xd0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb368de3cfb46a4384a4a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f": "0x4a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb39b7ed7da779c8f1890cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030": "0x90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3db6f1c35850476fe3a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c641": "0x3a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c641", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195026301997d60b83c06175726180d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b": "0xd0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19508c0b99136c0a741361757261804a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f": "0x4a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c23348a5e1bb080b617572618090cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030": "0x90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950e389fb0c07962e1061757261803a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c641": "0x3a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c641", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x103a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6414a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x103a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6413a072cf16481e51f42c8032f54a90c60d0d1dbe48e0ebcf3bd25756ccaf0c6414a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f4a732a441d40f919ff1dfd850e758b55f92b89acfa2baabbb40cb2d287eb554f90cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef0003090cc433f516a1f13141c217a3a52b99701a6f8a7b369a38026f7b7acaef00030d0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6bd0ffb2cf8fd9b4b160cd02ec808aa1b4607596112ada93f614830be608178d6b", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/cumulus/parachains/chain-specs/people-westend.json b/cumulus/parachains/chain-specs/people-westend.json new file mode 100644 index 0000000000000000000000000000000000000000..fa29853c70b05e47df50445935be42a4637f240d --- /dev/null +++ b/cumulus/parachains/chain-specs/people-westend.json @@ -0,0 +1,82 @@ +{ + "name": "Westend People", + "id": "people-westend", + "chainType": "Live", + "bootNodes": [ + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", + "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWGVYTVKW7tYe51JvetvGvVLDPXzqQX1mueJgz14FgkmHG", + "/dns/westend-people-collator-node-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGVYTVKW7tYe51JvetvGvVLDPXzqQX1mueJgz14FgkmHG", + "/dns/westend-people-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWCF1eA2Gap69zgXD7Df3e9DqDUsGoByocggTGejoHjK23", + "/dns/westend-people-collator-node-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWCF1eA2Gap69zgXD7Df3e9DqDUsGoByocggTGejoHjK23" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "WND" + }, + "relay_chain": "westend", + "para_id": 1004, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xec030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x100845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f744876aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151bf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x0000000042e478677a0a0600", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9446a2e9dc56d0fc437619542d91055bc76aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94bb69671d3f9f0999498b683e73934d36ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f7448": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d541fcf54011c18b8f8c5b4eca08a1290845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f4caf657e712ee5527fb899d47951485f8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x59933870656f706c652d77657374656e64", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd00582c95057e1687d115511068699374c0c0584cc28185ea3236b649401bcb6091468b5335a83460b397a2b8c2fa04c359796ed1f8e156844e030238c2d7b44a07717524217769c6f69236bedf078f0d7dd21a219b1042c8de5b6e191d18d9147e14bb892a10ecea3ed537da5549e2ab318696e8997dffb6236f4c6b755748372bd01bd3f3f2bbf1b03debee0dd86146b731fa3eb442e42ddaf1f93d8cc25eb5c43ce46962f47fad37c1e3d585f09bb0b9e802806fe289f79ae3f0853c4dd8d4938ec39b37f1e3f3fb0bf23431a4891eb7f9be06f234c1a39e787c34ba8dcc47f43c7ee2514f32416ef3138f6b1fbd09584fd775ae9eb85bf027584f577de2fd743de732dfbee7204f13593d6567feb4a9a77a0bc86b385bb7f9896f534f529eaba7ecdc4f369f6f22fea69e6868aea39ee0b17ac24ee74ff4b39e20fc699ef4198ebb564f523eab27584f3e9e9d83e77ed2ced5939cf9495ee734fcc49d867aa2a1f98e7ada71cb7a4e3dd1e47c87b5e334fc649d867acac160ce4f5890fa4469a69e66be63c749333b76d403c9b2e7f8c8b2996174870d0f19c8598c653ce03577c811951a5f363223bea8c5412eb3764ca9f1b0e171d1918ccc300c4eceda21a3713c30cd46c6e28bb311953ba0dc6103af1a02c04d1ed1791b6468c86aa861c666424c47e727be8e0ec683477d89bb5ed20210809fb2072000d4b246afd7d1b2ebc852e927fa520da56bb4837b7d006c641e80dbf0138fdba0a3f3d3bc0e0d3ab7e1274cd650c3a4af2fc91d2f710d35d410809fac07a09e6aa84106de061a68f0f193761fd8eb7572ee03080f1baef393cd754a25009c867a2a1d080dd7f9293b909fb81ae433336cf3fa1a7cf838909c9c93ee03c87dfc24731fd76bf80d3f8d7ec31b82731b7e1add8617e4406cf80d3ff138909faeeb947070ea71748ef3133dcefb691ee73490480792534f3eeac9c7819c9473d24fda49ef27791a5efa897be901a0869facd7504f00a8e1b59e00f09c9c977e82af3f61bfa19e02108007a927523d913e33739c7abab939a99e703e3373d24f3b4eaaa71bd26f7ed26acfeabc07b90d40341df7514f37f574731fd7b4d79fb2d77ad201e4f5a7598f589d771db7e1a7ac22b13aef36d45300ea134f679e867acab2d3d413cd270d9f3f613588594fd93c4e3d65d7b06b3fd1fac3eabce3fcb4a3f2b03aefa57aaa61c701504fb59eea69a00f524f40fe5a4f9b052a6428634c0cc0a0822a26aa04124891843274e1054d28c1d4047d8f4dbd56e79dd6d3eb79d45d9df71f75a1ce3ec787cd71ea8954f39b7a42fd20075af80246063430e307261b140c9678420ac448830c60aa30d9fc24536b56e7dda69e6aea421d1c6a57eafd6954b9d579ef512fa8b3e7f113ac45acce3b8f7ada519f78a626644e534fa3cfd42786989a80d7514f32a28fea09a5859639b46811071c5bf082092e14c4e4008b14c0200e31c8606a027ef4135789accefba89e4475a18e006a47a4f727ab0e599df7add6409dfd892b93d579d7ea69d6274aa626aa67f5b4030fba70e1067030414c0d4c587de2a6267e8421061d80f10427caa00553137ceba7ab9656e7ddaaa7aa2ed41152bb21bd3fc9fad5798f95833afb3d0a0ae9b09031f3bda069b8f46ab8f4b68041d300c5c65921af2e971e7d47b681de986d85f0b515c287344b77b1d9052dc46dca8954ef656fca0929bae31d78fe06b7de94135b960b333eee165f340d97b6462ef177037a635ad26b85f051aff76f096e534358625f5b449e8f6d916dbea46bc9685c5a2e7535fdfe7a535a84d1345c7a8ff4f5d2f85eca092a6f3033c32e101ec243086110888126b0747771c3de5413437ddee1c720dd0de72554aef4a69868a3bb51c3dbd09b62824bcfd10689bc83ea8e9fe4e7555292d8efb1df91181a1a1a6a8e1d121d1f2bd0cffb7667f5fba3f24617533e84ef7d3e0dbe7a318a1fd0f24ea735f368acb33970d55f9fb84d31b1456f8a89a1ee7e34bf00bda925ca68de7c6a9a5fa3f1ce753ec73bd5f934bcf95c55f5f9b4d7f36dd56745831e904fe4523c12aeabbf8d87edeabbf92c973aad9fac1daae575bd075eb48bddc507c1b6be9b8f7c0fcfb91bacdda9e12fdabdaf57b45b2e21d1d7bbe55277445fefaa2f97ba22fa7a0f84d4aa40a786afe8f6453b9c8e9f4fb3e87645b77753121b4a0a041b3ed2e5d2ab914befd68330982c21ad9ae9eb47bb10f4fbdcc72cea46d00fd2f7dd90c87eb1996ea3f82d991bd40504dca696e02200dcebaed44b4cd125dee1dfc03b0f0b192b64bb8735de19e2c4dc1ab8fdfb625f76bdf4ee7e83ac02e6ee70effbc5e6f2cedbd7fb395aded9e67757cf7783048b9715b23ddf6e301efe1d6e3eafee9341c2022f5cdae643ba5c82af79e3ef16eb72a987e1db7a4035f7f0ebc2dc66fe63ae8b7a0fc9dc6f90a51df7ae0d81f0efbdc7f03dfc203c4308e13b7cccbf9a2b29093f9e5f8d5cda73edf8b0fefc1d616e90ee06e0e6e6c02d97e0db8c20313434a44443de81efe212bf8721d5b8c42f00d7d134ff3dbebb9a3f6be6d36878875fc33baff938bc037de07bb887239df2a3a5a0ae7ebffa555292f7f8f758835ec31a5fed1ec3f7c898db1ba4e39a3f2fdee96e8060c1bd775af310ee5dc9fb0facf1514e6daced1679efe0974bdd6bf8fd8414070efe08acd764edd1d8da1689f2c43490d553748c26d41e85bd4da9b4303d45136d91286fa88114d0effcdd94c4a1b721e186fa5de39d6dfe6e48de50bff7f0d2205c7fb85e20287eccc69cbb32485890e2d2f67269bf9b4fe4d2be871787e70671ddd5f25c9596bf7867534a0cb5bcc63b8f674d1d4dcbcfd106e9def2cb3bf19dd6fb8b77e0f7cb3beffbc83bfc3dc73bdbfbdd90f0bbd7f1bb2189e7f7f00691f5e7bd4050ef3d25f6d980dbd41667f41b6af839da6f90ee0d357ff2f490ee764c777b7e0f5cba0de9ae90fd7cdad25702ee67bfddbda1eef67cee4d6da1837efdce5b9237d4b0766fa8f77337255cc7433a2576ac406f4cc32fed50bddf2dd2c59390641d2bd0a8e1178d1ed2dd8ee96ecfef814bb721dd15b29fac2d7d42e0de5fef0e60ff36a037fc6e405cc38ac3a5f71eeee11eeee11e5eba5cda6f10ae3f6fa817080a3e46633e33b8f7e5d27eb5b93cdcfb7e31204abc141636c880600112051014542ed437aa176a983a859a852aa66ea14a8192a1b651bb50a950d9a841a86554207ccbab50366a0b6819ea09ea50a542bfa84ad4256a139589ea442501bd41ad42dda039a054a056a06b5024d01a502fb409b40d3a05ca05aa020a037a054a0635027501c502adc2dba066d02f740974889e419340994095a04ab818a81c740c6a031a07f582724167407140b5a045a05ba814740c5d01edc2ad40e7f8156898977125dc8c27814ae15fcec5bbb81797e338b8109e83dbe0477819ae8387e143b813a7f22b6e023a84337133bc8967f1125022fc097a042a062d83864185702b07c3ada057d02d688afe805241a7a0601c8b6b71326816540b1d03e542bbd01750199c06d407f7f231aa14b4073505150587e359ea0a2a0b2e06ddc20f2e7e74f1e38a1f5bfc90c10f2d3facf8f1821f55fca0e2470c7e58f971821f28c839430e1c3959c8c1428e1b396de40c21c70a3955c8b9420e15728a9003849c24e44021c78c9c2f3963e43021878c1c25e42c21a74b0e1172bce41c21a7092422e4a491e3849c354856908090c306298c9c20e4080167091c2570864863485b20bd41d2028e13384de050c16102e7091c28705880b3029c2870548093029c2b3828c039018e0970ace09480e4060e169c1690ba40e2028e14a43390e620c9413203a90c243290e2208d8124065218487090c0405a83c406690924344851203981a406a909a433484c202981d4857404120f483d2071218941f201c90b120e48291215a42a4828209d8074856402d210c90a890912159212a414909620397163861b396ecaf0b83c22bc2d6ee2b819c30d186ec87043c68d116ec4b811c28d1736616e6070f3029b28dc547193e5868a1b2b6e627093c50d16a22a44508852204281880ad10a4432106921ca4294457485080b9115a228442d109940f402d11511165109442e10c5402485080622156c4ad892b07dd9cc109d40c40291159116d1145b0b362c1b0b6cd6b091820d1b9c623038080c04eec23f601f700f980bf38077c03ae02f38078c03f682b7f00dd806dc0573c135601a4c2f48276c5ad0830c92049996992f382d1c16333f98e141b6458d1830093a7e60c3021e4298b181c8063dae305a43870f7a983162a30716b6316436e871060909a4347a3441460d7e0276061e62729e40cab2430b3ca2d811051e28d8c1068f2b3bd69019820c117654610715765c61870f76fc60470f7674d9c16547183b523bb2209a0183d9d184466507133d98d0438e1d57dce4804920230599336cbef448428f2ee420618606354ad450c1e6a86183c60a4d097898c1030934506c2fa07982a6ca8c0f6a7e50e3032e06335d381acceca06607353ca8e9810d14b8197059c8d860c712d90d66b8643398e9c18c18150964a270b385bb018f20644b88bcb0d181b6042607060451171a1a5819b03832266ca8b0a962f405110e74a0a1a3093a989075a186891e5e90f142668b0c10647020c345e60b7a06991dc83441ae41ba42c609b405dc19354fa856b80c66caa070cc0461134346839a20d4e4a046079c153340e0aec8b4a80102a785c6852d8e193066749071c15191d5a0e68b9a2e355c38197059b22cb81a7036c0cc2083841e6ff40803e94b6c8188063966c89123678e9a35444cd4a8b19d81ab6867684ad096a031619be3668a1b176c637608618718a2219112dfa262a15e81dba2068c9c3244285437ea11a818382e7aa441bb9839028502f7053745b665b3e2e68a6c8aec053234e881049a2d6856a88982e605ad0b9a981e62b0b10157450f33e4a011e1f8a1452443dc42e442cd12365a645ebc316cd4d0aa10d5604a611a6116617a99644c24cc34e611269859c634434465be31b530b34043829c33781c212e31b1309383191cc84041e6093cbce4488107193c80d0230e1214484fe0be20a3841e637e64e1d185c717d20e3837b82d3f6a5073844c06d9173550c87e3089b04389cc0a992ee610b42d645864593220645db0276069f4643e58c0cd19322ab22b4855b21e645d645cb22a321e90ccb8197333c7468657841f33804a10e580c485cc17ac8b0c188c0c3dca2083460f2e686df41063b3a587164437e8f1051b2f7a80c1e6062412f43803690b4907181b189818060b427c824c1a373ac85c400386268d1e3ea05fd0a2c08308346190c9d2030e1e63c82c81060d6d06d5043953d0aaa021414a4103a3d140b445a2115910dbe801868d0b681699303c8ac0a971b30311173446c856602303192a64b6e011468d82460c3db2d00308978214430a829606090c8a058f21d40cf518c39ba099d1630b34269059838a1981790db435281874f440f3010d19326c9c0a3de6c8584083021e46b0d9c164424e987b815a71226849202581e6043db86454b62d28161e608cbc205385b6c58b20c3841b311c14b827d030f048f11043fb014f4107979f7123d0c0f1198cd29060280faea5871b3457445fc4347af4202a6183a51aa26a482dc82cc837241b39606ebe20c59060c82f640ee418d20c790499923d90489040905c6412a41749048903b9457a2187207d20bbc822c81fc832240fa41064102419320c6904a903f945eee0c60b928a64422e21958861885e886f442c442de4682166218e2146a1aa12a510d7886a4436221a114c2c4134c10f17c42bf1045105518a18454c41b472b3853874c345742232119b88546eba80568055806dc02bc0347ed0e0860b376f40336ebe60359805bc044e02138165f0e4602530137808ac0567c15a6e8ac050e0346e6ec056b8048c0286824d00e4047cc68d0dde1cef0c8f0cecc48d979b2e70149e78e089e195f18ef08cf0ae3c14bc133c243c133c2b493c322af06381a703278f8b224620822841bc2e5e0f240044048c40e0013f28f121490390f0c000050829bd2fcfcb0d3a031000016c9862cd315fef1b410163d8448a101b587284104d8a58b2643e480494264b96644284cc888188274e3eb001d24c18b6034e3800c588264142348932841427529a40697224c80c1c432c31820350903c7962c402828668f2e489110bb061060c4366bef0e48910433c7922c402bc50445007a0347922669b34e1c0122382960415d101274d40a033d38595f2c48910414d8858d2a409076a98e1c21ac101284e9684204a930f002d717244074169e2811008f1c367b6b04d868062049425422c7172443719028a110e0882f201238618a27dccbc418411433421e2c78c16564a089afc2c912225044d924031c28914294d224067b2b0207072444769f213c5080a28a13358d8254d3ed0e4030f008288261de8801040d830e3c64af919a24911435c61a504416912a54911509c7cc60a509c70803453851dc28914284ea20421d16449089e1c7184104ba23421e289930f987e8630e2890967a68d9572334385156209cecc14d6034b840812820821829af8203323051138792284932027509cec9809f381219a20d144c70c1b1bc5880568336bac94264e829c4431a288a00f0831331385856284932021a27c80882541463c91b2c4c9112d44940ff40e9924eccf10463cd9c09226414210e1e489105344fb9a3cd9801041503ec0812541433489e28408284e962cb1f965032b0c1958cee845cd4d0d0df543419bca3a83758677c4b3e28d65d560dddcdcd4c01b23dc8d92574e8ecc2ba5fcc11c1f8c9059c628a584b2c7ee5bb92c25438e52eebe27e54af97823ef9352c68d4f4ab9bbd2922c99e5be9572e54a182fde38ee0577595abb2c9777396617e4786190bbaeb9c1954c438c31ce186705b9c26294d5f536325b2c65dc1819ae8cbbcb9007091f4b8ed64aeb3d29656649b9bb5d2cba584609638c0c2f1b8e0ce5ae8c22de33b3b42c087368686868386e39ee8ee0f5e49bd742ec5d162f73bc38f24ab9bd1899a38c31eeae8819c6c8bb922186edc60bc26d83db06216318f3755df08231c22bb265c528e505a58c1bb661cccc33eec597059923af05e305dfbef71e335f2266666cad853cc21e3c1279f7b1e44a07ef6e662432030000cb1bdfeeaec5ccac6952c6b8cc524ab9555ce86313777775ec32336023330d7c3173906bb55d8656bce0052f8c732e1997779725b388777701f18794a68e3042f9a484114a1937eeca8dd9076fb1274e1ca90018593e8ca5dc6529659897392ec35df8de5ebcd735e1e6035e0ca3c591af0839666608f7e28849292f9a4b4aaed6358210328c31c2181380005e66ec452847d24624826b590b972db8cbd664660c423877987177a5e4dd95a944bbbc16bce2e315897a889e0e36bb221911ecb195e4dd2ac65de6b82be7f21599e32e47b91c2533afc58f254bc945a494707b528e300cc3468c3133c4a2bc38bec88c496bca7749c658c4a2c7fc83232f5b9121478e18efc695cb1b23b35cbede3b40aca9a9a9b1761f64e60b5e17c438462839486486917921b62c17eedb279779e5ca8d91f971e45d22b107c7c87057ee2edc27e332dcb7bb1c19c66586bc92619452ca85d77531bce0362f8871575555315ecc32420cf24a0b46e6dd85bb1b23efe3182f8c73969939b9cbcc115ed74ac9bc316e84f14129a365c117a184f149192333c708618c0c992f4cf2c818258feb8a314629adcb923cda0162001e23c82307203ce07b4f5a963592514a4bca5a0049002ae9b3e2b3dec8baa48c1b4bc9978cef4529d992721bc015e11525b46294bbb292114288c10861dc4b4a4c2425478ecccc5c31b46464b9bb97940c4409204cc099e5104431c2034d3c50653b8026539a3c598284101ef8316407d001239e2ce90014271de880114f6c64ae038926444071b2048a13218a1882031c30a2e2404493284d888862c4921d0310a0899328410128c093274e7e8608a00621a51b4a3b6ed851830d0108c01019229e3829628825414f3e2004079a10a193c38e0f248c004150ce8e0108f019020c6009019afc08f164034b3a00c54911528428e20400a21324449426509a74200a0dda12a08914294da21ce10123865812a5c91423866812658910528818a2c99201fcec188000470cd1440a0f0e079c1c6000063000018868b2c488a02342106508205184089252838c149b2f018c3022cacdcf10426ce0c91323a204a024449328372168f2b324088926507248414d3a60332325488a13249a5441430821058a105182960439f180078c28ed580238e140932847541d518468f26408920c11509a1c7184104b9a74408828460449793b000fe46c93265138b0248a1141420c61c49327429076004d3ce02408ca07a254221c08fa808f29509a401d3b8025508c7022254a93294b906852c49220284ea21cb16408294e3cb0240889263853a0348125ad0abc4242426f85f842090909092d2a5b212121a18d09ea612321a108511864262b1499a09e90100b4126a827846226420f2524042b262b8462262b8442c1893aa1221321a1c96451288c09eaa184509089d01312420909a15015934509a18484504f4888992c2a63827a8fc90a095d4c506f858450a888b298083dd45b268baa509209ea2d13d443a1183259140a858a4c16856226a8877a4c16f59820896bbcdd7d1e9022c406679deb4198e7629dab2369cc43c33ad7654a0b1cdde5d0f2a42df25a1ea3385cba0e57d0c3bf0e1fa6aff3dfdf769d64912ceb9bdae24a6f6a8b289a864bd72bfa1aab385cba2c2a57c87eb7b842f6f53e9464d8c57c3de7db7c328b6e979f931a4175a73db3bed6f71aedacc32dac745c219b11cadfa8758cbee65fd7377a1da3efaadd75519e7f135e136a1765adce4fa82dd5bedb914c7b36a37651fe6e47e6a6617f1b101254965062a8813a1098618e312d65bf6c210514bd97826aac76dad43a247afab0d50e899ed7b66a979b91ac9fbd07ae8f567f3223d9b7029dba839df5ac3fd7f9dd7561ef813d8c9a2f0a616e10172f85e3ee86b4d4d24677352d89f08e7c0ebc93d3728796e72ea5fca6a0cc8edb9d992b696a10a63905478ca9f3f0c3cf68eb97b72ef9b6aa4be1607744dad262a5bb05d3564d0fef74170fd6adefc03b3963b4f5e59d1fdd6d19cb25f91ec6994cad33de12dae2aee19d4dc9e00bbf85cbf10ebc751ade89b7ac6bbcc3b7be41acfab362f80cbba254e4d20f549a48628d26be5c69a2086bb4615aa8d3442b01075194e10619c6108669a10e7f5946e352aca5d2fb429e659d8eeb736f4a0b373a6a1d271ff943de180db5ee4a3bf04053a7f5a3d75a3255d5b824abd69bd2428d8e95864b6f53bd4050f2711c73495ee06870b6c86bbea6c51bcdafa181982cde90828b5c9291c23d2ef0ca69f824f37dc1f10d7a535dac68aea237e58325bad49bea010e90043121a63b5497aabb0fa48008bd1fbd2f3828466f4a0762fa8dd19bfa828bbea137958333de76244342980662d2fb2399182a1a4848d45dd014d6a8a9a2bba02daa9853e82ea8045390dd4949430736f8e04b7752b87ce9fd35341487867ac7f4fef51cbd97570046959668f4fb1c01bd8e9f91ca58bbf7b624f05c3b31ba743412ff5ad20e454a22d1e87889467752501d2f0fb463c68ca11daa597609421c4d9bb644a399fee8b71d79f3239db2e3d5204ef2e6c3c9406f8c849584e41dfec83bac41b3c3e0d241dcb276fc29afe1514d1b52da1c347b8f928fc7308bbca1ceb64891acf7bb3aecc3217eb445760e9eeabdaa078c315648fc167b75e801438cc5613bd68e6b27d453b83b10e5f53b46937153d336050696061af594ab7a3a6cc38ebbab8ee0dee7db8ce0f8ad193cc6db7c80e2d0d086895ca41a551bfb74786ea8d17b7ed9d3210ef57e5a1adc8cac1ad6755d57c5d705df755db2247aafce106999bd292da6681a9cb990e7005576e004136728c21c719888a032042666308517e6a881691fb9f422bf87abdabdab5f2b6522976a1704fb1d3b0def4c6b5a59563bcb3a3cc73bd9e1a7455fcfb71df1d1914bd8ad20d8d9e7e37d50f4b627dd2ef1ce0d1d4ebfc3e3f0ceab5d7579f809dfabe836462f0504c97e34cd48d0eeeabd56cac42d0897e0ad9f4b56e547dabd067afb8a06c1b7f96c9595e3126b346f1cde79c3dfb04560c3209d3d89c52e0f845bbb37d4307ee994d8fb489170bd8770bf68b001de901977c2bd77b1e338fea58514cdd7689cf4306423e6f28e3c8d5d6af9205c74ed4d610105165874c1420cd422edaaf3e3e6033cd708eb0ff758a5e5462dd21f7ebcdc7c60d81c0dd71f787e77c8120b127487d3b2665adaa3dcbbc5f7f0c2775ccbcb6b4f876d7958dfd38191442403e85d21efd00613c6ef18cd3e7c71c519cd87df22d9b1c7ad07f9f8b829e1c3473aa433f8ec6530d36076a85d837f9b91ec70d2f7ac837faf34b825e19ac4f5aa72e299ae27e1c43355d593a86e3d88eb495c415cb71e1f6cc11cca4c72fb4bc3bf6cfe54dd09eb5d6c56a4461ce0e4b3578f1f5de6afcabca24c5caf2ea9506692df2d62bdaa4299a9fa687fa8825d4afa4c18ed024801dda1da8a2d144ad6f31a4592f5141d6e46de7568efe8cf9379eddb59a350e6b7f7401db443b5ccfb68748e6adfad875747f5035c777168e85df41e9ea3b75911a6696f2acb15ae90a283f4a6ac1073451624d1b659dba54d4a9a224a9a9bdcb62dc9762ba874b525c97ea82a13573da14c5975c2fa33492d552cadcef7b4aca74adec2aa2a242b13a3ea27a1eb55ad7253f52a376155e8aa556e92d5892a0a30358132c9cb5b15cffa2301943c694621ee3a7148c84a9a97f2ef55d2941595a5a1fc6e3fc8ba50e712aaf85a632e12dca6ac84694ec920d5db97a603ef30ef301c5d2808133781ed423435db6b6a6cbe55db6b36aa7db2161ff6fd8c52dbc95b10a7244c2420d8c45920899b6b4f82fbcddf06c4cdb57a129d9374ceb75940f41d1fd124441f5d869e449739474fa273dfcd0233b7790fefa0a7d17f5ca327d13554cf679fac49fa9aa332df7e1ad9d413f7ed074de274dacebd9e4d3a6812a793b6a6113d9dfe9ccbd024e893f893c8f94928099cebf88e9f6a7ee2f113cdb97a120121bacc770b82fb4c3da14ca3f7f00d3d3dd3a84a6a0191bc4c4d82bb0c155edaa6374545989ebbf95897144ad6f2164592b5acc24ac33f2ae4b0b4dd2c6073996b74bb88266173d1adcd023332f524fa8eef16b1c049fb8f9f44d7f1dd8098f9564f36d744744d3a6812da67fe832631739befa0499cb4dbdca21daae51684f6999ac4f699cb0d089b8b5e434fdb67aa50123dfee3db2b6a019b8b6a12da479fbb05a1552666ae7d54853293cc372a9499b8dd22b276435a4b15d1b52a9499b4f365e8e81cdd9ee9d1eec8fbf5b84d80ab27ed5b7542f42eaafaf344de7a7599ef1699b51bd2d6b3dabdf3e17b188a7cf59898b94c3d3d13a92651ebe9e62799ff3846af6b948999ff780f7aaa798f5751461553f47c9509b2f49a71a5324196ae2e3b36155c7a95d08f08aeab8926b862454c156a54f146157264b192658a2c5c689aa4a4ab26c1bafaebac2d377adbb0dce8eab0b97743b26d20804a2a4ea18ba1ae6e75d553a4494a92bf284956245d1debab774382031a620cf586040154525d997145f41ff5f44c275a7f703ffd470e578570bedd4715c2e9c23af71d4bf4bb0cad7293cc49b4ca4dda475468f41dbfa942da77c854a1d147df3e434f28934d754254bb005240ff74fb70e83625c550bfcb6f3f530dad72d3e93a31313a55b94948fbf699f7a0271e5548d3bee327211ddf51abdc243a4dad72d34c15d251abdca45527642a130a30c95cfb569918759b8a228e7e3528f63bb6295925f4ab1ee0cce84e6ad9d24ff64b1f95a52f4dbbe8f10b791e6f6d13107d740ef23031baa85691a95d544799e2ab2aeb8f55258032716c123d0a71d7e90d0d71486c6a68a892e44577e226d1ab774774554952f4f78e49439e514d62bbf6f8ebe9f0eae736811f634ca2eddae1068476513d89ae89364da807ea628e072a88a3e1b32ad53253811b0d9fa900cc5c0cbca855d71e3725f0d9e3c6c307b87e8f06716d7d66bc1979437a00bd5b76d9a1fa5d979b92ecddeb9ff7eccaf8afcbce8772f301ea3625f0afcacd677e566f5904f4c6bcedd8b7f317f930c9524a79883d039536e9ebb9db91ec36df48516069f88cb44551a5e12f4dbb76613746b64da4bde7633422e51c0947caf1b1bd2ddbe663dba80f91dc7c1cf3211acd90483ef80c124947249dc749234e7b8fe6078ce3078944371948dadee3dbe746495cf00ee9f2939bf9b979f6213db319b97976b92581cf3e7568cc9a0f9c6d8b8244cbb9c61dc9e8d7b51ced1a573ba0d1467d5420a126fdd14ed3e86b1c12e93dec836e6b3ea4431cbaadb59ca44bd2f69a6f8f56d4c8fbf51e6843bbd737db6fa88d0dddaee94125162ec9f3a0100e2ec96374a3b43bb50ced4ead83762711ed4e300e1847cb4b2cbcc367b43c848377b0cbcf4acbe8f69c59252181ff79bcf5dd78a8b8e09dedf273dbb6f3f64745516c11059728bc4401260a377666556ebaf667afa7675acbed736adbf61e9e9a855d74eca2575d784774f9291289445936e2342d0a922c9bbf9e65d9afda4d8d8e3a394546b7b91e8944dbdfb7fab8e092fc4f33249210faeb6b1dfa29a5744defca48bda914183d59a3af8bea536aefbd87b34dc96e3e9b324196d6e957ab2e5c92e7cd07acb3da81801370fa361fed4248f4fbe9bb13379d7ea1131652bc3164a21f826a68418a21d32b15276efaafdac5f90ae8f5afeff6e0a3d8330a3f69874463dfcd875263efe12ca3b09353b47cd585bf70495e765acbb7a5a53c7f795be4fba225f76fcb3f2e9e805fc60d08fa1f6e40fca7fa57fa2ad50b4408a2485c8316ba9a867b2b5d1a3e6a47506d05c5c3da21e94e51a65caf6a507cf52e7627d4b24302e8475fdf4d89e4cd006b12d62957b5a8fcee5f16a01f320b97367545c333dc8c94a6a8010b00a4a0e14a102b40aad4867f5468c3bfa137fce947c32391d3f0413e1afe089c867f426af8226e1a9e089b866752d3f018e8d1f041f06878207634fc0f340ddfc34cc3f3a0a3e11920d3f03d5cc3ef306a7822a286cf616bf8215ac3df301bbe94357c10ace17f353c8ed5f03555c3d3c886e762c36bb0e12f23dcb09292bce6bfe6c74df5fbc20dc9fb7dca5774e5ab0e561d1f5eee29dc9294a6c87edf2d0936a651d6bbb20871d39b4a55691c1f3e3a74837770707070204e0ece27d47070dec31c979393f3f9348ee6e49c379f9c11158da8484c96a5e36fbebf9e0ed6fbf244f7f18982300d7f433b6ed2778b0ce9d16f805cc1913ea4bb477a0fef161940f367a6dd00b982ab3ea43beeebd99684f48e7b7bcd9fbb193940ef0ab950fc8a7646505d51543f4afa887637e7cf6c53c25dfdfa91aa5e15d58f5eb47b7fdf3e826f5c95b4f21c806d0e90af346df3117d44b739ba3d2f39a48ffbf0e1c387f4f1e33f7cdc878f1f3e7ce05c741c9168be2c6e68f79a749873d17344431ae71de9fc6a33024f7a8d73518d8d0f117cc387482412511f2caaa4243deee33deea3631f36373353347dc82b221f3ea60f1f746a3eeea39292d47cbee6f3138746898df31b124df5c5e8bbf9f0bacaa6cd47b7f9e87344e771e8d533db21ba0f29a81659605efb10d1edcd6726795dbd8771e8f63b46a237ef813774db865a677029be86565bb814df834a2bb43b7546bb5377d517a7e5f1d179d42b0e2ec58feacbc2a5b88332e1607716180b4cc75f71d050261c77577d517dd1f1d619bc23adcc5026dceb4e5ee9f86a0bef8c1e3fa11bbc23239365df92d4d0991bbc933d4e66f6929d379fec3c06efbc731cc77db71ee63e370318845af7b268ee70f3e1fee8b60cdd7e9b129de62a8fc1a5f889610f7bc4bef413d5d5a33d3c3f17035b5ac8d095389aa737752595ea9d2a2978a371be4cd13e7a532990a355e0a5717a532ae0a2a70ecdfadc39b5ddaeda9dfafaa31dd7ef9456b9896a8fd613bd50665a36c0757d4e8dd2353d1a5f5d0ffbd4340ce341ce8a92deb7514d4a72bdfa910bbed1f1efd5abdda9df51fd369f4783b8dfad4d89cefce8f3a32aad70693e7e5a5b921a7a6e9facbd0dc9fbf5b721a9feb6bfaa2e975e0de2d6de5d726c3ea2cfdd46b513ead12bda5d5dfdd18d4641b2fd3a121ddfbebc73d52e0a92eb3247c2fdfa56bbea3aea7269fbf28e4ced805e6fbf6ab75caace8da85681842abad16d11057a3de737baadcdf92d49a9672525c98e3dabd00d2ec563bfe92e393a3e9b8277b0fab2dc5c80ab7e810b5c807bfd637d3bdeba61c2fd5cdfeee01bece58609f71eaffa2cfa01ae83b8aeaa962a379df88b2c48b062c82414dd08e30562989208c109650a72120c313931c4d4c5bbb4c23bf00dde793d456f77504cb3978ee73280403780b0174e0090ee65d1f16f8bca63d4970527a03efe06c815dcc3d2dbddd5f1917748d5b554b9897e49550582a3e7e604d0c73f5af3707897f4666f78971b1299868f1b10f5f47003e2f4bcfd00a47250e7fd6d3fd47a419df7dd7ea075a1ce7b16513e580071bc2cbc831d562058702f4bc7eff58efd55ec8f5ed9eb81703b9b4fcba8d42015658faf7d379fac6ae1b8aa7e5dcfb28b3725a5cede33aabda256553da3b256d9a7f66b7b7648af6b74be075ef311db4fbabdc9ef45b47b41dc2c3f77aef6e8768c9292c063875806a8f5e2b776f2f3af6ae10ea9fcdc4dabdda9b58ca3556ee2be9c00ae9e84465f3640f6d3b201e03925a5eee18cee1f9dd7289fa36b1ad12eb4cf47b7dac57ed7e89551b919a9fe0e69f649aff7c0ec7a7c0fc4e8f645b7990671bf96e7cd47bea25d10cbf7b0047205f7b2f4f6cbd20b4488511a5750c8604b89773020850b3170b427f5a64ef0059b408ede21ab37658230bd9b8a5f7ea3018e7b5326f0d27b6eb275a65db468959b2ccdb2eac9ba100e68883164b2ac6da02b03a59e4f7b59ba206e98a48686d772bd8799eee623c188a34b0d4f1dcff4a2556ebab4abca4dd5abdac5045cf5745dc8fab20138cad1f19335a6fb8bae693725b0f9b102c13163c68ce90ed5f0165d3640b7292baa74107716a844a7617d5976f3e10a6409314a72c585b91a0adecc1582db94152e5af6a64af0a55766c26f906ebf347f420a451cb176a88e87b47bd00b0fe188f0ce1ee775819b0f57c33b5da9f9dd9021bcc3cd27c23c1f1a1a1a32f17b98e7263e0fbcd3c316813dc2e1be683e0defbc262d1b1d93decdc8aad1fb09b5b845d4e41452ab369f2b9656edc03c6b2a0195ea49b90c44b41f6d91adbb42e0b9055c37a48789b8d1fc216f343f07de79af77e8fd22db28e3c57a550882e9008e4ba3f928c0edd60054c758df63400f162e6009ec327951ab9db71e50fdb31548a8f71dff6d7d4180a28cde14145c4428861ac6f9b61e50bd5cea62a3baaa136e3e92b54a9ec623afab8b7748465e57afeaf6bba4af3748ac3f1c67311df0f328102dde5b237329a075ec39e20a1f65c5872719e1c3c3ea4ffe0756bf7887bbd2ba877b18f5def3329702dc1c41a146ec01f5eac5a5f8f839ea0148a8e3be8e750373194208cf8f11c2089b4c8dce0dd20d69f859bbabdf7b7fdf3f01c687e3ee701a3ee1052708ef7437dcc03bf1f0e79dade19777b6ee4a0d799675628c17effcf02f2ebdbf2ff344d681dfc7da5dbd5066b9b4ef622fef6cea892cbd8f3c6bdaefc646f8312eeff00faa7021c11b2468e301e105817b53b8f702ee3d177037ccc7024ed49b2281977e5fd1161d5ff1ec184343a6fd50cfefe728f24ef53d13bd9fbb45449f13a55d44778568b59b40dc4766edb8e7df35da09b5f65ad44911eaf9f8ecbb45b08b5688a876f3f0db31ba2b64ab9d76b8fd28903566cc983374bc463b9491d7f13d3ce976467f629df2ba3a12eeea704b12cfb543357f7bfe51b819a92efaa42424fdb623da7785bcf7c01ed628d01bd3d74594b41db18ed5ee35f68c76afb3cf8a76afab6f8f9b9278ed3d3c1703dca648c0a5e3abcf9daced7664fbac5dec0979cad5a28beadb90c4ae3ee76ea2daa15a748d4eb95afba4ddebf9dd225aedb43f29d2bb42e6e77be0a4f0f36993c2078f9b4fd5a507c6cde741115f1010fac04a2a9941700956956a91b9886aa97293cca7667d8e2a265459c21b3da126b3c429201696e2b5ac571a3e8857a53e2b5c82d9dfa57ca75dc2cbeb706fc9cbc8d1b73fd1e52105820577d520d89b458160c16dcdaa051f7bab3f57ddac775ab564571b92abfec0cbde2a5f879b12d9b03e282eb31e3632afda695d551c9579d50327d42a8de36a34ba2d437554e7a88eef6644a3af7554960ce5bed1a9f5758e46d6b93ee9db8c58bf3edf3e86224da31be9c53aef19a5343514874bef540f51515620d831422a04511b6f64c02e39638c3f5d7578a15859b543b515e327434b24f9c28e7d4eed96855998855918768dfe64d66659d835eb1ad6c35b96dde2b66bef46df30f98e65a6cc86d52e48b43137b377236b7ebb68ab9d3631acae55bb4af61b7195c612d4e800f4a696b0650959faa5a037a5042b3d471789bf6d7d7f5555843a65d95f95ea59bc88fe74d8e585baa28b55115ee19698d5c596f9155b7b503c1caad13391281389b2f7c08c760f0bfdd9befd68073b3b9765d547d94795a802418e7642fdf3be711fbdd371ee7afc320f8d7cec8220771abaacc3d54aa6067163d73919aece886a2705d5e9a841dc59159dbb4ced46a22a3b14da72e96d496667b5c3ae2a4fc32559851e8a63ab065f86aaca7232fdc2e5cb952f7234017a535fbc30a34e9bfcf65765e34d3210ec4d6e46506d7d76b715beaab6fd566d5bb56dd5b6dfaaee61f989b0abf75955b03abcb2d51b205a70ddf6fd6e72db3639eba65dbe63d1b7eaa7aadd9b0c47afce55a21a847da4d520d8b376a88cd68b46dbd8dac20c38cc28a3e728f283b893c045d7d09b4a4212c474017a5349e8d27367b4827855ac850de90fd0a9ad6f2fc73fab766ff7ddabcbaf361fcbc7fa6efb1a9977d5b99bbfbeccc3ddba0c5dd6b1b66b5cca2ed2e9e44775ab5a55836057efe4f917058205376b1077558f54f5a762ac5ebff86cd59ff7eaef95ac1a976647ae22963918bde82562919f8b69d9352d6279555a0e7198c7b57bdb76e4e6d923efc0ed2bdbfc8845db7c78f389670267de5de65d696ef4e6353c767ce6329f733ba41dd7a85306b347293238ba50bfc5798f1967e34ec9563beed1736ac78f76114bf3fbab5d501c75afbed11f73ed2ad449e84118b3e8ae7ad0cfbb5033b4aa6ff05b5d2ec529baabb2779ced95f52cab5dc4d2d583b8399bc93228a18492ffe04c36f38ec354d7957d9727cbb69a77178f77dc0ea823477c6859e74af2f1ead987401e263f6ef3f7a017914e53d271247ff14a94e2e9c0871b3396ce7e4347db91d1b9fef06f2e1a6567027572de5d4dfa320fe9b4fef53f6a7731fc8f0c6e70742dbbfc10c819ff47ed388ca807e457b7895e6e28fbb068115c9227c2f1e328c5911b3e0ed5fe68357f43b749b4fb4d8d57b8549d6708d491b7a9dd5553bbab47ed381eb5e344b5abd951bb1a9ada7da6766f1db52bb54ced4a1c675d1bf7de7b56fdb99e9d8b2cb02ac81dbbd15cab115f3bcb1ed59fedd633ed6559e68375add6ebe2b8ae86675787ffb86fb57b41b2af739584bdbaf52190a75e9e89843add82a1e1ddebbb1a20efb820a7a1be2b5047be7b90eb0252176e985a17ea7435dd6d98ce5efd26c6de2e7ac761daaa52f6f6eada56bb77c5fa76f9c882eca36b56ed82626761bafa959a1aaeb7d5e9aede6a17243b7bf57825b22062c9281322b8247f5169d5c8828743f570b870842ffdde9411c628e34a0fe94d1d214c1be18bb6a137e5050c385a6bf8ab8b195dda00bd2933a83412cee8526f0a095c3412dae8d9ef563a56d6cc2daae353d3bebca39d93f955bbd7cdfa2eda92647d6ddb11d1db9ed77b7f567867f37991462e5d9f9bcf9c7352d1e7bc2a773dcef7783dfe6417ea6bf9ba5e8f3ff8a0783ac4bf79bdbb6ac79d7d543b6eb6d2efd75523ffbd2bd61f7ef6eb9deceed5ee17edb8be2cd449687649f4fb0cea87a53bebf4e7ba50bfb82181ad0531df95ee82180876f77a386e4a785850f42e7bacddb3d2d6f99fa2fa2eab41dc93e63b1eebf5493bae41dc231fb2d7a3c657493d6e21b9fe945c7f0f8277e2e75f95a7c3fbac5dcdad8b465514dfadc72b7635158a440f6edb91eb3fb176efbadee1ec92d1d7832911ee0f87ee55e9ebd67644f4798bbef3a0da775099d350ee23eb3d5014049774cc5052f6f9edd175e87823289e0edd8bbc13fffeea84ad61b7de59bf01a20517c4add51fecd7e73bedf3cd374363d5f8ef6ff341c745f5514c472565b74e8160c105c17eef82608b6e5d0be21e69e7e87289bb0c8d5ce2ae5bdfe8726954bb55a3ad57db91f71f51dd2e37255abf573b29a8bebe5d7b10bcb3adfd41f174d8d61e371fcd4ad318b9543da31797aa6374b94484eb2292d9577d503c1cac6ae6e9f081972fbc64e1a58bbd5e5c50f5a6bc4411c6163148b5d1fbd99b0a4305586f2a8c27e22e84102e136f8ca98b8570853ce6dfd0fc9aa537f04e11fb20cd5f2fcdcd406ad83464050c2184f0828fbe216e535b64b146d3a49ed0345c82358be9849b10ee021d88ebad385ce2238441e509410228133f09f826602581024c7cae42fcfe0ab05fd10e20c61aba7be76e084a5e42bd54f8d2eb9246da9d3a5e422fc498ae3ab4665c89efb08635f477b3400dfd98c0c37382517c8c94547da954b94c5795cb647d692704040f99ae63d40922268b84e4656959abfa83f56d2038d355551568d4b26ab9be68f490f7c0bea84517ea74574be62b3c54445a259ae22105823345781292ac61051ab596f84563a10ef3ab020610fa30450ce0988703153c534c1df4eb37d4b0766f087e177ed17831988f04dc36c13d2ebd8536ec9902bdc6e1d2fbe9a11e2be0f5e4853cd63baeb2ea421df8853ad17a7578be7c4816c05e1d88ebd6755981c05ed55317f2583dd4815180a87ed5130775e0e321b0a61aa8232b9899d664bde31aaea9a26b8ab42b35ed2190e115f375ec9e640d1fcfbd9fb076f1b26aa91eb723db5aaadcd4053cdc92601d6bf743438835bfae1daa9340ef16d922db0c99a5b0610249798dd59f77ec39f8a23b8ef256887cd0d5403b467b967dcac75d75798ceee762205ecbc118ad4d4c6accf0dadf96443bf619b5b71db13e31aa3d6e49aebfbfba2be4755cb14fda3136e56a207886a1de5bb40b7aa8ea7116e60ac1c52c5d5a7a31c68394981451e91bddc9f7deabdebeeafb1e54f5be0ec23b32ca3ea84ab2a4f9157f1fd7dd1b5eede0fb817f315eb9b42098bc365ae2408eae0e3fa4e57a6989032fdd0d6959fa2108dee96a7ac891ddad1a2d5f82f16d0d4081018fc0bff7cd077e8ea0ab9f924842e812c28cf0e162b5e3ff5cb583df775555d5249abf7dde61867f554df2ce97c7e168525be4e88acae67867abcb39ce7086de6dccdeb0afe81bd3bb0ded0dfb1eae280f29d1ef0cc55c6eccd1b7ef696a78673e488977aacf8aa23aa3db2f8aea396776788d92ae63dfdee8f84c8d8ec73e1703f1dd80dbd4962e5d7d6ee1a2e3b7cf4bab68fc9ca857d14e1e1ec3a89c4f93f3adcdd1fc0f29897579cb1259492ecb1259f3fac4b26f635935721d3bf6238f55ed17a66159b52989cfea8f7c172f05d5d5e5a6241ed3fe532989b76a575dabd877625a454714b213bb0378262aa33bb13b80eacfd403359a55a013fc6844d794d1ed0e05049360f5677bf40122036e7b370f794b123f5fd56efbaca479eb5b25cd7a65b72e2bc9655d58edaacfcf9fecb376afbbeadab794d1f19376da49d897845523f2f3b36af3d867dd155281de18ec9fb5cbaed1f9ae5e5faf630edd0ebddf5f4af45e348488d04365ac3d319777761e81cc43253b952ee0274637c53c59831a4637ccd643dc94c035f194cb0d1b8042415f4a86ac1807f4f475b825e1515b9f4f8a3aa12bcc434f5fe7ed0894061242a2f58fb6786b00aa8abe8ca0b2341093be3e45a24e284b57bb3774d75f035060547fb72e2dbb6af7d634312da36bc2e89a7843c2af6ad609b50dac7475e6e8196e4ae261edae8670b954d50e6e4a2210ec1f78b96dd50fd474504d57755708fc797f8f9494a40b41f3b9e26aa80b38bac8e28b94d93f29b8a5b1374d344547efeeeeeeeeee7e7da25c5bd4d091778554d6ab7db645ac7a597fb7ea7bd64f7e21cf9af85c29c97a9f2999355b216c59f40d357f5708b3122d2b5703c761cc2705f73ee7be1c7091ab811cfdbe4166a4b5e59b8fb71eb4337dd7e80f1fe1bfbfb98f06dca6b8b0d2d9f90f0c2f9db5f639a1f6b623d7b1ffcc67d949db6e67cabd595adc1532ba3c53d147b4e3779c11bec8089f7b0fe42829095f74bea8525292acf95973bd592190ad33e459133cd3d145d6a6246bed375b045e6e4a482b045eab376fa8773bf286b4c8bc8739c82353e1a8765757ffe167eff8db030a8c2cebe119f9bc21e1673fa215023f5a2170a8e30a8150b838a4446fd8dceae8bb42b06d7411463bd8f3a2f9a0aa451b8d221a69efe19176d1a61da3ddeb79c3acd8b333ddde953256e5b854d5dab2d288d882c17c52d4200c2d05277b533510b32b84066608e1befae4d60e895ea66fe855146cdbb6699fdfb66ddbbe4136a632e79c73ce39bf41e61c188661d7ad63188661df2098182ccbb22ccbb2ba407963fa9abfdb0a911797e49ffcd237c4c596f2db16f1e12de2c6c4b6880fdc2239cc0d0167599f2378082184102e84d5b55488db540dbeb47cac2a7aa4b2ce5a8c4623d1b76b9f1f8d46a3911a73ceecd8af5b9f73ceb984b9bbbbbb6fd9d25688f54785b8eab2bbdab2ae6d11bef56d8b6c5b676ac1e50f995364033d36103785bbe3de67d6664a58d54e5bce848c745753397f44d6b342c93aa18ee78effe489a1658592f163edb25f59673548b5ecec336a5c7556974b5dec4ac22d835a2665e58743c5d899f2c3a1fa84af62fea66ac04936d55cbb1e6d2d19f94df5a66a30d41b46738d5caa2aecb21adea2dd13ada5943e117b12051389f51823b5e22decd879f3a9ae29afad48ad4fe6af90fdf1e17a75acc62b46dac53fecf37aefc55fefbdf72bf2aff7de2fc6f8e28bffb8a27144499129293e521e7aacbfc9da8b0cf4c64409d28e40c2a4af6b6fd1eefa8c87588d02b46286863a48475f873048eb4bc911198d02a480e615121f547586d128d983aa244b1afb0f2005f45b21316275f786f8cd887c5523bd2efa34fe8b12eb753d0b0e283fe1e7db247d639826fc9ca8be5ec7bf20856c1d72053aed21dd863402c1671d08c7fabcb8747d37239665d5e5d25521ede0a770fd240e29d1dd75204b0e25c6f4940bfb89e7cf5bf5a25a775dd57a771de8a2b284121d654ad5ef53ac96826aeb3dd0a2d52fbabdb4abb20af4e4e818c41d4f42f2523efc8a3eebd5994ee1ebdcf21750079f456fed1ffd79d5a290afdabdefad2381dd4941b591ed587f60fdd9f3fe1d0c7369fa0d7bbf3b5c1a32c74c82e38d0660fa42e6fb06e9b824b82dc262982324d7af5fd87eb12ff6ecbb34a31c764d797d5daf7e3eadba96bd9ed98734f6f7aecf4cabaa24d7671d617fdb8b8283af589d17edae63cf6a87eaec1d7b0fe43933ecbafa1da356b537f065dc1bd848ec8751c87b03bf6b000a8c7e7b03bf0128307af7063e962d469f0a38888463ba6d51215d511c2eed453bf9778b925e057a5d559c318686da86aec098f24a09b81ff91aba93dbdb4db6489421ad658f5a2bfc7db756958a13424cd5abcc98acf00e4d774f5a7e487734dd6d151eb0262ad55f15ae7b56aad0f1a73d0775c230050faae0c196304343a6ea6f5643b10627c062892b8060a2e28410d37b9519d39e8a136cb25e2501a6f78a564980e9d40510efa71fa04efcfb72ea0208eb553df5409d78ab9eba00a2faaba7439df8aa0e59a8934abd77fb65c5400232b46ce1093490c2128068e1d6845a411940a0828a30c0e0000ca635edbb21407c68c8f4acf04e0d1daf30472be68382833a3c66910e49161fa350c33f2a74ecaa78ab42a96a57fd09f4424ccb5abd83b53aa466a0a02bf94ea8bb7728ff0435455b50d68c2b2ddf89a02bab42d9365a5650104025d5b27ed9220838a02146ed90a081171a70d15a4e7ba1d1983163a630615a867c98400b29b244a121cb0dbc0c051db16db496211cd01063680a02a8a490185a33ae98f69d085a2eb16d98b03ab46d98f6dda965eddee1e2d06d6a8b363ae8b596211e6b78c1041686864c5b4f7b21285458918612720c0d99ae3a44a58b7fdd11fb48e1799b000e68843166023252584306676062cc18134d7731e57ef901882078c75a353a7ebe20a51b86747cc9a2dd1fc4a21b1fc4c3211e082ec5fff003e4f97e59b25f3e0427d36d988eefde90a1a8435d960898147104126f4882614ac9f1cef5fdb78885245e7e4a3ae5773b221f2fdf0aaacfc73ba3b69bcf4a198e4befd8abdaa15ad25049bb47a7bceea6bc8ef193f72d4791cc7edee1e10cea382b4a42b26df49ca4b91b12cb8d9e751f0e1db7acaeeaf142d13e1ce461a44fb48eefe11570ef355cdad37069ab57f5a73afcf636c73b437a8fd121fd8ca67a767d37a0ab715608c7a5bd45bf42b66e8d5c3b8d8b6798f21ba4bbc438c175572ac53bd6e3af317e79e19debf1dd5546c7cf2cb6340dbda92c9ee809b3909276808bf0d7af3152570ff257bdbc3c015c29c8735d619df878a536c0c95fa9eb0a97e29b70b29290f0d050c30b0d0df54fed76f494f8e54a929d038c96f5893c89876d795d76504ff174d88ed7e603cf3da1d644d42cda5d573a5e579e0edcf197144b74fc85857760c7cf07f9411153125f4b150598b0bf96f4b545497c92fca6ac7421c91a813561df54164af4a6b2186a24b0e561cb2a94852d221f7f7d813cf3f11718c8f34cf15718c8237afce5c6d3e17afcac4e920741190d8223b48892e49f894d9392647dc075279e69fecf24e90fda2fda8100095d3df684fc164651876ad20fd949d663c3263d603bc9aa3f6c95891d1307f6d8dcaf7b18b6469978a6594955051a8d692d55723061676274069313cf849dca353434945526ac3163c670ef26c4fbc32810de2f31bd40842a94648b8df93e1fea3d03ccfd5b02c648e5dfb3a4ac0e6f49296745719e1591a3908f3e406f4cc757152746fa3a323361be5f5be4d52c8ad0345c82bfb608fc66797385705d2e5df03d2fe07e4af8e0e175581d5687277255f0b03a84f012874b155311255d2953fd5527c5bfe626c518549756c5e15255e3b542f675a4bc84c98f5b8447ef718bbc0af4c6b05c218be2f798982b1fb748f5bddc222f072e492c3d04765a129a86c33cd9e577d8225f21d7e56fe01d8e4b171675b9d4ad1c71f48239a373582125fec275391c893abdfc859330c208454356c87ebe6a215b52e48339390bb7354e21471c60cef8620bbc02adbce1869731b2e02c72a80186cb17ef8a98273c049959b857e3dbb7f207737289a9e0f693196e8cfd6a77aa1142285a2d7383301510461212d9406f4c77aa1d231b81838f3be440c3b3a6f81b3ab611a63b229b7a411031c6f7e47bf2317310ccf2452eecf5447b565e309f941d8a779bdb9063bc88a5aa9846163c1cf6f28f6e8d57220b1e0e6f32968dfb6b2515738350e14bcad2a0a8a23ab033762d3e5689555b92aca3dc8e445ac325669ccb50ae87b63ea2a26fd47a56e035de81bf827855f873f3e17ab980cbeabcb4f968cd1c9ba1cb3ab3b93eeba9c1d5e623cf711545ec39e2ebfda1c169dd130d42291f1fab808b1d0e5ab7433f2b49fd612aeba1ecd03feffc2130057383f00e1f6e1a5c8743f383f04e6571acb3578d11c8ea4ba82bcb27ca75abeec3c1b2a2956989db69fd6012527df51c5552e368d862ae1acd0dbcf0c5045990f1851496307535cd5de0c296152c01c51263aa60e26f119a9977b69211bedad5e4c03bdcef4436d5dc11a13b225040811449b80109b878c3d471cd35f082186cc00326c8200a634c1a10638b3554f0250a5b82605a2970697e1c42b3147ad0fc0bf2c02fc8600c15c4784298284cbca9eee11ee6fa460cdcd59b92c2962e5decee89d6f02ca525a555c147f992c61f46ba0d97f9efc93fc951ca077df87ddfd673696fc5195368430a3e68498360c7c3aaaa64c532c6bab04a9c4f8394cf8f47e2eaeaccd5adae22641ab974ae8f6248214b0ba03715464ccfd191c773d18d8f9512096fbda8b35caa240775b77fa235bf43354b7e62a8408e827bfd7c78fdd880234c1661a460c38b4cf29ae60688165cd08e9ad790dee33c6e7e44e72ebaf6291abd735c9abbcd166c60e97369f89a35b6343c4e90861b92eda3da3dd1aa4d0977915649b322d151abe8a36b32df68a7e3fb06903ef31e9e9526a232dfcd01af49a2cb54205870a26af3619ebb8876412d7ab5f9a07d24f3ee6a914c25cd9cab1d49898ecb7c3ba823cd542432dcb7837a7451a7e395cce5c6c36bd239da719f1508161c579f682df39112d265ea0fe933f5c7e6dc13adb98f6827aa4fb426d5e552cd359bef6f68179bc7e196e49d3b6f486c6ae4524d7d8fba5ce2f1c8251eb593ddeb1d3b9e7d07ed843afb7c1b92d1b54f8dc2ce7ce8b86b4c5a04e3db7ce846e7aec1de51bb243ae3cc87d1b92aaa9d7c10ecac8b5cd23eaacb259a4a73799af77046696af73ad3b44cbb965d5efb45835e67d9390a8e94e435f7d7dc47f56756129237aa5dd6f361c2f40dbda9305ff4646d447fe6b14fde9064cd7d1ec3e8e4a8e81d63a34a639f23da05f5e8cb3b3a3ef3651e9c8f66aa8ccc8eae1d872eeb68db47d8ab8be894d7dce386849b3b76ac76f3dc456f53b2dc73e76abc19e1aae8232a738d765a6b871b92d1656a0fe428b7253a876454352e6d6f51fd997f9d569f683d2bf6e5daa25d4d6f0f3dd17aca43b2a3df6d5813c38c5f7a06a142413ef90b772408ed621e9bcfe6e1d603aae77c2f0b067847012d47cc224c17b144f9288f84fc49528912894c7bda332b345e89315a1b0fdbeff5c0688577ae846079b8a7445c0ef2c42cac231fb97027e0580d3041300f932db23c6f75b82f4645c60a8704ef60f3b23e043c2e8fc41699620bdee15f4a704fb4e4f1eea5a065f7b2cc772f8b8ec1d437766bf3014af29eb5c425eed6e7a3d9977748b366b57bfd361f1e580dc225ee136ada630d8a768570983684c4e90aa7bd7ac760ba8d83ab5a65c970d04acbfa5ab504f91015de01735d0c86c1301806c360be60208ccdcb7e2051559b0ffd7c599c110bd4c112b78025eedd452c60aad9042e1eeb4a6ef472cb13c4cbfeae7e3d8c5d9a1adcfe71f3a10fe2e9302f6f812d925dfe02bb635d067977f1d0f08e2b32f35d1e6b75b0cfdca2704bd827ddc5017bfcd1d869e8d1d8838c1a7bc647de31d33e33eed867655d406800725931cbb22cd60ec3fe33eb31e682b7eca81408652f5c92a7817219ac233fadeb0a2685152b114bc462a5e52316deb1ce13e4f2714be41253318bf845ec12b7885cb4b4b0c737e9eb8825628953c42cd913ed7d4a6cec40a3c66acda6843b56a7bcbecc7b98e68f0aef64977f55ac200179e210d491358e1f78e7d56efe71979fd84f7d5005a204d7c395c62a168d54b029b824b5982562611d797e82bbeee339d915dec1c23b399717943e6c0e4409ae5b2f4347825e73ff8955b8241ff43a52d9275bfef48618c02539578ae6b90b97e483504eb18edca279bc6330efcab3c225f98cbe2a43b50eb57c0ffc41bbd73f6645824b12fb4f3ace91380df1ce2fffa88cde5d3d7a7cb7c777f3c1790f531aa78859b8241f3724204f105047fee2b0fa1a7d3ce75dc4a204762478e7466e4a6c8ebdbab9fce90d6d916df96b4b62736ccaebac76da756cffa0db3994c3a1242a7f436d68d412e43d68c7f1a0dd93a2e577d04ea3a171b4fc0c8d62e9a03234e835577be0927c10508a6ac7f506a6e579803c41aa64ac76ef4add717d71252ebdf8e50703000cd5e7d27804e4e9f1eada17f2f0f8b4e941dfeac0f390d9d5e9b086645cf9d2dde392c282058eaefea6e01d0534192890a3cb185d69cb3b7b715c92e73ef368516e2052705d3d824bd52d1a342dfab66ac4fafb838f111e11f42785e5e1a04ef51a2cdc0b5d91a1abbfebb2209ca14b39c83304ea54afa9a9aa9935acf4f61763f52e8d06c74b07f9d2d5b3cd6efd3d296fa099999999a9535ed3d4dd8c482c0dff635d14e31b525a228ebbd3e5c2552e5cb8cc52432eda965e6a6aef4a0fe96e48774f8234694aec77a051bf77ee50c9f657a7bcb6441aa4dd152e554510c13bdbabcfaa6a82eb4a9a88be2e5caa3e04f2bc14eb547f4d0d57c1ea7c026efb7b61a874f52707efbcba7f6f3c387827be3a73c13bef87e59dadc6eeddbd303559703c4557d5bb1c302c6ca5ab6a4bf0bc676cc132469632b6d8331a56d7b68d480ed3b27603e2041785297a536a84e90474b5a9284861eade1b5d7d5e1ab46457d72b37baba361f5831b04f46c85b3ba17e337f61b6c87675a6b2bb3bfacba15fc5800edabdd6f1b75d54bb6464be292854a9debd305cd4e82226c53bdd7e19e38a17dee99e145dbd3a02ea54af9e14bca343070d97e439aa65c74734caab2ea241afb74ac4952320cfb53af0d51b5667027976d4bdaa63e0a23fc057df2055fde12d23ce31c37cdab9aea6b7e6cb3dff040924cb530375e695504aa3e7909eef560a3d2f31ddc2d1538eb5ac479f1047ef1bbda92758e9f9b62b46edd5c825eb536a16ed72f0b96a0e123e962b2b110949d5d7635fb5136a4bf399df4b862695461cddedd0f37287b933cf001e5e0379e2eabc6bb5d32eab7634dc5e757ee1ba9a9e6754efac797d8bd5cfe56ebdf0b04560cff7c03bf0f39a568d9f75391adeb9ded019e6f2dc0c8809430609be4031cd9f184c5def6a4afd525f600af2bc3658872b987acd7ba3f9184c355fa146cb7e61b8099cf6ee85c1ded958ef6448f0b1e50b0375361f180662b5abe1c234bf30d59f1b5be435d013136fbd7b5868ec310cef586737b64812ecf1baa571c982627a30d9caad9361822bbd6458f9a2bb379361a5caec7d8d4d3f997ec7e982f45e5b9e5dab7d2e1db8f8c7f16d6fc80f79386e64c8c6c3de166e5369b8d1d87f7dbe219b38fa7a27d361fdfe96a708a873fdfd7d3eec61b7ac211d9bb737a48b80dc3eec617fb72c2cc2ed15b145acf0cef6f587c5c9f210813ad7af77ef4a5f6f8a9417bcf4f56ec9d0d77e51c2624289d87cb08a0128a371e9ba240279ae9711e98b89bc31ecd37ab76ef4757e115b04f6f527bc03bf62fa2a69d17e9de6fa7907feba3e8477ac5fbf208f56af2f0e56af2e485f37f4f50d72d59f97a59a63ee2f300f8b14182f3d7dc03cb2803d7a53604c10e5a6c030515573583ab47089a280c53432192f535bfaace036850617346cd06f6adad2c7024eeb4da171853633ecb22a1921bfdd4451cb971f35abf3787a2e2c05a426480dce6b701ea4870706700909a35ac4092ee8c2124800042e0c1521872014a10c4aa8c218bc88821660c069a106b548cd6b706a204f0facc8b8124697242831460e52b05f9c30c51b54b420c11560a0841ab210079003f4f0b02906f0d4bc307051e2892f3c3187125db814fc404c1717f8c00827f842cb424a86484d4d4d0e438e5373419e203cd4bcc4a3378586959a2f570c667a79b4d579df8f9667c8dbff70bddba1c78ed11b243b6a0f88db1416b26899de941b5ff46e66e03aad9fc6f1ce8ef5f79aceaa43b8f4fe844813e1d2cb814b3210591f625122d67b873c1aebbcbfcf74b10115525faa5071c3c4419e0440a105490803054720030d9395ca420b7ad4560a0b4068abad9f7778da7a09f24493f521b57b42448b02a79558e75dd2201cd516f2d4b0ce7b2f03d70de93784085731bd6bcc7399de69986753cf7413066e0dd09b72021a0da437e5842fbaabe94eeb577f3665b12443c6eaf159471607567020050ea6c802325124c67f8b8f64e4f53bf78b405475594772f4e20d2fe0f0420e1c5081558469e6a85cb7e2eb4e6abdfdb6a8a5fa9aa2096707c0b7683390e6fb5803704d1c5cb703f30e3d15e370896bb8c4a78155fb4d18dc82f9610706f04e47a4076622cdef164cc3f36b20cf912d545667658d14abb353acce76e4212ebd0d1319ca31d4f0176f3e5c06379f9921398cb86ac8e693c30e5c82b3b7e6e9e0e35dd57b3d89f5765921f09dacb1591db2ef495b04fb1e678b5cdf13d9e17fcf0af3ecc03af035e4c009e968781dc23e9687db418a864f6686c719c0801d18b0430f03601c3b7abc382a4b66347a59e268781e3a74bc8777609e65808ec34f1f4e54694ee4c73b22dd00743adaf1361b6b1e2fb748cee3aba7838fc75fbc8343fa1f5fb3456e1e6bf764878e1fc225226ce440a4cbda58e71fb5fbd13914c90a81f7411bf07080c7a13d70099e447fe012fc2910acd33715032b04be86f2b042e0df18aee6d18b1435f4a576e0b123c61d766a1ebf5b5dfd86204f90439b3abf90879effd16be872a9c73b083db2e58dd5d906428f6c816375b62b3db2458ed5d99e94d2235e50599d6da65d4df7a01d0ff546a1e179d01db4bb9a860e619e8b0814b86d6b46874c5571f14597315e172ea5b834aa8f0b97e087b8f7ee71e189a61726d207864b3b0cd961072babb352acceb617537891c50dcdda7cacdc66ddaa3fa2c74ad34f0d2ec14945346ec9aa40d593c0f9c23c30eba5e17b00827730c0c30f4130d961871db664b1858b2d5f6ce9122feb9decfdf6949a7c27e316b1daa7ba455f6f34029d9a774b5ad552e5708863c0ddd0718cd5d932ae33b85d9e235b4abb63f9c41bba874bf071813af01cc765bd70592fd7e30279d6d4dd4b42432f0d5faa2117c8f3c05cd5b958ef7af016041cf33f3090e70d411df8abaa1dd7a3addaf5e8ee8169ebefe1693da36fe10ab17ed1b8422cb93ad6214e0b76b4f5adad77af4ac3f8baac533d2a435c826740dde1376170dd03d3f04f8da13678e70ddee98680e19d30bcd3bd2c6ef0ce13c33b1dfcd2dde3d2f043786708f21cf1a28cd5d9335667bb1a526375b617f21cf1a28dd5d9ee1e96c843fd78a817889044f9e0ca5b3a5e7b01179178736c116e781eaa1b079216a47a535410d31d5fe9f82726fe71895ca5230f757c7174acded5fca5d290eeae7ebd5cc22c2c8df2b93eb7cfad9278d8de7e647b3baa35dabdb543da31e1fa69e7ecbb41eb93a21ad220d999758c76ac465befaa7635cd30e02db8ee71897f5ca0e4c25bb03037c824c15ba7f25e7db7089bac4849f01c592742b62aeac465a242c5899b760bd0c9b4bc13434cbb03e8660c20ffc6c0452ec977352c044e26a0ca4d565dd62aba26a85d7f0c9f981ba45b31342b6b84f4d2e0630a8cdfd36c91f7fd8ee5deeac05f21f1158df4d2984609e21687d8afd2409e1dab139fc30f211ddfd5d0d0f11de76369204f09eac86befaef98eebecbb3bbba5ee47773f5a6ad97e757c09f2fc0075e4af593baebb52f603bcae2fb5dbd1f00b12da2c7a956685406e4bf017d5b6046fd16be396b604bf4f8c70cdcdcc15e8b4d56520b8247fe0928c0107cfbfb8d2622ecfc7020e0c1cbd4f63a8b70671ef0a29cd3d15eee631c16d8a0b6f3c2ddcfb07a0107942240c1422d0fac23845f60fbc06b518ad5fe7eaf15a9402677d317a75649ecbc4d79429b2abfa132febc5710916cc1bea36d5ad17540ec47cf144962ec4000106a68ee3dee9fd0f44204f34a1b428430a7e20032e84e1d2c5b4df0ef048881b1a1a52c2d4057ff9b2f2d727567ef03d2c11ae754d8b6b70f25b7a3ff1a5595256b177ab2a0909cb3a85198db95c5561a25c21fce2834788d1aaa4b42e2b462e55bb9521a394d6ad68494b6a518bb1bac2c719151770b451d15d21f0c917912023d75455256546238410c65891c19263ed628c70a74c4308e5254b2821e48dbb7079638c153db25d59307209725f1abef7c6d0e27bef3df9a2d630565a600577ad1d3ac65855b08a558c11aedc8d0b81c010468ea564b6688c114a1821841156affa62c7b02c2965aca28c31ca48c61c2d7ca4525ec77d62d627726bbcc371795f18ebe58c28adadde45f96e7445a3bcb6ac5f94e1e8f93448af14146a74c595e3527c8456d5b8142d4bca48a3bdea4549d351639dd8697d6b0718c0e6352dfbb65d1b7d9272b1aa16aef62c19c428235bd6b7c36929e5b32c29658c52ca28259dcb3b57ede275415945196394d10b8c5183bced25137fc5d81be37bef41186384ef6dac1d4da49d55bb68590be3930f46f8e07befbd171f64c2e3088f6299379acfcc3132f31b83991f4ff8f8bdc7f5d51f4ef22056ac71105cba1eaf488160313fadcd674aeefc47b16b9fd5e6a345f97759da26dfc355359f150379c697393ab23d27a49b8f76c616a8f126e5b551a6082e61e74b797e458160c1cd6bf3a9a4b5f5806a09792ed67918d5b4b8f9c80abba86d3ca337c504ae37c5842d3dc6db7c887072b076343356354d68a3377546aaa637c5042c4dd3bc5d5c42c595592e2dac91af57bd11243f34341424218410be43f898f911a6f16407992a6360f164073482387223e1a1a1a1a12006f39ef08448c30b06c146a2230b32a27842a48fc6fbe3f7becdef6f4a7cbc01720517bf0da34f8cf5c90e3255c6b8a21f8adf2bc15cded146ef45b8f9402e3d7eec84cc5e618f5a7d59e981965073876a291ff60c7b4a824012844bf1d60d1d6bb8147f511c2ec573a9e31f18ee7aa7753cc73ca7239cb8e941f10af01eaf9daaec819d50f3ab5f60b88d8f16bdb8344462818429ca88838c31465c6f47736dd7bed77827f2ab91bf9bf5f9ea463ee34dec4fd2e7040e6edb8f6e3f26703fd66577ef79dec1b2d1948f8eb0ef365f0d824b2fb6557ba0ac220a3f696a70788737ca33a27c7f4de86826d470fe98c03d4b1efe86213910e11dd161f7ba7b503c1cb28a2ac38deebec3cb08d3f0416ed82dab71f389572263bcfc46351ab92463d63dab64d52e8865ed568d0d02b787fd603e6d7947bea8558fbc237a553b28379f27695555957c15a82da8844a1405c2548f928e29443333c2008004631440304820140b87242249d3851f14000eadb64e549fc9c3200862c818638c2106c000000008008c0840027287c87f9613048de68563e7e7b7437c79fba550fa2ed3d3608e505284e2460d180c4d199d905e55a6e3bbe013a98ec4f2e352d1a66164e66db5b714b761efafc342903e27940bc1378c648d7dfb52907cd9a4fbcc5ee6bed43da76cdf14db3d85e3865fec30a53ba6f48efce615ff828132fb4fc9ecde9c422b8f50fae28446dc66f9ede696fcc3458ecda791958182dfb3febd17cc343ecaa6610a5b3f6bd62d2c801461d6b176428a8edf26a03abfaa148298a74a99b7356cb8f51dce8882b545e461646eab96676fea1e8e5c833e82a12f55d108f8088916fb38c8d6e5bcc1b4ef3f5b4a9a1c83a4a9e77806118e2f5251fe7de0007d8c06204b06f6165e2568cfc3901366a86cb36939eb75ae3f717afaf8a259bf8592b07216d2b9159bf6248c6826a12f99a13a65c1cd474722b2bf39d5ef0a06021d2122e826a03e6ef98abb94050a3604f136edbcef14ddb6377d92fb26921f6337d15450756a2fae4a5ba85630f4e80639fbd5ff8b9dfc765b016f83c606371790a81b1b6a9e0cbecabff333a17bd10e8dea7cfc752c685550b30082534cc92e11a4f5734cd77c0d85c9b2c4f25b049794d0cb772dce3c3ec596481f6d7bdb31302aff8795e88c972956ad75cb536c793cc5a7fa90d9c930ed42e21281afbc2ae5861b7fd7ff7e327cec4ae795182edba93c750b75169d8b888f69733dbf05aa7c9b65e1f88d26500a592bcbd612d9dbebffe1e2db42cbad6c5196d3156c2731f13bb2553b802bd19bdd8f8a3f3d0a056f553d5e295c905de0c6e379fba209375a458abedf72e806857f6ac1dc4d625f3e60cf99db90fbba6bd40a119f61d9dc84594e6068eaafb9f8d54b0d8a85372b5a8aaeede30a8d4f1225b052a740cbf22a378f7c737450d17c648519e385566cdc973255847119306c907a73d45ab209b4a6248115824181fdf37b9ca43d2fc06a7148ea6aaa21868c9fa7abeef8c65c94b3c3ac848fa762fa8a1275d78556c81d75aac781905076552cd597cf509b5c93a1b2c71d212418b5e3834dbc2edebd2124dad2758d0ac73c8bdf4af35a8b403aa75066f3ad6fa0b97b784a638cbf49309db666bf7f4fc12da24b9ea26bc844f31ece1a774ab63e0a050ef026257d519f933608d6fa2a7e446e54d8728711909f80497f4e9900bdd85180cbdfd0dca943f75616769c5e328e44a15070bacae0f937e54be526b11036a6c1e5171f6cce4a6f2f1133afb5d22305303d35295b51323290a5512ffd4e452efd56983ee8b1f9f26b1c2d668d9a562a2a50a635950e82835fc0d545579d2206a654e3c05742a6f4d2a9ee1e79857b3ba4c3f7614300dc3cafe42fc51c0c84fe65f780264e13b54d919a9be5af98ff3e9a10cebacd4fffb4c167e422fa923a97fa4033cb693ecf1b646c6877c291778fc9c5fb82bad661b0f2997652b68763aaafbaa0f7e9404411c51b2663ed1667c832b43d061f6d1c971f4e0cb2f0928f6f04423d62a7c21bf0eaa4b47aa8c0624216d81d53cbac59802d6ec196c000420495382c452092dfe80973edca78aca854e8199d615972ef8f9b45b5928642a1ac6ab8f5b6b7a6a118f13d6335d9ea979fa3ba88203c83ea618275c23dbf768bdb3aaa243cd80b95197c5606b8d5fbdb93301714a09326a31a194c623ca006400fb05e137a4dbdb7964bd8675c03786325e7ebb385866061cfc3a63de5008d2f26d660713637234fc9a98dc7cfbb0224b90b678f1f9884b6e01dc9324836caf01856df01275189e1100c0eafba2eb86a59838460158b1791f93081c8422721371e30452f92dc6a218a9040cf5d4834074b6b12facb7b861f4170f097dad305179ece7dc79d8557c21d1152d8dc2cfbc0154a0856a5c3a691ca1ca3736d49a9b67b258f20ecb138593d7f849490b53a462242eec0b1284a1cc0a7dc11ed6785e4cb72cf047e99c42924c78d85371a500868403d954291ecff3ec835e2fa8d5f5c8f3c541904b429cf2a193db54795aa96adfc4a864e178c60deed5890953444a89e29464e70501cb34e9bb478a6820cabef5194b51da62af04bbf7be7d435db924f0b599051a76a29c5d4ca363dc69daa2a658a4b3f8132468d82d63395758fec8ce9d23601ea556fc1b103b9a4edb86d69aa4077ca9f3c6661f971e3b298bf4f55895aa3f1fe34dd3ed8b1e93fbad4c633b0b76b5287303ee592aab454508d415c61abd949386315629c372f7e2e399b060ad3f97704121fdba4ac8d92a88eb4622ac72aec827cfe4f26520a6e41813b7217acfb39724ac5fafe127f16ae9d49c2557ca9666a7ef8774555a17efcf48b6d9fc949d902ac27dcc210939bec103445e3dfebedebcb2c4e948aab90446e8a700e80a64e28d221f9ce15c275af23983270d99c120220c30861ed105a31b65408889001a6f4362c5568a1cdd791c416d9aa0419c05832b1304b97e12ee1d7aa23ece7feca920f6aa04a62554eafbc25b10c4d228c07b27f4376ec4afb27b3a7b175121a912755723712323b557e6fb6f733aed200e38ef49ae1f6516a30bfde2f14f71676453550de682941552dd35aa577d2943f542ae60dec8170839910ba18aadf29672c21b3044a21f8e4c98e3a72b4ed48dafad46b7a6a5d69dfb4b68533d11585285fab843da11bab8d75800b9e85b2b67ad701432b443aa33f9eb4c273048dd0435e586aa8c037a5f691613edc404d0993d59132d46531f62d6c1ab5d6a59d38a14320fb33595d766d7b74c8ac5a1288e8a13a0293f0272674b0e9d9acd1c98e82b0c95f1cdf54941f0f370817a50806bab05e118f430a7773bbdf41bdd04edf5164c196c7864fb4823e7f4f964606ab37628eae39f7c2c27c7b0cb9da230bde1b8599e49ee35064c9716d17f1b89f80d42998ca1b9de653db92255c293a533adcfc5b69aad932e551b7aa8a8c5e456ad1aefd1bd8ea4b13cf273ac3c56a3507cef5cad85f4f5291aa79550f625f57036f5644799a9f4ee8fcf4a2925d957fc74c957f526d320c3c80a87630058dda416ff55e04f290ead1beda72ffe3f77b45d14bd535a12d0ed916fe026e91d9c4126eb28e552dcc0e97fb175e4c53643bf8865b4b620706db81b1a46c72de1ebdc87fc072e0d28c4ad5815d2619e63065d3f33281d5a3512cec577f4b6101e44da4700c8d745a5aef16b10013839a717af6b743057ada2fe6ec130dfee837ad6e62d2c8f7336f49a6fcd46351e0b2fb1408239267c8d205d0f89df47f33dbd0d99e969fc22ba0c7ef5b7c540c81f5931b5fcf08181e8459757251482d32264632109d15164d60d174396d558be5aafe24b4d65f21e1fa730ae31f9564184ab4e5b1419f37381b735015cad34db7f951d0567e41c0861052889bfe5c226157d8ef9ad8d920d6e3be56297a1a05d5710d7503df326a1baacaa557e0839720c10db6d269c91c8974deeb54db57b755752594b93acd1577693cbbdb88df0cf1b593d74f14c42f7d930e86b876e3b1359b4e4dcff27a4a354e2b617a2a8e0018e9500de18b3c98a88e8c2bb4e754ffeb05819394c63214c2d7f480bb4b82f50537af394a0bf63bbc035886f11d73b37f07d8031033031e070bf15d67f35905269e0336e7688f3a5ed516faf30c6c3679596718fb6d217e489ed78ad1034f160c22305668573d4e0947555d3937abb52167400247010b873630e13e425a1de400c6f04270c230e67416c1264f09613e6a50491244e6319934f11fbac884bac85f55aa7d356398e709159d5ab5127d7c66d9031fb73cfd7236c4059b780bc966e540bcc502cd1c9546003cefc009f2821e854e666e388b8480ab5dc1da90faf860b65c06d3199213c995106a40226a011f472974dfc43d3ee5b44561029929a0fa2c6b36d5df995e90224fb8b1aafe1d1986d306b351595742f955b5a6db6330fbda2940b96d34a037ce38a0ce9b66ce97de7f69b5e972307ba982d91e596af53cea70d8fd5859fe5033bc8f6701d2d0699dc8cc8428f6b4387d8a46c9884a7627d745915508a282e75b3cda5ae5ac4af1b892d30417f1800fc6f375955049108a915e0e40c6ea7b7611f8c295adfc2b717d41f66f1a38d1d624f776debcd9d839b66c358fc5142ca92415d3d367eaf20ac4569d601240c0789824c025615ba8e93a7076d86940b8a6974417ec52e92b30d14ea88ce5f9eccf674592f292b64d9e3fd102d28a5002c4681a114c2f895654aedc7902cffc3fc111e587fc1906a9ac72ed83bfd7c5043251b379a76981cd5dea657a5c6965d8ff63644d67bc1a1c10074fb12183cfe38e3b62c775721d917a97e158e10d5acf5db24226d2bb72078f1531056f165d86b87540d223c1e49ce25980bf2ad011c2848f456bc50bf731968d6c23f3bd04e23d6ab3ebe45d52413a8e9b6378f755f1cccce303b0dd070a57e947b060cd9312ebcec6bb20b624e671bb8290c83c2daee1938ecda6d9be61f33324e99501de6ab99634c60885fe5c68ea0284e8058a95fada4ff5d72bc4d6c304d4d00a1515009b8ba1cc2417e101a55522199e7990acf0b27b6b5b76f99cf1221ed15f061cd0022fc8925c90332dcd732708ba3d10447443bf07980471916e146c963f187aa51ce857a61fc12d912b7bdde9cb9278f163a1f41cae50a310136689373934019fbed6c9b7e150873c6c6dfa4a4adae037e1826426f9e942d566978367788e7d8356eb4bb05b707991e4f2d39ff113fe15bf4430e6d42f4ae8aa4b371ac5e263e760754d4f0e0ccd8089ec13586ed7f42dc2db1d1e0788087b673cff266e0c4a1dff4d168aae009bcde51ee222594fb4aa374d1c373bcf402c7ee658de07ded5d256d0430f406a8d6f1b2cabc75551388c181cc49acf44e3e239e8c9573a61ee68fa9659c12bfc20dd76fbd4e18079991abcce3cf658d69ffdc45e3fe8cff431439c65cdcb30f214a63bd008bff3e6c6becbdbfb84c6f745a5c8eca69b21dd57cad3e6e03dc27317998a6e5512f7230278d8b11fe382d2c2f252d9c2678453c9a7d718a0f0e054518f5502622aa5ab52ade7dc35ebd2f820fd62aa2ebb8a640ce953502b09209c9cbfc4b05170013f2b04c7d077d71a2dfc9f9dd7c17575059db23f734aca880181726cec887b9a60d4197d5da604bdb8abf728eedb9a4c77cee938ff90a19229ba8b69d22120dae67b1771df4917d02154e3fe2588f3856c123af717fdec2fa4e1f25190422433a7d64810677f65f8768505657d0a814997fe84eae013d391851b928828a8995608314b30fe46dc2ca96adbbd96b8bb282b4f12b9ae00b2357631f318cc33d23054dd78680ab0a5dceb75691664edd5f722c70e9acb4f215412425256b9fb0c9fee56d655bf440a36a85e51c1de73fd2f490ddb40bda95d7dfbcfc835ce25fbec321923280b72075e12e495e11d51fa19f7a6e6c7d23f2ba157462ebae863634be49430010ccc27a56da89e736cd88efbc0c3e1fdcf81217a96274f18b5146048bbbdd570c174ef142646d2aa593ea5190c1c6884623bb5a6346323e393a7ea6d84ffec6c4bb6816bd25429158b3c0b2aebb19c266d27425515c5e56d89f16c503ceac3054dbdf33f412f30364b67131d855d413eca9966d617c936940161f6022a9be7c46d43a00327b2b8f55ca04e7782b2ac8c84a52b63a7b4cd51dbc03424161c695ddcd9c9c9d22bb79f6860728ede10afa4be592115bb132ba688a2017e82b55c11a57a5eb44e2fe69fc33b93b3ceaad1674b302b5dcfa11292d763a4468fa0673d980ef4f597d8ce148556628378c3604e2f9dc6932004cfb09c51d9d0edb677534b871ff59a86d3cb3393d32b22689207da1383c5d6f3e6acbf73689d6afef135685dd88a391f1b621704c4d29ad7ffc5c15bc6a46a5f0b89f05d6029245a27f4e0c61b33f33b5e90cf25276e04b8e8d71c4a523edf77d7465022566dc98a6c409bdd4e40802acbc3dda25542c8c8b1a288943ccea8130b1db8dfb36b8d46689239f2cb6a41d2ee4b36e3f5e38924fd872efeb5fc5ad70eba86f0ec2fbb957fbb14990e0f97be33e52a1be620166522e808cdc165682e2132fc7c289d89b5d6885a8ce1411428259e879cf72ba6b77674f37b987ef040adbdca531dc2d6e4856b3427a45beeb31c3fda22849762e47571d6f31da97539b0cf7d7da4bfaeb849f073db248cd3bbd489378a0da781c29a07fa0be4ef96e305f1c56870fa9509209abb1e072b2e762d77fb937088e35e53a622bf94b95c43b9918e9b0c52ae2b12e92ff785b63b0d3994cdc193c1f126cd5936a46dd27c740b562ada718efb40f914bd191c63c93a8bcb8b517e019988f73d71880738051697cbf622561d1e66ebaf642f93784f3dbe46b98c73c46d9c63db767fdf95cac6b5533461d4d57115c739f10157a921d082058592a5f45b1cdd7bb471330c5b43bfc162bc7ab6a34376530db52d92b3c310618d18f108da31cfc13e8e258318d0afb351eed4eac3026539a7f7969dc7837ff23384fad76fde19bef9b95f19515e9d7d64d7bc2db28b6c157d502d99175397926f2716627d2f1dec888f62991c6b83f4c64b0cec417a015e1207e6495ddf0cd55270708d4f4d2bba047cca236a7790d78e103e5d2c655617915b49dce7361df221f2a66220aa47ea5b6222ad084a1aff1b069a6d39b7fd4bc16387ba01b7e90eb119232e491627757bbaf805137b67f384820e2ceb1284ed0f3a8c2915e719b6a9b9f5d037b501b1bc7b141f83e21c83ec645bfd46bb60ff49ed0f50443aab45013289e373205d1338292fcd19295a720f48e79162af57ddceacf2f3e09b500b17cfdff8bae9fa4944ab2b4001685e3e2efbf8f0a240040f82d9ed2d9c2b9d3b7b0074bf02e53022ed42b1f80796bb35a0fb863315ae7aa38cc362fd7f1c5ccdd91349603678601620afb4af25040a3a98fd3ee1a1952814a8abe90ac30dc0275df9617b48a1786414fe2e5b049d0eb3e334f8004c48cb47b6e4bc6d9f403a3608deb059700d5b04d1836d40d6d2f57a3d45c8023a01c61ad21f3ce7e5be9e6ffad109dbca6259d622b58850ce5ce41241445a8cf101f661163e6e6f604f83e13cd8772503b7ddf70f0fbd03adb1a21316b655388814f4ae36a30e4474e2c1b72738e99bdcd5b041aa0f8f51818070f42deb497d81f0cabb57a93d0d99a1feda57cacb58dfacbd8f9d26169578c73d6f64be37be534514f971e7574aa3c28059e98047734dda794875819e9f67b535ba9c6dd7f8c9710169fdb8dbd99db88053d1fd5627219219fe2cc816adf7238e522ba8983d1103487effb21e01248a8fe8202aecdece00707aefba439211086ffe1e9202d408d744ba4455ee596314b14148178f79f228310a81c527a7a31fdc4166cf3fc2bea975e51e5f5052ca02e981e75975dd3ec7cf0a9c62bce129ca6b463b4eed8f6a084bd14013eb70e2988df09b791be88dad78dae3e2bc6f2fb1cfacbd5136d9bd7d92e77f25efe9ac61c3b49a051e1902d99b6204e81b21daa397092e494d416cb3abf110cd13a962d21fa9d9239d1b899e454ea350a23ad4e26acdae67056889466a6d343c707b0ac5ad1379049ebd887bcc4755ed04e35c4ae777572332c1a7956d295262c376f07ab002fdeed0e6bf7f16d991165f7baa0b80bb7bfa71a28bf68db872db630bde03fa2db0de90cd4a3b7dbba3e8c3e000cebfd48726bb202cdfd83212409cdc11f3d0d9c08c0d1ef0e063e98ab74420d9c5c923988f137eb0acf2423f96084babbed05ff9258faab04af40901372317c8b2000b73054ea5db478c32becae2ad33f9339c9173386bc0215932cb50f3afe04c77c8c91371dbbc1664bb4f2288f7f89ab1921f5f0e360bc1601fd3931d5470cafca774abc80536092c865c5ede3c083058a847ba813f1e3b433d14d0cb7e7a59e15f09b2bcdbd11aed4e4b26881b8ad450f23b566152fe393d001b359605f6c27144e1154bf803073cd6618c0ed4a1b3d096213a6173e7bba09b71a38740ed907a2a8cddef183994998612c7f7fb97523ebcbab5486df33166f057c6e9022357256d7143f92edd529fe5fe1002359cf14c0b470a1f1821805d4d4c5d2043f64af550fe1146e48b3a11bddfd49c9d4a0ed364dbe8f354dc37b58d506b70bd4bcfb6e4e19cd98456492418e97355d50639864695fc2c8b9fe41a3920b812d40192e1239f312a6d3626625664ff481c46140ba71b66f85b8398477463e653d8a62ba2ee5224e05434bb5f9e45e1000dc1cbbac35d3c3c656a25888ad0fa59cdfcc2594d148d70e5a92cbf70a4dd9383271316ced44a31219f3da11c149a3442050aed96b91c0a655f6c127c6f463748f61faaa02ef06438db8215109aab481ab5f8ebc35b0035ffab6142587921190ac5e76a5c68c8b30f21be547aa112110a9a960de7b5de4bcf16dd92d87f893b202c7710e528f1a099d647d71e4445e6241e53a473865f863f1ad3b8a047c3d25d1936c710347a9270b432e984b6c9e50727f74413a76dab40d59196c916830c2badc0d8b016c1f72093122239bd2c5b23df6c38c296f312b9da7c3ed3caa0b769b97e044463c9442162ee6707631e83f010cdc30dcc7ae0990796261e38db02171ea2b52c65367dc82c88f1118dd8b1f509203ccbf18b4caab303b37e10a8774c2751c83aea69111b7450038f8d35c67bbd5b78a2bbdbffcbb3c77b9da27c74d213b80e66be677b5b931e252a0c8aa010e8c6499b0fe9c97d68d9a6a42c26a5c5575f3ab632a8dac9c236bb422fa97a8c6298ec1d4e94bdc223ab33410160e101064fd6a248065d166ecb3e182eaf289e3bae18cd73f38b3962e55bc638f34df6cbbdb3c01db93cea82a8a2774094add36cef8168bbdd89c2c18219ec5fc814d9937a486181780e5a7a076bae97b55e4a9df91cde2859ee7332d49cd90b7442ff6e6a9d2a976cee36209ead1b23d57cca183ea910070fc0b970bd93fff3b7e94d8afc6dd570cc6d912b25af7822678ece51ef54e4f21db503770bb96ed2adb89b1467131decf2c09b4f7467901c524d49fb88dd1cbc170198bb3211732528e2c7a386c5f6b496001592da563f33ce4438444f691e7b7647df53a478942bc6bd4aa799118f3131c37063cf50d13611ecb0df422c0fe8cc0ab01560ed9af86b082247933168620ed57ba296af90176b78bce15ce913161ab43a1bc0ec65a2c70c0f4220722acd8b8c82a9f970e84ec156525cc62eabf4a41552c80bde034fde5591a5f7a70b4448e5e8bf5ef2389a131d9e627a6e30b914c97f10ac819c78a2c0cceb4a1d7f5c8091315f2756cb9a9e3f9f83e4e0d33a63f39b0754a767bbf8af1531b503529277257c4b3eb7f3f2d2a057aeed008e6020c0d6d248fdccb2cae086eb6dca11e6d88bd6a5d16931e4fdc456152ed48e3a29ae47e3826652655fdc5ffd34d202e8d185827d9038fb2dca3f382670d3f1139707905bc788122c6596c523be876f6d2414d241c68025615271a05cc0b8f8e3c98d495b42dcef94502de8c07efaa785bda05a99592e945616bc0580ee6b4176a1c86b3050260c6d8f0f97325eca7aa3239e14b842421d481b113b73ea81f132b1b9e79348d38288d63e5a9aaa21c57ec0a535613b1c068225566970966c7866512f8ef1a63e90cd1f751e2b581597b5947852ca825e6dfcd417b682b08db2bd17114b9cf969db8d331aa3c45cfff8ab593c432662f87c6068847ba70022f913e62890b8dc121e9f9bbe568f7d00fce5362f4a421f18588378fee46a5fc70c6d8471fb55081223a1600b4c02a2ca7591e061c4ac45d1ab96ad53e6f9ac473ba66e6dfe51c728c794b6459500e9aa9b2ef922f0dc758a2fe4fac636f326a2345641c419d6af6c98b467edc4b12513d7af1de5dc382ec1746690cd1aa848b96130b640692969f273f185c9dda4303cd260329303296587042cc30e64964ead4bb01d4eceb04a0a71f44f5e61acb0fa8c88d0c9307efb1f5d5ccc306f578e7beab89861898bc63b0453561105a1c49a2382cba8fe179f786b53ecba47171257fda994c3921e43aa644da2591152a09b8bc31a494cb53c09f59b7ebd5f22eabb09bbc1b68ea7087c9720020130dad5da1dab70376c682b5cd3e8791ce490f5a01a0eb37fa23f36e1be3723d19010c022c757786a2489ce2d2097a0f17741b9db712da931e8aad48499c4da9a01b699ee23b7a1b7074d4658e062ea90fbe39a78af7fda11e49f465c202a1cec298000941d044e88d850c6a5a7933b8d1f887670b5f50c11561ec05af95a08426d0714425d666df9a4e3fca0b182b530168c224d0676abfeb86840f9b1a319236252cb244f01f7c7040c17f11a2e018ce38fb3f217bc5cc8657c980908e96b6303fa6015605ce598cb3784f47aa35d0bb12288d8b29f25bb024291a0aa2981774138a36382842b6c9971dcb3adf826e46895503b826411bc171c94c7f0b328076f1bc43e74c09d9b01cc405451221264f48a1310628b5763bc0ce0833f8cfb2eb6b6daff38fb57092d2e75f8e68cf9dfe672ed403168689f2e4b0f7f0f38c4eaac673dbb7d67d8d7de4f9bfe4fc2ee53478d89e440f8b86fcfbb0dae8926d8ba3c0763bb3ffee04e2ef88bb0128c47fb09ad9d8f581cc191cb0dc1acd7943ff6ab38755a1eeaab050ba67304210c42b1bc6c0f503b42b5bdceeed408946392e455985bc33eb4dd8c347d6fe9d7c49487f75ea201e7e2b1eedd65b5e77a6a71cd864e79190e9ad628340f293d603a2493774f8edd6a83638400fbd2b8fc9feb719326513485b50053b063382b084009aeedc576ff8f4272fcfbbd1f2351b2531db36c85ed457839bcde79d212b2e582d38b494a38659f6c9f11021f0d553163441d1a48972798ff3d1ae69f6da9368c9754f413b1c742b181949113f593ea3ff1fa5e8e718f1957aaf9194e5d8ea37889aac7a026a7e1818ada03d76cdaedfab84f63907329f9fab8ad093cfd4756e1f375dc775678d685b9ad9dbbb60ed768040cb5be05860a03b0d105037bed76b6e42460c9e5b9e0d890ca82c75f58b5eb56da9c0dff5c412dc5f744b36674902f562d4b2cd003b795f8e5fa800e843628ad90abbbdf8bee6122f512e824d9f9d2f1e90db2a918e59beccf01ffe0df8b1507c28dedd2a300a7d0c7f832f091a0c2eb21d00d9c65361e22fe792a2da70001ba7bc5a449029a622bbebc16500d69453e275b4519a5ffb1425dbb4259225760ff37c0a320922a0b9583c5be8b5cf84cc9effb86bb22e5187235181638a2a581bad728048ac38a846bf569188f0844d9bc5ab7885859cf32e3f689a81acb7214d4e7e6bd3de59b0cc6b55e33d45ecaab3ccdce32a4f0a0bf1eca9ea01533211a16e849ce976d34f0d46a643bd0bd7808a1a6c72d15a3ae1ac6a5c323f1320374b4be8e0e8219f4d8c283f10e15ac409ac4ccb175dc4a5e8606c09efb7b1eb83431f9a8705b87d14df1fad5b976b25bbd2e07a11a206b948b5d1380740134c61eaf411d0497a0465692caa7427f386f765b6a91a986624c23da8928a906d9aa6338f8c1711d32a3b5d9c42546e36258187ae7160343d0cc31d884da55bd1c5864197e7bce2fad8c1d1b56f4cbed2de4b931271e037f1f6b34e205154213884a908a4d90832518305b0f79caa5bb04fc32838267731e9be2bed6fb17f9c33899f091ae40ea3bb021a76253111493aff90ee720108b3007169acf680df55983ad82cb6a88a29b22cf778a176b65571285f0b6d5396368ca75059149cf08cc194c0398740d40f5005d54de53891f26c7c588932485a2e6ba51a0dfea7fec627476e63a5e2bc914190795ff283a70fc697eac719ed9fbfd8d6f5c6df14382672d687eb17c438b8ca1e902632a199abc660d06befb063755331668f46697e1b305f8babe9d489d1e962a5eb869eaeed4243633c3b5df314daa4567d64fc15a4d7ee2771d1ca3a1877e1d4f2f4c839aec10a20648658ecb53efd53cf97219219cb470a6356ebdee9c2ccbb720014f2bcbc19f73872605c3c44bb4119a812aad6bc3007da7541ebd04b74ed8fd014288f5a45be3386d5012d08247c77bd615363c1f9a54120e558857e6605bfea9ec0cd877900d17ed464173a9e97ecea6b4773f86b0ecfe102b45b09577544c1d509c9567872d04cbd64eeb08f61dad432770305c40a202bc069a439b9eab7348cdda91719f0e12fd8188bdf4afd6cce69325aadc9a4f7e9cc4b2c5df6a1ed58d7b6c20621756e3dbef11fb43efae94d65d0974a5e17f398ef99933f219a4625e2eb9fc44b472a19e9ac3fb017a40073ca9de64b23141b6454cdbe9056cb16323d4db6154dcd5b293829d9149b46f2c386569ac78b8d037dc2094f6be6c167b51dc9c7a27e3bdd1c05dbc022edc03778b686ba459ca5c854112453001527062072425a9c11b9124cdfa927fd2c66fe568db28c95f0be8ed381c420dc42990cdadd46a84c8dbc3ba285c363f56ab8d177f4745162e1baf8f387f9ecf690393b4e375bb48496030af86e902fb4c893c254570d9f4d21202949f4bf0d662cdbce12969390e26acff43c888888f6ac855d0f44160bb2f627ff614eb4ae5a8de991378d2cbe82339174dc20e1c768e13d80f892221041ab18a5fb9307d6dfb0b832d3ebd828b22bc493f23ea3193ee1b2810ae4c5f11afe20930064dad77150b652fe3d556413788b6940a2f0c639d483c440ba256f14186d7a091fa9575eaace358d440297860cf1435a7bdfb2041ab60256f13bbba5efc34ae8e80d86eb1d44f63d51f3612bb9d3f509bd2665dc5a9878d0138a5b5779d8f7d394ba2af1a84209336340f1aa9203042b3a210630095a86e28c124af20362a1fbd93e6b21e9899f0ecb9c9f4d610f0a924eae1d4f9f5b326228ee8e2611927596f28233445b4285adb2dab24ce1681204bc603a352a059d4fb6cf2f149ffc9655a48abc0c230ee6ac328803d51f963411ad9d7975ddb50810a957467a439f2913238327c2161b9af0000312600e8c1b4e47b4e642ea71c317d190de4bc7e33bec82f96bb8fb8d2cc89bce4fc92a1236efb8857ca02fec4ba5888864e11b719cc29355954f3b7cc428072916daa9980d2b2344924582a15145812e60b03efb3910be83e00b56c8c0479e7e52dac56a3eca68cb7afc2091fb36f05db992c17c267c1df30abfe52824d545f570fe29f80de5fc80c7ab45123fb3ceecca6999bd76789e877516904f69b2ee237c1dd86c786e70b0c062794a0128784a33a1413c5f19e10aa1458c8764d7b58f0d00a1b1e2cd600a1453ff0247e6879f200bb055cf06f144b55fb0107207924e18b193440a46980af6e298de572fb6a35449e24548abddd8f98f1584b3aff0ee1a284a24021a6f51328710fa4f7100bd39cdeb8068aa8b32a11debae07fd0f89407400f726ae03527c57064d0701ae815ac9db6990e2d5088805ea5f04ccbf426cc1d82816b19ed95e6d9421c06050dc061c7577624eb36b76b12bb496956aaf2a8242ce55214870f02c0648af77ced3339a896dfa814bd8ff207e3ac02b29bd0ac56aba56a44586fe2ae8791eaca54330c0804fefb792c4cd2e5423d0d9a8bab05fa3f706415895b02f17b3d55c8b753b362393be673c4ba19b278b9ea5bc7b10a7f497407837026fc6167009d0705087f861559787aa48896dcfe6cc649de17128a21cc9f8681ec49c31b7b3330455e36c32e9a8ae1e44522073f196fac1b31c5073c9bdaca3988a8cb399deb4d1ce703a3092d18dad9a7371401b7b106bce2be8939bb62d90aff001932dd4724f46a02c41a28ebbc773e6184720e3ba31c1b8ebf3a64f0b5f54534156740ffb07cf2ee51a872b487b42eef3468c132c5eebdedf1ad6ca196bf6e3e3fd8be775640bf619f8d30c4eb61bed95f40a975f979b0ad4be3c1417419485ac8949ee8c5f033c5223b4a3ed4f0656aff690462bbabfe09c8e58f2123692416e3efe611dbe40ed5f2bcd04f1f4723a9b25bfc04af75248b861f0226eadd2656c5ab66b105150635b7c9ee88002b12bc28eeb933a21850235546bb414ae266138fe8a70005f408cd10b5997fc3dc635d28a8df742fb482ec95b0a79ac661c21c70671f5c64c8947b855cca212b29690dd3e36a9e2e33915f4b5c317922a314a7fad0cea19225eee30383029241ef4a3164559cb4a95369fab4d7fadb5683ac827ec7a68439bc8f4ed0bc64ad4197f4ce3203829050e101c9783c7c3484c67c5fd2686587e1aa587f5b2fd9076043f8050b7b835249d997202ed51a666642cab1e555c3cbf06e1a76773100fa6addc54871076ec399cfdea52c0316c77081053fb65ef7d1bca85d17d1a461f14774588f0d79e6e2db341be6a1ada7ed0a270d2742d63151c3bda5dac2cdb0345babc8320cac7af1ceac61ac970b338f863100a5a190c61c72f2949d4ea3fc143ce9dd781e77568bdfd5d01bc3fd0bacde9e27e91754917ae017bc361f34189be55b394964b07a8bca2ef2dee76927ff5aed07c8210b191d296c27a776ea48f087ca425dc4e6e61ee75a8cb5329a955c052cdf2a06c965d1e10439c391d92dc17c06af9191f1e6fef96998fbe9e4b0e1d5d20e28c051d430cb89616e5a8497dc45e3cca9a58e82d570e2175edcc4bb8c1145244145c7dba1201bd085f6a8a068dd8c10415bf490fadd1dcf48765dcb82a303b8b45bb5ea49800ba24da13e43dadaa6c2944cb460e0856a4cba6700544c047a039026204e18326733c25bce40212033700b544e68542ada507de374b2ad545adfc5b92f6171f889d51a86798418205fa6968aa272d43231656f4dc51f26b547dcc09b1e2ad53a5a47af120a0b145b4f6d09cd0b2dfe4f8956abd7b4d7bb81df40a2a068061228ec84912dbbf9ef4795120d34039f7699247b5bee32ec19c8b0da680fe04f4a94f0fcb2aa2eb45e66243ac5d530bb7133ace3804a5ed0ded65b0a839f6e603fe6b67f85b5ccdcb13c96d9c0dce111b38508a026117cef64bf73449425858bc7f3a273c7c84abf4b43cbcbbe8179b33069a774b864e5b444f4424b5b54bc2a9153c5a080c9b6aa607c6123c1f5a99c3636f222bb611cb40dc0bfc7fc6de5074c27ebcc37a425e0b78c142f7656602caffcb4e04143a5c1784cf0fd7afa0822e58a2f44e55697061be3771d4fda8f7c26d66dc759aebdeacae5906ba7bfef2d86f993f25066dbe39ad7ffeaa39f9a07e6ac3a61790910b02ff1ede028cf5537cd7df3a9d8300c4a016d616e5c590d281905dc83e486cd494c08a209fb3673269b9e587f438ee3502c4e8a2f0610e80f2ef0b8fd0b7b8394e81ae5add6d9fb46fca13e4325d887f598203bb182ee574e482099f315e5532a776095d5c613608a98286c66c1bd9183badc8adbe18aa2149066e4f731f4b64e1d5eec5a40dd468e36612523ab9bbff47e13934185d88f45f3d8215da91e5f586c2c89acc22d7020544284ffd4beeea456353e4178dbe46a4d7bc4dc468d0e8ad1968d7013f0febccd57806bce547f2cc1a1134b6dd69923f5e65dbd75d6db25b7126b8b3eb607c037b1f12520951befe87b43d81f35d7f5e46ae81300b793c9a54523f7e8ad10c744175814861897d9144bfe07ad784474059adfb02a20e82e2e65b80570fd76f29d15aaab0daa170b8dd0ec77af340f60524b5eb98b7c06e461ceb798bd270383ce5aa523130e5c2a0b63c16e9b741623457d684551c3dd42b01baeeb8b676e3b70e7f0393ffa94a274b767b2e8048c49be4165381fb59f3215f8404b8a46a2121586fb4793aeeecc0250510771812d9c2c25d538373921d5e8b4eea4e087992a603a3d93a8aa01ae1b5fc72b0c3b300b31f9493292ea574dada2bfec7b507660d9fe564db4d7cb9b648e0f48eb9ce8fb6695b9236da1e014ca1937c834a035a07aa1b74c0b844c74c46ba0ea355b84d0e626aa251d045314457abb38805e823bee76d49e4de5f47ec3e4b4976abef2b137b577d9877a48ac0b582b37b7dc8b2cfa8dcebd85e37129e4f38b3b6ab9b7d362d2e6e4f959c6abf92e32642959c14377411d6490083a059b08654c3821c2cd42e40eb9d9b3038419c53ea3bceeaa5f18d36e7f54c7756652b11954f01b3577c62e2e62302ea8b1ca0085ad024bf716eaa29aef6b45136d2ad5abf7f0ab62a09c6e6bb8c62670fabc4529219b8ca66307014e51e5c772cc1e85f71ae48bfff8dc951bc5b458884032727c402d07147a9ad2c91dcf45bf8af9214cf939aebc1bf7545a4610738c6eaabe2ebda812a6a2bc87a84c12dce005fedbf3a19899d38cc3db22037cc23e10001f13cd868031494667e7277aaf5402c960a9f8a179cd69b3530733e4a8ccbc6e864c178cf83bd35624d291eed2f8b7f127709b7bb5ef5384a9680066ac609796db5548848a197fa2a832e7517d8c019daa0c73a2dff5c38e1272d31e142f98065c0ec5cc9c10bc5038a59036962e6feb6526e587fcfbd5de21a5d762456af3219c7bc37d9cdde4ece04646fcfee3e7282ccf63e78d66d07140d7a7bfe6e0d3ebbd09689c59e20b5f3d9fa139c8421ed601a97e1a66f1fbce5c7e6335c48a87daa58514897f0550c704b4101c39d22aa1a8fe79c082bf27c21541cab0aedcdffb1edf56ea70c48d28372d4e3f494ba48fac58e8300406f244e937bcd9a721a2f3b980c409ec3c909f364fbf79804834eeac5faee884fb6809444701727b5a5decdcb2c10b0826a5024b2b80bd08b7279f355425dc5b621136fd7c533b8a1fde260fa1fd1319bc29fff6db1274e53a70238161c086d15ccdb691f360d8ba12ab4957f05cda37be84e190785d8eac0e49823c7b93b4b73204418c6f8c4f842307f4a010552abd9ec41cebdc7cd3e60de2310f33478e77e1a8adcf6e75828fcd7cdb815467e3dcd96ba76b0356393077264fa1df73db1528c7593c9b792ad51d6d074f3347cbcd099574a9e48a36d90f1f4d5a57e73d8ba3be92b0dc51db3ffd6c6c907b81d5281deeb773a80a97129d617df30970b3260a50634b2370d1e7309eb6c4c99840f9305a449088ddeeb02390314897b890e45e8df99fa270fc9ca04e0e098165130a2cb71da4b9c04e08f4db0e44d596431898cc0f86a5855fa28cea6cee074b2c3fb5d814632e6529bb82b4c49a7882bd42416cb2e48f4d305dea13196d93e8f32a7d68b10ad4c943ab9a0babb31672c7bab90399b44c7abe4a4b26e72ed69a9b45f8722670fd21e65c9614fbf67d4b4425349c499f5c5839db4c7e18809d16eb549a0347a1540ba1e04cc2fc3a0620d044852e09601d383d55f7c6d82f8eb7c5c9453fbef4d5583fe4cbb15e19e2686542e60daaa9335326775879f18c187819761c1e99080947c7089fe6491c75dc55b3aae95ce832fc7787c380ab69665174dae7c888dfabd03aa275bf45cb84fda74c1f7ee8763385985122f04f6b3da99c349b2c2114d3d268f6ee961e23cf18cb364d521b62aa6e37a2346281c292aa109289c324bad87cac5a135425ac0ec27f6d87ec76aef91471455bcee3b885a0cb08e04e6ef48f14d09913d7754033c26b213a055385cccd2524d8e0897f4c3fa64401ade64849dcaa10d92f0a965de11ee7baaf12cd8463e0de510a8dd757f3d1ded1e67159743d1fbf08d8305390ffb05d36fc63e45812d0ee09b977e5b957e59f47ec72466a857299daf47aae072046427ffa3ff450ceee073c0e700ebe33d13ea8d07f4c06a207844083244f4c402f4261f449c5cc8ff862cce3b0f84b586499e981ee8d6d8e6c5201bc47746ada15fa8b4a765fd85f9be4822b0d9717a9174c4527cfc83f001784136b779b7ab3bb3b2bc6d77da13cd9782118e54ace1f9407e65c391a7a41b2fa8db77c42e0615ab1bc42c5e3ba41668156be025b402b380b64f3d9cc0dfad0d01bfb337efbd18965f094f321da6e4aa5dce83fed86ae8b13213fed6734548ca30a9af20592df895713c990d10b22d88b33f3db3ea5cee541033d535e05eef850444adc53ae5ccd2ce7d0666d42d9b4dcbcd208856dd7528ea015d339a5b563a53dfedece26c6b7282f1f6a5cf8655828111be73413774221ef9532ee26939a62dbe435d937eb03d283bd4599c5931d9d2c0997090c6e8bcd42cd6ff1847f935a4f74535f5277cb842c23dcc862702262070bf661509284b3806987d218d4452e7eaa17d31c8f93ecf41603d6819b72ca8ec7769d4963f2789c3b02f595600f64b747b14eb824319bddaa8c55062b5c06843832baba4420a5fb776246ece3105bdbd4a0ceacc87d6a0852755fbebcaed09e5f567b4c02117ed4df0cedece60604f029fe3d80929087e39b381c9a297a832798061ddfc555f737f6bf76393997603cd06ba07daf82c34b41202427d5a3703b43af54a69bf94bd3b46827160d289057538d120bc79c7d75106c158c2385e46c9676a64522b9eba4a3946ceeb9a2d124b8f4420cc8f5e24babc8de331d8d2c960a468261c6331ff4fe13254da0713231e2baaf68d1192e2ea243525c011e2e856142d9e8420d1c8ca65b9e8b142768b436bbf45b5a91b2114dbfeda5698b56c8e660cfcb16c16b6304e2e2c55df4a6dfaac48a704b73916ebf91e1053f7b5584c8116819dd09d95251ecf5143a63529f0028aecd5d3056466720a70dcecdfeec699ae89a10a51691cc86cb56476b1eb481009aa43fc4ced737e113d0ade3e688e0c829106f88d66f9f9280f057dd9c2586cb4025d3091fe5073746b2dfa1a8edf2184ff41de380abd961e9f09c7f4af1dedd9a812ca8f1d62164f8bd3701382f56f448c897a15584860f600c8f11905e092b098cdb91e76b06a771526f4e33fc4d51b39c498a8d72edd9bc732650b23fe3ebf9efaa3a3d7e44bfe083def805a2bf381c4474a610dd4a9eb58316281a1774f6ef12d9aa7788a431e5118c608c4caa05724e2186fd6da199193833453ba4624d0851406a9d817f7107b54109188682bcf8201d2d804733607f07c1dddb2ba7c332e1bc8cf4315577e08cc12255b1d1ace46aeb82eb3e6838932b92fc168206c193d29e4e0f7801b7d9b8c285109291743cc189981062a9959b4d58e325d97597f68c007908bb13cc56d47fd9fbab099c44f4282ebd2e82dbf1117db8b87313bfc42b5082599619f00c85a3b7dc43c6ff00b84d57bc57d06d40ef162b902be483e140efcce462901495233704f15c8f44b9b455b82adcf8db0578a1454a7f458cbf7fd4fdde084f1c9c66f10c194d25826f909a90add0a803ed035ceaa01f685a5d9a95d8828b27a178f7b2b607a0c3c0829ff93ccfe76b394903689e5497b905ae05ae1ed2a2159be8a424fb71808bce19352041f677e52b2f04fa6473757ac10435868b41976358b3986beccbc7f346b586f294e436e2e6f72a045c915141300191715d559154547e1c504d36a1d3011d6c83ac03c5458f0a0b12ad391136cf3b5c35c02759eb437370f0a1b94688e16c6994f10ad9203e2f83239bc315b1feb65c5dd0ac44014f3c81f8a18eb6565075d837e197c71beef1f803a6efb2b80d93dc45f76571533f06fb007a43a80fae45defe2ba859fb0c206c54d1efb39225db05bf6415912a88b697bc574204682f1140706e0795c33279bd0856aac3cd53e34c84a4728fc5bb8c1c720fe6737bad7d080343be24f49263a0ea8e0309753876287f8442c5e6265a753124e78c81a7bc068bb10d79f6e3cccc15989542fc81b1d06e2a24e124ac0650b549a1cad0120695b643c2105e58fe090a0b8586849bd2c471e6def9c36874b9aabae7e29fdce653d3b7bc50a486a22de15ecf1063d9669c594d2f3987cc3e3d64eea80165d0933ab866238d7f099844ebd640f77e842b055347b615b87645aa6d7c17c8683b94b51dc7fd40f0834c466144721c24e48bdae8f8ee27a8c4a211f7385fc27eec2ed911810fe14c882552bacdadf7a5c904a65b341036c64972d3514e05240adf2246d6a982268c1cdcd0ab1707afb04dae643df5b83c840d4f7bd8e27a83c7b1417dc3f707e69ae0d8e71e62609b76b2d8b9c0dc09de0f3e219880e13d6242b07f51f0ed9b733ea1c31673985338157ba8cd2794be714e9815862d0207b765bdad19281301a4dc55c2dc6d4678c825037217cdb16672f936fbdd3a426dc98e636139167054a751fdc8d8fda99e4d3f7b09e0f5d29582f5586f603c81b09d7348fb95c542a19af1f522f94943902002e3810f79562f1c1313672dba19eeb6f955d16d2887c39e4182328150a362f0ce37ff485806e476403afde87bbdcd4466ce071e3ca680f271e8d9d8cf8b80fba0dbfdb9980ac8c56163742e33bdbef54112ff03b55f48a2686f2aa5790be7db7786532edf43a97f184349cf52ceeae3ff091f29e37d8be79e70bfea3b847e3ba16e2ffc528ed1c60ba8222a79ab2d655b25cdb29ff078e30a42b9ec3838638b7a6840a14512a0db590b7f8f98c6b2fa3cec7beccefb032caf928ba04aab83c05630cea824709a19179170ccd2e33ffe04f06a6d1ffedf1dc58921754c0f51d72be197b656a11afdd5bb41d2b195eb65aae5c6844d6217f8f940b529ebf7f643b599c56076b55888395228420214306c6e2a07243278c5b05d5d93dd1aea711932380e529bf501778246cac668c0e21a67cdd114e5dbecae54dd84b42b75f90c4d5aa44e21a87da062cc8303b34984d3d38d7953cfcd8c8e60e54d708b8a5dcd21e4e3c8645287dd79d5c5712f9ecc28d6370476e05fbca067862d27b33662ca705fa06ddce2663548a39fb40b5c50c46ee9ef96514428a2bccbb84494b304f84529cf4a49c801d332ebbd8df34d35c6770903a9972e4884fb617fbb08364c54b3b99aa757253bb8e034c75ad2a89e28ab5e49069bd647315bf2cbc8258d5f81493f3e10c368f2a54be80a9338c6aa8830eb0d494d89effe5adfca95579508bbffaf65d6ff956abe9760061386e83e14fc8db5a166b52c614da71450347b87c74e155f213aedc299ca64bfefe35cdb0c348989101d6e5732af8412b43766973acac8372794d728850ecf0c190bb9e196bc19b1eb2f77de7e32209ecc2e413dc329d24a855208f5b4935d33ef3fd1ce69abeef7a44f70a4ba5aec9d0ee41fd443808149b202c0202d20cb4e6dbb88916ec5f6aad2df7b05c0f81e518a4ae9413011cb05f255c25b7c6d2b1f32b3a50a29ed17f20d4994487918053195da80027efc7c3b9cc6f153c648892f5bbdde666a9120bccc96b3da89cd440ce0feaa2016a8f6a5c5e188b848d20223829bc897c5de73de0ad83e224f49d69120d8e5df2cc09f404f86476e72c9d1dd19bf74c253f308064076696d9810df88c5bc5b823e07ee3028fa8756855423c4b3804482129da8707509a21d68590583bb11f1b1dfe438d02b9f414f2327fa2cba3d1e1c67d4ce11f8a3dc3396085dc704e9e1cf1c9b2ae7b7daf6b48dc44631dc322c400fce6d602e01406ebe78f9ed09f00f1434434f842c1164e0eaeb52f9553acb51e39fa00c7fd2ab7fbc3d120f34808424deac23348d0d7f070d09abf6a0c8d8d6d12a429848aabc4b4504723dca2df2378286562d69c054386c93b20b5987d99b16548895f117ba7b3673c5e4acdf8583c67988c062cc24f00033a585a95ce13d236a03c152aa1e126c550bd30c8a6fc68df6c94e3ea2c40db0da7bcc3bfa7c13eede22458f30cb76e03e139b740fcc857c42f9cde919c49bf8eefd170ac14414da5c0a61ec0322639947e6d6eb208c9b738daecb738f3c9f9c264ca409c7ff62735fd4831353022198559cbd16d35d830d87c7ccfb24bca3eecab793e45b38826d7424b5c6cab8fbdd1dac8350150a64445dcf4f0d56102f3a488888d1bb86719e56705cc160852d92635cbe2eef6f183d9165c7bf3e5c03a33da99b359d1e5093615446e816b58a3e751828384f7d1328668db843bcb37e4678bbf49fa3ae27f0d398d4b009b4b07ec1eb5b8be107ed077647ed75c98d6712af8196dbc0c85d4cd8284635ae24526c4d2c350f4cf3045d4d8a211b999fe0fc41c57fa6294437da9212c098aa35dbcbc09f08017000f981844abb2524f345a6b14e51845d605f19e7b31950c4e2bcc114f4eaaeff17211b6def48a5e9ea389c42ed8e6babf616627d33333f12e7c99f17936d52fc3e3ea1cb534c0f41024f032086d4ebd7d184d7247616fafecf9c24e5df7f2bc48208f65f97382c6b8289d357d5dcedfcea7db84126f9419451923903307dd18296ae24a7ceb64e134d95da6ce8043dab34f2ded8812431426e243395ffa0be421618c99457583201d91c98354f7d74d7c31092ee2f280320ed98c792896c51b2d26a889d6d27c34e1c98d88db36bbb2b9a003a5b635b1ecbe141c2926196f356f52e1eec8530ce630f9d34cbb035c42b3731fce88572f7c25a24079e034b2a198346b20dafadb8adacdb12cda4ae86db162512d166c754b8b7e388730de55bb40ac2ad14a5b848ce715797fa3488d5ef63f58fc2c3c12e61a08953835ce85dcacf21b8d275ea75a9256d3ebe04004a07b21aefdf92a4548cc16c0cb351ccde31bf1bfdb691f9e9a9628d8d5809560cbf8559f91f9613aad68675214fd481f34851a69492a320e8232f3267267c47368e7981f8240c5445942d1546a356203f2629ccbc6baa380ed782ce11eaf2712260c5fb7e2393988d44bde87316c2190f8ae6fcd60f1cc2721c1ebd61f9b6a2e58b1fd84bee94a1785d9e36fa70c3e96353bc058343e2ba61fa4d68a22e95618a70561c5cb56a4cc0b700a1827e4988753b526f842704b6465bc3f4ac39529fa62de016ff0ed1a33fe87b8b6d0ab3e828bb0418b318b90722e8cab4db61c3da6e3639b3de1b29fa2d550cca16484371078aa567c5e8361db0a8ad78a81050c1373e62e48458e23b423be52723272342d1e7787a5aa5a15584a0dc2e9358203bdfd6555f8104bc4b27fc47ecb3f913f5617d4ae18f0a06fe65d85b9356690edc3316b1c35546fe6a89b1550cfaf009c0cdf304d279c77fd1531fb2069e9332cea586ffedde9d4305f53edf89b5d7fc3f4464c4fc39e455ddf4cb4af0fdc2613d05039e49cabad216d71331eb3dfab3559b3f9c1480cfd3c3b5d2fe52123d2a0a03d7ff6c6648307ccdff833123f9c55e8d82fd7009f1db8d8b1d06f2a3a298218a890f53681544f5e9a6e7405c807631ede1c785fb2e2079ff10fcc7539542280c4ef1c46ea006b18e060e6ef97082e2ad2eebc71d6db443a0b263d380656fd9d0abf8c6252298f9ba62119bb04f5e0bdab77745825fc420fb7fce44abad4d223acaecdba12f7c4691d3f0972d4eadf306f2fe1e6d5ca586cfb172e35b8b37683605901b5100da410104a3ef28ae4ca0a5a05000f13be11f1624070ad0d274106706b47701e45e53d39f685f423baea88e7f6bf7b5038f6d461140d5cda53566f6a934fcf3a6e36876e0c253b8b64376c4f3dbb3f0909b7a970e18126ffc6db3047ec0b4b0e5f98221f23bc3b9335e75bf35d0fae20c7118c26e2cdfc405052ec7cb0661732c78ea417e2d759483f61ad7d416355a9c8284fe04834ea0381848fd115b77f6880a0b6f6731dade1f395c2a6d130a3d88ff17462234b187301d8ff184ec5eb1a3b589ea5c85390cd253ce8031453ca2e5d0d0cb1be12fd390bf30cdfabd31831492b433d7651577f5999e74f124d7c0e8d21c3d26da69f95957e9c42d40dd95b140f8c8ba0f4075d63d10bba4e5f3be6225f767cac257f250e9da14bd6998004e513f0789444eee279a18432bc6866fbf378d04117c534a49c33f256ed303c5bd315e0a4457d8a169d40c4edbbc9733b3506b7c6581fd28152a1f6be5292e48e46fb8255b777da30e1d1d3687c4c5e9c7b62341dccefa51138b913e6046fe0570f35599d02395b87c540e5a13e5778624b8c0a4843ce6caa7947f2eda09b997ea558477a3d6c95c397490a152d264a9c4960358e02b737fc4092d68312b35c746fd06faaa200f7f5d942851217d86913aa2a20450f2b0efc688fe5586360c67baf6650d1d37f362a90969adf8501e1c37d4ee0dffc066fe1b15e0ec2db04683a18b3d59f522fb2880771d765eb77aad3845b7e98b3870839fa374fcba973355442c5655768e6f5c1cbea03798a0f31944de5e66b01d40d4b1b051eb3c2db1fbb7cba801e7f2da52532cbf61df1bbe229a9e15d4a5c3c214dda68448f429a1e34354667aebdbbbeaa3813c152bfde4b3cf0015ef52138a1f2d61167cac475b6a2dc5621c145fcebd685faf7b512e5d162cc63a5d3cec12a55c1addacb8a3a8f3898725b476b6deb74a8504022b1bfa1ffdf465ac8a556ce1bcbf43fdf5b5ba1bf3e58ba4a7618acceba5bfb2cd9d5ef8181218c8095ce7425dd65b472f33e32563bdbd4498c9ed9064dc07d32471d886a25c25a3e186c0a6c82f67d6d4f74a2cceaa8eda80abec2d05d41d225b1e7d2d3336db9e682ed8037633c6fbb6d301e8995289f67007022a35991fbe4a0413ba9ba71062e13369b327fa6de6676bddee62f1d06c0c088b4176fa8f396fcefa2705592035cf0bb39a5652e0d456b7112dbcdf0dda5f739596c0e0e6a774e9c96e900346ddfde0e150c36c55c629e10859f57c5db6fec4581970e411fc0dd44d7a41b8888150c54584e92b5c0116b40d9064477dc55db72177d36826ed970092e20180326823dc2028f09bdda510ef635f9ebbff4788449428587b5d9c3f40417ab2cdca83b5a442d3ae937de26ddafcf2b1f02e6f2e8409cda736878a06f1c70a182867fbc97f4d2a5b93eeea7c39db5c5710fce1656364eb68c58003fd3de652c15fbcce30dcb899ed4b3992cffe03528488ef8911695ae45089213598be99e9ccbc1f7926e5c3807c989a6d7dcd923e80dadd606a46a55b16c9c0162c5c74fc54c4205ad4af6ec2e94b38d4fff157126b2a39916f1b50dad846fc6ca2b9b68bd547bcde0db1700154281e1e768c717a86d659808dc6577bbac915180bdf1a9907596114486b61130eba080c00ae323ff19e35a95ee2163aad6bed1002cd840f9264a0303f05783392f3563f4779642f0502f417da703495fcfffc57136711a2355de2ae611fb3a9c40fb9d036887373c5ad2f9dea0e6f4f38ffb9fc880863136e04c7a31bf9d1ac2e83a257cce6ecdf60be7f224b3717739885c35a0de4c70f065c64942862157ecdb5c1ab27be6cf4694b8a2c60edf1d7d716c71017a0452a808d4693432dfdba51c0c0af59591001775cd68e326946cad8d752404983f32418b12a1035f630a22188e45aead00e6e39761a7ae87f7323d19efc58b7beb6584d4f4566ff98ca7b06790bf77a76b3d54fb651e0779b3776434e93a4f936d2f6f6ed01884b2a2c33dea3e2882087d9614351fa39bca80714f89ce1bc711bcc528325d92053ad0f215365b8c875789a46cb8a056b3d80c1107a0d405478195287c1768e749a825036faf64e24707350a07de5d05b551cf37ce12687cc082063a1ddbbd523a755c0f0336710c49afd11632ca93da68c8378f10b7a3c4b44abdf91abe97ea1ad4d75e976a34fbf2e20a1ff5137675ae92980e8e844e42b9a5810f897ff16ddd0741937acfe9eebfccae7b7548fd378699bf284d151c584ee1bd576ed49d0c135bfb4c5b80de479f7283861e665bd2619a6d5bdd581940a7e81e9b58dbb74795560e7c0557f557bbcd6d4bb09e55d7d34379b5a688b7dc4bfc463817ca47f93a0a492aaa7a9dbcbb6a20399009ad8434a1487017b3b92a977f5e35cf12d8e53a92e460c938ee72f491b2e550b2cfea7209d668ff4239fa54b6a7172166aaba37527c9170868a05357f1c365136e7f0cc0e20c8c0899884c919245611568fc23017657fa893db3e0bbf6a730957f9fbd8f4de76d267c9cc4e9a73e40de989bbe1f11de5392c73710370a09581f20c50b4ac763ccb0f016561f8108707edf1b77d66f89aea04ffcfa6b89605fe9811c3878f00e722ff7fcd66fbe5409e6565e616e08c2a246645fe4055a089b382337255f5e54c1766edbe95c7f819e81ea21c030c4bc6b5a551a9d620811070218ac54714fa862ec6f0d9a89d7c3be92c1c9799bb0b44470cd4688ac9e40558f324872cc1f679102f76346713fd27c724566feff416038a310fec496bf258b0af6e6c68e31a180d6a167e8bb1be7ea93753fd76b05b2842dec747ea9e9d563501e8ef0f31caea62b9edaaad4f18631b0ac398b0a751a60ca3c42c6eaa55b0000e16ecc388f10e8c2dc22a692b128c9447e6de00c72648b3092ab4a7a011408dd3f5e87d0d94b7f5c228c7b05aaf0bf2902290e13f6b6c119b4e63edff25494b0dacd488fbdaaa2d057ddbdf3f71130a3c5d620c6d668e27a4b4aab89062d4b42a037a3cea330f7d08a9dd56141218b154eaaf11758231ce82a644a224065fbf24f9507c5981b5f32e16a24889ff172ed451131eba21a86c45ff50bbe10313b008565560e2cbe7e54cdb6b66f7cedc7cbc23b835c61ee312ccea89d5b9442ca85956b65903db6fc71e87aba4a767f0a97bf97ae96acda629d578ce7cfed01fb62d91103165ac67c5cda90e715b4dd6828c5c5506dea7203d6c42ee92246193c4162c796f09f82e1edf16690755b8d2fe68340c6469b651fe7a9c76ba2064a06fc28a4fedeecee068ce0bc4394f6ef0864af8fe021d22a232f9bd1ef4bda7b65af030068d84c06f6dbbf80b42944616c741d516a0378c62e259e0bc7cc44ea06ef2f3080d555b93372c8e0073676c9459bc987df0aecd563a5f748db9328b5c03260e886e4bfe6ce218da2b82aea36a9645057411c03023fa00080547916e52ce2b5517d376e2cf551883e8f87095997c2a041be3691b5cda569ffa9679b6b05666b312e578a11f5b4e4fb1f181632b99c9825d8991bed4fb5bf6678b95424e8b2e68c137806cb0635caa210fda402be5b62081318eedca379b79205aab56b8d959817f1adef2ed4804d404b3accdc53c8082d8eb91afcb164c1c09e4520fd3abbb130d56943ad69517d7512f3a6b5f66b2846d2616ec37f3adbf1bc1406d9e6ef8590f4fe86ec1304288526c35d045468818cceedd5f37c17a753f7c8e8f08d18b2d32bc964fec19408ad7b714afde7db6feebf046fc077a644c550512e94da0dab30d826605ebd19c9f56c6b8b46db17252a783586aa6cbfcd109972fa41100405a6dc92af79fa4180ae2d21a8727a430b9bf2a39a857ed508304de6acb97b7a8b4d2421aef21b15713f91b824ac5fd2292fea4f545ef6a904c4f3914483ed49c5c9e50105c7fc7902e3ed540262f840e260f6aca264f28082e37fe88908c661033809a4891f9bb0182e77328cd5f6356ab6fe06caacd3e01bcb031a2adf58bd1b169f84ea5aaa0743e847f5538f3af83e77d7abc10ad74267fc909258d63547a2127d67dba7561049421f375a37234ecf907a2d167bd6217b19043de2d85a69db28c5f8bd87d6325636d58bcccfb500dbc41704c03a6129c6552c732cc253df355ad0123f57a84d8f051bd3a0d83409c87b2defd6504d21cbeb5cf2824b3d2714bbd46672001495e9015dcefa44674033e1e03b7ee3d338bd9195fcfb37accacb7205a083a8edf1965fb6616960e5c770720842b517d9bde0994f860a55f5699178d92a0cf61dcf38acd805c28d269f71505631b4b017794cee7219ac40f236abdaa129d92653af6729843c809c2f0bcfa0638a862e40dc3f2f4e78270b2606c2af2acd0872929d9acf7388afe988fb73d4da85d009104ee59925d3c99a1e991ce94e2be21536acf5d766cca87b69c5519f0f10e107112e387627e5b7bf35f68492f8c44d74e31194347c0676d838d258dde69f0d02a0db4fdf50aa1ae8754bd7eec5a6ae14cf2e9190ac2caa8d7aa02ee944bd1c0e34d55816068b159aad968326d19843a8ccd55db694ac9553b497725fe6389b581d5addb6b34aaf553c6775b0b432cfeb4a7f6d29c8f06138b3a1f06fa858ec4c66aca958176937280f2c687bd718c0908af035452a797dc0f15d103b8d9a403e1d9d5c44f57ead63c48b258b53e042d047830a3519993160d305afd64c2e150eae7b2aaa21701ae6fbb44a7b38dbfef61900730d4eb0cb933d9b1debc62cd756b1f0093439f7d2149c91a19f1b157008600aa4c732bc3aa133a849ad706e1ee8066f2bedad8760c20e0e1eeca512eda6f2af7e237c36910ecd8a019056371cafeb5aa6d47f5d1a17b31d04108c59e92294bb7d9556b1319f4674ca018cfa01807d3719d40c5ffbe004889ee0e82e6b0da5996ea3701fa4eeff888dd12f8377566edab3d6ee7c4408cb3cb800157e942d6f8b322ca291801e60791020fea1cc653872ece6792ad886f2d718a026bf1d579d5b74ce2cdd75578427c85a0b097c7c8449f6610ce5a683723422704a8b88f0c6ccd278dfaacb248a08b0a37c98ea1f9965d8b2bd5d9e8c9605fbd0f9eb5ce52cb310f9f86bb01db8828e2583351fa41a3285548fc379d8ab1caa2341ee639e81c0de445400f8c85fb0f4b30f896d40f50811254bfe54a71e2118750b3a11eac808284223e51d943b4a10f7b8bc53d1e8402681a163c111cd435773844f07054b30f6b2b0d84506ad7c54981250c5dfd30b04294f31486e92a98eadf2d83dd112768f1037480bb0cb592ecb1787a2ab8b01f7c476c68283ad6ecac67461daeb0696f0c35411575896d866d1c00a2de187a240c575e6ca8648e17ec847f561e6b841087a8371b656de139e69f8a6385a3be14df5dcb5e7e9a6ff071c68986b782ba80a876a089dd836659fa80140fd0b049e3bf8eea39c6f6d7bcbd01671bc023f7648b05ce7389655992219b9695d1632ebb17f42cfeac34d9f371c6274be311568e0a8f7b3ca273200b5eb0f4c6c985dcabe6dfe1a3e03923082b58fb1ca2b6b706d3538283b712ccb105806b248e81bd2929cad22a4a5d1ca5fd2d110b16fcdafa738eb42b82d7e389adb17f4f20bf3b4a4e3e21f390b93b9f33a4e49ea8f00d608e65044889d122a10f939533835aa600f04fa11b3b4a3f3df83073c0bd93da15e37a47679a765c0e6db5871ff413b7f4b325b595a2d8c176ac3221d94a1522b40a88f429744922b68fd30ceb3402265d9f951c491c07c496095a3b06b9f019f94178b954419a5718849afceae5e23a1948aa2001f624f301a20551d456bcfdfb5ff063f1c480a31c58a50f1828457a4aa0ae512957340becd84b542a732a215f70dc6468803458ae41cc786f6ef87d7acfe22abf7107703ca3b41120958bea806591bba5782dd8eef637d2aee2170801e43dd918f211dd0173b3cd3dd4fc96abf8b80cd3effb83379396036bfea0990820b0fbb5b022e406927fa044a99f5bb700d6a3ce122477e0894ebc979b3054a1d76bf039700d9768159882744d4e81c92a1b25c22f55d31c152085c152452b10abaf45f69a493c2892606dd381af28e98cc1072b6c0c0d8b78b8299047d7cffa5c8bf9d896e72471c89f728c3d9850790aa1789a713ab265bbd99f69e75c09568e3b62ff692af57af49b6a6752f7fec6869fafabfc0d20fadbf1e34afd10bf88003a1f03eb6df849ed922cc994fc925dc9ec9ce997e824edff35c76c26a10fc38f15edc8aaca006fea6d39fd3e001ac17adce402106717233327e0a2e956ec0b0ca7ccbd349916c435ab5e8edb1e095e4c5ff435d39b6f3dd793259e3d42c9d449a4869a1cc6ad23c3c1805ce3e3e9452d0e8f30d9ca037ce8b280ba7740743ae50bff809338049ff4879e6e4cdb6b4b6ef5c0702fd39c2badc4ae848c88789da626507ac103ce66787c1cc6e5e9b89e6a79a9325dc996a682fd30f72cb27cb4212276fc200bb8bbd8ea2e1b6c057b7fc0c7470b3063e30cd4d86edad60033c920632ab81c66f8d0fd0c5cefd00e959c31d57bb071ad3d105c8d6f781fd4fc0f423f8dfdd9b5f84040cb1200648688eda9c3405c0bc91f759464b54e20a2e156efced8cd0125571454acd377a394b6844d5aee052d38d3cce12da5189eb58a970232f674c4b54dd15526ab8c1fb2ca11d557305941a6efc38cb698b4a5c414ae58dbc9f315aa22a0e31009c6fe87386d011b55dc14ba51b163464d5c8fd229bceb922d364a8af3ece1d1a165850feb4f05c62ac6e9ae93a0cb2cad9f59361990868a76409037719a65fef1cedb83c053763c20476e1d89d142deb831e116ee3d9058014184c67a2381a9643263ea020f1e719590f34c241592d4a70236000ef97a0c04962030e7ba7b5e3bca4ed26ca084fc85f198a84ae0a7c8c7b0db5f3e5b42b7538b49850c7b1b5d96b542b4d6a971eb91455daa7efe5eec97e0344b54e098bc9d6fba62d36577444855035fef96dae41694a078811d2b3383b3a20a3ea2f1cdcecb00355e7f69e2859a47b3c50ee0c97e7ba4c06af98c95ce0c5beb904d3c533a2212f10b91cba3c487b73930f09b382c5c4cab74a0f53af255b7193ddb47eb0b092f3b96e9e4efe123bebe03b921ad63b033990198f2d5eb8d8ab1b22c3903ec97f53551c8dfecffe7f4e0783f69ec8289b2632071ace2d58905ef7699a35a474a22dae62508afa1639173d00a09a0840e91d0b668040bc148f8cd12790f226f49487bc8395b5454d3e5be41e701f1118212e5893350283b548c63d882974a975358e5a51ff7bf3d048e89abf55495884aea9e42100e722ad1714c36b76ae3a83e03020ce0eb1610d92f1748f99212e7f48e72c4e5988a3dd19649e1eb0551525f7645042f23bca306778cae8eafcbafbe9d5cf34057d2d1859bd73551a158bcf81addcc3a071cafe67b5cf63568306ec657588cfb502bc0135cbc53281b4bc201f3d6eac8ed6243ee172931d8a87344ca9537333542b99b7f424cea489b55515a01a25e26a02385ce4332bb4d96e89c6c28cf44243ddbec01a3f4c5e9fe50baf83297fa1b001185b5783bbcf6f7abaf392f01eaae0d2bf42f8393b077b98d28a80b4344d77f709862e6205fe17ba68c91a1e5f891df244048ef95006652a8a1b72629d1753708f05649dcfb62208e4bc2160f9f7a72f539ac02d17f2412b3ad20504640054c10ddd6480514362a9069d9b9bd0e17e0e2410127e06eab3117854705e35cb6cc29ffe8cc4d97445185fbb27188d68bfe1191b5fda9f6f4d822ff78d00eb54a5b131db51fd8598d04b0ecb5d6c57cdd234b1eb2fd89116e6c881172384775d4f138dc1bc7e769bba0ce22401b7048568d0072a42f68f4f6a89018c8b8416be4ea1c08feebe187ac7845e275bc760ff99bfdc910dba3c5b37183bc1608fb8bdcf92856e3e8c13555f5e7eb07d0f664476b15e15f0b44a1daa766411f89c6b580d5b1066a3e7b5accab1fff24232cd99182172358fdeb0b21fdcb6ecae7335fae75d82e8681990471de4452cf3abeab5813ec63db8f269b9da89f707144e214b3b482b284fb08ae76baa02c8dca944152af0919bbfc1abdf2301af7db76543790db601f1c928810ab4f43756718ffe4f699d3b7afd9e994a6f49197f60cfe3beb77c2f045870923a3b921d5d2c9ae38fb903b0f342e1b90cbc8334cf1fb7917cfd85cbc39cc9fe5a108c2124c2d04204cea02f10850925f5741c2a00e38cc5f26592c3931c2b269460c3f182c62c265d1794fac2a4a2bc6af423536e79475f01192aa5688950130ea3b1b3169e56d4a8f7b9e638539b350e8d1f6ce9ffd3d3b028c781674337a94f4b025ad6f41889f1388a9bec7ed7ad67f42ba830a599ec0465af395c6608f3b03cefc0552793c53ed3cccb06c60ca7ad3624f5dd6275560a786f710475403c223807dc2afbf71c5572d2ab34deeb836cc85a488f4300b261e4bb042a89790c2e188043484a0d200180d0e0b5cf60757ec26b5d932243a5e6c559a1ef748bede96535e2920d83ad7c5f6ccfbca8859e2dd8db7cdf6e36709e017f1f25e3c8d7147f1e22dd3242409fd6fc624b4dc3031e6fa91750e16090c15e6b34b41ac42b281a05b543bda6008d9ba36f5bd43de6f9007a1352a703f9f4474e10457336878ce478159c2f7f1dcc6441700b095e30c78ab72d2205599585f349d9e8a9016f75226c3f23efe715536e15c54c7a73c72b077eefb4f42441067b19190d11bbdf9f8143d6e5083216fecd2db2a615558451e073bfe75ca34a65d0e85407c6449cb6449cacb42493176eac7e832d7ed4ab0a7dfe57927bbd206f510c7e9b14606d18ab1f2334ca91f66e88432e04dea38029d8b1590d8edeeed08bff5f5a1b2ed9663c84f8eb5ec3842a940a90d0d35f5e4ba89d5e5ee4a62a5c1abe0722afbc301e3c8eacb06e24bd6cc75f413504ff09008bcf24fd90b219f775a9105c50aab91721d6e4e13c10f58735623c7f8585a5ea25a225eff4dfec3d9ea40434a0954a812129ec60c10ec3302e41323ca6fb8a91e79411d39496948d1ff2fb2f24e1353e935464617cdfd078ab9ffe4d020b9101e458ded2d9694b8903f788364d3004075f4a6fa72ded0d5635cd9eb6ba38584d7e4f724a3806f2485a9e999710ea4ff39e7c2698761494152d103f3e963a793185275d314b162477bd76c047d495f82ca8ab4947433d3743412b5b9da53e5441da717eb436df983cc662af38ae7b33f0a80e6eb429ab161b73c96a8ce637a99786317a400776f17bbec15f1130324c455c60fe9bb6f7fe5f229d56601936692c0921f39f6f73d52982e96d7f66f7d22623a77c40ad71813f97d63e6c50a65c7d386c657fb1e6afe0b95ee49f0e692086b6b6c3638f88b5279f60bd6b0f8ba631c2953f3f01e546f40154a304ae143990ae713d247336df9767708b8d47d0424655c2d52a91c122376801290e038eb21931b19ff0c250ea2dc7274ec8bc7b7eb8cde84157387c2081dad56c2615213f96603e0b66c9fe01dc5903aabb8e8857209e15a703efc1d16718477d7fdd34ce9f5e6f1743b3fd9bc6fcfd8e636a1734ba887be2c75707887b15f526b028e49487a6729e3c0421e00133eef701ea8038642e6d682f7a19c13e836f21bfedbdf7967b4b99924c01420899088f089bf6b5975d1c83eb98748e4c2281dd1f8cdb98e9fe28833c4e3c767241fa16520ebb6674ea74caed4f49eec3685eadbe5d2f372367e72f5cf191f7f81ff849ea7bf8650141f90de3b004b4b0788bf7c8b6482986a9f87611cb9cbdf6c2253ee35cf336a72d2350f3154fdb44def4144fe45be440e9253e55cc6bfdead5a3d73704135e1253d470933ce6d233d0b9f4ce432abd88eeee726cd7f7b203f3cb7b3c7bd50be9571779224f45e4f578952f5ce21b6450d621f8342750c405f824ea2e09f5460cca8a53677e60009f3c29bd071a2084f04bfa0eec8bdfcb0de6eb3d5ebaf3e9aa41a5253e6d08f3d2b7f4b2c74ad4b430e377404936c9c5c88022217de3bc6d302af3f68d7af23b4f488a6729cefab05950be9f7e610cb9227cba5c877fe7f2935da4db56e28894a72ebd662151c204a8877ee0ec0ce939d25ad2844f50b614cf07149d37e1365048ef22dbd5edb85c7ee1c7c52e01dd137258476f18091df125bfec7288bedd1be1e6ec9d73b78304bf75dede7d21efb88e5cf48812a4ba1d72ebbccd39daede81e06471fe7edd3ae1e9945ae6e87fcbae70b95888f013179e662978094df95a10bd0be23621761b03df3dac8cab46f326e5c3945d2a563de110649f07265fa09356c22df1e3691eee153113eed4c714c856c510835faaa04608b5228a1463fd203046af490a224efb06de1f6ec2bd993416ccb4daee01c2323a98c2446e62f2cc1c74f8a6d4b7c858a611886f11732f1bbed9ab3a739163a26449ba2c491817823fae69833925ad5a0e42b47c65c8846d10c441ca57823926af78595fb9a78294e1b0596220e20e28d1892220df9c3526c168525f894cf0891d70106231011c7ee8dace3227506438ebe79fcbc83cabe0c7afccb27ed0b91881fae132746947e3af7b2a3fef4faf30ba23ff9f5a8f17a024f1a727e594e8638d1f92d458e46fc440df7c84be7801002012396de830f7ca2cef916da90342f59dd7407336f2f2f7d247d8f11833186f465496ab18fe004c8909325aaf45e161a967c641aa64cbf5d30940b6062e737536cf746e48f5fc89827412f8cbb1b12ec98de9f4f143eb8d3d1e32fae999bb16b01b34b400f2cb609b61b29bfb8e01cc1cf674f0946e2c6f42448ff9289e71b3ea2c3187a4e7dfc90b83c09fa7165e6cf287d9b9c12fc976fcb32ae141dd17fade4936423bc5c72eed6481ad9488d4e7dcb5aa3a183c6d3ef081a4fb7660651ff8808f4fe47c49fa79e711b71b009364ef3adbba668ca146ea76bde5ee69a27b52f8c5db780d905f8ed764c0ff97514e0e97744019efa56b9dd9a8d1ab71479805f89ebb18f5edf8ef9495aabf08902e72f7fd971f1a93bbcc218c6d3303ec32e9f4ebd94bfbef8f3a329fb3db018a5c08229377a297da4450d31cad5eeeeadc12eec628e5bd4d01fc3463630c7bc048e4830d7755dd80c9be08b6af0028f182449fe164a0db9c8cc5e99440954c3fab292e49c5352215e0c5e578e68861a92fe22f129e460f8c47eb997c0cbeb884f5e4c0f302c462c5e546c14a3dc4234c72eece381397b887d2ffcd129bf11a594524a29cd328ed98c913967b864a8a1ff9425f0628e696812e475087a6286266628f21b2fc7169d01c7c6a4deb06d492e1a1e20864aa280cca286f5e77452752698ca2835ece7de9ae816d89c7cf14b438dbe956b1272446cdbc53154e9d2a902c830c4244e2fbedcc8f9f198ce1eceefe5863f21bdb10c549041911a390a8132b8974fa12eab0c411fd23200f9d4659961489b210815ead3321479ee72ec179d8bf9419dbe94c1c6c1c6aa67499233331bd7f916bdd9491c603f7e51a585cad7364af36d548ac756eb5191f5cc3f1ca38b8795e421e9f2e824ef7217efa25d8e287354fc078e2c3e714736678f485a9c3df644227c5271c66114c9d9a30ea364c4f090a25c3c73195e6cd2267692177f528d72f9f86bf154547c65c5b9cbb1e2dbfc48b0facefd63e78d443d44231567f1426ebd8ab78affd02bde88415651f96afc6954116b62a7b56a299c6b5d0e4ee4313fa85b10dd27db63fc8d5d87801e5b386226f2ec17b30c6ae8830f9f8e9d009f5edaf7db677f44f0697e92410650f63a62b2e4e517929a5523e680c036a821f7ebfc720a4738b23e30259d4d47a90735fa7aa983d878f944f7e3e42e3927a5949b11d383fa34661e46b32c731dfb99f778ea855ec4479f5e587f661e75bebcc7c7ab7d7697a33f52b84a3ef618b6acd52676ae356ac1b44a3624949d3e2690df89f3ebd209a3e44f9bb8d5a82c280b6ab55aad6c48c3d18024d17c3c7b67437cca9c9d7aa8a5604851c947a74948947e55fe90b01ff9c34c90fc746741d036a5156a6c5d5990962265419e05adcf64418d923f6c629fee4e22d59a053d4b26724842f1914e9efd6212ca26a31818540cc947973e1f59ac833a2516ead4e153e83b7c0ae5cfb37328718e7c8efce19303910120ab24314887c54c7e0c6935949cad29b235fcbc2a3733e86e4c3f311cfa2b7f193044b8487cb00166dec461904b35062bc31e62bfce316ae62c8ef9c4de605f8f53b532ecbc838ac38d2a0ca9f4e1544d95377b1864c7bc59844176aa79121292ba477a60159fd846c9d7d927ce74d290d17e3c3f128e63733e31e4970a4fe87c38552aa0f7c6bcea8d9a2a36b1535a2b89e41e821ff3a34e0cc3fc62c7ba05b4639f12f1e7cb8e971f8fe93d8c5a1bf68da78ccb53a4edc86862db52d8013dbb8e944b9ea74e4c0feac4f990d2257d645eb267dbf0e8797e21d85fe8b28f743ae8cbaf590cb2330f6a1899b09e99500a46d09ff451d6dd1a7721356d5fb0776fb46f9441f84befed802e9d6d4bd988c4e77982f848f54f2f29d29383c35018a8856408cf0f1f9149fc6115d1c9d9a08592a4f533369c4c9ce8b18564088ffc217d30f96115699dce098292a4b51ac240f84713264758bf137562180c93687c6090bf52b4013208b213cdc332cdd7bd514ab6e6da9a69a40a95b6d9ce8060a6c54e56cca6377d18e46b4a6f1e61f9c020b39ed068ac8b3b079b4be71823894d5c066ede3a2eb7487ecd7108c849599649e76f06d51ed2f830a3f1bb28e9a8e92169522abffeb6679e4d4f53851a3d9c01e2176c5b0a57809e7de66f788c6a2acf52642b3be8805676e875b50d3fbf15201ed80033431bd572c6af4f4ff616a0478e3220c568f951b726543b3bd8a8fe60988a1afa00f38c03c7d6ecb36aec99ac465d3e8dc266141f9a58566f8379953a8934bdb5e9f9337fc05059aa15d58773c5f33b572bc3bed28c6c2acdc8a6ea2654ea5593422790670f597e7d4452591c1348dd1a6fe630c8118b5a102d88c6c3a889c35375f9a543737080441cf38918b840b5da2285a563d7c42e42a5f0b4e9c4970604c9d2826a109d9d39831bfcfaacc1e29838f106a7f0346ae6c4149ee799935224a5089fc294253c3b7bca12b666a900841c2d08fb329f9c1964ee4c9d891371cc1acc1bcc19cc9c99f3292c09049654ec74c86f973d5bf34346f161ec44ad8933bd0931f8f5f9c4e2c88ac41baceab490a203bab0cbc78719929eccc7cb8e30c89e15893832237b839da19035a1bf9f1a3dcc582ca97ec1ead9b7c85dd8f475c9430a13b2bb190ff2cb7e565a330b61db5298a29af9ed9df830fbd566eac3a9d232d654a5c41882cc39ecd725c7a0acec4e70a97d5ebe3ea07a60137b0f65087bf82b822dc4bf9b7905db964291e424910f392353644a9ebd322a6bb189bda9c8517d9841e1a1b035fb2c320167ad15a532255992ac951d79f6ec48e6c3a82cc966adac256a59b1e3acad895bd3720a23eff193adcbafe6586290dd478c2ab9cf1d36f2f3bcce60d470913cd71d3b7a04be5d08e75bdc3a91af4f9e46fdd026769fd52a61fdfadcd91accd7f78a670fe514bf2e5b7115d383ba59f8507be28ad587f36715840d7f7d9a6a495c192a33d77665a8dbf0fbad8b3c9ac9dea292dac49cb3ff00841008e08bb7b9397f7ea6c071658be6b7834c6c52db637f3640b6f199b70b4a26326ffe4c264e386f02b529680e3168fafe66932a544f87e7bcf91357cf1ece1f3eb5a3261336f5377fe6cfb3cb6ec7e8fb2b75ce203b7699dea3699390999967d77e32a65e70ec99df62173fa7410d29a3486c62e79119eae84733d89a99e6a9d1b785d4e85b2363e3773ddbfeb68897fe13001fb72676c76e8ffd831add07357a07a9d1bb07357af3a046ef1dd4e8bb35a5dea9d15b07357ae7a0728d831abd756af4be418dde36a8d1bb062d2f31b8ce7191f1b451a43609a96dda826b1ac8e0766b5c7e7d9bc1f50c5eb896010cb75b6303d73837701d031a5cc3c0b97ec2e35af571fd8218ae5d50e2ba0535b8068203b75b0e5cb300e4766bfac431c833df4faa89eb15e8c0b50a00c0f50f1b5c3b21c375133b709d821b5cfb98e11a053c70eb1b0f33377690b101001d4c600e38d428c57c9ed3b8c10698971932482e315a462c2b2a3545d4cd887665e4d78c056dcca4720cfe1067bca2cb38a59763ca3863fcb827116877e30db65d9a816d836170a994735e181d9243e359196deb46a4dad1d6f9f5ddf9f5cdc1c991237faa5686543715ae76ecfc4565275506573ae6ca5eccd1532407118e1123c6e0e72f8cc0ea5946a396182e2d2e2e2e239615171795eae2e292e222ea5c5c5c72d0bab0bea574b7942d5bb68760e4f1d1c9b6a5d428997239ba3bf53bf33b34e54525b783f1500d2ba51ce5e9a11a52cad56ec4ce6ba8d1392f2a2da580299d8eaa92b2c2ccdddcdc0c6e2c2b2c2a952545d4b1b0b0b05c53864a94cd8b4ad65b092761f223e78551496973a47b6134a3d9baa6ed7092e39a23a76d5c2792225173142dd76d0ab79e288868c8afa7f004f9f5ea854ce4d755562b7265a539ae30942028434150d80790cf11243e4792b458385127e6449ca862264e7ebd9b30810254440f343ed07c0f7b642632774b392f795dcdf18adc2de59c172631ac3962725e1786519a6952d39aa386d12cd3b46de33ad975cdb1d3368eeb3a9128a5ca5a9b638d222ea6a470b1aaa870718545b2b0344796156ed70be9b6b4701b2306b72e244922354792cab6c24516c9c2456fd4e9884f475cf45a3a1d918b5e8c4e47fcea225d5c9aa30b15bddcb450d1cb4de881e71683eb3bcc60e3135402f016a5124791b2c45098cfd04c042231e855154aae55dc6901e15a5501ca40520622a0c6da2ba0e084c6ca02cd19f80c73159b60c4097355c58c187a023680ac3f700966c43da368dad4a01a68b48cd26034df5e833002652029c31124b83f724803faee701a992b344c224c23181876cc261869c2d476b8bdf8ad122ea05ebd74f9978c17b5e5a587e0cf1ef2b283bc6c9d978dd3405eb68f97ce438c6a569b82bef6d0885e3d6956733d97625f38f3411fd6a0187f7dfd83c1f6d642d8407ac5442002835e5d40170f0774add6105e46ae15d04584cb08cc906b15775a3cd78a03ba9cb6a8f88b2a25731c6a53bcd4582f256751656b4ef8cb436d8a6fe72e88999f5f5d99f63e418d48c41c09a0537e725ed76784c6da2ba0d0c44bfadbc96f031aab45d55a40f2b151d48666a60706a343d9d9a20850b8f0f2c21aa64423abf4a564f8eba5bb94b23ffef9a0b45eba4c818d74eea593d88852f315b2d92bc990cef8654149c25fbf2c282bd985124c8f164c4a6c050db8e0bc309a9d38f06dfcda18e2bbb4e0f4058910795e9a40e7c178119c4782b9c0e68d4819f8f6911199c755cab5a4b3bd4717650bba1093028309518e6ca1032d7240061feca8418b26399881fc13d8d0ba949dc4f4b7e3c8242e1768160c68a04294214c1185f5049b1d2ae273052dfcc0056420834df4a2461a7f901ca60341522082ce1430480de1050ca9282c81820950448185193a2ca0245192d2282cacd035aafb656d11e4d262fb90c382b56141240bc02feb89164f7e90a4fcb29eb4b6f38106f58726f129dc2c6c307c8a71e3388f71db7c3b6edb32ef65db7844cf3cb27ee697f544f597731ce73a30e7bcc777532e310c1bea4d509bccc34a735d562cc81ecacba74d7cba9630c85e77b12676ea7518cb03fbe5d3d7123e35d065c59e7a8b057dba0bcc090c058bba80f6c25890e3629262f21c03104a1fcfb28967972916006334c074f0ec1d4604c252517e8bc578e31f650305c90ebcf40ad4a88c2e61baba1b0ae43162dd8dccbcc35854e6ecddc50a2165181663e6fda036f4db6628353a03e91f3d5bd3ee83d3d2e1fe19d24d88d0b47c03350a4b61a9540f91d5a8253e4c5a4e9e3d83f233d4845b584a6a9b10cc30e3f282165a2e2f70a00a3e60b002137c20a40a013861c70856ac200d38e00118452a18482e2f0ca9391031182b5001e30629bfac2847d0aed8c2cb43e9e1f4903dbc2e29e764ae6dda4cb6c7b44decf150a25f4808306ff9ed8690a954fded8688a9940de6d113fd76432ccba6fb4e0173101829ed8bae82e66802065328014a22831fac16291f27b4f8628814543cd9bee82abe2b891db060e5851dbcc047ce1186bc488209a038a20430beb8e28b2ca492f0880c544d64b1f2f1a48712849a44c103297664f0238599a4a3e20b2b7cc17a0ce33444c141e6048e5fac8658dc45a9f43457162a80f1ad89a51e2ec7300cc34aec44321734304ea2f880d2c587a52f58a8e6f051a8e2f051a8d6f82854fa285463be67a1ea7d14aace4295c647df328e85ea0d1f7dd33816aa36702c54619e85eacb47df3a8e85ea8c8fbe893816aa323816aa2416aa2e3156e887946ecb47df583816aaa38fbe8d3816aa2c1f7d6be158a8ae7cf42d06c74255e5a36f2e1c0bd5fad13712c74235e5a36f323816aaa28fbecde058a8761f7d7be158a8721f7d83e158a86eda47df6ee058a8661f7da3c1b150758e852ac6b150bdb818aec4d5e070e072c80114b59fa6d3971e794829a504bf3f7afdd8da8a995a995dad4c15b61e438dceccbbbb3a667c6401571be583fafa36ea72f2597b39e050a3f499b76daa9d03d68606d198df26d7def7de6f18d79eff46b9f668fc966d1ad79e0d0ff31bc7b5f7f25bc7b537e33711d79e8cdf52b8f648bf55ae3d97df54b8f6627ccb631e0a3a904a6d7b4b77433f7aa3df465c7b2cbfb570edadfc16836b4fe53717aebdfa1b896b2fe537195c7ba2df6670ed75bfbd70ed71bfc170ed6dbfd9c0b5a7fd7603d75ef61b0dae3dfa9b73ed61bf79dbc7b5377f8be1da93bf95b8f6fab71a5c7bfc1bc8b517753081a2fe6c306f6d042027c8944fa65f1649be6840042804f0f702329452ca15412fa05d7aec1937a879657777cb3e818c1d657797769453a6b6c8b28799191f6868b8bbc4a78e717e3f9953d441b24819eb4bcedddddddded2892e9ca98ad26ba70eacb2e17bb957ba899e8242daf895dd8ee2e17b1599d84856b2ee675ad6c9a0ab77172a77639d6b70ba2aed2a18be797477475a14be18676779b1b52596119b5ace4624ee91282d6b57ac27a8ca166a293b49c43cd442769b959c10b357a4bd1cb8dc40069ce90c12f33220c13addf242f0b13634594bd85064849f8e3c7af5b2b13c460d0f3e5f33d5747f1a18c6ef9e5554a571f62a9568ccf8218d40296f24216b7ee55130963d6ac3c8d2c168bd5a4ac258ba8641b9965dda944f85a4da06b67426154e6e35af5ceb5ea9d6bd53bd7aa77ae55ef5cabdec952d76a275bcd27b028e5a4945276a54a22f5ae216ce2c9e179f6ce691d465d3d171149248736c9617515b85e19a94da47b7ac5205fa9ee893312cdb8ab2c4967adce54192bc3611417246b5dacac75b1b2d6c5ca5a17eb62713b7595a9b85613230679b7bbbbb732c8f287c13a9b5c394e1835e710141f7308888fdf0b07c8efa573e174ce04da59d587bd43fa09c4851d59856d4be1b56a2335049fc18b8378788851978f5e81bd02c15e816094df95ead5f49c74adfa894c05b3b51397aa531fb6ea52cd1f06bde80c2683c166606060604a254a49b5ca9e69b26d69db488569201fc2744451df1f1f36cf75844dcca4723c832e9ead99438cca962c61c2c4891328170bfb32d5d57315b988307bf3f06908a3ae23176b74b1fe49cd2e9e58e81d989f56311346311436b1f39093f601c5c9873ef4cb507c0c7de80d845bf163202fb84a7bd43b3b1b0c0c6f31c6ddb871c187f9e95ae625d0069fbad26d7a37dcc8cf3c219b536fdbbccb1bc223620241ccf8f931400c1b79868479cceb8186c1eb9b591fd40f6ce21cfdf900e393e34a297ad13c70b7f09fd9c371a6c2704ca861e91905432243952d988161358c13e685c5c05a4c50a387a52630a1482fa1465782143d512401092398ef274da4463f428d6e841a7d8bd0ab1a9d084368286af4dd1a18919c91411b85a5cfe9cb635206370a1f815b2e26e487db6c4d37a34236ef27d7cc5f1f0c4988e6fd8444c326b963e6e557629072d760cb89aa36addf58ba433787bbbb4b22d51ad2f5f8b97fd3781e36e40260ea7099b030a8401a861cb41ceb6b45163834508dadb4cb55d20ef598ee2ac1603d49ce53c17629a5f390eda5f8a878e5fa0a341a09bd8165c586114b97637d61462fbb2ddc0c2e64bc24915c7667c888f132a3a5fb25dcdd5d18ae85aa0d0e589bd9d3a630b0d8c0d1b841c569e4548f2b236bcae7715255bf1fbfde57d42e86e3e2f28b72a50fbbbbbbc62e0e59d3ee28e1eeee86926e268ef22974e9ebedd179481a5766bf0b001f60692c2068ac5fd761e77d02806b2b6af4dedddd5d999652dae01a498dd5a78785eafc48851a5d7e4441951f9d856aff15aa50e5ef33d4e82c54e3b350ddab9b0b2eec90ee20c3377688339c085abfde5e3857dfde61a8d133e2469763bda3d4e821ddaf23dd19910f3850658dd2761ab5e970e387872b563f46ae10a9562c912ada6c518b6bc3a2c2b8a5e876f86e071448b2ba1d0a14afa083096cc5ce72c880966a2153769e2990646540120cb28513996a3e3538fc8e7ca6aa51a45dd2ce0c0f0d4bf36994103e9acf0bc120bbb6445bc2a750fb82f685f5f1914142b4899dd25a4924779f1762653a0dd50435968abaad0fb70835ec0fa79688b9b8bbcb91f9d4be5f977bc178894f547b92d1a164a9582a954aa58a3eada8e4a7098bc562b1582a954a25552d553468b8e77d5f4c4ca986ac51a339d65029629b6c504c492020205654812029e6c49dc8137b582c164bb23ec98af9624aa51a3570c021075082607304eb0ca5a92c928f03751120e84e22d54a9b5420d20679ea0725b904c224d02581a6049212a8255029b2e310048721cf9e034f0e5cf4909060a703e4a2174e22cf6eea74c49fabd541eaa04373d4a1bfb9c3207bcac7f4a0f24e9b429094d13c2fcb57c9e4be906a5f582bfd4212f685a4eb0b7dce9d972df067ab460b0ea2deee1e0d38705d0e2dbf9e5d290c08692e0660431b256bb4b9c00c163e1f92bce8f9909260bcf4c200eafb4370ca26f3da28cd59f37e509b26686ca8ffa0365996695e075e1cb0369933b12c9b10500f41e60178994af9fe10f1653f06647cb174127130c7786a89047f12fc42a86fdb4d6a4885943c7bb1f3cc5ef03cb37777f96e8d10ea613b7679e1122f2fc7bebdae8b4712fcf2a3ce42fd07a62b6db0ed9ea46cc6c11eeeeef340227e12cc1e6ef492e0752977d79b775dc654ead7b79d8062fa6edcd61c14984f0f891bf918e6c505e708a6e4a687c41031357dbbdae34df6bd3693dbbcb8a6cb27f75d2a7f7160bbb8e9bd34a073ce89e83ece89d83cc606f5880dca7c9b7c0f9b1e362cfd08ea9be76373cef3c1f9b6498b09528fd8a05f9b5d1e38ba0ca2c7f7da6c25f876e9cd6e47009efab6713b51f6fdc05ba4d77575739b0705f524e8474a63cc784eb16da3a090505c2e3d242ebf99cf3f444c7d714d190888f801c51134cf23897eeae0f7b0c97ccb3c1f993701c511349f44f37071103d9ee34f1b9628a29f12fc584ca5302931df2627a1c064e744b1ed17715b637db14d450dfd4b1f69d0d89afd6b77e6e03cd0b3c1602c31a992c1b0b44f6a585f1496e03f1b0cc612f80196c60282263d04adb09fb0465cab2abd243f24e64bf7a4112ffdedea35d673bca4c8424a29a5642eca949185ea35478c458d2e3fab2bb34ea5e496524ae9de25e5dc95723462a1e96e2eb6cea2465f29a5949469a49cf9ed2ce4ce3933b9bbabfd0752b050b5b4b4b4b4b46c38039adc23c9a82a4b6c8f4569ceda17579a90cdc3a803cec34883cec3f8821779189d107db4fb2af791b64ffbc0f813572b9386b89a1ebafc52ac6571c8a7de058af83122761968d24fbd2118408412ea01f1c322804121a40f3434cddf124ad06f1fc19efa4b8241eec0027d77534d847d135ba12d990c95ccdb1ab98dd2c087a2c55ea79466c6e4c9c8a48c19c5aec999c2198d669a47aa14bba6cc483e32a4f9f59910f47e65a86432aedac44e2b098bab1ce617e6b06a5408d6c46e821e15b0fa099f5883b843c5b36bdf1269a831665bb371b4e5535a28c718dadda5d8eeeed2ee6edaa6eeee4cc3a8d028f6b451b44debcd5a8cd124e512725bc64d5ba913851a1d0ab4bbbba75c19391ab18c9a0b29d4eeee26390d29d4e8dddd9b7d7b7c1a86c8ddc939b99748806013fb916dc3bcece9637ffd7cc92fbd2ec32ed15637ecc3e4a6b587dc671f10479e1d88ecb7519987154a8d745de35ad34aa349b93a3262b46d7fd551c6711d1239da27095c6c527a79fd971008e872c80f08306a777d17eda4dceef9d4b5382322986198413f412d36f204154427b0a640f92483e412234a295b462c2b2a3545d4719b9651ec9ab239ca20cd10b187cdb1477397b3513ecd9841addfb614caa719ed724d6cae49762f3d7aa0bba134d3364e244aa92a2c1ce6ad0dcba8c5c585240346c6cb0c517f7265a44b8f2398b2996387a1574a29e5ce39e737e79cb347caf639594a97334a295bb06d294c59918290b32f6629e3bc3c6e6b2fbe8c3de48fbfe1b3d61776e26272ce39e5ce9de7b93383304a1ba2f1683c292b236c0dc4ccccab5f5fc7d6aff5b92e3b5c206ea04cf472c3035b42e4963bfd92524a29a39452ca29a592dfcc08299e6c4a518592eb87c6d27a34cd0a3d8dd25a6df2e193f6c3a015f6a48d61c19ec7b02e07037c6c3a3ccd9ba0369b6bad46694db41f3e6941da18f6a4ada1e54527d6c4ae79f105cb03bbd6a451336d0ae2534c31b8863d45142cc8ce335a93881367f0ec51c502609f69d4b6e9e0d9e30b626adbb68d531d538dda4cdd0d8d293ed15d506ede0f6ab3797cc1a29cd82e6c5e5a3633a578fee1d9f02452635e055e7aa71461664b71ca6a4b3dfb558259535a14915bce0ba399b6719d28a5aaacb08c5a62b89064cc7881f9d949c915cccb0c1924971837d080d999ad9f182d239695d97a76151afec2ac9d56a9d414d1ceb377eecd88aa0b88591db769de2723aa3016909661ac67a75f0c297e7187aa624a2e4f7f3e5bfd3c3bd605d1f2d94a0da5181fd383ba2ba3861a2d9fb5a860c695abadb1e197db676a6ba477118af0fdb1af5cc9d5d6dc20a59ca9316c4cb43a49658f1da53227816d79f9b471d43c862a3d7a7b37393adf0590b0c7bc5932fc35bf979b18c65f5f787d37393a37393a5de86cecae8b4fd77793a3c3e0fe4d0eceabf82607e76f72825cd74dce0e83fb37393c0cee4bee2627880ea3eadfe4e4f029fec79b1c1c3ef5f87dee76556e72540cee87f52627e7260787c1c56ab06d0fb5bee4624872b184b989612496e4383eedfc480cf6e05a4965df48229d26835a54527d6ee79715c549142fbc2a68f5cbe2c24994275c5098527afd83caeeee9d73ced943b2532bb3b3a79c73b29472a5d4e1f23d6bc0c51f5b938451a425ce047432d334fd51cacd4e9ebdb750f9af18e374c2a86ba74d4df8349d67d5c3ba7cb8a5f37c2969f94b49a3321e36b1673c4a3ee349ed346a1b6213bb0f1e202b9c1e9de720970f37e46af1b7057dc1f3d38bf1cba1305fe7169f5cae256c52922979f62c1bc2a8d6e91cca2d3e919a23b7fa8da149aee84e63d69a229b52ac299f165c6feea246a79768ba94b28bdde75377f710cfafd68aae832dd4902606e6030501a05ba874ccf735452e15cd0000004040009315002028100c8904a3d158220cc30f14000c7fa04a5e4a9e4b234912c3288861180621428c310410800c02244684510ab0cd449cb75694f6a3953a2f124248d26c8237e140c5de51d2da45942e72876ba1c4ff1797c08983e19f58233c216934e42122a86c5b0281f67179c86404d848a4a362c05091c92ee172a320ed6330bc5d12095d5cd73480119eadb0ebf9af6b70409f188c9af4f9651ffcaed07b315df5c5f881bc8da12142532b3807239bcc3e485fabda2d5a1efe4d45487d5b79ff2745594fca67b971757ac87dfa60f9c90bf7772feedfd8a8f9a708161075af3dad05357ebbea6ba51136946d891c382d0884d022cfa15a181d3de8290217508c054642bf86b9ab32f91c1dbc6e0913a3a792d2a17fca72f9ceb76b5f3c94e9f11882a974acf22c1c3a07f0bfd96e6e946946f188438ddf269ecb0c212037bf4675e207d192a7ed30640f2738130a5a6a53a1bb0cc40bd735ed65f8e1c66c43423e31d2e187fc45de3b22b07718c726920032d3e2342a598eba4f0b637dfaaa229d758cb24ebd040b19519f0bad2d001feea2b42697c60458e71f85c6cde143f0d6134c7f327ad5686a431c4efc260e071200e9b5866c32ea382dcb06face99cee8063cd836ef2657378814bae2dba9f747407f6f543cefc5b6e159304ecc1884990f828c385f8457a0b245e5c47b351c06424ea8aa1d794e0d1baccf22745826b18cd4ff1e03e6b1925613169ecbbcfca268ce5b28506ba4e794cae7b68ee0535ecdc8d7474fb163cc3b4ac03a4e15af0abc44ccd7da883adba166f70c964f888e1810da1c724c4231aac04a7fabe300cf0416de9c6cb79928ce5ae4aec45e6ec4000ae20b23c84858e03ca99cef461976fcf40b8ab30b7c4850d248b7b9e2ba27b62181a8c7fa8a5e5d7c30c19282bdc8dfa9ceb51991f7335154a5e6618b6811f9162ddfa46b8f8a4e159d0f7e29eb6f5249f25256fdc0eb139f756b1b059de5adb5239dae4a97d3efbbfb6420c1f367e35498d701cd1558b9c359a5f482e7933462705d043caa7284ce87a43c7b72f1391d24ae0dc17925bf1695a5cdde6f8b88a73604b122c728104ac3f29e0ff60b063541707342af45fec882900cae7b27d970d6a540a6f37536409203bf79370c77420bccf841ac4456ff8d69e2d65f4b5849e25a7c12db56efd90cf8525ec4ff3d5ba23ff2b651e647e472146d7c83e6745c85f6709b5a25b8ec3377f5b6ab55c301a5332b8f2c62c82e419e2843bd1695405ee77aa84b58ada4ffd709f4c51d7780d05c3e3053d847be3b8fa90bdafa54b0b4186c97cf60dae04e97b0ae89a60bc2c92f43f17806203931721ed4137547fd3a3726424fe26ac642092f6924561426a09b117096ba594c5be3d8981d05acfbf336cca1156874ee53879ec966fdf42bd874bad56be690f9afdced68ec88d91f110d209822d7e2545dc1158c962e2d9bb575beef846e1b4e12a9c3a03b20433287fbb7e522836a729c08e7fdd82da2451855b1aeae6bdd01059156d78d468b9cb4f949a0d8284ee4305a3080ccc25a5b488def7a6e9b5278ee7694664e710c48c7e5cb8479edc2e1f85d14f64fa4519c9c5cd6338d033d6ea82f13139741e447ef622101a4091fedfd08289318d482dd98436c2caf11c9fcd06f7221d974d47c574dd5849caa6722b90eb68a42b41f9f4a836de099b52bc408bc68d3764fab0eaaf40b18b615ca52d04a65be23af18920f71c8382d1740f4c19b16c4e53b6917e9ad7f1839ffacd8198b63bbd69370940100f9244f1d9603d3195118efd761d05a49b525e124220abe5ec106d10d9d47fac700ad3e6d3cf793f73ff81aa67a2ad31de42224489ae5e1bffb84aeb19133028035261943cd0a07049a9453aa91369d6534d3615771de6975086c42fad1028ab8bec74c01469108f7fcb1433f49302ec7aaa94f20b2d93df4035aaf5f7d1f58a70bba97b438025a659e99e288a8b1b1754bcfe51b8472ed025d255c11fa36ae464da687e0576549fb324ec2b6b888077ed7d0dc3e2ff1976deb8862a25321087031b6b13ec8099daae50a6138803e5ff6daf62636c65e54b96ab4e52f758dea57ad0920d4a076f1e07079f881f0ba118ca97408cc9710335f6401b420bb41a6e1ed86925144f260b239b01acb18a683d58c8ee497963e6838e8800cd9f5f856b98d5b7b58272868bfdeea1c82b4a0c492988067af1e598825f2ce43fa6e70a6ffc8aee102668e4ba18a0e182e08d7c24c38283c3063598a194fdb1c60eac8c08d7046a85071ee380c03b7f7e391ab8486c2fe111963010bd7f5818b3c16801a09ed8339138a09ad5bdea6fa64ceb0523ab621b429b4c97016a4e875a773140b08784928b53282f51ca03d3e2dc9bb3e638494fa72cc3cf653ec043fca793939fb0ca52d90aa9a54e794b7a9bf1dc200e943a4d50f0a275333faf996175ab2b370097e41b9f9f6cb3c7d73eb91bd861ecd81e5a3ba1f205ee1597f40444025b26e176a51c23957abf656ab932205551630ba565c16d4797b0578e5a18e2704e06f016feb605d2111d4ce977bddbd038db2f388196e704b8a9ed6bda4d7db5ff70a0f412811624f23bb5688ea9110620c164cf15762e0802b8ea0d7598676cd27abbf9abe3c472bb4c798af4785c208b9c23e106334bd35dae56df41c78556864cacdbfcf807eb48dc590fc7a74dd3a51bdc1b19fe4ad619a0fdc1fa799a6c33ecf2fbc4a0fc2ff9c8d22ddf6474eadaf3110dce7b07c87514cd51c71089458c576b2dd841871f5705f54ec1e8c04e5435706a202b3d1f102979dc7fdfcb0c7973385598d0f0c187efaa214fa98c2269ee6934141cb8c0b38b13cd45cb3edcadc41dec95cc6d5e083363a976feb578aa1906a4b3bed5c6c5c9ee79f268252481a279b75d6c3997940a12f6a8083b0d3156e913ff8c273e39e69333ba37d0e17a848811503254b5b8b1bca3ca8b90852895b91758ce0f6887419419cf430544c6ecc1c80d686a8c439fd282f8e05a698c0b8d81421d5c462a54bc64f3e2091456ea019f627e7fc34e5ca2b2a16cde5e009fddd15f51d09b74dea706105294899f73730e0a84f194569c5bf5fb759c122ff6eaba5a18992fece9e9d15c3d714ef6863b5fc6a28608bcdbbbb1f4267c7e9f02e30e178a9fdfb245279389d565180050f590f89298472cc98265b453f4f8884d48ffd8cbb6913a47b0c13cf8f1593a1c1801fb0c6287487070d0633e0b8b2bd8dcd2870bb6f3c3ee1f25a987717240ebd8865c514b0d79242076355bedc436c33fd80b2e7b499c34b11471215351f83d7fbf89a5d302cae111b3a5885e95150f0b0af66083edfca4869c79f07ca7d2d524b82ce12eb6ee25b70192c476fe84e9d9b89888618e15f3afc35e1733637927d0b77c5b3f38b86fcc72b2ef93e332cd034047bb7d424232eb8629a8d875751bdf72aebd6ed19535ac0faa849d031afd1497518ac6fa8ba506071d4962bd6b1d9753a6228ef673856263f4b920a8a4ccd04be4fe6cd61679fcd6224d60ee728d9f7d9098d09f7ccec84dba00e466ba3be6743f99127edc01331eec4cea0bd740d65c316e4e9cc04c519ae2e9e8060ee58ae997e3dc138a822815e71ad259a9ff3f298c1f6607c685a12154d7c520605851cd904ca815e6817da47faab3c8deac607fbd83e9acf9f5435c6bed82b8f0cea8ab873b3517dd0b88b213fa97e1ec09002d8bef3ae761a4eaf80b7262041857c1d1e0ba7ea10e9e4e5fb271731ad72ccd977a89ff91aea413882c49c6d83a38b16058d8c532748b687edaf6ae76ca9e784e9b22ed33e4724b8a33904b51e52eae86cc93abb9bdb05372f50f304cfb10106b512b2334dfd9e10fb490d135d061f63aea8af2b06e4ac3100ade10944d80e6eed2057f86fcaaff910f8afe1c4065ef80e86d89bfcd1dd75d76bd5e97e09fab2e68a3da7978ba72f2d3a928d6fbaf2b378f986e19d1b4e3d216e0ee3c935a73b5f202062d55918e5214a1521af1bcf8bb7d4732d95992200dd0e46afa1c48f22aabf09eda2ca687205028cf7c9b3e69a32383bdf85b5b4f793a82bc3cad80e1011f92dbd93a9c5864dde692572a6ba0ebc895c4d4becb4572b225d28ebda37542eb44cba57d3af5742f39d617e76cc2042ad70fb36bb8a57d1d911237d6276e386d026c33d791ab81ca13010c1059936c372880c54f7572491b41f621c53207efacc7447f2ea72f25c24cb519429e9dcb1e034b83c6b60381341997ceacdb827ead72ec1c184a6237b501cb870b2f63c46ca1a5499721aeec4d695c26bba1142829b822d60149e66b414f4014534583513aaec2e545819e9bd17cddc1888aca8498064a8496dacad0a5d4f6a236d55683d491bb58542e989daa0ad0a4d4f6a035d4dbe092718746c2b47db1b997c8741aa37459bab73b7126adf83ac837e4651df748aaa926dcbfc4311f467d3090f186b626e28d64433826d37a6476d00fc867e5973ebce6be6955cb233358c7f0e0449d3b433ea1156565aab0fd16dfb60b7d75b903569a05e21166fb99a10d5d46104a6aa495a62f33f6ffb32f9ef47999948064f38a636ef70f550e0fa7bb41de18936ade994b43f866150c8679056650c461d1511c1f5698d7623d457a0e54a20b56d0333cda9b14b0134e60c901f5f8c4bb40d8d75d57f2a285d4d20cb0f99df3a93523cec1fb3617a7b9399ae3bbb1d7a66f7fbd862240c3b119e7ba101f39df1c528da17642aec818f176952d2cc4cedd3b5c0af925786fac77673bd7d06f81a27cf7ce09dd11643d3a4f02e3ada82d5b0aa49a5ec2c1fbbb29edeff9833086fed70ee102c667326987ea78fc39c18c093bb2b280dfa14131a3b4bdf4ded712e5836f474d123286007cc6a4260df6a7fdc3337a33467b5430ec1a305df5588013c28ca754992cca29e0e8077626d4effb3bc05011d1ce5ceafa9f998f9baa3f28cdb8ddf8d75a41569fec3981be5a1f7156de67e0c02178e66e6b3b568a66571d90ee0998ba98f309a937354331de7105976c94b4683a034fe3a13fd56f2ce396e8c01ff393416cc00bb7c5499a9ecf9f29ce6ee1aac07cd48f61d07afb41d1dd88a51cdebe3d96cf0c5a0713f786c4ddb681a9637c395240334e221c86a00176f76b5373881d0a45f3d2ad9cc7c3ce6a9ed4f49fc26b8918b1cfe2f1bef2ce55aa9b7036152d2fbe5604a6656ba734c3cbab3db66a15fedeb30c1971110d6d0b6eed00841149dd41be1c6dbad57ee074bbef0215f2483d8e8c498f499ba1311246c9c86a559628f74e93d8485ae0a43163275f99dfd6ce441ef77bc0a5e0a51fe690792e4e78cd968c2ff65ae368557c5a91c15b076580f60a6966cab4b9a8a5a5634cf9d5ecc4b8f7c92c1466489d5b6d3654b22d297ef4bdaf9227ce87bd831fb784a8ad3bb64eaf0fd4d6ae7b9bd1bfe9604f8e7e8cfc70c5e565c21d0a681dfcb6bd28c638f8e0c783d73debfcfebf3b1d12b083c7bfb7036b41f550081b5fb743f4017a49e35790aeeefb2c1e66593dc60ed931b29d5165f4bf33ab3521d95fad1d71e0dba046de8abf0e82942d496e828f79c8b79746df5d08b8db56b4dcadd21d608d6736ccf99c4da7f3a4262a3d8945b7b7ee53e32d30729b2034699fba48f14bc1ee51f4b8e7599cec484fafad637ce7b37f534ff004f08b0d5f861ade0dc9dba2813c9651d749a64c9708c4149cae7c01589cbc7eb78bc479b96a5d511c4499a6bb4caa43fa078327539bfb777a4940e58da845d3d358cb7fa996be534fcdd17afca53f8b75f5c2ba7e1efba1bf55b3afc99620f7f83f544566cd35f90a8754732f2aafb0ff4568e4871e5abfb39aed87ca04e78347c8814947196dae9457e4dafbf9860a26ffc40fbfcde826b4e3e8d691d14c51076f8397716bc1cc7d2426a02cb37de136c5c33eb88beaf6f4ed6b6bf6a6dbe76f9aa7ca21777927d1c7e04d3c439b162efc9715a99508ef57b30da7de07b0e85f5e72ed458075d481310dc8a02c70621ae4eafc2e4bf21966d593df0cea0ebe25f702eeb29c1e17477a0ef0db09f9c206f51b988f000d3fde3252b14c4bf434f1d7a3f95d02db93b472fd04408d8a006ae8c1f4c4aa874eeda7a90fd6a1b31fd41cc6d0b2c1c18e9670a6ec414ea469997d58901de37391ba2baffa0a8ab9a29342ddbc7543ca9cdb6df9c01015cf81c40a61c84bedf72cc21f91ad0a17f03350cbca8af934ca1c12ec68fbc7ccd806148ef6d4407c93289430e4321e04102b0f095e11b9b9e14b835628a1d2058000f2fcdc2f697203fb7269a267ccb2d501877e7ceb3f6b269b04341db31b165a726a51b4d10f81ddb492fef94ba146d01a89c4a45e8fc9d617228400a6892f2d1fc38c008f25712eb381b7b112b9ad30a9418cd64d346ba74394dcff9916303ba118ee95aab51d16d374be82874e66ae11ec56b8a8190bab825ab4edd3ebd5a448de3f68ef7451e70cefd37583f873c98258e7559e26044ee948927b307b9260390831c99ba06d4b1f056f7942e7a70d1672ee2a24784f232578c78984d2c9f1ac82273c0ef3a9cf750d2422ada7dff69ef39cbfb3dbf2c7ca0cde714e25a2a4d2c7b5095591829c2f68a9df4545a51988f1b951782a9256130d21e88cf5627ec67de7ff4304d8ff10a0ed9243439b96e8c56ba4521b335d55d10c7df7e0e4e1dc8304a4ab50869b31008b0b1a80b1e88cc4c0683b7b211045b2b34a94980a7083ea98fedf8465700e1aee06ab57384771f1dd68b32d5d04a59166eb0a5bca94147d8a905bd040901955f39d4bf204ed0b5bb7b13be7a1c04b82040592f310ab10c1bf725338c12283f7021afee58291986f0c95e0af7f5e5288c79e2429144db4ffdc30edd14159159992e9bc63591f146a1589941ca95a7f276601360f0dce435a50685d5ece0f3806746766988a4294407a931c5854594cf15baec264a015ffb86003aaa1b5ceeb845d5c96df3bb2d9123aed48d3bdf22dc75da97bb0e3fff65e06341359022ad0653048e93bd49765f70e24ac29a736d4fbd63699abdb81a0a359be35305cc9b0b1987ecdb13d00562626345144d0755158fafeb6fe51173ba764584fc7aab95a05570b3050c7006b69d97b2399a42b057329648d3c8a6f4fd7a832c26e471660bd8b0c7bed3831850ab130ac0a2cefdd66080158297e435c76fa1eca94c1c0ba4f5c602c0ebcf9d822467faed0d69b064311743caec87425f9948cac5eed0b5b7aa06a6b829e518c66cc3e3d1e1bbeb923142e5d9a50804d29b786ec9a88a5785ea31ae6a5c9bee5a22c417e015fdf38cb05c6c19565c8d745a345e3d61e263e125794cd89c37a0c267b2595ce9e479a7938d9b1d6dd853bea448821a3ed930806c68c9370a888cffda833d1eb2005fe9cab29488fa60f49b0934f08c85f51a9577cb200f52143987bcf2433aaa5ecbdbd68aaeb3786adc5a0e04bfecd8ee6c8104fcaa377b2a3b07a62e5133f73f54316bcbc9671912c1e0f41dd267dbfde50fe3adc4e1de0bdf611ade0c407dbcba1b7824662a88aca018d6e8b2cb42d008b5916c11785bf5932d739582dc707d4e9f825aced1e35c27e483fa32424f4a3e2c12e924e464c00e384342cdb82bf9dea4e0369496a8b54f900170406c6283d646461e1638324e6f9b6c53e8df5f4e8a2899aade3541af39797f07f787c992e69e465e378651b9a4e63212237205e1b12cddfaa10c09198d5334d5cbf6eee2ff333ecdf7700067703c1f79849671613acc584723a2be0454a92b451dc9488dede27387abc745960507626b665cf2f0b4aa5b1c73050db3ac52153f4777dac746b72d095492bbe49eb318ca0e78b997bf123415795466ac33eb90948a5fa3bf3333cc518056b6dccd8d7ce9c4873ff33b571fc60a2ff5459120b9f25b552b9b8677ec0d10897ece4205fb02a2b2a39eb1c4413192a3c07e97176319a14f1cc505a31e88fefd8683914381d987f214d4b08a976fd6c30186d55e3c136211c8a79ef582711277dbae45145e88e0ee28676c183640322ce834e47633f730b9b090afb53197bde057f84508d1913ba742908a0144b9bf1e8b7d721f3cbd9b8f4afea4c9e0c1fc3b8bdddd8ab38dab2c305d18afab2d29f68210382744dd553c67193b58fb3d096397f2d7f7a2223362f0dcebdba9ad876613e5d4374c8602d1b45ab8d27522de3b8a4c7f0fbc0419ec9a1b7b9c1221d222e8fa19a637e0b56b3773c0c50df422acabc2125af18792da2e757ce2220fb92a1416b9c5962f8edff4f3f6f9d8edd4934523071754f06d45ef20df23c90a698a3e557e0488b9c6c887901ba291e7e20ee1c985e592a3ab6c8bf3001f6e9160a5da89bda523d64ee08fd5e8142beff249495bebdfc978adca1988d03632b03967f3a9bef130a1fc1426d89a7ee46a42101c3a1a0da3f6ee8a337dae28587fa0c316667f0c1ef7bdad7df72624bff41918e14d66084115fbe77f1d88d9e82c719790d0e06648ebbc985bd924190f616731cc79216ccd456a5b879b8f78f949810998ff8466d6025cf88b7e47a0de55f7ae2886387774da510bdcbc2677c1cff5372da86ec748f0907de3c6c0e5468f4c0ba978404ef99ad60b52729a0a469331bd206ce59be3ad56578f6afef03cc53e41a13c29f1badd8d0a7b06544a40b2beba80c28a843e3edca635ece5368e4a77cbb0f2a6d3613d38a9697f79f429b9574bb0a9c9edb78e6aa910ab8c0270d9c2752aa8531273199401579e8007a07ae6ea68d3ecac19b01f744004998444932e867a727b9f6343a719eab47108457517f52e469578026cd1619c099bfa524587ce27ddcef8f2d580cc39ec1888f3adca8702cb2f1cc13bb7f0c5a140fea20738a4f6aec766cdf1697c87556ee374137cbbcd3423a43f0b5b64e3d9d770f7c16bc2b143cb61b9cd960e3b50cba46d138df1b2739edb894dfbf02b00ee6cf4f259e2a100cec68e60be5c5676e2e3656799a3e9fb9b7255a5790095264b74b0dba79bb78b58db2225c6ef7275ec20f37e751e57d88a16c8bf0066ef7476619917c3af7e70a65d9ef3e1b6f16b73a4e8105cd64b05cabdba601828dd37e095727e1ae1a267a923113d91b6d2e856129a0251d4fa4c965d59153351b123a867d5e74b03a42a28d7719c51b95b82ac427ea0c8f6d7411c834514d61d090f0d5ab38ddf056e2c5c21c46eee2cec0063fb1f9777c52802a5f370121d43893313a98057e7232a94c9f7902359807e2e3bb89eeb69e4afa3cfd46782ebb177156ada09721d8b80455540ea042ab1c817062fa86f106b146e33166351cc80668d547973c6d6c6c490afb434234f276015a4005da1fe2673dab7602f62d0b941acaf17500048215481144d17b0dc7c0958d3abb8796d03a26ef6ef9d9bc44b18533945005cd2804fb302bb34d53a2b232c65a9fbcb804f6b56a2c1cecdb8645161d76ec0553312cb9b6c54fb1858e4592d0f7b7e19398a26c743ffd554c1e95e805b04c4f705b3006d4d8f67104d022a2099852d4b7cd86d95848337f3a5c47795bd84972cfadda18c9b1b9b31462e6b4b647dd051a11175d4d7373e7954f11f9cfd1e969ef853c1a35fc60b760a66b2fc06c6f3187c88afe7f06558b27e082c501dc64b90c960fde91962f7f9f03e9678a2beaefed8c52ca31faef930b446062c93245581fa91cbc34a6213b7a2441b3f54d41ddefec2e4865a09f13a7eb33a0706244b283ffb9cc2e5fdfa9ac4838284b345d36685402d2a39b4e88758772756d64ea605977adc7789f50297cfcc28e184ccb03ce2dda0250ae32934b954d98cc6ef4409405c4951e44cebbf3e7fb8ad64232f85a465a265a5853587e33ed9a0cfea2d3178a3c8ef64aa3779765d8561d3419cc1fd8552dff46107ee0aeeb2ea3f45daf9cc6715019ab5654a5f69317745619ec8e11433a78aa47a9b52c6594c2d8c96bcd69af9509e9d5df5bcb2a968684b227fc037f25bcbd273c40943eefa46c3c70dc4027823e007a83b78673387bfc325a1d0530b4ad46794844aa02d219b6beb2a717803a124bab86711435496526f78de023b46e261ff835559cdaef38ad9a0f1d10c25104ae5e1ad4ed3997c56f1faa81a8d82c3f44c5b92f23413371fdaf969a5fda789e0bd8fb82e25513ccd81a5c1cf47e2937a9fc00bbde63102aea8b059024535f28344fe121233787abd40be8cda78c5f74a2dc4966f075c5471229fd1f32658d8442baea915f122a9a52f0e6dfbdc270d722027c54737760165b470f1cbf56508fa58995b3616a33db67988c0394b9f6f2b6a1ac9a6c2b027b352aad7b207118ddacf8d4d6de9704e263705be246e777510cfba7d65c868bc4acb322e79160c68b94b48a9f45a55c68536ef8ae52d54ee38c32d43c9bc1b547f4759b3d128924e57f17ea04cca15d93f541a872d2fba2710423a7b344c940ef7b92a6c1615218ab4e60f0d57505b93c271a65b8321e48996f130bbcaa57d579afcf18a0520afba48c54cc25c646671203133f18b42a6b4458e57e4e70204548199cbc755cdb17b293db6cda503612be8d14de2bcf41a9289c4118e41f1e8b535b0fdac95993bad7c980b50f3388105bbd7362ee6b31007dee0d52863b534ffb4f39d3e9c76a6a0c04bfa0de38d49f98d37579767302c8c6b96d4e70d275ea0162b02ed87e045f114994f1a53b14cceb8a31e8fe2d7f80797c2e3cdffb9c953f37d8597c5f1f614f38938214317c0d25c842ea7280868f3d72a5b0f39693d59552e589828dc0dce153b83b3f7d3544ef6bacfcd5bc52be639227612106383090b133f0b113ea0d346a8afc1727f9f7ec720b587086cfc34867daed2004201ad2ac79ba701997fa31accf8de82acc0b082d1008682160362829d4980715dff1e844790e5c786b2cd34c41004eb872d7aea8e9f297966e8899c312096c78db03a57500dd9220b31c228c4a272f1c927b4e6645cdc8c54ceff2805fdf2e2d7f86796945bb4138de76a222644a8951d85a760cbd84224ebf6324e0d52cd08219affb5609abc5f949f5161b4b7b1e2c5fa1d2da6a21f95565aab569b94207264d91be6c5a1c3f3efca27ba1de1ca1a1cf9cf0861f586118b405e1d8065042d253c9b225c45d6a91d564cd899bae531ccabe2006a28bbd6e7b3cd1fdc59bc8f0e44af690aa0d89d0a10fcce58e109e07a6e19415ce30160f230221de83c629ef169e629181659ae32ad36e23116ac08244ac4d67022e647538a85670c13a72f5291247306c1580a539e23094210fb82b0548c5cec2fb091e12fed0e52478a98af21ff7392202b7d90fe4c681fabecee80d8c54ecd3c6a4946515ddf8dc96426f7c3b5ff64f3463c0fd12d5de1c16d2a657c15a34de6a50cc9de01d35142b68625a4683e32c32919e588562c2f6fdb12cc50f28c3fddc7cc654cf00b8d0bd78c986acc40e9688785226717dabd8074b64234a36487e01526fc98d51c1d1b7be8e108f10645d94059c4e5f4b439ce977a3b603225ae5265055ea0c7b3930cb76f66443154ede3664f6a1446467f117f2181c411a57ffe0d76436f80f208581c8552def0cdd8eaa80f8f1766013012796170bd76278286a42bec6436038190b508c7421a744da1f96aa2f14cc0cf82b0e6d1220d5fdd598815d3fa0342a2054d18e9dc3c5c8f05f6c9b3bdf513c8fd16c93cba568b42706fae577ecd280b4d42bc4c3eafff8cebebe5a037db82101457b4cbd02e1604e102389d32319562465141e78da094b8f248d7e3e704cec13030a12f44260dc810f83ae91c5b5f6fd204d2cfb374425d818459f2a5a8f9c0f1b7e03d97292c982e86d0159635202a870aa77d067ba60727845eed6247848400cb0c3a24603627bb4fd113282800b69cec081af5f4e16488177d67a8176976c4010c7bca7a29a98abe39336e616f6689ee2d14b730c9c21b8cce36bc165e5b6b12719864c2716c096a01469eb2f915ec1b1b35893a8dd99a0178692028454be189d767c3dd44327f6a2bfb070b4a1ef793ab56e669982d9ba0345ec970d9fd8fc57bfd2b2d3365ae93188e4ad4cdeb0ef333160c5cc7cb92cfa70496220dad02b81ed06b073579370131e00490077900b3afc1b5f120a0eb63df9c10835e1976e9ac26a656bdd281b1a1feea659a5f74ba656a53223b660d21839f029a3b974e4abc9da0a92fb4c4cc2d0617e9c81dd7466379d81d4941cea9229c055e326edad45c49b4379ee39318012fab011561b80870f598685faf699030733f9f011b850dcba8576998e6ced476b07a45154f51e97efcf0def0d86608c1edf0f232216fde0c6b350a96ccc3bfc3441d764f26e81465abd4a13c01dc813f5a849d2011db25ec41a2d9fd57545205f71a71fa9965d5dcfc100d1d545460a05e9cb072d399fb0da447df7a527bf8d517c4a0e065517dd030c6fcf03f6266f51b2a1b92058675f8c7ef414a6cde5cf506b8823b5bf894f1c05d619737314e432a721d891db72dd4c9e5db89f8fc2b139bb35ea98e8b061dfb10f62c8c2a4ee3866c503024b10b5723b2b86986b75e2a211eb2c5630c9dbb021a94734e6fbcbf793b13cf795a64ef964860c0648d0692c9cf545738e23a0a95891bc19a55fde72e1ef8aee5bf15ea4fb3e0f6c55197187a68de893b61834c172886ffb6f28b5ef9d2a47bae48b11380941d832a83ae99e5c150d66e3df391813f8274a5a0836e3f34804cccb8d5458813fa4705a28cf4c773620854b4fbe38829f3592f44b21349deb55b420c3439dec04c415c71870588d91ff803398d591cd7f0fe993fb475be89eba71e839517577821fe3cf4b6c28f4d651c70c40ab1f0389097c7e1ebfce867ec11b81bafd0fafb252c9801289c64c555cd568707242d94b76be602477f3b0b8134bc36ca5f2db334f20a510ab77ec55986afdd30d15f8436ab3f6944004fe18742abc68af448eada4d377d56690af08b724d8c59d7ccf88e43572642b4177bef0863b5d8898376677bef9741e181966f69c19858e4e9155c4b0ea1f698978e88b50444b3f55587d58a6b887d3b6339388a095d9188006d3cc1dbea161dade011a68d685d2362071ecf5dbc0ea63e8b117e3547465ca70e13d378497ec586560d2a116e3c0fdd0f53236d05cb32265c789c333c28e9ec9fd88dac1929d191886153f97f4336ecb6e8bafccb759d7b74efc1c8fdeaf8ac8936c8bff29285118ae2a9c5d4968832d8c2f14e8adc6df13da3b7306fb6baf1788a3d7a17456cb7020e88a88a7d6ef4b7cbf79dbccb3838788e36affebc473737a5fb11ad622889082a3eb6eee3c5ae3bcbe32ed03c6d066bc219964bb563ccc279826b738e895d62351bec80352d80f1d1a7df3ded8fcf361030d240d7135e9e87d10bf2f7af6747668808e5fa3ba17074f628d7139344dd0a88ad38b89360ec77e9882fa224753e8672c05cbc21a1e1b814a1e3e37b77f38fa2109982cdf87b1a5594f78356171d2ef598dd602bdb2454be006d6a69b45a1d752d68f65d3e872196067f3cd1df45f938c99a2cfe6a8f202be53d8d04575fb9522bd03122f4ece3db258913319f2ebd522ff5dea8df689835d8b87892efee5927d110d6c0bbf70d5a8a8cd359a8a6e79b49414ff67c90f0b9e5a73e5f65fe467b3f7c397b5b0c87e70b9384592f94babcec533c290d29c060bdae6e5e33d660417102c135e0f24918a92baee43b7cbf370bf6768c17aa6047e54ca3b9729da55c1892c507bed76ceed6f9c42aa11789da0bcd3876a70d4886f1c6c477a7c3dba7beb51198d01d72fcd65eceba6bc3918b981f87720649e6564270ab82bd5d4d7e45b8b09fddf121af63ec6034db418cdea9ef1bc54b17debefa6e079b19df744f378efea53ee44ec3f9da53cea10c76d1cd9fd6a7b4cf20db0e810956246d90ea8035b3b4946893119de69b2577e8bca1472e63373e3ea0901f7bb58120994e31897dd68d32805fbaadc76c01f2b282e5ff69a1830ba7e851b0e92e458cab4580c75db6148ac8a1b75cf175013d893b032ae7d2984fc29a0e4d19d4896087a3e9ae9dfd6c22d302a1b8257c8ee4ce12d87fbde4264990be11f7a56e7807f4ff7de69563f10d79028cb8d2f9e320abf5dbfdae638461ccd9f1d3d5f8234d49a944a70f128dc4833167e712d155895e2f62fcd316800a99044d8dd1fea55a7e9dc89afdd748f8ed93e03b17fd4f94ee413b0052d711aad679641ca1f32c5385cc3ed380579a8f47e45d307c0dae663c23b4d62f27f91a81b73491c54b8051c9cb9ed28a0327717e351ff50089657ec86fd99f0e9e963006064e0dcbfda146e209bbd6257adeb50e705cd8880e59540d2e7a50b92c49e261f229d0681db39465af8d49bbb0f89cc214dcf62c16027057df7b67948eb42d4120f8b71df0441efec06312ebba43c7c011ce104d50ec6eb9816e30ffd1978657bd39d2e80fe3fea755f53e14adafa8ee7f926d53e607aef4bcf167e32ec607becf843deab16b2db43ef49eb44aa8eb2b1986fed12e406636d2842ed3939d73af748b34cb4b423e38a3e4ded61ded1f39bd3ac583bc2081e4ef22e2dd41f7af860538920650ae5e431b8f1bc22991b23eed3075586b0c587c8403ef0f6b2116d4bcff4d68583ab14e747554bae19ce8052d2dc0ae6d434414403b8c9fe3055037b6901de030379952df4aa1c061482a97f60e290423e3c965efe757917a42e3a743521546ea2c0921b24c8e525d6a210e5726232ce88d2cc335561ff598479151b1f20ef4fb7e156e6fb7cda1c1bb6058680e46bb0c6bd362d716b38172a7b2cf0ed75faaacf78ac12d9f2161d6f55ec05404dcb91671a57b4042c741c733d704978d7aab3aa5abac4338932f710b913d74f166d9bc8abf6417f4b2f8976c4d5bcdd908b27a05887f7541053f57810d3c1f1c31756ca8eb940d114841f120406b59f5241aa8c45f03addcd2315bd50497170ce28b3b3b809e71980ca1025fd8129ad583cc1acfab9b4c384a7f54d7590794da65792e0a8dba355aba21d6287859868d3ca15d4fc59058757c1782895c0cd51f97ab10cee214c453ba80ea0d1108a5d8360dd39a064513d1b6080055c896966a6590df11ecf0bc773e81fe171924f1d5248b09bdb32155860cde9ab225a429d40c5c9cb702d054381c1ac315f8632f257d0fbbb188b56cc2b8ca695c74cea54eeb7184ea7c6298e1c33c59285f35d729d5059d73d054ce958e6ca36ccdc9a5aefef58618453fb66cb723b80ddf0d0c53925bc6b89b52d58c34318d18aec4df0dab83c67983ee201390b4dde8406c04217cc9ce19d2c9b0f7dd70c08a83e1cc2dd557c4e98e3a73d9b90251c7bc9433b4b450ac524b03995d71845198f0ec100cd283ffa71400c1a46e725628d8e0f4b92029002fda5a4373cd08482aa8ffc01d8e26c7de4f4d2033957853364a115ff0aa8de3bd658ce6186ab86f2de18334267b6a6d6bd45c11d69e155c3b8e849db12a0ce9da90998b405849b025518141532a1daa80b488b9180eab99fa56a86893c89ef49c7e94ff9dd87aeec963fb3f7804013419df1600c187d03bf2e14c8391bd8265f2c1feab0c5900c05715a50b1f7c7200b3dccee7f598f2a89c957ff7924da97c77bb77d25b1ae0d36882abc093435f02fbbe3a3381364188bb712d46fe20a680f6b5a02f28ba73d741481b9294b07156315c3c066b6436624ea20fa38b25024015cee1f342b59be8b7199014cc5b14368509fb4725ea8273bc57214a6529b01d44655943e5d153bafb0a63d60c4074b97de0abb1d178e07f7172bfc0e36e29148903f24fb4cf14388a70fa3a54a30b4ee2dedc1f4f340249dfc73fff7a7da05fc1838d6a099299f5b8bd5cc749123af8b1792ea0505ea5631a3f2f4df52443d2259aacaa2e030fc0cbdaa07592ac1c1b0384ca96d5adc29dd248892fabb076f86802a41994187d94832f4d9beb58eb82c71caf099aca027e0175a8e830ad56b9a60e327da4c7d014865fcc7a7ca0983b8f80da15dcf20900cfc4fa16358d4efee349115f84483ff5c6e5a8e69d06f4ca4121052cc9066f6d6cc6a3df86147a929c649ff3375c767400efdd874a8800816fe3ff43145772f237e8e421f6e6575f48dd20d4d62ffd4117278d66c482d25057e102cc8b43e93d318e9ccba118bce1235579dc552fea9af31916599634cae92a202de9bf4aa08074edeb33011e1f247c0407c0004e00972db36922cb0fa0ecd15cd85875d1b2c859fc4aff1b3122c6078cd843d3f45b9342b5ff6a00e0d03b4da5732254910e03930da1b675b8769b7d18283265e0e2f505f83220e03897b246eab2860506831f229651e897deb8515e49f4dd9d2fb2dc9a5ff2ab5678077b9588983638ddd01704cdb8eb2ae136419a858912e90f1b086e4713be03369ecff9f2fcff3b907e2196d8488cd8104487d47c09b48912cb18b7eb2b7a0d72ee41be5b1ac11447d5d79a1780eae925b88361218a60e1f2853fe9bd3075e750d585687cbf029016f92ecba90270ea5af232fed579f996796b3a0d4d8e825702e9f54f1ce50813747dba881322b8905ecbff25b0db3607c5a051b735006fd9170a5ae43d287ac99f59c8474c585ab0265c702cc0f79a4550030c954595bb0c27984794e76aadb6818aba6875e61467fc1f5a530f3df63c7275c33ae4e9bda8bc92a50b15df96222e676b66ff3336776bef647807e1b209ce539184ce989c8eb79ee54b814ad1427ecf8b108541899ec9cfb9704fa90b06a817e027b982442b356ef263ec372ffa43c7329f280b7071025ebdf4ca65f6ac23f2b4b4a47cd7be02a3389e45541844f694d5ab287597419869149c1e9e569c68d90e8d675ec53ddca1310676de2246cac506c7555433dd89acdc619a8447d9cd57f1da42809cbba0edae0c8f109b04ed471a3403a7a5e5644fd657dfd2ad94c83cbcb176fa147620078b68b694c0049cf8a1cca86f4ce3d5fabfaf9e626269acee15e362a0cc5fd1a167e0b0ad7f60e3f69ebf8100df8e32744901287d85af8c96c867525b56c45a2135b284f800a2d2169962835b02709730cd1571a6380a21bf48f16c22bb1a2a1e6e096c2b9d435c6195aed435cf9b14cc4eb10d2ead88ada0dd3ae3e5febc532ba1e90a61f559d79cd6c23183a3a75021eb1aa915f6e6d9533e7fc8dd7d356e10ecafcd74e95bd1ad85246af518824a19aa6002981bbc2bba318842a5b3b030e319e848e221a274c1ef08c43e7098306ab1c4f0c92af859fba542eb411c2aec1ed69156c103d1d735d002168e8514ccd02283a8a03077d0971d70113f92a5867c04c967057cbeaf459e3a40cfd76717e9e893e706aea58ace024ed3fd9fa172a98c92b18c9c5598a8acb1ad8190caaf71aeb0fdd8da9ba7b9fdd7fb61aa1a20f65f4f9f58f0c73f64648c35495710a38e109bc5988c08c570dd1d0f60e38393c0c301a3f3ef188c2ebcd11f1e3427d34d004537d6d6ed71e8b2c7b9852f835642a94427990a4da3cc3ad27b6c719c9ead4c70fc08bb6f453633a1e9a5bcbd2e2ec3c90096ec479e0a01e3c2d892d425268f2722fe1fd0cb4d47457dfd67b64bfa0b3e8df2430e261c6cb5c2281dd54e438dbfa3c3101977c0a1a2ab060520d73f8e93413d4439f7e7fb96405ce1dc65ee2863dada7ac3c9efd62e960ef420e594ca932b0916471d6cfba9b35c40f4385d11cab1770aa0b0882a0a9c0608c443400b3b0f202652904348c873b25f98fad305d205ba728a628645b092fdde8b49b87bbda63cd0df96092f502ce717fd55a560c3c99fdf69f39fe3c2d14fd96e6f67884701a641b30546479a53c0ff0e54889b52a7aa2ae79a4a3ada0956eedd178ada9c10a3304061ed0c86e42914c0737a0820bf8f49ee1a3f2883c23d1148a0cf39335c26864990ecde6ee03d0021ad4aa912ae15c288e994095a186620d1c180ed9eb1370a5a3a21904beb0a5707ad98944b836a2c7b1904dd76580836ca412558b1ff86642a737100336ba803f6fe6ce50746a1716faadac5f54cf1b5a661a0e28bb3679c0a00a250c0f2027d8061f1838f51aa3f742f46096bdd38d3412c13c809ca3bb33b066f80d7dfab7cdfc424dac616b0e19ce40571b4e23d05c8633c5c9c2fa34315d780392e7f4a0b381af88b58a87e5229d2aeeafaa9da7d725b8ab0f9f7e4316d2f65f698fa516a0ce0973904b193a68724be3261b34f22cd1cc0da8e8ccf617c5a403b6e2924184d0b244897154c182215af02059023e3d69c40c3290021e858b91527a4711550003855dc6249a43ffa9d591eab47fa5b8ab6cd1fee9aa9ed7909266911a70a91edec5684c7bdaae2310d6472544bb196035763e753b7fef4a107b16d669edeb1624b3bbcff51e3a4dbdd8fca4950b7b44328d1f9f60b219704253b94889645bec06d98b1df5e73d5b2898bed9ca495e6f50e51d786e980654373a5d4a6a161ec8ef3e83f409f25f672a06da20ea83c48b6228d91f0be8935e99952a6e83237a60fb29bef246e1728e74a54ac4ab7a40ce8f2a3656ea6b5b7a7ce1f24374f9bf29737b259434df54ab596d4ae0cb109717fccb1229110df8e5af1a82ce24369b9549eb23465f4838283c0d4d0e4851c7454847292439461aa649c9388859871cae69c846665aa0de5c2bcd1bf198860409e7148209e09c19181c1c03d6c19174e9124cab8897c4ed1da5c11eec43255e8c520bc2314283294b55aa28afd7a5028aa7e04814eae0aa3847ca594ed04c39088c98b2b26076cd54a26e5cac5e29f0fca5b7d0d74484130ed6b035fb6553e081d52b0dcbe3bba881686d860e1194993368f7b22ad82ba6219bd250e28a59485fd0e44cb9a65a43e0b8bc04e9e0610031d49ffe658760d2673c85d532c9f67fecc282c87d7d3109039913f0641b01a653f4517ab76d566ba809a6a72399b476a6c89c8ed006c264ac47a00d84f47deb2820e2bc7dc9597c02227534a3a5ec56a30c8886dc4d99830671f7ee698e4075422b31c9d5f043de19b7f9ce5b3218e21d18a8782e2acdb96e7319b56b7c35a95d3199554827bb3f824a9984d2c4ffb80ce4bbcb44c31f4d204a1ea8142e7a67a4d399ae1404148354d53437847a01d5741fb5a11b77a1fdbf07bd0881d6c0fe37e615540e89e2e8972205c9e7d85387faf5b218270f8e91b827fb782553107e02ec40e32a8f9db75f2be0566ffc60219f526b9c8d83e4adccd95e9438169dd85339459cca85e71270928e6ba033ebae6ace3c785b9d6f035bbe635c7fcb8dfd97df5a9c46d1a013607985fe6222132202456862c2c987adbf4bcde89d187ceedd9bf05187398425fd2a638d79e9cbaa603d60935987be9ac13be05e0d87d41dcb957f6840cb874c5f4d369a2200de9f5f5d22d61401a9d2ede69e37ee0dbdec63f8ee6f25bf4f3986773525f972c77fe4773dbd71006108bec2a2a4117985bb6b7a50c285376076f27b921f1089d14f73b4db4ba3dbbfafe47e89418b6a5f3b16c5a5a47a40e73548a35ee306539e16d3e953b77dc3042e6e92cc0ff3513f7dda56b3a96322e056875238d413ba4ae586274d06f8f754ebd74e30e7b1c9a4c21ee4ff19572516c167faca13c84443782db832c8f563ab68fd442fb0e8e63f34fb895303a5d1ceab61b2d84111a46458f0307c92012a0600314dca6a1d4a986da20c0865f0a95812c1bfeed9b7aaaac6a5c6fbbffd186c52cd24c76bc3d0aa7b599f08a95b36e79d11497cb550eebeeed545e373214a86242c306cf5ca6e4f5262197724e8ea54be98bca4d57f12c16dce5d69a83150baca1f50d2c81f201792c7092bb936e2e50f3afffdc4c13dd2609c40562c40d8e4b29234b587d2afb4e26d11423eca998eba93c4ea9c8712a6d9f12a4bfcd3775f4e70b901e8cbec2c465c2ceb8da67e8a969fecb0d5aa1e6e76b961eb6c54f971230b99c8a5660fd6b4b7453174fc4a94e5d6c2b7fa84306c2d85aec5ba537a3dd507f19936d8dbd564f09944e4aa5af2def3296c3aec609faacf2cfac06bca5187eb207d61cb0355c3abb0107e87e6c92aa9b0f028e01c9e092819ab8170990b69ce80d219c1b49bbea19ad9256298c7217c39c27d1c79cdc96b003965eb5e2e5e6163f8401d329c2ca1a16a4010f3f84205c3f61e5f36a49ef19aeb0cfd604ed2fd0b1d2905a29ff2b886b040196964b8781a8508063218844375a43bb7512c8a94929fd5963eb564fda08bd6def50e757b7796666bc1bd1bf0915b43047ab190dd83e3251d294299d0989db3e5bfc29fab0d85b4911525d378e9041d3c0aee493c45402568e8ab05eb39e230e30c7b112d0cb09d6deb63fcc3a953d786f68fc65bbd5eb7a20aa0daf97256f81d83c8c623a86784bc16999cdda368641eff08bb78ca04daab840643ae0e9a59875ce35cb17719a89d37fba9fb571799c7432b6c5211936c7d4db51b0a9423f69c2a071c7cf782ee85a9866b1355f294617e04a1604c1abe5999c92b4b5c1e67cca99bbbfd987a0e0f78ca62d4c81f0c92c5eb8efb14e1524af305c0c52f58384bd8781223dc133821480630466b45bccacc46ad96501ba436a6c8e53fc0c1087139e50e10dde945038f728007892bd44e51129c197ad14f36b228e3620d1be9fcdf40cdb7795e9a9431210bac87b21ef8bd815eced56904a6ba635a5910b934800f0b0ed79ace448d37004d69448c660772759491e85e2664f11b3a6a003275342880794956c6f21782bd18a2d05e33b50e768f2e21062a8be286f4e9480d1fa979cd8564e2a8d75eab7ef2e3d42165721a15af83c2108e80e0cade4ecf073d187148c1052c4ea324dc3953eb98d1597d5ed076862f0246831b5472689be012309100d2fd6f4ee6633bc98753b2ac9cdaf41e1ab7ff950ec26d1c3c2715ccde750c338a68a1fe4eebc30a98e80e19e38d2d26d282a02b699aa3b186c15f632f7a13bc120c0f4578cff74b124830073f9b92dd63a53c1942f663e971d532d0f50e16f12c3d93c880d72c2cb4e01298be0f88cc51f0625ef2ffd35d7c46f9a71333f54c994a835a11042954279b7b7fee4185a9c82877e180df28ffe17370cd9d51a73a1b8f59732af252e36da1658d15a725e82a484e160cf33df072cd99fbf4a592fdd11b8cfbd4b9790384fb55a380e2e7cab92c1dcf8ad09ebbd6556e60fa6baf4ac748c1337eb6b5dec9cebc463886964fab6ed9c3cf7a2fe022cb635b8b6641267b0951344ac0ad0eeefabf004cb6a0d730755873a1f85c258b29164177a0671d10e9dbf2cc0d105e89cacae308173cde57cc1eeb8b3f8b065b12fb3d8e2fcdfcaa610efb4b797bfeb3934de1f8c889aae2d31a304a979e80fde5a3839e86e2680406bdf6e3ab86f3ad1f0cc69f867df00b82bf76fd1471b5356b803323b0a88921128891d594add2189753712fbb58fc55c3188063d33432d0a06e8f3bdfeafcc5e5417d04838bd9e991aa4de152cd78c46dd746831b7d97eaafdb7fabfaaffbbfabfd5ff53eddfd5fea9f6df6affadfeefeaff166a65bf0f88605cae7fc5cbd71e5f6b39cef8ad9a1fd5fb51cd4fd5fd54ddafeafdaaeea7ea7e54eb47757e54f7c3a726ffe272986ea1f97bdecd229b7af2ac7b6ec593e5c3375cc63d93f26ea34d0b7bc6941ef8201d74811de4238fe389ecda7d24517df52d75b5396f20680cac587cca0944db0ea81938f6223dbfcff4815862790456ef02796b15d6d15997d280967ce325115d41f6ac6b7cccdd011f623e8c21ce0e2347677ed2520c08fce6585db88df3dc64b224daff1600af74f169efea16c948a611f9a20c9a36c202b74c7039bd12eb63a8fb84c0c4ab90cc40663f22066cc890d761802ac3ad8799b4867d71b1f7d9db7adfd4c39d4ccc86bb86a07e0ff76f18cef3e2625d2a4b264e1d5e28615c282104f470af3054a19a127d98c8ee865fe2332505dcd60883795c078194f59645bb9513ac7083d5c1b83ad6d9b254f6ba635708d8aec7188ea50077c41394841ffa59ff3daa97b1480545bfb123af53828473b0240283b3ee6e692c201420465646c40d347d2b1c9f6511cf9ea4c35a2f950d39d081695499739480885506fa60cc69f52bc08eb0adcd11e1f1e2e53091dd0cbf112b5491aa970353442f186f5464434e31493bd958fd9cd12e95f15ebfb94658b8885b230ff8ddac59050e1ac24b9891000e3550b9c85f80865107b48ce364af4d2e1bb981dba8f03e296a3eb6548c45c0ee2862e01c206dadb9865baa2e8b9a1265df55aa3187a7cbe90bc93a06c58ee7d0785be665405ef4f94192fe227adce98537e4d1ba9b6d7988d2ac6973f0684e0c1b2e0a3f37602bcc418ea0ae4f94e44ef4a2a30604a506045bca462142b3bff110a20e0d256252929a08262a3f6b0cdf282d8216065fc0630ae7ba435095ba29d6741da094f8735ad28f8f8999f2c8fb1f9b16f2bcdfb8d00110b1fa9c44b5a330ca1d29a193566779d9ad1cd06f601febe41813ed475fc4fdff182f60c2f0b329ca3ecce9953ff12a86f0110f80f8553f24d826b79205d2fc54fa75173c3ad191d118f8f2bd42c64fc06d44bdd7fb38708aab54b7629df427780b4a129dfecb84f144c4891c7ad0d4e379ab64c40bff83b169d6c25934cbc5b2aeb570678d7c340d7122cb70c6132219fb86e6828b57f99072e10f514ac9f95ed7d9477f6bbc953c025344769d83fcef2f634861845b4be3d923cafb4e00aaae383f18c377a2a5ef8c1b0ac17504f437641709301c52b215be3063aee9db2082bd781cafea693f7e0518e00eba6b51bbec447d609054754e641d33a68d86590ad0ce2b4cdeefde31d2724050fff19720dc4db5b26f2c0750afc32b326b25b5a00d70af9325df18ff878b646082c9512c7c04953ab2c20cf6b2a25c8ce3282bb2617468728c2ac3e28a0dfe1f813710feecb4afaa0060765ceca69ddf157cd2949f2a26e8fc825dae7f192f16b7b22ef203b24b53428db85844d8dc1c9ef768ed7cd9aaad01edf4c526edd11bb14f068116313575e14df0634d979cf44427a3a34273e3e0218cbc0a8f1eabe0f5d24a7528b108e4eb36f3dbc530b84b93d9c1674d1d53245d1da07c2a9a859cfe364c80be476aa3091c4c2def68ddac91f8b1ec7772d58b4d7cffea0efb418e820831b4047e1c64953b1f7adfa7f262eee08db3573e2e61d156288c100e5c3ff2b835bd3088024fd4881813d3d015aac2aa397953b4d863ab1ce8749491c23759845c4af4e654ae439e73c35a10f95a6f91bb2978e8d09f90a0d48186c75a7ed1f68863f9f51f8369e4fe44e112cce88183278418ec6f4053877a8202e8452a35e85a3903c0790c0709a146e6b4d94b0ca8788d488a698dc9473967a750ea89d8b7fd79596113a5aa02fb989c63fc0718c62239936e04cefd3e7810f97d53bdd6ca9cebe5bf826079b40716b5f4826818fd085b68f11f55bbbcbb6fce422bcf7dfbb3377e6fb8501f10945a767a7c53569da646b00caeef36815b2f4d242ff5254018361e0e285ff74fe13297286d279e83c4971c84f24b5bb7f55b1804b08fd2de57ee7f4c5b0fcdf1cce9957a60f3db3a3e9b688ab58e57e4f07f4b6e4f35d5b34c4987868243286e026580404cb19e58dec832823e93b7c02f26b17efe3853c9b7c0116dba694b471044120d80f400eec476bc4ba726ae4c7a08d260713e838778acd0c1bf46738d85b174632d5e3c7de65f0fc68a0db69e59da220d51060593f38c5162ead8b46fa00d1dcf5240b3034954c91e6d705f434fa5a4897d439e0075c0f660502e8ba29597dfa041f247bf053a14ed9cbaea27ec1c10b48e62db26900536ed5c4bb6126bfb1096a999dd91de7cbb4062c53bb4bb371834a5f1038a9d052e820f7d040981510adcc7a4795da328cd5e5efde154e24a1b0612f83cb58e781d76df2b6bd1912e17b0b988e9a6939c17d8ba5a0213694f9a488fd83a16df3b891061788df663b83cd050943560c03ca42f847ec478e8217acda9f707af69dbddc41b741c81e24fda221b999780566a75f23540be98023d81a29a3bc1cbc7a3e2544347140a5398bd3dceef698497d34bb7da89404713e0cd51d3de7f0e4021dd4617a99ec4572b74f107365ffde642fe8aa3c371ccb764ad293eeff1e85a423bfeedb96006b29a7879608204df417883c167538aa4546ad00bc534835702ed7a2e13d1ad070c2db04e218d658edf872fb63f01f893ace95c2a351ff60ed08c21680b0bc82ed016a6a919382a6719df3e94c6e225e9d9d702ea4555e4e94ab534de7cfc01e2fb9f09b95f22df52413a69d07548ff09b40285eec630a7254ccfcf9fc9fa5ff4e83cebe12771a880b133cb94e8d0313cac6b1dde2cddde8466c512557ec4f5bdb9b47b9139290e74caed24281c09dca069feedf8975474df38d4b53950c1baa022d1670390aea331904342410b5b2d8ebc76f73cf31643359b908c4434a4e1fc1aa1f2a2491299e2d54a40da0b943d4c084b2fc3062f2ea66f89e0559ec74a62dc10a13c301d5f602c889f06323d16a886726e31e2cd942fc624e83c4ac604199646e4177237b80a1aa5257f4fef03344cb86b011395000adcb8d8dfaa869202571daac77e5f058ec876ada51632f4da6c3a20cd03e104ebc963d9f92f163b74d240654b9b2f4d41dcaa2707bdb4e72459d7b164187340f77b7b769a19e5fdcb865104630a5e8eb4bff7b59198c970e14470196e11daa7686fd2cc4665f6548e9d658353865ba8784aa9d6056bd78f2341bcbe506bcba9210cb646fd64648ca856542af1b63cb2b9f27a1440b26a85d0df0c10d015a3f632877c45f12d97e97b45d38d8c985bf75ae1b0c5920b86c759b5ce6a4e7ff47fac105c56187bb170243174b44eda4e34299485eee29fac74a028cdb74e3eac92611e588a11a2b7ccf2cab7b36402944d765e8e2455a83daa1c39ce7209b778bbf1949ff3649c8b0850c8966ac31fc25a6ff22f97ef7253b1fec33934819b620ff3ddad6591bf132554e3aa4856bfaf95d718296b1d6901d27c103825ee9a209507a53889587a196eb476c218e6db9b437a1ff98471a60bb13e4a282e84c6790f228244e82474c398a354866061da34763fd695c0ecc9a2bf01b8966d0b950cc80fb1a2471b6235f342d48e0bdbbcf2a3793e67a8b50cc01136f965bfa4b9df07602f1c692846820436451abeee11698f8f74c99138c6d4c34c33506b22c5ba54eb580d237d67888bdbc1450a0d08b730a02801eedbc5c05b14342309f2f6184f0d4e29761eb4c0e4a5ef3c313b081ce61b204fd490fd4b3bac34f6c8e4f604079ed178bc4fff13741638b0ccbc828f219be515916130b28cef0292307e3c5b837420c0786865a3cbfbd383317b191b9af117e03ee1de3cd08201eecb5e6e388c92db9eed6e7c96ab90aa666508e550df5e74201f52272689d8eedbe9eb96066bf2efe0a680bb71b88c42148f7fbf28f2bf54280b784d7afadfd60e3d81e528d908cfe9a4cfd10a11be4f5091ea5d0df86984024db481af4655277d52f2a066b5ac22df221956363fe4f56d17c307c8547e5134d02561313880d702d31628b2da1d58569b2a6c371d9111a41a50b638d064aa8eaa2bd91e8664ea323bbe4756e8938c82418609d1cbb5ed1d0b1561c95f6f7cb00b42144082ba40c04b28c80ff0a86afcb5b513c9082e8a9918e6af5f7bcb011a4ce11ce5192b3f7a0cf7e7b165590d5f674d4baa9934ca25f46f2c3e6b4f003ff787b36b4033b09289a6a3956b280a3d1d092f9ea8d79aa5cc86d4a5c0b8f3fd24ccdf88f8befbac3086c48a4a7919a5b2428625946a48eb46bd0f4573823431c0950f239b7ca79b9cee1d4e10f1ac0ed8b7594848c87aa13bde02f17b3a0cfd98f0e83bf1e222a4edcff445459a8e740cfd40dc0cd38f4ee0e92d9f26098f8875ea32d9af4af813e384ba54275beb249882bd783aadc37285d84a464346c40454e13daa137284eb3291bbb6045d8c02662380be8cee0b9be2ecb355a7c7ea242cd0c1cb51ef26a74952bde5fa6abb6ee6fd533c1ced18ec7b91d1c834bb67ec8731171212ae4b092144c21525558295ad42562a86c49463f3f40d4b82ce42e558a35bbdc66e7c09e9ab6bedbfba762969f98f37718c2b15fe445416cc6c64b7b0b05b3f34a1ed9e118c04e5f20628053ddd86239f12c9415ba393183f1ca1ddfd5293168eea9eca5ac57fd7a9779ed2dac577f714774f71e714af2dfe3b4fb9f314d63ebedb2dc553a25064f56dd0234ba955912bd62ddd1722f17898293b9583ce56b5e578e7a9ad6d7c779f62f7a9d76afcbb9ebaf329bb4f5d4de3b7fb144557d9a7ee5bab782d0c72077c22033a9c728e58304c69351d8a715b45b95b8200629094c9c04c09930eceb66efd88c03cee9b9224109041aa6029178997c4fb0eb72a308c8a270d8463d145d9d994c4f737a026fdca73935014f6825ad99c08092d35b2bbedbda594524a199808480847093683336a5c5e9e71002edd1a4b7786cde0f2f2f581fb2e9190c9b087c0d9aa0cc3b02c6f18868f533ce8f94479b7863d4076b404fc5a83d16e497597b4d6977767dca55be3f2788ca1cc675e7d144fdf7e5d7440882bde239a8fb41374a20b22a3f64cc3c7a2cf4edc28b38048e977d641bdb90b22670c6e0f5d363beb8c03995ff10c4ff04c4922f3b1db6b1d9f28dca4f1663cbccf3fd6f2d0cd995949990efdb520b43d61dea50ba286e5fd49762953ffeb180da54a773f63164511bc9929cd9411a7c6173bc619d77b669ef67b7166bab98b1d7bc8c17691b557b7de016baf266a1e723d4236525acf26e8d73ae3dfde7b579d6bcd5c705cac9d316b8176df9c75c618672d10d973c6f88222101dd939244770239a83ec592be3901682cf454cc89f333e87460c822008e631c9a875b56e786370f9c1f8fa2df56cef531370bebc3be32edd1abccb5b814899e2724f03a41038d74c24748c765073350b3dd62aada7b84f91d88c51f6e2ac772d2729adefb3d63a44b1b5d67141ca8a66216545415a6a8267a1b4ae55c05d5988138f431066a7f2ba0c4dc39dbb7ba3d87af87ca6666a9a375bd39a96cc68e8b87b147a0dc37cc9f0cc6f67d9f48000638e8c798c3d3f4685b027fdedc0a634013eaa39cdd370e619c5a8580dc54eeb7aec182cbbde288aed6ec6414622865113dcdc4562a3c42b72e56e2ee72cbad63b1cc9b1041bdedcae1b95797d99a84134bd0f3a51d60ed612ec57d737675bf5d0cd7926ad6662547d485bb8691cc73a43534f110213ccf79bcb5ad75aab09c814973fbc879af5d82f0e6f1f9db194d7c7b08add2af6b966657d6a971e4a197a5a9661228afc23a8cffae1899ae5010a365e60898d7efd165531bb25de5b26cb3ce79cb3d1dbf3034829d267cf688af4d9c573f47a3d0c4b13c97abe2a49bf970c71a5f6f0d9695e2f88ec33afd96b32a79d69cee8358a22a7226462899e20f220c5551e3754f9c5b33efe00d26bd4444643946633d74802fd26b6833352f43a8e68883b9e29d28f9ee2c71a82d1cfd4e8c39b13453435413fe99a2481fea3acb33eff314453f7a1eba5a7e68319aa4c3d78b820425ae8f704d2d73c94f9f5da59dedce821ad3ccd5b348ea3db1907231a7a089c869f7ab85194f465abfcc324c09823644c326bedbdf7ce5a1a0b93be61cd205e2af25bfced1b4d357a02e9711925f14597785c169a6439c9bab1420b55b65e9c097a61a7284f52102472e0846173a4079a267ba68f989b3b3c86f48991022d66eac06067891e372f92d85e5811026b3afdd840f3b55bc3c542bf903c508af078823547a75f2cbdb6bd7ed1f43ac36b3c4dd782404272d4f98a81050b2ab8ac11d3dbd3a68a8b362ef26cb8d32480af3f17543ec3d79f0baaedcb36ae7bb84710a84517bb59b34a0dfea61fd2705bd71dc29b2bb5a7e16bb4cc31ca34fc5bbf4e5c10d66fb9757e4be29e0b1ed84553cdc5ae059b1670ec34c1009219f1510b32638b2a7ffbfa6b51a55d7869bd5a0fad9bd662d1400f2a36555675a46a7c914c72a25864c9e1034b07b6ca708310226ecea62195bcf05108114a7538282199ae9982903c763284088c37daa2c563dc82f718638c75d307a390e828cdfd3d838486c843d50a2caa7ad8b73fd652af5e433474d1331a861fdefad644f7bae8a2a7d984e8227ac55244c9bb358a71d6dd0f7173182d3e5e4f61c04c3105aa135ee55141260cef499a2b499c3819411344053a34a87cf97a13c6c585ca4f89cb8f451a0b7a5ab576de6749e27f9252c74b871ba89d2e350b60234e8c1953baac21a3c3737213e825823e34a6c461216c4f972ecd25dda0739563852d2a44b1a34b8d8e20098518d91565f28b32d86af0244bc781cafcf5c702eaf111c9c287128cbc75ee62d164f7d71f8b1c3e2c476fb1bac165050d3b71e4d42e49a06e608db951064ded32c306d60b31757a2a7469f9358e52172a2f5e52a47438e9aa571272098675e3a42429ecb1f1db01838e3ebad4fc1534441632423804e05589520306296a60d06d748325ec0b447be148bf11027badb1901e392ac6785079d2050259bb39757c7405b2f3a58b056f4cc7d58f189e2727402bc6d4f1408b589d18746030e15125e889e9662909c718ae1c2c6c6479c18b2e355f6331d286c9481e384e2a74da95bcd62ba45edf5eb779ad7536cd0f46e1eceb6f050f330c346e117d4af427888480fa62ad8d84c0d8c9454e44f6b4fa36f8a9605282116ef8fa53a1e235d4d93a13082c900afff003489f227d2dd3aa55c19d415c7c94012f75ac986640b971a54bcdb7ca4b706cf051bdd2c2c9ce0b37a27a72748e74731f6e0e63af571e630f1d639c9770b5184dcdb7f54afded90fc75f3baf9f5b7cbf1415f7fbbdd52b661cd7bef0da3dc2128c78c551b355966baa2e852f3b797f8a8084b181c177edc4861896e7b0dbb7eefad5172a9ac9f1d6aac4469ed3ada5ecb9b13ca7a2ca45107fca2d27cf6a17ba481e71a88d0f1376f21f934f74a34abeafbb3f5e9873f535fc58c60e2326247ef1cb1c3c9cdd5c771fd5ef317dfc7dec14dfad3ef509ca1bf32b0dd550cb0df2dbc531af1d7d7bdf756bf515f1ddcba7aae8e53a3afaf5ead55dbbf16c85889d2bdfbeba4c5596fac37188a5914f1b5e20e45d9282367b41aadb4dd82701987c3d7e2c0c4c2c6c4c0b492de8105ed96fe7a06a5eb1a9636e8f8eb268f43e6c0015fcb6177e564ca552fcbd7c35ef80dfec25eb80b876130dcc563d2e1c4c49b8ab259ebafdf9c753397bf8eb3164eb398bfaeb76eee66df9f32cd37a405601cc751b6bb5197eaf6eed5f55d2d309fd6165717eeedeed66eb7dbed2e5797179898d8ebf57abdde6eb7dbed7615d86c369b00706062e5ce5cca5d269fd6165797cfe7f3f97c3c1e8fc7e3e1aede8deaf57abddea5babd7be5bb5a21f6f97c3e1f8fc7e3f17abd5eafd7f33fa500827c56cb6e59ae2e2f9fcfe7f3f1783c1e8f87bbc0bac4eeee2e5dde65027d3e9fcfe7e3f1783c1e0f77e12edc85bb7017ee3a85860000800004a0820afc3cf1b5e7ed79215394a51ac1e7f3f97c1f589f3832932945af1210f56c84d66edd04f6101cc11907200492ec6c8409ec26b063d7a789ecf91e59dd753b33da758888b2989c89909072983044ca637a542df5a05242227143167cfdf568c1c336820715691bb1a3ebe6ea07fd78c97c5d28e72bee76bb9d0a47127bbb9345f118f229118fdda74602f8f4c3dbb4fa32be23bef77a07f7ef45739b32e8eb6fc7d2dbae115713e908fb76bb9d0a1d044a6ce082a8bf1c607fbff28e0949fce9b8fab46ee958fa4fab57050371d31aa8c2f0572c676d885b517b0edd5c78371a66db081e3baf57cd58cd1f8eac1f0e276f9d5c809429577e38a63c30da1454c8faf1e4e0786a91b39752f6f58723c7e32313c7920c1c2a5e88c110617aae82afbf1b666e60f9d408ec51e320217d20234ac8991c345ec0aa65d03431b1b0860f972632a10bf905a42f1390d187c94ad89e1a5546255332a248e9d8d1224b8b1ea42fe0d5839ef317f2ebef860a61968868098c4b4406d18d2817445984e9b91b393e35d2807e3caa9f8dab2c07ce8a362c887286c31bfbfcf5c713e342b485b455266f2161309b57e5f7992f456c50bd01befe6a8899370449787daf759d9d14b5880ac9dc84e8a1cb440f5d0c51125efb46ab49c2c4b28ba366786e81c6588daa0fdde947632bcce0e24c83737093bc396ebc9d416e3b4beb37dfdf9cecfd68447de99b71f5d8495c909724a8e0695efa9e4900e7b79bcd36a20f487adaede6370f3ad39a57d7e0e3edbc69f010f786735b90e3fc76a624fccdf72c02362fc3302dff860a3d20e96b5e8e5e3a4e04493fa223a8e06ba810cd4d8c5e73dae8b55acd2b09b91a8af35b5abb6970dc99e23c08b540aac4a112d0e037d484061f5113a4dfca33c5a127ccd780dad0f484f9a47923671b5cfb0d4d399866596e7071838b1bc4d26d3eda3688e2cd6d38f1e6b60da228967ec389362fd10c193cc415cf0c369b68b3d95c26dac4164a210ffee66518966ebdfcd5f0fad5985286b771bc590f7f34781fd64edc9992f038dc975e2f880d6edabc7421134dcb2f5d836f38ebcd6940d3f26d1e74dabcde2321d7a0014dc3b7a16906c779900b9df51695684a7aad3aeedc9ee14c4f987f73f276bb7988a6a3dffca4d9d903926eb75b909727ce6d676abecd71cd4d948ef35ae9382f1d87d69bb379cd83d0fbb69b88fa42373761f3d26fa805444aa111362fdd86cebc44676efe888665fd408289cd5b0b62abe77c7367b57dc6d47ea552a6d9a2c05e51a111b59ea8599fddeacfd548f622984bc9372fce7ae3c9556a2a9429f9b43bc3641eb56c8bb80b02570ee1b0935fe6bfb582d840c6a8d008ecd9339a6e29ad27bd4ccd08a44c49b18babfa02695e778981917073e067bf2400dfc4750096b16ebdb5d65aa32771739a6b6f6badc5da43dcad4f5c6ce2a221095a4c640b04412424229b063777cf0fb7e87ad0793b6544b69ba5c64a94da2ce5ddbbd7752b8512e4d9b10f51e43f51b7c08d926811d143dcea225a6f97cd2326c4d50241d0011fa85e6f02f4d0c1ea459b91a96708b28db5f22c92c16d41670736a56c836b709c2dc8fd76e6a19b0373a06f3873b708740d67bd45a3256f677d59ce081fe1dc96c1afdbcef20c7173a0d7ce0d6e0e74da0942899b039d3c4518a15e007a8633bc4560c5472240b732a4dbd56990cafca1edecc0a61c5df4f2246fae66d392065a0ba6669a337a8ba2c8ef6f42be783088a6fee09992df810dedcce0cc4127c732357f4451e4340c4394f4cb436bad97a8a8b5e2adf5070f1040420a6b0c29ad6f5d2d1fd2cd55af5939a20f5f7b5b56b6b28c42234cc89f866e518b8e42236c7e8b26fd880a8d207d1c9d4451e43f81f4a427fd18cec2192a92e31e47df635a7de199e2c2d0c11015613f6f7073756fd71bbd587fbe59487fc57ffd88f89a9d94d64f207dadb586a817e0b4d6a508444c4e10bdce91619e525ad7a9070fe6191fd84d8c735f93a66429350b478ac854a5f50f8f7378bf20503148512f483f549f877b24c22db248625f60612454e1ee91070ba2039b32ffca9448565ff4a10b423b7697c93c7f9d8932f404f9a118a27e7343c6faa19c797e5b1d847b647db848145d441d539ce8d5fa78d1d3a30c2c533d80e8e90288721ecaf483286a5114bdc3993a9b5d51b45614452d8668959d22525d8de1cd85a258657bf6aca06d9f1dd894607e2aa7aed34545d7670e451158a3ed8e2ece6677849bbb6ebb5e56ad9a91f932f23c512f105d143d6789a28b2ec208a28b62546a02f833d72ebacdba20662ebae81515496bd11375eb67f9c733bf4c14c52aba9993442b732144c52004f502ebb8da87377adb11cdc3683beddf104976a544fa115fa297f99d79307495c70d095bcb392eb24f7323c74737c718fbed0c71919d9d151f21ad50a6358b743c7ae8e182484f54af9779e8d88d46c72349a227905ee678e6c16d4a0b8e9765ebe11b3bdba808376771ed823248df6fbbbb45db457377a053923e96b839116eae4e99d637bfbdc399b282e03dafef8beefa55b657247b097c44748bb68761999628997660538ac04776c62ddae4f68c55a695ebf7f6121704e9db451861bb95f99eba96676790a809f92f51bde0ba3e65670736e5cd5acaaddbeee11e55dd76a47b5443a2dfe6b63e2c15638cb1de556b3d5e8d6fce39678dfa871df9f544975a87702512adb5d6b5d6daa4b47ec5d4f0e6ceaae53a6b0b68ac445e3f009f8cdee7befe642cfd0c1dcfe1eb4f86d88f98d453390a3b5da607c1dfa88e472fefe6c0bb4db86f51f06538df684abb791abacdf7de1b4d4dc8f828c8f1cc4b0f3a6f67bd45a50dad89f9c1196ac27a7e103541a2a935c17bfa45d4c475f0a226acebd728aa2d8a6a34bdbcdf2ec318e3f0499d4f123e9ff85ccd4f49f8accf0f989f5113d6338a3dc7107bfffa8331f622baad6faf763693808f777d472470117610b4bdb7d6c9074d9a96f2aca8e263a582a5b29262802d533bdcd8f88252f29c95131cf822ad5c3612312430aa9aac08b3a7e193213ec2dd22eb554760e4b056535a9c798b3a7cfd55f13efcfaab5afa14e7989786c88c19234b543e3efc9c4531a13e3d10dd5cee1ae97aad1d675eedee81084d59bb6e51fdf088d72daa9f86e0998f80cd8ec4f0ba9e29eb5b1dd11be1c245b7c81a7932c2f4a99989707dfdf096c445f5c5535952638cc24a0d5f7f544a54bb4f8d8894e39e1ba5f885a3481ca972fcfa2b82e653935442c674faf229f943b87c6a92bc2c9f92660fac4f4d9207954f49f3c8d5a72669d5e453d22c92e45393f461e453d27c9afad4247f00f99434997c7c6a92543b3e25cd19bc4f4d124ac6a7a439e5e25393045244c5a7a41935f6a949d2e8f32969eed0f3a949eab0f329692629b5d7a72669e3cda7a459d5f5a94936a5f994346b707d6a9242c47c4a9a39b63e35c9205c3e254d195a9f9a2492ac4f496d8488ef53d32432e531c61893a11511518e056b9a436e33a85a2a5e8f2953f2c9126733a858548cb119d42c1578dbc76ebddc330e2c0ca2884559ce194dad635b8215b7b543b688544d814d795d7c31a66931d97220d60bb6ce7904c37a81f52aa5bf10e92f48375a2dc9a00d80da906d43872c134d0eae71426000f07867a1d590e103b8820f9c344614f4e0e922240d99235c98e441c3c44505062a3e453a0c00cd113656738404aae3c3859a325e4f43d6f4888870aa68e9a1e50c8c2c2b50200ed3949a2561fa7819724b7005ca9293363d78576c74b980a1d4034a193b53422f568cc913d5c44e1121a89e20be355fb248cd9182b265b2aad4f4618183c59407baf8b696f6f8fef01002852b11830557548fb13980221066cf8e9d0b2cb8c811e489c375d2a38852cfcc8f2a11a218e11107891637bda82701d0a8c01c42352666e765061d215d58f098f2b2c7ce0d205036179e0c69e305c8949e2a1ca292f85cc0839502102894c6b6f28ab324c7d28c3981ae54b960f323c98b29589e8064305aa0fe8881c1498b180e4b6aeca1a106c6180c27a81aa926cc9216ae7663756670a87ee08801cf1b177e3c9581a4cc6851a2b1e2676501374c4f3482f4e081f9b805495962038ace8fc6103c32a8b430d9681a33a669c31b2449e8d0c9d243050f833239dec4f9d2e2430b13948bc0c28c5311ac3770c27cd081a34f932538a8aaf478d2507362230e56133a46a0aaef4f1b3e70baf05c58e2016526510984f58488992b65aa54e027e6bbb205a505167684bf14baa2e891716607a7ae911a6344290c6b0c8e0e201930e0110392263c235365223075a74c0c1c597cfcc90180147459b1b37284ce1b335259b8141953e7cb0a3f1c504322007304cb102a7ed0fcc820c81a3dbf314d6b56b831b3355ab4784169ca1eaf01c430862986264f6250e13d915c5986a46923a74b8c2513b022e2e34b891d1836b4101f68b2f4a464d901f4d4052a0a193852744cd5006201518513e84e8c16fef8b051c683265871e0307931fda011802a4f98b0d09387c7c7cbcc4e0c1c2c32d43ccd90d530225fd42ca1da92e1438313254d537645946091015ed0bb8126099c303d23492cbe9894963c61c17127062d09468f2d562bbebc6051b3809d0a489a70ac314304065490962c4bc0fce8a8003352c18401cb71a3ab298e9c25205887e9a0232a0b2b6cc9574f01649c80fcd0f325cc8e7da1c1e8a59a415cfffcd9921a5385ebc51c5d6abed497df4eba706a01c791166cc469a3d331645f7f5259b4b4cd24f0919b43523c2151592cae944f89e827458505918691a58a3cf8f52715f5a951763256a29b5de8a20eb81f3ae84815e71e6add758b9e9ab2ebe219825908efeb98af5b56ced73cd63b3636863446f454f78e01aaebf7066464c8f4da932b612af6f0d0c126892a0796933250b6e88103c5ca1908ca4adc67a0a46424117da47ab53da11137f3292f9af994d5933ee92dfa23c6382ae7cc64add65aa37749cbb8b1be1ae71f6108113943e110730654f37c5961089d2d1c5a529cbc48750152c1494f9fcf194ae933ed33a0cf393fd19ef2ec338076edebcfa987b9b9c6b47a8aafdedbb7b5d6f54918a5388731c6262eaa679ab52eb54ce390447d2ce67baf085c54f3d60f2f8b31e00354e60bd60e3775e4d8b0f2d2d1448897364f5b3e7b7e6ae1738ee173ce8040dcd79f138b1fbffe7e88dddbd373858fb6f559adbdb9ee5297bd1a011f817e7d677bba1170d1c547a02e9f2470d1093ba74fa110b26bd7e809a1675dd58d962411e2a2ec208ac2f61c9e29ac42b0779eb0b0d3859e3d050adb410f5114b69f8002983d0a1f69bf7ea9f051f6bb00ccd565cf4b7573b6eaadef2a2e0a610b85907de3a27ba370d1f54c45757bf7ee3b635831b057577dac51bdbda4dfbce675562245e9b30eec6fdb09b73dce7cf4aab3dd66b667b3576f7d5b9f6d67d8e01c14d1709bf9388e34bb35137bebb7bb13432eb02e2f14d86723e837bfa127d81cf4aa9b7909a1106e1ee441e809db6f5e75a377503fe33cc8e6b8202f319e1517e14ea1ec384fd28df844713d8414a48f5ef31052943e7399a328a144cd432f21739a7350a484adc4f69b9350ba39483ae8375448fb065c64a37a8d92d0603d03bac39d4c6f3de814ca28898a8b78f832e122ebb5b3e222142588c07e09257c5075a07f5075372fa184500832af790d3d617499571de8258442a8f9f68d9e30f39a57ddcd3ba8af1dbbcdb78fb6ede059e222ebb653e83ae849ba7ca637eaedec83aa53aad95908a3cbfcce4accbce6b804e932f42e6121ec34272fefadcb4e218c9210511317590fd11c780a5d9484112eb25e7151920e855b52bee1d5586f20bdab461bf0649cf3879bab59883491f3c48e162d78c26899e9c253c5053e554056884064701843ca39e3a4a93550aaa8668cb943a38a910a7b610f58153c7074d9ab9b27a1f07cd0e73f9f730644e6887ccc7832a3ef83bdbacec04a8c9626a01ab7346328711fee87263d8f67e830c61863ffa00192067e4adad75fd39b4f4b0fb990fcfa6be27afdf5d714e6d35c3682254619ab1f3f28ca38214992429f3e704e98b9e3434793939e17a09ac18c076b8c3596780428ca08e8a9b79fa00022f2d034e5cbafbfa62a3ffbe8ab570e3c453931f90d6fcb1a548f42541b0abdcd6027885e03f403d29fa6221fca0b3649953194f54b0fb9a1334d4286889a9c7eabb7f188f3683d872e82248b9ac01e7aaa1dccd675ad18889ee2dffbb788a6d6b36f34b5e3996a07997a64d38c4fcd1e5b7fddd4fa8baf7511247d76d959421fd808a38292dd9592315d6abe9516a0959f17a83e7282a8d0c745135d17adb516c2a26149c070f304430c1c157d4810e32443eb4a94d31297a70b4201c01ea5a00278d3634f1625602cb650347ba4cea4b0e5cfd41e25a22d229ee404baf1a6ca9d29b73a633d3d2f808dc5f952e759d05a6badf558450462630b12266365ca2c2d5942c74b972034aab438e6328d76bb1d8bb04c55192765a694669293355398b610f965e9e06253f32068446b0a6a45162255b4a0beb0c400c3c68c250f697ce08042e266ce10b0500cccb2ad70f8d1c40b56af490b5b4df8fc7153864692a61761743a60a9c0041e0c30a47962868536293091c21506a78d11195f68c0ae80200882a0941e2eaa219d242b6ecca1b15505a68a8c07c12a3d1e8c5115a6480c3f64aa9674368b8a1fda1276fa0a73814c1515533990ac20670a0c3d5cc2fe54f97a7085ad51082b081027362a64647909028217daf0399302183b4f990a1a212c2e0d5858c80366088f2c5a5b354b9b2f2b4b634e6056c2a402f063910474e159625c09a102c4451727575be2c89111664d8f3c587a8874789f7907cf4ea2b5d6a3141c36338220088220a843b089aa822265e74d910e3d7640b9f101884515dd9d3db8ce9495db102e5d58b4a6b89c3e0852d5e5c7e609932bb32a4c90dc91f2e76a0c0b9da01a5b765a7bbe30615589b3f1589554321d9b64040897d8571baf3f51929ec4b8f34219365672f0e44cf91d36e5487903c788d163d2943be80079100564a8d4d9d314a8294a9295216b5ed8e324c78e1b735a00334a990365964409de0227cd941a30479620c166e2e40802256c8892ee0e12153480e04e8f4b130e3e2bc8191a237627c88aa93d31f4f96548a1bae9fac08405e60989933171747556f8edf9578785df23b091c38592b02aaca92b38a32ed264a8f3a4d1737e49802c193b3225c89c2c74f24099d1874f1419177ec0c234984f16059f29f7de7befbdc3aef68f4f92df76947efff612ca8d73e47495b6b2172b6368685912f38543979a5f46a779f0c1a5152c94c01802258dce3ef951e193ef141e512dd40002660e17307da88c8171c325c7459c8de562059c93520bd13a24b5e85a6badb5fe3f5ea248e624197cc644395285a40b9f1878c42005f5420b77b65ce13559e99ae448adc7bc5eeb313eafb5d61a87c3e51a09e880162b479660c9128312012237aca26cf1924405ac3d702ab04c1cb439741004391429410f7556aeb416b17e3f402b6b1ec4583eb0b14a5569e68ab59725634396ec79fa22444fd69a2c2ac8445193c28f0e938d95428ddb92bc3de70a62b17873ed3885b6c721aaac5f6a2996470abdeb350314769f775fc7e405b151bc4f32c4b31176b71b9131c64ed52aa9e1fcd5ada71ab515839a2f8cabaa4f87fc7acefa4b5a259f92b98352bbf56ca2f4d1ce345a6baef420057a447e13da31769d42d46b4ffa3193a08f399f24b811b0813ea343f600d7f76cb728ef0fd90cea5ff1af763c734046cb9bb58c7b4503b60a3c5142d6b767edd843d868750da82c4f0da874cb0406067677d6c91e3140c9a7bb6e79b5059eaeea2ad80b7d50754a61d17521211221602f8251cb848baefff50f36839deeba9055597506a8c282e3d3555d520c375a5d5a5ea2763b5dd555dd8db2173b8d184453d929649de655379eb51ae934f4049a93a557dd09a3cf3c049997e8093597f90c3dc10a849acbfc049b8fb2733c6b4e73106a4e73eb349a93a890f59927e952905e43852c2a3481d16b0ec2e835b7160541e6333445cd659ec2e6a327e992747596a15e10ba90f5241d790a59140508a4976109259a22454926e96c670a2b106c5e435358a5456a5eab790d45e1841046b8083b261dadb828442b4ecb122def02c2d4a888054f65d5cd9cf4717626e9c83349e70179da1de93334458a10e022ec23e022ec245ab109701176141f541d09b8087b091f541dcd3fa8ba19e9a9dd91156c666d94e54531455d9a9367cdc7319fd9c5b384fbd96ba7f5e122ec778602849ad3d01429684e436d0f1761afa11f549d12ad866b5e7534ec2170eddaaad391a7d5d5994571f58270635028bbad3a99468532ea013a429804ded0881f3f447e90420dc7e64cea38a5f66d33a9eb5fa1ec499fd1f474b0d29e1ea604c06334f7ea05568bee13e6d06a3919eb3eba8fce6326cce0d6197fce196734c5f86a9b4330c4453ae730c4f5ea7cd17cc12fd9e23c3acfcd79b2cdda4b7b692feda5bdb497f6d279f41ced15e66c71c55bbf505acf39e79c73ce39e78cc305e17219e68cb59ed23aaeb5ea5ac39b33730673ce39637b31c6586badf3e49c3dc563348355923f48ecd80360b832d58ba26507d6029ca8af212c0c0934441785f7dbc90b6296e40f15c618638c31c618638cabbe53d61f94ddef0918c618d78a317e397befbd6bf574efbd77ad9eeebdf7aeda4e597f4fba7e4ea8c07e4ec4b4aebbd6bdb5de5a6b9df10e70671c650785f73acc0990df53e2af3f274e20be7dfd3951fafdf5e784470d9b63b0a8097c9a4371cad4347515b29ef40e56661cee0bec2b0cec6b4cccde8b71ce1ae7acf5de6026f5de201886221886a228938d188b32d93892e46c24c9d98c46ab89206953b2fe7acd37e5af972597bff7eae6ae5fde9d7175f0ee8cebc47497d274a579d395e6cb8bcbf66c9575627bb6ca4eb9b2546376376695ec6eccceb04b26b85cae98bf5c6a1a1137777db4f7629cb3b617e39cb5de59ef0d82612882a128ca64e348ca46929ccd68b4da8c56ab95a5cd76bb05e170193268d0b0e14c3d25eae09aa987bfb99bbb19356fee6217c15fad7a010c656a66ad8a4186bfae992a061501506161bf266f7c59741e066831b1abc5c0ad331613d35f46cd274cef96e6036e9df1d57cca8b77209636486dc250a673a8ef18521b21197d408ada31d0f811a3c6980a24bad47c26549b49122c1d7fd26889aac1e58891d11659de4004939d064f320cd1f2e66c5985065ed2cb21b08f08d375b381882e8c43a61aa61bb2fc1e152e0af191d12fc952ce391bb93152a2a5646b8310e677e59c087d14e6ac84eb73f650899acf4ee69cc3d00829c9ce89acb4bcce0a6b492eedeb3debaf49d58f960c7b8c31be9b2634427ffe8ce2c7644a52b627b09aaa7a526e118740d2c0aa4a88140249838abb39231a091f6d7091aca63c3e926de0a6ac324bb5d1a137431b68d4c8835b2350fc28813326c707654b0466802985b92282842a89539c96a8542c7875390911b24800000040006316002020100a084442a128c91351fa0114000c739e48564c2894c642b12047411807410cc3300c60883100196490510e1aec00cb2ebf0b4c37be5abba1c40c81c717bec66256961067ffa54ed45a85c447962f41ebf5ec3a41662219e26989d57250e70e428dd66f6036109e90924888fb69e8cf267b4aed0451385ca133bf38aba0dd53149fe44b0dd1a5bf402b72ea037af37278bdbd0d519e072b8a7b00b8c21807a3792e7d57205fdfc0e0473e8654011d73661c010e73b2651f62f4e1bc53eb1f601e96d9a47eedb16506bf0e9fb982dbf601ffcbf8304b1366843a6fc0146c7c7182080fb18e2826492d947288c0fab2e90b754d41023a2642b0ffcbadd0ecaf3721d4add586e69c2faaed83efd41670b04890cac40e53ef9b6720156e6513af0f10c508550c23fcbe41f9a81403369dd199c32a4924430065b20d02221079313f7e5d53843b54c865f6a03c6b9c05721a2acc0c599c297ecd55d41e243731e5b724df175508f42b46bf1dbf3a94b03ca521bd507a42a6e1e5324bba4bdc0e50e21345e2d0a952990cadeb5f6d8c4a7507b22f63f59c5c0990341fbe31808d635ce693122a847536eff49b67660b929f1ec71c4dc778330ae6440e79ebf274a291cf1151fb3b697c3790438fd207ac10dbcce79bc3855e00960bdf0db31c4f25c9c39c75fd71e67f3a09ae637d96876cdac505517ac9510f7cd2af2bd0e0d51b7226c2eb1d8fefe46cb402ed313f8862c0a565c9f3789b604dc60e0a2a4dfcfb0e30f8446b21e8a09f8e04a834412b1dd844b6f287d3c3a12542b4c272444fc9e750a5280b02582d1ff640f81dd644429cd0de199e87721c5a3d35133d2dc011522a96eb6a4e3f3573725550595cac7ecf52ea10c4c472d490d1315f98a3e90b4da9ddd5a7c0102376bc8ab3529aa3bd71488c95df095becb9c20a4e6bb95dd6fee9dc3d1c7cc4e71f7d31cdaac428ad06a6967b26bb266224d65c0383a258b6c38a6a4464d05a35b504d22b3d3e5a923b0b3bedb8ec0e66287ac135720ecd19f36dfacd48d38b124a8383e4efe650ab3043001b811562e9930f84c8866af9ad0937e40e93d11c5bd9a40178fec3c7d6887cff5b5001832bcf37f2c7a905534bb0ecb0623c39b0e5dee94808536754c48b4d1343e3ddf8e8873baecfee8432989900d67d0d55570b4d91e7e37f1ecc82526e7fdea7a47755e69020201402043a0f9ef1743724ae92c7881876e7ef8f20e62593d4170545fcf5f2828bff6959ee240e741c7acc0104cd9e387bad69a28021a48886fb7fa6c73d555d379c35deb9e9217335acdee1d0190a913e39e56c4face8ab75d311e61998d07507f64c3feb0d739563844d777c98ab6a4749d51ef00057578155b4c6a65e855d1f267867a48f05d0d5af17ddb49c5d6e4ac1a8e6b26c003d8b9e1f7ce64b20a41e8610ba74e71a1c01396e7a6c20f5f8095bbf46b0854f01538a2e7f3d67674d85b4c4aba9a681bda1c6a1c5938f582d1da3788f03a68fcc4c3e685a3a11d34f7a49564a7a4e69c3625b647a4187af9131c5f6775c1d2b423b12c1919e2ff634535c9f5065ed68a3b2a30cbe0b85bb464ae25963c8c51109f60e0edea61ca41508f840328ecc8e38359eae15b3f980df54176e1c2ceb8e289661a0696c742d8808843827541cb36f4c57db4b91e4d290f7a3b59d9690912443bdcbf390aafa33b3e2591077af60a31f60c9e504c14072b8f98f4fcf4780f51e80d7d9ed3e4dbcd9036b511d94a0245bf73bb605f02cb07f3721bf35eddf918feb33fdb77ec37eb901b01fe0753d80770440e1e6b08d01da93986956d40d8debb9fc09c4dc57bd6cad8c34fd917eb16a2f44fadfd71b0aa3d5d69c395049a3eb73664fec681207e4484ace84120b97257117e89ec8fdb2c2d0d053c88e1101d8b95361749e75460fcd5f771def99aebe416b4339aceee4c53987037f3dd4de1986b6bf17131a1e4babc7afbb04cd291ffeabe40658612b56d6cc3bcab4ddf2f62d5f7dfc3ebf86c11ea4398520c085376bcfb14c228d5f3b583cd6d65be27df1282500bb249c56778b340aaa52055608c13f5f9e916e58146e2caaf6cdfafd4c37e21eaf8a1a5f0b1b27b4f5ebc29035c9b0a4db8cb8c588a2623bc421b2e961d78ccc67ae4d9b6a3e9983892e372e088fe178ccc8f840d456b3a9143db3d108c4a9cfe60a68faeb9344eb0615d06e2ba45113be468fbbba4a34f1ecc42224001dc3032b78a868bc0dbdc8ce0a27155f0d01e0d982d2f648f6329c5f72c7071a4300b5df4add83f9462f1bc9d70989e8ee3f99fdf5b1e9686213de7c18cc6c704991c6e0b810b0bf40b40a102bf320ee1208c2133f53f2e8dd374196bda04a2051e20919264f1ce7a1d0bd00f1000130645bef9e436614ee3e4499d59f10f65a519eeae672972f2c09ac13f21a3f46f6e57dc309f8d2a9a0b2f1bc391f60cfe68eef9fcfd29c2eecf3b7acb03775cecde44c5a34d06e86fa97f35611b4c4c6902cc19e6f1bb0dfef1b1c1dfcc6040ba5aa1fa8f2bfe48ce60a48a721b090494ec3854c96fa7b4d451ee180c989117ce21b16146b9c1b821049ca7b1192e1f3bd29c98de00bd9ba28e84757b19fd728879be5c0fdcc6ae8da2418ef6242b31550a94125fc99875798be3c5e205295057030b0fee3864ece79ddb695816201c94ff92f13104c2bcd33048156d41035091f3cb4facf9579a924108944fa797d50b17df49d515e1c39f109bd106cf0f9d38ecabb8eaa057a2304efd52fd7b0c3ab642b13dfaffd82036d145a3dc339edd02f97cb2908b55ed16b0257d2b1260afe5484d80ba5f999a65e685b2044d184c4281650759e17d4aeeaeee00addf188f655c0b74dfa99a6d4fd66a4334afd214a13979575e432ea28f8d7298c5522b260aa47ebfa6410617d4b8dc78c2040aeeb34b465ea299f7ac21de79c70db099df55b0c94001f20dc1d50a72f9768fda5dde121546af0b87110fe346bc56520e70b3fe88fb2e0a9b41bdb654b2fbc6e43e36b385d07b825afdd686543ef75e7c36297bd5a60ace0d885ba641e39956e77636514c5580ef566bbcbf24b375eb9656abbfd76d2e35f991287da7498df16486c6bf8c79d327e1fc6ca006bdf16278f1245511cbd01cf0e829394be97299d9d52bc8a8405ebfa524a29f8337e8dc71e97fc5f6e064f0902176f44883907052fe84e7f96a6842f32b62958c62c773e6e598d191f15c13a1c0c2714bc7481372cdfd1e6f08fa223d5dd5eddde9fc80897a29e605987c3325f631ddfe992cefff47c0bfeafd4bdf3be7fe30175fedf23295edd520472c1e59ddccf986df6082d14cb33d1fa49b3d7180bd49ef046e559c70daa6e5f0619c3bab522c62e441f15d93e89be6b2f42e25239e2414a346c55278dd554088bd33e1a11ce4687d3a4134303765b9537e5377a7110255df03c1e5f6f54a0049208f1398c35828ee3e85d454b3fbec37f60bf4df3d49d8f2f28cdbc597fcf578a0c1f7c767753059008eee75d65da9c70a1452fb628c05d2398531a15025333724bfefe8c263237e873db326bfa2f819bf45d5fdc0f0177637837e1f185ba9aed81202cb859cbf14077fde0174c4cea78fd583279da7250f5c78f2f6d7ed9c11a7c932ab707db462c58e34ac0a08fa5350ac6e41d88091a8c77e70f7057a1bc58758b0189817fe89fa446d89e04f09b9c2565f55aca59d57fcc899540fb957a2c4ae469383ad8e15806e91fa6e02e76d138a5a2787d545acc998db67370229f9e958a93819f34b6ad7ada984fd06d3fe784093742fa6aebb7d55e90870a27009c5e1adc2d3d6ddd07fa543baf3e349e68ffecfaa80b62e7e11b925e61ec50cc6c17ffc2aed1ee82b547bcae45cdad940d951a5b2e35d9d2608fef7b5f1ecb9f82b54239acd5af0cb3c528c822dc2a5598257f031ff4b19c4308a1cea4508a0791f1ce0babe05cfbe95357bf736fe5b165c0abad623377f93f3cf8caaf24563e728e93ee13368e7e767c042fb3a28475d7f21be229c6b86f750a9fff417982eefc625e43c530cc6ea59a5e9a6d0e7a8979772bbf4b21efe7db2eb0034668dfbef7da5e05216f91536169200c120d29c64b18fc658af13156fcefa2fb2f262f566c90958dd45c650f38795e29110742f4f8a887e8d810cf58aea583bc1d6e3b801ec636a881b464ac8732a1a131f156e83a598cbc27a4e81d15819f413ec273c7efe24316c67b266dd1376d8d39005bd931b384a289b6ae645a08ea06a521bdf416d8abc39e1cc446a081ec671803cd96ba14a1f6d65ff48626e1a78ff97b70bd7dc9931350344ad480fece772500c0c50d80760cc4092a3cae60537fc1ecf2b2c0fb93c24077528d5a9d78646de21972a2c4ea57768012fcd31862e0a0276a774faf5f973624354e49025eaede002030c901147044dfb96860b7b23855018a20d7dea1915e011491c817894ee0dd97d1b9279d34e99a1a9a8d236b6c67d81be74cf497fc7c4a3a5e6e4f2954b76a9044fefd773e5507a9513910a8c3b300f84e649c6652350b4ab8b520874d812b31213e373aaf29e0cdf2005efc2ab7442a0c0546e9cf215a3d762ae5ca9022d86986c7081b3d553a911246bccc4cabf276873d4bdf65b4487eccb0b1c2275e755fa48e73587c54e433982171e17d95e24c86ab7d677925bbc8c18f6cdf118a5081e27c17509dbf6d854e50747d722478902ed930933ff7bb0c47dc93a465722b3abc175adbf0d73122f0738ea09fb637dbdc532271307e9764ee65a5e16532f03735997961ae6990eaa84abe9d639a7959d83c5aa3f2882dc622ebbaa5808dff1f670e28a61a865e80716a5e9d313d0daa31ed8eed8e2596dc34f4b0111e3b1e9646e135c2b0570909c8bf48e534d681d4d645204e595a07d9784fd365f89cd52f08815b645ff85562796efb2a9c896fe45b445648c1ab31c1e19a568b16ae73a616e53efaabfa4564c75666c04abeb8b1ac9a1c581196730828cb6bd5eb1c592a5935ab5e38c8284a47ac92b8d301899ab56a07a7589a9b53a4b868de2053546b95ee53ac66649f9b097e724e822773b509272492cc544e862269b376417c0112ff6549038d4c79eb5b302cc16fea623716e5bf3003714fad7523c6bd9cd17422fb667419cd001da56430d2c020153d39d0bac05af3924f4099b98f0704b6a84ddd874f1764396a99f00f273ab3a46aafe91250bfabbe29749c3fbae65f95eeb19bb86513a72611cd696fea899da2d6a0847caa7cde652f024ada32d2fbd48e2b8f39e0a7187300069db6569ea2b0a0eceaff4ac09884a6a8200d5c6c4db143344c4dc792517e698f66c2adbb2270c4a9d0836019c41d8160e288494759a45a314186fe32fefc54243df655e0db8ec26c23a826ad38a9d68bd130a100f24f45c5548de04147a674546c132c2d4ea47e30c75704c56d3bc5725e40c10a652e53677012cc45d9251762b841773c04e868a996a4c4b582290f680e4ac9d1f13af033eb8a15e11d61d66b7dc980901bfb8d8697f0d228a54f0d4a6a1b30c5c0ac2fe6fc500d888aa205e63bbbdc2e30438d28e98c2a620c9cef626fe5b43cbf20c4ea40687ccc6ecd3bde28ca44ee627e1f895a34b66078729d47ba3274929f6b7b6497d24ccf544054333089d3b12c5da42bbbb26725f0f8e8771625db5e7f723529842c182d57b395ef1a6fec201ab80c5f4df608086907cd92f2943ae7ddb0d91f4b353cea9be72e8d080cacefef50ccc30170a0f065cfc7a5e0aae2473e5f9e924daeb92b085af2986a0ae5ca79f2ab3cbe40352e353f6c5567d179eb241aa4e1ee664a0a8b4c108512f82520d62e8f63fc43b613962fe32b67c2a202296dccf3ae3456e1af043afd1c3c84a2a4ede7b6a3da244e4d1297277928ab98defd8182d80705887c2bcbd941e45d9a365a9dfcb158487fc9f6ce6dbcd0b49a41809f54c9390e4393a02efd71705075cb16afd2f8869a111fcd9aff7c30641544880fcce09892a259e38f072042bdf432d1ea701b2960d594baeac7d1f6fb2ab2218e29aa6b100421b5faa5107df8fbbfa3d3c9f746d6fb3de5034a14758c154d143e4ce5b3d23591d70eddf12889e89400432ec3ed118803897310500b0d7f532b08f7b3fdf14ebc0ec39892716a63353118af72dc656578b0b03112cc08ef32ab755a46754232a0659a35611245b7d601f4b6f25ac74e30b25a580167f5eb2fca390df0257401da05246643a64de5375d6d038137ffa415135626602e7e7412237ca307a28d226e9c2c886b3e7cf7ed7b60c9abf13dfee4884a3db46f0fcf0aa449c9b83959e865ecd617d1d805f47dfe17b99bf1c665a7f145c8569ab535913364722271c3a3261ece8501dae471bd2c7f303c0b380eb131ac1c167c120c451880560e833e0986220c402b87419f04431106a095c3a04f82a10803b0ccd85380435107ac83878320e699a95d6cbe42785a3a003d1c787811be7904397b0624bc08df3c821cb3bf324e009c4e22d9afcaa07a3e16bb370f8ae342cf8fc59752681ef01c8b4ff8926f6037a824a356b7e5b7da8261ff50ffdbb01de51127b70ce5e66724f06bbb02ad64df314cabc122b74c8fd33f973e1360a163a3f07acafa5248849eb3b6a021cb48905bc316b3c8525eb327a95b6c8ee32c58d21ca4564f42bb362a9c8fd96a3b947c254929b9326d34b873a25fae489de1edf27c814f8adde1f2e2455a7e94a57725be64e7a4419d762d7e47f2c6514aed9ba08dee658c451560a5cf27204739d5c12c66f4ee470a34d5f9c1447879520ed9a0e7bed26a553f2d377c9557b951a96fafa262d72e7d41615f9aee544ca4fb919c9bcee0d6976bc70d234109fbb676aacbadd9f70faa64bd6fae4de4f935b879df962e29f20a7ca557335c34cafd63a1117b6a61854454b642fa6a0ba16afd98b0105dd0d7cf5569592d1917dfa564592d3297cfa5b4585b3297efa274592f9916dfa5b4ac2f3296efa5b4582d9996efa264592d3073c9f7b1adc5105c603959545e5d64ee238b7e647d8421c21275855648df3ad6929c57efc03bcb06c50a1d94ffb2e2127ea2ac08f00a6828e0bcaa3b34207f33d0a8fc98d0a47f4f05624bd53f033ad9dfa4888baaf619a008f85e0ab1dcca1f138a988f389675d4954b7b1cf99c2573a72f51a6c6389d1c56fb2606110b5057456b15c87f561096e54e3fd2cb5321314327c7f0135e3e14a1b11ca98b33fb9351ce0e61888ad42d87f8c9f2e2c49c58147258fd2d2b482baafa18a0c9ff1c8a5c6785ef844ae69738c4c65afd9950489f845657e5cb0ae95881f8b1b271061fb294187f3a71aec06d5348da561076a34720f23bbc59955f569b0e28e27f5971071f519a8d2b75b26ef56f5eb2bcb3d1b4411981c48f39f43202a447d4c161856ed9b17a8b8dc60ed56a9a474fcb82c30adf9243d88a91acfab54273f3fc1d391beef423bd14890ff23bf98e9f825738dc23829f281dd708fdbf12221d8ec31433e542642a701ba98eee312716eaca73eec75f2c60869cac7da2a20fe96c34677d42bf4635330f6fe93cc045efa4cf2e5d6307c7dbcc3ca8e9f016321dd0ed15e8014d70bd79f3b6d35d73499886d7e32983768c7d1fcb24e4647cf3c6bd8275afdd50b7ff388fb7992e75dc315fc4810cb77b049e9bb0ac0b39f37347901f4cc1396203c37d9f57becb5e65a6720425b186a3441b20b8fb0a1fc354f47e22dd1dd292167dffdad7b221201cc2f42421a800c0c16e93ad67fe7b975ea5b7fa630781a6027e16b8a4f9b8ea14cb9d42903406ac633f3ac57c5f5632f645bc71df8837486f6d218792f8e11bbe25bcab315c7081133885de0aef1badf595976a8e04021b58b29957780fe38207eca8cdf228171f0716e063d41e1c38dd6f8ad7441c1b133033478d2090607b5148b2b1e8f900cb4500fd73229c2f6ff5af122a361c986be1fbb812f3bc37be290f4df9aba9d8e00800bc0de2e924844a0062a8e5a661efa4a97c39bdfb2e79df201b0207a91d119b64b7e25cc00c603b253511494416bf27c5e616cc2d43c046e4af9132b616c1c3a3049f096d0fcb5cd56d792f20a5d4db0ed3c0b40af52f5752816b5994233b9a4c6868055a6bf013e5c5e5f1a076b1effb43add14b841b8dd2f7c7b50c2c2faad6a911b62c30e5fa3a6ba2e8e67e0be45d91088167f918505d07f2c7dc7bde5cfb129631fc8b1474e91bacc431362c6742c643917237568b6337a9337831bc96ebd39a717365945163434562c5d49ad894aa1dca65f2135e772714d42009aa58655d03adb7e686854e065f7d9da4914f511076e8f4b08568a86fe083822d6014aff15fa48a9620e2ae8e2c3ca68dde29e182ab339b021fed62fdcdee0ab396ed620b4d015605563ffbc274114eea168e659a5adf4eceb6b541de112522eb5dd301a82ca1f4476984d727e917756a7d33d31c8b6656f4f227bb3ca5090866de243ae5ffea04c4c2611391feb03a59f36e800eb903b841a7d14c5c690651241e4915e8633fcb3e1d80eda4d27bbc866538103e395e3aa030224ef49bad2fe24946f17958b370282e3578204677c48aa07870f9a5ed5f86a14341813b105283c9d2d51dda9f323cd7aabf05f2f36703f1dfe641ac8b9a7b99092283770f900688ded54c1c4e7c52ce47d8cda76e4337aef41a3ef6293ff918aa89dc5b983411b01b749329a61f243f73d0ba63867505d1618773f1706345df61641d77dadf14f4dad223f2680b4546502204876e6cf13ebf40b855e402bab73bf5861a7f8d38543fe4938b8232ea2790cefde77b62c2947d7bc5dae460aad80169dda7b272a61859c5d3cbbeda45605f75b76a3bdfca0b721ba6d195f104186d3d49c51c32c4caf1ca9ca03653a6638eb58cae3a08887d46d03e37462cdb6afc092c217aa0544adafcd5da8c120ffe097434fe148eb0988684a6f2e3c026a7bca09d4db41d78162c507a886c4d1fc675c83fd20ae26d2c0ea8faca968de869967bb5cc16576949d99862b06ba852b99be3a10592a16fdb055f931765552da5e3269be74347bbd8aed139639d79dffc71334997cab2f3457f4b4c281f9eb960d1c68ec915ec64e6ffff6b8de237ba1942f63c74ee785c09c7ea4c8528b385364c5b46e1df4141476b78763cebf544bf79e82bc70e4c567f37a74b21395a9336b41bad41bd129f1636a1b1ec49b0cb0954875392416eef43b537dd89c285c7ec3f7328063f676681c864b67a17c2152e350f9b209b066137358d2da78581a68339a7ba3c57f26c2136d8e4b85c19c55e2de0cc21466c3fb40c041fbf791a5f881a02923608d93d0fbf7fcc16c0f76e4c993f9805fdfb64e2de1c9f985a07863c2903a6b2ed8932444eee12dab8e7a1e127e411551639fdc804f51afc723d559d9cef2f8fed3ff0bb774b62fad6cb81490f46371ae53c33f1820bac90d9511a4b302d8659b4f2f41dd8140e60c34ba4b72eeb65e27f87bdfcf0ac494d9573c7a6a9578fbd2f7c4c7262289223f8d3b19b2853272540e44b14f79a9c50a0d29a197314b346427da0b71ea6ea9cda666c1f256b6ed25d36a40a4ad528633843ed458d4352ea280e972e9dc0fd73af7c4fb85aa05a65c16f45afa0b046a74bb398af7bb568b7e92dc325d62b58c297286c079c5f4d5ea094cab410599ab228ae5bd42cb9b0550c823849fa150d6123e39a9109c1bfe1d5294ccd4d27f0f71e05bb20bb7944303b9e4865173b0276cb7db5d2cd2a4440de531e10b08b91c22875854ca192096c45b73da0a22e0102bbfc971a1d4a16b9567a680d54115cdf68e2e245d928e96f9b292af0fd69a1b63640248b1415a8667cc63d9563d133a04bf842cfbc75205ad569daaa3229c4eea79096bec8489fff611ade6ce7bfec4aafcb06897aaf2f52d9c6a188f628a595c4711c04af6f576ee770e9a7668dd48c5388e78e9d156ed4aa60216ce7273f6c019ad7c7ed192340adcc966b9ac11a4e28d9eb66030372512b97207c5fd1d8c97f59367064f1c7d61c46594efa2ba8559256e4d9b219fcc404cf2e251debde7c0530e16f530fe59123b857f7871b730b53da85541e0e0466558c6dbad40d1b5a59c686ce60785f3ec073488c07930041dab9de0190064d7a3687d75fcf8a40cea7dd98bc1f58120aeb75f8f4fe5b481286974ec6bc219d80844d4ea0e78f3736c9a16eb010e07c889da56e5e3e06864c46431d1742afac1b8cdf5a0c2ad465e04cc33e3530d223ccb66ad5ce97075f5130e8944ad9a6b662e58e44e93752c1923c444eebd6e1eb90c49e916eacd22e17b2249cbd2a0de2a18ba3fd722a5e84ab80f7d7f485650b321d6b7c1da02ca66324c03f77bdde9a5949d7bf3f294a6830601becfc086e8d2a1fe38e401580c86bba602bef865d7652fe713645b7cf385a9e0b8211e60e432e12b9160304c251f2f095db086fd5ed461e1a9e14780d80921c14bc734ae5291a30d04193cfa9ba8d4b9c39871478ef4ab18394e7012bc9abcb44a5f730ccdd74fc1d2729e8ca8ae8803367e2548561a81e4142d7004e5a8bf373ae31efdb3d1e7a4ed81804bb950c5db321815565a8757ba5512700a1cfd976bf1e72c1c6a47a80eb5fbfd2075283ea7d27e6760c060350a6683ac942be25de3f60370e5a416265193c2e4f17f7246be5f7295790ef2c0a5367f1c9a007053c56e2b8923c8fdbdadedf101fc29e4feb1c715bc1a0efdf53e775d02233ff0f3e1d54e97ecfff7b8f14756c78992f6a60368af7ea860eebd699abcccba994dec6759d6b58b6d9371cb4569bd0f5353f88896b1834e99626172645ba8924f6403d947701fff6c60fa92982ae60386572dfbb23cdaa0f928522048833745289fed72a7c7dd3e787dd8672d8240ea26173ae38ae663499bb5ab44709cade8e148bf76f1948cf0cb4c219b2f90fd0d54dbefdc87ed33f5628f541e3d090d8fb5fb29b6870088d81e82d9554a90ff23a8d0ae2dc6325a5bb2482e1a26870e7e4c4127495d5175abcd26b9f2ac936a93bfd6f054832b0b793add4ca31269a2644e0921cd38d490c98acbbe079d723ad5434dc4b737b6014a71b09f2ad5cd6d6c6cf5f79298d3686dd577ab212301efd3ac92637027a5912993a1e4508c3807fd69a6132a7ef21c29b9f8541f6c1390ad1be0301bb6a05f510888a99d9f586d0554dbe8779a92be1f6d695d991177ef3aecfe9ba20ba2a961ecaba3a221dff228186a5cbcf0bc822f50c26a8f17fd6f8aeed5e9e71edbcb59e526f307146ff29045a998b42d3d191d6ec468b6a9d15f79f1258236856f115e4d23a0aab128224d2106c0d6508f7a2b4b48ec5d84e65507994d996d6410385844a58c85ea5786b8cb12ee9c64b17c706a3af3896c008e497d6812253380440f20a8eff448ce06818ca72abb1a628bc653b12862d71d818f209c53ab44bcb3a75a558b79776fc14420a1130eb6f273ad4d7f5a70f5d8150f17909db344f40b19b566eb1abe4d5949cff00daeac422d05a330a5b4be99a95a31e6be176f41a2b8d3a0d0091e78fd56a040ac90958e1e0721412206aab6399c69ee9aabb4e8e7e3cf60e62daea721e76134a758b0177031fb4d56d91efad5da8ed3e0210543bcca4c10fdaeaa0bc2a78797a28c56234d773881b1c86b686e30d80b63ac380f4255517dd3b9640f3664204b8937b31dc015c0a6d7503b2811e96dc860b7cc3b302708ff2e7c2458399e17d81f81fc774a3ba65f480eaea3e3e7f90acbca53aed48df6731d684712487196381a6a6951759cf3875d83fb85144db9affc96be55726750f0e990f0eeeba4016b9a246d4617a2ccd346f364a6b66a382a84b2790ca010900766ad6fdffbe98021b76e9678f2caf75143e7e4fd5a51d2183ed95eb44b816a428d3821b6a4be7052d5167c91d8ce11215b242008aee266a2822e0d41ae5b483f51269c214b25490d3cb7ead19faa5b9d66d7e4cec1682e7c4e6c71fb60a4f79044ab4e9ffdcd9db492c6f83d0d44b688668cd036de4cdb03715ca7e0024cf249348035bb3da9ff08e5dbbf66e075857575e4d993303c6c41be5c89d51b1530bc462330854edad90a12f2ca241ff547da5a36a2b47ad59396a0d5686d98673df7cec32c1e9251e5d06e6f91537a2e2fec8b06e9bdaad9deed790b8f82aa9d320877b476d06041604158b7f6849d96c0845dc611a7c656f2a02fd6ff18a311f751c2f88a29f1c10da268cb724252a4253471812cdf59937ba992e0c525383a4efbe7ec5ccaef1d08b48fc26f4ac19953b335415abed8d9c37e160c64a1ee4183ba89e455cd9287d63a8e735068d920d1b04a35ecb17abdc28bd6427caa0f268ea1b6cb2eb92888b21b5d66d5ee5fc6738d64e8ff8377981470f9db8ec48b7728d789ee1a36ac56d2bff7ceb9bf837d2593d43a37a62f3bfb49ec2d08ba600f3fd4420b2de57b32b188bc7dc3e382335b4d29ca6f710e361d4d10a6f8c862c2b3fb795288ae70b7bb09eb1edb83ba7b681a8fda90052c26c342c45081726141baa2433636a98e75b239e256ac88b1ab6510d2aadbf6ae888b85ced402b3cb2d170429ca1002f4ab3107c050cb462bab70eeb52c2f26ed7fc839692728254a81b3f30b1a6e543574f1993cbf1e10e9e88b9fda7faafc456968e0650bff5a5ced4f9b6adc81ad0bde6c6dbf6225e4e23fe49e1e2d66ae22a81110ed5eb7e91d6cfca6b8ba976c7945859e82a343e9e74c825de9f73ba318fd2329c963e0aa2795324265052d95c448c73112007b3344324fcfef60ad5e47ca30c254c02c7753aeaff2c05df376dd046992694f0f5acf5b57a9b9ba215828c65d4df5a043450389e6ba1aa531b67330410ecd60a4627084b1d3af1cfcea34edab29aab22cb2ba31595b9756b7cec9595b8b36d9c5fc4e6cb90d70dc12e32786831b74c1f98c44fafe8f21a749c5cdcad45cf34d0d73c4626f4059116f1ef43e0d6a873f4c65d562afddba6f8a520f175c2c7534a507d2ca77d3c7491983f76f1b760d61421fb1b8473119b5f2fa79fc7660772696b73a10c8c06bc8ef96009b6a9fb74bd88ff1efe7366423bb1412185855342d54853588024b9e9c6165e1f552a121c97be5e8b6b29e756fb3862bce4c948ec26a5c17a46c41ed326b8c69499311579b5d9639990f8bda677dc90c77eb3f8cdc9b3cb5423477b9b2d97b591b4575b1c6d45468b7db93672683b089a558818c3906eefc2eb892149e617136cc2603e961d098e1d224b757e77997c98bd11ecf47ab9d7efddc1cbf0c372bccb6326fca59d0d9f66a121496353cc23b854438c49e2d9dd1ad7be0b2477f9acef30bb21413f817bcbbe646773657bafe6e7c167b2d29750475c3a4702565d29602b3f13ebaa6253ac02a1d1d5c09b1987f8bc2b5121151a73f9e9719e92bf386c011c5be75278b6d849128075214082490503ef34623157492a1c1cc42cdd61d040ff09bb6f00b540111d1bd3bfb2b94fc3e39bcb1e9f5c56a51aa6da32a19ace83a3c92eee66e6931ffe3b129d89e2c9972aa39eeacabe8f19c2992533f1066c40a29c1022afa5d7b2ada8eb6415a5432a9bdb15aac56e9c8973358473c866bfc2bf0416f640d081200e6c29b3d89eb46314124c855a8f339040aa8a9fc0367f13596fff80e4850853188e07282e7e55361e28d1e0fa683cb8b12e31974183be89eb065dabc4d42cf99dd0b0c3098fa590895659c8886aa224285f3b58be94830c63c2a31244ee57982278446f4bd8c06578c9da7d7c585cbfbb01f6d55c15e7abd5ff5c244c960e569ba691bd047fb8fa55f3afa11c5f28818550da93716f1d4cbe722240cd42855029072a0ba63b8709f13c808aa61acad1414b52c39ff1fac40cea328bc76336dda3c62eb056c5b1f51f12e9c23fd8c0314f16f900d22b4f8521b7872ca022f5b34ef86a87fd7842c1d84d563cfccc5c0c30aa1bdbcbe4e4bbc85a38ea7f537f592eca5e90e66553d399ac7b7404e61f38cf8cd941a1684b2d427f0d42f9cbe8ec643211d2db4e644f845da8853d75427f1208c665c8c3f6e8a400206ddbc622b587517724f469ec4bf2402e5edaf1374f9835a445e25487955d3543ab911ed1b8b08b29e3de011430ba8b16e33aa1370ca48108dcd8be3446ce2c76fa8251918d5840b6f54a0818640ca6c0cb4f8f053ca3e830a977f2bec9f46324c0a29419b8b2556b96a8c024a01b3675441d54bc40f205d3b5f110a5a67c77f2a8ea29823e9d991ead542fa1f940fc5c0471d21bd3a41e69c101c598b8f66dd23795304db904bfe9f9bafa1355a23a80904e104ee77021d1964f5e24add201003e7b61c93b01aed22f83986023142c4bfbcda90486e0a210725f62898e9ecb8699faae311275420a3b9c16c5717e61ae6a9bff4fa59406ed90976cb64b31ffbccb134b731cb02b28e586bb6aad29add374a5294490e309ab9eb2865e053f5334a384aefdeddcf68391103cc8113a436d24d0725e0a52225b1cadc8d9a273801556b3c955a9d16df182e2b03e179c773b52fe8af28e9952cbb707dde0e305569515aee5c58d33ce096798ddb7bb0071e509316377287761c2d5db2b8b9e1ee16398eef932b8673268fc5f6ec460aa278402f79b4bb96c87f3701bca8f4a13b79fe0563aff691de413caacdb71eeb24dac3f8465c012d6092a86f0ee577effb2ccb76a48f83696dc253144e23b0effc7a02d4ab0c7b76fac9312f3364db375b733867d47249b7f70528679c660f3925e4057b9145964af96a56f50c972f1e73a4bcc3d181466ece1e8947671f1dd856e4a71e72834f1dc47234ba058512587d970f09284b3d932433b51b08afdd3285418bfebf04fc84ddf70f1159626f5d31757f58141cd34f4df6fb1917a861fba3cf943f96c10c587b31f2ee7e8c4b3e18094aaae1d2f104623eeda9f2b3231a8816293c5af0bbdb5222c2eda29aa5cde982665ddb7d4ceb7f8c3c649948ff25a271d4a1dc52df0b3ef753a444006d25eb21a7ccb1393d9834b286154a01b18ea2ab869dd7ae0ce4537799913b3418990a1e28037e422d844088b8b9a3b4021394baa2214da8bf30e0914996071ad25d0c3b8d8c1cfca5a012fbbdc2341f20d909d4e77ea407a9b997fff96a2256cb99c464cd3c90b9d25887acc3c579c63fb80a6eb40a0dd68019999247176b662fa54bf05cdf626fb438639b9ada96bfed873c3bfac55fee82a36e73aae27719279ba176090cc021de6bfc0e19acd1f9adeda4c33051b828fd3e41da347a4ed47ab2a4ef5b87b4fe1ba8e88ce6213e2eb72b367a98f30971ade4172dab8a1b98f2e17b0ba9e41f0e4aed198a91a956769fa0b6ead79a80a7805458d73d56b755daf7bc48cc38909ec98b0651a328be37c40a98f431393632a110ca70bff78d972f919339fb48e5a4869209adf5eda891901c56cd8f6d98b0ae745fed23a4d93a0fab3563eb5bb9da0a863434949a66382a56b6b9089938b62df738f0858015b070c55570ff57bc30c76f3843b4d118a1ec380e24b1136780059d8225337e3503f396eed1d66802c7a80dddbea735c40595d9e364805bf97f3478e1ea74b9743d0174ecf1f094eb2d36527fb3ce50e330475907659f2d1b5af087512289b7d44e7c60b09b2156ba6a7416d460a44970a9d989ae097c1ccf482e39dd484ceb66c5ba37d232d5bc88fb2b1ce159970af183fa938ac93931ae98dc04502f6625d86099bc9117854cfcf67014615e6103fc451fe5eea899095556408ac7417db7c34aef0ecfb6d438298efbd9c5c0a8d433ca061ac1e4cf57cf6d568b7381cc8d02cc3c4836aa5548e18b8c6bcfb8cc678926b13f5880dda056f47132bb71afde2ba817436fcaacd31b17e6cf2babefa5dcbf5f210d9a0403a516bbf333fa9437d1f52b769aea7dea53fedb0cfc4de8e67d27b6559c2e73b4b550d78ce2f5a580d9520779b9255fba16946d2be04be897c5333ee0a5e1641ca9e99c10f8b6d1ffaee431814e4d1aca383b4113414aa955aaea8cdfe47389d8e0eed2ced934cc37ebc49f70226ece570d060123cceba76d22100f0189d25876826fd464f4b65f99850749483f1333c710705b76aa7208de070c1a1e4540325bc288d27efa3a0a5e09f87b671f727b88d24bc459935ea295c6453729c5854093f15b93f537a04571ad67888d7815c49b59784bf2029a8e998f5a9aad96ebc66db4104687c4f0f3f859396883ecca4fa14607b1393b13d65ed1d89c6fa97a94f7dc3cdaf160971219e655949993e090ba63b97287000874dac3aa74f33bda1eb583a7c20848c71b1fbca63217deb4209a1d52dedeb7233690cb1424ad10dc0e5879fada96a000cfee90082cdd5f901c7e7175aaa1aa8cf03826ade0415c734cbcfb3aa520cfa92dfa6a1c1c378335095e7d7e4262c33506a03810c279dbb9eb391e1c819e649ae96de1213d3f46c762ed53386ea74696ea6a412dd257122899d0e9b04dd15edc29fa943bd54d0714f68a2e789b564c904bd9f892b0e2b1384c887688c10266effbb8bc8e80c22e444e48fa9e0c3fd914776b6b7dca4acdbbe91142862a8bbfbe123ae536e8c30d4fa42484c4e0ac560a89bbc89aced1796af1b57bb01a2376320ebe48a61b84d7dd7902a4c96ab6d5e047888524ca25ce5b7a7cb6ac4fb8a2d227630de9b6d65c50a1dd9faa69fd00ba08c5ebc9140667e6ac3cc971128597e78041ac2f0420791c6698c2bb4543686c89884e580a9736352a53fe531d03ac98a57341ad66b4c078ecaf8de658a3a4c82409a75512273436cb31c518b962c298ea5026048aa4c77204ff779461ba57705590cf63c11d395196f7cbb9708987cb1ea5c11a046fdb8ca0605eb09646ef7641e5d8032cec9e66020f46f19c76e9e6a91962fdbd018a922c083dd629078e8967ce7a06333f98a49a6ba2c218b666b1886dd04ca4085acff989c3ce968126e4b562131a6b47a4e2cc91b6bddb6bdc218c801c8b3e7ac66a29b526fd49b8802b98170f8b1ec58d4a266a1f86fe2ac975970a83cd6308aca1a513131022594aa68d9633e10ecca19b59aa33d5f23b1b79f5352f8c9910a12995cb0a2fb2e2af817f20b9493a7c4de262000404368f979df1a62a8b42363eb16bd86827032ebaec748cbe73c690eae78cb5521aa46f8a99e992a433a4f72bda591d564c3294feaedb0e7f5915dfbb326a8495c67a254529ba16484a183d8615e25442cf3915d73a9c06d66d684c4cd6338231373afb8118844faa81c8ec8f01903dc162635773d6d61b5c8f9ce0500b989e699d75b160e5dc89bc716ca3895d867f39adc543bdf82263970f40a32634eb3327d8bd63aba21f6984effa4e46093759f63aa82db04b2f4cb1a216a970227620f1b954715f60f7f1b9eb7bacae0fe17144b858c3772ef4dbf48c419db9a49084870d5cd71ad758e9539979c232632c5e9c64da76af66c17fa7f4596494a0899c49b379ddfec460ead01ca4174f4e0392c1518f56b8d0af8b9d22752c36718ce6b063fc351971938514b7de0258819b4d31777e1744fee5aa70cbf0a31723bd88fda28963b2040e41d14d744c772832fe7dee1523296cd323bcd84071f385715207c38451eda2397ce11211788c342b9129a93434d088670d26252ce15544cc543f94be2c884c8532d12581d09d46ceba3513d98d84b1d6f3bcc47bbc1b92b34f9f83726f2d0c4cfab41cfe97feced33912713e9f00014fb22cca4453f4b81a779444f43428b3e8c2b8a071df96398c9076a1ee828733c7627390e0ce558eb352efc9c4009dd9b97f8dbbce285f859a37e5176f0bc9530569d6c3b5c02e6404644192dd2c6c0b13b521bfdb1b67dc171bc6004e278a2b578b3edeb9085d0223d13febffd90f03a3462efae350a919f9328bb8caaf7a5218e789440a409ae74ca6deddfbad54e8fc984406d3a50cbf1854f3663006097951fb3aa42ba933719277461d8b2d6cabff389d2895f5064909120a48923be2c683a1c4f68a03f0c09bc2d8414a658810a2a04ebd77ab288de4c30a95490d0ac14fe5b895289896490558f63bb67c5216c5173e4a6e40ebb46ee30c74b6d4843156bccf26c8eefdbc947a2768e5d36583ff0d74e950766e922fd38e43f5a2933ad69f6fe762e75f6c9d5a727b4002d811f9ba63cfc8beb356e86e95c71d03799ed981684b47f8b7d310b7580dfb4f8e204e797ed967f2b978c7e2dd43eff3d0786a065d928d60668cacf90eef34eee9f09d4fb801c775d6860744eb51d094905d8c7911adfe1faf2c867fe4627d77fcb9914372493a8dd003261f46b2696473134c95c371dfb5bc39909bf9fc390d0926240f97d4b1a69508e58763d6627c50b7f135389519b744bf9f04decbe7e7ece99a4f4c74509164d31c7a52a7c0d1b88f1c43da4554b8039c441e9c675cf034023555f0be2529cbd5c248b5c8c9f2bfe3f016d50b9355f41b0c942d7174328f242ffe7f933385353989d402cdaf0632ea7ea703b44ac05576fc03130ba251ec55af084f5c078dee0c60ff0564b820138a75564d26b96cee84358295560a0877d3802fd06521ea252904433f201767caea11d05677c2d4192ab5bdb6daa6c0df6f4e7c6cae67155284002a3c83844ca6a26b52acb7e4be1a464e42fdf15c9e81dff6681c3e5b824bf34b3e4cfce2ae58b22a4b07d85ee104d9e9eca5b9ff326d844937e396d94bb25080801d92ce8c64da8657e4881c630b95fff5fc786cc3caa9ed6529598e0fb9045049f3db5ad0eba7a157a67a9399158ebc0176ac1b93da76c61628f7954ed92d5a371b9a0104752ba50d4fcf2ff575da5e51bd8b01c808a37c13cc38682e7c37193dae9162df1a3440cec77fb02374c4ef83893db3d79009609fa70859a0f2f8c60a1d5432af00a0533185542bedc65a93a173988ed74f4558eb35d165c4baf3d98b3cc1b73c9582cb8168574c3352a3e6e4d6594f8ab17fe43e9eb55ce1d5ab9e208ac574bfdf2801e45b7a888efb056fa4683d11bae189ce3f90c703b7b4f170b0a2c3b6acfda06997c2fb3c52c0567fc45eadab090f713e7c96ce88180fbb424a2656cc97b1e324dd855576bd54733c65fbff84848c8c170c000061b16b805f5999238343fe04ce5c28a8db2af0a120578b992c53bcbf994cc6530a0f045a316640495df2310752eba23a4aef186368c4e6874984e648fc68e81a87b54158a1b87477d7f2cfd8802a44942252f0b7e54a6e3ad34b8235ba8c3f01313cb375e0e7eb63c0e31e91b8913335d777d912a5201ba496cbb2313a73a1f96f6248a35771cc67622c1fc59b84c1ba040cac4b26baae4b26edfb65b50446f4574b2ee5651d30117fa83f32db653d04eda0fe4f470443ffb0262eedb92fb906b3e97ea8b365924cf75c0a28d97d3460ed23300c29a3a08a7fdf97739c1fb210a43652127e66beece852db543a2886e79d17524766c719b4b0e11096c8819fb6c10f14bd9bc019182734ec0cd1a16152e3e5c31f78fae7c85d4693fa7ea1d166520297de6a0fdd8cf8a3c139ffd61ab4b43279e381504e13e44252aaa415605cb6af99e65c50c984b618ea26dc6f3705d1e7af48902d40d764e8a58fb917ae5cab83b27b20864d60e05036801b6bc8fca352ed0bb99ada1667f482ca0bc13626edfe16aa61c214ae32fa41d14330cc5c6ba502711b32a1b2cfb96cfab76050541bbc2df58e661de408eb3e2795f012bed2d0ef3cdb975c98dd0ca3cc4540ed395201f327fa1d19e969ef418263404d687bdd35d2e413ff70d77785033ac450a1e314ad84de76e3c124eb547629026149c2d1b34a54a5e0710ad1cb261116c2a94e891b5bffafee94ab3b621ea7914986fbe653422829705f2ff99e13aa443c2c359001005e48573e11419ff9e4c5a6462608a5952c3e268508d1804745ec4ba9a536ec7b9eb80b095b41fc12fdf851ef25b07fbc8dfc4cee522ebeabc9f2ce22326b02526fc04d49d410b5c50c9d75f851d599ec506096fe00ccaa78b0e9942342cdaeb56295c2e96850906482c029eae0089069015b76e88056f4da7eed0e9c12a01e0aa626f6bd4bda0b6f5c0bd35101507889052172739739cbaba778eb8cae9a8bf9285178fb0f08bfe28668bbac2e71fa5f9ae9c07f430fdd8bd8233911c4620a9ae8e1d15d032aabe3d6f51cb44f9c0671b8d1f4b6d13d629b73a735156ea11503ad5093bf217f1236c236dc2d1acedc3ba4ef85a9c00e319c618f45c205f5a14d6355653aed2931227a44ac88ac8a0495a9975f33c94896424ea9c145ec261f08488141f957c0485acd0007adee764a0bc3503c90bc5b0e9aa9a4675ba10552ce51ecd68b2c7f73c62db2f985ca03b5b0a0ada0843a88e177cdb058306d2a94d5d090ca9768c269cb662e5a6fab24eea2a4ef6c3adac45404b845cbb83bbf9a938edc73b12ea383beaefcb8de89887bf343338665787749147da4425996e118141ff49c1020aee76c737216109201bf1092508fe2e9dae6aea1888e32b47fabf2c72c15935827cca01bfe216fca72a398d4082fff44d586a12fc3c1da55579a29d0a035b5b717a9d68f893ef5e1ef069f78db6dc358b164b08c1a913236d052918e126a14abd0a45fdeabd70307be58bea683f077985909ac83ba4651ba6418cc258bf4d474062985218b9ae2fe170f4ec737106d88abf10499c4bc31c8a0d087b127a1fbbf516659c39110ef6d9399ca225abdcc1b2960a2dcc019253bdda40b6fe669429603c2aba908744042baf76a32d1de5e47da17c8d717856856f8ce89d14b60ee827baf4f93defa8886dde897617294835bc158975e0656ddcafca210debb0cd45d2ba9a8126b07c5a5485c3380e248d820141fb444b09f3375624f60a8b1a9ddaf1707dea38f14a931ff9d6968d6b1a2110b608a9fc876c0a4c6e0b42222ffce37c2cef6780263f3d738f4c874f4d5aa539227dc7b9378ca242bc4d17aefecb03eacdccda94e428f1421469ddf8b6ae100cfd392eaa47ca70a0a2ff990c92dc19c1629a0315b36b25c34547f0ab199e4279bf724560b4ee55471d576c1d603619561921f74e0119b3d023a8729110165c572a9dc72b1f1e4775eceb46929241f703d5719b8fd108200923acb7891d04d6dfce6463554dab766dfc35e618c3143e80e1aa8c132cf6b74b8c60ebfbac43defd8af5bb7c93631fcd4467cd2f403ce0abc8da461ab48155e9b05ec4f2ec2c617fbb348b1d58deb34c29cdd2722b6fd911d6cf4fcf3f21e43213023eb2636e77803e7d4ff3356cc9ebabd33ed801c344dbc5d77ab8349448f1b806d375d800e9d082e9d8516a81aecd10e8e1a91da89b4047a696a0ad5b78ec0ad8a6cd381fcce260c261b894fe3b43c3cc4b12b9dcbbe2f725d3f262c15c85bb0eb260636f671349f80b22fdab6ea0dc87492a608774d77f7845f0310fb61fc8412c5f2941e2f634e9b1df1e4b1803270a9d0b1f21b64237859f8118859603a3bc5403a598f09a439aaed856f98239638f7504d6310c3d6533b55e96502b34bc16bef85b68b355f4f90e388cb4bdd9dafa9b00e706a4dbd0f670b75514061a3bd41928bd4d3d3b0505fd057a846f4fd63ad2c8af85eb69df9d0baaecf33666cfa24850a935448d071159dd843a17d4b352880e92bd99aacda9dabece1ed0bfd7cbe2c943f1216b210b0187b3b380693643147ed03c3d769977806f938ebd829c973beb19a511b9b5f7ba364f2fd59dc9763ea4e72a3e12db0541373f5e6765c74cf3195176d789ca53d2e253c4b40bfe8807b52a92edcc64216de1aa5018476821bb36299bc48e659d143d4b9bb5ec20090cb1607b868c7eef5d79ff1351b31e8ffbf56381cb89ed3fa4b45394a7535be204c6bb9e62146b64a955891acb8ace3f39e87f9a92a716b29ed52371dda5a401604059ae69cdab47a9e5bf47052f7b0a5e28cc535cb20d414fe9b574aab600042036aa1651c5b44b1f7c5403aab3880edbadc12da65f4bd64698e50b97bbfb47f14b4281382ec4a04356cc4d281c137aae01c151ebef9b7ee472bcf8cd9167ebb339dc6f08faf3c16eb6c9ac2c61e2e3b18174871800d184ca4029ac0abeba8e53422af8767eae4281394aa1c6e6826bbe474c5a507a2f5e17737973ed2c89604f333a5e5bb38666cdbf5025d18c951bf3604451bbb0a1d16c596b348a2396e32919a37e1bf6ac4ac8b790cbc1bd61b01a0ba6e061ef0bca2b356ff9c517e784430d1e83d79660f75ecac4e837d97d7ec1ad957ee8494a0a14e104d24e0e94dc46f1483547ce9a1aae8e310f8479aacd1beb19a63cfd4b557caaca2fd0ccc9b27d83dee963aa551f80f3925361aa67f9d2cd7df6b6dbe1569b4925f0c63e73f273e485c2e1201bb27c69f9b66dba765b366314f417c69b28074db218cbabd0379c5d2a9057881c1b6e5692ba146f91f5d25efc9911212f148787ad90ce33eb1b0e9ade9a707f7ac03e040e0c9bf61e0304f5f70f5da720d23b0d17d6ab141396d3e44de2675514d7b1dde736ed0f8f6e4881c4d608b95a587a12a5a3622bc905d4c1bc438b24b342d24825497b22230d20aed14b2df3a657a3828132caaee2e1f74988b517240d330435132c3724407847f7b18e6552b7a908d790ef632670dc0ea881829fe807c848f17dbf001a6ae6f2cd77f202741cf70dac279452b0a3ff0b4e38822cafd691968dd3f3a079ec8f521e995fe2679fae6a116454f89f258fd12b61610762510e10fa976eecfb59a26f12ff566e8196d980413ba403f834f17ea881b517d21082d3e047222c00168c0048f4c2901e865731f55076bcf5c432fda8ab9d69b9797527cc5863dd387f0371f7f5e569d399d7ed36c0da9eaac4157e2f28cfceeef42806733a6fdaabd41224a08496e9182927370559a9f414a69b8344011d1e0699c0e82c663b75022b56bc0e159b175e0e418ee4845b0e36077a54496f44f2b1a3824f0bf99106f39bf892f5295befaa9cd044bbd289569b515f42c6631c090b592e6a7c6f05ae46009769a7d15076353a586ea27a0935beb16e3cb97aac7ef0a6b441fe306eb91cf27693acfd6011bb67e2ba9911e59be3e6b03b273924bdd95aaf1196430c7d3f19a26f1556839bca318a2635e3210640f10f10578775b9c4efaf43815955da6b2a166c89fb0f9d63c1fc32c5cd6d8ec5879ba84f42c635de1858a78deea28356510190c9a03318830d06c325b589f742bde514be73df6d63cf2d10a3b76a7245f2c6f88e53b2a0e0803a4620bc44b869a7fa24c8cc2d136be2a07eac07beee0e289cfe3353959dfe54c79bea0805265e620c09b48001aa25194357508c54ab038044093430c588c183c2262a84b09134070019c27054190083021b900c0200c25702001ca1d12f35fdcc5c8e46dfce0c26e0cbe820b4624945fcd2bf80ef9f84afa3d61c8e63ca6565a3646b654a29492965370426041f04dca8097fb8f54e680425ff66d97974d2e4fcbbf2d2a4f7de85cce46ec75c328bea6e568035a71172efdf95c6d4a239c3ba69b6362fb311afa1ee30a8afcff2850579d206c2e9022b28caa9f3061b841662af204b389fa56e3dd77b51c61b05d543620151818c92703232a29866477476a01c67bc1042263742c9a90858018e4361433c23364c279bd785ac0b72c5e029fd5a11725dd6f510a4ba873041a019d7b2ecd2b205093bd6dd21c6744fc939e7bfb108c229d355560854519495f484744909458c4e6ce636a1493c3c24b75d125143c49b55e5c11d6d66e3a9f7decf8ab664428ab0c45862c858b03445d06eca70be05ccb88ebe6060ae604e3ce15620e299853552b3c2b862604de5b81d9570d1e9e280c910dc4edb5a0a17d98e50459102f512527241a1326299a1a28825c77eb8c274ae65209ec63ed0d5ecd094515f88c78f1600187383ea4af399b27680d353c3d40a0151f2c0d5dd9af4e81cd910a229a09d1536ae17cca2aaeacf12538836a5ee2c803a6456c07c612695e8d65024b989c8c25a496041c480b8563226a972ce39e7467c8399c10159942b23373a11ad1cb12429950d76e50d2dbba2432d65a794ec5bd7fe77efd22eb46a93b6805e9008428a6939494467670b1d5f6f7136c5fec8c98155c730ac1dc9efd1867943db7bef6273f0873b22177f301e369f27038ba10b36cc32e74fefbdf79cd3caabf7de7b15c6f8c3356bd63eacc3be4cb9cb466c40f293dedf9a75ef60de7b1fc6c4c3a6c24efc3ccabe1e60b1747763ff6cf3c4b77e23be41d53d4de5496129694106eb04992354442d5183131784ecd28adbd9faf88a993a45c06229e79cf314d5595e4b9172cef95c128b21bba2ea4e47e48ed8c61fae2ccb3bdf7374cab0f99a68c90fd99ea536c67896d29e76f25a9a98fa6bfa3b0b0b8b9265c9da7b2753a4f6de8b58a4262fe3aa2c8d8011f5c52d09c5d3045ba999caf2b15ad10cdada058551e872165d67a9f73a40a7959c735e779ce53a24e7412815c847eb2d084703901338376655132acc5dd7e355146994586a54e2c072525860c1a456a20d4dc56bda83c962b362e2b0cf3437a0134726106818360da6ee5cbdf7de85d1a4bcbb532a56f72e257b70015cd6887853e2a9f55b72935e09715587849cf3de56900ed9f32aa90edb8c5eb98091125dd1046185768a2e6d93cef178ac2b479e3834b3140e8809312693147362e6ed4a2bb96322c6c29eadf367ea73d6b4c010b23a9a59aae3a9e1127312336311010783848ae9526669917bdfa37a3f80bea6d28ec81622acc4ce4850c4684565b77316023c3877b8ae90a276ac24412e5b99ed958d4c1b7386d5d9aa6f27f15bcf9b507cb4b1555e5aaaa8536596162466e344add2492a81a48a86787b4b5d044b2f2e5c4ca74621b81864198c4b36292c8daa86acfd7b95d6d40167a084d9da8724948e2a829aab17536e85eab886ec698978561b6b04abe8c35b9b97b04be69f449c739e34dded021ae4a5f7defbeb31fe70e51e0fc6384b2570b46d6cc6e5ad3783dd9639bbda4284dc222983f35ecc33c320470ba604f5c4c49a0a8f8a30b7269f14218c6c23c325f2d69b79ef3d6f272f9f7ece904fcc1625968314f2f98325338543f11d030093789f91c61402f73d1701881da306192f457538e720286831272e6e28241f881a2a49629a45cb84dc3f150d008f14ddb6a9b25f70ad5e1451356121918840f8619210b33e5f265cbc90008bc0c42d960762ca2c425b4a6b3ee752e05c20aecc4ba9ad678b2c84cac6a4f802ca424c18e4b6a33322eb25028384000a11433d1f01eb1b4cce2da839e79ccfb25ad19457c6d8d4d88931aded628936a03176e4a7f4de3fbdf7b4b24f651f689c0105344b2d47286fc8fde5b4126492a2e4e296b400137d6025a885e6d24c8aa8b3664c08aa9ac28ae47eb27aa23e5ebc7d0ee71d01bdee3a7664f14bd2689f1cc1c2e711ee51c545d3ed52e8f24570b374e90154af38ada8899c9a07aca5a465afc8b511351715ea82de2a461c412ada9be0f4139ccd20b52bc4ea434e4f36eeeda982e2e224e67866a2ee6a741b47b2262a31c5eb43f52c6c6c4c6dcb845b1c1a01e680f8867b6f098682b2d0cadac7defb3fcbcbdf8ff8b189c1b123ab95b6237c124f7e62a121b793ee7a4cf55531354d001d68923a9f80b4573def0921aab2fa42c27495e3de5ac6049755682e8454025ee656a02244081a1aa40570bb769b6a40e4468bcaa8eaf5d202431b808a02896d0a316adbf6debb10c489bc99f01714fce142bfa23f8fe7085a29b4a19b67e9c38867e98e73ce79eda59de5bd6456472b6b3a2d8cc1fb66200b39e7bc44736f269fd1fb359bacde521ad07e8d4b63f327e26f7aeffd44de691a2b50ce79b15c035db1a1a979d59363505b5c6772973889e10b5967464512c46493158b092f37262314d765ddd15c00c11fbce7c99cc032239b73ce8db3bc218d6596ae94f9aedaeaab39cff9532e7bd1f16663018a01b38d53a4f15535aa116f9031c998fc512f8a565e382156a334ca6147b6ac584b48372748b24d6b301a6e404bbdf7de5b9af2ee7f2ee9d1e8cc6bee9010d55d49bf8a3c9c7d704e579c89bcb58588a1ae33bc770ca82b97b71b1b298b01b850526ee9549168a1ee6aedafb43c3cabed907609694591f4cc24396496d5626fc67c9c7023eace16f7defb3219bb6c436e6a504bdaec11762cadc5696c0e690d2abad6f1632117ab6f8f4a96424b6e76626936fe79efbd160fafb5867e1dc4a8c6a66ac05e2cb1609c050df52c4b24290a49cdfb27adfc7165efbd0bbdaef2cef147632e09e8b29fa9ecded2cf0a65855aa3d6a9b56abd5ab5af908a74529bea7ec439e79c7320e42c9320643a4733f35cb10a39d69cb7a07e80b2d5ac55695dccae72464a3d291860b68cc4ec34a32f8248646433d2567add047d4aaafbdfbd3be917e22cedbdf72e64b195b775cbb1d27fb0f5e7e5bee39c072c272482d4a6382f5675702f1742f466ae316c14da73c9d82b05958a6e79962e39e7bcec5897217fb3ccb17ceb9f1a9cffc632ac6ed435ee7289dc68c76a2b3cd4025a59a28ab104882f9017eb96f64daaa9047c037befcd4878c6adde7b9cd4ca2fd7918b283e567b3287c30d73b25c8b722f825c4820299e692991159f711f36cdedaacc1cb9bdf71a227e378a89d10b406b6a469e56d07222045b34222da72d135542af9d9503ae29be244e534d68459b2fa90e2e395565cead890f6c4316d0603cbd6d5e4cfd35f58ac660074c4e4b203aa917323ab0942da599841c6a6e97ea0e8348c21b3088650727535f3d5a605d67cb1ab6ed2242471626ab95fe78c1c1c75fecc9bcf7fe37c68521c69039ceb9cc39bfa02dee92131312de06d36f35b99073efbd13e30f573eee7461a1a4288a7c6458dc9c4cf0c0515cb1ac0c6709c5cc5807676a02d544b5495191705d394965386969d021c7a2b222546851c032f3143a8d4ea71eb1556f2983df53f4de7bdfee8447c49fbc97d85eef23204146ad40b281396a72c969c2791a414191963c8bf1defb2c2bfce166b3da7e1eb0d30eab2ac4bafe786fbdf71e9577ef1d8357edbdf7fe7c41b49cc7cf68acec81615b8a207193db293adda0984d5fe8d88c30ccd00038b860b74ea704a982e92b0dc963aa8ec03a7f92716a5aafe66207bffc7df67399963c98e221bd5befbdbfa07d979d6419a779ef7d0b63fce1cadbf54befc9fc512f8a56765ab961eafeb3117354ee60a6ee1788271108a51d1054d09fa4220b0c8e93d3071d0e3aa916244136402f4c9bb215142b94c4a4cc54de5c781f004b73a1c4829d389233c2c1424936cd78ca429cc1c60c2e233b51d22424a62a0eb22d6ed814404e70611c708b90c8ca558869d214e3c044938f928ac40c915a881bccd2e1759c1fa8c466b413ecbdf7de6c4350edc927d48ba9bd127fe4bdf7f1cfff24e5b6585e6eacba6e9c2e48226b6063642314e015efbd47c61fae8fdff094fc31a912892b463dcd60d5b5a55c5b6cac3b9b3308355cdc6062507f5dc92b1d9d1b29755514852a60b846c4254e1536b0f77e967ebea467a071efca904225e10197b7de6cafd83645786ce52e44de2c9d8fc620222a231338909f957d33b21bb03245f2a12273e860950d26cc43e4d829c55ad377076a9ea53c2bacebb5dcc09450139651738e8aedb89375d74892101db131644d704a0b8e9f5cd2a49337982c594d691a9d96a102354b779c73ee45dcedaa38e7bb0dde7bcf42e54216eb6fbb2f92474c2793b1afb998ac8633a6ae87891581c5f25614c2287d969af8a1517d6f32a3bb8e7517f2472b6938e79c733ecb5cedc55a76e4d158cc71bfeea19c33ff09b84c24af492a5e5c14f0026d82e2a9330a726e4104e113d8a47be974a44c4a6cc431a83bcb3857f73e97f4defb75b78b1fa8547ba018dd7befbdf7de7b33bf6927efbdf7be8f04ef7fff4b6b00ff97eede0f61634d7f1f90a6396cf8fe4c6b001cbe354cff0e0e1afe3407f877f0c18dcf83e18d07418d1d940f98016c009c61ff19080138c3fe1b28d0abe120cc02f7cf81efff017b1f06f2be8f734fd374f07d3f775d588e33a08732c61bec79d41de4dfffd08307c677e90a600427fc39b8218e371dec1e3c78d45f9ebfdf00c31e3cfc8759fa9f7fafd7fb9d96408fb7a1017e7737fa202b3757ffda75c669e798ea0b6ad73957ace3aa75016ad7092745d673edff6eabf7b55e40ed3aeb96b5b38e5867a85d27ddb476d619eb5eed9baa39683ae3efbd7770e8bd87e0f7df603bf7142d010fdcb404dea0bb270033be07c27f0642907f0409152858f8303d015f70f87b83430f1f1c62d8fbfbf6f7f9f6f799008e811de6af827abb1b3edf06b1f76d4cf0c37d418309c1215ccfffc254ed2d042904879fae4270e8e12621b8f70efaefeb490040cfdebe6087f8cf7ffbe0706fe0f05777800215ec779882daff37b8a3f510fe26803becaf82da038771fdc67ffbdeef1b6ea8bc31843f1c07877baa867f0660848c5d2dff57fef1f7a021d8c1fffd65ae77244a3774dd6a19eaacc5d0fdaae5a1070db2fcfd1fbe11feb6c0ec8313287cf8cbff3556f8bdefc63b8c1ff07becef83430afc97d6607fff37803190fffb37d4cb00ff5f9f0fb8a7867baa963f84d9ff37d8e1fd105ed9e30383430cbfff83c38ff12f042d042b04df0fef0ac10d7ffa3fd0f09fa639ecdfc1b7fddfc161093ad84eff3dfcfff98fe1ffce072b047bbd3608c09702f085798ffe6f18aaf6dfd31c20fcf9c3c0fd21104f00667c09c0073f0250e36f00373e8421861f70f83180430f2004a0bb019cc1bbf2cfe363a88717d41d03ff3b38fe4b738833be1b67c041fcd17fff1c707088e1c7717f83c35fdde3cef13b8013eaf70f5f8ef7ddbd7fc8e194a9b2f57df71bf4a0a1a679ffa5831d785ffebeeffbbe0e5f50e6f8be2070c8b1f139883b060cff7b78fc7e5dfede8386daf7871e34d4bbffdb080260c7a701dcb24c73f0fd0e3b0de6daf7ba1fefef199080f59b7c83430f1a6af9ff763e8ce076be16f84fd37f3b5f06b8353df51a5f1838437f8faf713b29bc16e8dc63fce1ca1f2eb254da9048ae575072b507c11b0e110bc44de645e879607989f4defb1f2817fe447f7e9672112ed3cacc622f78204ad60615b39592c840734139568950c8ac556dc03bee4b35a63af3fc97cc3647e6280742a2faf4a60406b7553c01eb702971e62d252d68ad98d86a88a28c239848c84884ad3d477461cfd072768ae5fc21d94b38e7b3f4830472356927282121a64cb055670b0f59ce985b0964073322591f30aa9a21b52989921473e5c625a4ed028c03e4e3da244fc5f9fe35216003394d51ecd30e888f61a8640d9ac50c1182e089222993180000030014c491304d24b9840f1400042cb4b89cac4424128d46038138100483016030000c0000000000001406828100623c2364560340d5f52e94e94f9137261ed65cb83b49a8fca5785f054b83872958f0532f7833bcd9c98b02dcdef87c6b06ee61e219658b59d0a7ac3acc2ff84a8c5ce1adc54241d35dc2b6cabe556588d9f07d35597f3e84b1914eafc023285d9f1277665c3bc1c5985379ceb6588806b05f5be87c68ffebf415079dc0d5477484971a680b32059479217a76deb2800e6935bc2dd24cab56597a32cf3f327918015193ab25f38a1df77076adb3a57f59550496372917b2604b8f3fd03e2c7edccedc880d12ce6271adb003094abf92f21173367581c09b62a4c7a055a3cba7bdb102ea9485367fa12472e19de9bc8c1efae578450d6cdbb0baedceb1bbca6cb36b66d1bcaf60e8a2c45da38d5ddc055e620b4f9845d7a5f24056c1a2bb0dc11750fb10ba850151f7928df7e3c4cbf5379fac51b212f3b7d3c09d3663111f9ce83a807bdc1593f69cd2cd52cd3d78eeeff6824313d7f1faf4e2c12fb8ec4af41628d9cdb9af6bf01b39b1ccd32bc7486a8175886e70b3d026a3b1cc319296394bdc22d13c9d8e1fd7701430a42dc49fa2b17077f7cd564e9d70ea6c88ce65f64022a605843bdeda19b9458335e8de7fad538f3bb351ee4b2fce945db92da17bab6292e4b67e5d01d215d3d4307f890b1112702a1a017df96423cc3c44aea13ba1f65a025d6348c324eae701c72f30528407eb949a517ba2d6ffa85b926e7ec4b316525ca9e6c09596b7e05dc4b2c8c3513e5616892fceb2e26e066f4b7e8646a0116ec5e4b0f1d22d12ed87ad831a28a6026d2231d64e0214bf94a7ba60cad87ca0e025eb683ac21a98f228ec624bf381c2e8eaf5b4aa43b821781777b6c858008d452a856367299c6c70227b71a5dbd257987a52d6d198ca15939a9ddc97842f8ff2c284b6aed9344c26c9dfb7b3632baf250863ec686b865d49de383d25028693d9bfab69af8920b1364b6b6d6f597474471bb8db0d0d233377379602cc93a5416cbc67d26c0fbf37d9067771586e973deb3a33cedc21145c973cd8b10ef54c163903cadd675fab45150427d237dbeb47f13680251342d8078d34d4d7aed33b6de32f07123cc0f7d43c48360f71f37e38ef287d13116b7169b743b4f3cfbe104d63ee1b53a6a5b715c8cf2959b561174597b6d33994199636f62c61636b1b5d23affe326c0d66f8b00e98b67b9d537184d88e1968f1a4264834d1b30d6f92ecf3769f9902da3479c2cb7ce03adc6ece11d532e43f1ce205831ab0b660d4405f61d8ed5005740a090a4be756db8d8e7d9cb42d2719ccb6f8a3122e92615c0f9930d54dd81b5bb1c4114ccb4dd18d82676812de5080f499ade6d3b8d03ab4390939d2f9629459e632dbd955366ae69e491dc5ac2552503713296630b904287bedff64cb81d1b275c258509bd0f27b46a8e2359f1e12c93d9bc5edefb46b6649da9b7a861ce710398e50e438c5c8f1e7c8113c45f22cbc506a6c99d640f66962ba9ce51a75b52997ee464eac7cd5eb547e4a9cbe4a96f6ddb61cd476c4c93c926b98b988f605df7472857f82ec1e52421432d9b5424836bca7b6f16f223ebf8044509be4da5b7936fe9ac7198955226eb9fd207ca0d7862e5be98f729269da67c639b00703f600a2e76e784c8f6383ab8b8f95db22dbe081e24dd9bc5a75e5c0f0ceb13ecbb0ef75dba130e003f8791889de81b989d14b4856b6caf201fcaccd83512df4117c07daa65355a7542b0f10e7dc571ded2c07e32186dda8a363b610dca2c2c515262f0eb85de6f961d3e00bb9d170f6a7f47e73ce1ab68445dda59cf6ec7937c5e81937da019e6b91b0be5b0daa3dfbbaa32646da39f1608767dc85d500db95723652fe0697555180fbc052ebf2d4e029ec4badc1b77613afd8de7830232a19f536eab507fbff2dd4aa0900c445cd716bdddf46eb684947afadec8ab11e469902ea4d0f0fbdbaa809e6efd3206210c3a8a40dc05ebce81b1175842ab4c968851ace848b15a9c952d00ab5790558041c6ed7043a624c5c3da4101a6d3cc796863f8789397e518e49facd334ba50a27fab1f49097b89341d025ee455afdd06e073ba7b57ecd2f12500a28156ddea4dec30d439ec6a28bc265674c223ac81e5a6c40f85608ac964995611747ed70cd5e9011924e04b8d4374e60b27cd6eccda7ab738e17b4fd99c24717f1dc3d7e74e38c3a1771020763725fcda9b1c0b1ec95083c9215922f188a6bc6ca54a1f9944abf5280d811adbc8a0e0dda262b86cfc2ce503307a39e7ab1aa2827e91b9a59c6e1a25ea042481d70d8bcc984a46a0c812e58e01f56b349802c0201b66d93043b1eba8157025866e6a3fc512e02efcbf5b972665821860f3780988c419900332fab7ce3639a8180590150cf3db85d7172769709f881c8e373b200eab7e583b3a893fded658bad4040f04e403438ca3e4a53d3db6a864862aca2d69da1361185f2a32be9d655a0990e1888289fa8f60b7afd8cedfee2ac4f68e598287ee82e2e09a30c1c390ed00a5fb50cdb5452b4ac554d10f1b1baae6ade32196edec0fc45f8cd8f379b9822aded3e06789b8b244246d33649071957b2c1a0e4401a6075fcb1089e09ed952ac3623118d9d37877857dc71f2783fabe1c03927abe2859884dba1711600db13d1431fe42cf34548ea5e5999eb9e612453d1fd6a1ed66256e00275de3d0eaa2780e99ef897b4a8dd5b6409116ef652369bb6ba27d80dc1af70c64239b1c45aa311c0bef0525eaefeeccb68c23c947b3d4060113580aa4722945b15f4defffb218c0629182c4942b01b38d63089af83174d6c2b88ec9cfbfb2ba4345d002a9d13682dc4150df2d43f5d3bb65f8d81ff07ef4817354ee12a61b8de6dc01be0c96209b03790f51ced10df0c6f175751dea8b156c49d60d86140c5fe414423cd18d94c31a52542718fcd867e972baf52457767a7525f576fcb9f22833893551fea4342d0cb141d9af4e1443929cd2a42be9b01fdef64f5ec452ab38585fd749200d9569b016a60902bce7285f07210efe5658012b9cb7dd0c556d2ce22c8e95bba95dfa5d1cbb32359e94857b361703f32d7d6a956aa8cc752e3ec767c09e96240d2a158d29600a0db90885eef383bda0c78e2d59d4b6d6e87351aebdb76325470ff94d942c070f464262e8f708f4416665f3f3e38f399f8064cf73433406d251f3db1086863367d94c4bec326166a3269386341610d2a028f407f549df02b18fd8a5fb23625924b62fca9a6a5f76e626513e6f7d9751365706a7b5f00df23c8194bdfbec5239d159a50d4237335e6a8bee3268792977ce3c5b38269d126ee01960a75f233e34dd40fe0ebb94537726e9fc1637a3348805008c03a8f0bec4823007ea442455832d398fd68a87865f529d622280945e0e312db014c17f974d1cc5621c1e11728f3475d437f2183f46029b66eaaa2d1740359e181b9d2e9d94f26127d64bf6279605335a6ff4ded4af96ea1b5da1a62d3bda343a41b1dad80dfdcec8a67627b2f8cfac12f91f278d83de3e2b7eb3405be4b77a9cc59e32e2a3dcfd26f55c11a8805c6f9324ef5ce2253dfb8e07dd24eaf2b3bbfdd2c62c32c79c9e8961dcb952a1b1ee2f69dda8ef3d8f6807a08c776b250ae9063d7975e4d0ee5db01d86393a5c1a3e7ef20f2394d158bd2ccec67b714a782bf7a5f35eee29bf0ecd5830fd10ef528c038da849534043241972931dd3943edc7c3c15109277f7cb2153c11123bedf05aaf9263e0e2ab7962125e585e3fe02e022210f6bf19aa52cee19e34236edf9381e65ec9f838017f8cbf98f3806aa806417dbd753a67452b75eb83a53dfd3fd0d697a7c075611a9ee5d3cccbf9c0c4f30ea8a2be5e4cf7784e0166893b32fac12a6b351a7464e7afda198809155b71c1dbd50d97814ed63e0643d6b8f8a67a320549257af10a94d9b0bf7dd4e4c98545c94ab6daac1795f9162192444c5e8dc1e5db0bdc939d8dcd64118c44a3940f18a76418c6692e87d17cb28dcef47d33f82b86e201b0c1a617d4bf5134296695184431e97064f1f1fdf3f3dd14bcdf27e02aa4dfe0639c306bf0009cb5a75f5eb7041a19f47cf40452c657330fea246d4d6ff91e16c2e615651160cdf96655629b1eb77ee5c055a3a787b5e4db7e34d1d1ddfab92cd74ff74d5731201e884845e3c88aa5e80647884f6a4fa2d66a175d7267df207139937a4468c5535829bf8c4492e81f48bb448e7eafd215e0b44beff1abb43945b7b001a22f228519d25ae79b48a0813154433ca1e0f05409c88d0f1fcf40f863a774855320e7f9c6943d3a5d6501f7dd74abe345706adac655a02b610333b822d6b1eb1dcad670fb2d680809f11ecc1e23acf663b49168280faf4d929b2c7c757da3c1ae98c8205585d60bddd48cfd0c50dd4c088cf65c9fbf9a2f89985132caf2105cb633cdfd889186b46194f34599bf780fceaed3c5c57e9c47657d8cc0e1bdb51c29aa8cc7dba8e1cb98c81054032ef7661f1be28e19891bf4db51e7bed951e48289a9184513a16c20b7d560e0fb2552c308e84cbbbe9add9e6c0684204b88936cfe74dd22bf817bd0886728ae6c322aa6fc550cad3c0f80097985451324d2c202a29af043e166e49c3c1181329771a5abdb5f9e8f24540747915d3062b04353e20aae3c7a380b0f6b23aebeadba2f33ef2208b2ab9905837049a491d77aa84397063b48a8bae90ab2cd3bfb6436206a0328a4c749957e790af914106d59094188018ab5c18857830ddc4d131e44926927253a2115da40c84c0c139a90b0b9def0d4e74de065022c340b2435e65d060eaa4e40dc93cd58ac49e8057edfdf520ba99146396c780e5533b7c76e2ba7da9ad713d411a036e717a313aed1a672d6e3d7d4c6c389dc726aaa3d206b4c6354fa8e2b3537550cdb0b0481e2abd9b529053b9dfb4a61ea301fdc713b058fa559d6c7f4a7e17b308429224a26606b9b838d0bd85390570229b293464d7304010854ab46c2e21aa5ae731c47a0a2c1dd292a4fad858605b2523a2c1a7fdd0198cc01d3ff993923eb022dcf4c4e80d46822631135c5f48f49662d0cf6a6e7f9182a772cf346eafcd2d4c62e7c69d44466e100522ac8a935fe0e826e9e8dca9357e3aa0c7f341270fa857096d997fab5dc0adc792515bbb305ef164ba53ff60c6ac6e005e7c6a6793794e38557d349f5a251240b5f70d9713fdbf01b3107ad60f474641c88865033077ea1ba625c361550a11deebaf90d0eb00d9e76d5267404204c584d7e4b2ea0f185200f1a60a871b88e57588f5aa96763160c2cb8b624c47d9323bacc630aa174af4a68e0cb0e5b31d0f5892eb3bf1aac2375415202065aeaf2c2a9ab9a648e2e1237bdcf2e26697b516f6714de19458021412cc22b91f7a350ec318db77f7f7c342bfcd320bd51c3a26f126eb06c9665599b5825f9b11b765b5f8e3d5e8b2aa6167d53de451b8a90f658a110bd9b89116fff18dc720078167529abfdccf0818b6f2b45c2c386c297d20146617f454f90ca93d847ae06fad1f99084fe32ea5d3c2222798740bdf6495c376a0d2a884e52346dd38a2ce4829d8c949ba35adc2172418557238405c6907f3b07860f9495801a3f43b8615f1c3a2fa4eac4184ee4f4093a15e4b44a300f6b4c2c308720d9bb7a8370ffd04ba3f95b1741e966c5ebf893d7dec4960c94268aff88ac9adfe7ebedf31e528cb24a95248e65815b3346e7a26b62fdf85aec665f2359a25b91c65e289699564ef24ae0b29e95c9e81e4208d12298832152b12d062ea403fae57f745d4a948834c8496bf0619cb7346eb92a53b6e655e908503e5de614c476adb6acd1297af67ea515280e532ef24327bc2a8a43c246e9080497c38f731470081459017dab6cf218ec1fc2718e877849fbdb9799406fe3c4eaca091e40b81cdedcc3330fcbf3d9beb4eef9e7df97c8da1a65809f58dcc51df1051d5a024388934003cf72c62214e5c30a04aa0ca5e7a8c3800a4b2f342e17a2809d18e8cb9e7aa7fbf3180e739ba1285fea5f3a7164c853bc05354a9e14da28052f2976b27249cf4b052f22db63471f2feec2df40812f887ac765742bdf80ed9159a3bd6676c28532adb91c445730c44e2d5fb63b3b12d07644c5b2ae039d09e6dfe79cd604418d29e0a6c915590743d3faef0a3e58250e0eb3ceee8f32e94f7f035e0abcf435d0dba41c3d077053a5082b3ed78ff8644ecd61326b24132683909e78623b8914c6564f211095747b3f7019ed44e2f596ace8413bee42ecea4c796b5739b98910b12d691044b1241e273db64cdfb7c381901dba1169f01f1eb24c3aa527c1d0bc6e008f4b9c6455108a05b174c6c685a17d351e3e866c854c5a75557f74119aa06b4d202c5ab28f6cbf67546d4620de4cc892b184701998a5350327148dd2a938b0f3e19bc18af4749950a84f8ac6bdd2a6c5ce2304e6f7324f1b51068c2c24a73b71d08143337e13488861ca635c4abeb7eaf138c079d8217bcb28a16b363a42099767e6ae323f376225593d896a8e34fc654b28cb563a506394f4cfc3fc711ee3044e9bf851c42e1dbb9d8377eae76d36273a202b3fb0896637cc74663b0c734f1fe191ca2e35094b309a7c48bd9de9cc6aa462dcb0bf284c22f26323bc0d73c5b05589f25e40403e2141859692ec231c65d318e185bf4a3b49f1c49ac4a3c2362ded0b876036eb7f380e2fe33473ca8469a07b269ba5834ec317ae2a6a1363908679a99e1df084b560347eb806c39f3ad04883f46eef8eea0167781105bc90876a4bb4ff073820ee9383d5919e363092aa42be0c08a968ec86e659b923a2f0a98fe09501e2e618e3e64ba32b644d71894232c6679825815cbff1f4c16c1c59c7ece3a591cb215c908136ea3323171fcfdb3404b71947b0ab02ad09ea13d4c783a599b82c3161b6c8e416d1b88c393af892920cb26ed81b58ac7f7c079d210f0b582d08e54ca1e756cd078ac0c404dd33d83d814b8d32bf33f827f7d33fec5b97811d7efec37085f44ca3711ca1e3ff3be7e839228d41cd0e4e15d2c5a097784a5438fc1460bfe9740a66dd1da4db72a8f6efb031a728c9ecc1d4796c88440f37f8fee3558187ccbea35c0e877788620147132424d757f5fb685cddef3ca449025135b89171816724aee2a377b31793a2bee229644a634822c99988bb416491ef9c54b34394d9ed4babcfb3f4e640dcd2659a355162a114005a9c9f18137d9353aaad173d8e5f7f7f7a5b882e9926f6dfd595f17869dfee82562e3f02f9217d19756ba64e9b9ed8eaa597cf23cc2885aed04751843d55fbc592c0b87ed18444be49983d2985d7ca4fc598d9e99579f9cdd2fca289f9d075588557a1005f097286eab6c4ed3855ec2d068034d982b22bf3f3dc601ea693c3a1eb1cdd6339af76bc9e83164d5eeb39d828508742579ec83b40b82a9fb16b28f29c0e25313cd3f2250e209495c721d0774de2a62931d01c8834151dae789a809603651d6c5377245e25285a4387e76289fa3972a2763692d787463c45c6f5120ac845366d4bae48031f1bab79517cfb1ba78acff94b19c44acb2e396385d5b57852067f1f18dded3685c1b2984c41b5e3cc02ddbeba7a1f30055cf286f1d778552bc2e96f4aa8e0f962edcda7c1c2c1313a1259e741a67141b0df394640c7090dd9ce618e4b762ea18b9b676260559640e48c1b805b7378b0b17c8e363f9a04f892e2e9d728e84648ac328553a926bd919b3dee41b5dee1bf34993532716a2ce723c7522599f65ca47ea95907c88affef98cb82ae40db7c3eb281f12531a05141ee3cf2cdbfa5f0df5ea9bf2d2a5cb8656c43dae11ec3f96640c6426b678e714f242fb23f76362370f9aa587a86ba73e2bdaed60fdb826e760810fb69ed813d6c2d9c1dc27e2feacaa44282a4ed0dc6ac86da2c06345013b8a1ddf10ee4b901049bdbe7514faa8fb8ac68c7ba6c21113be7d29b55c09b0309ba6ccb3a1e2244fac0a32dcb426a566f0d454d2b1f926350ae646f801c0a4a6d2a0ec5f7993bfae8995d63d0203cf640c2b9073d509158c70ae876e50046be5076e0341d918f9b892d0a822cee7d9540c64ec83619f0858288a16f797ce4851ded4b385d33ff0e6c407de92778aca3335bf3e5f2056d1df2081b15f03125f79823083fb91e39d33eab17401267bf2805d4ae13710cb02d9c87a109c60fc8e250fc2c957b29110eb634d84a60558925573e31d0908d8d72218a90d002a09298b4aec845306f69e5af6eb3c9f313a73c9b39e5e6ea6f5be0aea82ec5835e15786013186244682b8c215800cb674a2fdad26eba1800f71c9de2f86e0b946975f029c45578080a5b0a216388ee3a2022285a14131239c1059607a3a5187745dd5bceeb3d74813c318c9ca812613d9ad735268682bbc6e62c83e690ae336273b6b348108cd979715a361c15c77a3c56c4a5128c82cf3825c607b6b53a36f99b3001fd223656a0ca8906545e9e4b9833084ed9d3e1e3503e365db2abbfcc7c9273464a1df0fb08ac53e0896b62dd94805cbd291e24cec199102202cc62e0ff5534c02ac3ec853cc0d68b524d65d19cf4583925a9e0a8d4a59308902979ba1ff1f07cc43583ac256c1d07db9819f7cc382e83f80c05778496ff76d0cdea147f1b3488cb3bb205d91f500caabffe00329250a1cb834577726c0471a7ab754b1863357ef7ec8094aabe5317cefe258724d1d759d39ca984ae05c6b6222f97dc0072f0ff7bd1f93d41da39f6192416bed5187fb94f4ede50e351b38d5a8a8cbfad92eea57e8861269b2c8885f49ff62b1a3620c2de2d698407d146135f3b3f2018be9ec80fba813a2e67d780a9f81a05662563b25d629745c349c2fde38002b8427971999cc8c2ef54c968ac009fd95be550252e2ed2aa13e7216e12725a548003893392c53ac209364e54cad3316813c3d433e31a587781d4f28e3dcb4b3f213fd7dfb89a571c0e6b5450a3f8b5c4d112b3f0919313c12e31744f9a63e350d34726d590409bee5e4d170e469d272123d47d7c431fc87bdf6451d407c3a3f6ad392992496a28468870c39bdc641c9332bb2cf6b67d4aa4b823a6642dcbda466a8fd70cf25e7d044c202199c89cff308eeecf230b8f181b7167ff85882cf5e35e17f93768958320c701726d27af2f44e0a84aa672628fb8b4ad030cb4b45fccdb4aa821a0ee3dbebc631db5b84dd14951e83ba9d03929cd6f4326a3247f4418e1712a851ccb340d6a10f5efbd344b6d537389643d460bb1a1f24403b81a16c574697b92e94310c44c8c3ddfae3a722db2447ee4e74f0bb6933caeb1f651c65d8af426e629e8775a47b01c8dc8460fd38aae358c6558cf3034adb7465b27095451eee618b03d48809f895e23d99c35ba135fe4e4624954fd5498db8774690346c822bf3c67a88d47ff6923188b06ce35d81012a507677df2ecd01d2149b5e0273b06b5a2ba6dc7fbf9f241c3d1cefe8f2a40afad444309694151323836eb89b34437cd27311dadcb28afb0c478e5ce25b679c2c0288ec23094aabc8eba5793ab7b71e139a8e2d58d1447d92305c33412158d980d1adc11c3d392592268ca96fb0bde876c4e70ac8387d8a87edf81a20fb70322e3cad6d226c6aef9495f2763c171a0436ac52eef3636c9c22818eb868a2babc963fba01b4684a925354353da3509d3b867e4bf65f46088481af8d3815187752af895b13438f55c25c0abda18ff28be7757845b392434010a020cdacc890ba5b8ce034c4654a06252424cea9a5781c16ac1ab1d058c82451b3a17bfa0c055d97c37bc10d7d05721ccfe2419464b76c50b539b216163df2e40c68166b93d19d3b5da8d0c3f8d688e68a2c58f18507f12d273ed7f2cc5f025c1a5d5a371077d13668d25a4eab7330da72d37b14ec32f8fe54add4f043882cc4d9528c17783466c714653113b0f92d08dc51e232acb86bd91284747e3676848b5f70df05589f0370799addd87649dd5949e381a007b43650dd972aaa407f0211f3fa30d79090e716f601da643b01012c89a5cc5489836c193fad52cd8605023d19fc824c4168a7a7fdaccf4994de0570e1c4460a0b9adf5b0a0c1d0b489358537c7f41c828ce70f06991d21d78052e8690dc0451dee6dd89c02d47aa1db2474481ccf58c578d0deb9487078022c08f6905dd055883fd436fc23fbf49759990a843f506af33c9835241edc7f71ce0ab936e4897400db5a18f31b5cf2e60e327273cf960960ee2b18a94b1598cfd32c0234d66769a277cda989fd02fc7e12c592bf245ea4dc872ce22ce2617fb1f7b3f999ff59a6176d80057140ba7021a20057b2bd9522c8d3922aa090340b11f07e82e54bbec865a8d7db50b973eab8d15513b04553976f4287dee8731bd6e41434f8cfcc3f678ec48166aa9d55c790cfccba92d3e7f85942c27548c88a38acac1229077820013cbb9463fec3b9d9caa85179d84581e4f40985440157e7b89a7a2dc9d09198e149950491c1f988f47fac410b8fd804ad2fd46f601e1579aa505dd24e346707b7bab680efb8504d1505620ac7936ff4fb60026a5668c7ece157f9eb5062e0014e3b26dacab0e6c5a5972714702c8eab47b209fba575f3e4172c60db4fea3ccc67309efd14c0678db7ff2171d125866cb10ed2d558af9062ceab082f0d4d70e12bae06891e886bd04dbdb43c4ec777d7962142c1cb7e6e2803af4236e4d422316125147478ea68acb0734ff258b3b497237fe97da0ee664e73c8215d8ccc6cbf184cccd4e84f39c089bbadd8f6e81bb9163c77a27d220ee1a0ae3a1e307447c204a8c943ed5e3451fc5c4eb8d6d718cff8ce8f0540cde7ca57a6483dbd97704ebde2fc0fe278d24ceaa5ca28b46acb53d92680aa392d2c679366681f1b8f6a0befc969e31588ded6aaf365a4de830a3c6e87b38c2b4436ca844bf7a973c40ffe0fcb7261d7bdec892a76cd85e8c1e46a85672955684254a3bd56469f483647c7532aa7ee8d6c87a9764dd11c5376ba8f258a1ae522f3120868c4a9dbdbe5a79e212ce40dd83978451c8e84954fd3893f082c9828a1d6dcba9333d7443934fb59f41ce972c8bdf96eaac7c594e4636e62211216fc101ef526c7147cf51b903dfe424ff011c6c3060963758e78fe327dbb1a26d2dafdf11cf34355fa269deca2f64847407740fc578cf17092969f6c1b89c55727c7b4c24a67c5da403c89cf503eaf1928f55309a19a50dc96a92167b15af93cb12c06d0886547c9701f2568862d0088b00c864fcb5006b48c25db22a044683071082ef28f0f6c1ad1829793668796dfe0a8a414a752ff0b46cb58ffe6fbb1149a195dddd3b930ca908ad080a0935d58515d7505cd335de535fd339180e65633a576345468c74732351288602116323b59726fae6302da3b7dff015a4c168209a4a0b356ff0ad0be359e929953a39313616d94cadb51cea689232a4a94b0d5fddbab5d65a6b2db6a893031b4bacdc83640fc54ac65f2d24bd8532fc183dc6185f55c518e31573fb0be6f68ee1c02187bf188efff6cd70dc6fdfbadd2b4833cd78f8f1c50340ca16922d94a3d51100ffa20520b6da5a0c0000dcc88c3b43e69b4a07c1784bc6ad8f6fcbdf3bfabff673b8308fc315fd0df7e5635cd29baecbb70be9c38001c4b331dd38a6635a4c537126240550e5b7ac1c4b1b0178ccc718eb3294b012e63246e3e5f693188dfed16d46a3df5e6734fa45d7311a8d61fc93f4f87ee08b49692a9d233a95ae01a9d4da423f43bea62280cb663cec259e2aa23a39171143e447a1051fb57a884529be9a88a36f516a3591da43ecc70863b3cfac187762f79cea2846cbb2e2089facaf2da34a6a21598da3f5d5f2ea16c4a6966f36d6e5ab9d2692e9db6abf56ebd2eef297cbd43b6f175cd9db43bee5ceb0aeccf7103bb23de4454a7153eb72f9591b45b2561fa2b744d8346db3f916e13ff9ee2d977b480fe921447ec667f7dca85b78bca79499248a8ff51d65921cf1051d8aa0f4994dcc24d13d8af550f4f5c6ff40b361d18acca6751049270752ac35e933914c25b052b309582918ea61607753d543d8d46bf7e99349bea64377e933914c25d5fb6ca273b49f1fc467923c9f311b21d8cc7b08309b0fb41f180ed247ecd27eccd81f3bc28754f41f77c266de3f085ffaf88df8fe1389b77605f16b26c9f145b7eae1a28e2d4d094c9c31fe55d5a33627efdb8f4f251e31851f331f06d3296536e1515eae57e151e64f24197eac7a889efa79fb773452f5a2abe6168f22923e1f6836fe2304b371527ecfadf79f66d37edc09b3694ea4d86a6d779d4dfc60411356d5c4a79f55d57254c925ec8ba4947c79949fe684d9740b188ca0ea519acd07daccfbd944257af8b4fe2597c8f4adfb03a351bd1461ab5bc06c18abfe4100060bcaf1e1fb0fcc26e25b783641e31259facc2666128fe25578148f329be81af0c595e6573c0a86a55e1ec56b006e1d45a6f0d5de1a8c33250e8547e137bcc271ecae12857956394551ef4ca893e889c88dbd68ad7ffa45bf68d68b9ae84df8ead670f74ba3f0c97ad1378a7287ee4f4ecd558a6a254c7e9a7ae150748ef8b0ba4791e157ea5194975e7afc51732f79f94bb67ec7ed2ec98d4d30be6a69fdadc499b847e11e45a61e26c234ea77bc18d3c78082ba3294b05710575b8caaead1fed4febd7515a356bf02731d8ab62e0f0d3b14ce2437988f2f6efd76217d98987b5598cb5a49e550f415c4590e054541eb6b155551ad75893b14bd6406616d039c5511fb3a074386125d45e60e8543e116be82b825197e2b71283c8a1d0b70284a5d9ae288a88fb42b1ed4866a6d57ae0849696da9d4ac58f166a55d39d286bc616956fa2f21d8b468430d4bbbd2acb42b3faad095daacb4a176a55d71a123cd4ab3d286a214b2523d6b8eeb8ffaa30a352bcd8a50b3d2acfca842cd4abbd2acd06e4d9a9120aaba9a9127a2b8e3bbc63cc888dfa8705aa4ed37e6d98bcb0dae93e3418d279e2c261b8c6ff124e9eff056a473c46f2fa8a4252fe1ab5d7684e35fad95223ec986636bb03d6d3eb49f88dc28a5efad5ffcd58cf8c3a77f59cd48862f638c1535e1537b8bd9c4185b4b5c6a4560e0209ee5c7864d30769862dcf094e2ab87fee64350e3e91ced29f62643890b8787917ec3a7f6d65b950fb125c69360be158914074cf1d595fe6b1fe9df8063608aaf20cf846913eb361877013cb4bfd84f6b3cb9dbbfb8d597aee8fbc2dae0dbbcb472be6d894ff3e9b722ace957f12bab8a545694e256445a6ca21e7eff379b1d6de67d2bd237885f2ef432d21dec87fd7c2b42dfc2ad48d7086a453a47855335233b5ce63dd58ac0e81830b4e880a2e3c84b7d1d3d22582a35d6914455f5688149e838e2372a1c4851154ed3f9a42b7a78e753ea217c3d1442874f9bedaa877c3d14f744779d1c7f4a5f478f3fd58104564f4476170fd2d1e303a94b4582fea2e34886ef8e61e50edddddb57efd4d4c981dfb3752039b9d3fe78e9f0c9d7a42efab3b58e2379bea3288aa21a3ec1f7cb8732455114f5d4a52d657440e3979f4f5924d6b306f3ac7d0c840fe1bb8e9ece019f42194a581d978e2399fa165bf56879884ff0ab17b5dcaba918e34718f82abd88a5087fb6503ebfbed598faca1bc36f99cca9187c95bae1cfb7f02563ad70aa0e28f0da272e13ffe55a282e13bf5d1bc565e2eb6061f40975543d5a9efebcfe3b1ae98adee556df725b1c7b108c18ed4e9b89df54948b8a92e38b3edef776a7d958246de67dc95ac73bf9bd8e2446d78a4256d491c491dce82544f14976cb91d49144ac2e0a4b7e6f91349bc7a4cdbc2f95ac4592a97724f747a15cc1af2fab1e1d5f4fbe5c8ea2cb7ebc234219fe63d26c18d39144f5153f26f9bd07e94842c711fbc442b14fec131d49740df81204d33760b12a0f0d6a4af2b394c58b06bdcb515ebf59a6e8141ae437284739d9f3b46aadb520d53d59a2d6a4544f358b4489aab75e48d6bf543428532db4fdd55477c3a7ea6a3a3914a4e895784ffe44388aa2523a07f58d0ac15839aa3daca88f7f491a94e1c788638c55ac9e766df9ab77a474aa6216f58efb360a9b5ca8a752a8d62e1a24aa7a58f5adca87e83dcda6f990da61fc5faaea30cd87f2a6f0f5a392b00fad7062e56997eb47dcc769ed17ddf87e61cbbd6abe64b5d692dfb6dbb2e8946eb05aef35577ca23e7ed5d2ea1fb5623a65a779aaf7bc7f74ca8fea45f161d49a5d8ed60fc645ef64f8e8140bd3295d037eac99fa8a2f47a7740e9683b2f7f811efc9efe994a604e609c9fda79fe50a42f8fe9e1c52081f7ccc9d27feabde07f1dc3064f0c09e605555ff143e75c78daf16553ea8ecfd56d543f455f5deefdd380de115bd74b15927c7779c48fb917c223d11eef7821f3fba1158597caa4f8a7e44c3447c79263dcce53c18b719f78d49f5491062d3e8df57efdfe2d2837f11dbdc188ea611330725d2f711b7a482bd1a89a4c29ee4bf372bed486f7dff9e5534f68b12572aad1df69feb4dfc89ff789327ce5c84c2b28bb10e45398546f2f3445261bbff31e928a84aa56b447ce948d18591ab4591db372645174472fb4ed24b2a155983b2fb9d3692af46425df7139d8c3f67c44d226936484a0e496eff1c6e3f530d94cd700f895ba2f9f891d6939b115afdabb41981d296e25ffe7364b5c012663314bea410fb2f97d987a327b98cac7fdf125f96b812bd9f7184e58fde557c8d842c7c1abd1ce153fc97e5c378346a45ea68341a8d9a9127b9b43469469aec80b163c497b3ad48e369459a9126a35664d48a34234d5a91d1a888cdcb0b7ef911be4a30dff8b23198842fd96620be94b5e6d2f2f359b3a1fee16b460e223e9bb287d5994deaadcb78d00fc2055f5248509b919fcbefbdbd8c47257a7b2d2645224b86002e239b6c465a111f12143436a2b36a686a6cc40721a4a9b1e1227c2a9d607b78d9fa8d8f36728ccec577b4b27e5ef9d4f5a8ace2213fce5bb11569b3a6fc5955e6649533497b4f5c939621761dcd86cbb4caaefa105f3672a31fe13b74ecdcbb4cf18951575f6e5f539f7e9fdcbf4c7110cf97c3355da3d9a8e91a103e1a1b35acc6c6a3a9b1015f0d8357d64be585b45e2ecbf8927ebd14e230592da9c6b97619d5b40aac638c113b9723e6c90efbc7f8d72312d9cbf833cea7bec2978bb3befc5133a312becb0c9f28bb24bedce93debe1e113fc979953107b8c4d72b5e45c3fbc403a486dbfc3890c794486bcf7de0e119b193ed37a88abfe7275c6909d2144a2784388bc9d2144a218f2de1b32a3d403dbfe9ad14da487fc583160f2c488c913a3d168f46c099396167cfda84f3cc1a487dc65e309b6843dc164f4ec092623b6843dc1a4879e188dd8e889c896c4674be2b3254b7e727cb6248a1c9f2d61e232f16a26f96af9be2c268ef4a8af9e0eef493419c236920f4758861296c23415be5ade7b9745f844d9175d237cd1e47859f1fb89ce61f1897a11854fef5f16e17e827ab89fe8275cf50e3b9188c2573b974ffd73461c265c3e5e96237ceb9746673f4697f7a2a78e09d2bfcb4858fa8cadaeb5d7b95c79fdb95cd2f7a544ef492e92548af1153e64f2049327ac8fc2909f33723129113f104da58580682aae859a0a10eeba7ce5547421759f160e8b86c5afc407a2835ac865e2d5425e552d04f125473f5d30fc16a4d1198a21c594b62c253ef97c865b53692a55256a2a5428528e4fb5cc8f0fc4c8d4f5a349d9e113f5f3197d75be75996869798a2f528ef0564cd8dfe1af8ac0e85dbec5187f47a3b08a2e7c265a7e87d358ab0b9f82c0e85dec6da1d48dd665c252d567a88ff3a9afee45cad4bbdceacaea33f3a9a75d5deee85b6e114e58323e13f6e3c5612ed333fdd5ad2e13e36730b1f333dd424de54750503643054b8054d0f3b8705a342c1c8b90734243724a3bc4a373cb5ff2b2199632c45795a497ff625f5e42f82f97745d2e63332d97b11958f1fce85cb4ff243ed9773fad9b33baa12ab4eb5095a12a435586aa0c5509121aaad25586b2cb3dd9e558aa122414546528a8ca5090505095a1d65325a84ad7703f449b695f354b2dc3c1722b95ac6d3d6dc6321ccd839af6f557df33fe106d24faea59353f8ef0552a89f0c397b5f0108db506a1e4413d8c7396f264f325b6d495a3dcac5d34435586aa0c5519aa3254a57f44de32449d8331d2489d1cfa34b13e8da5ce515b5353937544fdbaa8e47b544ac349b9533a3954b5a4acfed592b5564dd6b7bebab5c2f79555adca32ec954ea153e89426aa7cef9daef8ee8978f1bdf732c236a9a7288aa2a80fe227fa8cb9536f7d0da22406d22a4adf2de9928af0bd7bb3e4575f2bf9be55b2caab94a54bea6b6d4f6fa5d235a829f1d59fd90caf3a3641a493b353832a965787aa5015aa542a95914e8eefd4a08a6564ddcee86bd0a80e8de855859c88c8898cfe6a3ba26be9b3a25129535baa542a954aa552a954648fad522727be78e574510f19e5dc6b2a34ff4d89fd945129f1839cac676ee41c951247f722229f2a1f2cd18b22359d853b971761ef4884ddc85247f1f5a3c617613ac5ba55a5f8a2a68431925ee40248053b402990029402284028604259b5d60a67ad96d515cabf6cd4d4748dfa229d1ceb692a8450045f545da0652357e72c1758df7d756bba46a514c4d7c43535355dc3e16a1b46a135f8b4b539e19c71eae4b0f954caa457628c5468cafa43f43552cdbd65892ccbb22f8a38fea8e651cbbbb48c2c96985d0a06a5f014521db9e4c2ddbd39276cf3596633c6e6cc99338710b6a7f136e7c4978d39e7a43fabfc4a75604d0da9422324a1adb58f5028cb640624240a1420c935990109c1a2359d9c965d84ef78e864e97472588c15cb3dbd6799b9dceefc5d1dca5773f18a11fe55638cf2a3c83a39be7a3e0c11fdca554a9f5a5ad1af94d65ae9e8a9cb3bbeac8b3be9455424727f97db7245f8b2f08fd2c8ab4aa9899d54453e7f7cb23b1aa493c3a81447afd0204a83e2533c979cef3ccda6777cb2ff552b16f9b4bccbb797b98cbd5cb6f8b230bb125f958a7467d6497ba74ea153e8143ac527ee445785f2d5be7bbe4e0e11be5383e494154b6eef09315785729392c1a8bca7cdc497b27d0ba9ded3ee9747d1c8ce97514a7cc92c7ffee8328733134bcceafb5b578612b6c23f1817393a911fa51ca9873d2e9d1385af763f2f2792db97deb7af543a878c0f237cf8a2d2355a5352796c42169f366891a988d9c892852c80cca4c80224e4483e4066526419ca9451077572d83b46e3d86d1942f6c3a00d1b3810bea4587a76b8a6a6a686b673c8846c3c51b4a839e77c133239a75312421bf9fda36ce4f754e2209e4f7d55fc6c521cc45f47d1a4e91cf32977d998b6f241f58baa1ef59be5f9acc68655e1f4ad2a9c0929b53a3944d038fb91f7442d84f3e5d3481df2e5f2d32db50ffd276cb1f8f24c6de4e912525c23270521fc7e46d339fa67f7e5f2ef2f1b99fa7a2d7ca2be06860bf65af580950f35c3af090155e150acc6cdaa47fdf9ef52f0bedfd1ea65352296614dd7b09e460b9be4bfa70249d4afcfdcc3161f9aa26bbfa489621e9489324df726e93a369006a1ab34a08305898a77503956fbcaea32ef4d35a8f52a517dfdae2e6b03b898932920c9415a863e7cc9122574be00e58a158ce189cf02ea7bc5802a036c01d58b904300b1087280a2bdcd4c468eeb4477576aad6ffd86f575875ff09f754db05a39ef5ed54f5355df44eb9ba8faa659ad0724c7820fa465f8f542d104b2a7faee24ff721f5975d51c1574961b27de1341ddf8781140e2a63dab807801a50a1190805285083d37a22985addf26136cfdf661c820f87a4ff5dd5351b6be5aef2a1cebebbba69ecfcac9d6cf7a2ffa17f517fcabbffebb266aa24c70bea9e7039293ab57555fc97cd5872feb53d9c23fe40a3346e3297ac19f52876ab8064ad1a79ea2a4ec9c8441498eb3524a210e45caf45ed55353956b4eaed85fee2feba989611e543639cc83cacd68b43759b8359677b4ea768697b5675dd17f958ff8145fa2778cd1a89e311ad6d32bc2575ff4df43e937aa7bd1bfe8cf9f150ffafe953a496cf211df7dc4eebbea277465680c5acc270c4a745f62c2a5bb3f3342f636c30b9f19e173a5121266a96040469e90fd25900b9ee4cf0cc8481399ca0cc888cfb381f3205f325901535851032b8a40e2481616e47ed62f73a8f284274e8840042b5c6102e76eb02c84942390d203d6ca22debaf1f5dae793b437fd2d32f1e8ecb9b3f51bfd8c75c3d63debb41e019b1142de3c1fa411ac83206a577b77f7618253f31634f7dcba4c638c316781abcefd559d734f4ab7e69a6b2d867f6badb52f3ce7accbb8ab66079d5b036516707f9ddc7c341c1e59c7b508666439d9c39f4f2164f00a56e2eb337bf9fdfdeffac19c07104a9709b285f9f025b391d1a7bc143e7cf8fdd09f36b1cf3bcbe5ef9154f0ec99cbc8fe7edd563c89d1a2836e10e209dfe7e860940e9a7a788847f072aef6e505af5612c9060d00dcf042aaa436544b2a954a31514ad59274d0010d4132222ec8cab8a0020a89e4dece39e7303a27ff491c9fbedaa2735cc80ac7bf914a319a50e8dc7bffde83ef41085f1a1e8c158eff73514812d1932cdaf053f292120738594b4c8c38ca0aec1c0cc7fcf793e1a0feb11a14d21a9108085de7e960842fbe076184d6c1a09d8b31ca02561d744388a57552fe000cb55aff27432a3d20b7e687216638de972ac9416906eb5c6686cbb42d6c7fcbcfbd6732d145bca3554bea7e3f804cb2d2cbe448a3438e51ca2bac94b1f892a1b9682c7ce9306aa1f0f53f341b127629955a308b3fe3f5b2df162d4a68418525852e3801820a5c40865a62821d51a968408213402811810e1d4a275c84f1bde7b670630892792fb2bc480a135cb70b8293b59bb9293fa49a5d1d72b2bac7921396e4642db52e28258f27bba7a521d63dab7a2871ef9408793c2fc6c0bca083dc2e280487fcb0fb11697139213c9e762206153c1e4a0915315a7cf0ec61b1af08ea55f00121428408b9b19e527ae10ded4aefbb7137ed86ba4aafdfdc54f752e643242f2b6307d6e443747a6f33cb2c5f0f9b6c964fbd67b9a50f86835947407b9aaffecbbe77d8d4832bd5f7d11f0daecfd7cf2032a4b59ab354c2d77beaa97cf78fba6eca29e56756c9872d962d0b73c29ddc375835c05f0f0ee3e6bf2489e8066c08a8a23f6a700e3b778452125113c7d3d500d9cd6477fd872f0da1a38b1f866895457951a244d122424c0046a3bd080b120d6fa7233b1e20afa4ecdc2fe9ddcc5d3f48b93d2c9b94d6ceecde7d331b9936d3debdb45ac86a65a48c288bf225f332c4249824a44792d5920e8004942a8e947f90503f54bca84263a9e1b91ad0294597cd978cbda2cbda0f9a2ca519ac94d5ad918a6f73fc7965106fe6909dfc41f32269b2c38e72a7832827a318b5b16e0b19c4e79743962c595e93e2bd5892c1a31c4b645160946936d5bfd7a1d958ff68da4c8339c635d848d1cc52be7d635d03b09952c9da1d7a6477d9ccfb3a939f34800f364b0c64d7c365efa66510985f962c59e408208b22fd49014348a9c8c7162745ab8f2d5bd1c77786c3fa786bfc672f6334de8b6e331aefadeb8cc6fbea3a46e37dbdf43df57ebe77af4a075f8431e27ef075a0ee0495802eb6051b7a81051303dba2a24a6043f30a134260431de478726b4d487e5f02b2ef0a1b8280dfbc4aaa95f49eec2cb4f6ef3b4ab3a1fee12052a6a9c1bed6bea7356bddb78f92af1204da0d857b480da5c6e29c0b22f393d9b98ccc8ee711a1b9c85ecec77befe4b06d4269c87b9145c988ab116957b87ee9803820ae9db3c23a07ddcfeb847b59f9a87a70ffa4744e3a777a6bb0fddcfb517d302b346185265ccdc17bf85f94c12b1f1ea30e6cac6587db0a4a4f5e9021460b917683f8b02fd66488ec647cc094dba1ce78c9fd65d3b92bd639db6c802841ab2ae590c3c439a22c6c3f104d6601fb883e30b0a33d2e723b396c73ff604d442d00fa026b02bba071f23d8ffe487487c377170811f9d5fca0cdb799e6eafbe6a2c4b693d5bb4f34ce072eb716c4bdee7ef0fd022122b7bb409ae432edb5f66c3ee9dd1762b0a5182ed39c78efa57ccf2a76914ce6e53876380e421cc4f94840962c59f273d9b98c0450114b8896c8d78ffa8305493993b88c733993bc8859d780efee7b78af89244b9fd944d7703f397a72f4e4e8c96124074f0e1aecc0b303cf0e3c3b10d961678723809814ec79739e18e1f3d65afb4ebf4e6c07928e7a7f2a2f8f4bddda2eb56e0f96adafcfaa0738f716b6debd5559d8c403cbd6f760d9c2d643ebe15b587e7c9d7ca9dfd1faa94e9ef747294bf67468bb14ea71081b362074d04167c345dc3535d20bd6bf54436aed052baba5594214233d478c54a12a14254afd36b16452bb5249b24409c557290a13264cba50a9542a55a80b500bb4779cbb216890cb441ae49e63d9a7fb7699cfb80bffddaee1ff2ec4575329d97df77553ba86c3ed83912c915bcdcda6e4334d4eb1de1a9dd207e81cdce814e7a0bb74fa4db10f6227a10f28371d14ba46598574823dcd196955abf8954e5c43318a925280827b3a4829508a924ea0544dd7a0f8d157d3359a86aab91eae7199b6a481cf4ef78e8b42d3d8af0af6ea9e7e429c95be173f3eab68ef68cf6709243e4b20614aa2e0dea3422f8a85dfeeb30492244a903cc1761f794b2049a2844ea141eff5f81ce939b2049d22a9148af55c354ad9de764fb369b57de8149f2f5036e36a3ff1480e1fb17499feaac254192827580624032d320392024aee4c65f4586df5d56fe054f2f68e25259dd29a74be9473ca39e794d6b4acf6dcaa306545f61d86838255ec9eb32f90d62daa2caa47dd3fe9abd59db056d5f30e9f735638f369acf19afaa7b51f25f327149b4b0cc7d5d5ad9f438be97ab6f85905021cfb21e86a55d2ef70da289592d297314a495172c6eb60155415df126d8156b5c585663aa282bad496a7a49a430ebfc3278ec57179fad6c521c54b39a156c5b7b56833f03b4a7b08f35fe565023fbdfdd36c3a8acfc028f9d17844663b9c4e27d34957a467158e0ba59c58dbd2f23bbcb2e2c5ef70d1164a94130b330392a28acc3ad31b6abcd593aa1e9e4d55155e5411079a1910d00c783825d5782928b61fc6ed7c43ad807486f114a6dedb43150e09d72858abaa3fe40929a6fe8d8f83ad705cacd81b3303026a81cbdb2b7aaab2b8b110dd0ebac2f028332029d4f0a2e670e3df705d2a9c16dc416470a1326abc141556660664044ab66ee7d912ef084685f3e2761edd161a838d9f434cc5a3f38ba730ca50ba9d65bca880742e5156d8f8f1675411cf27d8fe0a48e7185f01a301ff869787240935d5787dc76560118c067c7a85ed215390cb08b90c7c1312f6e5e508fa2ce1d3150f9ce953ea5a4f71ac70220e37fcfc1b280e1748cbf46fb8d844f1a4d6cf6f31be738b41adb7dec2b846192b9c48e5ed232e03bf7b8044fa5446a5ee7cea186b8de268ba9e615c965986b92070a4cbb20bcb2d4f65aac47058a25abdad2eeaa589a239991abd7c7a4d23516551938f3a7f5296c2a61ea89f3b9cb2f66baddfef52e10029311c254623a632bff1689374c69e53f457f597f517f554545954ecf9a256773ac132a02564215f0e830c793036648a0b99fa7abb65933f7fca3b9f528a2f2132253ffa30b1f318be0029b88c8989c12d05e6598513c4cb85f99818dfc00ef6ea9d3c84dfb85a4a764f92fd7be7c76fdccecc6dfae66a23d9bfa5f80df7445cc6e5bdc221fd88c63429e6c2c0bcc0b80a13f3f2313030306f7a817131d5fdcbc7dcce1bf01bee656ce810e365084006eba8f7ff1996c8b6bcff8e05f80dd2fbf3d0ac73e087cbf81017dbc0cb7bcc77f6ca474c8ec1971030bfa3c15c0bb8cc0b0e026fa0749b741b8acbf8bbdc6eb9dda47f5cc6df5f749bc9edeab61297f1afb7933492db4bb88cffbcfe43349bde9955acbf5b20880d0461385a48f62bd91b8c5356b4ec3e85d49f18f4381e2384a492212b70219301c50089bbbb8e10b22c81892b8e2045880d373d061654e1640867186205153f8846409e86c58822248f114a181191207978ba797672f7a4b206356c610a265850890287228a143103118420451137fddedddded3ca057cfc0766ef712a7321a5e33038281927c49c9d8902f3783368594383022055b7c010b1e08a10d5c2c9942891159fc4891c47f204306518c78c10c5238020ade041028385852640564d84293241cf652c95ae7b05b9769fe1c5029bbccdef5656d6a04ac0c3122240648602209346530c394315861832dd070e303185cf13c34c04900e48bf4d95fc2c005478c21dff083295150220b32f0e0053797cd2d0b4c40d14211292d30a2c58d0480681f04c3d1b93d0f486eb026f0c020b74ac2c2b626cd29f69ad14744c9eeeeb2888c31dcde7b1fb1230579d9c1075d0ef2b2732e8636044ade2133a02141997a754ca4c0897ed84837b03332531738c1ca1732633257e7eeaa07c9428132bf3102984f4c06d52106a93e9bf18bb60ffadd1c5018827f8ccee1df1f44b389c15a8cd62f993979411072c3977d72c5c82d5da33f860d2564068d0e1efc0603dab9929b0565576225468ce204032aa28b7cd9c80d5444155072dfb8e641f88d8b94fdc6d592fb1b080d3cb973ff06fc863391bb07f9aadd5a4be7c8340d587e6ede6537d42b4ecbe881b5f9718c9d7ed62f5b88f31705653996011d514466402fc06287cc805ec024031df1932f9b1dc8054f808e6092fd492ec5c117f864272b35623f5fae56a10751aee8a00822536e6e6001152ddc40c80a1729b871315091c4053b68c31558b070a3431114a0e08a14dcc00b46dcf8fb165cb870ef39dc5544db2a1fee9b74594311a4224845b04a0227f68c31c618936d863d736962d9146c7832c51ab4f899820d4fb2cf7c6a15702137eff3bb26f7ef6f1eded1c1699a8cfb9bf731b79dd2cdfbb8636fde373c82969920c17b7877e4cd7baf24d0defc59a10d6de8a20b972c2e591ce6bdc7d863ec65777625eb428edf6a93410e02bf73ab7cc464882f21726cf44908d3dc79733242d7e6a4e6ec427e4fa5ad70dc3d7bfe1a63ffae699aa429fa9ba0bff71a8e6bd73d6dedb9668df0e230ad8bec5c8333ac4b88ec70dc02763b97ee578bb0b994534a11cbd26f88babfb667cd796bddaeb36b8db5ffd698738cd558971099311a6df6a38209ba0761bb17f5d494b1616bad7db7d6dcdb11e6dcbbe047e810c66ba2a66c17df43f9e8b7dc669c0909cb4832b00e8176339b589619743fb173781264cced65e5f05c826d9901ad008acc5ed24973bb930acbdee402ebdf546e4fe38bb05ad7055c4a295f5febd2e1017c590861dfdabfdd357973f3a78cb039e7ae391f1e76957b56b1fadebdfa757bf79a22e641bd09f69b5c3f06a4db47f3c7fc9d031c63ad23bb0dc167335ad6e9c44677b7e2e1be79766ae091dd5bd5a300dededf991a7336dfaf699a247b53343de64072b2bbff0e6fd9fa0df6f0d9f7d3d8dec7ac7c68cd3dead7f9ac7c684f931dff93d8356c42e78f943d5818329ca2c167f05df05fd679dd4b28a3f496da4cb3a1d55d4112a0a8ef37aca200bba9f009eab39bfa37d52db599f6b3ed506a0368760a4b63edaa07fcf7f5e6b82782baa92ffb45805512a80fa588f5e9ac3dbf0b90e7ef708981749659eff58383b07b87c3181d90e943966f63fc1ae195adc7d22945253d55e1d81fcb32bbf1bfa15565cda6ddb06a5f85134d2ec2e75c5ff94dc5374d93c737c9e85c7ffba86abd4ef8d36bf2d711c26e68ab78b030b029727caf781420c777550f54c41f5dd583264becc3734d3a95059d5258f72d1f7e96f8a3eb12501e2e6a0a010feee1338085600811e490213ed1772f9a4fecfb1f7a503fff0795e9d3ae7cc4ccaa100109280d60558880c41332a5a83ca9af7c90df20453da4e0bd260aa9ce71f22fd33349538414909c1ca9d8f1616c950f49b51c9ffa56f54065fa03c4d4941fff9a00eb1c3b2e41d90cc6b26c3b98fc03a6eda8444db7853de9108d000000004315000030100a064463e17040d0622cfa01148010849c4c704c96c8e328886194320611630c3000440004404668860d0254a190779a38320a0b241e3f7f71e5218d5062fb260c49d7d85a3ee73d37ccda8669e3ffbdb8cd4560db8091882ae9112309089a22bf2e7252f8395a0a9069c2062e011444c21b8bf0f24a7d2b197f8ab61346c00dc47d7ebaa95cc3ce0f146c06dafcdd450eadc0cab22e73c1f07c6fba19d3fad27e07727913543261e2df5b1e0fe5368fe2e3c1536d6840f57be182ab4b4d39aa35c2e2b797f3e752485a20539a9cdf6ad7ae61bbe6669611c2d883a0471842f1663248db9def9523cb7da235ae72fa594b220d3042753decb4e0101fccc7728fc460b03d81da2ab40424ece586c95bc889305a3c78e4efbcde9231b5f0c05b9cd13bea0f7af0766edff1b6045bfa9859cd9806f21831e88ca1ecea34c8f333e1a00942d6af04c49744049c9bfe9ec30f40bdc3040fcfa425fa5d6d702b8cb1436b287931117616925563525cec0cbb498a68dbe820075c4595d6c0d90d0ec4988fbb3eac178410ff2bf306821eab91af1024d99f22439242ca90883313585c102d85ab65f124be0ebef93662399a974d25c4fd27e1bc8ec68c84fc081c37c63b26ae72f7528d3d4b9e9011d075d0319e10faf3eebce241a2d46985bce5128d9c78d1e8dc209caffb73926e702d13f5eae73e52ed5de611e3c2f13ff38625b285a908e37474352e798f2c267f1829a0dd2587b4f8efb94fc6c7ec087f6eeb8adb5c82e2c3d4da90831082891ca516ef8592abffbe86ca35fd43923775a4c4b774bf5fba921f094c7d1bd06732175681575737413d7351c2b5092f45dfe71a2cdb6d90f882f64d68ac0b6313859fde3e0dbe5ddcbcdd79cc6e1e72b75cd6ddb06bc36e1dc5c8c7d3c65f017410782a2b44def5d445281bb537e74c51bae542c545270c77bd1fc227cf2fca655f6d3e9b37314d804719f7f3410350af6b456a3cd3a1c1254fd326a194e94877b741779ea67149c8a510d43c340c5df61412ea45a895ffb008c67d2f71ab60ffba429da1c20dba2213f2b9c7cd302d4b3cd48307aceb3157b29280b69100826fbc69cf070eef4cdd8a0802e0cae022cfff01093ad850ff0308b0305453fced3640cf27734eb8a5d1705d37ff29f9cc63b5b92012ba798d3afc144073db2c26d1b531ac24c76a47661dece8f26d01bd3040e435917b8245a5f87276be9678430a5870842817033d7849509de1e6d8e1fd2dd133fb1eb436679329a7015dd86ff38e1d0ea0f181688c2dcea978a7f1e873aabd54f486737dc5143e3c3ed2e3e5eab133774a908c0cfa49513addf3c008ff3b8a5925c2a71ab8c32b2eb47bcd917ed33ed62725035f96d419e64dca2af869f2b2d30f98c60c9c3bef35411cb4e3cd5a0746623ea724e8bcaeba3788c30b3aaa2cc0c62b83cfcf92ded68cb991398eeed9339ec080813e99be0679ed323f58e5eb2d98f17b6f4f01ab0dd9f405786078d62b68c94ca1dfaa0955053870d8ec137586bb1f8eb14c485a2b017bf7aaf4d8550716bd7b2412dbaa1191c37d403944a95f6a5ee441b9ed60cb809206cdf68b31005ffbbf9e2908bc2f03bc8ab4bcfc5a618493a325725a771bf93eaa22e6a82a934416d60b80f3ea1adb0387db3552d3bea595097ddf1eb3f725b0c43c24593788b9014af65820dad4c40c5c0bcf03453f84b07420e22c9839dc1323b3d4270a78ba2b0adae28dbee3bd45bf2fe1bb100cafd4b9121a89b2320dd3727ca1481cefa090d50dce9e98e098eb489d4338ecf4632de2cb88ed062e022aa7c626128b994e2a8c522e71ac85695d5edd32b37af904cb13ae4e2d4b2f6ff1fe98c838891c50b2f5480069ad80943093fbc2b5052b44b84d1ae0433bcf90778df979ef63e501e77d09f97cb5bf992900c81f5b0eb065e25dcca3bc09030dc862c39d4062ea9bebf390d4dc358701beb612f5ca9c1ba8ef2a5f7e7dddb161153e00e8fea2fa6ba2ebe661e5e55ca3482b7be307834eebd89caa4d58a6a00f49a280b821ade005ebd5b3eb75eb3303205393ac4396aa4fc1f30093abcab27e0568c8cb65def05a7b7d6d818ddecc355b39333aee1e2f5f6155d3d39b7272d6c24f9e45abfe382f1d568c86f4d64b0e9257c689866be2d67dbc62ce0ff9a941df0020abfdf60f9cd06204416d790037981d68fb07d48d364890cebf72bb1298945c7a3c5262ee97d6404376e0bad3a9f43d503c0c3cf2d253f2c4efa30da73adccbad54f0125517f5aa9fa32174285dad52e4013e71405b8b5eeae0bda69e4d853f84e89926d12822afe47f4096c1e0049262755daa57af1bc9c0e8c24737d93012100e057d9397c780078351fa0181651a16484013ace77161c48bd5d3d3abd9fe427b9cac6f5f04e06a608530a2abca1574653f909312eb1a4a514295c24fa899ddbb5e0c5b17c9e315c6470fa0bdab747a06f21743b7ffece24f0b56d8b05ec71fc4f816d2657c486990872d8fddc14e44fe25f497d1fbbbae0c36a4d6f7113b60fac320c87146dc9995fdede0d38408cacc2e08ac04c98c7ce1f61d60e2b17efc522d5e90cf9c517a1181ce158759c6e0360d1cc1115b795bef68945f469357fba3c521cfec1d5012343b984af47f38e3c92df210dbe51d37b6f755cde9013c30f437bfa8dcf0b0d5902a2b29d2c16f9e509e5a2924ce07f09241a56200b3542c74a7076615499807d2cb612d8377e36d30f222f0b7e1a7c279191e7b1bc0b6765ed5e340f8838806aa260f44e65e2b1bde4c00e56e9c537f1b0db9aa53158f60dd20fa29dc890b2f99b19b2bf65b0b522feaed49b1f37b18541fde161959f250b170988c1e5debd18efa6385e319125ac4280c994f87c03a3b088e7eaf02d04c485edb4900c0eb73d8c23485b2e417fcb3a2d0a1975d498466a07ade753b06931fa237b9820d3108508ab0d81830892a725d90e5235c5029026f66b34b38a72e270b6c10010745636e31fa030801b3117d75b7bb21004b77c563404bdcf58d32b817cdb86383c049e7ab41b3c394a8b21c45a017cd8e0d170a991238dfffbe88120ab12cc3ad37d6fb4563f74e224768549d683ada51f4c9ae918f13420548fad531a5d5351d42ca43fb1377ef68e653f3837a93c6d4600d9a801d2c99ecafebd8a9eb9d49bfd6c3a5f3ec002c5ff3094c4770e5a10c31496e77520eac6c2851f91e6efa8615f2e2b9a58f17c252c3ceb8f5e6888f67e622ac11d48f839170c91577e19345497e3cb04e4d0edc397135fee6f75867df4f3fa47511748d7c9d2ffba82264d122fef13a819852fcffd841babd3a99ae39116b6b0ba7d402ff3d5c432d3402b223b79ac99e8151ca30b3c42dc81abff94adff721b10fded9c1edc053f6c3e81a928d9d96ad5615f7457a19449f019ebe34c0288e0d1e3c9e795869bf6da771e10e40f49dede8fec639cc1c522fb01ac2b003d2587a0a2a6abfaa7257dc64f5e62c970ddae89b656a6f67c6245a691cb4efd65a4bce6eed20ac31033840585451eaf29facab18b9af71ab8d43476e51832dfee00f8306800c240bc5222bb5feb90d88e056d8dc0b65a3ec7f3995e9d5a88ef7b87111c3663b227fd72b9a50f4a7467522ca296ff0957a5d0f5388ae3e0fea08fac6110c9902ad6f7ef6a90ad3fdfea074fea0ceee4181610f3a8b3d903c7b8b53ec7440ed7849c1c6e722db6f2d830ef38768d9b4c626560bdaa8a4dd1691c1a9d0160480fd0f6beea4565fd09839d94af922826ed8597cd91b0eaafe3688024ddabea024e7050f240c73003f7e9cf02f014ad86211d7b7d3954538317e2ffc6b9e6d5c43158f80ff0420e304e4a253854d70275479f9945a35115facef03eb7d8ea9e46cdbbdaad178e252f029c73b6566f47d846ba13a72d5d4033a53124b34840df0cbe934aae8dcf0b0961a3cd4e2c356beaa7818cb6b86fc405044d6ce1b7cdd0ec1e3dd0b81111566a37ffa8f0fe09f2b282e0927728bf526ec94cecaa564cca50ccbc5e33d2fa7df79a71eca8dffcad8e1ffa0df27b9233e2a61d77e9f1dd3239acd5376479aa91e790d26d431e9393590e1ac6039b0beffc5b3a405fc23a6351854dbf26b97fe0a1d73ae4211ef5a2f0d83aa94b292424bcc5b9aa564503fbf5ef7c249242d9b45fca1372849337300f0930ede8e68a14df2928393339b01d927522593b162b33bcfa0ff4fc2227892ec7e3eefede64076ac380064c132e177fb886efbf181b6acb31f40f6ed16f1d88f8e2b992828668cae6958f968cd841bcc35c52bdbeaefee751b5374cca4d3f868962c888dc69d931973742b2dbc13f082f903eb340c57563cb080bab3bd83b19628d99557c3fe71a2f7c8a9c1be17373afb9f99a88fd6d26af05141343ee38db9513742f06d42bbe548b6c3be9e6b67353f6d9c88a1f8d5a278869513e7afd7c039523e220505718fd2fe1def05cc254cd84b8c90646c100ef8ec913da3a4f696e59a7f860d228776179e6d06e0a258b89c6c046a1a1dbea838d9b8c037d417012240c8c0e32b4facec1de8e3402bfd1fd59d94459311c51077b6596f4969183661bfdf1e1c5143634f93b9fd4f3a2ec7b8b412552ddfa2c83c348b74d69cfca14d6213f2599ebf499f4303530273426bd8ba63757bb6ed8ec809b761a27a452d68a973aead0067e3390eb6cdb4d80a09f3545a258ebce703387402d285d58a4713fe01004a9e631889196e9d469fec808749d008fb8f37a597b29ed3ae11f55bf98478fdaf55ebdc0c98cee824e378d1796183c8945921c80053a637ad2c56af490ca1e37f02be2297260efdac2a0ded4c8b25ff945c3a3ad206a02bb52765a3b7a478595f04e1db7c8ddac60dfe4002b0521171d74722d34dddd4ceb2da8191232d9918ae49f0cde9b6a16c4f0d5154f1bc883bdd39d0600675b126fc6470bda1727ed3a601e950f3105fd2eb6a589237b31d6ff27d82d658c44d1783da8fd39a584b1378efc8dd1bfb2bd5285cf295537d37244bd3c19fa5f94d945e51a43da0cfa8a11576af7dcfad3ca7fa331705f7b5b6910c1c4328152584fe4035d5e049a71ca0d585f39329dc444f21cda5f63d86c60c7006e4f7a19b12b8b4f18c2070d19f2023e632beb6a46d5da707ef2a033ec6f21ac4f95563238253099e3ac703752445a332c91d9ffc9bb5e3f5f81183735ac6776e6573a4c32487d3db02377ce1f60220e9b720a8a5d0efcf7373dc0ae469d89818a00c191ed860b049455dc63e97cec840f93d48e3bb3b6ba281dda0c079b4c58bbb0e37958275a48981f0f6b728e259260a65a06f08c9d96f8604f1a1cb1e321ea75621a560a12ce527dc0d7657c3b3a41be2446a7f28b31f69fce28e468e9c34db7adfe888abb4e569158bcf63dbe9c97b7785b83f564425815c768d13aa5f90e78ecc3c9518b1b4f90388fd30a59945b11c88c39e124c5dd6129eaa78e4872c106b342c7470408c14536e22974d7e7a2f92a20fb2f8541819d04c72ebd840130d887f4466a2ca557be00a5283eb7dfa7785813aca0cf35db011a669aecaa3738097033528805adbc50dd096a26d67aef6485efb26246efccdedeedc6f6bc2f0405a2baf2a0ccfc1d94ccc59f7e457d55575b18bc453cd5a11b63a190afd916aa9130ac480cd598ed2430babf9149a2bcec4e7ca40870e9adb9577d223e6c80c1a858fd4910a3eefe0f74e4e7163ee41448bfbbba3cd1a5036e21c7ce103319b0cd0a80c13c53585caf684292da2b85e840454b97ac40acf5708255fc6a5d7f107cff151998ca047db21443392cdfd3648b7b1675ee18aeb200d8fb1525e87964b2a17a8852cc43a93d69090751dca6eca640fa667bc4189e307e130808bfa58bac699cbbb4ec8cfe871dc4f2641f1a92d99540aa54471bb1588acbcdc46f8e4f11e3b117ae12df63ab0ebf2a0035b21cc5b6320349b45a912e3caa1d2a57003b3bc02f78b0782cd7f0a99428504cd4c042b1fc71ac4c3a38c91e7603d598e0da6385c55b1114ecfe99599da2d74a51a01161807a80957178cb7259d78908af4896948a61e33a0a15be8ee8a9c5193aab81a0007367ad93b612ae80145a210e2b525a1dd73580786b797f1cca8d31b66b75cea4fb90305c7d6de5f77d661c7742e01230f57b285d6593fcbe3d30e8b7e3d538288b64660ebc7911f71e83aca040c092e11a2d2c62da6c116bc139aa0b42e84ac1b043b1cb01be8e5c5978d0cb89a44e2f028ea33f9be9e1ed7614efda642fbc20477f2e9a9cca72baa013f82700b372e8a17ab30cf0905ed0cbb6f517b6434895802745bbadffeea6dc178cba6f936dc50d226fd75f381706204b3846e894232fdcad28b55cd54a452621f31d54a7d8a69e554b19ba5217b337f6f7b6ed94cf3a64569ea20b9eed8dbc50d22a774530f50c2de40faa8ebd840791ed21e2ab6a7268ea0819f0dcdf8bcd2a329d3f98b687788804c85121d09ebf069ad7e2a2cbc823279355e7a0ab325d61c78d62f30edd99640e0e9deff8edefda405f1a08668458221f1ea0fc35296231a30a966e11ad51ddcb3bf29557a0b86ee635c47518212c4668b28906259705ab7504809cfbba87c7f20e53206879e0451ca05606f3510bd89ceb5f61138df4a980da6e05db7bb093ee904c2293b7b72d6aa8447ea89ed567e31c3d8727b414c3e080db592126feadd4d11ecc7e3b7651af9a2199afffda9ddb4114595e51dd6e3e12832e6c3d92f491919b170f31f0d1cc4426fb94b595a0ff4024131228364382063b3a01f6495ec8390bf2bbd6880c41b7e5e4641bb2fd773d6d026e99efeb5cebc6b2ba11d856c796fc25910aacb8a90c54e1fc007ece723aff49607d9ae364547a71b056d0b168503d58108fd836debbba0732f46f2f7de9dce0d5c29a9136a40ff420753271f46ae64c7d8bcb97a398ceb6a92e31272b6b155440bd91edf72151c04285d6bf588505ecfe93e013460bdd1bbb17cc44023bf4ac3ce990838361d0e8862b59415e4cbf25dbdc009c4a68f9b58c2f6f4b8174b16589f4e8ea4f19cac1ba7e21a3518f91c10bcf942b21a91b9d0b165c2263ad195923e5716d092a22fae88f6748e52522c2bf48b9dd825dbc1904cd53b9170ce5007fc8673ca8206430d9262462b78398a3e9510182733b1fe227d5f57a105e684b4acb8b3e4187e98ed030f11ce5aa58074599a21010e71f7870f308d51d26dee5e50238bcfb205172475e77b43b160a8b0d874d3b10917339fb1fe158db1c906addaa11232bb09422ef2fedd5990d4c7b375729b5b7d54b831292c27909bd27b40480c5323a878513913ebc9844fc958a7a03877af8895c5d119d832330e9efa594aaa1762815da70eab0ca8e78a5f1022c3969f533b02a201ebe51a05d1c30d0d426771125ef09a9d08aa566b0d2de107b0cccf907c07a5ac3de030788e4b65ae7671f04daaa60351b3d7d6b8d19a4a17fe680434b259e109e5b29035ab85e2229805162b50f6ebfb00eb6592e456f7591d851904a3d5e84267a86e252645ed7b79d11e800d37bd3fd027a890fdd95f649bcea0e54e52cacce67a5f897012ff8a4bc41a2a9a7e835a1b20c9844aff981bad1b57a2841ae2d65774b3062e8802f3e4f56e508bfd4f3b480aa5ff2044dfdfed4d82b43525114d325a93c99ddc1c7708e298066ea1bcd2e012450460a538f2014ea0cb5e591150756f7c8ecb82b9ec9a5ab4a0bf68a1be514d8792e115b0ac2033d61e159473dc32244cbf53981d1cfcf9e409c234f95f355703c12ac9c044919b33060f8a1de59deea74fc9adcf1bda0854c11fec9456b78f53d5e661ae3748e0da7b820fa8d2e49e770466ed6ba4c12296724299e9644601610a46b22d7dcf970d3b063edc22772b38f432cd9078cc9b592d060823a872511e933bc89fff7733cc405394521449a1f2eb106b8a6d89c721b74a503393b52a00d22b4c018aa7a2ae0dc1b64b98c2776a8cce4289c4a6e0d7304080833486e27d70eb6fc0498606b024381cf34f47d667e48a845f9f0e9a4ff888e21d59d141ee03c8e981ec48e7bea81b6ac8f0017a1438e6dd0929893ce8fa94b817bcf137c1226d645dd44dacbf9ee90db916b7412fce40095ed9a3298b1ed49ae9565d691933b59ff28ed2ab1d465089295db5b09f16e1445af6f11803959d238eff0208b17cddc202f0e125150f0c9e389eaaa9f87ee5b04db810e870715744058884aba71f5eadb22d708b193af02b227b1fdcd1e5834611ce54ac7686c2f9d50914bd1b043b933b6f7cd9bdae8c348f99606a3c026413eaffbf7395c0c0e98ae833abe99e9d848f888aabc4ad19e50e6f2aaca49088548231e7935387ec181b29cb1216749f34b86ed5ddac03c3bdf9210755c49727549384a93aafb16c02fccda8cf081223026e8e2f962a228eaff5412dc4eeca7ca1f3f3fa06e57ee3ca57c87083922597555c7bca26459850396cf4f5ac9b79908240878b1374595e16264c8a21e84e9786d6fcd9125bf57fbebca849194f451bf316d27b92f3b034ce3a1fc9fbcb1a9bcf669ba8a6470255d9f2fb5af6ffbd0ca1a5bd7dc1dc153eec0ee46fbe1d72dada7b78e1b8aa42b1cde63b295e0ef8051d94686dfa26070202be8b0dc79d719270007c3da95976a0e40c2318ecdaa7d23074e8bfbdc71c1d4c08dc68bd5e7f6c8d7d1825cd5ded13ca87affeded8008588d398862a29f2b5a090c5cbb17e61ced0d93de6679db5f0073a347ba7881fe40ad4586877f5789cf6fa3caaec1bd93b2c815ce1214d6dc002075b3252720a556e22279b95c763ef4b01a5bb2c1c419d37ec271bec3745219299c298994290c5e910df741d7c51f141251edd91e4dc13ac3deafb043b4e3944fb5029264a6bc65227c47fc7634fa59e3157ec00abfa6a4165ad7703eb2e9604af041e0657a2483b31b8e133ae8616a147d0a3067619dcdb3947787d1d9d4a9e0aee082fc57b98007253a58bec265a57483c9cc9d0b228c5b845f2ab5fb813b71d45a2f490ccfda7823e0820583a50045d2185435c3c6a2758a7a7f47f7c00c3c1011ccd52d5a74224ca0ddc0ee834581dc95b48d248c8be9a8397cfb036641ad29d79ac2492fae44ca4e9c88c5a078bcecb6398fc13baed11e16491c7d2fa8029e7d0cd13c4c59db2e2461aa2359dd71906eed495f225e13cd1785cda23442e834d4804faf488f9ef3a27a77a7ccbb0890b3729a76fba2916fbfc56e056af563fd6d4c9df78c2cc14e5020147e60c59e0ee42437e00f9823291936cd4e5d623ef60226764b072f5d8a4137fe48e545ad5b108be723d858cec6155fa33a7f57e6fb51cbf4572adc74b84c3d0ed044820a74d421e896e824d346a6de3d6484aa2e7bdaf8d83b64db19b23d72f60b2ea4f3b4f16b2fe76a76a99184cda4c1cee8aede8807585d2e6300becd67741d6dcda197d5382487780a11ac4fe9e0e2f0cb8484300843f482ff3aa7057743e55a5395ec65d771715aac715cb0552161d50db1cb0e33207352e6bb7a66a25ed9fe10d1e2759b547c85bbc0d8fbb15b83dbdbc8901fa02bd89c2d6804821d7baa3569f3a450cb1d35f47dfa7e442077d9eb172f3ffbd5c40d1a19ae8721e4c877853116c0234a6429268372c98f4541d5894bb27c4ed3447c249b2e9606de5ccf7132d3a52c0337c1813e337acd2cb703d449c349ca9675738d177f6a9909b9d613bc08c13675539c8e7c552c98a4ae75ff1ef34a3309ce4be605ea270cef6200fb05a8ac3386859ab3d7331fc40f43bf03a034cb5f1dcff31f8f326c24f7c303f151f0d6f12daeddb0151ce2e117dfad83bd8ed2438fa369e921e4eab5462150b4c705d84c558257b8e1410ace13cee58870f8b4fdcea69766a82e54b1f91c0fa649da7164ff6db9ccacb708d5ea8d759f64ba334d1038dff12982c1a1d77bed4790b37d614d12534038cbd834f20a7b1543a363e892342a7cb5112d4b72085f8e46182e853158a50ca941339951f1dceeb9c3e280ada5b077f20de76803c0cd04f06efd6311815e2f54199edfe4575846354fe815a6f2f88da4bc1b27f5ece53b46cc630f479926c1231549be40d05472436d2abe74d3168853140a8dcd6ad150643715af9bf0457a72d4328216850229b3ae302947aae5bf0dbd70b4a6bd3c5bbf52c2aeaddc9f44398d19792a2c1019e3486865462ab4fe70177d3ccd81cbb2d799f559f6fc3257ec8c78d81cda8a8f2671e48bf5d6d8083fac8b2051d06dc540c6c9a950ea6c7a775ca2513d213d11e3f4348bd498e8010ed4ceee0b0348ebc34eabf7ccc70833c3f93f79699e8a5661c707679de233a46fb8ecd08412f5ae648fd7d6fb7db1110238612e138e2692257301f9881fc389633a447157935a56feda347515d15682a9fe8b4ab6af6aad9299fbcad64cef571b06aefdea183b1f50e63597aabe23d95a28408e6d53a94c098cdfdea76965eaac0ac9769e294b0db25ae821ca07d4995afc70e261cd8a5627bfa359bfe671ee25ef727f5e4d005bdf3fe39e47461b0f6f7a387f65218d5b054f217c7aee3dcb3474429d2ac5a41e489451eb35ec56ff200667a4964ef207cbb7b0921c28bf0885c8db9e9ba8c5d63826eae95a43f22682b7ce7cb9f21a5aee3030933b8515325ca80233378934536056cd8b8b9d1a59cfa73ee336fc48ce382ef0f5b9ecfff578c4d68f1de7fb5e33236eeb802ed624ccd5cbc6c009ceba0e2047307d0421c61e0aec01e06403ea655a8ade8daa7d679f00cdcaff95745d32fc2a71c21444400afc707a16426b02ec477b7012c945ce0095db3e44644f35db20e554da1e2f5e034849306b098b06798703ec34f8cd3c63b8a83bcda834f5e529063691a9a5e360cac0a1301055d39b5a83517832466296bfd9aa848b3d200a25e467d539da836f55873fbffa4559e055f3f5b3cb9d7cf4e4b1ad9f79e9b5f208292b05908b0782d30f6b444f5398fb53455de76048a80d86c1a576362d3398a7007259c426adbb80e53486474773c72334fb1aa22ea25649ec852ec78171f84a7668fc5dc393b81abf0878b8bbec17d674895dc3a1a17441f975f3fe666535d18a21bb7aa31dc7615f8c51652e69d3ad29ed85fe120fef3bb442824bd306d5b16a98fedfa7f7d63aaf38cd73b239bc70673c2173a8ab90ffae8ce5dd2a53f2b412f2ebdf39bc9b84a2a3390eb0955d0cae0bcf5a337c620483d81a6ab57156988e2e4b912fd41a5ca666a1781a8a65d068abbc2f0b253bde1bf8e6b7adb9e3b59cdeb3691fcf8156c5c41385fe16389236b1188d78fea20185b2d23732bb86615d2cb979b8929d7498105d3a9d3a0a4ed634c1e68d9563183ae3e500fd2301cab3fc677391c11bb0548b2041df5280560179bad5e255be374a921caf75d9c081e9451a69d6f618e409670d89a342d981a6a97a629dbbfa3ea11b4ae510c3bd9fa03827ed2febe4094b0eda1ffd67a6aaacba3ee740beaed62c9f6ebd29861213a14dc49cf6a456d516caab78d6def3ab54da34e6c4c278055efe21ea36312e531386f5cc7d5331b6444e95b649f86ad6459c9ad648b3dd238f6d38f43c76b5573285c6b30b1342d9af1eabe5eda421fbd2372c255d2adfa689ec224bbd0c9e11e022958f5ea73bf806acd358fe53ae5e32558abd4df66c333125aaffd1c4c055845cc759085a9f68621b439c0229bb45fca16f02039ca1bdaad3c8a5eb9c80df1408166830cab0ba448cdc9f757a21a5370659a2871750c302642b0b5d30d4b1112445d202258629e09dd7a1ba7187aca385c0ddde6e8e276f308bf812ee20f9fe5af4b2db2d5d3a701d0e6f178bfb511b59c2dc02f105980ecab8f622e42cfa4e5077ef2ec6ee454bda48627bfc2843c118cfcc13d0a8fbad1179d0aeb6b1faa4e668e2716314ca19a16d9a64660dcc180e9fbc67248de562a02beb36adeaa23c525261a730aeb6cd7ed1083868e28767e9d55371e70a6d75dab3e3ddcc18012ddd9bbaf3b1bf6a72303ce7027a50b46ce6731de1d359c5a1589a67ac496e1e0458c0537d080d6c20307992f5a4e02225a1c8f6588fffb298026498c1a16e5bdda4b41d4f3aa920c3b64c20f53a12dc489aef74e16a097321368567d2bf17423fb70ed3d1d095098b7c4463de0cf422db2fc0327f96b82d5a25c3fc3e1bf15894f6ecee4a9c16f1149c0f71c080d8cb44d463c241ace4c86931019f89198c485e6c426c79aaf78620e9c3af91abc0c020e9ac6d590ce233154fc050d60f4c6b17fe74c90d6bd2244af2cfb3a6156adb6c5aa97df9cf70c9ebdf9f1b6f2bcaf38db1df4591ff08c433da71e9c6c8048be0d6503f7b322a1554e9c9c5f970375ff57e4334f880db53f7a0c7c00dbaf70fbbeb5afda25129cd38d4038fddf2d6e568c302617ce3d723033601acc94fc3358ceba12d18cf8b3ae21873075fa6db0b5f95f1c8e1cc83b121be2f6b04b57df10dc80c195703a094910bcb3b4aa38c8685ef223205644839ca41a0806cfc1df39c169e5800cf957f100da1d63638ff0448513cf4244c2a1a19765f35f9e53c08e4d0858738659c15763279f8c4c10174ef2c29ec00874a36dc032725549a3b36f04f0d2f9c06199d6017f9df8da8f738d1bb1fadda8e496bd8bc5de0676153e913a618faef9447d82ee3bec657ee49f1cc842fa62a7473464d3f52f5185c316035ba6e9ca7cbc1602c7bfbf06ca4e1dd220f4359d782db687953566715e2ad48a473852884e6818dc80a1a60518e46cefc0efabe8842cf21c35978b76905f2d11e242f68d60809a84e861be4c0a8663e6eb2d8448fdc2c36a2b72ec940b0bc193d39f6b96f67a7c6aee784f4e7ff35a7cb3fefbf936bec178ebcaa01801bfc8122e8d031a8e1b883715496b53a30fbeb19f05274d2f98134f0299b6b76bccdbc96b9589130f94ba527745d2971c1091a45cf62f738909b968c7d032a24e898b598514434320b7a9d4e7ade32c2e648aed75d29fb6080bedea1120a695808eb9bb9c6f635453d89a8fcb1cb0db7665f6367982a645e6d6f32e9b20fed1b6455943a6f778c92a70368341e0f477a5e5cfeb50d76ab857ede16236b256d956c079987c12f2942cb5548ecc505a5a6085963e3158341e6ac88e64a841df6d8404e5176f8fe533df1694ded27cb68dc7ae75b24ec4f55666d2aed30a8a375ca39515958a055ca012f0712f325af5977185236ee6d41b38472bdde0486b80aa7d01f7839356b9e7567f0f32699da9d61d8e57fd051ede4e860ac8c64dd4514390e6b58451be304f583352f0b98b3a22aee804facf15b8e17f1054714ab81d03a0c3c374ec88b84940fece34fbd44c83232f741b045f69c8ff72655dba8db99050ffe3250f95e91b7fd8f75800e04002c8cc09e3e05994238e0dfbe9ecdc3d0793cc63aa82c1fa9b5aed893556298d8855d93381621a6d1e8836ffbd9443d950751aa113f77b2059a49c0b7b55936ed58033aa63aae01faf579ee3160822398a78a3c724ec6aedc7f5e5a55cebab9bc7cc2efa3ef29fb3d1316d2c2032d4ab9dd08b3f3672f52b34605dcc4dd92da25e056ff72f149b2a57eb2a02a7c06ac774be99936f7a95f9fc5cbdc16f2e7bae161d30eb2d6ecb0b6da71ad73b9bee149582e0f115517f123d0b3dbb5149869d48c61ac376f7275ae80a05ca1c96f5be100434696a856cdbfaa097b81c8380f1d3447f7a1b6c906b82cbe0a986c22e4f9d2444efee664258a37a950443a79930008e4f6e5a44a7b9338807f7fb6a3918ebca7fcf92b1784a246689aad8fb6a6b3b757e63e193ee1b21482084275774706f1071dd5496ae90cb4d40f8a95b4a860b7c9e8bda8b7ea047c15672bccb71f6580742f1076bb6d45e9a960f3b183d9430a8f7340f6653ae3d99638821933e87a33926a58cc93827bdbe353afc8665a692fc2857f61e5415498d384851b0744128706ae7506c778cdd53f9423a6f372cc97c6556a520255db3ea4d41940cee03157554bb312212d61f3028145f001d0a5a7b59750630baadcbc21f6c496ba8049dc9b36407ee19869ad48a1cdb3cb0c873c71041007c403fc89cc17aa9399517f404993b5a71f9db978a4c2252f504514ef3afbf0b6f42178750ed217324be3c8295475c608a4c690a758070e324d28a3772a7ebb6234f90cd26aeed577ee28ab2bb58695a93bb18559a6949a3d361c0a37dc8ad4204f79e383aa1d9e8c3c84f55661cbfff834b8b28a1d0b6bf0c809301c9bbb9b48b9bf0630e51122d77605e35554816e31df18f7dd7deed2d1322bcfc0a864603cf7f2d7b52c9f1e2bde5d19549b2e4fc387c414a6461fefb147801d5a3a0a9bfd3cbdd9693da5b03ad6913e9f719a06b7833fa25354623ab55c668fac3bbfb40e6b8d4fa86f8cbbfa03d3c31b0c62d984253f7a20d0eb0d164d96b559fedb561f8bf32f3a4bf2d48f0aa0107c140e5f8c862295517ef4e71cc239eb7e1adc3553fcd612380860fc5f70341b7c1ed31b486265a8cf1841763009c7cd28ebe00113446db2d555d3ede98cf10e33f9e0b3e2aff832fbac89f058b868f2a89d11d93de61f9875cc02261a882431bad701ab1b94ecd4af8e690f3694ea5a25231e2958a8a95e20f6a5fb2142835bb1fa199082e5330f87c62dfa72c154acdee43401309225c4625fa43f8e79861ee42e23d367ecadfb97ca29ba309f043c342282459754c065c7415eab8e8fcc48f0454dac240a468675cd4e1c62080fd0781905057953ab4c50b7541a8b968e234df868ff6163887f691d6d223c530bde158cf01bbae4172db385003130c8c1c51bc94d09140908cd931e4d5a44d87395e8f4b2bb48f7949c1e986c9cf94a0e6ef637b9816146fa3d72ce679a6d7fa8c5426a3abbb78ec46784e2f74a755933d27ae2619bba44611bb8fbee23317bc4617bf3d3f280926501c1f602da2507ed9b79d32817163c6fd8290b26a04bf5c20a46eb97eac203903e988a45e82b5e7509ffc284bd9726693c06f97276f3c4d64d9381e350b4b190d872eed228edd5395fe31b633ff1d4feae5e04055cebd9288d9e8df0f742b3699c0cea4b4bfeba98584ceda9b6d835ee742cd10e230ccb02e0fbec0247929e0b18d544c72b0e54bc8dc41439e7cd018ce3e414f9d7a4635e5d5e7c47ffed8fa3f38210a7090579b288c7b67b0ed7270ebacc9e616ffabd4496fc1f8ffc0f6f95a866961bdace9b25ac07a9d75e67583df5c6051584e2ffd40d7d88712e2ceb7f9cb11240d36d520e3b14f24d08194aacb83173e76a07a63d94d4ee5a8a42a2e3d7c97b397cf394e4acbae4b3908ecc191850c20501818d6443e9b03c300208c05b1e020977c516418cca6f0037c7586ca906ee374d18953582760f138c0e3dffebb61a21e2c7d040c0bd0bc7939896543578445ba43ffbf08d3e2e6ff0246150a6f788e8dda967dbfcad5baaa47e2a7ca908513fcd3d60094c896ade2a9fea3c594743f0c415f4241386f1c17992dad9d9602dd9b439fab38911c82d83c6e56990eaaf58c064a05cd0a33b6b15c2a29ce6ef8f2a240f75c87d8cc5f067e4842fcacf179676dedcde152e665eac1e5db5d099adab6f67b78fb5efbc4990a0153c92f708b7adf85c0b73b07360a1896d486390ac1f25a2198579671bae6c1ab196bb349a3dcf7ff7868fc2a6fe0ca26d8442fe22d6b516a402f1f98047bd0856425e823d6cdc63839cc10e9480f6a5d94bcbe7bc9129688bdcf5c5b21b0effe619f856bf0ca2278b8997a1097fff7c41c16c283d5e9cb7aa0e177e7e51e445b8a193d0756a0db5b14ab7b94df9f34490af0ac482a2dbfbbf7ab87b0f881391eb3a36d6ab1d2862f5700d6f8dc3153247eb647d7a9f19c1477afbe735678c2bb944e6544f70b5870b79ade76976b3655cc711d73283882e4dda615d31ed13349f9f87f1315ca8d8299a3aa61656db49b638d752332f26829cc3a81d40b7b654e2f3103e1c0c7de62759958192459ee9c6e330a36a305994e2ecce9049d5220707d122530e2a2838a7b035ae3f1b046c46de8502c53005da64aa9e2c50266a243d78e259037c163aba5026f9812330fb5eca11fe8b72830d0e1a87538d853fd6eff864af90a4b67e193cbfa2fd6d77588e38844d6552269938e7aaff35322fd6f3ab41cc8eb935ce7baa3ef51723c353e64556fb9f60925acded903d299a323a3c295de2215ae4f6075ae4764b6743486adb790fec70e886867d72b24234fe039c0cb8c2253b4808593be71e5b6ec890286647970e6f0aae50f011c57034a6520093b66edc9c608c3d50077a15a1d09488bbfad5b184332cb6ef20b862e1003da5d6eb6bba5520e7a4dfc630cc05c055671539724d36b9b4ec089435f59b8fe92e676b50a8ef743eee8838d5676b05d966be614eb98509ed7937712bb478244852e8216a0da78297922c7961bf4eb5e1b5d6c710a8a45f894fd18f40083330792faf569ff6e89e57e3646f1cf05a1c357d156c10d7ca74cd91940160ba29402c76dd1c4fc5925c7949d3e82b3c57e65899fd0b721d645a3b91e1c03d78af8f631d971f482e8ede11976f9887d9e7ec76e6e604067897148a1f1a1d35b94a51c574e9142a42e61349f804b1a4b76ec4a47aa30086223c9de43b6074dd09eb67e86d3c631e0b578bed1a1f0c39a382dd719d505c12b002f08161b151dc71ab22d58983095351f2a8b0244fa9f8e1a22ea28c90860ffbfef85c080f569f9bfed4496ef9e66b62d1035df955e194381f23c48a0511a451b21add25cf59d3148a265e8cae3b8285b41dbdcd3e4a9a58823162a2ac3194d28b11b646d13031d3d5cc77046077cc5485effc778a10845d03a4381efb3886b93084b724dd67078f4b0c1355ebb419d373ca16283a9ff18ed12a8a6cf23574bf592359eb0b578f67dec1075c337254f0c6e7545ff172c589271b2337ab06f2309eceda0933b2c151bf617ceb9a77bc2d183e3c0e12261e14e076542e14b857cf83dbf62642f4f67e83c71dce831540565e9eb656588ad54c6bedd50f3b866da212ca492cf143a1e36aaa430d3e9b0ba6cb114cb6986a457f47982ee8238806d61946322227c2372a69889e7f5757da3da4536744fc771b977b1c37a95902adc16f5c5e85d972be85fb16e3e8d30e4a04cc43af082a1c77097db801f012a084d50e23aabf9e68ebf4491428ff8a88ece9d02db1167ca1934f171c41c66f1026bd848d072cbadf7982f6e6c883f0133257945cd087e779b1b6f1c085d38231ed4266f63436296b0a25e65ce1eaebcfb83467dfe85b7af59112e486d1a1c73b860c356da1c90268dde2e0a088b5b60a133cb6811674a3143303d2de7f098a12f0368b3eb32ba53986ac1d631e3cfd69557fac28336e1ec964e77aadfa1427e3ac51fe3746fd782295a1ccc015413f00ba019aa3a0ce15ae65715f29e86ce2c2d530854c46c35d2563fc57ad23ddb3faec90ab1a95836a94b084dfb4c451e15a425ba378c8d6f05c51083da715000ff138176fa56e69eb4535b3910c4833c79a16c18aa2643bc43357a05d2fa9c7428e7f027d39d8bdfd0d3e0b3e2a87c066847b16423f783871d7b0a295a6048859c915bf910762f3acaadd930ffca65825c6ed0b944829c85249ba17ee9ba720290c0a11e15a0a81cc331eadbff9000e22245b13652e944bebe3355921f864b56a332855bf5459dc1ad1d0f1018f6cba305d6a5439c1e174bf5c238f1085931573fa1263f509f6535f93075ce6dd2870254ca19209c4ef97fda9683947e612db31b27c3b1591ea4e14bec4f43e526ffd8e8c0ded27b3de059963237d40f62d0373e83a4db32994f1bb58d22f19f189cdca89c9e123af575eda3da0af86c1f60872a629a3aa0432a3000f0f13e0f2f04582963b4a99c1ab73c030973f41016b002c6cf815f41258ec9ef777c38c00930eba9602f704ffad8403e6224076ef19171e302d9d933567e5ef67bbc8d0aa02fb21c3b360ea918d1d37b9a83ae69711b10ce516a9ea7540554ea6244581f62e17a9bf2890dc2f35c344f57cd17d766896afe074577baa0848439803256f3244bfd236b2f5b97302b2cb0fbdfd1b2829a2954e51e167a9b8f4e76a55eab794c5bcd45b998125dcd661bf55bdc6a6e37433efa931702849570158c97b0915418fa45b9c7625daa7910c274aefaf7b9f20db8d6795106a9df27df340775be546e4a6856f3ce93e6685490b9104db7afe695530b10e7de7b8dcce4829dd7592519b1f33953a32876ced78c4cff3fac91d2e731769e9fb01bb8dcc32d1109ea7a8b7090830f532e66b981e259d8b6e2abe5d58929f315cc921eb6f324e14360d92a81158713d75bedf7293a4183cfb416efb0e1b167e0e0175709959703ffdc0ba232eb6b05ccd2c5096a302cafc2da81914b0b98c48d006be8e5113bb6dbd303d3f4cba1bdebfb0b85421157810daf05650be3404dc86f82896f23987c0dc1f2d77991a721ded00ca154f07f6714c1810da4c4ab046ef3229f8552f4884661c1c95d8213ac57745c635b5643e6eec9b421fcd24eb5cb1acc772193fb4af6efb78ca097e44805631b22013390437509ca1ae6976d08762c329f2591f3659c16a63437e2590f4d51753da271e053e8700ed9d4506c61b4609a3ccc8d43a052f90a755209f492882e09b8f7cc08fa2baf6945ad4cc5392440189ac03a407bc6d97a17f5903ec5ac90f242a09a4c14d7a68db625e57632d852c91851a5686f0f88e07b26fba3d6a1cdffc50c1214f90f2890647802a00e3f6f2a3ea9fac4706a5162d17400e7ae55883e81cf89bec83be51381a96fd01231120534de58ff1022a412a683c62224b607340f1dd22cf92d7d2222da5229a504f8b4ac4561a2b1aca24983d8d1ec666b14d638a32c21000dbf142e392c3dfb50dc7d86df64758a5bd6d1e3cfb712ca6f82ab4d0efad56bfc017b11a1c2535a3d4ee53c5e53c291cfaf9fb80889374e5905d1e5c168a7fa017a32974939b89eafc5ad8a3605aab160335a73da34bcaeedc667724ccd66abf627b6d3d828879963b3fc16333f15d2f89fdf79fc35b593e62f09021e7bf5b4abd5f6ee9540e0ac64a404b402561825892762c49b54b4b90cdd55ca07790b5ae0c120759df06f125d0653bb57b60ca2494d0f2d26879bc5d451cd482f98cfd86602af6ad6400f9330ce1e269da37caddb809705c2e821609e43900011b4a2c749be85288fd2b51d78af19cbe3a955ee90a2159907dc4836f2bd90029f8ef0d38193e2bfad53a002b372684920761bac029902823fa891fc5f8135033144397d088b409a4101fcaf5eb5848351a1eec4d17727ed9dc398abc72b4043f3a3ccc66d0e4191fa4530532d434b4cc3a38876805f1ba6ca4158c268995f8084b9bfa6d1812fd0d969f26d7beb7f0920696449cdcb5e2d00f336588c8ea226a7d96aa4e13a19390a75f5106a695e12e28c333193dba0027582167e9e4c86357736918890e23c673fb7c1a11a5c7440b7817b5cb6e4dc16f7eb290553427cd516eab5b9beed64ee29853a1d9380f802f9313c2f22c14c3a3ea3cc51a31c72fc2f08b829eea7593e51715c2753babc29a77dcd2a5f4a5a0086a9b337947937af460ac0907be153ec4f1ba1a70700ff8528bb04ef24eb6d2581aca9b105604bc71d48c7986200b02a02f78f068bb4b88abec4ca3d9331e5d799dca6b031cd31060c01fe974d58c6e51cfd77347f217fa113040ee6d1cd83f628f1e9533d7c86e057039ec1241aadfdc1a87cf3d7581f89c4c0f85158011838f315d51d290b213b34680bc8149f5cf0248803de2a7e0786460c71f49f323b30d0963b1e25a78b1903260cb1ddb185f7e48060804cb21d6cb3290edf817d7faef5bfe7da20ef5d0726bbdc9f863f9647f9ae9da41ea9362dcb29328853862007ae710baddd457914c988c0ab77f73a62a43d1d4648738195f805b2ba6cc6d10a5cc4d2f96532fad85b3d7db6a0a58449fdee7bc4400ac86b0377b7baebb057f1180e468b3dc896d8303123ceddc1d3ca00713083fca8aa39f1b8f641ff52e96d0997ce2e928e0a8696526c0333e8983a2a807331e52a4a34d165ca878e0a857c2c372357455d46e485f9ed5e6957e0be7b4805422af92591e514b2c8b43298755f52b1f80df9074f850047f3cafe14d3650cf702e61a4a9aee40a0a77da4ee920aea4ae1d02afe73218c0c684e3def652e853b990a09001304dafcc95400da31046a31040b1e8b3417b245a60277895064bfb03dd565a490405a8a55ac9d7312060e44793293cc14752a4eafb702cf327483bf831b55e3faad008ffa3304644d94f02ec3736a99d4ecae244c1965af8823d2570a584c8ddf83d7a1a57f541ecb1626fa3e9590f1c071be678ee00a1fccb31457fbc451ae8e55d3ab4beb8ac5ce227b0d70094fbb06f1356445c6c6f69c32a677975036743e5ba956b0b33ab14d0f28aadbce2fbe7e75ce331c2bd77fa844cbbab51d5b8369e09d92da7bf9b03515a8b1c9bd37043e0c654ec4d0f73348b0394ed88d3bad81006e291e1a8e6621c4337cb8e5de023d22f57ddf75ea7dee2a871792ad487a5e0a804289bb2b156ab42c3c2119cfd3f5ecd4db1db1c6e2316ebf3700dc117f60e8879defa2683d2af9b4745b28e5826132d3c8962d2618bedaf61fae0632692e8fd9338c46b2561a23a8468b7f64cb7f6d61a6925d0104382086dd9adc578adc853c85c68e351f0574f3a3d0e23f0a9d1048c179db6931d3062ae458c1874da931e868f171015794d447c122498fc63bae2e8d7c2c1c74c0baefb159089892efa8d20417ef956b48599cfeb9a6b156b9bd2d8d27d8a8e7c1ef7c3fb7b7c02efa32dddec9fd36d66e6f713c5b6118f1f6a6bee7535770656a66aea17dacbc1c8e56005b9eccb7f729d831b8a76f6fd0622b09c25dd06f6f0ee8fa24786fdc09ef2d633a278bf2a4e1bd19cb7d29de9b3a18ef4d38de1bc64a511192b6d1de75bfde2f9f70697263af59895a0e6006c1cb13579fd9b36323100be6f47d60922d0300a0d076a2cc5df0ec4ed2c046ed1f50412ebdc12b569dcd6fa338a6bb4ab34fe356c10710e9c4a584dcb92bbb132310e93571f807221b47e6ce6988e4ce94a271bedcc6dcd67263a796f89e4405baedf58b12e0c70b16b5bd3adf59a6d69ba7d3284b98bbba673dea1c1d270207ea96db6e7c15b1ff1ca334a8d31b559ff0097986c4de0d3946188abcc1f18d824548592a9600da1e709f80d699f27e15b3dd09fb7c488694ddf93f7fbf9009b903344a818e687c27cc27f1d2caa01b4127ac905e08fae2bcbf2adbd8a809f343c1678bfe085159c3fc4982fdd621d8cb3615816a98d626218d6533367274315f15253ae1b9edfba2a87c0a37cc74aa0e3edcb499ffa9803661072a06e9430438886ddfdbdf8da8520bb8036a275aff315fbc4707d1d9acc17be9e83b7e88abfbd5f41c532b7ed5e0fc32471f1c2aeb13e285ef98bcbd89ece0829eb87320327353ce57039dd66d8d8d6f4d9c8162d35030ba9c4718b602ec61a02f3de9e389ff055ae2aeedfc8b0aac004af68f2b50500ae70b7bc11f355adc49f79fc1b91578babae2a60991988076401619e659e604aa9f1dad3bea2fbbf2d7026041998ba87e16e2e180828b326b2207407f73f55ea03055c39e3a9d156112401b9fd0d343b45043cf7d0231ca04011846c6e58cf140c428bf89e96a8ebca4801d67ad55ebb8272c85cda5b4ce564a2d3c9913501b437109db0c201c25904a1fcef954db5cd2c3fb8c5cd8c37b37b9aaea4d9cb72e4e22e6c4cc070ee77f04a10200c835f7afdcdbbcff9f6a4fbafd6104d92b408ee8ebca40c62779041607a1ee240872043215d3eecaf626214783dbb5e873a8d4fe670132f81c19822c8380b86b1136e5ca76f65fa26158247b5c8c9d567708580076275670b9a27161b7a224ae440950122151959044e4a3f0c21d54fd0d7fe441d7a26aa061ebeeb29e052c0b088ec11593fbb4d20960fc9d516b9e2bba80e4b3bd5b3a6d3cb6c1a4cbea587b6aba4110491137fcd49de21e608982af8facaf61dddb8d03b1b78185e48e5279ebebeca7a3d96b03ede80f0c6ac66287783276b407007a29416998830fdb2ffb3350f33464216fa73b7c4956a180e87ed64bc4394488ed82a0a69d3c0ccdfec3103b5f9d83ed13f56e88d87eacf0696ac436a09bb6ec28a04790b56bf3960caf4d4e7300ae2eb1dd336f17a6f558fee7360c13dbba95b5ff8f2fa79a235f04679b59344a2cce45d19cd8fe16c9365090643bea98eca349b67f43a48d5eb771d1bd7099d83e2dd94ebc976cb7bf62b21dd9e1ecee0201be738fb11df3eed7733e2ef391d9a98a417728e1635fb25f910da2aff243a87e8812740dcb43c347ae84d5a231fbe0a3e6afdaea779bcf02ae1bd7365e33ac94fd4ffc5866d9c9c6918069e650a50dbfe2eb3702af6c7cb5fef777970eb4b4445367e2918c1afa945c80f2f083bde7262473e7407868c4ad46260f2d02aac0edddc40ab647518ccabbf8b2b86cace2bb80e0912c179d020d6f9605c340707a2a360f0f2e517e8cded356ce3109f237056d0cde8fedd07ed17d5af0340a090eb7759163befafbe954cc5b5d95b93562618bde118095c42c4f994907dda1f75ff7de3a359e4c68c4b1e0c9e1c11beac6b15a4fdd712e750f35bfa3f38df24af03729d1d1a4016ebceb2638c3b92701ea8cdadc5e68e208daf774b1834f2525e0f6bd19e783531035c1e2046da988ab112eb5c4a814e48506900c0adacca193d53b5ced55abf5bc755d054793f654809c4aed94461a7d35fb242cd74cbb76f268cd7e425b444c69e1578ec74396daa08c97ecc82c9cc8ee474e8661398f480e27a27d29a2fd6a3639fe611e077aba4f88938f4c4d01006979fb04e153b3d7139893dbe87f3f0b0fa1e7ed58408cab3c91cc1da3becdf3b77137fd137ef231d970a766123e3bc39e70cca647ee97f7dfd7769835f1115d05a4b0860536bb06f9b4254a06ea4369bd964ae1b4c46a283c066acd4eb691cfab8bd71a6745a26bbbea2962a4de40d2647b850f20602ee03f938d6403765025260dba3054eb8aa2c0cc09227f4ad941f1835017a99d27ee82a28e44bee530dfde04edcd047be9bb3ca203f698ae9d186bbc5d00aea1eb192c8269fe6b0a5f0d855877b52e40f477f7b65cf9d5745874ee9fd24fc29da1e82e94cbb5595cd9bde10adbdd433200b27d4ffff0f33ed59da5de3d15f22676f854c95a0b9e5d4572ecdb373056aea4b153d7b50ed2d036b869dfaa53b236695c71203f299616e7d82ed5bbf36aa64f89e8e071efc455348f441ee51a5f30e2d0ee7e7fb41cc37bc94ead20e44990ca906d02116dc58d402628c7f560f3b1c9c21619f3d402b2ff2ea88caaf9268d61fa32c1eec9c54d8862790d0c45949e0c8217bc40efecf754eb92ce79a8b9c8ec510517355b0bd5e5dd23459d1c1260ceb3fd29a3ec9d08b27fab474f6b72f5d6719bab7b985851b10c1f4c7a43e7d650aed44cddca9aa89f546d13b9adc93709d187040081be3c77974a0b6579ae1cfac95ce1d9fa89e223d5cf012de896d743975948e33ad507bc7d6583fdd49987c792b6539b80ab4ec0a484c91cbdfcba5103f60e373385ba9e92d2402e05a83edf00e8d02b0b8d2cf1a62fead7c2d21e623efb627f8e6d3d61e454f6906100de272bac1ea0c2f08c258b10734f9d7977388908ff745b874c471830595c75e687412b0181f5631d82ec59f2dc34e0408f8e483b815ac95e98a3155d7ac0c69e87c571f1d2514716c920cbdda8923d97a0a68e9ab9c833f3879cecfc636806d90d6076a147452dec3c424ca01077aac0780468df586524bfcfa650d8f156159a57f6c97405dc99afb7a2f0596c29a5859cb6a627a49a9d03bfe4e05c40d1eb68830ca030fc70c59e716bb7cc50fa90f3f5ba25174a7e9635cbd8c26548d793056ddafcff42b5a4d7816ab1348a21aaf23f39d2898ab2026aa4146eed1a097d2198f56aae189c56ba9c69190d5fa8ff15b9104b620cb32e9e4f3a1440ae43b5b05aa83a09c8f55012c83523ee150344996df0f9ad93bcab3abcd8b4e6238750946bada6bf58ccfa9d3fafe542cbf56cbd893e7c88cb5ca3e2cdb5449204f0aac971b4e65538d72d2fceb5e738d778c17e35376dc9aae8c91440597ac665dea50e2ac61aed7f85a8242b01135898e56649509b3bf8819bcbd6042d8a1e5ad986b10a75bd2c4776ecb0eabbedf302d5452670767cdd38891ca995bc7d67aa5d197bd29002bd3b7ae34e20c7c33a6ee1b76242994bbc0d4afa541bcf4e5cb1f1ca0ea017f94408d4e1098d1627eb14e4981842191b3f324ad6f804bf7b5ec48443d4256f2487808e8044098a6afd277432c50e3821eeed78db1f7e0f76aace447223585665940f5cfcdf23828c9d363ec2f141086c52695ce8a11ac2104d04f9cd0a6447e91fe5eaa90206aad9ef25a604bc88be6b26a692e73d80a41256c8af7bfb0232ffbba00422c8f31040504dcfe3054c1d19de14590c41b050aaa963102cea20764e5b60df3f466565b598cf3b15cac7ec7c72f09fd1aaa505cad3802f4163923529778434bdd0d29f1718bbc10de71ef3f237fc76cf0180022507c707f034b6280af406506906204c0d885a15bc8d3aa475fc29db78eedf3017253a8c0ec6851636fe7ff49b7e0d431eacba6f235631ac0871389114ad393ef8440caae1394d4eafe80451ec061f54c936882b6b3c55eb8a1e9f45e2569de06bebf1205a70106e8746598fb9b39ff1ab724d893880639f7c8e0d2c328319a186ec828becc08f939ad9f1e15394f850bfcb2c4cd6b8a50a42ce74a5725a3f8be5f321c10278cd2e92c0000004d081c3b9a7319474b6a5100d4ad14bd1693d6d19a74efc2d0b694a0839a224f1a2bc2c871054fbcc212a31457cfcc361e465f6c8e73d14b03a5be6715b8b509715e54c4a1d5d3044a8ec958d5b60772ba318452202e9ab7089d0decdd3842e84e2170185e7f2c96d134083d4e732378d886f6051eebe56db57854ae5483b2b0b3151f925a2c8f1717949423885048ffe61b6df6892b1139846d6268fe36213770f3d23ce5c0e938b8f1ede0357ec54748de283e1f49208ca482b1f80e536582488e6bd341365d7056edcf9a0a0607539b86d673930a7e300e620391d69b38ac3cf258c047521cb58c5b2bc7af1eea181faa6179299be204ca0e111fa1c159d06070365837b6913816ea6e2b8168cfe984a1b5fcaf098beded6861583e6579e3dd29eb5b5b9f600877599dd637dadc62a3df4f498d2946596d9832829dff1e4b8a9a68f62c15406bdac7b536d49e3fc9621d762f540e5ddbaea72f84e27d8a0863a4deed95b354342ebfe311353563b39ea7f0a0caa486bb2053c264e2671c5e42ff64cbd446ca3a25dc05282f93dda3b9e3d1e8a5240a7b46d0c9e330c4980f58e414fed74878313135d2affa5ec1df8eeed82e4d7af3a6bbd7a8527a2088c9425d84b435516bb8e4efa8a9f647cbc459cc13dd6592007e5595d2d2b1d7fe51e6d255e7b06eb16e3be8d41aa26d3603d862a3210565eb2ef66222b2f57947eaa3917b623851242fc8fcaa23cd999e8738d8cdcb3caf1c0ee2aecd1b56fb4199f5acc5b3dac6b55cf5f4152e90e786ae232df6ae16e9d84bcf63b459ee0cd79b5d4b53f0c9d4670310588410b9c15c47b14a859728fc89f1828e4089bfb61f027175f5a03b5f522f45e2adb1050ec3dd877f020bab4947838b70805f29fed026a4c6be4a57ef6b8084a03ce9cae8120e24f4b3e5af9a4d4b91b4cc472ad5793321530a3254aa970c8474d2bd5118ff853ca268e05fbf58aef75813f030f8b1b64945096d251976ba8ddf3df47f0e6e9784893f680619351a03b1882539f219857b363a3a5b42804574ca1807601d8e44369dd5ccc3f25203516740763a507fe596ffdcd76ce0099bd598da8a92a03b2229fb069d377f3644b9f7a1d5086871c5ac90a9abe2cf161c29afdaf5ad0429c81fb6a655e36f693177690b5d239635110a8e831c434a879d19cd6938321662701b5cd0251edf17e1017562a3e476ffb9c796bce80ec47405e3b9d5f676958ba73ab579e06964c150510e1a793e38e7ed17e70b131b6a254464efb2797c294b080c56cb550ccd4048159d3286026557390aec9d2e3bd5a19d4b22cd3fae654ad057a657a7dd2219e8b4b3e06b3462f4c4ba5f5b970ca7e35cafa65eb2727f662e00b682858e809b3c52e71f63911c60e53897423297c7bb53d5f79ad725b72df160f03ece49ba2b2de5a802b657881ec57b9903a2ec72f31e5120197540c1db14e90cc6006ecc19976b972dc4f23c471d0455eea4c3123c0e81582e1110894f5f2e5b6a09a332635b2e851dfcee5326b21eb89b324d7716a87e54c60be34bdc28e99679fdd84e02a1c321680005383d6a844696e3d40cdd10a02669e744ae8aecd2a024bcb5f9959ab3e9105dfc05c9eaecb42e359a1b7befc3e9621ee70bae6948c91e5bab342a4f55dfb000f4e6072c1c514d67e126037f397eae24e63d199bdf7296c65469d38f98e81b4ad392ff9fb36797ef84df054e667f78e7f7b6d1e1cee005da09cf281049af331c4a8af4df5b84246ed0f5e6b107601b59101cd42fd6070b5d41da105a1a62b35ced5f368200949bbec15243695baab46a815e4d0a90ce4dee68d6ca81d0d242a91344bfbe75a1f3ad31662e22158fdd901ae8278a409546ec757e6990a37a00d84d37e20534893c656846289bb015c602576c9f5e79da9acbe4642e84f36071d2863f0e2e0b442afd06db6ac92cdaf4233f2a93f897c71066b2100c9946082a908e095d6ce6b1047e3537128692ee02aee8726333038871a7a95d22ad0ca1d8b45f2f24446632016677c8f188dba2415c5af5da1edb27fb682e6f450c311bd17e783039def9453fcc18de16aee928e8fd7a742d679ef0ec849300ad20141aefdf646617138711443230871d984dc356533654b81942d70d702d73f4cbfa129909a0a19f1e4b18b7a08619028c83f3c9f38e73b07959459f7aa3e4c4029c18caa6b57327be8f94d645d4d66c08e98eae1bda7d5b4049075506f2b974e415b44198bd46af3f554c420d2c360b9b14a0de2a8748c04e7872cf7330d540aac6fd69ee209b2ac74973f27c5198ab4ca4d6945a38a163ae8471410c837565dc64ada0aead26eca9a3db7abfe9e591492fb551ddbc9e853c1335339793265b34b4d8a93b2565282df13a4e280bafcfb2435b6491ee901a541d87386c394df393d99d68a91a8972a053f186ce60aaa5044ae86ce6ce121a9a55d6a155c2daf30fad53c6e6c08e04b26ec16f858977bf7ce3733d3f3b9de166669dfb0a5c31706d24f8ac30c3a396aa35346ef8c642daea8a9c8502aa1ddf82c836cb5693b8bdef8dfa0c5e83b4f57ba3555206408416f27a9277ba5a661c2dae45366e18476b2ac5531231f6fb38bcd18112d18590b923ea59cecdcbef4d0178d565a5cd8813a4891478961e9bdf52a9785a52d45660e20dc7b5b7c2b95a080dc55c4f670271d355b0d5ae835a467ac71603389844d9c8abf76d36bdb7e786196f42b369f1e890e0d4c4eea031e42a708d3ef2089eb88b8700f212d13523ac5c7f48cf0cfb91d8a5e47f6b34886b2843b33e1598520b99301b6ee3a729128b16f4b32f5703728f1122d497b787b84ed45f09b3454dd0426c33ef144141cdcac81e46d499de1676c959f9a917733f4d339efb2e77491df66cd50db55c4eaedc1c8af5ebc19f5315fdd3b33dc5650fd943ebbcb3da12697a1ec905ddc008ad2543ccd0c221e37d9ed8ef34be0c79caba8ff0d8a0ced04685d74611a9ea06b83a1321495ca5c659a9b8e1fcce06d3b211026dcfb586d8c9c7ffc68444d8a230bb2f1e69234901c2d118f42cafd8d3e1df6160b2be5dee31d34aa4a149abb845b23441c0953e54fcad963dae1e0a21dff97b5df4aba27ab63430291b27373a03bb2a1d9b12873b2658c4150ab7ccb589ac277e91837c7aa34328681023d870f3954dd9c7321acded09b8f0c1c42154b8fb220947bf6487dbe11829e1b001140a04e94579c7d76651a462e4108e6031af8c56fb86e029c44c9cd67c2dc93371990fca0c9dd248de6b4594ea82da646559232025f54fe38110f09c8e525a957008ac32b4455ad55f8067c8d2b275fd820df5cace190ac49124b730264990f414b2c42f59dec2efed728430cacfd4a33c648a381286dd8f579c7b71f7d06306cb321947dfbd26c980152692b989c761bb46926e2ad19c4965292070a77f54a1cca3b835a0b2166d0bdabbcc057c0b0c911a3f65e6768c0e9a7901a8fa7fc3418a61b814ef8f14fd56f036701d1e76c3a0e90368779361ae04e36424f26552425caa1b138f97b56b3a08c65978622b546b3ac2945a3a23c634cdebbef93f6ef1ccfd070f16775cdaa10cb983b01b1e6012c382cc5d596b7b0399c2ae9b7cc13289567c80145aa08196a9c6ceaf63b51bfc90f620b726f65110e45966b9cbc64872812c4549c80e483a72dabfc2d29e48d6eb90be3414d20f7b655c7f24f449dfee652ae004246b18d9be79416c0407bbb9f46caa02fb81697fa40e3e4467612eae5b95b1313d522766eb2b51a272fd271cc5e491836f40a8a468cae1723f616ba3154c74e1b8e67c5528d550fb111e02aa39a206a9c5c86492e3498fe560888fbd4dd2a365c9de2987aeb13396eda21a4732168e89f031848f76d8bfa4687f91783bf72c57d6400f741964d1ed9e5996cca8810cc44c864b2e20aed9a0f45453c3691b6c227b8f8afcf8a42e36408d69a8608a803c80537d566abc071f2dc906a686a0947db658a5520e985e4ef7ac9cdf2b15f2c6b84a369a4941b2a371f0ef1d82e367a6badf908c7d95911a504faf4b6d0302b8858f7a0cf1cc22f4c54c558b29b58d5cf7d7a5147ee397cfef1ab3e2cd30ee552240796cfe3fe0a194158f1fd808a9cc6a7e46cb852670afe1bf82626d810284e4a540ad8727d1cf4eddbf7a2386a9cfe94a475148dfae9c97e951614f1b02228369a8531360c2920e3b44fff647ed642477c0c942c036cb26b4f974ee73f51a9ce99a378e35e930b9638a645c9fec76b32e0b18c826940f00a2cbf2a365b0057b29af378ef1de85c4ca5926f9939036545a4bbc74b0a8036118574a05a35e34e46430fd6e9434a98801ac2fc95b103cec590a00fda38a02c44b132cf316b2d5692b985b30dfa77f5ca9c961968e6a3cca93c3780aec36b03cabddc751272b0ffef2445919e874fb89cd08f70e8de7f98a21ceff3badee4cada24193543c9bf2e6f8da5ff3b4e4ac09264edd2ffa12a068d2d86e78b377f46956f3d726f54894de26f64f279a9781ba90a8d4e5105232b93ffe3cfef30162fbf15661833beae982ceee706ecaf2f1f08c77a036dc648addabf1b9a6e4675196cb599e53dfe96fc6d9ee2a9e11f77f6d26ebdf6f2c7f5f89d4da89fc8aa81f6358db4dcea454b2bd15c7c260c055025f81f0551419a5e1589f850d0ae9db72f48b434646c5371b6d3f7ce71a0a752bbf93abdd6304d63c0333e7e6bf09bf342f60fd4d8c126719f4dcb4cab5a5830c087d494a2fd84edfaf436dbfc57748eb9ea61bf31d2e286e5c1e9339e76b900b6c703107b882a49297dc103267805425aec72c740b3beeba218699dcec314e62af306d1769da2316ecfa2918c5b608f017c080dbc5889fde93df1500d1de48f0b256c7839d49907fefb83523ef7edfdc7f594909dc9a1890dcb674d7a62782eb2e64a87f41ecd20d2bf7430599f94a32bd9c01b482ec7572fb940d87bf4492607ee9664eabf59efd3b49549312d583017f81cd9aab6edb465c7a43eec95cab1ee8928e7bb38d1988c85a71ff0a8a03e1e2c3262662c8aa16eadb2b528003cfc5c9b8ece5a8dd4d7c42b4308eb08c1e84039e2227a06239c2f254a165666fe94d72b5d618ff957f49faaf76af6a03e15f4b2d0302b41f795b2f6c792c5b67258f3680b70866fb809b40807759961cd2cda72e34f177c54b350271670901a9f118f3e86c1f51cb521719e4d272f2e819402d344a28119365421e87638c456d224d29d6c7cc2f9c61df5ca86503dffcb572029559bcab9c63bf0fdc1577a1ffa028655a99bc64343bb22707f017d8e3c76f4e03908e889bd7e9010ca74fe640f1a04cf8d8771728c9f582c5dbe5b2a4b697098a545924cd11e6a3a29cdd0dcaee6526fe6afa48cbe9a73b2cb7b2d2ddf967d3e4be43bd792101f93be4819cb84607d5ff095419e4ccded490d9f416e5264be485991a0968a48982185e54168edd451f2f8d43562515247e7d03d1305a50a776ff125a8c65fde8a2adf8011179b11784f8b43b589cd2263fca5d5443cbbde77992a5ca0e522a1f1f7f277496572740ed30f8a610acc978b614b3674a1f42acb6fcf6bc7d95796475abfd8f19c63f8c58190160d3cb2ca5618a2a1b749f112f81cb1226503e1b972ab9b2dab419fb7745780a3589e667dff17005808fe7a65690a941ed618f2ac0237756200d297a9d53b7e0b7902344a8608757d6ce0a286549bbd09dae8206c68b3c4cfe745ccf95c9e6637aead3589bd81cb2c097aed0dbef5c7d1c354bc3028e886bbb8c81b9c58f7610a63bab68461a6793335029609483567adbcaab8d9c7068e29752f0b30aac4afb406dfb462718b662c0a2699d52fd7fed449c69d82c14e66f9d4b51e0105ddf38241c24cb8201a45853b82f87f5cf18f2d62ef2b32091b386d39f712c82bc97cd80a51761a2e1c66985dbd2c207aabb74bdec8b90959422c218a1986129987a464173fdf5a3a371b26629fadd8b3eadafc49a562a613881470815bd8cb613697615877ebcda7671e0182ff84c37ef385eb72cfaed32413371cd69fb9ecb5c1dce49cf7a6281e53a87feea21b9c098e58cbec799352a73a8549098cff589c1f392106c01c5f98c746e128fbe6fe87e95c6aa5d3031ffc2173f3f9c660c9226e3813e0e900d380617e666664c135f9742f38f6a138e8241e6c14bcffb3b369ab4f208e2786b9c9447d9fe642bc7beacc2747d7b2b597366bee66aa5b63d8dc5fa8b798e747cabc327a1e18fa4558402043d98592d926bd9accc42887818db9a001012ff0dc83f3759e456f9285f0d62787cd139bfbcfead88ac99dee97fa94b2d07856ccbea5cbd606b4696137f6916501d267b67b5e0afafff455a1828e31192dfb1bbff51122f6636c9f7893362d1d769661990864db2f00d372cc815e3c4ba3329dab0c8bfdb6fda60d7791586e75e58322c721e597bffdba3827b238231a8a291db6c62247e1e7c51a1cf3016f47244323b2e30846b09c1663e42bd703e91676c4699c7e41735b2358016a9c06f8f6d9d6b4180c55103ef92b3ea41251f5710e5ee74f168e99a75fe7472faac0981cb08232351641b8248267eed36b5b7b595710b2d4e960d3b0a5b81d985c1cb1e12295316ecaf25b16bea1c29516db416d97340fb34fd41cc20ea6b3acc27093816c1bfbc80ecc7455dc647e55a99ba850b4b907afdef5213f07321e1c9d3853e7a1fef675b9c39f2befa71f5f30ac2dc848f167273699d3efeb300c072ab554d62f0f2b887ec7341c54171373e1c386508ab95c97c7d520bff7df65be6b83633a38115e23b92be577ceeebadd18fe31f3085e0ada978b49495240efa57eb6762f323b1d0d3300600424b84a6070a9ab0fd7faec85bb521e721840ae7e6eb5c06a13c4568874418247727cff0da0af9adae2217a598012234945f851ddab10eff96ccd8be60a98df7cc4be39c4e55d92e1b8e9d5d3709282c26b944fe0698f23f340cf81c3665acfba995da1f448914689a9793c124980d25f5e1aaf5340a4e365731412cd78bd124d0dc46123161e97195ba3377ea56173e4de7a70be02a1e80e83870fe706512a89e05c3472798d113ddef3e4580eaa68069139e69875322aae81aac77340ded1efb4efd8cd887fbc92e4be09c7a942022c8829c356960a0e090b500967a9369b91deb432793bd3a74a0a52410b24062ed0166d401ecb7fc93b803f3bdd75cbdefc686a531fac81c43bc9dbaf6f91dbd578bcd93f5c9bd4cf8b524a706b9a5b0d3b2f5a7b6bf03e526c71cf43790bc3b7eb1e593517bca8d86d2ec25e5b42bc8cbe791bc5a15935ac955eadebf86c220878bdfa73e1944e31ba43df183be7af300ceb6b28e65b83610c17c41d38c1970a9764ace0b55147bc8820014faa8c7e74084f39d1c837bfc034baf74f6362fd59e9787b034bb7ffd64e08177bd595beb5bcc4e9d7ceb9faec4088812427d21f5fadd457729b7502ee77ce44b1d97f05a960f114ceea30305900e269b40f0757fb6b9ef2b9265d1da56dcd3d22ea47883d33fa51ce5105cbb857b2221a523f4bf3367a19dafa27bafcc269a68edb0b7fd8a6b6a0c43d1bb760d17e75f8b4c50a8300a8c4ad6a77711d6ca63bb9c88fbd921a697f2ea685046c1b9699610b2ce55e47ccb64337299705314ce4b3a3baeeb55f16b32de2011c297a92b1f253c18b2c0879597de1a5a717bd79480c7fc92c2737bb412396f1651e92f629fcbf08ac037f8a91388c1d580dcb1720ab50011b37cfb002e77bfb1d132e891f9de504974c8d9fda58ee2821b135b3964f0c918fa18f2a17f732729856215476fae3f3096e0621b8e83da4c1aaca7f2d027cb903caf91e975a30561aa50316514323ba3d465de4aa18ad6b18d6089a2d5ca94282e90e4ed46bfb628655adabbe4c69e3996c5c8b3cdfca53f775be455cb9411f0915663d117aeead5695fafdf37dc5be926f4b3e2192902de5de524a29659232c9097509d008bd03a41a2c18faf6178aa44cf7f65fe7d17ff3cfcfcf4e739cf48eab3161adc6845319a752980ca454c665608cf18d31f38157c2bbede8bf2f34abbfc770d2adf62ba1946f2f0142086f6e64b3621cbb6d16cab1e3add36ec6c8188593a7dca4ed30a5901da3fcbe46c5d4c8a8bc48b33aee3b375723b3ede89789f19ee679620cf5783c33dece42bb0bcd4a7dfb90179a157962ab5d4615a3bde772767e50faf6129ed10a927c0bc198989818ac3dcd64da7a9acb7c525b6a47cbe15257e6837298f9c826876f72945663f2f738a7c6744f907b34d78eda7ab8bf321488c030393b10da204bc73e4cde63eb29ba0e331ed56f9e84d1c435d72a2f59669a7d3a9763ed7a3a97434d421b975f8dc64bb33ea3619a50eedecb1e1cbc06fa2b227f38204c8d0fee77a20644859e1e41aa9eb9329934699e9db63c1529629f29e693e6e29b48687388e9a4b9f88aee9c4e2693c6e1994e1ae7452f4e8668d694329f34cf249a43cd45cf63e46e02d13cc99ddc846af409d42c1b644787f789c030d2ed4b9e3a7f7a080ef11d76d93800f8a1a7038b9a2c3d7c17ea1ffe21e7260f078e6f171e4e7bdc3a6b7356c28aa6e8e8d0435f1d6508d41c51b3583f5cfcabf293f4d7d3ef700a4c2e3ad2ba4092bc8873a4f5627b1294cca104c13140a989c0759307b91ec30d723ddb62789be30de9fadd813880011945f480e4859f9fd68e7f674b50b668c289202846b0376ac10f42284d9af96045533484312251f1cc7e0c993e89c43061ae10ea985f192cb2134d09b744c990cb962ac2a806b350afded5cad10e993a8443cdc127cd1d093d1d68829006d21cb491aae2c84f17daf20472695607a57ca320a10de873a30d89321f0821d560d1aca1834cbd834410a959d084b7a5c8856e35235313ba0e8922cfdd6043225844a195cb82ec6fe58d0031861659a4a1821d84d16a874a9127051468d181065244491284563b446a875cda37099196727488d440b5422650e8e178775301622488d43c2f08415d969a05c1107d032589413f40257806fcd23f0d048160d019df0e9bfc200502c11f2084d04b2ee20291a006b74cc91d44825c2052b3b6bcf972488dd05521f2dc4d360ef486794915067234541d5861c40baca8a28a2545b4bafced83215871650a1a4038028b56f7946f177cbb8e1e270451a19d85ee6e180910591fa30f397cecc18b8ff121fdfcfc28c15c7c67f3dd4d93a20134c9810aaaf4d0849b598c7898f2edb09d66b959c87488a38c2e3d045346196d9ba059353e66fb3c32fdc8cfed6136b65a0b2dacb5d63a67bdd556af715ed5ed56e757f942eac3f19e63dacbe8e6a3c647cd0ff9f3f3d3e726e46f206d66ac31c618638c31c618eb8c31ce39a77c32e2508fd465fbec44f0513e56e653a9367f2addbb6a168df3c8d7b41ff96eaf463c20f179cb353fdaa7cf0d3ff9d0e78f10cc870f5f6e9d7cd3e3b6a2b08fe08c734a2ea88b9870b1493b3449c908351e82608c904a4ae78d2598d4d189a4cbc809348c7868926a3ae5334a81d2b713df4e70f916c22de516b5cc87fc99e1e3772278e9717b8f934f070a89d1370b51b049f27231eccf579107c5a1347be1d8bf1cc26bd2fa4f1a7c1cf6f958047f7dbe8ed164da2e7cfb33cdf438e9290ed08fd8963d9dce74b70e86495f6c13c6aeadcbece3a4e322dff25910b85cc2be1d9aaedd476451766e401548a21564512a9d3296b07323c70e27c0529c08c88676cce4892094ccb003307a309a001230574ac0021e342145956f974b5cf1dd4dcccb07f15a228dab5b7def30c3f7b75bbfbe485144c2437894c5fa4aa86bb70e15bca022498ca1222ecc20410d41441081831145372c9102b9440f056826d2902b57628cd9d363891aeecabbab124ba6f69d66bd1b5c8f6e12fc375ed59d6e1d3d4eb0657cbb5cf94ac5e5e59615d24dba4aecb5aaf1baf8e0ce396beb09e91c597aff689723cc90e14b87d2daf71e191c48c1520f37f0c28226607c510329493c313aaac116444fa906244d7819030647332c81460f4dc288011238dc40071f7a6481c40ca12a4becb0c3cd962c922863073448318612489c100189d1185da008a165b03484922025bc38c1831a6a48eac1194e089106184a524da808a30c18145d01434a1528d0728592123f70319484498b2384904284196491a5072fa2018b09942ce941141c80428410689084172d2d88c175021758b1c40726c8e841940c6c81030a6aa0458c24a034b1d6044eb4347114a506253276a464e185d10f1a32983284840aa224be40d1c549c3c0c80ba020e5408325237e5a8937462be9044742d48046922445785284109ea8420ca517bc2e48d22823076368d12407284788a0d8c20730ac1c3185890f31c6dea294d271ecd8b1634f29bb9790aa5c79a2e3e63a47a79aec23a49c734e09db4e55833127a594cefcf6f150d5179979ceedbd189f2a498ecfa59cd7d4fae99494bad44c4fb3d6413fcb43dde6880369a8c810431ed172cdc528a386a58d37d8a7d6d5fc806f7f7ecd0ff8d203f0dbbf6c477ca901e0df4dcb9654a2548560e90466c80c7ae94366901718617428046eaae6da6695555b4b2989c40059386ef66311969224e69d949b25317fde5f7c97b73c1d5416ac65aded8c23d01612e35693bf6e9daf12e4982f4b3d03a44d75d41c7c5c62bc2c7dfb08451298ad1391171968e6308166d0049a40136802651298ad094404c404d115525451214d558c33c619e31de1460ca3c36d6806f17de784724e891911c4fcf2d34790de6146c4283d9da6dedd9f3c844cbd3309bc567d5c3011a3f470c8d350e4a92e013899f8cecaa2d904f578aefc7044ed3b5be33bfb289419dfe13cf40ee76f379eadbad93072771afaf65394771a3a49f17daaf2edef5d0fbd3b31f1446018d91cca3e1d28675073d02f7f1905ba65bda7b52d06195a75531555331a80812e64f8a0c30dca3862c906b0b082861623d2b8a283fc9eafdef3f79edbf7fc3900055ffc33cae1cbdbf86794c3d1fffc4d8f0e0821842a000902a4b920d707b97b5f3c5ca159d08104691ef8d3fad929a179e009cdb2b9e972ac624e527c72df4dd74155bba6c870f520cc4cd855ad85277b429dec69ab44d9de1409fe7ca75900f0e725342bc6fdb154fe5c478f6669fefc8466a17c28e2507fcfdb6fa65d106298967bfbc9bc202641dcc985b6ccb300bca7856e5403f06b1dd3aa93a0096d6e35210138d68406e0ae09dd3800aea60dc003701371689b3a19be6e30c97e868556ff6536783a347b3a540c002ad17f992ad3545a72d7499ede64311b4cc2c898408220cdf997c6cc27bb91e306ccb21e271cf5ecd1512400d8858b4f19f5743a9d586821e924e9e9743a7139dcb4a7d3e9a4c20a5caeeb3a9dfc74da296167d36097c6a15e339fd4c6834c06be506429de95cd6a662e0c51ba2e93df38aa6630e96219f893c4e4b61fd3faef55dbd2098218de975aaf6beb3ef0267b7126b32ad77aaf1631c65a93619795925239b1df89b1f6ae8661c1d8b314e69593f9e0ed86eb2243afd9dd2e2a72d4b029b04c5dd9608f1a7694e13f231ca67c75011491e3d72da7b9ded17fd39c0a326642d95c6b8c27934f379d4c73dee9315ed55a9bc2a80c7bbe329f5835d97d55bfa9fb288d98d6e1c73ea63c519bc2d7f1ada1b1529ab9f4aad564d86563a53e7d564c7dfa4b1a13232775e82b7a6fbe1d52ea35c638a3c4680d76d908e19c50c22965add40447b8e1da6055e4e89daafd96a057f1bd1ad9f1bdd7c91a5ba78cf1c5bf31c62b4b7e8e92b18bf0d357cd45ea236b665329b1e3cbd6a61373e9984b9774de39a57c1fa7d4b0239be65af530d6e86999fda881f9c31a9b2eb74b4b7eeecf65e633b7d5d3794fbe53c16cc743e3a7173f9b36a5ef513a29ed9ed06484c401ff615972948975769d9148fc5a678c4462f89a1fd7e2da516a44e2533aa9df8941878f653f681e6ef7de5b33f974a084b8638cb5c65a6bcc17625b889ab4f7ea63dc296175c6c78f33d8f9f86074e9f746dcd258149993442fe58d8460e47662899f6f8b81e7f0693526c831f389efbd05e4d74f7b1f610db028dae88d9448ea192c51fa1ec102824c68c2f35a0f46b865f128ad9556fa9e9727df0683fc216f8b68bc93cd5bb79a3f9ce855eb763ee2c06edc3e4237c4e2bed75a8fe7c9b670cbc3afd2e5270a73b3d2bb8a9587fdda74642403295d7cf40c3218e29f910c987ce780a6f2ae9a6b1b22a27f46444ffe00ff8ca4283d97d3ed25348f8d80232161c96e398dc345c97573baad6c9ab35ef27423f03bb85a45df9acb7dcdc2b0264f1ec75fbf327399499b6512e39612e3adcb3221d6e5fbad833fdf5d9f29e0c8b60ef318896e5b8f98c5fcda8ca41c1949e1f2d2719639a65dcf341f381e737bddca6c87755923dd62bf5bd7598779118bb5cefad5840cc1f1d8c6011c8f39b6c180b94e746b9fce651dbad5ded7ae7d0e61277b57397a42a90a08ae910cfd5e5865063430c9fdcf2889292c7809c01459417e47548a8ea814f1ef88cad03fb7ff8ea8f8f0f4df11959f97c0855014613129c1e2e50b2c4bc03cc910db7f58a0f84141194c80b07f463a04c57f588c86d0a187e86e1f4b7a8c4ffd4a8dfb187cb91183a52011a05e81ac648d5f1ede194a74a1410aa0263908e1d45014060a6c30224b1a39d8f69d1b36ab1d49457aada9957a8ee621c1890e202b2481c44b7f32c74b59c6cb4aa74be93b9147fe40d4926e238b64b7a6f3903f63a2e49705913f10c91f885a2f0bd25c926450d46aefe9c8135d1e71b1ebd3a99ece39e7a4dd1055f418c460ca771e8366411ffcb018b5e0a7a55a415645c997bfcc0297ab5c90dd62da6ba9564bb9cb9507f5ba837aa4437e882a6a42d673e4b19b8ec691afd27181b058c820c2419831ac50814e115848c0c3171e8870468b85263b8031841731b614f18131528002a41c301104a6d5328dfb72d59269aab8b121cb306197e4d53fa31204518fc8aa7f4625b8e1bb9dd721cbfc335af26527064d9ecd51ac289246774ad6f34efa94513617636c289f4c8bbbb569eb85e1d664a5d8c5adc5af1a9dd90f11fcf4d6bae943be908b6f364ddd914c6194525fbc28ec951119badd6e381fe4d33fa32837320a11a59766d33856be6773142645318cbd58b32a8c79e8d388dcd957f56673942527689b2dce41d6eed82004c11ba1ed8695a09dc8638794bedbb16a75b3bd6f59498a386f7b10d56edf669e070c25459cf715d36e5c3eb1f0b59fff8c8698b055079cfd673454c44398e33bd0adea8aef6c9e0525081fb1f8185f70f41106407c84c112c7aa37226c2a60ef14c117330484722501df7e339f7e5c740832412c02e1a35f8dd4041137603243174bc8f420480a3039c0520510ad86e23b8722be65b6c81dc7451e7a839316f49d1b91a75bd07b449e37455118413f2de82a441efaf3f3f3e3a4059d8566d995fcf9692131f4f14ab45da461aa142201c47db44866f042065b205185195376be853002438996a223b8b4dab96e2adf7ee30a0dcef8efee6e6eaa90e48e7b38a459dd537ab205e5330123c14b8f5dd8f868c1784970cb165b5899c177394260b878928511424614e1882c5cc0c3b72a8ccce57437af8bef4af8ee720cb1e58d88e081881f14a1e5db39211408ede097720eb7fddd7427a487efbdf85e7c7908f9517f9ac524008d984f6695c9c48cf2708cef70beb3d8d0741271a0df303256c5b1a287233ad6c543ec0a4c8a87b17bce96c0abdea1fee9406f6d36a9946e361721f7bb7f5b901dfa1c8a3c4380c092b4f4031d7e40460b1e800b26a6a411c50e9a30d1823e9dcc272724dac57d37903ab459c345c85d6d48fd69d962bf19817c944a3804bf50dd54f26de491ad4cb573c3b9fa7040efc883fb6e665e906f121402e915c5b66180432863467e3d27ac4abfc2cd69187569e12408a577c6cbf2b8bc2e35be7b4a4bf88ace809ae9d2fd73732a29398e20f383dc612c870cff798698367c8e9dc773ec47febafa315bf342d97693f380648e7df1232f20819110405878a0c3130d680248072dae8082072f99d6a37df19df61bedceddc6bab8efa6064bd32151b3626c4db7d8f542b3ba8754f3633a1043b0fa899f7e31cc5ab9b2198874a11490d4c6459ce937e2bcf14062eab2d865ed55032716bb80e802afebba50292da382b2f58ef1eb5762d86218635424618b0dc1646218c5b0c530c6b4d468c1116c4dc716fba2856631e0a7df8a61188602e6353be49bbc06fb91c7df427313080ed5004939e63b8f0121bbb0b1d05caa4bee1e129020290d0269d08a93fc42e4c9f922892674c842881e58d19a0e82c8d3630a183182b081141e6c684d6761ba0ba72029cf5b0c354fb452fe3278c50b15452d940bc5f80acdb27efd5a913444168ab1a2295c686e3a86f9ea2863186ba1b989b1c768390c988736340e5012f6797d8361fe7558835ddb90e6a6b3d02ccca7056a22030992512052409e1cb5c520d3c41445ad93a7b4ec2f8c25274c1ae5272de537d2c049104848f805181fd4e535166beee2796004c120264e9a673a7cd22c24a6c32128a568bac52e16a6b730dd8521d7755dd876427333a55d5591ea890c816e84afafadc70914909e1d66148841509eb718f09416ca611624e5a72d060654b152d44aa5b493a3b4ecf24579794ff7e8d1ac0bdb2ead95b5d7aa1028e95feb39440ac00feebb407e514481c40be05f1457a6bc8d4d9cd80759c8e570bb696e1372dd2145fe7248e5dfadb3439c52086d742378e80fdae84400c37b3add9187ddf5d6a1d7adbbe0e972094dc07e39852664bfdc3e1c995f7edd9410b2f4761674f903fc336201d1a390f2ebd755cd35143d319571ea9a9c8566056f68e90d2da17c5c9871aeaa15b59fbab05aadb5f5c2d66b4ed66b4cd66b327bebfdebba0caa5ecd69bdb606cef1da5be593c96afce530ab51b9c06c472f397aee9f91122b7f65b0b5f5ba9756834dd9755df5fac96af161c6e3a1f1b5a2b4fe7bba7ef2180dbeaa59325b77e4651cba29f391397971279e317e253e69d76fbdfc450badb510de9c2e97d154cda97cdc53ede9180da230cc7c2af45456b72b8a0cfd62d7c668ef61f5d71cb6aa1db2f5ea9808fc930ba91b3783e90a320daeb2c8663e15fa9d7242db5233754788a854524821a5100ec0f416cbae3d4d51510e331e0fe801d2e3dec38c87fc930d41d80699c28e4fa60ce597a352a8d3e9ba289cc2e598635986717672abf178dae540e8f5bf57837fd2605073441e474f1afc179a95f9522cf2705c97529869dfe46f783a4b3b3c750ae5a9dfad5b11f114ae00daf074e896dcc1a1a7521e0ef8343365287ca94051b423c4cdd143b32a4b1c74e898f1eed5e0e3dbe2e3abe2a3ef783c3d22ce3361e5d15b8f75e170c0479e8f363e1600d59dde8588d3265c8fde0fc7c9b71c3ac0e4e8d1d123f2c0271127f2c0a399ad5b6599adcbaa231bb37536a7b62eafdcb9ef9ed253baf1dd53b2d36a53ca677d6a3e276d04bf5a8d068a381d79fa49c4e956ab9c819e589b333c8247f0081ef5137814b1ca673f893c00f0953bb7d3f5931b4f9e7c0c8a38ed34de598c3d22451e006cbd64b3bd889323524e79d4e215cf46fba5c52c80da5fb75a3910d0f65e1c8a11e9e98c912352f41bbfb4b8a527c7edbfb8da95cd6a623b25ec642b879f653f687eb5e1accb783a30f3e9329e4efb8d19e79653e372a677dcb4560b3239cdd65993c96562105469110b68a33d468b5b9e8e168f6befa5542fa17a296f4b52c3e21511a995bebd95ba0cd50ca6487c2771a851c3625b639a7e329d6a6ab5895d7cbb292275f5bb88d4451de4d021836a7401580093b4ea32da1865cc62a54d69adb5d61aa7afa2e7342bc7e6f19f3cee449c213966703b536edd8acef97cb00369d792219cc2dd348735eb527b270d16c1c8039bacf22f612f64a240b74c1b24c2ac804d40ae1d36593de6736b22b1d630c8c4d3694804570f479cdaf5f31d0ce3db61a694ce79b1cb524a695b1999aa55caec5e3c7d6694562a299555de182ca594b68354be7d5689a194104a29218412425ac3822bae90d1677b3767f4e972b3de739374c6289f742963ab5e4a3ae7bb9169588fd1afe994b66dce7abc20078356047ebbb5d6e6a04e53d07e6ed9ba7f5dadacadb5569aafb5d67a67531062a79d73ce2dd3ce3ecd91834edb995220e4ebbaacb538e2bfd9335cd6dac8b3b2bf42634584dc395ed934cb621bbdacc5c6e8828a175468b082f4a60244a5c9fb537fd6dbe1c3818da0ebc7fcc2deba1b188ac0af9756639cd1a79058c277af8c9f4e5db376f55d47c2c79377341f4fde9d3e7a7c2cea317bd71fb347af2f6edd755d3eaf123c7a6f325f742514e19f3d0f47f4e92e68e0e1903e536ebad1ac9ad4f683fb94a7b69ce63247f9f41c994f4769b6b9cc336b5f47028eb69f85017ec6f2b32ddf4d3617bd93deb27b0a69975dbb947287dc734eef99bdbd085f34b5ad01848c496cab32fe9b3d03a4f8459e9ec6993e5768ac8890316c96f5ead3afad1a81df6dcab47c91f209a2c76490df7736cfe889a1efe4ef3011f4d4dbb356a324bfcc0723c92ae8104b22773bcf880927d4bbe751d3fa9fdbdc6428c0a75b4e969befcd9b731b7f456fa49f0804f3120623f6a717a1df4fa3c3a73f1cf07323f2c28479b9e130ef74aaa96adc3094d4bd2e0f529512b9dbf979e3cd35abeaa0c50c66f86783ef4af8e95952eefc7d6245cda25b8d8fe9596652c35ce24bb3cdcd48bd266e3bfa238f0190a6dfd0d1acb7820d4a3fe1d00d4abecbf1d3862fdf3d2e3f9322a4f306273f29a5cea39f6628d01b9c7c0acfb5a68e6db639e8ef39e8b0066e73ce39bd4692fc8ca65879eab37ab55d11fa94fe5c6064673e92ca93ef1cf0ed3367b95ad527e510b2943562635b7227e95b55792fb5b4d62abb2b959b7c5e2c7df4dbd7e9135cbefdfa9db4ca8cc7e4afb3b12fe39c6649acff476bddcec79d1c3d82b030040449827e7822a5284b9766751228b7663f30efaddb9e7a86a96e9673751e199118268c8d8e5eefecc7089e627e7f60f4e8db769433aba4c53f603b85dcd6d9d01821e4ee2667e3fa0925f9207c9a736d44604e029e4e84b1a553ad849da7b31ac20d97d3aca5cb05b771e407dc8626f4ed26be78ebfd0f006fc614bda3557a27715c97528726f4fbd88108da20e105f9069a40dd72eded2b12aa069f8d76a9c567a3b7aefa7b36acbf67a36b9f2fc46a37cdc52da7264bee0d06ffea93c86b1eddf60f031118e6613042df3ae0ed069fcef4aa35048ebc07565f64f5288721de71d35c2b3f23269cf4edaa4b2172ed2dc7e6812ddae549ea77663e34767bc3872f520a29d177a7ef6c077c0d04ed1fca00df3cf6faeadd34c100d046578c8bdcbd2dff92b63c2417ae7c1b4d5182100864ed40aebd1d729d02bedddaef10600068828d67a3fd55b15a7d870088d3e5ef1090806fef721e014fa7b12db20b1d02be5968418b1c1d663bb8efe63e0cafab85d66e4c88f5cbabd64114ac5ffea8096718bb4c2693a9566cb3cbab76f97dcd8ad6de6bc2267cf9a89ac9afd5fa61e6e3374af9d2ce7cea15a52cde571eaf078fb9d56a76f4df77eb756a1d3eb55796c2f5c94cff8c7498f256ebecca5c7baa575a20e5a3e9df951610f1a6b71bd71cf57e9463b9666aed954dd884f3e9fae5113bfc88edf5eb31d86afdd65a7aadf5abb2f5f6a3b4cea143ec18aeb59ab0099ffcf2133627deded3c1b6939f4ed06d8751f9f29d0df8bad66ac2260cdfb5538345c412cb224b97f1feec64997f00f8f62ef5cf0730c23f7f5df7cfe3a79395c07247e8d45c4393941242092194b27b85355f3d9d257275b8e115cca0cc289d14bb6c9d544ecf7ce293186baa5aab5f174c01c7ce772a20e4f71dfeae3cb0d52648a9bc525eb64e896117c3268494425a297c1ae5004cdd07b64e1a81a3b516c93e19a35d72dfcdeac51bf1bd51bbb4681cde6eae0e4bffde663383f0a233a317be329fe817ebec42bf5ac431c7170dafcb73e26427f2d44d47e344e7a2c27bd00ab481cb12180814a463a7c7b332f3820a3c9de83828778fcbc7f8b850e1498f5e8187e355a51c397a9ccc19eebb91abf73d27a8d0428c31d2cee1babb1f0785aaf7b4ed6c66adfc895d1e11b274d5034262ac7a3739397f420f4c49966244bfdee57cf4fb8e78621190155c28f2f3d15f7cf21dc672f4dd4bc2d224c6296f448f36f8f8bedc883dc8c8efa837a1eab96eaf713c0605215faa563a9aeb0182e6e49cd46da473ce399d880c366fa3f46b03d29cf4e79816a439e9980f727c5fb718b6223cb0256b04e50efeb0749c0091689e6ec9d79233c889061f26016a960d4c6093b9611d2750ef9e951ecd49a7db099d4db36c2410a411491f129066bdd0ac194485155a706148b3e6092734579dfad44ed041467edfe9681ed9923a9a4567ed9f6164b5a9736e45e0cb04913b7fe9cda29475c3e594d0acf7966e58e689dcd92fc2a2b82b409771262fe328bf8d3be371f2f6be4f461eafe8535b273fe517a5c97826ca3c2d74af4b8c7743548ff2e72da8b6ce569fd90fe8322e331f312eb375288f49794d2d91ab9b7a1aa334f83a9a85725c2fd6fcafe61fe39ded72cabbd5a3fc3d9477e683aa39b5bdc7d3f370aa675cab0cf1d0b39457797b923f792e3ae34f9e3d3f8cda71fa985491fe18af325b37e4651ca5752ad4269b43a53a943f97d184a0b621aacf7ebd9f311aca7b226ac7c9dbe695733b5f6f7c759326a43d7ba675ed59765326cad5a357af980f7276c8035b15f5e3e4d98bba7c8c937043ae2ea43dc6bb7695c75addc6f85b5b6b4a663e2994e62fe3172583d250fe501a093b64146ab3cd45993d66d2b356e3237bcc8d794f474646eb509e794aeb50ae7298ed40798cf7449516a3d5f848f9c953db6b4ef5a69e98b5d3c3340e5cdb10d5c33641af2798d970df8d10a1e9d885987c4e3799e2c973660af327bf363e8ddd1f21a679dc3aebf3c8d0bf6c070cf3f65ed789c86b3352e38948ef8010eb2617623d5e79a553bfd6ad5fbb11b9a4a762542819999ed8d3325aa7d23a201f3d26a57523f8e828adebf968f2774df1fa080220224fbfd7bbe9d7e7b5312479698469cd3812a0da5272a435b3bdad8b9148841d8ff744ea4f8fda8b3b30667dc891a18f442eef40f0466a7ce65db4712372794767dcc619ba11c936211a665c890c4a8e380d0d0dcde5ef25a01bc1539ff1970159794fcfd0ccccccccccccccccccccccccacb6f71230b3e588035d06c8d3191a1a1a1a1a1a1a9a150d0d0d0d0d0d0dcd6a53b2d1b80c339b92f770e2cccccccccccccc0ccd16e986f97b0cfd618edfbe21f58dc4230210225f0ec37c97f96034851a264c98213cf5ce7c5c5b678566bc3321191e520b4a8d8f975fc58994876eb70091c54806ba23cf81fad487e963fffcfcfcd4fca01e9d6e2818815fb721f5e96624fedc6407a14c6f4b573ace579747861e3e1cd29d48f40e040f9f1093db8dc80bf342d93b6d9323605ef2f34704bed436ecefe57470831888b4afda3622d463fbd4ba6c3379673e4096c016ca5140541b1295c770a4a5f24bb31d4460989797d79cdc9b933c6a4e273f49e9971022d13b931b892f140295bfb740541b92233f2d2431c810068a30cc10021a4508d352b992232dd5a624c651a68d4874e9981e19fa1aebd1b1cd6becf683fbea5548e5319a0cf0a7c84910501212ccd042f2c2b4f20654a3a5da6850b91219941c71140a85ba4860981694e92a2d0f5da5299141078a1c18310333ae10824b0be54784b82001115ce0c3113360d152a9542a28727b4f47b5c97cc9edd073e4a181a6a524061a685a4a6450a15028140a8542c5a05028140a85f225375aa84d8908a4a0c2831337e0c10a865a319e72253228f980521524bcac208a2282d042b90caa4d49f624499200dd68a91c886ba93c09d0aaa57220db527992244b6c0bc9e9cbcfcfaa85a482f9f969a93cc912ae8524a6f1d352f9921b2dd5a604e53136dce04a920f55c4b0d26a8f59e5c6b13c50c9e2043760a20b1a5a0d8f0c91a48a244264018303a496a963600067c85fbec3d8080aa57fde0e9d6498c215348ea0c881cd92039b050a2911d00185110f07c7424174d3bd06acbcc3ffdccef8f3cc43f3606bb1da7b26cf341d4af97af78a642cb618fea5fdb7d6f8e49c2b9b559661ac66399e0ecc7c723c1dea60e0a6d7c6a9ddbbdd688e7a7d309a846ad4d87a220e6d1aed84c6a1bedaf2d4312b72918e86a6ad4b8e87e3bd2644ffde6af5deeb423357bbb2c4c653502c973699e16fc65553b9d54e3ea3258192657afa562c2f139af19ece2c4ac7a3fc5e7846832d952634e32ba7d1922cf1d66a03f2d6ccd623e250938cd6a3b9d3d6d31cd5d1e5ca63fccb7c4e3276a70414ae1a7694e33f2b57befc45fdb37225075ffdc6f070c60bcd38ccb87cf598fdb372850ad8dd6f9aba0efcdd7a34477b74a41c3a7671399ccc55a94c26198cf98d63bfd70c5e69af45f3315af7905aa98aadf6bed65af3bdd90f9a1ba70c8a26dfbd305e52eeadcbabf64222f8baae2b730c6b98c52e2dd8a7fa553737994c12c74c5ecee37dc51863184c9e798633b71ace827a124ba6c137dd3447df636cc2a60cdf7b618c2db610638c5736580631c7b21d98872f7e667843e1906fb21d96478dbf7e5138d3e0636cebd535fb51e32f66addb77c1eb82d062590a7ea3844dda7b1c25e9efbf2b51b6dc9b8ba1e9dda4bd3f653c6a643f6abcdd220a4b61198cbdbfaeeb9a99a961a100aa01c47b27994ff8743ad59bbcf9093b69355f183e9dbc9eb4ea399f72356113ae399f340c4bce19d75cde5cbb11e3c4e4879cbd66b702e58cce76e880b2744dd92dc5624cd884537e792a2695651bc658963d4bddcc94313e9d7c4e19b3f65e0c3b51a11876d94ae95cadea7bd4d297dfab5a0dbe97adf56535cbb61a1fb4bad5ea9cf6523537ad636beb755d5e1dd36a72862fc52eeb26b76eb193ad177694df3f2b4b867e352b8655cde42637994c97a517be7e69d8c596cad17724fcbc2ea7588d7dd55aec7a7739e64fabc9f0a55e73d1f7ecb3d65a8b59fb306ca3b6d2e92f525853436cf8e02545ee6c6034f3791f3521ecaf663ef0a3865dc99d0d5f3d1c77c237a349e8c6d6dd2cf4cedaf976aeae5c8a1e0e884d19ee8723f32e5e1e2011879a607ab5d68a495386596bed551dd3e09ffcd27cac631a8ff7b65a6bb52bcbd5f864cae2b418be6496021c5a7579eb698c8bbc80dcb5141b866856d7529a887a3f691ed8a2de3f506f276d0364c113408eba4983373c88c3d3a19ed26010e4a8c3a5ecab9cb3dfad87f0fea19d6891614dbd1079e0d00ba12aa894ccf784452b99aa1909000000e314000018100c8844229150302ad8c5e60314800d8fa6466e3c1809f32487611c641031c610420820004440606666661d002f70d35175da46bdef9979f19df598ee49717fc8ccb99653c21d6ea3a49d1b3625ad260de216826de05a689a234fd595d222ddac64f431620257f0996e917b5893dccf2c566add5bc1a78508e16621143ebbae873b95faeeb0d5f524e1379a2eb32d5de606acc180c970ddb54c9e9710fc1450729a58c766291499b09f7768f86e9bdf26bd19a2820932da469c454ae02312912093a4c892dd325f798442e149924ed9e1d519af51fc793f558fd9ae00a0f1121e41d54ad5ecb628ee247445c1aaf11be596e7f30cce2ae825cf6abddc3e497e0f3cd95f432274883046249beb25fc33dafe32d7e2efe2828e4c69b34aac50f67dac490f2ccbba4e3d37fc8e2d684a1320b6426bbf001b4330e489a64f2c1a765ae4f014eabe7e120af8b023eb92039d1b41a83aadd004cc50b8356857c7be30aa1baa935fa2b669dee5d40ad88612d19494532ebadde48d44190146da3f9a39470a0e8348b6da64e80487df69a5ec2db4618d94a743c6884cba9d22f5fa75b0d171347dc94a794f3c59008921df550e26da4a9cb2b57913bc82b406d6a619c3b72ad90d6c10f83da8f71b071d5495fbc4b92d18f66f38e430149efe42ae9aa9e00f1c29926c1e24283f8e912f26e35874a42c9ee51ac3014b43de9c362c7e8fb60951a8ecd92396ae49211bfbd3b3560a13dadad95d4889e1fff9cb2997f8038d48a9cf4a490a56865680d445063f160138de5dd75945e65735fecc171c3c79e99e400c0f0e5d68292546632629d946a849313549af6179ca2803eb79346fb95d8fd91db9bdf961d0347a79be20b26f5e2237eb7d2bf5790959fba890d2ebab10643cdea4bfbea3cc74657c93dec373515c0b1db8cf3d563d6e285b9d80772dc631bbb8158f49ee131ebaf6bb0eccc6ae63d8440fcfd1731a4c15f48f410183ca3f96c4af646d137deb4820b70b917d9fe779921aba25d5d20447dd1f1f19dc6c91ce3d9f56907eff3482d939ac82dd615b735732c7fb595f97a5e34f8f5a741c180e33eb59645f4ec31a77a2e23b60f14520c4de39c5dd1b66a054a52a4cc9ae3802bdc7382ab9d6a328fbe3bc74ee1e0bee2ac0bdce212c77de2575a7ad1ba44798416a63cad5ec5d68ac67f3cdd89d061de988b2fd51cffd6b4913888729578412584d0bad655bbda08cb6581618f92dcc426cabd88a0820f94f42ed7e41b9c93d39d7b353003cb343147d31a9109026d3f188a28533a5dbf385a46dcaf338e24d966fb662acf51f084b501f3ae7724b9105c3eaa6d395b43fc6e19e6b96aa9423d1a183c0e1818fea56fe85e9ce5f789084153d6992c679dfd442c13ad9ea593eb99fb7f86c9f1e31d26957ba44de628a45a31daf3a404f74fc2c6fa7d81e50f9201d851ccfb5e97555aeab9c9f26544e95070c41f32908a37dfbacec99156c0731acd9160906032fc60c2a91d1a7f043ffe92ea43c478b9849912b468abcd6f97abd60c9db48bf815ba66103c43dd815f266a1a223c05db74965b09bedfe3968473a94708b7ae4b00dfb038751808715b435f4e3ebf0730b1ad3f6c754e35a301ea92bd004f1b888318ff6340c38f02ea221e7cee4bf5a0cd690bf45c73d7d12030a18608fc4472ab31254ddf9123436e2cc84a4666837d3bce1f55c1a2c102b030b7e5190a738b3ee884d9d86429e7e71f93295784688983380054f370f2d0d39bff53d068324db7164244e71c0bc43b97fa4dd3c62d50f35ce1c71697822c88c87d848b90d39260b51b3a4e6f00fab1a9f461a731edffb46a8c2fb0ddb3a33dee45b25a63890e9a13939f6b70c8e88f49b1dbd020dbc852626861545e581ac02e4542356fdb51baac10473eca8f3459ada679fba0f653b9a86902149a03a063007650cfe54c5e0e2d4a9f2c5b6bb2a196bf96b294a8ee67dcccc50f7b9075fd1c6e3d39884183f1f08681c34ee19c13c6944c986775c4938692a2364f02025e74f8ef7fe82c839950a07445542711fdfc82d42a7aab84da5f8504f55c02b0cfc4bdc8937504b62857470678abe29a934bfbd0ea9867cadd021528502c2cb3cd8dc08552fef7d63997db0d2cbd5db30c888cc9c5bb74007ab5b4d3832d8f72a5b9426c643ecb3bd0fad0811e79bd051fb79f8fd81900c3adbcc5c6dfa796d9b892cc0f37f2d0d40422b1caff4119757f44fe7955b7a41e3eda6f6091ebe7af80945a7451c1bc15ea19b545f7950cd3eb86f1d6dc36d65f4c622332ddde7cb26b5fa3ea3968a78b46fae9bd2bea9bc7bf3ddf4039e5b3cf247bcf0bbf14d378fc6143df0403ed1a57ce34ff569c7dc3de12be4d0ac7b77f7523cd610d820c7b439f130494d91fc917105df1e255addc2777a56345733cc6d7126c3df9c30984e0f60270de71efeb2c99d2479c973f54d6502ffae556608d9807230e42c1a4bf33bb9c5644b5009fe50cc6e84191d54c04743a84790b9d215021d10d396d48d7a0d64a2ae76a3b61ef03ae51b9cc8e2ff40a02a6097ddd50a822a1461809af7c3c3ac550e1ef095ac784b8b9f19e21b4e77860c11e38a50cc1658eaa75066aee4abfd3ccd9031cbe827f90f4ce88501f50b20d907226aff7f14d41f8096d077d869c4b42c93f7e63d8c64ee9c558b2d551e3b21fe11ca0f0762629756116439ffa39880a640aaab06288b0c80428e2bdcc6546802e2e772531808c1d1f25d9c02cd17bfced01546223cf4bb093e90988c405e92f4c6efbecb84ebe2375ebe35e0bd2443d6d962ca7964bbb6954d9a6c67b442e4a32e82e745488068a615e6d462bd4713b720eee49608f4685ed992e1f17a136ae17e98dc7081501709cc79463a83f347a4cb4b548d03263d5044d3a3c3f0da12fc888aff168b69f5965af3764c07a0b1e096c441525639582bf88d22cd2d87f25dec9269fcb719328ea2884ac80c873aae3c8e3eeba6ea35834992bfa2a277ca933f57606fb36db7bdcfcbc4341e54609d785b22205916c9a649f82b85f2de4e34b412867226342fe8fb54b4cce8ce8edfd4af70a9ab0994f1a7e51522f3868326312839ac98437fad88e11525b97be6635dac31a3084c9c73c8dd25b149d59a7ba78f8ec05b6313948f42dfb0bbb09ba09a45447e86ff8c842252a52907a36d27151f1dbdce773b4392aa9c45ec9d94b431c89c98d200ed680d96915efa7daddd221c60d801f420b4c25ee4fc8e79753f3858826097436df308160b3f58ae8b89d2a2af396603227b5761c1ee1f7acb7e8e2335b738913f134bb3b19102d74bfb6dd2d164036459dac3f98b49ede60a59eb6aee753aecff3c6ed2e194b2431718b63074b072f3be777c6fa5d1482952f2674e8bb80e5540ce32fe570386fe6e9d2b4e838905179137d6fdfe8b77d21a625009725efcc5ba3af5e9c5476f17d79c89ad62f02471d0409f82c334f3453c63cdad6b3558fa542bc35fbc1e72b2c3ccc1124e93e69fa65bb78f7b18fc0e9bf3a56c69f15577f26c22ea1611114d3a9913123c0a112c6680b9192bef5c2d48d9a5de3aa98ccad05db127d5c4700bc1c644523b25554e28cede053066bfdfcf6c960206e25f580d91f96208ec2cbce484826a0c9fcfc772ad38f3038ccf4a08bd4b421d50264feb7ee291baa0d529abcb44202adf5c8b77cd43b0cc94200e92bf468ccf3ef4e5a23185a4dcb57520e6f9b8be5f1657476bba77fa16ad280278f70a629352a1e149843108d9be7b1a4b0eac0ec18dc798ac4baf656b9095d684d07b366e4cfa1f595fffd514cac8e65c6e7ea05e3b453292cb5b12e45ce5296742da0e05410c39e6851dbfb45c4771830517e2244b01f02614e6f6502742c66f9434de8d0348929085b4ad2d86184acddf23211c856634504072c4681b1a3b10224b7cfb23e332ade42f2d2449d084cb46675f4fce503a7f998e5db10a647fd9767224258021a56246dfcfd8448981924c1323518034746ade6122121ab4ffc83d5b42af955d0041bd4c5912448db02686c6ac4e87b8c59d78c15f562d486612d71e25df0c98383eb8d0a3c91a53f5a5569dd61e1672bc66150708f7c1d9aa69d626e2fa006d67ac5cfa07c4ce11880af0683d399d41c5fcb9d3f89ad04894573e5cb637cd2fe118eb522e67c47e26755ff3c6e463e75df6f02a0ec4738a54d5ac692cab51f0bf3f221fb10c5163176b24e725dfa3d1b7186ea5248d4a53525faa394cddc250ee4ece36a14b7e9e481c951a8e72d77a0063e256716e8bf608b951280a120fe2ff782dbeda9a29245242ff09635e686d6afa64d2cbb2ce12cc41b0954b001db316bf958d6ef95a5c0aaeae298121a07314b75ae927e74fdba8c9882191d5c2219d7641b379744c81993b338e6464995112982e104198df894962ca4b44e4d860c8206554a8521d3ece6f7d0e95a88504a406a97cb8568b142e970ba370035397ac7bff4b7ac555fb74d91eeea0b491e0dab7ef03d2c6b8e46bf03192d97869285b17f27740779af875f3a191dfb03deae811e8b3aee745c1f2e76f9a0e0a83f654d6f0197b88b849132d4576c5caf1276bffa814961e5c5c164420b5073b1529b72c218a2945c6f4977a6daec24fbaf29aba33fd5427a2262b6ad1f8da6105b9234901e2e61861cc2dd33234fffc1cdbb700a32d72d23755acb33e0355c580c9d54df5496287ae5ea6da71b8148fb982f62b57b3e94118cefb9d9dd08cafa70160ebbf64687c688815a11311f696a311e2f8c43b6d4484f51d1b47fcac4e895bb64ef18afcf640949b6755f8b3a0c6feb10ef2d9d50d7ac83a74fab7b5c260007067e78d627abc2327bffbb79e6b6ac8cf6ee2265a3cc53d1c2009556bb69bb439aefda96b3c119678cc8f975467762ad4a05b169a7ab8417d7e05ff28ca04b6e56fba6c8bf8f05272bdd8653903793c3535796436904e515bcc9b2ce14887f8d595adb8145cee75519068b226cfc2b6c8b87a41c182461db679b7140234eac033b966e8d7854e6ef98fc3f9cdeb5c1f48de357987bd7445938724bba54cdee9c88d81a6af723411d47c6ff24b50107b623d6b17acc9d32b25bad1e4994eed2554858d8aca5cf4d67a535372159d584d9ecc4db9f672f48b0a6cd204046d3f479837390cb58c906db84b28a2a0f72bd59143138232b8668c2160ad90b88c429958acd0435193033a4ace2b00a6c9db32c4be7108fa5ca8cf5b6887cb977cf0a0e068f2a41705bacd41d52bd7e4fd47d2780ee6da9e537ecfa2a0bbba1c8bd09bc3294a15f6339407fd91fcb0e68b3a724ddea06ca60c5b0093dc50b55eb4c5a490549317ca5284f61cd93a741958ab9c51a1ca0e671d1f598c3e2a1b094148bbf6a9826f70c95c5881f668de8f76bba199030bb0b57a14e21ed20c0e64ef7b9fdf737f600ed51d11d658d529221db7e8c3e24a14a42292f7c42c57bd9f1e119e11182fd0470ad04b8abb779837faffe19355117a29f91903dd1121653d7790a3602e78ea396a60046d61979216fabe6d1be7d98772ad7c910b603632b9b9136a02eeea49874b754c7710fd41ded4da5a366df200b3caceddad940ac00343aed539db7244403b46424c9eaeace0ae279bc279a76ad10a926621d47844289c4241e0d242c8d2dbd5be4784567e846611c0f40f07dacb88d0559f6d034a89fa8870f252495ad0cb8bbfded7dd6a7beebe08b57b85b38a06b214fb5057599a95f10deee685bfbadb88b86b187b4375772c9bbea16e1d59e8c637426361599d6e333f3d1a6ef8d8e5644860b82006c5d5b6be397c7123a7ea887036f84eb3162129b72cb22e67748ebf7b4698602953d8d8035da4d0a56222bc46c2b1a520fe2eb4006c37400207805ff5cb0f57b8dedfb22341386fc92874f3253abbb796f5a57b9a8c08c836b2dc79038bd417417381ebb94827473845afcfc1ead0808682a4db0ab894d838e87df84ad9b686fe78d433ee44e8ccb72e1b4a962bdfe4eef22907dfb3fa8668e0c0b9fa6d5f345eb24715bafc9b34eda67da8d4d0f34658385e93e078a999738f0c4027c4163b95d942a3625745ce95b837ccc86840756f8dec75043fdae19ab210d02217d4a97a55422a15a46ca6617dc795d71fac371969505b12d5d8ab3b294aea82bc50a63146548efdd6ec3b5e490f5f8f25ee39fea29132b8017b29d4397041305a789fe393ab610871c0bc0004def9cf22c04aaeefca079019855b8e96e702c8934bb0419d49ec9502e57342289799581729ad70b4557010ec0dd936da090616ba71cf2a88a25be5e5358d7cf5cdc2976ebe7bbc9d8f15409375d8c721c2cf664d4ab2c33d7573b26048ced73a721b8ec5377d5caf2be3cc9a6e87362efb375cf570e704c87a84baefd17d1a2bb9bb142e2b4b611b73639cf1be3b4f737b3774f5819633284b659bee072d73c87346b50b7fe532fd0691dc051f8b53dd196de6aee71fab3441158b2a169c41df3e83c60ef0d82919697513a734359c295bba2528459af43a6802b898d02f2782755c0adcae3d6a412ba0096442d33572da1624661cc60018b5021a73e7940ca6aa7a070636ee1a4c259edffe73007c9d891ee60cdaeadeae15d2b80ade36db86a10a967e274976505554c8659700ccdedc92bdcc8c63b6ddd5370d405597d49acb4447ce1c2ebb5fed07c987fa6b982ced9c71a5ad16360dc30ddb891700dc7e0656a34f2e4a105fb6dc7530cf8e07a51af55d1e4a69e1dd81f481eaae9b111b19ba3acdda28702ec0f694e386d637d547612bd277cdce56f4bb434b9d43192f524b0e1ff17e631ec334e467ad86c61e08898093e841285fd2828547e539ed73df41f922791427b962d405c05f3376b875575c75884cfa74adbae758c1ccfecb0810fc648cb7c64e33a4bb6b6c50d7bc3ca2fee051567ba42319a39e57bfee16369147ee3b86789012297dad7cdc3a05f23c26eb4b877738b5221b8557bbafa0813d7264602ef3227fb303ddcabca8d7e9b77084c6640f2d72ee41553132f429c5c92520506eba876c9b7cee53d9385b7c7e6b4865e00e8baa676e443a491dcee64cda3a90890194a9d5c0d4cf90b694461a5c829fb438f3427e562a6c6b5f5c12424f62409be2867b449afe3f48d60b352fe552b3bfc5dfadfff0930eb81390df2f94e31d509cb9d1a142edd39cf21faedfd69c2b3d086e1f8d0fa6741b61697ad8489992335c36d2e0230b7a4626998cd5697db8b8235c6397222dcd3ea6db3ae02828036b9ab56bcf4afba4d99f8b04a1b3a401d5f1a64a8de8ea71bf521d0e65011e469b6e02076c13d19b26b121a78f02bf63c7cebf41890c875be8670ff01be305a90588bfa650d95e34bfa134c1d866e8b4d94797aabccf6afb331540e9b3063063e02425dcbcc563fc53ce19023ca16996543d880540f3b69c964900fb6172d8080e01bac649ed22841779ba70dd1e7a5fecdd66abdcd212a1ad372c6bd3c0570098b993181a96eccb2fd77f63f0a07eb7c5b754beaad73811869eb144375f12b1d3445bb25a564bb1afeb446ed447963f9b131eb0dd11a65b5e2865e8c5d0e64ee9cbccba60bfbf62e34d5edb05b40fdf4ba9f619adb7bcd59692857c92848fd0cb1ca2e784bae08b1b1df3b20a587655286a721a0661fa306f142a2c2ba57f76a19c0752a3716f4c3fc8c1d30468eefb678702008cd33f31d30166393ebf66075818a31787e914d75fe6dd5bdcc1888e221b8f4088d08b0658119c8d6c47783adc982f3b880da40c5729224dab0573e330ac1472fd35651307c35f13bc1780ad892770916135d18d5ba36e0edf416d7afd88514c24fb8cac5bdd09aa8ec347804e0e96364bd04d040bc4a4c702f01c65ef9d5ff7e76dd60d927a753ae261e5e1286f1d60608b89738f3656e2a0f32d005bacbe39690e386ee55a47f7b88848bd24331deecf11cc925c14d213acad469a6d07435bd39037f07e1cf709d8a26172a72c0ff104efe1186d0f3e28c62d94ed711f773dd146fdb1e1ca5f7cdcdde355b056eced71ca6676f3ef2649255f49c41c2a30a365aa15ef3f85824bdfc6f76594f1a76c896bd6da7d65411c5d6072fae36dfbe9b29013603b285c7b97670c4d4d8f0f75d44a629d09c50b852bc06a04815f52ced60264ee9834c94a4ac97673bbdff96858f5e79c4a276268659ddc387c7e1db1ed5cbd64f04b5a257467c2c1ac58797941cfbf32df6843c3e7fe5f3b0292140401af9594149e7ba52f15c54dc16fb346f4262ee8361464a170a30b139c3c6180490259b7cd66a31a13a1b36ca9dc397afa6738b807b82e4008fcf7a03a1adb6ac136b91f7bd0a5d2238c529fe5b4da1d521dd02daf6e0d94b968c865c57a982a56c4bad75a1c6e71ea205424f6e7a5499b51e038c0c101ddd48d084f58a85fccbde278a3c010ff742fb97d7be8e0120116a4f81efc6d542ab7a3bdff9afb0aec8d96ec482e5fa5b33d3c8e7f8ce66eb50bf3037dd974bb003903c747b8772f4a38de8cdf6417db5e9ac985e3570ff07bc150ef6db755e99fef33f4fa3a65726ff3ad86e4fbe043e24c6894d482c43ed8f25aa19a4f2c52612134fbada5ac33e386f8a401dfe4c9eb06a7a53c57abae763077a3935a3dfbb73d067eec5ed1579c4c519299509f503a098dc3adbc148b73874f9ef89dc11a0e08cf2f44ee5813029fdeb18c72b2033d9e0576f70cb76a9a934a1e46c6146cca2eb4d75a5bd1ba5f056e75d2363a6abfcdca2324e135f8a9fd676532a75c8696897c16e512124c21bdb1f6a545e1272a458adf2317ea784dcee45c85dcd67d71d9b31181f3d3cebfaf810e636b0ea947f5b7b82c6ffa41aa3f15e3680eab8a68020d3b96f1aa92ee18fde66218292ddd700a6099fe743661c218e9347bc434269d3cf36695d17eef5167cb0a5c34df6739cd90ba150056327e64e3998531da44ec079f263b816d6e46b5f0f6067a3452944762c2c0a46ac04bcdf55357abafb18bbe61876f25fd91afb26cba7ced00f42dd44b8c9ec2ecdcb30777f99383c3e247fc9c695a421dd2199b1132de527cd0e40e1a46c82c5a3055a441e7da03622a282da80a01b225fd5ecab35acee62b8eba4f4686af8240313a7fc20c7788b6e9d9f45fbf500e900373e6fbc6611b0eb34071dfa2ec18323cf991715eb78ae4e870ff449166b7a52a5af0e19eeff0fb92349a82083e8e5ee271258f3e280c2c5a963a383a23486de99a577e68418e1a4c838153686eca7ec1342d8caaa094f79bdd97dc1846b55bd13928691cdea8a4b58ddaadefb5252f45bfad5798d89dc9cf2137ae7cc3ce35e2443f62b86e15ce00de19ba9af2f00745f68d9e18774f5ca8f1795cece6b986270e3b7992fc089e6ab55f22f076aade115df21269a925d79560107a3687de8bd86e0a5ea4cbe8767de716f4367dda9da765a4e65e313ac44fbcc939232ca4c063c68e0c7f95b84c8a0d2bf9981c1602a9b8c1c5f0ac51b1f6f1dd9fdc2020031e45c5ba6a4d35fde20a96d049595ccac0099a13fc62200be84646dc5d645052dcf9a6916dc97a94996ef548696c02fd3b5783d5a673118c56c9285fe9cce98a3773f9c02cebf3552a421a0f21ec51920d66b972377923114221ff87b8d95f8dcfbcd8923017a1ce655614909aba3793e273a4db00b02ee64c8c5f69f965a3c4205b5338c69f2580444f3c58f837ae28acd126c97ece98af6218dfcce74cc00d1028979c82e83277d73bd44c24525341f843e676a1fd898c2f26aea61e6bfc3457197255108b4e836c80a940b0ae1f823f5b2d3fadf0977c3465b2a0ef1c9e99cbdbed4e6395c9ca735ebafc5544958a0c5e8f49f5c91520329214ec872c87282e1f5eadeb947ac60c793c87986575b17e3704edc9bdf00fa9bb013093bca6ec38d05120060d49b11a63675e5d04e1649932de84b9017efffca2797e242bf0b3d26a419354d1cc4e1b6112b22c462e792210e5901df5280132a3d44359a7a84d00a6d4e24e5421cf89e05f306457e99afa12eea94a6c4fb252c09339199239279ae88ec71e3976d6a73530c1ee807210a39c5e0f59d576b559cc9ad89a456823caf6d31e41ff61f96f8bf3eb31f44816495d960ab790ff1e55ff46a4e58685926850bc6d6bb75cc66d6c2668efcb8f8ea317efe91daac587d6c4b82f091478b8b73ecca00d5151a23fcbe12cc56d8206e6ee73c45573a48ba6641e22751e55b3591281aeb9de4ba253cd52eeae09d6b453d631682c2e55676b85f23582775af54badd31d9065aabbe7ad21328bde7a967d92b7d51fb326c8a5f074827bc0d7f5782c456c08ddf5786898fbf2cf497f798f123c76d2f3821432f1dd0d515769f0c256114ec019699939402296f65232954a20122055d20dd76ed1774e0356ad28ace0059d2bfe755443a81ff8446394dc90654e8633ed122909cfccf69bf88df64bfbb8237e278974e68e3073853b454739b07411cd57d6fe68d917b6956aa57e33910bb74e08ee0965a18880603409048c94a02a1aa2aafdd41b7c69934479311a369144bd8ca538bd6e63dccabb686a86697306de7848cf9a9356d0693f5ed73a5d47fbf2fa76ca20beb2506ae6a7abeb3d930f6a95ac3cb57721f1af8a2b81dc8b88f7721abf5df741ca0e64b2f3e743a246460ab7dd096194f3bcba089558a24704c3f491cb9d7616b2dd261db8c4a3193f0a0ae97806c51de42c4c70cab82be2b4ce12065a6fa8edd4aec11e4f0e26a85984b051d68d215153ee7336cf9c21e712b9ae362d978a69841115ae3cefac2c35767d7bc3c6ba19827f88c5c273649fd2c14f7e9b76304ee526d7af8d4901d48029e71be6fe2f622d7c43eca2fa943797e70f7478216ca58623b17a4ffc57c00aaabea44c8cb6998fcc3fad06d8b3b910410f12cdbe62160e3c282ff51e1d064ba118a39101b021547ec9952d67a6ad0d60165f13730d39e77cd22f5c30f9e654725f6aebe945f6a300c84f8cc957be5cc3deb49e22499a746e57c270f5a1b64d751b6dc49ac8e38cc6ecda80f4dcd24863323987dbf9723a179b6e1104efeb13ac37cd996df00c6e768a0a5f8bd5602c2cf1d0248278404488b86443365936034c9155fa99feae3aa825f2f120e36283666a833031e4ec5833693961005a195f9db933d5b3eaecedbb8b95643cf75d1ce583a1635e4504c708df5f6951d9bdf14ccba4a1ae64618751170fe9d3b6df1ad0042c7dba04ca5d06b1b67c41cd4ee6c64e61f4807dd69c888668d0d8626dbcec4a1e4ee7a8d384ba0b259d8a92bafee9d007d922af604a828121f37f18247b73a8e4876dbda83c46625f26b66ddd98c04fda34fedd9f763465fd0e7ca074ba32dd19d0ce8fdeedbd0a2d5b238bd4b5704f460987972641eabdf94e337dc8112d812cb24aa3615ebaa9fa029bd2ca2552a3a814dd3ce2d00799c3eed1e2a6573bc59356f1642e35ff9a148de62c5d758a2db335a84f48b0f70dea04ba8c1b757b600dee91beb3f85e3126c1a82dda12935d77ea26e64faa8557201450c85d7e40e627a20a0082b0f59def64123ccc253e45e559d8b20775cd77adb57481dd88bc17d6281346f72c3b894ea427639ad420bf376750de2639b19880dab2394715067484841f04425d45b89747d22417446aa1f127d752749772bd72512d871da04ebcdd6974d3c15d8518c9d8999690b7c566d9b3b819e6cc2d0aea9ff276ef46e35e002098e504795283a540070294c770d68b465a1d7331d82742debff8ac345deac800406eb78d43d04b595bfb5ef15444d4280112bbc58192b41f700aa8a7d5151454f054528affe675a3d65e5aa5195964ae68456eb1475db22c191d729840817953d5485350adfce4507c19a2edea201b12144aff49b7ea4b6e558dac31b094302af87037c55f063f00a93f45872f5116ad00fdb4bc43399b485505aff420cc5750d77e19396b42c41e49aa06bf91295fd5d7b7d8bc4a3e50c975db07de63beef593be38b24e532e1f7aaca306a415a39ae290540bde6a2276b70f0a1e6ddeac65f86048a4dc31a6b441e4b270d9c31fb6564f0f6204590f3d44aedefd35d3f5e12661949471779de67a88d9b445a5a48936920851b1be0fd26fba7bcc1c7c1f8a118628441105fe88f5a07f79db49c30603a7f7c38ae70d4d8fa0be69d438e64e945fa0f26197b9fea5c7606363930f7fed28df66958e4357387cbb892c72e3496e6f18af01abee52da3f28a366278c4620adb510e9ccda1d8f35b8657d1e28ae3c69a41482daf2abc5cfe978bca0fa8f9963103abc7f7f197d764fd5878214e9ebef8ccacf8bbde10f5a006bbcc7be44b026a1be95365192ad09fa42cc8c17e2a34bb768f2940e5cb90cab7b2a42bf93b90cf2ba700558c709afea59b93adfe78fdd9ce7fa95fe6589b340339d396d3fa3ee240f6048b726aa96ac960af6420073ca360a551105ca9bf12689fedfd7fc9bb0fc0204629faf6ac8be47037ebe0d7b964bd666f22ed8f88fa5ab7fd9d162831de56325f79d235049b4251e285b121c2d9ea0078a22ddcf14e20dee5e416a12c443a50181b30732d8c5c00f6c46300bbfa507ceb473f7b6360f7be212fc11f68cd045a03a41e74426c8237a7bf5a1fe653ee2e9d9cadb3574248fda8ce079545f214cea62f2dd7f445efd4420e3f42b4385efe37aec323171fcfd33076586ff17103f243ae485d71ae1a894ac1cb91b47c6d6678347cf635ee515c74aed8164a0821645a32d34f1efcb3e9441d2e5d8b6d06dad052d3ed164189b8fb554956f91a8c1b742920435dfd1b8ca6b4c84ccfc8898cf84172d47f55ae0fa08d092b839f3c2cc29416914fdc41be2a1cf8c6e60dc2afb219b2b4bb1bd7fa7abe8beeed9cc2fb6e59332a041684b75069db3628845178e84090276c636008ed148ecab0b323bf3dcb40ed30803c71d1655fb07ea982509c6dcb0f060f4f99a5d5e0c166e85e189c32518f9c4684edae6392ed47d0d1c764f8410b30453e5624470b1d5d89e3005adea2c668f958897bc18839941889b7eaccbb1d8960dd8523c1d3fe33510e92f977131a33b5a8a00848f52399f824610f6b00f5d65f9a6c40554ad6743a3cc34d5849561169974ccf6b7506c655fd4d28a8fda711e775d90281d1e1f187562fe11783e681ca31620b2fa7f449cccac54047f7a0ef45e014ab7a928274180d64fcf672edcd263224c70dc5e9f0566acf9eff8b782ce55a38cb765a49834b003ef04943ef472cb51dc96422243b60d2c6ca4051d260416055113a53b3206eee76c991a9c3618c81e1807c85a572ee38804ae0bf023c6589e6d82e4153dedf0310b499bd31cf3f6f7c1dc1e17624f203b25ffa2580c85582880e34b5072fa000985e5dae39ed2e01587e8df5d809b60bf61015e2729947077de1046a90733933c872b4b8ac3d0493ea31633290a0f4728f50714fbbb08b8f80d11c63a8eab159067cbaa8419fd43122d11cfe4dab2feee8f3900200de03a8de09c16069d0d74bcc5a94b4f17709a98f9bb6f1782251023a0915b983223ac11e605d6cc5f39b8fcbfb4b19a171979f941c9d84c05ad5fc213efa149df08a83f3bf90836bc0c5c89eb95a09110d4a99cc222cde3b483823bc09f8e82d5d309bb89040168bd995ecbc76356832c1a80f5f65c0e05f4670fe2772f287447329a27a067ca21154f63d43a0345985f0a76cb720a74700848e79556f4e4a0bc812366667957c2418c64175176e64586dab5c76dc791ae922014bf53b59d37173a84850e617f4ca952a57d574347657d89cb547059134316268ae90e1fc843c30443f01807699411ddf15be217822d34ebf2078efe68c00e70878e7e362105ed81e21cb44ebeed3231cdac0a73677e0438092d047d24841a7b204ac7761ea9c31f070d797899d40351b896a8d9d11f784af20d0450c3a51ddba7ab7b05fb2d505ec67eb56c8dcbb2741cf8a1741e1b7f1eea96ba2bf6ecd3a25abd58e56f4d3b49a0b175c04522aede79c30bb782a3b2829112ca000456010b6acb187eaa6832cc68dcab42d23887f07e6574119bd9c71c8085288e940f21b0904a8ade05e0ae879901ab7dc8b658488c7e45da2c7b21929c574d4d8000a813c8a9ef1d7e08dc2a58643a46ea02cafe9ae2b1ef726bafa9dceefaffb46fc1a936dc4b4d5bca9e538bf172771c980894fed7611e82ec79f6540f00fdef8428ff2e53aebec6331365ce9f32228528f59a7a8bc7b1b1a03857a1ba776666ae26a93b8caedfc49eb04c59cf54b6a951c7ac3403002d90c01141376e8cc6fcf485383a2bb6d9781de641c15522d7dca77a90211920514f4e59311ab63c9ce74d0c45de8b7736f77ceb454ac086f82dc63380585c1198bf0cf6e86cc4d43b3bb448bb13e5821d88871f73ea7a87de40d95bcc7dab35e00bf32ef8285cf2eca2856da5965b27486d51322a87d608422b1f41f33c9ac1c494a9413c6f234e099b298d12212e14e8e15d96e14ac645ae808a39eae4399ce8ceca8297c5e69a058fdee02d821f1f366703c122df8f4f4280661ca504580458a90674d30a88aff53e39367ee8d9b02a197739b0cbb29acb13d86ffe604f5b5af2e20a83dd71da5e0f92372db652a8271eb660881d3790b30edc9c00531d8586e3fe96353014f437aa14d03abcd501fc0a7d1a0c9a3cd061739b56ffa49d3ab5b7c95c196d23cb3dbd366a27c9a12f2170ffa47e8079f0ad4018f35bd3787bb8a24738fc04a17d915d797b5b1d1468d33151abbb7c7abc917f37bae5280a8928bf77fe433abf10c27876f7191019ad7693036e51a2cdc6bb1f583339779610d9296c4498c1e66c34d66b82af1ba275fa23c9b90bd8964c74158bfc53746f94ac2a5f95d4eafdfb640d50b2802d5c3c66bb84e297bf53f284caaf8615df3f9cff934db6c89f73245bccedb2274f71e136f2bf5ec0cff238513df545ab7d57d657585142fa606c3ec54a310cfd8afcc0a52dc4c80d197837197081d30c9d72b4313e529134ba8d962e2fa62af92d2041d710fe70c92c21a3ca76f0360d8210517fd01512dd63b8148faf8414de01c1f2cf2135c4dd09339a1c38f3c2b13bdacabe1b773d332b5cdbc6293d64ee34f1c2eb7680749bee7297b4c8a781eb0560fc9cf86ca430a2c9c18fcf631658438ba308e8a7e609f561d0d6394ab33d885c46f0c234e25a7851f4b1b5a9ef259c47196e0c710981d34fed13d6e37d4a50a158f65fd217cf9e59acbc0d1a68b49e31b1d5beffe502bbd036e0de662f21b58ab06047f314e5603ca6c62650803a30ca02a939a053fc54a589fb6fb756ed6e58721a9f2228b384229d8242c6c6eb2830f9ff07e83720b7b2bdcf7349209ebc0a0a26829e725c11fc5ed0edf0bd373384e266aa904838e2a0c70de32d78d30953a8403a6c258953345649298bfdf4f36fb7616b7539f88906e4ced5802d8f8017bfb0427dadcf95a162e516dd78fb53cc40f9342b8e03de1634b062964f685e3e7146d90704132ce9f103f2d19da32525d6412b84283b55829140a0b7a1bb81f578f0057dcb14e44a2efd89ff2ac1c9057954915e3b367d923a91cc3efd18fdce61019253ae46641d0ba28065009ddd500c14dbd45f6e430e8fa7acf72cabd56898744f1c4fd40469249226dc25b946024f6e07d59ddd9429e3d38d46312232b917b9ce7e19ac613a4dede9c9045670ab87af8e538d03162600e4f55ede1dcd3c7d6628f72434110fbc8a6ca38df1f6bbef228aa7b64143f724008fe0161d1f71c909f1617981f0df1bbe65b245887fefe0ccd4644f0d26a0cb54b8c6367e25369d96a85945379890d24f52f8a580622858a66c4014a30182a0998f1b0ff801ced4be83a2cd8f2618d6e22b2319b9afa9aaa1d5a111ba34870ac275286b4079958295630620c2d89e02ce5e214dd35524e32912e848054536d1ed2288af8d35d93266705726790f193a245ef2c0a986070a620e1667853418db68515014ff5da6208e0a2bbfeb77bbf03c7049986bd3db12295d5940f5c406f14eab88fd4688df8bf5240bd15cd7df3bee1efb90e5d3243df6865785468b120a43daf7f70e5b437dbe5b3f7090c958ef0ae1166f0b83aa954ad0663eef6b3c3bd51088b48a60710be15a138b1f25d7a193325c9b070767f1315d81c037f417b0854ab1f928808fd2031e4e4b7d87dd5fad3fc60290108fe4011f67bb10674df8dd481aa05f695e6f0379f6d01acb1f83c93d9a967f817ca2901e4366f42f16189d8ee5eff05718ac1eacd7c728571a887036f2ae3c370341418b494d4d19c70a7c0e05b37ca234332b1d0181178a50667b934afbe38a2f509007112523848960683ea1b24a1e46d54176b15ce29417a1d3adb8eeba514ccdd6f9f1698843d45dc6518c71abe938011dd6194123e1a445c0da8636b3a85f39f848d190e3b715abe851cecf221a54f8efb10cc40f65f3e1141ed5d31f104067d641f687056579e11d3ce25bcb2b6a5b7d399c94a3b5a4e7eb5e7dac17bd03b157123d5b00f045af4ccb1297e69a243b0983ffd56d0ce8ea733131ab46441160f04f21ecccfcacaa7ce88fe6a95005875a29336611acc92efb7e921289c171986298bc6e047f145ac541f5c2c5b1a74825189830505052b12777f7c2b186dd96006747b1f7a43e0fba875bddce4460ccd788830dbc6181f205cb243aa639ad500ebc686f768030be14d27a99cbb8768c8cfd96f28c0c4b6612380ac44ea7134081926e971079836d918bf6fc784bfc851855e9dbce4fa251c03ecc7819312d6fe7a172523b13638298ec01da41aa717bf3832280d247a6414f03d77c83e9761b3106eddfd690025dd9a3e4acc830843d4c910ccbfaea62ff0b0fa0ee2b320b16505d6796af43eee4f203fc3e2333bd4d2b4a44d5f8bc702b58a6223cec54e2c241b5f5d5904c1a66ae489384b3d74f613579e463faab43e6e030fa4686175ac2eee068fecb8fbb172ac17548b1a565d14c462e2d893a34d84d6385f0cb3a4a6d67063f7a3325ac955288681c718e823eb3d13ebf902aecbd4dfea5d16d40e74afa3fc3840e2029c1d3d437b0f0405737c6ed3c307f36ea61efd8dfc25c186fa7320ab0000f53983dbdf67eeb2b6200038b26ea404c4ffd5bda933d870687e35349906fab7d8ea1b3b49aa8ddf8fe6793f78c1d4bfe2c8f5b9d4dc93ae6d4a40ca0a65bcc81a6116b51ab3e26bb6377dd535d340af5b2d8a1c2172e1a8f98c3c7abc6bf97fcacce20fba731f7e7e9d2cea809f84573ab67fcd24d10a35d54e7f11f0e5ce0f740a3a2a157e226f654ebe5b900ce83ab1266aaed173259403bd6823938521917af8efa6b00c81d5e51296d1702300dad2138dc4603d545ff666209961a1cc8c784be229a26ff38450a76877f717da47369c8748c08c47ad636b22443efd00b43405b2d9f5213e6818ba67460ab38446912ba66fe5912aa228891d6756c16b1b34920749b7d9118c1f836de6fcbd36803bb619898146eb4920d0e2a21ac4290e717b6857ecc58479fbd8c0e6aaf984f40f22c3e596d123ab2c040652c2cdac1ccf5b73f26f8603fc1b4a4cbc97059372e065255a72758f99656b69e7257def75d850ef48fe64fd39505cc6b84c193ef185b7f5e539c5b36130b8b4d28a0f9612e75d8d1aefd4b387423c766c021259a46300a2960378d59d1ba251545341bee48e9adb99bb305d110e464534e5b589143be46195b3024e75747469f286e877d20c5c7a83ba45ce37ad03ce98f2cc4a5e7829739a4088d42c55fbcc6b5f5264bd0b75ed739de7f3157c1300da1e0370df24446366283049982ef116ddd3fdacba6b4ebd006ddcc39820200a8d1cc4308ad6837eeec4c9735bb90fea28bc32c49600d83ece6ee3a4b4640d44e0f8c4405521451c617acae365b1fe6270ce77bc9d410fca48c2843851b3af229ed9bb82e47596d45ccb9569bed0635d25eaf3d0280d077dc612e009979b62d98025406023de262e15b38716133c9d93794555c76ec87b198d3bd5efed6becde141c7d46f7d97a46e117a8b013631c080bbaacb1e7104ae501609c9ec2a724b93efaf9073aaad3192bc909f5a0b89b7dfcae80a4d66f76cff4b929aed5ed0e6162f742e2b049728ac9e0e88ce00f27a200ed2302e67b0dbfd141af01f1765639fa0a041788d4fa07c2c68440dc7803450908b2b9466a69c327ff7fa91a5edb0df195e818093d0043af4dd1b2dbf1133241561b75c5a0ae6a1cbe2715555569aaeead988a07132e629de3ebb25ad8275c1be25096bbfb9cbcf60b88340a099a4022110ff011e35a3e9771b8bacae65d6c13bd0a2b1510aee217f7e5c3a9fb871174cc6041f90be9626f295f47fd0347dc9fc0d3d72053d2161493dd938491e7229caffa12a8ca5522018f7304a13119eeee115ddba1230802a92e8a1d2d8242760b7b7821d26c5afef2385060c3b5102afcb9e71a57a30559cac6e34d1ae5c702754cc952d7f8d117aab3c17967af5c643ea3fd5f5aebdec8eebdc4eed43f3237c0d34368827257d0970b4e3c45674902a6076a030d483cc328e98cdcde411f566215a32b7fb7f4fdfe491dd6f668281e3e1e51b314ac459e8e34e5d583b1e2f077963b3c4a821bf511057d87874008e7dafd488f806beca4667bdb53a1247a685300c36bbd77ac9cae28f0a4d4f957a562af5fa44fa6709c7be8e62a6f072983331e7240a1cdf4be855d2279b46d5ad0ce03721537dd2783671324daac0c0c3c63ea64dc336ee48c33641188c4cc8a16629998ae3e298f2732ebfa0f4354278480e2029552ea5e6aac7283a59ed16d0ef524423f7b59da7161e39d315869143dd9c30ebdff0ba458f067bdee76251b72a490c72dea26ab3107c714b21635943ce51deb01b8718ee23e43d3dcf0f884ab8ca23a985a51cee920b89667a083d0b368b54a5a6f4dd769c1d4b59dbc4057d4737c64a32bb4ec0448fb1bd074c6971848853971bed98018c159c20dd133dd6c2ccf19a99b0bc52d2db088c5a25fedd7eec14444944f091d5252a9d0e09fe19e744593c7f718a04c60be114287c0c6b9835c51be870310eaaf69449381047ac664265d625040137243a33d1e016d3413b34682c41605cb75a66707509ecb6732c06e75e0104fe6871e9e0d319e788cb896873ba4f4deb37ed030cda661824cceef4813919eb4515e115557d54ce2fabb2b6a2cdaa3dc11d389abfe278c92f4a64763f907bdec1476045108c865d62ad6bf761541a771323cf2d6cc49191025a4725bc5e1c21d80fa556db7047ca461f2716c4833bd1c52a5d761490dd74b8dc60ed1e65851256805a20f930e309174266b35572abf74437aa408edcc5d41e2724efcae6c3344ea65279717eb844706dce60c430579ab0fabd80c08dd0c3d893eae2ec71ac43ea777da7403f97d8e48b2d6f6e457566048e98c1d1d3a3a884853cf1b126c883048d90072959560ccbaace0ec1e59313c090fdb23f00c82de41e2b0b025823e1d6fcef0f5047c014045bb317e99dcb002d549c9a2ccb04da838c9e591acc5f5e4a4f4c993b05fa0608113f02263bcc1b107ba447520b8f73ba74b64b8060b6a0f102d4b59708e56d41c3a28a86841d19d0f97d0a1b159afd6c5119606bf7fbea3a4a600b4873e3bf596b09c8024a046d7b4c2b5482f1d4e05e9f92c7574d96c604ecd3972e279f0592ccda8fb0369e72aa448ebc6c74b055470be6902b6cf184ab71abda3504205a09abae9dac67f4cac08e45630c3a6331f4df95ee3743f1f392387dfe3529a65958e3dbfc16fce01939ab0ff6dd0652c10b1a854c3d1dad4e4501f51f2f3d1844f522f84d057dfbda9e65c7600503e5a19fac0c9d527dc0dbfa29d88422b8560de5c258ba464871109c0f10ce1ca4e4492e17d4f6b57ec9b47cd77c35cd248fc167ca4a577b0c77e35953c27b294071b091e2799edf670dab12db4273d667461537e4569fd870a8142ff895903afd51050bc2243e90c9372df8594279d4d1e7dbd0fd10f6b80863731215c7b230b6693a98114a89608b64b40865edbeff32525394e730d3f236bff4cbdcf0aaeb5d8c2bd3fbd4623329f95b0687e0161b1ef1981c2f422071076954bbe34694a8017a735bcd28d7bb4fe2a09cfcdbec51b4dfc30f0803b1a42d8e7eb54d5461565f80afb3b71ad76dc5cacb8e04fcc18b4101e6c79aad808ba9cc9c9417910008adbe866a01313e61ca6918d6e498e7ea35678aa9be1225505b7e1e3d900c12af64343fc44f0374607ac2e76029b718c9bab1eecc709b82b041a10fc8e1e1ed32c8b4b142c6bd4da27c13dc66345bfb5bb9a81bedabe143a27b7913b9eb5f1c092acfe37d8f0d649e6b07f5c03771a13dcfb3bf4e31e9b04310fe60312bb3905ced9afdb0279e8bf38ca45433bd93f569a411fb1249946b78df18617b538300a69b3164fa6f526468ab8a16871af14885ca4e8bbe3f0a14ff42f2535dd9d956eb956bacf84f1e94f1cdcfcd3042fd6e017a47e0055b339913569252088cc0714dcd57f0b74afa3d374319362237b80ea5a4267cc6dabd9523825ce266decc54a622cfd7e32d0664e9d7aa30442da4134553d284ace0b1601e51070593c80f30842a633bf7559d56d3b5aa942b5636962bb8fb0b2ee0f34f6ab18b7663c06fbe9fa56f208bb04d7f1cf5acf354ac738e747fe43d3b0cc9f1718bc9d69e8f3ca7f236985e9c97b0bdcc9526d25e50103326bdac3f0d06cec4490fb7aa563ed9b771770c98890d6b51005d458f3abaec012e0a7e4a9b0a6eb7b9c0f249d188de38e5124e54fe411698b514b01c48c1576dae7594f2eac608bf1e0a9252eaa680e91a522479101c4e886210b3a39ced2a0260d7348aed774e9ea51078c150875292cbc5ebb8ce8140d66522820e384a052b56830f867f890205e2e95ade123b3960d2a1e28f80b7a699b8cb849f42437e222426a8151eaa2538acb2e7f8bd120417243a931d1ac0fcd734fd73d0665966ce1f0d0c49c06041344dbb88c3fe5e93183208ae9a8aaacd98f6e1a0abc76d9b423d102863b22bbc1bfaa128d75c44b1d6d427c33c1521e7a691246ecf61372b162a63e4be9e2c5280c5ec4bad258598b89aea7d06ee7da1b9bd4fa9c496f0ba1913d2ce48a3ecf8b1474481cd94911cbb70c34d92135c0d057602ef3718f01a6985841d7f9b9184cf87fbd594c24beba5db7b98c40964b9b4c2517999c7a1fc057058005ec4eaeca832bdd498540c19e400cd1d519d008e2dd9cbe20d0aa273cdbba5e70a556395daad2b957fef8435bc28c0a64bf94765d8d33d3ece160e14b4b21bce7e2017395c8bc998f809a77017d2090accaf45e142c45f2a03b73b0345caf2340787fd15be488cf93411194d8053bc7e908bd67211cd14529209e4328e95c3931e0e087404561b9c33f3d100533161cfdf54d8fc53697f955b0e5fc874b590de9cbddedf59a6801ad662abbd8bba8056b6906828f72bcf3cb83e99d5b18174092aa25f468cc27b014a819c1e8ed9b9b1984913f1f5cd1aec326e9727e13c0035f47769f102b151581bba3b4972b0511382a73f11be5a86a0eb85fa4a7c1604e048fa9417c1e3218f1c3cc35e8476faab97a55c3da0b88334ec17106166fe078e8879bc8973958660ddca98f0e76ab275fe2e96899180fb98f96536c35ca40c24a98db9cddbcf32729c47c0206063fcc713faf1b68d164f8e171370ffdda99e39d7468018e612e5e06c223b69a9ac0b1106d61455a3355b8ebf7f700f5968597ae2a7aac39544affe9d98f0e28d984aba5010a6f28d6a21d83f78434646254162b4ffeecc0af5ff8253d30b760cec37c0fdf6c0877f8648019c73b282777fcfc9d8b0de400461605859babf015c084d5aaa77530913bfd54263667b69ce7281eb23581d33d78c08127f40a240dc13eaf84efa25ce43b360a8ec10fb6526a50946fad9ea60649c109a23dd457943598ea1a68a42a3457cd9ffd0c15bfe7f414c968a1adc6f3b94c1e64d047c8b2628cf33130b26d10769153a424ec3db341d97e886564b93ffdf278b9d61a0dbd87f039b0bddb28f91f617d64eeee27ff6b56b491229f78bcf7c597477b51c2b6fe36d209a1cd3e986c22b51ed0139610112f6f4fd9c960257b1680dcd66a185beb86878724340c60f221d69e387211cefd49fa04ab4c309b7711e9450064e5b82146b2743ebb5262de301f84cc1c127356a9c2cef4689e66334c669a4688519b487b2753f41d01d100f913714bbcf562ca45dc1d0e413c95d18019e69d434325995364dd47823800140438c18eed06c8af448ce1fcecd0480fd4e5b9902c47a097612b99456389c8f19342e17ceff0914027734180bba0bf7bf35b8d2a981a42725737418f13e5d5f1c8b4fd844385c2ba7329ffed981f1e580853c5bb2ef21793de52e518e81d093eb7b980d98f34113dd60edd0a4df55e3e928eb007d3ec9e00ca0c64453bbdfac9acea7a47b825e7e850c32fe3e0412cdd8b21730e9848dc28b8d4cf4d6cc72548ce4a1b3ad5b1af9d315ff0e6c661cadc26794cac5f5c16fb7a6dbd2661ea09f4f7a452d0c6a7fb3040c25a13a65e7874ba3ac500005fd5e06c53b0be94d627a57deb29f9dac0d815d49619d00f6ddd932e1f79849b9c53c0d2daa8d70fa893ed56a278230c86efe6fff4fdabc64c54d2fe9b953d5e5974546764a544908d5dee04b9d77a9053b96938a1082ecf743b785aace1c61b8b883696de48f69c12c14d63e985fb95302a0b81e24b901bfa2eed820f4974a5a0c7c6fb98718e5960067695622cc08294c52d0c6046c8520f95edbcc692f277ff73b24ee63e45a7b7c21dfbe1886eb630bc79da07a51c4c523f0c44ddd85c94fdc55d5c285cf28ece762a6bf7fe712eac1af3ffd12950aed9d33992adf81723685105646c248204870115eccfa1130b7158fb9aa6050c0c612e54a9b9ce7cacf870c78b35fedafa7df4f2a9a5ee07c20939d5d1e7deea73efdbe732f59a9a06a68cd7d3e1f658dba270ea12714fc2c04d5b592b30291cd9ee74a552aceaa491cda583e076d3bddde0cb12bc2b531dbc235f40f42ac5a50d124e0c5522c6e3ca31e10a14f7066e97671828b8ddbc78b6654d82c2156ae13e390718d37aaef2235d072e7d17439edf8de331b4648d1ddfb6e56d6fa250725ca519081b22074a5c311077cfb0aaf9a23e82996dc55773b77b6b7f884cb06188b8694ce171c0f2afdba133c63c5caec941829839e92e6d3d5a25958bbe8085e3f4fcb129ba5c9d073f99c488ae5af5e71d1e10f83b07949e0c4a4da45cdd99741876c92cca824a485a76bde3b27f52999cf294b7aeb0b9b9b9e12f701c7dc0b5b530d392c5ef00228d2fb7dc768b1fd2a66a096e32b939aa203fbc29eb6bab9e2b0fd7343ebb2bd58920469f7e3af89220b3aa858f7eb1b547d6b329522c752c1cd4a1757ceccb4b06678d2e273f7b70c0e6b15364528b3de22632a95c3c9d6cf6f026b285b8bd7e9e4925c1eb987670e38e5c01bc5d93046a4c56c06d32cc25aad48da50c23ea8c094b25f9e549deed2a65d246cbd8e44d64b390a1828909f568ca84b5f68bb7496faeb52212b52bfd9555fb47bdc7503af4e6fdf1a8121823c046c8fecd5dcd369cd86ec195ef90e48327d499785c8c320db98c3bed413bd9d381837d8772c10b842197e6f48a38f1ccfbe680ad96f2530b38dd8b88605aebcc22837741598abd0ae8f6b8fb01a5408c0a0776397915fbf62c3658a6e38f760c6678aee9158c753adff0483717e8402ed5f1732b3f5fe1cd9b4d783f71204ea4cf79e0cfa417fcb86764260bc421ac1515e20366c1273802a520ed237f221439aa40abf4b726c74cd21f4f5356befa3b1ddfce3469ba17b73644a8278a58d139835e7cee130b3159ed0650b95fe6d8d15e5ab87a4c9dce59a33ead8755da8412b8a1afb10241dfa46ba5e29f99ef44f02267b9e8724f24028cc47d51e6a8fd65fef24d70c7c8430ad127315900a23d7c73e34c81e3b8b333a95d069b03c2df4cea0b6b7f255d4baeaf42d2d60580483cac77461d9b2c6351019412fabd1b249966dc782c64536642cd61c77c1a1b73ce530419f6c3a81c5acd33f335d9176a83263675225627fdc756301dbf1836d39f683610716971c0188da5d48fca374ad0a523eb0e920cf27ab696194097b286532ed0c958d4edd6c8f09b3917be17b31c2f6f5809a3b46416ccde64a6079991b30ee7cdbc3c73f71eeb9c134cfa20d069e005afa376e0f995aa23b96a53ee9afc12e544eaa2e8f4d70c02895f8ada89300b7fb07b8b859f1f7778e2451cd3c5a14e2117d5152e081e3e7ef9332a1fcd50112939949ebba9359b081aac97bef81fcef101b14c25291aaf50d3922416a100c2515d1a7a50953a2e7642e408c6532efa78fb5f4a811ed8fa643ae222720364c65e22998330f116a07a60961a16a099bd4eb47ff03a88604ba981c52d0169bab95b5203a1e158fe18546128dcc5577eb485fba8fbbe85445d06a75976a88a30fe0a30769d42a8ece38eeefa432ece02780b4d1e99e4bf41eb84f6dd5f3012a0c55f908881bae7b67ee734006c2dbc257496e3a6f3ea8b801b278e1a51b5a6c6f2e6f82b560c88f6ffb5961fd640ce6f36d3128e58dc1085f1634ba3197fb3131755d53dabb7a097feab6fbcc8227009a8ceb933d993c64ded3cdcf9c05d647200d42245ee1fdb0b659686790a85c713302254682fb598586f9652fc3effb782bbe84fd7506f66e30b3186dcce6107abc2bba76061fee3d3bec4c46e7541903aa2610cefc11ceff9a09202d39f4351a7cf727c1e0560fab4115562f22d646fc6f9ffdc957251ee389ffe09d94448c8770b1810bba5c6e29504bae8fa84d87f14cca62ef60b4f4f0ef28e795b1fd3bb4ddcb94d9d82119f2e57505d06e608d220ee8b84b24e5b438286ea034b6d23e0d181cda5d4c78eb202d3c0d4704b85c5995431b752b7f9b6b7a83812633ac88d63eb3cf2dd1dd8454af15241117c76a77747c402f8a0e9572460e0e9075ebdee4dfd0d278a7c81e8de39f5ed1bbb818c414ac7b41c0116d282fec400f5462caaf692aa74fec7051570c5dca7e16c9f7a7a4eabc4e50375a27fce38719d899ff626a1763b05cb83b4e5ce4078904d29eb3628cac04a71bb090d34513f521e342c3a4136014f4f2ddbce97c840d9c55962fd69f84697c0dbb990532484f83643fcd71e912c59acb78dab399b7d6f51afe1ed68f1fe1ac63b29b5f5b1432e2273ba797700f9781094d8070fb83e3263809cff16d606d36c3d5469fa4fc850a27200ddd522611e4adfbf1f69b51afc00e6d561d933be9a2a42b50f78133cc77b44e3d284e0ea3da2aa15596597674f87ed11715a994e518bde2d3bcd45cff875538f083f06478203109ecfa811ecd700bfab39f476e57319a7c0cc69c5e88332eab4e318a3518f16e034084286a95d4d7119f5fdec1344f3e847d67f7f78a4bf1a444aa6b35812c9a101d6345b06ca3e3fdd2e6ec20c79d8953e65b24d95cd2d5a367898d4ff43a58d451b2c8823faca8d8086e7fb325f28c895c9e6175794c9a5362d004cec5cc34bd2aa5427b892e29c3ccc864566998c5f315131e831b8f7f51621b2b4541a091c9ca5d90f4ee3fe3615e5e4042f5396d277e204eaad4dfe6a687c6ef0c3747ea2218ecda7a71c88b4eae299bca7a1857006d8e6afa5864af6815beeb5540375494926c8499426bfdad482a514748e4adc08112ccb7035147ec04f0825b829938f29291a63898fd20fa64e17c9f6c93a27fde70dc7cf131c452e075a58522e7ac2256352d608c3206815ab4b6e2968c543b55fff336140e935392be7540eaba424df5390972e558afdfed3b1566d235156147eee1ccb5a6eb2512179b69c497a0e4ae468668d0439ba8f45ad459900f41761ecb1b9bbfdc46821cf57c4e66c66f39ebc0a6c7400d43d41283a926586edf0015f0297e325686b6ae7054f005d0794d02ee539bd279bdee372ad1e2525d828bc0fbd6d15a0a82cc741342e20e21ab45c9fe986ea584124cc28cae518d162a853564f0dfa8aeae56f97db9d14acc382eae5c9145e44701dcea0b2a6aa523d9dec9a146f8d92ce375da2eaea2265411361f35b54177c69155a0da080b0cd925240acba5262598a2bbc8403be0c4475c5dfa702400e69cd68afd445e38e06273df0e5dfb043eaccff094130754b7205bed36f6e20bd668c3522987a1e17e6bc7e660c12c139f8c51db2ce8ce3ae6a7b158e43f85f9c8a09001aac88763ef41ba4a7dc8914b6a16a41a843bf5ce42f195db05a52d702c23091503b19330cd0b296a5d0d4381e42033ffe33e40e249e6848f653b95a0859a260d2e4cf3a974269834820fda1c8a91f4177666574cea1cf3a20534756bccb4bfdb89cf862da8fea4bf553f0dabd2654210a4bcb61c464fb7a01bbfb9866ecf4f63a31ed92fa24f8ee1945db7e26f3bdec71163b1a603877c28f73e0c34b036f56ad97c4d0b1864558bf38fa3a4110f50704b22a20a326601183e8785c3a5d86bc8d6efc3cb41d6b1660794eb654466d31caac4b206ff79e7c8d20112610e621cf1f69a581ec5518a530d0f93ce9f0594c63d406252efedcaaaa3825ded87e170dd16db4b2fd692c19608d23f5ac3f7665ed26fb5869dd88c439c1528c53f6e234f6df8da5717c356a774a2b59aa714a9e2763ff6e3c69dc27595dbd44d137eada0d99213811eb140e8333838934a6b17ab1b0218bed0957f03eb1924eb790e687b9e670e4501b1e2f40815447d8a0423c0effd8bd4967d18cf6fdb2310c7494cb54386d1efe526cf72eba5f48e75a716995b564bf6876712131273cd49a2e8ec0c1b793cca30760cc7e66bf50e9dd90df82e07dd5be1f032a8aeaf2f0c76154fd3a403e1106641c6b8618063b03a551c4d8d2de1e3772839196a9ba9046d1e22b5d59ff9292e114e6e7fedb65dd5dc071f1d3bc04b6b2a2a95c140e589e506472ef632c7cbb8d1cfbd24c141322a5d310803903c7476989ebf0c3db04a84114b3332687c11abd5a9a7973b7db88115fec445955749a61f49287da5c6471560fe4b24054d33937395fce502aae488aa6450b982852017d820b35c01ae8a75173ee46da3993c8351b55a75b5b3112aa92e22019c90c0976288198790a8236cae58612b6f3757ef632b37d3f1284505a156f8642135704fc35891ab9fb338ea8875ad113a0fe8723ae4d2941df9b7087ac0f4be64e527cf2a63e19eefcb0f38536a157d78194f39d69783a8cafa9118e7e812b3fc8fc525a71a1c312d6b4244711a0e06a9012f6873801b8e68f5bcd4a6ed332723de42c407570f332515efb6b287d33170c5bdb884caa37d7377d1e6db0c1d82b63bdc04e89d4cb2756bfd96d3c5657183b3aa2b56dfc77143600eb37f8d03709c583e33afebe9bda951f8426c42728e453abb4ca75c5bf9b6fb4a2337c4342f7a9e7aa0fae105d54c8d4d6f4c5e7caba88b94497d621429a656bac77ec1362f7e1003cab904321fd6829afbba87daec9a425ed578e59fea50e55e6518ad841ff9719e3f1d9a35f3d560c3a02a622d0dbd1fd2a0aebdc7a1aabfe3daa8a02115df38d46ef0040be8e70cffcb07ac299f5d0b6eb97a25b48d609cd2b25b71f5cb428a97ec29222e87a8cd074b2c60f27fc71c4ec2310c16b310ecfb93498966d687caf936073ff7b19bb8bec4baba2a64f5381a6fbbf3aed85ccf6f8d68a1494e4419313b3ceee637005685a23c574ac2b7422397201126020313b718a621a434dde8314b5b598fc8aa971728127e48bbafa247b662bb9342d032cb537a0a08be74d6b439393472958d8151be9c30a1d711c586211ab668a53ec0eb697a61ef0d646d9f9c37bf69be9dd3bebeee7e6eee160bb2fcadabd6cef3993a074007e905ea210ec032d20bb3c3efce317039cf0d511e91162ba94c1fab41d1428fe0b6007845e13ba0f69d275ea00d639a45d514dfc471b5d0498426bb1644e1ac91c295b5e3208d0cafed0a79ea001415996c66988bae626ec5a95076ef1650ea1f271775f858cd352bd1ca200f30c3f46bd566d5b92d16699ea1d009d49f94cb19561b84e07c0662535ceba50d7b3d339297d1ef80306374252c6907a04b6154d519cb901d5c4f92a56c934167cc9f09c68c8b98f0fb675b6ec51246f4a4aacb6bc969112c51e5dc91eee80c24f3a72428218786d148625ae1777cb7111514f8470e69cabf5b72ab7e67798bad7474e72ac8f2c5636b35b0c967683048dbab14c585d7a5becb531aaff7a791eac23a1198cc50811effcb0c6c876bad6bbef6fdc3e17ac05592c8afddb83ba654f101e2958f55c4b51f77eaee29905753b46a2271fa07e89705f833cc5e762ba34afa2ca6cc3372df57dcab681970842d050e7b6a7ed289a6ecbe77c161ad3904682e441e1e1a4ed4467f44d5bb107d2707cd8c8683984a5865c923ae707711e89c75040a9fdd6e3dfada5964e80549dcbdcc4918eddca5501c5add93f6d319a1e304481da45946537f2e9a0ce2413c87818e65975a5d40e168cbcbb1de325912411f05d1153ee2940fe12f0d515144af063622e44121d337896503d61f780c32f01b1900844f97e9b4fb7e6c419a2527768db92649ba23e00f20fb102e02f4f6978576307333b5e6fcbed0d5002e2ae4763e3e1d42b866d8a822ab0181cc67a245e9a7089d50ebc60c304d6103237eacfa448c2eb9fd97442a759b07276eec1a8d93545d986dc43874df34002952559cd69c9924544eebe1d34910d8878deddf8dd4e2a25513f4e19be76b59aed2299951a92a798d2e7af9624b02972294a378957e6d711818b129384162bbbf722ba82d624e1e38abd04c17d5a22e0e7bde8b747a07ffc079a73e9a1b8a182188a2ee6d9f26524a242ab33d7d6129f0c1ab9e9a6f9c0c01115de349dcd14da44fa8118247488aff64452e02e04f0eac80daca8428f80700841d7b0011192adbf90a18aa4969fcf5df373254132855c6ccff4567c8cd4d845c352aa790481af22f4f22c00bd006a814630ef6b0c988353ce59d1ddf407a0092df0600972a79d17c8dc06fce8e1129923cec677fa029e36c29cff217149cda3581883a79c16e9767bcea4cd1b5c014a4e38f69b2239e65011868f0062324d4e8bff426e274ee92bd981f4d3847206bdbb3510b5693ca2046e58005a24a93890dbea3ce20c5b681ac97bd68f0c6818858a36f6997c18cf26986d99efb66f483d21e29f05f7e406f897f4f8496317b95dc5dbfb392426ac5777abd51c989caac40243e462ece2989521d9cc227811541aa35f8ca33cda44bcbb256b0354ab03ea398fa95b300eb34a078e87ad8eaab270d9f75d557b5eaee7669fc8e15d89b7eae1e77051bac2e3d24da98971b840e4c982283c3b4e2f0001d86708ee0fd790e0481bcc6c87dc596239d926f656737eb0214208d972ef2d37d9726f29a59432300a010abc0a2b91e532e25eb9f4d64a64b15cbb65b1fc9371cd5af9d6cf6279b712c396af4470fcc0fc96cb073dfcd11a4df001fa062a6fcd1c8aabd5b87b3ef4164bdc2b9627e3396bfce1796bfcf139cb57620febc1b18745e35afa2b86f5d164bfce1af74c4c4c18e69cc3cc720dea1c3a0bd42e97cb15e64ef4993a9562a25ff3571e8e3fb273fef90d7d7a987fe415e7a0b8590f3e4be52a5d232313e32c57798d17b372cf5b6208e6af9c56cfc7cab9917a1f844e250f5ae9677005ba1659ae125b5df57ca8bce5d2e3403bcb79d096483d1f3ceaca73ec7325d2ef2a07218bc23ea79e0f3d7e2ec71fd9a76f6fec617d1e3997fa37ac876583775baaeb7c846edb6eca9547e8b119a1c7a646b104cf742ea7f1fcfa3a9518e3a0186a2de3618c87aa0f5abe729583e2b67950837afb83e32b66f4d11a2585a97c056a9711b74abb7ca55dce83ea111ce1b56b9e04400ff1d65aaf5cf579b80a5d35c2081fba4abb625caec71f2e5f8d3f58a1b8bfb1c7e65d36afccf23a9a9cbbaeebc20e9c99999959026abd1af7cfaf5c256e9b11fef396e80fd6d4c46c578c28292c6c8135a0d78060ab86e59fb75ae3c60f8e3bbbca3f517396b87b6c56aa300cc56df3a12a0c6d7a6ca4d208360eaa504424d415e96da7db40f4a744edc5f90fec9b73d835df8090409f1cc16ab9dce5b98cc7cc6495cfd898a03f04afffb2ab4057791886a0cac1d15298e779ce1908c63e160890cf9e3d5f8d1900a1559eabc61f9e77e38f1957f9d857b9f6c6702666c6d592898969b55aaddcca2c168bd51d813190d72fff40af01c397e73a679db3ebd1731b17fd94b09d26dd7eb08dd28ad5b50f4e98542851b9cb573431ac18073f08dd73cfc3d11b654cce312dffc6fdfa2074ef6b8d3ec2d11be5077930c7e4ccca2c974eb362a9562b954aa55229f9c087a764f118c46e63101f1a844e284e84cc8ccfa8dc45e3afce3fefc496ebef731a9c591a67567670dc26d80fc1051fe3a06bde079f772def5a63047e3ec6f3d043f740cfb3ca5b9de8a976e7dea85bfe75ae7912d00e46e0e77de8f7819f0f4710dc786f045d52d8e7dd0ebd4fdc9fb7c60c80b0ee8d3f54de1a7fd0f8e73c6828fad06ed4342efdf5d1cc7ce37681fe79f77dddd77d0e7e32323232200882204804e8f341fff2e7d3bf0fa49ef7f3b9f6a114f4c64ddf73967fe2c63163e8ad707fa07aaef2957fda439dddf3a12cd5abdcfbe1435f3586603e08faa00ffa049d1b77e720087afe89a16b71dbd75e3f0d82df687d743e7de4a4c641bebccf9c995e197aef689b7881bc752e456fae96de5c97de074080fc96ee6387c821c298633927e332341ee3ad19cfb3086aed2e578dcdcbc1b1ebc697f7a2f18ec603d201e980000142e3f9581a1aefc61f2e1feb639f66941426e372e9332d970c2ba6d562b1582cd66ab55ae59c7326c218638c31c61ac8ce05aeba4094f40663acb5a6301f0b04ea7dea0ccd6b2059815405d4052232765894deed79c5fbc10de268f0254261f77e705dbbe3eeb9412068fa2dbde706e15085901bd42581c205a90a24cfb91124284a9cca532b4f79cc9ef196b37e64073dfbe75d1669fc13f574d0e84197218222f4d5711324265fdd661ad71ee839fb973d1cb57fa1d388bb73e933ee65319cd9d9c3f1a371d07376cd93c0e7fae53938eed7079d87a0eb71bfbcd187f61074f923807aedb99b7150dca0d38c1900a1c170c6c3f1c78c7be30f19077bf4cc37cab874170b948959f97570dc2d164bebd56a057a07aeb283d9c155065d832a954aa5b5d65a774990aa40aa02090a12143074d033e8d943317bedb9413708e83d0753423708241a01d40dba41190b2a8836a8dfbc9b43f5665b5161924d128ac6baa2274ceed1132ca923286a7842658584a8e88888918e12dd580746ef4d68434285c2362a6a4e1ad5b48973c4449bd469e949893acd252a54873ed59dab64bba1a11036596ff61502e106d59bca418ed79e286cd342619b6dfa6d8cb714b67a10244ef77885c7177e5c6712a702e0680a20a5a725ac29e14987ea8eb55ad417bdb525a1df7c13aa3b5bd16634f46de8ad3dfda685ea40511d2e54a77640bb79ebbdf75e7cbffafdea1a140adb1c8b9a12856dce6d44f4b98d88eee0a6ad686bb2296d5494be6c55b6a30dcab6b4316d4dbf3987ab16953453020aa23a5b73fa4d8b2262a48aae2571a20fa125252854872e694c4e1a12aab3a790464475a6a684ead022cde888ea68db041267154fa0b0396a4341286c23aa37db4c6f6d686b43bfb916a57d990919f2392108856954d3666b890a206d52c94546cdd934e2734290399b40d38814fab5c105d6b5250adbb84d8813b723143680d91450a873e2bc31143ef71cbbac3b28749e1d8fdbacde6cee8db8de60975e089de751d61bac3d6951f526855329ee0e25094971585bda84b428ed49f34ebcae2d6d42db916d56779e42106104124aa83bd6370adc58b785d15b8bfacd3722aa43e7c684ea44519dcd556f36b7a2d79bcdbb7df39af8dacc6be16baee58e3925beb922c6f152e2e08983c2369f95256a0adbfca5695388eed42ccd2125495234f96d23facdf784529790f89db7242bb62424bf6793dfe6d16f6e637b2efd36e9464475762ea23a5b1b519ded3aa23a1bc72ba13a548cdfa8d34614c6efadc96f60fcde92de87d0120e57ddb1d7a9cd36fed8a0009064fe9c734e5a59207bcc48d139c87e1125bf04a13a428accbe54180c0683c16039b07ad362d25b72f13921e8ba80dbb8c10439c88716ea9c737aad3cdbb66d1af0687627b5f5cb2657d580de541e9e1e4f95b0bc992e7594e0d218f37061efb538602e0d731902d5c91246b5bca1b0ba93c3537778c81bdac30310248cc2a86f203fadb51af060217cb0f75a09a36f42909f14bbecac9e1184a584edc89beaa0bee63a46b0792c250ca75c07d54971205098071456bbee36dd29abb442dad269677d3a27c501a394527a6795f5b36f672b602c4a7983e91a93a73583de3c5f44e2546f05e910768e0720cc22b68b1a369be238e7b484b17a9ee6694fc8b1a9d41a52031e8c61b09c1c1e1e2d242f0b7a5684e3b028e650afba7631e438e40e2da8070d785c4a1efcc5cd185f7d1be0a5195f7d2be0254f15e50e06a4f6da2882c4a90ea6a1778eebb89a4e973bb4a1fb45aa0906cbd1a0470829285560475096989a5250a80e15aab3535efc520aca8e140eef0198de42ef54d2574f25a594aa4d359560871ce4830c4339d53ba51950500faab35d20f4e081d2d64384cb241341cb231e3a20905377dc73a4bb6a86b4703ae4a0231e3d4220a127477239924cd2493e4928195557f0d55a6bf11772a7fa206faa8f3106854afa5d53c3a3de54af75688bde395f61b01a1cb4c9d619bae954ce499d523ae79c1267fe1c85d09c3e169a338b5553232345059ad534cd5a6badd62e973b0cf632e741a83deacd0c9193b59ab556d3344dc3d5037a536bd096c26aedacacf49b12673a0faa6d1d964e2bed7775d41c1d36a8864edae06d13650c8b32c6893216e3442cea221edd530940820019613af2346793c80c9a42734867cf138cc084ebcee6d245ed09e1f5a682d11b04088490739403c3514495485902fcc5de6b71c028a5a34b1cff49773f3d23692075a746497c5dbaae944ea7fa750fae87300209b846c9e9f435cace649ae86d7fcb2a3f5dc609cd424303a1b7cd3f68ba9abd2d7a99c18f4f1458c84d8e8e8c8e2614aaf4f4842b1545d6092323a33b8592926644772cdd985052b2d4e62ae1254b4bb3ba9c955b014ee12484baa19a675df74454948d61a2bd2af4d6a4b0c1c84bef8aa8cece44be7ac59908d5d97996675407e80e4daf7506815374d2a6eba2686bad6be607adfdf60e8ba64fd36f90c4b9770c92424c42d122558c31d2d2e9595c98881114aba2c4828dd8943853e64a2b86d6902a5f21d820530722a29e62767c853006b14ba811aba3ac3fb1216a2841c5aa6897502386c328aa4b9fdf0027aa388a5531e710eba69472cefb5551c6ba2d4b69c6153800c5641831ac00c5a4e30b8662172cc139abd0ad918415a85887454cba9e0ba0a1dcb13de9ec09d11b6bdb8694f76af76af75a6b6dadb5baa02badf40671b2deabddabdd6badb553bbe6a43a4c9f5edcf5844be46aad9489973e4e5b299d734e8ee3348ed338ce1ac97df3d23112f286babbcba565d345e21ec99d1b34ba2e12b2ab3ab8864f7db33eb76c9252db7c87459a30a6310b5577300e0aea522e3d136442741e428d7563901462a9cdfd0d0cf50a5d22596f248e91c4995ec50b2471a6370531f4544aea96866a89b37a2971ae9885de29a1a7a090dc319ffaa72477b49ee20b442d404770880650522af5746c81aace346348d7a18e5210945910b60294b637a8d65a2b1413d55f9dca30d7ebadd5ab07b1aecd246708318ba19082dbf13255f12ed59b796fd34fe96407a6974e7440229b5e3ad1218daf4172473d9238d369ac1ad51b34aad1cf588a0b1835a0cc62136a368b4d3fe18362ca1441b3d8a447b3a0ba3363d3a750dda952a84c499ac5a6cfa1ba2397664575878269124487ea0e35a2443f9dce28906a0907a7596c3a1582aa3b746929eaa96936fdf4b944a1eacdf4e993a8681a955077aa51bd996ef4d341a83b0ba862c9516cba082450a3ba236335a8d6190f6a4447a39f5bcf4ba7dda2e3a4b66ddb91a1df0cf01b25e2b76d63d54481f2312f91cab8f2dd4b242953b29d214a4bb72f91ca5872f9d04939e7a41ae890b556590f7974af267365d174dc1a09111da2332ae44b952626a3264ca6d00c82e2d2b3d40386e3a58de42ac7711c9df2fbf6cfbf4200639ba77ecf17621b838820866927a36889ed7d4aaf26455bdb49d1db85a4a9f3a854d43a295ade99436a4ba9be52e60d2bc92e275abb9973b13c570b3475eaeaa6b8d4a574979296cea3ca7b97341d33deb49b53f6de2b71b66dc318739c8e4fc9aeeb347d0d6fad22ba3b72cdd2e0250e1b223d5d87e6e9a2747499a2b3ca384aaba594524aa909293c154d48b9cad074dcb9eb72ce9f27d3e0dcf0a67197b3957253e61c0c9e74948660fa3cf273836bbd0ff0638933bdbb73de3beba472ce69bd4fa6d15159e71c7d72501fcf1cd339e09e456144dd7c6939e83ef57a89e4858aea25521954a8d79a243d67119ae652d649abcba85842dd7979535d0a28295f7d8baffec443e24451181502925de80307280a93d7a56f8c359d74f677ec4161950785d5eaac31a4b3c87842f798d7c51f29ce4590f2105b97cfb9f6c2230829f99ceb8a9d136dc79df3922ba2d37cd671d334bfd5effef9297164948c12076051b96145010b77187518f8464a08835a33137ac6e09cc1c0ad2c458a25a4d65abbcc5ac3ce9cf3119c179dcc127a6316167ae397224b496f4c297d55e7defbcece0ee6fc0ee6dfe75e7a1cd49f23d64e7ae35bdf266f7952989ca201bec3e277a93cc5f07e97f3143d70dfe77ad7d6ea79744a4ee2489f629e426f3cada79ebbcf599f627dcea6f4d4d265159f39be7c8cf9e7d893ffe77fbeb3fbe7a7b8e7db978f6d5fe2973fdfd19edce3b2577b89bffdf3f83b99bb14e77de2a7307f2add6e18a7d592b8e2890e736a30b0f6f66a23d02cad73d64dd3b81abcb85cd2ba66aaf3a2cb51644d0333a7e67d6a7c4580daa843f771f802c91d5b94e9d648eed07ccaa5cea9845dd746f0d5a59c122726b35eae52ad28c8d8e783174bf49b8be2a0288c0b855df0daf80d491cea9988965b95f49b9213690c83507738288ee3ac7825ec5299e372507587933757fccb41cdfcf59dba1cd4f837035f7d8c5fbdbb53e2dc14acb552fcee07b509a151d179a77f204ed7e4b473ceea5db54bec9d723528b42d870dd7aabfc0dadfb75a9d9a487fffccc9a59e5c4dbaee4c21f4c6e59ab32cd3d35f5c2e57bdf2a6ba5a9bba0c45b6b68d7befb5dee7e5abb52a0163b648054959b1ad24f071e5a49fb5318d025d2278bcb32eb4741ab3a20736bf185be9011ffc53e6b0eee3ba3377cd9edeeb43fceb9db6c9201b465fbb691bb3621e658eb41c92e215f2264063db78b5151a93e20da2d708855da122d70695fa0c115afabe4174de7befbd578a9fa4a92128cfb95074491c1658eb3f7fafb7c18cfacdbb0d77138889cd59bd916182b994164bb3098549e152186b3d73b09052f8a5632e95e2f0cf198585425dde72a270c04e0e0160acb5cbe50e83e9a62c3bcf4be1e0dd17602e86b349de842177b0533cd6d01bead4378de1290b938d89c6ccd2d9e0148ea7580337f2c0cd936300d80dc673dc328a8ce2b7037672086086f5cc35674db059fe02d4154bd38da6ac2471dd71adbaf5cfabf75da8caae97afa712274a95bd973bd8e5ba217738e7c639f39de3b7001eff9c652366ded7a53eeefbf0f669f7b35fa51301dfa6bd0eaa5f078fb41f8db2ccf00e93f057d35c739c92fabad42e0f9f1b441318bb9c7bf94bc6b3cfac400f1d04694cf00f014de8a0ca41ffc4edff7ddf0782e066b9f408c88021183a286e121ec4586b96cfbd9b84d71e3ab859a08cb7401907c71f32ae1a7fd038e8737deebfee6a86e6e5ca33332e97cbe5eabaae6bc249f8b0d5d2e3be810106175c5053f37ad1d0ccccb85c32323131ad168bb55aa95461a835087e9fe7e5dc75a914c7c9c88c1bff27715adb0e1a7fed9071d76bdcda7f3ee334a2943833e3d65e7b8c768d312e234a89d3ba3a2dff603ed7dddd1d6be92434bd8f65ad206f3e6ff3b6590aebbc4b39f6944f093d3935a913238e4ba5427043a5f2e9c93d19631f9f9edc938d388ee3388ee3b8ecf37af5658e3b219f805952bb4fcef952e09ca67eaf769d85f1d544e86139d41218735ce72e5f79a1831efae7e910dcf856286ed67fdfa71a23f0f3ad3183241a6b0ffd63798cb85561e72de741599df3a060e81f1886e32b023fcf723dee1e16f8f3aa1104379e35ee9ed70efa56752ddd396bfcd1796bfce139c80ac71ed6cf38cd0af46656ab9c73fe42d0bf0c865ae6fbbeeffb96d0aa71ffbcea591f7edfb7f2d05b2d96f3a82c517a286abe12770f4b058220087e5fddacd7afc50fbfe79bc562f5b0bc27acb79c6b8eebbaddc37190f2d49872ec3d9ff22b6e113e35eecde5c6e5eb1e85711c7463cfdf7b59d5062646f2c6b93c953d45e334331ef3ad1cec56a28c6b31a4ad0f405fad5c8b9bf5daf5d62cd7deb1c4d05b2c71b581ca47bf67531bbfa7529404f3d2a574499e979e23af5421c206338460b15224d6a3ee9020a40727a0886164c9173159021d78d001892c53bed021b6e5d302909c3c319501664b1131e953c89cfdd0829c407534975f5eb2f1d2794c262f5dcba2d96c5683f4f94475b81ccd663322d22510291bfefa85e1f8eb5b070fa5bff84a2a24649182bfd2493e49df92cb0f7f632e1091bf7b1a114ac1dff01229f9eb7b32b97726fd559a50febaeef5d97465c85fa7f9a4e5ef8d9a5dd1d2fa4d8558df14888891237f9d0e0df19722f97ba992bf4cfe36f9ebf48806d66f9af4f24d95a0502a4c59fe7ed7377da25afe5e1ad58511023e741f9e2c640113af52a954aa1bae5275a1e45559b586aac655aa50e552a552fd18e1841428b63881450b2954439638d1a0850cb31b546196708c100796df3a5830e5432e3e74cc85111f5611fad08a0a1f869e63e5c987dec393853c353535352e5960c5d738079eace686cbcdfa1b357fe3c60da62d7f638b2dfe0613117fc337f06437649e4c75815395bfc021e0c92ec031fff2f2afd05f2f95bf5e503820891266090a8a1f62af2e9cbea420e8cb124a003941f12f06fc66bd6a00bf6b5e1586d1ab54aa30bc7895d39157f9033c99cac3300c438f79b2f03dd90b8b169f3de7ec3638a84e8d67cf79aae1734c1661847c41ce39e797e78c41ce39e71b9eb3e79c73ce393f8df1eeee3c3c99dfb871e3c60d778027bb81c1d3108f8137c09361805fafd7ebe50cf0642fc7f90baefc05ae8127bb200cc3300cc3300c7d019e2cacc1d2c2d7f80e4f56836d940a8b0aaf72057832158f27cb577a50e3adbb8dbc32c45b9ac68b4075546e7d0479e5e86d68e40892b757b678bbb22efc9e4a54e7e5d627142a5fe5ad23e1c4cad1111acc58810b1343949042c60f61a40146ccbaac3b1a1229a898e18c32c84822d6458b234351337c81244b6ccba399dcd3e8ed9e456ffd75c5bf5eaf97ef78b257e8aed07178a5890f5dc793852a954aa552a9542a9527c093a96c7872aa962b473e3bce5772f8ec3a3c59468027b34dfe346290f1347e004f463333333333e33933336ee5678cc8f9ec06f064195b6b1d5b97622479eb399ecc7a0e4f36c3e39262f42e2f8027732d29f9effbfc731b1c9f9420fefbba7c2ec297bfeff32e465c0c8952860f667c454004e91044912c513e672d290560a98ccf8ee3c9b22b0cdd153a0e979c7ce8383c59e84f8038a40e8bad58e3adeb266fdde5b09ca8b75628f1d609e0c92c0f982bb64862f4850ca43354f6555c5e35448696219e142102838b98ca0a245ee503f0642abff1649fcc14242fe3304f268359ee62b1582ccf613966491b9e25258ccf07f8ec02f06439009ecc3a003c198b2726cac7b88d278bc1ac5f51347ee52bb759f9ca73e8ce0c9acd6630c4565ac04022c5880a5167d0105bb9ac3b0ce0614a900f4d28f94214934efcca2dabe68a1bbf71bc8eb71adcfc96495ec4f0d6ad63ed45106fdde5451b6fdd9ba2bc75d80f6f3de70b276f9d071834bcf50c3c9955b2f9ec72f37c96493cbf25d367158604d140859623a4901bf6d9733ebbcc2e973ee7333ebb87f98ce4850a9f1dd71d129c387a620b164f70e02196bbf0f2d9474f969b444fb66a61f1c5b7dc3d59eb8a2bbe5633bebadb54cfa9577cf9fa55a85a9d84b0562a5f7c5d55da82aaf25a5715098b295f957c2d632a41a97b52f9cae4140592114f841c694214832de921090e47a08e4843b6d1820e2c28bdc0a449ecc789335e68e2c469cb0e47310d908469218b1a48b43043ac03326c11028621d08882059867b965f90d4fc6c2366f5de6bcb5d6ddcb5b6b1d034f66a3fc8a0a0dbff20b3cd90a8b600b24b6f841081320e125b6f5ab545b5ee5b8eefcf0a28b228ab0020824c488a9a80c7995d7783215cd872ec1f8d05f9e2cb4e237eb73cde79ca79cf1f98aa3295a7c761a4f961d00478cace0128509b20011dbfabf4fcb7f8eebce0f112654f882c4ca134980119b62c57f3ee32e4f569b8e5e9701f4da653c99ced2e43d8fcb7bee30cfdb12c37b2b8f66f15c04cf47f0bc2d6abc177a940c0fc7bcf73c979e634fbfc78411199e5872a2e58729b12d8f784c9143152988a288a1e48d60082d47801822a20408cf65dd2180952e46618c38634a17b19cdfac2d26786bb744f1d6635c4e7eb3acfcaeb9c9b245cb119f25e9abb772ced9b3b33c59cee0bf2c39fce72b4ff6a93c99a7f4a0cb2a1ef4d09381788c2bdf7d37c6779dc370509dec9de7f0b8f09d065de723785de740c848c177449dcf20235dd739ae3b15681af2821a504c314512b1ee09308e601a6209223666886d7dd4754864d4f0dfe7da937d4a9ca8a82049156d2041436cebb73ab8ee2c00ea88054a583162458a989562e5ad839ecccebcb7e53dff3c9967061950a08a3461e20415dbfaeb572c5f9fc800c3c21a67b47002a258a5c2e5ab7b9eac7a1ee389ce93754405f894cb2edc95e7dc613838cfe19c470b329ed3e0b9223817a1eee018c77949aee8214c125fc66821c67539828d205e5072f4436cebe75c6e79f49cd46289e7b4407aeb96ea08e0ad739ecc62297eb3be7e75bde56b1652b85ec64928cd93dd3aab5f28ac7a8fbf77ece191b31a85455b1e5496879fbe55619eaaec904acb531597a7ae9a19a94af05b35d1f8ad52a9542ad556013d556dd5d053155197b04b7880df615418154685a1d3530fa1d01d4bc4120943a5704af91dee702ef91dee10e869b8c3a1a7e10e8d9e8658682cf4cd6fcd546fa6eba8d755f4d64f5a9eba864277aa5375d2da8c25bd750fbff5d65b033dd55b0f3dd55b1b3dd54e640e27619e823509d4f2492097a70e9ac104ce2f7e831b9cfd06959e3ab8045201ab804e4f5d6e10e829086ed0e829586df8aa0ddff8fbab425f15faaad0f77d1f14ba538b6ad1f799e145fefd4d30bf3f9ac3ef6f7ffb037afaed6fe8e9b73fa3a7df5155c1ab2a78fedbab42bc2ac4e3f2d43d33b27842b657e4799eb7bda1a71ed11699634b98a7f90b14bd99beb39687ca5c9e7a36a34bf73bcf27bf3365e177de39e7dcf4f4e9a9cb9d819ee69d879ee69d8d9ee62732c793304f3baad451a58e2a755dd741a13bb40aadd25179eaddacdbddeec2fcee7607f4b4dbddd0d36e77464fbb2323640e23c23c4d15a58a5245a9140ca9542a954aa5a80d1cb58193f9cd0971429c50d453e76694e3388ee33830320798304f319d613ac37486b1113cc1f88d37de1863bc31d053bcf1d053bcb1d1532cc626c6b6b7a7ed697bdab61db6bdedade88ffcdeb66ddb1bd0d36d6f434fb7bd193ddd8eaad06615dad696b4a7a71a754dc9d3ad699aa6695ad3d320648e194498bf4497e812dd7b6f9326f72e71857cf97d8bee254241e60013e6a9fd3267d65a5bc53a3d750b85ee4c23f466ba9d293dfd6d857cd06f2bb4ad96df765ba0a776dba1a7765ba3a7f6880332c796304f7dd72f402a14549410a1a2aaf4d46bad4dbff4558b4ce600239f8479fae569bd81a2559c9e3a8552850a4e181c9963fa9e609ee6cc27a81c2b4f7f53214f5d6e2a54f4546e0af4747e797a7f3a0a720157e6984ef7047a3a899eba9c7b1a353d8e51977b7af926f4f714f2d4e7ecca530f2277c8a7518feb0874c7c628453aa3cbd339c553199e7a0fea983ae5a1324858223f5da648b3969c7e7a65aa3b7648c70e31fdf49512bdedd04faf5f608802003f44f4c4df7bdd6d70e4dc0b850b4203f8ea164b2c88782bbb210ee10d7a57a19f9e3242abfcf4540d7a53a59f9e8a416f5af4d3392b7a53a19f9e51a037ada2f77cfae9190bbde7d24f0f85d0db4e0f93e83d897e7a4ed24602ae1f5c3f4c5aedd536cca5baec7da00e552b562b46c63543f3c2e086637041cd8b66c6e5624d0d1a6a72c9c4b4582b711caa412b55a8c1318320f0f332b009da8ea0b20d00001000ce35140001b8860400cbb96b25b17de725bdba7cc9f13501ebd72d40bd8e2854148c08a13a1d5f1205e932665dc6b0286b5783f6df2e9586c0786903082d82b4f8992509628c254c52f0c429d642112c4c4451a454c513398e50720414534e8085506c7a15a9687f26a5484c9fac15e81cb9837e90ba4294589014565ac4b4a7c0222266c4f420124b1326806229c82982b85822a647235e1dcc1fd8e7f8d2dc41f83a27e873fb38286e107ab208010f5ad0d337df32697c752e9fe795fac457e7f8bb31877a0f0facedf227fadccff3dec8dbc8436154c7ce8c6d391446d3c8e1a850c1185a7809e20c2c52670861830aba88410a2c29117362ee88e00db4600c309c7410a38b284cd842c41953c6a0b282239bf782de7269f7e4fc6bbaace3cb41a03a5b1e852082fd0f48e7a5b515797274b0919346912846441051020923240b5060258c2c2bb0a2822962b2056498c08526c04082880d3f424aa004172a5440256a8618754d745118f5cb3aa241a03b328d26113640756410aa2393908e3c15f234cba7214368d49c3db575f2d097bf8d8e2ac3effc44348db264041a4594c085295688512fa1eefc287132858921a63c61ba22461d04ea22d49fce41fd8963e8a39e0a734689ae0f825fc12dbe04edb474ce69355517da750d6397cba926e6f05058a54d246ca004aae354e2d4e924603c6e037ccd9138d5375002092a31f496549aaa8b505d47ea8be6d9e0575775a1bf70d9b3e9a9c8696ac152076977299ff0d244952b3720edd0c401f18211943596706143a787681780931c8690c2046c3001149b3ea3ea4e6512d5440bb03c3951464c8a31b028993df5108315b1f933a4c4192768a3852e4e6162d389cb42cfa7eb3eda75390383de3e17cb6dbb9a94a9ea3ef72b9658caea784ea7f94461d37dcb26292575b969f68abbcefece98a296a0949ee85153d3d19312d4d2964c33078205e56c9e973099c323ab36fadcaffe92863e7068daf2320928683651d8cc405afe0c75342a2f3ae905282f935e68f27b2a25bd40e4a7d2f4399d0b1307dfab9ef4964fb349e2e4179aa64f208cb52e61fa13964cae25adb44989e594538abe89b8473651100d6b5dbf4c6261889752c7c2aad0016a0952126c08a18035585881862448583065fe5c81961a982cb16105a02c341755ba3012840abafc3067a4d03f485421450a912884c8c1cb0938e420860e458a6062288b9f6ee9ce03a086e88d7c00944faf3b1c38628953123d54d1c2100fa2a44145d40d5d809c884dd7d35dd3652caf586fd7db273f4821a242840d439ec4ac4bdda4e56fad21496241035a26ad90f4b097492aa4e03bcf37fd2b9d7af9adfd55c1091955b24bbf7247e772d4be83fb3904f6b377a2106f02f8d93bca820adfd530df5930df5d36bed37297c71e079272da46f3ed1f24df79d20a6a7c4adcd2534ebf7f3e69052a5f9d73bca55fb79cd3effb41a56fce35df9b7bfef5e0c1b98cfbc01bb78c020905e0bdf77652dc9cffe0bc3ac779dd80ebc62d3de5d4eb077eb09472c4a317d85ead6adb768316608e4061610a131a2617199e7081881f8ea254224f299d19a23427a9d06426a9c0e4c83a4d1a6cc0b214a3a2018ac478e0a1489818156da2c4a6175b968089515136adc046095848418c8a758528f3046db4808d18152b111a0850324429d669b98a4174c620028a580a9e912d5c7049c45258ad21350e0133845114a3a2a402e7cd357b6772c78fecf4ab6bf132b4e79d27fa169dbd9bb4da4ec4dce7397e8f47cda2fceb44755ed8e9f30d923b587ffd0ac91df4afdf26b9a3fbebdc91dc21f3d7536a687f6d86b6477247ccdffc1d499ceb9a0cbd6fd04e05fdbd1e07dd0600c0e93ec02f298c73cd3bc0ae69196c82c2afae4b398e1d9da1d96ca6422c09d457771bbae3f4f96b52922a3ad521d0a5388ee39c03fa9cf434dfdc1e3df61bf4d893718e9fdbe836e5b66d567ffbc7fa2604370921c38bbb2725be3ad0dcba36da7c675d77f4db54faebda26ee9ed4ab83ebf5a630e92402e58d599a37a1012f25ce8cfdd014269db38ecaee1e5da39a3433b2d7af5208f40883708dea8d1565bda11bac3e70d0474e81fe504a71c041da50521067704d49314c31d2de0d5b5451d25060840479b9b284b959d39723be24316d3b4b731e3d41c24313979899bda850435dacb11d7961244b8a3544877e28c2a4739a7130183621506041166709a90d1ff0ec4b1246b65c3464085714866887d0d6a0195351d2cc0c252901ccec501228b8a3244f4ba89668120b2cfc86b1718612a6504584d4a62c524f82e0f2a40891254f3583828ba83aab62041e82028b2628aaccac704541160a0a214cb8a0276d183df9227405b4d4da3a1304a628d3232b7ec3a0c08001c668563ae7743d2907f6a99d03e0d2c9ba5369144e4614c582704980298aa5209160a04186205b17b1145845c4ea28690276fef9240a1f84625dcd5594590cb5308b753657514a59c11345b1eee62a4e23563cf114a3620e9358a769dbb77f9ee9882d40312ad6c0104b81ce39449db34a13e88d5d30964dd6359e236d30d599b5d64a61726c0d39d5a94ead95763ac5114397b9d051322838002b8b1b2af474090b8f8c322a702dc60d4aa9a5e3b67efd06a594e6305ae1105da622c56283524a73123d9d524aa997bb1491cee97dc4974ebea3486b28791fbac1f7d4a728c97839c677374f517ef19d56e994f6e9cb719b709bb414431eb4f4ee8ee2125afacfef0abc145f5fd1d2af28e5bb9401becbde07ea4dbbd6563aa77492a168e9aff9e27773ceeadccb97b556a97dfbe7b3103aa738bc69def7fdf674dc1efca764dbe8bcace1820e1c742acb0abaeb2284f6b2c66fd892199d8c13d75ee8e937668ac0f215428d519f2f041d83a4104b61d2092577dc97aee485c8539f93ceea4da73ee50e9b41123dbddbf2943bea4bbf1e05241a4b98b65c89018927c088499ca7dc413348a2a7dc313348a22b9678e20d636a357cbf6f0779ea4d74d55d57e8f912c98aa2971496d9d0d2bb36d400d3c6061526f4020dad851ada9bc28b960ebe448a72e237fe2e5abaf612294a092c5673081bbe448a4292452640ef58296fa8d08cc2e47736301cd57ad249079b93524a29054261734a4dd3348dee1ed64bbfb4b694e8aaf368dd6229474993a4862eeac89c538d270965cb2529a59455d64a11d064d3e0f2d3e5a44d48655c99dd1052972a26921a426638e7f44aa5e8c306263a75e649e91cd897936a4e3216dd49153091c14829082328c280c00c4a34218392a52432c4e6131a86ba0481d14318226c91c512332f5c9ea8129b2e45cb7da1a5735968177416d21907bd714cc751e44814494c4396b0428803ac28912183113438618acd2f4b80c05802060c1902d04489142858620a32b0c4a653d1030a93fef425521a427c6e41cb9c3ad38668ac84c492a1235b91e6665a0e7a33824da0c31d342b1a2be11d700b73880b1a1b5a1be12334562a5adad2d01b1316d2d98b293a95654b81a68219b4294068d512da093456da76d8a0f24ba4356e90e2286f5226b3b6ed25d21a3fbccd2ca35bda5a0fa159ae1a0d74f074d1fa7510a1b11226a2f112157a63da82d0f92592195eec604617cc447333cd0abd1989d2e10e9b1a1a2b6d3168ce8a664483aff5a0b577e180b958bac686470fedd25e5af01b96048f3b8f1f61178f52d3e3a6c73e891ebba01eff0caf668f6dd14f9f2dc805d4289963baac6bbcf0f287072a9820e2e58727a633622bb0803403115924d1014a6ccfa0973e872e60e5882e4ab024871b6070c1cb2c8d2d5830850b3fc44c40c442973276b0018ac4e2044d1491c40b617889edc91426a025a8644b188b8b9c558acd400200001316002028180c874462b1280ad23899fb148010799844624a9b09c44994c4308a41c8186308310400408c3180a486481d051c42f72e9b97777aa675d07bb058039a27f07c80e61f40663eb5c01bcfa62b4932ad27affd79ed1e93977248a50151abd099d09dd7e9337929474b9aa2c4a832a13a6c5ede2190462916763638c7838af196c9eafdfac78b831904320d64eeae36e84bc9a14d5c15cc3c94bd7adaa02fe9d1969c9ca454cb1c9412c4c9586cc9eddf779deb7c9dbdaed8eb842e2703e219e10456387f739e57cc330a2f591295a7e7a3755e77aa33a7b5c21a9425badb591f62b1a57daa14cf099b9053c4806c6ce070e970078be9f9fbb204750e35d31fc2624b72dcd736b59e1356825e0e781ef1ad8fb26e125165c242ce29684b58b0d852a959d1e8aa8f860286dbe771895969517bd979fca2656452206fb1a56ba1b247315aca01ec3c7e3130320541e743aaa24716679adaa52cb6d4fb2a26133ffda13f365b70b19cea56a14fc742d6adfb39cd0400a9584fd103451772140650d8229d2e8ce229a15ce17600749fd4a15538cae1d593c3461bebd5299aed74752e96829f7e7a586b85d2d39aaee8d6bd0bdeee722dd687a21d5804a434d8cf8ed65885101273522a8c3f242f819bc1414a82fcb121dfd7d5bb537340467fa152377fc7c384a7a818a51b44d9bb31a5dc8951abf2aacc5bb5a45ff87062f0446b45f00cea6e1041287bd151d384ecf957013c13b9cf7d78d126c2e91994286103f5f285269df2ba5ae7488822ca53025959a4da10f53162b8adb10998eb746264ab8892e01e3cf1849cefdccc701aa507b0dce03c77deb3b90e8e6fca71212569cb7810f9ef03ae0f15fd2cdeb9a0fa48d264ce39194c15f871e8998fda064f76351fce11d2df8b792089f829ff8a8b3f9306782e7d2ae13de6e5c199f4c5887e32228733c2240a8eaab3168d01058e339167c08e70e8a3c8e7c210762ba472a3772feb151d5f70700e431ab4c0f1c3e3eb99b61c3b980db67fe38073bd02e8d2f3985576f34fdd1bd0904470dd87a45d682f4266c082907c0714706be9a8ccb7f4b5c3465a0424be3b81a63c3bfbfc5464aa2ee4d451392c8c146a6349c7d6533e1d167dca461ac723d5a647dd8a9fffe7d207aeca964a40712b120d13d7293096a34fae9d663ce3e02e9c88b6776a89e93c8cbca79d882e7427d8b4f93c287c83240267bb59c6f75cc551a2d27df50d3a8e2cf44d9b2cfa6aaa4e5a674500a133d01a81e5d557d150724bb790b6a52d6191e172c14a7743b6afd009f90e3954029ad0d3e4a92737344048ee86a824df7cbdb7eb5a189eeaeec26f705ef2162444dc798375f12915794753949b07742f8b74edafa7468dd70ec067770488a245022d47f7b1b4133c1a86100456185d90d8c4dc00ffe0320edadff2f427145a97a308d993054248e4bc747e6bde8f0a7ef4302220bdac7fa4020a280b904ebcf39489020d1fd5072005a08cc7322c48211f36f83a77eec0122896b3a603b44b4e7c1deeda1bdbbe91d7070d57a7df28e9118ef5ca4d3d75ed01565626d42e21e1e975f6f5991fc271f83d5c80a37ba1f33dc5ce2cd08611892e7ea731d551500092972f5af989697ad5844d5d59daf8389d20bed31a71ea6990191fd18ab06eca8341e18a56e8459ec70b5779ce5ee31d8989c876628c4b361cea7bf6674610706723736b236c5ffedb0dba87029954b7041103e5c4c250921c2f69dfd238150f88f9e2c11223eb00d58ab4d24ae212b6d0709d9d1dfa23af46bce4e3232f6530d85d6905f1eca6b721435e327fc54bd97c679f24194440480095890037298d60dca8710db47526136347485cb5638a97a88a553d371e9572af521e23f0598135385a84fc776b04566323330df4026c38b0cc8528dbe854cc58aa127fdb91e9014ab30236471b56a90ef820fa1dd8fdae734f0f451db22c8e389a2b4563b94a81f25e1004616f4bbe963b0b34878176313c7f739c1598edeac516b279e34eff4c4178871eed82050d302ae91b3fa5f32633b676bd79ef0ff5e57e6c8333b736eaa889a665f626ac09ebf57ef68764e4fb559d5a99a8bf4ad0d137a9d9fe3fd97e1669efffb9961492cb9150cebf0a61f5551e26dae28d35a2fe117536b8d6de69be6a2fab33d0419f118f6049735c8525a323e5dacc97458248a220eff82b75e4aaa869cc9727f9d31f90b92b1a795f7be6722246313a3a7b00ed1f323099d800286b3ac2b4f7a9211e37270e28ef79128fc8f30e9afa44b8ab8504bde22d9bcf2e465b903e18f50da1ac540eca06dcdcdcf71485e2c1188c2bf538414be3869145344c6df565a07ebcaf26fe10609f5584812f7f85155892228bd0978d7f8fac1c1e1ecdb72bcad8d660ff8000a79049c3817f5170124b24cc62e8615e086e52176886191a98534ba8d2b8b4d9fde7af716461c91610bef069b538439dfa4f5fc2eb43dedcd4ff3f3c4b082dd08d460caa1c671bcaa1ae9814580e978e00af36555362fddf2443d488f30989d570bcbcf5d8809720700a30f9c6b3ad0fea718e39589a1020b4e598fb18e4343a5ae0f2c75c7f9e83f76869212d621104fd58148c9fa14ab52aab6718682ef04a88674422be0c493a1ac738cfe2f834f65f3f9866b7431f427087bd2fbc3ca3a0d7bc0805e2b624ada25416d5cfb342650697131e508d38255ad17b44f454527d74d3a11eab00aa601066ea04219378a0940b5b2c62bc0408ffcbd44e2cffd01a433695948ce4d89a406cbd22378d642f49d9974445d8c4a7e07c2737c73c01ecb3298a20e23a10558caa2a048a03614a4f990ccf8486eecaa35a232cdfdd6f7023139f7c9fa14a8b2b01092509a819557f6ae4b654b210161eb03891c2f20e12606c3bff6dba518836d37a8bf392a2fe222dbb48e6e84b6776daa06862186beb7b9381ae4f75351604f9be18b758fb33a76b65426a0438cffddbcce501a7e9763f0aa900cfe913d319210907e8dd555a5814fe75488fc470ed26d97e378c0dec66d8b13702f6d801894b83c8cd4a82e7b8d53a5dbd345111387d1d84f9a33dc4b3e60f338880e2da490718dfa1223cfd6264530266ddd4d67734c5bccedd33405b06617c5da9a348d25516688bc43bdc552e7398e43de7b880576d1eee3dae1fbb43f6f55948f1647b218831a248cc43285d2487c7907cca3803939ab68a3f0c3a24c0004c4e14bb1f126010c2fdfc1b5d0783e0a5c032bf8e1115a05e103b4adb7dae7027eddd56dc6b778e7fd680e80c41ffbd1849a418feb35dede74e2bbeb87a9ea300bef74cd2bae2842e168748bb3ab7eb833c88851f96493653b93f18f1643b0664aefa74da0d7b00b2e93a2c625e2b83517d1aa1878a7dc32bec06e630e371f145447829270d14643ba166440fc556464b82412a8dbc3e604357c65dd3791fc3d159a7bd422eb834ede92f006231336aa5b3e1af548f73c1dd87b05967033036d46474931e35e2c6d91e05e51af395b709cd7887ca01cadc45181ea485d02878ff414b0f2575f9a58a53ab7aa24beed1788ceee1c9f9eb8410c7b4078ece03aa25e09e854ac1fcfe0eb69be835a4d6c2b5faa58026c6fb5231b3e174ab28b18354bce2caff191a3c6f31961c65168db9bd707c2bd35a8bcacda2c56c0548d09b4b1926e9932ac871a2d1db0e51819896f110958898ba4f1d69fa2fb7b13c10019de14551fcae9f68edcfdbb5a0ed8373bdb862e5e22f5f63f2bac4a6ca86298ea028929d59c622cd6e92652e62e5062a81fc0a8b3486b561bb34e4efc307cf8b6d257726616a550435464e0c7930f8910ff073c66c7fa3548b81d10162cd03d4be8c288807b677a13ef973efe7fe04822475ee5ffe029c11cf1a120481b5464acd7a94d790d24fad82a405c5bea5be01ce76ca2223eb32bd649726540aacd9f268ec07b7e15e6da078871b5d447eabd1e9a731f77e983608f60b2d602729f20bd437ee1343337077db6c89c5120944402dc0579a866223f6c434e71f5323e11efb9bfdc53356a2947ed21dbf4eda3f7c9984583992b0d5409903e2f52ef821121ff47af76b60efbdb70d507c60e4ece1afd58964d472f67e3d74f6fd811df0484c9a670aecd83cd0f422988545e0b058d867c54e67c2bdb900560444b4c2a5bb36900dd01ad412b494ee04706467419a050aa09462fc21d344be0e7f252a03ec061cfb8fc021a805984b4110fdf594cde7b499a4004bc060b1cebfeb0fb5f7590190f69ea1cb74b07eeda6bdafa01e04474b22f1bec5eaf7aae9eb2386e81abb5ef4dbc5cccf2f11a3a1e668f1051b919aca21daf788c7d12fe61098af18739138c79ec7bc2aec59391d5b74aaeaeb958545a480fe270b6bff9a5cb9b65ff87885b60f52aeca3cf930a2d4d2566fc5e5c223438e057ac6b9b1280bb2b05e84af701545b88cfe31f86090319e03e204adfa93a122a64314a18fc3e16a07da163803bed49328607250d56d429b9eb74b701484a323fd8f96a4650142eab9e3c9e7f91bc465129ee059eee2ffbd16b58947578a7cbd0a18d90ea376631a0b821d91884724cf7914a270860cd146dc9b08b43845e694487cbcccda227aa8485d8175f3aecbecbb7973ef5bfa709655f0926ab519a763524bd73f8304e3c6c23837534d20e42d0ce178ba3d656fd401342c114733bdfec66460fd9fdd7f0fa54043c499eb21984a3ff348bebc61ee9d8155841d6334b012eb7a9f9c720b4cd29d9a58e8f1c94b8589ef245d52caa9127848a7cf63e47c465efa8ca4f58ce81f6684163d471c560b1f445a1434faa8308a04fe5112ab6d8a999b8d57bbc76e85af9875db6d6e10567cb1257fa6209e64311be1c329dc6dd947157d02d0ce666a6892564966bd4695ff245c71d41d99ad896c1534b2af8d1ec3662ffefed0bf58812d8f2d77f2d873788c4b1f2c6abc58ee7a2a63f91a65607e2dfe2db890283065284bbdda0d4a4fc7e83fda1249a663bc393d51c89ff96b77e64983bb05a2c90ac4492ced2ceb47c289132a7146754e3bf77a66cb157d5919be3f7d6cb8454499225cd0c40aaf1e86896a74837421ef76d57347a4348fdb0fcec3232886ca43165dfb3cc64abb77c2b7fc641256597f9a5eb10b1e9eb6645ad7fd0b2dda094b70123c01e3aa47b4d0764aae607551b32f82d631b68e08ad4ecaa7a8ca140883ae91864c4c66e98f37b1658d337e803b81e61513f8734d9b0fe4911e042437c4cbfc24b08798efc4af10c60c0d351c7dd342933253f4c57392477efff1feb6f3170aeff6ebc73942f7339115b9070bb9928a3c495a409f341b4c0edd5937012fabe63c7d7f59f6e6f4c32e23d135de6ee24498c804004eb8f4be17c41fbf531b758255ac0e3e901b70a3359b675a0a5b5c0d975e796d1037e39e647d5b9cbe4717b874fd455f9d00a71f31cd3c34acaf78b085ebed120643847dc5eaf16ef45df29198fddf88d3c6099dfade6604ac469105d5954a3e6d1f9c1b159eb16e6d9bb07047123116c538ae5354158d3fc370aa5a7b265b847030122fadde9b6708b8d8c971b9eae9119f5a3d708ff76938e990d3bcfba4016ed1d12b17db269ae17790974d30586015414127fd03b4ed13600863e7c6c60a3a27ad145787c0846e7b9b5cb7d7a187f3c6b3d64c6168c5e7a6269812908bfb20272ae45199eebe89230d629039ce472107b05952166af21a26c6fdf73b6e221c198d92adb97aba31332d382419ffee1441b2e860d50ed8e2bce552a08b0bb52c4716cd56f58ffd788fc916c3e43331917bfbe42e7043329fa7ffd6521a9ee3e6168ba73315b0eef8ff5ba6f616c89ee4fd57b3ef3586ec2ab25cb5849c4a01190e8488d7cbecb266f63265125d1c818922e845f506a1c5740ea37eca085dc2f189ac027a69c8c774490c8bc294cbd32425c5be44f58a669eedf1680a185c72d3825e2751400c54b357ee49dd5f748f157dded472f3706b9378703688787122897c7cfcffba3928576f9a50c55a96a030ca272137ce18f1ad0157777472e23c3d525309b747ea04b65940e08d8d2cb7acc34bf1a8a94fc7aca3676c299114ca1975f09a6849aedbb2434afdd3bcd5c3b880b27c0e9903bce431fd6b9da4355f97394bf662f6615c62e61ea6b5d97efaf16a9e815b6cb8aa94ccd4b8f9d4da6be2b810defad562a30042399fd0544ba0b353e27fce3d04cea4be286773d4dbad4811b86b89db52f95c8ac3a73367e065c37c9fc5c69b50dc0149c70746731f5e1e0e18a2a78c8e274a84f2308be168f94eb085125044d8f8fd8e0f1b4529fa482ecbdeb64921faca214478fe7b585bdadc57f2b2db9e5cbd7599034cec2f887fe27ac32f974392f8e90f603052cdca3723f2f46e2a18c785e469ffa82781516bbed57046a7b40ec006cd01c63e35b25c4eed3590f6507a7673d0a97038a8034904de1120d8b0cf184fa4964207d498ec5aa380a31a31c3abc8638246709b00d77b806dca64e5e7d79cab306b095bbd2e907957b19888d2db8325095f57ab6fc0083f1d5a5ce28fe8ac47924c9fd8a3d2f798850f61bf75b914772be34e939b498f133af7d656cd99671f430be670e6a03ebde389162dfae2d572d89717c1e6b0f4bf0ade3f0c9c87e9b260289960dce6ba35b9fd8eb32457d4dcc05e45db79685ea0803560796cfac1b2d732017a911d75f3ec1e0b030be3f46935248769e921644888307b7d9b08f9745280df931229994c8f31ff5c71112dfa920f736ff4072cac8a6ba771550358121eb34e4c3932dbfa16d51d0881c61a0a484c748d0cd0b01c9f0d4b8544eb176c741bab233a7216f504ae650fdc2907e7dcd4105491d3b3c17a1ad631d18ec2ec506d287436110d62c7b7b6b03c7aa8b731e6a8d3a96d565fd709d4c2684bd558c29f0bd28d0d016836b4b5a806b9eba7bf8c5b2a3a73ed23ae53e78f7b336f507b7a6e93a8e68305ecf7766749dbf974024c17a24d7cba7b554219dda0a2a694e0b6a9ff861c846183ea0c9b036ae24a50231ba644a35047d9f97133b29feef476131ec18dac3026f03ce8759aedb1a49b6c0c5c9ed98c0594d39cd1244b6d26d41dd19981b7d33e4126c97e26228c3a3ff7302cd53cd314c573b6a89cdc9487a7b10b618b82408b1fe778c27021e03cfdd5e4be4ba3a99ace7b37d06928e749b5c05a614173ee546c3342c37a9d1c04cb5fe887db0400be270952bd739d214bf6b317789dfbfc5154cafff61d2cb03c86fe302e8263a95da64d582fb17b8a1dcba7c6603f34025558e035b94acfd4cfd7377074f18c2da9786ff66965c11dee35e67b964971474e7776f9305fa47eb6850ff709b0854407a599ebf01d611b5834e31507eb2c8ba171fa82bbf9a8962ce5608a009f5825d2613cc6b4277fba88b83c01fbce483b92f722707790d85e50e81b647c96eb87e3f37f811938c86f6968436c2f064c41491120605641460e443382473e40d00ae41f4b381101e2c35b5e83bcb3fe0b533b2bbe331c78a94e038eb1996b8f16e79ae5ed3eee34fe0d70eb250c3e8f3784480821f37cdbd8fba71d34d0381d191105ccaf5b949c38ad588324ea3c7887d588504c7e3de39e472c445d90301c65a600cda254ca0d7d81192bee43e3ae5c4cc765e9476cec01ab81a0639d7b0d60cb0e321b0f332e6c4300d92022ee43744e4e47febda274959a31c63413afc28e3b124e21b43c2d945ff941307c239bfd5f142b05f3f1be5633257636ad5d7803aacd0c8518e6218086dbe35e364a12f3340f36700bedb90ecaabc6b6652fa959e389d6ed41079e63c9d59c3a0e8ae7cf5df0443f8f6f6d679108392042d9437782df70342af49b7f5e3bdb1b4b7c57a18c4ba7a805f0238d3019191ccced35d20e6303ebf86b7814d4f7da306d4cac6ccd286995184c0eb9f8228ac195c42c006704d7407ccd9bf33eff9dad7bbbb5a3d413d6a7b08f28a1a353820ce5ae01dbfc913212db2c79542cc89e2a974f880c2b4108822daf9c70451653700da210fc64842b3ba522199509d2d16e0cb3ec0a51340c5a2c8f6fa840614ff4e6b0eec71c237cfa44465d60750df6edb99f65df7f1327b69065caf3de2f15ff3ca5bbc388b868bd083c9dc03d4125b67d7cfe21f0cdbc0539d1d83e6436562fcbd413b8af409bed9f95199538eb830ec7d53e9c7fd25756fc80499310a012fb252322ccd0f576946b41790539be7c94ef83dd646d6b7020e36ddcad8909e6e37e97c7324cc70455e27d35173f85044f3d96d75551922c8db962e8375423103dcb204689920b956a08f2d01e2ae9584a3513d2c032fca27090a90e8da6e982426c0199a613f735f51d08c5ed3fffd89d6bf40a35501b75b443e9bfc2cbbef22a9f05c7a61fde37b18bb852ee0292739ab619340caac0b496eac3c1b277e63089fd012a9702e6352223df4d3721185b7e84eeb048d6e35cc0279f59a252ac57cf4bbb1cc5f31a7c19fb8facd8fa86126b68fea587b7a10f27234c9fb036e61786e8e15fa6a791a64d90c79e10f541ed7310652b6b75bdb8b196a26a0889074e9b7165323590ba95b5e6fbd9b784eaa97ad085453b88cb0f0f19ced07811cdce9c5fe79446b4803b0a181e08db53d3eba798872de7dec5da5bba4d09a7f5945931e2d96f26cd8c2178beb0dffa5f51a4a2f8e254218dd0486c0f49fb9344b962037386e7413a7dcd520c230ecd4e3e96406cfb9a20476fd184bdf8aafa79d7ba1875c6c4ebcb33640d364033b6b2f12ab6afe5913513fa64e15ea554b0538b657cf64476ff27387d1db30c5fda18fdcd2bb594da4e79861cb1df8979fd016ed3240ed00b72a1a682c38330d22aa1bbcd76bc92aa281bfcbb76961293ee668bf882540a10711db23358810995b962882166aa1c79cece2a0a42ea50715fe1c33b484640af20e5d9202774257147a3c5facf98daf31363860598f4fe56d59d2559658eccf8ccdcff0e66678faa9236dd6534b5b642d9ac1f64fc8f732e0999803a457a04b823d9591c632ddf15872714224c5ebe57f7215c0b62d9a96297d17a19cbce31b83b834c6115bc476169b5f5be4683fd7952f80d3bd28e6c43826f0f55fa4ceb2138ef0cfb95424f34126d089294c646f49b5164a5893f07afbd545e10a8497fd3035f0003b269ae2cc8a490aae3d570d2fb0e7bfe2e64ac9490b9158b06d7352be8d8c06c095c5e835c4b9214244691e8a9a7d24dd882042119616c33a996e47c07ad16d9046fa9f772f17717299dc9eb019e42c719351330ede2675fc7cafb44921748c80d51c47b3a7cb37dfbd9a8d33d205c169b482f371be51bf3c54a66364e210ac727f7ecc8c31970b1184a2903fb85906efdea56167a24d7d2e57e6b4571ec6441a09df633957146bd90460e8df7b7cf2b46ab5b32aeec19174d5f33f1af9f86f76b064d12d45765829453d993539136a972fe21a99c4c309d83ec9f79228382a6d5b37cd849e3c3ae9f54a479f98375b27b34a6536662b057a3cb3bea0f04993a3e00b25774878d135d1b18a92c4464324a87ecc14be1aaf73146ba7c4c4a5a00bcd552b0b204229efaeb835596c94b9992367ae1285c89daa74ea513a4db70d9740923211a7bf8016605e9e7718db13cdef5144609fb827715abb0d787ffe4e0079bb3ce79e61cb07f1ae973b03a4c9093513e4fe4804c83437109cbae66618eed9dac3a093d453345a5e8f9051d0328dd18aea29bf405a9a0fcdad863f3da4323a4652fc9311fcff7c2e04af7fafdca36cbdd0338fadc459e3ec0bd872d45cd1bbdddba1b5b161868db61d08dc44861b36286f566543e702f29192bedf93ac4b895ac96394ab1fbe3190a3125f7316991bb414a8477d8a590c2ba7c9a2ae7b30d376b6be196ec2933ae4e158eace2766956b18ee00f1bd892f321f33f182178c2925a1c6998a3bf14e68a4313b7bf9718916195d261d8562e555e0fcc69b280f9ec566f0fc89c3c575f4e5925bc430221b79655444d93df68f2e36c99343f109a34fedc297fa687f34dfcab1c4c1b09419a7d3163f18779e30c603578f44a9ca91273a938e5304d6e39219c93562cfbd50a3ae884c26d93e2bf718080fbb2a2ce4d057359d1414f0325c6dd2d89ebeb6c46fa7f136ff5df6a3c9f8b83b9df02b8dc580b9e2ebb4519467427f7137fc4f11d224bd22a24bfeb317939e22886a5aaa0ed1ec5c6d9183d4e5f53eb8550cb7a750f193eca0bd375746145e8ef778d698fec8369cc13162d75cdf7ab0ad34de00860a8b35e7cc7f55c3cd686114ae80b9387e9d37a960ad2ae122eddbb2a3c18e90e47c0227203ea26082da525d13928fceece8d52ce80edc806846935db041045cd1fa013e0a09d184b292c5983aa185437fea068b6acc1d94ff16515927629d2b6ab620a3bdb8400c2bbe8073558a469dd1b8b62b9acfd87418d23be7076f446e056b3cc3c332e3d712ca5f7b88c1120e068266f1b2d5d549ef0075fac3203df0a251a4b02d17ac6afce13301fa816f5dea58346471f9118a640ccceb98382590306718d15b9110c408001349981b77622a08ae40832eb6bd3116b8c6f120d75ad1f25f3e499cc090e27605800c922ddc84216ba7a5b078a33b95c20143dcd1ca55ecb90a1105ccc7cd0a6863a1223c9a55591cea5bec4d76d909d64cfdb80798ade1db399bd5413c11a8609efbdcab335615295ab42aa0f3a1c4ff4b8688fad040aad35347ada09ba310d8702624a826e76dca6a302d374f4d32437c6a94d5389c39bb3a74ddbdaae6d09d64830b2373a1696abb4f4b83e36adef818256f50a5e4586a4dc737ed661358e0f3992aca7281e3b677544b1a929abe8b594d74b5f8929da76a94d9692456d4face45c8209da8bedf474bb0a5a38c5b2512347c098eb6ac3d79267f7fa5d6428d0299f87dd93af2dcf6fac7d390ca2bc5c95331f11361b6a1a2ab057db5e4c2229b1abd41d7cdb524c8431253918324aadc8401b8cdf034e05d823580e9a859b370f71b04705cb1a6e22fc8fcef89c8299f16cf1a482357e165f300ed8b73418ac7cf5746fecfe2e270d666661b47018dd204e845e998afb6057c5fafdebd4bd4b2e6c7081c74be3eb228c0c3237c4e8e410a8d0c39b072266533fb187755fc437077ae3329b8149b9f8571472105fb93902b8962a85eb3beb3071bfc889c158e9b177d90083386ce1370d97b19b71a5cceb299cf4e4821dfb7d5d8a56de2e3ef534f3bf4ab5d2e8c75f9d770101ba1fb05e0cb35339db0d3898e3214f1fb568f5980ecd20d9c6a3e4bcce132b678de1282411d5a15fee61c96e548c43dd4d214e8126f13c6af70a3c1512ef009878cbbc87da9de99cfb8be8ec542817edc4b123ed4449c13a5970645deba8c3da73abb4d31e460a7b779157abd4fd452eaab07337f8bba3758c8d06de85b028fda338b6878ce28cef7788bf5071cc73038a79854d41745aeeb92199045feb9b55907bb35e0278d2e114afd7d80e09037739e31b267b4f60fb5fb642c8b7aa3e6519e11350b7f88d6fe24228155a663583f1dbf003ac7d5ed49eadef4d1ea5e7e3e650a7c98002f72716e0e9250ab0daa95e897f5d68644203971080a52b5569951d9c65831005972435b024a92aea509bd04d7628df0e1a112d202b507119c574142d1f0b3d0ab8f133efa062052bc884f3cbebea5ba6be371aed8071b45469cf7d16eb47cf658baccdbb8d0b98cd3bd363adcff423cf658b2718e7b1a481d20cbad1219342de06c034e84d979900951217a12c74f3127950d1191bc691e203bd3671caf2a545c3f83b36c877e1225b78b0b9af9ca7db2bd7745db70222c3cc23e7841ce76e2e670895aeac444890eb664e748323439bfb394b19ff2f5474a84d18abe39d76bb0bfff02e50fd1de0ec8ed319b6fc220dca1d0afa476dd352e8a89bd91a28d00903482ffff956f9a0a9234ba2e94112964270bc19d65eac27aa1b878985a29dd3e188d6fa319faafe50d094abde4868c74d12a04e3e31a8ed28426c6164b8bbe23f8d6725addcb4537f4f8d2db8a782f48eb7d76d7cb10ee1112f39f748ab23e7ce842b1b4f3ebc8649dd200f0e25d9edff9c18f81adf540c0047ab4640f91230505dbb0c0f8c12106b94a42e746f4afe06f9dceb85d9ccd6713b42185179e1e5807e9535ae37ec5d778c0f194c56f7b512a5006e62c64a21cfbb6e02647033d3f20fc8ab085dc27b5278b282f55aba691ac17f150c7f4b7fac5c15930ead19e732982916af9b8fa6fb0cd8a425af455929ab55bf52e9dd8f4b92add4ade0110f14ad64cf96a5fc85eabae43cb847921a7cd22801832d07f6c79b7b2339942fab66c626e1a6acbcb7083c86f20a4b19062f5e938e94ffd57d21955cb996a26ee0e00881b3bd59b6d01ee509350b4d0b9cd6a99660e8362d8fe6522bf872e5cccad89d4818181178cd296232c0a4d3474daa4422cd0a450a4f0c00f659388ae906e2040d9082201a922ee3512ad175a10cfba9fb9e6f62dc3c9881bb782d835ea66033a5c425aa60c389e489a24b6b48fab3c56e7042fe3f1a216b9860fc85f16ea136cba4a2d1482afbfe4fde90e20c2f583ac52105f5a39d89058feb96d4c960bcbad657118deba6985c5488e0125daf07f65d40279fdd1b547ee42ec13440c6894dabb0849516ac745b6f26ea0684a768a3a36aea299cd7d4c731ca57c4fccb874813912fa72be50b529a316fd6ea5e3c4544c702a533ac89d0ae20120433595fa81e912f83aac3b2f9911f7f00d3908d20f7832f8395355ec6a1ea09ea0192e183188d6adc80b4f122250690e3aca2547b0c4b3abf22b0408569ec74adc6dafad177a72fcd743146e8d39c4b5a17dec15c2bbd8e8394f8baa3007431038ec7cdeea981ca9b8f52e21ea288d227ff322b2965a1dd913ae74f1e8d54c90f7dbadb93a94c6d66637b31b94403c84e881d7ac421b854f027850396a13df8d00f58271452aec362534b4e0803a14313376e568731113b0a4cb1ac385aa8e4a0477b34f76152c908f4bffd71682b57d34877d2acb3183850d1ceb626e4cfea91909785341720df031c32c9e9b2c98d65250a4338c9b2d41c8f0a9c15bb1350e2ef876a1bc5bc98203ca7885d7beb0e2f3bd16b246326c948063f572bd8d71995a1ecf568dcf59cee6aa1ecb23a24282c2cc3f4945940127554074f084523021e04cf52c5e4c356678a7adc8819ecaf1bf98870470dbd74dfc22e289fb878c73c16a6af0b7405a10d72175444aba64b6d0e30b77206a21b2eed94600b4ad819c1c5a56ca6150232aff0d154e809d8999ac2a50ff475144385d0b57d7437f006a8af30bb3593a3e3709eb941bc5fcbb58004c90e0ae99dfc0071c3a115e6fcecedd1314cc1068ae1d4d757fb7fc008512238974a1a751f982dc877ff790d03c2a8ac7effaa50f0546e81733d2b8c316c42d051c11940e12b6fe4b4b2a4cba594e4e89ec10971105b00d019fe2583e09164fb451ce895c7fe1f1c29592f9389dc180105763c43e7a0bf10bc927266dfad1631dfddac4616123d2b4bbb82cd2b5035e637882e4aaa2e24911d98caf078e1cdef6622f778b9c464d6e13e17f1391e6ea0f27299b029e99d275e4c8e1a818df0435eed999a6b663a8150f829f20dbd6012b5ac246b44db30c039da46d629a45934efc4c30ab950a1b76c4cfa3562303845950766d4e9e000d44811c1e3457bc489c9802f3e6230d519c5f9b19a8fca97a44a01c487db3a62681938c2c993e4fbb0a62ad6debbbe3b77b4725a3ff74bd874f90719a9cb89537176722d5be2c6b8115cc75c5c8319622a4316f2afb55f652a12e3c82a716406b43d5427bf15e20b7d0bff022e46804e695d2340168949ad54ed5f888591900eb1b308b1ec6ac86531a2b6f258d319113cb6bf3f2c4c22f9798b050cecbfd238a13b7bca0b5ea83a2c9701f6c9a7fff8b6d175cc86aac3f4b9585c4ebc0e8075253462ac0c3ce7ab07cf786186ed336246c6f5829ad69f8bb5a945bc01b90e41bb3cf874813f3ab16a4264ae655f239faa8ba7fa99406b9e41abe318f4cd4ad64e1351f6fbe43c51297be3b4ad6e6b1fbcb9ee601d497523bba5654d06f8649cafb84a08ae486d8459b22664efab42ff221aec2ab76bd5d17ccf7b5618e4dbe539e1ac7ac3810dfcace4ebf07d10439a71ed9d1cf99ab407260d53a10e0df4dc5fb2877ec18a1d73ea1ac6b4965465f63aa7c087133ee9eb04e74d0a0427eaa5610994f1b463a17015a4b6fe5aa88487a28f55d739c3b3a0ae4dc7ac05ba0e4f4aa0cc29747d94ae2968edd7c7bc676e7f937ad523ecce5167a19c42a536d13647a911af548d77a02624e7d898045db0a5a2e27100c427f460f11ddbf1c82732e7e0d1af26141c6563e7c575a3150293cbfc9886aa9920fa25083fe42ae92b173f2a5f5d4ff8ffb94e3485f6ea042f4294ca0b9e981c5e39a1734118a9bdf55fb7558e03836daecb762c52c88f3707cc35e1384e2c144fcef8602858950a17a537a81e0bd515b37bb87e411d8f6299fc3edf8f4366c67d17e07b3d823c6d0c7aefbe26ad4ac410b090473536ce75737529054fa2d537c6b06203e5e33a971811f4fe5ca49a902e69fe582ff6c96dc822302a823b9670eaf54595eb8deaef9866542446e818d06c6bf8ce5493516a02d72b6cf98e0193cfd1ddd7308f0fce41d2f33fd8b6d8ecbd9ca169d054c4b6fde71c8b7bcaacdb4a683b8c849fdd653851901d729fde10e11c28f50049c65d28a855f472056ec302b1de0f38cb16484b22abfb0a2a1990916520ab13a9c33a7f353e508f505f3ff93ef96ebad8514efb1caa349096f69634c5b2effd8c03fb0727bf568f83b7152836c0d95f2c8e885c5b8d37e17a12fc328f31c0532d75e94d9ba708412a4942686365bf5bd4e901a53006b0da8784c54038ebdeea433446c3b76c8d2b0900f6239f53d7f82395682550e3e23aa12d2e21723bce84496c17078a557cd8dc6950a8fc50fb04f91efe2eff7c821f0b3d18484b6e8c4e05e7ac420e88d812c4f2e534240248862436f26bf8c3da48e4a61c433ec8954f96799520f1130e5f622371626af72bf4a7c748e7e3f5e962f5a148293a0b36a8a260d0f7e366afe057dfa98f6ebaf41758122a9ef60643f491546935f0995df01664c07d03450751c66770c8918c0515ec4ceb60e918e07a500ddd42182da4d7a09c3667846dc9d1c9e979cd2ed77f6d1b87e37d512536a292a3eef76a43fdb1caca47460c04e129d8b7a6b2f3a189c23f15f0777685e5fd9a48549d8581b634c861d0458b3ca3159e2e6126d835a15f92db8db7d73a53885412a0f23be695ac6124b47e9f49eea942120137c2720c30e02bfa63c57d49d3f6fab1b0b05e6abeea8b9cb7f1a6d7119a88d45a8d84693dd4bd71d86bbbc63e16c8d22b2f7ab55de032c454fee2503549011da30ac194e9b47944a228e66ad735ea482ea54a4534d1a45003ad30a39bb2d4257d6a293b6d41cc594fcad31763feb56185be30f640499011f022cd8def44d48a4f567a8e88c6209a4f0f9d9cbcd355d45c74bd82e4f75e67ab68f51921bebc9c0eb207e8b1042285444c4ba73676b9bfd0dd0cc55efe5afc614f9de8d780173ce562c27f790bb41ca435f4505d64db25b77bbbf0b0272c5f005a87cf9743dde2b225bc3218952f2997a2684fdf01a8ffcb8edcd49e8960561607a9f3a7678dbb20880e3276b5983081ee3e0376daaf3008e04b69b0765dfd63f09bb8b94070e0ea1a06b30e6a76f8002cf322ed6fae13a747008a3a4be36288d41d4f5c42f09f84a2bde83418407af5196d0c7aab373f38033a6df588bb252875192b838799b65d9a45b364e2de70d9c9d7e6a9bbae4424515b4f439fd580871285a4e8577198fc388e6ee21492bc342597e10ac365741b17238a3b41bdd603034c38c93dc146a0b07d3609b041cc337064f2459361c60a878e4267091a6038fb8f888dfb9056c32b653d82d942cdb25c5f7a5c3ab3ca2b757393f329919ec952b754a2cf580bf59712e468a905f1c8c7d0563acb382390c367c47625bddcec6f168b21dda368cd4d20186585c4aebbe515d879903bb2d44787b7d47d2f5506b19dabd0a6453bbf3668c9d348b4460c0b01e48313babc46a34953864ad6b0e7bd219e50b76dcd4473116bbc7d2a0485421fc267a617b1818c6c9697422388c21b01dd29da626d10d8fdbb2dc3cd9c7664ded8ac223156ec514065bab9416c0f3645d236b58dda819f20870c501067fac8fdc719dafa8e4bb3f23f8e01389e0fe771cf8d2ada622368eeecce773a493f9857bd97cc81d7544c455a4fb2b93a8425d323150178bf6675224ccc694ee4bc73de85282bcc3a84911dd41912d7a4a8c6073afcd0bc124b4d143c808bc34a090a848b2fa03bc41273cd2237c8af8c5b84eb1c0e3dde24ef54d8aae40b2b7c2ad2a73cabed96c325dc8a59facd4d20ce65dd9dae51408e70a26c1f13bca2c74646033efb28376fa9a0e861ca1e0c25de90d75cd3a63803be27c8e61624efa37649fcf592c8e99adbfb071beddbbc89b7d17fc1fe17817eb5bb7bfbed51362e62e6770b0702bf5e9fe142926b6d7f46a4189943460a83731f7e3e9fbd2946a23118a74a11508ad6331f5a558c66ddcb89e80c6ab4dbf0701e2f991de814737c2ee4e9e70e1f9a3087273a68503d64fcd900f9b9fd44bd19bdc34e664f3b0d7983ca3db701602c1dc2eab06b7bf24943a0c48e96b13a898a77540832ab875649455c65ea9185ebccaacc45699eac5828e03963a6d3109bcfad5f1e504e10b2495f5778b82d8538ac671d75a56818adf76cb1e394088a0d72e00bcd1a10bb1ec7e73d983e282b8bc0b0e65ff63dae8f1f92e9b2b142aa12d5b0872a2c84051f50b516361e55607ffbe69a539e63bd6af84d604c0aa4e9082111de72af4c461e09c737944e1aa32431fe83432fcff74fd785cc19c6cf70ffedb4f3bedcdc4a75e2d85e851619ef09e3f53f99d986512f75aefb593b1dc92789311742204c264e6002b49a61ba874de0c30185f5b88f8b79981b3cc33e8e93f535127131bc8bb78979ea6dcce390a8b6f8abf997e7a98d19a0dd1d6752a870b66a9baa55145fcb90cb7d4730891f80d6d7b73bf1aaeb8f7438feea4bf2bdc362abca7b59c0ff420d2e7ca613b1619ff63ad5858cbab84b3f45f0a31f0e2d43625982b3640021e4495310ea891b8b20d2ccade27a1269685441e51f4ed7132062ea4033e21b69169730086eae387def170531a2cfdd5c8f331a61565e6ecc31ec86e929f639c948950d4152138772fdd9225bc86c98efc327e1ed7d9aab290667179a1ed343185da9fa45885c428f79a631069d8f8c9e44da59649e702abc17d7958da2f33cff93b584a782e7928878616fd575968766c5a1542a083134cb909f23baea326a7a1e15713ea7733beb3968958969b2752217e5eb3760a9152b2c2f37784444a5095996947e095886d9794b64fce15c4bb03311f10a8f142fba566ee6402a7984d770f57c16ba5a2e4acade711469735543fff5984c90708108f46e1069587ce5f5a4fc409804a9be140ab566343f44f7e2e20db9427df742f6cc09fd4a5fa66148e50fb1a34ad399973e06e9cd8c6a02007289cbacbb076583f40938df10ff707194b4f7f1ef00bc4337a4e1a19bfdc61936b28b941d99298b99bca120732249fcb5852328b711394b3300b21ea05910dde18f53853e105a127f79da2ba32e2f4c909bbe959f3d6e73c3a311889cc1771fdee0284bf2bbb3fd9155490582abdb7f9032a14c66ffa99952c91996be92931ca8dd8cdc40cfaa3064aa52e8a896c1dcddce07d354640c74a829335d53377ff0b33cbb8509c1a8687cff9668d384ef64841b60a00cd1254b2ce6d0bacd1f4be18fabc3b0e470ab3c1f27a0e7c36d7a3e0aabe72392bc9704406eaa359b1df9ec5d24bcf71f421f82605cbac1e39dd170a4e06c8c992b422d600f5350c73e0299047cb6830f943ce9263250841db1371d257dd03690c148c8d81ccfc5ff7067e2545888bc397e3dd154269989911645bc78592014f1281ff04fd6721d33161bc7e66f0c19e79673b20e92e7ee3b8478e041230b5bd4e95909962c7d722db19facc0ca02d4aecf6443789848526139d92bb5aa290c969eac9f010a32b0a4a9a6edb230b186910bf91cf2a9e7c125fb8b6d855afa314453f6680cc556578f765739ea87f7baf82ba6c2cc93613392cf105803332c4894c06fc604e9ab6a111035fc63b34d394e0d8c016ee83d9e7eaf118c10fd2e89ad7a028cde0aad4302308a9319087639353f169a87aaf23ad2a89da2815bbc14bd27631de0525350996ec188c8032084e9588fe7d5de97bf30b1d0c337f8d0831dcec0028575c78154b4c45965d13e6165fe71baba2822655fa83b075ee76264dd86b2633764655c848cc030ff3faa251c1a25080c8a43d629ee32cc9e8347038536e6c7c1d4cc18c7dd4eb1c0b241aad6b5da0e150a8d59a200f133fa812ad6456c35745961ad6962906b777d91a65bd6deea73c564d88a806560328055cb0cc736450be61431486b3c5d39f544e8b902f02acdc22912091b8c7731c455a68a309d28adfc60d060e56a9fbfdd3d22a4d6bf6b911283bf7d29907d4fda13c0dabf289c8d27ed89c0df5c14ce96772d44e06f5d1e60cbbb56e460b62e0f62df432b72b0f6f9946d104a51a29ec69f7c827bfe44f394647b3ec123cf6ca1bfa1872da39be2a534c92eda2f8911ce4950061be58d2043e4601718787a7748d515a1e224cde0b2a7c04036a749d99a6cbe382371ac13ac0bb244d56f1a0627770f2fd844115976a92d61e35d286b2c559bf1650e6117a48c5195d4f4f4e64b2ce11a6da68e23e6aa2ae34c1330561541398b0901d98615d12f4efd503cc54ff57c289c54254bd3d8ebacc9e56a3be1726e98aa2d5411f040bbff7dd2604dd565fc3ab968418a1f00bce1ee45710e0f35ce3f6a5779e10aea3cd1ac601bc60f9e55e73fae9d1d33edcdb01c57a2e6e817de729e149a3f2e37a046b5a22bafa00af32a03a8bf4195d69104aafc5f784ba29a59e44d27739510fe90c551043047f5c7df28b408e727db699bbda1ddda921b342a07b2cca676360f0a01f4e3c1700b6e5f1b0a00de55ef7a6afa0359063880b5e881f820cfcfbb9cc0661c76d173e3ab0b4e44324c1a33b08e7fcb75e41e0051399cac6f96fa8d9122322385f34401379f5c9ad83b3cff1af0e475c0a9a7800a79695021e96fdb58b387754075583d32babc0fd6e72723f7d3fe9bfa8f8fcb3f49a1a08debf293ffaee4e90487d87652cd129c52aec8b42908e7488d6fdb4b46adc14738e6b78b87a7d7f30000e9c51df08150121846838dc36c6188f5c3471aab7fe9547d5156f0e4abd78d1dde215c02016d0b019e3d3c735184cfbe45256ead43aba379658cc1f5cc468f4ef38ac47654fd62931225165c33a934688d7695546da715ade6b0b957f45c46c428bdd7bce879c53aea11489b55e8a762a12404d2e366de6806dcd68c0e0f99dddcc72020abfeb4907e7f2090bcf25a7d52a6912d185d2fd54fe6a7f20f869a057a49afc607aa0cb9cd05eb8b1a76d77f37049991d74623d915f555dacba5f5497058b6a36720be3ce049da86c63a800a8662a76a11d030f66c4c3a2a5658a32a7017743997a0f15bd9056e9f11a13bff32492093bd7b05624ec2f56aeae4b304c4e7b7a1ce39bd2cde9cf9856ff165b496b844c97e77ec178db5eeb08205972569a6cb09a39c9bcd88d084adfeaa50a7c8f39533d50b688cc566482a0a8813c9b2ede3c0d7d6a3e164a478541c782f33a2f8283321fcb8000b397664ec3b881f4b06f54f01d39f21d71fba416c882571f1a64fe5d9776a4e976a5f2667c7bac24239875352c8766658e6289214b0d9508e11d549fcd6fcfad569da672ef7e20e7b3e6a505dbfa4d1806c105f524a7e0db5eb61927b9d4fa37d15138c6486198fbc7b0c7d4180dfcfb284041c46465fcc8702060fe3c863643b1710ece773cc6b41f0e65de56f7a9eec5ae522a9f1ffe0167916703239033f3ba83b9260e75669d7d6e4415b3bb1de22ff2e37bd88ee43c4238a6b9f2336093682528390b4c37f3f41ef4952d8ddcf6b88129fe15e66cd67124f7a77fa3b023fcbac08e1607d2a06f7bb630b961b4de4286b6da44090294720a402f86f093bfa79967785c9cb1338d745fc33cb1a747dcd3f6e884f52191df8daf28a6e5766cd7dab025d9f222bf4dd1b893596a60aaf4f4605825dca03fd70ea06dc33407a9830643e74a22155d70f977f1cce890c0cf1837b46134330565f423869717c1ff5a6e0ed56af7340acf96b69833f606d94f95802ef9f2321d1ddcb6080f2352227cdeff7dcdbf67eca40438b1139e5c23f5970f43dbac32781eaf6272f87d21c91420ca5d61a1dec183c015f2e84ea8b0b5805ad11471030c75113195bf9274496a377e587fb21e7539340c7d78f43c598f07928f2aec25053abb91f89ab3a105c361c714423a8dd835afb9887ca88df0e8e8dc5fea47b057dc86c6298db417027c3ac5fbf0e749846a20e5dd921e96705d43eeccb5f9377e9192b8b30e7f5798b312ef984028e6c80e4fbf870b7bf7e3dac782aa3e00de7ce3abc37c659e2bec52e71ca7dc632b1aff37e21368595682fce9347b4d7735aa047b447b3d3b03586b6ebfb04690e1188c7900e7909cdae35096b19ed257d2221110080d1f31d66621165a5493ddc547075a19a764bc3125c5ce7cc56311007172121456e5f1a23022e4267b0a7a70165a70ffe22473dad9799785a761d8cd56e6dd1ba5a96a5a9e20253941cc4ee9d2be9ec70615f2bea179677d36e2b3257adf1f7295269c88d6cba4ee12adbef7c4d0d1a724340e581cd44dba771fdee84e1d6e1c45401fdff70423bb7cf247c81d2ad56c1a2ead0e817387bf6cbd220dd53c520563697eed725dc553d561e88fc9e41be3ec9015501ba0b556f19cd8c66bb5742d06a816b8d9bff8b3137a9842abfc44a308cdb8845ed17299c4ef8a7b3246787359932fb5e702315883de4e32ba8cae4ad0da16f1e7b80c3d14744f49eef4003278685e2b4298c0f51b5a927d2bd0bf8357dc89340a4ef6e7288f79dba47da887c0838967cdd6e8ddbbdc4ef49e45b2a08f06fcc3b897c686d9bb5ee09fc7e1fab50a3c81727c6c9ab85abb4bafb7aaf00a5688d710aa10649ef8becb1ba245245a4351691ff9b44e67eb2389d1e2b2ecdb5fc28f21e91c6b47bb78c3c3b05382d11a6da3f8ccaf9c0e65e0682b4cd2473fcae890996e1d29323de8e4ef761e499a804824f748a9a55f708ce91c7184bf19d55dd6ac9ca4a0aff16d735879f70395785dceb0c6a37cfe52c3af63b851ece8db3100e056e2bdef913f304da19cc81a07c0b9b002f480e45a65236d5e6f4dadf81fa35e375759de38a6d2e47ebe42ecfbdb47980d6c8fd50cbdf93dffc50d5255ddba1fe823ca9d1759dec750076fe8008cda190a4280bc7cfd16d6b0fa1b922c8e54c6eafb2ae7a62f6cdbceeb5c89ec98a83972bafad3b0a1acb61dbb842bb20820b48a584815470c3d8732b70a6f012df80b3c5ca1555040b5a90ca68d23fe3cd3c94eb75f4dca787ee135295446b02924170ccac1ea045fe960f4316e185b41fc302aad2bf484a856d60b43fce97829aa8f937f780c0fee498a2dac746e4b33a9cf65d43c8af81d6cf34716e503df513266dbde823148cd89e6366ad2fd036045830241c94da7a4e3c71c4062cfe03121d728671e8be2f7484967b7708cb5d11089febda8fd673bf0fa47cbafb9b7d068115308a86bb6d9665f49d05557517d594e98754fc51fb432aa7a42c2f29887866dde932671e343e69fcd97d4b1ecafeedba8a3fa4f2a2fb6078e344c4d357c1da1a11070210638193e160c334d13fb7ec552d34efbc0b1acd6c1a6ad0ccfc250111ee611434637ac3ded499695da2c7bc32749b1c7f7b534749a54aa640d0c4be8a462d5ddc673f5c0256d452c4862bd29b7c7b8c6a9d88396a776cff248b431fb7c5c843b45685d9dcee7d109d21fd2817072cc699d05ef6f2eb109a15126af1967b2bb6aba8cbde4062af17b999402f8bdb98b6e2a7c73829b64662a84bb5033f2d3e8da0549733ab53b29a47b2e02824092499bcfe910f454c01162bd8fbf6ea1e720f8821c8f286ab36e939231b69556725fe5218612fa122bf0c6e47fc1895cd09aa0337cb3dc0eb650cfb1173b903332ea7bf993b7c6e278b3cd8aa087370e96fb8352f1585fdfd1e21999fd74582a9df815307e860e62a5a288c09c8bbb6fc28074ed58052da76f2cfbdb3797a2b6a5875e0d48c63a338303862ded71a946cf94afee22d66eb182c823091a6d97ee584d38153f6e20961f7c61d32987371e920ba329ce9b590583669b3cd5961761d2a2b074e59c95aaed954d24decc74953f3c37301cc68b6034fe787b03159cdacd0dae0071e5fa4f157c98a0e9c5a8428cb78b34dbceeb1237eeb7a7cec7da6e5aeb4ddc4bebbbdd00642748b6e3cca36a3e6957730c940296505c7d69fa24ef21f9857a69312d75e7d58b5b38295fe40dcbc1c39dd201f46685772d4db0a9d326e299a6b858efaa3f84206976808804def68515b68d4aed5dcbbca2ff632a93f8f30a933aba53b1c73ae53257162a920fd55492e6c663c4aa1457c6fa1eef45398d4f9f83f87c7bbbbf5bddc321758308f744072168239110f8c5567164437dfb774069bd4d94c0ae3a44e8a255a1dfe4f0d1840c97003190e046ff540ca53753e5b57e7dba54ebe55fd6f7ed284bb5dea3b84389d99e3c6f422f379cdb36b2ad8c9189321a34949a96e1b5613ded453ea5e22cdb0a895ad624addbe1ac6c5ead34529a0f3e91ddb916c2c380f427c3b676eea79b5b6725317fe121fc77a252b9527a59e7b67e584ce0be4b40d4929eb3ffda74c68e6fecfe0b860ab7030c7d0a6e219494de1392a1ca79472ca0d629a4d2915565a4a2944ed511dab80d1471440d5a5cb3829b54c9ff879919a94f27df85427163fa32f70592928ff1b44f43076510ad82669607389180fedc23da313b2f45b2fc1a3d975d3cf410a6747024204803093290a508f2c4429902e29ee06940d60b289ec383ee46950ef4a83a8a7fd927da3946dbdfed302ea35750aa5710282d265991edaaf5ac26526af2d3690990d8442602ca7232f4108ca96526112245825d02cc946316d7438e6aadb9d816d15b0f73e967e6270c0fa484836dcf53ec836a237eed2e7873ade491f9a3ad10272a5f4dbe9ee6362bb4f2cebd2b6c8db862f323a78cc81f00460c60ac720928270158f24dcae63cb695864ad247d4d428f39916128c0141b09930242e75975652c28b70f016929dee4b1891f19082afc0414d7d1cfcda8297303a4092b1a1094684ba4800fe8543f3e077a26f03307bfdde8c6cfa3e27e53a81e259799ec0d216ed93ab08a760df0ee50146b6a674ce05d0b015c5a0a15c0324c6aaea60b1019ac38f3ee8bbe73816027bfffbb6830cdaab1956aac39edce5004a8bc2628c9bac81fc52f52010261afc174095e2acd3c7edaf4f498e6318daa234ff33007fa93a02142a5d1e9102a3c634e4fb9ea955e4844806fbc6d3e440ea1b72ee3212885049fe01b16fc268dae0845de8f5cdd6d8640881e8d4ef4d0513505b7e1cb146bc6129f033a51a08fd780da05b1c299e4fcfc91dbfab50e70801c91711cd311fe521a8874304c29ebe4a7f7bebb3a9941b90f5203207ddf1a0cd7b45ac2bdd876a7302d86abbb30e62326bb26c6c45108c78cd308fcc74b4a006dc3c791653da777d74e3a96a532c825fdfebd35ef96c27e3776749d8b30abc82f2fdefb5a2bcdb9750dda02a95ae08e1668d059e0667a306ae466c50a35b2f6939d4ecd42c3e669eadba84c21e5800e536c7c9015749144a60743c2021ba298869d8ff102ddf21328bf4ff488e8a4ebba6437a4fb71412166255a490f1123d33d44acfd360ffb899923c91b52d89452f91a31cf9d6807538d926c4ab3d27f4c6938dfc2ebd9572839e117f08f954c4fcd07d9bbc2382895c27a5e8a908992655f28b935d8570f73ccb792ffe7d5831597ffdb90d64912b0b7820848614eaaa4914b576ca209c93894bef3ceac0453ef5a0718a2238f9d81f4011848a554fc64ab1d0cb59a8be051c2323fe1f4255821edde536b40d67876ec9af7aba844b112816c656081d96ecbfa28818187e8daaadd4388a54d12cb20788932169782ad4998aca484a773f04ad8fded56fe949c006e380ace61e0bab5f3d6eaafe841e0e8b62f3d79e0b686943820e5b2e9a6be1b65add760af588d03dd08caf34561bb33124a3d23b0021a3958d14863a5919177eb56d0f31b5b0820133cae19aa851b552473fb5eed03b03659b176a36512ccd43d65236d899b745a8c09dd937aaf272193b6a16446601c66f5d6fb3d2214758b67ed5f674e904ad2cac0b5baa20359b62a3f367e5391b9ea361f73e747ccc86606b4d9c13fcb5ae74682cd90ca77315cffa5ee70c893f45adf5cf480722b387a74573ffc3d00d6ced8929f145aae22c5681708032e8565dc2a24c0dffeb331d785602aec5146ad84c2c7e89237fe3d51c71974dacea5e4cd3a56553b57c3573ba75b9b947354e7871b7fbad7e8ce7306d1dfe95eb9373bc8519468ed43d184ec5409356e2ad150c0a117fdeef577412eabcdcf566016df5a51c6eeb47743b89e4c5e450e0025b2cf15c9ded014f23b8c552bd8193fcf416e4f22c76db345b0307ffbd9d822300f87a0a69c5f0d4c4b50cbd1420496cd118ede83d463113588ee68f99a7dd230c97a9a1f78088e570166102db4c8dcd73c5da26fa409ce757bae8228c42745f36d18f7316ffd68b2e032475679d8ba8c1e2b6bbae7e4f1f8a32c7c5e4db4b2c7613f01d68d9a0b07f5262c87974dd39ef1949fe72b4fd2a7f183e33464add44b2a1b42a79272b5c0dd649bfd1eb4ac031959a64329be849e279b71dd44499cf331b755d90c6814ca1b3308a241f959096888fc0c74b93505af2f54084770e65c20f83dad57ba49309fd879301449bfe827ca20ec18f91379fe5c150314d19b4dac0956bbef57d1975dd936929e9af00603f13f7100bfb19ffd765c888728027ab2a7917d3e27af62e526b36432720843207db0484d82aa8a28bb6c93261fd3a043859fe46ae338ceff7144e93899745f523e650d8557e2852f334e5ecad710697f5fbe916365bcd0c595997a86ff7b880c796ea065ba7c3019215d13ff800a0d234b5b260cd49b7879bb4db076cca80d8b90505d5dd123355db4b3afefc6843615965bd08a0d349c7025995ca458477aaab7081c869ba972a406f96b8aa5c0a752d38ab48d38730f98109e4ecc1a8d02efddd5ce97b8856c916daa971ff4da0f6521b46c394ff43230d75a98ca029c2d33e189986e56fdc6fac3a9c6af080ab09a86b1888711bb6997b008d9442ddf839840855ea7f9128ce3e445a9fc3ae93c5ef7ea25feb5112d6b34816185b78a15a990633f2687a5c412aa4834c5ed37c3c938bc13dd86f3d16365fba7e5456371bdfb9049c53a6f5e430ddcf7009d51b9566fe6a94d99f7d428c576947a400078de21ffe3843017ca8ccd80c7d4da0e26a821b43c8b07c6b8288df53c337e2e21f60401cc14e31dd55507e58ae0a5b780bc4cdcd161c122d5a7282ac2714f39329011d4b29510132d4aa0ba4192c8f0c4ae909068261064547c709d265adde300cee556376f12b2705558d0842dd41c7538fc35f53835a60c818864c0507bb5237a855a29188100016c730c53574bb74e0fd7253c162d4dd8851eb7ecc0e8ba402c1fb69bd3a1c6aea97307cb683f73a97499be1341fba4e64bebb6466226a7674094966ce573a0736f1b2c6c9b00dbaccd7fdbd409b6c9ebd91ec2adfd303142bdd923a22435184b924e9169ed78055d7169beeb46f3337c947f97843261439a2a16e03743abab520001b6b175b5c1aff7aa6d2a6be5641e8c0f2263d70dc30003031346ea54b299ddc82cbd4198d605ced654104ace515b111f935c9c777a11e4060f8372c7c247765cbebbfa329e80e1c53f6ab370c16dc9124c6cffe55680740b89f31c33a7ffadfcf281dd22f38ea4a460e9d028d69e85b012dee46753925ebfb5138dc1d02611ea13242e6bce216ef902e74b9000da0c3c7f5dd9e39ab1adb37d57563633de52e3449814335816ce0087a859d051628b106b8ae1379c6d70e003e746bd0ea883dafe0655a0f9bdd2a21f0cb7fdebb496745a9c8ca0094269ea6880ea83e6eb134431cb8b4fc5b192129f2dca5c75c043b50b0b2e7ef4726d5cbaf365dc86e4262aebbf23ca32c96b689d5ac0ec689a2c77d39365759608e9172f13354a78c705ce1ae3ba498d464e209a776754c5f4ff891b74ad044fbfc404bd76eee906d0294de7026952322c189b533298b4dc7fe6b7a484fefd2275f57e05577b3a13cbc96811bb446bc5e64ea66378dc1cd06bc1687ac5c36813a299b5693006b6bf45be0f29d8df30916232502b28921a473fc833409aa8ed50bc5563ba03187b42aa12541a8938a09eadaf0208493b42cce2e3290b2402ee61ef6f3bf32ebf7e89d18945ad530e9f8fe45e7e68cba24520667a2461fed613a163014da833d641241cf1a50e509257831848a3c2540043d41158d36524d19cab894ea659e4b42ff65c651afcab3127048a36c19a0b87cce1518b177fd0a5121ab6f1488ed8b0ed2b9272d98a74963870b2ad6552023ad966ef5e7c6cb2d153710c2b0bd25b04c74a394e5874d82315016ec889744707e339194e67ce9da21b8b7ab9e2023a06900d9b4e3b6ea2a8062d7b5022beee9e2368dade72ebf68e0978e26e592f0b89a565ab315b64f7d30e7dba5de83c15fa58e8279ee6b4bd569fb1ff9473f365d094f1b824e260d02f35cdf8d912d9996c26b88282903a5265f236a0a356f4d880e874c6ee862fd114c6034a3167a55b2090aada747a621a5de7973813f6e5991b93ee50595ecc180dc8c2e39d3f639db04518203ea7c009f0491f0da16a9f9f7b0025781c202018c566cc21e0ddedc8511a40dee14576807e63ce0371b69fb7fffbdf4d84961ad95bee1d9c0aee09d50934248059bb8f59abdd4a5e6ed448d15a2c138359d0de836deda6dd4dbbb51ed074bc40eb7bce5ef23a5e80bf21c1b361e4d4ec5bb2254f0073257d32f706ac31d84bbd94e9e76996b67975ac85642ef45b8923afdf4d6490696e9ee4ae2ac844bac6f6113772d39f8d0609114d91fb360e992a750d95b6012abb6b35688ccaec60056442431b36c4b08412212ccad80c038921c628a30b1e345154cea7dcdf81850f4c40e0c0c2136794320c6165ca155f987104758a830c2fb238656152122a7b0555243106194c422da8514e2a391613e346060f823c40c9200f493d30916100e898a692451eb3f71be678170a9e4f3809e558270dfde4eeddb930a7b3b99813f22187bb2c6f5673d2664d23c96430994cb682934c567652ecee60e4830b1c7b2565fa6f0b1c7b65ba9497be29f092a513d4a10a2135a860d1c4059f186080410d17c650a2461a75099327a829923bcc90eb61ee0d62bce84b9863c19b65053ee4fa179e4c40492dd4450b1db2c8f52e14b4c0c51b3898010c3720624e61b474831c8cde68828cf2061bd058cd1f2d948ba01ee4fa569853ebd2d21146e800a5892876286334b06a53438314273e68f142931065a54232992c49596badb5dee45a6dc855566bad4c44ea0a842db03840175a4ca4264ab9a570528393239e24790246eed3dc2c49ca0107414be4f412931a78b406db264f727daa15068dc207150bc8311f4ccec8ddbf9849ee241caae43e0c28b70ac0c8e99b561313294072ba0506132631b90fd62065a3359f85c1122d72bd17e488187c814618a4dabc34b2d50206e86917e6b4fda1e5589ab46496ebeb39a13072f7cd38dc80a776c30c79022e28038926300ce9104796010749b3a7a332a694fd20b5061b6cf04205bf281632d7579b1a6c306d652257d60a700c8cb161f623f79dd420cbf3891efce4d8d744a6e7916712d016f943cad463ba36f75f3238a6759970130f70da68485567da5ca339433f99eaad1e5333a94ac041a84ac3baa992ab261ccb401cd99e856c8fc351c3667e79018bdc73ac27d23676535769a61df099ae21559b2b65ced023a1e444a6548a4c1fd39890aacd2c95f21253369a3dbc2cd77a4f6d438f00fa644abe265d03a56b66120d5832fd803295a19f50a61f51a66019fb8c326d2ad435b481323ded25999ec6da49a6a7fd24d3d3ce21d3d30ecaf4b4bdc8f434a6cd327d4c6b22d3536e0dfad435b12b4416a2ca20448b5042868a8f1ca34255ac6001ca31ba46a634e0e3774f9eec1a31657a6e8daee19eae8c7be29eb827eee9caeecf05e29eae1015a2445a133448df3da3bc1d7d4a599b754d4c6ba2fd683fdacf613fd56696404d84328d693f9a91172d0067de695909923a7586822f5267ac94b3f41e9b4d43c07ba4a8e3fdfb2c67a80392623b955be05833dda33a13d43542d5e636d10bff758dde2494a950b5992591d111522b5da34f869f631a52a6d796e835267a0d4953cae1480a4e4fe68f06412335924c962b29aac0e364fea807c109d34ab64e3259b6a44865332bb9c76925cb96fc28f97969810c90b8a00a2660104d1d24f5e0e4882c4ad46002b4a495748d489bd0a06b94e92fd255ba46f7e8437a11df9cf453eccf4c7d5670810d5ea8810b6cacb044390fab362ba23860683a6386a524ca79fcfa66101a96e53ea5a7a7b3669e669c3ed257a18b74cd24c5996334f7c1201c9b44f3878fdc7d0d4c1b1e73fea8c7424db4582177521b188ddc9f65306084792d724f2a72dbe4fee66cb2a1591819a14a66a0c2832ea14833989163609e4f3354c93f404d268f16ab0c33f0683aa24211231828000272cf27726b9f3f48981985d7ec61af9a3da8b8b3f1f894748727489d29e9d75b5071e79e3bb5df46716723674da19242bad4c85965b5bb275f195da529cfdbaed2b461c9733653c3e6eb6639ed6e9d76b78ef3eec779f7eb402fec402ffc50606afe4381a910a354208ea24a751412a812799e7524cb92a595f4cfd312952d96a8546152a24241148a100da25128116d32df58d6709a58b23ccd2b3389049a63407abcc99f0ac42b252b16abd5228ab271dcbd5de77df6fb6ad3ef769ef77d20187e6018a250a914565995aa3655a15218ab54ab154bb562b15aad961657abc5e5727179bd5e602c0c4c6d0af3029343d411b323c7c11e452cc68424d0dc638e9004aac3ead0519bead0716323701df518d3a2e4799730c7c8257ccdbf843918e64514a9d863cc2b82520f690acfc83bcaa2285a51ac2d5271ba50319feca2ca4dc63690e96376366cf6a8755a548c2753b2006b7c5326b60348073ca7e0d451dea124d3ef50d2358dd430fa0e7352493ae0f919f6cc4ae8534da2036a67fa9814124d09e56bc34d2da59ae85357e8534b29a61d4056684e4a7fafaa04265768fe9c9934f3cb33065fc83225639826338dd1861654b0c103a32473290888271eb420258829930c229ce0810ca7377e909516b05205063f402081418972ceb90483171e70a5f48507dccff9a8268517197c220759b248af9d36cddab791e61ba24294043a87e43743137c72305a410d1ca028e2428c1235623206da8c7a38c652c8df44ef1fe991322780f98701bf3c70b413e6a9e07d8fe99a8ff46eb7d109d35070babdbbaedb4814bcaedb5e49d0d9f3c8d7ab61f5923132a78fbd3e4a3b0599824d27acffea145e96525a63580557c0dc85f13a9fb0c10828a888d1451745f8e060050d6ad0c10c3a78f2d4dd3346736bc99625fcfa20f538f541587926cd10945b66e0184d9d6196eb7dd05857164c5ab880c51940c451d607a93630441cf1e40541495e7043594f537fb37dd104c81504a63aa0e963f0df7d7e6001872b453cc101135e6830785ac2c906654821c38869e86fe84f0fa3ff90c0b1798546e2864d19d6805d062c995e86870c477926c940946df24c5222942f238156ee7fdabd2447c6bab1afa5a0853956b39ad61de6f4351bdc335fe018b89d1ec42d6a7fc0b17945a6b7d7c6d86d77db5dd2bb1d63d722ddd904cab0500b296b8fc9f79b1541adef461170f98a600171c411479e797add3d25614ab2e4220283576240ca3749311c654ae93fed33ef78768ce9646b9f709fc60878fa2aef29a56918cd2cd43e431dce1b69ea0c6d183d17e304ffd5b015136e98551b18a77634d329d32dcca9e742126a8b0b1ce3918246efe30667fee84ccf63be9bda067d05091ca33464fafaeeb727d064ee4d89b2b5765ec9d65a6be7b45dd7ddb70f4273e34333274dd636a4c9f1f014ed7cff489eaf23632970f211d2c9f36ea336cbaec31d7437b4614696a4a4276e9094044652d216405c356c4802b69870ac05d22476ca9cf6ce8063afd7050377a6b4f56a1f40e0d802e80b0b38f63794b758a7a742f3c7eaf4d469fe509dbe91e60f7cfa0a347fa44e5f9be68f96a3deb2b120d7733bc8f5b793bc0ffcd71afbacb1f56f35b2fea9c6d53f3caafea546fcaf654cfddbb48dbb9df75929f62d238ae698e9056a1459801a61bac7e80e2e881a5f925edf63f506d4e8023e56c7c8f528b035f651a32b878f5929b9b6fce696cf1efdd6c81a75462ae4347bd0e3b1912ad0ec418f1a6b084e2c321da268b413b5864c6960a5e41659a7f4bd7a78f5f0eae1d5c3fcfc0b0aa6290b4457ab6ddcedbc0f0c5129ac5ab15a2d2e97d70b8c189343c750cc90380433f432f41a7219720de9d831e41a6a196a0db1865643aaa11d1f520de1a1d4106a281cfa38140e8143df9037349243de50377487c80f86ee1037f481cc10372403c2100821d4d7faa62db3ffb87f648e77ab891aa903030661eee533d75fa74d524af467a3b2f9e3e60d05bb337f68df5e8169f38139b37ddb41deb6eddb5f86c0a21098c79c88f81ca7a18e8e63221d638e181c0543c150f226eac0dc8459c27cc80c3554789c16c79e7e7898b1a7887b4f3daa1ef59d7ad43972c47f8da9bb8cabbb46d55bac7babe377a3a8834279a52bb75cd667b3d50efda933db51a9ed785b51a35952599dd988e8501936caf38314b07cb65e35c11121c100c713511248e2302a9201c4713cd59979715001519d99972889923896eaccbc45573c2e966479c3882c6ac03174c337e7bcad735a4dd37e4938e39b321bd72d13d31cc9819ee9df168e79c958e910a7b66ded1b854006c6129e3da069763a255d98c5760967ad0f452ce45affb100c76c143072bddddd8d031a80babbfb8a2b5e9e6b05024aae40a8916b1418240131cbf51706820fa8228d1fa26071858ca607c8c2904110301c418588ea6489936ac11f7a705d4025f737b0d63a25d75a2998db8adcbf5bfc8024f7edad376891eb1b1024023b5507d1957b2da63aa8410bcf8294c6d0984c6131b74452ecb0a82c2f51b0a5475238a161fd1519b1e3f4e7c7698353c9d8546a2f7273c08e1f983d4030679abae0051c3cb027cc1e7d4b1a218b34acbf0a03f74f6757222852aea8098069d39127f44c8d1120f623d72858cbf3065804347760f6a8a7a191d9a31e7c0107515252c279db22b019a933f59f13acd3b07a7be7ec72ec22d5243e3ce7ef9c32310c04f359beef53dac3aa8d48eb77c0fa26795e5eeb79600e96e07dcaf282f688bd0af645ec8dd80a86e3cb2cc3cf50272445ed3bb6264f31669b4d45aa8d8edb1b51a1dab46e5db72f97c7a810c70123451ad664df71ddd519db7d14ac3117af23e6e0288a0f1f235e47cb5b5a8c586998c335d97cd064ad122ccbd6721afee99ad8b10ccfb0acbeda44c001a81c012add45e51bad88d7dd2373ee5f86c036a36073ecd9a2206aa44e8c78ee9dcb5b3e73bdf52f66d47171cc7198b1fe6554a1c8fcc165fb1491112aa340e1757c27bc0e92a7483b4f7d788ddc019f83e4e983e1c5113c0d459dca1abfbfc6d55d46d55d237ecb987a6b44a99efa0c1f756e1475c210fc0cb7d56733d567a9879fe1cf500747cdde88c5e3cfaa0c471f5911d81b660f94e7072968f970ad42e29b32e0fda4e73e64fe923ddde739b2a768a767dee0dcdc341512fb94887e7e879ee429aae4ce3c47f2749f24779e794afa94875dac4f816651fb0ef72cd6f714d9f7d073ef29dac89dfbf91eee95143552083df71d7a8edc99bfefb9a44e0589c0f44594b4cd2ce7dc6a50431867d84c328c600a2206396011c65229d6be9159d257da7ddaa44f657dfaf3796a547db4ee5cb09fd366b61dc92dc3d9cf4d191ec92dfb5536acecc75879defe084b9e33c833e988863cfbb965cc639a027e6e98801f58e902f27353ba1e63b5fa2e9f640b62a88e4b10c6a93094d3fc5193fb96a97bc247dd1ea7da80b7d7a93630b72f526d5efed84db693e9751e0db387c1b2a8aea946589dd15cc622136639abefe71867598f1a45213e6578137cca90e4f129798a767a87a78725585628439287a787255864653fc748cb8fa644ce659c25c5ed65fb8f25c3304d83b5cd074d344082ec74cd84a20ba26c534d55aad11439067a41469e47396b1b4d5de91afb5453d74c28bc30caf6292b4d524dd9feeb949252aba5cd33707f93613a337bd87004009db1ef1195a901620470002a7f246cf6b05fdd4004da51d0fe91f422103952a7d63e4ec78c2e4d7cea339883ffea187ae3cd10b8c2e60f54b6aec77c88eb31a4a83de6b31ce2123ee643c2c7903d453b7dd487a01ef318b2a757075baf11ff656cf91d4d98e56cd6faea3396a813a2c4719630e32c5fb2ce6ce6027ecb67b3d667ab879fb13e531de5328adbc1cfd2358adb53bfe276d418aec65959ad166b55a821a883640f382414123e45f614ed8047bd27f5f0c2804c60fa0fdcae035e042694546075c672b33045a561f6db69ea8c153752075667ec3f2738b5945282a594563eb757d329db7aefb392a210eedf876cf7c81eb2a768c7fbf69eef3adf4571080a259c36d9b469a8734febc894eda9ce8cfbf6aff6a415f53b8aa290efdd8778bf64cfbdf78eec29dab9f7ded395f6b8ced873a3b5df386b77eb8c3d81e440c3ecb4329dba6d114e368633b54a601056a16e27526d26ce533ff3474dde1ea4dad4bf80f00f9555500a769d75960be057f7295d707db522798a38aee2340dc7715a7f9809761d749141eacc86034ac13d45da7b5c07dfc3faea9c10d7c1efb84891bb4b237758a48b9c75c7f5fafa95119e9946e438cf0271f9c8db63281b1e7466cb71b13ecb8fdc01cfb33a488adec3cfd2de17f257c13b76ee464ec7b57a0dbff3d883ffc81dd757244fbdebdd78f3a5a92b0ddbbc0729c151f4481d9c5986accf094e6149594959c9dbbf0eb911bfd035de0bfd73b57e217fdec8fda3a1284475f017501d24c57b017cf192a210f0aeef8077913c45f73c29170feaaefb94dca84180e67b1366b9d259dd1bb7ecadb8ebe98037619620d95374df630f7e48ad62c60d71942eb2a73ee4b2eb3bf62e52f4eefa2c774421acbb3e847517d953b4e3faea3d2e677dc8ea2d6f217b5c388dd3ae5114010a2574e6aed372119030b3771366d972d7451d978b26ccd2e5e210d7bb96d1dbce753a33d65722f7d667e9328a1c290a7179cb87b8de227b5a77bd85ec29da69ddf59e9615abd5e272295de32c5bc659b6c659b2c659aeb65f72a73e3c3772b961db71fadb0eceba3db51d6f578ddb2c71891a67098eb30c73747c346cfb8a09d3c7788842ecc1efd883a4c87d96b3ceecd48bdc2c5f7566bbce7622db71b607d966f6f529a73c3f4801e8b3b5aac9c7d55aabb5d6de0dccf5db096f6d92312379bea4bc72e667b5b66a1dabdc9c73ce596badf56b81dd3820376edcdc896f04ba4d021b045090ed390f5812571f34b7bb68741e13ef73e30367d768df2c042a044a90b56c3da09d0bd57003b595a0bddebb3564b19acdd46a5a16284259b2d43ae7a4b5d62cdc928fe609e4e6c656ae46a061fd200d6b1f3456db64c03c60306dd3926099c7bcb80bceb7348dda12ba5c8fd31a38bba601dd00f0d7da37b8596957c39cad6fcbc060f0010bee0c1ce699e443d214ece599e44312f4fa20440cdddd31f820b4d1c007209a06f8f0c371de36bee6c54d39f64ae385f1a5b9f1d1b02725da1b32eed294418375dabab8a1e6369a689706091dfae20be227259e929ea8e0d70e4fd888c2461138a906201aae1b4ab891d43333c72e66baf8a5e59904e5288351ccf0060d728c266f3f74b13d81c1299ee0af090e08ec421483378436a4376c60e20d250d8062d4ca36cfa41eace4d80bc9b6f1eef74b0ba6d9e3fd7d796e795a4bc79abfdaddf58a39e79cb903c8f300e39c99b764ad5d35dbddddddb57bce395f9e747743e9eee6bcf86837edd6801a464b5b031c4be5cac4e99baa041c8c1a3673798a802ed338b285d2337d4b0347824beff52fdc75a4a8795eadb5d65abb975a6bf55e6b6dd5d7abea15f55a5d5ebf9aaab576ad416d717dd7dbf0aa117ce9eea946efaf9162ec1dbfc3deb78b1a39d290b5729aa8695d6bad55eb5a6badb5d6ca69b7a2603f029abbc7124622f55ead41668ffad50d68a871dce87acb51e3cc2d1aeacc5247b1c6ef2ee3aa35d2b2ed28ea741e6b54a5bad65a6badb5d65aa74c55f96c9d5ae262dc640631c42ad39b1e32102218a074891c64fa514a29a54c349129a5a74a940a6d62874c5bd6337a8a3495e693cc885ec9d4898928f277fbdd0b73be2eccf16cb7441bb9fb0d73ba7bcf8539975b0288cc7d0b73b8bb599b6c2f162584c85a0d016a1795a976513fa84090a092eb5f49f090eb9f841bb91eb694eb696e5ea0c651966cd38d6993fcd1ee0d248caf74effde18c6c6f7f87b2c8d69ee32169ca0fdbed8182470c91eded114d58071cbbd9be522fe4e1a35a3bb7a892ed1a3647b596420123c7689429ec5cb23bc8f6f617bf0ea37123dbdfe004d121e2856c4fa4c8946c6fe48813d95e052956c8a8fc1021036a21dbd3264244944154c412464db24d32628c6c6f9196f4802b2622f79be26c29926c81bec8d69615876024c3c5a00dda91d094e17f70d90820c12b53b205b358cdf33db67ac61ed7f0f6d676ede8eb385d2408d0fc5193619482e81439d27af8f4a3454027885290769ce682684d38a5d4c5d70bccc1f1250c5f1e1e267c113b98977196230d290000647bf106d1c1809114b2283561717aba9c0f1f577c5cf1e1c3fbbb12883c602acd2cb20de25df34e51103bf2e59ceb62aa8cd9c39e1b534f291cd019fb309546b69fa92bb38706f3d789bc3ce69a4e8ebf8c3d7df030630fcc5f7e9323c609880f2598b1a8fbcbd8531fdefcbacb775e77795d23775c2e923c7d97e3f4eb62f8971114521f7e87dce983ff502d233e6bfcde1a57608ba563653f4194b85e2dd1fddaefc9f1aae5a0c19814351aea00c34d5cbdbc300bc685e3b274b02ec7fb47411de1c18b183cc5183c7e88c18b232d57634fccc5b3c69ea29d988befc9f1940e8ee3b496524ba9d5eac0aed3c811f874230a2ff74851fb90fad487f4c57fe02876df719f321cc58e1485e083dfc107df1d24798a3a7207f51d244fcc51d7719ff2fbbaeff3608c625086124a5de4988f185c41230633987d5ea7d43533065592a88842f0c3efe087244f91771d244f0e52ec5e9f3abe47eea00e923c31a4d8d5a7bed317df178f5a1d666cf98beaafd1356b7d85b1eaaecf662d471daf58ad16970ac394263e15182a04b55976ca69f5c5929589e999edafed5d83d34f3edac6be05f527b44d25674870593f2be28ee496748684a6b4ffc165fda129eb91e032cbdd404a274d7d887daa3e55df938ae1a02c281d829e4a993fb43f551bd47130860c9280019d226fdd6a35c3f038ddaaee86e1481d113a47cbf55ab9b434cb3b81e575b358acc3b0548f195917477c1160ae5a8d306f8daac3c0a844e87e501d08406de80a68100d0da2691a74468c06e5b468e68ffbed039836548a118d11cd25a951dfd3d111e11f8cea33143b5267b56a11618a2167ae8ba408e30ce62a1a86a4cecccce5acabc699ebf8464a464f212c3135896004537d3b55a24cd566fcf6c137ba443e469bfe18759ae5e8728cd4687bf7d58f8233223c84c3604611ae1a4350a942b808211c36d25284f92c6340b8ea43640e43f6a02e7315ea32ef491d0422a9837022a8cbccc0d02409c5c18630c828fc09e1dd48a5cc1edb7ba44874083ab3dd526a80184d8223a9915148e6780f2fd251a4f73db3ce388153b14bc713441021848fc027770fc98f17415248ea207c08ea32bfd566e63051088bc5fa47eee0c3b8f1ebc0d567beb3fa4ceaab7fe44eeb309207f5d6b9f11361847908a3ea208c319719c57f7cf907e38e93a38e8f630e1d8ff92cc74518189508e32c43186709c2384b9971961f8cb324c797eff86ca6e3b398c37c96e333f1aa8f3e322aa482116372e8d8f1b2639ca50e1f198542836810ebdbb717a936abd6b796bf5cee621db75a2bcc72b9bc46dc32fac8ba3feb5323eaf63b2b52e4ce4291a9af3e047516d9c33aea2bb2a76887458adcf167897a8fc891a210d6551fb23a267b7a8a76f057ef51a55ae32cbf8d1ed599ed2a9452de9e5a35a98c8e1869d886b2af313eb775fc10e2719661a7aea8524ee187e0abc81ed5f14312631d223841e645d2218213c4825a578ffa907e8aece9f784af9fa5b5f6a3a18e0e51bb8b0606612238f3c7fc09626dcadedb71fb20d586757b22d5a6e531b75721369b721ca76116e92eb961f7217d9773e3902aa41e7cd88db3613daa7e0f7efd47411d639109a8ef1867993a6b449d8639644f910eea29384a1e9f12f51d232d5b46513b0b7ceb43c2b3c81ed5c3b7c89ea21dd5c3f7e08f86227822fdf04ba1743427725c909d2227d8d4957aa5daf0e07185c7956cdf3c7878afd747eaa45038481e9973ef5dd40ebe5e358a9aa8934a7126cc7296e33117bb0fa9073fa41f5e1bc58e14921a5b478dace7185f8f195d0e33ae2e8e2f0747d7c3b1e575d7672e6f69b55839c659c670dbea2f9fcd5e3397cf5ace7aabc535ce3af36271aee8cc66299d120b1e5363903a638f0a41a72b0dbb52dfb7595f7c15f029b5a620fc326de67ccd1fa84c8f6a9a3f6ac619720e2053ef7ebb3dbdc954a3275f0d7b61a9fdd1a555332dd1a7b6a04b73ce79546d6ccf64bbba82e71c5faf9b0660c9f4888a8c1059720c8852ed79d4354a744d7d1282c7e95ab96fdd2f0566a91da7534a8c1f377aab513bce579da129a4d451c3524799bef3a90dfea49a85ecfd0387c03ee54ef7bbbdebdeb391e2fcf6913b3d1cb9fd5e9c3bdabb6bf772a81ecea7fcb8d1c571a408566a755c5ef76ea4dae07b57a1daa8ee79a74daa4dcbbdc7a6d38a46c9c1f719eabc3c7c193fefe1575fdc688fc7ca71f5dc2d57ffb2b61e35ce12751f6b5f3f278ae4714d944f597ab7debf1bf0046334b909fc0c3f7a069e9fde290d4510ff2b327b785fdd00cf7f34de6fb86f1fd275df3a529c3ddd39529cb39ce3ab61deb7f19e77c4bb0ade8b7837e2bd431d98551b7882dfcb1098a3b19f79c7862248ea701cf6e63dcf73dd9bdef4e637eab890dced77b85b92a7083c0f8a144bee20b9835f491e17297ea428a4bbfd90eef6bec7befb907bd451640ffe47439dd45b5a67adaee28e3f6f34e25de57de5b5bcb7784f799c77d330ef9ee7cd77280ab1ef4ea4fe1e35ce923b4e7ba407b0cc76ca08a3ac00d4e736b3fdd50645de5458a5e91ad481b40dd34ec3b6a3c8a4200ddbfeac830a3fa4bec99e7e7d1892227d49c73be23ab37d37600a449b5019fdf9509fa10e8a14b5efc41ca73f11e657c7b7f05bb5717d3b3daa362d47fd334cb7d8492d11d18999658726a0469887a3781a8a1a0d75662ed4a3ee423f7cf92ba6fbf181ffa03620a0ab1c9b58565e8e4d2c9e0b09cc79dd14b59bf9e3fbf6034c1bbac2cd466537e002efaee0d8339d485f94a1e40d1ab82083212c2046152ab878420c17f450d21fd64a02cf3067ceaf07dc2e2f5a39c00dc677abcd8c08207c30eec821beb8b4b054a9f0ebae91e28ec6c35dbb4fe953020016820c791d31302f576b8551a0f78d1477b6fbed3ee5fc07801900cc5ca018b2c03e48149191450d2c84e0e10c204a7420db82088e1f314a7ad8384518af16983e84118330be986099f18311760326479a71bcc9018ed1ec18837081758c39462265e098b188381ab18261c6237586fe6554a106f845ef32d21f2f708cba5a5c2d935bd263690997e487a9c56ab15439804639a856aa95ca8b6297a2780c70228a193b6001126c7051d219461454d8e8411c4c9a6250d2e39102a546da043be118054285a890e6ee6f0986a7b6a274fb68a9bb1b1ca910d6018e5121dad1bbb640aab4f504c7e873acb15004f4299689a58d289631e808f4140bc5d2362f748d262e54c9b48d5a08ec59030535944c6bb791953eea9a181d835ae922da888e11e6d0351a464f9d28164a3b041fdb2204a576d0239cc61bb9bfda849494e4be0a482693d1b07473c87dac5926ce05b99f1212033cca7d9435c3a392fba1934c264b52f61746ee834d3299cc89ac53e34aee7f15066e85dcf79c6432590c40b649ee774d32996c06bb84eb21f7ef924c267352735049c97dae916432590d51bc29727f3392c9644f8e588191fb1a0d4a778d1de4be9d61299ce57e059a416743eef70e65cb643648f190fb143ce1850453106c1bb0ec1dd81c2052ee1bfde45bb7ce7d0ea86d5a4e32996c05289c1250727f265559d0bd22295f1125c7b82272e7187714e39038a5dcb75bfde194744d6dc209559bed683b1272002a6f47b347df45060c16d13531ee2713815140a3aeb1a742dc4fd7ccabdd32cd1f2db95e237a35daae79db51ee734d4c1b7aa3c057e4e6bcc8ad5dfbc961e0180774ef37bb831fe2fd037b26cee77da7bbf77acffb47f27cf7c0f378efbafb9460d73bdd334e7b5bf79e228ddcf1bad37fb6eb36cf7b8f47655a1b4804e5a00543b4e3480ea8e38238202e4aee4f0eca1a539c4481428a25d270a2441c1027c41171405c1310896bd5d137656213091f4d257a973826508ce999416b27e360b040935b4255397851e6ea087a95c2a5d9cda0afbdf3fc5f79fe13096fa1841336e02fcfa4d91152cc7aa862760311b317381ae0963c935a48e34669a1e90e61579e492d4c01d4021248da0b37371c0e6a702d04c121e9eac41931745a6c9aad60ce3199dc3456a0d1512dac7023779867921556d8a01dad37daeb36e5eacb5a3b1bf6d2ec562ba7f5d5bcce53d5f0a2f5d2cab5a6514ddbe8bd98ebee9c1f124b297084499369465002d377291537de7c2b2a0c211002155e7121c8da7206c6b75a5c35cf1a82210442a0c22ee06727f7aa19810064eb47a66fb5dc78e9c0b0842228a554dbb671a4b51ec1f5666be7116cb92dbf5e6e478bbc663c8f606b796cad55d36acdc235350decc50d6002b9ce39f3a90a41ae25a8ae4ab39ca6699a366660cc356b73fea879d64e1502d5fc61bb864e9886026db6ccd7b4e1de3234326ec874db53120c9367921553e4188dcc0eac40c2092b7e88c30a264758f182156ccc702bcfa42a6ab00d8159792655b1e445154d70605421c46529590184a9c8564a4c00a5949e544a2e5882aa02082b3d010a9588524a2b3801ab4042ad21c34705511885173a7853fc8051289143d7f445c1021cee92a702ce0aa12e0a15d0b049b174a398dda055e1c441e1b44406851525e514506c014404c512548ea08852432905911df34c4a92449c017ed1e4a06a8115c99229c25a6bbd73c34b02a02206152939dd6a013d51812d10b7ec0c74e4a6620b2a8ee8178b880a26b9d677151c036560338049c143ae32c8558a2cb94e0134c519b956980a50d270c1104f84a821c8a7e8051b3041459014a320da071aaae45a6bad95ee40298cdc49dd13dfb57392d6be8ea70d778b5397ebfa6d9c1aac6adf312d11df9c3f5e2f5502f5d68610a8b795abdd48f702b1821ca39101a3f24c826213038336b8445f1020cf241fd2e8ee2da4f0c2072b4f465164a17a5ae10a10477038c1420d9a018b125064c12409919332866b0e494aa61632b8820723294f94c1460b4c7ae206199567d2135fe4a04d907bad1c090289dddc5c2d86b336da98ae996fbd646add2a905a8f288ee281568b25446c16b48fd96b4990be3c11c582543efbcff656efbd27617263ec45f20cca7f7f1b7df224813ca123f428e5392913df047db2f626bbefcd317655e0e18dddf4c9dc9842cd9b7d1d71aa36dae97329b5d23c28ac61247425edec81068ed17cd003eed75a69eabd6dbbd2d04cc0e6d85749dbb011e6009fdc39b49476960f286ba72378fafd23f1a7e49b354cfb75c27b9a658d524abd27f01a48c67c68760bb2924170da60103c1e53238ae680415a95ae69ea9aef9ab6246b55b2f68f8eb156d61a703348e2344c53d9a0ae7107d235e0b5e3aed96c508ff1c81aca03df392041e0b9300cc3f027b092bf8320f78edea33ef91bfba823813cb9a36dd807b4e570304f9f4ca91234cc99debf77efc8d6487fa8ec091c6b25d5afc65e37b1b6628363af4c29160fe7d4bda7c63967da4a53571c5004d0ec69b1d7ccd3b341bddaa45e5ff178e74c3f35ce398322310ca8e499e40495ec04121ee8aebd62f1ae0599193c093383e4072cc0f7c9df187b0ab96bd01b7f3f9fdc8d3d44f98e15077404daca3ed8eb0d8b2c782342864a110d3e7032c0db0e2d3881032773028873c1891f339c90dd2170c7c675823734b828f06606e786958d0d7cab6c4b608e0b8e09af9e2003896d0c2951905a9064871a90547084b93c939080907ed000859861862e06a14f0816945c18983c3568e07e9eba20aec4b039dd20b05081d6840b2249061bca1fa42076b8a14c82144412262f28156599a18481a889a6abe0b8485c95385410471c2f1c01c7d3bd010e2c1b15cccaf30938be500207159705b895e713704cd9b6ae5be6a609255cf24c6aa105a416bea42d80be16ab5755d5b853dddd4d1b15825f7b5d5f0eff9241c8de345b6f6a574abbbb5b1b67bdd7dad82bd7ee6e410180a6a03516e77df224ada65367ba7d346cf698ef535b6f027b551527a9935bf2a8d9dad44e0d6529cd97865bc7172663539ba3f6194a406bb5507b7b85d599f9edd6dada72a1e5421f753439dbdcd29c5fbdf7c91b581f5a94d5aad75d6ef33a59d52eb77dd60854aaeb3a246e6c9ad5eca6751da99200f7fb54088110e05cb30780a635026bc7566537cd89fbad950355614ebdcaaeaad5aad75d6ec3b9b9a9dae536cf1aa1528539db559765b5ea75dc76bd4eb671dce65d23aac66d9d358275966d59ad7adde536af9355ed725b678d68d5ea755cb7bee02e77b9bb699685cb6d9a9d72dd68e3fb3e50bbdca6d929dcadb47277a691a9b597ab53b8cbdd4db32c5c6ed3ec14cd6edcd5ecc682d534bb4db99c66a77097bb9b6659b8dca6d9294a5012baabddb8aeeb3cae081391fa512272d32873468869e3dd238fd029473a22bce3a4f37d1fa8cd99ae2b01e7ae820743123adb8756a809fd6aa59736b9360c4354c5b9b9b9b96118e6680fb954ebf868204a0280cf83666622802aa123cc1fdc8fd01fd8af6d4a4e81fa973b427f3652236314c989fe44792a24a173c5f517365f1a05aa47e8085e6b853b1ef5118a005a3447e8230931430768b99234caecd1471d11e572f7d20e4597586a00e5da7d823dd391b3a2fb3e125c76afc7b50ba207a824ec17eb8a5832542402000000020316003020100e0844029140980992e27b14800c709c3e6c543419489324895118c3400c320620630c00c01862cc1815580013595583ba6c62d36a3072c29d1ec0353c9eb0925514d671fdb51a140d5da71ec7bd18b9a20721450af5fee3f66168a1604baf9f559e60a67a16e4cbba9967462d1bfee7d9084cd0507a5df74888c7c6a6f02cca6ca88fad26ff2c552ef86fdb6b45305adcd3ac8a4513120742e87b40045b6fb83b80c508b1ce15231918481b3e908c2c73037e020475cdddd8c6d921522c6ec69f86207d5bbb2cf9853a9725595ab2a34246d9772f6b0c3354639bdfed01aa659a1894c340db9ac11798da3b29ffb2cf83e9a42bd994e273fa15e69dcb9ed8ee50bfc943620adc4fd0c8814d008b1c55067bab68601dab349ceda2403ed29d4a221c2d703901f2469e81196f5c19ae6c6505c3e280604793393e0676ad98e3ad814da83cdae00e4b25fe35b20cac5fe38a76676bc41c0a4c7680515640ccd67b283df3e31f52eb3215ee9f3de8db1e90792ccc7486a2752f8b14dc6a50a312b9ff112e0dfe0ebda20a4f610bcdd8cadf64498f7f510e60615c9b0338140616b9002800f6eb3526a42c67365456e32bffda56d832793300b249f24c5353943a698468de103d3b4f2cc9199acc1b342fdcdf100ba31256d7285aef1a0d7754e46ab608cda160afb6880583873528e3457e309ce1492185ea350caa74b06a9cc7c1c74389210fa553eb134b61581e480ed723de3225c7c16e0ae0ddc6c04262e01f6319b272458ce366382efdf0c522b2105a91e7f513340e7bb66907cc1d429650f22d2478a0b68d6189abb119ca85c7c8670d36c3d0f09659c8d6522afccd3916f2cd907097185376a50f27b99ae64e9132c7f536c364bc064e2708f64af0f5b2ba7eb406fa31067d04fad1e1a5667af0739a830b882d42787617cf0385f09ef2c271b8cacd8de7af972b42af37828826f427ef6e35846ea8b41d9145f8c43fa1bb620c1ed96cb92d62b8258267f9811db5e8dceead7e9798db61333087852c829675c224d60b6ee78310bc64b001e28276a9746926712ff91db549a041bc1dfd3161a56c3bc00b41a76bd62e9a700fb19b680cbaeb5cd5be8ccfac6da8962d43511cc8043da7a1d50627ba1c67c59b6e1a76403e5f380cecf8c4be755283216bb8c79e691eee6079c8132208bad7259d830d9ba2f20d1178bbfe90028224ac4ac35493639c0f12cde7f61d71ea2a4f28c3cd9a4cf07269aa3c1066d37c6ebe81ba8a1824455846101060022b0e3dbc81db3846320a19f86a379552925fed6606627617c2d69d81596a6b161bdf1f4e46d4b6ad66be64c94ba7300c90e963a4f009cf278223c17315ff885128c7ef4528b18317b387da22e1769571abea2b3a78412ccc7d0e4782bdc262d38406b4c270b6382c64db63782a3ffb192238653e70212b33716a9bb855efc82d562b014d54d13db3289c1581ac8a88502576b4408a950256b2d40d383ed69eed32a286e7992ee2b98b9ee3307832ceb681e36b8bd24ef402442e1897538ce441bf17dedbbd62a0e81b8f456e90e598e29ea2f92884116d02194bce9fc8aecacfada0b2cd3736ce9176e80e475db78d03ecefe53e0b5796356aa02b00e071b57c98c5cf75fc19392e1969d8e6ca63b343e24a05cbaa9f713a47c624e2c10a22e3174323f6f493fb9a3ced5466c191f170c90bdd0f24941ea0d4c8215d288f1b486e571a195bd2c93b406a601822be90ac7d50236c23c38fe6ba6367726a24902aadb753913617f230d4e9001338e5f52b54527c05ea71166a638e7d6341001ca50ee3f9299aa5e83ef8a06b2bb616ae8e3a37781cc6feee89330c48181bee1b2cf2c60c03968e189b38c42431b1094782328c48840bc0f039e545b08a81eefae3ddcb674b8a3d272f61d0a57a6ddfa5fc2227f2b566112123ad30121618ecaa3e5be82a7f5b52e42cbacadf76ddcac796883c5b97f2b06dba0cf6bbdfc802d5fe48599d52f71e074b32db10f89d1b7fd62e2584460c91a314a8b929558cfcf6d5a4d3d8c5d7abc24176f312ecb3f66f765e4d6f6981dfaabd8fa30c829a5c24042e612a9ad27455cb6c4715e6a09add0cbc176cb35e81de1686baeb94b50d9cd0a4f3c23c0066f1df12cbe8e5bd52e3b302e829941e36adb451bff0b9c3bdee8af43639f17a85d15f0d96c5162b202af1aa3d26c066a9fd118fc1ad4a25f52b7b9559d7b45374c29906bf3291ddc495f50ef0ae5a14c45a250749f5100a33a0189c68c53654f9dfa214cfa352c7ad49b854da51c70f34da1ce657952cd1caafc58f6021616d1fc75cff6be1e103ed20c076e1fe284b1346f70f0b3a3062bb83378c87f73707f66ab3da77510a6b12c071b25493c1ad748d717bf35774a3f5b0ea49d64ff9789a691adb6f08d6a82e2256f97dddbd0299187f56e5e368562a23d1bda75826f24a9574c811ce32f18821df840f9a3660c71f31008ae232578b6412fcb0cc501f85376603dcce58f7e5687394c871ea2cd178c07775796de20635a338a1584b7c10658e0391dc957141498e874baf203e3a98bf1f7f04c9624ab40eccc38ddce1f9e8c8635cfa89754b7aebbcc0cfff63e6871d843c721172f2a228c8a22509c9245fe1b7ca5490deca4416d3d3461484852a8b57af4e3c8d53ac97eef6ce61aa4912e26c42e102f547aa4ea5421786237a6a46bf981b095711204ff4f9ba44d0e8937ee470b13a733e238b214beb14d445a06df784a25bc80738b74adb2accc81ab99d139083c8364227dccd7305fccc276683bdc2364e5b9481589e5003a47d7fd987f60834dc241caa5cae256a8d3b8a6f0c24ac07e3220e308206b8404340af2c71b199674717285e4e05c173f0958bdf2e4af528f8dfffc41764df6496483138ce76ed0ae44a79afbc18c4a07f2ef1a2d67341859ffe7fc6249513f1be05ef142463c892b088070b18682d4f4596c9bd8e896df15a6f8fc95ed5aa842bf75df4e9d9633a22829d552f69e71ad65244f61093016781d64fa7f05ead812aa49de30f5556f7ba668c03df913f100f261c3d77223994d546caf7ac498126c25e7a5f1eb3c9e396d914c674b9b1f71279d9146819163d86318b430c5906810800181c64a0232e956065f50f8aa2060cd92150670116586efb61281363b4cc39096f7ff50b8f74355516b1c8d0058841f4ee840118f81072c57bbcae4682c64acec195d7f62473ce2deaa6c4356f72abfdca6beb3beab99039fcfe944b698c87ec2ade9d3461818a6e66113b65c928155e2cdb61f11d15e8ee9ba0c25963605488e661a290c1223222bd29a4b34cbb02dc79dd5af47e0ea31b697d6bb6dfd758e0e00b9c5234254785b4651bad7f5cabf0b74ad1682c175fb636b528118db9a903f39215162d12852957426388452675f52e6e91c8ab7b9c6ea28ef7a800ed671c034544bc1971ac2570a32eb085d9ef9ff0934fd12098c172705c387e686ff9702ef3ddf27d0abef8722e1b0f1328e855f0353bba50f46021a9281ad48eab4f4bcdde60d0cb7850cb42798c325ad2cc06075996c75a2ed4c723a3b5331b1c44191e6af9501f979cd636b3c106b9055a8f2b79288c27f62f2eda3e5094331c48e47e64e541751cfdb454760d82428f07bb4d76c7f6df161321c2492aec55517f2256a7f988618afefcfdb9a97153e2f7810a0ce813a35af2e868ab9080dafd651f7845d7f629d91a073ab0542069f86478cb86e5e0c00b82741a112b01ed6b5f4cb0fcd73db7402f990c99ecca100cb69de7250c862ded2111a545e4c87b12140b6a0af03b9cb69a3c166370a2ed992c0cf43cf7b57a1e8208d3091537fd518a8b45f09cf21fcd507b85f24195d698b519c95420355e9e1b37d6ca8c09e56aba19216c035e1853c95324f1676a046aa163c0827d090616599733eb16f835f56071a46a39c92e663c74a88f4eeb04f78de444011cabb8474e39b69ce0355d105a5d88243c3a9c4ff4f519d836f054d5569f6490138b840d3d6cbd7fd59fb7382c3ff6dead026484a683ba765d8e1ff7c3adc3a3131006eb6fbf8a93c479f072344038254af840ac232f1bc3e9d525916e25f7f7caa2ecbd5149eb92cd37517cd14f7a99a78879fa5ebf8523c3d1010fa1595171a3a61613906b4060c0238875c033d82288380d6db9c36c3d66594ab68a92b663512564c40ab3121c936bad8184353fd2d1ad7e65ef079365a617349e720ac7f1df90e0263c847bf24ce676ba8462d0973aac7c33124ca75fba7c14048ff25d4c8fac48eda1979ee28c8b9468e732bc409f6d522ffe7f2015f36c32e74bb9bc7e4eee94a4f80c9d98f83b0c668cab0c977bcace5f1b65a22f69b3ee29cbafbae10a41e42f979e017cca0ddea3e9dc1bbeeaa46fd62de3ea4afea012b0366fd19e2cc92dc7cf930f1e38c043381adaa704b9d27cb9dee662a9c1249646d3be6af3d49b9617d4fe82fa1d83db79e126b49a3f58ded48ae9b325d3bb6df459e7f62d06eb7f892905dc2423f5a6ff19af2b808d521d83f06badfaa1afc7eb18462c51a7ff4a8d63b7a43e64304db5b8bb2b5b83a2b4cb03c404c6ee2b9609a9cdcb5cdb7540b5c1122961e92bf7672f02f9c7f368960069da47638b34cc905b59bbe370026340d2d5edc6a09b10cd1d2076f3b7a0331d177436973bd28736d91c2fce8952d994e414c9f941ea5f2a54e858f8372ed6a566c8157e89d19c462656b4c9e6817d9b0f4a2ed91db56a9d662ba966baa410f2a74f69ed7ab502965fd89eb44df5dbf8647ff7b2be5fb70009605a2c2949930dce19d45db2758d589f427229a5ba3131307c87c2642c0b552a93845fef333075439ae4c5f6bbc486d62580aa9501ca6b943aaed6e32c5c2db6b65dfe337d6198bbb9419bb72c0cf6aa8d663c0062200297e89969b46bfd1472ec64928f6bff6d8def1ce7cc4556833876f9d5bf9671c1bd0425f8137860513f7244c1c4665bf1030f574bae7d83093078a1f25a7a21a194c1a492f7b5cab0d6baf06f657fae1470ec600ec41af130e3bfe6466b7838474665bcaecb71f0f250ca6f47977785ab7c1990614a12fd878a810d432588e45cdd0897055b95a0bcd385fcdbc15031c046c185ae52d321032fd7c090c15a3955c728abd3805b8d8fc00a4d5d147bbed00a7fb884f2e73296e8b3f6a11918f3c2b86771210c9da98498a75e984ddb60eaa56e963989f841a8df2d7d068b5b2bb8a725d53c76e73213a1ce1cddda91eddfaecf12a8141623b15cdb535469df0dc996bb91692f53bf9a10feecfeedf653bcdcd0f4f78029cf62905048510e1c8e882f9b091bb9d12c0dc5c08d684f4e2467995d65341200b8f9413b24b96f1c404f75d7e74256b8fc0c545f17fa8b9e945370fec99a02e097cd85a7ff2c62f0c733a293d49f55b260dce491a23f2b01f66526ab0e25f53ff2ad8d194a503f2fb5a8aa159a4e7647ef1b15c92f8b45fe27c8ae405580950be995392ae8984fe4976cde7f51e7d74460093d9892feef40687a8637b8cc976487c39f1438ed1b4c4b6d0570b8735ac74b742b379df3b3ecf0b38f24e4c1cb7083167bd3a113ae58ac5e248bc1fd75bde3a0e915370f1f98f527bd201f9a800b4893916a1e65cdbf3509b60976d9c50f3ac3127582d0655d8657e4bc8a684b71235d0fcfdf1c9c56807d6bdf0a1b4e4dbb5e28a937ccb530309588f3c8684b8a180a8485bd7e1e0b837cbac6088818cb8eb596fb5a6f276d7b5a1816e3d27568532be94bf053d45e2b3f113f21cdadf093f74931dba8e9bd8a4feac54033499d4e9ec316ced98bb0a4ca06594ad74087fce94e8f69c60fdc87eeb78ce5f0efe0d526134c461cd95146b50b8fd13f159294ac7a3a79345f92eeedc9091a9af5ab9b2be9959c354f965a441d14d91b5985f6215591d9b0b6630d7681cc28509309a4fab60385269d34485881d0b82906d2f9463582a050e7d7d98cdec7a292c4f1ec70b3a94cf36ce7e11d9d53d6ba50c4ae43017d53c4cdc5184e6df438b4e95affbb508b595060a45e907e5e76303cc8c47a6c6741612cf1b4b8ad41208a6d1ebde502c1fde3e8ee6bdb8ece0c032a711d4028ad9b34bac72771210ff87148d6138b3511ec4bd17c9ab04f8af4c9c4be8ca991538caa46adc7e8d287131dd7303f4c09a263a8f286221a338cf5a064ce6112aa98f549958d34b8742ac97e422c2f8850b9aa27a2720785324ed56bb3b26ecfd6eb6040b96dd196d2a603c8e10c208384e759daf775ee0a102c0bf5c7901d25000659103733a96c9676adf5e58eb3c2b1c54cd7479afe3f7be1048785fdbb6f7b409bf8e83872a32a23ac04877cb7445e7c81bc74707d43bd9027799b61a3c337fca7f3d7fbdf3d3881c982aebb7fb9c543b991f463fe540f7b961bf5b0a6c5e22f05987054cf5d3d24dabb2e8e3b14d54543462c2c5903cc4ad5823d1a19afb837f0aff74fef2f76ff6ed87b4feadedcb40dbe54a3b192eafeeaa9098f57320bb582c76631217cef1d9ca6c82b3e3817458854894b93fa0162bcb0d48b9c8548d931e25f9f110945af147f51759f4ed1297da98137b57f2f0b52adf653b485b3d17d2689f586628e1d8067c2e48ea0038663a028b21408c2ece035b2d61e9da2dec8c12a060c7877033e3c0519632a324ae034d754ef78bd0c69e56527351aa5e790feda91d353926074a9dac8f47293702856847163365cad620a6d2f76b51e335f3bcc6cdcaa8c14a374f0c9cf335b7591742baa014e9aee1bb9935c6641a08f90bea8cf0ccb7a518ac82b4d09d57d949546db4b8ff7603d628ae01333d1111bb473cf00956d4e0d695cfb78dfc9349c6723de7d694d80dbd17d80cbc821a87104e8fc298690cc8354da5fc871dccf4582118690a1333a46c6c1a457ca457a6bd1cbf147522215641566669ff9d892ae0884bc1a9ea3dd421cb47990b059709eb5aa820eb2a772f3ac847439741733dc0b0c6c90d0444a51c56899698fea5c5857f7b00ade4ee1ac1e82f1eff38683ec72f35039469b0a5b24f45362a753205d170b4b93a42308b9d0431fe0d30deaa1af681f79b8f1f33f4bdbba4ccf7e26aebeca99c025afd392ac4bd53039890af265e2cba88d4ad9f34b49aa0041448c68443543a139b50009f82f78f50ca79c4095201bc7cb11bbb51604735b1fa53b4c294415909af83f29c730b5ccaac4571725eefb9a2446aa96913fcdc7c21a8412345059d5759b1fbee5ce1e48c6e23f2fe909b939489dc718dc19cf5e18fdb74c1e85583dbda9d9eb2d9e02f54a8ada37fc834d258ac50aad53b641617fde669213f6f755b5ce6f5704efbdae5a9ee85109f5d88d9a199a5536d307837a510ddc7504c266b80edecac9f5530de83349eae4c9543f9f308a6a80fc77d99714575d2dedac81260a4f25e91d4c80da4a569f8a9cf092e89ee431edd154278fbf0ca3377bef12116fbf457091684c9d02de59cc4f1c865bed9ce73ce6ae7370b866cf2b101c0e4060ba6df063c339592c31aca325132a9ee0586aa3c7a51d16f857879144357624e3802b73bcafdf013249cec96405d45371642e8ed483893b173e57699626aa0f10ef398aab641de6c45954d110dba84ea7d2503d54b2b657a3c0363a657aa9c4d1cbca1c36a2d26b00136382ba127774064e51741baaa350355c1d15b28cfdf9f4c6d3e1a96ae1416d14ea54fcecf2892463730360f7842a8557c6234661ed6f48d40f09af62f08e41f6cebeb282c3c01e2fa23407cbd873bd45604944a2eb2cba7f2b013454c1ceb0367b24b5edd315978be48aa98926358f34c964f3fc899dfc1f3d77f9b4195e8662c3b74c18a90d62eeb0e20c3f4a57077ef5553bb518100636b25ed4075c523fb130f1939f19f9d614cb7fb99908ae31764bbc24eeb0169dfc441287e992552d45c8a464f0063c706917e745889ecb794104bf0288abe960dadeede415c7c9933309f3e6331df10e1babd422a83d8446c4dcc38595e4f128d669b13aefa0d0482a1c6c8cf10a4c3892fbfefa73c6eb88ad427995b143ef90188694ab886062021b4aa8b8a7c849a5f3dec45879cf73a2837f6d0ddf62f1d2340b4c08d1d30e0271c13cc649e5b78f2db1c90c6f252b425b850f1093797c81ad8c350dcaec2d6217c40b2d769dd9652955047310ceaacb118683e674789b5dd4e5e95c88a464f2eca9314933b89981cb486205d0963ceb982745a6fd94b98187d7c555e18c8d046e911c4d5aab9918838770a75a3caca627090ac7dd8a7687517a57abd94473789d0e2e23af5bfb3984441635a70ccba721f846c0e4c14b4faf86efcf63b97b70ac51fb395748aae27590dce8869212ada3f27f4db6f6745002c7218f4960d8fa163720d44ad8488f6f535577116cbea76b73a4610e1af62b728a7e43398994a1799c179a8cc556157c200ba3375cb22305655d1cf3c66e87632f21e179c98d9d7c7756be7dff075eb07c9e44fb9f11508e8b9f29df5a7c32029f29c98b8fb2a9183132571c7e6cd32f1e6f4bc5484b5e40cf20e2cd0293327dc100fc404f029e4b07422640eb999629c8cbf7dbbf5fb1d580a5ddb96569bc1f4817b43f0353821e53e24a986c52e94c06bcf800de6c75ce68aab1a318d52c6f70fb75f1e7aa235025654f14ebf7147966794f7048a3a57251ed7582ed686becfbcf8d99f526b14843ae82b85815966f341c755d84b1a186736d12dc843fb84502a0869f3a2a59eaca4dd596207d51c4e887004265acb3358b0d045b2640ba24c53c7ea8594d39bc170e9fcd601e06a48cda80cebc22436f8b28f94c674fa699694155804f74e516d7ecbe5bfa30fcb992259afd67f8116a2c04655c9615ecce14d0c35391d07ac966c85783808a2074132bcdfee4815222b40baccae46153a4b6ca1e12c9c02f76e152b35cb1834920bd961c1d9ec9fd8679c329558486250fd9b4b06b492f59961379fcbddfc10f9f9449afde5dbf906a1c672dc47e16ad2b407565d4067ecde67196e8495600c859398f41b23713110e72d6798c3ec2f1536aaca31176d1d3f0cbb72883032037b0bc12f2620c68d52aa5a480a4122c0160d65d3407bc8424694ea2df24bf3f96fe1509af8e26b7aa13af7b59e793ec3c9b3e9a9012d69626e631df1c4bd8100ade983e368cb541120e8d819aa6be52a4472b4429e83cb6e0709a0c27e17bf19aa8d65831c63ece808d11b87e0395dbbcf97a6fb98c8db3ec1caee28adbe168c7fdb959bdb35a37efbd95279c80e95c5145905ba595359fc1a76b92090e3c7138ad80cefa04a59993f744361809aefae42e75c27a531615e2a76a2130a44bb9c67b0512a6229165a72e0419a738b872a07d6971cafae53018d7043231379c4d3d51d7358fb138a46f9df7adc0d4f65570d6842ba331fe91fb1aa72af27df5d4e58a2a6b68e7e744f271d4e547f8035e5df86d6142bb96a331264cde001ccdcc52409db954a0f96674131fe659b7c6e21c14ea531d394b31d46ff9b02984d793ebbd79a01b130f5c90679ee3f6c049e4fef7a5445359f4136fef0dff8003d4fbb8d88da5d4517394553afb980b89a188b8c7656cf564c5143e33345bef33fbaf4a06d2e04ac561f6663b7f5368ff2ad6c1b2a50b2bbf760d3fc954144177f8a141be9d70001de6f5c6a13ccec4fd97f8fbe34e9f1924e8a7c31cab20d45e6960e6f2591371c6a42aaa0c88d4a74af5eb3f999f6172db3e156f2072969b1613c1f7801cb6cbb79e29671bab6bae175de23049bfe766b04f3cc85e0460c6e5034f2fc5692f16df34667bd654aa55f1fb5e828b3c650dd8436d06c418c365bf831afbd5482664f868ae75aaadfdd42dc6f095d4de55efb90bcd27e539764ff3d01589f008581eba2609d3c61786e9c5d56fb17c8946c33d10d5d0163e4e591dd310968b4e6c91ad5cdcf94d2449d91750c990aff9085d832526febe61e31ac8a13ffac2b177ffb19c87817d82b1a5cfc8590d26a8b85dad50379ac431fcad2b936db01dc310555dd860a5e6fc253675547b0553007ac808114b09e0fa31a7fa1c65ee2740658d05d6f4e6df1a507bb842c2114948f0115c18e582ce62e18ea60b665e540e3907a35f2e2008a64fdfc825e8a8909519bda2bc223afd191cf4eca8ef47e9024484b53016fb02739685e3c2bdef8d4d5af8f1dd5f4ddd72dc279474459f99b69324af64b7f2ce60b698978f994cb0be8fc080d21bd8c1d6d1fa7807b398c1da931a4691235db209700a2127fdf444e5644236fa40ab6dcb3f38c4c65c23269f0420d6a602a08fcd66336a226678db1c96c16025e620f1a8cc0c1e8e8f617e5056c5c530255b75981341b1835a2e0a195fe1c6124d01b7fecc85c3891e8619f1362e12954aa328ae4cc73887ed845936d16e287c88927f87afb59e9b71b4b7e3bd387cb3e432ec644269e8ed9a5096a053124715d0f5be173c432d3b811abbb3836ed2874fd6b49f4f439cac6e10e9f3263993ee5c6a2dd8c8e0f9d46a6834f4697ca9c484a572437891a9f70f4773c3172e82cff1c5469ac97436f37f765446529a5a70413d4a294d0d135d8c88c3c9bda05179a5c743a16f44989a88eae06baf6300c393314a537048a783b69a192083ac8ea40a3b66e362f709288d403ae926c4138cd700b1d0a590e4c79118bfc62ee5a8b731a07f0d2a0ee06b9a0e5780bed551df4df52fcf782d6cca8f2693d4d73d274f97c652fb68a7479a00db9e007830ccac769825f6009685f7281c15374e0d396e07cec392f3fce41f056e482f3427c38cfbcec6a01de4960cbf22d35e5b1ce3772c91a381d291229ffe0b34015256a21d4cf4a68094ea08a1d6792d48b96b89743be8b52b272f2ca796b7a5a8c9416a620209a9b800e4fb54d9e8b315121d7d4d64bc96e8eb9a45ffc9d13d1b5733d60c830ec5864cef1344915d2cd9f56e432c062a42ec228bae9328a244ff924c025b5e2138ceaa95d94efb41b70657afd7a70dff5c3007b962eece29bcda05f46608abc6acb6b70d9b5553fc675ad7509d5713f6916c29b6fcfd2f32904d50a433b2368f4f1f504f144c03edc335c3f0461928e7056cb802c5cbc24231d17e66ef081adc9a832ad8050b99a7ebe62aed05030c03e9500770473b1009c4bc29f80ac302b808b21f42a8d442a248f75c9964471fa64497a124a9b4d45299492f2d8d17ec1c546fd861829791f85436a92afbcdc26415053e5de0378e7fc901001cae2cfc2679ec37948a83d2d9432cd866b0b8b9e13a251125a5049142dd8dc94c9476e385928010dde807fd3c380b169d58ec10adae750e0c15e3dc147aecc19dd454c96edc1461e4b5f3bf55db32540445284c84ce946ce5accab635eb7e67a89c3873e61e47a1318fb38e862097a682e6b3873bbf96107c9ae7ab10631444f3e8801d45a7b82460ea06bdd508975368393ced81b23833b2d5ca7da63223d97b2c9cf02df822a27b66999b5cdf9418c3050896a0db841cf57eb0a984d25cc305dbd5c218731508c7b4c0f2db904eb79cd04846dfde4e73585bbfa0e8fcad3450181f71d0b6b39cb63966c681c274c9e235d21bdb4b7dec73240fdaa64bb7973b8a963e0ae014dc299306392f7c30d093334b3741613f29eb5aa0896d7c8cbf3e8d9654c5dc6e1e43a8b0cefb3dc250ec38a660d9d64783b070432f6575586a62d5a2ca31f1296f6d8eaa28c5b922300c1f04117655b5d30981fb41fbf1d1e8c4d8c66487c66e95eafe67e8e775d0ba6b3f26576c412e1cee4b238873e9884bd07a97ca7839df9695fa727f62f2312fb596670cc93bb2f47a7689723ae98dc9263366d59b0af4bd6bc01446946ad6aef8adb9d92374017bcae501218bd911f3a07bf29f57b0ca8c45470c47f6ed40225c7ac4c429dadeac119a8efdf04a63aa4bb37fc26d80951bc7cc7e2b7ddaa37773afd105f276be139abeda097c8c1ab3b71354409094828054582e33532bb0d58066fb7f13e20d5a4a9a839127766fe2a80813d96025f4f8fce080e451adc5ca5ceb1a3fd2aca760bda40c629243f9b9cf9aa14b3c6aa40d21c4d14cd87dfd6528c2f35dd31deeeabe9c1bee4a01ef4d6ec43f66fe84c4700d81c4c09090206be5a3a404e2dee4a6f59cb8061112ede9533861b55a9e990e4354d080a8d2b89f75d5d2e6cb2d893bca11c0913e17697fc57f90e6d967cd3c7936ee4ae434c90ecc5a6006faa0d4d6916d1835c1c2607cc5f62a8a26416c2719606d9ac3a729af38bf7476c28b831da1e8c4e0dde59e667e256702b0667de5fce2936b53b8a670e28336b10ccb86ad66806869e79b98af7a79113a3bc2d3732147cb49f676d7de76fc8a702b98556811b0dda82ddcd1a4528281f975302b9d874d8d56056b524b2c6ca8b01c48bd573887cd6321c0b75c8e61642d38cadd25025619238d3a76c4b6e1723020bd0d208e355eff8f70f9d6de8324ec822151d35d30e8ef1b8edecf9013041a17c1f037a2bd92b287afaee2f8d0301972545614569b60c262cd2dded436023630c009a69db1ac9638610f6bb680b08cc7146680fba87a84f4ecd9292b32e40457c74d0f61f5a39fd8358056bf38db870bfdee1591ede19ed61b32a1bd1b65eaf511b872439ea10c6d26595d6d3ce78b7e0c5368787794d19d3cb8d6217be3fd0eb3a192b3d611cab34c97164c16199e8c2d05be41915ac641ae907825c50e971fc918e2d6ee9c24a4a970e01a1fc00f3a7f63ac0fd416165c02ea07018d00b7a707f10d0a42ff5a55a10e8fd4d4c45cc28e5f4d2347c04adfbb4b85efea9026d8f94d7e44846d0f79cb8e5a67fabbd44417807ad808c9c2d13e4720c7350e72991b46476b96f4e569c1675680dbb4693068709c3302c959ee1b44506ae017efb79b31f6919600bba8dd1ab542dc32f7765ab6f3fad896ed9533ec19e3c08102d237fc9fedca9e8b81bafdbcbc5a2827a774283ad9675a9d7d24604948b922d120d46a6971ed2877def4a04dbbc48df2366de17442443c6e9843eb6ab2cf5cbed9bd7affa59fd59741f8c1cabab2007b06e6ec05c8e0f997e9ea90d2384fc5b490319d5c08a91c4777d3c428bc16224008a06b16e3272dbd99af0015c3e629d5152c62eb518af03cae085dacae5dbbfcff86ea6cbeff7c82c876a209f5daa94727426a7087a4d527061b028251f8d049e3dce87c2a6a433297a9814a3eed8ae03f0f7b9174c225fb48097fbf33fda30257a8891e03a436e24a68bef42ef7dc4273a51709e03b6e9a8a69d76f5fe4bb81c3652b0ad453c86b2f3dc4ff9b617dd2936ed003a258ab70113f82f7af82e21a1839ceea8e3858fd07953edab7b3ade5dbaf3910e81c943abaaf584a7d09e777727fc7c6accdaba3dbc8e6a99a0e0ee3c9cb751ab6985414130137480406c5ffe125f244273823136fb1895e2d801cc915aad3be365785580eb4bca2d4c6c1f4a5a659ba0c9923755aa97b46f2089a58581f43014c614d0d007946c1d419945a579171ad6084a0fef7b8d885dd5a059f609c56c2f99080de3231286a3f70fd875352971ca1ac243c346c295ba27bbba240514b827b040227695402c88bd1556eaf11201949b3d5dfbe48e0e21faec877222683c3d437832f4856d1bfb1d04813ee0c2447e2fa359e02068279e917b2d10e8ce67a442dfeeecd42ede71633363e0382985719be0e62db5c2590622648c051802971fafa5afd6f7d1a2a13c01dc85c4985c5a6ca375d1368f1afe8fcef47200f6ebb6efbf6b6a06ea9666a9347e92f662dc855870d0e22e02f729459fc657bd1de392d87af0224fdbebd54bab9cab4a44630029f727602e123b808f0f271a817aba648e0f6742bc750e225b02a9950c045e250fa54950315ff8c8e22e0585d1ca24e0cc089b3387fae53e1308808181016b0dccedff420f77132339544eca348811aa4683039cc083cc4fd14d3344100c26837772beff0c23b71eee980862dd019a1467708bb1e7d471e5f1aedf4f162addee1e1a5e373d3f74a6b50198be36df3549965333d25ae01d0a31029962f426a6eb7a00487aa7e98803e41f4f32f143e7f417bbe2fbc7de82e6716c018de382e3ec9d8755107c8d7a33cd6a890146ae10a943cb3118fbe7bd5bad0adc89d46cdf814d98326ddbad3d6e66bc7d0c373e4839e3c64cd6f71fc7f408d94383691d2a1d7600c891566bd33bcbd50e9847269ebf9daccac0c1a99749e993b6eb58db73b75edf7ba2ab5b4b7d669b5d761759175765edb0042fffd5f7007365910e5f7337b450cffcfcbdb8970346b84ed23bf40f7c8292abfd4c80e0bdbe6c3b11c6d59b760606aafc3370beaa3630b83c8060547334cdc4d3a6da46df9f30005c523b0695644ff36af4ac3743bf9d7af05673691bad0fc2b6f1ce324696607681986c2699c66040005108085e27f47709919b12f89b0638d731834e1ff36057e81ef740ee62fa2dfd2667399ca8f02a0c1c031988a536f86b1a8172afd5f73e38065be25eb5c160f2a92ee11b54c65effaa0b0dd62855fcb24277e4f688aec82db8f7d8cf05006642ba52200e466201e3b67f7659756d3f6b1abc36c320dbc456206d9390d00e0798f59b26523c668e7b6f4bf7b69298a96881a09f051eaf6e48ce72d36bfb0b5f3b294d224a9c2e324c666ef46e092b6cdf1c6f39c9798746b8fe75e808997384733a1f5fed42cbc771b217d6a946831bdc0caff8964e757a180ba5d324d0b9c1b21ba1a5fdb7891caf142700f98c18f172e785a0dc4458b546bb68ec7678e7e17077db72be39aad28d65f06e8415411eb6f874fec8f467d42b19f99e846fb1a011c319f481de08e3f1b648729805229d0650c84e774326807e90f82b221c6fcfee91836a8dfd3e92073e27a6414fb11b801a442c63ace63b8b19afc0611c7bbfc58c87c9ccc0026536d849a27d719bf92c1dc59fe323444286a607ea51e83fc6af1108a54c3fdc648637fc8c7421523db11ad8f3cad87fe3db53e30e6c2c94ee7c3b5795bec89ba1a9754d82ca9ab4723cf86f8e1f8495af0c3d71dd1894eddc3d21da92d4e1aa44c7a12db33ed7f473d9e4929a5de284f08e51ff881509d583bf5879ac8eadab9109400f75133d34ec96311924c3b622229e2301eb0e809696870c6e80646524a217270d8cf56636f6f3a46a2ea0393525464c32f9f8e7339dafac5ef5ee9d4a503abef06ebd50b6de403669b930c3d14bf9c8635b048edef4d3a50401a37f4d326987fa2e5cbaab9927c8b88dd252311a1f49511a0568ff57284d9b562ffd20eebbe0cb2ac22c073753733f3cfb23bfda2c7267fb12fd82d0caba1f7439d6abc0da80a0350beb5d77bfd6485a4dffe4b132449c2f1cb2441f96fbecc28abf4edd005b3199351b4120ce295ee4ad21a387a68dcb548b4ea7d0370aa1553518939ba384152424e4e51ec7810aa8cd10bd444ea30a32e785e43d287bcea1911f9be035ab3c54b0d6700c263c00541e4abea2a2be1eff48f52cb9f17381b49b56e9f8b4c73710d03a2fbbecee76a3a50f006d530a57a9a8535ac58cc5a9a5d7b94ba85a7ca9357be3dd4a0d5427e52e4ebf60ca10ed5943b4f2b0a46fedf642c3353ec0ac753dd620e7efcf30a6e9f609ad873d2da28012ccb2d2712cd58420168a6ee3e88346930f9b5c23e01d349c3ad87e17301e1b05130777ca60da1f12d5a739d3f3e06ba4f02b12a39e10e0a135acf78157ecc6199ace18cbb0ab6ba7f46fc0b61a359c7fef28b3128e1450a8859c6147059ac76a3c3b3bf174df6b09f5765430e771e7c4b66247e25b88a711cc963613ae3a6ce90089acbec7cae9211436710469d6003b104e34f20ff8c1d5a2d14fb16391db1fc98aabb3123c156914bb35186deb0b61b72ca6a888662adf8efcf3f08fb204b94fbf0eebd282a44c90f5d0aa2dbe1ed6372285cb4119eddad38eae2bd1d23f49de18056bf70ef9da175657e4ad55a0d18e0c31e1c994de14016aa70eff2dda9eb58def9540242d1ade6d0be67e5ea2a25dc72829491a2b10ba58cabe002c84c82fcb7b1f61905f8be3b66a5f3544acc722926855a14f5fd5364df619c9ecb079681d05ffb5893536ad2f8a9bca929ba388b68040a7a5d641d7042a72349a4729653a2fdf6893f58e6af2c0ee2d242b286f5e597e44a5a4f3e1c946c5f1ee5eaac373d5743aa259092b258b2a57d0ba745ec00329041b11fb686eaac26ee124c2220dc72870909d70c76917699b444a93ccd7c26e506efbff71d14e7586a45a98fab033f02267e8472f2235a720846d8238995683a036920f7d2d63512283caeec78e16c799fa99465ba45762908f5f8d6072a554493090b2313927b2648e66cbe1593c78d19f0adef59f24e1f06083685ed33be563e2d445accf9d7994cdad3675f38125b716c2c879e0b5c2992878f29620c9197dfbf6849c1c97149a21fed12541f761c8e19f6dbfcd7dd3e2703d8f22f7274fca79d0cda95a9c4b6d6a5cbeda482df2208d08325a2dca867f4d6420e97b640a983fecef30455e779a5ecf32bd208895b2b2914f354844ce5e9ad76e32b2d49ceb3153d7b652fd32a35cb51156f7aaa0bc8b6879941d3bc52a93364c214b6362dee09e116b87d853b6fa0d093e827c92229f11f8b1aae7f8c5647aad693aa5ed5a55a6f163c4a755b8366a1e042ddbfb6b29f1b51fa9a345b677d09a603dc5141386b86ee3686d2d3a0f61d286c17115448f1a79dbbd9f07ce15bfe5abe4251e9237b35292c61b796b423e739752a8dcc29415af676c2e875ce9a8879baf0f706a75424ab335403221c2f664fa6f2ac3666ecf4c6a92690c0b1a11b7ecd511ac25aa0594b0008f27cec007f465ec6b98817275ca2aa5260be46ff96e2cd2445d9840b4292dd805f1e0db436c2be2f53dd7c469a9032fc6eef2ee3a18b3b504d71f3b574a0cb40bdbf6e5ec4716a11203fda4eacc5044fd86752fccabb1c8ec23693ca298d6906c61fe076f39c5e70ac15c607b0068dc05de61d045060c2a853d8295b276456b87e627de34eb87aa0977dc135a9d3e29cfe4fc386f9c2da62548a2d3dd47018d3d7dc7791342d6dd19146ab23fd39d3586d52e06862b3ce7ac5b188be8f9ffaddb9a4b46414c447099e2e71a0b419f6f047f2c6e1e43690bc2e3d125becd08f9ce1d4286038630f8798a9e8db1cee814a99673b1913d185bebd10eb2e2a0fd7258f8bb6db0e242eed0b1e419f99676c1fd215535477d2a5aabd35a8f40a21cd4aa2626b5020a1205a59e1da107bd34f649dc4a360525c4ae05ed909666373c9faf13b31fb9cc2f79d501072a904892220aef17b9b7f59299e2ed38b76eb049b47311adeabc5dcc12737e0b1a4d3094b49c13e27930572da1b4c5829c9cfbecaa80137406de9625600f072be21fa2f9aeb8935b3f0cab28690a14c79d5dd3c58a47723a6a26cb5390940912906ef7e63416271b2466a36f5e3ee8abfb53d753fd8cb001082ed14f21f453d4313542f5de6de079468987be1177d645f03c69d6b8b3c9e323a468066f79a8fd4f9b518fa4af606f71263c94806ded6d9409b47a9348e7d843bdac9c168d533e4601e2c9af3a6ff4e4d920486c7c9230239e17849fa796937e4bf7f241e8c25ed8bdf4c11e9ea58fc2fd5933e7bb6c272a956f0d5f874dc6007a22d3902020036efbb5395ee6ce44719b6d9b71f5a46c24279a088fc266f9d42b5a71d4e1b9054dd165b0b5aae66eeb3ed86393f8f603aa43ca072e57354fe73029eb970684edadae5efd44630d13e62742949450ca5cac49f3ee0a001fbcdf0e0a309023d3b0a8625c0ecb78de9a191c3ce951bc327b7ed584ba178c0caec1b48d34f1317681c81fe07a06f21a50fc99999eca4805109da8804e023c62c46a6cd863f4f03022a2cd9f7ed3b84781c6eb7c6a84fc47a8ca16a9168cd4aa56053469a92a41686df06cc5cb151ac996e66dd29c23a84b1012a821d1120613560e31e8e9e1c09231681d857484ab645cd14c4e5138c29fc850437e497565dcfb11150cd0944d6c209615d7044db3a2888a762cf70699728481d8f791d09318dcc6ea645de326eb6939d73c2ca4253451ad60895a68eb76c7da635a00ee457ade9b20bd241be4800cda424a8e418926886afaa66db80d933ba45c25be2ae97b15e2b1513b3b6646fd90c950fe58cd71c0cf5c986cd6a70778d0d5fb75d3e8693fcdb84f9b06c01006ff4fa6c4b594a1f2d52f6fa02331879310bde07b87c97565836171e5a0b17d8c206b70df2a1a55cb76ecb0735f8cf7b140c34d01fcffccfb50f6154a51f2dc3f026d99b90a7129d628d0194caa67113ee85d69088a3e91448fd35f9d7f44ff4a5f7f83594ed4ae4a9b48aecb08f0c2bd832abe143c2c7b88f09dbdc4aa18410cc96db04a3a64732a07bfda1f12a23e65535ee4f4fbb24bda3f5cc97a66b0fb0289adbe46600b1a2173be7ae29bdd6793f428b1d75399e7d8e1df1ef3186c9d47f84aa0738a44a2fc4c137cd460d08a709d9cbca28c3ce1a6d11f81a9f1054af79e28e7c7d71d4c2e50b7562aa1f6d43fefe45808c775981745c3c5b734b2688d4c77cfb762a5e3c559e010fcb877a01d905b90147f9617088f2281cc54e3949ac482645f8f65e2041540082d77d0de28f6f86929acc6abb9d45cddef463e2839eb8297e1c94af0e8282ce90af76ce76bd335da4f004db596102895707e1c58d8893ad41b608f36fe1d670cec7e5e250cda9e1230a85bc2be6d6d63e8d41d0a639fcdb7b73e2d3a769f031bcfd169518e5989c04f3e45eff2b028b772c7512780a036a7c6becfe80dbfab2d2a5e1fee6d4b9d0b1f66700120ab888f16406e94ef27b912ed89ec4c12e9212e846672cfa0f203313965eee5e431f9cda1d20b8c2399e4ee1722d39b94a157130674cd3150326c693a3c34b45686c883425a949133e40fa569a843f87580bb35fea19da80406023fa02b5a91e63258532660f3e1d8c5bd4aebc7e55907b3ca7f68eaf0f9e32bdc4076c514f0fce38da518cd216ae35fd08d04e92310a6abc54a74ad601e2ad8d3c03248ff66883a60281f9a8dadd4dc48eb01e06e6cf3cb0cf48b8e1629ac125770e0cf5ee7ca4747b3874ab07edef4cc8a4325c9bd7e8bae09e7a5a880a62f72b850865add24abdc4da8e50a062844721572614542165b5526982580a8960f12e94be44ed23aa4c370312ff16c65fef5a4eea2b7c5b3569e5ec3160aac6504b1adee5282b0c6931ecc32307e63b55eaacd04fa1320af5e477fe91657c15e41facffa5d43447787b1abd9c1119e9cd99b46b6fe5aeb26aaa1e897e1cb7a6c686e10ceda1e71ea937fce75d892b16c3dddf1ebfa1b87c2a38c53a2fc6c5adf268a451d9b1bd2c259b97ec8490d8cfac5de0d0a2d85798647b6e674ec8f78f19d29eb4cee8acbd7995534cbb1d9990ff7a3d39a9f73cef484158ee8cf1650cee9a73c6b832b9f54e88bd63d8f974ebd63a355bbac68544bb5effdf5065e6b2f40731ea42ec01c07c944b7c5d1d8b3c097bdd68e661b86c898a8f0926c93c2070e4dff33f98c56155556db97de69911da509432b89d60fe07089e6a6bbc9acd15538d127857c67cf17228acda64509bb25e951a445ac3106d8ab455f168391f2de1a74968687781638d3d5301b1c29d76a8af4dfb001b9e21f6dc979e8214ed852cd0c3cce5320b441a2ae5a7aff87f1ec671d6218da5a99df226fa3a4b11918d89a2a1b26cd12912cab64aa52b3d29e18500ade2076e589729cd254d96b0e1afe5dedb40b833a8cc7e6f33c8b010e58037f521acd3f0cd5b53121b2180a16ee94210c3c5ec4c0e39131d50299fd500e145a3c1e0631c412e1f9b2cd06995c397557253cbb0186c2792a93368c04b46e97e2fe205db39d8e761bd46de1d32e6200751395417d1f83ace24298efe6e3c0f5a83e2d53dfc0c07be40aaf9a7ffdaf9cfcadc017215d63f6c78d64a0024d758b4b0a4d9276156c0129e91181f6a2f2a5956d1fd6253609057a568cbd704112e554e0bf47140f777ec183a24144d05f1405688d9bdf6000cc2be0507c874feeef4b3dac6f7f0fad397ae1a6886eb8f7f127f585727c904931bbc1a6f88e82b40d731c1607943448e5e89d2476e7ebe8d7563104b5a6eed805bf3ca5b2528e87e25d21437175c3f1dc3b4d7ba217fe7b886b54236345f2b943d7ad25ca4fdcd8f93ca0e218168eb2bc7e5af5fde1779e46253eaaa806f9cc6a08ba79f152e88f771b015160e9cc4d8119085144b92775f9f7b7dbde84691753f974e103778cea7dc5bdcceafbb0834f38301d74f48cc80c5437d183f97e2e3c457091c83b0943721dcf8ca22cb413ce613f65c2bd6332ceb097c88bf117f3df24b70585f2cebb9ae2f90d9115419a4638719cbba0f27f91512b03d968cca16e0611f49c74a550581b1a910e1fdecc0206527b05003da3174c6eb75e14fce9a379fe0f2ecff31c1fae0d4386ddaa97a06604a530dfa2dd175cc537f009399b006b5c4dc6cc9e67a8331481f0b298457f466536d42f4ed4f2a20e1085cf12889a268849d676bf209798608ce5759daf8e9879f4a0c420af1a4ac7d66bb758fdd4388f6a84dc6d812543dd239db6ae8797b45342a5a200f764af8ae301df7276253603ba055ee733bbbb153422bd63141660efa300ee22489512b49f573ea5a0005b4ccee8773d2f92e877697ceef8c283af4fa2c4b6aeff989d27ded84343c8e68826f527073d7dc2b21e10f456015c8708623bb29da718d1e040b39cf75cf19904a677ca44ecfc4e508f652d0352613638e54b59c6dd1f342014f19f96c343641ebc3636af7f996613cffe0cafb2d61331a3f0cdde79de8f54c5b6458c822628a27f4b4ba27d70c9d3b118b92878362d9e380c53ae3ec0aa9d002097a6c4acd77f50554f3ee441ff595299c875adcb809462f3221556ea4552a7685b5d8bf6855c6b46974e12d4e20b3f6ca9a724685ee9436e193d43745ea24450760322eba938c82214c3e4ce56412c962da3c60433d58b65f1146d4f4d2f22b1fe79164643cc974eb8d01efbeb8dc5bdc9dc604fea4c24145a29ea89f732891aa15d0be848e2d32246bb0ee5a0ba07da1c6863d345835cf53b83f5c569f33e60d311dfa1961ded1cf33f5d91088fc3fb2d6b9bd532c9a63a0c9e106e64fea9ed5b379e6f524e41604ca2a60f3f71b1e56609be347e562cea0c045deaf9214b809b805a01ef2c28f272dc7eaca636bd742039e2c0e3963001f9f1b9d23138f810bc344bf83261307b0f031b7e285d2e072c9a440171177daf52a523f98f01f430f0d58b2ad63949a63e0ec5bb229cff839b20de9714fd2503c6565b17072db130aaa6e8c2fc197dd983a32ead8a4e5223e46c507abc85cda4eacca0578c2200ef4eabb8506ccfe8ee7869742fddd141dde99f94e0a953212e8238a5a45d7eb55be90bacaa31a54025abd2539de39089b421f1c59bce82952a76ee0253764da069710817ee3fa7da6a5600121daa4a2bd304a36f4885d85c8a4a82e523c7f69c0c68b1aa06a495ea3e2e76e52a2244f0a6fb80e6c90987437fc9473edabf751359638364448600fbfdeeed64990a5891c8d293f6326981af11324448b11fba821f7c3bf6278e8474fadfe0f0559beffb306949d2d182156691261937e84ba090e25831973a87a3af654b4fccabef1e0066f2407d823852fb112b4b794e8d46c9f3e8630e4f4955a1f1f20d05f1ef802c5d758eedf2ed77402772081282538dc56f4da97c03fb91b281d96559f96822c6f0714c3f1fd94d74abd7f0ac08f36ad8acf45f940495b2e9f8299005f1e128bbc1681e0487e6fe5678a4af30d1b2f21dbfd36e106ea5880427e78502ccfb0abd418c619f2f59d6e20db2209317b2cfa0fa0d5ec12d9481b1b83891b79b8a613f1f7b2e22a1f0bee6e0f8856b087119b76248170b7a04f68568db9ce55a17ce1b63f305feef680bc995001c883c5014ea5eed06fdbf7e7f8268952339a6099d3334fe0e399c951e6e57809ed57a6efd20cbf16f8f7895c789fb0542f2718c52c2559001e870e483034031401117bcc18c6dfa4091dfaeaf531d3dc1b9de4bb587a6e1b423d9ac88f6530fbf5af08d6d7f23f5a378f7e3125e48429413ad69bd25425e9153c338c30f68aa6a22b40690b004b31b24edf30f2c07921ba55694e58120bfc80fd276725ab8ea1b161ed3d83fa7222ebdd87bca65420f2b24f9efca0aadc8cb2100f2ae1f92c50219787bd0fab4f07b0a590100bcfccd2fa974f3ca63f97324a314bc882a932f76ec44112b80ba80ddcb804be80fd91a497dcbef2df09a49212fc97ace786ebdd21938dfc31342e1d330017d13caa092d5fcc95ace143524c2834ac99626cadfe4212b511b83d4aa2b775d163c3d8416e328231b410b9ade6fcbac75f5cb50f3e48395e0a090b529808361a6e0a1197b8b30244222ae56b57f7413faa1d5d873876a20c3812be9bd7ecab06e551155eacf60d2caa5d433d0390a8a26f02e5f2c26f64184cd67b82d0b1f26a295e74cf02bbd311980e0d8ad8353fdf56996d5816095d52380c741e98c9b962e0112121f639d9c2eb4ae8b1cafa37afa02df737231316378b50698490da2d9f4fe4145b7f31d9e19a3089e5c8f789979bc406051e908eaef2a0de0a047fddfcd1dab7f90796e6d7ca8d70f6003c5368d114f6a1d62ceea707398dc512e5061315f7ee0d483a52258fe8680b4e315d910289c811355bd914312251a0bb79c32a0d0456e26046c4ff9b24212093f64dc0e8673fa66db1284311b23a68c11619ae2fa4cea6827b570b92d04484fa88272e5d2567bd8f8a3a97fb50622373786ec1b98967a200bfe698c02af7097ac5f8d59ea0ca1f7b1c6498f4f3e601e62bf508486ab50ab01ba0d03983ee0c7aeea2280ec78802db774de081c6bd206115354fe95234b1c2c4fa5641121a6ec8fc964f6763d28c011ad7a8a295176dae7f0de9731b376c6033b7fd56e192700cb374344cbedb9b28434a3b4bf9f885bb3d7ae25e7413938521be2ed24f55935006ea4c20c94284beb0250be97cc1fa4c05d2f732371615485a8563846ab2d17298ac83b1998e05eaa9dd3a6993db7605e90280bb4447c2857aa0e33a4cc8c6d0b896e9e3b2c4d0f734dc96b17c0b5de9b4c7e65eaaa38f403aabdca42b804aa534782de03cd82dc00a744fb9055e02f4e3e0ccc2d4f5d741768bcf2a5b39c211732e04de5bec19bf0807042b0baf4f3c2b6d13e9388485bfb72870283468bcceae459521dbe02e25a9710d6ff669ebb30112094e1123dace5e2d9cb8f8d5fb247209ce634a5fa2558b1fbc28dff5116a5dd05e9e58d5b9dc05595ace19335a1c96f8df9b8434b4096422c9956f148b5dd586ff1add602b8da76444b446a4bf317b44658717809de460264a9a705bde5be55432863fc1b4aa650daf0f7446f78af3e93c06afd8f812d7d9b12f2f682301881e1a5be0562ed2c10fda7a2f7bb77b37aef71197d321906555269eb19ad955a905c96c9e11bbc8430986bcb896c4c4a03c138eacb5c4075522f33f94ebe28a64ba0b0e014b701e53b892bd13e513b333886fe04c76b4f4339069a9864449a636f275bddd20d3be32cc00b52d8c504822a3880c19e1170dfebe460897607acd0a8b89cee1a24b0126e3c1f3052072c350766fd1777473f4014f8525d2e26f7d2892ac4b9ed46e5306e1d3786144541ffbdb2de4435d318121993e610fa1e4a41ffa7e3f20b55175d7e3da1c05f70813607ec44affae722e1bc4477830d8612c6cf820a1836dfe0e24013677fba1a33668b53f5c80068f02edc0ecd38d76544a6e81cb4d3ac126351ed7446952e0d76fa95111888fd42047fcb1b40560a99ff50bc98b43046495dee39a4f36b1dac8625a7b4d1d5d7dd46c0cef16e875f1dfd8f1f4267b052e2c3d85ae40c9fa08272d8563a12878fd3cd9d8b2b98468d829fe85fdd8d712b8d9d5350298620af703554a63065d1a5b2c970f47a214bc09938b4fcfbe5ebf9c283b2ef01313961e03ec9ffc8a9ffd0188b7494cbf425f061a0e4147d5641ef72e84309a2d0879bdf91f749be247b7eb8f1c4aaa43be282076fb111be794e0708a974e22df55e83e7bef4984985c1e3fc10414f88ce527a2d6327f9d69c5b55e1070ba5f10912ba64ea9fbbc3b68d0cd78b527d8b1f73fd30ce9af6d9109dd64ebce152e499f0bbacfa47ce7cdcd6613654933020f3bfc3942a962649114635c5b1475aa43ee04085065c90893f71470d4623225d06141011af2f5e6bc683d74c62b5aacb2e821ee59154063d9e08708274070593ecb893f05c23cc05f8a6ea9b28126505c36ef30c1589f7440ac749363478adf5e95b2a26dca759725545697ce51dcdc3cbe1756ebde1c3e86a201c4d112874011614c4ef0312335bc0c7b9cc3f0fa7ca89f20ac8c5611b32e56e3e477ba740afe78f9169d1aa66f6c5b488a6cca73cece3bd39355ff05dec955a390ff565b1f7e2bbbe35c06f17b73a33f7aab8a0edf6f5d6c6ecd9c3139b8f70ab3d2b2b9758b98e488a738d9a0bfec875cb63efa08ec4d4f03ac92483d2245caeba5b282651e91e1792970f36b83a746151737e1c49dfc1193e82c703866ae57b1cd43958b8b586f56b0713ace1032d8d3940bad3caf6a4f74b1a55c1dec7cc460020449c75cc56a9c5ce886820bd42e23563b0f6c8a922a999a30cff9c81666e8f1edc2d3325306f05010926463f53d375f78c70710f23f4d16c5c245857eb8f78fc0449dc0a116a5f236c0fadcb7c345d3d663fe670a03bec86c2974bfa564bb2d674c85b485cc608aff578e3d6bc961bf4b52e0554006da7cd6c74351b868d887ad2943c84c67f5488778a92c88a438494f2e52f4be7e923f01e4a74f32302ebb951fcfba6c6c175b35c9b746d8224054d57c18dea39347aa08894b3ac43779cf6a3f2f82f5fc0f99a56ddb2fb64a6438fbe25515a3b1d9da0a5b65724850e15bbbb753f6cae490eab53f44d1ac3d6c161b35729c7c8738bc79db5aedb5ad223d4a10f0a9bddb13db4a7290f5427b47178d46f6269096876b35d36f6f54a20856dce3da0e7e08d5fb22a6aa62943d52602418ba17383334191255f993a209d5ea6e714a72c07956129aeea2f03039cad1e7d23519a82c1a212cd36a4e214250c22c9d78f27f0750bc55a92bfea16756e7fd5c948819a3b47873136fc2384cdfee40bda676a4bc49174c77193291437b8d144cf83cc21108a21b9a88ea89a8fac9101f697b2797e523c60d447b42f708e03312536da7f86b8595edc6fbaaac76170356ae53cd1f5d05c11ef36548280c1430cae411b137a7f26df714fdf35a6fa19107d35c3c46350a7b67789481fe07da67be0f4c1e0befb840c9dd8bb7a284e71a988fd2863e590af78ceb183822199c81ae89245a6f350568a681f8460c97ca9d0a78f65d496d4df4106b555a9a282838138916261f9179d5b779fd9c7b4f742df2868728e605a1f83274c71296bb81d27f54bc7844aac1c54e67bc68262c18a4c7c2a06ad7b839c087a0128e5fbfb40a92a7c0143ca0c0be67aae01cb24b6c3e192331c542979398a64c1b9b4f79accc89910f31b36e1ca3430a248b42746e29e410e5639a116b8b3d823a29a4c6e2fcf404d341d5b41be7c94a4d37ac7f8fb0aa42f731f7e4ae8ad751cde9eb429fd3c229fa98f2c1e44b0b538be0deb12d29aaf2d539f3d03953db177d8f0672dec02feecb49dca9100713f349e41332e8cf2f339d5e13be254a54724af1977e7004a0c09264464e4292632ef0102fe36a65e77e6a5280635bcb8e1b3664669124a62ab6beb174d386f144ea75a65d21ae382ec94f0d2c0661f3843a9729007bc75b800fd3c2845179881db563c9a37213c1135f29c48be8cf22ece1faf7f82d1d2b7466dd4b83e4652f61543fe2f7b662225d3faf93fd659fd2918638faa722baab9b6d6d7825b1e96662c80f7f25f55bffcd4ddbf4dfe51c23e5b5709ab900b900ce93363f185b764360cfbd9a5a271a3b01422ae2fd2b1a6714969fecb306e52495aa3e31f3e534717b07ed4dc1a5d625dd8c1bc0854c4926b44ddb379c10613a6c46c5ddb284ce2040ced49af4bc6294a42b9cd32069672541af2e80f8f1d2b9f89f0780e2d437ca7725b14ba94ec7ca94b34fe7e591f5f931249c18029b8a88db375088a901d0650d04880cee38fe438dc018b3fc08ca20b628fc6f45050e6c6ff70c55f5f5dd063df17ba4133d4591071291116818d51502d07aab9202c3870daef563ac444da29ce5736cb242023178a1281b536b4af75199f41e362ce1588f260cea215401e11eddb6ef32691d76a37a1e9e4a2019e9df910840833283e0e3741e019e064462403f499916284412179dbee4a1f9588b483a1f726a187547912a38081c02e4c0514a00d93e54ebabc92f59f958feb64cbc1b53744284f08738428d39282b14f9bfcd06020c49666bc8275b8b0172b118d31739519787c3ce0bdbdb424f077fa0f32f635f01a07b97c8ae0811a2120278cd8356fe4822dc67219c2b5c1182e68b659c2a7e07764432061440d2a78788ea92e06b970ee9031b4f6a0dce039741d2ee67c680048923e30ec9640999d929d17e06ef7b865de226d86532cf634a623af5331dea74fae1967c60bbb4dabeb25ad44d73e8cd02a7429275382025ee15cbfc16fead161d68bf24cfe0cedc1c6727d9b15277a1b047f7ea2c8f08fd903523b4432292020223d99b32c26ec74af8603c2304e65db9976f6191a22f1fae2baffdc68f55a5e531ec8253ccd37121db7a871103d528a627cace6a6da4570e005a8929d5cba1388e70a134211b2c9e43b318346b864fa9a8f22482f0473dfcd133341de501f31d0924ccf1243eeca0ad2772012d62cf15f0c3097927ecc525238c6f288b087f85845597e4b4049fd2969e63918054b708ae7015e0b3642bb6c5639916e7ebc52405ae64a6a80dadc8a32a9a063636980041989a4f26a5aedcf6ca638baca202ed57d895c824d4e7c167598c710bd2e79e9f771a54e51d636154ef5f9c56c498895f5e98e419068ebd06f638591848f3724c2e7af1bc2cfad4d7033bf6bfe4818ebf02467126a94b70350a0b0327c2671c256e11e555b7c9d75067cd74d84643968a45f26926ac79586ed42cbdd3154462d7872e25bc90423bb4f52d736c9a60420c915956d032e06a7beede405d692d103f9d3e9c98ef0be340d36b41ccc0cddf57aad9ee50df14c3a1795bed732f2b9c96c14c24d1dca0052cd01ef934bd68ac205d894aef9e9518172232d499802c81d663ff9d1e6579bbbf2c463d48f8a5c54350243c38de1f10397e8a21ba78f8dddad362dcc99dc0c0248880ebe711d502ee16e74bafcb957bc670adaf8182d7c26e6a839b3f13198866b3c504205687fd8dceb006be2e7cab03faddcade80d6bfd29019a9662a6a2821c12452ab25886c0c49c5d3a21e88811b821e0d2dd8cb288a21c7eb35308a59008348e80a16eb5fee2e67b7990e1571a489f40889ab1d30d2eeabe24bb62e4204dee487feffa441512f78c5193c6291d1ff707dcc1ef035d00b9d1c1b6e171b1015c25fbdc0aa8fdfbdd54f7f43d6c2c01d7d169acdb7e2a520b2bdb06ce8cc26a153c70505b510a64f2ddd6f1099fa4929cb35b68f891b06659c7ac7d5f699f96b79af9189edbec23908a5353b0e1184353444c209dee8a76ae0c608eeacfac626e2835e6183392905602d55d527b58bfea4088f0b706a4f90dd3155d25be555eb51b2e67a0c2a2df9e9ee9aa648c376ab76d04a08d300d436dea27b3a188c111e60bcfeac6a8462dd7bddc0ee7dddb7c4046198431d3415c4bd36b52ecf4474ae1dd1975acfe472b855820360c39b5a1e63f6bb5ce85183f93a734a9b848071db293452ecd1bfbf2a739847718202aaab8861e75fc92773b66031400899f241a52c86bb0a6bb70216c88c14ecb0b03e2b5867454c0f4ee9681b2f570f4138839253c73639662ed4787a422738b3e911215a7ebf50678b2c768390984f34c025db7e783436a50dfa17bbbbea1ef79a985f83d3d6f1daa465769d01ae2b8b82ad21c9884ccf642c8ddb99b1862bba0189a51903899f013d0262a376482932ae82ab92104b0d7605965a75b7b5ceae2e6bfda21216d35e43854e57da6aba8a585f183f2bfed2d3fdda7c5ffecd6cb278286e11b7baa722cdd7eefdaabc8ec10c30bf2d36bf22c1e69b3727ebdddb0c853ba0466b324134f599842c95897b901bb1394059ce507d9b4538695154d15426b7d59abd5e4514227b72947d4a8309e34bb2b795344ad38afbddfa173f899aec0ab6ae325374da877a785eb74e5151d05b4b00e18e010c6bf889257063475d2c819b5170619a90506e9981556f5e56dbb9292ac6f56a8e9be2635f565ebe397b8f89b55ba5ff86486ceb55fb8e712089376f5671bb1939c6b64aeefd08c45a29f7f6b15876ab3b5f0320a395dd6fc4c5b45f7e7c05f6e5be3922f08db156ef562fdf66b5ff976221d62424b8610d575a5895759d4d8d43e57eec413f36e35c5199eedcb473a39e9fee9c4e679e4b027154a18c9e7342afaaa0e98c739b9a2a513b14029cb2034ad37e905b301616a903e938f3de942298474d14d934c4e616eb722e8fba832557500021aa572e9773c504e2d29ff250b0d8af6bf782403ef5aa307f8d8faaf698046ce0092436e5cde6dfcd2ff60624a622e2c91842c8de25646f29654a5206d0080f09fe08ac15279dbf4c901da5a6574b4d4857b8531ceb6683f5cd7b9335944274ddacdcb671cd81425de74d908ab846e90ec184d0ddddddcd02104d8f095dc1350221e613778660dce9de47c3f752c2cc52ad939c9aaacebba5d1348312e93c085f79eef369d8813895e24a9d3b0f189e73c96d9c775de779fcd910f314e4f32be72db87084f5d0a7c382288b84817a07ae4818a887c0d2b4417d9a38e7424fc7f644d77d624ce63963a8ddaa6576954ab4bb954ab4cb711ea73363864f273d312e7bdc270633fbe813e37e3ac419f29c73ce399944229144d36998f1335469c1cd6b04943a37054a29e5a8afa0eb46d706ac24a69343c486b624c94ecb88939a4cc9119d1b224c68b02438ad1f273db29817248c749992233af486090d96a45b3f4e7a64af237ce3c3840736719efccc7afe23e2ce7086311f117c6dd06d835b47467c43ccee16cd244436139c4cbccde63985b3d94c262c723869a210dcd133425bf2237da64dda8a36691db6c905145ccf488b555fad5146bc49f38c3cfb57ebae6d9f8e49374a5935e90edaecce29b2d9a6e838ef3aa7a3cb737e7d74ae9f6b14c50164da68977206c53f22eefc6e88cb793727735dbf9f86e390971286934e5c7f7fdb9c337da18bcf75ae55a2cbc431c3b4d1de9c0f7fc32012a67560b15decf61942a2f0a3e17f4d24bff47274a8499320e0e9be0d7b342d103af68973472d3973e3a9f70079a37688be8784a15e72c11d7d0639331970e2d1e389f3c2d357f3f3e3d17eb2e1ca2fb5e08e5ca98473db3f920b488c3a9cfe91c011784a7121f54f29e12fe0f4b9fc0e55e1faffa00e847a0fea3ee8464d338adc05a468717bd0d1e4a350f485e3bd1d72e95d389a1e24c340cce7207dfab6c51324d81d392220e831c65889a4f30378058239c64a142ae139fd5c829c4fcfab5b70eec09f011f6cde73907be00ef333d059901f180a39081c5d1ee4fdd15043e0e883cd0b014797afdece59704a181627c03edd450187b41352d05af910c20203b1c466e5030288e882072bea0d0e913f58f920a558d15089362205af950f36a8d063841691859f950f31363f9060950022b05583723564a60004ac950ffcc56a7292134161074cf05935d81161c5e07cae2e19282cbb63be7d8eb15118ea23559e4459b7d65d6ce6124eb54b9fb4c924661bc5cbaaae51deaa88552ade56af6b5d93331e8b20a736ac92b29d704a9a288bb67cec276f993afd403d3ff24dd31a6539f6232562a49dd02c87b4f6aedf566beb1b87e109e461f34de776477d2fec1a8bf6ad876697f3f64faabae6c919eaf4060034de86a3ca5bdfbad62abe9928eb2653adb9aebd75cf6371f38ee9def866e268b7ce44c854c8ad3309a46ac423966f79cec7961ff9669328ce43604b91f3064520e71fc85f6f9f8ede360edc9c829b2fa0e6b7adb72adbf8cbe93a46f16e0e46a12e43398afa1b23025e32f3f42a7624154f4b2c4e908bf99890f26d5752854ecb0746f8a9e20685078e052b25fb285d4ace729cb5d6dad15a66f6696dcfad79b4962d187ab6166509565ab6b6729c9a99bd10b2d65acb6cadb596ab4f2fb138e3adb5d65ae9d55a1b7a425249a5ec4f6e216992d61941c2e6e892bee451a2f6b24a294138e84034dabee5503db4c91a293b1c7df8aa84b72ec1760bdaf0d26b659f1ce610444ec1fa0e3120a7600d791b86e414ac97a1efa6b731e18428fc28bd93a00c146c9b53eeee2aeb4d275fba74b5d62a6ba54f6ebb67adb5d55a6b6dad9bcbcd6ed6d64a7909dd73211904d9bb829c90ea320c6d71a9b76c1f84b64079c14dc1a5de3e6bd0c9e738850549a90bba9a43059244c8637e945e788d0f4338e742a10d28241204cc0ae437a41557f39bcf9366c698eae356bd561fa512be86494897627cfb04e5cc21886432a28c9247d6f342e66f3df820a760ad646823a760ad465fc391967e8643a49b1a557aae038172c5711c88e37c34aa7dfa10256840a0708812349ccf70881876cfbec5ef1b34ce2de92a1624a55cc8708c79d9b576added2bbd6eeee06191c83c43c7b73e139b9d2397fc13bbc38b8d4b770ea0319882167a4dc515bf04070d9a7db177eb4d6724c743e3b0e7390ce3e379188038d4061080c221a496797610eec9286bc8263f5196e14ccc17e3bbc70f4dee33cea954e0acae7c220429e73f85018a49f3bdb855e415b839c863948a7d47370ad35e446765068fd82dc9cdc26bd0a8eb5ed47fdd2b0c31c1d24048e4184bc0ce23de7394897413cebd22d38d63088f7dee7205d7ef7271af260976efa513a7f1c72d275337c092980220a14178bb38443585c651261a7b6523179cdaa05af59cdf09a550daf59718d625ff19297c2212be169251cb2e225af59c9f09ad5c96b5634bc06288fdc3903850627506c017416c8006fc4ace04e6701dba76bcfdef578a19423212d2ec95346b2eb0778062267507c23917ce3b81b37624451aeb08c5c6802e995d43bc52b4872aeeb4fc746374a4321f649638c31c693c2f96291a70e1558e850217d506c38354ed9137a7f80049954c8991df2ad1316290bb45e477eeafe9cba28382c52efdc6787c30127ae08020da66004245af8122b08e20810ad0f10310aef1022ffec8bda5a6b3fa760c7b1cdb499fdc86a311f18cd06761363c9463913a5b596d2ce29f83907caaffe8542ea8bfc14f27c7e28bc35221bd81d716b92708116581811032cbcc048058a58c209ed8b2a4a7044d7b4db4e517bc2e4d96b4ce4c94f9f249af00fdbb4a5354e8d2838ef2407ac07255af4bc7e404213302cd0840a7c5cf07a228c279f0ea29b9fde957c70c75bfb5b9333fc6c052faae05a710c92153b45c17946c191df2e9b69712d58431119e3b8bbc4e5bf99ab152712840cab15bf992b93afacb8c979655a6171f93439f83a99358a69d863ca565d11f7fea4ccee3875acce94e95c702eb1b2264c28a537cd71f7deccdacd71de711cc7711c3806297d7b954fc10aa42ff7e1c885b74669f3488de338af853b30a05dc7d370eab0a60e0d8b3d58e413f52dee18a4f4363c51977f7f58e413bd3516af1317dcdab34f1afbf401d2aa59048962cfa286042f641286fdd6d8516ed86f8dfdfef8004b5fa0743529ec4293e2492c478a27300f94049013eee8aa39bf52638dd8db9756a3ea18f7a545bd86630dd85ba734c21a3b293e29f385355f58aca2f17a69bde4bcbc5e92704a4631abb5c878de7a2f3a6f5f90bc7dd9791bf32fb16f91b5cc3835cac85a7afad5224bf14991918da4560d9f166f0987d4f074434b0f34bcc5e58a468dda101a9e6ea05ec36b843dd47a038db0464b58634663ddcc73fd68634e585cc03aeacdafe4d627c029925bffd1a5b06a98c2ea9a130d0d1a61ca4da36ccaab51b682293a8db2294aba53622cda145997c0949e9459ca124ec95fc26228e4ed2550c629302596a2044c91b168ddcebace446a5d8e747d1a651d25c50725c507c51924b16e6c6e5258ad3ad154d24d9852b302536a336a2d78516b5e8264d3aa16b74eba21b138d5825b4ff1e1d4e4969d4659ffbc23b1b80c6750c68a091ce79bbc6b0134b5c8b4ecb4bd2d3b6f79b60fcfc90378d57ce65c599fcd3e324d38d53590a9b54a46d628eb1c2753135f46068a355d8b0b177b0c3233833e1ead9d2eee489ac9d478b444f93ced329c70db65105a495e7e3b78b446199fb7ce3255f33c5a3cbdf31de18e2f2de089a6c56b0df04453c3690daf59d17008751a618e8f8686d77048751ae10d9d3bbeb054fc5e7155301082fd8ad78f0619ce427291741ad583d4a2c11d39f8d2ea1757a3ac773d278a68f4c0b74c8d53356fddc94b9e25af25ae3f0293caa3f57a244960dcdbc6a3958445f93c5a3c5d1079c9c3c5cbf49267c9cd779bc7a305db6115a9c5288b8491bc5e128547eb088bb2c5218fd60bf6323e2dfec29233ed36e4831f5b643132c03154f2098a23fb0660cf611095927792515a09a76c07a096f34a228371ca83c2ebad93629ca26f9d2463155db54c8d53e30beb85c5a9130d34bed391306f5dc609a75a89b72ef323f3c48653202231dcc4508453ac841c4d0227c0a2f59417d60ef48223914c6d07fa638acf9872f3293a63cacedb1f3b3dcc44cee8a4c45ac5adaca7dcb44aae604bac6cf6a38d81c231a5f55dcc73bd8dd9580acd3a8a4fabe4aaf6f3d6c6e46a9544919974debe76dea2ccde5a2ff6d6b77034519fe17869d0002b87a70cccb73612c6fef0f685d5289b73c75a7beb33fce43f58b4ee83c5f185f5d66bad553fd646cec80a6ea76d23b55825c386e4b24eda21f1704af234e1c95b4b52c2b01afce8f98ca4d65b1e1f233f9292bc6de14712ec2d29f696247bebdbb4311f19a7aced236f69f0a367ed92b73edad23850368a56b732bc54f8c64e284c4214000f043a72192472f98f067773ebeda3f823e738bfc129cfbf14e806ea2317115122d4f907d250829c089494e3184834b8a3f89cc83907aac570cf398dcf731c7785183fca3c87450ab82b6a5cc7715d08c4cde0b9199c72e1c21c64adda74d2a5a3e28ef777984fc3f71df2e0c41d2793e9c3aa3981b0f8cd1aff00f3380aac672810d998b72c6efef492278b1864d15242275d46cb9af988f1d1aacbc919d7282f7e789e589b1a9cb6b4dc36b7cd8716dbca187794b35a75c2a9d116792e42c3dab2ac1a050f09b3d56ad5566bd6fad16a558e1badd6ccb66e107147dbea5eadcb60f4ca1672a6e90a13ea077e68b5ea8455929966645644668970ecc9bbd8160004608d98512afd7047db126fb4aaed9a333b427b4ef2d3371b5216acb594526aab531fb91db8b63451468b27b09f2fc4dc6819e314f3708a613018a5fc6a5575faf9a02ebf1b6ac85dfe1d9e92e3bea198efcdd39caace35c3b74f20c977376d529ed1e30bdfde2e83766666b0ba9de19530d3ebf0c483fad77c7b572487c725a65b741de300491b383820b6a966255da42bc9e1977399b6bbd0b9047fe89ad5dcab7ceac2a9d6f9faa33a90ea3d7cd893b53d5805c270d0618892af2e6b954bbed65a6d2ccf9ac4154dd74d5ddd5b2557e2d75a6b8f46d598ea37aa7b75b1f268c94a4edcf93d200401ceb71e420ef21e38dffc44130ab9466d0e82420edca44b65de9a743bb8e5ef705ce16cb12843c91da5cf6c714ac6de33f686756c1b230b362f65f8e55c794daeaa35d50bcf351c5d66dc12e78142d9a8f0851f65545c2f66d962f6f990df0ddc03e4aaf3f965c0876c140d47b186a3e9c747659d6842eeb9170eb13661ad358abbf9dd70030702b946710ee242e93957b3aa5eadab5a17ab48301b1b9b1f905898e459624369a33d127af1cc84f8d1ee60d1f3a3e5b1b1672b7b1e6dcf739d4d69f542ae693c07854340151cadcd588974614c6de74403a2bd0fec6938d92810780a8543e88dd7ac68385bb55675ac51ecf79a46b1d6d8da3cdb9b67db7a6e58a3d8ba5eec7687ddbaac4e8b7d82fd836903f61dd1b067afd51acb295574d6d66a59d604d6e78e96e59324666bced700e4c84f4792844749ce442296042645cc878c8689a37252724c639fc9d2c187e5c3ba01c242826e07d6e91c6195e4ad4bc2a9da2179f61a268e76566d21f9e916641e168bc562b12ccbb2585ca3c2b8a36521e1547bcdf951faf8d8d1de60a1f3a365b53835764a5846709e6bedf9e9b5d5c94646583b8598444c1b1e78903046cc27c0d8096d6d0f3b7b80ac420b19cb61734898952cd02c1256551e463159b2a4a7478992244f596000dba9b3d9ccce6cbc16ce8f94b2c636d98ce65373624df0536b95d5d9720471473bf38c702a090fb7a205a8052115744eea4bc7c5830b2ecb0b74e4cfac35734d9b9f9f160fad3bda598d53eda2d96c6667123603326b550db3beb4990d7334e18e7606fb51fecc2cab553aae97ce0b7645cf8f76f6ecd64b62679c923c57dc3cbb5dc2a969998c449fd75d91b5e1900eac6a589b638ae8a4cb5887689f55903f780e41ab84f0556d961c8a823b4ad88c5363a5d1268e76f6ba04a71a516139b8986be256a070c74aabb4ca842b4dda6963d69a51c4b021c272c4d44188146a3d9c32e9e0d2f9e11f2cb23579b7a9db9e3f6611b41f3eddd1cc39670f169925053de48cae58a19a72f80d13eb8e76c7a4f3e3644d1dfb6229c1ed649cda7cb33be19c3b76a755b365b23b5b38595e0ceec8d99de79af39576a2816fccce1e4f1630d702e703f092070b257cf792c70a24ddb1dcc89922a61336c938715c126e67d3ddf80c7ffdf49d5e990205a272e1c376a50b76715c95352aa68696b54a94b12cf446ad7cd1a45a1f4eb5e5569a805844e08443354e519d9f5e41b99a3ec2f0d20df0d22918239b383a8441da603780119e55cebc9ae0e8be42855b41c9a8b1e274a2b1667a172107a45b087fedbc7f747bc87330d7d0635475543d81b5f94f36541e586ca73cb0a812855b7fb4aa0646b173b5443f8ec9adc1f39072174e202cb2cf1abc677ddb40ef4b2f9d010b6051fac9864a258c748f45897204093349af677f74ad9bd4c0624fcb4804b44974991df2706a3f7568437d1e589cd31907373e7f14f26310d38b3ce5db61e428fde91085a70b825f15d73a28052492e241f82a2554e25eaff7de906331f458bc63cda3f88913415480bde4a182cdbfe47122fbd3f5f9d753c0d30d5f5889dfeb722c8aa3114a78e2786011e4a290b2081a8dc4b757ec91bcd72fc78526702a01278f0faeb8a3c3c3c138c5cd786baf4b094a5da7d66f7784fe745c5ac934c35d3835ea46a0f40ee47c7e21aef3c0b1e63def79eff51df26f7842f11c38bfce398a87bce641e0e83d68e428f773f9897c04d6bc080ce2fd47bf03715306eac28ea80dbf2a40be39751008e49c7bde83c68777028536d4073928f45c38e2da2d64edb679f520a607397f3a40e1e65bc843d392e9ca2ebebd6fd4b05f8869970e4d2a04127eaa983dca4b29aae0f96e54b9960f5cebe3c9da9087928ae358ac6ebd6dc8adcce0ce97a78dc1d3e6349c36afe369933f4f5fbab556fa66dd3a0f95da74d62b4f125a2f9fbce4e9228cef6ea97e555cbab9e55803f355c0b8ec20f7fc07a738af8e03100f942c8238b4a13ebbf452e7ec9ccfae3bae731a4e81e369fae6414ccff9698636d49f3e431eaa6fa0977347b1ce502843334c2a397701e28d1a38356566e01487d55f8841408e4dba8bac3969194e5c4adb85236ed76ecaea643871db2f8fdc9cc1cedcf3a91381ba8c9dcbde2e7bf4311c1cf1f2e3317a9c15ace0c50216fce0e0bc8a282236fb54e577c8ccdd3c27f3ecaeb5ca96d38916b4c0070c9c973c6014f996973c52b879ae556c04470a2ba4f881109eba9ebd4eb6a9a65bca188bdcdd9486b12a832561e61b209e5d8509772c3d77e318619147661961b95e9c92449ef9872b99a83471b91c2c5e669614eec8ac9655caaedd534adaa32c3db55652c9cdade791591d525797050cc290420a4b2ba5b45228402a78c2c3c304131c16c278e2092490f8f9e189134efcbc7e268c4629e5ea4fa3da69f74fab2e779b0b5798dcb1fe7cc37e66cdd164d71a7b2de2d97f7e7e7e7eaccdcfcf8b14451249ec70519b024d66451821a6de362f79a650934478c903c691b725864969a4c5627b1c239f3b79ee2e9c629d238c6a97371485a70f490e0f121d2a9098220858d428c12384124018a2964384c7cd2e8e20928406c5932d6690028a25a2480210293801d310879cac1c91840e64d0040b9b2994c0c54d148cd0e4a6470920f082a52297072c340173a20b22787105058680440f7e70aea07d61c3b4b1f98d9330121677824002048b9c2370921821a7e7013f3e412809430a4558dd08ae7fd734314416c460a266031d0cb16aa7e5c0092b9e00c4133ae8c1aabb218a1006153a70c2248a55fbd46915a9083f20420e9ab8a1e960d59e92e48ed3b542c51d4d23b5b45a50baa66bea4c17c34427e85c1f9d1c9b370e802346145992b863a5559a8b8469af3400d0247c77b3b86e8b532357615d5bcfdccddda29556699d6bf48223475cb56977d306420c9a68024444c605edaf5004a538a0b5d65a69c541ddb66dabb7a6564ab96a69a594560a85d79f3703cab7fca35b76cdaca8a52509d3ee13870d55a48df6fef631fc6eba7284bb63ea16bbe8f2a7a76a6020303e252ebbb4d4443f257c14c2d3097747c944af8cc59ad9933aedf9d4da90c5da755a20d8979d31ccbe7ace704e5f797273b0ee0b8564034e4a9c3bda57ac53c2a9510619e44cbf74f0d37348d97da1906cc049296188e8bc5eafd7cb5fe2ab559c7dbdfc258bdcd1be6a3f4ad8cbe6b04ab2ac76e4673db3ae1cda111f24b5243c4a24f55c1e10649c2c95f807df214597176272d4008416243949a7eeb499d04997b1694fa92c75a05945a31c4c0313f9d1b320bf40c2b858cc6171fa37625105350a75cf47eb1e8b25076c7ebb542a954a25ebfd361c876460877c296168a86f4329614a3e8b783e8bc899b9d348587cb1c8e2fcf56071fe74949aa704f606b612161bd6aa96318ab6ac61b2a79b97dc3a7f3bb630891b5fb1b492a7cdc3a2051ba66244c39efadce1d4d8369325278e4aae3cd79948c053bf38ecd764593b59fdb23e7d6cd75b973c4c7cbe5d9c9ade473a8776dbb4ca6f44d68fddb22c201da70870947c9c2378ea964e224f3ddad3dd1b5c2b431629e520a60dea54f8916ff8bd90819030d455a6b863db340ba76dfa666c9ba75df8541fb948ce7c4e7d8a40ce8c60e200959c3a0258c5e2a41e9c4a7114bf4e434edd9f989c583a4a2663ccd3511cfd75b880c46de71ded2aa1929fce225b0197fcf41450b288024ebf60eb278c075a709cb59f9c87a68360d0c3c724328b4c1cf3a9df1b9452d74f29616c38f20b7e4e1c1667d35258a5a7932e9dbcdf107e63789a80a7336c66bc5dc093a7dee30710263ddf6de06973f9d693dd51c268a59556ead99c1b833b5a5617e394f5d7ce0c368b559611b0832edc7a250c8b10a67b6fcc0dd3bd59dcd1bea0fec8e027133774458cba02d60a16ee567fcc6c7ecc66f2c70708c6db5cad405817788209683e7caccc2e4b6e03c9c8a22e9951e0fa7347dfe9da08add5c3cb7127b8218cc3e9a4cb288304c09cb35ba0790123bd2a9d3dab21396297dce047cbe485f5c27a61893e7e907048aeb72d167374be25a7a5a5c5a26dc1f996b1c5f54b20791d7961b1685de57547995a8e96a9718a9dbdc11a3a2c5a99279ce23087fdc6768de67eca64b00c192a30ecc4e092a1f2c27a69e9d4a045d62263928e8e96a4d75bd7d1b282279aea349c463884bb4f5ca681864c6db2fcbcf54ee5a4a2225363950d999f1888c4c08a01a7894d0c3932b5186e623012438bcad462d079eb2adb7c69bdb83835ca68bd7d31c2a9b60113e34b4e0dd7ef70c3a9c6ac51d64f3ea306ad51d6b3c1a5e12776ea40d4ac5a9c067862a7e140d4ac2abb7bcd8ac3d303aad770e690a6c53d1c42bd253cb1cb558b0fa1e1353c710b6802656a1e923bcad4688072556984b25161147794a9e17a21f35b469db73590bc75999a9cb10788bdf571a6f5357c8cc1e6adcb1a5ebdc64eab7a7861b965972d2d3dd40865a35a646ffd06da430dc54659a7a15c7118b251a389f32bbebebd134511f5827a717d3beadb41ffa525a58d25a48a1476368d6a4fb9369a40096de87ce847c27821036d86244c061f199ab08a262603eddb464cc6cd1d654f8c293369c2a94fec7112b3433eb58288153734b4d1f33696482b8ad898d990d988d950c262c7c8b0d81e130ae18e2f2d9dd6bb60a8174f728a654f481363725b3dde9e71a9482e511c8722ac228534b246b5f70d4ad84313c19db1f976144ff1b173cdd8b08a46c9cc0d7b1d8ea5191b49130b6357be1b714719d90d0aefbc64642f03c8c01cc3c78f84691fc00b9ef4eef3b974074d8c06f1390914a789c91e99e99afe05184f7a47001aff35e146008753a71b08a089c99952045a3e7e6ebc8475301e0c015e5a4a2b897547c9e406872238dc509a8401b22361da1904df9f7051a650409206f8c819cfdb15b0a3644a21ec3c12a6f0a22585208bf142fbf57604b0c1d9f064abec4ababf7419e3c90e06ef63428c046212a6ddc74f0b0c8307c3a7636bc100defc76809c3d8ca263e1b0385fbf04f5a19e279fda888d366234b119a7aa7f3433a65122ebe154751a0d134ed59046f681343d2cb6fc4aa384459a253434161b2585869602d2cc50401a194d2cb684e3705380c9bcc3fc9b31487dc57c3af885f6d3993fd1e8364a0aada492ca0a8bdd4c325a38cda051a3c5410e14bae0028000840420a25eba01bc0043001b31324624250cbb953d319a18ab70c06864344b687c689c2c6172d3f3e45bf6f47cc79a30f951f634f9963db5ef9e9f6f2b6cbedd568ac30d0e4538d54d780113f144c2042dfc12d5e3eaf2baeda38b7b9c9a53fc90e7760b99efb56a1872ac9f8ef3d34bb06fdf9ee3d48c97524ab744bcd24fefa794346c4f43707679078d27f9845e34f4fa7eb58fa15748f2f1e28e765e72a6d4d5104493332cef792558efa0932e6368236b78a156abb650c41372a1d6c7c44ba7af5132013df1ede3f7edf3c828e2f9760a826012a6bd82e0ec7207ea489876108b3bb2d466b8e4ccf4a64cfcc852fb0675dc8bcb3ea6d4befd24536a293f24188b6bc6cf378d0f6d2810af6f77145ce9326e26f50e77c8671904ec903f5bc9789aa157923d97452e94e23a0e9c3a31f9d2b79b7eb07c7bc80aacca7ab116a59e36dc395fd65a724979f2e4c9d34b2030cd909b9553d427575f7eedde83500b5f10ae0c42ebc87f4bb82e1c7143b0108cc5f63fc26540e8a983e257709c36847e0b05f01ca76a7eaca93e9f476be7c53c5a2fcadddd300b8311955c4b678e56bb1de18e804021cf743f91e8db41471f0d9fcdcbd1746fa93b8e34e79cdc5cf298c4a5928aca542195462a2b2a2b2b2b2552caca0aca5d595919ad88be9595150f89ee00ace764ec0b79eb8c51ed2d845854267286bdbbd637ac6355219f5459288cefcee6dbc7eec6c7d00bbe3de49233d4bb396faf4eeacf13cf6b56d365e79e0f51827d88e75d3872349f775f58b39a5e9d716a89e4641090f09336613ffd00370718c1c4f1e9980760d150c367b426b5d62819a7ea8c6b153255b79062c37e8623cd2208df4e41d783c2f18638be2053b730356a367dac3fece305bdf4ee4ae13b06828befda93917af05df566ec07232f633f40f10c9e6668e353df591f279bc7ad2bc09ea92061da3b8e06efb9e0bb90c7600a10222885f1a16f860c86bef8eef3e6cf936f174991a83a9130eddf4f50c68824e177d8e7b03d17f90ddfd7f9d7c3e721b9e06d54fbacb50fac352fc99dcfa847c9992a0509d3defec22fc413f603f50a350973a7705d586c0fe42e143354639d9f1e72fd74d0596ca7aa1664f58708cbd68a7cbb2be79b7a71851fbda639c1053fda9bef2f583f5a23dfb6655d56e79ba777e7e0dbe5e8b9cb48d6491749ebeb37c7c1fa8188e37c3c1c2dcff33cce455c085445e0e713fc1c3cc04dd7191477acac5765759d144946d6a78fc0ca84c5be619df117ceb826dc1aeb1a33d2fa3eee45a3518efe40912803db8b2a0e8b5d5b2c1a71b557566de170ea4b1de1d407561c17a7a4ac4658eceaaa3955a79bd37114b0ba1ad57ec1da6a54b33e2eeed64682d0dac72020b8be7a37c3b6598f3aabb83ade70188712bb3e8caa2e8b898912e6374c31b7c5caa81a5389e252679f2ec2294f36c882fef3d3d1d550038b5e4e0d9c8ac51a25caf0b9db0c610daa36c2a8ea9f4b4e957cb4e2a389452412856329e4da088bb58d7ce57e6309c73bda74a09f8ead8d344eac559cc3a8ea957362a2d81d67cb10777311386312a6fa64e9d03131a2e86e32dd5b4be10dc7fbaf12bbb4fa164e5f400f1a4ea578751f1320a178f5209c1a79f58e045e4f998f025216b71bcad18c7d205539e2b24ff768f251d155f51a38af049351bc767e7b978888da87dceba4d39013188ba1d09c9e177ab019db4112621d95bc82c52f4ec231eec1e1258c8461ac84653c631a08c7f5e96890e79d3c8f08ab648f2ce4b308a75af4433e5f49e60ecb62a12570f1e3c4f9d012784234144af221f7267ff1a1c6c187ba3d9f20279a07cd5110c841e0e6dc711e084c42e40c32780a65ece8b4879c5dac1279888db0187229237693b8f19528e49b19724b84c590ddbcf979362c865ca5883bb120673c0ff9cc02f7a14984537c856923c4330c0913729529eec83a1ff290b34ea87a88f390334fc8bb241f0a790776fe7d9ef779e10ed744561839c77195f3cf455bad278e736ec49946e054024e1e1fdcb9c329501218e795ab951b85e3bca1ccf9a872214e954c33443e1281d243e0060a85e86834f21df247e1e97a0e23df7ce49b5ff79e1332ce17f91d71a2ef0b39078eb46b2f140281f6d3c16d73e3363b848e3ae5625cc45a6f18e9a6bd8009f890b931438e2354b8a34bcca45f53a794a70c8122fd46b3d6ae9b10fe199eea1409f9917e3a4035c5852390326a4e3f4d6f1785b2e6e69bc5aee15653e455c0b863ccf420de538f610f1bf439c8bbceb74f474729a55e6bbec1d113f2e437ff44a0f46a7e8241bcf7bc3790e7e80ea4498cbc8ebc7e3a4022ab46ceee32023f1d3762742efb1823af53ce2b9966bc50b2e828139e9f56bce4494213dad1f8f02c0e2cf2b0387ba68c1e7925d961915b392e1e62338b501c58641d58641527f7d41ea5d64ff2443beca6b3d63aab4f7b342cfa60916717724e6e2d20b8d5b7ea396c01a079a8540d1576494d992292000082200043140020301010080563b1582c94a9c2ac0f14000c91a24a72489848a3208851104386186208308000036004446664463600806dd29e44fa2773a85c0e22dba41633158dfd1caf17c6152909806e6d2d0b5b5cb74e3cb306ae3b05ae8ab11ce70be447d92275883ea948dcce501813caf544a155e9bcb52eb1a53cfe8195c3fd41185926a5a7789e3aced3112416c00a367bf84ff79208702a72a5c07a4da403c056330a149a6172111ed112bcea2461c744bcf7a6e00e89f0ae71d806b6132a32ad3af96e121552b30259e1fa3e42d2a7c1480622683f6586eb642b35c21edb57ebfeadb8676afcc09b4d5b541fb973eea9d7528fdb7b063c73e2dfeb3716fe1a542c20619ec1ebf9a925bfe93714181e394a2d433a82ae4578eb357d8c38debcd05aee2263ca71f1f22332e5783ca9f452b97cec15cec2bedab52bbaa612e9b2c251c90b51fc31d3fe01aa1f52cd3b901b7c68e73cba6e19ab578bf180ac45e23f77130c6755fad7f2391231aaebddad6165767038528d3beabf5707e5512b5f8eefab5183405c83681cb75f456e7f1bd2e5c12c35f8284670acfb7179c1e0e0a65b3f017975eb08e343484d9cb442054f9c0b7a89eff2d913d36a00c5d6d3c0068b4178b488ba634ad34279497e83336001fc25c64aaf84be90376b6f13f45a4778bfdbbb56e6c2931183c5b56f605af24bf8341766197bb6b0e25a69d8dd545dd9b7a469a696f9af5bf701b42fe70522f6759722d8b089399b34535e64791233704151cbbc59663ea50935a88e5a367ac0f3061bb6fe760c05eb05c65fdc1c5e1dbfa5b640a93ad7eac058427bd856df5d9c8973091d5d24911d578a51778261faca0a309ef00242d66c145f493c7d4ef7b0f6f624ccc9d0aae53a297908c57c6356a0a35187fa19e9c0ac0cc3c825bce854067e13c1084f30b465c6a8d9d990123fdfa90b3753ed53b3e302d72baf8eff54112cbf881d1e43bc8fed987d85bf09f79bacf6c72f49622c17398708af0589eef249333a152227fbdab8bd9d318cf8965fd58ab090788eae1d59fb914772ce82c4630b4f50b6d18df5537329a8605248ee5c41447c30b2f9f6cd59357d65e142fcd8ccfcc50e9d679f21a607642b727a01ef40f7bac77817ab1d0d38370b34727c6e1adcfb14050f92a7959f7c037900b5782de14a2e1a6d33405bc4ad82dda9a04a789915aad39efe143d30c3b8fd422301917ccc6a1d50df21909b8c2a17d54ee7abff81acd093233afd078060b56d312b5af4955211218d352bf213da89800f141f65ba9a571bb6b2c0395bf847698340280a6e6381b52f22a9ccf41674196a5009d5cf22182fed218d72a4a9191edfd9914ced190ad96b1298cae887d393a9b1de51263cd9636d12e488713a4daf8a121646141a2e4d8c23edf482a29f351a4faa997030218da670c0859c6ee99d0da2a13919789f500175e62957dd8bddbc9eb3d3694581d268877ac19ab9977a4f6133195e0d717aaad67097e934034bee1882a12ec2f978d81909e0388ea3e245208869311f8314fdb6227792bf4a06ac005f0e455d099fbec983ab5677a949c9f73c5031d301c2a58aa84f8357f194d1c8217f9377ff68112422ae9e735d9b4f48f9e3a9d29537c4638f5a7e2e898cf0c11c37a8f1060cf3eecc19539226d1fbe612037e18ff4d9db1fc174a4d872ed94e300923a50b7bfeb84fcd2e9295b6b1ef594585a970e91ee3502768525622ce2c06fc4ad90a8f0d677cd6ecd49f75a7fd30c07a7fc3d1452ac6d4e06c49763ea138ce3a445e70d6681d5f0eb027de39fe5b0b7d3008e1d76994cc119fc3a6b1b95852f2ae6e9f3be829309c713b6c77df0abaf1eaab364ca32f47853ccef7d2625836f699f520853142924525423318949282208838490abbb991db960a0a71f76d88ebc5b60a060f018232af571d9a623c3eccc812ad043edd645e82b25659eeed38d08e408c1ad3c44f433e3a8304f75f530baf151bc3ce9ecdfdd1a04307596162a5632982a0793dba6ad19906ba5618101a6beab790e6e3146090f3342d7f617da94ba44471df5ee20057218fdd63aaed089751de520673a6f7050d4419c88be0060c1451a84e1e46e9f492be509177082f9ee632b31c7e438a7108ec998a763a759fcc8a2a2823d37fe92a3dbfbeb934aac1c115649d52ddff9ab3f3ad63cd88c0c8aa8949b1f1a598644cbab2fb2ff81229dc9b6cde1fab4114f1ecaaa64f9d88bc46fc3e4bb59dee95cbedb8edacc00a49dbb71caf37313b5dc5b324108619bf1bf93dd71e6e00b63d5cdfcd25b576044c3e540e2a3b7243b73a4ff9130f9199b89907b94c9f258443692ddaac6a430f69635b71775f8825231ea0b391d4b79c3a43091506453f73b113e331fb0c4634fdbf9bd1bf595b070fc308abd52d0e3f960eff20eb1774b012cf0895fdd354c1325d3a0ba71185b04104bd55f32754b4b8b07ea23d675494cf9680855534ac3b3f56b9b6569f3fd58747ed9ba82029e3149713fd20f52dfe61d159f256277e6f10e9b7b2ed58ba3973b90bbd2aa12e4f2caa7852fbbabc67a126d1773932f0ef9fab629da27cbf0ce48660dafd9bf53affcd7902d7d7b46cae077a52aaad4cb3ca6b8b8d83b9282528e68da68bf3c829475c7f9e9bae587729e71867ab5369ecba26f97b6523e045ee19c712db3d8a62424915f5b83386c183eb7c1353c8b3985db1d1eb3b32ce9d99581d8c345e563ab144c067f585f087e5e8b4c6a0c916dd1a8c100c14c6d93a7375f70d634cdbdd79b942d88b51b3381fe3b7e406060716dbfc612e90183ce6087f45719616900dfaa09d3bd5895ddc8ebd1beaff6c63179e08b87f33515afca036bdba968b71edc85853dc75025af8a879d55e2e9f748a71fc4a9367d0625d77402b393d8e586b7d81f8a8a3dd5473683145a30afdea6d9e8269f6b5d0b18bbf23bf41a6620f49a1f1cfaa344fa51351d68d5c7c53e9844886567dc789d36c406cb283f4461b10b95036088c2cfda478221f38a998af5de2e40fd4f9135f5639c4afa0c405e8d42e7a8e078b0e4ea5c3e5fe47a599e0d13ec36e2b3cece2697aaf5b28290e47d2d6e2d001b79e64d98e690a06918d435202ab28341166e9622766b1cb8d67506ede01d5553c14253d8f92888d191663d024c37c7d7f90b4fae9314eacbc2230371cce886b89cc2fb45ea977374e2a8b9b6436bab15eca712768d71e0e22c6a48084ea2049c24bed9f115935d9ce615c0db01e7f24e8f13eb54498a4e400342bf569f9bd948f9552e8fc9853fcd5f922a27a6bcc0efbcd40d9153ae451a23ee13c102c49bb5d9b5d9f408c08e09fa6c51048ae172fa5830aae948f68f22d2075906f9c204e4398a2302d38110df827705b49b0f315b83b1a6ff527a543431dd30ea10a106cb59914f372ed69045f3ea59ae3908841befbf356dc2bfe173f8d678b1d993971bc6fdc972bf13877296453769d7e8a21032dfc1bc35e4f03dba7db96dfc78fb360020edfe756396a57b704c21ed69d9aa98c23c59e8001feb97d707b9efcf7b607ffd994cbad633b432621814fe3ec762af0a65535ceb68b9cbc1b3ea643d14debeb1d353296f13d3902eed4391f5bd44414ed8cc59925011f20eaf0e33bd71f66db5b4fc90d0f4873e9a2a345d12dd5cf7b38aa46d0b89315724e5917ca7f222ca9ff4b34cf486d36f6036858f9519c756055e85dfff991e67f0aa4f2d497aab7ed30630dee796d21d866523231242d6d3b653c21abd1401fb52f1fd8c4f7850115ad69b6f6a54d54e882dc7e87c2b4a39beb7a742eaf3f5f009d3d514bbeb32173eed39ac762c2a00542d1756cf63c4db4e04ba6d2fbbc2deb6ccc71db471618c320fa28dc0a7c6c472aa683d6cd32e0eec9f18b371ee26696e6fe6eeab8e9f0f1b40b2250226af5c809b186c83f310f941130ef5128a0de1907756582a84692cd929bb23283fa5f0902c1c55560311b610453e43154ec06d9060dd2b15c739a438ab6852990305b30837f9c6866c769d6741272b641dce28014aa6af38ec342a141c27cf9d654d6e83c9cc44d6c6701812c7a9a80cc141b18a4ea7f9f76b8132218a16353f48c0c5c30b2b7b7a1c3bceda682d4d4a4ca9a67204267e7e1f84a039f1ed47be05c6c0b8da30e59a1b1e146c22c65b8ff3a7379f82b213516b161b20891738128c32a95aaa2cb867aae4a5124203cb00d59bf50ec08253f571aea995deda13773ed72021aa4a4f9f89cfd350635be3af82e5b2cdcee8167cee486133cec6e5330fd1adeaf3616b0277bbbd050c593d599438e2a14be520f61f3a51bd13e80481a6d72466527b0b549cad304e45b365fb6b910ea097bbecfc220e9eb0ddb930b3a78236649229b2e6735cc074f1e64811e1ae31e392beed12507ac452a1f796028ef5871a25a0fc3bc9b05e60b5da18fbb4e85385d97cc902fc6aa801c37f2df29436fb2af3c67c9fd4ce958c69650fed33906189d1e3a74bc61c77e91a676bf92382cc2d9312e73fdeb950f800dca034944f72c0e4a3bd753b240c0d87d55b32088c5efbf028a404d29c94ac9fce2daa53a4a8f4711f3de89eafa3a439a1bf557c2bdda67d24c82554a1169fc417494866412b97e7af5c43cfd6c1bc6cd55b6735e5f2f44ca086333308f5db8b096c7c8c319a6a9481748222cd494f399bc66641163e6fba8a6264b4560722ad49b3919717886678e1f1e652ca69989edf13fa38c0fd1211c87abc4c96f9ca52b9c92e9e5f335a36390205f0fddac9bc44a0e8a97dfea2afe71dfc441110b3a83d11da0a6d5a8b891bb4020d1db470238c23f280b44178c287b73401d7c1af2b684da488b575a7c6adc164fbaf93395066ee58ad07d1fc4136c106bd6e2e345aee3a26f978352995b97e2500de752879a997da298d9e476a456167350b26e9b24239853b825ddfe08aee2c78fe60384b81fba91e918281400b2636df61428c3dc3d3bf00596d2ab3a1816a06ae06176cd1520bc8c8b875bc3bf7136a2dae63d94bf8ad2b886b6e315357040606acfa03496a67949ef8870d2919d022831b8a57d0a0caf6b1459d4671e13ab37463a0444653e16b5dcf0242166527643839d6aa2ae70d21c0fbd04501c25b938345ec7abd70c06a4f82f6fa0d780cce16051f21b2f96d289d3416c26129962e55ad565090b4cf094f9c3e95898d8f00e9d278fc1ac81fab2aa4a0b40080886b49a8a414f4cfc30d04a3faed1655cd222a771e1cad3f6a3651fcba3bde94462e3e4e5f973996c659117fb119751fb788a123f53a0a19d4ca8092e5913782300d5fba1c732152b2d562f3ba5a4410727c3cc1744409a94f08b4c0abb2e6e07eae62133475ea2cec0283a1f14f60fa1a50f599ed1a130d27a426cb2562e0e43fc75d6d21f1d366200291d3824ef195dc26b53a92df60ff54580d27f5339c561802c5a412fdbbcb91cfcd04e3f50aec86449ff6ca4b37afeac3772bce58a279522cb5c1908a676d8c703d1d91607af044b1140a852dbc8e1c64597dbe261637a6c762b924e5c3726a097777f1f4b571d40bffaee266fdb944f96b1f817ed9851a144e78c5f0f9d22a74e224c69cf8617c21f0184a731c9a535ae503fb02eae8453a2f0ce1243ac9fd79edbd77984b2a49e2fe00b173060588b16064ab7ca2b6cc5453912494cbb34096df06d56ad30189194083065991f81ef166713032e9dd7e6ddc0d0903d0d38985e9ce3020774467b49d6b094ad96402a798a642046089c3d3c7d828ca4129a448402f92e6b22ce2229170129863d1efe4afd1c0c5ac353c5954e2592f26ddea7cced15f985ec85261279722a076b8e4371762e5a25a31bcc8728851dfd4245e6e29162d4a2f50ef67d7f0e440dc543fabe22bcc7a0e1ff7b9d4c08f480d823eb5e3ac9d7c23a57816c3bea792466ef6f328d7191b9c4a81efdaba3b9fa8005781ff6a4191af2064dc4b91ab69c07a671ceefc4ab7563cac7443b034203e4f50326e653ef967123b124ae6e79163dee8de85fd572529bf82236f73d4db4806439e97b25f95e2f2cb1ea25b6369345a1c3ca48a8784d7f0d9ec491a4c3d705c1240eb8b692c031b9b6011a274ea9c0f3c766955473725a19fccd77b21720a9031838b9f00c9e76e0c11600c56e2d522674775f0c087f7c310d30d9f40e54a950d431127dff506d460d8666a45bfdb46a3342911861474298903e74331cd105b9021e1ea40df1f47224ab6ca011420e04981af16ba73a9b66ad5553778f658139c016038f8620ee6f46f92cf8bfb4661ec66b1152adab2604a213943d13ce92c00263b34176e36bcad8855b184acfe8c9e3b50e043613e8a6d38e98e51f9aaaf3326ed0e1ed081d5d0ba8707d9b90a13861d37d40c3d6624f3183119c5ad69e790c90f843eb5299ae9c9c88736d59b7ffb03b6dfee1bc7f4b19959b22180adffbc7b776815ead8d6a447177fa69c1ddeffa3fff455c24c85b372778091746d71ee3a1f386d28a0e69801c44e5f67f4468c0222519450b37017e172c811162e768cf6df3afe63f20ff57a358eccd068a007474b98fd4c78c6c58ebc1cbf61f0705df118606b48ec0b5d6394bd57804b7341237cc145622c2aed266d729ac6949b3f9604dcfc7eba7ae9ac8afa5e4346bf9ab874a4e0e9a7cc34573819c1d8046761a30833b09756f395480ff365dd0c3e9bb982e18bdbb33401398d1793347eeb9ff892610025bb79dea8e1cbb3166ee38502409a946423f7bd2ff058319e3d0835dab393420709dad177efdec95e52f0367f944b10c3b8cd4169fa96ff9b07d523853f0bf32d0131e5870ae09212638966c039dcbcf134e79e480a353707c132f06bfaba023f3326d5130e2be966754ed04495ca80aa6c8405e93945ff9d15c2319af4283c09bf0765c6852ee6e36283aac54aa382835b3b14071bbb2f1cd7da3a1f15106646eefb73a90299a3c68a124733fe50fa56c79ca137b5edda7d85de74edb21b21f2ada13f2c5c2db87548b140f9970175a0a813112e4f5241fde11f4d28993c68ff9257ea4b73ec686cec0c6d8894924e099c69d663358aa6abc9ba7f864e72cbc37c5ebb04d6439b4ae621bba7fa7d338b75bf90cb770cc48d40d16dedb2e6947edfe25605c39a96f24c15d1849a88957af5b1f88510f624080335c0e537db6c30e68ff42c56ed39dc2a7f328f37b8dffca116a021633304789e305cfbf0b5b291c00b6ac9fe3fbdb6d823cb7d8171c2899f46a45066fa4867db2a6d74eda8c9de0590e06080441ee8be9a12bece99e713a66290766f026d3bea34f80d126af467fcd7d7b0a2e9017010d1d715e7eff4251f0ac7479eb9f78da152227e1458ddffaa4dfe0b5e52e474b32c4a1784d7c3b92de8dde8ee2a160dd52a1de6eba74a88892b66ae72b7a928eadef3d4a6b693434114bf0a2950d5d9bd9a14272902afff1acff0bd4b153f72368f32cfe05f3c6f397e51e6041b5c7671f944a496ac6473657d6662b823bfcf4e4ce10c1d0f84c739a06f47aa87032503e61ef8096736809a533330a413647c5e1d15843a706b7a49704b3c65262473946e8c4d2a22c19203b5e37925b6e422a06089bd7c1caa9355fb53ea4e1fe9da80d5af83a2d9611579e77cae9dd73c9188bb704141f5122162b860e513e63b5213720d513e66d8dfead3f498bdfb03691e0e0c4df566b8d020f881ad148c89d19460a22e43f16263657b17c0ebd91c69e46985d133704a622a6f4ad37bb9571754cd3e3c2369092fa503821014444930b84581bf91302bc974fcb32fbc9f7ca58b16a2932f60f4c2aedf916514bf428cc0a27f11cd233140a5c081c50e714355e280aa8faa2594973d1f7aa3faa2353cfcf2a858b021ce1be8d597581108c000398afafc917bb34d701009495547c381e31cbd7f219b8d208cfea943ecef66e3296f7c8ad81432a56de42468c0705a239088912dce3ab87d9ec6ef9b0fdccb9407c81816cc418c3c09211259dee2daa9907f0ccadabbcc5c5263ae060ac0ebbd70562cfb7e6b60399660275dab57356f5132b1ad23c6fd7046cf18d13ce49b84861fafaa8f77c5d1f8585a789577c96e76508c9da11e9e769de9c5b53eb7444f2152e20a0481248396b27cd88304500dfc0a7d102d055b1c9069e7c16e6885423104c7657f6880b866852c417ec62343b09f2f76997b5f561dcbe6386aeda19218db865a9cb5c5cb4535c61fa2d8521e21bd9b7b60b137bfaa611d1e2f44c6aba47977acaff0c6658b8aa7a6fbc1550178632f68fe10dd867a596fe7eef73a00a0a22c7dabcb915f5c7a38f8a46a9c1a3fd5e6c484bf41d94c49a100b26a66a377458d6c2e22931401390c2bf428f6e2be2aa437349d1c06eba1904817dc58f2671cac6903a352377e7d0adc12cb0cd0806303af1a561905577ba5eecc275910825f7350cbf4ab13620a133350e1d8d40186eec111a310e38546c89bd0c0eda5e0e75b04e1c29e16048b3e059a07fc2761ac71cc5415ab75233b401ee5c820506df9864216eb5e090a9c6cf57cb8175fda1bb82f888f6cca0a302c7b17b2d16c545d05fcf7bbe34854193d423e226fcadaf8dc580eabdc13e4f704ee8562937a07038a1e63f92f32db83e605260e3c9cae1932eea19d76f87e1202ef5e71f892c03b4a3fcf008305a24e698cbdda00aaab67f7bd1c26ac9a7c5e4802e426094d1dcba8393c99bc6d1c6671aba95d462df7b16c68ce4f8ccdc6be6b6b7eaba04c71af23e87b91425184852e5d28ad4f883ec1b33f74f1aff1355dec7697ba0e3f7941b8a9da1a9c7d1743a9be209db57646c3bcca7b9e6495b89f68d3344be6bb5b5417b09860e85deb22984ca2411714359b903628aa3d012ceda4aa4e3bf7592cc2052111795e6c24389bbf1ac687b638399189085ddf376f81122f88341334e1c2b5028bda1704910aaf38314aa946509fb22762704860a7a4e3d964cda04a182c6b9d639e0ecdb0df1ea0143c9f42830997d30780e6337fbb6c9364225ba8779eacf2a514ad374a811cec5073901adc4d2fb79e185289515fd86147aa8c1011a426d6406c04f59f9db976000a1d39e20f3a5dcff7565efaf440c009134c431c34059321ec8707e46bf7307903ed819677fcf1594c44cdd98ef6e5c43e3732052996fa5391b356140b7609595629a103a40940f311384da46c3e49fe335d97cd7b7ce2b27a3da0ccd1be732608b986db9661da00e25466835606c317fc83446f579a0272f73c97e1a3deffb4a68262a5c7abf5e75a07f83c232bcd5fcf03e80ca760282974c2ea393ba5d15c7ca47bf4e17c5b1f3d7a3c3add0d88599a6974cd2e1ae40b1851376eb4f85ee653c11142368e4aaa1f478b640d9833954210480782807fa826204a97f5b1ab68f35108e61b58f3ec9538a81cd4c1226209a5af3c78105b93d884017ed5754f8931c74a33ac4968886ab3ca706783a1a81c8a156d3f1f6753ba24a70045705128030fced8d10998c95a354d1b3021aaf854db005ff9b3c99ac9c06c0c1f4447d78f720eaa62bc001a1cf2774b82934b632403d7a50227edc3a7d2aa50fd026881f33e60bb1d7edf8fced2255150522d62b77d2bea1ed72ce0a416bfaf2a6bb07ba5957da7f60b9cb9f72af2f2bac3b01bde1a0474c18111b3a5aafea84c9302e01a82dbfd5374ccf6f6a805f61ab002bbb856e828c0f4050e42431d63b662ab963106a53ba9cc1dee33be6e7e416f1862acc751d7d986bb4614bf60fc702ec7fa83b12fcb9c6c23dd108e1495f3db8ce3bb248737fe4db1e9df45c703c557a6549f7b5438d593f482e2c08b6d33f3eeeb7488a53d98610eafda2f684fda6aad871e2fce81b3231e044a5059c9fa721fd60fb7010f9593d3946445fee4ce4c54b17e01bd9425bd8984b644742f79cd28331750f5c301bd106f87cbd678a5a15e4ac586138964c2f004e5687ec30de9f5faebbc55c8ebc9271ac4d36a39a771038c7ef3d668b43390381be9df28848f26add47ebe21e631410f5693d337a7239313313444d68b69e918864a5a9a8699bfbccde67a9f43e424dfa84d9448c53b6fb5144d46db4476e8827f206b47939b7a802214c5a45050a9cbca39e17d60efda7f353b974f2a65103c4aeda48306f23be1f7649eaaa35f49f92bfcaa5fb8683cea783de00999fa7f3a9dc74f206262c223f3aa1cb55616cf3f57e6abe2ab76edf98097d39ffc749224caca0d0d0159f5e97cc43534eaef0e22d46941d536f461230240a9fd7047d58d6f50300a59b554a7ac0da9f51c83bcb46390090e438eb4e5404406a15b2f6d8810f93aec575be5c45de5981537dbaca3eb633448a63e147e52198cf2dfa4b1e5f3b49c265752d8566e5d17a4561e5b759f76d12e223db7f60568f9b925b8d82de1acb8ed0415560f944dd2e058b2d4c206aa75d809b3b5c1f42be7d39686ee0a3f67719898c968034389119f3de2f6e76bc4448abc4cd63fda16884a02efc97228da15297aad151ea8df50b9850636cdc33a84746480c19005b7ebe834749c945ca805e6d45b69b25f55ee734cf00c4069ad4a13401987884af208f5757c011a8fb1031bcc0b70224a1c9c5edf802a160497b342828b69a3d5d2fe648bf82b852c4b8c15ff837d1c775df3bc2f646d7a4dc798c44afd1a65d78879c7c616f60d20ad10999b32b603dfd284d9732b8bc655b976a039ac4b75b76adb20f6f79246afa4aa5601c0b2ef351ae0734745f22fdc7263eeb5540a5f233fc996ab0fa26a87d704fb2051614fa5438808f1f104ba43fe6038fcc45da9aa7fa8a4d02b150cb8644e6e6748fc9015bd27f4817603ab31a66de0c7e7aecca2ac053b1bb8d989ee0662f72efbb6eaebcf4684f94261d9f3827a3c13abc79780c19979fd1ed2a0e881f46b9356025931ad56974e186112facf996e96be9ca7a92456a469f4de7eeeb26ac2ecc01407a44b5fc613f167be3c7d076952d41cfa80d9b76c6a3695ab9450a56994d9d6972dd04f75b3ceb9c4c3343aaf563fd429c68f2a30337fd8f3d01ab0f99d63324c54cc6b202d15cf113d9a589a206ee8015d609ca07724497527dbba4137821602729cda605f1c0d8a6ec6360555e1234f31f896d40b09a4d2d209d51a27679f3efc14af35625bca97477a5cc2a447422b0403f60d2143c9b11b027040196fcf39b4b3e9ae2169db1159b7e0680d5b756294db7e2ab072f9ba6aa42aff3f9ba430f26e3ca4742a7a531188f16192ff62459ead5f55a6bb922dfc94938eaf8fa8607006bbd2cf41cbcb1bbe6722c938abeb8d65a844e2071969be94f27b50de78d8b3608ad0f0dbdf9f2a0ee1714b091f7cf4f06a0fd2579aa0e37e10314db7a35203ca0c9273d6fb8fd00f4f068e02150c414f2099d1c73989b635cb0d0696aeb40b3da9ccf1246f4bb33be678b84e28e033710b4f92046f67ff0e58c0241fbb5798a78b03176a39b39b61e5e1be182fa76d93c4b08d09b44f79bcd88105b8fce5980464f78f0c5ec6cfc39e524041176276b72db7d5eb43e38a1077512a2d540e242a6f88724efa8b78b21d282e336f481f532c5a4025de9781185ea6cbe8b05a9eb215b845614fae74140d7d047075e09776931a1d0d59a266d4f34417875da931bc4fd75ec2bbeec4ad25b1fc4d6df1b58bd196f041416b854356c8a21148f1d06d170c2ddae9e5723256458c8c15ae402beb77e28e07b152aa0cfb844939f22ff0677e9dd76e8b3332bdc5a779f635e97891bbf35e40778b3dbf36ddcd9261043e6dca6344c714054bf10d74ff866d318391c7f96359067e58ba624a4ef6b4c153c380aac338032695c32a5f5776211ae3e03b0892fe8755dff76775a0b6d2b392faa6f1bea6571e59f99fc10429dd0648d00d0ec286564c1d47eb3ece6e40829b4d7461d19aa45e547621557cc2c4e5c7097de76f17696d808d0a50b16efa024949c5e73a3271da506268d1f0b3f3fc8cb0c08683d1aa7bc93b7b1b2f99c85c88f1726a1e84eba1dbe5d667c52edb23b0ca9b22f749069a80cd62e3d4b983c159a878e1d73a3f376e4aeaf1bfe197a74ac1576f060b0a3f51507322eabf18a31953fbd0ac66b1f77caf18356ba9e8bbbe7e011a150a25011ff8bfd7cb0a99a9fcd1ec929d641ed607f42dbf0c239fa446a10da78754564a65206ba2b9c78ff093276c3aced98277df4bc3844b284c0648e20b4411200c230e3c38587ac076bfc1791b33ba7f0e9865791e361b99468119b81b809cdc504f8aedda6e306fa1e041148416c1b1817b148eec4c5430670cb3af0f437bd283a6f498a34630677639911cc5eedfb237b0aee8278c56fd30c44b3dd86be3ea21b6577f1c664db44ca4e45a2bb5bb114d78435aeb9d66d1efe397be0a8312ade3dd98e47c8b4bb1be20455d1dd1196ac81ffced8607a868d40a46ccb91bdd2c8ba199901f2d25028ab382490b5d17b19152315caa831a894474a00f69c1a8caece795276b5047607373a83bcefcb1387592674229cb4f86579cfae2f985eeff2c19df4a464f2d96f7cea3dc807b5f2395c2bec577e0d0756e21c00f7f827ed2a7fa32751b9ee5b75efd18db8783fda50057cdf8f2077c1f011cf64756cc16fbdcdcbffd719c9d8d145dab732adf4c0db45b4bd70ae82c6fd346f795b66eecad39aad12d76d4903a43ca886a8e4544e38aa0699d3b9a5057c2f520a1bd95e6f4baf4920f6b9b52785d678ae346b0c7853ca9ecb18727b8edc28ffcfa667d089b1ed2373860ea0b7bbf37e86efa4a70736cdeb3ff56e183dcdd5d1305ecd75b56ea5aa348c30cd1106d5979041670f5fda2f59046a99a1465dad17a907300ef6e379d7103b4d4d5fffd0cca64081c02c30194ffdeb179e09408d98b19a876c0763a1dcfb16b15f76109c12898d27d4f5e24e1371007f66bd4c9cdb4c2ccc2733a0757e4904a5ddbd3bde25f2df13ee210e439bc1b7e57dcd092c2fdbe7bf25e00200e9679f6ff57fc29170bf5f56eaff37ebafb1617f4ffc25194c53ebd28ed3eb1e5cc5d9e0c2a50e3f3afee7452b7103ac6bea092e56befc45c132a3788397a58abffcffab7bf794848ac4935093d5dd74126df82eed54c4442f1c74e7120168206a8987b8824d5e83c43b7bde594a240a69d364f1727490344ffa2e9332b8137b36d2316c528b9be8aef286df4f6be9ead5f10d88b5e0b896b0911a8f712add406d1f05f43565c96f08b6e6394a3a4c29ca350bf0751feee806a14a6c330ad8ef933c5931c52706d942c3264964c475670db2eca91067276a7a044043a38134f8cfc0ed53d05ee3a4eaed84a3318d1c9d66826395073845b9a9257572e66165782d916bc47e68cf52684feeaae270e0cb026b6f60cfd1c5781bf48972a9eeef8577aace5156ea1c32d51c829a62aa9bf2230b3001891d0b7c7806051f3aa74d408d1854eb8355d50af9421d490cd5f7ada2480d65287c7824a7d94d8aa539f826c8551fcd4c43197d33f41dda79d1de456b85c49138d9b2385283857330695ec3f31859ccbe94c05a06bb3e7349db1c0d63189565ae2fa54ecbe25f4436a53c63722058a8d504a20071c0626471cacd428802d845b47b64a0aa2c85e32a1df6de7875a6ae817938e57eb0f4b47accfe52ca677d7e36400f3e5124a0dd8f87256197ca354b0510f07aa927131cc6a61f60419834918210aba18d8f72638b2ee963339abcad8d1a2d2ab2e50c91634f4d452f6326746ab177714c9799298ff9a65be980372468d213987994306d796e8910343c1b95824c07cdb6a41ca8b4a339ea1b3ad0175dc422c5eed16e68c14c9535104eb2fafae056cd746281f32e86797564d0c4f568df4df7387c9a36028d08d9a839cf925dbfc8e5bbe02b93dc6c9d5b02cd944262cdc1c68975134c34098c4363fdbd774c624908b0fa27cc9bd4d692004d623996f1f14f656ac6927c5c210b81caac2a632e108c24bc74542d83d98eb440efaab6ff7661f60385b06208a7feddd839c84f41e95aab773651cad595e552708b38e33db3a449fe3f4ea88237c5654aad438a249b8f536d46426a4a6a5071503db01733b5f3705177d5f7c2c9febe8a9fea23aa62607d12d9a1ea9ee64d8ae2440c1ba2e5f77aeafcbb48237ae4cf3c7547987d016b81fa8286d114d9b4f8c34e6d79ef9d8daae6e618b43cb4b6bce6acc73c6237043445387a93750cdad88ea40e0b6e412d941fd3f27063d698f20b5bb9169c779dd813a1bac0e031fe7767a8a6b45933110bc289bd4ce4c181588c4a448f8b77442f48d94b5135ed9222b4180971ce3c848eda72cd84db16377c61be195933363650d704644cec80dbca0c6133f31cc688b013f2060fb26e35ea9d997963d7fc95f3c39e3f218d6f93ed66ffcd469f03c7118cd17ef0e9954c8eb219cdb46830c3094e07fc557d6c14f179698a5f379161a71e95d6d3110e88254aeb017894d2749ccc6834a37546a55d2d1331a4ea12b722e99ec9d19afa4249148a0001fdd17407bee0fda45e915959cf44006db0124d552ce0408a17654caa9cba42c1b9c4a8fd147e4b6856eff2194959a67bc379971a621a162dfe24601882300a374c56cafef72583bbbf67a8ee24c62aa8376e4c71e88992f816ab62fbaf39c7596899d4143bb76263471fbf9551c1f8b77f7becdf30689ecda0cf6f228b126594285aef0a710a3740e41b5e1e4ecdb8f74ec4f4f93f424c087ecbd95678e2a3f85a8e5340013e48a5414e6db3af19372b666cb86f6a22ef847d4f41f30674db180dc71ab4085011febb46af064d6fe04e39f8b16b38140be6f6feb1a01353d7d36c3917e43dfdd546edf3078c0a7d18fc1fc6e439945177e6c59b65f1c07464ab04f6cf943576f93c94e19fce3d7bc2d0bbe119e3c0a02855d7065545179d6a62d0f9b2dbefcc3085cfea079b3c334b91d7c0a80890ef2c7e1f68ea91ae96a1fae96392d8ca43564a10e69fbf050438183f1307f4b2320d370470341da01fbfbefaf6969466060416fff01cc990df7dd6a5ff2fec2d375dcb62a0be08b318125cfc00e01cb719aa375c3efd9b34fe8787d16206c2b94297614243fab3e274d775890d08a1671ca1c22c8f475f2ace28d507e99c9356d1781b79d22dc45a80a5e17005c8d25b3399c5663dd7e1fc989367f4be849042f4c0c91faa31dd696eaa1698e189d8c3765370a432b4318598ff885aa631e9b8e08a127c353198142b795049f78328732313b09109155a6f536b9d25cf32d421b32809aadffdf82732fd4bd7d8ad2c479a317eea7c4a2aef7a97459cde531925f4b7033ee6bf04b73a1d0cfe56e6885548f577f4da1def897e170a262ce84010850e7c0de4676d755b364e2260fcb4907e16ed07aa84c02098b748321c64716615feb70759e83c54893fb407f2f7fe3aef87ec2580bfc09f8a13b7afc640bc067796f9a99b61f95d7bbfa4cf633ddd9d1c0b795db606561d13e69cb7bb5933c4696240a0d2aeefab197e2198a9d823e15adf33d9b74817b3b79cab0783919577c5af03a3758818a20f8a2a1e4b37f11cf7167caaf524355cfecf616d8e46d35d85d338950e40b46237622087bab04e534fcea8ff8d3f43847f8ff5b9ba18c3014a03d39bd1bf0bd0e2a3d8ba314dc1891d082697d487a92b27cb3afb6706b3b3cdf20ac1ffee9942a172f0c5feca58c12d17ade3168f2db488fa3e8f9d278e1b3a58ac89af24137d35f61f50333cfb27921a1487d4587eb7882612a54f693372cb9ad762d6d03256c11f9c647b3da790b80f2bb7b6849121321de3822acd6fc12118bcbce99fccab3d1470171e72d4e9dbd0ef974cd1f72c1ffaaa2724f1a8dd9fa3d5016a548648c8b959433f1a04901a729b97c0637d252b7cddd19f3ae35908dce757829b8e6ea3bf69ec7d22019e60549193c56475bcb25d5bc3fdc4f01b3838a64d5082bd6b5a4d666a25f1c9fb62b2e2fcfca41050e712cfd3b61ac799ce905213bb54b0d243e2697ad693242d0f6875ec267d0589bd410d5a5fd56891301a531f9327ef3b609c86a9dc678bef5dc4dc7e4db4ebb7d3ba830245671e39e38c0f29fc687fdd60ea6985172dc231045dca119a62abacd7b75e0d85b4f0ceaa608ad3665ddda381e27c8c1feb65aebd584c1a4d23552342f5760932037addd822a25888617a0e1b93aaca1a29b2bc87d83bf7efd2c0667a6f957511bdaf59fcabb01c1a081db4a495db3adaad01f359fe61a8f655fc0c2baa4a33cc5cad1feba8a221fc08e77be3c03546dee1163ca8879ddaedbe1af8b461fad6837ca55aad09b1c0d33bbdafb3cec0791f98e3da9b226f215eb8ef7298373a7f7ac9e1e070d0b690a4a010d5d37c465090a74795234415b2062a58ac388149729d9fcbe4a7b11b67c589bdf98eaa5a48baa17e0c7f04ccaef91080af462a362e5b4355ee68221f8e48a68ea7858327bedac09bb81d9ceb97bef2ca96a57fcca0291a4822512e3db2f0c1639ce76da44dcc671f703a4981c8147e70e96c6063b66ccdf38ffa98f1df59622a943d59288421a4e2391594b43085f7ec3cdf84e9f88e4091b1cfb1b846b1ebf233dede0b1a633252597413929190c0b29b0a6975859c8cc353807e4e39dad976b7c39ca19351a632c7e2bdd5dc265361596dddc7c7c1a3ca51a2d34b1c38158d4d115a97fce1e31cc394319596ef6b53a74af9b174e21e14ba12943c4926757ae6460a2500f7aca1e3c6cf826a6e7c8469f1e5f416d4308812f6c79900f6e48ebd79981f5f435f4d9e391a31e5786f75cdd20939c99e229401ce45309370788f88d51d7d1e864b3e3eb169e6bb347db5353c0e10bbd1f87b2d0bda822c9cab5de99a882090ecb91dcaf8c57b7a45cb126624c3c3aa2e33c3f6241abd9acd80615dcdfbd3f99343f5fa6366cf883c1ba976a540d22d134869a06ae1ed2e13a0cf91260a35eb239d984c16014e1cac39111c229c583b06026fe5363a07227c45a6b3f3bc6d7e0ba5a95d3bfb9c1fca2286a55cacc84a9d6d79ff01fdee3bbd7a7bea6cddea02258ee89f558e3b974f88782ed5f8c505737f59df2debf85ec6098c22328ed017a06ab4036c5264ca1f33d05d8c548dd534a24e6c248bcf4f7b5536cde5162dde8d6dc7559a77a60cb6342136bcd7bc540afb568edfc769be5eb82daa15dba6439bad664bc441f538ff304b562a1a10ac6990a708b4376babbb88f61ade59c2c2a7620c5516aa0e453ae32bb8978105c2f26eb5cee519b898e22babbe2ffee2467b935b02965d228b2ad85f225713b0bad68c57a2b22eb63f25cbb622d568046d38641f6512e388bc76a345afb4ff7634d72b92283c55910be45f0bc68409b4d48e2ea4c84401e418ea64fb3b3b55c1627fd83ca683b35455134cdfe0ec72862d2beab9c41e1118742dd17234e78ecde5f60700376ad6dfc75903092c7d1d0104dca6790eb9ff9b8e31f757d2360b09475c63e7a427cb6b16f6372ca7ecbf3349742ba525a897ebe8aea9f49471899e428e3bacc20bfee91436de43c3af79d07f1b7142942909431a7a82a91e731ac696c044967af50e69cf10c5ed6b825f40288996655050dfee8cc0c70872ff272037645ed3f76fbb07eb873fc1c4d926c5ae4b478cc8cdd12f35ba9206518e48fc45354435567bc3b9af999b07d097cb8c07fddeb89e2d1024b5525f100cefa040e50922d4b8794b29d3908913527ac00259013a3e3ce22c71849b39815421fe4a54a6dc85e375f2c37ecc33e941ac0cf40bf8e52ae2b0647724e269094ecff895b557a8495c5ccb11c9abf0519332851d9134f68d0fc970a5be29b53db4f439ae92f57948a14b4a3a2c13f9ad13664d432a2037a7352599613986963615af7729c01e091ddc6913dad697156049154257c02feb3167a5eee87483d43abb99da974e48b6b1f9b1dd9ba3f5183752413a9eead3c4152a9e1dd1edd3d2bbf5700bd8ea7ba4afbfae95bec46a243fc79bf6a99dc68ab3e356e3d3bc2faf34a22794ef6c0c8a72204ddac8ba4c05b9eaa093da4cd0d9654f4f3b993328f3fc5cbfe4af6ede83b23117dbae6f9e3bb368581bdd4bbeae3b9551375ba4487d91854ece5c4e61489dfd03f7f9d2d51a78272057fe84a99ff3d6a8c827b239e830d5d631779787ed8381f174fb3564c4905b44ce64299cb8ded874680185dbba1155276d482e91d784f1f7560a6a000ae2b4a1cf2ea7b06645c438ab57f06d2f0219b30840e9f1c170f55ef90a4303d680cbc5caade23816ba929e88b06037430818dbc358bb504a60b9a26328b5ac59cbdb347f49ad9607faa23ce5c5ad67ea0b7a4fd1d71553decbccad85d471d4bc325986a383ba1274b6db8bc1b021fd20032b9fb14a5713d57fdbb59a630874eb5bf7869242e533a1bc62fbf01911a63dc0e5030553c378ab1887e2fb9ac2aa653896ed81b6bdec42bd9c0c5f935db02f273438c0337b307af11b6f40d57e87b4177d4dce3ca6675f1650512c6c0a0e5977f4b40e8583589e2ac8116f656f005ae25c5fbc316ac14db27c5e87ad38788b441a4500ca66b9153c3df47be249ae9b2d6e63ad1a5bf631cbbd75312da3c232ccbd9c9efec72da6073083ad32bb0c32947d96d0db7f3e79b88c5a9f4137374b3dd02f9de1ab19b76e34b4c944be2b27c3a77c13aa37464841ca29afdfa445d6c14607d4214a513e58cb25f0ec86f9e0fecf6b2b8f185940cfa8fb5abc7ba4ce3a3b7ffb0930e8c9d144cda83439d20f3dcec90f5732bb224e2673ce9af80398d2a754c5c89d7e0ddaa2eb52c7e2d84294b349fc15c3d26b530f25db780ee815bc5c68d4cc555736494f4dd29f6fda2f83df322c7eccfbee92bae3f2b600aa1e8fc9d9be296c21b5314146ac12142698d5ba32f96539f7abb02ef0b76be639b0d235c55d9a1792c85b66c2d6c5db71a59a7d1e93bde1fc8b44482aa6beeba52bd41e690cf780133fec1b0ff251294d24baabcc9e4ea1f7916431eeea56c2a6d0b2c106c39819ab0f036c4fe457efb1eea511d9b22312757d1ddb55ba6e0abfc64c8471fbbe139ca4ee589175a57c517d581de08e3316a494b961b6e32ac6a21c7c6684aedbcde4d281c90c481bdeacd6c03414472e055ec236cd5d7eea18f300a45287db38eb05b205894ca71102a007d9423dbc916c919dc1c18ab08487e4ddf628f423317637099c9e3ff33587ffff844cdbfc9359e89d506966c014c9067ba8094755ed9a370cf2f214d4057d64b3a5aeadaaf8bc8b992cb9eaae3b15fd86e9dc1b9b909204cbbbd65dc85fd1755b95d2d983151f179b293e7ac09003a5e4ba21c68f431d7e209a66d456567d2e3d43c08a47c46ed794e3f4676a1959d6386740a5a496024e256f27f5dd28f0e0b15657405f72b399dd1fb8416efa99b2667fa5f6bc1a4632156e53d66079a38382868bdb5ed400bb003cb18d75839e0305124959e25a05db13ad69030206b0ba25c3bd9e621621d3447d8aba239d830a11d21472c01991fe2fe1c3b929a778100ec67b46c9c3e914566fd50d4890cb524e67291ed8623077783e29b987ddbb18080d68cd48093c4b682c8ec107bef5cdeb5b2012e7b3af81e9f11698b9f36dba5ab292813e7c8b1292b5963bd67cc2db02d12dae423956fd67e5feb66ed9a190db5eabfef0269bd74f795001808304f95745ace3f0de321d9a6c9e934f76a2a12be60c1592431e33bf9155b737bb35c04d9db9991e32269ed9956e61a546cb9ff63b6782d38919ce16226762e67183906bb515c76fe11f251010b211ffb59bb941e0e36ad9c5fff8c51066e780cd32a32aada192aadce4ed3a238a6241b4b61a79e2e18f82c212471860f1f38c7e3bf801ee8b0227e5b0d5ba6c570571ea98c27ac0147eb0dc8039def13d99910cba4eb1623120950738a92037ada7dc5e3282bcc8838a0dd071c5cc42350f62253ae846e77cf52d06bdb6ecf056dd791b2acd685cb0aed8d44dd61dc8c9c5f8a3a8fbd61d6952934b8020080b6d6f5b83911299293d0beee54e84119e6cabefa88500bcb4b247cc09c6cd1f3d17b17e625548ba56084935aa874adeb313419274f241d9851cd1e5d060e6b47d5dec6165fe3dde70b40155acce73d737d44d2589d26b5eef959db40c03439fe2106a33a7b7ad4e94b79fbe33683b4f5aeb2d6d9196c2847e3761ad3f5dfadc2564e4d29d25bf1f2bca7fb4c009ac0343eb52369517faf2f5a244d47acadf99305c3fd70d3fc77bf0f08371ba7b98f563dc0ec08f47e612af101457e1577a76bb520985e95d3bb532db307de486743198a9723b58f1472a32df7930a44b8ed36d4ad32cf7c83e7a62d6e6bb50e6f8b71a4de3ec8b0a9d3a244d24be2d2563c2dab56dbb434ebd9a8285d18ccb281e2d51466586483338e7b0a3b60601da0301514ff18f3b79758f76430a3de4674fcdd0bca350fab6ac0d316f4762dcf347edd273b1b514606cca46bed88d840865c6417b5a39a74ba82ae215f7069a3e66efbfb829c9d2804685b4eacbeedc1e543d06d212909111861a3d89350ffec3ae3ea74619b40b5a8e7c5f6f5193edbf5665dac29225b67aad5b7d167bde6cff818b074aa6f714da89a4d804bb2f4a48aa2ec6fb987cfe478a84df209fb8d0ea32352a0cf0627527496fe9157fd4c8836658d93b5fa07abc404b6916e3eb53f9cd1d793e2b8054455c1e5ba4fc691ca86819ae4533dab01762a16620998e06e74543a9df34446c06b1203940f972ecc764afcc6a006b97d3d2849c0bf13caf1a9a7ef16324a2f5c0be7b150a4b075adb6676affaef8d6df6f51ff7be2b04b0612be635be083493ef88bd3d52e3f261d6f6681189878642968f3b7a9a88c27bd94c3b7b7d457f06afe90c2474d2424f7879237cc4f48004985487b993cdff5a2916c6482691917ea1dd2dff174dc361acfd5f09c088760a148e9d26a52d7836b348572652451cafacc18c9b757dd2b24500e4263dd2cc843cbb27f63a381f175b2a52f15617bf6505ece9b63e7ce0feaec47a5f5a951b3f927750deeecaf06ac6e40fc825199266edc9084526c9681856d5f25cb4e901bbf118e4441ded5aaf02fac5964aead18a5bfb0dfffb82db79ea2e754cd26c3c58da2805f3db74cea32ac676572f64ef72ee8ea077c92651b4534080d750b76a1817c622e859e5e202e3d6116c9b73afbbd57b8bd48903b4b5eb21ff6a54c46fdea8c7b33dbb666efb4c077c2f751c3b50b0d050854449719c81896d9c4ba26b53d08e1f88ed22699b3ad2ad2609cf4f4bd1e496b04388349542ff263752f810bb17822c4941f28d1aacb88ded4f81213a69817483fd1381228136cd67d5bc2c56910acc7c57b63197d4a96ff5ef27452872072eb3181a5d038cdb827d124c0ef2bce413d918fecfaf7c52f0e85b77333e1f18e4706061f98ee0105859f6bac9e1e4175edb16cd08376248c69165dc0af602a9675c1f26a31902e4015b01c28089709268891e16b4db24e73cd00a4b0596e0579a2a1aaa66392140a3015a125f96cbb20dbda83028decb496a9ba3490093a1084c4d77eb99cd1beb7c580e5943dc90b6d3f7867829c1aa851faf084a569d2deddf25c23e857be71a40803a5d76d8e1016b081ec23c08b95715de624c9a1bce79c8ea305d3ac93fc8288e95e52d4ec2516b0df88abb590a9be8c4677806545e36bb98e71d2de1f5382a60479e98c46b36ec2d4440c44f60f8766223307fc7c400e0f7c494dcc9386a05208014fc013b241b877b402ac6ecdada1703b886958c1835cd2d60c216cfe14513ba1f8e0ce88f3e85dff2447ed731a8c6930b67a0f78585e300298bd1fb02706252817f140d4ccf7fd239c651c9c1057d03010a57cef19faf519060d4ea60175aaf46cd8755360b789a2a51a0884fb44f91b21d95a590bb2d48975371cee39e72b99d1d49bd2fd06ddf2928729af774727fc9d51b1114e7fbe7551a0e564d18b4060cd7946b356760dc96c2ce2be015932d652793eadaea9e2f78fd980240d8223c64be9b4357f07b96833283a5cd7d02db65138c1b18b7f408cc822c9dc84b4b702ceb82c9cdfbf4e2d253bd3b5bc9c48a772c8a581b1fc607af6c00d689219f6e2b03ec56bbd701bcde5495df9295a78a371a29bc86139918af5a762038edad6842c5cb58b168e0806ae54bbab988039daa53bc51931d0ac07694f901c35188855c0c23ebbf6dc112997a3e0103d3c33de64d296ba27a74b5593153bc46a432b2fc7d5ff7fc20b21c49bde9504fc0743703eb7982384ff4245cce0d338f954f9de41a16868d5d9b95c50a89143021a82b6ab211192d009e8b8e5713029cb8efe066ef774d28d67e8739078ac4b66b4e33d51319e899cfbef4d99bbc73b4f63689ddbf175ba80e2d4219090d54b72f008888fbc843e095ac70c6cac6b612cba4a28887e45f8e9eb2f46a42d6218ffab6ba1ca4e669eb0713a28e6f31ca0deb6b1788e9454bf843d5792ae8e97cda0b1cd83a8717324e679bf0a9b57dffaa56b44ea72f5cbc07ecdafee2b2d4054075c48974e86837c0404b56f64707f56dfc34f610efb07c2270adcc936789432cba1455de9226aceb24ee805b2d56b1d894973e0cc588eee2e9848e7f3dd00e1d3efca68a0335f3d52bf811980c38d075fd6cf419d6ca5adf05898e32332355ec6cc54d0849827bbbc9e884d3fb77ca3bf31e30ed304506e658a9cf1dca4e56c92979d35da6eda65deaf374c4a84b20ab986d61367e43d50c4856f5f9776ac2fe73632b454a679ea45f23dea64285bf9090d445ca6d27a168867fc2782d4f6936222873f730bafdd56e4ae0dc29c2b345fc25bb4f31984b8b273a25bde838c4e36a200a1654dd8f27712a4910b476a785598ba973121bc3539f657a7e7f460e3253221185e46bd5674e4e4ee796efc2f4c9cdeffe371d7be95c4ef653805ec2ab9ccbba5f3fa370e15f2043db17356ffc027ce988bec0ff31b75492badc65e346d87754a92aa073642ccf3484990cfd4c64cd615dceb1fadb92d2a9ac6d1bd888d1aa15febd9d2da14547db91bfc2f526060d7e14c38620f9e63dd19358ad38ed98c6b2aba5f81f0e94a9c3cad4a165ee0032eb701976884c3a5c860e2a6307961987cab803cbc4216576401974b865988b2204fb3305a40df58f515dd971604bca5ad031872ecb2c930efadfc8320c3bc5bb3a54e61d1cd5836d84a108657448193abc0c3bf006927207bac832e3409976f8ba77501008ec7bdba18629ece46b46776169f80f77ec8cfbef45f330927562b08bbaa3601d8d11f34df3b8de86944dffdcfc4e402cb4a31a4473cf31e4978d2327529e39893414fb0a0a69f860742aa308728cb010b158e64c490549e57c94230bd619b9a6ae62b094edf042251a0545e5d8dd7a75242e94155a0fca05620bc1b96e42b3f0697fb72e91b38ed3b2036920dfce30982f0221140c18044d396801104fdfdc6123d3fc017f1bb9c0d203a2e59ad8bd7be5e7d1af636cc68425fd6d641188ea4dc9f03de8098ed3d5fa518815d2b1ae312171f71023aca88c5f2e4dfe8fda0c8c63a659bef9d88187d3f0c497d2b92d1be4f0a2972b9e6fb191d52fe5b0e49d70ec23de7713b8e75dce2187776079fb2bd15f96574fe7823a53630283002c5c391544617e04cfc749553a43233b02d55dfafbcd02e0ba4020e418d68aceae925b60945958725128f999faa9849455a8f50ff4c69fac845fed3204a03524c0cbb94aca7bf11790bd81c87a51a50af234791b1cd5455516afe8ea0c33d171e0a77983ee20f0b2408c5478414ba2bc5e2f6c70d67b313c9add49d9a4ab01aeaa7452082b8db9335e7f2264ad4b8f237ce42258bff16f78c2e2802cade323d1ed2dd3547b56f82b0e8028c416b4f5a04f84fc30a411e3b209f52b312e2a71973d5f8ecdcb785ce9e57579be7918e5e3f95832ae3f9f21d553e4ca2363f3c90307f492f60c683f989fc5cf196b9f3606a9b1928fe9cf5714711e37daf599e1dba60d02c2b0a459070b6f06e10752b859927f080d6604a9ad3a4c26ab8f3fb114784c8cb8db8d29ff3be2fed80e83d762b69b1d360cc5f37e63489a047a3664149b4f70c382edbe7f46a3a6d6799a3a2d075b05da69075fbcfc213de5ede3a022763dc316e8c2c886bc5e7d5ab006b05c7c7fdbb435e9b6a1616639a41ad04d10c4a3112c7c6a2592938289439b3e84e54f994384a66514dfdf8c1197119d8fbc68874c22300021f908dfd27eb61532ea2510fdae912556b6ce1c3ed449716669bbbcd7b4d18657c06cae57e24bd79406b726120ceebae5cf10fddb967063a2e418f48be08f6d53e9d8295e73aff0c1220a6a487f179d4a5a73922b392ca6aedb03f9b840d364a2698cdedd56272398ebbe2a456e10786571af05c339ead4ca15c1b84fcb0ca6b41cdea5bbb8580b54d724b9116f1512a956567b98fc560a129184672af2cb5f256a2036d19e4118b53325abedf62ddf4b7e722297ab2a2e7a0be02725393eee225f428298a3ee3c149a89ef0202eade6e531b30132bb2e249121032537bcb5172d9cae7825dc425d8ba7b3feb26dd131432c5700a632f7a495697e7f61e886159b7aefae7736448127b368f1ef9baabcd6a83eb243e0b67d367cf4427bb8e7cc482a01ffc83f90cad1415d731b8e3d38c7f47ceef690d54f6e6baa8bf27a5de096c0b21f2f063834abbaa7654fa9261db7ab62ed99f33233bc59a672414ed7bf562f9e0a848fa66214542185b80c9f682d827424d28f21a1741fcb9166d4d7532b4743e669570d25855f448b9835b8455e47395ef21e0af871d5b3366b76e1df5067f18f7ca3f316fd1bede536da19ac2fce37ff31e087ddff2901bdffc6a938b177aa4f48fb26cddd3d9e8b747f8a80fd6b204bf6894e29468ad3ffd36c8cfb0408b6fa39c3f796865739f5fb0180877e5f572a5f22e35c4e5399c746f5865a85028fb743f6b4ef9dd86081bfda28025353a1d11ecd7a0dd0965d25f2b0d411decd8c9acdc20e6c49e4fe2f041fd1fdebd2a6ec8bee923860a4fe1f726682cfd706f6590cd7d89bc5a3b8e70a8fecfa79eadadf8b1e56ef493d086606a0b69f1b34a5282c3107a6781764ade1a9dfa5100c10d667289c0038bcec023ffa4d704727bdf293b61387fb6832f79565d9b866a2a9259c229d91609a2970065dec012883c612a460f294a422980736907477150632a8c2f1ce6af64199e22d644f4d2db185c25b0e61793ec842e4f4ce076a556922ada97a4ea3a6b5a2818c30cc92db3d791d657df47d6fb768300722d38158660b0b983224dec83b2419a2f3a48d6158d4777238223a4884c255ef3f18c8302323aab7945ddac640e41822d28d6a237ab2404e6fefb61f712480d7a5be7cd19ebd2620bb1ffb71ccf71a9a47b486727d11aab9d80441a462adc0826c2b5505ec5790a60885730b5c634452a12cb6285de3a9aff451626945cd55999c559d157fa3183a325b31d771ec86b6a7f85b9192e449613874f42bbd62d33ffc825e1d8402299eb690056db284065799a2ffe9a0b0b261af6e71b1178f182068c6e674b9da86907d36457128bef81b7d39e4f739ecd7a49052d0d5c1c8f3990f2fae3ae3023da219f6cf72574ede7ad3528a360b73bb20069aa45c6862be5741b5d073dde9b9fc6ed22ad5e14ea37b4cb577663461e245199613f3d39c5f6000e233342a878ba632bf706bce0359e06296e48b8d7cbdd6c2c35531ec8cd5c84dcaa2eed44a533012fd92ab59f96a36be36d2d884c0f300b47a2faa0435b77623128890a35cb73c45b4d83587257c5f3f0f48fb6b50d0033bd2d782afffb6367d4d4f13c5f580f6da32d736a48e0d80a4cb59a36efd4ec623700d2b220be318b253908e5d59893f5d8168078705720ce3529246f6ee1b6d148c33790908d75ba75da8a95a9aefe65e51dfa7ad321ead9e2c042c06cb952a6ce7d05ecb54be156dd6db23d854a87bc64cf803ef546b7d46d2b507e7b43fcb480bb2f6d056e222ba7d66f8328e5d69f4c60f7da1de5538ccd2f0f3278abac44eaca979e8edc8fc73eee6c44643fa35b0afbb7223a7155ebfd666b80c296a30c208e63b0f6fbc9f26f334fb820590db695d010e5d14c43c3ba82ce78d410c84cd7f235797ea3d03f69dd32ba1a479a711855d18deea545823a414a9e1b8a7c9a054ba4db60954688a60f7f0798ebe1b823c5369a378b70b22d43b408c0ac476ec0ad54cb1c7a1e045e71a513a77467f83a20b4b375cc82020f2ac422f6e1900a214d64694718396f2e55a42c9310f7255e68e71f652bd31a7e3b843db786dd30f9a8888556b86d59bd67be048701ec8ea7f739d0ec9322989edcce0cf5c2a1d421023d04271a523f5f7f1dcdf94a548728ea24109e6cc8160d81f8945473d933164463747417f349246ca3bbffe8ac68a7c1114879043475e2d00e8a86bec5be1c41f00702e0af8c60563b41f64980f9c324f61086889245b32407e45f5b2849f4a5bb3cb044e70096217d72d4440557f0a4df4c1e78aeeed0b475a753d636ec0b57cfc0f1184172c47adff593f9f696ab635b12202e2925521e8ddc75b774aebeab50890c4f4a071c1fb7e79179e79032d94b80a6a89962957075c7cf4c25aa170963083b549c651c8d806927faa0bcc5b5b28f47d61fc0a4741f34e965c56fde6af6abc9882407550bedda4d82b4e96396e40855294f23fb344962d2c8529a1fbeff543b716ccf6e4fcf08b42381393c0f99b9ba09a8adf6dc14dbb39a7fc7a30807d00b3c51a74e370248a5ebdbf588563fe73b21d13e9427b1044183d489a835fc6b93eeb84ea80bf479ef35bd95376aca0647451d9231b1c93a9a9b0a8d725ed615c016a00178d6d30d82798b7bf0830b7e663e68f40db0c8189f8901b69a8c2c7f8d88da444059d8f14ed5e2e3b599f25b3eff461ac5f1a6a1e3665bf1026b2fa7192fb08bf8476f7be3f82facc38951332201cbae989e1814c751e935585786bffaac83ab20e302f4b3f30b07e918d6e37a5098dfc64ad4812023f7960351a3c65a29faaf8df44b8977c01c7f56404bd2780ceb65fe3a4d8341defc7558b51f2cd44f27d82ec23f1d5cc91cd95e0ed5c53d4bae9dc39393d7c48b344bcdb1385c8dd682c8eda78028511b622b87b5218a5f1aea5c6a226a54c520b085b6c09b2d28e9ed382ee7a58f9e762306068afdd03047d44c6811b73acd1c045a5868356ff7f6121e0ea6f5fd20eda93aebc44690d0704bb3a1308a61ea8a13f91c38871c39e40b99f385a504f19ee45c1c441356eb1714cb8f5c529b15290d5fc4109218c398a980ca0ae2369016326ab41c519d52d225027fcfd9e01ae18ead62d752f2db5d4f35019c1e2c663a99d3943859eac9ef03def0607ed5fb8aa71194cedbf4b4ba8a7fcb338aa0b20e53e6158db435788e1a8865f55dae81b03bccd30b987cf9dd8056884ce8fad1ab2b0bc17ee3325cfc137bea7445cd5eedbdd8f6dc2ee7aae502958fc8e6cccf6d19392aff75005ee019166993fda1e3b9b5d0893bbfe5a4577ec4c3c815df65099706020f0e261f3794cd2574f99d468812240bd3fe425ac78f1220e87b0f9e17c2063d4549c0105bf419caa28de751068fd8e2cf6602c5e63645dc106d5c8c9176878480cd878d72b546aefb7cf6d3b37ea3e4a18f1b6178bdd4724b78a9a35232e558cf30198fa7c82742ffdea4bb259ad8b6b16ce6bb57227a4f07633d25895e8a280b58d85854d8fe0c7c868cf15e916360199364ddf14e720680693df659e2ab7d0147424ddf68cc67b7ebdfd86cdf0615a40d9ac9fa42622ace8a913f0d440faff78b39a29abbbb88ff39fee75e0d2c96994582b9fb31d5b569661a8dbc77c57fea5872e02177807fdf93cbf1268e4cbe52364b069edc147724f6c204fa3d48f0d1537767776a1da753fae77a343eb8dc8ca1ec702570de8d835c77380fcc5cf022624725ee229d9a9ab08cd29b563aace8ac46070fa99bffad8bb670d2c7d7495d1f8622d49d64322d043cc74dda9b1ce5e4c64bb4cb562946466465967122889652703fd597a36cb4cb2f35ada509fb4e837c6b0b810aaba6b140ae042af05a70b88436dad239912ea23771f1962e9e21769998cfb6e4bb66ad1f57db915ace1b76725a39ef238d2056026d81085326ae3aa53f1232b914866c19e857cee8c48d5d3d455a87187bd5d83eda555f4afa40f49bb542d02d756ea3a34c93f6cde205911d47505008a763a1f4679ad47d20d58569f618e1d4dd2cf74e1cdc5fe379fcf9add1a89a994097befcc284475faa6a330fe10f502359bfe1a22292b940a1013c4fb461a7c79fec657822198ec2893ae7116efc1ace3331e70aa8773f278ac1d2f1cfc79e6882f335b0d1eab42380f1bb13c7f98d870273da15554a6574c734daa7484b42227ee47415bdba3330a0edbfeb229b3878197871aa7dae771622445588bc56fe654a653f9171fa4b93b4ff53a03652c2a684f2ca9990d6caf379578b4eba9da58c508dd8c276948b5bbbe015d08e3711fccd1a410d8853a3f2463ba13ada4412f9194a86928abce2c0e0635fa57bcb66b4c1657b457d9a1809b1d0482a2b35ff29ef411900059803c19d5bda9b4871fba40b4e3b7831e70e97741c5ca5a1b292dfc5767db1aa52dfd68839018fca156524313bc8bf15fbbafb881be1d98209926b2d845bcccea36416b16467e45daac688a5b241758342d0f778692a63c9c0c57bac21cd2a1424c07c8217a06763c3488c56e859f1b0cb893255361ddf6c34b7166fc1f9d424becc2a372b1d4e6b4edba96423a358625e46024ec90e45f28ec0c6c3fe5041601642e32e4ad50403e0ac7a088de4f04a5a074eaf13e52966c623524e6196b541e5e5f7209bd116b07bbd66e813a9bbca27f42deb237e471ac24d5e5ebac0150f9fe6fed9166c86a9d5547a28be944090da36ed68a5acb52d084370fe66570cf3778cd009dad9ed975074124de0c2c6df076e4d4866f4a3d16b9292df09e0a5bffd747b6635398fbef6d22e59e248ce9ee8244791aa9de1f907afb848d161e9e8ceda90de1ed41395ab62ce56be3e1e9c70e7ef4883b72af83051f03e8509827007282edc49e441be8b5831642285889fdf9ff0ecc2dcfb7f82c1a16d14824a24d920e57a9460c2ceef0919c1d3ad7625332e3226847608e0f70545011eb93130d8630e323323ada24af28fb120dba156618a36d9efc0c09abb3bfe35a05f4a873328a63e6cd49a24ccb0722bbf08a33db1a538c3c28a041e1361bc2840a065c644695b5269e11cb9114318a11a15b12b6010d22e7c9ed019706767d5474f90a49feb4699489854f5398d8b30cbad6458ca4cfc8a293819b0042bf28d5264dea3ba8d606419b723c3590f66152ed808afdf237835ef5e7a89fc901f810d122dbbe4c308baa1092543f004513334e0dee3f6e2d69fd29f2e096c36b081d119c7923ef2d1b84d155ce6740c55d1bf7c34c80f322d0b5e248c33fb0b6f59e6ee702ab9009e7d1638f6c35f3dc66566573e89499648da83863840d4084b9ebe2f55f66c4a04df0c054aa167784ab8b3fa2773a4e7a1cd78daae974116340b1609831b5e919456c46bc2c6c97c6b2c36171f6563c57e01f16b390cb460f5a650cf5d4908642fad4da47d02cadbe95f5c30da514d0709e87f5548c33b818dba636e9f2193e39f880032fdc025b3293968657e8b0ff5d64c8254cc37b9779a03801c606c876297131be02f61a0e055da0a4d6c0b4846e47940636f263c7d2e27a80905b5bd65f7265bca94640a0506c205a305323d1f053cdcf703a1463a4a29d534aa518d6ab5d21ca101a289716147a6dfe18a47d5ab0ead68fedd42f362f000f06ad10160737d0bcdd3c28e2d3a2a1b4e6f7a53080255ffe94ffef4dc2a997650d9500281aac31f31bef428d309481303a4893501bc9351cd205fb743196edf1410ecb659fb7ddcd08b2844d891b3d0e1aff34b221a03acaf2680ffc9cdf547e53ca05b271cabc341d7f425cf86d25726d853b37ae8c26a8c6818991f1fd729ce8f57036d9d429ad8cb9664765cc53866f513bb23ea616a6e90e820b945563266d1c8587466d1cc58f4f76e8734312578e88e5fa98ab61895cbd58e9ba59a57bc5c7b5eb5c713b2dc126ea8456702923e7b2513e809d91e1348739885e90eb3502158c48271b896343fa38f4d910ceb8b1b5aad92d84e9353cab98aadba580d75ab252faf179b979bed05473bf9d94e7e482626ab556c359b594dc1e2c0020710cad4a2d349ca301c588c5aa068d1b12d3a362d50b440d1a2d3ace611823a727d6c89dd1623b745a7c5a76b99dd969f6ad352a4a547b3787ecb122d3d2d417e94b8218729852840d75f64425180c221da579a18c768d77f023b1c332a9960b77febfadc9bc00d90f89d06469f0ac12cfd44dcee16b047c3b02667cab43733e38e42a05bc311077a7369d8a2d363478679e048e3ca8fad6881786190414e95ac8135eda656dad4e0f430e22606cd64cb49c2d06dd9d169e929752d3adc90c7b0d7be7e734321b0f31b076e885bc22dd67b84a51ef5b84d881619da28ed68d8e2a365099d961edd873fb0fda57c3538dd611d4d1a42d7df7a2e134a45319ab0a48f01c4e210c201d42c0c6b0a9b1c7332136a008a924adb400e273b34f85ba0a65927d71f0721d75f8786e996bf26a4a759236701c575ff559066f9139a068468008a668d5c24a6792d2518752d39b5b3d1a29fbcee762bca888c14ec8fc398dea560d1bffe4a9b2b9f4c12d2f5c701e4302b9ec601f4bdc0f298bc88ae789ab50407e318ee76912968624c2305c3d4dc3011fae4a5043bb6e86c1bb7926215db463532cfb01ffd7aa10eb969b64646fb20a12e86bab0deb458715aac2f16b3fa2540816675c82de1865a9cb99920bd61179a73fb4b35373b25609cdbef42dc7ee73941c272ab2457ec61c5fe1138faada10e397677e4865649acf5c0918b00f5eca7889166f5112024aba01fa7e7fbc3f4feab242ba15517aba1d5926675e10cdfd78bcdcb4db366f8a3f4fe2f38cd5275586db8252d9ec21a9c66f1377543432cbc4c58b0cd4209879c78fc9442950da82f7d2904c1e964c4615434a51d4ca1aa54fa1d2d597ad3b367c329ac3f618595e4d752c90414e436afd70d5090c9e4b96068d1a59409656036bdfc3ae430a597dd39573ece957376382dca378226e63032b02ae42cf24f6185398ba4393dea51631de20ec71aabc97116afb971161974a54de91c09798b4ea5b357b1dbc39f57b15387aa279ecbcacf73d5d7be3fa6677a4f637aae7fa7793539353835379688edb7b74fbacd61363f916167b64f27d0045a6791f175a9ce4a61bfc3f1e5e532d6d795b12b1cb91cbb82cd002ec7acf0737b5240babbbbbbe646caa63ff9ca0ee42b4746a2c3487a30929d2bba3537f28e0c83a1653866de2ed579ae1a9c16635cf1f28751dc30929ceb5f51503c177d14cf355fe5a235a5dbd19483ddedd69414cfb55a220a5686990b83614ee1ca644deec80d55125cff4aa35bf178aed5122dfaaf6249704c5a745c38ae9c86b13c0e8303c859fcb79e3f1c33bf8d984ea587dd55cc88163d5c7f14586cc69de11462972558984b91225ddc3695c2eee73a14ecc830d60c0cbb0d837aff961db7dcd2a3bbbb65a74587a169001d00b6e8b4e8b3083d9f05d2d0c45a9c9d40eb2c4f573cfde32a25859b95fa2e77fc1504657a861c328cdb017b6d7b33393c8c3024f7fd73f4f4dbcab873821a0dcd2c266d8a09b875ad52c50c71a7c547b3fafb96d04366744ce3c10741ae823b76f798c39c429a98af627755a3594f9802d61e1676c7613866e1c0824521988503a826c7614c3cd72d23c1818573430ee38f03a8c39112e1730ad8751e9d9dae07c7c891124a595c99d2e24ac956ac742cb0e0b944df8274610a86398532c0aab842666cd169d1910f134cb8de65998d912bc30b64a0c5b0031c9343defeef19db75af0996c6b6298416b15d1df28e5b11d77f82a418bb3890ab435ed008b6b9df64078a9f3be79f5a3e907f11892885d9ed290dbf8dceda7d07fee72c73f4f2e9f3fd23ec4861211876b4777e29a28d9749e7f49928d1b5ec32dfded9835de48b8ec0f0173b2f071072274c9482913bbe30baf3c69cc69a35763d2eac593ecd721cdcf9d448b3fc9980dde08eb4c89d42ee488f8c2fdc080816d530dde86aa0f76c41edc659e6a72e10dea11bed41654f8fecd15e7c7bf43a9a04f96ef2726b36dd68d35c847a692f24d8fe4e8255739006d1242d0a81b40b900e35cbd46817777ebde118f993e7ce1d2c18e74ea02ac49d93fec83571e7733d1cd33fe76baf1b0d87764fe37dd7bd176e34a4f7de0b77f09e1472386ea05949a6f794d2a63d302964a3947650b384e6923bb597b3cc9f34e82916cca2506071fe9c94fec856dcf94d7b448f522868af20fa93fed6d111d7d14a41950cbba02e280d73ec703910e36ec052938eda8e963a19b30143a5bda9a3a74e8a504c22f553ede66a38773e9561d7e2ec30d46c5a9c2f20966ff7de8f2907d874ef7d01620a508002dcf98c4a718cd603bbcc9f8fa2f97067eaa65297391cb522eefcb174c7146b22b83375e78b5de65b2eeca8bd3a5a5ff335ae67522ad25e2291f66a98941fedc6889df9da12b33b472dc89d53e74e1f77f2683d777a6bd484dcf9228d0a41059953fadc4941f99d45d59e1314bc2d483e907c20f930bbdb5266f694d87e1a57d4ea6d61332080ca2b40562e9ff458fe715e59816d3a8c4afad320ed69a8f6a270076d7bd1ef404395d47e476b632322ada381a5affd78e306c74c805d0aa0c5cf13aef312d8c5ff851635ca46e80d1b9153ced99f104572838578897cc99bd9d29f6826dc78a14aeaa7c333363ffeaf616200f1bd01c3c6b3efc54fdc27cbad59ce047fc27fc781a8524a29a5333333f3125e977edd886cee5a4f6cf4be438bd45ecad58e5de87f7245753071821d536e8a8d97176e34cb1f9c71bb5bbb736e96b6e2b3e5e42922f59bdcead4db49a2d73614cc0ecd425f872eec7c990187a1c0a533300ba5479861917e0515c03858a48f92804f1ae000724557fae505f0460c7e45a18d94a71f11eb2fe39bcb5661e953aabd4450cce9d5644a58e5951e8e51d9ba2d9c418204b9c9a146e811211c23e3b216069132743c20538e2948860ebbb829e8fa6f405bd0a86714c4cb310599826408f9112aaf8c00805d8d596c54db9e534e3a3f02973b5648737236cd4b62a9f622924fb3e66b4f8a694f79b61e2b449bdb732b77cefab3ab1cb839cbdc74c6110fae46736298f13ddce06a390ef3d9348b663f9babd16d14ea9877fc6c4e2fd24f8bda8346aca841d20f0b401cd3afb520a4e76a1db604b18bf6293d577b159b66a990dcd9a2f6294254b47fad05ab851d5b1072b591f47355ee04006ce3a935b0f7801adaa7cf9e73720a69f4c0f5f72231cc971797df9a56c30d67917f633a45ddf1c695a420ecb8c5c07933dccd21c3e02ebb4807d80001c4e62c2c2fc82ef24176d946ddcc66124337c3cd9b11c7d2e51929f2662cbbd43023baf339ef01ec2c7334bf76dd755dd749de489b37136e2c398e445fa32107ae87546a48a8fdd5449af44c7bbb37e3d367269d73ce508d524a6734514e8ea6699a26f266449e13ec387beee640f77ace6d58c38484e4cec83f2ecf09563effe6314054472cdb27d5441b6966bce1ce9e5003a2f2cac859c026ff7c66174a676bf5371789b4708845a11388357d090c350db9a1aeeb4e3f42431d8d356be4866645ba8e613537746aef813a641d3b1f38fd09c4a80486dc6b83322a6f89e7d10c95848ac0943e8950b34ea6f0bafe1078798db38ad124ac560239da7347aed1686e5e14ecb43db0fda63fa96e291c29cf94e93aa7e7e25ed33c17f7353b353a0f444a4d4a1958cd4db3a60e4fb3bad7a991cfea51b353e3a35933acc9a931a2c5296b7ad4ecd4dcf45770c481f67873522a7a999e73db46f56ea2cd1f0792fe158fe452be7ea0b6ce7297624d48f6bb16a77d13eb59e652699a802410f57560ca832ba0d8e20b1a78e300a91f5879a7aac392dc7e982ee79c33e5d383d40e2ce77209b984943ef85343b67f943e64287b20848725a59e6be49c3fc7fd0ef72018aa7ea30176e0f4362296d3683c0acaa79e5b4c3d0edffd1f79ae0f5528e10cf4a23c4aa8c3779e0c7c69842a9447e144affad1c9049e5850c67fa0d4ba4ff53df77d8a23914833f571f353d473a51ca542792ec5a954a977147f4e5e9a2ad12ee5c590e2f8081752a045ea25ff529e03555e05ce78f672c88b42e3657c03534f031c7d0adc1e05e44b8166cd86b1093f886f852113ced3e5254929bd499aef79de244d1df33b69f2becdd4974ccf2d9a1ee592a69f2d9a4e27ee74e2508efa1390efe9e44b2853ea4be1e94fc051de5478f2b59ec83f81fef2f428707a2e97713efd89ebf49ae7d271fa185f4532fe24c69f4e31c2a9423b0bcaf812184382a87719c447101f313a26549e5d60557ec6739ecb559e861d57e549def468d831fd7bd1578e4b713f5be44623d18bbe1785fea6d0950ab98e5c3afc4b5f4d9d946fe2b9be5779f65ca94f796e312515ce406fea53a10e2da6fc6c31e56bc91b7ddf9ca43949a4994abd07ce678ff481230ea99f9ec7fd0453cfcd2791e69cefc9f05c3aba9f5f4f5d7baed10c5025051ce5cd55a552ef8ef215081c77dcd4cbb8e8a6461e6e2a1c775c979d09447907539c0a1c714899bea6de14b25c99dec1d49bc0b11e49a5407fd3cbb42b05cacb793a4a4fbd99fade536fa6fb2ec573997cf5b69bfa9a0253d39bb16ffae9cd94de5f4ac94d4a3d28af173d29d7270394408bf463800e006968913e0a94e104c2d059d407feca04451be04b8b2ff4015aa4a14c4a071d112d9c9b4a83363535c4ce374d2f72e64914d67fbebf8d6824128944db084cf9c0f66f221eea48a6b438fa5a1c8d46db735fc1140f2c37f2c18b174f3135516f4d5b9b545229daa48d6db461471b978614ccba0d0f8110a5d4bde79cd329a594524a29a594524a9f524a29a5f44fa2b0f3e94a8bffe3021ddb3036a80fe25ba89009fbfcd3fde59532eda0776b9ad61c0373d6bbd24b2932791c3e958726bf8936d07b11687a1f409cc372ac892497975c8ef5e0dc3a1a792ed38f3c97f752f42ed9fde0e84c5eabf45ca493eb14aa48d5dd73996cbc782b40acffcadddaeb91e722e9d0e2e8a92703df2d54f5dfe0891c0c22bbee47ddcb277c709d9d3b7843bfbf4804de90437445cf3787e892c21bfa5deedba803e56a7b09d6233b44a0f416d07d3df15c3ae4735f6374d2e41db42f024bcf5e8e6dfb9209a84305225520771f79b82d2b901c7d953f0ab91e910d707fae1cf881a8195bd87165f42ba28d962f7b5a94737a406c38576c979aa8bc32c30bdb3f76a928ac7c1b2a3faff4acae33895d663049d77fe65f5ed51b74776f776f6977685c2aa9f4ef07d33dd7942ea794f3b96b9c2abfadc31c5a746d0b477b67286339e8e039780e5a8b8cbc1cc6013f32a759fcc5cdf27cb098c842de2cfedb666538dae91dc1f68fe268ca50d3d1b411ecceed5df6e4e0ce393722b9a120679ac0d8a06e439c36b470b4d246bf505fba30cb7c026c08b873c89d3fcef766605d5c6971b638271275f3667583a387dff3b67946981c98c281f5ef1f85a3aeb2f32dbce185e0849b6b72d9a7093c974bd51dfc9ac50bdb6d2f5b87128c8007cb1ecb3e5dc02efdbf952b605936c4d0f589e2dd784b2c508b5e11769c409e10a3219dda5f4d6a90edf5d7c973ce395b6ea41c1a3354525428a94f468c13d4c954b22624afabdc6813fd6063451a9dde5d104abece7e2759d893203b6eb755b96bad95be454df974e6a05212a9cc3ba46479430c417320d271eed73c029424c87d6245aeb73314fcd587dc69d199c3c641b511dd390eb335cb649e9df322503571862eeaa67734222ad352d7e26c91513aac64816519103b3220742ecb80b8b9fca3cb329b25976536492ebd2cb309dd11a725eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb3d96c369bcd66b3d96c369bcd66b3d96c36739fcd66b399fb6c369bb9cf6633f7d96cd682e7c268138d7ecca9693f775c52e7f3d3649a5e99166960e5c3100377a7a9095b325519ca48d88b4c6704cc2465ada857b33870bb9363f84d25ef56146a656e9d8d71f266b8018532954eba134f876bfee65d5687b14dce4ff27a1dc98b21460c4f878b7b910cf13bf618e0afbdc97c67b73084324658f953f224d1d0eb78366bb6d87daa82fdbc247a23d178cff3bcae85e9329f55e69393f994f994f994f994f99481a7d3e9747241e69393f994f994f994f994f99485dff77d2e4c97f91cc97cca7cca7cca7cca6aa8a8a8a8cca041e31f04c31ab3460d6f594394f994f994f994f994ad802018d6a8b1b2c2020b2db8305d70c15bba204e97f994f994f994b120ca7cca7cca5a68c105516461696901808d69c386b7b4214e97f994b920ca4416f7ae0aa19282400a022908a420743cbabbbb495158f9ec2f766912303333333333333333333333333333333333333393889088908890889080c0cccccc4c0242123bf8370297221f5a11b42246f0c209960c01830c342c7955fb3d925ba9e8c2fea8a0a5dcbeae457e09981841bcfcb2dd4f5558f9dc74a23ae9dfd759200370e5e7302fcdd22f2fa322eea164977617c661d8f510d5a2a5d4c6a5d767f78bb3b4cf6d0f6f10a159d2172f378b6ecefdc20b12d65fb258b666ad2ebf46a2f34773c10e767063831df49ca8180de173a2e3d1f1f84cf0b1e07301ea07d40f289b51189f13a72a4e54a06c50361d8fd1103e273e273e137c29f852d0f1f852d0f1e878a07e40fdf003ea878e47c7634e1bdd890a948de581958fb2f94ce0acbdc43851493ae0bc3fe06f9a81d6d3dd4f5049e510298b0063760f30dacf705acf0776a441e38a2325b625a49187cb73f665dbb5d876ceca2e4d851dbf3a471cb49efbf505853ab94b77f7d9233333bbbbbb738ea69ddc70d22e12490906d3e9847447e1806af749a93b14964ccada5c42ed1b4e509c6fa2ba6d5c6c5de7f1c89bc17ffec6f4d24d240519f64435164d9512d6a2be9403bc70c30a33ad0a4a7ca02561fdb28f12980c2cbdeca324075b12b62ffb28b9c1d75d86c9c020030d1d8f121f4a98b09ffd2efb28c1b9dd8f0f5bf1831e42f6c43ed506ab71e5cbef5bc161e5be4c82564404d4a5982b3f443d1062e5470bb9ddfd7d6b3a567479880f4df4f6b787f81083dbbdf263ca2086f42085ef3f25af1be3f2901e8cdcd1e5213eb4e08edf1dbfd150e30cb1423ecbdfe47f51c8275c0814c811c110926421822144721c49a2050c809460074a482d58fee7c3437e1880041358b0c510487011821d2f20225394c0a608394462ac78810e8c208512b640426bb4778a6b032b5e4c2c91822b5ca1c5cf18a89bc3f4333bb18590d68e96833b5a2fc88191567525766e5a75f210c96955fa4550ab6a3438e20851e760bf4009b156dd821c698247063101ac55b920825ab5760e4a19dc80a7553b1dc43c11b020c741011401c6ab25b170c10f20ad6a3b07e50f1dc05ab5d439c83d50824f4eab411516ad6aea1c943e6e5e2db90415b19654420a3aad7ad23928655c00b56a8c1eb0569551832bac70f3f115406cf80a20363dac10032470e083900f9d944a35454b35c5ab55535a728aec084e921f2fd8695594203e5610a49a02a5874a7e8a10ad14215e2d0755327cb5aa4a4b5699a4f9275fd8b41c943e49cc88006a559554d2830a433e7454349509d50218c995a18a882bffd59aa4f9377e38d104ac55654c881cf4b4b80930d21e57ce90e680d2892b92106a35a89aa255553a076515433fad3aa37350c2c08757abd2e81ca44161f4b4ea370f90404407943ca8e0c44b909687d60b18acd5a04a8956add139c8834c22c4d35ae91ce4817fa00515465afe0df2902244cb435ea911825fa3468d1a3558e017d20ec94337c745156ea6945d8f9ddd53ba7cbf890d8111243dbb8547f0182174a33002e8721446fcdcb81c451141aef45c5e15962ff5f0a545f9595891bc2da9c31d39f70e8e966871c7e52ac785292dfaa048952fcd92a33045f668b11b46b6fca54e8b9e11cb1a6853e5ac42fe173c6018b9ec0346ac795cf6f942c8adecb9523eb6bfd0b9ecf3c5cde521977dbc805df75c29286cff680b724b250ee54d8b0196cda844ed16463dea981a81000010004315000020180c070462d1703c0f24c5c90714000b7b92406c523495c6c23810032106632086a1004200000618408c718628a34e031f2e31e3bb984030e3c072d23774d7e8dccc340dee1e9f349a369d8c69635a80cf4a699aa0bb3bf928ab6a9df1bc3952f3e43426e8dd997c9655b5ca68de1ce979529a1394ee4ebecbea03e65529cf2d44ac6b6e91a84a9c68b79ce99d2d22d72b8e68b48ab8a85b6bf6a8c4eaca45b8eb6f91b4afb9d3dc41bf9e10e95a7d44d3511a4d3baf7716ba7a53444caa9c266e74d7a18a28ed0f923d159b354b83c3f853d93ad219216cf888dabe28aee90d5d089e47d33b32a75787a209efb24259cd67162ba3c4db040677cda352610f36388f483ae099cded10a586efc832fea0632d7c3d17c28fb9806a82a300dcd5f2a4eb6ff849b32f8d4d601a3c70c5596c0073e6a340d1ceb20df44d27607091d033c6107af0b11b7b2307a0b5fb13b730d599463f5849b30d10545c899710dc42aa6b71848761666b6ef65dc7179f73f0f0dc01995fe6a4bcf60566bce50af85c40bfcd611f1a60d42f9e71f3b76cb50d336b6912214051c75ec1852e02090cf18beaf70aa846be9c57d06a2ce8974a19a3f224bfba79018bb2fb52af8d0e02c6b3c0c5d3f7774f00736e3a012969aa7cf7e809d9119ef257c33430c3c116135c48154088f6f8bfa5b7cc106abbfac360804e67e0643023f2040b2adeca058c29fc2b2384752cf70519adfab7b2a3e5763da8f50a9ca4c832c21219bdc379d28bc96bcc9d0d5185963cb464a9f5e2f31ea8be8872ba0d6e976fb70d9c4aa6fb452eb8ca99064a9cf0bfb0e4f388b75c5bd966eb825770f7f7d3dba0204b810dc36ce625a8cf26b93682f6dee68c14f0ff5fee15954f33fa698e8306d28309e172c841d2f2b96f8b1aa051cfa2dddf0ec88c8fb678d1f2670732e3c75b2a68f4c26b0aefe9326aef7de708c0659cb0fa8a173f3b25317fd432c5cbd79d93181f6959b15b0752282f0dfca00c7a61222c0cbb492efe76486efea0658a96af3b26333ed0b26ab70ea052f66e926ca0da1b2d0fe945e1a2fe3d0041908d8f6d79d1e5676724e347b7546968c2d76912bf6e6e8108d38be9949f0360b8f588b078acd30b2e02259b3fd0925a8947869a33317a8a96d2726e175f3b25377fd42255afe68f672398ecb3f4211b1ad52f2931a410100f9be88936da7ecaf9dd90195e6aa98465e7e4bf9431a2498e56bb6e7a82435f175b50bd6963ef7c1be4840318fedda85d4470e977c3cd79682da1aa3568f92959dd1b6ab98ad1ceadcc661e304d1811ee3b4380f945f50430edee383e058a70e87757f4a0560f078f306a9cdee0fc6c0abb5be49118ead4693fd025c68de8b23b7d34bb41ec76a79f61cf2d62dc883e3bd357b327961d4d9b93aa4f0a7386ce8ee9d3ec136bc7a78da4d727c23c4365e7f469b689a5e3d366d26a409309708b9eb3f2cf55e8908928f84b53242ff1d188c54df98aec2ebe58c21ba95122c03d7a5ce07d46431f0454d4b8376f47a2321f1ae19cd4df11e0ce1b23a2991a25c2dcbb1d9f92b1e8e9ab5c66ca1229d6a9e31180cebb22e23bb528c26265bef67d9bb519d8cb87229893fa2bc2b8f263c46aa6a0886c379be394e6a6bd8ade664537500cdf8af06f6af1088895719dfe2e56f8ca9d579c086bea5884a1b7b5863b04bb30c4cc5e95d852e084e094aad05ecbd1522160ccbdaaa8c1e26db083161df127a4760a5a6867f5174220f02a72d01784edcfc180b70651ef3b9fe084b604eb6e0c1077a7c7f6b1d3e9495918a8e3dcdc2228b8ea75763484323e4fa497ddee518b780cc8868f7641a91d3f347541c49958c24a2a6f84ec0e1624b4d06611a75c0607e2861aa1e2765e9477bd3685a47c7e64bbc4691dc3429bac6a22bf429fc2fac841e3d740041be0a5e01e2527093c4e29cf16603c28457a71ada2068a44030e006c0f6e396d5fd3e25916aca90b3abce5aa1931bd8b8230d3f8fe35a1ef1df4a1bb282067288568ac6efd618953889ca2f4c34dc6d377e94ae2a9af65e2d192573b31ff72570515a31043f7dfe3cd7f0768ea3c3117a01ff11e8815cb38dba52ff55c50ef148df42b515bc2d3a154178e29a46aacce392a8cfc8a1ec112036ef4a95984d3be981981a378f654f409cb015a8777266d00471b21df31802d7c34ab75553730e9f30d4763a098843bbe0412a011f3f7072f1bec3eb73451324cc0774f94adc432975423e34864c396e74e3ca1fbd7ff56b38d2ee5568d78780c1c64f39888f4a1a7fb712fa7548282b214b89c6bd49ca2cf545b038b3a5ea78af8dbe88b9860558bdad84da68a87f2e8118a2004742944db8f83eaa74a7c6b05e57b5594757039752a728ea2aaeb7e42671b2f2395da0d3a6ffc1863a92ba00c028f09aa63d3773d45937f3f125056bcb9315799994e5e2ebedd67fd1be220f0591c86eb43380e26fbd87c077f1e96d08a63bc4d0e76501a06438dc61555c711d1585de79565603ee92039f07914f674bfd80e0eb86bd05f80c77d05673eb45e9782448ff785bd8c8e4c0218fcd95629eb575376babaf8bc58f1e3b3d767cb1f63b27ac698bc3ed959c08dc1549900ea5cd1026c1704b8d68542d61d5728b9870f7fd21028c17c9ff98900e1ca4fe31487d2cf23a858043cc1a9a79c5b297069700bc45d116c08c1fb1ca7112a2f62d9aeaf468467e12e781dc372780f7ce6aee39aed2b9519a590150e01cc1d96539d83523ecf580f5b41754d2abde375e0b40c5b5befdbfe1811bb6227c2b1cef6251650a49c16c16353838e3c0e94a228992c01e76559f5da987b076eb06b6b6d949aafbe9251d494ea063de118a3d6c79a9499d38f987ee066afb03c86a2625e0357c2edfd0762327677ae02993fe00aa2810d4353f29531ba6b596043d2a3dd781d7b3f8aae085043ec223ad647596ebe7b7426df748d7af661bf48dcd10b70b75f7a6d4a8b1e18c8d1d80591e83afebe8072ef682315f1654053cf42fcb5a14c27557f012a17437265153815589827f7d1731347542bf7cf578b24e3d8924559c3544d60f8c3d5cd2b6896016e6be90fb3f237eb682e974abb5f2e22e91b261c0a51a3c521914cc6c7dbb238797d8d85f390b1e8729d18ac534553f9c6bd8d7a501f254c49cf2e6da8460a4d150e61e956c84ebd27e59f86830dac1f968e96e3d93fd21a6270293532e31c60a3bf8a6b2c9656104bd281c7b5172f242987d4811d0c577d1da55f62b1253383a5a232bb530c0d6a553215c01c3869e78eae1248039fe5444a1a7658914338f098b143a702c74aaeb9a71ac6c1b4f9d0b00eaf45063f0aebdb2e677039a0dbf3bbcde1a4a72600dcb9c70fafbc4d61335b0eb6b270e38de54b68ba79e0128dc0c1aca266e3c2b5bf81e67653054a448ed3ad70afb668324aeb2c57316aae3aaaf923f53b677450dea0ab49ee1c182143fed092cbc47cdf83d0704f4ef48bfbfe6bea022832551bec0c9b36970f9dd0f453d7d33cf0d3bb1de46719a6829dc619f26ad0a47bea403132d2536a31e51dab5929ec2f7b53b162ce29164dbe4d2a01acef2c0afa70603d400c67971f55cfd4c995aa68ddec1465e1c1a76b884570e70c48c6dd4e17e5c9c1e0d21ce08401356e564fcdf61afc4a2098fa119bb38a6648f2a0131100105d588fe835a592e229d6e8666479d8c5adc16b6183aa8ad9befc406cd6150dc24ccb4f7ab1821ebdb885e3d853903383319d372a359d3ef6d4828d4a74a7ac90f90d2672bb63cf120c131f6a60d889b8c29b26d3d9b24c000cf9ace997be685077fbbe6eb3d1439669fe631784b7ec08fc03d841d9c99d205d07af5162cc7568cc4d9472e338249b445d461c3a2f658b3d35fad1e2d7206932bca078a260cb57175d4d995fc784967a93567848427c0c55e7a53a1a2b30b3e8f03144546eda55f012c0acc45d411c70fed59eef0193f09fba7b7f0da00db194330842b6a02a314845105ef887620309cf018c7731a8514e95d94278213a332f16ef910e453ba436455c7562f4cadfca604e54dabfdb855d532006f715965184ecd941c467cd4c735f38577eca3732473079fd030c8539b7b945e697657a561f08127daf4655cffe00a58a98ef215cd9771be1acd60103b5b33500e7536567952259637bf994e348bce59d41ad94eb3c28a5dc9f239b34f22949d68159bfaf8ac951266ddb773336f39e2d47258d9e76b6202dfccf1b34e22d0d2eecf85266788f5d36220f4ed34a7e1b30ac8e7082c20392508f82e5e818c946924249d42fee873f1a363f2397a9387d0530d5af0a26b18cc12d4b50abdd60ba427435418e9197e6c90aa7840e2b753782d9ca6f985e1a0292429e102c968895b87678fd277de94452ce1135d9951f40506b3e65f42c6d8c3b64177e71d2ad975ed2d37eb86eb9c5c796697feb57c52204ff902c1c40f486467787d8666b2d070eb7d6e22ef2181884f1618a61aa58a438662cb66855c9b94a0e44b33df4b4a62ab3d2529a3b82052929ef12a3a9b4c54f8ea025ae8ee18b47fa5c4575df080a91598b3a72e9c3c74383ff2f69b8d289c79853284049b4f4f00d236445957bcbbd1610cc13161c2805447b364df445fc7c031150852d6b8fb2e682278e11b7e99164a7c8a3418ae5ca87f08e90280b10180e7dcdeaf32d6052b37a89ec0a306b73147cbe321c0dfd174cf2bed047977fa8cd5fb48a13ff09904b70344b50ace1e957da194254dfafdd32e148c6578092a3cf506494733af283b52f597acbe233bc313c63e2090f19780c39a2d88555440659645b348a232f62166c52d684ff64ea17f971c2e9fdd77e14fabfa6e14da1b112484d489334652f1b85765b1477d91c1c71597639e09d6f2b2e89f83a9881a5033686ade38f237add885f826405305c3dc25e07cf5199181abb3f0fb1e9186c236789f5841af5d07f4b03ffdeb05d1402e52982da4201fbd2eec6272a0802834132def7138eda42fe26f3a4025d1e155fcfc155c5a19d3f50e95c73bdef2ffff519417b75b1ab4535f028554516ef05afacf0fa66d68d9d01d33c3b800a5f0bc06cd942435d9677d6f2a1520b18dc1c73b810e9096c38b0efff803310604655b47235a1f2f24059ce4c0df5015ab69f118d5e2131fbc7333aca0da622b7e374242d16dc28c47740e78c512e1e888ac64324c999493cbff0927c27b324b7483a57cd05b84454d3295f213784af491243d6312a6633a95820f8fe6ef5acdcf84055dfbe6f9812600e6c0e7dc3fbe0bc75f03f5f309e0a1854b4a2bde0c9dea61864a813b3035ed2102d548a1c25f46d2a52839e284de746a5f1b1841121f644d971d691d9150ed62f08007f8c4e6f5b9ebd07b932a348951ade9b065a0eded31adf1e682541d0a7ebb5c52b231f8558fe7cfac6d2e55834d0ed47ba52c2e63a433cfb69dda8a2a6e1831ab1cc2d11295b5a516a7b6b58b01a423f89e1362f4f54b0105940468dc3ce6c4ac76e7f4463bbb4acfd2cab8b482a61c5e55a57727b0f1f46b317dbb76ebc27ca681a1fdf786ae9ab109323a5b8a46b5541efd919414c5fe96b8643f1d038b425430aceb31590857d693b764d644d52db68baafb32eed472eae29734b45a1b6848761a2574c5cc8a243e7963b9ed9e8d99041c427969453484b95b41184a18d4ddb7391aaae249ec63879915310e032e920c781bdfb2614c57ede439e751ee94a97be4e430f2b52acbba386ee6b6528df855b6c15099ab3e6bd023e806ff9f76a2a13045b020298f5c3f1779e6f8fa96048ca59e1dbd9456c5f026fb73896f0804b1891fdd8deef6a62f3162624f52381bf20b202842cfde19f63c460e1ef8912c8567a0672a9969ea7cffae08ecad186a9832ee9e64960372b5e4cb3e25abe19a6c51bd24c6fca3448c29b9d7a52075343604fa8d7e71bcfe877b3de34a6ea299ccc4138266eb46897c48b337b4451ae796dc6afe8e1ec1eae07227c29980236dfd2caa838e126d804a5461caef39dbf92e1007c4455fbd0d6b0158ebd17e438074ad71c6c9028bd892892c87a27aaa2360b67b22f89145f7de11c516161a56abb1cd93f04544413d3198a8a0f14962aa4858ed112b22e1e763148e84c50429b37e93036dbfb157f70189be47ecece2629c20e7bd023c1e30fa75598d57b8b27837f985f2abfc451f4125ffd3194d37633ccff5885050161a3c444ea791f04eba41f9959d23c11e87bb2992970cd2bdeb025daa7b50cd4680433f468f71fc66051132063d4e0793a62190ca7479162e42e47f63d5773d3249f1ed41af15ed771732f920355a8cfe755765d57f0c6af2d07a8eb03e2c3b2a992431394d1493a92267166681b31131958f4af93ab16f8700153c5046470580b43dfc7d9df737564f9a2ca9b1204687b7675cac03898c9327913c28fddbc8d75ee193cf57cd77161b22423090263fb58a223b373ff7251096a0d4edf2491418d985c1324516653c3ca76b9433cbecb2b8560cdf6cb1e913aa342069a033f19b0ad79b986409059c4e790849662834dd5c30c23a65679e03525a26b2758a02300cf2b5c22b5247df973f667a7f3023bf29ecfb879462c7801c4fb6d425a06cb1d6efb49262ba7a0d61b773e109b7137cecc0abf20a7b806942ae647c408e161546cf9f24f209b33fddeb3271ab65e017d9a2143ed61e4ae0483b8952128cc8e0c1647c45049bd68c4f58d0d63b66f4f324c1add144f20a39094c90e16e285bfd4f46a23a6727681157e41f2e30a8e0b87c1211e82ba3486c887e034a2f6f109a049baec014e8bdd85c11a00def503a0e098279d49289124e73436e85a0c670001b089e228885d8823eedb82184dffd299893ea921c3f9cfeecc35328b553751e6751e63f552d55c0793891e56b4311b51914c3b3c7cb0c83655c08e216da33af90aa083fc9da4b10f0364ddc17a73e3e41a3ecc4aa30aeab415eb5f6bd3670d42e4ab0570295b388da791f5783cb75822e13c14d716a2f70928d03e807c5f7091490b1b855d67b944d9f54546477ce233b5da8fbd3c66a6a2006ad9049a4f740e0ab4a3dc21ec748eaf3d32a67a5f8e1df655ee0c482b2bffb7a9a64bf793316bcfba681dbd1669bfb4174bfc0433540c6c02fdf902a7820be9846460db2ebb068732d62ff92b798d05c5c3154488431f700e36f25de8aa89e57134394aa1bfea12e3e80c2e7a858831131bf12ccdeca99433e54ccd79f30b29c203c43d79b4245debea7082b90e3d08b9438bfc5d3e7d48bbdde22dc44c500f8885dbebd5bc3201bf5a3b0c0104be7e594188b9f7f0917d13ce5dc27fcef11aaa188a62e064f556e52322ffa8de38909480fc23251021475883f258290498e26820a2471c7eeeefda6489664a653124c092f5a6b1133333fc0ef2e71b8223974608e46cca842b84f8adff3a90eb734019a25a48d0146fed2db5a3023465fdb0d68822b2f7fdc19d8a6a210d48ffbb4d0948c6c340b8c4ac2e04dde0dfa53596eb661dd660db917453dc2a40a94506c665d145c54de91561308cd4d5f0f0ca291db6709a00036f166158397a243d3087740d11f2c5df876ec4e98804c631a040985c122d8711cbaa7ccdc9028164448716d6c6ea26777aaf710ae9935b2f78ca060e75a0b09a772cd10862f0e87228d3ccedcd10a9aaeabe46f643122fe1cb5ff058de6e8017c0f347f2c860550acb9d301fb508c7bfb0bebcacc88e1c9a75635ed8a2b768af694fe8e975ea3f2436f8d8459bf9ae027af4f916b5bcfd0d155127be8c5583f0b425d8cd40688d44a6b840c4963fabedaaf66058f8c1e29f5d9c008031ae5d0cdde501789ca5ace86b57f7db00ab8916cf2f03e73ca46cb3a221ce0c9481533dcbdb0eadcdde140daa354e41185ea9e232a1a71118a6d9cc1ddb1b15ca5917b58c5039f7d55ae8c1d5089b1726d5a0bd8b58282b2af88db6d2660c4f195bfd3151c55733b3681d8b0a6da3a5562a8ef08c64a0e692da883d036e2ad824ba88ef01533a3ce8f2970653ee130ccda28f12a3d01e95c4e3d7686d68a7bf9f7bf730a9d4ca85f4a009850b0ffc8800cdac2b1df933c3c71e847ed53e64269ff8c64afa5ec275b4c7baf9646ee90799dee5f3f5c826460201091e502cf1dc10d2b98c43f92dceaaebc85d08847837b6d18fc16f47f86a8992c69ec2b52cc00b8e26da346ddf10efa6abb21d4f167024e14cbbcbc91f417166884dab227f4f9d1763c4546906df10154126967bc480f06468eb629f3c44219a8156ef41c2c20dc426ab4432503cf6486a82da21b3cf9664be5c13314464f645f1822a2c3c5ec57fa320dc16b877b656fd29e20405e1dfba57155dae3f10094771432151fc10d51181f20deb47b28812e4101bec9701cd8c5f1f459f557a47a03b0cfdaa4a9cf3ba5a54fabee3d4dc4639a0ef9744d9dcbcaca5dc47d13b917180adfd41e0b3726ede9646197b151ae997d9d9c5051787bae57b736e6ae3cecf59d0dced18c39d4bfcbb1906dd601e5fd4d310e488b3d18cfa793c5eecb2601330e7b9979e7206d6f52c39fdfeb0772f2a1ee469e46225a94c16bae89a57760c3f901bf77ba4719e771740c1d0e2358a1526fb294f9fa8b6684b929fae33511fe5d95cceb89c813a3169d2159dc0a4bd2e320a04cb9b2a5933a0d7f163949be04c24b9ba63e8c284f6bc454649d49f0e3c74b764017d5038e68f0efc33edbdce8ac92aade83cccc4f84ede21f64bd87832f05076c5b00347356f5b91ab5587537d458f42d04b8be9bf01e731da143431a7e1019f304d42719afdd0a873b85631c9ed47d4c45077f2e54e34f9a2f54361b1cdc2851081c7010fd8f68a904d82e09b04ffa3102a9957b24e0154da119d7285fd5de9f92ca14a950f5acc419a0e41777668333cd3e3cb739b044df4d78113cab6f2f59d9b5bab287a0d1a011b2594e2d9064d912a0f37fe3f49c4a2b469df8e61426feee783be36bbf208053b9b2a82fe51026029164aaa90f0857a08c3d41c275f5e85430a0133b2e2547199efcfc11714a456181daaae21d164a22924f6411cb78fcd6a881807ecd789cd9e03b2b1c734bd64256ad40f920861cfb5a901f275928cd1bc103d8d4329d9fe25f1b222ad0b5a655bcf414a9c2858ac6fc791e8f3a8e2a994b7131ad8ec883b19e2bbb3391860860046d7785bc922a8299c206f487a414f5752b8e4cd71d122132151f33e00698c0e05cdc131f41eb3b631828320d8c650b6dcf4cb8cefbd581f8a880fb2165813e5f44cae09a1dee551ef8516bda6c78d4674b27c30b2960639b333e1d25bb0f3394d09e7c8f25c99ec82388921e84cd08a3ceb8086f45931070f103b6e8be10203ec2928dfdcf012d32b9b7a9c097a87bf308a1ff3ca77bc0a3440839e842ff7d66acd0fed1a35286335f1b2ed63a6bc64ac1746fca5699a3d4778e087074e38e1831f3e70e10b07fe70c02da0746368982d67f8c015288a1b9f06f072a1a00574ce3d1c78c01bd07383f489d58744f3381295050366e5b71e7003a8259f9300b65e1826fa7372356a40fcdf5cc38ed9849be9dce34f477278a356a6d17fb41daea5cb9e339e83b02b4272c3145d47db061a4af443ae2001dc30bf8fee644e0aaceb33088fa32c8c2004cc17ba2219ddc34a46b4457b3a08a7360001160070bb812242e9a994bb38ea07c2323b8c4ada72f0990925a5f713d8804948a5f696009b980d593a2f12da94f9215afaed2d0137dd038e7710d0e626c03501a2cccd099d32b004ade13e375024fdda8a8b407bde25fd86b44c61823e3ea4a4a3ff94ab1589806ef3c3345310ec3b387ea579a5d05f467c81955f3906d7c1963ea5a3709a310fccb25541abc54898f0c499c7eed956a7258d99f15cafa0704dc6e874887fa3b90a19a5163fd2c59dece7b6bbf6a0ae915edd1205f18b21ab4d7e92727420b8249e645ca8c891b928f60c3f9a066a445ee1d45dc72f85034ccb0c0c776ca176aa6e904b8c46057733f2e796710af348be2d7190f6441ab2fe71ccb4506b0f76294a4326175d966e9a5985371d56e5c97994535949a35ddc05c15a50e3ddf8d0cb4e7ad163eb1323defc69c8ec67d8e31f424a83867f483544fde18b326690367340c5c69e5e6a44b430cbeed48cb8b8a0e6cdbaa4ef715b0740458538223cd77fe94ab4faec5bbf5db8561b3084ab9a72d4684be20a81f6f92467bd40d18d4bbea5447c67e8747be5787575aa0c917ca9f3382f5feda3521a3d0adeff49b1f7793dfb1fe53aabb0228e90b1518370ca8abb0458d43d3718d0d9643fafa8b614094ee848035d368152b1ad8b783c3f0c2b210d2b705eca2cc55157ce304bbcf90751eeb6937eaa009aa79c15a724c5a2a834070b4e5810ca0f7d1d71fd701f26144abeab6de176c6817a2080cf43924febbc47135180a80284f90cb0e643f2de651f1506b76b2fd8b1a90abdd76dbb42148fcbe8de62df1ef2cda8b01058d852a0c84b510f3d284efa5af5c1ed2fe68487f80ca8de381f1d659ff6976ac9e9caea3a79aa910800a26e3be0c9456aa3aacd44b6f21cec45c21327829b68f3c95c8572197858ac6a95b6aa4bf5df2089d587dfbb5599c70a33f00b2701946bcd5513cff10ad525fcdfc98c9f0394252a86dffe5b43702f42ab6d0fc94cbd5ba9f642654579b53dd7abb836e6e97207cf7e4f25f487a40923ebd70c81a3b08fba4535e3452e7307c17e793a86207659943e97991889c154bb119bf70afd4fe06be5b7acaeb7d563372d6c94ae1e3382c08274aa2b910c14ea5d5f447cd1b0e2c97868fa49949079ea4492176be767ccf7705e5c1115674a8eadbd43abecb4a53e973d4d96e058ec5558cf6147fb8660ea4b5ebb04e3fa08792739fdc0950ae310bbe5b5122e851480528a03bd514d99edbda82bbae342c407c87d6e2de1686edf152b064d5237c130908b705ace2baf0c24c95644a89cf8b226a5f07bb5de8696a19bb7f85b5039679e296d7f9b5f48c536a566584a7acebe8556f010d4e5f3bce0ded012aad11baa8c9c4aa68d97598124a9fca80ffbd5f0c6e94f473fb8dec9e529046a87d634832ae28b7d8f1c1d4b8a35505aa576d63c0da6e924474c7d635e2e3516653101a64cb2c6abe9142508337e4f14bd0d16d45ecc5d3b0bbef111dcc0edb6e25f8c4afd5934370adc3de9c517132fa28a89bd15141432ac5529bd82e2d7b14ef0bf51946f05e55704d18a84679fd8b2f464648f5141110e731653b7a4148730e0269a1cc789851f354e6f2a282220655947c97717440e810492a93f70d41d486f707a7382b6caa4c1a483448c1561e1ea35ed844805e516815035012c672de7bf7437bfb40a0a3d38ae3fb9b4e4a1aba0dc906b1be28b9e40701594bb3db50c04c55d3e851d9cf797f28e44d355bdfa0f5341697ff134fcd9b4d212534109118f7e09658090247676b021cbbfa9c8294c2050abacd9da534159552ded8a84ed13b21f8197b3e2a728514436992e3989f22967670b79256483392a471451df02e48d55e31551ce04e714dcfb0129b4c36daa1f248a9fa298c0642b4aec64f1a25c3df67ce7598b6f41a2f4398da52af8e032ae91949f3584d42ec1cd667a8140a411bedd7dc2e2ac94da3fecd3f30a0e354131dd25fd994500804528002a7eb367fa909a94f2cd18a3c079db275f714ead7b450cfdcc3e91498acdbe17cba7d27e59348b2be9f8fa021555b9723aaadacb287c0918b5bd33ab7dc79c4e3b252b9bb4cbf44ac352db7b36400372a2a0b6ff277cc790cd5974a4d8dc8275387a6741869604b403647c2a8a24317c9436592718f610c0f781f48cf9c2b4324f25023d3f4a857612457634961fde1b2ed2be11dc12053fa1f4485942523751f8a0ab70d4e1b6ff579b7bffef7289156e0e44adaaf547b5b487680979b99dcd9abfdbe139944b297b1b0ebde0b66351958c11a9a6843e5590b60a85b3e1fa820c073e274abc944a8688dacfcf1ff3166faae4bd56217b20da305174a3d97162f997a07cd054599619cd4261d58ca43cd78fb46b87b5d18a0a4bffa47f5c58234ddb8c3bc85a37a403427ecd5c5e63d92c85d395d55da0b91ecc20addef0b3c003c6d2837299a696abe027f73b0efce3d150c029d15f5c6fc1fa5e8a499d5d403d498a6cd18fdc0f273b7be27e6ad846c5ee66ae6008e918150520cb07e7971d9bb06416848f763661a7e4f17a1f56020c3d951fd7871e9f04ec7658944fb88090ad38fa9ce08716ba909db8806e0c1719249d515158d5c142a556da3592ada9d9e84d2d29ffcc65518ae64989028683c3ff2f2021f32972854ffb2b33c5a61b7b3f280b95bd6392cd9eca62f49ca47a6bcb94b1103f1488121c5da270b2bc043f0cee0d1b40c9ef3be537f6ae30d783943716aafcd5959fdee36feca70c2fb5c36a770b011c20989f4831ff98647e9eb246e0d1ec6db69f6ffb6be711cdc11a96b1c477b3533770d3c6a4b0ca6fdeb0741abc73cc678b610838fe6241c5c1f6aeaa08d770c34a8d378ed91b715377847bf04c4b8f745d5406ab821ed35544e30d6012f4fa037e1a3cf741c0e7219b34cac84cd3463250958960350e8135a99c53fdee4c88749d9f1341496a0e6907435b2e6a9e625bd0d1d72a53cf79c75ea9cd01dc7f19d7e1fc6b839ec377bcf8916cb016b2cb08eb63179cfd2889422c1e91a996ddb2f7b354317347ee12c69f4889f008b5228a8d7b1ea2b96c91be4362489a503dfd26d4f0dcd0c16bfe03e7f9869873a3bbfb7f59a4ead32ecd3847f47ae93968f77029d3f01798f142fc10b69ea7392f94ce80d1a15cec11f5e3c04ad57cb2822842e4faf10a0231e4bf6237a14a30d81fe85d966217df74d8e3ff6f7c484d6cc561d140560f4d858fe194d45b546c00bf740f45f03a2bd0df042f286f6d5840a5605243c962f0ced10b156596a0aad801029c4b650835d047032f9aca6c8a96925795a1695c6bc10dcf2a793ac04068c190b08779c4bde7f59ab42991935dd33f8dd20cd8fd28d2e5d5d682d6fc1957350365d5a86bb3a465735b37d444c4fbb6e3cc55c765a71cf25508ece51ba0afe5f71ad9a840deee283259d728a98f6c1d4d79c5b4a98084e1dbb5e7db77af73f61fe61a9f3b9ec1ee62d4709bf75bb6a879b96de88807a07dae21f489757d0a3c74693f23726a9c7880a4622c3b230879369cfd03d0f3c0a36dc989671e5d896aff869ed75006f00d69dc1bce0dea8174c6d964e00c0656797a08cd6bc8ee0509cf3d1640439383d2677e2becc18306df0a98ec5d577213eca5715a252c59ecee0e3cb78d7bdfac601e5356fe6bf9638ce3a2b25d207a5e7ce60cf85d1894f4b702c7c62beb643cdfaaf436a386abed7c105bbb8ab95d42257331ba104161bfe9440cadd303f57b38d2e3c01662618177b0001dbc3708667041a66ce7203b69d7fa1ae0790e336e66bf36a7654155150aba25a2984bd6e172a8eb7400d47776fc04d06db450adf606bf1351dc30fdb3088d5a7d96527752f70605466412186fb87ca7804f36970f52180626622b77d506dffd14b3ef8404f8bc43f46cea1d055f7056d4ef788becdac15d6a72fce816ae4d055072af8ae57f84f0550a2c458574aae783c7cbae92627273220e35d7fe4d40c5a71d92bd977697e3de7adc21c55de5c7fa23e83835727d250a793779ca55444823fc73944c53bdea3779c2f772e981ccfb4f59b7cb02a7b6f938146291bdd7921a7c1c5bc845b2fbd4000a85f8bf70318fdce5224b518814d4896ac762015e50d2a24a348bea2472cfaaac3e0daac2d31811399c45314983c80f0f3e8f660be7e21d4dc75498d039183f377e968ac23da49b94f767d0b4ac4e0bd3b3a7f2c1358a4bfc561175d22d6af4535e88287af6f16a6f925cd5571bbfc1fa6b21a5dc9c66455de8ab46414ea02e4ee5b60d74ae0490e0d1c52d4034a9cbbaed861e79770d8742cfe70d84fa2644bb19b7b89846afd3a892da5ec1a94955408ad7bbc9bc8524713c07da22db7b71a564ad6353e5fab431eeb11eb137e29f94906c446f50abba6f98e8670f3498bc905c6c8e1f18122dc21053681c3c5e89b5aad5cd3cf3ef9375a2678b82a20b50e1b49b7f810a44b03f0c941c38f19c22531c53b680b380d0114d0e2b8d87bdada1715792143251c8154ae9e21f12c3871c4436f8be5a77e279cd909518114b644765462e1103a5ca98037ab00f532cca9a182b60faeaa247d6a0e8f3cf8b34613f2c18fc2325b18134cc1f1f5e28be12549654af7a18527735fb8bbd3e0939855680c1e994f68fdd4c0225e85e4d9e7eeff6e5b5a1d770982c48c9927147b0141010fad0aaac0dc04064659487d135e41802d53b4be90bf67b0dbaee904d4e843129f7889a1d0b4aa6e2e8e9a0e9e3b821c8d5502a0a7dd5fa0d2819afbf99584b2b9a8519b7fa93da6289a2ba836a0528f9b32493e9d10ea1604f0a1de457e1790ddf73bb1c4cb2909219c802c01deb42942585192890f9c8baadc0500ac377075aff682e3240590d1cb17766b0f12788e6a37f37514e64eab74843cc434b4b25a0ac71915d19e54bad30d1455b718e00c81ae4cd856b95f6db98b627d0a440e3d5280f4281fb5460e8c1cebb1c414607d2dbc2a6a0b729a5fda6713c4014ac430c162480a3a882b3e80215383ea7e0f8cbe99c1b670026183b5664c5df61a3a702128366aa2c808e7ec1e4257ff534f502a31740127e6a8c0894febca2fe8c120a80373b8621393b089225c8eb7d591cf3c4c31370a887820943a3166c573ddb28ccd55cb4e12f54f95419b0c239934fedb27d855628b2913b1df18141b3302696692c5651ca7f19b1d027c66d3c151bdc2d8a1fab24a38f8a56b527d30a5952ca19b01847a67c13e14f1c2fadab6ee2fcd76032c9fd760f85f284216027269f1809577e9c0904801797660282eaf5ddf7924c211b691d618f37530e167f35f7cfb49ec6b099f6e2b013cee58387289e000b1082ddb0e55fe73fb6ba67e998231c72ace8dc69787de768669bd63c4b8afd1f78cb9cd7b62e55e99fb55eff12f9a9b9e7fee74562e701b1f484b8c3cb9a71f5f1185017cd8c5f20085c219be41382d15c0daf95707a0ed1dcad35d1d39d2016ddda6845cf65dd1f12d67d94e50fc55820192237e965fddbe99536a116564c049b624de954738d6eb263116b7f1ace5e42dbb3ca7d59aee9bcb593fc82f124d71d40c67c6e2114a36aa5f30a0a7770f2a76b6dc57d33cb07f925c8b315598cc1a44c2c73d14ebeab397ec4cc7a8d5c07bcde6825ff04c16b68a18f382068946f4bbdfe7fa01d0f1d2aaf0fc6d87a9c3530a42e56084a7c9c6f9fc06fa9eec03820699611d02eb6c86ad3ff7ba49f9b332a1169b4ca228833a9ab5bc483d5559030d9911e881496be5d8ec46e9ce6dd15ecb21f0bf03c8834da74150168640c04d0b04d1a97605aa448fb1f6a8c22a865384bcd448abf5d28476e9b128f0b231a7aad1ab8c11bebb42ccae248471c5a508b3b7a144d2ba0899119adeca308a4447ebdfbd34eccf05e58ed21dbdf4a1cd28fdf89e5aef915d202c322cb934b301b2867065d7f4278b638b054caa6b86ef95245450f25bad8b7f799c0914d41666a604ebc4095ef750f6495306004301d1bbaa45b9fae81d8953db5375c5bdd7d624f662235e6bad32146de7e1f7a5fff54da2c993267b34bd421dc09bcbc5891f701673a3931da824767e01d5ed6eca4b48f601438e11d945440111c9376deacc9e8bf329cee39da99c848ff7bc396dba75e2e637bfd1e6b01ea26854f6395dafc6b95c0f7f0eb0135fb1bddd2b50da247632bf576e069353cb3f7b24930d2daa569943eac8ab835d5c592cd6334a20edb6006e859d0eeb9bae3d0a0b274e93aa83f70d8019d45b61a1976244f2552bc3a184a27cc0d76c4e012677278b952a56d02e44b1007b28a3b9340fd5da16556d8b44e926c128558fde052cf96189e1e1388f918c8a25030f8736a1d8f2c0d1883c2400a4ef3ee49e644c3b9193161338d870752ae882d1729554af3fadc32d94505a8c0662f7f096e185d8ccb52639da83cac4d9ba533f6d08e9fcb02ec43367a4d02cf85e5711ad014039ad51675166746f8a98ce185eda814b671c5d86cecb5e74ca26e5187ca61c07fb5498b0c688396a658996eda0e236f71b4bc5205d8a92ac3acea5333281c55dc1609614645369a9ce9d324fa3d59d4b4ae00ed238e8bec94b59f68cc145dcf04c88571d823325339f4ef73219ce0dfdb94b4f12bc6051ea6f462b0b36a1a3f402314a7b8d6d2fe2102bb4910fc7e91f83a46c8e5c5c7ba4e680c5d57638f258ebfd8691585ff65f38f5381aaa720b67f932b7713c65ac3f983dcbc157f0b75820029d08bae36f2a08971d901fd95f3833f17d6856eaacfff5383f02d0841bad17964e4f51cb6105e4abf51c88ac5db172effd57cb5dcee3f3ff4a3553c082a3f3772854e320043eb0f422047a6305236b7a65bbbd9b4f440070a7ed0475f5bf87c7ba4cc0ddf74af590026286e65ee88ea7525b77fa400012c955ff063414d7c60ecdcda376a9d9ff51fbef9e680f5db2092aa0a8813be75ff54f4bc06957f9de4d69b41f37c1aaedcd02ccae289029c915b51f407f1b9dba1bfc2a633644fbfb1a688477d7e27bbb0616b3953cbbedd3acb44a437ba1b59bbfd8332950db4aed939edcf2897a60631ed3131693aa2975a3ccfa49c471230b50688843ec9576d8918730f94f6876eeb8cfffb1f6acda46d93d4e24351a9e417bd0baaf495d6058d9fc53b57ec3b4ce8ad8714b943762bb3be6c4fe374906378792baab7fd56c93b1c8cf1968c815a49e97cda2cb91ab262313fee141ab33603f748a0b8e7d8f12a5451eced9205e5fb7862b383984ac85bc31fa8ee5db1dccb77fe7ee0a2358c8d8e8f792f6464864ddd67cf73bbdb12aa847dad65c34e7303259cd92786cde1ee64202ac68adb536ec373238761b016a37931eda3210c3e165e45ff4cb77eee87c35425e1beebf754005ca91ac3cfc538e90fb2afbf18a90513e6949af44c562d309ecfbfd7d93215e2c43321cdc23a925ce333110c745954204b87e8470821fc7067c2bec0d6ad7fff6889d3d347c78890dda2b2348a94d23d18ba83b8aaf15d1503550a240532ba32e5c0a100dbaf10563a9a038749dec531d39bbe524fa33e1455a8a8cc1dc4cc89fd103a91fc80a00ddd479778a68aed203e3b0629fe690aea304454e884c6b1335699114fb0e9282f65704ce7b79d9a50d54d18b8cb182b9443c1a0d5cd0eb329b69e534f4dca524bb6e0b47a20ccf1ad5a8d00f87f9c259918c4dbcd2d3c5b8a407fe0aff2918e2a4b8282eed4ba906e7b724d65f38cd401dba3b28627973f4f52ab68e1a013295ee7256a3d25f57f331f06562d43ab34c374020baeb80f3b84f0d4710bd63fde7c0ed90b34e9690e89bf0adab6f38e48f8a7d3d4f039e003188b188536649c39df8479af495ad5e0559632454f6109df1299c4c2d88a4cea9eb008be4f5bf211229af2a232fd09fef8a955c3576d33c361ab632af975c7aa6d6f42a6f6a0b218c75cdb1449f7daba5c26e6bff316789c47de60b3e372326501ca63b5c6f4012ad88963e4ec05464439ee94806f1a844ae0fd7e5bb80d119a89d155dcbaf38f9ebc0b2ba7ecca6362f9d3acf2f0c8234615a85cf13430b18794da24bd13c6ae4f18ba59a44012100adc48798a5ed29a1d7c979149f8534e654a286a88ecb5191c9cf261cd93804f89c996cc188a6db2b8f47ed45ecaf13dd345ace61190d0654764b67b8bd1a2a0126c3659d49be8d81de9b58a102d40bedb01c60c3d40600b90898e03113b27df3bfcc45006129964499fa9c372dcfbe21bdc2887f65ae69283fc4ec29b9909ab9021d724258e1df54ff323b2d619d195f582a7a6ff43aabe97c41447c976d17c5447fc61174bca71eb56bebc97a122d1be581787acc4efe53a6db6cd2a8080e388e91fb1d70ec37e25014b8909811e64cc3cd5000544e8074bb9ce5566531b1027a43bba2d113d65580bbbd6db1695a999017fff0e087ce0fecb3bb3087ee59931b16062f71cff7c7412c357705c262d10a5b37cbb041401ba95a0eaf2ffeb9e47686dbabb9f0490967deeb51f6f192b9269d98e31c0ef93af5acdd0d308363b300c59cd8c6ac5ab80848bef98e688ad30f50b7e05aa83068c1f935a966d3478f71a2cfd6dc4d44bf56c51dbed89262627001f54f758bfa22cea46a1733370beb547f0166003b05c6f82c327209303207770a222eb6b5f706287ed6fb59b4c1ed01c60165f39863f886e6f73cd4d924bc4f38fa589eacd63b6fc2cf92da3888d3cfb7c512c218d0ea77e3e009a2c51256efdfac9df4a777ae302d313148649f4044e681bd285ce400f9263c5b6b1498b1d5bd25fa5c3193097e010eef06f01a9cd49990f5ab50da1d5fd8f2746af5d139db88313ff2ec4711626e04139ad79d77cd5115d1d1ba36ae6f88d695fe77be482777a1c47549c3c75bc14367e190033e85a6630df9e292cc7958d71ef056e14a58fcadf4959132a59c39690123a13aeb42099bc15c55a8b91f6ca27f83c4bd82fad63f74fc2e01bf4f6799d92f8eb3b5489f03ec5a0279973d2182832a9d3c8bb1414c533a1c0493fcd250ceb8ba35a3a31f0f0b4f9608d2017d7931c328f1d267df66d439081781c1ca6e7335a077f768eafa2f23333369f77691e5fbc57be5543b5f1611237bd9ac88568edb93fd2059a28e98099cf6029067ace9433c37e23f6c03b31d326abbb4a6888430e9c2ca3af84d61c6c4b6cdbbf9fd3e9fa13e003a357724713cc16d308a1fe1abc223f2243a42f06461accb89572e3da538b7b96f9ac257b0f7082c5724c67e0331fccde90014b4830ecc340d77fa7618a2488205af011a95fb2eb05fa2c00bacff897fb0ab80eb98e279c9096e2d5079ac793c3211bc87c785a1a6b75231f95e33c0259541e4b1f291f5b0160d6aeeb8e41c78bf002cd574ef8d560ed9c77de8e01cb24bbade2e3002c59255f86640d01c1c5a7e82729ba68d9b336f3b220ec641d2334ccd4612e3477e4be42297dac1eb63fc4cefe1a1ad01cf4d564c05ef4b72011174a1e8cbec01dca56517d3f43648d21a48059d6cf87c44e59e1fd02d636b0e4c6002138008539bf297e31a9be69dcd59b0d22c7886ccb46fc37e70caf646cadd63132f7b5db00ee9bf210ebad1d111c4e9127884fae601dc22f1f01f72b211f6d59bba304e9afb74803f1d52f8d260245294bb414e8343b003e31531e6508b8b193097bf9bb1bd65f5c5b5a87d1fec2cebb38f35b455291b45999aa37338d2618fe3f6a72349873119f38d930ad51011dae129bf69eb3906fcfca1200a3ef462929129856f7a986695d5d9a5042262a7bfe1c53392bbebebe478cfee89c3c072380dbaa8f80c01339867e9bfeb469f305312bdb7375217648736ed8809d7714320a2c14ef79941f5e081d2f72e5a89d4b43b2df9168366d9c0bb89c33b6b0332aeb6f3e23a43c43a14cb09c980158e36365008af1e9e7dd6906971de676c9bdae26db678dc28890738c42d73a696a42573aa5f93a84a3ecc98a0e63037b31aa8f4e115d9e742593199151465c36382025f663b94f88da3dfa5b2c604a0568dcf770ac29aaa58c666e0ca81719a86b064ad169d4dc1e28ae9345d6a2c1fd173dfe97d974f94e2eca20e4a6ea200e4bdaf900ee842474f16c600d61dbe6ce1f316414a0f4ef035c6dd7eff361b4838b48281010f4aaf5da57e9f00142779cc0ee39f53132d40b206c9d46aad17e41b238fed2f09189139412c1e2c0fd90c1f4bfd452235e06fcf2e871973dd1e9673ecaf7de49382c39b230b5360eac8f29c8dc40f9bb1789eb32ebea17b9becbc9b9492bd0b0fcf7e6fa31a738505a7e07b77ede6e0d539403bac85b3d8352eda947028a0ed668366a908331b36dfae564a652ae3690d03c5db0b61239669e0d419b4d5fc90c6824b4145615175ba504cdb90876196042b9aea6fd1cd7364a0c97ad49080294b702faf32d695c2052b2da1c034a82f14b0eb5f1fbacefcabc572e535bf0b4d36e442301facaa39a59beb8ee091023c0867b9a8b2c6156081160022000b0aaa3a3a3a38148d8be73f7f83e4fbcc7cc6efb3db95ffeb35a2cfdb2bb5776776f29a594297006a806b406f7de7befcdf7cb0784b8445d08bb8f9f00ebcc1e1debba27e65df979654f0ad6711d81d0fb3aee42c7bd00882de058aca6e31d9ec0047af4d014485140dc79950a95ebb9df7e797d0a90f044165970f9de2392be761dd771e17baf2b89e35c155cee9fbd38a736e7e1c0e33aef0375288ee466697131e5dc818eeb97132a3503461523b39ae1a549c3a251c346cbc6b5f5cf73c426fe9acc39c7c4d979dcfd60fa5a1b5ddf1a5d631a47d3756776ed955d7fbc6b5087325d8b315d8faaae4998aef78cae59525db7a0ba763179e67febd6f0a25f4c5d9f5cba46b5749d62e97ac6ee1a4635761d23b3d25dcff0afebd2ebdaecbaa6e1ba66d1c05dd7b85ddb08bdf2612d1be739de135fdacde1f1fe6f3ecfae46d71e8daebf1097ac0e6291010f4bdd00a4e95a9b5d8765d722ef7a9ce99a5c75bd65ba6689e9ba45d5b50b4cd7a6195dbfa4ba3ea1ba469dba4e85b87ce95e89c306b608e41d8171e95ad5d2750c4bd732bbeb15d9f5ccd83517bb2ec3ae4ddd350dd835ebeb9a86d7758dae6b1b5cd767eeba85bb16e1760d00dbf58df02b1f86cbdaf559736e8bd37527648e47083a76753f3e10e327f8c9fe3c500bb131364d5b7f5e2876b8c34ebc4e1c31461ab9cfa3755b1c399231d73981831bd8a049139c895c4103d20a29a4b837735492b82bf98a2d829c015905d984f4b9395cf7530a8142dd7e36ef374682afad4622b44e171ac619e31b4406477620b26dd8c70f0d125215a42a48177ba0fe32a797c0a56fa97ee92c0f715cae82748732c76113c648405ce6b2793ae167bf36081f3a5a653eb2557771b22b089096969652a21335898db9ce83795d0a9631be41f69e91b36ef9c5b8f3b83bc4b34b9d5de2ecd20d62d92078efe5b4023eeee12cf2f0385afb676ed33b7a229e4c0890aa23ff10c4043e570898eb38fc5901229165df90acf3b8d775dc139798e7dd7b71d7718eb3e24e67ee21378acee32ef696ba256ee9ee70add5620724242424a498540eb84a4771749baab2cfa9465423babbc4dda5dbb159ffda2b3097b98b3d2c1ec9e618fb6a48230debd075dc86aeefc51eb703ca65de71b4766e9bde111215dd81fae3af73fb1a27e5f6c092a796523a26a4b0020b3b68bb7254c7752e9db36cef0f90bc232472c9ae9ca422bb6e7c3a33f87f421be36a7e487655329b4206a414bbf22451c4000a212447c8274827504dcca8b52ec1806462571eb4c40b8c2891840b90208becca49a0933c42845a2b12d902d2089205e40ac822c81f92080184a40a4670dd3b8200ba245ce77da08d0d8794209b1229a34fd06cf4b917cbf46c56182ef107bb4dd45abd544ceb9008ad33c562fa1c90163313da457e6ece795bb29be3f1f8d3f2fcc3e3a4604f1f90767358a0d158983d0dec02ece2699996639673cee37168feb4fcecdeb37053a9bd6f0b02ff75fc81af5fee7d0b739acc2db64079afbdf7de5b1f1c3913b47146a48aa881e688a3a3a3a323ae5352529a559f749687ee1017cfc9b5562f97a9a3326aadf8e4e7fda2d6ca75625a39b3e5474c714ec1d191d275d52052e20c67eeb9f681714a5929756142acb53c9c404949899c5d1f2cdb888fe57564209ee765993db390a313dc2633d9098c00c271f8047787cfff18c81121109c6f6eb0d2b5d696e0e22e5fcccd3a205f0ddfa31c5c7c319781e413e37b649384e41e450d8240f4f7f1daf1ea7de0e76d132b914aa338ce4225d0e620bc9a933299ae1024915df90f517383e3382ebb00d0ebe7a8d5d53517922820879032f204a4106410a41032080904f903e943fa409a80ec8104526b55227920772075207bc81cc81f6409481e1207f206d207195baab51e5d514a1a02d2915119ae2071492a05a915889fa467bab773f86ddd48ddec138b5d71f581b82405444a264bc9421e78b64a32c638e79c6bc696e4cf735076220be5247976f30f0eadcdcff63596873cd4809dc73b50a6bfd979dd76b99283ca9c24cfecc9240fdd964dbf661682229627bf2d972b3c28dba06d468157c61884d1ca586524631c634c47208c628c3f188b462aa30fc630c6db38650463fcc2e5ca48347a31f6609432dac62e461e8c3b18a38c3a1873307231d65c92c62d462dc62c4628e393118bd1c948db957f0e97ab336a39d2020353094c3c2d485a909870e041b5f776383ba1752ebfde715de33097b9ebacbbb0bc09476deb833573a110dc0d5de5d813773a90e585db2483e934869f38c3cee51df8e1fb29a5b00228049310254107bb10638cefffbea6094230b9f66af1d3a1e5893008930f972b3d55ac60adb565d66b3d41a93695d7af9a351ae508a5f480d7f3d80d51a9c1cde2bcacf8d3be7c8c58d29bf3faa16589574f4b919bf3f2d11245528b90c5b93932519b8eb2a429133d3bca92beb0a448a43aca92822ce9109634d532eb351bd1094ad5e1fa624ab7fc3b41e996571357eb288e9e9aeb49e972127319a545375b94d205c59e28d345e344bcae342ab23aa96cdda2386ac551220ab8731c2c81585c97403458345627a25a8d62345833d0b03be61d192cb4af51d9681dc5e123e56d2ca72971f4e088dd9691157b9aa5ebe62272d9bec6557341f1a25b7e22aa3a5c4e9e9c8838d34523b2d191c662b338985f99caa651a1f91aba2d8b8bb05110d04f96a1d8e8071dbeda4ecd9465b9c355c977f99295af9eaf295f43dea7bf74f8978fe7e943404a7ea03062a287b69ab9157d8deb076fe302420e5c3c177107a884117780ca5be60e964025ff4a209699b287c364382eaa3abe28d526b5389fcd9e551ee7f3e2cf176c7473be1b92cf0181be157473c01f30c89e79c9e3804d881cb5393936fa6a55c758836ac3c251db65ccd50d8a8dbaaab72a5fa8f2c5e4353b952fa12855c74bc9db7c5ebc92d82cce0b8a3fed6b49b7fce58b72e352de7cb79bf3f2f12da01ba3fe73736ec6f8561337454116e7268c20d40609f42ca2db213f74ab43b7bce5bbf9749a8be6825283aac395c5db6c61a2b6a3630db6f98a713236baba95b7af19cb7255645b966b530d63a7647629d3af59ba68b715a574d96ecb725eba6eb765b9493cc1a67ba6aba4b257a5371353ceacca87f55b726259bfa6f299b0a4113b73755bd508712c0696402bae5732e5b611aec01268867f33e66a25c3e556b85aad64646456e9ea1693ca82bb11ae6abe0078abaec45e787ac95cce1bf3dc712f2f2671476553d970c69ccad63372d9c5a5a5a5856587ab9bb8b32a127746da958dbad0d5ad5beeb2591c74a4754b83527345b138a80c37a8f39186cb57c7e5167c51b67eccaa7c980c4e4cdba0b21518f2e08c063963d3777406674120b6e30b46bf65eedad6fb168f2c5f63392e5f49ec06fd965fafe2ce26635b8b7de867a19cad5f9effb3179fd3993d2c11955a3b337c7b72d6ecb62e1ff2685c8d9cb251ee27dbec59eb2ddbecf913c4fdb450b2b08213b19b1392707373e3068b15b3b7f8dadb381b17241111bff75efb2a15ae0bfd8772d8a8352c8d7ff4de7b2fa6c9265776dc9bf956a08c8e095522cc38834cfdffffffffffb7e0977b5d4c26979796130b6aa7c819238ca80a63b40cb8fa663cde959c9969f0ffffffdbffffb7f1ff6f4d5ddc7befadffff0ca8b39cb92bdc959c2b115c5961d63e4ce3fa76e4ea15decdc979d7b7b08a028e063de87ab6cb7bd6da277c2439c2840fa01d6aacd85eff263e88e0a6f810d229f191830fb1a330c498c1fefa37897de1f16cb17f9358ad23b2c9fe4d625634891d09c2e29c6c53ff26b1158cb1c3fe4d62426411e301ca52195014fdd8a1ad88adb398627f35880110aa294b2ee062e908219688f49bb3c332590262079409291859caa1236293fd995c31da57a8f460f26ed861171e125bc4c2b6777f2657a8f082d8a8fe4cae24d1017145054faef0c0e560874b5f6cdd9f8992958eeede84d294bd992845e101dca92e05bb8506606c172450f60c151ca12444b7826deacf44e9062edd6210bc16dc9ed8a7fe4c92b0e81c5092143e9282b88caf0929acb0535cb760d3f60d0909090909e9e8e8e8e8e828c925c92529a5836f5cfdbc43b0d7440b41604114194001830c274f4bc2c6c318586871c4154fc29916622401c41325a42f9cd84af0c3e409181841e20745309b3e395d2cba1b1d128ac0a1292609887efb3349f2219c6da1f59f580439aac31a56148f231f6bedfa0b43fe85a6d61cd466e5386fc7bd32ec5434ac170c9d45d2f273ac155bd1dcd64ad395a35de7709ed9ba45b55ae41417bb72276ace15631c7a3a4c0de18d4fc6703966d99d77ba18779e0ef32a9c71c1a6530a4626b41897b8d435eca95b212ed19af29deccaff7900ac4d7585258ed00a60042f089f174e69f21e88c8095346785527a441a3468d1a356ad030cb1c97a398678ededcb1e55a2d1144104104115a66c95114d77b4920c05f0e97406a4100f184b3db1270017ec78ede0530c208238c3082006cd4b8b9f75ffffad7bffef5374f8345b377421450482124b3484185f073a2421004204009259450420904b0050879ece83d5c0166802d33406882b8f326eca486d8f584300128a4d0dab2aa80d410bb3eaf07c03c300fcc03f3c03cf001b008ad2eec810161cd39b7f0fff76d0fd1cb71fc7228ce7ffbdb7e17103220c4373e27b6012104c49d164207847f6b3077210c7b7831982b395886e92ee44a0cd35ec89517a6bf902b2d4c8361b6fcf24ee4a1818ef92d51967e3928aab07b0cb883e96ecc5c05cc2d5acdedb9802cda46b6916d840c830cc3078bc16556e2cc569126979fb185857f1e3202f290ffe030982f46a552a95435956a6545857b8a90fd0044079a154aa06022d652a65fd3b3b2524bd394872510e7b97c197d0d0e109c1e9cd86d25d9d316dd6ca92e81785433433882e11c2cf90f170c86cf844124858acfd758286436981995e9b205ad64826ae14cc933c3a2e16692d99270a895432be613e46e72a86bc94ad7173fc294c721e55f33a6cbe693e45c72cec2242ce7502b87569096cf1d1a2287cea1ee1c1a6ac9ca18f28634433457d0d0583eb07a5832d61029d4e9e5d3ac1f9a2b688668682c1f583dac2158b2140d0d45433bd1d05e68681f0d4dd3d0583f6068b3996e64188227a3d3105031d4142821d410ea0a9b13e5a98da2d53ad369082723540c35054a0875056a68062a9642c550a8d809157b41c5503114addf4c2632d4a08c4f68ba6c36d32de683298d60ae4ba05548728fe846a506845b5a25bce2deab94c8f6badd1691ed6b5e52a844b9599c17175e10759fb1a6024238d6ee5813c7daae8db5b1e6c5d74821b2a7cd469a48d32d2c814aae4b2093832590aae431628229d32a464882664b4e2f9f366a194acd728c52b3d56c44b75a9528a9c57971f1a77dfdbc7a8c6eceab0824b2a09bf3fa61c90bc8cce2bc74a075cb4f462f2c464a588c8c983042c268b70cf59816f3e553b3d56e3559f6428a4f4fcc95644f237bda7413994c972db599b15b7a33a9804a1e9640260863a6e98e99a54bc952bbd57ed231de4686c307470f0e1e1c317b5a3eb3d3af71dd5c4455d2aac325c58bd456b3dd6eb5d0c44562f4184bcc4ab5e263f92af235b2af5921f133e4873f85f49a132b9440c184cc7673d96258cc978f6db5d225d08a83644cd9c361301fd177fba87c5f0a2e01160185c0283498423738832957566ccbf2555a3d95c9e523faa87cb7cf24ea96b7b07c29b804180528a4bff44bbf54fcd29d92a90c9c8de6cb27b5a5b5d44a5a4b5532fd8aa94ce93202551c26e6a68a8929674cb13357a93d5534627409a4e2a10aa6dcb7a53265565654375b9ade6c44b7a25b4d25231393a6351bd1ada8161323eeacac286194aac49db196c4465db634add9886e45350084aa1ba14a8335a14ae585aaf261614c299640311c8c493bba4ac9a28dba6c2e1c43f75197adf6462e5bb7fc8b29bddb4aaddc6c697ab311dd8a6eb5d4cacd96a6371bd1ade8564badd46c695ab311dd8a6aa9959a2d4d6b36a25b518d255d59196bb634add9886e4535afe3b2aa7c188c4fcabaf0044986b19f6bf2de7b31bef8e2b7b4229c58101db8a8dae02a7f5a9e75781c9cdad3d270a19b537a267f9e8db0382d3c2cfc791662715027fe3c17f13a30f7000b32d0916c0c9f303ec7b8e8cfb193cfc14648be65398a87fa73ace47372d06dd9bc0427e9cf31148fb3f99bf8bbf0cf431f476d741e2332fc398dd7f1d5be7e0e913fe7accff1b55a8bf12a8fb49decbd70270f68c5abbc7c241dcda090a57a86f42fa698b8d507da308f1e4d8963b1128886e7f275fb9a57d2cbe895be8aec69a3d4686009c4b3e23f3c9849a55bfe0ae36d2c4d8ac57fb86032dce4028a131d74fb8a7d8d65c998b50eb60ab2d19a9fcbefac07f59a9f6e7db05b5a582cb3e431f90f0fc6c3f383c36cf01f2e580d4ee354e20c3a9bf84ea1b38c336dc55a38e8db52595a3d3544f0add36c94ae2c7e04cb6b94ae2d1e87e5344a97937fd5ec4953bad2dbb2a6e9a23d0ecb57b7f154e26ce20cfa4e254ea1b38c33bd393763b470286ac52cce4d1841a7101242fc14623985ba53a8d5534304d710d9b7c8bead912bd2d2726fc9d2efc735cd8c6609cd102b07568ce5c30aa28299917a1159b2b30848b384664633c4ca8115630561f9a8688660688666d00ca56886449a2196ec2c029e455c4ca6fbe9ebe2a241fe9dd2539593110a092a0835432df9b3f2188edaa8387aa23cb55143aa28e277a85395537a3242214105a196a066312723d5c908e66434e364943a19f5212723d450b79ca58a22aaa2a0f80edbbd883ba84e571105f0d3a68bd6a1f08f49dcc12f220bf92514c92e531ac11c2c814a5dce30030dbbb7709653ed14e5d40538d25453bea5b2dd13913d4d416d4e9e5add4e451e14eba2d953c8ae3528b4af39d5aa8e9793b7e9e214e5f5a45b0ed6baf5898d34d594d39413d137d2f448bb238d1c69236da43df91a276f63392ef28ab2e9a27d0d1f62a3fbe5e524ee743ca32ac625108b8325100dfef11841754b4b4b302333d0b0bbe697c57263a1c292b62cf1adca6b4b6d116a89e2cfcaeb8c9aaa2f01fafb635d8ec16de0635b0e5363fb9b94c0a75715baec65afcb994b7da00929b8529c4b7de017721cf7852608c3240d7edf0f5ef792030f8f33d933e9c99702cb63e301527936e05d0006196ab0381fefbc0f9f980b45fd759e8ece0f711d1e9e3314330fb99c73a835df6139adb305826cfb23b7dc029fe3bb57c2107a4bdbb4f7056cd10b3d1bad3c369e9e189e54cae33ad77a99d78ebf600b4d9bd7a3675e5f041d067bda0bbc60e3522a117b5a71a79a55080c35f478e102326cc0721a6ccdcf57b039a97ba343c20e134e6bed598598a857abe6def36dd21eff920324e820ffdac1171f7690afb1fc05888d7e0dad417abc50837c8e3fff851e2e78e90e6b5e0f1774d8681552855499c9eb61cf9ef3e4b6f6e70c2c76f5667f7132b7bc7afc997c6c871eebcf7cbad02fafb907ce9f5fe0fd9a18fb58a2e327ebc65bcff24bc20d7aa33f7f34c55997870558fd6205641e21b8e676b9f8cd3139cfaebdf2e7b77acf5f01d58c0173b0c7f0a687abc5d5e26a31ce5c6e72c3948ec366062a06fb2dff0261e15db839aa5bf99b23785e9f5753752bc7a6ea3e8ce3942b746257a1f9e3e4e0592c1633e266b33aabefb0b6569e8c6d0a7d8e39be797f13cafb87977b39e78c4bde2f117c73b95f47e6b686d5a6e6fcf7e6d09e26bced6969c8fd9af0e753a721f76b61e091d02f9f8183db799ee7e112958184ee558f7b9ec73dcff34cb4ebb814118e7b13fd59fe37d190db9e893ec7f58a2f17d9660ee5fdde9b4d6b9acc7d4729ee35e16cda1974f095e11ca297e3ec6d0f8754e61ce7893c6ec73ba7c2b07979f4cc562ff470e7ddfa7d4de578e69acb9e4756f10036f48beab0bf787634f7f2898d39164f387b9f0177197250d7dcb5b93ec771dc76ce4421a0bb0a6ebb0a99d5dcf634cde0d6d0041bdda99d32e5d4de3bb54d40e067d95bee718a2d033893019c756bbdc86f31aeb8e28a2bae18e35ab9d501cee11539ef9e3326d36de24c933d2fcff75e0e55fdd7d4fe72a770e21b9233c647f91372a4e4debfff373923ddfff7c840fa81c6b2a29bf9cd1808877393afc91cc7407f8f7befbd7fffc87d4f0c24bc71c903f36bee1ee4ffffffffff33be3b705e77f150c759cbcd34be52dc1424d5227deffd4a92d9d4b3f1dbfbf5724bb338cf2fbeddde77726d4f141f2299fd2962da2026bae7c389f76557a448d6538378e0aa4cb7b19bd341b12dcba3f410f91475567e7a97d473cc06bdd9d1ae88591d94df3d9bc3e9d8d6d9ed79c3e6e8d49be7d5b49fdb568c14fb46c7e6dcf84cb043fb195711d47d1e151dfd2a58377e867a3770736acf6d7d5eacfec83ab3ca8c763de16bec1055b04efec4b5c25611f32ba260fbe53068cc31c89fc3b0811a38fe241fd11f8ae9e8355157b79d3943ed288b2cc7d2e463d718510222d72609540fa830cd2b4c9b3d214ce43d18c6a3f90f0f561fc7c7c3b2c2acd8028689e60f1c4898e61646821d6dfee3f8b8fa4afac7a99041ef54782aaa4de1eae8b85ce7c9539f92ee71ce86297aa7c22be91dd71834bf1bf61c8fb0aacf673b1ee87f9cffdc63cfe7d66e81af12fa9fe7e7977b8cb1c6188cdff94dd472ccb5b5b6e3d0147adf9e5cb54be867a1a4d7edad3cc76aadb5d6ca2d149bd3f9dcd8e7a099ff671886a1c581b59767293e7a149bd3f97c8e5a2794049a681764e533d1ae4851f763893aead94e16c56a251988253a2afe20fa9442494564e38c93e99dcfcd09896ccbf22299959f6e93bab5224f172402e984c06ef6d2b119da9a803eddf2fc77446de89e80201843e61dd79e89dad0bb328b982459c0f27ef7facf4a7eabd6ccb92e73382300a342115f5b45548db133c9f18b41c72591ea76057e311d7fcfe331781cf3ea710c7a36f4cee68b6fcf2158e23fcbf0cfccbdccdf8643adce5c8db133bfda25d129cefb40cd796b4618ee70002894a2990a4d740606fa35fd19d2d0efe71ac27a53ed757f2f974a79cfb32df1558117784a78a154e92ad002284698e440c40eac140049d6c4901d5c19e2080a40230a70b3103fbc507a6e08224ca2c2464b3d908e4e0f9c104166a0f3c3bb1dee0f9d1f254a38741eaba756b07d4f1efddad35296f31d41b822e551d1fadffbb735c7e3226ed0e50e06e3b0e5bb5f9ecbcec22e0cc3324c73612e3998e6ba6e847d7da2ffe6741dec069b46a8340c9a0d82c866060040010923170000181008854362b12489b23452d40314800e618a4e66523e964763c12487511044418c418610430821c610620843aaec06016041993d38a45abba2e1b8e19360cb62331aa998d841ccb3799860bff838f543aca47115e69b4530a6aaf28b22132156e48ab54ce8fe69a1764a7b7281ef1c450f424d061f1b572ae538d56c227646a8a378e1a1c42454d77830422c3c469d31853cf84f7053efa54bfb96eff75a565d8cc2d3281a0d891d31382f152a7e6ef2e39eabbacdc786e2634739e1d2a72cc9c3045322f908e5e778e2b305da34dd311a7a3283b3e77b45d12e491db06343a228893f6155c3da0d56266becee846706541a7fc09406a62730a9d58b777e0041bbfa013b085e04b49ec17c65efcc211432863bab9b07ce1a64581b8673b003ac4d7344c7106b9105999b2cf565c0eee8c884134548364925e4333169c9f31b5604e2a55c2433ce1be8700f959e8dee72d2963bd039ee41b86116cf416c947172b1e22bb73857996ed6becfe6abff1c09dc0ed75b6c81e245c701fcb18dc6e2caa451fdd447b2e08f0db4128b30c3eda03544753f8d4dccfc488514f575a1d4dbe29bfa04eea9741eeb407cd0f6a39ef76ee8ae8774b5a3354cdd16d278b807e989441c4e3ce74566b2b9fcd06c632d376b50d1d9260792d730e648b626a0f49a6b60eaddcbcb7cd81e8e6449d4d732e626408da0aaea1fade30298f4bd8acebacbcf13660f50d553600116a150b3fa92299bb78d854be3983434b3d78191446c68b1cfc4165663f1f517428b706681c4ce42803c160a02c82677530424623cd576ed70ab24de84bb50e86441713723f4779742a8696c395a0933492f3b709fcdc0ac28c20491d7a5ad3b097dd09394f70316c6ee2015f37f7c7b694f610b1e88f13b1e461a1090b7859a83a363f9b3c35721c315f51856d1e68619fff10ff4fbcc6965648bb588f6f9d74c351436ddaa4081079fb745da8ed0b17118853857ed9c0a983d9600aeab22814a3b26246f3ef3079b12e81c744b156f75772cf313d2a23730af3abb8e7b2c938669ff1861d192f8e5b0e38854a7b142f9fe0d89514a6fbf0b07424a399e2b52a8eae1f2c78e5cfa2cf350f112e8af9b0f174a27ae46b795c316022831b4abc4580d8de05827fb682a0f083b39602e2066d07583d529000db418e74a838ed868d6cce5c3de830e70414201e9f4d934dc33a6d01b8287105ec0565ab1b426149e42c294041e6bb9e3c20f552c5a5885c6fcc6695d3d1201e8688fee6483865b619182747f970357ff7304e6f78b2f0e65a40ed47d83d38dae4e02e10720f0b5b5449d22a6fcab32f95018e900981415fa424039a01a3167d3cbd5167ebad0a1d531180bd6b6e8a0856615e45c0e5cc60612dc980bd9da25bdc9c443e1e48e64bc8b1cc88df1239ab518e1f609d9ce7009657bec0d76c17d1ce0b799cde7253b0bb512ee33fa724a1c3a95742856a233fea98d8213e821551425a63cb618409a7bf92dead217e225f75dd3054de9a2b9d3d551fad818715062c03584898131cb7a4654c38010afe5863f8256d7cce9992023e15b1dff72e5be6c6534ea50b4783f11d4a00f2f2229ef4c90a2c10856cc1b224089e79041f4678e8bcf4682890c6aedb89dee64c1d1386065c7627cb7ef46f11173e9004643be490f1499756f6ab536b41902b980da1651f279c2441693849399b8cbdbe092840355c675837ab9ad01c5c46f2307a97374684923f1db7ea22f23484a12aecf4db89e16a993ff7c8b0f5349c211679e069c8fc138c4f2460d60a5f1159c9b4e3db0990c9231270fbcbc3a52e3df5fb13dff4ac3d4553437ff64ceeb5fb514a2ebf0957d3a95902322e99fd88453c873396663f942319d2464adecda76dcc3f569b7c8e68da7fde8aacff45816bf955b88bcbda0cc62657ce84303e009bcad467c2c777400aa5ac8e374caca61249a93510ac8831b70ac97e9c5943ef348934bab9789dd3f3ac894f6f45bd10f47572c81ed841c8118a104c4180407e627b0cfd1dd959371d0fe3056a92a1a1bffdd991318301ed02c54aed58c1b78fa771e86dd619975834acf1f94b4332f479070c21ee063a29e88d8940cca5d3410310834f7b0cb9078d1c0b93fd8873d82812d40d9c4a22d5e252be0557aff0adb3f5829390799072ce08397da85100ecb4d93f301ed557d6dce1b87b4f919aadef751f882a1f061aae4b3a4cba90e29a50d10d61bb0f03e9fcf19dfe4b9c7dc23d86f930abbf2f58e26a05b26ba4294c6024d1a6969c7b8986fdce96119a7e6a1aafc7a5cf09c3573b3bcf71c10ee2981aed7654d80352c4983ae6140840c1d01a1004fbcd7fa4c9663ec3deb6eb0c7fbdbc72d20553856bb7a2a3279918e2e8450994f086a88e63d08283705976d1bfbfdd642b189c00f20cc85227a55f9c6705cab45d4ab2ef7acc48494262860345cf1cca2d14691479cf1fac4c85795381eaf9971cd9314beaa8de0f88cbeba0649cbe1da40c3d6cdb24b4782b1fbc9dcefef85e12ff459889bf6c9b116b7477083f23d77d447070fedfb41412ef6dc56204b98fff07b1f158e9a5b6371174c0a44eaddabf852672e5b2c47db500d510413a93064943495015cf95d5f2ac09da88955b51511deacbef0dd1b64ec5ccef9d7cc93777685358688fd427899df16e82203dd7521ed028147cf14c0afa78db788a4c9387e1a959d10f13ab17611516a50d547199b5bb59f927ab76d006dbba49cd86293ade87a482c278b6eed7c2f4e9e30301991a972577b3ae098c5fe77c5899bbb470f0c1c6cb122255b6c7d97f7a1f421e20696cbf9d4c10d6f790dbb61c96030c36db082c77a24d85b2d0fc5e608efb05a6ecb04f95bb0480272ce4c257086d1097c0a5d1ddd65722c784b5ce36b30d4b7bc29d7902048f01878d3094bd5182ce43c45c4f91580b4786858cb45f9809b3b73f9579987263e916db21205223d7ce027ae118032cf09c3e6f021dd42463f01a8ac6aa8d6d51bec0e8ad9e959b5adbc608e6f9bee1bfe7c5d862e42b91d9dba95dbd20b5daf8d63d2661db1d245e3507d183cbaf6a2642aae36695ebb5af13532dad2c2396299371ee8a5e8bd268e14827ba61817bf1cbef4b87e882663f90ce1771caa94385c0c774d63462cd84906d0567969c212ebc8efd7b03ecb77e4ddf35dc3d821afe8494c6652b694bc018b9f40ce2295032e6b148fce7c1cc31e59de5ed66b981efbcfe6955017bbe3fc427ef66d377489ad8cbf2c6f3c9ca0d306f0d9908ee342cc12b62bd8576ab710804df67ca5d3dc45edf01f3b94ee0afbc03121013a6f1300e693ef65e6ea16807f478a9ad8d4ec60111d605a4ed88f2eece20802237219032bf5f73da60e70d97639d2f386563daac52812e05249daa276aa7405141233eb6b330687ffcd3b06d9af62df1ea0d8252496c811d0a048f431d4274ea8a5057aa881a9f66e39cc5b8953dd696b17b6317fc53672163a9b6d47888eaa42b412531f73e8791890c279c1c4c94e27c9c774f85708ac991e8025b80d91815ca2884c65eb3eb1a87b36449c39c0ecb9ba29d4c8ae6b6d076fa2ba9d6d76fb912dbe81e5d7869bf6caf31e8106c0df1bba580aba5c09bb938c55ec6cb378606286a02e89944093761813c75df8c7277bb49a7e477f6914103e8a87bce75476b24c0227a83a7ad3f0e9eb57c76b339ea7b59a1bea296d024d234e19006dc8e314e1fb964c7d89796051e9af24f70a27d7ba2c2731b8d299a044136a6e7731128a397ab1bee07786214e9a17601be52841dfb0e23a92fb9df539a0663844f826c74dac97341676b04b9a295a1cd7c007abbc1518d239f5af10ab51082fa8de7d6e784082c151f01bcd89330578737506e582522e972cce0c1bd263d91539465065d82d83d77aebc77eb36c26e5a309281f09232780ac0c122a201fd60ffde0bd7a02aadabf87cc589640232b16f27f05a60b4f2ea64813052fc88c2d94a9791cfc830d9340a5312c887d83b925952c7069a35fdf4a8e699113eb38255e5ecec321522e534485d91b1151beb61f2d7e48807262773862eb004aa81e19150359570c2135e342127c4c1e31af5c4537ccbb3085b73cf843923e2c1226dbb3ad1b8acebb90955c0e3eb8fe5701af006d7c9f172f5a7bf08611d78c3d2928bc077c8560cfe8cddccd7da356e1d564e2a2ee651b7e235ad39e5d5946f6dabbbc815284be42d9fac7595e510d662a6b7409c929c7b425e11212a7a3a5f4b684a46f10ef0c884ca5e851ca0af904bb428634c51a08f74f80b0a74442b9e812a0fabb216ba16b5a16c109c795cb10689e6633e60e4e01a5f34e8c30e88227212aabc0b37111668d7c2821f5f9f6b912b3857411d7f7df98ee102a055dd55abc242600e4bbbde019cbb9bc06bcf44220d924a0e3a39103591af0d03bd00a3f25b5bb804bf20fdee6d285c7d3ef1516678c431888c24835f0081af01c208843786c6f4c8f02beb0e7935a1c97cdca599a4711e0751c33c091ee55cb7f4a397caa04f0e34c697dcc53a1a662eb8524dd4bc611f6952566bcc013577c98b0fee02962c19b5cd221e0b689dc3433df162de781d5b1820de7eb50c717c2cfeb7e4039541e12f8f81aceace3625768da4496522094a0383e6b18e04624f95540e97e45e7973ea0b2624b6f1d585bb5676bd8a3a7eddbe2bab3f36f8f5431eeb20a15c8f15e39aafa872f206fe47ddc19f16d6dfe11a294ae0334cbf7b5708e5cc6809b4e10795876b765f43278127e3e56a5d1264ed56efb19c904f08f7edebe219ee0886a34676214ceabf5aeb4c63acd9ff99dca80bfc6b97225c9881006fc7d91ae16889582ef68bb0c4b011033a58fb9a0fc48530f96fb6d1f4778cc9f3cc587de5b03322dfb8cbc3db4e47abb3510464fb3811b4c253d69c74ff6cdf4fb1bf4c3dc731396b8f99bf6874ba834ebe8dff9638fb448309f6caa798c7523de2560507860f67c1230b068209e0c781dbc1c7d9afc4a87d6cfa30d91764d140919f60dba3c780d5e44ffc9546864e28f2cba699858ac7607ce0427de21147f915fc20caa4556d4e07bb4ad3a6277481ffc57d80723796c06bdb4683d4b6b0c62339c0dfcbff894677847120bf543604aaa0d247be1f0f207d6b0032960e4558124d57314b0215fa23adb58730c54047f22cca70eb09cc1ad9a7d5237a903addef7a48ed1c42c3a4c0794e3cc0c74312ca830a6339cc8834334e388bbcbfdfd3282939805f867c6adbecf8009aa6aef3a4b8fd1ff15eae07698ea6cd6820d6bf54258b6e9d8652c801b734c03b2338c4e675abe60eba53ea712e2cc26fa43e8f9e2fb2b1cd8dbd656caf08075b45bcdea0c18c4053c1973f9e61a7df2c459d17fce72153d173b0079d97c78e676086c43a349e2ebb2b3a8030e9eb50be517fed72c001c619c852b228652dc1e79d7b93b3a424714f7e7bf6a4a34fecc605f9875e4a068964822b7096903d33283c94cc3999f3320c453c8b9ed6661a9fccc875abb929397b2a1592ee06853a31d879da9c018ab93727b5bc13c660554de64363692c8e17c2f4e3cb929943a7d9e1195a368ae85f938f797b986dbfc3083006ce79a81e320fa5ed444bbb89c991fa6bb2522fe759e76762bf28fd6bf8dfe1590208db265e19a50c34cb6523860535a30dcf3bef52a892aaaccf362c98e6daa00b14a7e98552d2f2be55d7e6b932a85c6908a82c2f0ffc62de241db6ce1850d3f039f37adc91038301c58d8fecb0f781f159b33d67f7be630949b17f3f8fc00cffe1786ac4ac0e93fc92b5840f45f4f325300b48409420f95fd86cf73847ca079f4f071a012c44009970982efcacb0f57039d4e08abbc4c987acf2c279c0d6275b8b01f685e882bc1acaf6d40ef7883a35362b0e028150a115f42349940f93861f6ee72aa9699771c2155f10d51e6ba0dc9102e172f3bf8d79b531be28ba4bf12b29771fb897587bd75b53763c178efdf61a5c314bce2776547ef08ffbcfe1869a987ff4bfef09cfd8833146052f5e0399d6aa147eb8f7ca276d7b60071d04f6bd4972d3524e185124f29be396d57279725deb27f961c8c1736128d241076e4cbadca6955e88e012a2e1ea097dd0ea0e2b0499f9c90f1164579b4b357bad349b08ccc002f96a1db701f8b3c2e7629713ad54c9c80ffa51178259c2d28e46dfc220c8510fe02f18bea4015a50aa8e8681275cdb014116f8dec15c1005575fc52abb5e15cbc8261050dc9825cfca235e11f5219d53df654335d9a0bc4f6f83ceb9d58972e7db89cff151df46bf2d8b7ed591e5dfc92ae5b65db8db8f837cbf68277d3ae962d77b6491d420262d7775b3ef7a19f210bca93ecdafa34f204fba1401c95e8c38bc4bfcafb4b0137b1f04a4f27f5ce38cae2a64c373807920bcea20687b4fbe466e8a4b53d7fd28a9e5d2c959ebe5b0d8f1a93d649cbd3beee7c9436f99bfb61ae7f6e90ef71a13532fb2d4a2e7d0b35d1a0608ebb7200c43ed02a9e3070dc651db34bfa2f5c7fb0cacf6a69cabafe68033f5452c0ac0e98bda7e51d4ce16287ea3da27cf2068ca691c27ebf8f2833fd2a748c3b36c9ff609bc248d28e2b5c8c0a54a2155c84bd5eb3c299107b49346522247d5344a92b09fc7f4354821cb81103033fb25d6061fa6185c774d32f0647433e8a62ae45af0b0caa11c6495ab1f5d4cd731d363bc9f30a6d1fcfe7f0d9d47ba8d0261f65cba1bd5ae676dec80fa5cbe5fb6964a8567b9f222a3caa2fcfea7b546e47c86c71d9daf2c4255406898e6c5c0b2d0da19d77cb921091db75ca1483da67e3d4e5869efdd690051555ebe054d9147c1787d5a66a4250ca3c805b43bf7f8d7b1170adfc6ea15a320b2abab1410d624bfbfb83ca3ee7a56093e7830afe754929947e1f0557f6a57e44c7c411c943b7ee3f1d3078ce71881712f7f8c8877901327ecf7a2b5c4d2fd37d13d461b6768e1784d382e8bea11173d1c5711ceb305bd4bf2af1647363ccb2ea9b12ffea05bb04304d333fcbe9e5fd8294be478ace79db78c4d7d05c78eda1552948eeac103e69f287af0259b1d9a250da07af8e00ebb750baad16faf3082c34374e7ed1e4cb37ce013303f5e60459deea676e02d9f9545744e5d54c3ad52105641028adb1236807512e3ed10f6f6661eb0c2f63475df1ba3251b91c5bd9afa5b224ceeb6c60609133f0785464fd14a0ef9dec1533040464af3a17b3280b4ddffcdf7cfe897ef32e77a097d761caff4ff1d7fc59252df96e90955f053fdd073adc964b5da80fa9a76ef1e51a18187a558b6f1becec70ae62c1608c863c19375088f0929dab400410ecc29dd0eaa3019206b2a8a93e44808645643422a68b210a443555141e603744701f915cc415fc1533a2657145006ca61821353f0bed3d426e614b7b2fab8dc3110d44092143400a193c5ce8fa3332641d08e2b7010a0ba7ca93f88f003e62ae29aa305441179d8af56998e3aa6d568d9f26131fa879039fa88cbe1c259593983037038c4b2f1c9da039339642b9534dc17bfc07fa580b0d31d3c99289ea463df04c899b323305656f4760693dccf660ae0445c984df7e587cf4bede78902676528cb40e69221a6974ff686ed2a6905230131f7f14e353bbe85f6284346ba1f85a06d9d64899759efd448bee94d32fe97eed277eefc670c23192e9c62fe01a866c7453f0108049626cd81d60ac9f6858cfcc1a3c938f8346515874a38edf18ab3194cfb5fc34b1cfca484e9366b346613f5a11c30c43996f4964194525214f6ea3678d423c34628bee97fed414974074187c554b1a85ed068dc23693e7a3518867cc330adae54b891fa76e7aeb249f0820551521e11d2c08253e4dc5cf90c12632274984dbc543416759409cfdbd9d1d3bfeea4d4f42ba9488fffd595a09d3883ba8582d27c3f7f79e0c28a4cd305c911d4a00b7001ba8c4a7f8b90483b0b3c8c420d08fb49c932ffab15b5e11e01c087ac4040a7ea140ae2ac7143cc3f7b50fa989e309f45cb768e72a2cf61c963bc9d4a39ad0cf4f8514ac96d879c45698a3046edbc23c54f108ee699a4d3a65465fb1abe7da7c200689ed4556c97887578c9b186a117642d00f835c55c0a9c27d16d8caa68454d8fd4eaca5c0c589d75e005c1c9e126d4864bb946b5301483f5cf0f03e5e7bf164ed8be3a6467d3394eda3a6d3006dc36c158cec02e65a68467ba46058805b82dc4ee1a1250e4d0ac6542bbf43a3293a21e2eb1d1e992734500248d6f2bdbeac7125056362d82663dad1a88fbdb09c18e7c091819d543066ff37dda8b4a14c3349b67f695d8f51c4ff6e9ace5085c19152bea811c11365c7c2aeeb6517d3dc6330320b84592733a05f67f23c47625a46f91cf19c925142753df89b9130050e422de3bb456dea3ad90ac15a4a08b4258f315a46a32d1161959e60c3e87851d33b1ab546237fb087a2facd26a80d8d86169697999297ff9d89fe9b2d685f1221749f0dca47377bfa8bb2683484ef1e691dfae2b7cc6308b03f341ae547bedd644e1e712d5f9a2a079726613bf4a5d11f50316986b23f84ec6b881a1b1332f11c9f94dfe6b556e71f36655363beb20eab19fa66d836d8aa8b4a5930ea7aabd9d71096af72e5439874d830e0a2690f58b35384234cdf4fd16c40333b671999baf516d41b2414b26008feaa71bdd1eaa908c1dd589e355109e33266addeb0196b578c41c20a8ea26325b1603133b8af02af6263facf079a167f6b62b71f8f7f213972fcd5609e3e3c7b521f8f7f26ea67a5a4dc0f5ff063581fd7efe5d39c796969638ff0b98f32dead0d8ea79a64559b513f4d1659f3648a83c6c49ac0b32de41aa64015548f33b20b6125dddf675255b0907986a5a74e64ec40c754eb626a810eee4d408be260079d20cd1de1099cccb4534f141673074404670ae6016378367cff53efee5f44aa0112575720fa043c7740c7fc5a38455431de8b69a84f55662b7b705086cb923daa9385e529f0d9f78013de347c5079661fe4e5ee21f6b2c0369904f59310b5490366150700b247175570f63c1d98a030f7662edcc292e62171ef0a457d19953c79cbe36c6eba1b1cd5801e22c81e530dbed7d3e84121c9236fe50a3eaf55ea410c362b3ce22c6212d8d30e17bdbee4d079f98229b6ee7910798c3b06ad58012619962604ad4e1aa642dd03f59755184c7461680b68f61d2606ffbb53e07506258501cc9f53188c6c9009fefc65033a3fb3901cdf6ad3f4878da30c4606be004d7164c89096289bc9e23f00417200bd4a6ccc8cb1cc33eae6454877124c58708e21e79ec0c368fb4b5bf5dc1f4de36cff9bd3aa39ed790eb33b23f4416efa005cc51443eac139b1e2f48ce9dddcefaa994feed8bd74819d6198b905d1f2d2aadc99964a1adcb59c3f14336eebb898842724c5903a2706c79312c33817c939cd66b5f1e805ef3a932c66453d681d9a6bd67af14addd8c9c438453cf961562464547f79b4a2326f685605b5ebf8e5ac56b55ffff54e9c3bda26f653223be87ad76208ca215df3efb4c65e6207e0b4c835d08db4ba4ab6d6920500f93bc493611e40fb5e456a3691b8b302b15de5a13b3ad5e1fbc107177cbc5d41008c51b326a766a9100faa44ced31a90c6303e766f0bd9213c0d8d72cf2583f8b55ae8530fa6b3c2e9eb54a4365e73c5be2848426d86f38ee622c0f7062b3650e67dc307ee81257d5c415bb361ea60c063168cd95482e0d00c10ee319ff0eeb98d90cf66b245449a09e674af01f6de9b4450a64ab490c08ba731b0052ecce0a2f7ce3f45e640d9d4535a1df8ef19e8b8ab32e0c9db62a0cc2cc480dc8e38a8f3126d318628dacca242946fca740e6b8b60cc7f1b29981dea4268807bb3237a68878eb2e3d6ef6d343ecd1e53d6097e337c254836098b46087c46fa3af7dc0e11aa56b721a3a8ceec7d5f7fbce4d35eef9aa8f4f6dc9b25ce6aa4d87e329429e2c0d21b39f48ce1ca343d55bd98f7b65933f2c66f1a6c17dd437d4aa17066102a3acb4789c608d1d360dde9b6c682c27ee39395440e289de436b532c1db6a51893014bdd85ef759594c68bb43c2e0aac45568ab8bbe3d05ab6c19785245e95b4e0e5891ea93cf3aa28b93abbe3e68e706a3dd6963ebba3a36efd0c0b8770c6459c0ad4f4f156d38baeba5c8ebad1114ef227886f0a7e0d1740930e6a12a698ab5323971bbf7ececdde90bd547c19f307186f1109755bf007d2496f907b68284ba67ca7c2b092ecea3a18e88d4021e90a4f653ab91ec5018c2c72c10e3637353d74d9630148c99ee89f97167cdf92d080cf7e7cce031c502870fb5f2a51886b78d50f89c48c7c0f51888c15f3c11d0a34c229a942df3f559968ad5d073ae58b12a461da14c97513e484135f18bfbdb8374aa7f340fec99934bbb6e7dc0383e62fb94c9b6e96df2632a272fc31f38c318a8e49cc6060a7400c11b0ce2836021081403e00bf02b0c3ed1aa7a43c6a2f5ebdac8f55e76dbf6019c3dfcdb018bca9db4c9db76864dcde02c960f9049635a9f8411796b9f947056d25dc18f6e2fc1d94b1daaad40f70274e5aa98e58bb9c0467107460ab6fc7b3f7974b6f6db8ed7efb5a473e247fc85268340360e17cbf468527c96ca88ae23d577227d071ddb2cf371ac4703011e081482f83bcbf68fd13b0cbbe807ae156c9e1eb08467677d3e7e79964dbbeb0d885c7faf9e99c6c94b5a1459e11ec17deef370dc26f6dc8d471e91268f22355c153549cbce214b4e42e1d35567a1b80495d698a888ba422c9ea7e36f799081418fde15dd6eaccf8bc58cc81a17660f45c0babcffdbc8260ebe3d4c0402c624fac92411f23fb11add7dd70ca946786b9f9eb9460201ce942f3441d4024586086a1427b4dbd717696649831d7c5c041319205d20fa5957ebc7fb9a03a1f4f0907e43683312b04b72b7e98daea38eff5b58b207e548d3c71ac63eac44418e6ab12137935b27393da06ec04f23c8a911a27637294d23a48cdb3dec65c5843f32a41297091ab6b4484c46884eaa26595176f7b2fc333a448662c9f3956275def8b55980d85b31637e86b48fb104a98733715666c3c99a905fa419a5670cd2b0e1bfff1217b0eb2b01929e0f7b9141f3ff41acf2ba62634d267fc48a75ce356f4583da538620aa56bf11df742ad2043504cfbc768ee45ccb86d4cbce2091ccba94f2629b4335b4570316ff3289013af20d18b31ed4b320f5925b5a65c42e113a42de851d8b5c5839114640b9024bb9dcb465d27684cd7912a77044c39d9b1af5e016dfdbd3104778e2e7839137198ca4b58379c55080e16e76ba759c0ffca9735c9c98b42a1c00fd03b4916fc5f99634efbd88291eb962e54fbd314154b0db850f4fea5232360083de3a4f1d9a1062547673cfe5569143f584cf004499b7d9d13c7b17fc3d0f374026604608463cf00a60c41b0db111e2b07cceece01e2cf393bf493238f13f478b9469c4652acbbd29a06728b1e372448fb7acdec3b3d15fcdbca0487485dc0b487cf0014fa0ece210106dc0e1fa72c32d3409030803f587938d6443060cf3cde47e03c6e296f245ffb665e54b9aed307ea5fefdb10b4608b55ba540ffe6aa4aed623b6b9281b7bb2a9509520a8deae2ae641beb7c84fb3af86809858c21a1a189afbbb3ba9da93ac8debe9e4fc6745fa282cb894ef1a7708f02b98558198d3af5a6e768e9228945bf971b29b7361e111f165bd08f544edce948bad197684b1b8ef9f9a4eb9c7a87894ce6c258abb3ebfb9fcc243303132ce2023ae3565b14f0d28a2d45589d2d8c8a7ed63a53fdaeb8a66ab53687b9d841ed4d503a8f6a6c564289529425ab14bf9bf9eac08cdf20da16c1eb0bb7d6e27a6a98eb68fc6763a7c12d2c8275809f456bd20581e122946da4a4063bf9e52b2974d859eb028ff7d884480baec87f5c4b5b15f87cdcffd2ad49a6edf2c4ca8cff62ec1b2e60d2c6c086772314c64b1636f08e7bd664a091893de959496def0ae1674b6a05b4ee1fdc969f4e42cfd5be52302a411125fbe2c9cce64701f852823ef64264038e821a6ed15cc01c87c5dee396a76769127cfb8bb78f0c1b8784cb4ad50387fe7035b60fd7f1d9097bdad73106789d73a8ad7b627b4ded785c44b9cb2a9169b4cea35a7b9a10c5232b4c5fd222dd7a35dc86060a024c5537ce4602f9e9972005a1996455277c668d09dbc096d991d50f7a9e90a3515abfd8579ca6ab38d267b01aeea5c81c59258aec6443bc0f1ef0fb94f639e63dff059055af5e5a0f880dff0b042790926e796c45ab2ea5826741b69ffcec0459662a261847ffd7638d10683019e089b79b3dc96362dfed3a65160360026f3f31c9c06d0210f6fb62fb2b81e2aed2355ea22e09f9f4924bf28e9efb6c22d01b5bb8de81e9644ac43eb4c8e84f84b19a286d4f828d4b44c38aa44966e87a6080add850484bd09b86c67694fe57acd8a69868040d62dd51bed284d75b390fa9e46e40b09bd21105382446888e56228c7090773e4d02324296b8300ffb81319a5b43ab66b4d3a7f5361d3e60659130216317e233666c7d421bbf7c23f228983bce323d8a672eca3ea4ad6263b58dd71cf6c0c3a4895e0c4a4e2ff1783ee4945e929dc376df5ba07983e15000d818c59c1aa85d0290393cc4352cc59b9c7048b0cd9ef68082ea704f828c4454a269da7807898adbbf70208ad2b57ca71be8951f984bf46ff9e9560572f95234d6a7507de04e7090872e58332e8a42fee4dba591ce910bf577aebf37d8821a68d518fc26357e845eeee9ed4c4da188419e8e31cf8445f33e3a2708875ef2fe96b963e79b9bb1504ceb25a3d48445d3d420d4eeb5604a26b46d733c092a4fa4fbadadf7821429dd53f522659136396fc0f56c276d2f96e93a0ac2b390880c6076f0ac0bcbc73b05e464f63086cdcf32bdbfbc5309febe0c2ae2334e09282369fec5fd1304a65f74700fcf45c24c018d862d1c0616bcd9ac2aa203ec6b830d895f724427f1a48ac0744c8061b8c2cb152c7be1b005862b18b6d0e00a9651305ca1cd140cc30bf867c5d1a9e951d3accb6a42044767d3f137beae2764d401191d126b8ee649140a9a4ef001d400bd8db09155b64475051523a8ae84ca882a235019b1ea115447a832a2aa2da8aca46a049515511d59abf2cf82a372e0a3c6f866eb426cc45c74081ed12164cae09f3cef6318ff3a80b13d5fe1213d4de26e7f513907651bc9f550b3948b100db5947424b02d64cbec15062b61b1babc6ae4ab9e095ce98877dc5ff9be548ef1759549dfd7306bf6340e4cb355e0b2ebd77297bc7e2d85c6f09f08118045b2e4a0cefb677ef31c66902d0fd36f6411c960a846b13ef5c34dc321c7d366c18f13f749d5df4940880090f2cb5600a7f367791c332a0b662d849365a40f0d73fac4958ea305d6a27872927b12567081ddb24ff847a330f8ff916d587a2f030fdce89383be2c63ac528fb3f56f60f56c35116595ac5a8534bc6757f30818345343aeb445cdc8f0bbc9b4cbd0d892c97fc86f8c9905a6a87feae60a217d095c8aad21fee3d620c7ff5c0e3dda7bf5d13db6eacaa6d6fa2c603bbd8cde251eebcb30dfca8c3eba31dfd3115078d9699781b33059e6a975b2e586e8f17f2ad6759f119f816e559640b37a541533cdacbe29af7b81eb8eedd3e6fa0f0fe897a6dfe0e01af66f06fde565bfed359a740628923669936949cd4f74595365969188714ecee187baf1fbf957238055750fa286c73db8e08b5ce0b09c82f2077d68648062527a2f2a2240a743c3beb907f43ff734b5ce55df2412953dcfc106ed05626e1aa6b4ab29f0daff42a1844df4e14b3b5ffc6b25f42d2f8d83a7302e099864ec8cd0d7f970396980c2dd4bc59ca722aeb8a1370c922fe8d194afb4828f8d362e83b3653e232277231e7e7c0a3e1afd3da4c6adf93811f51ca23a3e74712a23b1b972835e105bbc7d3f2c4246dac9b0f66edd878503830c048109bc051a252c1132036f4335b5aefe9462b5121127aa623752cd0bd56328267a1cf23bf24377a42c9f937ef09cdf7aa783a790d0091238c31e9fe2216dc95c9cddde311d8bc0de927bfd7e1500c1a50a4f57f05999833b16bbab167a8d5565b02d04da65557fc103d8e3e4ccb37838d2429ac786a6322ea1e47f5fd91a4d6ec0ee76867f880f9619db507b65d5b9cae69b5319924b1a7520237deacce607634f9ef0158b29b5ca756e8ba01934ffbd195807a4cd1fbaf169fec1c76227fb9d5dcbb5a60ee4e444a065345a33a74400813e100182e674e706bdbb849a1ac5ab64f401ac1b92466146548da5eeebe031c804d9201400f25289eb09d95ce8f59c837b46fb49bd09339eeb1c4b5dbe5ee666922498ec607379af0e2a4d48718d93e6f7969dc1934fc64549cc73001b4ddeb7d7cd07c225ad141634862173cac0f26a2116344a46e4c1e05f4ea9ecd675897f812098c8b9602cb7c413fc1e955142fad509a88dc68564f70b6517cc476ad405b12e52881ca30263721efe03593033db641a1fbf0e2812db78084d51fcbe8134539face46c613e1c7e3c6bf2c2daa54559a4393c4d168265f0bdc5c7c06be0e831670a9e16d14dfbb7ba8f447a1dbdc2a5035938ab42f56123e0327f89bbf182808487dd98a23aaeeba0cedde7173531f1bf1a5b19807aa80cdf37592a913557836d791d8e22e2ee638fe3a402bec10e298a6ffaa005941a07d2ecee97d9efa92b6f17c830f73dfd304380225a598980b5e307b74aa222223127e648e20fe4fc4cd476b83e9e5906fa49811d07c14a65c79d8e2b20b8b266148b6e795a61e126d6ac5758147c40b4ef70425ba42b248115109b5b2b86d23ff18aa03bf72d9f1a79489add72a9cce6d1b7401d69d4c869d6ef2d985093e010410ffd405179e90f2b3db1622603be7adf7f968d310a152a5510b48362c173fa25b6b0acde7faa53943496abb56341fed548a01593641bbbdeb92cedf362734993eec4f1da8d3a8d6b1fe56ab1fb73bb826438046a1c66b62b3ba75e2573383afbf3b764e46ec711dfd0defbe2193b20ff7e13ad5c77dc95fd97cae277a7efb3b843d8978e63c293744cdff4a184bc5ad0c1ab71620d11c6f89fb5ea8115342c940c2b9cbf9a046cc10459f16fdca05361bebf83c25f30d39a88067d7382e76f85ae6cb6023864722861a35e90883816100cf0458024401b3164ab84efe45c98a2ec55c23714c022864ce2faddc5bf219919ec588fdca6f2c0599c0d8437655d49e6ab33e5f4ef2d32a126010e59762b7fd77e1727a7b04bf466e873a6e7694af9d194fde451435bc2fef95f8ae0054a42c1002060eccd0ef214b0ad65801d3cbaedebb9c040dad82104a7a7b9dd4432699bc926ad29d93df34c606be9de1c3214fdc0e9898425a5800d9017e0065a03a6f9fcf1a68049bcfdfc9e9135e44484473e9486733935b0cc111daa78ae7a9f9dbb4905f35132cd316fd62a02dcd9065090403fc71822e312174eb2827a5c6b846ad853a4adfa63cc3805c792b6ac973978170fdc1c598dd1fa4e608b6170866d2f80aeea3d244326c5688dc1f0075e8ff4d9883ef1ccb8b0058f2bb735ae3ca54c28231ce295762f34dd99c5747f7208e44397216111b02acb17bdb78084d4f7f1298b8b4110b0c668040e30be5499c5af3d729000b81e172590ce5a059251b739b2fa1bd24a537192bd81ea987755ee52276871f6affcdc2599c1f3309ffd2b5ff43f792e942e5b799c2e7201d2f08cf17a85067195888d7b99884d2b37453c3e5c1886b4bd9ebd6eeaa1d8ba9b046732ca6264363ed6ee2b57ea43bca700b8f23d1016142d06a8f3398038cb30966b9f92c2162bddab7f43e8d8887c575a7459ab0488028caa103811c1dd4e4c1bde281aacb7fac2e1f97337c5cd97df76dbfd02645b29e7b14b97ccd4bacf6a9b85b62346a632d1a0a50d5bc4a0edf61e804138a2a320a4ce9f8f34c7787a29629bca679a7ed27ca03ed8b4193934055b1840db023a1a1c6b5150d48b90827f36b5b2089f614adcbe60111ad16d27319fc09f17cb31163535b142b07a02f2eebed51af13f53fd32ceb267f07a408b5c68b4b2cf8a9c47a418b38a04c7183c93b31ed58454b860a9be5bf87c8a8b3b895896c92ec74b9fd7bce27e532fb7603dcf69e82d884c13175aab22de6b236cd9d90290b870ed3835935b394c8e51defbf2c63333982df3c385fbd76b61b6482c84703f1b3f47a027aa83016ca44a1fc86b61dd690269418e699c315f9aa46c3f8f3d84859d318c5f0e7a5bb95b13d7ae98e5ddeb69d793704b0ef577056dcd5773b56dc85f87b5c5c3c42701ecb891a1e8e765af51b1a23f870efb36c688bfbbe944401dec7e960dfb82be781924e449e17a4497ab4f11e95d9974a317ac41cd11c1fbe827172f4d57c5a6eeb6c5d625da8c766733bf4d480306ae74f3c8c8f54ff5346e7a3f576199ed99176fbcf2df136ffcf2de9337c747df7da2a8e1b2c96d02887c4f5811da72c39c544fe1e178ebb18cf7e28d5fdefbf28ff77e7af2da439a0ffb2809f54909c1ee4d8254ddfaf822a2548a824ada910e6f24b974b1ab84c81c694ffaa4aad2eebea8c4215955a3c8e3455a244fc2db93e658b13ff215f190db9f5a20799f7d3bb47cd2d53abe193a08b49296fb6943355872b6053ecc4fd1af247131c2f51cddf448d32d893918a980783f337c7c567612cae2f1ddef250d422fe484207ba6b5a162cc1c2488342e45cc7f4f6a699401d691db366b09bccaa2436dfd1d484b4b3100363283a7a95ac3d0100b05ee57f58a9b7309ff5679ea6f4ad3658fa28b0e801397de2e6ef939d9173fffe28b8a88385438388bf78a989836c579745ad6bd7a753925964a5cc7705fd2c0169980e823636b3a518b92b7b56f981e8ef6544ffd49393a39d5de2dcbbbe024e404d9b917b2304015d2e265f60457d72971855180705cb94d4cebcb9dcabd5801ebd9aaab555773cc26f003af526e3ed68e344abdf3428ac5246176b0525d8a922079fcc8100b01580e1576d169c4f0df7543f45b642d8e0dbeeaafc69c430883ecd93fb132f1f97386a2f3074ea89caa294da9aa72ad7b0ac386704b4990574de0a18707d6fb1d72077fbe7c05b98a69ffdca332b748c105b12ff268bf9fd010f846af6428d2be9b83f4912a4b2919c5cbb0f151a829c73ff261dd2f93865337a42ff74f62117244076911eb8b06e53192dac2678ed06c569794e7994639e0188020c31b692e8145bdd4c8ca2e073baf8fa9980725e7f5e5d4f870f25bc67a6b685142d978caf81a6871d2cf2207b92159e6b869c682120b8272a7031c9f46285381246d893cd8988f233c106945ce3073e04ffa4132eea3546ca7d183229e72fc007d415552d20b37569f0e9f2c6259a02a389ccc4796f822e44d5cc15201878e0259fd8a74daf6cec9e1a6d0f0e75e5129072dd0a6125006415dcf9bf06db592d9f535ab27b3fc4a9777a0a2a7092b2cf68c4bbd3504e640b57ac2700b3b212adcb8a339683c13426cda46e6474352383fbb3313757099d2919d6671cf0d87158212f579a5b51cb59b3372a5ec359ad8edcb4f885ae699c3d99884e714b0d29282ebfeb44efcf4eb495c2c3682b0d70909e5e0e6ae081bf8f8ad504d6299023a971f28426a6eca5ac94af2403a0b336163a9d72a8ddd1abb9bc7e96682a7000587c306605dd422b21dfd60c2a4d45ae8e65e96c43368baad53924d9f8721f5065a0e10cd58bb3ff39723b6f1297166b5638e40eab276dade6c3a0492243e871658caa7d4d16d3a498e39bae3b2fd3bb498e25c5c5b01131b467ec51c16c95877e16b8039140fe097c813f7a5e1f233c4f92380b15865e670c134dc28ee47050ee0ae0d8939dcf1bf1363cab8cd870569ea5c399899900841b4eb101b9d2f5571e6c02062c23376542e3910d96e72eb465a1c0eaa02c72720cc417a8ea74468df337f16ebfa637b2096cc9c8f940aaa8fa6f95cead4bf29b914cf2342bc7d6f6a9334bd01f3eb90e1f34f1413a87046f8869b78f0e44b691cd361659e5ab11a50957fe78d595bf71d30d921b20d9702a17f9b0dbe9133a88e2a83b350c770b398eb5a2758aa7eb24a570c0a8d4a684b91934271ab152360896702c36db2afc433ca2d90bffc198fc390b112e00cfdd540a356a64aa7de1b28b014e0b4d45801db1f0f094bc234ee03f28c8c6ee6c1e4a0db4f4ae572dc3fad0320e1bb9a7173f5dec8a9deeda630dc16b90120e87f5e5ff8d806b32e6cea3a3b0211f9dda1e72ea393317d649cfa338231d729ace4c25e5b9e5cae6a691290d3753ff664b51d2d0172403e64f059d68a64e54b94754eaa9826b2e52454fb2300084b0045dc5eaa16a2941382067c90c1df0eea2f414a1b63638b905fdd60666c1d3457cf621847c3000f0b090ffb3c64a48104b57ff2f0d3f0302098db2c21d7024c5f785a7479b9e951e826f11d75c436513b9138866c226f24731edbbda3d276c644909de5e6e894001e331c631d58044c3cb99cb8e5bdc740ff9810c910299b5992d148afd1bd95b879bc1c1dc13f4401954c5f30e3e9632e8ec954d1a2a2b166f702e7b6f72265881a40e4669fc39a0f53d6e6906e268f06534565f051053eebe40018abe25f7c91e400d94662b10eaa40466448de57fc42795644ebefdb6f1498e99524cc9226e57f2d9ba1cbe405f264ac10133d1ae844c3af8230e0cea7593f1a0cc5f66464dbec623181859fe1a3760a889b366f8c948474d16d5ac42faae8508713ce247bc05d511357083044f803c70ed2e65c9e9ce186e3f968a05703ee5f5288b9f0d91f6d86a37936228179c103bde1915d710a8bb45f64da0c7d20e57c9dbe8163005579cbfa93063f0beb1d055c4310890937f0d82fc29952b105a1a10c2dcde2a1250b5f604e5af1a378d21bb48e318396ac052df30a5a92ca52e34c3e3613e4064168a49fdbe1e2ef505f7b81cd0831befb27469bb59c92ed5c56d345007c201fb3a80d74889ef2344e41d186ce546fe0e560fde4ace45cf1ad146e02cf5cda9722a9725b78731928ab5fa5a66f8274357d952034dd84c48550647d9941f8a6b3c82040ecfb4f1a4033f9c89c6560fa58d87d27041fe254174f97b97324ddc924d389b223db0335f730dd65b0473bdd5174f4763364796aca3a45334e6240393709e8f94920da0ac50002f13e758bdc4ff6de07c178eedb07016141d7fee2036f8681671317cdb0b33b21f7412a1bbc5c248444fcc2a8060680436c0030c09a7b74810738e5d5fd8d9c455c02997b5c3a48509059e0a2bc834d88dc4a84ed0289571a91d8fa94ac2e963636b106aad9b3845416eea63481ae428df819560716806020824d625d9e63be544f6464482ef603779b7a5708492ffc038b6986ee4228850d3c8d8929d9df4764cdae123a8d785a0a817181d046bff37c1f0ba59ab70cd28386a29b4a1fad4cf3cd5d39a6b3228279ed0b834321eb0dd2708a736f6202e190f43acd2c4c03d4b113d430884d334886202898c6ce0eaa16a4b63254489ac364306c123cc08676727ac05579f0e402a3ab331856ae2fbafa269ca0e36fd0e1d5701374e214e9dd3c81153e3dab5c131b5033145a30c319b806564b93f7fbe2012994a1852de2a1c80ed5b5ede38884fb3b2a84fc492c2913a1b9bf8ed11a449f519c9288deb4084f83b89523b99bb192523524c451b39ac16aa7040d932e0260362cc0d65a4c95107f33e8af8c1ca95b8cccea10b617aea0c0167c68210cb4291565ef3df440be96f140f11879ced6e0d39a419647c8374b33ebd50cad6af8b3df31b50925cc8f2768a333023ce757ecdcab19a1ff7055d68814a6d1926ca41fe72f320e2d36fb003cfb84387c0185a3764c17595a4dc50af9a106aac1b5df08046d427391c2624622dad40003e01087c6f50ea26d9fa639782d5547bfc7cbb3ded87d0ab30fba19dae3c87b94e3ebfd9a980a4d2adc31d838d449c326159eac26817b55db5750e0284818aeb94203503fa5fa94dec6fd9458ceb8cfae6fbe3e8bac7ae6323937bb4ea8d014cab2f94b404a65a8d317ea0bd39b5cb6d6bc68f6cc24dc628b83eb0ea36fe6ec500af2a2c65120e246c365b82d59899abd4198922f531bc608a8a551cb796f92c1037e41483752604c708044c617127ba53651c6d4482ef988dcba5c8cde012093a52a7c57e57a781023e5b29b5acb0f4963113b6a7940becb4bd2b9c408fa2eb3f97f3892dd61f201411f6b26538d6000bbe0c25d8126716f206cc565587d81c3dc4598c5f376f5a8e7093100d8a4ab3ac3b55ce668dca2ae8eb268436bf63390cf74e69688a3bdd9ef7dbd811e22afefa64e88c1c4f796e385c453d163aa9139f1ec3ee43876c84e42ab680f701e568fd06acddc04a80585615549152d227073786f00c2ce31a66cb99b94d11167f1a64b1e8689126ba1832fe123952185c2ee56728a4f9a91dce5d254790b4b5b9e25f4f178e4a4392503e874fd32e4a8ecbf18924023162b56235e02e54dfe2176a26399a91123d45304d7f4a978041c445824a86561135d6f26e1b596ecc084dc5718751e0cc0dd5a1ecdf080c5783442a5e939b99948f67064d8d4c25a425d0d792ea0217c2c9951895be158e30c2e34889bcda410fa1127eb2dfc17d48a891d81de3924754308f4466a84f9fc76c2cdf7c73312a8974c01dd8203bae85369283f06b740237fc0e413dc10cd26ecddbc48ab3791f72c03624d8acfc0d8a514e7a2d4ca202a475c6442a110fc161bf6a8049b6ac3aba042216a97d4727e1e49fca0f788961978c483214dd4c2398d1d7c9449f220a081173bdeed9cf605106780286d6449b538a395580a5f50c76f356c8c9314cb985a7f05a4558ff96826b7ca317accef3407196f1b5bc374aad9bbaa7851a3ccd23352e7faaf354c1a24e0db213047c4ec9993338d64a05d071588b68a7981ed778dd4866428360c7592d8a0f78890f50eb86c4f8bdb9c5ab10166073413b649941cc5e2428240d1584bc13ef80331b3a3840ad21eb543613eb9948a0e8b12f08fa0d0eaec9b92e39fc18682db980f0cfc0c3d8895ac554e43018670f0a0e6fec06024814716eec68b5376b134238e5490665b5e506f9ea8470ed6c33e254537e3c20c1c162e9bf15250ffc99216d4779eebcbed97f26df8e08a59217425c3ffbcde0254fe23654c4567ad9da82b3a21a83a076304e8cf48f9a0ad3159e037493158d4311822b036466863b10dd8a261b131089c31ec5ab0fe98ce031171106e1bacc6a74f9cfe02fa4e7608599e7ddd2da6f3d2a0d416db8fa684a08d06141b57df3b6a0474b2f0d3d864369724f551c72ee82edcfc03fbac448eee8fa9888deb3a4efc07016c930893b488c4bef76aaa86e9820c107dc4075712e293e4c3f25b2338ea26995fe43125cd63d8efdb2c17a6d4f018d0ee634580562049bc1acf9be907eac9d8087a08eb62e40081d96e9e69d578eddd13bbe0dafcc09787772f16d02ebdfb72d6ef0f83cdc77e958841a6fd59008f8402d9f4f357cebaf3d082240972fbd5d1f9565afb57cd18c167cd0373796c5a3e0bde4c82e20570602cf2169d08feae7b0c038d9ed985064c7a703707330df44d0488a3efc50ce0bf3ee57faad303599d8cc3e950e5a70655bcf87087c8b857e14c203c02af75394fe54c5b11d0feacebe25675c35a35eb28417150bfb9e5ee5c7b0300ab356d18a2e18a50be6d7748e011b4d992ed0064abc1b1acd69ac99705262e76857d5e6e74fc8c3c3e142d4c2aa64f7aaf29664c4c1f259a617449039aabd50f3109b2b909330bbcb2036aefb2985b30d655510779309ee67dd2810aa7ca4573b8883d520129db18a1831ea33fab0e47eabd482a606b9983c5023bf30b6bb68c3ee7ab6187d77c4c3e68f52eb59bb6dcb084c2ec23b0f67b79ae7f12b0a5bf2c217bef2df796524a990280093c0994094ca030917b728f3565dc653be3c15a6775cb5d52c504ecc290d2848fadcf509c407102c5091427509cd0e2f6d4714030f7642f688a7274b80153cbdef73938e7046d5012ae98445118f40ad4e8aad2d8981b5d55c76ec71be69802daef03bf307316e389ade72872bec15659d7b1dff053599c63bfe116940517661f641c716c9c90e70629bae5f90cad2d7861f641b61ef288a3cb24723d3b9d3eddeb96e7f3f319b271c6aca66670c1f8195c3364b406f6399633c6a733668f9d9b719b11ab29ec579433628fadc865cc2de656591307029c8b5e4510b89ec91b6835855d1c6fa8d5143efda8a020d319da0cad5538ab2097aaa1193aaaa119ae6e891cfb0c1dddf25c3534e3350386bb451ffb0d3d74351dfb0db5ca9ad85497043df639928ae49047847dd954c02410d42d7fec0bc83e54c03292acc3fce23f576dd1ab19318ac2338e50395111f1d855a79f0acc388249cf8c185d55008aaec108e23185b133011a485240acf5e4a1566107bf2b7ebe11e66f98c205f51b68f386eac01418ea160c13be8cb979219383c70e5a1568cb989b95c968df893292ec3147e8d50c8dc6fc740b74ecddab19248dc25ec62cf9981e8f3d46a75b9fc7dcc693ad55d86790f449098dae4e42e8938ec70e7a1973ebdb168f3de6f6d8e947b281238bed234fae568124db8d18eca188e6b17b321eeb602fc5da6307714845393acc2f5e74fa79ec201883892d23c95325d42a7c03e655b056a960dd1a397695105e82127aec950242adc2ee5c6411ad55d84742a2d4634b96368f71c89371d0e996cce325b5c77e03adb2a863720754abb0e378e1419288e8d564ca99ec8a6fe80175f20d35f2061a4561f7c8942b32f5b297d05e8bdb09091a237b0b2a41c3926c8a3c7435276929aa7932cc4fb7506f6b6f6d4851f606bdf8e3adf883ae2c4752d2460a132acb3b65dd624c8aa62f1d145fe28e8b4028ca7a5e2297e24e08442ec51df13547f125ee50949d2fbe30755cde2f73e82cfb2bfbe90610776acaba155f6fddbabb2097e2ce8b3b34baa2405e3c30baa2248efe21624c2c42579d5b176574e582bd7f7684f696137bde5a31c98bb41495e41d38521823bb02ec29e6e49af4b035bdf80c6581633f7e1933697e2ac8f4faf7a563e3100989840e5e1c36c84dc1719d2887a280f0982e243cf2be28aadefbd14460fbeb764c49229717fb1d73e0644eda91eb2df295f3797780dcf102a1a89a031792f74553d55d3ae764ffd1becb58e4f2c22c0b74da22fc16f47a1ff48250eff4bc7f922576ef3ac65e57f260afad2fa35d68188da2359da2de35b4f4ca7ada026306f97a736e835699b77e491cacbb255d68f0e7dc9ff316462ed6fb7ac5e1fa252dd95e11401500b1a49a60fa98231303f3e2d27232b1ac94482a2928591c8942f0f370c75d5be96c15fde28954b741495f02a54d27f0b6ce594434128d4423d14834128d4423d14834128d26c624d1280c45a311281a8d442314d188741a8d5a44a3d30b4c8c8c8f248c99182e2da796164e2e2ea05234325e50cda859b9d984382c4dcf4fe472ba5747ce670e1f0c510ec65e8c181c07c2008091281447a391188a02808242591a39462a35aaf61b555b63341a8d5636bc412e8f6dc1ca90117ea36f54ed37aa96351a8d463108a07523c30066c8d57904406744001a346cbc481ce5308ba28b4407c5d168341285380588010bdaacb2eeac86bb85bda2dc0ba90b16c5a568b0c3f0957505317e30d7dcd7e7b803bf91080c6b1034df9d3b9bb93a4f65c211d714d589e9d9746aef7579b557dd25d10dee31891d343550cfd92fcc6ecd56c980a12db82aebeed4d4f4d72b36bb22a66d320e636cf76a7516514e87b1e739c62b95854b2c5e62c7333040315e5ac82da76a02a291b1f242e98a6e915266b1228ac84c451499c98a143193714538f2c64423ae41b1c930b43b8a0988444271132903808e62af203c57e4d206e189c17882c4603caf00b86a798300f2d2a15d1040f0184ef7bc8178e4dda111e2ca9a2158a3572c3086d0d295154018b6b08375cebc832f8872ba9ad92af0e63a91cbea9c7bd71b419528a7c336a4cdcbd0370fc008b66f170f243a07af91bfb3be3ddf9e4121fc749fb95aef46222207c71d1d0045e38dd570ec09c7ebaaa96b6a422eef6cc6a86292cb6ba41b6f8ca2aa0807eaadd33c375ed77879d014bd46baebf42f5946e0ad2cf5df8867a004082d655d97092897d7859a1662ccc098a17a41064d0a6533c234004bc05ea983798a994555649aaa36b4d6f453ebf519aed7ea36351dbc778a90b3d6369d747489007ba6b4b1dd2653a7667777fb539f497c52a0e4cbec9302d7731c28f64c5752eb9401d671a0a41d2bceda9b1ddc03f8bbedf5c00ed35dba9f527f92cb7cea960b0dd43ddfe1f3d041e7443e9df3976e8970f8b2a95bf33987e9d6c87538c14887f974cc0f8e32a3a73e1af9832301629e7a4ccc8394f6450dc371ba1e1c270ffe1b5d6898e1a9cf30c383ed82c9f92e1d69dfbe8fa5abcf08b9ec2402c836d8f8f8f4007bea2f4c1ad8a923008822ff397db0865dc2744d85222fb9071d7b492d890a498ee54b14cb97b87e8ef0864c125901f3e63a9d28ce45cf715ec6f0f4e5392f09d09c771c59723e3fec9d021630ff2331b9833fe7b3fb16e198f13af41759e0c6f9dd8fe46c88e977a40b0d640e8f2a0027b7cf0fa19ba41b4bce6fc99136f351943571a8dbd8a05ea8a27382cf8f9bcf0fa1158073e28093b4644780a43746705f70bb04b6b2cabedda4823cbdab8cb4a39f68f161099d20b009d90c804dc1a09e5ad091ef5a50912fafce7710ecbb16b4d3b5a01effc54f8a011715b51a15b4ef1a15b2a7a288177dd7a8e079c0cd8665b21f3a2f3b608e454144e8a7f7a88645edbb86c5ecf1770d0bd9770d0bd897a6af61e18247e1e165f82fb2171307c0f691874c1dac854f6893eaa383886d32ad0f2580402e67041d3e707708232f3a2e4914b08f1e1f3cf0ec131f46b28f213e78401f3f42956f1f170e44a1f824dc02bffc0085ecf343901d853ffcfc9024ff70c492aa80f243912629f88b9b4a15242f4929c589924ce5052c4f587ed8c9a78f052dd6c5be64418b971f5c3d9ccc7adca6733f3f4444c04388c93d9049e23d8ef43032653d8ae4d13ec92f326c022e01f7e0e17e76667afcc031749e88b00097b4e03d81ab4d61baf784e9e112c2fceda30304ccc3a6539bae33d3b98149b6f2eda3c383757ee8b8267e010d0fdacd3e711961137009403e3efc7c172f84b58ee6321f1f86f800c4871f4f632d27c8ed9382273e2970f258481605cd5819428d4d9802203a5f5c808592eebb860514f0f4be6b5800fd74eab79f1e4e2272d79c60fb12d79c50fb49bbef9a13a0f81189df3f3d64cffb41287cfbb400e8677db97a7efe7017e76a151529d74bee39c88932929c4559b74aefb9770bc53db749196fee78531a6f685056cbc9d8ac16934e79eec940e38519476909a578f7a6a0725248cf3e83a22bceafa75847e150525246dcf804249fbe9db31c478fec4c203c3438eab56e2d98ca92b9210333cab81d89c0380743736476ad238e2797a00be44157290e23e331ee3908ab2c77cf4fb56ec1d0c4d8deb31e33d2d86acaf394914666a4a9d594e74ef3f330230dada63cbf23ea6540313c01c9d69b235d68a8cff97c8e2c6d784b8a8cc8302ee3302ee35ed2cc2aab462dfbc82899b11554531e48e668dd6aca739523b28bae1a886646519e5f7b2fe7097946de037fbc070e79ef72966636e3713433baf26a74cb825ed608c20de89e832fa3f3a029cfbffb4351b78aaef1ae4d49ab384e542349ab3c07bbaf4145d7dc9dcf6d287ab0c4d19fddc5494fe973f9b98f73dfe1e36bd43c3bd60f6bcbebb9169ee74a3bcff9479b37f488e7bce4ea16ca73d7cb128fe74a3acfcd6681ae4e79ce713b9a4533037dbcfb14711fad5ba3ffbcfc7afef3eef2abfdf7d9fe2b45d77ffef39f8b4bfef3aedff77d8ebb95e29f7bdd42f1af1475fcf709e13f9ad90786a00b041d77abe4a07bdd5a7110c64119075f1c04dd87f79278d0654698f165046108c232d304cbccec3defe13aadcae1ae9af27c3c420675b4caf31209ba5ae53937763f23ab5103632e80a11ab51919ccc59f1c584e2c4728c7095dd160739a99e846f729debab5ea270b60a87d6034d4a8d5a859010cb5ca73cf225f4fa2c5a4551ee73bd08f41eb168dd1420b3fe8ea235d386fa0940be832dead5bb738eebbb25e3cc62100e332a409641cc66348132051838cc3b80962fcc5415765c9903c5240009c17d7993830ee913a13e7a575f3cad6cdcb9c5d2f26914b8e1718b7221cdc7fce8d29efeeeee10733e29ae2fc65045fc21390dc7dbb8a88445221b5b4b4c498c538923384a2bc1a4928caf37b39f003c12f043f0b7ea0b5addb8792032b813617217f9e8b906df989fca5e4276ad8b8b146ed7a59a3769d66f6f29e831e5976a5da7bde65098af73cc60c85e6896681ae8ac11a496ad86ad4eed862479283a3c8c551c55346222f29f8edc8c1151616300525b4a1d9d06008024390124acacac8736214e58d39b0967074c9a41c63a62222b9743f72100489bcb80d9a9292c19aa09a2614e579d843519e128af2bcc8b5dd1fe7415194ba0e5db98eb53689efad75dc2dcead7bddcac15d76741dcb834ce277a05ffad82a8f23bd559e25edb443eff9c0def3724616efd5a8bde7327af4cdd7a46b3cf79abce7a50c1fefc110e43d2f69a078efc8c7c537d1ab4f0a6d7315d97aae2297a0ebbdec44b65eb66ea08b9e9dc8e0e83a14e5b938ba8ba23c6ff121d3071d04759468ddfade73d0731b9a0dcd79d0d5c83d771dba02dd7377d195e8596f61d1ba75ca2353dc3a9832de9ff1da28caf3d31539c5394f713b66af11ba4ae88a2389bcb897e29edffe9615cdaa09a229cfbd9616f8af8db2308ee7f727654c412e7b0a25de73150f499f5702ec74abc5bd939b6acb4796d6f3d2cf97ee7acf5750fca3c2cd4785aef19c8be2cb185d7e5ebce765fdeb45a33cbfa7710996b1a48142468ff77c652c65ecbce725126953a328cf6d501b1a45793541b4f73cc551469706fad05d906b82de73d065ddcbe3cb6869aa73710c29aaf3d198471245752e1a9ba2c097901bbb07696419dae3b7224ea65bf48a4a56507e50fc15fdf53babf7e27bbb0fc7ee31371a21736479a3e3f97b5394b5caa399bd4780d97b34b36e91de731a5ab7e87b2e43a75be07b2ee3075dcd08ea16cb7b7e3dcf7370c773f0e539e8f29cf3ffae830eba286b3261037580d0158cae6e11ba6a1f1608bdd7a231614120f16508bede6b81eccb992f41178d2fc1d87ba00ce394e0ec3db0e5790b88ae6a91f7bc15d4126a797ee3f98dcecd0e5d75eef9cd8baeb0872d275d7cd97ae2e4cb99297c79f3e33d2e5ab7f7ea165fdef0bcd7331e89434f2153a139acac7bfad5d12b6c495a7a5babadf58351544e46924b8ca9add55251bdb3d67945383e1915dde89c13e120e239112caa3ee71567d4f3e627bbaf9e1487477e9374a9a1e2609f4f0493253801a884fcc59e5a51666489a73e4219dd9ec6c0255fecab813d53a284be039245ee02a3f42b0fbad050b1051e8b6ef8635117b9bc3bee655be6bcfc64dfb908fb8450af0f3684ae42effc8bd115e8dda9565930826aca48af62ac68d469895ec5dce849d6adeb9d9f6695352a4fb6ef4eb4ef3a1f8d38c68a5e755756e73137da097d77c9c6a4e754b480eba08b46cebf99c395997eba85bd3bc277a458ce0e23f7007e22a29f80e405d007491b541c7530cea827c3d4ead5767b9ee7d91b9e80e44fe439d885a3310688a6ba981b45c5dc628028aa136a5516600cd155f909c170d284ce300a822144579e77fe0975abed28c2546c24224830d210152188b0cbe34b1841f6f3ce3d5ab7a677fe05d1abef45513bddf2bcf3eed527a4519d9797c7dd51e27798a84f08aac9f7a22b54168deabcf36004c1086a5507fbaefc84bef36f8cb9b5aa730a63a7559d3b17b9736f6c21a855ddf4c81215e43b2fbdef30897ab5aaf320047d87121a01813d53c2087df513909c02f3ea1600beeed4916d49ae18468809317cd18f1ebbf8288f8b5c9df3182acb46c893ef105d3da12cd039175fddaa0147144d719ea3061b6ee09c23952f5fb66d86e35cbc22cf1c74d539e73574de79aeacea5d07f39ddfd80b4febc6eebbbe2834b860a80660cf943924ad4f9e5e6b0063686f7d5291b52293b5763e2ca85b01b09dc356964d571b16ba436f438ec9f356e4c96103454db2063ada4051d6a653d631b681d94c5191b77eefbd57146314d5cd39eac965db42206ca0ab7296d4861cd373d430e7a42e7bebed437af6d055d94f568e9ab2230af2f51c5d7f0e6f42c6224c845112862397e21031f6b624914c4059b405caf655871bb5d65afd76633f686b153776d789309106b61b6366220d628ec43401934577c385a1d3ac1b5f66968409eac812cfbee2405d1a4323855a352a2197241915dd8879d3ad5530540d2545af48318aaaa4231a55bd4425791786bad5790c4d241d21f29062742506e99114313417bcf8ea2549f615468df6d5ab0b435cd851f46a55ed4811f695bb512d8c24145561d860d452aec8d34b183594adf342043bf7e449fbaea26cfb85a2daed3843512f14d53ef382e3e5dbf690a3406102253e68d1420a2c78c20e9e180f328fd9143f3a4b2849c21354e8568322d4c0263af2f229b3667a114384b839098b0082c94e1341bcd0628633b30892832546f841041598a0036b7fc8d3ed4cc287e5210b7fe4e9d5c543c927d90eea30bf789b8540984fecc8b70f91a087e1db87889107b3d00a72f9d9747c36fae9c8b05655b7a3686b1508f3a05cc2362ace425ffd8e20accc447cf56edc61561d5fbda526007fe9ba646997a82521eb337b286c06a04f0d944cc7d3758d60328409cf778dc98f2f2f931e6e43f77294ba0d6834181ae28231e526b24bfddb3e63533d86ea5e1d559d04452ebdb2680f140d1b0aa8aef365f7c4e0eebc31ba2a5f5eb819eea228aaba4d41e25c1376aaf5cb7573ce7164227ba66f923ce0074573bc31ef8ee825f5d27a96d291e67bef1d71b8aef22daac1ba5ca3efeb730cbfc1077339dffbe93cdc691face12c31f54952afc49f12ce91b43fc91107b91d6c12c9924e3079b034ec61d493eb8a972ac9aa5c19cc5338f1f57a940ee34e07e7eeee1ef94004cad371776135f5328d3e9db656045e23b7885dd91a5a4c96b823b9fb5a59b0d6fe465eada14a1519773b3b8f33ee0e854b31853685ba5eab5a85ed088a3abda62acbc3dd5d533a98d6bae2d5da4f46515f2cf6b356d109797ab3b0a07c254b0bfc0c2478fdaea5b6d65a6b75a1dc4e84dc4e5a3bff7e932e94f19794f60eb5d639ce49daf6e9939ca40b0dd3a997d35da8f3f094e4e1797817cad49d0821a62897a6a826f19c27983c541abe6aadb57eb3acb556d18abcb25ed65a6bb518d7ae56cbd5aaf2f39ced463bc8d36dc5b4d6b63d797a0e4747f274ec83dca0907befbdf7de6badb5f6de6badb5d68a3c50a4d2626a89911961b4900ac1173e978979817189a1baf6c6712e2716936ac68c1a514ead993630dc6b597edce92ba21caed60a0508808a856a29ab8615d18d27ea8c00d41229f760adb596c6b528b73c3d8b720ff9e6dc1e68d45a6b541df9ba28e78a720fb9e6d41a2178bad4762048bb149629e3e0ac93fc564a2d2b64c9054690cd96a306aeda181c6889477a13f2ecbc69b09f12a3022305068ed17d0b5d67a3ee25c1dbdb42e8c2ac765ad9f5da36d2058ebc2e1fb7aa88726cd75abb5159c02814cca42a4a0b2929947befa5b1334f51947349162bf24c596b2d8d4813e2a07fc91c4e885cb9d6046019d716f3030852db8160a59613c1502cb99c5c649c0c675c7841d5752308839ca9f914308a0ee03230312fa8262fe49956f2b4066a03263b50761049bd032293fc22b2c1844b8b4994734f36180600e41dd9b200206c196250094200688496da1a2e1c656142b6dd6d82225fef1a169b4459874594636158555266592195546884b5565668e96a8e1e1399a509797a8a28efc8d7278ac80aad8d21b43c321d8972ae28ca3bb2cec71076b7c09108668201180291a74f4aaf4e530ad474d25a7dd44bbde03a3c3baeb2288e9d756b5aee36e54409b84ce89cd0e0e5021aec743bb269a896579ad0824c02829119d03829cce088ce8719b08073924959e88a9059b000229786d093453e588788990f114408414410d8474ef94296559a703d802da941c6df3e302546c0a2e85e39e5db07c64497856cbf7d604b703bd9fbf681c57260b021ae8b9cb30b0f165e2629662d3c5158010b12d789c72683952968261e1cc4a0b4c3c233040c48b5da0a4f0c7a50a1d54ad806881f2938e330fbe890300f0f142a6e2a5bb42043219422c4093fc41e1d142149f861e4835716e2f30211153aa210234184486c2321360002f4110911e2e3abf1d486845948103f7850bc40213c54807ff0f35541051d10605e164070496418a8872bc5ad4bf2b24008e29c6041eda15d6c73031d8a330e730f6035d598888f28864451c427063c3e7e10fb818ca6fa4b9c738610c28fb3e97efb0c110576c1093c24e76f9f217290c410b01864996f9f21747451c8ce846b926390e015e4931132c828df3e3208c20319b0a05b22b37cfb0871051b64f1db4788264372508e10371f217852be7d563083b7df3e42bcbe34d18a803dbdb5dedaa1c27c6f0ecc13e5d0ef9b9fa34cbe737544344c1c53cd7f305ae6314921979707c7cbabea50d37fb0a72ef31fcc883cf16cd5a5d8bdf002f1e5d569af95fa48dc22c8530f9b50160aac8a5edd0aa22b8b520342fb3113f14089854e506c20ec69d5516b328b5d8b02ab2cba24068422a34d5066d4094a8da336ae9b9dc561eec9b8d6aa5bbfa6f3c6bbb7d6ae73d05371ee7126f8d961b25bc5719db5af1b32efcd5a755dc5b6c5df7b516c7faf53259425c26abd9c5fb1f6f78ab4bf579cfdbda2ecef15637f2fd9c4163d6c5867ac3999c5ac371b693581156195d52d0112634d44991371f644a4611e624de7e3c8ee47a79ae072939bb74e128ce4038253d44abd697f17ce3965366cd09a5f58d35553d5e74ef566b5d76a6d083a4051d5e712ecd5e70e266ba0a9ea1d690388ced0b98b04fa3a6d93cc9169b9039545f2eaa16c62a8acce3fea514a76ad2f74a40db2248d2f24d3b475aa7a9d369cafcd965740ea285420e4ab1f80b2a62c5b92e6a1d0f0821d8aea9fb22854a0f3256e9cdcd5f42f94d6a7a5de147214b5d65a6bad956ee181dfbdf7de7bbff9b5a8b5a21cf1094575272073148728aaf33b8a4f1240ff8ed945511d26c5286bda40a87392ec085dd53a27290922e8bb936cc97724da77de32be24d9bebb5f927ebebb3825e9f69de3b92af18caecabb43a32b5b77eaf9cebb3c25f98e0a5fcedc1b58e81974300ea8e33bef964127061cd0cbbce33b179fb40ca03d19a1a9ee24eb4e328a3a1911874833ba2ac52192915b84ce00c64832ba1aea5637f8bd46353148af4498f8a281c8339242a401290623511826b193ec24ab30b7498a79578c4df10871e8249be329d6aacec5238abc18a3ab5311931423c55a35f4ddf7ba5decc512bbb325be2bb1e9a7559dc7c846a47befad76ba0d7ae9fda128db1599f3f2daaecda6e4fe0cd1d5134a7a750b86a77eef926bbbb63b6dbd3db7e7bafcb441b1142421cb03ec9952c6f5d4b3a5b58a8ede56db8daca770913bbfb579339f5af1260695a8977e501676ea31b16ed9b8fe084dad82e8aa0965dd2124f17667f745513cb02bf2a440b671ac75971cd831598289d3e174ce030e16e2265459a725ba20028b25478840010e7571a8b278a841126c48b0fd80040b36c0a12edea88b41b47b11c88a41a478f341a659c6c54d3185ec3d07fb042473de39a51da5dd8df9393be4583274b046921ab51a3690526abf8fe33e4a4990470dabe8c6fc1d72ae5b2fc94a4628eb25e673ec3d1086e237a51dbd5af1a157d02c06c0e03d260cd1bae1e894e72b6301fabbc67394d146ab3c77c70f56f004245b20d9c5fa7c971bdc5bb275a328cfe9a70454a00215b85811334a0a48526129b9bccca19aa20ee4e7e7c78993d96ca644090c0673b95c3c3c14b8dd4af0c4068d76c312771964e89797d74b367b89282999a402b2945c5e5e5c86289097393487845020a712cb8f1396f9539a3fd38949853453c24296d4c8ac3413a204242985c15e25cfb9e4257e92832a0e965e6585ec56914a64399f44826ee90ab41405a6f0cc219367ded02045a60cf22752a004f38606b9dd84f49357172f180f8b0eb961ded020466845684296bcba78c178288a0e9161ded0202e448657172f180fa5418c1479ea425e5e5dbc5c664a15c076dad3ad9439a7cf740bfb74d40b0750408e1cba3572d0a77b60ee744bf4e913085d655fa1f8f4696446572b3e7d1ea12b169f3e7fba65f25972924f9f4fba757298558c4fa73a2e2f3eb1d037f4d5e2e5fce259f3766b72737284be994f9ecc24d0286bd268359a8d89be994b96f8346bc6a8a8e2a74f1e3a83e89b3964480b7e7a0828766da06f260f1e52f8e93550d6016aaab68486beb9c0ac01ea1fc4c04275cff4187d638059832789357df6cdccaca13e2b8e6523b39f4760944579688a3ad7d56b7128f2c06f9451c415169512c9d4a29c989363110eee6360a88bbe288f8bf2b490f47522956e4f6eb72726b10a2c2bb425749649644965b4b944850a416bf0942211fb14152ae8100ad3193293cd21e2076660f2f0327f93c748141ee0027456fbba06e4d98b32000ad53d78c8486114456334356fa8105d439dce9e52191d62a69294a7556215727b497960660a1047b8b7c5822ddf27d3b33c1e4d4472876baa71684795141b86a3108f423b8a3e7c308776041f4409edf8596bed4a68adb51eb61d87834beb127e2c219737085f7bb637e90766adba3aadaa8e2f0c773018659d5c3123b424b2253b4a60b39e9acd4eba53cb0ec84ed72d2a14851f3dbe1d8aaf26b029b9c36ca07c7d7e7b73deb955a439de9d5689adaa7e8bdc2294a5b263849684aeeecee5b9b75be4f65cdb5572975ca07c875cd8e581312b4ec308812ed68a31ca0a8544997844ac894a9258621a12975c57125f88b1cfa504912f455a124dbe14937cf552b489b7af6e27350dd155299a9e549103acc8258c90501dfa601fec8b7c5f548c5dda09213104462867812c45990380728d9142240c4689159a2c06a3048c122dc69690abc3d82acb6432e56c82b17dd551b97dad24115fbd5461f2d5e784111a2261ad320d55168cac53d5672e9ace57989d8751a2848151e2ab93a268551dfa6a1aa2ac0b3bb97e9c5ea72174d53e4bf47c25d58ad8db14a6211f2484be3ced7cf541c2c897279eaf27d82976927df56b4935ba2a61846c745592a248526b1548b5395227854858b756be7a0b298a6e51af2e768bc270a1d4b8de7653d46edb75ddf55e8f4bc10fe2b0dba5e2295e5214d68b97580acdca2e9a1e2a6e5d8649dfa4b875192b7a85e2d665b06877cdc862b82a8b478bbbb8f5183aa9198d0ecd0e0d10badac103f845b74ef39a11eb56cc5b992f4a992a3ccd8f5413658c1e6fbd65091d6f3dbb2a4b74a18093fbd21424e764dfb9a996bb529d5d471906659401a24d53a54c5078ab2c995a4d59b7a54ced8659302b6eddfa0b5d95dcbacf6016b727b75e0ad2ab96261a655f2d4778de92a478eb24979dd398430107a0281601deda287e429ab2be3296c697d1674654abaca78ca9aeb18e32ca6894f597b1a653f53369a228eb9c3812817199241465394e94b15194cd1149991a455999daadb24427a213985107e3bc4cef17b7f8f689018fb72f53a32b2bd353cad4de96b8276c9f2040bfa2d5085f861387fdf98d1df8635292fb4bd1c95b97b9750b7495324119496e1f2036982f593fddba15baf596257af5fd5094ac5b22ff92d028eb658b91b74d38310197785b72220ed1d5138efb689f6727b2e8362a375ef19bdf9cf77ed72bade865cc3c3465330f45d9aecb2f8a2ac95a656584e8aa2cc9649a78407486979b4c90f592ccd611ffd0550b8da2be24f4aa05c9d704708971a605c90cd0d3e86a8649a3ac5b999bcc4de6d62a29ca922cbf5a65bd926aadb2ee5ce417796b950dc9999d565997b97d3add1a1b096f7f7272b4903be617382d48709945bc6da1d9928bcbd838dd58fa96b171badb75b7eb646e301cb4bbb7bb9c7bf77ef77ee115e19009ca308ab2416f5dbc1b89b4c0381197db75ee711e49ce08c964413b110e99a05659ef70f7f20243cadc6aca7af7e2f389bc902538018e8bcf91c88bccedad9760a5986d8264eb56975f6ffd25bf32ac5bd8ed8e13b8705a5a686f392f4bb2b75dcb78756acafbd9448d6b858e9e252759b10bc7608371075c33551de39c29aca6ba6c21a129ca0191e492ba76109b37d6452d0a5a55bdfcaadd71c457b7ae79737f660d0aba7591d0aaea7856f16557dbf1d5f1c875b5cae288d0a9b7754ed0c2c4599155bee46a5dedebbd41e13b5cc5bdcd1b0e0b5d83846e715fb4aa7a7dd22a2b72c93df9ea5cadb230ced96472d4531fcdf240a75cf346035db3a37de8d5f5a9c3f4e4a7972a5f52d7572d7e7a495190a3c8f42bce7c6efed8410672a0a82944713c988705d83ae4f63962c9abbe7d8cb0e28b0cf9d286e7a9d3904105496e9f22b62f6ddaa748eda9d3880187825d08d1a803ac5f17572be186d34a4032840f4db62802873a0c22b9716608e1348e754aa24c3e4584be7d8adcbeb4f131a2e76d60e8563b7d2a6a40e37044ee8e898344778aac81c4a1346c288a0a91fd9b5604203f9d52fb03bc3b3996a254c622649cc5cf959ff3ee68c045a989aeca6caa3e1950491c2669ea1c4e07a7d7da834c9f70e2c97434cb74fae4f1d3f693455d38d4f547e8dcda1187afd78db812874ce260bd62fcc468143f3014614c474472164521f875dec559f0465eaf15428a8fb093732ac2216a00c511396873143903397440e493a4b69f79a33643df3138f99e2fd0e2db35d03713c8aca12e09b038588ab275ab73ea2d75d6917586717e65dabca13cb3863a4f39818c230ac3b99c2f6aa33cf38626e9bca4744444f4164160e28864094e8033222d8cc22acb8664e934d3ccca6a8fed69f50845cd92cc9bda44d750a74fb48a7a479694ba88854c6d9555715a0114a7bff84a76abb83b86d8da40da7901365d85d69ba5070e30dff3e6e7f3bdf9bd40d2e500fd9fe3e81f4b0afce43ca79db7687a73f4c0b5ced949bd22817fda9c284d71b38c59798a538e9e7acf1b32c788231ad53fc91c2394e89afed276d76cb2934cbfaf1f98a71467458f214ce1a7dbc601da91fbf953347f5ed2f660ba7ae2c05a3af4650cde15beec9fefb2879e0a19f1022342bc8c10e2dbc7081b3c754c4352e824a3be7d8a8842114b9080b9db4ddd8ebbbd6d58980d02a08ea8c4ecf526452d192a9201000000f31400202814108ac542a14892059a9cec1d14800c73a64a70549a8b434990e3308a32c618420c20c018000021800c11cd080010fd36bd57fa28c5978b790163696b2eb4b1190dc517af206c1166d3d05088c32306f34e2568982f3299f44c0ffd6d89b9c9d2f19b5a2a7e934bcc55b9ca6e2108e1786ecca6dcc03f0457b2b7444b89dcd1f398830b712e587a3f39d67c8d0a3f3741ae04aeb24d71303d7dfc0f9a7e6584ec4ed576f1ee83f0d5c4c7facf0ab9401f082f6f68a5fb2abda4c833aa795c0962f03cebd00292ddd169887d509aaea92e55c75d980087f38f44c578c1e163cb83fd60202462a801ea0d93552c251a9644edc45270f703f73b80b3a167e853a987e6c6ff1a7a32b43d7976d70e4f35ab3b94f591777c95881da86aaa2de59fc4a13e32f1bb64ea4356c2030c328fae6fbe4b061ce0b111934169ef8a72e5ede0d335ce3b80739d5ff7b00d046508167906fca821837ff8e8ed5f5170b065c8260c04df7e6907431e5348ba9aae4c90cb9fd486786595038f25127df4480e3784a496235410813b90f3b670cd4488069d26cb82841e765ccdc1a8f2e59c68962da223e34d18acb3278063ed3b1eea0ce0b04bd0396ff77ebfc6754e54b11d9eaab33f4f7f91015e2e3432d0dc4911fce87ea8f0a90d6cff7236c0830fba51fcdde4cbecfaa9f69ab821d7f6c9d53b403688061acec7ddc5e4ac268da811adb49c1bbf54aa736693c266a152b46c9c01bf529803c573490f3c6acdcb854c884b9cf6413a47ab2e36ce75930adf8ca1f4f3d185b2dd5ab5e98c7d5e1cdd0fdb4a52771a10522227d09cd0c05c558e96b397b015f469570e428c08b95291abbc0a83ba5595faedf747006c41b5c569c0bfd813c5175cd1dc671561e1b96888e00587aa4aa11717544953365687fcd5e64fe41a62270e0c2258992095b901a678d6d94e56ce0e8cf48d953c688b5da82a811848456b750b136fab5cf2d5d001b18fefffdb4cbb382418d5259bab4ef8dc595b31b18e5277ae8bb17ff1b939eb29eff6f3a7e7521858f3bf8cd3cdb93a5d931c35cd5db9b69d3c108e71b865b581120e01992f61f9b3b194d845f89d219939578d00ed6a62abe12d7a11edc455d85a27693f6f32e0351d6ca121fdaa522ad53ec52b0c851df97eee5bcdafd44d915ee73f1d1f835b61c5ac1cb0ce86cda90d61586b510942d7638bfd0a5518d01a3be05e6d8f80f13e80f984176a5703ef364eefae203ce3e73446ffaf16a899df392ac9868f693271cc0fa0b1b1db3ab14299e44c2b2f6f23fa0789925efaf742f47ec316c4fc0df4777fc953d3464b5f957644ee9cf4ccf5cb7eebd2f6ee224ae3c07d92bbe62dd6c3e18c0d16f3b6d0c786aadc163845a7aef92ad1a437c67d445ed22441b4ea8a906972e0b1640f9203baea4f352994305a4dfab8e70eaa2a52646db84363f9aa218ad74257e44dc6785cc834048ff472516c1827b39ab81e6c7a089ac2781461dcd6106a45d95648e76d32a6b3baaec8c9f90269567b5dd1bfa5576db138f19ec7433dd6fb0b96d2efe4a69967e2cf74270914b991ab3d6fa17e6ca08d238583119bc2ae45490a27f6d6147067f3978cc5c561516e87cce2472b91a08d101f3b4cc5d67893bd7a30941a85219bf1a24dc5a9b8c7271e5360c5b8f23575bedf577c407710d8b2e3b974b8ad04bdccc2f0c1e073540838f5c83abbdff5e98e48da3c177a079e64cc2056826510135c9cf70622057abf873e69269fe38f4dfd4df36c420c613496631c8800d01bc573f48bf2cbda8a6d615f64737ad13d41224ebe6004ce72b8c7559029218afa96369b959ac3bfebe296964510cc9e314b9ca1690f57f54dac946967d7fd447128302216dd3bb7c6af9655d4f1da970581b17a150ab2b29c6033dc5ad3041f4e2edd452308d935e3f40c017db47481cc49c8c2b0b35e896e4f560fa4bb10775521693b3a958565d1a6ab8971198c3a84b1b204c4ea2675e90851d2287a9589d8b56a170d38f986dfe2b020ee1141a3c0e617b81bfb5d7552067157de6f84dda31d800e3b0cb86c6af688fdf94ced3dcbf6de4951321080000b5660f7a4e54db717804dcf95ee876f5ba3ac5bd0e8c38dff5f0c035f489cf1b65855a29edcf01d3c64970a161db6d1737429245818ae72330f1f80f6bd197a449538199f8bc2f0317725783e7ca7aaabf896dd1eb9cbb3ac7271f6a56e0d39652661aaa7a13d30615aca8e1aa5439164e8a0ec372102d064ad2dc7c4db0c6920aac185f78705971637bad805c60cddd26a0105a2779213533db3802b22f24511e7b33b7151fa8ef8784fc73e2e06dbef292ea8164a8ef856f06968952fca86732992678c288b2bddd1da5718bee41d6cf714af5530b87eccd5b7b903ac6bfd5a54d02c21a462c7a8f7fbe54c4609979bb8f5b3430c1386abce3f7c34a9b7e5af8fe9fa78c6b2cfec48f187e3f90df347100c0669826704ebb5816993ff82c326c002310abe85725bcc5d91827ebf6d0a004c5f2b1cd21e751f9905b18bf8df86a5932e94ce2a9d4b4b41b2ea4a382fb50203a86f0f2498645c81449ac4eebbc6fde9d53881e915ec03392320fa91df6b38793be396252c2e78f33576887ffca0d4fa9e35817ecee536272e008083d2fc51694b13cae478bfff8d92a9cc21c53bd855f111579f2e018e1cd50f4daf4c71e9453a4c6b251806e8d90eb695d9ee46e12a9e1df91ecc2baa0776b0e1c387def0ce55f7f3441112eef96eabb63a5250412ccc28b6fcf29709560f9cee64e560db6bc42358b2459a9dfaa245d78c290758e0c7bfd62f8d7b896c36e3d3d9a2c39ed0caa6c6c8c63052c049af92d47c9b15dc4a0b0c8fa5ae70c5407e8d614e29dc4a43336d614304d060e084ec4d0a02278b7a70a9468fc9442311b2522a5c75bb428a7e20cf481dc8bb80d8a9dd852f9d4e0fe83b0897b10eb9267228596c2753ea72356af8641006cc4aca6419112044b1f83b7469704909b3e02321f2bda1ae7be73409a2543b3fdd9e5d2c653275e25bf56070634adb27c000ed2ae1e73b3e209ccf30c1987ad9aa4b4c037cf18826d7363c0dc40a82a1e49736248535935167d7fb0a52b20c5d067557215066eebe1bb278647a41bd7e9934a9dd2d37aef69d1c589c1c2deea2fa89a20be42ec1a8f80d92c2fe58511ae33b9201f23fb68b1a84f719484bf4273bd0564712a2f1c6905314445c69299d9eb5b4dc36a487d2809fc6dccd5494aa9912d97ed175d8b23a1733f0df84a005b22baae334607db518c9275aa77a492ff8062f5f44e46b12a5dadca9338932307d25b10f725e0d5adaef571565c361ce0be82ba4afdc8145b8bff71bdf18948249a7fb700711743d11bdce91f77e1c030d975e2c5f43032c6f7f44b5a6a139c07175098b9921057d0af9a0c274e71832ddd872b0f9513805075e7533f77a8a6393c0d53df05b148dcb7cbaf3aaacf65091376582d5d7bca11d50ca532ef0067c3032ff7b6a195009a7b088e1e0364102b549b4ea37423968dc1daba14678ca156572cff635213b2ed23b1711f43f4aabfe2f127bafc009dfc7722d0ed78c91260e04446f53e28047cd2dfc11e88189c03d22aa6732702522273848014980a6a2c8149f1266aa12756ee7e5d6adc4cd0116d52b1a139f962a7400151061737eab43344778d72dc97d5adcf849c59d24a331f461373cdbe8d136185a48cae0eb2d3845bcce720c6dbce80250c57da4b29f9dbfd3e1080192e6c109c424e1c84ec1d01faf028952dc77d4a278e3cc8476e8379f94f79a57ca5218d8cca1f45641e56fa5a36de9597ae0610fc783a60ff59601526e53a634006bddad3abb8f5ee9297ee743c5bff5ff63883414d99d8a313988da821a1cd5c354aa803058e551921748ade9457c22088d0ebc7bc7f44ab646c8158b49e4bf1caae86d057ccafa5b8144f6b7eb3f28b965592d8ca0555b40dcd73c118f05ebf2a830e2270b21f5813da84e7361c3e8fbe7dd82f522680a5d09d2f3a03c231fcf14fb34f670507a2d066f53a6028458321167275a99a8cfeea5646941e1b50dafa8a5636287e6943f711fc7a02ca5e513ffa842e8da4dd41b502b9422ef80d0f40c41eeab6e1f072f717aec0e2abe457843907b1d83d793e304393adcf54d4f2c4766e52f8d8b0031c1d813dd816c3dfe031d838ba289f9449d2b8914518c9542aa4c1ebb7a9e1e1af8b1d3b0d144107c766ba3440798d1d700c662fccff0c8b3746d4c431b45f06e92e34de3db39c3f4c6973e6af2f11b401b4db3a980792f4042d45c2f7a2ab422bfb22263859c8e4a78126b3a02832e6197aa0c31b36c69cb5ec34445e3f6ff246ded13b13fb161679f88491e63daa054065989522052b3d084575b2f5aeff802bede46586c8c44f9dd41c0006ba5fd49e363fea1787f695b982182786ff7d328635005be94d5918a6eb051dc0b1089a6c54043c298b0d7b63040327667c7edd5880b4421e6f3ab73799100664a1b0e4cc5925ddabd738ed842e0373403902d638d89e8dca38bfe450f8477bec5dcf2e8cf3920d9436d48c1d1a8dfd9136b446913540411a79fb34e6943b78f143d7c222e7417b26f5e89ec68759a40cd77d3ce1fbb753b7098ff53fe2ada9f53b51994714e8bd5ef2e6f5ac480910d95b76ac9c249e40315cc3bdaf71f964dbc733b679340451a19e742b90d2ade744da7752176062f83a7bafe93ea63e64e2a99e4a9ead4689611f918c01c64234a6147983a7ce70c2e9b4667925bcb5a3a6913cbd1f43ac6af58d206816e59af9cf39403622cc7246a6399555a6ca19bb3a48936613019cbd399a80e7c36ae6e29aa8bf8cb12899666b74ffce661548435d1c6d0a6c6192afbc69432a058f855b719da10918013cf365fe776deb2f2b7ad898508e4c6fd6f820fe9d04c71c5945c5b5c1e16046000bf4ecfa8a8bb3f79deba0bc6093a828b8b47c110d32ac3b1b21e5c5223a20309c493449eed468df3507505347b37aeb4085425bba98a54ef19ef1d7e272abfa791700d9e54bfa3146551cd11ade8dfddca927886c83530c39b5ef07bc769b857d4689edc31952c657a3b66d53b0289caa300f980d2eff91f8dd1a3ee871568c5b22a760bb493c3916e10dc2490c2e8f90c5dde93bcb0eec05e2a9d70caf3768006df3d96c155f27a79c844237c1f1872d14854a251dc11112432f9ce50f25c3f7ca051b1bbe54f41adff86b3917ae7dd0b72e2012a988b293a87d44fb5affffcbabf5a81bcba227b7eed0dc012bc3ab147eea09150f51fcfcc540023788b0e20a573cb53032a9db1c573cd34ce955b40f322920e221eec26eff3a153176d408e07f7296e0eb8f4e3302c1236589ad32e294cd1f50e9764b9cd9ec30e488e74564a56b99253b87bbad2fa01471c2ac6c8b9f66aa620def3451b5fe9e3a18e17c59822eb62ede73d803c5beab04b1d7b68f74a87cfe1c278cc9ef2f3a05bab3e459ebb396b74d24da5f02811ddfe2b6b7203b9b15e4ae0b14d5b5512220b343a52aeeb75fa002e507588ed2d24090cf73b0fe0043866195c5026aa65f81033f1df83c3fa6351cf1a8bd8fb18fe7f06e0c8dbf26b40eb08b29ebacc979855e11b81e383b9765e7b5422842988fe9a84a05ef10c7ad8235547b77768ef3c3d50cf61b5cb31ed9c222124668bfef863d36fd673edd9d0057be02c4daa8bf541ec01057dbca451d346252d560a798d00f72df9976be42b234599b80314d2e76eee40013e9e06fa604d87e2e174d08a88c3af8a59e9753d3a769a3581df057c492aac553db409f2407209cd54152da5af0a52ec8052a42936f2b1891a3789288863256a7aa99479b9a86cd433c72cf930961049493bf61433b23031ec3845768caf37159ba3460c7245fadda37166b43ceac758b67813e423699209c6ac5c3a3eb123296f8502d66cad7a821f020a95921f2a089f0dd5e2c1adef061488fa5d64305772775a0c8b1df0c19b0975d823657d66bf0247dcabe26976378fd7986990cd77beed55ca55f1dc42cc05658567e5a7a8de11d76fc2e7e532f190536cab5d66609dec00eb33e6008754333333d8d8b902d54f424a5e1a3918cfc74388ffee9facc011e24badcd8e3c42fc8bdec9d61b212ecc17ab83470829082d65ce8d902d96cfb9f48b94ec6f1a9fa07a3f2342829715d949c08e8c7e8268506e1fb0e6cdb2f93cb9721647506290a04c81d608f593a0d910359fbc10f78e426f462922533cf56bdd5169568a67b6cc7e85fba278568bbb122599f32b89d57426343d60e7c921657d5360344ab3d24b42b01457bcc19641c8addbcbfdd1e6ade41e84658ed0607177a3a9e3fb0e70a71921984efadd7bc0c074489ef04625aeb1fb83c55d31c074e0aef5967e4de2d280e060713781130e0b98d06abc0115ecb459bd3039586e3fdf986c97ba8783c3f49111f6410abf3cb064cf1aba216d5a6bf225f251c7421dc9c73e8167173a79633f198c091059ec463c6ca9ce23f1379baba5908dc2eb61f9abe0773c6899f21bb349ccca4e326b43e65ef8281b1b5e272e58d1d8822b2b29ff834c059296cbc053bb5e84e04d84e32c2319d3273e1371c6d191a229e00addf609a964e690e1a22f7d55a60c161ab82df52b82a951af32aea94d13d6cc421ddbe2cbe205ee3821110808daaa8b439399e581c740884fa9f8a28bd764a30662f2ba2bfc5d30838ac655abb4f7b1171c495952c76fc1c720695783b8c0a62d8697ce9266bd34ef691a97c9553299360d3f65436c955b753a3d6d80b4b6fc2c134f844b93db09b6b75921dbe434f0bed31e904c41b1e38207c9d590ce59ff4e9cba8f2abb48324b7d807846e73c07a6595b4c8766a8b2b36f016512d1877502320501c9d7ae5bc297ccee0aacc7f2e557cf9c26f9707e20075fae0702c45dd1efe73c0ea9064351803e4c4db95b9bf16046d43e64ffe12a17ad0fb5fa7385d948f3c28599adef84f143ba35696279b70173ad34c92dba18426dc6935adfd48328bd7d06181fb4281c06fc8a02e3b349cfc1400eff946080ff25dd9f0b6ead40797fbc58e246073e4f0767ea8f2045f32076df2d05b0f577e59f11733220101b0788b736eba8bd219f3e6ada686b6610688b180e78e03ea305f933ce32e5feb86516043fb5aa0e9cc7bb6c75cb785807730d88fce4470347974bb1e9b34c348437c95e6c464502326a47fab5599f37b3fa184514f908d6d16438a91fcf4034b73ede40ee7135a4924bb39040a9a860adc64f942bcfad20c0c50f7775e2633bc69b5977b78ff0d3cdcf714e420de5f4cf4ff3646f7d3e5858b1659a8c8d7c8d34dbeb3a6431f1a8c93b1394e50e5b6b560124629eb654a8ea9f21739185e8f89144ca22ba018ecacc7d3d919ee2120b0e7a679a694258dcdf50a8bbeaf2695557846279d74c20f9d85e04a66ecd619849191d52d6f42a74172a348b70bd93633ac9c1592d72ef84a6957f27197a03dcb1bbdf51e56f69f87d5d8084e9e60a3a42fb16cee57590b771b7600fe22eb77056c5b3e00258c37e654e62a618c19f67a69caa43ce5d6cb84c3a9c8a84e551509d5d90029c6a519b70d653fb1eecf91ca9b4a67a370ff72e433982698ae436d36efbc23170668ac922c12f62614afb179a3b44f31eadb32b93093eef7a9d112fda05ab2d8a3cea5472df7bb3dd7d296539dd14d1f67b083697836969fb2827689d2ff4fa67029d7ff9fac233b496db479077b6c2adefab87741bec4d193f83f6c13c957681d434503958f38023f3470e54b882d29f518a5a6a04ca8d932bce3699093594a7a2c305bf7128bb8a16ab2686e330689701c66096212db314cc7205230bf2f236758c62196d70857c8cb21953ede69b94a8805e6dd183a1923d88f048eee3bb544d4c60847aae501e5506fcc4db207c9848a801548707e01fb9ce99583cce83dfe8193763fe803ea48af87f9aa8db7b2cc4ac60b41b1270554ae0f9296252fd2a3f9dc0129c17f910977c4adea4f4894cb747b88470855ce7f1143175251aea6fe61c29e4242b1762ab690ea83ebfcb4fb11be9e662d0fb0f85de6c1afd99f71a8fd09472d0f4491998f20ecc0a9f25c6e6aeb57ba2af9a0895e151a879a7b36e6b8876822ee569b6bf8b61b3ee2623097fbafa721772697d04eb42d697dedeef2da66d65633250656e25b94946e15687683cee8f458f6eadfb1717728549ef69a1cecff0e81d986cb3ffd00f7966023ef6d9ac6a6670831e25cfb653e06f242880c101e8064c179db6ccad16cc53f25522df00e303649166fd832d937d5f40ff8f369be45dbac92e77934f18e16d3c3fe2673fd2ca189e22719d09ebc7b47a02effa949f040d3a1caa8327e74180aa197d58a082d84c8196d9f21e32caaebf10f5ca7ffd26c7f344bdb60f047e5c93ecbcb8b13556f41f3adf6d06dda1f39f0a094922594732b6fc3a3d2de0febb9e4b4e00cfde042cb63119304082ca3eae983fdef369e14efb88b6494e2d9d9144bca849514bf787d9b74558acd5e94c011e3dd39726f58eee0f906081b5a5198195bee36573f87e1c8720585e97306172244cbab22a1dd877bdee4d7ed20103954597064f48b2c988b3c1355b31b4e49dc3a863f8f76ff51298a87838a53c21362b42b1ba5ecd1053b878633d3482b4574733218668101fbbe8b93b8482128a9a77b144c563f6c928d9527092bdd929d92e9bcb0f3a26969df31cba9dee0dc68ab46f65493445a901ef105cb0c7495738b924c0b67327012406fdb0f27bee92a2dce7724a698b44f486d53fd6f4af426a9009e2e876d5e7b31c34e61f10168a4b353ac76354cb3c93ed9b8d80f69fa6126d453fca1a30ab2fa2fb30800776284b0a1e40aa242aaae055d766526995909b91b90a286b7a3d3555b7b3d1dc9b82da37958af8ea93da1552175c281049ed809109176244d1b317a80fd64ebc130e52dedf7568686439f310ae59be203c1354c2a87eada2528c89ee5c1d68c7225c719e6032e43e8ca538b4ce795f6faee8931e09d4a0beab648231cc956c1a28605251c16e3737248d6887b4f38d82f4c04f874404fac4d70d83a5eaa6d8015754851a113c258fd199c24a8f3b42bcae72903204084dd804acfb572263be8d544bb24258c6d7c128e42b75041710d1ad1d5893da0d413e85a9ec0d2d876de29a3f6ded1fbd5f8e21f0b2946513148397c54ffc54dd12e84731e0b2444ec0ad1693eb015cd6e25f0ef9b6b4e3eec5effa7ca170ee0c4da0b2f2a22b1794d39d91d5bbe04215b1232e72f3bd3e42b5e0a504894e16e7a103619e04fb278c0208a7cf5c7e39fbc66333e3429abe78a8c7da0a32d642eb1029e0ef240cbd9f5de5bb3353e4ea91d00dde4caa18adbaaece64cfd254b8c812c75406e47d33f6319ce5a31eb40f90ab8a1e13d912777a4a6f143855aeec510610ec130b18679c6fa04823d426a990ed8032409c44461f9ad1213c8fc4ec3e1ba1ad45074a70f0ebc3702a2a0955684a13d7a4186a38e889ccf351906f9f57322c1e266e800fbaeadde7317ac05259d85cd40fe76a191b05c11ada486bfdd363ae81ec3bfb6739701da266c2c9edaa7b8c43dddc2e2fb3887cead3c8e221c5ce5ed7de8e13b7e9953ac5c3072d01664ff9c049c6b0343b5b281fee0ffa4599873f562a9b3680914db7289f6c7ddc0d0ad0bb8e8cfbdd1ef76d16d649480f0fa9f82f6ee0026821389254633efc349efa0d229cf174eb21f0464f7a14bb0f6a5604a06c5a35371e8ee219946e44d4420dcea1247a7454dc27f88c2f90595c25c6955e1c3eae8d349b81f478fe3ebda988dd239d49866a661a9cce6d0ec5d268118bf44d425dff7d1c2832170dfd4f261832c297bd48b1b9542273a8b521d822f2cd837a27567f11b63936e4fc79e81f4b628ed253ed3cfa6566b70ed3d71f9211c4c6f8eed98f66126303b1716428cf32e8aa834d6692e76b4208e6788121eeee17b808e9dc03d848f5828a28024835284dfbfefe59fb2b5c31a7514b2360ca3102c973d61815e421779937ef051c63b4b53df4b1a58740c4a16b1bdccc15779eda70397e40a0e08628104568488ae85f56175af598dee1a047d0cc6b50a48704808c22ca1435d8195d7987c798474843eda7b15796978da88c5bb389d15baee03b06f747e785a8e4305decd6e814d0a4b56c8be3368f5b39f456fe01e3177839a4510aa78cb7a18c34668a5551461fd4cdf065e2501a64b2f6812b031c756b4f3d1bf09f636c908cbd8878f8f9db200f0bba0183a0dc6b897494ac3c53685cbc54af765744f8c29a86a907b39e38a683e3b41d57378eedee9a0685ffde84b6952ec296c956a5bde099b7cdb518388cac1fb2340665ce803335840291907dca1d2514f43441181d4ac4fa6d99b0ef7095a1a681bf1f89fab31d8c932d07639869050ada023fd8aab91a14a6b76502a68c617d54b3f30614d40d57ce6d2b35e94a3371410cdbf6d30a4854ac2e25e4f9a232374a895f8496d9c83236fb43bd6cb9612abc10a1c600aefefe16f2e930dc03d5b812b2c080042de3676feacfbef0d95a60ac3cfc8c6d66c065a25817c20d56c7a3ec5ee830ba7b11d7846da6ab302944d54808855b5629221053e1bb483087ebcd1bd5e4a937862e6cd2315901a8592f247bf2ce52e908271f35172727b19c9c5f6076ef95b29ac002f05bf0a58ac5ae69ff911cb0335da31197aa82fe953238a212cb00cb45d7d1507d0c90875b992f8c426316a9b1aa4aa8ec99cbe103cfc43c57ec7d6c24c710bf1a2382029ab4eab28f21bc4a1c3232d12ffcf28834033f2494aee84a7e96c938b23202f1fa341a4cecdaa7271b67f6265babde73b453a13bbc536b32e595aa408743805fa9badbddb9e2a1c75869a4ecb3c7adf9646b17d4ee5541efed96cb1485de5f1ec89df0b352c39d9c4bc3fff6077644be46c3f187fcac97baf752dd7cb9c816024cb13222d3a2215c0bbb842e5f89687e56c244226b3da6e2a8b399cc31ffe09483e674010c2cd27a80c66a195cea428c21960b7b25cf0041772d438241913ce1e68fa9f53a32f68ddda7904ee4abb47a39bf56c3e0df071b38e3eddbf3be57388cd435a1990431e10af2b90579a3c9a56181b34b30e43a8130522a11aac902fb0c9bf9a5bd4ab91cb7fa344247e9071c9477f12211d3cb025d736d01e751e76f41eff48195f6098fdc05b4db41ba8b44885c50cfb46296f366382d81cbb99f2846729d5321c2cdc6f15fcef5eb67a58508f79e307c5fb3dfc46ad355417119525b1da28a534d5b3adafa3c6906ed60f066c81cba5b68bdd154c307617a578d7022156fcf15cdc98dd7cfaed34e611015427032780ba53c2d6dc62fac8102d188f2e969ba18628610cce13c58057174d2edf14f990727601b1bbce53169e3786e9a173ebaac5adae6a6f6416f283d611268d57e13f31b6ef9c2e84ecaedc38ed6f31467751a5d8b40bd869cba400a573abb39b89a352db4944d2262368eb316f157d873221bc9548ca50012a071f4351050af945e7168848f7ede197446c45b7cb48ab8f8dc125a5a16c7e95ec0591cae95799299f29b592f837e8f589bed8f81bd478bcdb4b7c1de5f7af3f565f0f712b7f9fe30d0bb68eb66a5d53f90ef47e17f237ce63aadff928083a4018114e666ff34c0f7abddacfa1be47ba46e5efd1af85dd2dd7c7f0df2fd2a6ea6be06fb7ea937571f0d7c471a276bbe12ae13832a3a90aba746c3439e60b109b5742984a1ccd46e7e7d0df03dd26eb6af06be478a9bf8d700dfab7273fb37e0fba5dd6c3f0d7c1769b7a9b7f80cfabd5269b3d648f83be8f92cec7a937f90d19bb25b97f332f669aa8a4b5d48b24b0382cab74354da9149e0587c560bc764749fa9483739a8e0895a27e68f4b55c11e72ca75cab3ee06adda61f2b1bc519c85bbca920ed7b3050e2424dcc6e1c1e87e4650461f823c27927a679578ec88ad5c6c92002e44b13caac1afa17a21fe8c3b28b61beb9a25a154be18789eede046e4453c2bc6a63708922b62a190abc814af5cf8e3a09f46e7d340de977a59184faee199297367ec989c9c7bccfbf9ef849555bacfc30c3da5900069b68ac1f0ae0ed56644137b79700184d909136557a81c2b359a1901bfc5395fc9ddae4ddfd61206eaad07b71a0b1ae831ec68a62032dde7ec2c1aa074e1c2872824544026e03eb96e97e79dbc5f65e7c24f36cc5ea0fe47b27edb1faad67d769736c496abddfb4d6b7d9dfacaa9b8b481727db5b6c73ea59ebc4d48479c253f4d2917944d7fe8f0948aa2945171fd8db27adb2b2894a353634efd687d1e62ebf4ffc029377e415e11ae2681c3152886760f31ad3410b9bf521b2f54725282a7f12a612185c83b8a382f694b502624d96a138fc6ce802056ce4b2648f19dc11167b07bc05fafd565974bbfbf849e97158b1621bd8883aef529d78e1a88c2cd42418c61fe89d13a4a6f96d35ec8e2d7e72cd68f0e5de9193a03e0689e408ce4a422e447dc50473a9e042ba795017f0ae27db3cac46e46c1163907f264c0a517d0f2ccc65212a0db08bd7a831eae2960f0fd1510c6fbe7f4ba087fc292588837eb054671f5d3024786207bca46d4612a4b0a9a6d954a0f9b4b4064918854a0d422b0f1c26f6214a91e4b0c5a990b6a7720a7479bb218e76aa6a4a7dc4b3be13cb7b10c931d175c63699fb1077394e72972012b7ce298221ba6ecbc14e5848cb54550015172c82c5475e4567e9317b699156225540d89cb6b71090bd7125acb4b5b4b98b7062ed34835488eec4184deb1690030f1af3382d697049be70db3dbdf15240beae7a6124caea59a9f007b608d437e0d0fe403b5a516607bd50a234e48df1e9148ac95bff456d102c8b54a70bc406411ad5657901ea2509e3d123693ad87584728584c012f30479e85c8aaa52152893c1a6e0bf71867b3110d7545ba838f3128721f6af287e01be192f8cafcac25238262aa003a0edfa284af9365e02cb6284a8b7a592110b1230f5b7666b5daa507429488808da2c7ac9348a61829f402fff928fc3a19db52ecfcebd999d0818ae36fd388b2cd41a4b3d0ae0f2528f2baa20646be88cc933513606f7c45e1829464a0baf7317b20173628cc2cfb4e785892d43520fe9bf4dae5592a33994e0e58adfe2419cbbdb3f323b7fadcf7a1436bbe09c45e297e2129da0e6c71a45d126b8ac1f5908becd7a8ea9b9839c970f9beef5be67f1eb545247fe5575a6c561694036e68a9498f251812fe006ff0a797e9e706d84444ec35e830a45f462a88d947f8f9bdf929d73d54b22164bcbb3c04ce867d79482f58ee0fa5817d22d461567b921a751748e77b19e4ae17aaa75caa46f06fa46efbe1f190049c1083b2d58051bca13b9e7eeffa6802d344f773cbdf90e58ce1c5f634579b4bf79e7e077f0d34eb4db7792622fff81153f3a290f108418175d00a7703d53d3914ae100a41ebf04e0b5f8789c42f3cf4eaa481f38330cb616464368408a35ba3af25b06b8737fee21bec98c16d60b817b97f258053170a05082a47b8eb5f3ed5c2ab0245feb97c9994d6941b502134622bbc3756304b73209d1ec10285ef558696c58c39f0a3826f12e217af14a1c4d22149a3cb31365112bf3e256b47e45fbe21a652666f94be3d4a83eac3037ae7e0422a0bcdd0cd486fdfc8226372f12d577419dbea72795db88d02e0628f5a51486b465e6c49b9e8e3eaa0d6ea954356da30722f32c233806e97635793a98c9cea603e93df44a0df0023f8d0662b6c74f2ce40fa4ba39317ca9b4c86524f90ceb7d6091b38921af608d41d554eed3a76904cd12618ca21376315bf354883bb96c5b179565c6588db3bf82d5d18a903b44f98fbf046828f17f0d898ac4de7f9a70bd86bb874effb090c97dc14fc8af195d8052a6281e121828ea2427e53bdcf69e1cb7e2f4618d2065be3a30db06e081c3f45db60d4f48ffd4a2bca2461995e46c0bd51e1ad00352b56a1851b5543cd170dcb762480e0a86df34905ad3b4d36950045417fc491fe2851495d640e3c9590d7d94052a3d7e718d067082986c773b70b0dbd1192dadbe9035b1b523e4f1e6dc215b8bac1c824d75dbc8c25cc82d8c17f634fe45410227b81059e9d37977e0dce39cc5a768c9a847b82c4ef89569ddf3fc7efa15d1158164133645089a97c29ffa0c6499d0eab9d65d4bedfc0703d137c93d2552d1dc243464daf2c2b93304e21d99de369a59fa18e9c0b40973f4250d0c6b3ecc49c46a5751a0d8122a01cef787653712889513627d78628ef2cd6ea69aedefa9862e7b6a7eff71bb559506c412017ccb171fb3f1f909ebf39ba5b44b3f6b85b27d5010d957948d003bc079d0549bf6fcabb5e69914d66ec76b30adbb75537be241fc9d1865b8c97ffff753893d3f5008bfa8547978f8ddf5620b99e9d55a7ad955ca873fa4b39009e7e30fdcadf6a9129ff5e3f488347886cfd4a0187d6bea6e708a301fa339eda27098c8693152268b099e92c9351ca08858bbc4e702c913557e642101d13e64f323ba02ce1317834a8122f8fb4f3b60e89d0357ef33e91c30d951c11a289a6b09e91c63334d27b2f384d6e7965a920ce3576f2b5c2a5ce1abb126ed47b8285dcc7fd52407d64c42d42321c5546a625bc3b40f06235f8e69e125d65df6854764f5cb9b9e82ca31afde2923f8e4b4800beab0e8df9c86278e810333862aed733a67234eb07e90984b2512f219b5b04592f7072f25329c54e64053d6bb1d27cdc0ea8c3c05df4c9f067f3240e0d58c60240b1bad51e37bf14d0dd072a34cb8e6d2becd68e8813930b56d15474424014762d759520e656c5dc2ab174d49971117e99b6a9f60035eb71a5140d88d278fd0bd44244245417c44ba57552d9a148368c4ac1d0301cee8ca990ca56f707b3bdbb27ec717934bb38656b741fa4e16ee856c4d0aa4ae0594d3042bdd866cd9760cf77a3120e766e3b40fcfbe11d4631dbc9648f63886ece6a2cd1c5c74e16b185917097dfd1e462f5d304d1a820a0f044fb6dcd2ccb4963263d39897767d586a422a5f34a91eacc74069b24d259678cb796bb324b4e0e0100551f98bb45e9450c2afb8ddef09f769b6a089c318e87bc64368b05123403dc1e5ea9351ca1a3cf97bb35f2d31002497579ea19a83126db1390e604fa7816b34fe93269f509fbb47b033b0e0a93344bf2959f990601fd7eccb36c7876189f1d2c1165d912f5a051c9986308afe71ca6c89d19ffd2001f48322ed31bad044b04599bec43606aa56d4ba7b65eb15c2fe1367fc26ebc106324119f05d1419981eb04671e41010f453b4d1de02765a5b5497146916f5aa82eab06024c86a8e52a082b12c319c8c5fe4f506503c460dbefb2e9d9764c48e58673eac025bf072cf927907d17fee2b728f39144848fd6c6ef200b50c4171af84cf6b04992d12a01f490428e99840753bcd2e7e2ed94f94a75a6ff470c91fa7600258de989e3f9ed4dd3a2a7ced4afb5a6ab47a228f92dd634f1d56ca7e49ec3fe2e07e435bbc53ce494a17bc7f6e83784b4abd3de55b6bb36fdbcd3ae6635cf6fb1e682d048fae8e2b49a1d748a5046213641105e0d4b9802805c6b75040a9295e165ece21f92f32d2a7b39cddb7bac4702361513ca369471fbe130fcd84ff1e56aa9b537e425342bd1d5560d3bda9d095cb1878d8af5b8f24cea95d7cffd73efb4c7bc02ec41efd63357c496ef58904d5f42e7720c6599b97f78919c0036325192dcafdf58942a92403ba78c070c16730a4366d7c6324acd6ade2d97d422a93bfecb18e0e07aca9e8afa2bdc9ed54ec792db18c2ebc6c7687b1c14be995c6f3175afbf161f2ac2571b4efb6af1929a14a10ecbc89f4e5b8389b079efdc0d9a36a064060b7d00b0d35b0a88c0ca2a7114ba3c85cb3fdeeefe4dbf2957c66b0ecc7cc78df49ff6e955089db276d2d45ee28ed313fa5decd894e511dcd26239ac358a25fbcf421deb9b1e5f642f94c00171698d024f74eabd4d1d1022b8a8b886540661a94dd4e7243b1cd957432556f576ec5cab204a757408e266a87fa9a2633035c5c5fc13cfd398c49855a44cccc1bdf80ad16aaf5779e176e190288bc6f99a444416cfad66c99ee845732e0648413f4105ff2ce5682f30091f4558edd75569f9699abf344d11bd98d88b89ed2e3cc53df01c41295929eba4417b7c94f5ff4f31e0165de85947c9d7a3924d998750deab9153be64d716c71d3f7410c37afaa269d64a6e0439c3376a1eb8e36dfe00f0d97b24fe2b25c6c8eecf844f82891830c5964639588ca4c45fc046001ce0dc2b83223bcb8f8584f1aa59ae774091f2daed8c326fa202683aa5b283a25d434d8040078f5207fcc4469b5b6796d8630372079cd733868048f282eefce2d3ac2b7603a67b223d385e1f670fec94fd921f6a5afbb314283ec42a770341e0e550089b6031717af0892d64c9e6f1252d049e9a843514074af231f601634f36453a815011fe149305945bb07a3d0510b13c487861a6a0b872aabf49a406343d2b0260aa33ab1a06ab2a58f4fa794dad717c9441badcbf8e14b8e2dafac6afb0ea1d8d4eab1261274e0cbc8e22d73fe8a6e0e41607aa2b4bdbdb1b3284413d7bfcd34fc352decd864f1d5dd7739dc5baafb9cb5221cf4d9fbdec2ec98bc59bbbb9a0f98ce8692f5dfaed4e47612ca7150b2b719371ed23fc8d67ca7d7d09cff62de3dfa31324fdc9a02156d810212184b91ddca708bae0019c0bff084dec70114b0c06a1e5e289d17f09b71d90629f6ed0542b97bed87179f6fcfd23adbf8e6c6a0d736b0314bca2b8a1663a41f09c7a0e799f8538fd3269b078b83d1871775a7b6438fe57c29984271f6938a623cc1431c679d2790d7a017a831a9144faf0e52bdb3aeb04fe7da10a190ac9784b77a5dc45e1e61f730f4879ad1e92f6b756bacfa52bcc42edcd93f97ac1899bae73400cbddd0b0a9a3d2051e9ab3eae798e476fa4a581e959606eb5e1cac620719f3dfcb248035df04969ceda27a19eb7440527e8c68e0cae7c17e7dff16a62817128d84b6c70a350c829ad15b9a13a1335544893efcdcf3273c85a4c7786ed337880e0abc7b27b8d7883ede6a6d674c8011db42f02e4e2159c7fa657a3a06a68d3119b669204e6b38d9556cb589c331a66763b9b16afca724ccb5e62222c7e792655fd7c4bc48ca501819b6a89da3f23df86ce2f35b554d214853acf70e512b8332a2e4a732e98f0b0209c317ab90362a53245987832152afa7135f3f44495b625c2a00fea666fe5a52869fd56add4669cd4cba10c05afd83d50d0b37dc538293b6a3c8bda0030e2b7ad1cfedecac0b5989fcecb821a9e1f2c4fd39cf3d9149bd835b7328420b5623fd17ad38e1cfb5e3895a5ad09980a59b63132fcd70f3479e550722e80e5855d808e919f5192367e9f2ddf448df0a11608c55f96b39512b4a1cdc6fc6e05c2475c24e9691d34a041b229e86e0634f53215af9b73743cdd9d52d22208a047152a02458ff86c8b30dc5cfcf81651de6a76a55507cda0b24aeaa45a2517f6397fc4211d368cce21443dfed5b4e96cb8a28424c226b4e9e7923f032e8d6086fd87d56847e4c4d95ea0e263219362e7887da3e474c30497b418c80f6914ae0d14144ac443b4f450ba7b452157b42954cdea20da74ae95b639a5cff576febd45633a50e58aa23c14e567e2da17f651b9e3835b2da98c456bf77a36f63fa1dc74d4b04466c1f452987a46ccb0f74cf0e3c4e83f906a5e67d7cd2aac5093f58333fe206ab10a968a7b02e70fb7ba8b2d44b0f21464cff168eca8e987b0d609e4d69c8cc9a7a2800b08d16e5e34252d435cb9c7dfd1e4cd6da0bdb02653f6048ec49260c8a723099788e353e098fbb1a42c9bf944996469be0ed36e342f1321485e2627122f93b65ab2859789e795457e179a8a81737a549c7491ad2792a09dabed1b0e0d1598c3a429148bbc0528f50295ac56715be24e8f405e3cd5fc0058e6101a67bd3f77138b5d5c5821b950dfb4513cb27439f4fadf1357ce580346d1ef234616e99764e49cfc2b1d08d07200136e21e41cdb506bc556f55a3371e880c30d27d35c7c9a348c450bb9056040a605174275bfa3497f386318292ca679a48e7488c6984c2b558c63321a6f850e8d29e4c707649639541dfdd21f7efd64457e1c43e0b5b97b5cc76ec4cc2cd23f0800ffd7a9b145b620e506f8c058353cc9ff3679ab0ac9514fbe9a4e3ab6ab5e51b1d825998bc4c1216d031f9367c7a5f71f6278efee74e9becde825b8a0e0d740524183bd1d94854904ec59dff1c7c4d5c0acb1df309761655b4f6c63fcc5dcc15b065d95b6042eaf8e556b4d8583516e981e949fd9f7f0578bfd236841b714d2d45ce81cb634c2851a724ebf6ec05de9aa7e0aca1a5d4c8900fb707b4e09903723891eaef6170bca2e17a8b01983314fd36198303fa25350fffa69f6d8d16b382c5297a090618a299e53311b958a032338f84d81d4396bce8123a9e0dc31c66adea66b91adc52f1abf669c678c41e50bc9293351969cfe40119489ebcd64a17e4969a934a4b0bd1d6f9ab42460700c4f0503fe9b5d47f0accfbc3beb915a65ec0b413cd3201aa1042e17ca5c3b613432bf7a792554739ac71d2784832522a51929508b2b7a8d6e9456cdc69cfb950958232ee1b08f7ee4ecbcf0559c07f8289c42271f522b70c364ee9211aa406cd014ccfbb6d3445268857e5e23842fe99aa8c17428a7d786a96384d10824c08c65068aa75eef316ec8a88dc1c369751669e861da1e2e99fd96dbb5f809d6d44e832eee20c838b5e1053e3857025bafada67342057fa63d669c12444bc0dacac8ab8e7e62d2920ce19ac759c6766b15944c6693e065112f4da299f9e2cb489db7f2557d7cac7bb7db13387c6dcc44b301f097b74eba1250b53ac264668f412fb6307040c5a2311349343f75b1d37645f5799250cc4ba7f92167a71b55ad9792e22042d853d8fa44e3efc759295a94784e11270012aae0f341c4a87b60cef09adb0588eca8ee43fdc79575526a41d1a1cf8969f069a8ee475e1c7000148a17ea0ea34c001e4b0bc3284c866aeeab768a049ec0178ad0cabcf1fb3f404898d9bbac31febe5298520918d39f2304223826e38fcd9d148f41b6c0f9b5d489413d4b666062fe80d9e3e3c34fcea6212172be3eeacf8174a1f6bceadcd6c460b8a80c998be3d721e876afe27795f18bcdafbab084ca31431e62639be7269f85cb3e812680fec3dd038728231f90fb4cc10b5bc540d4450be1ce736019410fb5939006b9c0b236f3268b776ca1af0fa450850f1efd04753040518b9981772ecbfd1d38839c1d01f42a3af7fc527e80c5d94432ac89bad29853bfcd24761a394371b5799e14df37f7fd8de2814b5a0109d743892bb09f2cca7223d7dbdcb9bbf115fd62c63151be50839ba001df9de3adbdb31c1dfa4e14994bc96ea1b7097c5b063d205564cfe659d253eef01e49b7c38afd22070b707e98009b7218eecfbf7af13905b887c8a3b99b2b5442723997428f4dd410afc71b82703735ef7b58c52786b10cc361e0c2037c50d84540de7b0fc0830206ff900555d70239298919fa2ca4457ef22d9e55491ba99672cf7c09c3729d691df32d6729d12acd5aa375c79b5c360eff330bde15c22784f3a3085131a6587d0056b99a2ce0d1b1362a15990bda11d47a7dea1dc3b046e87319d3c48791ebb1fc165bf63d16ff884d5e18149cc7feb76173ba4dda38cee83d061e7616aa452973a13867313e4c5f563546a7af13c5a261d0823fba4106818b8616f6150e01ebfa2ba11744239076ba2ddd1ecee6d153dff8642502020e4c70931f06bcc18820df3e8784675063a9cf219a24f7a08fd7b24e63f19e3f3caa35efeb302925c2a98878aea2b7354454a62692abb37108f8ba74551fcdee4824193155e9e4c5caf102ab7db976a6ec4978c3a7e30400ba14ba583ad12f71287c1d02f356b2e4f81559e6cf3afccf7fecd916ec68ab8877c21acebd055e631ea730e5c1c323809166fc30ecf3a4810713a19846e13f76cc581380ec6912dcd7cd8b52074c02f65b5ae3839051e1bb5cfacadf22d53ea60c72414c4e007c7ac4410812877cd726a62a187d9e0b4e492e976a271e1d981b22c240771dfae4ed2d5e42996bc4c3e66f07c760041e266e3b082a0d4d377b08fbebb72c99206ba66861097ce733dcd729e81fa4c5c585d0319bc66abf65ae68bdd9863f081ce206517fedc24f5eeedb8fef15c2d4ba14ea2cc373fee1c8b4a9afb42ca8ec046f4e0f76bbf94dc39ed4858b942299038b434ddee02f3c59ad9ad360515f842b7c7570198f1267616fab6db83e0810add95851c3df512c6c6eb4ced6bfce1d1be445379398bb4de32b216c4f54b9020053da45ca053b9e10cdd16b24183dfba497c269010c3988616f83934e456833a5dfabbfd62d8ead83d2c6fe50a000f386f906b341702705d29edb22db90544557c4618aae2ced221a0152c306322956bc8eb182874744fefd24eb8a0645103da1cff559e75d8743f6e9b4b22e1e18862f3c529c87fd2be09e5ed3ed4feedee64bf02bdcb4bb5812bf8c461e718b028900b1c2488970f91a18a2901b0b8c09dec00d4621aa49e6e861f6e633c2875882193e6caf1938f5aa5d9170352d1ad43c8ee35c9d67e02c9b984834e08ab23e32461ae905a7e38cebc688697c0c373c3863a63c702f01d5de286adb0e63f83f1adf44ffdc3041afd34daee954145333c055e7c52a68748321cea8697cf7318396afd2981d0344398fae403146455790bc5e52084451f7c35a48d04106ad59192d299cdf86094fd931b60f6ab7ea0f9bdfa40b71b0b1b12b3217d86953570c3fc469a1353e936f56058d83416f899401b0acea740f4ac0ec14b1e55820a1a5682ba419454237c6ad05bbdd6adf46cb28cb694d75809417c8bdcec116a9aec649004dbd495fc8c23cd7086a742c1dec0ac19f1182749623c1b88cc2a7a5616751f98f81f27db320c1cd71e761804c3f789dba8d06b06e439a8bdeb2fab5231577e44315e4ac71d889af648c1b2df6a8c740b076d3b3579df4d9f321e3fcf760ef40114db2869f43eefad8742e93428584cd1f878c10c1ad81c9ae9585a770493c05c3cd02f28b83c23b9448d79eab5e0a994f0895b807120aa793b00ab9a5c9dbcf876f4580be1cc1426d8452c0ce67dd73456b00ee70041e6f7c53a3c5c95565063b3972e419dc01878421d38ec14311cd60cf7ca7aacec0578a5b86a699ed0cf1c2298cc75ba950ad760ce56e8bf4537a72c1eeafb0d7eea901bc597ea94c5ce2b2c5b9a18f3f3b1daaf6a7799c80995ffa4bc18570030241616c420d856717ccfb55d58e23d6246b4347ed67aae11a117559ddc992a7a1da424b0a456b31e0c23cf20ad2c04fe8454a3a84057bc9f286a9eab937fa3003041b39734a55de1de4fc9106828b414f2484663efb16ef648ff902c5713c9a1a428922251592c949b9c40eb4883394e24c3e63f6ccd090d6886ff857ecb73a67d09983e34ca10ac2e4a6b87b2a7191d37fdc1030fc5d54fa8e8068a06338a982868d4a42333f697c4142f79491b3fd8888f901510354fe5cb136771299ea9e5f6d9cba1c6c5c76cce622e6263e6134172d1b54c151cc7148744438fb2ba4c3297cb0f2b561b282c71b6c6fef520994928c98b9e38a564fa77f8ed60a953def5e33ec2a1bbee76160ac3b1bb350cbda96c20bd738d8672948c01e61cfca6609354f0ab41296cc2d9b254c9834464411de7fe532e1405c8e3180f045c68cfa0623c1a940589f62a50e4f92244649649b0f82c95b006fc364719128c6eb3920b8a78e340a3cf93f1902b67964ae91b58a8d3f2ef7708956c3ba4dc64870584a8001124fc46922c181cb672c2a001fc18b7862db0b96d5de6935fcdb4f2d336f1a3573e340f88f259ac1e648eba9d7bd41ebd1661350d0b34856c73f395bbcfb04c6231c89b754cd1b4b3c224cf419e2483a19504522974d5b5f290e54e96febb6c5589c7273762cd0f7ed0c1a343c5248a9da67b0c65f2bb95bf7c3f8d13bc0811fe100b60368060d9e8ad919e60d6ed4a4e9426176228f811ab8683117b22617ddec846549b8868b6ce0c1f7c944e03eeedbf8c22887556a2161d92a2c9aaefd65d6ae5aff70bc1e08622f413084cca6f237c404d49850ce23e2c7aa8891eef3978bba4f3261457940c80266b15ffb10d01af0aa28d8f123bd5d23ef469e27e10bce72ceadaf9cfe500b2c4696c5fff6592c8b6ca706c16c09c5e70935a0008f0f8484b254fb7032ae0420e5dd1f4a2b1dc64584fbe4b7f50dbdd9878a7bebf5391bf8591d11f35cd7123e387072ab26a73a5001b868dbd194a627ca9a8a0864123fd1f16341310f3109e802d8fe7ea463e442073856f5a50e02601010c9ec331980405e37bce4fb1e4d6c89f9870d37389011557f6d848bba88b5d80379d9209a6f563a621a66e59a5f0fd1b2c44b7d1991792dbc62a34a482d220cf5f4d5adca89ccd2d722190100299a96f7fd398b41f515aa962ae93be67d79b03d54edf1fae8877021b9f6fe265e3fd4d44e36b5f46b2fc444f0e2d5a387de1c4d7f31faddb0cc5257bc0e8fe2f02bd68a4e7f386ad0bd844857804f5d01e22e936728c004dd37146da75c7da206e1ca8f3f64a9e3fa5252438bf5b5fdd047630024a6b878f7be017962581b5c20a4093acd49c68a25bc258a83ce56df8a1aeff6387fc79f4f8c682f2808c36200761bafa147fca96e70c67fc16eb24e7c06db4bf1fb4b1fd775116048c84fa9b63296eacee0ffd61237e3f9116e0905150f88da1217d62295ef638b0d9cc462b70c2ea5076c22b324fb2ddda029885f8da5133c68dc204e471c30d5ad4e93d566f003ce196b377da2f85bd80c6ac43547dd7f647ff0d79699ca22f8334a4a39ef2019b72584741c68c53ca68911bcf76d36358fbca95ff62b2041f8852d73caf221f1fbc4b3a48edcef1010337127675d1d58d8c9b26fe46b849a7e897d03e5d64f39d72c2ce41fb0f70d3890df7b40b9e7a61144490c0442f57517d2f2d87e15598c8c0c8beaaf6e53fe60a7400354172dd9660ec0551f054c997d38296da2e3b20059ae824aadde175d45778910d525dd3efda0bda21c648b611701531e47c79d9b209a330d7216b507b9545ccf8563b62ac5435fe26bf60d962a669c98ecdf1fe6beec3d209af6b2ba0704c6113e6f44b19868dc85bf95ef4b4a0c7bf85a884bc5febf820554d817193e2ecbf98561cb82dfb3418f8a31d03c1058a034fe53baaee4c53a1e9e039fa3538140747108ec4420c249ed1d83bf9d6848e688788b682627cc3dccaeee41cb6fc842e950a65cec9752b4603a3e66ed5dad9c9808e32307c5455a35b085f9d42f4a7497b6517db6bb3cc922af8b0e122105cf20c80b2dbccdc3b883b2bb6f868291c64b4a43397085fc2273abc9ee7c0ee6fa7ba0625f4cba653739a8199f4997146b6a65fb49c8a3f46d585bf66d7d5be5ad4d88051b815195dbbb7096e66fb128242d69a9301326dbf4e91f2f699986008bc036efee33ed83d459d28083311610824564b342c413e5d4565ab5b58acdc013788e112b34b02274302aa857bd9ef4cbb9646265269b14e3df1eb1d9620f5d70556930176844390c0598fa8e3d2de8ad92fc4663c9d94caa87ea5f339f58b42875e99a27814e2a858ae119a0b2164576c83fc03c308f0eefe3985873a571972d9107bec267dd63ebc3a37d0b856f5d981a7b40a7c35a8db69f5a684149d6387eaa84989c2f0cb1313810f3743c07a68fd61f773153fb0454a40ef321e09274389f801689c37d04bc48eab06c4c24e04ffd433e30dd296e7353c53198d88c2fbd6d525b48333c5d7a9852bdc22529171695d4274aadb45b96842859eaa502b521423f543c93eaebf0320b07ade8d01814678e933738341e8ac52bab717ddc9dc41de9d925f7a8e8d13a9cbe35b1b3c221266e40b0e4f4e935a06a703ae9ef33ba1a4d0092e5a897c550feb64a219c24ebcf9257db40fef11e78dbfa359ece808a8b1c7af4159df83060b56a2b256733f90724c69873a0da75650bd8dba494df53ae3db6c4be35a298c165123b56d2793dd34c8f00adee5c0cf961a79f43cd6ca2cc3d08732584b409b2a40373380a51e11abe372838723f61cb3ea041cd1f1066b76245cff405a299bc217db4326080621dc9ca13ccefb3e16165810b4a33b5f409d0138c2bec671267e061a9478d94d55938367cda09be11682023a26811634f9aca8a3c1c8f001b607b1f7cc20f5e51be272c531ca07cb5fd53e20df76b0a7f7c9d6af98c62e35af47ba2e518b4a98f5b791a7d40b1f343295fe284838ed63333f65d18c6d396b103223a0c031d30e056ff63a15eaab9df7aa57a2976ec6fbf6d87c435015e2470f5c73c5fd223d01e8e2f0015e18302629284618244203514c6725792e5a3b5ff224373a64e5edf928e0a7222b76f655fd010982b6f7d233d16555aef0d1f1a40c554901ba9724278694a37ae8e0e4221d9c6546b962cfeaf2010154b5a93f8aec80e51d12e861634d28ee7901cfa58a48346138d832bde037adc65bfbac08dcb46372e17cee5ae696da83a66fc69c04a5c0944034c5fd2627237423d17ff5d6d50b0d5680d59f1445cfaeba7d2e7b277c29dbc79537e1ccae4d927a55e45aec39a9020442972d6fb0a184766319d348f026c4b1b893aeb38a762d6286218064765f455a7774cae350c7a5fa55f843f9f93e9e84355199ee7a156814b8dc9cea6a783d244d4a4132207198f945c347acb755dac70d3179f55681324bc58bd7e3a4612c4c1c1cd0d4116c2d169587447d0d14979a81bcdd8f7cadc158b0a5e81544e0d5810c4ff2ccd51811a8bd963ff5c89e5a063d44622da3d9e33d4e105a21dc3a10bd7653194881e1355836b5db700e42c616b62acff95cbdfbb77ca996066060e0e5edb96d10b21e31c71a08e3018b8108f81eb55771b6af7edc4653278037557e2b838a85615f8324c36fd2af3ecbc7b51ccaa479341e1563e8f9785c9f5e122a6c691a38e135daf962352f2a81e1f8696a4f72d89a41650ff8d36c29367ceedbc674324b53ece9bcb0d3da8420a5b8fc33ecfcfc3b03fb5df5148951b5076f7753b7c69f5e44a2081aab06063f6c22a5d71d0c71f245b10b0477b1751e097600c609d022ec5252d5a1e54ff870af74e6664104766407fd04c3905a88200db27f9d256292450412c8487c5608dd68886936fcd7d6f50808727929a1c5e43c05598147e9abbaf6a628f6e2af831e5c141b0ec0613c80697e3b2892bddbc709274eeb4b2d05ba3c3352abcb3fc60c4d2683f6cc772aa3a8d60c97304c37d64d3e619d5a3a490182a350358d449e2a141f935194ef29388ac96e41e2f1753d38542a763631e8116679ed44cc1fd95dabaa9739188dc04113dabd3bc8a7b2c90282ea8393031641cc86e05cac52758e48ab6f0b95d4a58f59d8b06f546c2b814fb531e64259574feced95dbff2fa8f17746100c359eea8e32f8ba7463bb8c40904fdd646375c81682178b8cb53145d845f0f394563aed38aca03330669104ed642679f09bd2515d15c2091808d47c82c741e70560eaab1c25179d9c2b690cb1ad6f35a50f80ab4202571443270d74f653549c9f8aa55267130548081cdf575d24c860fd046fdcd8e224170d2ad0bcc5f6f227301757361a98d1ce38855781a36a3075220288918bdd004dc25912996529875da24ee3d3bec190c4c2f8dec4cbdc7012bf9895f3350dcf9ce741b0e4c7885c8202d248ce9fc328c69e0d110aedbb5c8421a729d78278dad99c7c9c55cecec5ecff3ded1400e76bd31b0ee3c28c65088b915ada4698ae402b9eefd66a36b0a6edd3c7ae80349ec752d72c6e42949d0c6341de647f80da5191131436d63596274567b9f67abbf8703af4eaa74b7713e5b44f709aba6713d66e05d2c7e6607be4cea68b70dad796f449c6375b321c4b40aa74d90c66dcf5cedddf09b4b4a99c46f96463d06a88cd22aa9f39567e7a43490e11ebf4df2ea1ede19818772135ba116a68328a354fdda9511f2992406fc6a45b0bf524ee311a985621abfef69a22f5288b11e403b361c4d96c76434ed3c37ebda47e7965138b59aaaea3393948174a94f9dcbe929c5365b69825c265d2aa8a1fb9d27eba4731b976e50846e4dc63c504092202bf5197b1887deca8e245e545c25a580d3ed3e8a685cb8baed90a98cec1c9a0a5a5afa2c82613dae1de966c3413ea3ab365ba56e24bc9ede4f10da4c3e36c551a4e44afdc8330d7c49442512d5f92a3dde31b5b50b1a7906c6863d638284e15cfc65b6dd0f90e60b070b365f8f243bb4488fdc1cddc8fc0dd3b7be77721169af978daf025d08833a015537af9fa064c818fc71c31a4fbc3f3e812baf6b762810ef27312fae3f4603f67eb4b57875427881a777869f3936d5b02e9e183a1576fa94a6b20f62bb792607fbd583dc651c1b3d13a5ff2df03833a7df160119754536f670332e685f16923a80a331960b24d3051fb9a0a266a328ae3ac0e08d11dac003d208fb4184724a9baae257234bd220d2621ac521321b98e2248a47e18939c807f186684162aa7efc5eab698afaf91cf334c3cc0f051e7c5904e0dcb143b11eedcb4d9f7ab86203678c2dc72002c121d4221c64977a89e0a446a9648a7f90a06183e28a2c0c144a9cf6cc914d196050c7ed8cb9f5042ee139f0655ee0421a2ca26ed2d500217d28ca6a90bd944b4f7e0a31563e966e80f4aa53cbc7019f8bf7dcf29be998ad1ca09761eb9c79f58d9dd625171985518cf3db50d65b8996c4fe8d2eb8529f3371a79d9f8895948facebd9a3b5cdd88ad06e85f526aab047f87708ef1e7e2ce067593f0c82291a66f4b84aa9302fae58b34e2976cac774a44faf544ea3926219e3f7c6881ee7959803d20d29111f88b43e44ed83bdab1ead5d0c500fa43b6f2d3349489a0a3294e1585f68ee401b1fe90bb139f12ac81a1bfa62d3a2f2c33894bd982ce6960179ca70895fb9d2439360274b86869860208531fd934963e6296e52003051bde51755eff71ac318e18ab146a418e3af4304a3d145c46865591e4e0923095ca8ea0b4853d5ef42339483b942f2a488922af87193839e56ca4aa7505aace10f00a7d8e1d200d065b5dbc7d72b960e6345fc3492b6a1d3be61d21d9c122d2bd00deb33b92a969cdda1c358ebf525aed47bcb33c051f7ffd4313696cbec30068bcc8a08068b1e0086c3d89637af00419c58e62641cf2894a60f62e80b63f20ba12617ec0b6319978df99da3141863d8f0b52244743ae5d452d02e62f6310f2bb114152c38e8328ce5824c919a392f45c1554aeb8d202bc998536e0f782a2c42aa4e69cad043d8ac712ce451732842b9331d7d7ef4454c9f5a1316913d564f83ce1069645032439e61c16d0c0586d2182697a569d79faf11f10b3d57f20313b97e0493f95d6943b6712767d03434da304a60e1fe0b75f7130d0ebbfe337cc23b326bca150e010bab1ebe15b10d94f7d8669dea3973245881fe1380f7d06191ea5574284d4e8e925e32458763a6698d995542f0982dc4b16af72788370a7e854ae100202464b2579685349d75c727d4cedbfbc0347268c3afc39a398a74cc559fef661b347d2ac084748c32bebf40f0b3a13297fcce3201e9beed780c1e34523627d8e34ebb84111f7a23a6a41afe61eed4a2c111e66f55c21cc80dfdd50a6ed94b8fb7397464fc9317a63eab84fb9da15bd73261ba95a6dcd7853e6f9bb7cfad218c38d8d0dac2f5e72686d39c8eea280dea3e53da44c6fd500abef8f00149c24ab6fbc85afe6267851b6ce039f2b677bf08b6345c6a8962ef14c31515c6e66bcf9171acf37c45b76aa61450f19a8e157e01bd6c848213f65eddc062608545ef78273b4421c14815fea338f1af204a7546b5a796040f5a9988226ac67a24d44d1ace7a1dc6ab07b1fb2821fee0d8bc41ddfe6269470c1928dcaf8dfd5118aaca94ccafc248e574451e04da91ecaecc63a402e626463153795dea2f0202ad662619ca869f8d033a4fc3587412aea64522eaf4b0ac5f10d1996fa99b548a9bd77813bfa30879eb92ed0fe41da28c377ad4c91a05ed137431002aea782c2f7f5c2eb7a155881241a66ed0e1861522a3c94ca7f506d1d3d6b7794182f151e5769646d590b76b71979444c50c728d614015782039236fc837d69e70473f948105bd32d0b0a19b80607ae1e320fe7a72fb07da692a6a3880628433510d6f1e4454dd8715f3df5d66c4a7067caff43fec174302a1125a077f08a078ca8324a9019cde67ab044fae08a384d36c0242a601f2326fcb800bae754c29612556f4606d13ab2993b92f1757459b31afc6723c606082aa1f84d7d44536da99677c192a160804ec50249d63fb1beced58c7e742ab7a630ee0c939168ee08acd7578d36e701b130798ebe45b3c090e201b26b120992859715ac8b653a5e5e7e5da26e76c4be23cfc5b4cdc4ed113fc0d5abd6fda26099f0e17f08645d137c622b9b53a42fccc11b39ae571226c683c0bbf1a41911b6c25e38aceb2347adaad81682ac0f303035e7806dcb849f9e822cfcc50086ae726cad351ac0c2d7bc2e6842bdba3cacf3aba48d8c607a3af5629c73afdc921c23c832c29333255ff55e435afa5bf96b51c09b6f2ce5eb83c2dd50ee07fed1bbb7dae1ab5f02e841fcf4b896ac84956983071f7bb1201b39e7584616b37c8f7c68386f07dab79f46eb9680f1ea76cdedf08714b2c6c25d569aebe9b82da7331ad22b24a1bb74d8f645b6ff5b6202bb4165a806c82041463030ae22296551058c9481d92c94baafdde08f1f6197c7842891a605fa621f684b0c8bcfa7372e0a52357293091689cecc1fd51bffe60dda2fe3994dba4eac3e87e89ee33edeb9923bd25782361cac1b5ae11b24bc8de5bca94920c2b098c090a094d5976ca4a6d847fcb9a9a9b93b019f80059494214a40043d65f43e4a492990ca07022cb14429882acbf46a866688b22287456fd80da379d9c61c759cfe333b670d103f14ea027b8a017874e1b5cd6e5332effcfb410f3e733a58fd2a685eb0ec6146396a145003edd590a4f3901dd2007d79ffa7b3f495cff90f543e5faff8fd1f51f714083eb4f43bbfe384386aebf9133ee04da620ad7bf061c5a70fd7390e27a0ea270fd79a072fd7946ebb90e94e5e83a10d00fae3f10175138eb38572a21df097a5d74f982063c4708c21062f084095484e00407542001842e5a9cbe415067891798518511da184211bc9059000c2550390a1222083990b9af71dd882ba9eb6fc41f499354e6202529187a255a72d648e336b7b9fb38d94272fad29fc4925b2365fb5c8f6fd70b69618b7b1d990b35711dd91045c295996ccaaef0118779ff395790f8839f730549e9fd73ae20f9bef4a298330409f82de6d038cc7b507431a724e6fc27facfff949534a59fb2f6fca7acc51627e2d0a3f7a6a22b645871456d3693e5e030ef4b6c847f4bffb99e9724ca0e948842118eca1872f2863498d07386135692900499f7fee3bd0779ef960787794cbb9ef74c1b7218927abbdefb7bfec3c96a1127e50bbb08f93f1d7a64ce61fe60d506b48c39efc8e77c9ee56c2ba7d5ef794f1273a6c33cf11de69fa323d361a59c08a9989a3fbc13bdeff73cafa76cf2cd7dbe39537a439abd396e82f3a320ed00d85a6b58ad507aacf558ad481182154558d69d5beb57212e15efbb296d006f87a763ddd7af424c2113d6d55affa44558bfdfebda44a41377a83db6b86aab4a7c21e09f2cbeb55d9c160694d0a53df2f56e37097577bf0b3fb7db8588d035202dba349016426d6ae7b6f30a418d707e15c2d29d405a04714b9a176461f4248b343c2cb23002a501ebbd520d6a204447d3238e4d8b38596469d8044396851824560d5c703d4fe679cff23ccff33c2a0d9b563c2c0d9b178b86cd5bb2faa11851b5e89c38a156d438356a8e854f6dc293bea2889ee1846d30e4b0e9d1a00c2852286a0c6f895a0e6e493354b3c2752710952556f095def4d1e37cd8fd71cb560c7785fb3173f7e83070dad2bd54bd012ea371582b06bef6f2b84c5d08ea739ce971fae0a6c1714acbbeac444829a594d249bb21973b8b9952cff1059dce82cef3ca03cab937d7b5d7d9be9eed39721d47ebfb7cdc4ebb12c92bd9d00a7974ce91be376d5a2b07821cb57d3b77e7c45929a5940b57a2a477da799de9086b07244aa2d4eb97bc39312475d8813170a297c30bdaee95d236463ad239368cbfa7153ff8c099c26df206ae95f8c0d4465efb68d2d5307e7675aedb953357b471bba7dd7b3cb8dd871d166864d133ee84a58b65fc738cb0f42e83975b5deee710977bca0159a1e55ee173399657291940b8a5eb9633318f7e28e5529f255c6a031c540134d2d9ac7b3a1b805780aa68e2767b3320628acf0b3d300c729d0b1361e9ad585c031cdc7edab19a36be52c97a3743a985de9768e89ec661a4671cb809cd85b6c0a4124e9172496fa4490f0d6f3e618d70882491e69cf554c5f4f4d2beff7eca482d1abaff3a1c2c309ff43c05d5338514e092fe024c7a1346fa70092c285cd24f9e5d2ee9394b337fd8e930ce248e0e234111fef8dd3943129384b35a45c72ef0e93911078775cf893938ac7b1d59199278e6e4d36a1dc3e9b69e4f8fddd7893cebf0b6652cdd7f27cb529e14a186d338b77b9550e85f7a27cb2d80c2884856556184dd908675ef7961d8c930a6e04a3f278eef648b06eeeb57314747c689efb0ae1444487adbdda6fcae825b125347843a321d597f592f3f7d7f8fbb4a9cacdf3d0ee534ba1d11bde0b7a9b4c30c35f4d83de98bf87f4f6fbdd3148377250a6e97c43999859009b34099dcf9ff7ddb24fde0936c0b0652d982a12be9752e35c1d0e29c233f28962888812f35bd406f3f35c140fd7ec9d2f748469acc1fdd774f6492fcc38475dfb95e5e4ca6ee87e822f437958c088b74debde785218bf5df3d8d9353d6751dcf7aec721071e8b1eb3afef9aaa0748c1176b78e3142dea1fb25dee5de6f7f01aeff57aab1ffd8a3531c920b4686786ed3428f2d1328862d1a4c0f9a4496ccc8901e15d0a42789495a317ca0e4e1927efeb00906d22589ad1892b46228fdf79f98847e499c2189e395a14777c11ee91b7fce9fc80ce238b59fe23839c461ee857efd43bf435ab0e9d106c5b171eb919b166c4dceb6e6d3b86d4dd690b0458f45534584fdf305f6f9ad2a435f5e41ed36f12236a85cfa3e705a505dfa2e1ca93dc12e7ce8b95dc5db38ba2d7ae19d5cb4c1e44ed30b3c2945e29b33336485dcb953caea719668613fdb4f9cb624aea808e77fcc3dbbbb338ac4e86e22a9e10631a61873ce793a9d4ccfb326e7126ab3d98c89952d4e0517b7042fef5081c57bd8763251a029b8f4151388063cd7697cdae0d0f4382d921ebd825fe9733c8ef73aaefde79734a527617fc3fcbd6e6f3244c8349e75119a40aba4471d72e8b1f2f0d570c32722297d5d92b3850b2a7563f534a44139596ec243c5720f5b71d2e74d85f4869388640bad01c512c3a74f7a4258f28c5a2437f4c83c1d3ba1ac742cf54ee36c4d5649c7544fdfb4529d2cca3215cb3d4ce324d32e0a87b24c62584e2d3974cc4441ef88f0758828a38725ce48a20a279e20739d2b435c0411842600413185cc9f67fe4cf3ef1e098ebfd28fe37564396cebc8cbbf88392c7b791d1937611a941eb9c63b96797ad88ae54c3c2b4921546909e79734acea97a1522d2cc3fe90e546fc478751ff77986db9f802bd38b724059596b0f4524580538877f0f57fbde389198871e777cca90a66977bb6ddfd9da48d42069778b40c43ea811e16be957a1dab262295ef0907ae9222acb5d64a1269d775f48d1092c4d29b73726db06353f4f9149c272ec2923d4a297b9198eb711176efdd338908e9698be78ff97cbdfb5af66d75af733bb1e42f0ab9ff1f84dc7fecb34f2506ae27d6fa3aa5f74521fd1f84f454320a396e82420841a7b336c7712619b8bb03ee3aeecbf99ed889b45c32271512ce1f94521aa64179ea901262757975993e81fa64ca62c9ccf468a80be93bf50ecb0d3242ae6ea9957e92a662f930a1e82223e4075b3ea4adf8ca597a5fb6f8bed40f67f3948e48431e969b65fe981cd7ea163a837ce67a602523d4f2f2a1c2940ac717a6502a1c6aa88103871a38546aac56ab1b3f553f552f375662c9f7c67c9a0a516a7c6a74df8a0621aca763dff7c3acc06c302c1df39f2e7ea15debfacf41efb37e4db68a8e775bc5b5a5a847dacb74ecb8f5d85af8fb18a4263bdada0a189cc1d34596c354e25c28d44bf9cf64329d2eea82360017480fecaa80f888406c3dba5ea686c854e2fa5746b5d660a9c1d231d3f7d7f8985c6f927953cc9b4c2613653ddde28c1e6830c1c749d7fbb833b3db2012547a4093b1e6185c6ac4a819aa11eab15f05147a9517f89d038fd5904eff63680a317d9099c5119a3ffa6b723063353f1deb5fc1d88c1afc611267663d66a38de6a48c381de6ef0a4387d17c6636a477dc76dcbc30dc71f341e87d5923f4b2aa11aa4cec8d9511aca732b9b57c8172fb53218a033fdb17c8cfed1fd2406c3d120182a547715a6a4638af03c9721b0816730671812c0738250d2aa03193b15e475623d43117a363a8a18e9d64f806ac0567c158f0156c05db807db80aae01d380b130153c039682af70141c03aec24e7013fc026682976025380966011fc12be02298081e8285e0270c04ffc03e30139d14a0c0042518810842f03b2c21010f27c85101cfb8076ec24e98c64130148ec2353682a53012bcc32de029ec02e6611830157e827b180ab6f2fd38434eb20cbe1fe7e624dbbe1f87c8499ee2fb718a708c643b33a4daadd971fbaf7a6daeb376a72033be8105c1cb7cc0033a24293b90030736a0810c3c536131f03c858d799e81bdc0b3cd8acf52580b3ccbc0ee78be627178b662679ea3b0157886c2f2788e81a5c0738f5de1b98a9dc0f313b6c7b313f686672a767c6ec24ae0190616f6fc026bc3338f05c033133602cf2eb0aee7256c0dcf536c009e95b010786e81f5f19c84a5e179c7fe7866817dc0331216c8f31116c9b3141be47905d601cf46589ae7226c039e6b5600cf4458063c47b103781ec21e7986626b9e85b0333c07616d9e9fd8053cd3eccd33105601cf4e6cecf9072bc373134b3efb606378eec1e23c33b109789e5902bc8e85e15560857c0aac91cfb12c3c0a6c913f816de14d605f781e6c01be0496c893c01ae047605df825f6002f028b80dfc10ef91058d9bfbdef16754d36007ec40d5683d17a84f5f00dac92672d2c089eb3b01f78c6c27ae0f90aabc3b31536c9b30d6cf9ec633bf05c85cde1b9069603cf34b01b78c66235f02f1bf34c85cdc0f314567c9e81c5c0b3cdee7896c25ee0590676e6f98ab5c0b315cbe3390a8bc333147685e718d80a3cf7d81ecf552c059e9fb0e3b3137602cf542cecb9097bc3330c2c009e5f6025f0cc635dcf4c581b9e5d6003f0bc848dc0f314ebe359095bc3730bec8fe7242c049e772c906716581a9e91b0419e8fb00f789662699e5760913c1b6105f05c8475c073cd0ee09908db80e728b6e67908cb806728d6e659087be439087bf3fcc4cef04cb3b16720ec029e9d58f2f907ab80e72616e7d9072bc3730f9600cf4c6c0ccf332be4756c025e0596854f8185e1736c0b8f026be44f600bf026b0459e076b802f817de149600ff023b0447e891df222b02efc0ef67e082c02feadec4d167661507a6c584d84d176fcdc7ed516a107d9e920b3204d82d4fcf4efb839f93b86760839f9aaf9b9dde64129212c61a894416657c983e03ff01ef8fe9a222775f8fe1a2327937cbfcdccc9f2fb6d684e76e0fb6d6a4ee6f0fd363b4e72e0fb6d789cdcc0f7dbf438a981efb7b1399981efb7f1711203df6ff3e3e405bedf26c8490b7cbf8d9093387cbfcd909315f87e9b9b9314f87e1b222727f0fd36454edef0fd36464e4ae0fb6f664edaf0fd37342723f0fd3735276bf8fe9b1d2721f0fd373c4ed2f0fd373d4e3ee0fb6f6c4e22f9fe1b1f271df0fd373f4e36e0fb6f829c64c0f7df083979e4fb6f869c9ce1fb6f6e4e2ee0fb6f889c54c0f7df143929c3f7df183919c3f7c7664e26e0fb63342761f8fe58cd4923df1fdb71b2c8f7c7789c7ce1fb633d4e12f9fe98cd4917be3fe6e32402be3ff6e3a4ecfb63414edeef8f093939e4fb63434e1ee0fb6337270df0fd3122270bf0fdb122275bf8fe9891932c7c3f397352c8f793342709f0fd64cd499cef27779c24bf9fe47132f6fd648f9337df4fda9cb4f97ed2c7c99aef277f9c1cc0f793414e0ae0fb49212769be9f1c7232c8f7933727817c3f49e4e48fef278b9cf4f1fda491930170d22536f181db905db1df8f43731200df8f537312f6fd383b4e8edf8fc3e3648fefc7e9717285efc7b139c9e3fb717c9c9cf97e9c1f27777c3f4e9093e2f7e3083919f3fd3054607afcf5ffeeee5e24e6ea6899fee51f6661fd4bcc99398052048f8cf56e91e478979833d1604296c3a4c4d608d50cd5dc6a886a8a6a8c6c6636349b9acd8e0d8f4d8f8dcdc6c7e6c726c846c866c8e66643645364637433bba1ddd46e766e786e7a6e6c373e373f37413742374337b71ba29ba21ba3d82c468bd5623b319e584fcc16f389fdc4826242b1a1d82d46142b8a1991339246d6c81d9287ec216da40ff943069142e410792389c822d208878653c3d9c1e1c1e9c1b1e1f8e0fce004e1085571bd1571660eebd7fd1a5462616c20b0303e1fb0303f1eb030413a5818a1241666a8b430b70e5818a21c2c4c11072c8cd1066c6ba601dba265c0b66a18b0ad9d0bd8168f056cab0707dbb255c0b67c28605b3f13b0ada01b6c4b4802b63564836ddd22605b4435d85611046ccb8806eb9a3dc0ba6848acabe600ebda698075f130c0ba7a8e58976d06ebf2598075fd28c0ba8264b02ea118ac6b2801d67583c1ba888c58575111eb327ac1be6644ec8be6827dd510605f3b32fbe2b9f6d533c4be6c07b02f1f03d8d74f01ec2ba805fb1262c1be8684d8d78d00f685635fa47dc56ccc8d8db1b131353666003646003686c6c604b1313e0eeb0762637e1cd6ffc3c604f9b03101b031430eeb07808d81d998d1c618f5b0322b58191e56a63663657664781cd62f5a999e26aef741053359cc5b3b67cdf74e6864730a00fc9823cc1b931873ab118a094d01744c680a317d3fce6c0a79ddfe174258c27a7cf4cc1ffde365616a6f6166306e61767a5c19c17a62ce2863cc98dd7e528d508973541280767b8b246e109edbafa32789b00462330262e358cfb9dec5b22d24ac77bd8be572b1accbba7e9a8a904146ad4d90b245901721eb4b5411aaa844c4b238430e2b11c9c4bc58a5a052d0ab14540a22239c406c4ed60835ac6b80d86acae8187d1d406c3565a87c107a73d37a947967c9c4bc587c5f9af4d8a2518fab231b5aef7a92c90698cf21b690e49863a021cbf13a32976d21713deb59620e23f1022a6632d7ebc83e1518a17f59636ef3c72df45bc2d8ee508f5d2334c473e0cc563be1cac8c9d7b356462ba3fb126b841ad6eff23c569843ac11ba355b546284f39635957bca72cc4ab21b2287f5c3d85949765323a4c3860d0b3c4371939c1c1e4f7ba88dfad029d40ac542850789ed918e8162a5355aa352e88e4849e08e2fb9599b660841f053431c14921427e96ce648f7e0d797a163a138c3e3bb194b69b4498f1da3b51ea508bd77fd8b23d176381eaea735e5568e0acc975c8d9372eb8b2028260127732e76cfc3f64f6769d8104be11a43a1b3193187d68e6a1da3b5e8ebbed5dff85a54ea6a11c9feefb0466262fe49e273b32984395ac7feeb7350266a9ea66992359086f458a5ecccbc9024be486bae2fb9d950496bdc131a0c4c8ba3c190c452e655f3c6d8795f365cc1e8b0bcc3cdb8d91ca2dc8cda925e954dd9257c43b106af28b1f44e62190eddfa469c04b9c47537b1c722a4b0e04a661fee198b23a0a01b50175c80bab88553743fb7be4e3130dd2622ffdfeb14b312cb317c8fe52fb1f47c4aaa3b3de8d62f61a610b739d9e3eb37cdc91e62bd951eeccbb064c1c49bc39ae6b03927dfd7ab3fee9be0e8248cf5377ebaea6196272c357fb00e6a422397d8017440c4d1fc3a902d5fb771339dd1c97a6b6ed6b193887a3da75e747d8d7989e2d7a7fadf0e29afd7eb5f5b3eb6af18f105fed7bf446a32322f9f7e8833f311664c7cd497d58c5b2b1aae2f6b135a5fd627c4525f5629dcbac6ad3d6c932baf77f2b21ead8ff9d37f2570c7a41e18ae58ae9fa91c18ae58ae9fdde95f7f3a75fb70b55aad96f84af2fa15335790ba5aa9d7c875e18af51d48c4b5f0f5164b9a5bea30f33a9d4e9f7a8ef9934802beaf5ffd90d6d263f55bc75e627912c5a8ef657cee3f84e528dad57f319463786571e6c88fa2ae56ea755365fc1e96f6b2f1bf708ab074a2eb443d56e6cf69883a765a892d5c7ed4cf1d5b168ccb9fb2738ea315e3f27b261974eeebe74b9c3d9efef42b1bbac3c9927002795174cb4a44b465027941746fad5bf8c52f3e3dbd27b154c19d3df29f5a5f6f4d3ac6e2cf29fe9ce115c53fd99648d3e34974317aac618fb5fef452e14fa02f66b774a21b91185b3a564bd76254d4313ab2563deef894add2e3457b5bc23746f432b4d01c563fbc219621cfadaf124b560ff1e56274ac3e8b15d2d8f4daf1fef1ec52618f586efd2a3d66fe9e17862cd6ff38eeb04237dc740044d17d3d8faf9f1dbee8eb5f3fa710efbe9e762ce65f3f4d445e7647135e8fb55672247bbc1339398adf50f86e22f2ef3666c1f5f57d8a2984bfbe5331633bbe7af7251412f325ebbe4cd8eb5f7e87f539bede65e70cf2fa18fbfa31c77d7d0fd47d3d5b53cc0fe9962d5befe2f19a01fa82cb453dbfd0e5fffba1c215565861058e078f5f616686134bbe9c8a79bd5e33a0fe2592806f8c58f29f3e894b7ceea4746cc7d7ef769aecf1f5737cbd692112adcbfaadc7fa3d60463dd65fc1f2110fdbb3b0a6a80f23058232317f280b28117446672e179db9e8cc652af27786cebceb7dc78e97a53dbe5eff97cee88c36a1b41871f6f8124b1dc093fd1950cf2209f885138eaf5b8a3af6e232e4b02aba2c17cd58e6d281effad3d777a9c01def127730094b96dfbc0c1538f680d52fab9330b19e3161f56b6d63679c42edf610d3a0dd7ae320f5cbbe1309f347fd7a7bbde7bdfef545fe32055f1ef3c2e97730094bd6d03d89494ebf842fea93f812be5e468ff599857967adaedfd2637dd6974d7bd916622cce90585de2d8637dd159c762de891c563f15746fbcea9dc845f4b2326f6162c47aebb186fc325f56d397f5566f554b25ea98cc161d3a7eb68eb7b489a5b41e6bc97473d26d0eabefc588a577125f371991cebcff2a1afc3cc3ebf9e9ad63315fbf6ae918517dce14c3ebd91563e7cb5212f0758965ebbd0b73a39765cb9905872d592f76876e654b7a4365cb558ace6efdd02ee18b125d3dd66755b1bec9dad0d89cf442b7b98dc78e8ddd8fd83226422e2197395372b43bbb7025f587f49037adde6e444445456c744b8ff2d0b08b104a8f5eebd19d367ff80c2da547efd92d290f035c56c55aa44658b6142aa5779ce3711f30a5ed8145f8667f5fc82cbad7eab53a0dad1cd7d5ca51cea320cff5ef56ab9075f33c0fec71b9584fc422f9cef5063d2fbd304bf881619630ec22acd54da169559f1514863dd23f816196f0f35033a8d1072a0c5329774a398ee338a72e963a70b6cbf1cb261ae19b33dd9f8008671d99e6fef0968675397a5c6e3f89893ca8c9cae3593a46f23c4bb5551eaf3d93acbca5c98a8524569b83f165b5f99ca4da89d5566dee59aa8fadda2a96ead379dc2de14bcd20240175f1e38379aac048483185b6c1d0388c0dff828eaf28dbd74d45faa2beb37d3f063fd47b8fb2dd45d1af826d2ae217c77bb67e67e9a3acdfdbc481871e43c792d2429412f9e653886d3e3d7e29ce964a31e14971362c3e3e58343798e26ca9d424024f8ab361f131c2c257b690a1988af8a752aa2e21e9556c842f70cf508cf4d8cfe1f8fed9b1d22be138d231771cd60651494bf1ae75ac6ffff0b3858d9ad03aa6ea9d265bd65fa90bb1188ca5c9edaea264499b88d47ae4a163ad18441efa6b15f966f20f6d0b86f069f83ab7e2f011494e085968a8c71bebf3293d86e09dd94fb5d62f0111e690822b391e46cc8106986f7d4bccd191c18838b0deaddfcf27b7e378bf385862ea88d0f5af2f4710c733955ffd4bb53844cf4958726d480d386ec3175b8c643043bcbe5823b5ee1bb556d53a25d6ae37d1ad47551721f73766a64a5851c30d2ff83dd263f70194a0f170a29225f3824618cb1127f9e6b07ecf33b158e37844c9c9c43e6c8369d130a44d34e07c9b90c49011852c46a47158fb35896508cea01de845d27a183187d500428b91ac258e0ef344ae31ee27fb7bbd43fad623518f5ba4d02a2d61c9362cb68ef9df8afad9a89f6fad181a86fc34d1801323e6f420420623d23892e9451722cc64303fa43f989c9c2420115424837997cd39c1195064adffb8f2ad4996a2635c7ad6507a0a49b9dd4d6e37ed76d76e57ea47fa91f4d7408595db30eeee34d49d28c85a3f4d487c0a1a55d464300ff353d67a99eb590e6b16abfff6cd99726e5174f96556b4325a1dbdcc3a76fa7e22a8ffa6a9c8e7b6acb52042ff2c5fb79e19f724bae6145a60c6cce463d883b0ac11c299e1084d21df376d1d3d302f5bcc6dce1f253d41d12dbdd7a1d6aee95405c7bb5808638a9fcf8910c77b5f23d4b17266767158182c0debf7ee3479228cadb4860e5b184f58c1c44a162f2cb0d30411c6f062065164105290424f95279ca0e25e99c0445cb8a860d0461bac92bb3be5bace6bef6ee642382ee949c85f76b392139250edb88e15c56975afac369490724a29a5eeed0aef0c39cb61b151a7a499ad39ca962e51dc7ddcc6cd6ac7755dad5d9c823cbeea708236f728c24a29a5d49da31c75ef2e51d4b055aeebda70ea1ec4711b570475e9d2a54b972e349572335d386fd1698ca01c89cbd2b07602edc2755cd0aa4bf863e43c9ed32ea8ebb82ed48b36acb4d16588cf796a1b2a26427e8fa406a9a446c9054b4c51a20549eca85810f223b1521d81a38b4a8a6a052a235445a86a2a225451427ed510323231930a1b6cb02ac7061421b041041b58a488d8c246c416518e88a094d6181c516ef2ac8e88705279e611115eb0f1051b457c717474747474548f2a1bf42913520c7e7a7e7a7e7a7e7a7e7a7eba2fa21cede00b31ca110f1b57d8887214e5288a4a881b6ca882604141f544455301a172a2fa41d544e583aa071513d5ecc651aa8d141ba92aa48e5254487549ad9132f2f03bc0fa71b27a15b7d471c3d6b2ca12620c5514ac345835ee87fbe17eb81f77a12a525970361297852a8a5a2ba555ee8724c4fd90846e5711e459d598509e66d528902a8a1b552847b7bd3c14af71b5971390935c6bacb1866b8d35d60082c7e572d9db216d922c65caed97e2b1c22a84cc47aa2868dc8f0ea4ac9c4789cf45e99c734ed7f567f9cfd10647e6ba4853851e526ab88eeb39c9aa02989242c85c87c37b19595108f9cb192fc52515851414524f4815a5d208f9534e483521b525c504cf756facf102a69610f2a7d0c801a68842fe993a23a584903f65464a4b2a0921bfeb44a36045f7b0802e680cc10a2e4e907146b7042178b32767dc7002842e5d8b403082d739f1921085f52c1f185d7b2710129a90842358b731d008a283020e2d094ed079c2b682283c342e4968c171d1d9a0993ce17a58e809d7048391831853840802e29942f3aa4c21743d004201549c0084208ce8a20554a07955340c82e8a820832743840c448914c6173621298c2e9a8454c61d5769c8b7c3e0a27f0285f173859ef04f7e2f4b18b63b81a0c0724b66e2f6af7027101832b81f8afb3973c36d09fd7075c3fc9c35e9bb3dd223cfa1cb39c04411ea180af945a6f5388e210b42da64294497da1df8ce9f3f406cb96e4ddcb508df9c29698e4c21df9d8f44cbedf0c790e59cc427c4723a6bb3aa28e1888e084e09b44e093ecc31969083217e80842bd2c0a2e3844788054984e1c3942cb4a0cc8842e85430420d0237c4c8fc71814586775dd7755dc7f5a0765dd7755dc7b16ef761b785195edceea9b74493db3da883c708ebc7983b81c0a881d1e4962c21541a1fd779a51e6822a1be534a1596fe6baf1de9f34c4470bc672ac2dc59a690ee20f40fe1ce39e79c73ce39e7f773cef98ce5e5e7eae79c73cef99d65ce39e79c73ce39e79c73ce39e79c73ce39e79c73ce39e79c73ce39e79c2f30b143e576eb58ab179f1fdb0f579acd7a6633277b8a97e3e765fd4b92d5e358fd0bebc65d7d0e31c9ea572bb105c3eae2f8d5c5d14dee93d85afdbc2f7fe37188495e7ef5a517b1c8bc2bb1a7a852342a3eb429341a7e28223f3589c31b841cde20e417206aa5473ddfdb12f4607b417c73a60cfd8f4aff16a59e83657b25f8843945ef858a9a1a9894fad35ac5d207f5b284a42f678886b8d4e07d834e67dc6b98adad9819fb69ba59ae671311d3ab5ef59fab7e822554b8fa1cc461573f4d2977ff22f3bad87a7979a13464af7f237c1abeca964a6ef8433c65c31ead3d51af2456d1478f4df26c6ebf8d0b483a569fc3696148c7e81fe91815c1172e41f1cd190f043d93093ca150a610f452299416d41150b78b2a82b1dc322eff2cfd4c21f596b25c2e0595c0f09250698896758755df0df006a5259e524f8fdd42d27d3d9075e274988e8c65236226f3c4ca53771cd69dd591dd88222c513d3d4da5c7fe26494595c7a766e91e306e572ceacf6dcaa89e8e95282a561a45a5636589875a1876844a544a3da8292506c21471f9c119a3cfcc2f4d2ebf40b9fcfc0c53bb3c63f5f9050797f961885866b5d383d00bc90b513840cfeb3ad42907e8d946a289346eb2cebacc907d2cd0b32db4861191ecd3017a56004998b103037ad6660a24845a6067bd4ecc41c18ecc133d87b9400fecbe1b2a1dd9f799aa8eec2b998abcaa78cfd9d294521273ec677a7ae199272128eccc1f4d0a12774841cc2a1311ca967f28f194a834ac79a86460fee89d291dab9f812984d69d697bd8b12913111a863124af8bf9aa74215424eb6c4b0a990d0817be04353b467a8f54622679d7e04bc0bc934b9d7b492492572bc93e3751ce779a18089fbbbb3b0ebb3365672ccccc234d934a9ce4aaa5ee4bd4f449a8f7a627bdce650ebbc1d2b3e56c99532a7994248e34b456a794ea904258bf1c29edb82fbbf7feeb2ccde8d4e97befd5cab9bd54e477cfc9cea5dfaceffd84b5bebb12b19c62148137a2f04b4fa5b2a1ed5b933834ac3e8704b3c097a33b200e19c841871b3ac63d1217489eb5f04e8622d71c563fac222d736e578af170bb5f9280794193e93ff63ccf9b7967395bf2f53fa148ae734928f04fb66ca10b7e07fe103781b3562e597eeaf5c899763c11526ec7131f4f8e525b49082c2ebd22ab47fa5e9610e752fa38b48a34bc58dc4a27652c4d8256300b7c6b636103861deb9efb12bcf3fb8e4bd0f3feabd6f312306f15c1eb9598e3d327f953b5dda75abb2bd8e30cc71e69c6ee7dd0d07c596bf7e5d875d3903c92a519bba7345dfde9f538e7eda7fec72c927e9d75d6d416613973ebcce8c329a544763c11d2ef7634c171ccd148e9e63e22b5bbd99480e6feda2d7e96f4b1d82a322f8badee93b07fd7a4ce4e36fd34559d5bdb0a0ef27ddb92b1dcef3d5b2a29ff7e2c96e78965fdd2dfcad15a39b7d7033be67df7a5579f56e0d627427a8f89a5ed3a0663b859b78988fd8f92405ae266ac074962a9470fab82b0470f7b45e4663dd6079b2d8396f461b8ca611a372be7510ecf3a76000ecaadff9dc05938fbbee466a4efa9e1861a6ee0984cb287b8275c1132b464793654bf4eb9f565c4927534d4477dbb35078b904aa9fd118be40993f9498222f3252745e6f340d2734c787c6d2ab68ef57b4fc2226ee5f12597c4ad5c0f67e5dadc929b812519b660097c90878c8ccc7389a399b819d724a545f83d578b6e7d93ccd7f5643f65b9350cc52a3dbea863fdb55aad56abc1c0c00c918842123fe9c18e318992c05a549ab977cc8392fee57990be233d25914824195bca7c470a4da715c615fc4422914814043ff543fa337533cf1d96ce27c70cadc1e8b1fe685ba861f53fd3299cf2a8f4ab367751ec18a2110000008022005315002020100a87c422e14094e5a1a2990f14800e7fae545c4e160964410cc32088420c32c6104000010010630c119a213202075cc1ad66ac77a0f508d54bc4eb36671b67a84af82206a4e67da7c365b725c37a3e8337cf8d6e4dcb2cad1022dbe9c540ef1cd4596c58a078a5724029d48d11b262251bba72dc9fcd663575f96497f92488694b7035946ab0fdbebe8b0a8b53aaad45c12302fefa10c798d6af62cf9665d88b0af0b55e08f2a7384bec4c17faeb4df790305149ee179b899f777c767b9b6a7046c84bd5828e6c5b42225946b24ab22bfb2b5980e6bb640f1c60b2e26c953acc638bd228fec9010262a2d3548c74515fb2f039e2fd4ebe0dde8c1bd878f458605cc139df102030511011a1bd0c73b446031a31977829c54183c711ce962e64527729813c90ec1a34f7309a564723cab372951bf6e99e3c29c4a036e7e28eae2a9a4361d60e92eb80e6ecfd65058a87685df79da51c44c648a920981c02ab70c937e26502b1715b10fe4f1067e4df62ba1a4c8cffca17ffe2bf402057631df4d4984591a1f87133429686a2088418f1b377c002ef05028872a881f0f79dceac8c7904af5ddae20651407d8ee646643464a4b53ddc21a7679e4a4f1efef2b9ec1c0239201f0d5e72e2ff7a93e367736eaf929e7d022df157f9060728ab39c932022bc7cfb5105947d9693d58cdde99d33ab8986ff85a06ae78833bf1d2be6258cab5c7eb276d74b4049a43fa87c1df91d640fe4c14b9b6467a02a28ce4edbcdc52668bb46be406f1a0abe1601a1d0e4066718ca7de8533fabbdbceee4c2ee7b9054770e0b54f553dc4ff1465a202005501e724f4e78a28cccb045ee30a42644cc817f7d03d49d23aa9e0406db74575d9cd6c6d870f76354ff2d72925aa0facfb23ab57b7c54c25296818204b06090297792d662a51f915a3902dd37245b6c816b70ae2173102fe1d46acc471d82d078ee9525633c9a01c004b3a96a41023068d9de9138bf52e1ff57fef9b623369a924f477535fbe01683c6f783b07af2e7974e0ec47f5ded542340ecc4c3b7899098b6f08066cd249ed15e552120a2874a79c88e0d910a3e91ca38c9037f5c23fc381de91f756d99fbb8ee2aa9930dc55c1cb68a0d40b960c9d536e8b9660b8d4ea08b853904b465582bb93654dc3fa8046091942e83b2bbbb2d21b8528baff25d02010591683a4002687b068906c8c477a2a92b8261d11953e1fef16193ac9e79df0b5db30a045ac010ab1c0c03247d353eb981b631f9461cab6198bfce30beb4ed78421e2b21fa605b3ef146dea7139659a960668e13c4ab926fe0536d05d68b10cc85fe0437a1776c1d361612ddbee13c587a337920b5290895b38d14d76cd16e5f53219e0e795c5f8d0824c094303dbc26a4949bafa8e3e6e31d345da25728178bab209d68ae64c7072d7551ad1829028ac0c0e124233fab9a8c6c268085cd1cf597fbe6478d7caa321cbb9fda93b99846c229bfc4660d6e92fe2995052dce320c071dcdf4e1c7f96d735798545ae155cf7901a4d864401a7c30aa4de601842d08988be659898cc22bdcd87fd553fb0eb512ad0f5b282923859828d3df151b529b2597fbd7d9b0339bf30d84b8a063c7e1cb789dc11f070eee70e0b01e92ee5f35a8fe549d314f3d05f84b29589018994af9de7a36bce4c9a62f79d955827e95673562034a0d234f7daf11f6aaba13684e9c5f2095f4624fa7cb1b6b3c00891724a9a9a5a081bf79da22aad03feec85db1b7f327ec30904c3940b0504112b9ba87293727cd5b09aa5698db37204b878882d3a67471799b36612e894b976b414d7f8e35a48bdc41b4750d4e043021f0807f61614961e516110121a3efcc50c166f654a545014017216011ad3b44e7f4be4f774b8c3f92a6fd2c57a1785368c6099b9ab8d4f8d3d8f2a7cd244e4b14f72ac354853fc7e11539599794fe59e6afcaac06158a9f4c1014b4552f39c782a6b5bedf5be7af120e5a1695a4bd2a38f95c820b0fe1db04b044f7c5b4e4a1e218c294abcead1f431f43d629672943a10fdc0278cb130643d6ad3b448e1dd969a90db3dc7c7a2fd6261eb936bd4402262ecfb8328148a9ca6d1171e7f118d86fc942f9c4af2098ee04533bd4a1f1c6450c038809730dd90e6d5c7df7ee228f351daee88779a766ad9c0ec659a46574012a7f49a484ed23f222d105f02a9cbfb31df02f1fe1724941a1f475c1547aeb577607d3848086ff269e7f7c241429bfdc11f8dc126f484547d1abc6bfcdfd4d26225f908006ed7d68325ce259628259fe7ea7644e0c1f6c75f6fe7120b50326875cde637a669b744a3a13583ba104685c751c8818b1d0cf04915825b194dd34870848e7947fc13d147495c6fd3b4ecc1e0e92c7804e1153cdde6f9b4ee16fc51cd0133a75fe563f0e034bab359cbc8971040bc4c8315deb9a0331f99c41e23731badd3000806b613cce81b40d3e7a94799ab7be648045951a85286ff2328e101ae07361e2bbf841ff33df27257f0e5d8e7b56e8eb127083aae60415d2a518a9e7ca74c1f671646a25855e3f0fbc868fb9e6b70d6e8a655990e1be2909ed7df2c39d4382e1079549833e593d7d90930cee0d13882883e1523139ad774a019b33f850919828996ab6806cdb2544c6624ce64eb611ad04a1920fd3a524a935f252e4f1e2a253e23d4920f27c3ec70f5bb98b3798d09e66eeaeca8b616cdb3a7de44632033e6901ba21f64784391d529d98f2ff1ee1dd43812876f8d42bfc68d5c93e85b08a98ed4b1d7daf41d8f94dbb4367d2470a4e430ee03ba0c9c452dd028a2c8056af4b6275694097375d14f59a1c8a35fbe8c462cadf2631f85cfdc8d5c028ee9eb147e5af291dff476abfc76f604093c943379c5c9f4e1a8bfd3e61d47708a5902b5cdccbe6ee65bc4de2d4246051c7159a67fe548ada72cd305df5298a13f68742c88a1c0b3211f9ec0b77916b1411781743f4e848693dd48ac5812036c89ea1d702b8ea68164a853ecd1b984861e99c81e1f54391204e0780247d93de349db7ccb61d073f041b01b6e9c5f8cf482196eb8f82d8545ef83a742e4bbe1f7d9b589de7260b5d44e7e8e70bea0fb368f885b84158dd4f1d487fa0ea0231a1e4b61e87420f7e80437192366e7e8b88a8cd1889fcbf07047b868438738e2b91d1bb50ee3701cf97189cb45ca68f4e3734736ae02b3fa2434fa6a514dddbe31dcee1dc90aa835caf1a4e48465ce4905841f66b14cc6a95650647c3fd7eef48284183d2a68133f80015871d810306e863050ccd993ea3e7f016b1280a41fb03e4192e220d8cab405c5fe210ebb066daa1747c88bd9f3c36e3853647d339d9fa3c6c2e8b561cb9425b95d2aa06f24ef4521ad267f269ca48a0be1034417dc71ee36e9efc0f80e0bbf78723e2f7eafa9860bd9a0686682e55db2f0a18e70b2c982f74fdd432e5aa382d370d96c628121602b18c2d26ebad9026706d3d80299449aad4c675a683b7d073524dc486bd158dd3c8d748a6f18948b23c9e05999d45ab56e60417b772d384ac7e0c1190329924a1f774ca44526692db71463a40e005605f5280b4a371ce311ad6af039774f8a6fbc3033ad6bab3c5cd14bb5f46d43fdb342a1c4256a4cdeb8153abdab409493ca2504a28d28db24013bbfcdd2342d8de6ae447a6b5e2a95fa425857d994f488850b428251094977a92359011061a4887eb2d7bf5b08085c25901bdaf5aa78e5fa78ac9711e1f6bccb7d49756c52b817f1546d1f66e742f93e66050621987def24fc2f95f0b5b2e5fd672c8204f01e744d5350d30f47dfaf46519cd8ee9f1315c4eac6a70bb3aa657d0ae02bef26cd03d616e6a0a1fe14d0bfd69a7fb19f7fa632ba0f0610001904ac2c6545cf1422bf7c8c940ff16acb595abdfd2106154d6a275d9454ebba69896c76d9efde2d3360e0325414a1bbcfd480cb687f0aa1873a0c1a30187fadb3e7737face438f6360ef7ae94a313b4365b043e294cf7bde33c3ab49d4f1a0cdf9734d9cccfd8312fe65504a71dedea86dbdb6634acfab7a043f052e60bdcffa65cc379a4ca5d4b323bbd000d06c1d0b1d4af1885c634c063b28afda71f5b82e2597b1c15926ea5730c4d36abdd6e3c428bed2f794de867dadb094544fb51718c7f06f279f606fc887b5469a549c526155b6ca8b2a1ca1634a8d052a34aa58676b4ba71fc410b8546f6242b68e261831b74075af0c22e3ba5a2088ed5fd70244a14d98f9834ad9fc83273a0606bc5aa0fe652b4625177a637915dc45a8bfb41afe9e2daff6246f0800de02d4f64503a0349166536e394fe529ff46dc827b2d92df888ad452289ec41636b4209b2a7eb9a8c853dfed592270a631553eca283e9d2648a6486de4ce01da229eb1dd1de8f890fa857301d6ffd0bbd79ce4a452c932d815aacdaa45a50349a451028c14db9caf45c4e1f82ed0ead0184a1c0a92916d3a7659d51990a96ef8c55afb30fdaae206e2d6d0c06ee61f4623a61a0b7adb5b176ca615ee20e3917e2e2b16dcc2ed0b731a08fdbd5c35f8d7d3ca0ef968be72909bb0b583dccb5953a7b8cd10f7415ccbeef088473fcd03c71a8892e5ca38e3e86658a44c70c82108190165d135d0415ee7e5ff4f6ade192f101605adec871ea3d8eb841c019ede1a5fc19a828c03be719d0a471ca23ef68f459e823ee6d0c24e1bd406e3ece2085edd124483a91df51907bfd8d4b1083b8c194c1eb7839b6474773bba03ca0114da4ead764b9a0a59f155c1c9fc55d36647a3e1ddaaa0ceb70ea453d21f941b2208e5414e3091f65663f7c6507b74b0e34ca1aa8a2be72f39d6df56c05e5105d954e2d761d8cf508696b1ac46d5ec2d32f17e9713eff1ac4bcb703dfbd0e4e2e4b92e2ba8fbbc9f781d26ec431a5f20b1d8f9664b93a7f0be650c33b17729bfd5197bb666311e5898819fbe4c09ece0a1fe708a3fc467a280682c9a07b715a6758aa6a030c40e46bc9f8e4f3375a15c149c56ab7c91a6bad073cfd1896364a85ba97c8bfbe8988601293bb63abda2549392836ced5cbb1c5db0400dfa891f64cb08be00b496eabd7670711e584a4df08f53d35431e0f4241873b06983136df1846a75df688cae04e13df149f4ef1273a060598e840b625c1df0b313eb727a6ffdba9e965ae1bbe7de57be0d4f8d044251dd411f6f89f063a74fbd974f4cd36d777b6adf84b64a33d1bc0fa9c2d4cb87811f39b022bd8b468123b48e4e939e2a106afa224561dbfa1444212c98326752a8017912a295eb1143c11da20472cffedbd530d47e4682835c61ac8a283758e4c8426365fa9481b9ba08d5b39fc4837d1e5d224e084b835f5441ed2e11f74b571db5f857ebaf82e712ef96c1e80bbabaa9153f62cc9f7f45589f83cb407cfc2e38935bd2cbec907db8d1c68b3c34855fd11e4f377a72861799a47eae97105adac3283feb938262cb02b17cb3c1aea019cf9cb35ffd0c9b3a9e5860f399e0ad003c97051335b3e2e204c3ce4f484307d12213161277dfbaac48be9bd949799396f55ceb0675f4f00f65b08a659c61d93489973ea244a2b32dbb0c80c4329f70d4a6c962bab0536292793955647f373b1a795a90162b51b97fb77a65301583915023bfbe05a576527cb8541340fd0a75dc6159571c56c01b5eef1c00de38e52a6fe29c0056672818b13ed0b74d652eb8588099de03197b18ac287937e0b265c05ced653c96121489ddc7859ac5f9c828920a8f2204227064c47caea9eec1a7fc4f69fe4a0931693096af84cb698f55ca110dc82ab6cb1c1b55f0a96690a430a1c59d942cc814eac6ca791e29d6b5a6b1987a96915107ff013e3f0f44d381c0237a396f35eb90194289698518db14c60d3c30d8581ed0dc788f8dc1a4a649a3d1129c34f26a5916ac0f544938a944c7671a94d24c78163c4fdbe128ac8b11ac7a5bd7fb19855b308f8714dd41bf5c6b70f5111d6c46d54ebf577cc5d5a1fff825d5988b1a70bf3a65b6ec79ecf4e9bf0b3c95e51542f5d37861de7734d5c66c088a2c11e0b509fed5701535a63db238b8b02b68de3152bb8a5435622f41dbb379cd6ccc2d6bd7ccd3023b706a7e2a5cbd9d3c1181ca22a0aa0077b25b498edd511fb851cb90cd91d313441100f8e12a220160530805093e734cc102a98d93f9926c2622392a4be3b4d0a4139ebb0bce70ebf9ad02d3c69ab9f9d1ed8f3aa8a6e69e000f2012de0a6f578db36ea69769d617e125865583b46b07327a0e63b9f80d5e5d18b8c2159e51e42e8b105512ca46fafe883cfc20c3538bca65b96bc2b336ec2b20cb8e1d569918ae4e26280703549b4389248752f63c675be5b28fff4819f7a59a6e36e64520c27c49e95ea0804e67e4752d6e6a7e80233267cc50a225a0b27b70d50dd86c890fe8d80073f537a67f05eb44048685e83372e4bd82492e20a5340b1ccb084027784e1d789e20ed63256613ad26efc37ca2ac26073b6855c6d68c02547073839945cdfd1df36344e65ec11c59d484fd92c861851e85bf37feaf560cb18a968ff792fb56db107d5253b696164b6e64220f3e9b9fd66241ce20c6487dc2a08464cbd322f6190a9882dea0d2f9076ef65b80412f101b706dbcd83912018263b5e1f17ffde3fd7cc817a2024894ae20314607a084295e41a4273f4937161de20c1fafc730ade803129a4c2f47ef924a1adcaf63a524e6f6b650012905fb759587336e8b590951444e5d2e16209aa7c7c6469dbc685acbc67dcb06b962dd9865c8cda666413d6b7e94fd5c13ad10480428cd5cdc58d3ace348c27156368df9edb74f60427291681b00e6ea39201d8e190bcd4b7110ee79e4c4a6ff36f4fc32ac03b4419b0133649b7e12a441e8bb3f0a99160178851a7d56c14fafc87a950740e48277da50b4517a35ff024185edc0ae400d29622fb7ecf36b1db057849c87809cf51ea34b6c630ef4fe92a4d75c6ef419caf60d3ccbaa377214d9c29703e126e67f9eae6778f400b4c1f51795cdcb700dcbfa0a031b76e76904bc0cb9514d92df84711932bcfa0d5b55a87a216d3f37f69f3efb536e85152aab877680a3e5824da8b1f265991c7ee9de29e8dae4c2cd81c80c2b8f6336abb0ed9367acf057b381b47b3b561bec8fbe14bc748d47341d90e2a0282a4a6ccb3aed9b520fa053ba718e857206d6f4d2475b452f269b0b39dd6d9e6c2956b3cdb61ebdf06d73fd3fa859705348699d0c10750c0f8273ae474d74e7970da0b86a5b8a113fadb860760df4fdc7e2fca2d977a84047bae149896129b675bf15ddd890f37792001931bf6c7b5168d5e5b30b7f6806e8dd0a5fcbd84c38250c7037aa30259ffcc8ddc1131e09dd3e00501205e642fa9129651b562e516d8c6081d36bb5a999f8dbacaa80e81e7775819b36586241af03a2d83b405fe850300d6b538f40dafe5dd6c7f932401c685c3489ff0308b76e072a4b300c37ff0d2d79b1b5ced9689bc7ed8f2565d6662c8c139a99a4a0158ff27b248f13399d91200eb6a4440aa2edb6eb552fc60fbae46c11941a1ba9c65704eb90042689db538921fadc1c3c1e919a42490b69ba1ddb7e702dc98fb340580f02c1b808658442bf1ca1bd222b39eff8eacda9066ba1370567fadcd65549d821deacd3355f1aabdb060d11bd4a1de31b57571c6fcf6f84fb8d063db9761ba25860671e459b309068abc37b8c3a126c49b822caba75648661bdc695f6248acdb22da30113441a64a91264c89217c12891aaa0185be68f7d70f7609a03440b40cb19572b0e403eaa042333ee96e656049d4a07bb9f00f922cc56b53252c937cf65032b9fd5a2587954d111e51191c6ad97a364dba14f6fd8b4d54736bd098b1347040e0f401ee210cc4a6d55d2f9aad3f8bc560947ca966133f736a411e23ae4dc40157786aa14ee48aefaca331117842be5c6649856ac3d6a68d790c9e3c33100fe7fd354223b0a83886830b96e4a9236479780a4b6da377cb24329d23054c24bff374cdf605d2f7c14dfa2054fc3169b76b11ab602c34ea0507347377b773869ee29a17380afea2991a208a5f84c095219376a5aaa23909a501dacbc7d430c5762e5a324250a90b3360bf7ee59ce70a2ff04704910d26b070507569c58bc9d37b242b7c309eec65382f1bdfb8eb7f7705d0e259dc94bdfeb2d7dee2fad3a3b0b79fba9b7df9689d95ec09b1ca8827ebdcc622f79a8ac3854f1e8a62e11c65c56c1e6732232d4133444306fb22d425a8de0ecf2827dcf074894c50661cdfdd7e2d6758c614651c412f100f87a54921b7eb71efd7b07d779bfe2e08ed2d82ce0376e059e8196b32940eb43ace3ecd45dbd87072251ad4dd8e56dc6dbdc7f02affb3d5a990dd4c6e9811e6d530436b79079a60b02bf1eb20d42ba1ed234881e10041b78dac4c04a0a1f2a7d4c01d9e47e988d749eba5bcb19a706f1423bb81df8b264ab42167accb4088969b0b3c4d284f110165f236e2832c437e25956e2f60fb77dc56eb30943482e8a0dc73ef7c79b6b3558bf07658c093d116d263ff5216923c7ef476fffbc7e73aee709dd7beb642e812dfe102ce099350053569fc52182126f1b897e8cf7584521b2286b47efa9bacbbcecefc980bd642299e971ea5900c4250d53b257c3560695ca1cf1c4aa8e8312d26ee25584cf25c0b0307f68f17b05c0428625b1fc01b4a70ff94d124422777e9119623959a4192be4ea100e348814368c57ccfec67634b2ba8f6c92bc34ca96f1da78979b9b0c4b9f9f4d576bc8fec0f9c3bafa62e96338242bbe1c003e599a5a42aa9b5709c34dcd822304202de9e7d7b7296c9dedad63a21eda4c9d3f1b5c0f4b2e3ae358e00b2c7bc1231b3cb0e37fae2814919585a9563fb0e9c8b39b5d150ed727758919541582164da5318a542a350580182d59edd52c01adc0c0a97311cd013fbb08b710892a248da8180b8b84a5e5a4f19c9b24189918576e6a5339f740deaa6f1bf95ab42bc607e62800044b714482ddf46cf32777e5234c0a7e3420d72851f47fa1a46de3e059277442f17c0bb22b8d78a8c868efe0d0ac095f3aac80b1420996fdc625784a5eb6296ec20a1ae0a8105a2dd47756da1810aacc0390ff5632112b20ee5f6f418294e78a8f24b85f7ba12747492169fef70d0bc91c668ce891f384119f65f92f8fa6c34cb99ab7ba7e074c9aaf7546def7acd86125f5400fd6179333e04d64aef170d812ea404259e13ec07a537cf3c9ad7482f1b0db8ce6413b94ba53512012ec5d8af0349d50698b879ee048cc63b094c4b50c0724106182d1eda3b6e60a0986b93733349d78e514274eb4248c7686272150e0fe1c581f9d96c61da004af640d353689447a1c96cbff154eed9c909eb89a945c02124b44ece515f0ddc35557cc4f97c9c4c4acadabf6482b82ae03bbc3c3c2519bf8241e8a9517de9909baa76fadfb06cd5cbdc25593399f534f9e155642a00f4d540e2fc9c2c93e9c7f82a16a46d190bbb10748b0946ab7d7c74c041f5c659cf8f81e1ac8732cff52369ec5a9e8c41f31f2d0dcc1abd9628af652858d69286a47b20ce2eaf32dde5fb67e683b59f6b2f2221602106edeb58dc54f9cce6ce3f56fe45246cd7661998f7e58200d6f1b42bc6febaefcaa96e53b3408ae5d6de0fb1fcd0c8d1626273e13e473eb1b804ea76fe83855cf5d877914a8f190d59854924d52c6ad4439518c694cdd06deeb25b9f15f1186112678c9ad36772ee66e79eb9ccfdfd5f0a266afaa0513deb61de15b81294291aade0f4b760fa7272c82b8bd6478141cc3336b01262c4e8ed20dabfb53111a7e784856805e41781e9dc943b3b7b0b8512137256019b33bb617e0bc0cee7d07e38a365562e0c38ea0799bdaed239c97c3e0ead1fd122937d11fcab5aee3a2bced546289f4574baca33cbe4bbf518442d452e907574c2f7c5e2fd6b5b085c62fed75aa6f6d92fcd975756e626cb81a93c4cc20c3960095b8568a77d764585fc8b0f8b362fd56f6f651fb106e41e7178f1903eca72ee355a26677f288f8039e8f21f673312819141520b736e66997d25b3526dc6b2f9725827b7584847433a85d6edd96499a458b9d4063e45325461c7c31ec82d0413e7fa70603b3a532196a572cf8a07353671aa312fef057dc22e10dfa83f054a9ca3ba8783f8bb281527bf6545355098bff90ff7aa07cbc279148f5e82a74a43b28a95b656e69338a71cbac6fc65bbf6827d0b493eee0cc078d409d2fd66a3b62fd43af10123dc76b2992f12003618b92050f34decc973bf523df4866e04d80d22df543382e1221fe72644c980369abf514be0524105bfb874bd424387e365193f61417eff36cb3602071594691f96848b67ee22c4afba757d40203d00764443c5070e551e36890110536ea09b83fdcfabaa85ffeaf01296dd08ace14e46fdc27a04aaaf852e2f970d1f98d4c3fdb081584c87a1cf526266550459a94f5e1d0420048b34c543acd0b28753e38c54c949f06246ac8df6f252acbdef26746912470f2b792532a49738f1a50b2c4f8fa6d883e399035fe5b92a99416c89f31d0785cd51574d9540e25b846aac4db0716c8ddc12ebc04fba30cc8535eba7064d512b80b172765ae71390d7fdb5e80c6df0b05be7db90b3e53cc2f9768d5c17225b492fd85fd6372e11d262003128d4a1655eae6ff45488c81ddbb05d8dc053393d0e3c647def0a5dc202f74b5877c2e25db419db06881e800e353a6651c6d8c88a3003048f0727f4d9f2e6cb6c05defc4bbcce26b0dc04ef6c372703e671319816014908425e383612c5a843403228f39526badeac114a5422685d6992ddd7ed90ab94894a9406e8380bd3bfa634961d638c0cb1e94e35398393bf871b9c82f9486d62ddd5579ef00b8118096e05392a3045cdf621368164438254a6463c190c373f21658c4a2c9bacf0f785f20e38bd6cd0356c81009a1e41fe907fca7d6102a2ac5ec6da8507ba3636a0db676e10edc010629c5ba2c17d9382ca00ed201b997054bfb06f6581198c56697707a681094b0891a41e65a1f00f17ce84388947fe061b544d0948578e0991a176e31caa9bd7dee68551baeed9334fe8504e450254525379d41f48c11727ff87569d15356d551b26adf1bf65be67e20cd80f8dd5d10ca14af3ca7353f8bb866a7a4255bf988a0b4d6fd52f95ec9fd8637b2e3e439d4022e62dc0e65faa5c7c103e1056f2218c62b63a918c2fb3b561fe9ff0bd430f8571e483f9d004253e43258c82df419293f3f977e040e35c49370cb96ea2c556f9455cb4de7f66fa419d1c0ce3fafe239ca3c6d56586e079613aa836ef0d0eb9854de1b6f5cff6a74b11b5682008b861ea2ccacb1f88d3955580169e939d2012b3f737bd18dc38483ecd3ec0b053f329b4d53479698a4fa026395dc46dff994b98f1e467f9c0a65c8797d26510c0633ccddf6ca6729bf8ae2497fbe0a34a39a0e04c4dccc571c9033757538e159158530e5039225c368bbe8262472d27b06536879756e54a0def6e466c94c181a3dd4bd6597d3d23223ad89fb1c9068d79db2a4014abb8088932bb0cb6b4503bc315a3917c4ece0f838440366096bd1beef6beed4b5012c17d22b003e83ab22afc5f45cf7737ebc97549df045cf44764d1f9f2aa023b37bb655ba9121edda2c163f192a48e2f979f5793b90f716d4d164b58453a5b4d96b7033b1e209ac3aa6dd47f309cde22273eb3ac1c54f582b63a3a89d96b9a58c833917ea1355a5799750eca8bd59bef8c7fe088349b42e957becf45b9f7f81dcb56cd0ad4cc65a4855a04530aab3e100d279c223a058a15007963a91f386ccd7f62092820407d636151b782a6d23a9f7109d1d83d4b0e118802e464cf26967ff6d131a730582d544d90da33ddc57ecd6089d2f0ef144f5c81f0638440813d93f2ef402940d27a051582b09201a3ec4d010fe40f1244d26c55c174a64d25a7991fc604ffd0a86b4141895576fd96c71820b83b04a9ceea34555a4671f3a0b2f2aec6ef43461925f6236192b4b7921d3171f79a6a456d20aebe367bf52b1ea410a9caefcf529c8809bb9451a9f15190c9d39196c9f73eb0e2809d9a452458db662d0cad2bd3c09d97d56e9a06eee6ba00d787e8fe2668bc168f0381175785a74540a88595c30f61415eda7cba32f8d98f70344cc8f9be75620dbd84f0565c8c0fc6eb5d8c0e7057bce9a0d1a966b9efd3ee15c2d97f340fde2d63d3394f1091449c7c4a7af82f9c570f16864f84acd50b41681dbddf03c3c87441c681a231ff46cfdc9c2dd95782cf57a39ebb1089f9db96cc750fa52e7e85dd9b6225d9bee41880734a6199fb5bfd542228de73c84d9072eef11cb27812de22a17f31424e9380412b57915c39d56f5b787a63b974d819f07310386650e69414151c1b8d3aebfc684df85937e686ab755a2637eef4bce78c12704c592332469adcb8b4f3d5f9e0749381faa901ea9b9a3f0e13b62fa1118b5d639d4085d7d5d884bae00114a6f894eeeffe0dbcdc61b9bf1e761f09a6a21ce19ed95b07bf4884c926c96bfaf7c1779b3236458e388dad75f304112d48add9843d673380fd7b36152c0e13db7b58c168aea795806d9bd4f50ba0d1ce819d4c031584ed08846f4bca04d9ac890f6d316919a798f30872e6f1eeda00a4350a350e1cddb36da94cfcb292da9f109ac0e9107c02777ee1fe5dd7ef25d5474ab70c80580bbeb3642f657637e2ec245314f48b284c48384e4c24899fc0966342d456f41123a0589710f1b66df86711ca3f82589f58125afcd97352a9bd61b7735862c830183f2a3bcca9390f023131e58e84d4174d1f7ce3f77fe2b85ae4220402c8a63fb9a7edfb3a3e5955f5e502b4c83a0b8f2e83d704488d57cf19872e61c4812305f94791d37c71a7370bc1e5d7628da716693df274819a63bc11025c269725edc25d295e83a04c2765152600fcea342f0fbf7d436554e0b90be32c84fc3ae26ab635a71475a68e8266b97e0d6c3d6630f1c6130dbb400ef388da9c47b8605cc90d4d5c1f7de05a809b4eaba22eb58959e3eaa8e9bcc2e1e66f810e3b071f1d6dc10d642be58bd4ea26cceaec7f390f78b3a350e801a4e19e6db9d6b61296104f680caa40d1bbf2e0ee7414d6213cd5ecb6ce0a6c5ff3454bf9b8d8927b02dfd4607c10337501342cd0d1c5158ceafee8fe75361b8c19cc0c980c1652e1fc365a041bcd49052170ab07b0b38ac96ed31b0030dd100c03a15c5db72725409df1dd660532b5dc00db06706ad85c39e461a90752d6175c0db393c3381ae08b9c690cc38162cb4b9c450160d1f4518c3e718c754c91066da4847e3ad7615ebfd1ba117028be2d076c09c770c8fd65d78e5aeb14e1b6c63dba1e8f9ab85c99330f5db34cf91a328427d568c474bbca246cff3eaadc37076043a092eaff57a79e309f6108ba6f93bb9dbe37aa39158758b9bfb34890877c27100c2ca3415fd907d37d0e61a1252a54077427a29fbcf9667c0d3a6da710818886f24616049ea760d0bbb560f11091b1bba7e1471bacf21dbc4df0dd82cdb0c8eb8b836f5338798b9818e9d7611142c2f52b6cf6a09eb4a96cdf9857fd0a5065348852d6c9ec1fbf2910a815927d02d8b4e4edb939ba663b67646153b1bbf545168232365b7bbf11ed03e15bfa68150b8d731420345d0d6a77c2ed31ec2411c89baece8e362fbc9d1ae258d3124c8d0228360e0ee3516879823f17055bc490dd04274ec11a588503e036486334f2f18fb7f3c6f015860c733c3686a002e0f68fb758d9f73d0d843fde7cf8e4445acf74eb396bafa79c77d9b79e90863cb4a1f015f3eb10ed9eb7167823f9144d913e5a2aee69e0836742b0c65f5022f6a25776c69cd298f471df409966305c72efa550208d00e300982cb71255f424ce675d4c94b36330c1ee36b26f40376fa1f00870cad68827f67d63eac400687a7626ba379fd9322abf3f02ac904388e24ce3aa204c6fa3c36260595e25c873bdeaab262455e390a338cf7b92aa8d961463469dd9cf319cc9c0439e01b89189403955efc0f64210674c933183b1416c5215a3e2edea8c2ac34600145bc16296ec286933178c3058384a171f48fc949b51661cc1501548da30ab09a910e300dc231ff268c08fbef9d5e87db50658b371e09ff51c3a38a09b0938afd2a9b70b6d8ca535155d987310c0ea4dfe0e6a9858d33465cad816ca473bede6273ca879b8de18b297d695e8522b9a21b198ec2c73bc45109433954dd1f61f9930b9d54f92ee266ef42b0135b3f8fb225329713087012588467d0cc022eea9855f5aa40661dc56a6149eb7b5b7bc842aa8fab29f45a915e3fce965e89369226413b35ba1b8478d392ba9735e87daff6da2bc84a84d4bf0514af529f287796c75e671e91a4ec3b66b5c8b5a2609e7a0efc0de30972f82440f691d6b6e9a54f3aba63d67dfaff88aa99d1b6bb77b9d331441681083a68e79a8f497e371e0e824e41f51549fae97a3a9e9ce2ab846bb0dcd18cf1107305031cb8fc17cc9e02fd967a20a55d0505a640382697ef4eb2364476f609ae1653142776da86c647639167694d20edcf6ea6cc6e362f8e9c7f5a4fc298c00c6674b5631e2958118bdb0613a06a48483931b425e5934afe89cdd6b27b389b8d8f6bbf2c543fd3e060c293db90977aa5bcc91329db3c6a9053bd95aa93e438917427fd5cf77aa6cbb77dc6bf560721ae13572df2df3dbe80b25b3ea07d30671957218a9faa6cfb299ba27b78fdcabf94821f38d725df9eb8a0d2e8b18a1ba96fd79b3a0e09a948ebd95ed1c2058d3d9759a9a36b540996c0922888c17d49e119a4e3d4b5f92825365afa66095d30c04f0c71f302bce9e0166e8e95227aab13cc60a975cb5a8b4ab46372fd39f6c04794d628e7325ad73be9d2315515b718e0c17d326a80ac22304555a65917f8720e7f7660dc63f0fb8072d236008d807cb4d762a1ab8b122774ab4cc9298bbcde36357a71b9cd1c2760ede4a6014e0fa6a567a710e22b35d624477e216ba1532a3260871ff8bf7e583c5cd25ebffda6dc1e6d8bfe976b07e0d38c15cbc91cf981c8ac4a75710626cf8f5e51eca1d87e967c36b8ebb184f45c1b188525f57a192a6f659310c050334cf8dd17237b5759ccb0d202609ab6a86c27a34323a1bff2aac5fdc8f635948fd2783f7388e5a6ad46f1213c3e7b071e8a38b874cdba92e354483c0743b89239dd1056ec04ddd34e27067c7b973af4278b74462a2daa23a2e7aef16fdfe66a9c594151e70ef949578f7e87868330b8be2b7d625583973250b3fab00b0598e399f1621e5b3bdc441f1464239e465da2f503be29cfe919ed1a873d480df0b8bf0fb4337d75ed11eaa5a6f29a18b0bee6154970956641108aa7854fce02961cd06d7cc8b4e15b22af65c173c57446f805325100479c8c36798dea59b011d0b6a6a30c17bb9f959ef2b62335cc4e56db27d55b8f71a967e4ea2c87bac7823676b16625e80994b96a970d027901b1c95d5da8c5afc358ddff1582da3bd968efc3ed6374cd0429c8d4d8867aac081ac3283251ca7365de509c6be9601a374a9ab1dec53dd7f3faec321e47d062257d6cc7c04dbf03b02f5914ab965b5be933f976579890aa613d6d963439f9fa04eaedda45d506a7ce42698f9648cec08b725ba6e91ba0d3ac235e37e8548798fe2453d35d90718a38132c55164aa7772388dab6a491908254a0c3b9e9ea01292a7237abb6ab8208c8a7b8414a4036d23895c9daa659f40cdf6b23eaf2b16e022620d76e114a537a9ecc6d2f63b1761b6d7c8eca74a7d2433e1bcbaeb41b46af3526c7b6df6d23cc4641ef38b0d14a3b76d9952ebdef805822aaf4488214fef8e248b2114e93a9aae3a089cb3af01fb406e494e0905292c3a6874b7558eb3a1c80e922b8d236220218738b76b0daf89284689c9d05a4172ef54784f33851a90accca1aee0faeecddf7d823dc837411889769447b38d916091350e7d745a1b912237cb0fb2f353356245f1ee8ce4572ce3e7957b2d94c406d60b08f4ddec60b9cf6b3f4127f2af2593eed47f1e6dadcc725a02f5f08033c4dba90f45ad15b0a9f798a0cc02c3a60c594dafacd567147a2cca248653c64ab166e866c5a12561383c5cc4fc73dd7a538beb6fe4604e3d78875bf8bddd2929aea2beb8fdf22d25a72071827fa93dd2e614aac2bb1a8391c44c3a1bb9f0b724322b7763cd4950a47efb044a2a155e2218536a21bb97fb89022b40365b1f130f3d0f09521f9275b0121bc99fa5e9378b3892bd0169abe949b2cf65cee2d295511f9476030e215bd7504b8e17580d625b31122274c4cac4a03af1e0a31f784bec48816c77c398b45135c765d0999b4934096eab854a02f8a21fad2c0940691fa5e6193aa23934ae7f3e4a1baa33998dceaced9eb487d1cbd3bf4cce2f30f5c51ff832fabd00492f1e949791f00285bb78805d66ac0bd4d2c587e6328b5cc01e172f87cb8cdf0247b7784d5b86600b00d6e275b54c4d5a0086163f9d650a66817859fc982c631c0b48c4e287b0cc012ca0ff8ae77865605d01892bbeb095816b85a110992c84d5a94521c52a702d343a2b3362052c57f102abcc5015b853c5eb4c6538a900282a5e80cad49f02c04ef16353a692150860c50f5719bb5540a62a7ea3ca9ca6020aa9783e2a038c0515b0e23abaa21eaf83af7021ed58dd2a50db48092180129a644612fd809aeab4a02312dd3c2b370e1b4c5c27b2e531dc344066d1738fa22b02a034d309b5f914d95d1febc51e28071f060593149036855e52fc415a131a288564430a124e818b4744b9a1793e4553b4beddef440b12d63732d0a3f332e8593dcab0691be5610f8f24d9c1f6c1887f75aac7781b57ce0c4bab812698edcb3298e8c1d1b9a369817de54791c07ea9554834621cf889d14275e123e7c1d40720d5e9f0f3d47c5d22928e204c53f96723bd56056ff8c9aafdc9bd6fa1b3d9310e479127e6b81dd58f6b3881b573705f5c02a490d40124433abe95bedc5afe84bb7351dbe0e944f7540f82f458f3a0700d64af6a801541e0603c1422bf944a59289abe270cba1ef9d95938d43b722b04beb9b14918e4fd7c56073a32b2a6c8f8dde84a863c67a6a651c8e7d8a91afe63248a10f1efd9b9147ac94b0c068d4feee1401728e6b463d48322a70155dcf502828ae7cc2b9855920717a1a61697bd84800239f30a6695e4c145a8a9c5652f21a040cebc8259257970116a6a71d94b08289033af6056491e5c849a5a5cf612020ae4cc2b9e1285f4dadc99caaef3ff484097a59534ffd2029bda9a867cf184d9c45bea15976ae743a2a9ba91d234ead57d1db885c3aa6e1ade3a4e796b39a09766adcdd5f6a0d26080ee2c684fbffe2ebb83f80b6d30c3b4488fa930f4db2fd500590b05bef64aa042fc96e6da750d5288fb2a359a874162761d32a41f91f01a921a810a23c2a9ff23fad40cf83a12a0ea96a8f3f20038f921f0d81da15c02d780021c29a41bc0e281d2b984e0e08bf2114be54ad28b2a2dac7e8f12ebd852c5bd009090d5aedc8158d7dd1a544bd88794510a687bc26e35f4cc6024dfc609b6d5b9257fa147343f29e3c6fefaae24003683c4534ff1ddac49aa22198384abf8cc66e55a6ccf159668093fbef64e2e9993b8f49e3b92f1c82b625c4684f10289e3a7449f4a7c5ee51b1f684ed73ff95df5291ba44ce5c8e17250b64f7d94571e804b68e56e48b47f6f2eb5f79278a6fe7234beb3a1eb6157324a63f8d7fee0ca492cb6e9c520b5439f24c4352fe009d758bf7623845545c1f7c5bc93d3fd0bb7d49373bf7cacc97b25387907d63d32333e24ba19baa9b99b36ad6507259a25005731f5c0a2d635146003cc5af7ef88591e084af6d0c901809a6bb25fa31a4a4c7f16932baa04e50e770a9b6c6f17e498b06bb1aace9f0be4cc12bf7ecafa76d3510af54f6ffed1ec9e2bab522cbc0f32f40aac22e544e1a2c680130bf00654dcce15dc7a584b74ae7f2a7a1e032cb5a3f6e60f3b1785c0cdb52b16632b04bdd1632ea27299c657cfc85f66f2e81bb8cc35034cc70fd6cc453c6ad115ce55613564c01f315a187deab80131220911541e6068d4aa177a642b52e06b87c885545decfe9b31785e992bef28f33a1660d6c61bdfcdc6df3c4587122427d591784534b638aaf1a5c7d9e10df5ed3abc3ce1c2ea2e2c10bc9279ec26b96b634e3db07ee481080b17e84a5847659ca1c74115d314743b2a446d3c5712eaa9c4ecdc268b660e7ebd3371d94c35371956820226e351c2049695c75d9ea2bcf2c34a80d7ca19ea3fbc4716b3f145bda7396355276a6399db61cca63350778977a23e12e227aa2f341bd126e9234258d668da61d95333c4215610bb1d2953fbb0265c321e049e0d0c43d548a80864db03cfdc11c7b059ffd3e5fe462ba6b4d459848d40e3921c430616900d58466f4e035a2d074ee9952cd72d08dfdfdace80b036f628c1fbdbde0173c5c4594670faf11feac470891a15bc0c834880b1621f8c7b87fa66c8262a4dc27d28d14b0a08f6f534a25898125a13ed515525c3428bca9828f8d552d5b49b49f2ad5eb99e03778654a081caa5608620c3d06ce17dc6ac11ee5672de017c78e204466b1afd0143fb4925bf219be2ab7cece19e4b84a9946ac7ee6dbaf50406a81217cefd9cd9bb6a72755871140b0d175cfcefd005911af53fb88a8c9fb1ee3f2c4711595d0e6f9d0ab1c66fc21254d892a923077a9e385b7678d110f4fc839bcf5b538af632bcf965c1d46ab130f8e1635614897f756b99ca55ba9151d93fe32b8ceed4b68f747ba8bdabe627f8245e796cdf573daca3ac7a47feaa110d227070d6c01bcd04943f5d1aef3624632305d9039173d7f564b721f0fc7c6e881b5637947546697a43865b5869abdba8ea1ca151c2095a7ae55a280b88d7a5921edfc426579fe97f17992a150a68ccecb3a9e7284c83fe34d45c31b8a38f1af2dc6fa8e3bbbf2fc196573ca345f404f6d2a3ddea5409e72ef8e2fcafa474cee6cfa8a406c48f292df731bece53c5d5e3692ced3c698664f5b46dc1b0665386ac8c1b8d60698e0f2109ca34306796669e79348868885ab3a8018e51e33e48eab693c585ec25dbddb447305db8959a55579f27c3200210dc1f1dabd657432261e4bc0000cc890368014614195e7b838f7e41c6fba3a4e5c91057bc62a874f67de9e23e5cb022e81a99278d59719203981d27cbef54c18ea53a9f5d637892ed8151af01f3dd5bde66ecc9a278c4e300b8f4c882de42b1e4a5cb87cc695d3eced8cd6a699090cfa17d68be76677ffda27f9a301628c71911083bdec2212a6fe2c758ec9e0e3273307be7e8880d0d6dcb78a29ef3af76282c3a2da08a65ddf94eff097d8dc8a41a80f74c925228ac21456acc22663e072cd06e8e5b29b0d72276603339d08f31c977a6a71a246031829210cc27555b353cd1a5e34751cd587d72a20f7d287659fbb26c22b79f06293c93d44f66ef11a23b60fd6000e4f61b5886da15c8059458711a5d453c59d6bbcd1de262e60da24c5ea336c3d5e8041120c476f2b54ff7cefdd3d96447bf5cc03db2fb060bc970dcb005957c868d07fb1368c8030816e67863b7d8b0f2fa640c7f9e384e7da9fc90e37d9fbd2882f85380772ef2eb6261981a81023ab34ef28117cd0d22e9e8d46290484f5e8c0f9d26134bdad0ea9bcf84a889ebd455e070ece16430603bb151d3c1a05c1dd193150538bc27a244f04e6a3db68cfd306ffeb3abd7e8f61d9371219b98a092c9d33875cc542abc5445c6f82a925112bcac23b3f135cad41552d420c3634269a820709bcafdd774cf6ef5d849979140017da63873c65f9b834efcb78f04ca748dc0371819d0a0dc38e98008780248288a4169342631d52e275646a1678972b5876f76a8c7d14beb98f240a9330a3aa546e88d4b4a8b87e89c13b9244d83996e80d1d3b961ceb875d48a12205ee0b3b4d0b647b8259565f559b90e0ad2db014f2d9a63b79bddbeab9a8d94c4aae14ff7ccd1cc9b40a94f10ac89c8db2d57a42e62fb035af7036ce7328bcaaf303a84b8b1515d345bd8f90a2937492279e49ac446e44c379eb584ac7a67d206cfb0cdbc1c73d7262376d82b0909ac5dfa065271fb0691722921717f803c844048a98b3c95d46dcda0a3611d00b14bf9b523009fecd05f913fbcd21305b7dedf05b12bc5c3f79debb0c23b5c244e9290c13ce4641b645dc55b46452bfd405d116d07b5709d28961438beaaeffb25975b26cab3c3288d07d1d6fc84413369e0cc90a0b0aa37d9f6aea0c1c3fa62b432a7b99c39c4a1779dc4d2a821632384db74e71ed7497502ad1b8f9eb213fc0bf7d90e84d17d0cf205f1762129c71ba456b7e5dd2d2997f676b86f545b7595d7927398e508f173301f67e0a965e05cb5be8fefab1bc49e874cdf014354dc9dd2fe8c4c50e94757b87dfc3582abc2daf71570daa0e56a47f9f42334fc519ee6fe4e85a3daf072b1b86d7e9c3a9d697047127f4ddc20396ec818caca1f5483aa86459f894211ac1fb6cd1f1809c6a70ba8ea0254dc63887bf5ebb0dd6c9b72d45f761870fd83a179dd4398ae5d001b36d0b7266279b70e5aa99807d94af855914c5c79c13a99b982bcc258909203b1ba4646e788051cc1b204fab41db3e5ec483abd4eb36a19f88216586ac0e766df4db32827c305f66e4ff22aee9b21646f239660b343dfbc2350ec5a4c793a685133aa4082d24da3fd2e157521cf479b6bbcf2895a7b06e924ea867621eefdeff1031da326338172e1c7b04de27d26458cd81d4befc60d2db91cdb3458c97f6fd5447194b242d37f4d3064a89665ee1e7afa1026ece0988639fd0bd90643189dcb3c468e9839bdc0df586401c9090d5476bd7e04804fdf11d04418281d90eef7f45258b6a3b643d0d032233e4d0efee6ed77dbf2cb4ce872e9e0dd30a85d270cda3a55cffa1b105b58deb261d7aa24927e22f7f28053cc14d2d7fe06f0c090be3003aa623cc15f8e161fa29d536e865cba77143b39597671f2a92f4de00c89c225b82152c7818a8621dccf22ae82b2e554f1f5261e17e26117ccc263b34db400a21143bb092a96a3b3f1a46048be465d9f307e2c7922f9c978c02b56b9f7641455e8065241e0a733179ab1c043c94770d77301560a815c811a499a7a89b7610e9a1350e2fbe222ac106cfdf6262a23bad1f2b216fbcdc739f6d960a810fd95711ed266c4969aba9fd915b16384c836861c0ea72941dbc88494984fabc0d0e283167f0ed97d4e2cf17d7787e037995ca144c4de90d5e084b7fd9d6094c4f62d3ba07f525683b49c383d8443e2698010b1100f32cb901327bcaa72cfb83dcbfc57da769d8f420c45398fe2d8d08626bf02ac7028a509674ce398218d3f4082465b8f732e31b007ee006efb43e4ac391931f51a266f908c19ac4df02d2f192f0ef815548f431ecbb0e228e61aa2206fe9f8ca197a81321b1035039478237f5eba319ac6d15cb71203e661100024708705905c6d1b7a814c9124d5e79469b7ce68a62c59abe726e6f50c31840254db3ba09476bbcd82865aa9665d27829d7704b9a9825ee35ee303942e8a70b9a878b492dc86431be5840615e10271a56cb06b372be5fec9972312ada4a6125b854aa83b13576c3594b83941384bfcfc52f656cf226611312a462bd9b7f4773976b06f54a537cda9e8710b380926475bfc03ddb1987080da585408dac40d6c83d9437ed81c954eb8cda7f3e0d71ceb4df83a49a0995f2baa2853e429b5b493008fa1aa4e33c70d7a949d7dff833b59ccc898cfc46dbd71cb98afb86ab8a4afe9ae81a82088190023ee2e81ac26587e813180e0c1c7f9e467fbf3ced02f238f3f9fce835566b2163f6a08e1ad1e3203049642b676aa63f763ba0354ef81228cc16f5949d71e333f953aad9cab040eb7754eebf3ba1fc576f2b25e0832c879f5485f429f1f57eb8b63ef750f386e7d42e39b654bc3db3a9610e12e5ee188b4e18047af9f16422c7ef9c58d68695136c17890c163bfbdb27080355a866cfce87bf9ff7d71e4d9892681a6c63ffde60b480784e368ee56c96f72bc482b0396360e44691255d37d80111f1aecf41130841821bc4ae6fc49d972627155248a97cacc0fff040374512c2684509aeaa8b926a0cc0b1c2caef1f110a4093e360f9002e0cf9404e105a90b918ffd6401a3d6ffeb15cadf66bcc0331ac8683c4d8bec3a8c1a8e2181851cd8815ab83a8224b6e0b5921456403358c540da44d0322a1dbccaf0a43946ad7ecedc26a6595a30f37d391c950d684c714349adabfe6a1eb2cc7de5a3bfd6241303f89c382a95d1ccfcf9ceb3da8b4e0feb9a2db29b68b13ab1d2efb4c972964a2fbafc0969b405aba6bdfe35528e6e08d3452e01119379259b1450b634604a974614caaa7e519e4c52d49aee431900d62397ef0222f0fcc390ace12507f0e3b4fa13d92946a5813c3e3dd7ba4ac9e590fc827ebed7d2bc5d3dd5c53f3f3cebe0316e330a89c42d094f397ca4a7a8433ba1f8642ebf6016f764bf5ccc6387b44e91e15f7f0eb4e45df93947376c5ec79593133e3344b2ec0b3c1ac99b34b10b3b977b0e94ffd1a75dbe872032ef2c5ac483d3dc943d50e2238c7b087744e8795dd78832427db39a814e7ca14fbe073b09ae48a8157c590399fb738183bd08f190e2a551ad36f18f31d201663dbd788231a44b3ca99708733d7b3b61e76fdb11e1eb0974454719adc10f26c79edf6f346ec82a0edfd3c587781b61db906f2ec1fe5143e87a9a16749d4b6afca5dfb57452307a80492870f25099d3b50977f02f5950079928724bb1c69e05e8aa58401763b012616bbcaf82b99eb4c95164728d6b0d093f273d5e1d18d4236bab124e72df96dde119aecd84e8cc21bfe69615d94476cba20f6e4bf88af7e98173a683a083b630875ec545253fdac1b6c90a6a42fede3a36bd698ea10705612fbfa7a9e986566035696e65e1724e0b97f6035a63e180b0df01a11f38d8531d1d91e37542a6b307d802e804949841c53fd1590a20f243506808807289e070942719f8aba94906d97067f8f419906e4a34be6833a305332893b66236d1de4e0d5b13e3d391ad0d1eb1cc5e665f1e4edf60d5478e76f337d27dd09146b29c16812b0b6419facb3b47660a77a795ea4309161001dab74f72a44bdd55dc348e60546803a071f332b60cffa3d4c4564041cab5e2ae2861e184d08cfaee2a9d683df0add2e4e9489a9ef5f96e96ff9cab71c0d09677881c2ba6e8b20fa4ea4624a96ba1ea457ddeec8e26d68405a773a73c73dbfabffc4f48e2529c490b057b286142a76bc8757984b1bf8ee3014d343f11d595969cd8276502b46e5fa6f15ae7b9d72c3fbc6ddd2a34fd04f9868585ca1ca2d476f427b4087148583f337d68af6234074f5047a1a93eaa104d3830a49349e1553f5383f39d5428f3502307ba1eb72d41e0ce53ee5664011ec086daee58267b0212697684d540fd3cb9ea9c32054ba6b15ff0e2c0fd63b232f789e0179387feeb84f20df5bb28e2063eb6f282a340d93cd5703236328a1cd9094872f8480a70ec9c3048d5780965969e1b1c380f5c6081b91ceefe3ab772be461df36843e6573564fcd835a50f41bb0ad3c26dca97dd8b84aff8671c0ec9c0cff0a106367c40f1936df0ac531ad7a3a371419aeadfa487ef759c5553bb095642e46fbd149fbd1e9be508841dc6ff56b896bcd7e414714e971bd96c3d83a3c1897e47034fb1427ccbb7157c17a2a667aef9f3f90c9808967060605be913af6734093738c6d3ccc51bf67fc038b5fc6d00416e831ae10d9b845e80748b7fa0e60bcccfa8435bbc6a02d5368385896864b6c42bbd9d7f3321c8ace8f5861419fcd196a9f95e72d54d62eb08739371c750d7293620320f4e2322fb0326b6180de712b1547b5f11a0dcd785bc9744232799df38a9d7b82f1d91714e43a2b2eca09d8bc3317f8b6b92fcbcc06299b5f403387a7c6d56baf20d35c0f49a67b17fb527bad293816deaf10e3f5ce89f3d93d6a9c0772d3dc3b2c40bdc2fe3ac71d1bb029fe83e3ae731ea600f05eb254c588efaf162bfa0d62c5c0c87be4e77afdbc41da681fd87f3ba6e4fc0e6593c177f043feb9e3caaeb9e3ee705c9dfb811d98cd90ad135b66b579b0038bed299c9a30e2060675c142d408e61480a22ac2c8cab88d9c887d3c71520ff18f708efe17dba8de139d5ed82147dd541529b89a44cf4c34fae71a3eccc41e5383dcd5edc0bfa2bc48f74cc60fe11dfd876d0bce208ec087a34515032ec6480ea6f9c45b1eabef8baf5f299d8113758e7100a5149464e1914475accda6f6fe6e25916439064ab5cd1968be3cb8918f21c22c1586411924522741a3f5ec711450d7637fd2c391cf131dd4146079806b4d17401a71d5cc6433d7ee61e6ed43a1eb4a85e30855b96429b7dc98e1a53e1fd5f88507b5837067d216288201d9ee8dd1e278307ada437895d7b0ca0e8993786ac3cf5f5ee5cc98a8cc7f8b1e96b513b12900a3a22d20bdc4d53374bfce341f9422861594793c326362f4f8441707e2ef8abb66be3784c359d7208caac09b4a3ac945c21acd0f1fb921c5f9d285c1718c0768d50252e0add516e6072b75f8197750b6237a5ca755f5af6f2041a2343f0de6d256b9485f3a126e2c76f040cfe167dd10b8aced8d5c0daceb48da98b7ef9f5fb4e9d62a9ebbd534c0e7b0dae0888ad47d9ce88fdb94c353b0f9551031368c926377b6f0dc3c89128d9d2614002bae91c73554d17e02ce53954f37ab47104d4525f6d6968d729227998f4de5e737eebbb81be50d1476075fedd84a500b14372f5eda5068d3917ddd197acb4637d1ed65d256586196526a3c78ca232e55ada4aafcb5914a5db46616fe9c8c14ba17e94b361dfd7332b75e85f0f1c1f42a92bdb9b479722eca3364069ede597c403bbb44400dddb2dc743f5c37bb786d70f5297c79bd809a908ddc8a80f726fd8b2e16c5ecf24b8948f7410284416d834d2e8ac70aa254408e255fdb95f9b6190edf9e69c338c1c1986c800aca249d0c232683d23568f70dbb2e1093787d11b2763fa33fe783db76afeb877445ba22a0420d00dbbfa90ccc447f1f1b8436cb62667a6133a96d90542fd07b04f1c5fe2c1bd607b4b672e12a7374cc82c761e74261c1a62d54d1fac482413a023aeedebfc1022be3651b05e6bceb7548704c48399fe101baa4ee59a1e89a5a5a76b30150f91104f53183ed0995c1635570788333dea3b8d8f365face8037e19f08146350e506406a0690178020e505bfc14966560dad4ea842e1b19d34506614513009a960fd71be067dee68dcdcb2474c37b3572dcdcdf0d3e8b5ad2750befe0bedc1df646b7453c60a056e3035401094667d3772f598d318e05bf73f0b0c199a0a6310b7abcb772faaed128758855ef097407188d8c7d6b42bdcc6a532725554b43d9d3f4dd65583260269f8afb27c5ab0896690ee5cd88d5b10a01b3104c4b03da511e7b5d308e329b0ea678fe376bc800c3410de0657e0e641731160d920bc980d935c8f87d6391e255c575403c5a394e000832447c7efc4838ebf9749f867da4f2ad17e5722e7cead8a9914b4c667fb38ffe336e2e7fcb62dbddf6de5bca94a40cae0731079407670f0db4469f7a9496a7f794923afe339d1eabd37376a5acbb842049eeeedd3dfedeeddded6d73f0070cfef7e37b24abcdefe9ca9fc4d5636acdd1e161e549b6322e54c38714850ff9a44a4fd94265dbae94edffccd354b632a357cbd184fa2c1a42477378df5de2362d76180dced6617587a64dfb3060b0143bd2da8b54ad0cabcd07b9c0fdf5a90d32d346473693513ad371f5b86432991299ac653654804518254bbfb71f73587f7dfaddbd51c674e89f31fab2178de9c474629494c5e8fbc774288de9501bf3696862343a329a190d8dc562984a2c46c5ff33528538e04ea7c9d37b1e6dba8f36dd963e3f9a7cfcf0f9d1f4631cc7f1878f8f261f3e3f7c7ef498c97a86545a050a2222a48995dd270b902c5984f4abfa348d63d348a58925d4c14016d39191e952154cee96ee3f97279bdd198fab747b787c331e4a3cbaaeda9e190feb331eb7d4a121636dc64888c578f4206faed5de3226847c6fa64b5680a85216cff3bc96cd64dddf8771777f9ec5546ef703ac23623a3a3db31e9d9e9e9eda4ca7e75a9d1ed9ad842049b5eeb6387376736b756ab5a52a5ad468cd13a25db259cf7828f5f7f0e8663ca852b63cfa65adb5cf63d6d3a653a59ef1e89ef1689d5adad0dc12722728091942a4938290d0a6bb009f36bbd2eba4503da0489851a5a52a9464ab54693cc02cc1a68dacca66b2ef4da8af33eb6433d9ac5fb6f4cf4707c9998eec5567b6825536ab5f19f34a9a1957902cbb6bf6a4e3f2a7fa32daf2975571842c93a0df7ed7bd4df7a70d03ea9bd07d577ad77dde40727d9b98cc6d649f6be9b2713adbd6e4d3dddeab4c97a07841f6f2c4364638cdf501b369efa9f5debe09f6636dbaedfeac6fd3a68c862aeb572dfd6536319a4f9b1036c8f6ab6d1b99f5ca1f9d4f9b3636a72d9f144bf6a740c5c1e19336d9fcc09d6efd4bf81c66636397aa64c9b63cf1d582dbf45cdf16c05fc8986342f02d5bb66cc9b6b441eb8ea6ddd206cde1ef2b10aef53ea4ed5e39d21cfe313bee2d6fb6234c0df7cf784fe990989c96ae3c59a292a4a22065ca519e329439c874c9892072bbd054046304ee7c7aa64b4d493252bfce0e4aead749a57c857cfa935ccf464a92a24b5372b97e3be957cdfd245722357ac04ee45d0e69536801f42bad4d279dd4486d561a4de00eea172d8d526842df7eb56dd6fa3c67901c8495fb2966f2fa486c3438e17023f3597f704d7242e45aab11b9fe8c5370fdfa130ea952432aadfed4ef5a33b5c0f8a2ca5252962a9ee4dce3a193ea9ae8a682cce49e36ebaccd25277448c1921331e4735c720289cd89233a362d353e211b1751e097293cdb1424ac1f6c295081ad081a6c2478c236c3e7836d85cf062fba242c5ee1c552134ff91ca5586aa289711c979a501a7f947920969ac8a1a98915bc29f01746c704b3b2e8428057517849e095ad074ca388ee0b2678c81e0eaf325d6262289fe3f8cdcc7a788c6f86992e318164b4852c205202141c69498ac2458d8708b088eaa2960213a4400527197e3bead953a45da41064436d891c6ad072be2089207e70f8820a1b1747e0b03f3631d9121329882c99a5256ef925d3a525bcc89ee9d212392852b0478eb8fd8e1c611db99656dba77caadda77ccaad4ff99475f7a9db15d5762daea5ddb5b816b7aec5b5582db7bbee39587b6f676d576b960186f77d9edbc0fab5e79dbdd752da39a547da524a29a5494255cea115afd8894e76f9669b435074b257b4cd1c8a258bf5b158a1e8a0e8f6fb557e6f43428a012383358386b3d65a4bddda183ff8c6bd44acf5a6d6666bad9d8025e2a49452cafaf961fdb07e46204624ac1f9728630c630c2e515847c618583f2e51583f2e5158476ad0a7b1e2a14f479728eeeed7258a1371248e64bf7e8d3ee0eab8bc722f0aee301bd794bd963a15de069f065ef7819ef56c8963a4f0dde82ea8ea020c55e8754c7583d6a0bb98cbe5b11bd94b1ce3c877236f20e2ef135d31996e90e987d66be1b5b712b5d9456011780412d930d7edd65e777b6f47d4f5077621b87259b9c48091c19a41e346db98986eedd44e5deba44efa27c776d2488d54724083c9d1d1cc8ca8db454dd444331d80451d8047b9bf830e40229009940232894300f8c08b038940b2875a48e8136d34f20bd30bb83ec92336951c70b0e2119b660200031249e5bc35839c06399188def8d313148e06c221b3326a5ad338306344e1703232311db430d42d26ccc5dc68b504308001d0c8d0fae4e474c34c92cf8b1341a28e6a5c95c2c474fd3e3d41414545352ef74daa4d3656d536be100d60003462539b5d6b75af5e5d74b211e085a84d1ad1f6357d4f50b9e9104ea97671e3978483598273d0bf3890ac4795e84bea17ee7bd2b097a72728a88bbbe50b5192b7fac71a2ba38a5491721080857c5e1ccd05b94122d116e60ae0026b10105012211faaaefad366a5b5590d70801ba4049c9013688e489b43c203d488325feee22e6e06ecd383da80c8ac8c7cc4549ae376cc01449825d8e6e2ae156d516051b71a249ae9bc10bdd86061169048b48144a20d24126d209168038962c21c51cc8a87eca8ce354eb4894d0d038b40221e20514f6863b3e2b1d7836b3f252917f793e9c57950a6b82fa9ab5d1178d4e6f51b2b1e97c53c103d58f1741edc970ab49deae26e962bd5e614081be45bde9c0df3d5b0180bc3621f881f582403125d1cee46354c6c126da22dfc408696c3430c5cafedbca04b72e5e72d7739c2de83a7aae6a332bf57df2be07efbe71844d52f13eedb1dd7eb5abb63b89c1bd8f1eea427274aabd46e2c21d40a73a5d65ed1171a8386d47b4aa9f894e2a7945210fc8a724d51b9cee42acb954a013f4a29a5f934926dfddb912b97171c4315d64b77b0adb5827da5609f1e3d7c7c805fc4e55d5efcd577d3efd7a137a0cb37ad55a1726c939e3427d630cd74c7a58ce9b1129ffe18866118866148c3cec78aa43b22066caeff7d638f27f1e6b5408390706d0f9f7e9bee878f1e9e4787c5daf59edf755d36e74cac08fe80738da15f33ed9bacd3230b067c4b0c43457d3ad3354ae89531f68dec78ef3fc4615d1963cb1e3f2d7f233b314d624d7cbe07fff39e48131ff0c3920838aacadcf93ddd0199ba32e6beea4fd32b636e19f300efc1f7e94abaf39531b6ec69d37b78cb5f1506764a12797b17def67687f4cb84ef3df08bac3e2cd2c467f5a10f71d8eafba9d39b2fb7cbaddfbc895030dd5995313dc48ff1095fa52a4d1072f3b7facea7affa8ff4beded348f6840c21229274e7d2ff3e8cc7f1df343f766d29c3a24f186c0f8c90be00ebcc7a78b85af57fcd362f0fb2879ce990336d66ec6fcb77daf7fe81d7a39fa804c7c02d329f363040dffbfb44a1c4952ac42025418c2d922582e2fafd49993afce0d75a49911ccdf0238dd054dfdd28a71ed407c998b7bcd5b52f45d94249094051a2a4a2aa0f200ebb654c8fef8b71c09740a5729fe02d20de6a82e98e8b12353499c10f4534dacef7de37691223c2fdef7dee7f259126f696f7bd92c8a53bf7cfef726014634b5518d8cb1f3eb12a9a1c8b9fe1ca0954cc14ff80c902578a5ba09586403d4aff1bfdba53ea9452ea14f441a9d625b5abb5a1dfbbe299a95d573b996aedbdf75e99995a3d06122cb6098345094b2b75588f0e85b9bc55ffbd55b1b7288c5ad23ae156813e0c14b882e01f9078daf75a7ef8c33720f813bf4fc41feefaf372fb0171d8f77ddf574d50737f78fc4c755cfe0521e1a350d5895acb5e61b47a575845aa2dff6eb2d6622e937e316fb9e92df7d1a64b6e25b8fee036f4c62a018588ec5ef5bcaec1a6e475f55a6bb5abef7dc58381eebf0f55482a15f815a9617506a424a57ed9e8520b7e6aed48f6722cf0403e2b5353967cd626f7a75e71d95f057ad5f33ccf7b9d3e47dc0f201dc520b0f8ab5f955cf6b76193e06fa4eae1305ce2d04573b8bf01284c04ea0a02d77cba5cd45602bdf1b2bf0f870dd11b5acbdefd49a5b21bd9f1371d2c631ea0faf07dc45f3df8ab924813505562f08dec80df87bdb2bbb9fb6f530bee689b9dd40933601e5f39bb82bb3ca1c7f985ef22199294b66a8f5aceb4d99546aac70eeb318ee7e7ba433b206dfa8b4ff625a76a62aefec4f4c6258b4f67a50ee8856bf5eefe3d2e8789ffb2560dcde17f7a99fea53806a0057015097ff545542f7effe9aae2abbec8eac32f22beea3b067c9ff057efa37ab12c1da0eaa0096d0b1139ebe141863baa5cea842ee05bd2cc4461f3da6710d6949eb49226ea21a9a92c4d4e3624274c6a5015aa40b828284a6b5486e67025423a7376b5ec5fc3efc3380b7c3a1308b8056cbffee94cdd11cde12df5fe8e14f324afd90802778e0493056e772427f446080a7325dc0923b23f652a9d09095ca44911f0bb2ff2bdf70e051769e2e3fdf745ba07bf88777e31f77d4a11c092ba4ff7de3be02b63443a281aa204443b5da9d31a2041b443779ac4001aa90fd11c7e0457182b35ef6bd475b75c894077c61d42976c5f2f66aeb15c458a290c7ffda73097afefa2300a537dadd4966ba6ab6acbe47bebd64c0cc0b9ebf2cd2308b02d59f54ff133e2b52fc959cff33cefafd7799ee795a777bdebddeede1809f5fbab40656f21dc66ba5c31578cc65c362e974dcc15a3b118062b2e97cd9fb8df596badb5d65a6baded6eeeee6efd5a7107718a2397cb7bc1e5d2124305206d8686c686a6b3e95777bb8edad0b868685c363436e6d39c544a2749791a1a1a974c97a008237bd9343b803ec4b05f3f1a8d46abd1eee9e7eeeeff5fa54af6cffd35fbf35de9b4e8b4f05a6b75afeeeeb5ceaa57af5e6bb561a94a5075c5dc633a1e731d59cc3f26d3e96e4ca76dda6c9afe9893cc4967e6546badb1584c95e912143264efd80cf59a30d7f9bdcb3dd62feff6d16d5cf65dee8ab5b5f5e897bfb52edaeaff5c9e1297cb6af93ea12c5aa072bb9f9a6e93806e0bf902b55392ff2079471e913774efbd57e8de4f9a3a1aaa79d4763d246621caf7e807292849a826de84a8fdb08c69c37a1a574c56cbf76d7d11f2ac00c24a62f9761395d46cf35e9a36bf7b5fc60c3ed2a4c996def4dfbf4917098b45170311f9be0735ccab79eb2601e19cdc983815e5ebd99478354f12be00897c5750f2fdb38be17e47ae704c9c98aa4b8ee6b82f06791d8eecd51cb652c2546463e294af937c7ff50497efdfa8e5f2fd5e09b92ca96aae5b81dc7f3b4c73d43f2d803dc61de7eeebf0f041c2937c1bca0c4af7b3cda0746568ba6ebbbbbbdfffbed4be3058c0f7dece4b1b99525d40539aeefd63a73f2dbdfb599b4d03191b41a9edfe2c682dd9d45baaeba4773da8b7f015c7d6da2f94c2a715ad9f375b51634a0c089d65680e7bded3de20a29756d81745118325702136d723369a9ad0b6f876b77d9aa356fb597b638693329c84c9f64551c4d65a7b59224bc4d75e1878878d63c0a7adf7daeb366687bfb5ef651059fe72105936927b05a1d82f6a6db562b562f5b26bb6228fd7fe1cbe9fc396565a8a78898512d219eb94ef51bf77ff3dddb19dfff745fa41eb80af88ff57d29d22b1223adebaff516f792ce6b0181ff0fb411d87c5f8b493a3b7eedf58c37020e9f0f041422ec7f2bd37bc7dbdceced0dc19ecb0fefba3c3bcd2be7eb1327d1fac0dadf5acf7dded69b3fb19483a0ee40490bc250ce957fdee898064accd0e74b5d9618fbcdddb6bcb1811fcfb7dfcbbdf471f15117f5589e94889bcb7ba57d5cfd5e5299e62a6a512b8a7eb1e48090ef39b94bb2e7d0c63be0f48c364682508c9dd921352ee6892e44ecc5dd7ed7cde1d65fdeade65d32ffb407448ddf89d90f705163c14b600b70721a5b5d26aa4d6136096502a325ec0b576f78a077fe8dead785c3eb4a2bdf7ae78561f76ddf7a1e7d5d15bf1a83efcecf759917e20287ea81a573c78c5135251f43e5c89325cc0e73dcaf5572e2ee1cb0bc62fd8658563c4c018635c2f594331fcc89ac3d0457c595d203ce2b6250ba15fb3cb2a043f7258e36ac43c7d1f72d88da7ef440eb3d132a066d3f718e8aba58868424deba88639cd85e88d533ff5ad5fe1cbe5e937ce8f5cf7a4c9e0aff03b34aecd5b8dbf1bc55054895fcd2a7145d6fc8522b8ea3a70068d7025be9034bc9632511ba1e6404cd4727f7741eca956325aa8b1e201df65a500fb223a09130a1d11943db0537d4f4f50ab122757aa4a1d351acde1e41ed11cf541f232e961090e190cb392b94100a71aa71613019c5a4c33be4b1ac9dc7286bc45e4adcea1e5004151f1e429e38845c5706da2009f38399c5c161ca97e752b4f87d29343d156dd0875d4666a1ed9aad11cfdf42496674341454565c9f2951b20dd6ea696fb0ed11069d01ae20bcd3280f059737bc937c817ce5b7d3b6af3c5e485f472f24afaa878e242ab9a5bcdcd5b35b89a5c4d969a299cdc13f085f4725273abb9959d433b7368fe93433b6b6ed97683e80add2012c7158ff8e34a01e17f46355ea73b24a68fe6106609767205924638647038e5f383a10f808462b8584e6a94a239947430e990a2c336a5a5294b6e9c9c3b4e4e36941b47ca7172de92d16440b22059925247cdfb6e15b750e5a4a8225f4e2fa257d1ebe8c5e4f594fb054513ade65673abc1d544d5647921cd68d400fd91617eba4109e51cd510faca6cfb29f8a44ad4684ad466563caa779282a0aaa68217b276304b70df7c83cc52346fe55cd2939a12539b343cbf40cae9434d388796f393135473abb9f5cb83ea97cab6d39f9bfac996673f41b99435b71c5a4d5973cba1bd9042a08fc4b1a886d5b89f476215eb43a0ef0200002b9e8b9373a31826294d4e6d3e512853598684a072683940426d76122546353a5a8893a56135fad5d570d241d228e647328756232628c907c206592c73806ac0e7bd616fd5af718fba919cec8e92343b592925af93358bd585ac199335bfb851cc83a4518dc764a5396accf879cc213a496b6a6e2800bfbba5fd5b00d1c91bf99246e383634983c42ed1491bf9924635de92e5e7adff0d36103758f1d440b5d9753f32e1ae0657a3c18a076774c2670e2d47a85fdd831d4e2eb4113d109d94912f6984e341d248e66f903b423b433476889038da01cab6dc949df253be793b2d64a8efcf9f1ab34089ba39b49ca19c9c229b08bd53e37368f406fcfe2cd5afce6132df9fa71a16cba151247e304522684cf24a3a862bfa6088094ececc1972d8084cde7a8de0d466d3d70856a8d93934b2c605b4d52f43d65c415bfd2358a1af9a2bd46ca77ed5dcda7401bd99f1fd355cd01b19df5fb305bd617d7f4d14bd81f9fe1a2d6ab2a0372fdf5f8305bdc1df5f83a337f6fb6bae6072d80837afc1426f5cbebfc68a1aa89a2b3555d09b900a7aa3a2371f534500d0679101b976c63f6bbec8fd35b9d631fefd3c38a9920eb97f849bc3948e723f92b520c8fdd886dcff7243ee8fa124f7c3e090fb5921c8fd3272c8fd3388b2d3b022673590b30fd01cfd97541a02c9ce1f398b81e6e857913319663390b32434477fcd33b011906be7c6cb208d80623b371e26c68db7f731a901ac8131dfd2e846499578c87d5225a3dc3fd3817ad0de7b59869c0d65af9c09c5700e7b3dc9fd4a3829b426a0d7d3eb83a19a5b57be9086c04a42b79b549194849c74c9973492794b9623dcbcc5c10eb871f9fc98d407341d39528714ce61319c7b0c87d3f1913a907438d19144ea7822031d51b95f0716d4690ed3a1a403ea9fc626ac43ca8793c3c9e148d91c863325fbc9fd3456208cd92b75d4fe889a9b2a2728e708bda1a2828c02c51981a91680360254c36a7cff08b71a244e96e6f94a1c14609c5ceecfa1e5fce400e124e1e03c69586ecaa1c57268b11c5a2c8716cba1c570b97368b819793563685ced685508e2194347a24a0913086e60cafdfd8c19392cb36d0a3eb150ee3f73683342d66a4767f0fe0da19adbd008b7be33e44aa9dc9c043f109dfcc21bd1492fd701cc705092d7d6a021f3ae2f3358326062b8c8b8d0a054585d0214ee033e58e2eec1e798ebd37ec72d94fbc9670bd924380a1e427374d7f9ed964f5c71385a9e3577e2f2ac52dd956e93d8247648b4441606322eb0adf03bd703207067cfd1683418da850a95cf4ab338601932e053ac341c4d015e1e6574c0f5a939451bb96092cf4aab9806d41ef4c79c00b3da6c5b844580cf4a735adb5073b97b889394524aabdf52081112e09366c9fef56916cc9431148bde6bad932e52281a398488101a4e64f9fe4facb95b435877e84c8d1eb058816a3efb670ba17c56a0b33d90fb2d0e628d27f069ebd387e18b7acae7e7bfc2efd877d1d49ef1680ffac5227c7e390139a26ed1238c34267dcd64ea16cd09c8c58a66b4906a527f85df7179d00fc600c7f8d34c1373cee4b6b95d7dc39216403046e0fe9ae9521645f9a7d23e8adb4e7d5bbf543176d4e014ee18921256b1917299e61d3438e91765e2c129d31d4352fa15e32fe5abf8621052a73efe069d825d227c81eaeeee4a7e944e093ffb15b364504a6917a108c3d7af76bff1aead97dddd416d6957d2eeee7e0abd6f37a5433397569f9696bc1687061a74b78b08c55a6badb5d65a6ba5558346a2371b7877c74237b6fddeddb5dba5cb9502ba5a6fe169e250b31f57fa8ab5097a534f000e68daea3e55ee34a0ca16a4adee714687c5a02d32db287317816ccb6c6f79b7587fe976dcf786c1930630a10d085d87d6bcc05dbb76db6e21dd8daef0cb9f232669b7914c7edf77f79ff85d589e637615489e9dc5f2aca7912792554455a802494de5709d8d367dd79dae7ad294e768bfff2e40247f9ebf8e3ccd8e6748f66a39a64a24456da799bb0b0cc9de9f1db880973ddbb96f5757dd539bedd7a4042a686a2a2aeae969e98a945ec1a9df47ff0fa86f476ba582a581ded05c832a254f9adc9d2e6bbb526b6dbee5d9815c817c7c9f933bf957bb00b97690abe33867d8e4a0383ee88a8262790689995e1838e420093e6b50ee1a44de12045b83fa659f8cb1df2198d9be2d5d357b6d7a2c46639596e747c9ee158fccc6a6cdeead48db6fbdcfc10bf8ac51b97bd3564bdb76a54e7300848d80be1dd59fdf91ba6547f53adda9dea65f34ddeafff037be3923087cc6784fffc6786508a6fdd2c96c551dc25a032ecaf497a49c645a0ae9a7a615413ba47c6d3e008aee58c2293d81f4c4d11339506f41208a174ca278118b7ac0141625328725a8a76af32f39a143c5ed382556a5e5763827d6b34df97041385a522e0a1c997c62112a4c82d00a222c4a714515bcb25247a3172e58aeb0d92b6a4d5cf12223d3a52b9848e12bbe184a302296ae29061820645c01242307d6948825eac3f284a56906ae080b0a3096232c435882ea17068da9dc5fc54a3d2b8b1a4f4aac6841be210ad2083e6d5649c1563c7192af3c2d59c1249fd6668346113766f010235a2b94e4a64432ac252ff67eb32fcce728f333e3cb38a1107a2b8cdfed853e14bd60821d92d0a08253d0758a6145bb402285a90e054c3a5a4567d6c3a3cbe74cec345d579c8c3f5ed161b47d3a00e1de0a33a8a7a1a9294634b1258514504082021350901409b17a02091c56892b2613596215577e08e94ced5a7d06cd5691a4ba4873fb5e9f61a9c0d1ebdd62d75d974d9bf44fb146ada9704271d0fa3ac34c6fc8b4db8eb71b1285719927a5dc27c8f459b94c8da85bf1ae78ec779d8d5abd1dc5a670ca27b5e1a0d76d6d52fb5dedbedebef73be780071c84956f791ac9e0bb9bacd163b8505d054c9fe64a25d3a529a4b2156d1edb90e9d214b64cdf4563997a0c219cbf107c8b0cf2e7226d9ca64d18f057c230e1d38afe21bdf1d2d21cfe5488677f2314468346df4687870f26998ab6262e4981a35228e573269f34b9bb3e3383804cc57c8aac3659c04f6514e6b980a3414a8988229cecd09f51d84799c2e94811558e9e76682e46caaded3a2f582170205bfbb58b90f68bd2efb258fe4940f162c826e1c41a224664239e1f14175001b3e48ec9ced14a52ee31d3252b445690d0ce87da514f7ac0c205a426a503401161b4050d47829040306405e88a504390521316846480010490b042b4c4110ba65852a9d0fd48c860cb5251cd88233625507201e926050a513e606ba8a205d8720a01d312535b90584202326891830b504a6004092a8e55a193f0ee7b90f41ae4e9f1d58e7eb5d5c0501282920411131ab0d0800a5a5091e48b21b434d919c203a1a5a12b4dc152152872ff27b2440064ba542588fc92e9529522251fc20d286089024912453b78922a38e006a8a034450811a460348211fc6287f66b611b562528c6cb62c1030d47564ec0c316456e67b0d9278b13d4b04511372d4abab3743718158a2b9f52a580144a48e1c11695040b76ced1ca50a958f95cb07444e5091f80942059c235db8f596b3abe54c0f8c1c202146d07308ec4b06484c90792b082e5052d555c819fc865b40229211bb658199af20919f1e5d2072827a884b1040ca49879ddb1097ccecc98ae1ceccd00932266c08592239618c23d9074b31230c860850c5fb0f0022962b7fa45b049feb2d8b997d40f1603b37dcc52959f4aa552952758b2a8579edf0eab6175c722a90f648721904889a28724244e764e97ff0083cdf63103be80c2c51038d4b434d1ddee8d858d4a9105125b10610224465590a86c366c70d282899fa0232b8c6fba624c3ab61e62401c790009d31095a21aa4240816524021c114a71008f1e1f15db47d0613d09880a1071c0ba020f1820c2a234514c1e4862d515cb1f2c57d7999c086255d2c81418820764e57b60f84de7cd68525285ef030b66f3a8c42cd00ea89125899e202931ddb9d82afb68bf676b5a558dad37ab7b4df019f2aa8d48c1846357568080000400083150020201008868382c160208c026df31d14000f6ba0466c5e361648831cc86118a50c22c610000800801063648686660402a83951c1c9ae87a05e62f374a8638cbc2c7ee89d06db23c06b2175756614da146debbdd907f479c0364f8726beb990490cc8913ac109cd9fb7ed0e3195f38dbb87cabcb9cdd6c52ae3d166d0ea0bf622379b881bc726b9c9a3278dbeb43964e058ccd434fd42606f7382ed919ce27f51ee3bb87690711b2b04c4ad59ada286b8f1cdfa613022127c91ee6e13f7741e2e1f4a033dcb250fe07e023a9f88379db8f4bc00452d1c16cd0c4583a6c9058d8314b172eb9507bc5298857277f876b0d5338ecfcf60d7612a2d76dd88dfccb9fda346ddcf19b348f2ce298c89ec575d0010ee05a2d1ee62d7e37886f2a79ceb7acc7bfac2ba994ee64e7a54d17253a878625e28b642ede3050ec0a10116646b714ad8ca82c0b5575e7450dd5a1e3891e95242a15afed539845fdc7b58e863eb179b5656d4ab2cbec6f63843b6bc3baf12d0c3f7586f12f74b897a4556aa8c89ce2eeadff8ab0256e4d45a993307ccb07b0877cd09837d268444246a17394050f6585f46aa0a4f2a59b1f81482b36a1887bacd0381784f08475f40a7ed34d6e4aa7c7ce365681c007b8f3b4b628f6d6cf789efba2ab7eb8a5fb2454641bbcc1ea87bd2ac4419027170db9670710adfe5530f9f6e9ac43b095eaccb85704602e354d875f7a8325809541fc9e69d231851840400744cf667e0810942b8fc8079254a304bbad3358bf7e3ca00aad02ebdeac77e7175940a064719283a2d4390e80a1caab6fcab09cf044a0cfbe82d2524f317ef98eb092faa46c7f8a0b72651864b0f39c74ca20afc90a312aae03243fa8de737b11958e9b0ab44d319cd09b31de62a1512ddec98c885e6bef4d4d0ed7d7a80f98ac7f5720344e00faeb3e29369a09c37099956de146d23150c9e7624e501881757361a7b54f40014c6589a18b98b00bbafe8fd31a6eb0deaa7b59da2e4dad24d395df6afc1a5a596a12828d46a691ac35aef470c00b90954a9bd1c339a54eed25d6a958a25131d164fcbb89f3cfb015b96214390648887510a14e5220c1e7797649dc5952d35814e162138df625a4669b990024a33bc36a63757e39578ace634b776d42a556351b0e32a34ef5418292454fc0cd0b6e735a4f0823e426e62543316c810b9dbc9cd3e82cf0bd726408b41ddf6b44ae238c9a0f33c28521f725083f0b61748a477c963226a709f8de6525c92a43cee62d204597d54679616b5275bc91d6ccb22ff28a2f85967737b2e9f8b3aae01db75fd12f5d3691819e1f13734a32370f4c25f189c1a1356e76e00ac2ab6834915a0ea01452f6410189b0b84b2ad31da847c0db4227af9323ad02756456e57cc8ccf5aa8b18e095538669e7040935057476d44d6978e497ef1dc1d2d1729656fdd29f48a3c10a50566cc6525c9fdbee85494c2975d46cde707e9bfdec270aeb19b261b57b51e048fbff28c34c28e8c76e73a94611512fd99366f2e8ec5bdda81a22645f2b714a24a60b82deb95611bd72b6cc5051868eb2488569ec7d945760a46520f8d65499912fe3b41fd904d935ed9d7122b5e9bf1e850d5a432ca7c22a85c0cb2d5b47a1dc7a8d5b625ac178a4e2b7af66dd0c4029acc471cde85b71e3d193283419370cdc0155ce2f8e32f37541cbbb9a3134098e39953d1996a01f62ddb786ebc560e14cb982defe056a701842853f60fb3c40033993ee9a55f7601fb7445b789a88e64e269d6f8dfde2e3d70109fda60eb5edf399da807ed50b2199cf000ce3271c5ae74c2c86079cd85f918412f90748d58be2062914e9cc4471e30b0f3e6ff9bd1092a65995912bd39885d083e02ce77af195d878063115a265b892c98123bb662e404d6834725ffe80711522a4cbbef4713bdf710951933272f5617f2984fc5dd1532a50d8572165db7a9415680ca24410c184b1354ef4618a5f9ac6643583719c60434bfdf3cadfcdf363807f12d226263bb4623110b1550dc35dba7a6cefb60ca9bebbc6b8464185ac08b28b2e1de0a6a29e16e7d8836af2014f2925b55e86da5a00bf373c0990f8653adbfc29abc9938a8af7cf9c8f1e4b3d2283f3cb4a3f4ecde552f432dcb77fa600446dc06c35f8ae5e17388c2e2df5aa98d9a7ebdb6845adf1a73b94005867543f7ea52798b65cb9f91b5e802b64518f34761a00015883e8e3242ce9dea0f605c8e8c8aae97edb5c606b5a29e02e1e74f3732f1ecf063a53fd6e9a1ba998d06cd6500fc15f26ac421dceca784e412883e2e7314584fb507621229401952a090f7bee1728842118f18894e8b4abcb95ebe06e9a453beae24c7677411bd55b48f2ef8019af82553d7ac8baf7b47014e6311c88553252cd6807ad0af5d0e09ca85395ad4c0dc6001107202bfa0215dd608a2bb7f027c20ece97eb8fc45e991fdad6084e5e8e32e36bb842f0c3926f2375f9ed14a3914809837de84b7b0bc55ac34eaccd45e72bdb1c6e4b0226d135a960db9872d91c834bfc1b9a606307275c8875b941825b9ccba65f4166f1a5d79d3752754ad8188b4e9ef9b2a05fa55f5ace91207f149f03a8d3e3c1c464fc8ea0b6a69c196d2360d367f951f3b3fde11834b6dfb9549f095b560584e0d15014878d5a48514c1e968a70ace65663ff3ebc763f618b413f131629ada2436f60b2367145a673eb18164daf19fbe865ff194c47186705eeee8986d665bbca84e5fb97278d7ba8aa05948f269612e3efce798bd7a015a17898155c36f28ae6326cf84590581ca718da2a38bf70adcf4e46f715493c9341a2468bf10d30a6c53f0c609bacf9e9d0c8455226f68e6304c2dab7e053a23f50ec141af4862742ef036845d778e4fb42932a4531f6e850e10388f6a6de91c01e32f88759a45209a13ff2fddb9a1ea2135bd9885d8a061118979855a453082432117f8e01144c9617d90568bd4b1b153a1dc59aacd526d2667a896f5eed5fedb6cf8d8021642d7fd7adca0a706c39b28b43bec36435219566457e810e684958464b056645ea113fb9600c5cd02abd41309b3454b2cb357b3329db2a843ca987fd09fd41fe8efb879d25d9ed9750d1b0cb2a76acb6b37b1cef83c13fe5f2bfa9104698b06a28a582065ede5e8f00c7954db9d9ed6ca075551912bbd3398e4665e6941a11179108653805e4f54d425dddb62865508b0c47d8278f320968f554a4ae8c5d9df8d0770462120350b2f996bc9709601b68a98b8a8f6b4a7d23879f0efdb49a60c39302e21fe79d6d67d1e71e6e431b98bb15bed3ec77d2ed0e22e5799013edbba0aee6078d1375b337ecc7f9137ff5f6a4daad13551d095c41134cfa20162a2e17ad48c02eeb229957145b5e6104cdbb2fced840c1552271317ae2d108e3e37685f1246f8b735865af9f5cceee4bbf20c6d87559d0a09cce3be1eed2dee386af11edcd648f5531de60db2c481385d970bc0706ebd06682da3641bd21ab2d7b9a08fee9220050fa98b29db0cd485de3835b2fe8d3abeef2a9101e51ebfb2650ab388b1091a82c0ede071b7d52c9cbfb115b02304d74267c16bd1a1547e26bde8b52ebb601711e7ae092425ba2d8040fc6ffa243c449eeaaa78b7eb26d39b329eb80ededf382b8117ac8689e1aad0c1d3ab616436259d931d1d154fe57a062fb0189e84eb239026859e659018d045851a6ad0a71d548a275167588429d8064be8165c69fbfd56261cce579a13b8c7552d2b705f15195c78597367559221685c556f7fa18cfa5e07f7bf01fc74a1c3cf90a187069a3a09bdea4ca7a9e99396a0f116086d0ba0b6deef40e7650acf3d3926b81a0d34c111f2874096fb3873230d9b034f0259c22745f1597db2da57e5d0a4544c166572142439f3066c3b387ef2008dafe2ba923a4e9e70bc19083430eaab4d13075d0780f3ac101ccfa038f1dacc1d0defd21514a77ca97fefa081b647aeb7aeed91d5a309443d354e74e93602a4fff8a064f7a66bc0f4f93e90c1be47c4940b48f0043b92af6abffc211479c664cbfe06ed6999f01a315c1a3c25abfb4f6a2355ee04d6e458b0c7ab256823b0ff49d978a437c4969897f987e377b266ccad4d9448a11f6cc8bbef736d8906eb87c679572c2ec2586a4eb0722466ef4115649622751111fd67253e7310fc1640e78a57c4c01fe073f5ca28c2688a64ae0d464d4dc9e099a8c16979303502e08478e8330779b5613daef3e1eea588f02b0697cc54e1617868c6d0c09b88bf05831345db934afddaf2a4088fec64814ce12fcf23a33bcc25b8ece0dd4b82de0a74219abc80c83605086af18a3a4e8c103c8a8ac5d915476405cbcdc2fb8f4deb68185a8254c14346bb115701fb8479f183942e3c9935debb8d161b287d82ba01ee2081812e77426e1fe2cc84f787225c00cb9be9ce05f65844605324d2d12c8df14372abc6154f539a47bce1a9eddb14f9d0545194c57332c0b890ad9905091063f8664e01743ebc6ce4d357d471275866c79692577c6e64c27aa8cb067dfa3d9a8564063c4a1d16a9671e3200e8224a787a33a205beb627d2f04710853ec4d12ce6f987c0067a2b00a0eaaa4cf45eeb69c997e3de847fb899fbc4997be9db168f73c4fded8b9ce5dd86b5a19ce6fe717ec85ad40fca11a35d28fbb3ca3e714f78b5ed76f6a14b0cea3049afe9697503137a6043f3c94e6fe52f2b60d930922d8b6288b7f924a4a4063c2742f1946f71a7d1bfa94f3e1951126d891b9b3650ba90ed414ae50495baf69c52484bce7d9a78a86632db0c1ab61d5d82c1052bc56aec14b73442580a3ebc3cab5053906601efdd82bd942f395fdadd5a774888bcd1893bb81f90157c4a8c21a44ff994131edf9b588bcdb404570c791d6857604f3e2025e84b2241594ab57b9711854c069036cae42eedac73e47e7b78a1979a92c0a3fc8771ceb79da9213c2baaaee669043a4578e213593d2a845b746e52064112719a47aa2977afa5b0b9902c887c2980bdb79cdfc013fe2e189f286859b59b4393cde058402d911abd650a9ae18c0b9a47f1fd2a1e94745816202cdaef7432b62f471aaf5e5d9dcf6e733a66925398a8c77706f6d014b4646b18432ff23193a4bc938a66329d78ea426094fdb635a30487f3cd165af792c8120e589b1269bf7a684744e2a818b8ab90246c6495ef07c6d722b2aa86e1601c7ed68723fe45060ba90f4558e9ab6bdc5166f58a8ecf70fe3e912e5a98efc3b77fd587435de24e1729cdf2c7df5ef70bf78090500cdfa5e9a0a2c8c53e2d3b3bdf4ab55c6db2e4d8556f092c4b1b06b83934870739a73b466f31b33cb517a94e70cc5870f76b40b885b8db27c76a69fd91863b114ff7ac673732b5a27618020bdf68dd0465927e8b77d4d3dd6f84815b83f1ec0cbee0da6385b61e35b2b5a159bfeec0810ed732301245215e38b7c6b43a45f6a88a88f91ea2c9a110173d2499f2ab41890c9dfb5a4f930ba6a1fec219292b74e81f3adde5cefe154388cbf2e237c39d69ea726cb3fe9bc5f40e53da56123a9ade5dfe23593299e39db04500f5a65169b38716a2813f92ea3b2d17717a76ffe5f9cd569668e1868c7713516685494391b37d68ebe3d26570b9e261a572183a047ed57d89c4d4539c73fe16d6a1cd65c0fa1e56418588378a8a29387faaccb0172b6cd10a338dace4c2debc630fd2ed3d1127b7fa1b111c2e97c66e73a0105195f4dce3bbfb864b753ad86fe3315a256d286092bfd0989309dbd558fb68845f62d3a0487d24da669937017da0f0bc6142b6789c563fda6ad04fb3ecbeb94969667e67eac48fbe0a002f4a6bbf986d2e272488d54553fb5f538e3a1dd8c5b62e35b22aa33604c5e00bba2aa4422c6e89999c630d85588c29561252d09c49ee700a7dd4b95836162e100fa340a7318bbe4745f43043e6808b3f799a4d4fe75998e8b18be5a5e2e314981bb613445c9cda1749423cd55be5c5f53a283b0480bf86f98634a152850d82d7ec2524dbe6cc2dc2dffc9468e2e7349d78ac149112a1a35a0290a1384e3c889b81fca11394180125e3f415066ca51b2b34b140cd1e53a9b12cd9a0fdb1ce98cc259276e0b6c1d0315f03a02ff96218137b900a90146d4cd5337e1e7bbcebcca88ab54a81b44cc207defe6fd1594a540aa261b919ac113509342c01528f745c45731ab4e48c46aad19e31c1bda90282b55aecf70a6147b3e2d84972f65d3dee829a779bd20705a4d509eb0d08cb482deccec079a3b919949119d5359617a679a3ca1a96180a254d2f423a74bd52b7b7519cba3329f834b20ffe8a772d828300d1f00ac9542481eab6d5ac55d94e9a3213e716346fe4481c342208841c4a6ca02640dd075042c81e410f788fdc539a1299ff30fed53350e62b81442da220f7aad3db28a73419423d23a26290f74f1af0b61f8d891663c6fe0a6ff720ef8bc2064f6f002587246a8a8efe8a0c22cc896dbfe1545b65c35ba7acaf4c28de85bb2cbb81a1ee7b97b1d208bd13faa6f8843e55bcd3a8bc32fc441aae53e5e41ea26fbb4dc8252cd46bb6edb354f8501589c8787a55c53430bb78a65b1137b05ac8fc65124250db2e28374a1e10a23b0aadecfae3f4c1771839e308adab1c8c57b7cd94130a12c7be598762d65af03488515ffaaf7bb902e06050764eaff4378d1a1c77265506cb2b43c14b2d0da2e27e0cc3ed2ff2b4918dd03654c56efb3757eba40f8ea97368b1c1c42d031bf89f522df9c6663a8f1ccc2a1b898a70a31fbe24219ff65d0f9022ae0bd10224491ab2742b8301edd839a0935b92b0ed3910de95b01fe02a2dc9e045a78bad10f89df66ece6f64f1be2422792377042631022c5153e748207551c98fdb1dea979df97d9748508e90ca9047729e5862ff0ae8c650261cc653b70881733c32212b272e3a855abc19120c69696f67d2344bcec52253fffec7850a7a52b1dfac8b707061410d2e1dc7e062e1cf5809a27f9c90645c15f7b5e0f8f1a81c95a64460e676556a4fb4dce0fa5688232909f9d93894f644c2f3097a5aae60a24093efd0eac91034ad2f8334a9f54069f06a4e14b52d9453eb25b2c1b11cc1b2ae40478f525f1e312db6a0293e1a9efc08c3e2edd4ca60134715e2426b39f60f46638e39bbf0266e2d54a515efd6a2658975b0adfe2740a0da2016c58f2675805e5b7185ca3a7bd585572e5924c23714e549291e6a5a971a05425120717c2b6468c04551fa2ef8fa8bf7d930536c3d2221b5171545b0f4ccecff640e6d94ae40a01a02d98ee1f1b976814f48f92dcfd1b101e5bc4c2734064a7fc2f424cf7be481a550566ffcb7228e9938b4d307b52d4dcb20fecbb204c0cd841c73f09263661786e21dd139f54af538b0208a166c656c29f6e741e34ad488f9cc48b470a1a22d198f6932d30493ef4cbc06db7c153012ce6fa38be984ef44799b2661723875af49d67747aab74f9a49308317bbe240bc7d5371e592720c0bd8cc4853155cd1413b24b60d8a2fc61c3965dd14441b9f393dd90bf6477d13d0274b56b06cf642b89ad11ea20d34af6b7c2ba56d99c25ab2844821d0b30307182d68dee73bb4b89e5866a2b5e606681165259063fd640f8dd58e324f35a12289e385f6ffb4b4d573ac63b50f8436a0a4d2ec19b97c8cdcada227c8e92c2a9a074be095e80560e89dbd2deeaabe7c329aa644a84fb0b3f82b1ef963a0a28d557d2d7f94e60855d5e2b5d25e4b3ac2692b63bca182f4292656c8b6e2142b168d226b017c37c458431f209af82dcb9ca78c107eb428ee64c2fbf4b53dba13e1dff43cc90491f788e9bed528f5f4181608f8203d953d1018ce8eef28f11db31c7b3cfe586c110d81ab40277bca2b0b68698f5267a70c7944600db7fa26ef51828800915e8b3d7c0f0723ba51763893183156665a113d5b93d2c3e3adf557295f59deea341cb5b84c8fad689fe4d8c2ac9b32bd9d5a5ebffd3789dac001868ec32610bfc10691752364c1b3c3a23e81568a792d43aa0f3bacb5ac1b6ecd0b9d92496331539b74076409fbc75e70a747a2804851d2ca34643b04105ba6574b98891b7613421ebc8f8065921d5d00c982574a3eb20aaa74392432073957070360e8cc23f6c3cb32eee4b875f602b4b61bd249b6219fa639ab67c526063b0c9bc824d52aaac574274d90eaedc165ff886853477a9e2aac5f610c2e3a695b6f2adaf2694d823efc4c79f5d4e2b6f419cfd6b97bf267c1d0649d94226de2646f72d20c8201fb41c324259f9cb5d5dc1098cfc150f0a1f64cc291330f5823d380890c4a404af9c44c9daef7bb346f09565aa59e11b23f75562fda1b26d7864581fb7a825b66aeb8007a9d44f5d8893f40b752f1dab17b917b714af2449f5bca22f8e9daa880a2531164082991b14c54867e6e7d95960fa726a1b06c2c3ce92d0df67bde806a73b4106d46a423ad8c655d64c14f0eab1592849f735c5ab16658d01a877181c837351db9b4861471579f33bd56eabbf0b0e269702371a8ba04caba2313a5e90fea55195d721713c6b09a50f33f445224437efa8dcc0cc07a95ada0e6089b9d3b2c0f6147117246586beccd5aa7afc4fe21b56e68258e541038620bcbd2316644807f6ab9e5e86fcabe0140784875f8a9107d131fa4de1f608c335a06ce4d9699a44296303661ea7dad0ec0cd42510bc2566a2d284dfe6f4d8a0c340959909204cb69428411a61085ea5cee3875d1d02adc27abd015111cfad9d6b99012226bfc1013b152d01270a716794420c8786f0ed7951886bf7883187cd53660d4af8725272547c5d8230d0b9b39e447bda7a1019b617bd6ac9a3a038640b7eceed4d4dbcc0189c4bcf247a779547736422b6020e31d9daee20074c86928639ea0e98ba806ff2a6df1a5dfca24d4a1c12ea8916721cde04cae639971a6f0b40725f185a8807404989c3f1ef4f43fdbfbd0d1a1f25e5c7c944d85982af986610a3788aa27beaf14a6c058144112b0bc872f25a901cf3903b187b6aa980cd7a64672c946cb555d91c1865408a088d5079c2ebdb77329c927e27ac2abecc3ad1bd7fb594834e969aafee9c9d23b76fde5543f2d73c544729b1e58c994fa0805521adba10b44e4ae57942d881623c19e8313fd4ec8e5464131230aa02eae461d5a284756f71efe4c70776061769f3716726a0c977161b7e89dd1cd228d0743ad8c189fd185a37839d2c9f1440f839d239e93f59a6fca05a5ca8c2f354c4a1d36bd31a5ee11849388a9c323881c0ecc6db534b4d71e12a3964d8a0f199710fa9e57e7d14860b8af5c3a2c11389dee61decf5898e4cc8a6c0c7c5d0058a69fbea3532ddd75663875743c1f37c7719e3832dbf49bf37efc2701e79ea401b2db23fa5d7f1c77264647384cd50547c63ef61e465d3d8a3803c4064c86909c1d95c02e324b1ad20f8a98aaf552619fbfedf938c3ec0a16bc8db3834c37df0b655c408557153cf5c9ecea0b4336f194c1370d91d0150de774e9ea434ac81c731fc85e4ce46acb99842ef24f0819eccca43cfc6598106fb35c8f24be374f6e1df2389d67da5c34148de073bd6cd770ec21cc070a6751f33408d7ae46ac315e985e823cabea754cb321419428692892e5aed353ff4f635a1f052eb15462965c94789ad332a59394a4b1b3d85d84c803bb49b55523d2be84d24557de12aad69e6976be7e526baff95822ac556b32918d69032bef0cdcae704ffc1f3d8fcdf5a4042f335acc9e7184d8ab384fc238e3b7662074ec3d2ff50dade69bb2845c7d72cc49ea06f38ac288f8485b5f4a0bc6fde6c8418c92fb01890da94ba8fce2ebdd0d7d186cff06d50d93c30f6478764cc567979bf6b453f218d548ffe9c23b773c0070d0a8dde0d74cea0279ab47fe65c37a0eb6e9903ffae3cc9a59f8e857a2a298ba9ec61f0c70886126297fc5040166a2eefee03c39db89c26a0d3fc7ff06062ffbc82d49229295ecb9d4653d9335294b122528405e2dec8815ae4539e2bb67309c6f6b1927f8c743d1d2db89ca501cdcfec1329b0d56f67314f632e973b07ae6f163725c186bf591699a23c245a34c15d05d6082eb4a2e8d02d1a9fa1a1702b2b1bbf608ce8c8029213e1026834cafea42b1887d6bb98f55a9426ee9bc0b1ee72d1a9c6682235c0f770d2715ba601d13e0f99482bd922d3478dd7db5d6d0d0aaa05eaf9784b6636a88346930ed0a61a9c17787b85be05ec54edc402a86e9e7a9e0bb754efd1714e18cd4b192a4d59a1e33424c63d0646acf4019f1c8b964421ab1a441233019b085465387db768c789174fa773c6fbd9a436bbfe569890d32327cc99295c5789e128cd12a893457fa4dbe60263a995423b5b5c778a13eeb755d19d1b31f2ee90f5157606aa7b3b6bed434820f99324feca398609e4e3ab1128e8a32faa93bad33183897c0ed7709c61e4b16d094c12b62b9d9438097d8583282ace52811e47985b807e716bbcfda4d69cc8e628cddeabf9683f238d09ee80a16f2a5c030baffc01228f02053c83bf600f8da5368595c46ecda9a6ca08da021c76bced0a21ea74d52e43c1483fdf4b269ce513681f959786bae38026518b0e5fbaf8444929a618448397f206168c28baffebef94956dd939188425489c49dae769088bcbb63282880c8c9411133c558c43f1d03eb9dd15189ca2ad9100b933ec2b50cef0aa5e934c8a674e5951d2a863132725295730e8a137b907c43d9362a384da7890f0f08225c06cbd144670971a4af2269d9b3f414c09b0ecae77094a8fb1ca832d8b0c8869ab74613411bcf3b118182590b4654ac294f85b9de73186f59ecc52d8db1aa02da405f9a6c37ade1776a0a3b9cd8f182f5ee1b1f120b0b4605ce885f80e03e742ef9da60750bfa51ef7daccf244ec21fbd1e1f5c044af62733c65aede7a03f99ca32067d6ca1a9b1d84520a179a8994a1aee3b430b7c6339c3ea59f0b9c8593089bf3dab8a15b72fccb38af5767d2593b0764612f6ddf86f1458666f1f70625d82d67b410ab26c6e85e01b3e47d58aaf95e1988edd041f4fa4c75c34c20de5981f14443a86305e23c1b07fb1313be28e3b03f3e1756338ba9150088ab454c0313fe8161f5982ebd5d99780b9c2e53aa8634d4fa95c6c176bb8f8147c24f6653f0c6923df2348a1387550889e11c7db6d3a167545f8841a627cba092dda16acc423e676982e80f4f5e8dda2908621fe645e2efa9a13217a738ae4cab0edf83d0d22dcfe8808ca56254998b5f7f1c7c36844564e5c9f9512f7a87883f19830665936b8ee87eb2b1dcab3ed895fbac7e711289a8eed4eb11c1226875e51f0979625c07c5c3dfc013dca563f306dbaaf0ea2fcef0f2b9b1ad9a0b0df6879abafb6d04fe6039ebf2e865154dd761bc522f274e80a3bf9420c8d60bb5c9eaf6e2682603e7ef1129557a8ba7e7668618259bd17167fe98a8201432411e63881ef850c99766637b5588229a5126e0d3329aba5ee8b115c47ef46bbca7c442be60047e3c3b4c3de2319951f7925d384f95b3a4c55ac4bcfd9d9fc208769bc185d42d4e6c337d06ee24ba4818b8ed0962b52b1860329d42cc58f0ef6167ab0b4f1100ba92019a60782b097d47b3916bfd18a552ad74e97f6890f0678ce7e33497de0009fb641c67e37f2fcc8ff2365b8860657be5f4c4a70092b662514a12b403fcd6990e92d9da2a66dd38a98a822acec8dec8bdde4ead097d86402ec0e6a56ee2a3dcdad24101696859c6a9e8ea1f60253fba9612c38454acad3709a699ca47d1274905fed65cb035aef0f2b57462b00e4735fd2a34fe245b15eaa674fb948b64e73e22ad0367d426ca637a103555eee3a3ad91cd4f19daec16b9f036b64fde67ae1667c967dd4a4ef2fa53cce37e868455cee16d83279a8d4f0282d9f0150796b651ba053ca0c6b454960aad16a864c641b26db99ac210ee1a2798916c7200f8b26db91d085a11c6c035312dfe7882c122201f1d72a425a3c1d6754ada6ba70aa6a50225f3a880a1275fccb2d623ee2ba8a1241b57f1bc488ab55d85b235196f339b3dcc509a938c5c98db55b5f635062fc23ce6523528b6fe308821c39e351789a252950fb0e3a221fc12f389bd69c080bf4a4c288b06c8eaa22961ee12b443dd264fc69541d26929384cb19f496fc0174a08838b725edfe722a9acc6c8ea0270f22c224e0ffc4e924a8ad18ada0b0cd8521c47d54a13563929b0e5aeee131d3dd00fb1c6ab81979df0ed73bab5fc7c3a952c17fe1f601ed095203cafbdba53c8cdc2b2f52e00bf3f549bd6eaea6f4683581f5da4a34b230256ac147b08589327e343889781a75e1d4db7527f7220dff9707007f093cdae61ef059934a1ed739c3a512ffc43c7aeb598da8ae6adce0ee12c40015ec55d2b70bb9ea19d5446b8f3f73e06e90a22844d9655ae432bfe76ce96403b7a9daa8d212242765b0c2fb3ed0204769313cb3582a0a1e05dd28c62cf703462a09460ffd565bd1cc8e92052bdb0777c6c0f08ef381d6c63d85a6b789fbdd904258d88e44d577062412dc444814ab6495dd7b0de24603961410a14dc6d1157d5b3e4fc97fc240be39e2efb0ebe618320fef83d61e9e513a080ae924d37a1dbc2c4dd1f4a4a184100044f42566e7146de84b4fa63e4f91960d94f6f88c9e9587e7dd4601e2ed81b2de2a81a831f8837694862e02a64f60f2cb8848b2fc12ba7961042b2a99343f6ac2884add5ce523070230a46fa12528cd792a4a254ee200d46f579d401a1350e9f99c3d3d16812687f10498992e1ba98ad47f4487ad2404108081c8030e9b9d9fe99ad0c519d57669b663034eb119126816e38ce35e7e5fedad15e893101d44bcf46f8d71cd65763a8a2eea930f9703dbdd48103370a4b8061ae60e48307b1460c31e5a58321e0efe7834d5b63d49e46139b547964193075abf9b2d97951d6cba698b0b4931ba93c6018a060ec163f83cafa60dabfd09707168189b519f3689461a0d45214c6995643798dfddc0a12bac90bcba2c94b09e439cc888aeba8110db89fe48d20e954a3bb3b4519b8f8b886001724c5eac78f81b714b9c7617cf754a41f2dfb6379764632a54785ad6e4dfc8be9f3213188bebbbe08e38dea075f9dd264e5f17de4f738dc2468364d56b968313265009132be56e1223c14ebf00e36271288c5efff2486801cf7eb2513c2d016960652e008c0e3074f7cade9665008034e06c26d532a7044838690ca85935ca987089c9323954d03467da58b00195756bb48ff857a398a38d3dc5f22351ce59f3085e293e24ead728fec23b89811b516af0b7aebcf44ee3cf674b414627e4e5b274fef83fa0a1673abda096e86c0006f847a63b9b13203c0385e0081ce9025078034712a89452848cc07d704129ac8f5cc90ce0ed0e17f5505e31bf6e1967f87a316e54951396b71aaf4a27604b7b823d935673243fd81dde9ff7c2e0150cf2e00382ca0ef4111fa1c94c12dde710d707400923676852f940f632bb667ca21c2b4c312b9d3daef6f86a21bd530747f4adcb2911ea9588513428aaac412405612b4a1e1cadea8946c0c494cc6b6bc20a183112604cfaa6660e0883d3d5483cc1d389da0842e9681605c0c316f027ea4efb4cb274a1a693d396e36ec02d17fd3ce8a89c2020002990ebcff9b1b64cb2eab707919a5c4adfcb581b0b8ee51dcd87c32295b5ced16ac8ca0a68c7577420ed4e3494e31eed509377aa34741a3cc0ee1defbb24c8a9e9202805aa03fe639031d32a127aa489719f932a68feab2d53a21a1582f864da552e858e3686cb2dbcfadb1a3a3e28e06b81914a10ee2b5c22f8bde5b8858dffd48949a5c41d88b68a67e1099c715f7eb04a5829bf0c26256e90a8c9f86e4a08a4bcbb178f27419515255af021debe4cec335cba344a37a3f6b18ead12cba3ea38355646306ed58ee8a0ebc6d1eeb494725dc6f2781859ba8766eb5c47e8b0e859845d5c2ed2c97c25edbc9d3926194224613fb939d93dfe3cc24ce3a8b1a07ec44187433a8691673a141b3eb06d83340dda759085965c1552a1ad23608c20628ee96195d3fed49246d6b0188fe63c9da399942325b3327135f2e0da1154e2bbdca6e30a1b93dc9274f4cac007d83407f8c3b9eaa71582e9599c90ba3b7d6fcc3da1806306710d7cad28e6dff8bb4dda4e11ccd2d1820e05c9a0719e8a1cba432554883ea6985074176624324a5ba1939022982ee9cd735b0521c31be08a64d75fa3dff69074b0d5cdede54890d959a02f5edf8fc4495004fe5494eaaa5e9036d5cc8fae38e1e383e31161140177e7e47bceb8194a282844ff323b13d27bd8da19786a58d754c62b13b193a2a79c4889c083c6280175757508ca8e57519a621d227b4cd3f6099893a23688e9dcd110780a37c8b8ba86cc927e248cc17b4aa24e8f79103718ae675006757dd6a34e6b90c57982bcc69c03d55aa5adda6a30c9b1b328df238cdf9db46937bbbda6cb39b6b7c314e0580cf2db01acd47c758d53ae5e3d0d5a2023169007d0b01f442675bb29e4b2c7868a1e4f14957dc960696907f2bde1e71beb4f675e5714c2354f9b3a36c23614b4e806a5c0f35ce9d62790925dbe23c7267094c1e9803673ff8e80f0fb274d07ce9ead74c1dfbf4162aaece27e909cbda6925d6c8671537363a6bff0a8d9a050888d62bc61130ec8562d9386fe3b95c352696f1954fc125a0d83dd2b7467d4142197ebda8146476e4370ac7cf6b918f0be66963216b0b88133e82faada517519197106d7111c70008741d9e41090a3f6559b2184bd01f48aaefae208ebd845d571cea94ff420506964da3dc459147b3df62fca22a305d24ef5f77577d90fd3786e361899eafe96f8a2edb23a5d3382bb6928957f47c2cf66ae53a957fa789dada0d717f0a0699a14a084f67cfc0aa53dcb9d48810164433b855678b407aeff64e493d7b018e7b96a19c82cf49c48ad06d7091a88248c8e34e0a71507aae4a36831dc26d3dd27b407421a93a6cbd2943090d0e60b3b872e56e6b04dc79d11e1eaaca6d75868f3410b645fe721350464930c03371fb52d34075e05151792e7bb9ccec444f6d9a42c709b34c4c166e64b4b9ce7d0dd07b63167ac737ddfa9206c503d5c5404e2e8baa2c7814d8ca3fb9092cbd49e0cf4be611ce8427f2f9c4dcfd6a8f2032bba12b8632ba77c3ad443bd6d2f94bf28b0ae759bea33c60138aca0114040dcea4c72b2e76f72e81c5b1e7bf16fe49768beccf16b6f81d28828d0181a69d60ab609b514d03883ad2b1d37e850a96afc3074ecc5935d1c4188037c627ba56a883c16a580d1039c29e42837df70c0f6d7f13c698abfae41f6d80bcf690d2bdcb830e0a9c0d239472cebe75b5008c3330df0ebe556a2e0280510e9b8814b43b3058888fe52165661ef31b31e8592a21da4f59be8955c49c015d69914eb457347499e20ff9a94c3042e465a46145b87242382bcc1bf1ef04531c36e1928c2013c35412944886090390131268a980125241aeb0e7557eada9316ce55794ae7ce829d67631bd07979fdf9a6b20d35b1e0acd3b68653c1cbe22032ff0a2904398802cb03436149bf195d75798dcee5a39e167a60a116466757f2e5758603a371b1efd7ffc69ba8b6ccc6f0e418c5bd50a4c95d7372d7052ea5bf4a014de0bdb6430643308e2ed3cc436eba80916ab5515fc48a3c617aa6ef6dcab32a357553ddf226463a7e704defb62f7db377f1e566b7c7da48db4b9422dfd72408b402f66950430be5589a929a535d4c851cc3a577284b0f2431830c791317cb976bf4b654ab031c2a85e2089c0eeacd36c0ae0e15e95b3083b5383f7254d384afd64182313ac9f3f5143ba40d45f5b10a1303370d0ac74ef9aca003ce1e935990b979551355f10b12c57826ace2dee0ac28bee28db6222470351e7042158307d1e34b81f4bc659373086bd84f2942846105f3fadb90f4774adbe62604733d9e39bb8a375cd63811429b26fd423286aaf8ec1c1f084dc8ce27a5d4771ea17629cc00f8feb5a2e79eaee3e42698acf4fcb275211bd23e9b37824a1dadc16ec43472335879be969672e41112268b7d6db29ceab7c54cedd541410361f8446d0b148797f3cfe005e5ee14e52003737a5a3d28ee0a85b9b902b5122b403fc26fd880ca6a1dcc046c2fa1f10a23a4f404bf7d9602ecdcda3c9f8490ac3375b70043ca3baeb6ed5f6a4813ff95239b7a55614a064dcbd89eca21e79c961a79ecd99eb466d15b700e4c508086c3a911c53b746191192a4118f8245d7556ea3821ef923b72667dd39db981b0c1a15b9cc58afc89487c9aefec317faefa144b8431030e5650fa3a40d86b38993e27ba46d970644c1a24ca61b18427d042aa4aa4e7a13c21c6fd9bced1a19ce9b47719c7a498be94726429c916f5ddd3c96d424ce044d151c1f1ae4cd34b80f4cb9a76f30aa8e418a26d5f57a8e29c1c48c8940d8439d93baaac18a36e758b3c455a94bb84a4b6d0eb53111c9117c8c2eae121d91aaacd796b8d11c20db94019ec3c5ca76ae61be4ea2829534f619fac164062e9bb61fda47a1e1086329ea704ca3a21d68a3c13f21704c01296086d4cfffe222831b10cb86a09a18cc455b586928bdb4bd0a0ab0ce683a41abf01cba07bd5eea3653c99b4fb622e510e9a837924021199009ae0a41ebd10d1dda4db5e8d7328926b70f41328bfe7d43ebd705b3360da622c1ca44d0edd10c067dd6e19b4697ec96f307587b261d564ab2f345521441dd2521a62efb60af56300a1de9d803e79b4873568260149ab608a1e17dd604b5e4e5851cc6b5df20f73cc32de96224b22236975d805c5a6c6630f7e417d425ddc70dd9ad3c0bb7425afd01fb297125a99ab5db530b433d42244b85f648878519f7dfb2fd311893011ba06fe1f702645e2fa39887b84e06b206d53312ec96c61567b59f14392fff1eaa51cea458dcc6fc9920e4b0cff5a24016ee7fa337504ef575562983d3690b06bc08acb2385d1b27d38497d7b6f4ee3718fb2ed3bdc48db36d2796aba9430f80bd1e057c5415ff84dccdaade9faceb8da83c55bc422c8a39e563240d3a56adb0f935bd5b478fe782d1d28204a65801ebe7ce1cbc836b89743f0af6b954f3d276a9e2d364ac52ad994e8614bac2fb338f6c0eb8061891bab86625c4998f2e4b4a0cabee530a7d07bc95a2df566ff703627b904bb2992849f0a6b2439dc17fb75b73d04c679aa42def0b1180ddd3b7ff8202ef6741ea6cb4ae4d1d0dc58d352b80451bedcddf6bc51c34f45f643b7d9b2e5ba05b36ab67cdb6f2d8c34350fa3a44d3531f40d7479174e1a64c5733114263940ccc01340db22279feaa51c98796ed2094700348e92b354ebb637c62b1434cc95ca53d534a691bc7607de71932b295413b0b4648836cab239ff8d91c2958c2b6d04ca0b2c2a341cb9c360c74d47addad0cafd436ed9c2456617a14c3ccd4affeae0b833b1c9d30219cefffe3745a15702eb6791543b29ccf4bb024dad8a35e4bc50053cda0506d3a0402b3ead8543e6db6c53adecaa518c8db8783c8fbaf652640fbf49575181f81ca5c9ba3c73cc557a67454782a14b1a04548ff6cf9c8983478436ef4e4eca16449822675fe3094baa10056563eae79d564a92827dcf9f0b73cbbaa9a525bc604a8fbbc1e3245b67332393dc7dda54d1238d758adb5a0eaf4841b64bcd2f153e388022be80173a1e942919fa9cab9b49644e11c7628996157bfcd5ba9cfb0da3d8e075ef99069c573b31ad12bdc6f6cbc13d5c8c73737b9e1511c46996e4b519bddb374ba866dfd61e7eb6108db073231f013dd7faf140289ff204fc5dd604049f5d15ec00f12ba80eeca7a4a9f0aed700146643467942eb71e4249d8757d46dd3ea3c51dc86a5384a58e384d20cc21be398768d30bac03f6b089b8b476349771afbf7ad9b5fb458c8dafca7b6b15e5662a0b8f7624bde342b7eac557fd18bed85b5a414dc727bf1b0fdfe7f56d34f83fdf067f54f3920d00a0dccc3b8a4ff67a0622a9a37879f3f14d9e68854426542cdabba1260031efce480d6bc6e7aa98dbba238fc40ae6e0e170255de8e98cec20996c2167ba4debf9ad2772907aa2dc01dec4c18421839cf343be836127d0c6ac5fcad05d483c68f2d4294c6802cc19076d425387e05b8d2f6cdc49609350499ed8313fb78f6f5486bea9d17242f9a16232e7aea00cd496efb9b6bd3788f1ad20046f99444fb988df990bd8f4cdad1a1fa3e20a12a8f4b12188e2ae4ae9e692dfb7b2fbda520e4560700e4dbd12a48608b6fdc4360423cde1e9a2207d02345e37206e92149ba7c1c8f4c18d090afded9569fb26adcfd1792cf47e518db1de3820eed1740ed4f9a36b5f030b1f70488c48dd31031d486a7d6f6e38b6d467fb85f8ed59f04695b4a474e9e6079e58832ad85ab3dc204f1641bbd2686bd40d5d048e905bf2b1f9836af5aeb368f8825f0f52913367e54044bc9bbda4dc3e962a303250a5615b600f0b74edccae34617e71012dcb60a2fbcc24f065769b0bf0bced295be68e2a09001f3d682431233f48e6daa45e4749b7b2e67f325bf775215038f84011e88da3e126ef78627344ee00d8f16f9eee5a14b71248e23f86e488277bdab0de85461a8a4592c439add9c6ba2e0e3303e7e0280ac3f7b348b1b6afd5797e7e254f0c3a31a1e667c31b7f264a08ae0a7153e1519f18bb661cc8c1623974f11ad9b1e9ced7077dab3562389a50eb38a716bef78a83806bb43aea5e50ee5cd9d2275c1269c6a6084b6699e4f56fdc3ed1e0ac84d539c437593a2e162ca54c6fc0ba3c2549538987d3bc404d695e9d70a1a32b70640ae49d3ae53e0bec1f63cd87c4866c6a09e0f39b4320be4a7f5aadc3cbe00e590695f5933ee54e5af3d72e7eced431b0fac8defb7f69cd485059fb1df93ee91f5d8bde3a5f023d3906e1f5288470fa9b5b272cd08ff0ecd785c511d77594d98dc31ca9229292bf3d6de9b23967696070c5234f092f2b2aafc972bef67f9bf983ea2ebb2475c57632ff74fc6a9ae069e76b2cc60c81b337eeec3bb46b58b83e2483f0f423c197fb87a7ff4d6ba8cc3e01d5262ea22c492539b70c1f0b68a1efa8c71bea080c76ea22388056a6a8d58d656942524903b65b003138d980a21b88ea1a956cc9ed5b686ab27bdb5e1a455803a50eadbf89f9981f311c8269166153208d251a34bd1d231cb3f69f96ff39e939b1c5f21e5251066bbd3add450c08772093d2e6fa17ccf31b89b61f1f69f530d6b6a73b9d7f7429ed5615629dcc79d5da38fa7604549c7c3936ccbc82fe470a83825426278abfd7aa3b183067d622d4bffa29adfe131e39c5c3975fedc46b4d8486240127964798d8448840a19b63586bbcd52f5950f3644ed156ff409b73db62f3651a5e1bdaa6ded6312d7495b7700e0321fd6c7429b1aa87778e8e16392faeee9babd3adfc36a0f1b928bef08a2180c7ca6bbf9990fffc887e6f864863f00a170212e44e8e389290f47d61cf4c2964139947d6e374582161a603a28482825fdb8785fe5906c82a261fb7685b2c288ac0f1f50fa06513029da95ff04252731170a90fafe3c8251292dad53a464459240f6105b4cb1a58faeade338454a9c51a21763a7b955ad46346bb2725bf217f1aecf3bd6236844be68428ea2977666cd50d595cae6463551f0584cb7675f89f13f1ac5e57d5273d3a6a5209cc5609ad9420ad0fbab4ec9d206cd5b6601e973adbc2801d068360c1bb2105f68d7542b550f3cb6d8e180ea599809bdd44e6450ede7983d7a19aa7e66fc3e178aca7d7d2d15bd6c48e7654b26e9d7f43326f0ee255e20cbf929e4c8e17e7095d964bd725e56f93c1cca2ce9e0359ffeaa09b4a4cd9cce1933353766e332bbea2d3789e25a21ebfbb3ccf5766f5d17e2418cf9ab90e2fd9f9cd4cacf130863fe48391c672b56d46eacd00e752e37692c3bca4ecbc58c6bbfaa16766ccc67e26fc6b8b69fdfec30488c5995e3ecc50ae8f40518f74f008ee523325b3f1486f797d3b4fd16973c1cdbf6cbf31e08e93e815fccfcc848e53032e3ee10052af06d1aacd043b24b8a186f19258313a693c07992010b0415f6a50780b991e6127844b80b95561291ea8e9f500c9d19936b883899876d4bcad4fd8eed3b36da809ad002865583692116914c0bbd13d2f0e3f9a41945b456b09bc02e471a6d166e9fcad7803b8450414b5bfa5026c3b6e7d8bb842ec989d27a191f523c0e92886d6596315bcf77b4fc2b5b3435c09e1662f6594bca4c5f0577420c87ca3cbcd8efedbc3dcf5a8971a216718878a75da34dc2d6318b0283eeba784c206eb38e9482c4461048f69b2c0599724160d76fa50c3cb69d57895861727c68f29612feb7266c21f6389f44df87f4a7aa1dd7bfb3971d3f59badc0f5d7a28fac4f9dd34ff5f4b02d7e3d44678c152a4b03ee89c5e178412d66682db29a4924fd25061c6045d6501616150d8943d9384a3c9ce94d109319cf894853c29f9bbea7bf031fa701928c84d0ed0c59d04c97252ec39eda492bdfc4e20ff0ee0cac680a83ba90e161f781a1b2c88a9b8ceac600b1f2c45deeb18568c7c6a27878089fea4b858f715a4279c45f1ec3fb2fc74023b19b57f4352aed19068896224a7f0a3273e8488a763437121b131b435825a79975370bd0d4a3e0901cad184af811c810b1f6b61af616914b090e9e090b617e6e5968bbaa01b1349beba97dda4ea559f19cd95369af163ba9908d135332da994d38f349ed4cbfc852c4e8f3b5f639a3f6688502c0c47626e305ce99bd8cc8e4510ff31bda9b66ca30f7c0de0642f973794452c384ecf20d0180a6c55dfae9907b00ee21eec2fb400074106004259f4e187906eeabff9e68d03684e2c81ad4a7d019d532d8491d537daac7c106ce66f5150c0596469c227ca575105b62fe8649cd02ed9049d6478c7b2e09ecec12a52951ac4f6e226002557f272699e83d5cbfb345b66dd1a936ed493db80a2048cc49ee49c8ff9191b5b26099713e97721315176d5ea6eb94a090d0a163fc64dd07544d37c8f3af7a0d14a3f92550a9818f54ea4c9c0c2cc90b543fab3b4a1cca1fc10d2d11eb4c41981f7db51db89c1d4c50a8d3eb50422da685925630843d488feb372049e031d3bdf29be2208262fd8506ca422ece31f5d7d665afaf0bd58ee0c3039dc32314fca57472aa3435d0bc6bb95775235b777d20af1ef0e1ccc4dce97a3bbdde09637bae154eb412ed52cef969b46eed276eff8d004a45def51f4c442ad1d9d1f6410bf2831fa3fa4d022f1ffa1d455e775fd6085df3c1ad5b05a02522e819f0dc0a13dff0bb27acbf7e75eecae5161b47a4b0555ec6eac2ba89842c97173e5b551e157ac2602b3dc8cd4642f7c1a29c4219969df06b1d5eeb83c25fdd3a22f9973223d284e053cf27ac1b4f6cee584d635719767ba0a6e6d105dfd72a0f567ea0df181293482be9009f82165bc519ee51fac338c29b3f51c56483cbf0449e09ba5873f478c11e9fcab25201033c62404eaf678f2b10c8eaf2c08e51a8aba3520e090f48ca6d10cb8ad80e13191634f73b9191a8f1a5f0cf2fa1790164668e781db1d855d78802a2d4fad2eb8c53a31a862e27fce24a3dfb961de606836e5e6475dbc00979fae5a7bb1fa9a2cadceade8458e7bf9bfb97fa4c5a994459b5bdacc9dd9b78270ff802781c54d936d9b173740eb7016dc16eaee914e6aab46473ef235a2f1d946518fc28be25babaf0f665ee373a26909494f2513e7191101212232a133e0bb55324f115f0b079c6c374611c88970aa8af5356c989688eb6cd68c49db4d72d667d7e19d4820b41944aa03f3200c887671d3138d99ad3aa78123bd3de753500e2ae7c0b33273a6731f2fbce96d64c6163fa8cf221c3ea9271dd24df4103424f6c125351657b1c11e092a07cf4212c54140a0f2116ccea23d7c8854c8cc11bec1f74c2a448dcf4572b910ddc70fd3932b7d88190aa991906d3a314c6a49c349a6f7a98e575da5c21f9b0ea616eab72e7cf33905456ba2f54f859f5a34a65b31380c07f75d936d48d231eee96fde7355a838743dd5c8ae88ba4f1bd1a27d87a8123c7d871bf748f74f97ba491889d28426b2d6eeb5fe2099608363800dc037478a736a3abad8e3001c44815fd811950bf3cd763a497f430878de4fffb2753bd28d2b237d4e020444836af284fa733cd1d8fe75baa43500521e89020b4e39da2efc6b0e60f403ed0571a932973672096a6662cf7135c8ee8712006e008e03551fdaa014826703fcd50a290290610456e32f3025c5d659ad1a3eaecea58b9203865bfa1d51ee0626bcf71651e2e571a9052968fb1ad359e539e540ef6aea406155646c342622f9e8bc65b58d8c03cb6f218ee3121b449a3b7832e710bed4e5d18f05289b9e1e55d4685642542a6224202d2b6d2bbb1a08b8ddec68c9bd60691671e103937f33630f76fb218bcc89f541c87c24ff15ba964dc422e43516ef8015179b0c57d2bd7a036b6a1c36517b20ac63977d07e6c0213ad8c70db16f770e938c5eca7d7107e0c718aaf7421a91a79b8f433a8613a2534ea6913f2e9f17528e26347684580b0586073139f5f32145f8259be9a4d226cf772745ce3e395dec4c3bc099e102cbdada925a358bc8cc8bd8fe8171866bc52bd1430cd03a500fe1fbe2f9018dede592a76ab673b704a028b03e54fc30f65d0a621da50f8391ea38703e624f0f7fe3e43e799f64a53c8c3d3aeb6abd3198b3a7ebc1eab7b067f6b4ba88789b8f7fc60fa0ea17b0d1ceeaaf8e64fe22662e5fd00d243c6acc6789a8280ff1283399bbdc5f1a007eb5348a9ab6fa084fe10bc4739c482e8be3efb6e2011a5faf3def2892dfbb914126b14e1d7595f5d13e35b0ad5551507cb43e1b2a992d29d148ad81b5d695ca6fe33b99de27c947e283b9f14678c56fb115ea16ee4c6d381386a070163eecc058c7fd39a0d9ad5be039cc741c0848eacaa592878697caf8a58633f3800f9850bb68b9a8afbc03f3d16761df15e00768089455ae8faeadfae16dfccbc9f75bd9639000dc9b70d7ffb40f5eac1bb418d00a7431ded4954b5e01d20766d967726b974bfa5d95556038bca8d9a16f4b93df9323180231f6d09bbaca0a74dec20ad74b172e2809b23a29105bc3229e24971390b1f35d2d222d4a503eaa3b0c913fa576af7f2b78f88e2994d21dcd12ea5886ddbdab0c2bb6bfec72e5c738c0e2753f847301fbc6037bfae55a9fcc7b182b91aab55fa60404428de1c261f5d3457e67e90d7d181f564740bcb02d032cc282f1d5c118c6f30e0d846916d4f2c29446509b01c1c77a55ae303d53558130909e6eda5d28f7b06235ed94b4520feebf24ce2090fabcb0c36b0670a37148e93ef3c941a5fe45a8cb38de05714404c327697e5f060a5030115e5591e79c2690dfacd3bd475de04690c390955b5eb2fea578497d098eafb459bc977645b3b260ccc912ed3effff0ad812bb7984dffd921427a89e6ffecba611ffc0055e833064291d88c229e16d066f1631596b5e5b59dea8d8627ff4debee28f5304ccd580b80206179ee117bd0f6f082c0793f7017edf4fc4a3d78db1ee1e017354cc8c0098a1608ce4ac129aa58625b811c46c2e8078ce66df12e14c67e66e0ac141a47b41aacf67419ac8f49b5852646d2c17b520374e832739b75528d3d9999b4290e98cccadaa810b96188247eb532bcf0155043a7af11c064e88a9505dcdf2694795e65f50ef673f88bd1690320f7416b25fb5088934d4bd8e2ffbdceb6c6cae456803dcb94597470e8394c88b1526772bcb258d5fa6800b9ecf6ac1b61d02fdb789f06b6ab8d970d76878931efe437f5febc616c705297f4de1261e0cd025dc1c2a9cba4fbc82dc2201391108a37eecc5d2580850c4e215a0ffb9c0e453ae63e5f0ed64bd7d002ec9f68f8249ab6417caa0ec64194b50a166547756fad760e7335ef1fc7af7d9ff7afb684d3550215189d1fa95f3c76480f99a89c6d788d83989263fa1427c7d4470433c4a85a620180893c90e1d46a0c9b0476f1c65a3717c1af5c8a106264bb95827a8040ec495a2d4a0cda746adbe713e06dc5031ea84cf73879f017bc3ee00e87d91572e5b7b17736990ca3b8dce443e76b96acbce867c61f9da4e78e8319de7114efea54f6eb420ea1b95da290697a8b904882bcdfdb9418cc60d2a27c23df63eaff831a3205f955693aeabb38d0b1d16b1535acd8a571a9b1b1c3ed84ad2f16d96c8d0c3ffde56e982ed41bfd055b9ae4aa027095f82587c3ecb9b9cb40bf5200bf0a2ef2090a0c222d025baa8b39e13648676550c3a1a18b7dec5f8778eedf1d6e225136d14e8591ea009e91df50456fd460155ff3c7ef91b609d5c0fb70a92eee01bb0681852cc42afb14f2a1a1f5792253a37a3793a8d571f494ce1cf18759f1a7ff952681dc3c5e44bf74bb874bfd84bd174c3d3e7fe743757acb8b32b9e9b5a4a9bb7f8cce9c58d3d70f2f02cd851df25b8f51e83f74b8ff172e28ec87003ec44c539aa4fdecb1ab0797691b8cede5689a08237e5e5747b86f819da6d955910b51ce782646900c0a65cde140e3afc5825938ad019663cfcd1e4267925f8fea8e318c0b93822257a3a938b7ec194594558ac0bbc11802c4a96150f4630eee2a6fdc95135d196232df42fc960e68821500680db0af16987cb9c0dca0f6cdb9c8535230ecc547055b66fe03f205a60633a6ada30f2f752532d6e233de7924762f8b70265d944712697915b57e4fc25b1781cbe699823913c80e184979b1f1cb37b073e81fb0e11642c0061aa8dbded77666f35c6b1f989ba63f7b82b1e34908c6bf407dcdcd7ea466b22b1b896d3711eca603d1ebb4f1ecb0d6fbdfd3ccfdb5abc9fd196c70b67b431dcbacc98bfd7846c12c4a8ac66ccdbee22fb738fb7ccc122bcc6ee229450359a13572ceb0e02363484019df2395bc83d09f7d4f5b8e7932d619e789a07e723c9f8b4762bb7e4097d1f193212ed80fd7ab3c20a470553b8f4f1f9752c26f8b3d436a542976a55185a1bb938b03e9f1800b8393e4c67d23cf56449784ba19efdfca771585e4977d963b18f889c8bc386150b56c10acf8a8da15db4927e0a16348ba0eba4d001b52dcc517de996a7145a1b8b28116f495bf1e3b2472e790cebe7051e705605c23eacce41376440021002126c0448ccf671d7c6748433cba1a5d6fb2c859daa8649fb41f81f9217435e07a3b44e61e333a896529c5e8221121c0e19db8710f2c9a525e287c75d9096638aec2ec8a84c2a7ea6312fe59758ca03c90b79a52f01335b0a87b718c2b8c5439694b5ccef2dfa6071b3f8103498cb06af1b54be59caaa5d61c1cc3f51ae76cea70c8936832ddaf5799568165719c3e414b8a4bb1d4078aa12ea040ab9a09a086e8daf6369aa66f240559b30b354499fa2c71807216179a03680756417d1df503e60620b3a27009f9ed32f2ab4162fc7be255bd45fb45f54c8eff52380924d733774227cb83e1960805d3255357d017186fabfd425a496a82bee68f3a53325e3b0799829097c513d76b5fc22e7b97f63e164c3cf945fba3e324359cc1fa0772df09db03daf1d4def1656ecee0068687f7434672e02375f8007347f187a37dba35a4087fceeb1fb06afe0b03457876ac605b7788a3186f9403cbe38a5c7d9bb24929723dae527f2e2f055bc3f211437b28765a7c19705bdb226fa178779ed62eecc83634c452237e77dd1749652cad3cf2f2779f0b11fa003a20e3f12c9968320eadcd474e59999ea6ab7aa32bfb25f2625809f43c3952e5d0a02b7ec4f6428ae3d06459c14af8fc1983deb8e2634abf4e87d9ce82f24e0094545abd02cac2407291e42a16ebc2f4866e01985ff1f60926786b85e32103d4c19d8e37257b4a3849c82d981e643b0f526f365da1d4033d728003e9e9640d10bb779d1b2ff77dacbc9458a4ce2bf855c38be9690b16b565f06b65313104b63d8c29def3ba1e096e3f423939a2887f6a52f8d883fdffff597e34f54f96d76a13de4afe8be2fe5435b9348202812f096407b1a6877928fb007db3e11ec6c35f2228a249e68b9d452e8420e5876a4064ba19139c792689fb5451f450712d020ed16e12d43c92f778d38b2cb61f145bdbea53b222b5cba7f1becbebc5dd7805fdf4070cdaa5e4b03a37df5d7cbe49d540abdfd0d922b3a5f19bb1a9119c95f8daf1d994ec6c1a159d3a951d61054bb4daeaeb447510255d496ccba9d57f9995e4c01b43e0234bb60f5a21e723061584f2411ee56d7ee8cc88232537d19b50a3f15590196ee23ce041240641f7174e9b4084f679b1320ff18a87f30ed1151c6546348d9d353d1a4aa2ca0a27d1874c23c32f34542e991b1301f4d4a797825011c28d6f55baa2f19abb447f762821a90ee7e2252a3e3c11103ed13e19d1e5a8bab347e86f47de098d4d9de6ee6f304c7b9c44509c537adae5f44248ea7e3629315aca807035728d5437a08d3d5cd279f8782b8444d7c22e5766758e6127ed99cf8ce7f585e85ec70b18f17e09a09b22976492496c6fda980bb3c63ec317379b938add2019e943399a47806577a41a35be67e882b65df8fb76702680dd4262b28495c2c1d8f54086f9208d9e47c12e61b2116ed6f0df792d877a1d52e52df55fdc7a1d9775acda4141443b73b44d76748f236db27e3e46d5792e6b808994fed30aa21b304fb88cf6e022b5caa84f66804b51499e485ed082517035301ee9e218a4efc76d3949e4064faa24146206bb87481e7fc963d7734d315026d1b1a5f6ef5e68241f79a546cff41e838c133b114426eb6fe61e3a9ff8deb0fab6f593f2f699739b9ae91c2c34bed8fd6858037684437690cac2b9003a94d72b45a13a97a289036206662d72f333b2c0604ea131ba058200e33930ea57f18833e84c46702da5456b6282a6cf9be462df67b051aba6e131daaf817e5d24a32bd97c455e6eef544010882b33379ac8fec30474f1b31d575b08de3b06d542e7fcbf81a0b3e24227befbde5965bca2453140a9309ca091688c5e69c57ee18948bf81b89e5eebbffb8dceebb973f3b706c2496eb0e0ffd65b326f6ed62703f8fb19ff941e6c7e6fbccaf4bf25f54e4bfe45ff9e1146d04b50447a281fc2461f6d13be4f4978e91d04f6233037a877c8f6a9801fd92f4ab102dfb57090229c133f571710432bb8464c7dfcb7fc9bff2c32ab4bbbbe7cc6772339106183329a420dfead04e642c557509e3f4f4456a8a4a8c555399a52c5a969a9a87d3b7d3b615fe4b47ccc7698be6e4efe4442ded3a7372af4139b71f352d8cf368539a962f3db20952d316e7692dca79a4b42a9ca7a1983c717653729e2a2d7bb80dc5442adbdfded3341068be043bd8e1df9626f4972cb2bdfd716be9f40e69dffe709e968e2d59aea0d453c4cd4e9822e3647f1e5ab2d7a92d65711e6ac49c73d65945a92d2d2d65bfb0ed27fea6368dfcd59f0a66d892737243cd20955fae204fc7775e5774d1774a1d0fb9e8bfba1ff2974f81967d7dc74f247b9f097b3d1221f9df53a06577c2ea7f7848e231047ff4d1642d63fd683bd1a53d081356358ca40699b04a2473ec12f2efdeb12b68e65d42dc7fcffdf752d661a61060b18fa5fb96b9341c54e4ef05797848cae4d8514176ce2967de1300a55a6b7d9985673681a9b5ce4d9b4eb3a9fed4526770d1cc5c729e86aa1245ae3fb3388f005c9ac78933efb927fafbde27ce66de629790fd99b77826d997322bd461282e4e39c9ccf42f66d2def39ec353898894712f65dc7b555ccfb5bdf72dabdddf273233835d9b09339b38c29109abdf89559e28674439633df893855d1bf8434f1b16eaa4d38455d113975cdb7f4f246b726ddd0b79dffdb661a1eebf0e4b230f73d8e2f9e4fe2511ebeeeeb6dfdfef23e3c42b7e5b6ee7faf6b99f40414516bb2887396eb414ff4c58155df4bb988b7ef744321f177d8a85ec77b672956b9a2bf7f60efa34bd83bec3e86b3b55facc4266772b75975c59b9512465a14e306a2dccb852840e5dbc50b546bb512506d40a94a8323be2a109a32aa899556ea8dc50b3259f017f64b17e0467debb8986fde8e56eddf0b8be3fc879c2ef17721e1c8f63b9ee51f32de5953b26a1233756497372e43c2776ce51c43eadf448cec1f967dee470c7960a12e1888823048205c2f737e23c383e4706576e129a5d3f26a1d97eeb5b3f9ffb16f7dadf16d7b23fced9778945ac105193a31cb2fd28bf653b93d06592d03c5dae3ebacf830c2a6d1481420a636e9011210183a2ab25379c84f145e66e67d074e5fc66dfced774e5c03978b4748699c3021040965524ce514b9445a250eff0af112b0026fbdf19cb6241f607b1c802a9c93451467f91fd3b1c44e54acd93e2a824cb0f6aa694a5945d480de65426ca21596f6c53503323a722d3c38c4c97a9ad0351aa747186154d98b0a2032e27aca04b952b4c20a9c0b990a794d243d439585c8bc5ddf2489b053d6710952b25c10c72b03f9fa67bba9a3ccee49993c71949d43be6cb2539dd444d1ebb4c3f9569e37e97c9f345983d764b0c5db2280335850fb2f923cc9e0fa4008289941464866043363f08842a27197f25f90de45ffe7a87fcd693db5fbb4bca1a8972ec8d143cd0c381a52a9be86f0fe8e0215f2a9630c092cc9261725add2891152e86c8b2746485194eaac0610d355e6095c4195f7049630b1b661b406ea8a22c72e7c5152e83941422bbfbd65f8edc5055b4b8a1c3ed30844261f6b57434c80d85c2156e70f994909ff38420cc45a66c766a5a5292486ee42f39cb524978640615a140048394232d8c4e60034a13998651e82f2e80819443174f634c91512cc284c91f9b2aff7811797c39e5b19f88c8634f0d91c7b612228fd2484b0e42ba808c185e34b5800c3064e37b10a50b325637c010c502d9e8b30697265eb6a81045081b6413ab6085263d8c81428d20648ec7aeea379e6003ca4a171ab4a0201be54ccb0bc29c20658523232b64a34c229a314951f9c205594c794236caa62c81d8409832cab872c60e3f4c911135354983072788f842831632f96153142a5e82a0a20919910e6a3c29030836be70814a26c9d090a508274ddef832840c491a4bca68a1aac8892b64f295cc9e9eb2a252224b93182f52c824912a292ab080810923ca90c90fa2306981062f9c686943269f663d12b72c6e2a81e94f6c9ab8ed2506555e6230e5250651f64bec033fedf33c24df6fef9580dbbe4f043116018d90eb9a00ebc10781057ed6fb4f2cfafe1681df13d6695f3dad03a20ec6302fe013996619fa894c5687d5a163e705ab9bbb5bd0fb3e91a9f3cd07af779828d3b6bd109abb7e0d4181d6e1229a90f470376c4809b2f623976d67a2ecd58ff3de6b5008f724c83279d3c48e89049fdc9d80eee5283f98dfe1517aded72010ed65de3871a4436c3af08359a6cca474f344268fe3dcbdd35816db7669655f91d4ed1acbe2123c6f9ba6f94b295ddb36189e87121183d73b3ed4aad5df86a020d7d7b4cfdb3e4d144267b3129c6e127fa04d0caee7f1fba40ffb23e7a0109aed6ba2cc1e67c5123c5bbbd59f4088905f11edb51fbf2d071dbf2c4408c5d65fd4cead4e979099b79f79fb8a84be58827f20cb640d57a981bfd719d4e8565f7b5bb3d7eee1a3b397e0990449663ac52eed3b6bbdc3bf3e2d525f7b593190ce1aa6da8d4111938cf9405dee8b3bc6720f7dfff0aa4a1ec307d08ec56c18eadcd6130fe1c746551ec398bc93ca30eef8b1e1bd74f864ef6f4bc7bbfe1a72e28eb7e5c61d7b0d185c4d714d1ca47d73c934f9c7ef84d15cb95249da209324d217f7ea5e6dc9e3e519251299fb5d25575722c9a22b8974258be455915113f635aaec1bf4d56dc25f26fd0c11c9468994ad287dec6b2f64b5e7f0101eb2d8ad4fec53ce820dbbd4f496795fdc515245758f19060c669e14dd637e6324322391ddfd528a3b764749aa89259597c41d3dcaa39ce74b53aafa820e8f2f0ff0b1f3fa0f2dd54f2f809239f0197d69bd9f7ebc6aba507ded2bb6cafe55fc78238cbd5e3e423e3f26135429294c1ea912cf4895a8c234de3c83cc99802c760f2ccba7e91e393920d934d9933fc5e692a918a4bde63e8d26fda177385d529ab0db44902feee84a54c95f34457cf4898a57a429c170479b69ac5c37f2974d72a435acf8486b8042812993531356c4a54a4f2fbb461e5f6397a133f4937c2947a839e38ed65a8bff9daaa5b424f46febd218ee68670f0774e23109cd1c84d11c78a03d9132b2ef2e4f79fc6c8360d109d21c843ba1ce17a11a46790c1f205b06717d62feb2d21e4b4b27b463aca5c728153da4f1c59d9cd248cad4c97946cac516aa97f3c8aed5d6be499059fab893bf9ea2ee7871f87047eae4347bfc8586f94f79b221eb90fd9db25f47ea1d926ac2c0dcd17e944a092b344c76a519bc49669156a46c9def775e311e3eb6e906fe3951dc917a67dcd195463a7b6492d2928e0e8ea93bb65aa137512c3f62682ce5d80c5421135776d504fa0707be813f4d0b774af9fa1f1e4c997fe407c99f98c7bc72c758def91c48770c431d2acd88ae6165453f8597c999dcb66ddbe6dca4ac5f9221dfc0290e26399b33af288597d5df1ec93748f20dfc713471c78b83873babb45283fe1c24a55fdd6cddb637ea1b56526b39ad75ed060251e9c3436243ea3c325e12b35b03fafdd56a5fe0815bed709873d2dfec9c9c56ab1b9a1bb3033053aed3342663bae9d14929dd404f893b5f7a9fa63119f36d4f15049ab235a034839ac6640c8bd2e95c51abcb4dd14dd14d51cba9e5d43ba3744da359fbaa6cb3f77dad2e977e4b1f6d67cff6f66bbfa8464196d69af9baa50f4a91c80e34d0db72e953a4eccd481f33f77f2010304b0c0210adcaad8109d37311eda50fd95b802043903ddb867f481ef9637791dd22fda8e1ac7ce2a8a8362d95544a4aa5a453bea6e1ef1dd48bcda969bf695ab5562bf394e57b728bbfe47b210bb7bfc1ee9e70e79444d839cd1e4e6a46ce23b311771774ee9c5a4e0e0201c0a81961e07694145df22b77541460648f7e2da7cb49f50ef9ddd207ed459e21f526f85b68da2c4b10e8b3b8131c81f6166f78ec8fea1df2db8886e42216ee3ba7d939d96c67e7e42ff963e46b4df23527f99a92b634f3c483d73b3753c47cfee5edbe3dd13425caddb7eeeeb64972917e235fb66febd7da4e20bd78fc7860ab789f2e5152d537fbdd24a92c3f9c02c5ea6cdbb635ee60e636f265ed4a4a43cce75f9b184243444243446d25a11c512265f6be885013e6536c8968f4fbb3dfecd367babbcf84f9f50fdbcedb3ba45336bcdee99b29ae945a3721359bcd96c83808721e99e55bc0791a4a4a962cbf27d2941e9aa2805125cba6b5a192b6e423a684c9f25fced35054923e1d595a31ee94781cca633ffd723fedd73a74987c29a54783fc20f2633e2e667bfade0cfd2afb7e73dce4defb2e4956c16d75c4daef594f831b3f7dfe7e7c657cad9db9170c4316fef0cf9463876711fa9b3843f89e857bc24200dfe2b15fcb6d24b63d0581f01344dcf1ceb727a6dcb43aedb4db9cd64afcbd636e16bbaaabbecd15ffd0279bd39351ffb6c579e4d62577bffdee7b5c8e5d13d89e7b22db731e9190f7dc7b8787868894803002ab83544555f5d4cc73ef342ee744977fdf61fcbd092ec73d6133efc3e2be85c09f6921d68378e8155afbddb797011627c27d874d6012a2f9ee4d9879ee899840f3ddb70c0c81f5a0cb674497e34eb4794eacf9d613c9ae74d28c2a1e924b494e4b4632a9256ee256d3198d3209bbe413c96e96b04be2a0229c0f9f4886e35b56f3a3749233dfd1e02126219a6fe121593561fd4c42334f8387e4548787383c24a3b8286eaa933435e21625b64bbefdef3dd1253fec92ef6117551659d6808152cd33095da6f9433d7b6221ef6bf0907bd897924637f23c69691a555d4da32abf0ac50d87b8e1dc883d6194f3ab6e9c46adf7fb434c4236ef193d4056d3c243ad6712baffe1a116f63b35e436380529abc1ee34835df38936a9dc3f7a14cdb78c06bb26763cda4c24db9ee6ef3309cdfcf7dff7342ff43dcde724f4fdc543f3c3d369c2fa67963a912e4d583f27ce240e734b2674d825c47a90c32e161e627205714f819611a957515c30c30c51b3996c464d58ff9091cbba4f01f72076f56750e5b66c4e4d586f692244145188e9b00929e0f04c0835a24613d6ef7205ddd92c63f1f0ac9ab0feeeb9fe1c50b701454fb9a1c8235da249d408c4bf39659a94fb3398e1b6cc4544caba97b2eee7d584f58fb32af7875d42dd6397e3a0ed4936ceabdcefcdee77ffa6d4bf2df56f485b52f763bdcafda3cb31bdf25737a5a24e39bcdc9675321a2552a909ebee8dfb6d75d54fa9fa69d5d622d23c078bcd91adfc1139a81c6765287de884b3e7675aa981bfb3726cf6744f7fe2ef1dfe2fb7d28718cba077d8dc3d7d0a7e405f06f197ffdbace11f1bba68292514f7392905815a4e577baa5573dab6eb1d725de79e273d277f6dc9fd4f5d47bb6e7a273bfa9f045bba539654eee79133c31d79644929edf7607027c6ae2c335126ca33c430e5af003cb1e33cf4f3913e3f1e8ec36ec30bfbe5bfe4df503eed6f82d3c3edf7c6cc91e6242ebfed95fc767717a51ca341a09e3e5fb879629710f726ca1da0628c0acae0d2c20eaf1e8a60228313269c80e14fb3477e83e0bb79e477b77fd8964a87cc81e28e3b3b2fefd121933af28f7f5e8cac967039f2e28509265546514e6414815942768feed1efee7f678fd64484d5194d9ebca08ae88a0a5ade98c10ca5324bb4f0babbfbdaffc0dca22a6a8872250a2382605206972f505190a2b02246e632647bf4fdaffe5b82e792df329762bf70fb353b9664dbf0bfc6118ceff93bcffcceb9e26e43b6b75fc522b3765d57f1b855ed37edfa0bcca4324cac889e404952e65102dd4366f9d2a5cfcb7f49f1b4591d042c24054290100ee2af234aacb070dfc3e1f774c4a836354eb3afcdd7fcb7d7b4ed7df3df7e03b2e3af3975e898dff3bd9c2b6e97471d1f1c993d563774b1e2316894ae10b8f77008f67bc23429bebf68f2a823ebe81edacfdfd13d2458e9901a3e62df04efb937a17bfbfe2670efbd09f6bba7ef12525da1bfe61f85f38f28a19708f7de13f1befb29066953cbb9e20665c76350adef750cca466299da7f59491d51a23978c394f2e79cfa7c8477534693a1e4f5f26c870173e5ff6bc2fcbd2d6a52cde629c672c789fdc4131748b34d745915ab13a557bafffed2e888bf1224ea1409afbe123a6258b8f762b158ac3efd6deba03e7d3a33a5fff58efae357a9bb83436a91fa9b0422cb584dfb0c72b849b45c0251a65f394c65300812f391daccb8579bda13776c31469ca7f308f4bf20ee97c7af7bb064cce715fb57f837ac9ed3c69def436cf2fcef5bf652d606a22954348181010d37dea801080b125e24911186962b222762c0d41046126a60b183cc044a2748a961852dda9092f9f78e6bd25af17a474747c7b71f0eb2dc7cfc35b170cfe13b75fc471d9d30ccdd733fbebcd6b458d6c4cbc2953addc37f3e00ba0703543ab7e5aff6a2cb31ff18cb288f2ca3bc89304253d94dfc8e8e28992fc2083f4f4f227c4b5cee3bafc1ed5d1c6312ecc08dc432e5f0cf93cd77f658d90495883042f5ee89d73b339fb577bc5f128825d2174a5df0fb3eacc45ff24946c926e984a5e68fccfffeb662390cefb5f35e895ff9e68f14013ff6e37e0f0e01ff3312cbe07b3958b8327f0db80d25c3519e4ff31e65652434df9c2beebc19aa8a95dc5032441d2037940c55b26c9a3d38fee6717eb4ad1f6fcd8f61b6718c636d0b4b24b083ef41d1c5faef5d43665ebe6c3147664fbb55e813a8799b2752f336f689b4fe065be7f9b0c7f23802a24cf35f23b6c49f3039f168249669dea301da3e28d3bfe2cc7722eb3f91467481ff3dcd6fa20b7c9963d7e66b3e041b5c83439811bf6775008a19e4505f7a184b8dc556345283659395f93778dfffe57ffd43aaeb2c20bc97d92d20e4af5828b59385b29158eba05ace1597e6db3d582351ae1e984af9ab4e100809a553feaa5f63c6752ba5c0ffbea3a6a8aaacb621d69b70c2f72cec22023e6be87b16be60d087995cf9e83ecda8d9546badb5d65a6badb5d65a6bad9522e55a6badb54a1fad59eef7eed13469f610c9ea7f22918c3a2151a54a9728d5f7467398e3388ee338cff33c1dc22036af8b155a3c2df57ad10991dd5f73c21a82426df111e133656039b9e7afec79273c15fe31ebff728c65cf0bbd0e7f1ca614886cb15cca5bc5a3d5309d787c7971834b8dcd9507d67039effdf585dfbd932265fa1e55a654b36a5acd2b3aa34673467fcea60ecd3359d0afa9d9a3e1d93461f4e98f56878e3cce99b576bbb786216e7d71a7a40fc7b3496a409fbe63d13d269e40f40e3aa77a07bde1ca97ae7ce6468ee449ae1415253545556525936492925c6a727aeaaa118e2879329248b3c9e72748902c005adcd166faf6863f5f3122197d97f2a5d9d332faee347ba88c3e7d8ab4cd0c748f8947c9244b1f12cba2d6c0bf9d0023fb919bc7b6935bc7e257c5636c7e98623c5e491b6671c05c893511668f2c6a98ff5115942a225849261d992bb358914cb27f0dd5ed3c4a2bb949abe22149b233ce74fdee89d8e7b009dc5beead09db6b6faba805690f82f63fda07d15e8241f5b59eacfd8ff7da1f993ddd5b7bafd6571f12777beeebdbeffa1b0b7d4a264c73cd13ab5cfd207635fefeef7b30c884693f13a621097b87bffd6e6a2e21f0bf97febdf71fb6fe2ff38f77e25b71eb069572c5b1096b2ae9d6499330392777549327f9f8476a50bf7e4df7702cb3f9a6f9576ebd9209b3c135d6d2dc1bd6af8d8fb4a25c8de33058c3d56ad0b8b2481a394f3d327bc0b7f6de30fcab6c5f740dd9fe7bcf334bb083ed9170dfe5257f55b1bed781f5ce9e1bde0fdfead51cd12cfc9ef57f26ac4eb9320b9656fa69024741f541a8ff533f888f0e892b9bb826476e40d2c2a8090531aa9488f203902c55c45022e349192e015882885896323b20b104d4143730d9246248a19582369028a3e80c1a6e2003a7873b86d977bc3177dcd979c59e68ec03353cb18a8189aa155480441b954808a41fc28821458932348c717574469dec2f9df71e2a13e3025a5c45f1c4094908e1c5161a6a8894c0585ad2050e4d45514e944a1531468d235440c653d323606002d6e0c21623c2a85a019949bd08ee45983e798a91e77c3be79c736e94d3b8ca8d76b3148fe16c798f7c1db6a54b931baa05171091c5d6c3bdb9a15a80c288ebe5866ae1a905a7165ab84638017d98e1c1850f0823d60a4930783f24812bb0d042d794f49569c265a1f2d830b23d347567186d4c535c18186a1624db440c1a0f4f9b104fa88c29a95a99b21eaa341b83e1851fa2a08c8a5c689252610d241f9efe7ed6da1b5a1c47e0d0869825eecdeda58d2a965c9bdb4b1b5fcab8addc5eda70c291db8b1b45deacb5d64a6987c9f4c7acffe3d347a3b5f2a0b5564a296d51efb2e362f539f106fc355dced95ca6686d18bad37c41c2ca08c88d25a5d4dd8520a904c812237179122dd3b72e4dd3342c44fed4a91949122dbb9ecb2eca0f09cd49b46cdd6320a594adda5d5fe2da3ce69cafa239e7b499e2cadf7c72ff8fdc41a406b355a43305d521a715402e529368b65b08cd13fff0573fd886e6b36eb6f33ed6370302d5db755d576fe326ed97e52c8747259bed3a4ffb74c2d063d178b652ed03b94d8aae7bcfd6cefbecbdc765cb79de07da0e7f1dd7bad7de5a6b2ad536aef3a4b84015e310c389327bd6beececdbcebec4230aacc7b5741687ed4fad6334afe6abb1a173fa501b0ae6e0f9f45937db79208b050269333aaaf679dba4985a2579eba6991bcadbd3ff36aa793a1e18861d7827c8519fbad9cefb5eff98a3f873d7799c6dddbbcd504a4367dd6ce77d3f513e3e38beb8f2bdae5f1c3796206bb25fdc6fdc533ca2206b42b496ceea3047df8a3f9f4666e65935ea53f3d9d88040d537ecb33dad3f3e4b3e64a8d20d2da553ad6e1d38736bec4da893439b970755ab7b82b4071bce0977fe716e70e87cff0a227dd0df623c7c7efc70f00569983f787163cee9dee0d4d0b46c74c270867503d0e56e3aa48fa9715fbba0205c141bc6d1822bdf8a1eb454102d2d9d55eb6f411d086431087650f1f5e06b7d76e3aae83c5f8e0a7244940741fed2709028f1fbe13c35f77b74ab4d9fb3bcd81bbbd205cf09942940b5306349253973e225fbc89bad5473226b6fb5bf476764ed888a16521ef1e9cba863893ce7b3e0459e4d70c8b3c91979ae40469eefb1ee1980104c2ddc4dd6267af6cd0da582982cbd23f9af1bc66cf02838a4887bad0fa473c519e47093685f82ccfe1a7d2a5eef8c2da46cdfd3ba99ff71af7f7c0a02cdbcd7eaf20508314b5150265866f20fdd13e4447379a1e9185f21d4e1dad0768ad1478db4d6b4b74faf664f7d6bb3b4a4b85666fb1a53fbe86c1fd061ace1310cbdedbb07587b281487d93f72986dd23bec8f19f8a02bb406f635497388b8328f2fb3d77df7700acbadcdb27db17b281a0db3dfbf7d4bdc0d0f3109c1d4c24cb6613b61d66a938a9ac95ac66496292363f2f7befebc56970be6f1a740a030dbcd0bd5bff91b3cb4810cb28a5fb34f24db4457754dfb1a1e27d5905c2189226040838e3941c6c43d91cc03bfb643ed650c2e5c644329e0aa9031dd4fc104f3868c89db4006d957e56a55d034cc3e96b7da926ce9986c5fd3e2375bedcbec3428b6cab5a26569d2ad1f290cd95219b2a53864fb3550c4eca4a12b512dd31fef965c9b1f7d6ab6f0686d0da646d88d2a50504f98cbe6ab72b5b76fe3d43d1e902dcd5bd6dba768740f35b29c945ce45fbc27c85aad0ab7da164d8b3dd2a06855c4498d66cf4483cea4cc31d9be0d9e80cb2a9e529ac8ca1e1877a4565cdc714a51abee21b3fdb982ee89e129755b906d2846b6d79726ccbe2bad004f90c8f6f46ac2a8d517d7abb42f8788eb797caf2581ef47fa5ef336a20e1280747ef9cbbe900fb7a2b8f229b6ef53fea2c1d60a088485a992e8e52e371895ad231df98bcab55fd3e58ed42ac8d68848643ed23dbcb71f64dfd2e0d1defef0187a55b347364c7bce2adbefa9d959b6ea1d961a39ccbeb5f786f6f3ec7f981af9cbfe48e9d11248d403813e2b3d2e170301b3278239367ba855c3ec773f865eecfbfbe452a323e701df3e85227df85b28d93eeb41911ef9cbbe58201056063135ea1df65b5feeb44fada4b492408bc95c0b490045e3275b4a392911577ee39113c7e7de8b80f4dabd1dbfca245a0629160fb7c3e49d523cd940589abbbbfbec963954aefc9e2d77778924d3316cbe994dd619eab0b303a5c2d41113a5096b9b3fefa152dbf66ab9961b4a85a51840c942caf5a4aa8cf1b265cc1615c6f810e66ab9bd8cd9a103159470727b21d303540a565ce171b9d65fedb2f289b2747520b3cd62a62f33162a7a6eec71b9b4ab7215ef7a4b682ea9db35aefc49dbe7f496b8f2a9fb78a941cb398992bc25baaa039b6dc372e3f6541c71a63fafd70a0e1133e53a6bdbe8a67852d2e9c7e59e3de44f988bbd83d27b6f531c36ddb3fd868f38cc5ddd59649ba93cb6d5a572a92b69515bcfae4e0fa8ba75b75148a0d0850c30b65471c4a56162cb920c6da9f52269c21ac88e717372436d71813b828d24dc629cdaea8496f6e682bdf6c9764a57475c382985f142182ba52c8eb8b8524a62062a29252a5a3cd9180d29b8bdf6bb5b5c71317358e7d15e2516a0b4a0a9063660d802e9ef67adbde10a5bcf2added66775e6a1455614151936ac60d7343315163dbe2dae48662425580dc50455a427b3fe6635f99d2f6219ec4104e691921469ef3ed3d22883c7f07049a61ad6189173ff8e0c60b4d608ca468526336461a6450279082a0a1d23ddc8823f27c9a818552464e115f80c1b71b8b275f72b84d1202f2f839511541449e2f8240f3a220babb3bcfd31f79fc58e064058a3c5f0020d074f70f5f780387bbbbf704e032808827793e006288e2a68780d2b10da54317493aa0f1459525526e0183174a7118450f950d21c3015ca1bbd3a816c8b8bb733cdc4c738b89cc0102392c8d3c3f870b18be847822cfc70102cd3086251c1f0a10c2853cbfb56891e7df841d121635c4c025862ba44030a20a1748650135c4123297b4891bb23f07eeee372fdc9b2bb434c9b30502cdb73ea590d8707777ad6ed6adc5a3bda10d08e431f769e93420082a41a8018416797e14b314be14ad4104982612a05cc0060c9a5c212305940b6fd034cdd73a00f5b737fc57030143f6bffd8229a438092207274d55cc20528430630d2b3ab0404536a7172857e0fcf0d70b3cb2ffb0941d8825d9ff86feefff8af9cff8886db8900519332ea698e41bd018024b1421961023bc7dc60b5d667c28c95210fde13888f3682ff10ed04bb9e35331eaf081860440f92045d30e79fe37b991e7b340a02982a919c468f2030e3ca04182159890a18a37a6d008a34b141458200844bf56486ff83d8491fdbfcf795a8c349e80510187ab35a2480088184ed812069817a82163228632c23ce79c73824054bab2c8a30e1e5a64b992c50da624a635b243f520050f51d9bffb3e28b39cec1fc4033a671379f290031779befdbe9e42a225e947edb7f9ec80254feb7489ec5170c8eeee0ea5411d64c83c749062072a79860da5081e533a002cc8a3ce0e9625ae589ab244840e48feb232268780a1e92688dbb9a50893c79d9c471d566090ddaf7cc90e75c58a1cac6099658f112d79b20405173298b022060150c2052b5ec081cc931b64fe22b873c91e8588ecaeeceeeef873128bf250b47060caf3e9b4a2429e56a6c8339ca28c134bac700163836cbc535a20a5882b3f54b9fa42369f90c0a089c5b294f7c6784ce9cc4c91fd6d0fd93ff41bb864c781061b8ab243e008aaec3fe593f4e12964ff6bd43d36c8fe7209d923951ae66f9fd0f078658845f6f7e8d669b5d65a7fe3b8ff718d04d6fbcba5a0a2fb334f24037f46944f2c514a693e13e6ff337f7fa8060fbd5878e8e2211bdeff96b18c5cdedb3c91ece5e1a0a299a7792299abc3ae10c067bdd07d1b3cc4d4fdfd0e0b81cf7a1691ac6535d835b1449a307723509434a27c12a7aadf9f7926f0ef0b81f5877a827804fcd4514a266cfe48b58f8243bed788be5cc712a487bfafa2a6d5fa9384211d797b8fe340a0f995ceac498066ee3bb964452985fbf3eb4ba9a728adca2ca764981bae7c924e1b91e777c98a677b2bb7e91ee8f01e994293832095ddaa86a9ee52c354f60f66ece7870e1dd6cac89b467ed059644e6a85c8af0f447ec5ed2fbb8975878718f39af887fe6dddcaeb9da114f85001104f3226fadbe7fa37f887fe6dfd7dbc561c4b4cba630099d26fd19f62c3fc61096040f8f53b32f7af7f58b1a59526dd98bf8f8d0a63c64ce65350c83293c5fc7dfcffe5deecc6c99d7956f7fddf16eb3daf63d9b870ff59ef48332331f9c9cb1d7d26357b5a8ba913725803082da42c9149021c51e503951704c182104f513d5249c9de1b86ffaf572c96479f75ef55fe92208e2e77f4273192aa049a3f1aac7243d14095716e281ab6649f394f271e3952962f8bfc25a9a418efa5acf21e9dcf79ef6f9c5c0feb3894ecb0d18f1ccc1502be87a598ce25a4bee702bf738df659ab455ad13d5ce27cf852762210d1d581670f9320cbe49af7ec5fab06b7bf6a7a5ae61daec14ffee4dd287147128ee40f1761fd7cd6b3e47b5d87c7ee7bd6cd35ee799ee7799fd81904df03710934d7e0f1482ed2bdf7e391ecdd7ccd8fdff7f3413c76cfaac149686e7d0bbb11aba885eb12329ffb895dde37beded16d3598d0a062ca4a8a551748a69081cc0d4f7898c105a924973e2c95bef8d32895f2d732e291347bed80cb7a9c87aa6196598ff3d37e372f956a5eb2fece1e57504e8e8b48ceeb888e24f68469205050114cbecebb9e4928c7e77c4eceebbc50ceebe44c2c94f32e3c14629f09939f23e7c73007bbe6b74c4a194580289e6cb1c30967fc008511952e61d810e3c88a0b2db1e6bf0fc01fbf3c7fb479bc79235ffec00f8f347f0ffe14c7d8388e63678111f2078a240c65d64df72cb1e6a7f87d38e7f79e48420a320e28922033cee7188d7c19e7bfbf796f8ade6f20d08de7d5d48035b808ceb3fec7f34df7dffb14e9c446bedcfa090e692b76b06287dc12bfaf69e1bc109c677da8022bce83f39f387f8243588f03621c31fc297adf89e327c4c32e21f3bdf7ba9b97a09010bbbc27ca386fff67e288321bf932eb3b716c659c9bc7113b5bec12f2f371fec733d8dd88bec55f4e4847db87f3d13cbfe59a2f76f69a74a25c924ad24a0a7747cc0d5b58a580c4140b4d6196d4e8f242952cc2b8717247795434c9c0ee552ec6c9e8aabaefb018bd2c07e789b2540271fccc7970f4e47c8d0dcecb77dfe23c37e3a579ef46740db9b9f9ce2dd135a4554456bd08f269c892fb2937d40c62e48947f097fc503412b284fcc463fbab9559efb5e75ff79f5864821f9021df7b0fe433f22383789c5f82ccdfd7bcf74438ff89383f672826f117ce4ff1480c77ec99f34c25e7e9ae8eaac835b22c8232a51508840347bc69d9d4887e247a91c3921c265fd260efe2d197b2fc4e8af11e7f9a30f9bd4eeba3fe347bbcc86172e65359cea4b2f4994765f9de92ee0255c49419ae88c20b26434b8230c3290a932b2e663724e57bd21d8dfcc80ec55f7e54e45bdcc991bcc85f33a0867dd6c44ad20d52a9fe9117f9cc5ff2a3dcdf80194a7c81e58c222f55c8641866a800ca55125daee820932f95a627c375a40300516566458832d848638c31b8cc80831426a064e148b347033c64814262d6460d5664f29fc84246175f50b1328a4126df676ee456a1f368e293d29e94ba196196f95269a7a5e15643eaaf69ed1ca385425bcf5d2249423396998b8894692f651a36fa60fd2e213379fe4cc672026dce6bbd6a5f2bae94b6368659d3344de3a0792acd94fed027aa5cdf9bc262fbd6bed617d797a40f892992d46087eee1983ee91df533ea1e4e455758a2a2a2a2a2b276a95d4aadb57e5f6911fdaa5fdfa790be0a56569da91e1e75581687c77beb86eb53a7de511fbc72e5952f5df9cc8d9cc73b427287e249d3953c8bf789345f2877f4a55cbf7bcfe2758243ec7301a325a864cd5595450b952a1a010080009314000020140c8785629148289cc982ac7c14000b7e94467456994aa45914c3380821638821c60040080122002344341108a21798b41a81c021032337bf5a6b195e2436b39f8d2d811e7011e5139b085b4dcc8c2610d7846b8261eb7d639554c7f0691a5e3a872783356e6d608e08e068f16ade07147614c14b5c872bb9cc54636102ce39567baf3913f130d56cec6785e9cf8a7bb53fb21991e1c565e100175806edd117f2a4bf83cbfdbdf07e01ed22294d46101101fe0de6a127bc6038f85ddd2fb370d22543e2e44cc9add2e0d919f0d6e804a0606f2d1e97bd3daca27c8c03d908079dcc76bbe4f4f46d42bf282b71230fd35ed500ffdf8ef047cd68e2d9acbb5586e981d309043083d12dd43dc79a790bfa9905db04f29c6362a58468e13d8b4f05bb8b9f7011be7026828f82652e47a9459d3c759ba0e5bce84cefabd6aae8825433f8625139d9069fccae475a9c7ddf05dc41793f6daafbcda6b69629379b7774eb502494f12b42e7703be9df8a45db121763e8bd42a4ac7cf32a3607ab735478c8779f8261f2691535711247155f594c476b27db8687b35e72fc8d5b63301bd894534df8c988bd44eb7c950a071cc130ae626112334b7c6a68a6d8371a9a358336951862a2187483c3772eebdfa3b5dbf2b3b8b0a7c4ded8c6f18df323209ed3a73de2e7bdfcf0a557e8802f212e96f311dc22170386708f5a0278d1cdf41a2025b7c4eac5c8c586b03191dfaf7dcc55a266cf8053d5d18d0512c827d172693f0cda0044ecb94fd59395785d2334e503b99d7230d12e362c238cfd28e0f3def7402e12b5d8fbd2dc441cd8f83a1a6ba3d5898a0b7dbfe1e3084241d449ac77558be757bf5bd8e77defe46e7bfa0c14536063a9bcdf45fe4a54b18c4760e35634fe37e61e50a91fef10cdc4e7e7ace1df837522e348bb4614b611a459650318f50a2336c450f15110638bfff38bf1b1354bfc598c4ac54058b1cfc1428d4001ccd8d0d46b0982861f5620dd64b28f59a1d45479abd28b7e78106a6d4968653a4c175e52c04eacba11caa39853ff48d3fba7dee04b1032a10f8443d54e984cef924847b32cff34543463627ce4e76b149c1de0b444afa911025b73c0e68b258cf74d3320d60d248da194e2adf597931ba7e0a3b24050b0b8c460b6be721e4a0eb3c5ddfece65ebbc9b0bbe4b9de1de06d1171f3e581516a0697859fe0869f0e0459f3083309279837b8b56bcc7606d5c0431cb3660bbddd1a7cacade363a8d44fbdb9a7f0366d1cd78d0abac94d5e551b020120fa6afe5657b89ca998091842c0a495e420bf7f3daa1fc70a2508bcbd8ace21fea46ac7b943abe1edcf232f203e13402cc3a0e26c58844746090d21170fe491846c70247bfe6fdd28bb1e77602705f99491cfd0e76cd1ad750af6962f13b034c68b224a5e68cbd54ebfab9d9e1f68f121275f00bdb4a67ff5d09cd04f4d0a075fc5d0698b8d04bd24b3c154af1e8435e6bcf6f31a0a4e5f202f2a10074481154dbfba92529b5998d1cad1281686aae02632bfd58c647fc1688388ad90ccd0de6eb0ab1c57cf944176c9ac060767405ae2f9dc3c71ac7f3980c3b329a2fd1343b456e20b608a3d038219e7f0666ad2ceca2fcb4bb256691a3d8d8759c7c60832fc1a30e2215a636b1a590bfc99adbac14cc9354a186958c020d2584b2a848f5acf4c5fd6ab0937e213e42b5989c21e3809db14abac36c64f139d773c47e6298d89563d2eed72984a0495a60ad6b3735043c5cb7fe937a1c5a4c832ff262e1a9367113db60dcb30e4f31075eb0440a260260e38b06e9459d5d379ade8d4b7df5a190cb05cfb95b183769c4909f109beb246db4071a87781021b7b63081fb39ba2268a9417e45e660ad0fae2ea19759b8aeebc051e078704126adca049235df051f594408dcf294199ca4e409dce4f846147fcc37b0471e739fe013ef330e3941423fab3914a02fa8ca0fef0848a4ade3cfd98456b1b243990781f7439fc65185accb042e8e918224d28500dae325b71d042dce74320886f006430a8cfda527065499dff13c7d0fb45ea84272e0322d2ed6157c768322e96e4bc987a94ff467bc364b4d72f1bb216f2506f19579d114e9aeeba94a15a89943e93981e2b98b18337e9458dbbb8e9d1f56c307bb9b0c119ecd3a496e4bec792da5587c262e49de2d2f53180f8ab9162e14cfb7e104865f68bf81113187301bb9d0e6213df4e4e4cf933229fb8e676fab419448e3ae94d9298cd3c6e321f0618c178febef62ebe0739237d9cdeaf4af7c8e46f4f0bfec821cae252152ee5a7f0bf93498bd7f4dd580ed4865cff67389caeaa78e1993ba646b5fee68114cef81010b3ff2a9277d45fc33d7b7c05c46fdc89d5651c597306c8cd4d9a09137e9bc9f276bf9826572d057e5831f95479ea58debc1fc1866d79c7d9c554a6c2b8f27cc86b1121d30f7588dfc833e83ff58628504402aada4743c03a18e2a842e1a7493119e835153cce857fa36e344b0836f420249959b73d150ca1e68fd01bdc97870a07167bb0525fe0ce1cc2298ad743146b1601188345c554a6c30f09c897437501d650711d9b848611a9ae97306c2cae41d910a1d49be121a88827f2c26f1fc177657f6c5d7d73bf73d1bb8a361fa326183a3b0f2a82280ef5e2e763184977584031b881dd32a490e75786b4c02b4f4eee67d15a442dba170c6d3d529f5c63249c0c5d74efbb33f34ffca23bf6d0d8fbddd99782ec9e5167c76ac3bca9f386bf936bd2261532d13808d15b2dd1dfd5da84f2c7b0591dc6a0d84b06de0ab7e19afe9a151d415d77ccb3fd4b7909b417b1e2850342ad957e4c970dce4e8effd6286fcba0f1df47fccf36ac27010bbce2d29e82813cf90cf4d81c6aaf06cfab58b9de49a981a25b1e32e69a2cb48fa6f00e21e595639a779d84210422233c25ed949dda9921dac3cd6400b7c08f75cad6bfcce50d9962c3b14421a61293853327a18e442dd16e67cc47d08d0bf9b4b0964bf33333774b597425b0bd932cc633a74ecb31923602db5c729f13c1db44d5b6e603a16200e44a4b659e02d98f9c3698a2adea07d468f934cbeca3f7ba0a6d90ba4c9aaca7ab67ef4a586def6beb2b729f4cb4e8298892d9b325be48e71f68cf7a444726d195d81f68c2a82df0bd68844874001a8bf8bce979ba823ab14d37aa81a339a22bff36b4943a8a176724889f5a890599e894000708f9a4883116669ad0220a8991e1f173e39abe3a34b3ee1d8b72f296cd59a1af89ac5055c13ae009a62fe02352f152d439bbe0ffd4293bdd886d9dd3fb0315658750eea56cdd2912a64e3cb5ffefb5febe7da0f716d19908d7e3fe1e67901d9f438a8425d6406e2ba09648d6a00a56fd135ceae45bf366b984d7a29c19517e7f839480fbba5cfc6aa6b24b40c03833a1129e69609bfa37d472c2ab39f7625fbbc39a1f4dbda0952ac37793db6ecad231402bc622da1859cdff26559672174412bcb7c2e4599ea192cc2e1121b5b6d3720ef2dfbff04a2ea1c397ccb0f158889842386db9e081bc4ca18ee7637cea3d6e78f25c995e1f393f3ba95f39dd9411f41c7a10814fb0a0af6dda443ac67bb634f9c685afb1cfdcbd89b2a4fe3e281687b82263b704223a0f492e1f240c476ba186a976cc4d5bb2ba18958114ee667f884baa4f8ee99094c84833962355a9d4243155c7e84df8eb93116ff618b1bb8775bc13dc04e9416101f052006a62c14dbc331ce75b54c2ba6c5d3d60f7c785c540a70dafc59dc2028c6c044db1a1079a1937290c5e74eb04a58a2d7a3eaff22168a318a6aa2968e5809a0bbac650e652b87f21cd599caa801142f79c088525750caaa1790961982e47711cdb5bc05a3462d485d5d51e764d6bb5507aeb0d358c71341a956fba10f072facedf962c84b3bc1e8995983a919892b919794c56173699bd0b7a88db9a45cabccef9ad46c84cc661f433e5aaed911926dca6f4660fd1b2eb6cb39090eae42f33c864e334560540a4df84d157c8cd2210534ae8bf3bd82356ed55c01c780a07a6b7aac968f1a7ca54a4281a9aa4dde1162908b9dfb4adae78793e5d74e96bd432ca5777646c6e0d0ce185e40d46cbb49059c1402986ea5842ab8f2394036842f8a885054053416ee8c0da68e3be34cd22a02e6b099eeae2fbfbbe982b2b95cf34b5e6175465e0def8da1f0a26e4b0797067f4853cf679b9726d2c9cb5af849213723b47780e302975065a75f709105e1ee74d4dddb8b31f43cbfa80c8ebea9e39804ab62e7677ee66a94f7d43e9d41c80aa50249374a4867d9997a803c539730bd5122cc19f500d9570d9e0de8416f7ed8cf0eafe954fe46526bb64edd1e3611e0ac10c5155682ad3f0d3c3de2134ab485656c18deb08cf6e22cbd20177d39d7f05abc4119080725dbb01687f9dc53101db365923065946b0f62b54db594fdea5d0546c2a352a08c088c72a96d851e1c3ca0716d524ba512d0618acdc10fc1879aa139a92e1663e24d99c0f03934ca26c083e9fcc5ccbe9970c9fb79d1055e0da7944c0ce112e297740b333880b7d8498f2f36683fb88617e8a4089838585a3dfe8ea53860649d9740cd218267f23033d119cd370626e4ed8fa029d93f3fed899458ab74105e5f2ec214d30665ec996a537460745b81ffa6058f13dc47ff0f1ed22f1aaad654d05b146bb1df55401bd94f8a417aa6acc650b8dd6640317e01e32356225be24970cebaf60e4352cb47ce01459a270a9d1e9033a04763e292a30028d2962eb330beed9d477d75016ebef2c6a045856fb6ecc2d6390adcf66370bf06595e18376674d5e1194a8b92de060f8c8c63a717459255e4949f68301176594dd41a68a1f5186834a32b9e4eccff5618c5821a8473e6d0496bfee0234582c092bb909b4a6a936a0cd20d1a15878a1a5fbf815458d95fdc9e864796cc05b65002da11c32f5deac11a5c0978cc89301c41e73cc5b7160c7b23026695a2a5409627b804e5a798f9c67c8d973ade50d6f03b0057fb5d9b17594a4090d538ee9c3552430660c9f64b58480456fb5dba80c6f33ee9f0da6ddc5d6ac8c7575643475f794b16aaf8289f94cd50e6153f65e56d6931baa001e950e1fb3499a66c15a38bf97a2fa6d1696f9ad705f1145a20114fd26d5e3521970c880870996b4cd213e07a8e18d414aed08a036d2333f3803ab33b817a0c1cabab90e9402c26459c3abcba2f0f9c126221ac4943e19cfb7dc18a6727d42674a5ca296aaa6be8512c84322034efcadc07dc3a4cbaff4ff6971ade68505445601d8423c7db849f6e740d9c485603b59eb1aa129830578dadbc2b8c5561e2e0c0b379d190e55c6ea328e856fdc716f2cd358e09ce9746bf04bb7a665665a008298091c5fdb3cdb026d59f962c7177d5cc29b25791dd32de4db2007675b52d2846dbae219f92ada5cbb08248dc094c2a0aa319a62d36f56ccf535bd4008be756663495ed8987ad66b59f9ce62a6257fd66a6dea5debc28fb7507800264678ddeccb61d896dd198759f8c206e817c99b896308b6c3e4247fd2b42f9992dc02d976169592c3d7a8ccb883c2f42119de4d66b4accc69711b9595bddeb63652d8d80c7425939b313cc42c2574a1636f8b168f2cb7f850ed8df1f2587a894594da67553b0d8ac05bb001dec34d7b5fc40d26e8e2be13898e87f81229cbc54b5aa1395fdd2d9a460d9f4ba8a26e13fa814d73321822b2d953156a600b220dd7cb447a23f272d5e5c2414d24b8dbbae542db542f74e1229d4e359fd03f7a863d3fa1fe615eff81c1b5fc7f887aa41541024c02b93979cd4969820108fb969f0e13b226a06a21f71a8a71e7de2d148b87cc44b406d21a6443f65b1cda1a35b2e3d382e9fc2e1421359e2bc5852347b3a43db5b3b1fef8118b88966c43fbe4df9bb41b34073133cb04b82a464f7ef5774cc3458c5cf614518dbcacf487e04ed967f32a85d4925df9b20c1108465e60a4006efa12db013c90d5d171a3cad91df4622ece25c95d516fed17dbb2c92bffb6983d1c7c21ca2ec6b346d86a170d11e51ebf3e03bbc6720d6becc5e82b87f8432aadc64c7b98725abd78fa00264733d8f70abdf9235763ecdb50df174bbea31a60b6ddcf05fd789bd8589e38b7974ab1a1950118655110c473c2cfc925b2a184677a242e077c6fa83aa28b454039e91c7240041112f8fa43b8c11176a51e2fc1b190589fbc25801931d75c7efc5968404baaa4b03e79782a54848729fa56951097578771cf0640d8b64223d460e42ba7021c535c74ba667d6847dde1666e07630d17f7f68aef5437eb099c5618422759ecf904c0a8439f6fd014b2e03eee3c35e746e50569c2ab68c8c4c0871c9a5bbf6e3b505f0e6ef40d3d87620cf7c415d9bbfc7bc16e4f9fed9d1055e2f311b29d2dce482a6c6b4e3221d48d08fd81e6b324aa52e62f7b68829451f4ca121f3ce1cae96c2b6c679b0e2027eac8523f7bb2a25f2c3b6bde88a870fd55055631ca5df99c46cb38b2f5e3a0af159d3f301f37ea4e707561df2ecb7a4a1884835fb5bcc64ad0a7d2d77e00e57c7d67fab9f85394f31cd0fad4252828c4c8c78babf9c02820989fabf8440a157c943cacc703e0e05080d4645220fbd9f2568d02f88756db36f688f96f4de12e51a2dcb3aca3573893b2d5621092a380198b57eb185fb17b975fa58a24e37623589460a5846b19d1b0fb3fa54d96db2fce34e13e7b928997fd5551a5b2d02b0147046276d1ed308c6e7ccd24f9c0ad764a5c664e3d123ddf265ec6c2fa4b68f02d263d4c8021cdb2a651a440b362dae61b1f404b13ebd0183500793cfbf90f632a5eb6271b4071b001f58a2c070eb6b93501949e6e89ec8a7b138f81ac0b90b20e8e2ecc1daa12a54835557462f93bc35c8ca691b68cc38e3c309d7510e7f4a19ab073d03e0d4c3302630f95a0ed61d8ce5f344457cb1ef6aabedfbf6507a2baf8c6d65fbb14ec344ce5b71a291151450fedae35cbf45dc486ce433255938bbc5b25f5dc8a2f110c24cd3b6475e5bff603fce9b530a9c231cc0ecc6773a2d5b3cef556c24780a745aad9a2edb1255258dc747203b56752c6003cc8e5890da3b0cf5e028b61e45164431c80add49678c10f31c19ceb5cd26d9541ac7d19b7d3a07e84ada5f244c65fd447ccdb6ef046ddda8ab48dcdf8891caecc1d832812cd0b43ad823f88d293c17a6d2275f390070a293672c3853e3003dea08069a1557e62376083039ace430d9fb11c88e9c7ceb0c8ddca3bfb746e2217b9f04ee75ba44e42f1b39c67395f0538f592732ec292bf159b278fe62838a77fc17f0a479628aacd9bf4c2c10213d7e4193c53b324ba6d195ed100a49d49c106ead402ffd42b29e3ab9a150a649c44ccd305a2e4af3023481024be78c5ffac4bc82df58d0b19d1713505e81234b3eeb2f6b0bb9dfd50ae9f6af002a41d22faff95b012c9405baa13ed811fad5f217106735fa92f48fc929181d3d60704c3b644a61aa5c9f2baa72b2b591704ed977346a35a804009ccfa7b82594928352ab6b1ffced3d5d6239922c68693b353142b45b8ceddcc6ce4d8f1fcb85a9fb05cf0ca13c6648bc2e145951fcc0b3f8984def03f3c426b04bee8223bd8e9915017e030aa4073e63d7da141f80786d02d331361cb6719962e87d341c8bab92a097d82fb3ab3194a6424f29d7647e89d36cad3a9332d8f0deaeaf7bcdc0bd66272ad7db5e53ccfd63719544454cbac67dc0d07df07bc086731d813cba488938be3b0d39d86ebcf304a0b7178e10abd18f920b52185611a76d217f0dcadee065f4f5a5c90417e43bd42a0fac228cdbd27c530b00de40e5b4842b06275a7f85d007f83a5be3d26541ec9e014e2a869e7b226bc5760f7e4e6a87afd5602e27c51f51fa847dc1cfb38b11697c9c475c5f662e72a4733e8771462f4947cd484165d003ea02eea70e5d145b71d2a3f59b58b6346b84898bc0a811bfcda6637a3a33c293b41865a86741f4eb25146c7698428ad299177c48599be6092c628c0638b45970c47e83ec4346145c60e2e6cf28cd8e619e5769f63531e912dabc382797a7f2f6f2e25979f49e264bf7bf7e9c792426b4ac9d300fe61b24142084ee1c7062c4a91a290c1c223708b042e3b5ef5568d9f62068c82789718885d599117f3264f10efdfa71f07412e066e3fccf90be4410c05350fbe8dbb089b1d580ff5d9c0836b8c11289abf31929400b67700a25389fae27f145246c50bb485d0d8a8c315d3844538e1c1533ec0d90a168ec9af71202754e0991eda94f5c09c5d0b1c1f8f394d7148b9ed582ac98144b6c77c9d407d846d92719e2fffdd6e9f782c9ba78d9280431b22837b22e409ccd5bec2586c898eed5099dff05baa51ea463c04c5101b31f804a562fb2b5b4b267682ba515b48b41d6d98c14ca8ca0aee921c2e513dbb19b7f16edd6266ac0bd86de02a3ed771233d98dcb3306b501f135daf048628d68882fe888290521465b0c99349098daec05e9d9a6d34a86aeab696092fc87a99a4d4ffa80ddcc20c8ea8ceb100d20f84db673f2a5e8a0d4d053a228d95c637dc15960086ec350abb5274ad22c3286b91b4380809ad01b6c74beb77c27c4dda35b1394287742826681a34706de8f8c7ba244dc99a7200a009ff6817fbf1726ad0c71f8f9d226ae55982a67c85deb407f1d16db3022c7ecf56e4ab8d0333da96e4a11951f9d1cd69f372b655f03dff2363753aac16f282ce486f3b2179f61eda482858ad5ac003ccda799e9e7d31be334c8b1e4d3270b3fb2c419b671460c6feb8dc49e2fabf13d8ed39af1605379c8d9175862d749e7b353e5ab19260696109e19e3eecfc997d3174f300a5d5c1b6490f7b95ad660e7f174798ab88c0c9a11233dc673ed02c623580dd6024fdd2eb2638cbc0fa196b573ed8b714a98d1838f648665d80b908e53c96c6e8bbfee30237a22664664d661e931d99aeef747ef0e50e1462b4ba007b8cf34d28f73e28059f4161e3637d679d4a2c31c4bcf6f255a55c89c3579b3276455aa16f291f1b047c6b2ea05b6d8d6d480c990d1030150b51554489e567d44ea821c515a9ab1bf02e00cee3ce25c6d53efa1c45d39c67573653955f9d526aea248dd74a5608c440fb3d96eeefb0fa238ca7cbdabcb0801dbbda12554909ea369db0f487d71f1a625d11c7e45eaabb67622a2778ea5d4eb51604ad2de3559d04c06d3457490463efc90463a1ecaacf00000983b308dbcc258b4e74e65a7932ffac32319e57deed285ec12689cce1fe2546ab4503091352e4a35feca7ea899c0d00409b16f1e1d240d909dafa332424a52bc4743dbe8f232688c8ce402fb57201ea4e1209f51f9c85012f293809c90b60647f7b4cc82451556eec2e222c333143391a2fdb74a6fff3a1b09feade437e744e3084afc83b3ca42f11bda142390fb327030bd07f518d045c1347a27512fea7da461698ff6f7f50936a556f7254a7f9937628b11ebf88857fe88104796e7acd13f34901cbe098f97a5844f3b5e57c4e4be3720016e88e12e9b452ec425c69713c217287c7e2e8ef9f9bb8c6903db95c2f66d5b59c4a494e3559649d82f00f689f7ea7090f56804fc3e1672ee7222a0d798ac54940105d3af4cc0dc52e4770f35ba910578ac98ce8d890016a10dc0606f5ac6b53202cc85af7226f78347360ccb8b11c4a8ffb832480958169470eaecab6e13126e0e1cf8e13a88a507b66c43ead1f0f99813f73dd5da9d7a572c283c9fd7ed0728632a8f8cffb90dd1623f5d1100b0eab67afd40fbe6c8895f5b7faf444d017a784da215f0f940011cbabfa187f5c334480696f9e36c6e761a7645a8d2f93fb6b8a4c14356d0f5bc134fcbc3c70c3a4cf8470f30024ddb40208aa807e347ba23c47e960937bda86e54106330ff479236525936d45daaee04b219b7122f6a4516321b0b64c509483d4e50627d18b981e4a0c6bbf6d2510c28466739ba19c675d1fed3a36f4888b634bebcdb7730fcda50dbc12de3bd31853b23bc9feba2b80bf1db8671442f20e439dbf1d7b98f1a6ae0ff7ef7e430841a5f1dede73d0e98728811c3a43c6d8f1f7781ec90b78f9a0c0d0c55f8f0bf946996f9d001efa15c4ff71f9ae4f1f374b20a98359022f6b622b686bc08c969a309cee7b5ae021a15298acafb815e076379d27aea2b1abf01494a2b32021d0af3d9d180eef907dbaec5b2443d6ae9091ec1eea026205a69cce4f4a0d68cf8136335cc4001f628b484aba5cab43065d0ddfaa0d7f5e530e3beeef86a3de302a61cdd9e2ef851dbbcc229be3357b509d783a4f5de66923c62dc5b3c4b96f549a2d73317df37c0e40c2cd09ed7f39b1079384a4bb1eed203198e44fada6e9f4876ed3f55b73f1172547faad21e45c851fdab4afb142107feab5a7b8b2c07feab5a7b8a2407ffaa5afb8a2c47ffa8ba3d8a2c47ffaababd896439788b5755ee5708393427a570ed21867038ad315ce555be0c3a17d11dd312e009ee6e2314330e0c297ab0d2fca7b22ce9f9a476a08eec3b500e2892346fdc127fefad071c4a8aae963e4b4d3ac4ef3b6941453aa00ffaa1f29557190fe4d39a43c6a5af74d8bca24874480360c0d287cd73e037b99ed04c428ff9d2bc0ec39ce1ce6f25d52f801a180a3820a806c201e5a13d76de870dca3e96dc38b3918103a4b9600dedb4ff4886a279010df4e88085b32a546bea257aee0263239950ef407ed187199d5a1ec93e8cadfd64850b36b276c6260ab39f58131c28b8a06e089afbef99ae0774d7e5e0192277e721b6a7ab10609ea696aac4172a9fe07da75f5fc233b001d08a42b268d8ebc2843f45e48a688bf5de238b1b8d1c768648fd22e8672a2ba80ba6792c160e6f854a6ee745f480fbff2acc3c5e5afe982f5e2d9e835af47aeb27d126fd7839ee2651a17846514e63331314a8e47227d233d70bb798f9e5dcb1d6982b39b88a5ab18f79ea8a9de5b947e2d5d044e01c3ea6d9665038e83194986334344bd03b93faec96047e03af2d6010214a7999ba7754e3d01fafe7052223062eb54ce9681a518954f7a99599b8eb120262d71ae72b3520865e4f9b9ffe428b32b0d2ee82294e676c4753322f52003df3d702fbd5587c3786cf70a0615e2842abb2b75fb6cae8d2544756afb5deccfc87c1ccbcddc1a78f001953032fe14828d9d08b597aceb01f1f51288d6beef198baf4c932cec27265287e4fc45af9b9674989215035db28dce983fcae7f68ea9e256237a78956944c778dcba498573d5367579ccbd83a0025865e95c8236f3c4644763cd58cd7eabace1a340b553fc4244b02ab1770ad11c3f3a65ee07c287bb5238a57db8c528634b7566d5e92bfed689f583e6c8534793bdabaaae253d3b44debe51d71e419565c471c486fcf852a8a57fee72de35b35e8b40d35e7350e246b1bebb03d42c6dc75aa6b803e6b9cb3f15ac2ea87f00e20780d194d559739ab9f423b2d163f0bc78015c7ebdd6f62de3a0d6144ed142cf614c179131024c2e65293b0878600164676948c9405ee1bb9036ab36b5aaec3692caaf99464ee652325971e2039f47f40885c923c88edc4296a06d5c7a37710236dcb06266d298cddb633f0036f3d384f10e5f1a4c169bfade071445f0f8a330b0cc5d763f53e30a19a4d1178c869e89565ad69dc8169cddceff0bde8c1cd4e4667d03e06f691a3c5623edbd56a1d685b91b3d2544b83554085b778cd309c23c7f7f15e908d0d51ab7fe17014d9ecd1d8fb3a541787e8c474bf2c67a4f32a3d4f9edeb93187aa16b84e3892b5e39568f3658eb05e81eb9f317553570344a1aa7257dfc30960f3d9937c507f0295f8af468374b0c99f7074e839fe649ac7d07a048f6a4f48518ed8cc606e506b9c3c313f5476bb01eb8a247ff19c3c753f1c5712229e6076e8b38f35d3a2c5289db141229f9848024da3fad6f0faf5561e70619cb3d68e9090861d61dbc88d7bd21ba542d04cc7347109744d911056db13c28559f84219b08f187523f43916b46f23ab21429431d2defa091d8a95ee5666f6873499ddb65a71a4b4de8860c3a5e283b190a7fcc15aef83d696934f804a27cae1eb45f8850f4bfbe82669c48533dd7c8023808b2b23aea455a3554d53d7a0cc1bc18c56d0bb5cbaf6faaf465b9669dbc497880e156150d848dc998b848152fbb3080512bbb724c6ea0afb4fa77b69d0c1d9b0ea92d96c9e81e1c63e55cdaf755891e736eb911b157ad03a60c71333c2288ec60ea5b4e01cfecca90a5179bef85e328f07ce52bf4292e334a84e1fc40c6de8fa704dde74e379bd27e44459c9b008415b2211c2674b100f04713db3d3c33a9c99f8fe20baaf224499f69c75c95cd0edb064a26f0253a649a924e33ab69d82c971541e8da499632fd54e86cb2a30360cf73ac00a31231d15d6c83c224e3147cf3b39275c71e029de1d0594009a0406f364b53a79819ed6885368b9daf995604dbb66eeead090196bbd837ee376e82da560e805a7172ec55354f6b5d91fa462ec2a8784dd4231200f590e824fd3025082a6eb50908198d7899b0de94c9a46b0e3c64e23a1790196096e1bb7d5d3757df94bec3320c365e39d72c5b6af248ec149769767d8205a136908a690656fa8e1ff45217277c64e54bc2bfbaf57503d9421737bc803800bc688a091f87acd18f5052158fdb1561266e5ad2bcd06633bc77dab8030baed68951a194e16af7ffc00e88ff65bee15a2d9305014c95eb0260af56ad4bccedd20f938fbe4400d45ac24a47b9e0933d788bebf9f740b2bacaf58dabf314ea287288649317fb85d8891fb00f6ac6cfeec64d72623d9f89fe03d40474cff6132a1eaebfa8f75eeef5c0efa15dd1a16eb10a773ca823dc704a1ee9f0741277742c58ab2e0ca5a8e7fb2e6a3465ef09e833de23ffd40a98415c5e30547cab530c5fc3ddfbc670a27058eafd023424284df2f9d01c218406afdbc0b218230651a3dd7f8abcabc69e0fba58c8c15a2e4e30badc07cdb1adc1ffaac3986ada7e68f9b9abb743e43d709c4adbfe03ead262e0e03bae51205eb80db115ca79014b251c702cd1aade0de019b9e3e71b18625fa6a108bfeaf49da72e8023616b280a15eb512064bf54d2a24f9eb0968e21fed285ddf4149dd66d86c0c0e622201718e2394f85170763e17797b04f5ec8a8d58b273a53a26f2f325486dd32b59618344a2a60750117c93e6bca057f5b9ac972b8d8b41a1005ad7bf48810e6cfdb91c259502561b2555016f4be119182ec6c49a223d29b3bb5775e990a889390149d080ae297e70d5db22b2ce431737f552b27a681f0e04508118ca2fe4b306fb789a2772383c0b11b4dd0881df56bd63884917fc861325f9cbecc0f50b8ae72e0afdcc79143cd97df59c87f457286ed7c3936b9a7bb6b280627d31aca38ac2296c7d7fb7279c662260c7d3bcbd7c76dc9fc3bc1d99d71bf136bdf3104d2fa35251f9ea47afa4ebbf28d2b9a559601e948f4b1a0fb385e44e9c35eefa1bb1068accebf0d5333b610a03463652817f9fe685be81699a785fb59c8b27ae8f34652e366d337213e41e0109f35f692c78f61912a0c43dbc284c8c51b3007e3f6ea44157ba6e0ead7a68c057f3d2c1f28169fa094f2fa8e034fa261e2777ed584aff9d685ab2ba64512bb2c45c96f2148b0ac3b3e33a448dc9c5fe18c674ed5b410ef5ea4ac6bd6594227d04333efbee5d92365a340610478c75f223bd4e4db031d0a783950730bacfb8e93b5fd670ce44f102489b80c29d9e61b1190f610bbb2acb117660c625c118130ce36477682a7db1527633a180c537a8cea108aca6c4846fe6ff484c2e012c28f8368c3e7f0309b52e160fe61bbc68b91b3d54090285ae1cea9345cac175e10a078a38231f4321b16491475a7e4accecc62e172bd8ee49613f5bbe78a74de08b84c36c3f468577ebcc744bd33a1c04c7cc7fd2dd3277d55d7566caa679098ba559a8eb162fef39eafe7f3c4de7d37f414682f6d087ab87efdcf66486a59adb3b337bcbaec2193fc396d831569a1e3505bcc66ee4532e05d0dd175e6f7c3b0492020442cf149ad85aec90d1113698cf6db048f2c36257d0c93ffe1077eb1872452dd8666c69964821c65f725b75b9010fca4aee2c8c9df69e49f0020862e21b5e9249eea83c5d23702fbde1c5ce5e519df9899c4b03da1adcb252a9413ce2fbad54b29bd567dc8e0ab854f270460be99cda127e4b1933eb66581deb24a9b4e0e379d3372d96aa3e09de8cd0f8ce44e7b4364de60a7f8eb1338a47cd1cdc7c391bdac24c569bcaf6edb786804deecfa551f60d77332ce1f116744ca6d664b95f78e8b73396426c074bc181d0ac6675909f38647042bd00bdc6703b29b64ada3c1d7ce112f6ab0688755cb3e93a1ee68dc1d4e0eaa70a4faf61669c918dc5570035fd3e912f86f7a9df3810825f261c1d117be11eae17f7911cca593999ac8823c6d438c25af43e24dfee469e5b97d992e848b6103fb256c8334503b942137127db62d4456a86fa8bb040f861c2a9e7dc74711be8c5c57eeddf240460c6b387be2eff4c4209e4d3c3132bba2650e6f9e4aa36136e62e5501c7b1e706f034ca64e9c0418b8f222bf7f431fa075c38cfeb0f34546165f1e83700bd7aeaefcc03ebbbc0142e2cee7a1cc6d68336b36c72dd32cf9e33f9583e44d8de202b70ee709b9a716d516838c66fa511da9d56ad6ce0d1b09ac1ae918d1c12438c8b8e6f4d7164aa4aca86cef5cac513fa23d13be41d576b449e4d141f67cb6b11b3cfca02e547f30ef570e4216c2d892ee6d5f0f271b4f6b4b2d1dee01cbc1673df015bcd831dd258ea26dcd2b242d212a1e231544fa919346cce189c24f65d0b32edb18a870620500c380216930ca6b4945e3688da5e50966132535a88c4f9a80b3034f3720fa8f9755452c71e18566142e39614ca3587ee491183e3b0d753dcf3d163205a20e6c9ea572bb419daf78ca548f88eaf395d1ecd5f7c8968749a5b193924cd45f018a43d392794f2c3b9d8c8e0933633956345535f6999b5453aa152c8fb2c98c19489875581aa4bfd68b40ea1f8c0227153c68d1ce205ab7a43480de0edb5585a1f45deeb4e8e09e505a02424f79d83bf486d920107db6c84ae37437bac8e0ed61fb253faa0e8bb4ad606475d4d12e892acb7bf2aa3fa3d854a68649d9531dbeb9dc02930c899337821c14dd450d198ad89ea122ee46b5f994dd643d366c53cb00d01af2f4e9f07ca165989e6baac6a646e8883e2b2b535257096f9deb54b83c22729501e368dec047579e93bc3cde1893c7957788021db8d04b4f6bc8b6344a6acabbf45d9327a1c4e980d7ea48674087b93a0c5ac4593aeb53aa8f3901e9492eda23fb9248053154b8c7c10f32161042aa1056f62cdd2c37ee5b499dbfb6156cec666fb3334f78b886b76eb6d6c042082b7b91b19737978be4db1ce3b683d45055b14a95fd405abb9bb47943b27e44f9343244508c8e8cd75dee753e2978cc532319942473cedc01c1313b2449e053a2f741c1c0f2006adf4c3683eba43565f55b71e79c9d23400a73a9f3e209ef1c0a8e509c20a004a6f38b0800b49d32a447681385a61932c78b73cd58606a1a3b45c4637680a0f6fefc518915b172d255e71778996e42c15fe8085f1c8901bf836aebd830647c6c194df14c90143fe07eba2703d7e2ae48c39cd0213001e7d813bd33de29bc626dd07360c80461bc28e09a10d2d567994c646f44574ec1c539154ca6102be88b450b1c3da7a279b945624b0e5d42a6e464b8feb2155f59d1883b87b1f07909f33e99e6ab87605818938658bd496a78e3b87bac4e8e156ffa751a790530e5fcfc35795226fdfd8f6a24d9d2e205715f40d91867da9e3b4201e9befbc2e78ddfcecc649130092ee8a05af33d83a5d5177d87c4a26b3cf883db34a0543c39f991cf98cfc8031e559afd18acaca2d4a7b39b15ceec10506a74b1388b923529e23605b1d0ae473095562444d795045da2ca514e32a78d33b8f1c2ac31dde86120ffdfd9b5e657af4ad3dfe5462668aa2ed2e8a0aeeea5a97415490c6296b642973f8648a7bc8b1d0fd4d53534a56e5623eaa693ad9754c8c62543befb51509cd753fa24212738731d8612acdd33523915509e239bcf89b56c77e8510123bcf77f1a9598bb7d3a4dd6196a24837e7aab1cbe0bc1b160edea868a48e3b82d2f08fb2eedc8144ed28d450d5ba4d179e2380c0da9006c6b7f401e120a8ed4f151e913733ea5f2dc5678caf4c6ed074117b40ae6163f68cf2d18a54b4b3804e0505f9246adfef50846c35a13fa170afc5e5a89e5e9d9e84d37571e3d38da21c517ba47a8af735b2ee665a98799b0e67e77ee6d9d8cbb3be1c783a9a05fd8f32e1ccf70130cc532956b1d8527afa0b07fe6a75ddf7ba02486771face552ae2dc1731776223dc99f415ab527c0423d666f64e631608d1b7ea2595a04c89055236558106bb04744c4c75e50f9747d077d4c71679f8d78c3431c9025fad8016fa33115a51c5a2fb3f5642ac31209d8147b854268b000e66dbeb2b366c70f1d85df8c065a62ec838c54e4268496ebeee53c0bdaabed150830130e450557c6579b28207921abfabde336bf46141dbfd29c999895ac1e0e98c24cfd1adf2b1e260b7b403756098b7332f4c9a116c5025e3f96a7ff406220fc6687c24a16f6cd145ef400cf0a2e0701d8b92ed4b2fef8dc7ab748fc584a447b29c325ad5f66ddce2cd49710dd7a5b206b8ef010d41964b6a065ccb96dbe36e91326148560fc16358de3416f23641dd8bedcba109ab7a5240802351a386f8467f99b9af0165145f376b1fc8fbc3bf8048381f1b248f6664b195b10934eea108f90082058a7316075516762585af681e72a0d321dcc62781db93fc6aeb716a838a7be03abf38b0f6dd46b77738d3f53da51407442cbc4cc230a72ecf26772ef2cf404329f189451208d00d2150164307e4cadf7b13d8862bcc4691f31ee184d2f80f253d607e08de293039326385e8126b1f842b770725d263e8ae2517d7818deaef8d719678243e01cbb7aa48eadd7cab3a21aee1609cedf275d4ccbb4317ac29c055ae1bfe569702fa568c118f28fbef0281dece25274454c630fbb002cff84d78b75ee1709a7f1d4fcd24903f2e27c8850146e0620dc8866168e03df9809621c30dabe42a3333074e6cfa635dd373fcbbabc9c9fea9e84dc0ee84efb2678812beac7cf42d8062b0d6b0424244cc96f2931a48fcfdafd733765f5b84df625569b414b5d0663b227dea9dd94c1e2c3bac9add2234ebcfef1be30e668384b15a616ff92e6cd50b01993b3eede1f61a502aac70b9f9d0cad5434e981d73cedb2fba8d8385513b5bdf3619383f2ac142da8c2295d206d9420b54ce9afd135574c73634f3b3bc3cdea8a426726b6512d66b38d8180a5c2ce5c3f4e1c67180ffed20e4634ef31c9acd799994ff80e9c2bbef0cd9b05d74334a7a53636be0a20a4840506f7ce31598d0b2e4d7077ee93b0356886e59892f2b34f810ae33ee1e83d21a11adb862259d1e7f441e5606e476ba68a5e3d87750f406472b7660ef00e2c18c893b9af22083cf09c45961f174c2fcb61434abb75d534debc3266f309c88971f466e7f47eeb839517ac81f67412d309bb1d5bc6e09bc62570a21686e526a8428f2bd21f9973e602cbfb0bac3b8514a00dfa3280638f6097a849c458196e7c342963ca5eeed6e84453c632651f3465e1589ec30d5baa4bf98f0b1bed2d861a6801b4b28ee3c07dda54de0a1446ea6306ebe0b34be8d26bfcccfbe434d480d8c5480dba31e4ce5d3fbba4e208a4a92a1af8628c2a43e0fb7046f4ae2fe90b82c40589db6c4062a3782079707041e2e91a34f00f8edfb5c80472106c9a8f711a6126d9cc8d6b517c3e030511a084c34b058473589540dd511a1ff6542244e46ad0dfe5078f9b91a809aaad43820096a3057768db910c1beb276ed3028cf0c9210cc00e435f0e515ba5e5c21e3285e4455c08d385212e84357e8ee46e66c7a961728ad0f3fa6dfd60f00cc70fefe80379990d83e5db1480ebe7e9922d1179295cf366ac7cc01d510f7af523ca9940d87e55f0952eeb2d2712e29e21b5d75a3c226ca6daf169061b7d532cff8da94bf36b8f8a03d306b35226890e42a9080d36b1db9a3be357b1389bd7ef20bbd7161c4c635f36fe56155fa380a79c97b63e52886eb76098380d2baa20ad68216d8d3b9778e903ee27774e03cd5e90b5c845902f858acdc039c1b8c4406b0af5f7233fc2178818851a863e9b9d023ed21a230458b360728c7f9f66e18f546d95d1033e857e438fef5af6095e5f3765af6b102fee4ad9eb7e698f97d273f5e171746c7d7678a5e2ed0827ff921a42c458d0f1c1c43db09f736cf032fb28067737c73f280b33fa46d8c065efd4c058fa88d30283deb6fc555a36b0505c00282d019cc1abe75d8aa9814477e918036b57e4446a5fdb18a83a8ec09e2d6c6f53e60f0fe93782d522ab8199a5c518161ed1ecd1e0382c90d3fdc9824150ebc26e7399b35fda3cc3d303f5206c8faa3ab9cdabb88ee7ce0ff387a511e0eab875913c105e4ec7d1002753eef49604868d75ca297e3b521a7b3ccae59e793b23e70afd46f71b53e694797381aec31578c659029c8aa060355f9471e496b23dca73d15c0670b50235f735320d526186120fecb6cf53833f4d501686c99e8f508080838785bc3e393f22296b80e21ec0af199a65a3f4b213017f30f62a93da4423d828c35f87587b790632367982457b2c4442ea5367bb8de6dd767ae7e93781572a8203fcd82ed116f2d571ce89aedfd21572452f6e232a22bc97bf5d08738f05848178df6dc991b147ed7298da05303199126da28118b55febf31d09a4930e53a453a65d7ef0a7e879cce7d6c81af1854f4655e46b32dcc51c15160caea20920ffd689db66ddd81b4ed5893e965159f68cee9fcd1b1d449b2d1b66d490cfb2f60f2476b111034f94db04102c1289fc102696c8d4daf3dbb8c5760fc6a6da447d8e98a5a3be56f45c66e0a5dff0b61e09aae3d05fe0b979eb010a5a478994ff2310176b67218fbac73d542cba3e1ee23a8910d2fd654975d67b5d6efdc691260beeec8dfb573846cd1b53c4678fe9d70abc5e5c05fa8e8c360fe9321516a8720c649ec34d24d2e88b271547cf6fb65daa90cbb7fbeb71728ef6f4bed3675e3d6956f6dadd05034b8aae61b862d979a467814a2ca24f49d9f3cfa369370961e591912d5fe80e2c2e208babe0943146ce5413dd45f69f50c3d7716e2c82e38bfbcf2cc04f7256bae9a4ff94b4d34656a476697378e6e2da06dff10f1890a55b594a1c7e090f5330494f94f9cbbd0ad0c83104147020001e213f003c0153d299a1248c77ea0b9dafccb188d26ef656fb495083c2a6346936264f79052bc981ec90214b681b012d3eecea94821ec146d8bc7dd53668f145a8864539ca67d81dbc69a133b01cbd7bf5b07ba9b2ab8efd4758794fa223b216dfdb7b51c25e4938c7c45aeace9e12ca797bacf02e497d87db8b61fa02216ad47f01456501a3f634b9bf4ae417c240aeb4d77f193ced8a8a27cb8bbe7c23c6dcfcb74806d126ef475c60b82852356d12d02e9e07bc79596d0cef7c87ab1f50beca61917087b5e1a9a3df057978ec68545f22d05da002f8d8f38c21e1d71a477fa6f6b299b714748e65fe6ce3628909ad795501f5e4546abf2f0f380f2e8c36e337934c31fd322b61a2951f8847afb140cc56e1644180475d356c6c2ec01ec4f3fc9141e8a005ced9a1e24014c13b1248f6741c1fb838fbf83d78c4d96e7f22984045533d62a918d7c0cea5922059381413f6a6209ce7c5bbb76a1af9eaf435f8ad6b5acbd8098d4c4b97d4be529f751757a305a8e3aacf08288afa3abfd5010c15ea6c486bc238d5381afc4a6156c869e190dcc8613aec4d6aa94c496322a209806c0ed4b96667d569d8d89a677c16118b697b29986a1f6bd5170844c2f5f034a8604b5b24f9c81e2aaa48417813aaa0376f8c9d794697f73ba6555db55a32142c46a0903e279265f406fb97cdf3dafb10cc980c92bf326c68dd2861521c1f6a92fc618e01814ba537c73d4418e2aadf4446afad4cb8a97c3f31a5c93ca1c33a90fbf00f386607c40470b8c967ccf8cf71c610d1d6431dbdaabbedf12e67e03a3a128b82b26a1cc7f3da678606b8393e4be541241b827acfccfd7275d3aa0d1c902a3c5b8c29799559fc6bd45c42f64dbc9b96d7814af1d80f82b03e2899ca104a5e880825f3381831efe1b956c6f4c0f05c92463ca3837ee0810e519c6fcf20813d0d6bfdd9b6cf5493ddfa35f802165ab7ed30f1faf230210119484c01a208385fa560dab0617238ae785f6c243dc0f1d2fb073b8b624e378a35ac6ea8409067440d599803f2a828974418aba295bed4bc5a24674c03f6fe8b9c5acbae3ffdbbd96e17d995a62b02001c3640064eec6711b066262b98f09d269540437a91891711c853047f4484127f7f4b8f84c735c8ac85ba6b6fc6f116b0df9a84c62b2269a585404fd93342825af1c0a95c3f980a08da79f35c5eb0a18168c96adaf2516d76370eda624791e9d8e217276a12d7420206aa452cd8f362c23e1256eac1d24aeb9ca2669b1e0036f8c8e787de55557b47e417812da6314de2b82d929394c22c8b063b05398fde33eb61150620a58ae3dab1704009b472139246b25a810d1830b001260b1de0e89639cb10e510d6d973a7448a4ce6e288de646183a51e74fdcafb77629b2036031436565dc5382726f11cefbf05d2ab907d3c18297007b9f11485ad88bfac0b878c2399f00051a87f4db71ff2a10aa3dee38cbe3e16b4f8ede32af0445ac2de709fdf03150b02722e3d5d1d947a5283e18a4e8e3eef750176867c46b29a703e75352187d52707c5210a98e85df21cac7cc43dbc7a9da81cb1fce988225ac3480ea114517e4c78639b15e5f511911dece30d4bb1e20a14d75ceff6c993773b19f4d8cfea2d1c4ead7ae014d766908f71bd90ed8ea4da42274250c8814453bb9eb8b21574a6013581d070dc76624afc1188ca74b8c779b870d07479e8d36390d10d26e93c26ce8f0d20c0e13feb398b044c4f0a288614ed26d31bd33b663874593b3c0f29d919da2fdf181eb3d67efdc8ffd9d38a56836d03ae9098e48a60b70448e65a365d9a450f7ffdc6e6798288c0a11185d155b2082cfc4bc2d0b1e674d340818ca78904fe6644dd0328da74e0ed1ea96e03799959aba1f6849c4ee1128995e2bb1917b3d7d98dcfea8e6cc11ce4dd82ba5513cd522049c46146497d079b36d8dcb6abaae15a681f5853640a9048124354605c5643b6e1449418a273951812dd8bf40b05acb70258b4c989f5e3adc63c0332071c4735066edcfa5a02abd4f2ca38003f8981931ad6a5ed4b621cca66335f50d8df4f61457e5ac0c0445dcbdd7d00e537c9e78b8416dfe31fb3a9020f528b319d9f3a9e54413003d07df10022398fb7f7e77a4e9305d7c1ebae2b18a2b7b4a43518031f109ecb43a72963a604da22f823c24a48943fab92aa0d2731015793e44c4aca90b232af6c27b8df88e6cc5b76f34ab7b3348dab459b02c22b2d5924a4d3d49c1ee067c1623f04bd0986ff89470650e728ac3b93f1d3c6b19c4a54b9d852e2690510727b0898f5b1aca828525ccdd4ae942f4119a734e853a116637c9558d335229fa020c1e261243e6521d00688daf5ecf3233a1a1a349ada9563c01ec33b427238e9c7b9f1180c55ac33d994ef656d9a1d3754e0dc45c7253e95228cbbe51e56616bfbb1e8f4b48cf242046b56d003370ce304b30f2858980f301c01508a17181a59d1ad6c4dad5c62e7684319058c35dc7cfe4ac084f9c8e6b6ffcbbbd4582300de2adf61194f513720c4b371f0e9ae1e6535d216e595802b79d19b8d0474e455787a1ef4bb0d29de08a0bb86afb0f799d58874dbeda0f046817d1e1ebd9f587f6853c3666dbdb8253c8b01206f112fae0a1f08d9c435c2126ed70a2d047d2538e4d12104cea939b42944402788902e53a092b62ee8b3087c61076268825d8a5da40e55dedde385b91b9b0997031c30384c702aff3023cdecb9f95d5cc62923119aa7d55e0dc8edb06d0407aa6fd1f01492cde0622bf2f23352192a55f1e2cffcfaecae7ea8042c4e0ee4fb2cab3cc048753326335693dcb358bf2b1e5161e365c12081fb1267a3704374332f855c41f11aa457790d62a319c328b121c86b0e8c2060f2aba6bc1a48bc5e501c90e7f6a0570a8b1f74e5cc3ab8077d55c7963649440d15775c7c0e70f0759daae40eb5c9dcec2c90a3f9a7c1cf24e3d6921f80c5af6c3af7f4b680c9211c65ca7289ea66b3863972d32f371239fead3e34a3d6a9ad8f7d22bab9c268e72325f99f6fde3f45100e4146cf9cbff53af26972dc39ca8086f70d2967c0fb3bdf31259564abf7bbd2e7146e312bc686b53d83d5c60faac32d38ed7fedd9409a86615af8b8045ced6616ff95ddf4151903180ac767f8e624d6a2d7d863ed437528659ebbd3cd7cf775aca4474498a99c2d7473a1801f87542d699820e33e2e42b49d8cb2892fe1d95541124a2132d6211a6fa1579dce56cbb4221ddcab1899533ec6e0b9afb9face6a8b7f0a39b836efa92b9c53eef2c30dd9b3501ceac449fada7e6ae72975650ea4e771e5c789fd3eb7edc08184f70aaf6c21bbfe4923db21a961ae847073ae2acdd701e5898b6c19da52b15986cd1aa5e30374d164a5338115ca4d63510bb71cd42c602ad21aede1473ccf7ea0586a79cfe3a388867126bf63911cbc3d25d06aece51f62b198aa0ec40fa6aed1760a23953e06ba6b4a0bd9837a83112efb516dcffa22f94e9685f23b32729b1d3595251dc2f0e5c4ce46c0c14a922180db1088615348968c1f4c945f03d485803a541320dac2cd5f93eaa6498ed6ec06ec550116145b630b378bcc6580049dadf9adc744b3f1c7d38cc61a6ca62a506d9b2ab1b8f446e46aae776f23dfdcc35fc6e3aaf5e915fa72db77c3461b3cc6b31f031ccd170af2b71090204247f48fc314c4bcc892228210fc457ef63bfb18e381c2ea5c93324374c5e971fa3d58b85f333ebc2e21d38a0bf30bfb8ce8b00b3e77ff868cfaf5f1208dec01e99146a8df1a7fb79dd753e302f639945b908d864789e79697d6f120c18d599276c552501197fee117a845baadb68a801a87eef5aa57430152ab195d2e29fb2e9c024ade53e1c9146e4bf798c7f4acd466a55bfd74b5f0300df1bd12c422ad2da181ff0dd6c5635d4f583511454fbf9e969905f94768e02902e2dc103dc6f0cc0ba2c1bf1b934bea28f681ff84279036403bc00d4c4313f4283aa5a2f18024c3c98371c9bfe770272e06e5b945e984adcbe3b68dd647342b029d5db50b1b9e233a1e8c4fc06e02df46447b308bc2af01af55e08380b78a38a1b80f2a0ef38507577fec4a4e476fc76d9a1a3428d9ff648d64e7f807a63e466b82835bc0190fca1a18b44ea3caa1958a18870abf9d728b446ce4d225059061064e46987feec205a712f5739d6261a2aef218bc3ff1eb46b1020fb44f658ba1f70cb79d676369a23f4236149da379abcb50b326df0559ec371fefcdf95ba18d4a5544a4f804ace96ba4e23acc772ffba0d24c47539ab9fee0bf7a15f7a4beb160b55491be602f4dbeccaf802fd346aea76abe0466e08141ecf1bbbeca28677d66d79ef8cbbd70bbb7f71832c08d361c4989820687caa8a97ad538df9d960a2ebed08b62e0aed5c840deb53223fd66d5ad20946be41cd45c48a9347d6f131b175a1c696a3802b360a320acba7fcf034ee2dbf15478862be58a3232133d0b6686145d7091f19ece7441ba06bdccb9fe3de83d88264b237f7c9c6021f9153db41266afd033eaa83f76ef5dddb54ba52441082a492562b6e328ba2ecd1172d975741048e95dc3e1f9f7a681117be12b82bd6a9790117efac2c6444054392bdd339a37404923d28bce2ab15bfd41c2744235c60cc83d306bde5cbcb589540231b73bee04f2e5bb93498fcd8de9e2b4f42830dc849b6df526cb5f94c5e26bdf3d8d03d1a12a280f10e9651e2109e8062fd8743398340461015684e94c75d932a7d94eeabe1f9f3d77ec668b232fa3499a1e604df1a850589681cf87c8f90c8ff8c032374481c526f30432be67b96017994a60b44e4ad558632f78d1e89097787580c1637c0cecc5b89bd1f922c98609845bc157f04a6dacc53fdc4419b36deaa8cd126a966553280a33b0ffb0ed66b32e10fac268eef47d3b8520b3a75431e45a8801e5cbbfcac35d16867be1a1a6ec727884b05717236abe09a916049bdfac3f5fa2229b3ed537d4d1a48bc179e2b3d39b3cd104baf288880cb1e873a1199259db9f0a73855993f01feb0e354f2cb88fbb45c69b982908969de5207354d0559feb291e530abbe4d4390097eea3d931428fe9e52bf1c0800794d40596a4042b2b123d9be4ba1888e9301e7be8ff1f4729e19a9136cf2268e1d4792a5e3fc263c494486f01928273a8189cdcdfbf44a38efe60deaa67b87cef04b9df77a04c0b818d181ba2dde831446190037f5a80e03bb478ac10469429dbc8b495b206b774119b17e7292282c2fd0690c07b57da07b1e61f2e27c7a30781dd3ea952416097edfeddb9e40fd8874d72e9510d374b691c811c6eec989300642aac8665e243ba16dcc3c7e5f34b783360505e003f88ae4f6f7cca3c335ca783ca7eb70182a3db873830cf23c21f1a51f8f29e99a5f9b9279a1264bd43a10ef5f62420e6a6421e154d9c04603b07d895d2476fe4a074989008129f909dd2fcca0f344052e80baac0571c5a6f4b803e0266e230154062e8dabaf931c0d618c96229d58ee00b686a1760762b1ddd7c5e0d26b8eb9a096bb37d0b6e8af9a14e8c370d0fe5cd9e71ccb32bf77a0114d9f9eeb5247a3570d7679d5504a6496c31b1b07abd68b5290edd6ead888dc0dab0e9a28b393a7e723dc0c87f8715b7062040d16d56bebcd0c35c601d2258a8e248ba13629d114e6341204f433cc0222dae2f7a813026c69d42e3ab94bde41bc74a03bacfd0d8c69d5ac47bbbc05a016ebbf30aa8ad2ad49fc9e8b5e1c3e76da577bf5b4a85b693f8cc3975182f092c21ce51d92586f9112e77a9721642141cebc305444c28265b0343d0367fef03895211ac0bc5777133a03e5189516d274aa9b60939b47afc75aa0127859ca7a75cdcdccf04d4421121ec41fc0048800f22e2c26950cf37777d69fe639adf45195b7bfa13d78048e9f951f96a2f37881cf6f497318f26bc3549386d5362daca6421f35fa46ec6f7139843bbbe07c8d334c779464e76f70ed75818e57ebb8e8ca2710b434a367f2481e4b3476bf5bb5cad016e2b5089f897c6b24d093b45b7bb8439988e1d1e4008dfe2d6de40b0b059794d87322e25b0827f7cb1e420bf0e8f7c5773cc2deced0ca4d12c02e3b54c73f6308994b400cae2e06c0b29f3ccc1ac00550437793dcfdd3751302d767e1c9ada61a9b2abfc1998f0779790234dbf39d0fc5d9b2f00d94ca0058412dafd65d1a3c6fdd9fe0f9468eb8312f78221ab3894c5e2f2cb8a2b4b0f4ae44aaf6da1f2573702cbd93d04264b9f2c80ebef1eec6f0f9add079fc68bb74e0c897280efdd1f941dc0670c62e53fd1856982889ed6d4774fe17f161ca25b1516215c1fe564a2bf8d994496bae4e7d07f3817ef2cc702ef51cfcfe94a6da5bd9df345b1010659ed14085a1894e56889a378f9fd2b0fb918b8039ff9cadca37df66abf010922e003db28657ce42530b0f6a9acad08a58967a91b91d3c13d34d90160beeaf5808a4541d74fb68af8899795fac006c0bf6caf99809cef60b2e2b96bdf52c063bc2da2328be2c816366e36fdd0e69d5113ac32eb61b74c0f107f98b66901b1e98f1ec8bb95a719a7177ea4128a73e099a572574fa7a06da9a1788265d9a0b1c4cb9eb25cbd3bc07eab9095081bf60e0c8b2af02da6eb143622d4a35d51661795ad711baa0d584b833421b691ad4b5c8bf1d52987f00c2307e69aafec3c50855e28e0d98356ba65ef29c2b9f0388a4f4731b1851ce7aa6970b6a31473001fd83577e640f9afefc34ccb56208e1a6c941c26a73b9e6926a5cf3c8cc8397c6a52a4db8a8ebf0eb59ff5ecd8326eb49f878e6b45b98c6e21ffe92f486091b438adb81b7d3490d296f18392261bcf38e6440e568ca9755f2ffb02a445c25277deaa8ebabb0ce94ce277410d52a4a77e90ce9b3d84a52868adc618eb9bb7d86e9b937c4e604bdb27d1c6fd18df7307661d4a24eda7c13191fef8613350caac7c601b36ce94280edfc172e11aa13726452b93af0e61e144ce76e981a54ff7ea1d7cd9b74f0e00342133b4ca51e8dddc2c0f5b0f4544d3b531fb497b6a9db5bcbef72133404e378d8134ebade8bdb97d5656afdd96905458585c35ee6034d8a7aaeb3d56511a05d6369941037f38d3efff73d9c699e4827f3b2af344e0d99b69f449a0174cfa1067d73c84232aa3c6c9caca7f5a0303ea2782128e59e551b2aac082465d2c884b3e466b1c8e7f512415e6c9fae2b415a8b71d04dbabe0ef6ea26bd4103f8c70f62e005b61e5ab08c5c00458a7539cc5b2e151e85a6d6a1705976766e1081930a183fcc5b5e023ce008ea2fbf5e3a415e8b3c6da66d7122fe2348e882587c4bcd91c5472789a17b7cf7deccb34f181e101aff491dc40fa482b54e573dcf1ccfe0b49942a46f025d5b6d6f63abe3a76f03ebbc52bb79f9f4c9ae2277c2a6088460034719181f9da5cec9a6e39d3c6f34e972d810f5817dd06efa06b9ac59d54525197c25956a3925f3c682b18988def47ccecd9ab42c9bdbe66c24c98452a6208bc79df15f609fb4f3538898f009121d8ea691afbdad310f1f0c64a40e765ec361c2a4cca3efb6c0c18ed5a35154d9e1ab35811f456f5924d1ea9674ff73f88155293557192ab57bef326af23c75fa0a95c2850821df076210593c84745a9d26213e3561c5bdfef1d3f81fc88102d45fffd45da83bbd58bd83f4fd15a3c6f636ec44d1e38e969635e761135978b75b3c73f3804cd6781bc7300616a61b194122e2e431785a449bfbff5d1b4010b880713498b223d0b6653b386d2157e65df65160a109f140403da4c64d822fb67a4d22c652b8c9ddaf0ddb6e4383191e637715602ac0ededabd8cc54b52a7899d4809d7b64bcde9350b185560b660c580ac95875d1777e40b686611be9e722bf3c72ed233678f840f50784d2d58d6f2a11ee5666f5f3c508411ec8e7ed43fb0ec4426d3738efe28c9a9be4edd33b356c7199ea89dd071f30cc0a6c6e6ca5e8e4354cca6e39a6f535ffe1658955d03b60ac986a617608ab2f72b606aff3ed7afcc9f6d52154df0dbd2ca290aaa3978e8aa6785537c316602cee268c140ed569db203edfa153197bc92e662cbff2b30f3261f05171759ab9e09484187b9132eb743dc135223314a8e1bac358908790c4ffba8029e2095ea544c81c42577cff6a984e2e6a1d5a9e0b8362ce2539ce0af423e4e43ed15516c4ad6016e140f42dfcc8bfa040c4224109bd625cd6c118cf104560b4edb0d11ddc702a060e435d7f93bc8bb9b165d3d6ce955f0914cb734802b7f20345ce2faa1bd545a46d738527f7fb9f4ef73f690c6dcc5090173b828141f93d44c82b14b48153a8f3c7941c3d44d5b6d89e19b40c7b59cbb34b1376e24c460b75e5f5c4a34b34a1574174a690c063a0ac76000c42a4fdc02247cad9ec1968dbad6e37cad100af57a271039796164c0547bc0ba7e966289dbf7fe2d962a5b2b694ab9c493cb04ee6e423ede9e964dbfde002ac42c3aae38553a9d7f60d9d0fd5331f1854de4780c1fc8a2c5cdc1ace9c6a2d3719450422cdbe2a454e72e01b46d996c5005cc23bc1e48eee825561d4d6526c046a1576658d2cc7f4dedecc7470ec68533263a2a27b4ce23d99e677547fefa911b9f1e5f472ef73b0008bb3392d25842bf69bd76e845e1ede58279678943a18383fff4f93f2ffffd1e8848df4a162f76e1d5e6a74897330b44c0eb7dc73bf68c30177e81055cef070b90b02010451613e06739f1256bd694eef5b38cabf564c1874939205603ef57088501127ba5966dcd7b6ea9f3a321045ac1bae72a1fc8c6f7403e9c67175cd52abe673755b95d6cdd0df6e070f2fbb5937b5ba720a89e0a1d364ba37f9538334a75ba33b4805289f4c2c9a27a6b5517d04ea4706e75daa4ad8ab468a0fa31f3b56f16cd1527f10c07303b5f4f03ccb8021ad826524858bdb9d069f5572efbe8b87fa9595493e0944950f359b0554c8af84c810cfa2a4dabea1bb9cbba627423de5706d7489739c32b733e9df0bce58732aeac44014d9df0247f22400e948e268092740f2329e0a17569ad04b72cf7b64f900b2af55120f905fd12e5706df170d01b1594973646dde31c6372dfa30ac663bf615c91b4b6cf312a3d06b42409215e717fa5f02f489701ba622b2b5336542d0b5ddc6815b78304ecfe960a0d8413efef75182255d2d3355c2b2e89ae9cb021419970836a29ac8259152650f338f88fac10d19587925387aa3c5465d011e6d938b14d2439b603897e39c65c1853a27bf23f09494ae645d6e9ac92bd1d100902e71f47312ef0ec263cff0b4fa3f8465e3859e9cdf98f4674f37fb53d09cc4009506b4dbdc165322ff92eda8b8e060f09eaf10a5d39d4420aa23baf870009e966050fcca80b10732262bdcf19549fa318bb376af8d84cd2a723b7e5e91bfaeda957556690bae0b6c03365dfa8ef50b26459850887a73233860a620b834978f2c8bf4c6bdf81601af1279cb3fb9bc84251662f96d49b7de0558254e4f61c1de853cc85832c020728136a95a2f24e8f84478e3baf0e4bc57fcc0c2c48b51fc548cb16d0d9cb6551ba19cd3f7fa9e321cb0fb1616ac424fc61352b1ed9dd6196d59be402697bb45d10b50e388bcc7f2f8bf6d1654c648bfb8fb09b4b88d34f6fd8a4792fb3534af3237a7b120bc188a7e4bf9baf406d40b82e4078083ef1a69fda8816de053d1d8bf2a6612f0c52912a48eea7e702f5349c2648e5bb53dc8f2c4d09363b418f2e682ccb14a40cc91a27a03729e2e4a9a1fa4d0cf51615a02dff1f36dc43e3d2381303f414d81ceba37dc6427e60c0db7ffd8675661e90c258c96b941fb8b8a1f9707c42e64b3f4ff1a52401b04800751044bb2fa7ef9b0f996479e78c24867f810419b6441a65bb26bb0dae7bef52e68d1a875ed4de9683dde9414af7228005af6572f780081a7e6be51c9e96163063b71d52297d4eb5c68dcef68308cc0bcf3ad7c15f78d531ab47d252683975fd7ee28bfcea3870a1ee4263d463975f5940886a560701b47612980a0a4c20ec33a4de85a17f2392f97e8a17ea6106938429156802ed8a8ac7a38899017be862e06a7d9e34c2eab09232cda000da798a4eb40b1479056c4dfccdc60aeb10093e97c602cbf349a98811282242e0d6625ad86ac6818cd98b90890a9e18527152e924ee394accdc323b35d2430218cc526d6e106bc2a345d5be1798877a56727a65cf6545f11398102eadd021ecc32a4542e16afa45b4e4209181bab0495df0f299fcc4e80aa92927622b7c39f1ddf70ef6305b9f82ab41f6be1f4e6117300bf90c6ab68c92135603ae3241425eeaf0fed4cad748be5881c14506520a25060a45720d5e525190155fcc6960225e00bd8fb6ad609d7dfcbb5399ee06083dee75a50bec02834b3c0aa1f04d7169177198c97f78465c1b230ca16b569d82a3e879f6ef66ba1e22ca78b7fd083d951f2c3f082ca2af591548f2b4bccc7717612d93b20285b98868141f2cdb293833a81f2bfa6dff4491b8fa1097b0e35b877a87a9c4f7a556d304075b30926469374db8e1c7a947fd60f8b32c535946d4ccc70ca8a84d51d9fd442ede63238c679316930683335c34d45a9b09677855b2fac7eeb40c10f656dedb37ee1287b6f4569241c488c5627a3719d55b532ccdbf649e370c2b6d3d8fa634ad3f3488659ecb825710058dc39aac59c6a32f437f7f2eecaa2e00c496ba8b3c8e42c6e5a4cc1a537d91cff5041d8ef565fbabd69b15c2c5cc7e1cf81e68f7e77d07171cc82b96a180106c7a4c9ec0fc6270f404189453ededfa3a8e3117797f04f0322ac7ea618e1ef726a92a7d2ab62a5fa69a6b951e82926e8f2d4474544a9a44468ce74c538f45fcc4465d8807ffc568106b5abe99934b9e7cce594e27bb31a013c484cc22746428829975354e2f168389c67325329ac09626b379a781c5281007a4c77372fddd47c51f6ef25a1a1a6db53c0a393a78b8a8d2abc275a71015d8b1acc750c37d576593659b277ffd24f7c4c3b0435e79bef8c15ec6634e445541393c6c0e286e68016015ea2e5751a417e35187dbc78062e904435bbe0aa092e756e0fe0a9b7912335fdb8d4f8321acd14f4cb85786821b1744c56dc1b44daed8a908b2dc1453063385e9633924638dbf1e7ca3f579376e60bbc3b787e84752b8927a29f02ffe3c7641e398bb97c661a6662ae31a74516c5b2c92f8587cbb16320787f3f6e00100a9f826c3387238573a070770f2471ac39229bc6134b2205072bbb0ccc7c297adea35ea1b4d141387695899b7c8dfdc315348021829240b03c60ae8715b8831f3a6da6da430de968dd910aeee198431b97f24cf90334274bea49318ffe7e74d7d724a520f7d9af1144b87a71291ce5578bb368b7e20c1e064ec336fd2b2a590b1151e992c9004050151fb17106ad7ca8d6c9396b78eb8678043abe863a2f7c727201829525d2955045ed4db50ed707aae60d348446bac3910fe51bdc866d103d6bdba0d72d52249fc9e567fcea9187393f75e8dffed4409a9647c41a9142f0fa474c0dfdd6376f88b1f0183bce279d7c67fa01f8e4b7ea406baeb1615b221e86670910d10fcad46f3504b1bfab84a0bcc09dc93d1c9c9ef4756191ed2117ba9a91b1d92ae1e1ba70be3f0713aebf51dbe4218fd3b322bb5eaf93095041ec1fd26c0ece47efb64e5e986c276ac6d1cbd51d92bf6f3a39b4fe916dab9acad8c324c58224c50981351ef46204e80f6def5325942fe8298037132aaa345b87c7e6f25bee5399718c7069ed63e3e1dbf9c6602b22027234ba797bdc93f235dbe207b6dd1e0a184ccc3851bd07b35fc916085b6e500996460e0024eaa3c776f8be362b15859dc96d8ca9c2e822ef58922a3b5a669f49d33863ece12b713ed44da56bd9ec0934c679646faffb3311c8a47a87ee89a3af56861d76af16c9b22ea59712262e5f4681a43081cb40b35ab1ef06cd4d185433fdf509587b50110410e1d42301e0edaae9457cdae57c66560efd1340629f9af035bf7a7df5396e55ed6208b123739e990e95078876111bdd2ebc501adce59d2b12bbc39040121d8f2eafe62994c936541185872a68497ccf389ecdd63d82a39e90469f6eb96204462b75c372b290ee5632598f51ef80bdd2d3693155ce7367b04b758999d7a122baa16a83ee3b54d1f377ba0d6954d03301ac74e1126459b46d11b24994344e5e4937d4468285f29efdecc10c2c73b4f35844ff3055c791a2103b55e8d3a3835b592094f09036ec0f02e0006bafb6e35f35dc134fabd5428cc3600310202fc28834cbbf61f049cf0aa16c59557862713e12ea5621b7cb568756778cace1403abb204573ff855d82eb9aeb1b82fb3aea3e51ce43d7d75a9034d30aaadf2600cc90aa4d80cf3250b239fb6fa5683d25ca18a218eae67bd31cd193470e7c8f097f4e149a51322108694b64d717e6729b0e6d5ee67cbd2edea847b86a31cb09ff202a90b67f6dffaa7d1d5c11d80045df5aa0a73a89cb0b7b153a8ef4a6074752e1997b942c9c8c8d5e41a63eb22b537954b442f342746fa2e59812be774e29656e5fedac8d5198fde7bc228196fe48a8419b6395c91b311bcb003daa6e68459309a59a5034983c0ff835e5bc0e5e1cc4dae5374b7d82dc05fc33c3bb0027d667643d82e597c880b7f356f945d5adffa5803b000c48e3f248939b1d4bd15628032842a373dd27b812fd854f18843eebde5de5b4a29a54c490694081808d207b8bbeb9ed591581b8127c31f23e80c20c39f221ce52864933fc31f2314652cbabcfdf972da2beb77c68023794a07c09cf1f0afcef2c3f3a72f7bc0c07704e61ceedde74f81f80ba11d0bfd491bb6c4e8e3f7abdc744f4e16f9f459e47b9945c2171e98e46a953cfc9bc53eac2f59ec7b5966d879dde765d8241e2ca4df7758c068e1e7efee2b79f3f81c61145813302835370c71c3cd0d3637d8c81c21839241c9a4925ca26bc51d729b6b85db5c2222222b8676b082688726b296b0766c8ecd49a25235c1d3844a8542f9104338aa9534acc1a09b6ab5ac6ad9ead3b909c579e0aa66e23c68264dbcc97593b76d9a0446a56a82a709950bacce103864edd81c6b498d4e1e40864ebe60844c79a899ba94ef3f70c8dab139d612d6126b899593465a6b1255e0010ba44044122c1805dd200548f8013ae205339043b8911d7e5e76182740d97f9c48f1e3a4e74e1938ca6f7c0847f58d0b864c2673caa4647072bf9441c91c21939249e5cc59e7ac937ef48f1dec1af39bf38fed980fd9bf391beef5b44182211783820d61433fcd247f43a68dbc0175c3cd0d36370c7103ea06144905243160c720ccb7929d5c95a4725562936b4fb62f331f2e59da97f6621863bd7d9bedb055faf0cc039106c8afb3d4b1ccee8797c3bd683b1df69758977559336ef0e73af17bd78131f5e37b0c608cf5427c498e8f591c0f5b9e15887df952888049b63f2b0f9d43901e3acf4e07f7fcc63df8d88d7d5d6adf91194660f9b28340344ad15a072a65120ef0c772bd7d96abfbe165fb30cbf82431584c6018490578ce3967942fbf90db0718e3d70718d3b9df06172416f1e1874b249e9ac65dbceb194960f9b387b47e0a9114e0fa1f348250e464ff207e8233b483098a39e27fcf421ffe407193df4d4fe3c7e7a8bec3c8e8d951e6a0bbd0165b84639f6c57a99ae0814db44ae593c221254ce1c0f103f895b35e5a6e7ecf29276c226b244b3881b4155d037eed60d7b03fb91ecab3dba1ade81af3ebcbc935516d221e6aae1da5332a7bc02ca19d3d7812a56d642be99bbe714232620303c98820be1bd9fb09252001e7439f0fb9238e7f038945740dd8357c28161171228e51c41972f7a0e04f060e4174f09721df87dc0423ceb501fefec780bfff219f253084fed8e7907be8f02507619610810a0934606c1491dd44cb40934a09244ddcae28c16756d23230cbef9b5c031c5f01d9210c22b0ffa06bb4410533f10738451850453346e40b963063dd867e0bc58d106e668cc42842ccd4298486683263440eb18599da41b7e1c80ce6d7daa002d8b144a0be65abad6f75f68dc82094702ce577d813787632dc6bb7e12b590e7325ee07f884123c4a668cc423cc89cb6eec091ca3806377c1b0933f4b030a3130c9fe77257bffa402c682fbe6ec9c9408423f48520522e4942d780290adb53e410114706830655a1c8b0e360121781234633b1b5da280a5c5eda08f6110be54fc6520d3e6e44f6e4829f74f2350418a991fac37e2499c20c10c13f18fcc60cd0d29e5f9328753161daced58acc55939a2956f7170661ac931fe57c2a280e7d36c871c52caf167db6c6336640222767ae71631041bd8060e1b18f7af7c0bcc91e404f60863609017050e7cb2ff0a908c96466225c39f238af0c44e864219fef408656c9bf02d0eb0fb86ac64fb99ed3ecf5eae2f87e44a2457cb83917c795c1c0f9defabdc247f3e0c89084c12ea25ee327f09a400863f3d3ed925c39f9e548fedbbfb70e4fee892bb2dee3a6b47f202eea694d2162d68f7376985b3d165fe5502cc52bf8182686866586a17a455d1657e6b9ffdfceb8a1d3421a443832b860086233610c109a4284145124cb0a28756bb6f25d38f218dc0242f603a9f6361f1db77961c0b8b978f1fa33f5bdc84b5f8f934b0169c673b5a3cd63e88f2f5d5c76c8ffaf18b7856e962115d03fb1cb1997cbd244f0fe3800fdced8059bb9fe59c78ea26ee327ffaece4f9ad2a01fe5c0514427084105a20018e0f666820f181179078a204152c9899fff3bdf9180ef0f7593b21e374e4e0de7c1840e0f9412640a4880486e8b0830532e0265a6b87f1bc97f7f871bdf53d2eacfbeaf760fd11ff8bf3af9deda8a414fe3e4f37d5cf81551ab593dfe2d918da9c85921e9b9ced062db4d65a6bad7debf431c638b14f80106666b4d6b21d2e65cbda203be4c0814511387e5f9d34027fd67eb42b22b38c5bc4b81f1a0b2bc802d08c118cc7044f9861423ef6524422e47823e6f825d4000a2756900229392062c60333a0c2d0124a8a5257ccc47f4f2a81c51f0c7fae98923f4f9de0881c1f66ffc1920c9fc8f0c789a2fc83a31c93e1cf0f9a6427729c48b1fcc8fe4256b80e025545cf4c8e23e15e8e7bf1bbf3173ef86b544a89955fe988b84b7ca30ddcc81f0e59c8fcec9b9bffe190b39f5df62421b07dac663dfab7af7e021873e347c098adeb1bf8d248c0da37118c51f9d8aae812adff68c89285c530be4a07e10b8ad42a8bd690bbfb2cf65bf7e19c23d290bf467da4fc7550d0901bf9120122e8573818051060403303b31b1bd538eec5c76ee4fe2ce58a04118191047f4384c8b72f1fb35e4a529e1fbf953cbb1e7223db95ae51321389273913618e0f135de2fff820287f1de4032015200c7f9c58e5af5126f8e384909b560dc54f70a6543232b2339240a1508d6a14910b134f18420c1de6095400ca3ae86b544efc46c5ef94f7e00de0409b00bec42a2511f0257eff88b56b22a017df336d147c892c3e62500f15c5efa05e29b102dd85033b64d742eec5200f023b13165e4a61da7f0f65b60f5f5acff219cb7d983116893b8c613769efbf42056ead83f16c0ce1c9bfb81b36dcf370e992f21e2ed9f770a987dac050028eaf1561960d707b160f6e0b134f4080f063530220440103d48c0192482289257850822e24090186992c7c187124f35c421270d37e1a604af1576a704d8c314697104228637413b44d6c524a0ebec0b7d6da1823e904b8df5a021fe17e184248689f33462b4cf1185978748e3cf4183a368ea5528c72ce18ad3045c6185530292a5d7dcbc9eeaff2e76c29bf62521ec1e9d953cad9d1dda74f29a78c81f7edfd7ec33d699aee36a4856f238c7b6d6310eecfe1306480fd3b27e56e93a77c7984fba5306167eff60527ca5f112f32f38541f819c9b4fb8e4cabf3d5d9b7b8f9988da86f5acbc14c230eb5d6d2afb5da4aa19521c37a4b96ddf40106a10ca491760d991965d780dd7c3a390bbe480b5ffc0945ae11ee95303efc1602670882c456dc246fa91d7e32220f890859c0a4b51697de638c30b48063b4534697682dc658e27c24bb436ff8229ff404f64426396a9f8d132cd2011c373608c464fb243fb3754c68ee9af65cadb5d65a3fe3646012df62dd67f1d57db8f4cda426d3f49423ce795cd57ddec4a5f4dd01c6f37ce52b5f39cfaa7bdc6547ed4854e0f9d49b583caa5211fe9ca70ae91f029329b64fdf79fc646110619867e72af722a57376d0a23faeaf8f75bc95fb8a741602dd132267679da748e7e94ddc731ef7627b16b07abb29eda8f5266e82de24f67c5a2fcbea58ac1732bfdfba3a1ff3fbeb1f71293b9ef0388f9bec4797e25e133ff991bbc4bf0103e379ff3cd18da6c09fdbe4f896497ff7801915a1a77c07c6edfb289eb6ce8d8e722cf551ecefa2c9f5b79428f1db0873588a25eb461d900074e9278a925b4afc58fa12b6273933a4c88d78f22cf817728c40e418479023eddc08bec427f9606994e3872393eb3256104208e10730d8f2d1a13c1fab75289e684f74a14f2dc69fd16e88c5bd2a7f91de05e9bab83f404479e5498f95e453f9148312b07f77f2fdebfeb5f2978acaafd86f298a50e5feed9fe86448840c7fa608ca18777b860ddeb2f6dd6d1803b1968ee3be95a7618619585a48a4d75ee545c9058c2f95de05f7c128bde03ab3644054544a1ccc2a2e545c904824920b181910d2bba801e6f0183060746ef1c2850bcffa8d4ac6c3332ca93cfd193658e5b58e8587ce2adf83cc2bcfc39657feba7ec56bdcabcf2203b2f2a4ae87ce2adf22e3a1d2ad7402b80d63ea8c10e0cf5101b8b4a754c2b862f555fc9342b9d6674203914f85dc1457120a8cc97ec8a7dfa287ce2dbaafc5abbc7ce2a6ef7aeb7e7d09e4269cfde82252d936c0fefdccc7f5f729267762dda93bb9fe9d7183b5771d3709b97e05c6b8f8fabe0218f382f4f5b18b2bfd0b8e872dff8b27715fe945e782eb9c654056fe5af9eb31bf2f38985d7cd73beac7ca5f9fc3492bdcc5f5d09954fffa54b0178c00d7bf643c3097db30a67ba87c91cea4ce6ddc53e9be955fd1b219404894104fa938aa2685b2be0a59c954fe663e56540a701bc6f010e237eed52fd2d96ddc9b01449c2102fc794daef55d0709377ddb5b5f856cdf5fff663fecd641f7ba73db304ead892edb35642ac347eaa1e82618739f769fd502a62f698c6aadf5b60507330b0e739f4592eb6bb6c64df1ebd7f72a914097fa42e4fa9f352257b982fa979335f0a5765887c435adce9118824372480e612cf4eb7fd846eeb79171add5dab75dfda12f32ca32471a1a949d73540c108d9e9d489964bff044e309c3b701add14e3c7591bbf85bb798a66493fd7786f2673310f9b3b93bbf090277864fb440c76fb2bf6cd2d324fbcb5494a9e8393c871f410e4960071d4842c0fd9853eb1807b821ec702e741b1a887b12b3745337907bed93029c919ee09a4a7da7b880fb71dc9bdd2763d8ef54a38cc04732ecdc2937e1ccbfd90e8b511c99dac8940695712dd740eecd7e3225cfbfba0f06c66b9f6f1f54fbf8fc29f007897ca08f053230844891d6c1bdf93708eba023889bece3e0a6f9ff0d8510aa6826b48f8f6603238e30e208a7d9115f68fafb9470e0d932ad85597c306624811f9b5dd3a854aa5229c618638c73555ad9d50a8662506484130387e687956c9ab66923a0cbfcc9442e658d035fe66331704e805737f37b157f5abccaad8ad666b25174d21d6612f29c4f63dac8b33429bdb680b9dcb99da685134c8272c06107f72613f29c5cc81302d0656634587e9c1f3ccaf3bb867ea3e0cc8451b02028fbf083eb781f1df4e7c7532399f36df7e1d935f0657a26f9607f1d60f3bb22eecd0ce3dea401432346aa3e0d162293889b60101bc486a0a00a694783e44323d7c009940c7d9e7892bfee69a32c612c90f4acc5f8a1cb4e877b41702c954a18bbbbbb774de39efc9e9e9e784202244450af7ec062982008e6201d82f4581d38f440d95d9078c23a104017f917126b0911d44918f4037c918fc530418c2230fc8122274bf9dd13adc5b854b216631fdc04a1e83822a59473b640f2e353943d3af42a8411f6f8ec45dad9702f76777737a7c32507103d3d60d64102f127e0447c7093c3dc201c2442870e1d26b6783f0d4ab016b0ee013394d90e786184df910657be4b9d1f201065f85045ba8dd6a36fb2d67770c76a40df765fcffad64bfcc38a1c42244bfb567bb6c372dd909b69d783fcf1812a96a82227cf4e48bfc30cd1444147b88123878e951dda0aa21c507171490c413d18f46f6904c6c2090b1fa9612c9e688149f9f22117640f965f0221299002183a094394fc193a0183915f316f7fb9aff3779fc54bab3ecddebeb4f6b5da7d6f653fae6c1f8394fe0e5a722fca78b892e3e36c078d399c5a306ba8fbf54d9ed8bdf8b667938c653bbcd301bebaef880f2aadf5d5028265ab6b255d63bee566c0003b36b31d963bed6c7d905449d798f3ad6cc7f4dfba0201dd641df31bad975db31ff56ba553fa7dd4318c610c572861f35d1fd2b7de5f6658ed6c87734a5c9d913bc384fd99f1b04fb37d961f93be7d98596ffecc533281c1efc8908b615c7dfa574a5213d8637f31920e70fc1ddf4a8610c2c4b73cdbd13c483ac0b29bfb8edc6caddb6fc8cdd8c4d703b9d9fec3bf388b63a1dd8f996df7dd442ee4a66ce550608cf6313b13e2b7c81a703fcb3a164dcb7e3ecc7cdcd7baecb3ce469675379eb829be07b949480572d3cd7e74f6347bfa978359cb7cd4a7b7dea7f7312c3f92e4d3b0699fd15aef6faf691990ec6ff6976a57cbb68bddbfeff987e663cb34874bcf1f085c9b6183619e1863eff045fe8c1b3c3f07fef83adc2464c30163587c7c04c098161c0f1bfe8d457d5aefc6d16fc1b1e060deb64e48fdfada63d98fece95f998f7a9f76770a26195a21c39f20f864960b5ffa61feb21cf367c4bdee9e7ecc9de56317bbac7ba766ade5dad67d20c8b5a39c8c74caa87d46a4a6ddcf6e57ca178e589465e7b86568a1470aa14cffe995614bdc93d25253f525ee31712f0b01fe9cc7e3e3606dbc598fcff47a49b36c87bd9d418875ad15bbea55affa554707dd4589fbf76fc6c3b3fd2884fee4c901b5e45f16ad9e722fba50e7515d3c30a6730063b026d8f595cb80cc97f3a50c42ab66bb163836f61b0492141c4219f99bb925fb5c01019f0ddf4b8610a821c3c9a39281a6ebe12b009eb687a713741c4f1135bd211232fc0740d30df732fc1ef0942302b54c0e310839887b32473cf9105de2e7d0326d933ff8245f1f0cea0ee371cfd371cfa1402f3e8cb96d2c97e245d025be1f197dde3fb9fbb407ccf3e9b7b5ee4980a6fe58ed092760eb1e7bdf7744d40b2560aee33b57ecd1b733dfd181972aba4a95677f3d27908bccdcb77938267eea1b7789590e30a7aedad35d7a6cfa9642c9f4baae2be238373376afebbaea3b37b37b1b19b5b79134f2369a3ee432fb103f5ade4d222aba44afc917f07c55d74d224aebbca8b33823ab28dbe15186dc8bff22f7c3ccc78bec440e05e37c750586ee5d5ffff35544bdc8f0278a28f9baec552f7a5df3ba7ef2d5759beb13402ffec6b50aa04b7ce7608600ac01f4219e1a068d136114727c26e488839e418ef108f812ff6b2539fe15534030309faf3ca23ecee831a28adc04e4a6a09f2856cdd34d704e2d3e5e9c04b0f81696c5977029be6802fefae68ba83c5d765f29065da7c10a0c66872d2ded790d5fda31d90e63104c14dc0fa56df1684c2818e6a3e69b65dc7784879ab1cf48aed6464a6ba5f0dd87e81af0bb21cc11e7b45fdf4d6e331d6529840f2947833929849d9476a52bedacad0a4a57c09fccd039082d8b3e8457377b727d735df6b2f43f0e482b79c8590edf821042cbe2e69cd93b8a0486e3a9fb88e4eb21673db5d5a594524a29adce7644aa4b29a59452c618638c31cacf87fc21b98cc88f31464ae927b931431fc90ff34bd1a5911c99a11d12fa51be77d006f8f407080f000b705d4f3b080b00bf250767e88febb187cf1981ab9d2b90081155210626667e882fdddddddddddda5949fe4c68cec90c48e89ab63c13a3fe246ac5f20449c2253e6a0c309e680fc6edce86e2ca23989456b634962deb1fca0efdf83fe90a1c53984b188aee144df0468630c8358d0fdf0bbdb3b5ce7b8172f74e7227cab03bb889bfa8ffe83535ac208613784088c09025ffae3432e07f73ac75de23fc691e3d3f30448a857a41c60961f33c3871d94dd877bc87b7a2127bbb1316cb00e1b724f568b83ba19c2f12d8cb11a65cdf9d94f77f7cb9239b7a50922f56f0eb3b9444768f2e73a48708ee3a827e0f88d8a1f6f88b0ffec7a045700652e432757e8646ceb23d60afc6d1b8c81d9b351e3ed1ff2b1ef211feb8ef47711cfb63340d7989dbb12b00effe00f677777f9f5ddeb4358737d58b3f7d7de354e47bbdfc4a794621886752cf785603fff1ec995fb6eae9c75af5a7f245bdc909b6d8c14ca20f86499efdfceba37df7ea5fb73de7baf04ee7db98374227de0c4fea16797b5369632efd25a6b8c9f7d7ced7217f7f990afb7dc4723dbc7eebde863dc917c59f46d8e5861a61562c4b0bafb4a6c0fad255966cc6f100c05fe304c3c4997ff72c5faeeb7b8f8d6926cbdd5b1de5a6d594bb6978f5724a7c35de47d293f878ec9a2bd90eceb675f5f7bfa15c99876b1cb1dc91837e4e6f9d67cb97132c37c0e81411bd8467d0b843f592ad9958e6eb20668191b3972f647bff53dfaadce8a935203740de89e0554964ee01bee7527abdd668896e9dced287f3bffad6f1c2e37a080ffbb11ef508f94de97f067576a70bc1129fd1a238c8c4118e2d8050cdbf00d48514851f00df00d341eb894124229a1dca1adb5d65ad68765cf3d39bb16f7fc1b4223cfefee2b12e105af6a7dac7442a9ecd9413a993eeef56c082ddb5d296b7b14496315288d45b10a344671af7d42784d61ce498a82e7370e247020812305385238528003091c48d490a480bfdb1bc03008b1f60b60edb15df36eebcc874b868f456fb66b12df00cb99f5e8dc42dbb5e611733f8450a4d4587cfc903b2ac109931cff7b7c8edd9b1428a38ccae0f88d03ccd2931041cb678d64a6024a864e32fca12295319210704771cfe6eebac806f7aff2f7e1ee6eada9367eb243b1d6dca2afd6647f3b14bbc84d3377d7d6a80b98bbd70c61c680235976d08fc46e244610815304f611fb883d8683fb63bd9845bfdf4511f87bf92c194bd6fdf0aef75b2b56adee2b45ce93eef6bfd66fa9d7e7d280f818cbf5998ff8d8f557e7f5d0f9ea68c419670881e9c314dceffb7da11118ba57ad0b23cf5082e74322f0f799b2682c5af7c3b38ffd67dd6733a55d735e8596850cf7acebe97f8b07bd63716940ff758e05f3fa6fc782753fbc8c3dd67918d6c1209ccb1942602a7dd01ee5bcf5ed655d78310f6606f1a28bbf45858d3542ef4a9f1e6f20a10d83bbd5b3a7ad563da47bfe37cbb2cfb28b3320d9bdf8b3c759f775beddbdd7e7fa003d31b40a024206e18b676dd24c2426cda499341389cbb22ccbb2ac7befdf6b3d04722d2bfbfbd9edbeb6acce56e7e8f40a727f93e6e9252db84353868686a660d65a6badb52ceb2dcb6a1910cbda6b755f5b3bc54e699a2fa41ad53661b0accef3d503a5a7a7070a95524a29a5b5f6ad950f8158295b4ab98250601552a21021c1da4e28e7c4532d8a2e4d5ac606e5e4787cacce39e79c534a29e74320724e2bbbafe7b4316ddc884140d5e8001006beb467ca79c606708b649c6a912d02cc8d9ea020cfb6bb8542677fcf80cccebafcf9324ff76a851cdf8508b07f51d037737f4b3caa45988102fc7999c62d793828e8aaf606ee29d522dba75ae42e51062a007afd36289e1660839890a758e558e3b6773666a000d7a21c8f54a0fb0842352e82489224499224499224499224494d4d4d0d12244890204182040912244890d4d4d4d4d4d4f03cf1c9387bb416c70b5f2274cf964ab65bbe9671864cdfa1fce9d78be09df59805dc34dd84dc3fa14c43e91af2494fc02c3c6ceeee2b52737b2c3eec7b532e462fb74069190ed6903f03bec8b763c0f1274dd3b46cd9dee3dbd9a10c49257d18a9cf8081fb9473ce9f3d54b7a0dc514e5aad756137d3b69e2c33c6a285ca0ac9c58b128c182c3264988106eedae8f4b3d77ec30f75d8c14f0378984aa94a36e2843231aebac654ad5a66ebb118afb28d77e7c6e642218135b69e9cbf22c8f3b79ebaaaf32b143fd1a8e1fe00b8ab7373dca37197f9b8ef8e5db2923c1b007edb6af80d5bad56abade537ece8689bf11ba652a9545bf71b2624b471bf61a96ddbb6ff0de3e1d968f8ed3a3a3a3ada66f80dc3d964f8ed5aad56abed7dbba448d9587ebb542a956a8bf1db2524b4c1f8ed4a6ddb56faede2e1d95efc661d1d1d1d6defdb85b36da4dfacd56ab5da567eb3a448d9547eb3549b4ab5b5f8cd12ea1a536863f19b95da7252dbf6db66f16c76b55aada46c56ca867f7b226eb27efb21300653a954db07f129b4bd0f286385b6546a7be8a9cda6b6ecb7f81bcf76b41d6d47dbf697e5d9b67ab46d1667db6ac8766c00b834ab1a4280b796d5d16ab53acaf3b9195a87a9843015a6c2544279fe73d963298c074b61292c85f1e4f934d0706738c2708e8e8e309c3c7f061930192b29abd54a4a9e2f03cb15e3525d429b4aa512caf365c0b04a57eae2d9aed475a5b42bc593e7b3bcb02eaca30b67b38e2eebe8e8c2c9f36390ea8ab5b2a458abcb5aada4e4f93054680b4b6509592a4b65a92ca13cbfc4220478e6af3eb152168f95b25256cae2e1b6efd2b66ddda7a1502b29abd54a8a670ce3b0b75e6018a61252a95442793ec679c622173f73116d8ac7a66ccaa6e25b52c4b1aebac67c8c23e6eeced5b936b7e6d2c0977b535748e489539590e79386f0678fec919de22701bc8e9f6a0d00f84ff204e03fd9246b2f73dce4bdf6720550a64a69f94fb6206b2f918031335e7b49e3a7ba236d503b3235c54dff0528538180c27004633a287eaa3636443651a4acdcc4550165aa8e0e1284600c0daf7d54f9891615c59e22a0a2a0ac7de471938cd73eda00cad49a8883ac7d6402636678ed238e9f2810900ed092ac7d44b94986d73e0a01652811512c41d63edec01816233fd19d239a9d6813c54d252a4019eae36385221813e3899fa88d900d140d06145086eae844e103635eecf8691615312952692bafbdbb00cad01a8741d6de73608c8bd7de6bfc348180fc06c87134d26b9388680c53608cca909fe6ce4ed18e91f60da5930065a64fd7983e4bc8daf70ac6b078edbb899fa68dcf932c94b56f959bb6d7be730065a6f6cd0363f06bb2a8a8738a768ab46fadc60459fb2602cacc9a227e9240d165be56938134ed35006524516bda0f813171a76867686a1fc4e7fb4c91b5f70165a44fd7986f83e3b391351bed718031f5b5f7dc645ffb0290b52f2a693559ab01ca489d1618235f7babbdf60d65644dd798af69ff22dba1f564afaeaa940074750580aeae6ae8eaaaa59392e7cf700104debe56a09d5a2b509eafd2695cadf56bad2d68c864d05a44696531c39581522a83528a59b012a53bef93e76f312e18d486ea509b12b5a1365427cfd75e582b45b4a668a5282ba235797ee6c292808834a015a00c8828cfbf2ab5c5dcf1d9d9d9f1c9f33116749b363ab3fb4addb4d1c9f32f3c2f59346b3a59248b64d1acc9f32d20b0cc5f7d22818880808088723f6675f76935796a353b3e3b3b3bb172f1af8dd991b4d1d1c9f32b27df5a3455ce2c4a00ee0720f3e192b1976eea8ba3d9b9ace8e2289e3d01d6982f3da6a664523228c097accb86e00b0dbe110acdc66543198ea7acc85de66b3419e308250e4522cff5c8e2d4a37a548f2c0e0cbbe16c293f09208b425474b9a6c13551fc58144ff628ca9a96c1a6f451d151113645d6e48939e76fb92ccaad398aa76be32e535ae602016bcc9f39791ed9237b94e743187383802ff33f4c0c79fe7569e0cb7c173458feb7f5e089b79ead273f71d3970d552137d5203771d9909beacfcf88fce4f2f3b32899512605ab51154fd9508c422d83bbc6fc7969a2cb7c8ce38c42d6629ccaf33597c7b2083dface55e8510cd3d215f2a4ff6545795ef766f7b1cbc58f32d307c06935ee321fe3341a9a78ca86dc65beb5d7b37b04fe9470f9faf5351b3741ef08edd4e2b700b85eb9cb7c1a2e5cc95de65bee5b32a3980dc5d3ca976cecf0a5a9f1534c5d9a7b5331e6e479cabedb6543eecdcf86f2fcdbe21ca8c1d0bd6fa7e8c9500819fe48419483c09371a6d9dfecaff6377b4abfa6b2bff569f63446c99252877bf6c2a48c87ccb2c351ebb2db3d099ba1047f9d4780956021453a0bc1666081517af182b4b292f9b0326c6d33630478deecc79669c6fef6e53c77966533bbf34ecf11f3e4ac0cf7725fb79c646499a5edbe21305a28baf755b21d17877b41080105a1d9a8642e95653deb14cd0c00009314000028140c07c42291503026543549f614800b849644764e9b8983518ee3208610851032c6000200024300040866a60600d4af085e142c10bdbcdbbbc8b84d8af6b23b2d2b1b8bd8b7ee3f4cc28aa88bb7e3534b133fcd442533837bea87f39e242735b1255efc59448c18749606eccfafb111f6d557057488a22944bd6640743af79af01382ee0f07a8805a6e422c24d0d0357a1315901972e6498a4221f859c5dfd41b6529ba6c3914857d6c728f28388e54083a619b8c41b673eac023926046a2e37c9ea647e52269c504640919d6edbfdda60885ffdf77305310a4c3e8ae495cf8d9558de2dbcf51e7d9ceb37b8bca0124a1d052cb28126ebb4bbf8e1e672271c5d6660ccacba336a29ccba97aa4da2f12fa8dbf562e6d6ff812568078d79d9b3160b7e405b6844364682444e0c59e2de54324cbf9088ed30f5c8c7da1daa6860fd2406d302982ff0b9083da2d1d0886864364ff53aec299b6ce651026a13d19c99c8ec1b6e56d07128dd5352d19ed6f957268304e2c23742de1eb0cb5fb82ca2a3cc570acc9c51252e95965a98f4195909c38e114a5ed958c0246e09624d5b52c1f93efa004957382918fcb9ad29c4b64458f44f8486fe1f93b13cf39292a444f9119311c3dad29103cfdcf18b49338d168dfb0e80320f43727cb2ca390d402815829342ecc7fde7a1a2a42366b62796000210567665236aa15fe46047768d5354ea480380c2b58a5da9986b8af114226e446636cf90c66402f89b680ac5c6b4dabd7dd89bb2237866680b250577423a32c5aef76aea4016ac72b7044aebbb6faea0547e05de31d32f63437ca1a32e8c7017c9777a12188ae3943b3e22942a8d09f98780c06afbb3a5436c903a843df214052a3523083c480f0824f20b9f94588f0ccad7a8f10dc7f4519736a154d1751d3891b9a059d992804658e716d8c1f62638ee0d1bb959361c52087a7aad9f755c1180b47a8e3cbb141a21808523f70516a59978564741ad9030bcaf4a29f5303f321d9399722af37de16584b64d2cd6c4f78098705391490946073422f41d4fa6061876c12a77a294c93767d13089e5fd08d8f5ece8dfb4c42741163168a8988552066a498823122c514c48c106b526c04f5744c8f23bd22d1fb71aa2f1833526c41ac08310ac5448c51282622768198916226c68e901e477a22a2575a7a27f07e0ae27e9a2effa6237e53a4829599dbc5934983ea70e23716d83188882d5033b6802db0e3f976ed343991cab168f299cf13cf338c67e6d6d89aa3a80e516c4e80a1ae6217cc9565bec671455b242db26edf5375d62872c6e9ccafa2a35c9eb9ce0f134ae167c980b5435b4ee89c79aacc0289846189e668b05239b8be5dff7a0a42a687691b7a220093177b0e720def3e217f919083ded9bb82d3340b611606193246e3d7d03da76b471704f90aaddf9d341368e4fdfccee6829ec6560abee53f688b433f4ee8ae06e1cdc9a4a252cc7359176085b391098ab601dc21dee19fc1079eb4c768d33e9730e323ff4d2357c0f074383dfc1f96941ccae3ac7587b5af695638d66b1e78e2c497e75a5ade651a98c82ec53776352bed9f4aff02c1fc8565f15ad0c73fb0dd8ab351b443c0936eeeea841812585b7b9fbacbad692824f5fbf1097ee5165a80fa471e4ad659e073f3501ce5d3386d47637cb89ab87572398d653071cd835aa5f63b64eef8fcd4e8b75a25cafa40b18faee6d5b2cb1f528a8f939ff6f619c0ac160e7c515de5bcf2f7ccf10cd2d6d3baad5e7784256ca3a1a6cc64181ca92d1234fd45744bf7d3902244dca81b1c2f5c775334b41b8d744ba35dd240171ad45d8db1ab5aafa1b493e0cd401ae88a4676afa1ae34a2bb1aef4a63db8db1463ade6eca6f048d4a9a2c7ff1fab1ef1b146719335e4d6cb216ec466a021855bc94658b111dc1ed5318ae5e56d1060018783648dd804711c4a813b712d504d9c441395ad7b27c45722ed253d7fbbd9487d6aced891ff82f009c57a0f7979b11080a2f62cdfb1192e6d38550b23ba55a6e2dfb027b5d08ca36242e2da390e714d371321ea3aa29b738cb100ff2793ba4e730075922dd6cb952cc305cd65b4adadbb4c533f8b6ed3bee8ef5af0df98ddbbe0c0ed50f072e844baf5e2b4e06942b9b6dab73ad183c5bb8ce992fde5c1ad5b53cb1e0cfcbf3d65ceaa308cfca4c5d87036427b95898b0c534be4971ac90a1611455f538339ad33773fe14f20d21d91cda62beb53753299d00c32c63323abcd3533cafe5ff9b1a7ffc66043db8ca14a6d8f02f47b79f4d2ebfbe1b3098ba9afc108d3bc214ddfb8f9ffb77caedcfdf18044ce9e61fb29bf7066f60052e648d1380310bf137504812a6d2ce5fe2023698bafa7e3271ffee72fdf9fb83614af77cbfaea9369964602a45f8e502fbf3d1f4e7e930a550892b4330c52eff8ea501c0946efe2129f4e389e96f0a05244c5dd79f236511a6e83e0ffec167fb6e68734835dd11814fa33698d2383f48a30d614a07fcd014feb1c4ecb739608a0c2c38b28bf0de0db12cd760caa0fab76fe2174afddffe38264ce9be9fa859e21db43e0e88bcfc8804f0cbc7c107a62ef2199371194c51d9870500dec8aa06e3875696c3914eb82098828983d6d490a10b8990ae8da1a2627ee3b094e180126430b57643e30753b44ffb7fe5ad5a00d8cc0aa62e037f421f03d06930958118c0653f4c31794e860b66c354128ecbd1dff03503a801df341da65277e7a8614af3242ec87767a49b21957b8d68348b110c052e5e4991c31440e93a8408c450aa9fa2c87523a101eba24fbc6eec53969aaa8e45a5a6a85c9a0c48b96f0aab2a533e28097ad316e8f128734d5bc33103081c53a665200a9afe0454b7d1a49633ab293872fd7f0c253a0af9d7642cf9a47040851018805e6219561b56c7b37533718d61907979483876093ac7c8c70c60fa6a820a619a20f623d07c86c7b96a0f550298d3f0157512008c79df8697b1be89908db719b1a76d8d55932af03ceb78718a7b290205b8008bc58600c4fe036200e223015a2568b64df4d59c969c7d5e1b60e1de5168c7954760f50b0a6f979f3aac3c7b173aeb9927f68d3a3ac26046596bbd4f2da7d0ad3a1377095d5aa6b413c6fe05a21a22ce4b4bcbaa5725b4dc0deaabd78300fb9a295f42c0221f9a1eb5477286a48a8d6610a812e342759df8839434a8aaa4ca0062c887bb3c7d6ea3982edb3350b60a582d0c10be06175614d40f3e6722141a680b9712ee767d33a9ac345ae54e617fc675029262ef69492c9c14ba9182254aabed007e70a4b09cf7031409fbf3bbcecb2fecbfbeeb3cf0f322c95528bb4a3869f331c09de3f7ffa57f1a8fe1f31c88d0bc1cbd40a0bcb3b7eedd6893797e8634558c1d5a83048dddfda1db8bbccf3e1b42c703ae8ebb77200789042273e0c4c73f5c19707b18cc25ec18d8004cc80f48bc81aae085df6e14f4ec6db6ec5095d0850b53745af5b6bd0532fe4c58a8a87ad14569fe9f75e37cf6bbe0a0c29b24d6849fc33ba011b01ab92cd0203df55e7f2d403041b84db91084589e82f8419c6c76eea43a2ff29b58f21e3581f3e55eee7b0b589bb72732ea8bf62fe805b68a11ef83e041dfda841e2932c273c0284e3822060fdc5d5867af7c3b1c97bde5c94ce9e83282f9bc0d1c2c68e376f90c338f819656d8959cf5df3f6cd04f8ee518527af6c73e32471346d51647ff670ed564a8262ca29dcd662418d3f32629b3c2d08b802ed64b2cde0ca61119368edb5e9ca3d54c6db8468094d65b189f4e088cd6bef3ee08aee694de2cf6376f34b4a435ffb79c77e4c5b3909a978a0bc6a385323caca61d94acfd055f722a966fc0c46efc189f04e20d8f23fde41e5d0b62e14f7fb08828ed0a53615e87e55050f2955f1d281586ff0649a0c2e74156268b20af44592a1dab74f107e21e8334251876d939cccd9a383f0f1300f425e8a0b5d1a4943157533870bdefe86224cf648140054f2452d13268b20e70078ed2366e7187749117ea57b7b4b50ed519faab3ce6c1c8a670c8611f0ac7e00d6c066da85421d604915eacc1fa880f6239bd633ac7553363ad03ba898cf80cb18fbf5c9db84102303bd7bab3d5da4464dd44c4a4fb44691d1039f0308b0b6b2ef2a9572b5b717d98cdd96f941dd0e7c826bafab4bdaa4fc5e32cd490e20761fd33e3836c08006d6a0129b0b495c084209feb25cb90002c6587e10c08c4484f69c284fab7181470740b216609245ec1d8d63b4c0aa598581106b764ce336c1b0d1fdd4baeb49949b98cb495190aa8299bfc8d519968ca23a84879cd248c380f46d8f4be374c7146506931d5432aa6a05f563915431807215749043183944771baaf44c89d98b0e4f838516309a9496cf337635b414ddd5a62bed1a9940d6c98ed75de825bd7d81b442489ce2676d8929633faf55209ec1b26a5fc253d4db7fb670487004213e44182d621b17704193928f3ad6500130209860751788ff36013cf8c2966afc8828290c64564c5bf34c91c482bc421f4b5594cd053e6794da374bf65f8ff82b905cbedad4df55c121b9b61ea8b1ee154ac2d8806655939c96f00ee120a87b516ad2aff93835327b073b6c4c9c996cff6b722e4950a297ff16a17d4f658469ed8cc2a6bd0ba3421b98521e093b2ac6e462521d732a879e033d62029469c35f0a8188f8cc1b258308656c72004cf71d18b5582910635b83ee9d2fa2366d1ab9910b5f6f05aa86fa0909585d24315d9d5b9c253f580f51d9e0f360dbf198e3e2d85547edfc3eb7110b5f06b1b3def7f55b9124e775f3d83c75b2501001e9dca9809f781f8e83c632dba6ceaedffc0c7ac2cabec4d45028f19f80a99ecbf077597fb3149dd080dd70ec1ad37e7e47b4497f4934c09e9de5e07290389c0d61396e192c4330a3856cd1c67b54257c347a22f88762ac5cb8e6f40b1cae82cd652c0064091dc0618329576f720cf3304b8136fb120d42d52eaf46813501d4cca23f733e08c138fc6059f6f24da96cd5046c78efa3caa75a1ad0620b45b208e6734de48583af4241c8ae946e5abdf20ea3b2ddd818745dca0f2c0a9ac6bc576ee3739f6de87c875fb3dee864481d9a8472e96e0e629792e9ca0d1298a7f4652e48a5c83c573ae30ba15d721955a69b71b176b8c4e7feb2bc138458567bcd14cbd1ac56a6fb3379c75ad1b41a80a4fa53301b94c48ec5f5e317690a18b4947ae84dd5e827d9fa79f5e43eaa0c8e38cd340d9c8f89125fbd4f2c5035ce877556ae57cdbae23a4149b404168b65343b75de640b790099f28e0e7dd8f8a808262b7812d3f93d855d974a3713268e3a799478de75cc2afa19eff05e004a1456ad79e511991216c0d4cce9de452063125a7f13c3e20ef956ea737e316b528b43d53ba1d35cf59807d8a7783440e199ec5f5fbf8427060058be7339fd13ab314647149cd8aa867c0fa42ee283f52da5d007ea7a46789468c0afae0661ba44f4b0d961bf7e6311be06bbc63f8cf8fc06c4b587182ee24efdf0e045997998a93f363692cc905914435b1e5f5a357b0270b0e0fbc71e9b87003a64de3e3554dcfd08c793408dac295848e49532eca253a2d259037b084a04e6e1ca34c40acea091e52a668d79035b97209e01c93859fced10b86433b588db6c5ee8ad1bc2315eb6b61996f12c1053bba2b7487bc515b1b8de9372fa109120a100bc52afa37e06af14edf429406ec1206453bc56a868d7b27712ba9ca07026fbd45ad978f0f18f16002415dcd0074dd3923e1dac52b5679b77eb4b072b5246570bd0762903ed46ebe12084a678978f163a5eb43bb8bdea7977832c68aa5f092d377f7eacce4cb429a2c288ae8c24b4eb80fc4edeedbe8f13483051edbe86d74824ce9224ac06a3a971c1eba901fcefa50e265d110dbc3a66a83e40698d5f16511a8d1842339a4b1a0053e851cba390c1c7a513e9a740602529b98875af36a79f47027e63c9147000d4c1717748f2cbdef4229716fa16a9db35512bf980dca59fa3589154c469c5da88f4f9b757a361ade3993214f903ad6bf6889efcc8dcac568cf201f5b0e39a42a542808b64e7179a05131136be6eb6a0aada49fed268b3c0af24fe974c800d178df49bd4bce5dc11dd0f690ca8560f316097b7a9bb55b26d9f10dedb2cfdc057e341e0d8ee5fe3d096dabdb65868592d33d016578c6238f5c030ec702fe06884abfa47e3fec2b28e0b8250255375f89a0efce478e0b8c50f1b7ef29d02266219ca1ca633453cc9f99cd360a57ccaabfe606e1c0d350937ac7daa33c4c2ae768879c45c6ba20d3b2981959fb375d53304f39a88a0b1dcefa93198235ebe067f8a27d2d9da87a3809403fb95579f7a4d0194732acb01425c4379f14ac965232301185c4e751a85c601c5d70e4e4dc03dee2a1ba3da0f0696c64d8554db25ee21545b584ec304561f83f7df69828223ca2815c643c5571fb78d2ce04000e210ac5acb9f4ae2684ae16c340d337210ad94624f1b89cf95502058a3a4d4f10c582e3abb08256e06037b0908b13acfb2b8e0f58f57393750e197c8996c21a8cbc7c08143a9564aa4320a0305e7d3ed54f30f9fc6f25384bc113ccbae0df0e0de74d9d9f2e227fe58dae42ff0a28e61ff11ba9352a591fb3c35aa813fd50825b2dc4bf8682c09240104b9157196fe20ad2ce305c6c2679e8c265fb0af0702180fa2fc297b14d5d423a22da9fe39f0ddc2821cd5404691e07a006aa62b4f5f0e99c78b9aa79b5710babc3d9fe3569ecad169bb21c955201e9f1efee72e0f6742207c8ad386fe01d1bcc2cc6631d0207fd0f1fa6158c8e93ebf5fc7e72b55e1d37d055ab984aec81e722ddbf87201f1d2267ecd245fdfef8da6be91816ab561ecd71e0996fa47d0b84acb6c43466a9f24a0a3216fd1e03f69d8ee639622cd18e3f1a7ed52c15c273354146968a261a35202c91890c4cbcfa3291af9cd0187c3d571cf14f09ca0d47a8547950c2e22c852f2e8fe7cb6da494b74cc0ac82233450865003830aee4d846da4d349238b9e6ab79bccf9d1fcc8f3ae2535747f0343cb25956b0863945caeb76371ca759854971de90544f1a4f228e821b13ca124fbfc83a5433dfc0486c3d63116eec9f319b1f259d85c6c3c23296f2d50bc7abc548c475cc5081c7a5cf21e57c9b3e26c7ad77c168e27784a55b9fb04144f8d9070f82d11f9e5c428a6152a15b5e4b34ae0ca2a627df419a6069f20e212b8b17520216450e90432313a2b99b5f0cd7e6df11c9a8e4f28136f619eacea49fb68c931111b48f62f4ac8b63a33a08507639f525a37962186d6d2fa630ae4525d8b95c982d8ab1cf1e4c87e7488f25ac7a188808afcbd7cd1931397345653aac0410100e05bc30b3a1ded718df391a6a9d19029c11f748dfb11142a61605a539dc27fbece8b063b34091168370f4f6dfcf10827a62ce8d09ab823a06246339ab9dd61263042d3fee45b3f844fc3ef956b55bc6b62de9ac58cdc225550282a0d6da3180147b3a30c0b562ec2c530ada5826d9add9a9ee024d938e4913c2499eb23bb247139d6934aaaad4f15181864d2f4d0e1e771b7a088ac1630212dbf3692b437fc8f7cd2927d30832c164d3af360a9a90c9d156cb278b1e4df590d6c2fde11ad3b24b416119455abb84c4ba3562b0d08d5162c7413fe3f779de98adf2e00106fb4ccae77464b824d6b5dc0fd2f23326ccbc31ee1aaf05baf1af6b77cfabc840cb717e89d0f6e56b802db0561b8ffdf4cf2bd2abc14a940c1abf3520f5153c6dfc501b9c15800dab0cc027ac417ed5450792f0294d8b979886b0d525b2ce51fdb8ebc568779902473b64df29419a84db2bba56b7dfa169fe6ce1bc1f5d2048089a86bee09548dc49ace0e3151f75c6b31abf5415a2c20da42ae4d7ea2df73228ac39b2b724fbfe7ad04891a15ea6b0f8ef6f984949ba218ea45e2c02e6070882726bfed09978fb5cfc531d78b92362da8de1169c443868200a946305fb2cacc015d154601649d424451c84d133c287657fa80eb1acebdfeba07328a98172f6c7a36f655b5168e402b218c907dd3cba88b95f2cfc578a7a8a7e02d6ad6594e5c78375aec77e1717a084cd96ab130d3bc3c06419a2c70cb6e2e211348e81b1c66f03e147ca9ef9077c8cb876791769160291e0eb16c160910ea087c9db077e8b82d4d8b15309be901c01559323acfc715b1e17f9b1500744cd250fa17d1ae67d0a096626d8d4e8ab0a3b5cdeade6ef56c5d198e6efdf8ff0e79f899320c0d4df7bf49d9855825f4ea5af1315a297e44ec55ac1aa6d7da2c8e9506ddb3683d650d8ff4c5c89082963a4256d179c48066af70d1bda50b29d9e26cfd8fdafab16408f3732c815ae8115400db6d36b912f2eb82d4802a8a393a4272ee005a15ed029792485cb55a6dd237c41123234dd27bde838010d0f28e47202ad5ccb259970b0c434ebf35b92c2d303dfb1b4e3606e9562d1c1e451907ae22fba88e00aa89e76d31528abce6e5992dea65d43500b63eea6893893bb65e05037990a16e7e7d6c2f215c740b7d3fbce44321f4be173e5293b198eefbd92b1e051c2b72254d1ab43092eabc58b0b271476b05b0dde078822ff159c1a4d609ca58a46bf422e5be8ea93db07c5409be18214cfbe8f83f672d598bfca62997087bd62a55cb022d4b10d4df48376766f989e1930ce527f12e3515110058b711149e9b4c16a4aa944f180f04bff66e04d3028941057c206849f77da9d35368a3888d1d185b37239afcef0bc6d8940883ad970b5967a38ba969b03f3d90ad93036aca923ed5a549bd44dcf056f02cdef22db2385d150c818002b43100a76b527e924e8414667fa9dc9ea9bceb55cac2f3f69b6fc003fde94e0e69fb9f150a9731e81fbec7a912a8f2725ac61e1ad1a1e2eebda2252266471140a0f9ef0d1047db4ce444ac3c291486b142115fe149d132098943d0b7d93d5b0f0c95b33ecc675184a797b4f633e771e50e70100d3e8da3a63dd9799ead12831983dcf55ca9a2a279033909d66e6a9e7b15a7ba7a51d7d65729d5748fe80ce35e8ee1a681d2adcdf8c4c0385365a67997242a2c8d4924da4460bd77daaf2c51c7ccc81ed0deace9ce4b5fbac7461b9dae8ab5f7d6e44a2119697fab2efc536342f4bba083466ad372526b270faefa3c9df0e7a649d6d3cbb56e10aa60063d514efd1f4e8dd44b40023f2b12f5ba25201f8070c22a4778d412f511245e960df0dc1df480f778fc2537f9be37ba4de3e0131ec812fc42859638cc00c18042351979c87fe321932e936ac0369460fafe1369d14ff6dcbc3023bc4a88f3128b10efeff82a16d623549012d5688711e2aa343c6dff93404d04ad924d3038d6ac00a5b5125572a377dbbbc54a11585655752615510f0157438fc40058a676282aa7c458ccc87a80200199428529b290100d98e9e9466f2484d8c94dd57e2315b03f29a8f9aef01c5a6afdc2cf231ed6c26b518ed8ca1700f0a4101ddcafb9ab3493a96a211d7e2849692b56adb5dab476a180dcc19585c8dbd292a9f8ce5f207f130d9a00f7abeb7a6ba6f72d78e51b1585d887708d260692acebae9eaf1c0dcecf1a823c666c9080c2b49a87c40e550316ef229b80cf461618eb8017aff3d2eaa6b9620861f04988114d14c66766ab0f6bba92054a8c4de79a845af2884b7c93ea51146fdc2fae550fd474d6fdc9460b26879e9019107644f235ea43291814a16b4c8228ca9aa7a8b8ef3d6880c4b27204c6b55cab87d013d1c610f74d61021bea2e1a96f64265337322d1be2e5a89bf2128a24f9795322cde7ee67c2006cc9f868a10ceb142343821a312f12ceedf9c5b528c7bfa69f627e9ae8c8792faf3a140780abb64c0f933235cab05b9e5a86b4de1f1449ec413a34013426cfa49c5343372e2764c24a3986c71d8fd962abe1efbf18a02e66de5bf1de93db653d0bdea28d39b93885648d9f9b9b6f239b96aa100d38a61f055b6598dad24b3f8a72b5a03293fd256b4f098787d22623d005d005889880e5cf62bf5b524c445a3aa976031106f83dc0f590d99bca870b3404defca9ea22fd344e42132c8b1791a1abbf2b5289c29874f223ff1165b435a28a81e25b2e5eb8154cba9994fa2bbccb6746a1e5c81afb0f23152483b5efe54b5163523d275c85c30c0802af3972da1b7aca64e728fb8c2a5b11c582f4588d24c5a734e34fbf1037f7d8083cc5e332eb3a9e2f0e67a2c42268f8c92e231a0e3eb79d995bd375f8d32d2162aa0a732efc3489af2316051136b4a736d0ee53a1833ab01bb3a3c03b8545c6fc42f5551fd1f8c45ee20ed2bbcabb9e6b485e2e560b6111346eb0381f0fee19163a5581b1985b702778890902b67eef4d21c47f0c7f2ecdbc0702a730a1d01ae8da1db3eebfa985794b04def695506da106dd500af328ebde50f2c2489168a11b7f618cf850dd682ff8d62458f9198268538ea85108b5823c0b81188e92f9cd7d88e1490bac8384989351579cab299bc5b5e2d5622087cd5e339a12613f3ec430331ac7691ef38458aa451a417055f0f904a74fc6f7c6155d57c09bc127ace3c1d9e9a67c378ed6c319147542b35c1f864611fa4b42d307a44e50b99829de5309a0fd6539008ade756524c591088f8e4b56f80927749cdc1db0eb735994eb1f2b1c5535bd94534f862d91751bd12786e01b5655f7d1330b9d8c298a7c5ed134e1b898f277de4e334c04051be1b35d74ef9cc7c21399410c7519a80d0098e0bb076ac894b220bd3504881e606c0f279ead097df14d44086c7974c7056fcc4d9f8f52e99707aa9190a6731459f997269691e977fec3d54385a583eaea2d6e4731a5735e8a013dc6541db45150c33ceacbfc0e078ff652c9ceb918c8be605fed5f7f72db28b65c838141a8a142d0bed1561127407b7aa4e733963838e5c3b79974a3c07b5270297ba8986781d081ee28b2538061d4d4a887fe51b385debee2016d1f3730c0f9bf11c2b5c548fba8c6c0042e47b490eae2718510b8912e806d16f0b7f53013ede5fbfde2eca18d69424004e21584f83b226bc34f152d521da1700f54a06507ba5238ae580a6dfd03ee800a6e1c69b55555ce1cef5602b4511e085d2f25f369e82128c59cf8aeab183fd8bcba95ef80559dcf4f0c00233b8cc85bc8b536e345825924768deb8b9de40c604acada8e827b575950cab4fd9c3adecaab9f49b2d465e98701b6f41a4bada62cc71b3f0b35eb93d5abed10e80e51f6d0c14faa9b22f15a01f35c2a9fa98de63aa018cb8ebf933815a115cb0412ca5f7df825c80ec8bea39c996437634d8b306e384a9af54c9340ec94fe225820fa549fe3f547109601f8f36e26e7cb6900bdac2fd2db4814dbfd08e37c0a14f1e8f0a12822922b4efb1506fa07ca79939df8ae5009c0d512e035600d0b4c625ccf6d1d293f29d6e9e88ab88c358015f921bba4b1b7206f52f4bec566086292ab1558b8fbed9a92d19806aacc55415ee307984d19702e70c38fa8cb4a6c866907715e012ad0088a9432de983e05e402af541d932ba6b0bf7d93c1f686a056a47cfc2c1c1038a876f90e452d30bd91cabb500f70d4bcffa064af3b3463b40bf5e8a7fb223e4d2b3352c7475d22110fbbd78279fc2e3265b56b720affde3eeb2d2f1423a53d9488cc8499ad9c34464b946afa4e3a1f20dcb1b393f8071145aec112fffa56de290987f90f6b42fd65e3a4deb08851c4cdf5ae44f09a2c73f0f2ff7a3886db891ba098991dccf8210bd254d4310c4b8e49db9c116b5b1c12082265b43105de8e6dcc9d8e9525688aca0884d57c1a6cc9cbc888017a2c6b9912e319a25ae6b83a4b28819ed09f950dc0750348d189be474255d60d14a4dc566657633c30bd6448068981462a322ede38f642c4f83f1104f81baeea47ad916d41a2685e07088ef5e6865c02bda2f2b6294152d135b23b1b1bbe6441d39c97006581d6ef05063e59e647b598c0c527558bc0706b848d70dd6bca40f83a0ae072b1c28943ce7a3c9a78a91ea2f2d148382848829c58d7339108831a9c8b1960105712616356639a021d2a422e32c074a44b1a95ca4d42738c89757959fca3f10088835bd88b1960011d1a68a34c6b22023625a31e35c1e04e24c2872ec6540438c9a488a90438fc9c5c4aa71954e486bf60bd5c37290721d8a0aa0175d5b3fee736715c21467691935168755ff21b005eff56e4df1a945ff39d711aacbe1ca20abd0caae77caea0645b0765d2cb54ef1a68b8143eed04711c477e673b6b800aa6fd5f0094e28a69d58cacc8e877b52d5ff2ec7da5bc8ee2613dd425d06ca14c50cb98d710b4e7109b23cfce0941edfd73164c03a368221345124b175fc99292bfeef3346d085533a7d6c2e07341b2a161449bb1041c93783fd27330545b8b195b113c96400aadfcdf37b13d452f4712cf0c379cc03f222dffdd0bc01a4750941d5e42c9b5053a24a26ff7d6cb8feb5c91b8621e38936a07c4386ffd300af8f44edea3c16547f2b2f95f4102fdb3bbfdeb4d429390996948ea1edab1fda453f4ffee9e01a3407507eae86a1da3170cb2fd4baa81381d22ad3ede3f96ddaa575b48269d47ff2de8af77a25f7fdebd65945e67e258cec71eeadb9b7462c7e2ca4486f6fb26106e04599346baa3f5b863e730229a86c9d752b9207020a2a7231a3a28f602ccf1beabd40fb5e1d306ea4da671d6401d5d51cb87b47879f972066d3ca92baa352f7fc32b3dcadde4473475cf122df8e558c0e39f33403ed23fdc07bdd2cd7bace5918e2144db4b5d3d6d450a92e7759711f0c6b1a946803148231361920fdc0ef2fe766ed9158bec2972ad02f0f7473aa873ec3ba65848914f6803d9102012d66cd6f84b8ed57aafae778276f555dcff15a179713cbe3e9f72d3e0367ae0d64ecdbbc0f4ab0ea28183c4406ac33b25ddd24ac7c8b493568e719172df7553b1f3a2fbfae8e9a77c35ee7ee437f67d9cc0d41267e087842f5f141fafa9fb2fa29bddf7f97899dd7716f943d6dead47d1d35429b7faab5f756bc8bd1faa78366260fee39118518ac6c3aa4b21811975532f921dbd7063a346fd9027649182c560e3634ffc1e4805fa80a4f9d3d947da4c0acd5cfede85de459cdadb580033ec10c084dac542217118ed221e7ccf8990da3798505f35675ea64b8f96c599f973ebed26f29d8234b932d68ab956649279a45aef6084c0c1d762e054dce93c6c8f601f626cfe5caac3d3610596dfeadf01f2deb55ad94522feee9b1bd7d483b41a3cf69fb409c268a42e6f5e5fc233c8a1847f61267ede54e92c6540439a674ecf103b8c26cd35c5d09326021099619d690f1a955ff80c1f86c0d30382ab210b558d72d6a3caa44d6a20a0317d92fc6446bd6a74224b11b1d0ce5421637111bd3f225026f5693e8512fee3461018aefdccfac1d2de5a286a15bfaa8be96c53aeb08b1bc54c10d810a77bcff18a17fc74f45f317dd9117e4c594e22b6d45be198edbd6d8c5e603e2300d2b1fa8f1d497b590696fb69b426d4eeb4cc4847fbddc01b82b3959cfa4df90d10c31ba28fb839ab42f7c2b8e6213ba9373ce8a0be87b2ba157cef1d5b0963c2d6670765e9efe3a7054835570dab5af7a1911d8df9e06df2bddb829b2c10815f4efccd0a7222291335ca6cd33a5174114c47ac9120a5d0025af344ba08d4a79144c808e1861ce2c655182e114bd89799ebf5bb232ab6effc39d607e08cd99b3806228664edb54f42e4aeaa04cbbc63ac7e138d16e9d2880183d58a88698413c9b724d98d30e76213b8b850c059647057c9a84ba40260ad8e4fe2610483dd8ac5394674d7a5ce76194001293fb3cdbfd396f5495be0b14dcef4cb32511c2fdab1c274b6b6bfb0e12ebbbd308253486d8c540b6542ad76c5369b6f7302cf8b779fa9efe338230114683b5f3800be4cced7e5b0978e68d4c20525e9b6200776ceea63e09b509c5afa579f67982b0a65caaa203441f36a44a98bad0f7750d2243b8def7f6024c45f2965003445842a36ed5e4a69b9fe12cb652da3fc9db0e404652858774bf1f3bad3545984ed2895f4a1b094721086364fc8425ca2396152961ca20ba873e38308ecf39e75029aee0ba491c5d0dfbb07ac3cd3807f2fcb68d88b319760df8f1662204487709248b0ff02f95311e110bb9a931ac640c9628b10887632de51f3b05e0cb8278ab449d9b1d31747d8c40816b375ca9478a9b4a4e615f920e6d858157392a20f163d809c94926fd49c43474bd3becb7cf22507dfda771c78031eb893402783f2366850d0512a7cfbfe6c4ef460b041b8b8b87af47015b53d8204cc2d828724bc871b97451d7e7935e42b9b96b10c1d5a66140c052ab0ca036c276db4b7f6da0018e1fdac8816f5e451122099cfa98b0162d615f2ab44adccae35d4b67fe2d2464d54c47fbcca3e347a6abc580c22264a3ab30ae7de5d8efcaccae884bd8ce5f272e83fc4f06996d2364de7f9b35a4a8b232638c9b2e1fc9829c1e4d053eae5c5d839e6ba4ba8ebcf8af0d3e74b2bf53e656f8c2aa165f15f68949ab7097adc958620e88ef1ec6ae9151c5d227f498cca0d9f19b3e789eb33fd6819c502b6aa9c1f9d627582ec03020598d3ad4ee7b7c17195177484f62667f25e39c2ef530c22e97c4e636a588fa06f60ea29d68dadfe27cd7528a8e05cb94573676acb76fc5eb7153d79fab3c13532e7396e2797d59b26519b13c7d54c0af67b64a4fdd53b5c6d5b3fbc39c9a833c796e3140bf1f21fe0390215c0375f8411dd374abd0426a9312b365cf3514f21392bf49dc9a723b14dd77bf396c6db4e94c857e694abe9db6e9a34d82ac73a4517370b0c2ad22bae7d7a9287223bec466e4fa4b91e4ead1eb06a0d7e54d2b73bb557b9cdb7f8bb6e7d42489dabf3e025b512ff39a1fd756524dd6ef85b0a8c1802abb97f70bc7f91834007a0df996a91c1d8c4e3132f7f5a92166b74f5aa9fcb1d1534995bfd09579fca1290f14b3704ebec63edbeca60fd0ba266e864b294e1244f90ea236edfa1e13a0448f21dda9ba428cd52844cb7119fa86fa19602fe7e7ad5b9c585cf11e454392e46d6818e46507d780928f10e7c0bd8dcb1d46d97d40169613fbd1b0b1d37afe01d016fec8c1c1132d6eb88213f967560df5010e3263d70e116427240c950fa12daeaf32c5c46a6a86cc8e3ad85de17d51ea50203c8b45ed563cc6d9496be14b4c75331f0b45e6c263a19e978db63e3b08f72206821297b36e206f42c53e48a61d6c799183738e478d3deb9a44a36a051f52b560eea6ca3357b2fd6cbd164e49189f0f5e2a426eda149b7d71535ee5d138e81d05c5879ca2f8ca289758ac3f001d9cc1502a582ce586a32d10e6f9c47f7b3f760414b3a917402d9471120b404402e7007cb0dfbb25066cc0730f389374ddc2305d9b745c684e12c7cdbecd475147d540e85ee4c208c3913e1eb9747231c01a94840bb0a4a56e027452767c6390d0bcb2178533f4fb32156c8f78817dae6555326cb1b111325a7d4a65d030adcd407e15f18e6d6de1a28b8124f3a4a6a6f1ba50254f66d8f09e760ef7e44ac738b7a1d701f5ba477ae82b69d1a7082f78021e4b05f362b345d6ad2700a23b7c6d77750bb66820562156e8446bd5826884118b7455ae4c24afd0773fed31473604a0ac2214ac2843d22badcf259b95ebe29e66ca806d6b353369d6b3129a531c4507140e762bd3af69516dd24334ebd2270d0341dd9a57b1b0ebe65c82d106004b74b653ab8ec82faee9c7a2230bd787db0ba45c3458cfe1c9bbd810d93169bc455375900e40ec9f0ea94510337fb2d6b0258988878c0d89d8faf4614de7f1172d0f5df175ef1af01dd56d6943a1d044a13591a7adc1d098d1ea03872b9b6efc957c2808b6cfe0d06747f72e46e5b01872bbdf083b899ecdf0eb660633bf547e87a3f242d48c0dd0c91aa0864f62a5e4a597982b8093a74037fbca3c7cd9dadd60f40bfed2d4b83b021475595d4ad6e1c1bd507907217cc823c3326930f912499556e10dbaaaab0e1eb710711c6cf112a596adc499b1943d8a12c9a8c9951fae6ba6c7c7f08cf0bd8c6612850df7d2cc9733009aa36a8ff7b1e0a121b378551198c158deba86a18d012018fc56d889549323e81d8afbfe89552eb06002cd927ace3f178801fb160e30309a5b46bad68e2e4a103c9c258154136a03a2ee6821f045d2d70536f4c89064010010cb3497160436d6a0428891f13f0faa96271f50d3e504c87d3a1d523fbf4d632a7fb5dc17d764b63a63f598f46151fc64cd692538c9a104b190a04af23d3ba49106689f41b1671c42e58584e0a8ef03263e4709db4102d2ab7aa19d4893565dd8a6133a37e5df6469b72e64f699effb2197a6d9d35c101c7dae332ebee174bfe715123e3f82f9c22659014d391e0b7844708ed941dd209597e12c836fac581726c9bbcd155f6b20e658d6b24b011a9bf8041e87998087ff5dd0ecab3e40925617717245eaf3109990c25d152ea1c6bc84884bc465f2bd85bd2ebdad900e81bf7a121420bddec148d55e089b056cae9630a181302772e35ce2431032663400323e85967b35a44812a2816aa47745c1f78ebb1e34c1f264dc99a8186b1cd91680148b65df14ab8ada262cc60c404d0fd354444dca1e296111b471886de4c71fe552c6b85722c757e1e5157051b1fc2b2ced10e27541e782612089d846420c98cea642887da150c08461450a1c9d38408388790c210d3db23ce2cf88e5dc8d1cd5d09aa0244843b06ea5dfe6fc4515b801a7d5315117adcab51c0d889710fde9008aadfe56cc00a27f04a5161e9c8af33bf470011c4661335bd5216863f36b289fb6011a96a773c2020ca105adf33d55c771c256329bbd32dc132c1834c8db11fb094dad69c09406adc4377076f45a63f6b5475664d2eec0a9b2e10d9102fc6b5eaab287b86e14188a11429367f6928f7026fe0f5a2111fe9350f0c58f2c7353ac38d59e196704fa2f145b4793e6e3c149280c103cfd46af58a71b5586f3de3547b3cb41df66350f27a72b0c243a4f2002508d61ed3c648c111f3b86063f3927820cea92f6fc57f2341247f860487db48630051c0c8919021e1937b01959efdb5830423ebadeaa5c5a24723813a6faeef16a3f5c21474dae685bfd58fba2966e763dc5707decf754b23646b19f927f177b86d0925c957c7ff0eb19445ea6d1fff0a22b981677a7261245be156f063f6edd982c547c2bb2fbb333ee32e607cd573331e69468b314db3c68fd6cda27146e60c0f8288510c937a19c07df06701666f0bfb5dd94880d4ff1e3226fe0969909abaeaa76a2fc2125f94ffc646848f83d340f55ab9c2d0b7babdccd14d848fd40f49c14249cbabf87115139d9953c7ca4e96ae8aa2974197113e6fe49e832b0c48c6e4e327408771885e7e36b41ba97081dd5b0197044cfdb4e04a1056d8829ceda8870263457ea9a7ca5dfc19ecf6b4409e4f00f91ed6b2254767f79797b448d72201707406a1b47e15b34f7ea23ba592ddbcf3a8b0abed1e8f86bfa60934fa1a092462589d3683281f6b5b0fcc22f4ce055fb6a1bc80c551c494abed479a6c3c4ee7d10dc3b6baa8b6d6c116f9f02a15e406d1c66c1bd4036768a414dc158e11cf06bfc0229620e23264ec6c83f0362e43f05e72d01bacbb630cd3108641129e723fb1da711def38418ff47a319d1a81350f59840fb33a34e40580783aaf27637df15c9239af9ffb50c9ec1c665e13c37591e30da7705985028d324c20a34a8c009c10ba28ea274a0d92da188bf4ba8b082b1b64982c1e10c41b7072e88ea622590ccd5497598621792ea34dfcb254d06670ac30700e5d6c001553f00d7eed676bc1de95b148b1129439bb0fb8d01cdffb4a78bd4356c8ca784d11a81cef08435542b94716656c7f55cc319cce42243cdc13dbd5a13f2c87835d03c8eec00cac9a4b5f9960dbf0519e3bca757f6c82c964e30852a9091512a31d7a094b9c7de5c408154d5ec4f972eac106bccb2ff84f34b6ba3f250ca81dfaae3eb26fcc69657e7480676cbd5d16fdb0917252ca3d4b44c087c2b47dc1c1d9e962c1104614c58a0cfb8163a2b474f9665452897eabcf612559b81ffba345baa13484ecb8882c9b054ee2c25bedd3bad80a5a48a1833e4c052e39180ddc64b99e6099f317a56403ae6d14b4328ace954a19f0deb90334563423a6ff4b7b43b41e9a68b4ee3285b1c3ad189813672c93ca427ccf4a9c45c03b7e7fbb2ee02a1d5e4efd4cbc78503c12d8c6f018806a4638859974b328d9454c5755575fb49b8892b68337e197448dabee301975ad1cb03dc4cd93d4488e04e7318f2ea470172a89cce3ea550ff0ad391f85dcd58f4e76faf6f40ddcec4fbb413b03f25372254992771eec08ab9bb441ca2236d826908a785d271aba72affbebf1adb34fab6969e1443e146d9427e8116f307e1ee9b3721a5cf395f9680feb11a7c7936a6004641818e09650b446d412fd5779ec1ea53ab8535e5a96d1cc2e102983dbb8dd28b3cffbdb319f0cc167399b80671d48d264897b9d5bc7f281c85b3f8279da6aa15c76c4eda15a101b096d63c5a9eaee75c574c138f9c70d71af9cbf5c123649c9664f208e1944426576d3c5904b77b02545318162da356657e510115f66899281e05b9417021d308edc5f75eae1a8df5372169b51f9fe9f462f963cfa78a099d72ddb191aff267c072fcdd653a570aebe210f4b369eab1b07f1c9498f930d5a9e123400555a845c3b909c737d20ce4dc9fa6897033d1e858749d87890a80d1513a4d31c80e2529772eab6c62cf34b5474d05fea2ad36211b22d494f7742bbf495cd4560fef73ae9ac6448b752980a0398ae50285b5da473c291b5cef8938099fadef9c34e79ad0aeab17d83b2f69d9e91cf2816a2613b696a0cabcef02fbcbf8440c469c67894762f42e4a561a5f8ca349ed5389ec0d3f88de34437dbc793b89ab1dc282c6584329d7f756009f09b5ff1cee6f1cf24a00c35527e5664288579da026f1ed176c016c4c9cb8ee6580cca073bb3e5b3e81d2a135c796d124827e4b818f4f99cd36ec261e2eb1d9ff854021dbf869e0e7f3538478ce83d328563f0d94a8dcc84c302950883e5f072a6bfe9488f641594bf0764e67e5e85c0fa033bc2a451151facc99840991728ccfa64a0f70fc6834f025b21c456500189f2a962c3e4a7581c33b319131ccb4e051bf880b41cc29984b3422a77089778c0ac4034031bdec126b8abbe59f812f8d241c27acbfbda2932a7c4ac957722ebd234f759d9a1a387cc3a16ee9d0d55d882ea1f88efab85ca2eb77b1ac22165593cd69c407a5c98300eadb3a257186297bd8a1014ab3f99a9aa7b18057546bf33cb1457135fd583d1a44c97d10bc451993ab891a6061cae305f2d0fda61be0cfbb6496a6f6542f4aa6a9a1ba32abe9393e913da26602d4f298e313ec86bea092e34374860ff6e1bfdc0af7d4703128d45124bf0a2c9abf6279ac41c478279df079d07445989e843cd70d849c697c992e37ea07386b3880f328ab679f28aeef318a824a8396c3359143bff9d92b1fd2e4996cdc828033fbef4d8ec75565e7e9d8aa82907b8de1e19ace1405364f1ea172eadac386699b14873f1c9e1e237222d2e9b4918188e018d9fa98440aa2e99f225a1eeacea10c41409d896e94874250cf3fa24db314578cdc107669182acaa272cb22b8a446a17c7f578b015a8fb27e96a1300a31dbfdcc52cef7ab39122c88fb91f35b8b1044a03c3e31a475cfe61a438415b96291e4d68feeb8b11ae22e05795c95b259cda20e8122c195a4da72c40a8bd189500ea4bc4bbd94e19472e1512a09bdd42d002c66d5e22b5a476fbce55e44f9cffb30bcd9547145654fabaf544f9c5ac22ed95895206a03d19d023b31041c84c89ca7729b55a3ce9ad1b78d340038b0f6c540c00c2142ecba420d12e4b306550d92bc2a7e9dc9639c7f36fd944eab295a2cf21aa43f1e0d18021a223ba93ecc435385e16cec226777e16bbefa1a7c353294ae99cac9eeddd7c61948eb098812034df6d069073fcc8a24b2b6db599afffa7d02086aa939f9644f07d94d14e8a6e1972483ccc8715546c6999487c79af3f1c170a3e3d1bd6102560100ea401092fc4f411e59179eebd2a743f1adc6b5da45eaa6ff83868c7a528b291a406aaa52575e62c9141f265ce82a126f1e03ca6c4012b0a4856a1f86536441b0e1b06668031422c72ef883988f41a927b45a2fc869ace55c9680b6eebe10d633e1bb9a2ee1042ef4ee7bdbecea527663af5edce8ab25af5ed928baa693648159002ade05fece178cdf8203f12c9053737a6e277ec39f690bcfbfd651210ee06949a8fdf8fac2b8b8bcecf90423b33cc1cd05600d86646c1de327473626fe6d6ad554e85c84ce193ca84cea49dd6d0ec7110330a3d5865ed5863e3986c7fd2d7a4152e5c6d4210d7f37977118a9bbb0f25757df440313438efae100df12ee17ec9829acf62096b26222870c24f55739d12187207d347b820d658548f8c0c9805cf2eea2ce224ef633ce3257c02a7771cd2ff733456bd0399024a35dc5596d08a495926ee00da4685f5b659c4400a0b100acea86c0d82ba9080f6802befbf51a8e2f837834ae99497c1064931d8939cee5d38d2dd86b2d192314208647a527da786fcbcb12e8787ef0465b708bcdf502981c98168b8bf5dbe921827a6c5fbbfc29c2f96df0f6f5ebdde18d2e0d41a387ce771d613530df208bb999988e7ba20ff2666a8a40becbd6d633f72232971476cdd54b7af0cd4af3a183fea0672953fa8342b09d2ddf7b1a150e06d4bf20b22304633897e9fdac9961ab7130b4c43e9303acab711aec3b4262c6bc30f3c701a6354e5a9e3e6e790f3c9ac165450ffb88547882140f8ce54b29042077ecee9f037a498efd88e0d12c3f0677de1c49ba5e912f393a47dbb4bddfc3bd7b918bafefb4bff5d82beda04385ca56f01ee3eb1279b292baaa5a062ceae0bc807bd87e09770a5b74518740346e9a6ddd6dddc9516d5c972a1a2c711a7af38fe0152be60879c6d63caac8e039ef0fc83299278c86b323383062ddc9cdde91535038b2b536bc4c57898ac785099bd1c1ee5414c67ea3b3b5486f50cb056e4c9fc7c9aa16bbe4c626a16bd823fb065317a373ab6f687ebb815419ee3280d3e47d24319e639aee0b0e5dffd68b7cc378fa82db7c7c8703516c684e4fbd8f91b13f916ba05ece75a4200639b849177f7dac3c646860c860d398ce9ba1fde8aafdb9a6411e8e4e2f593fd014d1ce7e5b02ed9b647e920e38b1b31c714f80c48f0e58415a47970412348db6fd35f0313f9e05809472403c92037cea3bb9b5e4eff76c984df4b4506c92e2434f950f548277654f4cc45b73cfbeb193f31267f2919d44c68ed0c632fa5f58c8b2643770a346b4ab9a5d2867812300d2fb20294cb561b8dd1e5e77984321492cade96c383503f7593a108c3c80310ad4496dd340fd80dd602b068e9abb276a8d11a7c2de648098f5cd79535574e5a7558b8e500d436b5586d46a3662f65ec9ac7c7935118cb3c024f76b9177891dfc3a5ba56c7fa2e9310117e7bd3bab759f3dfb5b5e7bfceb568a28da01b524585efdcc66ce3eab12b7a497fcfdc777f94b51458cefb45c6ceb2309b903fd83b263e8a307b02b0ea2de3fe587bf1421ed06db4b299261b843731b95002099ec0094c03d4eeb82519413678b3a0326347cba7a0fc99a52e895a09096570301d71d8a084b8104ab0d0d27046ef0a27aaac65556ce4ba12f59709a5d19396df49add57fcf01546211f11be8af9d2a6abd6eaaebcd3a4ba3d41cf90c28589d68e9dcce192505b75178f19f3b4c35cfb7bf25fbae3266419bd0eff3099ff0243278eddda86473d1c6d076f7abace895529c546118d5993025a188849de72a62ec1ec52ae20fc9669b88da2a01a56de2bd74caf055c416afb177d14ec0ab2120c7f10fd6524cb36a0d8b474274f39c728f58eb950798d93f2073ec64e140a3e9ef8686031a9c54fba2a143a6290ab71e469801449537a30bd9cbb860dc93b8fec53315b02179c4bc411c3dc0fb2e01505116e64fa71daa24cdfca7a0944c494bc3d837130bd2346653405783c5239e8f028eebae12959554b388c66e6f28b2370594a6b6e40ee6c43773dfceb63ab5de23ca7202fea074013185a2ddf21f97a2f620bc9afa537c9146f960da52c5854b947773aee3a7b6c9a216439cab30a5bd0c29c619b70166dfebbfb0a083009181b75817de4232f3acea5ce8e3fae3b2df8f9d8fa62549d20c76305b286a14f0da1fc008b085a1b4fc30fc38ef7d7004ac80000998233a240ec049534989c47ba9ad874d41fb43684a727c84c4b0e6ae579a50ca5d9310ca49c3148a8bc49a6ff340ba619142a08e3e6ed41ea1915adbce00672c23dfca0df1296f6955a3a98d8321bf8892ae2ac05c27e7ab34ae34c9e646b64424abcdfb5bc0698917ad6762586d7bef5ed3bd80474ed3ac9edc777cb3e84ddd2490d675b357405c81baba879aaa76eb669d3f5c2f805c76cce5489ebbb5b99d54004d04a84dc05b0755e5c541ba90ca2391cadfb767cf9dce380aa9ac0843f969584efc4922152639f8202f6d95ee72cd72a6cc661ebdc7f0f8a63ca0b38750e4220881a07fa6edc30de7b1afe4618becc94cf50280ddeabb63120862a0dbe6df181bd31dddd076968bf916a202662c200e24a690591a96608d6a5b53128866a4191fed139d53426bc1f8cdc5c69896f7ae2618eb56f85cf85d012c782b8a0588f070bab71e04f55558af7c609efb3d137511d76847f4e333cba8f93abdf0c9a1c3ed2c6842f9fa5851d2ca318e75eb0425a4d1cdba71a272b84a5d48fda54e7d388f0eba35184ed96f01177c3857858b07e568c76df33b132092b51697dd5344ba051eea1fdcd08c58ab065c6ae48893b2bc98ad12d62a1661a9c2f6a2320f68a7691502a83624e824684171f8e2cd29d52068a66ba030a2be4ad7a222e99d2aab854075747b411eea0ffcd166333d0fef586d9d831f0e9cad4835b015c9ad492bb1287da10ca29d588ee3f6def867ea2ff4dc3485fa0443d0f48e5e489f8818895f0b9c6f9270d81c6181d37159d4674c70f2385dd91363009a985815e2cc48cde695838e2112d458ad153b47ee788142b3b39f3a1425c019fac49940a75ff899d6aa970a93000b924d3683ccb0b790cfd923a90a3859b4684471f8db2f2d3b73be0627a30ffbf3d0d197ec637a48e5921fd026aeecbcc425656bdc865f2ecc1472aa6947ba99c6382ce024afb09f0414669251a71460c14d28a150de1f9a903c88cf2c73fa643631749aa063728e15f3ad4da8164342fb0029c264e8e1c05b1766a313bd85a809570f33fa46f36a8935c24720c798043cc401a4f2f275ffe49313a3eb6854c2b2001cb3912cff2937da58d5d54cc0d820586e17256addc123031482cb34c03b33be0d0e141acbcdff47da6c47a03f38276ac327e5f6c27c8077e08e9cd145001648911a350ac84a8add69f47f2b2c2af6367b158c4dd3e9ea1a2f4254157e37d156d9a0cf8d78509341e0f70745bbd903b67bef9922031588997454a5a908f4f8be8188e296ead04215bb240b0089e2e222c2e43629c032af00800f8f9cd3f060bc8cbca7c83ad24b0a7e60862226798cfb90d202d010919ac64e4b216aeea4f5f56fbba06c5135f0867029b995dec40329a115801a713e75010892a60c0c301afaa697e873cef23b6aaa519e9954abf958883138bcfc9c4ff20a714c0a3e3615b7730a19b6cc9214153a39224c9230dc6b8328458ddbf6e113ceb0a84e0a6023037a0cd50c0871f75ba4c06ebc46eacaa2925d3f37f3411bf6c4e0456076aab2d943580041fc333e329e3ff5e055694bf8e5834b1e3f508012b8d5af79c2c2ec96d550dcc250376af6dd52762fd6f86ec655f68d7078c1debb0e4b584fd0d077b09a90de01e532579274dc0e380ac827288adcece6655f68b81e24d385e1499498fbb9e5693e4d103a95d497e3d84b0660557264df0a49b15abc30fee7ea5f426feb8dcd336b5933ede035ffec0366d1b5bcdf5c82c4c4549db12255d9717caf62ecf77d281dd1cfc4b1d106c5b17f6c6235f0b74812ffe59df2bd3fcf8339463686d7f02709e4061b3e544068faf08aa3d97483cb9579f33fdd604406824402cf2101a8491e059619e1054a8c8153fc8028a4c5b0ea59f1a4303b50dd0411bb46b8ab06d429ecbf905d27d93d5e728254659f3aa4b671e3f8d5090ed6b7e9957d23f6edc72ccfbb05324a69c5e6c6c163e801cd8453f0b2d501fc4d3f13d0199ec5fe9aef37168308107ff8635d272d5ce3147700f7699d6224fa7ab6a9e44b02348a6a155c62a91685a1ad1517228dfd691cda54943adf32bc304f1d7886f1df7b51a1f2cd057348ea79e2be31af1c8d6127d80eaba7413a58d7398074cbeef396b88beccc314f9654eeb89c0b25233dd0baa32b3bd9e97f5b9702a8fc1ed5a621dd4525063d3c470c9895e02466c88033b827ff97d4deb333c703a453be7ef1f407c9ce5d1e8cdb936ad05fb11d7c478d1c521c211c5d1040c2099c1e227e2a5db61aad21c3f5be695348a716517c829ccee3b41e8832c8c286efab4c86e76b8a9ce5aa09280b42658c1f155d153ff363b9ba0dce45d743778647ca61147d8a283d40a5af793a8a65e0c436426bb0da51e1f81f990f3047481bf8bfe082e544bbe0903b450e6288884596a0718e0c0a28aa139e6137592253c396ae5c42c62cd9f08be32fee56fd81e640c6506678e0f48401cdb551b35a96ff7a5b6c5520f6aee18997e30df13a7f139efc32fce5fb33c684a2d3e71c0182afb7cd2b942178bb79ebec9a539932a97c380bb357bf1684f6e14188c461a20df5a278082c137aeb615a694c4e517b5a6ade3c0a9b286b35b2c47cc1450afca9cde3cda1eb6058f909ae55014338c29f25e346f3540e9c91479bd94ca2ec7b40e07ca14116efe5a0d62a45919efa8a273d38a2e96a0b1e4f3fd26b8aa62751272ee4eb8594d485a42773af223b3162e494abc78aa38dded4d71b9209a6102b62a9568e06fe741095b4dc4b67a918657ae865214f61b0bd0bfb65205c760bbce00622599d20a6ec42c2090cb6ea21f77ef34c4f64891502249c78e985addb2a428630f2782cbd5fa93a7cc22b0353c17ff21cf05188163023f1463e22f2eea25087a9eb6408180020c094950df7964e4687a44ab7b01855ffc7fd49f4407f53e0f8b237858215042cc8049b1256060d78f0b9f983eeb8ec9b9ac94bbdf8bf8ee6d5328a58278e36b57e128b25dace74dc4128b1ad7368e7439e893c8894db38b099e6473b2e55f375e66e9e019dc831cc4448c8ade224970572caebdaaedbc77da3bf1d4367fc74d5e51a858c73f39932a43c55d6bf8af87388c3eef661bc71948d4d2ed133704e458e0c199b9206cb22f1d658acdd85c6b931ab5732d888c5f679b83e29a3c77d482706a8252c5a920a254fba04a4ad9f2c3cfa46b198ad1c874556cf9308ebdc893d6dd48594d75efab104e2ad43ec18fbbd26aa03fc5f022041a5e6aa78bb28865b3d7b7540bdcc18058cef378d9d9b4a906b43741656a90f44450685539312dbc1a1908da7a4656430966a0cac960a36222b1dde623287913066f59bdb18a94c4fa9a52ec6d845c38bee9444549f52b7c57e4b7ad35951b9d881026b3ea26e557ec70466d7f7913b9b4495365f6efeb16eb84754fc40668b6c9e32ca0b11ad67ca9e4d8dbafefba01d834d1221370c0d763271fb66570992a253d33fe7cea31a38b4d1bb1b5eaf3fe3fa27f769a703d687d04f468117f27c3102f2079846fd9836569aa394871a36ba52c01664a665b4fbd5a435c9a937a5be7c65b6abaf2bb9088bc8e5b87b8c381dab193af3d0ded7b575584233ea5edddacc7b7f429a84b3a681fb9d9a86d687b16122f6e0dcc626a5c502e06a0248372c18fe5d068f0198c0fcef516d325b9c9bc88bbabedd90d63ef241ec937deece7f36243f83fd6d8afc11d188e91a8db6d5c1a7db7feb1bba0ad352cef9b653c75af2a0bf1eda81ee05b2d28238cbae90c66ff1f2e2f646011639cf3374b0f08d89d7a60568591c6f20c1cb6b9f9afe0fce30f02d5fec99fed38150d077557a79ff5ba1867d40b17a8fda214450663ebc1041ae2a978863d8251dce3f901f8dab8c12a4a62f6d910a6a3102037221b6b9902841fdda856a279fbee0e8b7c73962b0dbd5fcd5f0e5bfc6b760a83d872a045711666861305318a42fd720c2dc6ac38aa51284e1b8577055081feded106b844bf556b66e2d077a5dd13d270f82b7285d69ca509c48cadb07f25f33e3473a41e47fc258c16b54855fd3dc6d2814729a5c37fa1031cb4ab64f10fa348dd0080af6c9909f9088a75a22226f3c6094c56881d5448ecd7e84a16cb90162a26f5900558e0ab0b293aad473b5fd1c31b5ffd282e076124a490569e7d91edcc3550badc9a184d092530790e6378803fe364134bab78a61b5b0e00cf01a1e0c2fde15ec29da32259c238c038c708a77f2ae175a288d0abe46253236b082eba581491ea252318a3c6542aad1522487b88539fbf62aa8e92a7c377b594713418d459e8801efce69b1279293d6f46d91260f4f22df4d1d14399cde72c3f19c516193432b53c75ce64402bf0ff03d6f2ce334cde1c943f90b1a2851da4793996aa00ab786756d30647b94664271028889168c75963ee172854dd8c8ed03bc25c6d885874508e1a4f7b4f517ff2b0a685fd8f81f7bf99ff2d973c13acde272601b5a76905e5fa1d182a2708feec4a3783b279a14636199de28d438f65b3e0ece7d43160e8523b8ffb17a6e596bb02d81b93deaa5da65086baa30e2d7eb8d458583578783deb84b20ed3c3833d1fbf4f447316c3495a1b1f964a5c9b7401b7e9d5ab0acab49398ff099b708ad62f5bf16fa6885b485a4af88c608c23f6ae04ed8834ab5bfc5adbbe8e48edb6fd165e1aab644ab50b5e2e99e146c259a67c506f1cfc51a6094183641937ae1aac6223d837f3e1845b89fdc2f9d5b674aba114469d4e40516b00117b89d1b18fe1aadc8eb9d89a7e0e05fc349d587d29a9651f41b20da3a23e2182bb656675faa0a82981be1c3e0a65f42d80cc1e2f6b833d3161261c8ec94dc000ac98c62c4971750e0254ac40898ffe90948876b3dad2f4db0a8a403fcc53402ad9b22ccdb36bd1d19f5e12ee135019259e4152f7e65f0cf6477f1de85900308869b65592145450224f87790a93ac60af1c545266c7f278102cc16ff9084ddf3d56ad5992b8dd0c387466600b949af46e65bc9a1622b09e06727e190fbec75dc22c87663bb330b379ae03687cc07f41cdc616f29b5b1db5bb6160b07bcf3362ca69f983f0bfa320ece4dc603ca8b4322b3551a32c41b79d1ce5af8f36e86ab59c6c1897d0999aba6c38df6f3469647fb72d385d571db1aba34c7ef71a6b39e2102c5da08273baa84890bd7b22951119f2ea73296cf15848b3f91ab72546d45084c305ebb3eeec63bdd1d0eb7748380d45494b819eeee69ee1961245cbe18a0041f392cd2b1458ef3ada310f2ed21bcbc800e95017b38bd4d14fff2915648508049501c611c1ce33bf8fb8faddc5326e3fc1ab913b2aedc1e2de4287ccdbd06e94c8e1446cb35b2d0fbc41312be5934da864259a2a512db0366859823a7ba2194d12f8ee0e99f43f19c6dc14dababed63a9cf5c218562c38a5cfc011be5d7efa3c98f9517b13d17a6cfc607f961e1e17f4ba61b02791b911ede67d91d55673bc1c58dfbdb811dab4707f994eeff594cee7a802f92565ce193fe6ac6f11e4d5cca4f72864861f2d23e0ae7c46106dac9fb787e767e4e9f4fb573f2dfaab604707003130a861a73337800cd221bcce518f9b1ffbb2dbd37fd076212370b32d5976de259e99a1074691229d82edd9cd64d4b5db3ff654b572cec626fa9e6307364a1f547cc1f5c7231988824d21dfa085a2f0ab76bccefa8677058b35cf90300c27cc52ba353dba1ddcab31af6b073da80864eb1ec2ac7dbe81e8ba20a01b67c2bce4e99bb0fac32f4c845fea1bedec23f9205ca9d869afbcfcec0b04a61572df78a41581a33901ccc1556f7b5dd8b1adb4202f046847d1b4155eb8c0141083d70007ee00ad3815edaf2e22fc11463a4170eeba5be340f274bd834789de6b3d87cac439771e680e4d968fd69592dbcf882810b63a8ad6ab098c97a361a2ee89333f8195f3e4be0d45f2b54c62ed20d61031f5243a8904d4dffadff8e9889accfbee69f3b65ff5b63ec9bb53cd82b230f055160e78b97c46a57210ff61f72da25c721ddaca5e3bdb5cc16d8db515d7565cb12d68c6e4250ece3b73d0ffa872bea206a26c284a7d16948c79c54b1892f954f96ce2571ef9842fb9fe9d98843961a16de8661804eee86b3ef78a33a740285cc46cfbe533788a932ac9a6ba77f9111a9a99cf22742a8baaf23bbd4e51f98441a529bf74a3345df3a91aaffc5b408df3a11cf35d78313b6310d676de0716199937cdd37961f415d0ff0edf7be208d32e95b6453447f0f41c0f101997a77e185c43dbf32f2843d79ca0f50597a4675ede7b3392279bfc15c809151c2d35652deb0ac017128f928acbbb300f1ff6a5855407921a2f16036fdcb2308d4b419ed0d99fa61b362ed883737f418a283ef289fd1eab272f02dee1a7627c4ec0b2487a99937541f915cb90ce55ceef2a331eb8ceaf52632590dd410bfdf3b554432287edb0c04d5ecd1bd21572fde326844c522223d26607c7163c1618a39d51105d7e37e8eb7fb8bc7fad5f70ea09c12df88ea2164bc5621f511055f829f6df428865846d3c02514ee9c5adf59dc8e2266e915a7d9a8583475c07f836b0f5cd22dd46856f4c2f987b48afd06c1fb832022e944901476f62550ef020fae6850a4005d4dbf78620e12fa19f11745c5f7a35b008f86a3ba64e0b353ee5512b526f461fb47ed776f1f471781f753bbefa9da53c5cb2f9c3405749155fafb718dd981324af9f4219a4ab8697a7ef74bb6abd745fdc6a5257f69070d02f8bb21b4250c7bce9495ac50e2f590466ff833d36ef6c2a20153021ded403f2750609bcbb94754a263ffa03e736e5eece00d7975a993bd38d486a64f9170037790da0e9e6a5fa0ed12995fc761b1edbc3872f397f87dd7d4aba941f040106a302ca08a7e3137aa60a5f8b73f4377284661c681e161e496c70cfd0223f04c0b59b84147142c3605720eecb76652fead1fab24bb5e13c61f545d2349fe89ebe0c603d78171e85984966a2e8a0060d86abd874e316b0748d113af9f5898fd67abf0396fd4aac5f336a9cd8a202eb603c6d9f60c6400fa6695fb0b0b8e4826df89f038fe46f2442275617cab203eb0d43c20afb08d86f95e6005444c9b5d070590c15569e0c57505dd045ab82ae94f586ec043710bb5ba9fa512a5cb0a2f1fc5da2cb491ee5bb86541208af86bf2d331b01984d4d50e3581aed9272cb7956bd6f8e77fe2e71223c2cf0c4d81e83dea3d1042be8495940077820cebcba06469b541be5ca184fcef1c0ed56ddd34b87c2e0a146bf5728cda431211be839d06ab08470f3ef52e687b068e862ee913829ec68a4a6d0f950030efcb6fa91ff4e66deab99236456f6d8d19664cdd040018f4e4cf8894beaff3b42a41b3747b04c301c291d39b29a9c58b49bfad4d124633ccde33984b203fc77ee7951d342d0b8e4fbda940c7afcafa30d99575866e2aceaa1f9e4fab266504739eca0610856094a0c9696dcf88c97036e941e722e501d78e03200d0f283659509bcef47900d4cf65b24677d91eaa33f13bda5554e6f6ef229c1fe5fd1237d5404120c42fa3253c0591f680b7571a88fedb125587eb4dd007d2c4d8879f27cd325bcbfe9f16d6bca063ada4b989d6171dc23ae80851257582ec77328816235b1da9a0633fe85ff10c43a923f1d56800aad6cfbc484e6cf1926d4e2b666c903927c617e4938cf4e5e63b27a705e5abec621e625bf2d4c686ab335463fd4e77df0a6b6e2c69894f01af66a6f759600f075f10520a7a92d58e590773abb9090ecbd215111dc4f877e9906e5f101300ea4e264ce4a90c1b3aaf2135b289c43ecd95ec9729909603e7b192ec66dcbcfe5805bd3accffe783bc304b221aae7522427c53205eb905566699d56ba7766a63c43c2810516350a3f905bd2bb65895ad619cc6fba436d11d65c144b1d5f85761b0fae0644ce97800a964695d5f23ea3e5445143f931ddda48ac2183a3d5139685efe806745d538339498ee700ded0c78b862ca045367cc0da4fbf57b250cc112b9f8b3f6f5a99af17df27328cdacbb915c9dbd8a3a7450dedfee8209682c5471b9a5f3d1f7f749140ab1744d23721872ad53cd9661d2746f0c6d25a7293819e393995900810185316304aa9e0e9015250b20af5a57a132581276bab3576cd7657ecd4d12285467f55196d18a55039aacf2a61e45e165d0d37b8e9c5706a475e973bb5f8d76772bdaaa4437005a4b002aa5c56775c4096954d63958b27e9610d3a6ad03db522f7496b208b0bf3f062d847ba1ddc8b2a50fb884c2d5862c31eb54f983fdeb652d85e9ea4e423f6675a9e37d6f5c86a05c4847a90de9165766ca50cc2b2b216112d697befbda5dc524a2965e207200712077287caa7ca6ea9a373a4ede5ce6cb121466643d92f52764a4f2f725b3c2aa360e18bdf280d86289c8b994fe25819e688a7d2469cd99233c318e9b9ec4205dff693f9fdb8a954a6ca5e4b2094a5e256455882712a8ca850e2d9d58ab78bc679dbd0f891060e1a5385bd6af12777ad8c77def8aa63b827a03b8cfab60a85556ff8a1d214638c31a63a865b0211e71a1f990082913d901bc621a62afe9b2df43192130415e1abb5d65a6b95c2566b6fadb5d6d65a65586badb5d5da5b6bedabb5d6089fb5b7d65a6bad12b8a8b2a38c0f31bafe87793d7396330341a8020148d757557d0c19a3dc232b5474252f3d1957240db78bae31963ac2571f1f4345860f20b115a7a4548ad7ebf57abd5eafd7ebf57abd5eafd7ebf57abd5eafd7ebf57abd5eaf9786a3a9caef6ff89cff3f25e52828373139083e953a0af5d3e9df77cf7bd75dd38ef1eb2de7a28b31c6685340419efbc034755d1bc9421fe7b591ae1b8bd948ce441257f2273e30f0257ed1c518e30e34f5a12b6a6f2c629f9874615324892aa9a2aa5c29b458c1e89cf0d1fb1acd162766343dfeb944b32532e1048fa6bf45b3456abaef91578b1cd00006dc05a800a180046404843f1cc0002f800b1f0830801401b0e821000040e1a10590971d4c7eacf0d1430790c7c90e1d3952384a72c0e106945671c3861a4e348cd870b5be19485c582d5e8d1864a031a3d49964b0c0b0e2c20b31b40daf54305a78c139a0010c5800100a4800027e3880010ae003010620801e0200001e80bcecf0c3470f1d78ecd09103470e38dca06fd850030d365cad195c582d3568c810c30c192c30acc478c185950a460b2f54eebdf7de7b1f5f54ee7d7195bf5009e3bd3d2a59eadbe3d3049110d00f5ebce0c13e42f7deeb82f772c3df87f7e666c80a88ece4b0e05e25c23cb5caea3effc6c4bc2a604303b340ce303b3636363b2a21a1a12868d2515112fe2016796a17307676623600f2e1b9414a8a4e0b34343521c819a2c40850506656e4a95b04c562b1a01747474853c05e305130313152f1f1f971a26828e80910ec29c9539fb8183204a7053c319d2552a99bf0f592b9801235331940a16246f2d42a8262b158501e1a222a02836424c5e924b4636363b3f3588c080e64d04f4f0ea6266951536383840ece4d129e47c3022922c5a428851999983885a94d293f3f40501811090941d37c50bc178c65643268acf4108991a7a74464b934e0bb0e61c077cd59c0cc9f3d0031c3cf5798c8f24101dff59580ef0a43c09c417e98f33428b28c38c0371d32c0372d2ac04cf994333e4c163a91256908f04d6303f8a63c029853e6f430e7e94c64499d007cd31a007c53257898269ff207c85c61145912e8e57b22edf04d617e7cd32279e463f6449644eaf13d7f74f89e413c66ea73ceec982537913569747c4f9c1cdf5307c7447dce9c1ce6fc8c89aca983c3f794b9e17bcee879fa9c3f37be651692626b4428b226900ddf92a8866f6944c3fc3ee7918db9135913c9f52d89b4be65cf0cf35287ce4816ea322f7362abc4a234ac6f69d3f22d6f6ad09834274864d119df3129061964cc4320bae88fe9735e88d8da58be23110cdf5168e540228d31cf23bae8d1ea3b1271e1dbc60b31228b22d115dd115bdc55df30dec2a55189adec9788aea68763ba5016f66940c88005f4205d7708881fac8070e3040021200c225d57e887704f2328fd01420344a402841bbc413e847bea6b43009a01d008802647baee4e0fe19efac2044007003a3ce8fc48d7ad01f2a36926f402b403d00f79245d59928f9066b11e12490789c44322cd485716b423dc33b3d141938306074d8e74653b39e4e85056068343b8f10d3a3a94aeac46b2d0df08f7b442746503500d403484f348baacb531271265593a235dd6d21ce9b2aeb035916698482e33acb025dc5f8d90468ea687b135920588b2aa103d92ae9a2459e865d0894167868e8c9025dce00743b8bf9518e191a6a74894556334d25583aa4d8d74d51dc942ff42e84288b40a9154e19ef430c20d7e2d84fb7b11aa84359a9ec33e43d245917c7c7c28d250ce39e79cf3e34b98b3cac3ab84393fe7ec1366a973f5a93f14a93a518bea500d7a42cb6f5a054d1f4a218c855cfcffff9cf373fe0aef25ff2e9eef22ffff3fc44596fa43705ac013d35922e7d0459e3a8c05c562b1201629292929292929ffffa798782f4f49c9fff3c32d53525864a953eaabcad40b6879256a4d9da919f8c36bb3636363b3d302050505050505252525e5292928a0f7928282f2a7fc292828474141196a91a54619222a5261908ca44849116a91a76601736b2ecc85b9352b4c4c4c4c4c4c4c5050508e8262a2c27b4131314939ca5350c22db549686262125b91a5368911c141d04f4f0e505042a124a1a41310044110044d4c4c6e620296782f2620887293a39880e041103cc95283b486da502474706e68122626e1d42b6241b1582ca824954aa552a914088207c1d4e30b984a991cbc09984a3d954a219564a953484952a0325300c130b3d9b1b1b1d9518142a15028142a954a3d9542a1bc97140a053e753085421d8542fda8c852a37e80a030221212422ae5a3224f5d92a7e63298ac26ab19399d4ea7d3e98442a18e429d1e5f50a753eaa8a750a7d34fa713ce4896fa8493e3821e223b3040a142a1242121a12492effbbeeffb4ea7d3e97b7c397d1feaa7a34ee1f77d527f530337d366d2cc234ea790244f3d120b8ac5624125cff33ccff3beeffbf7798f2f9fe79dfefdf4855b6a2ff43c8f486a8fa82826e96809df174a9b1d1b1b9b1d53d7755dd7790177cb3b286bead0d5fd5c22cf1644967b9ba7ccd394e70b24cbbd96a70be2ca3d97e790b872dfe5791357aeadb6245b783d6a285fdbb6d1c05e3046d468cbdbb651236a448d36165e8f2cac78aab85cbfc9c89881f9b67b3ab2706ff7421c76d8bb90aab31361d2d927c7cd97840e04fa7c3c2c0993d8f479ef65aab877e7e62c6547c47b1e10f82401a40249a4b24308580e904cb2213289647066ad31e5140f0795313494afbd7ddea8fe2b61122661125692b09995648cd7633e0bb7166ec9ddcecbf6f52534df9e3f5911413e9db52c849b417b46a14fbc97edf367b698666cc51edbe77710230f2235c9e9b31765cd1b7cd2bca7328d425cb947653a4564b927c9f23592a5cc29dc187f9f1682f3dbb7e99b276354783de64dd468decc1b6a145b1d9d821e51a43a53696c57e73d4ea7838ac52b23cc02ad33665e60c6c4d6366366cc8c9931fa7e6e5b096f8ce2bd7461b7b77f1e8fd2b7203a2e943233942f1913335b4ee68ba8b5a71ee97b29335b2a4fc41f78550c1eccc3c9c0c52ce9d18cbe9f31865586aecb5773991ad1292814e891cd14a9c2eacc54d14cd50ecaa2493237e69ec394d27b5488080848dfbb50cab5be185146a4414f49bc178a69e9f8251c464a4b5d76dac5ec6535ba5b42f3653e110a4dc313cf6c89f8f37a74617d14225356f8a851f68a2d2ed4402603cb86b0baabb76730d0f73b468cf136b2b2575cddc76747441635a2ab3b832f01aae04b42df9d81a0465a691e41145a4064c908446ce19b98ec18a790aae78930e9f9edc753755ff25e4a9fe1478d60686484194ce6353357f7392e43f4fd96a1aad1f7b5d65a77adb6567b2c6b7501f3e1073db6327d6f874c56ec01897c3b36e7d2d0dbde7c43a02dd237f65c9bd495329ceea55088d45cb8f14be75c745bce76329d2c2767389996330fc4957b53ce3e205966a66a66215877616c076565357375cf837d3e20a18c285342df1c538be0eafb8a547b628bea99bde8ea7a3c9225dc42a87427c2a4bbe373261b36ce9d06976d4cf1713a8c802a91cf95713f632a60ca97d2f3315614e8fbedb12a0ae2ea6eb195bd70de4df76ee2c270471e2ecfbc69e87b22fafedefbca83f1f79d87f3b479e3f5e0ee8542a436bdb683e0166eab83d0c289c3a9ec1002960324936c884c22199c1f8a3435ce3f0e85485d7ae6e5289d3bf7a2eb72a64b19c73cced92b9381cdd5fd41f03bcd70f43d376fa64a8673887591c9c495fb9ab392117cd9bf5d1ee3a9a23a82289d7be9be0be7109c6fb69b135ca3ef5980336b50f17b53ae7165decbd488b254842a19af0af369a17be1e59861105d38834ae1040a5d54e0dbf367fe98c2222a3d029a2d27d84815345ba8d1bca11d467d28e0bcf7d4c805c73e1ed0e746fa9eabd195bc5cdf65abb91c44ea1ed7f61b4859d90b7f57a55d88e0dbd94bdf2d7b457d431a0a911a87df54dde3578de9efcd8c8214d94091238e28c211a5d3d229bd86809824d2f4f31546d773734e395fe4e38c6a7d0087b30993f1a44b38f0edf959348d66cbf62a89e8aafe7a0787280c4c288410f2339c3253b5294cac9eb3d4ce267ed097acfde4415fb2d4561c59331657f593e7fb7e1df596af329c335cdc7408edd984be4d8f82c8218a446566cbae316d271285116943b3a52b328aadd214b165fa6e32bdcbfb33851468aaeae6bd685ad41d9036a7100da23031730504137413cf3abd9e8b99de4c55fd29d321733563d3d34135050a9a2d5aa84ddabdc54193937e6953fbd4d2d3415970fa1c9a44b3c59ec2582cb645f10511446a8a4487aa0b0b7c9b1e451b6bd7d2338b3eccdd8e88c763a58ba872c854a9c84a6fe24abd145f10b4d6d398d912843ccc6c7969afa71b88adeea597c22efcbc977919f5d154554991684893a6aabe4403df9e4847babe9e1ec554483d545ad224da437d9288ab7a15e02574fd0c743d91225df78c8141dfcf9e3dbacacc1698f6aa35fa899454df6589233510dfc2043c7df25527d18c4d22535527cf06ebb92dcfa2a9da5e4f5ba01d1361e2ae6529e5753cd1aee170abaa8eaeb7afb8525f345bb630c6bcb9f96cb83f109caaaa7dde6af2b4dea8ecad5807d6b8fe98a73e7c4c95dc41a60ee284078d377c80507c3bc674dc56989b808b71460ca866cb8e4846649740f66757c96264a0cec0484fcf21471610baca9e65df33e86c86cee84a67d148674304e16cae31cc615fbf6d76fb0d3a4490d9129f3ddb1e73dde6dc686c210d2aa78d767bd55badb5d6da4d55f6884ff59189278af48e483893c03330db36ed9b16a2a62acbeefaeab37a6f155e3a782286ab2b2f9ed346a68e3147045c7410a793b8ecd0f391de6f40624bdec6738f31def418e34b9df79d662c3bfa994f37e552de9ff7e50dc38724448142b868fa1d536557da080983a2b71315ba80bbc4a11f17f3768ee3b82fc7771cc7e5792e172173e4ad12e30a7d9c337efb0d2ae34da779cf5008151d5ffa964f7a7c3afef37e634a9dd92277f2fe4197bc122e612f4b1d71b827a0a39643acc4d8c2d8f8b905b73616a3adb434d2d2c8a12c44f12180403d903a2526f8eab90b050106859156d19109287a34f649d25177e746ba118fc74a9b3e6485a6cac2b292243e99335bf0ad76fb6a839983c8923437882ddceda78e74e11ac9626f7188309fd03a5dad754b9d1fdc2b4729fd4e2e153ac976084dbe69113da249251ff9ae32a86f92e373980485af7938300ebfef04e38f6427c19909a8a224528cd2d9fdad7507517a10a94f7aa8b4e9a6a72e69eeed718d74491a1a7d5fa33f95ced3c165d7b4efe8a369d7de65a62e9bb4ec72a9cb5e97bb2e735ddebaac751977f97639ebb2ed72edbe50882ecf6e9391838cf881693b8f5252822fa2c456a6ed6f586be38fbddf2bdadeef1adae2d055b4b7f5bedeecdbd76998d3d7d3213b4deaececc0d0f83b468c9f9783a48b5cde37471dd2570815fde92c9bb4ac892bdb4d3acb25a492ceb2f793e50e27cb1c95e1749637a24d675923a2e92ce3698375966f52066473acce72d5111657b6aab34cb5107165fb8db983aac495ed374faea4ab546868b200506578f8ae3140be69d2cb373ddac10220ac38b4862a1ec20309c19730d215c90e61a42b5406d3b688ee440ca14bdfa96be7b45c7172cde9be7938342dc4a99c4f9e6867c10eaff944bbe955bb4a7f4f7d57e98a43593b845267aeec312adcf82309f7078e841b3c85fba129c4d1f6a9197cf5db4be1063b1ca4eaee54081fff5ebbef8aa874481622b5466fb40791eff2a680bee149f4eab58c6be48ca0411644f990a0ad6a5a6be516cac0997bd16139336cc363ca0f202195850cd47373da991d3655de8b9c33a33617cd793493c07a7cb3844597dc4064a92dd981b852b1a6f52516f8360d2d1f502311ae9930993964e6687c4b5edfa6519f6d4026b670b00c81aeb204baca154cd56ca14051f4705cc9844f908e4cf8f4e83d3ba0eb3f1d99f0d9d11236335552ca447a424d1555c97a7b02a76a88a9929f308d07092492901d47338fa39dccbcad74aea46ddbc671199648785ca7954a5ac7755cc7791c121ce7691ad7711cc7755c892b9538ae3395de691df792c9e3342ff34e6578e275971987a5b76d1bb7715ee4a8263d4ff38ebd63ef98fbacde39cd2b3dcbf55cccd929a695eb382d6aca51fa8e6a268d96348dbe640ab7895ec3b293b2e4555a65e5e4b67dddd76de7368e662f65eb614a354a9f7925aee3ba92c7759ef7cee354760801cb019249364426910cce0f45aa35252fa31ac799ba73b72b657b9aa3469da4f3344d3b8d7ca6128a64e4a47da692a67146d21591a42401e7711c1706d179b2e3f0d7b9be8ef3bc8ef33ccff338e9e260529280e3b24e22d1691ea779e73aae93994791d04a9ce795b4aeeb3aadd33aed25adeb4aa64e33692593a9f3bce3cccbf182ebacccac2401f78eebbc54c9ef4b5a49cae33a2e479d595d9a45e45449cf515927a52b3623c9825fef0365e150c6cc55e5d9b873186fdfa785314031f146a0185d6f31fe3e3db5ac51ced2b461c4a94fa034b6b8474a6194524a29a594524a29a594524aa9a5d88603264bcaa34b949f7c85c9e5411597f22ab32575d4493ed272fa4c2da5cbe7b0c307901e664b6585efbc1719dad8c12287a9923eb45c44caa9bcb979ddbc6e2ea5f5f3f293c1778f3d1d9202cf2dde65ed35b3c8eee5eda5ecdd944bffb2e9a7fc7d249f4e92478eca244f65d44b72ea2a72c9c1ace226194cc9444c00860899058df127796e50728b9f6494afc82737c92bce65a9c31ce302165fb1197d92e86a9f301fd2f7c4f9f78cc1ec99ab7bae878b73f66c95e6c23d63be674f0eb74aa7e4a83bce848821b86bde4b774a4dba94941679fe0005b1c854899bb9ba970ea8f33c772825e32e056b943c7ff28a3c83f2149aaa7b30cfa149749fcad368aae6d1544da49994290c8d99aa1795a1b0992c539a9aa9b209335562aeeebb4c87e8983ddb8762932786bedf7248df5ce39c7b15e34aadd59b3d53e553f36d3954b4a513f4d06c199244b2289c98464a4f26ea082e930582f3debba8b4847c7aaa62b07650ac6dd2658a9be9a5d24d9eb556b31ae76a547770a5523744047487c6660b2d2add4b7f688fbe8f9b12d117ff5ef5f398c2ca85e1169636eec7eb51fa160af1ba0f48d2a4c9ca646cccb432f71ee3ae843f8c5d4aa5d2718907638c317de15904cf227816c1b308be8dac5924aeee31778c4b8f2fa65249bbe95aa9743c8754323426e968167da573dfa6739f772e9c3f5e8fd24da1f7e26d9f4153753fe79c73ce39e79c734e1c737e0331fe3e9cfdf616d750a4236a54faccd90be601edbdd8cf195bb187fdac4264e5f1b291beaf4dc4562cdad547dfd71e1d9cc9c48dd0a3919aa9aa185f3bbea99e6edb7c779a6d9fdbe7468fb3e3eeb83b2e7dcadabd74ee1e77cf6788764147267c6074778c81989ed1d57c2ed277996a15974b99791eb636a66a1add8391258d62b538b6ba5b57d493c3d97b99a10d8c7d4c95e93361cde4993e431642a4fec2addd7b8af7826f0a85d4a8e1f158e97a941afa664f3527903282e0ecc0afa3210526b6d1f39c4c15f92a953a91c55ee54381dd1890102b83cdd5854555b5b9ab188b2c8eb65f4074dd1dc81d48a1ef814040745d26984882beff21b65afcbe00d1759b68e208fade87d8da7e2f80fb1e624bfb3d0f5108fa1e486c65bfff71ef23b6badff388ae0b74bf23b64cbfc7117da0ef7388add4ef75e481bebf115ba5dfd36083beb7115b23bf9fc1a5ef5d628be4f73558fa9e466ca17e3f43067d2f23b6541cb1e8fb95e8ba47482fe8fb55745da4fb17d17593ee7374619818b105fe5e155b26bf5789ad13949816fa3e25ba70ccbd4974e1978c0a7d9f8a2e2c737f8a2e0c4389ad94df83b1b5e2f7a82fb64e33267def45179ea1b9d7a20bd3dcdbe8c235d2febe8badef97c5f3a7f772eba515e3efbbafb7d27bde636ca185c70803c66374c185c7e8723d461b361e230d343cc61a6a788c36d8f0186fdc788c5a3fc61b6e788c38e0f01873c801c763c491e331e6d0f118753cc61d3b78e8d0c387ea7bea1f8f91c763d4e131f6788c3e1e55fff155e6b6dce25adeba9ca5b2e9a59cfa482ea9c828947cf294949c59fc47de92a686660626f38ac130494847464544434241403f3e3d4d30b18309646ba6ca9694e0933a190f5d09c9206d410dbb8065304b3355b6885ad3853d54ba7b175a2258c319ccf4755a667196c14c5fa76519cc66d85a9b591ecb636972802d4d5c599b44646531150f6e00cee05b42c75420755caea5b135b3e524cbc21e1afe3e10943a303e2f3059571a5956664457f655f6c81bc496bdb441745d19c9125babdb5f18fe40edb670d555cf8c286b025119e9b23392c548cf5c992c96c16e2c96c530fe3eae0ad1ee47f6917b641df2c6306dcf23efc83af2c631da3e47c69173c838e41bb2ce37b20db9864c43de3748dbdbc8aeec4286915bc8fb367199d87707da7eaa72112fe45d69a0ed63d8afe45d71a0ed61c8bbea40dbb3e45d7ba0ed65e45d9bd0f633f2ae4e68fb18f2ae50687b19f2ae43d0f634f2ae45d0f635f2ae5168fb96bc6b116dcfcabb2a41dbbbe45d99a0ed67c8bb3a41dbb7f2ae5368fb17795729ec4a855dafb06d05b6bdc0b61ad07603da6e0afc559fc577953aa00ed5c9196b3b235d196c46dbdb0d44d7ede9e9804c6c711a88aeebe373c42bb65a5c20baeecf4f06b4bdb5b71588ae6b2da05d21ba6e50501692622ba342745d21212b20c5562785e8ba434353388a2dd314d1758988a2a06dca09d1758b8a9ea0ed6b516c956e5f99105dd748b258a32668fb4a145b23b7af4ab04bd0f67528b6486e5f8b44d745922c16498ad842ddbe46115d37a908d1856186105d38068ae8c22f15b7af40b105debefec496c9edc99392a0618e1043849710b47d7522bab04c13d185613dd04174611a94db94db5722b1b5e2f635165b25977902f683191fe8ca03cde2f6750638883bb17572491ac9627568105d1726596c4e6c6197a5912cf639d0f506dad640db732bb20b2fc92b7eca25387f6791f1b9bc7a8bccbdf0428c182b2b30c0c0c22243c68c1931c420830c3468d4a8d1d2c262b9b8cc3043eb858a8b304cc92e1c25a798e49383d9e42a3278545671928cfa482649e5d24d39f52e9b9ee5ee5acebe65ed2c727d8bbc3920aeeeedfdb53e2fbc90c17a62c4e8f15959f1f98101861f201616a0201932828466cc101a8a218621221964202aa241a3c8a8460da3a396962324d659dff326b9dc2549db6398193e434cebf5424605e6227c2be6c54b4546bb8085c734f92d6832cd5b9c8605ac6eabef5be4e7ede9fb9cc3bc53fade4556c93b45dfbfc83bebfb56de2ff4fd0cd925ef157dcfca7b86be6fc9bb86beaf91f70cfa9e46de34e87b19ee63c83bf640dfcfc83b36a1ef65e41d9dd0f72c794728f43d0c79afe41d8ba0ef63e41da3d0f72fe41d8bbcc6568d2bf75fc671e5fe94bbb8725f92bfb872bf22a3e2cabd0b198c2bf7291925aedca3e4c795fb93ac1257ee4db22aaedc8339465cb9579165d070892bf723d9465cb92fe51b71e53e9573d8e123aedc6719480f71e57ecb3efc1057ee571988b87263d7759bc783ea3d013a84c399aa1c9c16d55bb8bd8cc5168cdb4b1dc49637820882d8c249170d82c140db170b223e010d82697bcf5daefed67a6f55d5c3c82d9c74f9a48b53a5a2433586267d3a522883516c0a37fe605a0debb56bf732114368de8ffa4e65df85dab550ca1d99d3c5d677fb1a83d832dd561d6dcf993aa9b37ad357e9cdc9490d5dbc3e215513218db4f62d75a42beaed57c87a2f9508a935215db741b1a3ca3a82261ac80303858ce6beeed62b659d59503d8326a71b2fe913aa67d084dec495e54e9788ac7984b8b277e1e2e48484446f7ac3e93d399cb9b297df2e5f0ce4f1c96027f44554bdb3199aeddc05ecbb301d5bdd6c913568b6986e5f7726cb08bab22f9d0efe7c3df6fb40f0dfaa20f068958e4c04c1467371c6286b695380e6c2e8cafee4b4c523cd8fd4c5237d51f20de3e048c509d2ecae57d0f6d587b2a8cac3ef2aa47f1c1aa396666e30e5fbab382fc28db18b707f61b8bf1c6ef0618b30c4bf342afaa2ac4973638f124aa20ba32c6983b8b23f09b76a45b80f73817128c026b4fd084c07b09db862cfd95b1a201c681b991ea8e46a9a0a93245433030000004000a316000038100a050322c1388bc24498f20114000d72a24c585438178bb31806338c414621060800040000303000000354220049dd88f78c7f0a0d0ac0b5b2e2b5c336b0d53a88e8631cad87b96e8963c8e4d3ed3f405bc762276bdbb6be4a11d1d9db079d20814681090b894501e0f18b08653c1fb217e43dd5df5eb83b125d729a78b54f4e9fff435104e811e6a0c9880515d48b1dd1174bfce5092730323484cbdaa3da1d2afece43cf9cb31d39d30a2a59a86cf96268cd7179733d109826f166dd3873f15ea97886126c5f404ac3a37730bacdac45cdd843d6474cccea2f53fe0db8630297fca0033e9dc09f2fe0fb12fc8fc05051c0020f9c0181d38780791658e580c76980ebc074c06e8bc00e41c141dbfdb3ec16242cb02d2876c760ff22365cc2ce3a44837c3be7290133924604e5e8739440824d0130b88804ff860eb0b445206e17a734253e0c2646e9bb563db321526fd2a8b5efa8f6fa46fea45811f73a0ac6ed38a2b98582e81c29271f15ad4375c39d0a35e5854b291e05eebd3b42b941019a228f706e4840a4d823ab7b128c29fcc8ea9e04638a40b2bac3f13185906eb27cbfe3ba5db48b3a19e31f23083b0382d375d4236dbe928723d3bb2eebe683f781a5f4981be1c7331af049642776cc16f914dbe53b0e21ef17a786458b8b0eb52ea64251769871076805482894b2edde5fb360d4970e33573a8defa8a12867b3edc533410477a7fc77cc6e1acf4ee8654fd35f623a97894c45cc83dbef100bd2e5c2d9fd61eee1571a07bd0bdd6986048857d1bc68d748edf7dea6016b4b250a242769c63f15d5e4b55461da2eac154c75455c47b78c94ec314616465e450c45b780a5ba2a08340ca705bfe05101d85f909662445705d240415a04f51c2adc9c045c6cf32235fd969961cc986821fae213e0d2aae256922ea2fc4096f4a08af000b98a93a31adeeb228d1708e37347249c0d6fa508109a6991551c038c28f8cb734dc55647bed0c35da547d4ac8b4b4fec43bb0fb9047a66beb07a688d32937b03d4d5bfa4b9ddc109d80c3b99d39e51a4e807c5a5d41ed16818294987d3e0294a185b355410b033517243f876a7372be0d8290fa5144ef4c67631d88c0ed8afd477b281e11bdd4166e15014b0fa3d2123ca34390f8340ebeacf1d22477478571a4c849bf64e976ea70f59f2305613e63244fe48ec908fccfde9ea90648be372990c3610d507bf882006a2f2cc37ee0077d00fa48e98a3d49750cd988934240a488b3d0de252568496ead9c9a39af133071213883694529cfdd928066fdf80fbd52b44913fa54a309db78bc8ee59904fe2919b2c037e36996f6dd784a65f814fabbc6c14c434495b22c72459a86371f7e46e4835aaacf64641812675e08a97cca53a08ceae127e897e5bdfef4ab5e63dab6f1eb93392fbc5dcd3abb352fdc71680e73b14253f6e8bebaaaf26b1398768702ac1271792e03a7b4322ee81cbc8323b34a8a4b5e29ebbcdd8e32a42d1c9fe5c1bab6293b570aec7d50e755bc175c517095724c91756cd278e9985674214d7d8c7f6e3d5441101b28731158719519ba11ee05da160a9bb2ef7d5ed6304449d779bab6fdec650d4e3c16bc96ec8da9d4f84bdd01e242e3a3e587b7887945e6a80aa43ab5da82bc48eab6a10f4c42b25ed3d18ebee008ed3fbc7494f2860a44fa7ddbe8bf715508086a0b14ecccdef51a32620a3edb152ea26b456ceca9ce85c325c955ed90179c8b1a5997736caf6c54ed65387dd8b4b835670f876221cf12dd2a3bc2afb8acb3d742516916c0f6979265ac5eca408c430eb87bf37c64704791843623fb4776f61d6ca5ce9f10b33b3968325f3bc7766fb57236b0108858c7ac1d51e59d5b27181d2820f3364e3ddeed0b9f257fcc9dfc79b1bc144da3cb32c889ac79c599b270615d4e7ec3e84acad48109940a54d24bd09af8716b60a0aa233b26bc731dcd0f681baee9068ddb8bd3224c8e21a6c65b70769946c1059b6917376407a0ad99378d21b8b9aca4ba7fcf30eeff2e4fe3f73a2c9f4c09d5305d6f520d6ef9d343713e8bcb75f563bbdfb846ef725d14a9893ef9f96ef5ce2df7611414800286660fd41a5ee9d22b69b1ea1c08241fc5c797ad8c59d2247a0d36b30c3f37b821f66d65c2a4a737c8dbaad720ee1d268908d4d9f150494c3ef3ade7cc22b094e963efa5059dacc03e5a9bf50e0f07204279e160580f64a52e3b4fbbf8b11133b02155e91cf5a54179d2653c38ab7d9449fe9ff8901835e99350b679fca2625690ae43ae748812d17a88ea531dae0189de501381af45dd907c736f1f6734c67d033413f6a46c9c77559a4b0f819e4ce2ea17b633db35609674821e227881d5b42e7b1c50b39a8823c8ba0e8a2ca037b7e886989e3e9636b7f36fcd47b29fc7ae6ee6480fe62bfede2fbbb7890d55b576dfd6b5fd03f81f3dacfd9320aecb5474bb583c1038053ceedc8e5f7eda93ce6dde985d3e9e4f2fff4e91c32fdf543d376e6d27fb77418f3ee19c4ec11b7806e550c52bcdf7444b41f80b8b6235d38f87c847064433be035892df9a4bd88292d1605504f447e69a8d052fb31a289ec678c2fceec33bacae9c2fbf63c28ae6129bdfef94262c2fa020aa9aa8f3955f1f630541855c108dd0c58d7d2f8408be1af45afbfcb1bf9377a9f637a997e4ddde0a6df38695431799f3d5f7b19559324ca1bcea12d43c62aaf1acaa1054728dd1057909bcec810a30d58e024095c3d57d3f2b3a547b3a725155a0c626d1d15daf8a6e6824b233dd8e02e671b149477fe4acfafd60645fcb4bada94321a623065fc35e03b6875a8a6c2b29c052d4291fc6733d175f12ba4c695205129b110b20965ce70fdb51db8e213f2fe997530cb4b17f501c015e718a37abc96055747b493fc5911bc624b0195e4c84bbbb8a471f8ac5a43c7aaea274273c46a74a492865df1ad44613f60fb5be76e5c046c0c817d6d7f0590c1cce675dc3e9d6b26d3df7e83cc059d2ffbee681845f901e6afc22f6710b187b30d14b7315c5542397067b631de6b86ab9badb4098cd82d39d633707b55234ae8a0332056030e1a2165bb300a0fca4feb484cb835eb0e8414d512c745c20b6894c1674c95942d8afa83cf29cec04c35d318261b4f65adcee0d3629db59586f04e47f15bdb48130d5228ed18a5c52baab5a5148be9383b63e3ffa7e162ca31b6b75d5faa4bb45142b55cdb0755d29455aca3c52562e34e489fce790653a9c1956474f194d1c66024133d53074b57b28da4f2b98884f415a2092e59273b798bdf8cc85994ba0c5c65203b59782a120ec1b134830fc50f22f01f00343a42168652d34a0400bd55139973651154306e9de645bf88907015f2e029938066fc3e0c03bbebbbfb31360d6d3053c912b9ec76d2c2c962fedcd72b10ddbfab8dc743e02f313b06d8a0827e576b12ab069c26d2d14fc528658a9018647f5a538cee29d9b83610bd95f0ee231f3ad5d69106bb14ba3ccefd4e8539c5b9416af8507d6620171090b148870aad370b10cf1abe96068d9ffd52e5a9a4db3545552ea7d9d6a39c898ca88c45291d7950937a6b14cb1c439e83a204dd8891d53642283a858c04e543b427ad7d78293b795795525ef563c428a02a16484927304851e1f20e2bd4c81b1026aa9365d43b427f1dda5b1651f6e0dc5091ca397bf6e38b55dbb062083bfeb7947e21d0936fc636e49db24c9a4da14b730ab70eca23a79193f70349bdf486207cf1e21777edf29100895631331e403185813d618f5bbfb8f3af1795e93fd82214ceec29faeaea8808f45c2e1c30451116ae269402948ff4969fcba990b72755149094d8bc7417c36112d1d05e9ebc46683950e9a40ea7cc0856371fa25ed401237f29d5dfb7551eb4ce2f405ae7d88126dd93b230950aedded361ce88d03134f40e6c9cc3d1b9af9465a1133a8728a2cabece51943646080e056d45ff831b55889668469d97a6b351ddc259153356b58dac2a807237eb1b749ffebb518ade2dfa34886a8fa15f33f496ff6d11fc196aaa76f16d6116b11f087a5fc861a12a925cb060d555e19404f8ae51f299aef343a9f47af156b36f22de22487cdb01aff059d3049430cd15db152ab28885855d2b5463ba1588e37607863f98d4eeac1906ff7ba0e3470d323cbda00bd2565efea0a0cf28acaf9eb7d7c9f46ea865a3871b3976002843171a2018cfea5910c23d604c2f6d96ec488394afc77416d77e3c69b2703884b7fd961ec98317a12128c774570a887c728984f61a30af952288b19c623513704b0815c4ffa18ad68802a8c899e3db134168e549f38293757efa98da21574cc5db5b071bea6008a6516641e6a147a5daf523ecffa1b3f8309ee28cb9b44c6c46658fe5c258cb7203298d011f8b3ae19ba2326624bb5b3dc2d3ae4d3c655058a55a41ad4377810f9ebd6b8057e2042e4bce705d963a47f1f9a605a53441c446dd98062c36a51a0a28f2eed0712ceaa6d92fae4546d6dede148752348071fe27bade361a30fd8143f680baafdf86cdedfafccb22c265514dd10509507f0e15680781502be739ac389462eeb2d5f9ab0254eec7f9c7a12c6a68df63ace5c5ce63f80665f77b39531ce2dc5a257b88c297bb30d7c1f2eab9f7bcd9ca07ff991ff43bc78f4a740dc3698c6fbb1dd5ead82621d5f324236d40f42a5c158965dbbb8a95cbac9adc0db65a229b2820d85c7167cfcaccedfd0db7540bb0f68350a70a142e260780860ba182f8d4a83f54478eba812b238b2c4445eef95a44cc39bf868001bb84b4e7740571f3d7022207fcfa119eecf2a1cda7ab870d5e8b4790373e0660c746e7ebf293e328171c1cbbc8ff6546cc37ae35951cb5186ea2964a7dd197a614afd399b09268cb29569eed260245c75a8990038610e10991808b7a6d78a38ecb2bb07db0dc149e42d224dc2295757d203644424a4ddb18b64320c744e4c3f9b868f15d1e85e75b0e6138838e01271016a05600551a57c67e6f888c63d0aaf2579635cb6d3646748a983a8a278636845c008893e63052819ef086962dad9111098563c6f70b9cab14fb0972b00a15d41e30dce2872182f949c8677111116884e01a34b8777c5902c6048e223fd5db8461d90af25808b2023829baf3af9aaa2874f556671928c0deab228b2768a09c6a811d147957f184f50a430e32d1bdb4bc47b484b804c5839654d674a04ba93953a79d9727052b63607f0200b2e8e27be29944177a8d83926b1f682421507abfe9d3f84476082b04dc10d9e7c8a9a7d7fec75164c1da8c17db461b491838467a2d351bf2bdd4338b27f3eda5fd3b94d06a6adebe3f53629cc47fde0e095dc2fa6b72d5781b18fdfdb27132ba86c6634146447e71b8de46a39def5ac5b9534a67a21281c830d73668f9037b706055df2edcd87aaaf38953299e12dc45cce2714a7fd785430caa9bd1b688848acf96e90bd0ab2a82913fcb04f32f1dc3e83e60bf125d60bb510926880e8aaa511f84e83f6106a382c790e3a15105fcd841e833a88e95f851fb07b96c63d6da48ae447973fca83b1443f351f1d5a1c98cd264a9f3b020975e82cff6605fbd7dee689be8d03f2678f105aa14892937dce01ea4aea19247cfd76f25d1b1fa319182204395e026eab550127f37e9b8a185fb30b2460162f161c0347b71e660f50ab40b5b99d4901a6824c47edd358a22a928f79cf854a7ef8bd83db9cfd72402f8d48f1992f48eb7a0fadffff5e300334f3da7882c5031085d6825995a7ab25e8a457ff56a4cb45bebdb20c73e920ac98b990f8a2efa339bb7f7670e64dd7a3a6250683aba7e5e0976339eeac2eb5a6b2f8c24d39adb608050cd8e7b3409a1700d933c01bf8c62bc19629099f3d2af9f89880f653a0be6d2c35b9c7531fc00bdb6e96e28eae7e2953a98455c26639f0fb6dfba52fa200eee9cc385785b658530aadc23a9a13f974c466f564b0f7c6e124758eb9dc05a176fc1c6bfdc33b320038874f8519e15a7e2ef28404fc119d103c165f468070e94102180d85559cd43a502e63fece4cce11a372810a8ff1cea07701ac044e7afc9433c005f100156139f7b87c8dccb439908ab01a6f3c893e224856b2e7399e0dbb2261522d303d1cc5b87e69d1124a0178bd8c5d168b7f5c2e6d754b99714aa34e28f95d429a1df2925812376e2ea3555adda974bbcc7088518a41cb1dd2b716c8c56ff8aa31af038c5135557bbab9a09d44506250ac5cd413e454a726ff7e6f794921558427bf978e7c1ddd2c7da4cf5dd96b0849aa9d924883d97dd542aaaf2fa970a9b6eb4f87a81a9e22f871e0de9214eb140ead78a2849fe3c9625a01f18f3e6dee1bd0a05d4b12f8494fa3930901ed7302e8c8967c528cb01c3f64c749d1cda990b2814c84e8bd2ccb51543017c17f4db0c747d02dea31a0bc433908a6e74114b29e4af25c2547be5760777ce3537df73612496e971bf70d73cfa767b327f9522eae7ae2ce7d072d2970c57c180f174bc30324c8f55dfc067072c09d106d7d4e425edfabe3084773226f9b1863bc67edff58293f133f7ec344b5cd3a81a1656aefd0bfa3919de34e2d1a78c592461c4876be2846d5f5765b143823b9c46028f334602d8f83c27a3e66649e5efe22d904c3d113acea92f4461cc4a4ce49b1c29c38a283420401f4eb8bd9b81a575a63c0224e4596086888715b31659a628f0ba199c5ece988824dbfee0727e0f2e8c0683bd637866feb545c610930af1b8e44894ec7ee966fc5de2947c72b693f647014b7ae28a343efac47a992a3c80472d4cbe5fd68dc0c46adb83255436a5f40fa6d11f5d04106261255f3f93a2460686f70b779ac61c00dc7dbd3ea3da69589b7be19886bf42776a08d244acbbd15f83a0b9f263a3f513c2dbf7dfc9231b575f9d1a7eada314453bee684fa082ec02ffe61b8f400333accb7d7dd0379fadcd0ab8c0a911b927c6a0fa8b789ddff607e54c3c26e6fe047248e975b08ee3e6aa21baf3da32ae1ab6d7b18128c84d33815b0eb150c666cbd308fe3ff18f43778cc528d0c4d4a5cb868912c60d353ba9ac513440c00d31e7c6df3856b67be1d2143e1ffac1e5d41bcef6f937327b4eda559e5fc722672cba0c1c7868130725fd1e379db1f4c6fe9473e4287cea551d2b119ae8cbec5111be3b27375c7f18f55b8ce7c5a4b37af360baf4ab4557446fdede79699c71e736bbce7fcee4905044a244778b5c1e6a669745cf072223a828e00fd09403f5ded0ebfbd603ee9b1ce22057eee0b7cabc0e0366892486b9db6fb2fc3dec471e72a57f5c71aac44a49079d8c981c02299a829af1688e53784ee830d6003481f6c5608baf498d5d1cc9a51a2aacc2986cc0011e10824d34115544ec61bf8cb42a595fce1bd906304e74b86da2acb9430f06237a37363e15133813a04e830e42876e6dac6c60b82a919cba7a35b3f4ecb1244154535a79501e3d3f65bcc4a6ac9b61d1d894f35753230dfd88b832c837452f4e9a01ff45fec4b2c5e478a9001f61da7b9b1a92ce29bd4d5f825bd1028066d2f4575aa5f80613fdeb839c02745ff501740f8b6f531d87bb0802f0aa57e7ed504b0887eab629a95d3955b917e0eaf07e22ffb583da1dfacbb040b4e6aacba4a8900d2368c9ce267e0344ea950a0f2d95780f01a96976d2bd7d4f7a74b39643b92a8b3807db6ad8eccf207d3cb1f3ffe02f956774ba98e9176c0f43b96ebfe16bd0ba14819d50e7274477920520a5538d4ceac0833f0a7cbb20e883967994b0caa1cdad349b73e104dadd322b217f86cbe5dcbed63882463345e4ce36c2d05467c4df350fef86382750db14cc4d62f2ff54ab8d16e3a177a3ccbdd3fe3b53c2dcc6dfbb4d8ac78b518abd9ee56afdee8504abca230357e6c9a56ee3caf566415f846719faebb0cf6b6517f0a53c7bce62bc89cea8cee332e6225740a2bfcd89610724c2d87c0f84f1db76ab2154cc3b892e38d0c40396d2cd0bcc5b62d69941d1beafd53a389bae49b1d5603cdcc0b8a2570c3e7f29c4ea459f1fa6db4c6d37cff65225138f47c4198a5a9211e0b92f2893d81f9b356d0b4297a5e0f25038c31e9c6deaca6865e51eddcd3be7065f14dd37ac4e3f65051b610a5259df2c62096980bdfddb721e5b02d19fc4c8b3249d57163982c8553c805cf712c36a5126e609f7a2e2d62d4d9dcc1eb5bbce2b51b6ca143c98711a26874a45d697e4335f9ef5d43b0a8c75aa9c738b176f8f421bca515ac3497a1023eed645ad6465bcfc9af050a69f8deb7a6c026fbe864ead93484eeb5c58ad930b0d36dd0c324c96ac07f5b40e7d1ca7728c197e50e375fb4fc0a3b1d4b8144978f3076dc43d151cb2eece46cd8366a6679e0798c9b80f4ae0ded9c1911d06db6dfe7aa84cf526549d27cb6983d174e394b83c3a728a30ed074c8b2199c559a4741936980b977c7f55f25052bf971b4081cb61baca129ed721777158c51b7e2eb7e04dc11263557cfc42e4cd936d0e71cbe2066ccd834f8134d5389524b1021d97cc1b39a8ac1404464570ad72cadc6e4bdd76f8e35497558be2ce9d842dab364bd3c713ed5905e68173af36d33456664cd53a26d9c76e7559e1864dac7791ceada78a0fa6e0068ff74787d199117d90290a7bcc18ab49797b2038c080b66b2f049c2cbddd2f9ac5ee5b6f88ec9db61e6367755746d508fe1767491b243294bf2e2a09b9631a9962df5731e8005f64cffad10795b253fd885243aa06fee610d269c3730297fc9fcbab4e0b5757ad208df28620b5c7961448c137c545469d547427232b678eaad519c18459a8c8a852b3eef82e15e8bba904ddcf196465cd17a4a2b3cc5a17473485c1c57b5fadfc4dfd4b347f85ad8a6bdc8765898f8ea594344e8820bd11aa204786b1286ba5e48ce0a6ce1a84ec6f1f3886edec8691f468ab3d13424778ba76684b7ed2670a7003c15e9f8e6e09c70f28c4400f344cfd65890848ec5ed75ead552ba86efbd233618be6d06f556dbd16adb2ee2030024c730ac33feb11c6de03c945cacf8e83851621b8b3639c57f7e6b23411304869ff55d9d27c42c2661f85992139d6f03049b25ecdd289cbe622bdef1797ad0a0fa95592c7389c11745d5e5af68dc489aa8d85d2ca70010b4604a7d010f1450a583b1cdad048011b6468224ae54581e9b7c15cce09645693d2040ccc6dce489172688c79860534eee5a80568b411a1b9a624d722c1a482b2dda04e51a1a45d0c3b12b86247c62d8e363eadbdd8a8e2e53e62dba628dff92502d60f111699ab1d5c948f0d109cbc0b085952f7be9df0b04e0534d78cb682c9428c0c88ad6686dba5ba41db33a77bea14f0201f5a015729d4a79c747097a2fadf97f741d2e345d65e83645919f8c995f9b85f0615b572a61a80f9e88d0d3da104d097f692ce66e79eec531202b84fb11f8bf467737a04db637d2dc6c9874436af5bc0a0e776cff48306692a77ba6ba15f45006a4b35e90d703f1d83d2c0e5da80d6120c04b60d4168d7a8eb97daef83a1ce51ace4fb2635445111e38e189cc844b6c0499b6bd5195a9c485f4f8f674038d06086d058a5875b93ab80c852c40c97d2668d6b5c7884aca211e57a9c1d6781499e2a02915c959da4357046031cf1d4d667667b29de9461a506d2e38937211442c5a789d4317ebda97f569e4d3c38ab9d5c444464e7c5403751f15470e0507c78163b3cdeaf4a125b2cde117258c0bf7351a25fb106ac7cd3adb8d6fa5cef6d5e83bbabab54aec1ea42c7df4f6756c7559b389528a24bfc92be09397f1f2c3b3b2222a8406f58262979d92566010f1a0c4e53c5872688512a80d43b46e3ca1089cbcf9fb61b609886784e3b10c834e4cacb636e61896e763a7e2f256289c588100422715fe844ac07b354c01d9b3ef89eb2e67a3473b28bc7940c06f37cf37e0c56832a80714fe8a3fecf6d19fafbc31818ddabab4b2ff940dd7f117a6b54fadfe78c0bb4da1fb799a497401620467c189142f9d4121c44d7e647a47c032d2fc9a03d4ed6a6c0f3555dba5b7897460cbbfb3c08c119738b069530ac4c1d8488eb567402cf2b0c290306a0f8433b7603dd1a8a4a4ff3edd86313ce02e80885595f699bb5469f668dd73dbcd4797c522f6b46cf901622e5bec33a2345a4c72dca52c34a1a784725cc89afd13080cc7bcbf33ed4b543234c8acb4835971c2d3df9da4bdf4aabb1b2bb6c5f09d1c8368d01a13de2a5e079a7bde6fa20b8d0e38fd5d7d9c60e3200c3f6942725bc3b05278057be44fcc6dbd6b0b8fb4ba0e37fa116fa6de2ac795f4164c8df4e2de005187fd1f41e89441e8f5116e437f809d02e9933527b4a5c7eb825b18952723405421b978abda003f6a18fcc95b5b85789ff1a7a3fe672fe3838b6017d0d53d2f43862a84aba4498a9e6767aa6b0330acd843fe0b7aff2f80e794e0c4e4c6dcbe3cb966eadb4386d59ec41a9f3673b0215c45c68b92c8fb045b589bdab79692698a1b846dfc4865f9e05ab7a1281866fab77fb69bfd918d05baa707648eab1b0c84a257b7c92853910f33b8b182c32800791808be6a8a7883140b95e93afcef40138df1b75b724812589c0096709026ddb4ffdc2fba8a9b223e690617ff777933abf7c59dae3f9065ed053b22aef3fd07384d78dc6d55075f8d3b42fd5589a82104a85a6250816ab87dfaad46ec003c2ac90f50713cfbd7e9e8cba09084a058d25be857195a21e9da093b562b3224a87d66aaac441574461d7d54ab03e549b922f554b03276cc9e9e436c2abc816db10417e0701903cddf4c1201126446bba6eefccba1e16ea65621da04d883fd8eba84907977901d51c17097fc40d1f3449bbde16733578261fb5b30d37ea72812c55c7c2464d1abc70cacc73e930e374d3238d57bd948a5d40f514864bbc2bc3c040a13cf6413a37600a1488e3be7ab77239d251799f115b499ff4a508ab87213e42db1cc19f1a814b0bf05ff82bf07f60571951a7461aa1fa96799d81e191082e145117320f884f9502a22162aec3905234218b2a6439611d89528d3837f38c6c6e6d90a0bdd12a1359c997f0d62a278240a12e626e0631884464a88d18612750b762ab19f98f327d9cb2e3dc7adabb1f70483ec8e8f5ff1667464020f2aa78cc8edd9808aeb1cc730dd5e263aa097de3463d5330261a2a5ccb59f10eb5b11468c4af56668c2723fd3dfff07b104154059d23487c53607608e055c1a9f74c12a2b31992f50c1b458ecaff84ff923f5aaead9732f296d8be34584cb5aeac6febdcb507cf3d6e6383994e04f09368ad5ff4b1b4ac75c7101dd9613a593058f2c82714ae5b042a9015fcd0889dd0e0381a5c2a1abacdf62576a4f6ecd7c25b998b15d272356c1ae8493377a61f8c6210ffeadedc98ce1618eade2c5392ab19e60b2932e68ea7d350e4fe2a59191c8a3a8d9c40d8286e5f350208f6c157e156ecb5fda193a15625521e048494f6bd41262d85a998ae9811894b23c272eef26f03c72cd0458938c098f02e45631de45514777273e6d27fc71685975ee1d76b7c68bbc9c817ab4ee02309907403901c9ab16df03f2c44f530549614271352959ecf5c78e24bde242ae479fa548820e6f6f44488dcaa21c3827ffff88f8e1a16ab042d4c56ba74cc373127f2259e458706db70548f81c13e0078aec3eeb45b02ec123fb0996c1092d748f2f37c61268a5a510fe19b5557865c1586e04f823d922e4521914226b3dc0717b3c18f623c2e9f6d8f6a023936cf0d54472ee4a0543e098f08591b8d969db4f52e2383af4254a6ec5264ca07132e71ce9746cab8933625cbd8b792a81292c25765bf043086208c1d4464d12ebce431589411d7f6a79c49acf7eda43c231ebcfcc965f5ad5e2caec257aaeb87b1266b109084a326500dd549d196ac406e841df09c4bc56f2e430d562942de57d8614c58662cdd19091b201d282c0688d386e559a8cdefa7684af0138a685c96b26e595b24d53008959978d1632691b46e85a561164ff36be840a8d95feda2efdbf0ea16b0c26072ce9c00154b0ecef3beaf4e5707463accd5ac36cefaae6c97fc2e99ff4e299c260c528a83c056106752cda62f1122680a261ab7add1ce3de36dbb50f6c6ead69d01180c36a787fe390c17cb911d34ff10f0999449e2f52abf917584d382622e4fe5c4ceed53a3159d9ce830f0770c7304978cb65f68814b846484f18ad6498368404794975246446b0e4d1568711157db420f04690159b77ed2a63926c40d2ab00f69437eec557d29a10605e19a130b837a6b22e8418aa3be536d3fc74c24b1078df18bf322ded64c415e0e8a2d1080466e6752630f41b0d815d49fe181d6908c2c80228ba0737e3432ace0e169148b3508973b2bb0b3708cbd9e4d022762a34c9b5c4abea7b3f3110a7b069b14f656b9b717afbe7b183941233901cb706a46f2c44aeff418c9c3b3f2063f5e66ec2e3ddc12d5538c73118fed0de3875b2247a3cbe620f802806f2e8c8f972332d80f736a8facf8b647a0e9a444726c4e98d6453fdde0118fc55f81ba5400710e47aeb24b185d4d8c5da13c3b3b82f8cbd2090e1730ae88e1cc78dd65e45bd2e561be61007ba21dec17b8e73bd9b6e535439b91d7d1ac661ee31d3bb1de318d70bcda630fd485c90d3dc42a30b28d235ac5d60bbecb68fca75affe6a7e166eef0aea5b23a029de2765271318f36db3b01633a081fa494691ce5a9b7719d37e0f507630eadfe95ec49014a5ae856f81258beb04d57ae60506f0b8af4a1ee4641af27360d7abfead70d695e44b2c62057d73d4055b0f43eb081ff24581ee684097d230df4247543183cddc01122408a0db5cd0013d3028d80d39a049b8cc5eb0ba9adcfc2a415cf1034d14deb02fef1be007738fab72cca8b114aa8cd5a666b5db306dc2293e441719a99f2d6e6d2fff168c2e110a2ec06cbb58243ec0cf0c059159629fcc2615cf61db6b532fd369bbae1bf5d71a18fc1cd949d46cced7c309d1742e2843c506b9d62a479e29dc6f38a63e5821c08613e473153b8829356f61351d48db9565279562cd4e0741ef09c6abbda08a6436fbd40d27bdc904f8708e86dda088c9fa6d7d9331e500123e32607f1c3e917b73cf24ca571244679662c44efa45f63a369e88a607636ea5cfa45242d7f8e0f8eabfb9aa5dd2d65cd1b793230e426acc44e390f63a21568b3cca4e8bf29a913419ed438b1fec95af71295187fdeaab7bd028c6b28cb618a1fd2ecf555f4ad8215bdacc7e8a767ef6ca577f0c6430985c345579931e3776644742d0866f3224af7c088e0d21cd391d48c8d61c64aa4c1062ba6e271602a6c6c0c80975a6277db84e62cd06f190cbbc8193541dd1029656b948cdc8a96cd7caa1cef068cd80b8d2a2f6e655f88522ce74213a77be10155bea4c25e89672543f3a015bfbe9bc25e9d7b08c4b393fa514c5514f6cf04d2af29ecc549945129845844d517ec11d15c606769d8e66897648cc1058f68038aa3dc334a901ffca029bc8eeec574288e8a3fb8f534dd0907089f70a341a1926de55e4d99b09e893b302a1bc44d847f7adccbc9368e30ec3516f98b15b962869a079864497973c48b14f7b1ae435406ebd0d49f956449f8db3aba1d35101db1763126f5ed5a97b580b9870d00a4e0283a0d23afc07687fb2914d29ca6fe56ed4ce266674fdbf7883442bb632a93eb08dfb44075d6b774d803af6aac448f25f0176a49067e52288eb194964daec87128780ff0793f3bf9c7a943871000ead6c7bfeac7a809ba9dcb87a7fb1c0fa9c015bae4098ecd10b727bda39dfdaf8b94914c56068aae0787c3bb48ccfba851c3319801aa631c32b6f5526a878866143689dd82cf5178a608824690bda1210db6b1ee762e83ab59edbc542e56a82bb2cce272e6a5e8edd4a7c867eae7e9b31abfc3150acd77f3d94d453d1e8f3e44835a0fa90e033303ae4520805cb72754122fe068c704cc6859f17803881f2810b3141dbd7d7993228f91a41a767fb67de2efe557e3e0f4e96ee23a866cf8a0d432381aed8e72096822ebc5242a1be28a40c82e42e52e46b7c3f617f0cc78b96182c5f0fb0d29ae24e7c5aba05c51973c527104ae01c1190bb1a5194e9b31be62962338ca800566f1e3abf841fd57e6e26658c400b28604a2419f7b65d19429a6a194349611de245ea78f435028f87c2aa05697033076e49263f34d12013da24ca4cf8b6c145e4368141fd1198e92ee542c8fb8dc8dd315fb9b866b88eebe7fff9dc463b43ac5456b2ff8da2110e91aaa03cde689b5bd7d9572d6095b2ba2525d572d79387586697dd8b67242a7984fa906a3ba7d3568e34267bd9949b36ec18d3c77d03eb68040eb6b03dac70749c16903402bb27897b445a62e1795c5b733a495ba81eb45ac5a5304c0c4dff93b913956812b86a84e6e3d1580afe74e595581ba9b44e9ce822171e22a2a1bc40a2e4314fbfa3174da49844ae6f7570f534e09305dcb386287e91c80001cd16951c2fedb65a291d5fb107f34410f3f19ef9d196e20990769282308f32318fe2bdf92e26ad7516c27ca3c608d19aea353709c9ed2da9b2971cad5dafb7981213522abbb58cff9a769d10bd28d237c5499e381597e9cd332a119fb59fb6fd2245fecdf287d3d1500891bc88801aeb9ab207b7f48229763d9da7ebe10d4e287fbec812fd3313c07cf2c6c1c75ed7254b637fdb44f6e9ccf7bb9bbf5d2cd16e79fdde1e89473047d7c5ad9cea780f73f837869a677d1c2da1830be36e8180659846e022b1684f6dd44854fd0367a132f49d063924a2b4e190b1c9db476ba6d49938fb7494cde990bfc4598ffffbba572791a0ebb96124e558461bab2889c7b54d165fdd178d647209a2400e51b3f258e9fd038b91bb691dbe3285aa6d0381221416543dc187aeafe8da220b2a03bdf6e68ce417486e623d7d7ca7fa2f1241166bbae28e6a3dd5ff6a6ad5e78ebc822c4b37c5bc58d1d1aa07c0b60a7fa8f45ccaceb6b5876a9ca99e1fb31bbdebf17e112562f707c2ee2742deb769e53500fc3594803c091e51d9606d165f5925514fe53080ea306d4a1e062d18cd1545d1b6c2665a78eb8eaf93cab61a493bab12a7cd1f14c4f6ca4f580edafe2770e101cb87ce952a76d05b1ea3574b7969a1821355d5e7caeb8cc9cac6c7a882fa7fbb164f58b2e4809be8a065d1e4bf0231cd095b716b59d3dfac5dace736dfb667d45ce0b57930550e2642ae05338256e42c69d8fcc4e893b84a7d9e3ea5956079b81a88e53a7f2a2cc8e9852c36d979a1e23541b7363b277f4aaab9b464703bd6ef0c7e7a481a95857392e18b9160a647608ebc4eb12ad57d6a662c462f77d2490b1aeb309b0804bb7228618a48466ab25eb71720154b244c6ba10b0ec60d66552107ee9ae531c9fafe9c2dd017d26e1649e24623c85869e9a43bd0097153ec0127e523cc1b3dd19fc9aca405a38da37ddd47d530c7c7f7b9044f4ff657b8137f6f06aaf38e37342e51384bc418ce1bd39c01453c2468811f2ef320dcb7a928ce8f29ae8fc326122dbef6bd317605783b72be557c6b2dc5017c56db898c9eb958456ff4c79a700a02b09770f03cc195de186b2da4c778113bbfb0c103a110b13fd2ff95eeaf8761d7755a7f31f92f559b5ae6a25a18902c1e5ccc3aae584eebae91eb9cbd7df220cd2fc3bc2c35b2b091a3969cf82fac28af9adc6c4227c952001a00b4496a0b25f90c854e7a65cd0705c000b0c04c42a963a508469d10ed4ccea39f9601b14bdb21c3ee3a880f16f875ec5e412501fb700c26fac82ccb6dafdf300ec654b2d490154840bd49aae2bbf8042f8a92e77290d3a1f9c796e4fc6ccff1860e0d4f9656c7971f2ce656ec7b847fd7dde3b7fb56f56617310aa7704d424c1bb52a4bfcf5cf7513d85c0adea14026fec10421957e56a591c89ea2eeb5bf2ca78579dd220b9fcf4f1a1f4a0be6ca857118926328cf1e00bc0a7a20482e4a7f561dc35f36578bac4c5429c00f39f1939b33ae3404ea0fb5748234ef26cfe886e79dc64b2ce720291971557df67f04324213185e20285bf84562920a1cd7f12dd33711a91c3c3b2af47b1e4931b51dcdb82235c24040f8cc559aa2db8dfd7488bb7577a29b065c264c4434645aa5e2b1e92556b55d37848e1612d4c02c7f364af288da4cecdbafa1d9cb510bf7e2e8f767ab339d225fe60ba122a36851154ac2d85f5bc443659d2bef6111d378ff34b26733b240fc8f3bc730b6ef485fd40ee7e4fe64c80f042ecb23f2a02396f21df0ef76b780919967051dd509bba28c314f90412c4167679f2a0b0becad46bd3ce03369912513b661695fc3447bdbec44984e177a185c04022ebfe8307803f72017e0c6c9dba9daf153aa4b2a067f2f7f9d729e3ad5cd9902c210ceb435d4c778ea563048ab11b26da13b475c40cb8f5dc11be7c6fd1a3f1e70089beaa3a7ad8d1fc3e8378da577aa2a28f3ba37b4652d7f9688f9c004ed0a06654a1366e951f6416c70e5f6430ec7ba47a18e0dd712eca7f1dba3b1e1082d4bb0619e1938c882215161be77a7b96613f78108482ac8995713183d7fcd3602f9da2b43c8e49b48f0c328a527c2a7b21c3cd6f2c2c7e330890de29e821e4112032921f08ade80655c16a259a16be5cbfdfd0dc10873db28e7806226b6a5311171e0c698a334a8e9d38eaa81320aa6ccf24c751852a8b9e6bd4471182bb4714f786ae5e5284ca2858b7f166a2949c4e2316e511737f82f4876daeb3240b6e835c27f64e01b9b4d444e378a1c48f33040f1142a1899eeabb0d6f4fe8f1013d27de7cd07ba2de1fb839b1f7033d4ebcfd40ef897a3eb87162ef07f4ea80ef37b87562cf07f49e78f383de13f57ce0f6c4de1fe839f1f6033d4ed4fbc1ed893d1fd0e3d1dd36d35a456a87e58a5c718b31c35d68cf75eb607a65a7f77c72fadb36e38903d429879282f08f70aaa162851eb1da23cbe39d06914893c82ad0e50c195deae03ece76d1e4eae94e05fa7d29bb97f014773aef78245ca7758a48225e1be81ae13d01e21e59f4b7b308fb2c661ca35eaf18e85680655aabbbcab43f68740b952cf03040d5c1d1378b1689705235f54db84df570a1c7f054ba46fa20c5bd44d5fa38c934fe5688e8abc94e36db2945be12ac44b9f14988f0bebbd37de0265ed745e2ba8319eee39acb9c8ee665940112ff2d91dc5ca77ae690b7aaf397ec9adcbfe699a1d1ee5f68dd2e819b708c7d2e16693ebaf7a932a272aa92cfbc479b12deef491ac8fa04c6c348249205b718abd29f90b3dbe9cd26e407ff5843a42338578a8cdac27611baabd641b3ed5367d3ac32197fee01e89d9a44337bd5b535013dc7989fefb89dcc929d1ce37fbb10091de73ba86162f49cdba584893d123e02b2c62801e914780d98b79baf4c739247848f40d6304a209d025e03f3e6e6ab698ee491f0119035460948a7c06bc0bcdd7c659ad3670afe44de2f72e41cd6bbb09469064f97ff0316302fc3a0f05f2380b7b147a077fdac82dffcd4c96af88fcfde3e696403fc03034d68ecf541a336e01f08e88566bf0f1ab101ff40812614fb7da6b10df80f0c749bc19e7e01f7f11b60bfe58a7f80fdfe04dce783e5c63fc09e7e02eef315e8b02e3142c7ed16a30ce2857d8bd17ba94d5fa95f649c32bf9808976b6bb8340ddd26ab3705a6163594c16ac290bbbf442e8014b960bb9062ee04f038252c9b16aef7d9356e2337972370d5a7770e30a519dd129f8c8de16f4e5ec7b971360bf86da194c69a2e83d0aba664fa74d32763ef9ff92c6d53210707825370e480cb5113eb21d6e7a4fb0b4a1dc75bd1bbec8525c3812a165e7272e54dbf90ab8d0a225aed4fb709a0bdb60c9aaaa8b2e9d2a74d88bd2f736b2ba7745c1e064d0bd00e47b64495d8e7a4bb0b4a21c75ad3bbed0a25c3c13a2da92d7652ecfb9f8e538f8b23586ece7788a405fe31c1ed5e41c869175a2ef62188107007bf5c06e9ee50699ab46926c4eacbe42c95131d178783d2026887910d7aaa0c4aaf9ab234a44d9b887d7fc6b45bdc617b669b420e4b3c72efdb0549d8621362dff8eb38755424c27aeb7c4166d382cb209f936eb5e11e52b2c20d564b523db3a9ab107292f1c86ee7349c6129995879edafe3d0519151ebedf30b896bc17c308c1b4b277f5713722a551a8e651d825f6b19f4612fb1373a292f66b025d21fd14b22b91633e8113588eb765b2e5cf2c809ea6021b2e5f30d4c8f460fe62d032582861019987071e00e448f583075c803b5b0eab01178c1d40fd0bfd1d85d2eb6b0b7103b8c89cfa14b600b41671a859d6a87403ada1b752cf0706504973d1649b3cbb3051bea673f6689b3e0c1209e3627418ac70cbf4a903d383cd16c04691c55138cebc8f89e97b31e419417c6e099434341b786b69cb251e0444a4318619c03cf9797fa9d68ef6afc556383c2e72bb8acb1252d16d4ddd95cbf31a6f800b3e0bdadf26b0b52f2ca020e98a59a2c803b2c6d4c4bb617ec42cf3c002965ae82b9aea3b7fd01aa2e4ee7fe2e0ca20aa4490c167df2a64698ebcf8b0c9031b80ea30f21b6d5815521740b134e2b46dac93583d437db52d068bc01c5943ce0c661585716788f157bf5cd2de57c468c0115779066d32235e13423c14af6339a98cef681b56a5dd1ca8f0c33bb72f563819f321fa0461373b72d69d42d7154e1a0855b54779dfc77037a2774a4dca76a6558014267777a6e0130952daab60610439521aa31cde796ffa3cd35224008e617335af196492fd884a5913bdbeee83d5b71df246c72a38da171d4b021253458582f13fbdf99da9b3ad07a20eca306071b096cb63f8b04dab404ad12b46c874913d12f408372939e5309a62e03647ba1c38d36cf5f8d04ee504003ff33aec9a711d4a0f4e0302018842011cba440f61dc501ad43003370e841c9138277502028b7948633fe17f1477dfd81133ab11c0f6a34e66095dba31388bc7af59a552be9099bfba043bfebdd66e2e3fc02e404ca057f875702b0dbd30f0448f160d4208aa1c25759371b816ba774ddc9f2518249a40372d116cedde6e14ef2e1b515f88419a3499f544980b49a7f3ed74d19309af6e229663e11ddb6d561e273d41052c38571d8cb9a6d7b504c13f99ab9b3c130188d5c3417c069b47f5e1d307fe7b3684f0d50364032d74630d513fa16f71b5fd7e1146f60881fdef2ec0b5eb2e3d73c7f978d5af9ef9f69995b1abcc37ae6e56b96170e167d3628abd7315a73b14b5d55caab1fb925e0b9c33cf4eb02b88a0e56fb88f84baf8e7bee2cfe764343875eabe56ef8a303461b8d1609adc0e2d9f4be9a30be3220b192017a5f1a19d448bda046e11625de3f890e7404b06d37f11461520b04e9e639894914d7a22e726495b9f74cc6dd656a84bd09d327a3996b4c9ae7c7be847a4e0b2f205d809f8d2bcb9b643438d85d2b583f16e332423b0a68116cb8f29cd520e23dcf674998adc94bd0e2c643af80d0e32b66c60647861ad00c819c762ea6da7014d6e15994a7933e05fc4c135488deca6e262f82961b0fd01185af482a2ba11568b010c04dbb8335f11a335f54273699d7884294b75bb79ff050156569a593843e1718a1636e39dacef464f588a7568980cf557c461c869c8c630e2c87be56e0229d1cd22528d3ab1d5fa95cec77dd6639e9ca6293a842dbf834252f7fe449bbc252ad52a95d3ce766abe6e1db0415ed104834c1eeae36320a45b0b819a66770dcb10a041be6699e04df9e3cc42d7ee4e835ac3db4021616491a290c322db319aa3c6d70c1f485b9ba2065ed903045a9f4e993619f3546c9c85bbf86a7d7472173e9ed5afca82aeab0bb69c54d542191b1cff1714b5c80afe18bf0f9e0a3c09cbf39015686478c2b4927ed04f8894ea8267eab6d903cc2167e5cf41aac3d5a41266a5004f441ed10c446af6e5d7c5e060db37819b35413de6612618a9759ea9527af2c6c892a685bcf636cc222a3caf63b964a25262e53f1f95018628ac086700a3434c175181b350aa8ed2849679a24f8630578257e8bd900500b97504617efbb4266605885c0c5603d8dd2aade9eb436a6f4dd51d6a4565f7eb76b932cfa7b11537c0882bd8c781e848be91a613af2116394a74b9b00fea0095445dfcab626dd3e8a90632256f5a29a0a329122bc173b2887bd6fa330dbe82c5148c207152d110b4c5822dc424ca4d2e31b1204ff96907269a64b843f170017e23307d50304e3024a0ad6675930da15a7036900a44ee43238410b58889043b6be4bc3fb78c0823be20f6bcf0ad62eeafb8831d84e5d9468806836b7f76c3006203b0092276e39ca02dd39f0871895485d6eccfb6850f6552cdfb65023bbdb967b4b99524a0139067b066c0644fce6918014f242ee0b39d2cc104d83acd50c7156924ed1f8ab44cee33d9d8aac1869e6a76dccf6fc2e8500be63297d355a7cd27c84e8e3937e993ca22fb27a30d2e0a987fa6f0d634e9d9141081ac69c4c9b3afda22999cf3149831c7b1af411b9878779d6857bc85849faa5077fed792af1243809483fbd10fb421eb9c79c06d93323f1c8101a729d9353f9c348a4f2c79c30263172643e8fabfa8a4df16f31bf40c5a8fc3c42544ed2acbdf74456e7849d9ff40b8f4894c343cfa472d8397e84b9d5a092061be4ef97ab78069be2d766893a0b0b80bf9d74bff017c62a030d45fd106bf62420d5ec8bd8f70373d5be1e9ccbbe2cf2b46a57fc9ab6b558a772c82dd3d639e79cbf33facf1ebb4bab8b72f0393df4982f17e10cd5c350798ccacf1f7622f878342847a197a21c2368908bdbc735ebc22a5a1ca3865c5d9ba141777fd7e6af8b026ec334d175e940bfa07cc8ea0f5e43aebf430f1ee2d36890e30f1915fa906b50a1251297d8184ec2baf0f3f6dcf6710d865db11caec18f3dd728d998d0cfc9ac7d40cc397c836bb8063f735fd8d5098f5e47d5be91271364258a2f84e86582ac441fbd1abe0185aa3d6f5fd821cf49d5be3008ab863eec4e3d81e8b7ba0ed1277acdcbb4772f44c14cf54c24449099164e0d8d10150431b35a419055e685b376122a73f255a3b743bfac134a5e3b34b8f1e541b81ec09122458a1451a44811458a30c2881bc9439a8706e53bcd46ee517c1ced39e6d7c0356ec7ed9147d40d31c2f46cf50efd2c6cd5f4bb383c254bb407d7907fba41fa9d25af59eb92e4094f5637e7a6b71b87995aac23492452a9f4a4d2934a4fca9e64327d347dfc4d853bf95093696b168b557aef1fd387ad4595df3e264e9483e949fd226d221c4c26d3579a1e44673a9a8e35cd89fec47939b827c5393b28180a5bc3c90e033558fad26f2b5cec9712973848d5dddd2f1984d0b0715ea649a9ab74f2bc6e2c2cdb290b346417bbd6e5c4d16e1c28fd9202e4e0bfcbe52d57cba753effc6b1cb439b16b5f5040e149a7e443e97c7a4e2c7e8252da949fe0cd05350e18ab5a39bde9b9d2896b9c525029a814540aa2a5a7255a2a957ebf68fad960e94db3f4994c4ffa4c2697cbe529d2f4fd8360c844386f615cecda18aff2bdc572970cfd895afaf635510ea527b53666712891bef403575637e9495fb83f2ba6d7ebd5da98acca772067329d5053d7e22d158ef372949e55494ffacdd44548626be8b09cf3b86e599d3678e5615d4a98f72804524b5f54ad6c4ce9b9a7bfe2f1a6a88bf4210be9c3520dd945fa50ab5c227dc9db7521919e04a44afaf643a21cb266b9766a4df33091d0af886e48d5ee91c1b94ff76c323892f7c3aca5a7ae72a7b2e4353ec4697e1a84d2836fb415425a5ef4159b852a4f5fbf7840b7bdb531984f20ea41ddccd380b1867aab2b795b7bda497426eb223d6fe184ece2a0d29b7efba5f405a978cd4aa2c32e8f9d6095f48427b6867c4fc96f254d4b4fa36be9c15d0d4a269f0c4150c9d55bfd8222bd8fd82f28d297f285dde3ea9766f1d029f93ca453f25bd687ba88b058ac1c52d5576c28ef701495cbc1f4d1c54e78c7f4f14f610fa7ffc1f50ad2cb6f2bf606e9e5bb10a7e99f5635cb53af8d29bdfcf6e917ca6a56f7aa5166a6c98b2bdcc99395cac7e1aa6675de924d4f7ffad39f3e04eb66f2bcd5a0bf0c41d01eb01e4c3e7e50cee3ca51fa657ea8f9dc4bd4fc4e9f8fe84c3e6ff984ebc3c3bac8cfbc2db468967bcb554c84a69b695610eb26871ee9644b861ca1621d89be0eac8b7caecc6463425f0ddaf783eb8c2c4a40675b1aeeddc72efbcebe37cd6bbb62774d777777777777777777777777777f00e8a6f19494d233d40c964ec6cac9a49252a2a49188db421ae64d4e940377b77c005adcf3e1dff24de3aa6e6a0400317377c7dcb1ccbbd8cd19809f00d8bcd0735cf62d6d444a1980ce63f13c37e2475a35995025493a3054c9a453f227a55dd74cfe0bddd502d6adf3b80aa0e545a21cba8fcf751fbbee450140cd60f11b9c4ec98f1b93b991f5a273bb8f6a6c5783f2515e3b99e1f58ed74f1a94df79cde3758fd7aff6e99f86d240411dc5675c88d3b8d73811cf3dcf6f3ce76b2644508f42e2101a206440a8b20351bc902d3fd3441560513369af5d6e84699a094b661f7a1235cbbeddc8e29065d9472319a71bc3be10c3b058d3d25dabc84d18fefb366c54caf580726d9849c4c6ddd01cb0bc369b8dc96a736f40a8fdbb9b8da9080db721751bb231fbee52c4fc5f555b0e36b51151fbc306d451f56fb33182f2174a9b6ab32efdbcd56c44b6219b4db80da9fd581056d4fe96af75e90fb2e9a2766b3144b5b1315d57fd6088b52e50e866535b675d9bcd663323d4844f3557d0c5e60d6c9eb001509540fd6d6af00059908cd22fdafb17f2f34fed33c658d3ac4033edb3d18fd148201914824b50ff61d6b83135aa7784bce1b4315d3dbac790c8fd93357c43eb1e55674609e43f2428751aec670183fadb2093481d20adfb864aa08dd16abf496763606a47069250a48f040aa5cfe87bd2aeb65c35fae6fcb825d00fc9b15881ca23b68fde11dc47f3abed932c4f7d53eab8a97a734e1e28349440d2a6416923817e4538f018aa9f6a6210ad45302801117e90c40d8e114630b1daef56b558b06c809098a289305ab0bf3842337561e6f6f79736d5ccfda1289486191a34c9d20d36086a706b475fd4bf23503be487d493c13e052a908f7d281d52807df6215961f29339683fe85b086d414518551031f10f14d0f3031f702106133056415ef0852b0b1610e104214598982205090e155e5e984253e212bd789af4dca049cf4c17ae29dd23976998268330fcb32871d040f58f5eb82d7f39e7fcc2f85974ff661894be068cd81d1dfd9d8b9ab625a4516d709807c48f1bca3a76c786771da5fd3dd6b907ac101246cd454e101a686232fa0c3034680d256c820139a93da36e132a8e5071c4173ca70fd54f5150cf075395d99c737eb4412dc36c541682865c8b697ecfd0ac9b3b57bdf000951fc3a2fbfb60aaf2b3279a00759bbc802a57b709153bd82c7647c0c094c0a70b6208c1063d50d23304301811832eb4605344161e7d32ced492aadbc40a1cd4516d51616be65ee6506b3e3bdc8fbe1bdbbfbf8861328bd3b50e31e7fe31a4cd6d14bfed9e442041394a0d61bc90e9cc2246bad811430c30d8810b6210250821301658866b12e32c26a15eb709152ec8c0a05db70915ad4ea3cc3c01b84249174dbce8c20bb040050b9a062da05df31774a68660f4c2365a0218362d4a506021e2adf07afe8a91134954fee59f4df6c915b0d8040cce8bf4655c1023c4051716058c7bbab0826d0917b880d98eab6127810950a08634602815402a2abea8fdb34915722aaa6e132aa850b1ba4da868d2ed845adb4a12e8fe66a1df57e2646a73da6c281d64780bac55e83380e24700d04a060140ab6d69845b0e59f050b1c40891268aac9a8931e000070a4a34618990556f175bdc6089283bc0410b80563dc40e12385f6881c516abfe9094624805eb36e1c94295e248dd2652dc5497ba4d788c50e9c6b00d1d9b2134610746908217abfef941eaee6cc2836477ea7a2a6bbf69d5f4c8fda8185d86eeee26553052bb9de5046dc60a585841a7b7273ad3e897706e132bb06aff77cb04f225b675958ce7c0498dcf4a06888d24404856f1818c5c5358b94a66a920c64a0688b78a1f927e16f8ca3f24fcfb409a6f2091314629638c31c618638c31c618638c31c6d821b94942bac7285dba7b8c1ea3c7e831babbc715349228de8d44088d94929993b02a7fe10fef1aee1a2464ff304bee9fee2cbadba7b3e89ffe01da5d6e777b7b7bd7d7b7777d7d7bd7d7b7777d7dbbdbfb421ff64b820b480881ee87119460d077afbffbddb7170eef8efe9bd69ea679ddb163c7eed9b363f7e46d329723c66fa646a6c1d1ad69214d0b69ee3fe7d653bbfbdd318ffed1c37e7add3d736a9c466ae4fa248410879ea07162517e1cde5cb3ed6e5c9633cca0001bab80ee197a0628b4d0c689b6a5ebb86dd4264ab71029621e06e6f20bc5d762dc6818a52d94d5605bba0e6b13a52e398b5836e57b64d66a00468c5ad8b85d6e4bd7616da2346643315ad860a79a93a99a373d5e97f899d7eb121ff3a2d3e0176972a3f7eeb286b54002d12fb3ebb28e3b20621145c0c06ca4923bde9d218b0f9b62e6ba4ee8b86ab85068a8f19d8606291168dc6165e4615de2afe0d010a672f41188a00020d81b20d7881fdf878dc13e76fe41a9f161564e3076b100354a21704083698318b6d8c77ea91c8631f6bbc3baf0d0010db9fadcfe1ce0a155301dd8c0ded8c135b0c7b08781f91d36302c863fdc2a54cc4467444ddbd78c0d62f237c6b9466611832a5ce0822bb8c0e7671ba9739cf07c98aa3efad0abcec2d5fd49decafc8ce521edbf8fa5cdaf7bbe7ed2a0d722399daa458e34b8d5370bbdc7a35041c3d88316462251257a7e57d22f9e24d3893644fa25f4fcf1a65f7036265de5f72a1442e948f9202b20ab5d8554545e7be754bcb852f1905c605729bf35a7e2f52a7a735dfc559eeb112a9f0c9055cab7f367fe745910da329e78e1d8c82a320c0b25c83936373820f84a829b0802020857c518e3bab014818e6a18310cc3a218b483e1547f375582a0214cbde1818155996053fd1bc4b3413c1bc40344f30e3d1a6c9808c0dc80c0bec4dd79024c94117b199f01c2dd7d6ada0c85329bb84140b4b6e89a9641b3cb551c5783c631269954ee188b4049295f50fed998f88de37b4679c01673bade53f33f64a01b34708d6d7fd645861f502635642139bc0d41395c1f76d260f38e4b0a1a6e4b041df0400f3e61e0386fb97b483269b0b52efcef2ae9721af8864cc2d6e0e761d944e50f1f509b5decda5a34e3ee73ce097a8bbf1e1bd32b9fdb9acfd4bff0a9d755162a506d5d88d09dbd218feed1b7f7e6bad8dce0e41869e19834d8964658a2638c44a675e9d167e2cf347f6351829a9e7b4e7b83a590b1318c8575e9675142c6190a294119635182661f9a72a45815b33685b33110a82dfa86a276d705d58275d329b59fa3b46a029beaef92f491ce9942e890dacb45d86562d98927d4fe1428a81c81f6f9d0200f5b651c1e368679d6e503ab6229982c131b365229cf63d91f1e29ca11fa74405b1aec0f7da606bb99c5aca634a021f3304ffbf08dcc3deef18f799c507e9e9939f67d43d7e51adae36890196490b12d14e24221ce9b59160a65a2904814ca3172a441661ee629cd908a9048da8732d4dd255218c216564024eb0bdf327a6dccb6e6ac5b8cd15b6483e6518fe3ace3178ba061573bc6a401ba882dc6db35690caf2a9cb9b523011c5d0df0321040b480ff7f3dd99c4ce00034d685ebfa10540d4a065b0cfbef9fdef4fb1d0b7c75fa90640fc45b993e24a1afce9b16bbfdcc606dcc2e995dec06466d2e88da34fa654472f92b7ff886fc4cec9f698b51be644f833c0df68f3cf9a44119c514e4931efee44e11a8d750eed4152ce8fc28dc128ae23f7d9095e94fbfc3f4a74f0609bf4ce94d9f0c921da6577999943f3d7f3b549e3efd64e8ab7c322a6ffa202be9d3a0fbdcb893277f656490ec389d3e14e97725737a148947e52fec61015eada756a2cd572b831a3c79bb32f987cd34d8f2c7e506457edbcf15947fc81ad2661aec2d4a83bdfd8476a5795ba9fcb620ff342151a6a0a1f49951a2a1f3933fd3b5314fec2b8ac905cf0f6faf065fae1a3db1a98ee2a78e78261755541f5cc3ed27ca3672ad8b10547e9bcfd6f383051a4a9f9eb8fdfcd4ee7e7e5035a22936ae5c41f793ac06a5912aa140d530aafead9a4f5bd6657e2d2d740a1493cc4aa263634726411a43f4dcef6fccdd501e1be31bf352fb47e0aa8e53fd3cc3a98d4e20c8aa5f8705287dee995340fad173a597490112faa138c8aa44b71ad0af5c121a2ecf10345c9e9a02243b464ffa7e1ea447f1af08c59f0544cfbd0ea26f9dd381f4f580f154ff9852ca186d18f9eed0200f26d0108651fca17470a26f3d85f57732a0f665be640610e5c8be9d97dd475a68dbb62dc4bd0ebde23e19202b69e3c91b1cc9c323b4c40464e2229c2b9df0b84c351bb34beacb55dab70caaae7972474a39bd2da84bee78aafd93ae142d98482612a736b72862316222ce5e875e653a2e179018dbd2084d2d5313a51b6e60d3e0d66e89d32fabf2da9802d496372f89f44bfc7682b288c5082a6f6c6c6c4c2d7f62c7a8ed4283cba6fa7788907077aa8e56eddfe1c40b5c752d305ffb09b84afb50b3c1fe19504d0b57cd8f7fbe68870c028056da4f309ef0a842cecb3601c349cf145d6057daf79eda78407bf4f07e1ebc5f070f1f36748c36bba3036badb5c922d579977954fdbffa4b9ba6dd4eff6d16e5603182fadbf45a51a3eb8040e3879defba8b7637a66bd7f9b061b116e5c8321f366c3d62be0ebd9abf735d6cbace07405cf8e6ec357b3e46f5c7acfe9b63367495369bb9673fcccadf8e067534d8cf2141e7dbb8b1bb0bd817ecfb55fb22f9eb1c54feb0396f63d1adf3fbc135a4eed4a9bb366768ced08c71ceb96d5362384a6cd8c8c6d82a76576a83eeed0d13d7e8a6ae928140cb07332b20d10848acb45f4268b5af79389a01fd5b8c1147831230f10d07c440605dfac315fe685842d7b936b36c7ae6a2ec375e910ebdd23e88ac99ebc22f3d69236f1ae4aec3625b1ae18cd68c26e6a4b4fbcd06b4d96cfc3fa41d8b15a856431bbed9b8aa570990c14a06080356d80319c04a7ed8c9b0144ac861622503248352b3c23e2440380a4758c90f092a262fd0d046863d40fbf6169457159f1262d9176fc1fef51a7a2d2c994425e841d962e948c9927247b25a055333333313c46a650f4eac51a2b1c478a28a61a486f2895743194a1f19e387c2a16b7c1bba461e16c862b15e55d8ba352d92288611605fb277859f8497316eb6ad6fb62fa177156e0b897a34ead148447a1d7a45f2e44dc893384a5a4c443dbfb0261510fcefbaed11c2826e4e4a71abe1bee6d4b42d8236ad9f4e9995020b7a84ac80605278b11251418c556b47c5866d38d53fe94e8a17348c3bac23f27887d99503f6a19db8d3e00578353dc55f466b88c355bbca5691c753fda1191a4616f7c0ef221b780c140e59f6853c431ec63aac7ec1ba3404ddbe909fd4f68dc9c1aab22436d5af45c83a7107a886acc33a3b590a100da394221deb29ed0b7f60529403b77c569db8135ff127ee441e5e2d1bb13bd822fb923db310aa28c7a6070e1e33d38008bac151d2621277e28e6946c58a520d680d5896313386f9fc15cd1b243014dc0cc7cdc4186314a540a121cfb010435ee60761a4553df886c806b598f59a114233d3430f0e78e0f58517cff0cccea604a0051c7181cbf35a269a8d81a9589c6152424318897df619c03efb509e81ec518ea361fa457ed9b74fafb5d32f795a2eecfb69c9966c6133d8fa61ba5fc6648f46ff0cd4e3f39b68e0bfd107b40adfff7f57d9b001821efa03b12b1e5f0c31c4132b20d8124c582141e1e0cfcf957fdf5500c5eae9ccc62cb12aaec9a27862ddfbca63042658152fb12488eaa2619d223464256a5889585137486a4205a12d5d836dbae11a66ae61aee1ea514a4c4a4c4a1d99d4d1a1af50181916328eb63d46d99dad5cc1a82f643859117ebd5efce2d76b4e4abb9530a80c83ca67973504c3a0fbc99c95126486728056b18f157b8501aa26ca817d6af4226e3e4c026a2faf386f579b87dac16dcf8d466b85d7635dfa33ef4783af8c5fddcbb7ec8b1f6f1eaf441e6ac7b67d32f20a41380149fc20476ca30f87a7dae33c1e9e6a7e5541436c0cd8183686a50f7fdb122a1f47bf6a888de1e503856ff48b4fc83ffc5a172734d42acbf4cb4c0cc588239dd9e3cdeb95e1907890d33b0dc5f66a3484fe011bfa100034d0c0ac429f00dea6413645a1a1297bec01daf7f39c73722884d33b5813e60f6ac7e9577ee59359f9d3176415cea0aacd006d2352a5dbc69e45d41edbb6edf95bf150292fe383ac4e1e2ab4712dd50edde5ae4df593e6fce9ed58f990b7e3f4dd27d3fde937199d0c0fb583e5653ccbd73528ed653ccb771e6ac78cef7e46f7a1b467d1baef646822952ec564ea52544a1cc7712aded62ea593f12a5c4ad7c91869cfcc5ef62592e78357567ee4aafd84dacfadaca0749c5656564e2b2b2b2b2ba7959595934a7742a4d4a0c0e1a926d550cb092cdaf5dd51c54122f574128d42e58d4d83363864135e339b5914300c04c15716a61d1314ffffdcc6f4ca1594bf7ef738162214554356fdb5ca11a15bf34b8186a156a83502222dd917a9d3608b8905bb3f1c75513b4a1c2624111ba378286aa802865c424482e4c2664072a904fdbab47ff1ff77bcd08200b784b6b410400032cc50a3bb6368dbb62dc4712d2dd80bb86bb7337b8bf8653082b29ca51b6cf053f99b0d62ef5f3f673ea7cfa9bd0ebdd2e6646d54091b6b438206a3698cc655dbd9b00182ffbd1da055ebad6a8ce683ac42cc06a309311cac26ba47ff5d2742596a088e31c353046d391811e62dc787e0ee2e7f5cb501f1f4429f79d86f5be669abd011f3d9db1c7583d7f85ce317f2a8fe385a0679caa7c11f09e461339e3240f54fbe8ca0b28707a37931c7c7685ab56368ddf4cb62497aaa904a33a7b841ed815243ec4857438c85e960f1058bdab2e70af9aabdb14437f04157d3c9c07f7e7e7e7e7e7e5c4a958fd22ffcb37b07a8364feda72a0fa459a08417ac90c80fbb94df11336d02f51abc10eccfe617e8d5e9574c2ba6959597b1b2f232dee4a176746fface2463258be8389af39f3929edbaff2cfb8d0509053b27a8aff67137f3676368ed97c1da9807702a1e6a878ae9553ec82ac543ed48f9d39f3e99d366f2bac633fdcad735a8f9a65ff99387da21e34f2fe36b9b0f357fe56b9d96eb40cdcf02bd3afde96b235fb3b440c3f9538342774da07ef141021ea3d2ef9f3fae3a7dfff44213e528cd93d72b77b96a4e978bdb7519bdb635fca7cbbb8ed29daeda735d9cc6d5c9f4d4fb61d6d0f6a1bb6683eea793697f4236527b97b0918d9921576dba8ab7ab94ec471efbb0616be8555e875ea9bc888da464afe2853e641f9a2ba8b48db6453dea14d108024000058314000028140a0643a2b15834268e13b5ed14800b7a9e4272529c0984598ec3280a4286286318010000420080cc90cc1007008dbd79295c44d20807a70f079b3639a8f7498a0108bdf681dc2da5a404f3eae43d04dcdee48b93bbc20a63a206ffb6c09587d2328d53ae0c40eb39a1cbe36a2594e5daf5e0e07c9c5b6d45f5b1a74463125703a08c267c1b3b633c47c1be0df41873baa9c92f2cdd9b6a6eae5f6a514569a3d12bbd64678979903b6f37af57386d6095875d6be545288a17287e23b9870b96f35a5937b541968baf57293d91df3e434db18c2afc1294224979277f03c1a3d2dfc06e6817913d66e171a8603377690414c1861bd49f87e16219b6b1e487618bd8ed5a0e6adf1e351ca6ac94b02c0eb8fb44582a61ab044c1fba2ccd758227b227419b72f8045b10a5977e11abac66e3ebd481d1cb103e433974d35c47fdae56e0736314c91ed19b59ba7b500df7b271ff5177094f297efda76a1492aa98d1ea4451a5917d455b9afa8a5ac564f9dccf825dc7a47579ace9ad75702e78d26f0c46cce6292772f9f49d476b022e5b568dd7a7a9769b95d236cd3b77daaac29d1d394e8fd29a1e8e3adbf74a9c7f22c7467cba77a0b7344fc74026104728e118cc1e2829ba051ffa9d14efec8108254a0ab1163586fcde861f0f33f0f061aac9e94d0eaa5b7bb7f2acf1b2dcae9c0031531655b623fcf702cdf05790bae690137ab57b4c08a0944759581e156b685623b7f6de971a4ba01e163960773420e786acd1bde6c6d90db9ce55458beed0b310f291fa03dc5a6846582249852cede00ad7584f74fef8b2e53086765610a186ca488c0304a1c67905f9aae292917d831f637e624c04654a9b3efb00db042eb042c0d6db9cb871a2f50ffdc8bd2519b0656ab43ac044ea104b30b638c6b9e9f158727a4af1c0f416294b3b2d8fa8d091a08091b969b3e52e3ec4ef8d904ecdb8dc7e6c7ead0658765c10024faaf4e562e4fc703d2f59468991160f7090bd083b3c6b789028bb6af74edb71e7905b8f206893e5362bf42c593d0304e5436228bad69c2b44672366101c5670d699f13392eee5178ee6f73b4de6baf5cec168abdcd93e020b4625f97ebc800fa871acbddec4c63d0aa286597222ff1a3bf525c2e85062e8451bee5220a2c67edaf54e44ec62270736aa01e11dce09ed57228b7ba9ae3ddcfb2932feda16ffd6a7511e6ae65a9c82b732625f1903d9d0408db6f81ab47ab62fc4a21e9c05a83ce045dec689d0925698b1630addd54a66a2bdd65bbf4be6a9a4da36d03bd73e50b215934eed2fec08d827c6e8b616fa735b5f91dcd6713aea7d26d189f649df61671f6a83980194401577a11bd70f401b6f343f90d70800413fa99b303c415d65e7c3e24d94cfcc13c62c83159361d6990898242d04d4618d7c8e77d6cbb3363f1099a35caeabacf2e3eb8bdd86d9f788dc6d3d7d4532c2dd57664a71d5339f52555a168d77dcb97e798b74299f48087c61304f47652e425019f4e2f7952c95ce92bab5caee1cd4f3c934d222d13dc0e31d83e9682478e069dedcc2b498f87c3678d2c49ed9b3c4cff22ce549a145b9b0f6d6f34cef27c87ec7f0b7f8f41daf8430f6bffd2265a6ec745b898cf25b438900c5134ab8ecca423481ce8169c93e0e59fc7995cec10d62c5e9017dfd2856d46f06c68181510cbe6f0db2865f453f31fddd3c30a6fa0b6cbaed9070fb7502f4b5462f73f9ecb2ee55da0bd61250e3a8fbec3cb22236e9d35f31066fe0a43d84f9f290cfb7ca41d16b84295c27c06ac4d9518852e79c25e152480d0e316f57c45e686cd51bcf738f8208bb47032705408328155036ec3ba4d8c47911d538b9637eb2ba6e975a062b70320a6b32dcd71e6295293442cbd157ae900eac7b385afb25b65f7e2b14d03a1301ba1907085ff4840ea3d05eebb267af15ab51dd4175a5ab672a06769d148ff7c347fe8c452216ae9f3fca37746ac3888a05bdc96c7f9e7648248202b1551f5cee5e8e98597b55391e76c6a9d784b3c271773695cc3b07143cd401e03bcea1a0b167ad39743e40d64b91a34a71e02203ae4d656d7e2ca63a630b17ceae4a18037280980396f880476c70f301dfc6d9902814586dbffbab0668b6b2c463a30ff044df15808346b7c212c7bb716848922b2e118af3b2589a5b9709e9373e7a9051f411ba524869c60f9a7f6a23e58c8ea4f1d81c98077dd0eae34dd773507d0ac42283a137e9197b33ebbc92e95ac7ddd5b398b5cf2295cd2e7c5dfbfc4e05582f248c9fe5604e0a73a62f1425050b8f1d29f1751e8df56ab733e7abc08eb8feb917dd7344b9ca2efa2a5255597a0ab0d873cc4c711f3a7f7ae3950d01342f4b599022c8ba716525eaceae80170896be6500a1471bb2f72a45bf66f043f46174ae7f7a15301c8f160003a9121cf70c4d43086b4c7cdc35088dd7e4f5dce7d7e5d0cf78e7b163c2ea0e56b81b517c0e3cdf4ae4f50234c2bf50bd81df4ac750eac554b900cfde5d20a04eeb85846babe35cf04ebcd1d844163822a1a86ebc3d61d418c81cad2750d3abd3b3f2ffa92b631387dec8209aa55730ec7537fcb20d5aef9c660762842859a69d54c130a7e273da6a24fa98a518061236b2aa8fbb5dedf29c2e695eb32d0c425f3d0daad8df5adfa1aa39debaf2353af7c5a5ffa214649f9070399b653d32bc83cf0c411c3525c61b5658647cbcc8806d4799eb8a0a44d2b8b7aa348a9cbc7654c8765dd39b467ad3d6933b73a91d055fd543e7840153cbc48d12b6c0e6d20f3cfdb92d6b6938cd37e8b93236b58fba21ceb2401e7a14bbe746ef487cedd70eda53fdfc8c4942293fbc6d7c909fc7547a94641871e39457c08633becfe6c68989a8825e1974ed3dbf89d1ff28f12b66c35f1557bf022e53e60443a7f7dc4d19904bee9cb333af51c1baf526d576869c225e8ad99a1d3b2f3a4192e6c921cff42b4349c00fad742c9e034a2dc073df97d00d3b1cae3099e8eaf2b687b9f3bfdf6c25a91b50eabcaad9cf7e035985919e663d738817aa80fef17a5c20440096812628c61891332051d9255957ae7f1897efc24d41ad9ad9a0b1c041e0b83df7f15a0e1081fd540cc47c1e3ab4ae2c5083ddb9a94411936459ab0a8c6d17dc65f0547b0543ee5d0dc1a5cb5e22200edddc58c805922d4a294c4c2358a5f67538e5523bea40df03798ade3f45adf9a4c9b5bd726d4449c02deb14f43b2b76b89e4f8f0bdd60f10bfa56910441c97d6b3fae598e35d378854b973e9a3682ffd6ba20c459f153a205578b94e55d2c71b3e86544d1c4d0fc1fcf5c7224166cfee13b15951c1583194e5adf0b5d36081c5851702e910691ed8ff12575036fe8bd2092dc266a70674f9d4dc1330e845ece2090a893838b5223084c3aa2289c62e5ea060df545680e20a1c6c0c1b5bb2b78400b9b97178161d55f2ee608215c5c736ee2f4a0c0af94613e9c2ee6597a2e8c6447faf6e3074306945c5fffbdbc1d8ee24afb3b6d1aa60fc2c2e2e8ad40b118fff6c0499cdad178f4e4bfca6253f2430f72958c928f271ef6002a2465f1e487af3bdf8e328f3ba27d06a9d0cb358ab70adc45522e7f851bc9a9985ba5d225dc269ea9c1aa159c0a44bc0564389ddedcd8b8b6db61ce721575076851adff494a7ca997e0aa0a9bf2e627110a7b1f2459c957100d853ab9e0365e2f4dc2beef4fa8c114f1e7b4f735527b21ddb227d53468a1d975100b537d3f7fcb681404244ab093438e80e2e93fb08f1651ccb49c1974d3f98e789669cdfa1cbe755b8e880d88fb024b8b57816432903e8b8b89a22b75cbc17196679a580a4cfb44f6caf1c9eb4cd09f635d24b8d44f3a61acc4fb2267b0c097bcd86d42aa04ff64a3b423a820409d95f3f8494205662cf7b145e0503c49c85867ea6342a9ee2b107efa5a239a3d5ce4b6c0048a108365b198c7418bb6dd8fb0b55c313a74420e4afb5bc76127318ccf2f1b5468f89c5633d6c21fe83fe1e5e73e87d33e7d8ffa5c8a8e3d06f2397b7bda5ae3e77c747efa895d2c4b06cae63bec11676a9a147229b3432a56191693152466fa42c8d1c4423b01d29dfa8d79486305df7021d62407d1e4707f49692a8b2cd4a92bcbe118488b10a2843b234466e68b2be6143faca4c77d8df96e816898e77bb19ea4cc8188e4d78ce7bcfce34de80459f51f7e94b102146be3ff42fea363c144f745f620444335a966832965fc55f7ace21c87d2781512766868efbabeb35807a553274a16403be6c158d4d88863d8d56ad34efb7739c4db9c1d1c91d44f0af8577c465c389b0be07d5527bd5b862032a1c4f48b71bbc3110e50cf7a016b4f90e7dc3c41c66176cb9eba4b8ab86fd98dd1861c6c9baa27a44635e8253bd858304581dddd73a971a9ea5b0a1f064bd121d02bf75b193d5eae33f8bb74c60f049308302827aef75220b76f4719e893093e9ee27bbe432258844aa229119d4f0d066ee9ad6fb57a8f3813bfe6da4d2843c98e01e1910281347883a5144530ba00251d35ead4729e2db262a9e2d9e3555c17562877d7fce54f96c1987cfacc62c4a2572b8cd65ddd5d3bfbd4a90bf2e0e63a46477e254c2821713851d3bc04b513f4607678607be5fa71c667ed831464a6856a06b008c71da338878171b04d5d8d14e10628c0b858b72681cf6278684174ce51c9dc7211d5064e6044c6f0a55d33ba35a1ed2b740223d6e9758b2147f048668e5f01c16b7c9804c164d76cd929a52a4d218cb0bd146ba698950626219accc3b387c3907482c527e34f0b904d45ecb1d40e6ae889dbfcae956faec98243b4b833fc54bb8f822172f5ebcd48278f3d77b6b99ea4c6f1833d9739f2c13b91210bb6f6a0342db6e1ba7122165c17c0904c293eb8a2a1d22bf1a637237fb3c4ee9515b67e27d567f5ddb262c0be63f65242328e5c08eb9082a6dd883ed533825751667f9c8b0d9e1201613970dd55da13d54f13a1208e14b23343b2df3120bf134a14ad04478b767415e4922dc300a3091124c8f388c7fb1e832e8382a0086061c59f3c2d133832abe2b0f0f5c0fa38965387d1b6dfc2e0261c8568aeefd8ca8382f47a9d9abfe54f5843bcc4d9e53e1855d988f822985eec8b00623a2697f2bf4355909091987508055751e8e196c6767a9aac65db2081667aac1bd4065943110144081ba38824a413c877f77f524a62b040908cd87a37145793ce0770af996d401a403145a8fa92085230d02fc10ae7b3ceb2321aa8f1439229ae68e59be34aa91d8d59bd18ed98b3f2bd555e8822308f1d1357298641a13153be6d91773b72427c8d0a6e490d1cfccf135f9e9ab8f08f0c6bcd5b15008c2e5ef1288a979d27c674055322c3680d978653aaf6c6ababd9c15ef53974e1ff7fdab3073c48e40dc0302ff95477307b3ef6585c5bd7fdd33650b00bd07ab7da8bb4f0778da7b65b15fbc4eaecb7c157e11b36fbd82c044ea07528a1fbb9010e04234851b9d5d14eae1a26972ceca39a56e285853b07e68a782f2921342156e6f67f995bdac232200468eca0946a6c9a918b3108ce5b8cb3f0fd90cbc32938adf8d96fefa3a0cb74317b668971eb4696d14a4dccceefcd6230057b696f4ce98c21d090cf6a387acbfc27efef1b2c9a770516dd588610c8558e570551a9bf2b27bb6a4927982dc76ac176d51376a5775d41e932a556d7b7188c8515228f4f8fc6974b0ab7f1eaabb641e3f8fe7c1d123b2fb430cc6c61865f28905086923a7c68d2c387594f5288008a23135f487492d58e437681617df65299efc22abe22f05ab4953e3c3bb86eede16d41eab8e6dc6e3f74a53ea6f50310c24010fad30a9411aad412356d2ffc10748980c9f0dcc0c20a516d3e16e67cf62e51e9c9502c09c61a079cb81928532d4bacc48e461828566ceff1f31cd1af2292affc5c08f6f465a09e416b816f15edfd5cbdaab93c7779e61474091e6e61e5b9d7c6193df28e41210e0107cf1c78eabfde6484c90d296f0b3b4189e6e09a329e5721f5a01b04afa8547fb8339e37ac0fd2d3c2bd1f6807257918621f746a143bad7dab0623abe8f460ca11f081f67bb137457703bda0580327da536e050e14eabea9ad5afdfce6b8fdad4b23979f914ed84b3bc56cba6891859e0a130cffd9d5f0526a636f80e3ddd75d32421d9aef53a296c9170840dc0c358d64664af0f67685d17096e5f4d82611b088e0b712c19d24419f9c16232b97582302f33cac3d8878f26ec59b314b11506a8530555cfec65271e5212a5dc78f704147b320076b652af4e7b2d158ddad1ed8eb9353bea4db24252a2c5cecab0ac147a579add83658407f59c5e4afffe4dc318e4078eb3077b040859f23933c4e0ed5c1c3da9160b01b4e694c892abb0e72155d018808b634af7bad596c650404b16afc11328161003c452a2727f1bbd5b12fe0a197c077daa8523cadbbbf3dd7ec9d3825ab27202b86d36a5916787002b9cae6378a07f15e8d8d8cfd1dcff5ad53488692c0fad9deb4b65c12ba7eb260ed018b827ac5dfab213fe9924399c3ee0005777015605277499e121c9ae767213b5d3c6828b85c43046fa16b40790ac84523920c7258300ab226302e931434f4c1b24881bde5866e9db1388b3bcbc01d054ba7608a1e9463151f06f17a000a4803b5e4ee37715efd5c1e1aeb945a065ff3c417eeacb0a9c9d776ced7c698257d72be762e8f6d48fc5ea54a792497c5bacefe0895c7c529e55999c757ed3e0662f429b482569cf3d9312a4af91667a303c9c02ba694c1c2eef05532192c1177641035b996963c6a3ac5c18fb9a829ced83ea96ea6cca9242b8a918c197423904a77e247cabe28ee73090d84698b44c9910b0a4c9001f19570a0e879126c828fc6272a8e579c15c107b1ad21197ec27b9fc78743a5cc00782600c51ea0d55c496d7f26e29d656686fc99c399dc4667bc4a20edbdd6bb3620a1cbfb866381583a017dac296727702a3c19c9cc416940dda6359a65a4b42c831d00a0434e6204d697563a5387181a240cd57a0ee53f62d9681d1ee57e171ac5128a0b15db9da93f948d3ca6261003b893b0f48698a6d1ec6b58f6f0ac8eb0f66784339e679de9b439c4e901db56bf9cba8141bbc5614ce6cd28b8f87310b32a2cf6c5c21ba680dd54c31da8522b1bce11c18f70437840965a59584dd27c8a09c4f72daab15df0fcdaeac1549eb8f85713c50769d3f5fe388923452568034f884e2fb4aa1b103596bf9f5b550088255ea0bc93bc4c0dac2a7107d929f142b867b6af271ac9283983f42bc589869327cc4b8c421a4ec24ed4589c8a373a118dc0111e15f287702955c63e51b8a22c45ec90a56b94cce85d3e86b142e230ae5e127ff90883ca5c9975782be9a382d0027fc4d16829ad048b51d7a6d78b9e8ce2c96b6f462046053203ae442ce8b07c9d99518621cee33ff8fa15e4df3f795daeb86f5dbaea04466756e77e6b73bb3f5ce39937c8524ae39213796679fb7edb091fcb3fa0a116af752a441e10b4264ea446839b4c06fb07bb6fadc1f12756085db8be440f82d5bd9dd7642f38cde49102ba04f7f469501f3bae4fb080ae7a01a124a83c6da67b98f22ac0e4bc64d58b223e0b9aa182d37e0f6e710b96971853e8603694a2196eb54d5eaa1d15a8aa007f10c62154c510723e9745b82a15d01f61de16239c8b31b3832a911ee402c9eed23209cd519c8d424c2e3ed809ac8c25fbce0f4834e2eea85079e6e5473c91d36b49f810da29a39b1d122f97c31e1c528d790a5719547771ae9b463a5058a50853c025eab00b6161bda8b4381a0f7cf370099c7fd52cbe5a10a986af00e0572a911451dec92479c08b86f922c47bb599524184718dd4eeba6444afa8e217b197a7f685cf269b802520d52c8727423e0783f5a0a81c9411593188367ddf785d29f2f57b67b8523c9ad387a097566105a10ec41b4749b32af7fd9dc1d45ea22af472e057fb342493c652ced039c6c031dc65f68872597407c2384b8dcb2588bebf9af625114d4480a0fed2f7e452d0eb7227fa21d3235d4498c68d035512bb9fe8e931056d404a4646bdbaeb15f5e1b96660e810f8a9a91e255310978b5ab15e1527fc90487db3c848bc092ad5a54b64546f257024f080ce4612668318eb462c9356423398bb28d9a3b446424bcc3e77760146153ceb5cb9048118ec948d5b6284848188a7571e748feddeb66db0a01cabe25b6b8dc19769138a5f19c48ea79d9184c636101d473e1b2df2fef15eb86d31c188e784ab56c0b619ab8c85c0254971080d3f06d3bde250074dd4edce54a8433ef787622eb9d22300db82d5a4bb2f72497e3aab4008f900a194983c8f27c52682065ac2c5d7c09bab646e602b2ed9b7fceb19dbb7ba6403f1faca23c410f0597849869006cac1a6b1406c9b1ef603359c39ad98c11e3254a4bd0d5008f456c369687fa997f5273f1ab01e913f3f56957ba8154b176ad7c6ada6e5c2713bb9492a1dd0bade4e6f808fdf80875a4a36b96358f3014a478a41c52053eeba847a233d46c92b582a212d5410e8db42d6696a10005c411033ca511a20614d9095ce176f59772c8f80273cc0a670234dc24e0e67638ff2e8c731b01e154a9d3c2b6140f141a123bd075f3338484da9f513c267a795966ee6d54f8065d246d45329e89045c64a074a0aa08a4912b5f621885546a5280e04434fa3b3462367efb548fa59529cc2446e122e46cca4ec78241644a813bdda734f0e4ea9303277e1d4cadb0181036f78b6103f9fc3fda09d8d18067ae7ab04f32a7dd29e8be69c508595bfaba747cc002f7da4c97e3e4e0413ace40515262fe261e2eef30d02eab4a177d046b364b7ca4313d3071151463d82c880a45fe19fb642b289b24a33306590da71ad88c32242db01723d3a105b443e4edb9d2b916b36b40225d76ab408d6d51b13aae2fd76f160dd890fd651c57fa03940922a6e306db24c9f90a53190fbef55a2d52b3e92d82f666890928e01f7ea6a25bd83ca28eb84de20ee1e42a015696c6b007bbbef731232f497ef2eb656b3081e4a47e7a0f188c161a7921f18b5582ccb03bc065c1b6139b018a7e874537b6f38ef6d568da98ebe078ad4b6cc327704038ae4c7a8f75d6fa5b625b34a90ba88870e80e3d9904181e185be3160e85dbdf70f4efb3367bfc8ac75bfe01286b77888211e60e55cd17a558394089ea519aefa37c112d793184d3ff1b5280c82f7e7927b80a1091d22d5f32e4e08474125f5c03d812bc3b13199c236f99b5d712afb8e67101cab9bdb4f1e79ce0e5fc7a5df1924c8f238224109eb952c2f52d02530c2caed251bbed8e3070fa541ad06f665f79ba36751411b3b1b05aa165dea7e42dcd23678a2b47b7ca8467b689646e1b57e89a10f795a703eba14113f2931b8d4ee0010075b32af2c54d2de927e8312772b5062ad4140a05d80eba645c71e05af70285bb286c29cdd7227de7319c1069899d4ff36a411dbe18730cb7d0c634f15f82852fd22caa730d87c7ecdca88e205bbbb02d96859604855c5666102ff863c2a103fb827fce12dc9d067360875b92f33e6eee9038da8e2bab4575d8a13f8f0d281313c75b4ef9c9d40d7c71637e8918380b15e44338a0c0bc63793352cee4a0024aba4e4f47542bc2fb8c41cb697f4973b8f90106b124b81e4cdb0469d92502c29846c448ade40217f0cbc22443386c4d9a1d4dc9f7ec5a159cf91cbc2ef1a549fa180befe1ca24977556a989fda59e7362134c8dd6ae038eb5f5500e203c10625d739a938812a36327a9d22507d93b398d0b2b42737e4f3857d0c4f9ebabb0e1187267b2a494050486b0f1d2e81e34ed2ced6a0075c9d003ded39077531b1e9b0d64cb6b8090fea708e1f06e6088c11d5a612051301729103eaf1925782977180bb091c7e7a4c408c3b32e5eec1cb2baf8d535253b82325b7ab5a01bb32c98ac49da11c1bff20189cf33b25563ce106324a755208e3a644057b011585543ccbb2574753f33521d6405218e92e1e7bc07af553617fd260c349eefb60e7b5aeb0073e55a28b89d9987a420ab3ea2d8169d029383e4b9b84c4a31138e98f8b9106bed444e91fb1ad25c592e443b49910856a73a1352b2579ca241d104792822c0331242449d422190746d206c6692436963f05d4a9898b8a6cceae30032ae85b19203fd130409e5dd421fb5be7513e6b80be4c617c729221a295c2bd0d69e1081e15f6024fb6f14ca6c7351a8b90e001d6e353619ac733518fe8a582c80009a64c629e1ddc6c59303168dd68a46c2be6590d382b4f5d3370259ad89935ad24a0840e54d9bd1bdb4e79c622043e8b649f7786f8dc7836223a3b23643e9fa06729a52b7fd9fc2754e9b337dc9b378a5d5d2affbbcabb826f3c628d754163109ba42e594796df5e2a82e53e982310640e8b657e51bf7786e222f07bb29492e083171f64c870944c37794dfda330e757ae57b028228edde2b6487478a3f7ef013fb20035f183706554d37014d38294f05c5e8dc1e1ed3ae0db179a7a20018ecc6e8ae75ddb60ad136f962fe1e298bc47aa4b6b54d6a8c23eb83b68df86d5cf39590ea8c5339844ac4952110417feb01cc2b5d9129da611438dbd44634d09f253955f406380107754dce97bf25b89a44a275ee9760dcd26c20cfeb1e92d9d229662270fda6dac324f3208e22c306f4ad67dea0a900b27eb31381b8c560f037e0a81647a83bff06270c639adce20694ea82ac7d45509814e984fb839ee81fe7ddb56c46802b236d67e385f943b645c8c59b8f60bff2f0550722822bd23eb64eddcfc9c4b2e5c2a00a09865bb6f1d52e92c2f0308c2d85a7916ae116a4d391267b60aed475c8c9b08dd8ce2acc795089d16f9230892708d5bdd57ca82fc0988b4e96835a5a4cf9c139d52f3c14fbbd27e95a61b8cc6096346c4c70f47659ed0048c8d6ecb176907e58da0296b1a2f46852709d1bc60c95f961ca35840d40b444f616d6c73b8ead334022b7b840477fae5a34b89dcca91a85f157cde11a92e7019dd5efda4f6d313fbcbebcbac122041ea4b905c7ccd79b7983b3ecd4b3681d822af1f61c197274e15bb7905aad26b5d116d71a7c84534d597eeeb4a80e48c5f36da3e6e60eee41e0f3121437946eb3740c829228c73bef5402fabe752b78b0297bc6f2172554f2ebb1a24065e1e9cbae1c87baf4d13bc7f6983d084ec0a2e6bfaf1ae727fdfb4786b99e8e0190b265232471f20f255eb36ae585947278c51b3c71ab41f05975ca5783145be5812523eed1badff8a5c7256fe78c7681ed513a664b9d95755d919270e19f446aa48a385b4891a02a5158b63dcbae363e777437fc601c4134b3850e4028155130100589bd713f3a0cc066616d9b3da6dd0be59fcd84606c8784af7e60589ee72d333dccd33632317f8ed5ea4fc5bfcd0ff5d90055f0f95bfca1364795b25daaaa61cc73f98bebbb964003989c07b7afcd34122ddc68bc774ed4d1ea754168e74d513a1c47861a1b161e58d510fc69d9a1573112cd32a892f36a1c9d326543b1e79fff716121b0f808afe5f67569a887e08e66db4eb6aae83f3460d33e923639ea851927acb5efd0d82003e2de16a0b0af39c8114fb56541d7f1bdae1281e4ba75e264c3d7a6f45fd20ff2b533872e07716bdbb8752f9e702802724482adc9cbcdaa7664712bf0b8215a91b746b96f49d05023962062be70c3311fe2e46bd3398d53e3fbefeeaac982428fd14b38a050701adde4c84993fdac587608439610980f155dca8fcd3a801eb0f82e1acb100966da9167ec838192bcfd8b4eed255031688d46a3a7e6abdd6335bd6e81c05b95f725dd4202762c2392feb6ce4fdb233ef830526de8d619240c8aab303d233526c1e600362d4bb38308049c9dae442dfa07ad0880d3871b49261db02cafa307306f2b0341d3f49ed415b852087b0bde7dabc932d918b8d066d18077223fdac611aa1773e5d93e9fd2ea6a74879920412636c799c2e29b08003401453969e775245159a86c61dcb53ad908fcd31192259e315d4d8c3befc7f966eea693882425cb34304c6a78ca6a558465746c2feb21450675b526366de01dc9f51813c5b9dddd8f23f68cac8ba48b5271b00a97480efc69f5ecf1f2e59e538ebe88adec37a14627baf4ca83fcbde14f47c55b8dfa7074700df122e85bc80f59a2cfeb822831c4824a0089f3e0aa0102553b0d991a3adcf6c3e3689dd1a2698c9f13646b8de7b872fe19d3d730d6de7556d2f14afcedebde5d565af71d6fd7f7fce844bcc8c616c51e0c54092bd3b36091734aa3d9e87a5dfeca7d48533c22af84a0512ed030328cf73b781d5af37b726f6aa1c9cdf23e785aba7012f08c650c7e164536553829c25ab274cfbdf382b03efe602315ff7e0b5ae3bca2d1f59ff7e683cc5f5572d747ad17b2671dcb889c83b10206409b4a8735fab69a19c0958632a7fcea834add733c404008652ef0df185aaf4ea2c6c13d32be1a02538a21256e9c033858d37b7d70ed125d9ef108b82f902c803c6840aedbc243423b1717f20aa9eb266d1181ff7bafe7d650bd499eba0efcdf45a7140987b126ce86d8969b1e08454edcce7ff4a0d880b3a9074a862808ac49e7c4d3264c8a202ead6b02f49b330f3ff39f5b720ccb194eedeaf44d6adbfaf200fe69e837c56c1f4f6d43e80f4146858140ecd348ec7c2f1cbf317553b1568af5551790f570a2d23b5425d1e65cb287cdc3ae1a3920e5995c2f97416ab18a686baaa641840c981919c25c14c895f128c4859d00616f38af19623229b27fb189f716c2b30b80d97da1ab39c2ed7bc948f7a0631f2a9f398a5dcab097b93e424d6f2a158a2c3b761fb6004ecf8b19f378cd6162c1ce8a084aef3edcc41b76d8985c890844f67cc3516102568432974a0dc8e5bebf83d5515ff3189b1beff30eafcee2fe2f6dd77c7ea2899531b3558de4bf7a9919027f97a667fa4232fc0b042e785dd90ed7d1c66cea06662b3e542a8b91a82111149092f4b54a4cb4a63e3e93719478cd95572e87798a26f03c4cf9a25662f7363571293a674a069b3a9166119ea648628a251050f8fb92f4c8db20e3b46d98f35ed009890b5b43c48d5661567519800069fb1c5676141bb9e27048434763c3ea85f8ea3b23bae259a6079c29c53358c325c536266406823e759e954451767f192c35ee83764348a2f9a4abf0f8d4e267dd0608fcec3aa300788917752151de86cdca5ed07ec58055568d4b4982af15b19251b61802a34644906051a23bd2e5654859cc8f01f30db5b7ef73c42fe508767c6ed11fb90684f0f488092ba488ec89de70bd803e68638a0be51ea258556e15793aca714536bbc228a995f10b4d015cc277b383bc9cf63550f8d3c2686f0b6603eaaacccfa7dc391fa5f2223647d3129ea680c777e58cab467ff0eeefedf87653a8fe6e6c50d1bea2e0ce9b263db84360d6e13c23873e8300b6d8183abd8de8643d75535702db1755559d18998ae2a4b0afa05727c0edfe0c1b8d6b0cdccdd7846c2d6c02d6176c4b9ebf47edac0e5770e47f36a002caa700b39d14c7046bf7d52bb04daaa9c01ba337fdd5b91dc30d3923318511ad85aea305a37a68b0a6bda1565fe806f28ac51237848f652d54140cd35aa73ba7fd0558d3a6c2f6f8ce6df39583285113030e49478fbe314e626e4eb846fe4d1c828f7632988a75ab24e61a1d0b0dcd5897013161fdfee5fcc9857f1a765d523b4ff9d81790741554b823e51850143e2ed4fb820cc369f608c2b5a85797c6f262f8b9a93f28fbc7f1b0003f24fd7c4c6e59f9cc73c25fa237fc8fcfae5286d6f88b5e971a4d9b9a8c2e5bb4187c2c3648dfaed80c41ca10e4e7cfb6045b9902feef265a50658a3209c2897aab83bc7660d324d118d84b59d7c1831c1a70d55ac786a960387b00405a0c5eaff740d8dc4ba4452ada8afc4e8dd4d046d4310d90cbf3c8c89958d74c318ead209fbd7d6522feb7937a2fa3fb285fd8cb7edfee9e0f3ef40f8521e97b264a5a153b30baa4f735b15054115b632f44217b9e20f38d330b330e304fb362d5957b626f159cbd2135341182dabb7da64a6f30d204c08b75d869946293592610f5415ba220c39fac409fb1e04f14d9dc489cd806ef219c406e2f15ad4a3e07aff1160fd37276396dcf46675437a4d0fef55450933d442f7aac62d935eaf17bb3537ab9b6961f2f1fed60aa092bda76e1467ceb53e6c533e6179d4b0b460e8021e103737ea139380c5473209204e03535f783a70df106f0d80c863bb562d8174645a96fdd1882b4d401637abf35c856aef127da5744b85ce361a0c2fff1f3de07a445e38feeadf624ecbd6b04873206c9a83728f7b236ffdd98b2b30d0d4a84430b14468dfc352fb7f9076304425a5fb150092b71e731d9c8ba7e0867fc92197d2ba92b894c5a7a04bc45abc594892b529d0a2ff4873e50a70df8d0493ae4097baa98aa9907bd2d7584ba73f111f35c74cea38b16d4a6adcd3d54b8907b414ad1cc6129013086999f4b4a0852e99b26b761ef749543a916946ddb8d2090a122db989266b995cd443a80f673749367d1b003d59fa9655fbcbf4d46c8328939715c9565a9dc6adc9a59faed0faac801ce14a680aed2ce0e50cecc2f0106193964f8e8508063cb1fc11c108eaca7f1f08f8d5774686b37af84a0c96a94cc6c4024059f895ee2d8f6cc65c6c115d0274d8ca11cad8c496e4d5f8ae3d5cb3ddd6f0ece8cd76d728621a1fea6ea93b65108424f6b066a534a06008377aea4ccd8f39fcc652509f9f245b2231c0786b0faef6e1dc1e9cb9dde957b4491436931cf8f771c60715a5008cd89354d5079f676ee267d8ff4848e159bc5b14e93fea3a777c0677f11fdeb98a0de8e4e158d591ce1df8d55e2b360db2fec82d74ba2d07bdc67ee80646c93ed3df8a90148b34b7bfc551e8199d62f72553762020ad4598ade47f6c43ea1ba6d9297bf26b0ddeefb05287b96c1aade29daa9c2abe85950fbbff9621b8ea8882f266d431f9ee46c84b778e31e0d1b9de67c7ecb798c8c63931864c63cd6d46ab1b24e69d7240d9ff5b0953b72385b7234f8879cddaa6ad6e4768177942cdd9bfa59100d1267605bee2507d3162618f1cb0d949eb35534af752551af1fab1cdbc9f46d7f3a8f025e0e498a0a5545282c413fe4fabccbf42cdc776a75626c7d02a9245587a881e67fde3c35e79eeff5baf871d0dd0d8cb47faa11b72905e94ea61db4c5057ff4efd323774d857306944589e9dcc2ffac7a170592f79a928283ddacefb901c54d7517a537c22a9a7d9ce2bc1fa25ee38f4fd8cb38788440d010c55d030a3a8ad3d12f56bf7d14ca3a81ac1103e085090d74d80b3a94172ef063045a31c9e58ba6781a0a18b8e18dbf9386b82b79fccd321325ae7234efb8912754c9655ae12e05696c5293611f98a3a566adf28c9d873047946ce5902b58d88fc21f89e36d39eb51008a8690739a1b9159bbee6509fcaeced52c4d39c598a5ee1c4afeef9e8a64b4d57f0cfed1f488d2746c00b82fd3abd5389cdc70614932e9386eafdfa290a2fe1585d02bd8b5e0bf432fe8777aa8b5b596487f531cec4ed300a127bf5e4f4c246bf754d506a6b07ecef3491f34775acca3b51786ab6728fa27b7ff75a1eb680092196e443ebe63c828fc028d23e0f59e8322f599d1d1124797ce805f8eac3e99a484c01cc0e368a8b2869a8941f04b2f31c03fa12bc66a9613505f2a7dcecf704e6b41bffe3c77ed400db196370197274214aa2cbbe2f3d4f3482f711a00566fa866c5aaf3288f56614e919936985b8fc031cc7fb70f828bf1f438000a234089b679a34d1a8ae4390ab6d51f20c7173bf68b6086ebd9806d60c7f3978ddcf35f74a4c1ce3ffd09e1c3cf5afc4062037e0a004faa2ce980174e5d5f07d613d23a281545fd109a4f124aa610e0300184dbbb23ccd65e50759e102a3a302085c9fbb1de86aa05fdf6566821a70472b82bca51ec407b659993dc2fdd6846b22507426ffae754b0816b8041d80b66eb30c33086610fbef52012452092d82e066e045402877c84cc273438b050083d3c6f2013d0fb027cfc5e2cbf94a93263d91aa54dafe332e1a81be00683f9d87944f6e0bfeef9872fd53e348cab8d1a035495a7a790e03e5153c35ae4393273017d046786f6d0c3c001b77eed0a48083ebb70624cdc43dfc56f16e1ddd9dab7ff8e36e76763c915dd02d3821e7cb91d7a964e3eb9991da248a7028647b62224e9a37102c7130cfb6eb036f49395a36e433e0bfd13db74e313e66e00bc9eef1efa275c05394d116f94c3a478f22e00bf8f70de68cc0b4ae36545a0352071a2896a844beb18bc92049144ee45872561409e4bed21d3c8c0ed903f2d98c93b994262588559aae9562d6babe1bc38c0aebf90b310930becccb441b73603c3776d040437d3aed8a13b926abda34618e8d05be0aacecf47d17f6637fd2677e3c23766c041f420611dc84e4ee70423ab5fffa6474469282adf1b052cf2bd506526b899084f96b947f8de1ac462530602b9a449796af8f45a00ebaa24ccf0f7e612057fd7bcd02035b211e73b22abc8c19a282139a39b7ca88e3867e99362cdf5184ce8d1933078e6b6c135fa2cd6216a28f02104d5139420b8bfc51b0b81785ceeb9961479d211b7ed342ff61fb70f8bca97f70633e319c97c9c160b8d5c0a2197fdeaf235d747557d034a7c043c154c4a5f3494f006b6f15257b869f6088e53f10d13e4162b4411c438d902ec4e8bf1c799d674e70e3c0c08ca4c83efcac73b6a14a2ac1938d8b405a64dee736aee784d4aafc4ace6b316dc90b3978436c0ad0ebf45f0707fc64e3547360617ee57047731f43cd158a793c8e95f39c6ef8e87862c3c5f2aabec646b35da7b71d110bcbb035baf4dea48d3c9b76a76c9dae32bace33b3216bc6c4234245cffe0f291e99fe543c028b7ab6e9b61c253bd156cf0e32e68597bb67f348e259c9f381e269e6b8721c5ac80aeede15c69366b3f1272ce135c3de44c0ffcc473dcab46712d00e30df117da60431509090babec5cb9dc427d80d91204146596d5ab335dd5aa9926f0621769ac5703e31b56c3f533edec6f5ef3b3bac4639a1097a0ab8d678d89b4a008f99dc60d8bbfd76491ff1e5fe12f883c5d44dd25320bb14cc31bac5ceb7b9e2a5b2b0b7063ac2dccf09ee943e6b0a7e1d34d0a938d19a0e8f94059387bec610dcd86798c13973e077ad3e7e74845446064d8b68bcac8d03ea9fa698d005de53e904cf314d09fe6bf21913afde2025e40e07fd115d55a60e70b808e87d8175999f638e48617435008e405f75b1038fe74ead308a7f118e49df1dc300ba17bc259e54012988b4ed57a6641a7b34c780dad005484ca64eec01977870679791fe8669b0566111817d5c8b7bd41602f663e1102424a1a7e23a695ffd511d4e1e28957bc043a15c1b5c459bb200eb30fff750b00ffe7ca10051e87f9973c14cd31944810498fcfd053cf40e214cbd8dfc80d666d80780282abcebd95e42b3ba4eb172d89704b50d3734dec1f33a180ee55fd39fc59198b34b7f9950701d817990e5bc9f4b123a4ba6aca38a80e6ee751ad7eb2c557019a0c9046836e2c530836fe67d7285059d8ced981f2b9738fd18c54ac7b2aab320cdea0bc1ac8fa5f8c7a6cf6eadc6006c8dd3c20b9419691180e3fbb603762eb2c092dcdbacd89b08400dc0c9e6674141e2010dcf1025015deaf069ed980a60c5ad2ce2d275eb5bce8010978d0a17725b44b88b4690e74071e351a6036d9f07a9cfb00df63ffd69e7fad94f142459a3b543c5ce3a3bc93947ca1e25b886a00147ea8649092135f56997fe50f552afb36a838a8149f04a60752445e8349a5556d279363d3336a3fe2f33d59bed21b8ee6f3ce051f77ff60541f330ec95310ca0512d005d8533aa0e3546a9708147ccbe239896d3f018da74e5869d0a23ba004f44da01491ca35146750f3a22da433dfd693b638eab5a3cb89f5e3be33738c40d4913812a8446198876dcb2d354696f82d55af6bd440d7555906f28af7560f3fa060dffa63f95b0aecbb3cd2098d95856c1aa74c62dad124df7e02fb34cad6b5e13734c9aae60dba897b9570ceb1b1109a79d4fc9ecf24ee2e1d61af686e5f1cfd2717396360f27ccad49c86a7d849863c61e16c2c517c7c1ad32a8dd9da5b3424ec50c067a08e57a320d931229452df5f75840d703925e84187a7a5704f01351aca4557d4aa1a6a760674f8db177fe215ac116cd19c7202d1f49ca10f39a78bafd656682b1d5491f626ee80960a4357e21bdb5826e9b0c25d47fa6b5de30021744921323c1173bd4b1c6fcdc33f614922ce28bb3b95c9f49c85b606888866ffe5f1ab4183519e9315375fccc12e5a318724c6422538928da33e518ef6298654594ba1797cf1ceab7846206180f87b3cf4143f5fe009d68e0e05084830f3e8bb8514c6842a51855dd3da2c21d1de57a4a4919a113fa0b28eb108fa4232abb16301e84aa976127ad02c29d1f2430879807530b3d521444885a328b2215802b700a14e9698fb12fd20d4a61cbb35e01c3f1164605be8e2ae3c68af08fb5166fec9f73c16a0961f4b80f1e8a309f3a29aebd8f244ab3de993cc9b62e114eebbaf78a41bf549a37e6e4d0df01b9efd79507d826f06d20913617f090cdb3e621cd10f252bfb9b25c0cf6906d79af52308fa386be9c6712795255ff9d43e0b793706d3323dd3c0e972041abe19f1452822acd69a3eb08e8c4435d16935049d49c439f2f5d2f9ca5d8740b44a3f7980d0342ca8f2f751580c1a686045cb2dcea61a08e7e4c49064a075ef3cb185b62d457fc155c350cb11b86e16186252062be194817cb65a8f2c493484619b9e508fbcfba67b1bc5ebd54713e4a62187ab7c342498f9c4fcdf9247964cda652aa9d38ae973a7fbdd54ba4b4e24a011d019950749cf51ed0c2a68d82b7adf99227be0ecc07312c7b6806f5283509a15ef6244939381a9ada211818faeaa52d49ea5b849d8e9bb70a9b4218a4981b73e4630f24d7fd2d1b1cfa1b2b21f140b0cc8680ddd93aa20ec82f307cd66f6a2df5a894c4cf9c0d29e11f431a6b176c2cf56fdd4cdcab1ec0f0ad4a003743070ebdd10d18f7e12ff80b3de288a82ad20168531596fcf0edac29e8464d435c83dcdd9a86d42b6442536ea198aa180001973d1e88973aebd7f70dc112cf5d489e6a1f7f920b507d6ad620add2b28f5308d6abab362b815ac4c44ae36f215efbd81dfd5c90e3f95939e856e5e4fc29393a52dc19a9b92bd606fb9ab98a3af016ce1c7d2bb15d582a070382ca014d344520d7768e6033bcb52725f55c9c214eb4bf4ce7b8f6047702f5d25e2d8386cf811dcd82a3ad099f1f6bf90fc674f4acdc3dae6a1c5ec41824d9872bb3560019a4368588471e24fccde8de7cf92643758aee3be0b0191f95107f50f435c605e858768220a3651b226a830bef62e96cb571d2233d68abeb921e883472b472bb0e2d9a096142b0b6705bf4e08edef2f86f382ddccaeeb7dff6cf05f88e1a4c9d9868eb44b328bd89e1f2979ce5534334cf26686f27b75cb43b4718f83962d28f74ff352e5a54ce2ea1b136445c865afc010bff7763a69c30204c4bd942b377c90a36aaeb196466866443b26ec18a17aeb28b0e5ec185fe434774d87054e03f1e761054256dcb1c25ecdba1991172c00c856393d8197ea85c914872c4c92aac7c70216b9725b4cbefb2e860621b7b1ee05073c01cf368249c70596519263fd7eaec94d7186f2f6293b4c2cc700e275048f1eba75c9c1180277f4fef5b28be1ca5f494e9863d95c745331ff29f45ab1e5aefeb5b855c69327b8e51bb9ee338f0c43b10d46dc70eb43fe475615abb6a9bf71716569d885953e1764057da83b8ee5a2c2aeb88ebfdd5894231bda0449405685f75bf1189ee4614705ebacccca0e730700b9e902f1aca5dd640950efe46b26807712f2f6d9f80b09af439226cc45a42556fd31fb93266aa75b4d4ac14a78aea37b5c1897f6bdf9c27b4426ccc40a24a5181066520de767345815ac4d303fce455fb2147456a205e75cc1d36cb6840b802046c941fbb0b1adf6808059be7d1e74bfdd2755973b30b583b5d8e50d58374512fbda54bfd36a01016254ba0f53cb409d5097aa43b00a2fa7eb6c74fbcf0ef087e5f8f9fa4f82bafd7b8f10b522f73677f0a7ac6400fe59f93ce225328096846a6ec3690e26853bf9633946d45671af5671078770214c97a48cbc36fa2d3b71514ea8d51cb94bd191e729b86843e85cf697bd5dc5202b3f71f47e51f1b11cc3a6efda04f25515559502b30b774e8e556fb224110d56e2617373f4a5d59419ca01deef382b14d93e430148bed7d5b279dfee741f2003c13bb3017bc5a42ff11dafe373846e1773cbc1a90907268b406b59b110038088351df85dd4c95f3ad811e13b859d6f7f67f72ce92d528c55e0d185f5377381b3ca377662aa3cdeb9266678a408c2a389ee599358916e64db51919d029c511a49acc8178edc77a235e70f75b7e4b9a3f14276459905927f8f4a2d1870339fa05a77c809368a021c98a32307682d534a193b4795baebeb70eefb4080e8f3a0da3f0ce6b4e35936fb0d28a42bbbecdb0e91e69ad2bcf016678154d4e0de6dd148fdceeb65c08aea91b8a2f48effd42583d0f28b6640bb718bd966c9acb48f18a5c606e2fc996c8c724778970041c944439dc41819031f8e7cb370b7e092a2688546acd931b960f7c4ec1c5bc7a9ac3f57fc8dd206b7c327675581c22fcf1a401dc5e5aefcee0a73fea0a26c948b43bab8c6f34b64e2ad19ed21542ec7cd623f8e0a15347cecf8765cccae8fc3d24b6bb08d1e4303fff088757e8c0014cdac2551711f782ef3ac212687fbcca04fc1e54be333b35cd7cf8a44553cdf89b7d8ec20f6b9223e677f856686a0fb1f9ece254d84646d7b8e41cd72e19ee1febd4dc0a00bc94481f7b71e6beb7812c8e4d7a03f6608c0e8f80257be1ccb6e39fd29a244e58f0772de6c4560b11bbf461b11bab052d94d7928baeb72d6547a5a06a066f10a56569604a92d05d960e55ad516caa6be3fa4e0d9a79b10ebd93033d07e7dd9e47820a3d37b2ed18c65e184f1e7fe1e1f3df128b3089831eb0ffd6712351373058e1b5d58314954360fea421787eca9964af410978d766a8d80126c5601327df2392e54cb6f4b9bcbf0f58d97f3eee887aa827bf06e6d7032cf510fcd4ee15d49f6c76a1cd053bd0a5dcf367d7a820cbd60b149bdd9b9b344b1163000720255b4629091a315db89a3c185b7418c4b38f961678c42237f85143b4432041d4ce9543b4e25dd0ba29f3682d6af1146b67350b9dea91546cf5e13f5d8774a2896979800d63f87751825448cbd02aacace2f5b80d159a662310dfb40f44a8452f5792894d96f5932855138d4b859beb39d0030d6f2904bc3c78061ecc23074e1f435061f9b2c2461a9c43cd1daddc44ae9ec229d2e786bf617ed94f5a85c6ab334a483d77e53246baa6992ea191d14de01008058f5fda48bcc0590cb23bfeff7756074d9acfa4462960d45fc2221886d07f6e64ae6d241d7ec952d845dd730a750074c1d1895228c1a50d8e4c64e9cdb20c17cb9e66c84589b3a6bb31871c2a48b6b7d0e7030479cf8693a1e0dd437c4f03a576dd24b6b252e797cab6499c42fc30562b9de717c2ded316af4da83591e1a9336080f8a9fd004ff3e2a4cdfd629515220610ff85efa1311871a24b38385fd80e58cf6244b225fb75634d1bbf47e08019d56d5fbe8e6447ca87b887be8a83dae6ab9da89fb09d81254f11441711dbc9e6749f370fa5d22d8538fc0b74751a2409189e9fd9464a7860ab02e618ebb15522f144f188559fb714f92f2cc130c79d52b42e6fb590f725e8fb8f74835d7b25529e56d2016cfd4a7735628a5a3a8ed75d9ea29e3dcbc927e0edae320f4c8cd0ead703839fec9bf3c0f6ce44ffabff094811a3c9c09734b57a2dee9a0b558f05b53817003b53df8ed626a5cab9ec6312345acb348420a343d7219226e6c6cedbc3e22af3ffb91a3788f5ce1f723c29bbeea910a17680d34db3e0af7183d19052ecbb39808e517182ae48ca8d819539dadfa8163440054047461129819a6f28239c914913f8de5c1e5d887846e4ebe1c7a54008e4ef029729169e07e1f8938ce59d77530a61661eb06d9c2b4410cf530dfd5b8c6e635d06d0b90cf537f0e3322720cf0e7212d62f797031ab9ce9621a57618fc964bdfe1a4212d8267a12852fbebe977be81e5253f1fca63095c7ec9f0b54e98f6f89f68b1751ef69cb2939500b5a553c9b3b64f25ad0f4bad68811e84d9ed4bef6257e2eb611af635e62a1aad81e6e3a7bda5a28c1c60c8575f20e5cac0514444c5b6376bae59c5cc05d2c64a034b432a7e999ddfb39b87935bb8ad76bc2293ec04f111277b7c150abb1bd3bcd4fd1fd6079d16feeef889596cd4280f756959f9ed725a031ffb47e501833789080a6f0028e2e183e1319b6923b25a3ea785f8effddc19d3d0e41478f17e9f514da363caa92c00a5dd60f77dcf2583059717389c375bd457549194f6956c9146f6c06aa82bbc51bb8303c8ad9582eab2f315ec9c48de763820393f6ec534de2543ef99922aefb00055851b1f90414588a61ad23ab01cb3a6163bf126fac232e122a2906f3ee97cf24f8af0a11d9e7016c02c74d4e0dbdccc1c4f8162a103b116f62bfeecf376ad519bef05fd95ead94ee07ae826735c04a4ed00db45d364919fa21f28a3e869fd8ab3700b4373045f865840705154d33b7258b9c404f5dacd1efd4c7fbc5047b9355f612d01407bc4dca3bd5505300a8911a9019501dc848f8583f12166af0bd989652c267917ff314dbc7b51f78e69098781ce007196c80d0751c43d14ff3055fa0dffab875a7334c383a1bfc4674f7c1f2269b51525f03e293fc3eb75e1c4bda93fe15cd22edcd76c34b8c8903dbeeb5a145ab9740d591ffa7ee5c3d81c46d0d4099b1d3b9d74b46c7337a2eb3c18f79c29ed9f39ece11bab43182f6484a3b217537c2051f5836b207790afdc4d167ba4f7a9edad40edd723d175bc827bf84dd811d3491e3a359748df7e98ea09b5d77dfbbb83b816f2e57fb6f20ef6bd62defd6cf3e57dfe0d845319301b88e16f180b338dd58b4bed0e048a0825d961c891b5247f30531045417dbd980727bb00ed3948f27ef05fcab2551a5b3d34ae7b134737e401e952c755b1f796fc12976fba1c17248be801a2fab232d4a4b8e22f8a4177eac5224b2d3e17604cbdc6359f61578bb27748e7c415538a5d1440a6f99851ab0571ea29c2cd5c3d882910d0f084316bd098e42f40292494efa016f3b451fa60221fc02b6958de4ae349019efc13590aeca088cab6f66ff11d9b39a2b1fe26761320ae08e705d01eaa7ccb7d43838d2a97a6da9d40bde3efbf8788550fe2e799058a29a1dc86ef54a0919a4036016c54a2dcd2294d2e09e8b80b709b37659dec025528900852d326242b3f24b964a8b36ec09da792d1f52755431334c233503569837a3267c818ffd4c280967b38f5dde0bad8ec1fd954fcabbd5a52f851e536d3b2cc77e6e375081f27489db78a7cd9af4471712ba623c0d319b9619796fd508efc82bbb51ce064bd6444ace8298a19db8eb07b668cffebdaa2ac441aed04dce3166762846b08ab4865aab61694d79ff085e9e1acb27d2e0cb4609439cd8240384730ecaabdb4a9052cd299c502e170c358236fddc6e22eb64ceb2db9564cacf7df3514c53ab983b379570b2dbbad63ebb4696f72761716ccdc97c34bbfdd50c118408bb4d0fbc08cc75dbe3a036745ab7b7fe3b6f5c0d6a7f2a04ea314eec7ce3bf56b5efa8b54dba7aac9d5017cffe32597dedb53f633bc4f603bacf16925a2010334e072e8c68c874be0062cc6a1ae26667c7f07d944fd18b88d525f61003ea1887e7917d23b7da263e41701853279e53b474015834425009e65a90a285058c9be168f5d58af7ca6d62f76ddb5e50db7bef2db79432252903f108e508240935f6656a08e96b2ced369227535e901a661589727777ef81f52247699a6b6ebd0756b35a6c962f6c96dc0f438b6dc1642a1b633599cac099b3d0c610c4be463984821c9added799e675ff645c3b50c16a556490a6f891d752fc626d37f10145c40c105f972eca84e391ec243558850e24b8a4865f245cac9c4d8877c286aaadc553eaef271958f8f6ad54d81bd1f7dad534aeef9040517eecf54f717bc790635ce0cb9efc0eb36dc73ef815ced6aff84acdaed2bb8d195ca8844d9387c563e2b9f958f8d75fe7ca66aaa542a4a69d775d2c7c7a7e52df195ceec4f7d0b3b4a2ff67e50efe160ae68621647b04cd341047e6c12dfc67e168e09c834d4c291012639bd1f976e946ef4b539b539b5cb714c88da90e99151d27f3afdf6ef1a3297c8a10d2a2922b60917a96d88df7025452401a92d7452847f66cf2cea21b2d64e4b860e78be23664fd1759b062256f49a45f2a687f8b368b29e2575668fbc6942f69b645896e3dc841e2dde9ff9d6dcf065167980276cc666d17ca920b5e42cac31b5b86ddb26d3733ba6073aaa350c1e65ac8646994c49721820e514b24a6ec0aa45262d78171d721ba9a18f1c93c964aa412322841e5da629703278a65606a7145b21884c57a0c8adb1b41fb769975628d9092245a462d122c5fd94231b20255332f5e3df31d17e74c0a48e5442a272a04c91fb4969602c5807ec2ea216894c36783f7a1bb8efbc1f3d0bdc77af29216dfab526726b41b9b51f79d33674cffd9853b7f8503a59e89efb163ab08551a8fd58a1d98267386a42d64aeed75a370447d97217b37b75f750c80c35673fc72927784121d3840c1ce776b72b64ce29ed682c120a996f835af41cd4626badec44d205cefbfe202c79d3401e0e5863693f2d6a3f4d42bf8c1578d47eb49fc691d929a51cc771da8ff663229241a2c42a31712fc626d3bfaba8ea7412c57f9309e397269ac7af79fc4a558e3a9da8ca3e7de9dd05962e4da488d41ed84879371c4de1f8aa1d1569e8e219aa72a44b154396cdc90569d1829fa50994286aa4a90e7da22a2ab0e836dcbd18137144a69cc9eeeedbe2b8992ce551099994de3fa1a36166fa1bfd19c4e5448d73dd25ba8d46a41105391489b264a8a44b255d54256fda16c99bf69883712f2de6b817202320d27daae0427fd682aac747c593939353adb59ee77954455525964c16ce5d4a97f28453083d260c2649df69286598ee188c02b8989614b93fc59d52dc8f02f07efbcbbd7c6e9b29b0c9f61c78af94a97baf37531cb8b383e453dd2339a5b8f738239284fe09723cf2462e60e48e64998091cb21f73b8aeb714e0729f637e7d3819d1a813540ba2f92e23a7023023797dbf44654333770db62b939559edc73e1f6eac20df6e2b660ff7103038fdc8a5b499d39b995087916200b20cf932ca1fe7c1d12b5b1be54027d1e6923b309487aa48dcce3fd311590a708f53796a32acb6e2cd6c6324d6ef5d3628a5b396abb17d31184dc2fa508db6b7be56cb0dcdf291ac09a153671f56db99fb9c9169ab815f67af8d9a4587d4df048bdae9aae681d52038f75655aad569b197724bba01bb24811a94798e4509ad3001246408d4680824739c29c736a9aa655b759615cc6cd02b833cb2964b4f20c6d9665d9bdf5def7951b91fb391c33f23a6efbe7b6cfaf8d1b755e8eb885cec361c195d216a44ea772a60c320e4986aa63b81e83bce9ef84c0f6afa6695af561b292b0187c755bdb62ce15c03a920c1e7d6129947edde6fe28545d2cc6a9fa18ecb7f673b6666bb698ac6aa5f9826bc7711ec7791cc78d469c0f93d50fd08475bd99712fc626d3bf287235489d4e809214cf14315a29225d132da9fb2366c5c4a44eced07d2c9a9e34095ee0ae06a9339fcca013c812661352846e3921e5c8fdcd4a02d36779e10d59c06e3359b33587e6acd6dab7d666d65a5b298bc5459105e9e53f7da98a29204b2a2a27b2c3709ee0cc3e539036fda3cd0c07a4f433ef2010d27f5cacc50693db676cba0ad65cb4e47e923b8806191c42f3a5ffc0213a9e1424e50c3c4ea19ae9605a1c390864e649eea5c5be433e3c6426d4f1332a0c438b2de2f7d0614f039ac2e071fa7c4c60a12b83634c9e90d0378357a368becba3c34c5bf0e8301a70a85172faf69f133c5216a5413bca281195881ac761fd326158c3700c47313c9d726ec0a30bf90d7cca4495b2e8c273e067646723f39090e594307364cad2be7389c8517eb1c9f4622e11e5ae207d4269206f9a3e316dfa73e0d3454f163c53a88ada2077d839c0a30bb9d0503f80e7534f28175ef227033cdfc13a9d66da32e2136639254c16180bd647f03883382360a818f1d28466d6278114933a2350991458f043f3fe354ba11ee0c81c0de85edaa69fc6613f5a61ace491b21ce6555096cff65112ca2a235396a31c46e50626595251bdf23883a6cc51459e418ea2540a951b5e546e286ab191258f9415d4935bb6d8e0c93da99412385313ecec200994296bb36abf66b6eb6694cce7e4e38fd697d64a8b73b66610abd538548a874f82841a67a86975f9f54ef09fe1018f549504c619697915526786de86bce913bcecb09def093c3aac71aa10532061040779742fb9ffe38259de38d5fb24ce8169ab451248834a20657dc1e30ca2ac9d3c5216cb6135db4b2dacece98f0ea3b058e3c81c43df0d0e8cdbc02365d524c11584a1c5aaa56de6572b6d339fcb0f9dd9ea07b3f22caaadc993eb101da22c1a448213da7a967996591289c532c0159c53772a0177028416619a642fbd0b64a1ca893f1127f3a70b99d67436c0fef36bec17c956ce69ede4ba29ad5670b2c768bfe059ab95424e89ba9eb3f255ce5cc5649a2fd87fe593893c7f72a764cb3187d65a6badd7c1d55cd996e5090a0ae251831cc58347108f201e1255a1d4201e154a0dfa545227a74e216b152d62e0357e3f5c1414f4b9518b2842eacc7f39e0d82287178cd17345ca71e08232aafcf045d04e0e724e39279d38ccac668ca0f982ed73fdc5da8e498d491d529e5fc17c2da9f3803cbff640a2a4db23798e36499e3cf266be9c3f63f2f42188dbcc51767094393470111ee56bd60f7d98c462332b26605dfcde91eca608ee518ac02245a4320125c571e008b5d67b4768b1dba63f015284fe05c89bee4e51300a3d8129b5c82c72cb2bedc5bd125fecee2b294211b855785a74227da7da5329de72a1fbb4feb8575f4d98a38aa4e8cf98b7aa771aa7e3f146a0d492928d5351ab54a7c46d2253287ec48fce573f60ead27d523a7241deb85090b75cca1367fd38ca576ef302892325fdba6a1caf2245fa943ef5217943ffd3f2c22e4f6e43a74bded0cfc2511665faa3a7f886237e0d55c0dd867e95225307fd2786fb475f21d122bdf7de9b65bef29f2e083cbaaf1a456b6ef655a6cfa2ef41f47d45df7fe8d7d111b793b5d65aa5ac347c03737286237cf2c711f2edd06406963fde2642be0e205c23b36985658f190a39756a60f9dc27056e653f81d88bdd66fe4846b6d8618d09fefe35590d0bdb6baf8536d8df5ea6647b3d8578589342a9bf3b145269e82cc45f0a1161c94dc15276500a11f1e4184a59fe06d6b0d0a52e58c382b5217baf05eeb3b00648a67d66c316b62e6c1616bfa76dae60ca38156e57516d1a07277b8c5d8f4ecc2c01abec95697b7525cf09739b51ee8ac0a36dc1a66bc21cc57d1cb284aa8414a1ff470e89aa4b489bfed3e9000700a166c2b22c038fd5955bae7ec0eada964bea8c1c555b5c5857ae32f0e86870540552075902fd7e7f49545db90d1a52a77e0f7a89c643541710d92a0c69bee0b17dc063d74e71b64bf09fd5354bf0b07fa608fd3dc38e81bce96ffa9de18136e6366dab9036fdab2bee16b93f8c58ab07a185b94d4bde4c2145e8ffc8c0de3f309828c26608734af4443de43633288f3fbe508b6d87a6c042382891c79ba9cb6d9afbff9edc3ffd835d19cb81c74e896dc5f0fcae476bfc052749c0aad3eca69452ea4ebbc3539d1267850f9c784288284c768802c427051e9b4beec2215c582415f345e6c7803de436b2252f171cc27741d4f483350e6b1108f7dd3b08a4eb7e471559a58b3cbf611dcbf33bd084e5c02350749bf95e97061e3fcf9729fa038424f028d7f8d3f79755b0012683697176911be6a563cde508fe155af0814237043e390c99beebe4be1dfe02f084b6bd56af3b0c5fee77144da9361eeec3a6b9a369f7de7b6f966de154f18029c34b1964c02651f69db95a268516140dc12096a5f7233c4d31434a1efdc78c279293a51b603945cb8f2acf2953ab3cfa4f1edd27cf9523d1381dc5c79fe4f9eed3a8eb01c882493003ce9ec31129c2fcf16ee1c993678b4a6ee0f8e5fb5aa83d67bbe9fdb036cbb2eb819004d6fe4a296aaf79274879733ffb0058c1f733efa96a71fed6e1e4c1abc6d942d37747e800c2f2bb15ceb20ca4e16621dd6f9f53e51b937d47deccd7b1041ea7efb436892ae0a92e9c4415ccc2b15d33dc4c5f820d98a14509d38225e389a768b192e7e46eab32094a30b31692607b133a6fa009336b52de68b7025a38f67b7f2d32c375076a713a6a1a31052153734e553b2bf49f16e77c52111e8bfc84d0a2fc18780bbbb85b24759f76b8c28e7ded8ba4324a9fbaa4ce4ca65f7f2a2bdf2c3dcaca3e7a0517dcc6df82354eb04cdd7702247b7bc321377b99ba0f42136c3f0b9b6caf016911cc711b7f0df40182439c00b19f85436417327a70a9523614ddc67f9b03b7b04219787efd1ac3056e10866d85ba42e3d4a4306b32cd9dfd5b4565f0049e734e77cfb22cc3c288973b084ab55ab3e8e8d1a1e4743a9d4ea7d3e9f57f52f1047154a6a5bdf8645ce8699e5ea7d7e914733abd9cd4d097af174d4c16bb3e8eb245de4b47ddf1b2eecf2af7937680c7114ffe806c684b38e02247655bcdbe9b02672db7e9b7560b3279a66af2481d1d242a2323d3c1f4a1fe78b26156145a57518fc2513465453814b1dcac284b56b4c2638625db22970dca591a673efe7c849c5396a95452c716c91b231265c9b04599ca160519e18f791670c8c2bb4d036172b5d837b345955e70848c66e129272bcab22cb345b6c816594a694c0ccd8ab2221d3da525a695c90897cbe572b95c2e4749d77fbb6868fa95dbd5b05136a7168e546659f5ecd7d7bc1ff66bad63119f2c041c8bf864fa36e8f82134e1912229d3cfbce9fb456f9a97a886e2adb8db4def047a63436c50438b9d948ebfb196d47940ee99dff13bc221a61d3540686a9ee68ba43813d8ddc36332fd075f03d600e1f135cf23ec9e9a0fc26605edf81d61affa874363c4d32e4d7563da8ed62e475dd7c8e5baaeebbaae769d81c71bebd52af3028ffdfa2edaf1fdad65db0176eac61c95c162192c8365b00cb683b4c37463a41d3b4cdc1606cc95d4d16e0da5bcb93e7c0694291d200d464071446af610d79d9319af3e056190d9a434343334343433f79a6e91527f52605d627aa4ce0879a8a5b45966efa572080598d0ca88582c168bc56273526a8add8bf17f7f8e6cc51cf51ffbd8c73e669724c9a215632821456a0811199b23e5647b22a720c68822e5e4fe7f2c2675bad5e325e98ae115ee5e58ba8d1498f4a59fd482c95436162b918090bef4f34ba5d20f8979525822954a5a09a4a5278543485f0a6bbc97a921a5aff12addb41269dbb69e1ac0c91373d12f24ca613032c2bc280f3118cc51da1764c45e61623cd81875d1d07ae1c225f7cf0ccc92643db1d8b5328f6c98f1b83cd00206380053c3e5e3732af5f7d136966dacfac89b586cd5b5cc931ec3bbbbdbc7e75e93e95f144f2714dac751a79328fe8ba2c8edb4f8433cc100871d48911a42a40173342122859fc88909155c524eba1f7396e0418ac847440a874efc65ff50ed0d4ab47214ee521fc1a32c9a49bca7a008a45016e2ef7d1b52b29cd206501621cb29303099c5ba933559dc095e129ac5108549288f04a41999863309cb6dfaf1bd9315643281d2c6df9d01d95bac2c64bdbce911380a004a46cd54bb90cd507327fa107d741ee590fb68514a49abf7837b510e898e923a12d5411cf193bb7d6c203d7e1b64bef4f36d28bdcc8fb2a8c69f05fc24e9d5785801fca407829f2423450a65525a6cb5d841198c49afda87c827784d200c5228538aa5c5061b086c96cf2af5335d442d1635d83e452e1415d1ab695996619cb54ffb148dfa831748297d945d3a8a01728a1b934303ecee5eab3b25ba42d343a364be5eafd7ebf53a51fb72d4e9248af8855f4db305a776f06283149153103c66a49c683f62fb9a61491d9585398af4f6857bb229f3b12fd25b6bb3f79af9a32f92f240d203f19ef4f349a41f8543464ffa21314f0a87c8bcf7433069e33a124822850158a60a4cf3f8b9cca3c0f7336fbd1466fefbe9a9f0fdcccccf843d5a741ab0c2fc3138b397fc7d72e04f47162c2b9716f163d01e91373b3ac2c08d832f563dc775edfdc0a71238762e7de5d2385fe3f87fa0942550c74b0fbf8fae63035115dd699c99f72f55c15cf20c9dbf1fed8edd913a8d2573333ac071e6123ea9baf7a73d8e92a98e0b47f1bb5ef317aef0fdcc8f76c7eed04e4748552d7a56e434fe9b0b7fa1103ce29ff991aa3ccf927151c6e108c3f492ef73f395f15322493226d293b10c44f2f7181c9d2873dffbe8cb853c1e851d3219639cc23fbd1ff84465a79525feef390cce8cfe03bd9f0147cfcd2ee6036f8b31e1d8e5988f792947930c08fe901de52451cd32ce0c1ca97fdda179b43b32e59ec13e22ae95a9e0c0caf3c50e410d43ba974e611dce17101e9b68be888ab8f7dc585c591ac7ebdebd6ddbb64d85e56af75e6edbb66ddb885af4223348f0385f93cb9016bd06e92f578bb3895c7ae92cd91b8bab9ba85b5b51c73083dc5ed332e99b36bb679117ecfed5a96b8147c925bbcfd044150a277b504a29d73f9952d5246a23d2d541f89069eb9029751adad590a3ec8fdbf8fb4889602fa221a2fe7117727d5aa5b4bc20bbcaaec2e71a5221bf2bb7f1c73cc02315cafea325ca930ad1212b97a8c80a4f8a31a8744da1f58089acfb78cc91c4eec043dec8f978ce2b8b784c6dfbec431ed35a5adbce1ace9a6b0a6ab595863d1a15011e3f862085204094812a5600d20204a82cda4282d4b919000066e4d73d12c9b6fecc71ff16dc1de4d1a89c1e59ea340f78cc29226ffa679260194616913a715c7aa9b440689d3d18408071c4370b426d94e5941e2c311165aefb85cbf4050c042ab401218a1862062b90c2c30e1d40a3063c643c713285cb1457e82b0433d258638935d068328309b09a10a28b2bc410c3872f921851f1c5138fa271e7e7e4d1847b0044960bc8720a98a27cb39c02c64506ee2ccb5cdecfb21866a86188e632967b6a699b79dbc0546a99af59a41d5134b9c48a5aa4c1c124b2c24dea3da594b27df0ceb89e3273ef5255f0a79496b2dcaebeedb42ccbb24d9b598f3cb37a7bb4383ff4f06dc0dd3b1ba7524a69a5b1f2d1f07bd8aff4eff77f405f05fad2521a82b8c5aedf55f0e65a6b28294d756bb986943e0d4d3db8c9b59742bbe7296f4251decc8f4263a5ab4274eeb93bf74cb30931997ea5999b5e0a3d3d1332d0b3f4a6bc91548e3cc933f4ec1e8d24c83494f2667ee79ed1df1fdff106200ab648f0f8cd8ec6600b7bc72f4f123cdc577c52eaef733e0d3dcfb06bb1ab6b66091e6947b30c43f2a51987e25c7ac31aca16a7bb97c1ddfe51fe955486340c49286d81e72f71779774f61728e8a82207861cd938ddb4ebaec1d95dbf6b8f3aacd53b618b0ebe5cb93c7c89e2d6e00beb0b3d828bd982a5bb87a38bde3de577cb147287a30572d02b8c1c7cf9313939ddd88f4765f2ccf9238f1e3cf218c20eb880909d074a78504676389e647f3196e4840058ea9231af18591bf10c47a88005c6114a767ca4cb073134da8d34dfaeb9689cdc7fb19729b9dfb48321bcf4e4fe7e1a4216217e28620933740083149d543205028e4c337d14263065caaff8c60bff10a58b3c9e9c10b25030ce72ca1b4f747823892bb68479e307eb0493b29cf286ca0c37dc28c231594e71c38c30dc70b941e4c6cf109c9401cb29b8bcb24c9653a811bbf796d050b1001f1123994144cc2a48260c9e111144f80c2e907845b862cea0d2437784cfe88c277ce08c88796728c1c35604ab3b8388241a1130ee0c1e7aae0bb6d396303d1cc1da02535244cf192b5896d5161cec6004eb9eace88b6f878df0711b79b750c1e382a02dac1f8a88b9b1ea36623562b4c2e8420dbc59d182372aae139c4589823d1974c13244f480b72ca78401c416611cc9aee06c8e0c66b56099315678a4c56501be594e01a38a0fb8cb720a1851e4f19483c05e9653c060e5996a0366add62d2fd691e5942c2cc834cb296d58c9a3a955d7e038cd6f4e7653d6b65e4aa95fefee9612656bcff69ff7ba3f1d4d3ebb7b9b690137ce3c5369cf6a6b4f9bd99e594d05668afecc32598645724e06bcdc652b7f825bb632c45a06333727ed3aebcfecfeeda1a9c598110629846b5a48dedc16c79ab9987166307338a627967574e58dfc99b9d1a8d670c8095234ec22def767e4757734842cb951d0ddeadb18180ec25343dd1fa594f7662158edd6ef0835b9c6049e2fdfbfdfc196b902cfbf2d8eba51109e3206484ceafe96c713a4a4dbc8c72329ac37adb6f346314f8080806a4c4c0c2ed5c754a6c7abfda1404042566c0c1c0ed4d3eab1f387064032a452edf118fcd0a7c14721db89b4e90f0cb086510c6ed31fb386db59c2742245e88f41eef73c136aa661126ff25c47692eb88df6040c41bebcbe19fa0408080808887a5f6668a88a7ea18919e9305115fd62daf1c11320a0bb06d0970f6ae8114cb1f783d63c0102020202a201100f4bbd14ea137d33b0d9d5a0d0346de3c18562db368e07170aae83a2eb3aaf8b06c58dc31bf99c718c62a0d0e288c137c481a714e94b2943ba210e52e9bb61a693c901fb68060a9a98d296d94e436f2a7399a366528efa52f2ebb66d5c49731429d555bb4c46782225049b27133b99845a2793f2bc1ff6bd5104fa5a919949882cbf013e18204b983e3952c7e5ac4096d2421762d8c013896984bc690f8778358c62ba1b3ef6947c0a66e07a0b318e1aa5e48700c45b81c3325ecee9749a408b28c874a93b49a5ed4d4db0112c963ae9df4c4a228199bc00cf60ef870c12ecef9dc5dd5134a914124c983c13266dd32f998c9ec0f3c7c924b7a36852f2b998cf0936e17bdb874b145d4e4f9e0d98f333108310792219e7eaa70b1ba33c4ea06b058b2bcf9f455db237e7cfbfb825876253c50a9a2f589e5de6c064ee290723c21fad226b5d96c8dafdccfb71e9d8e50c8b3c7e39fb22676b78c9191b58e46c8d214cd64a2a5caad40050b1724c02179eec3e00249e90c10ae8b1e93474f3f4809335b6680316c50a921c68500508a1151c43eca4babbe919b9bde4eeee6e3a3f8429bb710c90c72e8f3cb650e3063580b2a7c144f66f32a9f0e8a1831a204007afdc6ffa168778f5e07a9dd19809e6f1f294c70f0d160ca191834cbb2e9cec31d656a6d973f7354bb8a1afd2701774d91c9a2564e17c4d11ac903a1ece3438da9154db66af0c335bade5bd5f4d1dbe2ee52ca1becc529caf551589535738f5053236bb0c9037edc930730e2b87f6f82faf6921236f9aeb3456152d0090edca51dc0ff7b372ee67957f26f7f345959f558e71ad8177c81b53d753727444c3aa711dd0cfea678544a338a01ffaf3bb16bfb034296e33eaee1918fba3daf69a81716bad75eb96b3d65aea940ba5d26b7577bf708069a911e13a290d0229942c654cd25c79b4d845da2957351073149c5b132a74bea19437fe5e647a5e95d89ab09ca5d4524b2d081a905ee431068a123c16ab81677bae36d84bb3ebd2aa381ed54ea3b8241c0f8f4eedd17698edb4fe754929a594524abdd24ca936034e925032b582f6320369556a7beca85216925d7007494e4a0bb3bfd64773962727757f037790dcd47d9e9c94f61ca5d57ad637d448dba67996cbec94a94c866d7a2a64b904313264d9432135ccec9f207b6c6776dd260479c0f3e5d3ce94e032ba75dd6fd9775cb7719da65d1e11e0de68b85dd7753768e10db9ad2bb5d656a734c96dad4a8ccd268f54223925590b9a1c8fdbb406a5069caac52858753f66b469b7e3de0190db64ea4dbbd90edaed4aadd41fa953a94bea580dca17595a9eadb5056d432df66f5748710b72d446e436438db315b57885c4d9cc90627f502757d8f14eb03b486e8afb8dc851dbcb6d8a1a678bb56886c4d9660ed2a67f03a712998093dbc4e490381387ed35974c166412fa4f8ee25a206d3823944c15a7e2549c8a53f554b9ca515c76a7aa716e26c58c03a7129903678d64dca6b5bcd23c2000f254994c18bbf039a6898e61a9404525566d5abcf3c81bd9e2f84195b5d65a29a5943ea5943ea5c9feb5d6a6943ea594eac81e3611169f73ce39e7a41ebe787c9c265b0b4660f62cf11e936b3651e3f05c93b7ba86b5e83aaa6af9eac5c405f778cf12da53647d5e48f07c9a3ca947a06d7cf8e4f6691c7f942ff11e6ad2565d3493f3481d2c8dfa2ca29613979cd0334147f6b0c25aac5e901862b2242749ee9d0ae683ac7324aeaf2a2c94c95863e1d9f6279b40936fcc94c30f4da66e42a6234b9b5ec1fe0035ca91f0951ba6f06a2c27f65f00d5dddd7d4a517e4fcecafaf429fd8e64b986d2bdc516e788cafb4f16b3346146fe46e4d9d5b20b469b16e62deaadd99a13e657d21832add286be14354dd38cf8ccb276332348a0ccd97db5ed6a716c19c6557eb6cf75b7914490bd1470b62d6d361ab2df6c5b5a1464ff69b76ddb6ed8b47b43a53c99958c95fb630d68d1b403e689350e8d0ebd8fe4a6341e4b64c390620fe158578b1968e190368c4c40bf1db2b016c39038f60d29160d5938a864af42a2b225bf92d9922b722f79038f902d397dd9c976723f92534aca048c1992dc9fa99a04298b1ce55e4819e64aeeef222da8638ef2a1ea69502ccc51de721bad0616d638de6ad1d3a2e04e7b1b46a2b4efb77048cda4d1b0d6fa3c3775c3ccbb657b4c39e0f9a3b772672c2bdc29a5340e5afd069556df8146c7fafef62dbd19cd28ad5f36ae6ba239e79c73d6215b390a86933d4627e2c45fce64ff0a82f002ecd59dfacb0914f1b09b59b268d1c2c5db0beea22b9aa871687a9a489c9cc6919e79fdc69138b44793eeb58ddbb850c8f6343ccd4073e338adee79cdfa7b88f682d43ea5210d345f13ec7b591842e3d8f0d4e3cea0c4458f1340542982e7065f747491a38a9623716081410884bc914518306f84c1c318340eaf420b319af0224a14372892831a4432086ab28233d28861c08d295138f184183efc20064c7544a502a02e5850381c3044152c7c60a38d1afcbc30b708827a40182db83285922baa7801828e4108e6132f6091440915396051836584f260078b9f2baf282f58c224c807b8e022092f45acd1c31c740738d8d203c7134954b1b28217a008921382e6f24296014c95c317246684f122c3fc1085164ee8400b94243e4490242a08a30c31d650410ea6bc6182377ac062e88a18726cf102dd218893204fe0704590133129b4ac9a091a64e084c80730604aa85e88428a315cd0f3841a4978dc1801134968f982ca182f262f3811aaf123469631e0c882441ba0175a7880920510515411021c565f70a8020d2d4ee0a04110196e708111a2055478c822f6421124a2f8b08226529a68a105f6820a868c4461c171c60b5a0f4b5c4132049434d8e8264210070e2e1e28110a420b8b76a14011592c99838da02ea6cc27a8b44443b65c8b27f27c39ff66a067071c70d1c10e37b0a2c70f71986144cac111aa487133684805f28a14266293053c3d6e14c008d54b8dba45923296582669cc91e2b224b69822c5dd8e82b2e5430494e2342b8ea0c186f4228a4f15aea32001c2e0828c34ba8e82430820c78f0f5d52f41da4384f505ea15aad9172507221470a8c55e642e2111536509d9172f0a322e549f8947ee58d0d688f399b68710dd29830537e919e342079f5cf9f32bc5c142c5fc272c03926073c5ffec03f787e5e9161b703962ea58f9ed38bf43caf34bf2f1c61985ee9bfeffbbeaf54fa6f96de476fb3f47d5f2afd57fa8ff425d2974834df47f22fe6f3beef2b7da5d257fabeeffbbe8ff47de9332f8552a9f4a5d257fa4abf7d69bb23de66fe2b3de9ad9702e94ba527919e449a012b8c869949e1045aa4de09f5e97fdff76ddff76d5fe9f3beefb9edfbbeeffb1ee35209ec5aa4fd3218a4f9af2b914a9c47a2c1df68e495b652e9493476c6decf46239a906a33a550c8f7a52785e3f72415be2fbd8fe63e5249b648c37e1f3ef23a6ed36e66bff92da1726427f7c892ca4e5126ca32cb6cbf9681c79bedcdeea8293485b467426769713303dfc699b9c55cc3ee1c5b50887d53f6fe3cecfb7dff01481dedfd39ad7ef61b78ed57fb15a461661acad9b64e5947e08e2ae5a4650b0c575bf2dd6632b3f4336bdd3a08496e953af7af37fd9b39083c78be5a945376d73a6a72e29664996709ad7ab9a791fbc7d714a13dfc315bdea6295f4e25b7bbbbb7bc0a89a2f26606adc611f27eb8fdbc13b0055fd8737d5d9a6b922942fd7a04899c3d7d8ac3982994644bd2a56bc553a2e6eba5d4b9f5a48636dfc8ce85736606d8feacb2471b4882a9cd9605b3b4189369385f2dce56f66f9ce994eea39148eed188e0640f4e66f1a7b2c3caf8bfff1e7f8f7fe63f1fce76164707062dcf0c98167d47c632194f2ce6ef6b164f051c562d617db5e8426892169da7451076c042be9f6166fc1f4893509e1675c0e3b7d71155b53852950935631e45327e97a9025b5558632dfaf52880fffbcc4be1fbef3f3c013130fe0fbcff81334b895a41daf85f991f03a410a864edb64541eb14d508000000c314000020100a0644229158382015f464f914000d7b8c407260321688b32088611832861863940140008008c8080d698402020b2ba1ed1ec9d590690b964f3591e8eb276b3525451ae6f376b814be5ee644bb0a8989d3b75811637262373ff2254e2e75444af19c3adb172ceb58cc67d41815a5be2abe2e30ce4be0a04084a1af55663c2c327de7180d298e1afe2db169d7ac1a9730a460275f908684f4053d7f593cd4174b5ebf9825c5ed52b37c73bf59dcd905543ce86407ce6ed0be9ffb22b9c16d8a7217c4df0205225dae499f97041ad7c2b53b5a5a0b8bc966f3c0eb5db0fee2b00cb91e3adca9d075c6b6684ddad537b41ab71dcd516d60d59860e3522c00bf204bbf55f2b6312e2a48717149b49989d66eef123a1b023acf0869e7bb1aaaba34cb4bb2675870eeaeb4b2d566fea89cff74cf7f09827d92ab0caaf8b734a60f969ac44c3ef1ad77a2441870e71b01dd7b2f5d230c5d122e3f38105bc32e55e0903a80611395b58f5ce410b2696037b2978668ce61801bb30fc66c7ea7bb6f31ced86b39d6e35a65343571b2d1fde9f56fd920a6f1195d9a91244997082919173fd36082604ee896ba08b22dea35320bd06c3c99d309a160b0ecd825d020376f99fe164b1f9a3f08bda67b2c32371d6c864be99d73c29132c3f61a6cf10352953c9d744e671f80de89a9fc77b4206b6aaaca74cddfdc81df386d52fffcd3724dc6c9444f8230dc4ba89de3e06f83ed402515b205942b1ff4b968e26937f22250ecf170db168e69f1b924f5a2b9282a8fcd25bc02eeac4f1f1c1fdbfde352397a67165835dca144348001cf328b76d06e782cfda1548fbe7d78753e409fea45a0e713fba5d8d287b0a71846806b064f99834944d6a5424d00f13088445060438be9940f9e152ee3a5c1973b33aa2a8d2c9debcba9afab989f83dc01495b0e8281970af051a6da0c72e7d08fe5871658f4c010685211641dd08c8f25346b33bbe066d21a3c385205777f21059956363b3d79bb6ecc68f210e618020dde621d03b7ec4b42899aeec9df81dc84aee9cf38857b887589df8c26cde5fa541054c08ac13f95f135d504b7f4f0758138e52a1e71d44346e910f1a8f49e2326725b9014e94ed3cf52f42c3e9bcd798ca9026388568dc94b555bbd9755125e64ef2f06e42f546b0c537780bb20a2826d9ee7200ec310369a8fd042440bf1b674db90dd5c902f3aa36b66d09b688331dd146bde947babad8b22813029387b4b9bb93bcd3621f0c198fd40bd8484d4e532235dd940a0b9881cf89191acd00288b7eca915145065753e8965150cd1e2a20923b139a258da7445c6555c89a7823040ce44aa898984251811fa244deb74937867e20b093e6010681e42fb6b898853ab4829b553f30aed5167d29038213f852e446cab1fc2efa94d27eaf58f3e0bf8955b13937f7107f3b6aa0c85b58cbcb3126dcefecb94c122422f8d98c124a6384a38d90fc184ed808322edeafa4839da8e503b9ab805a926095ca180a0f16f8498293356478db8d906776531e039b7ec9854767c57a399e715ff3d11c8f766566efc77360326d31275e48e76dd4244ab3aa4302d527b7d3839caf28bd72c25d091e6c953c5ce9f1194b4872e57a599fbe4e2b0ee1da69fa7cbce644260ade0d895c3ba178379b453cb531feb2ddd54100e25508ca205a32990c1465120e6917f3938d7f1f9f435ae92b49fc5f4e9d546b554fd03e0016bdb74617a2e1d9623ba4da8d865a14741c8b979e6e450d1522649db2f62d5362d042c682c3183b1c16d2f86b0e8fc542c3ebc0fc2b595f26898bf12aa2ef54050b0cb34af6a8e47b4e696ee67fdd2f0b5e8f657d0a43f50471c24816413f28c02412bf47d0f7222048dffbe7291ed2728c8f6065db3cc31b20b3739cb2ef01f5ebacd1d93a9897254f88248b991d04a49a65755da9f145d091c52c9d0fa5f8f8306bc39e5e9bd6bd4da8687d33b7b5ae4bdc5476875fb9c79ef66bf1305b2cfe8d91a1997f36e0b27abb5cb093e963c5ccf744fc2ea063f47c1908f9dcfdddbe16f0a2e9812ef465545bc9e8b3d03716bed85c4d8bd02bd4b1e36f97e1e959f572e13915287180f91a3a3a67f2d3be5a96269608de3101de10bd4dc2e3aa120e4ef62b0235241cbd3828a497aedeb81798216defa0a8e5a116c2c557321c2732f8acdf08fef33b620814d3db15dc2bf3fa9720eda97d7758abdd6fd9eceec79e8d4d996f1fd96faa499ee3bbde2478e2741c0b7576a88237493de66b1ea4ac5b67b3e3427be6f0532bb4da224ada4c93fd23663833d540be4a3d78ec3da0c469c7b7664a9846847ed73e7b67b10da0f8103bd39219ab2209a447f2de522b40296c249582a7f42a125672ce6f2dac6b4e25a8140eb6bce2966946355946a4994cc31edcf375e22285b2535077329d2fc16e2eecceb9568fe1dd720ec0de71df430378cf3f1c921f735bd976fb24ce4fc6ebae8b369d749f90cba6179c6b10b9e66eedb2112137bac0a13ebd061b7e9aec596519f17aa06485d1cda89fd3589518428717b2d689836248d3b6d470668b566b37875a5a0af592014b757b8cc82d272aec9bfebeec736e949251144547b3e2409be3f26624e8ae4d0dc2abf15b62178ae5345a6c6b27315683b27b2baf37d84ecaf647567dd95a385ba8b2cc3725c1cd40d4a09d8f6e1746949b404274a0bae9a06277b872e53e7f83b45c22dcdbe284c7a58df265e794c713ee1b6d1768f35cda10a7e5e628fa9c88c306443a2614d176ce8f8b9ca31a950a97164b5bda0d26d9b3ac25efbe2f6d817c781e3cff2156d7de2c425865549af41ce85219e0c3a875fce9bc82be43cb90f8290af14e68eb8d50061adbe934baeeacd447ee5deb82ebc963e5f2df74a1c087229d9f0ff440675666aa395e4300e7a8a97dd5b46d2e504c860f4b273fdd89473143881fa5c3c0245382735baecc0a412a07fc2df46b1acae1923eb7db4211241ac9433078a33553eee00ef9371c63db5938825b506c9ea4cdb7317f191307beda69f04d0faf1d81d22dc5bbd826ef9432574cd01ae026b4b7ceb1e93824ddb6eaf8dfd6b93af216e7dd601904ea16e0059cb1a7baff93dde1f1d402486170ce9227a2bf036cb2a5980483be08bd0d7d882031fdc9542a2b33c9116ee300cd9ab68fd2ccc668cfeec38fb381eda7d8d75e1b50631685d9865b3556017660c6fc0d28285135c985d211b4de884dd1fa47be4591cf707d91f9a353b046e2ebed276c8207040fa0cd86f425748b5853ccf1568228c985feb07cae0ae25049d1fcfac05ea65eb3376b23d82699eea4e37173f81f56d6465e9904c6be6228203de0815a821bd204b1c2400c8dc3888875c47fb210dd4a4ea32a6b28ebad43a20e4884995d0f3dd25fdb4d365f402e21d7a7b09c2de2c0fb2ca092dbb5725aec6e07e68686e58ac97327f421d0822a7084552d9ed188e4e47cfa2d66c32ed1fc4d6076a60b0f1455e6d286c5fca38f020364e73b52a9c89cbfebf687cef737c6861b78fb7cb80d639057dcbc6a808ec91994b60ad352b1d13642c20d0a432adb55c591c4d37273a2b5e12a27d110481a6c0b6e487cd14a9a5f5009c08e70327fa6122f79327a2f6cd8e88d589a6bc90e09f88bf9a7c961b071befb74cb20ee517ec9c1cd22723527e97a191f2a4eebba486d7c1753dcf001dd0979131238a02483d599421833a5310fbeb8dcef64f6a5d4f57851c683715ea26e4a9229d9c605de3ab9074cf2f646398d9ad9ff5468827286ecff5aebe9f42178539209dacc91925ea18b7cca3fe219f0485ab6f6d5590212a8b854985b7f01c8c04064bd943329eb8067c5cd668d7ef561fab35fad9eb9a204075db6d20fe568b7c283e1b598de63460d197589da814d9f8f5aad61d4c139840d5dc800f1cadb30d3abfa45313399866f572f0ade766a284cd3ad711e525474e7ac6e1f66ddd5b7b9a5a58116fe9bdc84799c368197dffe4cc6303ee185de521c8725023c3198bd28e35ab653a0efcb91fa13367893dc3d387c835d567195b4aa2b8a3ca46663f211b5d90114d7b87e63afff83efc1050f47c2bda17a706f60a40dc18f231191bf9f1a8b5e2140db2a88e2febb0c0398fc7dcd709500a9f382a7d1ee3ed9b3a626517b82976feb445bbc8d6a263538b26dfe71d3a7e9808d24ccbd46881573988b15a3f24bcacccb2fad49bbd08a3198fcbedcfe3745a42428f0f0b92cf06e2868857096dd2b729f77071e7d439007a751ff5310e5517ce865f815d5e8f9bb34f0682014b4d81323e24effea7a21071c43f303513d97126771ccb6b68435f3e96d0929bbbe40df4126bd9c1f9392dce73c07d23d5722fe84fc562fc94782ec94c0255cc2e23399750b113e05542c5579cf91f824624122d9c80a875402128448b8924c9938a117166fbeb4b0152af4c5fc199fa4a279ce955fe7eaa902fd0c02b7e875b6bbf4c10c126bb7455fae0ce57adbf9c39a9f97ada5f4c0ddf0558e3a8d6387263f71d89bdceb056c485939e09c0f5c2218890f27fbcf4c913a6c209313ac66ad916e1f551c44694b3dae7f10324b114a28a5b26828a3ffe9b8db5516164bd1b6c250519d1c289839e922609b0225da752874f3f0616a2a4b8ab90f80fa543654ef1a4ef1a1a1d55a47bc3f8221442886a8651167bdb344ffb0fa10a49bc4d9a72dfd8da51fec0277d8dc8ae39c45da21cedb152fdd20f199e3d0d2c8df087a614a48d0a230096e5c2fa1dd7dc4a265bbf8fbff549e92cdc6f19e0ce627483b225646934c7e937a9830c2b799d941af16e7d021c25dc90cbc1f6d8c07344103d0cdb99d403b734e7ad412b190e1f7702be4dccdaa24d99d0ee4808097d24787c252ec51d0e398817a5a49eeea971aea95da161f6afd5d2459d4660b5aacd9a7548f2b184d3ad076edf742ddcdad853e3efed083e294bce0fc3013b2b0ecab163bd3eacfde715725dff81a709283799e7b0df3ebaf2d1fd16c226a12cdb9da4d90f1f7163ef9a3422276826934b3f8ff897b097dbea1c6c5d275bfc9b52aadf3427457f7f652afeb5c536a660a6272433007a36cea7b91d91c73c3989eb221c57de15b39eff06dfbd6f4639e9bc8524afc140ebba24aa1e865c128452c1a505ca551fc503846f6ac1fa2f4a2e5b06e2a97bf568e6359f61f4284ccbce338e2fc5acf5614a462e7a4a2bf082d8855bf2dfd16f022ed1653f29203d8730f174cedfd89a10a6051c6d530afeaee56cba7685a956ce78d9c831058d1a3f7b98a810a32fba2951dd582021394f59c6c22218f32a639bcd6a11af4ac0b4c3a370b508edcedaa99ea03e1863d1558109e4ff843be513cb5455ce416a1005c9e93991862a0048559b62f82e95f9441e77c40025a1548f6bc48004db7158bd21a5473a65e87c346533168a520c67a7d31a6cef9774dd5c270937d89e5541ab795421f7375a1a555959c6e7bc9da778f831a26469d3f2304246ed0ca995a59bcc6a887910090503bdf940152a0c05f303c3080d21f9bcb2b758393329e4d3ca884d259d423c08371da36deb17bbe5a1a4a96d7bae0a92ac416dc5df8e1771e80c25e45c5d3bfdc91fb67ded1be92a0b88edd5ce3d5c7ce429289b9b0c8f888c64817a4348bef2a1a5d7c6f0a49e72bc99ae52e7073c561067cac966430a4e5085fdba12de78f3d695628b3279c2e97485d1e874f9c3cc91c1419242301583e2800a0d7557e83c757512a51feca4cfc32f45e4b885b0137c12be14f16027c83f7fde66b9e0e136706d52e040f35e54b8b4b44cf15b5c164e38b1ad04595793fe75fb582d9c6a0d2baeaf994ac728853694fe50bf488482f4a76a2920ce3f3ad5c3163a5db0325ae378a3b6ee23f04ac171f6a663b1bbbedff1cb36e403ccb13a0af7462f0f933cb8cd0a1778a3461674175ca8c8be7b4f4febea6eee5a0a12c75d03906e7484827afa694629b713180346c1c93e3fe1a2ad5edc6ab8a6e118fa8b0011dedb9ad3abbfa1b1e6880d22908ebbb2c53b5c87ceff3e818c49faab229ce4877f20151a52dc9f741141410f1184922459a7f61769baf13b463cacbe32e001103fe56e65fd66c78f3696e0065eda99cdb5285bcc8d8b8418462c10228d29664f427008e9440439f5041d37e292039299f6a8daa0ba4e4bc016fe89042557d5ed1f708963c1412b378707a5f379bc9718a217b2383e38315f95f74543c50558e6508a1ccbbfee7b09050ab08b1d0f27baaf38650195f1e9faf3a393702e079d850014e3197476c58798631b5285f2c2d013fc967051e6220b6916339af3813eabf57cb2d6bc54b5caa0cb432f6157e80a77d1caa41abce762c2185dc78b22ba06ed5167b91d459b77c73020ae42db380634a6e47a74ae14da40a811b3475d670842cb34440b4a06a7c8365a3f9444d1c4289d402d9fa380d29b667f7665fc851aac9154425442ba7eac4e2a4e08f1cf8a465dd3a1184daa47f6a651dc0b0793420587323cb3e9f25060c3eae70d3512558163feecd1c11ed16edb0b2823971cc6ea28a457376eccc46e3ad6f916d46bb1720194271291cb30f8e37b2c8b3e01dbb51bc619d6a11fd1318a6781552fcf645b8502b4cf951f606870472b8e5d284eeda971fd297e0869fe02cd6206cde512929d05a9f4220d9369933c9e9856a7025f97474e4fccf43dc328d2038933caf210057aa7b37e6698ef8587d811a12c72b8a299a73376986ebd509e0ec0d42434ff0d9844a9cf737ea499997c8e9d1c60b5365a3b15f43d2fd2f7a7fa2c5bc89082e2ce30b5e2e0c5cf8c7d59d16083a20ec026413db82c0b94c5a547db0663d1d9c937143a6c4f8543bdb8c835e890122b8ab9d194968b8f61d22c0cec6664157dadc9452acd0fcb486ae6299daa3096087d4bca22816cce4c8f20ab1246c605730df81ce073fbe65e3f5b6315508deea199685134953fa3b6c2a47091e63a5d2cdd09e5b288f37fad737698c1396b42117c155003dd0ffd8f9c3f297574f48841f3e7da635f1d92b42051771012353ac867ced851a0fb8f341d22c03a42b8a77a6d388d67b3ab85c2031dec5d2bb6f531a80dbed4d7e80605158002b4da802de5fa20d5ee24936c451fd5c8e479cc240ea37fea259ec655774e3f6131185b9a92b816c251f259ef46f615e06295ca2b5186d6b20ed813d8f90f5f877d8f550ee067a7c89c00769b0cb6d533f707fda7e1c2c2cc6a6fcee4e248414ec3b75d1587c5b835a69176e4d32e27957648e4624f18b49234a03364978fe607a083cdddad06499444eb252426d4da7f271f6d5c10e0575e1e9bf9840828409ecf2d03482b578997af1b46430365f9372ce76eae5a2840f6e17111aec98a45355366989dfd12d9c096252d0d23bad82ed4e63ffe27b1c65dfe2c5b1e1b31381054a6a8e6afb182c406078434b530fba6d487a1f3172af91f482112ca0ea0ab4704a2e503df8f7e97fd131528dd80618ed0ba9b89a2efa8b8d68809462e39eaed327909ee2e92105c095a211d14afceea340c455511fb24811101307734e93124fea012eaa968dbb22aac9a049f158e141b55eea6c48b7100feb1b8002aed693987aa95563fcabe8a2b011d6cdd3f3bd3bcf37f0042b67ae11fb347567e7b290ef3375887830686144113f8d308215140b977466f67a10cd142505cca490a05d8f8e20e7f68c54e3121cf1df7e4df6b3027e3e0c715842ee1b9e05cfe60819b4e6bfa7b921b7068b7496d26f4b4f4c036267db08b358441e4a28a9b985ab0b9467b82c99ff08570205f7e1147baa278a0c7b677fba0abd9410d58f630e5c7c255d890be081086b22eb8f28944d16a0c0260bc81a0db87691880515be9ab6bf44d37a276619bc5fdd48e7dd757f673a5cf90cf4e9907af1b17c0ce5723b568030776dc41671dd778f46cbb2f9524adfec0c02d951bf0373a9adea73f766d8dd4c57cbad804af4299c80acb2aef225b1aa75f84f9461c0b31b5221e7e0bccb06734106c176c0ad97970ed93920aa57ef4518e5d95c9d9ae192487b21e1e090562e47e1a1b5f80b67dd15e448547159229787468e783324fe010ea84f714044d86027a042a65df841c229365af231d46d21b14f2443e80205914f2e12029aca9b16112b5d078fe3bbaa75a453161c2e1230b7f9b20e7b2103df04abd3b558d1f00032f125d84bc525f31c2ab18ed03e699c204716820c04c1bc43e03327c5ce72beb4cf3df2bdb72b509dc771231a3b478891d0cc0222dc40c190f12b3a0cfcec8a59910861c3f946b3c1f9df74e2743bf35e64de82f430f48c97b7bdf9928c20f07e9853be5ae634b36a96d14675679748b07792fef742fc161f709abdc42c9cae71339186c9ff815fa59b97793b64d4fb27a0a745f7f2d399a4c2d0661c39a5ad7de4336e77ff28e700cb310bf8d05c41d80eead6afc4e5de501f99408ed1cba251fba45538975919b198075652f225ad7cfb64926229c53251988d30453f0635052b4c26d93b2c6d9c78cc1c81cc450870de73b4cb3805d0ca459dc8c8c4dbacc1eb0808460cfaf17d138f84c55a6620793fff76196ac8cd676e52b797fafd19fd102e95ca1006468ef7b346232dc14f14526f2d3f404dd209bee35c180a6115879de91d54618f223a793d720dd920b06df1f408acbf2d5cf1ebea080da71701ae4a718eb57bc943739576e2a5218e7a9235defc9e4225173795382596fe08f7fc8278cab647ae35822bef35bc5457915de441c48dba1ad9a3bca46c44fb8141e7ace65730f59ed4ff443644e00a273f6da8d08047ea90f65f61ac3d38498eee39087f55b9946288a6cfa2375fbc0bef0bf8cf05722285fabb40b9bc0a77da318a55d278002d1b880ffb4dce55397d7ad9bcc34098a26d941eba1a11053cc6770dfd2791211ea7598f76cdd81e5daedff56d85d47391373228c29651bcc94c47ea725c737546d9419115b11148bd8e786261be0bacb8ab80abfa99dfbb67d224af03d15987ba7664ecda4aa6a9861c9d5901d71faff704ffacc5ee41ea935d88d1b5cf0905eb75196948cfa891bce36e194eb427d7f842caa37f1c3f7566f831e2f42de987c5cd058d00fe5e3a85c2bac2f356958bbd27ff8d8e062a84dabfb0b2333aa7cc07d10a9d558e60686ad2f860dbb713269eae705581b617d2869fcfb34c7ef6de1003764febb7196abdf930980919c262b18f0c343219e2c12e2fbfce144ae31919a55e80ba5af9036198687d3279a796ddd8552d5559d83087424fa27ac0f0c369fbbb6089840deff620563b4c82ba42356820b94c2bc3b555e1636f959ae0f81032306999657fdbcafa9c87472a5daf4e9a959b4408e21b7791043517cbdc7881bbed31a2004411c27579caea7f4a6fed906ae543bfcb4ad3366ab6c85f12748ed4b2ab99885073e839e491e3543830fbdc4c282cc2db54b86b28e30e124c3a280fa5e45d6d1a151948fa35ae7857af6a8d6dd2e50ae6131414724281d1ad72c6fc63d09e09c17d41f721164b1e8e0dbf4a821c9d412800406b717deddf7540641963dcd3e06ee1d1617b630d103a7edf3ba33ec19376e213e708e5cb7e38ae0cc3a71290bb4b592528c3321c9b7b7a5590817e620dea95b06b309fd31ebd3642bc5e88448e549cff02195f414b029aa755a75872cfea0c51907ec18082105ba47c8a7619e099491156e428c3bdb689ad20786404c8a11483c3acb662b75451adebe1ecb42e8d22b542cf3ff178985db0bd1340946f421eaf19953ae8ca42d79c0be7028f629e6673d453c01446712ddb0ceb959b641e36cb3ac4a02206387b454dbd7e4f9e040f5376ccef7df186078a00944915610e73523abcce1ba50e7e478b3832c96461031792a0aff91462272c680787cc32e533ae073341f25afcd090751996e8269ca53c84dc851d59101cc56903b20e3f4b6e2c0d374cd84f68260fac567501c48a305706714abb394b7cbc59dd80947b0646648d172cd9ea510b1889a53bd45c4baf24c9859372c6d22d6404bb10f9abc778b8432a7fa19bcde5b845b68c0d7f339388e42fbd8d2cd33b0cb4299d21267eadfbb2b0e1843ed7ff2ffed75c98f1824ad50a5c18f547f4ea554c32f7512557757a13202beb71fcefea2f48ab80a572627e4882cf043f1f600817676c10697d55cea35150cd888a56bd26f23722d00be303144dbdcf8e3a56eef0ff959d398474101af2615eb4a9a0db9d96261cb1c2831f4def782c6dcaf29c51b6b6204cb283dc38883df52ba92bb7f1119ce4513e7355a3e8ae48290f4c114934ab2e4cb548f5d8875c4cce848f0df870e4894c2f6b62be3f408226c49b8422bdc688dc11a76d19e38ca3d9e2f3cc77153c0394617270be46a1aa634b31d9fd1d0876699c823cbf06f8aad2c18986eb1b8f1b914a42e36bce06b0f5a108a9d10af5c52135d70d417125b8374649e46bfb6c85cc34e93beb88117c1653a0f92d3f17cfece3765091f749a92569a9a06199bc2d5e4431c5073edc4e068bfa16c1424a0908265e9bef893eba42d1c711c771b8f71cd2b16f41636f7f293cc377d2112e5d298eec775f4dc4693c584598883ec51285145f62626e99b510b1070c56932338817cc6177cad6c445fd9e7220dbc12f9741cd11f5bbe9d1fd3cbf88c87f1c75db842524b57feeb6bf393f2ec0c18a3ec5b48a21a0f16e68debcc1f0e3352c0992c3821593b82fba8cf64e426ada7b52be782545f00b6bfd98811df975ad2312a4cfd51d43dd9bbd6dc247fcadb79d49f625be6544fcf8c9cd066ef3efe50bfb50ba33c1992ea7a405bc27c33fb8f9437b43d51d8d868f1c6ebf1ecdc5f90c26e571e592a8b30aa5945d503fc24dc0d0c910c54e6184fd6868eb75a90240c2e8aa941b99ef8bfab7926db10c2e0100204629b396d6531142bca13243268f3528244247ae09506fcc88cb9ce40e077712f46649b217bdc99cbf61cf208d0af18c9d556f4c47315fa5be92424f3c07e4c105be69b4c5558e11ed7d4a5a4d81ea86535975e289171aa895d396b3c5e22dc580b7ce43be1ca8f884a8be7a5bf57a4da1ac9567f7443de9113b5ff18f8cf47838f0ced600a4752651dcdc523f8a05c25573bae0817c33e2218b363ee70e18f39d2c44291dd62e3ee7905102678e210b196619923d4725760e39caabf00623d41595009008914cf41041052ec3b2ee23fb8680601a9c8a1440ac4156b242a7f6d0ea0bf63a23f6c72808ed5512b4ebb17976ae064157d336bb4a4ac09a742d9347a05e4efb09ac042a56ded82a236dcce3d51d79735a8031d6b6922578fb413fb2413135ed1bd42938331b4a4d633edc57238e367d19f2e8984f8661e471fa4747c458792aae47569cf6a862db09010599f27067aa1d61c6184343a9ed608ed856299dd5be349f5d969854dd35a25f83f2a2017ce832c7f4498f00c10a08700057e8adaee2eec99553edc8ee894e4fd1fcc07dee3376fbea537b372e96525dc71e8610c098d5dddc5d2ff0b93e12b8c4cb66ecf70e73079aa1c2d197c87f6c6f4939ba68337593c17a03ba11d3bff42654e78158dd04679f8f7f440bd621745c90dd19c629a788190c77fb2ae893075ca5f3585adbf0d2c0e0d15b99654a8b085cc0f9911be21800a6a329abc32da14b3dbcb97b9d96bd4f8b282a9dff613c22f4245bfab5c95cc74de6a93477f79b7e0ee2bdc4a2b52489b44ea5a23fc8ee82241c41798e2682e049f11426a5ed2f6c1d98bcfaa6870d0819a123f0b6570936571c9a276da1d0f69f065052160306adfd02e6bcc294c4a1c26441d6bd89f56d4532ad764a8fafc2caf33a52a07c8bac5385f063f460044f1fcc333fa4d07637b567e46c9c097050e61fe74038a5fda20c67359b5164a76e26b1dd6362ac556a23d7bb428e3dd75cc641bf53c9eb3979a168b6cc67162ea1b160634cb4dc499390ab176b896053dca299df33ac815ee43c1492eacc54d687de98d0a34d6960db0c9a1c480e29c4158acb606a7d1753d25c9ffeca7b953a7182637462e1a1a0630c1807b37785cb138d91186ebb94d8b326b1f5a160f59b9ecb65131138f5c83af339e78b9488c2e30a71222e079f83e1fdad87aacbac7b4e25e5bcab54e640cc90c46f3cbc18c4df4b4158f1b54828bc29ca83f0a3ddc61f47b4d7eb7bffddf7f64105192bbe4ae37407cc195006ae2d499e8d136bae7b52b51fa8aae187431c5c36c4c16a26622e5bb30fa38aea33c4a9de27ed0498fcb3b208bd0284fe1e6a486330ebc018c2daab25a7713f94f855ebacdc8f68af76f3b1b36b3124f8742c53046c0bc8169a0eeda00b0a2936ba8791c0e6cea39b5c1146ac6df618788eb0d1e575fc6e833c29d74f3b92ec1ece50514f1914e3e910e7efd8a56f965aea70ae8046a568c36e17ad3716a319c414d58949df8b1ae784a5e4852ecea072acbd13cc30766a019712fb4a0d693039c8671eb5e71cb1f2ca3dd4d0b8c71c5b121c62f10b34d1ecf668d8b4ca2ea809969e4d9f34caff8c425859ad9ebd1ec07a0f4e8bdd496bca60a07b1d3e2faa3727981a4a20f6c85a49ed63a06124f9aedbefbaf4ac9995d10288f0a276d3202cf287708a90712b3979128ebb5b1d23e52b6201be1ef737012d34020290907ada0017d1482e2fb9d0275580c065335f998f9f454695e484aaec065d3f9ac866d98fa609548cb65662001d1dd52fb46f6603579e98651dae9b48d74f6e1d482952954a35a53ea5d95926cf64391e21ad440058a5851f6689e09ad821db4d8c3b70f4201d477f3fea9bfc1eba52c8a53e31f8cb9c73eaef701295d6d36a70d0e8773fbcc5ee05d0f297fa9f770206e5412de5d96be7cc4f941dd7d3ba376ce114b68e2c348538d8640bbfac65a4253ff873f3d81aa7223c653cb777deca91e856fc2a7b1110704ef6b8e63c3f387098481ce006a144d0af5dd68dbe1d148eaae590250514279c8f00d0a0b6675a0df4a8e89438631ca5f69272ef13650bc4f889f0e8ed2767fad48aadaa5b9c9faa1c293e1805e618022ac2e5d14d34b86c9b49999960cd116a04da79c220dfa24cff2ee69996176ee6bbd2354234445967a24ab81cd31376f14b1d7c120a3ce9a1e83e9b1b78e54361af361548caa17cfdd9bcc0881105edcf2681222d946c8fcd0c84f2a1b07e6d2a509441c9fab25981102f0adb9f4d0295a6a516f0d1004aba0d099895f62bf74152f40ed9099f83ec6eba00c84537ec22fc47c90df608ff29b95d2d00123137d8c02c51e63431860d705ba7247d151cae6b224c4b6eb22f1b9125954c24ffee33552d825996c645df7ba01112e1cc4b7e21773da09ac92ee51998abff23df9e72135232282794901171d7eb05f582c97e15ee512f0edacc4a31108b32b62600a43f090442b49254532e9c3674175f8335b3d0a4b6e4021d0633141daee2efabb136f0c4d6d5cb21793623d88f6c03c4bf1eb9f9db7bce84baa4979d9cda2725665267dc422cc2b27d2f339f3e8c9e6fc1fe7a68726a949d027dfda1fa1fc5358b3c841e6e791b50cb90d0a633624f1a88829bed5253983b57dade49a1e15f44df587ae10cafdd10b6d1b9ed0321e06eef7e53357a7ec3baf5fd099af7a346b1d79c28b0f0cbfd3bc3b85564918c1eccc669dd1e411404aad31464ee364b9440f2d59b41904845857e198bff39112e242a73caac55bf04495d0b4ac115a1ffe086243291159cc86c82481f3d0e3e482b3d2c19abeedd62dc31e9b962311f3eda936e708b5b952421749ac789a3a49591ea73a125cbbcdbd24d5f84b0348edde1c16ecd5815e71bbb6379ab7bac19750f428ab439a23442a8f508bd4270c63c5705318324adbbbcb602be264b367237cd47d3b4e0793ad0d1f21f331d8334f3fb92d602b4e65bd3e06d6e64fe002139d906c9f1d95f3ffe25329bb624a3cb9d1576f5452120f76857817f4e9544beb5d19ab28a7bb1fbce5adbf605c3354e8f57fbcf007559bb570421472734552b3d4f30efd64aa1664d482cd6aab8a353c1a91217648025030fd09dc0b20102ea8f9c016ef1fa58f064cc1d7e04a05594019e6d97cb0c44f2ba43e50aa7bae2fcc41505b8038b448fde57105faad3afaa5f07b098c6640db7d5d78f4ed58f6cffa3468e6c6d7ec1a6a34d295b51f4a9d594bb38a995e84cacb7adfd9b79d32cb0e2d33dc4080cb53b6c3d432647aac7e68c43f7b1620178759c6f8c03479723a1bc11a75b3d381730553ffcb5949d57dab8cd89f22e68e84f967515fd5063375caa35281eb30e6e2be1672a7aede954bfa3294dc0b67d85e2b5c0ce03e85bb16bdc0f62ba1ee336eb8e55c0ed4180f61b1c0ebf26ad366bf9d28854496806f6d648f27d32c3e71a7b24605d42020996227c7c0e1133aeeb7dc5890a3a541723e2333587747a837d6f151009a32602df018d848fc56067b80d12cb68a4e178d4c5c0e1437908b24a6893da05d06bcd3d858f29a0db83cb7c4841e5e4fd74e54f804252226f9041e6377b2d283f06d203190c5a018f78b4bf2854bc800df5785785fe000a1b089ab6a00e58ff2967f13bf0a220ea58b6a8f0cf70a8f3404a6aa0f1ff7ff9a4524eb96841504727e0811010d14f0d06f4672807cb489d72ddbdc923b4bd5f20dd49f88df218b29e898671257e2053585810480063210d314cdfaed7f802462e1f8a9e421041fce69274ec07181f3bb33915187967840fdffa3473507d5cd62964915123a16dbf14bf558e130dc5c1ea6405ef7a495771ed385165295cad6c7d9111270ddc979f9dd9dc0a556ebc0632e806d633b79ecee03d1813ac9f2e14116bf501e83e4cda7aeb58f3da1e3b4af59cc7f2a2bc2e742225c944f3d43b8b6b73e805cd32cf8d532a4f80c2651c224cc0a2982388dbbf9e62f664891597d204b24f47ba444bb0417956af01c0339ddc64b18a9656ec22a6a329790959c10a2785ed259510f33c6f843a4021faf99d7b9b9dd96775eb2cd080e3373a9085779459260d42461bd0eb988d29ba15b5790abe4aff0abeaf5f94110b63db0b8a094639659a90cc39cac51bd6a3fe7bb0b9e7148b77a112293b81ce11c4f33c04de08dfeeeea711dd55b82e5ecc4d544370568c4449ddb9a05d27ccbf7463b9119e8ad9119c807cbd0cc8cef60ffa5cd98ece2eb1083addc56c63b4bfd856b9d8048385d1c3085fb0679da2fb0824e1daea397544aae67c59ef6af34910264361925d24e6f55c2b587cb915c9fb509832ef48639d1a524f53da3644dbf32173c1f70b018f57de551219bb83f652c2d88b8ac2e26e6feaf798230fc3be6f082da6436db1f2bf87022d8735335cb7187a2d37b70450a004b4a12bdaa8eaf6fba7c8384403c7bac80a23da90e7462aa50c0c2cd384c30aec48cdfe35a865f6e87996af43ca67fe77e7723132f3ec40449d80f87a0fa21504dd841407c4e0dc490190eed617050e5f79a6893c202650733d5c39690b6a2923cfdca9e8f694c96bfa1d70154a9fa5f921c608b0676a7e44af58dd06c6251bb73da42d830c5ec330abf281acfa7c7fc95c36a964277aa62b6c4b60cea9de9a1486ebf8f75537748319f1172ec19c8c0140980ac2856b1ee3274989bb947f80f3d5b265ba45e68a9a32aeaad5e7d681fefac047d75517864a29ed723335205de72a2037e8b3d6c5d5691e2f2d09a70b20a9479f6f673033be41a6b3840870f98f1995d4f40849ced46268feac59c926be0cca67fe2531e494de2b02609de86205d8097f05152bd1f01fb6ea367f2a267e190e29a2a8ab89d0e9e4c469f8fa17bbea5c4b92f68be7dc703092c8035d30cae60bdfa351da269eb6ac651ac5037a9a0ae7907c22d64f72e2ab6b5b8173153f97ef29b22316b4277e2d2043971faa2e2602381d7967123668b6e27ab9ecf50361a22384dc6405fc64afdfe1b0644fc58407c10b0a51dee1d0d7a65dd43dd9543b33ba18011477e5839ac5ecd4a73a86b5dbf887282060ed64af238e2599250ab993c694ef3d47e0fb93f79561251a4611c5eb89ab68d16d5083660e7a7ca6d2146549c09c0bd6bf13bf66102ccc2b96951c770d7f73d727e44972786a853ab5d0e97a38eaac548b60fd9deb1a9538320f03981109819942530d2ae6740b3149f21416f2a4a796a6e417ae57b901662f12ba9fbe11563816c2fa97f83359d4332bcb8c5a17b3e358dfe12ba1c02e94ff13b5a5ad35a0417f215482f5de2d21522ae548dd785f94b075abfef2deb3e0eddef64b7553e3ca0a0df05edd4ede25a1b65d0bbf8a4c6ba9234917e545ef14cd225aa3d5e8c88133438eab68f0aa4f046ba1b028ccf3c42a6907009f06df32ebade9ad6d53d0b7b8a5fc85d906d9583e41cec26ac310b06d830bb64fb14fa6d4eb5e5bc7aaf6859fce0b279d82539ac88cead959541dac7b967615ae587d92a738b846e3e599b0f77f8a7cd03585d468e6804fd144eeee8f2d55267debb6ab492acafe3694a7b2eca307553d8ee46c9063c7b0ea9fa0c11c484fc6bc99f3ac3b89515c8b4ad19eae175cdcc4d22b57ff779c7e85d5eb5f3b1b0de0e27ab3107aca0475da3cd8a4272bac8c2f673deaff85069cfe1c7b93315731eb0494545263fddbc6750b99e6d8b674026187f5fc073bc69ccf7ab41e9ea7096f8327cb194c2caf5c00931f1a2d41c6458321da33487138bb7a56f1797f7ac1293d8b705d209cc8f5a5f6b47a91ceb58983741f7379ba9df9c1e6f3437c848cd3065e0e81e023406c59cc612777eb5ffe1cfaccd987aa7f7ac8609ac92afff6fa740709ea4992fcf8f2929c03b35ad4ae3ffe91944f8f6f89ba183ab9492ed1d408ee199e16537c005899ee45a152e9661a1dfe11daa040ea97effcb399b2293c32f11d8adc5870fe26262ea8f9863a67317717e78180abfd682c5d3117df2e17eaa5593f218d188b3fc0b2c5114fa2cfb0721e77f6ed74b4a5e832289c03554a86a6e559cd93927b6359575a6f38927c118335da2cb45800cbe41301ccef8ca47d270ab952b53cb3af6676308687cbcbfa082468795e6290e5d1c85b5118a54c59b1dcaa6763779727e6bcb59ea564f7898549d9dd8690eb253ae8a5ae7497180a39e0fe7af09e470f5c515552b5eb3d5e492d2d528b7c0af973edf1d9b02e326deefe12050ced45c967c0c6c4e981fcd24df86c84c50148947b809141f7b57ecc8bb1fbff29d2c063c0ecc9dab6cdb0a3bcc91bbb68f27ee2214011973bc9e8673a396f001e690c8636ec92797c10e661b35a72f2db50f6b4fbcd940e8907d5f87c37f9a9ce2f9d5e2425c25c4a2b8b8f163ab848b98c3d6cc7fdf2056789001f25b5805e55a13cf912e234b51b5b39bd9a8dae66a322328330453753213043ffdb8d08224e883b243ab8f54cb06e9d9759d1e01a41b88014149165ca4d5e7f8f6d7ea50775ce73f03466e5d5468132be8d7a5566111df2308b7831bac2da30be93172bdc0fc53f0bdf1e3f6f4b1a61d3a842bd5a2ff6f985e953126a04696e0771fcf67849634e3b3d104fc287148ceb6e4aeea78aef8349cee864725548878ef0bff90821f7513ade168f02365182b77e9de2ccc4ef73fd5d5ba3519c1a62f5958c8e962ad9452725e11d4b6549b070c4e4d98061d90d789392b2ad8b773cb8dac21b37da4a39bb053f00058e425c2c39c729d6cbcca49a699524cef4caf6b8a445254835a3b9c088e68a084cdb8a3f491f441594a700adaed0477fdf279e564d539dc7a6ccc8e08d18773a12400a2d54a06b9841d205840adc27c2132a5fa16b607dc2ab15b73f8b3b505bc3101f181ccbfb51f1ab9c293b643515f6a4b42017adbb67dc1c88131272e82a48310b06632cc90dec845142dbaa446d00f445c3647a2247b9e524c0097e734c75bd5bad1424a5a0b4640449615c009750e2d0beb964457c3d8e6270d2da2e3e604057319847214c637dc0b6ca75dd8fd4fb1fe1baa7e5b40a4f192f785f1de03e2b49510fac442d710fce59c884a63afbe25a2b83d76be368b1d95622d4b6a93f5026744d72ae8a8a0a816f59417674f579dd9421a432a8535c1c66f0054eca219d36ad26a9430d16e468260dd84abc85c32a3d63b9c1d0bec08629d385f70aeed2981676d459ab9614c4662d04ba1a15ac0a0537162372729b9bbfbd618d193db75b9ac6a24b3730251d0874ff534631f901a1d6e41f62f8dc82681200548309927814c52e1a7db3e1957b51976a742a97f99f7daef6a0bcb137d385a1ff453b43d5c1c090860f63340b75a3ef79d115a35b6218d6035b98ae1c43634f40b02b64c82abdf7018f387b80c586d5286f3b2b3941aab49b3be25c14a0030be34abfcb3f672c05eafcc91fddb4c4856a693c319dff8ce271458b66f1721b966366bdb71395d19bb45beb94c24d6ca98e42a000d8055222d67e1f16e3edd03908c23c3464598db36f9777762e2e6011ee300627019b55881139147778e56bb1dcb0ae55108bfe3067d3180d1028dbc3e1502aabe17b9cc353a331f374973205a5dbdddaf24d3dfab4ee34bd6ffa0fd7c1dddf68c2bd4ba0c33f87590a61f3d2d27a352f522ac272c683bdc8ad40717eb85b1323e4175b922d63c87ad14474fc76ae93a105434af3b87c934ef3aa31e043daa55fd017f078834c18336d85e8953fbcf06f4280d198466750ff42176a7a55c02c06cb75c78efbc63830af48d9567526262fb46ca3e945b3807203d9030c2bbcc799ba5901312c86e1cce0303232311f0b3d4422b5577f231de85982c55fb72db2188226c19b8014d1989d103c266fa6663b163f1a45f7847838698fbbcb9286a349a8f5cccf0464ff4724b9f18b9cc1fa487066527ea87d93c4827cf0c2be509d460dc212f224da2026f06e65e5a5ffaf5a334428dcd1db44d805bec5f1e88270028eb20f5e08a129fcaf9ee08302372b69fae3e8c6cbb89599f114857d995b96282321da8810477e73d3de6e9dd5a6009f1cff9769e31a3ddeedd191f9bb9f35590e3eb8ed7a65da4bc3ab0f542c604883e89eba37f786bd1b5fd9016792e7bfa1ccca4973f3cad23b9e8f112d71083119cb26553f7de9dfc3dc2fe0301246ba567c74fcadc284e86f01ed5100e01152f6ab7612875474a0accc0ceca5c5607e1a196d321de78d3d19bc23ae03ef336f3a8eb8cb848062d5b6519f4aad507d2093a52703097fbb1c9275c23e608e94968a114d1ff888969a3e9447ee348c955208e894a2d2a8df87dc57b28534816f0a78259229ad2c01bd7b07618d232bcd77583a972501ee5dae4e195915f1f27b9cc642db64dbf76fe784269e32b43ae0f44535af80760ae9cb37ab8ec12a82db2afb29cd8b8893adef5b3277278926836d6746126322026c411c4c845a2a6dd588c09584d8885074e387c7dfb3251e7afbd170530f939b5e463332ab1876ec7a285a04801d5a3772a8c5c2b8e2acd78a3165ab724f0491285e08dd4ad16e618a8061bda2cf28e08e7d596523de51515d596048112030c6483d3fbe51f7113c7da2a00848c6759a3299ac70cb3d9f7eed158193b16359a35f7fda5cb96332b5dfd9fb3575ed88503179c02753bb83caaa55dd442e3bdc24ba78d300a8a4638914b829c2613a061ebaa1144d9da4778fa371baa4067ad75c0e03551d392739e354f5b9172ee105d491f052019a25c8dd642af9fec26b1c326e621ce1393bacd6368e27e4aacb7673d828dc07d9e90a0941c2479f48c345046902766e0477dae13116b5d732477682536001d625c187ecea9be6d4a0af83700bf6411853779678047123ecbf0549b877ed50eae3c796df2b190b6d56032224dbadf3575ef5595abffeb80e3397798ef13710a2fc221012cf888019a183a4e5203ce4dca00bf218ac7498a47edb1b1f1a0b3a29f374e6994278d1c7d9259c926891ed6a8e610357316c73e169b98230dbd91167c7fe03d1a1bfeebf79673277a8f9c1ec73761f32c50967f0f873a02f0535c18f3ef0b616ed1bae5a53fba536dab682c64f984d59f03926540ce2949dcb54fd5da8f0eaf1ad4e16c918a75f54169c12a5c3e5160f07b2907c2b6796a3f427c4d67cd94e94f05f34793b9c32dc5a694efe359c0136360a9a332e5eb290ba3d995068ccc2d62407fe1651890b505c5573decd5ce0c846c6db78ea660edc7d734ec48673c66e743847b1f9af60aefe18ceda847beb303d66f71a4d7d661e60110fc61fe335f265ccbb865ae2ad4058220481091f70d500af257f0d6c6924d294926f660b49517093d028a5e45339165ecf56a9f1c9f4fc967211b0bfc8d87591409e5274537f31e0fc6e06303933441011938cda8fbbd5bb4816eea0412e948512c6436892705502b2c01d73b9e3419fd8cf4f71c312b8b4d38cdee15e950c74aefd06519bd5169cb3edd88b81a5e8892ba978e1efac28018c7c20496ad6b807ef760cc026ae1a96434ebb634ea3b221009ac16e43434ffabee711aec80c06124eb7628eaf997d189e7abfbe683a60b63b458a7647c300fbbdfffa49021d9c480b7b80f2ef4f06d167891191ae5b8272ce0a5b0c741a2d402a2d3a723a619d7ec3c6d8c53321ff0db9b65efe55bc0a8f68bf75c4a1296f1d524474db97f08b2b4b7587111f16aacbe484d9788aa7a08dafe9fb2b49386a708c5bffb247cc070050391825f199b14b9fc535f23c30e37d2ea78ba816cc66230495ab57bb6c423d30be1a6645fa9efd5adc03e21d062d793469d82a9ef3167a09c9047ba8b2c04d957f08657e12378fba487f65fbd82df5787998d3243979f86fd1b75f27f2e044218a6a51a8f2af576cfc6766d3d3b41a384771b98736d691c94710cd66ca8c231f4c8ef139d5525d4fe757543682c8b4b0b501387d1227dc5ef736d8c2a64b0ed5512dd759ccb40615c62f85ae35a08567ec5a0b9a018b48304fe1ff88ad59b5bad600c6c8709a8f80935e9a238040ffd6ade414c719ef1c236db50284ef198497f1fcb6a6ae746b33685215d32233da666af1467e721609704a9be00880f08e66bde3085cdaac1262044f2b4edde21623c0752bfafc32496777da4e7086ac7238973876cad51401b918159ee8e44c874cbec2ab52153b7d22c01ff9cf662fb51ff8e11c8a79b79b03c1847e91de32770f48b608041d2e28964630b7fc19665d0e4f1dbfab8abc3a0504e227d01b5800ecb40ed8b9a4027100878e3a616298420c30b464cd9cc34630e9e09fb0dd123fab2b0e40868d195cca3ab7b7ca1c08a8d528154e54052eab52b1693dba1cebdf1d91fd63349c953b3c9f3af6beaccb809be2fea9fa4fc168d49aa134d855b86b5f0573f3ebe60b071201217a55e5cb9746accbc825df62d9c357fb54c704574b8e6f7d5078024cd574757cf6e591a8d6ac2afd08904791b9ffb83b28a326866650100f4de6c903faf901d2c00f44bf45571016f2d4ef1d6bf32776517423f53ef5d38d0ce126c4dae7a2482855db5ab5fcdf2a4ef947acedeba1898e4d59d5a346ee3078a2e63c655c3966255406060f4e0d9adab6347da5b66f3bb0bb8823c9b9e75ecc89dc69bc1755d1ce111932c1fa0ae2d6207f2a3aee4ba42d541af1544af3690ddc05d526df2a5611f3e752ec2d46c769fe43d960efb01a494006006df5af45d4bc0edaddc5ab65dadc21e1c68edbef2e3e6535284a89394862863486e1194cc618ccb6e0c5404ebefc02ba67b6fcbb1f4cd4dadc6b197418aff0daedb5fdefc3452a0255403163c123d58073449a9f39b5e81498eabe6939d7c58da7cb7179587fc22e503c3eb8598c9d6c1ebedbdee78d7c623b9401b190bbab8b23621bdba34f611757ecb57812fe56e47a63e7f805b2487323ec4c544df37963d384e6a61646bd91f771d7766d6d2159552b804e76e6d6ed5ff4273212bcb1c2044336229c16e99a7212bd3cffe0ad8e1e3b47c2d3cd55e8bc74c9c78df84a990c37699ac245d4f77f93734b9b4b17e183669c6c751ac48d67ecaadc0415944012bee20e4458f03f3fd151abcb09e2be021b341c9e1faf67d5fad7cd787d6e463f85b4141b3a3581fbfb2a56a31b0b392c9fc3d71e125aa8dad8eed0f0a51757bce272a514accfd0c80f6c004e691de05c1ad29fab598273f6cc41e41eb58608945346c88bd900412594ef3b402a79e11d47e8645368353d58ecb6b06d0d78fb9d5d7fae9248df9ed55702aa325b153a58c8347bbe9e1bf3fda6921f199ea4eec4dff01097b0d1fb8da2d95099e15b22bd1a0c29a041f02de1221a2524c1d4ccf5f2ee29f04bb8ec95781e4b38f53bbb62a9c5a2198c1b399bbea76035677054409770007d25987fe3660f350fcdb3b2acb4da6b7ecbc909f429aa848bad737575e743e8d59aad845a36e223e5b079f9c63060250a54c5371c2626fc2073da4d09e743e3c236977ca8983e53224b5e1cc2c3a7eac235fc44c0527147c78402ec3ac00c3139ef5b3ea039319f6dc39af37423dc14def9ab78448ac7ed8eb71aa562989024462b221819e7e8229167c8c4882f3b0c8ab5ffaa249ec3080cd84f888c3bcd334e61785349b9cb2592e6d55516ca686575098480984cb4193f4c7707657b91debb04b0e8fd5c083e467eb42489d2c4125572328b34d8dcc0c52c3004d56060194237f082750114f5ed09073056b80e7e4f60ba6f13da7e143ecf07b1a738486592f7b8bd01b60c7b7cfc163bafae1c19dcd009b7e2f91339d03727ff0d301220b5e62e6d5c385bee0f5024a372428e77b83a18071a307d4afbb54ff2c71ba5289565e57b66850751f40638c02689b284b176717a1606d6ddc55786a3e019e2db50fb52a91f7cf8ff72a6637eea7537ef5d063ebd610ec4b7f12c448e4351693f2854d2aee080308c9d3018a3f931a9d89210e89c1d8233dc628f2542b96f25597f5ea8f05cbd907978c21cb17d3135bd5a47eb8c66602ad80d5df20f48a2d308ecacce01635ee5077e0163f160651bb7cf322ea3f699a9c910647305e965082780787a9bce3d0820053f721190bcb436e034169c228cfdf65762bb4403d8ddadc91e65fff61443ebde3113f54bf8f998586d403727c76cc12138bd32a76ddaee4afbceee5cdea89d15659d3413466231f17431f334f5ede59f18a2e9110a51dde29b270075d8d38ed261c177b8032c9a7595e098dd30f84ea8963c2aed9592583b7e7c445b6a539e1a66bdac1d776ef0acd219d0e36e2d21619113df0872b9fcbda8f7fa4c465fc2536b903e9138ae9cd782c668430b48f781168cf5ca97633f30c57b51e6e73e6b2ef33b3b893dcd6ab0ee0e3492896b04fffb6c7ff9b831ff6100289611e5980016ed1d94ac769dfb62099714ab232db001ae6f871f4214cb9d190ae080fc2f8d2261f2cc451027a2808d105189d7d0d4dd0d37dca7f262c1a9c30d37f58bf104cb37c87a900f42842962b8419f1bb001f4a85d2ac06b14cad7f8b3ea2cd2766c5aeec50cfe012ab5c486758139ec733a5d519764ed56dc2d3a4db667042b72ed9e8a252c3749a907843b131e283d490d80823c1ff8e47efc14e804584d2cc107b809d05f15e54865ffb799a9bc43ab676050af0e55dabc7f0e1b9efa1e8cad474f601ccb9ba6e73ee9333258fafb07ecbd98b06d5821f194cdaafc2ac72deefe7f9d3f27eb8e96f4301856c831e37f4b2c24dbe7dbecebaf165fd44d1a91cb585689a2ef9c22dd62196c2d7ccbd1c3dd2b8022aa628862c4269b47c40074f9ffb7064c618beb2c506ad9d61a50bcd9f252b86512b0193559652f02096764fd722903e0550aaf6521f28801096a670e820a6ca1dcdcf7cbd2b551d4e0cee7057081e28fd85413575a8124a1f5b64dcdc590f263aa45d1e0e97bae6ddecd347c1304d7cb019d706866e11667b2aedc4401df239ddbc0fcb0ed444ced2adb96a9f3fcbffcb3e96526a5e46c4a2325c8575a88be84c1a9110eab0af7699d78a0f5a87fb1a6c188c6bd5157803bba2e622f929eb7c004bd4bd4cd7902e6e6488820244f3f9096c4e16f532b7805fac98ce036c12836ab41887a77795afe9ba2366acc117012acb92094c7d11d3f50e91cddc1a088f81caeb3798222831a48ad7501aed11d4f3a419f96440fc399cfc37115903033ec3b8cead43dcd43bd3aae596a2eaebf1a687d7841ffcff64802f780c8cf827f22cd897cf53ad99330b7bdab927bd050fd224783c51f7d9b03c0e9ab0443a522d40352e3f7e7dca443639928c9d4d8606d51a4a303a910cd2b3556acdd0844a3f2abe982e18638c203448a9f555912f0b765570589d666ac5825eb274b9eb60d5ee6029e3331bfa8e952fecaee1fb64bb9c06887a12b9eb04beca9785d28c2c7f5c50f9a6a5cef6aae13e3285d96a46c32fe0014deac1adb53a2b611616cffe3cad07f39cdf0ae47ffb84d8976d8eb9de338712ff70f381b7ba3360559a964ab60b43754b8c4db2033d23183e9fa90344a86d827a019595bc825daba0a46f7b1c1ca46aaf6b08c97a3ab4b245bc36727bfc19fcc603d15818e80522304014c828e298bb0d06f66d691ecfe695de346466d180e82814a7a132481f2ccfdc20888b40661d5a5eb1752b487e3b1dee218ac0f2c0de46bbf07b6548e784065ca0bd61ba823873dd576b8187ddea0e6fdb1b2f9474a1925cf39249048258ad102cd7e6710733327c7b45b5694a0248fc83a2479e49eb65c0c4eb08bdb9f7292f420f543a5c7c13e306059bbe7cb39005198bf3b5cb40a86d6cc18e94682a57c3398671d83681809e5c72b3a24620762a751056b57c68c2a19f3b86ce77bf6aa5e34b5fa1a3a7ed2e7de89aecb661366fd949f804153e90bbdbc42d4fec58010da89300f015e3d40b942a7568a02b2c2f71d1fdbfa5eaca21d394ffb0a6d30c0a3351b22ddc3930e5264b97da2327907f9e3b40173b3f6ff181976c423f51e5093f1d708b0fa38c00c9a7586b8060db19060cb44719d1d04439f3f916b6d989a1640312f04483dd3f0b11a57c63182c3eb19b202a879567736aefa011d6477de3e6d6c26771cfe8639307607a35ac16dc760851b9997063af920d7c8ebd0d6299c3db6393fc0873df62160f7c5a174a0d3e209a2998c33fd212309d5a8f0268d9ec5e39d5d00784a0f3e30e0883bbefcecbf99e7c4c345001ac0e3e5486bba6e1e4a0694507647c69d49f689328e47a52c03922ea7d096b2403e467fe6df269897d97308c1178c5bc447063489656e6f5138e3be8afa6688905200e586d77685b20682d20c9062a2982ed3aee4f12ad8e2174070e04cf8fa9cda67a6ce10783b578ed3b215f92fde8a65272c03e54dd61c1d386828e65faa906a521cd851d08044a65e5a0ac10e39861aa3979204803326f1e7d553c990b49ca5dd2828325c72f56e14ae6af62b25d2c787ce3c8e8736c6ac2eb027c2379dd4054c3cecd8faa75ba5bbdc843d49076483e5d1c5b6c5bcad0ab47c7bc56af2a3ee5733e266ea86ca916621a660b1cac85450b78c9354ac54b4428c4c96b54792934013e870b29c67ee612e5c4c40769dc2ab2122bfc4a9440780644a7109f4a98e1e29212fbdcc078f543236d45ae8e4d6c45d929906b4eb181b27b029e01837c881447ecce5bb65ab9ae8b6e508850fc712e98bdd1f0bd10ccb32e8894bba609eabebe579133686c11326d03cb31ca24e7e2c374fe123607ddfa974483928fb483d28018e03117c8b00111d02196a88fef885a5b6040e37f8765790ce63dcba31f5e73f94f5c4165b0c335480683bc8e32d73300df80d12b21b13aec5712be78c82c431d8810e0c75ed13de953c7478e56f82f4761c9962884d32ae73e48a772571dc3d4adec529f71b31f7c9c0814ca2c7ff06f4475a8eaba24a3a660e1a7c8af694bf03ca3445cad7036e3e57e317929a8b0153073c2129a4912f8a5e0bd9eccccc4521c924f1197b280dd035874850894edc2dc43f94ea764517543a75431a137b97f9b5aeb49f3bd213758ebae6bd81820174cfa2e472e0a1a4a444d4b307fbf3f9795dc0898f6f35241fcfe486bb681a3e895a88e45302822badfe1b337b603968506bd66a5dcaee1e65d368462ec41a39a6941f4516e20c3efa31c6fc5e14102597e6da980f083f7ea6e1da1f781e65f52d64102ed706190006cdff9d23568c88e122821a669af61fc436adf92acf4bcfe25d0f6bfeaf8057c355cd4165ed311588adf3895bca1425d16273e9bc4d1ca8ca8815b2b9fe4de990caea67a07478870fdc4c5712ea4e504a6834afaa1e06287c623db558eaa142c528196f11a2e27f088485fadc5d9fb2796948084865afbab4816814895e16a617e08aec99ff934658fab810b898adf219727911feea9d1c16ccf0c01d662a7ed17bca6139555833f43c892f705137bd8398130ba5856cefca49f953ffd82bce369123d85531a53d2ed7cf68d8e007ae7c6f2bbda105b93043d9c061ffe39c60c6ecd713cd1bea46fe67efa52b45008d23f436272077a425f8bb5f710021d5c04696dcd3b3d298c75b71df98477c47b6c23e9b327d81421037d3d603951e6924ccd5a79d13dc09efb2009c9e0b4ba3258123444de3952ace15beb6ec467fd1ab5cda3f6fdd86e554e7732d8660677511ef8820fd31132189805ac0cae68305dd50939455096690af06d213ae9e1cb7ab5827c04896b42dac2295ae36890de60307c840343de7512c1565fbf9ee438c7864e9c6698cf8a8855f636ae3223f0bf0f4b3c964bae1c861a3417c21aec5a4295340d2f5082da1fd07b6ccb338234b20c5f863f32365d802b9246d4c26924a0b0a71bfb33bd1dc7b9d4c62f99c74082f87af496c59f2c0d0461ce36008ef7e065645c1b256ab9f9def840da435bad03152a16094ac6d51565535a22d2a1e297e27da2d82718e6c94168c134d7cf4c0621fcb2a438bbadf0038eaa21655443e6107fe42c87864498733a7f7022e8e298fe41a3e46d9807566130d25fa7e0050e6cac18f4c6b46e512ddbf2cbc8e744f66243069795ede915021ed33d06b4902265ffc6617f00891bec7b455beb0f27a50ae9f279a6b026939c3a5cab8db15251ebed50cb05235c4e09277e182808f68e59e03cd0262a67b2eaa5e30801e6babd33dab5c3507b9869795f4df4240ceca05fedc5576d133c464535ec461e88762ca95e60b0e4a8b6daaf78cb44db546378bc07212bd27858207822ac7a9ee1f3897a3cc8319d59dc192f9ddd16f6ca79f8872f989588b6dd02159a7b99194f27582fcbc4f4c3a14051d8919e35e2923545a03f2382b3f0324e6dd3a7acb067f4048f1281d080ed755cff33601e6ce5551d22d826791a1ae7fe359a375ddb5f53c1437d1fe3048ec125a4f45d3f1c402b9d9aba4d85dc52a094492a45133799f3653431f047cd82c7c98d0bb17dee14bfe8545f516a5976e0f5a724570c164657b6b1002d519eb8867cbba6d5f0a98605db90537527cfc673d31ab4162c0f01c8f6dc28e1ffe876177954b9d26107bbd2b13b131ae8d3b1a9be02737cc8a647bf384ca6ae8b39014fc7b6fec3dc9055ef6465e180ad7beef0faa580b73d44a348b32f4890a5c6cb58258d25124e5b0c096d5f5d5ba034e99716d40cc7336ae48802df85d90c5d8717b4db64512ae8818ce6f89d42018e5a20f5126e222a77b83bc970973ab20a1ee4486731e3a8b4366f4462d332838de5a8d37a7cff3e5ccf85318de5b3473c102a840baa044ebdfe911e820d035d2f82d356f4fee2dc7b80e975c3dd8f1515d4581699491fe836cfcc6b9148fea733e48e20d38b18a0149f3c3370ffcf123f12a229e15bdd60ec34486d9ee853022cd661d429effc41e2aee43c6ee8cea7043820f7ffe8376bcac18644e879260259039e81a125cfef4960fed0f934e91c97b837d45a21c5a6548891f6a1cdb6c11ed67396fbf326260dbe1a85fa25c25f0d58fd80bfdad313f2748c522c87deff74f84350730f1a38e544acb39598287c1b3a98f2d649af7bae6d35a878d431c6286d119a302b69d56127e3ab55952df8f2c12d2b4197193ceedd1b52de461d3670030343fdff06ef0f07a674b7f34b3e0ab3da2b17db2ef26988c3902c0993bb931f6f46e6ef80234d9a121e30e807d2ec6968c7abeee64b7174d984b0609ec73aa34b4f59fd6482f9512ff368e5d7ae397ba56f833ce5feac84e0711fabee5d5f8a6308335b2edb9000cd5cf2af0d013cacc1486e86e2e9fd95670ca066acebb9c7631bafb44c9bbb03a3effc61ff48edead378176f0587f041a8f36b087af2509b85e7544cb73ccc08eef84c2feba8f77ca63809ff000642c5788a3b2a7e5280416f78caa6d115b4f475cc9afcc8c3e3e42fc3f551af73dde766699f54f76e708e1f87388ac90f7da073816397970e7dcee581a2713594f9f74a31587dc2db679569f295cd02cedddab0095aa77f78b0d89005415b80e510d3d70f81fc4cacc8032428ea12661881333a417f17b389f72307029f9416c94499b921f45ac406bfbea074a1ae89289fe517cfcb5928607d26804382066e82a434f7ad6678df121b9ee5829dc4aadfa03a08c61474e96da94e6c18631c9727b22301fb79dedae8b2136d75e6a0f19de4956d629c543185d074960a102d0f9d2cc671d5151975f3c1b3ea04406966a6ff2cb8939afe5948fa865dfa50f63172a5c2534c22e4bed368259f315e8296a00ad0202aae0de42e1b864a918a3df16ff8d60dfd52ed2dc9de376a916b34973e3a7486391d942b0486eebf7865214528f99146784621a4b6e19586f8c510c0eb252ea0689086f602033947ff11bc743c66e67714b64a186c8d31185b3c9b6f9f7e4c545dae52c52683a59e11ac6430ec9c42ea2dcacb84d984e46b16cc474c5d57e11644d5e307b0f9f06f2e0560fbe91ef008d27a671a79a0fec3eae9bb943e6c35fb65245800c197891a19cc2078e09027299d6215aadfff342d05a5524d058ba7188b41daf8c58b51b049399597495fb66081785d2b9c574c507a8082eae4f7daa303c07963c93cb022221652d765078dca376103a3814c370827fd1b9103a71c3b0806375347af2c6d04a67cb77e3a2bf4396b483f02e04a821f9d5f4e180e9f8f5f881e8236ca77586e66d3607643887ae2b7a9a44aa117c742f8a80eff15f42eec3ac081e792582f57cfb0ed6b58732a0fb72d3adf030cad87881ca2bdd175673265e5c4dd73267947d40567b8c5fff7fedf60991a21330014ddd78b8909104d25e091bedfdcda8f218e4362e1d14609079af167f465c278f28a5238e53d7cce60d47a5dbbcefc35502d879718e1b3e5e8b2df25d9bfe61d7a69ef99ba65e11a914fbd9cfa66dd2741ccf68989e3a80957440d27c4dcff5ba39c898c2e9b130cef1ebab17b7de78f3c417e154951b13bd2960dc1b67ef2243c4e5c232e6ca3321eeb50d0480244315724b3ca3744ea42d50dfd74430de42b8fb0c1cbe724a21bef8bab9638b62cc74d8a0fcdab60bebaa38ea42de741fee961807e76dabf587980b49d366c58b56e7d116faa968c58acd35a539be10c5f6d9695b541a5fd8e72d3b3a0b12195345d10c0b9545d68eb65795a813b885ca08649518f061e947aebadb8f5a30badd8ef583a0182b4162d8af49eefdad20f844e7a3b84df47b6089389c0ff416d8ea841d71d940470726054ce54c1f9f1212683cdfcb0e0567b5b1d910cad2229ea6f98264652832af2b51a415c655ecfc192ff707c209ecdca8809065cf95ee6b47cd8425970809e0f766dcb4716653a965dcf6bf6a863cd24b45888b6ae2b5e2cdc468a0300803fe0d00ba92465ccb46e5e48404762741cf7938f3ed54323e0a044a4e77d1c8d332cfb2d0d2a67d6404461e80579a8e2f8d61dda0842f95422a013c2fb16f95f308799caa5d8d25259a52133b2233178fb11f5159923f488ad18ea01ab6814316a9c83fee5277f6503c8adfde9521ddc6308ca541ba52e7a63294ce11059e2b5982f59a41e2773c811bcaba42a8a004fdecd49037b98005a1a42943c1701391394e4c3e2dfb946ca990b6a96f379cd6887ef267bc0b356e2d288b2093f29f46867f1ab55a0836fbdaaacc8539cc69e00424953c0a7d0829889f231f3cdb0aff35a71b1fed748b8d80e0b4a91780262345ee9eee36a907bfd87053484c710704a084c22eb7534c419b3521797f4c8196c974b3f1d4af18eff705dc7e7ae503ae230cc322a70de89ec88022da50f497b77d253f460bd4844c4aea7039e8c5b03840d3cba2ff0a15c19effaafa3027fd93cd7b3b08ffadb13e4dea1e4e9bf43625f44ebe0ce2c9df6d78bb0ef894b6599e9edca69806e55fac2cac6185e3395d3e067be26a97b6a758851d722c0867823f5b115db31b8850e74f4218e3f53b620b705c38aad973905d0730ff8ddabde60b8c646a54a353dfb8499dda0659a2df7d1623e5592a8129082e1c48f523f2212389ff8cb38ae081332f15e0ffb83096eb71e10797a0ecc1a0fb0bc182a1c81b4b3418cefe6b9d466b39c069eee8705d761370051a1682074a668eee7fe370f230856a0f330e04747c858f929686e3410e9a3700e5cbb9246e5c6eedb7334b319cc76d5d30c8ded6b626d27aec214b364eaa8fbd32846601acdc51e3ff84f3283a0abb315b66173d737314c200f8805ec202eb5a66073e0336d58eaac8dcd308ace2eafc5f50a6dd899358073f2bc4f91fb2310d0e1b58ee435177fce238f16f6e69f15cf23a7c30892aa8547624b14f9a120c00fe7bc1f7505255d92a65cfb1e9b00d522214cc32b7f0b14355125f92d21ee0d6f0f65da33c320cf23a082ca2e3dc39264eaf7f3b6dc45004c686fda8be58c0e1209e6247ff32979f5e738e4214cfec5a478a6e5a92c8c436c60174842eb08dca07b14001287d344b1c96c51e34309610a003036c88c8b97605884cba58ed33e4bf70d1131803d1137437da762ca3ad9d9b14efec02c2b2d38000b36e23faa6104d512de939588fbad7e5ad84748736852ad024a6a9ea6c187ed77bb80a6c6aa095e27f5fcf2e5314198901428fcda430f4c57c1965e89a53ee37370288fbb97b15f4fe4087a4a715d274dc720bbea41691942b8f1c9841a8dca7b4d55e9bf3899f5e44826d30a10e58997498ecb2e45ad32b794740505618a0fa703e5962c6684bd4c6513cd485cb6868c7cd8fc651ee6c8a22adb0b3ec99677b8ded06353e7e9917a00d4b16d5c2e2daf1584ef449cfbd15839860583a501494bd779e935f370cbb40b676e5b097565d344349efa4773ef2f75d1c6de71fe142d294ad21130e88c90ce195a2a0e096608056e3f72e253067d6edace99352a3953bb3640a10ae66cee7c6230016b59468840627a9d7dbf8e03cacd28c0054e3a2a008c417363e4ee0300079265d3bfc41539e9c17d1df4c3bb9ea50eb20a6a5a116a6e5de29d5c9c9ba67a620a38745ebd2fdbfad4479b878591a35bdc65f7cba1497a7b12a7409789f3a4ce32b9ae4c8b34243f49569b03dcbceabb5ea482f4f1cdaa65d4c1ceb349873a1e2165e0633e1a335f4128b12c8ede6c95303daecee286c0856935a7414ab4465ac337c404479c3b3f0b02511e0468033256a9e51021b2f9c6bf3b05a443e608be58fb9456c42e20e4c4be5b46d7187f6d2120ec1ba28d8d5dac56356bba95b431c5565ef325af6c558d18cd8d68292ccbca72914695071bf6929c458d866272da1df54fc0b5420735404ddc50193577b3759456b6e81a81cbf64948e498de5db5b27799365b226392d754d10652a86c44aa807ace821334cbbb117f4e8973e4496f66f672dc630e9ac74c34bc237bee07128bf4c72148f36d22f86db456fac2bdd8cafbb533c280fc80e586f30f99d147562078c47ce2f03b4f20e4be597fe4084eaa8e320291c17a9be6c564df1c1e19ff161b334abb592358cd42f8873f52bf0f2c62aaf41d919c6bedc527a8123628faabaa008598da32d798157ead0b5103c7fae5f11d076fae426e512a66053fa3c82487ec3fd087788fbd231386e8a4b1115e0bfea24660be26cc64038c9263bb34fe45877dfc1e71da8a07fceacf8f688e5b377f508f1ef853be18d2be5bf4abfccdbc0ed5e54cf8e7f3e89eb2182d5b272b23f1931a2b73532c8e1825aa5606a072457a48f848460bd3ce9268524d793545fe08ce0cbe47ebecc1cefff61993707cc8b7937bb88667cbd7db43971adcc2aba56636599f8876ba5c07434f939d844138f5ae9002a0f01f029969508f19b0c795f33afa027acb53d0a56ca3728bdb96b689655c798b22a655fc866c1f3532275310cec5ab7469526f1500989314122ac00e7a297ef80f266d59835d093e34ac27cf618b1ef1cb4a9f729758a96560dd995d9849dbd428c97af653756775169340777bc5fb496058219e87860884dc342dbb2a3c8d8a2644e23bbbd4cf66de020a761c717bac7f67a0dc4747dd97363d62ce5972cba676752378eb05ff8e64e189b96a3d97136e946efc7613bfe2d1f3198d30be6b5f9418e22670dbbd40dd334f2d8e31f8ec0fabd8e1178c5980c64d4d15e5d59b60566f88cab9d2dd51025dbf9a1602c3be5154bafa6e820ec06d0082ce035f7f556a67ab599f2523ceeb2fc55a9bbfc181e03640704da928a8059305656a503016676f00f7686e37c00c5ca32438a933bffe3d4aeed85014d7bd040951a2d42b451a5fbe239ba5b9f2b613a2c0a086640bca012b48452ff8868f3c17f0e0af937fb5a3a14354e8d2f09d97bef2da59452a69402c207b707280831cf97bf6b4eafb172326919d7d59b9b8f2e593a07bd72bb0181fe8898f20fd0f9f6197ab54344007d3b0dfb0425b72e086686265680011ec264392cc8df1a9d79cb4f5c3763e23a55462f6eb995a1032f7d83fca47085e91186a9000014549c5e6ea90a0088a2e2f4c7c7715cc3e32207a49dbd06208fcb7220a1f20862feed746ce5ee7b4c69addd7a0b30c07678c8d497eba44b293d569f1cf4c8c19bd4f60e638f43f5aa61061f7bb764e61bc085bb9043edee2e8470771709ab1083bb6fbe587daea7de83915b4fea622749173b3fbf5decf0fcbaf6db859033fefaed4248d2b3672e5a0d8c51ca49b3497963522303fcb864755c186f5ad5e8db5dde4ea78cb754d570f096b71a35babb9b99512c2c376ef08d686ab191137846f1c6c2db0d1c2c3870e040d9a88103c74ac58103c7098749c38103c700ae305e1bf1148c9b79b79beb1dbf1d13a1a43003aabbbc946674c9529a69359a5683aaad624f1951e92e6f5d396d77bfd7af5fbfe55d790206bb290d58aabbdfebd7ef08c358abb98470b0cc1903f50ac528b51a54abd6bb6b6a535b748a8b0a535cd42efb1daa292eb800eb340d9f50edb2d40d529b7252b9f7d62baac77befbdf7fabdf71eb42cad06fa7bdbbbde6b21b06d0cc33818e1c2850b1742b80b17a68ca81ba38492c20ca8ce99eaa2524a33ba24f5f3d0535dd48e6629232a866529232a9665a92dea7aa6c5a0fd42586b8c35e529232a46218cb0d695951a357677771746086b0cc1b8f9bd97d2a2b6c3a1a221a25fa2a12643301852c156854889880a9114a2329e9dae0e1ea2783185c80ba45f222fa0fc2f911760fcf54be4851753888a90a0d05fa2221db451ab96bb21b20121269750078c64d090293732e89fe714f178b1c4442cb192fd816808e9bb8c6828caf33685de655e008d1c210f41c6c8d49f43486de0c7be3f27e4c9cc3d39a58412be252b56caa7d0bb678acbbd3c305f138d906a5bb661dbb5c1d511dd929bc60f0a6c73b0e68c31ce6dfe906e39d56ca65b5cf72005d241872eb51fcfa7cfe85a8d8c6750082195ce3ee99c51b3d1d6a5ac8930c22aa92d5cb8308cbb1caa30a4f25f7e394d439e7b6dfcf4dbea5684587941e54106ed12d56879b87cd46430c05f590811f3ccb50899513b01e808bd00745cfe329297116d915c165d088a69c4a49f9df9154dd1637adbd7b6e823661eb51a8b99ee93793e989d9fb90e874ff9e88d5f53415d1feb43ba4728314a3c6abf8131cb472109a43409687ee282c0dcc451edf28ce3adb6ea867a394d2bc19ca6236b94a3fe60e6a40f663ab6d54ec09c4d331deb2dc6217607bf371023cac78f4453668845f3066120fd7407cc2dfa19a7d8fc186d2ba0de5a4dbb6835d3a555d39999d4f6e9343d8956c77c40afba4844e3d367e855fb741a7a45e3f4d5ac6d868d86564d9a56996850a7cf98f49a3ac1c3cbcd4b1110f2f00244c48942001157d3a3a8d2a1bf9739f59b9957410475fa66acd900043f731c0f332f3920d02397b5ea8893641fecec98cf19bd7dc600e99c5489d52c83121ef7d132102ab13c6e1c6c1898ead5e4b85321e09cd9cf08eba2df5c422a0ca91c0f97d0430ec8e32890e63ee084e80ca5ef68cac55fe43a7466396b3c39e5a473ce07e18565fbe08b3166d93e2d5b0cc6c8bd971fe77720f8c8d5d57131c6615716e4a1e5432a83927a0724a5d4a7a42694dc5a26cf4cc73c1574b9d198f2211d3bc96d52e926e9d22daaddc0539b493db1fb44e5187a453b01841042d82f430969182da63d9f8431c2d74c2a3b7c961985f0410821f44d4218a9430851281b33ef3dd40e360321dd94092a3bbfeb6344095971a227ca775d0fcb349349cbb0cb0939237c74cae8f0e90d6b3b2071f7d20c4b446feb31b33403b7201fc0e9e1bd37b9986305c2b5dea57afe84707e2c26086360b61c16ea0665d12963a64578bd29d02923d44c0fc21815846cd129afe704b66c9958dec6d289e5c65273b33cc812513586a785acaabdcb56ad134453d2031af1c529e37ccf12b2d23ad2e37b906339e58b904e592556790fabc42ab04aacc21fabf0dc58070821bb7adc4dab76e855454a610d96540e6a495fcd86fbe796082a0f6649e5a82bd45aa1488592279440dda0e89430b2a8607c4be5d0599de521cb2d53a1e49b8fa031258c8fc5284867895472bace936123a3d6316a1da3d63172231decd96875b4dbd0011b9d588258a0c0723e8328961b5d8c0ac6b7548e952039419692cbf9c8372bf1ed3cc483aee71b38321c6fe36964d91720fcb9e580c30574a041f6193d666876c468a21c0676d8827c605790c3e91b5a0f38f828d5bc7d9922ca06cbf2a8b1c223fa6e2c28ed4705a6bf6933fd4d2c47b6d6afa624f560503a2a9c9e591e6b71a142314a5b8e196b0594f603080a65a306cbcaf6622cb7b64a8fb80a57ae3769a715867c7ba7fad649e5d89518ad860ae4e828e503f88e8306c0414b008911c3f5930b82a4723c9930163096b771460b2cb373f400db28b9df7b90a39452f2a47cc88da7fa881eb518e62f07c47b251d882e371eea233ad4965a17c6190ca482296a674a1823dca5ef3d19fade7befbdf7dedbee2906f77bef4d0146f8faba7c7969901c2bcc0f5b2472cea74f862e6fd7db48a35e323bd5ba32d56ae2920316cd7679e4605e93b6d7632b9796c598213fbef6deefb803c022f1deeb6ee7e617457c4220841036afc718638c11c61863e4f9b80b3d107d22845b044dba5af08ff55c9006d07ce08f89a9014a7437c4b4641c64da70e015328390f9253981d9b11d4305fa98f9aabdd27c5be692d2e4c998b80b4ac3b4373542ed2ea28be82242f5aa350835cd57d35c989db7a7009ebeb6888969d5838fbb8e5a7541e92a946e47fd20a2f9a64328d764fa644ebf707b9ef3eb24867d6915bfd0775169c708e239e174548a4af4b4c59e85816efa79189d83a0406fbfca8063c0a0d50161e4224f0c7aa91395a8a82f35888597dc518a252829eb1394373bc875fc1bb6a51ae6591e2faed6d0a27614487bcd04638a97deecedde32c02436b1a865190f330f84677e66dedd442d06cb69a6f9cc771ad7cdbce63a3ef33579b7e333d4672c9fadf059009e25e33acd3167d9509b8d154ea6ca759ac3cb07f0ed9866c34c238d3498f0513ab6c1bf280f331f39205ced5844eaf22ec843ae7b29fed9cd9994124a38537fa55ca85321cb3aa2a296fa6caba10fb609a8d319cb629e35446d999ef3f3c53c8deb525589e6f9cbed07f8b45b4ae7e47ec90559895c0df2646e6898e7cf9fa752f4c9aedcd0aae72b90cb9e30d08f8b8b132f8fcee8e2adea9dd4697a723f621097e74dd5dd2a177f7954ce8e91ca88aa1c7380ed8e5a2bcc762a3f9c10bee4ba2a3a85f7801b6668558c3d7670e4c817c8a17694192587ee5a76f468557bcb0c37a07eb59aaeb2435a6520bc41bde43a26c2fbe7ef1fd7b5bcb4cc00011f08f778117a457140c05f0e0874c8bd97f7e24eafa83865cc57e84343a58638e100fedb5c703ec807f16cc10c85446d61cf79ee96a65596cfd0aa6607c8909a0ee0195ad50ef05e35e5a433472e48e7dfeed2dd6c59d32dcbb29ef51ecc79c843ea813c6f6f157cbe2f74a331728bf35d4b74e837a9974eb7a535d09f13465405dcf3f297673088875c77f13f884928556c4169b9ea3348334aebaae76b56ad3a392b879fceaad67eb4f094e3207e3ac39f1c6b001f3bc10dd5e104f1cf97ebe011959f9d8a3ad9bfbd5e4edd29a5db4bc334734e455dad869ede1b31a85d0a0796164041354c6744d44ae338e460c9987aba71974a45d81c4cb54c6ac21401c098028c7f5ccb8f56b1df8841e5d419b55b2899744e0796161c9981612d9ae7acafc6272c66dad662d3995cf39cc74c268f6e7a8cb30bcbb6e7d86bce074ec32ddfc76036b79e971c3282e873fc0e1571f4589665dbf32b8699695b27c33e909b76cda36b1c0fedd133ad267aa6d548bff6724d93017254becfb61f7060d1df20892b7eebe5d2815c1e6ba26b9767cb83f6538bd3781802219e3a53877479585d08fef24cc7d334964fc0b01969de6e0e23957f4e0cb60d3e9c3e7ac64ed3743257c13a47e7a3e764f1d07fa0f9e839ab638144aec586010f9d010f1deeee7639fc587016be9d0518a0e9db7fa8bf5c0bd177b7fe4ee37beb4220844d4def73f82090961f0cf8f61f3898c6ef4c428bf42861af380a959d7a47dfd5820c2db470c550df074e8ac1df073e7aeafc025b4260d42fc734194d119e6a887ef9db2cbfe0e59435cd8608c3aff6237a07c353ce07189ef2d50f9a34d00f849c92fb412d966360390f699d2eb910b448807d48671f92e3c97c4ce7813e76a41341a1431f932342c8c987f4f671ea1a62800fe85e3b74d80d4464459197def2437a74f93aea96633548b73a4ee3691a6f71d2e976fab9ed0fc9d1d343ce8713cb802aa18933a4581a4115cb89a529c2e8a74b48b919331754e840e4463a39bafddd4da7dcce8d1aa93d6d902e6b1aefed2e845b734260ccdddd5d82541198c83ac99664605cc1fc72392597c7c8600cb87cb91d1a869de90e44a8dd42c1e1399467c73299f5c13ea74569ce5b960ff3ad285b33177a357db7e95bf3eca045edb2df9956b1c7c4f09c1b6d155d8ebda2989fd33cc8ac1391b0bb4b01e6eeedf7208c35b2cab0c20bcd1a638c316ed2654bc90dc3b28cb3785d291c440c8318f61a630cc7ca47aeb3005d1d1b2d1ac01db63bfa10c098b9615e86d662e886dfefbdee6eae2506fabe8f61cf0c60dcba1e1f57bc33400def606ca0bccb515dcbae2eb55e833b6da68ddfdd531ac5b66bdb563d8f395efadc6e7ac01e2b3c4ea080f009cbf218d94d1108337171c983e99e8f28884b68f0ec71494b8f418f72d47e30165a6041a31014929dfc54b1557133d7c52d736b7b8fed0a0028a8999f9cbdd9351a723ee3b62eabb1e5716126ad5ed5a461d7e95a1e976b3cb0939bb49aec74f2d56a4edcf67c31379f47d4063fa27e20a089a222fc737862490466661f28e6fcf0b153ab5ac66439ede4cb766d0040c175519a614e1d428cf2a7de8dea41f8bc2d7cf057ebac9f16f4b1d3e34a83dab14beff99c61642dc677097119394c440670b2214537990ece4324f65091405fc2893857506623b5b1d74ca880722461e5e82a18353acddb78b8848ad44e0a49a1e5117309ad20a9b8845c9e391c41ab6348ed56c011f42b1cc5bc145a1d2d857004a9806416183f06b3776a6af94accf2c93197010aa9fc27efe0d399981adcb3d2165737dbcd6a340ae5366cb80dae06d7f37685d3bc725ab7522f3595a48b738b4b323232e22d88ee2e8661338f614d69ad95b999ef6ab6429f39c6b516dd450775ca7178476bcf778ac341e90d2e76d2d3b1d1d91940422e2e352ed0eda599e665de51772152f9a18c754df91cbaf06febd1a9a24c8d288f40bf03d7396b3bbcd7d3d3aad67a2a2a8b8e7b9ca712ac5002c90a2590e875093d10ef2ea11ef5393fdfe61fda799faf03790d380171ace3cc079713eaf68c541b4b3d191b1e69cac656635bd976ab4fd98631698c1454a94f8f9ae652ab69cda4b5d65a866d526b8b097649e9daa4d4b36c1ba3d896f3f464795b9e65da6a1ae637a9d7fcd26a340cfb8b52934f9c5d9210b39ce65d8d6a9511b57dcaeec1fa4694872b23ec9e9eef225b26297773b5b6bc7ed9a3be6b87ea3297940b2eccecd897563d74e9cf177a479f7cc5aff315cf4ed65b3681696354ab9d050a159572a1ce539660cca25643358799065dcb79d39c73d215c73ce7a5ac2b529e7cb558b79c8f27cda5e6340f896bcb3fbf8e49fa2895353cdb6827e0f9f42825a53d24845edd8575540a4a87324253cf1360b24866978526d4ccd925bfbff4aa5f7a46e99c73cee9cf4dccd499d217a94491a0df2126a63011e577680453be83013bc9f0d23dfdf3eb5d3f591efde497258a4ac37bc0ea6089a276a9d47b61242e0e39fe6176b2f4ebadb33a164951945814a344d9d54db16eb4db4d26cd6f5226aea5db5bd34e94d2a7693eb72bcbe4c9fbe4dae93a71342dad0d7b26c74e0ec434fdd45b97e1fc1e9db6556959ce6798f496402f69b3f4028baef5000845518aa29cbe73fe6935d776ab2cfe271344e62b27ed04581e2b7d6c3dcfe179e937a9962925c48e2e871e637cef01795e1f7b8ef90e1511fa9c9f5b97e1bcb5f4127bd80fe4d6f1ce430eea4a650a51154bbf442f98f2f597e80551b431aa0cea06d122b59856ad8fc87902daa5c738678f1dad7a3bde63b0e8c2bdf46a695ab57e84112441c1110a96c01c895a60c1f366208564524b9091094920d6b19450b194a001808e824e6b5ad3064accb08eb0be484da774d26073ce25407049921b4cc6ecbe9e75164ab7446bb4c41632343a2ca1039dd3efd012552cc1e48985d2067a84f045e62f964002851428800ef0baa88748c9e882c2ca1356e21018127809b3f305a37cd2b2fa07326666c7e426951ed2d78e991e93ca15496cf1051464407144ab62908678e08a2a8ca63083093b6364a88cfe0e297194094924507a5ab20316ec5021e50b22184d312611507091861311b4c0c788153f9044c0a28b235d9051c4d01652ba08a638c4048af093c4072df8220a3b50028c1c2881845d52cb63fa941c9f1ffd8eaa2e8b49c7095d72fbeb1ebfc0dd2c451245579c60c98d1676f77517dd15d81c93594fde676aae7f8e36e8b7956c9491b4eb1d63a38ccabebd44bf046c5f7cec9389eb5dccd32d5555ee33313133d008e1f000f661038d236e567a81fd362e4d9820e5c56229e8501243763254269950e5ef5012423c3b1a3b2faa1c4a605b3df88b2124a864a8ccfa1d42c28cc88418585c606808a191052f19d58a6f1cbf43483cf9fa3b848493c905b62fbe426380adefcb83b0176a10883ef6c1300684dac5279e9d3d023db13c6093a0e7e012be83f97df13db8c59e08ac0f29b92042105d4044e9043d404a4be90c4390483105511a53f86981b442c69d224d141162f403329674ac1ad8e0dbb39e42e6440e809c50c4839f21943089af48ce114514e184218440c4094de099184cc10a15f4f47bef71c7ee9b997fde38ef6d6a24e982899f810e9d1fb3189605042b844860032d80c08416918b2b0a591f24a18a2e888842118c14617134749ba1554ba40c68ea85c000da40080b88b888504207377e879468f2d877a9ef524768e1a200a02fbdfa81483022944bb5caa50967604d3802b536b738f56468de1056139a4069304a69ea9d904474021332d007d7ddf0f745a40471880850f63b444489c77e8788ec5c37f0ca95fa53cbda3d059d9644017befbdf7de0a3a846f210ed6bbf7b4f8b7420fe43d0821e4a7fa17f30f3a148261399a005b2dbee31f3958d8c066b590d1ea6d5f0106f8e59c2c3e3a847e0169de67e28d0c9f80edf2d0326cf7dab56877777777b7dc8d90f66abdbbf6ebeedddd355950a0fcad280f32eca92db06d15f3cb86503bd47b47579b7269e13590d3cee1f0171df1f33b7404152f7e878e58f22897d494dfa1bee343a022ff9e08fac7e1f46f20f55d101d1d9dc749bde95b78ecf24e7b1f5e1a69fc0f3f44bf365e1d4fc77308b31796a346ea7bfef78a1ed4b1a276a9eee5aba3bc472d02422bab8f7bbce2d1351dde8957385e3239cd832f7fda4c4728a6b2022ab17bf4e831e77cf9e1d2840943f8c8b5fcf8c8b544cff9684565ef1146edde0e1599edb15c073ff668223a44ef08395e0115a843b110ca32175a25f31cee68988e6955cd0b0c2e50081f17d32a1788c0f0ed422a845876490bf2400a210e5c96503e8712f598d318ba2a0bc782efa915e4824d82f07b2d36d3e3e41c90c3c6ccfb7ab45753f3f61ebd6aad67941947c339a0557406413db675fba4072928ad35959a99b192ac24ab07cb4215a1c506b6d8c4071255ad62ca3dc4a076a9981dabf9e48b356bb0785c8a9a3146044e08a16559944a8108b609a8312814ec32c9d98542bd54b6ad802d9f96a41256a98fa152f0ba681a887571eb588d65319d935bf781a75c5e82625c794e8410e5e920f975228427dfde4b4c7332519694592ecd0a873957b9dc0407d92fb0132e40fbccd809b3536637b1b364d628951cbfd4c32ddbe6d6f51384b84b1175b7a5a4545bc98a568c41ad9442a3765cd4ab7d12e531522bb154a9493a49ab98f492961ff1fb09fb020edc549647cc7e9170e01720b00578fbc50d8bc33e6075ecd147a44fc2848d5ac571e58c8b2c43d12bcb90c8ae9a6d2e0dd318979a5261abfcba1c7235f5b2c6cd2565a4760588813cd8eea8314e297bed55639a3f15782ac79069db0a38738845c722c41e1677d6a7dbdc9621cdde7b2894bf25273c3afff2eb644894a75d57a59ab93cb3c0ea78ce3c9c71bc53e3079586d2d75076e31c7103111f3ec1f68566bdca387608c509ab6adf554ce20c068fc378efbdf7b8c978efbdf7b8c3e8f7de7b8ffbbd202cb4c822680c558ea22254a1822d468f151e2728e821c6d1cd1e5d604c1146184c053671e408a63286ac528c181d8580268a8c708312e335155530f182c6a0145297479f9de26091ac294a5cb0483892a54dd5d1d17932e9c4891ec588618911e3b1ce832283808ca4904163ac93d3902d84b04ec6883d3c64849dd833064eb74e67604c01860a6eb21401c098228c54af70200e8472096d10b618f2ec3be87eec40401b1f22f860ccb34c01c614cf39cd7c04061b3b3d8ebe5ac326622384934a3939d9abc83ddff5daaa2765dcb8cbb5d8c42698400a132338112505462908e2ed862fad5a52f3bc5b1ec745e934ddd2c3b7f783de1e9799b09c516fc0a0b26fbf97758a5f500c17402ebf434df094a0e777a8043adf594b186a22c93ba1f33a7e8790187dd21009903a2a0d91c0c9f70e35e1a48bcc60f25d94d18c24cf9ddcb1fa3d66cad556f584f2ed30095cf2fd923c26487e2091d71e84d5c17284ca8254bb26fa1692d2aa6ea4ef242655ec149992c3f753be6f7364ff86efb37812294796a820a902e5fb367cbf86efaff87e557ad3cf674e24ca942452769814790cca5b406f63e4e8a7cedbf82c79f6fd192c11cf3146d586ea7661e48aa752bebda9532adf4ea5f4aaa350be7bb5432508fa760aa5575d548ad2abae858ef805251d9efd017a4b829eccfa68974d90b40cf4d14e8d9647cf5c9a3fce7574a705aea3442c701d2d5a81eb66926f17ae9b412d5c37a9a8c07553e9db6d8e2ce95534c1efe7d86c8e2c61c224a857d189df4f6d364782aaa842a75711c9efe3d86c8ee8ecec1ce9e9552cc1efdfd86c8ef41c2972a40852af22097e9f65b3f1419a32c527a957b189df476d363e493e547ca81c01ea55fcf97d1b9bcd11a023498e24f139ea553cf2fb35361b9f231f293e527e7a157d7e7f65b339f28304c9119e5e4523bf5f379b233c47881c21e2a3d4ab58e4f74f9b8d8f92cf92cf9291a45e45267edfb4d918493242c508959f5ec511fcbeb6d9f8fc204112d4abb8c4ef679b8d4f501555f8f0f42af6fc3eb6d9f8f0f810f12102a5575189dfbf361b2350a2443182d4ab98c4ef5b9b8d112423538c4cf101ea5544e2f7e566e303e493c427c951af2291df879b8d912329528c28f52a1ef1fb73b331a26464c9c8924eafa211363e3a3b3b4b7a158b789f254c98d8f8f4f42af2fc3e6f363e457e351b9f22ab639fa65f94203c92efa2920cebfc380182d27792cab7cb27dd4d8a829c7c2fb922554565f2cd9384469152bea77c4b6845661e1ddee1793251a961bad654ca9ba7a312cf529d5c17f352d42e2abd538f4aaf67087466101f7db508e0d0df79fafc42a49d10b550ab6eeca076b585965a2642e9980844d2793211e9e954bfd103bab2adda2e9bf2924a232d217551292a217d2b396955e328aa5d0bc12a642539c3d4e04769ca1296a4287aa240c2c21124499292187170f7bdf738caad43c89199b31e40382d792d5d96b5afc8b90e4f3d2e075b15535ba9bcaecc310969cce596ead5fae3037f5d1ee79c5c37ddba72b89c351bae1f156868c3f40fbc6cc955aec21360aa554539a9679a0b37fc7b51f94b0edae6e22e17f8878aafa26efe65b5877f9ee17c0cb2be1c04d4182261bd94913d193ae9a424c8f18299211b6430a52c90c11394d0c248952251884115592c1108d08385154954a1810faad019e32e29fa42e38019689889f1e981cf173e34f0496225c16c7c81aa196d1e3cc8e203b56c7574f23b9cf7cfa9103048024893fc3cc76bd65e8d7a67791721a5d4b2fcc130166394b23e187eef750073eb175b0cabb00cb91b5cd58bbbc008217ca956ad207c7005613bed158c542a01774e29a5e46236d3aa4769ad73d22d73595fd295b9dce23ae994cbe6263959e1cbf703fcd8f5f8c8e1c48f51409aedd2aad900812c5759a0017c1deac57d7949801042286526a53479669232cba8dfa43e6b9194c6986559e6cf35efeb724ab5ecda6eb2b738ec071b9db162d369af34306ad6aa884acdee76b9a355d1e7a43e2d8beb7aecd8f1313a2afa8baa5773caf8ca6025e89c049da9c0ec1fd04322e638a991a81deba4d0a8dd4679e83998a818864d97b2059dcafc23bea42995cf7d2f50b6d1d236eae91de940bcf38820e995e460aff67f2495ed39dc04d1abf93a5c60871e7082f44a725d44d42a38a107f192644f51a953a4c0a424a19e1c96931e0cf4759f229f80df212340788c7b340cf48ed6754229ad359552753149bdd430d08ba488d4b703f73de949176b35ee118ae54df464baa761749e0c27350c74a3182295a762aa8a52aea342b06b22ad8c9ae4a143a409f4440a9222a32847939354c14eac8d23b7840937e12be893975cb74e1e9012b63b9e598842853e7f878c40f9ad52a3ef772fd137761eb51aa8d1c05056b52f83913256b5dfbd7fdb4bec249a10f5d149fabe748fbfeb9dac8fba0c439ad22aa6459ea8555c54a55a7ec305b5637696a4daf5d1e42290352895596661f67a328f9378b6c1debaf91767512972e442855c6420fe91dc2f66dc47494fa757f0c952af1e3315a5d742530a18f47dd19ed4e91495babce543656deac6302ceb18638c2ea39fdcea70f84d7edad6f44c5bce3f0d738865d84f8c926594347b3b94ec903d60d7a882af8b60afc85101b41aa86962ec3ca754282e460d4add4703935a0fe87bafd9c4f267592b8a5214e5caaeb836b81aa9d44aad943669d284d25a1fb71d394aabe29539142da8a2b82cc62853a9de151b1f21e66ecd5b4a2d321292124fafb4662abddae955f3144ee2a5d6e91c4d2a9426d465ca3439ba9d7552931f38eb187d535d54e8dd5279f299bfe84182d076da63a41eb52c4372a40713919e4ceac1c4a6fba8348eb744b564680600000000b3140000201008870402a15844a0a99a5e3e14800c86904872589bcab32888619831c8188288310000000400448846230008d9e1576b798a722338b150b323ef2f255feb28a2c76520351a52ea0f078b75a4c8e6c5165b11260573478417548d4e75c83456e12ad5624f8385dbbe18f4ca4749c2b20c93385c2c75ca67b067468862a656f83cf39007fbc638c1424fe04d0e3dc70719bd1299bbe25a6a4b330c8b1ed13c3192b658d644ed23667c38997b377df5fcc311bb76f97c023f568a68fa7d188cba174bc9e905e106105229127a89bc6444af6ce88c96e18344bfa72d8736cb1f9114a55a1e2117b2b5901ffa46f6cb35eb6a47d29ef19f287464382f9d948ff5d78ad632fa707add957c97f6d8cc6d2114fc0c9c67c9c2b740163b8955d48ed8722f2eba99881404b8cade5c44da42085b8a8c94e8b77d9448232496926e25225164fbf50aa5250b7dc85d27af4df91f2f27c9f44152227abb9f4a2f057d594420e37e9fa51979d245d253f6f282c41aebd5770f59657b6932bd81e49d2a35550a70a68ed13bd128e34cdc8b505e783313ca64b83437539074536b82a430ca632bc128d8bb3968adae8cbac92c0da6a46eaa7c88faa35ec11dfeb81ad27138ee5189de0165eb7bd348bf6b1ca4164da004db5980498e5c96e8f3c527e64c31879d074a44b3d487071ee19f72359a0334e1c8aebd156d24d59dfdda798d38186372bda8e8e69e4f144a30a04df6f6cee7798c41884dc9bcdf65abf5476c06ddd78655654ca8d25ae7733c647d8875e5a336a9b61b72fdc1ba634c3071573fe4e64cf759d5900bfb51de3ab661ddd0ac8f943b9e51b96a3ecefac4ac2cf9290d53cbed9917a8ec46f755aa94c74518c5f419e5b219ff751760d754c382c08ec31a5b68d34d146c51c2604b00431d192a3ee5cb105ea0a8450aac91a688a531e357c2d5272c1c1680d15095c9c611f3e3813b250eb981ac5c2d86e27a61d4795d8c47b55b160a7871ded410ef97228009a493913c997dc8c7564468f1ac3d3b6b1e2d1b821cff2088fc33dff1071e6444b4527c05fd39fa64b6291334e6b8058d63135d98ec4ca5c162342639459394ba9e8f865962651c9400f1a03000635268df04329a6a12af8541cb523ff957a31002843937cd70e8985e5dffb908290ecbd8c58d1850f5476829643c3b859e640b11a44452ecaf8e40e7795cb6464df970028cd57fdbad779ae43dac0a1d55e71072d333a66fa423a5b06f1fa036121904786374e6b786fa7c2ca53e58c135ccb1ec829a0f74b12b33a214a7c8159dc37c7f86919698454fbfc679340c3994b513414105161b9a53a6a082466b3ad5135150519c6ca93e1b7c9b0f5a5809eda6f641ed7c5aba46b5b9096c3d3616d2a0cd76c257d822a2a35534ce0d34ec5dedcfec48d0f6d0de6cec1f200b30476d19c1b8fee9b1c9c514062bcdf01426961ecbc8b605db93cace8f67e6a5758ba5d4e6944049aec007d7ada1b3e610d7b83c991d0095c6a01e9add39aee71847a26f77711bb8c467be4c9da93fb63dfd4254f85542a9918dd748b0a5a7e195b068f70949f80e6a2ac9455697a14ad703cd1157e0c13b5b0d233e2bd01f9d9d486bb123e286bd78fe7a2c5a6b0f088b07a53636bb717cc9cad9dee75420d68a2c9583047cec041f5884af8ee12b10bf158da567c15eb606ee7333bc9e8f1ea0930705ac02f9c0b9670c451fbfc1a531a19d58bc069b40e73df0e43544a0351257dba4bf353e67d59af1b20d7b83128d96e78b9efa90f83eaabaa1008f74e9860b266ecab8f1e36a99bb38b74f9f6b962d76b1128d2a130a007488c53f3372ae2731fb3d0e9158fe9e3c20a0e37500894346ebd4852ac827375e31b6ab77e17a379ff4938c4250d6ab2a9616ebf6590382e66aee68a6b6b339b15ecbe47ed28717aec650cbea58d02d44864aadee55dc21d2628e43e40667203aa4723537710ba2963b0111577a87c810e2d500b45ded4b9d4364f0ddea5ae426445ce1186242cb57d322671252da1484da529005520dca83900e607f53dd7bd84ecf396107a763b3331c57d9b4238ec809945001166cc894650028b40ca6e7d1ccd0d6420c2af2a939e08befe7e3c9b25750ffa7482db3f71c6468a41ca5935e33a30742e48c3843b9dba7c780a56ca83df5e95368cede34e20071309841440be10535866611e80878e916fba27881942321b6380d648c847e164788e7c542eb5e93868a2d581133e0323de955162b9b407a0b67aaf20623a27dcce93cd7fe6b740977d68a25bfd06581f1ebfdb04bf86f5d3ec933f565ad43cb7ae2c1ada00d03af23f4edde8346e24f206e3de0414399811b54e58f1d72b4a4c5caca3b081c6f20c4fce986a77113660e9b619917addabbc14da2db4c2d32633b8f0083ed06c78f1918d6ba0d55bf838402467ac8c34afdb2a8a79cf5b2d731f5e7bb65c999fadfc90d3024c448e0a2a01046f0e1340baf1046ade959ba722de4df7c0fbe35f2e960320501d42f5da2a6b942d22bff9d9c264be23866e48aac8fb997bb73f6902cb88180d41d72a3d4f7ee504c638cbd38ccc220281d84f83614499f9d9899ec8c3b24c4f083573067365f899b4be8eb62d399d804fbeab9290de1892fa2d9caddf75c9486602217bb11d9d8384dff22b182b851fe5908c8519f28b395390a629571c46e227bb194b06684ba34de9a37403a79f7b3203d47c8dd9777a6929a87d0c5ca5a838a88e5f389a31f2af630387b37b91bd0ae10e2e066ec0ea59297d800480513ca8e5983c54d96ed7a5fdc312a13f075514cecb9d87eb6e4d45a0e726fe061c3153bf32231acd7b3ef77641b9f9431b4513502b7df5d7934e63bb599b8c33079a6bd24f81e79e6b140a40c2bc8ca33e705c60a03cfaa37de08f6744fb5922a5db9b66c8de8a3b4adb7c55aeb4669915e7ac5605b5b123d50d813d1d9a668dfcdfa261e4623901b52d1e4806dc6ea23398d5884c4beea4e14b22bb19e02a0c6229a7117a303288a0778cacae0dda979df080334e7b440d08afb383e64bcd36f60b6192d55be8dda70664e82e40f49e1b06151759f2f6aba824c731d746cab8c4050d040771c301999d04f3984552decd3919df52005f4da71132a39752e09645f2097fba9fa2a7f059287102a706f17c84c18ebdab06887619d70f613a7649842f9bae75c3e02921cf011a2aaef29f27917d9c7d086d09a83a4a28d679de26c83a0b741aec54f205c5ae1efc657bce014b755642119b9f96b23ce04220ccf4253c23d4a4e3ea9fcdc44c048080c46ae2f780d622588d36006192f881747a3e0697b778d3ae1e4c436d4130a6703884f82edd1d671c39b773f7d6dfec81a95a71d102a3270d410f5f760c76ac1978ef60d25e609fa37095f4b1aa48d5446e98f8955c1f6bb81ce72ab7f36d50850f0d07f6c10c2eb91543ff32a4481012acd4addc21856eb6198192ee4afd454e7a99ec0c43a66e3b4a245b7049e8d7fd565c0d198e8e1dccb88c8ee2466c983412db6d9760d481949a3b38063366e1a480ab10ee271a38ae92834661fe1546325fded9db2942e221bfabb6b2fd2a1f57c236ef07077f2f27d2a7548f62c2ef1a6bcb338d1380adfd8fd5ea0a8e85d4c90a6f889e22bcaf4c6acc50e07969b5eee04f9eb6977ed78f6bb1de5768175bf090930bded085a69b5587c8f4647f92ada6522d1bdf917fe8a0914ca461fce5a393c47675fec9b8516d9120cdca5eb29418bbdd66c108a8fd1a229cff260eed2c1bb41716480a28870b6b0c8b7bfb129d99c62ae09c9e540fcd83d989b814e433616674d42b2fe396f9a0cfce43de37b51370b7c9bb1f27addadc3cd22c7a26352c4956ba835ab02c0f9374b527f7635c5b01de0611aeb6d16dea130601530d32d012c685703c687b2836de80be934efbea446662be7ddb08ac935d1cd1ba5abc0bda9615526703c60a68df17cd7b26157980f3147051ee8e919a1b59893dc3835c57da2571f89150ec43c05a5102ca202649e57bbdc624a6785670e224cb1dd332163ddf598053109ac42d402cc76a6692ff36344e43d0db22ce2043be2b513a152c0e71b038b68cd2ac62c2ee86fd26c977c835754883cad5de4718fc4d4001af6ad0cdd61128efb6e3ded8973a1f6f8eef3f5cfdd86c4438fc7cd660bfd08d45a8ac723c21bf275617a2c6d42ef68205f540d314cb8961cbb9340bf6df362b4ccd2edf2f2b8d38c82837a3425929c3c56769a0fbac1445cb4ceef710c23088c0b435ee05c52b229341a2a538ccc31c7478625a2e3c291a13d17af6587757b6b59be684e2b363de0d4bc32c21c5e3934b2d3e16be0d94a3f3dd720ffe18f68c4518f7f964adbf7413439df4687c66a271efa649a31b731338065b74928ca44ce6e006ed7198d3cee7f762c2950435eb4e6ed5e24dd4898bfcecdcff020ddc0acdf7ac7cf6b682750af3a05a5a8de0017425a4c86337a461df36c7558e3430f8866f5026d857661614c8e018a37b825048cff5bd72cc0903793ad33b4bc7f03dc3363444c3143bcf8b2712bb1882139ac6b0fdce061ad182728792097e08a760f911e5ac998c7da08c8c192179f507437c86061182682a657b941511d03e498f0011688b21ec0ff752420ffa762938efa7993345459a62adef36d906f0ae454e84022736d9611b6432f3f3a401cd8823108919566a499d197f974edb06d8c121d650683a059aaac2648754f1e88cc0a25d7d8b6ea85b644d7c7d2a93e9de7911ca70cbaa09d9c59f7c22c5f151638362dc6541c8c2457efbf5483427494603fff3b00bca1146430e90cfdc599ea5bf8959f6edb863ab5b523a8f8cd4c0fb3c1686e0b1e8026150185eec2a33c4ef81ab6d6e3717237970d8680c35fc5764a9919de498a957c1a6e24a5b34252dccaf74849b2f662f677c7dc2817675f3190d6a6b1f5eef0d79cf4fd6c7de2833c0518badce2c27b20413bd882df63db4f8ff01b55c1b153dc0c0dac161c7a067f7b2274a17448632d04538a8269c808a583b0d4e13c058e398bfb896431d8c3d6016fca0063ce68cd2b5fca531880958f701d1a0de45fe64ac431621aacd955044a606f41e75b341636f7a6c6265817ff1862af729b59aa6e53849ccdd2b07c6e1ee3b7a206fb697cb6c74775a1ab046f7ddc157c6158a27c9a51436bf9a3104c20831f19987bf0d3d00d4ef23f17660d3a53569ad028a72b3f6b22ae2f89e8545f4c5943d7d667640c5710bcff6ac04d85191e8b922f7ee3acfc2becb964d41f8bf289414388616288cd30240f2d3197a0c2e52cf13194b82826ef17b507fe3119174a7ebabfb86af9ffef2683f8cbc16d9722387f0d83359c1a958da717c7e949b56c1de96b2091e03766de7fa081a45a0750d310496ddb0389a304b323d3ba980ba21568c70e51d9be3237badc9bad620462dd3332712756bead4e5eae1b7e1f26bbb796056a71de62e954cc56bad0d7cec3274690238900082697e5f0f3d3ed0240b89fb9f0215bf19d21f915444c964ca19777d1437e97d54c891710d4d0e961265208d20518a585a19bed5da17d42bc44302b6c2eda0d510c0be8fde8a322e44116d2b2888eca986b7e60ffc5a57eea13d24671c05be87956917757cfa31f7603f960811d01de8076261232d083d0d3497c3a09751d8cdc4dad0693218d920e040d243e16a27a91f67eb63a72da24d38ec0907c7f8e6c7f25004a016d56ff6b4906c89e20ce81627e25ad4d56da3cbf2b3a801d97cb80e1906896aac1d30a30a66e11993aef9fa76f2714ae8b1272c3d9066354eda853043ed0ef75a21ff335528c91b43a7104bb04917328cd535ce52630d5261582134b43e2801a416f0a3d766b5104efecc05ad7d518fefd07ceba492889d529737fe21b05436f26e6028782d0eb0b9d3d408a556ce69d64d4aba4a2bfee8991cb01f47ab47c912aa3f1ca1b1dbe2c87802403bed0bbc30277f1ba35efc77e203cf6a040252fb2438a451df5d6c8c38230833dd220b2783a0dc3d62d3ccde43e98feb79d6f7cdc2854e31059ad1ff8ebb7d94894c6e780397be8dc7c2f7940d25d742f75fdc8cee9fdb08b9c1158221d998d9c8559ce30e261493c40afb70eaae76cda610cfd101e6cd938a13ddd0c4c6e847c2b7e991fb90db4c2d57904446c6d04258443c2f04fb6181f360f924842d9fdf06ac681796a793985ed7a194a498a804a9da426e455dc858baea67cc524f4b0f6d34f6c8fd21b84c8da476cb0eb779fe1ceb5ed4c2028c8275fd57b8515d66e9531fade373161b10b1979eb4a579a797fdd3098202a51c4a2baef59f28cf8d0f545ca0762b869f6b66327daf910024c561f732e938841c7a73d3aa745b28e0cd89d5fe53387a5aa1055acc41413e7b9df8a0e4525c879a85c7209b11025ff0c44b5c97942a1c1f186b4fd64ca90cd6702e4a5c010734d5ab05ae414515dde0284201f1e5cfbd535079427c4875455338b0f103ee7e419006e5e6b291719d43f7b7cd2a9d284be8b02c395db3ff169bf77ee55dd214872ee87c7cd6879c909ca9e473621085f0ab616db247c94b51a738edda09e17060e02952b656cfac5c809ab97dd45912630b8ff6c659945337ed70e0ad7aacd23b664107310b0b12e47cb00249d30bebe20690558f6e112f627ab293bd75ebd4627fe3c55f4c9e6e2fad9c63af74aad22cae1debf8fd68fa0c678fe69632430f501194da25330f23ac63adcb795fe4f4d51d0ffd689f1613c0721bc3c23bb7a3bd53eb7224ce9022ca6d44f489892ce10fcf4fd526c3d53532ec7e90d2ae88f7ed7b43eadcacc6ec84a87bd345cfd30adb7974e666915e7569320704e3a0450dd7595a9ff495879867a1b4b290317a9ccaf7b49c6e22df96e91a87c62f31b8e3f96350e74ae342e400a7e407af4e03b4a4c878061abd8d0502648ff73db626a6fc40919d645e257a35bbdcb87dc7b8575fe77cccd29376fced78511811b630e08651fafd33ff680b9e9ac6a57bb3bd01db85d7de6d1bc9023454080a6da7b15fb51150077e6d1d34aae8597f27aecae8adde0f3b97ee40b9fa4dbda9442027f7ab326303a2ac541a4eee06f69878358a1044eef014d5582ca34a0802d504e6f221ee4559045b4bd2a19c4525b608428f132980d10d46dc84ba0b774ad1048c9dafd3f932a9a0f1a65f421c00a121f64fc59b670f6fb9829d375f8308869a8d824ee9f169a3f082fb6076d456cdbe60171e425251d412dcff22383a523026f0a613ab9e12dd074d5d9b2e8c84c0c8f170f0fd28a25be05bfacfdee135d24234ed8ff68972a27aa0046a90b2f266a49c97e5488cf1080acfb6852c773b00e2ed5b305dd620a5e27ec3a1acdaab66422420836f8686e8d4d59df802ede1afc9403a43a402a9ea0222831701b234c026cc0030a02d1d07ce5c06211986cbcac0190425ad300d990ad8ef9b43d4581eba26275d69d4608b0ce2588109de0f18c782451e5abe0bf130cc1b35364dfbb74d662d1baf8840c490f23591c3fdaf4f04f66e0a2d1da2fdc3926c0da3e6bb6dea547b5c298a1eea82394e946efadfa31ce788fb909853aa804b414577587c51c025023afdf3b41c574fe11f8b2946520b734adef2727e5b6361784f0cba5634f0f02a0dff362a53344b0c9ac284d781ba5a2d08e760ac3507333dda592dd0e7e64d2b59bfb9cf3e1a97040b9569f74c0b7edf258bc7b02f732cff9cd8c1c2079e8fe03b185409c6e0adff87f8335780697a636ba4b525b4dbcedbeb08d8e3707fe2c92a022c81ade33fb0c139eefafafc3504cca6a469e2170f669343f33f263515773f869d77495707ae2c4bbcd5c385816ff16a99e5584c7ca08757e26cd1f46923e4ac44ae13a5563996647237781c8393232b7b3a518d77926d80ce179ff68525a735105aea784ac56e66a23d2104497345fcc2edef23014b0ce84c55162e2959271522cbdd4360d76c9c722692b3f67c4ecd6a1d778fecf10afe3f4f35b99bb0c2a1c8c8faa3e95aec8d865fe01eecae863773995978fb0805320b319c351cf7f91031d1cee90e4e0ca36792693aa9a26d6fe4888fcf036b4b9ff1c106b3044b7b2fe2a6351d452800e85a94722684ebe9964d16d6100543bd538b2b9b480241d006e0fdec8c840086e46ec5607f58d5dc547116616ea2bc4504d439def87aa216fd58cd89fb271c1701176bdb62a679ebbe58f8ddb091d5371c4ae5ed34c9f99d4366f234ea65556a10697d9bc9f3595249009919b327f3e284aa4eb5f83e36dc7bdc0b3dd272a16856d49f4216a9e05d2aaec17365497b6a5a3d765e9465e3a1a4d8da449fdc524d42f1aa36973ac547c7003c8a2da44749c13fcda908e4722e7e88fcac982ceffff3b223416b958a2ca86807f716e5689aa31639e77946d9377044237a6d182517ee2fa4a1d04a2185b0b48164c4de6e52e3e1bbf3c05eb99203f8e862ac94d27ea7a232a7e6a86514a000f7bb4b01e7021e19b62be79ab9b1a4bae0e3627008b915657d4e2bddd87f07ed33de19a39a34ebfe6f09a07b4caa43a7380ae9d13e5de041ba136a0661234b483cce2be45e4ee0cf435a1d68949d4e67140fe90b3b08c63b319f1e69e4cc53819be18f6560a9030da414efd5419f545dc310092fe09c98edb553f551f3c046c0482416ccdd6d6de4b397aa4e0fc692d88cf5f80a76f3df39bfb203ed98922b840fefc77935f326bf692b341143db4ebf8d14a85917a73829f153203400a513798c447211f94859b2625e58d180fd4418c80ce64f58f42fd5598acbbccad2648834561c6904384b6215d8aceae3772c1851d46464bcb312c7a96ee332e2cd5aecc6921cbdaf7242834252ad106b43ba13d2143053831c3a4267e50b290a051db3e70f5e2228121ca4ae3019ab0c884c9e484876df7225df15d7c35a7e45bf1a8034d98503e9c235dd9dc7e53dc474d6cf070ce37e9821f0f9b28ed557852c520ad9c005802deabe07b9a438d70e8b5b6ca15728816a3cefc19245976c5a772028aa4cb912870342ba0b61f2e71c2221d4d5f56d8953ae88aea6fa5203e7578882551242675b567bbafcbbba14c342028e7517a3ce2bf523a5cf7236a78dcfc2530abbf19b5d6ab66b75be3f087172033da51f91201cba15c825fe90a24a558f06c0175596326debb37dc86243cc59107e117f7636787155a8baa883f2c8371409590a0c9269ac906cef1ca13c126c6d7cb1c568160f6ca4b2084c2702d425fb4d9ad25c53c0ae47964c7309ebb0827dfa3b8ec7bf1836ab889a500714cc8cba0fc903d17d65e04d6a3fa37105126c7547b50753e8a12360335c02805a3f218a1bf173f7fdb0792daef7923781eadf8d8fe501225500b7264045dafed40327ee34c3321d0b96f8a463536283fab12f0041584093083a184c5766050f10a1a7c4d5a8b3928a1da656310ae6310bca76baf69c44503de2c8477626c63819549c88dee87186a1cc219b1e1e92fc6b8975a877867c08a42b23730f30a39699d38c4905a5f1b4914dae7627c8612f716ac351a12f6e0b90a5d91b0d56649c84aab77c7d0bd0510c1620af85493cac08fbd10c648d027777d5f15291f19d47be3068519dc0ac44d97058377186fbb4bde273cbf2d71b892c24ac22060c5d0259c4a2423548f11d7759e0dd65cd9a806c71732180898e2380482f969a6bab10d6b9550aadcf0ccc0819cf63e355ad99114b3713972d33e3ac687ebf904246f2d2f1cf0d62b869da2f0f949ebe8ace2029d56f3e3588495c3d2871609071405fe6bc2eb95048a083ea34d7004b3e1507711b6420db4f3ac9d993c4c51fe7bbfd616df4b5c3a20077fb01750f1f27c3274b2c0b761bafb8f2cbe5a0c88a0c05e3d9215327cf40fc8162be892bc30f3697bdd64d1cb027e4764a2b142b66b1296d43996a50f983b69578049c221963d4c1ad40c738d32deb8eea65139f145baa6fd7d035d3fc8d15ea9f0ec709b133c931ba1cb547fc3b938f1b911612757b6ebde6fe340397ed8c4d382c87636327528e520e6fe206143a36b9a5bf5279ea942da146b43ee079214ec192638f9c3d54886f74a7a08859edbf8f51ca442175b603db7939e67009412f05760c3cf627072830829f1c65de89f3039d48542b8bc5224dd8b74047c37b438f3f9d714afdea739386aef912f1729ae6462759381f0c8c33ad8a460b36bbb40451932295c0d0ab6d86cd763878c10b8490af7029df2707fd8f87a0f982c3d02d4467134d86022a4ee5679b1550ccaa1a20795d7a567b2ecbc21ad680925ebf9664bc8af393fd163af23ac60b3e9870ba35a9b70168af4ce330ad709bf4b17f7a9237f6d141bd28f009a7d649195286d686c6fd059a01bdfe471fe24e3b2092fe172925e2db138c1d3ac02cc898107e8ee1cd47310a9a2284018d541f45d87c6d9c476b3f0b12d9cc4a3f175b7697822178f817d6ada97cd1f5d46f83a63a7a2dec8d9b8ba1e8109c50d857d288648d4b6117709a0ca3ab32af0de5058c24b40931857fa7b2be9ad5690c37b70e736a23ff484e7c883e0dd352907235bf78bf3b51540f0bfa9325443e8a0c3b1227e892e476389cd8975a9ad90310c3f10df4a9cd8af6935e2207eaa21700394196d01e6e254d99c2d392ef59f6689de016f625a64e948b6ed5425edda128ce83d98dadbe0e9f5976c4e9beda4416bcb7ea74bf5772e104471d4c0a0f9fe2234b8bfc0b49de76574443a4a976fa123b3e5fb7d71a35852ec3fe2a52bf40c01edd9c584881911dc1a832e2eda34aca054a8c62e8267671fecd6287bd223d2750d424c2a211d7dc3a103f2959088f848401fc33efb429e5494813b298d53e2483161e3e8c0b71bff40436c9ff8355bbeeca85f2a0e2f2d117ce25651e10f44fbae02f6375f1f7511f9d1fb640b9fc8c81c55cb56d00a45dc3ff2891e72158065805b7af73c96e8480ed4e70e340427c20801eda68bbcf50cbd5a42cdaa02356c0adaa482e05107c007eb195c6cedcff2cbf4238730f157ce9798bc620b560a61d0d974d2003169dfe1b7aa299df501a8ef5604e3acb443a13bef486dfb67f6f61bf30e117807dc9861a4e2f4c3b9cfed7a4e825cfb2b5892b6290806ba021ab9daec389567b83146fd7f579b8d84a68f01b6e7c4a2a4fc994653f07e6b536c0708a5ff320bbc8e23784e6c44d483b49a41f664d1cffdb49280f3708f9a59a1977fb2cea427bbf9555a6ab103af1aa1a94f8d7003de3b65404de68a479e9d4e0667e810fd4004802b312555cec8d1f8f3a73dc79356e6beb9e7c91b077baa07eadf17824eb7ff490acef0899c79568ba4d41a193083793221855591b206475e1b5d8ff0830e68a42dd3f53db1e0008a284a8d5e96e9a3ac03cb52182683f504e08457d2b0cc613f0c6efeb158d7cfa819547baf6b34882bc6aee7cb7365b44eaaed08208567e9261aa05ee27a370de7e6078010c876b7df2b1e07ad1f595ee2901b3a612dbece4a5ceb7532891be149b751b2c2ec11392be2c1c4416ac6cb1581c5648d12416f85d79609d0cacafb6ae0a97ce22d4469d169e4b182025648595544908fc3949dec06896d62ab49ad7e0336bd99f2d1f8790e83efe7d1a2da9c1e180a83276700d37d2b93e9d1a2797c18b9f5145ceec0d8d42a792031c182f33bf5ac562f881ec075c6dd3fe3e7723dcd7f121cfa6a5bf5bfa520921cc45cc2c6558c6d8693b0006fbefa9dfc05af042660dbfe0fe9650f1f878f4dcf4a815d74f3570785e70a342426bea8fab1576006d3acded4af1d2ec116dd1a5688aaa8a1e3b1f2094b4179e7ca38a29e03c4e9cd97c101cd49ef245ba851091dbf4aaa92cc09b064a6fe9577d7f3fc432a73a40458b8350915a336110f29680f4bc5ed519384426297db25aad63c64eea887b93e6ac0cca1527eff1e522054f2eba12945dcb1fae7143b4fb573a431a9060370980cdcf763b5bca9779d2e50c38dae89c4b4db009b0d98ed2e15cbbe695bd7aafec7bd41f214258e729a3750732d56eaaeff4727d7d9d430b654b21a29c9c1a86523580a81b2d62b1a08592314f3bc97b6c485ce2f138aac53ac9f902f5947e58c1cfc3ddac5dffce8a9c0ceb578908d1cfd76ba9ed57d10ddf96d07c05f0c103829628dd3295e5e161548801a480ae78f62fc2f95c2f5e3f032b868928d2202b12a9ed408864bb580c3a2b2e0ada1da37c7cddada7001871a48979e966f24c26da617bc9b2749981cb756b98e766e7421a9c10b2e1a146e058d59fa7550fe1c7b228f8734c2f11f2a28070ccaf111946d630e3961058d524d03947800f37d23413f3a68089b4b80e295379933e8855a03e2536798dfbf5384a75a168e000553802d460f118e3430db7e4d201c4d56b3dabf0ccccb09e11106b12f258bfa210b11126398a48b05a397d715bb4589b63a5154afebf4ba3eee13b4c9ebba0a9d472998c0e30e8868ee3c66286e94af3df9113fb0bebff0fd6b2227d98c493b3055428f78a8905c298a020037991476d29c987316f6fc9692fe4d016dbcfaf96b8616f3cef05395a57b500081b42c9afa04b2b58184e339831699ae69907234042f880f3b5ac921fb70556228936631273e23e6010ec8755ed8be7d0a2dd1ad4d561baacb0c248f07915339d44d968d816b69d974aee51d5ab0c8142a258cacfeaeda7395a4b6780e8ac1f3bb7351923aa11427346301f8b51f26ca26c5f70a85ff8234098eac17014eaa75072a7c0cd4be4692e51ae4a4df45d7118fb659a43116ed4a8ace0727531f4d89f0931985ab5f92b4725347107c36d9931c0755b9a645590ae7c772ecf682bf5fe9bc8af1534f373fa0fbd43be107401d813ba6e3fd8683b4278360cd9c58eae05a30c73688c4d00a572f7c48822c3958e1f24f86340810ba0dacf6240be4edb0d9de712ef002892c09a5d53d5848f4d644d6406f7bc4eab342c727cad233073e1bb445fcf57207adbf921594bb40f5b650ebf6e28171079c8f67f53b6e1f1464691c983ad01268b8a122b523db82ced2db50e1427885eca64fd043b05939fc4dc42c850b8659e2f8820b09d30576f5deddabf8f04320e99cf3d0d7d76170fbd80d89c41956af3d01742d1b96ddc2408516c7da6379988c961415c3cb5b3a4f18257d6bd9f78983d587a22b571d1bf28d6135a40c7f450efdec8f723f1bd269f25334d2846c78b9d0d7a0fcd89a4491c11da949f8fa25c31ec854c6e58ccee99cebf392ad55a8db5298ee3fdb48d7ce2c3dd79d9e6cf6d3c5062feb5dc345ec445e964098e98eea2d6b73ea84d74d5ceb2d1c7528f3d781d23ac39bc0035832beea25a305728e0981309c8d2e80b9fe3e1cdb0e2c93838afbd74e2f622681c1f3547df891ce08940758aa087accb8d01291062ac099dd45e6a8d8c5c98ef158bed34df1e5d8749ee5cb1f0a1a6f33a13ddc20f849a6ec97f0a1abe680bdba22ddea919f8ad7e2938861f0b6603f488a01c2456063b8b88bed35d2d91385d73516d616178944c6085739db9b9882c70f69a47981b80df720d6e827d021aa0b37337207784676786832afc119e9ba78ba0ea0ab8158b6bb99a06d7e5cd5faaebfdb32bb8e1dc0227eb7480ba4b7de9466c36ac664d496dd00ead8326ce5cdd363121647cb3060d2ce3c5971e21e4fe044aa3521b3368869f0148dafcd5fc7ccdb3c7714cbb02cfa3d32dff30dc0944064b3eeca25acea3133afe71913f0ddc79fcc2242086db7e44ed180bfdfc4137b33d7588c741ff66d8d8b859278b13b4666fe0e42ada00cdb47cf2a60003af029fdbfe3ea90d6f5caa938c4cddc1f87ffec6c71a83f1a2eaacbdfe4137b9e91aeab506b647e3e34c628219aa60ffb7c4602b6d7b0e619be8cfc7d18cf7034ddab0c12c566eb9ca2444c8147498ccd8bc9c6170371910f0d2b9b047157a8d2ac835894f332687ea79c77442f6e4069770abd8faeb7484c4950403711699c96943b0f0c54049fcd526a4a20337a50c9d36ceab620e81c9c62ba9ed26f42e923b84805b161169266408eb53e421fbb4bcc965c8d950139a9cb848479b056a403bafc80b6bb1433860c80f46a5ede415eb4efda32af3d2af78994ecb52e4ca143b0fd7f90d88354a74082434721743a6cbbee476f47209ad495b686c1e9dc982f20466ae35719f56952d700fa5824e66f115847d96a84bb6fa1c7ce01267e8261881df283ec18234da04c564af44c4e5f0ffacafdd11cbb77d9107dd7ea3b71c0764f9b8422e9b961ee098eac7b9394a39c3fb0496a4432c0d41bcfa9d1f9db33f4803e0b4291751e0d54f643b39d3087e0b03612f33cf026b63844757a2dfe8e8b8416df163a3e235d78d97551804ed1b3402da79628e201fb8fcbf37f89e67b83720508c87cd1dca7e18a3dcdcca77c39e1335ac893aeca75ec32430ea66947d2b8f33a8a83f42f054d68c69ef0fb97175f5e9fa021cac2d26fc55041844b36569802066799c28f9eda66ad327caf3afca7f36b374c89107e804d73a526215e336407093cbddd049b20af61e87cf4de8db012e715afc64b789458f5ced4eeac13a4843901a5f307374cd4c65715d3966426e22ab9cba052d21bf7d9e3394899390800674676e57543055ecf39bbaa26f23b64698d1c6ce8b6f8b8f4e49ebd2356b0d73796be5e2b3f8f94c636efe05f8610c938fea898e00d8de89f684950e5d8e111660c9a34e64f294a8354c79300234b8851a66d3421bb30cc7fff679ee09a1fb193a14191f5469f4c4f2e8e44a24712ef49b1d62d5ddad019bcbc41debbdd2adb6d76ae3388edb7c5edc4ca735ff8e3ad420a81afc1735dca4137fb686bc46063246625ee858f100c1b061af0a9b42e5f514f1f4f5ac57716d035d09bd3fb36441cc45f2063b9823dfeb5c1104d1e8018455aec96ad637f3268f3215a3588c04784d4ef9c62f2c15aabf04c003096115bdc4e6b0f8f52359a2c80064cc2fcb2addda6da3cd74609fa6ff203bbd83f6de18370896db19f1856f45f5ad38222c1f26833432403110cd9e87b1bd40e15c104e7085be3611e9b22af1b4b44264f55f18b9b4fc99062c9acf7ef9fd8d32e81b04c1adb8e6c8856b974cc58ca8849c663c8dd77f8dbe3dba21019bf93ce798937c1fd8cbf4b83a907cc4aaff5eab5b8be97da735adcf8f336d58933bd64666aa10ce053e3b1351f8d9db19c8f579b43ce03afa1bfc8257ecabe779963189bd0b1af410e1bb3be64785d70c96c9c243f3f1a0a3202a7d1a9950f374ec356ae08950b92355ecc1790edc23a9376367fb75ddbff9dc5525da430b404927698c70b4b345e363a4727aac8ba398d15e6b011ae2e9b0021a7059654cb1731d067d68f811b6fa3247fe1e96205fb7949abe558e2338060f0073a3d3f6836934401d020843c88ef238b4d88d5a5c866debf9b87a9e8e79639ee38025871d1470c032b98e915fcbc67055a096faa52a7bc021116e6d5ab0592c3731e094cfa2873e955d609fd769d15d00360242e0ce60e18ee76559834a39abb0e45ed56607c853306b1c5956a6a48cb689c86cfe27e706b404a619936be8eb590db66082d45b157c138e2425c5cd7edabdf800fcf94c8591708b7900e680a0a4e9a14ed42bbcad7018ccb7bd0229a258234e3dd9e846346dcac58e8781ccf5fe02b45ed1faafce4d1d442946748dc8d1f4236ffd1966ec16f588753f383fc57cfbd575b1a98f184f43d66c35a98fae12b3324ddad9909eafaf4952b4042037a0609376a0164100ccec77f44360135090617547e331059530c939f59df054c4f7e69486939a41b26a744f6a114241a44576b2621b043b97ed71181493d58920b6d22f35750f0dbb28b709aad6603b19e51d5e90a8b1cb51fd5c14958ac9ee4e1ac1ae7ea51d9545da7f4ecac3e7dbcd4534538636c8877a252e10693d10a7d4f4e870ce49abdd70267b55b44c5a94882ccd83ff7f619c87670a35824ccb8170a21b8f522c9bd10600070c8e60b369edf48983aa7a20c94d1c84cdf849abb07d2ac774340889652a7fa93f10fa3915b8b5d87a09e7f21fdb6a44254116db6845fe72263bb3e5b32a3f7124584295656a64309272b4f96026be9021bbfb85383f2ea6ba9ea62bc29d90bbed57b6e0263787a380fc83cfe95549e275da2e49afcba1e22a425094e4f2829e639ffb0334589089c539f14c0d844a34672b41ca24b2a47b903e5d9dd55e7800a981f0f56084e80586e022e9a4e67962ce1095c9c7888efae508e597a35daf101883829709393bf90bf46c30a1009de2e4d8fb7337271a280f71833dcd90ba56786233e2c3b2406e848fdbd34bac20592160e2af215318b28dfd4f331c6dfac51d24f8a8bd9b1bcd8298179b8f0707386839d314b458b0210f7192f0396932d236d44395b089619fae55ba8ee17199eeea8c00e429eb83221081e933bc974d59544a4a6964c225b75ac7a00c3b5614dfffb84adbb8465a1631c2f015b8bb8f1a2c506580a4d5235ed15fc6351182686afc22d1635c45fa06c113e60e3d21a42aee15c4cb5fa8e0a7d54a9ad9bd903cbd8b21744d7b1ccd22800209216468a8ab3deabe36c5614d5358401268ae084414528a5761fbb02f0eb780202f4fff22ca9962e8a5fc2e98626a7c3dd24fcc6af35e07830f0cefd6c83f215512a43a0600a095e24d7f49639a38ab97124ca14e18734f1c8cc9627c91e4066dc1ad0a5c982841adaa022eec294296703401a3ef2844e3f1da98a47179eb2a1cd1a0abe8e55e72310b87b842249081d1ddfe285dcd535756546e80e4eea289291a89f7287dff283b85c03b1af593387d70b5226b4f5cf67b32d96f547165a85214266554a8e29f813e69b92a62fb50677a13ebc90e306ec37ce0cad6dc840ab6039adaf0d102b0fd3e996c1db4859a381a803fd3b559d784ccd1a874f3c26001570d67b20c6b0f29dd9d73decc867c9605a3e0522f5bca98674f08ac6d8c51e9684ba962d17158aff6258f492e2fc9c8b4e63335c8bfee73cb6061769e7ff58de2d9f4230e9280a4e413334adccd90e232f30e050c5fb14983831e56d1a986d91b40a818118ecd6859ad055194d32edecbab20949e71f3429ed8f19040170ea9606d729155bc3ef1ef0c930a4f22ac1914d5aab6db19cfc33413f5f3fb7a4355455440ae800f07b1996c0fd2e733a3c1cd311288c17e041cb7b7d4c7e0a688091960ed722e2bf5360269b3da2bd3c1e5002c961cbd0c857c4b073b4e5d81f9e0776957e18343df868369465a18db26e8dbb88b34489e9943db5643d1641bfbf736c5414db186eeee88523d01c97f062c1c001128083b67a268c71825f9005a2726cd44a28d664e972508b3f82c422b81340e89c80083e28e1d1b8062cee477178780e7957700cd2b5ed7d414d4a942588850a4b2fafec5e1305f89c375ea485e27f0e4cbeb68ecc244257999f84a06c77971e1a5c0eb10ed63777123365b7583f15a5b1fda6c12418e3d588f143804e98c12b157ecd431afc5ebf16bc3453a551983de9306fea8d033dccae7af2cb622efa7635df0ed1b1b9c8073073f11f2e68ca6032dd30a0b2e1f28b46c8e06d0cf7de943a96d24cc2d7a3fd732a28d6e394e20145b19c5459e2a38ab33f81114fbb0cae78269ef0345c4f8ab713fd8f1138e659e59ae061c48d6ef8ae0c8d3a33d468706303006e7c89c9f2361a3b1ff6bbe8b6ab57cac7ef633c2a779726280c9ef1572a7a3fc0660176a85c3d32bb5408ac7f22adf743cfad62b365260f35d2d78d358f9dfbeb069e44f05f5bb52c8360466bcc989f810b071e23ac7ea248cc8f6af1cd31925c3dc02028ca912cc0a4f4d380a6a1e42129ea9ec32acff5a1a81091414204492d519e9c775f7832f200563353838c342e58e0340f3de74a3006421bffa8b65f41900b5f73df19b9c79df9f8a5c725734e3168d5805eea963b6e5373aa09405e995d7734a3c06cd79676d92e8191b1a162b66a9cc5e165e70887d7ae60501f5700452490a27016a6d453632981eb43dea08c2b8fa2a0ab86672c7739bd89753bb147c6ee05918221ed5bf0335191103abe0d0c89603aa15cdb5b6c406fd865dc7c4d2d11b98542a0cfd1b0ebab400d24e83e858cb0489f192579e306c7cc9ca05dfd64e2b8b1048b3d9d1b48489fa6113e687fc12d4b2ce0c5185febc55444c080ef27253a0068d59d8026a69e42e658b69aaf739208af5ceb9dc40d7fa9ab86a6cdb2b90a5bf1c7507fba4ce7d0c6ef8f39304cac2ea1242f98d820b8c32fa55bca27172b10612da1c32c716e4164b8e974bf41709b44db43d48f58caa1b08ea5df9194a1c7d4b98249414c5d17477964976cf97e80ee07f2451ba16e9d95e919a973aaa7d465c4af61823a255e743d13960e8329106e11338f1eda62bb4c4f58558332d2a0f82ce4fd162896817c4209116993473ba69bb5c875a383f82f3f15c758736867484869f6ed82fdb27729d6b7493b81a4ebcdc4fee3f452b5b6700259767f51319656967f5881949c6b123659da583497c014acccdf4628356085fbba3552d6a70e95f78fb122d1c534c16e83c82eae7dfd74e89ae8a6ef216315840cc46c8bb8a4a8a6cc6c8ba083d780dc3ffdf83c40a0d478e8e070b9184e2d061eb600273e5dd3c6f0bbabaad397104e4dc24461724430580416a252b8ecc912449c9aaca34a0c144d800de442caf24ff6e6bb5e330274bb420dcee0d23aa9581323fea7c6949d120dbacb902752d7924134c9be40fc602be986e54acebf6483d1c9372d4d729d02c9b643ef5ed1da27132cf2778b8485b09571da7642c64fc176c20afa2e3f6d0ab54cc161ba81b4e48837f0f2cccf17210fbce27d5dfbb7547aab56e0ebcdc46f60f6358ac44ce68b69416c46cdafe55f8b7cbdc6b9a7ae0f3bf413c57740075cd81dac8ab242d3638595bf31703e7c12df9d18dedaa50ef2832583bcd6a8ddd0dfb04f994577377a886d84e26fc217be950d7571060aac903ebb91a3d4a8a76c934340f7e953f5a336ab2c388e1c0d74bea71dd945927350f04d69682851c5b8d98ec1a0f2ce23b2567f878cf4959dd6e66461bae324ee67204ef0095a63ec4af32a5298a91698128f7fce043af6c1a9a13d9f0467e54d676c26c93490a86d1a350b8425459fb2264197fadb5fcbfe832d739969c211ec18a7fbe4ccb5e01ad2ce97bb26d0809a3ee6d0e2b07d24f6f7d311babfd05ff5fb54d17848c9a8e100b2b5f8fb119d0f98561bb68e53bd440ff0af1793b0bcc013e37219dd617df3160e0f5120ca8b47f806c165362b2dde6ec745be20d81838950af92ab1e0c551c964b2f24c41544edc72cb157b04cd6612dbece8a423122e8dc4087f1f506323743e24c48a8074ef61768f84a244c0477cc8357ed331ef14b93d094219a27a01606c92dee8b4858c2e94aa552da1fa0cdd7c60085eb183ba93e69ac0eaa1e5252dd720b8771b31bfc8f8e60182424f9001be0031406dfb77dc1e564e54dd9ebc03de41fc00d392df0b38098f70156b9bfe0468e5b550be6ce20be039b3ce089e86e6eb7de0ea0c0055d2616e54f4b4ef4b8f3324a726fd3241e3a0669b535c1c790502e684ca0b74d20b67bb97bce548ba17c59ce34e5c4703471a0e1134caf08ff435102ff511f1452fb88f6ec7970273a5fa4d00ae86264ce0cbb110dc4012b38829a64442bf041679c2662d466c84dfd11b075d7fa00f25e4c26d47af34e518849a4bafa24cfc9ccb4282c00b1c8b17246abc0250089367d87d4848f4a6dd02c55d0200ae687d249ec12b92c28c25f662800f2e98a174918f82c4099a73002c0670ca82cea87d6cfbd83f31ca2929976d24f8b76c9a30feba69e98649c71e92d9a9bc26a1fe00b92eb5b3678a34865407836371eac2acdb95c01797999b52f41a04aa4d03a4dce48218a5876725106feae704800af3ac5ac1a8c560b257bacfd6f48874ac1589139c72843e56f9a9a2e05a36518902420cc2e8b3fd8392166290604774f9262c86a97f334bbd19a69f004b9de235f8509b38da70df740b3309d18c9d61015c755431586de144aefcdf61c112332fbf57ed9f1440a313ab0d0d203dfa3d6e9aab6302b4b30bb193a3a81dc507a8f8d8d5afa6d93ec9bb9a9206a5264a1e6829d76795966da7c52207b557814ec44fb7ef9402556e00dd7e8b77e912a91e9b4f81d58c2d8f92fce7137d14c7e5378a87137fd6476305a5bfe706026464767d3c95e13c90f12a213bb6df4f24f4ae0b86d340400ec579df32649beda57eca8f51ea1cdebef146441385734ec261b248ccd34e9dcf31d7ecaa00d232b20c32109feddad2b9f8a44a4edeb4592cea91fad4354880b44f0f7ce331926ea901034dd14dc4aa4d0249e9cfad04c1b967fc69e98ee91ea3e0b9f579b659ecb17f0bad0e86a4b43674f53078c635bd95ba867eba559e8701e5197777fe700f763e9e6c872c69ed277ea3273f62a44d6eeaa34ec59deb760ec1d3f87274eae7e9dd5166b7ba5e0bb3ec55e60e7e1b1badb3a55b85e75c7968aaa9074e18f1974ccbebb7b9878606f58f6b768c297dbf43c81e1b9a79566cea520ccf1643aa7fdd3c36e8008f7a4cd3ffb2713590d86a01db54c4a64303225bb16a3f112a079c6923573a0cebb6cdacd9518e050d4e0e63501f6a5e7b1ff3f1fa5132e28d7a86952fdf0cd08f567b484be74daa7def0b3877cb178657f88a8ebe6f7983580ca78f16a566f3bf63b1ae1f2f91feb74cc281f63fd8a251762846f9650a8b6a6bae4367e657b05763f975de6cfffb9f2f446052e97d9750b77b1b51006a7398647f5032db8bee244831731ee37ef923b6b4780771bb43bfffe46ddd02497929281e51fa7d6f7a81aa5c436d571d8c55d77cd5145b140c54f9bb64f1c57bd3b07b4316afea79cca3e8c93b4b88365a4fc1f7ce8ad25de48ef40d37dcb0a7e90eae4b3744593090e3c6df0fa30d6d7bdf8eadc3fca6fd2b1819db3d18384c3c19f18c54b8c58bf3f09b631a32da10fd1bee1cc697f84752d53c2847a6d6d6372a90243de0507c823266b1b19170dbe3a5689f1119b0ccb7f2b5a4cb94a9c94bc44cee28ee8c821ff042b623d38b9bd59f8232a839657a2befb2696e0a0ef29806a4fff8002e04d3080d7da522603ce48a281c8cc37ce0d2e6099cbcec55023d16a2d11491735faf87d4c17faaa1f57a6a1d640d5363160b9ac2bd85e40707fe5668e5ba35dc7caa61a33ffbb2b07bb8061ab1c58fcac48ae4e9e31bdf1f54b364826a4fcebaa18c18c5dac9143c27477e393b833c6734cecce468dedc3b37e58d225041b69282a2686bd32a4fd29b629f59a52182f30adec4a552e81e26eef97c6cc140b9b93e6c1b6ad604de586cb66bf450050b4e66fac9ca101139f319a191d1e141049bfb2cebb450fff47630eef3321e3f4a9c4af56ba09f14bc0bd6a4c47e10f3c767f7d5d10e4287302ae1c1a2fd8ba022cd4ab2c927185498b02897e81f1c9737039b7db96cd411e481a4f32fb9a1ef35e157c20fec25d11785f004f189977e255a51e314946806da863d7d0b266b6353b5b4c601047f5e738a47f6572deecbcd964534ba1450e8e003b98453072d0901b9605f93d208ed6252841950c4f92f6e3c4b168542b12c13055341fafdcabd2618b64dfeb72dc78760b8751322384f9ab327f13ba7bcbc0153a9289482db50157814f853703e63ca342da2968f3001f74a5bf7486911d636ebc8fb73a976e1474157a0a1ae5edbc0096fc0168102c6fd942b5892f8e3eab07a1a91280cf3ca39fe4b0d5ac52b35accb0f3d33421f59e089fab9ddd5532a65387f3ff840234bde1b60c491f32d84ed4072e34fc1a0ac3821300317bdffa394d5061bf7b4db922d068d76686778c9c153601dbf0839c46166931e173ad2fd0adaad64d7b96f144ba989f75f976235abc6a8fea1b4fe68625bf4302eb20e25c0b5485334935a462c9f2cb614569b9ad17223d29b7b65e3beebffc9f7a0cdde86cf36c96a6038dbd9261a3a5c5bde54ee320ee35df3e9dace6576a64ed2df3ba2cd2c4075f2663c7e470e2020a4418f4523cc112867bda7c821539c4bd3128746a434dedd3661a1065d0c9e25fa89a7a602b4af9af96b53aaf282e570a08314bef3a27362027b9a38933468cc47e6ee7e53d0398767df534d1733028aaa60706b1f9a3b6f93afcdef7482870a9fda22bc47f6f790b15b3f25a62862657b39c70059aee7487c463f5d1124362c5eafcf4e772556befbeecd0071a7bd3b79e2f1ae9a4a5749c5dff6d6bb3af66e32752c8f2384102fe7e6dc20234442dbcd831b4ba86dec34d06e48d2319312d790b1247bce2f4f91bf06bf7cdf9b7376e21a21276a505e97c1f333d99cd07a4a783d2151b544c4e90f04cd9e047410a8a9b05a68b4853754603289f1859d68f36b47f040d458eb5baca63c9588000956483f8920fa09d479824a0fb1303c7442afda9ccc15b2d37dc7b21b31821c2c050b139ea5789af4565222f4b41ba8701da51242e39d5d88e233b830c3834d1a19b34b20cf107a021014ceb03c63b42230b5463101d64039cb5c8498a634ffc37959f208b0065ae4c69aa257ab8fc51f254054826a8b231087a47a5859b8b10e9db81a28264e118c1a0366e09715d3b306c315f50f9dbdcbb75cf8f20a69be39bf3de7f7dd09a04bc1a06a72c8487cfb1f2495765d9f300238c072b786620a251620c056df492b8d30d7c697f1bf11fcff2c60ceeaace1bd9d9b3b9220ce74ca788ceb1f1bbf8b49151b1ad098a2b9725b77c7d97b70175d157ce04cef56d300d2323a2f013b3cf3f286e2e30fa9861339401f4bea1fc3e2ce2a906da63e16ab3a771789335db2168493bb3a661ace86f4a325fbeb6f8a2985abd50cfc439ea1951051755224be8addc4b391087f9b2d2bdd90fefe63f8ea3a3748fb4d033ac741e80c00c4086cd9fb4fb162496cc39f1f9eab967a411e4df91f95b1344c9703f4a9a5a1b0fd8f4140b9796a7b1d30e6347a3ff84b32ba40b0ef3b06c3ea2ee1f436fc7607b1e32ab0f641dbdefded14fa3121be0c24f816c560e0c869fe03ea852243f0e41f1801085c9e7ca4fba21145663582bd1366d13c94aa60a4b3a91fe6bf8c35539fb6ce7bf2bae45a18d40a69710b581f80234e42200a6c48598ba5adef8b2b5019abe6f54f0371f42bdfe713bdc3f5fb0e5b04e3fd3cbfc27dd3abce00470ed7e85d3ee88ccbc2834762db9492ec653b0ce0200829f30fe17c530843c8baafb71d3277566960a42fe12a4af46b07ce567bb9676942727b58b3bf6d50b682da657d1e6b0c2d0e60a87dfa9c54c9609bde9c12f80bf0054675926e36334b325b43c3278e904a635c69c906ef7644f88b4e1fb8d71530b1fab4169bd3ddcb18a760d68ba4d53004034ff9d65b17dedbb0ae41d0e361af521e23d002c8a5461b4b055f627f9edf46691c644f406fe9fa384deec3c4e25b39f2c8cd1067c8995cb49f17111cc13c5f941e852044359242f141096a6cb457949bf4fe39a01c6033fecb899d6edb888dca93c745ea2b8e794a0404978e23b0f2007df433dd2e08045385a097987ee247ffac35ab803b84ce60c0f0451636ed9a117a1cb63e801e60055624e577cc3134fb20605eb2330d75c59c03229f298cff9063ec555b94f443f8e85d3aedaf701265059e7b79b0f5da3e887d700e7a6c526f6e6e13fb137d4bb3715a6d35199c166e8b54b0d35b35d4d31ab709802ef5c9fce1f8468678383188322d3fa0b653b9cc92b40a6615294b8e691503c1eb978f22f6078fc74971dc42096cd69ce91cb63a05d474132bf84a4fb8a25544468433dc4c40da737c7f6c7887e044f49030f3a1f331eee233adb0275e68eb4d04e9381b25ef4a306234464c152ab1f5f2943b3b3ff9907057c216dfdb90fe5217160bd416db5d07306e251227adcd2a55211d4e46899add495c144081d597adfbc6903bb2d37665fe4228d0ba046805a7b91a268fed4890b13e3617bada7bc5e7b1243d58ee3f8c30969469efe387a9a0c6f0518c08d5434fca2242d45dff0e8ae06e3bc86088f0906f5ba64bfa8a97d856c5e4d9c6673242d0090de394cbcd96383b228a38da131cfedcc260bce6fa0568176c79667e6bd06e05674a57b5df1f326421a44677c03abe9e6f699fb277476b82edc23598e72016bd45ff79395b03b90dc2c59883c2e9842d3bdf87d261503f2f6d6a02f34105206749fd2694e30b46832bdac18f99807b211551053d63217f17904ee1ee1ddfd3b2092fa1fc5d5eb00be060b1e1d5f5ef620158554ba4bdb08a66ad8903611a6052e16cc05b44ed716049148bb06620da51c5b993d2764a08c255409ba7fcc351baaa0f4312bff980be38048dee8bdaac6642a19512f59a6447aa79d40134027919ea650370a010690b8aa2d3d994f6ee5ac17770650c6c424ee55f87d03e07674b0f1c883571ab75fd537c626a0864e33a3db0dfc9a2b0508962a50ded875c61309ddd3fd25c2a4ad2f535794bbb497dc76a980a9f3a0abf1c10b464032f1d29b95ce48f59fb9613d43c638dc8a2c83de47ec64ceb549a49ad8d7afc5e1a4cf9edcdb08df892a53144d3d01b8bd446990f191c9a10fc27fb07363653b740ef5a9fe49d0ba098e0d6d0843288c186825dfeae34c986e02fa89a741766186b64873c8186c7a26b2262ab6b424e37138a73be71868bd4bc9665e93337e9739e260a60ecc77b5734ea53cc06edccffeb2c5362ca7afd97dae8152b91ed17450108f34cc0e8c507fc74ac50a24e81020ddadefed949a61b97feee16ce03c01d90cfd62659c0441c8509051ec265c8761e04846108025fad206a985cc213aedb81a1968e7a32ed7dfa3a519f2462a0055ab830c015b01a392966b74bedf6c82f87e53e6abda3761af1d57d9ec24dc0b9696ffeebcc0da33bdffa5df959e614b515b8d038aa1b75652e4b4ff70dcaf6432d2234b23743edf5915fb0f1b66d46b410d0c807bfe1e879af8ef8cf5968709cb36b8bf6040ca5592c56261c99695ef0ab3aabbe635465a9092980dbb0c561529e6afafad974c6bf8b8d7fbfdda434a940fe4c4b7cceaa5c1e59b9a9019c1eebdec9dd35107b87b9c879fe398012fdc8d75bee1daadcceeea70a9268ff9d8584206c8087565a8e9a100ffa38e5c6a36b216af856f7bd87b8f2d692f52cd496116a946d93469454c58c4aeb21e70a9c03de426141bb231090022d8a9fa45850bc566ee66c6079ebd8fbf0ad607a095e606bcf6985952d766c83f004684e21aaabb3e1e2860e2b5ef8aab5d3a82e3902301da98635f14c86e42e0ac6fcdbd9fa68d36302ef1dde09428f09700888410a1e87697760739a1c051f1b3d1287fad4cf0a896865ce1e45f0d938bb866723a60368080c500a4e4ef9745038ca35f84666b61745dd968f77b303b38943d123e718f273a86917adcea4ae46c9592cc12eb7c87af1e0403a8184c26a7551640369a9531b266d427f6e7e7bf6bb5360ddbf02ac4e269a9f4cd8291353b476b47a2c7a542d9d0a0ff5fc8241eae51fc041514bda13c942fc8dbddc2907553c4fad4979e40e087fc5308831bd6dd493098120d48ed233c51734c9bafe46bd43f73bd00e6087da07ce0c027cae06c1be6801ecd4ec1482dce0b4ee0d622603c1e7a68fe19927353162b6990d3965178592029ea7a230a32b4669c0ae4f523b7d946f9032a13ea9371f407294fb27c6c755a2fd13c763c7107d7a902a06c317b1787a3a250e185ab17772a9777cac248aa24b257c40215ac600447e91419b9df34b3b33d5617860eb47e9e8f43a2f8bbbe108f831e840678303810d0dfc65aca39d3eb7a15d0b2218cb4253b056ae576b02eb9871f0522596c3c4810da3d05a7480575ae6c0de56de1be8eee131b79b48df80007e5ff3c4d060c122cc9dd83614efb9329cb81349b7e3c7747999dc77b7a2d74136c6a01aae7bcc58e1249eb857dc330044bfdd9376d2ea0e1f0e9908ab617e1b991bb20880b390031ef98b5cd81cd4da398389a19d0bab1d2cd091183894c4cdd6d81ad3366266d132d71308050c28cc953994a0e3c72250e795f447f952f82d2d5548fa91aeee4709dc45b0a1cd83c14a6c40c31b592cd79ceba786f87534aadeab60fbf76e5671067a9eab4f4b7b514edb0b1abb220460e144c5f9f596181a22a83c38125465de84b9c1ac75a728f5c71069c4d86e13648df7081e2e61abd8fb9364f295a9a8ac8a9c780e70eeb99e6bb4972e2c9d45fa5be786870c261a1cd47ef83755ea8dfd5dd61ccc50a0f3d1a5a2756af3e4e3e16b4f0d7cbe852cbcd52b297cbce48ec4c71800904912cd3812d5358d010dfcd4b2a31ff06a6734c8ba0e6b2eb4b3bc84d1928d6629798c797f53b84b71cac80b753a725d7c3629564e2c96a7351efab080f5790e287af4332371622f8c65ae8444e6c9369f2621078e32c97232acbfb12c3019f47918456c6ba11acaf1f62736ebbc922197a439552806199a16d63d337c8f299b9c4b18fd7f98ea936807aa046c24b3b376ae11d010c74c9d05dcddda98760e2d1a25ea8d7fb9dd7c8beeacc3b1dbf572450ac2880d5da5050552895ff1e68b43b7083c55ae759bfd089f5b8776ff4de85c1b7a8eba80da1fbb76e5fa6eea2515bd705900f30008e2c06a3df32a51aa03caf920c692b80f2a11be700fde256851b0b2663ce407312136f68cf271fb5a44685ea0f1665c87c61c846505897e5b8bbcd365008f12868a0d3d72b39f57027e803046040b90e03e854cef90eae07401b2732ab6b1088c33958231d03170bab1376375ec0ea36432807864528c5193b315ce37cfd8ab6c094b565b28a38f1d108b13fd86ea6f631a5f0ddf9a0b8bfa9d7aeb1b437cf5e7b500bacaf122d1f75ecd0e10a254f310d12e76a09026492c713b913345e08f17750d3fea818e3e25ef7c8f98d2a346522b8c86421fc731ab8943888c1a8331fa3ecc93ac54e2721499431ad0fc7fc07782042b72029ff1f471d0133472a604be2b7793a0ff053d31f86fb165477431a823ce2c2085bfa73f76d8d0fdd132be8b029753790b5d76a8578a6608c205632a8404a813ac50682572199c57019dd8e51eae29b47e201a0114fd37a71ced0199f01e082a2f5da1a5774dca3f06735e65a451f451b6992ab55810c08119bc2f703a5aff66cb34f7487fbae4e850586060b75b4ba512bc58e8d7f0922366d358b99c8221848d4ceece19fa63b1a15083139f9b7585c7ec3591e490ad1b969ce807d26bab92447c4db0a336877906a5638b10684156dadf90eaa67b207ef0dc43b702cacdfc9716604e54db9da5e1303f98b4edd26e71edf70a59c440c35c16fa9f287409074b5e044878c8bcda86117e69efe98335a520b3868c4281c1552d69e90ba7303148d9e5961aa4be6f416da3ee7a4cb3b5fee69c589a19eb9c205e49df03bb3a39c2cc4d9da5beb2a382b9b1bcae92a186abfb87644bd317f76162d91714d8cc4de5439f16e97ae87253e2cb5b01860242f16b8451480cf631267c27e4d5ab73f7c10b03bdff95fa27933a934214554aca4dd8818c39ed52ee32e6f74f5bb663029f3ee70c6f4bb914c200e8c7f26f51432c9cf5b6e6b1b98d1756372f448bcc5a6580a9471a79a0fcef9f8ae422014c2106668fddc9673bf5f00bdc83cfac4801c68062a24347e7c15dd32d2f23e76ebc70a9033dc829abba897367599279c3e53007260c545edee6adaae4bf00ef5c2a862fc4d8f4296a4fdbc13848d696053b2e5c3b2ed72555c5b933a18645d02ccbf38cebaa2770ef1466e2764ac9adb82118d857a62a8b285a4811a8170ceebda4461a0afee8dc9678c1208aca04d76cad5d17017cdd982919c0d14af104340fc3a9c53fa4bc57e66f2307465b166d63f1668b3ab077a208dd6a71ca349a8b9385998d010e915f70998b6ef24c3122068972628c43b848b0dc3f8be20196565b25744ae714d1d108b2dc756a91aae99610ba5ff09d28d007800384a388778a431a3091ae48ca713756b51c0c76edc88e4828f48c8f59f578bc9e7dca472c1c307d75b7fbcf52eda0ee39bc528a61d1cc1a7080b63771de9817c76f63f6315b3fc81b46b5f9ff472ba2f3448c0880611e4fa4b59c6db981594abdc6235a01c71797a7edb1dba5d949a79111d94bb64474e8385f2b93037c51bd4b32481665352e0488f9dee1a20807cc74b68d97f9f3a0cbeab15bc291db844bfbcec4d3c2551b514b41d2f9fbd953e2e7d29a60b6e7a974e2892d2d9da894422f08ae8fb354e6c46b1d6c7916eb523b21682d18b3e1401119704c5048b98f84510b8998e15a15edb060d57af746e06688f7f0137ad8d1e0206dca105fbb59ef01325ca6f6b026d95a2b5041d95a3d6ca5d99d2c05024333e0b8abd9e5112e314da0c5f7d1f872e284815bf73fe0a12f163c0655560e7e06afd29cfe61c14ea8d0bd13483d0a825b5af774d694c1b207f52f9c07a2949db70cbd846c1d4a74b1ffc0310fbdcefef018c7d1fd07ba86d3812f583a6c07aadf5432b2f8b5794a81db4eaa12b94c5523251bd485574315360cb6e3a1a0b57772024ff68e06933578629d84a49b37c340b57b2f943918c64de73d6b10c52f806ab53f66c505fa4a0e5e3277b42fb7c6ad660dd9065576d103c0a8186b3d048ab2fcb1502a6f5872f054de2e83f1dba75dc47cd547da58a534365c4c0e9c316b5a5366ab01205fc16ea2a1df6e7f68893e3517339d8c1b6dbbaaa9e8f772c46e3ce0eec93ae3451639c92e45b938ff6e25a106293d722ebbd5c607ac91cc414923651e79896eeb80ec8634c1c8b1622bdef0be61fdf290f8a427e0724e929c10981a3ccadfdcbb324460d898d69b180739dcf3f8a604ae8884f2466e8c12fc7692b442e146149985247110ffaf6522b562ef36b8e951bc9d37f0a59a7caad89ca70aba61f843d91bf619a86356a1dcd3e54fdfb77c3458e92723cd19e1df9bf0af10097877365304fc8bd06b23adc7ba659134fb4a3e2764a652fd48a5f4706d404f200e127f6aee18089e8fb79acf967a539496f70858dabd6e3d222c7ded0d659f0b31f5de65c08e57403fbcc0e053796d471ac0dc67d097dd97d951941f3989f44bec62581192796ca8ee70e32ec11bf840b5c03d08a90d1613bd5dbe1420400e116c328133c685623c9b90a3e0ea6f3221bc83153d6b33b27a3fca31d6ef9db207a0fe8ce2a7f096a027e47bf61a001ec0824de78f16904338e69f40e80bca36739a33e0f9e4a38d25367a6b707c9852673978979f6edd0355c21a4c86333d76f1da0534c02f49d69c095ec8f0c3bbe69fc81ab4456be575177098e6541f95f6a45fa9b08e6cb35db217fc9b9fcbf8df2836c7761917728aefd2980c4ef3f5c4b7e162c119e7122d7a2b3b100b661b6c07d5d0de68178b36843940e844555523584d947baa9f1755aafcd479a9b31ba0150a2ea5df05f1ecf1150adfad1391d2da02d0701df7c46dec36f038b2374268fb816c859303a17117cae6c514c89b868fd14799f07a4e196cdf7f2916600653b4ab85c2194928fc22c7f787bb37248411e2c618b46b1ca891b3b529859960c3f18de3da2d69c319324406d743db6fce82377bac3f6a5aac9f76d36079e97e733eed6029fcbe9091b70229c0c0c9d8509ad098d2510e2b68eb0f8e0d8825e3ca221f192190e15c15e490ed850184bb1fa2fd34516fe4571844f4fd1cc07525c980f9b5a1485d7c8a26639989a221c43495788b68edfa9f39cdd2ac3707b233be416d94296941cb9810694b37d6236b5a76917d30f0dc01dad1a2215fae87de5492c00f2d512841033bb183bd40f4563aa57948ed7cd514b9f41095acf40c7ea5afd9e708bd4faeee1c4325bbb8467d0976aab8fe338df5a7f9585a6cf89a3411f9bfa8b804e4c84a093a36d432cb7797d40d1e42240a48688d9028e49500d16a6311fa73d023d8cf663f9725dab6013516181cf4c38ffe29501ec43b7a206dddde65195e31c3f808901f5c1ccec93619f4aef8f070a357aea741a7212f4c3184c403a1bd260734732b2b5631dc0bd5e944d58449df2afb7c1d3930f361638e60e768c68b4968f70138d7f1fcf881be73ac6bd8763df69899cf9aee67384013043a71ff9afc1ecb24c2019d0cd05a1fac33a7a1a7bbc1e4ec9f0db16cd0a7fbd55c810029405263baa72f523f92856327fbf0ca5aaafcbf05dbbd8549b583c0fcc839fe83c11104532768e12c0e730f4c636d13429b8472d271252479a6d9431906cc70b3f6d436208ed4e41be2a2f8ac9f24a22b42c9772f633694368ddc787bf52825c61eaf252caeaa4666b6ac678f0be2ddfe7b3a942d98c11b7a5329de6ebb15513c5ba8b8a78c9632278af1a69e4dd61a8e0fb6b87ec400afa5419c28e42bef576260d320d29abfddddc1cd5bfbaf0dc2dbdab6829032818842edb4cbb637a914666b16ab6d9fb3abb3676bafd62649263858e34c7f587922e14da4be2293223d01502904bb08bc9ed08dac5d75451fd15d5b5e6c9b52264af0b72df06c13bdf2e7ec4b3a85377ef2e78115262b4e3715d3646cdff41dd07603247ffb663c0e1c8c643867f017decf76cc8926d64a74c7b4749d1231ff2773243f8638f2a8d4a6a644169c5d620918e72f1242895ba80a6b77075ddce1ce14b7ea3f59c6bbbd69ec93b60a046cc671324d4938e399b44295e953e507687cc6638169495826fd200d9ba1a58bd7764c0b40076fca205d9e30b6b489767d9edb40d3154c180d2c9f8d1b9de6bfc45677f6049041857e6860bd87a02da63e52477f249d4008fd5a8eb8111d061e13cca731a60bd252c8230979ca035521aeabe88425d96652a7584535070871ae57e4298cd48f637accf345364497a7af25e854a7af40e753d5dcb0e6ba4cd44223ee9122305058ece4bcaa28ca5f3a689cf357800040acb5cb70929230a42359684ec3425980b2a3604d7718e2c3c7ab1df7896a5aea448b608019f5d05244fb3b94c32a56d2ac9e242c71e1f3ba2438682142156ae128e5445234cee77c72546087f25a12fd6d5734ae165ef03dd6ac739dbae8a86c87852db8cdb9f3d5e41a8cab007d157a88585b638f165ab369a3aa4ef5ba55756c557ac9613ed6ae025f1915441319beb6b834150929e3ef9b53d319386f32c066bbc21fdf64e5823f951363d5b8ac6487aba4c51ea69f05cacb1a7bd5902e41c4ca60a40190ede1eb8f40395f7befb96368d1c6385797202a1d950382a30090e45763cb80b171e752c21a95115f229eeaf53364712723d0c43882445ed5727949406ddb748eee54b58aa12ca1266d6d64335325ca8a41ca5ebf53636b0d8b3406623ab64dd3dcd03e170811c2d240e2b7450a41959e0c5d511fa021eed3f2b6595f26533bdea8fde954e429da466d57c083ce60e6a37c45aa73b09ce4a7a5db3631cfb035d062172d29999d152b8c18d95f14b0a06cf2973a2409aaddd33e7419a95aaddfb24b474a47b938085cd05d30e39488c58ec4cde115a22adb53c73eb8aa639a0e0899546924b60b0ed17ef900ee71d1714dcf0369a1d88673aa2ef91b3d6e51f176c538e0f9d6b704e9c28b41f74aed38847d24c41fb29e15e74ad07115820bbeac442fef730eeb2773b4491d89f107dfcb035a615e21ae35a44672f5c8a32f6b998d69cbeffec5aa44faf83bb3cb920b83c15999ec72aa74d97ac8aab60acb75b03b55a60a9a4700d676cfdcaccec6de6207ceb69683aae664fc3417ebc859f6267414721ba5a060a35309816873251be2e427ce84a56b885fc0532122e15e97fd8c0b50ba9fa841b22c32317153511fed57225f5e226c94ad7df0cd19a27057c73d8c3b30c28f188c023b559988bb622921d614fa39d44ce9a99fb50999b638b68f406b5191a09852ad12084c0a81abe73ee05641914f1c79c09e8e5ceecae053508f89ad8dc16e86d6467cd725734aab7af6a1418f878ef5dd07f62e4e56c7d90ae1b53c85f34c1240dc4d481836d760f7339d82d195242f8e02cb549c91306c473ceac77e2757d273d4113925008d37d26c7122dd2b84137af7fd0c2b413cfddd059a198990dd48c829200041c0b198454d4855e96c6b80124b8c2fc8bf3fe4012d2c8a66aff10a3ffc5ce749db3d375255006dfd3595c9bd047a1284b949a088567e90f71934f93f26d52ce4d885d3901b6892fb98dc545c0069235d673fdb8a234ab25630db8b0bd09208c058767301ef4d1daa3c5937894f1dfce6e64e04a44a102c6a7405040a7d1abb86de9e3a4671e3a9a41c3d15d6560bb0ec57bb935d94e2c1ac9665f64d5f16d870de40f1590faf5e00b6c9101a5aefdef8345c2460562196263a12f84414759b59b11f53638cb5ba6d16e7117b6c214b2dd8c00284f82db58479fb39e5f55916aee598ca5a919378ae223eb551a221719f8bba997362383a3d02cc8713a83af6891bd6a97a377598d7fa3bfa6c383f932f205e94ae870db4d77c5fd50e2aa98fa3255077a5b6570f5f89faae1b94f8b94348e527105a0500a130b01ab4cd41a118dc062949d504f39f69c75b11c60253d77eb8489d126fa3d56feca0910af86b8102934d204e1660e6b9abef805b706c2ee4d4f61916c79546ad879dd89fa6768062bc54df3808e2193d6068246ba347f628d491558901d7134b1cf1bcc14efdf173935a25e50f329ee9ee268c6dd77ccc4d30754ad52750857e48c00aa2359542a743335982bab029a86b114286093d35e4438d6a06092fe954c453ea513687f20b5c15777ea0aa6334f88dc5b6e2db7663d68a7d2ccd2df25492440b4afde985aad51ff4613096e31f2c56ac3947cc30316b6aa8a5067f190aae45b8d794fda0f68af00652c1b4571897505a12192c9c2432f1fcc38b7728fe91374325282e2a975166e8fae850dfcb57325d1d1ab0b59c51fa21b9324ff3502e9a28e99f5655ba6084d0b7178a36e9054e8380417780398ef287cfbf1f9b3fb7e794b11aef72d3674fd5643660cb26f56f670c9334c4858cb2e568197188dce2f173548f630c731e13e6b06cb024f73c24a83428a50f4dfeac2e8ec01a5ed6f810c84ee4074f4a197076931971826f52e017e85955b9b4d00865cab472eabcf95017c6a7eae3c95c48ef85a2cd25b60c19967faa17f8e6bf771b945f8b512b491b33445155fb1f1cb27650953e90f0b142e73be4f9499f19354ba17cb7130768e6a1b031ba2b139791d26d2e93fbc27c3eeb5094b26164914069187e972a329eeda93f1e021ca283fad918281c823a1c8a6b47668dc27c3507f77f65f2dba585edf950ed6cc744071e939b33235961fe6c2924a61d18cef8b81bc203ea17960e0604063e849f90c4b4b63b00bd7ff0c198f6f23c68745938b0553b860cb8b4540063491141d3de3c6251ba2e8b1b9932c32db5e6508ac45b2337a71e78c9d5a163d62892b7c8ff22290e0f791a988f79827cf89122fdf1f3022d8cac9a48896be0f04e789121fa3399fe357045798c83194087273a59264696345a8ec2c9b9007e3a2766768117ad673625e11718e3093f95fdae9d3e0ab9bfc3d7d718c8bbe80952c2118494b6ef6bc00c2b5c63c4b30886566252527a23182f18cf41071082308ec2c4b59c906e416ac2615f639a8c204baaad56d10b5b6d3cbddffa53e965d47372a8829f6e29063f557a4b70b11087ac0de85bd016f504d982c17e28e2ef64b53c32d5c2827bb146c17496c79c968be472d2d9205f35c93c97ffd58c5033c7ac56964ad74f04e44c87ff036242ae0e5815f3d96142048460146963e2beb4c268174e0c7536519ef62f3e3101f34345ac81d5ca68ffd780fbf7dc0682270e244b6364a59b82f1de05f2a1010fc328597b57a1881ba56062004dc3b8039951eee9d22c917f8a7a091799e3bca6e7cb3dcfc969f22c9c0b00dd3be8cf32705b32f707ffe4ca93b9d6c2e0b206437e85671ea2d23396f4de114279884ba9969b858c5649deb5e8fe857893d05e8036e789b04cd38175427b502074c60479d135ed8023074b79d3604497c35450f127c2e105c2a780a90865bcb7cffe0a92523ce88c42b0cd352224ad810349bc38900e9d76d23589428557bdf92251c358d1797e4b7c9fa3022ae54dc6fb211a4a0f8b524b4d9416f2856118896f1d90fca643833cbbe56c94c36e92b60580dda94448d013b097f6e0d5e9680ddb2c0f7ee0cea0560488c60d436e4c30bab270148bc7815a62887da6bdc825a48458ff0a315e63484621f6b6d8c637ff3e9ec8bd2ca773516a20bcb29d76e251028113606f8d22dc54bc4af502511c7ffb4d63d774f993fcf982f3d8dffed11158d647007dd80a339c6e7538c4a3b3a08e42f3a1bf2701df21eb14db10a71a025b9e9655debd9b1c07714a4b8e9c743548d318cc4f81cc5dae5085f17838e1aed5d702b3419c78eb0ab7cbb34075bc0a7b7be0b3ed2ebd11404083c87e6e217689cb3cbd95b25b96e26090a6d91af0c1793244a123a9fc7f6408dd4b4453322e1a7bcf8383bdaeb7cecf928af280688b4f5c259a12dd905390092a36deed350a154aa77e57be69c9464f9f0f560db8dc98479132bbb4b644033924e834a46121bc03ed448677179569cd98a68fd1347bd51d5401b40412070d5ead5f419fbbf86155d4b6282941b211d2a11489f35b08993c31ccdb5f88f11884b1c936ed08ad722856069491242b0ad1725ab4124bf548a2522d21451e2d653bb46c1bde318b7b1b414d9a1c91e598062b09dd5bdb01089cac2c8a6982c8193615a32e15c14b42b35d35ba49239930723b1be748368c176afa8b9290b121c870a52e25c22603988895ec2ea7a93993cc94419a0576720634b689368cdef64aad2a059ffc3d2d80b0dbf585f90510efbb792c1c347f033a9fb684593092324a1b554423385c021ae1b80a39c71aa2c5e5cc0b713e0349b395305ef7446283de4d253c0b185365743e2037540001288239e2ccd5937e051d5a8c51f8e333ab2790ce3669db58d16a03fddf766b442491c8eeee4d3b0608cb075e088e75b9f58e62a8c3f6f29da7bd3cf3f6cc66d6b5519a67edcc4c4e612c409e960273648f1eab953b0a75afb43e57f0f124df56f0b1e97ce78bab2684e4ac9a35a75a91ec492ea75d7291a3a53d6f2bf878eba5bce944315d02c9bae6348a397adc532acd514cf79819a7bd063320f3feaea544192baff3aeb504935722cc13e05dab8a97269f3de6d5ad70b102e59738fad5bbd612466f65e8edb783f93a4d6edbb3ac54511ef366491a11ecaba609b9b6db7a470d88cdceadadd563b4de54aa471f68599ac3f0d5d5da3524d730b7eef33a8c2e217ab348aed114c1f2f65e739abcbdf69aacb9adbe0175ad31d7de3a47a12018aadd94b8b7f281156f3de637ea98551d866fbcc1b74fb28e751824f0b72eb2bdb773a218c9b7cc6324e598372d03bdef1c698ca4cc6aaf1ead539a38a47aed1a01ad499eaa713bc4bb4b68b758f796c3a96f0da6a409c1bc33ab5d7352ec2d47beb1623657d38454676159aded510392f1f05d43c23dd7bb07d5845cb74e71fb09674e834bfeb08d438cbad87d5331997c06a730b7ee5e6b53b7660aa34ba074037a1bd0e5d5ad53c81381ea3157c803fd45c0e4a4bc61534e614efd7a87d12554a7dacd2404718180e2939c7ae6287cefd655be9d452a927d57d73cf3a692b9cf323011bb70cfad43ecd7dc61740f9a4d6f068d1b586145332121bdb9e26e5976caaa1825c1e5b6aa70f91873f0310b2dbd6b2581c4c777ad24b6786058e7b0f3d77a07f7fa2e9657e1e09f675aef70379da7f2cf5b4964691e6ebbcf7b4be0a4ee870afd5008218413a79ecb1f8a532f9fe0944ce9f339bcb885f607218cde427c483c773f507b8090d00fc4423f4940414df9d842c2e8a39f62aa7dba922022143a9fe0943c773f164e7faea1e07e96a4882ef4e72444713f442ce92dc81c8211bebd8d2c810f08eba494b4da6aabad165a7736b382e4137cd7a31785f1d9f8766a9d1b66ad6fef65bfcd12fe16bd05a18748e0cbcc82fe84b09e2a704ff4f47c71eab16244d363a5ca085f07e05dcb882f8cc892458b0d4c5d2cf1a6e8068996112d29aec35c918f034e80801151b48cb8f23ed81b2c77f3e1a3c784d89662d066f844839a6d2796e6991b31e50d6141bf3cf3d6aadb6cab2cea18acd49d96796bd433cc6ab7d4354c3dc3983bada186a16731be156a5ad92d196839023384baa45ee7cd9b748987b0a653c99a4e314bba365970ae5183183fa0eae969a2d69e2b3d374ba8b858a2657df0c9497dbc555f4129755ac50c71ca081fa9092014d1b89e36190a4ef7793745946e54455b44559e524aa9f39eec8433f3ae4504c46faa2208c7141ad42951645b4ca1824db9a6ec70a79880540410329a772da21b301ed836e155382d18586949b1c1cd1312185a62c5872d2730420c05c660a2cc14547e18030d2e9e3e2de28ca7334fb5780255440b524554b16e642d54576c501ea5c6e704540c70c881724a671c3394d249a93b1f684f61e0c5537a63e5c60994afa668791def5a53da289d61f39c5ea7dfb7737a38aa1b26ea9d32677e63e5ba8ffe5cd0fa39dfbb969429aa20bd6b4959e2033dbff5bc7c2e429efa13c5cb9bdeb58838427d4b0a959f338b9fbed2b9336f32afd0f3a71b29ab18e5e873deb5a4d410258e4ce7591197d0e03f441b02ae8fd3e9eac0e06e19f4299f4bf920bc81f0d433bb2778e35bcca72debdb3556af34c79c1a126756bd95933bb27f4e6db0ceedc4085f09cf25e9077aaf819ef3e8e073ceb9d31ad660617529a5cb9b18b477f75bf5e0264a8fc4d1b5c853ab98cb01f7033ef41374ab6add4dc5c568f572a08e514777a897c3b37a396174095f3eeaa0701c741d0ea8beacc0812977533df421730e1ac0f85e66e1ad9e3b7509fdd0abfca276de80ea6338733401f3fb8131fc0342b571dd6f28303a06d5977faa530e282f7a4e38b9aef504ca5f3b6cd7b59ed8f0aee5a48c6b3959430354887b1d8bc20ebed52c8aa7af57e775714d515c18a78c625013965b63837179bd301314577aa5d5b18c55bf2e778f61d755a91c43e774daa1723f5db7461d2d28356d117a06dd65f5b618ea937a459d28bc71d3eb745a2975dbf3452e5ce29cae2b53a3bf1b183111e6abd36a8a35eb126ae3e88efa958750af17ed3036be1d57b328d9e5ef510b08fd64d6ad2dc1a67aebdbe96d55bd1d216ec7dbb9dc5aeb3ede8e1087c0db896e7bde9af0d63aa3b76ead75a7b2fdde193208210411526cc0dc6ed7665a3de6627ca57a5dfe30c9afe6f256d873ccdbcadbb9727fb15086eba618afcf33e120a19f7bf3f5882f9e53def68b6d782bcc6193b7c29c85b7c2302cf31c435c1bbfb48b72339f72ca29a79cd01fe63960702fc7fcad204ed2b2ca63de38497379a09e8279b1dbbb21e2a6825bca5b61ae4386db5e20f62e4a62aef376846018561d13a2c9633c3cb6c566f27630f7691b30e7c0e3893f18141ae6b63ce61673611e1bf35899c75c1006c330bf10c3b0acc232eb35d17630c410308852eb45b9abb369c232987c06c26c72f73148e8c7643239c44d82a18f6e32cd5931f056d5c7013d655ccc730871abad9239ccdb45e2f65fa7987473c9edd3bc945bca5b55da4bdc0feab594bb8adb9672636fcf05894f7353713caae720e2665eba5ce2244d5ab2e4ab4f9ca461d05bd5ea96e852a95e1de21ec22fbc55751d43776b2a54eabd28940b711fab5737c6cb753ffa2b4ffca96ee4ed3caf3a708e095f6b759243357d75a7aa99d55e3236ac75ef6520eb5410782be800d7793b6f27aa563d26c4a8920e1d9613e05b41949f50384e0b24424208216c87b09f17104208a30e56271d082184ed10b6aa0b082184ed10f6d30184104208fbbdec05ca0360deb5ae90f9edbebc42c5df539533465c6fc4b8eead56116e40b07376d3c5dd6e0a5d42bf74153820e5152c7fe483752fb2481956e70cf29d1bb9c9e276cd4053e9ee0ea3bd34950ea3dd3b7aef81b939d07834e794524631f168c8c2ec3d4cba732e73383ff3a6723b7ace39a74e472d778b1f375476ba317321cfb3b8f9f89961d3c75f398ae91e52eaac30a5b0d6cb55f5f97b4d6743573d1c17ba5a2e11a275a54aab8913f88861a29878148f22e6587658978ba9b0b89b2a27fab65ac1e858de50abae541e5b005c0013ff790b781ea5cf0693a55213805ef20a3c27390aee67c90c50f3199e93b2d05bbcc5b78df9ac7e3595c78022f662199bcdd3258e935e9da6d61b2deeb6e3a55f52ba8f9ecc675cba0919ce7c26a3c1eee594705671cdf2e61e66aee8a591376ab7a3770e0903bcf4fc26d4e4e195616665cea25eb79e18b72c6f3d9f393579a39c4e8fc337605ceb9a0fe79757e7accb5b96d56dc76fb5e68c62ed45306840a6341d230132fd42b201cc48cd9be30185fac24ffccd0b3d79e85710c78387f95d2d08bd94603e7a8a4834fae99ad760d6f41a9fc1dbf4191a70f9e817bd683c62a1b72fb3ee1c8f0c6fb248842e85de69f00262e33eaf3aec0b5ed59d36a4627ee119af4670ded350703f3838588694d3d03f9726e442d50d3aaae417d6713995daa515996eb17b5bdd0a99f12272fae5574e11b1d507de78ead2b294fbbcaa09b95c4a4761568dbbcf701294e770a705117a7b39751d7823b974142e12fdcadb2c125d66cd7da00d46d23f643a8de748e1cad268f226f49be6d44d78137a2cf7e0126691f2d4324e057158322b33b7496c26ddca16eae512b716a462ee10cfccf1a835c77e3a48dcf7aed504936f7730c32d2815c4f9fbbcd690bcb02486120cbd731270efdc8ee810cf0dbaec08b4c7cc720e3392a0dfe0bb761d2fd3a2ae56b75e14ecd5c329bfddd5ec27457d94b523ee7701fef6c20341ec7dbd26861fdf02e0fbf8766ec078d1a1a700793287ae42862beebfd942d5d8fd8d1670859e8f296040050fbc9de7b2fb28068f3dd66af8d459662dc935c9316c65f3b682ea33d72ef7569aa5aec234a7ddf8f45631b693a4f6ceb707777363fef50f8f47057813c6753ea3bcee6324cc62187603c6ddba94d3a8e8d8cc47de8ae7042d36fc7dab4ac67530477bb50d18df47e9ba463aa1705cb5daa183b9c96184cfe38b6fc22b6b4b98ced7e45f1b6f30d72ebf3420daa5e1f858c634225d82245cc2ef359a9570e7a06625128e38807effc9195c5fcee5ad0579ae65fe80e2672fbecc632631e6ed178e8fd5f8e2df79066cd9058410c2863af0223e2f22e4d259191176f7a693c20e8ca856417a3ebb29e36e3a3a3a3a3a3ba4cc79739a65962948297b6684a862052978b9386ff5ca905e5475cb0ea8ea894f77d3b95ea46cb2e5567fae8a19e78b9beaa1542f79383edfcb6a0613697c2cbcbc1128bef489658e5bee569bc42775ca0d087e3b0bc3f0d02f6db74ef3d63e81e047d6d67f61249dcd96de324874c88ab9be2ee2c7f8957471dcfd4079e76d05d5bdaac6d5a90624abb3469fd14fa67c6fad3634396a6674f8c951d7351d383ad580a06aad5507768fc2f16d72d49c6866bce43ed084497eb59256f212c927295aec2e5c5fca15502dd3f819efe96dc59ce4251c5d6a404811fef5a81171b7145fc3d90948be576ab8820fa8309f9735ee13be21f06d9b5aec72e93ea07af73763dc0c0671f3e5aa6acf96d35b23a2248e38e2301212ddcbe937c403ea43b1cf5b45cfdc8f166e86a8f0d0690659d1b3119a3dbc41160f07f8bc95f421ee07c9fde8e7f2d45344daa977dea0bd3255c4316a3161e52f8fa4c6bce6199755034272ac9dc69de38161926fd549183e79115291ab625bb3975be831684486fed9843e5ecc22f9e527bc911c731abc917cfa0cde842240f2cb6d29a343a8435745dfa09bfc3ac49be67056ad08f59ab72297635a91e896e45608e6995684fac92bdea04f3fe51411eb24b74e72d705f38cab64e81f26345ee35a11ea5be6dbe5d0555198386780adfaf41a8b4b4e83b799bc093d509b308be4fd17b334bf4af9f66845a88f2c67ee47770cae6b310106b6d0af3d40e80953d69a9008df6943eed7fe1e42d2717c1224ee6868e88166de3a04cade3a0b5f9130e09fe37139d0fc249803cd222a7cf5119030e0dbf1b8dc690f10da71e52dfa04123d3a84b949291e1de43d84a185689e86654209e523c98f3ece644e7dc687104258047de43eedcc79946940a6064407be3e4f3d90c7a152ee20cf0d8e08feb420b5f3e6306fab9f79ebf1d2adb55996b19e4d963984d0ad7b67363aaccf115c471e216fefed168fbe5d84bcf5f01242de565de0e4cdb7dce40d55452a4381ca73ce6d051f5966597f3e3d9b9992848260e84d492cf31bc9811e0b3a0c3fdbceac3d8b637e03a2d09b9439c9ad6f98d3bcc5a327f9739d1cacc5d4d4a9d39ae786655786172f5d70d95205750978ea4efa6680b983ca40ef2709e533473179e2cd33d0db6e5a0a56b5923b58e39e6308f4e9d3db85e912a677f6819f6d384ef253364f4ef23c4216217b0839955f6e27f90d0941c0e729df7032d0fbe77392a66b9a10e8ac97af6710de1cd36ba697a66bd3ebcca9bce93cc9310d0885821bb7949e82bbe74e4872d73da826e4e4999330741fa8035f476193db60ea27ec3e07de7afcd652b6d56f2d45c318863da49e95dbd97afcd63369269ef1d684d477d2677aacb0bcadfc1db92339e33438f54d9af16a4c81250b32c6b840992b5e9005125a04a103368060e30aa3072c528ce952e518a39ac2d2c346166c64f12146bc7a39cd0615bfa9acb0118335e8bb161b4fb071c498357620c4022ec000c289d6125856c0860c23f84008882b7c300326a6e8620671a48cea983063e0c862e4c491911366bce99d91135d7c7bc600cf604c4df1a4ec0205085c11083c665be284f2adc1830fa2a05eca35ae78f9d25d4ad7b932e72c55e5be9c35aefc165fbe27c53ad57b5a12ddddfd2050bf144f14b57cd84c8d2c475da000820c6944b9a1045798f88087521a42558a9c10230a16217e48e22765841a5d5e00ef5a6aa8f1f35d4b8d3364d043a38318568279c92d564274042bb992871bf3e847308ff948e63007b567ae39c9498872f3bd3087ba977cbbcfc64d36ee4144477274bbf523edd61e41792907951c958dc01cda394807e65037b94d36e5209fd612f47290c31cea397211e610674ec24144473a3ae6472e0fba1efdca47a06739a8f3cd412c60587e39a8876013a223ed2f0705111d79de3948f397558039d42bcd8c0e8dbb1fe7a5c6dd0f7512a25cf77372f7d3353888c8c82907f5cf7593d36025d6319fc14a888ed8dc50600e75cc95587973538139d44970c1cd4a828850703fd2a7bb1f2c2b91ee7eb6a652c23f5a86dd4fa741258da2478a9232321d05f733dde7fd38152d09c14795b0d1c5fc2026cbeb78d71243e59f0684b582f3e90e339cab7ad97516b09ff903cea53fec03d8438875d0c0f2cf7d98c05a017a0f1a564cf8f7811806d1d0bb3c45a5222246552a4a4a8a4c05c1452a082e5c0a063d3db475c4a52725443d8561f237ef5a680ca131252b8a3c344a85c2f123a334ba481581e32a94cf2e2a78676cc1447586152650a586883a8ea34ac5e00494afb477ad337ef0a577ad33c2fca6a325086694f92dfb67c61833c2fc7dd732a3cb05ac7bb6b118fa96124209217c18897bf0c5ec3d24b0768f7607e1835774cfb9f7eeb9f90f429f58163d8b0e352011c2e8608c2ec6189d938e04bed48840cfff5ebed689dbbabb5b23021d86e9e79c73ce39e79c736f8b7d197c6434f9e8af4be812b676a0f8dbf40da83e75a0f858908ee31be2372bacee26f658cf9f949e49e80d65dc821e529f299abaa4a76a6aca089fec9a413ce3789ac1749153424a446b0c0c0382cd49e9a494d249dfcb7a524adb0ec9edbabc1eb0c64c6253db3067c5eb62c1bdfcb1acc72cbeed1463cc38fec946236514a34aa5876ee326746568e85b806ee8694e6234f4d181302d438f413243bfb9a2a18f3947f780dedaa97b40776cc49a93e391fa01f3d87f723f30876ecae8cc0908118c26a338e5c3b579e8d3391ef53431fc9a530e8b9d30875e738724e6a7534f9c6a6c06a52ae6299bba304f496cce8e33522c528f60b28081f2ad1511bad264e85b107a82ca50106865e82395a18f19092433f45826750fe83e2feb1eb087db5a3ea38127e878509797cfeea4b7de7baf8bdd7bedd56e764947489b067eab4f4729d618312c46c7b068b14935207dc4c5b0fc7ace3935221990d91ae3e851997b3b71c628b521d263de6e36ebcee2aac3c5dcfa7396cd9548968ad3b1d474ef032946022d96ba52b27aaa2b8624e863860416919e613988ebf2d6b1bc75c53cb6c880f2365fefa38438070b61edc7640c2a0ffd49978f31f56dfabb52354553b39104458c04c2fc5690ca2195430a05f7f99309885e21019c07b55043a12d3968b1e19d293e40e78b756fbbedae85c598df321e76e59e1455fe3d7426b8ee528c87573479e8198d3630c2427d966276796a4af8aa3cf49a2dbec6b52f4fe3360efd09f126bcf59bae9ff0852716581e3a8d437f6e54c568c9db2c83585aa91181f4a409b94068a8e92b086c40a011a1711bc7b49ec18dbe69fe846c9a3f4d2352e39a63936572e87d9de44f13725dd2d0e4ed72e9d0266fa6c79cf4271308fec29b09055fc20719e4408c1a5001e5876a436abfa59696a097f076fd26dd946bb0ed9f98451dfad32060b74b88083ef3ed7a4c048fe52d7af4eb3e106b79bb5e7a6441967411fc6b6d119dba748ae533bf5f3ef3d91af343a86b6e6b70f48873b88659395c738e87c927a641a25fbfb420316e9248c96dbce4366ea51681a03ce2cc35cc9ace2ab9561d8b3c2eef227093ace8d1339f3985ca41849ee4a921355ef21a2f79c9a7d7e0acdeb80daeb1c1d3b3e83a5ce29959994ba7f14b2b72721f685f66833377da04dccf8dd093b00c3a9c06e70cd03f3a72d4ac5899bba7c1acccad29f7e01491cc4b9e79296b45322bd3723f40e809ed0142421ff3262120f4842604a786c4a057f2eef28861f84be8a176f98b9875a09939d07c24b47a2953826484bf9e780bc3572c8700f540a8bf84be5a6aabebbc55c346e25eef5a3760f21af56e105a548ce92fa8f8f2ae450593e779d79a42cbebf878abb743d512469196a2e1e2705d6b0a2bbfb92277848375a8eda2c0f8e77e831ffefdcbb2ff6d478a86db59fb60b31ce1bc6b65d9220b14599250bd6b65b96187183edeb5ac30a265850f0bd8c18d1434544840d820071b30818314a60b1a2a321ae878c1cdbb561535a4deb58cc2b40e17f5ae65b4c54d6a05b77d8b0e2e613377dab1eedf7b875bf64f870b8bb49762c185ee3e3b7a24f45d98d14a9a0b33733f92b497772123b91f497ae89130e0db8786befd0e7d7b36e4eda0ca288ca0f0f2f6452f47ffb2975ea2745ee2cf0f7f82eef0768a3c88828b144dfe398e7c2498e30a6ceb704d3a3a5c2eaf084a8950349165e611fd52b7b8ae6052ec13434539645f14d9d01ace18d20eb824a04202946497fba0b0827b875baff4e2df133ffc73a699ab06db45e41cb68b80c135d9ae413b715db8ed3ddc6ec1017eb8aee5c49113609ce0c209230c3861650772f1d009290f550fa1cec3f7e506b26a20c8c545e138f4585f1332a8b71329a5a71b2e1785a302e58007ba847eea2c00bd4087a6d4204e5c198e0c774614474fa06041101a4a9827318627330c3de17182058d2e4f1c0e88c64c61f458f4900391143eb851022128374421c68b0e454618118421e61152a68e93183c79e2c27063a4e0c96552822ba89079030c1b5050410e76f822cb11519cf113c7113f279528ad46cfa0cc132fa81080931e3c5139f9e274d08227683ca971d286ebe287131fcd83eca48627ad270a7002e6c901a2133380789200273db801195e3cc1e289d322f3939edd87f376dc0ff9524a29b32aa8115edf2a4edfaa90ec248160e5a59c327829e5e9f4d14f278754a49c502080f929a56f70453c9cb40c8cdc0efa0cc78327570cc0490f4f7a9c1821460c119470658850e589edeeeeeea6dddddddd4d73ce2015364a29bb65cb39bb674f299f0bf4593c3df4f00f94c2c78e1daa6723a51052082184105219678f51e2361398a3820782dc8fe8cf53a00e59b8dc9ef095d21ea2f985b70a5ae27860cec8bd157468e4edc0a11d9cc725ee668296190fc101e02698540e711c92e0d0df10e4c90e57beb9a2946fdef3383fdc6dc743efd11c738c8439742390073bf414baca0990276725008701b8fc49793b97c3558e1a38c57df3117cebf9ad79a078859e5fa1e7ad5ff6ba2ed019c4ddae0edf7ac8ca2128b79e0001234df1cd1ceac89bcc40f4699e1946e115f649818517827aa8975069a3b76a2fe1f68291b41cbcc23a30a75d003800580500a870438139ed24e0b60273da336e2d30a71d6307731c3b98330276304704ec604e083852be8bb8fcd10e302a2f385c38f076260ab6d8008d772eced9dd4db312f7f21bc7f1adb5f498101c4e7fde603acc433f61e6157ae8741c1feeb6638746bf8f7287197ae106f5124ca6cbbc557b0818073793d764a49cf3bdf7de7bb7ccf70b6fd561705c705f27f907c315b279252bbe7c09c240bb0770602be4594244bb7479a7e5c8e339f24e8025ebce470597e18bc279abcc4b380ac9278ec2c47f489af451c1f1687f18062864dfcb0106deaa33d72c0bee4790e3e1b6b8f1edea68236f27c349b973abe13a29751297afbe9df0a4ade7a9c9dde399a1a909c16d50eede7411b07b39a81c02762fc726e7c0eead6af209bbb7a2d199c91bce57d5571a35bc91729665236fd58e836bca5e375807863c8ed3024ac778c0e64d2e62745a9c73ce1979abe99c73ae4a77f41ea01008eb82ccfcbaaab5d63e9daf0fc8253117e417b55be96f9d6297e2f470b11e13ee5b59b7feac7d39d6b3eaf555710a3a7184b85beda148e651384eeed64dba49042293c43f5147e6052c40e030f48ddd57c883f949488e0bf435795b58d2a7acd1dd03fa4da7a762e896f6ec783c269f9796f9747741fef40a79aebb0993f08e88c22a5c5f4e7515de6e3603c9b7be7aabead9afade76ba6d2698639835f58c9450691d552bc39306f5d4ac77972b1fb19c5db3d552f1007dbfe832cef5a7080f9adf4ce7dbc6bc191c43b6dc87c2401b88e01160057e89d3b6c552438376576a3cb27a3d0ebf2ad190553625cb9550703fb5d4214a5e7f85ecc235cd8005b63ad7e42b99a553fd51a6794225cea4b88e4f41b4290524a29a59438ae5aed90f3419cab83021ba43f95a2eff456d42b76291fdc53f59aa35c30e69bbb8448fa7549592f395dca2a2bcd2c8734581d3994b1f22f7afcd145e0779670fa9b1a110d7c7ee84f1b824b0d1f540103febdeb6f6523765f5fcf5c7277a8fe494fc98ca41f7a927ee8394ac37e59a05436efbd97e388cb79b9e616e0e42caa33cd5d42943d04408765dce7576abd77f3dfb08798cbc11c0fa883c3e46e3bb087be43c7478f0933454ad820fda60bbdc6deeba22ddd25440fbe053c9e7e9904437702ac765db473ba132ad583bb65d93da192f447f7171fccaef4254430bacf60716ab8d80dd27fd5eba2fc96737af656d27d7065c4b1d3bd67ee4733bd4b88a053bffd5adc0fb7137dba7c2f63e75d42d49906ffd9a13402336a44a053f9d1a994ef85a6aba6213a64704cf9fed4d1ddee370bde4f382d74026ebb834388ef6ec246789dbb696f1929ef5a657828c0bbd60fc03cc4499971371c57ad76e024fdcfc17c9109c6fccb51a647ec5a3fe8128589ffccfcaece2b81edbb84c8b53b92751880a55418b77eb9ed9148943104f6768a8cd418a931e614b284a89fbbec664f763e492965778a8c7b7afd1cbe1e58878177dfce7c5b24a20d892fe57bf23d6735155e98a001961417b739a31e074c7c33186be8c186285b8890d0025bd0022c3fd061861e7260c50f9c257808211811abbae168227f00070e0e0793196ca475c237822e8ee80288284e8e78a305470c2fdfe8217b68a060846ed8d50931c628651a2fdf58233a2e108c0ab3cee95deb0d2f7c2053c58d297468b24397188660638b237e36247ebe3103000041004ea2f8220b1340314616308878a3872cde60f20dc4752d322d3264de39e95d8b0c984786cbbb16192cdf36c091c30f5e076499df56ef7e7ef0edaebd26e08a142828e2cb12547820e1106ac10d586c10e204584079e28a50b6289302881ebc685169020b1a411072c10d9c2c9183319ac841882fb40d37aefce08cb0b4c1c40da2b7ef5a6e007167deb5dc68f2edc6d0b3ef5a6db0a18189dabb561b5eb491a58d2b8f7ad76ae305df9e391ebeddd6366eb848ae7b382fc3ba771136edabbbdf69ad05f244283d3a346f2b2d7b9994ddca332f0f7a8402795e8f0ecd9bce4a66d4bd11ca43a19f07b9b0da7be8dd6fed411e4f1051672dd2877aa8837ae8c707e6408740f467ea790ffda33f330bc8cb485ca0fff22654c3cbbc39a3873c3df403dff876facf6fdef8e742da60d2060adccafde6ac78d846240071dd1bef7e73583c3463c91b59e260811b2b30fa814d247186162e44c4e08a267e4c58e28a1823c6687154c40f7414e4111a220a10a20c41660c1a6afcb82d35e480051c69f430c3961fe8430c3ac0218a2edee0f2e407ba0ee419018a369478420734c071c60ff4e228892070f0814c146ffc40f7a94758f75aad960f07e796fa8637ca982f2ac618638c1175ef8e5598306c94b9d049153d3d36a8b5478a6d8b15019eb6a884cd5595450b95a219010000004314002020100a8744229150301e17ee7afa14000c96aa486a4e1688931c46519031880002080184000080010400cc949815ec08497c1cd2c5718f8ed1db7108bd68bb15333bd6df315d1cf4ecd01ed7907c988e848beb02844f578af84d2bb6442e1d7fb097e65cc94819345617ac7490db0e92ef69c646e4da714fc7bd3be8edb007cf35da4d3770722837c7c11e0e0811eb8a899a9e8cfc4d13bb2cae8e43ec45c33521531ce8eb68744eb4ff31e5b8b010f3b0c58a208fd3771c106d1bf4d96d5016c92a0e7bab2321df68c52c71d5742b238383487db45652cafa45eb31c8c85c34dd8a19737c7d1d24d2d1a46b214206da55b2ac3a4af386466c814b0d77a465e4209b1eadd2846fad1811cdcd51766f5a732de79b41a3ab0b912db4828e90859b865dcb1acfa4b59a446b1c23df696296c5d581ac3d68d44d46cc446bb564b5c31b1ddf769c9b2151cc4ae57225b642b28453b2dcb86b23247e6b3661b808b13ac5a3c079b80b308789c711182c8ccf6bd5d4a8596e6f0a6781e5878d461d5330e0158fa1695af8c0bb9f4a0ca8579a6fd82dbd831d5b12d430325f9331d695ba18b264e36ba9751036ff274437f48a38b4c20beab213cbbbcb28247edefb56fa321614aaef8f11649d79b7a069f4fb6c9845d5fe2bd27b272365a6a515f2f65a39f361130637f62f3029ee502271d77499fc4c0e22eba75d3af2a3c31b737813c7be3507c9f734638ae5c641c45e5a722f869c4993abc8c8561a74656e1db2b74622ae72e754660e68764e29c76b60d19d13ef19797674764e45ddf45d9d5391b97adc16411edcd2162933a439d77a4709bfe104b2463c86e9a4d31debbf486cfc3ac9f5b1623e2cbb9723c755aa5f7960015f31bdffe50fc5b889527a2e6fc2ac0a69940da9f9fc9de9f0c35c43c717b6bb10b94fc1b8b426305e89b17c31411bb18559af9fd12680e68d80b05c03ea0f8cf57b6b30cabd195a3a550882bd496df2015b0b0324bc9ff95774a73323c82e0cd14a2185cf10199697cbe8723b90301e3d0c5aead0b75b0d1d1e5231ee29e2c36bfece82462d92af7429e6865352dc4d8515f24411df952add10508229c623f8c7618d65bbd1855f7fbe383730650f3b66e502abe682f76717757d5678396787bab929bb1620160f30b56bb7edce8095c108d982b0d90d254a1c56162a0b1d41e8e2ceb6b56fcc409e2bed9e74a22c902786444e28bea850dcc4756b1a15bb465f732b0c6d04ad60cb1350e883e5be81fb788652d79bb8064041b61374423f697688c0c10f040dbf5a48fba3379c3a46305cc0962042946f45d061c3b24caca97d2f9eaf764455e57f24313e77df16f9748b5f139a44b24eef1c2530aa8f9578f15b6fab244fe31f8eaaed70ef6b07f9c7f5f990102552a9c58736e36f85b7edf0db6e25e438356dbee3828f61bbf3bd9e9e48ba66a4a89791fc59e4acbe0f0cda49e6342d02f71c5eb800ba2488b3eb49add1f58877d8064f9ae6c6912e314a555c0284a37efd47d93c147b8fc2d584ecac7f4b9cdf07875d1b7b63c9f8d67666c89ccfda2b10a419a3bffd14dd2475d950c44778775a418e9582438a9ecdfb7b2f4f425d81cfee8a565fbb288513f9266ad2d4de6399c87a15dfef33cec485f5206ff6bf7f254650efb488d47c36cd3b2b94c78156594f0a6c93c7256debf7678b73a39b705043b25fd1762751736e4ec1814c05ca64a3a4b7515ff4274f80c3241a51937aa66ffbeaeda3d51d396a4803e26c9f1430fd2a44f4b96c84fa408915fd0ad5817e143cc095041122ef9bd37afed4e0b6d67d0e6ef28392eef7f5ee69ee505e6cc3ae4364f02419818cb72c2ba87f79fcebdd0537236b2125364f91a519150de33e7476d854607409471aaed4027cc02920651badb076fd16aff58852156d415547c953466493cc06b250c0e4e4426a0891e78d8708f0ea7edf286e05188f3f2a6e49321b933103b68cc92a82ac8389d33fac4231274105fe02dffb0e35cc3a2c2c5c280bc321343cacaae10e3e5346fb129034d8f30573d9b9442c477cd95eee2e8d0195525390710506146b8593168f76f1bb154f65ca86164f2424df4c97aca3a925a63b096f95ae711028709359fb9c9eb133ffeee9232b25d22c730f0943f60c36d6a24448d1134f9f5009bf56b4019bb027a46dec16dee0081c0d14cd7e770923f13773338900757e2f01146dcb85e6b16556f06d33ed6d3aa7c94c9a3cc833c03c51de3323fbaf0140929171ccdaac86c3aa403a903d3d20c2501043a09ed2000bb768285567078a847b8467119f3bb5d6e6f84da0240c63e9e570bad2b8634acd8156d39954095ec69cdf11e12a655f5c01f72373bdfe14086d76fbf2eb9d313480d7a58c17be4327c1176381acbe45ae2366aeb200c320770fafc95eb2dd41947002a86b187e25318ac2fabc6eec88fe3e0ff4a1e8b42e3e91c29c59f7b1a0433e25023a72a5252b3296b0bfec776d830104a0a9ffc1aaa64ebad00df9aa24a6e83c798193b4af30ba169a834b6e8a4076fdb2378e0ddf5214400af7d31782f4cc71d3b9d212512447b2dfa2bddd9166b2f57878721fd05f418ed7ccbef36427b35bf3ad6700f8532d91c26676e9e5f10a5ab05b28fc4e7f34a17618d0288ad8eb71a0e5004ba0b8bedc30bdda7898613c112ef5cf41669faeb898e19517ec8afe5378b408e225f8f555ce8c781456ec48d394bd33f63f84b3bd4cf928c631bcb9583cba8e6faec3be10322ccb3b0cf1ec0b23f20bd6941a078103cb5932f0ebb678d33b874d11b6866a628b2a23a18d03b054c23c554c26ebc1fdfb61b263d4ff90282484b038b9812c7343391f1004290013aa7838c7220f55870efed056982ecd949cbbb3e142ccdab4293e7d2d43bbd641916ec0cb4e2aa821d05ce80533bbd2621adf683c59fa3968d3b8818160fe93bc42b1e7de1c90cdbea47402f0d4c63c50056af019aad96c46db6b70507fbcf46f7ace60614a2ff056542289f55eba64f8fe9f895b454913a433f88c0ee18b00186b9a399a9e1dcfad4fc919987d8273edf2b02b765994a1d4c3415afca08854d32e4e29d46d76e832288954f523e69cb94609a67d2ec26aa6514b745204d2d137ccd2041a34cd350b4158306617b73b3dd0bae3e721493f64cbdb9d0a4fcc7b82518177d0304f9d7df7f85a36103c5ccb01b2a3933f917a1e1e21dc645145dd3a0ce520aa9478727e9ee6204ca2fe6c1f20f4c930aa94039033d816819657c71a30262585c87a6613ed0db230ea9780ae64f139aad337bd6bf58c263ad687d59f425daca12c1a5be4e5044b3e313261414c3f024a88c5e4033d07e968f1d0d82569d71b5aad930117af9deeaffc28403ea7929907aaad71257e7ceac1cfa65f066abf5e18b0b7abe5a63973e81cd9602dcaf89cb48c76093660b0b3732abdec65cda5cd28dd18c4f69c1d5c2816bda303077d951935551360d9c41c3b0e42933f10d535de725085fbb601d6af19d38eeb1909ea5fbe78abbd0a37aee864c7c10c26aad1d2062ffbe65377c6ddea0bea0a4d493c492742ff9453b2a5fe82473bf5affa9ff80ef211c8a3fb1b021941916dc84f20e0bc9d76079827d1bd4f5a31b2af30946db3248cd4e5ed2dbd7aad2c46b44fb6e3ea857464675645bcebebec4cf3094929a4c398fd1620aeb96d989aa7ee936a7d96db61b1462d78285e204dbdb0e431272d1fe19faabe3a2a05a6f04c26c252bb436e8dbe02d0a83280e7bbd1c1a1d1de2df4a052f229395fe8bb1075788d94e5458faa3fcfa6513b4f62104999cdaeab806a5a0aaf21daaa996b9db0e1ebb596b5bd159aea59e65428495b2ae89bdfac01bba9b3ded0e55780d4db34c7e8b079a13bf9acd0e0fb144b2ca4385a56ef01c192e9aef65abb4c305c09c4477fbd478e7215e5fc732a778c45dd36239c221d7427b4b93583e2521d661391cfe75fd4f9f3575957d137913cd9d801e5bf2b91064a59447029142200a1ad92235e282cee8c1c49266c850608f2d23265150ce56a864a99d14360de1a44431d38f3784e7724425719e3de3a0f1eb82f1560f98a690745e8f3c5ca2bd8184dd361ee2d7a61a262e5357c3ee999c3a95329b8a1c105f1672e42e8034940fc8a51a4ebded62c3f68aa82f03e2209afc9f646c108284a647348b6966799f623e9e2c7d8138ece7cf566412f4c2c4176854c2472c28c58ef9b48daf0cba72a3c118d3af6942a5d7fa189b18e089d4c3c8fb5a1fe84365f179254c57a31ce6eeb1f50922a7219620f5dcd202cae6fa47ce55a1387e128ed14743e206874052aefda55c761938d8b508075f0df4c412bcdb2b0e41f44ab33ab5924e4c47cd910c8856ecb1386119e9ef5f233d01a12e840da3d2ad05a0101cb358335f5c02b6b5bb663f8447d2c188555f5610ab477669c8f1c3862dc301987260d45cfdcd029709a3860307bde9f3fae6abdabd0f23b7080460d966667dd6e5eb98f841e26cd21a4da17abdfb08b7cce390e46c98cd7814c0c52558b338faf7ee09158166b03420ed92540f731a1917113f158f94f8f3e49089bf3b0da5ea5bd94eef8371a5b5cf63a6948c813e0faef07da5acd9a24a260e219576548f4b7aeb63391b6fd48c5341bd0c8be0a439e24abe9f7ef02da67d96a7ab7ee304d681e9741c89635a3fb067459f47e40dd7cb14340af7a82f349783b6564d436a43b9cbe6be68f8031bab1e0664fae70ce2252896c8c9bcde3c90e3c65503bdf015125428413b7718eedf757eb7ec4013e76bd02ac61a829caa1232c1deeb91f4e923025843ba4619e4ed16e9a2e950d3ddd85019e1982d7ca5fd5db9878b4f352785375d6227418e1071d958977f6bebaa190729a20c0e440970fa0b7970826e3c0b18f31fa3e177b915231364421fc5cd191999b1edd045a3166e6718f143e2c400eb30c3633a0e6df56b6200a56b9b88ad69f24619624059271405a544e4bc16f48b2eca60a8f77a75633db16ed8d4e43c419f3372c91364138f5aa55a2bdc80d7c7a64dc4d664998a2e147a5c052387df30d28e11e9ce5d397b773670ce25f0e4ce4189d488e150eff7feca6d6b66f67890d6644b7c2d20b04d950ed0763dbbaecd604b47c72fb0fecfc1e00a2a50a9800ceaa9b3e5315240626599810ba5e433e9a94f473e9707b6692b2d90aa0be1a583e005761b1d974f903d1537b476067d982d19452994897fca6d37e675c515d09480d574663def48b45ec6a2c76fc9d30c5e35a9e3f3c35e3e917c17c2b2e3b563f1d4b97275bca542cff8427cf767e3935410341892c4099df698d1be4571d9513e5314b1e2f5ce176584662b74b81c3829b54e5d695e84cece141b1fc839142c8601e5f110818d7650a14be8f6b28e1d3dc5f8befb3494eef146d0a0712065cf6a3a11de548cf0984e60a6effd152e1693d712c2e9407c37d6f865c170fc732920926ce06e822472bbb8146334bf22aa8ae6585137654d5f9c6bf6000e88b329418b5d165fed8243aed29c76b9709e61cac911cb57a8464c07a5cced521136fa4899f42c924ddebad6f11fe916c934010f472a9613da887bcf590b951e4d494dbb32174fe29c7247c37e4fc2ab1a43922984210a5ac0d39b96dd2a988e4ab82d2697fc5617aae2dd9f0dde1c11b31b34fde95c6a054c3a9c3f88621dbae34f745ebb947ead29a2968747d6f2e6160de1ac8d9d5ea40132d53e3cf7c5ad6f68d372613552834fcdc77cf26d512c64dbfaaa4acea6edf8df0be5f74c1e6be16efb80d6cce3cc088b9791f9128ea1bd6e8436997852e44a1823cd787b2a47f06df6375e058ac0958f5de314c858b1cb118f8ccda9040b7dc2bfcf2592a8f055acce0fcc319221c00e25e55774fcf508a780c11fc0cb94157fe8027283507f48bf45dbd0a65e475f7c2f934be1504f598447443a0a792eacdeea73b04f74bf52f5a18d8f7a8e440ee80eb959ab4082e28e23f730f878235520014f6f7507ee19b2e553d861b893e2fa31c6c4f5b6a478fd93579af791ff2931ea911ef8f294b6a09878d8be8a2094ba9c749fb13eb22df5d97328d2c566c4db93697142941bed7d384009fb592baa65d2f1ecd64f6080d343eba2c191fe84712283e09fab3daf610a344f2bd9b6fb250f7a3b91022ff2e6349dd12413a298fdcbf186fb507086238e0750a82757da5254f653bb87689e2e3a045ce0ef9680856401988f82b729e45baccd86da6379d04f904edfd6e70d04227e61dbdd566c665b731631299f171ea02792f242651a9b6074d30f25ee304fe20eaebff4cd02a2e39799e90206c5d80ba56fbd5639a08c050353c7e87a2f720606b05e975e94ad7ffc19d956996a0f339dc77a9c2daad456fb2e8113ac7e3cea8573d9c6652d309548c595f9495f3060bd10bb2ea4a38fb37a3d3fcb57a811b7ec6bb94cef21c0a1cda0090dd0f5e77be15fd51f83e08bfbb535e997ac56103e60195ba7c4251613c79f5dd7a132aa1975171af9d08d4d3ed4cfe6435ff052088f31d5954f05b05ed245ec572d19e9ac64fa9d4ef7f746293e0ddf792d53c358775a2529d832a53321f369f1f434b8bb020c3f2b1996eeea216b5cbce4503410c98125f7395277ad284ca68a0b9544d326b39041138a421111465a47762850b6f07003dd62fdcbc27c4b40d57c9d6ea60f6e6d866dc63b684a0f8a106d42ad0650e679eb7ce0bb20f2d5271c663de0a26a9a12a74648834c2439bff5199e5c8994709a0f3949c825b8b590b9b0a8ccc8965a1081e092740adac9a6a8747fad864878f2bd504ea39d3f976fb253afd49cf6b1c2814e64308ebeb9658dc6d426f7cb47372c3878a68e74d12d4c8dd0ba746b140ae87fcef9572de3358398b3f5f69ffe6029553a2eaf8e4daaca2450cf22988cdd68ace8cc979113101724940a360468cc7f9bc31a71e62a3d326f645d5c8ad8e225ad9141a7eee17638975eb4c802ccf9ae7ce1a088dd406e8c5ea9509766916fc96a07562c6ee69e858c6c0f604798df30b90680b93321f36cddc8f73a65794443578a8aed6def6867b6553c1104b584837fff70453cf96b61edb119f56fc63499e7a79d825c62f1ef3af67f7affbee8113f311fbf4e87ab10be0d18af44a89cefba50539546f029754f6b3864cf9647e2fe1b54458e7854624c25ccce17e87a63cbf5539644e58addde652a0e677ee7899237e61f160fa8fd8a97f08243159655f58852b9d9b4ae908f0a5887bb96966c035c388be3d36e0e6e6ca7d06429f978f77588d77f30cdf0b336eaf54bf461fd687c2d054c91faa91b6895eda84a09258640d01427f17b745bbdf3f4d39a15e696fd07532c89583d66ee7e8ad8f366f6a12537123ce3f3f96d5e47882ee2f3f7b2720d58e4ea3f0bcf2ff8d64fc6eec620d82b663211fc0ac39ff6f067708f1a0737ca0a9da42810eac26a06b5611ea0f03fe4c90f86915e5af4e5d6f77db3ce47a2049911b042129ab77789b34f57674ec1034093e7136555a99e546bfa8d4531fb4eec0634d17223256fd17be48a56fed19d576bfc864215306dacb7b40478bf83abae8410f1b95884a55d5e3e6165dcc9c3a227581b557318817b29a5595c6dc530703fd5663907b04ee6960d9f97f730e1dce079d23005df3f0809eac0c5229e59c3217a420005adca3531536fc1436aa6a211406e3ec15077a4fdbe847a54e15a548092ad2d1daf20b2a1c1e6046d3c4f47080ea930ec054a871a0bd2e1a1c10c0965e366eca6757bd561c2111e013eed45890e002941386b0ef359eb53e5f84bd57fe610ad1897ef9a20f55dfd6b9978ffd712fca85b628111989debf4da118204f5d9e8f1b84bc159e2a4bb9b54407edb80f064983204cfe0b7c4294ac2d34b3bb455ff0eca2c6520d6a66a15b8578fd417d6a582b7fba139c2b6b9ba1dd16c61eb5f2a644612d28da342646b078ff141a89e0c565943619e8260a68061af452a34c7101e2b5218b76c8e4ec4b74927497c0f13728a431d872941d6b39f32793cb2a489c560e6052c30992ccd9dd97edad6f7c282fa0a074a0e2a7b69650aebe6a35942defbfdaaad29025d9a47cc3f6c82da49aff112a7276c64d28674b75f96bc1d8c47149638a4160c67a0fb3514c08fe92cbec7f38d5ffe2a27694472afb87804772ac29a20b3088cb2a6fa79ee719fdae35d2962cb988a5db8931615491c5a206a8f0a568501d6d98ae164ff32d0c1c24c94bdd28e6e6d3731964f7a7635448b459749a25126a1ac91fa59d0adecb06cc57df8eca68db905e33b6c5fe59a9c9eca5c76d1f916ce89a2afb1be882a6cb28566c8cabdb29e9ffd38dc8dea7dc859f0d5b8aac226cc2e3d612828df802f2962877b1027421cadf7b4e44cdd0d0bf2ac0bf1e329f571fb6d67dcba27d291d6f5b0cc189b015f8ac695ff310c8a0018e18cb87f802eb05405ea02f87c74b66332e6fa1db0413cddd4449515d2f79b6de6402355cc04be4a44c36ef945e5286a3df6b69c7dae5477822f37c2157d6cbbdcf69f4ba9d3ccbf7e9f05e35e5f68ea7fc7536f60979bb3f00057da80370dff447c9cd75c23b2bf2fbba02f290b7d03b91780d797bf78d49ee6e62e56a43e0cb975f6dc8435cd9f56084b860f8ad77d528c906447b24a8d8e185f0bd81bd3332b78548fcd8ade6d073c7d65d92c4877d2596913a4ba2c1f87444040ae2ef616004a0f2142da24aaf9720936cbee4ddf24aa9428c1b83c8abc697b26a2a15a2dafc43e22ea736bd9c264e4e2220355edf6a1c94345c7fe4df19536b8737a599b0528f3e2f62de1d9662fc7740e84af7710670947b01df7861e99086d9684f02f220c836530d339af8948f415ccc773eccbae644349a874f95bc1036770da5296e95b5a5c3b949c782d21238bb97e5daacb82ceaa552bcb5e7e9609cc933cda79edc4be9f7c55115fce67e6b01aeda8a55817fb7f9274d9892a586387aa4d34e12fa500e3612f0d29b2f896dd39100744db1200acd1a4ce4146ee84c1493301fc07b99f17044172d102010f91eeb4b2aeab342a3f442ab2e2002ae1c4700451f4fad2da26df3c173d3acd71893685f5441ce44d142bc5eb712a77a0828a741dc3ea83f36273f22166f9101a1a2a228caf7ce8d9ffab5835b2f5d0ae81d9822d3b4e1f33822b9c48b8c4e5ca2ac26da3471f96149756c8c52315a383d943d6064a577faf7b9fcd7de90c44e9efd4be11a28ea46ec657a9f111d49c5ed23844ad982bb22a2b34fa85ba148f86824cdf2ae356a0fe3f141ec7d6ecf0a3a503028d4509f7823721d8da75fc32936d9703e4e1c08d0cfd259be839cd38a15d7d3120c758e908b3a63a1984a932fde5d6eef05f7b1caf3480c1f9956b1bca2084b2b873b4afa23a732feb93222d131559c77ad7175f2e56d505079bb475029d2496e9821af6a3f84fb65b2812255c2a6a1303669b0037b9223e1c1c5fd5912cd15da42b0b0dd5720f5b840c1c979ceedf7305c521f7ea160e20ba6f6a56319cd4709702aa131569281ce681621be4d34e8267344504cbf70db0c3439d6b84dc2c6a64a5feda36c064c1ef6c548608a5024681963a67a79cf5e826975e29aba9a86582cd053c91be9ff26db00be364a1d3a18015bb9bf5253cd0230a8114c0c9a0bd97e1bad1ee13be6a1a5031656eb1c5d26c1c6e8343451181c63ea5763b73ebd80fce06922146ef3da3eb998108e810fb850642820fde90d134fbd0c661028b6cf07fe90b67d35bcf7763fb550c29aca277e9495f458a02c5fdee65d29cc27e141daab6d12bd5eb8eeeda14d8d2b9eb675074089cb5fcccca73af34483dc6f02fddc3c80549bd7e4c1084c2d5976cb5fce2fa9fd1869774c091d130c6e418d9b99df0cf7df1dd7bf07b7bf1dc7bf0597f65bf59b379da945daaf89d425137efe8b39a92d31bfb26e312246591ed2c4d3986e2e0b34c19e219bb12c2026250ce1b36dbd9fcf90283cb67c7a04e288b8aeba71b3b789c93bcf6aef5d4c40fe2a02e6fd1031147a03dd4d6237175ca3661c66bcba2b925924cbe73014b9707efe38104166d0631b5713d6006195747e5a101b7f9d542eb9609c59e4262f60a1983bade73a9ec649cb3454fd7425ad09d0cc4e7555118cb4a78b4c0b231e45bcb7681821727919bab3c594c7c30e2a5011cad00e6f2846adaf1f9d9a1a21460320dadd69fe170fe3f777197a0c6a0c6160326159cbc714b2dd53a51c02ace377f0fffa791dc8b354f544335d1d0f24e75614ee82b02e1296c584d2080bbf4189e6472f0e36672e7b71ba6d3db203ad74ec6343b6291ae391b33d09506893df0b781d285920c416d0f43ef1e3c1df80be7b04e65334c99078bad32fc388be83ef05ac834df49d0fd2ca0f95aaeb13b006bd5127a8a6e83428894e0a67d7d5ca69a5dc1494072deb1cdc9776aad8eed1850f4736843f6f1dec10c2abac24f1a5b64e7ca1e8c3544ddc2ab40061c0f2e663ac1a1f5c99eb93f5cfebc1360ced9cb9f0bc676befc86850e2d31612abc4c17c3b64df1f0de96ad9f247f4900aa158e70de9005251ee35f58e04a287e49cad010d9cff65088f5df4c2a4beadaff737b076687df0711cf42eed553c2b69a1cac921d420baba017a15bf3a2f48d043cda7b9b11e95797d6459721a5acdcf7a82e2a1465e1f44b2424330e972d84197c64b0b80e0c435106a5fd379f7d5e99bd4d9e12b461825a5d193d00cf2b891eda3de1b612bac00042684f409b06f051159879f58dee3b723221b90d8bfc0824c5d567d4b5f4ba6a3b7509e08cb78ed99047ce756972a370eff9b32bd8ed4c77a97a0a2f2b6e02e15b0b919c21f20d10a9def30f3fc9484bb817e7275ae18a5eed82854d6d4fcaaf1ecc4616d9ef75d05a62d519c16925f2f974f9142870f5ace4b671c4688394ba402777a2240f7d6db4d1c0d3dae482f53ec42de284b3777c93c0c05d5d543797c5015bb08d6e86000636fc775c885bfc09cc122e1f5613e7e685886453138803cfcfb0032b8ea3cc3c641128241b5319b080ce5d05b5a1207a9c46712dcbac1439fd38e9278c59d8f2ef09811b52c67e9a6b5cb24db1a3cda299baa37db3f174868fa01602559da5e3aa7a299c89bff3a28e3afea14a867f73ae217ae2a184ed5381853f7e1649dfe2e9ec589693a3826d6433271e285a5cfca71c325d44e090e983941bcb86288e40857729579b97a60de94c9c8e0b6c312e59ab773d87e0faa294d08e0c3de699d0a2db25c9d879d13be4f14036c1e6feb1e1eee57b0cbbb1826068bb1b9c0625abb4ce1e99cc0dae9318bc7396b4a135c6656e1f7dc708b70c1ec994f64ab5de7f168d32612bd34fb9b016d0c0939ae34088a42a04c451d736ba2225793f908f7ba2c91b24910c5d3f7b81840cdbf580842f74f8b21b45820e8b7843d50ea0f4e3d7f7ced821beb0685bac4a3fb6dac1d6a6b5777d8ce11506378b91ac4aab5888e005ac9e9b08eb13b5dd3323ac40e1bfd54f3947d5018b34c6102a97fd41ec5786d6e05d62a40e6811b839b1aa54175e4b08e1049312eda50922b9cac22111163ca78bd11125b9311a81aec06f62b0649d42b4bd84928c7eaa2168ecfddc1100b1b5260909f29964f42618d6fcd297665d82aada099f791ad73b058360fe8d1a08209d8f0ccf2ea467e9fdcc9f10bdc945bfec76124f681d60bfc5c318891fe2c78cb4a4cb6638b3d20577985bd6968204fc032c0c6ca604537c0f60fb7d82fb2746786493221705aaeed8f686273e9110c5778fcc0578ea74dad776fa16581ad7433d42335edefa0f6be59264d3511a13c4962921a15947e9cd360a98d73c810e0fafa1f27b8e27e6b12947755430b6f2d4aeab45e8f8c49a0269c3934f29cd8c14c9ec6701394d5632c515f0007eaf86e3f4ecd0c8bb05c6e17978803c6a352ea06f71c2304832274397959e2c848cfe10e81587505afbc2617a708428a9ab7d6bdc854c453068caa321d7da92dd5e7f3917b3744aac9df12eefce446ea60da9bffda4d61488e557fd43703f25213a5908451214fdaaa840bbc0781c293df6ab70d0c6e86e09d623a6e55d2ab71d21aab0fbe212106dc88cd059943e4d9c4c27c4e13f796e846e0c634ad2ef3d633b47894924b664825dd145d806e14947bc0e361943a82fe91d5eee3bcc605d2f618a651e92308b612ee21d050803719db4c14b74d399cb649db42e96ac5538b42a151d090317e83bde0bd4025779f722087cb6b444f633d585586899ff1a444cf8e9ea7f9a2a0f678a80e8b7aab3b19976cb45fe113366d6f16be17d463d817e59d8712f260d4ac3d886826881bb10b5ce55538e7dfc4f0e9aa8ff7c4c79db5a70ff07efc91694ce2199bde44e9fb16fdca4e81c6849f20a5fde75d5b6e559a1edc667b83e83afe117278e91562335d680174fb0116a83237f394216e8e00eb3a672e5062de26c67b7bbc2190c1bafbdf4d543b7c097a42232743d2a4ee5f1d5195f4f7235e3e2f8258b137436974f36a45de44d745f9339e8a0d1b5f20be57f6f1d4405c02baa1bd006d79664c9fa648d87fbcbb52e53214f84a0e51ca2899ebc77a86f54d2f5586fb03aea6d5c1b91065e44d48a29a8530fc79d1d28550f2521f206aa57501c5c5dfca47c46c1f65bb526912170f5e83a8608604e9546df2ddd2f8a0e2a44b8fa85585b812f9f5f56ce99b198f026552544e12ea418bccfb3c44e434cc97b01c689d42bd4f4980b94073da813ddc74cf93ea47354480f4293686e3119dbab11e3e8c2837988b7b3807c12c3634d001574c1f72b64e22c3f1002a03d3874816d85f055e943f6697618496e39255862f932039fbae754fe9403a72337441946976e40c6a03cebca93f4e2b8e0825ba3486d890b978693153e529cd87d88b6bb027cf8fa98626d23138c1934d6e47b7a7c28e174da161433854cd522da934e1eec3f275b37a1d597e3df74f17b2fafa59721d595f5756af3febd763b0de7a69fcc1b54e31d344237271b4b3af0f3cd7a12e299858d756b163029f681e5b5864e6fd5d1ea627a385e1c710d4e0cfdf8d5e8d092b2260a748d304afcfa937f71f5a52a3769cdf0445214f92c0517feec5144757857045b4516f088e79658051c4f453419a50efd4d166f52d49510433b5965f7cf3caf9f6450bcb5c392cd1ce48ca3e14b266887f44d4b4108647a11d2af6012b4c82f19b644eda671ed5e2e7760edea1996e109ac63a39d0a5329353eec5114d60304b45844f82f003f49433d4b2002dc1b220db0cd25030b347df360206d1a2f33062d74ec28bd607fffc5030fae1d1487016ca971a1f1f917b96530433f1025a7067845da050c23f7ca71804b6191ba0a98882b06bc92aa00e3f691b38e62800d008cf12b4fd8f89f0281b451488eee597c04e79c626316732b2b4464a89e048fe2890984d33e2a8137c2f7037845960362e4490602500f4bb59bc891cec6f12907bf332e30c14ed4f1e9c31e2570e735fcc185a3750e51aea2bad7e3a24d63875b2bd29387bb9c6791b4685c51269488dac7164de29cf281fd2958f06d3dd8290235beb1833fdb088ffdb079b2f6b635460f071ab984549870810620e21ce5bd22cfdb31dad13db13af3a71570c262a0c0579439a0c75121a3736258b61d215d04f9b96593dfe2f175aae297045c2eb76c4abb6118ae4891d6be3ee34fdca82de64f76ffefba7adf7cdb804a5af6129e17e43effffff51a1d3cfd10ee87158cb1ea0cd1330236766f96e026465e54893b4f6d561a5404ccad234254fb1c6212fe021fed4bfa97cbfb989281f625682a49ca57460a240223ed202ce949096496cd1406a3e72f81ebd325b3ba65fca54c2248fb0689fc5f77d83540f33d81767989277c7c68959ab19173303c0e3a06511e447d5a54728591f3fe724c7799cfa51837d444c833b6e95f1ac21b3d399d9021c20f59e8440dd5fdb4509b4e75308ed1f3dd38a63170ffcb8eb461c2a1b9cf8d84536c78f851a3e1071a0b073394ba0cff9894b3950ca2e7614b4b56cb7d784140ac536e43467e9521e20d8dde1b9c010b4955bc184d460aac13dace8ba30d7e4cd8981f1ce08306f3c357d4b62f5920e72b21a4786d7057de7d3a466fcd8c7285640b2d1a51454b9b1302995edce618e450af9028589e30274c1c57e2bca739bc0fbeb210b3fe9ac0a9d670d075ba323413e58f145c0416112264623b4062c74a45ddc3a924c5b2a7e56231e1315296e62acb3bb5758eb2a8609917c9b32dc07244f9f65a31cc06fa1261fcb63814724e020a7a5f24ab3d452cc384abb2b4dc8873322d37a78a0f1c2c872c12156750265ef8f823edbbaa7b87d0da134d2a8a8b6eb4c5144fa006770e69bf6660f00c3def96b2d58c0114f85ce6756c9a80161585c3fafd469c0bd32de60c659b597b192d75e7c3be7f9e69730626142408f632addb2ecdd8b3f2124e70c771ffe7d44d6f108c5d88cf2b4933f98067eab42b963b4e345432702ab6c789aed8c7612bdde717fa4ae4d0c39989474e00f061515bd75f3167ab217a0debed3f86cca78e4094cc8ab8f11b8d48d5d88060e29b91f583e071e76115682174d30ccc4112623c772977413a89d6d32dfeafe6592cc5b8e94090ec434db7f863c098f1c557441c4986ea62fbcf762dcfb31305825e3197b1b99920df4f935f0602a8069c2bee2e9997fa13cfe8546ad5a44ed7645661a8379d00cc8e2f08813b0e37c32a2e0552cdc26ab1e814ba1ae44bc5acfe910502f2dde2385c7cfba4f90cf50325da267c0ded1689f7ed783e03146c43e52d8988c3de016a3b2b0e3f71855da316233f2f0c3b143d7710eac718e41c5c1e2c0cd8bb4a1e098ca8c06496a8f252a528ea0796107f7005e40c07f4853c0e5e1c9aa16c8b83372a8672aa9d56518210398f3bba8c143519f79a11189cbd21b5c89f6a2414e9ad24d09c4e8f8db081a75e681419f3857bc5626930dabb44a3b8144fe9377646f3a2dc37b756dad756fe81a992af47c950cb9f08622758d26925841ff882df7414c8ca4b7ab1241a0f71ab912d515dc710fb0fc68f69635d98991a884194bb6c2867824a2cc2a5c25ab240e95c4e0c2278741bef8d4c02798d95ddcefe8698afc9eb20a2b4087e4ebf7699e063fd81f9a343b8ff9d203764288800964cbe35c70c4d683fa4efb6d10948eaa707f844ec79c5eedfceb2dba26143bb18abfc0732f1166f2af2d61ecea36ba52919e1d9d3f73934c458cf675e8ba4f61f4d937256490b20b9b4420f375268a444ea3be528a68598f8f9ba2257464aa5b6ff40b4500f6c6c5a817622af11ebee33ba38033b097e7d590c1c0fdcedd997a107f814b1346a5e03729364ecc36eb5ca00dc8803e59cb26efa42c6f28986374c78d63066b76553a9cad5cdbe17db709382727c8d3151ffb540c3c8d05bd661b4c4a82f54b8e9fbf736adc34d5f67dce537ee39dd5c4dc4c35e3060ae656abc7f3ac2ef9e8f1893e1e01900ddb2a1920dda38dadc6d0eb79d52a4ebd99cf070fe2ca18279ddf1e5fe7320229ebcb15ac613b9867e846e52d1d20d38bbd96401970ebef340ae7b4102257453b5ca08f1f8e712cecf18df84380b4bc1330f447b719cb9119c1a8b1106160e2ccadea40af93cdd7132ef718e7ed0bd428f18139a28382ea1d72bc24bd717ff8fb180450d64131fc72e96358945a793579015b11820a66a06a8109bd29ae226d9d8a7a04466933d773d13cca50d5fc3d1ef7780a801e102c8028dd7ec1cd31f2a0e0f8879a432fa4bb384b575e7988674a4101e9bbd389f489e91cdce09d5249ac0602a141537e9e7881546efb7be69c5cc808bc5a483328215407826fee19656322c660ad1cd395fc34a47ad58df2b3c29ba9f3051d0245e550259ac2570f4f9eff65e39ae2070f888ed6ad1dfdc7fb1a03945fe26ae348444547ef16c8ce2c80611a8e5790b3e80cf048b87eb8a8c76307aea93cc0a3a0e0f48edab2ed9c27ad05402f8ffc9a84dd825bca45db8b6dbd134b9670acc3c24bcf8a938477fda94bfcfa509f730e0c93d1a23e88e2825d66d7374b18343573534b912ded6c9c88e8049deebd74a540c4bd64634c9a537b414e0b34ac8580674680152a794ac01e62d40b5b568b4a5159be8274d9927157e67c1a36d8468a1f64b9f5774f28f6ade1236894f680ad00210031d21ed0a84fb501d859f25e4d9a076c7926809f0400b5c3662f699a45656639b3a0614f45e8433b8753cd1f84a99e45c3941a473bb3d080b56bfbd28dfe00843c10ed5e5d69a894af8f8303d4718498d07ed44fbb391eb88cda310181fbd58bf18c910d98b07262052b426f24f2ee325a6d641dde9c2f2d0ee93ce4ba53c7153cfcd35eb98ec7505cd7a6cb1e1918ada128602fd1849df93ad9cf321935cc3ed543d07c7ef4bb1fff848f1120c93dd96302b8d0973830ede87caa0a1e81b61c8865eb7808f339b9310cbb40ced42e0458491ac377859066594b06707275096c51b294f9d725890b90b3ce7279635fdcc94809a96e5e495af7a5e6de55698618c6be88f2cf7f306f12c5a7fe3a1dbc67e49583cb4ec6d1a533b29f806e18b083c83497a506777588ca9855c6cbd7faa22c06a81304055079ab49bee78982c7e735de210a0da9c7244b6fb8a7252afacb576bc304c65c63c717994032d8452acb92db97824cc4c0a22c69980c77989d2775233a4d1193e09498b53b40c7f27c1e9ac15a87180626e4b5e4241cab6f6e1ac6de87c4476b259a37d0b9fabf2a6ec8a9068ed1922ab660da2a07ff01401ef73144f60ff3492845029473eebadfb0492a74b56c2a570d04dce8a284bfb2aacfc5b5dff9383e4caa2ca34b8b98ba45290eee9cfa78a800a81ab7dca54b8274e8098bbb2d17777419e88b573da9eb641c09a6d167c0c283609be2d3803642a95887ca32f445e70bdade830c5effff23ac01d312f7bb53dc0f88710271305e2d6bd081744066a31a3b5e8b25509bcb544a1aeed6de3fe4d9527caacf5c39410359cb395acafcc34560b81a44ff4867635ebe99bea831db014fbc6c053e99a66cecc0bc8818fb96bbf0c0613e7c778d357d1b88fae6d15b4f6a0cca8b4fe024d96bafa28f2375f2607acbc423574a798e78a6d99e9474af68af02d43f707889e9ddc1a27ca1ddfed373bfcab7f8cdf5fd2b739cff97727fb0dcdaf5ef3b702bf6a0a01b64b8ed15c8e0cd0afd0f6a4a4f6813184f61036fe23f3191691b451af77a17c26c4e30c9d78ee9bc105e172d66dff34dac39c6980a8a0badd1d79261cafad5d397d95aee43f20aaea378f7ea943bd249f65b6582cc2b30f943e83a4bc93279db0e04f56b34e07d9f7600a4daeabeb72b59ee421daaa9d15a09d779b711063b18670e6ffd0afda8ac63f7ab45cc23f9e4baf07f546627856354f0aa85ffac5146a0a147206254cb07ee8224ae122b00fc321673fdf9f5889d1646da93b004ce3c7c19e5f089c7c8180ef80a7a8862f98e89d98fc0917fdf38a3c49586e7f30bdc52323169cfeb1832b26af2e8111bc8fa855b3bcbe272a189a8ecc3ae5288ee2fb88565fc222898bfa53d86311ce9223688d64e53ee12c2db3255c699c663db42a4e7ed7b9f8be9f1ec135773c043c6be012628810ecf23724a06abe33c8ae00ca94bb25f7c6fc36c5b08a1e093670c7ab9748804844d6ef13eec61ae3248a456ece9947387127991629b463116bd730580abbb68f8c98b876f33dffbb7663495cc57781db210a4c3bd6ed32b920dfd0c841ad9cbf9425567647fa13d8db50665ad7f0db9c1d481053917ab716978770a5f72e2d1e026556e1f73c6617a2ac8da780f78cb6c21dfe55d6efccbc80b331dd613301223dd1316de82e65eb3c10678561b72b8ffaa6c3bd802e20d13add675ebfb7dc8d8134538b34e47d13a05682cf28d8044df20070bbd45d8ec49de31954489b8f03553476f04bcc48e5642c2ecf16d23c31c37b676e3af9adefe3b1a859b318a8405737c8678ad11a6ce23e84a4dfb3dea30ece1650eba8e4a5ae5f67213727fc2d47f4d8abc8ab4f5529404833f44dc7022609be02da7d30cfb7d0bee592c2395abeb5ff9eb8d2c0f48275ae1ad71b4adf17bd69972c52baa07f0046142ffe6f3ca164133c6b6b3328adb14fcd6e649ce1017758f5fa680fccc52b5bd40a6f99f0ef3321ee9be8061d1052609b084d184788cc0ada9b9d88fc53a12b1fc931edfe98a433c4af65ba0cf9e1a379b720eec3e68ff9c3aa5d86f4f02efff08f50755def32aee9209b23b61fce2cd4dfda19bb59721259b23bc8cf8b5e7b4c94be92f548fc2a49042b3de06cdf237f5fe0b653a90add29b044b5e1a400a5981a4ed93d973c533a8ade7175dffe071ffa115b4c2ef9a1bb23c87591fdf29cb49a9edb032bba7224bf32305244139334caf04002f781ebf5c1f3e950dc0416a006ea6263727ae1472003761e5337911ecf35a28ec98af6b1214d441b175a3318bd210c975774c70707bb2d0f76bf3156823990d6bcaa68d575c0645d5594186d94b3ef615deacf34378566624298fa10438bef394c046236e491b426426087aa15d8efd94bdf6eaf2b595a56b8775841738eee7869b94206a5e7bbcd4668e58345d979b5c193b654b2bd5c825e22cbbca3bb9c9c2b623fd09f90dba189b4d196bc0b6b0ebceaeadd31dcbe9a7f3e99f09bb23136b4c969a9b926c8bf50ade45ead1b2d9af51ef82c47d49b296584c1540a15d5bc4c49dfd536baeb8502d38e96d18fe158907595dd4e3bb65bad87e50c8fb7aa814085cca5d0bb9b491895861baaa9162918fd503e567061f3ca52f2b2edf44f76949a0b8c8ca24595a3fb488f993bd4a9c5fc2c5960a4255cdf541cc7ed8269c48b3d04b0e11388bf1684bc9a16d26ee23140ca0af875442aae40e42cb20d09f1b1f1f49a1429469c4d71b32486fe8f2f1c6101834d93d2d4ea3436b96277c02b4ab3452975f2f82daf35dc77e25c2ec2a5d816d32d08e8b088e90fccca105bc89ba5cc52c9b902e5b8afec8f2032449b8e0a8256a9a191eb672f4033be5ba5454e2b6cc37748554a0d3b6449c23c4fc963da43e08612f66d12ae0b7065a1d190240d95e1d1832438b142a91d67f7926779de173d5e605e0265f5defa47fd7da1c024ee006326e9249df8ad7e42fd1d5c7789edd09762748120542294614f5741701c0761fd31e703dd8c441730d849859ab4c0b7f46311a0b71ba268ebb285e8f2e84ce02982d81914bf0201ed6f841624ffb54f5f277951210772d59cd417028c02aeabe63f6986d5a31d48fb8fd8298451f7af956c4d94d88cba1c2d08cbaefe0dc00745457b260eacf5b430077c2a23d9990050f48cc63088dd853d0b737c2e76e3618cd7792a3b5abd6691f3ef07c5bd4e08d5884b3a92c570bc81968a2d4d69a384a41573a190118926fd3bd558f06fb89ddd858040b7b287530a315900c56a3ab968af2e3daca79ab5ae2a56d72b0ab7594b996c7271b305ba0616a2337e6042c94772b191b6dafd076d754546f1b3a9bf5e590da61b7313887a7b3344f1c46a599676c4be8765b910b89cc1005f463aa08e33a0090330d2029caa66c928bcddd71bf7cf81a8505bba4f444f1e48d7e8757300948c1a4ed160b4840ffe912f3fcc4d84f977677614f817a4adf46b165cd74d5ff970b8edf1ecddeee05e1de154eb378e3fdc44df8304851c7163580ca95232f501042e170ad07ddfa69619bf2f1cae6a7f7e752513b5410d8aa170e139335f120437e2b17f7be67ebcd17b836fc64a39485b8e6b67485952a844e89bcbe41e7383644cc5101a8218839a878021c469efef01a833d4d65efd7e2ece54050380a07e4bdb3753e6a91ccf36d56fab37e44902465b106718e09496f84593e15024491689a0142b710bb47a5e5f9a41f6e66ce7a08ed4069b5749b63895db6ab628d63fe550b38f6e983520c25dd9026936754b0ba62330b44e22c0e4584aff060ad013ca325c162932a0d933baa3f4c5620cb0c2169d9da5c8096a6bdb25378ea76c47ff322b4c4ad9bc7d21285541d798bad6407c6749aafcd174eb09c4abfe33e9df7e516547a1503722879eaa802144367af92fb7644847873eba563312cc18220ea7bc9a55a0aec410b675c3ac95d8dae92ccb6bab41e3d4473bdac001332b5f11c6215b22ebfb8179d51c946b668b7a18b98a15d9e211ab3e24ee9b1b5b117ff48a7f223a94c7413913d19112d33f02d2f5ad55ff9d2aa7d33744d71f1d04087e32e3d23ba161d9389cd42f26ccdf2b697c5cc81a1e239a1f76acf0cfe8d82daef57b1fa47ed533fdc25d99eff926e0036d17cf6c0b5c74b6edbedeef3645b284158bd426b08b48e06947573e7078f0cc9742dd8241d24adcc94443abfb477837adede58e87872bc14506cb6d04b3c5fd8dd23dae98dee24e412800d9e54cf138fcfce8526063ffb90e637fec17cafaf52e3771f1f8fcf385d16f324a188f5301f97a287ca4a6f8c275d74881ddc3f154078e488fda3073ad4f9126b4c92de94b2e19629724a697c4d9af0e21db8249ae010be1737eaea44256594ba524380082822d7aaccb242daca5ee9a72bc4e02d3b3a7e4b8076ac8ec77b67af8f96e3d438cdebe401cecc261adc5069849fff746316f6fcc6233b3b5edaae92220b66514eaf65f02c8dd000cedaf60670696a17f15a0a1bd09c79176b43ccd33cbfa3996609927481b87d88e0c68b390eed69a33c090b560d6a077dfab7d71949287679aebd634aae832c954777459d44a879b6faf245516a879b30e2a1d2b852ed92e85006d0541e518a256503f5b0440f2b9d4ca9cfea8cc06f90055ca6802a898ac3c4b194babf89972793a810b4600c17d9720d705fd7176f403157b8b8d7be35254fa46a27f64cb8cedf195ba8228e98dafd91003b622cbcbc8feff8dfe499e76fd63a25217283215c61a2892b4102200fbb2605f6f80bb9ee61cb2539b2f493bf6d5aebf2277633b575227878355a092f144d4096e5d7949f54b1bed5894ebdde22cea661a4e3125634b97a4da589eb638410e346c146917bd6129c7e3b871417e184e72399b5be794aa66f9b86734406f6f085b0bee623596cc586ce7f1c853124ac28013eeb3c644db83161f5eb3107e3ed34cfa9d45167621d88b622d571775dff6dfb307a409df12fbe330b0f2d7b9d94dab151ca7b9493e74d334744ad33194aed93ba662421a261ab12bc82a26f4f7c6d9ea2fce1eb07e2d6be7a4e705dd483e9eae5f14193f50b01c5d9d60499d109a835ed36c6b3e1270ea94aa75905458ba6904d468315bc975b6db77319064cd943a194070c7061f2b9ae70c998d8f8cdc80d513b23b228e5e563760ed08dc688b58e55d702a06a08264b25a90283ba9a52b492b16575105e39c6b2ab8ca70461bc15a5a5d0110bb93b12e195cd5a1f8de55c1d4022861361ed5b4fe0d00e38b8473de1d13c4cb058628dcdf1a35ae1010de34ce91dfc90bf9d6f908e78cc6aa9f65561981ed09a0ec8a90014ad8d22dd2e76097dbb6d5fecd1103bc7197fd77eb6d4e4d4797550a8907a06f24172a04d677d6ba5e41f02bc69e07582e8b0c33c084c7c59edfa2dda1d51a2200131949739084733164a38febad34bfd778cb6cd5619bdbc07831b20f8bfa2f29d913aa24eaf26520ba1b7beabf9e622f03dfc641e24dbfeeeefa8830c465766ab1c01413eb7b3942e7070c8d594ab204d2bda3dfe45e756437337a95afb9d668051e09f46ea142c6800915fa0deb54c20be57e63d1ae7dbb39697c2b791634d71671285fc460b2dc14f6783f27ee295b7eb01b2eb44de22002712e0905ad2899015d2739feec6e408663d305ddeb7c1e726eebee2c0b204fb0bfa5fd35ce809e6a9a7cfe37d47718113f31b360d8bc157d8bfa2b004c713c288659c86166ed5f4a62ba40410eb7b13a6bc2a81171bbce1d052bd05d206b97e86ceac9a20738a5fa8f039aec6fa2dc468f00cded9f5ade5a9972d3ae44bfae3ecc487f9a09d5a3f3ce97ad801046e6d5dded461bd7f58461eef8cc84e68a85099d76ea007155506f1b07189f62dbbb84b7f11eb700c851aa75e7faf748716bfa5613915256c2e793cb61fa37d32a2cfe64378b68d97e0ae5f2e7bec78d365290a8df5d3527a5c3f76e6ce6f6d8368a8fe4533ed31cb0fbfd834cc688e66cac249cae3be78005cbb562214c1a3107b036e780ad9a9fd038b7a00697510bc89af47ef4549c6d61478f56b4e7c184f9b246a6f16bffbb5b30774e1ce03d23af944805c028313ce4015492cbfa99004ab4e2efe333001af9834143d9f3f68925ff9fc1a6ea188545b686e246f919a9d1b7ca3aa57526aca0094e9bbd86ed5f689a9f87dfbe6209a54823b02977e6db0976c463d60b537bc3ab2dfbad348a3e0cfea25416ccf8a6f53aa58731d44f4f5cfa5bf7503cd179582df248d9044034f92756dd8405398a9ba6b9b7f8c8177bb7fae81b87303f1e2e910988bcfc46e02e8bd19564f48e1bc73042aaed6406b2728ff80c080f3ee8408269f20048748866a6ce32868ed6285b04d252ff2a48a8eb3ccffb0774e6998da9d7e65e68929abc9897820c35b520f5abb2001406145b511805871a5b5ece5313534bdfda24500c8ee0cf09436c5e56f5820ef9038362f18658d7b748d01a046cb632b7ba3f009019eccb9f2f8d4f4d0450741697798b62bdf02ded8c021f026168b880a04720060994fe42fda22d0342e707815340d6f6d559bb111070db00134e4a5589f885b11667da3a91931fbfc05921c24060e7e665b06369c77c484bbb01dd5092a98fb406913939faa00fc7025872b526e17b0629d7a4250b84b698d8cddaab4604b6df6ca81959077a18dca65177268ba0afa52f158d2d9af8f9c211a877ababc904abb090bf4bf30e8b8e4de7cdf411b69be6ce55b190495ffb459079c702e2b04a8b96c26369a1bcb8bbaba45bfa415ae3127b5548bf37b116415f726bbd794764260c111a3342343cc9f9feb87e4384561e08a701a299d50e96e9c961520fd8d3eb28f84a863fc57c48697a90530d2bd4c1bfa769ea8d4e683918e3abdca3409554827a359323bbc1fd3d6f92adb01ef39d82991a706f29b1ced99f9aa64ee4c0c3615df3c8c81674d0ff60f46a937d6e84177be7e187d00b8c49be99709c2cd52a170822190f7ce98f8ce5bfb1d53a8e01d44b880ff749ec1b33f9869dcfa6c4b242935e621486ffcda79472ff4824ec1592845f2726dcc766ab71a615f418ac5d812db31ae546634ce06a9e5e96d5092e794327e09579f73eb2d290efcb60ce6711cac3312dfff8dfa757b579d34f6d45f0041ee06f5d27d78b217197208b18863483f410e50b69b275141296b7f1adea2a36822173a819097dd41dc993af0ef19c022f61166dd5c681df2a9a32bf7ae2ca91f65a345ec38c5612b886148de48c269530122e556408da3a671a1bc1589714e32b097c480acfed8aa36125971be91ece551d70fb5f0fceb2ebe1e0c50e67b0419218e7c291e27430226e9ec0804b70d3c01e4db5a5e3f7a357f05f6c72468ae5fedbd3bfc31e6ca0dd05e4c22a11791db7d92bb1aaf601603179b1e00b721e720d770f1183d6546c352d9aca1b433588d1ed964e0cd8acd2019f874fe7000d6af2e4db53572de40f58a91f723ec92191b1985e85a1135e5758824eb4477683c09b393412702cb4270cb25ccaad918a291bdf4e3324222612286a8fa3eadf91a95132e7abd75c84760cfa1351862cadf178547b1ad4d731e34f161ac29cf32f65bd50de4de4ada217621d000b7f0662edd11a1ac46cbedc22d33f88cb335e4be51c9d4f6505a695b32552552597b773d87fdae0c462ff8bef4b7cfb5536d06b550176107293bbcc3b65367736d647cf294316b77eb63b8c3f0946e378c8e51cf3b3e93a2baf294fb0694a582ba2338c851059f53fc887c259c59f298eb4ed5014a2d80b1617fa67cad7e37c1ec1986a81244fc375285b539fd712648c00c8443e60a74600e0d0ef36f6a2b5d0f1143629c8028bac271235fd1d39eb059d4eca815b02e630f5c7123dc1bf63b541e4d088ee818652f8a46e11688817c11f95d0e60be9219eeb7601cad2ee7d9e760f32a4b0c448c4023a15ad1552716878b075f89e9b9f8e0b23939f7df686dae6ca2e37daa1e859a93be5434ead5ff4d67d1aeca5c387dfe9ee103c10ea59ba27f11b1d045bfaf19c949c6466c542c342f59dbe29424cef38e42c6b00b4a868f7da278721554ce8cd65d19ec22ab341e5bbd11dbb7021687329b94e3fa54bf7f31b0dc5009a131ce7ebed716f34da379bf59be310f9fa90b04a21c3629ea98e2698be66e865176d31b64e611e2ae6af9d0923edddb8b3a33640b8683e314605fa62f0a69cdf48cc3f548a1415e2564d33b467472977324133070b8f7435ccfe0424830ad82f913507bba94ec93015fc3e5e0ce7c4f24bc01f7172bff47055d33d0042dc76899bbf3042091c8a249fc64eb8cfb11ccf3c8067ccaac7762bd4fa102565cc97b774e185c3cf6cf4afaaaa89d0571ce7abf26bafecba66315156e6d8d923676a8bd55be97a8382fc19a3f8dd9a08d21846272c2d9fffd1352c2e7814670937404303852f1e235856f5b0404a812ce943ba20f92dd1011318b7de792b7d84aa5ccaeaff87d7ae855f0d0b465ff19f9bd74fdb548ae5273bd7b88e5828707b3d36696c2c3ed0b483570134794ad09346ed83005d290ea3c63d58c3508b24bdf4ede5a6395ab9e158cb2ecd2ecc9b84306727f831b4248f84d2641e2b37aaf8190c4bd25b5160381b11e47263290d0cd48f76f91c0818d4f85eaff72cdf1b63ec26ac1ca29913d4223368e4fe17ec84a2bcd81b36b49d2b64fb035db96ddeb06e1e8830e9f9c5e21f4cba95fbc24d70ce1f69325a9e2b861fe7bdf15576eeaee1e6c8d0a35fc4a0e9cb77b4017bc4030ee28022d6b3b64876b3ea0135dfb027b3b4fdb328f58d16e75714365a0ff17dac645f0d99fac4919d8d8028d18349b8483cfe9bb9eeb688c776e71fa846fa4737faa769ce55d3c225ebcea0cccd8fb0a94ffd13bfd284a863fe7b4c0cf444474fa92d9132454cc9a123425de96e0bf3b92064e0e1074a06295e38ca5589069005cb33b48f130b570a2919e5922884bb4d7b105ef811dfaf8640fc6e5cb6df2093119f029bde950620380d6067331913c85beb8bb48e6822b10cbb0ffcec2c84256ac799b12269c6068de2d4426d8743ed5cd1e74fc9eb93d18c0dbe20bbe671123f2a38ee3347d9eca0c2d36ee0a045a9615913f0c92421a6bd081fa1f3704726d00ed658755e4e68373c0c2a56174a10190081d7b85c7be2a4412b0ecdb15a728fa35bf5aec02ad6f6c460a2b3cc33a8b83bc607e89eff7405ab09eaca0e16c3380307bd733988f3dfb1e58c1b082f3a17c0489b289574b288054e37dbc907c817623c113066f262cb0d845ffb01ca7712dd83d1460d1dd55f2567e8a5a90de39c8142154df7b10f0ba70832a08236c3effc16d6c4b1e0522e4f3b2f18c431b1abd4266d660e12ab0a9bf098f04c5f48e17eb91959ccd309d08a44ef596423b48b6c385a7e9e56b80637971aa537eb4ed4043c7416298a4956a4b11425ed3e7bfd8b2380bc517c233da779efc6cba7c447effc80651527323734b4b924d5fb328995c12269bf0747e2c6405dd1b8f952976a464de668e4a1d08c11a5cfc1873f60bfc1eb24012b89457a9c8c714534ad1cf0596d9424764be2b4f7d501b324bda23644a1868489fbacd8c5d8b88570da309593b9e771a97b0448e40541e0321ce19b4ece353222cc8c6f4379d3766ecb6a490cf676b339eec7868397bc3379fbe0f58711bc6ec843dcbe6f92faabeabe8ffe992fe8a4daa8fa34929d5e88c3ff4bbe4e24ab036c63d86e9989d802ff1abea8153e9c02225a526994c29a2681d90c1591c92f2f700ffb6927f9081f9ebf2fa104c1afc87b2d2b8759843a4b861e3d7f65730deadc5c05ff3467a706b3bf74654099037657123bf92120418989908900e0a540b141d3eb22ae054b405510daf65211229a7c70e29865b0c217612c1ba4334e28e7eaa7cf56e776566558ac7dc367d2843d106e45d30183204102efb5e89851d384484266783fb57f0d6941a350e47e19ab3ab9463214eb9729866e81469edd13469e49be20500f9fc8028978c3c4086b0f91d0b29c23f500acc3dde1d98e0feac0142051406f6e91e8d0662cf31e474ef72864d61208feeccdf58db68d510eeb441c9a9e11bbb9697cadc5ffc7ecf85226005673df90161ac486f98fd1e6b66e5035fa2d0a849b3d0edf97d6aaddaec626237fb6bf912f8484aa1a10585b0cc292e954f7a1dabfa79aaf14b168c2999ef795feb7329bfb1cb37a0c1f778af060744943e653ca419feb622fc227b7cc796ea36bd571789e0a52fa1446325a0de7891635ebca79a64b06403ed1fed8463ee257dd290f63e955b056a1467908701c28f3ccf9491488f336956c26e5f0a3249ad0e2b0ce341f6b20cb40c0bccc35afd8090f08923185e33deabf188c0be719bb294be797ada74d0a6d8d6eb58e54adade678bef4f186a66eec8fb84c0a105f110405fd8d59042628b2c4475d54c9f175c82bb0cee70ce4c6db68ed21268f8217888e4306f7a8aae9baee10f7f6b46d6c54754e48e7dee340f0e6d256894f084916b8b266e298fcdf4bb902d3876219f5c50aaa3eb3e5af6246cf07a6f4bcf5882c4de8a6c9f7fcafb8d40d88ae8f610d95a900cc941a61350b51f80bb2a5b9e52372b27d4bb3f8e76e14a20c63d204ac167ae5b15d56511d5b2f79ef44f58ce41762d9d143f5cd89247048df1a1c8b66765735a31ccd4195e8e8dbf5fa4f208dd5d49f48c4ebf287c9fd3398d288daefce185acd29fb58ab39bc5d37c8b69219fa5547a6919c0f1d034fe56915a632dfc76fd64c6cc9f1df4638d21299d1b766cf85374d4b3a7cd723fe354928d5d744679f8290b40c15e17b08e0fe15f913ea2eaceac3040f61fda36d0c8a6afb16c13a2c3c703951941a52a01e8ae08a6ab260b2109aaadc122c4f588bcb7c699b459e8e86b3cb50a72b43c9459c182ab5ed83af0301c3a38b63b3fc3dbafaee47d4acf54bf719831ca9767825abe7afd81a69feab0d2b2b8f11289a8c8e5f7f418eddba2654823e4705bae7b1443c9cf5a37aaff53112b4f74b4fc538a5c32e8f4ca3753229283a69dfa986776b07824e1ba27727770eb243bd1e7a878afdd470d835328d63d5ce768e86522ba914b2db0648b519ceb641baef43cf4114ad24b26180bbd2d346445e40164af4b7b17fc31ac4dcb0e8a839612cf775bb37351c38dbfa38d0f3a8e7db0f92d0d349f83cb7bdf67e08e6322696d56c50190d0ffa5e16924c3fc8fe6fe70bfb9c9ff09775074bca01236b90d5d3ebb892b29737288e43f7c2207b81f108c6b35b89af5f02abdf24927fa9b41a67c754d96b2f97fb7d36e680d113567ab408f7b37bf65da3a4c9e1e3a264cbc90a3a211e3bf5782ec12ceea352778cfc049b8234abf5d148d9691c6cf8c51b0970c40d2a0f52f1ace776117566d5f9561cab5781a582ca0f523f1b58551c84df19c27786891b36583b8bad4a9289d701081222d4dc5bf535f3ce84edf5db86a9bc1681201f0c9472d67abb0b5032435d2242cab2c9759f4ad0190e5bff6b44b6f1b29aa4d5efd6185ccf42fbdd5f72f14e055729dd2890bfa44101968a89d2d347496fdb86b60eef15ed18570e14025afa2738f6cc23a6272af56f637701c7de09af0d9818117d64a091bcfc999cd7f2831e51dcdb1debd5ba5c1977e35db4c1459bea64d5c1ac3662b29707ac51c6014e61a6c9956307c2efd28bd87101783ba631c1f3b06be5662aaac1a9d6805e753173213c05f282267ce425478d1e3c06e28c4f12f2495d6e9c12e2f2a4fcbfe3f0d4261c9d14622adb747279db72b63a498330b383d46abb92b0adb808e1ebe9e381b131b0a73a2c2b6caa06a5a488bd107bca103ea1cb7b8e7c6e7d670fdabb651cd187db2cc7c07957300cc2a80d70b41038a932de608a2494a5a36787467f5a54be0475d226ab5a42088f07458660001cad8749bb81eb8abfe2e409d197f78a3ab6b767a79fb3ab9e42242d5c808722e48a0cd633dcbe9b94e5c9ad4dab72b7f950ea02708bb07d97a372f561bb45072ef34d22814b3b9e44bf097939eb81657d0209dcee6d8bb8b1686a389019d558cff9e7d8451908de60b993e3d90bec3685cd809705984a8bafe07409c34d1bf4e66b02e52bb47325547c746248fa974a0576854826ed8de7c8bfe8e54e11ca33f840052c799d3c840bfe4d2db4d816aacbd966e147d216a4c1013a533d4c5740cca0d601cc582a305e8ea7e3f5e129256b69756b01abaab66a756caf9a1219dd8e7eabb45ecdffb2fc49899dd87f6b3fb5cb5c1c21353e5e44f24e001f8a8781ebe7147afb74a4327cbc2e5c3fa4dc72cb94a44c29a5d404a2045b0434a49430b57d477b40ec909f1d133f35567ded989e181d9b2507d7e632adb53ccc5a3a319788cbadb96a8f522c98c68e7000b1733e3b467c6a5cf429d1bba263ade4dab9b636c91a2b66e64a89b5615c655731a458282696eea8dd01e42295d861df12352c585f4ba607838e85936ba9da8aacb12dccda2fb1145cac8a2b5748aa65c2c4b21db922808e7a766ef4d4b8387dac0c3d2d3ad74aae9d696bb1d6dc2a33f7468c75c1d5da5cb535a45c154c2e89233b20b606891d1a246a74fa907a2d1b1d8e9c4bd536b5d6b6993589b940ebc9655ccf2b966faa85933b746cbb8874f8b0499e9c368747841bd5f7d9b39dbd5ff4d9e7bd53f7b72fb74fb570855804e1f231fe450fb7c1abd38303d4a121711ef1e94276780edc0d1b4ff9f2ec9c2e9e9caa9c241f1da15fb4a096de6517971c133deacec1d9499de8048d39682bb7b66e6b71a5c55b8d3e3e9c1b6d764cbdf753aef84ef449eb66cb79795ad87858f12f3734bf68414d9df1e698c8c1413f7e8d4e8c7b732a9b39866a71239431a9bf469ff335da0055f6d0391c1f65f0c561fb79f8e2bc1f474be3b4f8d9a7fb08a7e8c5e1e1a1e0e3170769e4f7bfb5dfbbdcefbd779854c822a1a9ad1cc8b2b2ab7005ec67c5fa6c8f15df3db1f68fdf9ba7bfa2c1cb8a70655d3cac2b9e26e806e9a6e7175fd4ee3678fbdedc2f9e6e1fbf3767ffe3f766ec073f7e6fb67ef6290682796865c13efdfdb3e01787e7e3170783a457e775931f149978b1f4c34651d44c8b3db5a1252772239ca8954ed85278978bc53f7e697a96826799ae64cd7a0e9a8877166a0c7b7fa1e6586fd3ec69a981e6a976d8256a02833c18d42a1f88e0f03bc3f7b19a808c5fb2cf18553be45fa2ec4dbcca705ac006c0195c74fa75b9e167ef7ead450b193881f283f1b1aa5d3097ad5df63b03c4489f7a08a3db11abe9f6622d77b0782bf0e37726edb34056b2ef72c1decaf2f13ba36226e91757b65e309ff8d4e62e73ce5968fa7267e216f6a9d7e95d759004d8820bef8c0fcf3b639c80fbd5e36ab95b6e160b0d187df6ec9292c8ec6cf856862c16228e40c0e670b8616e3ba73d605cdc2ba18b7be5fb906d764b59a283f460451f99200570446203a9a1305a0b240a6407372ff2b874a08a0449fe719991c93de998738217d482bac149e090073802e078814fc6159ada580b2627d4d48a6079219981e459524e482ea419f29841fe0b1e8f209ffabe9077c80ac7b2b0231286151203f6b8850349230047dc44468e73822433611d7279ec250feff8e08873f440bac86f481fe4cad492bc203688d0b092393a294935a148624309dfe7fc05940f6405c7a333a41e720047255752d809120147382f25aba7b31f474419d0bbca322ef95bc3fb53c25d40652d13c834646e42037981186e473a46a22c3ebc63051221133f6e748400712ca9b3021264633874eade4c6ccd2c9d50f11303881e084f46f02b28f5a887ac81c470348f7e4825a4cab10e087e0bb267b4c0ac4a78ec42ea40d2b01500dfc03636f420bd341872c68f33900438160d8f982f8f107331185e3b8f3790398883231e12e558938395e1c89078090f647a84ca40f82faf5170c465399224799079479ecb44f2cf930c2433c16cbac586050d1935ac6adcacd6d0944c85594db9f8135bb91594588a8cd444849c424ed8e87a8cba8073ce39e767c939e75c8a0fa9b6836c3966c54066bc3c640488217e020ffa75e5f3946011444ec939abc498d3a33e252e6fe03cf314c56b9d1255148fa2a2688aefc4624e21b2003df76a50d13775d5de268afdb05f51f37c959876d40ff528c4a2e6f9dc1773511cea55621a9739e03eed9a7b6d4ce73470a8b186ba2a8ddb7015f092523441c17d0b1fbf50553f45d017c0c7ef9351eba75fe875f297af8a9937ea7da38a5ea8bc370a2a88811ef52ae7dcf4c318cd839c9bdc54b3d7daa77b53558921f4cf41628c5755d563f5833aec3ac87d9067ad8929ea08e8d7d9a3a81d52bcea4b8d01ebac8a90937c98e24f151f0c66af66af6ab56882a246fac510ba0f63847fd546cd933768df7590ba12bbefc91b549f2bf0f90b7d0545edf389c5f3138bab267e61ea85be0862a528e444164d84a1c0e33d416bed33f79a87f75e0ca18bdaa7987f26c6649f52ccaaaf8042f6a657750faa1db48fe944a147b51652901cc278c041bf941e66cdbe08f321f8f5a97648f1dc9bc3ecd761d0eba199929a66575595687a156be3cee051f20962e5c79c9e7b94740414220bd09b3efb9d0166ef522516cfaf7bf206ee4d9f872b5ff5a60ffa534d7d87517da98babca758f0f7a6dd43d5c152188c278303c3951f7640e4c6239214fa82b6db45e7d712db25f5ff020f3195f7b56bf8c05f3c7e5eb54d58283718a9a7f8a35682b9c8481e97f26fe8e7eef49804f843058db0e06eb460f2e03068bd8bbc759bf3ff99a300306ebf4b07efc360dada801c99a7a8c4d39364d22705656fc0bd311368ec948eb83498eb8ae2b7ee5e397c9cc4710b3acec2a5ce9bc5f8069e2c0a405b4324df198e5d6ebbae2994efce28b39c5f493832e3d01022e21e13959aca5baa5b69f251a3f4b61c6a518364b59374b55384b532350207dfff1bbc4a3c467132c5924709ec473bf600d2468df017fecf231b00427f62e2e9d757161d9cfbd997e13c69a7789a053915353f2ebb7a9ee6b999c4b68826ac282c36a0f8800ce10c2d660e336acf6000a7800f3000950a002ac500f35d018ca7f61e5e3178588bf3f7e91dc2025bf15b26b9fccb13ada5c9f1b3565ed8f245096bb828a881312529b06d41a41d2719f906878304982e744c8930f34b63cb31561ecd716db55d512a5f03ab230845ab5239ea2a31ad689efef481b4404ea073b23477ba4467b37e6b2e062b7acf4a29c928c88daf7440f8a1336ee083f197a6d92912a20aed38e56449a960c562a06cb851543aa3d5b6adf8c5c1e231f56ce0887eb893b9324455ddb44aef5e9e1eed860f7dea32fb7c48be40af649535b765464c2cd29ca716d8a66dab825ecdf1dbb230812b7366aad1d42c33582b94b5aad198b144f6c150a36a8dd21da11422482fdd1e426af651ad29678737f70b846672d09313f185e2d5a1c50ec1124fb36e4ea0c9170958646b43d7d6d1d1236aa089b06d76ee96879d0583f3256af8bc5d5920a204aed50519e50892ba11b30276cdb5e7b828e26ce9de2d1aa506bd1669079b94817acdd54fb8249eac45dd0cf8c2063dd9f921216895d1b642e475b8b1b6d56994b9374a1b2da272a3327f788c406016a7b00e93801a2714d306997e0b52b84b83d3e8ab0b56b664bc2fab6d6aac2523439321172e3b161a286af8ff5a1d7dea07351c84db5b536d6a8cc8888e5e062d5aed82a4432ac7834d54f0bf87281a97e5c545c4941187c0719e613a26bf27bd1e466b88e1f46bb25a9226272458eb87c602c7d6eae6b59962d4865994326c50ba31c7a389482ca6028c910f5a4a5c94071a344dffef885710cdb2d3e6ed6dd990b2544f06c32c0c1d5f344861130241ff4d53aa182a3e5a7828f5f18c69f7efc6ef901b500f168018269217ab5fcde2d9f576b26af65a8a568cd25a8bbd63846a7660bd1ce8a4bdf62622d962ad6c0f2417bee4be81e28c41a50124e50a62a5843d09725dec0ff738880df87eec7c97f0ef1dfefc30ff636f1be487a5d589d9db3fdbdf72616794bd0b9f6bed07a5f54719969f6beacf04daf83ce239c01fec5140f2e787006a699b356f898033f9769859fb77e61fc3a6fbc9b96112d34ba85668769be5767af15d8955be7de258358a73c6b8bec93be7ead8abef6a9efe097a49afaf9b78858b32f86d0e7067e9125a3fa058853a0cf44ec2659f3077a137ba0c72cd8778f626fb260cce4971f0844b10838fbecd1a1fbb7a21efbd48348ff69c1ec40f4343fea51e2cb49f46e99cd6e2de6aeae9ccc631d4575099a66aaae163c5653afaaaaea851a03ea55357fd577550455ab6abaa27814ab1952bce9f30933f4804308d9f42ede5afae209f42843d4aba907450bdf447dd0771425a6176050a0e71a08240683689fdd266b7779d14063e02650b3df9f67590281406d01468a1a3757d235e39ce1c5bfec0c1c74266ecfc1019818b3b3e71ecabdf7c644e1cbce70e1c205576fbdf0f54a066549c4ebf947bc54792f95da4b85f4f14bc5f3ae002282aebbc97d8cf6e53bc5e46b9f5b6cd698eeb5ef2e93b558ae679aff026472d58a5f167e1fd881be7b16753d6f2821ff107ac51aca55632841ff6dfe550563387d5681de543bb00089e7f901271dd0872cbffcb8f74e0ce61c02fd7b099dfb102b1fe30d4af54369aa1fca99998c5fb837d51148d8bfbb4c56cc2288fe1d247ad4c2b6807fd102102743f0c061033956f1e7b03328817e527bdbe54583d26bf5034a4cf58b778fac9b985a3887ab70256f2dba14d35f16d70f937fc7a0c15780879c6a7501f8b3a0a7474f6ffad3739d354af74133971aa5bf5255bf7c55ac183cad67a976307530ab0918ba941d2da87709f431a67f5a81390375f98b668c490491feede582187b5da66c201018e2dca5c7aa29f3fbb01c06dff0364be7d238995f3c51bf5355fbe0191ca2e9b003fe280d20a2f8d2a3108b419fbde93968aca2dca3dc7b991ff440af0e51cf61e7957f1275cfee672913ccda6d5583d2393325a88d693a97f68ea6e99cc680f6a02a025a6e7f8126aada7734fbae7de7b0d1105a1dc650f027f81a4fd03a4f50d3b8bca1fb5c0ca1550a28e95c91820f769db5d69d1883e229788da2689af78b1cd5fe043fa3dba3295cde807a3525c2fb2ed1342e9dfbdc777f01064e6b27f6befbee28c4189482debb5735a6404c512df4dcf79dc2a1aa9aa673296a8795824759bbca7ed79d75854e44559458cca9aeb0fd7eb74fe37a05c4178436a66a1085611eea2a14ac7a3ecc013b49f7cb7a50a7479d32d88ee01e418e1874498cfe8cb2be9c7ee825c10212fab9e3ea2b70e824614fa8c96b43c59327e8934ddd96510951ddc9fb58c8d652142929f1aae04336bd8d28f2037467a4dd389fbec3e1d1e6b6c2890d2362383888c8b656f4a089b561c09b443199505b0e882c612665e5a743878cb1450b23693594484c8dfd48c96552bdf7253a3941cc9abcf08c6ea8110da12112a6e50542ef3d8533c1328a701ccb98980479d231180ddc91ae1443dc7840e8bdf725bec4eb80c58d3bb716444b4d6ae84071846fc8949b1c0a5702e415f856da7bae2d4ff5ada059c2234b2a93b911c183089491b4a12a9e84fb93869f404a23529bea3ca2b2a79aa669a6679269cb91981a71e306c82b87022232826ce0d13802ea622254f81ea77ada636a62d66473c66104945d8f1d3e5cc4e8f27ad9481fd9751f609edf3b38e7fc2c3947fafceaae2389d239279f696e0d434ea4ae7ca29aa436d5910f4588b4a5a9b8d3c2a17286fcf2c903e283444ef701e6e9d016501bb2971462b246a672f7935304c7a2c6505894102f850be7db97669a02e7bc0c6b326ff69c19a6649ac2ce7918dfe75bce2afb983393e1a4c3868f14047a1429facb2a2bb3039343b9d32a8d98a669a6a729667464678a01f1e48bee81254c9e3c3ab91d145a90a43a271a4e7f4e63682a990aea662eca344dd334cdf4f4795bf2ba5146e73543e6e96ead8610323b22b0a96f8295a5f03a1836224c985c19d568192de15bba4a61c3ce0bcca9d32ddd24f1e8f281978476c159d185a580e2d340c7b42f1c24ca8c6c75260c601451c322c7e9859b5669448a73cecff2cd85535c1ab2098997054954c937bcb02da4df5904ca344dd3344dd34c4fb0124640cc7c0ca17de1572d4d870f92b5b523086632ee9b49212727f2c1181c8f1a5f75482c3cd821d1ba76a8ae849246df4cbaf1217ef318d185e027af8f1bb22f15463d4a27720c91aa19d1440d7233379563e2ca848c1dbdfd555970c2cdee46cd16581691998f84fd70dc43b8c5cead2b10632dec2b2a2fcd299f49b951c101d974653e8fa2a0c0e90c4ec6dbb4034969aa0618101756643bd94bc5e63cc6e7fc2909b5e5a0b211a19f157463c63399292abd9b125e36425860459ff3b85547ed12cdf7de7befbdf73ed4597bdfab7e595985789bdc976bb97fdab25ea0b071f303b46314cb692a9274f186e570209c884b3fe459f1ca91043fecbf2732293a100702bf0ffa1743c0a5b5d639e7ac75d65a1700a52803051cfbd8e3b3b48f7d56138065e47d8c9114c142ba3ef63f906f7d42b8b0dbd4c73cde30da16ae918679f878e3cb6f564710c278ebe047073aa6d9518a1fc894700d340ed0fe0b12625ccbd3cb910b6cd054885a9c44200183cdd0fc10dae7afd511845f138b1e602b2da0143ff0a53284e12d58f7718ead51942208d4badcc77eab09e0421dcc131fab0ae041278edd5496bb2ccb5dee5d967befb22c7799d1508a7f677dec53921f6bf89888330743fc6574ee485c5d64af5712bf5e551ffbf38801c92714841d5d69374424afc030dadad91a178c1a424b201c9dbcc7df7bc7783f858f5faf9b3f332288bd068b41e46b9f16d38f5d34095fc919d88ca1d555c6665ab8a05f4a93586e2fb5bfb717d3df2bcccb6e69da5d9f5d6f178c272d4c5fc919209454aa6f9a21e0247ebb7a266cc8b9a1ea61d7f7378737236e87ce14535a57cd37df2e1c1e4b52ba749b3a0290a5a8b42b19514aa70c2501000853170020180c08084402498ea420a0b3ed0114000859cc789c8444201b4a02611805210883300c000100023000003100c3201487422ae7001c476589e2a666b441ca03c20922b520c4bf202792e30f05e9ddcf6146674c60352afe5d189b47a61c63436e1924298a9f0b4a7b36225291085866ee6190d429c48a498068c1eaea8a0f623e826f38c145147e905500bf360df90bc8ac014cc5f30845198007e682627530c22aa91dd9d20ed0634ae4d2913bf0cf4eae741f89a809f3dc4850cccb2991725a4650242779fd32c79793319dbe7f641c03b8e55815918f152f49f961de1aa40dc84d479d64329c09ded4f23440830b0c3c416e983638b83d448b73123773823250f5c284a8d2b7f5b0685c41bec7a496017994304c568015d0263049f14a860961e59314007bcb31d9fb8cadafe27e1fdc2e42deb8cacb43b2266917b11b9f6e9d419a58de2ce140c8d551efb9676a8491f5ec814461b828a098a1e14d9b0e40f5a70c7df91fb041e664e3bb885ad62ec9ebd32d3821e7207f0eb4d4c86529b4b1d0784ecc0fe623f99702de6f575216ff2ac046f22f3878fe3e6f258553f1d284738c3072722883928a12db22621dfc92e07de858713b885d85c57078fdd6445a6da4fb5f692c071f5b4919d09c36e1c2cd981d20b34f587c46f83411e8c105f6e7929d8b064db9e3ed691712e1bd0dec37974af363bda92c0b4e82f09183906feeef5c34dd6639e70dd76840ba3841d8c5460542d49b9e24ce06ba57426097f4a23af5e919a001411c0bf95a13bf25c009a8abf9915f94b2c12341f3f829fed322c1213311bc6bbc6acb03e10fe45eff92eb7eb9ed5709907ed9e1060a277c17d91a6e2e0fa2c8777167be15a93f5a9cc6237acfe9297f8bb3c00d4788255b4bd61164e1d7b856ea79a5307cf54104111ddc076004e12689388f9d0a6ad8b5f11314b2632a8802a5dc0ad03050c6be3b0b6fc5788540e665594870f32c079a8bbec0d337a1da8e4dd097819294afb66900455730059b2e68a75b8347c5eee911a8f12797e3bdbda262364fa64766eba97b88595125478b29becc10f0150380a5726fc6c0248a8ee1237b42729664956c26ab5185ff319455785f8f374b9d5425fe17d2cf88682eae1405e89c347d82edafc615daa49c0a74ad5aad0fe0750d00e7aa78bd32fda92c93650e1b91b17b4a943350d2ca895c974ea3fc94cf6b76e23783eb514b22f420334c56e5006b16b48801ffed3856a9e4073848e682764578f807d1f8b7c3bb5d4c5d2ecbb352c5672514fe36865ee316724a01a28ccde29863e516ee0c068b2245b111b341ab53cb0ff3d495c0770161980812a16f952f158dc26872ba4a15d069f789d8b7945f7681149f33a24ca6c889e236d44d2b4cf197075b2642280ed68b5d1fa992d953fcbba349a6d276f36b49609753822b18f49e768b5a6b2e53f111b44024fa3d6cd059953ce44c2cdc5e223f844d66d79a7c00a737b09b2276466022018d8a11804785041ef00a839327923761cf2c81fd96d37a99ee91fc5eb002ec3628716fb0920187a85a46a4aa38f604b6ce9838c1dff826efa116397a905a392bc664844a0017595ac83196f99d498b2925e06e8b61e67c6eca819088178bdfac0777525a9008558fa108d1fc14159b7a575650b3509d4c0e328da6588eb6b2c85b4237f6abcf2f864393c4e22659dda9f1ba55fc3e42fc0477ab746958c0ef7208eb2424d1b326a7f73cc62332edbdd1d5d274037b2485f25e4d9776699c87192c4d20d591916e7788b41ebf7c0b5bc04ada138c217d2ea359e0b789644a042c77ba70007088ed2039ae746f2ce6a600576b914ffa229ef5cf23c7ad0f0cd7a9bf5749be3337f41f55e83d328a725cc00c3a02ccb0ca3ec5f49071440edd3395b6f5801ae08d93a7e8801e12d46a13de0ce6fefdc641027d635bb2430dfb92bf43c60859ff8cdd71ec369212a4f9a89d0447c3419b9bf1b9c9c7175c719ee91bb0899d1f8088db3b1bd21f7e09ec05dc35a3240f1b09d864113b9be514892cb6caa0d09bc34297589bea8a319984bb1e318b484631b71d0b52f6f95f65b9ef83aa7113eb614e40aaba6d80708fa9d4c49b092dbc8442b626a444094297abde00f1c2f9a1f0165bceff8b6147bc5401bed5d39a633d3d6f1d6d986a320e275b30046f0fba5ae98b63ce5c2b44866e52c1f98e0b92606e54a940ac2ddf28c9f3327fd1c2a61c889439a95d3d78f0c9759781b24a2ff09c5a2bc36090b4ac9cc1fa330a1f9105aeb6eff67b1482f9f0e80b36fd7ea9c5117b7bcfa5ab6dff5b0e19d4f2acd87683787348a9635015ef427aa5b32520c68a5957fc38b738a64f94875103c1f566c0e24c88a86ad991bdb6a9bc352d08d35bc4c3ec5c4cd7c709c19751cfebf1d4478bebd629ffe459644dac18d13f469dfc9538032a928f5f3f448f6bfceb80cb7ad28dc60bbba522a957edbfb247488e6bdcbee83ea550a4dcfe26342e7d42946e304283093612259b46fd367aee7e00eff5cd2b8b7caae7f9d662ee6b26e3c9a6088df2787b7dafcee746102134bed21394e28e6c6a8050509c17c0138548132773a3b12322228276b6f682cb8d899f0dc81390694311a9c977c0e3fc6fbf9ce79b8a4fdaae15ca2b2ad6e9fa0f84f2902106458f48e99d5e632052b60b31d886229cc9ae411435e257e74723eadba02801f28b286f72582485181e7206f47e561c2e7de66bb2c8a1ce17cc312872834e65a35cc3a2b121cca238aae51874358b2acf363acc1e8d8cd8c88d9cf07f664a8e6dee7314592614c30409c56809bb800e5b2dc2caa04a4a3ace41f4c05e6ed62d311d152831dd72d73df18fc634cf46cfcd503537cc8079ca49409344602a08a89c2f49217e1becef389e70027ae17527447c92138b48e2baae581d687b9e19232bc7d459e3625cb8bb93fe3106f8263fb6cb26d719d65f7e0e499cce0db6754f92b9866a3656e5db64f5cc1ff6cc800a4fc6ec069b9d6833f4c91ddf1c84033ee53313cc0fda9d7a4554e02a6e54b00057a6f36daed14fb60149f0ab2e50afb201b78b5258688072101bde7616872fe0a076818d48747e7203f034f947474c560cf8034a070b5289462803a2d30fb0aa4228a3516773aed0533eebcb5992d725db67998ec681c9b17004dec9ae1ca92f4fe0f608ff4ae0b9706a2c2ced31a66f2e52c8c18d7b9714d7fde96f1a65fa029c62cc23ad4d2c2bec6ee6f7a37a4bf4fe30d647997b8052cb4f49fe94d262bddd60992460095be4f9b700aa0b94f40209cda02cb4b75f34e82026cc6d266d9fc67b80def505d23bbd29b5834083ff0298ed48f6810681f88c50cea205fba6975dc22f7820a81bf140447b40d36eafc503f1cc0ef5e9fb3f00851883698e608037a202a27503b38191e795e15fa99353709a021b65af92d48e9668f61fd38cf72f06deaeb289fa2cfb76a1c450d8ca52a27f2c718de1af454f36b651516d1442508f9d6ff57656984b40853327d307fe92d2ff22c0ba688e5f5918f6b1ba781509bf18cbbbf28c8f34278dc740386ddd02832de6307da018b510db1fadc5dae4adaaf9caf892cc2e5e53b18b13460212337e0e1301e2911c692d160d71167cb05a403c5af09815cce0d45b8ca45f620e1296be0349154cf729cc56819ac0ab51629efa0d2c30922962bb4a65a46ce5e76402b454d305b60dfe58107ec9a7a41e02d0d447b6a35d86b6f832aaf4ef3e1c9e524ec749d1966b8bb31b28cf0510951e02658411a9c97d0d15d13f5187d6c5e89573d6d053fb4e8e6413fc712bfe96226242bb102fd8d1c98d5dd049150e3e4efa382af49e90a63c7ce73571db1cce518f8535cd7bc1cda2de04f7807307c7d3141b9ba68a925ed61326eb04c13344117c1131e0625b47e9c4a5179d7503f45cd55a1824c3cb1f5327ec810eba6b986b9f1b06db0b66491e23a1fae69be4225fc3af15b29f2aa1fda0ae56eaa961a8645a3f6671a046738419bc9f698bf37583e732e8c3e4fd18659a70e109a7bcc2766124c60db3e7422ae0a6af5527c65686ba90bcd8488f349b89c8bcb814ddb4960e549a02f50503b3eb02bd99c9121088635b6e3eba7746664812bb64a55caf4877c731c025f6cf4cad0bc6480b3bb7262386f20199f8a627a10e5bf0bbfbeac2cdcc2d188960cc2629507799c1a26835305231daa10b8611ed0f1259641f635c6cba75d2b36ea87a9c0e0e34f9254ae5ad5f4db6af7b72cb01ca17c6b7f79444c5cc4b31d1a7616e66bb0cee39d99811917c9201e353b538adbf426c6c1bed68c66a6f797483feee617557a3afd24b29fc1514759703106b4b8327823e7c7e6b10ca8b0c6a2e0090781e16763efdddcec44c2a32846e5368c261eb72a866abb4d08d3318c7e8d8a272c89473879b226315396f944461d66f8291ee18c9464a309fd131d9561e1129b615eca6b963c7493211bea523b9fe6663c9f9b2ee3886e1b0b6a4541069edefe46a514a23c49a1f3a22e9eddcc0aeb82b6924a7d6b2f1af2009f3f01538f3a959ed435bb3cb8dba6f0338f076ca6027b4482705550c649191aa14eedba81a1346ddf070aff53fdf1da55b3b8cd4a6d19ea3ded11b16fb2353c5afb0639b3db84d2cfbffb940570097ac41a3651b99955190333b891fea91e499748ce9a48519645c6449113521b500164092a5f78e2f0bcacdc5d6bd6e7c60047ee8973c41dc7a6ce531f3f1cbdf6e09dae5e0918f9c50e123bb46ff454820679d90ed4ce4f46f147b1621c39792ff1f0d7c3b82b5017a1029f39b31e89ed694dfcc7967cedda7e36800b4a506157e712ea873da9dc425630ddafc764cd377f042256307e8447df91a331191f169701e1f0b961a7cf3dc0e782d91f4718b3dcd847a8d8e096bf3d9a5447a5decec71a3c01eac6a9cf74486c2dcd9152ea8449fddd5520eee46103cac03e721f4ef1cb580648baf574e6470355741f88e798584d7aaf8e4b5f8b5b0298b1c3345bff0bbe2e6d1f220e4c5d0c40c5120aef92ac9295c990a92a59b4a5a0603ad165891d5583f07067c79d06c2565032bf6a4a598ef98ab6ba01dc13be0236fb71e720f43007d54c2c7353e5a6c38242206f286e5122476ba9a39b6ee5858903836493dc63cefb174ada9d92a4dde5c66c39e5ef88742c5412a4841f7ecc1e8612761df721179ef0475391b51027bd0c7e6b996d90e1e758d40616d2dfa0ec51f63a8b1b0f44a97b1713b715e5ba661b8352f42f27668eec5358086b28bef919c560262cc311179f09b3648943f5e98cc3614a66f71449d68184f21c234c2a94c8cc582d3c19cde8d5feff0266ff64811d9729c046f06527b8f932250572498ad1596d21975f2537bb62cb94ae1a68b92d9d3a2eeb6bdecb80a39d05159a2a5fc82cb18a21f68c1fd836539aa6e27648c9223c11104c785281ba66ad695e0011fa2fc6369413ff9581cbbf56909a3435b26c49b7586967a076a863634cfebb1259c122585895a74a78bfcaf0109d4a24f28d3e2401f50b947be82fca32f6141e1ea411e68ffa1a0e0fbe84eb683f2a046b5525243a2e572503ee6accded1cb02b5ded2234bbd465d9088f5897c94ca9123958ec5321cb472bac6f51407bedc106dd510365fa0cca4d165bd2ca7201f356a80cab04e80d25efed0c3eac801582d43c76fddd4b7700818bec44bd10661e1ebb6611d66a38ddd09d421e047a50ab702e1172d6474c18dd874475357c877422924ae12ff1058208dc4cd026869452732e517a63563a875fd6557fe6640d0fa572b9bf92482dc07e0d02f0185b1bfa174decc0b42efdc4fee8d6fbaf8f5e087a918176c1d74118d195e5729c20bf6dae6dfa53a73f348acfed797b3b57e760c4efdb8b9cf345ff29fd5a9df0b37178c91b2aa0e8aba3c84e015c92ebe5949e170d65978110ce5328c9421183248160a7212a6667c04fbc5f4cee99c081f8e6176b9e211f328334d5620de2913adbea6b5278639b53ba6a065b95b464e8dbef9da5642ed9be61b313faebc22085099bf7c5fc3ce04f221f3e2912a0230a603a238403124a20c45f7a1e26218e37e3954d54871afe706489087006e437eeb1ffd7c93dcfafedb89517c3ea07c48746c76deacdf8d68ecfb81b0b470367b0dab193d2c8bdfae15c0409f46cb06919a892e47481c6481d371f7d502e8e1cdea0fa61add56f0a77c7163a26be92b14113345b3830760dff17d16096d38fb1d221aa3e6f88e169b2d8d977d38b0c183fa42c6291d33da2dcde1aa43b4b8bf0204a0c2b15989d7bd8ecc00ac0eec39d9d01eba4c967a34d862a5cb690ff968ab4854aad47cc1e9c0edbcdc7762922f5e361af50ef12be9ee637b55e0a5197e0b7b40405226ff11561cb21b29af7b81200e6ec72567a8de5024350e85afde85912ea225cc413ba8c20e1a8d96b457c88cc579a4d60b9a09acf35fde77297542fd7b478d481eec97c217dfe5a992e53261cd4973e6c8e8f35ada0f8412a85e6142cfc76114ce97c889c7c622180edbe73b0208c0e4b768b12f5cb975de4a1d5add8f4e3c4bd1af9ed995b50336f88857605d9aafec5c9410c76b6a03108c8018ba160a12256b86dd3d58b8a0d8c87e05edcaa54aeeceaf5502bee74dc8d5bea1508c188985ad00688b9b877f18d42b9564b5470ab21ac545ad474cfd25c66f6e4e1456e38cd6397990ae630794e61fae9e882372b92c460a84430984d30c8b6d7834c339a6ae0b6595e8fc38059ea5168d0ead0042a0088091b49166e3710e31b77f8b17278c4bd389f88fcb06c9687c5174d0fcd1422bbe7a21780910684c4e24f3e2232bde520ae45f8e025fa4e1ead817ad1d5986121c2988857ba6d63612b7d96f6e1af6aea63b96c58cfc97742945bfd1a4257e53f71bfc6bdfc071e6fe3544fbf05e4e577189ffa62eab1b27aae46d1823bb9888d12a64b3322d3e2561c9e599082bc09552f51ef6079560fa4a35d39e2cdc696bf22ebf0df170b8f96d7709ad2f2228b0625230029f391f1e14bea7835c0847c886a5dca3ed458581882dad2d556838f2aeec99c8fc68c389ada6c1ea4b4a2a4f424b8f36949441d5eece13d3f5fae899af88959c7216a002763d56b50ab35af39faa1181d9c9e227a12317eb00b8752274a99396aad66348f5a44263f04642f195ebd90edb0311659a424b70a619913793d8c32c895c3a243c474e1f5d7f25c4a72702531f588641e2287f4c7910e8e3cd235f012693509afa0044708c97559f683df5526bcbf625a484be08c64daac443fb330e3b41d98cb1de01b20aa5750862e897e7e1011cca75fa8856616d58452c60c050574115826c528b9a565f252e7d01de1683aa06ecd40c956a5f95da87cd1cd420dcf74736a880b96f34289457e02f90eb4be442b2dbef01239eae5272a5388c502e871b830a3c813da719e1a64ff3708b9a9b5544c1d76b83cbdebf67607ec588b873479910f11cb239c62f14c44f711be6c4417a4d6194905aa58ed5a0eda29ca3e1c1ce04d581b99d58962752f74f9d9f97f34c2a677ffee8aac0501c1387b3dc22e1c7089201563e41eed19c6ea052ba02d44c30449b1ef9925638997d2b386118c4faf19d1b8d14076efbff7ce4b56143fbb8fd815ae6c16152c632dabed8195eec26e3fb3f6745c120326f7358740d16ce1d142cc915a2ebf8880c8780a960cdb940a82b2e03559876e3c69c81910e035ff854f2c592aab2a5892a546e9de4cedc5e41828896cb2b4935ebe89cc4e9fbabfd18b1a89f83662e6f1d1f3f641884bbc8508cdf45cbf1b0cf0c2d8557f0858a4613e82431f25eb122bf275bfb6c5942b1f059f9dc7ae100f3347de7c0c8f14dc46575cf16285f5fd9f489e4f950d85c3dc98205e64ab7461057a6629ad864a6e73eda74304f2e038174b666f81c0a033ad7460cb732313abe67cd6c12610623873a8954736ccd8cdfe29befa0f97330c1ff14280ddf57942c764decba34c2c5ff5227e165a9f4e1b2afd7b0e01a8bfc5cb6462c981ac4103b40e143c10384e090d4543060a2fa44778a9cf3a5088f6f70e80513648b81b30c729ae8fdce65307495bfd554af9ed73c2d066cb73c8698e21fd7c79ca29e9a419939188f4963fd24ca61f2f3daa1564804991ea9ce0f0e8296e689c1af4b629d662b156cd4e966a8a95f47c14967aae2ebe4f816f2a029f53739a9403e7fe1fa52418b6d388615f1ccd87244d9db24b8721f114a4662b22fea582545c223f2385a4aa34d68dc1e8562fb11ab59c8d3776ca780e2eeb96b58cf901bf898a16e750515763d56dd81da23f6494e919ce945895a919056f75171d7b888a789247f87af559e526b8bfc974130a4706d5e02e7204b79017c9689d96064cd10a497c1a9aef915036335e0c6dac5e841dc0290e76600ffdc2ec9eaf91814c570aaec6ec8eda4ee074d77e10c8a7ce7e8e6de521d20e1f9a0b3f4c0f83d98a08fe26b0f027d91503b19ccd8fc2bb0fe71109170c44ff2bc562833082ece3b0532cb8d001999d62d90a4b8f7974644bde432f93be2712b4f2022273c452d2d9245db329a3fe064c3255309d1e90f3dd6e59893f2dcaec37eb22a24d2e8acc8543cee929d0d24408f353887a6527b2e2fdd28f1d3dfe21288cc115ab81ec8a5d7ea9547e599ede01d3b6354aa9884e857481c1d8183a9aca8a205a6b1b4fa38f195f7d2f083ba78dc9bf1af8c63a00961a9055518f73dda79e4bc6f1f3d83cb10877e679121cd1b45cd6f363206b34fe4b48a0c16cc228a93476ba91fede887bd007be9135f944008075c4b91e257c77c623e4f61793608dbcdc3a02ce26c1ebbeddc8cae23e396294720426cc11394639562848d201def80c7cb0d81ea391fe102bc38d481cdb3f059d4a31750aa3934cc87e041d00c3d76f03197e6901b77b27712c90774fa207f9f64648aa48761567d07fc1b7baca474df19fbf52026ce1c28406b2d925f90fb4f658f4bb8cedb593e8ed236d24e2436589490bdc160cd8f360466fa2930311ea37be2c7cf4f78bd3e119b828a839cbef9bcaf9bce43b44c22fb148e551643e085b10675a60d8036aaf16fae6c9e40e8b852d5032f6196cc3ee3b1ce39d3fc28ab50904af519a36e81e293cdcc9fa25b8299d080f6c7116cae3caee977947def65801405376a645c149e58a0e416d1ca65c2ccca660a2d080d5a92c162e73ac0e7039a68b97d4db1ffda1d6da92906b98163ba30dec52351b05a165246f40d106b453eacdbacc7e445905e511dda185b9a26a33545e7c9115a90700849cd2c146d0897f7a382e3ec167ab0429a6991b093ec193c025fe0491f7ce6ca6fe2d859519192475cf38d8c8c7a5f6670b263932c9af2ecb67276e7a5218c633068f20de1275bd371bf31e3da12fded34080f3cf4bde96058268dd44338e80ebda5406546bab25b60f998a3253feaa400f48b94462884f872417c02ee7b285f9cc443a01e2ec867b577f536c2bf477857357f5a4bf0eec721f91c2293be9244dfad69ebac1f01899c01b802757b24dcafe08dd8d8e0ec7276f09d3d618916dea9ab09341d17ecb9cfc9ffa784dd1c9691d5edf04a66776b314a30850ab29911f7ae9021eb249aef8028b543b6f779967e4c895465bfeb8a94825af2848821a307e451f9726734ec22ce2731cfffb0c9a131f2428903c099a4ee6f46b85f96ca4619c678b4039467bb15a709bad946aec0bf51a813708357d5302cf8805276ac9d6cc419bffb2d8e910da1bc6edb18bdc20efb08cef0008dbb263e494221c503d2406502e94c0ec1813e11469abea57b49b9741258e79ab0e6883bc98b4612d34a296715c096592379c4d157edd06adc808acd8301d9b484990f362d96ea72b71723b2a27d2205f2269580b9dac410be54319e50de222b99b341abba1040b539762fc946852e7161b05b6869394ca3d3db788504de430c100d798e8ea7312eb16866c2c542401ee0c2d69892de3669ba28eb89f1c2893bc56553e2b4d1b2700c1557563e40283f59c27d3f2e86808825f13505e56b4c878009ed9426662342c2dca556074d35d5c50bb25a029c72e1433845b0477355425a058da2efb9652b9c4217f108a180df9573632488d0c9c200032de280e292015e91e28ddfa69a06a71e33a962c40d12e12306ba3edf4368796da8f3c9891fd56411e3592c5776000e521428314d48dc90635a3328b8916a51b939607081aea84231aebab2ec01217b15cad6426a3810b69f904127a802a93eb2402f10d838e0e0298332bda64c2cc0241966614a9f441424112bcb258bb4666d91443355c97414f4f2550ac62a0697d218108cf2b298ad96a6886928a6fe0c65cc806f6d4993bd42c8bcaf013cb8c3dde437abd48567f452a86f51c94ee1976315e4d359a4e5b6e1cef75af9c83d18f3aa639d0b227700973f24d08e7831df9b2db0b80b741137d4e9eea498f6ff980a9cc40a026c8ca31faf4e9031f9d796f79acc478a5ea44fce144e8b407cbc4cd51003c35be86088307758e325baa8bd5010a5cd2640f5ff92f03c5f0741bf5d4ea6f058e322f57114d6c518a82d0af0bce6c08747677e211fe7c149e52c19218872553870fc563a1ff2a020f769cdd4e2624807668bc738192a80f0436cafc442da1a5068a8d83803ef1b594227713c3f990b1429e088f2220117c74a1fa1d0a70b35ae4ea234ee8915cb8cdce94377d36a63d3ab83324b4b225d5c5c376ac9bb8a9f1893703b3f62298dc9bfd16c68a1f610f7b4e562f8415fd416187f665de670bf9075981c62a11f3afd9d554b0b3a76dc4cf66b33d7a6d5a178f5ed88cfc1ba4d46045dddb36d979c248919935a60264d229334a1091ef264534b932d55770189a282888709d051f0854009472800e6f218905b4cd13e5e8ffa1dddaedb3124b862085356f1359e033ea4281e9dac14363a8a65901f85203ed810b472a7fcd721428df6b9416d046fa0058a3aeefa3a79586edf4813d3b4fe7a3b012789f250f8198dd207d481748a8b9f287815da07755e05fa2f0e1761c31a08640ec8f471f0e3d2b414b8eb9b11ec3a16c14ec2de9823da9e2891425a48f270676b3b76efae065ef8efceca66c91b94640c74f4c82605fa436f59b20abfbaae22aa6eab97c509900be46c0fb8019798de7b13ef95653f14224288401d727679849dc21610b7be132dc2766f3fd1d01645ef692c5a856cdad9059e7846c25b4cb2e7a4c4477ab939106cad99ae8954543487323cb174ded8c872663f49af1384a259134486dfdbbcb596ee75bc5817e63225c743211d90c3c14ff719623f48431a2e061c0370159d83eea6a4cfd3881d1ac737769b0e94eefd92d7ec7ca0018cf38a050e759e5687bb4a32629c69310d2ca91f23fb98a359d5ab6b2c32e3357b56d8e58b984f9b910e1b713698a1e723291d9b81cb568a2dc4dd7646c1cbb8a8e113e952cecc89a3380a9a34cc0cf7f3fd8c0d5e69c3428dc22db887e0bc44fff97c5c72e9a10d9f9f5c407525d9ac21450c2767d3182bbc7a69b66666eb4010f38075576922effba2be6beade0c0c9bac33c3c1779e37745b55c2cf6d073a10f4747dc6200f33160add97b22056aba6e896681421c6ac5d2b3e68f40b26faf5845d83446052653872419b8650578f6cd37e3a3d7512b481b85755e6fa4c5128fa1cfdc1bd919b6e0f192701a0f405bcec3f8805ff25a44b80747b00f4d5edc380e5e8bc7d0d66341e364394276f3f4ab5ac98eaf5266ea7d27d31b78104e7c262ab62843dee47de487b4d0d5f48621f9af6e1acf02a0b2715d13f396484410a2c42e5c20841cfd7481547ba638ee91c5537e24680e5d328744bc9efc21fb9eeba46b397d66674a3ee5ab126c4f0f0175f28d898d9c2656534a34533755a495c8c745a2b68c74a9c450d39b00418517727b0583bd1efb8c876375ff3a244389db1adbcc56c7b0793ee977b1b11dfd11eec919702c1b9b8507a0140550fa14fc6f1bbfa338048148d50abf8da876a78a1215d6318808111fa23e3b5e84063fc7912c08d6d1bb42b98296cad1bfd584aeb915b4af7cc7ab8d852e29fdcd33fdd325a141936a50568e1a339113505f5789f02010155036354d46c03932c4ef0d396876fff4b3f59d26fb1e9a2e2e10b43596573c0ea0945b4226f9ba94cee16264f974562827b3a1131b9da4cd24e12e50aa7f76484fe1cf072183b4c703e89c20acbaaa604fb56be1b0e33b3f68c72db79a62359246150613de70fb981c2341499688037b7c02cb3b1d1ab7ccfccfb3744807ee4f55e8e6a353ac7480a15447e22ace49183a4e348bc6936039dc1c3b1d94b8c3fc3f4d419cad36a44d5e39c61d832dd6cc8cc02ca10b6c5e3468c9c80b5a5bd6a503129db41be90e3d225e2c2c82ede842e7752de66cc5235db46b319eaaf676069da615288ea310e2a0711dd3ba69a621233969a9128ab0d1d8fc64935879c246f8403ca735fb2c2444348f9742146809574a23d0002f3401c5b438519803c43888bcd17e530ee1d254a5ad83391da74da38da0b0da4803213262a00dca51c7ce761471a3dd94e07414a240239143738c58d0c065d53a4e1b4be38a03731079a56d229d8ee51c893b7c3a947310e7f0ad2d854d0e6e0e54871938cecd51768f564a2aa5a38885062a74394cc4454b3050529c58d09293444b7350db54f9890f060d06edc5100e1a2c64a48dd628af1d94e790fd1e8c158964d2a04c46d2b4d392eb80ea9899233b1c2409342124494aa3415339b52d1e6862f3d0f5dc3f7ca01cea0154cbd18d2104d03b8a5c681f2addd2d060912374f8688ce67869900c0a76dc08abb9e422b39a0ad1e5ec815834c5f85e5c7bb2ab236b52769af6de422282f66f213b42fb271919a1bd43ca8ea4d55b4e13a1c1b330f6082d9d646484f616511dfe1c0b1d886bef32d2e1e358ec20eef01f4759459a930b498a66cd0afa399abde875e039e8e5f8c341c111443a8ef5d394d29683d39152232861e0203293f65d310d98061d32124cebcb02e128eba2dd48bab525c2b6a3cb818243060e727394d5a15d722141d14e92cae52082a2e1b0a46f4033211969a00d9643876c872b8ed2eb28424c73212cda51d687765532c2466b945738b871a46b4209193988c8a0ed27c11def0e9d8e25ea20f9d31649db1c3870a4e198e970c9b19b83680e1f8772a4878ac211e31a950da5e875e0e6a8ebe0eb082547facc799e22111a2d4616708871f09b636487562e280eb6accdf6d0fbee4a644854b2dbb2019f9b62d4b4bb11feda9b0aa007a4855c33ce818b6ad9e8e0702992a63d95c3ef289ba6fd808c1cd086c58c1d2432683c64901cae0ede731842489641b90834ef02ba2fae705c40522a6e0ef2645e0237b4aa2ec9a50ac7f6ba9aa23d415ce9ae162bb9ae4cb4e2ad2e3d91d394256b712ecb8f26989eb92f1177f627747784cd64a61c2d581d1e85b2711ef9a322141ec2b661ff0dadad6d0b2d2d2d6d22a59449cad8060007fa064e9cfca895d47b4e99db8215fb5557a532852944650df6796dd08cf068645c1b361757e337578df97e6a4b7bd77ca508886f1f35328cfcf8fd7ca5e82b6bcd74b99bcee66a94ab51add3a8af747d3ba86a9832caf7f349a1f73565ca4b837617e4d1d2f086905ceb6d8ddcf41ae69047f0078cb990275372ba540f99e4f1d6bc095e3f5dab9963dbead668e24752cfb6d557c76e8de37b7bc59d784cbf9e9a3caa5f4f55bfde83d2ee2aa6ac5f1eb667ecac4dad3d5dbfa65a35edf6bdf7deee2ce391ba23b0f1d76bb79db4bfb147f65299f552f68ae174b95e0a9f288039761c34eca56e8fc7bc949d33d3b66de3346dab2e60628e1cb545db2e6732712f60f8769a76ef5679b45fee5a6b6fdb3b5da8cfc96d5e7b0d398c7639779e0b98e3ce7b0113b3da525b6c6ac7fcebd53a677b669ff55232d02995c7756f66db66486d74caf657dc6a9812bb2ef366346f66def65d71fb2b8e2278dba2edac028e03a73883a74b7bf516640cdb313f73cd371f2d8fcfd47028a594b6cf777b7777d7eca6d66a31d7b78f6dedfdb9d893010bfd2667ae75980701ec3be7b28fc76398879d67dfa53ae7bcc6b8d57c8b23e6792b96e0f531dcd6374735273850c70d2299524a29a52c6060eda93ddddd7555575547adb5c9a7d36f595a6baf0effe15ecaa22c1d99ff90692ad5d5b3af29db9aa9f12d3a3ec246f9dda41a102c29a56099044ac12f26aa419f46b5ab842b1727071365a7b5a20d55e0d330d748835fb467f292e3b0e22ade39d839e802cb11b3a525c572c444b5937698a8b1c787423e4fb7149eae78a240c955fc8592abf87515b1861b5cf10515378935acb88ad760f292f768008b0e7f41de9ce434e49cb3e0b090356625e463ed1ce4a49054435ad518156ff1fb8698a89e13e682a49a3183a56825cc660b75537873106a183ab2b8bec7fc235886809a3c520dcb11cff262e979c5705b147fbd434c9839514d55c26cb6602f85f83a16da1cf2680309df9e75f65ab7518e9a2b82b3d6cfa897f60064559ef9f209ae4020600d78d3a8de58305bda55c20d043261b61b26aa6f1a6653358a055b0fdb0a361f9b0a361eb61db69a2943f2f64d879a1a73555b0e1b0e1bcd76c394c9bc7d4bc166c3a69a32b7823536e3f6826fdf56bd8092b77359c5411507854412f651280a47607a2c1651112f93d9c2d52b26aa994c5497ee93ef4b846fbf3d9d428ba4f0f24c97f651789b841708378bf03a09af16d32508d3650beb8577a70bef92f062115e26d3e5be6e15d78a7b05c63ecc5768fc2ae16532ef16b3a53171de2d66e6713aafea65c31a7f5f3566dbee76b7bb61988f9baa272622b9aa4df5791879537d530759a3e67cc0b5cfea6d3265586b5af59ed974711ae4d1be48bfc7534f4783c6992ae694a8dbdb8a61d85e0a9439e63878270a648ef90b19b689596da95e2aa7280f2a52b1c11c37f5b990bd908939c41cb5058f9997a23e1d9b9897e226e6cd8cbebb5f5e344ff41e32f552a1f655a4d9367bfbac5b115f51063a651b45f0934e7914d81eb7b12e384fdd4c7dce6c4e5badf552b7f2a8d941c7e17b21bf2066b52595c29e6d6fc74629a59452da02c9d64cefeeeec66ac5a9383fbdd65a2b7ecec7c75aae6ffae65eeca52cc618f35215c3c4cc4bb50ddd076d1b31ce9c13b1678f3107761e8fcec8cfd478eb23ea5133ccd4f8cc676abc155335700d4c1c2dcf74b9e268797e3ad71805b607d71d0da7a2dfd6a9e85d8d9f408db95996b3c954bdfa785557cc3424f4aa5e5a8bf6ab51d4cbe0b853662a8531b3a59d143260b6b4672f8dea03581be2e781bf3a0316a08029037abb0a53267b7b02a60cc8db65a64cc8db11306544de9e63ca8cbc3d06260514a64ce7ed07304001a64c8d99407d993219a9493e4e271640f207c322352f456a7234a09441251f692e0a47905ff3503802d36b62919298d516d04c813a47ef7d12e9a94a0a13c077240a714f5df2073e752f57fa1d758c654fb1fec7f762ecab5e0a49e1acd4459d79c1b980b9f002163331ab2d531e3ced98dfe20d224fb9ec6ba17a837386d4f6d553db3707d2293fbdd4563b4441f7a8dd411aca13ba83e93412499e7ad7c4902682d82672c872cc78ea4be09c071ab85c3f5d2e9c1fe94fd71157901f33d7caa5fad166f7a7eb89253c75fbc4cf53a75038d7c19de0f9267c389193e11cd9734ee4fc7439a15304d7766c0914635c848b7b6fc6451318639c598c31c64584b84d62d0408e008ad284de20c306d6af16b401428821248822a06471c3141a701bd0a10a1510171141a879e949c20b6a504345cd0f46f80c4b041353fc40085608114607821802902ec81f2e40c2c41901c06614a1f75ef0e7de8b8d716b3b3e42b901c98e709778d2e5f800944491cb82174c8cd0c2c6860a1b9d27c8b8019227acf8420739433eea6384626170f8032c6badb59696657e28232da38c9462caba432eebb22eeb7ab6b451385b6ab6cc6c89d912dbf216a02b404743030c31c0916f3f801114ba03a0d0d170bc49be3d8556a3528081a94d61aebf1a8694f50be56e327536a17e6cd626de57086ce8c57d75dd684483060e1c3f5ed60927cc15fafb7ed598bbd32ded4e784a293fde5ff1bebcc834d33eef71064a6e36d733ab1da5e258c30f47f02027c35961e6e8bc2866c4535fe185cc51e2380105155844324ef274d46e721a384e404185b2d6601641da212dbaba42c6979f38983d380897143e7e1e79ea199ba2a790c153cfd9142fc0703897a2025ae42fb28e941f51404822a3a05344876c4521240a203a3701e8c10a32ac580110543d4c307a7e28814209428070850b4465165cec409902c6cea8c80f3a7c411e2903079b2f7274231a5420c99c1024a200c2915072921c854d143ea0900245942372567203051250348142e74754879366a9192ec8a7a2a935fd42e1fba77ffaa77f502ff5488dd95139a979b28272f3ed49698e66f35c6e449dd5965ee5b17093d22deddb4f1279bce526a56c98280d63c54fa5c39d7654bf9eaab5a646a3ee66ad745a3aeb74c16ac56ece59a6994cb525c3f5f2b8b5e67cbda6e657af298f47fbccf4eaa959316f26d3be2b9e7868356c43608c611c87711837a2b05fce8f28fd0e32278e9877d238d738cf44cc396ef36ece397f28a5943686797777d77bf3b5561f1c6b2f86f9bd78b4359ce84970a127f179e26986f73ecffe13c71940ffbc7a3c40ccf3987f18e789fdf39863238f6195e7b11a5e1f436d7d756bc3caad3a26a3611299f8014cfef89442e3c326ab41e6919ff3060f816fb091fa4385c16c69c7434c98fa63a2daebaa513f541ad419d420154895418d41bda930c0373506b7ea0baa0baa4dfd517da82da8ab29136ae19b3aa412f9f6caea0594401924dd1f89b096611bf7795d09b75a1f0265f0492311d6326ce33eaf0b8132481a89701602757f0643fa9fd7719b966125fcaf52c2174aa641d9e4a82bb28ab7c78dc823aa31b91521e7ae620a984cb6ebea68948d46378864db299bb213a0c1c1625d073aa97936d5eda572d6a646bbda8b31edbb58142d121c4e92694da3ac8f4679923cd69ab797a751d8c77e959452ca0256fa90ddddd6db87acb5fa906fadc559fdbdd7d3512b1e274f751ee4acab271ed95f9fe131d7c471061d378864ead34bd16ec6c5faf4aa679838b63843faeb35a5bfa2784348ae354fbd318bc3ce81ae441eed578c6bed6a9dad610d6723af3ade71afadf69a4cb5653ac65ec5f8e2eb94729857bd56eca5ae7787311cd229d739b0052dfb9a6954fbaccfb712c05a0ddf3c27eb3088378864dad5fae528c6a173cea9d771188661d8e9facd1c7b86c2fc524a296dc173b9babbd65aaba5d6da8b4371eec5b4a60663e3e4e1c1c9d6b1c538736d86c7beb9b6913ef31912e9499f8938fcc130adbb21b72cfdec397be6e9bdd331f304d2b3dc1092fdc55187754bbbda8bb14cdbb8cefbc00c0a8946a4928a698565d471c5d374a14e63bad01a26d8982ed4dbaf4e7677b213d31354ce8f277c19a57f2e6945be356e890537db93e4f912454b08f50c045100f9e98a424814514011048a1b286aa050b59252894f55c2a3e46595904a70ae12234a80c04a6e1e6b219c13a1d3d211e26ba84e91a7de2f1a9ac862b8b2c83f5d4a6af0a60521ff2087919ff8b9614736b94b8a1f5140c05ac9074aac56d05282111d6e78f8026767065d1cc1a601180f301b52b8d94f8e1c11e0a1073ea4944f70d1a447ab872a8cc0c5164474e881080e53e5f091c34796a31bd1a8024fc93b19fbe97a624890279e48c1530f38a1435f723891836198cdd18d68389104a7dc8f19272391ade684cf8f2820bc7263d70f4ad70f543e860d2532f604851e644cc7081927b141451eb30d2c728dc95896655996614e46ce26a73f5d4ef8f8d34f9713367022c88fa8ab83cfc067e0331001d682b33e27a5606759db99a26ee7ec212b4cb197bd35b364b83a4b86e7aaf15908f25c350614f25ca0d2824aebee23a8d44a2d4aa36cf33c75dc3b184fef3cf58c76d75a33abdd0d73f66e98c3baccc3baccd3be0dcc36e7da346bdf0672b90359ea5cee405ee8138dec68549b8ebad090234f5d64242779eaa4d6db9f28527ea2344dab4a578e8e2b67e745d21b6a4383d01b6a438750165df593ea339f442935904385a7d75af12545bbabbdb736bd5dedc517e3da1477f662acc358a6659a569b6a58a6691bd775b569c7751bd775de0782b52938f381331914028542b56948c54b894c2b5e8ac4722a9d4eb5e9a9037933d443dd0c455e8afeec44de0cfd19924aa452a9362d65df898776c595ec3bcdf0d6338c69fd4618424fa75bda3d97e76a54ebb8406548d530e305955140a0db793aacadb5ba19b9d62ca5f265a33a8ac7237c2b6a3fa50ee4b940656dc1c4124b2bcbae9c2e6dc99c8ee91d9a4a23454a942850a07435abbe215b38a19e504fa827d413ea09f5b068666838c8b6a0657cbb46c94de7dbb366c6b7831a0ebe9d665b90ae174f8f0f49922449ae56abd5cad4d5e856b215a6040ea395647495adbefdc394f8f6150c87419faee80d65b57048922449f24f346ad430c1041b363cb461589b86a6c742be7728367bb0107c77a8fb64659e3f2695a344658d79c94651292a4751292abf47a3d168341a8da85371a4f238f8588b80c797b7e2c561c4aa312f9545b65c382f72f2b8be9db3d1955e658d5851893e32f57145f5bdf2ba00111de4255a6e030ec03852861050e429f219998a335e39cff2d3f5d2e2b5635bb4a7ebaa3669cb4b59a20f799c2fd107d18731908004243c37f6e8a9ab264bca9aaf90860781518d134b7f23094c605931d56f2c810d934a8964bfd1044e1a8942a0fb8d3584a00c7e5e87bff10422f68d2878ee86987dd4ad0f68d053b3d4156bbae20d21d98a5374524f9a37f509b094a48a53af3d53a6e494f499c857e73aef03332894f978b5b03d4403872722627c755aca62d0f07c04e57cf55601e59c3e1f433fbe7a35fd6001ddae8484b092fde42ce1751ae128426202f9c8b1b01a9e7d8d6b43e9698423c75279eaa2ed355d469b6aba3894cd49ce124e20a4ace9427d25a4e574a16e0a9b0c6b29ac3da3d7b855f1d543218802f1148cb195186b0bc6cac553e7bada96865d063a450b39d66bca681ac7faea1c8b623b3ad10d0a8e8a9e1249e3b2bc36542e7aa60ba5710a593a5cc15d87269567e93a2c3d9216af6ab3816aca785ec5e95e8d9fceb5603bb15e1bbed674a9b5822a2ff2381abda68ce735f3c290e6234bc9c29a2e95c47a9f3cb294acaf6e833565ae872c2315e9bb703e076e2ce5b300f1d547af4d355a24a4ab9a2e36386a6bb88963b4346cabe9a9a759baae34d9d654f655f9a6acd69429fd0d21d98e747a8bf4d3b696781581820a3347c7b628d67aea2b948de3041454a83d2f1c8cda4d250d1caf9b17cdaad6e04893768810e5156284b27010af9b97cdeb45f34a0115673c6582f32ebae8a191a9f0e247540cf6d9f4b39f5131a62dd5c3246e0b63e7aab574ce7befbdd3f68b55826402094f7664934fb133451354ec8c2a067d9d3d35ad8289aa5e6d98305dd3a8eab471a4b4a21881c27a526bfc05726faecff5b93ed7e7fa5c9f86b9ab4655980baade2a9830d88f89aa5eb155a3aa57eca6b654cfb29c4d26c76ebe8e4d86e00583dc55c38031f894782cefa6f3a9822564e4fa843a0495d88fd0106cd5302120b01b8cb4c170b016c6baa28781afe8d1ce078817e46f1ac68bc1eca0d8ac3c1f1e0d567552ba283e350614a55baa83a49434efe36b0dd97cada1205f7dc4abaf232682dc8a6c43f066ba549faeede62b1699faf55235deb6a60bad235697c38dfd7dbb6ba8835cbd7a33a8a7f6b648c599ed7fdf9e0e19aeb70c2c2a95eaa748037512fe10d869edd7e28987edc05317fa07d7026ba145596aa1850c740a0e4e9c3869e7bada7aadc5f75e8c31c63c79ece6294b92e4c994056d663709f77a8d9ac5128548105dadf29c3de0f7d39d49bf1ac61b6778bbd364e2a0b7a3f1d4de98515bdc19ebc62e8f4cd85e7b2faeb35e4e4775ec45a0c75faee3e8b598184edb2ded214d1e47188b79bb945e4f47d5aa9d3276ba509f3fadd665de0c27ea6cc3d846cdec8722e4313f3182270911458cd16a653332933cc50b68b49fae2978b0c4cd76d0830d4d58af2692d0c14a0a1e2834410871fd644ba8347a0425c0780ccbd18d6848e1f326fc48c1bd68b3baaca4a5ab3b7b4e3c7a7c082587507208254f2a9e785c289c8ab6adf6dadbd55e8c590cab4db19f288dead14af9c9c2f1d25c95bd1663d766561bad2b47c7a585a3dd79615866b5ed5a8ee39ce3c2d1de589b20f6860b473bc4b238aeb3de772de8f3048a4f947245969f6bcdb783687ca4688fd5e6db477545b224526d4aeac68a83c4b7374ea39afee65cedba70ac39df9bd79eb658e33c30342aad9cc211c8a23a73026548c7bcc4f491248dc44bce8bce4f6f168dd2311a90d2d334eda3a76dbaa4391d43339dbc6372f2507e663e9dda740c25491a84a443487a844cc2818ea119941cc52485e6a7d395061a06bb13df31e9e499334f701c28744cb36acbd65dd7755de763967dcc6fa2d5b63850063faf721a89cc2149921cad6c56abd54a547b425babd56aa954342a954a7542179aa081d943aebaaeebbaeee5e5f52f2f5cedd1c0cf1b6995139139642539920cad1a06bb57392d0375ad564dd26ab55a2d9700e008454f57abd56a45721cc7719c06401fb3cf9b3dddeca9b347ababccc1cfeb2a28d332994392a44682dcca66b55aad56f549eda93db5a7f60c40002da1a7244992a4a6699aa6ad56abd52a6b12f3ba0a6265a69139244992e4ca66b55aad560470013d2d4b5496655996596badb594f4c8ce49122bb17b5de54ef80f67762373489224c9978e009dbf32abb2582c3104f6c71b4b8eb0ddda61851d988d1fe4d1b40406347687c95161fcf89279413f278d2da99914965be21ef174090604902cd396c0e0290ba836060e1c3539d4971c2f4b5435dd8d301b3e59dce9e2061c3c582d4737a2b1a4871f7190988725354fb39fae25aa9d72c7e7a9dde9e26f18e286bac28e9d2076d8f161b5e3230793c9513b48ec60b1a3f3e30ba662a2c3c4c94f9186a63e0130bb2e634f87b5b6da1a765d96cdd634aaddef0a40076d14f574c840cf58c294a7c13a7d0f0224cdd381b9e6cd08c0c6b6d5cd7ed7376f46006f730d4d782c9ca8b79e8559b734c67076457bfa1b70b34f08f180826cd03318f2d38585cd8ff627162df8eca70b8b1d96b0b0a05962c6929fa74170e18f2c9ad45a6ba59d4558c3a44958c3e40a293f809fae2b74ac73190bbd8f6f08c9d745c0fa713a193de0c411ea90e9b3409fbeae5059515a31e4b916a86bf44355e4252fad38c947a6cf3bee0b3de742907a6785cd77ae62054d1551be7a6653858faf6e6b05c53106d0b1d742e7dfe7dce7a07f0e3a97dd0b47904f7fc08a7b5f987d7a12a0bfe29f7fceb965c1f3cebfcfb1c702e7e009e49d389e5274ca679f0f8ae30924ced0299fc5712cc1eb3b9f5f82d783fe8d20c7d9afb783fe8a7be20d21b91363b82dac78166158714f8441c53b5fa141610cb7357e2bce892a3edd14560f02d43b6f8f878a681b55124792381b3572dba8110b221644beb9281c51f01be8f5376761fcbcd3c073f5371646d03f91389ee037dfc2fe0df44fe4c4f13a0df5b7d136aa739086fa2131e437248ef437071d1447ee86a083f537bf34d0df5259a8433e41803ee8f441ef4418bcc5130fea9d6fcf5d81c49ff0d375c5ea61e05c730e9c9c6b8e01ea9aab8a27af3997c5b1c767d00d21f973d0f7859e77a13602faa05b8f473fe8d4830075d03571e41cf4f0736e7a29eaa56698bd769ec571850a768e7d0e869e8fb7f59eff27c2c0b9c7899acf2ccc8f4dc7e15882d755fd08e8536ffdbcd14385764d300cc330cc4443b3d75e7bad268a59b7439e9f3141cab21b8eb505f2b11ea1c106a71a64ac3663f561c3c74a6364c5555ec41b0e58a1e4a18fd9bbf852636e8815479cb5c5d5a886a951649909e298bf86389a6888a39fc4111586d5c35a71ea2b8a0953af98289c107fbd62c294bcbd32993059cc52c94f278ad1136dd4fd59db7e7d555a4b61fdfa2a85fdf5550ae95726738ba9c29c0518f3f7f40214a042c839fc9dbc540ab13f89932524f94a4822f98aaba890482a24d139daed704dd7744d97f60aeb4e58795642ccba21977cf370e4a2f69530f4e10d9eaf84209c95307f7843067d25043fbc218f936725fc3ebc21937cac2a9ebd8a38cef02aeebd29541167a34ae2d83f4fba3e0aabdfb18ef1b22ddb73579764bd86c4505b2422890f5f2b7e99d060123e30da6f0ba068505f1d85ec91e0abd2cf1eda44dbb66ddbb665079d037bc8f4b3afd0f9eb4421954ad698d9e226e6903ead478604b1f1684caefd23baa8a703243f67d9d489a329d7fae36aa9bce695ac64252b59c9b192df9d268237de833ce2b29c321a119fdd120443742bb9896364e9f9c61c3b46223f0b7d9041505b8f9dcb32bfe10cf6ccbfef8e3dfe9ac0c85f1f4d50e4afc86d610b8234aa37d6b753a7e276335db24bd62ccb7992a38d1f2f09eab297bd2ff5087ce674ace1f55916b36d87f742101921a045c03d9e0de62b7416e25093dfdcf24c19ea9b4f236ce45b263f5e721a81d4a4d42ac27b4a664b7b1564138f0b9f6f1f33d173b1208fdb0d88d4a4556a99f05404fdacf2187ad571bb09bdbeddbb4e93f9c87de4af387a363f7f7ddc6eb2d83f8daaadbf1d4b0f57440d3549ed61737b2061c88f5d17827005397284ab2ccb810347c8026b719ca00c1e96305132410636c48f246ed073b3844a0545f4102288223c41c511677c814397bca818c3e60a22a89081103d7e40224508248030c2135ed01d8061848f3194c0c2228732565edfded16a6fbd18b35e8a647d5a8c65dac5e5c5326dc3a21566ad889c9e86691bd7919ef5bcdad46b655a4729a5745a4a31d634158d8f6fef6a5436dffead1ad556d5a8f6164e1224705a39642b45fa8b613ea12750423e34d43343514265a887ae6e6c82dcac86b05622972847a4237289482bda11bd44a21f51149114d18fa8a451894a3b6977add6729ac53495cefbaed309173fbdf6d0215af073a365b4123fbdc929a3b51230ca2993dd00f1139b26242a4250f951ca218941223262e27d3b562d154d48be1dfba822846fbf55e5c777e66329e7dbed4612a392887c7be3976cc4e4dbb32c6793a95fad56abd56aa9542a954a9541a1906844c2308d892d8d47f29d85907f80399f181f91cb350b84240b21fff876dc9f181f91661969e1902e922449925cb55aad56ab452a4d152f451f9b56ecca4a6dba82e7db67e2d57447705e7040ec567a4cbebd7a35dfdedd11dfce79c101297f5ad5019cb7a8a42db606390c11d10c0010245316000028180e0984424112c3619a26d30314000b6d8044645e389709a3a12889811cc318c38c21c0100000010246866caa50009203f840a43a2e5418d123764ebb2ed6133b44e4069e1ecb4d2cbcba1a98c13213f2d0e1e0a4123c8e7cb7e613c036657988f5ee3b0d9168dc801884fca1092d6a72a36b5111cab34c8c28981c3045eadb07cde1631d62a5daaccca6b24ccd55816d8ecd38ca29e9dc089b303d23423224c1f244f561049e8874fbdddff2671c78b284b1b9ed0e6e568d40fa113d670a5720f76ebf4a6a741395806438a8576435e8324b4ae8c8669612035684eb75f86cb3d1b159a4240e2e219e5f5bccfe183c4cee0bdaa8354a817c93398b8aaf6d7da078a492d42150342a4e7203d425bc44d235ca20060220a00375d1aa854340bb8aa125345c7c75f5dd3af9a95edee625e085ac966e8c4d61f4ea826f1b11979b9e502a653999f704b1f0a76bf63b38e77f822c81ae263050674ac830240b4360281421ddfb736d0cb6c53a46936df812caff7b4f3da30bd5d4e628e062eecf20a5eac19555ac46d697b82d2d2890a2dcd0c85f5e3947016f587a0eb46b36583b3e5c86bb39efdf3f69c99d971e329b8c29f8d8feb77f31c2cdd3bccef42fb904333d69d94132af247da393586567246a869bce14bf4015dc72f367b32f6f29a5c54b4c847b9b8e14d38440ac6c87e0f70d3877930a260be9727e4397d7953912694d013a3e6f9f371426088b5f411200bdeb0287d130f6f0869952f5126bae5e2a9cd64b5eb0b041212ad6bf1a82e427e45acf2e019761d895363f4262980d7fbee1fb1e2d5554134d239244636187c04cbf583b858d67a1085e3df07b018f4308e8188c8672f88e8bab6f1c038cb3ad924efda414a328a2ae83ea17dae5441110953073375dd08f8235e1c0e8b59acab5957966c28b7a7c98316b8b4a0a66980db2f9f18f322c9f3d9beaa76c428fc38d5531da4fc6b051caca152b9bba39938c06f5eeeb74f09d20348cffe5e78b37201328f41f40330b7ce82f50cc223d32fb7f60dcc0b50595e034591822090afb4c660a7842c83ae618fa1234d32960ee50628c4bcdd0393672f43f013ec8461b248b579cb4cc3da8f055fea791d117f4421decb11d53227695da80cac8c10c67244e1046e87efdd48304a49020f3b2ae7ab51c45e5443dc177b0f1050490ef10404b182f77abd30b8c6d92302bbff33f76c703c073dd1350ed34c7e635ea9602fa2ea537c4b1f387aa087e38df2b1c9352f4971cf5b0e40504444cc7c1d112ffae17de1665b2a07ec1ed4452ca5f32067a11f5ee6689cf98ac43daec4fe1a4a0573e28dcd40bb8b6080a728e1ab5641d64bba34895ce6d9542c7e62f080df78d1c30f5bc30d9957785aa091d81b679569226e6464f5e5f6930625e35d8073a7381f72657c547d6d6957b8a86a02625ccad4184064c17e72d2a7d93a12839ac2507aae8948a12b674c6ae9af30a974d06a01a98e946e99eb349af08fd9a8092e0ab6edb11c695218f683ff547b9c542e21d8aae32116973aa83bbbd896ae671452a2380280e371a37fdd5359ed664d0535df89f16abd28ce5ed8ce594c54e706a942f10de3ae421a26828fa0a236a39dd0fdffaf0066bcc0d9cf787417c13be5e0afa90ddec76a33953f911b703af8b272ade478d53e9365d2eaa442fad3469c07cd159ac8273b77d66641fdd112c4edeb9402527ab8fb708925fb8746d6688ca5396a8a4615b711bdbb0617525a683a2dc26f4b28d2433b4d0206a9a1c991b16ba8461768f9946c58f8b64ddea956564347e2ee9146162049abe528e154587967fc5dd90d6563e810418b12cbcd7faf268897a2091f5311c30dd00bc5be818af3f1ad943940c47ed31ad5089a9b97f52b8bfd40359a89cfd91bb91b3a8e306341854762d412cc30bee39f1da03c39fc041a325149ebb20853af3ef839e8a04fb99edd3a44e91d1928ef3f80b5ec7aa1a273fa960349b52c1674a43c9ff43fc5e39ce2edc44347faa1b61845924a4c0d18890d2670294a10e1f8015a33bc7dd20acc446d6e8d989ce4c2c7794d2eeca2838001f8b1e87f8bba56e929be94db202df243f0faf076d54ec71c70ff7e2a8c7095a5e11d6d07fcbab06d562076828dc0fa236a4265f40c2ff5732438193cc2c649819ff43b2ad94d2a576c51d26f032618cecf2d0eb51b064294ead26c4da83af18436e7910c442d6da530ed698f6887c518bd929424989b4dad1c9c4c1fec45820224e1b7168dc810a0d23504000693c321d9eb8943e73f0e52d02f9509b88cdff7326e034b5814cbe89bbb77c96d834b209f81bc50298f14d2b249779b2dea154469364b36624556b8293e0f342dde97fac0fdfefad666d78b885a8d25a7ad0b5a90e794f2c41f4a732f005e120098f3587621b127ff0daa69a930fab2606c4adb08958ee32c8a029c51f0d6ec53c55ca3b7731966ee2da938e4ca1935602b8b9fe4e349fe4fad048a07341a1842361c6db9dbb1e93490a0c77d9285e01f40b9d1f20ad041697467c099325c102d5a25d5b100bf3f4738a400638c18cb90b1eaa62eb4b60e209f843daad01a21bf7f6501ae4e8213061644fb4beed44db704fa9dfb124c69650d89a1a5ec4bb06b993eaf99764cb58b045c484c3d84706d03e02998a56d448987f4045358d2c1085f764512d222e26cd4c97e7c758592532825aa8203bc48786b0c6f093d186d2700aeb06d2e3a524a42ab11d3f810f7998ea5b0e92cd3b2241135754025fc74309000acce0d7737d5cb7884c3e779689647ae97c781eccabeb8f0ca3da243e70b3cfaaae87d1604a2972524a6f269795844a3ada47bd9f60d511e2b8c56cc0d94ff322e352eb640d072b17db1d8c43145656a9cf0f83d6f9e5ef8530404c3535b5f72b5c0281e17e2625a3aaad1e51eb81874b58407221a8547c8701178f5779927416d4c60da5019cd6ed397d4253a2749c4b5d306d372526301cb03871a12d955354f81163753d4a352ee3fd1ad53876a9214335b9b0dda1c10b2ffe10b10a6be79af58dd5c8da9c535948970e2f48771025421ae691e2b0847ebf2548cd9fd9205e633613bee968f475665b76288d9a51cd817d72671660944bb2e21565fb3dfe23ea6912821445f9904a0b7a2697f844f0cfa160ce3d79aaec2eb03b2b9d026412da0290fcc6585c5f0fa280b13d0729befcc39fa97984f223cbab062ad812eb963d3b8b41c39f19b4bea3f92e20195abe7720c1298be58da6081df3da9aac6c05341dd3c8d359f0dc14dc1347e96d0cee4bf6b19c27b9b82294d0a2cac3230685063707860a93975ba1e9712d15495d1a8f3a6516dde3f8a87465529349cf97ec8c93be7728a0a54495684718ec99982d4d8fdf980cd0d462186290d2b9164eeb126390f73bb1bb6e98507871d39369183ccc92cd88b96de6a2d72e80a04bb830e058fcb2da0433309c8fe10c96e0ca14079a5e7eb2a12ce1365264c3b5a91fe2f58bf9dab035d18256f0291e3b4fe7c59897067f79cc14a18837fcd582d14a2ef22346824b22379f26dfeaa4086461e82bb72aa888c80369cda88fb79a5caa83a590d545052b9273558672a61973f178bcc9f7f7044d673a8935d373dc48bafe362193e56d1b7432fae1a0b90d2404d7638d224561911d59b1ce53cb8464ed61d1034307d51e0e650c2deab49fb6242535f3c7cc4af33e1e8aca2f7d5bd6a6558a4616e436f1c8a1e01a6a721d456a3157e597f824a3b864f591c4b8bdb7ad2f53497aef0d113a0f0a8d4eab27731ebc51571dc62c7bd2c4ad18e607b3da2d6601341e487e047605231075f22e76c28315aeb86ba18a636abcefe41ef028d2409c438c96e0bd055ed545ee709ad6da97967cc01052d199ec00639d85c8b4b7f1c06af27536a5992ae33553cfaf6a08e2e9f0e0397a93217397076f273a873befc11b9aacc304ed1c909822ad5e1fd9b85ef120cf01156c5fff2318237811ac507aaae086f1fa1dc102e20f11a58c143bee8cd52d5d777a920420bd105f0bce75196d66074fe95a5558801c58dd9245b7252209b280cae0fbb59b74d31f622d34e4fd0247a6e86e55681203489e49b25bc4c1860a7f7a7dbda1282ef3607d909bf52d5d18e35dc18c5ac762beca6fb55486de05298c23a1bf4514b01dd4617fda19c6dba4298c84b76e0ddd022abf2f39b2bac505637e78626f0dc6aff58533e5933684c3e50416d032818a5899158264fd4c0f8b8548195ed0621bd3b9adca51447f3fb688f6072bd5ac6f4116d418da63b0de90a21b5f8d8c0259e2687adf9d224e85466d8033f469c030a60fe05132e06650c14a0938342d71047a035369057571421dacd888c3899bf878ba672b12f8966ee4b281f6f711eff2330ce51a76020fce52438e07e96feddee56b81f596d05827d1397138df4b403cd26368c5050832a0d5f2ebde7ee567d66b982b95539de4792386072f60cd25f4a1c68e62ca89f37b3b8f2ea2bb08d7eb0697ff38c98895b14ed48c28b8a2bfa78b1632d0b2a678824d4f7c1aa13ee29d3d4059a9905b890986ea3077cabe26ae22bf4e4198b8b75b33684616d1bddf20a86a93bb89909792104d98880608a955fd7c114da7d16c49994664065281ae3f1d1fdd77add20e97b72a819e8688018f58ef03e2e100bd69a892f6c66a942e954bd20818b3c3a82ecfb74d80ed9aa3070544ec53228a272e95b960b2ce14b71bcc6215a78e8211f3b71609e86f6a0628e64acfef37fda23907c8699de4e4548a55ee18659a7a810598a8b279a21e883a731a636c24b82ed42d9b697d1c92d07e2f10923a7ef0e0775884c850af59c82f3923c69d063e47d43ca0a10ffe9ea348320521db4173227069c8a34db2d6229f08d2373d0de4ce1af1d4c50a8650e5ec2a8937ac26f42aa410d6a409a8ffd546ad09a3c62ed3c4d3596c8bba4047237f4ebc8ec52665038d981b176226759c5f14b8b91510db9ed87a629393fa5eb79eb4ce97495264f677158c401ef1040fd5f9993a6570ed00d285f022ab282a136352317a445cb46a18925a5129711746a85bc940b9a6a2acc6f3e646f67ea527ce2e3f717f23b9223ab7c1c1610778f2a8504139af8a6c609efa218b4c4f72b260ff34012dc959657b81aa83f70d4ede39307999e473a9b74657a28b41d36aded93ab09f9a97c4987a3ba1a2d23130b3ae03e20d83564ae849d54c2e0490da92838528547cb37987c244b40a4e3af171bcd844068cdb2785efc1e492f3446b3d05d3b7938480af95e0741f0d63cd19a2db471b523460549cc0649c6e0d890b74c9ea5b847d373ef0f1ff7802b9ac4013a00abde28350ce364c9679dc9b91a78d94ab1eea988420733ffc80b4ebf74e0cb700c6a9a7626f7f10a23f0a4da181c7c6a06e22f5308356677e7b226da73b99456ceb829824fa663ac0b928768d0e2aa55f164e3beec43bd09ccb9d82b8c67298d978f4707c0461580f2384ba8530e6be65a05b5f1f8e91ffc1be2dfbe9f0703752b5175b747aae90f429360a3d2056632e33c9a0dd3a75cb810da755bd87ad1cf947e0599726ea2ea4104c03cdb2fc7203cfe8ab909c87a4acfe1808b16990fd049a8addc1bc96fae0dacbe4fdf57a52ad4753baa7c3069ff85af02c0f4af7a3abc7eac5006814a4430c794a76e4a6afbd64ef258bf4c6e084319c3ff01a898106d93bb264ce6f24718792823011f6e7f6c678580b80c4b3a4cb81254f1396d71778e6101e85d40d045493367e6487efad55d3c3f8606567198209cd30c4ff16641f030cd9b1678bf1139d1136750fd9214e2afa815a0b900f8b292a84ced49da7194ae9fea5cde6ec9a131dd21910978936ffc60b0ace15f2f0242acc776f99d2042348ce66e9f9dc969275fa3966d5c30132ac1aebe3445ae2a90bd019038c2a0f04e538741a03b476795c50f6688b9631251fbf76add1f2e3074197d1a5b79f78e66f7e3843a6cbc59ff2be5e82e1e2b0fef131691a2d831070494d0325770b3319a4d434edbc709088fe6827e4416d82927d1a7f40691a08385dbb323d15f43ac99d249c11d41bfa63a6f6799fa57d9aef2d56793c26ba01ef8b678ed1f6ee6d09649d770eae1d60d97248996e37b99431dd1954866ed9d2594b8f7cb8f7faa809abc47ec4a12f780a8119cdf4843ec07435c71da99b03df07ae2fdbb7f9b01119445f69bdd4b126c65e0fcc7fc816152b3ec5cd04e7e66cd1c8441b1fd2b1e8f278a45c1fce5d1e8def1f6d4c735cfc6cab643b46d9e7711ab7d998b8299c320daec0a3c80282c926fd1c0225d1c8c5b7cd846236c3da5c98de9d60e36886b0f436adc73ea7e73e262ae4bc2884812e012fcef6588dd2cef5a745883372c36a955e50bb47be2b394912f9c553efc58208ebd2c1aefe3d4f99fbb4fca6aebfa3345c61baa7aca577ca3079c11daf4aa8c4bc6c9556d70c91cb522657d174c69919ff9e2fca264625de7e8e8a35c8f008e38b39355a3fc78ee4104425ba63f19229528ea0e9a993ad943e47a333a51c6a9a8661f20138ea7faf3e9957e597544aaa48b25060ac0cff5233d98e53d2b556240d98a8a0c3612136f97ab801f7e12d8761202349164ea670e5dc3c636d75f383a18c3d3c1cbecb45d2854528a7b38681b13cc8860c9e5fc9e0bb327acb1d06a9770b9cfac7064aee223e40b7be194cfafd889023cfc68d46ba38e54105af548e96a5fc9c8ae92fa22176af23e3eb96d9f4323aa3d0a238cc7cc6604650fd5d82c2a42728ea522e60fad4036f1280114cba0a3a95c11a61763f0de6a6b1aa48b0fd9126f59da027f9c59549d5a4900c1a82be9a88b0d708037f70f33b9465a2b6a716300aa2933ea45cc95d8a00e99ec2257b1072da5187ab53b00e3b5fc5b4397fb4344a73bf85c586ded7989b8481f76fed966453347cb1fd61def8e68dd0ba9da0218fe656d6013a6e44d479b98470e0fbbacbd76871cfb04bdb081c63befda622cd6f5e5ef302e88ad9b6ca32c3b68766945603bb52de5ed9f60106679f395752f7dc0d0cdb0d855b4cd902027548ba3bfcbd4e402d9969fab6852cea2e382ab6f370c8cca416743fe7cd9367cad0f6b54c79086f01c1b61554d0d052bfa956e132ee0aee524842a77182050d42cfb510d84410c156774f3e2888608f4aa9980b91e4e202b3f2535e96a9c5890be5b40935f79a03334ee6c8ad3f6e97d5cf4df45926d2486b2badbd1a321da44cb45534fa2c23057df5bf6018c3fa53c789f16a18d6dd1288865498e034086bd0a03e4d3d53b1a07a730f10d7d445b30150fa7e8337bd7549d2c0267f260d490d2aee7542a8ba508e301715074508fc39b03d7083bdc9ac5d998242296f4e1d2169c5a1087459ba358524a905172e4f95abbcd35a30f5af797b9110e7af27221862703e419e3218f770f5f22eb058c8921b4b00447c9d9d0809181c65a9e9097411f50a4e184a0b9207891e04d20123db8e68b1da0cb59c1c50ce909835e7a188372c4bd1298468af08264661095c014d83dadfef47e18747334ce98a9d593aa1953512ba9605b87f1d9ec309483e49a3d121daed70358496877228655fc5b96d1ef5c469fd0c610ef613fb72cbfcf85ee30405ceb1a097731bb66f0f65c051a44a19981bc9069d0f4b9cbf067d04301d442c0e4a35d60c239d8d9964149a3e541c754b669699734b843cebdccfacef3af0cbfe01ce850c1a3dd266829b4d2397d5a055b3b12104d7663cd08c30c3a3cc8beca5785de40856d8fc5c15e1195586621d844fe37431ce37860842af9989131492c8d31c640f7dce08934512051dfe13f3aa86c8c594d76725d71074f2bb3d7e4bb0944cb626b9a74773d69412a10bbde34027c80cce79ef39ae82d84b4ce47e3f867c10e74c8c426d9870670e5378b4e0e8d6044de6bd49f0672f36aab5cf1819524e255f1a4c0b9b3f7af370130457d93f6dd65c0eaf19bdc47e211b3c4e8c3267a5dc0968fb87969aa2d7ae45882b0ac60824f49cf12d57d628de8bc5ed011280350e5b64ac34fe39e1322d3ca7e7ea13f0027cc353a9c2f24916417935010309532cb7ebdd217a43d59405d9f00f0c11684a5e993d982b32b2c98d1c84ddc4959de286fa93f41bc04f8d16c3142bfbf0d0684ed7a6efeb0cd99981214861f761f269843c2f3c1d60d823da378c0b312cf5aabedef1ac125b0272cb6b8b1bf045daf52a68d894d5079c4e6e85621d5208f566810acf4b7be63eccba472111328932dcc2529a967b2956340affff93c8475741bf9785ee2cfc95383cbfb558d351bb7a70cec0ff8008d8ab5401788dc95f5a2b9cba486c32ff8ae013b7dac5de583a3e412a420a6ce39b50180db4ab970fbc72321790572f43f5c5aaff153548cd6ed9a41529670d6015629497fdf9bcc22c591aa9bcb1362e848eb4cb6b610f23828ee15aa368510fe9cc4cbbc3350ddcc6a939404e03ec5937b89e0b4040c8d1df7c408689176ac3ee42669481ddbbc637bfaa60c8a7af05e292aeaba0a7bf81e3b1fe03496d2e0639abdec92af16b8a592845d3e3bb178ca292909a59ff07b62868051fe9c022a88e08219357a8a1d449e92ba4e9af7997b883cc125af9203a47a624c7431d1a06da226eaddfc0740ecb79a0eb6cb342c06f56936d4654ee8466334271ddb525a0843768e881b367c8887eae56389cc557b166f5e5e3930062dac789c60c9865da3eeae38d0ca98bc6462472c8e02ce41db880089a13411ec88dbf6b9d135bcddca30fc881cc8216287d6c908657268706ff536976aba4f1b9ab6b41a39db0969960786063f509cb0f9f4e12a87327a6d4f8402de3995192327fc8bbe5d91894cb56f940626c91cd936ccb6fc59e8fff1f65f7cc7217e880d9bcdfdf95ae2a6f6a60efe99115d7ffabbc8e0096a747f5c8a06480f9ee87eb04115a49f38f030135de04246d0f461ffdb5ac0440b6fc648232b11d5d34053e417a9cae4e613e3241bcf054dd36b201e06dd4e11a449067ff55743becc50deb2190b0cd413cc0cc53359ac991039def6833fa72bb4b45448dd4db94e14c20eed35545831e8d00234f036f54bd9351b76419c09b502e204bbc5dcc3ec1e061a906df13d003098857f8db7c6d0c6fd0c98ce176fd57e8d265cc38c2f8308f73dd92e48a509f9cf69630ebdd2d660458a032c275e2bb30d4ae097cb2c79e03d9b6c566e8804ff6a123cb540f6271ad2f08fe0249f40340d5a96edab7720bb6b0c0d0215bc79256eeb11a2e55df4d967b2fd730af666fa054eac26fe6cd2c8835b362684e9589377a42ab86935e8251a6a9dfb7319554ebfe176a0dc6832fe5081475d631ec44449de385a7d8b4d7581bd389d64112ca6912e28308969dfe59ef37422eec345199cfa565016ad5960f071fad8c322be3d651f94d40ffb9b4834017cbb352400fa5e63d9033bf075526f7522fd1c827fa58c2916e33d26a2b08f70d06bf86d51be1f2c386001c383b07fbdec6da28dba57a8fa8debee6302a7b586029cf84278b602b94f426d6f9bb7d68cd2d211cba297d71101fdba9f2ddf06e063ece03353d9d86ee4a967bf613f12888039a01ad151ecacb02f98a93a138ce646a03c4d62c15bc47d9e6d7c788f6a61ac1366346d8ae423276979bcb6571caf4feda95a714a01893a0dddc095ff45fcff9ef21accf42ff12ffc49446d0d519a8386235a0ef83b07fe84fb47359177fab0efb95dd4c61d9ed8a634bc35ca753eaa0d1851af5f310528ad1a9278a94de7ae9069faddaa0d1e2c36bcb8334effc39d0b6084dec32cf59c1fa4e47a16d7e29a4b625f6891f17d5dae01d850cf655873f2e74e7e02cb88054423fab0385ab3dead6759c856962dbef6b33214f2a1113563d419624e07bedf5af1fb76c7ed67b4cce188000cf299073de4a07e52c0e85bbf3b80ea027891117a9fca202b85f9438c74b77ac4eea0fef130ca558e3cf07fbd09dc4518a9c7f816732c17b0c25a3bdcc791143685426409b23a106428d56d93e237165c00cc624fda8c9239dd4440b81f6ef2d6c88e089f6f15006924ca45355c58bce1d584733a9d38416259d1578598b2294097da3401088c7fd0290fab20edd36a62bcb3d61c8679982af51baebed844b885291960e12c760eb50d5929ee8be64a61ba5c7e01031bb4f022e03f7524c93f49a3e34c001759963744349335eff76b2fecc10dee58290040541c5ea20f7b180c9637e891563d3e0c861ee4895e06742e94d65c47c4395539d6cf9aca3f2215808db36d6e1d241e960c2c1868a53ea63ed868461745e9ba9ea0ee969ca7ad8507c20c51110c4fc56dce05f2a42f89f9a6ffc1e92b98535268d15104d22496f384a1c01dc2292eb918061069a847d6569dd33aa3aa24a27bd949b0d8700e225953e30eee338cadfad19efdecf6b57c00bc877f4b6d8fcb60dd0b4f0c22614580c6635c8415205acbf1488a2f1617f3a99804f7f5bda7cf93dbfced85ce114a2cda35d5b6f4312d6ae7d3555c26d77bb291a8d5d66383cdc2a5d482adfa04ead337fcde43e6c55b71d3539704a4b0e6c7b0697d18e94173ee1849506e989de8a0604124039ab1ee117a41951c7c7aeb019df7edbae6d9c00dba6ba858948e45f3fbcb19f4f76c66077b7bfe5ea11704d18d1ad308ef535e8ed076e6302d94eeacac1b2525fe8160dd6112fa6d15ab2819fbedfa3b6b80d0a81cf62d42bdb009d1f448f0173747416ada8c8500a60bf56ec71d8fbb2fb36442ad8d22e3e3462469a999dfe6ba92529376ef2eab2c1a980b58d6894852b69c6da3a01589ce9fdea3d54c9c6f3913fbfdf030ee116877f4287fa1d35c1143f6aa2d80198de6b559e094283941851a9ff520fe0c3bf93a3c991298f353da362e0c338ec70feb32bbc6c0a0caa04a6301710d846d0c946872423f31cf84b730ae468ea220d64b382e4a5ad92db104cb2fdd8c5060b30bff06a5eaada4749d2ce4ef01ddc9233f9cfe264bd929c9dd0d15368ca5756642a95d836583b96323af0f52042012d0505b19c2a4c35300a621811738d80624441601cf879c0650e3dc60f478578361fd6311ce7ba876d09f1cc2ac903e965ae6fe4e64cae7aee264c2dfbeec899896a9563db045b2c9b9ec4eb9a50008e6b746cfe20c965612d204cae8b75c21a6ab5eccb16b55cd5bacd8fa84c0cb9568844fdae312567a2783a17b21b1c76df14923d80aa8759993ecc35ca1e768a425bb1ba121389c0b6a61d618d6c34a02a900855db77b3ca86c624d2f99f51a349dee4505440b583f3549dea426cc1e3581f1833560e5180d1e068e69ddd3a421253fdd19c4a320d63643f1778152d42b60022b8489da49d781a20631009622298fa41ec04e0da42cb4e1bb016e5c48fb39290fd90e4e44076a0f73281933c80ca48627eb93d2602c6d46c29bdd8d3a6737390062ff315033e342a24947c597fb42e68319970a85da0d837b4ec8075c37c630bda0708ac5948a8904045268845365ebf1aa5ec1ca2e2977a587bc7edd955680c57234d1682a7099548e98372e2d99c8df4af171d7965e9ff68656071a5ce8c89d4cba55ca5328334a880411e91e4f935eba99e5bf15e04bb7cbbf0cd6fd6aaaa378e9fe3b2f6ff3122195bc85fa32928441eb52ca3c8479105d3ebd105dc6a7cca4a78dfad9ae1bb67770157844dfc52dd9812acd8156252fe34a4c049a3b8ad7efc24dd00d931e4515054da02f551c96940ad4cf606203a9fe81aa59ade50238f88d0a41e279604065218df4819fe1d407ff4940901802ae87df67c103e1d9a0a5af5bd84f576febe2c89dd1f6da5f43257d87c59da459a3605f4311dc84c13eab3c9f7cac311995e21fd08b195181acdc8601d9212c69482a5851bb11d9d9265e0d0211c43263b595114fd200577d638e97f8bde693b89d17aea3b28aed264a76ffe19842c0aaf8086633420128e0ae63e0784b1bede39fbf3170d26d802e73c58f5b4493395a7e9b1f896343dba2d61dca5476c008318e306f19c481a0af6c13ae3301bde049fd1458bf6e0be6c8fa7873af40ba707652f4bd3035080315e808fd18cba88efa916998aabc7a818e0578a0c11427275663c974c02bde01c2caf5810c9a4f2dffc0392255c52af0a7c317c2291ab6cd3d9dfe6a6c742754417005f9fdd6d0bda5ab2df2ae000c90924cd4da542ce878b75f9b8e8190e5a55bf37e90914dfbb0c8bc021a8a2c2d5fd4d33848a617f223a0ed4aef0b2c665866ec81ba5c85ba81cf4a5a59beeecc50a38ab61f8fbee1510a5280619379baaaf863b8f9eae3f03312cd232352b4dbca84475fe4f2ea36cfc2104da6df6418e6873a51ee316dc1f10115a7de23fd1d9cdf4fd1cbc30618b0dafc478ed273587d7b4e1a1d2ace91a3c98f08a903789ce9bf96d27b3fe989d93bfb5ba0925272152b030f7ede0066ba537dc8e9e512a601901fc5caed0623e23f201e8676ea1515b4f34d3994e8950ec70603c61301f34c1558a008a66f171f33a302ee0126fa72babb2521cdb9d77f95fb7584adff740795de5e225b0c325823abd74d52a23681b0abdd778cc2d96bc2708fb569450dc4fcc04e29c33265dee5b6032e908eab8eecae2e77cfa7db107a8f71d9476f883b758d5fb580a2159873da95840eda943036b614f0db273a0949ec9adeb68cc58721b9c707deb815aa49a009a441da97f80a00807276988c469028db81d04b58e4e28cbdd0c4e436469a57d0dc0f7281ce1906f1ab16225806cea4b6f548548856c3e9304429b26a5a85188c9c1ad147bf6c03641294bf85bee0dd690153b616964ba1ebc5188f523e8832bd8e00f04ea2010e80e18048441280402c046a64ac2ab5bcc2447466dda499236a3b5fc5f20ed9dcf16254ca6b83a246a90e9d1ee144d4abd054b84a200dca3a9f44f775f3c1340fd40e694f7212931a022bbd5d2b8ba080b35e10358b6bd268019a3022fb8d4645e6e4477f61db88858095beab5188f26d53fc2825f7ddd430b6f50f1516eee1d9821bc91e7a55b34ac354a7f4d40e34cb1a589aa33e5fe9e404cf6d23d046c116248cbe6a74b82131c2b6586150a024bf758272c28be4c87e528bf8980227061de2a7c8da0709dbc1f9fd0417a1c903f9adee81974f00861c28be0dea8addd12835bf357baecd9f225c7157a1ce799810c6413a12ffaa6bbd96001dcc5026dda7f40be8f209a354b0648ccb601ca0a6913a4f2b12cf097af6a222e37aea7ce65402757db521a219e5a43a7c7591763a05d1a68c5d47f88bbf356c9606c8969313f2a782af6a4365634c1e4ad5565083a3f2015c5096544c40dd1a2e26ef7c7b11aa4bcd880449df77d1114011ec3af984148bf1157315af80fb55ae92a418f6426dfc02dbff9be153e7e5b81e6821af73fd5a1d392d0fcc9c4c9d9be5ced4b4ec4aac8822c69897fa560046d770d2d5c8a460ad27f46d420cc04e4eaaa745b0c8b1bc71e0aa0c07236f2af1ace148b1c675076f64636d72339f49831273924afe4dd2fb95a234c7e0c272760202741f67f2c5aaf26b156fa760be6c981264d16bc7beb6b82b57c5fc505fcfcc910ccb82aaf9a4456f761fb4020b724c0861ed76429b0798a731284f6b8cd5bfb581897c300953dbcdaed2a2192384730acb7e49a246e0587b06e34aebb43ba57b7be2db7b316b125db699c361cd212565069516dde8c4e803e86fc6c8c179fda71ec92f11a87976477a16985822ef463365efb26a8389c9a3dc1bbef30ab7d65ba26b2666e069e16661c0e60cbca7810211c1c0179a7fc6e67d6a2c4e1441de882dd6b85fd60a277a1380e4f48e142fded85cb166c061256a3f67138f3c06f04db862e947f5a96c31f87474a90a31d4b599d0ea9c361a57c51035a5a62eb535dbbb11156807514be4108f102a84247119f1d1e655708900c61f20ee239d6ec3c8f63bd033e1ce156dd844fd2f4fc047050f73a3a65014d2e1ba222b5cdc2e653e15ecf7d7e51a2d68cc9031fdf1be51e5ad4135560cc773aa4eedd31598c644dbc4d96e4151d88b275e892dce908c48a633ae43b9311cf348938f01ed534801b03c40c6b0288c51c2de84eedef91867287e5216a4182c7a94649bd87809f40b8a7c9237f4da57333b09e97c01d7b01fdda0cb8ef58f2f0fb5e405c4095f8bb67fabb8d6f22f071371fdb803f165f9c420c8adf4f408c40f28d9cbad3a8bc87496855cd570cd49f645cbb4b7a7fbb04ad6e14b0efd839218da9012ea846586c1ae64c812d986440a182e525b8b9bdc79482a45d822fed4c890091ffc82db342a4abf6da8c3e6efeb9ba413630d6a16ff903b6931b64fde5c30ddc7642b1b670995260cf092871a472d50329787bce31d9ab6700eaf2d42ddadb4e97bbfb1935e64f727651803f832ad03d6bdd6273d2853326be50d6e2842bc04ce87c392321068273c818080716c6013cfa1beb1e0640e5e1ab081d327805892c1d01f59de276fd8f8c75349437b3de98d88129e0c0e6634fa79b552cbd0e482a203b8133792436c17c6e9a6be7b40b0e3cfba7db499f19d4a72771b8956331208e6aab0ecdf0ad3bb776810c3b02192ae368ce0223d520a04461dbcc565d359b085d52931881241fdb3bb4ba09196adfbf4bab0d49e473a58ed30d5c261256b8775ce05ded7fe6124d200c5dd8d0c635c10b4deb885eafdc01d6281f6631f0336852b4183751fa9e86464beb34a06d8d61c4e99a70d49c3caa0ead62b9bae240b9b51ca53a0afd88a8b1d8e9b1fc4b5a58538ec2704551c0a256f244ca256a62bbf4bdeb23a373512170df23c88750c45e66641ea3a5060c91ae6b4a16c95091afa1a2cb1504ce272bc73a0f07ca05935837b33e2d7f6f535603ace76a1d1c4f7534c3e38ebfa94962c24b2c36a5e92fbccb51b8d7ba9099588f29d6d31223620d53a62125e334e27c613fee78aaf14a4814c08715eb9ed38485666f46f61e026bfccbd1f55ce45251aaf0fdf14e8162ae615afae907219b9fe87185043650a14e0866b4fb44f420d96e845e5b2da0c09421fa8b86c6bc44666ab9c9226d8cae27372da0d014737fd86ef34a447bc2853b677cc16bc3c0daaee302de5f2e94dc1fb0d431a3bd83f4ab54c8f9cfcd6a0cf2dd37eb60214bd3523cfd29a8b49bde9364c41e127163afe19617e96f5c42bd474d488f47b36c7eef059b6164d000c692db9916b9d23cd12efcbce3cb7d96aeb0929bfa90b1d76b724f3b85bb25613ce5790093b8cf5db70d4eb36bf4c6838523c1bcfce73f648a6c55d4ddcac1e7aff3256b5647a8c34f97f3239b411037b34b280561a473cc83572b7f4d43e100287db588577b3d44a3d5a6fac77dc3e60fc984e4b60d143d318abb90b8e63772a0e0792fa1ef2d552e3ee8505b10ff11abe7f850512a061acc1b3b7d44341d852eb1e99ee3b3918c8e4ed0d7cec476630c256d503266f61bae2a47ee3ed6c7efb7e657377597978123d55f56a2bd1aa099b1632e690975d8751ba82b65b63da4eee89c356e5be8c125f216368380ed39ffecb269765d377a48d72789bb0e46ce17309230881ed30db184a7eb6aeaf941fab458b5ad345a80b9e2a57440f4c6d716c56f17fcfaecbcc43e68e194ae44ed1a98c5b21fdffaf2a5517e08d0a49554bf1b532f53736e33403b21d4f6a96ad61dc86a37b7f9282b5e815aa7e2eb19f0c3a22205b0ad67873fcfdf777285b85e4e93bf32c016e97a9bdd1b05affb79afef71cb3d21f76240925a3ad02b0f6e0d0f4cbfdb754e6521712ee5d15e87b8f649b78424c6b86b431011dadd6448c2ded2875d79453a6bf8eeb9b98ac1416f577d51c91c6113fb72c09e852df60d124d5a4b5ffe3c878b5bcfb4a5d29269a3fb2c6c6628f7941fbca5d7ca3f3fa19ac32141fd100398d4ae051fd8f68ed3dbbd1f02b2c9b64a0eb72ff83d00815b3410e6d42ad50a0a773eb089cd47b0821e2c0c40855caae27e8391e9f604fa64867d3928be95ff49513f287e6a7a34a3e28464cb90062b9f486c0961b0d2a9849610062b9e94ccec999e4552c3cba45a3f4877a062d9abaf711bbcfb0c3267704079e3d0deaaf6edab8b9763bc323843a75a868ffdff7ba4f4eb542ba73a137a9401616686f96c4a66321ee41d01e9207cbb59d3a4e3e15a68bbb8929b54440ea1cd7016a5c1d75a9cc5b9e69d8e63edb639ba5688972f6f5c678f3146d165000e8c32ef29b6a5ef713f6efe8396c86d35c5c7b414d075d1c4bf67f3a7ee343d9e94c73e0f4cec7401c95144f2a08be34d1b9797303aef299f2fe18f36114bbef4b72800bec489752f8a8cad67005a490678eeb358e1cfa23c009a61c5bd1601e1cfccc550c9ea821dd9bc65aca3730b4d7aa19fc03ec955f140d45d5afa8fa362dedb11dad978d36b44516e89fea108da34e9a903a50de82a0012dd47298cd878f7506a8cd016d8e542a39d816eca1680cd35f8329780635fe92046150146b50800846e737711779070f40a8be965d6f00f308483db4160cc199bc56fe639c6c4659e5e8335b0d33ec34760b8651cdcaeb1744e1754c15638f5f0a265315035413d5fdd6d0d632780b3f34c6435c965592547e6e03613d37b620383876b91d7268a7b3a01b8ee390e0c17a930eb7fd28bb60e71a6cb61bc9552e1b34b44a176888faeb66b9a7ed3781cf6416502325860c5700ea9be30784e130a6c4fc9dd3c478bc0624a2fdec9af089b7b3b5ee48b6f65922ddf4499a6065d9a3ec0781e0147452180fd3c469009d31fa7c467d3a6ec809b9bd6a51cc70bd7d291093c1a01e5854cb2ee8e055951869cfc4a91d8449c9264a2db8619075071eedbe5193e0e4132e64f2036c68c657c23df8a7bec0780399db09794a4d5807de771e5c0b1a7572e22922435eff53a4977829189b782043c82562769b532ff9a9fc76d6c6077d6eb83a770d83e0b066b5855343630fe3518cd1743a6306b8c3076ccc42183849478be7cd4b43192d93b728bf42af20f93c560b8c8b07ffca2957e2df8a20146ea13a50170c27c575f30e6851749507d72c39f767c8c83459f730294890d670d4629de9293ce069edf5b226738b5a8fe3edca0bb4816027481670561648044337329149d562aae6e56b7f029eb71add5911a81490654b5a01f1dea525ac780aa322ddcd33f764d2265fadb33724aaad3da2254914046b5a28f93fc326615136ebc1075b1c76d089c38bb7580a15c8e97c27866c77cd0adc6c6771f3b31c417745fba357663a33629e3fd6b4491f59d1226f8b5b84096146c4a9f05c6efc1efc43050de687d53c1906cecef161c384fe7c1c4e77fd20df5b798b8ceb21c8f5d28120c9598d95376c09c68823f1252f57751d7cd2fca2feacdab6dbc3f48a43b849f26b9fc17b8a89d023cfdf93dcdf94fffffd9b6b89562cb085e9b51dd4a2d17e2fa130c7a234753da83621833830b862c0da5c5b788eb8600fed921806f21592215a571a2d1c525617e814045a0b6562cb3ad74bdaa441883ff40ad5b450aba502b4fdbe10caffcb5ee8475e7ac21d0b9e1230f9e1a9f30de7b52322162802bad41419b6955cbbfa43a80e0cf251a601d614ebe225696453ade66fd0e8b184df4150df2c34a0634565c112fd9b104c229369a8866d55452bd23674aa84c5c424063f4a1eaef3c28837c202556420aff06f36598740b2bc01aafefc0fceda1b37e4c4d7e91952c949bfd1d1655ccca3aa75929b5aac22ec67d2f9686038e6ed1a2919c18c6ffcc9363446588b2fa84d34a9608ff8a05b7a34a40430df10274c01fb41128cc0881936a7b1e3f487b905a25e77966991b99cbf483148b122965d159ded288521afa1a4942ce39427a19fd2b3dae9390955c30cc83824640be4e6b46a67b308df52bba2b54dd1fb2973eb575f5dd7ef8cf28dced38d2c1eb608a0cd87a5ff7c1cae04bd8ef5e87a521d79f4156abf5e344fbfd0e4fc9188529f2a0981b29770d54fd8ff8f8a40e2085b36dd2f74433461ec69ed5425d32bc4f7c3543de6d46d7ac8dd3650cfd1c9844514bcfd81033e238b63a86511129183d4f53327dca6335fc64518855c6ca40626773d875be818a8d80886ec2c5648cc4eabba98899763c22363f21430e4ed3d06bf534dc195fcebda6796795396c4f7c64505f86b89a15c6a1259496bf11574278d47eb47114e69a00956a7ced77f2ada2e6258e1a36b00e050a6d53102b66900bfa68eaa1e16813101461da9d0298a83908642adc924d5dfe30b29e9f12b287157d80bc05328c0f5dccada710d51c0f790c4062114382d11ab19ac1347d0054d19669a8b5695fc17b39a60097eca8274e531a4c2573bb2e504e6893ed3f5f3261c58f71d61ff53e1da65432a71b77919977e9e55909bcd0dd9624cd49f6d2853eb3036c91927e7c26e6b83ed31dda6ee6d93ce1c2156a8a9377d8046c46616848dec8420981a027e6e9b235ce8e96193c7caaa23fc7711b1f11a23d58c6415c8281288890c8d27e3a59d922e0edd1278e6de95a4266c49c08d440a0b2b9b285266768340165a60e6844f2389a72fcb7868d9a86fbe6815d408dd3dddda7470ed380c6ac0dc8756581cd2422c09fe3357bc5131e22effa9e4069b42439b4c8670463c56b8250ec0b8d2d11e26abc8e21d5e59d79932bc523d5e629beb1061b463d6a0f56db1cca33cb490fce953a0b5778f4a9d7108a539081f47ea84d39bcd53f12f6670f5f4d53f567faab0defe420550e6bda0bda82f932029ced430fb04402885d40c2f2a89a92171649dc25ef0fd3f1d8c595229934a19bfae2101d7746c6d9dd13d806a8b1826dd154deb9489adeb87405190dd9b538453125071101fea57d28a7a27a3546db8ba2a68aeb71169fb6d28a7bcb03471db1c9edf5846f35a61f0bf6a79ccd36da9e8d6cd49ca63738459c857bc449ecce67e70c13673bdb38157cdf91b0df870c1dca28381e5a7e798790b158451897e8a0176280dc66ae11331de06c386f3fcb7a80bc820007de24bb15d76a93180a0bb5c9c2845020b4e2a4aab18e89354cd0af3a70cdc98be4abc35d0f3ac0bdbe6e785f839b5c9ab303709fdd36c8b953588dba7a1d1a88d8bd08fef868577c7d9c0092287f03885c8558295656ee983bafe9eebafc2c8411c6171cbea25fc0c31ce0eef3b772c904970ab28ba2ee81ef238660d6cc02fb512c9ad6cb513410ee62a9d140a5748d9d5b2242db00a2e4806b3f32e333e00ec6ca978194c0a123919d903b00cdbf306bc7d310031d8005d9fc4a1a26409a76f7e359e7064746179c715d7380f370adda84ef1d52a57669a6665fa801a899c65a9f883bc773718de5cb4d2cabbd68b84733e9b84cd0d2d3c556d0749b3b4316c73fb6d71cfc170ca45002ce42f26209c498fcfed037991121abfe51598e38b98b29938d2840ccc9c69cf481f862c8fcce0b64ff1867a9c9bbca3a483e3e658063585c06068e9c4d38c665f50a547e04c49fa37bb5dcc9b275f7d8d3f22289248e826690affd784f7424cace37bf5c65790291bd8ed575607647a92912e75aa9e79bbb9db16b0528dc1dbfe73233872b8347c2816532e609e177ff9ecb84967dc1a4b0453c076559f41ebff351b2771d1cdbd40136c8d926ea8e5ff1729df70843f13dd540a4ccac115ebaa2ed8ff1b914e78e4b733d55b6360840c8d825998c25b474b7d3f8a97c3d27ae780ca7eff70a6c45e6d0fdf249a97138b6535d0cca5b186e907abd8cb9938a1575556ee99bf6bb6a152fe9491793df6e42e8477b1e16f97a0e39c5798a7b468ea98107425db82481e3a3a0255c709af2a20b2027076bc4c178eff3b0130e0916219380f0faed4d8722c918b981e038e01a8e3ae2dcbc9cebc35e35d1e5e9c0cf25cbaac088c1439c92831672c77173a4e1988adf746e4582998c7cc695aaaa8bd2dafd7222ed4965fea514fdf0a32aa7f7599e44ec403c0071dd600be8b6287217a097f767e2fe8bd46af8b7f4a6aea55912f0d3464815acb9ea92f6a3e6a50691ddddc75e09905caf97178b76c43e163c027645337ed71a4861c3ac756591047172a4a3490ed52a12a82c65d3f989fe7eb2685dd8b7c443537dc08538c04234b7a9cf2f5749632eb62fd5f1b87c5a25f4e4ea9fad880bc9d7ee7dd9e76fdcaa448eab8a28eae2036eeedef757d61f85f68e2d6a4a883d8a688006486354223f537b79b54a84cf3c73e74e4a5182ecd77e97b9eafdd70e603d574e995ed60074d70968737a7588e28da5ccea333b54804779c01a4f9d620b4f2dd449225f7dd1efab65cc58060b99e8c274b2ae713e93badfc42a6905f3dad321bce4ad9578ab1ac91d6ad0d15031f7c051ca0fc1464bfba467ce3740df478c030b39ca55c3a51c25e27e492db22bb7ca5e23ba1e647beab622798bb2e1e5b94831a53d9c1ffb9d84416e77000c4fcd639a8b4785020673166ce802a94b6236efac3a5b2c7b8d57c095613ca2ccc92e1c9e65325315d85ff6b1bbdb29235d21a34b1b7079f831f6de4c9fcde8a48c79e08f050caf64acdf7d418c0a9a734a2a784e2fcf405a7baac50d9d7ebebca5f69a7a0d72ac0c454e8ad2cd9e314a00996038933aa7bcbba86781862a2093476dd4d8a64790d5a97eccdeb2d10e9213332f140f2a5a8f40a5e82fe5b72c490b525112f00ca363526cf0f1e7f71a225d9a7a3326d5a2cf33e341e29ec19f862aeca52299f3928a6b347c4cb6209c1ac507baaeacf299786273e3dd75eccac05b04a9b5faeb90bc12f73713f1eae10093bea4a8ecc25885f963d018fb50b4dea04cf4abb01d1e6a11ba48883eecd064b9cbf05f590657a6e8fa34b35cd7f23d84f95e2fabcc2feee602255b073d6b4898c1f840e879343aaa52f5eb84e652a7bf275cca32e0a147a61f8afb35ccca0ae9751eac18b2abd09955e8a52ab49c2a7436157aa252a1e9e56c914ec5051d0f05d5848206bf1de85e11549022c489bfb3df6fe3f54fd0df66055d26485459276e8792b98a1fe8b14c4cbfb52fa4f2bb73992130ccdf638a877834d49d29683c33083bae34bc4b276298b65fdd8525e96a1fe98393d9a384d9195649a3d806e15ff230a554e1e7aa8e63384910489957ceda5048a8c60f6ba0a6587a77f7584473f79409c87705a73aa26daa73c68409a9c6c3cac571d566bbec82fe9d1628dc7c41e773b594eee24598d9cd1f05c39ae50ed186ee97fcb0e44174c17cc9309eca53de902637ade604d71c19cdd36b7e125a5b64cc0d3aa9fb293a0e752714e6da8883554cc076957c2eb4fe19f6240e56f11eee8212c88b7dbc17d623fb71a18a9e4d2625e976b3d304df8a77ac6f97c48779802ece0e5233ec971b89099cb626d21c7c3bab6ca2e9d3cb05c3704393ec094436d27c170415de1561d23c334368bcb115b43ee3225b575fd4cfddfd3e10b61bb0d6b9822bf35ca09e5ccd570a543ea718e90a776fdf14a489382ff837fb22be6bcd172b5f5561f223c6f52cca10f0b2e5efac775f9815b5b8ef6d74515530b91f5a94b96719f51405a8c9944f257d27af23f8624b93fb2d3c4f0f76d026051ab48d81a02908dd730293441662968ec1a867ed04bf092ef4563c7040142253625c1fb5574be57e2cc9acc5a17457c70dd19199f0d893a5ada2611a85057cd06d481c60d66df81912b0c2997bfcc7e5629592faa0652eebf6fe45a2427fc3846359689da7e969b460830837f110fc99c5394ef8244c8e2ce6210847099b3514d4cf25e716279404a379f7c8219ac1c02083becf50cec628fdf45b2611750a424c1924a0d53dba07f7f73d753cca9f48a49825f419505becda0fd8b0e7e0f8297134a7dece31cf55870650e5ad5e816aecba3a656751d064449e466f7ddb5af25e460af5a237b7594f816f800922e06eed8b6b52e0a5b759b6e4989da77d908104a3d9ffa14e6bfe4db401d0c9d80aea141f9e76e53e48f80a2b23fdafe07142e1da399f21bc7cca8890508f5bb3d6c36f6e0aa26fcfc98fb022e0c6c5e01b4713ca479efbd8ab4ea92564fc621b0557e9af1629acca6189f0a4942eea376ccb5576fd73b31126fa3536061d53591e298b12f172a8bf0cbb64e8bfcdb1adbe1a8439f950e2748a27a36873eec26cb9f810f24589fdba59e7520cb2c4d6e66e810597b89637fed9fd96666b32a4e50bd3ecf5a14abb1de3eec350d6e5ebe59c4cc915242b9c359d455397f329052223b96407b7dbcc9afd3725acc6840d4855dea0a2a3be159aa098291c4d8539c0c21029a768e933b8beca91e2edef57e856664a79fcfdbcbb8a48520d733f95e6bd80f9340fa4f7513a4d52c2e0cf79e4fa28b031c112a0970f7e5f60e695b5055c83c044978209e8ab569e356cac11282a139c0127da59e49871f007498b2d72999ebb476ca05989a1c4e978cd6cd740bd0a2829f80a94ae2df4b2918b5d4e87cfdf30f5ffeb2177982e0c153e99d73d7266242ab3c25562a9bf4a9d34edb7749382a52be2e8d8dcafd3acb7800f8a3d50bf0667d8efe1ea86fbfb2cfd9b9c34840f11cf15cd6b2f55879d812c40237d0f37478b27be22beb122379c27090a11168070452f96f58ed959cd3601cf079a38df88d276bc8964f70458102cb5400e4c3ec40b3b39bca254076ab4126cdb17e82395494c3cf4022ce9250a7bff240031f9d2dffb78c77c2a7498d5956326d98a3275642f4559bd914d6e87b283f65307c0047c49277fcb5efc6245ee176c9dd998ff527cf105c5108030e2d03d9a5d6cb4f9b1d71d71ee859bb90aa418fb47679774f546621151a19d8aefc5e637bf981d088e6c036c85e7680bb32becb3bb189a73045de5ae902f45a1359c7a599e7510a8f9eb9348290f9c2e93efbd83588bccbf4aa622ad8229423db85a753160d3c3303dbd6dede4881f448f6fe0cf9ca44888bf1f4d92b805ef218fe0e09516d125218dec3c7fb545dff4d9759ddb3794250a297477286dd8e40dd7f0964d1f1fa6c23055cd80eb51138a36c7ab1868a26ab8445deb905f5c951bdb8e8a3400d3d560bbcb36ae4f1bbc0753311a4cb448b136e07972cb14c706674c8d7a6a3b249c12fed34475d961d1988eebf6004c10b4c3ece7fecfc90e39731768d5940a7692b43d0cb3505ca67d871c29dbccbd9f215b340c9e21605262350f5da2563c738ba8cf400bd490299106d7232def45e8bf92867b5c101ac568e8ca6d06494c6a195c065ffbea55b329864218ee04aef83c44d099950c31ad380f5a7d961d7be398814d48509ba5d01905cea0447bbcf0c7f170ece701f1b6dd096f6e493892db479a9c1ee6b1f61382368acb87bcbd4043c5074e9fb62ba5c8c84663ff81f27b124162d5c409a3a4273782567cff88d8cb417ce68b33a830a1ac4ae69ee5c71422b1400c28edbda5f0a635e11dbc363fd36e8e94a25e5553d2a18e304c6b2aa9bb4d0934003276af123d5cbe04b14713ac0be707d20d2a7e816a841118c98ff27ed2bf11f008b4c8913729ae4d662688ee6381114b56c87b5fb4fa1fdacad3a00c97d1bf3d6cdcc382e0b164002acf2a88cd93f051a891254d02544ca2325533e6ffa529335c0c9395a1c8ce8d4d894a233be96306029dc1bd02d82b796c9d20938b18ab6a558e55d944f01477c155cc46d95898b2d1fb92583aff8b3bab795910cf97681549ea250c317a32c924cb5433d320d341f64b12d24ec88084cc8c036a92be855d30d4594d1b5a48507ce93cfd308e29ec50d280fd76c4ad6edde6e7e779ea8ee6be80e586f99879f5edb1b23ba4878718e3e38a0e26dae00080491f225327e04390a850b70f9d90ef404665968ac2eac7171e6f9758229248647777f70ee708c9071208f8da4a7d0821f3819092242a9f84143fc714a5294653964c9975389b1224654a1072ca90293f1dcea6bcfab73b9c82439fb213a994252948550a13294aa29572a488941f665c293f1dcea448d9618614294a4d4a5252d09490e80f7967a1e3734a3f50ba81d2910e674a43947c50e2214a4cc947290725d71542442847a00c41011412d81c28941ba0ec3cb1e2c9117a7892d4e1ec49d1931f74dbe1ec89921e1f6b4f8ef48f3e81c1107957f10488272f28a1c3d9931f494c26f03da29042802890804441a506109831638824ee1d02087808225c27ae12235d8c412af5ab287ea488a898a23f289b422fd150e82a3fa0a88203144a501871118a255008c1c91b34813c18142e2876b827aaa858b00fe8b287ddb449a7c518174137e92792dca075ea9f98a20436cd9871c444aab1c29d978889b483251329499ec1339372e8a1870f3b24d9e0860a8c600a52131dc860891feca0871df8d0ef9dc1c367f0e054ea574f189922a7acd65a27b4b5d65a5b8456a2a124054137a189d036d0b8d229915c1da6c319d20f12cc898a85d983fe688733cec83f1ab98695be07eaa3baa492ca8aa74de41f4c9f7aea504ae98dd871bc11bbfcdd552c7c75f2af847eefc5d4052674f9f57fdcbb7d646067676727c77ea650ca97d3fc9cbb614edc28f48873c2aacf0e47cc8437f2238d2dda94d30532e8cce5d8ddae491b1b3e67862c40b479cfd841cc097330b76f6cb2cf57dd987d72d961c7fbc7f6367c9405989c06313f4388735f060ea43a84287c2e3dc68ece799a5c6fda962a16bed3ffd4863751eb31d0b0d3d54ca7c4d7466d03f9adb447c810da4421643e7022c909a40e674e30e99fee70c809596c1b42a7133efde3664eb88ad0744aa2b56832a1dcacc50655231f2ec2901e8be0d33fae08af0e6745606a42493761d44411d349de4da8405ba4a227807a94292dbf8583f5ef659e70f50f46ffb8268cfa37a3882788fa879f983d91a447978bb883838f5294224a6045141f18810a2096c090f0983111a5c90c1e8c678e2c20c36d085a0ab0948bc48aa0264181387547f0467ef7f23819bc919f9750f907deac6c3b495a6b82345cb4b4b8bc0604696c2f8f8b200dadc56317a481537f83208d2b9174f9306f93200d1bf3160669d49749d4590c105dbeccd3a61d48c3c5c39fdb69037dd3f0b5f5e9678cb5d3cce33eb3fd74b165b60cf18344c4ec2ffaf0452952fb93495aec6feaf0cd265cf6478bb47cb449860889883e48f1c92453878e935cf280439847538714cc17bc8949a44899144896a20f22e8f22b1149bc006f22105a152f9c03737691961fb5201ae35a10bca1d2d5e96fafc8820c617c1b7fc1025d775e499f5e76504e4a3d8fd61b1cf638952b1c10ae3e94d7dd8072b582ef6978439fb3b1e10dfd177ed45043be5ced6e64ccd94edb72d6b4ed7630cc9836f4f3d651ef73e6620783f7997bae6300e779a8f77eeb6e6c1dc57346791cc31b88e10d4ea1e78e6a18f4e78ee3beeb18f0794628f1497baf3f7737b6eef1cc71b1bbb1ade8d7ba1b79c37743fd0a87de6fa06b0ebdc7dd3bf197a27574fa4b7d5a87df8c9ec2e41bd14683a3a40e67473e01e870c6c451d74b90c6fde943bef9a1e8cb63238234eec6a3f2a8b85a6dc33526a15ffe51cabfcaa34b17a4aa05f9f72f875d76375cbafe8137336a1f53636afade166212fa675fd5c2c5b86baa16f06baf711d97d71dc9a9cb52115e8aa6738ab65f6367c35af875bf143d3fe2f862a71cc220728cabe803ca88380e2e0e24de40488996ed3011eb5bfc2884cc074dd470d4c48101241902081741902a840523e0499aa2831e4030a40a1e0ca1070ea2f0c11582810c5e2ec5e91629a59433d9ce6e81314580190660220672c6840cbec319133d4cfcc030744b12f497ca5d7e918ca44b59974159c62372fee8f4670c9ab24aa74f278fecde7befbdf7ea485aedc514e32923a6f6620d6b5bc61bcd5cb7e5cc51dad12ffea0f44f3f4e4ca26b09a59089fc814ce490222944128147309660505929d1df21dba1a40348966420aae0d3e9cf15220ffdfa428d2ea18e4f975bfa48241d981154d2f9f9745e5d7e9473525aa39c93d25a2dadd6de8bb176b1a66d5bcedc9639aeeb4e278fb3dbd39ab32814675758280bcb949185d2fda5405d5c38fad2224553a92963aaa238bb3f1a90badd2c48ddee96248ebe8b0b47ff8586a5de82b66831656c716da5341c7509f94c618816fcfaab6869690165a83095d0ccfaf1bfc60cba0d5045d3d4c068a9d35c6194d4bf961699144ec0d78afe9d2099f217f7a681feb08a0549030d3fc6971a853242084822e439e794f3b73a27c79ed6b38432fe404937c0c801194a218f012438623a96ab25112092fa29e2790c2c6124e7124b288b11ea123828b2c40f4b08596287257872169c93295a3a9c3961c2c9122732edc408cac90a56585a5c5e5aa4606254322e66687e732739bc70c2034609353162d49400e305df4f33e3424615b33add11e41dc4aca84a93292260848268e42363932a4c10315322095662069a122ed894e821cf94f0e15e3a9c259145e7d2e3e7d34a136fa6930872e97096c40a50b3244ab0324bc206160d8b96981ee30c891bb440c287d40c091f0e091e98981365cafb0548582a8ce4409260567ceb534ba945421326025cda648a4e29a5f484958c6c720419184d948a900046de90927925488084132740a2081d8c8880a9e216813f60a208233f5260c4e3053aafa61fc870104204074eb0b42f249d942e61a4430c231490a0c6c8e7064a29a5940241af743833ead16b28610412bcc0410f2f82c3fe81e78500a15962071c6696e0b1c145163d32538840c5844fcc1212c03891f1a49c18e969e16405af1719ca7fa64494c9044f0b112c495e2b33e841b980472bd1030e9e123e404e5904e99e94806bb2439e8d605b4109b4244a90034ec286115c24b000629de45082ea84070f4d3d1237e894524a29a534e5034ca69ed2e99303a514099e4e9f524aa93542d25b74385b428ad1921844518af8d10810019aa8004896d56006fa83322afad3bfa38f60f2a389099cbc9aec781dce8e5892471cf58f1e51d423378f60d2e35ba063233ed09f4e420f69226d44530d5aa77e0995227e9063b98a53f619390c6204149711463d4618718311417019318408023bd3a6fecd7f39a51ff9be37480b463b1bd1dab054e24c4ae9b76012c706bdb9f59a4bbd7ee628d03aecd78ea48a9f5ebb5692383a49e2bca622716e3cd24b12a749e220ddbfdba8d347449ad6ac925d1ba72a49bfdd9932726cdad4f7ec4eaf16a857cd55b018fefad1d42f6f972459dd7ef28f66dba04ad6ab2aa9d7e754747d9727bdbef6e9a3aee2a1cfe7bdb6824414e853ba7402a263f174b57e67b9addbbaaddb3a0ee3adeb3aeeb9cdd6e7b8e75fe43cbe7d17c0787619bb583dac1e560faba7de1e38596b955bc7779f3e0df5d62a25e631cc7eedd4b6df3a295baf968fecb68e0c4fddb77cba563ce5fc5876b8dbece464c6db4bfcf3f3a661fe75d116f132edbaaeab74eb3a4abbdb82b560dc77fa2fa51dedf4bdb77b7d5f77945eaa6ff731d5fbeac5baa7b71524a2d071f9b682b482b482d4d8f551c5ab9bd0e1ac09a93731e951870e674c325a55f261d639e7ace3dbde6e397f8c75d68f760d2b20f6ee6b4856cfd53a6bcd12f2b0c79cf3d53c86d9f54743d711fff4b073e1f4f96bc898173fe8cfefeda0bd7a8fa70c96f7627466f17e65c369e36954f34f033a9f0deab8fe6973d86d1abb1672cffcdb34de326fcb73167792d70873deaeb35d77bbdb75f759aeee593c9d907bef65b9583e5dd7b15eb7ebba6f07c5ddfcf2d52ea5f7f57d14ffee89771c97ef7d6fc3b9f11db46bac9e160ef740c291697f87ec4850428212a6581740873324549973fe9d7fedfc3b2dfe1b3a1d4b79e5bdf7def914c6d64b3e6d09a1f0a195ffe2076db3c4a143f22f6e79d36e2b480b072546611042c82ffc0abfceafdff2815ffbdd3a629801de164c6bc1eebd33b6bddc1b9753bbc06c0569056905915aa446a002888a9dfaa2938aa31ea3c319154818d207d0e18c8aa50e678433c219e18c8f79a75b470c3374fcb0c3592beb0569df21bfc8e7dbce06b6811f76f8f163fedd9fffda638c7f6577d5d53defeb384abf6081befcb33f39be7de69fced4b4fa5347fcfbf62567717205e9e4af93b42eb24e3fd7d68c6bc13aeef9c4b55edbafd41863508ca78f5d0b5b8743d57ab5605b67a346fc33fbf455f52e364c0b97fa2c284e5a6b71d51d0bb373d6dad730c77554ddb9b03db6bf436a120f0168d6a9ac321ea4945cac1f351e7b859ae59fee34f22fd569e51f4de59f565fd3bee5535f6b0de99f7d1a7a8d2e5adcb77c5a42341ddce7995690d8d2de0557eb4c7e9a168ce5bf0ae4f25ff5a1c54398af40a08c5abfba208d99af3c739f3f66de0a42f3f8e5476e3beade7b2d53f9d77ab57c62abbed534db82713aecabba8c49bdc87c0b7e19f3e3fa5be65fd4f4abbe4c7d5ddfabdf551c25c6b3aeda345b23d1b2fa9f0ec25f5b3ebdbef678669fdec5e65eb56197d97a77bbf5aab129633b82e11feebc05afb117eec26bac85b3f0582de2a0c4a9bf925b416c8d7165d78dda8ea60cfd9a36f4f5fcb6a3ede85bbd5cd92a1c3fd4c3ae8588e22afd9a326ad1b4a18f3dfe615df4d2af4ebfece276acc6b63fedea82379c0b5ce72a10681381987c58e221897f176805892d2eb75ead202d98a683e40a895cd1bf3aa853cb5b41220ad5452613618ef063a9e9084a8ee0d397928ce004a873fdf42c59c75496a80451714d71d4a9c07aeed17ee6b8e75abcd426baacfc765d0b5b8f1d8ed3cba72c170b5fd64be2e0df5b6703f3ed5bb6cffb1451d15b8245de65e9e8204bcef73defe696cb82bdef76c8eed2c23fd9bb77e19fecded790717fac57e739df1833d7e17d972777f947e3a633030d113feb6309e9dff63ba48ef834f4bbbfd971f71e6f9ff167964fff36e9c2277b7c9c7ff39ef5eadb3fbe1f5fd2f952762cc48f5ff4f817632dddce5ce5f2dbcbf7962eff58887f5d887f9fe59238f2f1b378626b87ccf73fd6c77a75f927dde138fde53aee77efc9ed2596f284f9173bfc9fc703c677db3a1b1ceb6e6fc9a3e235ddffbca5b83d8be7f4f9b71df5cf16cfb2bd3f9d3664b9361d80745b74e4f1b0e960512754f7a878eafec4ea89915fe9711dda77819aa3972587fa4ef33e8f876ebf2d097b7f9fabaeeafa62ac73df62bf6c38bd2ea689675f7b2e560fab87c5eab1ac97b7e45159eaf6672cb17a5a7eb46c9194a1bc01d690a30d18de20a7acd65a232da4a70cb1d6da1d682548509418a189a03f5d859e52658afcc51eb73aa50d8637fb25a3e4b2d72e75d8fa1eb08fea94566a5f3440d3ad6df172809e356dc3e915bbf48f523a63cb9e63eed1f618ebec26f43ca58c733bc5e71d7637b61e39e6f501d07127774dc71dcbfda2678d939bf71ff49ce794306fa7f943f4dbddd876cf9893b86321ca8ea5d52ac552874a6b85924bc9288a155194a23c11c5499422a20c45917d29293a0a0e4a3da258114529ca13519c442922ca50145914a028b0283d518b82438f23b0d6421142041a942322c03dd05072b0d6de60438d15ac85b27343eaadb5c2865963052badb5d65abba46998d0a9d4afa0144191024a52ff5220824f033ffcd48c32b6b4efb8fd23081964d8a177e0ae3d9777ccc11bc29bbbe5943973ffb0e1735ce00036aadffd1ba220dfee177025bfdb31a08d7c0e051908c05fff7b6c3b1b14d35bb59c6cb9bc71cecdc998cbdbe6648dcbbbe6e48dcb9be6e4cce53d733277b9ed94c3d18ec3dd9e3979e3ba2d73728e50667aa2a7293b1bdd94b0a3a76e6f395c6ada7478f6ec651da1d474eb79eb9778eedcadb75d6da54319967805dea9e3f2a6ed49042168665cc8a86260522d5e5c5a585650da3b755cdeb4b025bc3f9d12b67dd8e5af21e77cec49bb75baf4f2ce2157f405b9a22ff451fb7fe00dbd0f63abaa6c55d35651912bfa7bab96e48a4afe4df086fede3bd3865a2434cb7f13a4b13365d8acdebe26ff70f70157f47f69da50bbd3fd975f78da74dd5b1f10676947e244b8ea9e76afb7fffdcd4ec3554b98feccb8f8f6cfa77bccfe60f65743a4d34fed4ff7162f32fdb23f5ac4657fdf5bf6f7453a7d96fd711b5105887c94806a69ffa896f64f4c56e50963ade7949d0c37ab669006877a082594ef45202fb24e254def5eef2f15b76aa8d39fae937575eab2300b6479eccbfa5821f6c70ee1f8111a4e9baf26d6e9d7004d9b19fdc64c2f8188085a86ab5aea32566f9313df6673d266b5a1b421aa5f0364addd3f92e3b623b2b60576fc086df32b16f07f15f8ee4b208234b8a7361b4e1bfeede0e606922bdaed1f4e84fe544b9dbe6a69e72571b86d5554544daa2a323f645c323c124772ca45191480ffb31cdf0801b9a27b0810917d64076d2412c7f21cf0051962b77cffc01bfadc07dffee9f46d27a39c929eec670863a4a7c8f517208054430443c6f0e1a8d3b87de5a1fe81cb2bbb65cf97e199262aea73dee8f24665dea8b474a36207f5a63a36bc0e47f7f8b27c8e16be7d8e956923f7575d1c065a7ea0a947d4c32e46948e75fddbfe32d0f5d7987a6befed530cc2ce06de346f54c6bc5119e50788baac31b98acf5fa0e35763f127c775fd6dd6d88dda69ae234f724a88a2c0c9297d15381150c9f322f4a73bd5049027d06b2ba53bfaa8cbcf3198fc729176e5a26c74247190244ed3940125fff291189bdc231189c4991f63ec31c624ce911c24719034d98a208dfb1627668f34c9eebdd7c9891d7f8fcf1044f73378aff98773a050b9dbf4f1b61f2f116dfac9455df2263a8683b42c3523eadbfe74faf699763860a0bfba7aec1e3f17593d73c318895bf4d32d50972978fa884826a2dd8a885a639c9333d1947f5c8fdc081da1dc81a63f7f7b9d1c95e52a1834cdfe0c39b4df7ee333685c27a70ea1e9cf97912bc987a01fc610f4b74a8004202071e6bf90a3be9cb102015448c1004f975c598543fe940f5fc5352039bfe122499224499224393aaa4996966c92a5a59b2449129ce46a49b6243ae28852c6680517820bc185e0427021b88ccbb88c1fa1eb5b5c254bd1963a11411af4e7c652046fe6ff131dffab1e509f33783339acdbd5678c0732578ee14d74ecbdd794515dd346feeb0583fdfc74385cf4dd443f4b12a4513fa847575010bc89ff4ff4fc36578fb4bae28e7283a0a0205e248a0d3aee4fcfbdfeccedfc3a9f9ed340d4bc887ecd7dee3e739b3b71faf4284ebff71cf7278f7f9c17d11f87dd7bd4d79828eef4f9c475cf711ddde74e6b6d035e24c68bf0222f917b0116322c5e3fb07b93601526ed5798042382f11012121212121212121212121212121212121212121212121212121212121212121212121af2c369e053454995235724a1567e7b97d4b780f9bcbdf85c51e7fe65d67245902eead38a1698f73a18ccfebad4c3a46092545d52f77d202929a94bc1c052afb98ed4c3701d2fcf5da0c2522eb0fef2f05bb0bcb8acb4b0b0acacacacaca0502814a701163b55985c01c47db84fee4fa7ff7087c160fc080c064b82fd20793762125e533c3940d323fc1c70da3cadb7f2eaaa2e297fe2802bb83062c488112377ce39e7a494524a6badb55a6bedbd176323921b9193cfd9ddd87c7019f01870f05b30c6b2cafa34722877109fc7628c3146175c8e8ea44c26a5bc73ce6929a57569a9561a7f7eecbc3cc6633a3ed45890f32baf96d79d29e59497e984737aefb3c7b9dded2c7fc6a9a9a4d8db3860f7be762bc0ee5520bd7bfab6e5515f03cbafbced5430b43cea73b4f01c2c1c4e9b6fe52fb6afbfbeb73587f0e6abaede710e03ddf18ffbd3de1e765f63dea837e68d585df066fecaabcf47c1fa04d2e7d340753a39ad8051499fd6978bf5d22865f1b07a82641568da542435e69ab580d5d3828d884adb2343185f7ef5f9b9c6ea914e29a7b45a7bab94947e1f51905ff4e934f24a548b26778196ffb5a4a48c196cf89c56903880f83f67e033c68f6fc3d2d0eda7135da01fd38d73104bb17a2448835448cc4230f7a3bf4a8451a075685fef5664748494b4b55e1a6f055152674bf01215d02a84d696101568fbb12a59820222222645464712072325491cdc0ad2821115291131316a05c1ad176e0589e95690256c45863060f1ab6bca387d7df598929963ae87986fe49afba0bf1a83fc045a07fe9aafc6e51a2bc73f1dd8e5f8f5c61668c7bcc6acbc0ed91548ae880c913814a812913816d21a39de03ad3e9da740dbd81022fc043ac62a922aab4a24ce4ce26ccff52ff64f67368590fed5585dd2e7e3c0d9143eb163f544142267b96ad44e1dd5ea7d8cf9b51ccb55aa126d529bd1d3e974e3bdf8dacedacb72b178583c97d543133b81d7c4eaf9b1c3c37a794dae975785d5a3c46b22c26bfae9628050ed74fbbe144f7acd33590c6cdad42b058c6ce6b2b3fd1703440b10bca94daa23e8fa9fd7c4ed2fe6e77371b500e1f28249d26b124d56826e8e6ebb3f9dce3dec2ac73dcb45bfebba59ade5b402d14464f5d4c972b1787a5835468e435a592e564fad91d5c3712c17c742a6ef9d369c36b9fbaf02d1effd96a37e4fa075d4affd56ceea91381fefbd96da3d5f76fc0a5dffdb6237663a95411af3af3754778e62a8df9fb5c6faf7b99f7ba6c30e07f7f5740469ec173f6818459f8fabb6d158acdfc715d6dde068ba0cd2a0a13278733f6eecc96e927e2f10ebc5ea6939e292ccf92d18d4a9b97d8c31c61af3820555d65a638c3156d17356d9b66424e79cdf7ad177817240291f340fd70670b5e21e0698f3a306a01aeacf6bda709f25b641b9dac10081401beeb39511d259719cbee331c424f02bb4f75c0c9c73ce9f27cee3b8cef6bbbd93c7bf1a3b6dfbf7391acc61e6b8afb1b951eedefbf9fe16efe3f8b773150669dc1e7f7f787fa9fed1c09bed888aac81d8391658c41863c4424a7925c76269694e3bb1a095d64a3b1cb462312dc742c7c721391617dfb06103efd0721413a3d405e602fb8a44fcc9e553ba848e3dff579190cdf4c85b621b955a05cc2c365185cb8f5c65a2aa6517980b1589b3f1af2e0102020202024275799fc6b8b52d4751ec34b9c066fae42d3124fa738165a29d2e3fe667cae05e5a9819a4a13d8cd33077f9f96280bafcea02db718111e102238a89e11f1e047f82137122e09257e42253c938d8e596da9435ee44c7ce35d5a6da549b6a13244283732c362ffff4cc451c118993f95795ba9cd5cd6e77c35b6afe264b4d5a634a97241ce8efe5a582cdaff3351e209c7fe7f6f2e92f45d32b7f79e997975cc5ff5e5e5125cb3c9040051554c0902e1de863fa1fbfbee46b1be650799a1fa99615df6dbb1560c7f271f7dcd7907ffbdbc1389e236f1cb380798dc51c3e85ae40b620782329520c0929160b3252b248350849d3b6a53b84898989898989898989898989898989898989898989898989898989898989898989898909da48a3e8375af939f73485d666f046564e633f947ecc868452fe33b71f293f66a6c4652ddf90c89594db91fa7304cb6e90a1cc1148c7e768665cc8a86260522d5e5c5a866a88e13054c34cc9500db2dac2020d69dc2e6b8bd7407f9589ce71934ea552a9d491a432f46336f40349c71d0efd386280211f4ec4211f490cf9201af2213bd2e1908f21433e7ae8b6c3211f2618f2c153877a54e9f1b36dd1e1508f277afc7c877a1481877ae860a84750bf7df28f03fd87a11e40433d4e30d4c32587785499433ca84c0aec10e54f18e3a454a7cf875d17250789f04fc07860640a183722d266feb4e7e8693bf1daf1d6ab15a44bc9cc1c9fc6dfaa362bde1c5642df164caee4d38ff4023526638fb11584a866c1838914542e07fbe9f37e4ff7aa5f7217779a7fd1762d6cfdd60ec795b0e52187daa9538c71386f2c71582e89b17cbc6d9b84d6380b94df1d34c6ae6ce842eef8d22fca5ae5db26386c6dc120a48137f261058237f23990fe688e68200d2853499ced9c888e5fb9841d299fba2070cc6c9f73f6763dc9b8c9e9c21fdb8878d0dfd190ed6863b21dc5fdd30a0204d1c644e2cc29437bf91bd02c9564bbc15664752be6376c8fa1bafeee8d8aedb6a9cab6245744f492a4e9b2a97f1a52cc591d66e4e886399b0ba63fa0231b50afbfc1b68da7d70d1671ddd9b66ddb5c1bcf6bcae8be6a5aeca87edd9138dcd7af3ff2d7af3c1247fb1c3865d09f36f3b72d731fb7ca75db86cf09227fce315becd560e7078d8188c48e04219129992d21625264748494a4c4ead941015f81ce9148644a6c5c4254642471b423a4242589a3711cb05b2ab5a956817dbed4b468b18cf0a3cd0b360551df1dcea620c27271d6ebc29bf91c08fddd23d75e4803ce5c8edb6ebe407551997c42f221fa93e9f2bfff132a8ca6cf40cf3a842ebf732bf4f73f77280a9a5941c4084e8094d458cba7f56a0569c15ab09dd94e12eb95049e2cae9865a1a48a31b8405533dab52ff911bae663d81833da9531464da39d8d997befb594aa54da9781191b5a37c4032909427ad636a85dbb753624e742d031c6c8ebd192dda94a556a52a5529b26e55cc0ffe538c24ecb3f08fa4305cdcc11e550327102e46ad62322f4870aaa31a5c43f274a6289b90a1225ebf3b9125abe94810a9234094de50a42890a9a157594122612c72291a196a088248efd6925ad91370e7638b6d7788caff9af1e6d2f4883e6bb7f5195624d8c9734fb5ff0d7a479f91739491fd5a3971823c6d7d47c0d9fa1e669e88b0ca6bdef25a9e555b097a417a597a5172ada7572d598afc28c51f3d2c5a679fe2293381f94bd289138f66589c429e167d6c12509d270f13309152471687e3e0a89c4f99f8f92499cfdf3514a240effe9d2a1cf2f27e1afc15c05438caff91c31be86cf1004fe196a3e468ed5c7f8187c8618bfa279bcb78b23579fa8a097249d12ee9c99510115f4227b994d1931522f0fd302e65bd4d899fde9f499cf9783318363bcd6c5c30e874e77a182ad51b35532c65649ae82a1e6637c8e9a8ff1351fe3b5a7d1b4d7fe85f65b7bbe352dc6aed9d6f21a7235e7bbd8db0bdecc8eefaab46bd2a6d9f548aee696046fe6c7d8dbd2b4995fb3e1b49179ad4305bd407ff528e6b5ce46ccc32903a5527dbaab1e4e95ea635efc800ad2f0446953a282663694ab9977b13f9deee27367c305a5945edd2d4da4999979bbbf0cf4199677b157feee6fd5b5df3a1b2cdacbecafc254fbabae1898fd5520a9162f2e2f492f492b1f2de2a1825ef6a7b97ab46d8daea05a7a215f20a944907bef6be8fef42108427e08b8f7fe767f52c1c0bdf739b8f7788eee4fbc86d4806ce148682e1634bf628c5fdb583dd3e21aa24e3fd3ffb8961756bee35fbef913fe7b91bd5ef1849b9752ca189a07fdd23ce8a568b37a9bcfb17a9b9b5fad4ef895dd2f1eeffd8a316f6cf9cf2529e622c323e392f921b32357b466c3ee851f377fc2675ea43f6b1f02ab07c043c0e64d084110394e78133e04375c351f00bf7a00bc3c81ab2657590e03007ef53900b0fa1c6ff326709c6d781cc7bddd1fb7390cb4fd2f4bd4eb7499f797014ff21a20988ef9d5dbfcf7226bf1314ba97f91bd287999bd2c714982790ae392f4420eba7a29e50bb95ff297305eca1aa01a221207d71c913825bccbabd357a15ee6f78fc4d94f9fff0b184f5d925e2f32d7ea7572a4cdaf3e87cdaf566fc3b1096ff3367c069b3781ebe4c8fd76bf406997a44e5f6435b129c3e65db8f8669f7948f3335f0da22da8b4add56635756a660400080000d315002030100c88c322b14814258226b90714800c83945062489acac35194a3208831d018a3900100000000c8cccccc08004d764550068090cac4e5272e174690914cb74fbad6e4d3508e5c5cf3f8dcaa5edd4ce3b878f7ecbfbc0d77caba119284b4321f5fad0cced96919b9e3067219bcc27b3f67cf109c0d7832d8bd0772fede90c140009c299d9e1b8a89d394eeeb6b0baf176c15b0bfefd97c288811aa3ae3cc953e70d61d0b2389f1de83f0dd8db4ac579ad26d9a7ead9abb49e838002fa0909c5b225af2e0b4d02d6659cec0c12959e58bff517e132082415d1273d0cf15c277dbc5bcb1432783fbc4389d938192527d6e919cf27882a875916188702f070e8fe3096a2d8f98a2516b392d9f000bc64a7b2976a155108b294a8a1f22522fcf7adfdc0982591683a22de8afc080c85f12ca8725ab5677365f594461ec8ace32875a3350da748fbed8824bf9e141c5f5631341c9609e0dd26a863e6cb5e60f5f5664030312a9815a0ab187f6652aedd04f4377a675e1cacadb106e320bfd11e32a7afe7dd459f9a350fa88ff3449653debecb5393c894a39b77c9749b5ee03f24c72b74a9a530cb43a19c0ea8773a98e1773f940a5d37ba45563ea78f7b0930a3e6a5af128eb4081a36a0288929d8792ebd67053ab0f19881a15c80d3b0e9354dccfd2e724cad29550b9a72c90d66345882e0fe1a0162effd7b79e2c160dd58bb71d7b43c3cdfa736f1d14d48efd33808a9d0f094d605d479f50033bb6bb26574b819d80c0320fdcf262e3608af82eb7ee0752a3ffdb0170daa431c5e13c29fc85150b3e688c90311bc78acdc18eb17bf8ed84b87aeb852d418711605074049bd09a908f2a1a4307d091a10d58167607126e04684c7d5b093ecfa0093c12d1659edeaf7d3ce35ea901569b86f32d8eef9766be6b3ecddcf6cfe01e38c4663ec92adf8fc5070a298af1d51d032ca0b283c3e523bd34ce0c479875a6828cf0d1b033ddf23ebdce9372ac3b4d8be0fba16376d23faf1e78cc7a759b5ef1274c2d57298b584b0bd0538de0f32fe970d0723c374cc9f4ce583f898f8ef12ec33a6059421cc71f932125421fa95c17edbf96006816c46090d8ef10b95022d97fe4ddd8285683d7f266e1f0c582e6bcdaf3f7d6a4c25f9c4af7cb482ea7cadfb48a74bceb786445fa358fa1ce0429bdc2994af9b7b2703866f855e4f11b6a8d721d0fe4c43f9bb7556529e3c1f1253bcc2a00b029fa89a38bbb7b56b045b51079e281465469b7c927ff8777f0118b82718b3091d6f4399cc2bca8e9e8879ab9fea11c80a8355be86b820cabfa648d192c660e0fa06bfa7e6bf4d7aadd5f518bbf693b87ea11c2cd799c204401dc26a40a1206bfb55e7f30aca9bc6367f24724ae2e154a3387f11f8b1ac29cab336739a93a3797d3095068293c973a276752dd3902dcb1e8a29cca262da4f58f20a8a383f46713fa30486c8014d76909f0584a06829ae885452eee068f8fa56d30272c33ac9a8a37627b0cb6e605f98633d447d62a030332aac25ab8ca87b8470104f2a8ebe1ec035af03a9f0ea0d50d2b6951a0f143444fee6609a1bdbc12056b74bbd988da1480567882751ed1f73b9ac9e2c9a027be4e4a55c2f90b041cb738c68ab00e97a41ff8915122a595c9f206e67cd716c085d0a3230fbe3bc6e570f5d3c9a0c19ea0848947c2ef2859e0e3760143bc4b43da66ad13e1cf3cc5f20b84eb1816ab63f45e691fa01755ecb79c07f3f8d6b663e72066db7a41d9be2636c0376ab7ce885e0157c6bccc298679b0a545c5e564d8edb9d6b579a57e20a9609615f407d23701d603b048915d375bc1948f327948e4873b0b8eb5a0595f1c80e598fc4426dae47401ec38f4f5db55e0b8a91250fc541b17f590836cf550a17ea161e2ae0e3d5c94be5834ba399570249d3864f2d8bb35ae1ba43df43679fd94d65156f12e50d6f4a572d69ce013aaadbe1c460fe5fee9734112e042a6a343cb9eda0755e6246727209af1caea7cdfe801af90f680ac2488a23b7a6e44a190e5927f81da30ab7dacb9602acfc33b9a2403d73cdd7339a33783c84c8110e6c622307dd81c45762dd43e00b90b5598a1182acb0f0928dee8d01bb97e332a2e932bb4ac49706bfa16f2e3fcbe7d1da2d2064147ea35f5d342a81305da9181bfee18959688005ced48b9b65e9a107572c531e5cacb3b280e8bbc02901ceabd217927072ce17c3e352a74163691a0f49602a8a658a5f74e0957bc279532a84dd45ca84e8d7d4490726ca75010ec47a21642064c2a334854c7151bded885d5306320dd813bf425bb1b414d5c270afdd5ec6fe40caff9508d593cc4624db177ecbb8c66f66c9192f3c8fb65e32b76ac5a1b16b695f5181e94458f30489539e9bbd3adf0ee7872f16e3d0be956220566f052e9d33484d1ba82061da9a60242e60a1913bc63b3313fbaf22decdf6bc8247fa8fe7d2e164d45b2733807531296780213fcce6d27e45b804042fd47b0e96608a2b5d92586d78f2528dec2452a009a73c3f59e376f7de3dd37de78ffeddb9f64563bcbe47015a7078b1dc16cfa42b4dc5e2e8fa7e42c88f0107af3379295bb32b2dca8e8a6c24d6ca6782fb9943ea3e56cbdc07b3a33066d32a7374c864965a861364460315ff5e0a8f4c565aef3e8facd914b1e409a425b920bdf2c26d1ec493111caa831cb88636eca6c29a06b3408e3cbd663e0f6a98c463b206dd23c6abae92e0e7dca682cba2266c46d1753d53e6d4bba744bb7a2fad8935971aa945120232aa8fe44b5bb1904b5b2088ae5b428b07c64868b0a9753bb9c7b5fca8b8ad72824a57756e621f0647087b3b0af07477a085039b948298f8d5642575ebc60f6a0c837164d8e94ec2a413e72173c706a37493e825670c29a36ea46cdc7304efd5d2a3c424e048c714dff00508ace5825908b85222f72fbcf05e992c4af874416b41abcd4918a191a89117c833738fc001eeecd54a1e7967fcf9caf8887a42b3b7832187fdf8bfc94b3e33c93957a132909c797ed2a15e1fae20f974632ddd0d003eba16e6fe5e4ffc4b911c14a5db25e83c088aa78accaeb93d89c16a504f34138258dfeebd3abe0cdbba5adcd06f862f5b31527d3818831288cf6e48c7b8f8ecadb8749c6ce4549a640fb21285c23104192ce0f20bcba45b96151bb031933c64112d1041bc0d2222b0cb2e3d5b6acbf60f25dbaae9eec4e85c195fb2dc5a626d210ea281d2d31427894b3fde5d0893f5bc124ae72c0e410856fc24ea7a0cae8684676c292d9bc498ea61d72517c0deae6ebf4659fd16b9f5e1e5abd9fb1434d9a122c6754f5cd316709688a0c663acdf49a4ec455e0342c1fda82bed7a80119cca53645bee283f47becc0454a961b70426cddd17a49fb0dc5e21299855caed0ce6b1e97b2428c038c824f36ffd1186f36ab40039775a8c8a0c25cb921e70425fd72e6791bcc69559ee827d01ad3da9e1aaeb754fcb2515d76c929f6f529101b31b61f88ece5b0af34796eceaaafd979094dc4e65ed50c1fb5d85940575c3a53e635d04f34a3a1eed53b2f0be9aa8a44720609b2b9b03d1865050c176bc6249033769645a1a56aa92a45036579d96581d080d3361111b903ce7f8ed046981d23de24c0e7e776342788c60b630d100c1cf1dfaa05222fdc0a58e7872df20c67e99347877404a96ab091221941e5b7d023dbc919f71e97a0b326c4cb530cd8c0b88a6f6484fa8d49f3c5e84db67ddfa4e939f5803729749323bee9c6acf2460c858944c2853542a925d0419f8d07d2c9679ad9aa5ba00741788263d407f3770b822eea793ac0c90b0a0fbe5d98012ade6dd2cab2b7284413f82e919e210b25e5858bd6e093fb600aa80b1eff9d563c4ee61432eb90d3e2a4287e0b832a2741a0b07bcde6613c31d0a6894537de97866d5a26cf4969e297398e545c060e6c6f22dc34859ac709b38bcdd65cce6d484c7ec685b3eb0042113751a9d9e4619b88e0ae7691d0e3264220723cc6b5a1752b91a40600d6bcc298eed195d31bb44eb46da38e284907f658825e88cba1eb6de7216de26fcec7590b0cdc0b66e14de4f80a23360b063785186621756552bcedcb1448439bafa2cd3f9faed5b03f14e099372cc38a336a4a0ceadd364d1cd572360d93f9f040d04ff2d9ad2813cfbc439717052a9e5e1890df6615861d368de8c03e116f5e7948a5e41a50a0d4651d70a2ac24783a3014d109c3992d0d679ef5183a3a9c8b3e306c3d9c9b486a93a3c110730f974ecf93f32b7d457a867d5380f51a8c8cecc9024abc7077599b126078ae48a5489ace5d1525f1ec0ba75f79695c2232d4fafc0634967fc1200bb7892964648bbf19b307ae844a9f6ee18497821dd6165158f2ee8fab200c6c0ff4be08aa0b25139286fef5385d5d09b06d0348bce59c8a000796baf472f79ce104953ff03cec5dcf4c805dc5632cb4b784571afca8234a54996c25b25da6880cc3791640cf909205169a9b2e8f1d731e7ed8353eef09a9cc33732eaa4653f83934aee19e2d5f8e52b5d0ffa6fbb8a8b45b31ca766200b8cb24cecfa29f4731e6aa3e309c5f8d402cbb13016512307ee84331d38cf957226f1b0c99fd6ea6a95e6619e6f2d0b054905ac94ae7735fcd99a02640d9474affb92cc69ac50d82db2e1764aebf9907357585b1478cd76441bce3a25654945062386c7c78f8f58845dcb943aae9d35e0c38106825d46dc0121af8e8fba7584396d6b8f832ea5869ad802bc0fc551722600ca27f55fbce6c7b2f8ef417ae2ce23483b5d85555e97102542fe92fab13eb7b35ddda44f26d597ed55e8abf2d57f3aa781c8e36f96d60a4c76a3226bec55703369db2d93c916ef2ecaa651375c8acdc1496a63e3d961ca8c4d00fa2aa616f8aa11fca40be6619a2d158a7c0b733a9fd4e39ec88ede3b04fbdbce3f164d4647ac8f6f21bf777ae5a1ad76d1f2e7a70dd4ebae5f51f1970c0f086b59e10f60a6ec23f70b51f3b95b11cbd0ff495ea26267570cc09dcd447e6c2c3c36a522637b870360b8f8c18129cfda5ad18235e8bbc39210e42c125b1cc0422642e4e95f8ab1a2163d6c869035cc49dd0754cc18998f5a09a400e3fc49d95b22e490c322568488357842a7fed4934117ed2bba9f437b6b00fcd2dbc71551136c48780911826f67f8ec635b697e22dfb12ebc92b7ff67da460e94f951364755fd3cd9a322a538d71ff1ff4eb2ca43f61cdec4243bd67153034455b3f488c08492c332b82bee5bd0d24b61e96d94852587dbe1ca920b556b7ff97e7cc02917bd12e631b28c79d659ab28e834862d0151fe721c3893a2e006bf645ebd300560bbb7d8981bec3e2f6e3a4144c84e8804a266cf31fea71dd23727d586c2a6087c82cfc4731a9c43f4bbc48a6c99c29d0cce315bfcb3f5b5041abc79b0716485c27ca185f9a1a4c23f0904836961f84325e4e9251e7970d36995d5f5f95837a19bf8e2a6473924767022813229fa084a0af8443398108a3127ffc75997436b9750006db83cd1fa47c8cca8ef725b41bda04303d042fc95f3ee460dd62a00b2ec078380d70f815bcc3227804d859db99444520212fab832dafaf4751005c52ea2e03631b25c7ec6aae833efc0c2adbf25d15234468ca03232d09440686d6f0fae449588eb180da07621118a119db0d2cf60720658ede61ca4b65585404c3976561ea455b42a198879d75f126b44f513455902f03d242d283179746ce31f27e67645a37aa65d83566fc5195fb287a8876b7437e548477fa79ded5bd3b3f873b4968a86cc0ff075a4948e3bbb5c5c951291e89d32f678b94028c4cbfe0c5a930b84ebf0e73adb228a19355c3249f253b9ec4443d6262bbf313544f969d989f71d65a2f066146601eb9084acb9d885b262286952f025eadee19d91dd41923685994f097b5cca5aeda6cbb94c6cb1c6873b7ea2556138820e711ccbc86e0ef99f522c2ef39058eb838f1f66f5d800a02233b01f2f89d50ccfe008bc1e7fef1333a81fb3f0b3ff364c8d0a0241805d1dd12bf70686c6d8ccd1663dd6a722d6408b5cc476b45d5dbb881094a538ef40a7fabd836c7540b6a8887e499271da8b1a0c1d159bedc0a1ce45a7926c552e12cafdf1634a69b3bd0b72d1af8a17036e224e0d9135ff203e3e9574e4af3b2cf995f5756a1b72a534833df154450614719478f0b2a0c0f925d4c80d67d1ee3e9e9417496e58758f4857f2dc789a34d51421c379c8b8bc45f41062854a18bd5eaac2f5fcc2e20d5ad40fc2636ad462b52565384e812bf9ba6cd33a8bb4317b10e8edfd446ef6703dc93e5d08243962604329993505f7e5e42498b09dc8ff98c8da68374eef98fdeccfa8f2292bd3eba307f8b7db45eec4f0b237dbd741860627b7673bd58699b398df0c729b74271cc84afca457e8a8f421a33b3b4b18389e78847a5568cbdd4ea3312d4ec16840a898d5ab7b5f8cf4f5470f6f3ba34a8fccb6db69921388b7b8edd500dd1f1604208c46135996b041eee3a390e11984043dd89f5465368847e16e3d99d1c1b1f6b0dce8f7c8c0c18ef3e142a4e5be47c43f337e0552470f06a7b751750c6b9e6a6600f69b9f7d1a23a223f5f9f8a7605d07589380746fbfed11f0efba741a16586f15a11b1544b054197a0f513cdc9533111840cfe4f3dee09b87f0af2758b903608a41cf9ee438a935de36dd08994bb15c326e5d7696b7b2641621f7f1143eb3389797ab0400ec986a4e031fb372f7faa9f5b7b2227c61ca85d9eff6412b443d979acd663894da760f50d63a7a85d747dffa68cc4ec868e678e2a8e8e80b42208f782a73f30f6f37e4135ceb16124bd3ca9e1e35e5b60447b30545c490387f246d7dcc6d13c9ea0c1ff8cdebb487d3c2b7b16cb0179465e66543e4f00e3e54bea4e10550a10a3df707b90f7e6b265093b5c2bb33431e8010e3b6589dd256675fbf09695e0fce68a92caa1959e257a5d2089553bcc549b982b081bdd51d57741dbd7dab6cd1152c469ef6839f6a67eb19616c1104aa14e80e91034cb41b63294c6793e44100267155674b1d35de3ce2d68680a7ff4f0cea1d65049f6a0b528632e6ca659a2edf121852c865131e0cab63291bc0a50d93ad902f675a9239de3a9f23276de6e93d815c3929accdb7d31a8243a233fa007ac2aa832273f9ac7ae5e4c193d005fd911be9034dbcc23731c2f6a1755371ee4f7bedf4d300f95d8cabc1abdf6ec25576fcbf8bd289eddf40b8987e0b6a88a1584c83f093e6415eeffb38000b861c72ac268454914d9d81292cb3879d888832a1d24451fc2baee581d9330fd2ed82af294412506c90d1b45c2992d35c1baf1c2ebdfa0cc52d9571875dbb8468e99ca14a87a93e5c820d654556e831e941c5b839a81340a2ebf0b27903ee3a92cac174e1c3676e4d8436f0b322ece8800fca4d4d6806912d804a61489f0d726ddd515eef354598053e4c544d38449e4d3cb9ccc402bac3d13a825f72955c9ffd5335eecfe718f512018f75f04dc80155a5dca045321e8701c9db57f0ad6f2bd33e2f8f158255f93d71e43bdcbdae458ea2cf4ba037c453b02d51a12d41f178cd9f95ea5192fce56d664897fb1b05433483325ab61b3d7c0a4300c4e00d6708b73424485c9e6566e2b38c413564fe151c8bf5eb64eb818cf37c4b9d6c11d09b94fa50598bdd12e5d440c6ddd0f734e566202d7457420673feec9995c6e2f8ca30b6176294916457b0bfac9fa0a714f1dc4d622cc1b8a7d7e158ce2bad464a976234e8377c697325f7a801cd6e3ba004f434ed293b124788f0e9f3db2dc7e7ca2625b7851f30c36c96af405b31a63f65483a0e8335594ffcedbe19aa52281df3ae3772581fbaf0412a9a9e5635002dd70a7746f0adccffeafac27835465f58c47deed9b480496f5a4e9bc0abd57729542545d8681f0ba633cc909339732856537a5ffd618dcd12433c6243140364b04ec425e047fcf14959601d8aaa6b70223bfb1f703ee4e4618b7d3b7b160084868357e46da9ea5eec5fac8ae4416713bce9ac787eb349a780596121eb2f96aee61284b4fcd9063ec1ad49f292555ac5820c61e22210b04744b76827dc1123c33c8ee44a70c81b73c0c419c839dcd159b15e8da3b1a640a71349683ae6f85568442f0bf58b7ccdbcd3d3ceb4342159d310dc907c4a80da8656493370293b6b0fdf21a32d9b1d9258bb104a1a8f1b243cee0efe79c244eab3e50ae2b7cbd08bc2420ed66933c667f8c6b6102e0748bdaa9bfa474fc6843800522838d0bf5182a85caf3cb45f518b8dda852ccfb0c07e0aac078ada6efbd09fd5df0d6c163c4cf8d9848158fdbc17ab28321a280a3978fff6cd5a69ec7a252627b1ec6f2b5d3cb2be83d1406b43b76c634cccdc82b70959032eb6d847326e586057550987d871a3ff7937bfdc8fb693c960e59e728044877d7a06653d9c14163d61d422d3fa989522889190ac99ce267b2af3323833bb75065c241d448aaa549a2e8cfd9638e2771691b33cd219657f32be764abe9b7cdae366d63226f1e222b582cf928b25860d83efdaf2296cb02bb049d7af4342ad672abcc05c825e27cb5b6ddc48c6dab92ad362e67464259f3209f40b307356402140a451cf394e45908a4b867071d006e9fa9d38f78642094be8ae55e2c132692c5bc210ecd64d80570598cdd4b4957c24099a6b7c8d11b301f12aa70df428406e594afb77dda20f2e5bea61c7738ba61a8c5c68c127984b5cb7f97d5b0109fa37f1dab3745f4e8c24b35c32ba08ab6d6300c51e4f65fbdf4584015e0b3e35c3f68d5030404f4c8205194555d032fe6d61516e70cac3ab81701d2ae1511a849ce3094812c83948b19887d1ea6142aa831ba0ebbe3b1deb5637eeabb41ba48a1914517bdaa0fb5bb5f9d7e5e01388892792907a5a4e190746aa7d0793dc2af6ddfc139d797abbf52e7af62e960ab6d2f580147b12ebc80bcd0f9c4518773d76e7451bf8568f67a6e498e9ae5b8a81681ebd3bfffde40343f723d778bd5c07392284bde265e9280105d348c25a6e18f1e10f29e2737a5baf3fe602d5111d3cbfed867976b2bed28b81e75b05dd75cda60b63cef8c524eb329bf02108070343bb8ae1188468e6b299bc3680b1f33640f5f0346b35548ff9de4a1655ed8646a25ecb6ec0014d8eea1caa28e972c6631438d8d395aa831939e4d725f25ca17799a365b604fe46e5cb4c52096e1427176e26705e93c13c01f6d0441132685deb36e0630021c4eac3f7c181c9fc22f107b5f888e1fa88c61f3d06d7d4cbe304097b0bcdc6f901f7c892523d2fe75d5f3c67af2b0a5bde06c95d5102fa80f484c511043ab4bfc177783a4552a98b0e1dd9df5399aa2112df249f6e207195fe6e2cb6684d997283f07a867d484f4634d8c0e2276b7096b1ec33eeb3db2fef0cfc004ae7d2f3d519eec7d063d51d47bea170080ee0cfef9d540787d2d693817911145b1174ad027144987a0d6060ba34947c863a172bcfe99490859712854219bdb249eabfb0093f1e3006413d9b0a4c067a73982042455b02b6a89b9294b68f53a10baefa7c6e81707f73317cf9558fd5d1a65d327810add756c01b956ac292ef51618bca98ff4910db15e8cfcd4f0bc07a3b70d6aef62136ea9d09ee5176b80c23ae3d068a65a5fd3fe8cc63dedde72da4dd50bd79c2b6af510706bf4a7ee17aeaea88727f171c1caefa927cd048265d50c482d04a2f41bc91d1b37010e2a42a4c9af2d7e87661a3a36334fd1b1dbc885a193f5c3328b08de1fd270c413a8d7f00554afe2fd56bd5b3904e5d9fa43958ad873177b0a050e2a0cfbedcc13c59b79c359acd477abff6a10742b5d335bfdbddd3b12a91ec8946768916312b656d0e8a9d375b3be8edb79a3f02dcf1424c8ba0ac092087be73d214e33bc23f65256e6d2621317690a715daebae09673b76e060f6123091d3223e1454fd02033440aa1d91ffb39a3b901fcde55838e9062e86c036dc730ce580852a66d14ba643b43b0be84701385392686d875a99d0af9459dbc0afc2c7331f99701e69b3d67c61619b2f72650519972d23430c1f78edc97835a6e048c64f493b2c90643a5ac421616d938bd5d467a39e22fe2b02b5cc43d19f48598f34f27f04b8bb988097838ac51219f05d57eae41677f64b0caabcef34f14434c920c448f3a37716ae60769c486b2ae840e2ecb3c0190879475027206eb4aeec2acb991e3e22da458d4be2f906503be4f5f163f45ab39e3e2c0e5d801d729978aa0f87f8f68ac94b82005257c2da8e186dae5841bac65efde576274de8ab7da2f427c6b3482cc43f2740fe611fcda113b4ead11102f703836c298d1a5642026b7f10c3324799bd7cb06607c0ab6d8f8d64f7713fcf6777b5e81f49e8996d9170c8603f0ae73614f5d3fb62514546f6e038a5a62a1c51261bb2af86ed52145574d23ad459fa2863ab25cd50f60b069a3cbad82380b5e9e70c4ad5c2aec30d2a1b1a4169882f5e1091ee5eb2f233e24bc0c401d0ba6d6857616270bc22acba48e07e385e5f51b4e4bd3e42a7fa4fd83842bab3a3ceb0c5ebb016356f8170610f7c29129e4bc3230324805bf83053f6f4a0784e74bc631e39ce4ba397578849ea63e484b86de937b741e3313493c76b7603a3dc25e14107e68de23fafccd502367a948dc70644de12b008eb39278442e123b45a0a096f5bcd9c2983c00dfa32c3fd34c1bd138fef61cfac66950e82550040a0861f40f024d450129711ba4af2e6e1d2d00c42e8eb5fce406c81005f75760ceff5e5052102be0d0a043cea7dbfc22a9ba9105db6f1f83fba8d7d458103f23225541d268d4bc05503f570564ab1cee8e924460ec869753149ad2d2f3d44a7c45f2806e32bdc5e1dba731242f18e8f7b91a728f4b9e9c86c276f70ae75f4c32505dfa85c74006cf6de0305ca420906d1f44c8c6bfee34c4ff8987ee2d0284639675e305166a9ea4f9d7696ecd377d3681a237f4ff8677e224b1e3d05a3dd1d361b87d2325f6b5d840b8c1badd426b1ccb5d9f457bb05aaeb12e9a315b17c0b370c1684ad829c53f8801d9330fd77ccd9565e3a95373d12e3bf43fa898b91b8e0846ab2e8450c2a641218c2629c22f6b7f05e09e075f6296c37cbd67053dbe8d76840ec099d1f4f85f82685849dda20bcbd19e504a34435d706cce1f5cfbbc461e1646af45a9fe100dba44d248cfcf468bc8471a1ef9dd41f496a76ac44dd0d1021f8647c329191df135bbb9edff229adfc4476374bc8f2ba59a6157cd4cbb4be42b5f86c985654f2ff63a6598286b3fa81e2f830af6c83411788917af92a176e934ab926136e77d6fff66d7e7b20f676ab511e644a5261d9a113a4ead1522a05739a1ac8930f0eb1c50d4a0473b42e72e3aa3b0729907c4e060c58189a9faec210e1de87de02411f897d975d3fb4f4bb691b04fbb5508cfece2f1f7b87bcf011d4e4bb04f965d206a2f044ab1f0c54b389c80df30d935e6e99d3cd995e8b4cbbe96290db07d7bad3b3e2efbbaaf899325bc6cc3c8c121d85898ec627beeeb9634b651362970ea30eb9f4889fe60c93858aaeca3d5b07fd027bd25cb2c52a260b28bb92b21e87f20004ead209f494f5fbe868e02881349e1d0f152ac3721bbe79fc6b381cee18e5d79f3025ecd6dd5ff4a709dde9d1cc5e45397f323f9555083f46ff288e1c265f56eadcec40b43c975f5b7e177b5b5e9bac24590860b708b843cdef11ab8908f460f47eba457ab20f7388f4a0398844ffc44c53be9ebdf383cb392d1838d90b173605b0da0ac17f45503336e371fad1b288d23fa35cea9431bbc646a36e29a8c2237408254faa10ed68488439add508733138e3d48866b41d04602aa010479cf6cc15827e8b99e02ff6c1f9d210752cc74ba3347e8ca84518dbde9478d76e3b046d330f0cffad5734ca1f12a6954d529eca7c6b044dbf30817356e1e1f10288359841eb50357a8179dd3acd358ae3878a83594db1784615a37308e86a0b92943b004c0dc9cbaebb1d791885a65ff8d5280e7d5d3a374bb03ef3437f2e088e2393aa2c6d4f8521ebb5617143b0ba9ca603e9b27b161da2aa48a2b786c017a558031862bdeed1b5d03d9e454b7ba60ca0fed15c05b3033b454d95011f473a8cc21f366c6ddea6f4fdd673668af3025d5c72f4dee40b4f264a77c58d20904de412daf531a6c40e30e57b4286a47187890145577e4e1f6434d25301048b41f31b8e3e6dbd7b8ecac2ad01d4d93ccad11cc9d1900b9d2915437e27687f72d0530c62c771ad7a709f340e2767b589117e58e4ae3b894f01dbc3d083049ee60abcb6dd8c4a1d2c1e93717323a6310dbd0161b16b5a5ce4b3f0807c6292cc4f836305876bd94afec72ec43b7dd323ab858851506ae382011ac4739f4a76e9433950679ab2b79523fe489f8c58b2bb4ac9ae0ddba0148a75e7dbbd9a11cc9fb3e44a4bb23bdd09622a75b0a418c41a4dfe93dfdc637fa35e3170fec82a561ff6b898af20c71ebfee6a98ad5303fd6dc6f776015ff5d74d0dde46981c1269b82659c699d01a944f67e124bebb5b9131bfd63408f11f9dfddcfb52ab37e1f20b425b5cd123dcf237a686b289fcf06bee548051196e7166ea8c518e5b9439c93b2c43ba5fc9d2c8dcf6916a3a6a49c653efc39b1cdb1b6e3417b1f23316aeaaf8bda6c0da2ed657e079804fbf4fe254c1702bd8e2c744ca02a785f1745d10c719dd133d724bed31aa0db05b73dd00f2fe5ce85a04a487edc5dc7f21843f498a795c62ab83119c025d962d27d9a8588bbbc0b930e8365470874f2333823ebaaa3272da429da2b7926932a32f1daaabb7df1cfb6b98a76a52425de8287f105dca46f5560a60ae98d02cafac3457a83181986d4ea0ef3c83b4a31c043c6494c68da927c51d6dabcf0fd2b52907fa8a9be5f2ee2b16d4a7605d09e7d9e0c7858748e3101364e8f34a840e2c2bd146cd158bb6cb0bc8dbdc1733819355851d6308b89c546484e520d924911e4fa2f2fa3766762d212fb1e9bd0dd0d9db241b8445509b553d8659f09b95759c80704742675fdb5e5890410f2c0e8a8ef1a0f563eaa1664898f6eb5d3f77fd4fb22e308bfe541fe91c81fb812fdcfbd2149dfa5905a264f2dee89af99d4ddf0c185439554dfb82aaf82e0707a2e9380897955abc11ca3a4252971d691ece2d992712f44af444682e6f543b0f7a473ac5dfb2e993438d82ac18025268bb1a1c0e4cb4a4c5abcd56d2f557325880d64fb2c47f896d1b7e7072e6fa75dd768394e0cf8a9449037021cbad12479e08f0ae9b799eb628a6e224bbfcca0846e466673fdd466870748bfdad39c6229e77f6af5dcd5186094dc2337c5a3ed37442a92d7aae0a7726342cf4dd10dcf04f996005dd7d5419ff83b80a6ad68d187d8d72e322e86029596c658000c04e7aeaf798c8d5c6e34b4d13042804e86e1a88bf8431658ace3c9c7700371850a203560b8121ec30c05806252885462125875863fcdecb595db7f680babd532387124594c4b098ef6c970decbe1310e24f7f9c4c6db1996c77d30d5c337ab08aeb2303dc28a85902451f059edf5ebbf9e6ddd358f89a09f84e349f03a8a11fc72535f9080a393c9de66807f6efd879cdf0ed5624713ea9f843e9aecdd8e323d3f88a73ee7baa57fa04b68e8bbf1ff1ae448fe2b0eba8c9f372bc944cf45b2abc9159a5e07bf725e72396597b02f91b66f452ed64f7c02ee49ef2d654cc932dedb1f83891558bbd293cacaf3bec640cc999a14c69462a356c408cb998206560c26c3be11a4e537284d239b7680b90482e4e01687851b16e73514107806d19e38cd237edbb9a0f8272e0229b41d2bd47c7cc2a6ec3312fd7108de86c5a2a5d113459e235fa6228203f9925faf051e365164dabc07a9230b3b8268736886b4e7707d81dba46db84202e8050c4c3fbaf5cbc6d1d20513a8687c451181a370f0ff24d8ab626d193086b052544781024a83b7582dca493c1a7b721b841905b01e676b070392a70893223b48c6ead14f7062ee99486632de4af155efcf97e633c53da22365273d2399dab13a3acba4ea86e2067d0fb062309961498725bb788a868834ac0143f9ae2559f21bcaaf95546b6b16e2c02f1b59fb9529b8a1f35e895e1ce4a745b443b4764a6aa15f3ce0278f04c863e4c5f12e2212445ab9d9c2535cdac03935460077b26878d9f5fb8070d3ca341ca1321ede5f3ba8594a21de255cb361a292c651c69f013a132c0286853d62f797c7b966a61d673a3b86c2c014fd4516e5f160fbc7901b3de4c74872f3e3ecdc124a6308fea2c1ee616c82a639f5f2a0b733fe1ea551eb2e65d0fff0400a5a1afedd2dfbd7a159fec1454b8945e542088cf0b7272c8495f23aa292904a6ee7a67dfe5ec4e8d95e2a49363f8a006efd1911a4d510d494f6c599face891405e9f04246f50122a0bd66cafd7e8c9c4563bc6a1d8aae49938a7302703b51907859891ad5ff2219a5920f6b8cbc1a6089b4006998420f7e9a5aa99a0814ac626be4a5e5cc43888ebe46eea0c4bf3c7390bed64cbbc41621e29e251313e3a93003a474aed799a81318376e44b87312e7cf22c3eef6b111f42bb48038053b658361cf28462e8e21253db7320776805cfafb63c2f0af313d810879a0ba042a420eb972bc057fcd2579a4f5699895cfd9610d2cc98f18a4d478ebca67637224740dcce3ff181b1f99fd25372b3a6bd9235f39b7b56322be3276a01a51cef5bd05920efbfc68476578154ff5a57f394bea3e0d27f5b99c981d3f1fd42561b808eb004571c3c3d64da346e8ee5d27d3d51e2097532b0a72f30d62a458d5caf62567917a57ad1b6a30ffef9c73ab4a36928370f85847f4d79c5bb21a281dd117062ee858ea08bd801ea741b160bd68dc67b488d2f13344de86a0a7eced8749ed340eab6e13b7402f57c6a7352db2a8455eefa386dfaf36a7016fdd7473a5c6c79ccde111f0a8127f697e1fb1a843fb4deaf981ce0df485e44f4658292cadf480a3ed499f61ed5c180e90b2aff1d821859b22fa1d75ee2779c55b473e8e31a0c5e1b0867552e0e8c92590c8908848a1d0751ad47c09a159be22e275597ed35fc1e7b54582e7918437f49179f4a65a312317ccdbb4340ea226e8b669b2cdc08d08803b8d778015d1f364c8715fbbc772bb859ec7bf6f8d0df1bc44e92b1a0b0935cbf4f53d1fe628de8ad8a2ee4267f8cd846976046cab4ab3bc16370bc0676f4ec3bf08e3f501cea1cf9b04c86cf1e2f581373035ade611d7f5a794ca4583e53aa4062ff01440908511771e2740f74806ea4815d9325b15a899d10bba1f30d48f2968106e56d5f6989cf767f5b97f0ca4060cc0ac26827679a8f72f8a3bb89d8571bbbb7d75d47bfa0ea734879a60d2ef25cb5d28a20b2789f725ba716067e290ef49c6819459d3666629bc4df416c77e9832f6ac75c968ca7430d7a5a64543b606c6bcc45baac345d1a98b2f7a431eb59441e392c9e6ca94c891eb1861d7db4ffbcac352a44a8996d5b03df8415d533ecab2f7331441d54ae9351324dffd5d7146305aefb5bfbc456fae630aaff18165c3f745e9c99ec98b4e6feed1d30baa636c338f4f7d0bdd4e2c54bd4c7e146babb7ecb27079a3affc18d0b0b0c8a113f696a4db11e42e87a2bb24a87060e858e59d8896b020aea84d129c8ad24a6d127f7695eee8748f66c29f3664348e4e9d04177bbddfff465276d1ee778d343287e577e4936a09c24402ec890d2c8d55b882431229e3df9644c8385cf13dc20be159e820676ab630af20982ec617ce4d97bf46c01d2ea658c6e87317aa41b1bc9c6352a79fbfccb9deab1e493debc8ff8622a9d3c197919aee31b6e1c979a59b7c4f480a90113737ed3c9cf58a0d3fbd180b7ba54333fa1edbfa7200f6c7b430f0bb4b2460a615828c07c1b08d0e9f41fcdf6c050d82c3380019cec778b7be4f65ffd2c886b81e622519bda74c8b11bab74af915852dec09488d658a10a3be62bde8f27bc33b8ceb580112658e3c0782b83ba0f551e7ec21de8245b46380f62f8db16b83f67cfcefd93368422a34fa0054e662c40e056202e7cde6fb259760eabd16f0c3ed58dc655fd85ff540a653406a3805b4bf8f852d2ccf05c47986b3ff56e54f1cc5ab67a92d9ae54def0d79edc4d57186571651fd2d899fbe74f94d4e013684230eba04085f8cbc0bb4637edfa33c1a3aeda46b7768e17b793f352c401d011aa0841aa796ae56c002c9275af2684af9d45f8d6188e62549565bdccfc5bee793cb9151563cb6c028e4a1889f310388819baa47a8361e1d6210f74072e026abfe0a3ad5bd029a2bb2692a092f8b5a28a0a9e90f586162986478e3abea4cddf1ecdc1227c842bc5d69362797d9e6e9ecebab7ba9aa47bc7d4d5f4be8cdf9ce177417075e85aad0fdd144e2fe3aae9ade2b0d69588669bb08798f0fbc889ffe9a63921dda5331cea8aa3f4e0765d4539a5c3fb247f970ebbced7ed6256a85922bf988b7a0925ef6548fd81d7529b40030482297f04eec79d30a75f0be9c2e62922d2717f007e4ecdebbaa0d171d03e2441e37b43e5062ffa02930e9e5e3a6778b6665474671f2182557781e6ac080e41f5479abe6a967db4bd88668686ef83db165da98a37d61a9b3fbbb70460fc0ee7a14a800b43ae40724a45e081318a1399ec11a118b2afa2819d522b7d2a001ffa28425a9270ff6188b85d21ae2c0a411baaba4e6a1979d0d31e04d54dc808307a3ca6a2a1883d8831c586d874b42cada8351a422e2097adcb2e152bd246e6cbbd1ad4c385407f0166975afe36d492fa0d709da4b4000fc095e62db3ee17b714a1015a38a9c632f316b83b9730c02b3d907a27852eb1ac78d0ec88e4ae097f9db688327e991b4e96d9736ddc0f4d9583ea2e8e76829e15d572bc0854c125aab0f3442845dc09ce55d23899326fd8182322d67e9031fc3be203831f1ffad4a50098aee20d28779c1b68fc2931640a9994d9c346d643ab674e0cae5435af873ce38aa1a0c088851890fd05941cf1eaae996d7dc6918464c4a529c8f0589d204fa0784401d414140878e0ce9cb5267661a761cd60c7d10367026651944952c0104125784ce08533afd1f0350b5ec4e75d86bc11c9dcba5666bb110116004d6c7f7be55782723269f6656e75fdbbb0b6f06bbd706ad25105ca24d4e05768e8e6b4bcf26425e98d5997322787a5e1febacc6528a0afd6db7b63f5918e902eea55bdeb2b319fa07966384e5d527b5d0d50902b69a42090ce45404c1808248bccce933d07b0b8ba9078046aa1041abfd940fd89ee950fcaa31b4cd15f0c1c2ed77550543d8733502863c3057a0d47ee1c0e147479ec67119ddca55c8a790ff6f5e7f3fdbe11687116c1a72b37f34e7f78602e8ef802b1f87414f2d5cfd7b83c9273311000a60b6ed5875099a06e7733a3aad7a2548ac2efd3f3b6888c5b4b49047e9a793d674153812eb1dcf6ea0bf22fca9b389ac787782d9454ccd9fe60e6b2b738a792bfe28966736f0da6fd02ec5fdc6e2af0f1908cac239508651b2bb8842b7670374478187b6fa4f9df70a3ed355c158a1ee677190cd477d93d17b334d193b564f7e96254d193a97389caff103db0f774ef1bc8015878eff18355d45946ca9fba0bbfe33d8aab9af9d70b0a92c530cbc1050da47e5b864fb2092435828543110da5cc0d878d5da1e316e76f5f6ac49c6f59ce489e74ef5725eda2062496b70f65d98d314fa8eeb209858391eaaef87a9a5986a5279497c2034f0e69b46a11e1945c73254a52eae324a2412d0032335bd487b3bb2f01a4db4833d8cb65604ebce38f00821cf1c2587d93ce18dd6fe865db62859d31eebd173310c369e2a182b1ffeeb5d96abddf6b25677333a67e80e48bd0cb8d8394bdb6d6eebf0041b7f3d9e7b9cd76228954575122f502dad4011f9109e2100dbe367ebb097644619703da927c526adf5a11e022fe4e9b10083b915ad8c5d20d3f60ebb8318e323bc8e2eec933108a8058c66cecae02a2d23ade581c8e5ea583c9a531f1cc9b51bd831a70fa11d2037f1601c8b356632d3047fb7cc4923ad3e5a5d200bbfeafdb3155e89668f168e3466a8640e83252c4a234d04029dc5633742cab37fa2ca5cb4101014a47c18c6f2d73691c4e4d4a791be7447556a74ed72e9cd600c8c9f0c67e06ec6e756aaa10aa7e7f6c06044487de606260d5f906aaf54df82932688b4ece368318a84248548edcf548455c037d21a27985da09ab1274b5594f62b19b15e3917514b0742b2367f49abb2b23cc6c9c328a5aa97c9267f32c663148383af3067927b4c5692d2488f41f2606497a131c7b951614ea8b3fdd5a32b97cdb682b208a7adfaba2097d74de7de64df9cdd1da9f09e47e7322ec0b3d197ad38df54a629e79d9d1e7dc8a447034299cef28b5e6171924ce3098f263879a20181de12a3e15a63da2838029cf15f46785c01b9f53b3aea4186134fc886ddd7237e14721146f4d79ffd281dc74b1e386c9f4e8ce94eb99f4618f57a29b22809eca2d574425f81fd0acfedb8d3a5c7e8443f811aad4cb94c50cb8d407698113a8ea3aaa46c3e86c2685be8765fe03f224990e8c13b84e8aaf633dc0fcb9988d9e4bfb5a3628701fbffbba26bc74c4831697db3b60a61c17f33cb607037de8e7d63203ac9ebd79b2ca6a396f425a8f94d3d5d307ba8f733521b9da9ae1051e6a36eb477421fd3483b0dce759e55b4580516855c26c01943df6c0ee716157dc1c62499052c50090810cc103b15545d35b31e4f239be6558e653f712a9cb80397350443c5fe6f508165a29b73f3a0138460afe9b8243ecfb4840428137d50c78459a0705e8c1a4b5fd491bfedab3a7c49e64cbbc30cd00053c36e9d560b3799a5923a424b7b87991400ff2d034c9e9de4e47fecf95bd2334c5cf904478ca1952e95d50e09fbba11829b726c0e0c7ed2a9b4ba913137ecc8459b54e0533cc9c8ace9d9395eea3386ef6980342bfe170f269ce7bed65b54e024e4960e2a5fa5325b44f1b187015434e96f6b316c0476b7ac2312653464761030e8292b921e3f9034d0beb57e06836fa70586dc87d19abf8c966ea76e49b87c6149645760f7e2a7d70bf55a335a96c59d7972f99f0398fe13c667cac645f4a8c0926174cbce37e7ceecc73d8a444565b617517f1847f2286b9fd4ef4ad34ad28185b596dcaddcd0fa96c3a88bd92e2fd0c3be14ef04f76eb73b5925916948d7dea9235b4ef6c0b5e1102830932652268b36dee8b9c9aaf404dcd2b79fec018eb46bead6dc91e02e88b0e71fa22f82f1a598272cf58729ce69a6b851a7936282b85a8a5bed855f140e2a744e6228c49928d7700243ce97949caf42bf0bd87ce55b8f97cc238a79557e8fc402cf0f9fade5ecb69f9009fcce78705f77daa3e83260667d26c414ba0b25215d3e7aa232f6bd73a374ad97229a165d1055d3ae2f6011fdfb3eaac73d8f0298a776f5ea878abf692eef0da8db3d0b073bf8b93aedf2ee86f0a403efc506e9f70bba247e63d630eb1143d69241876032f269c99c51694eb973d33d4bc4cd4e54eb3f5cecc71b948fd379ec2494e4ceb12b8cc9b4019eac324b138421b94f970ad8d32f242ba895ac8af87f6a068f3497a5ac8b927481bc7f2a086b42f81eba9f455f9ffb815603d126538998b6010a9430b039ec8d0709b07628ce49338a0039158d07c13a2631dce700d1de6dcf8fbcbe9fd2cc63bebd1f499a96ce8eee47da3a8ed28231df26a846b48773d20781486eaa405233d8a08dfc910c410a918c810ad7ab5bd5b385f92341e508763dfabcf1ca8f4f2e059331c6f5b8eb7584847ea4e623ccaf98c0ab1c4d933f417ecb22c5d1d7479a03153276f1d1c69a73c2abcb283fdd1f59b7a165382fb02b43cb96fe8416b14a5f56c9fd85110fe6c388d10b7ee655eb8c29a8efed308f5450d62ceb24e35bc3a54cedf2c8e4964726feee2188fa10180a0394470e81d86beab13a7592135c83413940ceffd245dfd4031ee39039695c3d155ee6212711b817ba95d6e943609f4d6a64a8b8c258c2dfbc4809ab8cb792d3721c382dab3d422df2531976364c3dc490ef75fb896ad19cf1d60046d9d9388c61466960ed304758e41bc38de4eeb442fd3dc81eb54c1b29d9a09481b265c60e808f1c533c7b4435654ef31f1f6b1c88217f2e0df23115ccb83b3dc986ef297cc204bbbc8138973eb088fceb3f525fba3ed02cd4d874ba6f63fb3a1410eb7febc7edf0009010ec99a80f3d290e4461cba62bbfabb3b1bf6c5084fc6b0d28fb3b2d1ebb72c652a4a5ae93beab36e05f3f83855278b6caeb76aec0bce1d15ef42ae8f3b5568bbcce73a655749b3d34feed0285ba1737a6f855daf3139450c09e9a0114e4275a40ccc7ed808e823d9a28f4d836b78a22a58062ff405a13d1a728a54aa76bbc5deb39f2d119956c2ab6028fd2b017ad1d29af58f734294f96c50c131981d72cf3bccac479cdb69ab3a1fe30399014901ce08dfc5b951037d7f344427d4c1a10554e45f177c0707f7d440b4056fa169000584cb2dff459e86460ad77174bcfd805dee045ff003d3e8ba5a9b879a72eb2af89617dbd071f789738ccec592640f16ef11c623dd46a348a157c7da9f0c2843c1567d186c81313803e585cbcfa58fde22a0275f585ea28393bf0c522e008cc90f4caaa7299180eae65d2c72930c1d12c006870bd1f350eba26054db4405b5ad3d1f8b71d8455f2dc2640d3c31ba423588f01b48571798886b898340cdb95f31b86039f5758d14687554da8f7606c71e3a45c6444ea5d3b09985b23f1fd8b51ec35e8fe17a3bedb60e38da8df16294a17ba3007e6cbf546199f034f0ab8c76c42fad5c9fcd7efe7a59527e7308f259ce0b8d72d130c2f18ec6ca08336597c2e235fce01c1f8f1da88d519a627ac1239895422ea13780309c887895f81ff9175d402166ace37b853d7ca7859f484352d35f345edf4c931e373533034424cf8c7dc69206911ea6040cbfa50b00298ded2b29e9e8dacc1919559a37004f3f22f8340cbb1bcbe1a3237055848e0c4c96d602dbdda3099a284c9a1db2de75346aacec9598986affb2d03f94772de624007918c172325c5547ca815408451f7df2e023b91340e2de86f6c6c58586c6a585968c306fa44e7e842ceebe42379394e7483c37cae398d20e76aff812bb0774c6560d0c2e1da6d3a2bca3cfb26daf606b0aa4d7feaed1f3f11565470402ca7aa076a5916848d2b0c2c6db466e5384755b240d82504c524bd3789b7300ebf45ab1fe1b0e526f8eb16fbdb5307f48a4ae37a8bb61239d1f0eb2c3c472ee414da584b824d3c78e07ae39bb78c9fc34466f8469d688307c6d405dfd2410770dcee74b17e1c9408d02504e25843117bd9a19096b9717bf7fdf7de7be39bf7bc7dfffdb7df7be3bdefdef3feedf7df7ddb9b7c94309ea5acc4e4df1c1827bf89bbaabfa3198717f9ce9a38607cb7b759ab80e7b556a094cc5ae26f395b0971794fd47dc4ce118b94a286a9e108c4258a3abb93bf7e06261689672d099c40f0250ac4f4756108d368b1d363064ca4596b8bf53c1091370a3625aa8c9debcc0429beeac064fec5bfe066acacdf0d44c65fed3414fce5190da7ea44f4d94c8daf136504133a636e6935c4c17a7906eebc2b7fedc181061ac88d78f6cd8a2b58b9cf525aaaecf16903aa37f881313c793ffcc55ab74f3517164d33ac7bb105130a74e6af719d2c4eb41d95f224681131b881ba44039d385a95dc1e42107ae4965a85531ae8b488248c98d3861bc861c8d8a13ea89e0d6869e03dd344cd22ee9c066e64f0ac1e5244839e29f5c8f2a3c60a35fafe79e7f2a0acbeae784dcdd6b0534b2401acd4e74604820c86591e54de90580ffbe9326fca0ecb3efaca37311254687a9cf7479af2e758fc29d9085390d947dae29d62eb9da1002c65c864d3c9cbb82b034d4df28aa6b0f94d1a7407c93563b3244db4c52be15e92f2559a6fa57e0e48afa171a72836818ce90a4e409740574a9c89b67e17dce06dbb0f6fffbac6e2d955a04df5f1fc604a322e503f15f1b644a3e2d911ef41fa0a0be0f7544c4225840c97c58cf62c0e7296f34ac577068b09f3b0166648f247c41a7f67f42a390ed944f2ae7eb8bd9c8aa65063cf6c498e30da823a7b01fdda18e3bf7f4c4b294c3651b59852070a0b4b3f49a03758ac2537e01afc39ca3374f09a0d1d137b303e092abe448272f535b85864cd9760df65f1d80be7694a931210f93387d0497cae9bd341094514f14f87b2297fc0a082d6fb55a454885dae8f5bbb773a66b110b0a7a2e2f47bae235125d7f899401530467ad0e8ec7928a05562fac493ed5a4cacca3a22191fb425059c9c931b509f0f1c5ea1622b51332696af31c01bd21fa2ba83c6814a435e606456ee6a62acc1f7f32b0f90252f9a3c46d985a79da6c52f36269ec8f88b4573c2c9010efe6318c1db6e2c1ad05c6c75b298a5cc0ad9a938415e35f9612c247c51b4eba7647326c58dad3bc1d006726359a109b7b1d8a349ea0258145377cd00e0cbe98c260d2c143823d85e401f8f23be89476cedf60b68c5cc1445dd7e54cc5c7c4fd4eff67e2d18c85550368ae722fded4d4f824328772489884c225d7db0367097fc490498a23730ac6a18bdd165176123201a8e4a95e071fee5c85982dd5ba26620020937f32c4c20323e45d00a2d52c48ec7e4f0d23080735b29ab7f147529d7158b3db9c06bc2a80930573282a4e891190174e597c22ebcf40229a68d38380b7f4903b10852250d4081461bdf7d231ffb460e0240b7fc06451ac1deb211f5a05df0f4d4acadf6c274c95b1b1d44d582539ef8870244da014862cee3a998869cbdad0a3474718c4f05e5a6e0710dfb045d18018467b87d0952025b0e0aab78c0dcad587469d85587fcf49a8c2774fc17f8dcb0bf939d18b6343eb2434e098a415e4d29a6f917819cc293424d843c6b2aed9244a1f3a29e72d177140afa03d831b89b63cd9c2308c92eb44b003a1e6050f5576b0e834a5aa6b662d112dfcacdd33cc5f08a34385a7b3eae81024e66f0f6b6d8d9dd0a78ed64ca6481e01e0447ab93450f1fe8a1d2844e7e24b2fad5dd00c4ec741fe93ba5f5d3ee4dda5e31086713a8cc74fb6949290c495dc15f441c5d9eea41ab9048a309244d1bd4b766cb5d2e5b4e4b5141978099bcbaf2c011881560c5203bcce381ff2a4ca0389aa344c6f76afff96760b84f7010b8db1556110fa73367e047c3a9a6f2d81934a581d3c82659902279f4192c8bc48d459fa1da9c1037f4d5ec504f1a80e22e8f04a72e5848bb01b7cf7978795ec36b52170dab1ba6d55d19ea01e2aac8a7dd2da549dac30d6b6b892a2c5e7a9623e451a659dc5c670dd1952633b861420e9da4b0a50d25eafe0bf1cb5ada2969e271c76ea52dc22d7433db4de73905984f99ef1d6895068b0e3e464179a207568e0f6474d45e8922996003f9fe2c5237f38712d65a062fb69fabad9839f0cc9d79fdf04ca86b644f905e8c5d16a8fa00f5e7b8b3197f5629fc1755d54decb44ac14b64485956c88ebf2bf0e2914627a9505ea5947296bbfbfd4a4d32947819d20ba34a055a956a1c85c334ea4d9321d40feed6c7867e40b8b783b45b5bde9fa8d67d2daf7d40c300a3a5f0d36d65e0adf5b336dc4be9029a430fe1bf0174717df526445900cb094fff80f9dadbe3d429b59a3673e4440f6daff05c6520aafdf997a8dda423f948b87b43a4fa328e1578dc7c7611819fafdfb8071e36924210f2667a651b136899d00f2af4f4fe3d0b34bda6a61838251ff7ac7d19c333f2414c56eacfac5e52a5583d7fb5dc2a88edbb3647e6a4ed757909e9248db0c1909935eec8664248cf3ef7c77e79617126150dbcd53032f5a17659bc61e22b5e4dc7e4ec210225411843b220a1a186093eba22834727bccaa48e8dc64c95da74142ecee5927420201370f5833697360de49d2a48e32cc26040fe4659a75fda1e04a1935fecf4fc8f79706e2050b288686b88ff98942f87b8cafb23de0757d6feaa27fa09b724339e6add3609d221a5f32e46584d09446002b6af3f5af7a06dc155cf71d13c9349ad2ba9b748a9f771d873cccc1335b571af4b5684b38c2be1124336eefa163dcd8ac21b648baa82ce509026b37fcea1a95de8b2d730e981981490d1f354c13fde5849b4f20c1f3fd4089fc6f794f08bc5d5457e11ff1e1471da52422d0b1b788189f0fc9ea9014cf88290113b88687d49c520b9b4e7e77d875261cf8141db33f042e0e8d0083e1f25a189256a11fce1301f08217e6a1fdd218e822c309da8c233f04e139d9fb47c3022e5868298bc00989118dd48c062f0f7e78ad8ed1d125d8141452a5ec41db687666b13e7400c6185099d0922de30c414f9cc1f7521cf18184420ff9fe2bdfc98f36efa70fb100a343b839da50ffc6cb678f812c12f2fab12f82f70743bf87f22c29d69b58c268169a6c2205433665571eed611ff520f19a1f6e0b279910380c4082cb7a0248d0f092e0f6d30e6cb99668d4124af63e31699f5449106c0ce221dae7aaac1f6834136c5a76f126564eef4a4d8d4ee74d5a68768203c5bb01f518f6e069735066eaa606b2055996c49061c63f8d220d487033de384ac13ac65122b9592ee2bb17b237c049a5fe04e956685d5cba0769b1e2f3d0069e18304e5f2c295988e28c42c60026bfed07f5cf660d6c1c65ed160645451b76bf8d098a3b332f2f9579dc9937ffba8dda86c5823ec5f903cea840550dc2683e3d920a23074a89f37961c3c13cfbc586ade19c0d75290b44a1937ac7aa14e17d91f6297ab1d0b5e08a6aa11b1c7b1ff12debeae3dcc0fa6906a6720ddee4607d020d64df15cd4571d9564a32c3a32d0fefd555341440dc2a702ff2c313d12b2ccc8c932e5d2a691f0982f8183ea34fec4a7f1c009545b9aa051821135b4739e837c3d8abf9e48ef6a53a02915112a166534b58509bea7f2707b771920c7255cf984730073128a28a72041f7d80442489dea42eeb53ccbf6635869494dedef4b3fe4f5e5d90889100933b0de886720d93aedd5638f17d946f8d5d65cb5d8ef4a81b575b70d6741c0aefd558307b92701c4160a40dd3778fd02af82808eda0c2a5fb44d26f857c022aac777fc8a2b8b385979c38020c0d43e613cb43e16fa189cc8b30eb256a58675431da6d44626a3fe6e8064601c71b34e7b37a81ffe49faead33010fc1d986cc24aef2e5722c2d4769066d36b3b0be1d1a0425a6270d8f48816bb02c801e07320010363ff6acf02efeafcb9eeb18e32db13e3906145805a60d0fe181a33cd7d86faa63d5bba0bb26579e58527459331ee6128b8d5bc7e2758d230e18566f00132824757333e3403f60a38f9cd09f6498e4440b9273053019a1ac34dc684f7fe48c372aba03712adda85fe5077fa7aa03a4375be06689f684bbed0e56e3cd6da5b3b81de98c200616097e9485ced4843f105b433cb073e1fa39108f9492b4d8a59c0af981548478755452269037f5aa88d731e2081fcd9c14558a2912f8d51584e3fc9843b1bf0b6be99d8f1ae87771b59fea874539cadd48aa1e6c1908719caa9549da00f167d633b6ba5c29ecc56637705f29b00ffb8587e4ddad6fe4eb4850fd16873a238599af6fbec3f2d8929eb6435c3fbbee4caed452092df5b6acf247b2fe2f41e2965bd8a9f5643e9c643e09af8e3949a2e5440c5697592b1bd3540838418ef0f4088347191ea67a3439924a3d2f629ad1fd60657aaa0fffbe240b3c804b0f69c02b105a64aea0e84fa31adc4d8aa40f522d7a1ee2aa294d42356ee0791b600019a2ffa84a48c185a52fd835cf17f1592372bad62c0c3d27c378acd76b0eb415992aee98898ab2294ee45c8490205cbe459dc35fc5507b3110ae07f2fd44abb81f3d549f3a7056e619915071488752a98ec0384c34855d524446834445bd410a9d96d7481c9adeaa78f0366602600871bd69137f08b4da32c17844f4e77614390d02abef23585410cd82cae59cbd494033e9f828e096c7da198582986d5721156bcaea7ede247a4c6c69956b1cd043a364ea4c7f28937c42a6205a21bd9cd4a878b009c829ad8990398257be01e069e6c270cf2bfdf9f02d524cc0cc075cadcd72579c3b0b284a74da3f8d06a50cbf602667e2bf1465ae1b72061619265f4a0362ff0098d6889e4ecc43bb8acb91a6d00b79fa519b33e600a0a352b7bb05cd607ab0a17e614b9d7921405a4b5fea1fe4c9f3df9623c90ee2adef3e62254fedac3fe100ec500d950238d341ebbac2dd1c4110d6cd3707968a0e2059e4ccd1e782d6e5f70cd53461b56ca842fcb9147ebf57ddde475a81b7bc0e235bc7e569b09b1c03b26d8185b6cce0c29bed64d32c6e9ded281358b63adb7d0148c6dbdd470a8a83d9a89b212f895077092bed0d55b723f43a1a2f7a8d00cb1228677a9fd798d2a8c99d3162f308ceef4b42b628f39adb0bce3f6edcab2a3add94dde087518a8bd8cb0c2fe8cc42115e48ba4c620150800936832bf14816d0003af474667e568c8698586c940b4d96aa7ea3b7ff0f2152dd915c430b6d92afcb373ef7c4820b6669f4984c42352c8b61f2450ab7dc922455e7822a47b0f11c0a2a165102c13c678e1489724d633debe3f0354bbbb6903143cbb15ca2dc7c660881b97e5bdcbae0bd4cf4c251b7c2412b56d0994923e163c731f32e22d303558ba966636cef82e0b6564be0c793d0d4b4cf78118ff00c2a17fd2a28172bc88409fe959101b9075b49d34a7b3b4123c92dabe61cc86e4d273a02fdb41735c7982f21646d9ca5cfafaaf993fd1a9a4f7ecb1736dc6c114d76674b70ed6e7b4fe9ef1f10b6e4637197a19468f9c101b61629b369694581736a312015adbf2eec35c6df1858111b230e6fb9c7cec8244878e1f98d162082ec3479633461f3b0ca29d6b435a633b667f7b6bfb06943caab7d216f871615131d687df9229480d636a890a9a01ffab0ce4818967e1c5541ff6bdc2389b89d6fddcc38574c1538548d6b41fa6d414875d7addb9e3fcfd9734304b053ea76a2fd63456969e5dfccf9ca64bf8b9033b22be3c12d46d9a62cf88c0b8a8975179e0374a82232606a9709b5c37291a0df0e373b11aabc26479ed63e23170477e8b81d34209ad8e5b458faf35ebe6dde877e318953de5faa094dd0f44ced33208b7ae5e2671ce06ab830432338d9243c30fcc46010469b00ef82c185dc57be20219a42948a2f4a1a337c76d05aadea57dff215085aa8b4b6cc6f3278a9eb8ecebef123fe10c62c230849c83161a7dc3c409d1cf0884c00e027e790e02491d93530975578d6d03e9d32321f056e8b736983db1e5f3304c6cb5440f98d07ccc24ac2c8de7670889438c841aa4f7241ac2495b25cd1ea61c466f035c77b7a73531f3ffc9bde5cb1e81e269720b41bfc49ab81586a6e0930a6605ec3045ab7122eac1ca9b135a05df210c7a97a69279354bd4aff30d5f34201d47c368c5f4495333084710ecc7ce110ebb168af5a2cc41a309c8c8e1e787b0a1676fc5e1090903ee25fdaf92f04a0bfa11cd0e84b66a041d6be337b51c046e14e5c8aadbb3b489dd70d6ac82760b539d2e5ddbd174816c9e3378e61859fd70d73ff12d1231a5f9bda13f49de80ada4feb94458bdfdf645a9d7869433e546980a526a0dbbd9eabdc85ca2f033443f942e99b061a35c94a64a5079c5f8275e3cc2370a5f5887de8d9746881abe8792d705184f13acee6d7a15c2198dbd172bdee68305e16463714c7694b5395d863c0f51652cabae272ea2d2b003c2d064787953d76158f0f05a56e5a7ba74ee131285190eefcc1622b73a92356dd307cdaa71ad57338c8edb3dd7034f29674b846a12ca25de31f90a930318acf5a87ba08e778e69d4c710ab9d943a02cfafe2bf114e126e6e8b442979347272e2cbba9bdc41af44c836334c924c9b7c703d123758ef4e526984d4059494c101360436cf34c929565c86fddb3d192839a1bcef4b87a78f871e6bb43889d06bd1d50068880968d00aa4d8172f108ddb031fd342d5f649cee1f9484a55de97bb87e5a3a854a5fd23cee17dca8b2adb973c87f1392955b95fee0f13f8b0af56bd03e1afcdf31999a0f59d99f4bb31fc11eb22ea9c3498ccfb9fd2b1ea1dd937c9621fb5f8d767b0227915841a8bc077612f8eb8b2f190cf4829dca3ae001b18a1f1a00fed3faccfaf32d5ee2f3986ed132e5675bfe618ae8fa880eafdb57ff83eb13215ee237986ebc35b58f57e67deb0fafc14503dfba7df62d8763f5cfeada47d41713c2ab9a3f92faa9c2e76bb29cf4d58733c4999f440a62303d15541db60a7287ace8fce8bdefe09d0b63bc72ca4c26cdf22a64ad742b2800baf7c421db31c6300c6792215f7004dec035481549ed337b6efe61721c34bb1ee86b61b46c2ffa396d9e3309616421112fec8fc7822f75d9cf01b689b4df36b910b4f554dccb1072b7473c9430cd41b5e9984d9becef0ab12becfef7083c59dcadfb0a1ee35b635628082635701dfc03bfa506d3137cae116e3e5903754048694a24a26ef870e01faabb2ca6e2a53a6724b45593b9281f88815f28db47e7fbeed1854cf066a6baf0eb2a0751c3c3f2cd0f88214cb5f214156c3cbf2c15553e4ce05fdf82fb606889082af420d8d1034a542a93f0245f05cd52e86482cd0472b0be9335e30e317a97d154a3977ec7e717f3b1ddb82f5629aa798c475ca7784c9af613da41b040f2a7c0724f8ebad80d62dac3de96fa59f5bf747e5ad283cb0d16ed351808224d04458dcebbe59f3af40eac112cb33d1fe5581d8261d9bf7c436712ca7c2014d30135e87789f55c8f254b6052e9605bb12551048ab8a3ac07fa6f4ae2a9a09cc04263ea351d24d6016fe022ed04bf0023fed5e3214c07cfb5b4ad62d926b2e242deda6440f6e9aef3f1e0b7bf171ceeda6619acff57788167e44000f5576721b1041b6d6ee9d02db143c12a41157783b9fce29c703636e4e2f9c5c84143e13bc20ab124e24784050237822bc00e0c78f530be543a6478b47093224c49800e37a59b154ab4fa54aa54e5eabb2c0b0c081a6d5cbea05d5835564f5723a4146a987221a568d0a86d58392f1e1a164543294eec4e3e442b1f143134daa1edfea05f5e27142cf029a60a2c888a8578f1ebe21415250509e2a49069a943c0972fdf0a160504f6c7ad0f4021f5ce0d2839782b95258d2c3872ae174437a333ea91744d0a452a5709425041f3f409c62e821004d24bc4891014106042c4221902c0cc383a624f92586d911add5cb89470a8712c0103db0503d5427783e787a8081142f2e8c200b6b0e257a7c31a925aed5119493b7a10b1f0090aa81118269414505e5c2ea880f128822061fb2204284564e161ee193018cd11453d610f0038f1d1f5e14134e48562414e90166754405c3ea71323214c3e90418211e34c1ac867824b19a04612204f3d24426494c2281a61150af22302a180078a8606298d0dc6045130cab090a34219a52a756ca53cda082492151b9e0f31353be08613551c1ac48e011f4b202aae86504104c68a104115c60bde004a9851e6058e08a87078f91a1a3950c6a55420a762af2228312e24b562f2b18144eca09aa6789970a729a498990f3c20a4f47982001c28211e1c5480f404531435835a815502ba8b26872a1072a28f5420a8597155026f8d0399de0431388e39d6aa71bd4298503cd0d06b080ad0c58001731ba604016540c80071d9830aef6e1ec062a354c890225e9e8890c4d603d3c352db0a082a3ca20a34b1c3480010c3e2cd09440c26a8b073a708515333081017642083231aa31c2980017585c41c514516c79024a8d101c10014d282575a94307b41ae2b0c10564d6750411b4e683046685851145187929011b0ac00276a4c80820c4c0b0562604890004a8bc685424c3901013d8101d9b1fae1e3c500928800002f0c30d221560c002b2e0414a142345808080fa00971c44f15152e465a5f222880934408a240c20040e50242001441815f9201122e3d171fac137074a0eb7c107070a06df0a526e7826f848f08de04504326b7c1ff8d4f8c2f8bc3831e0c3a1ac782b7829b04e4899c083846f842f040f041700523f3e97d742f5f8625230df0b8bf5ad56a90fe59d3c4fd5305d428b8421ba47dacc955e12e7451bd25a7a2712664d420ea60ee40e203f6801a141f7cc6d95fe4de736ae2489d72849e295da9e06448cee56750bc8045a401ad00232440b480fdd3d9b79cdba8dd7b8bf4871a58ea967ea487677de790b7d52cf3b5a1510046d104315919ad44f0b88ccfd9c278973336ed4e96baf25021a227c5169c62fba675b1dbf15dda6e6c35b5a2c42122234b5583f4fc722ccc4fa2d1140114cf05bb6d51fbd66fd433a62bfb67667c6feb63aef0b082f41bebcf9f1386bab325260750add2936727ee4eec6e9be91c1111df7f17bcbd08523477a8e183952e4089123438ef01cd939a27324e7c8919e9e1e233d457a88f40ce9e1e9d9e9d1e9c9e93962a4c7881123458c103132c4088f911d233a46728c1c29d253c4489122458814195284a7c84e119d2239458e10e921628448112244880c21c2436487880e911c224786f40c3132a4c8102243860ce119b233446748ce90233c3d3c46788af010e119c2c3c3b3c3a3c393c37364a767c7c84e911d223b437678767676747672768ee8f4e818d129a2434467880e8fce8e8e8e4e8ece919c9e1c2339457288e40cc9e1c9d9c9d1c9c9c9e9a6dd1da45b2018c9959529a74a77bf605e3035353ab80e302f181cdc5f8e83bbbb77f70bdded4277db0801801b0efb828a6e87b500e00300702d00d8d00e837158aefde80e33cb371bbdeeb9f6e30cff2dcc33a764468992eeae110200135a3feae8764c734973c7f46fb9bb5b60a11b06e679baf8b890d6498a61032c9744d7decb4467efb53f3f88e86e10fc316bfd88a1f563892a89cdc743fd9169e6726a9516cda2176ec2a840770ba19223840acd7dbc4a8e0db5926681ee56a15b2e7108ce7792600ed80119babf38be16b481022f05dff815f9789034b1814eed6005dd4b24d1d43e7e96b417c4966e21adee131cdd2d7a3635bb4ce65e8629582b69335f6dd1ccaba599345240165a6cc1c5021860a353c49324719d1a4fe2fe822fe161ef63f11dcd5fa44b54749b3d93b9cf797a303a7437d82d1fac12d36b7b82f23183242e5027a0025d09824e3b4800092738e28b16045140929309d6e019e1092786002336e42446016c4ee0dc10d48588d7a98a0bccbcb1e4b4050a6e563809d114e4e4062c57a801840927728b1a2f010d890e6c40384181410b017444e184cc13322724086851e1821818a163e3a14ece2585385a5648c9f2aae3a412428b02c40181254e94a0836f0614587af2e40a2a825a34f8b8d081801c4a40c5154149def8208045093e9044a5050e48a28d0f012cd8f2803694482107b6c6d7c31222d490031c669254d081afd6014f030121a0c254c518df132d75e460041cd0c45810c6a71381362e90a414030ba4f1c527020fca68e24eb1041454b6f0eaa80201191872822343b04085b782140841510126b6c85ae08467c6015cf882ca0f94d8e20024bc2dce209244c6194954513284774410360471840d08202303593cdd174a6f44b14301105d0e1e151cbc30e1f0850e3820b2f723456e0a269a884327c3e80931816703b104920032103d1e4710d19715416030a51b07635439c1073f574e6842a9d9f8c10c56014ad0e24fd05d812e52f03d439cc10516412d450d422974d618830e21495a0b1446425a18a1091a26433a270210f450a1450c9a6eaf4e2262879d3470900115b2bb470c25d2c0018d55080ee846a107188e30cd81c44b96ee13174c3320630b9a4c1738e058a38a5697349c58524717077c2002234c610109ffd24117268608a3032ff6484b0768d06587305cc0128f7095308337ba847604e00138f8a137d0a38d2e4359acb00213141f118235ba806f50698de1c20b48541de802021330b0a00c2e38f0b0c51877f8c0035db000e575742010c61d2a18004b8e392ee02304dc17778c218120d2004460810035b6b84301732840a68696ac031254dc4184152aa8001460b8000c229cb823abc007ba15489b1d2a80c41d35245104c410464ca0052843dc01031a6174990dc1404e52963b5808004d961a739041881cee8079c003b8c0228b2e4d60643be2c8010be630400a34708316463b44308516b5293c6426e08168c7044eb0c5b8820eb42ab250b1638b017050a4410116d04842c90e20c8404190901242e081093cb10397e58c355400823260401264c751037a6c74b840a40105486207913886c02c31820b5bb860881d41b604a1860794b880123a78d9d124c8407080051f6066ac50c70b8a484206b6a7c9124626d4c1810cb49028424447142320d4e1850f497eecaa40200d29327524b1800e0c19b1842929d70555070e32708246a7820d84e0401d75cc27ae6c41041756a0f1001dd421148219a8d4e84069e2071ad4f122009714b82882873a92bc5187cb02518c8823861b814c1b74f0a08bce8b3a2c10461b466bd0d18616e41d2398d144191be8eebea3037460e00525a8715252021315c0c4c8f0f07a784ebcffa1bd15babd2be552a5ba1cf7715c8e24d34882628d28ce9d4dcde72c96610adae0b2d21c28d6d04a2c131f17fe2d04739d39506c2c494a43310c05c962184a924d8911b9121fb7a9a93f82b592e679fd030c74374eb7565b1a5fb05adbbc22cecef679de8c71c21777f2584b25f540d6461c69e8a0d6a08da636cce80194306a70f03e8863840e40c162845d947a24f909410d27845064052ecaa875d14203ae54111c800d1f6ac850495c0a8ecaf811024e00d8280114d7022108800d4b803978fd15c52085164e56f0f04042181ee8c20463a0da6085408d1dd04085fc068e8f107aec38a9808917f0003e5e03313ac500061fe8c00457285183da5303f1025652f01a65839b5ccc0620b688a3071582c081c540b78e08121d33830cb4a773d38d4296a63ef158d25de4dd2b2edd7dc6f724c795f8f8cce38bef303c8bba7ba65baa249da71d6bb21d15717265c5dd0da55b2911d0e898afa5cd31ba9b4a2b75eba29b898aee9c25baf310dd974a5377a592cadd4dd34addba6968dd24740f75a35ca8537708dd00e876794ea09ab4505ba0ba7018166b69695eb39e4beb36cb9424f1ee6e75b78feeeed1f88243409d1bfc7977a2419f46d0e3387349d62addafd5ca1f8ff4b274b7946e9d8e1a5ff0451bbe98031201736e7a88601a4e4d27278788cea4d1767644233ab522379682f8f3bc21b8bb1953db5f1c6bbb330471de5ac1f99cbde1a0ba63ba99f4d432949cbd42dd0dc3e2f9d6e84c4712fb2ccf9908ddfabcbb7f74ebc3a27b9c4b36f737b5e533d2fa8a74f708ddfa6cdd0d42b7bea46e254c2f567a1f87bf64e398d678fdd193f8d35c1c4b7bc36ed3dd3bdd5eed6e12bae5e9a0bbf105c3bac32f5f8e73c967f73299799d5eadcf6c6ac87975b584c1f258e2479ab9f8e338c3eef6ee8fd6dd40bae515812f98733556777bbdea6bef7dafd3c5afb7cc7f73fddc8f648e7aa6f366b7559af3cc5d5bf3394401babb846eb51b2dcf477787d0adfe81f8239d35c79f7734f79af56a699ebebb36f7397cc14f84ee36a15bbd4477bbbad55f74b7ce8eb5fb4bd6779986939c21be60f84b32ddbca11dc7d9dd3cba3d22ddeda45b5dd3dd32dd6adcd876a78b749c4b95e20b8a36879373359cf0629cbf36c4d9dd796974c4e1e1a9d5864c9d9c223a61cf14738c180973728aec1ca14d233a46881039d2536488388b1409439c30e3f2e274b7aabb532754777f2f074022a6c5686a8af5e9650939626258272339e4b0c3e727dfc17394e7c085d2ca613c872c5afc27b5c38bb3bc83f09f144d0e3bbcd068f11f9a1d54cea2d1e23f275fd1eca0a249d19cbc7d079557635488028a20bc205059b4781e048a468504a8b0a5f5794c0c2ee5a8140d0f27cf494ca352232b8b0f3d7cfea1bc07c4340f38cfcaeaca098542a154be7a41a14ece62b977b272f23c479d4e5f0e563c7f710f95c3d2e7ac7c7296b34eac1c562c9a4fb5c2a96870a92527d6e73815cd95538ae5a5ba8869cffbfcf4794c4ceac53b457333ca55344b9fabdc51335780c4702aef180ccd951c622ffecd5c011253d1e453132fc63ac55e68aca0dca3b992436c4583f3189a2bedcdc3898fd8e71960b50a08f8bc7f6264663e979139a15e5eb2a05ce6e52706664645134468e52adc8ae68adde72a9a2b76278721d63fdfa78271cf6150a82c2c1d62dc43c9b07888990902869f93c3cc64d1e2fd9c3c08187e50289771a5d3cff7a158344d595e5c8bffc0386b264b8c07e13f30eec5d0349db2e8b0c3c961689a4e593e1e35c8cce8b0c3c95f689a4efe390fa5937f30335abc1f187f99d1e2fdbc78cc4c962060f881712dde4f8ca37830093203ca65bc86937b343ac4646149e1e1478646072e9462dc731d60fcf39996cabd1f15a8685de92e31150dee8adde757ec50de3afadce311640694e7ac2394bb8a8918cf9bd1c2c51518b4a0b26841d17071058620b8b802430c77f28e7952bed5cc8b4c8ac57a59ad7c06b77214ca4a4c8ae5decc0a2f30ce52792befd1a3c74c1095d1cbc9656678784ccc8b9595af5ce52fa9192b32aef21eee390f8fb1b2a2f1a1f26c65e5a9988f191c0f97712b34ddc37bf80a5fcd57f3d5f0a051799015cd93afe65bc1ca0ade938f95722b2bc4be550d0ae68898fed040a229d668180009348e80038d036441038937545082c58a399dc6f88aaccee0e1c463398f212fc59249d1a0542af754eeadb888816acf73e29d911af2603ccf83a1697d359e8cb73a61b1049593afd888ad56ce4ab9caf5c3e5f2154c1023b6a2b1226305154ba568acc4cc64e1c24acab9c0b57838f19c35d3311f3c8652ae4ad15859b9f7c56480ac9093a489d26c64ca528413587c3194c7801013c2c7436609991796083229ffac38f1ce889d888658adafc6630da968501feae4f597eae189c1c307423c089d3e161a466c116b348c702266c5071a45c89142d2fadc6bd2b1af0a22b8c4da3d3162ed5d10e103d50d5b0a82f220289a196f66e53de3c48b7919156b6fd40b0f221a1ebe180ae67929efa4e241a4f2f6572aa388acf01064d51fca4375b38c3c3fc9a0e52847cd9cba3b9582a1619d5433ac99d50cee4474f202347d5f8dca83a46a787835caca0adf690a2d3c57414bebe48d4bb90a43a0520d7b7194b3702a5ff94cc7be978e792acfbd1f08a1658ed8ca5970c43ac6f2356228c41a0d24be88652f767219b7b25abd04f1bc95877a89f118d64cca5732303c98ac564156ac99202b57ad6456ac189815ca57a797d3122a46c4f2154d108fc65b79ce62e13c5f79105610168d939ad4e9e4ed2f8f2688951a15b4b4aef8887972bcaab0115bf9ea8c58c73a36003054306e058502e1abf93c9a56cb53cd04f13cc6559ed7c45e1ce5bd380f57f16022d3c3476c0915637946c55226b080008989a17878caeb01bb7dc34366e68a17b3b2a20992a279f197b602a372140c0a8572d40bab061504e51eaa0645f3f2d511029823d6312f945ba9899d3c8887f2134d108fc6494dcc778879be5a79cefa505652982688939a98e71ecd152f969af9685e5f4d9f0600478c26d66808608c588c8f18da5b42c5aef888f98bb7c764b5f2dc0a2b88cabd202a4fd5c458fe920a8272cf573c348d9520281a2735b118ea44f36241cceb858600be88c1628d8696396248501173c51a0d248a885dcfbb6f4e6e25c8a9e6945131cfada8522915946879b12029ef20294fd138a989a1686a62a857dfb47f0ce88fe531acbe189a4605e31ecd29c628e5fd751016cd8a06a7c29d3e8f8e171fa71921958a078fef8a176305e1f1e230eecdb4f398b9e211bd9c7818a17c45b3e49d689ca8bc69ac78312f86d7f781989316386231ce42790b652fe6b90a3db4aca062a7af4628c80bcb617cd5dd2e73c52362d12c35cd8946a883bcc0d0d478f662ad8f0c37d008c0154f8ecf6b583bf9baf081fa8ae0641af67ddd8098fe8ae04e5fa7bccf08e527d4cc12038a889d1c1580a6d8290895af1c0d21e688f1714a396ee543ad18fe3c266615e3c34acab3873ad1e066acc40ca162fcf398d4151f317c323a197d1ee3334b5ecc63f9c7238618cf7bf12057bc58fb122ae6f9151f31cf611cc5c348e52b9a25cf28e51ecd92176b1a2ba7537f35343894672f4693a241095df162301eca0a2a285fb94105e52c4723082edda9204e6a623156092a18d1bae2c53e3f9d50b15311949f7c06c5448552f130e231a4f2934caa51eed178334e50fed12c794c501e0dce0a6a48e52c3ff1b8e22d7d4b5e2c854bd1b05270f8408100820926ac10eb7e3959c2c01d77dce101b990e699b3b99c75facd56da7bcbf3a634ceba4172deec757a3847326793babba75b238f91076e0c9b1dcdbf5e4babf14cb1125863382270b037bac16ac972dafcf2d7fd3c47c795ce9cbdc2c4ca00ac0ce96e10dbd071a6394caba595c6c04c5dfc6babbdbd0ecf9b1514fd00cda1c1719ce1a43e70b46e483438b3f19738e42f5b33fb5be6a579ab65ed4bd19f8664368e1368e653ace5ad861b0ae30f433c6f14147f09d32b2e2ed10fffe26a77f7cb196c65b46c6483baaf7eaf8de66c76f149d2de5b385d9cb798ad66a952f145ba642fe9b399520d8e2ea2bbff73d766ea7fcb229d6479e5af52abd6d3dd36bbbd7937ba1bac5d7b0bebbcf90bf46074830fc537b1b9667d37758ee96e1ce7fdfa367fe1a9f319ba25c281ff6b5614c500737dec4f139768b05222a0293eced03e69894799bec457e7bf61b5e14db555447d01da10014db914bf48fd924cef4fcb14b4815f1392a177d33e9c24f69a755c38479f8ec979bb7f04e6c75ea9dbd82acda5bde083b4c6bfc4987ece62c753a7d4a29d01566b6f98ea7c67338eb3c6abcefe4d895644f8d7ffa6446bd2dda0584b5bab96c47fcbb86c737f69adae34773fbf183a1632d900abf57cbfeaacff4d696ae9d273f6d6dca7e379f34cfdc34ab1cf9a6ef1b1efeaccb9b54ab3391abc9f475aab96baff4d69b6e5da9a8dfefd258b69cd6b38bd49b76647a0f8f92ff934b7597c5cfaccffa6d492eda0415c68efe7ca71a419dfbf657f5ccef0692efe876189dd71a11d6be97f534ac2647f466b69d8c7c7a6c6c7671c27908fe3e94a4992e4c2293e311fda6ee672e1149aa34f643b3c65ee3395fdd7327ed147fc284998eccfee4e1ad2dad7a7ddb0c40db7be61c90dacb19cb517d9c8d7923f7e06d2ad2a608055669fcbf099be55a549267e95a35cc8d4aae2609519ce8ff194ddafb5a8b8d1ddbdabd16cabec69f4869856e25aca3ebcb646458cee06abccc4fa18063b9ad9e0ff49e18b16c8e3a24525090b36ac6183056c78a2db95247187cd669ef124fdc33a97c43ac327348f25910d335d03530d398fab25f123e5c229ddcdc5165a9c50acd3775a7d2bd5a58185a7cec771861e6b4d0123866e4db9cd9b4faf34f799fa8fb64ab1c75a5372ba1bc425ad250507ddddb68a3b3ca514084899e038be0e4f7fd55fa236b755ea35eb0eeec6b1ac792852d1f17bcd3a8e892f94fb3c94fb3c846f994af991cc59a0cf75e816e6f913450523cde5bd4c70464c94f258e228423b516a4c274aae9fdb4d52f678fef848509a68283d4a326830e3b7d94aecf966a3397f7297e7dff2eb6fb97eaeb4dde97fcb2ffecdf1bcb9ebbe2849f7454a0ee89af55c59df6733ff9b12c68f73fd25264a2fec32cdf5452a2b4264e7ffde9470330cdf4911223bf3965484c88ed5293d6e8e3f3b3cba89a4c3a39b48393cba8974644737917a767413c9c88e6e2215d9d14d24223bba89346447379178767413696747379172767413e9888e6e22f5e8e82692111ddd442aa2a39b484474741369888e6e22f1e8e826d28e8e6e22e9e8e826d211dd44ead14d2423ba895444379188e826d210dd44e2d14da41ddd44d2d14da41cdd44fa6a8b90dc402291945ed0b3a35362db1c85e6cd4c4cc63116624af42f849fdc65180cd364b85a3c1f69061dd791178d2f48c929fb3c97e82565b21d7008987363b3e3df32cfaff90cd752b6849b893fd2667d7a6f768feb6e20ba3b4b77ffe0430d466bb46ede1d904707ccb9a93a7b69373c6f465d34e9961115b64a6f4e9291f873b302c1604b3b3ca58fec3ec934739769e67c9230cddccb1e4f9acf0e4f998469e67256a91601dd3fc230d813ef8b16d190ee1cfebd36f46c2b76af51677ac7d4ffa6e44866b26b6533f16f98565af3fbe248cb1f7c41dc43777b4dddb6568790329e4f68a675080b1d67a54221c595deccc4643792640ccfdbd1fda38e29a1f564099296132e197f95ddcf5496b3b7260b9fc438e12f39117fe93e7ea4ff2b14629a83c1707d9ccd659e39e9c33ac7f9d3f882e36cc9808006415048fef195e75f9b675be420f671ceb10cc719da2a156b091b7fe947190ce786e7ad9630180c365483d6d00c8648d03d73d8ccf37417ff86e76de8009dafbd61b736c31edf5ffa3b842f78c3f386b3542dc5f7a3747799bbc5f1290df7978cc4af503259de6b8184c4f90383e16bef6572646b75e8da3cc53a89c6799f341a67eeda22182c5f4b9225967d6ede1adecda3b645f869c0bb79f45f93e1dd24319e2310c6730c6340436889d00a5b98986046900ddd373c9b543a6f403e3e3e49c28f9204833dbd5946e299a7486533cfa05b1db2e15c509eb25c1fd7a12271fead894cb6644919d3ccc56ad0454d8a2cc16033cf20f133a6f8fe5114274a33cf20717e93225c68c79f1d9ed2e7450b1425894f92f0a3f84049127e145bab43e36881c4f937dc9db9fb79ea6464ee569d1565e263fca1bd64b554a6fb3ace5c2d167aed0d7f4e26dea9234b71e8ef37f93c6d7f6db6d8fd5b36b2d11f6b95806cd13896423e3d6592f0a3c060e12fc16062c63212cf3c457148f6d786b22773b7090c478cb3ac54c6347332321732ed66f8a2ecca745274b2cf95f82f69afb8c3538aa3bde1bfb72cc3732ca253ca32beb4365a4bcb9832bd0c8fb3da28cef52949c3bf3e3334be600fa803e6dcdc84bf24c3cdd7c160b2cfd15ccbabc48796921b40f173f824fefba40db157ead7e6f0975cfc01dcd0ddb56e252193b8d1ddd8dec7f2a3387f749b1d674af35c9f86b4562bb18b9f6bf486c349e66c5804e4b9b9b9a10a95ee10c91605207204ac8e9e79e291cefc41642e8dc417eb93b84cc219b5add291469360b05cc854a98d5ea32319b86591eac43a7fc4c750da477c92a43763580e4f5d928e6326d627c939f35ba63abf4fc73bdfc5f7fa4f732ca499e8ee29ddeac1025f30d7cfd91ecbc417cb31acb496b93efe4be232e74ee6eecdc5bf730c5fcc3447c7271acb9539ba5bb4a1ec455bf12fd14c73f49238b4129b000e23397412d2102d23ad561130ba7b061f79fdd7e5a7f9e3f937acd477a37b0e1a074592d0c4a737cf706454d4dd444fdec7467c9b482f2d569324fc28389451d9975f5e9189373c9bc060e2fc9b2cbc92935dc9898f85e2ebf3798a24147f8943f3cea324b64aad2d975646e6ee4d46e66464ae948d33dbbcc353ca6c38d780214c5a1c6999a303eedcd85aa5373fce108b5f6d14e379cbf3c808143f679acbd9d09301c4859366eb0c3dcf5bf689ebbcd9eb93d66d91c7a3459c7f3bfa789e74dfa7e5caaaea16d22d1e991652f15bd1b3b5d7df36afcd78929eb3b344e30be2e0423bd2649fe70d0ec6c1c189f8265b71e2464be2ec32bd9304f3639a29d33b93ee76d2dd32307500ba9b866ee9dca03d141a86eef6814eee6e29be113e1a725c802ff879dae8d2ac519c9093e8ff2afd20a085163cc795cc787e5cabd9540b2db4e04a7c5cac9f3212a51b45011417a800a0804ee846fda0628d1ad2c2e9b916e3b4704ce8a1ee36a165dd02b7f89146a3f5e3d2026fdd0ed340b76e9ce85d9d5ee72de7faf76b352afaf45f9a37b7d9b1f85ffd16d29bce5f6e73a5e2b4a3f8392c6fc6d586be1bab754cc1e9357bc3a5ef682ebe3fcdb3ad5e67d80d04c24efc2b8e24e7e9334fd1f16eeac6e9e2637acb2ed6e9373c6f3e675f8e330a0c267ed519bd68abf416ced0ff4e7f29ddfdd3ddb125dd42af211a5f902c71d68e3d3b60ce0d4dbc797a73eb85ebd6ebbbf59aa9727d4c8b05d5acbd47e2fc26f3cea3ee56d2cd84ccdd264c2fd6a1f16b9d37208cffdaea1099bb4dc4f94dc22b44577241365aab1688de8c1f499c7f8bd9185e9cd74642c9764c1aa7381fa9bb9320e9ddcd9589381fe916e699c394285b8b94f479e6245ba5b689f113657bc3e4ac58f679662cdbcdf12f0c96fb8c775856e9bc85f34e52967f1c2729c3e558e6ca10c85669a533f7485f7e39c32dcc93c4346837c747aa4115006843043048110317a081450992d810828a07d603605c0f50c308a70096afe600022600d1023058f001c41326668ae0b1738027bef8e206074092009286081410b403089406a02405aa1915ba8184dd7261d6f882bbc7c9c6195a2b8874b74d956ed970e921a00ea87333567be952fd213ce2fb6ea4cd5ce67c27cebfa3f745baaf2904e850a9bc5e4a7c94b80d93486b72ed9fc090ae51aefd1377f74a05076712a9ec6de2cb70e1bc59c552bda88ea0a12489bfb0815c4b1bba121faf3fca72ed479938da940f4e2840b58172626542c6a22b51125e4cf301a2a45b2d7877bf68ab3bec451bfa8fb4790be7df5abaf8f7737d878db3decf953aa1e337b5f1b12b51e2f966a37f03df26fe1622badb866eb120561a948680624731204fe24c229d756b8535f0057737d7c711ea5e35d1dd35746b052bf882e4cce1fcdf4cc3c1d5d21cfe38dab07bc746c7f95828cea5ddcd272d47d3ac57b955a31aa5c4ebd5164f4cb9bc55fbf05222783d3cd60eca489f9040a1b0fa58ac14eae4793dc207f6d7de0c2818bc5e2149b50f95d79ee7a55243be98af3dcf3b7d4d3c6fe57d9e775261f9bccf5b7d2220799eca5bf5e7f1004f9f4a5503e31eeafb50457828cffb60bc1d7cde4ba73cd4777acfb3f279dec782f205f9bc4f85fabc197c9ea7fabc19bc113ceff4b134903aad7a78221cf942609dbc27bccf537d9ef79d64de87827919f2c9783e2c2fb5c24989b7fa3e94ce09e6fb3c1b1f453c1c503a3142a7a3af51fd55e1a14e35a8d4e79d3a75008f055e7fdd6ab5da6b0fe57d9e0d3cd6e782e77da9ef3b791e8f87e3b13c99effb3e159027e4fb509f977302634860b5e0f1f851c2a78497fa48f050dec9ebefe873180fd51f55b5cf3c029c7a46035e2f29e33b792a0fb5f27878a725be8f053b793d27effb50fec9bcd47829cf5be5a07452c8f13e1815eb5b791eccfbbe139c54decb873a2d79345fe979a71bef25e6f3582bef5bf2a1f0a1f09d70ea537b3e3cd5f7793c50ad13ea23e1fb3e4fe5a16adecb0a5e4ceaf3525e0f2341bea155cccbe7d15832abcf9bc16379dee9f3505e13ef84efc867c217f3adbe9477f23eeff360de095fccb74279a7d4c9f38c7827afc67bf1643ccf43e2a1f0c19c561e0be57d9e07f33c95081f081eeaf35228efc817e39d3e14bcd3e9fb501fca3bf27d5e091f8cf7e2b1509fe77930ef840f6675f2502c94e77946dc03e2022bc9e9859477f25e3c1410efe50516634310143c1550def7a9bed57bad1072629a78dee77d9ee7ddf4aac9ae81074f0e1a6cd04e43147070a161e60d1a48992c58009415b8d14a3326e8a4283ef049538d468a228c279066acd10c515891e3680e59f2240a1472d0649a9073240a203847660f2d8cac80c70a466660bc222bf0ec2832eb9280227e4783333a50b89961f1bddcb0c2be3cc78517b6bce24861e63229f8083f7a0b00aaf42c65443bea87ee5377b78ecc661959caba3ba755f89c4dfa6a8b72dcc7efdfb21de2491c676aa322cdd6f6b6e86e14ba95021b364ac3c995b59b960216dd8d4349055ff0c919e2afbd18d2e6911e2345880ce1d9d1c9a1d27dd44ae1085f70e629ca78f838053959a009458026d413950beead76d043cb8aa6544a66d582097a4045a1831a41860a2d540368423d791101c604203f34bdd080aae184424c56c9b084583d2a9813122030290e34b1c06802814a0f2f464e4fe04046054da92e4d29991246d02493692cd0f402a4c7b74292c24921a12584181294f0013544938f191f33a71b170e0b88ab8b0fa917583dab24271fa7242c2a9a4e443a382a18d5164da7185509a8578ccec943c9a05e3042a9175634a84a50c1a8acc8802003c20b90171156349cecf04155c2a90b1f526734b160059573190b27a060323e542e9477ba3901a18e6446507d2b29ab20aba254083d3c55cc298592399d4edf6975629d5e604c409d5650563f7c1cc1b856aa95775aa186a070501e8b8453901e5e524869d1d4638b1e5c2a541056900f2815f84023f3ee72070c4ac0c607b8a07146694b32a208263f302831626b1c36c011d2f2c15ad9508351519734d600410a44e42ca1c4922654400235ccb840041870050ed84212652602df1ddc408e3644b006190fe061871ba8d430c5a82806a114501841843028c0030316b0830eb2134cb035851e2a48c1955c53973b7ac0032a368c20828b04669481810b4400025e10510001c86ea0418a51d1901a6994e1810a2cd1e4042650c30c322e60012ba81002881f6e2007094470010b84d1802c4d2f0e3171f190c3056fa8000d322830812f146004015e94dd40c50629434c90c06c5c3cd448038d2710604496176f90614888099276c11b6aa4b181322830812fbc50c01105c8524586212548604686e8d8fc70a1ba8b0bde48c106d028830c0a4cc00b051c61440108908505b22a5460c001eba0e389131f3d50604211430831454a8e14041062c7c4c54b97d31d9f1d293abe1fb0b44065f129e00bdd33b891c47577aebf24521b05a88e1d743d38aa83ac435687943a64a803491d44ba5b1c6d133ae4e8592ec919143a0ed05fad0d5f427420d161d36da38e69aeaf1086838371702e642a6b7528366f198be5c2294042747b031040b7a7a5db0b401040747b59bc1f3c1f7af87a3044778fa0bbbd1ee4d0ed8d208e184820011d197474f747a34aec0d5a60001974f7890612490b1180d02507dd8dc2420a051c0180026718e96e0f86236c302a42004f84d1dd9e103348c1e6a69268d1dddf132630b840082142f4a0bb4f2110c1c5064a2c110608ba5bd5c401e6f0d680ddb1a4bbbf377660440e7528d10223dd8d42c06cc5c48d035654d0ddde1ab41d32a810e00a0aba6fc4fa19bf4ef623699f9cb79c2011389be19aa75869cd6df62a339c34b3c15fb33c67fedc5f0b0426cdfe5a20f0c10729191bc70904e692ac74f62f649566642eff6bb65629f8b59753327674cb369b65b2ffdaee8e99a9a464ecc36a8bc0bf2981b39af58fc1664792c9c06b6baecb232d6913c8efe79a92f836db54027f473f4773eedada7d12533c9b61fcf596d81f3773a5a54d17bddef9a3b5e4e3ec52a5f696ff9d29dfbf399c36e7b19caf7ae4b714df8e6eb3e77236fc3be42f3cd25973cf25e9629de358d65ef45d9d5e2db602fe9dcd7ce6a177b14e9aebccb8d2e918cf66404ec9d8bc650c9cb7b4187ff8d5878e622ecb76c4a0f8b8c47996f9b51be72d637e6d4d04331da7e8949c3e9b615cf837200cdaecbb3b9740210731c55f272563528c404a4ebf8fbb5627abd52c88c333d76a490e5272ee1ee72fb86e5e3ae2dde3643630a423ad245d74264f624049d586480e629ff5d4723973918e4f13ffc149975c7c6c49a6f7dde31e8fe3b4353bfd730c2efe631d5c0006171614b960143f97994a71410dae3a5b9dbcd1463759a1641ad2916462f286132d0ee1584849ab943fe92abda1d4229d47e32ffd13d22abd11024ed2d9718cb5408716e0d0021bbe20ceb5cf0224ba618ef124f1056f7221134e2e64c2b1556a9b19e3fbf4de49cbc10a5cb00211ac200cfc34df27ed25fde5307f390cbf1c0624e5715369055b3a174ec94c4ce62da7add5a1a41578e3236ee8c049b3d01d4547419b793b24de12cfc60d960e987393adbd374f6ff666284fe655a132c58629354ca1618a142f0aca8b11010ada40c19802b2bbc1b0c8611e946e4fa9db4beaf69030cde1604c73287880d02b05794ab0ec98744b65c6f70a696b0e6785de6cb51743daac81862952a240514a429ae1a888e88913199ac420c42408e8278603debcbabce0824d4d972e5dbad4da18b299dcddd0003378476da0bafb8d3ec119b31340514477635b5fe50978206733c7347782a4130475bf1c76d46d6482319a0c1a28f18cbc0c48a003de073c2e25b040b74d4dab045794404bb72771077f14a9ac5abfb66654022a58bc5367841025f0ee243642d00de279f35c27bd18681c2750ae9f144e618309112926ce1f367cbac16bef659a44609519990ba7cc6c78b156b3372fce9b7d377136272747a7c6adbd0139f821cdb6e6d2e6b7f96ee6e8ace1af338f658e629affd71e4f9acdb3868980a6b8ad52fa37290e863437eb4eb437571be21a73107ba65347fdcb9bada5f9ef32bd9978865e2975245fade7e995fab5350789f0102152c456d7d96b739c2ba538389bf90c67349b75d6bf657fbdd5e52107ad673c47172bb5559ab3e1d3bc662bf59b6aebfd719c39fcfa3c6fee2f59b14e9dadb83b73b7b03ecda3d823181325a32207adffb5b5bf33f1676ea71db3c5bbc7dd271d8b23a99b9193a89671a5b3718c39080213ba1bc907d8e86e1b36fac0169e0d1a975690ccf0032f2e73f42d6b8e0489f85c38c0e50bd05629124c9d4b96aab31557a17079828bb898d0158a1a34107f14ebbcd59266c71f35b668507c248fb397e6b9f93763a76ae46e87898fa1388881a678fd3e8564a43a0d1df40c0c752efdf848b3fcb634c2b8a9141643d15011524c4a1a4374f7db2659f334c8eea434a074f7db2639d3f2401d444053dc7734dfdd5fa2e38b8f0ba99207b874dbd4e06c2e646aa1c656692e646ad06fe17cafd6a13888c5afd979e9241d9c7dcd6951ecd1381fd7e8fd25b7591ceda5aff35cd2f0744b75d85f9733bd7fbd529f8e7fe97e9277e04737e6c14062b53f43d9bbe264034b747380ec2ec9f937876d400e7c0b3fe76c08e2e0c03099f3cdf375d291887fdf66a337dff29728ae5f7bd1e7065240c6190ebbc159fbf35868d2519252f84b30d8ee71dd5e111a1be8eef15d2706ffdac4dd1e11cf87f5cb2b8f7d767b4f76fcb1503c9265f6398306abf105ef93e3df64f76598e2af6ff39c80e25f711077adce6d766cc56df6fbf88c2cbadb61454649329c5a67b8c3de66df7577ac5b634c61c618dd30cc66665ea9dbaabd3af7177e3a91ca98195d343893553b7b0cb2c00c1066def086bc26dd1d639a44309812c63fe6c7651401beadda6a1b9ab79cb79c65d0508691ee0e6b79b3bf32a66366fa4a061bde16329898653b2601cdec7d2c16d3a55ba61ece800ef396f386cc854c99e96f32d3cbe62de718565a1ad881065cd020c5f46fb9f6a2db9a5f74dd17791545402147b7178327d428221ac5440668d093a303e6803a37d5622bb217efe3c7e3fcf28e63396f39e72de74d068674b75f0b84132cd0c1c0186d81271528228c3bbac36b65b9fe52aebf846522b5516030986b89a0e4c229642e9cf249d792b999f1e7a8928fbf92925c2b34d2e6384e20180c46865f94d517457a935172ca480c83d5c6c730984f48475a79449231ab0483ade078fabc652cc9bce514af0d311569e52d53dbfd9ca92ebf6de6a8cf7d5ba535196923473a73cf446f9e39daebc669fb7137471253f14a4ef4b9e1d924575659ab014bba416fc048925e6cb9d3c59617b7f71b2f6a402fba0b30bc8b29f4662fd9c52d33b6370688d10d8abb11535bc4002d0ca00c0002d6f2bacd23d561ea47b63b47a7d991e6988e0b3883b4551a5b005383b5bc4e47922cf10264e806dd16c92113eb6399154b2ec01065399cf5b9d032f314f3632ea474cb3e971317fe2df419a7ccda64a24f9379274993898fcb2df21628f48ef6faaff98ca47956b3fe9afdd766fe5f8bfdd764e3a4f94ef2c7ff9af8199756c4f95ff3f9171afaafc9aaa598a49969e6c46a69baaf5a900d569969b1440b2059c021cb028cb62396655144ae54f64bf3c72c66f971163978cab25881556632519c32b15a1a15472b538016afd3773405dcac1557c092560010dd1761214763c10516531acc8fb18378f7b81dc642ac58b0babb6357c011bba20b5cafd0d2e01553fa8a9acebf8b62c51c0ddeacb062092b96fcb582f54faae8229ce48ff4a6739d1dabb889d5d27c17565a54454f152d2ad8682abae8fb752c97ea90572a9aa8c8196779e5c529d8e83c452ad619663b62c74270934eb1a5a7207b8a256095d9142d592e6492a28d96ddf0bc49b104e8d8673d22426ae23526c5ada500d218675a6d9893a2a0418337a5289aa2c86970a6fb3af3f16f8eb7c801ceec4d37a39558ac5bced82245835566b2c759527c5ccab61cb56d925bdca1100397353ebb7626be786788a90885134450f8008538daea949c0968a397ee63ffb2d25c6d7dcfb6260087042cb9f9eb3bda6ee63e47739e3ec34cc53a27593e310758e9b43d31c6134b3cf1f30490ee066d95ee6efe5b3afea5f1716e69b42ff13fd7796769f36ceb2d2c936ae9bb91de792d99fbec983ae1c69735c7d49d78806d1e3d169a048329bd6d926f9ba4134756e6040856995da6af444b7fc9c742f1dbe69158dfc7274911bea5a5dd6cf4ab6c27cebf3e3e8f8526f9ebb1d024727e93243b3c506cae43feea29c9f9b78ce7f8586852fea426ce689008684a13553491bbab50f15713549a4852858a8bd46da00212014dc9f57396080653d2d9da84abfbb1add27164a20b50fc9189a6069938ea2e6d4cb4aafd9cc5b125b8e8eca8535a624b836ef3d7e66a7cf7758929b6c696f0719c404a9cd1ddadc49606aff51d25254870e6a9444febbe96841bee988ed5a8bb8d742b892d0dda4a94c46d544a024883bba9bb2fd6d246f1effce5b6e80c9a6721b5d299ab6d1d0087dddfb223b92f5eeb7f53c24faf95f9f8f82825b956288fe30c916003092cba658f04938e0c0927348f654e86a74f925ca9253f4795eadf743eb1712ed94b92f3da4a67ce004918e0e896f3a4374ff2af01fa6fed082e383c8fd832ef0e4fdd1153ba1bbca195f8889e16e711acbf3123ce18b1115afc25be114bbaf1384eaff17c6dcc0837c2a75b4580d10dfad7e9f89661688bd80216c1143454c494eeb645ad229634a6fe2f5444ff8d0830bafbda9c08e4202622620bd38b4c8868cacd4ac4ac418c692526a207fb7db11201a41bf4bf2915600edfe1429a0bc04677834a055800587596ded0ca24dd579f9967900f8681c17cee91e309832925f9afc998668e56621f9b996790bf6a6825b6fd2500160468eaceb4bc4280a36e02d450597e4c00165865f65f93e5c743b8d1600c5104adc4b2fc7888db9589e2bcf3c77008200d5699c9c60feb0ce5a87f85e00256998953082c682596fd9215028bccd6ead02edb5aa53e986912f9cbc627890cfebaf3fe609b585a11c7a742e21016c7a7b5ff9acd398b65ffb736802d3d8026507cddbce501cc40b1350020a0cdb57469de9e86694e0070e44a7302e8a21b1480961dcdf3c7047024809cee6e26ddd2e246c64f6ba6162db41761ddd2b2a4db662d3e022006b6a3e7d27ab555fa0a62e23a3b3ecd7349c3f6a60bc0d8a048a3e2dfd05fb399ef682ee2a91b67b64aad00a81004177297b158c56ac3c71e041641dc409bbfb51a0f0248833640c0413b0304020b507c3c754030758333212cffb70644939bbfe40f10de3fad2c6efc588a7f0bf192c7b274d1dd2d8eb68938da5b96a6066df4e6b628599a748333cc3489664cef62cd7a96177d9c34d7b7d97576fc81cb38529cce71947e20629c3853919657e8e8ffb726d37df581c1947c9492d48c9ed89bee8716f6010e1fc0e81ea78cfcc7f9a0a5bb65a50f533acf1765321f721aac32f361d560959938749fd68670a17d22d25c4b9b9b17dbe658fbbfb5fa58ecadf5e0437b119dd207c3604a788e3364b23f72dc32d53571c1a2a96989be28c3b369ca384ed92dcc93d694339b8286c4221ed8d83d0e8907304027e90db77868c254b4452d1e5ebd7b9c7f9e34b418cf1c752c2453b7d85fbf44c7c7be7b2778f74e6030a59ab597dc618eeece8f77f84077ef1e271ba792120ca624dba149397bd186b0d90e40babb7590a3bbfba65aaf463a7069af311db008633a6821a72ed6d2e10928fe38ce1ce6c8cdea7f53ca618bed96a4db2a7d9ae7ecad6fef63b9a9ad1c943e536c6faaad2fd290964938f4a0854318dd0dceaccfae059ab92da44cc27933d3bbcd8ec3017060d2dde0dff2c5d7d9719ce1933ebbc7edde0913adbdcfee9de43e53bc7b1cadc458dc68b0ca0cbf6d923272fe4d961f6301a3b114d1586660951996e5d2e6c7589634586586a525c3dd99fb7b42b77670ec34d060951913adfd2565e348714c72fda5f751226d7e3c6f334ff1c36a2bceb5b440b547c2775e261f565b048329f9d8f86b87a7acd1cd2bde32d5c932b572ff5a2b53ac1427ab5337cefcb63bf3e766bd8f0bed280e15e1aed5e5af32bc688168bb49746da53999acd29cccd62a95e5c229b656877e2c459b132b7542eeb20c33cd1cd3cc31f99156daa0719c61c63627cb5326f44bd4762dbd2fd6c7438a944c33970b99f2e3429a654c33e7532de5594a920ba7c0603b4b622ec91b9e4119bf8d8a3e4dee8bf36f557678aae4ec38929ed20631b6f4d2b970cab5f7be110c96f1e739c260b990e97336cca51d6799c389ff6b3e36354972e1145ba5bb49fac87aca5c3845a4327148a4b28c6d5884c84e931b9eb7accb6e2ba6fe77267bdccc33dccc3b71b6f01862fd16ae02536910e3d2316933ce53ca8de32ed197a9c15c1fc7aec0d1ddf7e9fdf2ca952fba413c75d36b9649eb4abe820207481c5a648d5da341f1c5ff4ab397744cbdde5fb2362207c791ea76238d626c49ff5bc6a5bd6fab53b4c9a5b5d9afa59714e75f5ba55e534bc714dc8db8b474e92f79a45bb6c9ba50d918cbfc36b1cc9539ca34ef63ea758a5a949ba3c15cda7cadbff20ce94896a24f9aa9bfee2f7db5553a9fe6bb39fd16e699cb6db199e5c22962fda4d9e79aada9f7758eff857237f173748e61ae66228d2f1897a0b9fed2fd5cd61940bb3a89ac556a27a87c3366c474b545d95a2451379160325991d15012129328322631c4908c94926432d897573e2357193dcd0469e619646b75e883418185621afe1b90914a630c1f7ca631d7184cbd42630c13bc267ce8e16b9a8107011e76d0e1cb0187263450d31a68fcddddf2bc9f42f397dcd1a460c1d703035cba7eb9d18501a6b6550782815707f51c7d3231b0687fd9a22f63f976f0cc9872811edcdab0cb0252e0c279959ac41a062e0b80d0ae0ac8ba1bd7f222c13ece34734261bcd1ab309cf05e5c611069ec9364473b0271b599be461ac5f5f1b53aaf8fed2df45b5a1a1cc4b8ccf34e929c2eb870a76efaff8896de27fdebd08e366925f66ac9d2675fde3c739b1f895626892f8a31a0fc4912b801c30e30b878547cb8eef63e8a6925c6b64a5f35a62207734c93c8f1df2741bf41432ea4466925febcfb01ef9321c5f3c77e698033b7a14db534db9c8f34ace56e7c1c1d6bdc463de397520c0bc9f38533ddd1d04a4c33de274bec076862813b31ba99622445c9c13ce441d70ad1caa46c8b985efc251a4391f82f442b8bf4688078c353840600e9eee92e84171d680378314433e1c513507c047431c72fd12e3640cebfc1725d4061801b0ce0d2f7c5f94df2b544b8f06f400b1841f7028816c0a4dbb958417e2cd2c7b4126fd1460ba951f259e96eefa33e59b8d1bd1b9572c899718e662d7ab458418b15ba1d960512edca0248027c9c9ca14cf79549942972e8fec2af7eb35546dc304595436bd4c7aaaa016dd55e9d7fce2639886925be8575de1fd5153f4e28f49046e9c056e9cc37bbbf196b7524da2a43f727ebfe6ef8aa747f548cb6b8c284595ff75058e146a563a551bc0958e144b7154ae097318f8a12239d372319a7cdd53a2d57ded0a7e3176d95d95ca9ccf3c19b001eeb9dfe4b9676edd08bb6e827e8e955aa1406a3e5ca0b44c548e72c97b999f8bf24e47554e46e4bc5a9613071a4b710d6fdd9d0fdd5d03d3a3aa0ce4df84b3c74a450814d4dfd5149121769ee9bf2197d52c29042065ff0c3fa18bf2ccaa7d4fd25757f48dddf0cdddfd167d40b704551b66d0b9732d78d32c3a606673d071b99bf704d0b39f80bd7ec1e27dbd1643250a54489df324469a184ce3bc9d4100eebdffc3575291a2872ba5335b812e04677db9aca494012dd494131a128e04c96fd3fc96bd669a1c71cd320ff5bb679eae6adbce98e88a0e4df79ae187410878e2776b1ce69c79b38afe7c6bf393bdde2196803df5f2fa53a1dac4dcf8f33bd653acefbb9bf9134e7332c457babcc66367cce26cd1cd31a83c1ef4b71f0e9cdf6c12ab3990d357b535e09b1d57dadc224c80808261bc7d7c16024b6a18a0dfeb7d7f9cb6faccdf12f515bccc1c7b9d2a9fb8ac97f5c9519ceda2698ded06cb6d9ff4ef15a9dcf5ca758ff898bd556e92ddb2231dba258b645bb1ff18f301145743f171fd1f7a4db81f44ca52ea4fbeb52805c77e39c6208c0463be96ed62743ae9febee16ae5d04f0a521de00714643b0d18db3ae21a674eb8608ea6e971063e87046778b33cfa0fa4f704871a5a31432779b04e9bee897680e671dc2bb7722ea6cd18f24bd4af82863fae4457aef2f1991b9704a66faa29ccdb80412efd49150f0a541e2dbee1f85b5b4b5c6329e4f922a94a17166abb4c303c52ac5ec980494b16d82875eb440f993441a8483c8dc6d0264a3d815000074e3a7738747f4fb249e3fe00ecfee71331b9b574dcdbee04366e314a7d7398eb33e6975f689659a44b66aaf149b6d511dba2f5ea3a4225b243e111ec71fb8f881061988c771d24abc2347d1e6faabb63265a50fabad321494ee6ed2ae1e907aa0400f4d789e6ef3e5ee71353dc0b4e7c9f4b5bda6a6130478e8b15229ce19e1e1d1a178879b543c18c1430f3bd4a0fb65328785496fbb4c4a3acce1ca21c8a543530e2fc053862ff8c38517fa870b34fdc3851ffdc3059917150e72dcda854353503b0c07563b0ccb1a0e73c2615844876179753b6c57473b6c7746b7c3764538ccb553da3d6ee70e93b998de60eac2619889c96178b68b49a6bb756c384c6c976e74e986386c89cbd212bd24763b6ca95de512ddde0aca972b8f01ee32997b7c3f7f6e5698387fc6b1f4e488e3d3f972f07fed97a80de703d978729765dfcdf7fa5ef85cb0a9f98478df020b2ba84033f3b997822f850f05ea44b7add5a17fa17f217c9f9272747fb536728c8689bfc3329f900720996c803cdb5ae341641398adc2c4275b364a8389efb2371bc44677df582fba1d360391db2c7e0e067359172c0a15070d3ab6e19cbfea93ee4ac5f993eb406e0294c9616f9b4718e3a749f9b14cfc8ce7133cce3c836a46985626e1dd3bc94c5f74bfea8c76ef44e85f28085f2b5433aa190969d7d8a4551881e074d0edaf92068361205ba5184a7707b50b97040e071c897bd2ed305bab432e9ccc7c310d979521ddb714436ab34f1860c5a3306887512dd876a7032895eec6a27ba6a7b88d60469c3b3513d4d438cde0aa2975cf663098eb91f84edddfd7c5f5495c22f979dd5f7b53b8c2b02d05b1cd24a523999bb39948e64a9ae7b7d924076724cd168f375c22b179c191dca723f942ee82bfe0ff35598d2b71926659fe11890d49734d38ce50a6c43dff78ad90e3e0f82be3f2ce663307499a69feb22169aef19bdd78e74873b5bc37b9da3b9b799d9ff33ce420ae96dafe7aad96a4d9e7cc34fb5f9b2dcd667ea9b53424cd972ed178389b3dfd9ac71c5f301732c9786420b4bd426f9b474c8ec0dd38c30f83e1d2ee76e353db8fa20dad71a524a649048abf7b8d33a437af7f3fac460ed6199636b866f1cc18ac32ab19d5679a44331b7c47c3733762c7749c742ecd4b4b6b6d1ef31c7ddbb45937dda6e6088ad2912d7adb3cb245ae1ab0a896d6d0442b0ea98625ddad9b89f39b24cd6a90196ff8af100d26e816612e1a72e806df66d7943a5c53e0285aa2db6153d6687168ca969e34ca94b17bca911d1e31a9a7c4743b6c89e659846317c4c15260c8f882d586b2284d4469ea769858290e97f7c93209c7da88674617265186e4627041a14177673be2eeeef1a45d508ac017c4597b93e112d7c7d586d6b31de7df5a7733ddd1dd49250c1652f12b14250c283da9aea439789670c28bede86e567b752415190d29011541914136254674d31ed210d211725e9d6b8630c879756ef30c55b4f8781ee17fa10a056986914e21a224a0c78f9b4a8fef8ba20d7f277ed14e9cf59362467674771b75a01b8bf571d1134a43408fa1d421b1965628db225c689fe427cab608ff0b25659d7d924ba36c2bcd1689f367fe3cd28f349aef078db459a10889f593a03cd1413f19c1132edd1d820c2bfcd76e2194a13adc15c3175cb886baa85376c310d29093212430ec2f2b3669a349089a9061812611e8762691defcd7fcf5727ff9cb659f6d753581920b996e5c318c9e1c4431b88480741c9e0d6ad0edd1e073b37a33e8f6641083dcedc1e14100ffd76050a7ee97a8addb7b810bdee8f65a308e33648158294eecf6be71f656d0f7a91bdd9e0ababd14747b28587201b15c3f58743fbdb9862e254ddd5e1b27304109d820417b59d87b999433e8ec93cf33a866944b23d1da187ea44d3a8ffe856cad0e554bb33834e9f8a225c2ff42f9937ea4cdcfb60817da277fed38f1fd255aa130a9b7fc819244e6e81c8198682c7fd26df2494948bbf2a7491e626282f44177fe08340265f2c33086549f3402d27d1199bb4d62e22b8de304e25982c172e1149e283b5172e114256e281141e742a69c9d1d1edd441287985eac13e3bfa2186bd70e28d0ae1d50d1ae1d0c31c28f2fd63f3e11fa87e746fff024d03f3c28fa87c743fff06afdc3c3e91f5e8ffed13ee8ee992225749330d35d88f4839206f8d88ea13365fca58d8ad5864e5a2be50506a4bd0bbdc08058576b3ece4943150f58daf4dd1cdb531a4df84ac881f7f2803c3a3c150a36b083826eaf034c5c2f0c98c1810d7473008d33ba9fc4948a8c6a1842326ae233222845322631c492929810c9829062528ea0c8a6c48866b636a0e78a6ecf8c0bf488ddddddaa76f500f520e931c1667f39ece5301d74b791365c46ce6887d99bce5f46aa68fca2e7d21a593232bb1d96e74de72f599d21feafd9b88c946084e52a5287c37ce66498bac36a7f650efbaf1569a31d86648484c4c467666680b4ab881245922892435fd90bc761b5bf38b81cc95b62d94b56648a2b51a264c81c45bcbb89d080c80a5c44c268d9381d9699fee5305ba5955ea52b2e174422b9ee19257cc1cfe414a76c173a6c081c0e1b7201d7102f8658e11a92c5a6c64187cd544bff4796a9f8b25cc8c433031e3778d870582873d86bea80ab31b9788c74db8a2f98995ef6793ebd79678976d867ea95d1ed91d1ed8dd1ed692003dd1e06ba3d31ba1b75816eaf024e747b6174774fb7f7e09dd7d65e2f2f2f2f3075feecf2a5a20dffce4cafce630070e9d480f80625c00b0a40608a9c125f303f0e7e9bf8f605a65a8be7787fc9c86bacbd1e170e12f882627d5c3869b91b174e7fe902977814b8710314c5e9e28fb11b305c37437437f8619d37f76fa2b86e9e74b7fbcd911b13dcc71dc6c4444926c260af1fba3ba65d2fa6ee5cc8f4a2d2f88238301886bd0046b71d2210d70badeec671b970816e50a4e53e0fe56caef3762493390c5f3087a72e9cb51b9d19a41974bc2f3a80033756e0b231410a2baa88228a6e8f8a6e6f8a6e4f8a6e2f0a7c411c3c92392ca407876171a8568770494862b53f62b53f40ba2f0ad27d9128be28c6745f04eec6cf938a9fcb51dfcd1c5f1b8acf44851cac32b341a4410eeef07ce2a0f8bb31d70fffe61d2dc4d32bc5f49798beccf9d36618cf1cc436e2bf7699d63297b3618df8d5de27fd15e2a1fcb63b95c46a75f689b54a397b859032a64facd250b645d916655b847fa4511b1d99e85ec85a25241a17b041c3816e178d4e77afd02e1a1e9d849b2f05ac5977d7cc015c3363b70a9ea77bee319eb624dd4d45e6f2361a5f50f6f466a5fa58ec15121f8af82f561ae2471a12917e8892a420552862fe8ced938ccb20f17556ac4c5c29c0d1ae142ad043ae1414d0dd18ac4f982611582df55afd99af36d35bdaa559f3e9b5f1b10d72d8573a7acee66c7f1ca47ecbfcb6ddcce1d2ee861c4cb241eed3c9d2eb931686c3f4ded2ee3c4fb7d8ad787fe975b68983e2cbfe96e1e34a675e67f8b2f7b1b80bbb719c7f5df0d2eebc36e43346484c6af0a25813254762a2146382148333f971a52428440ed627ad78dac08ce790838bef4a7c3c871cdca6c6dec7f2b766bdc841f1454bc298963372123d2e67bb3b5fa4d856ed387d3673fcc2b59acdfd38389b9596069fcd66ae83fbcc33c85fa2bf92e4e82d899c446f9b4adcdd71f06b6b3098bbadb3d98c89abe036e726d38b399c2f5aaf5910675a4b1f0106ddeda45d2338d1dda010210e13a9ec856b7f7d572791b54af383926c95d2a6589f64625287909eb8449003ef1ee712610c70f7b8177c260e3db953dabd93dde3a6d7ec9c553bcb6fbb85773ef6dd9d95d230bde974d833f9b85ff21f722ce4319622d21b0d69ce561bfe785facb808efde892ddabd1331481c8ad92217004290ed75016085eef620184c26131f49fc71a44e745ff4a2054a72fdf820edfaa18489cb0547771ff510494a02690de7e08e46a88708c634de432429c9c5cfa2105a1088679ea2cdb56c92f1a5e5253de317475be3e43f2ee70ee2423b8218d6823b782367fff7961ddb5aad51b7a9f1c7e3147270b6c353ce669ea7a8349bcdaecd490e666c475cdeb2db6ac3d958adce6b7466c3f4621d7af92825b956a86676c37f3f7fcd31a675de427ab3e3b7d55ffa918ee14dbed9e8e838a419d39092b1245fb3b3c67dda2a25c949e4331fa52438f7b75cb3374cf6c7c19b4a7d46b14763ad527f4c7543ee168fd35f0e5a0767b324076db0905c8d3b88c949f4b2d9ecb924c769832bad71ec60d0d09d63198a3f8e13c8deafa1da22a0a06a43ab346495b0f85f14257c32e973b3287c3229c5833b5870cb4deca22d82c3b50201191a70adba70ada670ad9c70ad0ce05aede05ae5e0e56ed2ba0d0b35386ec382fb8b058791d65f2cd4b8c3fc85bd8a6b35e45ae138cca66692f6e6fab27973a5303a5caa333e6f0b14a72e58b854526c0e5f3097b3218ea772a56e78fee8795610ecc04177a3d0ae944e8358e642e9d0607eec36e3d25fb3fbb65927d1cc77b46b6dd10a66f0ee7115f09648801aa7253923b883c7f5e9a0672a759dc74baeaf04ae4f747d4a0da372a9542a352a342a54a8d050b18135d32c5015e36a17743756aaa20dd025073738e3386517a4ddb104d313dd8dd39d6221a9c7a8b4302926c21e069942cecc80400000003311002040281a0fc70332c994b84e570f14800274b868964e9b8ab3248929859421c40010800100100010990903a026056c24c73aa8ebe0c067b5dc16621b892e1d6fb1e8b65ce1398559feda0c8ad9b043e018c43b63051c7ff4cbdb8ffdf665e4f8e23bac1b81fe2b27a497493ba5985fda3c590fda30f5dd7b15ab7f3ebae3b18be314d07c23270ebb33f36d2081740514cc931981d79b77653ad2d83ff839a9d842ec8ca03c11e618d4f380181da273149cf5846530f88fb6f235dad86ee6f6aafd3fadf26a8383d46bb51043b8c810077ab7bdd8f30449e139e48a97f1c2fc26a9cfa36d06db4fe6213aa79b2d32b9ea8e06a4144d1ab0867dbb3b4dbf692385feeb2da4e5721c6da25f5da9705c33d82e6a6fff650e455e908e58fa95ba963373a825ffc07b06fb0bfcfbdca125cb08eabf0be8266ecd52af3cfe34786a73488b5c2528883319ba7630296af345ea60e1596f315e9cc727f5c478c799a162621a6fa788a9e16020e7ae3032174a1cd328751b9b9639b671a3d196b2be023de52c4aa008bd456a771dbb89a455fa45dfac3ac5bc4e6fbca6e11c3493af248aaafc54ccb76303fb88086f2a53225b88c3b96928393d0356a4bef2935cee47bf74ae934ff1d7600fe2eedcb11756184990d9d7c0401ed72e25411d6b78ecd000f0d94abd02f68bc284b2f7e424ddf64fbb25f6b84e491826529cd8ab274f181f175d3684ad2decdd0c0759015cc7560d2af6de9ffe51da534fe1381d50331f84e2749e5596dea5e6b6ba811309ba748d59fddb4b5c9f0e40b40bddfd7aafb206b489dc2d22342f05a15eccbc1d94dcfa2f97a3075e6d5c5a0cf938767c2b51f5fa7c47a7afc79f4062cb6cc2e18ccb056fbbe0d65769182ef88dfa928af57784277183ffcaf308493addbfe843afa41e70cc3248c63b33025c297a53a3d84d73e48da5ce9e463e3df81ee75c079756accc4ee02b834f61b7c4fe9d86952677e6c4d53c1932638571193e55a052c9a0634dc997bc02d4086c2fa2ac6b8d39cf4f45c9a348116c12c18d01a4b3c8852841dcf17bfc32c6392c3b5c60c61c6bb7f2722f5ba12445831f6547747e055cfaa08532dd10f482937d7cb90bc873bc64168e23d4566c32dba880f91af0a2fca8c0834ac07a56b648aa1ba10c74a412eb40ff0de8e49ad008ff681fd1d8743c22d6bdcfa42e254d4a9c39710791e2359a5a9ed04e79ce8470d547506e8e655ddc51d47023c18770e37164736e2465ec69f7bccba40d1db82980b097b739de34e9765ad6fc9d2c7340020faecf861d9213a3849a93e8ab01f1b02e09e92a7235511dd2d887522a923ec61bb4cb64600e8f210eb43ee557917f1ae400eb9853a4e5371b4e2106724b18f703d61eb6fdd8811f38802e909527bf0dbf98170ef7a1b293fc06336c4ab7ec0fdc1dfb2dd53892e295f4d37fd3df6422847312efc306aef687f624fb7e3fc44b45caab703afec59a65cf6cc35f5530c1990679a39f3cf392851958f2bdef997c048fa27cc361a95c9c8795a0904e9c870045eae5b1d8c0d7ff48e302993b3f6cccc3ee6ecb4bd39508c68fa9c99ac84d942d4f5ccdf75b8e81396a50ae952838efcf62593c6a524073bc25f6e87bab511d1097ba781dc4b233f88a3aa51af4f9302304606187fea29299f32d2b73de4984a852a5e5e7cae0c85ca6c6b7e1cc9581a4596e8658ddf616096f8fd248908130c42cd55c0688cf87c355e792808d542693c5369a0e5dfaee001ca8523e693a3350d38d8b1e35d24ac2a67357355dd4b47f6c9854e732d99e07cdccdb592f6cc5054187294e2bec78f5fe88c2084fc849101d80d4c79585c9a59c954b0d982dfd5cdd7156885d9b1b3183297773ffc3ab52d1be82faf66bc066fffcf9260bcf83935d807ac2bf8b3798f0776e6810ac9fd01ef47bf80b7004ddc6201b3ee26eda192b6e20317927dfc276c7c22bdecb740f1dceee20101343697c64d6477fcdd3435fd455cc33489b8040c64a3afd83c8df43b2d241238df5debee5768964044a40ac51858a1552aee76f3d381b2879a6a0ee63cfa410dcd7997d9ff2fe0d729901be8fad3124acb02b4ab8d10575767dd20cfdecf01fa4f51cc551fd4b0f6e08dc06fb8475f22d2ac17593d6576e96aef4f045908d57412332f04cff7e5cb6ff05ad63617a272be76b4c1405287ad30458f81f52bdda2ca083dd998a35f667710924a31922731d8790000f699875101bc4103923f08f54eacc017f152e7fe45153c83c118010904f6ee5d6f81ae59cac83ef050913cf014ff8d75cd8d7fd078a8327ba06d5466b74d64a521e77af05fc57539853f68ed40fa912043b985e0abde8a6ad079250510583caf626f78ddc3152f9f7e3e33faf7af79f17e9ef29196a040f2668c73faf61647675552cd5c599bf5b3338bfdec3299ee1bb7a4e7f79fe7d2c62e6d8923c85a3754aa8ee00652295010c7f0e2419fff0d564576b9e785eb1b37983867450d93c0027e9331b907a680c2f43ff23e21de25eb8de014a31a07eaf29cc24f54ce13771faa5c1b465637c5b2234da84b9e8ce8f7c2f29df9979ee0c7831b76e0637112c6078f078748691b5c949f2a597668af5ddc04303cb5143b4fbed0bebfc95d9be991ca90d7445e15acaa480cb5038b2c9745fd663246b5fe2b69d9de37f3a898bc23288b17d406b9159b99c6c4b3dc5e2bba12a3f8e84d259bf6c0efd6a0717e58032d1ba49fec9e3c7d6bc7ef73e3d8470cd0ce7fbb18da35c9f1058f635e5c87d63c502b0084ce7c1824d873fe36db30751637e8f1d73f0acb2a7fc505da49706c136849b1535374e8dff13651fa91ae19b76277aee68915acc02a7cab61868791f417aac8209d15c61595408afe2f2a4cf828223d98bc55c16e4caa5beac871d09b3cde1e1289cb6782aad0c5e66717039de01d41fabf179db5989ecb17c9a3be6fcdb723e5a6c429950c1d0997a9c674c89d36feb92de921e0df476aaf2d349e2ab87c0d041af691b96c9dd8f3b5cabf524f9bb0e5e19442a3df4672e7ba81ec831cc89af39b08635a1f717de3981ff9f4226bb8b904df6e47088ed86ef050ed30d25c09d943d3e3a5cbfc89d082f494cc29db1eacac8cdb8ad9dc5faaaf288dbbbfe717616e2d9cf667665bfc6bbb3fa116b698379a08583143a770a2770565351bf89c7df49ba6ce30ebfa6163bc6eb76665fd42b105c7c4e640a428d3d893da32f4489f372f09292dbbed4e5f1f1125513392fd01b4e0dd419823dc7509f94f8f1a3554d88cdd8e907342033d47b7d263092ad12e6d65a775ad0187c0c8114d09714c89136c3459591edb09f1dfda60ecd0d997b96e91eb8d0b74fe66179a73a8fae061b4ae7108bb3a3733d5ed76a2ac053988eafef7a25325c9d3f5d32bfab7d9e2d2e6587ea329139496c4a1ad61d00d5d0cc75d11a781d2e50cd1e888a8a5d038e04feea82d9bdd08de6f7e3d397af362d6a763a57060ca68a75b53323e38648e780ef09a2e73f9162383de6f691fbea5dc4245dc385db486c30451f01b6908dec89af913b60ed8c052b7a6e2fa645789e7237a1c03eeb54171302d247278a481b91173df0010584555d90367217aaa88325dac7f382c05668e2a212df34326415c665475f9ce7b0156767c2b20bcc32a412284c167ec781fa14dc6c2a929b7c3ffa58ccf1e3172a305ba6641d050fab5fb44d1a5005917f8a0ec6a080cf61fea868287c01759448353edab886088c3064d0e38b81b06b25e26c7fb64a14f3058e073e630e20967064ffc769ce0d08c398707a391b13145777d50ba125aed78de42eb7c65bc0b763b3301440415a3047872dbb9bfab5edbe3042ab04459f9db838896636e9856290e341d2c5d0b1b4f16d080d4ab888a8060a9be92d18b28f7f41bd9e056b63e7060c30d5802c3eddc4be893abcfb0ab64e4467f4b0a379fafbfcc9f1648fa5a4d8c9a12a79e889f54b118d65316d968cb7a9525807ab51f8c80661b85a99b8bdd98f023bf42d3ed766595af629fdc90e7249a797a9548c39c6aad966a5b41e35144c6e905d113ed5f40be06a048b0ea88ad124cc931df464f4898bed026c64b63ff4373e81e5b7eb9e4beaa6b16f3ed32a062b005bacca640d135b4fc9659acc61fe432ad668163bb12412eb8b6271a75da9124527f7735f9d8b0c70c8f50eecf952c2df603c9f7dfc94a104a4f579ee2b760aa512e4eff4fcddac58140efcdb30a719662cf917d1f3d72e9c8435c9e7ad98c85176e016a99c0054321cd72f39821571a595062b917723bf61d1443174e5091c31ca4a0d3ec26d6ebb5d5705c02884241bd92e7c1cbaf89ebf3223aa31876d30eb8694b749960aee1b54d3752760bd69d7adb36948bd8dbd13581a92c2e2daa48fa50e29908a11bfc051a4cd19d01299d1ff2a441db8129f4259c38b10aaed35eaec18fb5e9e338505455df8589fb1b8389c020e36442f52bf95ff20e67d8d49784261c5644702a528a9f7e95fc9f4a021ffd40758d36a9c762220841c80ebc05f33f056bf9bd83f5d3147b62c3adb3ab53633150f29b655bcfecc4828c42e73c4e97c3d8ef98d6613511596786413e5013e5bf5c6f15694a4b1ac9a3b28e552fba1c242c2e82db7ab08981666e477b50630b1e5c20beff611579c1bdf3d18b00f68604b227754966a42e614505ed2cb4244b1ff79759291fc6d9db6f0216eb973e9f6dea48919100e6161502a8c65293f57aee31e761576d1ec9e3e224fbaff73bab8555d88b68923b91af9cbf94fce076003f28e03c03184349a672108ba0d7e322fc9659ce8571c4044ede011e4a9f5b49cdaf2e384e876a1c0a4b004fc3e817fa589e48757a589721791ecc3d3f7b9bcc530a88672826bad026d5eb3f2d7201afadd36431a2bd83673924c6a68c3505c9ffb3339cd25a77cc3d647d8c6b5ac44f062b4fb5d1bd76e46e04381fa6c28f3ac8308ff1adfb319c754f48ca9518b6322f5e708c1372ea51386b7fd51d0feadc382758893599f49789cb9d054c5c50146fbd4933fe81bd4f7c2cbcd200684a0dfe0ef5d617fb07464383e40b735f1fba4583c77ccb8dddabefeb9d0c1b2ed77a92d776d71e40aceeee4b3f7c0a98178cfae7c4eaad7c08a9ef97b009d6fa3505adb7609cb31dc52c814897a6bf359a97741c0eaa91a4e2a874b5cf9eafd6f5f055f4d7e051bf1262c43182771528f8a0e63169377adea435036b8c9fffaae848a47ec1ff5820dedd2981a573588ea80e702f49011618c9fe28fb230d07fe73d43673d21228cf4c62716b7e08a5b9f0ffd44f2a9f5e7af3197216e21e927b8c2e9a881c6c3772cccfdde0a0b8dcef025663d6db91cbe2cc134a219b6e44fe961cefe01d2f750c0df5f82c3817d1a60fa0de54bce0b05369c78e8de796c7d6075cf13e63508ea3861fbf6b8d4d4f744339ff6cbbe0330051d77ff9e64555cf9a2b55e8173ec6fc26a7f60c913fcda28241ce76e53b6e85a0f611bdd1c85d208a388e85af6d32d9e42cf994a513fe21e90fd5446f607cd82da0ec8106526c734e72728dfccdd4e9a3426139167dcb445290c4903fa9e8676d399889e6e6cbf8deea66f7b6ab1a40f7048691486da3f01329222d4029440c7c75540e36188a901f32e4d10c575a3b879c122673c50c389a32b06208731f4763e944821ad5edcf00de058b1476d3234c00d0f18dd2833bf90b73fdcf490aae2f694238d64c82bcfb26d3b313e80141bb91c516339d7ccd1dacec01dc63e968a5400faa13994746ba8eb8d50149dee259354f27a280ff6c8c284483b12ae6dcc20ab0d9b9642c15862c9bbedcf9360764603da910ca730602d5bb34a82731c6747eaa005f1949e003cc37f7b45248d17bb15bb53a4fa08a657d0a52da3f9d1a2733105837471c69e2f26ef10bb6f80f794e1d6c9a5c045d2c21fd7e30205ddf506d14ba0fe939e88be5336954888d5b2c9fa7b96063e50e5f0bed8c0391e3a2892452d0ba2b6f5a1e38a3fd58426aaf5b80343580cfd9934d5d3ac2620d538bd3c2616dc11350d04a44db8e3bca1517fa83b2207dc36ed4ea796364a0602a485acfaa8e67b4ac4dd14183d1eeaf999591d17fa6f58f37cbc71acc13f239b69ee74e6cb6df10e6baa7d65610bec2ea47ceeadcbec61d3a3d5c99e2d1d8ab4a4ef528bb6a57d5f5ea10bfd9311ba43955b2ff82c16ea12232e508185e7aa18e4b1c5941c51eb05af458964b4208ab095726ddba72177024e08a1c1647b5a8d0276986f959538ac4f84621fadcc91a14d3c0a746432a35323a3cd43321cd2e185a29d3b216ad164157a6107239f164614a825bbac777ff03d8c75f03d56122b59fc85247c511c7b13688ef51740bc5fb3a695e2094d7d4ba4feae3f7550030f16126780e5382673afdfbbcb1cccc7f3271647821b192fea027f317100793af8a376dbb4718044f78ebdb003d43957e4840e3ce16dd9e3f350c8e1c6fd78c06d5bf70ddc6075d6f9844326e48de44f88f03ae6f437165201f1b20c351a286a5220e6cc3115902cf322897367df84a9be8bdd57a21edefe0c366ac095d947c924c722a16f0f96517d28682e1707901961e4365c1871dcd9feee87a268d0002cbca9ef3643d431c0e1e394f61bf8c554f3df1f59b04fbd025568334d585cdd24af04b607d05b166c52f8f55ee8277bebf7a0f5855e19e181adc2b1138ffa6bf352866fb6ed2b3f8efa3bff44b00f1e1e46f0aa80b7ed1082292ac69c19652c0933506d35c7fea001a43ef2670d7916d4b32f131e594ee1a2d98737e91f8a873783b92eed04e308c3fd006d9c00afa4b8eef8e32acec09042def02950b5fc2c69bf3fb600e2c32bbdfa20bdf96cbc82ff4e640fa280e76ab4be5f799ec77bf206c5ceb3028bc008dc799c546484ac101a32902b2274a69e9f1e395ec24f5c7c1dd316ace9cd09072a2a51032161145a5a150f7debb5e1dadd4ba01de5f7d830304f66701c60f276316e7f7a4f103bc441a84ff6bc416944ba1efa0b46cfeb6c252c9589180c52b4077e8a3d43bc997cbf544124b76752376a67f6a7127786632cd344b906c77ae79f2c86224ba6735d2b24e5d70394b344d3a520b222cddd421809ad126f39224718b3ed0dab28180d4cf714a8715b9c31e61fc0a0db4b968d699cdb9a9ba1d74d3e5a53abccdd5172e4ab4d21723febec99a6386f5865e12c20a0b41d2676dd8e0d3a975dfd345178c896ac777b1e5320f42cf09742ba8a1968ad7672b536e36d739b48cd10959f961ff333587e4d3c36f5660b1d113006f945d0f080bb3b58249d5714b99d78966774a8ea381c858ba8d60de70197cbc08c6b396f91238b5b53b508bafaf6f5b8cfc88f8def496445f6f21ee6e67162678ceb1ee946f17768f2cf317a78045b4e25e1a790c8d78144d5af80a9559bcbb4bf8235e45cc297c4db51c03ef61ff4ba997fa8788ebea322966590c226f7d5b07577aaba4c13fdf1552e1cf0e263d4870dc3167f9ab7b1a79469293b0e845a3f2a1ff4508c87dcf99f7f817d4baecd8d970f1fb62dbf76d30261c4617276c4363e1f2ce5feff01eee66a6a3c03f6a128d15b4c183310bd5caf9a123d8053293c7f26fb3ed77b5c844c26cdd5aff42f5830bde80a7efc4d7b33e58933b94b56f5edb6a88e6019a4e955d611a50b925fc911f57f54cb8045df996eead97ca22174cb6f6dcf225fa9b846810cc0c2ef8c6b0b0432d43551ea9e444853db1ab61eb4bada806a972868addbc0168bc6373c8a17390ef10013dcc26ea8d00f5edbe4324efefa5beb36d30d16f8b2e419e4cc6d9b96b46d24ad4aed3dec455c442863a452ac0377a6241afb6f05add841783d744cb4a2b0384cea6bb1f0470ec5558a6904932c2a630c7214b6cb4bcbf2f8fb0aefd64016dfeabc5af4e2b4d90d7978ace0a4988e898a993ff0b5c7925a9c9a143115267da57ea5ab69344cc3a72ee820e12359f3aaca32db184c5b4176e3c1aaeff9e5fbb84abe50a07e28e1800459a0946d635f3b84c656a2ef23af501f49a28258d65c0a37aa82c1193f5d1d68139201e9b66b777a0b09b2f88280abb0ae1b498400470ed614fd1c8bc82ba9aaf522b256545d6adf2f545534e9694913673b4618aa10754f3ad7b927a7f7e6a27a65e3000409d5246b5ab30b261271596f4a11fc86f69a306a2d8068db908d39720d1a27f60dc466cb3c1feed281d1fc1dcc1521d3d3d6576b3727d27813b7e9b54039ce0a997482adf67b928d6cf9c4b22ab1490ebd378d04579cbe8e7f0534f036bb12cb37c6ae2e7ac8c6ad5007d8f6a3f596d26417ce57bc524c8c0ca340d6dcbcb269c116962990e99fa3fab61c1174ac1d037b0507b40c9923851df2c821345c377c4f8c0bbf938b40debbe83b05b142ef2df8f9ad011dbb96dc0d2ba6443037f946d0288382f1a30b4b1f3719799f3564fe323ea563c28669a405bf9f40a45d471daf13e7261aff9aa84fa344c3abe5dcbb8ca30c8e4ca8708416393b24ba6b1e756391b7da5e31cc2121a667a8bd1f818db5ab34bcf584b162b58603972b408edd04f5148ad573a0bb761be6c88225d4d7c8abb98c0c0b8ae3801682576cb4a2da5704e7106b100eef6dccb2e279a54919f83de6f27b2b3168b3deda4753402364b7a8df7c42e44d7755989bcc396ff7dfdb44842280138fe8de7a56fc31be667d21abfa197bb86135a0704dcc2b8cce57709f6a0961a717516601033383733400c8afadd84359e99ca2c379c0dbff3044928611290a4be2742bec869e1160dd998a3f1ce078bb81795c77e04ff015b817ea8ce2fecf2d59443f389a7a3285be5f12d51cae860583290be3179b3e27a34af85d39ae14697570b5a0e68b9181f3cfc1a98a4195daa3fe1185f0f3d3a2b5fe024b831df4c51e204f3b5b0e6dd80ad0310a66b8493f2850825919d50b42d76cbb7d07c5af57e90a86435f142af2c9b89426771f043a56b9d2c6ba86f31a7f95029fede43bf7fb0b7a45115d0094b182bd904130209de3b66edd086fff994bb653a5d6079fa9eba7056721ddbf7dbe40600a0b427c44fee53e7bc7c6097af14ca349e121534a56ce4fe4d40fce39dddfb11d867b1c99e79a186681500dbb31bb98c52cc71f9c0bd3900c1a132b7e3d6dba5b5375ef2a3f9a556bd965e23c2c973b5a14ebd1df00d2a55a10d0f7c2b0675450eb2865ebe0743a00ba89ee1d718d87f15af0c7b9cd180678daef640bbd8cd4b69bba1a17c8fc24a3718a77b774f0fdcfe91b1c19d32d46e9618db01ef35a4f6bd2cfac94a194840c4cef9b7c70e45e793da65c4108b5fedc9cbee84f35a20e463e80be73f1d929e0fbade6695fde2ce5851fb649b2f1c44b82d57ec115d80f2405a2c8eace68e27d7ca91e736678e8eb570fd532120d7cc5f8f2b6deb9c1cadc91d57b33cf74b38a12840400c10f081c0110a0b8a084125b7cf810387d874bc2f0a0b68918d890a0b7bc8d2767839fa2963a98c333bbf4d636cd487cf52f4567b9f2e292ebdb0bcf6e945ee72f45de74a278873bd7bf2c229cdac7c13ae40c76f2d70f0a9fcbc1f2efd87ce849bcf48dfb560aef8c1617eedb1fc8cee42cb7ca4373dddbaf55523cdd1b390b3d4ddadfde0ee6be2a988ba6ac98301b8515fd969f7eac2d9865cdf2734d9b5d5d0b929e618cf0794295d1601aa3dca771b368507df922d3920cf23a31b115d472ca0fe837f475609d0ecf79c79e4d9d287538d9f33a9b9ca16698b304308768bb8ca0d8bbe7721a69f041513f609bef19e7c0cec32211e66398a43c2fd9c6c10d31fadcfeb8ce263dcd4be16c7ff00aed58f4cbdb4d86c2ca1366d5790e319118d209d51623dd4dcc8e5e79d4ce2b764dc49a81b21ddce616a3374cd91508e97e6490492a3d2ed4492810b99e894584c7ade51f10314297e6ccc03590383121841d93a76a5c073a3025a4f3d2e148065440b7313bebee06341ff6229d747ae67f3acd0dde100e94610b8d03582124fcaee5f373beec1d2671d8a71403a7f8eaa61fbd229aa4688f1bfd83cc9c6b24fdcde9ff147e4a6372bd52e909c8629c4e77924ef55500fbd4a15ded09539285f17b27bfd9a80f5724ca849cdeb0f8494632cdd46b74bb58b53bc4adad57a6d9af9fc04c439ec42c70b23da87f39e6ccdf33b385a1f28736e3bbb83df189909ad821a964b2e651ca8d12a63709335695f9e9b2f0e9f713471e2116269626d31a7f665dae985bff5c92d987bdc598d527a4f5f1ade7877fc39bf93b8b2833cca5872fe44e8ecf22008737f8925fb9cbfdf0d0f9d6cd0fabed79c7652e898c7116dd46eeb3acab963207c9a49f5757e5ad48cb1a8fc698791ab3bab3bee78b1c808f32718ed967213644205044049959dd11f37d7f598b169965895a1ca563458fc1659aa1b04d855b8610b64170f1605b0ba4c799ca0cee72c9355b22c9f0912e2ba2f596eed16f43e07cd1fcf5df7e129a69cbaff0d61ef3d37d848e42f105f77ad1969db8e468a605742b9141f581a4cb9e6695529b43046af29e26db633eac9074ae984816fb132e712b86bc936b5055b989cc32f01c7c2325648707c8e2407a5182334613aad23369a9066fc539df63fd19cdaffced02a5e935b3ab431977e7948790e184573d68c5cb293437241c40ab20854f96038bdceac70aafa14a41b823590e17e48b2803b0fe2dd1798a58ab60055df7b6b7dc18e203cc9a22c8ff456eaec2cdfa378abd41ccd33201fbbeec88164d84a27d09725397c0bd4d6689b02de3f4c5f6e95d6e187d09aff4a8ef7f1ce2852e680fb2a6e27421e7ac4b58f72854bc53c790c3aefe315448bdc5b2e783d2baf4a0f1acb7c956470b7bf10301dca9d5ad2c868f10ab75a0a97094594ac617c8225eaf3cd61428772d71edb87035edf6f741dad3caed48290307cda31f1fc5c09017a9270d5a399570d362222f38afda2fecee5a6838ce126217bcf6b42b5debdff9777c3c26a13c77e9477450eab4fabe81fd699f09664a267c4d57d3d3ff9509bfacc4ade1c36184e14e056dd3c38feaa89b33054698ff3ea95df401e9fb83062ef148054b02d0e93e71643c3d9dd1a57339545e0738394d1e9bdf89a5aed4fd295518c15a272600288a7d12aff9a9ec6d52b23724dcce7c1c7ee56f78889d49c37c23f14b22e86e2ace876ee7e2bfe42ef9211863984fe37e86867943f21f02a55de4bc53c04b56f137d8e51c29afe505abf2eaf6870b40f71c3360fc496c851208e9b53f9a357bdc25be695ea0e14335f671de8db7aee24b0df9e8d2593295eb0661266d0eea9eec3e75693f561b4ef4047188aac62ab646b23b1e2bad429cbec141c63cde48df863fc727de5ec9b798fda598444dc99783a8fdb47b73dd7652c5fc653c8585b5afdbdbd4276ff9c705418dc4323ac23722a63d1a8f776efb8943b551b802a91f0700a9e95011b6f325756f5b31d97578de69be5b12569c2cf4257ba334301c97709c1a18e4b0befc268734b5f4103781851762c62c15e4e707efee6a47c23d3be5afa43e17760c3717731c2cc96f547e9676a05298f668357849f860f99feebb8fd2b86052079258fb9010661c59b49dda3295b3a0fa2022fdf71a4bd42546423a043870e35a541ead7c40f00aa6c1f9e005e9c630d6435f7eb15b6b8c5f05d2abcad031f69230b5b1eb61072bcf436071a7015ed18b7065597650cd8f8706bbb9cf773087ded67eb1fcadcd263ace89b4c8a9c08ad1b399d2ad69e520a8962c5c4c7d42f7fb89ef942a51029213648e87c6a99b93426d481e0ed33e54d880ddf9621ce1094d86e8b718030f9e2ac03cb9fcaf72ed414030ec2f4b8291aeb166d959c554841967681aa634d5a6d967f0ccce33bdeb39300ed7054b813cef29b452a0ef107590969bfd06c071a68e90313ff3dff54d84b80f803f402e7a48acc808fad213ba4e1b60003f0eff4093abcf5feab757807fc6092466a2fa62392f3133cfc1ab47096ccd145cbc33bf2c518843b755082b4099f42c088c5bb661101ccd2fca0ab54eaf73042d529572c46bab47ccb01ee7ac182ba62bbe2469f0a49d09e42c448853b0bf0906ea674a809d9f978c878c0d12536e5fd2f87bf35755e51645af8c2288edb1c21af7f2112652dd7d5aa8efda0d20301c366fe071bb3493f1a730a64c9a7c0abec91f762ae949237556a50938a37e87c55d82b3ae9a82ec91caf6d8991d49054259e26ac22db40b93dff968b97da17f6278e668f4832a836c30e50703cc570d6e4909a4f29a599ecdc2054a0c3eb72707400dfbb397a786a7f8ebe140d9a77f1af56469aa027c8cce4a603aa266e1387acb2439b2fc9c03b6a0fe328a9750f62c77ab1b104d580ff179c881392b434a9d97539b48d3f2ec5e8dcb973f1abf0ce9a0de5b60b8e2fc84ce9fa4af7f94fa02865fd9059ccc4e105567149b801b5feaccc0d65b6e09b55452ef8225420731ad380125d791577b586d96ccf4ecbc13dc4e2094f9f79d1d90cf51ccc22d4125495014224b01b0cc004f5980ae0511da2813a2e0c5169d1835fba96ab00e761023d2d61c01bb7cf34adafc843759edf2392eb468a31ce8000d5b02f87e0de8fd8ad338363afcc977158efe03f9f88df85610a1c37804f1f006ec520c078f6d502599ac116ccdcc2eaf26d43d4e2dc9053c575805f52fa10d41fd4190373a2c46fa0f654421f7e043cbee510bd8a3f856c33b4c9dfbe39a5e50b27d7aea3cf274ac2e693c9b4639ae88db8cdc8d316e7636ee68302b0941947f585823b00ffe4780db10a73ca6784754787f66a03485dfa159415a10bdc001f54a5022982aaa8674f7c8ffe1eb3de1d9ad3f4d87e90687d2704b56da68e5459a5153b06c2474a575c0c9ec6ccb105f521d9f3c049f6fe74a09cf2dfdfc7ba074241f3df3c262e75972d14896dcfa13a2843fe488f3d3ac5e3d6164d81fb655a0175762c7430851f417b2d937d6d7d467c849b731a9669486060f373d1f6ef827f4c33d1933f293bd37b4e191bba4615e7713d0b161e91b2fab7d245a50757ca3505b8925fb2bd065f37ac6eac07a38e372bbacf41f667c9a4a76967a768edd8e93bc8e62becc4859fc220fba2ec865a9936879cbfeab13392375ecfaa6d00c0b1e81386094db0de1b6e8b451aa5f82f4e1f8effb3712a0a7017215601c4ae71486264b53a23509236ae052c0265873601bbd29ee713968317556d8fc4ea9b2e095c0107417d23c876c49f3866b44b42ef643d308a900c2f3085721855cc24824042ee49c2e8b9b4191018c4057ee75a31321329cfc740bb1100636fe23c62e3dec6c384e7db8324e669ed5ebecbb55d402b5dc212935a163d4e7749a7bf1014244967f0fb5b3e82ee6d91b04e598eb4df603aab026e075fed377051007ca8f70513699c01539741f20d07cb5ddb8fffc1db528d5962a22dd5b373775a938bdbf69e5d40d898b61f98a86f968025aafe1dbd6e5bab9d19b8621dc0a47eaadc32b0e77b0c0ad102020efbc6a063832687c37464fb9a0d18b1ded437891e1ad9112316b82501af2bf016ec089fae25427d0fffe1371041366ba7a0a1d0d8478c8ee236633c2109cbfa128642744b4055f33f4065f6353ef135e4b0cd8a1fb82be7084e7d3df8d2f021f705a4733024a67d9d85e1c51679acb5677598395acd3cc8682011624766b40c356a42de3fd10b550186424b060f6387274fc8681c947ef02f6a55a8f72f90d043f03648cd85ec4d38063ce8234bcb331057a53294f08dd4d5f15b4ad5976214f8149d16b7ef98a47984ec03a7a7b8dce52a1f12cd12a83fa28e91a1dc09cb1b15edfcfd04cc06d397c675735b8cc133b86054fc966df7460c380982fd20b07241472235862b12c52c4a4cea520c993e818e8233e55e7d496f358b466a9dae1ae6eb2c36ef2ecdbac7f126d7ffa9f710e8f3150e1ae8e023b29b29d17e39be2e9e2736aaec7876af9fbce7a955994c69c956e5a472458acc9bbf92613cab7d25e5170126c9396a434ba8242055785201aab27e539441500161bcc073fa2440bdace0023529e0282119061706fd7da158a98149f9572405661192e4fb120994ac2f57a3df80e19210879572647737f0128a072e4531b50d5dc8884b6d9827ab05d3708ebe6127c1ad61c77d7fd15f639fc3365601587ae1576e99ec8dbcea42c764389d19fdb5a18c8cc005bfba34cb3e0124456f2b3470422148f963693e0088a76750dbad93765b8338d0b9ba47b1f86b13fb2bca63abe50347d28b26d067716837062d0f2e5d2e888a8e63b6b6387aa9ba3e32fa92cd09adcfa28a8dc1c19620c64275b0c7e0a2b95d7a424c57d3e780c7e807cdb112f7754fa49308a59d47092f673b72d4eec0ae6f1836785b52f1be1fdc930b25d1c19fda49fbf26ea6361c51ec3f5ec5c52c2b0d8898ec3c2e7e02ccaee01a644474fe50dc4d846f5d405fa0c08f42925248d552df57c3105100668cb7380087015cb56f182e99b298f48c5f5c913f809dbff1980c94a5aa0b83c42b64584607d6e7da3c9e511808b7376e3c2622e466a337e3542e0c80e72a7a86d028506ae43a557db3d90e03f0e80af12a1076e676ca2d2ddfdf81a828382334ca6edb43c5cef3887205cd83d506fe05d37eb4ddab2695b8cb394adb5d97acc3c6cf812553c0ea3588056958f093244297abec1cf5f034ead12ac28cf48f16572513a1707ea360862fbd20de47dc13327d0e387308918aafcfa96ff20faab27deab3aa6e15ce657f36f46da5afc1fa434acb60647cd6b47f5170530868351fecbe579796bf7421bbcc0c2699c50d9ea6648a1c626422fdcae213c57daba0858df41024a4dc84035d2133b60091c1535715c252979fd80f98b80d04207ed3cf1a57f2ade634d3ccd01ac10da2037fe92e425fadc08c315aa184d0588fba78773b178e0f77a49582c40131e11cfc7dfa6bfb8b115f7b4b59ea15522a7cf3ce4ca646e066fd32cb6e68dfca8878cbd30c352470825806b9101d4a7a4acdc4f5aa34a233d74c53820d45c67faafe3a67b0f6d1960b10ba5aad8288b822642b184c212f6b75c98afe51a8aec93dafa030fcaa9c727fd734ecdf655cd0e324e6f81097b821359d97ac3dbaeda5222010053d7d976a83754f23a5fcb24d0f46d4f220ecb3dd3ad90c5a4eb82f860555a07e86f320bc806f49c1f00eb020120b080a011b0f39fc1545e53724044cc402b170aa589432fe94d425bea28990626aefb5365519b49c13dd36c9f6d85953949bb693031113c14170dc626c04c993c785fcdaf951b2b19b23c7f937500ad746ca3d2210ab9771bb8bde5e367e574c7ea9122031df858fd622856ca14cf72eafcf4371c008da6181d9d651ee4bd8df8d3e0737f7d3128a50dd9751b289414192f15b947426df9f2beef4ff2f07d573b9daf199936b05a9aeb83c4f85352bffae00f7459a2a7ba4286614b0c78bdb5a54448e7110fb49386bc5034da6d8c498cbc95fcb6e766b451f0b21d149c6a2c94b02d4927a50450c10eb67230d4b98ffedf175cebfeb4696ba8e69eef14e5b86ad890e7eb7c3d3db6a809d466c19f434ad2744f0ebae8ec7b8bcb58d1e52ec1e1e71293511226453482a71cf1aca47d1edc5c0c4ed485d737b34ef3ba2f109a8df74b87d1e88a826f8c433e3e5da4499b9579221f5f3b176349bb8f466c26323e65b15a3e6dac65de39a3f7838c0718cf570334ee75d9f0f8448b40f577ad53c3506ddef4ed3b505e5028641061ba053d897437abe66f90a00b19224fb006090279cba9bcaf2de667ee97e70935caae9e1a978cd835d2dc653c19d9fda2b8a4504fe1fe44bf8d6e77249c3ca773d7cc7b57ecaa49229539ab37a1ab393bce39afe8c57b9e0872dee6f95d2a1397d9615b550bc07261bb32f5f6f261bbd5263c596a70bae221a18988b778ed4cca22de56f490ce9a38171da139e26fcff5abafa4275b1c8603cbdf094de0a0f7dde10f25e882ad01624494ac4ad22805663d1b1221e10fd4c7a34accea2082db8e504f0ad436fc1ce1839bb00b26e83dcdfc27bd711578fae4e3a743cfe71ab3de3d2b0dbeebf3ec7405c3eb53f30a12f10eacc6d80020456a6e6cecefe6a63478e61b043c25edef334e790634f8fca9e1fe1887c880758726a47c48871900d7dbaefc7950fec378c02210b68a9b07f15a55679298243d830a98a560e0d1a9a03ff92b60fcc1301282447c0251214548be0e7ebb537fecaedf8e2614ee271c525b78d6a70bace5ed263d0bbc1cbcf320bc0b0b139760f26948cb7c393e0ccef2b76502b2eb4185d441aa3cb91257adb9b4a5972ae7d1692d9bba54b9b3f76d3a76230903c0b62f2e1f075378131e6256140d7431ce9499851dbc465017932b7001e5fdb99db629de8cd2ef10299e91af284db7ee24806612c3960d04ba27a855929bd1295846f6eb2affcdb1014db915e75aac24e9bd3fe00f53cb3dc35990263fdb75e8558f964423e533a713093b8ae9da46074815a3926829b1e423f60a012ba932cff6f3ed67c1ddcf0fcf5971ec49030f1dc24651a5af919a28a6dc41982e337f90f80f4893f4f678aae529745244b8207937a49ac41d00a608cef91d7b57c05f21c1f0b2590d6f904f145882bf0a67f4fe634b404494ff49bd16bf9d72cdff482b66dd4d86b1f7f0b292e1377e1bba1c2cef7fc268be3eea3b6b83cbfc098038828e9991ee2fdfafd947470203e7e3da90ab9a0a8b3c4a0ef36a2dec75ec5d20a54de37f664d6653cdfb1070cc92313d0e1517d9668762f68964f5d252f91e8109623817a84eb37f2e9743a7f5efe0421560b6617032e07cee828c823d430147da5fa2538ca04b15f14f706cbce56a4533804a2d34a9f091d63742d86d2876ea94248a98373992cfc0e5cdbe11f3a8fe79a8e1482d41795da8743369ecb78ae5233994cd258c106ee5d8e0ff1fb867319230be9c2e10b0aa224d6ee71d043711580879e9289abd525e4a0920edae5185bc3afbad3cb2606d9013b8772d00e0563c2bae0add1ce1a7bf07191d2a3b8f6a5bddc481739cd95756a961035f75de5010284b7627835346aee32a6e63ca5f0bac075773a0e84a260c6d831745fb089fe07b9d7b9e01e78f50377b3156b337f9b3ab4c97648f040ac3d80e173eb65914f789660ec3d311d0a2ff83c3ea79f7faf353b182c6d38250a0ede02a93151c1774028e6b3ef0c96bcc2fe7eab1c2b15d8a27b509a9308b070f99e6c155fac6ea4e4ef724f8237b968d1fe753c117b7211247efca00a8ad12e4f24043046e8a5ca5ebb7e1989037d5964af311925e0a076f6abd143540fc25059551d8ae2c8b851962632f1147e0e98d04bc352c68c2a1ff952152630615084f01fec04163536c53439aa683a33707acb746c9157d7f58538b40b808d4d3214516c0803d90172e50c94d2a533ed5674881480884af1d0bf62562300319e43026e297a19999e67bb4d17eed82011891bce1f155f06b06bd54ca6f8849faf8819ad0ca353a7ccc1996b1234ed3b7a5fb5f331450a5d45664933430fa41a30f81a42773f6999cf7588310c962f31e843a02bb6a8615d940458400df13373d5a13245c628d8731212bcde882c8b3519a07347168d19be29c4aebab840cee6bd97308e5b7609e0b9da755c7c350635b1ce8c903c18e1553b79922d828e868b6e9ed4bf88f5c25cdb20a6bc26d1d59ecaee7aff6c7a81c6e7b013ac45dac4ed66cd499662733b4231257d1926a1148ef592ae6f77db9f5499988735aa659a5d8ce13ac237b40ddb21e92d9ddc76662103900950be151a61be4bf3c2042596e5b137d0fb7b4e1f9f50386890a7745b343fd3136aa5d95dd8c7d185f90459a6e1bbea7dbd81122f171b223eae993bbf5445e47670a1ba3edea97c52e424c13b1643d0acea72a0fb13e24208b12fd07e6d2aee72b73666819d92c81cd37e73e0e506a2504679b35818497a32b9bc58b2266efd03809e11a33a8971c354b04e7380104de0fea1e008ee9fa33ae64cba166fe7670bed1ed1c68d6858eb0ef3894d6822b8c7ff174ee3e1d232797ac7294285bea962b3c3b44544076b0932d299f4756a2b43f41b1e9a0043f4a5d3867783e8544499c2a536fe36f9a7b2c87716f10b8c9ce77c38669204bb296875e851d7fb0c53d254ed2eaccc0cad1bc91ad30ef261c98abb9387b55b64f4dd215a09120d6a24405000889d6369038ded1745f0c88d19c60f55e94227ea44804acfe9de8f5a2b4431a2dfa270b192f1b20551d55c277b0b961d488f5a778aa6ad066027efe050f624adff3d0fc1ef6f8a2a0f81623daca510348bb616c4c5a3e5ef6137ace1db93b0cd5085fd89941c6f3e0e65520224163002d99ff7a4714dd10adfc152ac9c1ae8b9a2c2c8b4cc71cc89fd9026e86a3c86d32537fa8b0aa3a5920e9229019fde28bed028122b4e750b99f45223fd5ac04272733303c5f62c9646683a63df97170681da772f0810e7bdc56c7280f3156cd59d97237381dda8706891fe5118a2c985caa41a45ca9adc0a1687e4d1152ff3602d42185aa168ba8fab38e26c509b69376e19605155890fa499b173b49f381ddc8f08078c6cd5b49566e0b8197222b62df6d4084a3a7def5ada793ac93b20d447dada4f0833277af65d6088fa0b8b9740d77b43fa4c05941a326d1f5d3432d0f72b8ca4db72b32299c7339904b738af6d5af21062215d9c8f992507b02096073dad41c1cf29e62550d373f3419f988ed1caf62f55937c6bb47002ab5452febebd2672181ed30c84fc4430e57af806b57a50cebdabdba80609697c4807be198bb00703ccfecac32165a7857d2463089bf6d71dd339e1cc55ede10ddf0f5b20a16b772a1e995e256d48ea38bdaf4ad90568cf2387fa3f88839e9da74073e317c70275135434634ff1d657a2724e6721d465de04d5b6c0579390c09c7865dfa88249ca3856f05eb17938737979c870d2ead8f70d73d0cf830a387ab5bc3096e3441b9d3eba07e7cf9a8b591ba5ad2b7da7e5d8c36b5ecd3fe812c0389a7668f9fdaadffb3fed4479c8ff29743bcb3d8d699d1eab9b1e47fd2dad33a471b091fabac0670bb03dd602958c4bfa88638eb04aada6e3be5989cff05017767ff49a3ff7d2da879e9a30f4b4b2d1ace2379f1976e381e28ddfe23866685b9469501ce08b9cd622102294deb64fb4823c955aaf4df0bb30a4eff853dea52e66e1e43217db05d7a3f649947462ea45915e0b3dbe9c26cc473be380ee6b23eb0ba5846db361807a0db0b4eafcc39f11b56263b3a6728e6f9398e4e9edbeefeae5504783fae9e5c6d39b295ca0cdbb6d92404e65999cd28828f6f180fd8891cff0b73a38894a10f0970a66dc6b31aa1b45676a4de928af73018edf6a4818f4ff2fc796d65c0ef76c3901cc827e7659ba8dac8801d486c6a31762cf5bb2824b0ee9b84654135753b11c484f94dd1f4af26b9ccff4a09092a89a6b15efd186a1a132b461aeb0e0327a8478fd1bffe9d07dbf0a87b514c2409d91b32cf506fcbe72999439dd24e3ae67a8dbc811b299659073e001f3b95b83566e27629d4c79b5a677e68293b210e9693921712ed0d94d2215f1fcd1c660d4e10a6bd03e7014065a961bd5e23506503a66710a9669f334bb9e5a34105435008aa1be67574b9b7423ecdad1c341269a3b32cbdc0bef87cb21c7af4b0b472f1c4facee8de91a10f3c113a6ef0ee98da8a2655cdb21707dc6480c9ac7418d7fd7bc15bd229d839002849517578c0a608c11178a79b02cb0c85d986c7a1203ef5aea58467c94b85757896906b1e6ddb0a4258724f2e316390e18cbd7ee58209b1cce42db905dd8bb5de57f07330db1e2bf0f7a801610d164cd11c3c42b2249e58a2727356cc331f89518867d934898ecb44335e88912df9c2907e87dd1212680cb3be79baa98e8e8cef3312d2b6ffb72f93bd6d3c46b76f66310c22472fed70c8ccd32adc0c62c672d82660337039640a5f91d9ae30f1a7dd2261bc09e1387cb5214f63d119d9a6ac84333412348fc13fd3dd3810dbeba1578cd149a3653d69a43f2fbd39940fbdf92d3848b254f97b3ca5e2210ff67d8ad0ce23f138fb2e111a294523c54d5b1144ce3777666e49a33f2aca05b3f25d997aa0491a6f90e96c390a8edf5eb74102846556f225bf79b3e710a5afdd365c26aa86f4aa960f23b9feb1c23f76887ee2e1259089053fb1b79e587cfa34f64a3e28763a3009de5302b782d7b024e38b88c1212968bcd2a9eeb50001d8d8c74f9d5384b81bb0c7d7741ce76e6d73311ac703595fbbeda027cd318daf12c15a7e289784efad75f531c40053b001fa787dccaefb1a0c1127ecde4f346318f97298fceaeb9a463758803211e0befe1257430237394c6bc70752483a73587ae24237d937c53e864bb228dd0f88ac3c61b943fbef098281e6b52c095536580587630b828881c160fbc15db6463b7c3ff74dd2a7c573c6b29fbc2613365265abaa9873da0f4a4aa416ff967930e82d140462cd4726b241ddeaebba4e611593024aa28e58fe3c99335a9d0760fba6a5cd8c9b9aca027db235d761b76f76185dec2bec5e82782d7ee1db86e2a1b84c18bc9e13688c559bd8a79a4083074b1cf73acd3e46f5081cb589253da27485d1d432e88104730cea6ee8b59af3f71ba71618becc1f78271e00cdda5caea311f5cc0237afc3b4508124574e9d9b8299f77ebef7696bcb9fd60be9214b991700293737f236c6b30c6794af4bb374dd7321f296226ff36c3f7b297741ff394d8161fa6673486ad7c8f6d6d2fb3fecf23edc7719f6bbd2b47f0d8699abb958653041dd79e5996ec877be7420eebd818b867bfe187f422fa3f6d4481fc9d93c4a1e115eebd4f9c7c24f621ca7d66c8587e7c9233a0182d9d1743824a275e6291cca4f83312c42d9c70965c3b635eb68c0999ec6f337d9ee24e895eab53a93de5a445357a0b5736f76c4eea7f09f841585565dc3c80b49a9dd719f671111c62828e1e9d2cfbe98412d3d3babab033360d33f0630b9c39de84404aae0444a5f4cf5a591fd6d617c35f45f9403c914767743be3f1dfad26ee0df59f123e5c0ee14f08ee01c62c628a3054f88a1204170f29f25e72ef9f6eb4d8d8624b68ab71519086cfb1b3908045d1a54a9fd7b5202c4ceff07187f60fe0f482f557e8cb47d166b97cabac50143c465e9f3574ece39cf7b1bd886c4d8cb59f2888f906960109f1ca4463c13aef793e5cd3f0f498c21cf8acef800cadf56af491cb2474532c701afd7c9e4c3fa809dc4820cecf16072620ba6548c52f2217d1292451ab02fab0c97cbda4a432c9352db26581537b22a6a531b2a95fa90c78a37d94f37e1e105d3d182197989d162f9f7e88361c55693357760921b5e7ceae5968538dd8c5e75b1ebb54f0267e38418ba7cdd94d006fb6e850c0dc4c7c0dcce8433f5193a0798821656a23a315f694abfdbb8d29dccda6f79040b033c584842b4f8f86e3dc1d346f91e43d551a68197c31401ffb7c4c042004d2c6980ecb04f083e88a7893ebc41a32b892c9683266787dca474540ec9c1ad101d0d7a188ae98050f1836a68960ea490204ace17deaba30b0f1089ee5bbdc993791530da2c9457f17a500603b9a0a39b98298789793b4cf02a627432322b205e6931f05788bc3ac410fe141d5f04fcea8ce29514e5df0bdddbe403284117693fbb9bcce44d3f395acd49363adac72bee0a2994045ebd97e6665c3a9ae79f4140cb69bc1d556f2653869af47c7d7ad4644659d39cbb6a9aab73de7fde7d85870e46c352b7a6d0faaf161f8ed9a5afb26774efafe2f80931d62c47db6546ab56db46f6c3faeb91c70873660b0d620f214c454c5c60066c72d04b78fcdf975657eabef3d69376c0c596f8af45ea823d2f988839843d66f7df0b9e6b803489f7d26bd440560a99f7efc31e34c97b4867245a65dac4b94acb4a42d5b5e7bcc728637da8509b290432314fc56ec103ce66055ffbeeafb0357c2ac72a18bb482086dcbfe8f0e2a4474a470b16af2f5a39503c84503b4ced6022f79a934342bd5ec09bf98bd95126c2965d71f9e572fec7e065359c151b719d8551df1aee683cc1412168fdacdc49d0eb35a008dc2457cc05730d5182f0b50fe4e6e91166b411fa350f722c0539eb22b6cec211a94eabb4585e1896a89950321e362732209e99d12ac1c01a745be7881184abfd0dfc71c46d2bbea2b25e97f88c25969059a4cdbe769ca207868dc8b4c24262c7ca23f7f388748e8f6e94ded705bb6d95c05a315d6356fa707b5dd06aa60b51d867c81e55e189d999067385eeaa17adb8ed78cca63817f82f358f682dd36280fff4fdcc581a8f6a92b5445fb898fdf2e872a1c8b950fe3f4733b1deee6153f4177578003df2c2f03d5c5e2b69bab0c3c9c22c997521ad2be6b370391253001a485dd1ac4bed59e416a4c506d05c4a6560ab9af5edcd278bf383067ca243b8db57c9894e233b217dc6fa10a54577abe4ce4d2d2ec3272487d62ab0cacd375221df08a2328424f3c8ac1af4f633f45f50308a8ec8abe315ba51c4227151217ff3ffb1a9256d60a604663cbe2c0cff3d6eebfcfd9fe29296818ec66bac9162729256e8e52d751ba972aa1235102adb9a7ba4a07b31013afeda09e587d4757bdbde77a73f7a56cd1bc7ae8e3281191bd752aa8e3fa5f390b0611384345a6224e03ac1f5db30a87941ff8d96215cb8b1dde7e10a082459993dc6e311b0fcc271121c25cb3c4042779cfc3c5cac0707eaf17a79c96cdbf8f561968e68bb11cb22839a02c7368ed1f8f75c3cd6944be6b91e5e7a69fb492569a3296fffb2a3ae9c4156523ef9904241b803daaae0120ca8e69c63ba3d81b7535f621fe8d2a4ee6646de537024caf3ca5de1b4adbcf789c2a37f38adc92e18a1cf91dda85284d653faff567727808134282c43a3f04822ec6c95ce70246487a3e2a48e17afced2e139ea673ed1ab0df22a576aef90199b4e084b8e58d3547856bc31ed1ad1d2157003af8044d1e28d224ee43c534b347db44f8e48f6e3b7405b4e2c05cd1ab8d578598f10897a539c528fca2642976aaa4775f7be655fc4cc622de308fb2c64f879243f2f6fb006f5172475ee2fd69a5e52d8b41ee13905c11e628bbccdd467c47dd0c8505fb14732925d4022e9ddd2ca1b618d0553e9d92dae9e0446cacdf64f385e6d104d3b7e89b22b1216c6ced8f862ebe2a0651d8cac9eec4894f8c5aae1a3ffa8ff30fe917753b0570ea7ae279cfdf1a9c88f46f98ef8b7895930830a60fc8867c13800986c0dc7b22c2a970379bcb0e200493587326f92abd886f899201d3b3cbb9574f2701c1e8cab9928bfa3fd268ae0e18cc09c11f73abcc362565016dfb6b871a7f8a75f572651a97c3e7edf98c77adb8da1d32835e6f580e73b222ed766cdd22be632cf95acdd0167a46590af0b01f8aa5663aeb7cdf4335e8ede416c0df81c8b5e771fa7ed2b12c9f816a1523820e6aa6bc3ba1f58c64652c39dc5fa470f86185fc9073b971b9205e09d7a07a058141c34c9deed39aa25f5a556e399dfad0bc725069c7d6b68d0132355c20cd4829e9e9f463e2ce45989657ce2db7b37baddb9cc4b87a1b028d08564635a34d0cbb1b42e3c67ddcd5e749e289d6099b5b0ccfe48850b17139cefbf0643badb7606b7b49fd48b3eabe4467270462d074594555c006b39a0cca1c32437f3500b6579dad5090592a7645a9b6b0174b723fa2fa82cffd5dd705543190810e742ff2b5b776f32d0a675afada6436e8c85e915d114aa1c1065ad820e2bf155df6436222a069c6218d22db7f509313692578d390f3438b9e25dccdd08ade0dfaf918b02192c4716f35288950dea5d72d168cacec54177308f16b9de94197cb19a5d35d9459dc28db9bb09b63d88f495aa5ea75bd566208b769fbf8b6d467d1d83c985ad79608c6f58937b98d8c08bcb654da05237d8cb1003d5102aacc9d184deacd59958f2663f16685bd2743a14681c59dd9af702dda9ec16100a52f9b30fc1694c850fe8898ba81a054b393e5c960c723a430e33e54bb024583607460794aa02e75041fb1242965d580d94255fa2a2e1be50cad251bd1ebd9d144f9d2209d7b505e3efe04d40da6c2e5bd6b2d046c647608a96271a7471f3f1afb95fac942fc1f68b65faa4309ef3634e89f73ed4bcc428bf7d5c4ab12645b5cf3b3c1bdb229569ba6a464f38acc89b55b37d5d30467d5e197efe37ae8437d562083191e655b2a99301a07ab9cff92130f67cd1d82fdf33478e3dfd2b0e37973265f58d9025bc12f3cca3bd41924ba3a1d433e8fd4fc014ea184da868bcfbb16efaa90f5e4d6588864aa90fc6dfd324e2809520fcfbe885d8edeafa11fcedacad47d572938fc83a54b8ad53a49323827d05ffaa28a7cf7c49e8c0555136d81b356c506c1dda003b5b51c08b1a8c019810d7851b98a6bf0b5c37d4d9028899222be9d09f4b214a887a4c0eddae8318c32aae6e239a6ee853fabf75d28db6e645f7d1d021955b21681dded600794f709cf39640df29127849eaf4a0ff06b2153c4d27a6189ba2223145aeca6140cac0b46689d81a042edbc38dbbd5ea14619775623bebf31f9ee2b8fa3befeaf0f13c0bdfaeaf3aac5de58c62e908020c1de4bd31a3605ea486b7487ea801b2a061e77c7e866a8041a611ad1ded711ff0567794a56676f4b65d1584fffd627329c488c150ab9bad8e6cc9c738b0a9d130d529d97961f2c676d2571b3f86461b00b55c6fbfe2de277d1473466f539892bd539f5b3838ee8a73d114b1c43c7c7f89f997a61110fb459ab137e39c306d785d1051ea1e62f1d4b046e96d806812d83feafbb6fcc94ac37642d554364791e9e7c5ed42e52215de614b2671a4b8234948e7544b4e7696ac5658d44b8ed864c38ca74636e409aa8594ee1ea8add1e7ba25515ee5a663810e7bbb603b2e38695bd506a6138cf6ae51188c5915ca34707cb02baa725ccf4257afd65dbdb0040f57c4d5908c28cd447754770b60514681fc73eb39b245687b88b2c1d2346513eb75d0259f8a65e78eee713f90a0c4c4eaa77c181ff125301d90f96440dbf1fa6c7c926d624b97b42060cf84ad9eaf072c98d1dd58cac2efbf8fa8361c1de100f7b4cd17a204b0732d0cb4a14a72a68848b012386b145f6dc95816352dbe7112d04b80ad5d8315f9184de28a6f6227f8ddf27d43399618f08dbbab70ea31486e9400e5f1c96a44bb10e49e45a312b9be340426800b2eb45107e846669078d3846be1f2738d964a8d4734f9084ca50930be3cd8f87fd6f1662a5cd0a1e944ad56c977d4a6af93eef3ed3734ddf245c580cd3e96df1556bb7a4d4f104fd5047a43633a127c2cb37e5298cb17544799186b69510b7db4910a331d5328ec6e0e39fec7b68cfb44de200a7d2161c58740ed7e569b36d73f72d84d8216dae259193417ac1d253538882217f75665a506d2203ea339c67e422717bb0944205d11735cd421b3431785fa69c0788af6356440a3298d0b1430563625b667a95effa6cc90591492d90ad353feb1f46a58710d88f7925607b6da3cbad12a93b2c3d8e928f287cbd47e0a72cf7d13b64d14197cc49402751a36b529a06ba01e8ef850cd2221251025c8624793063d3f7c7dbeb9c7638a823f84ea9cefa2185e8b45dad288c11981b3717b7753110c4b7be6ef5124a5e15732ca26cab73676dbc2ca7bf29ad7370d86979531dad75f2412ebb58a6d105793500b0c9b21540378b74c9edacacffa9d3b8f0229a2f795e72bebacaf9b1dc05a4fbc620f4105cc62dfbc13a025d52158d0916063d9e204418801cd6db9761ac16b36f315ed3b4933e547f63a4fcdf5dacb907213a5e3c08d5acbe29e360ceb6225ce6a985ca20c84d8615ffbe21f43c118c88c96dc4dfa1e85398a730a52632c1a8e101618811274cd6bfdc41cf02105a29992e1a1c54514059c3a5b031c5fcb43fa735129f8ac51bb016793bd45e24da7036367a0f609409242a575e458262fed4e7b316ce42277812735cea8dea54725c14953547842d3d024e5d2422918ba051248504a75116329510978de9fabc899ba354e60cd5f5691f0f322e5f3e8f47d561091b978d0bd908d715995e0cec7855f1210027a31543aa2a4561a8d1b0b66a991154a1cb638af1b9fd6ef5cd569957cf8d40eb43acedc885a1b9bd4aa7c79c5a3e4d3151b26657e5510d984bc88c6a292787ff2d730b851ccd4347eaed4f08ffc2c98836fdf459a80dcf7cf1b2d29cf5e3b06761ce3b80081d8c5a034274d0ac94ed959384a7e0498fa2b30c1dddae9a59b2f7bb9d3992181a299f73e85745ad355f6f6521bf0f6d7202297716c2641981d2499d3b8b20620bbbcb447014cc5521da00650eb6484b86a06ecd7b226417d5b025695e0182cf877097dbfccd7cc61d84eb459251fe05c04ca0141cf911a95477406395f1af81a5618dadb21513490d8c8989b215bbd8fda752630a7dd8472ef1c6e57b45f80a1c26838bd8018f4779b183b0732f6e1575ff0b2ccb82b91dac642c396c214dd2f8470c4fa71e4b70d05356cbc039172d2c6621e0f4155f20823898105930189b82d44e5062601ebe498570f983dce12690ef54d89d48566c535a8bef7429446a68dc6c3f14a7f38e15a8bdcd5cfbd2a7965f660ff58cb2fcf4b36ccb4583708ece76667b81d9efcadbf753c0e0a6c38b1587a978d15c0a8dc4c322d4d9ce28c56ecaeb2d8ace48109129962badd94f0eb01652f5c6560349185ee93088d48fdb2ccf9205779f45b7f27009196aac563a87482e9d00386c302c9958b2947263c96201cd9d17a168de3a29731802d3f93299fdcdebaeb311dc94aedc5a53504ce4d6d5125cc2ee2c80b73c88a443f51d32e6fc4e78e1954041c96a04e37ce65517ec4307d62ce4bc98368960e1d58d8ee80e9a12699991ae54b681dedb610b11bad766a10c567aafc9b503276eafca564f984b38f5d93d9b18e242bacf7f1e84a4bebea1052cf469d7fe092f4b89fa4cbfefa66734754996135567aee88b41113b7cc7e020c0623181d524eb20a736d6a03d97e7cf9d022545614fef3a4355bc151ddc755252b325be0b86a8709b9c5caf31a2b18dcd4879fe56ac1a9201d80692d6473cdea87bed19af2d652cf60c06ba93f2e3cb0db9cc4583229311bb1deb98385fdf6aeb88eeb3f1fd5bda586f14a4da443ff90490e4047d6e61e9996e3b358eeedadcf4ab9be6c1b77a9a762d09e11b77fef79cdc5381d2df0de63970ccb5eff0c178af02a09fa9d07798f02f8ec8a4a9dc1e8c6b17355ccb7e6a7f8131aa1da40192eba8f4f27f1d12d2502745a91fe60b198fc56100ebfa6553c57f4dbd580e0c5608877104041eb125ba6e20a7174f00e5658e044208a1cb9bc3304815891e9e01013dcd3a6fa51e01f5ba2b3602deefcc40506fba6e64979c0aa1f0b11876a3f2f6506e801052568bf3a884f48a88dfb6db60616fa23105424b63410f6681f8e4480ad13ab0eb0485daf8d88cddd8599c6fa07dfcf17efebfd4f9c515bc935da284a5b518d5fe2777738ec4e0135345e369466fcbbdd1274808b593e5a50d29b78c8f747df81c2872b6806303aae44c92d97729501e3685ddeb7293b7339548c472a7701e26d6a06651e69f239711e26e46e2e0f3dba31eddd0becdff0e252e1e7ffd3cd957e936ef05b8ba3006d299915ea44c8399c56dfb6b62b8b12c82eef02bc61f8fecad8ef6787ba833a18c741170ac317d779c6daa0d4d911a6c4bfb729490dfd7bc1dd380ff367db2cfaae5a5ef176cfc1becec65238a674db299f6dc4c6c1d066aab0c23681654dc610a1587410d210f497f0233bab3c8df28bb0de6c7c13316b215e1101255c75f76ca791d3aa754c60110b0f18c8a8a202239a627f4642cd141107d434f4efa3efaeec8c7f818055cebd6a9043ee3e2fa290e60204c50243a11945778c42024bc1e0e3671f54ada794c59d1ecf2ff9926a3c9eef1ad0dd3c4a6dade0e664553a178ef1b9a1ed70ab1d4ac4d6f6c9c510304464b4679e0b62aee4e8041df6a1b55daaed2c44880f20f857a749a4a2d12b0471389938a83e7b996564ff58539c57d4238df84125f637dd6fdf68a819faa686caf8f3254b36d105543285fbd0d74e6319bd927d111b497870e59f5fef03dbbe2ebc0c72c079b5e6af6ad74921d782f036a2b525a1fc33864d9e38db910a0d8d930a92388cbcce91d7d30861acea08d5571fa3b7a52c1c308faf11725d269cbd73b5d5cdcae30dbf43c981a16d2e6674d8f06eaa09f989e37b8ce3c5401d27f2b9460b86d265be660afdcb191ca0d5927ddcfb5796acff59915f3ee63e27922591ae105e415d03f0767efa66812bfef5db2418bc58b6a979d06ed24394c4ed9ab6d2ba26ee563c0deaaa861e9dc6b9afe807185832d594cdda38bc51a8ca9e0e3c2b312cc4a0a998bd9201ec8b769bdb696034e0f08901ed786b9f9c2076308f676a78b968f3e122f5581b55fbf8bb79e08137dfe87ffa46ab5a1d7b9db18bc305a202813b68687078feb52be7f490bdf9b6bccc68a4c00ca136f7fd96b6cfb69ed52db86151bf582b488004858ade456d3649814312548187f1d46691d4ef51b602b0e6623476438b2d08a8b61aeef92a47474ed634bb9a99464d954bc5170d4562a080ba81eef81c1e290010c60e2792e903a98ab9810425a1dd9219ee9b49bb4060ba6b222a382b89bb833d0f91ba041bb41bf5043c234edf47cbfc90d267840d36f4a5a5903d73bfb4f551fb3621faa3dfcea05c7d5141e9a18698468ebde4148d2306a62190df4849472e5c1b3340e140b24c91015700f9b5a27331f0733d1260c0de7027be1dad60daa8a27651f0579179713f053b053a0383b7e63b8bb7c2b84098f211bc4cf7292a640a55742bd5932ed5916a18905bfd9f933cfb4163aefb3e5f6c66d1f4639a28a70f130e59ce01a03de4ec6ebc3ddb9601609c671528a0351cf07936170d1c4fc877aee45911a6a2eca1f344865978030675ac7138e8934ddcac7d0c04755e1074aeca68a4ff70f98b90dc05231523efca31ef737d74b2aa18b79aa6c74bfef36bd1adc0ad2bc4c97c323626866257a9118616c396cc9bf4f4e108dfed7d68c1b31e9593711e79aa854c41e19ee940f237c1e59734c91c0650be098caa036f47960e531d2185df4e2c05aafac87f3a020bd6f79855f22e2ca66c7792a67ff64fe839e560bfad685bc79b3d65dc9188298836a4d57bccb1afe2bb0f1a94c21068ceff84f88b9354e6863023e62956b8dac9dc6110ebf58d42ff19157070bd9247f075374d10fb14972c038a631d5ef2ada8cacd3ba5ea9bef818242273e332cccf89e47d54e115d41fa02eb3182e31753a45f87bdc012a2e434abbd8e0da9da5c6d2eb9edd8a13b5076c65ed8e946201076cbd9e6efef7c77dae11a4bbb1b07b078e10977c64865e5cd124d85040d3e39c81160716d80474186fb70958c08a61c1ab69e935948aa4c305b52e04844e2514711e9be7c4afcc0d4a362fd113d8dd21b95f66e56c03c18aea439860fec321c606d6b896a28c167fea27eb2665c04086602717e135a40e34fd9198b64863319cdd7b5274ea2455b65eaae687d00132a61c8a3d36da203e9254ba6370d3a151eab977cad05e2a0264e8ab2cfc4a48555d4a456596dc53f8c23877a9b8703455ad214e69ba0d37b8674438a720bf768f7b664cdd76de924cd1340b42c31da85526836618f72459055016ad489633bd3a93adbc4c9b62ecddb9b55326a10967c0c20e5aec12e9602ad12d5b29d3b02106a2bf0555dd9a3fc876d243fd5aad440261e4f546fa4f912f0ffedd54ff0154aa11ba1aa0f2318a2d3bf978b0f64c830ae2ba2af532314337833739e798168f9ff1d598ff8bf54b33c0696d7922373789523cb313c8aa41570785212d4b26e9b4ff71678202f69518e2f37f8f0dff4ab96f49f3008a727988e64bf886cad23c9aaa9e6082e67f5ff4e46a7527497b87d0338e38b55d8f76b68cce60e0a3dd6e4d9767123b879980e45e210b694ba2441910db3b069f357a0e491d0f79cfb2e872357b6eabaa67b17e31d8bfe608f44f640953c8009e0655d065f7e2c1661078dc96dd554a2dd8c427ad178d64f74b0f5b8f8a0ae7de7853e04c242ffb310f1b1c002eba40abe814d8d2d4c416e963fa9f3297de128f7beb347268fb7edab3f4e923ec7fa1b7e77cadf3e0d8f256068d5b3fbeb5d26a7c56907ba232cffbb410af60642ce216f66573b0ae9af5698230b484118bfa39d74918f85fb1aa371989ce9e8ce163903ab8cc1e538f2c090e69d6137422fbf3ec08b0aea89c6335477955a3e1d0e33d445e86ebdc99ae6b91a591466eff9e36214b321b0d3fd968f11b197b46fe74d0bf95f2e84675e5e731de92ca0df4b1ccbc4a1e08224dafe62c11d31afc7b8bc603061458ce3c1cd2bffbbbe1e75654b1abcd1233452851cf4570187e595960215ca3d2eeaddb992c5c9a17756699fabb06787c3ec5be409005f5b910d92d317cb6f88d5312259623cbf02f4aada78a8fbb81b4ada9b7134ab454d427d92a0a0e0ae33b860a0963a844b93f3a22398844e8f49c2f4e0e5221bec59b711e8bad25d567a7ffb0d853c3c130a9e0ea3a5438f1bf90465de47f92a5023f5e66ee08c01e91a6abca5c8b091197cf703175d14d7b8af4f9af62f53a55854a11b72ba0681985d617f8fa3372f788efdfd713d094102c2536537d35350af7fc5c0e0ff4fd98cf00dc41c5b87111d5e42325c994ee1a65b02b41531cad782aafbe2671292c14e6323926f77902b14fcb82433d7fe369ba3bbe322c82df0af27bb62807d8f2a59ed30a417364a18bdf522700dd51c6c2abfd97ecf47c1f65e426513defd99b0615262e58a4960d7d3a689610c443626dfe1f1d05f620d19c83be91c5cccb27d41c8b9a61360621d742e8fa1762c7b0c715de92c87b71a0ef6ff5ede0d8479e9eef11e7239f32d4e067b70cbc805d325668a5d74cf8f7fdb1b0d416b4c75877c05e1942561369ccde1cec2de180787d4b5c939f2cf969ca8bd442fc83e146eebe898d61ae3283a12f497f2dac3d742745c54ff8e3fb2e5c0223fd6a1a2cecbba3320074798f62f557c4555f0aea418d896f6ef24e609ba0ca0e20a1fee3261df366b057fc076579b51d6065effd64b269b12654ad503399c7419059d5c6879adbffb4fd644fbf7eacedd7adf6ff261e96317434ecd6efb694f9b3cefc0a2e818e14cb207d069b4c78615388dd869fdfe63c30b53ce4e28a02199571d795a4f3babe748480cd032eb90bb8723c787961b5a3185c4c839d959dfefd1236e1a04493bb8160a42ffc2e6ca1ef5d6ed80cfaf0f0ef764733f3ad39ea3def07ee4489c827ab019f7b50e46e668da8ffbdb13e223d6c0b8a46d8ef40eb17ad00c8a41837492d8ca920c4b7418038aa6d78171a4f642236c9edfa4a393c530ff883f969b9e4ce4526b3bc6c758f5640248f74501e9294d596cad1be34da8e6bb387f83d822f9ac63bc6cf04e14004a05c35d06fa2bed6422c22a44d046407dbdb960d71ce5beb7f15f31a7f420bddfa7d56c16065d098d24ba2f82e9bfdeaf46819411273e119d62a620269e2bfdb24cfcbda89fc585f7710f97f727b2dde73c15e29316f77db03ca749915dda2cad26242c6bea86b13ee8dd273a3020459c2ccad6a23cec8b45e2ae6de0a0629f6e8f417e54df7dc4e7eae1ff43b831c3ab449c0c1ff3ce0db527001ef98ec43d9948779ec0cf4d3fa56d0e733a2d2be7a3281cb38e02488fd81b1412c545c690f5a85ef221d38e48b323f9964632d1b6b928d8d49ef3137c28f00c18a69759ff20f98da06279aeabff93f579a0f6d9d5691fcb314ace49626bd790d74881499fffefc503203f03686dadc2898b9bda665f0ce6daf6b2c4dc3c0f72790a1280546da4d63ca9b521580679f445ff51b7217f219adcbe1fa27825880a48843610ac9b181027f7a708f55eeaefa13fac34f76b95ed27d0fe6ff8eac55b0fc6b50f635dfb1d7c7f814dc99f264df538b967bb1c7669c6d4b09ad2eb2a122761860f9b10bd1c13f52d63b005f6ad72b829823b5d7e9ceb4f5c5efce8b4d16b0368536b75d4fe5d10dc6cf75c0304c27f1c7dfdbbf284b85cbc51fe38e4f8acdda4c1badff3c1562bcecdde2fc1afaacef95efd79a026543fea2a84a74399591596a8a5bb23454304099534eec75f70d0abedb0cec50ae801a1ddbd2773c3a0c380a32262a8fe8b80ee6983e4d3807805147e41e063e7be3d0d639d05e287eb62c5f9fa0a02fbde2c107038f0ed550774f0bd5da7fb3717ab9af14938545fdfac561a3e1de8d1d673b087d4bb1c91161b27363f4c889369e2bd23d6f3d7292c0efaf42341f08ca1f9bd4a9b081e429ed7290e97822f82f646b73508dabbd1afcd4523a91ffbcd7bfb6890ecc6ad74d19958b780fdd68318a9faf3b8bdea2cf8c6333f20efe4185ca310bc75e3232eb6919ff26eaac43f0285ff830c0d8619732d863151982e93f588fc3e5d17424766c5cd0ebbf9ccd2bdb756b0d11a27dc8718a489ee8b1eb3d134f638072b3c72474ed594a6628654182477bd6073ccc95729cfb5f6fe37f1df1e0361ed806e8efa7c19beeb7f46beadc2d868f86171817282984509304c8c54b0688fe45c594bebe1daa7df587bcbd98628c64b3f03e9cb4251e13d7c95cf14d77f358d9513bd0da7fdaf634f30624e55fe89aea3f4ed9b8819cdfca4f270a3e6accd4c4d57364b28d63e7a598bcd0f949a98021ff5cafcb05211d35f515a62f0e925455c7d7689c4ea479695d8fcc15211133f66e90e1b7d30b9bb4de8014a513cfe98951bd571d19b64e243624a6e6306321e9cede3cb4b0c3fb5a4c4e5cf2c8158fdc8b216db1f5a2a62f2ab4b4b8c3ea524898bcf2cb158fba8b2109b3e50aa629a7c942bf3c3a52e267d55a90d44781544f2ceca3da6184a67003d57f54c5e943c602fd7dc7b18dfbce2f645b6c3f6163f8c1c8d127ea3d62459071551b7cb1ba7ae9e8893632f03f4b0d426d7be9ec00a2f4577780372ce5c67acc997091fcafd23af9ffaf6fcc57ea2aed9bd1b5698fa67c104c2b5eb9d266ceff314272c210b1050506348be375ddd3dbfc32c15a3eadb862d3cebb2d4946b6e4891d9ff27fd9746e7afb3c0e45a6add0daae74cfe42fcef1d1961fe5106eb4861afda3d93cbe453daee85c6d9a32e1e17c7d99322cdfe9f606390cedce75e9e378a4bf28e1d9433ff3055ef602b17fe894e0ecce4b35e3f4e1b9a97fd36fa17fe16db5f552f2fc33fdce92281f296141e2a3039de8d48a1356900e56f70beee3dfbbd057ebd28138673d2d413a53417b1be14563bbf44b0c0328a138416f4a0daddaf6876cd267ec14a94480508755bde469eae2bc5bf5c9b32000b052008e1ccea06440493a8da6cfa1340cc72c031b431e4a81b99630109b178bff0a674a2634d918cedb2de358132791fcfbd516ab12dfe582cec3d8c0f56402f9a7655f276f9c4fb1ec1fcdade20d5f6741ab82f37f3070dd6db93e37e4f83f057e49f2d0418c0406b95f1042a0a1e94633946adc6255fed64319529882319a50a16f40c467363ab6fb54e6f6091231d9c8c30d44588cc27084eba0d9755dbaf2072287e435086a3eada0cf2dc4b620c18791937440a707e6215ead0004fa0e2ad9280aef310e64154fc2bcaf53928d1d677e626f9a43e39bbb019186dc44b334051e9d1cb3c9aa64b1f454759e13df0bc2d29ad72bb11b50b9a7057de6ee5685f181aade9506fd19096e7099cbcac9fc15096b71e83968a154df885e8c7fdac9adf767a7c8a9170972955206183a56b922e52222dd4477222a45451e59cce099bab0fc810f1ddf9504a91f289918324a865ab3d982b24bc72e7b6f4c1db4ed397bc59fbc70407e775f2e65871a833c7726955eeb0e2696719764f0a32d9604a731b7f930e5683b0bf006a55af18a63c0ee098b24c06d8b10add4b9701d809743b7b6a8e19f181104831646ca31640eeb0422cb2146cf8cc130b0803463e0a18ccc8a2bcd3949894859610bfea537d960760e5baf00fd8273b04878a0a539b29d7ba1f175f8a2097f6794d5d7f830bf7612c05a0a10088556194e8befa690f4e1ab499041a9bb0375020cb085d724f806fca6aed62f8f03de3603a5930b00e1caa8bd7a7de9f13818494586b2aaf4e7ef0d2d64b58db0b21020b8f6e827508a45c3106acd4c4abbb71231ff927a4d8a2db83a79aa9c348299f468a2b97540e16f8752ee97a81953323cc00e1acf9333f60c946b511ccbdc6d18cccf13c0b5bd4a6ebe7fe527cd819e0c1d4690004eff77f2d1ee9c8ed8e1496c7769c119edb73b3c3d7bd5af7f94fd36f6b6da4b3469fdf02e654d00506e0ddf3f7bbd3e1161a3996b37ac290d0e9dd832a416ead74a045db2004501ab550e88e51035ba3f156b8c9c187c377f6990597955d31507657d3ccb2e98e47397636458eda8be6d835d7ebbcd2a16cef33a225df0d041e8267d285bde3233362927ce26acb15b4eaaf70df7f773fdc79c157f416bd948590f69cc2940395e41cd8f269a323893e1c927af7a75f16c9b34f31236c378cc3b1ef117e80a6c58b4d911d713f7c490a3f15392a2efae7e944f8aa4997099c71a137884aa989bfdf4cbedb811dff85c234433432528a33a7209a28b14cbb93a5766efa7ed510ad887defb7ea9cf0a923f61157d8574d57c651ebc4bc391c0a20930072b124562b56b8d3519e6cee47ad3b565db6efaf91117abdcf8bdf440855ce9a1fe7d24e87bb36b5f987330302cbd45055beee21ca59d916c024be97e0fdd1d0e83c3f1a371a999f373973475a8af8aff03833274d7662754ab5877f592657edde0b9dd37ce3a333d051ccc754aee201caffb0c9dcb8fa1a2a02995a26446ec3424f75bfa4867e094646a7895a17f830555e669640bc520a5efe653ec1c8e8e9e4b0ba0c80c6a96cb9b549c96997eb7028d423436a1434feb1d0ab2fab8576eeeed6059916890cfda6294065d884d695674045bbdcf9c040afd2723596112b08c13a8cc69ff374879938b19b0bf867158d67cc06a056f123c262366a3798343c88ed4a91628fe12df01f64324e5eb12ccb8f6b178c886c81415c978a7c1e6fc8fb4cddc84833b3ae0b9bc94293936976451fd0909ba10bc720ea7ce8225c009239be3740cea28b2cf4f5280e0e39d77c56aec9e1e98590ad434b03400d2e126538e45ad217f25b415a9fb05637dc0ed51d78d11f8f61f2ee85c2a58f2fca21e2120eaa5443bfeb0faa5383093a61d46b1a1b43984110eacd9740ea4a57b311503a498becb00326554933fbc347f5b2f07a26c8d1b8b94e960923755921503c28220178908dc0c6a8780f7c094270c5f43afca7ae43e71372b3d4c98a0d4f01eb42c68b15555bc44c3479a21737a956f85abb7671b1c54c31e440a270558e18737d56769abd979a9b24223af31452b34a5c2bd29b68f62b33c5d76d04da156ffeeca0ff8217214b5340e9d2de52abfc1b6bd488e8e30e9ad64fee90224a377dc9331b4637c921058d1c4532bac8c4d2ea6127502479b7aca7e1c2239129ae0c3d6c4fdc8d800d4498dc17003484d3e664a5f38b516eb692e0a681bcab82d977f0cd9424e52fc69c93cb763216e0ea4c0abf91a5da615d8703ca7183b975686879a21b33d6addc2e23062918525c2667ab780839095821c5b3af93a2586e0387695d3402e4bab849bb7fb29fd117e6f7ba11c370fdaf89631e2b745c5eae0a9db0440aa83c89b12508ce71cbdbbca0b1c45684c55414644019eac9a126d2b1eabf5cc36ae9fb200aef292ec4eb7d5af3e0ffe688c20925ccc20e9ac122ba1d4d5d509c59da5de73a80fa061b13ed851eadf6ff3b28892544d45c0292fc042c646a5d37473c89591c894d5ecf23948c9ec3c40b95180c8559ce316acb87ea2e9003453609e22b945a4e5c61c7951944f7e7e31b3d809e3efa4396f2a74e0532621ca335b008d83cf694c86ae8b01ec9b09b22977d37e9a330baa5f4d185fb19900c45332804ceed2eab798050447a338a5d6f3de6938f0649550e8b4ccb23aa01e0b239a044fcc40d4156d4c54bdf4b174ac88a225e9bc283a6d484499ee19393008c4a4a58cebb0113f03cf4039bfa146ef91ee705a3587269a810970b0a33c9155b2e1df18ba226c21c050b59f7ead9a3b83ef177c6970fe382eb37c178e9d639984de94276338b3be3c5a6b8c1556a1deae2792bb350c8a1e63596183642dc7428fe3889babcc63d97f3062da7abdb31dd88141b8e2c5c37bad8177b3f3264fb25feae938734092c30286064e48ed1870c3b3e88a5c5ba40b6eae130fa4b4e800898f135a3d743899e945dfa6392ab6d414bd23a5bd946b2346a415754d5719d36d2be2e556136c0e49eea36c1780c178701468e6e0da0030cb0e645e0b4199492906d16357c81b82f7a2634c38dfe43b4f1bf7f1b71beb64f41ddad4c05381d8ffe11e38e216af32a4e6c7ff3194d9863f5a76a94e404a504ee03385848632da00b050d19c41af2b95902a32a56fad9b057d59cd6d6fb41286b1cfaf10b4d6797f2637ae2c1849b6de5a68b2fc9e56c86db337437c90f056023cd0cbd9bc12bfdfc1bc0d2dc3e45d1a31a4dc00531066393dd4210e1208acc1fd4c9882259d6e00621fa2e414f9495bc7bf21e025834f7132f6c9a97bc187f1512d8c10d6bb717d9331572864bb8851442d7d744962655d115f4fbeec2a965a199ca7416a041cb95705c9dcf6d891c7f16c3e4ddc88b0b9d9833f00de370bfad5bc9adf87a36619c8522e27abed3889735c1e832d107999ffd3401c0ec5e719562498ada78e9e5a7c2fc13598b8c113640b0912f7a0d52539a17dd5920defa8df58f5fbc55319037564840e7ce2c440c239514e10a69668bb8dca12ef7a8a9eb74584d35d0f7a49e7f4fa7d7e30507702e49c725d29067156734205c76c3dec5941c8a1f55e7f6689eb3d8318b9c9773a08338489b1719fb650614f77e0d5d69795c7914c8976732814377af1f211b83be1d7669cd198f67e09e8abc1b6603e978d0c649ddc019310e7b7657d3ece20a67c72eb2ff75994a4051f74219be26835a6afeea53331b6b8252bab6800d3c4c28a2874641c65813d9f6da3865c861c9fde9bbd32887bd18429598a9bf622e5dfbbd531560654ea46df3dd20ab8b7f56992ec987579ce139a24cb9ded431509719f0dc22a806d12b12c8ff366a96e1bb099a414d91f0f47d569b73d09fbdb197dfd2e443aea125cc320a35c3dd305d8b2941ee853034d2b41f27612211c9f7a044e78f6b26f6bfd6000822f03681f901cdee07792a2a5f178889e347eabd3484ba2f518ca66b0af19b36ccd8b6106d8dc18641e42203afe20022967dae91e2a03a5b6dcd33185bbb0db191c4d4750c0d053edd5516700357f9d734a43e6775dc09cbd2289a775b8990e893eaf5797a4e78a8d0524bae24634e35c11879026c4afc8c4ccfcd82eaa2b540b1c3f8a1f969cd71285b41d67dd602efbf55df3872dd238d3d834cf38bf8d46834923e29a5df7c96a3a07b59ea74dc3e064ca41b96f77a0e879deb5a9dd986aa832481e3fd08944a2d02c373a6e4c1753615147d123435f7f47421f3247d90e0e0aa52ba6fa584057a471dc769b86511c19c24e19b7066876fdffad9f9509d053a14605a453eda1b1b490baaa2f23a1771fa33d14f05197ec5ab028c00e01350a1b5c12688d74297efbe801f1e43c8d9835c02ff27172393e5324cc2368b12ed0af68d73abea029f11a89a2ef152b8f07228e2daa2324e0e87ea6ec288446ba32ef4b1c6a9a1d55a110b8818537b6c75f3c7c1369dd589d80cc0517bcddc220116e42440fe2a2ef4268d3cd859bcecda48a672fcdb497d8cc9ed731ca2ffa4164eaa541e30426322e74204ecdd6dd4ff72498bd6dd1e9fba51b58bf81ef7c678f7f330c14942e1911412728d40664a158dc8ae879f8a974673b3e03e011b10b9c820e32844fe507a9f0ec304147cd51b268c616d594bba70835a2f11879c21534d375142732e0b487d8c50322357b579def49024e65d036df8b82542dd97c7d7191eaa17bc3b932aea9533c21f7cab6446165ff31d9dcb1d0ac362c766278cf60d929b6e30d1a055d2382bd641b755d8ada09dbadc059cdb5c9c1d7349a7cf5d97b1833b1a465f729311399479487a2f78159b72092079882e90d6165f4c36fd38450d64af5b491355eaafcb7d07ab9d1eb6668971f6d33ce06722f8b1a5258bd44301cab1d943e82887619278b24f1e48b008d74aaf37aaeb6234a04342dd300746c2409819ba58c87a2ee8c759d49ec8b78312a14eab19c14386a20ba03b2d46dcd88b789c46ae09540d2a132b6a025451f2f1bdd18d5d38f7b2548876eca8fd57830d81a38af84d10111c8e8d145b304e483b1c0875f4bb52f6ddc32c07b7800e857877e08f11e064651417cc3b137dee640a10c401df418ce5941c75f03b2881925854a6e98eb894f1361776917960b2e2e043d1fec33cfc79bcc124a03ad017b631b63cec695be73103d5a65cad474bb74644a1dd03d78058c7bf60b647fcb37bfd98dbfb9710796191ccd15c425e6d0fcd9e7b43622e0d70f1785da723567185a6a9d01133443aa43a38853c849924450fcd2933f52c5f1a59a301a1f7e9a1f4c5b41026db128ea49657dca8142bb114f5b237ecb7d50597bcdb7d034d6ff8a88b22ecef22a4a82ca7c46231b833759729b47fbfe1ba1424a902558026bae869a228dfa7be4c99ac7d191f616bb92e4515ec0128cbfa83eed26325c831b438c51be85c76367f841b2ca5776393ba08f400043e3e460e13491040af266d0dd0349e04100f40161825b6300403658d4d0fc08246a20af62cc66f9bda6e0bffd6922befb6f82e67ec7c309c968d07213757f6cef29ba53af576def4c5481bf15bfb24d31957119310210800840f3c084014007400e50687132f572a11bce870e6468d0f2a3c38300595aa0347b680a07a42039b19b8e00d1b37b4b851e580030f5cd0c103b4e0645144278b22a74fd51395aaaaae20910503422491458a0851aee860e1020023c07225009d543ad28a2db4a8aa2c90d0521da12575eaa9ba5499a51e291268290d90527a9fd2fb5455d55fe95ca9ccbc90c925151995197c05144fd45079a1804e15422ad44974834d38d9a4aa6e3429a3aaaa28379554d54d269754d58d0d2adb735349e5e5b13c99d32459a9aa9b1a585249ea64a2840695e9c74727063414b8a3ba99810caaaa82a2aa2a59e9506084aa9aa4aaaa28379254644aa989fa323335f2992fdcf0c20426b0874e0cc3c513d00a18c54a0cf088a4aa6e2079c46752934f96aaba71c490460c99a969abea8691450c310406882862c88d9aeaf4657a4aa62ea99e921827c6c9f053d19050f34f262ba51e534df554a9aa9b337e82a40da66aaaa62fb6f429d4db50553789205255654e262c69aa54d50d1269aa800953553787185255376ebca0aa5c5055d54d2137845495ed190d5259f9a93754d50d0a6e0469c1182da8aa9b400061411d52d411b402d3fdc38faabae9e34b97d6d22fe874d4e1aefe6796fa8637d923759a405575a3471e55750af2f1627d7a48566ec848f3331851553778ac49f36556a7f919ce680fcddb121295eda9aa9b3b6eeca8aa3a2a2f74bc31871c3a6be7268eaaba8143055575938237aaeac68d9b3658993a53605050bd0f9d25315e4c3c3ef3af9878d048eb1364b2e17fa6f4f109c2d266a64c1189443f158d89470cd03c8579333d2893290cea1f0b99520fcffff44c1f2b41a653494cea6402faa1070ccafa98b4bc0f9d29134f55ddac610ae3e31384e5942a892989a9aa9b13984009244aa0461a4fa07173867dfb25313766944c5bf8f804f1942629cc9f4c271390e9e425859acf2545a5aa6ecaf831954e5852a5a73fdfe56b8fa98634a720d4fc539892a9a7a7d4f363a9bccfeca93d3648061f540f99e7e2336b4f89543fcc9be94905d57f22d55312337f262a68861b32c6a8aa2a4d894ab3105910fd54343a2a4ca1a3c2990ad5c39429cccc94292a50408556dd8451552310010a049d073851750d0ff0f20059fd5434ac0614bcc16f7837b81bda0dec86bea1aa6e42c037fce9b5b40318710021607c7132f5dc80e0035edc74c1c5165a78808a2ca8c0a2aaaa28371da86ccf15567403bc012d54414d3e55a8a0820a2aa8a836607d7a6ca8aa1b29fe4a55dd4401857d1a7c7c9eb0efe3e3c4a5c1c7878a8f0f95aaba69025555374c94826e55dd2c914455dd68a0b23d5b94a8aa9b244a269ef7493d0ac5cce8a400001d06e0e006896619787c045453b534f364441155490caa99992953da0c0ca65bda82da0243e9b99c4ca59e9f93a987f90343e9b9fccfe44133bbfccf2ccdd4b430d82db667747a6ab74c99f2632abdfd31954ea6d349a25028ebd363030c13a85f430f9952109652d0b55bea96c7b0a54ed34b1a6baa923a4d20bb258d35558101866613682626852a99acc0e6201894e6cbb417647b9a05c1a0a380286e78187daae774c6ce3f55d58d0576a8aa1b1d6e72682fa8cd8c74508044078542745058b38294293f3da9d1fd930a288ca95040281894aa5f03aa07880aaa07689229f900fdf4a012c0c29c315919a110d141c00874100081aa9a00eaada9a4730208b2c0a073424c3d181d13e61882880b0051020eaaaa9a29d4cd179d127caad203d160e2d12901842b6de81c20890a069130848e0192a8aa50bda900047c2a1d0380803a89ec088d4e01e0a84c3a05f0e2820b3a0510a1aad094a60e01e8d0210023a1a6161d9d4aaaeac64b55ddd05055373c5daaea86cbcd0c5575234355dd6cd14245478417543a2274a0d2110187aaaa48a81c5091107544a852e90c000795ce00e2a87406e045a533003495ce00be543a036095ce001050e9848049a513821b954e08595439981852e56012489583891e517cb6801163e239d959c5ce2a28142975eaa96267953f05a5827c7ca69534a99e5210991e144a023d22d47c3a7d52a71e5a4b534a89ae70464446f42aa4296d99a79f27e99c73ceb9d65a6badb5d618638c31c618ebeeeeeeee66666666665ebd7af5ead5ab57af5ebd3ac618638c3146082184104208dddddddddddf7befbdf7de73ce39e79c73aeb5d65a6bad35c618638c31c6babbbbbbbb99999999d75a6badb5d6e218638c31c61821841042082174777777777fefbdf7de7bcf39e79c73ceb9d65a6badb5d618638c31c618ebeeeeeeee66666666e6c511fa738d35ff6cc141070a505555f3f42ed49e1394102a1d2836950e8e25950ece23950e8e22950e4e20950ece9a4a07c78d4a07e78c4a07870b017c00827d1f138ff5316929fd0f674c3e5f258d4f890631b2aa6ee4cd951b2b15ea4b153bab54d58d04aa34938b89e74baae7a7874b55dd5089a1baa1004e55ddc070f34255551365ab04235326d3974fa1822609cdff17148f95590528a88c0955ea993f2629a9208a7afb676e8fc562bf746602a14e9f66fa48f1993e33c807eb331fcc9fbcfc7c6acb8d04a65ca1c10a8f1c8d2417315768e832ba814a9729533e35c94c20d309888b99275332f9c0406686f1f2654c617c82aca90b0c82e1e7535bc8987c6080415bbed443a6243f99610515aaaa42e37312812768e808859a40370cb85940354b3d3f3da78aeab13fa879e6e4c94e55dddcdc84aaaa861950239f999ab78c0905839416263053d3a28050f6abaa425355a11c7c5255a814aa078dec449066f6643a83424d2ff653261e526af6dcff09428536d1a4aa2ae85060ecac02dda366153babc82f5ea00303a6aaec9faacaf6986af8d307f9308168d8a1bd1de4982d604657e4982d5ca86cf922c18ce4173161a8f064d90266a26cfd6a8746832b29930f98f682aa2a64492529930f184e60558d8080e69429919deb136aca9411d0263f952692d090a3890e3d14368f5015005ec8648f1c4d4e6800ea24428d4e0f9449266b5455059bc544e12293285535654a26378c805054c6a4a922a664ea41f598e9b952a2720aaad514a2c10caacae77492d5e4e34385861b30600839c0a605958d1f7ad8d8d1450dbccc3f0901833d6c70c4246bb810628c0b28600263a4981066d63892470a50ac2044952915281551081b42088085131ac179020a0c0c12861104fc71720106bc0883061962c861110cfc1c324318d5066e30e08d4c20892a280b0cdc20c0937885266704d1c7146b745881092e415871a6833316f08207415429cac06142151208c2052f635c282cd904084e0450c31aa84bee00820e0fc4b0630e162010504451a28c1f616e00040e21a882e500486402c4ce117d68f14307a30b5452e603127402b95c808d2b8918018126b05ce0889b47424abcc0820b78f1631237a58a175cc08447e40234c1c1023f387204ce092d6411e20712f80322059fffe1024b6880074886e9072c0190c01011b081891f72b8c0c1d5c01840690c69777870e6872e69c080620b0948b1e685342701707141045c709a092c42002152ac88340038a1ca1e3a6462838605403820192c40068d073690441a61ac2fd09019c19415c2d8600d343d83350b10410e4ece7cb225860689163a9c5933e58a3964808133ce6c808e90c51bed8033a39b3011e8c408a0332af861660917b21863c6068540c00b268228801915c491480f3fcc36cc2841c6111aace171c14c183028dcc0050c4c98915205036470f9449020496e3c5964843c2c0952c38f4f78ce448123680830fe03297ec804c9f0814012f162c6074105a0e11021502a98400a062090a0e5c8414d899185084956608516a92016f42126f5c3015254827c3824933523a43ea0040154d80046065082dcc0881869bee0a0ba50c4040cb823f507caa7036fe09cc210817a8bac3f7230c3072ac4d3040a88d107273e64b8c0243760314e5594f050628a4b9438511500a00b2b9904728ac027272cb6b0048c0f9a7c2287014a68c2101fe6e886c80f8330e283132660c393287028e3c30d52b8c05446a8c1871444d08038aa437280284199b06853c40008052b38a00073841106488d9449ae64918415202f2688f92388e807500973b831c41a1d8430359205157cfc18600fd31941481f76745e30fd40a394a00323a630c913a6b050260b332611169982066e04d142e905380b08e2063391d2175940524527440851f241862f89e080cc1525098481028bc0e98952070b20811c1a9003849f15a8e18493401f403f598431401b5230f2c8cf1d81187854a161929f5e400d53b040a5e0e3492805283659c37dee683188c10e4eecf091020a363a00ef5881cf0e6aec3087132014e1f3803930f03e78c195b141480f0b982c0b6552d003229e08d141186592102304779936ca803963c5252ad4b0ca28a0017164008c4ab8f410c92436f088263ce8210d4a5a041c04517a10c211295a70e800d5c316352500f24399277a18a1a78c2a6b6a00a14791304b90b8b822484f18521211001f532ce9494172804106a1637a6258439131c6e031a3c78338c8fc40860c2420b580cc119dbcd18231a42e14a005fde48a165299cfc33d992200929b244a0b794c3108c92609406c8872860064f60801054b42b03309192a7c40638c0c12a0049979891366a4204320645a60230847a05cc9c0cda4932a24e9c103f0ca4113a1411b6770729be821078d8c8878e2da90c41e2640a30f102e0300116493273e78e169a080148cc1009af96b94f005604e227923b2a811e1923b64f03c8400c2130635c21f808c2042ac3c61c532f286268a3c109e35830a1cae1b8ad460d3c811beac34d0d82b6e25650339ecb022b4e0345981b4452d44911a6ea812c327150469a0c1820599ae404f6ca08c1947c451a708f1453ae2851f154a8bc02589e40bfa0726604694485143b160000a3970a40a84be1a8e5ca1a61a01e51dd4fcf04785c4ec640d209aa4d14098764cb1d2c2133a0832a358e3dea0401068a60e2825447a7472c974801c8134c084833978a88133a306872a423cbcc1001ba6a8608907784082670c11120f20010f5fbea861892dbe5cc243025a6083d4e304331841724822650230b2649406098e5032258d078c30c0c4070eb0c324a79116a022d234e2460e230234e1445ea830c3028968116300486c10c20223e8200c3428d0d9c30229286222c0c30b04b1400c385776ba0021860502c0c54f078b4c9d1d0219c2065802138276e0e28e11811dcaa063871e5e50440b071af9d9a17dd2a50344bcd8b043a507100b6640a5d1218f444c9044e74b0e3a7040082d6d507901103acce682037d6c593a40404d63002322e8924326373a446202292fe41047163280a1e3912a72602202565020010f98c8a1060d6431850b12f8400e0c881284951b7050c41819b88042c921025a8c3941014e7020763030a688335a385909e8315d9608012625aca1610c095ec8808e11b8a0000e8b44f2464f1825ec814319545c18809927c8c0014d6bc0179b9c2cc1c18a0e924c120115e2c021844e72d068a1e26e20648d0bece0a2b170c3070059040eb1421b379850d0734955a58e1be25c0014952b73034e1735506c074a6cf8038615309183044cd870852703a0c40391286283cd03e500126630840dab0b4cd620c0ceade10659b881c31a535ea8a10e1956163ba6f8a48628a238600b1688a0470d39c4f0c21e46f0e8d4d0803d3e79c1111282184b80f4b010200005c4b401891880a041210f3119f84289213c1cc00b311578810d0c90069648c42060032868600d1f50611e31e400254e2ed1210c1a5bc868c01473041126882a657881c416a23059484b9420726940181d3602b0c31d402880392493204a481a154c0836d921123274a0010c2a0224ccc0862040000343194c945183490698284b7c40052af0ec7c01c48b183c0a0849c0972dca0c20002be070c7971e069c7ed828237e69273891029125777ca90cf0811e37c4e153013cdc48448c38dc0f1598a28a126a682caca9000f6860903387989f0ab090838d09a088b980174c0ad0814d70ae502f702052800d3c0f9ce0450338a0a402957432f222a68a06bce0012e08e16501f2048b1ca1822b6898c40b15fcd1821ce44083094ed82304395718800622cc104406218c904303172e4b10600b0a4a4083013ee04550205344c1b3080288d0e4102b21e019e3063d4c702082c07346c4862087dcf1024f9525d688d1c81696470063a4b04cb1628b2e83bcb1c20ad6fc5042172f3c692384d849962e254a07234f6c72ea0269b889010538c1a4cbcda907893ba3e0e2470282be00cae2042e5634628104d8e08c0270a93168a411396c70858b0b867451c259e367064e82e862890778b061063a2c0c631ae9428019a0f0c28b78796104338c2921063d9480032a33ecc000892446c0a30f192a51c3c7e790120c32b0c14696113810c492e188c90233877802870c153821843a30603b20c30916c040943ca63cb1c51147988b2ed6a0b69040cd03d250830b1db600d1c1173208c3ecd882a5cc092ab1a283165b7228b103073f82e0a2c5103404c9e4006bd268010313347428c2c5d472dae1066e4209906879a1ae60c91c3a7ca1050045207149163224200b0bfa008405037441822c5a90b086151b7a4ec842d2e1c48f345218c9c2aaccd31d5d8ac0f2c91f58484208103758d680c10803b0e09105960dac5182124029918065d404254944396307cb0a30e010b960421279092b72834706598084e30190b8a0001d22a41285f8d0c8276a42320c19979ca06f9c94b2040b2da8108ae28a2468aca429c4036a5c2941044277c040864cae0c51891f1d74698114576630a1114116a1c4cb950298910034256022062b30b8a1111ea278a1b222461c3f6478d65456cc341b8e20848bca0a9539d2b82243545640288389035a555511c40d3a5080a2aaaa78b1b2702486aaaaf263c2145eb816339154555525c4c5480a1f7c7c1583a8a93a7f5011a91962931ee648e28918708861870295e844d2502791a9272483aa0a4d52552149626093490d62b0880d22905cc981c401901ce0914aaaaaf2f1092a4999f24069528fecf1881c6c3ca24655552b4899d2420b2a3c12c523524a3f413ea62a45ca977a82ea9f50f3e7c784e649644ca75310aa64f259e1967a22e00204a49c4c5278268f9494898c0a523ed553eab952509f32a5f9322b60e1b5827488c05a6b05e9a082141f130f0c086287167898c2831429f57ba02629a5799a405868904b20b242498534a7393a4d52f579d4271d78c0482a00593072800a75124589a44ae3730592aa0a3d525521471aa974083adda0528a06350b8281155525a505182451c180c443144614a1017c7b7ad4af51e48caa52c48a4a8d22687e824aaf884f05347f14d141912b5575e64b8a2c45105055a99e5250224928825349492492549da749aa89ace1414aaad4631aa3aa2a1ea49c9ecca74a3d89609188143cf020a534dfc764a269e6e934432e20a4ea0245225110b9529dc6209244853a8948a909e4429a542184a04e2954d01729a4d40492e2e34365ca94434a90c269934d36d904c743b00028691842c2c9828b9c1720c0054b141283e75a4b2167147246555528346b8942bca04ea2917d343db7f6a4fe8794ea394d17720a69a1aaaa1e5a8023495792470e10b040212c6d5ca0068014e0051ef410e4901fb07801124c6280630a2d1c998013639c3c8844051ea8238c287450224417105c40490a6c00c317141486bc70b282094daa14385a00c2c308e9ac44d27efca4206089161cae00a00e141c507c6881046760528227e0059b3e3a50071859d8408192068ac801171e4c0004ad011450aa08a01b04b8e20b1db810400f536050c0a803463c4ee208440183cc466c58c10d36d0801642001a41849501470cf1e346082998cc61e60a228ebc40430e9c2064053d5c68c0044e98a28b1fa838608f43bedc88217a637de0031f53a6543983f0902348253982b4207532512fcf4c554d00e5a54a2da5415141fd9029f5d4521ad44c337dde96ce98ac8fc95447a39fa0d2c89a4aef334a731a350b22a580de0692030bb25bde9aace400424455553950a0a972a02053e54091439503050c550e143b550e14285439507850e53ca1090823ecf054393b3054393b2c54393b2654393b36554e0a975439290c52e5a4e04695930218295451e5a4a0a6ca4901a8ca49c156392984a9725278a1ca49a105112a110470020923e4ec814295b3470855ce1e559593c3ab727240a1cac901842a27870eaa9c31995439636650e58c81a4ca19634855551f88f0418e1a36aa1c356254396aaca872d468a0ca51234495a3e64c95a3c654e5a8d1a1ca5193a5ca51e3aa1c353b558e1a0254396a70aa1c232ef9406700690c51e5a451ab9c3472a872d21053e5a491a5ca49e3553969ac50e5a491802a270d01543968e4a0ca41a3922a078d47aa1c340ca972d060419583461e550e1a725439689c51559501a28420001106e04114129ca8724828a2ca210155e59040aa7248a8409543c2952a8784ae7248504095438207558e017050555508032041a70023c450e58c30a5ca19210255ce0806a87246f0a00312a26c52e5448941951345902a270a0aaa9c285b543951a0a8aa0a04025426f00451e5f094aa1c1e2f550ecfab72781450e5f09850e5f08450e5f0e054395d2ca972ba3852e574794195d3a58e2aa78b1a554e1730aa9c2e5854395da2a872baa8a972ba9ca972baf454395d6c95d3854b95d3654a95d3a5ab9c2e0fa872ba2060002640f100040384e08100a0e87c50d928d24555d9d4aaaaa2aca0aaaa901f7d4409f111d2a3aa42795455088faa0aada9aad01d88f40185263cf010b2a3aa42755455888eaa0acd51552139aa2a14475585e0a8aa900aaa2a9482aa0abd51552137aa2ad4465585d8a8aa100aaa2ab44655854e505521135455a804551552a3aa42695455088daa0a91a0aa42675455c88caa0a9551552132aa2a34465585c4a8aa501855151a415585445055a1105455088caa0a7d515521105455e8035515f2a20b1eaa8a86c447988a849a6672f650648f135495e8b2b0470bd500f418a300555561a972f4085355537818d5a1072187d8f42149aaf47fa2040f20aaaae2830ba04f957a804c61de608490353720b1a683999a57a8f8cced01e92ed29ff145978574ef0e5e76f032e767c742c6381fe44af92ec7d63620d3e98a4804643ad91879c68c6c880875c0e36bb71c5b6b31d6ce2dd7f845669d2db7f1766b96e41c841ab12882d0159242b7eb42dbdfd7b6bf5ebe78e1b2b31386cb18212b247cb479aef97cba8ecd5876acdc2da9cacd5684aa9071bdeb4e7f42a6ae4eb7588b1015125edb6d577d0dcefaeca8343f0307e4fae9ded8c136e382ce4a22a0f903d712a12964dffbe084d7c666ab358f4aa61ed7a10dc8cbac7a7c8ff1aef7e5241f2d4e9d4ca8169242d2f55fa7bfcbeaaf652514857ccaf063af5a2d6c8b35fc094121576d36d245dd79837c6d99d01392ff3dc6e5683387d435c9f971979013f2237d2d3ec61abbfa6f929ced97ce9846769a7c44a2d493118976c264198976ac700d229148748345a809199ba3b17a6decfea28bfe290865f2a923d823a0a0511993c9c77b274c969d0aec58d9e1b21386cb4ea4d679c68ccc849890955b5dac5fb5fc2db2b784bccb42d63152e76cd36624e71deb413b3f3d2494c8cc0ef5a09dd5342099ed75fb353a2dc9d9c767d656c2d22125247f6cf0ef5bf49fbfbd9167cc0803a1246484afb219dfad3036e47b480809d9e87bb65ab7ee2b6c6d452291a8f1882509654046beadb1db2cf3ee764a721e313b227484ece6dabbbf5d6b738b1dc919161152235d8574b57b1bb4ec7d3ac99905113242728df5b67ae99c8bda3549cea3d83646848a908d297cac55c6567cedced5a41011f2275ff7d664f67af356434348672d8b9555c8baf29d6cf16a663c6346636c72202363b7a9adb4454bffda2d229148f44e29206bf389acef8e3a7d7823df5fd413d9b3ade99efddadfdc5d1cc8eaeb29b5d021a573c6ef44ce77ec0ceb756ccef749728e3b61b2ec9852a52d614e58ea038111899c91617303b92c63c7d6fa3b99c7fb2467d32dc53022917367baa59ec53c63463643d87022e943587fbac9dcddf87a13c9beb827b3cfaedf3a939c4bef33aa6f26b21b41d6e479bbc7edb16b2d46efd8dcfa36aef03ad88d17e4d64e29a04c24d74ae1a3d54d47a35b1713b9f8793abd975dea8fd64be4fc6abbbdeaac27df586d202953a6f471dff56b455a03c962b3b766bbd0dd63534be47caefedcb78b6d977b25f2616ce6e7556d83ee8f12091d5afad8795e7e6c1d0d647c0d273b7dabe1737433900c1d3257296595bdb34d06d29d2d5f303277dfcf6d12692bad97d2ebaeddca964922fb3dcfe8166cee41373b06b2bd3fda2f2e8e44beffc9d87cceb13d3e24f22be3cb2c4ffa17be7d442ea7b79b9df741d63f1d910c29846ced6b35d6161b91d32ee66c8bf1b5661132229939cf76d3d782eedc223993523d67da4e982c697e06e7c87811499b41f89e19ff5b8b4e18486729d76b637cdaff1ec4c14611d9a25f1b5bbc717aa37d13913d618bded88aed2fa411115963857f6757be91697d874867c6eadff7496de3a686c8f7b13fbee3799972a967f4a9c92f68cd9d35ec0623636cadb89c6f377d5f994d6e18a18d0be4abd7b5cade64d8f7d626399b2903df0b6c0a91f417df45dda18b8e9a2933baf37d468d6b98c1351f3684c8fbed7effb3d5ad372b8d3eb34483fdd2191aa0f3b3c9c46610c918fd592f33db7b5d3fc97964b25010f9b1d57f38d933bded1ac9f9c9d83ffd8f6b934d0b64bced2364decdd148dfb5094436db1e73f7f36bb4b5fb3e239ff929d4a88c4f100dc3652785e653a5d880c8c66cd9eb1c64b7fcfe6481746f19d6ae8c4c69c44fc6caf95cacecd4b0636567861d2b3ba9a02e22d1e9cb9ce6cffd2f6f4d3c229148245adca12c6c562023add07dadd0c1a6eeda3fa46beab732aed3d6d8aaf3435e3b39ce06df7be60ca148d41ecf98d10e367dc8169d79ab94bf55bba8e32376abbe66bcefd79b6c0febecaf369fd77e7b5b367ac8e68bfeaf5e4f695c9d87a4333e67b5fb51f67e2d3c248597d1b81aadf05dd3e878c68cbed8ac91afb2bfb996850cfb710dba43b2fafe62a38d5ad63e4e3be4ad95427790d978d77c3675c8d9f03e4337ad651b3a569bb6d7c777b7fa754f726ecf817e7ad8169b39245fe675be77d7bc2065724806e16aec686bb795d61687f49e37ceeadae96d953938e46d0b19be18a1b73a79a9407a3b658f6fb58d416e9e02799975f771748cd1e620bf21fbc24afdbd670a21e4e986accf17f469efb2abf6758fdb9071b9d7981bbff86e9b8180c4b021dde3b3689fe3e77eddef41f35a4e7fc6c4c502362890d3457eebed854c2974dadee5c1660dc9eee3b56e51a63cd9634cb23981b4f7bda714be35a37de720981fd3098b4824129d52a8f713542263630259ddc7bbe8bcf1d9ba58132312994eff43c95412897e824a8f67cc88da944032b5cc58b74b7b45d8d6a9211f5ccdffdd9efefed91b45763669c879d7b5fc9cbf66d98a15cc290586d94f2954b44143568776c63a1d3f830e6f2a28a252f0944231664302e9bebda53f1dad17b2d7a3d2fb388f7e4ca73b4a0364aacebaa09d303bd405edc47e9b33e463efce0563e5476763dc31d898216d735bf131d7abebf5fbaed89421195bd5b2834ce18d8d1f1972bebd7fadabae355f704dd218d2c16ad9bfddb7353ea3366248db16d7ea58ecf5ec793e3ea55061c8c6b8b2d5ce42677ae74ba14620a3adbfee74d877be17db64a222900c2ebfb7d667f45df4d6e3b33804b2d974b4dd05e183ecb1860243cec7a8b3e7d79bbd8eefbe908e67e5dae67bcd4deaaf81407ecff9587b73b6d7dc75ec03922384ab51db209dee8bb51732daebb736f7e8756abdb9ee425ecb1c832fc65e8c1f739233b3db7021fbde5b5b9b90edadcc366a0b69a9db5e8b5e067d5e0799169255d76873ab95bae97cdb0312c2e99c6de7a8ebb7dc1b8d5e67215d848f757bbbf745e826c9b9b1908cbabaec5aaedd37b81e0828a803b2b179fbb1ba384eb6ba919cd13c6934e22be4e3b6932d86edbe3bab24e7b74242167bc16a9f6d6c6db45548fb1cf96363874c69bf6643856cd35116dfb436b6d9969b8a0d07646bbaea73703d63f1b20934856476bac8da2d5b6fad3720a95df4a98dfcced2664db6b13ba550cf460ae9fcb677976b1fe3b36f24e7335f62229b28648b0ea963ee699bfe7250c8eb9a6bdb137adb60f3847cbf7c426691455eeb4231364e48e6f119b37032f6e71cb46942321ba9fb7b0ed95d18d9061b2664bb3959edbfb7b9bae23cf512f036aff49dc7e866842467d81c344afd5c68a3014999b3b1da3679797597c26636b25182dad7ac9baf7b6babadbda0d1a34e13a8f14f508975b14942424addf39eede884feaf99161b24e447b6cd6f6ce7be685792f3c87e6a642750e4d3a722056c32203f36f7eb0767d36b7bf9cc9f9ed84eda1c21e9f416638c73c1b61d670ad5c325daa891cf6fb9eba87becba7331f510da18f197be7e0b2b6377ff246777fa52694b189b22249dcdd9d720f3779d55b86c88906e677b2bde192f5bd6dafb000585593643488eded1317bd9b4765d4a727e2f08219b42b695e975d73e73487276609861eae7320c48ebacfbe70bd2ea5edf45c54e2f2211eaf4f0f18c199109425277949d99efa29651c8f835101246ebf17e5ccfb3d7eae878c68c6eb8007bf77d4b238bbe98f1f0037bfae6b5d75775ae7b929c194f1a591d53667f358e73c2fb91d1c835ebd3c6d431e338e1b5a59e33a52053e98c64cd6fb3dbb4cdd7ee3ad04f0fa366e4fbd7fb6cb3f4f6bcb6996e694bed294d2f2291e9967a46ce33667483e4c7e7206cb82cb3969fad14a3fe313ae1f36a1d7b137e6b3c6346f42461fcf6ec748c6fed9ecff8b50f921d9cb5d6ebec72b1da26f908d5d31c81246d8e45b675b5fdf5ce49cecf8ee6898ce36637c9e52c6cd7dbdfb2de662339a7d0fc68f426a9517e9aa4dac094e47c1e5dc76f08df4fd785e9f023993d9bf7c657794d6e2e12f9f87cc5c147dab898b16fabd61897632467db337aa03429df099365274d898a4894a63419cf98912c239dc348698dec7acecb4e7276a99feb801e2a9c74be3a3bc6bfdf5ab315d4236fb3e9188dace37dce158ddd42a634bf884422110ac83462e71933b224697fd978e1b46ea76babebdb91293572960019c9b61f840f2e77f6c64549ceb614643a8dd2341cae6c0e36fdbecb39dbf7efbd742f423aaf3b663e9d4267256ba6cbcd05adfbb71e906914d971a9d29dfd08ddebe209a7ad13cd63a1d2ad6571dad5d4adbaee2239bb16a6a49523a4b43ee6fbf032c9b961e441b2055f7bb3e56a43662bc939857ac75b46725ab7de5885375a7b473e413f8b5743d38c4c4f15f8266001e98b5dadaee16cd6b58e2467d4083299524f99b8836490b21a1dbbd1d17921243997dec7967ed4da391da47d355a58575cff2cf446725e7cc60434f209fa890ffa4f50a94a0eb23a6f3e99d7756d64beb28c61de2064fcde73b8165cfde0cfbbae8bb44d86cd5246f2d3a7b6581189ecf70099aa48841261f9b2d38b4f29201b2f7090eec6cbee82cb1d36ff5a32c5af251a4a581e440199a6dc201963cf9b3266ddac702e9273ea64a233051b245dd77ea5aeeb5acdce78ff8b3f3241f4ad4bfd5c0654439b2dbeaff6d8aa8df11631acddc8114e769fe93b92339b214cc3767ff9db83efb335cb004642e6b5b95ead5568e99ad69a22c32f92216b67ceed5dd0bd9d24676b9a5201b96875d87332acef31376b4d2b7a918c9fbbf13aeaaf2d78df8a34c8e5d1b9d76ab9cdda3e403f3d23fba7494241e791ac76bc8eda9fac55a7df2d812ed2df9b55a6ccfac1cbe019938fcf24a1fc71911352c6bcad9ff5ba67d1f4633a01714f9941466b6784de605b4fe773246720d3c9f29241cefaeab74b59c3c74c17c94d29c6bd45cea697c6485ffbb696e351ed49cd14aa99b548185dc70759847def5c8fe40cab649190fe7cad5546fbd6cacc452462ee9402b2018b0cf2b9e5d82337f4175d8b0e36d826ec5eb13a48ad65776775900fd04f8ffb092ab13357e4bdeb6b7c56bf4157d91289a043d1e778c68cc45891943ebc94d7a575b6481fc999f94187a28d67cc685591fcda73a4ff28fcf6f7929ce19f4c564eb306da8d1fcf989153918ed2d6f5adebfa26751c8374cdf836c7ce5f8d6e35f778c68c2c4001c978d5f5de63eb0e36cbee358d22171824639721b4b131b7d3757b41bef7cb6b8d8caef76ec4f83dae6f866d9980746cae8eec2dbfcc1e4e92f3a89e66b35a4a836a572420977337b276d0c50b298524675732f9581189be64f2a9229148f478c68c729822a3a3b327ad14fe6c2d5692f3a8f9a57e2e7b51b665b6ff3bc2e75eab90e4bcf8ada9d41c3315f863328d7cec69340a804b7be99b6cbb3567f7da48cea9138ce149e635d2b62e7d4fffb948721e3974b2b976d77467db4d27646f125e1bafb34c9b655ce122399f319dc88cb819b338fff19cf6cd56bf929c4f29545bf6b5f565cc1ec3f6d1c4b2b5b5af5e76f3fd6391919c6d69723b0e4bcef6d52cb6d77ecf9449ce245410d0e864ba8fca8e959d30ee78c68cb8b840ddc6e9b6d95b5764d4919c177faae764830da9a09fe578c68c7c10c9f7d5e07ddb0e4ec6b0ee84c9b203ebd72012d5372312bde625023dab77e4f52fc606dd49cece660bf7c6c91e75b6eb51071fc979274c969dffa10715b41386cbcea8d43302329deceb530aa80608ace88e3dc6f8d85b8b19748f453f667c7c44d29fdbc2db5ebb6f9db324677e282a31f573d99f4c75061664bbf7bde5ffeacf36695c41c215dbbdd63a84d0dde848cea99fcb76c264d9211344ff8a4894fab922d19f4c1535775607ed84d9c1f26567270c979d51379e31a32c2a8c5041a7d1c88407a0993f0a70008f4c3e644c3e253440ca0e0352b03f7aae5960001bad053c408a0a0a5001851612f00004b070c232618512b8bd041c008de94c0a05129e6b2906681ef597993e7444a6d443424d929402f4298511460d2040ff98eae8f4664cf54fa3119779ae81eaa35c27cd689442ce03444095308036994e268450a64eeae36362410076fec9041050f880474fa6040f50080800ab10a5190080133a80c23f28e08c4ea6d444c00d03423c82371594001b1e3d6934aaa89e9f510acd23a04a41045216d9c2e67f660947ca222df8a0c2c38a35546e30e123022d40808515547880031ab093821405a09000049c6042090720c10023104027478401046c04347f9cd340a805070f19e183f455fa626c8bf55a237dc659efb3575ff79af60ef99546ebbc5fbdffcc720ac70ec9fe70fd7d8efd9ccf661df2216cadc6660f1b9bcbd1215b6c1b195cb3ceb6dc6c0e1967b3b9da5c8ddb476e39e4b20ffa73abe98d5d6bdbd27148fb207d1e1763eee5cddac1219dce66f5ef628eeda3ce3915c8c6cf5de69e675df13e9802d93ebaf7e86c1b9f327ee7dc1bb25dedd6d863745d567b3a2603c70dd9da7c2dbee6befe2f5f6d48f645df6270be57597ddf050e1bd2adad33325bfbab7bab3a14c8be97bdbd8debce35b9ad21dd678b915a6e91ebbcd3b913c8e69e9935d86cebfa80d8c9e425cda9081c13c8bbee852f7ad39e5ebd1bd700b959c129817cccdad65ead15ae479d4d734a956453433a4ad95bd723e546a3a53869c8ebdeba8bae7eefc2f68d79c48dcd3034e47beb413aa1531ae785b44b0f1015c7661c9b6167e09040f2c31b29bb775a6f3e2dce19d2395ccfbcb277215f6799e09821577c8c99bbdc6e6b8b3111386548b8668d95dec9d5be792d18a020fb57ec0305d9b7386448f77ec5386d74b0ed7b8e3306fb481fb2575bf95e27399329f5a4b18b5d03cd1f86c211435af67cdefadeaab5e17c91080c9c30e49b953df7945d582feb2b12a19e17382390add67b9f838b615dab7614382290905d1bddf2ba22ebb99e9734299168c469528d054e08dcebe5dc7b8dbdf716b3d7715136d94e66ee033860c8f9911f7bd67aadcebe2e9c2fe4b35dddbc8dd13b2f74fb2211ffb411e08040d67f6e0f79f28a743a1a3f205f8571d9fec76e9daf5d2fa45fdab8eb75677bba8b5d48ff7f87ed9ca333dafb70b890b1deb6edec5cd1dd6e6e0bd911be776d3adb72af510bb9e67d66bfae73ed46d6b68c81e301b920fbd3d8e06cde62731c70b278cd1c9b411721f3089d3be8f7c1ee785bbc74da1971b090f55a5819ba79e772be1c4e07e46c9572ec76595bcbc28b734573ab796c2c2e5e90d9c586ebde5baddf0adf46b717c70ac9dc8bcdacd176d545ea168fd6c0a942beebea31c2d6a863d45e91c8c60d1c2ae4e555fd59d6dedf6669243903fd9c4823e652cf24393be31933b2000e07245f7759a57dddefb7baa00970a690cf6efd6a2bbea5ae2ddc09c365070d9c0dc815f9c66ae17cd6fee2c291222ee7f0318b6fc1e7b0577baeb2061f3ee4d7044e1492ad7dcfd365e040212f74d15b838e99ce081de709f9f6fe64b666f3c6cc3921a37deb51d8e8743fdb5a4dc85e3cdf35e6979faffa4c48379f8d17b21aff3e769f16384bc8f79847af971d65e6188e0624d7ae93daf51edf9b3d8e12b2c5095d5cb3b91527732d095eee3d66f65e7316e38bdcdc7c4eebe03fb8629d0f719090cc229dcf0d9963cdb55ff0089c0cc8daaebd375f7596d677f408497badd6ae6977fcd7a61a592ba36cd1ea6c2985d6e11821d7d7d778b9764e2d74d44a15384548cb16379bb43e17fff21389e28819173844c865ef5a91fa65fb8e7ded84e1b2d3c2c019425ab878f9b517d2169f7324672c35385fb9c981ec3b2f5dad5158dd5f245a7dc5cd27f26b65bbde9cd5464fa463f6427899dee5d1632ce30607926bbceb639bb1cdafd3919cdd290574d389f49e4ed9efe5b52664113562c67d03b91a3f165b74eb23b3586f389196de777e19a34f241289dacc2872e06613591d8d903667fb5ad8dcbaa589eca68c9d3e7cb6df61cb44becbf6b69d71c6fada2926f2ab9bef41cab0bec56223717389fc09abbf45e153b760db40b60519fa73f4afc37e785303b938f6ed472dab6d29642467c6cf12d9903dd65ae3c6ec851632568974bf1e64b6d4597bef3546899ccc3e75e8afa75b76349076f9abedcecdf5abd0312e6e66202d57bfaf3563adfb45efe2460692d9fb9ef96d4f2d6d94c511c39e44ae489783dcea7cfeceb52432ba63b6bb39dbf89feb18c8681f7bebb8ff7d3ecb3791c8b66cb5adeb64efd0dd4022216d14c6b7ecfb4521756dcbcd23f256c6f14dc81a6bbaae7544b6bbd3a34ffb16f5f8d64d23f22fbdcfb9b6f0f564d73122a9856bd687dcabb246e1fab26365675560c70b971df701fae979bc887cd6743977ee329b7159c6602021fb5bcd315febbbddca9822f21bdff9d4f9d60bdfb589c8bb626cb4b2badcb2479f314424d7878d46f771aef89c317688fc4adba20ea95fc76aad22111b716486c8ebd82ffa62a52dfabc972df6819b17c8d596fadb660befb2cd5dd030b666c8aed95b9179f1e2e55a8bcfcd5b750ed2663edc1422bd593b3b195cf1d96f5c6f108998cd143784c8fae09bd4f96a8696febc1944b2a3be98afe9d1c245ab193782483a97bf86cb5ae7a2d717c7b86981f41badc3659f6dcc5a6e23b8094436cabc2dc6a8737739dc0022abb591f257e72e3f8dbf78c40dc60d0b24c7456bfccbecfac5b6a2076e56202b73f51fbafbe766e5268dfe871ed44ccd2b22d113e3e60ff934be68bd52d8ecab76b11ff272637eadc11be35b6d8a44aefb90b34276af276d7e1d83970ff9f0bdf78296fa5f56e31eb2af63765abe2db6e994cddce821a183afdd465b7dae467721cf9811ea260ff991badadafac81e5d5ff190f5cd09ad4fdb66a5feaebe19e618c7cd1ab91e6cfa37f675ee2da46044a23b64bc973db3b0cdfa7355bae213377648cb5e6d8c8ee9e5eaec4edcd421d91d83cf1aad93f2ac4f24ea0847dc4bdcd0215775acb2b8b8bdfa62bd90899b39a4fd586143ff2f6ee490f1b2db228d5cedc7d93771c85a6774f70917377374c1215d84b6fe858e9fbdaf3215c8f7d07afbfb1865dfb0a640cee9d657f3840fdd63ed0df9d61dacd3d1cbb4e37a37e475decfd7848dbac82a6b43b60a5fe3c7efcd8f94211b92d537afb5b7fe5cebb54681bc16baaf963ac7a6b5f4ad21db636cb56df5c6d7d6f509e47afebfb8d6fbdeba852690d3e3fb072d3373f54e2e81f4c8bc5b8dbebc39eda7866c91b5fdd97339afb56c1ad2e9f2b60cd9d1b70d1f1a7251ee465b3b6bfde6a52490b0b63b5b75ffb1b57be719d25ec7ccf65be8d863becc90713175963b4e66a12b43de67ee9fb7699f6d1046386ec8900cfea5d1a15d2dbe6a21f388db8b9b31645b165f2fb7ecb3aebb41712386e4e6d6a5ceeb5a8fd62dd38d4ddc842121e37fd1f2f40a2733a54a528b9b1148bba2a595fdabd139489b08a49b8f529fd1293b5ad70a81ece9167a6dfeeed87d074332c77696f55ab63a777f216d9bef33ced9de8ccf3e10c8ba6c65cc8fb2736d8b7e40b6c8e66d2fde37a36b6ebd906fd6cbb799fb63f7c1d9858c6eb6376c8f6973f72017f2c6e7f7c5ce2eb576b92de4ad6bbdf9eedfa075d8b490f0b6bf4c9bb6ab143ae701f96d5dd6f8d139d7a56fb39017b278ffd9c6209cec2d2c64bb705a1b27bbef8d3b7640faebd7d89d8dcd1557df2b24df55efadf6ad68e9bcce0ad98cab5387b63d776b7d55c88f97bd47295b2a24dff8eddf6e9b9356c638201d6bbd9ad9b76cadbf3685bcb5aed69599b5cb45d71b905c9fd9bf6bc2f6b5c62a857c70f58aabcd086b7b6c45211985b1d516e964c7b83228a4ed1b61fb8f95d97c979f90f3aecab7ddebecdacc9d90be6065ea9a3dfae08add848cee5a5c14b2792753169990d7755ccea6b37aeb6a5f42b6bfad69b5ad39fad8631a90cc9b6dcad1b9b51ee9544232f8e6a2fcededbfc638090967749ecf18ebbe4f2f12f2c63917bbecebb9afdc32207df67b77ed7d16ceafef08d9e6e4877c696d6ecf5935f24d6e91fd39369985b5a1e0c608e9dcd1a75dff6c55e6d3675a193133d38cc54d11f2515ecf177dfef5ed7d22e4b72f9ff545f771ddd560e96608e9dcadedcd9f576ee634077252eb335e5e1eddb7e59fc486973db3bd5e27bfc53c916e1bb5de9e1dbbd7ad2612a51e859a211c5ca6904e78b92b84b5e38e172eee0111ea04325bcdb8c505575c1c19b2fa8d575b07ad9d5e5d241289da8c48d4ef06d29fe5c5ee8ccf95616b2467122a0828d2f03891fea643e6da7cb43efb7e13c90faea6f1d908e1f455439ac8eefbf0cd371d6393690b6522d9ae17fbb566f619396222ff1d2ff79cfe43bff0852e914f99b9d9ea6cf27c71e110b2815c8c7673ae5d1b6d84b306923e5ff452f76b8974e88bb9d8f1dd4a248c3c99b5fa2ae4679da5445e7b5b7bb61ce137ba280d647ccecccc2b7d97391a6720ef73ecd96ab9d2868db90ca483ce6c7b97321b9df19ba4b97bfeda756bcfbbb9617fa4949fe7fbe931d23a434812c9da5acd5ebed4be3b1f3595463190eeec75eda63cdbf37a2391dccb3518e36a5eacb68744ce75bb7174915617e77d4432f6d44ebb2cdb67cfd611c91ad3e7eb46bfcfb28fa146a4438f6e46dacdd9e962432c214624e317fbfebfdb0eaefb22914804470cf47e2a1ac881d022b2b2d69aae59e7756aebc340dee5ee11d66e8fefce185244fa77adce45dbd1b19b2f9488a4cefabc713da7b3a96522d1332312318ff899e9184244f25c6ef162feb76cbe3f445eca4ee3b25edb2fb5b1ca8e951d2a3b5eb8b4a09021d245183bfafb151fb4f6bd40bafadc5d8bad9b527aaff31f2117c8d9e2b717a9f5d816846d5488ac2daee76cad472d2f4ac5884422d16831132142a48b94fdd77b2b8536348874b5f5848c3aedffc98cc418d29020f232fbc96e3efdc7ae73f5adc9ca5b134f5c3c81500b249b8c6d7c6edfe5aeefdb96502032ba735e69bdac7af4fb80c849d9d7754fa365816cce103a3be36adefc7b0512da76e9a4bebcfaa0e289ca63f7878b6b75335efc5e3773ee58fb7b1f7e83dd2af4ae21e48784eeb5529f707e7bcfb70fee09f1c1a252680f69ddfe7c1bdd7315c2b548ced6c77447694e29f865c7cace965210094ced794d84f490d5d9bccd8b591aeb7d3290cbe4928774abde6f96568e75b975f190ee2fc7e60eda6e66b6ad9176ba7e6cd9da60637472e80ed7b0bd16bb9b83eecc166cbc9e83bcd673735663733b647defbd9db9d9fc552c5f7660c310aa4342d61c5df4b2faaffe7b3ae4a23dbbfdbcb72dea960dc36547b432109a43f2c3f75edd6aac313624f94e982c3b57442291e8413964abafbe66e1ecd75efb1f7a50a77f6b2a710cc521dd64b6c5c9ce1bdbd81a1cb25dbb775784b355ebdf2467d63e84542099657d276bd76debfcc55220ad6db7eba4df7cc5f6de1bf256f6da5d2b2ee8868ccc46eb9cba7faf0d39fb5ada1d5983bc5ad39e3291b121dfb357235ded5b63cddc3f845020ffcec9d179a4af57bb8e5a43d6bb18b239793ada6bda1348569d83f5bac7ebeffe3281bcbe165bb5ddf667ac17c999d3844a209fd64969b76a2d65ec569233bb40480d0923744bd9393ba7d33a92f36268426948cbfa6fac8b3df85a6b8ce4cc6d0aa121a165b339cec8aec6f84e22817cf82c742d46682b9daf929c5da34267485a9db3733ed75b269319f27e2f5e3fe9aaf7f94292332c43be5df13d5c3f5d74349221637c5e0d1ffb74ecd62333865cb5694368a1658cf637d4eb2124867ce776de3be95adc18eb3fa14c24e730a46b4ea98df1ff31da33356d974c3da9d9b8f1293402e92c33bf66dbfd606c4d72fe319dae162d221164c64e8390086aede61a56b615c2ea24e727d56945241a5921a1a6162b5dd29c4c3fffe36f8c5008e4776d585fdbdb5a6ceb2130e68d74b16db71677bd24671848e80bc6d6f3c8d873e71c3386919fdf5aad4f4be15bec47080412c2779fc3f8e6d7f7d73e192d24d4d442424d926c68843e209b23ac3def74beea5b4672c66f4d3c0d86bc9090f273ebbd6bbdde559d29e5984aa80bb99e31b668afe75b2f37c9d97d425cc8492debc720474ad74348f2c79405ed88448bf117a12d166ce65a83ad9b376e7ede1c8dcfddbae6bdad99919c9f6b2d22d14e182eec5a48dbcf3687f3b1572f7c8ce4ec5a445676acec68d9b1b203a6f465eaac221205ce9d5be639618dcf0bd95fdf5d6b5eecdd055f1792b9dae8c7e65ce37efeb89070ce6add8c90b98dadbd2d64dfc5f4676cfddfac3d2d64f47e46e3832dce8eec794072630f32eb5e570b69bf2c248b6f42672116b27e63f599adfc96aeee80740b7efddbcbd16ef40a495b7473dec9ec8deed80a39db31f7b42e76f87abd2a24d3c9daf209db3147a990ab5ffdb61febb23e39205f657e8dfaaa6e2e3885bcd7dd7a1d7cf3697d7703322e4bfb565a215b5e2f858490da5a3b32bcbc6c14d22d3b59b594afb317a190acbac7755eb70c277c423a5c94b1d8d49bc3659d9034ae66913606dbf99b9015aecb94b553f65a2b13d2bdf7d04deb77c26ae912b2b27fcbcec65cfb7f0dc8cb91f14fef66cfee2921dde5d656fbf9cfff5c12b251fb9a3fec4bdf1f09c97aaeb6d6ec65e9736740d2baacc3fee7fa6f3d423acaf83564f8da3155235fadf7c2fad74d5a1f3b23a4adf49fb51a79c53a8b90cc77c287ff666c2b1221ed47589dc77aed5d0f0043c8d8bf2e85fdfc6c743707b2adc9d8d51bdb198d9f48fab8bde5a6fb661f7a22db7c9475ec7b977b8703d94fff767df43b91cfba52be6df60d6463d7e8b22ef6fbd52a2792dd79f73b4a1b645c379116d21a9f35e7dc7da49a48782b37a57edfac8ed14c64b511b6635adbd9fb89899cf39b2ff77ded743e2f9176b28bac17ab8b39461bb06ff1d115abc71a48ba6e5dfeec2f5eaf424ba45b94d95c0d328b7ebd12f914fa3facb0726b764a247dc8ec62d3d917bd8e06b2db5d7ce75a6deba69b818cfd3e59eb674d0672b95bcdcfb0bd2fcada2492fb277b4e218d907e3749e4bb91b6fb775d8b8174733663f15d868fc56e91c8c51cdad6d046f6133e24f2ba85ed1d6fb48cad7f4442bf97be47b68bbeb78e48e7737acf8eacb99fb611791933fbb87e652e3acb88fcc5b72b5dcf23b4ab2e225d9c93de1be3a25f27858164668b5636ebe3579b2a226f631dd774adc6ca982622d9640fdbad451191ef685dedcd1999a79d87c8d8707274773b5a56a721d256cae8eacb7cba65f305f2a74346dbb4f12da7d00592ebeae8d17a845c272c44429fd4bd3986b7f27a8448d76e3debbe9e5b7c8e0d22f9719c75715cec9b734c10f95c5d3fdbe262ae56c65a201bf3efea5e6d7c59642c10c933cecb9af5f2b9663340a44746678cef6b84f499b140da399d9befc2beff20b31548365b8bedcdfbdc7477b13fe4edc80fdeb822fbfcc5fc90712ee7cc5cfb9096fa6a6feba5d3b13f1f324618dbd2e5d199a3dd432e379b85ff4dfdfbea211f2f7ef674717c6df39095ce77ade95a6e5a5af1900e2e766e296cdeda5d23b941af1f2bac7effef900dd7ebf55e5dde9ad921ff3a8d2b4ea675486bd92d67eb930ef998bb7523edd9fcdd3887bcfd6cfbb94539a4338bb6b2381b9b5f611cb21d9bfedde07cab358743fa6b97d972b4616cefa940d60519d7175b651da94b81f40869b58cf95f7b5d7b435e661f5367cc5a776b6ec8052f9b75ce47bb0d492153d6d6fd6b6e2d6b36a4d7c77d1ffbb7f14dd62890152e7bbfc1da6674b67b0d096becebdc3336a6b4fb04d2bd7ff3397b6d02d9d7c6c5e87a5ff4599740ae0bdfcd69e363cdb9d590feb359bdd1597b21e54e43dac5b45b6df0df7373351ad23dc2f5d6f2db34aed5249077b1dab76764f5bef867c84a57a3b05e169ff3b766c868696cd1b967d14eb7654838d975b5aefa6e5bcc9221239c75b9afb4d9ea6e1d43be4b698bb6a36d7dbd8a21bfe75df031abee4648c390b1dd360a1b56d8f1e70824fd5a3b52be2dfa8b510472de5be7cf49dd3daf180219177de61c73eefb168221ab6b6fd97deefeb706bf909519def91cce7e7a1d04b2bada10fa830bb67bfe0149dddfc547d96347e75ec405fb5d756c3dee427ab5ebbde6dfd865e8980b199bddebff2c755c6fe32d648393d97eb59716d2b1e9b37ddefb75b2c61e90b6dda6fce06b8bbec7380bd9959f656e3dff5eab190bc96ec1f5e2631f7fbec51d90cccf16747c5d8dcfff0a39b99b7b9317fbfbd85b2117737ca94367fda8ad55c8db8bbd5b917d85ee552a64afcabe57ec07ed37ca01491f9dac1f52e83ce19c422edbefeaba90ceb93a6e40deeb6ea533ae288584fdb0427769377c7f5148f7d57acfd95ca51ea190f16ff43927bcf11f734fc8bfd7ba6ee89c8dce3227243bbdcbdd5a57fd689b900cd7a390b1beac276442be3ba797c2f91fd97b09f9ded6376c66eacf58039252675bbfa36f7d9d6b75a084fc782b3768e79b1d2421fbf6fdc7bc359c6d61778084bc70f964f1c5afaebae548cecf3bc880bcec2ad31bdb5376ca604b0501757084643442ba68adebbfbe918f77690bea1464a50335f2d2ff7fddb52e58e90d8a1d18f1bcd56f8b32b8deff4f1d14c17a357317dd8bd01bb7765fbf16756ed9f95f5b07447430846c8dbafaaaaf4bf93de39d3059feaf48644b9f42fd1991084a0ea465b65ed92b3be75e4733df76c264d9f1993d3fa6521a1f93977f2cb03f91ab5117ddf43bd7bc75b127f2c677d339bb56ffaf7e3890ebd963b1e38b7efd2977c21c8b8d41c73136d6ae21734fafad95b6ed8f8dc17a03491df5daaf3257a9b3b7a070229f6b535abff2b3774edf44da785b9b3c576d76d9a7503491d142f7932d6857f409b91b4a26b2cdd99c6deb5c64d8dc30918fd646db64b345ebb6b14be4c3d96d4d7637dd3f08c50611b6e6c8227ceef1b9d7ba9923a3eeedde7f5b0d0c283590f0bdfb6e8d7d6f7daf39144b64832cb6e63a67abb1457787528984d04158d964dc5dd7ab5028918ba75b9063ed3757b5100a0de47cc7beedb374397fcea1cc403e07f9e76cfa9831760f6180220349d7b4cd9c85cf3566cb27918f2ddaa2ad4c191449daabce2b83cd20e3113f3fa0c4403218eb6b0f617bd15e5e55a04422db8b0d2de477e78a0e211448e4bfeec99cb1ab9076838c3d2257fbcb608b0d2ec7aebd08c5110967d357dd6c933265af36229ff562db6c1933227b7a7d95ad5adbbbee6f11f9bcb669ed9cf72b6c74c240bed868b58ee7f4f690ad2ba02822d7a3efd67bad39e36e522452024a22b2c5761dad77bac7ccaa7d0d051119fbd55ae7aa94c28ecc0e91f7794d4a699b74d9cbd11069b9b675ef7adae62c7b2f90d341b79ee56eae7045e7027963bbb69c99eb59ed6a85c85ff4e98391add7d9b4adc6041442e44f372ffbb6ee16f37d129441e475e72664b7d736cb8ced84c90279c68c4e500469911d3a6bad1d63e82bf263f0d1fb5ea32dd6b9d616505a201b9d75568f3e2383d106221febb9e2bff6262092eb7b46bd5d5821eb1959d08bb1f75e6b10b6e5202fd8b032669b8b364259816414be673b46c76f417bff907f61f77decfa852dceeb879c3e9f85ac3a7ba7a3f1f62179f165c8fc391fd2c5cabe324a6bb7bad8dd43cef5cee07594e363ccbd1ed2b15bacef57c8b0dbcd43c2fa91d68f94f97bfe78c8cad35d3f5d3fe1b3b646faaaed41f81ee3efdb77c8be91b17364e88df2b276c809dff46fe696daaeaf0eb95eabd451566ff7b2acd2219d8d8f56e8ced85c11d239e46d8eae5addc565fbfd9443bed8ec4f7d5587f7bdc721237dead8e207e7df7be1900bd65af9ce37efad6c5520a1ed9fecaa6d5f94a6405a7863b38ca7d34839be21e1bbd3312fdb6f3dbae7869c8cbad6ae1b56a7cfb6211bc3e7acd2faec74381d1bd299d77bb3b431579d6d2890cdba692bb70557b5ceae2139b6bbb1295bebdfe30924a36eb97767d7aeb4ba09e4622ddedadad5c6fabe12c8c60edd8c70354bef33a686fc16a7f7bbae1ff56e9d867c74c5d6ee37e6757fa22157ab2fd649ed7bab9f3109e4f3e71e72bb0fe983bc781806a09cb1208373aecaf15e33a47b1a699d1ce79deecd5b8684f6f92f7fdff7ecf32743c6ffdadecc6a6cb7d88f21ababf745f7ce5beb5f2f865c94dd647abb61b48bda30e4fb177bf5758bdf3166472069b7c75cc34ad9dfb2550412b667f7ad9eee165f5a43206bb58f9f2e38fbfb69054352fa96593b6b8cdf93eb17d2b9d99e6b7472b3ae3e0a02195fb30fe9fdf7ab35a31f9077c1da5cadce1cb3ddd40be9f72d8fef5948d9ae45bb90acb545b919feedae7572215db5ecffc147d7b3d7c12da4ad2d467795597db70b6a211d2fe8cdfeba07e4531a19be0bebad6f5dcf4256776ff5cb1c7bd616752c645d8e2d579f7dcc3a485d07a437ebfc2ec3d6de65d35d2127bbfb58858c759dcf392b248c6bc1a6b5527ad96c57856c6d19bae9eea990ffeefe31b4d1c5c58f03d2b9876d6d57e8fc57b72924736f4168db69ab916e40ced9fab947597dbea014f27683f7ce6f2cbaca8e4521df47bf0cefe445db2d1492a185cc2e74ace9e3f984b4ec97a37ee16c7759e884ec5ad76366eb1a73e7dd84b4cc42fbae5186d3dd3321637d96bec6faf563952e284b486f2ef2630be7ac91db85a201e96237e7fdefe6f5f7108a1292d568b952477db9b78ea12421ed64af75ce77d74501050949bfd76597d5e5aadb08452291088e18f5406952ce41c9807c35babfc7d4bbf9be1ea039623c634622284748bbdc7caef9f19a934de7739d0c50d4487bbf321b593beaaa5b91e44cc6e4c3a018217bb9f7f9f52d77ff6b24395b3771e102a5088731c2e7dd5aaf0517ec5ef3ef74eae633467db9932163e20264e21189c8987cd829050485080957bd3146179ddb6a7da10c21d9fe8bced0c557dffc98031997ab8fe7ba90a3ab2c7e222d37f3cb18bdd0b1f53c71ed3974af31c7188cecac396577f3b176b13a66170ee474af51e68eecc50b9b76225b3bb8a07db4a36defba1bc8d5b4c2c5de70b2f68e1359d7abb4f162b5d959176316389bc8d87cb6a5abd74b81a389bcb43d667aff358c777626b255c8ee5a7fb5732eae241c4ca4fb5e8dbb2d8494bbb511ce25f261f48eb1bb36635d2d29b60da4d3552965f82664b15626394354f3eb1a745b8c3db7fc39e8dd6eb5afaed4d2b79032660e75329d4e52149d25d23e77e7a2a530d6ea7a5622a9edb5dcf4677a9db74789b42d56562fa4713575978944ad011c1a487fcb962375d6ae9bf43d6a92bc88443ee2343833908bdd5bdbb9c52e64b63e19c8d61c75134e5e6cf55b168926918b299b6dfef3d96adb187124916e1b9cd6d10ad76d1f6320ebadee26df3b9b37f85824725e3abfe9fd7eec35562191ecb2e911fe7593369e0fe71169dfcddb16d37f6f0c9d23929d7fbae7cefd643eab4854513d3f225145f5fcb4c71a91d65e6bdd820d2b33c8d69e6650169fc988fcf777abb148197d9f93e47c328d524fa6315c44d65f9739643f179b917b4a3da3d1e342a50a0d5cb2bcd1631848ff7899b3df356d74edf18c19f980a3886cb5f9b3ef5de67c75bf8093885ceeb67d5d97b10997bd88c8c5ac9bf0bddecab7d67888b4ac5f9d96b585dc973243e4bfcada9b75b58d31421b5f202f6df3ad3961e47a97339108059f0ba4cfe7fcaea713be8e0b7200a710d9f69f7bef3dfa33a613357dac442a700891eebdd8dc3f7d5ded5d8f4a33bd884422d188211b44327ce7d7d639eff5eb82c8ff3a636c9669a58f326f81bc1d2d8434b67f0ad7728148baa09d0cbfbdd5fed580c8d90f3abd5d69e5e65e5920effac6eedde4fbec1b5d8163c79adf72e8edd83564ac7f3ac708197bd7d5b23f24a4dfecb3f6b3e97e4d1c3f24f47f8b323779c566f6e2f421d9f93e5bdd7dedf5eac7e143fe9df0fafb455b65f321ce1e92fdb9d6b646d698a371c6114f81a387a43fe95cceb8fd6a5b2bc9f95100270fc91a9babf262dcccd60c9461a8e48d6b3579288ea2188a610084415a9f150090001314003030201e8e4683f180a82adb071400043f6c6892382c2a1308838140200c05611003411003000c40310cc42018848710a503849ba820a8843e9142f02d1d1faecef7a293fd96a224c17842f889144598132a873a232ff207f1282968704c281f2a8c54c9afc34fa368c26c5041d4915cc40bc42ba4663036e8227a1012858a5ff75e2bceed089f4e4378f0ba681e4a8fe4e3cbe1dd617dad261bc82090e7cc33e9857cfd72c95dc3be794fcb1cb5414aeb0431797e2d09fd6bedf3287a10df5aae74d7dfc39fa3d03186d81c23900603cf25ebc2797d7a395f552e724b974a530f941f5547b152544a81523209caddea0255cc7ddd350da288729ec57a87f98b5c472a428bfa59e822bab4074cc72eaa1bbd40745b5c70fa7c9738581db46e9d17936666faac774a682f5d4f9150d3caba65c9442473fe733b9a55929e5459a6385f9d7e9d354fa627f1a9e54c77fe3dfd390b9d0c4f8aa7e693e9b0f86bfe060a2927a542d4d5a0a315132918758da824b817440fe523cbf1eef0f7a873c868824e5fa843384e61134bf6dde5d7aa8311ca145eca30e59fa50d74c0cf835cf8aa99b8bd4894ae07bdaf0ae6aa401e2c101e92e64bbf7950285884b5d3c573e6858495b4e4771055e521a43ff378964bc8237a807ac77e13f09c5f3e3d5fd7bef7f3ce56781aec55a4b8c38f0907aa9f1a6fc6bed883dbd97399c9614086c930ae4eab39a242d980a5b7fc3b841a331639fc7eee31fb37722984ca2865c4ce80d4c2d4870f908bc8086eb78d77f042aad20f093ddcb6716a6f77060c18a5448a60b7bafaf72ac767e6c9ea556978840d3a64534a457f5e56bf37bb412203fbce7a68b612f3089340465a66f810878aede8649dc6d3e4f96eee56fe38c066e24069e0e61b285dc272238d9d1c6096c8fc7ce0a3612efb7eade431a53b35ef29687ec958cc111d4825840f12cad63b399d1461e5fd42e96f68c2c5e11edab4c52602d0433832807108df25df10c56196efdf9c1914a37952940e6ddcb46d7c01cfb331b160c7789886bc65580dff94734c087341d83675b944f973e33d0eff2e35258ebc838d437c231d79c5e4e9aab65a82eedb4a94d34c3abd768e3477a70f5ce1714499b64ac6e9eed1104b5e7a841860935d88b37c9cf05611419f09b65b3808ec03590443a639f954215fdcf4e175df9989b2ac7da94f8f227498204a2bef731894003444dd42d8e7454af314ee0eb6d0ce885d2df7db68ad241fb5e0c67a13da0704690cfa6b316906dd5562bae2e52285f5c8fe4db7542707ba7f362ac146b563118d5774a3179bf1288a1a02ccf7949de7d98190fcb73aab8d10dd41e81b7d1bb350063da2cece285dd78b261c7d42c3293bad48423312e1be99f13c0c5666115e9586006b624c4d0e0335f73b2abbac3909f6d43227de9ccae6e436a7d39c58e624cdc93127c59c8a39c1cce96c4e4873829d53684e2c73a2e6849813cd9cd6e6343727ba3951734acc89674ed3e4f4180133e2329befe50a7c692539ee10b2e0df828c8072196d9b6c4425c523c8084d224b3c107e8932c140d0083d2295f8121e8862824d4841f42efa6a46d78457a24ab350444a8d308720fe0f6553d4b89d76658a20a681242b9e1e9e8f2a8345218529ad9c3f2dc7093463932a2332242d2094fbf591fa3ef738aabd0ac56237a9289dea955633732d7982633964f39ee1fba044a0b1770f801582574754b7b152277ae2bb8e012d2993c2c6abac59c96b711511b6cc584d1c646019c0ef911eb6dd6953ac87f12a93fe993665bb803962ca1f65976d4ee07775f74709d2404991fc844d17db065e96eec53d7625b8fe92d3c08f62f2d81f52436b92b6d036ae4926ef48da1cf23238ea73dddc4ecc3e40493c3b51370ec19f1655aaa543f6e7339267768824027e44b739d924ae502e4852e4d805bca39073e62c0666a8b2cc957090a285ab80deee03c829758984f979cd0dc8d5b045ea5efb28fc58d57c5ca290468b5dad84fe81e4179785b73da256b979f0049c1e82e1b4ed1687c656e3dcb22b38a5f9fe55d32b703f19228ad23c11f48862e2c50410c64823a6a62d5edac79bad3848d45d3569e259aea26e8c46e31304fd9d1c9251354ebd2a711c5cb39fb95edf6c1e76b45c1d19185202eec6f5f671250988deb88d8204d57139447148d1b9a13ee2d0b8ef4637e2507661c5bfd326d1663d1c16605225bf1e97c57dc10015b03e9a44b71e01d45ac3ae6cde62df8f532e6a7f9c21b9ea54e52a4c74371dd9db4de017a230faa3624511fb74ac100e95fd46e13689af008a6aadde097c07646f1ab2e95e1c3018da2977513e78641f18ed020e2f0f6439d00356bd09226db125dbbda11f94c05a0813a69286a6b541fd89471996d0e207051342558d36a7b01c38590b5aff626f9a2b1b4799d18771b066cc21fa10316cb0269058e84275b46013f94bf814ee18624e9458e94aa9fc9ce8dc290e3153a0238e6c873a94f73712abea1a14dc8f3f00f8a63386ed962fb9a02de4949f77ac39ea3487e3d6816ff7590d4ea9891849935e24d9fe33ba8295736422159b3e405070abad3d460acf98152e9e8a3458137002e8b4a461611128e44e896b6794c0014f8b0b2969e9d33a464246b12d2b1e4a422f3884b92a626d09ea52546c816b1e0d4a5488e842266510ecb56057c51611d50de278d79238c3f4e71e6ef8391d941df90f8d804c2db5be13b5ef4db50600c014b0c6335294e10f0a1580b543548680a2de6c6b8be176400730e0c069b2d8b710f844a3226cad140207a36c7434090320a38bb11278cfd653b7269b58a092d8226060a719dc7a61d48302cb1d68bbaea2141ff40ea4059e5cc3f49ef4ed06ea5a22cbad2a66b530ef1223aead0c96c63d748299e7114bc9343187e35156d5e96bfc4fa9945dab1201f1670b338d77d849a6fd1271a4b7a6ec521107a9714d1358396a78217ab33af1ed8d98946d455111ebb2372e579518236e63d56f9719c4d9faa13ffe33680b05411a84c1da67cc22d69b05371ece5d173db5c20bba40752929acf89034bb092237505c98c4a759b8516bb83fc8259904d58f635b858f360cae4b4c08285edddd4450e6b8240676114ae39ba7e1c22cad1f147da277fec1201e6861afc105cbcaa6f782c4f14e763043ad8a8d4db455031e1dc154b76467ca5e172425351bb6fe8226d6bb4f961712cb213031d20cdd800ad6e3e60c69194994e85b0295d0e650c443ac5496c5fb9047c16da59df7ceca65511bab3c9604d873820fbbadc55f48c617970c64d17a73aee2a10ad262dce32b0ac582d0b282a5191a2efa396c648d79b9784c82554d22e4593a0d9d1eccee1ecda989a092797d4b14eff4e890ff04e93117e71b650f8844fc4629423098f5f298302f6f1d040f242a377ef08b2862337a6d66d5947bfa008d47988b7021fc59e04b1915fd730fccb5867ae2e1592241ecb3275b18e814b724d5b122052293e6d9194d7a0b7cbc68b09bdf5b3ab63e1f0abb4cefa7fa35444feb1b345c62593920503c182d588b9ac84adccb301fcd1a2e25655786365162fe37259c3285a307c29f61da96341631bd5913d17bef8c71f4ffbc21d054672e7f778084c752322d9c68d50e030af414850cd6e63f00802f4a516dc9201109fdc274201dfe3908254ccbc3045a67f9ff9637285490b1f0dde61dd4c268fc115d0b3d27a9162d4bdd5c5d2420f5b3a4e0825c3de1c09b0dcede3e18ee807a619abb65959d1f876a67506aa99fc3caf67440808e0518d4b24e004629206ad20093700facf40eb0d8214bc08a15a279d4138e0b587c8b65e24fccec4f0938c8e5b5c49bf1d1e0a1c114b0bb39a09fcf7ab361af2ada15ca289439aaad565f3d490d96f8d8411decbadca7393de628581e4014e12202ae4d0ad2a49c68eea7fbc5717e96717b1e55dff06922c23313721e53b4252e0eaf2a26d3c10a039a2b71b80d932dc1fe72f7b57af6fbc3c9a4872c2f3002a5561622c569be5b10d0fcce82a286c85fa8d405ad4d14f520ba962419ca2ff681e8dbe7e08792fb614dfd7ad4f634aedbc88409792186599786da782b9a64ea406a09ec9f2e5146c4b14933475a4cd5252796aef2439fc0751fcf620332278826db00d913e6750f7c81f9b5a01fc7fb0ab43ec36d1536b31b3b36ec82a3daa17026debbde7878268e0d8a5f8a6203a72fe803caa69a120fef77a5cf39ad698d3601a153bddd75b7ae530291ff1f55421d421a2615fb7da1de17ea52a5cca578d5952ca0cb6c127aca39a5ed36b65ad97ba46412d4d0d92861ff1deac62cec66bbaf9e6ce4d5c4265570905db4ec21cd2860b571313d564d395511867bb9ad88620ae4541c74717a015c464a51815ae18fd7440007f7955c6dc1c6429866060272086c8e75080d1367983934a2beb2080703580d1010e7d12840380c72d9289cd13692893ca790130d7608efe7fcc254d75bdacb6d569dc31b570430a96d45c21ed81b83c7d111d64532450085ada6e8ee610c7f611e59e8654a12376bbb3c1360bd9094d588b4180c0f6ea27ab3752c6153ee1dd536f4ba5e49db4f061aee8df379d25fd551ee1f9f9299b4230ea61bcc181b113c349e72bed4e316528182850bce220ed4312b8477f74257ae9ae7edd2e7ba919d0ec3b459a34f727204abc56cbb4b837460f6f72b1c082f7b209fde8c2adf6c6aa0d666df5660c6c205503c15b13e904c355adc3df40851daa228816a7c213386d641da61403a7fc55840389bb2a8274034028bd1a8064af616d5578e6f1c22cc6fc8fdfe0a64b867c0228a53e643d65d217490cf4194806b8220ad2cc27ff9d64bff3c439bdf57631a5dddaf5f05d903f504867c1744f7893c45d2ec29ee67eef1878c66867a4026d98b32f912dcdfdf222ae4fcbb06024b0ecb73260d4e9cfec097cd900ed999dac287c7df72d887aa56dfcb5ea4d575828f97b82a68cb2bd86a9f0d09e77e63a1afd43fef037016575fe91417c157b8cf38802585d031b29310d889ac7a18c5dfdeb702928e9e3a30883b645c22df404c1aafe87a498357347b45db2b3abca2eb15fd5ed1e2155dafe8f08aa657f47b458b5774bda2c12b7abda28d57a229763cfa4227e59caa1478989f87d3cd07150eb95230055c831e1496fdcd9a9812538c69c4427e04b3cea88ed71dce7df5de149e5ecb8ff324baf30e5b287a87a25728fa42d103143d43d11b8a3ea0e80b45ff50f402455f28fa80a22714fd43d10b147da1b0079482592622bff352d185927ea868818a2e54744045132afaa1a2052aba50d10015bd50d1868a06a8e841451f54b44245172ad100053c3a430a8a54107df72e55a18fc9f968c09204a9dd36e245c8598a400fe88f51849dc6b4a9bc4802ff29b22097965189e4d8edd317351a8d33c6f3d08f691d4f2bdd8fca5f8fa09c1a69d08185f8562dac8635e9c7e8fa4557dfff6409ba103b90fb371cae86df7fb10d0c193d57cc6fd4b40fc1db0aca12f48103e78fc89ddee0c32c8cd3d7e7dc21dc90d70eb67b9838b46ebcb97b6ebab2df755fb5b427e60504b133d749dd25050accc8d1338cd01280364b7440b04c3c9a249106c2e90741a37dd83bea7e1ecada4349aba6b0f62210539980285f2363c531939ad4fdd823ddead3b01e4d0f3c2148fe5c92d29b212be5320e32f81c5292e8244d9713ab8d952de384fcdf2ef4707f611bce9834c7e53c31008f3fa7128c4cff91d08b22d6dbee59440523ad7d2d5108015be83657e11dec915423280291c77133361ff90b841b4c2c3b57497f2ac78099235b32191f437ba1ba855c4a2f5e6afaff810a87693cef5f65601bd6171b29fdde5a9bd1d4deb214af27220f5b172cbeec03904ff872198c0d0b8937bf19a83ea95b55135a8eac46d5182c9cd7f140acda2ca0b9016e901e4411f123817ad4a61ce320cec44a1bb6d514506bc38558265216767be86c353105b63abce86240ea50bcc850226cf60d20edce62c90714931330f7322f1e37ad2d73ce276198a1196db9305914caa0db56188c4120dcedfd64088f939b1f8f46019bc746f71e332d6f316d5a125378921e9b12d27b7c55f8279d139c4a8fbd451f91c247b7b8d63c29e2cc4dd9ce4f42840b4146d77a9ad18a7919572bc291479d4551aa22e250210cfa58afb3337aacae158da7400c7436deb1f4fbe90ad2a7fb06fba32b0cedcd61ddf89cba81c1dc2c1d18b0caedee225fe4c52a0238b666bee25f54d0ffe06523930d5050481517e45b34b5de2e01a6680a442eb2ac87bf08faad1cfa49fa7f1e364c19e1df0ac7a7dad1ad028341d05b819feeeca179a271a9534c1dd60e493c2b32eacec2eb1ba2b674d45e839c7b5440ffd93d7ff09127548f8e89c9d6924608154f04f7b39a23d895407ca2cd5467b78b0fc4f9d22ea032479db036ce3a0560ccabbbd5fbaf18f30d2e0f9bf02ae27536b5f7e5a3a29f9c3c96bfcfdb620d3fb6a4d918f678f97943044d1e198163a55e7e2812895561f5bbb52ca32e8a7b5f08d509b5cbe00dfa0ca5055b3a706534075a8e11b24f642ecf3a3a19c153bad983ce0a1d5115d7c83a003deb4b907eb299682ec54800532c24eaa64fb1213bcc132b72bfac656969be0f9a1143f6c154422851cd3bc65e9f8038658675906c4ad08103f6c32d13864091521643b9213142496bb01f9fd6948bfc8039b44dee4d9217b967d64fc499c520382b4d5a83c778bf09f92e092e72815d7f9d6d1bc46714e8b3b1524bdd5d973aaf3ccbd139d26057ccc95dc8a9bd0aedc3c5b100755bee46be1c1be1a215df9383b25d2004280b53033d7fe737bd67365f4d80c9e858a45061527704035605972e739770763cc0dfb0ad9e049457c23aa8b21ddd90b3ab33d1c0ac5b0538dfdfa22f016e38b493f48bc881f74faef7a080953439284960cbae838327763b15cfa56118356be80a37af10d6bba9ea4b0572ca7aebe665e19ca0f6a49b19cb4a117d03621c3df23b30e1030b4cc7a7f3bd06a92c58a0bb0fe1108ff520221686d4cd63ab3084d1f1eb1448f4ea39a1039322c3f9fb78409c8e9064aeab2c7be400ecfc549d4a1b3327f2ca50bb6acc128f976d6609c00284c43ef7f3445083be51f3a3bfaa6e334fbd3e75fd41fd03c15303f94b45151c5954d7d672d0cd27ce41a7841034290ffc04b340abc712f239719b406288eb9b20f0cf4eec5852402c9f221f38e8d5113e8fb98783ee5e08a1ba003393087147087f7224c7c7de0386e0105a899e4627d6d92419e62bd948810e40d1792965138368ec65c0f8cd3dc153d09fd16dc317b1aba610023e91a9698dcb9843e2f901138074b29dae95c21cb2ab84bea2d5688eaaebe293154c5bd265a01c200d43da295a900e5713020bfeb52629833cafcd783c75aed5459c0588ba169c4a615a9ff7997df8b16a90acbcdf9885661368043f566260ab4085128352efc0f49e27ee698a810acbda76508e89b57d01a656935f5732e1afd801ed0d927b3a98388d7cbc2eb0d9ef9a656b99fc6de16554c64497b1b36b3431935f120a476cdda18908c2ac081b35dd39328989be6349c1304364626b8e115c3dd11eccf67633517f80d25788c469c76ac2f63199bc277a62dfa6b7d24bf6923f8179bdd64f214ffdd74920f0f9b7830a5c433f70f3fb6a76ec506bbbc9e6e75e9cb1f1dd1e88583ecfe1ef1c763c3a1e72e9baf02610da548d1ad1503587a6db4fb843846a47615cd76a12f8289ceff2435c1456764b5e8df36c937453e0a9c32ead898176235f797b00767fe2970fdfea8211fe6a9b33d551fa2d6d04196855181d6d102a1fb0b7c8b86edeb215459dc03569b64cfae2b7c6c7394b62d0b4ddf61d01412d744e0d1c280699cc70d96bbe80e371748c2a93651e18cf4c99e160a88cfabbd823f9e912dd36fd89bf295fba47bcdb9d7ebf8fc245e2397df7da64f95ecb466aecd67d1a3815d7cc1877f22e491a16052c6cce8d997a68935efb6985b13ab0ddade94e39e7864d35ceaae6a5d259a1973fc64050375e110ca86fb9e3dea3ea25544e9347f5683342d4c9638f11b15b1f89b90259233cc51870f850e85b1f07fd4d2d6113a1f5acfba2c25ee124be205a8a98bf26cd0ca440bfd122a3836e4b50a05e35fc42201f791390b28d3f251aef5fa814d32b601f0328d9e08e01fc518f0653ac3e7bb8f57c451f32e9773c551646366be2b562e0075d3d40ad994994c9dd8aec2ab0b49f34db394a8b7dabe74b6bc7f4a8edb5c554cca86698685771b45fd07f89d298158f73c7a0250051fc837334baacbcb5c6cdec84510cf50c41aa610310ca7513ecc44b8b67d4bca5750a826088b909ec601eaf92253cc5aff7abcd1706667c6b9611d4f0879c890a9a77661584939aa38e4ce1b405c697136da83382319b1d2dc9240b0087dc14048a2eaf66dd966020477945cd4874552b2b2ae0c8fc57dac81b312a7b0e70a4808664f4b13f520aec46e421cb8aeadf6c1625eff42ff1607722d9f79dee30e0f012c6a09a15752c418649f0a42b70aceff6ca71b041b5ca26212264cdf45bc80b1c28df5df81bef682f9af358d5732245529e1b529235d2b808fb497e0abfa2ffcd0550623dbd77af4df2a4a7926a7e0a7bac377a65e3c1d0f1737578d7d116b53158f22006aa5f0deb35119bd0b24bfe81aab3f848460cb4783b5c1b150498b88ac5e8eaaeaba57e9758296635fb66491c8311d8bcb23e5260524eabbd94d404b55279b1807e3d923827162932396b23abaf4d6f591b07b7e00de7bd1fa6bcd95397c531ccc0af0b460ff967f4024c202b024ec1f87841d74687a8c086967a159615070bdda386c6d9225cec3dc10977622a11ff8a3d2f8978e6458cb836293cf4f5a6bf13bd03de82c4f6317113a43f301cae807b50e6d22fa616aada0899cee27b08db7480cc6c37babca89a2c8856edf700d76f26497ae47ba19dd8540e25c63ae088419d1af06c95738e91bab66e35a693e4c6db0734a1f46958b089885547ce55da335acb7f3f7e40a31ddc4a5ec2c56535fa86ee7c16bbc5967149ac5233e5fc4575086967505f1e62dfb718b4305972e11c9252b45b42a7a76ad7c54174751e7de3d8e30dac82aa8295e791700c4b9c0a06aac13258c04783b7286286649b38f60c0c2559122fec2a426e0d52ad3d198710757d4bc1eb8b926830ca96f5e51915f1928bcc959eae949a312d33833ea16ac0d0804efcfa433f01756f1c56199d1193acba24a8c4a9f3373795a034d1b1786cbf6409618153da70422b5caf013dcec5dcb8c669ec8e7ae9649577a32e46d168ce5dd7f01e51ec81feae5bd6d5711a46c1c99f97fff4d2d5333c270f8405ea18cca47f246606caefd9e0521282163683382e2cf605f170bfcba8338715309cf81d894ddfad30eaf202ff9bacd81e9567e5de053266a4398b928d9255e0660c510ac204b45f3a4903468eae9950e0475e6226a4ffed717175f7601ab426ad9f29ec5293fbac41e0c86320fcb459b28e70710bf0200976b4aefed02d50c07cb1f7d91bd9f8da39a5084cec790afaa02d35833753549d528684c3b2201c534ec0c2025b2446393273e37465a4bbfd0f68183de2c7f8325eacd36ed6683f1761d6c08a98f0e83ca58a02d62dcc15e302581b73915ce5031e6775da2857a5f0fba4728dd03d1061fabb49c591b45222105ffc66e63da523cc320f9b8574b8f5dcdc035f5c40605a03b0564f14123fb2c500611a86b378c5cf0aa26a690073b768109d510721f1751c8f46658343fbd9e4f1ae4fe84e6bb783439a0af04b091eec3787d7889c3e823f0b69a42c0d125e1b978ab3fadd69cbce5367ce630619edd1f72727cd93cdb1b142bb3b08435eb091e132f00007ebe28a71275b6fc667a0ba31fed0861c6ca6eac6bbc2b4f92b3233b00a84a332b241fa579d3d8386e02671444c67394dc8ccd220562204c959086525ff27ad84c3133e15ab4378acdf4bdb495e4e11398a9ea360fd680ff7dc351c8219b7dd7df833550fbde59d9752fa4c1c84396a25775a3359b6d528c63c6dec96ead62567a261a7d1ad1d872b07144806a3d5b29159ce03c841bf2b1ec665df797e39944b3000559f0dcf84eff2ff65fb8056e20325d25fd69bc08409fb2d07d4d36b72949c030bc59286eda18eb74b60100d010d42712719dfcfafa99c5fa0cd8f06e14eeb5b5b45593956f74d8c4b42dfb58f6c2c214b6832f88a624a3daa9996f0cd37e3b04a96cb61a687c2c3002a711e30b58030b9334b0a124790b741a3ac54a44171b4e3384cc1f4920c6e7a110d6f67f11fd0d7f4f8098fab2a5c5598423d6f57abe54ac3181febb849138a8ababfe09f170af462815c5d199fb18d46d055227aff96ece7f1bc5004c673427adc0f977af9e074bda5ca153a9aa60145f54a7a81ba8947cd08280f4d1e3f2eb1ce8310912fb4821cf9af23c6b40545484661261340003d3b9f42095aa3a922a135f024f3e99ef718bcd729f5c30fef5cfd8b0b4791bb95f2f437319bc997a7845f7db63ad8357ac1c6ef1fd951f63f20be1740426fe0b96c619b37954c137c3453801b94739c7c05a25f363b703978e2fccf9ea0fdb342f33414cf01c6e3f66f8c2a90426cf80cad681c20963a09ce52b58e8544b3073a1223692c39c6b2f4b5a56342bd810a14b71ce04963a23b262018705d4316227c5425045fbfc190d315affdc687b8847857cd00a8ea50f7156b14a04f486329805107a0a628e104977021d03fbd2b48ec8011d9163efa455afde6bdcc4b7a9cf678af4eda653cd291784f98f64166578dc343085e6a2c301853eb07e69fa7a8c0a7e76156fc7c932a13717fb248f168dc4c59be48c374567cd7860146603ead5dbb5454a0c1b50c615c7b68ef37e4a0a6ef1c605e5dfc27f60b0b975a3bb600fd1d684de0922d3774c1c708c2a7bb104e161acdf4468e83a629f017e94a664278454ff2d628336db7a4ec1f7228a3634ad25a4239a7513ad75e9ad12c916d28cd6896c8369466344b641b4a339a25b20da519cd12d986743a8cfc4acc21c4568ed6e20ad72d9fd4f4fd0f249a9e0a4d33686f336842fe8a314e7de5b904841553d4828f15bc5f5278ed6bbcb8606674a206f7f646498fb7be1c8797d770fe6a2cd17f6301b20f650f14e5b03590c11ddfb32ec8f30cc7e83b9bf943bdf916937590a4266e4902ecf5c8969f8f884b148d5811727d52fca5a1f42c92f372a43ef3d1bfa06716d3a597c30e56d5c2645a4fd3d1bf82b2412247f573b078d625f665a83cd86f6210de2dd00de181cac35cbe4fa59ffccef53b9430189ab1d75d401a8e1b039585ab24c91fb9f3307d4ba16f62c5e0a654eed9b467dc0c79b6eff8f3a6f428523fcc08ad12d156dcdd6ddf51e2c1e7f6139ad1509abb4e181ca22bdef7f79a26d213cf333af2a38d1c96c72b8278ba9708698987aaa3d887170b1b43b344cf5005f3c64c82c8f220ea3a9be51614b687b50b810cb5b57e767c0eef5d21f45ed5cf8ea9fe64eab42fdd27b0f0eeb98baf867681db26dc4c60a05f371c1a812a20d97ea0015ee0f303607b7f8fb21584c3fe75f187ff174a5f01a2cda8c3185544373a13716f93f6eac90a1fcac6260c308b1e01371301bc6acc1752a15f01c38c009fff96fe3eadd4d6463d0d1250e320ad917ba821c1e4a193ef318b85f65a9813e4419a5ba221fde708736c571e7dfe3277fa83edfe92fd1416a00537d16492de278dcdc929edb55e1d7414ac68b833b989e799f0194cbc8bb87f528af3559c137f2351cd59f8efbe99740ab508be6243be5f7e6e964f6421cda6de3f244d1012f0540d5d798e291b90e960610f118a63a32bff1da09282b27d6fcbc37c41924e2765ee07eab9de4bcfde63220e79e1aeb3bedb9c5297d29df37033abbebf5ec0ab57c92dbdc7809b097d717c5063063de8b1433f840ec55e5364340e1dae49c7809deaf8a7f750436de65fb36d1b767c9e0263e43629e2af0337d9ff9230b462a81c3d8bf60b50f8d7597238065c7ab592746ae5be627582182cabf59115e21a89ca1acc10eedf8cb0c8cefab1ca645179a165627666a5efa301525b4fe67d5132a8daddd2c7938caee6ad844d686478bc194ba4934fe895d54099da3ae58dbd91378dc40ca44df743ef200b5507951063bfa74e724481472587e7e43788ec25247f9615a093edb7c13d46af30ca73c7cc9831117f5ceb63f7464221abcf4ee0c7b24840fc818f79fd9b9bc5701c76d71bfdda322e7365a6f8994e39f4ab447a480c401005be50ae6e67331ccd6aa1f3ada4d2a37b7d29c3e48bb615104f8214d887b4cc65886f194bf100ff8a3a6683c38c8f895b95f9039f716a4b879db59dff38905d7d80918d0168fd4a28d11590605c3322bf1f0ae3a73e5780805529d5ebe4a7064db1e3bf1ef7a88da1213fbea544d8da1cbf5dd068485a3ca8f24b6019a6887c0829e6caf4e4aed6c294546c0081f98eaa30177188fe14c90b57ac7e509d8b1ac33e737b56b6395adc3c3eadc4cab2e00358962e669ff3fb3bbb69fb9d77edb567d1b60acc55b07d7d1622c2779e8792619c21009f0731cfa3c62ebf705e067943cb6e021432805efe0ecfcaad9c0f4ad3342a73c230d4dce0335a78d44d4647ff1653aea38c5120404316a7e9276c8009b7c3281472541272264de6affc29178a34df210bebd377cd46e1c829b9da918b8c54cf2aaef61428e06d500a74dbb4ad9ba2a854030e2520942bc0ffa9b6cbe98dba4ae12ee5c51b58f70169d4420b10355ede9b6f083cf02d96c407e797669f120c9f8dfc5bde3bd86b3361645c327aba53fe520ef1b76b25137ee26022001a220c4f3e3e6099a19c7bf54533787f983cd0acfe03a173888a8b6bf9dfef0026769e245c31bcfcbc41c752912e1280fa2fa8510fff01ed8c5b45ac356d0c796a909a294e2e5783588c3409349d36123fe630b3d64e5cd31182452b7a4babd5ebe079d7903f03882cb3f4bf8f629fa177e794b011e1a02e9a1819fdfe876257f907494842023a15c14f0abbd48a4463e93df3e54a7c0f6b1b596d3845e258f03bd2236bde3ef938fb30fe0c95699fedf5c06f2456dacbb1e830b7c191747dbd2218d9dfe4d14f0756166177d707cf2e5b82394386a0d1f8dc0512977245427a5f9989f635719dcb73ebb1730fb0dd56f24d1447801f6338c57bc3ce0cb5ada3edafe495583832809001ba5b4b4e179cf3ac8d1ee11a1eb54c919fc85be2bdb05b2dea4feec4bca6465b7dc2747f2c5e99b170b84c21f2c58bdcdc0f4bfeb0d6c391372bbb7e154abe1cfe805df36a6cab922f6fd777e059218d073eb8495a970c45440adfeafed9129416fdd6228f0acc3f56c33ca612682c7a81352a8e88b2563bbb279c3f2c4a0620f5082a2df3c4f9a1857261448776651dee0ab065d331ba69ce3cf9fb287b6557c359cc4acc63784d2bdf3e90fd9afbb2385b7597952a0c859a08884a16cb3687befb83eb566166f4c48cc75d74cdb05db2e4e6f88fa048902983ae9ab6b0662b54ea809870c6dd62d461c673e49e445d2cfd8f845ada56b0c54f59ddc1b352e8ca04d9405d1bd49cda0dc454fc1d924ef20cd8ea7d351ec096b09d31c8838406017801208f2a7c7769831ac0ba604d3e384c256c72ccfc49db4ddb9638f3255c80c01ca7de979c70832ade98f3149bbb5e0e0501a321a1ff4952d05cb399397ff4d40b67f7caa6debef8ba067fcc0174baa45c11628af150bcda1502beba1998968a4aaf01c23cb4c01f70f110edaa753499904f5bce4f66cea2f3fd803693c39e94cc4ae3d3e21caac7c318cf254fdb146883ccbae6a8ddf2b7f6e5b2a3d2953dd252038359a46585f33cccf1e8ec75c1ccd5dae7e60aa5e6a2abfe2352bd5d71eae954bbc6eccf1b43bba5ae74914f6ca16e47ac927cb5f6796a6a59befe56cd4a341e174da932144115509f669460ed319e5539386a5f67b09f0e5c35d198d294917093a6dd73641f6b665445368f041ea990f93169cdd1f8335944a75f77079ef28865cc1637fe82adc34d3accc9f3f3ab5657f1b9daa7b76be7e1fa3c505b8b747e2dd0f1199f1b05b613e05d2ced6ddc0b1cded3ae731dafeb6b6d07df3fac23b63696bb433ccc98ea1fd14eb7555831f681904b59c3d06439b34f2135f74f10357bbcf2cdf9d925faef1f37cff338d34667760d10daf697fdf3d73ba5561d854a643a5fbe2377f73b92ecb5352c07fbfc203856b2bbe178578ef3c40ec2bdbfee7cb9a109c13559929ba8a80ea45762ad61154f2f8063a5dd28ce56dc1336746b2e98ab47609bf2cffec67c4cfe12e2968d74ef680707fd83f21d13ff51b5eef62ec9d9cf1439b35b4d791f60e95dfde370a0299e09d02933f91505ef07a14bee39d220e5110e1091e1f3aa8490b72c150ca7fa29710c3d4626d542989e689beae4684b643a645742f5100dcb6816c3543be85bb3d4a5e3463ea01e8979a285f3f6e586b30ef006cfe3cf5f0bf8a81c7743fd948610b37c904db1215afadc6041f9737fb330084770a57e6b9ecde38598f4c84e76cc0a25030e4ebd96f30bd50b6aab55acb53de1eca128b01df93723ae4130bed8f5e5b5453746462679a4d2cf098e2c492ea5253dc69cf36291a570a50e5a6143d3e9c2bbb2c737a73db275219593d7abf5ebc9ace85e832391e7e4bb1783c7e1b69a129f3e0daff54a2016ed54d2fe1a66c6babb4f6b77a39d096f0b126b49b49739f840da564be79284cec3643049d8044c0697864f43657059d83c2403998548c093d079980c26099b80c9e0d2f0d5e1b28655653175bb7b2a5515185bee3a0f9d82e560b3f0094c1236019143a6e133b00c64162203cb41e7a153b01c6c163e8149c2262072c8347c069681cc42646039e83c740a9683cdc2273049d804440e9986cfc0329059880c643b5c5ca2d4b2076e1a6fbba46df96c68b1f5e39a466b3754db881a2cbcfb890683c4a6a104834102c34082c120816120c16090c03090603048601848301824300c24180c121806120c06090c0309068304868104834102c34082c120816120c16090c030906030486070983a91d888764030f1bc717a683861d658e0387ca9311379c0493898b266cb2dc9742a72164ac480455fa50c98a3f17042381e63eb3f5174ea43e0fd9220bbbeeac095a407f20ce0f149083c1cbaba4f2d86db6367f48ff5500a9792f1b2608a01792071d3909d00bb65a826786040d00b694cec1448830d945fe732d7c9c95e4479817932c298b1a514ca4cee1c0c31ace6691ae4d04f50e0bb3ba1223bc71502463a6999f39761bdc67aa28e0241ce8f2d5eff36e20f70a43a6d4aa863ad5aca8221633dd5847159d44a31a2f53acd54a34cc3ff89d5c36d1772d499cdf3478544c9cffbdf42afa5fc91a1e8da342c555b0ded67acc22fd1a26d9e83d34bc58ff1afc51d0ed672e124e4dcbb85fead180ce9fdd6b438fd9c14730123e64b8af6e50639d441890308a1d25f151c126d15c08e84a457ac1af430bbf7fce5eca85a3dc5eb26573b2cec24bb2710e6c454d9ff8d12986ee7b7858bd619320148ae8fc66dd30d3a832b5f62bbca5b32a6324cd04510bf92b993b4851013b8b422458ea9730f4641be90e366cda57fb6aa9551a91c91499421e3d6e23f5f36a8b30ffd4374f15bdebe58a24ca781a833bd0ae7f5909332806c0ee6006c28d3a54338ffc5c4f97ae68565172d5f250919245d4c25e931882ae017134ebdfc1541ad944b1800a78f4980c0170304088232c1ab9e578794b97a1ca901014f0e90a3863edb97016e849aeebc70f23c60b5ebfb7a94b97a6ae29653950943e742bc69cfc84433eac203d23d163f3e96cfc6e25fa6e2d7d597ce311337b77127fee47b5e1989c00f85b28569c72fa6899f5e9ddffa93288589e554a2cea25651dbad03a5dbc1de42c0b2f2e69c6d2181b21222201fb3ba0e591b606f6a11bc2f268fc57db69600c2c679db7da47217443634c7a3d31439db03307ba1f24c80e48a9dfd98afa2d17d0c67918b693466eb1131ca48341fbb42b42896166e5a3c036bd1dce8fd96f7035608b16b54e7f90ec39bf61bd93ba9f0df753dc344166765ee93065bdd96c0cf1a70b5c2a036b825f95e06e94b6ec0ff7d661df42ed048b092b403744a76130700116fd664395eadcf61df46e2ebe23c943398607bc29b4559cb4138e9a8801fef3f55baee3a5a445a3c11101f7e04154d2bdee4c86f46e06e7582c754bca1ae5cc2015c5cffe4b26fa649611646c8f4b9fe6ef550115ed16bce0efd6ba79b2ee3f787df333dc69357ec83823ead7cfecfcd02688a1095b374424a998f628dc72fdd557799bc4066060a1c0ae1c76d45571d1b8f1a9df94642382b0b2e1a5b9579445019dbec919ef129b8d14210cba2955fac6d72281a0db8e30042590e73a2ae8e8f8cf85e649858b4034657d85eff5720b822a3d0939e27d35e9d61de3df67a8d57fc7015dcdcaf26e965505eafd734d7b354dbf8115c70a613a0e8b0640c6652e91a0de087d9b2b9281c78a7c3c7c0f04c8c175320812660000ed81778f72debe725565b09d8c66ca9c42b91f4e3567ff78d3a44b27790319d9912358610f42b22d758fe59955f41a35504840a8e1633739171848bc5b80cee786099d997dd56c5b3b09448ed47077ed7d179d5c056465e9f2646aaffb6032ef9c485d7cea96cb9b67cef28a3966bb38af2879664746096755994bace39dacc11c6af8a2c16e0b950f03fef52ebd9bf097a058277040b3d4c38b820e80ba3e76850bbf4a77b50656eef5ffece58a916aafd9a85d19d2e3bc14480a9cf2b5d28a6c013b99517af20d15f78de6a691f270d6ea49a15cfe671872fe249582fc74f23822586ada8ce07a507ea6acbef5b0222f583787ab6ccba7f87587e18606f2c6d5f8f27a7bfe8b8a62fa15db792ac22bf41a711f2070d91d57e39ccda4b46e17ad8f7c3c138c8b8e2dd40c91a6b20df26c66791fdcf741ca7887566b10456f8a6d4a27201268e6aaac20bc501a0d059337d6125c9f0e91c010ec82450104c3ffc87a64d3fc84a9794db3bbcb1ca5abe892f0e9e77f563ab2877236c04808774e5719630b9a734899b007ff9c14cb0cd6f075f127a265f5c4ccb1bd05479dec5e97a50b1b2db11554358ccbd6c41da345b78ea6f67faa924135cb2bdf9c3a7f01d90d413a834cae56780af7d5c380ae6bfabbf33b91ec4fa8fe737ee865531db656f5ac15b7fb2b5f1699a553896a0bc373d5f11a3a6dff7feb51d44ed5947e6fa8235db03ab5744338636b6a420ceaae4d9f99584a84f59bc6eab49e1a49fbb8ef86ca48a7ebfcb9efcd9bbfd4a235d5f4b091e83b4f04496753d537324eab6b6817ec377f39614a0a477cae59b4c2280977387aba4002051c3f27946329232ee91584667cc5db4451d9de79861cc0d85b45bded65d0a872c779acab0439076520f69eb647156e0945460b7367fcbe5af901dc4e71939f43b161a29f7b6a6a4cd33e1f57232584d6fd0bbb2cbfd68dace15f9e7dd8183aac2fc8d099f77aaaa3ee18962871a38e3006bd04d7fae315cd23f702f7a4a1d1188913b1a790cfff0586547a64d8a4142294dd13a9bbf8698bb0478c091c75dd818bf30ca9f18329a0783324d7a8a2f443d991c021de1badb9f0b42d13687109fb21b42ddbcda4f60a56f1810a18831651eb3fc0d30c08f80e9194de74de9d9929c465bf69d56828e8970cf50bc915cf9e9c6d00ea91307d10e3dea825d8a8ac8e1f1b2a3c5378955cc5e6e660c43c660fb5c8220758c19864045f2294490b7757919861f1b87e1fbda45a750fbcd46861073cc3364ba95001e71b2fdf485d0162ba8f04c0237790dd368db394ac731f65d8246fddd57ebc86d7af7ddf2072991ad29711faa4c977d54f6b1395645e7a77b942d1a428986ba79c2ee5ba7dc5dc61fc172ed9db353b983f11436fa4a3277caf36db70bc57767f73f3b07a6a58133c7bd2d139c39afad21b3f1afe94bf732961d9c0bdd380eb437e6144bf12593ae49989795bc9440f62f5af728d7bf22bd43e23957ffc5a5214af8cf94a32eefa8fe36e6e60987c706b491ce7da9fbcdbee147281cd9d05b4f133a9dfc6396b0336dd89f1772cdf686fc27aca120def5d4736b3298bd3266f23ca51ca161f53e8559252b65dcfceee68a3cc87030bcc8a4e1cdcc3fa6390b3f421ab994dcec8a31648f77b0a499e4c1e74690d30564b26fa71e2d7433fd1d8785620a704f447d182799a2ff4c5b207ecc9c0c570b8b492fa4ebe1484d33cf9f4165b015ff36e7195717a065979004146f436de6d91499ed17809c40c7772f715001c42ae45c63a1adf0a7330fde895095641185971f12a7a20e8afb747311506439f873dfaa0c879d7b5962fc74112d70f8fc4cd52fe3da837dc7e562225130174f27e7ae9d61f85608154cf8eef7abe3d27c106fb00652854c3e3377fecbeb7c0e6b548d84f359f7c5f2a98a4a23f1b46760f0921140b19a8f48cd2f8ee4687508c0b522c51dc24c5f9adde1fa971c4f5ca2a0e2f74b6f3675a37b0e0af9661ac475e0456bf1aaea92321270df6b3ee8be48ded6503bbf28aa1a263564e94e6117474e16c0c36e011cab028ec384310c3660b46439330f0f0f0f0f0f0f0f8f3121b52563d8354929498dc4d913fa419e524a29a594343878177a3a33f1e9cc7490904d5a8206017e0b5f0c890cc7e58fa56637e6aed1020ea4ddf8ed8b2767352b6f20c8d978c568e94e9ddc40fe52a3de49d3d4a9dd06c227554b79a37c8774b9021fa071430b361083c97c41c65afa035aac81a0f5926b4cb3a1f4a3b96a20e80aff41c80b4a77e38729d0220de468172654df75bd6a1e50adc00768d0d0020d84cfe0d59764fabf2a9d81a483c9f4e875416ec9b5300341a67e7adb0c6a510662fa0a2a7ef8359552189574c335015a90816c4a69aca73825d5320652ea8831259f2a5477c4408c1d74f475d6383d2a6120c8d6fbf46716fc531c0c24d9faff4b667f81341bbb414dd7a8ca9617485a64c6244f65b44dfa2e10ae62b63fb94efbf6b9400a3d4ac4e6dcf5a4fa2d1073ce7dc6a0e4d39db440d4cc0c7aaee3fb7a521608164d5c5a524995cc090b24ed9a43f896bd9a9cae40d0abb14766902fd1252b9045655097eeb93664a90a041d4de6d4f7e4a20551819c569e7ee5543f85d11408a3baf2e65e6d778aa4801a8bf99c3d87a2402ed7184fe7ff3185a0406eadd2ff1854d0f1739e4052d9cf3deb6bef84c7092475a1762d7a27dda32690e6b4b4a62c324c20ddba7a32bf6f8dbb59025936745eb5d4288198b161747a8c89f24b1248a6f226dbafc7af549040b4b1dc9aab92feb8942390faec83d22d9f62a5c508c4ca95b91df4d5d72a4520665ecef6ad0a11c8f6f954dcd790dd7e0804d1d9124a08f117354220ad9cf014e4a8f49c83409241577d52a2f467e54020ddeb7afe547f39867f40d2d75c9bb24ace5c1f102bd384098f1afe19f7805c25ffb5aebc457b1e90abae4a5ace77b24f3b20c7ff2cabb13ecdbb0ec857c137c70d3b0b770ec89fcbefd226570b1c1064a79c27c2672b78abc50d08f23aeca9e03d1ab4d5c206c450ad11331b937db65ad480a062ce1de15135a8916a41038277ba7dff65ceedd15990b53c6cbc30137ea3ca82ac9664ce4d7955ef692c886a2955ae1dc182682335f58633d9babf827ce2d228f3d0173eea0a824ae5b521aa9488d80a92ffcfa9ac088d1f4b5610e4981cf1aeacb931ab2009edd1e3e8a8cca555418aefba799376d56c622a089bc4b267af924b2d2a482abb67ce937b1ff34e4192ae9b6216d5dfe79b821432e55eef4aa59f540a92ede7dc1f3eba8b0e29c83a26f3e51cc7b6534641f24c5f33dd4bbbbb2848669e935bce497edca120260f1bc62c73e878060a629e9353aab222c027481e2a54b0937aea2ad50002788218640a71395dd0071b8d2e0a07121104e80439a55ca5bc62e91ff1738220360671edd579ad4d9b206ba510e9b1e38a9a34410c4208219e7443663b2f9804366ab48001d7015004013241d24cfe3bcaa26cde0613441d3d7ade84d2dc1fa66196be68011769004804012e41ce6f62e4cebbc54bb104496e564e3fab50b97f2508b6d9a3aec345b9cd7b1f41004a10f584d0d2d93f194a6912c4370f4abc627eb40f4982acede15afec26a4e21402488791764e94df265fa040972fa7bc645cd234839d655ced4f7aad9e40892ec90b93557744d336a04c9453b8719d161ef7146102f676ff95c5a0439691ba552bdab08f25a7fe5d3557abd4f022482acd92aae5d67c6b11c4410bb3dc4e2448a30d10f41d2ab1aff34ba696749004310f6a3494b42a485205f8fbe6be72ce266128218e2f3979053f2544a7941804190749ccb6d759d20086a45f43655e9a462200857ba73e42adda775f6090008724a9edb63c6f3ed11fa0329c83629e63b522f5d3f90a2dd7d0691b9317d4a803e903277e5a98c273e903b9d8fb61c9772a3680f44b3a046687daccca4d103412b2f8a4aa755632a7920de9cbeab9c8e07928ac94a9d07a139e9fb0e2413fe9db474ccbf9bda81b42d1742bec65c5294752009953954576be698e840f05031580e33ce81943347b3bfd9532b1d01e4400a9ea56290fbf33713200e440b5de2b2f5fde8121cc8b39ae2e63e466ee9df40181167a7ce3c3e3cef06e2451d4f7993307dfddb40def351c945aa65ac9f0d64cdad1d629543a8f9d740f6cbbcdc76131ae3570331ce56fae72d65e77e1a089bfd99e2fa4163f4d1405ad12d4ae6dfdb9e7f06926a56b8d5a5bf2fdf0c84b7145db24d964ae39781b8f93bedad25b3cb9e0c04f5afe449dfd4c8d48f8118975a6d9da7c3fd622075c79c3da508617e4a612087a6cc71d4b7c39e1218c89e7447abe89fc442e90bc49474533e15edb24df202514685ea7650556ba72e10f369fece57791becc405829f9d4973f5d4cea62d90b6840ea6f30653329ab440f29aef11532bf369ca02d12cc93f7dd522d64c582055d6565c5bfb5066ba0229f3f6a50a62b2023989b7ad20df496496aa4076d5116d5d494386a840ded6d82a11d19d3f5320f6a84e2bfac34669a440fe0b91d1c3620a61890271837866cf34ebbe4381e8c1f3e8e9f47332fe0472b967899ddb14bb3b81147374b4b45a41297513c86da6a277d5092d6a2610b4458495dccdca69b40492be7c977683262b1b2981ac41b5f9a9889240f2117a42e920bdce434820c69cfd3ba85cf59d4247205cea136b7b0fe5163202c1335bbd6f3fedfe4520c92599e49626b9f01381a05216153cf64320b85fcc22b392a8242304927c128bf3a4f9671304e275cef82c7127bc060249ed6ae766964b22fe01b9bd5eb4fdcf62907d404cea42a3555ea64af680e06ff24d6deebc3379408e39ce1d902fe6e97071d995d10179dc6490aae9aa37cb014105af1871d721000ec89f292c7bca9a721811e00644eda4942975992b89086003824e594dc676fcff87003520bb09f52484ced7b163084003b2c80561a34e3f556366414c2fbbc993eaa493c5c882bcba961746b4e9e82616e4d45acd9bfaaf2a37b020c70815299e7e05b13283c66469e7fd7505f92f5777901fba2ed90a92f8b4b69652b8b3a46105a9f453524dae6d9634ab205d25f17999d9e94da30a92891f1ddb477e16a149052963274feaf2672695410569b39edae7553ab5cc29887f9b532ebd4147faa620f86aeeac6a73fba752906474e9946afc623c91827c1e7bf9f553f44ba320cda7f412f726a72251103ebfdeca7c4241d860711a9a29e7784041d4cd1be3cd49df8af90431ab85f8d2992752e309f207f3243a8c8e21339d206aa5a8793d293bcf0b27882784caeccecb2608aa3c6dfaba9626bb6882b849084f2ad6c54ce2920992be4aa2f4f5d827b76082a4b12b7fa5cf4989b35c821484ca4eff75dab52c9620084fa6515b543e152c9520e9dc4a7b7993c8e0154a90552c356c8ad137599904f1352699f9fcaf74154910e39877fc184f85592512e472f1a4ea7ffea6870459bf2ce49eea8f2005d1a13c7d54c409b923885d77c9de84fe4b236f44daa3fbe534c40852ce2c117555fba0b3085286b5b2a4d747c71845103399acb64f2995691241d4d3dd3b553a98b6382248d67aaae3821e15753f0439e898e810cf69ee764310cbe35f26738f57b61782144a0625f5acd44c764210dccf349cf8cab2591f04c1b3564a163b2a967541104d5752397452b1d1f44090455510223fb6594607042988675e1475b199f91f4876712df3694fd5e57e20593e99fbefa37d20de5cb0ad2c9f9e793e103b838cf16f6da6fe3d90a37b67d4b82767427a2059258da384675341280f047b1bf59b4c5b5d0c1e48b3417a8c21f4dd66ee402cb3d29d2eee7c753b10a3d6c91effd822d375206cf7ae05d59acd331d4841586c7ad60ab3770ea47ce19a2ac990764a3910840eed39691807726e1d2584c84eabb0e19069391b9d84d76f20478dadd41697295bbb81242b881342498d9f54dd0662ca67b15e152cdd6703e12e2e5ac9d01a88716d83de319dc23f6a207a5a8b31acaf85f5a481182d3d4ce9f6243c8306e26b1af7a839c794e467205f66887667340f72339052b4cc9e6a7b3dc6cb40fad92f65a72603e97cd73ac77a1c4b8f81ecf92bc796a56f8e8a81a449ece53d25878168b162f64f6aa2a2c960206fe5877c1ded17883129a53ee98fa24cc75e20468b33f215b71b33ee022996c60c16eae763121788a554a53f5561b44a5b20c5c8b7b433ea62425a20a8e63b8f1bb459207a4c9647dca26653592c90e5e398c75e59d3cb5e8118effb5e1699510903a9828e0e993e0915d7020692757b36dda2c209e17d81947f329e09bbad18b617c89e3146669c27d5b80b7f7cadeb346771819831c8a829fa05e5c92d105feb4f6d9021df542e861648bad51ff305b3b3d1626481bc3d975163f63fd852031e37147023d9b0e1e181c3c3a30e31b0408efd4c4255927dfeff1588db7945c65df11496b70241bdb2bd47c5fe93bf0ae4cf952e1ac4e33bfb5420d79f8e49795b6ef8690a44db2aa1d9eeebda495220bacf9b6cd8f930738a02298c87eb982fbb4d9aa040fad9fc29a9cf72ea4c4f20e7eca43fe9aad3922527904aa5558db2414d20fde5ca5b5d397ba58809840ff671dfa33d93be04821e9d0d9529c5ceb012089b64ccb1729da6742709a4ac0f325bd642a8dc4102713b848e49740462e56b92a365fba3698c409cef74cd92972210c6468788408cae1633d32190b46906e1633208190a81e0395a8be5de3c9d0b0259637385785b8da50181705a19e4b3b377b0fc07a4dc614ebf643bd4d23e20bc56540db6f116ef01b1838fca8d317a4c211e90d2a9ff4b9ee13547de017163dcb2dbdbc61dad0ec849cd4f5b35e6bc8fe68020746345989041748f0372e7ddeb48cd0dc8298712a69296316c40ce9a9adcce4c67cc32460d88a763dedb61d47889316840ca70396bcc4235e57016a4d0a996cc4486d4fcc982b0a692860bd9797dbe58904e2955f55432b6650f16049372daf7a77d05b1b3bb7e89470da3e40a5267c76f452faf246a05e9ed2c85aedc5fcf610569eda3789ccdf08ada2a4861d7938fb7faddaf0ad2e80a5bbf7fdd4e2ac87de66f4153f691511d00062a48a97a3fbc79386d3deac1380529a9bb3c23568427b97fb0a51a377294a51a377278053e40c300304c41f2985dbf12fabee674238d0b53b85841c980dd0aca8d09c0280539bc2c76da5dd11eef072fe003477940df17e504e815f8008d08c02005c12a57ded177e135c5d22bf0011a2f80310ac27fbe18d3e75814a4a4c9f37b65abfcb1120ac285bbf50bb74141cccc24bbfa9b4d59f413e4371565e6fccf447d9e20c7caf60a5ea937f6ea044108212b06d9fd539a7fb01da68013e41c77a3c56416aab76d82343adcc48617f929d69a205ebc6a502ba3d9b665827c4205e52274c6115b618224ff55c3fad26d87dc2f41ea4ca95977d2f7b3640952fa94e436578e1743a612049569f929884f1da34c0972b2d42905b1b0b7ea26419229dc2c878aad29641f6c8886f3008624483964c44698362917cff21bc08804417b3cadcda537552a0309725c8cee399e8c6e766000e3112473cbc933a589939ff3c1b6826483860d2fd21250648b0b4c608b19c07004d1332b790e32ae3b80d1085252fd9b446cd486d37fb0d9285d7c4103188c20473f25e6961d45e76d2180b108d246df1a953dad8b755a71e0a061ad3834e09b6e70053e400301301441ce5e6f9bba34fce79e08820a9e4f664c9ed2b38408521c59ad196584926fc238042969eb137332d8e8fa188298fa5db372f7b73da610e451317fb6a082e8c948086234212ac6ed2897416510a4746ff9ad4d8b4abb0541ce20cfc743a60341b29675fb9446575ccb079b59d200031a1004a5d2baaeb6e9cb961f6c673870a473128082a4016f187f20b5a6074f91cbebbc2ac3f003e1ff52dcca296c3d993ed87261f481606f6632e528c2774d7c208ae97f70b1b078a9f9e013ecc164a62c8a8e39ad853ed8762bf0011a2b80a107f298ec302a49539a6242a715f8000d859107625c913f3f6a3b9ab878209dc68d15673aac74ee4076d7d39a8d7e2756b503e98385a6eae62ff997348116b8a04c40d90830ea40124257b8560afbd94b7420f5c866b8d22b1aefce816cb92cc536353b0d170c3958e5fec95407999f0d8c037fa74ec7b88bcffd075b0b0c8db2be9172d4483890e35c8dfa87ecd8f1f0f0f0f0308f018c37a0b14a7de7bc91e38b13c07083d13a07d38d158d96d684d1866a837d26e1279e290d1b5e78787031810b64e00213d8c2638b056c718820e0e1e1b1c50526b0459202061bf6dc1dc4662db595af4430d6401cf3203a65d6a4c30d38362c60a3011e1e1e1e387e00430d30d26030d040f86cb9961f9ab2c6e4071b0d2f72d8a80181bb07c03803a92aee76650c9a35a96605c30c04d37731d7fe4c94ac0ca4ee4c29995fe938554a08c020032906d34ba1eda49dcc8f8160253e6cac6f99d2be187839d32b2d3d858178792bb98fd096972960209dc69cff6af74179fd05725cce6134d3c51ceef502294e687f8d41e73c25ed02498610cbbd5c4de37281a44c2fe50aa55483985b206dea11bf3aa22a8f500bc4afb1ac8cea29e7dc65816839853e2fcd234c5430b0c0b767aba998dee4690430ae40ae0d55252fed2ebfc70a04e9e1c1937ced956daa40d0acec522353812c1a55f3e7db0a9d845320fbe79316e3f669b60d8614c8bedf16be54eaa499741e0818512079c9cbdb6954bc54fab6030c289074d5c60b21f46b59f7c1865607184f20772cf51bfc2c69cd491f6c5c811b0995f4801a6fc38b2e10046c5880038006184e20eda7f79196f31a4cf5028c261037e53e5932758a2a331b60308198e1ed6fe1c52fdeb434c0580239e9fc9bf0f41c64a772d8482a500249666d8cd9b2dd3c0406184920ada7643d979f9f5405470e1be5120c2490bc744ab7940302184720fb8ea88976d84f49f8c1f6050d1c35dcc3a34d15308c403cfbf0a21e2aa59c8d84c33607308a404eb12bc5c84c7a8467c38b35082412d8b061010f8f52c02002a9d25bfed1ef15a5bad10818432087d81f199332cbe8e5071b0e1ac90425c7ddf101861088de39bb3a9ba4fac8c700230804ebdce1f35be6c5df3fd8780b2e1c80060308a4f694e2596dd099d6f401183f20488b7d2ded7e71742fbea091a3d0b02b307c40d0515b2d777a40ba4cfde99e0760f080d41eecda47b6cc92f9450d13e0d801413587ce12fd9846fa1f6c3970dca8f1657c1c80a1038257c56c3532a673751860e480e8196e4a9dde890518382069911bcbf3b36b33df828b096c8157807103720aa63f7da8cd160bd822b9a086061209ca053c3cea60d880d83677298b0edad2686b40d412cf4cb17c35a3d1821b39caf044008306c4df14367d480d1b2b7675d06216e44bddead9636facce6541ecb422b25f63aab33216c4f8eb49f3a560413e8b39996bb28e22bb57103b894dd76afba182e60af2ff5c0eaf39a5dc0af25aa97b9b0c9ab5295610b35d098da167d292b80ac27f6de83133935d15a4fc2e426598af24bc4b05b173d0bab2c946b454a820a7e865ddea4d67ca3e05c1dd934e265b74bfc4a6208992a175a5fa5290d30665ba62d57b525290334c5787df585b268ee24c77919e7e99c21605512bfee52f8856b8f10254d0221464cdc1edd6d30505b9c45b4cb99edcdabd0f361a092ddd48010e1be43e410e4a965fe8bbfefc244f90628bdfaca86421e3a613245de29da176369ebe30d380da6a400b4e90925ccca1c4ad1f6c769b209f4cf2bf63fd6bd6d4b605170ef0f048c8c3430b4d90bb65b3cc4666c5851f6c677799205652d22c4ff34d08fb4c0b4c10afc472f8d1d4b5d99c697109d27f6ad5558c2d41ca7e9f358adc18bb7d0b2e1c80baa891821c2c4834a0a04525082a9892ad6729c4c7e11af0f0b89170787850829ca6af4a9a7689bd7b1264fd5426e77a7b839a922059701f7db0ada945829863509e76b3a6b95e214152f1fdf45988e9feec830d211b351a50ceb47884e541466378322f51cba08523cebe789bedfc939b6868d108620ed2b3b2ed09ad0bd28211a42bd75dddcf182bda071b0e1b5ed4e8a28b42c3af8b2e12e0e181c3045e78f161a6c5228e224f25119e2a43fbc18623d5c851e36e0b2e1ca0e58610b45004f1ac3306dd981bd4b88920a577ca7b2ae7d0cc4944903b88efee12224d9547042d0e41962de566eaa3e2f46f089285cb9945594a9bb633418b4210b32eefb86fd60f32891004f529734da968de59074138afadae2d4f9f4ba7852048662a8e9c8b5b9be57fb0a120e10804d16537075d2df259733ed8be2827a0816c24265a0082a04cc6777be590e9c70f361b5e74e1c5a1f18774b3dfe69255290d16d4f833d50e68e187b366bcb3cce4445bbd466cd989dbf7838d060d13a4f40597408b3e90cfb387d69ab1ce102207b4e00329bcdf27f95bae29bc5a012df640ce16e3e757ff9cdfa21e8895eded42e8f049f525810d2fba680c689107a29e86388d9a4e55b23ed86e243c104de6890fe3a5738877b7012dee40ecffd10b1aa63c06d1071b0b921d8817a6a97167f65368723f29a0451d48192cadf34679ab243a418d93104e400b3a907f9476aaa7b93eb53fd8cc186097d045408b391093f8f3abd754955d4783167220adc56c9d742e7fcc6b110712784c1a3ba73095f3833c0e329a40560bb622d62f6df6984052fad49f694ad348c8f00a329640927183c8a06c4557d0b7c0d0a8bbe1450e0d9044e30b1c0ad862015b3c8028815823c752ae0b9f95f249d8d7c53ae4de3d8804b25667ae7ca72e28db1c81143afadc7b4e8b22c44678b652c5afcc3bd6a1414611881f5dfb74a8f5478d1241af354bd1c9d453d207a33341c61088a964791ed11cefd20b81245ae3967f8fc74f3308e4ac5db67eb23ca51908e46e8f412ec595f103eb3ddc643cd7f2acdaeeaeb25f738b105f65f880d89a2db77b2601327aa0a9755f919fba1ec8e00179b3d6ca6ffb9410ea1d90825f1a1d54c532744092bdd2cafd77f97a0e64e4809444cd8f3ed90f9f313270408cf59e82dfb585b293c60d2f68aca0dc88808c1b90a408299b44d6c2b465a6b60c906103c2d679de88d0f532fa1a90e2c758f70ded54b765d08038ea9fc9b7a3757a7f16a492ef975b61d7164eb220770cde396350baeb37b120c88759264daa39f90816249d1bc3a7a4afe292fc15049fb73d65625f9b7457103b86bd78bcbb15a44f62d59ec2c9b0a0b2825cbe6fdd55f9534a7115241393bb55d6269f570569bbc3883c9d358ba9307f12b2e2654eb597de4838b00315a4f59039a952298fec94da17850287808e53908288c60aca63c4c2fcc1346ee4f8a22ce52839bcb8b11da6209db85dcdafa1e393ea83edb81458dfa7efb414df997790829ce27d0a59e7dadf053a4641ce693c468b39a8431404b9f84e13ed9d4acc8e5090f4261d33959292d3d88605dad0010a92bb7e5e355db515ea079b990b6ad8b040a7a1e313840d9fa1bbeb9b39a778e8f004d1638cd92d162fde468dce424727089b3cc7b4ab1b95ccff600f8f2d1690c346ba512ee005171e1e8e850e4e105d84f650f7601577f9606b333333b3326f5e4dbc1600153a36619c4bf5f76d5e9a41746882b0f1edd2e9d3e515f74247260822554347cb9c1e3a30411ca5a9f6e356d07211b6a1e31298f2ccefebcb6143872508f229769a8c4d2548bddde9b9a4a5d9a8942078e806a53ac76ce5310d1d9320df49536229fa72de5612e4fe2c212d9708bf4c764482d8179466ebd1163b8d3a2041ce3ab3a7f3a86c49848fe8780459d4cac76cd7373a478be87004f12d9f26cf9fb91164cf2a7a6974ab63b83c3cd830101d8c20e68cc15396126a94c976e85804b1721a39ff14b662cc0e459054d887acbbb8b255e00334b8e8480439895a379d8309d322c3870e44905e2cec7c7ce7d48c7651d77108621c3f5df9b1d5632d482670447418829c195ac6a2eca8b7d20d2ff6101d85207697ceaf25f5a466100d1c25d5b8c18587c70aa28310c43993e9a1e3c52054350892a8468f0f959ec7cf27740882ac1e7f39961c05825c26eaae5a661e2f0610e41883b4338ba94f56c65de8f803d9739059329edcf85e7e20a78a9afe2b6d8f2e15173afa404c31dc5305694afda7f9401429eaf206695184ce7b207f0855dbcc3af14dea81e8ad172a56a59219d32940344860a3c609da74e48120173d88acb2983f6d1a1d78206a6e4e79c46fd3e91ce8b803b962cc83bc98d4944ce8b003494e7fed6b63e73005ee848e3a904e469af4a83965f0dab3d04107e29cd2316ff892ea5f7db0d1f8c051bac091a90b538e0a1d7320e8b10edf97c1d248cde540dc1c2afb6c8f598ea7c7db820b07d8b0c0062cb0c502b658c0165c4cc0c343ffd01107f2fc57061fcd24dfcf0fb60ec00d3ae0408a225ba63f880d7f1e4f42c71b0832cfdd947fdc48e87003c94a8a0c6571b2433d9f8e399e685bb5d940d48b3194988fcb0d13063ad6407a1f25b4ad3759c9d2071b0d2f10a2a0430da474d153b6a857bee17b81cc8b8e3410633eb5eecf921fe976a081b459630e9196fcaa6dc1f1f03020749c81a0bea64c938c3ed88605ce52e0680692bd9dd28da29f9b37396adc5801221b1b40247494813c4ae5317d9a99b9d5075b1726c7495f78781c5a0092d04106f29eb6c6c94a69d24e394a8e145060f9bb630ce49c5338156b4f2f2f1303317f522debe00903c1f2e69fffa81eeae46020dfe8b324735d5f20870e4206196eea94e7bd4030a5cb93d0ed4e61cc2e18c48c8ada186113a1830bc41c1934f6cf59c87cb7400a3542caae6d6a0a4253e8d002a9f4950a2aed075332c18d7282746319d09105f2e5dc59d994c7e6ef1d5820df08e9233dbe720eba073aae40ccfaf3bd5fdaf2ee63810e2ba4947ecea9b926414715083a7283b8985fd3a79c0f361cc9820e2a54c71488379b4a9fe8d0079b17364e051d522066a64ea372faa748f383ad230aa7ea98957438d1262764d7010592a65ef453cb6f3a2f1f6c3472241ce9c613486a376c879af78c9ffb601be55c171d4e20c9b65ab58d490b97f4c1a625e86802313b8e680b5afd928eef49a18309e414f745297d9a7e539a091d4b2078f0eeccbbb1e72b550229264fe24dfd9804b2dea8aba6bb9ad08104c28f2915eecc362bf4472026bdeb6d59467a58931108736ea9371ee53bee452027cd2b0f72b14a291b0a1d44206e8a1927359a18193d1f3852f04545c01dd03104f2a65469badd42de8a84401c916df75c2959e80802d17d636c73f38de1030472a70a65efa5f90149e97abf6e0a224305add0e10352906154cad72d6b490a0108902055e004353c3c3cb607e4fa1a21644c64df3f5b4274f0a070d9bceb4ed58bcdb2ed35fd23ba9929caf8c196a640e8d801c9c3b4c6dca3d334251da14307c49c0f6716af75eabe8e1c902cbaa894c92cc9a6a42db898c01642e8c001514ee64da571ea5c7694be280747db8d84030fd07103925f8b384bda6e83daca06e48a9a6f3a2b8c9650b9133a6a40fcd0ec194ccef6ead3410372894c733ebf17ac2eb320bfc5681af941d34dc34e1624933d7e31dc86eb9c8f05d164935036e379437a82e3010a78c43093692f2147d7e783cd86176d5b70e1802d6c58c05e41d0e16399e7f1ceda2357903f6999ccbc79e4bedf0a72b017add9478a1ee9c5600531c7b8fdb2ee2a88f562d9b4e6699e5eaa20d9b96dba0b313ba7549046db7fd97bccf95aad100315a4d37ce9ae4d7c2b9aa3068e1a2af02db870408e1a386a7080060d0f0f0f0f0f0f1a768918a720fb5dc8e81a79f62fae29c8ae4947a9a5521d84eb4d88510ab2283f0b66619933341f6c7635be284e88410a82080db7be202e33fe079b5a3a2ce08205393c3c3c702ca0051e1e5fe346e2000570e0483710da8087c78787c7a9b11ce8e22444e30b1c1c6308314641186d0db929753ed8727481878386094efa386b2c201e1e0070420c5110a39a3aa9f16df4c6145a28487539bda53539359e040569f5538c31da9b42e67c82349ec5b266e9b8a1d11b420c4f903ca3d58bde775bbc9d28556ca6ea330ba5f960bb5183861983200627885eb9efe1357f2f0e1ce58b16181a773636e0e15185189b208b0e55974578a9a0623ed805313441ca41a8a670ee9b5a7334722412b0e5480ec804597ee584107175594e3725c4c00429b508e5a752884f4a6c4288710952d220da4733a78bf179228625d498d4fe7a264b5abe2062548218264a955f705dcf3121c6f936875d52a9edb122c6240872ec533e88f320da1892205efad07ab639890d73e620462488497fbac84d591946048962bc4d5a175db4e0468e479066f6f38cd418257bfa60bb9172241c5a84188e484795a5ee85334da746581de77a4a638bcad9210623889b938a295ef21741d07bfa42293bb377f783ad0c08311441eaa4ff7d367cef27935e05622482f49f398b109f722da640804672c00c622082a0827aebbf5957687c8f10e310a4b0d5281ea48884188620d5a7efb021b3c00b057878a0c4022f8c358e188520864a31a577c768e579787c200621eae0fe71d344bb893fd806e1a62f7952fb32d3e883ad8b2e0a0d0f1a08150f8f1ae506175e180052c410c49b72f2d3544c27f0e2c67b78d88d1c5f20003e112310a60b9ee293b8e8a50c2058efd8325eabd6297f30ab5f4db98a56cde90774ef2ec9ccc9e2553762f4e1b837ab793766e5149ca073a48002361aa01788c187945b68eb20eb2984408c3d1065e74e43dc966bc77a20556d69d4fccfc1548b9187adccacb4f5dddd555d4c2f25ad660762e081dc794f5755fad7d1d9187720652ccb4a0fe75f971d881934c90ab631c5949d0a31ea40ec9ca7f1fed47cf63c1dc87ac2e2799ad31cc841541072c13305cf243910e3348cb4ba98ca63250ee4d826ac747d0a7d2d194062c0811cc4e9ec97b3c938295f8c371083e5ac59fa96e6d2c60da44ac966fd648793d1d406e286ce32fae3c774aa670339c7cdd8e11ebf3a366b205f7fe5c5a07c64dd470de4a465ba99274e03e92a3cc99c673ba85ad040cc0d2f2ae50aea418e6720f6f9a969be3aed509981202c25d3d9e632103e89a83da17b250341ef5bbcebf54e97c23190638e7d7c9791995a1703413635014f20e9fe30e549e804f265e7d3a0e33ee79e9b40d2355177ee1b261056ffe2ec72ea8e0a5a02496c10f1396b9229e9aa04826a0cd9e17e96f7c9241083f8dbd8a4546c7e0d0904bd77791dc22310a367f4ccf065aac2c708245da5f7f2dfdfefc12210848dca75a777a2512602292ca5b1cae02663ce87402e15ab998296dbbc8c104ea22b567a91a920902c27111f4fcbaa7310104825b32a5f1d96469a3f20e864495f8ab41d8bb10fc8c9aa636dd7a707849d11daa259c98a9ef2807051e67a4bbf1d10c532adc7ebd31ef7d201e16344545aeed89fcc1c1063e7abb3d30a07c4b4a2e9b7fadd805c5d513fd5cee8c506e41715938c124f49752d400d88964307211ebfdd172b000d48b2db5a474be7c9066741dc76dfdcfdd956499605d17c3d6d8eb4634190a7aedc6773d031b0205fac7b0b9dfb15243d313df3ebb362af2b88ae99cb35c785d3226c05e9b47bcb8c320fbb8d15a4bd74773945efb8a75a05c184d624448e5441daa4dab28b8ca582586947fe7be75041ce2f2372dc83ea5b780a62c8dd27fda34d6675a620a8a5e4e26d27f3c75c2948694fdf33a85299e1918278a674d38cca25e4894641fedc96a3f3c7478db228c8f956547835992bc984829cf9f12a6765a8a06150907b4c84de461fd39ffc0469f67bd63e43eee6d613240d9d4f9e39f7f5bc4e9046d3e7125dd90b257382d8663a17c5a2555e6b1364cfb3a7216f7984b69a2055ea98fda464837b9c09b27b0cda4f4434940c63826cf1a25f290b96298d2e4192f3b1fa8fffd5b1b604496b86509a6d3ce6182b41be4c3f71d3942d880a25084a6f52f154aa6a4e6d1224b1b9a2566de5b4eb4a82e079f66d1f3fc6fa2a1284d3e19ebe458e7d4e43826071f2bac55ebd83fa0872f6d2a995b3e90872d0289abef6d1edb34690d2928e5b671fc446c70882f854ff324d194dd522489f42fb62bdf55f5a11a4a4e19e44907793c80c73b55317238278157a693e646bce7d08827bcf95754cb18f9d2108e2bc32c5ac1d164f2f04a9463ec5735995d06e0621889d52bddc29ef09330661a95dd1e49d37cc100439c94b2998d8b7fe240a04316ab7b86d8c17d33f409094b835cf24f74ce7fd0762fc4d9b333387656e3fd866f881b4162b8ce8d2bafe4d1f08aac34e8cc991192fc707f255aad14c0d9ae22b6298b10782ba183a55ced3962caf0792ab766fc6eef50b33f240fa4b9129fc35eddb08071f61061e08f2ef3c97f83d2532e90e444ff993451bbb0833ec40be91a562315d0cb31ecea883ad57e6699dfba15e95d96d173c2f37830e841319baa7393407f2ebc688f2a4fb62d37220e73a7d717f1697712385197120c6a8e4fabea7368d100e249916b3e74a9defad7f03e18416a9596273a8164161861bc8561ab4c5a927a53c6dc18d1cd7821b5c2861461bc8313daae9b2cff639ac87196c20f7afb7ae5f8ca584d21ac8d9a407331d213590f2ce45edf4e8531d4bbcc38c34902cbdb43d87c9dcd65890908db336cc40033928d5102264ce1988b94ce5300b5d5e2a6306e2877eca8aa62e03f16435fa7e9ce724ef29cc2003416677a6f81a35b8d563207b1067767a945cf4c5403ccf41de52f30819e53090640cd9f4a36f31bc0a06a205215b2a9478f1b87c81ac56956df72aa678e30c2f9084e7d42626844e7a341c33ba40ca954dc9a5abffd039db811cc901c705b26ca869be5fb8090633b64036a16354ddef52ca7dc10c2d90f3c81c7567a376d65fd45841b66046164866a7a24853f7a515241bc9300933b040d2a5b46e78d07fd3b88d30e30a84cd516ebd5e3d2adb1261861588af69af3f7bbcae9ca902296455d26ff7769e440552fdbfc957c93fd8520e1b85047a43983105928bfe678b9dbf27b9144862a1a63cdfe6ab960784195120e6ab2454fca01a9f9e84030ae439954cf76d4adafcadf3c18c27106b4475d41c369e3cefc10c279084b6d9b8b9ad3e2b7db0358158ba45db4e87178b231388da213f4356b619d32f8168d97327d1b51ee6f2cd83194a209f255d239f57ea6cfb604b0271dfe7abcdea664eac83194820dddf6a540d2aee453e024129ed71292be5f2661f6c8983194620fce694d20525e32802296ca55516a16bf74b1f6ca774f1051188f71f742b94a9951bd5e822e5a8f1310472ed6c76566f4cbf19fa0d6608c10e9aaf5e83a70481247bb5e3958c4dc2738040140b225394523ffa93ce06337e400a69ea17b293bf89bc6830c307c43215bf64fe5ce1e5adc08b057878acc08b1b375030a307a49863f4d89917d353cc03e26cc7130f7616d3ad193b20c5a8a9951a6ebba1c315f8008d1a337440744f714ceb8572408eb6a174ae70620bca8d1b4567e080bca5538867cf419a657c6f601e336ca0c72a21dac2667567d48094accd2c9f9869e8940560a31566d0809b9376393fd316a02db898c0160a60004aa77ca1811624091c42c62c487d3a9ced05d33b9dfd602b830c599c9f7ba54b7b5aa83e580e326241b0ce973588ca1a6d5719b020d5c6d239c9682390f10a529b6812b9127e316f64b8822c17cc92c9cb1b2bf6090119ad207d8eddaa397dd2edb382b0234466b7d31d2ac6ab208eea0c4a8573d90e3219aa2096f86f8ad60d2f9e061a8ee301b697910a7212261ee3c71f99628292a35141f2e44169888ca719b43c850c5310e52e28b7b079625a2f0b828c5290624a1ffeb5ea74d3f7c1e9e0f0f0c0812387c7021660a346f9588087878d1ae5e3a40ea00e324841bc68a7d5c457b457c81805418c8a07db542a49f93ed85c862848a6e599eba735e496110ab2d5dd8e1edbb477c2842341414ecd41c97b1761b9b9911c01323e419259d466f492cffea30fa6b187647882d826d22f633548cdf00564748254f9b26f7251da838e1e199c20a7b1e8d5c9520a36322b6313c57aede52848282017f0f0f8e204876568825cd2b252740da73b0f818c4c104b68879136d6b5fe7eb0e1482748010e93021ce90119982076d2bb172b4ae98c460b6ee4380ec8b804e9f733a8fe0e9ae51b28a0e1450e4b90828ae13db52db4e8c6858c4a90927e8c92d3234c058f0c4a102dee3daf6eed9edfac6743c624886332b21faffc607319c890c41e54c6154f31a9d2638b056c9163015b2c8046d9c9880451e3456932d1d2d3386695900109d2a7742fb1ae24b2471f9c825700f304643c82686de177dd34737dfcc14623478d13d4e8426f2d070b12ae238879aae7c36650eef626198d202733b1a4e455cd7e8911047d633fb6de1574b62db87080e690b108927bbed14d1d5f54db3213c8500441e68c9963b8b212adf9606bc1d7405e9c60d32023112425744afba93588604c74c9dbdc2e2ae28d1c36081e0c641c82d47fb621d74ac3e76fa48f15d802506a8195391b00de20c3108415f191317fcec8ba1f6c4e0119852009cdf114842a3fd858023208418ca3da4d59990a1f4a1a374aeae2a003640ce2cd32ef9777435b224310a42c7a773507e129fb409047680a52cc72ca68794090ed94ec1669ca3f08a13f10e6cf4db3847a91173f103c9ec7cab3cf24c3a70fa42cb9900dfdf381686ae46cf986388fcd1e08de9f5b73b3a52493520f24eba4b97fdbd40555c9035183858ce587f090a8c5d77720c679fc66bc1b3927db8160392ce795af1da1761d081663c5e6202ab2a9d2817cfe9e9b84a5660cea1c081b42f588a7cd3e7fe540be11a35183d05bed691c0856faf1ebf72d360e07c26e5e0d9d3d1533cc37102bb5d976f43f31dd0dc4f8b8b9a4b9a5dfc6369037cca8587ec9b715d940f63bd7902df2a2487d0da4706954162193984b9e1a481964eb5bf447828c349034738852df4103d9f4650ca3a79d8194cdf46628135e4aa66498811ce64d53502ea654251942461948eb965fd5f794cc5c92820c3210cb5f47be52ecc7e8d9582ec81803e14c9d968e52b18479675890210662dcdab71773d9dc51184851db3fcb92a5d2a503840c3010455ed8ce0e234cf3f7c1c6e704c703c98b131c0f4042c6174896629525acd3dcb81f6c7627381e48a40c55701890e1059210723a27fd1d65b4bb0fc8e802f1840c4286d2bb4ceff1f048eb820c2e90ce4aa59c2c76fee4e21e646c81acad49c7aca64d43787a90a105525fce27cf52f34bce4b9091057e5330f95229a73ed8ee7420030ba852b962e67e16f30a249daabe82997be5cb55418615645481b4b5a12dc75cf69f465f208a820c2adceac9d34e37bea0e10552ab9b818c291057cb42c51699e4de23410d1c5220688ef7b15316a39c9876901105f2e58c71935fdcab07eb20030aa47497223c5f89ad31e720e309e4244b783e0ff7190e7302098ceda8746f6503591c100602a14030140404ccf79e01a3130000001018140663d1603852d5f501140004513628562e281c241c16181809c4024130140a854161302010088541a140302c10334231503b00e90b0718da21948d6c14edbb0527e450b7a1a402d5132a7944c55161776956ea10284c500e50bc42ed3ed43d94caae3b8232012a0714e340f52178e05503400548281d8a00024939285a50a26628e08f6d9af81ba563d7ac1eef19d42f15dc27d39f70416d56507dcf92b1bb8050181a54f7d41b4c4b42b9019512547e5040cc05eacb511c08cdc102961b43745f87a06a066123f9fc05750c542ea856a1baa12c40916ed4ae94f8504440e9826a09aa03ca3a1409507ba0c484220aa51faa75a82e50d6a04840ed8512038a62a268bb41c11004503750ab42e96114b19aa2ed0cee91c5ea310c4a1ed4225078a1b40005c44b9df83eb99380236c9a5ce57b3170478e66d611201bbb90bc11870715790f1643c35cc20727c442128b29ef4f5856aed4871a53e6d3904535b1fdd069ee2c8373df6d6f5b950276a9702fc6f37879d1f47be17b6e05b7be7a78d996eeaac5578325c5ed7ba352d976827b82c477c1f8d60c52b994abf2f0f964549d58011e4056884aa7ad3cc7085d96d8fd322e912466300043344b2f082182b863ba655bb844791c470d03434016bc5bafd361a0b01f1fcd1f298586bc31ab7b02099e8779e14b4d0d1a2e242fef1534400c0752dc5200f83db59a90af9e4fe3c5d5059068d6a329f82e8378b02c465e54ae3c1d42b1b430e2591698e75678fd5609537042ac037ed8631a997fd826c02431e1c9b40aaf9b33c715e7468701a5e3e21c00fc9dd3df55a933d995af5699d23868af7a24533acc6ebfc59a8119589175b24ea03a9bf62d02b460cd3d1d0c7284b955db9dfad42c28f667ee60166d76dbc9721a41d034ed769afc338310703d6d80d21dbb33ce7a656058b26338681c9af2802e49012d3cb8999eb3656c8dd85ea909e996caa4c0947fc8c374919d7a8db4fb0dee12e2eb2ac686baa32d4aaa59e529d1d7b89b9582890e0668cb7f6b78f96ad574844019887573ec7812aa565cbbf6bba41c82c7a48b411e57df223ae071f6910c1c5bd6fe59f1d61176b2371d233b7a95910da6cc731b9b67dc12108c6f0ac55f3af3fe8becac216f69bb0a18f7a92ccee5193a9849e095cb0661d98069f08f25065e523ba69891d89b0b8c1fe38312c322261e21541a3ac2bcf70efbf887e10546c35a4964e489c04504bbc81180de3948c92d4bddf93f72ce6acef74807c4a351b13ffe344bdc1a8f326c255af0915c473288862891fe1beb79195fc8000a9a27ad89866b2b2001d12f9c05a922307b73140b0e71ba9413b47ca05e011109c1fc3e0216178abce671964d08850c0588a54256474328e63a1e703592e19b59fbba6d910f08a8a347a52995c866212b3491c848cd5f79667f10ebd3aa8bb45cd3587b8df022d0dbf152112080d2fc8887e6d0b1fcb6e5f62a91ba6c707b06d8f73a928e4f4f47e6f5505a449ac49254440d92c47573a455eeee5d4f453eed2ca95d8c3e21e7179cbff05cbeea825797741ffd546fd4396ebb26fb4eefd3ad6711cefdde6bd9ec49d9653360c89b29cd6f952c68363da5ee94542a9e6bcbbbd8f28cd2d5eb81aa0635b4c2553c1ef2b6c4c2f489d9dbaf9f45336ec5bd9ec513325c5aab682c994122db71c313c715c006733e59bc45ea3549dfbebb956a3380dc3cdbb5e0f01601a0d52da4b743033201fddded1405e23449f5ba22ef63952077a3b196d4bf6876fc07b8636028ac41493b28f0718dc5279508e1837590c034316ba91acdd87c0f2ec97f4ac348948079ab70249b7198bcdb72b1b7e5b38189a3cf41cd9ce44ed0c73d5e0d250b98a5dab44348189c4d0fdc7816ff673af4a7c72cb3d675073a6c54826690f87a0f429333e0299e0c2eb0e4989a4ade18fa80b1a4fc4787c5dfc938d0759f318a473a6ed78c4c58333442a65bc765b7ff4706c3be28a4c98b546a152860413ba1a034ba9028385834475bdbf4410b8126875b7fc15ca3074c3456e936da2104d69a6f16f0314b30710049a1153ecd4df8883943e2830e2b98ac7b6d5791c36cfd4b5eba2d07f2720d8a0e1e936ecdb8fadf0e5f04ab72e9e28eb27f67c47e2a4828f460565172008004a91cae5bfbb64092207413557842a2f0a3f2f755be23b93d397839a636d97ef0da0c5e0ab37dc47d590d01e0e95a66935fc77ab010338e9d615c84556eacae788b7976dee841d9bd8bf667b723b57a83775c6e45108f720e35a2d8f3107b4e144310b075c7f2f4035cfdedd7715b33bec6d371856e7023ac5554f47e581e4f69a86f4130eedb40cfea0c76ee2985eef67a99a53b685c21ef692fd68988b3f371daafb536a41d70949d8f9fcb8faeda2d614aa33b3bb5595d1a129b1dcb907b244a6389a7e071581f08b9d35b5805d842ef58faccecff909f034ce3c3440846c143798efe23f0e9a55da0fc452c24ce6621135e875da2561fed15093a34d47d846fc78e26d0aea84995d5d97553553aca6bc334f6d38e913bde7b93380aa44446c5373a498a70374272d8adc1f79c09b0af8c068a63970371ae0ae64be7fd20738843ca775a139b427db5bf2874257f29765d290f8ed16e6a495c07db94f4405c47894f893904106b55827500690b9bd01b1bad88f40dc303f843d0d85f30e51f8238c9b0720865771eeb1fbfc477f77f8c700fd6f10ab92ac81c4dd554b5338d4f8dd2c14db053e8b63401e8b207d13d69923fcac532e25bf52e0236427e0eacbbfdb4aed7e513ea8aba8f0826f0501605bc0a8453880e14bd277dacbc71845024a23805fe9979d9b0a6866288865fac158a439607806f0e1a55b9f07123c125c420213097e426e751c72c6aeeb1b9bd0ed01dc0b2f9ac3a67e14cccd4182cf3a6707008d1303f88a10a7a55a59930666444ceb2418cf659c20f570a03a076faa7c7db19ddb97e8e91e0540613729b697d71605c917b917ff4c158661392f5790e24ef9c826ace7076142cb78b574b5a8513d7fe4560dd4d87e0eb7535d9801be02b85818451a74fd277d0d415ff6c2f6b800d09133c0e8993d2ec288e4228fc07f043e43b2796624d3b5ccdf85abf941f4a4e30789d41e533d025a2f7aaf88fd7024e941fb9bad84f58e5c1de5d48aae10c24910a908b025bb2ef12f3eb91722f64653e61d21782a00f83dcc01c7b45fda4b943c951110be8fc5efa147d0e0471533d6d04848019ada2ab20b6a5b9102550bccbabeb7129777830784d62a057e93d8940579ad2818efe51500d132890ef102ef3639c3298d6eaddab3a0444fa9637e244a8bd05c857e7169f50ecdfac8ff5daf1cf684bf5dff07793b3b879232be8e9a04dd9d9c853e913721991ee22e2406687f22bddc56bc30889b457e8d13b9d334b0e66e3c046f3376d509369c451f720eb99d41ee5247cd6cd87c32cb671b3c9a9670f99c6c5791fb3f97b5ded33e3a0cc5e6db4a7084472f354925b6ea3a4ccc27db4cdca8fee1b522c653bfb7a1722a051199464434fdd0f92bc4771ec62847ee65e190a24aeb0938e96b02fc3087148ec27b8393c8f6bc5eb76d31e7f9ec4d81c2f867231f0042e63c71e66f73ab8ef48d86c1740ef8b8285cae27bc4fc5cd99370ca2d89b34d1da4ccecd72e2ae5c46c7082c39398a2deadd84c4379815085eae6794d7d95bc14b85b41b08fa06bc7ca4c94d36c8f5dd1f82a1e55e942d585cd85c93f19d57a9af271608a82c00c01be45f3ab1a662c28c18dd7b3d3d5de4c40a1fc911ab85a135c4b48410a2bd2f5f7bf9df9dff6c921766d01854a938141d9c670d0b369a8126968f98015af2ea64e0394f74f6e41d0da6df604405bf13631a4804d4e9e074b891f4af2f3618a78ed2bed63f8d43d2918de733e44781b5198168a9a41182a744c196332e11b6fce68a8d9567129f3a85d412b08b2e664069c86839eb0dbaceb2ec0abbec4b69ca8c08c96b2c9b32f31481a8a8c4d8804ccc2f998e92e9a9d1955bae2dc8d410b1682c16127992745fb7c08b1f2be9be29e599b65a469a3023c3162839786a26bf3b367c3b37eacc29f3d3cca3d043b460b8892fd95ad95eb42e2f24fc5b9ceafe8918b47460112a7f4fba2dd4318b024b0df55554977c906181fc61ac03458b61dc22853dc12d6feddfc858390928032a2c1506ed5ea31c31619768498f6f649fb21f9da9acbd9e1baaba8b91adf7c076c76da65841b4502e32a8a529960fb32fd3ed2b3f6af24c9472d6bb21c4a5a5a27ab745779f9dc1ff353b21a40fb2c15ed83bbf9e49fbcb1cd8c4bf7be7438ef54e7d3669d42102ab2a47aa7aaad5df53dab1d39f25b4d8f5adf89d578c37fb887e548c17d0174fe7aba2f24128137865569706ffc80d0410466423d8346a894bc8d55470d42f73c9c080dc9b554e9874c172479c35f5521f7a9fbc2e0121d6ecf937aae7af4bd03c51bf2e91d275036c4c9603b5d0a9b2bac7295c854bc2a550ea2aeedb4fd4d14a974da0938ebfc7e6db63f9ce4b7dd19d33f350224a145e890c56f697f73fab10ed5db8e0d2cb0c955bb01f009d1bcaa557c32c9505d9394321f5340f2eb6c3c2ba0831a02458472b3c8d801438d39ff60f945341fe5ade48f0ad28600e0eb33abb7dcf6f337050ea1e4e600ee1ae0b7f0c820ea0c10dd2dc048938bc423d69adb0e5c0f1327a19f08042d14d5853b1b61c5077d1628cc7efcd45b3b63cbed6674ed4a46e5378d6ce1c0072752459f67b238252d5f550359275d2322de106be9359c1d2948add345e5d91cececa7d0bd8f1cb3a7e2baf67c7c6454bcba57b0f1e7ea52c0617f5044ea120b13227420ea8438f0e34721c462cb11c4ab19ea728030c14626d1d0de9e5a7f6dec2109d94684ead8cac61fa88096563513484201764cdba1ca1c56328995efae7c9b4140ec82ecadabf114d032b627e968e6af50a410524d71878f4bc66e693bd9bddcbe708992243746494e008a614793165794c9fffae63bcb513b24a192d9b9a8020ea216c09700662299ab6bf016bac777afd7e7adb87e34df70519122f9d36829af4706288ba0945320310722a58c9925ba9a9f47cc9dcf228f0cae849898ac8b163a5a402443b4a2f6cb7dab1955524988c2ee410bb9de423f4556bfad90acb92f5e3639a3bc8bbf183082e3caf1aabf8031ca08b92e6448779eb378b108765c51ee57173ace5394755058b560a99f59aedd8cb8fb825930a55bac56a4ae502364fae0e10466270be9ccf2dc594ca535787a58be1518c062d1582e2dc70e433a5a7f204e82c48322913083f6fa2bbd9280e2700479d769a6970b8bf334c218fde8fd49283aa63a173f6c4e6d4ec73d8e695a061c5428be834d0ccd7b0c4d53efe4fcceeccf8714ea628deb53a4167b2c4e8f82d9e2a7e8f1737b7211b3ebcdf47f32647579eb68687c4bd42e849a8627a273f7fd29a323435af2539ba9d3174401c321ddb25b6e604782cbe1c4ddd1d0718f6864625a4923f31df2ca62e329192236c5cdee3e58c47375ca4876e28bf8a024f95d4a00e212cb45e0663c4ca76e63e013e281948157f811a26e960cd8301d362036a61122660f9f165127cacbbf1830ef17e533fc3b0fdb2a2633484981444a35f9a9906e8f4a7ecf428e4415d4df370e55c756bb23f250f16463d6a9bbe62510fbb37d6a76200613eff418546398c003371d28caed9e1d55799537bafcff481d45d62e633ee730bf5f82512d23b0d2e91eb3a14c112f618cd78c177085c31d99dc56d5e77e311f70b337f623c32975f470bc8f2debbea6306e9159e508179c822a2a09bd59e48745f99558cce348c67a10c48ba900685280b3b7eeee8f0ac98a7256f92c618b9908f17eb23c2fa79c7bd5cc11980f078211db81cc421218fecd59f8dbe5d0657c52ba6315dc681e1bb2d818ae7e83e1c36515ee98944886f082b9c8aade48ef75db04a6b35d33a42003105fd856f19816336807f4a014a0fde471513e3c0379e3a6a8926154033e5655b5f5a72480d1bc0739f16f4c6bb3fb7f5461a1e38e7fa39804e07c22ed0711ab2439718ff5aa989abeae3e3dc2c92cce8e84053866332f238c81f24ffca788a438345a86ee7f39241d63e3dc6b57c391ecc719849b46dc1b8dd0ea76bc4b5e4b653489d6d512645ae027eb0f21be58cc617ad0c0ca444ee0141fee5a76842b337b9681a814646ab4c578be17333e93e6bec3ab92310af1538a38ddb61754cabeb21860d1c02889a535b3882f420001c228c62d0c0f0448e04f27e9799fa2c062b339c667fc91298733c33fc96171c142a52c463b52019be60d03023540d615b638d471d462bf67dad6cd98ff270656ba31716054d495c26a68d424017b9c25aadff14a4c2fbc24650c3510b948ca04e71cd638231908b075f79525c98f6a904b0645af4d1aab58f4e6dd409fb237df1a699ddbb608c1fffcc6a923f46cc07a45d15be4cc7de8bae70c5c3cba6147de727db1a58794c1eb13eb664cee0dd0666e10c7444a66787c8424845fa20279a2a1284758c30d092c1e7011213b39e01748c89ed1225ccd4a1916f002f1f13217516e020e5fac9a5f4a27d7f92834a1569f68fa7c3989e2cf871d2e436af24d750dd7023bb951183172f26f93642a7875dba5d015a8b0c0c163eec1eb8f85ae06e6c5326785ee7ab0fdd295bc203c1824529f5e5ad5baba8563b464730c61f03c2f562106601e0adb7ebee020b4e300cec0d7ab624647e5cdb9eed45559124c3a4aa17f4d036bdd07cd66c0ecb0085101f7127fbb70170b082da5684de81b53a334c58dd1b841b2cf42f4c118a7b6b856e7292312363b9e3f46c7423cf2a8c7d8d8afa0f468495046b73819ef6918fb9c9409026f1602b4af06c3bdf0e54b31cda94ba68acd0aa4dbcf3a4faf422adcc883532244339a06c5176043098cf23bca61a615d144f66cf4ba9653293b8350bcfb45e7b4becb8284add89e5e7679cb11fa199fa5c186342bab4979985d2bd00b7e15826aeb8256cfb5d835c770379a42aaf35c1233711c103cf44cac21028d0db7595e9e58c560e3f94f4e3932f9db3bcfd86b3643ff486aa68aa8a0aaf9e207a94dd359c920c3d7b106bbbc7f75e2b365283550b065f04d6b111bef6ff9d0688cd08aeda409853f2a1e63c334836c9927b1fe9c66127a33e7f549cf5eedf9f067b20643a43d69c2f0b08333c27097bf926e8776dc7acea9c15134096393053404d34f2f6873855b454eff7c6ecba979270d846d2beee2740baa7b3f3da1d9b9aaa9e98ac06f7b152feaead7e23cdd803c140736bb24eaeeba6041036fa4081ccdbe36037bf6d6556aafa2b37a21c2e48469f9158a89ae4a84c3ea264a5880a6fa09b5bff8dab575365cffeeb3d9415dfafd64731f7372dea6c197063a58030b6066ccc552b2abba47ded72ee5441ee9f521fae70631dea14709630a3dc8d07ee91cd20c3c5171a810accb5bf1885de015d48453ed1e1406ad51fe566831deac512a217669ce3ecf38b941da0a77a194a82ed1d7563baec3fb06bd096726446dd478f0a33724b2e1f79c643ec4a674b77e9b793ab7916b8a18d95ae876d5ec98434fd299d8ebbd9046d3a380b1f43c4d60b510238faee9624e4d970ae615c597c0e6922d617bf3757f0a91a969498ba41d3d8d5de1275ff645cc030cdf62115df7a5e6dc15bb938d1820792dd02a450d1eca93100346906c0d5ecdd6562c87b7b1026965abd86971f8b39d155676a17d1dcd7f1b7350072d1cef5b1fbb97200d3d90ebd85870bee6972703aae7a8ab154a076516e7c90a0ced30eccf6bff180861ff688a69d2d3bb040dedc22d7f24f969411a7dabd5600cb0d8bcabbf05f051960b1a90537c18a9086e1ad5f9288c209175d0b59166a583b158522b9808cbb2930954bfde185d50e7b07b5e7551f467ad3f06fdf043c7fb8da0300fc020f2b885ffda05139f716f76500498a3c3b02cf0a6a3fd4ac2b3e468f9ce2417c717c46aa9eb059f39ef44b09322d9db11f59ec0606d868a555d9684d8cb471ba61515c149532b31944775364b973da7e9abf0859aa41d814bbec8005d353a17d058a9d29c3e92cafd0d1531e06a9fd07069b4025427bb423b75b32d2d892239914a7cddadcf56a49be11f3805d87f16448f713532b2d4d09f72f432b604e078595dfd452a93365309ed55c157eab2f157343e56d5f15a2f657016fa3d1d72f96ed4ac6e2932abab78e0baef1c83fd49ae5c5b6d778f4da23f9e573764ef1cded9d612bbc90c31c9d0852a25591fa27ac7949123348a4357fec37f65709883fc640a6754f84f8d030b42ebf4fd11c1c5a0e3064ebc5fffe7fc7b611e305cd601b5c9336e531b52ec04940b338c1fbc212b8c736fe0c2089e96b9f3a543a74e41b66edb8c4a6ae05a95021c4682903ac42246038b621998d5a0894cc3d839268b75479e228539c7b6c5b2b8d98fd68306a747f37a8bc56e494f2e18c97ec2606308503952030d064dd76d31e7008b341389882c1de4ab988e5f7cd915c9663b2447408e5d8a4563ab12498e85177009a09b02813c7e7429fe1c4f001d1418a09c3e023a9f85fede18806d9f189a0a37f89579225dc33f8531046ec6f78f9f7827558b74d26421046a23e3a43815fcb701726d2e1ce1b55decf343ae93dd2db5501dd4dc597b2d8516fff02925832689b6e9d5b1bd5a6aa9b28537df82d933a01fa82431481b48c08584aca2302aa5a33badbf65095dcbba55d03032794595fa01036366317ed52d68946d93c7d602f51a124612a02d92ad3b9509d52afc24dd708a2105aca456955eec4452574bd81d49cc403d49405ab41a509df0830d03a620955528ec4a425105ab1d88ab8a6357ac6359c9920a4f4c18f20ea09611d77058135931f40811078741471426efd66f9e9bf73cf4bb8f7648ea308176f21b6027212f441f8158a00e041a846bebdf14f9d06d073d888850e0a1618a77b2bd3741e67e992d3b09e0c9612eb776df13df07e42fe47495236520c81ca30c9e7aa75c9f7bd8ea601dbc8fb54ebaf19a42fb85248b9e852e94e452e6dae5665fdc07a229788a4e7408ebf066c59c8fa3cdb02e3ddc9ec696ecae75cd5f8e6c5c224c689a6eed74c59ea7faf6282646f3882b7f63754b0e19adcde9ba074d417eb759145a673d33e1c32981b811f8fcb23a885abb3517706b67b3f4e66f09ae76d1230751f0f479d1e156d62b10e7c9f474b2c61f3b7f4ce2494a011ee14528468997155dbfa81891103456fee1666a04732e3109bdcb836a0ca2510611ed8e829bf12f0675e1729cde58f04d0342cda36b4a2bfd29c6db03d4f3c21fcb48ef2470b3a6870d8d710dcc99365deb993bd7402abddc68c86a9630aa3b592e264d45b6bbb802770fa96946d6101b201d9dd869c0fa3b440e03d6925b9efa866009447ff127e57d8282c7de6165cf19c0792129004b6ec03141be15c8b46023d05516d55d555e920e46f99fe3aaf2cca04087784f42954e6043b89370006d1e74799830d8905407c1696f267ff4dfdc7d9964e56db885c29ddd5fd456ac95813c809f9988daa45310488e8b3eeb4817c0ea4c589ec7442ff7ec1e5b09f10e7118cc7a50bcda40c960fd19283dd1b0dc618328e751961a86e9e310dee49ae0e4b12e2a2d1a93d5e92a8e999c5b3fe97e1619dc6342b3d412381f2e8dd4bfc4fbe78569d65eee350b759892ea185583a29f18f5594f653f6ba7cfad00fcd33fcaf992e399be9d3df4124ceddc9654bf4b8726b612542bb11a1d01c3ecf0fb8509438a64a7138a1196cab24b7b580acbcb1a71dea816be659ec597dbe848706183036d8181ed50008b0a67bae2135d5ef9880968c66490649874bdd83d84ef630ae1a153648b2e0d840370a15d0a2f76e018b02a0cb6e0d363634c1692abfb1273aa91306d8d4c77d96159e14dba5d526bfeb6fcec58050d85c9c253669d746c43a3256babe63fe0eaa603cd13bf4910b33ad75c1a6553b9822a2acbda745f8625929fa1907f2f73ee84c428cfcaa55c652cc7ef2143ad2f5d77bc7db51ec0ce87786288bca5a39ad06a8e460af61fe296829292ff14fd008e089d70834ac191425c43b3a34c10f19f00ccdb639541c1cd51e6b21db33b70f5bcb898e707ce2f9bbf5d0ee5acba5bea6249707dcf77075ddbc0f4b6353bbadb2600ffcea58f1f09f4cd402c4afd59ebffa0bed18dc3953772cdd689c2c4635bdb3d35f768b795960a0c98083847b9b135d1921c5d039c15458c7dd82be1e5adc2e61da6fefc311fa43c0638c83875b32898cbf41acc11f44d94b0cf6ef4b2d0e08373cab80e059e0fbcecf592780ee264be7ff33dce50e5756f104ef45e87540f8f5e1a78f9772c9660efd591feee60acaa103c39a7bba4476a3fc27363904915280082ec008cf020abfddb1effcb30501aa9b4d646899419a5819453f8952fc348a88eeb4a5a0382f72944e22ef166d09d5e8c06fc8fb9808b487cac3a84ac4555179c66d3186238ac0c20c2bb0a1537a95dc0437cae92e58df547754a3cbdd2abaf32ee9e12e41c8094b053eb2ea4a75c00b1f2de4ea24f356cb87deacbaf436a3e07d098d138965aac0dfe985aa8b3f7eca8c47b6bb36fa9e154af9e7cafb2ecbdb0a51eb653d2f2327b0d0a110a7f5d2e2ce2393852f6b28e9d77aaa2fe6f83a967aae95443b8299aedbf0264b14fac4af5e2276ed287a40788358950cfc95c8d1378bdb4239b91bb9af1f0bf200bc07af2ed4395051a44a5a20d7e8d205dc11fcd9800416f4e2a060d4b98c549e217c72bd47557a0723edd7ecb1a8b1d1c5e8c780f1ef9c49b085c6e5fd82bf4ce799639107f051a214da0d03a261fbd56f990077440db3c353a32c79778bcf64d2fd09a1e53e9d77bb7e2515a9bfa60fa0d30d84a180246cac0d1b8cac9740052b046d66214b24cbbbb1931a872e5596f0f2aa89db24769a332f651649454fc0d0e06e086796bdaeeb8f1465c2e18d42d5d202b2ceb9150331f0f9308ce51f497f3c02edf6a17fe43c5de4965ac5f0b3476e682c5365add9f714c72dc4ae92862602c322a1aaad2ac0f4dce08f67b315438a4873a39b78782956b53c831e0da4339b95e42f014930b0e78ad2114e2ad0ae45b4c1d8d6c3eb819e91bb56272a13e3d9fbdcc10a8a6a07666373dcfbf97eeafa94c0f2c667f17a5f677dba5dc7bac1297bd8234d2b178dfff60aa7c1dddcfb53180aebf1a2e2c20a7767b955a28e695a5646093f4aaa4df153cad65270ad3ec7aeebd5aa66692e06c6118b79a50180157b253cbba36d7db8cbe858473d7ac274321b9748daa7db990c88596bc87df3c2072e036d69519610de93a85b2e9f394d5c07a9bad16c99529daf3721e70464786323c141780484374096d326107622c54ddc89d3a6808aee543205b225109e28efd9675dd6e36ae99558672b9aac44afa300ffc194062c223fec844d7080feb4e7ca5e8203408f81fbebd86530c59a64c8dcdda4ff8bec827235e7f6adadd76c70c4d6c1ce55771ed874281ec6bfed27a866c380298170d73fb4a1b62c4b1ce47b5f31e998c4aa097c6dc54aa474f0f779c0be2f7dee957c27904d46160f0b9c0bfb4c34f38f63cdbe153b5db0980874777a26f740d0179081ca472506380df689cdbe8d75cbd6ae9f07f2ee94dadbc0d18fed21d4057a2479f8709438968a0e9d95faa3c1a75d47ce8337dfe7b5150ee43f5684a6850dea066181e57a9ff34798676d027aafa9eac41a7289b6b1c069a57eaaf274b0e4a8a6c870f97dbc094c1a6854b5e5bf5b6f79c00e89485f9fdd0184fe934a1f7e4385dcc104b470acfac92016b787c0cf2c6612f52c0ef9ac1b0e1259b9bca1c2e5ebef16a1954a87cd706b236381790bcac74481cb3067ab540074b027805ce718093e4b385d8e3a1a1a73d8bbee4700d914cbce5399d83400c3d0299c116741dbd5cc000860c8bcd37d4f48a3ac323e6b5d42149f13096c5ec2075895577bab85d453d2ae09aedaaf79de4f6c06b5501f3f262c5140eeb01ca84344546cfb4ecffcc3056702ff12ad995f94460942e11f40a252339dac224ec41796441c2c1dbd15911b941a3b06c663d5bc1b25ec131da7999eb6099a858f80799d014b1710ff12d1eab1bafb2c76dc82668d719ff2a95b2b97295b674acaa2aa34751a41b1e978bbce4553f7524c1dd13b9816988d5f7f7991de8c66ba2acc826a8e9a4af8b524f16c21ab6796e6caba2ac1c329565f4e41da67011dd6e79e130ea293ea51990dd55bd46d9ca8150fef2488b3fecee29526f16518f8cbaba0b6bef851455da6fbcc0a6af2551d231620e4df5b33fb60602c98792387b1b778b4bc199b09e94b3d6ccfc15910b938e6f19c001dcdd266364da6b8ebf8c282ad584dc6049baf9c7b4d185a5ec819298f6ae0ce73f5af6304acf88edaa9169d701172f38ac96119b3c2263e00ecfa44f1e2af798a77630f6a5bc5e31af8ff6319fa39c51996754eedbbd36753298fbb90662539cca7d5f92bb8b296991d48eaa8ffac1d71e909accd5d652d70767676f2651abe22f4bd59dac4db107a6e94d701764188db0dbacce61c9ccd312eb224bc1c2372c74ab85b8412c8c3687a89908b2b01e16a2d302bdbdb07b06c3c855630f07a385916fa1a01b2d64b9758d9ca8d3160247122aa51c2443687441528980ecac44eb415244443b249e10c80a255291ec13d3c973fe9d2052401f55fc4bfb2a3c458ec0ddeca43cbe9d8c1529fda1560d0b072e72a1239275317b7ae176146faefce26239e72fd08bff941c756e25c356b8a5b1387ae7203b99d93c533ce918f291774aa16c151953a2402fa470aaf2a78e7393fd9ebd49c34af2544495c84120ad89a790c3e124bb50bed6a9d3f2a7fbee790cce8f83603ba5abe8692330aa8be04889aa2981cc890ead911e82a534f905541e3537d0669a2015d5d38979226f6aa7c430c99dd2290bc91181ae92060243721421e82a71d058b0347f518ffcbbe4b473a09c3c57e4f273528e758bb8a8ba702cc3767dd03aab3d89da49541594d14f38ff64a6ddd1e3fe3939706e96d39d61079db7a4268efb0bb78ba014f9ac317d1e6a656f7b15a7db94b2fa79114e645832f5436fcb498b46a3b205fc08e462022af87fa43850b2a4d001c33f3f3f3f3f3f3f3f9f73d3906d1684b620939469a31446b2dc0b4a29a5b4654a6a0b8000087c70c012421ae91e000000c0053a0a0d0a4a0a5692bda881c2f8423d6c61b698f1dc1ea21b6a235e50a0101eb530ed5fd64a723eb1a2dd1b6a365a9023470566b003c7c9f1c58700bd0c704c80060d1a1eb43047135b53eab20ec68331526304e58840e2c01f1c3942308605b20f1eb3309ca724daa710656150516ff2480b8a854979479df5d8f20e1eb0302825cc48bbf495454d268081911a2315e057188d1678b8c2345b1d4eac57ea3a8d46a2078f56987594492ddd38f1ef345263a4460d5618942a7d4ff9a35f586e1526c1b39c9ef7fcaad8390f5518dea4b29c27b63778a4c22ccad389a7c412f478fcae0d1ea830899e97d3aa67b1924e61188fa19572125398fb54ca106d9d9defd82869f02885497850394eca16742d6a5648610e57c9e2292f1bd3ee183c46619245b5874a96ed5ea5284c3acc6762b40e1184c2d8b11e7f7b43a030f77eae3b51bf94f24b058f4f9893f4ab2d492f4f18b5440f591b3ac76dd1143c3a61cacaa5ef832c75da8438615ecb23b45ebad096ea4d18d73f89f0ee9cfc4c85828726cca94ab0cc8f2b61629509d39dae30a24b18136653bad44cd6bcbcfb92e07109a3e598854b3f3be5314b18c3c3453cf74a8a1fc24a18cd948fbe142b99291ac18312c6d91a95b3432cb5741b62f09884f1728cc927ddafbeb22f18717c41073cd0810e64e0a4e02109f38cec6ee8b06ca27507871839bcf817b800471820b8b103870868d060824724a2e00109d6a424ee7868c5e7571ee1e10853529e2774c8fb24996836c224f5a5d8d9f487f724fc82e4767830c2643a8c68bd7815bfc41d39c2b071b70873d0a25437c5bfa18625071e8a306f5d579e94939c52ab47228c7dde79f24605d596bea196032d228cb1a36b22f3c215781cc2a03a6891267d4ef3110d1e8630ba9ffc0c11954cb49c1d6324e50c1e853089d7f5270d935f2c7667060f4298644ea71339aa1b6a2b0803c7ef20cc1f97ea56dc1ee3053b7ac7078a87203c0261343ce001085387af5adbfebf9ca80d8f3f78f8a17052a5879c286bdad57af4c134ea65aa12060768d00823071f8c9fd331c4c8a14302601c1990c1630fc6f3242f6a563b87986de0a1079330259d5c9ee9b6b91e79306a997ccb0d255c9aece2c114e48b88f7512b39c32778dcc114fff36887b7556aec6107d3494f5151265907b328e1de25e496085379d0c1ac713f42897fc14b9273430d061e73306a56ece8a7116323bea12607637e7acdbe3861e4ef37d46c84816305ef67235e50008c7e1d3b3eb08112d87081471c4c699d82e9fc92ca7409181e7030877d339df469cb9d72e5f10683ff9d5467799212eb2d1e6e30cc57c9163c3caba9bd0d26e95de9b3b4e820666980071bcc593d2d5e92d4bedb084be0b10683360d79154faa9c530dc6f5b3aad9ba5ecf92172335464030d2810c70c0230d8ba7df4aa2eedfbe1bba4ef040c3bdc0e30c0625de26e938c94b0cbd194c628b580fd6965554be0c861127c7b1da50a67e440653e82489e75eac53623406f3a72b1d15b4459d50c2020f31982f334c99b5a8b40c85c114e792ec78c817131b0ce624d77ca58e6e11d22f184cca9b269ea45cb4097bc12084342bb13fcde2dd059334a6735469cb857d43eea8b17c6ec16cd248b1642a295582a805c349a32d5d2a65c15c29edafc9de61b2775830959e376d592a5f59f3b8029fc39909e332ea15f3b082c1dfb3ca272bd5584f158c96f4857e471396b753c170d71f46291fd9d0de01c2630a066dc9b4748aa717f40a8587144c9fef24534a679cd48e0638c0c0b1430c0e2466c3131e5130e7243c8eea38553fe9b2f38082f9bf4c8aa72c0908c4c8a143022f013f783cc1a817ca763b5f0f2718d34aa73dd94d4d288ee99cced275269862dc0815abea62b467a4f0588229d55e9493e4ab0483be20e656abcf464912cc3b52abfd94ce45916076cba1da7495144bcb2398c43b7d77dd69646bc508e6faf441848d5684cecb824942474430e83b25ecde760e7616053c86609c535aabb2f267955f08a64ad263b7e550108cb12bda9682a9e07e03c1a0e663d465b5f98cf407a624feae9c68dd1f46e803936bdde99482ad8939e981d9ff3df9d58b9435151e98cfa38a4e9d5d46d73b30ed7e1ae5c1729c309e870ecce5313b09ff988c5c98e52ca8a873bf94838a0b532831e13a254f7dd3b420e316a6d8a593eac83a313f6e0bc38ff7e54f9596a4b56b61cad1e2a74eefd1c23457d9ecf2094aeda566613a33a522c254ee5c235998c35f8793739e58986b5f940e965627561816266da29aaa645b62ac2ee315c678916b4f51e20a9358f31f36f2bab1a70d848c5618ed4e99ab0591dd49ca488d52f82083154653a5e4de6f11a593882c3052630479800e325661fcd25e4fa9ddd9aa3f830c5518f7f3c439498dea8c522a4c72477d5d25e9ff9f4485e9f6aaf2bda7b59f1332c83885d1c013c8308549f9ff9bd2292e853955b6f38e7c13534b37d464902279ba674a56d0f10d3519a330565b5b2ed13f89d325566488c220d5643debb96484c2306e37ae9e344f7e5506288c63a3345dbc3dd49d7020ad20e313a615e9beb5565d52be504186270c26db85964e6e172bbd04199d308a696ee8b55c495ae284d96564eda9ec24d9853761cee1d4588533718bf19020431366d1731d17de4dc99dcf84b172f8e4255c4fb2b862c2d8d152b5cff79730872841459a6026fed996308e96e9afb0fbf3701e4146254ce2730efa83288b126450c220769d4bfdd574947012e6187572cac944be691321c89084a9c4bf3655b28268bd6544c2d469a17c3d3ff5854c0624cc9526a85277ddfb94c6828c4718e72b33fcda449a9c3bc224e7646ff596f3e6d708831eb954517682b09362845144ecd2a81295b1087389962e28f9be935fb8c850c4f1f5e1bc543c11663fc1b20595538bdd21c2a424d1df73c7fdab3d1dc234eaee2c77a67912840c616a4bd3fffde3332a5c0893e86e574ac7d49fb811c258414db8d1a12b2be74198d7837e096df191274710a6ec9fd4f7f9ba5ba98130a73e6d79e6dfc4a40a08a3451595e2c2c4a80bff60560bbfac1e3ff4a8ee075390ab9d53ef66e6f27d30a8b8b0f5d92b7c3028a5269d6cdd72c1de3d183f874a9d640b1ee4757a30c876b7dac898fcf7c98349124ab4a0a3cb7830e91f5d37692c5e9e7c07735a92daf9fb53e68d7630bbdd97ca4942e47d570793fe4f92b5657c499da683e964fbf4d93b37428c73306dafc9222bcb09cd530e26b9d7935a119f79dfe260b8fcd1847bea7cd0110ea6501dfb1f84495782fa0683b6d59a9d0acb9f6c37188405d73db92a0811b30d26f5b3732b25c506a3e53873955d465d4d6b30f7892ea9b34735985bec92458f9d7fada6c178aa4dcabf1f4383d9ef44a9cddbff68266730ac28754fc287b5dd69065354535f327fff3bf232982a2dd4698b9f0ca6a4965f1ef74cb42e63308c109f67495cb89c5531184b0a577749468e4ae93018e647bd8efa0706737d50e3252d2ee30be6d2f4cf27a813d6b423c30bc6ba500f7aedde714b6474c1a85ea74acfb899b6900c2e186bb44cd679cb4a1f93b105d3a9534a4b3625794ab60c2d1894496e15e3b4475429230be6d8d21a26c2a449bacac08249eb4de6553449484bcbb882297fbd59c9a95630493ac9bb322144239eb2b31ff96ee325ae1c0d13429e54c269894e504205a9fa95934952893e8b12be4ff2f650a2b99c5afbb4d27793b8c48ae6c9d14eb824815c9243464fff697b22613c71e52cbda5bf8f224820f6044f1dd44798732ac92f3cfcf6c58f23caaa547f41a52a259bd1088350927a8bfc10aa74d5f13b7098edb000c36106234c9225d96a3d278b1f428b305dac919e93cc1176ad224ce9bf4fbafb13ff3f9a0893d0a7f49c143ee394bc000c4418944a3f6fb2440b61e943984cedf396ca314318cde3e26b6d8cac4e298449e9d85f96e8ce9e9a186610c27872794bbc68279db7a07750e030305263c43030e2450646c0306310063da2b38d89b39d59da3143106f5fe85f5c4ac21f884209d323ae69e71f0744924a4a4e3b7e49fafb0f880927a7cb59eb2dc47ed852c3f25c57b799b40fbf9850594aac382f1e1fea68e29f94d9af4c8c06a3cc60c61ef43319bbad756215a3c128d3916347b31ecc37c2bab39669f3fbbf2813a3359033f2605a0b2ac9f15a4bd45d66e0c166dcc1e862b29b7cae797dfa865af930ccb08329cfe5ec4348954b691d0c97c47e4b6cc7bf731b85197430957095458ff0e76014a53ba9fde8413f5d3998627ada0f3a85fe9f340933e26058afebf0ab16e4870b0e065382d8ecce396f3096124f5ab9b51bcc96b6e418323227294b1b4c52b9a6f5a82496c3891a61061bcc27c5e5c24b751c21a44398b10663fec553f2ca3e9dac1a0c5e276b74d0ae9e7288841969307550b2b2c6e899caa5196830e759c9cfee9dc1f049b89c4dc53643b2a9799aa8a56571a2cd2883f1b593a42441f5b4795784196430c69e97281ee43b29e131982b941efbe07ad25a540c2659d47f12a4e90b3acb30185354f65d91bf7ad903065312e47e90e2c94299a42f984a79ce980c6997b4c70ba64a3925bb601272a653b908cf15b960d2396a2d9ad4a93d680bc6d6ebd45542e7e036d282f94f7c92c4bc245df7cc82d1e4f0b2160fe2d36d58308d9dd2d94914cbe7f1154cad6226727c74929d640583aa09fb3995309ef556c11c3bbf429d4a579532154cf2fcecfeab5738154ec17449d259d5c2da6f65a560d21e174b3caf90b12e0a0625842c414bfee5111628187bcd757d4daabc579f6050e2a5e8514de84e49ca0926f1144a4d305e34e16fe2dfc4540b134c52a7a0cb949e97603a153bf32dc6a53da1124c26eebfa45082b0e4952418643c577ac5a970ca4382b1a4937b32445c7aab47307d5cad785dd90d352ecc3042dac450e579612b8249e4c931c353b54ec44430a7bd789ef2ec3fc9d5104cd797155fdaf4a71421984f68ebf3cf5e9d044f100cfef326cd53d2e9ad0582c1a39c20647be489af7e601c25c593f4455c77587d607e2dfb1af559269feb8139ade4d2b279f3c0a04d0e572697366307c6f89cf3c9d26a2a2f9ba103a3aa7fd6932f95db2c17c6d32faf1c6d8252fa820b935abe3bb1d3a90fafdca28b71a5c5ee4b6c613c25f797607e5a43c8b53027e9f9354f8a6f1a262d4c628429ad6072d4da6761d44ff1f47ea7997b2e0b5318a91a2aedb1309b9494ca509edf9470020b93ac54db2ea61ed7e52b4c71719e7de27fde4fbac2a0bf252ddfffe7e7582b8c2774e7c688aad34f6185c9cf529fa7cff952c45598e2d849f1b2a85449645598debe4b684fda5498dc46e7efabd4517ea3c2604a344fced693349dc21cd722a74baaf6e0a6290ca3ebd25950b6ebd92e85714bd22f59af839ffc93c25c2da72d9a9d4893ad5198d6c36de7131a9fbf1585c982ce49a1f208eb7828cc35722f36b5745d8c4161fcfdd01f3e96f8a4eb270ceae7edd2a7a825e8bc27ccb9fd5663d924f33ff9e884d9bf2fa84bdb79255a37d43e3861f834e7494cdeac313761160bd10ee2b3d4a892268cf9e917be25de5c9a09b39a1c5cd4e2e4d32b73f8c08439c8958ff2329ed5f29730bac979a11a224b98435c4eefc184fa73bd1206e9255650173725cc69afcec4caebdccf24cca692144f8e73af9d9384f13dbfd8e928ae65244c5bf29df88ad038298684e9fbad3cbd5ab5347d84d9b37c6af736419a071d61bcf9fd3111e25feaa41106b7245c979cf6b5748e1146b70a9b227416a53dfb5884c1c41725d856929374e95484003e12610abe22f6f249a62d3444983e457c8cad249a49265df83884d972d0ceb93e3584497e67adb494dee5ad10e6ef586a4e644774a5720d1f8430668e854913468f38e14ffd81e46310a654e741e88ae93752bea196638c30721cb33cde40096c5ce04310e6adf13d154a7e0739f90884417968ef1b2d3f0061124f28a957cbbdd376fb0793b4fc9bbaa3444bfd60b6def334f29f3e18f4973a0bdaba3e9afc0d35305e63f0c107e3b7d909f293e4c71e8c2798d875a2c50acf272fc6f8d08339abc4dc7d74b94afac88349fe93ad454ddefdd3f9c0835167f76d4c522622b78f3b98a4ec175bc3dfffc4ff610773e8da5267e2258faa9e59e0a30e4683051f7430796ae94a5d3d413ce8869ade88171418d981030c1c5fd8c80d94c0860c3ee6d09c5473c13c5e8cf021872d2761e429f14285350e66ffe8494a8227197b824379c1c71b92644b774db020c423c6871bd2a15ed37464c4246fa8e30b3eda60d06a2758864eb2c1343e4a756f553ed66050274c8e8a1d37542af9508349526337da44933f73b68f3498f2a7e09677b1e48ee2071a4ce2bfe491275fa520ca8f3398c35858aa14bed327e112e2c30c265d52fae91cf5e3c89cf5f05106936a5551d1367c90c12c3bf273d0e9d2c9cb661d3ec6601ccfda270959e29df0a5184c42eca39e78fd4952f14c1f6130fd8baf5dcd8eda5e28f800831d7ff345a834a3927b432d06ad033fbe600effebe9a480f0e105832a939e45291f2558359af0d1055336b1a495d8881c95d281a33bf0638c5190f0c105738a9854492f3b058bdf160c5b495a58972ae976d282393b7405d99f710b1f59309767bbcfdb41ce5c89b5f08105e3680fdd61ce3a35ce2b98a4184a27cb173bf3fba6e1c30a269327cf9be859c2e382868f2a9872c4c4b4764a39be4f05932027e458e568dab333059390117692f460424c56860f29183b95cea24b0eabe0230aa650a5a48a61af083ea060fc12b5237217b572cef1c50c5a8c12848f2798f4c97e920997dd50b3fbc187134c2509df52958d1ce86cf0d1047350c9ebd34e1a33372bf1c184fa4596a531a5622307da25984b38d1443dad27fe1f86a141638c3d0050e243090661daa27858139f3bcdaff8488241c34f0ef249d4011f4830e57a1172820e1376848e604ef52746f627b7a01d239844bcde8cabc71f4530c595d055f6997a212782e94227c1e47c4aad657f0cc1dca16a720a1f84ee10d1d9043e84608e26091b42e477495110cca3f35e52139408a1461f4030c84951f5ff232e7dfff88139468559b0915b2afb58f8f081f104b120be25dd5bf27b60f4fea4af2a9a101df2c0206444b68bb828d98255f8d88161cbe3354c2e0b4aec9ef8d0813997c926e8e90515b435e2450646ac141eb930f5259df5ecdac77b1ea931e245064668142b3c70610c9dfd6af93e2f0531310e8c789181bb854955d3ace6ef5c8b302933bb2ce39f5fcd6382104598a44eb6973f73ece7304212615262c99d276d5b28d3bcf84008220c6264e53779a2ba738e8e1723877e117208737afa609e63a227e9fd1d3a728c91dc881714401c8418c29c5458b2f0b53ac973420a6132493ce1a4d2f6d6bc4308617a93472c08edccfb4ec8204ce25759b6783d4186270853785c391153b910120873a5dc90f649ab78d00184f1746ad1e35919a7db3f1c8d103f98837ad8e5934b274f5e1642fa600e96e4202a56f69df97c309d24fd5c87d6865e680fc64ba9bf53e69fca62ebc194daf2fb7dccba2cd93c985b84127d592d4d2e291e4c49daf2ad933b98f3e5b4d809cb0ee6a09da4b5b3bed8a73a9854091723266a217430a9ee242e7bbe7cc2a5e69098b2156339642e8657d68e062172309bdcf1ab726ef5aa1d07533ad57d964e9c54bb32040e460dbb24560ad25e4be90da614b7f6254229498cc90da67c5253f6c4d2aa9d6f83e95a4f92f4e79c6a68d960fc5241849a7431ccd7351843c8924acafa76e1a36a30e513f44c4b6c9a70c134987cc499fe68133418b48bdda593c4ebcb9617881172067338cb3a2323ee1fd42166302751e7e5774ff546b7849032182be86855ad2f36ea93c138963fb61ab69f3b34069350eb14e5a4f068412d0693ea9f984b955ed2e7301884d696bc718fa5c4048329c7efe4b026cd4952f805831a91b73337f1824989a899934d922e98334b689b1454bafc142e98f52a69352d233a3db70563c8c84ffa4a9c128442b460aa93f4cd49d25287d530240bc63f71be2ebde6eb9bb060ea51f24349dba19d84ae60128bcd132ec86cb3fa5ff0459f5520c40ae6f4a673bf9cd6cdbfe610520573cefac9f2e38750c17872b674aa3d0573ba249c3a29c459ad468346181c302998828d72318fa75b17954248144c953dfc770c2fdd2cb94208140ca74e16cb1efd2d744643c8130cdf9f44b914444fbe2437d46c3cda881714187984030616e204d3a9eca732278bd8169d21a409a6122b9a7c4a9e383a486fa8d9086182c9e4ed53ef5cfd6b9603415e206409e6301d33f750799f829460548f32cadf4d50333949309c52975f117769a6448249c597eef7d2edd4b927841cc1ec29eac9c70ba6b39b0d5b448418c1a426bf6ad25629889022982db72729a7bad8d7cde810420453afe791a54cc786902198627779fe271dede4620789102198b7548ac56eea955a2a305263840266a4c6880482608a4bd331fba48529750e210408e60e2aea79b0f2af6df108213f30750942abc5c927965d7c604a29c6afe46c921821a407e6341654c9b9b2a8103a0f4c266a49b172aaee9c951d98a2e51897a399078fef8210a203937faddbcac9299494940b93d8b3b4255efb64890b837c09e2448589329525790b8385cd95fc7a3d9bbb2dccf29f154c3f86362907a416068f6976a25c9816e6b0781916947752cfcec230bff2ee41ca8b0e155998647b50524bb64062410010589c1190572c00c41505d28a1c290c1c3658b10a10552800241536bc4000082a708801813f611800e414070031858d04809442c7b740478e1824008414361600320a518c8100905010000414332800c827c6180088270a00d2890f104ee01001d9c40c104d04002413202098c02106045ac70b2020027209538b6fe9166d2a88f22d61f4aa3e417b754b3a2b610a9df114379e6dc243098330ebdcd9654e49e23e0953271b2da21d4bc2f0bfebc1c2b5913058eef473425b6b68151226f5207795fe93bc9f7c84a984effc9eacc411c61394b9bf054ff17b69847994f6c60725b3c130006184c92de9d43bbad935fe224cf973bac79ea07fe15584a9dae5527efea3cc9a08c3edbdc80a56e22f4922c2a89fe5c14b5299d749c6003984e947f6e4ec1babf59921ccf12ea5d1eaf9d179d9002984399b989356a468932d08210c2696e80ffb792604e3bff8ffc280f15ffc064a60a3410661ea3cdd2927f4e4fa2441185636ad645d9a5d1c05c2f066e7a2abdab6b20710e6b7dc3b33e69ef35b903f184d649da03227d57912c40fe6cbbf554956fcffa082207d309ec99dafa49f437435d3b1438c2580f0c12044bba7139fabd5d91ecceff5bf26d496a065ed8e1768400f26932577b6af0e9d3b79308978d0fb16dae39b070f06a1e6545a8b634a1ecf1d4c6228e93f73327492dbc1b0f5bfa7a6e7e5f3ae83a924f932cf613784dd743055103b25bd5f5a74770e06b558413fb69583c1c4cfde6a72ec78521a078310a526de5b0807b37a474b9d3ce50de690133a998ef7fdf0b9c1b0bd319fe3e28cea7810206d30e77c25c7ca57ca59942c0e206c307bf22dff2c9d8286c3c6d3a0bfc81180acc1a44a7da68cb88d93aa1a8c2e968352a624f97b2776004983f9e29ca0f3454fa2fa07418341896342ef46889294a43318de6fedc6bbbbb319fc8198219de48925680da14bb51e8094c11c3c75bc92cd3129ab36d4fe004206632751dbfd2a8589158dc1a44aca8ae5051dfd1d31182b49a9ca93beaf9b74183e258eccfecc0c0693f85bc9a1f3963ec55f3077182f61df7bc1a064df24f1e43b3f71bb60369dce6d4e5a5d102b170c5a9547afc9e5168edab6f59ff4450b2615ca36ecdd2c18c496f8076531bed7c3824912a774baee2dd1e4573067bb9ba04496cd57cc0aa6a44b070b564a7c4ea9648c30383052630485810303233546466a8ccce03170638cae00a302a40a6631a53adb3a890ae65ad1a293dd9f54629a8229ee7cca1fb62df24b5230f69b7512e6727de54f144cd16a334e1c3514cc157d4d2cd967c3d4fb04c39fdeaa52634a1a779d6058d3f712bd67134c953b5f97dac80b422618dc3f8a49e9625ac8106409c6341d4f3e3df2aba207a204534ecdab2a49ee20b603498249d07dae95fb5f95d70db5028204e35eb894f3b98752cb74d418f99f400e2f2c017204936aed569c08dbb2f48b2fbac6881719b8b1038708fc0062043c2045002182f144256992588ad5d9a58e1d62800cc168241021987d4cf4b6ab94ab7328710348102aa1eb33adc472718081e38b1d638481a368a0c6880846bcc8c04829c943000182a9c4fbe4a95392e474f70fcc9dce4bd64fa17c7b1f184ea88b62aa712ef7c024feee527e1162ae531e182e98bbb98d12901d98c3e79f754e4fc2281f440706e563f79e4ee78a32ca854137dcb4dd09c285b956d6bf5216152694b7305bd6969492dadef0a42dcc973de2938bca7215d6c2bcf5c94dff4f6861ca49785fdd2ab525bd5998bf33547f10113a3a938529c96dd2768e297629150b63c99f16bac4d2f92c09166657598f37d6f75ff15798564dd6541e6dc2b8eb0a9328c13fc74d28bf58d20a9318622eb8cfac29e16385d992b460616e3961c62a30953b49aa30954a41998ef76e9ff45418db44f760a52aea312e61062a4c67528c7df414fffe572dcc388549b84e722244982408dd1406e1bd39b2b7dfccd4a56094744a92f7f2e5741b75235e6460a4c6c89ebdc032318314e674fd29a7f4c934391e857994dc666287afff8da230496a26e7385bf9c4a588d1609429cc0805265e9bec3e2789df82e22c27ce9ad87fe20fb9e2d94c0aaa52ef894efd651dddefabf97422933d78bab05852cc3951dd9f94d7a44f5951de849f4a7c3df96a22fddf1956c2681365c23d3d9dae84e74831f11e09952ed060eacad559b5675a533d83417596ce722a89194c92f423c47c89f2de170d1a37e8a20ce6bebc9e7d7e82cc3f7940176430e8c8be87966479923c06c3d8cd863629bbc56262307d8cab8f669b5f95c260b6e4dbe5f216c485e571e8f022d9d105188cdd3946d69f58497fce174cf529479cd611ae79d940096c84d185174c964349a67c3c34c4d405b36ce787c9b9604ced64a64d8f8eb4f3bbd882294df78b4e9fa205c399608216656a829d290be660a2c794b1a4629e410e1d3bc678a41978946303755830274910ea2a67699f4719e0487c05839b9ec893b4071f936405539c962426759e9b94628d2eaa504eaadb849587fc5ea860d450929752aa640ae6f8a3c4939b56674aca17396cbc0eed401752c874d9855171666a1bac822ea260fae8b14d5fe84b3a7a386c70a00b28643a25d5a9b3827a9e6079091e679e138c35f2d3add7e4ac53dde8a209ffa8f9a04b92a4cedf32a1d49754db4b8d49ed74b104f375f8517932eacf829460aadf1adf18257a89ce0aba4882795b3b2c9bb250e2e74830688df83ec1b37504738815252398d547caff99dbe6092a82412cc7d36eb3fbf72482a9746c487bb1ae2e9384025d0cc158a28fc92a3371755d08a6133d651332bd6a4f368c18e4400b741104f39c0955aad5723ab980601cad7e4aba28f563e90f4ca3a6a2bc2c5b73543e30e8a45edf244da08b1e9883dc0f1db4e4bcd4100f4c2a6d9708cdd39fce455decc060a3247dd1548d0aa7ebc86123e94207a633492565b9275d4b557261d6b4ab347ae2ba2ee5861a18c53e20820b737a915d17dfe6e3b203076f40e4162695a4a7d03a0f51a7dd50b39163c759ca914347076adc05446c610c559fa6625d0513fe1588d4c2f0e6712b3a28d1c2d4154eef8e78be5c498ec82c0c424e322956c74f11fa8d31fa460a446461d62ed96b67a32ed48b857994b23f653a45e7cbeed0456061529b9d5d79dd57183dcf82d2772989f3a72bcc22e4a52959435543d80ac3ce472b4b6d66164aac3049ad1335fc24adc25c254cba47b54f4bc2aa309868e5a75e2b4f8e925418f5ded39ed4fa9dcd4220820aa3899af9d8ff113985fe40c41422a5c84b1274cc6ad0a0918890c2a44a5a3ff96ad409931b8529e9370ba1c4a7f3bf446134bc20120a83aa4b1f84b90814bd772addedbf1142e413c61a61a2bcbfc5e7ad2d6710f18448270c5b7a7f4c5b488413c64fc953b65ff05492706d22f7137f2a5a9a30404413c6cf9da396a2a98b6366c2587f4979875262c2e81e6bda4bfa91a6532297485491a7fa72a524552ce1299d4c3aa1632dce56895cb3d7939c2499b1aa062294a8f2c424afb14b93b3359e84b904f93671f424516549c2602509a27238ab9cf68984e1e285cfef93858441c73c3968f97c25e8e600914798839c1137a724ffb35fc4110695fa95cddcba547a6c10698441e9d2559653e952da8411860d1d7b3c08cfa9d2c71110598439e73b214e6b4ba82fa208c386de916dc2941810498441ff469f9c7efa92b71b6a31681d0b860822dc9dcf50aad6ebe4863e0e1d3b12af0234683c0e1d5e1822440e613a1db73cbb87ceae05ff632422863028933a679317cf43e50a8814c218cafe3fd53d1b8810c218a3b42741b8797ad80761509f249d61ca32434510a6f4135696b7f4775b198804c2e8f1ad338608208c7ae147a499d2d0aaeb10f9437af34e5d9434795f1996ba2188f8c1283a9b257953cf3d593a88f4c1f4d597dfa2d26d25a53788f0c1645264bbb53bf8e73c6983c81e4cda762b7d862939e4c8c6185d360604340d227a3009a9f9274a8cca259ef2603abdd82559bfa6a5133c98b6e2ccef53ca1dcc71cc44fb3e499b18391944ec603aeb8cddcaea60ea9483529f468a9a94d1c1a8eb26af495275893753640ee6924eac14d44ddc8965eb30f8051139b0ed49f2cfb92a993a0c227130db0895e3ef55ca256e0f108183c92441741e95f61b30cf13b48e6c936bb5dd40096c9040a47492ba6a91a12637d4d00e0bcca075e880c00e19982fbe68303e4083061844da608e57fbf9f5475cac09073c80bec0f10230aa20c206ceb35e83bff9d9845bf411175343a23e88790f97da94863e4d49f3b12d5e8e066c84583d9dcb84dcfa0c8f8e16b5a2dd49db31332037eca42ccbe077f5aa7d920c2525fb1ec3972f99d2d1d3c736931812ceb4bb8eb643240c26e5327b49f86c518208188cf17be28e47d90bcdbf60fc94640bb3fcde3851c7efb08088174c21469ae89d5e5b111f41a40be6f0275a52b9a4b863d25810e182c964536e15d472fa28da8261ab82ce1ed4e9d84eb560109b96a7d784790c6516cc6362887d91cb182509164c4aec97d47bd23ccc7405e3cd5dbc6b5b4e97735610a98269548d76d9bafad2fec6a03d90b72254309998ae75aa2d41cd5c813166f018a04183082253483239bc52b67997c68675fb598a10e91f5ead1744a460740f59a9fbcbd2c7350a06e5b95bff3614cc15f6b292ce79bb045111449e60fcab60c9da6f54c98a8813cc9f83b8dc279c9a60304963eb829f283915269854da2d392825b329b2259856c72abd24933546ae0473ce3da7739dc397b09360102243554e72d3549b48307d34b3dfea34fe6a1139c26f5a232784d4cd20620483f852ea7b8252cfaa2641a40886afd24fb92c7afead20418408e68b3fa3b5d25352c243307be930bdcdb7ba244230cd47ddd70b25be542808060f3a3d7550192a98c90db519e8d861b63a7e078e444580604cd379ea7dbe259c24d681c80f12111f18c48ef0bc1e2b1bb65e13447a607e57972b8bad080f8c76a56942a428b2038309ed7b3135c2bf4d37d4700c2295baa5d31dba8d52c210920b93b020b428f7306e3abca16603bdcfa0c1e02e84e0c21c3f69aaa8d0de503b2b84dcc2b0a659a57278c515955d15426c61d01a136e7b26ff21a41626ad12c4c7f7c985d00f21b430e920d2f3fc5cc5ab330b53ec53dda1323b8fecb2307e5a4b7a4f123b4993b53d84c4c2509eeb82102ac3c220f5bbfe459b72087985c1c4704f319e56d28d71087185c1efc4a5a47bbb9dcf9056987e3cc7facbc60a73ac0b319673106ee50b76f48e31f61021abe0dc93fb4939276dc40b0ae4483b9e0285085145413d44b4935a2c503650021b007844482a0c9b21bf429c6ce565595e44082a8cf399dba5e485b3b0140071083985492c2bb1a5acfbe2e60e08318539aea9385b2fa6fd0929854958f0ce6792c956824c0a83a996f0562d5e9f5b8d91e2c0488d91dac0488d91d2c0488d91cac0488d91ba43c8288cef776a2e696f2749e7869a284c9fa3a955fe9752ae26424261fa31d972ee9362878a1050986afeccc40b6d95a7410347c8274c26659c24d263443fe80983692ba9429db4667db2a1a408e98439b625ed24c27e4e1e4111c2098390a1c34e94deeca53f11b20993a0a4b1dc659289559326ccea23a39fdfcf84299d9b502baf948477306176ad301fab720983121fd7fd946e1f155bc21c3b28bdd2bfb99fb6488454c2187fa694bb6bbffa08c7404a982c9630c974651226dfac933f595478eb1885104918d6625c30f17fffb6040a2191308d3ca1ad7bb5632d5abe100209e3a5772a2d2a7e8d0e3dc2e01ee3a4e538d3e99e17421c6138258b8b5b12c3b3ed4698bbb6a497aaa44c2931c294d74d32f1c18c10a8b4508532591c148602a160280cc5c080b70173130000000c16124642b1683018cb7b3f1400044b322246363220202412141816094862602814080604014028100c838281503820240684543ea13ae222ac01009ae59fb935089128a895448466bad1d34d45ffade2588b16472b32e15e456b790a46719921baaf2ca65935ba7294f176f1c4c3b2c5107a48b737d530ee7ee3053c2cf8c530a651b9b8465837dcef354cdd38a1df74c50f7d6a835be779d5e88d766d48fadbb32a1b364ac55e6a43f06f76d89e4d7159da9c09f42f6fbdcb6a141aff0fc456684ec037fc3027cdcf929de97e38b0e99e9344af695f0f9d1b54f1f32ef0228feb08d385bd735f3411a626b5ea3cbc929672be961d2606963eb04d04c01b7f83f94383d31045601a7cc96109c22290065175aac3f0249cb9c78648cb0d182f1238525b0e7068228154d7b0e6875312ed4bbca119c4ca1953eda039c25caa2ef087fe0e6e307a658be3025c6c6b331077efae75988547de14acaea53fa6cd5d60e7a431f5a4b6505782e10f8ab4cca46d736257d0b822991ec14bba0c3efaa8112725f5a6367311b569fa9113f3853597a5dbe23e287298415d7a2d063141c1d2d529cbfdc2592ab200fa42d9fc1d07eb06aab5f0a8ea548d6470e238819a45fced0513bd683df53bca75051a54281d20ffcbb2a9ede1bb9042eee42f9ebba4480605245143a47325e40d9a4465bd344e102e64772d2c7961a10243917d41e0fbfa49a32632f6846846ec0cd0baec90f451c422fa34c91439bd7fe8337112d748149d9a8edb84968e667158cb6a5226f40458249f1c94c6b2189ca394d08b21ee06fc1d51960dec54faa0262f1f23b82d276a161e58bf88d41c6e413c20bcf982550e192d496c333d6d88b15143c34589bfec4217ff17cd6a3bb2be476e14b7064af36d9a464d247965ee43350411013784b159d8a0e948d0d1dc5a4ca1cc2bc981f925c8963fe7f3633979a94e48c6908f076882c66ec2668f9b7e502e3d2c4882ca71fcaee5f9eaf2ee5927a6ecabe290286d0d664118c2086ff44569b5ff4229e33562bb072274e5d2fb97a1c4832f6325806a0530ce9de5bdd3356e11398590a435e8b3e9048284357cd107c1fa78c63cf8cc470d4613b1301e500e753130c8af851221081b22605e12a14a401e1ed3a4549190517dfa2cca4eccb98b40936828e89a5f1406bf20f67b0215409d1ae25c520b03d34eca26ef9a134d39692abc525e5ec40a2d7e843ce554ecbcbf7d7af9c86ba145199703896f17e5655e3bcabac75fa821536e835181a3126f8b8599367112ac6c3a8a106d406d28d0cb52c73daf9691f668d53882ec83a7839a10b768c3f650419458ee50333c76b172580a89a16a64cfd8aeb7de040761febfae1369a3974e8951096fc9291d8c98085b5cc6ae6593b866a34a2a62b13efdecdef82bfd7975bf2fd4ea62e09fb09e8f296999fd34efbe3b199f12c7778a94407e91c6fc517937662555a9e444596030568ab4733b52ba37eb7e9930e340f4f85474282b4f50400a1e723858461dc90f900581a09e71778ed4a5490a3cfd1336320446f50290442aa21f24eec4b0dfaa6cb9fad951303623d1cd4ce3de392b858dc0ad290949d816b0c33b5ce9b4fc91bfc3968064edd321aa9c70bcd2a9cec05560f9e31651ff5c8430b1e504731528c8b348f7106b8ab8cef2ff3e3c84c9d26053a1dac623f12a583dbb79936482443bc146bc06a705469ce7bd05ba376a7c5a74d819b7fd5724eceb3623e25449380d55884a7651369b894f17a80294bcb1016e124a99e1a236c3a6fc43f57113223d648ab1c0b045fb8bf1403a2eb6b6aa3b29c535dab4abef8feb4ca939247d8d2a5ed4b9bf6597fd2e17e63d7f613644516e5552c4819643e13464b70eb2f5412f9d4d1cd7b3cd91e7d1b0b49a8c724e165e71da3cb699524812b4256444241c8c4bd97fb82dc2e95dbebd51423935e559d02839676261d24f17840281de9cb90804ce4da0dd21d60b9c4c30a377c6cd3f226df243d4a886f6e4dfc9fa4b3d6ebf5e4b2a67b6e0ccc594619ce867aeb21961ada2ef4df91f9799950a6ef4ca1fa46b4e6cdb763ebe280e66a1269ccc2ca770f47cdf5423a89891ed801bcab99f274c056d511ada5f2620e2a032a7dca2d88c6075fbc9577ed3e9d2dfb2e4f639841fce15e7acbe0d5a22d368e27d38034797e185aa0f94b91459ccb99c87626d2bcc37ab43153f4b51fd9b6dfa7737ab7e3368ec630af498f611f60801d2f90b209ec5a43b8112a1dbe8c89bd25b2e2887a3537ec96560c456af3aa26392002eb3e9a8ca860ab89b8d4de075363839e8d9f50002b73d7b56f6acd1486550cbfb0a74281807d4f48f948195b444ab7419e8cfbf33a4b97f33d37cec76320002a3dc0b4aaa8f6b2cfc2157ccfc0b91ba5a9ca4178af3e519ab8104c72514c3c2a26bb3818745c98ae5ff211a9df56033bba2709f3f5427433bfcc4ca1004fb0bdd506b7aea8cf42cc7813b139b755c443dd7030c5628e918d02c13a3b7b31561b3ca2c8497b45365d35ff2a4a3082b1c1f2d121b9ed0b194512ee61bccaf1e1515674d9321a04ea1b45042e5dff0289e1863cb93477853255282418f6f96cb0f4ca6116f93b4a49a4f8ea342b08b06cb2118b2ede239ef1046514765495a61b39a159f6773f6c06ce98d8dcc2156a7f24ffc4ce8c6bddad71cb5965fbe390b7c13202dd9b1cb5df5d804835f6b5fcdd8fa1c83c9140c9f02c77cab7806b9f61ab041f2aa55a9410eaa19fff118c6caafdde68a7fd3d153e945bea2c3a30559f8a5af32b4baa84d2427962e6b86ac55b89bc1166884049828d14357cc628398eaaa9fdb791e323113409de2de70818b8b3c088911f963381246021661444ad1330bbd03845de337602558339d39012b4762fb823ed19bbd03b8e0ef44872f4c9bb5583ed4e3b615a5fbd41238eb00a5aaca4af54d2aee73f3b1c6334b584457d8201ffb921923a78099b70ca8084aab25ddd492a3c2084de6c78578ae63f11fe06da241ef842849d83b4e0f605cc40b8769bf05bb78a1eeced314a8e845443541fcba84689a4c35975e170496a559e0725a2ea818f7e4dc2b5584dbae7109db574cb098f5d82603975bed85e38566213b3486ab24e518348cb95174bc7dc338c51a7339832837c8511d466f71761851d36ab46bf4c58b0a2c34c69a058ad8050c9f2743c0d3c92ae785ce21744b37b9f7d11eaac1225d2112056ea3b308245a73c0b4ea54c84bcac8249a2e794de7573ab3f3570f7930b9f485bfb30bc1e5007254f8d1e2d2800f3d1675070e1a749bba4281dd69a1a2123280b503946b515ccf4b40256f7258297164adf45291fdd6753f247a11d433f14dcede4fd1ee3c535598f4c8a5d3b041f1e30c8d23a9224259e9fde7e2de54f765038a16ee6803246952d7e9d4c8960cf091e3c50694a6dc1d016556bd35ac4068901361569a5c0e78a226708d7fb1a4c86e19193997fae4057080aac377d4c3e242e6e41df1dab51b9af8bafae824cac688096a74894b8dd4cb10b2bfcb1216b97e5c4865966a90d0cb24b6e4ae3a0fcd04e1303d674741f7e8ef7cc0e155f8cd0bb3b1dfd6ef7694355f085427918d1a6562d8503d6ebfc11eb6a021ddc0d660b401bd01276ab059c72e7b159507333f586b180eec22c24604414a4c2aca20d13de3dd02f3bc1ae333981e707ae4d94db1570507e4dd28788578b99b1236d8d839747deb0254a0b6bd85e3a74e633a1dc017001b00910035800716d032ee4946a70328972a198c74870668b99ab2afae8ba74857028228c43684f2101a10a75204a8e6801aa009e28ef209e1a342ac85c02362760192d474a5b60da0371509771673838eb49f989d8004213d881cdd8e79027304ef821dc04b46f0fe90bcab4c7b6d7e7e582af5c1b263ca2dc26c8c5459efc01e35adccff40143619fd3585e94a9e0dec3410922272d4948edc54655058cf5e22a9153e669b024def4b8e2a60a0c089810312dda90ceef7021ec07d68ec83027c2a164dc7ebbb8b9d54dbcb649e765164cf2a0b02995d08a8df48d8b92a4e8361428d5dba3fc1f171ab5a47ca2d9c0aaaa9ee390b840b5c10773af367933175087df26c4fbda125641edee95497b6d711a0a41519e946890eb3a4e23e17b851118b24480f6e2c0fa438a6d31163e47408dd98324b8b442a4706253ad99b0353011d39231b3d0c0ba6bccbcb29849a8a910efe38d9a07646b4a1faec7799954f8dd23ec64d69d2752fad9e10ae104083c06165abcff37c315b4ef0556305b4d16d3c1f47950279f9e942d920508873635bd71e01c4b3cfe535d984dd6fa325e0f4236d6b227309212e58493c4d064b8c12225ad2ed36812cb16f558bde95159a7e88dc1ed3d01c96687fc84ea92a774cbc6d1a617e4e41fcb42a5ca91930b3a79c1e71cbbc879880c6a071c2b54ebff6b7a24f834cdb2192d7c8ecf762a1c9bd71595983c4b5a0e5d2741d35cb0643acebff447ca3426eeae815c957689400d7acde27d34551211f03237bc6fa13e2d2c7dc4faabb84b8281958401c2201c1d96cb38e579ac1337cf0f45b184babc979fadf56883589379e8ce68e3321ee5155580a8185e13d14e83a99faa31f670961751c15e65c1b4b56fcefc4a2a6003049ad26f78b6afac8aacfe9b9313518693a3d2c97e5974a6a060d1c7c06c6e999e4a82b22a50bda114fa2488bd6914c8a5ca25f756c2a5a9478653209cf1182f51d1d52ec48b49c5d18491fdad9514eb1c37a35352c7491324723d661c973243a56d67fbfec391cf92886152495c3162dd2aafeccb719b723e034537b14354a59310fd016e7cd513f0371dac1d550282e016996be45db59720c437454e1d65f2acdba63e51552f791d615e3badb4df474947aecbe97d161679b69d8b9e9b5f623ed05dba78834d2019f162a5e6cefcb17317b260c29fc4fd85028210073c273cf712d4983b8a57df74ff090b711a84aea84686f5e26c8a396f033272927ec35713fbf6f8af6a6552cac2b042fbc86905f0bfe010cda14056e5e973071a4b3334fe48e489cd2ea194f295114971b9066d99db2190647a42c881485ba426e8fbbf9a2da8b544ae99699f5de9d8198eee88edb8318be291a80887969f7d03f67a32726b44ff6608885cf1a6ba6a27ada31276ce147910cbff2927e210572aaa7af90c38125764c5bb2e4e78c741bfd3ff462402c0f54d22b4e6df8a250dc966f538b7b4db6683304f09d204089a44f25e83a4e5fcc44e1ab3f95c934ede406268e01870c57d19d8cc17ee202546639fd87fec44563d3b4e2f4f2840f63cb820064f000ea8fba5c18ad86f3ff2d35266a0f0f8ab165ac4e6d969c76f36a2901f45baa90b1d7d2e018e6f023cc7e050516ac541c117ad8087a31fbf37c525bae1caf4996e132c4f7c78b2e94eed6d6e041c244a6fbd5acfe9a787ab7c7fb4601e687ad82ff20fb71a89380254f47d3b0890f0649c8e80c087860f896c6ae0982aa3cb92cf613ddd18c62dd3dbadaee9e53c851f7b66eacba9b0d534f4808ef07dde5b85b1fba13de5675eb9104632404a6bb9c955241788b6c779e57f3ba75fb309626399ac4f961aa4660093efb0084f958dd36a5672b444416dd70533a541d7c44151cf894d8314b73288791a60912be384301ac7f9f5de208cbafea144e23d9a623a921584b9222d4894351c989dc4d944dc2579ea80e6d4a9408fef65ab0d85c690d4131b95a941809bf9961d96fe3d96261ed8c9b39807661bd1332e28a3e72503618fd170d43144aa0bc73d02416f42dba1cb6aa35f2e679eaecac5415e67fda7b79940a993b9a0f0e535acae7f0135a44714da2de7357f4f0de2aded370d0b983e53ceafce44c1ac2abfead903571a7841745cd7a8220613cd4b9acbcd0478333f1417abe9474b6ede0d2390fca5764b4553e9f638b1f46fdaa8aaafff82ff6230162a7da2e7f297ed0a9af8329f81a83d639c286f5d6a5b0dbf7699526119057ecfc33008ddbd672f32f91a21f9f49304c2bde86057ea8fb350911fb7a8005026905b066b0a16af8fa7f107763662c38501c8c79312d73c8e0a9c16c03da3f58a6e44ba06d0d680c3ed99c333d39f1f204a18b35d0333831c03560fd079bd9455baaa80e5b579aa9342c5ae38014171e02f00c0066bced0c65f3841111c83541173538f735dd799ea44088bc418030e8d2f012d6637429c2f301521a7ee7c3eb5bd1bb28252fb130a8c8f955ed9f636717daa38ff9e0f72ec483980f02a4c84691e0170a85034a240e9cac1a693e1fd0a3fae1d41eb050975f6afe10c47f0d7bcc9176d3646e8d08a3abc9fe9d4e8b8878d537bdcb8ec388ee36ea36a6e5e45c78cea904652e54ed01e20729a098f68b58341e11f9aa9fc2dea2fe23e442b2fee450c7a17f487a020d8cf1eacf9a814a09482a51f0b129320b4151ccae1f861be32b5108d3e5097e3c8963da4ea7c36e26c3083384fd5a152abce115adb7ae316d172e5d5e53f5df0ab7636bf3a960c77224685db6c89a70955c24ebf252eea071495b7e9767d2b730f38839a9c27171962ff6fe094bd0e8ff9f5c788759f63257b76aa4dacfc80796d7eb41a725262422301ebaf51f30d136a173970d8dc480077008e246c504865a232252706bf85ad1ff9a089fc0ce6fd68e4e71fa8a0b3ad51de4bd9a1c441e105d14576999245f3ef167a6dbb365dacf8304a6a7f30eb181a9b992d0349cac92decae1bcc8e163dd0451ece0f3954d88b58b9c00896f0f134acf8a24e000537e16842ae28d7f0af15d47c50126e15bc22710500123c50d975f12815eae3bc07b90b25cadb43d32b6c64c24d5b4adbbd57b5cc0d281454b2c40193ccf21a68e1d28b755d9104bd32c5b47c45b6cd7209198c28cb4f82982e922d63f4fb742b8025a0ce6d980bdedffbb9a54e4f52caea9045da5a1bfb29e22ac036b82402ab9695a81d39207c8919b705ae25655ee294326e79ccc09d26a65def2a88320c3b843c6fdc1e82e86eb604307a5e2c3a7129735af086a6de68903182e1d718b7f40b0b6c80a3a31c9ae3a4df0a5b0200eb7a4d3c4d720917a224f14552f353b4b5e19355b6e44fa90edf5de6bc30bca8b5bfa3a2022aa9ccf433d78c891b66785b19f24ff4e5ec578d17901f582f31af112eb65de6bc20b85170daf045e5ff95a5c2a096371531b08787d05bdaedffa980dc80b8a178e578c17b7e5ab71a7fbe886ef18279573bdbd6af28ac1d737d8bc17582fd25ee4bd80f8eb681d0a96a9675ec70b58c6bdefba7491f5baf7528aafeb9a700707acbda8bc80e00b0e36f0fa2ad0a9f5852791e49f1a2f1caf775e585e24bd32793df76b26bc529cccdb5e377dbd04fbf562ec45df8b851796bee060dbbdc47ae9d9eb68beb888010fcbaa8eab5e16eb23aa8d9132baa82625962e0208491ebaac45f025c37766d515977011ec9fec126ed30893d10a0e1718b1040a90a1d4ac58be088021c58b95e4a88cb09750e1a14ed0bb28c2a38c6a9ced4fd3e22c7b596c7d4dbae488a17322ff182d9d14cf9c39fc08e6df79a13b5c4e91fc054e883cac7d40ad4a99486220333e3715a13bbcb1a71c700b576a9f27c8c033c509e143284e97a286676e05c2ad96556aedce4360b89d663be7b255bc1c91fd0be626feb52950ad6dcb306bef78cc3359da6be6a8d9c009bf32fa5ae2d22906194da3764668a3d29e5c6831a51209a455e154d9871cc1f6b0101bd6b82ca8958e02c74a980472e5d359eee4a65201f9cbd557dd0c8f57851a47baa3cf85521914b9cf602d8f90c85b3e7ee87d606f2676b20904f0e8477e338c3a835dc2611b9e377bb9b63a00eea4890e5192970ec72ed7e0c28f2bd28a3022ce4b2bab7171d7a473481c32e29ede2fe81a4ee381677a61cb282851bdff430c64f10fb34feb607d680f250ae825138ae8627ae4d20acdbb81e49f785f476f965f6925e73a086610a32afbec3ba998acf29870d64824a52e0418aeba14d6bb3fe0b9b36f06bf730bb2cda7e6b36cf15cea0554f41d1c1e877bedf1ffcac7012dde54f66046822b4ac635a07292274459d13b0b046e07ea24229efe517dc3dcca4f35119deaf184c20b8925cfcab4022da89e185b00661f5926e872642ebf1781626671652445d7475b71ac90624d0894db7ff94c712c0868789306d4acacbc53552397813db2326a92a62b260c8de841160e079be8134f47dc5113ff512d83041d4eaa182da97380cc494bf57de1f2b8c48d214a644f8c27b47fe9ace6b0cb7e4b928f7502b73b442d6bc9a805df848326e6d63e4b9dfb9c7e6d03460b81e397b42b2cdc2ccce9089f3e76d132e8a373958752a5606658efc09de3f842056a35348e67bc04b99e7d48cf0818b4bbd5e876b2724151151e37c90ff8514dbbe63402c837416f8a7fca73f1cae390048c9f7039f6e16b029038e665e957f3d52826d45208092a9e5529c868cd54a0a6a801f382d452c9539ccce897f611cc5e5f1871a939fbc1b7af252584e3ea133f7c2440ab3f7c450a40f6aa303d19f4dd461d21b0672c112d6d71f08856dcf405ba751da74ae7545317a3dd46aa0744577d2251d003f8715153abaa54f04f120512af1797aa010168ab44df3bc4e7d7e6067de125129b722b178bb766b8a0a498f09661da40524e6913dc4693291a597b97e1cb6041d2444283a1e88917908c6280c6f46349d6d0e46d10e406c3960c518ddcdbf3fb7e429d9860b0d300b0e547f09b30261e3af39886f569f01215f8f722077eadc920946ed51258ae1b14858688800adf138b83415add4874a8b11f00c8356cd435b76bf396a20190fe6c4a7072ffd0b6255e6378b38f74ed3dc04bd8f712b0fc4353006e7bef51df9b889019db12530546024548cf7eb3fe9ca739b6fd8521225bef807f7736722d192693d09cbdf5894d6a4c2e457eba47fddeb1a8926d724fed8f6f4b9937e0cf3a23cf27fe6b74ae93596b20400bd2130334382e2f0d88b4adb08a4c97e347a0a6c637fd6bd3dab51ef4d8caaa35e0c0202bea3f5a22ecf9a9bd32f06171f531e9f9c93bc2047bda013bb68b32fa9192e4680372722b98c0296535de642c02148509332effd740286881a3565ba8c4afcd050cd78abc2c449d5562cd0ccaff45a47f4c6808f501e4e102b6785f2c790efd31c625f98004e64dda306c4b4411b04bfaf6aa401902fcdb9b5beee789a55268a697c54b3c0aef92272f0c6422ae36c29f1db3bc1a37fbde38a5a6a0a3f6e10c5c1e73315301ba3564b50ea83afbb5b8ad4d4fe2c547246b79c0b88c813ae345734262d3d2b66aaca6e7e98d650615e34a03a69e51c43dcfd14f402752cb38ba3077e40b5966e8d004ed86f57e9c33b50f042b48d585c25874fd7b7584b3a88b5243a5ae7271f7bccdaef1bd1ac6c12e36f30575d62b12862c988eedf97fe20c4e0c54eec059d3222764d3c4f9c6c05e5bd386678de0858c24da29ce29b33868d11032767a0fc49e508c012302deccda0dd2a3ec094b48faa94d3d157fd6d01326140cd97ef7f04f0360a664c5570d2ba112c5104dd17937485844abeb6b6a3cb98fa13bc74a6354b058e2509f46517d79db46d0f1557c904b0616b540214e5aa2f1a96a4f4abaf846023cdb9af2bffc5edb16163bc840da6e4495b9688fe708e1642d30cceda38e0b1be0473537ba2a0dff66321dcbb89c9a9f1006454eb8a9285a31b1ca55b768691a2ea82e726d8a0b0eba3388e8a91c8c2ab48396947e65cbfb8aa7281f330c7defc0fe4f05d238601fa0dbbccb8100741698fc66c58e1c871027221e9dff629e0178c9b7781db1d11920228b5277dcdc8f415fc4780c7c67ea63b37cf51c01f9d888d32095e3c0cd64bca72b3cff8d58545250151b906c61f9357bc43bf63e7d6f94d25925287e4ba856f7e9c5466b844652c0481e9ba647541eb7c02b7229680e3b68e15c7fdf877d1b065541f45107f0f4071d81c3fb98a64bf61a1e7fa506c32b942a33f810f0436feea1918fa9ec6843d4801eacec92ee393c8c8376e0c623597006fd4409051db2d031b43117fc1a307e014818a08c7fb1d7a4b084c259a470b1cd58a3e38fce49c3a1fba582b79c074910bc5faac140da2df73d3845bd25db4d0000284519364e992b31cab6c3fc27af2c19d45e053fa77bd0f1924f561345dcf74dc45e57a9d825e180fa22ea01717335884c8ebd39aa5bf20d1d14733900d5f8276c30e2db3aa1aec8940661e386d533fb8d77f82541815b4812419a3748bf2ffaf96a109b5917dc8d0f4995f56cf1bc3c493fa0c23cefba03215166f1d5e9aa353c61a7dbcdb7fa5b7189ef96725cfebedf49ba281200668681c7f63177651fe3c24429642b6da8d0fd5ca51646d86faca40023a65865844eaeb48dd993531393eca3f1044485586fdb7583812cdc14c9f5c92f8c5b03dbdb44c6e8fe03c6c0e4aec5a2ceac4e334ee48ba30895535c7f18d1640047b808edc44d6cdaea9ea6d9501159afcb4733d6f60b5cf0fe8da9a1d140f563e586d94287843686bde54acab1ce078d534873ea58d934555e25d058613fc66c0877d58b1ede1cba54347ba7630c2afab0d7388e80cc713d7a89923070353fa65f51b92848f0013bf3d1e4595a76550da8e8ea342f0b93ba3ff2357064d9a5f3a14df75f5651731feb9a583aed00f97df819825e1f0ddaa9b086b015a611f54201c4211347374d60764dd75992a3d5f83f53ab707ce7baa5ead8848351e11a62506f68b7995048c596036ecf9dee6fad9bc3b10b06f25908db388e2d27908b87315d48a175d14c1cb8bdcfb81ea3edd4ae5399d86e643dc8dc884926ad1b57393a9d9ebdc4ed4ca5a1b26d7d197c8fe8fe5b3bca7737438909ad11039d1211e48f3c4fe479e1337bb7124c422042f13541566bb0d009f05966e1380c3a015b94d74689c6634c7d67362d317d9ad6e4ae49302628f30bc761294f0242d99044fd304fadd186fe8f1d86ed62541d82d3531aafde6cc8bc5ed208636736ca10c6356c13cd4aad7029d07ba12f118c6975065d52075f12ef7569d6c1af19e60354ff81da4286ed72d7222572e22a6cbf80a3f724bf8aabde964fb8932df2ce8afd9c49b87b1c2d3de06ba1038f994a20a9d281c8390890bee80a916d08b8664253f96b3dac9942836a0938c28928d45da01af7be58c29d1e844239826c6d51014b3b269dabcea3f58053bf5e35e3d910f005502ec22f72bae2d1c4ed517957c864500094084a112a4e95aef9aabcb45e6d327fd35cb3e19d454f73f87e67c0b51c213793f24d6a9a7bf9428ccf13eb38752fbef00ea1e8a5d18665f21b071c3015a086a73aaa36b5586aaa359d1c18b7e33baef6fb52d49674c3ccac36e236ce97000b4c0225ee59b02205c8a787561a1b3715540f7d4788d1b1708fddc2b9dffa4e2b6b639d1ea48d0159f12220ce71ff9c9349bdda08ec382c25948e124fbad046af5ae98f2394284ced153e1b3acfea8ef1dc36f1f2c19e6152c02b519311e78bac086bd319ae839f7678f43c1730ea137ee698621dd76af874971ab2e8c366413d5ee23c9befc4d3ae8cb5edac2460ea2706408c6f6dea2a51ef85ebef314ba8cdbe103ae3d73aa6b56d2247fb7810cfdc0c390f9a599743dee3bca1739fda6db7088b436a4f2b9e31e2b6cbc5ba41303c24deb7fff368275eb9eeb4d9965b32d8de86ba7d04dab939848a36880a961f88373724723eb041347b0d77e385056023e88e42a887967d4c397e5b3da481dde3e44ecbf212a3ec6b405da3348914c31da25122ab8605753a2802ec0573a209885900473d979dce5012c174cbf804f140b24729d067f86e7eef99f28953a38f94943c81126457747dff2e8211e101b55d88a35ec211b2c61c9b48b0a4a8712e702a5a9c3ce0824bed09240e052d995b8c79323865cde64b236c2e423c21622aa142dbfd1519c66a9ea87f6a21962e03689918abd7111322414c33edb4899cee9ef14a0df36c04fdc76928af649229a8e91ba49b5ea9d499670717d6bbeb4458e2d1c5b594b46b867fd4822dddccd496edd78d5db279565ad525718ebe9186873737ce36d83f319d99c45880790f0dd05e14a288819a0d0565ee53772c5caf3ccb83639bc3eb83dfba93e2feada8a14473c119d71001583946a61560427bdaba0f91818cf849e255183cd2d3cde21c1a19a7a4256246c91270893af4f40ae1aad472130fdf7cbdbd92dc35634813e05f944a2d2bf4f1796bb1e06c540235ac7e61c7a785535ebf925e49da4dcac78554f923680b78a7864f62a07be8a0dd1d3000653593486898d25995e1771f0fc86baff949ecfda156796c2729e7fa8d588f3158f06624c7920d2666bd1a7e0ae4d7a9dfa05bf44b15a2d495ab01644201db2340734e2b5c138a29dd35d05b7829450135006a820214141928725090a5c8860e2a0395148af714b0cc1481ba975e6f2cb650b650c0a03809f50d20249bb412d43ed414501a502fa030a0484165819a83fa0a05074591517d660e476112d4d35f54df8d2d08d1a0005fa87bc9f71e59034a138a0d8a9a8dba375a4d3fc2a1d042e141a57028605b17f29572a380662ef9017b4affb4f184e089d5539aa7bba7b74ff0bb4457e680958e0088021638bd04b5d7a6012a011cf1027e72c5b90d3460f0547230333333333333333333e3ad6a6e49f84bd2def7a9998e84029699999939f38bb4d94beb655b372f1c45daf017690702cc0dc40d940d4ef28f42d595050aaf4264fee41f7dc2190def8e43eff0841b7cae320767f313974eb83957b868b13f8a9d279c70faca6f3c8548a79437e15d9eb434d7a16af2a4269c89e01921e3c1cdcd84df9d3d68a4e8c4d82d5d60c23109d6677561aae2bf84ebd926de03f9143c4e5bc2abcadca9aeff3c74b4125ecaf6b6f38d8d8d2625bc9437fd93856ef3cd26e1479133a3fb87ce814c49822e24e1c747aef4d1c48e52965d44c2f72f5fad9cc2c76f030957dd43f4b01e6aa5451ee1a58d7512dcc27910d38ef03c7bc24ffd6a8467d1ed9207161aa00b46f83fbf2e3984a670392cc65603ba5884f3d6616cf14b69e1dd158c41cef83208015d28e28ecc23ddd7c6b42db6e019609e0365ef8216fc09c3fc0b7a8c2e12e14b5d881e3dfcc7901a4478a556b5393e574b98747108bf264a148fa3ef4ef676a00b4338c9caa317974b21fc784436a6433e39b5c3e88210feaf4ca86ca9a447a35d0ce2046147178128a5d565855f5b6b5d00c2cd8c0ca3992a44cbf71f120c1774e1076f324a900e729458bd19bd2de8a20f4e853cf5a1854849528e185bcb7ce8620f5b8ea5af53428cd51bbad0839b6d5716a326c777d9451e1ccd1c85cc610ea63552d5842ef0e0c4ce71acace64f3976eee079a0e51ecd6bbef4113b78b1d2fe24a69c33365d07cf345db8b8b9fb29990e8ed6a5b44c399e831b39f2782c73a889dacac14ff31eae39735e89641c5c0beb29658b0907377d8e51a33f67a70cdfe07fd82c7d1e846ef02a664ff340720c39ceb5c1b5ac315e366c70b263a3844f69e363cb015dacc1798fe6ed1617fd3c58015da8c1efaf89350fb13e6bd9451afc30295baa6b5e0de94183933a8346aab98ec38eeee20cbe4de58e3dd903e94d1766f0ebcb424d7af77d304da18b323ca10b327892993ce56421cfc63ec7e0c73e1f5279b7e4cf2a8b001a7080190b5081f18089011860b432a20b317841a673eaf33058b24918dc6493efd3dba74f6c607052a84d1f4f298daa54862ebee044ca3e9f428cbcc1a3c0d08517fc5837d144aed2c009c33c0bcce88217a4736b773c29d9a6b9e0e624a65ea2aa1e6a320b5d6cc1f530df88a0115739ad0527d867bac8b1c71624ce82e31195d171ec369f2d2b7481054762e81439cc41f6e8ff0ade6a49eafe30a262262bf85107f6f1b547a3feb92ab89d46bddf6a3c524f61a10b2a78c93baaeaf8627a201e53704a255b5270436d698a4926471a3651f0ee439a1c7cccd5e751eea0e0584cd1a8123e16f1c0bb27781f2c26c663db095ec44bb86af97d3c6e13fcb9f590af2ac7517ac904b72747fda9836e094e8c9c635d148f4ed4ba538257e9c72383e47ff1b38b2438dd653d6b1e8c0427d53433bf473942dd2378d21f4cca2a2136876864e8c208ce6955cc1c6a0ed2a518862e8ae056081d44cfa167124d21829b4782677664e9a05343b05c25ee5b324e5a32de2e84e0f8797f4835953c3e9d17ba08821ff487e7e95831c7ca4070e5fc2cc974ac1d3af9033f96eef01963d7c739455df8c08d0f36bc7f1cb48133480a1a0006182bf833ce202938b092d0450f1c4d1125d3ee61ee781ef8ada91d994ca45e7f076eccc1cf46ed1c3af0d63cb4289194a6e1d1450e3cf1685dd7a136a4fc2e70e09d9aa5930f1a363be716aee60ac164836bc4ead8c24d53e757579f24696ae1ab7f90d523adaecc185a2c91414e6a2e330bd7e358c2a7befa238f220b276b5af228078985779f37c4b01ae1810516ba7be8b22185fd0a377d882977fed815fee5a9dc1f5f7f66785be1a6cc6178c946787858e19ac428ddea51ab70c6729cb4238baaf0acdac7645294efe854781f6b4afec993736451e1876055e359abc325ef145e44184b1d6ddd9d318593e655227914be3ea570263ad5d4c49ab1a491c20b51a36ee4e3d99469149ebd7475850d89c28d93578f8376044b9b5078dde331f38590223e9838e002147e664c72e73996978871f109d73a5a7dfe62f8b9983e02179e705cebb38ac7e1514e7d275c0f39790739c7548f0e27bcd451660a396c92df66137e6de8f71c246af64f6bc24f1bc3db853467c2b56cc127ad0313fe5fb97d071506f52c908106ca7816b08094d1012e2ee17aba9aeef49e6a730e4b389d51a4c73dc7e3279706b8a884e31b2c9d471f917146186298e102166007b8a0841f475dfe793a9c7b749531092756cabbfc45626ca151391792f036540e5a425aa443978b48b819c5e3bbf0e61b3984845fd3614a1edb7698b57a84ff762947ee0ee2c211f8d77ab4a7b86884133376850837000304b85b00178cf03ce610362507ad983e30c0a003178bf0e350e3718ebfb28cda27b85084d7a6319b99bcdbe5342127b8488463967924a79c64099925b840841f2babad63d824c1c521bc0b6d1b1ee591cdc87261085fa3736c92257b6667e1c045217c5f9f681d965680a2810b427893831c73e79acdc0c520dccc1d79d8a9d7334178b3a5113da7856c9523107e2a4dad491e20dc52bf9833765ce6e03f78afe12b9ae31c8d6ac70f6e8b75b0da4c1fda07177d58ad32cc7bec52d907b8e083971dfb7bebbc3c0a6930c0e0ca04177b70cc524ea9d4cd6d43940b3d7897c2870d15157b4b9307378b771c8be40f1de588afc0051efce8debe233476b0b9ac021777f06e2d5bf4cd15f35a90c7c0851dbccd1937ed41f8ba8f2a14b8a883eb51cc1b19a7392c3a11704107e6a3f70e2ee6e0d75ffe38e778e2420ece79e841f8c03ab0f9302ee2e0fbcbcc048bb269b97602177070abc3ce21875025fd31f7064f3c984c31d26ef0b3c5fa3bee0f176df03c8814573168ea103d5cb0c1cf41a61c5a697298cd70b10627450a39667a9834393a0b5ca8c1f3682c4f66e9709106274fe8d8680d8fc66218e0020d7eea731b8f91b56db23070710647739c34757ce972f81431b6cce0475e9fed3604eb9443710b5c94618fe9b52224830c313e05670cb33e704106bf53481d8696e8ebf08bb195c9052ec6e075ac21bd3d84caa376810b31b896fde31cc52593467201629c314607cc48410a124080051080000b20c01931f8335640c60208b0800510200304d0c00408e048f52f1823b90883ffe7e9fb73b6e5b0138619a30c167019643440010458c018655c2005640c5fc19b11860a4c0c8a0b30f8315295437d44598e2f381726c7d51df55dacbde05ccc77a72fb975f1e8825f1eb947073765e13ab8e05b4b8e739a756cc18f7cd391e4554b62d1829b56e917ded2461f3f0b7e501922741e1f0bae7d0ecb7feb7e952b78ddc1e6cea7395670d4db83a8c70a59f361077051057f55724e619d1d9d395470c345b1f2b699ed28650aae76109682932e74ace46d1f476f89827392ed63b2fa4c9b2c50702da264bb8f1d8429cb13fccf93a1f69672187338c1db60d29f69a19ef24d70d35a6f757433c1f719f1c8435fc4b44b70346272a89c27b8a84a703b799ab0312649f053368b30f113bc3d8204a734794c9d3dd53f468ee0d4655f0cad1123f891879bbde7dbf01229829b27e5cf692385334d13c1dbd851cc933e7b90b91e82e7a1728a79d090b2d44270aeae2fe32d69c89f20f8f241cae3d00204efbfa388c935c4a4f40fbc580d2e73397fb9cc3e70d625f5a50f3afc9cdc036724f7678fda36b9c83cf0d73b70c9a9e21df8229552dc4435445407ced69fc5c758cdc1b73970523e9e90a3d92a8fc3050efc0b714dc912bd85ff81e59042b63a0aa7dac28b97187712d3ffe3b4167eda0c29c7a4ce19734a0b3f6bc887e5289d852cdc70ae96a3af1cd84c1a0b5faa3bff65a6b9ef13167e4e5e1fc4a444bb395fe18693d8e639b694344e573841bd358d5f67b1146c851f4b060ffee642d00ab2c2d94a7641ab3d478be22afcdf943326b190e632a70a3798c9c999784c725f2a7c8b61d2bc735e51e14c4a65a9ad7d53ea53381a8207b9a333bf7a4de1a6acb9490cbd9ad52b8537c136949a87612c4d0a27e2633b7ca4bc63148ec61cb3bde1d6412c0a3f65adecb5fb963843e1867590183771d59681c21ff1f1305bb693e0f1093f8ef1f71ced092fc5146b1224b77d02d0093ff048963ef05862ccc5893302b0097ff3782ed17ce943ac09274bc708b1322b4f32136ec9e58d09db794bc4849349b3a6aa14dab1750937d43f8e973a8958c27b171b3fcd66314255093f7eb625dc336dc750c20fd3c2674789da379984db9fe597d1b3e68b25e17ad429c94fbc338991f03f96c96cefffb11089b1a595027290705322acdd58d0907a92017884b79e03f310fbca1651001ce1e4ce93151bb33404a0114e85b3cae492257b8e9d21008cf0e38d39da7acd09c0229c8cd4f19b47693704401184d4a8d398245a262202900867eaa67dd3b804584018190044f8d1a76217f3188043381ed5849fb05ce1210086f0b4437e2244d9184b3f04a010cccb5ba5c684d74ca45ba9745f85ee8ab9c928110042f8b1c478c88e323ba2c720bc081ed5c6b84710cee4f40c9dbba335ad8170ae2f5a8aeea0efe30484573669da8374fcc10db78868ae397cbaef0747b34ab8e9504135c7e983173a08121ea60f3dc761f8e0c7bd21ccd7781c7396f7e07ba609b54c550f7e9f6a6d8dc6ca95ca839b29789aa40d21aa9478f065ed56be3f87c8aee920007770366f5cd87c07c00ebe791cf47647e126a55597c810803a3897fc835af4f3944d544100e8e055968d9837e68e3643023007cf82dfb4cd95f647b6185b888400c8c10feaf3558e2dbd32ff626c350134e080c38c3128007170c5df3b0839d0981cc2626c5d1140030ea84504000e6e6e93cd5c952c94bd6ff0f3062f2df3f84ff7b9c189413586c89bacb276b4c15fdf3c9a266f8c1cf96cf0e527a4d0fc231ad3b206bf2a741ce4f5df9cb2ac06d2cb379c06ff52565353e7c03d7fa0c10929c792c9357cf0e99dc11b6db9e920d20597ab87d67a646677da4f6af2ca1ab2c104bf5b73d02b6126bdb665bc1965900b58fd3105d858829fef8350a6e94b9259c818c38612bc1c7beacaa1526c24c1b989cf082a3fe75322c1fff86d24af68c56adbc611bc0e53248f94d21a5d2423f8d96b62a247a22a49421b457024e4f8c27f458bf4171b4470337db486346a7f1bdab131045f2d85b6ac3e0a880aa8d286104ecf88f16caf958ab3120fbe0391f4311b41f026cd3d5b2eedec9606810d20f861471a43b77d6cfcc02bf16c123cf81c7f4e59800d1f38593ee5515f5a8c2d32c630b3d10347fbfb42d6e492f3983c021b3cf0b24dc207e13e5c671c868d1df891781052941c43d810611b3af04296fc8b844659cb88b1b58231ca38230338011b39f0e394980927de79e6df73800d1cf8611aa3ee7357475416f815584003356ee18d5d26ad9a94557a5eb485ebb16a760d13d297b60dd4a885ff71986287e654c122430befa2872e2161663687d49885b7a515e52de4d490859f352695d5d2aa62522316ae6d57cc9148d6bd5f03168b59a87c16244c6abcc24b0debe13bf2f7ce71410abe0c162cd770859faa9ed6dc3abacdae185b61c0e057a0a5801aadf0cb6b72c7eb41aa8e6231ce20174031ce202c40199c61588315877a8badbb5b88a7d40761fe63f80c720130c010e38c5c85d31da7a4fc21988d9bcd821aaaf0fecdc247993a47972e158ed89b4f4b8a0a377fdedc1e655596b3a77025e7f130fc3e3e660f05354ce185db0ea23eb255b1bc13d428c521849a91ec20250564905152508314be04f1701a72a94c79d41885ef1f460d26a127cdfa3544e18547217914634e8d502051611e296eda9bc33c63a7cc4cc5d82a838c068001860aca20e300030c50b87e19a173a8c85e66d7f884ebd9ebce62637b903a659011c656195f811a9e70635467359f3be1494dca1c326798ef38e1779a2fb5d0c1d2d57c98e18232c230630c2e335230c6192e38408d4df85913249ae638827bf0f10cd08417af12c3bb43658f7a199c719970ad42e6b85286ecde6f0d4cf8729b56cd3d85d4d65cc291b99690c39e1471d7125edde4f08d3d55c2e9cd181e445fbf0829258c8ab68cfa50116b91ab0f43a87faff73441ac31092f3a7cf491d7520cc9918477e9c34a8da423e1e510ee626378486d6148386d39e44d9762ea0c3fc2e9186ca4b2c6432bd5115ece71a68be54952c4ae4623bc0d27f1f12d295af28c70d2866f740b32e699d658841d9175aa361e45f89f2f85ee502b11dec6b668672f21dfdb88704453e6de505be1aed53884e317dd112a326e29437857175b65a3596b570ae18f786873551a21f8d80cb19bd1f44889eb4e19d669c341f81f53e7cf51e5b5f6e808d4108493392d7ca0aef5a1e76a04c2cfa176d22cdf6f180588ac64cb426a32e22a33daee42c4bc3ebcc7b2c61ffc13f31cdc56b2cd39c60f5ea52c1b3b6977fc9b453b630563907101adaaabd10727c6fcef591d6e2a830908420d3e38ea5f92de3cf2941da900b3021998910030c03839d4d8832f7f36c93ca21a7a70226896584b39fd84dd831a7970cdae354505ed68e4c7835b21ad5531e53b38d9962e781063a96559c30eae7f14d5e3d59e1ce751073f4ad024f55f19a434745b3a8b22e6cec19b10ee83741da59bc7cac1b1f9a0ad2b1707c7ea2553ac1c0787a4e53daa4bb3adb2d42d844acb99e3bca3f46ff03ea276ea0935dce07d494471918e25e67a0a35dae0e794daa5a5720ec1552ad460831b729a54b9653b1ef5b750630dce57455f657faf670a036aa8c18f51d63c3efee0b27d8d34b812f35c8c07cb71f55c030d5e900f75139652962545438d33b821a4ea106f99834c1f64d43083f7e957ee3de33ed971c85800d7a146195c1bb5ed6c2149a407bda10619ca1e527bc8191c8305240c15984160c014a83106b752badc586b35c4e0c68e3a3b72d8ca6ff9aa11065f536ffa345536967e302c33615d3212f7713755697e5ed9b9a3865af50b7e1cacc40e3aea053fa5cdb0aa31637550a30bc99f8459a688cc39a8c10567c6ba6386479ed8ac0d6a6cc1cf3f6de176359eea3da38616fccebed661dc6b7759b2e0987dca72f5090635b0e0a86994689f109e79bb821bfd33ac9a6a6a58c1931c8794b975d4ea780d54c14f92a6b73c76d4a0421b2259aaf3b04f428d29185276874819e96422d490821762de1e4ba714c0801a51707cee82977509053f86edd5d2ee094ef020944568e7386dec043f8e15a1a3df8c0bcb36c1ffeed43f1629a9e5a84c6b30c18f3cc418c25d78095eb0cdebde7da1421ec5d80a839c91823f493594e0a624ee2e217860000c304c195c2309de845df977d89896836a20c1894c173b8e7304bfbc2787152c4731d9c7085e96dc13e1ee7637972278a1616b2de657d9112278b31ec548be1891243a047f42944b17ae0ac1e90a611a3bf7795b32084e9bc71e5ffed6ea0e81e0879c86cf1f450a23f98117cd73fc294ae7032f851cab56eb1eb8ee216bf4bc9525a78c07ae9fc9c44b6bca61d9819333e50d1d6d687f68e9c09b2977b3b3f4f48bca3e5023076e76209ed52e24cd81470d1cb8d129f269f8e87b2e7f0b2707f2a926958393f56de1c710e3530b3f753afb10738e1291166e9edc1d6d5acd2c5ce930d77d8745166e97e48e34ab120befb2751c2bd64fd42c81852326c927a5b70f197e855bb13b24658fbcaeb62bfc74b5699d3963f850b7c2738ff249dbc1acf0357b2ef896460f9757e158f2cc9c3e7055e1c774b0091f7f9ea8662a7cc9a89946421449a14485d39b2782dd079ed28ca7f0c27a8e825bfad89e4553f8fd99952f47e54a31570a6f4366b9604126852bf7692a75bc298875a370530ef3471f6dca1d96260abf23597fe9c80a859f25b468cce6dedd83c2b3f0fd71e43433b33fe184f41fa9ba4a343ce17d501ac246a64e97121a9df0b3e7fdbaddeaf347d3e00469f27a982112a0b10957ade3f0b8eb3acc9a86266864c2cdc1c496ec71473930a0d4000d4c3c6f31f3078d4be829a63bed3888f3ce023016342c31071a95f03b0c21ade63d0cb1694a7829071d73c797f25694694cc2f74052eaa808cb5b1f4e040d4978e2a163598e7b31160c308a093422e16687e1c2e598555247291a90f073b0f9228f64887fd023dcd614f6615b88cb36b93ed07084d3397bec20c2e4276c8df0253d69f0b0a753ce25231cb5ccb8cf39b566f88b7073149dcd6db3254b570183030d45f879a39798e43091361a89f02f58a6b4d13285bc160d44381dac47b12724b7981931d0388497e1c3dc99b6e3653b0ce149f0cfe1734af956430ae1a42f95ee1a9106219cb4f59fa3ddd58436082f3e4514845b5ddda1e6489b881c8d40f89f83570eb34d1b060d401ca2778879b3792e504118ef8216d0f8832f16afb6c957d25dc6a915d0f0836fe781f726bfea20b43ef896e3983adea26434f8e064bca4d4ca20712ba680c61edc18357b649d77b2b0e8c19f8f267c1c570e3378e4c1098f23c3a4c5f0eb260d3cf869a9d6912d2a56a7185b7770a346d80c2e727139e60434ece078347fdd61d8d668769030caf833740c1a75702e5c2a89aaed2c2927c656197fc60a9e0efe5dc851cebe9ce38aeda0310757b53b4c0ead392e4d1a72b836eb5de4d6d35b4ce3a2dfaeaa2a2ae6cc018d38f4f9536fce146d33d20e0d38f8b32e96332b5bee99dee0a876e5094f1d76574ac30d7e0e5bf31c2e34c11866d8cd81461b9c8be62aad3de973c4d06083e72b95e1520eaec1b1e81da4b19443ca31a50d34d4e089e5b092c93143dbef061a69702e57f4e82c7858df281abcadf0e1cc2684cf351d8d3338656a17b67bd682e44cc00cdec7f84b159ee1ff7194c18f2185909aae72061a64703444670e2a63684999061a63f0d306f1ac911e7d90927966a021062f761c4f57aa0e0d2d41038d303817cc7258193a6880c1d13c295372d65f707bb52fe36254d707312ed0f0821bfc4a3d5233ed281e5d7093680c5d1552bf64070d2eb839b887d5e0317498533240630b64cb2eb1a256df63a0a1052f07a9262e9f26028d2cb821a846897196b5659e000d2cf876f239d0caac1ed19116685cc18916a29f87f2c8a8c04d818615fc481fe4e83b96183c4327800626400029d0a882e7b3d531845a8b116343a04105bf2378cc81c60acf6102028d29f83db1b5cef36736bf8440430afe8b0719cba222997944c1cf1f59318764e4141d28f8618896cd12121a4f70734fde905e3d884e89ba808613bca81e3d1dc77b10420c8d26f8d95dac364b3ac6e22fd0608293ddd63f0ed36309fe250bcfc1d9848ecb43094e16719f354bd3213a92e078103d0c21765162f491e064f24d51c5430937cf11dc4a211d958731467052e542b3e4384b074b119c24e92fbb5f8c878810c1f7b794cc53d4437033c79aabacf6659115822b31237dac9cbeaa09821f5ff6112b39cdbe81e069746a8d4152494eeb0f9ceb605369eca0b93ef5819f2aa668d5f1d8859d3d702e759453e4aa2429973c7033c4233c4777ea0fdc811ff4998ba7909f8e431d942a67f672e07b6ce6d19626a6974603076e92fee80315edb49aba85a3e51f440f25fa58c7166ebc85ba2c3f3363710fbaa885f3231b62d2d2b07e1a5ab8a1628ee9510804fa075dccc20f7fc93654f2f7f25a168ec45691eeac21759074110b7ffed4273b76df54bf0c07030c52c677010b373dc6d439d4d09ea1d2c52bbc13d7ced6e8e10a37749c4dd383dc2ebf69855ba73671f1352bbcce5335ff4172155ef434122ca3df48c85815ae6dec283c974a089768c056d0452a9c9082fc9b7a45cbb44185f3a13f96495a2eff9ec251cb59613a4e2d996d0ab7e663beb273f8f0d15238d1963247467b099223851f5bf8c8a1a310267246e197e6b3efe4611d9e89c249f181a7c47b0c998422ca7b4a64a8458c115e48218957f8c71b528bf0378878a8b8b4084b11fe8438df98a88ef389705d3b484c163e0c394410e15998930f9b92248dc8219cf4d28ec3fa30841b215f070f973a4447219c2cb351dd7a42382292a31c49e8b9cb0cc249167294d63e27bf8920fcd8390a117290c3c41d083789871d992db1260a08afc2570ecbdee15afec1cb8c92e9364927393fb81d84af1c84f496d3d787c32ceb9c42f4f8e0d49d8bc7ce6016de8313ab6342f8ad0f9ae9c16fcd31a4ac2c0f8e6b6644fc6b8da71a0f5e957bb81a8f3bb829729833468e1d3c7915ed71ff20544c1d5c89390eabb359ab6ce8e0ffd685f00e42c7393307d725c494ee9e6388ac1cbc4bfd8166b5fca9de387839ec0e9be34bbfeac2c1a90ceb617491ed70dfe0c672add14af37daa1bbcb0792d66a40d6e58ef7b6dcbf923d9e0d455697c079b3c2ad7e0e438af77488e5139d4e09c87f19525f2771c4c835f1e3d6ac76bbd39d0e057280f420e3f315fcee0679648a3da99c18db0d9255365193c49fe75f51993c189f2d3c1e39c3d3dc6e07bd8982667983b8b183ced8cf0b151eeeb0f83ab61738c48b339730a189cb77ca972660d19a25f7072b67c95ba3b5528bde086af146f1d76aba60b7e247fd6e98266bf70c1bfcd1da5becf9d43710b7e70a3217f471e243d2d3829ca2ca9240be51c4434aa5c58f0b6cdb248ca15fc9842b3a369ade08516cd31345305b7da3ab2e81eb33f0a15bcb38a141f85e8305ba6e0ca2409b1c3889d7f22056fc37b8bc734a22912053762b6ca81ad50f073b09fe4edc195974ff046baf387b835cda113bcf114fbac72f6b45d13fcb6892fad0813fc9839d4b6492dc1cd88a16426e55357aa043f8ca1533eae8ffa264d829b7ee31db7d9078f14094e4eb1a3cade71043f764819fe2fa4cf792378533dad52a572f12982eb1a96952c44702be70d391e49922e64084ed218657216de6f2204ef6724a98731a53a49101ce9ca689eb2e5df08108c9855738edcfe811b93b53ff4853c37fbc0d31c84f330074ddab13d7065d39a5abe0d0fe1816b351b21d91df8d7aea1eef3474ca20edc4eadf9274b01e4c08994ff24f8898a6a29001c385be9f739c7d44e2b6fe15ba4b0cdeec186b4d2164e853ef1f14a39da2a6be15cbc5ce8c083165e948914f9e4ebb463166ef694520e92a93dea90851f76fd31529c98e763e15fced159924d2acfc3c21fbb0ffac7535a735ee15bbada9ce963c6deb8c2954e1d59e7b2ca7eb7c28fca397a489a156eecafff1ca7bb4ef22abcec090d49abbd93aa0a37489b57eee47292a6c20def20c71ff96fe71315ce895fec146e4796fbf3473fe54134853f3e1e580c0f3b3387a570225745370f22e7d84252b86d6ab964b35138e993e7f84ffbb05f145e14bfcdceec9867130a5f3efabda5f0a1a1018593d324e6f8a7d25be6137ea86429bd3bca31da9ef04b7367ebbe134e466e890f54db5de6841b52a99a8c5d6cbd0947ea4d634e9fdd2dd584bfd271ceecc0cb846ba9dad91b1f26fc28751c9792624509dd253cf38e723db329f38725bc8f7d5a3d22fd9fa3127ea7fc516fc943778e2f43224810fb9884376d775d963662f992f0839968d2e18984ff31886aec8fe4810512cee7601f8670ed223fc28f19a37cc6791859e208afcb6ee2c3758e8d34c28959c9dec63db69e117e241f72e5ca399ada8b70435eb7d7ec9339d58af03f9d97ade5add89108ff4a36a5d8d8136546841ba53b42b828cd8a0fe1068fb5e7225c8db58670b254f650e999294721fc38c698d19295684709e17f78f4e8cfdc37ad83f0e338cefcd5118413a6dcc2b8f97a9b81f04d7e6343c800b1e51c39f3e20face5f48e79cf0f7ec5e46f9f36a50f6eb9c7a7249152c4ccf8e0c44bb2f6e8b23d381ef3a50ea969e371e8c1514b1e6242a50ee19107df3c739c79f27870e4d723447adc15f93b3831774a8fb3b7839b56e93de7c8d1cba50e4e08974c8f363ef0990e5ef20d89721e84e4780e7e902f47ed37ef105939389335c47aa271f0536e8d315364b40ae1e0a74ed2ee1f79b2bf37382155100f65565d9d1b9cca8eeae01ab5c1adb41041da2c89858a0dde8f898a048f3578e1aed681c7281d7b35f87134b15250f118ba92062fcdcda4ceb0a9d1468377173c0ec931e6ca195c0f9289c49609d9b019dcf45179dc1e7d56cae0d9780897e092c18f3647da6138c7e077aa89da4a92464531f86a1ead434d4cff716170bc2a759072728b1b0c7eb6f14c4b8bc8cebee045929822dbbdbc2c2f78f371bc214c4d5e74c10b1bcadcfffd523adb0b2ef88147c719f3b5a4e89ba00c320ebbb2de821fe5ea0da1038bc90b2db89ee9d34b5bf0380c95f4220bae8460b379d5cec482f729c7e595535bb94610f7e0c515bc4e6963e43847c10004b05e70658c11060bccc0f2c20adebf870e226d2ddd442faae0648f2b2267e68d123e15fc4aed392245efec1c998257a9f5516d21e630a4a5e0648d395c5a999adf4b062fa2e0894a0729479adfe35a025e40c1cd51fc73f41a09fbce8b27b8124b2cd705d7095ea84e5f09a392efc5f2a209ce7b2459f3d9115ea3185b676394710632c14bda41be985e8b9cb304ff2248fa918a194a703a6448d91cabbba431094edc040969c4c637077981845bc264f1142a1ff0e2087e0c154cfe5390cdc9c30b2338e9354c642f4f07bc288273151dfda5d13c3124811744f0dbcb72a8f1ad8c035e0c012fc92969f3420847878a9aabb559060db38d75e97be34510bc8f24bec25d6ebd00829fd5bf235c8ab9ac9e2280061cc002154040060a30c30c7201191430c0b082173f703aa634d9b57931b65017025ef8c0cd646b124453b5e6350664c0e00c8e80173d703aecae5193e0e6bf8ab1a5693cf0dac2ab3665483689152d05bcd8819fe6a2c22dbd86ccc15ee8c0df889499c34fcaab85012f7290cfdf47b90dab185b78c70b1c3899993922e70561605dc0c62ddc0ab3317b248509a9d9d2800d5b78d56eb6216466d4c2bb1c15468375b88c9f164e872495d28376ab9466e1865493f3d68821d5cac2fb50fa5652f82ee92c16c5749782ba79b80d58783e9ef29647751ce67d85e739ea8e4da3ba8fe4708557b63139998769859bc26bb899c588b964856b2954a848491fa78957e188a7e9388e1252a82805291843026080a10a00d848051d6e19ef1ed56a716a559aac2269b458cb508c4d5434b1a3b6626ca9404fe1cffda64806ef98a09671609bc2f14eef402d4b7a2e2d60a3147eec71f0165db367ac20023648e1fd8bb7668a0791331746182b28c3c818230c163cc0c628bc181dc253cc8d07294714de5bce083ee2397cbe6d84c2c95831d9b0f8cdca01851f17394a540a0d17d7277c491355c248cc9b2ce7093f6ef51c894a6c0d1175c28b39426984ef1063856c70c293742b091b34b29f658ce3400834a00160801186d9d88457671e54c8c7d9d0849b3f34875566c9842252deb61eefe66d212a980761e2a042c5186b25535ea297ccd2d09637b535d70c1b96263ecbb77b58c22bd79462e80aa61a5d25fc641eb5fbaf74eaae6d50c20b0bf9b36afe806063129e54a749a8a412dd912609ef43f540d3078b8d48f8f2edb9e3e8524ab90609377c2789aa122d9464361ee1c9c59628b11929c56cc3116e7a8ec127b26a841f24acfa849b30420de2710a9b356363114d5776b7cbc77dd9b95549d7e738ca6c28c2736d8f6692c354f7f2fc55036c24c27bdff490aea11e86ba39b081084f4d3d4c125353bffa61d4217cd3a82d1e3954486e1ac2b5febb30e9d042386aeb5a9d24feef7c4e33cc602d800d42389d4a563388bac72c18609c415450061947186698f165c880d38c5b1b83f02be483f4d06a2d875e104696de50b9eb04c2ed4e8fd131c60e3e870171c4884c69447d349d21c7c61f9c6c96e5df638d1fdca4ea8107dfbf39e663a30f9e4799b726bae7831fd887b61c7eb2e435f7e07b1c67e728abdfa59c62430f7e77f0c19df6753429c73bb091073f65f62821fbe3cbc0810d3cb896e9132248f6a4349e31c8e080aac0c61d881811ca6af20718607c1961d4976107275c7f1039de2018606c1845cc38e6f30636eae047d5e77118eadf2ffb7470b2fc848a313104d89883e3514e2ae271e6aceecbc18f73a4192c498e33a79c38b8924388dce790b2276b030e779ca84adc9bcc9587974d47fddb51f2b5020b051b6f7023e5d198d62736dce05498945e3e2e8cf499820c8001c68651669c41460acc601ed86883df31acfa47ba8fe3726cf0375485c860af8d3598293b0eca27cc856c6ae852ac652c53d4ad42e49eecae9c7d1b69f083e59444345aaa766da0c1ad7022ffb15ba58bfe0c864b978cdb55847bab8494bd956206c7eea358390889417ec118e718d06cf529282e20e33456c04619fcace963588b1d07793a639432fe02608001c692c1cb0e4290df943f0af9b03106dfaa4f83fd7aa8cc121b62f02eddcc896c481f87d808831f87a6da933a0e45f20718fcd3a8f80b6e7f209211112f7819c3721c1f644739d205af7e2cf484dcc185d2554c5ca5e4d42da5d9c77e1dd5d982671a9ed5a31c0c061851b0a105dfc31c73a4bbcbdf1e08061861d838650c7d1716dc38d39451cc64335a0ed8b882e3ff35dbea3f72bdb161055f2a5c7d5d4a110a6c54c1cf15aa3deeb052cc41c68751061955547053df848d218d09868d2978d99384d4df1b392c062f461936a4e0a41c65ea0e19a2a568cd2883b0a0c7b01105dfa3d4126b2eacccba5a810d2838773147b79ef3d0de330134e000f2187833460a32000618877c1962d87882dbb12146af34da7082f3b19f04ebdfba0b898d26a055d6650420730101cc6082e351e84df31a6a1e6707b004a7377ed2afb3c3a465004a70636968caf5f17f48ae090690043f57796c9f599a150680042f4cce410cf748ed1db2011cc18d9a7f3a4372e87892626cb14005e6cab0c00806608424df7747e62531032802ed12437f94a5830c2bb506032042dda1f6764816cb9e318021f86a152386701af1174ec10084e06f8ed954a35d96af8f20789a4dd383a5dfa5f681e0bc59bacde49b0a06f0832bea83e4d8f9a22fc707d9ac5596a64a5d87498f372479cb9f29c1007ae065799062cd253db0200f1cdbce73e125b4fba606b0033f4e9ad35523f5a68b0ebccdeb296bfce7fbc80072e074bbf484d7f898ed0070e0751052ba5cc1e3166e8b66872d9c8e3eb00b627697f9af85dfe9c46333f3b4f063bc2bcc88a7fae0cfc20d61b639b6f0b9ee230b57462b3e53675bb5c4c24923c95d3c082cbc9eceb166e3657cf20a5f3b0e1f7cf814b2ddaef04a365bfe8f83b095b6c259b118b5c364852729e4284c5a93dbe05985e329580a612b1ef77554e1f8cc06afeba4c24de371aca221b6dc6f50e14721c64c9bb3f8dbc79cc2ede052e68ea318537897b244f44b7f26cf94c295bbaacdb6395b4449e187ca54913a0e9398965138e59f51734e1ef74716517841c387d9710efd1f24144e8ebbaad3d20414aea7785f781c32934c3ee15c88c827617ac28bb1ec22843c9d93453ae1c590a98f439a04cf73c2d11c868a7e1bb375bc09a7ccb7d3a2d99a70becdf3a59423134ef43807bbca21a4f460c28d9d1e6b46c7b674b9841f152679b2ca5f58c20f9952daa4e42754fc4525bcb7b9d71c5296e489bfa004ee9f59ade1d949385721d2e38b5949f81f8d5a85b01069341b0937446ea6a43da5cb2c24fc58525996e41cf866f711ae7d94cd93a73dee381ce1a435af1c3d62b4e4a18df07dedc2c5e86084973f47d172f068d2e74578d93bca7110219ee92ac291489b3f8ea276863211be86e793cd41f00e9322c2492107971e5fa7a17208ff03f328bbd5107e90ec1ca5690be1c7710e1661f24f0e427831246b10bec7610e6e53ca2f04e1c4ce1b3a5ec9379bc62f02e17ac6fc9d83cc1eb2865f00c29138f5208779e623c77df1073fe4e03bc895fac11f4b4935fb93f445fae05859844c9bdd52c8f2818c9a1a1963720f4e889c3dbeb5b27ca21e1c6bf9e8e3cb83771e758707dfb2e5ae8aa1bd8317694d32e4628e52d6ece07f86f41ea705af8b591d3cc91c937a881dca07191d7c5f0fdb3c152ea25f73f03d8d76b8ff208df82507dfd2e2eac39c61b25271703a8e67728e0c0f9a2e3838bed59d34a5dfe05afe0fb7922b468b1b3c8bda5b31fba266ad0dbea7c674c694d7de2a3638e153c75a9664ad3fd6e06f0e21bd4f44ac9c5783373149d37fbe75cfa6c1f5a8a9534c8c2987221a5cb954a9d9c2bf5a8abe384319735c4aa92116861963b4de176620c7395c487a4dd66895a1080b9390b195ea08190f1e39f4e45e1355972fc8e0b5a95f888885f1c518fc286d3a9fedef14e1c343f8420c6ea7741d4c724fcde67e11862606993bf005184e8b1973103ecd79f08b2f7026b2355ab61a1d9b591b39a7cf99f482d71da592d4c4c58c1b7cd1054ff2b5cb854b45463e56adca63db701e15dc82671662ca2146f960bdf285165ce90d972e3177c9c64df8220b5e4c7b93580d356b1e58705cfeb455d3927bc95f5cc1938e0fe3ff245f58c18fa393d68cafef8e48155ccd418a091b53e5c8325458d4b66e5eebee55b44d7de3ce829984c9d1640a8e4a1e57498b78075f48c1cfb2d6a89cea35d47c904146191738c820a30c162ce18b28381d562873ef6c1fb58782976ce46e62f204276788293e4a9346744ef03d186d0fd2d5d4b9e78b26f869425f6887d9327c30a1ad0b5b97f80a0b9792efe8a116b20427edbf3aa6cbfce00b25f8eeb14990141eb66a4e12fcb035e4c3c6c57f982cc31748703b48962cd2225c4ce12f8ee0b94dd0f860ab4345fc85119c90a23d3ab79473ce413e05c5822f8a5075d7cbc64aa6588958d5799484cdc49c65436cf882085ee84d99ddf19dc4241211be1882973cc6345136ac2ae4bf0c16909171ac000227056388b1805ec01742b0244ff020a2c7903d667c1184bbcadce3db355ca5ebe4bd73ea0808d9ac46e8d4af267ef103352dd2abebec2233ab5dd3bf860d91d57e1fb859b36bc518930df8a2077e201f7dec1593c7aacb173c70fcabd2bdf6a6394fbfd8811f59423a59f1cb11db173a48ac63c335b2322ce3224db4b72df38b1cb8a1de2f3eae9828d97d8103a78324f1586d4a2a57376eb1865689a55474d59ca967895631b9791879dd1bb6703c5c8e31ccbcc71353376ae15b8c8b10df8e2c5ac70d5a38f9a256925b6d8c508301060f6eccc28fdeadadc205cff074df90853fb7d937397914b1fa462cbc9164d121e59fe9ef61e15f5bda089bec2ea668fc0aaf523e0d293629db55127075460cfe1670c3154eda4811f7b0e3462b8c6ad1aa13938e98e7a536345678d92b36c9a791378d29838c30388cab0adc58853f79a53afe94230dfeae80941186095630c60a56405860461866bc09ce877190a713c50d5578e9dbe7e253e23cceb080052e48859f3e458f4ad559de260f03325260a63750e1791c1dd779ce8cb2fbc629fc50b2a3d1508be621bd610a5fec52d88a991c13fe52b8d59d42064de906297ccf68295669d60a39e8821ba370f25d0e724b8a549abdbf210a272b4aaa7bc3f974e44628fc544b27ef9d92b8454c7003147e147ce342232b7dd8def884a7b2f92cc7c41c6b466e78c2e9f9d88d4ee4931e689ae67ed5500a6e706297bbceaeb62acb9a90ea28df7aac594dfa4a6e6cc28f551ea5c7329fe49726fcedd20cc16cb31b99f0c31c91ef36dcc0846f2184b58c35b192b715c0808c15a060051bb88457174246ccd239c30d4bd839b65fc118452be17ae451e87083126ece68dff3d34dc2fbf659c7b8210937ea53b8f5794c9f236f063722e1788e33f1e8422d3a8e0d6e40c2bbfc51df4d4fea20d6a68f70fe3e0e9529774ccf961b8e704e4342774a210d6e34c2ad08b973c458f2c99e1105c62dc20fcacbeed332ce60c10d45f849bbf379846d8f27cc8d21b89108b7a3cfe50d5b561261447c1962deec7c1dc2edce7cf2390a1d2e2543b8eee9a71ec275546a1c46a9136e14c2ff3092e74acde18216bc0b5ae028302fc6026e10e28ec1b55368ac987163108e5908cd18423e8b8a81168493d26415b5ff46207c4b6e29a67828b1c6bc32ce60c1dd0084d7da4166dfcca179d8100237fee0fd24cfe74166ced251fce05fcaaf51bc25544eec832bff91d2c71bd9617c37f8e068e71e0f32cca3f38e1b7bf023bf8e8ed98e1b7af0c3bce6c9e2c32da6fe461efcca20a341b286e4d0dcc08363913f8e37a6d364b937eee0a498917e3a6f07bfbffc53073b0eb1d364ccba41073f664d215434871af56fccc17f15cb51f6402d5d856fc8c1b3ec1fe683280f128337e2e0e414f992ab8cfaf476030e5e4c21db4b8a7c9d66fbc08d37783592ed3ffebfadd0bac1cd717cd21d5d42c6a50dde6a754c1d8b76830d968aac4447457648bd8aa96d750851dd35d61b6b70ae7e52bae4a106a72fdfc4ca173647471a7c7b8dc1a39985923c1a9c093241247458ff943378c12cbc57e71c4ed26670824f999ddc4719dc942b3ec57d3be8fa2083d721e42cc5e41f72f818833f97daa3d2d7ede6430c7eb84e7533f2110627fffcbc79eca0347a80c18de077d94304518b1d5ff0ba3a0533bffcded7e105ffa3d0a13f3cf0d0e8e8821f6694f5bf681b97830b9eb94b65d8589ef66fc1f1918e32e61c728e395a70b225f885e920eb62b2e065c9152c789243edf4ca21a7985cc1ef9463d3983a6bf6d40afe4de4c9d1a7e419c52a78b131e357c36b50910a7e76106345f4a6e09da7ac49fd72ea2852f026e5682b485970cfa2e0e7383ba414cbfa721414fcf7101f783cc1cf74611ddff44e702a45098f52fa26782147e4184d153d929e09dec76c8e616a2d5dc712bc943ffdbf42f7a65e09dec9948bf85852cb4982631a4473146c24f86b7fd16112abed8fe0ca8711e627aacdc746f03e32979accf8582e825bafd61dc6fc1ec725111ccf23f2d291835a7008de668f2b86905e720bc18b29e52e8f2d17d50b827f9e6a65cc629c66407024c71ee7b7fb4afd0ffcdc13620afdd183b97ce0b544c8e9816f61512ee483074eb5dd84d4f91db831739c5bc3d781d371645ddb86d69c1cf8e539e68e32e7060edc980fb25c0ed272ccdcc257d3f2b87c23f729b6705eddb5a254768e2db5f0e62fa57f18ebce675a78e95ae32ded2c1cb5f3348f6763bb2a0b6f3692496c32164e8a64cd07a38185973a550a3144e6155e988f66cb8397aa0f57f849f247f9c6d30a2756fa9c32327c3e58e17898a27be668f7f8b20a2f72e0f93d54e1e58999ca369d0ab762ccd162d9a8f02acab4848d72f2c1a770a2e3cd29fc8c7d4ce1fc67f478ed3b53524be16d47e9c53e2ef33149e1c797e93a626f0ead8dc24fa9c2984449fe5389c2cdd89c35ca580a1fa1f0838f37334d4d0e3f28fcb83a75ca1e87d07fc2394d1e686fae78c271c9818590233be1aba8faaffd86ac1e9cf063b7e8386a280feab1093723484c1e826c5aaf09af3d8e33aff68cc49c4cb8ea1dcbfdf548cf0713cee4bfb0da64111173092f726861dc3a77a48a25fcdbec90aa57b363520927663199f01452294209c77bd62dd6fbd7cf93f09268ba91ccab926149b8a1cf35246623e1a7c7867d9c2c4b6421e14525b77ce93c97451fe17998a33fee54399ea823dcde48fd219d8d70b3018db7a804cee4e2d15828188804c280280c0829f2362314080018401e8b452291581aaaba3e1400045b262038362220221c14121216128a44a15020100684c26030200c088502a1904808ade85407de25c0311b74c85744aa3c58b1d5158ce9e7a6c0634ab186c1f473211481443168cf2f9587d4b92fa547c4e4745239baed65beacc14b12d0d5302a26a8797cea2b5bc911af584bd35820a23bf32108ea300da470916a8c046138a8575392f3cf06d4fbfe86b568ae31434e218c35108e4004a79f8dee43f34e2280492d317c962c39f76fc6999d33d9003d9bcc4af01c3adbe2875014a99d3940a506e1cb4ab1b738f93bc3a9711290666d306462a0ac67a03f5189ed5ba5ee2721b453daa8441dfccc8dcad25d8f8bb9ac73089770c00e0babf38639293ee8908f2ea3e658d84a05fe83ad8edaddda900ce5b8ef0e49f45684c049e6975311674905413a341d4786a23e6ed35475102928f0f711fb58d5689cbd8775978eee0d5b7bab2ac04d99769eb00c9d7b906baaa5ca4eadd83ef8e623a50566c7845c0c671149e747a50910d007bce6f3b16ec695fa70c89476ccc9ae0385e5f7681c72913db8678a3b6923a50776584d556ddcd61bb5c21a6a5a82a4299ba897b7556b6a0595adfdf4d0aef51158da8209530c257fa8b0c8912f433ec25ab40abee6c6b7f43235334dc91738f07963345d43d4e91378bc713407d445bcbab9dcbea32c53a07f5226322acbb0cdc2e0a146654a3bc0e7611950009d133a8d1339a153edd40e9bf606f99a5b6ad8d8c2ba41b7ff02314585228ef7ef8579b9e2f392fd1c9a94431a1d6f34bec350841b6692a17234b46e27dd8e00745a222b04163150b1c3c3d5c052f370f0813d132d60a12b92325bf5a755faf1cec5e14d6be06a511255d07d9b8540fcfbb09eacc68b2770681df8ae2ce24e7764e28bc91a41f0598ab5ccf7cafd82b07a0662281a2d129197df06129151da7c4a802d444d8a4f07cee549e6a3528977e97a4334ea2fbb728015c75ea0b9e832ed3166f2390b1be26192d0c919a8a8090021b96c057a85b06e35b76567e21483710eb7cd509e83b939a7e6b12e53ba130b04cec57056858fd63e0ef72d948a7fc29983e0b05cc1aa4b0190d3be08e4b430c154b487a4c542a6832433fb429c639c6fe5218e83c085c2b25e04dd8974b3d0f23afdf1bbb0b3efcca1fef470fecdc2fb38b0e19021ee8b1f139bd5c07df8db15a06f99d4a0880f0736de5f824d5a41ed9bd589227360536aa31d93c1a59afc6e9ee2d07cedcb8c27ee35de40f86728c9eef8096630563daa0bc15dbe26b65df5b1264178087ed4d2591946fdfecffbd9331e0232c5c770532b4c294bd529265eebb0b500dab03b90c09df7dab0d143890d8a8229a4092446782dc36bead278dcf4e02effc6aaff4bf93cc39edde52be25165bddc510913f2420ecb4b4d6ab3a649813dc38e2fad83163ca35c7e37c7d8336cf21654897bf6e2e2375d13eccfe20147340871422189eac7469900b1bd5e1b4ded5bc921391ad94df12c96de29022f29a33e16ca9ce5820763cab56468baac54317e235f51522dde46bd770cac0d58f7b84c83c2a3457af7b34567f6e57807f98727377a03e7bc0960179f7767902a48632ed862688aa092a37bf07cebca1baf3a73a4effa826b821b217ddb7fdade0199388fa82ecd185459f79fbe093317db59803de5d1c9439c19dbfdfc09358d8b30fbde4960f0a06ccb8fd733d8550c0aeb8140199dbab2407ee9c0f61d17553812c62640a90c3d6482a38108d4a465ca2c979a87141dee6ff8ede982c89271651ab02507088fae332377fce7450f801104d4a1ed7c0eb3173781998570ea85112755e15b96bf8a97d738f12a2c9fcdfbc8db74efebffa14f980904786959c132b26d3ccccac375bb4a2fb230926fd03e54ad159877ed0f1fa0198c990bb7ca97c51bd596729683e840c422701acaf687166a2b0c30b9068a638b0f8826ee74e9a6baa4fe705eb8246f09f3cccc23b8f58b976d48611e71002a9376c68775799bc52a7cf8ca9618639e941c104c9079db0c53bbb043edfb419a8d140e8f83006ab3effe265943eee3662424967b0ecf1b977f511164e220555b7f0fd970492061b1430e88643d97325a5fb7fa6de3aa8692ab80286a96b9f7c295bc680e18b7586520412df5b7d06ea6b5c8076a9b430f046bb7001bfe01c284bb6425dc87ae0764102c2fc063a1f1c320569ba06d5a8f6afb6d5118a2c3bb26ee727bdf4037f5e76295441edb54712d078482a4f7fa5fd5437fd96bda143c8ff86191af441a8fd8c8f80629f810ddcb0acfdeb88454c08016877a32333d163102cbc14cdbedf0568e02c840d8b662b12569524ed47eda9bdccb4d530cacadf8b1da989365a71e8eb407091a2c42c0bf374ad20d814b8980753502fd32f1bfbdface079e8f8e280c122cf50871c7b0cb59c39bf4f0d1c97a869d40724b2a6f104c34ab9a90730a71444e2bf4de0bc5242dc0a566aeb043ff2a0f4c4e71810d8020d603bf08d5a5e1cb36343ce1ac775c8c67c128b3c11892ab22b6edb4883a6a5f4e87dc275c429e9fd821034fc0314183a66c58417d581d1eb72c8f1990e0902a91f714e1301e1faad802ad56c7891ae80008a6fb67fca098d8624f686d35fd7f5c24a3b19426f7f8fe32a84da98d15ce12f39f408074166da2c80b05a1dfbaeaadd13410c2d55db7906ee8ba715add240bb874e2553bce3b78d1d4709f3a9e70bd77b83ef4985cad23adc7e194e16637ce4de7760eb18b175b8f4b4f3269caf8fa89533845330f0c2617b3d47227ceeb48dd3ebc8f672682465104b1d3ee2d41a0cd449c3905d33121d370113f94d7769036336a3a55ba3ad6620ee518dfb183caf7814b051871e62f31dc604d8924290212a912b08563f42599fd143adf58922cc677e1987a107baa4b0aaf11e522039e070ecd8067c9a359ebbc2053a09351ee8532c69252e79e05baf49445d5a1cb908b78c0eea3d3ea6c2a600fe84589208ff69adf055804aa4e4c511ea39dd212c2c45e38e5d7754400424576cb31a0755dc961c5ee380800539d0034e53b56a3806495e9fc0c706f968c08214a899b4edb59caf7e1b08e9279c70ca080ae22adf1285f572767fbaa137e3a2fec4cdfc723ba4f47fe502eec41fc3e280f4b83fc4dbefcc71a8d1e37d8a6f8bfe4c7f2ce0c1519a8722b629b299272dd58d88e4169dfc4dda13b838bddf283796f77ed6135df5c4488a5ce06b6c86568bfeae67529893976596127889012ebb25eded1be8f877594aeff71ae611b7d2de1e6a916a1129139cf0053c3a54385d66c431b14a7f4c48f960682a8d6dc6a18a9382648b13b7e286fcc4823c20c3a08698a05b1fbff50d1bbff464e67ca96b1a9fc46a59f074388c265f608928d06e772946fdaf1f27dac579e873eb776a06f824c09e9d9cdceee23c6b7510433a104e27dc24ae95ecec771dd3235b3407d0129aa2bba5427e056b48f4894f144bfa8c36553c0889e5419f948503b4566c27aa519d9a4111cd5867658fd855c8d237e286fec481fc20b3a0839335614d9da3e4c47fd3e1c7bed636051c62bc7744b00b4410448ade251664a17fd521edb495f15da076031fa1b49da3a26464ca96383c0502db32a0b240d086811dda20386249fd998a334c742799d72997ea50d613e1d21439f2d276bc9f3c1c1dd727b99b163b58b08ae987f6e46f453fa96c0ba36653deee1fcf40f54157491d69538b088d28f271c5906c262c75915862a7725b0c892e117cf79626366b274d72078e08fe8161defdfc7b44838f81ee7db7684fc22b53a2cd04830103dd86be6c8fb7147c7ab76b29c719cb84a0a24c24cec5ad1d5cb6ee044aea8214b815791a64cc8524c98c358d67818c36ea874319a8fc8cd40ba26ce8061452bad81404eee7be798e2fa46179124ca22da404c690103e07aac78db4cd1c0d3424220cb3cd8eacbf5e89d0aa50bde8625f58d20d1299ce4fd0c4d983e14fc2b8b755ccecc9ef72ea3ee756c2bb7d438df4e42b8c9a7ddfd11c322c6b61c03326f2d03ec58f4a5d586be36ebd666c677851b2b1a8274a8c15765b6142e15d9a606e3da6029228c599a1b6341397d1f0dc196560531269567850f05152917c03eed0c08c3eaba84a65f76b82c904a321e1b923225b77bbd76148fd2055442916bd10ca4231729dd58e13e1adf0eb420708b77818a695e24345498daf710743b9fe6a1b5e69229bdbe113d8b3e1ffe427738dee4ed5c54fa7c17e5ed998b7b69dc6761263b9327bb00966e4e5fd3bec295389ff63c759e8d45a8b138b8cd70bc841245b19a022f46f0c9dc6c9425bb82f5912d0e262e04c703081ede1c1685193513303add3b4921d4f181f7a9fe6be8017e22c130d132d25bf4a6cbada32b5304916641c449c319084ea41a7ea7656b3c1558d962331ab307243d36c774c4b0864524b256eb10e4ccffdb2473d19c5a82a1ce5369dc1c3b059235d35b7404abd76d5c5848d95e5528619dbdd95802b4729509ba049ecb398cea580a6e2e42a47a3ce68b264c1622649fccfb864ab3195e66f87a7c82e8f9fdf175078295ff88d60628ffdc33662b74ea5368342d12326ecfc1c5f938c6e02b95a1d5cfa31a30cce696f9624918b7b7c12f763c8ade9f14140337eb7bbbeac5ffce178f7f66312cbaecde92da6f4b111cef5534675de1481835df7baf2665655045fb281ebc33316674a9286eb8b99abf63bc7e4310b768545f19c498ea8bd47100ed574a39a85339b72a87dd0cb4c5d0fd8e5fd1236d26244f8148b397988a4b5c3af8ef4764a35379aad33a9da8f2ca6d9905b5271e35aeb27c9b9fd81cb8ad9bc4f7ee29e45aafac624cd2910a5a6c9d3540566915542a54d51c9eb46d7a1b2666db77ea54c635b1e96c690e1a124e5c016dd93b7766e9d5323a56e48d1297a778761b7f9d82fe52665278e1f36d57f868bb3659c76874127ae4b7097faaa24ce015371102b55f1d13dc491c723628f26dafa1a8b0a84d8aa68152394b8909b93e7e9d0a0b7dfbe78e9c02c32a752feeb3c9793be380d6e2c709337f5aea24135281a7a6319402717a2118a470ae0e61f81df989009b1238b83709734efc55e1c55718f7fe0e9bd9c643e9c6caafbcb3d5b5c46b682240591f42a379e2cbd2746682e7be77fd765586a5ab04fdc23939acb66f8f9ba56de8ec9bdc6fc7c863f12891336f14f31fc3e0fcdc0d1d3a49783c40d724ad9af6521d6fb0b0ce4e37b4d653d5826788bc315a8cbd1ec830ce57c53c551bc24ca91d15bc2a208f9ef43312b65b76d52e829e8578cce4a745a3324ec6949818e24f1a72b889da8c8c20a08ad1d30411a6754ed48b06c99b7954afad7f91bed2e29e3fa345051b9775686bc64b16544c529eecee6bc6e2cd03c4419e325775c87be91ff671de6ed43db43f123fce3bbcd3860aaaa6db994c48ec19d1dc28172b2905a0b2ab4f8895c6e2385538347783e3ab84d2f978528a7f224bdd77c0bc39a918cd81f21c44f6841122fce852441d670397dab659e71506017b3b35dcc6c1aad92a9d8063e91d4a03410e9dc723c21850b9409612fc51fdf107eb8a4610981bc466ca9bbf7599d8b4032d68f65a3bc3c7c35ac0409bd10f4cb07d60041ece35dfb81b2672ad0d0e5ab89beacc1d73326e0aa4896f6509700dabf20e12eb137f75fe6620d7450a968cc597fa83ca39af250812302eb700480961a7015042f9a665cf81c15a52ab0a5e95ee2f6a02deb2926e024da4e75512231337a9b9f0f7ec9b23f3ee5a18afc13d1488dc38af28f055e7908d8c6579241d108b194a07cdd59623ab6e342f288deb70a7ebfc6c5c14ea46441731886464c35c21fd1b787e2dc764b4c08eb6c786f540ae3dfa254252958ea87cb9fb9bfadb94fb1c7e06f7bc0f3509895808fed4f4ee6b069287a53f2618c24d5a5e7710a9e0a49b3c5e1cca7127487d57ba4942c8b2316a0a6533c957894dbeaacaa87fdc66710a5f5499189474fe75ab1feb7b75373ea4004c33a71be705cf9b267c564efa503db36979e0650a13c30d86dd495e0820e7c63a8858361253c496d4d15f54f9e1e840169d4c43e120e79245d18d0f21259f8a118116419a61417d790714f954311d88734274f12b58dc584a35454582c096c989e4f83c8b0b6a5079ebe782497084814d8da2b9f4a471938817e84665d4b044ad8c31ca4a135f8379d14718c8027f45a81a6bfc3c482bfa262716686433f5077822ce4d9d6f4f6506850649f8278803efd3f35ce395d3320dbcafce5f6d810c749b2e5cf0ce13294feae3a194ff64325e02d7002921195e101b449de49b70e79563e03c89a7b2a6ffb5ab3684dc1c5639c4165d22cd7e8ca77c5f903e57a26833d77e2053da22bf7eb5810b1960141751b73b63096a21f45c95445f4ee36098344911fecffecef8392b3d555aa2cfc74b5a335857137ed36b34c1945c55980e870936509c536199ca7e6efdd50de1744269cef330ad3b540f8b0380bfc57765b84febb15f993fae4300870c6a61a4287afed32683e63ff89886db275297bca9e9da6f937b09a6cbcc231339243e36f5f55f7c18a25a8584709e28554b5ab584da7f471e67c0d6fc7c0eedcfd435dfe05d23b926e509aa5078abb49093ff82bb2e641e9997facf76b3d05684b756854400674780247a3c2e220869f1d51e6b56d58ce84662c7455ad58f3a2654b46e53280dfe19a5e688629087a76cc82f1d224f1f15cfbebad9a3a852cc4523e21be7286e9eecdf2a31016e63e8c4cb43bb7418feda9c4892a0c5243897e11042408ddc5d36466299b114e1faa9ef3f449fe956ab66e7d7f3b8465c18d27cd036a421262914d6459d285a13d0d1f07d5a1d6dbc93c49b6b79c626e41bfc83ec50a5720d348139201621a3f0162fecac94360140a22506f66386ce222d762e091935539f94c967886f60851ad6401b9330e65821b97dd48e0d081113b40a6ba08fd3bc56893801240f52d6ea0683628262adc8183ac22369c0ced82579d548689dcd051a1183f5d8d5d42965263619245fb4ce467855d57d2c76af3a294e21307739282f18e096fc5d52d1a8e72ae4f63fe518906a5b67cbaf9bb1dac29b04a0cae02fced56e3373f46bccefd03c1a47fc9613583fdec791661b49e862bd9f5b2d8ce07f292c3b33feea141a75bb2104d3074009df3eee3524254b11cebb0015e894421960056d335b1579594e862ca301653c62b18fd31f8b7f109d0ce203b90f833cc5408df34bea5768de4b46ea03fa1e95a6669bbaa48424e926cd55665c5064c72a0aa01e919179d8201b945d7e694bf9f83651f88b43a438e9bf21e7d809bea5a40ad11898b80396efd5ca3b86821cb29bb067ffca13711810f044d17f01ca6e0ade7d70fb3a42398f48089ef6749c16863c7d252f0192d5e501b3c464898c05ce9ce13834a924d99c647168ade7c615208d4bbdeac412351d7cce724f84a23209d01e11598321ca821eca0402173be7e0f6b9fabf1b41d849e4a84e3d86c1aedb88a87f1602da4ffc05f348bbb467ef673b235527384273b457a2bb49f535097963ccd16a5cf6bd271a24caac9ed47f1f4af233c049e2d35e7058c9cf68733bd9ae336d53002840149379089266b128833e7452934d9c2ce9843b4a1e0662d09c8f3bd343a45b664acae3ee202c896400e5df52c8e2df486bb1e21322c73567b8fdf4a89cad58ba8bb65c236dc757459554cdac33133e3342be02428fedb387bcb0c0823db3655375403755920a930f8032ffb1649fa31baa8e082cea4dea1b4bd79de04bd3e982a4a8b8c69d48f9faf292c4d8d0fee1244efc44114c803621adcefe47c98987af4a0e03da87c426fbc3b2b6dc3d7f57134748fe554aad3309d861476466d48f7d7836f1132ca9b839a96e84eb60b2f223ba6cfd6dfdb86deb1d2c354d670318e08f10f8c691405c5fd61aa70417bd8d380de6b1c6b3208432a41f9db626f8e263bee521401e31346d7d84946f3c86e093ea197886bec3cbf1dbae54c0e2b8e40a1960b901544463ee39a5778464ac9c86e4f8cb1039cbc91de7c6b461238936aa1fba3d316bcc6c85606e176a93b6375f750044603d2031083bb18060c73fd2edec0b593bef0b8c7fe82a55c8676c8410506c2197fac425963f370954bae0637ae5e2398547b6352a255f97abdc9a1b89a4276c83fb00f356f5e8eb63a9afe8c216b6a352b0aacc6a9aff54a0ebb94a4e25b792034aba39ab6fe500bdd3d9edec91bbf06beb120d1af6e626d4cd300ab88b87031c37f7c50db732b2f5eda3d3301bc94bcf4c20709f42b8bd6539ef983bb4a6f4938c58f9786125e987401d413d39660d98700d4d505e183bfa3e1472b212c5e5a8e254dd3e377204275287ae1020f484d887047ffed78e200008cc2938dca14e04bc22bebaefa45df79991629093f09cb6300233d5aaf8787a408e90c7f3043be2468d32b3b1266d01472e9be23426975347fd450dbe7f81d63644a8379fa8d3df921ef11852f27dca304afd6adcb95dbe33efcc026e66ba8f86ca54a9b359c2280ffd609d4575886710d711f13b389d0feef48cced3f4554570fdce011dd8ee08819e7498f0e12ae8268348384d35c3873b4b4f26d4304e9f36894846a7b6684bd7fd53b28a6390c4d33de2b5ea1c3d738ac0c78665b0e6cc481f6eb4fb0a8ddfc281fe21d6120e2a40c41b552f56e8a4b08e3793f857afec1b4474461802d62f2d2d0e8479b18ec8d4dc276cc2ccd4b6bc9717803f6923a78bbfba4c50c6b8888f9ed612e28a97a8c60f7e3a5e872912c63022889de6187f8aa741ef1cecf4a3458bc2eb127e096122080caab9c75ab42c4a2bcc1c49e7a83327df2c4553699c157cfd1434b4110b8ed4574bf5172af274acc9bbc079784a5bf9d608e5e22e47c34a48b536a038698dc49df401c1ba36416b43ac18c0f988190a036f689218bca737dced5073067a3b70da3d0e0586372123a43fe74113c35d606bda493e2a6f31136ed76389619a8feb316ebc45d2b85c03a38083beb1dcb468b82ce269fd67dca62b66d1ccc762ade23bb8f0e9cdc41bc3a078c3b909eaf7e28fbfa1e02f821e0a368977fc74b2cda45a8dee60b0cf5ea8ab0e887d31f23d56c53b7e2f23c3f3bdd8de56cfc8529016f9da2932efd99b89587332fb260f5ceb1bc2071df4c695e7159cc118f4e09e820202a517db5303ef23417f2f5891ad1c3a053a0b72073dbdf3a85cffaf4f5c189a66b30260a0542d059a77c00ecb105d9e538203d485863ca4217b1bcca33247101fca7a4161b02700a37f7dfc0363c3b601cf302175460a187d5e67b2304a214808896ee3c4e22f9126af05fad51bd0ca5336993ca85b0e2be555c3e7f6ed43b4973301ea83131d6e742da3255bcb5206d89baf8354791a4a55a4e6ac775575493526aba379f33584247b5950e5b1a4d7db92e754634e587adbe6978f154038414ee03af7ca1155862065e3114d1ce69fdf75a675d29637d08e5d67879f9e82537c8a9dca970b7bfee8c3e7e446f0d8d4586ccd73c431f0f3b4a79b8dea9330d118071bfae830586b10f249593742d3383529d76946dc1c400ce95d313492d9bf99bbba6dd090db51895f7652136b595f43ea0e43d6cbbe6d1b8b71e28e4bf320954bd5dc5821b9bce3fa695278ecde63a952ee04043a35164782108d622838603b3e0590f8b12203e55c60f85e24dc4cbd3dc831a7bd8b06dd2b51b91b79e09ed6cbfdca99ec554f97c104c98939ccc4625f631cb586b5a8f4ebdf53f46c0983bdc2a9c5a756c3cf90e20c300a9b0bf7c77eef9024065b1c15b5009e37699f91aa4b5a791c266f5975a4d91c132139e8738510ca0d111ad0087ed611ef36cead1458f11c0821415cbcd9031c8b778bc71dac43aedea97c08bb0388ac968a9e8030b822d33916594fe0c30b8e83b294630ddbfb15747cfd7ca2b5e85e3ad231cccc081ce6c6b9edafc37822b8e974e7ac93523621193427bea933a1b5e57643ae5038d53050d886da0fef5c9dff043dd22eeaa355efeb0258c8314f1ebdb0d1f707850fe3faaa617680ea63889e519a505ea84168dd8a7a2279fdbe3b5139d06e28ed6a978f88704535a63d5b51502543b3a1bea1aba190a16f4b2bf64c2704fd377bf0a12b9d124dcbb432d64b689985467e211754009a1eb1c2a8da835eb753de580771cc82aca97ae1356b6d4d11c692814b3e107b415e631fac31b706a280d04f73fc05d26fb2584bf159355982b89a8c760bd7cdd643c1112c0d5cfff6f38c4f83ac0e71b070a2ec70c7a6506c6924563b655d870c884b33300d3a3100ed91315dc7c692f128a6cf98b3104fba1549322f9fe2724144176d151016949885a65868551b566ea02111255ffa8764a0d2fa93e485ae260d08f3a4f1caa0183a2eab995151584e5c7d1c5127962fcd7a48c206689a73c8a34093a0910010411d03689840edf0ed801fe293e5b079988eb9c45fe22f715c62525d832d97293f91624e7c0e7a0023ea88f13695b721f7dfccea416e0291dea628360b5046b49a24fe328089f10e852ea72202f4cf254c8d949470230fae4607fd9a6df2c1f00443a6a9ee2a9d5804007c033572b5d98fe7309f0b319902c1b978a937f68ea58c4c5266be40f9c0d32539b9625c509658b4719775dbd0b1914a12afe353ca5a4a5a0a992602c94b9855babedf8f93b78ed0a71b1d8674ae340d625cf81693409fce7bcab989441bc08a62db7f5577c03ff31b2e9681380f3cc2a0d1b9d9753a145f731c77a22dda93b13521f37aa4b57f5927ae183cbaf1514a04685a8e0e1be96c58d194b3d4ed1fc62613c302f422a791dbcf30860cc9b6d618aa8bc931ac6a2dc8e7e5f9f810eb99aa29c06c18a9f295488d53f4d28bb6eb7114d794e98517b22ca5512ab5f1cb1d8372951b0657062f6bd0802aafe49d9e6478ee075a0ad17a25a65bc192fad75f3e1d516b5180b79935ec822bc53d4002cf475d446b1c96c9a550ca217b67170525eaefd632d4b6e06d4bc32200b0dc843261e8a810dd2f84fcbdd6c75b8e827873fdf52e05addd4dd02b54ddc938f7028b835738b4a384d2b6c89825fbdf8b4e3d8d69380cad3be38532b48f38341a9d505eaaaed2c7fd110498178633006f849b75e2e09e9dd5c1eac5a32077036b76b2f1bf748558af3fb096d00fa9fb93a6e438d17a8c3b4cdc65948bf8742e20287dd51ce7c1dbf48c4b45a3b9d028fd7bd2479be6b1734c8e0391160103b52651995bea8314d79553dbb3a843768f93a023a67ed34864fa74328538b18df5d8f0e4a3102bd139eae288221c0a733c5c5b9b31e28ebd63a6c0da0c12eb78720bf7269b710806665ca593df07be435c9bc99596f1960a1e4ba5652caf01e45de9602d63e6ed0e2c21268c27c7c437a36b9556b1d9f236b3ebf6feefa1a8c7fb9f65268ea8a0d2f9333439e406994734b73a3b9b555a88ae4a0aeb255e098283359ff8a2295dd969a34b498a16896a1575ffe687e7331afa01906ac7a197cc2970a87cdd96525fba4bdd3137f5fbef31d764b0ad67cac2961db830c0c063935e725d6c1d3e2fbccf24d8dabf85bafb0659e4496bbe057ea4ecb3df0fa7e08111bb5a5a237b8fd8b76b2fc013fb87f30f0fba81e747f03edface72d03beb84327c0db7ebcc003c5dbf70eba9edea01db5cd9f9e9a8dffbd0d22f413d3f56ef346211615cd2e1045672cf61f74fcb43f2ca01779480767cc244cd9cdf5c75c70246f1162e7c276794d8a0374f1cc198f8ca56555192c443b3b11a6e0029332310684970532b2a55969675b6bc72c2a6a5c0f9fd3bd63af451e44ec087e1189a77047a2a37cd4bf9bb5b268ecbefd57a4a4c626f4902fc12be88546a9107469a3b689ca64ac6af9de45efa1350d62883c536577224f8d7dfac04b35d8f29d1550b447986935b4987ec5f81893704f852a9aa7f2b7d35ea6fcb003cc739b627f567ddc47f3b03a6188d1f94222dd5ee1819aabcd2b472c28dbc3eb8577e33e58ae4aa36d5581e6b622d60958037979469622482a21ff5bf7446860abec1230dd3b74be3ff614db67b0b82aaf7cfd5af19a9455527987bb2fac40fd495914918b748b2ed2411c90ee52d97b5aa5bee77d96ea2140ab9763bf55a5fcebf99299cf4a36a20792bf16cce440ec08251b71a6663b30bd83de86353dc4cb7314b66fca846c5ca14cff18afd1590b71392e47aadb5482f09a22ddd80184e4d6cdc5e5ac5f52f3a5957e9a5592852b9a0582886df194607479b6bdabf7ecbe0d47463cd2636f49c12438eea0166909b5030a5f2cb26fef8ab24551157ee53b55c43e879481fab2c89497b9b9eb639a76b2b4d81773d5a3d6aa7c441b8564adc41605b16a206db6d408234386cc6e353da09831e3987a81147ac59e7c1973a536b67b12360611aaf06c9abced02486e052bf10870ee5998b9bacce2002071b082371df6a8041e897327b4e4a0322c6f09dde51293f30055cf5ee22426ebd2e7f6678edd71fbcefa1e2c13c301b03f6a41563a559b5200cbdc0bd0eb14e0fbefacc47287153624a910d4062d83d78ba8d4150f354a1612b17068d8d1e039cd6875a16b579b47301715acafca4e8685528b85e577161d941ba9a17c2afa2cf7948d30c7ec3a44002b3ee372b1afaa01193e9d021be822ce71c6908e331d1c98b205eb0b979694581ff96899e0059ff9ec0a93d44e55c155c0d620a5da3eb544d8089ee3bf2e399faa7fe00667f46879190430385f260f3a7af9e50fb338f8da48e741636dacff56dd2bf9a4f7edbeb2f82d13e071d6cf613945ae68b4618d8e2282a6819d8a9715807ea5fd6218749140621725017c18493daf402e1b400fb8959dba7dd0d119371db934af398b5102fb9ed5417a629d1c6863bac178550b3ba167d2b837b5b029c05f07be45589046aaba28fb5eb98e00c7df0b217fa24928f4eb85ae55ceaf280d11d6ee691db7753eeb7a5650ab120ae8e8bbddae3a3e5e1babd13a5b79dead1d77beb4ef43375a16f3aaeb2a5777693847b590dc7ec648ef90d32a9e72828b54ccf43825de753d9fcde372eabcaffefd5d3ddea1b8e21e569753636a8f4a532bea80ca50f7d41369450a10fcda188998651addd1145b15263fc0f1982326563f396f75d8e20d8c9dfe3d36513c77f91157555d97da82fb0e596847a9030758e7df09cddde796b3ae89b4eacbb09c33dfaf6b6af93aa34364651a2ce5196f173cef9eb16f125a527f0bcd0e98b877ee1234a45647bb9a94cf8d38c619e5db3f92ae33670a90904e65e9bba850b5287fcc62c28a3f20febf1f02995d1a4ad1207359171d1c8a9974c30102838a9e749970684613b8be8d254d27830e85605a4b05527eba04d44958132ab10433d91420115d572797be3a787647d5c535ae13db3661d6f4836b9076912e0742381d87bd5eaa73dbbc577b2cc702f26e89da6b24cfc6db2eef26adb963bd2d1e61208b9106628dd013612925b50ce1489d6316c057ace4cf4a48a929b4c1f1c2720888f63ffcf4442247943569c4814b61a7a4ee6e50d056574df2192f9e759dd851592c024633c9980b5ca506f3919acac5c2b9ad304c732b1963e7b341dfaa05bb570e39c144a61864dc5711746483ae3d6ded929a823e2ead0a4ce088578dd54b787cab33a12c1a77dcee3b522eb0b2c310fa9fa7c466fd18fd4cd6682f93afc7038075faee02b166b1b748b058db8b955c74f2a1cea35c645bc5c4bd0395fe6b2802b9f8791a0e474248474cdf98eda3ea2a26d65090c43ddb04e5eb906ab14c365115fbd120544c50279daa98c82db58616760d58ca426e10c3e84b64288e5b8246223e8725795771c4f4ced3ec7e212d42f4e3d0f68e65cc7847586fa7176268d294e8fe5eb7c1ed87e9be2aa7faba9f8892035d2ce3f7b3bdeea61c673198cb89a620e269d11142bbf4d1524ecf39f2e4b2c633bb0e1cc5a61e16961b72ee3afff796d90a6ca9e6191353bc2ca2d75cebaa0ff5c855b1da2900df2fda4af9c83b888ad0e313d2161c0413042f370d2ea7f7092523f85c0fb33d6e801027171f4eeafd82dbfc92efd320c287f0ea0d13017ab121473750b224c301c33f3f3f3f3f3f3f3fffa8692d69b5b55640a45999a4243314ce0ed714a494644a29251545dd41b2b7875b002e0c23bc606d3e043c045104c523b230ea64e2f9855774a1bd33446261d8f7ae6471f353dd0b166695faff2bcc26bbecb58e922b4ce15f59f0bacbea31a483482b8c99b29ffa4c4cd6e8b0c21c45bcfe5514465661565d9deb96c2bbf61a5185a68223a830faaad3af841032a274b0029153183dbd7486f2ba78f1731c8898c2944c9dbadf336d5167197c6078d0f89011f241835258763aab7a714333ef2bcaa99097a6934ba943e36580886183496178a59d578573a153374e88477a20320ae3abccf6af6a414e181e211f61d800118531757ee9242bba44da9150989347a12f5fed5ec92f4161ce73daedebae53fadc274cf1ee4f46778df00e79c2a46deef34d5f0542a413e6fc613ae31e2edfaa396116a3164f958a37619432bef42fe9ad2b170d229a30ab2056e5ab8a15b17c2413114c64c725d212d77d14f35268ad948e54627977f7e223db4dec0825ae6312d7819230baf49c95b02054cb133512978ba977a5dc9e5eba83bfcc55239030d7ea99365591b64ac57c84e1dc472a55967516af76845929579d2bfb836857d110698451fe438c289d75d276618429ebfca46b0b31b208839639295676922375c98288223a1ff1d9c48768d3092289487737c6d25e4e75313b820853fc686f59e9fdd5e1d58730bcf8f03efbf95b7d94218ca1b5dd8812b69565a810661d2d49f1f2262221ccfdb9c7654b31e2204c23b45fb7948f280853742507c21c3aae1063ae856deb110161f44afa3a2ac3723221fec158f2548ab75311fd60962fa3772f85d80773cbef5b1277a35d4b21f2c1d82f4f9ffef0bf7bf1c81e4c962dcae90f97e4bc54d183c93d47c90f4a563c1d27e6c1e4ad82bebe7796e11dc48371d4b3e5cddc4e091dbc8349df64976807730ce5fa51eb2cb10e46252ad74773c988d0c1f0298da5152b96c147a3060d0f908f3d431244e660da96b7113fb197f7a21ccc9d2bc57b8a2f8c83397bb7b6e787f8d339897058b40a5aebcfb5147c837133fbe64f28b1e3522c2ae2865c7a4a42e997ae1bb6e1ac2acfd5581ef15db1c1e47272e777c7bc5f8351a5b73bf7bad5f2d56a30befda7d95fd55715846930091d5e85f816d9ed190da658a74de56aa9ea757a06c6335b44eb452cb9beb97a9442673d9ac13822e582181d54b610298339e8afacb30955f355d12c44c8603c172f94fed62e79b931985dab9da60e2a42fb1b118349aba82e2af34f2f7a56211206a39fd2ff2f4bc52fdd296030a8a855e98ede0f2b622944be60ee98723aa7bdd668ad8817cc5297526a55ecafd4978d74c1349fb3a3d8bc254fd108170cfedd2e43ceeb3ea17b44b6609627e4847ecbead26d695ac04816cc9ef7355ce675695f61013b6410b982b13643bf7da758337f0670f110b18229ab0911d352c357a43052058312a62d8a14ca0d64840ae69679ff51db721cf9770722533097cacb3b26f7b0710712918259961af91cc50f39e9230c04b91744a260fcec12ea1f364d5e1c8182f1e4bce992f97bf2ef1b224f309c12526f9532f5b97509f164e22e0b9f5dd92e7a1a1fddeefdf6235509268c1dbe4a8dee65f1a6de417209c3287dd9d20795d254ce9458c2b42a6fcab2eeced9534925cc59b6a2fcdcfe3df45220a184b1ebf2deab49259330ff2b151542780b3917488b40220993daae887de56fd8c01348226190afa232b5fecf9087a271542081445aee7cbacc6e924718efb390235f8915ba7d481cc1eb9c6b54ec4e069fa411d7216184b19536294d564402c922248a409424e278312f3b970b971009220c9636a529f5f2a274d6210c9552ae1c93bd210caec385a595a74218e4b928add9b7625e228439a9ea3cae957a1046e921ae26a599a9930bc298af6bae6fbf4b287120cca7ae9f84563f204ce9a268ad44a8fcc1f441d6b4b3455915f283315e79f2532ea4d87dfb70d0944afbc92cf960b82852be07a3ae24636749bf48b9ebc15c1b5fe2bd57491e0cfefe2d9a1ef55beb588287b2ca9e767e968c67fd9cd75c32d8f80ea6d83d72dea5155be7edd0293b351f97d37530b95659940ed9d2c19cfb54cceed072a7a57330997dd0ed621f0e2472300891a71ec5e8f8a37212247130bb0a32be7e71c5ac8e040ea62865fd25b95bf206a334535afdee05a1a2921b0c7641ac78adf9594124694305246c30fd47a53d4ea51c44bf06b34eb184504953987a971acc2a8991ae32326568531acca375be86ecec24f64183e99552ae5e55980baa936730e78607a1e3c5b77f6e06939251ef39c98ba2ba2c834935b5d6d8b6300c2464304a57af6b3a6a25fec231984709bdf2fea55652480c4653b31fbf39255f7b188c16f3846ec99bf7457f200183418a08b515ab3d2e5710bf60d84a5a083b9de597e205e3a6aa0be3a63c82a40be63fd9b236f32a6787122e98ecb4abd83115462921f781640bc673a5d4b378fbbc1e2d18bd3d25d1d15cddbb9405e3e6564ee5aecad3c8cb0e245830fe8f9748905cc1e06a955499dade2fa8680e24563078bae75e5541d89dde0149150ca79438e977daefb5621e48a860ded4fa63c9f2345a79922918b3db4c8ea6087d158463600f245230fac7d6d4d21f54580e8d0f303e6e8c401205b386ea2cab53adfae40b4102854eb5644ce52e5c924f4aa97bed4a5525834ff284eb9038c1a0845c91f292fcdc6a499a6098d55a793115a2bc9430c13ce63ba755ccffd859827174e69abaa7bf8a1f259883d0d25fedeb4930c8ced183f4fc22c1f0d16f4cf4642b901cc1ac7a3dc5be20a4da9a8d60d8d1f13a77880f252a453088cfce2acea9209010c1fca7a23335fdc3e7970cc1d1d478cbb5da15fc5ba40999953c8f1e9008c1bc5a3ae50b4a9220187f3d7756ffa3432aa10408a613e1fbb65bd9e4fe3f3099e5f7af3bd507d7813290f4c03052bddaaf4bf2e93d090f0cfa62be2b53df4172615e75d471890e8c2a6b8b4c177371ab8fe4c2b4522a21846835e1591cc185b35e9645de2d7db730fb8bcecb7a5fc8b9245b1885bb0ccfa6a21811d5c230dee9430b8390adcb69379a85e9b4c347d103a64f295c14e2d213441874f6962af37b3c4765720883a95a77bf57e14cca2686300a2d42748adbd61dd449214c2aaeb930178d8f1969810921cca774ef44c47e504162801c072683b83dacb42cf2b46e197c280873d84da983a7f59c931a04934098fb343bc52e55213b450313401845d6bf92e7f273cc2a838f3330f983b15fe94c3152c8e74b0229e503133ffcdab7f7a3af3fea1c61d287091f700f3bd1834ef260ba64f1a49dbadcca2e37426c4cf05090d50adf257366e64a755da7543d2e197c323828e8e800a1c1e40ee691e2b95babcba23e696207d3fb2ab9e43a175b85933a201d700ed7319183d943878fb5e952bcee3898f644fa856f3d1ccceeda65a99454c7d64dde30718351099d35731796d49293365c071bcc51ed968afa7ba4bdd793355c871aae639206a3cfc570a9c338418339e5fda50a42278fa29a9cc1a094978d1ef398e1bc3896cc2b6dba87788b5a55eeafa4ebb8190e26653009f912a6b75adcb936218329b9e77f0c261d1d7d46485da9c4440c8691e7afe6cf0e8329c713272ee9285ea8dc212660307d168f429e8aeb79f505e35f56eff4d525abf68229e8177e9edf35e982b16b4bc7ace76b7870c205f389b8a877b43cb4306dc19862b25a51a7d1ad822e9868c1b0a29d1bba4fb26010997246efbc64feac31c182c176fcc28fc9cf98d5150c73dbcae5cddcad7e6f9435b182c19312d2c5e8a8273d5c05a312e63de3ae9d50c164a3b7af4a4e6b2d7d0a86fdced962983991c25e77fb96728e85ad31771205e388dc8f2b2b6a8ada5030bec828efd5d1271885472de555c8264e3087155a57d6efe84aa89b601aa13fd63bc7b8453513ccbdf1e23c9b32274b306a9cabe0299c52aae399132578765aca5c4d5b993049824986d79aca1fe54488264830a8d4aaa7a65345c7d3e408e61c3b1dcc89118cfa6db4aa8a56118ce5f1b267538fccf310c1b4953d4449570fc16072d5a513effefd2913215c87092641c8b4cef669f304082898fce004131f24c260d2031b63c2839cec60a20393be6e6f8ddc97d04e2e4c958b1f5c18e3e5a7a79b974b5a7d0ba3ecf9c7ca2d17d7d91686cf612e46436b955db316aa5854cba5733762592c57b4efec5ac8486d91d0c25c2f2bac8e98329764162655f2fb71f585dd306561d422a793c4c218aba269d97161618e5952f6dec4c87095cc57983fa48aa6b42bed5eaf7485d1354544e9746d70e3c528c11241d20af38ec719a1fccf6485b1939217e3aae5ac12e72a4c39cdd9797c07d9e0842008125598de84cad4d94b5b9a325361101d477d88caf7dc9a2b418539b5755a1d462ce7739d203985f1947819cab4ac0a264b4c611a6dfda85e7b0e2b35cc52986fa53a73ffdeecf42129cc23568a123b634ad35124536bad6355982b43220ac368d7612245f8e814c9e09384e2b497b3709381822fcf1fed7f7ecc0d98a001cd06c9270c3a07b9d51c75e2e743e341f0338e085a8c3b26483c21e90472c2f02eaa5c980795c47dc9260c4aaeb614d74a4bfd5e13e69cf3d44e8b5d052135f82f6880f101620008807c21c68c04400000207f438c90f3cb230111e8e201695fd87d9168187140c43a923b8e0103f0c206e80b0474110222860904e0c58371c40083c609c3000000b9f1490c55e3868d038400e4c3c641801757e8c28b2ebae802005f74022ed0050808ea1000004ec8511d03f0e2841c55e3464701ba08c0020e70e33d64904e0888874702baf0420105480f037084c1c60c301480e30bf91112860c90e3e171000e2fd8c046ea42c883ccf81904e0e08259eed7552a21e585f5b66018a1a3445d9f5a30abafae3c71c6cfe0193f230b46bb983ad665031b69c6cf28337ec67260213f42c6f85057c88f10556386181e1e07e0b082b92ddb89c8ea20a5ae6570e3898137e36f788cf1a13cd4dfe0238cf391de06885f088d8f04a1f11172834f042cf028014715f223e4860c90303c3c0ec041856e64546d9a011280a0018ce0984216d44b2ca58bb1b9dee569455cde4f54455d0ac64aea95f210ad725d98230ac63b29b2ba17941c5030aaa4252f8cffaebb8bc6098304af6a88a05fd5a0a1c9f1046385cf6f5a65f5964ac6e184e635f6335d37d456f7e4688259b44811afb4e8ec9d87d80003617030c1f0297e0b359f3c845ec9e05b1f32401cc4460d0f9b00c7124cabc48e7afc10b97a75e78543096c96ede7c9ced86aad65b936389260fe16c272f8b6911e320e2498b4353ca6962ab84a4d069f87a271c238983ae03882416aa9edf7f5a8cbbe1f7a34388c600ea6c266671d2a67b17a8e22183e9e2a53a6ddb5f411e48411030e2218f69594974e5f70d1460d1b89388660bc943374501697c1977e0ce71082516d952a75ba5ee9d7ab1b23e0088241eace925dcfe031bd0ac8c001847472edbbda4d3f06c80fcc25a597ec3eb542c88f72f8c07c26ecb51a1c3d30e9ca9aa3aac46af9673f387860ca49c87e8efb924aaf346acc985123e5dde0d881e957e3b3c38d6ec8732f844307660d6d3a2a6eed990ad508114147c75dc945c1c5dec2b4162e4b137d9ad8e28ae5eaa12297ecc5927a145feac5a7816e6346dac0a416a65842bca82cb6f383162d8ca6574d87536aaab26a1606d319a9d47bf6071359642c1216f80a934a325a5b7e76cbca7185b97729eea515e6fc41a9b6545177bdabc1d3d80ed0e80018373e0489373ee4a305135618b43e19aac2c8f29c421498acc2744abed2ec285598cf946987d1a734950aa38e9dd4fc6f74cbd1a3c2786b265b6b5b3454ea14a6fc79c4659fd1e61a9bc2acb9aae3eb7ce523af0c3e2b85296ee7755bdb24302185b9b2966779c56a7a544e4661fe58426ef8b628ad6247c70d26a230cdba8c1b393a90f326f8df4047c70c8fff35098559e8cb7ab070724fb4d64e4061b44fafd3649d1a4bf14f205fa8a7d1796d434f14c58951ae63e284a79de08441685678d1966e4239cb1916cfe5b279aaabf739d18469fba396daf339c984e1725e78ad3b880983ce2a5cdb681da4aa6e7209f4e63645dd35f682134b9c3ba4b4ac45e507a95289c5434df364b870420973149e5fbda6d6240c9fa3947cad724918cf2fc5cef67f5a6c1b09a36ae9f27233db632224cce15d7b4815eb47183be9553a2764eb124e1c6114b9325fc656701f39de36c29cb252e51a739a30c2fcf8715465db8bd217b1ade625b36d4b5e26ef266cf777eb4f8f268a30ac921e1fc2579344048594a893075a16e42888a220640c2288c6d201e31248201038248e05c30181485055f10313c0008b02d17024120bc48180401cc97110c4201403210c03210c43510429cca8520fc90cb000edcc24ebace102efea271d18451c3cac2a269153b5e3d0305a8746d1e516a62b57bc12c4db15088b6b196d28599fa5b333c1c904375f0b233acf2b3888f3b6ce4cb90d76d32ebe406f81dda968e356f632cadc5ff44f7a652dcac48a78ffd128d2ea17926ff869762db4eadb98d484692c4de2567850fc01893bcc0705d5711b6f9eda350d52d3d3ba2b920a6e7fd95e92c138942e5be734e0e3721cf1ee7621faf7ba750a07b55c8cf21bd06f14d1297590b420bb5097e6d3c4a1c427ddd21b1c4d1bc3a1e0522cb30d30ab21e7a275be727a1e6939a3e6021dcb28ec200328ccb6c5c2d725f6052b95c92b01260d7704aee97127d55b2ce52185f3271e696db3783318e9c2c4be91b60262a6787b847d6164bd8e949d5ae69822c7329c92681d697c266e4f2030942b14c401a9215c8573cce7578ea2e933cb77e590bc924fea71abc4e365da3535e4ed8bd43e9e5e82c730ba9edec1caed0e8c56b0d8c1c2322b4b71d15d5c992bdc48dca6415374aa7b57fe7568adb91840c322d43c2921a512f19f11b8d40f172af52fa621b417a676e5611fc083c7c66e3ccfcf906eb03663367f2748963a07914f4f4e8bf361ad0a0878fda3a9b1e6ebdbe10c628376c4ece27e2fcb970cc7ef8a44018e99f5619fe3087f0b672d932be4c21866c7d8d150850d4993c107347e36be6e5b034e2ecb5c83d55e42c3d8480b1f66a469d20754b821a782e45fb9fa2ce8a810abf9b627a7c72babdf8fa1da1dfaeb113b58d6f69a9eb1b4aba5093cb988116ce2dd6ce6a8758b228bd134aeabcfb4f98897a3bbdc2f170c34d116d4458532e87ac4ad769e981449861afad730b73a23d31029665fb8d845cc2852cac53de46d5155d1f0dd909757a76228fcb706d6d25d0849fe1ec836bd8f49783b43649e51b97aa8c56a0329ee350166ffe7e89301351bde93f69ac9f1c2e8ecb03a568ec7f023ffc5459d76422cd2a2bbcb61d2e75e093235a9a6196c0182e7b54a70ca7873bcf63594e21b93754531052aad3d29229a2f2a678d528061148bfe088436d1fc645bfde84ca409655fc9888223440bf0c4882e3b259d10228f6c760fe5405ba1553ee8f2d6f6b98baf85cf517b3cfc2100e27d8e64e494e227c31385fa6a281aed2e9ee08c601db4d44f3b3b005008fd966049d111566142214d85e31f0cf4827a672982389aa5b801da21b271c011a8ddf7011a07185a819bb58e6781df2d4b31aa54f169573773c9f1f6dd45962524863b9a8f36b99b33c08595a32027aab11a0bfc01852c4dad85bd11e2042b23ccb38e938fc9721659899316b4c6bda927e50d61a0f48ff829b4375e30b0938e1b4a2606917f77f2d78417fb554afba5beed5a3c3c0273ac59936b70ca8ac95fa61931742fc780d54e2fa781a686b09ea727c11d3b8f0422bf7455ce54a979fb8885efe74c8c6f6d175fef1af3a95070bb2d8a3e1fab8b1ec9909c99431ef38b7a2f03d4b1a992535cf6591979b00674480b62f0e7c07fbc324159d8469452581db709743992109551d026412b12546e62b2c47c1e92da27210a390dd25c23040e0f9b603ebeac8e0449e99c92c377396495d8c9855124bad2251b1143697aa76ba1dbc4a176ec107775035a76a2039dc061d777d96bf666a1d0ea3ff5d9e31c2fda3b06a93593f0f8226b4fa8cb82211ccafac6858b4a94bc8ebf60d19459371680a19e9578856c58f845c286bae3bcac77e2d7e3ee96f22b3656f006cd11bc9ba25cd785282089a4a41c5cc8c46b237522687bdb3db1c646f6af49376839214e5cff3f67a1faacee881680578bf3c499db5eab988bc4e6d171fab344e0085696e8f51638aae09342543923d57554776010bbf65cd0c76d44bcda98a11e47f578f8be96da423b9cd1a452ac1fd0431d20e5cc5526d84d463aa024e6754d7a0762208eb27ca48b81aad0c3394340e2a142970ec2deb7eba36057d1bcb460ee9be4e4dc8426e762b2f78d72b1e37b6d74b72a68bdf2b6b784375b30fa68e48a4f11fb50a48fb11c09b23c6556e2c952d2a44aa38f34b72e8c4cab687a02aa02eb6c75b343c2c96d0788e9b4bf24708e79cb324d8c099c1f32db29c707c497ea3837b0a246f77c296b910bd49c9e946b9e6cb3605b5788ea99548827c0496cb5fab37b9848c802c4135fec02ac1673f1e15ebfa682e3e68398f6e74ad93528c88c057d93e96770fec4c0ce8a03b0a9adeafbf5d9a982ed32bbde70fecb141b50d899084564ce84ed6c33d390169b03e8407a2add3a7a1d76709d258dc68df93be011fb7377028f652b2b553702f0795e37a72576341b58936bd35d8d1b8dbe68ac0abba7376dc75c9e53dcedb325d84bea35ad6d5eebb8c2d3daef96bc3f0046fe45aa4dc6a8a028073e3c4c70bc0371b640bc6be7c49f1c2d7301b77dbeaf93ef0a075c1b045ce706f08bd16a1d579e3b8c10a5eaa29e29c36bf11066a4a9474b56e422f2380d93748dc393b707fbdf858a44d432437cb6129b5bfcaf449cebf94719d25560ebb892f1fe2a99f96d9a04938620f5144bddf8e98d6b03a67016adf0f2670fc5248a7de42536648a81493b21835dc91e9fe693fd4f08e76e67538e6b9ff1fe1e142486cd37c12f6b3959eabc14032fbd995cd863968d49f64d896e2663080ddec31fe4bdb8408fc88a12a8ff047580634648d92a1004d90761c0527ac0561a04236b1a761280ece013c154404dff7e37d00308b3f3e9bc6332bd36c9f7d5f46c00774510845a237a2a35e32f12f468f62510fa3a56c7ae327ca31db07489013c28e2c49b4a051db98be2b46c40fe015d0ddbf58d22f6160ff26f15c91f76623480d6767b85d562c32a4b7bf66b14481b7666fdae20c9ab1d353f94c62151fd2bba7850a7eebfceb8a983cc9a425fc852f9293134e291d9c0d66f3219126f685433af660444cd8dd358d3621c04c77cb1e9200a5fbcbe4af9ef2aee9828a7bd99f7d0ab19461be80d8be6249872587e04887101d86c45e0e6f63cfaf12bdf9c10b49e3a645948d0503d4d733c5c2df8bc94a2223be91c79685d32e758345f015398283ebe2207af8b4c4c8c87eecc600d4df2f952a64167e0eaeb761c9b332511ef727d1f291494c9223eaab844d3d154b2f047e0ab3f0ae0fa0e20f0a36fb70e9d571048af004735f84d74b6a05c4a5e2ca2218ba6f7675e58dd478008dfb93d4adaa8881f6b933129e0393d7a39fd8b310ee850a861bc271e6d55ce544ba8cd72d6a920b5e1c17a77256a5881d84fdab4af95e9fe69b32a5c4967e1e70055af5f448e76a0bc9d14634e11784611a4bf5247a78bf3b3b4a04d5ae05cc20ee614e5bc2a3d21e264077b0ca9cbe22d74187f2067b5628434bd47755c65057a0f0253b5180a1023ec21ceceacd5b30693a5442ae95b3ae408d13eb48659e3dcd102f60579136c7e54946513aa253dd594900eb8179c014c611b668e7d01f3d3a5ed0bcb8d8b882dad8423dd2a3ee3fa44161661003734552296a8d114da16147d98a80dcf2acce5343cac6fcf8eb6fb25ecf4a683c1edb23a4a8d359411ba19da8f89d8548ef0b012f00ba4d82e8909c2d07ab4a002cebdc0d6f600ed11a436b817dff8d00c4903b2a44842a558c8a716040150901951a88987487366e6a77fe0e121adede23f04da64ed4bd8dffe26c54a97163730200824bb158e864c06082691e628296c8a72230fbecb4bc6e6e80074a69f95a82c03629a1f70e4f99b9099fc11aa83e508624065040611d4de55dc2355c178fc873f877fc6e6cf2363e0e50009ea7bab406402ae47fe76abbe878d0483b8e183552fe5a936c6eeeef3d7de151658aad3e467d1826524a9ae28654d71ae0081a34805de4057febe4c13759d8f70df2e022ae37fdc370d50aee69147638cdc4d90c492528628ae9258a203f3072544176c695ab6f7ceedde5f94e3219c7038d8e2e703e91a5d9d6b4b52adeca4c2ce3f36405dc3de2f771eac9749a4d56b0a1d102b6b35ab2ef1325befb4e8f7551aeb89d21a263d7bd7da0e59e892bf2cab16bbc349ead58598e851cdd01ee0c6165de36f6fa167ae302320a8db4e116e2852769b4a6eb7b89a4e690918af731c0eed92da296cfce669952a4e0e4fca3b50ae7614ba15216ddec10bf7abaced1d5299e27d0e0d079dad11caf98fafc6c9f7e6bf679c5dd9d341b129d0d8f011d5b2697ffffe063e19c851600acf2dd245f99fc6ffb23ae79a4c47d5fb09efdb5afabe0e0dc3fba1a279daa4dcc519c752f58a126b4e52055bdc9ab4e373a2ee00593dd089dc545a9358d136a062fd847012bb6616c1a5fe6418dae976dd2333d82db8404209fc04062e189b41a6b176506ff2452fc6492f8a39c8dbec16d351c9ec029817a9b9ad119c096ca34e9492a09dd1ca8003dfff02c356d0ed910dae36c170353febbd24cc64a744410e35ac5f6d15cd03301057b5b37c727a135c9144124804e4547c10285d016fba10484db12c3dd6bb5810c070049adf7e3fe438f840388d104d3e89f16278db9b9893f24311199d80774b500ad4a4bb89d945c95f9b031f98f84966a569a08a31819c03b6a4e80fe94ce9ca25c7fcaf38a88572278e7ff7b1b5417b8444486418a2a76ae0e6c85710746290337b294850e266236fa08e0a85ce047f4d434608b9994ba923e4edbdb935d3172bcf4e78577aa493f7e3d88f54d0bea3c13c2cf389263b728b0e5c62369fedd0507653456c0e7dde35f45060b122c6694292f180fcd583843ea563241d2683c2381dd03822b3b5345a178a3d72e67b88e689d6b3ecc2d9cbd9616387ed82439242b354c933ad73fa8105dbea65ef4072a32a2bc22846dd5fed3a70dd11846d08cb82381f1423f81110c8a4661b567226990d7e0e7a9cce76d825d5687c7ab55a9ff7c74334b39c44ca51f31ba787e2a56d6d90124dea9aa882be2ec9d06c57be8d9dc661c31d460941f99ef2501497f7b094c85a88c98856f8a84392b9551929906bb9279468ac412d7415b63d408a3b4fa78ca32d3aab2c3698758d82823afa27070565942e3b47cf699d69f83204636cd15cc8aaee04eb6bec7d8e7fc86b20a6d3f3daf157217f381ef6cd7b6a2b312e336f01c7637d3e090d45c10cb423c4bb9f68f8d1a1336034bc7b1c25f8afd011f41e6566a4853cc10bbc180fc02171a8f69eaa4d08f50eb42bec5b13aee986cf85d27bedca118bd3c72460af27a959a2cd5648e34234fd453ce1aa37a807a6c85657f4af83499a6228aad6367ed406e998732b19b63345601f7f003105ede7296fbe4c798a73645fbda8559bec5157e5c22a67709a4a81a0dab2016a453c97cd02ccf0f300a35ce4958e4b26190fc86f73b7ee6a0b9a066aaa0d14af062f1f84dc41c6b89feeb7a7749817275672e9f8e0ec91e982b3b56e3ba1e39f2c9820278462e66f8dbaa55007d96029c4646cbe75114266fcd994e2ced893544a8ba22ffa3a9947f5bcd6210c535879351af37db52bd1d317cb46ae94e856be88c247523482219d94bf37d44ba2e534b3389029dd554724196b0b224a101292edd8366ce1f3f0f50bffc5191a1571f4e77f19d99bd7d68a2f5c0111a7fbaa075131d93cd9d39c488bb50c4e2ea0c581936eb23d26618de9bf303aab51c90e7baa1203cd8fdc97ad299e811e982e274912333d74eaf79ab6ec16188ac61a5da15da2f859d2cbe6b009512585f04eb68393256da1dcd9d0aa6d9dcd3b8a731d2aa98ebd1434d76dfbf0d980d38abf41b045330ed1d0c24387b34eb388b0c509a7dacd1fb1027d74fc69bb39cdb6dcb7dc941886f69500484fc8158ef03c7a1b08b4ace28d246b4b33457b5cd30b2ffbab9a96ea3b32412e4854be1ee31c41fca79c10", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x100845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f744876aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151bf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x100845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f744876aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151bf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0x6dd12b3ae7975bb95f841f4505bc193c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb328a22616a0e689030845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a": "0x0845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3295d097b09a3ea2c76aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b": "0x76aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb329ce21f6fa898c6a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f7448": "0x6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f7448", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3bd4d99ad2324a061f8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f": "0xf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195026e3d4ba592e973c617572618076aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b": "0x76aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195045f1ca6ffcd6f95461757261800845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a": "0x0845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509ec5a66bfda48ac661757261806ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f7448": "0x6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f7448", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ab6c705e19963ee06175726180f8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f": "0xf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x100845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f744876aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151bf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x100845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a0845a5993b29977c58c9ef36aad0e09946f8a10b2ad30956dec1207beda8014a6ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f74486ea1de453086c8ecafdcb8c05c2ffc5b31dca333e27af61595e11a6dc88f744876aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151b76aad3978bef6ce80e5b7bb80e9ae9e1fb23fa1133088fa9e0555d6d96f1151bf8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5ff8465e78528188a1511df15027568a300d1319d346dc7ddde5bc33dc8c27fa5f", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x03000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index 5475fd2aa26598b0e56cb3661effe319ac361880..61ac91aeb06b492808a61bb365758b6e08112f2d 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Logic which is common to all parachain runtimes" license = "Apache-2.0" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -31,12 +34,14 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features sp-std = { path = "../../../substrate/primitives/std", default-features = false } # Polkadot +pallet-xcm = { path = "../../../polkadot/xcm/pallet-xcm", default-features = false } rococo-runtime-constants = { path = "../../../polkadot/runtime/rococo/constants", default-features = false } westend-runtime-constants = { path = "../../../polkadot/runtime/westend/constants", default-features = false } polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false } polkadot-primitives = { path = "../../../polkadot/primitives", 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 pallet-collator-selection = { path = "../../pallets/collator-selection", default-features = false } @@ -67,6 +72,7 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-message-queue/std", + "pallet-xcm/std", "parachain-info/std", "polkadot-core-primitives/std", "polkadot-primitives/std", @@ -79,6 +85,7 @@ std = [ "sp-std/std", "westend-runtime-constants/std", "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] @@ -92,7 +99,9 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", ] diff --git a/cumulus/parachains/common/src/impls.rs b/cumulus/parachains/common/src/impls.rs index 50cb1c7f3e8a1b942a035338de656763d56dd0d8..beb655134ad4c0eb8458a9d213c235d142dd8512 100644 --- a/cumulus/parachains/common/src/impls.rs +++ b/cumulus/parachains/common/src/impls.rs @@ -18,12 +18,16 @@ use frame_support::traits::{ fungibles::{self, Balanced, Credit}, - Contains, ContainsPair, Currency, Get, Imbalance, OnUnbalanced, + Contains, ContainsPair, Currency, Get, Imbalance, OnUnbalanced, OriginTrait, }; use pallet_asset_tx_payment::HandleCredit; use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; -use xcm::latest::{AssetId, Fungibility::Fungible, MultiAsset, MultiLocation}; +use sp_std::{marker::PhantomData, prelude::*}; +use xcm::latest::{ + AssetId, Fungibility, Fungibility::Fungible, Junction, Junctions::Here, MultiAsset, + MultiLocation, Parent, WeightLimit, +}; +use xcm_executor::traits::ConvertLocation; /// Type alias to conveniently refer to the `Currency::NegativeImbalance` associated type. pub type NegativeImbalance = as Currency< @@ -118,6 +122,64 @@ impl> ContainsPair for AssetsFr } } +/// Type alias to conveniently refer to the `Currency::Balance` associated type. +pub type BalanceOf = + as Currency<::AccountId>>::Balance; + +/// Implements `OnUnbalanced::on_unbalanced` to teleport slashed assets to relay chain treasury +/// account. +pub struct ToParentTreasury( + PhantomData<(TreasuryAccount, AccountIdConverter, T)>, +); + +impl OnUnbalanced> + for ToParentTreasury +where + T: pallet_balances::Config + pallet_xcm::Config + frame_system::Config, + <::RuntimeOrigin as OriginTrait>::AccountId: From>, + [u8; 32]: From<::AccountId>, + TreasuryAccount: Get>, + AccountIdConverter: ConvertLocation>, + BalanceOf: Into, +{ + fn on_unbalanced(amount: NegativeImbalance) { + let amount = match amount.drop_zero() { + Ok(..) => return, + Err(amount) => amount, + }; + let imbalance = amount.peek(); + let root_location: MultiLocation = Here.into(); + let root_account: AccountIdOf = + match AccountIdConverter::convert_location(&root_location) { + Some(a) => a, + None => { + log::warn!("Failed to convert root origin into account id"); + return + }, + }; + let treasury_account: AccountIdOf = TreasuryAccount::get(); + + >::resolve_creating(&root_account, amount); + + let result = >::limited_teleport_assets( + <::RuntimeOrigin>::root(), + Box::new(Parent.into()), + Box::new( + Junction::AccountId32 { network: None, id: treasury_account.into() } + .into_location() + .into(), + ), + Box::new((Parent, imbalance).into()), + 0, + WeightLimit::Unlimited, + ); + + if let Err(err) = result { + log::warn!("Failed to teleport slashed assets: {:?}", err); + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/cumulus/parachains/common/src/kusama.rs b/cumulus/parachains/common/src/kusama.rs deleted file mode 100644 index 073971a70750f78fb8ad86ad86b0f07bbcdd3cde..0000000000000000000000000000000000000000 --- a/cumulus/parachains/common/src/kusama.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-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. - -/// Consensus-related. -pub mod consensus { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included - /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; - /// 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; -} - -/// Constants relating to KSM. -pub mod currency { - use polkadot_core_primitives::Balance; - - /// The existential deposit. Set to 1/10 of its parent Relay Chain. - pub const EXISTENTIAL_DEPOSIT: Balance = 1 * CENTS / 10; - - pub const UNITS: Balance = 1_000_000_000_000; - pub const QUID: Balance = UNITS / 30; - pub const CENTS: Balance = QUID / 100; - pub const GRAND: Balance = QUID * 1_000; - pub const MILLICENTS: Balance = CENTS / 1_000; - - pub const fn deposit(items: u32, bytes: u32) -> Balance { - // map to 1/100 of what the kusama relay chain charges (v9020) - (items as Balance * 2_000 * CENTS + (bytes as Balance) * 100 * MILLICENTS) / 100 - } -} - -/// Constants related to Kusama fee payment. -pub mod fee { - use frame_support::{ - pallet_prelude::Weight, - weights::{ - constants::ExtrinsicBaseWeight, FeePolynomial, WeightToFeeCoefficient, - WeightToFeeCoefficients, WeightToFeePolynomial, - }, - }; - use polkadot_core_primitives::Balance; - use smallvec::smallvec; - pub use sp_runtime::Perbill; - - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the - /// node's balance type. - /// - /// This should typically create a mapping between the following ranges: - /// - [0, MAXIMUM_BLOCK_WEIGHT] - /// - [Balance::min, Balance::max] - /// - /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: - /// - Setting it to `0` will essentially disable the weight fee. - /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. - pub struct WeightToFee; - impl frame_support::weights::WeightToFee for WeightToFee { - type Balance = Balance; - - fn weight_to_fee(weight: &Weight) -> Self::Balance { - let time_poly: FeePolynomial = RefTimeToFee::polynomial().into(); - let proof_poly: FeePolynomial = ProofSizeToFee::polynomial().into(); - - // Take the maximum instead of the sum to charge by the more scarce resource. - time_poly.eval(weight.ref_time()).max(proof_poly.eval(weight.proof_size())) - } - } - - /// Maps the reference time component of `Weight` to a fee. - pub struct RefTimeToFee; - impl WeightToFeePolynomial for RefTimeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // In Kusama, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: - // The standard system parachain configuration is 1/10 of that, as in 1/100 CENT. - let p = super::currency::CENTS; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } - - /// Maps the proof size component of `Weight` to a fee. - pub struct ProofSizeToFee; - impl WeightToFeePolynomial for ProofSizeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // Map 10kb proof to 1 CENT. - let p = super::currency::CENTS; - let q = 10_000; - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } -} diff --git a/cumulus/parachains/common/src/lib.rs b/cumulus/parachains/common/src/lib.rs index 68425a00b35890aaa0ca4fb5457fbf356cf81b23..eab5d7f45774d125df4c82fd36fb1bedab1d6265 100644 --- a/cumulus/parachains/common/src/lib.rs +++ b/cumulus/parachains/common/src/lib.rs @@ -16,9 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod impls; -pub mod kusama; pub mod message_queue; -pub mod polkadot; pub mod rococo; pub mod westend; pub mod wococo; diff --git a/cumulus/parachains/common/src/polkadot.rs b/cumulus/parachains/common/src/polkadot.rs deleted file mode 100644 index 744108bce2e5f539e17dc71725a7c1dd7af52675..0000000000000000000000000000000000000000 --- a/cumulus/parachains/common/src/polkadot.rs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-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. - -/// Universally recognized accounts. -pub mod account { - use frame_support::PalletId; - - /// Polkadot treasury pallet id, used to convert into AccountId - pub const POLKADOT_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/trsry"); - /// Alliance pallet ID. - /// It is used as a temporarily place to deposit a slashed imbalance - /// before the teleport to the Treasury. - pub const ALLIANCE_PALLET_ID: PalletId = PalletId(*b"py/allia"); - /// Referenda pallet ID. - /// It is used as a temporarily place to deposit a slashed imbalance - /// before the teleport to the Treasury. - pub const REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/refer"); - /// Ambassador Referenda pallet ID. - /// It is used as a temporarily place to deposit a slashed imbalance - /// before the teleport to the Treasury. - pub const AMBASSADOR_REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/amref"); -} - -/// Consensus-related. -pub mod consensus { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included - /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; - /// 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; -} - -/// Constants relating to DOT. -pub mod currency { - use polkadot_core_primitives::Balance; - - /// The existential deposit. Set to 1/10 of its parent Relay Chain. - pub const EXISTENTIAL_DEPOSIT: Balance = 100 * CENTS / 10; - - pub const UNITS: Balance = 10_000_000_000; - pub const DOLLARS: Balance = UNITS; // 10_000_000_000 - pub const GRAND: Balance = DOLLARS * 1_000; // 10_000_000_000_000 - pub const CENTS: Balance = DOLLARS / 100; // 100_000_000 - pub const MILLICENTS: Balance = CENTS / 1_000; // 100_000 - - pub const fn deposit(items: u32, bytes: u32) -> Balance { - // 1/100 of Polkadot - (items as Balance * 20 * DOLLARS + (bytes as Balance) * 100 * MILLICENTS) / 100 - } -} - -/// Constants related to Polkadot fee payment. -pub mod fee { - use frame_support::{ - pallet_prelude::Weight, - weights::{ - constants::ExtrinsicBaseWeight, FeePolynomial, WeightToFeeCoefficient, - WeightToFeeCoefficients, WeightToFeePolynomial, - }, - }; - use polkadot_core_primitives::Balance; - use smallvec::smallvec; - pub use sp_runtime::Perbill; - - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the - /// node's balance type. - /// - /// This should typically create a mapping between the following ranges: - /// - [0, MAXIMUM_BLOCK_WEIGHT] - /// - [Balance::min, Balance::max] - /// - /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: - /// - Setting it to `0` will essentially disable the weight fee. - /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. - pub struct WeightToFee; - impl frame_support::weights::WeightToFee for WeightToFee { - type Balance = Balance; - - fn weight_to_fee(weight: &Weight) -> Self::Balance { - let time_poly: FeePolynomial = RefTimeToFee::polynomial().into(); - let proof_poly: FeePolynomial = ProofSizeToFee::polynomial().into(); - - // Take the maximum instead of the sum to charge by the more scarce resource. - time_poly.eval(weight.ref_time()).max(proof_poly.eval(weight.proof_size())) - } - } - - /// Maps the reference time component of `Weight` to a fee. - pub struct RefTimeToFee; - impl WeightToFeePolynomial for RefTimeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // In Polkadot, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: - // The standard system parachain configuration is 1/10 of that, as in 1/100 CENT. - let p = super::currency::CENTS; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } - - /// Maps the proof size component of `Weight` to a fee. - pub struct ProofSizeToFee; - impl WeightToFeePolynomial for ProofSizeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // Map 10kb proof to 1 CENT. - let p = super::currency::CENTS; - let q = 10_000; - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } -} diff --git a/cumulus/parachains/common/src/westend.rs b/cumulus/parachains/common/src/westend.rs index 9d3e0bd1a0e2a51d2dde47b5be329be0bc8b5d38..2bd4d18a15eba8fc04f0505439d55cb56062f67a 100644 --- a/cumulus/parachains/common/src/westend.rs +++ b/cumulus/parachains/common/src/westend.rs @@ -13,6 +13,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +/// Universally recognized accounts. +pub mod account { + use frame_support::PalletId; + + /// Westend treasury pallet id, used to convert into AccountId - in Westend as a destination for + /// slashed funds. + pub const WESTEND_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/trsry"); + /// Alliance pallet ID - used as a temporary place to deposit a slashed imbalance before the + /// teleport to the Treasury. + pub const ALLIANCE_PALLET_ID: PalletId = PalletId(*b"py/allia"); + /// Referenda pallet ID - used as a temporary place to deposit a slashed imbalance before the + /// teleport to the Treasury. + pub const REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/refer"); + /// Ambassador Referenda pallet ID - used as a temporary place to deposit a slashed imbalance + /// before the teleport to the Treasury. + pub const AMBASSADOR_REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/amref"); + /// Fellowship treasury pallet ID. + pub const FELLOWSHIP_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/feltr"); +} + pub mod currency { use polkadot_core_primitives::Balance; use westend_runtime_constants as constants; @@ -21,6 +41,7 @@ pub mod currency { pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; pub const UNITS: Balance = constants::currency::UNITS; + pub const DOLLARS: Balance = UNITS; // 1_000_000_000_000 pub const CENTS: Balance = constants::currency::CENTS; pub const MILLICENTS: Balance = constants::currency::MILLICENTS; pub const GRAND: Balance = constants::currency::GRAND; @@ -44,7 +65,7 @@ pub mod fee { use smallvec::smallvec; pub use sp_runtime::Perbill; - /// The block saturation level. Fees will be updates based on this value. + /// The block saturation level. Fees will be updated based on this value. pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the @@ -110,11 +131,11 @@ pub mod fee { /// Consensus-related. pub mod consensus { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included - /// into the relay chain. + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the + /// relay chain. pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; - /// How many parachain blocks are processed by the relay chain per parent. Limits the - /// number of blocks authored per slot. + /// 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; diff --git a/cumulus/parachains/common/src/xcm_config.rs b/cumulus/parachains/common/src/xcm_config.rs index 4b0215d672b2e3f8b5602976524edfd01940725b..7a63e720b0797f9cbd05018dbdac6c36926cfc14 100644 --- a/cumulus/parachains/common/src/xcm_config.rs +++ b/cumulus/parachains/common/src/xcm_config.rs @@ -100,6 +100,25 @@ impl, Runtime: parachain_info::C } } +/// Contains all sibling system parachains, including the one where this matcher is used. +/// +/// This structure can only be used at a parachain level. In the Relay Chain, please use +/// the `xcm_builder::IsChildSystemParachain` matcher. +pub struct AllSiblingSystemParachains; + +impl Contains for AllSiblingSystemParachains { + fn contains(l: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "AllSiblingSystemParachains location: {:?}", l); + match *l { + // System parachain + MultiLocation { parents: 1, interior: X1(Parachain(id)) } => + ParaId::from(id).is_system(), + // Everything else + _ => false, + } + } +} + /// Accepts an asset if it is a concrete asset from the system (Relay Chain or system parachain). pub struct ConcreteAssetFromSystem(PhantomData); impl> ContainsPair @@ -120,14 +139,31 @@ impl> ContainsPair } } +/// Filter to check if a given location is the parent Relay Chain or a sibling parachain. +/// +/// This type should only be used within the context of a parachain, since it does not verify that +/// the parent is indeed a Relay Chain. +pub struct ParentRelayOrSiblingParachains; +impl Contains for ParentRelayOrSiblingParachains { + fn contains(location: &MultiLocation) -> bool { + matches!( + location, + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Parachain(_)) } + ) + } +} + #[cfg(test)] mod tests { - use frame_support::parameter_types; + use frame_support::{parameter_types, traits::Contains}; use super::{ - ConcreteAssetFromSystem, ContainsPair, GeneralIndex, Here, MultiAsset, MultiLocation, - PalletInstance, Parachain, Parent, + AllSiblingSystemParachains, ConcreteAssetFromSystem, ContainsPair, GeneralIndex, Here, + MultiAsset, MultiLocation, PalletInstance, Parachain, Parent, }; + use polkadot_primitives::LOWEST_PUBLIC_ID; + use xcm::latest::prelude::*; parameter_types! { pub const RelayLocation: MultiLocation = MultiLocation::parent(); @@ -180,4 +216,19 @@ mod tests { ); } } + + #[test] + fn all_sibling_system_parachains_works() { + // system parachain + assert!(AllSiblingSystemParachains::contains(&MultiLocation::new(1, X1(Parachain(1))))); + // non-system parachain + assert!(!AllSiblingSystemParachains::contains(&MultiLocation::new( + 1, + X1(Parachain(LOWEST_PUBLIC_ID.into())) + ))); + // when used at relay chain + assert!(!AllSiblingSystemParachains::contains(&MultiLocation::new(0, X1(Parachain(1))))); + // when used with non-parachain + assert!(!AllSiblingSystemParachains::contains(&MultiLocation::new(1, X1(OnlyChild)))); + } } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index dbf7e9c9a700556f4e5b1da69c7373a0b84f9e9e..ecd0059057a88f8bfe8eec26ee8181e21e2b00cd 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Asset Hub Rococo emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs index 877edceae3268b59fe24878a2d9ad66ec288cdd7..00f412564205507f2deb6b516ace435302fcf4c4 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/lib.rs @@ -21,8 +21,8 @@ use frame_support::traits::OnInitialize; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, impls::Parachain, - xcm_emulator::decl_test_parachains, + impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, }; use rococo_emulated_chain::Rococo; @@ -38,6 +38,7 @@ decl_test_parachains! { XcmpMessageHandler: asset_hub_rococo_runtime::XcmpQueue, LocationToAccountId: asset_hub_rococo_runtime::xcm_config::LocationToAccountId, ParachainInfo: asset_hub_rococo_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, }, pallets = { PolkadotXcm: asset_hub_rococo_runtime::PolkadotXcm, @@ -55,3 +56,4 @@ impl_accounts_helpers_for_parachain!(AssetHubRococo); impl_assert_events_helpers_for_parachain!(AssetHubRococo); impl_assets_helpers_for_parachain!(AssetHubRococo, Rococo); impl_foreign_assets_helpers_for_parachain!(AssetHubRococo, Rococo); +impl_xcm_helpers_for_parachain!(AssetHubRococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml index 0ff817b6b96109e7f57508bbac16d672c76886e0..b49b16bf8558fd78b0519dfd26a102ebc70c7265 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Asset Hub Westend emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs index 1c017c63c6fe343f3ed29aa0249e950d44a7fa78..25d7c1079b4dd3cfa08a27ac3e10cbc498279e34 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/lib.rs @@ -21,8 +21,8 @@ use frame_support::traits::OnInitialize; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, impls::Parachain, - xcm_emulator::decl_test_parachains, + impl_assets_helpers_for_parachain, impl_foreign_assets_helpers_for_parachain, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, }; use westend_emulated_chain::Westend; @@ -38,6 +38,7 @@ decl_test_parachains! { XcmpMessageHandler: asset_hub_westend_runtime::XcmpQueue, LocationToAccountId: asset_hub_westend_runtime::xcm_config::LocationToAccountId, ParachainInfo: asset_hub_westend_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, }, pallets = { PolkadotXcm: asset_hub_westend_runtime::PolkadotXcm, @@ -55,3 +56,4 @@ impl_accounts_helpers_for_parachain!(AssetHubWestend); impl_assert_events_helpers_for_parachain!(AssetHubWestend); impl_assets_helpers_for_parachain!(AssetHubWestend, Westend); impl_foreign_assets_helpers_for_parachain!(AssetHubWestend, Westend); +impl_xcm_helpers_for_parachain!(AssetHubWestend); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index 43c0f5fd14c9b2ba8d0b493e0e84ce93cb6113ca..d923f7388a2b294de34acf0d69372727e5e0147d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Bridge Hub Rococo emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } @@ -22,3 +25,11 @@ parachains-common = { path = "../../../../../../../parachains/common" } cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } emulated-integration-tests-common = { path = "../../../../common", default-features = false } bridge-hub-rococo-runtime = { path = "../../../../../../runtimes/bridge-hubs/bridge-hub-rococo" } +bridge-hub-common = { path = "../../../../../../runtimes/bridge-hubs/common", 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-system = { path = "../../../../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } +snowbridge-inbound-queue = { path = "../../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } +snowbridge-outbound-queue = { path = "../../../../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index fa9a287adf88b56eff1746a06471343504418024..3dd0cb10ab697af86efd71b9aa1a2df0f35fa3c8 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -22,6 +22,7 @@ use emulated_integration_tests_common::{ }; use parachains_common::Balance; +pub const ASSETHUB_PARA_ID: u32 = 1000; pub const PARA_ID: u32 = 1013; pub const ED: Balance = parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; @@ -64,6 +65,11 @@ pub fn genesis() -> Storage { owner: Some(get_account_id_from_seed::(accounts::BOB)), ..Default::default() }, + ethereum_system: bridge_hub_rococo_runtime::EthereumSystemConfig { + para_id: PARA_ID.into(), + asset_hub_para_id: ASSETHUB_PARA_ID.into(), + ..Default::default() + }, ..Default::default() }; diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs index 8e5b29e65616b550f3d87bd78488dfb61734418d..8c18d112bc12fb4883d313106fa66841dcad8d2e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs @@ -21,7 +21,7 @@ use frame_support::traits::OnInitialize; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impls::Parachain, xcm_emulator::decl_test_parachains, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, }; // BridgeHubRococo Parachain declaration @@ -36,10 +36,14 @@ decl_test_parachains! { XcmpMessageHandler: bridge_hub_rococo_runtime::XcmpQueue, LocationToAccountId: bridge_hub_rococo_runtime::xcm_config::LocationToAccountId, ParachainInfo: bridge_hub_rococo_runtime::ParachainInfo, + MessageOrigin: bridge_hub_common::AggregateMessageOrigin, }, pallets = { PolkadotXcm: bridge_hub_rococo_runtime::PolkadotXcm, Balances: bridge_hub_rococo_runtime::Balances, + EthereumSystem: bridge_hub_rococo_runtime::EthereumSystem, + EthereumInboundQueue: bridge_hub_rococo_runtime::EthereumInboundQueue, + EthereumOutboundQueue: bridge_hub_rococo_runtime::EthereumOutboundQueue, } }, } @@ -47,3 +51,4 @@ decl_test_parachains! { // BridgeHubRococo implementation impl_accounts_helpers_for_parachain!(BridgeHubRococo); impl_assert_events_helpers_for_parachain!(BridgeHubRococo); +impl_xcm_helpers_for_parachain!(BridgeHubRococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index e5e6fd7073933bca21683faef3459afdf1ac8e8b..8fcf4fe5e8069fe93019c639504a5fa1bb88f009 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Bridge Hub Westend emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } @@ -22,3 +25,4 @@ parachains-common = { path = "../../../../../../../parachains/common" } cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } emulated-integration-tests-common = { path = "../../../../common", default-features = false } bridge-hub-westend-runtime = { path = "../../../../../../runtimes/bridge-hubs/bridge-hub-westend" } +bridge-hub-common = { path = "../../../../../../runtimes/bridge-hubs/common", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index a774f31b0fbc87a7a6373a96ce3381792765f0eb..b0dddc9dbf9a5b71a776e3ae48b97bbb9f29adf2 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -21,7 +21,7 @@ use frame_support::traits::OnInitialize; // Cumulus use emulated_integration_tests_common::{ impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, - impls::Parachain, xcm_emulator::decl_test_parachains, + impl_xcm_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains, }; // BridgeHubWestend Parachain declaration @@ -36,6 +36,7 @@ decl_test_parachains! { XcmpMessageHandler: bridge_hub_westend_runtime::XcmpQueue, LocationToAccountId: bridge_hub_westend_runtime::xcm_config::LocationToAccountId, ParachainInfo: bridge_hub_westend_runtime::ParachainInfo, + MessageOrigin: bridge_hub_common::AggregateMessageOrigin, }, pallets = { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, @@ -47,3 +48,4 @@ decl_test_parachains! { // BridgeHubWestend implementation impl_accounts_helpers_for_parachain!(BridgeHubWestend); impl_assert_events_helpers_for_parachain!(BridgeHubWestend); +impl_xcm_helpers_for_parachain!(BridgeHubWestend); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f3fd5da3cfc130b9dee3e25900bcb2319266c0ae --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "collectives-westend-emulated-chain" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "Collectives Westend emulated chain" +publish = false + +[lints] +workspace = true + +[dependencies] +serde_json = "1.0.111" + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Polakadot +parachains-common = { path = "../../../../../../../parachains/common" } + +# Cumulus +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +collectives-westend-runtime = { path = "../../../../../../runtimes/collectives/collectives-westend" } +westend-emulated-chain = { path = "../../../relays/westend" } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..d79ef55072ae481b4bba37ef7a97e2d3783668f2 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Substrate +use sp_core::storage::Storage; + +// Cumulus +use emulated_integration_tests_common::{ + accounts, build_genesis_storage, collators, SAFE_XCM_VERSION, +}; +use parachains_common::Balance; + +pub const PARA_ID: u32 = 1001; +pub const ED: Balance = parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; + +pub fn genesis() -> Storage { + let genesis_config = collectives_westend_runtime::RuntimeGenesisConfig { + system: collectives_westend_runtime::SystemConfig::default(), + balances: collectives_westend_runtime::BalancesConfig { + balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + }, + parachain_info: collectives_westend_runtime::ParachainInfoConfig { + parachain_id: PARA_ID.into(), + ..Default::default() + }, + collator_selection: collectives_westend_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: collectives_westend_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + collectives_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: collectives_westend_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + collectives_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..a32e865dd9ce8497755a261c6922273aea8b49f6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/lib.rs @@ -0,0 +1,52 @@ +// 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 mod genesis; + +// Substrate +use frame_support::traits::OnInitialize; + +// Cumulus +use emulated_integration_tests_common::{ + impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, + impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// CollectivesWestend Parachain declaration +decl_test_parachains! { + pub struct CollectivesWestend { + genesis = genesis::genesis(), + on_init = { + collectives_westend_runtime::AuraExt::on_initialize(1); + }, + runtime = collectives_westend_runtime, + core = { + XcmpMessageHandler: collectives_westend_runtime::XcmpQueue, + LocationToAccountId: collectives_westend_runtime::xcm_config::LocationToAccountId, + ParachainInfo: collectives_westend_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, + }, + pallets = { + PolkadotXcm: collectives_westend_runtime::PolkadotXcm, + Balances: collectives_westend_runtime::Balances, + FellowshipTreasury: collectives_westend_runtime::FellowshipTreasury, + AssetRate: collectives_westend_runtime::AssetRate, + } + }, +} + +// AssetHubWestend implementation +impl_accounts_helpers_for_parachain!(CollectivesWestend); +impl_assert_events_helpers_for_parachain!(CollectivesWestend); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..2f22d9b7a842dc3a569c87f41802fdebc39458aa --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "people-rococo-emulated-chain" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "People Rococo emulated chain" +publish = false + +[dependencies] +serde_json = "1.0.111" + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Polakadot +parachains-common = { path = "../../../../../../../parachains/common" } + +# Cumulus +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +people-rococo-runtime = { path = "../../../../../../runtimes/people/people-rococo" } +rococo-emulated-chain = { path = "../../../relays/rococo" } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..27d5531a0c7d3315ff71ef13cce41fea6b760375 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs @@ -0,0 +1,62 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Substrate +use sp_core::storage::Storage; + +// Cumulus +use cumulus_primitives_core::ParaId; +use emulated_integration_tests_common::{build_genesis_storage, collators, SAFE_XCM_VERSION}; +use parachains_common::Balance; + +pub const PARA_ID: u32 = 1004; +pub const ED: Balance = parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; + +pub fn genesis() -> Storage { + let genesis_config = people_rococo_runtime::RuntimeGenesisConfig { + system: people_rococo_runtime::SystemConfig::default(), + parachain_info: people_rococo_runtime::ParachainInfoConfig { + parachain_id: ParaId::from(PARA_ID), + ..Default::default() + }, + collator_selection: people_rococo_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: people_rococo_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + people_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: people_rococo_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + people_rococo_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + ) +} diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fa818bf81bf60ac6358c1c983faf8657cb139dd3 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/lib.rs @@ -0,0 +1,52 @@ +// 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 mod genesis; + +// Substrate +use frame_support::traits::OnInitialize; + +// Cumulus +use emulated_integration_tests_common::{ + impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, + impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// PeopleRococo Parachain declaration +decl_test_parachains! { + pub struct PeopleRococo { + genesis = genesis::genesis(), + on_init = { + people_rococo_runtime::AuraExt::on_initialize(1); + }, + runtime = people_rococo_runtime, + core = { + XcmpMessageHandler: people_rococo_runtime::XcmpQueue, + LocationToAccountId: people_rococo_runtime::xcm_config::LocationToAccountId, + ParachainInfo: people_rococo_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, + }, + pallets = { + PolkadotXcm: people_rococo_runtime::PolkadotXcm, + Balances: people_rococo_runtime::Balances, + Identity: people_rococo_runtime::Identity, + IdentityMigrator: people_rococo_runtime::IdentityMigrator, + } + }, +} + +// PeopleRococo implementation +impl_accounts_helpers_for_parachain!(PeopleRococo); +impl_assert_events_helpers_for_parachain!(PeopleRococo); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..dba6e6f6d4d6afb82707543bfb4fe160868251f1 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "people-westend-emulated-chain" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "People Westend emulated chain" +publish = false + +[dependencies] +serde_json = "1.0.111" + +# Substrate +sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../../substrate/frame/support", default-features = false } + +# Polakadot +parachains-common = { path = "../../../../../../../parachains/common" } + +# Cumulus +cumulus-primitives-core = { path = "../../../../../../../primitives/core", default-features = false } +emulated-integration-tests-common = { path = "../../../../common", default-features = false } +people-westend-runtime = { path = "../../../../../../runtimes/people/people-westend" } +westend-emulated-chain = { path = "../../../relays/westend" } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs new file mode 100644 index 0000000000000000000000000000000000000000..612580c2b160efd13f0c5c7c937b638660c76226 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs @@ -0,0 +1,62 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Substrate +use sp_core::storage::Storage; + +// Cumulus +use cumulus_primitives_core::ParaId; +use emulated_integration_tests_common::{build_genesis_storage, collators, SAFE_XCM_VERSION}; +use parachains_common::Balance; + +pub const PARA_ID: u32 = 1004; +pub const ED: Balance = parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; + +pub fn genesis() -> Storage { + let genesis_config = people_westend_runtime::RuntimeGenesisConfig { + system: people_westend_runtime::SystemConfig::default(), + parachain_info: people_westend_runtime::ParachainInfoConfig { + parachain_id: ParaId::from(PARA_ID), + ..Default::default() + }, + collator_selection: people_westend_runtime::CollatorSelectionConfig { + invulnerables: collators::invulnerables().iter().cloned().map(|(acc, _)| acc).collect(), + candidacy_bond: ED * 16, + ..Default::default() + }, + session: people_westend_runtime::SessionConfig { + keys: collators::invulnerables() + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + people_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + polkadot_xcm: people_westend_runtime::PolkadotXcmConfig { + safe_xcm_version: Some(SAFE_XCM_VERSION), + ..Default::default() + }, + ..Default::default() + }; + + build_genesis_storage( + &genesis_config, + people_westend_runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), + ) +} diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..775b89ac208b022f898c7c54423e8a21b7214ae3 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/lib.rs @@ -0,0 +1,52 @@ +// 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 mod genesis; + +// Substrate +use frame_support::traits::OnInitialize; + +// Cumulus +use emulated_integration_tests_common::{ + impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain, + impls::Parachain, xcm_emulator::decl_test_parachains, +}; + +// PeopleWestend Parachain declaration +decl_test_parachains! { + pub struct PeopleWestend { + genesis = genesis::genesis(), + on_init = { + people_westend_runtime::AuraExt::on_initialize(1); + }, + runtime = people_westend_runtime, + core = { + XcmpMessageHandler: people_westend_runtime::XcmpQueue, + LocationToAccountId: people_westend_runtime::xcm_config::LocationToAccountId, + ParachainInfo: people_westend_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, + }, + pallets = { + PolkadotXcm: people_westend_runtime::PolkadotXcm, + Balances: people_westend_runtime::Balances, + Identity: people_westend_runtime::Identity, + IdentityMigrator: people_westend_runtime::IdentityMigrator, + } + }, +} + +// PeopleWestend implementation +impl_accounts_helpers_for_parachain!(PeopleWestend); +impl_assert_events_helpers_for_parachain!(PeopleWestend); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml index 5886158c263eab9a9229a701d18f465735b2d424..170d0b1a0b42a15fcac53a4a7c25181fec3dfb56 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Penpal emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs index c76120adb793373fca8df18656b18d51638c2d0f..244a846bbc2f53c9f65e4144484783f70a71879d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/lib.rs @@ -15,6 +15,7 @@ mod genesis; pub use genesis::{genesis, ED, PARA_ID_A, PARA_ID_B}; +pub use penpal_runtime::xcm_config::{LocalTeleportableToAssetHub, XcmConfig}; // Substrate use frame_support::traits::OnInitialize; @@ -39,10 +40,12 @@ decl_test_parachains! { XcmpMessageHandler: penpal_runtime::XcmpQueue, LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, ParachainInfo: penpal_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, }, pallets = { PolkadotXcm: penpal_runtime::PolkadotXcm, Assets: penpal_runtime::Assets, + ForeignAssets: penpal_runtime::ForeignAssets, Balances: penpal_runtime::Balances, } }, @@ -56,10 +59,12 @@ decl_test_parachains! { XcmpMessageHandler: penpal_runtime::XcmpQueue, LocationToAccountId: penpal_runtime::xcm_config::LocationToAccountId, ParachainInfo: penpal_runtime::ParachainInfo, + MessageOrigin: cumulus_primitives_core::AggregateMessageOrigin, }, pallets = { PolkadotXcm: penpal_runtime::PolkadotXcm, Assets: penpal_runtime::Assets, + ForeignAssets: penpal_runtime::ForeignAssets, Balances: penpal_runtime::Balances, } }, @@ -67,6 +72,7 @@ decl_test_parachains! { // Penpal implementation impl_accounts_helpers_for_parachain!(PenpalA); +impl_accounts_helpers_for_parachain!(PenpalB); impl_assets_helpers_for_parachain!(PenpalA, Rococo); impl_assets_helpers_for_parachain!(PenpalB, Westend); impl_assert_events_helpers_for_parachain!(PenpalA); diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml index 325c722951739a917815835fabdcdb3f2df6c4b5..2e8ea60efd6634e9ffe597e84615030a1247b0d9 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Rococo emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index 7ace96147106c34af4e8a9ebfec23f59b8949928..e0d37302120fa7c7ae9e8cd4f5e62135b3febcb1 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -24,7 +24,7 @@ use emulated_integration_tests_common::{ // Rococo declaration decl_test_relay_chains! { - #[api_version(9)] + #[api_version(10)] pub struct Rococo { genesis = genesis::genesis(), on_init = (), @@ -37,6 +37,8 @@ decl_test_relay_chains! { Sudo: rococo_runtime::Sudo, Balances: rococo_runtime::Balances, Hrmp: rococo_runtime::Hrmp, + Identity: rococo_runtime::Identity, + IdentityMigrator: rococo_runtime::IdentityMigrator, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml index 20b9737735fd4713196f930e2f44a8a2526e32f9..61174e2751b8871a09d77e01cab9648de90f9b9c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -7,8 +7,11 @@ license = "Apache-2.0" description = "Westend emulated chain" publish = false +[lints] +workspace = true + [dependencies] -serde_json = "1.0.104" +serde_json = "1.0.111" # Substrate sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs index 2ba47250d564e1c487829d4afa034b1bf0770f67..4d29a8024d17699a66be1d1583aa763884a0a94e 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs @@ -24,7 +24,7 @@ use emulated_integration_tests_common::{ // Westend declaration decl_test_relay_chains! { - #[api_version(9)] + #[api_version(10)] pub struct Westend { genesis = genesis::genesis(), on_init = (), @@ -39,6 +39,8 @@ decl_test_relay_chains! { Treasury: westend_runtime::Treasury, AssetRate: westend_runtime::AssetRate, Hrmp: westend_runtime::Hrmp, + Identity: westend_runtime::Identity, + IdentityMigrator: westend_runtime::IdentityMigrator, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 92716083d69e5236f863baa218231cec13c56cb8..8277b3d734f2a9147a8e9d1db5e95576b014c559 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -6,10 +6,13 @@ edition.workspace = true license = "Apache-2.0" description = "Common resources for integration testing with xcm-emulator" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } paste = "1.0.14" -serde_json = "1.0.108" +serde_json = "1.0.111" # Substrate grandpa = { package = "sc-consensus-grandpa", path = "../../../../../substrate/client/consensus/grandpa" } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index 768784ac06707cb7ad4615569d0f3a6f0985b6d3..aa3ec214f8c697736c42408c9e60c63e83bbb8e9 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -38,7 +38,7 @@ pub use polkadot_runtime_parachains::{ inclusion::{AggregateMessageOrigin, UmpQueueId}, }; pub use xcm::{ - prelude::{MultiLocation, OriginKind, Outcome, VersionedXcm}, + prelude::{MultiLocation, OriginKind, Outcome, VersionedXcm, XcmVersion}, v3::Error, DoubleEncoded, }; @@ -173,10 +173,14 @@ macro_rules! impl_accounts_helpers_for_relay_chain { pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { ::execute_with(|| { for account in accounts { + let who = account.0; + let actual = ]>::Balances::free_balance(&who); + let actual = actual.saturating_add(]>::Balances::reserved_balance(&who)); + $crate::impls::assert_ok!(]>::Balances::force_set_balance( ::RuntimeOrigin::root(), - account.0.into(), - account.1, + who.into(), + actual.saturating_add(account.1), )); } }); @@ -240,7 +244,7 @@ macro_rules! impl_assert_events_helpers_for_relay_chain { ); } - /// Asserts a XCM message is sent + /// Asserts an XCM program is sent. pub fn assert_xcm_pallet_sent() { $crate::impls::assert_expected_events!( Self, @@ -250,7 +254,8 @@ macro_rules! impl_assert_events_helpers_for_relay_chain { ); } - /// Asserts a XCM from System Parachain is succesfully received and proccessed + /// Asserts an XCM program from a System Parachain is successfully received and + /// processed within expectations. pub fn assert_ump_queue_processed( expected_success: bool, expected_id: Option<$crate::impls::ParaId>, @@ -386,15 +391,26 @@ macro_rules! impl_accounts_helpers_for_parachain { pub fn fund_accounts(accounts: Vec<($crate::impls::AccountId, $crate::impls::Balance)>) { ::execute_with(|| { for account in accounts { + let who = account.0; + let actual = ]>::Balances::free_balance(&who); + let actual = actual.saturating_add(]>::Balances::reserved_balance(&who)); + $crate::impls::assert_ok!(]>::Balances::force_set_balance( ::RuntimeOrigin::root(), - account.0.into(), - account.1, + who.into(), + actual.saturating_add(account.1), )); } }); } + /// Fund a sovereign account of sibling para. + pub fn fund_para_sovereign(sibling_para_id: $crate::impls::ParaId, balance: $crate::impls::Balance) { + let sibling_location = Self::sibling_location_of(sibling_para_id); + let sovereign_account = Self::sovereign_account_id_of(sibling_location); + Self::fund_accounts(vec![(sovereign_account.into(), balance)]) + } + /// Return local sovereign account of `para_id` on other `network_id` pub fn sovereign_account_of_parachain_on_other_global_consensus( network_id: $crate::impls::NetworkId, @@ -790,3 +806,33 @@ macro_rules! impl_foreign_assets_helpers_for_parachain { } }; } + +#[macro_export] +macro_rules! impl_xcm_helpers_for_parachain { + ( $chain:ident ) => { + $crate::impls::paste::paste! { + impl $chain { + /// Set XCM version for destination. + pub fn force_xcm_version(dest: $crate::impls::MultiLocation, version: $crate::impls::XcmVersion) { + ::execute_with(|| { + $crate::impls::assert_ok!(]>::PolkadotXcm::force_xcm_version( + ::RuntimeOrigin::root(), + $crate::impls::bx!(dest), + version, + )); + }); + } + + /// Set default/safe XCM version for runtime. + pub fn force_default_xcm_version(version: Option<$crate::impls::XcmVersion>) { + ::execute_with(|| { + $crate::impls::assert_ok!(]>::PolkadotXcm::force_default_xcm_version( + ::RuntimeOrigin::root(), + version, + )); + }); + } + } + } + } +} diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 58222f622c2a63f9ebbc856f2d0ab7afb040eee4..48ee1a3b7808301ed87538279610a7028f463897 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -32,7 +32,6 @@ use sp_runtime::{ // Polakdot use parachains_common::BlockNumber; use polkadot_runtime_parachains::configuration::HostConfiguration; -use xcm; // Cumulus use parachains_common::{AccountId, AssetHubPolkadotAuraId, AuraId}; diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index 6ea3524ed4a39d5da293c3f69f9c8a35917aab39..8718f1e83a003386fa40a99d4090906908ee717c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -120,3 +120,102 @@ macro_rules! test_parachain_is_trusted_teleporter { } }; } + +#[macro_export] +macro_rules! include_penpal_create_foreign_asset_on_asset_hub { + ( $penpal:ident, $asset_hub:ident, $relay_ed:expr, $weight_to_fee:expr) => { + $crate::impls::paste::paste! { + pub fn penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal: u32, + foreign_asset_at_asset_hub: MultiLocation, + ah_as_seen_by_penpal: MultiLocation, + is_sufficient: bool, + asset_owner: AccountId, + prefund_amount: u128, + ) { + use frame_support::weights::WeightToFee; + let ah_check_account = $asset_hub::execute_with(|| { + <$asset_hub as [<$asset_hub Pallet>]>::PolkadotXcm::check_account() + }); + let penpal_check_account = + $penpal::execute_with(|| <$penpal as [<$penpal Pallet>]>::PolkadotXcm::check_account()); + let penpal_as_seen_by_ah = $asset_hub::sibling_location_of($penpal::para_id()); + + // prefund SA of Penpal on AssetHub with enough native tokens to pay for creating + // new foreign asset, also prefund CheckingAccount with ED, because teleported asset + // itself might not be sufficient and CheckingAccount cannot be created otherwise + let sov_penpal_on_ah = $asset_hub::sovereign_account_id_of(penpal_as_seen_by_ah); + $asset_hub::fund_accounts(vec![ + (sov_penpal_on_ah.clone().into(), $relay_ed * 100_000_000_000), + (ah_check_account.clone().into(), $relay_ed * 1000), + ]); + + // prefund SA of AssetHub on Penpal with native asset + let sov_ah_on_penpal = $penpal::sovereign_account_id_of(ah_as_seen_by_penpal); + $penpal::fund_accounts(vec![ + (sov_ah_on_penpal.into(), $relay_ed * 1_000_000_000), + (penpal_check_account.clone().into(), $relay_ed * 1000), + ]); + + // Force create asset on $penpal and prefund [<$penpal Sender>] + $penpal::force_create_and_mint_asset( + asset_id_on_penpal, + ASSET_MIN_BALANCE, + is_sufficient, + asset_owner, + None, + prefund_amount, + ); + + let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); + // `OriginKind::Xcm` required by ForeignCreators pallet-assets origin filter + let origin_kind = OriginKind::Xcm; + let call_create_foreign_assets = + <$asset_hub as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::< + <$asset_hub as Chain>::Runtime, + pallet_assets::Instance2, + >::create { + id: foreign_asset_at_asset_hub, + min_balance: ASSET_MIN_BALANCE, + admin: sov_penpal_on_ah.into(), + }) + .encode(); + let buy_execution_fee_amount = $weight_to_fee::weight_to_fee( + &Weight::from_parts(10_100_000_000_000, 300_000), + ); + let buy_execution_fee = MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(buy_execution_fee_amount), + }; + let xcm = VersionedXcm::from(Xcm(vec![ + WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, + BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, + Transact { require_weight_at_most, origin_kind, call: call_create_foreign_assets.into() }, + ExpectTransactStatus(MaybeErrorCode::Success), + RefundSurplus, + DepositAsset { assets: All.into(), beneficiary: penpal_as_seen_by_ah }, + ])); + // Send XCM message from penpal => asset_hub + let sudo_penpal_origin = <$penpal as Chain>::RuntimeOrigin::root(); + $penpal::execute_with(|| { + assert_ok!(<$penpal as [<$penpal Pallet>]>::PolkadotXcm::send( + sudo_penpal_origin.clone(), + bx!(ah_as_seen_by_penpal.into()), + bx!(xcm), + )); + type RuntimeEvent = <$penpal as Chain>::RuntimeEvent; + assert_expected_events!( + $penpal, + vec![ + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + $asset_hub::execute_with(|| { + type ForeignAssets = <$asset_hub as [<$asset_hub Pallet>]>::ForeignAssets; + assert!(ForeignAssets::asset_exists(foreign_asset_at_asset_hub)); + }); + } + } + }; +} diff --git a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs index 47e92ed075fa0ce00cdf7a012ae07e13bc9bed6f..70a9408c309741cb9b9fa01bc2f54cc1a77453ed 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -59,3 +59,17 @@ pub fn xcm_transact_unpaid_execution( Transact { require_weight_at_most, origin_kind, call }, ])) } + +/// Helper method to get the non-fee asset used in multiple assets transfer +pub fn non_fee_asset(assets: &MultiAssets, fee_idx: usize) -> Option<(MultiLocation, u128)> { + let asset = assets.inner().into_iter().enumerate().find(|a| a.0 != fee_idx)?.1.clone(); + let asset_id = match asset.id { + Concrete(id) => id, + _ => return None, + }; + let asset_amount = match asset.fun { + Fungible(amount) => amount, + _ => return None, + }; + Some((asset_id, asset_amount)) +} diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml index 713cc2ecdbb253044cbf4063317a2cd8fae2e823..eb0a8a850d06928d67147dc14a11f566d1ad7c9d 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml @@ -7,10 +7,14 @@ license = "Apache-2.0" description = "Rococo System emulated network" publish = false +[lints] +workspace = true + [dependencies] # Cumulus emulated-integration-tests-common = { path = "../../common", default-features = false } rococo-emulated-chain = { path = "../../chains/relays/rococo" } asset-hub-rococo-emulated-chain = { path = "../../chains/parachains/assets/asset-hub-rococo" } bridge-hub-rococo-emulated-chain = { path = "../../chains/parachains/bridges/bridge-hub-rococo" } +people-rococo-emulated-chain = { path = "../../chains/parachains/people/people-rococo" } penpal-emulated-chain = { path = "../../chains/parachains/testing/penpal" } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/src/lib.rs index ad22185fa703ba49d9eadc54d4fd1a56b7e81337..70f23ef8260ca408e55079e5797b09cc817872cd 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/src/lib.rs @@ -16,11 +16,13 @@ pub use asset_hub_rococo_emulated_chain; pub use bridge_hub_rococo_emulated_chain; pub use penpal_emulated_chain; +pub use people_rococo_emulated_chain; pub use rococo_emulated_chain; use asset_hub_rococo_emulated_chain::AssetHubRococo; use bridge_hub_rococo_emulated_chain::BridgeHubRococo; use penpal_emulated_chain::{PenpalA, PenpalB}; +use people_rococo_emulated_chain::PeopleRococo; use rococo_emulated_chain::Rococo; // Cumulus @@ -37,6 +39,7 @@ decl_test_networks! { BridgeHubRococo, PenpalA, PenpalB, + PeopleRococo, ], bridge = () }, @@ -47,5 +50,6 @@ decl_test_sender_receiver_accounts_parameter_types! { AssetHubRococoPara { sender: ALICE, receiver: BOB }, BridgeHubRococoPara { sender: ALICE, receiver: BOB }, PenpalAPara { sender: ALICE, receiver: BOB }, - PenpalBPara { sender: ALICE, receiver: BOB } + PenpalBPara { sender: ALICE, receiver: BOB }, + PeopleRococoPara { sender: ALICE, receiver: BOB } } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml index 34713f5b48e995351d53c93fd607df236bb00e32..744cbe4f8c1e31ed5a9b122a5b5939509234236a 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml @@ -7,6 +7,9 @@ license = "Apache-2.0" description = "Rococo<>Westend emulated bridged network" publish = false +[lints] +workspace = true + [dependencies] # Cumulus emulated-integration-tests-common = { path = "../../common", default-features = false } @@ -16,3 +19,4 @@ asset-hub-rococo-emulated-chain = { path = "../../chains/parachains/assets/asset asset-hub-westend-emulated-chain = { path = "../../chains/parachains/assets/asset-hub-westend" } bridge-hub-rococo-emulated-chain = { path = "../../chains/parachains/bridges/bridge-hub-rococo" } bridge-hub-westend-emulated-chain = { path = "../../chains/parachains/bridges/bridge-hub-westend" } +penpal-emulated-chain = { path = "../../chains/parachains/testing/penpal" } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs index b03ff692b952ab4aae6de04016e83ca0dfe09eef..ee8b038a364d73301732f278786b30b18d534643 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/src/lib.rs @@ -17,6 +17,7 @@ pub use asset_hub_rococo_emulated_chain; pub use asset_hub_westend_emulated_chain; pub use bridge_hub_rococo_emulated_chain; pub use bridge_hub_westend_emulated_chain; +pub use penpal_emulated_chain; pub use rococo_emulated_chain; pub use westend_emulated_chain; @@ -24,6 +25,7 @@ use asset_hub_rococo_emulated_chain::AssetHubRococo; use asset_hub_westend_emulated_chain::AssetHubWestend; use bridge_hub_rococo_emulated_chain::BridgeHubRococo; use bridge_hub_westend_emulated_chain::BridgeHubWestend; +use penpal_emulated_chain::PenpalA; use rococo_emulated_chain::Rococo; use westend_emulated_chain::Westend; @@ -43,6 +45,7 @@ decl_test_networks! { parachains = vec![ AssetHubRococo, BridgeHubRococo, + PenpalA, ], bridge = RococoWestendMockBridge @@ -92,5 +95,6 @@ decl_test_sender_receiver_accounts_parameter_types! { BridgeHubRococoPara { sender: ALICE, receiver: BOB }, WestendRelay { sender: ALICE, receiver: BOB }, AssetHubWestendPara { sender: ALICE, receiver: BOB }, - BridgeHubWestendPara { sender: ALICE, receiver: BOB } + BridgeHubWestendPara { sender: ALICE, receiver: BOB }, + PenpalAPara { sender: ALICE, receiver: BOB } } diff --git a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml index a4360076d6bd4e2a4a78008708e0ae0891b7fe70..64bc91f442d1b27166fbc4f0a2dc22798c97ff39 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml @@ -7,10 +7,15 @@ license = "Apache-2.0" description = "Westend System emulated network" publish = false +[lints] +workspace = true + [dependencies] # Cumulus emulated-integration-tests-common = { path = "../../common", default-features = false } westend-emulated-chain = { path = "../../chains/relays/westend", default-features = false } asset-hub-westend-emulated-chain = { path = "../../chains/parachains/assets/asset-hub-westend" } bridge-hub-westend-emulated-chain = { path = "../../chains/parachains/bridges/bridge-hub-westend" } +collectives-westend-emulated-chain = { path = "../../chains/parachains/collectives/collectives-westend" } penpal-emulated-chain = { path = "../../chains/parachains/testing/penpal" } +people-westend-emulated-chain = { path = "../../chains/parachains/people/people-westend" } diff --git a/cumulus/parachains/integration-tests/emulated/networks/westend-system/src/lib.rs b/cumulus/parachains/integration-tests/emulated/networks/westend-system/src/lib.rs index 667b44a69869a1a9e991d13053cccc5f206324c7..9fbc773bc50e136af3272e851f826d7cf63bc0b6 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/westend-system/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/src/lib.rs @@ -15,12 +15,16 @@ pub use asset_hub_westend_emulated_chain; pub use bridge_hub_westend_emulated_chain; +pub use collectives_westend_emulated_chain; pub use penpal_emulated_chain; +pub use people_westend_emulated_chain; pub use westend_emulated_chain; use asset_hub_westend_emulated_chain::AssetHubWestend; use bridge_hub_westend_emulated_chain::BridgeHubWestend; +use collectives_westend_emulated_chain::CollectivesWestend; use penpal_emulated_chain::{PenpalA, PenpalB}; +use people_westend_emulated_chain::PeopleWestend; use westend_emulated_chain::Westend; // Cumulus @@ -35,6 +39,8 @@ decl_test_networks! { parachains = vec![ AssetHubWestend, BridgeHubWestend, + CollectivesWestend, + PeopleWestend, PenpalA, PenpalB, ], @@ -46,6 +52,8 @@ decl_test_sender_receiver_accounts_parameter_types! { WestendRelay { sender: ALICE, receiver: BOB }, AssetHubWestendPara { sender: ALICE, receiver: BOB }, BridgeHubWestendPara { sender: ALICE, receiver: BOB }, + CollectivesWestendPara { sender: ALICE, receiver: BOB }, + PeopleWestendPara { sender: ALICE, receiver: BOB }, PenpalAPara { sender: ALICE, receiver: BOB }, PenpalBPara { sender: ALICE, receiver: BOB } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 16e2f736bdfff477fb5b9fa65acdaaa36ee83993..445395fc783075e0483b6f0319b467ad4be90b14 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -7,6 +7,9 @@ license = "Apache-2.0" description = "Asset Hub Rococo runtime integration tests with xcm-emulator" publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } assert_matches = "1.5.0" @@ -30,5 +33,4 @@ asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } parachains-common = { path = "../../../../../../parachains/common" } asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo" } emulated-integration-tests-common = { path = "../../../common", default-features = false } -penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } rococo-system-emulated-network = { path = "../../../networks/rococo-system" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index 3ff8c37c64651c79d76a33bf10aa83db5ba20754..721f8b511ea6d604456631493a267ad7d2425326 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -66,42 +66,5 @@ pub type SystemParaToRelayTest = Test; pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; -/// Returns a `TestArgs` instance to be used for the Relay Chain across integration tests -pub fn relay_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets: (Here, amount).into(), - asset_id: None, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -/// Returns a `TestArgs` instance to be used by parachains across integration tests -pub fn para_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, - assets: MultiAssets, - asset_id: Option, - fee_asset_item: u32, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets, - asset_id, - fee_asset_item, - weight_limit: WeightLimit::Unlimited, - } -} - #[cfg(test)] mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index b3841af0e6c38372b8fb621fac468b25bdec63a1..c9270934ddfe87c4c969428facccda71b9401588 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -18,3 +18,11 @@ mod send; mod set_xcm_versions; mod swap; mod teleport; + +use crate::*; +emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!( + PenpalA, + AssetHubRococo, + ROCOCO_ED, + parachains_common::rococo::fee::WeightToFee +); 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 18483762ae19309ed139883f9cb87e4bf0c16302..24a71d3fb45a920bc8789222752fab43778935a3 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 @@ -15,14 +15,12 @@ use crate::*; use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; -use penpal_runtime::xcm_config::XcmConfig as PenpalRococoXcmConfig; use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; +use rococo_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalRococoXcmConfig; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = ::RuntimeEvent; - Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( Rococo, vec![ @@ -42,12 +40,10 @@ fn relay_to_para_sender_assertions(t: RelayToParaTest) { fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( 864_610_000, 8_799, ))); - assert_expected_events!( AssetHubRococo, vec![ @@ -80,9 +76,7 @@ fn para_receiver_assertions(_: Test) { fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799))); - assert_expected_events!( PenpalA, vec![ @@ -99,15 +93,13 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; - let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( AssetHubRococo::sibling_location_of(PenpalA::para_id()), ); - assert_expected_events!( AssetHubRococo, vec![ - // Amount to reserve transfer is transferred to Parachain's Sovereign account + // Amount to reserve transfer is withdrawn from Parachain's Sovereign account RuntimeEvent::Balances( pallet_balances::Event::Withdraw { who, amount } ) => { @@ -124,12 +116,10 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { type RuntimeEvent = ::RuntimeEvent; - AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( 864_610_000, 8799, ))); - assert_expected_events!( AssetHubRococo, vec![ @@ -162,7 +152,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { ); } -fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { ::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -173,7 +163,7 @@ fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> Dispatch ) } -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { +fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -184,7 +174,7 @@ fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) ) } -fn para_to_system_para_limited_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { +fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -275,7 +265,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let test_args = TestContext { sender: RococoSender::get(), receiver: PenpalAReceiver::get(), - args: relay_test_args(destination, beneficiary_id, amount_to_send), + args: TestArgs::new_relay(destination, beneficiary_id, amount_to_send), }; let mut test = RelayToParaTest::new(test_args); @@ -285,7 +275,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { test.set_assertion::(relay_to_para_sender_assertions); test.set_assertion::(para_receiver_assertions); - test.set_dispatchable::(relay_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(relay_to_para_reserve_transfer_assets); test.assert(); let delivery_fees = Rococo::execute_with(|| { @@ -319,7 +309,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: PenpalAReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToParaTest::new(test_args); @@ -329,7 +319,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { test.set_assertion::(system_para_to_para_sender_assertions); test.set_assertion::(para_receiver_assertions); - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -363,7 +353,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let test_args = TestContext { sender: PenpalASender::get(), receiver: AssetHubRococoReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = ParaToSystemParaTest::new(test_args); @@ -379,7 +369,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { test.set_assertion::(para_to_system_para_sender_assertions); test.set_assertion::(para_to_system_para_receiver_assertions); - test.set_dispatchable::(para_to_system_para_limited_reserve_transfer_assets); + test.set_dispatchable::(para_to_system_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -443,7 +433,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { let para_test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: PenpalAReceiver::get(), - args: para_test_args( + args: TestArgs::new_para( destination, beneficiary_id, asset_amount_to_send, @@ -474,7 +464,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { test.set_assertion::(system_para_to_para_assets_sender_assertions); test.set_assertion::(system_para_to_para_assets_receiver_assertions); - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs index 9e691bf02f1ff03eddb1374bda0155f4dcb86e2b..3dcc51b75ccc3112779e3340c1479e1b4d31bfb1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/swap.rs @@ -14,17 +14,17 @@ // limitations under the License. use crate::*; -use frame_support::{instances::Instance2, BoundedVec}; use parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; -use sp_runtime::{DispatchError, ModuleError}; +use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; +use sp_runtime::ModuleError; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); - let asset_one = Box::new(MultiLocation { + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); + let asset_one = MultiLocation { parents: 0, interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); + }; AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -46,8 +46,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), )); assert_expected_events!( @@ -59,8 +59,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -75,7 +75,7 @@ fn swap_locally_on_chain_using_local_assets() { ] ); - let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); + let path = vec![Box::new(asset_native), Box::new(asset_one)]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -100,8 +100,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native, - asset_one, + Box::new(asset_native), + Box::new(asset_one), 1414213562273 - EXISTENTIAL_DEPOSIT * 2, // all but the 2 EDs can't be retrieved. 0, 0, @@ -112,114 +112,37 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - use frame_support::weights::WeightToFee; - - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); - - let foreign_asset1_at_asset_hub_rococo = Box::new(MultiLocation { - parents: 1, - interior: X3( - Parachain(PenpalA::para_id().into()), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(ASSET_ID.into()), - ), - }); - - let assets_para_destination: VersionedMultiLocation = - MultiLocation { parents: 1, interior: X1(Parachain(AssetHubRococo::para_id().into())) } - .into(); - - let penpal_location = - MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalA::execute_with(|| { - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(PenpalASender::get()), - ASSET_ID.into(), - PenpalASender::get().into(), - 1000, - )); - - assert!(::Assets::asset_exists(ASSET_ID)); - }); - - // 2. Create foreign asset on asset_hub_rococo: - - let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); - let origin_kind = OriginKind::Xcm; - let sov_penpal_on_asset_hub_rococo = AssetHubRococo::sovereign_account_id_of(penpal_location); + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalASender::get(); + let foreign_asset_at_asset_hub_rococo = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + + // 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_rococo + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_rococo, + ah_as_seen_by_penpal, + true, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_as_seen_by_ah); AssetHubRococo::fund_accounts(vec![ (AssetHubRococoSender::get().into(), 5_000_000 * ROCOCO_ED), /* An account to swap dot * for something else. */ - (sov_penpal_on_asset_hub_rococo.clone().into(), 1000_000_000_000_000_000 * ROCOCO_ED), ]); - let sov_penpal_on_asset_hub_rococo_as_location: MultiLocation = MultiLocation { - parents: 0, - interior: X1(AccountId32Junction { - network: None, - id: sov_penpal_on_asset_hub_rococo.clone().into(), - }), - }; - - let call_foreign_assets_create = - ::RuntimeCall::ForeignAssets(pallet_assets::Call::< - ::Runtime, - Instance2, - >::create { - id: *foreign_asset1_at_asset_hub_rococo, - min_balance: 1000, - admin: sov_penpal_on_asset_hub_rococo.clone().into(), - }) - .encode() - .into(); - - let buy_execution_fee_amount = parachains_common::rococo::fee::WeightToFee::weight_to_fee( - &Weight::from_parts(10_100_000_000_000, 300_000), - ); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(buy_execution_fee_amount), - }; - - let xcm = VersionedXcm::from(Xcm(vec![ - WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, - BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { require_weight_at_most, origin_kind, call: call_foreign_assets_create }, - RefundSurplus, - DepositAsset { - assets: All.into(), - beneficiary: sov_penpal_on_asset_hub_rococo_as_location, - }, - ])); - - // Send XCM message from penpal => asset_hub_rococo - let sudo_penpal_origin = ::RuntimeOrigin::root(); - PenpalA::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - PenpalA, - vec![ - RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); - - // Receive XCM message in Assets Parachain AssetHubRococo::execute_with(|| { - assert!(::ForeignAssets::asset_exists( - *foreign_asset1_at_asset_hub_rococo - )); - // 3: Mint foreign asset on asset_hub_rococo: // // (While it might be nice to use batch, @@ -228,11 +151,9 @@ fn swap_locally_on_chain_using_foreign_assets() { type RuntimeEvent = ::RuntimeEvent; // 3. Mint foreign asset (in reality this should be a teleport or some such) assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone().into() - ), - *foreign_asset1_at_asset_hub_rococo, - sov_penpal_on_asset_hub_rococo.clone().into(), + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()), + foreign_asset_at_asset_hub_rococo, + sov_penpal_on_ahr.clone().into(), 3_000_000_000_000, )); @@ -246,8 +167,8 @@ fn swap_locally_on_chain_using_foreign_assets() { // 4. Create pool: assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), )); assert_expected_events!( @@ -259,16 +180,14 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), - asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), 1_000_000_000_000, 2_000_000_000_000, 0, 0, - sov_penpal_on_asset_hub_rococo.clone().into() + sov_penpal_on_ahr.clone().into() )); assert_expected_events!( @@ -281,10 +200,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 6. Swap! - let path = BoundedVec::<_, _>::truncate_from(vec![ - asset_native.clone(), - foreign_asset1_at_asset_hub_rococo.clone(), - ]); + let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_rococo)]; assert_ok!( ::AssetConversion::swap_exact_tokens_for_tokens( @@ -309,22 +225,20 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(::AssetConversion::remove_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_rococo.clone() - ), - asset_native, - foreign_asset1_at_asset_hub_rococo, + ::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_rococo), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_asset_hub_rococo.clone().into(), + sov_penpal_on_ahr.clone().into(), )); }); } #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get()); + let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get(); let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get(); asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); @@ -349,10 +263,10 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubRococoSender::get()), - asset_native.clone(), + Box::new(asset_native), Box::new(asset_one), ), - Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) + Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); }); } 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 f8017f7a1c54fa816006a29f52065048c779b680..a07463cec50ed17bc79eee0801562605a8b37e63 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 @@ -15,7 +15,9 @@ use crate::*; use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig; +use emulated_integration_tests_common::xcm_helpers::non_fee_asset; use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; +use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; fn relay_origin_assertions(t: RelayToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -110,6 +112,123 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { ); } +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalA::assert_xcm_pallet_attempted_complete(None); + let expected_asset_id = t.args.asset_id.unwrap(); + let (_, expected_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_asset_amount, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of( + AssetHubRococo::sibling_location_of(PenpalA::para_id()), + ); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubRococo, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubRococo::assert_xcm_pallet_attempted_complete(None); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubRococo, + vec![ + // native asset used for fees is transferred to Parachain's Sovereign account as reserve + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == AssetHubRococo::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + // foreign asset is burned locally as part of teleportation + RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_foreign_asset_amount, + }, + ] + ); +} + +fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_asset_id = t.args.asset_id.unwrap(); + let (_, expected_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + let checking_account = ::PolkadotXcm::check_account(); + assert_expected_events!( + PenpalA, + vec![ + // checking account burns local asset as part of incoming teleport + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == checking_account, + balance: *balance == expected_asset_amount, + }, + // local asset is teleported into account of receiver + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + // native asset for fee is deposited to receiver + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { ::XcmPallet::limited_teleport_assets( t.signed_origin, @@ -152,6 +271,28 @@ fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { ) } +fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + ::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + /// Limited Teleport of native asset from Relay Chain to the System Parachain should work #[test] fn limited_teleport_native_assets_from_relay_to_system_para_works() { @@ -162,7 +303,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() { let test_args = TestContext { sender: RococoSender::get(), receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(dest, beneficiary_id, amount_to_send), + args: TestArgs::new_relay(dest, beneficiary_id, amount_to_send), }; let mut test = RelayToSystemParaTest::new(test_args); @@ -206,7 +347,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { let test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: RococoReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -247,7 +388,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: RococoReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -285,7 +426,7 @@ fn teleport_native_assets_from_relay_to_system_para_works() { let test_args = TestContext { sender: RococoSender::get(), receiver: AssetHubRococoReceiver::get(), - args: relay_test_args(dest, beneficiary_id, amount_to_send), + args: TestArgs::new_relay(dest, beneficiary_id, amount_to_send), }; let mut test = RelayToSystemParaTest::new(test_args); @@ -329,7 +470,7 @@ fn teleport_native_assets_back_from_system_para_to_relay_works() { let test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: RococoReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -370,7 +511,7 @@ fn teleport_native_assets_from_system_para_to_relay_fails() { let test_args = TestContext { sender: AssetHubRococoSender::get(), receiver: RococoReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -410,3 +551,199 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalASender::get(); + let foreign_asset_at_asset_hub_rococo = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_rococo, + ah_as_seen_by_penpal, + false, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + let penpal_to_ah_beneficiary_id = AssetHubRococoReceiver::get(); + + let fee_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000; + let asset_amount_to_send = ASSET_MIN_BALANCE * 1000; + + let penpal_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = penpal_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // Penpal to AH test args + let penpal_to_ah_test_args = TestContext { + sender: PenpalASender::get(), + receiver: AssetHubRococoReceiver::get(), + args: TestArgs::new_para( + ah_as_seen_by_penpal, + penpal_to_ah_beneficiary_id, + asset_amount_to_send, + penpal_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args); + + let penpal_sender_balance_before = penpal_to_ah.sender.balance; + let ah_receiver_balance_before = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_before = PenpalA::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalASender::get()) + }); + let ah_receiver_assets_before = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoReceiver::get(), + ) + }); + + penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); + penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); + penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.assert(); + + let penpal_sender_balance_after = penpal_to_ah.sender.balance; + let ah_receiver_balance_after = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_after = PenpalA::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalASender::get()) + }); + let ah_receiver_assets_after = AssetHubRococo::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoReceiver::get(), + ) + }); + + // Sender's balance is reduced + assert!(penpal_sender_balance_after < penpal_sender_balance_before); + // Receiver's balance is increased + assert!(ah_receiver_balance_after > ah_receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send); + + /////////////////////////////////////////////////////////////////////// + // Now test transferring foreign assets back from AssetHub to Penpal // + /////////////////////////////////////////////////////////////////////// + + // Move funds on AH from AHReceiver to AHSender + AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + assert_ok!(ForeignAssets::transfer( + ::RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + foreign_asset_at_asset_hub_rococo, + AssetHubRococoSender::get().into(), + asset_amount_to_send, + )); + }); + + let ah_to_penpal_beneficiary_id = PenpalAReceiver::get(); + let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id()); + let ah_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_rococo, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = ah_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // AH to Penpal test args + let ah_to_penpal_test_args = TestContext { + sender: AssetHubRococoSender::get(), + receiver: PenpalAReceiver::get(), + args: TestArgs::new_para( + penpal_as_seen_by_ah, + ah_to_penpal_beneficiary_id, + asset_amount_to_send, + ah_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args); + + let ah_sender_balance_before = ah_to_penpal.sender.balance; + let penpal_receiver_balance_before = ah_to_penpal.receiver.balance; + + let ah_sender_assets_before = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoSender::get(), + ) + }); + let penpal_receiver_assets_before = PenpalA::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalAReceiver::get()) + }); + + ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); + ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); + ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.assert(); + + let ah_sender_balance_after = ah_to_penpal.sender.balance; + let penpal_receiver_balance_after = ah_to_penpal.receiver.balance; + + let ah_sender_assets_after = AssetHubRococo::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_rococo, + &AssetHubRococoSender::get(), + ) + }); + let penpal_receiver_assets_after = PenpalA::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalAReceiver::get()) + }); + + // Sender's balance is reduced + assert!(ah_sender_balance_after < ah_sender_balance_before); + // Receiver's balance is increased + assert!(penpal_receiver_balance_after > penpal_receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 74e6660d4d4f7525877aad4fbdade5d1fe2c4e44..3b2d3367d40d1984ee91fac96c37b524a5a97c27 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -7,6 +7,9 @@ license = "Apache-2.0" description = "Asset Hub Westend runtime integration tests with xcm-emulator" publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } assert_matches = "1.5.0" @@ -36,7 +39,7 @@ parachains-common = { path = "../../../../../../parachains/common" } asset-hub-westend-runtime = { path = "../../../../../runtimes/assets/asset-hub-westend" } asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../../pallets/dmp-queue" } +cumulus-pallet-xcmp-queue = { default-features = false, path = "../../../../../../pallets/xcmp-queue" } cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../../pallets/parachain-system" } emulated-integration-tests-common = { path = "../../../common", default-features = false } -penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } westend-system-emulated-network = { path = "../../../networks/westend-system" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index e2c03d2f8f0d12d338c4019b19193ca326ba0838..fbeb08649e78380c63799c4383bfabf08b1a25a8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -47,11 +47,16 @@ pub use westend_system_emulated_network::{ asset_hub_westend_emulated_chain::{ genesis::ED as ASSET_HUB_WESTEND_ED, AssetHubWestendParaPallet as AssetHubWestendPallet, }, + collectives_westend_emulated_chain::{ + genesis::ED as COLLECTIVES_WESTEND_ED, + CollectivesWestendParaPallet as CollectivesWestendPallet, + }, penpal_emulated_chain::PenpalBParaPallet as PenpalBPallet, westend_emulated_chain::{genesis::ED as WESTEND_ED, WestendRelayPallet as WestendPallet}, AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver, AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubWestendPara as BridgeHubWestend, - BridgeHubWestendParaReceiver as BridgeHubWestendReceiver, PenpalBPara as PenpalB, + BridgeHubWestendParaReceiver as BridgeHubWestendReceiver, + CollectivesWestendPara as CollectivesWestend, PenpalBPara as PenpalB, PenpalBParaReceiver as PenpalBReceiver, PenpalBParaSender as PenpalBSender, WestendRelay as Westend, WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, @@ -68,42 +73,5 @@ pub type SystemParaToRelayTest = Test; pub type SystemParaToParaTest = Test; pub type ParaToSystemParaTest = Test; -/// Returns a `TestArgs` instance to be used for the Relay Chain across integration tests -pub fn relay_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets: (Here, amount).into(), - asset_id: None, - fee_asset_item: 0, - weight_limit: WeightLimit::Unlimited, - } -} - -/// Returns a `TestArgs` instance to be used by parachains across integration tests -pub fn para_test_args( - dest: MultiLocation, - beneficiary_id: AccountId32, - amount: Balance, - assets: MultiAssets, - asset_id: Option, - fee_asset_item: u32, -) -> TestArgs { - TestArgs { - dest, - beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), - amount, - assets, - asset_id, - fee_asset_item, - weight_limit: WeightLimit::Unlimited, - } -} - #[cfg(test)] mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7de0a451f202217cdde1c32b8a4dd1b853ef861 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/fellowship_treasury.rs @@ -0,0 +1,131 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use emulated_integration_tests_common::accounts::{ALICE, BOB}; +use frame_support::traits::fungibles::{Create, Inspect, Mutate}; +use polkadot_runtime_common::impls::VersionedLocatableAsset; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn create_and_claim_treasury_spend() { + const ASSET_ID: u32 = 1984; + const SPEND_AMOUNT: u128 = 1_000_000; + // treasury location from a sibling parachain. + let treasury_location: MultiLocation = MultiLocation::new( + 1, + X2(Parachain(CollectivesWestend::para_id().into()), PalletInstance(65)), + ); + // treasury account on a sibling parachain. + let treasury_account = + asset_hub_westend_runtime::xcm_config::LocationToAccountId::convert_location( + &treasury_location, + ) + .unwrap(); + let asset_hub_location = MultiLocation::new(1, Parachain(AssetHubWestend::para_id().into())); + let root = ::RuntimeOrigin::root(); + // asset kind to be spent from the treasury. + let asset_kind = VersionedLocatableAsset::V3 { + location: asset_hub_location, + asset_id: AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()), + }; + // treasury spend beneficiary. + let alice: AccountId = Westend::account_id_of(ALICE); + let bob: AccountId = CollectivesWestend::account_id_of(BOB); + let bob_signed = ::RuntimeOrigin::signed(bob.clone()); + + AssetHubWestend::execute_with(|| { + type Assets = ::Assets; + + // create an asset class and mint some assets to the treasury account. + assert_ok!(>::create( + ASSET_ID, + treasury_account.clone(), + true, + SPEND_AMOUNT / 2 + )); + assert_ok!(>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4)); + // beneficiary has zero balance. + assert_eq!(>::balance(ASSET_ID, &alice,), 0u128,); + }); + + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type FellowshipTreasury = + ::FellowshipTreasury; + type AssetRate = ::AssetRate; + + // create a conversion rate from `asset_kind` to the native currency. + assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + + // create and approve a treasury spend. + assert_ok!(FellowshipTreasury::spend( + root, + Box::new(asset_kind), + SPEND_AMOUNT, + Box::new(MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())).into()), + None, + )); + // claim the spend. + assert_ok!(FellowshipTreasury::payout(bob_signed.clone(), 0)); + + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::Paid { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Assets = ::Assets; + + // assert events triggered by xcm pay program + // 1. treasury asset transferred to spend beneficiary + // 2. response to the Fellowship treasury pallet instance sent back + // 3. XCM program completed + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::Assets(pallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => { + id: id == &ASSET_ID, + from: from == &treasury_account, + to: to == &alice, + amount: amount == &SPEND_AMOUNT, + }, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true ,.. }) => {}, + ] + ); + // beneficiary received the assets from the treasury. + assert_eq!(>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,); + }); + + CollectivesWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type FellowshipTreasury = + ::FellowshipTreasury; + + // check the payment status to ensure the response from the AssetHub was received. + assert_ok!(FellowshipTreasury::check_status(bob_signed, 0)); + assert_expected_events!( + CollectivesWestend, + vec![ + RuntimeEvent::FellowshipTreasury(pallet_treasury::Event::SpendProcessed { .. }) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0c9de89c5f98f04a8a8f79048faf260c7bae27d5..ee720c2448041c085ee53c014c8d8125e7ee8caa 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -13,9 +13,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod fellowship_treasury; mod reserve_transfer; mod send; mod set_xcm_versions; mod swap; mod teleport; mod treasury; + +use crate::*; +emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!( + PenpalB, + AssetHubWestend, + WESTEND_ED, + parachains_common::westend::fee::WeightToFee +); 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 1a69a4f3f71dcd31ac08fb861ef59e70156c5b7e..3cce6c4417ea3a8fc6f3e4ffaa8620c0e4dd2cfc 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 @@ -15,8 +15,8 @@ use crate::*; use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig; -use penpal_runtime::xcm_config::XcmConfig as PenpalWestendXcmConfig; use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; +use westend_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalWestendXcmConfig; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -162,7 +162,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { ); } -fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { +fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult { ::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -173,7 +173,7 @@ fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> Dispatch ) } -fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { +fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -184,7 +184,7 @@ fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) ) } -fn para_to_system_para_limited_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { +fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -274,7 +274,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { let test_args = TestContext { sender: WestendSender::get(), receiver: PenpalBReceiver::get(), - args: relay_test_args(destination, beneficiary_id, amount_to_send), + args: TestArgs::new_relay(destination, beneficiary_id, amount_to_send), }; let mut test = RelayToParaTest::new(test_args); @@ -284,7 +284,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() { test.set_assertion::(relay_to_para_sender_assertions); test.set_assertion::(para_receiver_assertions); - test.set_dispatchable::(relay_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(relay_to_para_reserve_transfer_assets); test.assert(); let delivery_fees = Westend::execute_with(|| { @@ -318,7 +318,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { let test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: PenpalBReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToParaTest::new(test_args); @@ -328,7 +328,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() { test.set_assertion::(system_para_to_para_sender_assertions); test.set_assertion::(para_receiver_assertions); - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -362,7 +362,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { let test_args = TestContext { sender: PenpalBSender::get(), receiver: AssetHubWestendReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = ParaToSystemParaTest::new(test_args); @@ -379,7 +379,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() { test.set_assertion::(para_to_system_para_sender_assertions); test.set_assertion::(para_to_system_para_receiver_assertions); - test.set_dispatchable::(para_to_system_para_limited_reserve_transfer_assets); + test.set_dispatchable::(para_to_system_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; @@ -443,7 +443,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { let para_test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: PenpalBReceiver::get(), - args: para_test_args( + args: TestArgs::new_para( destination, beneficiary_id, asset_amount_to_send, @@ -474,7 +474,7 @@ fn reserve_transfer_assets_from_system_para_to_para() { test.set_assertion::(system_para_to_para_assets_sender_assertions); test.set_assertion::(system_para_to_para_assets_receiver_assertions); - test.set_dispatchable::(system_para_to_para_limited_reserve_transfer_assets); + test.set_dispatchable::(system_para_to_para_reserve_transfer_assets); test.assert(); let sender_balance_after = test.sender.balance; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs index aca60f688026e6aa68bc23c05a6550086020ed4f..47b6ab01e8f803758ea57bc8a853df2a6a2632dc 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/swap.rs @@ -14,14 +14,15 @@ // limitations under the License. use crate::*; +use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; #[test] fn swap_locally_on_chain_using_local_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); - let asset_one = Box::new(MultiLocation { + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); + let asset_one = MultiLocation { parents: 0, interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())), - }); + }; AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -43,8 +44,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), )); assert_expected_events!( @@ -56,8 +57,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::add_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - asset_one.clone(), + Box::new(asset_native), + Box::new(asset_one), 1_000_000_000_000, 2_000_000_000_000, 0, @@ -72,7 +73,7 @@ fn swap_locally_on_chain_using_local_assets() { ] ); - let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]); + let path = vec![Box::new(asset_native), Box::new(asset_one)]; assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), @@ -95,8 +96,8 @@ fn swap_locally_on_chain_using_local_assets() { assert_ok!(::AssetConversion::remove_liquidity( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native, - asset_one, + Box::new(asset_native), + Box::new(asset_one), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, @@ -107,113 +108,37 @@ fn swap_locally_on_chain_using_local_assets() { #[test] fn swap_locally_on_chain_using_foreign_assets() { - use frame_support::weights::WeightToFee; - - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); - - let foreign_asset1_at_asset_hub_westend = Box::new(MultiLocation { - parents: 1, - interior: X3( - Parachain(PenpalB::para_id().into()), - PalletInstance(ASSETS_PALLET_ID), - GeneralIndex(ASSET_ID.into()), - ), - }); - - let assets_para_destination: VersionedMultiLocation = - MultiLocation { parents: 1, interior: X1(Parachain(AssetHubWestend::para_id().into())) } - .into(); - - let penpal_location = - MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) }; - - // 1. Create asset on penpal: - PenpalB::execute_with(|| { - assert_ok!(::Assets::create( - ::RuntimeOrigin::signed(PenpalBSender::get()), - ASSET_ID.into(), - PenpalBSender::get().into(), - 1000, - )); - - assert!(::Assets::asset_exists(ASSET_ID)); - }); - - // 2. Create foreign asset on asset_hub_westend: - - let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000); - let origin_kind = OriginKind::Xcm; - let sov_penpal_on_asset_hub_westend = AssetHubWestend::sovereign_account_id_of(penpal_location); - - AssetHubWestend::fund_accounts(vec![ - (AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED), - (sov_penpal_on_asset_hub_westend.clone().into(), 1000_000_000_000_000_000 * WESTEND_ED), - ]); - - let sov_penpal_on_asset_hub_westend_as_location: MultiLocation = MultiLocation { - parents: 0, - interior: X1(AccountId32Junction { - network: None, - id: sov_penpal_on_asset_hub_westend.clone().into(), - }), + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); + let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), }; - - let call_foreign_assets_create = - ::RuntimeCall::ForeignAssets(pallet_assets::Call::< - ::Runtime, - Instance2, - >::create { - id: *foreign_asset1_at_asset_hub_westend, - min_balance: 1000, - admin: sov_penpal_on_asset_hub_westend.clone().into(), - }) - .encode() - .into(); - - let buy_execution_fee_amount = parachains_common::westend::fee::WeightToFee::weight_to_fee( - &Weight::from_parts(10_100_000_000_000, 300_000), + let asset_owner_on_penpal = PenpalBSender::get(); + let foreign_asset_at_asset_hub_westend = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + + // 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_westend + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_westend, + ah_as_seen_by_penpal, + true, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, ); - let buy_execution_fee = MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(buy_execution_fee_amount), - }; - - let xcm = VersionedXcm::from(Xcm(vec![ - WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() }, - BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { require_weight_at_most, origin_kind, call: call_foreign_assets_create }, - RefundSurplus, - DepositAsset { - assets: All.into(), - beneficiary: sov_penpal_on_asset_hub_westend_as_location, - }, - ])); - - // Send XCM message from penpal => asset_hub_westend - let sudo_penpal_origin = ::RuntimeOrigin::root(); - PenpalB::execute_with(|| { - assert_ok!(::PolkadotXcm::send( - sudo_penpal_origin.clone(), - bx!(assets_para_destination.clone()), - bx!(xcm), - )); - type RuntimeEvent = ::RuntimeEvent; - - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, - ] - ); - }); + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_as_seen_by_ah); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED), /* An account to swap dot + * for something else. */ + ]); - // Receive XCM message in Assets Parachain in the next block. AssetHubWestend::execute_with(|| { - assert!(::ForeignAssets::asset_exists( - *foreign_asset1_at_asset_hub_westend - )); - // 3: Mint foreign asset on asset_hub_westend: // // (While it might be nice to use batch, @@ -222,11 +147,9 @@ fn swap_locally_on_chain_using_foreign_assets() { type RuntimeEvent = ::RuntimeEvent; // 3. Mint foreign asset (in reality this should be a teleport or some such) assert_ok!(::ForeignAssets::mint( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone().into() - ), - *foreign_asset1_at_asset_hub_westend, - sov_penpal_on_asset_hub_westend.clone().into(), + ::RuntimeOrigin::signed(sov_penpal_on_ahw.clone().into()), + foreign_asset_at_asset_hub_westend, + sov_penpal_on_ahw.clone().into(), 3_000_000_000_000, )); @@ -240,8 +163,8 @@ fn swap_locally_on_chain_using_foreign_assets() { // 4. Create pool: assert_ok!(::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), )); assert_expected_events!( @@ -253,16 +176,14 @@ fn swap_locally_on_chain_using_foreign_assets() { // 5. Add liquidity: assert_ok!(::AssetConversion::add_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone() - ), - asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), + ::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), 1_000_000_000_000, 2_000_000_000_000, 0, 0, - sov_penpal_on_asset_hub_westend.clone().into() + sov_penpal_on_ahw.clone().into() )); assert_expected_events!( @@ -275,10 +196,7 @@ fn swap_locally_on_chain_using_foreign_assets() { ); // 6. Swap! - let path = BoundedVec::<_, _>::truncate_from(vec![ - asset_native.clone(), - foreign_asset1_at_asset_hub_westend.clone(), - ]); + let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_westend)]; assert_ok!(::AssetConversion::swap_exact_tokens_for_tokens( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), @@ -301,22 +219,20 @@ fn swap_locally_on_chain_using_foreign_assets() { // 7. Remove liquidity assert_ok!(::AssetConversion::remove_liquidity( - ::RuntimeOrigin::signed( - sov_penpal_on_asset_hub_westend.clone() - ), - asset_native, - foreign_asset1_at_asset_hub_westend, + ::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()), + Box::new(asset_native), + Box::new(foreign_asset_at_asset_hub_westend), 1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved. 0, 0, - sov_penpal_on_asset_hub_westend.clone().into(), + sov_penpal_on_ahw.into(), )); }); } #[test] fn cannot_create_pool_from_pool_assets() { - let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get()); + let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get(); let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocation::get(); asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets"); @@ -341,10 +257,10 @@ fn cannot_create_pool_from_pool_assets() { assert_matches::assert_matches!( ::AssetConversion::create_pool( ::RuntimeOrigin::signed(AssetHubWestendSender::get()), - asset_native.clone(), + Box::new(asset_native), Box::new(asset_one), ), - Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset")) + Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("Unknown")) ); }); } 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 2c43bb9d8018d585a7deefe065175bb0c4c93eea..27a6e7aaaf439a8722d760fa2d0fdf99d433f3e9 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 @@ -15,7 +15,9 @@ use crate::*; use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig; +use emulated_integration_tests_common::xcm_helpers::non_fee_asset; use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; +use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub; fn relay_origin_assertions(t: RelayToSystemParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -110,6 +112,123 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { ); } +fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + PenpalB::assert_xcm_pallet_attempted_complete(None); + let expected_asset_id = t.args.asset_id.unwrap(); + let (_, expected_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_asset_amount, + }, + ] + ); +} + +fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalB::para_id()), + ); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubWestend, + vec![ + // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account + RuntimeEvent::Balances( + pallet_balances::Event::Withdraw { who, amount } + ) => { + who: *who == sov_penpal_on_ahr.clone().into(), + amount: *amount == t.args.amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_foreign_asset_amount, + }, + RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + +fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + AssetHubWestend::assert_xcm_pallet_attempted_complete(None); + let (expected_foreign_asset_id, expected_foreign_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + assert_expected_events!( + AssetHubWestend, + vec![ + // native asset used for fees is transferred to Parachain's Sovereign account as reserve + RuntimeEvent::Balances( + pallet_balances::Event::Transfer { from, to, amount } + ) => { + from: *from == t.sender.account_id, + to: *to == AssetHubWestend::sovereign_account_id_of( + t.args.dest + ), + amount: *amount == t.args.amount, + }, + // foreign asset is burned locally as part of teleportation + RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_foreign_asset_id, + owner: *owner == t.sender.account_id, + balance: *balance == expected_foreign_asset_amount, + }, + ] + ); +} + +fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { + type RuntimeEvent = ::RuntimeEvent; + let expected_asset_id = t.args.asset_id.unwrap(); + let (_, expected_asset_amount) = + non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap(); + let checking_account = ::PolkadotXcm::check_account(); + assert_expected_events!( + PenpalB, + vec![ + // checking account burns local asset as part of incoming teleport + RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == checking_account, + balance: *balance == expected_asset_amount, + }, + // local asset is teleported into account of receiver + RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { + asset_id: *asset_id == expected_asset_id, + owner: *owner == t.receiver.account_id, + amount: *amount == expected_asset_amount, + }, + // native asset for fee is deposited to receiver + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); +} + fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { ::XcmPallet::limited_teleport_assets( t.signed_origin, @@ -152,6 +271,28 @@ fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { ) } +fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult { + ::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult { + ::PolkadotXcm::transfer_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + /// Limited Teleport of native asset from Relay Chain to the System Parachain should work #[test] fn limited_teleport_native_assets_from_relay_to_system_para_works() { @@ -162,7 +303,7 @@ fn limited_teleport_native_assets_from_relay_to_system_para_works() { let test_args = TestContext { sender: WestendSender::get(), receiver: beneficiary.clone(), - args: relay_test_args(dest, beneficiary, amount_to_send), + args: TestArgs::new_relay(dest, beneficiary, amount_to_send), }; let mut test = RelayToSystemParaTest::new(test_args); @@ -206,7 +347,7 @@ fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { let test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: WestendReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -247,7 +388,7 @@ fn limited_teleport_native_assets_from_system_para_to_relay_fails() { let test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: WestendReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -285,7 +426,7 @@ fn teleport_native_assets_from_relay_to_system_para_works() { let test_args = TestContext { sender: WestendSender::get(), receiver: beneficiary.clone(), - args: relay_test_args(dest, beneficiary, amount_to_send), + args: TestArgs::new_relay(dest, beneficiary, amount_to_send), }; let mut test = RelayToSystemParaTest::new(test_args); @@ -329,7 +470,7 @@ fn teleport_native_assets_back_from_system_para_to_relay_works() { let test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: WestendReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -370,7 +511,7 @@ fn teleport_native_assets_from_system_para_to_relay_fails() { let test_args = TestContext { sender: AssetHubWestendSender::get(), receiver: WestendReceiver::get(), - args: para_test_args(destination, beneficiary_id, amount_to_send, assets, None, 0), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), }; let mut test = SystemParaToRelayTest::new(test_args); @@ -410,3 +551,199 @@ fn teleport_to_other_system_parachains_works() { (native_asset, amount) ); } + +/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work +/// (using native reserve-based transfer for fees) +#[test] +fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() { + let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id()); + let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get(); + let asset_id_on_penpal = match asset_location_on_penpal.last() { + Some(GeneralIndex(id)) => *id as u32, + _ => unreachable!(), + }; + let asset_owner_on_penpal = PenpalBSender::get(); + let foreign_asset_at_asset_hub_westend = + MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) } + .appended_with(asset_location_on_penpal) + .unwrap(); + super::penpal_create_foreign_asset_on_asset_hub( + asset_id_on_penpal, + foreign_asset_at_asset_hub_westend, + ah_as_seen_by_penpal, + false, + asset_owner_on_penpal, + ASSET_MIN_BALANCE * 1_000_000, + ); + let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get(); + + let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 1000; + let asset_amount_to_send = ASSET_MIN_BALANCE * 1000; + + let penpal_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (asset_location_on_penpal, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = penpal_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // Penpal to AH test args + let penpal_to_ah_test_args = TestContext { + sender: PenpalBSender::get(), + receiver: AssetHubWestendReceiver::get(), + args: TestArgs::new_para( + ah_as_seen_by_penpal, + penpal_to_ah_beneficiary_id, + asset_amount_to_send, + penpal_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args); + + let penpal_sender_balance_before = penpal_to_ah.sender.balance; + let ah_receiver_balance_before = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_before = PenpalB::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalBSender::get()) + }); + let ah_receiver_assets_before = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendReceiver::get(), + ) + }); + + penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_sender_assertions); + penpal_to_ah.set_assertion::(penpal_to_ah_foreign_assets_receiver_assertions); + penpal_to_ah.set_dispatchable::(para_to_system_para_transfer_assets); + penpal_to_ah.assert(); + + let penpal_sender_balance_after = penpal_to_ah.sender.balance; + let ah_receiver_balance_after = penpal_to_ah.receiver.balance; + + let penpal_sender_assets_after = PenpalB::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalBSender::get()) + }); + let ah_receiver_assets_after = AssetHubWestend::execute_with(|| { + type Assets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendReceiver::get(), + ) + }); + + // Sender's balance is reduced + assert!(penpal_sender_balance_after < penpal_sender_balance_before); + // Receiver's balance is increased + assert!(ah_receiver_balance_after > ah_receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send); + + /////////////////////////////////////////////////////////////////////// + // Now test transferring foreign assets back from AssetHub to Penpal // + /////////////////////////////////////////////////////////////////////// + + // Move funds on AH from AHReceiver to AHSender + AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + assert_ok!(ForeignAssets::transfer( + ::RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + foreign_asset_at_asset_hub_westend, + AssetHubWestendSender::get().into(), + asset_amount_to_send, + )); + }); + + let ah_to_penpal_beneficiary_id = PenpalBReceiver::get(); + let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id()); + let ah_assets: MultiAssets = vec![ + (Parent, fee_amount_to_send).into(), + (foreign_asset_at_asset_hub_westend, asset_amount_to_send).into(), + ] + .into(); + let fee_asset_index = ah_assets + .inner() + .iter() + .position(|r| r == &(Parent, fee_amount_to_send).into()) + .unwrap() as u32; + + // AH to Penpal test args + let ah_to_penpal_test_args = TestContext { + sender: AssetHubWestendSender::get(), + receiver: PenpalBReceiver::get(), + args: TestArgs::new_para( + penpal_as_seen_by_ah, + ah_to_penpal_beneficiary_id, + asset_amount_to_send, + ah_assets, + Some(asset_id_on_penpal), + fee_asset_index, + ), + }; + let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args); + + let ah_sender_balance_before = ah_to_penpal.sender.balance; + let penpal_receiver_balance_before = ah_to_penpal.receiver.balance; + + let ah_sender_assets_before = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendSender::get(), + ) + }); + let penpal_receiver_assets_before = PenpalB::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalBReceiver::get()) + }); + + ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_sender_assertions); + ah_to_penpal.set_assertion::(ah_to_penpal_foreign_assets_receiver_assertions); + ah_to_penpal.set_dispatchable::(system_para_to_para_transfer_assets); + ah_to_penpal.assert(); + + let ah_sender_balance_after = ah_to_penpal.sender.balance; + let penpal_receiver_balance_after = ah_to_penpal.receiver.balance; + + let ah_sender_assets_after = AssetHubWestend::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance( + foreign_asset_at_asset_hub_westend, + &AssetHubWestendSender::get(), + ) + }); + let penpal_receiver_assets_after = PenpalB::execute_with(|| { + type Assets = ::Assets; + >::balance(asset_id_on_penpal, &PenpalBReceiver::get()) + }); + + // Sender's balance is reduced + assert!(ah_sender_balance_after < ah_sender_balance_before); + // Receiver's balance is increased + assert!(penpal_receiver_balance_after > penpal_receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send); + + // Sender's balance is reduced by exact amount + assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after); + // Receiver's balance is increased by exact amount + assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send); +} 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 826b55507ed3de79fc617b014d59efe2d68acff1..e75187bea95eb7c64d84b37a7dadd3b4758b6e87 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 @@ -7,14 +7,22 @@ license = "Apache-2.0" description = "Bridge Hub Rococo runtime integration tests with xcm-emulator" publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +hex = "0.4.3" +hex-literal = "0.4.1" # Substrate +sp-core = { path = "../../../../../../../substrate/primitives/core", default-features = false } frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" } +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } # Polkadot xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } @@ -33,3 +41,14 @@ cumulus-pallet-dmp-queue = { path = "../../../../../../pallets/dmp-queue", defau bridge-hub-rococo-runtime = { path = "../../../../../../parachains/runtimes/bridge-hubs/bridge-hub-rococo", default-features = false } emulated-integration-tests-common = { path = "../../../common", default-features = false } rococo-westend-system-emulated-network = { path = "../../../networks/rococo-westend-system" } +penpal-runtime = { path = "../../../../../runtimes/testing/penpal", default-features = false } +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-system = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } +snowbridge-inbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } +snowbridge-outbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } +snowbridge-rococo-common = { path = "../../../../../../../bridges/snowbridge/parachain/runtime/rococo-common", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 53665437887c95940dba08485dc19364a027a29e..5127bd759dc63e6797e6adbe379ced5fa1e96bcc 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -14,10 +14,12 @@ // limitations under the License. // Substrate -pub use frame_support::assert_ok; +pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; +pub use sp_runtime::DispatchError; // Polkadot pub use xcm::{ + latest::ParentThen, prelude::{AccountId32 as AccountId32Junction, *}, v3::{ Error, @@ -41,6 +43,11 @@ pub use emulated_integration_tests_common::{ PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, }; pub use parachains_common::{AccountId, Balance}; +pub use rococo_system_emulated_network::{ + penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet, + BridgeHubRococoParaReceiver as BridgeHubRococoReceiver, PenpalAPara as PenpalA, + PenpalAParaReceiver as PenpalAReceiver, PenpalAParaSender as PenpalASender, +}; pub use rococo_westend_system_emulated_network::{ asset_hub_rococo_emulated_chain::{ genesis::ED as ASSET_HUB_ROCOCO_ED, AssetHubRococoParaPallet as AssetHubRococoPallet, @@ -51,12 +58,13 @@ pub use rococo_westend_system_emulated_network::{ bridge_hub_rococo_emulated_chain::{ genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet, }, - rococo_emulated_chain::RococoRelayPallet as RococoPallet, + rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver, AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend, AssetHubWestendParaReceiver as AssetHubWestendReceiver, BridgeHubRococoPara as BridgeHubRococo, BridgeHubRococoParaSender as BridgeHubRococoSender, BridgeHubWestendPara as BridgeHubWestend, - RococoRelay as Rococo, + RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, + RococoRelaySender as RococoSender, }; pub const ASSET_ID: u32 = 1; 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 c55613f2826ffb0696cbe8046ea4f4bc4f425310..5a2111a9be940f70a168e0fcf76a97cd7f839e3b 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 @@ -13,70 +13,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::tests::*; fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: MultiLocation, amount: u128) { - let signed_origin = - ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); - let asset_hub_westend_para_id = AssetHubWestend::para_id().into(); - let destination = MultiLocation { - parents: 2, - interior: X2(GlobalConsensus(NetworkId::Westend), Parachain(asset_hub_westend_para_id)), - }; - let beneficiary_id = AssetHubWestendReceiver::get(); - let beneficiary: MultiLocation = - AccountId32Junction { network: None, id: beneficiary_id.into() }.into(); - let assets: MultiAssets = (id, amount).into(); - let fee_asset_item = 0; + let destination = asset_hub_westend_location(); // fund the AHR's SA on BHR for paying bridge transport fees - let ahr_as_seen_by_bhr = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id()); - let sov_ahr_on_bhr = BridgeHubRococo::sovereign_account_id_of(ahr_as_seen_by_bhr); - BridgeHubRococo::fund_accounts(vec![(sov_ahr_on_bhr.into(), 10_000_000_000_000u128)]); - - AssetHubRococo::execute_with(|| { - assert_ok!( - ::PolkadotXcm::limited_reserve_transfer_assets( - signed_origin, - bx!(destination.into()), - bx!(beneficiary.into()), - bx!(assets.into()), - fee_asset_item, - WeightLimit::Unlimited, - ) - ); - }); + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, - // message exported - RuntimeEvent::BridgeWestendMessages( - pallet_bridge_messages::Event::MessageAccepted { .. } - ) => {}, - // message processed successfully - RuntimeEvent::MessageQueue( - pallet_message_queue::Event::Processed { success: true, .. } - ) => {}, - ] - ); - }); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - // message dispatched successfully - RuntimeEvent::XcmpQueue( - cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } - ) => {}, - ] - ); - }); + // set XCM versions + AssetHubRococo::force_xcm_version(destination, XCM_VERSION); + BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(send_asset_from_asset_hub_rococo(destination, (id, amount))); + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); } #[test] 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 4e2ef1434fdfd7a098d6dd63c51bd2a47514be22..e71a022af4cf4793d59b3e557a70a29ae0aa7121 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 @@ -13,6 +13,103 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::*; + mod asset_transfers; mod send_xcm; +mod snowbridge; mod teleport; + +pub(crate) fn asset_hub_westend_location() -> MultiLocation { + MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(NetworkId::Westend), + Parachain(AssetHubWestend::para_id().into()), + ), + } +} + +pub(crate) fn bridge_hub_westend_location() -> MultiLocation { + MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(NetworkId::Westend), + Parachain(BridgeHubWestend::para_id().into()), + ), + } +} + +pub(crate) fn send_asset_from_asset_hub_rococo( + destination: MultiLocation, + (id, amount): (MultiLocation, u128), +) -> DispatchResult { + let signed_origin = + ::RuntimeOrigin::signed(AssetHubRococoSender::get().into()); + + let beneficiary: MultiLocation = + AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into(); + + let assets: MultiAssets = (id, amount).into(); + let fee_asset_item = 0; + + AssetHubRococo::execute_with(|| { + ::PolkadotXcm::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ) + }) +} + +pub(crate) fn assert_bridge_hub_rococo_message_accepted(expected_processed: bool) { + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + if expected_processed { + assert_expected_events!( + BridgeHubRococo, + vec![ + // pay for bridge fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + // message exported + RuntimeEvent::BridgeWestendMessages( + pallet_bridge_messages::Event::MessageAccepted { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } else { + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: false, + .. + }) => {}, + ] + ); + } + }); +} + +pub(crate) fn assert_bridge_hub_westend_message_received() { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubWestend, + vec![ + // message sent to destination + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }) +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 4e61f7ce0ddb771ce88810bf98935f2546842da0..a3a7d96a14ae2a2a32aee4849cbfda82254a50fe 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::tests::*; #[test] -fn send_xcm_from_rococo_relay_to_westend_asset_hub() { +fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable() { // Init tests variables // XcmPallet send arguments let sudo_origin = ::RuntimeOrigin::root(); @@ -53,48 +53,125 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub() { ] ); }); - // Receive XCM message in Bridge Hub source Parachain - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; + // Receive XCM message in Bridge Hub source Parachain, it should fail, because we don't have + // opened bridge/lane. + assert_bridge_hub_rococo_message_accepted(false); +} - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { - success: true, - .. - }) => {}, - RuntimeEvent::BridgeWestendMessages(pallet_bridge_messages::Event::MessageAccepted { - lane_id: LaneId([0, 0, 0, 2]), - nonce: 1, - }) => {}, - ] - ); - }); +#[test] +fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { + // Initially set only default version on all runtimes + AssetHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + BridgeHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + AssetHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); - // Westend Global Consensus - // Receive XCM message in Bridge Hub target Parachain - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; + // prepare data + let destination = asset_hub_westend_location(); + let native_token = MultiLocation::parent(); + let amount = ASSET_HUB_ROCOCO_ED * 1_000; - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] + // fund the AHR's SA on BHR for paying bridge transport fees + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); + // fund sender + AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]); + + // send XCM from AssetHubRococo - fails - destination version not known + assert_err!( + send_asset_from_asset_hub_rococo(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // set destination version + AssetHubRococo::force_xcm_version(destination, xcm::v3::prelude::XCM_VERSION); + + // TODO: remove this block, when removing `xcm:v2` + { + // send XCM from AssetHubRococo - fails - AssetHubRococo is set to the default/safe `2` + // version, which does not have the `ExportMessage` instruction. If the default `2` is + // changed to `3`, then this assert can go away" + assert_err!( + send_asset_from_asset_hub_rococo(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) ); - }); - // Receive embedded XCM message within `ExportMessage` in Parachain destination + + // set exact version for BridgeHubWestend to `2` without `ExportMessage` instruction + AssetHubRococo::force_xcm_version( + ParentThen(Parachain(BridgeHubRococo::para_id().into()).into()).into(), + xcm::v2::prelude::XCM_VERSION, + ); + // send XCM from AssetHubRococo - fails - `ExportMessage` is not in `2` + assert_err!( + send_asset_from_asset_hub_rococo(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + } + + // set version with `ExportMessage` for BridgeHubRococo + AssetHubRococo::force_xcm_version( + ParentThen(Parachain(BridgeHubRococo::para_id().into()).into()).into(), + xcm::v3::prelude::XCM_VERSION, + ); + // send XCM from AssetHubRococo - ok + assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount))); + + // `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known + assert_bridge_hub_rococo_message_accepted(false); + + // set version for remote BridgeHub on BridgeHubRococo + BridgeHubRococo::force_xcm_version( + bridge_hub_westend_location(), + xcm::v3::prelude::XCM_VERSION, + ); + // set version for AssetHubWestend on BridgeHubWestend + BridgeHubWestend::force_xcm_version( + ParentThen(Parachain(AssetHubWestend::para_id().into()).into()).into(), + xcm::v3::prelude::XCM_VERSION, + ); + + // send XCM from AssetHubRococo - ok + assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount))); + assert_bridge_hub_rococo_message_accepted(true); + assert_bridge_hub_westend_message_received(); + // message delivered and processed at destination AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( AssetHubWestend, vec![ - RuntimeEvent::MessageQueue(pallet_message_queue::Event::ProcessingFailed { - .. - }) => {}, + // message processed with failure, but for this scenario it is ok, important is that was delivered + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: false, .. } + ) => {}, ] ); }); + + // TODO: remove this block, when removing `xcm:v2` + { + // set `2` version for remote BridgeHub on BridgeHubRococo, which does not have + // `UniversalOrigin` and `DescendOrigin` + BridgeHubRococo::force_xcm_version( + bridge_hub_westend_location(), + xcm::v2::prelude::XCM_VERSION, + ); + + // send XCM from AssetHubRococo - ok + assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount))); + // message is not accepted on the local BridgeHub (`DestinationUnsupported`) because we + // cannot add `UniversalOrigin` and `DescendOrigin` + assert_bridge_hub_rococo_message_accepted(false); + } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..e62a73caff589081c1899eb48b44afcecb5f23f6 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -0,0 +1,505 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::*; +use codec::{Decode, Encode}; +use emulated_integration_tests_common::xcm_emulator::ConvertLocation; +use frame_support::pallet_prelude::TypeInfo; +use hex_literal::hex; +use snowbridge_core::outbound::OperatingMode; +use snowbridge_rococo_common::EthereumNetwork; +use snowbridge_router_primitives::inbound::{ + Command, Destination, GlobalConsensusEthereumConvertsFor, MessageV1, VersionedMessage, +}; +use snowbridge_system; +use sp_core::H256; + +const INITIAL_FUND: u128 = 5_000_000_000 * ROCOCO_ED; +const CHAIN_ID: u64 = 11155111; +const TREASURY_ACCOUNT: [u8; 32] = + hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); +const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); +const XCM_FEE: u128 = 4_000_000_000; + +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum ControlCall { + #[codec(index = 3)] + CreateAgent, + #[codec(index = 4)] + CreateChannel { mode: OperatingMode }, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +pub enum SnowbridgeControl { + #[codec(index = 83)] + Control(ControlCall), +} + +#[test] +fn create_agent() { + let origin_para: u32 = 1001; + + BridgeHubRococo::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination = Rococo::child_location_of(BridgeHubRococo::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(X1(Parachain(origin_para))), + Transact { + require_weight_at_most: 3000000000.into(), + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + }, + ])); + + //Rococo Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Rococo::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(remote_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + +#[test] +fn create_channel() { + let origin_para: u32 = 1001; + + BridgeHubRococo::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination: VersionedMultiLocation = + Rococo::child_location_of(BridgeHubRococo::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + + let create_agent_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(X1(Parachain(origin_para))), + Transact { + require_weight_at_most: 3000000000.into(), + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + }, + ])); + + let create_channel_call = + SnowbridgeControl::Control(ControlCall::CreateChannel { mode: OperatingMode::Normal }); + + let create_channel_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(X1(Parachain(origin_para))), + Transact { + require_weight_at_most: 3000000000.into(), + origin_kind: OriginKind::Xcm, + call: create_channel_call.encode().into(), + }, + ])); + + //Rococo Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Rococo::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin.clone(), + bx!(destination.clone()), + bx!(create_agent_xcm), + )); + + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(create_channel_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + Rococo, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_system::Event::CreateChannel { + .. + }) => {}, + ] + ); + }); +} + +#[test] +fn register_weth_token_from_ethereum_to_asset_hub() { + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); + + let message_id_: H256 = [1; 32].into(); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(MultiLocation { + parents: 1, + interior: X1(Parachain(AssetHubRococo::para_id().into())), + }); + BridgeHubRococo::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + PenpalA::fund_accounts(vec![ + (PenpalAReceiver::get(), INITIAL_FUND), + (PenpalASender::get(), INITIAL_FUND), + ]); + + let weth_asset_location: MultiLocation = + (Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into(); + let weth_asset_id = weth_asset_location.into(); + + let origin_location = (Parent, Parent, EthereumNetwork::get()).into(); + + // Fund ethereum sovereign in asset hub + let ethereum_sovereign: AccountId = + GlobalConsensusEthereumConvertsFor::::convert_location(&origin_location) + .unwrap(); + AssetHubRococo::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on assethub. + AssetHubRococo::execute_with(|| { + assert_ok!(::ForeignAssets::create( + pallet_xcm::Origin::Xcm(origin_location).into(), + weth_asset_id, + asset_hub_sovereign.clone().into(), + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_id + )); + }); + + // Create asset on penpal. + PenpalA::execute_with(|| { + assert_ok!(::ForeignAssets::create( + ::RuntimeOrigin::signed(PenpalASender::get()), + weth_asset_id, + asset_hub_sovereign.into(), + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_id)); + }); + + let message_id_: H256 = [1; 32].into(); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: 2000, + id: PenpalAReceiver::get().into(), + fee: XCM_FEE, + }, + amount: 1_000_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalA::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PenpalA, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_token_from_ethereum_to_asset_hub() { + BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign in asset hub + AssetHubRococo::fund_accounts(vec![(AssetHubRococoReceiver::get(), INITIAL_FUND)]); + + let message_id_: H256 = [1; 32].into(); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubRococoReceiver::get().into() }, + amount: 1_000_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_asset_from_asset_hub_to_ethereum() { + use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(MultiLocation { + parents: 1, + interior: X1(Parachain(AssetHubRococo::para_id().into())), + }); + + AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubRococo::force_xcm_version( + MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(Ethereum { chain_id: CHAIN_ID })), + }, + XCM_VERSION, + ); + + BridgeHubRococo::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubRococo::fund_accounts(vec![(AssetHubRococoReceiver::get(), INITIAL_FUND)]); + + const WETH_AMOUNT: u128 = 1_000_000_000; + let message_id_: H256 = [1; 32].into(); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubRococoReceiver::get().into() }, + amount: WETH_AMOUNT, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id_, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + let assets = vec![MultiAsset { + id: Concrete(MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(Ethereum { chain_id: CHAIN_ID }), + AccountKey20 { network: None, key: WETH }, + ), + }), + fun: Fungible(WETH_AMOUNT), + }]; + let multi_assets = VersionedMultiAssets::V3(MultiAssets::from(assets)); + + let destination = VersionedMultiLocation::V3(MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(Ethereum { chain_id: CHAIN_ID })), + }); + + let beneficiary = VersionedMultiLocation::V3(MultiLocation { + parents: 0, + interior: X1(AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }), + }); + + let free_balance_before = ::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + ::PolkadotXcm::reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + ) + .unwrap(); + let free_balance_after = ::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + let events = BridgeHubRococo::events(); + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Deposit{ who, amount }) + if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 + )), + "Snowbridge sovereign takes local fee." + ); + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Deposit{ 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/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index cb53d7fc0e1cd11d4d2b268315f4ccc8267ce29e..6dcb57f416102b9edde4f286bc33b1434bb899fa 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -7,6 +7,9 @@ license = "Apache-2.0" description = "Bridge Hub Westend runtime integration tests with xcm-emulator" publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } @@ -15,6 +18,7 @@ frame-support = { path = "../../../../../../../substrate/frame/support", default pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" } +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } # Polkadot xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 04746aa86705290184e20e3a3f65cff25a60b375..90a11d38f777360805b47224feeff0d41d02eb2e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -14,10 +14,12 @@ // limitations under the License. // Substrate -pub use frame_support::assert_ok; +pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult}; +pub use sp_runtime::DispatchError; // Polkadot pub use xcm::{ + latest::ParentThen, prelude::{AccountId32 as AccountId32Junction, *}, v3::{ Error, 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 f90514f80c3e1b4514dcd3cf179a34d10b256909..21f4b4ee2356160494a2b54f841e7689799d554c 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 @@ -12,70 +12,22 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::tests::*; fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: MultiLocation, amount: u128) { - let signed_origin = - ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); - let asset_hub_rococo_para_id = AssetHubRococo::para_id().into(); - let destination = MultiLocation { - parents: 2, - interior: X2(GlobalConsensus(NetworkId::Rococo), Parachain(asset_hub_rococo_para_id)), - }; - let beneficiary_id = AssetHubRococoReceiver::get(); - let beneficiary: MultiLocation = - AccountId32Junction { network: None, id: beneficiary_id.into() }.into(); - let assets: MultiAssets = (id, amount).into(); - let fee_asset_item = 0; + let destination = asset_hub_rococo_location(); // fund the AHW's SA on BHW for paying bridge transport fees - let ahw_as_seen_by_bhw = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let sov_ahw_on_bhw = BridgeHubWestend::sovereign_account_id_of(ahw_as_seen_by_bhw); - BridgeHubWestend::fund_accounts(vec![(sov_ahw_on_bhw.into(), 10_000_000_000_000u128)]); - - AssetHubWestend::execute_with(|| { - assert_ok!( - ::PolkadotXcm::limited_reserve_transfer_assets( - signed_origin, - bx!(destination.into()), - bx!(beneficiary.into()), - bx!(assets.into()), - fee_asset_item, - WeightLimit::Unlimited, - ) - ); - }); + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubWestend, - vec![ - // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, - // message exported - RuntimeEvent::BridgeRococoMessages( - pallet_bridge_messages::Event::MessageAccepted { .. } - ) => {}, - // message processed successfully - RuntimeEvent::MessageQueue( - pallet_message_queue::Event::Processed { success: true, .. } - ) => {}, - ] - ); - }); - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( - BridgeHubRococo, - vec![ - // message dispatched successfully - RuntimeEvent::XcmpQueue( - cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } - ) => {}, - ] - ); - }); + // set XCM versions + AssetHubWestend::force_xcm_version(destination, XCM_VERSION); + BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); + + // send message over bridge + assert_ok!(send_asset_from_asset_hub_westend(destination, (id, amount))); + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); } #[test] 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 4e2ef1434fdfd7a098d6dd63c51bd2a47514be22..ec2e68fc8894bc676ec9137c0b36463db25761a7 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 @@ -13,6 +13,102 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::*; + mod asset_transfers; mod send_xcm; mod teleport; + +pub(crate) fn asset_hub_rococo_location() -> MultiLocation { + MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(NetworkId::Rococo), + Parachain(AssetHubRococo::para_id().into()), + ), + } +} + +pub(crate) fn bridge_hub_rococo_location() -> MultiLocation { + MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(NetworkId::Rococo), + Parachain(BridgeHubRococo::para_id().into()), + ), + } +} + +pub(crate) fn send_asset_from_asset_hub_westend( + destination: MultiLocation, + (id, amount): (MultiLocation, u128), +) -> DispatchResult { + let signed_origin = + ::RuntimeOrigin::signed(AssetHubWestendSender::get().into()); + + let beneficiary: MultiLocation = + AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into(); + + let assets: MultiAssets = (id, amount).into(); + let fee_asset_item = 0; + + AssetHubWestend::execute_with(|| { + ::PolkadotXcm::limited_reserve_transfer_assets( + signed_origin, + bx!(destination.into()), + bx!(beneficiary.into()), + bx!(assets.into()), + fee_asset_item, + WeightLimit::Unlimited, + ) + }) +} + +pub(crate) fn assert_bridge_hub_westend_message_accepted(expected_processed: bool) { + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + if expected_processed { + assert_expected_events!( + BridgeHubWestend, + vec![ + // pay for bridge fees + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + // message exported + RuntimeEvent::BridgeRococoMessages( + pallet_bridge_messages::Event::MessageAccepted { .. } + ) => {}, + // message processed successfully + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: true, .. } + ) => {}, + ] + ); + } else { + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: false, + .. + }) => {}, + ] + ); + } + }); +} + +pub(crate) fn assert_bridge_hub_rococo_message_received() { + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + assert_expected_events!( + BridgeHubRococo, + vec![ + // message sent to destination + RuntimeEvent::XcmpQueue( + cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } + ) => {}, + ] + ); + }) +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 4b21d758cd98384f11a1336f3373bf0127d5f7b9..0773cbb059929cc360c28cf776986559b43e0ced 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::*; +use crate::tests::*; #[test] -fn send_xcm_from_westend_relay_to_rococo_asset_hub() { +fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable() { // Init tests variables // XcmPallet send arguments let sudo_origin = ::RuntimeOrigin::root(); @@ -53,48 +53,125 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub() { ] ); }); - // Receive XCM message in Bridge Hub source Parachain - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; + // Receive XCM message in Bridge Hub source Parachain, it should fail, because we don't have + // opened bridge/lane. + assert_bridge_hub_westend_message_accepted(false); +} - assert_expected_events!( - BridgeHubWestend, - vec![ - RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { - success: true, - .. - }) => {}, - RuntimeEvent::BridgeRococoMessages(pallet_bridge_messages::Event::MessageAccepted { - lane_id: LaneId([0, 0, 0, 2]), - nonce: 1, - }) => {}, - ] - ); - }); +#[test] +fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { + // Initially set only default version on all runtimes + AssetHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + BridgeHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); + AssetHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION)); - // Rococo Global Consensus - // Receive XCM message in Bridge Hub target Parachain - BridgeHubRococo::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; + // prepare data + let destination = asset_hub_rococo_location(); + let native_token = MultiLocation::parent(); + let amount = ASSET_HUB_WESTEND_ED * 1_000; - assert_expected_events!( - BridgeHubRococo, - vec![ - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] + // fund the AHR's SA on BHR for paying bridge transport fees + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); + // fund sender + AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]); + + // send XCM from AssetHubWestend - fails - destination version not known + assert_err!( + send_asset_from_asset_hub_westend(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + + // set destination version + AssetHubWestend::force_xcm_version(destination, xcm::v3::prelude::XCM_VERSION); + + // TODO: remove this block, when removing `xcm:v2` + { + // send XCM from AssetHubRococo - fails - AssetHubRococo is set to the default/safe `2` + // version, which does not have the `ExportMessage` instruction. If the default `2` is + // changed to `3`, then this assert can go away" + assert_err!( + send_asset_from_asset_hub_westend(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) ); - }); - // Receive embedded XCM message within `ExportMessage` in Parachain destination + + // set exact version for BridgeHubWestend to `2` without `ExportMessage` instruction + AssetHubWestend::force_xcm_version( + ParentThen(Parachain(BridgeHubWestend::para_id().into()).into()).into(), + xcm::v2::prelude::XCM_VERSION, + ); + // send XCM from AssetHubWestend - fails - `ExportMessage` is not in `2` + assert_err!( + send_asset_from_asset_hub_westend(destination, (native_token, amount)), + DispatchError::Module(sp_runtime::ModuleError { + index: 31, + error: [1, 0, 0, 0], + message: Some("SendFailure") + }) + ); + } + + // set version with `ExportMessage` for BridgeHubWestend + AssetHubWestend::force_xcm_version( + ParentThen(Parachain(BridgeHubWestend::para_id().into()).into()).into(), + xcm::v3::prelude::XCM_VERSION, + ); + // send XCM from AssetHubWestend - ok + assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount))); + + // `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known + assert_bridge_hub_westend_message_accepted(false); + + // set version for remote BridgeHub on BridgeHubWestend + BridgeHubWestend::force_xcm_version( + bridge_hub_rococo_location(), + xcm::v3::prelude::XCM_VERSION, + ); + // set version for AssetHubRococo on BridgeHubRococo + BridgeHubRococo::force_xcm_version( + ParentThen(Parachain(AssetHubRococo::para_id().into()).into()).into(), + xcm::v3::prelude::XCM_VERSION, + ); + + // send XCM from AssetHubWestend - ok + assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount))); + assert_bridge_hub_westend_message_accepted(true); + assert_bridge_hub_rococo_message_received(); + // message delivered and processed at destination AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - assert_expected_events!( AssetHubRococo, vec![ - RuntimeEvent::MessageQueue(pallet_message_queue::Event::ProcessingFailed { - .. - }) => {}, + // message processed with failure, but for this scenario it is ok, important is that was delivered + RuntimeEvent::MessageQueue( + pallet_message_queue::Event::Processed { success: false, .. } + ) => {}, ] ); }); + + // TODO: remove this block, when removing `xcm:v2` + { + // set `2` version for remote BridgeHub on BridgeHubRococo, which does not have + // `UniversalOrigin` and `DescendOrigin` + BridgeHubWestend::force_xcm_version( + bridge_hub_rococo_location(), + xcm::v2::prelude::XCM_VERSION, + ); + + // send XCM from AssetHubWestend - ok + assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount))); + // message is not accepted on the local BridgeHub (`DestinationUnsupported`) because we + // cannot add `UniversalOrigin` and `DescendOrigin` + assert_bridge_hub_westend_message_accepted(false); + } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..f6f1e24c550c79f3a13400dec79d55ef32f5c21f --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "people-rococo-integration-tests" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "People Rococo runtime integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } +assert_matches = "1.5.0" + +# Substrate +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } +pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } +pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false } +pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } +pallet-identity = { path = "../../../../../../../substrate/frame/identity", default-features = false } + +# Polkadot +xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } +rococo-runtime = { path = "../../../../../../../polkadot/runtime/rococo" } +rococo-runtime-constants = { path = "../../../../../../../polkadot/runtime/rococo/constants" } +polkadot-primitives = { path = "../../../../../../../polkadot/primitives" } +polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } + +# Cumulus +asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } +parachains-common = { path = "../../../../../../parachains/common" } +people-rococo-runtime = { path = "../../../../../runtimes/people/people-rococo" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } +penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } +rococo-system-emulated-network = { path = "../../../networks/rococo-system" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..6f2f1409135df0136b991ede4caa0ffdc53ffd63 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/lib.rs @@ -0,0 +1,64 @@ +// 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 codec::Encode; + +// Substrate +pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchError, DispatchResult}, + traits::fungibles::Inspect, +}; + +// Polkadot +pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3::{Error, NetworkId::Rococo as RococoId}, +}; + +// Cumulus +pub use asset_test_utils::xcm_helpers; +pub use emulated_integration_tests_common::{ + test_parachain_is_trusted_teleporter, + xcm_emulator::{ + assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, + RelayChain as Relay, Test, TestArgs, TestContext, TestExt, + }, + xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, + PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, +}; +pub use parachains_common::{AccountId, Balance}; +pub use rococo_system_emulated_network::{ + people_rococo_emulated_chain::{ + genesis::ED as PEOPLE_ROCOCO_ED, PeopleRococoParaPallet as PeopleRococoPallet, + }, + rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet}, + PenpalAPara as PenpalA, PeopleRococoPara as PeopleRococo, + PeopleRococoParaReceiver as PeopleRococoReceiver, PeopleRococoParaSender as PeopleRococoSender, + RococoRelay as Rococo, RococoRelayReceiver as RococoReceiver, + RococoRelaySender as RococoSender, +}; + +// pub const ASSET_ID: u32 = 1; +// pub const ASSET_MIN_BALANCE: u128 = 1000; +pub type RelayToSystemParaTest = Test; +pub type RelayToParaTest = Test; +pub type SystemParaToRelayTest = Test; +pub type SystemParaToParaTest = Test; +pub type ParaToSystemParaTest = Test; + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/testnets-common/src/wococo.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs similarity index 90% rename from cumulus/parachains/testnets-common/src/wococo.rs rename to cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs index 5cd6121135a3321369fee2132b696aef8a2bae81..80c00021ca53db3850d6d32c1584ae7ae924933f 100644 --- a/cumulus/parachains/testnets-common/src/wococo.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/mod.rs @@ -13,5 +13,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// re-export rococo -pub use crate::rococo::{consensus, currency, fee}; +mod reap_identity; +mod teleport; 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 new file mode 100644 index 0000000000000000000000000000000000000000..81bd7620e4615db030f3d9b19df91773cc7d5de8 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs @@ -0,0 +1,549 @@ +// 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. + +//! # OnReapIdentity Tests +//! +//! This file contains the test cases for migrating Identity data away from the Rococo Relay +//! chain and to the PeopleRococo parachain. This migration is part of the broader Minimal Relay +//! effort: +//! https://github.com/polkadot-fellows/RFCs/blob/main/text/0032-minimal-relay.md +//! +//! ## Overview +//! +//! The tests validate the robustness and correctness of the `OnReapIdentityHandler` +//! ensuring that it behaves as expected in various scenarios. Key aspects tested include: +//! +//! - **Deposit Handling**: Confirming that deposits are correctly migrated from the Relay Chain to +//! the People parachain in various scenarios (different `IdentityInfo` fields and different +//! numbers of sub-accounts). +//! +//! ### Test Scenarios +//! +//! The tests are categorized into several scenarios, each resulting in different deposits required +//! on the destination parachain. The tests ensure: +//! +//! - Reserved deposits on the Relay Chain are fully released; +//! - The freed deposit from the Relay Chain is sufficient for the parachain deposit; and +//! - The account will exist on the parachain. + +use crate::*; +use frame_support::BoundedVec; +use pallet_balances::Event as BalancesEvent; +use pallet_identity::{legacy::IdentityInfo, Data, Event as IdentityEvent}; +use people_rococo_runtime::people::{ + BasicDeposit as BasicDepositParachain, ByteDeposit as ByteDepositParachain, + IdentityInfo as IdentityInfoParachain, SubAccountDeposit as SubAccountDepositParachain, +}; +use rococo_runtime::{ + BasicDeposit, ByteDeposit, MaxAdditionalFields, MaxSubAccounts, RuntimeOrigin as RococoOrigin, + SubAccountDeposit, +}; +use rococo_runtime_constants::currency::*; +use rococo_system_emulated_network::{ + rococo_emulated_chain::RococoRelayPallet, RococoRelay, RococoRelaySender, +}; + +type Balance = u128; +type RococoIdentity = ::Identity; +type RococoBalances = ::Balances; +type RococoIdentityMigrator = ::IdentityMigrator; +type PeopleRococoIdentity = ::Identity; +type PeopleRococoBalances = ::Balances; + +#[derive(Clone, Debug)] +struct Identity { + relay: IdentityInfo, + para: IdentityInfoParachain, + subs: Subs, +} + +impl Identity { + fn new( + full: bool, + additional: Option>, + subs: Subs, + ) -> Self { + let pgp_fingerprint = [ + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, + ]; + let make_data = |data: &[u8], full: bool| -> Data { + if full { + Data::Raw(data.to_vec().try_into().unwrap()) + } else { + Data::None + } + }; + let (github, discord) = additional + .as_ref() + .and_then(|vec| vec.first()) + .map(|(g, d)| (g.clone(), d.clone())) + .unwrap_or((Data::None, Data::None)); + Self { + relay: IdentityInfo { + display: make_data(b"xcm-test", full), + legal: make_data(b"The Xcm Test, Esq.", full), + web: make_data(b"https://xcm-test.io", full), + riot: make_data(b"xcm-riot", full), + email: make_data(b"xcm-test@gmail.com", full), + pgp_fingerprint: Some(pgp_fingerprint), + image: make_data(b"xcm-test.png", full), + twitter: make_data(b"@xcm-test", full), + additional: additional.unwrap_or_default(), + }, + para: IdentityInfoParachain { + display: make_data(b"xcm-test", full), + legal: make_data(b"The Xcm Test, Esq.", full), + web: make_data(b"https://xcm-test.io", full), + matrix: make_data(b"xcm-matrix@server", full), + email: make_data(b"xcm-test@gmail.com", full), + pgp_fingerprint: Some(pgp_fingerprint), + image: make_data(b"xcm-test.png", full), + twitter: make_data(b"@xcm-test", full), + github, + discord, + }, + subs, + } + } +} + +#[derive(Clone, Debug)] +enum Subs { + Zero, + Many(u32), +} + +enum IdentityOn<'a> { + Relay(&'a IdentityInfo), + Para(&'a IdentityInfoParachain), +} + +impl IdentityOn<'_> { + fn calculate_deposit(self) -> Balance { + match self { + IdentityOn::Relay(id) => { + let base_deposit = BasicDeposit::get(); + let byte_deposit = + ByteDeposit::get() * TryInto::::try_into(id.encoded_size()).unwrap(); + base_deposit + byte_deposit + }, + IdentityOn::Para(id) => { + let base_deposit = BasicDepositParachain::get(); + let byte_deposit = ByteDepositParachain::get() * + TryInto::::try_into(id.encoded_size()).unwrap(); + base_deposit + byte_deposit + }, + } + } +} + +/// Generate an `AccountId32` from a `u32`. +/// This creates a 32-byte array, initially filled with `255`, and then repeatedly fills it +/// with the 4-byte little-endian representation of the `u32` value, until the array is full. +/// +/// **Example**: +/// +/// `account_from_u32(5)` will return an `AccountId32` with the bytes +/// `[0, 5, 0, 0, 0, 0, 0, 0, 0, 5 ... ]` +fn account_from_u32(id: u32) -> AccountId32 { + let mut buffer = [255u8; 32]; + let id_bytes = id.to_le_bytes(); + let id_size = id_bytes.len(); + for chunk in buffer.chunks_mut(id_size) { + chunk.clone_from_slice(&id_bytes); + } + AccountId32::new(buffer) +} + +// Set up the Relay Chain with an identity. +fn set_id_relay(id: &Identity) -> Balance { + let mut total_deposit: Balance = 0; + + // Set identity and Subs on Relay Chain + RococoRelay::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(RococoIdentity::set_identity( + RococoOrigin::signed(RococoRelaySender::get()), + Box::new(id.relay.clone()) + )); + + if let Subs::Many(n) = id.subs { + let subs: Vec<_> = (0..n) + .map(|i| (account_from_u32(i), Data::Raw(b"name".to_vec().try_into().unwrap()))) + .collect(); + + assert_ok!(RococoIdentity::set_subs( + RococoOrigin::signed(RococoRelaySender::get()), + subs, + )); + } + + let reserved_balance = RococoBalances::reserved_balance(RococoRelaySender::get()); + let id_deposit = IdentityOn::Relay(&id.relay).calculate_deposit(); + + let total_deposit = match id.subs { + Subs::Zero => { + total_deposit = id_deposit; // No subs + assert_expected_events!( + RococoRelay, + vec![ + RuntimeEvent::Identity(IdentityEvent::IdentitySet { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == RococoRelaySender::get(), + amount: *amount == id_deposit, + }, + ] + ); + total_deposit + }, + Subs::Many(n) => { + let sub_account_deposit = n as Balance * SubAccountDeposit::get(); + total_deposit = + sub_account_deposit + IdentityOn::Relay(&id.relay).calculate_deposit(); + assert_expected_events!( + RococoRelay, + vec![ + RuntimeEvent::Identity(IdentityEvent::IdentitySet { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == RococoRelaySender::get(), + amount: *amount == id_deposit, + }, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == RococoRelaySender::get(), + amount: *amount == sub_account_deposit, + }, + ] + ); + total_deposit + }, + }; + + assert_eq!(reserved_balance, total_deposit); + }); + total_deposit +} + +// Set up the parachain with an identity and (maybe) sub accounts, but with zero deposits. +fn assert_set_id_parachain(id: &Identity) { + // Set identity and Subs on Parachain with zero deposit + PeopleRococo::execute_with(|| { + let free_bal = PeopleRococoBalances::free_balance(PeopleRococoSender::get()); + let reserved_balance = PeopleRococoBalances::reserved_balance(PeopleRococoSender::get()); + + // total balance at Genesis should be zero + assert_eq!(reserved_balance + free_bal, 0); + + assert_ok!(PeopleRococoIdentity::set_identity_no_deposit( + &PeopleRococoSender::get(), + id.para.clone(), + )); + + match id.subs { + Subs::Zero => {}, + Subs::Many(n) => { + let subs: Vec<_> = (0..n) + .map(|ii| { + (account_from_u32(ii), Data::Raw(b"name".to_vec().try_into().unwrap())) + }) + .collect(); + assert_ok!(PeopleRococoIdentity::set_subs_no_deposit( + &PeopleRococoSender::get(), + subs, + )); + }, + } + + // No amount should be reserved as deposit amounts are set to 0. + let reserved_balance = PeopleRococoBalances::reserved_balance(PeopleRococoSender::get()); + assert_eq!(reserved_balance, 0); + assert!(PeopleRococoIdentity::identity(PeopleRococoSender::get()).is_some()); + + let (_, sub_accounts) = PeopleRococoIdentity::subs_of(PeopleRococoSender::get()); + + match id.subs { + Subs::Zero => assert_eq!(sub_accounts.len(), 0), + Subs::Many(n) => assert_eq!(sub_accounts.len(), n as usize), + } + }); +} + +// Reap the identity on the Relay Chain and assert that the correct things happen there. +fn assert_reap_id_relay(total_deposit: Balance, id: &Identity) { + RococoRelay::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let free_bal_before_reap = RococoBalances::free_balance(RococoRelaySender::get()); + let reserved_balance = RococoBalances::reserved_balance(RococoRelaySender::get()); + + assert_eq!(reserved_balance, total_deposit); + + assert_ok!(RococoIdentityMigrator::reap_identity( + RococoOrigin::root(), + RococoRelaySender::get() + )); + + let remote_deposit = match id.subs { + Subs::Zero => calculate_remote_deposit(id.relay.encoded_size() as u32, 0), + Subs::Many(n) => calculate_remote_deposit(id.relay.encoded_size() as u32, n), + }; + + assert_expected_events!( + RococoRelay, + vec![ + // `reap_identity` sums the identity and subs deposits and unreserves them in one + // call. Therefore, we only expect one `Unreserved` event. + RuntimeEvent::Balances(BalancesEvent::Unreserved { who, amount }) => { + who: *who == RococoRelaySender::get(), + amount: *amount == total_deposit, + }, + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::IdentityReaped { + who, + }) => { + who: *who == PeopleRococoSender::get(), + }, + ] + ); + // Identity should be gone. + assert!(PeopleRococoIdentity::identity(RococoRelaySender::get()).is_none()); + + // Subs should be gone. + let (_, sub_accounts) = RococoIdentity::subs_of(RococoRelaySender::get()); + assert_eq!(sub_accounts.len(), 0); + + let reserved_balance = RococoBalances::reserved_balance(RococoRelaySender::get()); + assert_eq!(reserved_balance, 0); + + // Free balance should be greater (i.e. the teleport should work even if 100% of an + // account's balance is reserved for Identity). + let free_bal_after_reap = RococoBalances::free_balance(RococoRelaySender::get()); + assert!(free_bal_after_reap > free_bal_before_reap); + + // Implicit: total_deposit > remote_deposit. As in, accounts should always have enough + // reserved for the parachain deposit. + assert_eq!(free_bal_after_reap, free_bal_before_reap + total_deposit - remote_deposit); + }); +} + +// Reaping the identity on the Relay Chain will have sent an XCM program to the parachain. Ensure +// that everything happens as expected. +fn assert_reap_parachain(id: &Identity) { + PeopleRococo::execute_with(|| { + let reserved_balance = PeopleRococoBalances::reserved_balance(PeopleRococoSender::get()); + let id_deposit = IdentityOn::Para(&id.para).calculate_deposit(); + let total_deposit = match id.subs { + Subs::Zero => id_deposit, + Subs::Many(n) => id_deposit + n as Balance * SubAccountDepositParachain::get(), + }; + assert_reap_events(id_deposit, id); + assert_eq!(reserved_balance, total_deposit); + + // Should have at least one ED after in free balance after the reap. + assert!(PeopleRococoBalances::free_balance(PeopleRococoSender::get()) >= PEOPLE_ROCOCO_ED); + }); +} + +// Assert the events that should happen on the parachain upon reaping an identity on the Relay +// Chain. +fn assert_reap_events(id_deposit: Balance, id: &Identity) { + type RuntimeEvent = ::RuntimeEvent; + match id.subs { + Subs::Zero => { + assert_expected_events!( + PeopleRococo, + vec![ + // Deposit and Endowed from teleport + RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, + // Amount reserved for identity info + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleRococoSender::get(), + amount: *amount == id_deposit, + }, + // Confirmation from Migrator with individual identity and subs deposits + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::DepositUpdated { + who, identity, subs + }) => { + who: *who == PeopleRococoSender::get(), + identity: *identity == id_deposit, + subs: *subs == 0, + }, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { .. }) => {}, + ] + ); + }, + Subs::Many(n) => { + let subs_deposit = n as Balance * SubAccountDepositParachain::get(); + assert_expected_events!( + PeopleRococo, + vec![ + // Deposit and Endowed from teleport + RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, + // Amount reserved for identity info + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleRococoSender::get(), + amount: *amount == id_deposit, + }, + // Amount reserved for subs + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleRococoSender::get(), + amount: *amount == subs_deposit, + }, + // Confirmation from Migrator with individual identity and subs deposits + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::DepositUpdated { + who, identity, subs + }) => { + who: *who == PeopleRococoSender::get(), + identity: *identity == id_deposit, + subs: *subs == subs_deposit, + }, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { .. }) => {}, + ] + ); + }, + }; +} + +/// Duplicate of the impl of `ToParachainIdentityReaper` in the Rococo runtime. +fn calculate_remote_deposit(bytes: u32, subs: u32) -> Balance { + // Note: These `deposit` functions and `EXISTENTIAL_DEPOSIT` correspond to the Relay Chain's. + // Pulled in: use rococo_runtime_constants::currency::*; + let para_basic_deposit = deposit(1, 17) / 100; + let para_byte_deposit = deposit(0, 1) / 100; + let para_sub_account_deposit = deposit(1, 53) / 100; + let para_existential_deposit = EXISTENTIAL_DEPOSIT / 10; + + // pallet deposits + let id_deposit = + para_basic_deposit.saturating_add(para_byte_deposit.saturating_mul(bytes as Balance)); + let subs_deposit = para_sub_account_deposit.saturating_mul(subs as Balance); + + id_deposit + .saturating_add(subs_deposit) + .saturating_add(para_existential_deposit.saturating_mul(2)) +} + +// Represent some `additional` data that would not be migrated to the parachain. The encoded size, +// and thus the byte deposit, should decrease. +fn nonsensical_additional() -> BoundedVec<(Data, Data), MaxAdditionalFields> { + BoundedVec::try_from(vec![( + Data::Raw(b"fOo".to_vec().try_into().unwrap()), + Data::Raw(b"baR".to_vec().try_into().unwrap()), + )]) + .unwrap() +} + +// Represent some `additional` data that will be migrated to the parachain as first-class fields. +fn meaningful_additional() -> BoundedVec<(Data, Data), MaxAdditionalFields> { + BoundedVec::try_from(vec![ + ( + Data::Raw(b"github".to_vec().try_into().unwrap()), + Data::Raw(b"niels-username".to_vec().try_into().unwrap()), + ), + ( + Data::Raw(b"discord".to_vec().try_into().unwrap()), + Data::Raw(b"bohr-username".to_vec().try_into().unwrap()), + ), + ]) + .unwrap() +} + +// Execute a single test case. +fn assert_relay_para_flow(id: &Identity) { + let total_deposit = set_id_relay(id); + assert_set_id_parachain(id); + assert_reap_id_relay(total_deposit, id); + assert_reap_parachain(id); +} + +// Tests with empty `IdentityInfo`. + +#[test] +fn on_reap_identity_works_for_minimal_identity_with_zero_subs() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_minimal_identity() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_minimal_identity_with_max_subs() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Many(MaxSubAccounts::get()))); +} + +// Tests with full `IdentityInfo`. + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional_max_subs() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Many(MaxSubAccounts::get()))); +} + +// Tests with full `IdentityInfo` and `additional` fields that will _not_ be migrated. + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, Some(nonsensical_additional()), Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional() { + assert_relay_para_flow(&Identity::new(true, Some(nonsensical_additional()), Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional_max_subs() { + assert_relay_para_flow(&Identity::new( + true, + Some(nonsensical_additional()), + Subs::Many(MaxSubAccounts::get()), + )); +} + +// Tests with full `IdentityInfo` and `additional` fields that will be migrated. + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, Some(meaningful_additional()), Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional() { + assert_relay_para_flow(&Identity::new(true, Some(meaningful_additional()), Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional_max_subs() { + assert_relay_para_flow(&Identity::new( + true, + Some(meaningful_additional()), + Subs::Many(MaxSubAccounts::get()), + )); +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..42c7e310b2628eb97011ff36f6c8db2b3dbd130c --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs @@ -0,0 +1,260 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use people_rococo_runtime::xcm_config::XcmConfig as PeopleRococoXcmConfig; +use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(627_959_000, 7_200))); + + assert_expected_events!( + Rococo, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { 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 }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_dest_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + Rococo::assert_ump_queue_processed( + true, + Some(PeopleRococo::para_id()), + Some(Weight::from_parts(304_266_000, 7_186)), + ); + + assert_expected_events!( + Rococo, + vec![ + // Amount is withdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { 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, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Rococo::assert_ump_queue_processed( + false, + Some(PeopleRococo::para_id()), + Some(Weight::from_parts(157_718_000, 3_593)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + PeopleRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 600_000_000, + 7_000, + ))); + + PeopleRococo::assert_parachain_system_ump_sent(); + + assert_expected_events!( + PeopleRococo, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + PeopleRococo::assert_dmp_queue_complete(Some(Weight::from_parts(162_456_000, 0))); + + assert_expected_events!( + PeopleRococo, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_teleport_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_teleport_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +/// Limited Teleport of native asset from Relay Chain to the System Parachain should work +#[test] +fn limited_teleport_native_assets_from_relay_to_system_para_works() { + // Init values for Relay Chain + let amount_to_send: Balance = ROCOCO_ED * 1000; + let dest = Rococo::child_location_of(PeopleRococo::para_id()); + let beneficiary_id = PeopleRococoReceiver::get(); + let test_args = TestContext { + sender: RococoSender::get(), + receiver: PeopleRococoReceiver::get(), + args: TestArgs::new_relay(dest, beneficiary_id, amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(para_dest_assertions); + test.set_dispatchable::(relay_limited_teleport_assets); + test.assert(); + + let delivery_fees = Rococo::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + limited_teleport_native_assets_from_relay_to_system_para_works(); + + let amount_to_send: Balance = PEOPLE_ROCOCO_ED * 1000; + let destination = PeopleRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + // Fund a sender + PeopleRococo::fund_accounts(vec![(PeopleRococoSender::get(), ROCOCO_ED * 2_000u128)]); + + let test_args = TestContext { + sender: PeopleRococoSender::get(), + receiver: RococoReceiver::get(), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = PeopleRococo::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = ROCOCO_ED * 1000; + let destination = PeopleRococo::parent_location(); + let beneficiary_id = RococoReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + // Fund a sender + PeopleRococo::fund_accounts(vec![(PeopleRococoSender::get(), ROCOCO_ED * 2_000u128)]); + + let test_args = TestContext { + sender: PeopleRococoSender::get(), + receiver: RococoReceiver::get(), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions_fail); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = PeopleRococo::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a5d9d6c0e30ba65269c7b56cda4857f4eab4fcef --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "people-westend-integration-tests" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +description = "People Westend runtime integration tests with xcm-emulator" +publish = false + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.4.0", default-features = false } +assert_matches = "1.5.0" + +# Substrate +sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false } +frame-support = { path = "../../../../../../../substrate/frame/support", default-features = false } +pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false } +pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false } +pallet-asset-conversion = { path = "../../../../../../../substrate/frame/asset-conversion", default-features = false } +pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue", default-features = false } +pallet-identity = { path = "../../../../../../../substrate/frame/identity", default-features = false } + +# Polkadot +xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false } +pallet-xcm = { path = "../../../../../../../polkadot/xcm/pallet-xcm", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../../../../polkadot/xcm/xcm-executor", default-features = false } +westend-runtime = { path = "../../../../../../../polkadot/runtime/westend" } +westend-runtime-constants = { path = "../../../../../../../polkadot/runtime/westend/constants" } +polkadot-primitives = { path = "../../../../../../../polkadot/primitives" } +polkadot-runtime-common = { path = "../../../../../../../polkadot/runtime/common" } + +# Cumulus +asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" } +parachains-common = { path = "../../../../../../parachains/common" } +people-westend-runtime = { path = "../../../../../runtimes/people/people-westend" } +emulated-integration-tests-common = { path = "../../../common", default-features = false } +penpal-runtime = { path = "../../../../../runtimes/testing/penpal" } +westend-system-emulated-network = { path = "../../../networks/westend-system" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..59cec36030b236135af7898ed87f6548cc12cbd4 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/lib.rs @@ -0,0 +1,64 @@ +// 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 codec::Encode; + +// Substrate +pub use frame_support::{ + assert_err, assert_ok, + pallet_prelude::Weight, + sp_runtime::{AccountId32, DispatchError, DispatchResult}, + traits::fungibles::Inspect, +}; + +// Polkadot +pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, *}, + v3::{Error, NetworkId::Westend as WestendId}, +}; + +// Cumulus +pub use asset_test_utils::xcm_helpers; +pub use emulated_integration_tests_common::{ + test_parachain_is_trusted_teleporter, + xcm_emulator::{ + assert_expected_events, bx, helpers::weight_within_threshold, Chain, Parachain as Para, + RelayChain as Relay, Test, TestArgs, TestContext, TestExt, + }, + xcm_helpers::{xcm_transact_paid_execution, xcm_transact_unpaid_execution}, + PROOF_SIZE_THRESHOLD, REF_TIME_THRESHOLD, XCM_V3, +}; +pub use parachains_common::{AccountId, Balance}; +pub use westend_system_emulated_network::{ + people_westend_emulated_chain::{ + genesis::ED as PEOPLE_WESTEND_ED, PeopleWestendParaPallet as PeopleWestendPallet, + }, + westend_emulated_chain::{genesis::ED as WESTEND_ED, WestendRelayPallet as WestendPallet}, + PenpalAPara as PenpalA, PeopleWestendPara as PeopleWestend, + PeopleWestendParaReceiver as PeopleWestendReceiver, + PeopleWestendParaSender as PeopleWestendSender, WestendRelay as Westend, + WestendRelayReceiver as WestendReceiver, WestendRelaySender as WestendSender, +}; + +// pub const ASSET_ID: u32 = 1; +// pub const ASSET_MIN_BALANCE: u128 = 1000; +pub type RelayToSystemParaTest = Test; +pub type RelayToParaTest = Test; +pub type SystemParaToRelayTest = Test; +pub type SystemParaToParaTest = Test; +pub type ParaToSystemParaTest = Test; + +#[cfg(test)] +mod tests; diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..80c00021ca53db3850d6d32c1584ae7ae924933f --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs @@ -0,0 +1,17 @@ +// 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. + +mod reap_identity; +mod teleport; 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 new file mode 100644 index 0000000000000000000000000000000000000000..6de2562b278b98e9d0825fb3a6d4ad7fd24558fd --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs @@ -0,0 +1,551 @@ +// 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. + +//! # OnReapIdentity Tests +//! +//! This file contains the test cases for migrating Identity data away from the Westend Relay +//! chain and to the PeopleWestend parachain. This migration is part of the broader Minimal Relay +//! effort: +//! https://github.com/polkadot-fellows/RFCs/blob/main/text/0032-minimal-relay.md +//! +//! ## Overview +//! +//! The tests validate the robustness and correctness of the `OnReapIdentityHandler` +//! ensuring that it behaves as expected in various scenarios. Key aspects tested include: +//! +//! - **Deposit Handling**: Confirming that deposits are correctly migrated from the Relay Chain to +//! the People parachain in various scenarios (different `IdentityInfo` fields and different +//! numbers of sub-accounts). +//! +//! ### Test Scenarios +//! +//! The tests are categorized into several scenarios, each resulting in different deposits required +//! on the destination parachain. The tests ensure: +//! +//! - Reserved deposits on the Relay Chain are fully released; +//! - The freed deposit from the Relay Chain is sufficient for the parachain deposit; and +//! - The account will exist on the parachain. + +use crate::*; +use frame_support::BoundedVec; +use pallet_balances::Event as BalancesEvent; +use pallet_identity::{legacy::IdentityInfo, Data, Event as IdentityEvent}; +use people_westend_runtime::people::{ + BasicDeposit as BasicDepositParachain, ByteDeposit as ByteDepositParachain, + IdentityInfo as IdentityInfoParachain, SubAccountDeposit as SubAccountDepositParachain, +}; +use westend_runtime::{ + BasicDeposit, ByteDeposit, MaxAdditionalFields, MaxSubAccounts, RuntimeOrigin as WestendOrigin, + SubAccountDeposit, +}; +use westend_runtime_constants::currency::*; +use westend_system_emulated_network::{ + westend_emulated_chain::WestendRelayPallet, WestendRelay, WestendRelaySender, +}; + +type Balance = u128; +type WestendIdentity = ::Identity; +type WestendBalances = ::Balances; +type WestendIdentityMigrator = ::IdentityMigrator; +type PeopleWestendIdentity = ::Identity; +type PeopleWestendBalances = ::Balances; + +#[derive(Clone, Debug)] +struct Identity { + relay: IdentityInfo, + para: IdentityInfoParachain, + subs: Subs, +} + +impl Identity { + fn new( + full: bool, + additional: Option>, + subs: Subs, + ) -> Self { + let pgp_fingerprint = [ + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, + ]; + let make_data = |data: &[u8], full: bool| -> Data { + if full { + Data::Raw(data.to_vec().try_into().unwrap()) + } else { + Data::None + } + }; + let (github, discord) = additional + .as_ref() + .and_then(|vec| vec.first()) + .map(|(g, d)| (g.clone(), d.clone())) + .unwrap_or((Data::None, Data::None)); + Self { + relay: IdentityInfo { + display: make_data(b"xcm-test", full), + legal: make_data(b"The Xcm Test, Esq.", full), + web: make_data(b"https://xcm-test.io", full), + riot: make_data(b"xcm-riot", full), + email: make_data(b"xcm-test@gmail.com", full), + pgp_fingerprint: Some(pgp_fingerprint), + image: make_data(b"xcm-test.png", full), + twitter: make_data(b"@xcm-test", full), + additional: additional.unwrap_or_default(), + }, + para: IdentityInfoParachain { + display: make_data(b"xcm-test", full), + legal: make_data(b"The Xcm Test, Esq.", full), + web: make_data(b"https://xcm-test.io", full), + matrix: make_data(b"xcm-matrix@server", full), + email: make_data(b"xcm-test@gmail.com", full), + pgp_fingerprint: Some(pgp_fingerprint), + image: make_data(b"xcm-test.png", full), + twitter: make_data(b"@xcm-test", full), + github, + discord, + }, + subs, + } + } +} + +#[derive(Clone, Debug)] +enum Subs { + Zero, + Many(u32), +} + +enum IdentityOn<'a> { + Relay(&'a IdentityInfo), + Para(&'a IdentityInfoParachain), +} + +impl IdentityOn<'_> { + fn calculate_deposit(self) -> Balance { + match self { + IdentityOn::Relay(id) => { + let base_deposit = BasicDeposit::get(); + let byte_deposit = + ByteDeposit::get() * TryInto::::try_into(id.encoded_size()).unwrap(); + base_deposit + byte_deposit + }, + IdentityOn::Para(id) => { + let base_deposit = BasicDepositParachain::get(); + let byte_deposit = ByteDepositParachain::get() * + TryInto::::try_into(id.encoded_size()).unwrap(); + base_deposit + byte_deposit + }, + } + } +} + +/// Generate an `AccountId32` from a `u32`. +/// This creates a 32-byte array, initially filled with `255`, and then repeatedly fills it +/// with the 4-byte little-endian representation of the `u32` value, until the array is full. +/// +/// **Example**: +/// +/// `account_from_u32(5)` will return an `AccountId32` with the bytes +/// `[0, 5, 0, 0, 0, 0, 0, 0, 0, 5 ... ]` +fn account_from_u32(id: u32) -> AccountId32 { + let mut buffer = [255u8; 32]; + let id_bytes = id.to_le_bytes(); + let id_size = id_bytes.len(); + for chunk in buffer.chunks_mut(id_size) { + chunk.clone_from_slice(&id_bytes); + } + AccountId32::new(buffer) +} + +// Set up the Relay Chain with an identity. +fn set_id_relay(id: &Identity) -> Balance { + let mut total_deposit: Balance = 0; + + // Set identity and Subs on Relay Chain + WestendRelay::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_ok!(WestendIdentity::set_identity( + WestendOrigin::signed(WestendRelaySender::get()), + Box::new(id.relay.clone()) + )); + + if let Subs::Many(n) = id.subs { + let subs: Vec<_> = (0..n) + .map(|i| (account_from_u32(i), Data::Raw(b"name".to_vec().try_into().unwrap()))) + .collect(); + + assert_ok!(WestendIdentity::set_subs( + WestendOrigin::signed(WestendRelaySender::get()), + subs, + )); + } + + let reserved_balance = WestendBalances::reserved_balance(WestendRelaySender::get()); + let id_deposit = IdentityOn::Relay(&id.relay).calculate_deposit(); + + let total_deposit = match id.subs { + Subs::Zero => { + total_deposit = id_deposit; // No subs + assert_expected_events!( + WestendRelay, + vec![ + RuntimeEvent::Identity(IdentityEvent::IdentitySet { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == WestendRelaySender::get(), + amount: *amount == id_deposit, + }, + ] + ); + total_deposit + }, + Subs::Many(n) => { + let sub_account_deposit = n as Balance * SubAccountDeposit::get(); + total_deposit = + sub_account_deposit + IdentityOn::Relay(&id.relay).calculate_deposit(); + assert_expected_events!( + WestendRelay, + vec![ + RuntimeEvent::Identity(IdentityEvent::IdentitySet { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == WestendRelaySender::get(), + amount: *amount == id_deposit, + }, + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == WestendRelaySender::get(), + amount: *amount == sub_account_deposit, + }, + ] + ); + total_deposit + }, + }; + + assert_eq!(reserved_balance, total_deposit); + }); + total_deposit +} + +// Set up the parachain with an identity and (maybe) sub accounts, but with zero deposits. +fn assert_set_id_parachain(id: &Identity) { + // Set identity and Subs on Parachain with zero deposit + PeopleWestend::execute_with(|| { + let free_bal = PeopleWestendBalances::free_balance(PeopleWestendSender::get()); + let reserved_balance = PeopleWestendBalances::reserved_balance(PeopleWestendSender::get()); + + // total balance at Genesis should be zero + assert_eq!(reserved_balance + free_bal, 0); + + assert_ok!(PeopleWestendIdentity::set_identity_no_deposit( + &PeopleWestendSender::get(), + id.para.clone(), + )); + + match id.subs { + Subs::Zero => {}, + Subs::Many(n) => { + let subs: Vec<_> = (0..n) + .map(|ii| { + (account_from_u32(ii), Data::Raw(b"name".to_vec().try_into().unwrap())) + }) + .collect(); + assert_ok!(PeopleWestendIdentity::set_subs_no_deposit( + &PeopleWestendSender::get(), + subs, + )); + }, + } + + // No amount should be reserved as deposit amounts are set to 0. + let reserved_balance = PeopleWestendBalances::reserved_balance(PeopleWestendSender::get()); + assert_eq!(reserved_balance, 0); + assert!(PeopleWestendIdentity::identity(PeopleWestendSender::get()).is_some()); + + let (_, sub_accounts) = PeopleWestendIdentity::subs_of(PeopleWestendSender::get()); + + match id.subs { + Subs::Zero => assert_eq!(sub_accounts.len(), 0), + Subs::Many(n) => assert_eq!(sub_accounts.len(), n as usize), + } + }); +} + +// Reap the identity on the Relay Chain and assert that the correct things happen there. +fn assert_reap_id_relay(total_deposit: Balance, id: &Identity) { + WestendRelay::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let free_bal_before_reap = WestendBalances::free_balance(WestendRelaySender::get()); + let reserved_balance = WestendBalances::reserved_balance(WestendRelaySender::get()); + + assert_eq!(reserved_balance, total_deposit); + + assert_ok!(WestendIdentityMigrator::reap_identity( + WestendOrigin::root(), + WestendRelaySender::get() + )); + + let remote_deposit = match id.subs { + Subs::Zero => calculate_remote_deposit(id.relay.encoded_size() as u32, 0), + Subs::Many(n) => calculate_remote_deposit(id.relay.encoded_size() as u32, n), + }; + + assert_expected_events!( + WestendRelay, + vec![ + // `reap_identity` sums the identity and subs deposits and unreserves them in one + // call. Therefore, we only expect one `Unreserved` event. + RuntimeEvent::Balances(BalancesEvent::Unreserved { who, amount }) => { + who: *who == WestendRelaySender::get(), + amount: *amount == total_deposit, + }, + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::IdentityReaped { + who, + }) => { + who: *who == PeopleWestendSender::get(), + }, + ] + ); + // Identity should be gone. + assert!(PeopleWestendIdentity::identity(WestendRelaySender::get()).is_none()); + + // Subs should be gone. + let (_, sub_accounts) = WestendIdentity::subs_of(WestendRelaySender::get()); + assert_eq!(sub_accounts.len(), 0); + + let reserved_balance = WestendBalances::reserved_balance(WestendRelaySender::get()); + assert_eq!(reserved_balance, 0); + + // Free balance should be greater (i.e. the teleport should work even if 100% of an + // account's balance is reserved for Identity). + let free_bal_after_reap = WestendBalances::free_balance(WestendRelaySender::get()); + assert!(free_bal_after_reap > free_bal_before_reap); + + // Implicit: total_deposit > remote_deposit. As in, accounts should always have enough + // reserved for the parachain deposit. + assert_eq!(free_bal_after_reap, free_bal_before_reap + total_deposit - remote_deposit); + }); +} + +// Reaping the identity on the Relay Chain will have sent an XCM program to the parachain. Ensure +// that everything happens as expected. +fn assert_reap_parachain(id: &Identity) { + PeopleWestend::execute_with(|| { + let reserved_balance = PeopleWestendBalances::reserved_balance(PeopleWestendSender::get()); + let id_deposit = IdentityOn::Para(&id.para).calculate_deposit(); + let total_deposit = match id.subs { + Subs::Zero => id_deposit, + Subs::Many(n) => id_deposit + n as Balance * SubAccountDepositParachain::get(), + }; + assert_reap_events(id_deposit, id); + assert_eq!(reserved_balance, total_deposit); + + // Should have at least one ED after in free balance after the reap. + assert!( + PeopleWestendBalances::free_balance(PeopleWestendSender::get()) >= PEOPLE_WESTEND_ED + ); + }); +} + +// Assert the events that should happen on the parachain upon reaping an identity on the Relay +// Chain. +fn assert_reap_events(id_deposit: Balance, id: &Identity) { + type RuntimeEvent = ::RuntimeEvent; + match id.subs { + Subs::Zero => { + assert_expected_events!( + PeopleWestend, + vec![ + // Deposit and Endowed from teleport + RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, + // Amount reserved for identity info + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleWestendSender::get(), + amount: *amount == id_deposit, + }, + // Confirmation from Migrator with individual identity and subs deposits + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::DepositUpdated { + who, identity, subs + }) => { + who: *who == PeopleWestendSender::get(), + identity: *identity == id_deposit, + subs: *subs == 0, + }, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { .. }) => {}, + ] + ); + }, + Subs::Many(n) => { + let subs_deposit = n as Balance * SubAccountDepositParachain::get(); + assert_expected_events!( + PeopleWestend, + vec![ + // Deposit and Endowed from teleport + RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, + // Amount reserved for identity info + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleWestendSender::get(), + amount: *amount == id_deposit, + }, + // Amount reserved for subs + RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { + who: *who == PeopleWestendSender::get(), + amount: *amount == subs_deposit, + }, + // Confirmation from Migrator with individual identity and subs deposits + RuntimeEvent::IdentityMigrator( + polkadot_runtime_common::identity_migrator::Event::DepositUpdated { + who, identity, subs + }) => { + who: *who == PeopleWestendSender::get(), + identity: *identity == id_deposit, + subs: *subs == subs_deposit, + }, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { .. }) => {}, + ] + ); + }, + }; +} + +/// Duplicate of the impl of `ToParachainIdentityReaper` in the Westend runtime. +fn calculate_remote_deposit(bytes: u32, subs: u32) -> Balance { + // Note: These `deposit` functions and `EXISTENTIAL_DEPOSIT` correspond to the Relay Chain's. + // Pulled in: use westend_runtime_constants::currency::*; + let para_basic_deposit = deposit(1, 17) / 100; + let para_byte_deposit = deposit(0, 1) / 100; + let para_sub_account_deposit = deposit(1, 53) / 100; + let para_existential_deposit = EXISTENTIAL_DEPOSIT / 10; + + // pallet deposits + let id_deposit = + para_basic_deposit.saturating_add(para_byte_deposit.saturating_mul(bytes as Balance)); + let subs_deposit = para_sub_account_deposit.saturating_mul(subs as Balance); + + id_deposit + .saturating_add(subs_deposit) + .saturating_add(para_existential_deposit.saturating_mul(2)) +} + +// Represent some `additional` data that would not be migrated to the parachain. The encoded size, +// and thus the byte deposit, should decrease. +fn nonsensical_additional() -> BoundedVec<(Data, Data), MaxAdditionalFields> { + BoundedVec::try_from(vec![( + Data::Raw(b"fOo".to_vec().try_into().unwrap()), + Data::Raw(b"baR".to_vec().try_into().unwrap()), + )]) + .unwrap() +} + +// Represent some `additional` data that will be migrated to the parachain as first-class fields. +fn meaningful_additional() -> BoundedVec<(Data, Data), MaxAdditionalFields> { + BoundedVec::try_from(vec![ + ( + Data::Raw(b"github".to_vec().try_into().unwrap()), + Data::Raw(b"niels-username".to_vec().try_into().unwrap()), + ), + ( + Data::Raw(b"discord".to_vec().try_into().unwrap()), + Data::Raw(b"bohr-username".to_vec().try_into().unwrap()), + ), + ]) + .unwrap() +} + +// Execute a single test case. +fn assert_relay_para_flow(id: &Identity) { + let total_deposit = set_id_relay(id); + assert_set_id_parachain(id); + assert_reap_id_relay(total_deposit, id); + assert_reap_parachain(id); +} + +// Tests with empty `IdentityInfo`. + +#[test] +fn on_reap_identity_works_for_minimal_identity_with_zero_subs() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_minimal_identity() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_minimal_identity_with_max_subs() { + assert_relay_para_flow(&Identity::new(false, None, Subs::Many(MaxSubAccounts::get()))); +} + +// Tests with full `IdentityInfo`. + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_no_additional_max_subs() { + assert_relay_para_flow(&Identity::new(true, None, Subs::Many(MaxSubAccounts::get()))); +} + +// Tests with full `IdentityInfo` and `additional` fields that will _not_ be migrated. + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, Some(nonsensical_additional()), Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional() { + assert_relay_para_flow(&Identity::new(true, Some(nonsensical_additional()), Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_nonsense_additional_max_subs() { + assert_relay_para_flow(&Identity::new( + true, + Some(nonsensical_additional()), + Subs::Many(MaxSubAccounts::get()), + )); +} + +// Tests with full `IdentityInfo` and `additional` fields that will be migrated. + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional_zero_subs() { + assert_relay_para_flow(&Identity::new(true, Some(meaningful_additional()), Subs::Zero)); +} + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional() { + assert_relay_para_flow(&Identity::new(true, Some(meaningful_additional()), Subs::Many(1))); +} + +#[test] +fn on_reap_identity_works_for_full_identity_meaningful_additional_max_subs() { + assert_relay_para_flow(&Identity::new( + true, + Some(meaningful_additional()), + Subs::Many(MaxSubAccounts::get()), + )); +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..e9f0158efbf5e7c344125463991072f3fe77c02f --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs @@ -0,0 +1,260 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::*; +use people_westend_runtime::xcm_config::XcmConfig as PeopleWestendXcmConfig; +use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig; + +fn relay_origin_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + Westend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(627_959_000, 7_200))); + + assert_expected_events!( + Westend, + vec![ + // Amount to teleport is withdrawn from Sender + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { 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 }) => { + who: *who == ::XcmPallet::check_account(), + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn relay_dest_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + Westend::assert_ump_queue_processed( + true, + Some(PeopleWestend::para_id()), + Some(Weight::from_parts(304_266_000, 7_186)), + ); + + assert_expected_events!( + Westend, + vec![ + // Amount is withdrawn from Relay Chain's `CheckAccount` + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { 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, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_dest_assertions_fail(_t: SystemParaToRelayTest) { + Westend::assert_ump_queue_processed( + false, + Some(PeopleWestend::para_id()), + Some(Weight::from_parts(157_718_000, 3_593)), + ); +} + +fn para_origin_assertions(t: SystemParaToRelayTest) { + type RuntimeEvent = ::RuntimeEvent; + + PeopleWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts( + 351_425_000, + 3_593, + ))); + + PeopleWestend::assert_parachain_system_ump_sent(); + + assert_expected_events!( + PeopleWestend, + vec![ + // Amount is withdrawn from Sender's account + RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + who: *who == t.sender.account_id, + amount: *amount == t.args.amount, + }, + ] + ); +} + +fn para_dest_assertions(t: RelayToSystemParaTest) { + type RuntimeEvent = ::RuntimeEvent; + + PeopleWestend::assert_dmp_queue_complete(Some(Weight::from_parts(162_456_000, 0))); + + assert_expected_events!( + PeopleWestend, + vec![ + // Amount minus fees are deposited in Receiver's account + RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + who: *who == t.receiver.account_id, + }, + ] + ); +} + +fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult { + ::XcmPallet::limited_teleport_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +fn system_para_limited_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult { + ::PolkadotXcm::limited_teleport_assets( + t.signed_origin, + bx!(t.args.dest.into()), + bx!(t.args.beneficiary.into()), + bx!(t.args.assets.into()), + t.args.fee_asset_item, + t.args.weight_limit, + ) +} + +/// Limited Teleport of native asset from Relay Chain to the System Parachain should work +#[test] +fn limited_teleport_native_assets_from_relay_to_system_para_works() { + // Init values for Relay Chain + let amount_to_send: Balance = WESTEND_ED * 1000; + let dest = Westend::child_location_of(PeopleWestend::para_id()); + let beneficiary_id = PeopleWestendReceiver::get(); + let test_args = TestContext { + sender: WestendSender::get(), + receiver: PeopleWestendReceiver::get(), + args: TestArgs::new_relay(dest, beneficiary_id, amount_to_send), + }; + + let mut test = RelayToSystemParaTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(relay_origin_assertions); + test.set_assertion::(para_dest_assertions); + test.set_dispatchable::(relay_limited_teleport_assets); + test.assert(); + + let delivery_fees = Westend::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should work when there is enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_back_from_system_para_to_relay_works() { + // Dependency - Relay Chain's `CheckAccount` should have enough balance + limited_teleport_native_assets_from_relay_to_system_para_works(); + + let amount_to_send: Balance = PEOPLE_WESTEND_ED * 1000; + let destination = PeopleWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + // Fund a sender + PeopleWestend::fund_accounts(vec![(PeopleWestendSender::get(), WESTEND_ED * 2_000u128)]); + + let test_args = TestContext { + sender: PeopleWestendSender::get(), + receiver: WestendReceiver::get(), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = PeopleWestend::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); +} + +/// Limited Teleport of native asset from System Parachain to Relay Chain +/// should't work when there is not enough balance in Relay Chain's `CheckAccount` +#[test] +fn limited_teleport_native_assets_from_system_para_to_relay_fails() { + // Init values for Relay Chain + let amount_to_send: Balance = WESTEND_ED * 1000; + let destination = PeopleWestend::parent_location(); + let beneficiary_id = WestendReceiver::get(); + let assets = (Parent, amount_to_send).into(); + + // Fund a sender + PeopleWestend::fund_accounts(vec![(PeopleWestendSender::get(), WESTEND_ED * 2_000u128)]); + + let test_args = TestContext { + sender: PeopleWestendSender::get(), + receiver: WestendReceiver::get(), + args: TestArgs::new_para(destination, beneficiary_id, amount_to_send, assets, None, 0), + }; + + let mut test = SystemParaToRelayTest::new(test_args); + + let sender_balance_before = test.sender.balance; + let receiver_balance_before = test.receiver.balance; + + test.set_assertion::(para_origin_assertions); + test.set_assertion::(relay_dest_assertions_fail); + test.set_dispatchable::(system_para_limited_teleport_assets); + test.assert(); + + let sender_balance_after = test.sender.balance; + let receiver_balance_after = test.receiver.balance; + + let delivery_fees = PeopleWestend::execute_with(|| { + xcm_helpers::transfer_assets_delivery_fees::< + ::XcmSender, + >(test.args.assets.clone(), 0, test.args.weight_limit, test.args.beneficiary, test.args.dest) + }); + + // Sender's balance is reduced + assert_eq!(sender_balance_before - amount_to_send - delivery_fees, sender_balance_after); + // Receiver's balance does not change + assert_eq!(receiver_balance_after, receiver_balance_before); +} diff --git a/cumulus/parachains/pallets/collective-content/Cargo.toml b/cumulus/parachains/pallets/collective-content/Cargo.toml index 26899a9e77436f09f384a552787f92c99586a804..9ed2822fa3009e2ec014a8065e00824c49ede83e 100644 --- a/cumulus/parachains/pallets/collective-content/Cargo.toml +++ b/cumulus/parachains/pallets/collective-content/Cargo.toml @@ -6,6 +6,9 @@ edition = "2021" description = "Managed content" license = "Apache-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/cumulus/parachains/pallets/collective-content/src/benchmarking.rs b/cumulus/parachains/pallets/collective-content/src/benchmarking.rs index 1f145f725b139b4e93481a9c69b0715471f7ec0b..943386a842766129c2b5d429dc3a9648249ea73f 100644 --- a/cumulus/parachains/pallets/collective-content/src/benchmarking.rs +++ b/cumulus/parachains/pallets/collective-content/src/benchmarking.rs @@ -56,7 +56,7 @@ mod benchmarks { .map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] - _(origin as T::RuntimeOrigin, cid.clone(), Some(expire_at.clone())); + _(origin as T::RuntimeOrigin, cid.clone(), Some(expire_at)); assert_eq!(>::count(), 1); assert_last_event::( diff --git a/cumulus/parachains/pallets/parachain-info/Cargo.toml b/cumulus/parachains/pallets/parachain-info/Cargo.toml index b5b6ec304dfdbbb758f44a1baf763460c1dcba6f..31f7b8aef392f02d46d8eb23cb58f780b738a143 100644 --- a/cumulus/parachains/pallets/parachain-info/Cargo.toml +++ b/cumulus/parachains/pallets/parachain-info/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" license = "Apache-2.0" description = "Pallet to store the parachain ID" +[lints] +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"] } diff --git a/cumulus/parachains/pallets/ping/Cargo.toml b/cumulus/parachains/pallets/ping/Cargo.toml index c661e4260c690d531b3328fe783be66f75ea6a90..5c1099a110a4bb54c39fef27df8ecaa01596efd9 100644 --- a/cumulus/parachains/pallets/ping/Cargo.toml +++ b/cumulus/parachains/pallets/ping/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" license = "Apache-2.0" description = "Ping Pallet for Cumulus XCM/UMP testing." +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 15166de15ae60761ebb08d209479f829c49bef56..43579cfe5bb972ddb6f652f8c86d8c16c4fb3347 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Rococo variant of Asset Hub parachain runtime" license = "Apache-2.0" +[lints] +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" } @@ -87,6 +90,8 @@ 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-rococo-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/rococo-common", default-features = false } [dev-dependencies] asset-test-utils = { path = "../test-utils" } @@ -134,6 +139,8 @@ runtime-benchmarks = [ "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-rococo-common/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", @@ -224,6 +231,8 @@ std = [ "primitive-types/std", "rococo-runtime-constants/std", "scale-info/std", + "snowbridge-rococo-common/std", + "snowbridge-router-primitives/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 46bca804a9cd3f141a8553bf71d2864b4f504e7a..61939a2c80a77ac948c07a279bfc238348699061 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -29,12 +29,13 @@ pub mod xcm_config; use assets_common::{ foreign_creators::ForeignCreators, - local_and_foreign_assets::{LocalAndForeignAssets, MultiLocationConverter}, - matching::FromSiblingParachain, + local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, + matching::{FromNetwork, FromSiblingParachain}, AssetIdForTrustBackedAssetsConvert, MultiLocationForAssetId, }; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use cumulus_primitives_core::AggregateMessageOrigin; +use snowbridge_rococo_common::EthereumNetwork; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -57,8 +58,9 @@ use frame_support::{ genesis_builder_helper::{build_config, create_default_config}, ord_parameter_types, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, - Equals, InstanceFilter, TransformOrigin, + fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, + ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, + TransformOrigin, }, weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, @@ -82,8 +84,9 @@ use parachains_common::{ use sp_runtime::{Perbill, RuntimeDebug}; use xcm::opaque::v3::MultiLocation; use xcm_config::{ - ForeignAssetsConvertedConcreteId, GovernanceLocation, PoolAssetsConvertedConcreteId, - TokenLocation, TrustBackedAssetsConvertedConcreteId, + ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, GovernanceLocation, + PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, }; #[cfg(any(feature = "std", test))] @@ -94,10 +97,6 @@ use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use xcm::latest::prelude::*; -use crate::xcm_config::{ - ForeignCreatorsSovereignAccountOf, LocalAndForeignAssetsMultiLocationMatcher, - TrustBackedAssetsPalletLocation, -}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -112,10 +111,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_004_000, + spec_version: 1_005_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 1, }; @@ -125,10 +124,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 0, }; @@ -279,8 +278,6 @@ impl pallet_assets::Config for Runtime { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub const AllowMultiAssetPools: bool = false; - // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); } @@ -315,36 +312,50 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = (); } +/// Union fungibles implementation for `Assets`` and `ForeignAssets`. +pub type LocalAndForeignAssets = fungibles::UnionOf< + Assets, + ForeignAssets, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + >, + MultiLocation, + AccountId, +>; + impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; - type Currency = Balances; - type AssetBalance = Balance; - type AssetId = MultiLocation; - type Assets = LocalAndForeignAssets< - Assets, - AssetIdForTrustBackedAssetsConvert, - ForeignAssets, + type AssetKind = MultiLocation; + type Assets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + Self::AssetKind, + Self::AccountId, >; - type PoolAssets = PoolAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = + pallet_asset_conversion::WithFirstAsset; type PoolAssetId = u32; + type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam - type PoolSetupFeeReceiver = AssetConversionOrigin; - // should be non-zero if `AllowMultiAssetPools` is true, otherwise can be zero. + type PoolSetupFeeAsset = TokenLocation; + type PoolSetupFeeTarget = ResolveAssetTo; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; type PalletId = AssetConversionPalletId; - type AllowMultiAssetPools = AllowMultiAssetPools; - type MaxSwapPathLength = ConstU32<4>; - type MultiAssetId = Box; - type MultiAssetIdConverter = - MultiLocationConverter; + type MaxSwapPathLength = ConstU32<3>; type MintMinLiquidity = ConstU128<100>; type WeightInfo = weights::pallet_asset_conversion::WeightInfo; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = - crate::xcm_config::BenchmarkMultiLocationConverter>; + type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< + TokenLocation, + parachain_info::Pallet, + xcm_config::AssetsPalletIndex, + >; } parameter_types! { @@ -369,7 +380,10 @@ impl pallet_assets::Config for Runtime { type AssetIdParameter = MultiLocationForAssetId; type Currency = Balances; type CreateOrigin = ForeignCreators< - (FromSiblingParachain>,), + ( + FromSiblingParachain>, + FromNetwork, + ), ForeignCreatorsSovereignAccountOf, AccountId, >; @@ -734,12 +748,9 @@ impl pallet_collator_selection::Config for Runtime { impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Fungibles = LocalAndForeignAssets< - Assets, - AssetIdForTrustBackedAssetsConvert, - ForeignAssets, - >; - type OnChargeAssetTransaction = AssetConversionAdapter; + type Fungibles = LocalAndForeignAssets; + type OnChargeAssetTransaction = + AssetConversionAdapter; } parameter_types! { @@ -846,6 +857,7 @@ impl pallet_xcm_bridge_hub_router::Config for Runtim type UniversalLocation = xcm_config::UniversalLocation; type BridgedNetworkId = xcm_config::bridging::to_westend::WestendNetwork; type Bridges = xcm_config::bridging::NetworkExportTable; + type DestinationVersion = PolkadotXcm; #[cfg(not(feature = "runtime-benchmarks"))] type BridgeHubOrigin = EnsureXcm>; @@ -905,7 +917,6 @@ construct_runtime!( // Bridge utilities. ToWestendXcmRouter: pallet_xcm_bridge_hub_router::::{Pallet, Storage, Call} = 45, - // The main stage. Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, Uniques: pallet_uniques::{Pallet, Call, Storage, Event} = 51, @@ -1142,18 +1153,19 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + MultiLocation, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + + fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: Box, asset2: Box) -> Option<(Balance, Balance)> { - AssetConversion::get_reserves(&asset1, &asset2).ok() + + fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -1359,6 +1371,55 @@ impl_runtime_apis! { ParentThen(Parachain(random_para_id).into()).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Transfer to Relay some local AH asset (local-reserve-transfer) while paying + // fees using teleported native token. + // (We don't care that Relay doesn't accept incoming unknown AH local asset) + let dest = Parent.into(); + + let fee_amount = EXISTENTIAL_DEPOSIT; + let fee_asset: MultiAsset = (MultiLocation::parent(), fee_amount).into(); + + let who = frame_benchmarking::whitelisted_caller(); + // Give some multiple of the existential deposit + let balance = fee_amount + EXISTENTIAL_DEPOSIT * 1000; + let _ = >::make_free_balance_be( + &who, balance, + ); + // verify initial balance + assert_eq!(Balances::free_balance(&who), balance); + + // set up local asset + let asset_amount = 10u128; + let initial_asset_amount = asset_amount * 10; + let (asset_id, _, _) = pallet_assets::benchmarking::create_default_minted_asset::< + Runtime, + pallet_assets::Instance1 + >(true, initial_asset_amount); + let asset_location = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(u32::from(asset_id).into())) + ); + let transfer_asset: MultiAsset = (asset_location, asset_amount).into(); + + let assets: MultiAssets = vec![fee_asset.clone(), transfer_asset].into(); + let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 }; + + // verify transferred successfully + let verify = Box::new(move || { + // verify native balance after transfer, decreased by transferred fee amount + // (plus transport fees) + assert!(Balances::free_balance(&who) <= balance - fee_amount); + // verify asset balance decreased by exactly transferred amount + assert_eq!( + Assets::balance(asset_id.into(), &who), + initial_asset_amount - asset_amount, + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } impl XcmBridgeHubRouterConfig for Runtime { @@ -1367,11 +1428,26 @@ impl_runtime_apis! { xcm_config::bridging::SiblingBridgeHubParaId::get().into() ); } - fn ensure_bridged_target_destination() -> MultiLocation { + fn ensure_bridged_target_destination() -> Result { ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( xcm_config::bridging::SiblingBridgeHubParaId::get().into() ); - xcm_config::bridging::to_westend::AssetHubWestend::get() + let bridged_asset_hub = xcm_config::bridging::to_westend::AssetHubWestend::get(); + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridged_asset_hub), + XCM_VERSION, + ).map_err(|e| { + log::error!( + "Failed to dispatch `force_xcm_version({:?}, {:?}, {:?})`, error: {:?}", + RuntimeOrigin::root(), + bridged_asset_hub, + XCM_VERSION, + e + ); + BenchmarkError::Stop("XcmVersion was not stored!") + })?; + Ok(bridged_asset_hub) } } @@ -1390,7 +1466,7 @@ impl_runtime_apis! { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, + xcm_config::XcmConfig, ExistentialDepositMultiAsset, xcm_config::PriceForParentDelivery, >; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system.rs index 4f993155c19c7fc521f00f00b4841a15064d15c8..b257c3825a7e756cf5260e3b14e17f78b34d36c8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system.rs @@ -152,4 +152,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index aa994a7608a20ffa8f650bc40f3e199346f5207c..fa9e86102c619c9ff68316cae2a27a7f79fea2e6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -42,5 +42,4 @@ pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs index 4514fbfa8763ad40a65ad604eadde797c6f6bf9b..0486932d1d6e44a7fe4a1c01640d6e3329577a2c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs @@ -17,25 +17,23 @@ //! Autogenerated weights for `pallet_asset_conversion` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-30, STEPS: `20`, REPEAT: `2`, 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("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/debug/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 +// --chain=asset-hub-rococo-dev +// --steps=20 +// --repeat=2 +// --pallet=pallet-asset-conversion // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/cumulus/.git/.artifacts/bench.json -// --pallet=pallet_asset_conversion -// --chain=asset-hub-rococo-dev -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_conversion.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,9 +48,7 @@ pub struct WeightInfo(PhantomData); impl pallet_asset_conversion::WeightInfo for WeightInfo { /// Storage: `AssetConversion::Pools` (r:1 w:1) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0x76a2c49709deec21d9c05f96c1f47351` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x76a2c49709deec21d9c05f96c1f47351` (r:1 w:0) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:1 w:1) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) @@ -66,22 +62,22 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn create_pool() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `6196` - // Minimum execution time: 88_484_000 picoseconds. - Weight::from_parts(92_964_000, 0) - .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `408` + // Estimated: `4689` + // Minimum execution time: 906_000_000 picoseconds. + Weight::from_parts(945_000_000, 0) + .saturating_add(Weight::from_parts(0, 4689)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Asset` (r:1 w:1) /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:2 w:2) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Account` (r:2 w:2) @@ -90,34 +86,32 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `1117` // Estimated: `7404` - // Minimum execution time: 153_015_000 picoseconds. - Weight::from_parts(157_018_000, 0) + // Minimum execution time: 1_609_000_000 picoseconds. + Weight::from_parts(1_631_000_000, 0) .saturating_add(Weight::from_parts(0, 7404)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Asset` (r:1 w:1) /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:2 w:2) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0x2433d831722b1f4aeb1666953f1c0e77` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x2433d831722b1f4aeb1666953f1c0e77` (r:1 w:0) /// Storage: `PoolAssets::Account` (r:1 w:1) /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn remove_liquidity() -> Weight { // Proof Size summary in bytes: // Measured: `1106` // Estimated: `7404` - // Minimum execution time: 141_726_000 picoseconds. - Weight::from_parts(147_865_000, 0) + // Minimum execution time: 1_480_000_000 picoseconds. + Weight::from_parts(1_506_000_000, 0) .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `ForeignAssets::Asset` (r:2 w:2) @@ -126,15 +120,19 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn swap_exact_tokens_for_tokens() -> Weight { + /// The range of component `n` is `[2, 3]`. + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1148` - // Estimated: `13818` - // Minimum execution time: 168_619_000 picoseconds. - Weight::from_parts(174_283_000, 0) - .saturating_add(Weight::from_parts(0, 13818)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±73)` + // Minimum execution time: 933_000_000 picoseconds. + Weight::from_parts(950_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 18_792_550 + .saturating_add(Weight::from_parts(46_683_673, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -142,14 +140,18 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:4 w:4) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn swap_tokens_for_exact_tokens() -> Weight { + /// The range of component `n` is `[2, 3]`. + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1148` - // Estimated: `13818` - // Minimum execution time: 171_565_000 picoseconds. - Weight::from_parts(173_702_000, 0) - .saturating_add(Weight::from_parts(0, 13818)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±180)` + // Minimum execution time: 936_000_000 picoseconds. + Weight::from_parts(954_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 15_942_881 + .saturating_add(Weight::from_parts(39_755_102, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index afe85fdaf28820f52c7a134e66c6884d96dd6aef..f8820bbb58cb24afe1afe034e131414368089444 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,37 +64,95 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 24_498_000 picoseconds. - Weight::from_parts(25_385_000, 0) + // Minimum execution time: 25_003_000 picoseconds. + Weight::from_parts(25_800_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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: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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `39` - // Estimated: `3504` - // Minimum execution time: 19_746_000 picoseconds. - Weight::from_parts(20_535_000, 0) - .saturating_add(Weight::from_parts(0, 3504)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 88_832_000 picoseconds. + Weight::from_parts(90_491_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `39` - // Estimated: `3504` - // Minimum execution time: 15_059_000 picoseconds. - Weight::from_parts(15_386_000, 0) - .saturating_add(Weight::from_parts(0, 3504)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `400` + // Estimated: `6196` + // Minimum execution time: 138_911_000 picoseconds. + Weight::from_parts(142_483_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `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: `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 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)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -112,8 +170,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_108_000 picoseconds. - Weight::from_parts(7_458_000, 0) + // Minimum execution time: 7_081_000 picoseconds. + Weight::from_parts(7_397_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -123,8 +181,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_205_000 picoseconds. - Weight::from_parts(2_360_000, 0) + // Minimum execution time: 2_007_000 picoseconds. + Weight::from_parts(2_183_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -150,8 +208,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_099_000 picoseconds. - Weight::from_parts(29_580_000, 0) + // Minimum execution time: 28_790_000 picoseconds. + Weight::from_parts(29_767_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -176,8 +234,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_161_000 picoseconds. - Weight::from_parts(31_933_000, 0) + // Minimum execution time: 30_951_000 picoseconds. + Weight::from_parts(31_804_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -188,8 +246,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_158_000 picoseconds. - Weight::from_parts(2_316_000, 0) + // Minimum execution time: 2_164_000 picoseconds. + Weight::from_parts(2_311_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -199,8 +257,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_934_000 picoseconds. - Weight::from_parts(17_655_000, 0) + // Minimum execution time: 16_906_000 picoseconds. + Weight::from_parts(17_612_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +269,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 17_658_000 picoseconds. - Weight::from_parts(17_973_000, 0) + // Minimum execution time: 17_443_000 picoseconds. + Weight::from_parts(18_032_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -223,8 +281,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_673_000 picoseconds. - Weight::from_parts(19_027_000, 0) + // Minimum execution time: 18_992_000 picoseconds. + Weight::from_parts(19_464_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -246,8 +304,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_171_000 picoseconds. - Weight::from_parts(27_802_000, 0) + // Minimum execution time: 28_011_000 picoseconds. + Weight::from_parts(28_716_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -258,8 +316,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_423_000 picoseconds. - Weight::from_parts(9_636_000, 0) + // Minimum execution time: 9_533_000 picoseconds. + Weight::from_parts(9_856_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -269,8 +327,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 17_442_000 picoseconds. - Weight::from_parts(17_941_000, 0) + // Minimum execution time: 17_628_000 picoseconds. + Weight::from_parts(18_146_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -293,8 +351,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 34_340_000 picoseconds. - Weight::from_parts(34_934_000, 0) + // Minimum execution time: 34_877_000 picoseconds. + Weight::from_parts(35_607_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -307,8 +365,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 5_496_000 picoseconds. - Weight::from_parts(5_652_000, 0) + // Minimum execution time: 5_370_000 picoseconds. + Weight::from_parts(5_616_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -319,8 +377,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_140_000 picoseconds. - Weight::from_parts(26_824_000, 0) + // Minimum execution time: 26_820_000 picoseconds. + Weight::from_parts(27_143_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs index 7e12453583d49faebef8c0a3764d8c91aeae768c..775bc3bdb80f54a8db97d1c1fdbf5a837fdb95b1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -58,8 +58,8 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `154` // Estimated: `1639` - // Minimum execution time: 7_924_000 picoseconds. - Weight::from_parts(8_199_000, 0) + // Minimum execution time: 7_853_000 picoseconds. + Weight::from_parts(8_443_000, 0) .saturating_add(Weight::from_parts(0, 1639)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -72,8 +72,8 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `144` // Estimated: `1629` - // Minimum execution time: 4_265_000 picoseconds. - Weight::from_parts(4_417_000, 0) + // Minimum execution time: 4_333_000 picoseconds. + Weight::from_parts(4_501_000, 0) .saturating_add(Weight::from_parts(0, 1629)) .saturating_add(T::DbWeight::get().reads(2)) } @@ -83,12 +83,14 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `150` // Estimated: `1502` - // Minimum execution time: 10_292_000 picoseconds. - Weight::from_parts(10_797_000, 0) + // Minimum execution time: 10_167_000 picoseconds. + Weight::from_parts(10_667_000, 0) .saturating_add(Weight::from_parts(0, 1502)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::SupportedVersion` (r:2 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x3302afcb67e838a3f960251b417b9a4f` (r:1 w:0) @@ -99,8 +101,6 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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) @@ -115,12 +115,12 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send_message() -> Weight { // Proof Size summary in bytes: - // Measured: `387` - // Estimated: `3852` - // Minimum execution time: 61_995_000 picoseconds. - Weight::from_parts(65_137_000, 0) - .saturating_add(Weight::from_parts(0, 3852)) - .saturating_add(T::DbWeight::get().reads(11)) + // Measured: `448` + // Estimated: `6388` + // Minimum execution time: 60_584_000 picoseconds. + Weight::from_parts(62_467_000, 0) + .saturating_add(Weight::from_parts(0, 6388)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } } 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 4da0a2500a541907f1126b776e3f73182062b306..2826c18f3f7c3fa82aac60dcdb45588f7f3b66eb 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 @@ -21,7 +21,7 @@ use super::{ }; use assets_common::{ local_and_foreign_assets::MatchesLocalAndForeignAssetsMultiLocation, - matching::{FromSiblingParachain, IsForeignConcreteAsset}, + matching::{FromNetwork, FromSiblingParachain, IsForeignConcreteAsset}, }; use frame_support::{ match_types, parameter_types, @@ -32,33 +32,33 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AssetFeeAsExistentialDepositMultiplier, ConcreteAssetFromSystem, - RelayOrOtherSystemParachains, + AllSiblingSystemParachains, AssetFeeAsExistentialDepositMultiplier, + ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use rococo_runtime_constants::system_parachain; +use snowbridge_rococo_common::EthereumNetwork; +use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; use xcm::latest::prelude::*; +#[allow(deprecated)] +use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, - IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, 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}; -#[cfg(feature = "runtime-benchmarks")] -use cumulus_primitives_core::ParaId; - parameter_types! { pub const TokenLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: NetworkId = NetworkId::Rococo; @@ -66,8 +66,8 @@ parameter_types! { pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get()), Parachain(ParachainInfo::parachain_id().into())); pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); - pub TrustBackedAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); + pub AssetsPalletIndex: u32 = ::index() as u32; + pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(AssetsPalletIndex::get() as u8).into(); pub ForeignAssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); pub PoolAssetsPalletLocation: MultiLocation = @@ -93,9 +93,13 @@ pub type LocationToAccountId = ( // Different global consensus parachain sovereign account. // (Used for over-bridge transfers and reserve processing) GlobalConsensusParachainConvertsFor, + // Ethereum contract sovereign account. + // (Used to get convert ethereum contract locations to sovereign account) + GlobalConsensusEthereumConvertsFor, ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -239,10 +243,6 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; - pub type ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -265,10 +265,11 @@ impl Contains for SafeCallFilter { // Allow to change dedicated storage items (called by governance-like) match call { RuntimeCall::System(frame_system::Call::set_storage { items }) - if items.iter().all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterByteFee::key())) || - items - .iter() - .all(|(k, _)| k.eq(&bridging::XcmBridgeHubRouterBaseFee::key())) => + if items.iter().all(|(k, _)| { + k.eq(&bridging::XcmBridgeHubRouterByteFee::key()) | + k.eq(&bridging::XcmBridgeHubRouterBaseFee::key()) | + k.eq(&bridging::to_ethereum::BridgeHubEthereumBaseFee::key()) + }) => return true, _ => (), }; @@ -282,19 +283,14 @@ impl Contains for SafeCallFilter { 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 { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::XcmpQueue(..) | RuntimeCall::MessageQueue(..) | RuntimeCall::Assets( @@ -487,7 +483,7 @@ pub type Barrier = TrailingSetTopicAsId< Equals, )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -513,25 +509,13 @@ pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = ForeignAssetsInstance, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::BRIDGE_HUB_ID | - system_parachain::CONTRACTS_ID | - system_parachain::ENCOINTER_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// @@ -552,7 +536,10 @@ impl xcm_executor::Config for XcmConfig { // as reserve locations (we trust the Bridge Hub to relay the message that a reserve is being // held). Asset Hub may _act_ as a reserve location for ROC and assets created // under `pallet-assets`. Users must use teleport where allowed (e.g. ROC with the Relay Chain). - type IsReserve = (bridging::to_westend::IsTrustedBridgedReserveLocationForConcreteAsset,); + type IsReserve = ( + bridging::to_westend::IsTrustedBridgedReserveLocationForConcreteAsset, + bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset, + ); type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; @@ -603,7 +590,8 @@ impl xcm_executor::Config for XcmConfig { XcmFeeToAccount, >; type MessageExporter = (); - type UniversalAliases = (bridging::to_westend::UniversalAliases,); + type UniversalAliases = + (bridging::to_westend::UniversalAliases, bridging::to_ethereum::UniversalAliases); type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; type Aliasers = Nothing; @@ -631,6 +619,9 @@ pub type XcmRouter = WithUniqueTopic<( // Router which wraps and sends xcm to BridgeHub to be delivered to the Westend // GlobalConsensus ToWestendXcmRouter, + // Router which wraps and sends xcm to BridgeHub to be delivered to the Ethereum + // GlobalConsensus + SovereignPaidRemoteExporter, )>; impl pallet_xcm::Config for Runtime { @@ -676,6 +667,7 @@ pub type ForeignCreatorsSovereignAccountOf = ( SiblingParachainConvertsVia, AccountId32Aliases, ParentIsPreset, + GlobalConsensusEthereumConvertsFor, ); /// Simple conversion of `u32` into an `AssetId` for use in benchmarking. @@ -687,33 +679,6 @@ impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { } } -#[cfg(feature = "runtime-benchmarks")] -pub struct BenchmarkMultiLocationConverter { - _phantom: sp_std::marker::PhantomData, -} - -#[cfg(feature = "runtime-benchmarks")] -impl - pallet_asset_conversion::BenchmarkHelper> - for BenchmarkMultiLocationConverter -where - SelfParaId: frame_support::traits::Get, -{ - fn asset_id(asset_id: u32) -> MultiLocation { - MultiLocation { - parents: 1, - interior: X3( - Parachain(SelfParaId::get().into()), - PalletInstance(::index() as u8), - GeneralIndex(asset_id.into()), - ), - } - } - fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box { - sp_std::boxed::Box::new(Self::asset_id(asset_id)) - } -} - /// All configuration related to bridging pub mod bridging { use super::*; @@ -751,10 +716,17 @@ pub mod bridging { sp_std::vec::Vec::new().into_iter() .chain(to_westend::BridgeTable::get()) .collect(); + + pub EthereumBridgeTable: sp_std::vec::Vec = + sp_std::vec::Vec::new().into_iter() + .chain(to_ethereum::BridgeTable::get()) + .collect(); } pub type NetworkExportTable = xcm_builder::NetworkExportTable; + pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable; + pub mod to_westend { use super::*; @@ -831,6 +803,56 @@ pub mod bridging { } } + pub mod to_ethereum { + use super::*; + + parameter_types! { + /// User fee for ERC20 token transfer back to Ethereum. + /// (initially was calculated by test `OutboundQueue::calculate_fees` - ETH/ROC 1/400 and fee_per_gas 20 GWEI = 2200698000000 + *25%) + /// Needs to be more than fee calculated from DefaultFeeConfig FeeConfigRecord in snowbridge:parachain/pallets/outbound-queue/src/lib.rs + /// Polkadot uses 10 decimals, Kusama and Rococo 12 decimals. + pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; + pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get(); + pub SiblingBridgeHubWithEthereumInboundQueueInstance: MultiLocation = MultiLocation::new( + 1, + X2( + Parachain(SiblingBridgeHubParaId::get()), + PalletInstance(snowbridge_rococo_common::INBOUND_QUEUE_MESSAGES_PALLET_INDEX) + ) + ); + + /// Set up exporters configuration. + /// `Option` represents static "base fee" which is used for total delivery fee calculation. + pub BridgeTable: sp_std::vec::Vec = sp_std::vec![ + NetworkExportTableItem::new( + EthereumNetwork::get(), + Some(sp_std::vec![Junctions::Here]), + SiblingBridgeHub::get(), + Some(( + XcmBridgeHubRouterFeeAssetId::get(), + BridgeHubEthereumBaseFee::get(), + ).into()) + ), + ]; + + /// Universal aliases + pub UniversalAliases: BTreeSet<(MultiLocation, Junction)> = BTreeSet::from_iter( + sp_std::vec![ + (SiblingBridgeHubWithEthereumInboundQueueInstance::get(), GlobalConsensus(EthereumNetwork::get())), + ] + ); + } + + pub type IsTrustedBridgedReserveLocationForForeignAsset = + matching::IsForeignConcreteAsset>; + + impl Contains<(MultiLocation, Junction)> for UniversalAliases { + fn contains(alias: &(MultiLocation, Junction)) -> bool { + UniversalAliases::get().contains(alias) + } + } + } + /// Benchmarks helper for bridging configuration. #[cfg(feature = "runtime-benchmarks")] pub struct BridgingBenchmarksHelper; 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 7bb71a77de7dccb6552e70bbec8d0679d29d7bd6..42c91cc8ea69c7c907bf9593200ebbf676c2b6f0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -673,9 +673,16 @@ 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}; fn bridging_to_asset_hub_westend() -> TestBridgingConfig { - asset_test_utils::test_cases_over_bridge::TestBridgingConfig { + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridging::to_westend::AssetHubWestend::get()), + XCM_VERSION, + ) + .expect("version saved!"); + TestBridgingConfig { bridged_network: bridging::to_westend::WestendNetwork::get(), local_bridge_hub_para_id: bridging::SiblingBridgeHubParaId::get(), local_bridge_hub_location: bridging::SiblingBridgeHub::get(), @@ -858,3 +865,55 @@ 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()), + || { + ( + 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 change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::to_ethereum::BridgeHubEthereumBaseFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridging::to_ethereum::BridgeHubEthereumBaseFee::key().to_vec(), + bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 2eb8e9a55d773d20af291683895185b6cafdb96f..1a1ed0465a34ee62bba6c2921df08dee27cd56f2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Westend variant of Asset Hub parachain runtime" license = "Apache-2.0" +[lints] +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 } 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 dfe4990df96640eb18e568031b2f4fe0f6a7ef6c..e0dff0c4516e3fa3fe9dd348a1171e6db74cfba4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -27,11 +27,8 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; -use crate::xcm_config::{ - LocalAndForeignAssetsMultiLocationMatcher, TrustBackedAssetsPalletLocation, -}; use assets_common::{ - local_and_foreign_assets::{LocalAndForeignAssets, MultiLocationConverter}, + local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; @@ -43,8 +40,10 @@ use frame_support::{ genesis_builder_helper::{build_config, create_default_config}, ord_parameter_types, parameter_types, traits::{ - tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, - ConstU64, ConstU8, Equals, InstanceFilter, TransformOrigin, + fungible, fungibles, + tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, + InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, @@ -80,7 +79,8 @@ use sp_version::RuntimeVersion; use xcm::opaque::v3::MultiLocation; use xcm_config::{ ForeignAssetsConvertedConcreteId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, WestendLocation, XcmOriginToTransactDispatchOrigin, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation, + XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -109,10 +109,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 13, + transaction_version: 14, state_version: 0, }; @@ -262,8 +262,6 @@ impl pallet_assets::Config for Runtime { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub const AllowMultiAssetPools: bool = false; - // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); } @@ -297,35 +295,50 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = (); } +/// Union fungibles implementation for `Assets`` and `ForeignAssets`. +pub type LocalAndForeignAssets = fungibles::UnionOf< + Assets, + ForeignAssets, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + >, + MultiLocation, + AccountId, +>; + impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; - type Currency = Balances; - type AssetBalance = Balance; - type AssetId = MultiLocation; - type Assets = LocalAndForeignAssets< - Assets, - AssetIdForTrustBackedAssetsConvert, - ForeignAssets, + type AssetKind = MultiLocation; + type Assets = fungible::UnionOf< + Balances, + LocalAndForeignAssets, + TargetFromLeft, + Self::AssetKind, + Self::AccountId, >; - type PoolAssets = PoolAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = + pallet_asset_conversion::WithFirstAsset; type PoolAssetId = u32; + type PoolAssets = PoolAssets; type PoolSetupFee = ConstU128<0>; // Asset class deposit fees are sufficient to prevent spam - type PoolSetupFeeReceiver = AssetConversionOrigin; - type LiquidityWithdrawalFee = LiquidityWithdrawalFee; // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero. + type PoolSetupFeeAsset = WestendLocation; + type PoolSetupFeeTarget = ResolveAssetTo; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type LPFee = ConstU32<3>; type PalletId = AssetConversionPalletId; - type AllowMultiAssetPools = AllowMultiAssetPools; - type MaxSwapPathLength = ConstU32<4>; - type MultiAssetId = Box; - type MultiAssetIdConverter = - MultiLocationConverter; + type MaxSwapPathLength = ConstU32<3>; type MintMinLiquidity = ConstU128<100>; type WeightInfo = weights::pallet_asset_conversion::WeightInfo; #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = - crate::xcm_config::BenchmarkMultiLocationConverter>; + type BenchmarkHelper = assets_common::benchmarks::AssetPairFactory< + WestendLocation, + parachain_info::Pallet, + xcm_config::AssetsPalletIndex, + >; } parameter_types! { @@ -710,12 +723,9 @@ impl pallet_collator_selection::Config for Runtime { impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Fungibles = LocalAndForeignAssets< - Assets, - AssetIdForTrustBackedAssetsConvert, - ForeignAssets, - >; - type OnChargeAssetTransaction = AssetConversionAdapter; + type Fungibles = LocalAndForeignAssets; + type OnChargeAssetTransaction = + AssetConversionAdapter; } parameter_types! { @@ -822,6 +832,7 @@ impl pallet_xcm_bridge_hub_router::Config for Runtime type UniversalLocation = xcm_config::UniversalLocation; type BridgedNetworkId = xcm_config::bridging::to_rococo::RococoNetwork; type Bridges = xcm_config::bridging::NetworkExportTable; + type DestinationVersion = PolkadotXcm; #[cfg(not(feature = "runtime-benchmarks"))] type BridgeHubOrigin = EnsureXcm>; @@ -924,8 +935,6 @@ pub type Migrations = ( // unreleased pallet_collator_selection::migration::v1::MigrateToV1, // unreleased - migrations::NativeAssetParents0ToParents1Migration, - // unreleased pallet_multisig::migrations::v1::MigrateToV1, // unreleased InitStorageVersions, @@ -1217,20 +1226,19 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - Box, + MultiLocation, > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: Box, asset2: Box, amount: u128, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: MultiLocation, asset2: MultiLocation, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: Box, asset2: Box) -> Option<(Balance, Balance)> { - AssetConversion::get_reserves(&asset1, &asset2).ok() + fn get_reserves(asset1: MultiLocation, asset2: MultiLocation) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -1431,6 +1439,55 @@ impl_runtime_apis! { ParentThen(Parachain(random_para_id).into()).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Transfer to Relay some local AH asset (local-reserve-transfer) while paying + // fees using teleported native token. + // (We don't care that Relay doesn't accept incoming unknown AH local asset) + let dest = Parent.into(); + + let fee_amount = EXISTENTIAL_DEPOSIT; + let fee_asset: MultiAsset = (MultiLocation::parent(), fee_amount).into(); + + let who = frame_benchmarking::whitelisted_caller(); + // Give some multiple of the existential deposit + let balance = fee_amount + EXISTENTIAL_DEPOSIT * 1000; + let _ = >::make_free_balance_be( + &who, balance, + ); + // verify initial balance + assert_eq!(Balances::free_balance(&who), balance); + + // set up local asset + let asset_amount = 10u128; + let initial_asset_amount = asset_amount * 10; + let (asset_id, _, _) = pallet_assets::benchmarking::create_default_minted_asset::< + Runtime, + pallet_assets::Instance1 + >(true, initial_asset_amount); + let asset_location = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(u32::from(asset_id).into())) + ); + let transfer_asset: MultiAsset = (asset_location, asset_amount).into(); + + let assets: MultiAssets = vec![fee_asset.clone(), transfer_asset].into(); + let fee_index = if assets.get(0).unwrap().eq(&fee_asset) { 0 } else { 1 }; + + // verify transferred successfully + let verify = Box::new(move || { + // verify native balance after transfer, decreased by transferred fee amount + // (plus transport fees) + assert!(Balances::free_balance(&who) <= balance - fee_amount); + // verify asset balance decreased by exactly transferred amount + assert_eq!( + Assets::balance(asset_id.into(), &who), + initial_asset_amount - asset_amount, + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } use pallet_xcm_bridge_hub_router::benchmarking::{ @@ -1444,11 +1501,26 @@ impl_runtime_apis! { xcm_config::bridging::SiblingBridgeHubParaId::get().into() ); } - fn ensure_bridged_target_destination() -> MultiLocation { + fn ensure_bridged_target_destination() -> Result { ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( xcm_config::bridging::SiblingBridgeHubParaId::get().into() ); - xcm_config::bridging::to_rococo::AssetHubRococo::get() + let bridged_asset_hub = xcm_config::bridging::to_rococo::AssetHubRococo::get(); + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridged_asset_hub), + XCM_VERSION, + ).map_err(|e| { + log::error!( + "Failed to dispatch `force_xcm_version({:?}, {:?}, {:?})`, error: {:?}", + RuntimeOrigin::root(), + bridged_asset_hub, + XCM_VERSION, + e + ); + BenchmarkError::Stop("XcmVersion was not stored!") + })?; + Ok(bridged_asset_hub) } } @@ -1626,120 +1698,3 @@ cumulus_pallet_parachain_system::register_validate_block! { Runtime = Runtime, BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, } - -pub mod migrations { - use super::*; - use frame_support::{ - pallet_prelude::Get, - traits::{ - fungibles::{Inspect, Mutate}, - tokens::Preservation, - OnRuntimeUpgrade, OriginTrait, - }, - }; - use parachains_common::impls::AccountIdOf; - use sp_runtime::{traits::StaticLookup, Saturating}; - - /// Temporary migration because of bug with native asset, it can be removed once applied on - /// `AssetHubWestend`. Migrates pools with `MultiLocation { parents: 0, interior: Here }` to - /// `MultiLocation { parents: 1, interior: Here }` - pub struct NativeAssetParents0ToParents1Migration(sp_std::marker::PhantomData); - impl< - T: pallet_asset_conversion::Config< - MultiAssetId = Box, - AssetId = MultiLocation, - >, - > OnRuntimeUpgrade for NativeAssetParents0ToParents1Migration - where - ::PoolAssetId: Into, - AccountIdOf: Into<[u8; 32]>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - <::Lookup as StaticLookup>::Source: - From<::AccountId>, - sp_runtime::AccountId32: From<::AccountId>, - { - fn on_runtime_upgrade() -> Weight { - let invalid_native_asset = MultiLocation { parents: 0, interior: Here }; - let valid_native_asset = WestendLocation::get(); - - let mut reads: u64 = 1; - let mut writes: u64 = 0; - - // migrate pools with invalid native asset - let pools = pallet_asset_conversion::Pools::::iter().collect::>(); - reads.saturating_accrue(1); - for (old_pool_id, pool_info) in pools { - let old_pool_account = - pallet_asset_conversion::Pallet::::get_pool_account(&old_pool_id); - reads.saturating_accrue(1); - let pool_asset_id = pool_info.lp_token.clone(); - if old_pool_id.0.as_ref() != &invalid_native_asset { - // skip, if ok - continue - } - - // fix new account - let new_pool_id = pallet_asset_conversion::Pallet::::get_pool_id( - Box::new(valid_native_asset), - old_pool_id.1.clone(), - ); - let new_pool_account = - pallet_asset_conversion::Pallet::::get_pool_account(&new_pool_id); - frame_system::Pallet::::inc_providers(&new_pool_account); - reads.saturating_accrue(2); - writes.saturating_accrue(1); - - // move currency - let _ = Balances::transfer_all( - RuntimeOrigin::signed(sp_runtime::AccountId32::from(old_pool_account.clone())), - sp_runtime::AccountId32::from(new_pool_account.clone()).into(), - false, - ); - reads.saturating_accrue(2); - writes.saturating_accrue(2); - - // move LP token - let _ = T::PoolAssets::transfer( - pool_asset_id.clone(), - &old_pool_account, - &new_pool_account, - T::PoolAssets::balance(pool_asset_id.clone(), &old_pool_account), - Preservation::Expendable, - ); - reads.saturating_accrue(1); - writes.saturating_accrue(2); - - // change the ownership of LP token - let _ = pallet_assets::Pallet::::transfer_ownership( - RuntimeOrigin::signed(sp_runtime::AccountId32::from(old_pool_account.clone())), - pool_asset_id.into(), - sp_runtime::AccountId32::from(new_pool_account.clone()).into(), - ); - reads.saturating_accrue(1); - writes.saturating_accrue(2); - - // move LocalOrForeignAssets - let _ = T::Assets::transfer( - *old_pool_id.1.as_ref(), - &old_pool_account, - &new_pool_account, - T::Assets::balance(*old_pool_id.1.as_ref(), &old_pool_account), - Preservation::Expendable, - ); - reads.saturating_accrue(1); - writes.saturating_accrue(2); - - // dec providers for old account - let _ = frame_system::Pallet::::dec_providers(&old_pool_account); - writes.saturating_accrue(1); - - // change pool key - pallet_asset_conversion::Pools::::insert(new_pool_id, pool_info); - pallet_asset_conversion::Pools::::remove(old_pool_id); - } - - T::DbWeight::get().reads_writes(reads, writes) - } - } -} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system.rs index 6c741af2a13dcca9cc490c13933397f6ab841c28..687b87e43915bbfa26330dd981a462571f5b79f3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system.rs @@ -151,4 +151,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index 2462138b04acf37f7cc5bc2568ef45a84e9a2e43..2f1fcfb05f39151e018d74e8587faa0e79afd8b6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -41,5 +41,4 @@ pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs index e48f2e2ef7267cf714b5ea385290c4180365ddb5..7a5aed3d7c69ce54b229d859f56a6a2dd4881460 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs @@ -16,27 +16,23 @@ //! Autogenerated weights for `pallet_asset_conversion` //! //! 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: 2023-10-30, STEPS: `20`, REPEAT: `2`, 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("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// ./target/debug/polkadot-parachain // benchmark // pallet // --chain=asset-hub-westend-dev -// --wasm-execution=compiled -// --pallet=pallet_asset_conversion -// --no-storage-info -// --no-median-slopes -// --no-min-squares +// --steps=20 +// --repeat=2 +// --pallet=pallet-asset-conversion // --extrinsic=* -// --steps=50 -// --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_conversion.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -51,9 +47,7 @@ pub struct WeightInfo(PhantomData); impl pallet_asset_conversion::WeightInfo for WeightInfo { /// Storage: `AssetConversion::Pools` (r:1 w:1) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0x76a2c49709deec21d9c05f96c1f47351` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x76a2c49709deec21d9c05f96c1f47351` (r:1 w:0) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:1 w:1) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) @@ -67,22 +61,22 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn create_pool() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `6196` - // Minimum execution time: 90_011_000 picoseconds. - Weight::from_parts(92_372_000, 0) - .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `408` + // Estimated: `4689` + // Minimum execution time: 922_000_000 picoseconds. + Weight::from_parts(1_102_000_000, 0) + .saturating_add(Weight::from_parts(0, 4689)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Asset` (r:1 w:1) /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:2 w:2) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Account` (r:2 w:2) @@ -91,34 +85,32 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `1117` // Estimated: `7404` - // Minimum execution time: 153_484_000 picoseconds. - Weight::from_parts(155_465_000, 0) + // Minimum execution time: 1_597_000_000 picoseconds. + Weight::from_parts(1_655_000_000, 0) .saturating_add(Weight::from_parts(0, 7404)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(1224), added: 3699, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Asset` (r:1 w:1) /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:2 w:2) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0x2433d831722b1f4aeb1666953f1c0e77` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x2433d831722b1f4aeb1666953f1c0e77` (r:1 w:0) /// Storage: `PoolAssets::Account` (r:1 w:1) /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn remove_liquidity() -> Weight { // Proof Size summary in bytes: // Measured: `1106` // Estimated: `7404` - // Minimum execution time: 141_326_000 picoseconds. - Weight::from_parts(143_882_000, 0) + // Minimum execution time: 1_500_000_000 picoseconds. + Weight::from_parts(1_633_000_000, 0) .saturating_add(Weight::from_parts(0, 7404)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `ForeignAssets::Asset` (r:2 w:2) @@ -127,15 +119,19 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn swap_exact_tokens_for_tokens() -> Weight { + /// The range of component `n` is `[2, 3]`. + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1148` - // Estimated: `13818` - // Minimum execution time: 168_556_000 picoseconds. - Weight::from_parts(170_313_000, 0) - .saturating_add(Weight::from_parts(0, 13818)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±92)` + // Minimum execution time: 930_000_000 picoseconds. + Weight::from_parts(960_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 17_993_720 + .saturating_add(Weight::from_parts(41_959_183, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -143,14 +139,18 @@ impl pallet_asset_conversion::WeightInfo for WeightInfo /// Proof: `ForeignAssets::Asset` (`max_values`: None, `max_size`: Some(808), added: 3283, mode: `MaxEncodedLen`) /// Storage: `ForeignAssets::Account` (r:4 w:4) /// Proof: `ForeignAssets::Account` (`max_values`: None, `max_size`: Some(732), added: 3207, mode: `MaxEncodedLen`) - fn swap_tokens_for_exact_tokens() -> Weight { + /// The range of component `n` is `[2, 3]`. + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1148` - // Estimated: `13818` - // Minimum execution time: 167_704_000 picoseconds. - Weight::from_parts(170_034_000, 0) - .saturating_add(Weight::from_parts(0, 13818)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(8)) + // Measured: `0 + n * (557 ±0)` + // Estimated: `7404 + n * (393 ±92)` + // Minimum execution time: 940_000_000 picoseconds. + Weight::from_parts(956_000_000, 0) + .saturating_add(Weight::from_parts(0, 7404)) + // Standard Error: 15_746_647 + .saturating_add(Weight::from_parts(39_193_877, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 393).saturating_mul(n.into())) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs index 340edafb0b0c3ea6319e1c2213bcface8a9399b4..504731f4a9ef743e62090582901a63f7aee78829 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -64,40 +64,102 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 25_534_000 picoseconds. - Weight::from_parts(26_413_000, 0) + // Minimum execution time: 25_482_000 picoseconds. + Weight::from_parts(26_622_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .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: `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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 20_513_000 picoseconds. - Weight::from_parts(20_837_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 87_319_000 picoseconds. + Weight::from_parts(89_764_000, 0) + .saturating_add(Weight::from_parts(0, 3610)) + .saturating_add(T::DbWeight::get().reads(8)) + .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: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 14_977_000 picoseconds. - Weight::from_parts(15_207_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `367` + // Estimated: `6196` + // Minimum execution time: 139_133_000 picoseconds. + Weight::from_parts(141_507_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `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: `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 transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `496` + // Estimated: `6208` + // Minimum execution time: 144_241_000 picoseconds. + Weight::from_parts(149_709_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().writes(7)) } fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_440_000 picoseconds. - Weight::from_parts(7_651_000, 0) + // Minimum execution time: 10_392_000 picoseconds. + Weight::from_parts(10_779_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) @@ -106,8 +168,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_253_000 picoseconds. - Weight::from_parts(7_584_000, 0) + // Minimum execution time: 7_088_000 picoseconds. + Weight::from_parts(7_257_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,8 +179,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_299_000 picoseconds. - Weight::from_parts(2_435_000, 0) + // Minimum execution time: 2_095_000 picoseconds. + Weight::from_parts(2_136_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -144,8 +206,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_440_000 picoseconds. - Weight::from_parts(30_675_000, 0) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_349_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -170,8 +232,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_876_000 picoseconds. - Weight::from_parts(32_588_000, 0) + // Minimum execution time: 30_605_000 picoseconds. + Weight::from_parts(31_477_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -182,8 +244,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_385_000 picoseconds. - Weight::from_parts(2_607_000, 0) + // Minimum execution time: 2_137_000 picoseconds. + Weight::from_parts(2_303_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -193,8 +255,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_927_000 picoseconds. - Weight::from_parts(17_554_000, 0) + // Minimum execution time: 16_719_000 picoseconds. + Weight::from_parts(17_329_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -205,8 +267,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 16_965_000 picoseconds. - Weight::from_parts(17_807_000, 0) + // Minimum execution time: 16_687_000 picoseconds. + Weight::from_parts(17_405_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -217,8 +279,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_763_000 picoseconds. - Weight::from_parts(19_359_000, 0) + // Minimum execution time: 18_751_000 picoseconds. + Weight::from_parts(19_130_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -240,8 +302,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_371_000 picoseconds. - Weight::from_parts(28_185_000, 0) + // Minimum execution time: 27_189_000 picoseconds. + Weight::from_parts(27_760_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -252,8 +314,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_165_000 picoseconds. - Weight::from_parts(9_539_000, 0) + // Minimum execution time: 9_307_000 picoseconds. + Weight::from_parts(9_691_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -263,8 +325,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 17_384_000 picoseconds. - Weight::from_parts(17_777_000, 0) + // Minimum execution time: 17_607_000 picoseconds. + Weight::from_parts(18_090_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -287,8 +349,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 34_260_000 picoseconds. - Weight::from_parts(35_428_000, 0) + // Minimum execution time: 34_322_000 picoseconds. + Weight::from_parts(35_754_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -301,8 +363,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_710_000 picoseconds. - Weight::from_parts(4_900_000, 0) + // Minimum execution time: 4_513_000 picoseconds. + Weight::from_parts(4_754_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -313,8 +375,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_843_000 picoseconds. - Weight::from_parts(27_404_000, 0) + // Minimum execution time: 27_860_000 picoseconds. + Weight::from_parts(28_279_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs index 9d0d0cbc655586937893e4d017475175b2fca63e..84d717b0283c764cac14cce63ca34f81c9f58e8c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! 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: `[]` +//! DATE: 2023-12-12, 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` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,8 +48,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm_bridge_hub_router`. pub struct WeightInfo(PhantomData); impl pallet_xcm_bridge_hub_router::WeightInfo for WeightInfo { - /// Storage: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) - /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) @@ -58,22 +58,22 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `193` // Estimated: `1678` - // Minimum execution time: 8_157_000 picoseconds. - Weight::from_parts(8_481_000, 0) + // Minimum execution time: 8_095_000 picoseconds. + Weight::from_parts(8_393_000, 0) .saturating_add(Weight::from_parts(0, 1678)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) - /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn on_initialize_when_congested() -> Weight { // Proof Size summary in bytes: // Measured: `111` // Estimated: `1596` - // Minimum execution time: 3_319_000 picoseconds. - Weight::from_parts(3_445_000, 0) + // Minimum execution time: 3_417_000 picoseconds. + Weight::from_parts(3_583_000, 0) .saturating_add(Weight::from_parts(0, 1596)) .saturating_add(T::DbWeight::get().reads(2)) } @@ -83,22 +83,24 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `117` // Estimated: `1502` - // Minimum execution time: 10_396_000 picoseconds. - Weight::from_parts(10_914_000, 0) + // Minimum execution time: 10_280_000 picoseconds. + Weight::from_parts(10_703_000, 0) .saturating_add(Weight::from_parts(0, 1502)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::SupportedVersion` (r:2 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0x3302afcb67e838a3f960251b417b9a4f` (r:1 w:0) + /// Proof: UNKNOWN KEY `0x3302afcb67e838a3f960251b417b9a4f` (r:1 w:0) /// Storage: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) /// Proof: UNKNOWN KEY `0x0973fe64c85043ba1c965cbc38eb63c7` (r:1 w:0) /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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) @@ -107,18 +109,18 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `XcmpQueue::InboundXcmpStatus` (r:1 w:0) - /// Proof: `XcmpQueue::InboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send_message() -> Weight { // Proof Size summary in bytes: - // Measured: `426` - // Estimated: `3891` - // Minimum execution time: 45_902_000 picoseconds. - Weight::from_parts(46_887_000, 0) - .saturating_add(Weight::from_parts(0, 3891)) - .saturating_add(T::DbWeight::get().reads(10)) + // Measured: `487` + // Estimated: `6427` + // Minimum execution time: 63_624_000 picoseconds. + Weight::from_parts(66_071_000, 0) + .saturating_add(Weight::from_parts(0, 6427)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } } 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 4760e087e24fc1b17467f4035a2efa808aa0d0d1..cb2fedeb146f8724bd5e51ae75ca50592f598092 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 @@ -32,33 +32,30 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AssetFeeAsExistentialDepositMultiplier, ConcreteAssetFromSystem, - RelayOrOtherSystemParachains, + AllSiblingSystemParachains, AssetFeeAsExistentialDepositMultiplier, + ConcreteAssetFromSystem, RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; -use westend_runtime_constants::system_parachain; use xcm::latest::prelude::*; +#[allow(deprecated)] +use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, - EnsureXcmOrigin, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, - IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, - StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, + TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, + WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; -#[cfg(feature = "runtime-benchmarks")] -use {cumulus_primitives_core::ParaId, sp_core::Get}; - parameter_types! { pub const WestendLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: Option = Some(NetworkId::Westend); @@ -66,8 +63,8 @@ parameter_types! { pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); - pub TrustBackedAssetsPalletLocation: MultiLocation = - PalletInstance(::index() as u8).into(); + pub AssetsPalletIndex: u32 = ::index() as u32; + pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(AssetsPalletIndex::get() as u8).into(); pub ForeignAssetsPalletLocation: MultiLocation = PalletInstance(::index() as u8).into(); pub PoolAssetsPalletLocation: MultiLocation = @@ -96,6 +93,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -240,6 +238,18 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; + pub type FellowshipEntities: impl Contains = { + // Fellowship Plurality + MultiLocation { parents: 1, interior: X2(Parachain(1001), Plurality { id: BodyId::Technical, ..}) } | + // Fellowship Salary Pallet + MultiLocation { parents: 1, interior: X2(Parachain(1001), PalletInstance(64)) } | + // Fellowship Treasury Pallet + MultiLocation { parents: 1, interior: X2(Parachain(1001), PalletInstance(65)) } + }; + pub type AmbassadorEntities: impl Contains = { + // Ambassador Salary Pallet + MultiLocation { parents: 1, interior: X2(Parachain(1001), PalletInstance(74)) } + }; } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -279,19 +289,14 @@ impl Contains for SafeCallFilter { 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 { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::XcmpQueue(..) | RuntimeCall::MessageQueue(..) | RuntimeCall::Assets( @@ -488,6 +493,8 @@ pub type Barrier = TrailingSetTopicAsId< ParentOrParentsPlurality, Equals, Equals, + FellowshipEntities, + AmbassadorEntities, )>, // Subscriptions for version tracking are OK. AllowSubscriptionsFrom, @@ -520,24 +527,15 @@ pub type ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger = ForeignAssetsInstance, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::COLLECTIVES_ID | - system_parachain::BRIDGE_HUB_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, + FellowshipEntities, + AmbassadorEntities, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// @@ -688,34 +686,6 @@ impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { } } -#[cfg(feature = "runtime-benchmarks")] -pub struct BenchmarkMultiLocationConverter { - _phantom: sp_std::marker::PhantomData, -} - -#[cfg(feature = "runtime-benchmarks")] -impl - pallet_asset_conversion::BenchmarkHelper> - for BenchmarkMultiLocationConverter -where - SelfParaId: Get, -{ - fn asset_id(asset_id: u32) -> MultiLocation { - MultiLocation { - parents: 1, - interior: X3( - Parachain(SelfParaId::get().into()), - PalletInstance(::index() as u8), - GeneralIndex(asset_id.into()), - ), - } - } - - fn multiasset_id(asset_id: u32) -> sp_std::boxed::Box { - sp_std::boxed::Box::new(Self::asset_id(asset_id)) - } -} - /// All configuration related to bridging pub mod bridging { use super::*; 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 7922b04e8077b229eda981f9d107dca0289380c1..0aaf1d91879aecfc78af5ed75c0220c29933f039 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -24,9 +24,9 @@ use asset_hub_westend_runtime::{ WestendLocation, XcmConfig, }, AllPalletsWithoutSystem, AssetDeposit, Assets, Balances, ExistentialDeposit, ForeignAssets, - ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, - RuntimeCall, RuntimeEvent, SessionKeys, ToRococoXcmRouterInstance, TrustBackedAssetsInstance, - XcmpQueue, + ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, + PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, ExtBuilder, @@ -635,6 +635,12 @@ asset_test_utils::include_create_and_manage_foreign_assets_for_local_consensus_p ); fn bridging_to_asset_hub_rococo() -> TestBridgingConfig { + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridging::to_rococo::AssetHubRococo::get()), + XCM_VERSION, + ) + .expect("version saved!"); TestBridgingConfig { bridged_network: bridging::to_rococo::RococoNetwork::get(), local_bridge_hub_para_id: bridging::SiblingBridgeHubParaId::get(), diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index e78d8331039ccc983b1460f9225fe6fb5e11da8d..22729df5ed5ca845d7a554758e3f9e3b7db8aaec 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Assets common utilities" license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/assets/common/src/benchmarks.rs b/cumulus/parachains/runtimes/assets/common/src/benchmarks.rs new file mode 100644 index 0000000000000000000000000000000000000000..344cb5ca336840046d0de80ae203ba34bede7cc4 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/common/src/benchmarks.rs @@ -0,0 +1,44 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cumulus_primitives_core::ParaId; +use sp_runtime::traits::Get; +use sp_std::marker::PhantomData; +use xcm::latest::prelude::*; + +/// Creates asset pairs for liquidity pools with `Target` always being the first asset. +pub struct AssetPairFactory( + PhantomData<(Target, SelfParaId, PalletId)>, +); +impl, SelfParaId: Get, PalletId: Get> + pallet_asset_conversion::BenchmarkHelper + for AssetPairFactory +{ + fn create_pair(seed1: u32, seed2: u32) -> (MultiLocation, MultiLocation) { + let with_id = MultiLocation::new( + 1, + X3( + Parachain(SelfParaId::get().into()), + PalletInstance(PalletId::get() as u8), + GeneralIndex(seed2.into()), + ), + ); + if seed1 % 2 == 0 { + (with_id, Target::get()) + } else { + (Target::get(), with_id) + } + } +} diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index f45c3289aab49c16e2b435671d3625f78692f07d..15327f51b2a994fb41e12a72c6e848a2d5139178 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -15,6 +15,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarks; pub mod foreign_creators; pub mod fungible_conversion; pub mod local_and_foreign_assets; diff --git a/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs b/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs index 9f429016f5302a28a61753331126a48f77ee7565..7dd497797eaa54e819f4991979c1c126df9baac3 100644 --- a/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs +++ b/cumulus/parachains/runtimes/assets/common/src/local_and_foreign_assets.rs @@ -13,49 +13,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -use frame_support::traits::{ - fungibles::{Balanced, Create, HandleImbalanceDrop, Inspect, Mutate, Unbalanced}, - tokens::{ - DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence, - }, - AccountTouch, Contains, ContainsPair, Get, PalletInfoAccess, +use frame_support::traits::Get; +use sp_runtime::{ + traits::{Convert, MaybeEquivalence}, + Either, + Either::{Left, Right}, }; -use pallet_asset_conversion::{MultiAssetIdConversionResult, MultiAssetIdConverter}; -use parachains_common::AccountId; -use sp_runtime::{traits::MaybeEquivalence, DispatchError, DispatchResult}; -use sp_std::{boxed::Box, marker::PhantomData}; +use sp_std::marker::PhantomData; use xcm::latest::MultiLocation; -pub struct MultiLocationConverter, MultiLocationMatcher> { - _phantom: PhantomData<(NativeAssetLocation, MultiLocationMatcher)>, +/// Converts a given [`MultiLocation`] to [`Either::Left`] when equal to `Target`, or +/// [`Either::Right`] otherwise. +/// +/// Suitable for use as a `Criterion` with [`frame_support::traits::tokens::fungible::UnionOf`]. +pub struct TargetFromLeft(PhantomData); +impl> Convert> + for TargetFromLeft +{ + fn convert(l: MultiLocation) -> Either<(), MultiLocation> { + Target::get().eq(&l).then(|| Left(())).map_or(Right(l), |n| n) + } } -impl - MultiAssetIdConverter, MultiLocation> - for MultiLocationConverter +/// Converts a given [`MultiLocation`] to [`Either::Left`] based on the `Equivalence` criteria. +/// Returns [`Either::Right`] if not equivalent. +/// +/// Suitable for use as a `Criterion` with [`frame_support::traits::tokens::fungibles::UnionOf`]. +pub struct LocalFromLeft(PhantomData<(Equivalence, AssetId)>); +impl Convert> + for LocalFromLeft where - NativeAssetLocation: Get, - MultiLocationMatcher: Contains, + Equivalence: MaybeEquivalence, { - fn get_native() -> Box { - Box::new(NativeAssetLocation::get()) - } - - fn is_native(asset_id: &Box) -> bool { - *asset_id == Self::get_native() - } - - fn try_convert( - asset_id: &Box, - ) -> MultiAssetIdConversionResult, MultiLocation> { - if Self::is_native(&asset_id) { - return MultiAssetIdConversionResult::Native - } - - if MultiLocationMatcher::contains(&asset_id) { - MultiAssetIdConversionResult::Converted(*asset_id.clone()) - } else { - MultiAssetIdConversionResult::Unsupported(asset_id.clone()) + fn convert(l: MultiLocation) -> Either { + match Equivalence::convert(&l) { + Some(id) => Left(id), + None => Right(l), } } } @@ -64,407 +57,3 @@ pub trait MatchesLocalAndForeignAssetsMultiLocation { fn is_local(location: &MultiLocation) -> bool; fn is_foreign(location: &MultiLocation) -> bool; } - -pub struct LocalAndForeignAssets { - _phantom: PhantomData<(Assets, LocalAssetIdConverter, ForeignAssets)>, -} - -impl Unbalanced - for LocalAndForeignAssets -where - Assets: Inspect - + Unbalanced - + Balanced - + PalletInfoAccess, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: Inspect - + Unbalanced - + Balanced, -{ - fn handle_dust(dust: frame_support::traits::fungibles::Dust) { - let credit = dust.into_credit(); - - if let Some(asset) = LocalAssetIdConverter::convert(&credit.asset()) { - Assets::handle_raw_dust(asset, credit.peek()); - } else { - ForeignAssets::handle_raw_dust(credit.asset(), credit.peek()); - } - - // As we have already handled the dust, we must stop credit's drop from happening: - sp_std::mem::forget(credit); - } - - fn write_balance( - asset: >::AssetId, - who: &AccountId, - amount: >::Balance, - ) -> Result>::Balance>, DispatchError> { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::write_balance(asset, who, amount) - } else { - ForeignAssets::write_balance(asset, who, amount) - } - } - - /// Set the total issuance of `asset` to `amount`. - fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::set_total_issuance(asset, amount) - } else { - ForeignAssets::set_total_issuance(asset, amount) - } - } - - fn decrease_balance( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - precision: Precision, - preservation: Preservation, - force: Fortitude, - ) -> Result { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::decrease_balance(asset, who, amount, precision, preservation, force) - } else { - ForeignAssets::decrease_balance(asset, who, amount, precision, preservation, force) - } - } - - fn increase_balance( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - precision: Precision, - ) -> Result { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::increase_balance(asset, who, amount, precision) - } else { - ForeignAssets::increase_balance(asset, who, amount, precision) - } - } -} - -impl Inspect - for LocalAndForeignAssets -where - Assets: Inspect, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: Inspect, -{ - type AssetId = MultiLocation; - type Balance = u128; - - /// The total amount of issuance in the system. - fn total_issuance(asset: Self::AssetId) -> Self::Balance { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::total_issuance(asset) - } else { - ForeignAssets::total_issuance(asset) - } - } - - /// The minimum balance any single account may have. - fn minimum_balance(asset: Self::AssetId) -> Self::Balance { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::minimum_balance(asset) - } else { - ForeignAssets::minimum_balance(asset) - } - } - - fn total_balance( - asset: >::AssetId, - account: &AccountId, - ) -> >::Balance { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::total_balance(asset, account) - } else { - ForeignAssets::total_balance(asset, account) - } - } - - /// Get the `asset` balance of `who`. - fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::balance(asset, who) - } else { - ForeignAssets::balance(asset, who) - } - } - - /// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully. - fn reducible_balance( - asset: Self::AssetId, - who: &AccountId, - presevation: Preservation, - fortitude: Fortitude, - ) -> Self::Balance { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::reducible_balance(asset, who, presevation, fortitude) - } else { - ForeignAssets::reducible_balance(asset, who, presevation, fortitude) - } - } - - /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. - /// - /// - `asset`: The asset that should be deposited. - /// - `who`: The account of which the balance should be increased by `amount`. - /// - `amount`: How much should the balance be increased? - /// - `mint`: Will `amount` be minted to deposit it into `account`? - fn can_deposit( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - mint: Provenance, - ) -> DepositConsequence { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::can_deposit(asset, who, amount, mint) - } else { - ForeignAssets::can_deposit(asset, who, amount, mint) - } - } - - /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise - /// the consequence. - fn can_withdraw( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::can_withdraw(asset, who, amount) - } else { - ForeignAssets::can_withdraw(asset, who, amount) - } - } - - /// Returns `true` if an `asset` exists. - fn asset_exists(asset: Self::AssetId) -> bool { - if let Some(asset) = LocalAssetIdConverter::convert(&asset) { - Assets::asset_exists(asset) - } else { - ForeignAssets::asset_exists(asset) - } - } -} - -impl Mutate - for LocalAndForeignAssets -where - Assets: Mutate - + Inspect - + Balanced - + PalletInfoAccess, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: Mutate - + Inspect - + Balanced, -{ - /// Transfer funds from one account into another. - fn transfer( - asset: MultiLocation, - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - keep_alive: Preservation, - ) -> Result { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset) { - Assets::transfer(asset_id, source, dest, amount, keep_alive) - } else { - ForeignAssets::transfer(asset, source, dest, amount, keep_alive) - } - } -} - -impl Create - for LocalAndForeignAssets -where - Assets: Create + Inspect, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: Create + Inspect, -{ - /// Create a new fungible asset. - fn create( - asset_id: Self::AssetId, - admin: AccountId, - is_sufficient: bool, - min_balance: Self::Balance, - ) -> DispatchResult { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset_id) { - Assets::create(asset_id, admin, is_sufficient, min_balance) - } else { - ForeignAssets::create(asset_id, admin, is_sufficient, min_balance) - } - } -} - -impl AccountTouch - for LocalAndForeignAssets -where - Assets: AccountTouch, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: AccountTouch, -{ - type Balance = u128; - - fn deposit_required( - asset_id: MultiLocation, - ) -> >::Balance { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset_id) { - Assets::deposit_required(asset_id) - } else { - ForeignAssets::deposit_required(asset_id) - } - } - - fn touch( - asset_id: MultiLocation, - who: AccountId, - depositor: AccountId, - ) -> Result<(), DispatchError> { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset_id) { - Assets::touch(asset_id, who, depositor) - } else { - ForeignAssets::touch(asset_id, who, depositor) - } - } -} - -/// Implements [`ContainsPair`] trait for a pair of asset and account IDs. -impl ContainsPair - for LocalAndForeignAssets -where - Assets: PalletInfoAccess + ContainsPair, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: ContainsPair, -{ - /// Check if an account with the given asset ID and account address exists. - fn contains(asset_id: &MultiLocation, who: &AccountId) -> bool { - if let Some(asset_id) = LocalAssetIdConverter::convert(asset_id) { - Assets::contains(&asset_id, &who) - } else { - ForeignAssets::contains(&asset_id, &who) - } - } -} - -impl Balanced - for LocalAndForeignAssets -where - Assets: - Balanced + Inspect + PalletInfoAccess, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: - Balanced + Inspect, -{ - type OnDropDebt = DebtDropIndirection; - type OnDropCredit = CreditDropIndirection; -} - -pub struct DebtDropIndirection { - _phantom: PhantomData>, -} - -impl HandleImbalanceDrop - for DebtDropIndirection -where - Assets: Balanced + Inspect, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: - Balanced + Inspect, -{ - fn handle(asset: MultiLocation, amount: u128) { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset) { - Assets::OnDropDebt::handle(asset_id, amount); - } else { - ForeignAssets::OnDropDebt::handle(asset, amount); - } - } -} - -pub struct CreditDropIndirection { - _phantom: PhantomData>, -} - -impl HandleImbalanceDrop - for CreditDropIndirection -where - Assets: Balanced + Inspect, - LocalAssetIdConverter: MaybeEquivalence, - ForeignAssets: - Balanced + Inspect, -{ - fn handle(asset: MultiLocation, amount: u128) { - if let Some(asset_id) = LocalAssetIdConverter::convert(&asset) { - Assets::OnDropCredit::handle(asset_id, amount); - } else { - ForeignAssets::OnDropCredit::handle(asset, amount); - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - local_and_foreign_assets::MultiLocationConverter, AssetIdForPoolAssetsConvert, - AssetIdForTrustBackedAssetsConvert, - }; - use frame_support::traits::EverythingBut; - use pallet_asset_conversion::{MultiAssetIdConversionResult, MultiAssetIdConverter}; - use sp_runtime::traits::MaybeEquivalence; - use xcm::latest::prelude::*; - use xcm_builder::StartsWith; - - #[test] - fn test_multi_location_converter_works() { - frame_support::parameter_types! { - pub const WestendLocation: MultiLocation = MultiLocation::parent(); - pub TrustBackedAssetsPalletLocation: MultiLocation = PalletInstance(50_u8).into(); - pub PoolAssetsPalletLocation: MultiLocation = PalletInstance(55_u8).into(); - } - - type C = MultiLocationConverter< - WestendLocation, - EverythingBut>, - >; - - let native_asset = WestendLocation::get(); - let local_asset = - AssetIdForTrustBackedAssetsConvert::::convert_back( - &123, - ) - .unwrap(); - let pool_asset = - AssetIdForPoolAssetsConvert::::convert_back(&456).unwrap(); - let foreign_asset1 = MultiLocation { parents: 1, interior: X1(Parachain(2222)) }; - let foreign_asset2 = MultiLocation { - parents: 2, - interior: X2(GlobalConsensus(ByGenesis([1; 32])), Parachain(2222)), - }; - - assert!(C::is_native(&Box::new(native_asset))); - assert!(!C::is_native(&Box::new(local_asset))); - assert!(!C::is_native(&Box::new(pool_asset))); - assert!(!C::is_native(&Box::new(foreign_asset1))); - assert!(!C::is_native(&Box::new(foreign_asset2))); - - assert_eq!(C::try_convert(&Box::new(native_asset)), MultiAssetIdConversionResult::Native); - assert_eq!( - C::try_convert(&Box::new(local_asset)), - MultiAssetIdConversionResult::Converted(local_asset) - ); - assert_eq!( - C::try_convert(&Box::new(pool_asset)), - MultiAssetIdConversionResult::Unsupported(Box::new(pool_asset)) - ); - assert_eq!( - C::try_convert(&Box::new(foreign_asset1)), - MultiAssetIdConversionResult::Converted(foreign_asset1) - ); - assert_eq!( - C::try_convert(&Box::new(foreign_asset2)), - MultiAssetIdConversionResult::Converted(foreign_asset2) - ); - } -} diff --git a/cumulus/parachains/runtimes/assets/common/src/matching.rs b/cumulus/parachains/runtimes/assets/common/src/matching.rs index 9149728125212c9c3c980b115aa7d142905baf2a..d6ecc3ec99f043f7d174c491a6a3d0d98b01e60c 100644 --- a/cumulus/parachains/runtimes/assets/common/src/matching.rs +++ b/cumulus/parachains/runtimes/assets/common/src/matching.rs @@ -58,6 +58,37 @@ impl> ContainsPair } } +/// Checks if `a` is from the expected global consensus network. Checks that `MultiLocation-a` +/// starts with `MultiLocation-b`, and that network is a foreign consensus system. +pub struct FromNetwork( + sp_std::marker::PhantomData<(UniversalLocation, ExpectedNetworkId)>, +); +impl, ExpectedNetworkId: Get> + ContainsPair for FromNetwork +{ + fn contains(&a: &MultiLocation, b: &MultiLocation) -> bool { + // `a` needs to be from `b` at least + if !a.starts_with(b) { + return false + } + + let universal_source = UniversalLocation::get(); + + // ensure that `a`` is remote and from the expected network + match ensure_is_remote(universal_source, a) { + Ok((network_id, _)) => network_id == ExpectedNetworkId::get(), + Err(e) => { + log::trace!( + target: "xcm::contains", + "FromNetwork origin: {:?} is not remote to the universal_source: {:?} {:?}", + a, universal_source, e + ); + false + }, + } + } +} + /// Adapter verifies if it is allowed to receive `MultiAsset` from `MultiLocation`. /// /// Note: `MultiLocation` has to be from a different global consensus. @@ -95,3 +126,92 @@ impl< Reserves::contains(asset, origin) } } + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::parameter_types; + + parameter_types! { + pub UniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(1000)); + pub ExpectedNetworkId: NetworkId = Wococo; + } + + #[test] + fn from_network_contains_works() { + // asset and origin from foreign consensus works + let asset: MultiLocation = ( + Parent, + Parent, + GlobalConsensus(Wococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: MultiLocation = + (Parent, Parent, GlobalConsensus(Wococo), Parachain(1000)).into(); + assert!(FromNetwork::::contains(&asset, &origin)); + + // asset and origin from local consensus fails + let asset: MultiLocation = ( + Parent, + Parent, + GlobalConsensus(Rococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: MultiLocation = + (Parent, Parent, GlobalConsensus(Rococo), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset and origin from here fails + let asset: MultiLocation = (PalletInstance(1), GeneralIndex(1)).into(); + let origin: MultiLocation = Here.into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset from different consensus fails + let asset: MultiLocation = ( + Parent, + Parent, + GlobalConsensus(Polkadot), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: MultiLocation = + (Parent, Parent, GlobalConsensus(Wococo), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // origin from different consensus fails + let asset: MultiLocation = ( + Parent, + Parent, + GlobalConsensus(Wococo), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: MultiLocation = + (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + + // asset and origin from unexpected consensus fails + let asset: MultiLocation = ( + Parent, + Parent, + GlobalConsensus(Polkadot), + Parachain(1000), + PalletInstance(1), + GeneralIndex(1), + ) + .into(); + let origin: MultiLocation = + (Parent, Parent, GlobalConsensus(Polkadot), Parachain(1000)).into(); + assert!(!FromNetwork::::contains(&asset, &origin)); + } +} diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index c8ea4490d10a4e969d122a1e26b7151ee9c70609..a3ed37596002987d68f7c59310f3abc6e9e0ee63 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Test utils for Asset Hub runtimes." license = "Apache-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } 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 08758aaf7bbb09394ad7860a6d72f293dbd4e3c9..6bb20b5ee3514cf846259724be04bada6297f198 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Rococo's BridgeHub parachain runtime" license = "Apache-2.0" +[lints] +workspace = true + [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } @@ -18,7 +21,7 @@ log = { version = "0.4.20", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -serde = { version = "1.0.193", optional = true, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } smallvec = "1.11.0" # Substrate @@ -84,11 +87,13 @@ parachains-common = { path = "../../../common", default-features = false } # Bridges bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hub-rococo", default-features = false } bp-asset-hub-westend = { path = "../../../../../bridges/primitives/chain-asset-hub-westend", default-features = false } +bp-bridge-hub-polkadot = { path = "../../../../../bridges/primitives/chain-bridge-hub-polkadot", 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 } bp-header-chain = { path = "../../../../../bridges/primitives/header-chain", default-features = false } bp-messages = { path = "../../../../../bridges/primitives/messages", default-features = false } bp-parachains = { path = "../../../../../bridges/primitives/parachains", default-features = false } +bp-polkadot-bulletin = { path = "../../../../../bridges/primitives/chain-polkadot-bulletin", default-features = false } bp-polkadot-core = { path = "../../../../../bridges/primitives/polkadot-core", default-features = false } bp-relayers = { path = "../../../../../bridges/primitives/relayers", default-features = false } bp-runtime = { path = "../../../../../bridges/primitives/runtime", default-features = false } @@ -98,8 +103,24 @@ pallet-bridge-grandpa = { path = "../../../../../bridges/modules/grandpa", defau pallet-bridge-messages = { path = "../../../../../bridges/modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../../../../bridges/modules/parachains", default-features = false } pallet-bridge-relayers = { path = "../../../../../bridges/modules/relayers", default-features = false } +pallet-xcm-bridge-hub = { path = "../../../../../bridges/modules/xcm-bridge-hub", default-features = false } 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-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-ethereum-beacon-client = { path = "../../../../../bridges/snowbridge/parachain/pallets/ethereum-beacon-client", default-features = false } +snowbridge-inbound-queue = { path = "../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } +snowbridge-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-rococo-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/rococo-common", default-features = false } + +bridge-hub-common = { path = "../common", default-features = false } + [dev-dependencies] static_assertions = "1.1" bridge-hub-test-utils = { path = "../test-utils" } @@ -109,20 +130,23 @@ bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", fe sp-keyring = { path = "../../../../../substrate/primitives/keyring" } [features] -default = ["std"] +default = ["beacon-spec-mainnet", "std"] std = [ "bp-asset-hub-rococo/std", "bp-asset-hub-westend/std", + "bp-bridge-hub-polkadot/std", "bp-bridge-hub-rococo/std", "bp-bridge-hub-westend/std", "bp-header-chain/std", "bp-messages/std", "bp-parachains/std", + "bp-polkadot-bulletin/std", "bp-polkadot-core/std", "bp-relayers/std", "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", "cumulus-pallet-aura-ext/std", @@ -156,6 +180,7 @@ std = [ "pallet-transaction-payment/std", "pallet-utility/std", "pallet-xcm-benchmarks?/std", + "pallet-xcm-bridge-hub/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", @@ -165,6 +190,17 @@ std = [ "rococo-runtime-constants/std", "scale-info/std", "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-ethereum-beacon-client/std", + "snowbridge-inbound-queue/std", + "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-outbound-queue/std", + "snowbridge-rococo-common/std", + "snowbridge-router-primitives/std", + "snowbridge-runtime-common/std", + "snowbridge-system-runtime-api/std", + "snowbridge-system/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", @@ -180,12 +216,15 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "substrate-wasm-builder", "xcm-builder/std", "xcm-executor/std", "xcm/std", ] runtime-benchmarks = [ + "beacon-spec-mainnet", + "bridge-hub-common/runtime-benchmarks", "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", @@ -207,10 +246,19 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm-bridge-hub/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-ethereum-beacon-client/runtime-benchmarks", + "snowbridge-inbound-queue/runtime-benchmarks", + "snowbridge-outbound-queue/runtime-benchmarks", + "snowbridge-rococo-common/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "snowbridge-runtime-common/runtime-benchmarks", + "snowbridge-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", @@ -239,13 +287,21 @@ try-runtime = [ "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", + "pallet-xcm-bridge-hub/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", + "snowbridge-ethereum-beacon-client/try-runtime", + "snowbridge-inbound-queue/try-runtime", + "snowbridge-outbound-queue/try-runtime", + "snowbridge-system/try-runtime", "sp-runtime/try-runtime", ] experimental = ["pallet-aura/experimental"] +beacon-spec-mainnet = [ + "snowbridge-ethereum-beacon-client/beacon-spec-mainnet", +] # A feature that should be enabled when the runtime should be built for on-chain # deployment. This will disable stuff that shouldn't be part of the on-chain wasm diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs index 8153e52beacc359705542a0b3e03952c975c82e0..93ef9470363cd3dd41a92fe529226ad3fd7b2e00 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_common_config.rs @@ -21,15 +21,20 @@ //! For example, the messaging pallet needs to know the sending and receiving chains, but the //! GRANDPA tracking pallet only needs to be aware of one chain. -use super::{weights, AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent}; +use super::{ + weights, AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent, RuntimeOrigin, +}; use bp_parachains::SingleParaStoredHeaderDataBuilder; +use bp_runtime::UnderlyingChainProvider; +use bridge_runtime_common::messages::ThisChainWithMessages; use frame_support::{parameter_types, traits::ConstU32}; +use sp_runtime::RuntimeDebug; parameter_types! { pub const RelayChainHeadersToKeep: u32 = 1024; pub const ParachainHeadsToKeep: u32 = 64; - pub const WestendBridgeParachainPalletName: &'static str = "Paras"; + pub const WestendBridgeParachainPalletName: &'static str = bp_westend::PARAS_PALLET_NAME; pub const MaxWestendParaHeadDataSize: u32 = bp_westend::MAX_NESTED_PARACHAIN_HEAD_DATA_SIZE; pub storage RequiredStakeForStakeAndSlash: Balance = 1_000_000; @@ -78,3 +83,33 @@ impl pallet_bridge_relayers::Config for Runtime { >; type WeightInfo = weights::pallet_bridge_relayers::WeightInfo; } + +/// Add GRANDPA bridge pallet to track Rococo Bulletin chain. +pub type BridgeGrandpaRococoBulletinInstance = pallet_bridge_grandpa::Instance4; +impl pallet_bridge_grandpa::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BridgedChain = bp_polkadot_bulletin::PolkadotBulletin; + type MaxFreeMandatoryHeadersPerBlock = ConstU32<4>; + type HeadersToKeep = RelayChainHeadersToKeep; + // Technically this is incorrect - we have two pallet instances and ideally we shall + // benchmark every instance separately. But the benchmarking engine has a flaw - it + // messes with components. E.g. in Kusama maximal validators count is 1024 and in + // Bulletin chain it is 100. But benchmarking engine runs Bulletin benchmarks using + // components range, computed for Kusama => it causes an error. + // + // In practice, however, GRANDPA pallet works the same way for all bridged chains, so + // weights are also the same for both bridges. + type WeightInfo = weights::pallet_bridge_grandpa::WeightInfo; +} + +/// BridgeHubRococo chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct BridgeHubRococo; + +impl UnderlyingChainProvider for BridgeHubRococo { + type Chain = bp_bridge_hub_rococo::BridgeHubRococo; +} + +impl ThisChainWithMessages for BridgeHubRococo { + type RuntimeOrigin = RuntimeOrigin; +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..c9d7f60e71a56d0f0ad7e1ce10cbec6d488d99a3 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -0,0 +1,292 @@ +// 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 . + +//! Bridge definitions used on BridgeHubRococo for bridging to Rococo Bulletin. +//! +//! Rococo Bulletin chain will be the 1:1 copy of the Polkadot Bulletin, so we +//! are reusing Polkadot Bulletin chain primitives everywhere here. + +use crate::{ + bridge_common_config::{BridgeGrandpaRococoBulletinInstance, BridgeHubRococo}, + weights, + xcm_config::UniversalLocation, + AccountId, BridgeRococoBulletinGrandpa, BridgeRococoBulletinMessages, PolkadotXcm, Runtime, + RuntimeEvent, XcmOverRococoBulletin, XcmRouter, +}; +use bp_messages::LaneId; +use bridge_runtime_common::{ + messages, + messages::{ + source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, + target::{FromBridgedChainMessagesProof, SourceHeaderChainAdapter}, + MessageBridge, UnderlyingChainProvider, + }, + messages_xcm_extension::{ + SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, + XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, + }, + refund_relayer_extension::{ + ActualFeeRefund, RefundBridgedGrandpaMessages, RefundSignedExtensionAdapter, + RefundableMessagesLane, + }, +}; + +use frame_support::{parameter_types, traits::PalletInfoAccess}; +use sp_runtime::RuntimeDebug; +use xcm::{ + latest::prelude::*, + prelude::{InteriorMultiLocation, NetworkId}, +}; +use xcm_builder::BridgeBlobDispatcher; + +parameter_types! { + /// Maximal number of entries in the unrewarded relayers vector at the Rococo Bridge Hub. It matches the + /// maximal number of unrewarded relayers that the single confirmation transaction at Rococo Bulletin Chain + /// may process. + pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = + bp_polkadot_bulletin::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX; + /// Maximal number of unconfirmed messages at the Rococo Bridge Hub. It matches the maximal number of + /// unconfirmed messages that the single confirmation transaction at Rococo Bulletin Chain may process. + pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = + bp_polkadot_bulletin::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; + /// Bridge specific chain (network) identifier of the Rococo Bulletin Chain. + pub const RococoBulletinChainId: bp_runtime::ChainId = bp_runtime::POLKADOT_BULLETIN_CHAIN_ID; + /// Interior location (relative to this runtime) of the with-RococoBulletin messages pallet. + pub BridgeRococoToRococoBulletinMessagesPalletInstance: InteriorMultiLocation = X1( + PalletInstance(::index() as u8) + ); + /// Rococo Bulletin Network identifier. + pub RococoBulletinGlobalConsensusNetwork: NetworkId = NetworkId::PolkadotBulletin; + /// Relative location of the Rococo Bulletin chain. + pub RococoBulletinGlobalConsensusNetworkLocation: MultiLocation = MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())) + }; + /// All active lanes that the current bridge supports. + pub ActiveOutboundLanesToRococoBulletin: &'static [bp_messages::LaneId] + = &[XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN]; + /// Lane identifier, used to connect Rococo People and Rococo Bulletin chain. + pub const RococoPeopleToRococoBulletinMessagesLane: bp_messages::LaneId + = XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN; + + /// Priority boost that the registered relayer receives for every additional message in the message + /// delivery transaction. + /// + /// It is determined semi-automatically - see `FEE_BOOST_PER_MESSAGE` constant to get the + /// meaning of this value. + pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; + + /// Identifier of the sibling Rococo People parachain. + pub RococoPeopleParaId: cumulus_primitives_core::ParaId = rococo_runtime_constants::system_parachain::PEOPLE_ID.into(); + /// A route (XCM location and bridge lane) that the Rococo People Chain -> Rococo Bulletin Chain + /// message is following. + pub FromRococoPeopleToRococoBulletinRoute: SenderAndLane = SenderAndLane::new( + ParentThen(X1(Parachain(RococoPeopleParaId::get().into()))).into(), + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + ); + /// All active routes and their destinations. + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorMultiLocation))> = sp_std::vec![ + ( + FromRococoPeopleToRococoBulletinRoute::get(), + (RococoBulletinGlobalConsensusNetwork::get(), Here) + ) + ]; + + /// XCM message that is never sent. + pub NeverSentMessage: Option> = None; +} +pub const XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN: LaneId = LaneId([0, 0, 0, 0]); + +/// Proof of messages, coming from Rococo Bulletin chain. +pub type FromRococoBulletinMessagesProof = + FromBridgedChainMessagesProof; +/// Messages delivery proof for Rococo Bridge Hub -> Rococo Bulletin messages. +pub type ToRococoBulletinMessagesDeliveryProof = + FromBridgedChainMessagesDeliveryProof; + +/// Dispatches received XCM messages from other bridge. +type FromRococoBulletinMessageBlobDispatcher = BridgeBlobDispatcher< + XcmRouter, + UniversalLocation, + BridgeRococoToRococoBulletinMessagesPalletInstance, +>; + +/// Export XCM messages to be relayed to the other side +pub type ToRococoBulletinHaulBlobExporter = XcmOverRococoBulletin; + +pub struct ToRococoBulletinXcmBlobHauler; +impl XcmBlobHauler for ToRococoBulletinXcmBlobHauler { + type Runtime = Runtime; + type MessagesInstance = WithRococoBulletinMessagesInstance; + type ToSourceChainSender = XcmRouter; + type CongestedMessage = NeverSentMessage; + type UncongestedMessage = NeverSentMessage; +} + +/// On messages delivered callback. +type OnMessagesDeliveredFromRococoBulletin = + XcmBlobHaulerAdapter; + +/// Messaging Bridge configuration for BridgeHubRococo -> Rococo Bulletin. +pub struct WithRococoBulletinMessageBridge; +impl MessageBridge for WithRococoBulletinMessageBridge { + // Bulletin chain assumes it is bridged with Polkadot Bridge Hub + const BRIDGED_MESSAGES_PALLET_NAME: &'static str = + bp_bridge_hub_polkadot::WITH_BRIDGE_HUB_POLKADOT_MESSAGES_PALLET_NAME; + type ThisChain = BridgeHubRococo; + type BridgedChain = RococoBulletin; + type BridgedHeaderChain = BridgeRococoBulletinGrandpa; +} + +/// Message verifier for RococoBulletin messages sent from BridgeHubRococo. +pub type ToRococoBulletinMessageVerifier = + messages::source::FromThisChainMessageVerifier; + +/// Maximal outbound payload size of BridgeHubRococo -> RococoBulletin messages. +pub type ToRococoBulletinMaximalOutboundPayloadSize = + messages::source::FromThisChainMaximalOutboundPayloadSize; + +/// RococoBulletin chain from message lane point of view. +#[derive(RuntimeDebug, Clone, Copy)] +pub struct RococoBulletin; + +impl UnderlyingChainProvider for RococoBulletin { + type Chain = bp_polkadot_bulletin::PolkadotBulletin; +} + +impl messages::BridgedChainWithMessages for RococoBulletin {} + +/// Signed extension that refunds relayers that are delivering messages from the Rococo Bulletin +/// chain. +pub type OnBridgeHubRococoRefundRococoBulletinMessages = RefundSignedExtensionAdapter< + RefundBridgedGrandpaMessages< + Runtime, + BridgeGrandpaRococoBulletinInstance, + RefundableMessagesLane< + WithRococoBulletinMessagesInstance, + RococoPeopleToRococoBulletinMessagesLane, + >, + ActualFeeRefund, + PriorityBoostPerMessage, + StrOnBridgeHubRococoRefundRococoBulletinMessages, + >, +>; +bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundRococoBulletinMessages); + +/// Add XCM messages support for BridgeHubRococo to support Rococo->Rococo Bulletin XCM messages. +pub type WithRococoBulletinMessagesInstance = pallet_bridge_messages::Instance4; +impl pallet_bridge_messages::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = + weights::pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo; + type BridgedChainId = RococoBulletinChainId; + type ActiveOutboundLanes = ActiveOutboundLanesToRococoBulletin; + type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; + type MaxUnconfirmedMessagesAtInboundLane = MaxUnconfirmedMessagesAtInboundLane; + + type MaximalOutboundPayloadSize = ToRococoBulletinMaximalOutboundPayloadSize; + type OutboundPayload = XcmAsPlainPayload; + + type InboundPayload = XcmAsPlainPayload; + type InboundRelayer = AccountId; + type DeliveryPayments = (); + + type TargetHeaderChain = TargetHeaderChainAdapter; + type LaneMessageVerifier = ToRococoBulletinMessageVerifier; + type DeliveryConfirmationPayments = (); + + type SourceHeaderChain = SourceHeaderChainAdapter; + type MessageDispatch = + XcmBlobMessageDispatch; + type OnMessagesDelivered = OnMessagesDeliveredFromRococoBulletin; +} + +/// Add support for the export and dispatch of XCM programs. +pub type XcmOverPolkadotBulletinInstance = pallet_xcm_bridge_hub::Instance2; +impl pallet_xcm_bridge_hub::Config for Runtime { + type UniversalLocation = UniversalLocation; + type BridgedNetwork = RococoBulletinGlobalConsensusNetworkLocation; + type BridgeMessagesPalletInstance = WithRococoBulletinMessagesInstance; + type MessageExportPrice = (); + type DestinationVersion = + XcmVersionOfDestAndRemoteBridge; + type Lanes = ActiveLanes; + type LanesSupport = ToRococoBulletinXcmBlobHauler; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::bridge_common_config::BridgeGrandpaRococoBulletinInstance; + use bridge_runtime_common::{ + assert_complete_bridge_types, integrity::check_message_lane_weights, + }; + use parachains_common::{rococo, Balance}; + + /// Every additional message in the message delivery transaction boosts its priority. + /// So the priority of transaction with `N+1` messages is larger than priority of + /// transaction with `N` messages by the `PriorityBoostPerMessage`. + /// + /// Economically, it is an equivalent of adding tip to the transaction with `N` messages. + /// The `FEE_BOOST_PER_MESSAGE` constant is the value of this tip. + /// + /// We want this tip to be large enough (delivery transactions with more messages = less + /// operational costs and a faster bridge), so this value should be significant. + const FEE_BOOST_PER_MESSAGE: Balance = 2 * rococo::currency::UNITS; + + #[test] + fn ensure_bridge_hub_rococo_message_lane_weights_are_correct() { + check_message_lane_weights::< + bp_bridge_hub_rococo::BridgeHubRococo, + Runtime, + WithRococoBulletinMessagesInstance, + >( + bp_polkadot_bulletin::EXTRA_STORAGE_PROOF_SIZE, + bp_bridge_hub_rococo::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX, + bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX, + true, + ); + } + + #[test] + fn ensure_bridge_integrity() { + assert_complete_bridge_types!( + runtime: Runtime, + with_bridged_chain_grandpa_instance: BridgeGrandpaRococoBulletinInstance, + with_bridged_chain_messages_instance: WithRococoBulletinMessagesInstance, + bridge: WithRococoBulletinMessageBridge, + this_chain: bp_rococo::Rococo, + bridged_chain: bp_polkadot_bulletin::PolkadotBulletin, + ); + + // we can't use `assert_complete_bridge_constants` here, because there's a trick with + // Bulletin chain - it has the same (almost) runtime for Polkadot Bulletin and Rococo + // Bulletin, so we have to adhere Polkadot names here + + bridge_runtime_common::priority_calculator::ensure_priority_boost_is_sane::< + Runtime, + WithRococoBulletinMessagesInstance, + PriorityBoostPerMessage, + >(FEE_BOOST_PER_MESSAGE); + + assert_eq!( + BridgeRococoToRococoBulletinMessagesPalletInstance::get(), + X1(PalletInstance( + bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_BULLETIN_MESSAGES_PALLET_INDEX + )) + ); + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..b0526148fa3a9da11e54ff676c3aeee141c623d4 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -0,0 +1,30 @@ +// 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 . + +use crate::{ + xcm_config::{AgentIdOf, UniversalLocation}, + Runtime, +}; +use snowbridge_rococo_common::EthereumNetwork; +use snowbridge_router_primitives::outbound::EthereumBlobExporter; + +/// Exports message to the Ethereum Gateway contract. +pub type SnowbridgeExporter = EthereumBlobExporter< + UniversalLocation, + EthereumNetwork, + snowbridge_outbound_queue::Pallet, + AgentIdOf, +>; 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 f3c1c9597b52194aa07bec1bda79664868b1a993..961b47e1e13b630a8c152caa5657b756f6b67844 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 @@ -14,11 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -//! Bridge definitions used on BridgeHub with the Rococo flavor for bridging to BridgeHubWestend. +//! Bridge definitions used on BridgeHubRococo for bridging to BridgeHubWestend. use crate::{ - bridge_common_config::{BridgeParachainWestendInstance, DeliveryRewardInBalance}, - weights, AccountId, BridgeWestendMessages, ParachainInfo, Runtime, RuntimeEvent, RuntimeOrigin, + bridge_common_config::{ + BridgeHubRococo, BridgeParachainWestendInstance, DeliveryRewardInBalance, + }, + weights, + xcm_config::UniversalLocation, + AccountId, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, XcmOverBridgeHubWestend, XcmRouter, }; use bp_messages::LaneId; @@ -27,11 +31,11 @@ use bridge_runtime_common::{ messages::{ source::{FromBridgedChainMessagesDeliveryProof, TargetHeaderChainAdapter}, target::{FromBridgedChainMessagesProof, SourceHeaderChainAdapter}, - MessageBridge, ThisChainWithMessages, UnderlyingChainProvider, + MessageBridge, UnderlyingChainProvider, }, messages_xcm_extension::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, - XcmBlobMessageDispatch, + XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, refund_relayer_extension::{ ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, @@ -46,7 +50,7 @@ use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, }; -use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; +use xcm_builder::BridgeBlobDispatcher; parameter_types! { pub const MaxUnrewardedRelayerEntriesAtInboundLane: bp_messages::MessageNonce = @@ -55,24 +59,41 @@ parameter_types! { bp_bridge_hub_rococo::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; pub const BridgeHubWestendChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_WESTEND_CHAIN_ID; pub BridgeRococoToWestendMessagesPalletInstance: InteriorMultiLocation = X1(PalletInstance(::index() as u8)); - pub BridgeHubRococoUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Rococo), Parachain(ParachainInfo::parachain_id().into())); pub WestendGlobalConsensusNetwork: NetworkId = NetworkId::Westend; - pub ActiveOutboundLanesToBridgeHubWestend: &'static [bp_messages::LaneId] = &[XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND]; - pub const AssetHubRococoToAssetHubWestendMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND; + pub WestendGlobalConsensusNetworkLocation: MultiLocation = MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(WestendGlobalConsensusNetwork::get())) + }; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); + // Lanes + pub ActiveOutboundLanesToBridgeHubWestend: &'static [bp_messages::LaneId] = &[XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND]; + pub const AssetHubRococoToAssetHubWestendMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND; pub FromAssetHubRococoToAssetHubWestendRoute: SenderAndLane = SenderAndLane::new( ParentThen(X1(Parachain(AssetHubRococoParaId::get().into()))).into(), XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, ); + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorMultiLocation))> = sp_std::vec![ + ( + FromAssetHubRococoToAssetHubWestendRoute::get(), + (WestendGlobalConsensusNetwork::get(), X1(Parachain(AssetHubWestendParaId::get().into()))) + ) + ]; pub CongestedMessage: Xcm<()> = build_congestion_message(true).into(); - pub UncongestedMessage: Xcm<()> = build_congestion_message(false).into(); + + pub BridgeHubWestendLocation: MultiLocation = MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(WestendGlobalConsensusNetwork::get()), + Parachain(::PARACHAIN_ID) + ) + }; } pub const XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND: LaneId = LaneId([0, 0, 0, 2]); @@ -103,31 +124,24 @@ pub type ToWestendBridgeHubMessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof; /// Dispatches received XCM messages from other bridge -type FromWestendMessageBlobDispatcher = BridgeBlobDispatcher< - XcmRouter, - BridgeHubRococoUniversalLocation, - BridgeRococoToWestendMessagesPalletInstance, ->; +type FromWestendMessageBlobDispatcher = + BridgeBlobDispatcher; /// Export XCM messages to be relayed to the other side -pub type ToBridgeHubWestendHaulBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, - WestendGlobalConsensusNetwork, - (), ->; +pub type ToBridgeHubWestendHaulBlobExporter = XcmOverBridgeHubWestend; + pub struct ToBridgeHubWestendXcmBlobHauler; impl XcmBlobHauler for ToBridgeHubWestendXcmBlobHauler { type Runtime = Runtime; type MessagesInstance = WithBridgeHubWestendMessagesInstance; - type SenderAndLane = FromAssetHubRococoToAssetHubWestendRoute; - type ToSourceChainSender = XcmRouter; type CongestedMessage = CongestedMessage; type UncongestedMessage = UncongestedMessage; } /// On messages delivered callback. -type OnMessagesDeliveredFromWestend = XcmBlobHaulerAdapter; +type OnMessagesDeliveredFromWestend = + XcmBlobHaulerAdapter; /// Messaging Bridge configuration for BridgeHubRococo -> BridgeHubWestend pub struct WithBridgeHubWestendMessageBridge; @@ -161,18 +175,6 @@ impl UnderlyingChainProvider for BridgeHubWestend { impl messages::BridgedChainWithMessages for BridgeHubWestend {} -/// BridgeHubRococo chain from message lane point of view. -#[derive(RuntimeDebug, Clone, Copy)] -pub struct BridgeHubRococo; - -impl UnderlyingChainProvider for BridgeHubRococo { - type Chain = bp_bridge_hub_rococo::BridgeHubRococo; -} - -impl ThisChainWithMessages for BridgeHubRococo { - type RuntimeOrigin = RuntimeOrigin; -} - /// Signed extension that refunds relayers that are delivering messages from the Westend parachain. pub type OnBridgeHubRococoRefundBridgeHubWestendMessages = RefundSignedExtensionAdapter< RefundBridgedParachainMessages< @@ -196,7 +198,7 @@ bp_runtime::generate_static_str_provider!(OnBridgeHubRococoRefundBridgeHubWesten pub type WithBridgeHubWestendMessagesInstance = pallet_bridge_messages::Instance3; impl pallet_bridge_messages::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type WeightInfo = weights::pallet_bridge_messages::WeightInfo; + type WeightInfo = weights::pallet_bridge_messages_rococo_to_westend::WeightInfo; type BridgedChainId = BridgeHubWestendChainId; type ActiveOutboundLanes = ActiveOutboundLanesToBridgeHubWestend; type MaxUnrewardedRelayerEntriesAtInboundLane = MaxUnrewardedRelayerEntriesAtInboundLane; @@ -229,6 +231,19 @@ impl pallet_bridge_messages::Config for Ru type OnMessagesDelivered = OnMessagesDeliveredFromWestend; } +/// Add support for the export and dispatch of XCM programs. +pub type XcmOverBridgeHubWestendInstance = pallet_xcm_bridge_hub::Instance1; +impl pallet_xcm_bridge_hub::Config for Runtime { + type UniversalLocation = UniversalLocation; + type BridgedNetwork = WestendGlobalConsensusNetworkLocation; + type BridgeMessagesPalletInstance = WithBridgeHubWestendMessagesInstance; + type MessageExportPrice = (); + type DestinationVersion = + XcmVersionOfDestAndRemoteBridge; + type Lanes = ActiveLanes; + type LanesSupport = ToBridgeHubWestendXcmBlobHauler; +} + #[cfg(test)] mod tests { use super::*; 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 01efb9d20cc3cbc0ba12c6872135a79678c78e50..b21cde248e1135e5c39cfdff11162864f7b4b6aa 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 @@ -18,6 +18,7 @@ //! //! This runtime currently supports bridging between: //! - Rococo <> Westend +//! - Rococo <> Rococo Bulletin #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. @@ -28,18 +29,25 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); pub mod bridge_common_config; +pub mod bridge_to_bulletin_config; +pub mod bridge_to_ethereum_config; pub mod bridge_to_westend_config; mod weights; pub mod xcm_config; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use snowbridge_beacon_primitives::{Fork, ForkVersions}; +use snowbridge_core::{ + gwei, meth, outbound::Message, AgentId, AllowSiblingsOnly, PricingParameters, Rewards, +}; +use snowbridge_router_primitives::inbound::MessageToXcm; use sp_api::impl_runtime_apis; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H160}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, - traits::Block as BlockT, + traits::{Block as BlockT, Keccak256}, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, FixedU128, }; use sp_std::prelude::*; @@ -47,7 +55,7 @@ use sp_std::prelude::*; use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use cumulus_primitives_core::ParaId; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -61,17 +69,25 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; -use xcm_config::{XcmOriginToTransactDispatchOrigin, XcmRouter}; use bp_runtime::HeaderId; +#[cfg(not(feature = "runtime-benchmarks"))] +use bridge_hub_common::BridgeHubMessageRouter; +use bridge_hub_common::{ + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + AggregateMessageOrigin, +}; +use pallet_xcm::EnsureXcm; +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +pub use sp_runtime::{MultiAddress, Perbill, Permill}; +use xcm::VersionedMultiLocation; +use xcm_config::{TreasuryAccount, XcmOriginToTransactDispatchOrigin, XcmRouter}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use rococo_runtime_constants::system_parachain::{ASSET_HUB_ID, BRIDGE_HUB_ID}; use xcm::latest::prelude::*; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -83,6 +99,19 @@ use parachains_common::{ HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, }; +#[cfg(feature = "runtime-benchmarks")] +use crate::xcm_config::benchmark_helpers::DoNothingRouter; +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_beacon_primitives::CompactExecutionHeader; +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_core::RingBufferMap; +#[cfg(feature = "runtime-benchmarks")] +pub use snowbridge_ethereum_beacon_client::ExecutionHeaderBuffer; +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_inbound_queue::BenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +use sp_core::H256; + /// The address format for describing accounts. pub type Address = MultiAddress; @@ -106,7 +135,10 @@ pub type SignedExtra = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, + ), ); /// Unchecked extrinsic type as expected by this runtime. @@ -119,6 +151,12 @@ pub type Migrations = ( pallet_multisig::migrations::v1::MigrateToV1, InitStorageVersions, cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // unreleased + snowbridge_system::migration::v0::InitializeOnUpgrade< + Runtime, + ConstU32, + ConstU32, + >, ); /// Migration to initialize storage versions for pallets added after genesis. @@ -171,10 +209,10 @@ 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_004_000, + spec_version: 1_005_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 3, + transaction_version: 4, state_version: 1, }; @@ -320,21 +358,27 @@ impl cumulus_pallet_parachain_system::Config for Runtime { impl parachain_info::Config for Runtime {} parameter_types! { - pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; + /// Amount of weight that can be spent per block to service messages. This was increased + /// from 35% to 60% of the max block weight to accommodate the Ethereum beacon light client + /// extrinsics. The force_checkpoint and submit extrinsics (for submit, optionally) includes + /// the sync committee's pubkeys (512 x 48 bytes) + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(60) * RuntimeBlockWeights::get().max_block; } impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_message_queue::WeightInfo; #[cfg(feature = "runtime-benchmarks")] - type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< - cumulus_primitives_core::AggregateMessageOrigin, - >; + type MessageProcessor = + pallet_message_queue::mock_helpers::NoopMessageProcessor; #[cfg(not(feature = "runtime-benchmarks"))] - type MessageProcessor = xcm_builder::ProcessXcmMessage< - AggregateMessageOrigin, - xcm_executor::XcmExecutor, - RuntimeCall, + type MessageProcessor = BridgeHubMessageRouter< + xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >, + EthereumOutboundQueue, >; type Size = u32; // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: @@ -451,6 +495,151 @@ impl pallet_utility::Config for Runtime { type WeightInfo = weights::pallet_utility::WeightInfo; } +// Ethereum Bridge + +#[cfg(not(feature = "runtime-benchmarks"))] +parameter_types! { + pub storage EthereumGatewayAddress: H160 = H160::zero(); +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); +} + +parameter_types! { + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetDeposit: u128 = (UNITS / 10) + EXISTENTIAL_DEPOSIT; + pub const InboundQueuePalletInstance: u8 = snowbridge_rococo_common::INBOUND_QUEUE_MESSAGES_PALLET_INDEX; + pub Parameters: PricingParameters = PricingParameters { + exchange_rate: FixedU128::from_rational(1, 400), + fee_per_gas: gwei(20), + rewards: Rewards { local: 1 * UNITS, remote: meth(1) } + }; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for Runtime { + fn initialize_storage(block_hash: H256, header: CompactExecutionHeader) { + >::insert(block_hash, header); + } +} + +impl snowbridge_inbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_ethereum_beacon_client::Pallet; + type Token = Balances; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type ChannelLookup = EthereumSystem; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + >; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type MaxMessageSize = ConstU32<2048>; + type WeightInfo = weights::snowbridge_inbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; +} + +impl snowbridge_outbound_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Hashing = Keccak256; + type MessageQueue = MessageQueue; + type Decimals = ConstU8<12>; + type MaxMessagePayloadSize = ConstU32<2048>; + type MaxMessagesPerBlock = ConstU32<32>; + type GasMeter = snowbridge_core::outbound::ConstantGasMeter; + type Balance = Balance; + type WeightToFee = WeightToFee; + type WeightInfo = weights::snowbridge_outbound_queue::WeightInfo; + type PricingParameters = EthereumSystem; + type Channels = EthereumSystem; +} + +#[cfg(not(feature = "beacon-spec-mainnet"))] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [0, 0, 0, 1], // 0x00000001 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 1], // 0x01000001 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 1], // 0x02000001 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 1], // 0x03000001 + epoch: 0, + }, + }; + pub const MaxExecutionHeadersToKeep:u32 = 1000; +} + +#[cfg(feature = "beacon-spec-mainnet")] +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions { + genesis: Fork { + version: [0, 0, 16, 32], // 0x00001020 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 16, 32], // 0x01001020 + epoch: 36660, + }, + bellatrix: Fork { + version: [2, 0, 16, 32], // 0x02001020 + epoch: 112260, + }, + capella: Fork { + version: [3, 0, 16, 32], // 0x03001020 + epoch: 162304, + }, + }; + pub const MaxExecutionHeadersToKeep:u32 = 8192 * 2; +} + +impl snowbridge_ethereum_beacon_client::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type MaxExecutionHeadersToKeep = MaxExecutionHeadersToKeep; + type WeightInfo = weights::snowbridge_ethereum_beacon_client::WeightInfo; +} + +#[cfg(feature = "runtime-benchmarks")] +impl snowbridge_system::BenchmarkHelper for () { + fn make_xcm_origin(location: xcm::latest::MultiLocation) -> RuntimeOrigin { + RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location)) + } +} + +impl snowbridge_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OutboundQueue = EthereumOutboundQueue; + type SiblingOrigin = EnsureXcm; + type AgentIdOf = xcm_config::AgentIdOf; + type TreasuryAccount = TreasuryAccount; + type Token = Balances; + type WeightInfo = weights::snowbridge_system::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type DefaultPricingParameters = Parameters; + type InboundDeliveryCost = EthereumInboundQueue; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -483,22 +672,37 @@ construct_runtime!( Utility: pallet_utility::{Pallet, Call, Event} = 40, Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 36, - // BridgeHubRococo uses: - // - BridgeWestendGrandpa - // - BridgeWestendParachains - // - BridgeWestendMessages - // - BridgeRelayers + // Bridge relayers pallet, used by several bridges here. + BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event} = 47, - // GRANDPA bridge modules. + // With-Westend GRANDPA bridge module. BridgeWestendGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Event, Config} = 48, - - // Parachain bridge modules. + // With-Westend parachain bridge module. BridgeWestendParachains: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 49, - - // Messaging bridge modules. + // With-Westend messaging bridge module. BridgeWestendMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 51, - - BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event} = 47, + // With-Westend bridge hub pallet. + XcmOverBridgeHubWestend: pallet_xcm_bridge_hub::::{Pallet} = 52, + + // With-Rococo Bulletin GRANDPA bridge module. + // + // we can't use `BridgeRococoBulletinGrandpa` name here, because the same Bulletin runtime will be + // used for both Rococo and Polkadot Bulletin chains AND this name affects runtime storage keys, used + // by the relayer process + BridgePolkadotBulletinGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Event, Config} = 60, + // With-Rococo Bulletin messaging bridge module. + // + // we can't use `BridgeRococoBulletinMessages` name here, because the same Bulletin runtime will be + // used for both Rococo and Polkadot Bulletin chains AND this name affects runtime storage keys, used + // by this runtime and the relayer process + BridgePolkadotBulletinMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 61, + // With-Rococo Bulletin bridge hub pallet. + XcmOverPolkadotBulletin: pallet_xcm_bridge_hub::::{Pallet} = 62, + + EthereumInboundQueue: snowbridge_inbound_queue::{Pallet, Call, Storage, Event} = 80, + EthereumOutboundQueue: snowbridge_outbound_queue::{Pallet, Call, Storage, Event} = 81, + EthereumBeaconClient: snowbridge_ethereum_beacon_client::{Pallet, Call, Storage, Event} = 82, + EthereumSystem: snowbridge_system::{Pallet, Call, Storage, Config, Event} = 83, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -506,14 +710,23 @@ construct_runtime!( } ); +/// Proper alias for bridge GRANDPA pallet used to bridge with the bulletin chain. +pub type BridgeRococoBulletinGrandpa = BridgePolkadotBulletinGrandpa; +/// Proper alias for bridge messages pallet used to bridge with the bulletin chain. +pub type BridgeRococoBulletinMessages = BridgePolkadotBulletinMessages; +/// Proper alias for bridge messages pallet used to bridge with the bulletin chain. +pub type XcmOverRococoBulletin = XcmOverPolkadotBulletin; + bridge_runtime_common::generate_bridge_reject_obsolete_headers_and_messages! { RuntimeCall, AccountId, // Grandpa BridgeWestendGrandpa, + BridgeRococoBulletinGrandpa, // Parachains BridgeWestendParachains, // Messages - BridgeWestendMessages + BridgeWestendMessages, + BridgeRococoBulletinMessages } #[cfg(feature = "runtime-benchmarks")] @@ -538,7 +751,13 @@ mod benches { [pallet_bridge_grandpa, WestendFinality] [pallet_bridge_parachains, WithinWestend] [pallet_bridge_messages, RococoToWestend] + [pallet_bridge_messages, RococoToRococoBulletin] [pallet_bridge_relayers, BridgeRelayersBench::] + // Ethereum Bridge + [snowbridge_inbound_queue, EthereumInboundQueue] + [snowbridge_outbound_queue, EthereumOutboundQueue] + [snowbridge_system, EthereumSystem] + [snowbridge_ethereum_beacon_client, EthereumBeaconClient] ); } @@ -731,6 +950,58 @@ impl_runtime_apis! { } } + impl bp_polkadot_bulletin::PolkadotBulletinFinalityApi for Runtime { + fn best_finalized() -> Option> { + BridgePolkadotBulletinGrandpa::best_finalized() + } + + fn synced_headers_grandpa_info( + ) -> Vec> { + BridgePolkadotBulletinGrandpa::synced_headers_grandpa_info() + } + } + + impl bp_polkadot_bulletin::FromPolkadotBulletinInboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + messages: Vec<(bp_messages::MessagePayload, bp_messages::OutboundMessageDetails)>, + ) -> Vec { + bridge_runtime_common::messages_api::inbound_message_details::< + Runtime, + bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, + >(lane, messages) + } + } + + impl bp_polkadot_bulletin::ToPolkadotBulletinOutboundLaneApi for Runtime { + fn message_details( + lane: bp_messages::LaneId, + begin: bp_messages::MessageNonce, + end: bp_messages::MessageNonce, + ) -> Vec { + bridge_runtime_common::messages_api::outbound_message_details::< + Runtime, + bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, + >(lane, begin, end) + } + } + + impl snowbridge_outbound_queue_runtime_api::OutboundQueueApi for Runtime { + fn prove_message(leaf_index: u64) -> Option { + snowbridge_outbound_queue::api::prove_message::(leaf_index) + } + + fn calculate_fee(message: Message) -> Option { + snowbridge_outbound_queue::api::calculate_fee::(message) + } + } + + impl snowbridge_system_runtime_api::ControlApi for Runtime { + fn agent_id(location: VersionedMultiLocation) -> Option { + snowbridge_system::api::agent_id::(location) + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { @@ -773,6 +1044,7 @@ impl_runtime_apis! { type WestendFinality = BridgeWestendGrandpa; type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; + type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -823,6 +1095,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on BH. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // BH only supports teleports to system parachain. + // Relay/native token can be teleported between BH and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } use xcm::latest::prelude::*; @@ -839,7 +1123,7 @@ impl_runtime_apis! { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< - xcm_config::XcmConfig, + xcm_config::XcmConfig, ExistentialDepositMultiAsset, xcm_config::PriceForParentDelivery, >; @@ -919,7 +1203,28 @@ impl_runtime_apis! { fn export_message_origin_and_destination( ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { - Ok((TokenLocation::get(), NetworkId::Westend, X1(Parachain(100)))) + // save XCM version for remote bridge hub + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridge_to_westend_config::BridgeHubWestendLocation::get()), + XCM_VERSION, + ).map_err(|e| { + log::error!( + "Failed to dispatch `force_xcm_version({:?}, {:?}, {:?})`, error: {:?}", + RuntimeOrigin::root(), + bridge_to_westend_config::BridgeHubWestendLocation::get(), + XCM_VERSION, + e + ); + BenchmarkError::Stop("XcmVersion was not stored!") + })?; + Ok( + ( + bridge_to_westend_config::FromAssetHubRococoToAssetHubWestendRoute::get().location, + NetworkId::Westend, + X1(Parachain(bridge_to_westend_config::AssetHubWestendParaId::get().into())) + ) + ) } fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { @@ -933,9 +1238,12 @@ impl_runtime_apis! { type WestendFinality = BridgeWestendGrandpa; type WithinWestend = pallet_bridge_parachains::benchmarking::Pallet::; type RococoToWestend = pallet_bridge_messages::benchmarking::Pallet ::; + type RococoToRococoBulletin = pallet_bridge_messages::benchmarking::Pallet ::; use bridge_runtime_common::messages_benchmarking::{ + prepare_message_delivery_proof_from_grandpa_chain, prepare_message_delivery_proof_from_parachain, + prepare_message_proof_from_grandpa_chain, prepare_message_proof_from_parachain, generate_xcm_builder_bridge_message_sample, }; @@ -988,6 +1296,41 @@ impl_runtime_apis! { } } + impl BridgeMessagesConfig for Runtime { + fn is_relayer_rewarded(_relayer: &Self::AccountId) -> bool { + // we do not pay any rewards in this bridge + true + } + + fn prepare_message_proof( + params: MessageProofParams, + ) -> (bridge_to_bulletin_config::FromRococoBulletinMessagesProof, Weight) { + use cumulus_primitives_core::XcmpMessageSource; + assert!(XcmpQueue::take_outbound_messages(usize::MAX).is_empty()); + ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests(42.into()); + prepare_message_proof_from_grandpa_chain::< + Runtime, + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::WithRococoBulletinMessageBridge, + >(params, generate_xcm_builder_bridge_message_sample(X2(GlobalConsensus(Rococo), Parachain(42)))) + } + + fn prepare_message_delivery_proof( + params: MessageDeliveryProofParams, + ) -> bridge_to_bulletin_config::ToRococoBulletinMessagesDeliveryProof { + prepare_message_delivery_proof_from_grandpa_chain::< + Runtime, + bridge_common_config::BridgeGrandpaRococoBulletinInstance, + bridge_to_bulletin_config::WithRococoBulletinMessageBridge, + >(params) + } + + fn is_message_successfully_dispatched(_nonce: bp_messages::MessageNonce) -> bool { + use cumulus_primitives_core::XcmpMessageSource; + !XcmpQueue::take_outbound_messages(usize::MAX).is_empty() + } + } + use bridge_runtime_common::parachains_benchmarking::prepare_parachain_heads_proof; use pallet_bridge_parachains::benchmarking::Config as BridgeParachainsConfig; use pallet_bridge_relayers::benchmarking::{ @@ -1099,7 +1442,10 @@ mod tests { frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(10), BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),) + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), + ) ); // for BridgeHubRococo diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system.rs index b0f7806be8ee7b3509895652a94f10a272913d09..df440a68a36deefbb8928b3a0e2a9b0aa498b66b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system.rs @@ -151,4 +151,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index 3604ab3c0ff33c5aa58267a960f30385c7457615..b134bb41ed134565fa4669c9b5d2a5414efcc756 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -27,7 +27,8 @@ pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_balances; pub mod pallet_bridge_grandpa; -pub mod pallet_bridge_messages; +pub mod pallet_bridge_messages_rococo_to_rococo_bulletin; +pub mod pallet_bridge_messages_rococo_to_westend; pub mod pallet_bridge_parachains; pub mod pallet_bridge_relayers; pub mod pallet_collator_selection; @@ -39,11 +40,14 @@ pub mod pallet_utility; pub mod pallet_xcm; pub mod paritydb_weights; pub mod rocksdb_weights; +pub mod snowbridge_ethereum_beacon_client; +pub mod snowbridge_inbound_queue; +pub mod snowbridge_outbound_queue; +pub mod snowbridge_system; pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; use crate::Runtime; @@ -52,7 +56,26 @@ use frame_support::weights::Weight; // import trait from dependency module use ::pallet_bridge_relayers::WeightInfoExt as _; -impl MessagesWeightInfoExt for pallet_bridge_messages::WeightInfo { +impl MessagesWeightInfoExt + for pallet_bridge_messages_rococo_to_rococo_bulletin::WeightInfo +{ + fn expected_extra_storage_proof_size() -> u32 { + bp_polkadot_bulletin::EXTRA_STORAGE_PROOF_SIZE + } + + fn receive_messages_proof_overhead_from_runtime() -> Weight { + pallet_bridge_relayers::WeightInfo::::receive_messages_proof_overhead_from_runtime( + ) + } + + fn receive_messages_delivery_proof_overhead_from_runtime() -> Weight { + pallet_bridge_relayers::WeightInfo::::receive_messages_delivery_proof_overhead_from_runtime() + } +} + +impl MessagesWeightInfoExt + for pallet_bridge_messages_rococo_to_westend::WeightInfo +{ fn expected_extra_storage_proof_size() -> u32 { bp_bridge_hub_westend::EXTRA_STORAGE_PROOF_SIZE } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa.rs index aaa6a3e06221e18d4837175f3040d464607bc003..8c2435599f59780be56dcaa5060addee4c5c1d15 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_grandpa.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_bridge_grandpa` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -66,13 +66,13 @@ impl pallet_bridge_grandpa::WeightInfo for WeightInfo. + +//! Autogenerated weights for `pallet_bridge_messages` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-14, 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` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_bridge_messages +// --chain=bridge-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_bridge_messages`. +pub struct WeightInfo(PhantomData); +impl pallet_bridge_messages::WeightInfo for WeightInfo { + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn receive_single_message_proof() -> Weight { + // Proof Size summary in bytes: + // Measured: `621` + // Estimated: `52645` + // Minimum execution time: 36_661_000 picoseconds. + Weight::from_parts(38_106_000, 0) + .saturating_add(Weight::from_parts(0, 52645)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn receive_two_messages_proof() -> Weight { + // Proof Size summary in bytes: + // Measured: `621` + // Estimated: `52645` + // Minimum execution time: 47_599_000 picoseconds. + Weight::from_parts(49_731_000, 0) + .saturating_add(Weight::from_parts(0, 52645)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn receive_single_message_proof_with_outbound_lane_state() -> Weight { + // Proof Size summary in bytes: + // Measured: `621` + // Estimated: `52645` + // Minimum execution time: 42_211_000 picoseconds. + Weight::from_parts(43_454_000, 0) + .saturating_add(Weight::from_parts(0, 52645)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + fn receive_single_message_proof_1_kb() -> Weight { + // Proof Size summary in bytes: + // Measured: `589` + // Estimated: `52645` + // Minimum execution time: 36_072_000 picoseconds. + Weight::from_parts(37_260_000, 0) + .saturating_add(Weight::from_parts(0, 52645)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + fn receive_single_message_proof_16_kb() -> Weight { + // Proof Size summary in bytes: + // Measured: `589` + // Estimated: `52645` + // Minimum execution time: 66_995_000 picoseconds. + Weight::from_parts(68_661_000, 0) + .saturating_add(Weight::from_parts(0, 52645)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + fn receive_delivery_proof_for_single_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `588` + // Estimated: `2543` + // Minimum execution time: 25_553_000 picoseconds. + Weight::from_parts(26_205_000, 0) + .saturating_add(Weight::from_parts(0, 2543)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { + // Proof Size summary in bytes: + // Measured: `588` + // Estimated: `2543` + // Minimum execution time: 25_610_000 picoseconds. + Weight::from_parts(26_273_000, 0) + .saturating_add(Weight::from_parts(0, 2543)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { + // Proof Size summary in bytes: + // Measured: `588` + // Estimated: `2543` + // Minimum execution time: 25_651_000 picoseconds. + Weight::from_parts(26_172_000, 0) + .saturating_add(Weight::from_parts(0, 2543)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `BridgePolkadotBulletinMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (r:1 w:0) + /// Proof: `BridgePolkadotBulletinGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) + /// Storage: `BridgePolkadotBulletinMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgePolkadotBulletinMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, 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: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// The range of component `i` is `[128, 2048]`. + /// The range of component `i` is `[128, 2048]`. + fn receive_single_message_proof_with_dispatch(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `780` + // Estimated: `52645` + // Minimum execution time: 64_219_000 picoseconds. + Weight::from_parts(65_848_290, 0) + .saturating_add(Weight::from_parts(0, 52645)) + // Standard Error: 43 + .saturating_add(Weight::from_parts(7_577, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs similarity index 90% rename from cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.rs rename to cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs index 17a45df5bfb3c51d8ce42d262bbff4a496b72acf..30ea9eed4a5b4f187ea76633400cff8c39991b46 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_messages_rococo_to_westend.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -60,10 +60,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_single_message_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `538` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 41_577_000 picoseconds. - Weight::from_parts(42_621_000, 0) + // Minimum execution time: 40_349_000 picoseconds. + Weight::from_parts(41_856_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -80,10 +80,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_two_messages_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `538` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 52_880_000 picoseconds. - Weight::from_parts(53_697_000, 0) + // Minimum execution time: 50_514_000 picoseconds. + Weight::from_parts(52_254_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -100,10 +100,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_single_message_proof_with_outbound_lane_state() -> Weight { // Proof Size summary in bytes: - // Measured: `538` + // Measured: `605` // Estimated: `52645` - // Minimum execution time: 47_424_000 picoseconds. - Weight::from_parts(48_445_000, 0) + // Minimum execution time: 45_761_000 picoseconds. + Weight::from_parts(47_075_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -118,10 +118,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgeWestendMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) fn receive_single_message_proof_1_kb() -> Weight { // Proof Size summary in bytes: - // Measured: `506` + // Measured: `573` // Estimated: `52645` - // Minimum execution time: 40_619_000 picoseconds. - Weight::from_parts(42_262_000, 0) + // Minimum execution time: 39_098_000 picoseconds. + Weight::from_parts(40_577_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -136,10 +136,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgeWestendMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) fn receive_single_message_proof_16_kb() -> Weight { // Proof Size summary in bytes: - // Measured: `506` + // Measured: `573` // Estimated: `52645` - // Minimum execution time: 74_603_000 picoseconds. - Weight::from_parts(78_209_000, 0) + // Minimum execution time: 69_120_000 picoseconds. + Weight::from_parts(71_810_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -156,11 +156,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: - // Measured: `377` - // Estimated: `3842` - // Minimum execution time: 33_762_000 picoseconds. - Weight::from_parts(34_405_000, 0) - .saturating_add(Weight::from_parts(0, 3842)) + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 32_325_000 picoseconds. + Weight::from_parts(33_070_000, 0) + .saturating_add(Weight::from_parts(0, 3912)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -176,11 +176,11 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: - // Measured: `377` - // Estimated: `3842` - // Minimum execution time: 33_805_000 picoseconds. - Weight::from_parts(35_051_000, 0) - .saturating_add(Weight::from_parts(0, 3842)) + // Measured: `447` + // Estimated: `3912` + // Minimum execution time: 32_180_000 picoseconds. + Weight::from_parts(33_202_000, 0) + .saturating_add(Weight::from_parts(0, 3912)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -196,10 +196,10 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: - // Measured: `377` + // Measured: `447` // Estimated: `6086` - // Minimum execution time: 38_612_000 picoseconds. - Weight::from_parts(39_412_000, 0) + // Minimum execution time: 36_774_000 picoseconds. + Weight::from_parts(37_774_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) @@ -227,15 +227,16 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[128, 2048]`. + /// The range of component `i` is `[128, 2048]`. fn receive_single_message_proof_with_dispatch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `669` + // Measured: `736` // Estimated: `52645` - // Minimum execution time: 69_285_000 picoseconds. - Weight::from_parts(70_867_498, 0) + // Minimum execution time: 65_934_000 picoseconds. + Weight::from_parts(67_915_916, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 111 - .saturating_add(Weight::from_parts(7_489, 0).saturating_mul(i.into())) + // Standard Error: 65 + .saturating_add(Weight::from_parts(7_190, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains.rs index 5c7c4a63682d50dc6da06f4e878b16e567d73039..ea68852804e3955577bf822d42887bf5bd772657 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_parachains.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_bridge_parachains` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -59,13 +59,15 @@ impl pallet_bridge_parachains::WeightInfo for WeightInf /// Storage: `BridgeWestendParachains::ImportedParaHeads` (r:0 w:1) /// Proof: `BridgeWestendParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) /// The range of component `p` is `[1, 2]`. - fn submit_parachain_heads_with_n_parachains(_p: u32, ) -> Weight { + fn submit_parachain_heads_with_n_parachains(p: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `434` // Estimated: `2543` - // Minimum execution time: 31_987_000 picoseconds. - Weight::from_parts(33_060_534, 0) + // Minimum execution time: 31_135_000 picoseconds. + Weight::from_parts(32_061_351, 0) .saturating_add(Weight::from_parts(0, 2543)) + // Standard Error: 80_309 + .saturating_add(Weight::from_parts(99_724, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -83,8 +85,8 @@ impl pallet_bridge_parachains::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `434` // Estimated: `2543` - // Minimum execution time: 33_360_000 picoseconds. - Weight::from_parts(34_182_000, 0) + // Minimum execution time: 32_263_000 picoseconds. + Weight::from_parts(33_139_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -103,8 +105,8 @@ impl pallet_bridge_parachains::WeightInfo for WeightInf // Proof Size summary in bytes: // Measured: `434` // Estimated: `2543` - // Minimum execution time: 65_246_000 picoseconds. - Weight::from_parts(65_985_000, 0) + // Minimum execution time: 61_313_000 picoseconds. + Weight::from_parts(62_200_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_relayers.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_relayers.rs index 70af694645da2d55336b4aa92a2ccf9450d2d92d..5ab4cb900d848f37f1a5777b686d294837688495 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_relayers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_bridge_relayers.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_bridge_relayers` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -54,10 +54,10 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn claim_rewards() -> Weight { // Proof Size summary in bytes: - // Measured: `207` + // Measured: `244` // Estimated: `3593` - // Minimum execution time: 46_579_000 picoseconds. - Weight::from_parts(48_298_000, 0) + // Minimum execution time: 45_393_000 picoseconds. + Weight::from_parts(46_210_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -70,10 +70,10 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) fn register() -> Weight { // Proof Size summary in bytes: - // Measured: `61` + // Measured: `97` // Estimated: `4714` - // Minimum execution time: 24_219_000 picoseconds. - Weight::from_parts(24_993_000, 0) + // Minimum execution time: 23_767_000 picoseconds. + Weight::from_parts(24_217_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -84,10 +84,10 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) fn deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `160` + // Measured: `197` // Estimated: `4714` - // Minimum execution time: 26_279_000 picoseconds. - Weight::from_parts(26_810_000, 0) + // Minimum execution time: 25_745_000 picoseconds. + Weight::from_parts(26_319_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -100,10 +100,10 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn slash_and_deregister() -> Weight { // Proof Size summary in bytes: - // Measured: `263` + // Measured: `300` // Estimated: `4714` - // Minimum execution time: 27_672_000 picoseconds. - Weight::from_parts(28_946_000, 0) + // Minimum execution time: 27_497_000 picoseconds. + Weight::from_parts(27_939_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -112,10 +112,10 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn register_relayer_reward() -> Weight { // Proof Size summary in bytes: - // Measured: `6` + // Measured: `42` // Estimated: `3538` - // Minimum execution time: 5_487_000 picoseconds. - Weight::from_parts(5_725_000, 0) + // Minimum execution time: 5_584_000 picoseconds. + Weight::from_parts(5_908_000, 0) .saturating_add(Weight::from_parts(0, 3538)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs index 5aa4999c624cf1b516ef0930d869dac460680396..5faded42aa82df52f403b68de2a470ad4a5a17b7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,26 +62,39 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 24_179_000 picoseconds. - Weight::from_parts(24_684_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 23_683_000 picoseconds. + Weight::from_parts(24_199_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - /// Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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: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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 21_093_000 picoseconds. - Weight::from_parts(21_523_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 89_524_000 picoseconds. + Weight::from_parts(91_401_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -93,6 +106,32 @@ 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: `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`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 91_890_000 picoseconds. + Weight::from_parts(93_460_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -109,8 +148,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_938_000 picoseconds. - Weight::from_parts(7_243_000, 0) + // Minimum execution time: 7_152_000 picoseconds. + Weight::from_parts(7_355_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -120,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_159_000 picoseconds. - Weight::from_parts(2_290_000, 0) + // Minimum execution time: 2_081_000 picoseconds. + Weight::from_parts(2_258_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -145,11 +184,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 28_337_000 picoseconds. - Weight::from_parts(29_265_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 28_067_000 picoseconds. + Weight::from_parts(28_693_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) } @@ -171,11 +210,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `292` - // Estimated: `3757` - // Minimum execution time: 30_599_000 picoseconds. - Weight::from_parts(31_272_000, 0) - .saturating_add(Weight::from_parts(0, 3757)) + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 30_420_000 picoseconds. + Weight::from_parts(31_373_000, 0) + .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -185,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_132_000 picoseconds. - Weight::from_parts(2_280_000, 0) + // Minimum execution time: 2_087_000 picoseconds. + Weight::from_parts(2_243_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -194,11 +233,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `187` - // Estimated: `11077` - // Minimum execution time: 18_262_000 picoseconds. - Weight::from_parts(18_640_000, 0) - .saturating_add(Weight::from_parts(0, 11077)) + // Measured: `95` + // Estimated: `10985` + // Minimum execution time: 15_142_000 picoseconds. + Weight::from_parts(15_598_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -206,11 +245,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `191` - // Estimated: `11081` - // Minimum execution time: 18_512_000 picoseconds. - Weight::from_parts(18_888_000, 0) - .saturating_add(Weight::from_parts(0, 11081)) + // Measured: `99` + // Estimated: `10989` + // Minimum execution time: 15_041_000 picoseconds. + Weight::from_parts(15_493_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -218,11 +257,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `13563` - // Minimum execution time: 19_362_000 picoseconds. - Weight::from_parts(20_056_000, 0) - .saturating_add(Weight::from_parts(0, 13563)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 16_624_000 picoseconds. + Weight::from_parts(17_031_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) @@ -241,11 +280,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `6082` - // Minimum execution time: 27_318_000 picoseconds. - Weight::from_parts(28_075_000, 0) - .saturating_add(Weight::from_parts(0, 6082)) + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 26_398_000 picoseconds. + Weight::from_parts(26_847_000, 0) + .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -253,22 +292,22 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `172` - // Estimated: `8587` - // Minimum execution time: 9_930_000 picoseconds. - Weight::from_parts(10_192_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) + // Measured: `136` + // Estimated: `8551` + // Minimum execution time: 8_741_000 picoseconds. + Weight::from_parts(8_954_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) .saturating_add(T::DbWeight::get().reads(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 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: `198` - // Estimated: `11088` - // Minimum execution time: 18_305_000 picoseconds. - Weight::from_parts(18_738_000, 0) - .saturating_add(Weight::from_parts(0, 11088)) + // Measured: `106` + // Estimated: `10996` + // Minimum execution time: 15_306_000 picoseconds. + Weight::from_parts(15_760_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -288,11 +327,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// 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: `204` - // Estimated: `11094` - // Minimum execution time: 34_559_000 picoseconds. - Weight::from_parts(35_241_000, 0) - .saturating_add(Weight::from_parts(0, 11094)) + // Measured: `112` + // Estimated: `11002` + // Minimum execution time: 33_127_000 picoseconds. + Weight::from_parts(33_938_000, 0) + .saturating_add(Weight::from_parts(0, 11002)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -302,11 +341,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: 4_290_000 picoseconds. + Weight::from_parts(4_450_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -314,11 +353,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: 26_408_000 picoseconds. + Weight::from_parts(26_900_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/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs new file mode 100644 index 0000000000000000000000000000000000000000..cd960597b4410fbacdf99a766eebffb94061b812 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs @@ -0,0 +1,151 @@ +// 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 `snowbridge_ethereum_beacon_client` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `ip-172-31-8-124`, CPU: `Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --base-path +// /mnt/scratch/benchmark +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_ethereum_beacon_client +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_ethereum_beacon_client.rs + +#![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 `snowbridge_ethereum_beacon_client`. +pub struct WeightInfo(PhantomData); +impl snowbridge_ethereum_beacon_client::WeightInfo for WeightInfo { + /// Storage: EthereumBeaconClient FinalizedBeaconStateIndex (r:1 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconStateIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconStateMapping (r:1 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconStateMapping (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient NextSyncCommittee (r:0 w:1) + /// Proof: EthereumBeaconClient NextSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient InitialCheckpointRoot (r:0 w:1) + /// Proof: EthereumBeaconClient InitialCheckpointRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ValidatorsRoot (r:0 w:1) + /// Proof: EthereumBeaconClient ValidatorsRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient LatestFinalizedBlockRoot (r:0 w:1) + /// Proof: EthereumBeaconClient LatestFinalizedBlockRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient CurrentSyncCommittee (r:0 w:1) + /// Proof: EthereumBeaconClient CurrentSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient LatestExecutionState (r:0 w:1) + /// Proof: EthereumBeaconClient LatestExecutionState (max_values: Some(1), max_size: Some(80), added: 575, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconState (r:0 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconState (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + fn force_checkpoint() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3501` + // Minimum execution time: 97_185_781_000 picoseconds. + Weight::from_parts(97_263_571_000, 0) + .saturating_add(Weight::from_parts(0, 3501)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(9)) + } + /// Storage: EthereumBeaconClient LatestFinalizedBlockRoot (r:1 w:1) + /// Proof: EthereumBeaconClient LatestFinalizedBlockRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconState (r:1 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconState (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient LatestExecutionState (r:1 w:0) + /// Proof: EthereumBeaconClient LatestExecutionState (max_values: Some(1), max_size: Some(80), added: 575, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient NextSyncCommittee (r:1 w:0) + /// Proof: EthereumBeaconClient NextSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient CurrentSyncCommittee (r:1 w:0) + /// Proof: EthereumBeaconClient CurrentSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ValidatorsRoot (r:1 w:0) + /// Proof: EthereumBeaconClient ValidatorsRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconStateIndex (r:1 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconStateIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconStateMapping (r:1 w:1) + /// Proof: EthereumBeaconClient FinalizedBeaconStateMapping (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn submit() -> Weight { + // Proof Size summary in bytes: + // Measured: `92753` + // Estimated: `93857` + // Minimum execution time: 25_999_968_000 picoseconds. + Weight::from_parts(26_051_019_000, 0) + .saturating_add(Weight::from_parts(0, 93857)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: EthereumBeaconClient LatestFinalizedBlockRoot (r:1 w:0) + /// Proof: EthereumBeaconClient LatestFinalizedBlockRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconState (r:1 w:0) + /// Proof: EthereumBeaconClient FinalizedBeaconState (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient LatestExecutionState (r:1 w:0) + /// Proof: EthereumBeaconClient LatestExecutionState (max_values: Some(1), max_size: Some(80), added: 575, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient NextSyncCommittee (r:1 w:1) + /// Proof: EthereumBeaconClient NextSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient CurrentSyncCommittee (r:1 w:0) + /// Proof: EthereumBeaconClient CurrentSyncCommittee (max_values: Some(1), max_size: Some(92372), added: 92867, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ValidatorsRoot (r:1 w:0) + /// Proof: EthereumBeaconClient ValidatorsRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn submit_with_sync_committee() -> Weight { + // Proof Size summary in bytes: + // Measured: `92717` + // Estimated: `93857` + // Minimum execution time: 122_354_917_000 picoseconds. + Weight::from_parts(122_461_312_000, 0) + .saturating_add(Weight::from_parts(0, 93857)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: EthereumBeaconClient LatestFinalizedBlockRoot (r:1 w:0) + /// Proof: EthereumBeaconClient LatestFinalizedBlockRoot (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient FinalizedBeaconState (r:1 w:0) + /// Proof: EthereumBeaconClient FinalizedBeaconState (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient LatestExecutionState (r:1 w:1) + /// Proof: EthereumBeaconClient LatestExecutionState (max_values: Some(1), max_size: Some(80), added: 575, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaderIndex (r:1 w:1) + /// Proof: EthereumBeaconClient ExecutionHeaderIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaderMapping (r:1 w:1) + /// Proof: EthereumBeaconClient ExecutionHeaderMapping (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaders (r:0 w:1) + /// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen) + fn submit_execution_header() -> Weight { + // Proof Size summary in bytes: + // Measured: `386` + // Estimated: `3537` + // Minimum execution time: 108_761_000 picoseconds. + Weight::from_parts(113_158_000, 0) + .saturating_add(Weight::from_parts(0, 3537)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..f734227a4111f66e583560656fca434a02067815 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs @@ -0,0 +1,69 @@ +// 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 `snowbridge_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_inbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs + +#![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 `snowbridge_inbound_queue`. +pub struct WeightInfo(PhantomData); +impl snowbridge_inbound_queue::WeightInfo for WeightInfo { + /// Storage: EthereumInboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumInboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaders (r:1 w:0) + /// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen) + /// Storage: EthereumInboundQueue Nonce (r:1 w:1) + /// Proof: EthereumInboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn submit() -> Weight { + // Proof Size summary in bytes: + // Measured: `457` + // Estimated: `3601` + // Minimum execution time: 69_000_000 picoseconds. + Weight::from_parts(70_000_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..6cffbc5344a6bd231a19b2801a252a2e287ed081 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs @@ -0,0 +1,87 @@ +// 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 `snowbridge_outbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-20, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `192.168.1.13`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// ../target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_outbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --output +// ../parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs + +#![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 `snowbridge_outbound_queue`. +pub struct WeightInfo(PhantomData); +impl snowbridge_outbound_queue::WeightInfo for WeightInfo { + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1) + /// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Nonce (r:1 w:1) + /// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue Messages (r:1 w:1) + /// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured) + fn do_process_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3485` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(39_000_000, 3485) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0) + /// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + fn commit() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(28_000_000, 2579) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + + fn commit_single() -> Weight { + // Proof Size summary in bytes: + // Measured: `1094` + // Estimated: `2579` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 1586) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..88c6c669c880299c6c4417b65e7baa1f3ea9922f --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_system.rs @@ -0,0 +1,256 @@ +// 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 `snowbridge_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `crake.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain +// bridge-hub-rococo-dev +// --pallet=snowbridge_system +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --output +// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_system.rs + +#![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 `snowbridge_system`. +pub struct WeightInfo(PhantomData); +impl snowbridge_system::WeightInfo for WeightInfo { + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 47_000_000 picoseconds. + Weight::from_parts(47_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: EthereumSystem Agents (r:1 w:1) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// 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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn create_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `187` + // Estimated: `6196` + // Minimum execution time: 87_000_000 picoseconds. + Weight::from_parts(87_000_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(6)) + } + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumSystem Channels (r:1 w:1) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, 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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 create_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `602` + // Estimated: `69050` + // Minimum execution time: 84_000_000 picoseconds. + Weight::from_parts(84_000_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: EthereumSystem Channels (r:1 w:0) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn update_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(41_000_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: EthereumSystem Channels (r:1 w:0) + /// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn force_update_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `256` + // Estimated: `6044` + // Minimum execution time: 41_000_000 picoseconds. + Weight::from_parts(41_000_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(5)) + .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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_operating_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 30_000_000 picoseconds. + Weight::from_parts(30_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn transfer_native_from_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `252` + // Estimated: `6044` + // Minimum execution time: 43_000_000 picoseconds. + Weight::from_parts(43_000_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: EthereumSystem Agents (r:1 w:0) + /// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn force_transfer_native_from_agent() -> Weight { + // Proof Size summary in bytes: + // Measured: `252` + // Estimated: `6044` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(42_000_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) + .saturating_add(T::DbWeight::get().reads(5)) + .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: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_token_transfer_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(42_000_000, 3517) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + + /// Storage: ParachainInfo ParachainId (r:1 w:0) + /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, 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 ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:0 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + fn set_pricing_parameters() -> Weight { + // Proof Size summary in bytes: + // Measured: `80` + // Estimated: `3517` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(42_000_000, 3517) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 0ae6d0b5623fbfefb3e55c8a5bf3f43370d21a4b..abd84f8e89b07799758c36b002c30db742305927 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 // Executed Command: @@ -68,8 +68,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `171` // Estimated: `6196` - // Minimum execution time: 63_453_000 picoseconds. - Weight::from_parts(64_220_000, 6196) + // Minimum execution time: 61_813_000 picoseconds. + Weight::from_parts(62_996_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -77,8 +77,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_238_000 picoseconds. - Weight::from_parts(2_351_000, 0) + // Minimum execution time: 2_044_000 picoseconds. + Weight::from_parts(2_112_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -86,58 +86,58 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 7_953_000 picoseconds. - Weight::from_parts(8_162_000, 3497) + // Minimum execution time: 7_472_000 picoseconds. + Weight::from_parts(7_723_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_080_000 picoseconds. - Weight::from_parts(9_333_000, 0) + // Minimum execution time: 8_414_000 picoseconds. + Weight::from_parts(8_765_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_415_000 picoseconds. - Weight::from_parts(2_519_000, 0) + // Minimum execution time: 2_192_000 picoseconds. + Weight::from_parts(2_243_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_045_000 picoseconds. - Weight::from_parts(2_184_000, 0) + // Minimum execution time: 1_866_000 picoseconds. + Weight::from_parts(1_931_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_065_000 picoseconds. - Weight::from_parts(2_125_000, 0) + // Minimum execution time: 1_847_000 picoseconds. + Weight::from_parts(1_921_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_077_000 picoseconds. - Weight::from_parts(2_164_000, 0) + // Minimum execution time: 1_797_000 picoseconds. + Weight::from_parts(1_880_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_868_000 picoseconds. - Weight::from_parts(2_933_000, 0) + // Minimum execution time: 2_458_000 picoseconds. + Weight::from_parts(2_523_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_058_000 picoseconds. - Weight::from_parts(2_164_000, 0) + // Minimum execution time: 1_833_000 picoseconds. + Weight::from_parts(1_906_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -159,8 +159,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `171` // Estimated: `6196` - // Minimum execution time: 55_971_000 picoseconds. - Weight::from_parts(56_869_000, 6196) + // Minimum execution time: 54_659_000 picoseconds. + Weight::from_parts(56_025_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -170,8 +170,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 11_382_000 picoseconds. - Weight::from_parts(11_672_000, 3555) + // Minimum execution time: 10_953_000 picoseconds. + Weight::from_parts(11_220_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -179,8 +179,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_071_000 picoseconds. - Weight::from_parts(2_193_000, 0) + // Minimum execution time: 1_834_000 picoseconds. + Weight::from_parts(1_892_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -200,8 +200,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 22_573_000 picoseconds. - Weight::from_parts(23_423_000, 3503) + // Minimum execution time: 22_238_000 picoseconds. + Weight::from_parts(22_690_000, 3503) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -211,44 +211,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_870_000 picoseconds. - Weight::from_parts(3_993_000, 0) + // Minimum execution time: 3_798_000 picoseconds. + Weight::from_parts(3_936_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: 3_483_000 picoseconds. - Weight::from_parts(3_598_000, 0) + // Minimum execution time: 2_985_000 picoseconds. + Weight::from_parts(3_099_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_241_000 picoseconds. - Weight::from_parts(2_297_000, 0) + // Minimum execution time: 1_955_000 picoseconds. + Weight::from_parts(2_050_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_230_000 picoseconds. - Weight::from_parts(2_318_000, 0) + // Minimum execution time: 1_939_000 picoseconds. + Weight::from_parts(1_990_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_051_000 picoseconds. - Weight::from_parts(2_153_000, 0) + // Minimum execution time: 1_841_000 picoseconds. + Weight::from_parts(1_900_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_306_000 picoseconds. - Weight::from_parts(2_380_000, 0) + // Minimum execution time: 2_081_000 picoseconds. + Weight::from_parts(2_145_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -270,8 +270,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `171` // Estimated: `6196` - // Minimum execution time: 60_201_000 picoseconds. - Weight::from_parts(61_132_000, 6196) + // Minimum execution time: 59_600_000 picoseconds. + Weight::from_parts(61_572_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -279,8 +279,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_554_000 picoseconds. - Weight::from_parts(4_704_000, 0) + // Minimum execution time: 4_390_000 picoseconds. + Weight::from_parts(4_517_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -302,8 +302,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `171` // Estimated: `6196` - // Minimum execution time: 56_071_000 picoseconds. - Weight::from_parts(56_889_000, 6196) + // Minimum execution time: 53_864_000 picoseconds. + Weight::from_parts(55_527_000, 6196) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -311,25 +311,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_093_000 picoseconds. - Weight::from_parts(2_169_000, 0) + // Minimum execution time: 1_879_000 picoseconds. + Weight::from_parts(1_947_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_027_000 picoseconds. - Weight::from_parts(2_172_000, 0) + // Minimum execution time: 1_827_000 picoseconds. + Weight::from_parts(1_900_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_035_000 picoseconds. - Weight::from_parts(2_164_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_898_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: `PolkadotXcm::SupportedVersion` (r:2 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `BridgeWestendMessages::PalletOperatingMode` (r:1 w:0) // Proof: `BridgeWestendMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) // Storage: `BridgeWestendMessages::OutboundLanes` (r:1 w:1) @@ -341,27 +343,27 @@ impl WeightInfo { /// The range of component `x` is `[1, 1000]`. pub fn export_message(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `96` - // Estimated: `1529` - // Minimum execution time: 25_636_000 picoseconds. - Weight::from_parts(25_405_640, 1529) - // Standard Error: 321 - .saturating_add(Weight::from_parts(365_002, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `190` + // Estimated: `6130` + // Minimum execution time: 41_598_000 picoseconds. + Weight::from_parts(42_219_173, 6130) + // Standard Error: 426 + .saturating_add(Weight::from_parts(452_460, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_036_000 picoseconds. - Weight::from_parts(2_136_000, 0) + // Minimum execution time: 1_812_000 picoseconds. + Weight::from_parts(1_898_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_147_000 picoseconds. - Weight::from_parts(2_276_000, 0) + // Minimum execution time: 1_915_000 picoseconds. + Weight::from_parts(1_976_000, 0) } } 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 de7b5315c883d68e486f1de4d567a67dbcd05756..ac5c4afd52d8885b396cf0fd5ef5253ccf929fe5 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 @@ -19,8 +19,14 @@ use super::{ ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; -use crate::bridge_common_config::{ - BridgeGrandpaWestendInstance, DeliveryRewardInBalance, RequiredStakeForStakeAndSlash, +use crate::{ + bridge_common_config::{ + BridgeGrandpaRococoBulletinInstance, BridgeGrandpaWestendInstance, + BridgeParachainWestendInstance, DeliveryRewardInBalance, RequiredStakeForStakeAndSlash, + }, + bridge_to_bulletin_config::WithRococoBulletinMessagesInstance, + bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, + EthereumGatewayAddress, }; use bp_messages::LaneId; use bp_relayers::{PayRewardFromAccount, RewardsAccountOwner, RewardsAccountParams}; @@ -33,28 +39,34 @@ use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, - xcm_config::{ConcreteAssetFromSystem, RelayOrOtherSystemParachains}, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use rococo_runtime_constants::system_parachain; -use sp_core::Get; +use snowbridge_core::DescribeHere; +use snowbridge_rococo_common::EthereumNetwork; +use snowbridge_runtime_common::XcmExportFeeToSibling; +use sp_core::{Get, H256}; use sp_runtime::traits::AccountIdConversion; use sp_std::marker::PhantomData; use xcm::latest::prelude::*; +#[allow(deprecated)] use xcm_builder::{ deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, HandleFee, - IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, + DescribeFamily, EnsureXcmOrigin, HandleFee, HashedDescription, IsConcrete, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeToAccount, }; use xcm_executor::{ - traits::{FeeReason, TransactAsset, WithOriginFilter}, + traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset, WithOriginFilter}, XcmExecutor, }; @@ -68,6 +80,7 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); + pub SiblingPeople: MultiLocation = (Parent, Parachain(rococo_runtime_constants::system_parachain::PEOPLE_ID)).into(); } /// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used @@ -83,6 +96,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -125,10 +139,6 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; - pub type ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -153,7 +163,8 @@ impl Contains for SafeCallFilter { RuntimeCall::System(frame_system::Call::set_storage { items }) if items.iter().all(|(k, _)| { k.eq(&DeliveryRewardInBalance::key()) | - k.eq(&RequiredStakeForStakeAndSlash::key()) + k.eq(&RequiredStakeForStakeAndSlash::key()) | + k.eq(&EthereumGatewayAddress::key()) }) => return true, _ => (), @@ -168,25 +179,52 @@ impl Contains for SafeCallFilter { 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 { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::XcmpQueue(..) | RuntimeCall::MessageQueue(..) | RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< Runtime, BridgeGrandpaWestendInstance, - >::initialize { .. }) + >::initialize { .. }) | + RuntimeCall::BridgeWestendGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaWestendInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgeWestendParachains(pallet_bridge_parachains::Call::< + Runtime, + BridgeParachainWestendInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgeWestendMessages(pallet_bridge_messages::Call::< + Runtime, + WithBridgeHubWestendMessagesInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + >::initialize { .. }) | + RuntimeCall::BridgePolkadotBulletinGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgePolkadotBulletinMessages(pallet_bridge_messages::Call::< + Runtime, + WithRococoBulletinMessagesInstance, + >::set_operating_mode { .. }) | + RuntimeCall::EthereumBeaconClient( + snowbridge_ethereum_beacon_client::Call::force_checkpoint { .. } | + snowbridge_ethereum_beacon_client::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumInboundQueue( + snowbridge_inbound_queue::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumOutboundQueue( + snowbridge_outbound_queue::Call::set_operating_mode { .. }, + ) | RuntimeCall::EthereumSystem(..) ) } } @@ -204,14 +242,15 @@ pub type Barrier = TrailingSetTopicAsId< // If the message is one that immediately attempts to pay for execution, then // allow it. AllowTopLevelPaidExecutionFrom, - // Parent, its pluralities (i.e. governance bodies) and relay treasury pallet - // get free execution. + // Parent, its pluralities (i.e. governance bodies), relay treasury pallet + // and sibling People get free execution. AllowExplicitUnpaidExecutionFrom<( ParentOrParentsPlurality, Equals, + Equals, )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -220,25 +259,13 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::BRIDGE_HUB_ID | - system_parachain::CONTRACTS_ID | - system_parachain::ENCOINTER_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// - NativeToken with the parent Relay Chain and sibling parachains. @@ -271,7 +298,7 @@ impl xcm_executor::Config for XcmConfig { type SubscriptionService = PolkadotXcm; type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type FeeManager = XcmFeeManagerFromComponents< + type FeeManager = XcmFeeManagerFromComponentsBridgeHub< WaivedLocations, ( XcmExportFeeToRelayerRewardAccounts< @@ -281,10 +308,22 @@ impl xcm_executor::Config for XcmConfig { crate::bridge_to_westend_config::BridgeHubWestendChainId, crate::bridge_to_westend_config::AssetHubRococoToAssetHubWestendMessagesLane, >, + XcmExportFeeToSibling< + bp_rococo::Balance, + AccountId, + TokenLocation, + EthereumNetwork, + Self::AssetTransactor, + crate::EthereumOutboundQueue, + >, XcmFeeToAccount, ), >; - type MessageExporter = (crate::bridge_to_westend_config::ToBridgeHubWestendHaulBlobExporter,); + type MessageExporter = ( + crate::bridge_to_westend_config::ToBridgeHubWestendHaulBlobExporter, + crate::bridge_to_bulletin_config::ToRococoBulletinHaulBlobExporter, + crate::bridge_to_ethereum_config::SnowbridgeExporter, + ); type UniversalAliases = Nothing; type CallDispatcher = WithOriginFilter; type SafeCallFilter = SafeCallFilter; @@ -344,6 +383,10 @@ impl cumulus_pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; } +/// Creates an AgentId from a MultiLocation. An AgentId is a unique mapping to a Agent contract on +/// Ethereum which acts as the sovereign account for the MultiLocation. +pub type AgentIdOf = HashedDescription)>; + /// A `HandleFee` implementation that simply deposits the fees for `ExportMessage` XCM instructions /// into the accounts that are used for paying the relayer rewards. /// Burns the fees in case of a failure. @@ -436,3 +479,41 @@ impl< fee } } + +pub struct XcmFeeManagerFromComponentsBridgeHub( + PhantomData<(WaivedLocations, HandleFee)>, +); +impl, FeeHandler: HandleFee> FeeManager + for XcmFeeManagerFromComponentsBridgeHub +{ + fn is_waived(origin: Option<&MultiLocation>, fee_reason: FeeReason) -> bool { + let Some(loc) = origin else { return false }; + if let Export { network, destination: Here } = fee_reason { + return !(network == EthereumNetwork::get()) + } + WaivedLocations::contains(loc) + } + + fn handle_fee(fee: MultiAssets, context: Option<&XcmContext>, reason: FeeReason) { + FeeHandler::handle_fee(fee, context, reason); + } +} + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmark_helpers { + use crate::{MultiAssets, MultiLocation, SendError, SendResult, SendXcm, Xcm, XcmHash}; + + pub struct DoNothingRouter; + impl SendXcm for DoNothingRouter { + type Ticket = (); + fn validate( + _dest: &mut Option, + _msg: &mut Option>, + ) -> SendResult<()> { + Ok(((), MultiAssets::new())) + } + fn deliver(_: ()) -> Result { + Ok([0; 32]) + } + } +} 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 9597d71f6b279ec209069939c3b0d339d452faa9..0fba28c47b439a699e457e816b63ea4e76f0e0a2 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 @@ -18,16 +18,16 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ - bridge_common_config, bridge_to_westend_config, + bridge_common_config, bridge_to_bulletin_config, bridge_to_westend_config, xcm_config::{RelayNetwork, TokenLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, SignedExtra, - TransactionPayment, UncheckedExtrinsic, + AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, EthereumGatewayAddress, + Executive, ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, SessionKeys, SignedExtra, TransactionPayment, UncheckedExtrinsic, }; use codec::{Decode, Encode}; -use frame_support::{dispatch::GetDispatchInfo, parameter_types}; -use frame_system::pallet_prelude::HeaderFor; +use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; use parachains_common::{rococo::fee::WeightToFee, AccountId, AuraId, Balance}; +use sp_core::H160; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, @@ -35,9 +35,6 @@ use sp_runtime::{ }; use xcm::latest::prelude::*; -// Para id of sibling chain used in tests. -pub const SIBLING_PARACHAIN_ID: u32 = 1000; - parameter_types! { pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } @@ -46,23 +43,29 @@ fn construct_extrinsic( sender: sp_keyring::AccountKeyring, call: RuntimeCall, ) -> UncheckedExtrinsic { + let account_id = AccountId32::from(sender.public()); let extra: SignedExtra = ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(Era::immortal()), - frame_system::CheckNonce::::from(0), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&account_id).nonce, + ), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), - (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(), + ), ); let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap(); let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed( call, - AccountId32::from(sender.public()).into(), + account_id.into(), Signature::Sr25519(signature.clone()), extra, ) @@ -70,10 +73,9 @@ fn construct_extrinsic( fn construct_and_apply_extrinsic( relayer_at_target: sp_keyring::AccountKeyring, - batch: pallet_utility::Call, + call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { - let batch_call = RuntimeCall::Utility(batch); - let xt = construct_extrinsic(relayer_at_target, batch_call); + let xt = construct_extrinsic(relayer_at_target, call); let r = Executive::apply_extrinsic(xt); r.unwrap() } @@ -85,10 +87,6 @@ fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) } -fn executive_init_block(header: &HeaderFor) { - Executive::initialize_block(header) -} - fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { bridge_hub_test_utils::CollatorSessionKeys::new( AccountId::from(Alice), @@ -97,79 +95,131 @@ fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), + _ => None, + } + }), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID +); + +#[test] +fn change_required_stake_by_governance_works() { + bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridge_common_config::RequiredStakeForStakeAndSlash, + Balance, + >( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + ( + bridge_common_config::RequiredStakeForStakeAndSlash::key().to_vec(), + bridge_common_config::RequiredStakeForStakeAndSlash::get(), + ) + }, + |old_value| old_value.checked_mul(2).unwrap(), + ) +} + +mod bridge_hub_westend_tests { use super::*; use bridge_common_config::{ BridgeGrandpaWestendInstance, BridgeParachainWestendInstance, DeliveryRewardInBalance, - RequiredStakeForStakeAndSlash, }; + use bridge_hub_test_utils::test_cases::from_parachain; use bridge_to_westend_config::{ - BridgeHubWestendChainId, WestendGlobalConsensusNetwork, WithBridgeHubWestendMessageBridge, - WithBridgeHubWestendMessagesInstance, XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, + BridgeHubWestendChainId, BridgeHubWestendLocation, WestendGlobalConsensusNetwork, + WithBridgeHubWestendMessageBridge, WithBridgeHubWestendMessagesInstance, + XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, }; - bridge_hub_test_utils::test_cases::include_teleports_for_native_asset_works!( + // Para id of sibling chain used in tests. + pub const SIBLING_PARACHAIN_ID: u32 = 1000; + + // Runtime from tests PoV + type RuntimeTestsAdapter = from_parachain::WithRemoteParachainHelperAdapter< Runtime, AllPalletsWithoutSystem, - XcmConfig, - CheckingAccount, - WeightToFee, - ParachainSystem, - collator_session_keys(), - ExistentialDeposit::get(), - Box::new(|runtime_event_encoded: Vec| { - match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { - Ok(RuntimeEvent::PolkadotXcm(event)) => Some(event), - _ => None, - } - }), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID - ); + BridgeGrandpaWestendInstance, + BridgeParachainWestendInstance, + WithBridgeHubWestendMessagesInstance, + WithBridgeHubWestendMessageBridge, + >; #[test] fn initialize_bridge_by_governance_works() { - // for Westend finality + // for RococoBulletin finality bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::< Runtime, BridgeGrandpaWestendInstance, - >( - collator_session_keys(), - bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, - Box::new(|call| RuntimeCall::BridgeWestendGrandpa(call).encode()), - ) + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) } #[test] - fn change_delivery_reward_by_governance_works() { + fn change_bridge_grandpa_pallet_mode_by_governance_works() { + // for Westend finality + bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::< + Runtime, + BridgeGrandpaWestendInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn change_bridge_parachains_pallet_mode_by_governance_works() { + // for Westend finality + bridge_hub_test_utils::test_cases::change_bridge_parachains_pallet_mode_by_governance_works::< + Runtime, + BridgeParachainWestendInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn change_bridge_messages_pallet_mode_by_governance_works() { + // for Westend finality + bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::< + Runtime, + WithBridgeHubWestendMessagesInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn change_ethereum_gateway_by_governance_works() { bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< Runtime, - DeliveryRewardInBalance, - u64, + EthereumGatewayAddress, + H160, >( collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, Box::new(|call| RuntimeCall::System(call).encode()), - || (DeliveryRewardInBalance::key().to_vec(), DeliveryRewardInBalance::get()), - |old_value| old_value.checked_mul(2).unwrap(), + || (EthereumGatewayAddress::key().to_vec(), EthereumGatewayAddress::get()), + |_| [1; 20].into(), ) } #[test] - fn change_required_stake_by_governance_works() { + fn change_delivery_reward_by_governance_works() { bridge_hub_test_utils::test_cases::change_storage_constant_by_governance_works::< Runtime, - RequiredStakeForStakeAndSlash, - Balance, + DeliveryRewardInBalance, + u64, >( collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, Box::new(|call| RuntimeCall::System(call).encode()), - || { - ( - RequiredStakeForStakeAndSlash::key().to_vec(), - RequiredStakeForStakeAndSlash::get(), - ) - }, + || (DeliveryRewardInBalance::key().to_vec(), DeliveryRewardInBalance::get()), |old_value| old_value.checked_mul(2).unwrap(), ) } @@ -191,12 +241,12 @@ mod bridge_hub_rococo_tests { _ => None, } }), - || ExportMessage { network: Westend, destination: X1(Parachain(1234)), xcm: Xcm(vec![]) }, + || ExportMessage { network: Westend, destination: X1(Parachain(bridge_to_westend_config::AssetHubWestendParaId::get().into())), xcm: Xcm(vec![]) }, XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, Some((TokenLocation::get(), ExistentialDeposit::get()).into()), // value should be >= than value generated by `can_calculate_weight_for_paid_export_message_with_reserve_transfer` Some((TokenLocation::get(), bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get()).into()), - || (), + || PolkadotXcm::force_xcm_version(RuntimeOrigin::root(), Box::new(BridgeHubWestendLocation::get()), XCM_VERSION).expect("version saved!"), ) } @@ -211,6 +261,7 @@ mod bridge_hub_rococo_tests { WithBridgeHubWestendMessagesInstance, RelayNetwork, WestendGlobalConsensusNetwork, + ConstU8<2>, >( collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, @@ -235,39 +286,23 @@ mod bridge_hub_rococo_tests { #[test] fn relayed_incoming_message_works() { // from Westend - bridge_hub_test_utils::test_cases::relayed_incoming_message_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - BridgeGrandpaWestendInstance, - BridgeParachainWestendInstance, - WithBridgeHubWestendMessagesInstance, - WithBridgeHubWestendMessageBridge, - >( + from_parachain::relayed_incoming_message_works::( collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, + BridgeHubWestendChainId::get(), SIBLING_PARACHAIN_ID, Rococo, XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, || (), + construct_and_apply_extrinsic, ) } #[test] pub fn complex_relay_extrinsic_works() { // for Westend - bridge_hub_test_utils::test_cases::complex_relay_extrinsic_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - BridgeGrandpaWestendInstance, - BridgeParachainWestendInstance, - WithBridgeHubWestendMessagesInstance, - WithBridgeHubWestendMessageBridge, - >( + from_parachain::complex_relay_extrinsic_works::( collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, @@ -275,10 +310,8 @@ mod bridge_hub_rococo_tests { BridgeHubWestendChainId::get(), Rococo, XCM_LANE_FOR_ASSET_HUB_ROCOCO_TO_ASSET_HUB_WESTEND, - ExistentialDeposit::get(), - executive_init_block, - construct_and_apply_extrinsic, || (), + construct_and_apply_extrinsic, ); } @@ -302,16 +335,9 @@ mod bridge_hub_rococo_tests { #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_fee_for_complex_message_delivery_transaction::< - Runtime, - BridgeGrandpaWestendInstance, - BridgeParachainWestendInstance, - WithBridgeHubWestendMessagesInstance, - WithBridgeHubWestendMessageBridge, - >( - collator_session_keys(), - construct_and_estimate_extrinsic_fee - ); + 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(); @@ -325,16 +351,204 @@ mod bridge_hub_rococo_tests { #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = bridge_hub_test_utils::test_cases::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 + ); + } +} + +mod bridge_hub_bulletin_tests { + use super::*; + use bridge_common_config::BridgeGrandpaRococoBulletinInstance; + use bridge_hub_test_utils::test_cases::from_grandpa_chain; + use bridge_to_bulletin_config::{ + RococoBulletinChainId, RococoBulletinGlobalConsensusNetwork, + RococoBulletinGlobalConsensusNetworkLocation, WithRococoBulletinMessageBridge, + WithRococoBulletinMessagesInstance, XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + }; + + // Para id of sibling chain used in tests. + pub const SIBLING_PARACHAIN_ID: u32 = rococo_runtime_constants::system_parachain::PEOPLE_ID; + + // Runtime from tests PoV + type RuntimeTestsAdapter = from_grandpa_chain::WithRemoteGrandpaChainHelperAdapter< + Runtime, + AllPalletsWithoutSystem, + BridgeGrandpaRococoBulletinInstance, + WithRococoBulletinMessagesInstance, + WithRococoBulletinMessageBridge, + >; + + #[test] + fn initialize_bridge_by_governance_works() { + // for Bulletin finality + bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::< Runtime, - BridgeGrandpaWestendInstance, - BridgeParachainWestendInstance, - WithBridgeHubWestendMessagesInstance, - WithBridgeHubWestendMessageBridge, + BridgeGrandpaRococoBulletinInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn change_bridge_grandpa_pallet_mode_by_governance_works() { + // for Bulletin finality + bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::< + Runtime, + BridgeGrandpaRococoBulletinInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn change_bridge_messages_pallet_mode_by_governance_works() { + // for Bulletin finality + bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::< + Runtime, + WithRococoBulletinMessagesInstance, + >(collator_session_keys(), bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID) + } + + #[test] + fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { + // for Bulletin + bridge_hub_test_utils::test_cases::handle_export_message_from_system_parachain_to_outbound_queue_works::< + Runtime, + XcmConfig, + WithRococoBulletinMessagesInstance, + >( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + SIBLING_PARACHAIN_ID, + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::BridgePolkadotBulletinMessages(event)) => Some(event), + _ => None, + } + }), + || ExportMessage { + network: RococoBulletinGlobalConsensusNetwork::get(), + destination: Here, + xcm: Xcm(vec![]), + }, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + Some((TokenLocation::get(), ExistentialDeposit::get()).into()), + None, + || PolkadotXcm::force_xcm_version(RuntimeOrigin::root(), Box::new(RococoBulletinGlobalConsensusNetworkLocation::get()), XCM_VERSION).expect("version saved!"), + ) + } + + #[test] + fn message_dispatch_routing_works() { + // from Bulletin + bridge_hub_test_utils::test_cases::message_dispatch_routing_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ParachainSystem, + WithRococoBulletinMessagesInstance, + RelayNetwork, + RococoBulletinGlobalConsensusNetwork, + ConstU8<2>, >( collator_session_keys(), - construct_and_estimate_extrinsic_fee + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + SIBLING_PARACHAIN_ID, + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::ParachainSystem(event)) => Some(event), + _ => None, + } + }), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::XcmpQueue(event)) => Some(event), + _ => None, + } + }), + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), + ) + } + + #[test] + fn relayed_incoming_message_works() { + // from Bulletin + from_grandpa_chain::relayed_incoming_message_works::( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + RococoBulletinChainId::get(), + SIBLING_PARACHAIN_ID, + Rococo, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), + construct_and_apply_extrinsic, + ) + } + + #[test] + pub fn complex_relay_extrinsic_works() { + // for Bulletin + from_grandpa_chain::complex_relay_extrinsic_works::( + collator_session_keys(), + bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + SIBLING_PARACHAIN_ID, + RococoBulletinChainId::get(), + Rococo, + XCM_LANE_FOR_ROCOCO_PEOPLE_TO_ROCOCO_BULLETIN, + || (), + construct_and_apply_extrinsic, + ); + } + + #[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 ); + } + + #[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(); 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 c6ceb4b4c90bd09cc4ae9a7948c87becf6008e73..dd02faefc1c81434b891d51674d4358f638fdf19 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Westend's BridgeHub parachain runtime" license = "Apache-2.0" +[lints] +workspace = true + [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } @@ -14,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = hex-literal = { version = "0.4.1" } log = { version = "0.4.20", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } smallvec = "1.11.0" # Substrate @@ -74,6 +77,7 @@ parachain-info = { package = "staging-parachain-info", path = "../../../pallets/ parachains-common = { path = "../../../common", default-features = false } # Bridges +bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hub-rococo", default-features = false } 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 } @@ -89,7 +93,9 @@ pallet-bridge-grandpa = { path = "../../../../../bridges/modules/grandpa", defau pallet-bridge-messages = { path = "../../../../../bridges/modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../../../../bridges/modules/parachains", default-features = false } pallet-bridge-relayers = { path = "../../../../../bridges/modules/relayers", default-features = false } +pallet-xcm-bridge-hub = { path = "../../../../../bridges/modules/xcm-bridge-hub", default-features = false } bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", default-features = false } +bridge-hub-common = { path = "../../bridge-hubs/common", default-features = false } [dev-dependencies] static_assertions = "1.1" @@ -100,6 +106,7 @@ sp-keyring = { path = "../../../../../substrate/primitives/keyring" } [features] default = ["std"] std = [ + "bp-asset-hub-rococo/std", "bp-asset-hub-westend/std", "bp-bridge-hub-rococo/std", "bp-bridge-hub-westend/std", @@ -111,6 +118,7 @@ std = [ "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", "cumulus-pallet-aura-ext/std", @@ -144,6 +152,7 @@ std = [ "pallet-transaction-payment/std", "pallet-utility/std", "pallet-xcm-benchmarks?/std", + "pallet-xcm-bridge-hub/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", @@ -174,6 +183,7 @@ std = [ ] runtime-benchmarks = [ + "bridge-hub-common/runtime-benchmarks", "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", @@ -195,6 +205,7 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm-bridge-hub/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -227,6 +238,7 @@ try-runtime = [ "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", + "pallet-xcm-bridge-hub/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", "polkadot-runtime-common/try-runtime", 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 70ff43c09e3f22ba746966136aa457ebd9c82b4f..eb5493872b40917dd7edf36994df45864a14c48f 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 @@ -17,8 +17,9 @@ //! Bridge definitions used on BridgeHub with the Westend flavor. use crate::{ - bridge_common_config::DeliveryRewardInBalance, weights, AccountId, BridgeRococoMessages, - ParachainInfo, Runtime, RuntimeEvent, RuntimeOrigin, XcmRouter, + bridge_common_config::DeliveryRewardInBalance, weights, xcm_config::UniversalLocation, + AccountId, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeOrigin, + XcmOverBridgeHubRococo, XcmRouter, }; use bp_messages::LaneId; use bp_parachains::SingleParaStoredHeaderDataBuilder; @@ -31,7 +32,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::{ SenderAndLane, XcmAsPlainPayload, XcmBlobHauler, XcmBlobHaulerAdapter, - XcmBlobMessageDispatch, + XcmBlobMessageDispatch, XcmVersionOfDestAndRemoteBridge, }, refund_relayer_extension::{ ActualFeeRefund, RefundBridgedParachainMessages, RefundSignedExtensionAdapter, @@ -48,7 +49,7 @@ use xcm::{ latest::prelude::*, prelude::{InteriorMultiLocation, NetworkId}, }; -use xcm_builder::{BridgeBlobDispatcher, HaulBlobExporter}; +use xcm_builder::BridgeBlobDispatcher; parameter_types! { pub const RelayChainHeadersToKeep: u32 = 1024; @@ -62,24 +63,42 @@ parameter_types! { pub const MaxUnconfirmedMessagesAtInboundLane: bp_messages::MessageNonce = bp_bridge_hub_westend::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX; pub const BridgeHubRococoChainId: bp_runtime::ChainId = bp_runtime::BRIDGE_HUB_ROCOCO_CHAIN_ID; - pub BridgeHubWestendUniversalLocation: InteriorMultiLocation = X2(GlobalConsensus(Westend), Parachain(ParachainInfo::parachain_id().into())); pub BridgeWestendToRococoMessagesPalletInstance: InteriorMultiLocation = X1(PalletInstance(::index() as u8)); pub RococoGlobalConsensusNetwork: NetworkId = NetworkId::Rococo; - pub ActiveOutboundLanesToBridgeHubRococo: &'static [bp_messages::LaneId] = &[XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO]; - pub const AssetHubWestendToAssetHubRococoMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO; + pub RococoGlobalConsensusNetworkLocation: MultiLocation = MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(RococoGlobalConsensusNetwork::get())) + }; // see the `FEE_BOOST_PER_MESSAGE` constant to get the meaning of this value pub PriorityBoostPerMessage: u64 = 182_044_444_444_444; pub AssetHubWestendParaId: cumulus_primitives_core::ParaId = bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID.into(); + pub AssetHubRococoParaId: cumulus_primitives_core::ParaId = bp_asset_hub_rococo::ASSET_HUB_ROCOCO_PARACHAIN_ID.into(); + // Lanes + pub ActiveOutboundLanesToBridgeHubRococo: &'static [bp_messages::LaneId] = &[XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO]; + pub const AssetHubWestendToAssetHubRococoMessagesLane: bp_messages::LaneId = XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO; pub FromAssetHubWestendToAssetHubRococoRoute: SenderAndLane = SenderAndLane::new( ParentThen(X1(Parachain(AssetHubWestendParaId::get().into()))).into(), XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, ); + pub ActiveLanes: sp_std::vec::Vec<(SenderAndLane, (NetworkId, InteriorMultiLocation))> = sp_std::vec![ + ( + FromAssetHubWestendToAssetHubRococoRoute::get(), + (RococoGlobalConsensusNetwork::get(), X1(Parachain(AssetHubRococoParaId::get().into()))) + ) + ]; pub CongestedMessage: Xcm<()> = build_congestion_message(true).into(); - pub UncongestedMessage: Xcm<()> = build_congestion_message(false).into(); + + pub BridgeHubRococoLocation: MultiLocation = MultiLocation { + parents: 2, + interior: X2( + GlobalConsensus(RococoGlobalConsensusNetwork::get()), + Parachain(::PARACHAIN_ID) + ) + }; } pub const XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO: LaneId = LaneId([0, 0, 0, 2]); @@ -110,23 +129,16 @@ pub type ToRococoBridgeHubMessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof; /// Dispatches received XCM messages from other bridge -type FromRococoMessageBlobDispatcher = BridgeBlobDispatcher< - XcmRouter, - BridgeHubWestendUniversalLocation, - BridgeWestendToRococoMessagesPalletInstance, ->; +type FromRococoMessageBlobDispatcher = + BridgeBlobDispatcher; /// Export XCM messages to be relayed to the other side -pub type ToBridgeHubRococoHaulBlobExporter = HaulBlobExporter< - XcmBlobHaulerAdapter, - RococoGlobalConsensusNetwork, - (), ->; +pub type ToBridgeHubRococoHaulBlobExporter = XcmOverBridgeHubRococo; + pub struct ToBridgeHubRococoXcmBlobHauler; impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { type Runtime = Runtime; type MessagesInstance = WithBridgeHubRococoMessagesInstance; - type SenderAndLane = FromAssetHubWestendToAssetHubRococoRoute; type ToSourceChainSender = XcmRouter; type CongestedMessage = CongestedMessage; @@ -134,7 +146,7 @@ impl XcmBlobHauler for ToBridgeHubRococoXcmBlobHauler { } /// On messages delivered callback. -type OnMessagesDelivered = XcmBlobHaulerAdapter; +type OnMessagesDelivered = XcmBlobHaulerAdapter; /// Messaging Bridge configuration for BridgeHubWestend -> BridgeHubRococo pub struct WithBridgeHubRococoMessageBridge; @@ -256,6 +268,18 @@ impl pallet_bridge_messages::Config for Run type OnMessagesDelivered = OnMessagesDelivered; } +/// Add support for the export and dispatch of XCM programs. +pub type XcmOverBridgeHubRococoInstance = pallet_xcm_bridge_hub::Instance1; +impl pallet_xcm_bridge_hub::Config for Runtime { + type UniversalLocation = UniversalLocation; + type BridgedNetwork = RococoGlobalConsensusNetworkLocation; + type BridgeMessagesPalletInstance = WithBridgeHubRococoMessagesInstance; + type MessageExportPrice = (); + type DestinationVersion = XcmVersionOfDestAndRemoteBridge; + type Lanes = ActiveLanes; + type LanesSupport = ToBridgeHubRococoXcmBlobHauler; +} + #[cfg(test)] mod tests { use super::*; 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 39ce4e04ed17e2fbba7f30c4da4f3904832ae8ad..717cde6280dbf2f7ce714a279cb1a8df0b56b526 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 @@ -33,8 +33,7 @@ mod weights; pub mod xcm_config; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; -use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; -use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; +use cumulus_primitives_core::ParaId; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -49,6 +48,10 @@ use sp_std::prelude::*; use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use bridge_hub_common::{ + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + AggregateMessageOrigin, +}; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, @@ -172,10 +175,10 @@ 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_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 3, + transaction_version: 4, state_version: 1, }; @@ -328,9 +331,8 @@ impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_message_queue::WeightInfo; #[cfg(feature = "runtime-benchmarks")] - type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< - cumulus_primitives_core::AggregateMessageOrigin, - >; + type MessageProcessor = + pallet_message_queue::mock_helpers::NoopMessageProcessor; #[cfg(not(feature = "runtime-benchmarks"))] type MessageProcessor = xcm_builder::ProcessXcmMessage< AggregateMessageOrigin, @@ -488,6 +490,7 @@ construct_runtime!( BridgeRococoGrandpa: pallet_bridge_grandpa::::{Pallet, Call, Storage, Event, Config} = 42, BridgeRococoParachains: pallet_bridge_parachains::::{Pallet, Call, Storage, Event} = 43, BridgeRococoMessages: pallet_bridge_messages::::{Pallet, Call, Storage, Event, Config} = 44, + XcmOverBridgeHubRococo: pallet_xcm_bridge_hub::::{Pallet} = 45, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -813,6 +816,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on BH. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // BH only supports teleports to system parachain. + // Relay/native token can be teleported between BH and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } use xcm::latest::prelude::*; @@ -909,7 +924,28 @@ impl_runtime_apis! { fn export_message_origin_and_destination( ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { - Ok((WestendLocation::get(), NetworkId::Rococo, X1(Parachain(100)))) + // save XCM version for remote bridge hub + let _ = PolkadotXcm::force_xcm_version( + RuntimeOrigin::root(), + Box::new(bridge_to_rococo_config::BridgeHubRococoLocation::get()), + XCM_VERSION, + ).map_err(|e| { + log::error!( + "Failed to dispatch `force_xcm_version({:?}, {:?}, {:?})`, error: {:?}", + RuntimeOrigin::root(), + bridge_to_rococo_config::BridgeHubRococoLocation::get(), + XCM_VERSION, + e + ); + BenchmarkError::Stop("XcmVersion was not stored!") + })?; + Ok( + ( + bridge_to_rococo_config::FromAssetHubWestendToAssetHubRococoRoute::get().location, + NetworkId::Rococo, + X1(Parachain(bridge_to_rococo_config::AssetHubRococoParaId::get().into())) + ) + ) } fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs index 3dec4cc7f182c9aede28084122747dca63b24431..7db371d6af93068467cbeafaa454330325da7791 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system.rs @@ -152,4 +152,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index ee49c72ea5ffd98afbf6e93b6cb4aa587ac81bbe..a65ee31d3e55ff8135fdd7dec35120e0a463409b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -43,7 +43,6 @@ pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; use crate::Runtime; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs index b0634ff2ccf499687ed14b9a833a02ea29f38019..e87ed668dfc7acb1a92a7535d92392a272370277 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_grandpa.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_bridge_grandpa` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-13, 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("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,9 +33,9 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_bridge_grandpa -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,21 +62,17 @@ impl pallet_bridge_grandpa::WeightInfo for WeightInfo Weight { // Proof Size summary in bytes: - // Measured: `268 + p * (60 ±0)` + // Measured: `231 + p * (60 ±0)` // Estimated: `51735` - // Minimum execution time: 304_726_000 picoseconds. - Weight::from_parts(16_868_060, 0) + // Minimum execution time: 303_549_000 picoseconds. + Weight::from_parts(306_232_000, 0) .saturating_add(Weight::from_parts(0, 51735)) - // Standard Error: 2_802 - .saturating_add(Weight::from_parts(55_200_017, 0).saturating_mul(p.into())) - // Standard Error: 46_745 - .saturating_add(Weight::from_parts(2_689_151, 0).saturating_mul(v.into())) + // Standard Error: 4_641 + .saturating_add(Weight::from_parts(55_196_301, 0).saturating_mul(p.into())) + // Standard Error: 35_813 + .saturating_add(Weight::from_parts(70_584, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs index 5d229497f3eb477912dec9304dbca1aed38b7881..305a8726fa1bb67da8ac239d9f2b66e795582fe5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_messages.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_bridge_messages` //! //! 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: `[]` +//! DATE: 2023-12-13, 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("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,9 +33,9 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_bridge_messages -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,170 +48,170 @@ use core::marker::PhantomData; /// Weight functions for `pallet_bridge_messages`. pub struct WeightInfo(PhantomData); impl pallet_bridge_messages::WeightInfo for WeightInfo { - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_single_message_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `575` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 42_332_000 picoseconds. - Weight::from_parts(43_375_000, 0) + // Minimum execution time: 40_646_000 picoseconds. + Weight::from_parts(41_754_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_two_messages_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `575` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 53_139_000 picoseconds. - Weight::from_parts(54_236_000, 0) + // Minimum execution time: 50_898_000 picoseconds. + Weight::from_parts(52_743_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn receive_single_message_proof_with_outbound_lane_state() -> Weight { // Proof Size summary in bytes: - // Measured: `575` + // Measured: `502` // Estimated: `52645` - // Minimum execution time: 47_466_000 picoseconds. - Weight::from_parts(48_724_000, 0) + // Minimum execution time: 45_848_000 picoseconds. + Weight::from_parts(47_036_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) fn receive_single_message_proof_1_kb() -> Weight { // Proof Size summary in bytes: - // Measured: `543` + // Measured: `433` // Estimated: `52645` - // Minimum execution time: 40_962_000 picoseconds. - Weight::from_parts(42_002_000, 0) + // Minimum execution time: 39_085_000 picoseconds. + Weight::from_parts(41_623_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) fn receive_single_message_proof_16_kb() -> Weight { // Proof Size summary in bytes: - // Measured: `543` + // Measured: `433` // Estimated: `52645` - // Minimum execution time: 71_599_000 picoseconds. - Weight::from_parts(74_307_000, 0) + // Minimum execution time: 72_754_000 picoseconds. + Weight::from_parts(74_985_000, 0) .saturating_add(Weight::from_parts(0, 52645)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_single_message() -> Weight { // Proof Size summary in bytes: - // Measured: `414` - // Estimated: `3879` - // Minimum execution time: 31_206_000 picoseconds. - Weight::from_parts(32_045_000, 0) - .saturating_add(Weight::from_parts(0, 3879)) + // Measured: `337` + // Estimated: `3802` + // Minimum execution time: 31_479_000 picoseconds. + Weight::from_parts(32_280_000, 0) + .saturating_add(Weight::from_parts(0, 3802)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:1 w:1) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight { // Proof Size summary in bytes: - // Measured: `414` - // Estimated: `3879` - // Minimum execution time: 31_211_000 picoseconds. - Weight::from_parts(32_171_000, 0) - .saturating_add(Weight::from_parts(0, 3879)) + // Measured: `337` + // Estimated: `3802` + // Minimum execution time: 31_807_000 picoseconds. + Weight::from_parts(32_219_000, 0) + .saturating_add(Weight::from_parts(0, 3802)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::OutboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::OutboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Proof: UNKNOWN KEY `0x6e0a18b62a1de81c5f519181cc611e18` (r:1 w:0) /// Storage: `BridgeRelayers::RelayerRewards` (r:2 w:2) /// Proof: `BridgeRelayers::RelayerRewards` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight { // Proof Size summary in bytes: - // Measured: `414` + // Measured: `337` // Estimated: `6086` - // Minimum execution time: 33_790_000 picoseconds. - Weight::from_parts(34_708_000, 0) + // Minimum execution time: 36_450_000 picoseconds. + Weight::from_parts(37_288_000, 0) .saturating_add(Weight::from_parts(0, 6086)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `BridgeWestendToRococoMessages::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeWestendToRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:1 w:0) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// Storage: `BridgeWestendToRococoMessages::InboundLanes` (r:1 w:1) - /// Proof: `BridgeWestendToRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:1 w:0) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoMessages::InboundLanes` (r:1 w:1) + /// Proof: `BridgeRococoMessages::InboundLanes` (`max_values`: None, `max_size`: Some(49180), added: 51655, 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: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) @@ -227,18 +227,15 @@ impl pallet_bridge_messages::WeightInfo for WeightInfo< /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[128, 2048]`. - /// The range of component `i` is `[128, 2048]`. - /// The range of component `i` is `[128, 2048]`. - /// The range of component `i` is `[128, 2048]`. fn receive_single_message_proof_with_dispatch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `777` + // Measured: `633` // Estimated: `52645` - // Minimum execution time: 61_938_000 picoseconds. - Weight::from_parts(63_009_714, 0) + // Minimum execution time: 67_047_000 picoseconds. + Weight::from_parts(68_717_105, 0) .saturating_add(Weight::from_parts(0, 52645)) - // Standard Error: 23 - .saturating_add(Weight::from_parts(6_677, 0).saturating_mul(i.into())) + // Standard Error: 138 + .saturating_add(Weight::from_parts(8_056, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs index 81cb0a66b7d277731a5d87f386301058d2634588..9819bd4065411bec6799de3f2aa41c318f53a122 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_parachains.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_bridge_parachains` //! //! 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: `[]` +//! DATE: 2023-12-12, 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("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,9 +33,9 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_bridge_parachains -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,65 +48,63 @@ use core::marker::PhantomData; /// Weight functions for `pallet_bridge_parachains`. pub struct WeightInfo(PhantomData); impl pallet_bridge_parachains::WeightInfo for WeightInfo { - /// Storage: `BridgeRococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeRococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `BridgeRococoGrandpa::ImportedHeaders` (r:1 w:0) /// Proof: `BridgeRococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) - /// The range of component `p` is `[1, 2]`. - /// The range of component `p` is `[1, 2]`. + /// Storage: `BridgeRococoParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:0 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) /// The range of component `p` is `[1, 2]`. fn submit_parachain_heads_with_n_parachains(_p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `367` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 31_241_000 picoseconds. - Weight::from_parts(32_488_584, 0) + // Minimum execution time: 29_994_000 picoseconds. + Weight::from_parts(31_005_636, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `BridgeRococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeRococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `BridgeRococoGrandpa::ImportedHeaders` (r:1 w:0) /// Proof: `BridgeRococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:0 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) fn submit_parachain_heads_with_1kb_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `367` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 32_962_000 picoseconds. - Weight::from_parts(33_658_000, 0) + // Minimum execution time: 31_425_000 picoseconds. + Weight::from_parts(32_163_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `BridgeRococoParachain::PalletOperatingMode` (r:1 w:0) - /// Proof: `BridgeRococoParachain::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::PalletOperatingMode` (r:1 w:0) + /// Proof: `BridgeRococoParachains::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `BridgeRococoGrandpa::ImportedHeaders` (r:1 w:0) /// Proof: `BridgeRococoGrandpa::ImportedHeaders` (`max_values`: Some(1024), `max_size`: Some(68), added: 1553, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ParasInfo` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHashes` (r:1 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) - /// Storage: `BridgeRococoParachain::ImportedParaHeads` (r:0 w:1) - /// Proof: `BridgeRococoParachain::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ParasInfo` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ParasInfo` (`max_values`: Some(1), `max_size`: Some(60), added: 555, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHashes` (r:1 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHashes` (`max_values`: Some(64), `max_size`: Some(64), added: 1054, mode: `MaxEncodedLen`) + /// Storage: `BridgeRococoParachains::ImportedParaHeads` (r:0 w:1) + /// Proof: `BridgeRococoParachains::ImportedParaHeads` (`max_values`: Some(64), `max_size`: Some(196), added: 1186, mode: `MaxEncodedLen`) fn submit_parachain_heads_with_16kb_proof() -> Weight { // Proof Size summary in bytes: - // Measured: `367` + // Measured: `291` // Estimated: `2543` - // Minimum execution time: 62_685_000 picoseconds. - Weight::from_parts(64_589_000, 0) + // Minimum execution time: 60_062_000 picoseconds. + Weight::from_parts(61_201_000, 0) .saturating_add(Weight::from_parts(0, 2543)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_relayers.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_relayers.rs index fde670ab927ce8d64cb7d8a2146cd90954a8f903..ed96f0cd87c9e73ee8c842ab9f4f5d60bf81c2ac 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_relayers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_bridge_relayers.rs @@ -17,10 +17,10 @@ //! Autogenerated weights for `pallet_bridge_relayers` //! //! 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: `[]` +//! DATE: 2023-12-12, 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("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,9 +33,9 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_bridge_relayers -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +56,8 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `207` // Estimated: `3593` - // Minimum execution time: 45_338_000 picoseconds. - Weight::from_parts(45_836_000, 0) + // Minimum execution time: 45_732_000 picoseconds. + Weight::from_parts(46_282_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -72,8 +72,8 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `61` // Estimated: `4714` - // Minimum execution time: 23_561_000 picoseconds. - Weight::from_parts(24_012_000, 0) + // Minimum execution time: 22_934_000 picoseconds. + Weight::from_parts(23_531_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -86,8 +86,8 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `160` // Estimated: `4714` - // Minimum execution time: 25_133_000 picoseconds. - Weight::from_parts(25_728_000, 0) + // Minimum execution time: 25_187_000 picoseconds. + Weight::from_parts(25_679_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -102,8 +102,8 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `263` // Estimated: `4714` - // Minimum execution time: 27_356_000 picoseconds. - Weight::from_parts(27_828_000, 0) + // Minimum execution time: 27_015_000 picoseconds. + Weight::from_parts(27_608_000, 0) .saturating_add(Weight::from_parts(0, 4714)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -114,8 +114,8 @@ impl pallet_bridge_relayers::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `6` // Estimated: `3538` - // Minimum execution time: 2_955_000 picoseconds. - Weight::from_parts(3_084_000, 0) + // Minimum execution time: 5_207_000 picoseconds. + Weight::from_parts(5_394_000, 0) .saturating_add(Weight::from_parts(0, 3538)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs index 9f17d327024c4f792d0f61d4001067fece87f7f5..83e4260e77198355d23ea0c38481d7b8e68267c7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_xcm` //! //! 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: 2023-12-05, 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("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --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=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm +// --chain=bridge-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,6 +48,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + /// 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) @@ -62,24 +62,39 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 29_724_000 picoseconds. - Weight::from_parts(30_440_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 23_219_000 picoseconds. + Weight::from_parts(23_818_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .saturating_add(T::DbWeight::get().reads(6)) .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: `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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 26_779_000 picoseconds. - Weight::from_parts(27_249_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 90_120_000 picoseconds. + Weight::from_parts(92_545_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -91,6 +106,32 @@ 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: `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`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3593` + // Minimum execution time: 91_339_000 picoseconds. + Weight::from_parts(93_204_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -107,8 +148,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_170_000 picoseconds. - Weight::from_parts(9_629_000, 0) + // Minimum execution time: 6_976_000 picoseconds. + Weight::from_parts(7_284_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_769_000 picoseconds. - Weight::from_parts(2_933_000, 0) + // Minimum execution time: 2_044_000 picoseconds. + Weight::from_parts(2_223_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -127,6 +168,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// 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: `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) @@ -141,16 +184,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `75` - // Estimated: `3540` - // Minimum execution time: 34_547_000 picoseconds. - Weight::from_parts(35_653_000, 0) - .saturating_add(Weight::from_parts(0, 3540)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 27_778_000 picoseconds. + Weight::from_parts(28_318_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`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) @@ -165,12 +210,12 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `292` - // Estimated: `3757` - // Minimum execution time: 36_274_000 picoseconds. - Weight::from_parts(37_281_000, 0) - .saturating_add(Weight::from_parts(0, 3757)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 30_446_000 picoseconds. + Weight::from_parts(31_925_000, 0) + .saturating_add(Weight::from_parts(0, 3720)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) @@ -179,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_749_000 picoseconds. - Weight::from_parts(2_917_000, 0) + // Minimum execution time: 2_037_000 picoseconds. + Weight::from_parts(2_211_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -188,11 +233,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `187` - // Estimated: `11077` - // Minimum execution time: 17_649_000 picoseconds. - Weight::from_parts(17_964_000, 0) - .saturating_add(Weight::from_parts(0, 11077)) + // Measured: `95` + // Estimated: `10985` + // Minimum execution time: 15_620_000 picoseconds. + Weight::from_parts(15_984_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -200,11 +245,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `191` - // Estimated: `11081` - // Minimum execution time: 17_551_000 picoseconds. - Weight::from_parts(18_176_000, 0) - .saturating_add(Weight::from_parts(0, 11081)) + // Measured: `99` + // Estimated: `10989` + // Minimum execution time: 15_689_000 picoseconds. + Weight::from_parts(16_093_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -212,15 +257,17 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `198` - // Estimated: `13563` - // Minimum execution time: 19_261_000 picoseconds. - Weight::from_parts(19_714_000, 0) - .saturating_add(Weight::from_parts(0, 13563)) + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 16_946_000 picoseconds. + Weight::from_parts(17_192_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 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) @@ -233,39 +280,41 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `142` - // Estimated: `6082` - // Minimum execution time: 31_630_000 picoseconds. - Weight::from_parts(32_340_000, 0) - .saturating_add(Weight::from_parts(0, 6082)) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 27_164_000 picoseconds. + Weight::from_parts(27_760_000, 0) + .saturating_add(Weight::from_parts(0, 6046)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:3 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: `172` - // Estimated: `8587` - // Minimum execution time: 9_218_000 picoseconds. - Weight::from_parts(9_558_000, 0) - .saturating_add(Weight::from_parts(0, 8587)) + // Measured: `136` + // Estimated: `8551` + // Minimum execution time: 8_689_000 picoseconds. + Weight::from_parts(8_874_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) .saturating_add(T::DbWeight::get().reads(3)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 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: `198` - // Estimated: `11088` - // Minimum execution time: 18_133_000 picoseconds. - Weight::from_parts(18_663_000, 0) - .saturating_add(Weight::from_parts(0, 11088)) + // Measured: `106` + // Estimated: `10996` + // Minimum execution time: 15_944_000 picoseconds. + Weight::from_parts(16_381_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:2) /// 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) @@ -278,12 +327,12 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// 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: `204` - // Estimated: `11094` - // Minimum execution time: 38_878_000 picoseconds. - Weight::from_parts(39_779_000, 0) - .saturating_add(Weight::from_parts(0, 11094)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `112` + // Estimated: `11002` + // Minimum execution time: 33_826_000 picoseconds. + Weight::from_parts(34_784_000, 0) + .saturating_add(Weight::from_parts(0, 11002)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -294,8 +343,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 4_142_000 picoseconds. - Weight::from_parts(4_308_000, 0) + // Minimum execution time: 4_257_000 picoseconds. + Weight::from_parts(4_383_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -306,11 +355,10 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_814_000 picoseconds. - Weight::from_parts(26_213_000, 0) + // Minimum execution time: 26_924_000 picoseconds. + Weight::from_parts(27_455_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/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 7c686190208fd2fec4a00b7b34ef25038b4815b1..9281a880c7e1266d65d29436ca88e51e896c0363 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,10 +17,10 @@ //! 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: `[]` +//! DATE: 2023-12-12, 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("bridge-hub-rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain @@ -33,10 +33,10 @@ // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::generic -// --chain=bridge-hub-rococo-dev +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt // --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,8 +48,6 @@ use sp_std::marker::PhantomData; /// Weights for `pallet_xcm_benchmarks::generic`. pub struct WeightInfo(PhantomData); impl WeightInfo { - // Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - // Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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) @@ -68,81 +66,79 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `208` // Estimated: `6196` - // Minimum execution time: 62_732_000 picoseconds. - Weight::from_parts(64_581_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 61_577_000 picoseconds. + Weight::from_parts(63_216_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_987_000 picoseconds. - Weight::from_parts(2_107_000, 0) + // Minimum execution time: 2_019_000 picoseconds. + Weight::from_parts(2_146_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_098_000 picoseconds. - Weight::from_parts(8_564_000, 3568) + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 7_473_000 picoseconds. + Weight::from_parts(7_784_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: 8_539_000 picoseconds. - Weight::from_parts(9_085_000, 0) + // Minimum execution time: 8_385_000 picoseconds. + Weight::from_parts(8_768_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_205_000 picoseconds. - Weight::from_parts(2_369_000, 0) + // Minimum execution time: 2_181_000 picoseconds. + Weight::from_parts(2_304_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_828_000 picoseconds. - Weight::from_parts(1_994_000, 0) + // Minimum execution time: 1_858_000 picoseconds. + Weight::from_parts(1_919_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_869_000 picoseconds. - Weight::from_parts(1_946_000, 0) + // Minimum execution time: 1_855_000 picoseconds. + Weight::from_parts(1_979_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_842_000 picoseconds. - Weight::from_parts(1_949_000, 0) + // Minimum execution time: 1_823_000 picoseconds. + Weight::from_parts(1_890_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_460_000 picoseconds. - Weight::from_parts(2_593_000, 0) + // Minimum execution time: 2_407_000 picoseconds. + Weight::from_parts(2_507_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(2_003_000, 0) + // Minimum execution time: 1_838_000 picoseconds. + Weight::from_parts(1_894_000, 0) } - // Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - // Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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) @@ -161,21 +157,21 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `208` // Estimated: `6196` - // Minimum execution time: 56_813_000 picoseconds. - Weight::from_parts(57_728_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 54_847_000 picoseconds. + Weight::from_parts(55_742_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } // 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: 11_364_000 picoseconds. - Weight::from_parts(11_872_000, 3625) + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 10_614_000 picoseconds. + Weight::from_parts(11_344_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -183,8 +179,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_821_000 picoseconds. - Weight::from_parts(1_936_000, 0) + // Minimum execution time: 1_826_000 picoseconds. + Weight::from_parts(1_899_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -202,10 +198,10 @@ 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: `109` - // Estimated: `3574` - // Minimum execution time: 23_081_000 picoseconds. - Weight::from_parts(23_512_000, 3574) + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 22_312_000 picoseconds. + Weight::from_parts(22_607_000, 3503) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -215,47 +211,45 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_747_000 picoseconds. - Weight::from_parts(4_068_000, 0) + // Minimum execution time: 3_728_000 picoseconds. + Weight::from_parts(3_914_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: 3_045_000 picoseconds. - Weight::from_parts(3_208_000, 0) + // Minimum execution time: 3_054_000 picoseconds. + Weight::from_parts(3_140_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_962_000 picoseconds. - Weight::from_parts(2_284_000, 0) + // Minimum execution time: 1_996_000 picoseconds. + Weight::from_parts(2_148_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_951_000 picoseconds. - Weight::from_parts(2_026_000, 0) + // Minimum execution time: 2_008_000 picoseconds. + Weight::from_parts(2_077_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 1_837_000 picoseconds. - Weight::from_parts(2_084_000, 0) + Weight::from_parts(1_913_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_042_000 picoseconds. - Weight::from_parts(2_145_000, 0) + // Minimum execution time: 2_052_000 picoseconds. + Weight::from_parts(2_120_000, 0) } - // Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - // Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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) @@ -274,22 +268,20 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `208` // Estimated: `6196` - // Minimum execution time: 61_350_000 picoseconds. - Weight::from_parts(62_440_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 58_725_000 picoseconds. + Weight::from_parts(60_271_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_993_000 picoseconds. - Weight::from_parts(5_309_000, 0) + // Minimum execution time: 4_570_000 picoseconds. + Weight::from_parts(4_707_000, 0) } - // Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - // Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w: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) @@ -308,70 +300,70 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `242` + // Measured: `208` // Estimated: `6196` - // Minimum execution time: 57_133_000 picoseconds. - Weight::from_parts(58_100_000, 6196) - .saturating_add(T::DbWeight::get().reads(10)) + // Minimum execution time: 54_903_000 picoseconds. + Weight::from_parts(55_711_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_899_000 picoseconds. - Weight::from_parts(2_153_000, 0) + // Minimum execution time: 1_872_000 picoseconds. + Weight::from_parts(1_938_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_880_000 picoseconds. - Weight::from_parts(1_960_000, 0) + // Minimum execution time: 1_836_000 picoseconds. + Weight::from_parts(1_903_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_825_000 picoseconds. - Weight::from_parts(1_960_000, 0) + // Minimum execution time: 1_847_000 picoseconds. + Weight::from_parts(1_900_000, 0) } - // Storage: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) - // Proof: UNKNOWN KEY `0x48297505634037ef48c848c99c0b1f1b` (r:1 w:0) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `BridgeRococoToWococoMessages::PalletOperatingMode` (r:1 w:0) - // Proof: `BridgeRococoToWococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - // Storage: `BridgeRococoToWococoMessages::OutboundLanes` (r:1 w:1) - // Proof: `BridgeRococoToWococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) - // Storage: `BridgeRococoToWococoMessages::OutboundLanesCongestedSignals` (r:1 w:0) - // Proof: `BridgeRococoToWococoMessages::OutboundLanesCongestedSignals` (`max_values`: Some(1), `max_size`: Some(21), added: 516, mode: `MaxEncodedLen`) - // Storage: `BridgeRococoToWococoMessages::OutboundMessages` (r:0 w:1) - // Proof: `BridgeRococoToWococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(2621472), added: 2623947, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:2 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `BridgeRococoMessages::PalletOperatingMode` (r:1 w:0) + // Proof: `BridgeRococoMessages::PalletOperatingMode` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + // Storage: `BridgeRococoMessages::OutboundLanes` (r:1 w:1) + // Proof: `BridgeRococoMessages::OutboundLanes` (`max_values`: Some(1), `max_size`: Some(44), added: 539, mode: `MaxEncodedLen`) + // Storage: `BridgeRococoMessages::OutboundLanesCongestedSignals` (r:1 w:0) + // Proof: `BridgeRococoMessages::OutboundLanesCongestedSignals` (`max_values`: Some(1), `max_size`: Some(21), added: 516, mode: `MaxEncodedLen`) + // Storage: `BridgeRococoMessages::OutboundMessages` (r:0 w:1) + // Proof: `BridgeRococoMessages::OutboundMessages` (`max_values`: None, `max_size`: Some(2621472), added: 2623947, mode: `MaxEncodedLen`) /// The range of component `x` is `[1, 1000]`. pub fn export_message(x: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `139` - // Estimated: `3604` - // Minimum execution time: 28_419_000 picoseconds. - Weight::from_parts(29_387_791, 3604) - // Standard Error: 552 - .saturating_add(Weight::from_parts(316_277, 0).saturating_mul(x.into())) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `225` + // Estimated: `6165` + // Minimum execution time: 41_750_000 picoseconds. + Weight::from_parts(43_496_915, 6165) + // Standard Error: 623 + .saturating_add(Weight::from_parts(457_907, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_903_000 picoseconds. - Weight::from_parts(2_023_000, 0) + // Minimum execution time: 1_826_000 picoseconds. + Weight::from_parts(1_911_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_963_000 picoseconds. - Weight::from_parts(2_143_000, 0) + // Minimum execution time: 1_967_000 picoseconds. + Weight::from_parts(2_096_000, 0) } } 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 c89ee91c5e447db55841ca6c82816823bb82b106..397019190f3f167b9cbf68cb7886656b4db11c9e 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 @@ -28,19 +28,23 @@ use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, - xcm_config::{ConcreteAssetFromSystem, RelayOrOtherSystemParachains}, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; -use westend_runtime_constants::system_parachain; use xcm::latest::prelude::*; +#[allow(deprecated)] +use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, IsConcrete, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, @@ -72,6 +76,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -114,10 +119,6 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; - pub type ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -157,25 +158,32 @@ impl Contains for SafeCallFilter { 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 { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::XcmpQueue(..) | RuntimeCall::MessageQueue(..) | RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< Runtime, crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance, - >::initialize { .. }) + >::initialize { .. }) | + RuntimeCall::BridgeRococoGrandpa(pallet_bridge_grandpa::Call::< + Runtime, + crate::bridge_to_rococo_config::BridgeGrandpaRococoInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgeRococoParachains(pallet_bridge_parachains::Call::< + Runtime, + crate::bridge_to_rococo_config::BridgeParachainRococoInstance, + >::set_operating_mode { .. }) | + RuntimeCall::BridgeRococoMessages(pallet_bridge_messages::Call::< + Runtime, + crate::bridge_to_rococo_config::WithBridgeHubRococoMessagesInstance, + >::set_operating_mode { .. }) ) } } @@ -200,7 +208,7 @@ pub type Barrier = TrailingSetTopicAsId< Equals, )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -209,24 +217,13 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::BRIDGE_HUB_ID | - system_parachain::COLLECTIVES_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// - NativeToken with the parent Relay Chain and sibling parachains. 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 4d477e1413e4c2506b09a94d06960b09978eb390..0e58b7b408ebbb6b2e6261e343e9a56c85962ea7 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 @@ -18,21 +18,21 @@ use bp_polkadot_core::Signature; use bridge_common_config::{DeliveryRewardInBalance, RequiredStakeForStakeAndSlash}; +use bridge_hub_test_utils::test_cases::from_parachain; use bridge_hub_westend_runtime::{ bridge_common_config, bridge_to_rococo_config, xcm_config::{RelayNetwork, WestendLocation, XcmConfig}, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, SignedExtra, - TransactionPayment, UncheckedExtrinsic, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, + SignedExtra, TransactionPayment, UncheckedExtrinsic, }; use bridge_to_rococo_config::{ - BridgeGrandpaRococoInstance, BridgeHubRococoChainId, BridgeParachainRococoInstance, - WithBridgeHubRococoMessageBridge, WithBridgeHubRococoMessagesInstance, - XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, + BridgeGrandpaRococoInstance, BridgeHubRococoChainId, BridgeHubRococoLocation, + BridgeParachainRococoInstance, WithBridgeHubRococoMessageBridge, + WithBridgeHubRococoMessagesInstance, XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, }; use codec::{Decode, Encode}; -use frame_support::{dispatch::GetDispatchInfo, parameter_types}; -use frame_system::pallet_prelude::HeaderFor; +use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; use parachains_common::{westend::fee::WeightToFee, AccountId, AuraId, Balance}; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ @@ -44,6 +44,16 @@ use xcm::latest::prelude::*; // Para id of sibling chain used in tests. pub const SIBLING_PARACHAIN_ID: u32 = 1000; +// Runtime from tests PoV +type RuntimeTestsAdapter = from_parachain::WithRemoteParachainHelperAdapter< + Runtime, + AllPalletsWithoutSystem, + BridgeGrandpaRococoInstance, + BridgeParachainRococoInstance, + WithBridgeHubRococoMessagesInstance, + WithBridgeHubRococoMessageBridge, +>; + parameter_types! { pub CheckingAccount: AccountId = PolkadotXcm::check_account(); } @@ -52,13 +62,16 @@ fn construct_extrinsic( sender: sp_keyring::AccountKeyring, call: RuntimeCall, ) -> UncheckedExtrinsic { + let account_id = AccountId32::from(sender.public()); let extra: SignedExtra = ( frame_system::CheckNonZeroSender::::new(), frame_system::CheckSpecVersion::::new(), frame_system::CheckTxVersion::::new(), frame_system::CheckGenesis::::new(), frame_system::CheckEra::::from(Era::immortal()), - frame_system::CheckNonce::::from(0), + frame_system::CheckNonce::::from( + frame_system::Pallet::::account(&account_id).nonce, + ), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), BridgeRejectObsoleteHeadersAndMessages::default(), @@ -68,7 +81,7 @@ fn construct_extrinsic( let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed( call, - AccountId32::from(sender.public()).into(), + account_id.into(), Signature::Sr25519(signature.clone()), extra, ) @@ -76,10 +89,9 @@ fn construct_extrinsic( fn construct_and_apply_extrinsic( relayer_at_target: sp_keyring::AccountKeyring, - batch: pallet_utility::Call, + call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { - let batch_call = RuntimeCall::Utility(batch); - let xt = construct_extrinsic(relayer_at_target, batch_call); + let xt = construct_extrinsic(relayer_at_target, call); let r = Executive::apply_extrinsic(xt); r.unwrap() } @@ -91,10 +103,6 @@ fn construct_and_estimate_extrinsic_fee(batch: pallet_utility::Call) -> TransactionPayment::compute_fee(xt.encoded_size() as _, &batch_info, 0) } -fn executive_init_block(header: &HeaderFor) { - Executive::initialize_block(header) -} - fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys { bridge_hub_test_utils::CollatorSessionKeys::new( AccountId::from(Alice), @@ -126,11 +134,31 @@ fn initialize_bridge_by_governance_works() { bridge_hub_test_utils::test_cases::initialize_bridge_by_governance_works::< Runtime, BridgeGrandpaRococoInstance, - >( - collator_session_keys(), - bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, - Box::new(|call| RuntimeCall::BridgeRococoGrandpa(call).encode()), - ) + >(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID) +} + +#[test] +fn change_bridge_grandpa_pallet_mode_by_governance_works() { + bridge_hub_test_utils::test_cases::change_bridge_grandpa_pallet_mode_by_governance_works::< + Runtime, + BridgeGrandpaRococoInstance, + >(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID) +} + +#[test] +fn change_bridge_parachains_pallet_mode_by_governance_works() { + bridge_hub_test_utils::test_cases::change_bridge_parachains_pallet_mode_by_governance_works::< + Runtime, + BridgeParachainRococoInstance, + >(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID) +} + +#[test] +fn change_bridge_messages_pallet_mode_by_governance_works() { + bridge_hub_test_utils::test_cases::change_bridge_messages_pallet_mode_by_governance_works::< + Runtime, + WithBridgeHubRococoMessagesInstance, + >(collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID) } #[test] @@ -179,12 +207,12 @@ fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { _ => None, } }), - || ExportMessage { network: Rococo, destination: X1(Parachain(4321)), xcm: Xcm(vec![]) }, + || ExportMessage { network: Rococo, destination: X1(Parachain(bridge_to_rococo_config::AssetHubRococoParaId::get().into())), xcm: Xcm(vec![]) }, XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, Some((WestendLocation::get(), ExistentialDeposit::get()).into()), // value should be >= than value generated by `can_calculate_weight_for_paid_export_message_with_reserve_transfer` Some((WestendLocation::get(), bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get()).into()), - || (), + || PolkadotXcm::force_xcm_version(RuntimeOrigin::root(), Box::new(BridgeHubRococoLocation::get()), XCM_VERSION).expect("version saved!"), ) } @@ -198,6 +226,7 @@ fn message_dispatch_routing_works() { WithBridgeHubRococoMessagesInstance, RelayNetwork, bridge_to_rococo_config::RococoGlobalConsensusNetwork, + ConstU8<2>, >( collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, @@ -221,38 +250,22 @@ fn message_dispatch_routing_works() { #[test] fn relayed_incoming_message_works() { - bridge_hub_test_utils::test_cases::relayed_incoming_message_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - BridgeGrandpaRococoInstance, - BridgeParachainRococoInstance, - WithBridgeHubRococoMessagesInstance, - WithBridgeHubRococoMessageBridge, - >( + from_parachain::relayed_incoming_message_works::( collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, + BridgeHubRococoChainId::get(), SIBLING_PARACHAIN_ID, Westend, XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, || (), + construct_and_apply_extrinsic, ) } #[test] pub fn complex_relay_extrinsic_works() { - bridge_hub_test_utils::test_cases::complex_relay_extrinsic_works::< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - ParachainSystem, - BridgeGrandpaRococoInstance, - BridgeParachainRococoInstance, - WithBridgeHubRococoMessagesInstance, - WithBridgeHubRococoMessageBridge, - >( + from_parachain::complex_relay_extrinsic_works::( collator_session_keys(), bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID, bp_bridge_hub_rococo::BRIDGE_HUB_ROCOCO_PARACHAIN_ID, @@ -260,10 +273,8 @@ pub fn complex_relay_extrinsic_works() { BridgeHubRococoChainId::get(), Westend, XCM_LANE_FOR_ASSET_HUB_WESTEND_TO_ASSET_HUB_ROCOCO, - ExistentialDeposit::get(), - executive_init_block, - construct_and_apply_extrinsic, || (), + construct_and_apply_extrinsic, ); } @@ -287,16 +298,9 @@ pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_fee_for_complex_message_delivery_transaction::< - Runtime, - BridgeGrandpaRococoInstance, - BridgeParachainRococoInstance, - WithBridgeHubRococoMessagesInstance, - WithBridgeHubRococoMessageBridge, - >( - collator_session_keys(), - construct_and_estimate_extrinsic_fee - ); + 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(); @@ -310,16 +314,9 @@ pub fn can_calculate_fee_for_complex_message_delivery_transaction() { #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_fee_for_complex_message_confirmation_transaction::< - Runtime, - BridgeGrandpaRococoInstance, - BridgeParachainRococoInstance, - WithBridgeHubRococoMessagesInstance, - WithBridgeHubRococoMessageBridge, - >( - collator_session_keys(), - construct_and_estimate_extrinsic_fee - ); + 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(); diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0d75bb2213f8953af88ae20dadbba0858c98cf13 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "bridge-hub-common" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Bridge hub common utilities" +license = "Apache-2.0" + +[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"] } +frame-support = { path = "../../../../../substrate/frame/support", 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 } +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 } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-core/std", + "frame-support/std", + "pallet-message-queue/std", + "scale-info/std", + "snowbridge-core/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "xcm/std", +] + +runtime-benchmarks = [ + "cumulus-primitives-core/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdfcaedbe82daf89d015614c4c7aa0f4717efad5 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/digest_item.rs @@ -0,0 +1,34 @@ +// 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. +//! Custom digest items + +use codec::{Decode, Encode}; +use sp_core::{RuntimeDebug, H256}; +use sp_runtime::generic::DigestItem; + +/// Custom header digest items, inserted as DigestItem::Other +#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub enum CustomDigestItem { + #[codec(index = 0)] + /// Merkle root of outbound Snowbridge messages. + Snowbridge(H256), +} + +/// Convert custom application digest item into a concrete digest item +impl From for DigestItem { + fn from(val: CustomDigestItem) -> Self { + DigestItem::Other(val.encode()) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..aac6eb036526af4414b6f46b9cf8874a899072bb --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/lib.rs @@ -0,0 +1,21 @@ +// 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)] + +pub mod digest_item; +pub mod message_queue; + +pub use digest_item::CustomDigestItem; +pub use message_queue::{AggregateMessageOrigin, BridgeHubMessageRouter}; diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..651537ff8b719c7b47b1b2e85ef924c6bac49904 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/common/src/message_queue.rs @@ -0,0 +1,146 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! Runtime configuration for MessageQueue pallet +use codec::{Decode, Encode, MaxEncodedLen}; +use cumulus_primitives_core::{AggregateMessageOrigin as CumulusAggregateMessageOrigin, ParaId}; +use frame_support::{ + traits::{ProcessMessage, ProcessMessageError, QueueFootprint, QueuePausedQuery}, + weights::WeightMeter, +}; +use pallet_message_queue::OnQueueChanged; +use scale_info::TypeInfo; +use snowbridge_core::ChannelId; +use sp_std::{marker::PhantomData, prelude::*}; +use xcm::v3::{Junction, MultiLocation}; + +/// The aggregate origin of an inbound message. +/// This is specialized for BridgeHub, as the snowbridge-outbound-queue pallet is also using +/// the shared MessageQueue pallet. +#[derive(Encode, Decode, Copy, MaxEncodedLen, Clone, Eq, PartialEq, TypeInfo, Debug)] +pub enum AggregateMessageOrigin { + /// The message came from the para-chain itself. + Here, + /// The message came from the relay-chain. + /// + /// This is used by the DMP queue. + Parent, + /// The message came from a sibling para-chain. + /// + /// This is used by the HRMP queue. + Sibling(ParaId), + /// The message came from a snowbridge channel. + /// + /// This is used by Snowbridge inbound queue. + Snowbridge(ChannelId), +} + +impl From for MultiLocation { + fn from(origin: AggregateMessageOrigin) -> Self { + use AggregateMessageOrigin::*; + match origin { + Here => MultiLocation::here(), + Parent => MultiLocation::parent(), + Sibling(id) => MultiLocation::new(1, Junction::Parachain(id.into())), + // NOTE: We don't need this conversion for Snowbridge. However we have to + // implement it anyway as xcm_builder::ProcessXcmMessage requires it. + Snowbridge(_) => MultiLocation::default(), + } + } +} + +impl From for AggregateMessageOrigin { + fn from(origin: CumulusAggregateMessageOrigin) -> Self { + match origin { + CumulusAggregateMessageOrigin::Here => Self::Here, + CumulusAggregateMessageOrigin::Parent => Self::Parent, + CumulusAggregateMessageOrigin::Sibling(id) => Self::Sibling(id), + } + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl From for AggregateMessageOrigin { + fn from(x: u32) -> Self { + match x { + 0 => Self::Here, + 1 => Self::Parent, + p => Self::Sibling(ParaId::from(p)), + } + } +} + +/// Routes messages to either the XCMP or Snowbridge processor. +pub struct BridgeHubMessageRouter( + PhantomData<(XcmpProcessor, SnowbridgeProcessor)>, +) +where + XcmpProcessor: ProcessMessage, + SnowbridgeProcessor: ProcessMessage; + +impl ProcessMessage + for BridgeHubMessageRouter +where + XcmpProcessor: ProcessMessage, + SnowbridgeProcessor: ProcessMessage, +{ + type Origin = AggregateMessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result { + use AggregateMessageOrigin::*; + match origin { + Here | Parent | Sibling(_) => + XcmpProcessor::process_message(message, origin, meter, id), + Snowbridge(_) => SnowbridgeProcessor::process_message(message, origin, meter, id), + } + } +} + +/// Narrow the scope of the `Inner` query from `AggregateMessageOrigin` to `ParaId`. +/// +/// All non-`Sibling` variants will be ignored. +pub struct NarrowOriginToSibling(PhantomData); +impl> QueuePausedQuery + for NarrowOriginToSibling +{ + fn is_paused(origin: &AggregateMessageOrigin) -> bool { + match origin { + AggregateMessageOrigin::Sibling(id) => Inner::is_paused(id), + _ => false, + } + } +} + +impl> OnQueueChanged + for NarrowOriginToSibling +{ + fn on_queue_changed(origin: AggregateMessageOrigin, fp: QueueFootprint) { + if let AggregateMessageOrigin::Sibling(id) = origin { + Inner::on_queue_changed(id, fp) + } + } +} + +/// Convert a sibling `ParaId` to an `AggregateMessageOrigin`. +pub struct ParaIdToSibling; +impl sp_runtime::traits::Convert for ParaIdToSibling { + fn convert(para_id: ParaId) -> AggregateMessageOrigin { + AggregateMessageOrigin::Sibling(para_id) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 7325a87165c9d2e470c9d198d0fff6141abe4ee1..3049182cd4e269bba76786c3d18b355103f61d88 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -6,8 +6,12 @@ edition.workspace = true description = "Utils for BridgeHub testing" license = "Apache-2.0" +[lints] +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 } # Substrate @@ -19,6 +23,7 @@ sp-core = { path = "../../../../../substrate/primitives/core", default-features sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } sp-keyring = { path = "../../../../../substrate/primitives/keyring" } sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } sp-tracing = { path = "../../../../../substrate/primitives/tracing" } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } @@ -90,6 +95,7 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", + "sp-std/std", "xcm-builder/std", "xcm-executor/std", "xcm/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 26eb09b73fa6c9755ee613ae6bf4ff776c541ec6..445f001f1a4c111920fc513be95f88d73a25a634 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -17,5 +17,7 @@ //! Module contains predefined test-case scenarios for "BridgeHub" `Runtime`s. pub mod test_cases; +pub mod test_data; + pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs deleted file mode 100644 index 7a86d85c86fc26f0d5610700fa13735cb885f2cb..0000000000000000000000000000000000000000 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases.rs +++ /dev/null @@ -1,1564 +0,0 @@ -// 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 . - -//! Module contains predefined test-case scenarios for `Runtime` with bridging capabilities. - -use bp_messages::{ - source_chain::TargetHeaderChain, - target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch, SourceHeaderChain}, - LaneId, MessageKey, OutboundLaneData, UnrewardedRelayersState, Weight, -}; -use bp_parachains::{BestParaHeadHash, ParaInfo}; -use bp_polkadot_core::parachains::{ParaHash, ParaId}; -use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; -use bp_runtime::{HeaderOf, Parachain, StorageProofSize, UnderlyingChainOf}; -use bp_test_utils::{make_default_justification, prepare_parachain_heads_proof}; -use bridge_runtime_common::{ - messages::{ - source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, - BridgedChain as MessageBridgedChain, MessageBridge, - }, - messages_generation::{ - encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, - prepare_messages_storage_proof, - }, - messages_xcm_extension::{XcmAsPlainPayload, XcmBlobMessageDispatchResult}, -}; -use codec::Encode; -use frame_support::{ - assert_ok, - traits::{Get, OnFinalize, OnInitialize, OriginTrait, PalletInfoAccess}, -}; -use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; -use pallet_bridge_grandpa::BridgedHeader; -use parachains_common::AccountId; -use parachains_runtimes_test_utils::{ - mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf, - XcmReceivedFrom, -}; -use sp_core::H256; -use sp_keyring::AccountKeyring::*; -use sp_runtime::{ - traits::{Header as HeaderT, Zero}, - AccountId32, -}; -use xcm::latest::prelude::*; -use xcm_builder::DispatchBlobError; -use xcm_executor::{ - traits::{TransactAsset, WeightBounds}, - XcmExecutor, -}; - -// Re-export test_case from assets -pub use asset_test_utils::include_teleports_for_native_asset_works; - -type RuntimeHelper = - parachains_runtimes_test_utils::RuntimeHelper; - -// Re-export test_case from `parachains-runtimes-test-utils` -pub use parachains_runtimes_test_utils::test_cases::change_storage_constant_by_governance_works; - -/// Test-case makes sure that `Runtime` can process bridging initialize via governance-like call -pub fn initialize_bridge_by_governance_works( - collator_session_key: CollatorSessionKeys, - runtime_para_id: u32, - runtime_call_encode: Box< - dyn Fn(pallet_bridge_grandpa::Call) -> Vec, - >, -) where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + pallet_bridge_grandpa::Config, - GrandpaPalletInstance: 'static, - ValidatorIdOf: From>, -{ - 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(|| { - // check mode before - assert_eq!( - pallet_bridge_grandpa::PalletOperatingMode::::try_get(), - Err(()) - ); - - // encode `initialize` call - let initialize_call = runtime_call_encode(pallet_bridge_grandpa::Call::< - Runtime, - GrandpaPalletInstance, - >::initialize { - init_data: test_data::initialization_data::(12345), - }); - - // overestimate - check weight for `pallet_bridge_grandpa::Pallet::initialize()` call - let require_weight_at_most = - ::DbWeight::get().reads_writes(7, 7); - - // execute XCM with Transacts to `initialize bridge` as governance does - assert_ok!(RuntimeHelper::::execute_as_governance( - initialize_call, - require_weight_at_most - ) - .ensure_complete()); - - // check mode after - assert_eq!( - pallet_bridge_grandpa::PalletOperatingMode::::try_get(), - Ok(bp_runtime::BasicOperatingMode::Normal) - ); - }) -} - -/// Test-case makes sure that `Runtime` can handle xcm `ExportMessage`: -/// Checks if received XCM messages is correctly added to the message outbound queue for delivery. -/// For SystemParachains we expect unpaid execution. -pub fn handle_export_message_from_system_parachain_to_outbound_queue_works< - Runtime, - XcmConfig, - MessagesPalletInstance, ->( - collator_session_key: CollatorSessionKeys, - runtime_para_id: u32, - sibling_parachain_id: u32, - unwrap_pallet_bridge_messages_event: Box< - dyn Fn(Vec) -> Option>, - >, - export_message_instruction: fn() -> Instruction, - expected_lane_id: LaneId, - existential_deposit: Option, - maybe_paid_export_message: Option, - prepare_configuration: impl Fn(), -) where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + pallet_bridge_messages::Config, - XcmConfig: xcm_executor::Config, - MessagesPalletInstance: 'static, - ValidatorIdOf: From>, -{ - assert_ne!(runtime_para_id, sibling_parachain_id); - let sibling_parachain_location = MultiLocation::new(1, Parachain(sibling_parachain_id)); - - 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(|| { - prepare_configuration(); - - // check queue before - assert_eq!( - pallet_bridge_messages::OutboundLanes::::try_get( - expected_lane_id - ), - Err(()) - ); - - // prepare `ExportMessage` - let xcm = if let Some(fee) = maybe_paid_export_message { - // deposit ED to origin (if needed) - if let Some(ed) = existential_deposit { - XcmConfig::AssetTransactor::deposit_asset( - &ed, - &sibling_parachain_location, - Some(&XcmContext::with_message_id([0; 32])), - ) - .expect("deposited ed"); - } - // deposit fee to origin - XcmConfig::AssetTransactor::deposit_asset( - &fee, - &sibling_parachain_location, - Some(&XcmContext::with_message_id([0; 32])), - ) - .expect("deposited fee"); - - Xcm(vec![ - WithdrawAsset(MultiAssets::from(vec![fee.clone()])), - BuyExecution { fees: fee, weight_limit: Unlimited }, - export_message_instruction(), - ]) - } else { - Xcm(vec![ - UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - export_message_instruction(), - ]) - }; - - // execute XCM - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - assert_ok!(XcmExecutor::::execute_xcm( - sibling_parachain_location, - xcm, - hash, - RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), - ) - .ensure_complete()); - - // check queue after - assert_eq!( - pallet_bridge_messages::OutboundLanes::::try_get( - expected_lane_id - ), - Ok(OutboundLaneData { - oldest_unpruned_nonce: 1, - latest_received_nonce: 0, - latest_generated_nonce: 1, - }) - ); - - // check events - let mut events = >::events() - .into_iter() - .filter_map(|e| unwrap_pallet_bridge_messages_event(e.event.encode())); - assert!( - events.any(|e| matches!(e, pallet_bridge_messages::Event::MessageAccepted { .. })) - ); - }) -} - -/// Test-case makes sure that Runtime can route XCM messages received in inbound queue, -/// We just test here `MessageDispatch` configuration. -/// We expect that runtime can route messages: -/// 1. to Parent (relay chain) -/// 2. to Sibling parachain -pub fn message_dispatch_routing_works< - Runtime, - AllPalletsWithoutSystem, - XcmConfig, - HrmpChannelOpener, - MessagesPalletInstance, - RuntimeNetwork, - BridgedNetwork, ->( - collator_session_key: CollatorSessionKeys, - runtime_para_id: u32, - sibling_parachain_id: u32, - unwrap_cumulus_pallet_parachain_system_event: Box< - dyn Fn(Vec) -> Option>, - >, - unwrap_cumulus_pallet_xcmp_queue_event: Box< - dyn Fn(Vec) -> Option>, - >, - expected_lane_id: LaneId, - prepare_configuration: impl Fn(), -) where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config - + pallet_bridge_messages::Config, - AllPalletsWithoutSystem: - OnInitialize> + OnFinalize>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - XcmConfig: xcm_executor::Config, - MessagesPalletInstance: 'static, - ValidatorIdOf: From>, - ::AccountId: From, - HrmpChannelOpener: frame_support::inherent::ProvideInherent< - Call = cumulus_pallet_parachain_system::Call, - >, - // MessageDispatcher: MessageDispatch, DispatchLevelResult = - // XcmBlobMessageDispatchResult, DispatchPayload = XcmAsPlainPayload>, - RuntimeNetwork: Get, - BridgedNetwork: Get, -{ - assert_ne!(runtime_para_id, sibling_parachain_id); - - ExtBuilder::::default() - .with_collators(collator_session_key.collators()) - .with_session_keys(collator_session_key.session_keys()) - .with_safe_xcm_version(XCM_VERSION) - .with_para_id(runtime_para_id.into()) - .with_tracing() - .build() - .execute_with(|| { - prepare_configuration(); - - let mut alice = [0u8; 32]; - alice[0] = 1; - - let included_head = RuntimeHelper::::run_to_block( - 2, - AccountId::from(alice).into(), - ); - // 1. this message is sent from other global consensus with destination of this Runtime relay chain (UMP) - let bridging_message = - test_data::simulate_message_exporter_on_bridged_chain::( - (RuntimeNetwork::get(), Here) - ); - let result = <>::MessageDispatch>::dispatch( - test_data::dispatch_message(expected_lane_id, 1, bridging_message) - ); - assert_eq!(format!("{:?}", result.dispatch_level_result), format!("{:?}", XcmBlobMessageDispatchResult::Dispatched)); - - // check events - UpwardMessageSent - let mut events = >::events() - .into_iter() - .filter_map(|e| unwrap_cumulus_pallet_parachain_system_event(e.event.encode())); - assert!( - events.any(|e| matches!(e, cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. })) - ); - - // 2. this message is sent from other global consensus with destination of this Runtime sibling parachain (HRMP) - let bridging_message = - test_data::simulate_message_exporter_on_bridged_chain::( - (RuntimeNetwork::get(), X1(Parachain(sibling_parachain_id))), - ); - - // 2.1. WITHOUT opened hrmp channel -> RoutingError - let result = - <>::MessageDispatch>::dispatch( - DispatchMessage { - key: MessageKey { lane_id: expected_lane_id, nonce: 1 }, - data: DispatchMessageData { payload: Ok(bridging_message.clone()) }, - } - ); - assert_eq!(format!("{:?}", result.dispatch_level_result), format!("{:?}", XcmBlobMessageDispatchResult::NotDispatched(Some(DispatchBlobError::RoutingError)))); - - // check events - no XcmpMessageSent - assert_eq!(>::events() - .into_iter() - .filter_map(|e| unwrap_cumulus_pallet_xcmp_queue_event(e.event.encode())) - .count(), 0); - - // 2.1. WITH hrmp channel -> Ok - mock_open_hrmp_channel::(runtime_para_id.into(), sibling_parachain_id.into(), included_head, &alice); - let result = <>::MessageDispatch>::dispatch( - DispatchMessage { - key: MessageKey { lane_id: expected_lane_id, nonce: 1 }, - data: DispatchMessageData { payload: Ok(bridging_message) }, - } - ); - assert_eq!(format!("{:?}", result.dispatch_level_result), format!("{:?}", XcmBlobMessageDispatchResult::Dispatched)); - - // check events - XcmpMessageSent - let mut events = >::events() - .into_iter() - .filter_map(|e| unwrap_cumulus_pallet_xcmp_queue_event(e.event.encode())); - assert!( - events.any(|e| matches!(e, cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. })) - ); - }) -} - -/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, -/// with proofs (finality, para heads, message) independently submitted. -pub fn relayed_incoming_message_works( - collator_session_key: CollatorSessionKeys, - runtime_para_id: u32, - bridged_para_id: u32, - sibling_parachain_id: u32, - local_relay_chain_id: NetworkId, - lane_id: LaneId, - prepare_configuration: impl Fn(), -) where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config - + pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config, - AllPalletsWithoutSystem: OnInitialize> - + OnFinalize>, - GPI: 'static, - PPI: 'static, - MPI: 'static, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - XcmConfig: xcm_executor::Config, - HrmpChannelOpener: frame_support::inherent::ProvideInherent< - Call = cumulus_pallet_parachain_system::Call, - >, - ValidatorIdOf: From>, - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: From>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - ::AccountId: From, - AccountIdOf: From, - >::InboundRelayer: From, -{ - assert_ne!(runtime_para_id, sibling_parachain_id); - - ExtBuilder::::default() - .with_collators(collator_session_key.collators()) - .with_session_keys(collator_session_key.session_keys()) - .with_safe_xcm_version(XCM_VERSION) - .with_para_id(runtime_para_id.into()) - .with_tracing() - .build() - .execute_with(|| { - prepare_configuration(); - - let mut alice = [0u8; 32]; - alice[0] = 1; - - let included_head = RuntimeHelper::::run_to_block( - 2, - AccountId::from(alice).into(), - ); - mock_open_hrmp_channel::( - runtime_para_id.into(), - sibling_parachain_id.into(), - included_head, - &alice, - ); - - // start with bridged chain block#0 - let init_data = test_data::initialization_data::(0); - pallet_bridge_grandpa::Pallet::::initialize( - RuntimeHelper::::root_origin(), - init_data, - ) - .unwrap(); - - // set up relayer details and proofs - - let message_destination = - X2(GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)); - // some random numbers (checked by test) - let message_nonce = 1; - let para_header_number = 5; - let relay_header_number = 1; - - let relayer_at_target = Bob; - let relayer_id_on_target: AccountIdOf = relayer_at_target.public().into(); - let relayer_at_source = Dave; - let relayer_id_on_source: AccountId32 = relayer_at_source.public().into(); - - let xcm = vec![xcm::v3::Instruction::<()>::ClearOrigin; 42]; - let expected_dispatch = xcm::latest::Xcm::<()>({ - let mut expected_instructions = xcm.clone(); - // dispatch prepends bridge pallet instance - expected_instructions.insert( - 0, - DescendOrigin(X1(PalletInstance( - as PalletInfoAccess>::index() - as u8, - ))), - ); - expected_instructions - }); - // generate bridged relay chain finality, parachain heads and message proofs, - // to be submitted by relayer to this chain. - let ( - relay_chain_header, - grandpa_justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - message_proof, - ) = test_data::make_complex_relayer_delivery_proofs::< - >::BridgedChain, - MB, - (), - >( - lane_id, - xcm.into(), - message_nonce, - message_destination, - para_header_number, - relay_header_number, - bridged_para_id, - ); - - // submit bridged relay chain finality proof - { - let result = pallet_bridge_grandpa::Pallet::::submit_finality_proof( - RuntimeHelper::::origin_of(relayer_id_on_target.clone()), - Box::new(relay_chain_header.clone()), - grandpa_justification, - ); - assert_ok!(result); - assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); - } - - // verify finality proof correctly imported - assert_eq!( - pallet_bridge_grandpa::BestFinalized::::get().unwrap().1, - relay_chain_header.hash() - ); - assert!(pallet_bridge_grandpa::ImportedHeaders::::contains_key( - relay_chain_header.hash() - )); - - // submit parachain heads proof - { - let result = - pallet_bridge_parachains::Pallet::::submit_parachain_heads( - RuntimeHelper::::origin_of(relayer_id_on_target.clone()), - (relay_header_number, relay_chain_header.hash().into()), - parachain_heads, - para_heads_proof, - ); - assert_ok!(result); - assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); - } - // verify parachain head proof correctly imported - assert_eq!( - pallet_bridge_parachains::ParasInfo::::get(ParaId(bridged_para_id)), - Some(ParaInfo { - best_head_hash: BestParaHeadHash { - at_relay_block_number: relay_header_number, - head_hash: bridged_para_head.hash() - }, - next_imported_hash_position: 1, - }) - ); - - // import message - assert!(RuntimeHelper::>::take_xcm( - sibling_parachain_id.into() - ) - .is_none()); - assert_eq!( - pallet_bridge_messages::InboundLanes::::get(lane_id) - .last_delivered_nonce(), - 0, - ); - // submit message proof - { - let result = pallet_bridge_messages::Pallet::::receive_messages_proof( - RuntimeHelper::::origin_of(relayer_id_on_target), - relayer_id_on_source.into(), - message_proof.into(), - 1, - Weight::MAX / 1000, - ); - assert_ok!(result); - assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); - } - // verify message correctly imported and dispatched - assert_eq!( - pallet_bridge_messages::InboundLanes::::get(lane_id) - .last_delivered_nonce(), - 1, - ); - - // verify relayed bridged XCM message is dispatched to destination sibling para - let dispatched = RuntimeHelper::>::take_xcm( - sibling_parachain_id.into(), - ) - .unwrap(); - // verify contains original message - let dispatched = xcm::latest::Xcm::<()>::try_from(dispatched).unwrap(); - let mut dispatched_clone = dispatched.clone(); - for (idx, expected_instr) in expected_dispatch.0.iter().enumerate() { - assert_eq!(expected_instr, &dispatched.0[idx]); - assert_eq!(expected_instr, &dispatched_clone.0.remove(0)); - } - match dispatched_clone.0.len() { - 0 => (), - 1 => assert!(matches!(dispatched_clone.0[0], SetTopic(_))), - count => assert!(false, "Unexpected messages count: {:?}", count), - } - }) -} - -/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, -/// with proofs (finality, para heads, message) batched together in signed extrinsic. -/// Also verifies relayer transaction signed extensions work as intended. -pub fn complex_relay_extrinsic_works( - collator_session_key: CollatorSessionKeys, - runtime_para_id: u32, - bridged_para_id: u32, - sibling_parachain_id: u32, - bridged_chain_id: bp_runtime::ChainId, - local_relay_chain_id: NetworkId, - lane_id: LaneId, - existential_deposit: BalanceOf, - executive_init_block: fn(&HeaderFor), - construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, - pallet_utility::Call:: - ) -> sp_runtime::DispatchOutcome, - prepare_configuration: impl Fn(), -) where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_utility::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config - + pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config - + pallet_bridge_relayers::Config, - AllPalletsWithoutSystem: OnInitialize> - + OnFinalize>, - GPI: 'static, - PPI: 'static, - MPI: 'static, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - XcmConfig: xcm_executor::Config, - HrmpChannelOpener: frame_support::inherent::ProvideInherent< - Call = cumulus_pallet_parachain_system::Call, - >, - ValidatorIdOf: From>, - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: From>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - AccountIdOf: From, - ::AccountId: From, - >::InboundRelayer: From, - ::RuntimeCall: - From> - + From> - + From> -{ - assert_ne!(runtime_para_id, sibling_parachain_id); - - // Relayer account at local/this BH. - let relayer_at_target = Bob; - let relayer_id_on_target: AccountIdOf = relayer_at_target.public().into(); - let relayer_initial_balance = existential_deposit * 100000u32.into(); - // Relayer account at remote/bridged BH. - let relayer_at_source = Dave; - let relayer_id_on_source: AccountId32 = relayer_at_source.public().into(); - - ExtBuilder::::default() - .with_collators(collator_session_key.collators()) - .with_session_keys(collator_session_key.session_keys()) - .with_safe_xcm_version(XCM_VERSION) - .with_para_id(runtime_para_id.into()) - .with_balances(vec![(relayer_id_on_target.clone(), relayer_initial_balance)]) - .with_tracing() - .build() - .execute_with(|| { - prepare_configuration(); - - let mut alice = [0u8; 32]; - alice[0] = 1; - - let included_head = RuntimeHelper::::run_to_block( - 2, - AccountId::from(alice).into(), - ); - let zero: BlockNumberFor = 0u32.into(); - let genesis_hash = frame_system::Pallet::::block_hash(zero); - let mut header: HeaderFor = bp_test_utils::test_header(1u32.into()); - header.set_parent_hash(genesis_hash); - executive_init_block(&header); - - mock_open_hrmp_channel::( - runtime_para_id.into(), - sibling_parachain_id.into(), - included_head, - &alice, - ); - - // start with bridged chain block#0 - let init_data = test_data::initialization_data::(0); - pallet_bridge_grandpa::Pallet::::initialize( - RuntimeHelper::::root_origin(), - init_data, - ) - .unwrap(); - - // set up relayer details and proofs - - let message_destination = - X2(GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)); - // some random numbers (checked by test) - let message_nonce = 1; - let para_header_number = 5; - let relay_header_number = 1; - - let xcm = vec![xcm::latest::Instruction::<()>::ClearOrigin; 42]; - let expected_dispatch = xcm::latest::Xcm::<()>({ - let mut expected_instructions = xcm.clone(); - // dispatch prepends bridge pallet instance - expected_instructions.insert( - 0, - DescendOrigin(X1(PalletInstance( - as PalletInfoAccess>::index() - as u8, - ))), - ); - expected_instructions - }); - // generate bridged relay chain finality, parachain heads and message proofs, - // to be submitted by relayer to this chain. - let ( - relay_chain_header, - grandpa_justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - message_proof, - ) = test_data::make_complex_relayer_delivery_proofs::< - >::BridgedChain, - MB, - (), - >( - lane_id, - xcm.clone().into(), - message_nonce, - message_destination, - para_header_number, - relay_header_number, - bridged_para_id, - ); - - let relay_chain_header_hash = relay_chain_header.hash(); - let batch = test_data::make_complex_relayer_delivery_batch::( - relay_chain_header, - grandpa_justification, - parachain_heads, - para_heads_proof, - message_proof, - relayer_id_on_source, - ); - - // sanity checks - before relayer extrinsic - assert!(RuntimeHelper::>::take_xcm( - sibling_parachain_id.into() - ) - .is_none()); - assert_eq!( - pallet_bridge_messages::InboundLanes::::get(lane_id) - .last_delivered_nonce(), - 0, - ); - let msg_proofs_rewards_account = RewardsAccountParams::new( - lane_id, - bridged_chain_id, - RewardsAccountOwner::ThisChain, - ); - assert_eq!( - pallet_bridge_relayers::RelayerRewards::::get( - relayer_id_on_target.clone(), - msg_proofs_rewards_account - ), - None, - ); - - // construct and apply extrinsic containing batch calls: - // bridged relay chain finality proof - // + parachain heads proof - // + submit message proof - let dispatch_outcome = construct_and_apply_extrinsic(relayer_at_target, batch); - - // verify finality proof correctly imported - assert_ok!(dispatch_outcome); - assert_eq!( - >::get().unwrap().1, - relay_chain_header_hash - ); - assert!(>::contains_key( - relay_chain_header_hash - )); - // verify parachain head proof correctly imported - assert_eq!( - pallet_bridge_parachains::ParasInfo::::get(ParaId(bridged_para_id)), - Some(ParaInfo { - best_head_hash: BestParaHeadHash { - at_relay_block_number: relay_header_number, - head_hash: bridged_para_head.hash() - }, - next_imported_hash_position: 1, - }) - ); - // verify message correctly imported and dispatched - assert_eq!( - pallet_bridge_messages::InboundLanes::::get(lane_id) - .last_delivered_nonce(), - 1, - ); - // verify relayer is refunded - assert!(pallet_bridge_relayers::RelayerRewards::::get( - relayer_id_on_target, - msg_proofs_rewards_account - ) - .is_some()); - - // verify relayed bridged XCM message is dispatched to destination sibling para - let dispatched = RuntimeHelper::>::take_xcm( - sibling_parachain_id.into(), - ) - .unwrap(); - // verify contains original message - let dispatched = xcm::latest::Xcm::<()>::try_from(dispatched).unwrap(); - let mut dispatched_clone = dispatched.clone(); - for (idx, expected_instr) in expected_dispatch.0.iter().enumerate() { - assert_eq!(expected_instr, &dispatched.0[idx]); - assert_eq!(expected_instr, &dispatched_clone.0.remove(0)); - } - match dispatched_clone.0.len() { - 0 => (), - 1 => assert!(matches!(dispatched_clone.0[0], SetTopic(_))), - count => assert!(false, "Unexpected messages count: {:?}", count), - } - }) -} - -/// Estimates XCM execution fee for paid `ExportMessage` processing. -pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer< - Runtime, - XcmConfig, - WeightToFee, ->() -> u128 -where - Runtime: frame_system::Config + pallet_balances::Config, - XcmConfig: xcm_executor::Config, - WeightToFee: frame_support::weights::WeightToFee>, - ::Balance: From + Into, -{ - // data here are not relevant for weighing - let mut xcm = Xcm(vec![ - WithdrawAsset(MultiAssets::from(vec![MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(34333299), - }])), - BuyExecution { - fees: MultiAsset { - id: Concrete(MultiLocation { parents: 1, interior: Here }), - fun: Fungible(34333299), - }, - weight_limit: Unlimited, - }, - ExportMessage { - network: Polkadot, - destination: X1(Parachain(1000)), - xcm: Xcm(vec![ - ReserveAssetDeposited(MultiAssets::from(vec![MultiAsset { - id: Concrete(MultiLocation { - parents: 2, - interior: X1(GlobalConsensus(Kusama)), - }), - fun: Fungible(1000000000000), - }])), - ClearOrigin, - BuyExecution { - fees: MultiAsset { - id: Concrete(MultiLocation { - parents: 2, - interior: X1(GlobalConsensus(Kusama)), - }), - fun: Fungible(1000000000000), - }, - weight_limit: Unlimited, - }, - DepositAsset { - assets: Wild(AllCounted(1)), - beneficiary: MultiLocation { - parents: 0, - interior: X1(xcm::latest::prelude::AccountId32 { - network: None, - id: [ - 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, - 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, - 109, 162, 125, - ], - }), - }, - }, - SetTopic([ - 116, 82, 194, 132, 171, 114, 217, 165, 23, 37, 161, 177, 165, 179, 247, 114, - 137, 101, 147, 70, 28, 157, 168, 32, 154, 63, 74, 228, 152, 180, 5, 63, - ]), - ]), - }, - DepositAsset { - assets: Wild(All), - beneficiary: MultiLocation { parents: 1, interior: X1(Parachain(1000)) }, - }, - SetTopic([ - 36, 224, 250, 165, 82, 195, 67, 110, 160, 170, 140, 87, 217, 62, 201, 164, 42, 98, 219, - 157, 124, 105, 248, 25, 131, 218, 199, 36, 109, 173, 100, 122, - ]), - ]); - - // get weight - let weight = XcmConfig::Weigher::weight(&mut xcm); - assert_ok!(weight); - let weight = weight.unwrap(); - // check if sane - let max_expected = Runtime::BlockWeights::get().max_block / 10; - assert!( - weight.all_lte(max_expected), - "calculated weight: {:?}, max_expected: {:?}", - weight, - max_expected - ); - - // check fee, should not be 0 - 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() -} - -/// Estimates transaction fee for default message delivery transaction (batched with required -/// proofs) from bridged parachain. -pub fn can_calculate_fee_for_complex_message_delivery_transaction( - collator_session_key: CollatorSessionKeys, - compute_extrinsic_fee: fn(pallet_utility::Call::) -> u128, -) -> u128 -where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config - + pallet_utility::Config, - GPI: 'static, - PPI: 'static, - MPI: 'static, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - ValidatorIdOf: From>, - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: - From>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - ::AccountId: From, - AccountIdOf: From, - >::InboundRelayer: From, - ::RuntimeCall: - From> - + From> - + From>, -{ - ExtBuilder::::default() - .with_collators(collator_session_key.collators()) - .with_session_keys(collator_session_key.session_keys()) - .with_safe_xcm_version(XCM_VERSION) - .with_para_id(1000.into()) - .with_tracing() - .build() - .execute_with(|| { - // generate bridged relay chain finality, parachain heads and message proofs, - // to be submitted by relayer to this chain. - // - // we don't care about parameter values here, apart from the XCM message size. But we - // do not need to have a large message here, because we're charging for every byte of - // the message additionally - let ( - relay_chain_header, - grandpa_justification, - _, - parachain_heads, - para_heads_proof, - message_proof, - ) = test_data::make_complex_relayer_delivery_proofs::< - >::BridgedChain, - MB, - (), - >( - LaneId::default(), - vec![xcm::v3::Instruction::<()>::ClearOrigin; 1_024].into(), - 1, - X2(GlobalConsensus(Polkadot), Parachain(1_000)), - 1, - 5, - 1_000, - ); - - // generate batch call that provides finality for bridged relay and parachains + message - // proof - let batch = test_data::make_complex_relayer_delivery_batch::( - relay_chain_header, - grandpa_justification, - parachain_heads, - para_heads_proof, - message_proof, - Dave.public().into(), - ); - let estimated_fee = compute_extrinsic_fee(batch); - - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message delivery for runtime: {:?}", - estimated_fee, - Runtime::Version::get(), - ); - - estimated_fee - }) -} - -/// Estimates transaction fee for default message confirmation transaction (batched with required -/// proofs) from bridged parachain. -pub fn can_calculate_fee_for_complex_message_confirmation_transaction( - collator_session_key: CollatorSessionKeys, - compute_extrinsic_fee: fn(pallet_utility::Call::) -> u128, -) -> u128 -where - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config - + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config - + pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config - + pallet_utility::Config, - GPI: 'static, - PPI: 'static, - MPI: 'static, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - <::ThisChain as bp_runtime::Chain>::AccountId: From, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - ValidatorIdOf: From>, - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: - From>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - ::AccountId: - Into<<::RuntimeOrigin as OriginTrait>::AccountId>, - ::AccountId: From, - AccountIdOf: From, - >::InboundRelayer: From, - <>::TargetHeaderChain as TargetHeaderChain< - XcmAsPlainPayload, - Runtime::AccountId, - >>::MessagesDeliveryProof: From>, - ::RuntimeCall: - From> - + From> - + From>, -{ - ExtBuilder::::default() - .with_collators(collator_session_key.collators()) - .with_session_keys(collator_session_key.session_keys()) - .with_safe_xcm_version(XCM_VERSION) - .with_para_id(1000.into()) - .with_tracing() - .build() - .execute_with(|| { - // generate bridged relay chain finality, parachain heads and message proofs, - // to be submitted by relayer to this chain. - let unrewarded_relayers = UnrewardedRelayersState { - unrewarded_relayer_entries: 1, - total_messages: 1, - ..Default::default() - }; - let ( - relay_chain_header, - grandpa_justification, - _, - parachain_heads, - para_heads_proof, - message_delivery_proof, - ) = test_data::make_complex_relayer_confirmation_proofs::< - >::BridgedChain, - MB, - (), - >(LaneId::default(), 1, 5, 1_000, Alice.public().into(), unrewarded_relayers.clone()); - - // generate batch call that provides finality for bridged relay and parachains + message - // proof - let batch = test_data::make_complex_relayer_confirmation_batch::( - relay_chain_header, - grandpa_justification, - parachain_heads, - para_heads_proof, - 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, - Runtime::Version::get(), - ); - - estimated_fee - }) -} - -pub mod test_data { - use super::*; - use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa}; - use bp_messages::{DeliveredMessages, InboundLaneData, MessageNonce, UnrewardedRelayer}; - use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; - use bp_runtime::{BasicOperatingMode, HashOf}; - use bp_test_utils::authority_list; - use sp_runtime::{DigestItem, SaturatedConversion}; - use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter}; - use xcm_executor::traits::{validate_export, ExportXcm}; - - pub fn prepare_inbound_xcm( - xcm_message: Xcm, - destination: InteriorMultiLocation, - ) -> Vec { - let location = xcm::VersionedInteriorMultiLocation::V3(destination); - let xcm = xcm::VersionedXcm::::V3(xcm_message); - // this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor - // or public fields, so just tuple - // (double encoding, because `.encode()` is called on original Xcm BLOB when it is pushed - // to the storage) - (location, xcm).encode().encode() - } - - /// Prepare a batch call with relay finality proof, parachain head proof and message proof. - pub fn make_complex_relayer_delivery_batch( - relay_chain_header: BridgedHeader, - grandpa_justification: GrandpaJustification>, - parachain_heads: Vec<(ParaId, ParaHash)>, - para_heads_proof: ParaHeadsProof, - message_proof: FromBridgedChainMessagesProof, - relayer_id_at_bridged_chain: AccountId32, - ) -> pallet_utility::Call where - Runtime:pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config - + pallet_utility::Config, - GPI: 'static, - PPI: 'static, - MPI: 'static, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: - From>, - >::InboundRelayer: From, - ::RuntimeCall: - From> - + From> - + From>, - { - let relay_chain_header_hash = relay_chain_header.hash(); - let relay_chain_header_number = *relay_chain_header.number(); - let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { - finality_target: Box::new(relay_chain_header), - justification: grandpa_justification, - }; - let submit_para_head = - pallet_bridge_parachains::Call::::submit_parachain_heads { - at_relay_block: ( - relay_chain_header_number.saturated_into(), - relay_chain_header_hash.into(), - ), - parachains: parachain_heads, - parachain_heads_proof: para_heads_proof, - }; - let submit_message = pallet_bridge_messages::Call::::receive_messages_proof { - relayer_id_at_bridged_chain: relayer_id_at_bridged_chain.into(), - proof: message_proof.into(), - messages_count: 1, - dispatch_weight: Weight::from_parts(1000000000, 0), - }; - pallet_utility::Call::::batch_all { - calls: vec![submit_grandpa.into(), submit_para_head.into(), submit_message.into()], - } - } - - /// Prepare a batch call with relay finality proof, parachain head proof and message delivery - /// proof. - pub fn make_complex_relayer_confirmation_batch( - relay_chain_header: BridgedHeader, - grandpa_justification: GrandpaJustification>, - parachain_heads: Vec<(ParaId, ParaHash)>, - para_heads_proof: ParaHeadsProof, - message_delivery_proof: FromBridgedChainMessagesDeliveryProof, - relayers_state: UnrewardedRelayersState, - ) -> pallet_utility::Call where - Runtime:pallet_bridge_grandpa::Config - + pallet_bridge_parachains::Config - + pallet_bridge_messages::Config - + pallet_utility::Config, - GPI: 'static, - PPI: 'static, - MPI: 'static, - ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, - <>::BridgedChain as bp_runtime::Chain>::Hash: From, - <>::TargetHeaderChain as TargetHeaderChain< - XcmAsPlainPayload, - Runtime::AccountId, - >>::MessagesDeliveryProof: From>, - ::RuntimeCall: - From> - + From> - + From>, - { - let relay_chain_header_hash = relay_chain_header.hash(); - let relay_chain_header_number = *relay_chain_header.number(); - let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { - finality_target: Box::new(relay_chain_header), - justification: grandpa_justification, - }; - let submit_para_head = - pallet_bridge_parachains::Call::::submit_parachain_heads { - at_relay_block: ( - relay_chain_header_number.saturated_into(), - relay_chain_header_hash.into(), - ), - parachains: parachain_heads, - parachain_heads_proof: para_heads_proof, - }; - let submit_message_delivery_proof = - pallet_bridge_messages::Call::::receive_messages_delivery_proof { - proof: message_delivery_proof.into(), - relayers_state, - }; - pallet_utility::Call::::batch_all { - calls: vec![ - submit_grandpa.into(), - submit_para_head.into(), - submit_message_delivery_proof.into(), - ], - } - } - - /// Prepare storage proofs of messages, stored at the source chain. - pub fn make_complex_relayer_delivery_proofs( - lane_id: LaneId, - xcm_message: Xcm, - message_nonce: MessageNonce, - message_destination: Junctions, - para_header_number: u32, - relay_header_number: u32, - bridged_para_id: u32, - ) -> ( - HeaderOf, - GrandpaJustification>, - ParaHead, - Vec<(ParaId, ParaHash)>, - ParaHeadsProof, - FromBridgedChainMessagesProof, - ) - where - BridgedRelayChain: ChainWithGrandpa, - HashOf: From, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - { - let message_payload = prepare_inbound_xcm(xcm_message, message_destination); - let message_size = StorageProofSize::Minimal(message_payload.len() as u32); - // prepare para storage proof containing message - let (para_state_root, para_storage_proof) = prepare_messages_storage_proof::( - lane_id, - message_nonce..=message_nonce, - None, - message_size, - message_payload, - encode_all_messages, - encode_lane_data, - ); - - let ( - relay_chain_header, - justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - ) = make_complex_bridged_heads_proof::( - para_state_root, - para_header_number, - relay_header_number, - bridged_para_id, - ); - - let message_proof = FromBridgedChainMessagesProof { - bridged_header_hash: bridged_para_head.hash(), - storage_proof: para_storage_proof, - lane: lane_id, - nonces_start: message_nonce, - nonces_end: message_nonce, - }; - - ( - relay_chain_header, - justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - message_proof, - ) - } - - /// Prepare storage proofs of message confirmations, stored at the target chain. - pub fn make_complex_relayer_confirmation_proofs( - lane_id: LaneId, - para_header_number: u32, - relay_header_number: u32, - bridged_para_id: u32, - relayer_id_at_this_chain: AccountId32, - relayers_state: UnrewardedRelayersState, - ) -> ( - HeaderOf, - GrandpaJustification>, - ParaHead, - Vec<(ParaId, ParaHash)>, - ParaHeadsProof, - FromBridgedChainMessagesDeliveryProof, - ) - where - BridgedRelayChain: ChainWithGrandpa, - HashOf: From, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - <::ThisChain as bp_runtime::Chain>::AccountId: From, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - { - // prepare para storage proof containing message delivery proof - let (para_state_root, para_storage_proof) = prepare_message_delivery_storage_proof::( - lane_id, - InboundLaneData { - relayers: vec![ - UnrewardedRelayer { - relayer: relayer_id_at_this_chain.into(), - messages: DeliveredMessages::new(1) - }; - relayers_state.unrewarded_relayer_entries as usize - ] - .into(), - last_confirmed_nonce: 1, - }, - StorageProofSize::Minimal(0), - ); - - let ( - relay_chain_header, - justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - ) = make_complex_bridged_heads_proof::( - para_state_root, - para_header_number, - relay_header_number, - bridged_para_id, - ); - - let message_delivery_proof = FromBridgedChainMessagesDeliveryProof { - bridged_header_hash: bridged_para_head.hash(), - storage_proof: para_storage_proof, - lane: lane_id, - }; - - ( - relay_chain_header, - justification, - bridged_para_head, - parachain_heads, - para_heads_proof, - message_delivery_proof, - ) - } - - /// Make bridged parachain header with given state root and relay header that is finalizing it. - pub fn make_complex_bridged_heads_proof( - para_state_root: ParaHash, - para_header_number: u32, - relay_header_number: u32, - bridged_para_id: u32, - ) -> ( - HeaderOf, - GrandpaJustification>, - ParaHead, - Vec<(ParaId, ParaHash)>, - ParaHeadsProof, - ) - where - BridgedRelayChain: ChainWithGrandpa, - HashOf: From, - MB: MessageBridge, - ::BridgedChain: Send + Sync + 'static, - ::ThisChain: Send + Sync + 'static, - UnderlyingChainOf>: bp_runtime::Chain + Parachain, - { - let bridged_para_head = ParaHead( - bp_test_utils::test_header_with_root::>( - para_header_number.into(), - para_state_root.into(), - ) - .encode(), - ); - let (relay_state_root, para_heads_proof, parachain_heads) = - prepare_parachain_heads_proof::>(vec![( - bridged_para_id, - bridged_para_head.clone(), - )]); - assert_eq!(bridged_para_head.hash(), parachain_heads[0].1); - - // import bridged relay chain block#1 with state root containing head#5 of bridged parachain - let mut relay_chain_header: BridgedRelayChain::Header = - bp_test_utils::test_header_with_root( - relay_header_number.into(), - relay_state_root.into(), - ); - // to compute proper cost of GRANDPA call, let's add some dummy bytes to header, so that the - // `submit_finality_proof` call size would be close to maximal expected (and refundable) - let expected_bytes_in_grandpa_call = BridgedRelayChain::AVERAGE_HEADER_SIZE_IN_JUSTIFICATION - .saturating_mul(BridgedRelayChain::REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY) - .saturating_add(BridgedRelayChain::MAX_HEADER_SIZE) - as usize; - let extra_bytes_required = - expected_bytes_in_grandpa_call.saturating_sub(relay_chain_header.encoded_size()); - relay_chain_header - .digest_mut() - .push(DigestItem::Other(vec![42; extra_bytes_required])); - - let justification = make_default_justification(&relay_chain_header); - (relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof) - } - - /// Helper that creates InitializationData mock data, that can be used to initialize bridge - /// GRANDPA pallet - pub fn initialization_data< - Runtime: pallet_bridge_grandpa::Config, - GrandpaPalletInstance: 'static, - >( - block_number: u32, - ) -> bp_header_chain::InitializationData> { - bp_header_chain::InitializationData { - header: Box::new(bp_test_utils::test_header(block_number.into())), - authority_list: authority_list(), - set_id: 1, - operating_mode: BasicOperatingMode::Normal, - } - } - - /// Dummy xcm - pub(crate) fn dummy_xcm() -> Xcm<()> { - vec![Trap(42)].into() - } - - pub(crate) fn dispatch_message( - lane_id: LaneId, - nonce: MessageNonce, - payload: Vec, - ) -> DispatchMessage> { - DispatchMessage { - key: MessageKey { lane_id, nonce }, - data: DispatchMessageData { payload: Ok(payload) }, - } - } - - /// Macro used for simulate_export_message and capturing bytes - macro_rules! grab_haul_blob ( - ($name:ident, $grabbed_payload:ident) => { - std::thread_local! { - static $grabbed_payload: std::cell::RefCell>> = std::cell::RefCell::new(None); - } - - struct $name; - impl HaulBlob for $name { - fn haul_blob(blob: Vec) -> Result<(), HaulBlobError>{ - $grabbed_payload.with(|rm| *rm.borrow_mut() = Some(blob)); - Ok(()) - } - } - } - ); - - /// Simulates `HaulBlobExporter` and all its wrapping and captures generated plain bytes, - /// which are transferred over bridge. - pub(crate) fn simulate_message_exporter_on_bridged_chain< - SourceNetwork: Get, - DestinationNetwork: Get, - >( - (destination_network, destination_junctions): (NetworkId, Junctions), - ) -> Vec { - grab_haul_blob!(GrabbingHaulBlob, GRABBED_HAUL_BLOB_PAYLOAD); - - // lets pretend that some parachain on bridged chain exported the message - let universal_source_on_bridged_chain = - X2(GlobalConsensus(SourceNetwork::get()), Parachain(5678)); - let channel = 1_u32; - - // simulate XCM message export - let (ticket, fee) = - validate_export::>( - destination_network, - channel, - universal_source_on_bridged_chain, - destination_junctions, - dummy_xcm(), - ) - .expect("validate_export to pass"); - log::info!( - target: "simulate_message_exporter_on_bridged_chain", - "HaulBlobExporter::validate fee: {:?}", - fee - ); - let xcm_hash = - HaulBlobExporter::::deliver(ticket) - .expect("deliver to pass"); - log::info!( - target: "simulate_message_exporter_on_bridged_chain", - "HaulBlobExporter::deliver xcm_hash: {:?}", - xcm_hash - ); - - GRABBED_HAUL_BLOB_PAYLOAD.with(|r| r.take().expect("Encoded message should be here")) - } -} 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 new file mode 100644 index 0000000000000000000000000000000000000000..e0e75f093cfc65c0c6c58243f3483db1a252518e --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -0,0 +1,437 @@ +// 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 . + +//! Module contains predefined test-case scenarios for `Runtime` with bridging capabilities +//! with remote GRANDPA chain. + +use crate::{ + test_cases::{bridges_prelude::*, helpers, run_test}, + test_data, +}; + +use bp_header_chain::ChainWithGrandpa; +use bp_messages::{ + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, LaneId, + UnrewardedRelayersState, +}; +use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::{HashOf, UnderlyingChainOf}; +use bridge_runtime_common::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + BridgedChain as MessageBridgedChain, MessageBridge, ThisChain as MessageThisChain, + }, + messages_xcm_extension::XcmAsPlainPayload, +}; +use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_runtimes_test_utils::{ + AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, +}; +use sp_keyring::AccountKeyring::*; +use sp_runtime::{traits::Header as HeaderT, AccountId32}; +use xcm::latest::prelude::*; + +/// Helper trait to test bridges with remote GRANDPA chain. +/// +/// This is only used to decrease amount of lines, dedicated to bounds. +pub trait WithRemoteGrandpaChainHelper { + /// This chain runtime. + type Runtime: BasicParachainRuntime + + cumulus_pallet_xcmp_queue::Config + + BridgeGrandpaConfig< + Self::GPI, + BridgedChain = UnderlyingChainOf>, + > + BridgeMessagesConfig< + Self::MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = bp_runtime::AccountIdOf>, + OutboundPayload = XcmAsPlainPayload, + > + pallet_bridge_relayers::Config; + /// All pallets of this chain, excluding system pallet. + type AllPalletsWithoutSystem: OnInitialize> + + OnFinalize>; + /// Instance of the `pallet-bridge-grandpa`, used to bridge with remote GRANDPA chain. + type GPI: 'static; + /// Instance of the `pallet-bridge-messages`, used to bridge with remote GRANDPA chain. + type MPI: 'static; + /// Messages bridge definition. + type MB: MessageBridge; +} + +/// Adapter struct that implements [`WithRemoteGrandpaChainHelper`]. +pub struct WithRemoteGrandpaChainHelperAdapter( + sp_std::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, MPI, MB)>, +); + +impl WithRemoteGrandpaChainHelper + for WithRemoteGrandpaChainHelperAdapter +where + Runtime: BasicParachainRuntime + + cumulus_pallet_xcmp_queue::Config + + BridgeGrandpaConfig>> + + BridgeMessagesConfig< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = bp_runtime::AccountIdOf>, + OutboundPayload = XcmAsPlainPayload, + > + pallet_bridge_relayers::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + GPI: 'static, + MPI: 'static, + MB: MessageBridge, +{ + type Runtime = Runtime; + type AllPalletsWithoutSystem = AllPalletsWithoutSystem; + type GPI = GPI; + type MPI = MPI; + type MB = MB; +} + +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, message) independently submitted. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn relayed_incoming_message_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + bridged_chain_id: bp_runtime::ChainId, + sibling_parachain_id: u32, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + RuntimeCallOf, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteGrandpaChainHelper, + AccountIdOf: From, + RuntimeCallOf: From> + + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + let relay_header_number = 5u32.into(); + + prepare_configuration(); + + // start with bridged relay chain block#0 + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::(0), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let (relay_chain_header, grandpa_justification, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number); + + let relay_chain_header_hash = relay_chain_header.hash(); + vec![ + ( + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + helpers::VerifySubmitGrandpaFinalityProofOutcome::::expect_best_header_hash( + relay_chain_header_hash, + ), + ), + ( + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + Box::new(( + helpers::VerifySubmitMessagesProofOutcome::::expect_last_delivered_nonce( + lane_id, + 1, + ), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + ), + ] + }, + ); +} + +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, message) batched together in signed extrinsic. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn complex_relay_extrinsic_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + sibling_parachain_id: u32, + bridged_chain_id: bp_runtime::ChainId, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + RuntimeCallOf, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteGrandpaChainHelper, + RuntimeHelper::Runtime: + pallet_utility::Config>, + AccountIdOf: From, + RuntimeCallOf: From> + + From> + + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + let relay_header_number = 1u32.into(); + + prepare_configuration(); + + // start with bridged relay chain block#0 + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::(0), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let (relay_chain_header, grandpa_justification, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >(lane_id, xcm.into(), message_nonce, message_destination, relay_header_number); + + let relay_chain_header_hash = relay_chain_header.hash(); + vec![( + pallet_utility::Call::::batch_all { + calls: vec![ + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + ], + } + .into(), + Box::new(( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + )] + }, + ); +} + +/// Estimates transaction fee for default message delivery transaction (batched with required +/// proofs) from bridged GRANDPA chain. +pub fn can_calculate_fee_for_complex_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn(pallet_utility::Call) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + RuntimeHelper::Runtime: + pallet_utility::Config>, + RuntimeCallOf: From> + + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let (relay_chain_header, grandpa_justification, message_proof) = + test_data::from_grandpa_chain::make_complex_relayer_delivery_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![xcm::v3::Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + X2(GlobalConsensus(Polkadot), Parachain(1_000)), + 1u32.into(), + ); + + // generate batch call that provides finality for bridged relay and parachains + message + // proof + let batch = test_data::from_grandpa_chain::make_complex_relayer_delivery_batch::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >( + relay_chain_header, + grandpa_justification, + 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 + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged GRANDPA chain. +pub fn can_calculate_fee_for_complex_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn(pallet_utility::Call) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteGrandpaChainHelper, + AccountIdOf: From, + RuntimeHelper::Runtime: + pallet_utility::Config>, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: From> + + From>, + UnderlyingChainOf>: ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let (relay_chain_header, grandpa_justification, message_delivery_proof) = + test_data::from_grandpa_chain::make_complex_relayer_confirmation_proofs::< + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1u32.into(), + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + // generate batch call that provides finality for bridged relay and parachains + message + // proof + let batch = test_data::from_grandpa_chain::make_complex_relayer_confirmation_batch::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::MPI, + >( + relay_chain_header, + grandpa_justification, + 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 + }) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..91bebb36b18760b91125b5775a5a3111b801b0a0 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -0,0 +1,541 @@ +// 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 . + +//! Module contains predefined test-case scenarios for `Runtime` with bridging capabilities +//! with remote parachain. + +use crate::{ + test_cases::{bridges_prelude::*, helpers, run_test}, + test_data, +}; + +use bp_header_chain::ChainWithGrandpa; +use bp_messages::{ + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, LaneId, + UnrewardedRelayersState, +}; +use bp_polkadot_core::parachains::ParaHash; +use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; +use bp_runtime::{HashOf, Parachain, UnderlyingChainOf}; +use bridge_runtime_common::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + BridgedChain as MessageBridgedChain, MessageBridge, ThisChain as MessageThisChain, + }, + messages_xcm_extension::XcmAsPlainPayload, +}; +use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_runtimes_test_utils::{ + AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, +}; +use sp_keyring::AccountKeyring::*; +use sp_runtime::{traits::Header as HeaderT, AccountId32}; +use xcm::latest::prelude::*; + +/// Helper trait to test bridges with remote parachain. +/// +/// This is only used to decrease amount of lines, dedicated to bounds. +pub trait WithRemoteParachainHelper { + /// This chain runtime. + type Runtime: BasicParachainRuntime + + cumulus_pallet_xcmp_queue::Config + + BridgeGrandpaConfig + + BridgeParachainsConfig + + BridgeMessagesConfig< + Self::MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = bp_runtime::AccountIdOf>, + OutboundPayload = XcmAsPlainPayload, + > + pallet_bridge_relayers::Config; + /// All pallets of this chain, excluding system pallet. + type AllPalletsWithoutSystem: OnInitialize> + + OnFinalize>; + /// Instance of the `pallet-bridge-grandpa`, used to bridge with remote relay chain. + type GPI: 'static; + /// Instance of the `pallet-bridge-parachains`, used to bridge with remote parachain. + type PPI: 'static; + /// Instance of the `pallet-bridge-messages`, used to bridge with remote parachain. + type MPI: 'static; + /// Messages bridge definition. + type MB: MessageBridge; +} + +/// Adapter struct that implements `WithRemoteParachainHelper`. +pub struct WithRemoteParachainHelperAdapter( + sp_std::marker::PhantomData<(Runtime, AllPalletsWithoutSystem, GPI, PPI, MPI, MB)>, +); + +impl WithRemoteParachainHelper + for WithRemoteParachainHelperAdapter +where + Runtime: BasicParachainRuntime + + cumulus_pallet_xcmp_queue::Config + + BridgeGrandpaConfig + + BridgeParachainsConfig + + BridgeMessagesConfig< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = bp_runtime::AccountIdOf>, + OutboundPayload = XcmAsPlainPayload, + > + pallet_bridge_relayers::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + GPI: 'static, + PPI: 'static, + MPI: 'static, + MB: MessageBridge, +{ + type Runtime = Runtime; + type AllPalletsWithoutSystem = AllPalletsWithoutSystem; + type GPI = GPI; + type PPI = PPI; + type MPI = MPI; + type MB = MB; +} + +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, para heads, message) independently submitted. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn relayed_incoming_message_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + bridged_para_id: u32, + bridged_chain_id: bp_runtime::ChainId, + sibling_parachain_id: u32, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + ::RuntimeCall, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteParachainHelper, + AccountIdOf: From, + RuntimeCallOf: From> + + From> + + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + let para_header_number = 5; + let relay_header_number = 1; + + prepare_configuration(); + + // start with bridged relay chain block#0 + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::(0), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let ( + relay_chain_header, + grandpa_justification, + parachain_head, + parachain_heads, + para_heads_proof, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + para_header_number, + relay_header_number, + bridged_para_id, + ); + + let parachain_head_hash = parachain_head.hash(); + let relay_chain_header_hash = relay_chain_header.hash(); + let relay_chain_header_number = *relay_chain_header.number(); + vec![ + ( + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + helpers::VerifySubmitGrandpaFinalityProofOutcome::::expect_best_header_hash( + relay_chain_header_hash, + ), + ), + ( + BridgeParachainsCall::::submit_parachain_heads { + at_relay_block: (relay_chain_header_number, relay_chain_header_hash), + parachains: parachain_heads, + parachain_heads_proof: para_heads_proof, + }.into(), + helpers::VerifySubmitParachainHeaderProofOutcome::::expect_best_header_hash( + bridged_para_id, + parachain_head_hash, + ), + ), + ( + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + Box::new(( + helpers::VerifySubmitMessagesProofOutcome::::expect_last_delivered_nonce( + lane_id, + 1, + ), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + ), + ] + }, + ); +} + +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, para heads, message) batched together in signed extrinsic. +/// Also verifies relayer transaction signed extensions work as intended. +pub fn complex_relay_extrinsic_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + bridged_para_id: u32, + sibling_parachain_id: u32, + bridged_chain_id: bp_runtime::ChainId, + local_relay_chain_id: NetworkId, + lane_id: LaneId, + prepare_configuration: impl Fn(), + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + ::RuntimeCall, + ) -> sp_runtime::DispatchOutcome, +) where + RuntimeHelper: WithRemoteParachainHelper, + RuntimeHelper::Runtime: + pallet_utility::Config>, + AccountIdOf: From, + RuntimeCallOf: From> + + From> + + From> + + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + helpers::relayed_incoming_message_works::< + RuntimeHelper::Runtime, + RuntimeHelper::AllPalletsWithoutSystem, + RuntimeHelper::MPI, + >( + collator_session_key, + runtime_para_id, + sibling_parachain_id, + local_relay_chain_id, + construct_and_apply_extrinsic, + |relayer_id_at_this_chain, + relayer_id_at_bridged_chain, + message_destination, + message_nonce, + xcm| { + let para_header_number = 5; + let relay_header_number = 1; + + prepare_configuration(); + + // start with bridged relay chain block#0 + helpers::initialize_bridge_grandpa_pallet::( + test_data::initialization_data::(0), + ); + + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let ( + relay_chain_header, + grandpa_justification, + parachain_head, + parachain_heads, + para_heads_proof, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + lane_id, + xcm.into(), + message_nonce, + message_destination, + para_header_number, + relay_header_number, + bridged_para_id, + ); + + let parachain_head_hash = parachain_head.hash(); + let relay_chain_header_hash = relay_chain_header.hash(); + let relay_chain_header_number = *relay_chain_header.number(); + vec![( + pallet_utility::Call::::batch_all { + calls: vec![ + BridgeGrandpaCall::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }.into(), + BridgeParachainsCall::::submit_parachain_heads { + at_relay_block: (relay_chain_header_number, relay_chain_header_hash), + parachains: parachain_heads, + parachain_heads_proof: para_heads_proof, + }.into(), + BridgeMessagesCall::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }.into(), + ], + } + .into(), + Box::new(( + helpers::VerifySubmitGrandpaFinalityProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + >::expect_best_header_hash(relay_chain_header_hash), + helpers::VerifySubmitParachainHeaderProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::PPI, + >::expect_best_header_hash(bridged_para_id, parachain_head_hash), + helpers::VerifySubmitMessagesProofOutcome::< + RuntimeHelper::Runtime, + RuntimeHelper::MPI, + >::expect_last_delivered_nonce(lane_id, 1), + helpers::VerifyRelayerRewarded::::expect_relayer_reward( + relayer_id_at_this_chain, + RewardsAccountParams::new( + lane_id, + bridged_chain_id, + RewardsAccountOwner::ThisChain, + ), + ), + )), + )] + }, + ); +} + +/// Estimates transaction fee for default message delivery transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_complex_message_delivery_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn(pallet_utility::Call) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + RuntimeHelper::Runtime: + pallet_utility::Config>, + RuntimeCallOf: From> + + From> + + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::SourceHeaderChain: + SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof< + HashOf>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + // + // we don't care about parameter values here, apart from the XCM message size. But we + // do not need to have a large message here, because we're charging for every byte of + // the message additionally + let ( + relay_chain_header, + grandpa_justification, + _, + parachain_heads, + para_heads_proof, + message_proof, + ) = test_data::from_parachain::make_complex_relayer_delivery_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + vec![xcm::v3::Instruction::<()>::ClearOrigin; 1_024].into(), + 1, + X2(GlobalConsensus(Polkadot), Parachain(1_000)), + 1, + 5, + 1_000, + ); + + // generate batch call that provides finality for bridged relay and parachains + message + // proof + let batch = test_data::from_parachain::make_complex_relayer_delivery_batch::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::PPI, + RuntimeHelper::MPI, + _, + >( + relay_chain_header, + grandpa_justification, + parachain_heads, + para_heads_proof, + 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 + }) +} + +/// Estimates transaction fee for default message confirmation transaction (batched with required +/// proofs) from bridged parachain. +pub fn can_calculate_fee_for_complex_message_confirmation_transaction( + collator_session_key: CollatorSessionKeys, + compute_extrinsic_fee: fn(pallet_utility::Call) -> u128, +) -> u128 +where + RuntimeHelper: WithRemoteParachainHelper, + AccountIdOf: From, + RuntimeHelper::Runtime: + pallet_utility::Config>, + MessageThisChain: + bp_runtime::Chain>, + RuntimeCallOf: From> + + From> + + From>, + UnderlyingChainOf>: + bp_runtime::Chain + Parachain, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::TargetHeaderChain: + TargetHeaderChain< + XcmAsPlainPayload, + AccountIdOf, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>>, + >, + >, +{ + run_test::(collator_session_key, 1000, vec![], || { + // generate bridged relay chain finality, parachain heads and message proofs, + // to be submitted by relayer to this chain. + let unrewarded_relayers = UnrewardedRelayersState { + unrewarded_relayer_entries: 1, + total_messages: 1, + ..Default::default() + }; + let ( + relay_chain_header, + grandpa_justification, + _, + parachain_heads, + para_heads_proof, + message_delivery_proof, + ) = test_data::from_parachain::make_complex_relayer_confirmation_proofs::< + >::BridgedChain, + RuntimeHelper::MB, + (), + >( + LaneId::default(), + 1, + 5, + 1_000, + AccountId32::from(Alice.public()).into(), + unrewarded_relayers.clone(), + ); + + // generate batch call that provides finality for bridged relay and parachains + message + // proof + let batch = test_data::from_parachain::make_complex_relayer_confirmation_batch::< + RuntimeHelper::Runtime, + RuntimeHelper::GPI, + RuntimeHelper::PPI, + RuntimeHelper::MPI, + >( + relay_chain_header, + grandpa_justification, + parachain_heads, + para_heads_proof, + 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 + }) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..69aa61db3cc66c3b1e93d04e2b7c7b0317da344a --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -0,0 +1,345 @@ +// 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 . + +//! Module contains tests code, that is shared by all types of bridges + +use crate::test_cases::{bridges_prelude::*, run_test, RuntimeHelper}; + +use asset_test_utils::BasicParachainRuntime; +use bp_messages::{LaneId, MessageNonce}; +use bp_polkadot_core::parachains::{ParaHash, ParaId}; +use bp_relayers::RewardsAccountParams; +use codec::Decode; +use frame_support::{ + assert_ok, + traits::{OnFinalize, OnInitialize, PalletInfoAccess}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader}; +use parachains_common::AccountId; +use parachains_runtimes_test_utils::{ + mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, +}; +use sp_core::Get; +use sp_keyring::AccountKeyring::*; +use sp_runtime::{traits::TrailingZeroInput, AccountId32}; +use sp_std::marker::PhantomData; +use xcm::latest::prelude::*; + +/// Verify that the transaction has succeeded. +#[impl_trait_for_tuples::impl_for_tuples(30)] +pub trait VerifyTransactionOutcome { + fn verify_outcome(&self); +} + +impl VerifyTransactionOutcome for Box { + fn verify_outcome(&self) { + VerifyTransactionOutcome::verify_outcome(&**self) + } +} + +/// Checks that the best finalized header hash in the bridge GRANDPA pallet equals to given one. +pub struct VerifySubmitGrandpaFinalityProofOutcome +where + Runtime: BridgeGrandpaConfig, + GPI: 'static, +{ + expected_best_hash: BridgedBlockHash, +} + +impl VerifySubmitGrandpaFinalityProofOutcome +where + Runtime: BridgeGrandpaConfig, + GPI: 'static, +{ + /// Expect given header hash to be the best after transaction. + pub fn expect_best_header_hash( + expected_best_hash: BridgedBlockHash, + ) -> Box { + Box::new(Self { expected_best_hash }) + } +} + +impl VerifyTransactionOutcome + for VerifySubmitGrandpaFinalityProofOutcome +where + Runtime: BridgeGrandpaConfig, + GPI: 'static, +{ + fn verify_outcome(&self) { + assert_eq!( + pallet_bridge_grandpa::BestFinalized::::get().unwrap().1, + self.expected_best_hash + ); + assert!(pallet_bridge_grandpa::ImportedHeaders::::contains_key( + self.expected_best_hash + )); + } +} + +/// Checks that the best parachain header hash in the bridge parachains pallet equals to given one. +pub struct VerifySubmitParachainHeaderProofOutcome { + bridged_para_id: u32, + expected_best_hash: ParaHash, + _marker: PhantomData<(Runtime, PPI)>, +} + +impl VerifySubmitParachainHeaderProofOutcome +where + Runtime: BridgeParachainsConfig, + PPI: 'static, +{ + /// Expect given header hash to be the best after transaction. + pub fn expect_best_header_hash( + bridged_para_id: u32, + expected_best_hash: ParaHash, + ) -> Box { + Box::new(Self { bridged_para_id, expected_best_hash, _marker: PhantomData }) + } +} + +impl VerifyTransactionOutcome + for VerifySubmitParachainHeaderProofOutcome +where + Runtime: BridgeParachainsConfig, + PPI: 'static, +{ + fn verify_outcome(&self) { + assert_eq!( + pallet_bridge_parachains::ParasInfo::::get(ParaId(self.bridged_para_id)) + .map(|info| info.best_head_hash.head_hash), + Some(self.expected_best_hash), + ); + } +} + +/// Checks that the latest delivered nonce in the bridge messages pallet equals to given one. +pub struct VerifySubmitMessagesProofOutcome { + lane: LaneId, + expected_nonce: MessageNonce, + _marker: PhantomData<(Runtime, MPI)>, +} + +impl VerifySubmitMessagesProofOutcome +where + Runtime: BridgeMessagesConfig, + MPI: 'static, +{ + /// Expect given delivered nonce to be the latest after transaction. + pub fn expect_last_delivered_nonce( + lane: LaneId, + expected_nonce: MessageNonce, + ) -> Box { + Box::new(Self { lane, expected_nonce, _marker: PhantomData }) + } +} + +impl VerifyTransactionOutcome for VerifySubmitMessagesProofOutcome +where + Runtime: BridgeMessagesConfig, + MPI: 'static, +{ + fn verify_outcome(&self) { + assert_eq!( + pallet_bridge_messages::InboundLanes::::get(self.lane) + .last_delivered_nonce(), + self.expected_nonce, + ); + } +} + +/// Verifies that relayer is rewarded at this chain. +pub struct VerifyRelayerRewarded { + relayer: Runtime::AccountId, + reward_params: RewardsAccountParams, +} + +impl VerifyRelayerRewarded +where + Runtime: pallet_bridge_relayers::Config, +{ + /// Expect given delivered nonce to be the latest after transaction. + pub fn expect_relayer_reward( + relayer: Runtime::AccountId, + reward_params: RewardsAccountParams, + ) -> Box { + Box::new(Self { relayer, reward_params }) + } +} + +impl VerifyTransactionOutcome for VerifyRelayerRewarded +where + Runtime: pallet_bridge_relayers::Config, +{ + fn verify_outcome(&self) { + assert!(pallet_bridge_relayers::RelayerRewards::::get( + &self.relayer, + &self.reward_params, + ) + .is_some()); + } +} + +/// Initialize bridge GRANDPA pallet. +pub(crate) fn initialize_bridge_grandpa_pallet( + init_data: bp_header_chain::InitializationData>, +) where + Runtime: BridgeGrandpaConfig, +{ + pallet_bridge_grandpa::Pallet::::initialize( + RuntimeHelper::::root_origin(), + init_data, + ) + .unwrap(); +} + +/// Runtime calls and their verifiers. +pub type CallsAndVerifiers = + Vec<(RuntimeCallOf, Box)>; + +/// Returns relayer id at the bridged chain. +pub fn relayer_id_at_bridged_chain, MPI>( +) -> Runtime::InboundRelayer { + Runtime::InboundRelayer::decode(&mut TrailingZeroInput::zeroes()).unwrap() +} + +/// Test-case makes sure that Runtime can dispatch XCM messages submitted by relayer, +/// with proofs (finality, message) independently submitted. +pub fn relayed_incoming_message_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + sibling_parachain_id: u32, + local_relay_chain_id: NetworkId, + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + RuntimeCallOf, + ) -> sp_runtime::DispatchOutcome, + prepare_message_proof_import: impl FnOnce( + Runtime::AccountId, + Runtime::InboundRelayer, + InteriorMultiLocation, + MessageNonce, + Xcm<()>, + ) -> CallsAndVerifiers, +) where + Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config + BridgeMessagesConfig, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + MPI: 'static, + AccountIdOf: From, +{ + let relayer_at_target = Bob; + let relayer_id_on_target: AccountId32 = relayer_at_target.public().into(); + let relayer_id_on_source = relayer_id_at_bridged_chain::(); + + assert_ne!(runtime_para_id, sibling_parachain_id); + + run_test::( + collator_session_key, + runtime_para_id, + vec![( + relayer_id_on_target.clone().into(), + // this value should be enough to cover all transaction costs, but computing the actual + // value here is tricky - there are several transaction payment pallets and we don't + // want to introduce additional bounds and traits here just for that, so let's just + // select some presumably large value + sp_std::cmp::max::(Runtime::ExistentialDeposit::get(), 1u32.into()) * + 100_000_000u32.into(), + )], + || { + let mut alice = [0u8; 32]; + alice[0] = 1; + + let included_head = RuntimeHelper::::run_to_block( + 2, + AccountId::from(alice).into(), + ); + mock_open_hrmp_channel::>( + runtime_para_id.into(), + sibling_parachain_id.into(), + included_head, + &alice, + ); + + // set up relayer details and proofs + + let message_destination = + X2(GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)); + // some random numbers (checked by test) + let message_nonce = 1; + + let xcm = vec![xcm::v3::Instruction::<()>::ClearOrigin; 42]; + let expected_dispatch = xcm::latest::Xcm::<()>({ + let mut expected_instructions = xcm.clone(); + // dispatch prepends bridge pallet instance + expected_instructions.insert( + 0, + DescendOrigin(X1(PalletInstance( + as PalletInfoAccess>::index() + as u8, + ))), + ); + expected_instructions + }); + + execute_and_verify_calls::( + relayer_at_target, + construct_and_apply_extrinsic, + prepare_message_proof_import( + relayer_id_on_target.clone().into(), + relayer_id_on_source.clone().into(), + message_destination, + message_nonce, + xcm.clone().into(), + ), + ); + + // verify that imported XCM contains original message + let imported_xcm = + RuntimeHelper::>::take_xcm( + sibling_parachain_id.into(), + ) + .unwrap(); + let dispatched = xcm::latest::Xcm::<()>::try_from(imported_xcm).unwrap(); + let mut dispatched_clone = dispatched.clone(); + for (idx, expected_instr) in expected_dispatch.0.iter().enumerate() { + assert_eq!(expected_instr, &dispatched.0[idx]); + assert_eq!(expected_instr, &dispatched_clone.0.remove(0)); + } + match dispatched_clone.0.len() { + 0 => (), + 1 => assert!(matches!(dispatched_clone.0[0], SetTopic(_))), + count => assert!(false, "Unexpected messages count: {:?}", count), + } + }, + ) +} + +/// Execute every call and verify its outcome. +fn execute_and_verify_calls( + submitter: sp_keyring::AccountKeyring, + construct_and_apply_extrinsic: fn( + sp_keyring::AccountKeyring, + RuntimeCallOf, + ) -> sp_runtime::DispatchOutcome, + calls_and_verifiers: CallsAndVerifiers, +) { + for (call, verifier) in calls_and_verifiers { + let dispatch_outcome = construct_and_apply_extrinsic(submitter, call); + assert_ok!(dispatch_outcome); + verifier.verify_outcome(); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..64ec8726599282671cbb50b26b31ebb307e2dcb7 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -0,0 +1,657 @@ +// 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 . + +//! Module contains predefined test-case scenarios for `Runtime` with bridging capabilities. +//! +//! This file contains tests, suitable for all bridge runtimes. See `from_parachain` and +//! `from_grandpa_chain` submodules for tests, that are specific to the bridged chain type. + +pub mod from_grandpa_chain; +pub mod from_parachain; + +pub(crate) mod helpers; + +use crate::{test_cases::bridges_prelude::*, test_data}; + +use asset_test_utils::BasicParachainRuntime; +use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, + LaneId, MessageKey, MessagesOperatingMode, OutboundLaneData, +}; +use bp_runtime::BasicOperatingMode; +use bridge_runtime_common::messages_xcm_extension::{ + XcmAsPlainPayload, XcmBlobMessageDispatchResult, +}; +use codec::Encode; +use frame_support::{ + assert_ok, + dispatch::GetDispatchInfo, + traits::{Get, OnFinalize, OnInitialize, OriginTrait}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use parachains_common::AccountId; +use parachains_runtimes_test_utils::{ + mock_open_hrmp_channel, AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, RuntimeCallOf, + XcmReceivedFrom, +}; +use sp_runtime::{traits::Zero, AccountId32}; +use xcm::{latest::prelude::*, AlwaysLatest}; +use xcm_builder::DispatchBlobError; +use xcm_executor::{ + traits::{TransactAsset, WeightBounds}, + XcmExecutor, +}; + +/// Common bridges exports. +pub(crate) mod bridges_prelude { + pub use pallet_bridge_grandpa::{Call as BridgeGrandpaCall, Config as BridgeGrandpaConfig}; + pub use pallet_bridge_messages::{Call as BridgeMessagesCall, Config as BridgeMessagesConfig}; + pub use pallet_bridge_parachains::{ + Call as BridgeParachainsCall, Config as BridgeParachainsConfig, RelayBlockHash, + RelayBlockNumber, + }; +} + +// Re-export test_case from assets +pub use asset_test_utils::include_teleports_for_native_asset_works; + +pub type RuntimeHelper = + parachains_runtimes_test_utils::RuntimeHelper; + +// Re-export test_case from `parachains-runtimes-test-utils` +pub use parachains_runtimes_test_utils::test_cases::change_storage_constant_by_governance_works; + +/// Prepare default runtime storage and run test within this context. +pub fn run_test( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + balances: Vec<(Runtime::AccountId, Runtime::Balance)>, + test: impl FnOnce() -> T, +) -> T +where + Runtime: BasicParachainRuntime, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_safe_xcm_version(XCM_VERSION) + .with_para_id(runtime_para_id.into()) + .with_balances(balances) + .with_tracing() + .build() + .execute_with(|| test()) +} + +/// Test-case makes sure that `Runtime` can process bridging initialize via governance-like call +pub fn initialize_bridge_by_governance_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, +) where + Runtime: BasicParachainRuntime + BridgeGrandpaConfig, + GrandpaPalletInstance: 'static, + RuntimeCallOf: + GetDispatchInfo + From>, +{ + run_test::(collator_session_key, runtime_para_id, vec![], || { + // check mode before + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::try_get(), + Err(()) + ); + + // prepare the `initialize` call + let initialize_call = RuntimeCallOf::::from(BridgeGrandpaCall::< + Runtime, + GrandpaPalletInstance, + >::initialize { + init_data: test_data::initialization_data::(12345), + }); + + // execute XCM with Transacts to `initialize bridge` as governance does + assert_ok!(RuntimeHelper::::execute_as_governance( + initialize_call.encode(), + initialize_call.get_dispatch_info().weight, + ) + .ensure_complete()); + + // check mode after + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::try_get(), + Ok(BasicOperatingMode::Normal) + ); + }) +} + +/// Test-case makes sure that `Runtime` can change bridge GRANDPA pallet operating mode via +/// governance-like call. +pub fn change_bridge_grandpa_pallet_mode_by_governance_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, +) where + Runtime: BasicParachainRuntime + BridgeGrandpaConfig, + GrandpaPalletInstance: 'static, + RuntimeCallOf: + GetDispatchInfo + From>, +{ + run_test::(collator_session_key, runtime_para_id, vec![], || { + let dispatch_set_operating_mode_call = |old_mode, new_mode| { + // check old mode + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::get(), + old_mode, + ); + + // prepare the `set_operating_mode` call + let set_operating_mode_call = ::RuntimeCall::from( + pallet_bridge_grandpa::Call::::set_operating_mode { + operating_mode: new_mode, + }, + ); + + // execute XCM with Transacts to `initialize bridge` as governance does + assert_ok!(RuntimeHelper::::execute_as_governance( + set_operating_mode_call.encode(), + set_operating_mode_call.get_dispatch_info().weight, + ) + .ensure_complete()); + + // check mode after + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::try_get(), + Ok(new_mode) + ); + }; + + // check mode before + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::try_get(), + Err(()) + ); + + dispatch_set_operating_mode_call(BasicOperatingMode::Normal, BasicOperatingMode::Halted); + dispatch_set_operating_mode_call(BasicOperatingMode::Halted, BasicOperatingMode::Normal); + }); +} + +/// Test-case makes sure that `Runtime` can change bridge parachains pallet operating mode via +/// governance-like call. +pub fn change_bridge_parachains_pallet_mode_by_governance_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, +) where + Runtime: BasicParachainRuntime + BridgeParachainsConfig, + ParachainsPalletInstance: 'static, + RuntimeCallOf: + GetDispatchInfo + From>, +{ + run_test::(collator_session_key, runtime_para_id, vec![], || { + let dispatch_set_operating_mode_call = |old_mode, new_mode| { + // check old mode + assert_eq!( + pallet_bridge_parachains::PalletOperatingMode::::get(), + old_mode, + ); + + // prepare the `set_operating_mode` call + let set_operating_mode_call = + RuntimeCallOf::::from(pallet_bridge_parachains::Call::< + Runtime, + ParachainsPalletInstance, + >::set_operating_mode { + operating_mode: new_mode, + }); + + // execute XCM with Transacts to `initialize bridge` as governance does + assert_ok!(RuntimeHelper::::execute_as_governance( + set_operating_mode_call.encode(), + set_operating_mode_call.get_dispatch_info().weight, + ) + .ensure_complete()); + + // check mode after + assert_eq!( + pallet_bridge_parachains::PalletOperatingMode::::try_get(), + Ok(new_mode) + ); + }; + + // check mode before + assert_eq!( + pallet_bridge_parachains::PalletOperatingMode::::try_get(), + Err(()) + ); + + dispatch_set_operating_mode_call(BasicOperatingMode::Normal, BasicOperatingMode::Halted); + dispatch_set_operating_mode_call(BasicOperatingMode::Halted, BasicOperatingMode::Normal); + }); +} + +/// Test-case makes sure that `Runtime` can change bridge messaging pallet operating mode via +/// governance-like call. +pub fn change_bridge_messages_pallet_mode_by_governance_works( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, +) where + Runtime: BasicParachainRuntime + BridgeMessagesConfig, + MessagesPalletInstance: 'static, + RuntimeCallOf: + GetDispatchInfo + From>, +{ + run_test::(collator_session_key, runtime_para_id, vec![], || { + let dispatch_set_operating_mode_call = |old_mode, new_mode| { + // check old mode + assert_eq!( + pallet_bridge_messages::PalletOperatingMode::::get( + ), + old_mode, + ); + + // encode `set_operating_mode` call + let set_operating_mode_call = RuntimeCallOf::::from(BridgeMessagesCall::< + Runtime, + MessagesPalletInstance, + >::set_operating_mode { + operating_mode: new_mode, + }); + + // execute XCM with Transacts to `initialize bridge` as governance does + assert_ok!(RuntimeHelper::::execute_as_governance( + set_operating_mode_call.encode(), + set_operating_mode_call.get_dispatch_info().weight, + ) + .ensure_complete()); + + // check mode after + assert_eq!( + pallet_bridge_messages::PalletOperatingMode::::try_get(), + Ok(new_mode) + ); + }; + + // check mode before + assert_eq!( + pallet_bridge_messages::PalletOperatingMode::::try_get( + ), + Err(()) + ); + + dispatch_set_operating_mode_call( + MessagesOperatingMode::Basic(BasicOperatingMode::Normal), + MessagesOperatingMode::RejectingOutboundMessages, + ); + dispatch_set_operating_mode_call( + MessagesOperatingMode::RejectingOutboundMessages, + MessagesOperatingMode::Basic(BasicOperatingMode::Halted), + ); + dispatch_set_operating_mode_call( + MessagesOperatingMode::Basic(BasicOperatingMode::Halted), + MessagesOperatingMode::Basic(BasicOperatingMode::Normal), + ); + }); +} + +/// Test-case makes sure that `Runtime` can handle xcm `ExportMessage`: +/// Checks if received XCM messages is correctly added to the message outbound queue for delivery. +/// For SystemParachains we expect unpaid execution. +pub fn handle_export_message_from_system_parachain_to_outbound_queue_works< + Runtime, + XcmConfig, + MessagesPalletInstance, +>( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + sibling_parachain_id: u32, + unwrap_pallet_bridge_messages_event: Box< + dyn Fn(Vec) -> Option>, + >, + export_message_instruction: fn() -> Instruction, + expected_lane_id: LaneId, + existential_deposit: Option, + maybe_paid_export_message: Option, + prepare_configuration: impl Fn(), +) where + Runtime: BasicParachainRuntime + BridgeMessagesConfig, + XcmConfig: xcm_executor::Config, + MessagesPalletInstance: 'static, +{ + assert_ne!(runtime_para_id, sibling_parachain_id); + let sibling_parachain_location = MultiLocation::new(1, Parachain(sibling_parachain_id)); + + run_test::(collator_session_key, runtime_para_id, vec![], || { + prepare_configuration(); + + // check queue before + assert_eq!( + pallet_bridge_messages::OutboundLanes::::try_get( + expected_lane_id + ), + Err(()) + ); + + // prepare `ExportMessage` + let xcm = if let Some(fee) = maybe_paid_export_message { + // deposit ED to origin (if needed) + if let Some(ed) = existential_deposit { + XcmConfig::AssetTransactor::deposit_asset( + &ed, + &sibling_parachain_location, + Some(&XcmContext::with_message_id([0; 32])), + ) + .expect("deposited ed"); + } + // deposit fee to origin + XcmConfig::AssetTransactor::deposit_asset( + &fee, + &sibling_parachain_location, + Some(&XcmContext::with_message_id([0; 32])), + ) + .expect("deposited fee"); + + Xcm(vec![ + WithdrawAsset(MultiAssets::from(vec![fee.clone()])), + BuyExecution { fees: fee, weight_limit: Unlimited }, + export_message_instruction(), + ]) + } else { + Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + export_message_instruction(), + ]) + }; + + // execute XCM + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + assert_ok!(XcmExecutor::::execute_xcm( + sibling_parachain_location, + xcm, + hash, + RuntimeHelper::::xcm_max_weight(XcmReceivedFrom::Sibling), + ) + .ensure_complete()); + + // check queue after + assert_eq!( + pallet_bridge_messages::OutboundLanes::::try_get( + expected_lane_id + ), + Ok(OutboundLaneData { + oldest_unpruned_nonce: 1, + latest_received_nonce: 0, + latest_generated_nonce: 1, + }) + ); + + // check events + let mut events = >::events() + .into_iter() + .filter_map(|e| unwrap_pallet_bridge_messages_event(e.event.encode())); + assert!(events.any(|e| matches!(e, pallet_bridge_messages::Event::MessageAccepted { .. }))); + }) +} + +/// Test-case makes sure that Runtime can route XCM messages received in inbound queue, +/// We just test here `MessageDispatch` configuration. +/// We expect that runtime can route messages: +/// 1. to Parent (relay chain) +/// 2. to Sibling parachain +pub fn message_dispatch_routing_works< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + HrmpChannelOpener, + MessagesPalletInstance, + RuntimeNetwork, + BridgedNetwork, + NetworkDistanceAsParentCount, +>( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + sibling_parachain_id: u32, + unwrap_cumulus_pallet_parachain_system_event: Box< + dyn Fn(Vec) -> Option>, + >, + unwrap_cumulus_pallet_xcmp_queue_event: Box< + dyn Fn(Vec) -> Option>, + >, + expected_lane_id: LaneId, + prepare_configuration: impl Fn(), +) where + Runtime: BasicParachainRuntime + + cumulus_pallet_xcmp_queue::Config + + BridgeMessagesConfig, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + AccountIdOf: From + + Into<<::RuntimeOrigin as OriginTrait>::AccountId>, + XcmConfig: xcm_executor::Config, + MessagesPalletInstance: 'static, + HrmpChannelOpener: frame_support::inherent::ProvideInherent< + Call = cumulus_pallet_parachain_system::Call, + >, + RuntimeNetwork: Get, + BridgedNetwork: Get, + NetworkDistanceAsParentCount: Get, +{ + struct NetworkWithParentCount(core::marker::PhantomData<(N, C)>); + impl, C: Get> Get for NetworkWithParentCount { + fn get() -> MultiLocation { + MultiLocation { parents: C::get(), interior: X1(GlobalConsensus(N::get())) } + } + } + + assert_ne!(runtime_para_id, sibling_parachain_id); + + run_test::(collator_session_key, runtime_para_id, vec![], || { + prepare_configuration(); + + let mut alice = [0u8; 32]; + alice[0] = 1; + + let included_head = RuntimeHelper::::run_to_block( + 2, + AccountId::from(alice).into(), + ); + // 1. this message is sent from other global consensus with destination of this Runtime + // relay chain (UMP) + let bridging_message = test_data::simulate_message_exporter_on_bridged_chain::< + BridgedNetwork, + NetworkWithParentCount, + AlwaysLatest, + >((RuntimeNetwork::get(), Here)); + let result = + <>::MessageDispatch>::dispatch( + test_data::dispatch_message(expected_lane_id, 1, bridging_message), + ); + assert_eq!( + format!("{:?}", result.dispatch_level_result), + format!("{:?}", XcmBlobMessageDispatchResult::Dispatched) + ); + + // check events - UpwardMessageSent + let mut events = >::events() + .into_iter() + .filter_map(|e| unwrap_cumulus_pallet_parachain_system_event(e.event.encode())); + assert!(events.any(|e| matches!( + e, + cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } + ))); + + // 2. this message is sent from other global consensus with destination of this Runtime + // sibling parachain (HRMP) + let bridging_message = test_data::simulate_message_exporter_on_bridged_chain::< + BridgedNetwork, + NetworkWithParentCount, + AlwaysLatest, + >((RuntimeNetwork::get(), X1(Parachain(sibling_parachain_id)))); + + // 2.1. WITHOUT opened hrmp channel -> RoutingError + let result = + <>::MessageDispatch>::dispatch( + DispatchMessage { + key: MessageKey { lane_id: expected_lane_id, nonce: 1 }, + data: DispatchMessageData { payload: Ok(bridging_message.clone()) }, + }, + ); + assert_eq!( + format!("{:?}", result.dispatch_level_result), + format!( + "{:?}", + XcmBlobMessageDispatchResult::NotDispatched(Some(DispatchBlobError::RoutingError)) + ) + ); + + // check events - no XcmpMessageSent + assert_eq!( + >::events() + .into_iter() + .filter_map(|e| unwrap_cumulus_pallet_xcmp_queue_event(e.event.encode())) + .count(), + 0 + ); + + // 2.1. WITH hrmp channel -> Ok + mock_open_hrmp_channel::( + runtime_para_id.into(), + sibling_parachain_id.into(), + included_head, + &alice, + ); + let result = + <>::MessageDispatch>::dispatch( + DispatchMessage { + key: MessageKey { lane_id: expected_lane_id, nonce: 1 }, + data: DispatchMessageData { payload: Ok(bridging_message) }, + }, + ); + assert_eq!( + format!("{:?}", result.dispatch_level_result), + format!("{:?}", XcmBlobMessageDispatchResult::Dispatched) + ); + + // check events - XcmpMessageSent + let mut events = >::events() + .into_iter() + .filter_map(|e| unwrap_cumulus_pallet_xcmp_queue_event(e.event.encode())); + assert!( + events.any(|e| matches!(e, cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. })) + ); + }) +} + +/// Estimates XCM execution fee for paid `ExportMessage` processing. +pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer< + Runtime, + XcmConfig, + WeightToFee, +>() -> u128 +where + Runtime: frame_system::Config + pallet_balances::Config, + XcmConfig: xcm_executor::Config, + WeightToFee: frame_support::weights::WeightToFee>, + ::Balance: From + Into, +{ + // data here are not relevant for weighing + let mut xcm = Xcm(vec![ + WithdrawAsset(MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(34333299), + }])), + BuyExecution { + fees: MultiAsset { + id: Concrete(MultiLocation { parents: 1, interior: Here }), + fun: Fungible(34333299), + }, + weight_limit: Unlimited, + }, + ExportMessage { + network: Polkadot, + destination: X1(Parachain(1000)), + xcm: Xcm(vec![ + ReserveAssetDeposited(MultiAssets::from(vec![MultiAsset { + id: Concrete(MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(Kusama)), + }), + fun: Fungible(1000000000000), + }])), + ClearOrigin, + BuyExecution { + fees: MultiAsset { + id: Concrete(MultiLocation { + parents: 2, + interior: X1(GlobalConsensus(Kusama)), + }), + fun: Fungible(1000000000000), + }, + weight_limit: Unlimited, + }, + DepositAsset { + assets: Wild(AllCounted(1)), + beneficiary: MultiLocation { + parents: 0, + interior: X1(xcm::latest::prelude::AccountId32 { + network: None, + id: [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, + 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, + 109, 162, 125, + ], + }), + }, + }, + SetTopic([ + 116, 82, 194, 132, 171, 114, 217, 165, 23, 37, 161, 177, 165, 179, 247, 114, + 137, 101, 147, 70, 28, 157, 168, 32, 154, 63, 74, 228, 152, 180, 5, 63, + ]), + ]), + }, + DepositAsset { + assets: Wild(All), + beneficiary: MultiLocation { parents: 1, interior: X1(Parachain(1000)) }, + }, + SetTopic([ + 36, 224, 250, 165, 82, 195, 67, 110, 160, 170, 140, 87, 217, 62, 201, 164, 42, 98, 219, + 157, 124, 105, 248, 25, 131, 218, 199, 36, 109, 173, 100, 122, + ]), + ]); + + // get weight + let weight = XcmConfig::Weigher::weight(&mut xcm); + assert_ok!(weight); + let weight = weight.unwrap(); + // check if sane + let max_expected = Runtime::BlockWeights::get().max_block / 10; + assert!( + weight.all_lte(max_expected), + "calculated weight: {:?}, max_expected: {:?}", + weight, + max_expected + ); + + // check fee, should not be 0 + 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/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs new file mode 100644 index 0000000000000000000000000000000000000000..017ec0fd54052ae0b00c19a2c474a8e265c768b0 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_grandpa_chain.rs @@ -0,0 +1,244 @@ +// 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 . + +//! Generating test data for bridges with remote GRANDPA chains. + +use crate::test_data::prepare_inbound_xcm; + +use bp_messages::{ + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, LaneId, MessageNonce, + UnrewardedRelayersState, +}; +use bp_runtime::{AccountIdOf, BlockNumberOf, HeaderOf, StorageProofSize, UnderlyingChainOf}; +use bp_test_utils::make_default_justification; +use bridge_runtime_common::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + BridgedChain as MessageBridgedChain, MessageBridge, ThisChain as MessageThisChain, + }, + messages_generation::{ + encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, + prepare_messages_storage_proof, + }, + messages_xcm_extension::XcmAsPlainPayload, +}; +use codec::Encode; +use pallet_bridge_grandpa::{BridgedChain, BridgedHeader}; +use sp_runtime::traits::Header as HeaderT; +use xcm::latest::prelude::*; + +use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa}; +use bp_messages::{DeliveredMessages, InboundLaneData, UnrewardedRelayer}; +use bp_runtime::HashOf; +use sp_runtime::DigestItem; + +/// Prepare a batch call with bridged GRANDPA finality and message proof. +pub fn make_complex_relayer_delivery_batch( + bridged_header: BridgedHeader, + bridged_justification: GrandpaJustification>, + message_proof: FromBridgedChainMessagesProof>>, + relayer_id_at_bridged_chain: AccountIdOf>, +) -> pallet_utility::Call +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = AccountIdOf>, + > + pallet_utility::Config, + GPI: 'static, + MPI: 'static, + >::SourceHeaderChain: SourceHeaderChain< + MessagesProof = FromBridgedChainMessagesProof>>, + >, + ::RuntimeCall: From> + + From>, +{ + let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { + finality_target: Box::new(bridged_header), + justification: bridged_justification, + }; + let submit_message = pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain, + proof: message_proof, + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }; + pallet_utility::Call::::batch_all { + calls: vec![submit_grandpa.into(), submit_message.into()], + } +} + +/// Prepare a batch call with bridged GRANDPA finality and message delivery proof. +pub fn make_complex_relayer_confirmation_batch( + bridged_header: BridgedHeader, + bridged_justification: GrandpaJustification>, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + relayers_state: UnrewardedRelayersState, +) -> pallet_utility::Call +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_messages::Config + + pallet_utility::Config, + GPI: 'static, + MPI: 'static, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof< + HashOf>, + >, + >, + ::RuntimeCall: From> + + From>, +{ + let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { + finality_target: Box::new(bridged_header), + justification: bridged_justification, + }; + let submit_message_delivery_proof = + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + }; + pallet_utility::Call::::batch_all { + calls: vec![submit_grandpa.into(), submit_message_delivery_proof.into()], + } +} + +/// Prepare storage proofs of messages, stored at the (bridged) source GRANDPA chain. +pub fn make_complex_relayer_delivery_proofs( + lane_id: LaneId, + xcm_message: Xcm, + message_nonce: MessageNonce, + message_destination: Junctions, + header_number: BlockNumberOf>, +) -> ( + HeaderOf>, + GrandpaJustification>>, + FromBridgedChainMessagesProof>>, +) +where + MB: MessageBridge, + MessageBridgedChain: Send + Sync + 'static, + UnderlyingChainOf>: ChainWithGrandpa, +{ + let message_payload = prepare_inbound_xcm(xcm_message, message_destination); + let message_size = StorageProofSize::Minimal(message_payload.len() as u32); + // prepare para storage proof containing message + let (state_root, storage_proof) = prepare_messages_storage_proof::( + lane_id, + message_nonce..=message_nonce, + None, + message_size, + message_payload, + encode_all_messages, + encode_lane_data, + ); + + let (header, justification) = make_complex_bridged_grandpa_header_proof::< + MessageBridgedChain, + >(state_root, header_number); + + let message_proof = FromBridgedChainMessagesProof { + bridged_header_hash: header.hash(), + storage_proof, + lane: lane_id, + nonces_start: message_nonce, + nonces_end: message_nonce, + }; + + (header, justification, message_proof) +} + +/// Prepare storage proofs of message confirmations, stored at the (bridged) target GRANDPA chain. +pub fn make_complex_relayer_confirmation_proofs( + lane_id: LaneId, + header_number: BlockNumberOf>, + relayer_id_at_this_chain: AccountIdOf>, + relayers_state: UnrewardedRelayersState, +) -> ( + HeaderOf>, + GrandpaJustification>>, + FromBridgedChainMessagesDeliveryProof>>, +) +where + MB: MessageBridge, + MessageBridgedChain: Send + Sync + 'static, + MessageThisChain: Send + Sync + 'static, + UnderlyingChainOf>: ChainWithGrandpa, +{ + // prepare storage proof containing message delivery proof + let (state_root, storage_proof) = prepare_message_delivery_storage_proof::( + lane_id, + InboundLaneData { + relayers: vec![ + UnrewardedRelayer { + relayer: relayer_id_at_this_chain, + messages: DeliveredMessages::new(1) + }; + relayers_state.unrewarded_relayer_entries as usize + ] + .into(), + last_confirmed_nonce: 1, + }, + StorageProofSize::Minimal(0), + ); + + let (header, justification) = + make_complex_bridged_grandpa_header_proof::(state_root, header_number); + + let message_delivery_proof = FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: header.hash(), + storage_proof, + lane: lane_id, + }; + + (header, justification, message_delivery_proof) +} + +/// Make bridged GRANDPA chain header with given state root. +pub fn make_complex_bridged_grandpa_header_proof( + state_root: HashOf, + header_number: BlockNumberOf, +) -> (HeaderOf, GrandpaJustification>) +where + BridgedChain: ChainWithGrandpa, +{ + let mut header = bp_test_utils::test_header_with_root::>( + header_number.into(), + state_root.into(), + ); + + // to compute proper cost of GRANDPA call, let's add some dummy bytes to header, so that the + // `submit_finality_proof` call size would be close to maximal expected (and refundable) + let extra_bytes_required = maximal_expected_submit_finality_proof_call_size::() + .saturating_sub(header.encoded_size()); + header.digest_mut().push(DigestItem::Other(vec![42; extra_bytes_required])); + + let justification = make_default_justification(&header); + (header, justification) +} + +/// Maximal expected `submit_finality_proof` call size. +pub fn maximal_expected_submit_finality_proof_call_size() -> usize { + bp_header_chain::max_expected_submit_finality_proof_arguments_size::( + false, + BridgedChain::MAX_AUTHORITIES_COUNT * 2 / 3 + 1, + ) as usize +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs new file mode 100644 index 0000000000000000000000000000000000000000..932ba231239973db8b46ccea56faacc5628a4ffb --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/from_parachain.rs @@ -0,0 +1,327 @@ +// 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 . + +//! Generating test data for bridges with remote parachains. + +use super::{from_grandpa_chain::make_complex_bridged_grandpa_header_proof, prepare_inbound_xcm}; + +use bp_messages::{ + source_chain::TargetHeaderChain, target_chain::SourceHeaderChain, LaneId, + UnrewardedRelayersState, Weight, +}; +use bp_runtime::{ + AccountIdOf, BlockNumberOf, HeaderOf, Parachain, StorageProofSize, UnderlyingChainOf, +}; +use bp_test_utils::prepare_parachain_heads_proof; +use bridge_runtime_common::{ + messages::{ + source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof, + BridgedChain as MessageBridgedChain, MessageBridge, ThisChain as MessageThisChain, + }, + messages_generation::{ + encode_all_messages, encode_lane_data, prepare_message_delivery_storage_proof, + prepare_messages_storage_proof, + }, + messages_xcm_extension::XcmAsPlainPayload, +}; +use codec::Encode; +use pallet_bridge_grandpa::BridgedHeader; +use pallet_bridge_parachains::{RelayBlockHash, RelayBlockNumber}; +use sp_runtime::traits::Header as HeaderT; +use xcm::latest::prelude::*; + +use bp_header_chain::{justification::GrandpaJustification, ChainWithGrandpa}; +use bp_messages::{DeliveredMessages, InboundLaneData, MessageNonce, UnrewardedRelayer}; +use bp_polkadot_core::parachains::{ParaHash, ParaHead, ParaHeadsProof, ParaId}; +use sp_runtime::SaturatedConversion; + +/// Prepare a batch call with relay finality proof, parachain head proof and message proof. +pub fn make_complex_relayer_delivery_batch( + relay_chain_header: BridgedHeader, + grandpa_justification: GrandpaJustification>, + parachain_heads: Vec<(ParaId, ParaHash)>, + para_heads_proof: ParaHeadsProof, + message_proof: FromBridgedChainMessagesProof, + relayer_id_at_bridged_chain: InboundRelayer, +) -> pallet_utility::Call where + Runtime:pallet_bridge_grandpa::Config + + pallet_bridge_parachains::Config + + pallet_bridge_messages::Config< + MPI, + InboundPayload = XcmAsPlainPayload, + InboundRelayer = InboundRelayer, + > + + pallet_utility::Config, + GPI: 'static, + PPI: 'static, + MPI: 'static, + ParaHash: From<<>::BridgedChain as bp_runtime::Chain>::Hash>, + <>::BridgedChain as bp_runtime::Chain>::Hash: From, + <>::SourceHeaderChain as SourceHeaderChain>::MessagesProof: + From>, + ::RuntimeCall: + From> + + From> + + From>, +{ + let relay_chain_header_hash = relay_chain_header.hash(); + let relay_chain_header_number = *relay_chain_header.number(); + let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }; + let submit_para_head = pallet_bridge_parachains::Call::::submit_parachain_heads { + at_relay_block: ( + relay_chain_header_number.saturated_into(), + relay_chain_header_hash.into(), + ), + parachains: parachain_heads, + parachain_heads_proof: para_heads_proof, + }; + let submit_message = pallet_bridge_messages::Call::::receive_messages_proof { + relayer_id_at_bridged_chain: relayer_id_at_bridged_chain.into(), + proof: message_proof.into(), + messages_count: 1, + dispatch_weight: Weight::from_parts(1000000000, 0), + }; + pallet_utility::Call::::batch_all { + calls: vec![submit_grandpa.into(), submit_para_head.into(), submit_message.into()], + } +} + +/// Prepare a batch call with relay finality proof, parachain head proof and message delivery +/// proof. +pub fn make_complex_relayer_confirmation_batch( + relay_chain_header: BridgedHeader, + grandpa_justification: GrandpaJustification>, + parachain_heads: Vec<(ParaId, ParaHash)>, + para_heads_proof: ParaHeadsProof, + message_delivery_proof: FromBridgedChainMessagesDeliveryProof, + relayers_state: UnrewardedRelayersState, +) -> pallet_utility::Call +where + Runtime: pallet_bridge_grandpa::Config + + pallet_bridge_parachains::Config + + pallet_bridge_messages::Config + + pallet_utility::Config, + GPI: 'static, + PPI: 'static, + MPI: 'static, + >::BridgedChain: + bp_runtime::Chain + ChainWithGrandpa, + >::TargetHeaderChain: TargetHeaderChain< + XcmAsPlainPayload, + Runtime::AccountId, + MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof, + >, + ::RuntimeCall: From> + + From> + + From>, +{ + let relay_chain_header_hash = relay_chain_header.hash(); + let relay_chain_header_number = *relay_chain_header.number(); + let submit_grandpa = pallet_bridge_grandpa::Call::::submit_finality_proof { + finality_target: Box::new(relay_chain_header), + justification: grandpa_justification, + }; + let submit_para_head = pallet_bridge_parachains::Call::::submit_parachain_heads { + at_relay_block: ( + relay_chain_header_number.saturated_into(), + relay_chain_header_hash.into(), + ), + parachains: parachain_heads, + parachain_heads_proof: para_heads_proof, + }; + let submit_message_delivery_proof = + pallet_bridge_messages::Call::::receive_messages_delivery_proof { + proof: message_delivery_proof, + relayers_state, + }; + pallet_utility::Call::::batch_all { + calls: vec![ + submit_grandpa.into(), + submit_para_head.into(), + submit_message_delivery_proof.into(), + ], + } +} + +/// Prepare storage proofs of messages, stored at the source chain. +pub fn make_complex_relayer_delivery_proofs( + lane_id: LaneId, + xcm_message: Xcm, + message_nonce: MessageNonce, + message_destination: Junctions, + para_header_number: u32, + relay_header_number: u32, + bridged_para_id: u32, +) -> ( + HeaderOf, + GrandpaJustification>, + ParaHead, + Vec<(ParaId, ParaHash)>, + ParaHeadsProof, + FromBridgedChainMessagesProof, +) +where + BridgedRelayChain: + bp_runtime::Chain + ChainWithGrandpa, + MB: MessageBridge, + UnderlyingChainOf>: bp_runtime::Chain + Parachain, +{ + let message_payload = prepare_inbound_xcm(xcm_message, message_destination); + let message_size = StorageProofSize::Minimal(message_payload.len() as u32); + // prepare para storage proof containing message + let (para_state_root, para_storage_proof) = prepare_messages_storage_proof::( + lane_id, + message_nonce..=message_nonce, + None, + message_size, + message_payload, + encode_all_messages, + encode_lane_data, + ); + + let (relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof) = + make_complex_bridged_parachain_heads_proof::( + para_state_root, + para_header_number, + relay_header_number, + bridged_para_id, + ); + + let message_proof = FromBridgedChainMessagesProof { + bridged_header_hash: bridged_para_head.hash(), + storage_proof: para_storage_proof, + lane: lane_id, + nonces_start: message_nonce, + nonces_end: message_nonce, + }; + + ( + relay_chain_header, + justification, + bridged_para_head, + parachain_heads, + para_heads_proof, + message_proof, + ) +} + +/// Prepare storage proofs of message confirmations, stored at the target parachain. +pub fn make_complex_relayer_confirmation_proofs( + lane_id: LaneId, + para_header_number: u32, + relay_header_number: u32, + bridged_para_id: u32, + relayer_id_at_this_chain: AccountIdOf>, + relayers_state: UnrewardedRelayersState, +) -> ( + HeaderOf, + GrandpaJustification>, + ParaHead, + Vec<(ParaId, ParaHash)>, + ParaHeadsProof, + FromBridgedChainMessagesDeliveryProof, +) +where + BridgedRelayChain: + bp_runtime::Chain + ChainWithGrandpa, + MB: MessageBridge, + UnderlyingChainOf>: bp_runtime::Chain + Parachain, +{ + // prepare para storage proof containing message delivery proof + let (para_state_root, para_storage_proof) = prepare_message_delivery_storage_proof::( + lane_id, + InboundLaneData { + relayers: vec![ + UnrewardedRelayer { + relayer: relayer_id_at_this_chain.into(), + messages: DeliveredMessages::new(1) + }; + relayers_state.unrewarded_relayer_entries as usize + ] + .into(), + last_confirmed_nonce: 1, + }, + StorageProofSize::Minimal(0), + ); + + let (relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof) = + make_complex_bridged_parachain_heads_proof::( + para_state_root, + para_header_number, + relay_header_number, + bridged_para_id, + ); + + let message_delivery_proof = FromBridgedChainMessagesDeliveryProof { + bridged_header_hash: bridged_para_head.hash(), + storage_proof: para_storage_proof, + lane: lane_id, + }; + + ( + relay_chain_header, + justification, + bridged_para_head, + parachain_heads, + para_heads_proof, + message_delivery_proof, + ) +} + +/// Make bridged parachain header with given state root and relay header that is finalizing it. +pub fn make_complex_bridged_parachain_heads_proof( + para_state_root: ParaHash, + para_header_number: u32, + relay_header_number: BlockNumberOf, + bridged_para_id: u32, +) -> ( + HeaderOf, + GrandpaJustification>, + ParaHead, + Vec<(ParaId, ParaHash)>, + ParaHeadsProof, +) +where + BridgedRelayChain: + bp_runtime::Chain + ChainWithGrandpa, + MB: MessageBridge, + ::BridgedChain: Send + Sync + 'static, + ::ThisChain: Send + Sync + 'static, + UnderlyingChainOf>: bp_runtime::Chain + Parachain, +{ + let bridged_para_head = ParaHead( + bp_test_utils::test_header_with_root::>( + para_header_number.into(), + para_state_root, + ) + .encode(), + ); + let (relay_state_root, para_heads_proof, parachain_heads) = + prepare_parachain_heads_proof::>(vec![( + bridged_para_id, + bridged_para_head.clone(), + )]); + assert_eq!(bridged_para_head.hash(), parachain_heads[0].1); + + let (relay_chain_header, justification) = make_complex_bridged_grandpa_header_proof::< + BridgedRelayChain, + >(relay_state_root, relay_header_number); + + (relay_chain_header, justification, bridged_para_head, parachain_heads, para_heads_proof) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f905d21b1871924e1fe9dc4f523471afaf81f536 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_data/mod.rs @@ -0,0 +1,144 @@ +// 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 . + +//! Generating test data, used by all tests. + +pub mod from_grandpa_chain; +pub mod from_parachain; + +use bp_messages::{ + target_chain::{DispatchMessage, DispatchMessageData}, + LaneId, MessageKey, +}; +use codec::Encode; +use frame_support::traits::Get; +use pallet_bridge_grandpa::BridgedHeader; +use xcm::latest::prelude::*; + +use bp_messages::MessageNonce; +use bp_runtime::BasicOperatingMode; +use bp_test_utils::authority_list; +use xcm::GetVersion; +use xcm_builder::{HaulBlob, HaulBlobError, HaulBlobExporter}; +use xcm_executor::traits::{validate_export, ExportXcm}; + +pub fn prepare_inbound_xcm( + xcm_message: Xcm, + destination: InteriorMultiLocation, +) -> Vec { + let location = xcm::VersionedInteriorMultiLocation::V3(destination); + let xcm = xcm::VersionedXcm::::V3(xcm_message); + // this is the `BridgeMessage` from polkadot xcm builder, but it has no constructor + // or public fields, so just tuple + // (double encoding, because `.encode()` is called on original Xcm BLOB when it is pushed + // to the storage) + (location, xcm).encode().encode() +} + +/// Helper that creates InitializationData mock data, that can be used to initialize bridge +/// GRANDPA pallet +pub fn initialization_data< + Runtime: pallet_bridge_grandpa::Config, + GrandpaPalletInstance: 'static, +>( + block_number: u32, +) -> bp_header_chain::InitializationData> { + bp_header_chain::InitializationData { + header: Box::new(bp_test_utils::test_header(block_number.into())), + authority_list: authority_list(), + set_id: 1, + operating_mode: BasicOperatingMode::Normal, + } +} + +/// Dummy xcm +pub(crate) fn dummy_xcm() -> Xcm<()> { + vec![Trap(42)].into() +} + +pub(crate) fn dispatch_message( + lane_id: LaneId, + nonce: MessageNonce, + payload: Vec, +) -> DispatchMessage> { + DispatchMessage { + key: MessageKey { lane_id, nonce }, + data: DispatchMessageData { payload: Ok(payload) }, + } +} + +/// Macro used for simulate_export_message and capturing bytes +macro_rules! grab_haul_blob ( + ($name:ident, $grabbed_payload:ident) => { + std::thread_local! { + static $grabbed_payload: std::cell::RefCell>> = std::cell::RefCell::new(None); + } + + struct $name; + impl HaulBlob for $name { + fn haul_blob(blob: Vec) -> Result<(), HaulBlobError>{ + $grabbed_payload.with(|rm| *rm.borrow_mut() = Some(blob)); + Ok(()) + } + } + } +); + +/// Simulates `HaulBlobExporter` and all its wrapping and captures generated plain bytes, +/// which are transferred over bridge. +pub(crate) fn simulate_message_exporter_on_bridged_chain< + SourceNetwork: Get, + DestinationNetwork: Get, + DestinationVersion: GetVersion, +>( + (destination_network, destination_junctions): (NetworkId, Junctions), +) -> Vec { + grab_haul_blob!(GrabbingHaulBlob, GRABBED_HAUL_BLOB_PAYLOAD); + + // lets pretend that some parachain on bridged chain exported the message + let universal_source_on_bridged_chain = + X2(GlobalConsensus(SourceNetwork::get()), Parachain(5678)); + let channel = 1_u32; + + // simulate XCM message export + let (ticket, fee) = validate_export::< + HaulBlobExporter, + >( + destination_network, + channel, + universal_source_on_bridged_chain, + destination_junctions, + dummy_xcm(), + ) + .expect("validate_export to pass"); + log::info!( + target: "simulate_message_exporter_on_bridged_chain", + "HaulBlobExporter::validate fee: {:?}", + fee + ); + let xcm_hash = + HaulBlobExporter::::deliver( + ticket, + ) + .expect("deliver to pass"); + log::info!( + target: "simulate_message_exporter_on_bridged_chain", + "HaulBlobExporter::deliver xcm_hash: {:?}", + xcm_hash + ); + + GRABBED_HAUL_BLOB_PAYLOAD.with(|r| r.take().expect("Encoded message should be here")) +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 06f0a6239e3a6db668bf5db3d37923fd7f62a6cf..dd526a9e044cde5db6ad47084d14ac146c5ee8d2 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Westend Collectives Parachain Runtime" +[lints] +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" } @@ -21,6 +24,7 @@ frame-system = { path = "../../../../../substrate/frame/system", default-feature 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-asset-rate = { path = "../../../../../substrate/frame/asset-rate", default-features = false } pallet-alliance = { path = "../../../../../substrate/frame/alliance", default-features = false } pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } @@ -34,6 +38,7 @@ pallet-session = { path = "../../../../../substrate/frame/session", default-feat 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-treasury = { path = "../../../../../substrate/frame/treasury", default-features = false } pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } pallet-referenda = { path = "../../../../../substrate/frame/referenda", default-features = false } pallet-ranked-collective = { path = "../../../../../substrate/frame/ranked-collective", default-features = false } @@ -77,7 +82,6 @@ pallet-collator-selection = { path = "../../../../pallets/collator-selection", d pallet-collective-content = { path = "../../../pallets/collective-content", default-features = false } parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } -testnets-common = { path = "../../../testnets-common", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } @@ -98,6 +102,7 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-alliance/runtime-benchmarks", + "pallet-asset-rate/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-collective-content/runtime-benchmarks", @@ -112,13 +117,13 @@ runtime-benchmarks = [ "pallet-salary/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "testnets-common/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] @@ -132,6 +137,7 @@ try-runtime = [ "frame-system/try-runtime", "frame-try-runtime/try-runtime", "pallet-alliance/try-runtime", + "pallet-asset-rate/try-runtime", "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", @@ -150,6 +156,7 @@ try-runtime = [ "pallet-session/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", + "pallet-treasury/try-runtime", "pallet-utility/try-runtime", "pallet-xcm/try-runtime", "parachain-info/try-runtime", @@ -174,6 +181,7 @@ std = [ "frame-try-runtime?/std", "log/std", "pallet-alliance/std", + "pallet-asset-rate/std", "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", @@ -193,6 +201,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", + "pallet-treasury/std", "pallet-utility/std", "pallet-xcm/std", "parachain-info/std", @@ -216,7 +225,6 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", - "testnets-common/std", "westend-runtime-constants/std", "xcm-builder/std", "xcm-executor/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 18c1466bf3624088ded34e4442691d65e6d59afd..9f6378de53a000f3a9cc474b82c5c5d142aef95b 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs @@ -32,13 +32,12 @@ pub mod origins; mod tracks; use super::*; -use crate::xcm_config::{FellowshipAdminBodyId, WndAssetHub}; +use crate::xcm_config::{FellowshipAdminBodyId, LocationToAccountId, WndAssetHub}; use frame_support::traits::{EitherOf, MapSuccess, TryMapSuccess}; pub use origins::pallet_origins as pallet_ambassador_origins; use origins::pallet_origins::{ EnsureAmbassadorsVoice, EnsureAmbassadorsVoiceFrom, EnsureHeadAmbassadorsVoice, Origin, }; -use parachains_common::polkadot::account; use sp_core::ConstU128; use sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace}; use xcm::prelude::*; @@ -114,9 +113,6 @@ parameter_types! { pub const AlarmInterval: BlockNumber = 1; pub const SubmissionDeposit: Balance = 0; pub const UndecidingTimeout: BlockNumber = 7 * DAYS; - // The Ambassador Referenda pallet account, used as a temporary place to deposit a slashed - // imbalance before teleport to the treasury. - pub AmbassadorPalletAccount: AccountId = account::AMBASSADOR_REFERENDA_PALLET_ID.into_account_truncating(); } pub type AmbassadorReferendaInstance = pallet_referenda::Instance2; @@ -136,7 +132,7 @@ impl pallet_referenda::Config for Runtime { >; type CancelOrigin = EitherOf, EnsureHeadAmbassadorsVoice>; type KillOrigin = EitherOf, EnsureHeadAmbassadorsVoice>; - type Slash = ToParentTreasury; + type Slash = ToParentTreasury; type Votes = pallet_ranked_collective::Votes; type Tally = pallet_ranked_collective::TallyOf; type SubmissionDeposit = SubmissionDeposit; 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 b7412705dde79df9af77b56dad99c420b8b5bc58..f49306bf8e3af16b7c006bb53b2fc526b64cc5db 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -19,30 +19,43 @@ mod origins; mod tracks; use crate::{ - impls::ToParentTreasury, weights, - xcm_config::{FellowshipAdminBodyId, UsdtAssetHub}, - AccountId, Balance, Balances, FellowshipReferenda, GovernanceLocation, Preimage, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Scheduler, WestendTreasuryAccount, DAYS, + xcm_config::{FellowshipAdminBodyId, LocationToAccountId, TreasurerBodyId, UsdtAssetHub}, + AccountId, AssetRate, Balance, Balances, FellowshipReferenda, GovernanceLocation, Preimage, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, Scheduler, WestendTreasuryAccount, DAYS, }; use frame_support::{ parameter_types, - traits::{EitherOf, EitherOfDiverse, MapSuccess, OriginTrait, TryWithMorphedArg}, + traits::{ + EitherOf, EitherOfDiverse, MapSuccess, NeverEnsureOrigin, OriginTrait, TryWithMorphedArg, + }, + PalletId, }; -use frame_system::EnsureRootWithSuccess; +use frame_system::{EnsureRoot, EnsureRootWithSuccess}; pub use origins::{ pallet_origins as pallet_fellowship_origins, Architects, EnsureCanPromoteTo, EnsureCanRetainAt, EnsureFellowship, Fellows, Masters, Members, ToVoice, }; use pallet_ranked_collective::EnsureOfRank; use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -use parachains_common::{polkadot::account, HOURS}; +use parachains_common::{ + impls::ToParentTreasury, + westend::{account, currency::GRAND}, +}; +use polkadot_runtime_common::impls::{ + LocatableAssetConverter, VersionedLocatableAsset, VersionedMultiLocationConverter, +}; +use sp_arithmetic::Permill; use sp_core::{ConstU128, ConstU32}; -use sp_runtime::traits::{AccountIdConversion, ConstU16, ConvertToValue, Replace, TakeFirst}; +use sp_runtime::traits::{ConstU16, ConvertToValue, IdentityLookup, Replace, TakeFirst}; +use westend_runtime_constants::time::HOURS; +use xcm::prelude::*; use xcm_builder::{AliasesIntoAccountId32, PayOverXcm}; #[cfg(feature = "runtime-benchmarks")] use crate::impls::benchmarks::{OpenHrmpChannel, PayWithEnsure}; +#[cfg(feature = "runtime-benchmarks")] +use parachains_common::westend::currency::DOLLARS; /// The Fellowship members' ranks. pub mod ranks { @@ -59,11 +72,6 @@ pub mod ranks { pub const DAN_9: Rank = 9; } -parameter_types! { - // Referenda pallet account, used to temporarily deposit slashed imbalance before teleporting. - pub ReferendaPalletAccount: AccountId = account::REFERENDA_PALLET_ID.into_account_truncating(); -} - impl pallet_fellowship_origins::Config for Runtime {} pub type FellowshipReferendaInstance = pallet_referenda::Instance1; @@ -90,7 +98,7 @@ impl pallet_referenda::Config for Runtime { >; type CancelOrigin = Architects; type KillOrigin = Masters; - type Slash = ToParentTreasury; + type Slash = ToParentTreasury; type Votes = pallet_ranked_collective::Votes; type Tally = pallet_ranked_collective::TallyOf; type SubmissionDeposit = ConstU128<0>; @@ -191,8 +199,6 @@ impl pallet_core_fellowship::Config for Runtime { pub type FellowshipSalaryInstance = pallet_salary::Instance1; -use xcm::prelude::*; - parameter_types! { // The interior location on AssetHub for the paying account. This is the Fellowship Salary // pallet instance (which sits at index 64). This sovereign account will need funding. @@ -236,3 +242,102 @@ impl pallet_salary::Config for Runtime { // Total monthly salary budget. type Budget = ConstU128<{ 100_000 * USDT_UNITS }>; } + +parameter_types! { + pub const FellowshipTreasuryPalletId: PalletId = account::FELLOWSHIP_TREASURY_PALLET_ID; + pub const HundredPercent: Permill = Permill::from_percent(100); + pub const Burn: Permill = Permill::from_percent(0); + pub const MaxBalance: Balance = Balance::max_value(); + // The asset's interior location for the paying account. This is the Fellowship Treasury + // pallet instance (which sits at index 65). + pub FellowshipTreasuryInteriorLocation: InteriorMultiLocation = PalletInstance(65).into(); +} + +#[cfg(feature = "runtime-benchmarks")] +parameter_types! { + // Benchmark bond. Needed to make `propose_spend` work. + pub const TenPercent: Permill = Permill::from_percent(10); + // Benchmark minimum. Needed to make `propose_spend` work. + pub const BenchmarkProposalBondMinimum: Balance = 1 * DOLLARS; + // Benchmark maximum. Needed to make `propose_spend` work. + pub const BenchmarkProposalBondMaximum: Balance = 10 * DOLLARS; +} + +/// [`PayOverXcm`] setup to pay the Fellowship Treasury. +pub type FellowshipTreasuryPaymaster = PayOverXcm< + FellowshipTreasuryInteriorLocation, + crate::xcm_config::XcmRouter, + crate::PolkadotXcm, + ConstU32<{ 6 * HOURS }>, + VersionedMultiLocation, + VersionedLocatableAsset, + LocatableAssetConverter, + VersionedMultiLocationConverter, +>; + +pub type FellowshipTreasuryInstance = pallet_treasury::Instance1; + +impl pallet_treasury::Config for Runtime { + // The creation of proposals via the treasury pallet is deprecated and should not be utilized. + // Instead, public or fellowship referenda should be used to propose and command the treasury + // spend or spend_local dispatchables. The parameters below have been configured accordingly to + // discourage its use. + // TODO: replace with `NeverEnsure` once polkadot-sdk 1.5 is released. + type ApproveOrigin = NeverEnsureOrigin<()>; + type OnSlash = (); + #[cfg(not(feature = "runtime-benchmarks"))] + type ProposalBond = HundredPercent; + #[cfg(not(feature = "runtime-benchmarks"))] + type ProposalBondMinimum = MaxBalance; + #[cfg(not(feature = "runtime-benchmarks"))] + type ProposalBondMaximum = MaxBalance; + + #[cfg(feature = "runtime-benchmarks")] + type ProposalBond = TenPercent; + #[cfg(feature = "runtime-benchmarks")] + type ProposalBondMinimum = BenchmarkProposalBondMinimum; + #[cfg(feature = "runtime-benchmarks")] + type ProposalBondMaximum = BenchmarkProposalBondMaximum; + // end. + + type WeightInfo = weights::pallet_treasury::WeightInfo; + type PalletId = FellowshipTreasuryPalletId; + type Currency = Balances; + type RejectOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse>, Fellows>, + >; + type RuntimeEvent = RuntimeEvent; + type SpendPeriod = ConstU32<{ 7 * DAYS }>; + type Burn = Burn; + type BurnDestination = (); + type SpendFunds = (); + type MaxApprovals = ConstU32<100>; + type SpendOrigin = EitherOf< + EitherOf< + EnsureRootWithSuccess, + MapSuccess< + EnsureXcm>, + Replace>, + >, + >, + EitherOf< + MapSuccess>>, + MapSuccess>>, + >, + >; + type AssetKind = VersionedLocatableAsset; + type Beneficiary = VersionedMultiLocation; + type BeneficiaryLookup = IdentityLookup; + #[cfg(not(feature = "runtime-benchmarks"))] + type Paymaster = FellowshipTreasuryPaymaster; + #[cfg(feature = "runtime-benchmarks")] + type Paymaster = PayWithEnsure>>; + type BalanceConverter = AssetRate; + type PayoutPeriod = ConstU32<{ 30 * DAYS }>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::TreasuryArguments< + sp_core::ConstU8<1>, + ConstU32<1000>, + >; +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs index 9f4c2a6a4c94a5d28fce09dbe800b2d7226723cf..caf0cddec664a55ce080ed6052a9cdf42a5e2f28 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/impls.rs @@ -16,15 +16,12 @@ use crate::OriginCaller; use frame_support::{ dispatch::DispatchResultWithPostInfo, - traits::{Currency, Get, Imbalance, OnUnbalanced, OriginTrait, PrivilegeCmp}, + traits::{Currency, PrivilegeCmp}, weights::Weight, }; -use log; use pallet_alliance::{ProposalIndex, ProposalProvider}; -use parachains_common::impls::NegativeImbalance; use sp_runtime::DispatchError; use sp_std::{cmp::Ordering, marker::PhantomData, prelude::*}; -use xcm::latest::{Fungibility, Junction, Parent}; type AccountIdOf = ::AccountId; @@ -36,51 +33,6 @@ type HashOf = ::Hash; pub type BalanceOf = as Currency<::AccountId>>::Balance; -/// Implements `OnUnbalanced::on_unbalanced` to teleport slashed assets to relay chain treasury -/// account. -pub struct ToParentTreasury( - PhantomData<(TreasuryAccount, PalletAccount, T)>, -); - -impl OnUnbalanced> - for ToParentTreasury -where - T: pallet_balances::Config + pallet_xcm::Config + frame_system::Config, - <::RuntimeOrigin as OriginTrait>::AccountId: From>, - [u8; 32]: From<::AccountId>, - TreasuryAccount: Get>, - PalletAccount: Get>, - BalanceOf: Into, -{ - fn on_unbalanced(amount: NegativeImbalance) { - let amount = match amount.drop_zero() { - Ok(..) => return, - Err(amount) => amount, - }; - let imbalance = amount.peek(); - let pallet_acc: AccountIdOf = PalletAccount::get(); - let treasury_acc: AccountIdOf = TreasuryAccount::get(); - - >::resolve_creating(&pallet_acc, amount); - - let result = >::teleport_assets( - <::RuntimeOrigin>::signed(pallet_acc.into()), - Box::new(Parent.into()), - Box::new( - Junction::AccountId32 { network: None, id: treasury_acc.into() } - .into_location() - .into(), - ), - Box::new((Parent, imbalance).into()), - 0, - ); - - if let Err(err) = result { - log::warn!("Failed to teleport slashed assets: {:?}", err); - } - } -} - /// Proposal provider for alliance pallet. /// Adapter from collective pallet to alliance proposal provider trait. pub struct AllianceProposalProvider(PhantomData<(T, I)>); @@ -157,6 +109,7 @@ pub mod benchmarks { use frame_support::traits::{ fungible, tokens::{Pay, PaymentStatus}, + Get, }; use pallet_ranked_collective::Rank; use parachains_common::{AccountId, Balance}; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 842ddbe54f2fcfaecdb4825c14a405c224f948c0..6cb8e096e4b3ffe3eaa488cd2a72482fe6b8415c 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -46,7 +46,7 @@ pub use ambassador::pallet_ambassador_origins; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use fellowship::{pallet_fellowship_origins, Fellows}; -use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp, ToParentTreasury}; +use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp}; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ @@ -81,20 +81,26 @@ use frame_system::{ }; pub use parachains_common as common; use parachains_common::{ - impls::DealWithFees, 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, + impls::{DealWithFees, ToParentTreasury}, + message_queue::*, + westend::{account::*, consensus::*, currency::*, fee::WeightToFee}, + AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, + SLOT_DURATION, }; use sp_runtime::RuntimeDebug; -use testnets_common::westend::{account::*, consensus::*, currency::*, fee::WeightToFee}; -use xcm_config::{GovernanceLocation, XcmOriginToTransactDispatchOrigin}; +use xcm_config::{ + GovernanceLocation, LocationToAccountId, TreasurerBodyId, XcmOriginToTransactDispatchOrigin, +}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; // Polkadot imports use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; -use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use polkadot_runtime_common::{ + impls::VersionedLocatableAsset, BlockHashCount, SlowAdjustingFeeUpdate, +}; use xcm::latest::{prelude::*, BodyId}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; @@ -110,7 +116,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_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, @@ -323,6 +329,7 @@ impl InstanceFilter for ProxyType { RuntimeCall::FellowshipReferenda { .. } | RuntimeCall::FellowshipCore { .. } | RuntimeCall::FellowshipSalary { .. } | + RuntimeCall::FellowshipTreasury { .. } | RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), @@ -532,9 +539,6 @@ pub const MAX_ALLIES: u32 = 100; parameter_types! { pub const AllyDeposit: Balance = 1_000 * UNITS; // 1,000 WND bond to join as an Ally - // The Alliance pallet account, used as a temporary place to deposit a slashed imbalance - // before the teleport to the Treasury. - pub AlliancePalletAccount: AccountId = ALLIANCE_PALLET_ID.into_account_truncating(); pub WestendTreasuryAccount: AccountId = WESTEND_TREASURY_PALLET_ID.into_account_truncating(); // The number of blocks a member must wait between giving a retirement notice and retiring. // Supposed to be greater than time required to `kick_member` with alliance motion. @@ -548,7 +552,7 @@ impl pallet_alliance::Config for Runtime { type MembershipManager = RootOrAllianceTwoThirdsMajority; type AnnouncementOrigin = RootOrAllianceTwoThirdsMajority; type Currency = Balances; - type Slashed = ToParentTreasury; + type Slashed = ToParentTreasury; type InitializeMembers = AllianceMotion; type MembershipChanged = AllianceMotion; type RetirementPeriod = AllianceRetirementPeriod; @@ -611,6 +615,21 @@ impl pallet_preimage::Config for Runtime { >; } +impl pallet_asset_rate::Config for Runtime { + type WeightInfo = weights::pallet_asset_rate::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type CreateOrigin = EitherOfDiverse< + EnsureRoot, + EitherOfDiverse>, Fellows>, + >; + type RemoveOrigin = Self::CreateOrigin; + type UpdateOrigin = Self::CreateOrigin; + type Currency = Balances; + type AssetKind = VersionedLocatableAsset; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = polkadot_runtime_common::impls::benchmarks::AssetRateArguments; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -646,6 +665,7 @@ construct_runtime!( Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 42, Preimage: pallet_preimage::{Pallet, Call, Storage, Event, HoldReason} = 43, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 44, + AssetRate: pallet_asset_rate::{Pallet, Call, Storage, Event} = 45, // The main stage. @@ -663,6 +683,8 @@ construct_runtime!( FellowshipCore: pallet_core_fellowship::::{Pallet, Call, Storage, Event} = 63, // pub type FellowshipSalaryInstance = pallet_salary::Instance1; FellowshipSalary: pallet_salary::::{Pallet, Call, Storage, Event} = 64, + // pub type FellowshipTreasuryInstance = pallet_treasury::Instance1; + FellowshipTreasury: pallet_treasury::::{Pallet, Call, Storage, Event} = 65, // Ambassador Program. AmbassadorCollective: pallet_ranked_collective::::{Pallet, Call, Storage, Event} = 70, @@ -742,6 +764,8 @@ mod benches { [pallet_collective_content, AmbassadorContent] [pallet_core_fellowship, AmbassadorCore] [pallet_salary, AmbassadorSalary] + [pallet_treasury, FellowshipTreasury] + [pallet_asset_rate, AssetRate] ); } @@ -969,6 +993,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on Collectives. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Collectives only supports teleports to system parachain. + // Relay/native token can be teleported between Collectives and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } let whitelist: Vec = vec![ diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs index b6f1dc8dc08038a8c614f10914f9fd8c14fb10ca..f43c5e0a40b6356f3caee233ad1af7289a95a7df 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system.rs @@ -151,4 +151,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs index d49a2905e7f7f71c247d0aba6add7ce2a0e73caa..a9a298e547edb49738b9a0612d04d4141301d4ba 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_alliance; +pub mod pallet_asset_rate; pub mod pallet_balances; pub mod pallet_collator_selection; pub mod pallet_collective; @@ -38,6 +39,7 @@ pub mod pallet_salary_fellowship_salary; pub mod pallet_scheduler; pub mod pallet_session; pub mod pallet_timestamp; +pub mod pallet_treasury; pub mod pallet_utility; pub mod pallet_xcm; pub mod paritydb_weights; @@ -45,5 +47,4 @@ pub mod rocksdb_weights; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_asset_rate.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_asset_rate.rs new file mode 100644 index 0000000000000000000000000000000000000000..51b0580f8575f643d48f4a094d8a6fb2548acc80 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_asset_rate.rs @@ -0,0 +1,85 @@ +// Copyright 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_asset_rate` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-28, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/polkadot-parachain +// benchmark +// pallet +// --chain=collectives-westend-dev +// --steps=2 +// --repeat=2 +// --pallet=pallet-asset-rate +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rate`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rate::WeightInfo for WeightInfo { + /// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1) + /// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(1238), added: 3713, mode: `MaxEncodedLen`) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `6` + // Estimated: `4703` + // Minimum execution time: 102_000_000 picoseconds. + Weight::from_parts(112_000_000, 0) + .saturating_add(Weight::from_parts(0, 4703)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1) + /// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(1238), added: 3713, mode: `MaxEncodedLen`) + fn update() -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `4703` + // Minimum execution time: 101_000_000 picoseconds. + Weight::from_parts(105_000_000, 0) + .saturating_add(Weight::from_parts(0, 4703)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1) + /// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(1238), added: 3713, mode: `MaxEncodedLen`) + fn remove() -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `4703` + // Minimum execution time: 112_000_000 picoseconds. + Weight::from_parts(116_000_000, 0) + .saturating_add(Weight::from_parts(0, 4703)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_treasury.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_treasury.rs new file mode 100644 index 0000000000000000000000000000000000000000..58540e646d8c3885bf84da512e2ddc3e42abc80e --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_treasury.rs @@ -0,0 +1,214 @@ +// Copyright 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_treasury` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-28, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/polkadot-parachain +// benchmark +// pallet +// --chain=collectives-westend-dev +// --steps=2 +// --repeat=2 +// --pallet=pallet-treasury +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./cumulus/parachains/runtimes/collectives/collectives-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_treasury`. +pub struct WeightInfo(PhantomData); +impl pallet_treasury::WeightInfo for WeightInfo { + /// Storage: `FellowshipTreasury::ProposalCount` (r:1 w:1) + /// Proof: `FellowshipTreasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Approvals` (r:1 w:1) + /// Proof: `FellowshipTreasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Proposals` (r:0 w:1) + /// Proof: `FellowshipTreasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn spend_local() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `1887` + // Minimum execution time: 117_000_000 picoseconds. + Weight::from_parts(126_000_000, 0) + .saturating_add(Weight::from_parts(0, 1887)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `FellowshipTreasury::ProposalCount` (r:1 w:1) + /// Proof: `FellowshipTreasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Proposals` (r:0 w:1) + /// Proof: `FellowshipTreasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + fn propose_spend() -> Weight { + // Proof Size summary in bytes: + // Measured: `143` + // Estimated: `1489` + // Minimum execution time: 264_000_000 picoseconds. + Weight::from_parts(277_000_000, 0) + .saturating_add(Weight::from_parts(0, 1489)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `FellowshipTreasury::Proposals` (r:1 w:1) + /// Proof: `FellowshipTreasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn reject_proposal() -> Weight { + // Proof Size summary in bytes: + // Measured: `301` + // Estimated: `3593` + // Minimum execution time: 289_000_000 picoseconds. + Weight::from_parts(312_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// The range of component `p` is `[0, 99]`. + fn approve_proposal(_p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `FellowshipTreasury::Approvals` (r:1 w:1) + /// Proof: `FellowshipTreasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + fn remove_approval() -> Weight { + // Proof Size summary in bytes: + // Measured: `127` + // Estimated: `1887` + // Minimum execution time: 62_000_000 picoseconds. + Weight::from_parts(65_000_000, 0) + .saturating_add(Weight::from_parts(0, 1887)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:199 w:199) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Deactivated` (r:1 w:1) + /// Proof: `FellowshipTreasury::Deactivated` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Balances::InactiveIssuance` (r:1 w:1) + /// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Approvals` (r:1 w:1) + /// Proof: `FellowshipTreasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Proposals` (r:99 w:99) + /// Proof: `FellowshipTreasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`) + /// The range of component `p` is `[0, 99]`. + fn on_initialize_proposals(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `145 + p * (250 ±0)` + // Estimated: `256707 + p * (5206 ±0)` + // Minimum execution time: 218_000_000 picoseconds. + Weight::from_parts(221_000_000, 0) + .saturating_add(Weight::from_parts(0, 256707)) + // Standard Error: 154_515 + .saturating_add(Weight::from_parts(399_232_323, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into()))) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into())) + } + /// Storage: `AssetRate::ConversionRateToNative` (r:1 w:0) + /// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(1238), added: 3713, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::SpendCount` (r:1 w:1) + /// Proof: `FellowshipTreasury::SpendCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `FellowshipTreasury::Spends` (r:0 w:1) + /// Proof: `FellowshipTreasury::Spends` (`max_values`: None, `max_size`: Some(1853), added: 4328, mode: `MaxEncodedLen`) + fn spend() -> Weight { + // Proof Size summary in bytes: + // Measured: `118` + // Estimated: `4703` + // Minimum execution time: 163_000_000 picoseconds. + Weight::from_parts(171_000_000, 0) + .saturating_add(Weight::from_parts(0, 4703)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `FellowshipTreasury::Spends` (r:1 w:1) + /// Proof: `FellowshipTreasury::Spends` (`max_values`: None, `max_size`: Some(1853), added: 4328, 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::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`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::RelevantMessagingState` (r:1 w:0) + /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn payout() -> Weight { + // Proof Size summary in bytes: + // Measured: `629` + // Estimated: `5318` + // Minimum execution time: 472_000_000 picoseconds. + Weight::from_parts(492_000_000, 0) + .saturating_add(Weight::from_parts(0, 5318)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(6)) + } + /// Storage: `FellowshipTreasury::Spends` (r:1 w:1) + /// Proof: `FellowshipTreasury::Spends` (`max_values`: None, `max_size`: Some(1853), added: 4328, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::Queries` (r:1 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn check_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `383` + // Estimated: `5318` + // Minimum execution time: 211_000_000 picoseconds. + Weight::from_parts(215_000_000, 0) + .saturating_add(Weight::from_parts(0, 5318)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `FellowshipTreasury::Spends` (r:1 w:1) + /// Proof: `FellowshipTreasury::Spends` (`max_values`: None, `max_size`: Some(1853), added: 4328, mode: `MaxEncodedLen`) + fn void_spend() -> Weight { + // Proof Size summary in bytes: + // Measured: `179` + // Estimated: `5318` + // Minimum execution time: 124_000_000 picoseconds. + Weight::from_parts(126_000_000, 0) + .saturating_add(Weight::from_parts(0, 5318)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index a3b42cb86c407a05ae205062ffff5a41a4cb7392..50dfbffde01f21d1138f0fdaa27649f962e245e4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-r43aesjn-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: @@ -64,22 +64,37 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 25_746_000 picoseconds. - Weight::from_parts(26_349_000, 0) + // Minimum execution time: 24_540_000 picoseconds. + Weight::from_parts(25_439_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .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: `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`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `1489` - // Minimum execution time: 22_660_000 picoseconds. - Weight::from_parts(23_173_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 86_614_000 picoseconds. + Weight::from_parts(88_884_000, 0) + .saturating_add(Weight::from_parts(0, 3679)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -91,6 +106,32 @@ 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: `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`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 87_915_000 picoseconds. + Weight::from_parts(90_219_000, 0) + .saturating_add(Weight::from_parts(0, 3679)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { @@ -107,8 +148,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_321_000 picoseconds. - Weight::from_parts(7_542_000, 0) + // Minimum execution time: 6_872_000 picoseconds. + Weight::from_parts(7_110_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_232_000 picoseconds. - Weight::from_parts(2_395_000, 0) + // Minimum execution time: 2_009_000 picoseconds. + Weight::from_parts(2_163_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -145,8 +186,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 29_006_000 picoseconds. - Weight::from_parts(29_777_000, 0) + // Minimum execution time: 28_858_000 picoseconds. + Weight::from_parts(29_355_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -171,8 +212,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 31_245_000 picoseconds. - Weight::from_parts(32_125_000, 0) + // Minimum execution time: 30_598_000 picoseconds. + Weight::from_parts(31_168_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -183,8 +224,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_255_000 picoseconds. - Weight::from_parts(2_399_000, 0) + // Minimum execution time: 2_090_000 picoseconds. + Weight::from_parts(2_253_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -194,8 +235,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `162` // Estimated: `11052` - // Minimum execution time: 16_521_000 picoseconds. - Weight::from_parts(17_001_000, 0) + // Minimum execution time: 16_133_000 picoseconds. + Weight::from_parts(16_433_000, 0) .saturating_add(Weight::from_parts(0, 11052)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -206,8 +247,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `166` // Estimated: `11056` - // Minimum execution time: 16_486_000 picoseconds. - Weight::from_parts(16_729_000, 0) + // Minimum execution time: 16_012_000 picoseconds. + Weight::from_parts(16_449_000, 0) .saturating_add(Weight::from_parts(0, 11056)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -218,8 +259,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `13538` - // Minimum execution time: 18_037_000 picoseconds. - Weight::from_parts(18_310_000, 0) + // Minimum execution time: 17_922_000 picoseconds. + Weight::from_parts(18_426_000, 0) .saturating_add(Weight::from_parts(0, 13538)) .saturating_add(T::DbWeight::get().reads(5)) } @@ -241,8 +282,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 27_901_000 picoseconds. - Weight::from_parts(28_566_000, 0) + // Minimum execution time: 27_280_000 picoseconds. + Weight::from_parts(28_026_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) @@ -253,8 +294,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `206` // Estimated: `8621` - // Minimum execution time: 9_299_000 picoseconds. - Weight::from_parts(9_547_000, 0) + // Minimum execution time: 9_387_000 picoseconds. + Weight::from_parts(9_644_000, 0) .saturating_add(Weight::from_parts(0, 8621)) .saturating_add(T::DbWeight::get().reads(3)) } @@ -264,8 +305,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `173` // Estimated: `11063` - // Minimum execution time: 16_768_000 picoseconds. - Weight::from_parts(17_215_000, 0) + // Minimum execution time: 16_649_000 picoseconds. + Weight::from_parts(17_025_000, 0) .saturating_add(Weight::from_parts(0, 11063)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -288,8 +329,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `215` // Estimated: `11105` - // Minimum execution time: 35_134_000 picoseconds. - Weight::from_parts(35_883_000, 0) + // Minimum execution time: 34_355_000 picoseconds. + Weight::from_parts(35_295_000, 0) .saturating_add(Weight::from_parts(0, 11105)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) @@ -302,8 +343,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_562_000 picoseconds. - Weight::from_parts(4_802_000, 0) + // Minimum execution time: 4_527_000 picoseconds. + Weight::from_parts(4_699_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -314,8 +355,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 26_865_000 picoseconds. - Weight::from_parts(27_400_000, 0) + // Minimum execution time: 27_011_000 picoseconds. + Weight::from_parts(27_398_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .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 cefc099c96f9f3031448fa3f7b810a6c9c7aaba8..2e64127d6a1dec7e7e31613b3ed91ca0551ffe15 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -27,26 +27,29 @@ use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, - xcm_config::{ConcreteAssetFromSystem, RelayOrOtherSystemParachains}, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use westend_runtime_constants::system_parachain; +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, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, - LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, LocatableAssetId, + OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; -const FELLOWSHIP_ADMIN_INDEX: u32 = 1; - parameter_types! { pub const WndLocation: MultiLocation = MultiLocation::parent(); pub const RelayNetwork: Option = Some(NetworkId::Westend); @@ -56,7 +59,8 @@ parameter_types! { pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); - pub const FellowshipAdminBodyId: BodyId = BodyId::Index(FELLOWSHIP_ADMIN_INDEX); + pub const FellowshipAdminBodyId: BodyId = BodyId::Index(xcm_constants::body::FELLOWSHIP_ADMIN_INDEX); + pub const TreasurerBodyId: BodyId = BodyId::Index(xcm_constants::body::TREASURER_INDEX); pub AssetHub: MultiLocation = (Parent, Parachain(1000)).into(); pub AssetHubUsdtId: AssetId = (PalletInstance(50), GeneralIndex(1984)).into(); pub UsdtAssetHub: LocatableAssetId = LocatableAssetId { @@ -82,6 +86,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -136,10 +141,6 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; - pub type ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; } /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly @@ -165,19 +166,14 @@ impl Contains for SafeCallFilter { 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 { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | - RuntimeCall::CollatorSelection( - pallet_collator_selection::Call::set_desired_candidates { .. } | - pallet_collator_selection::Call::set_candidacy_bond { .. } | - pallet_collator_selection::Call::register_as_candidate { .. } | - pallet_collator_selection::Call::leave_intent { .. } | - pallet_collator_selection::Call::set_invulnerables { .. } | - pallet_collator_selection::Call::add_invulnerable { .. } | - pallet_collator_selection::Call::remove_invulnerable { .. }, - ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | RuntimeCall::PolkadotXcm( pallet_xcm::Call::force_xcm_version { .. } | pallet_xcm::Call::force_default_xcm_version { .. } @@ -241,7 +237,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>, @@ -250,24 +246,13 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::BRIDGE_HUB_ID | - system_parachain::COLLECTIVES_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); /// Cases where a remote origin is accepted as trusted Teleporter for a given asset: /// - DOT with the parent Relay Chain and sibling parachains. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index ca45f4760d091a0eb2114c9127394e605512e211..54af73c3d03dd78bd21affd35bbdcae8d1be5664 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index 6c100deaa9e44e87e259bc141b082c90bd0eb7ca..94f2d34b265a8e66feddaa01d524ab7c02cc5549 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -26,7 +26,7 @@ use pallet_contracts::{ }; use sp_runtime::Perbill; -pub use parachains_common::{rococo::currency::deposit, AVERAGE_ON_INITIALIZE_RATIO}; +pub use parachains_common::rococo::currency::deposit; // Prints debug output of the `contracts` pallet to stdout if the node is // started with `-lruntime::contracts=debug`. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 81cc8558a2b6a4b2109bf3a42996c7fceaa9e7dc..79b6b6be299be1b40fddfd324b68b1e53b9777d6 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -133,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_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -723,6 +723,18 @@ impl_runtime_apis! { // Reserve transfers are disabled on Contracts-System-Para. None } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Contracts-System-Para only supports teleports to system parachain. + // Relay/native token can be teleported between Contracts-System-Para and Relay. + let native_location = Parent.into(); + let dest = Parent.into(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } let whitelist: Vec = vec![ diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs index 30fa2c4060689ff98cc427c84f81866172845e52..b473d49e20e67329d893e1e565330cbe9290c64f 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/weights/mod.rs @@ -24,5 +24,4 @@ pub mod rocksdb_weights; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; 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 2ac93aed3f8d24eb1c3c0a7b6fd028620553484f..569ca6e587c5778a881634bdab441b7a6b938645 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -27,22 +27,26 @@ use frame_support::{ use frame_system::EnsureRoot; use pallet_xcm::{EnsureXcm, IsMajorityOfBody, XcmPassthrough}; use parachains_common::{ - xcm_config::{ConcreteAssetFromSystem, RelayOrOtherSystemParachains}, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; -use rococo_runtime_constants::system_parachain; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; +#[allow(deprecated)] +use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, - DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, - NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -75,6 +79,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -123,10 +128,6 @@ match_types! { MultiLocation { parents: 1, interior: Here } | MultiLocation { parents: 1, interior: X1(Plurality { .. }) } }; - pub type ParentOrSiblings: impl Contains = { - MultiLocation { parents: 1, interior: Here } | - MultiLocation { parents: 1, interior: X1(_) } - }; } pub type Barrier = TrailingSetTopicAsId< @@ -149,7 +150,7 @@ pub type Barrier = TrailingSetTopicAsId< Equals, )>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -158,25 +159,13 @@ pub type Barrier = TrailingSetTopicAsId< >, >; -match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { - parents: 1, - interior: X1(Parachain( - system_parachain::ASSET_HUB_ID | - system_parachain::BRIDGE_HUB_ID | - system_parachain::CONTRACTS_ID | - system_parachain::ENCOINTER_ID - )), - } - }; -} - /// Locations that will not be charged fees in the executor, /// either execution or delivery. /// We only waive fees for system functions, which these locations represent. -pub type WaivedLocations = - (RelayOrOtherSystemParachains, Equals); +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); pub type TrustedTeleporter = ConcreteAssetFromSystem; diff --git a/cumulus/parachains/runtimes/coretime/README.md b/cumulus/parachains/runtimes/coretime/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3f09e57c7d40d3b5161254df4ae543388947aef0 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/README.md @@ -0,0 +1,4 @@ +# Coretime System Chain + +Also known as the "Broker Chain". Described in +[RFC-0001](https://github.com/polkadot-fellows/RFCs/pull/1). diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0af44045da4975da4910c793e10657cd684d38ca --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -0,0 +1,196 @@ +[package] +name = "coretime-rococo-runtime" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Rococo's Coretime parachain runtime" +license = "Apache-2.0" + +[lints] +workspace = true + +[build-dependencies] +substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } + +[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 } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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-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 } +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-inherents = { path = "../../../../../substrate/primitives/inherents", default-features = false } +sp-genesis-builder = { path = "../../../../../substrate/primitives/genesis-builder", default-features = false } +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 } +rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", 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 = "../../../../pallets/aura-ext", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +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-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 } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../common", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-utility/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "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", + "pallet-utility/std", + "pallet-xcm-benchmarks?/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", + "scale-info/std", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/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", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/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", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +experimental = ["pallet-aura/experimental"] diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..60f8a125129ff1344a1799246e931acdb1d139d5 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/build.rs @@ -0,0 +1,26 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "std")] +fn main() { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..a85d67f7b4cb815a3a17b159e1ba85677221f64a --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -0,0 +1,227 @@ +// 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 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 = 74)] + 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(BlockNumber), + #[codec(index = 3)] + CreditAccount(AccountId, Balance), + #[codec(index = 4)] + AssignCore(CoreIndex, 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 RealyChainBlockNumberProvider = RelaychainDataProvider; + + fn request_core_count(count: CoreIndex) { + use crate::coretime::CoretimeProviderCalls::RequestCoreCount; + let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + + 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_core_count_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, MultiLocation::parent(), message.clone()) { + Ok(_) => log::info!( + 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, MultiLocation::parent(), message.clone()) { + Ok(_) => log::info!( + 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, MultiLocation::parent(), message.clone()) { + Ok(_) => log::info!( + 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)); + + 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: assign_core_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, MultiLocation::parent(), message.clone()) { + Ok(_) => log::info!( + 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; + type TimeslicePeriod = ConstU32<80>; + type MaxLeasedCores = ConstU32<50>; + 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-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..95709117578d1d3ae68cea9742939aa98c15b1f4 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -0,0 +1,851 @@ +// 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)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +mod coretime; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::{ + construct_runtime, derive_impl, + dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, + parameter_types, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + rococo::{consensus::*, currency::*, fee::WeightToFee}, + AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, +}; +use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, MultiAddress, Perbill, +}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::*; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, RocRelayLocation, XcmOriginToTransactDispatchOrigin, +}; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; + +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +/// Migrations to apply on runtime upgrade. +pub type Migrations = (cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4,); + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[sp_version::runtime_version] +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_005_004, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 0, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u8 = 42; +} + +// Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The nonce type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The block type. + type Block = Block; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Runtime version. + type Version = Version; + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = weights::frame_system::WeightInfo; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = RuntimeBlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = RuntimeBlockLength; + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = weights::pallet_timestamp::WeightInfo; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = weights::pallet_balances::WeightInfo; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10 + pub const TransactionByteFee: Balance = MILLICENTS; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + type OutboundXcmpMessageSource = XcmpQueue; + 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, + >; +} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = weights::pallet_message_queue::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< + cumulus_primitives_core::AggregateMessageOrigin, + >; + #[cfg(not(feature = "runtime-benchmarks"))] + type MessageProcessor = xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >; + type Size = u32; + // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: + type QueueChangeHandler = NarrowOriginToSibling; + type QueuePausedQuery = NarrowOriginToSibling; + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +parameter_types! { + /// Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +/// Privileged origin that represents Root or Fellows pluralistic body. +pub type RootOrFellows = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +parameter_types! { + /// The asset ID for the asset that we use to pay for message delivery fees. + pub FeeAssetId: AssetId = Concrete(RocRelayLocation::get()); + /// The base fee for the message delivery fees. + pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); +} + +pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + XcmpQueue, +>; + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChannelInfo = ParachainSystem; + type VersionWrapper = PolkadotXcm; + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; + type ControllerOrigin = RootOrFellows; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; +} + +pub const PERIOD: u32 = 6 * HOURS; +pub const OFFSET: u32 = 0; + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU32>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU32>; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session::WeightInfo; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + /// StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow Root and the `StakingAdmin` to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // should be a multiple of session or things will get inconsistent + type KickThreshold = ConstU32; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = weights::pallet_collator_selection::WeightInfo; +} + +parameter_types! { + /// One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. + pub const DepositBase: Balance = deposit(1, 88); + /// Additional storage item size of 32 bytes. + pub const DepositFactor: Balance = deposit(0, 32); +} + +impl pallet_multisig::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositFactor = DepositFactor; + type MaxSignatories = ConstU32<100>; + type WeightInfo = weights::pallet_multisig::WeightInfo; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + 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 + { + // System support stuff. + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 3, + ParachainInfo: parachain_info = 4, + + // Monetary stuff. + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + + // Collator support. The order of these 5 are important and shall not change. + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, + + // XCM & related + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + MessageQueue: pallet_message_queue = 34, + + // Handy utilities. + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + + // The main stage. + Broker: pallet_broker = 50, + + // Sudo + Sudo: pallet_sudo = 100, + } +); + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + frame_benchmarking::define_benchmarks!( + [frame_system, SystemBench::] + [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] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + [pallet_message_queue, MessageQueue] + [pallet_multisig, Multisig] + [pallet_utility, Utility] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + ); +} + +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()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; + use sp_storage::TrackedStorageKey; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime { + fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), BenchmarkError> { + ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); + Ok(()) + } + + fn verify_set_code() { + System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); + } + } + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + use xcm::latest::prelude::*; + use xcm_config::RocRelayLocation; + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + MultiAsset { + fun: Fungible(EXISTENTIAL_DEPOSIT), + id: Concrete(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Reserve transfers are disabled + None + } + } + + parameter_types! { + pub ExistentialDepositMultiAsset: Option = Some(( + RocRelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositMultiAsset, + xcm_config::PriceForParentDelivery, + >; + type AccountIdConverter = xcm_config::LocationToAccountId; + fn valid_destination() -> Result { + Ok(RocRelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + MultiAsset { + id: Concrete(RocRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + RocRelayLocation::get(), + MultiAsset { fun: Fungible(UNITS), id: Concrete(RocRelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_multi_asset() -> MultiAsset { + MultiAsset { + id: Concrete(RocRelayLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type RuntimeCall = RuntimeCall; + type TransactAsset = Balances; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((RocRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(RocRelayLocation::get()) + } + + fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + let origin = RocRelayLocation::get(); + let assets: MultiAssets = (Concrete(RocRelayLocation::get()), 1_000 * UNITS).into(); + let ticket = MultiLocation { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + create_default_config::() + } + + fn build_config(config: Vec) -> sp_genesis_builder::Result { + build_config::(config) + } + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2092d875c8328210667da4cbb95de0642e60ae3 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/block_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Importing a block with 0 Extrinsics. + pub const BlockExecutionWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(5_000_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..f7a1486ed58972ffb430578f63a1326852e2f74d --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,71 @@ +// 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 `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_parachain_system +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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 `cumulus_pallet_parachain_system`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_parachain_system::WeightInfo for WeightInfo { + /// 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: `42` + // Estimated: `3517` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(144_747_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1004)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..f5683f747a3ab1915e0b3fd41b9032db7f13b91a --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,152 @@ +// 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 `cumulus_pallet_xcmp_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_xcmp_queue +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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 `cumulus_pallet_xcmp_queue`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { + /// 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: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`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: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn enqueue_xcmp_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3517` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(13_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn suspend_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn resume_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn take_first_concatenated_xcm() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(42_000_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: 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) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65711` + // Estimated: `69176` + // Minimum execution time: 86_000_000 picoseconds. + Weight::from_parts(86_000_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) + /// 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: 79_000_000 picoseconds. + Weight::from_parts(79_000_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-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..332c3b324bb9c1b386257bf7953d37aba8f5af13 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/extrinsic_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Executing a NO-OP `System::remarks` Extrinsic. + pub const ExtrinsicBaseWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(125_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..7c41112152f9e03d82ff8254f2f9519bf270a11f --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system.rs @@ -0,0 +1,168 @@ +// 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 `frame_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=frame_system +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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 `frame_system`. +pub struct WeightInfo(PhantomData); +impl frame_system::WeightInfo for WeightInfo { + /// The range of component `b` is `[0, 3932160]`. + fn remark(_b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(775_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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: 8_000_000 picoseconds. + Weight::from_parts(4_700_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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: 5_000_000 picoseconds. + Weight::from_parts(5_000_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 { + // Proof Size summary in bytes: + // Measured: `164` + // Estimated: `1649` + // Minimum execution time: 79_510_000_000 picoseconds. + Weight::from_parts(79_510_000_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::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: 4_000_000 picoseconds. + Weight::from_parts(816_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1000)) + } + /// 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: 3_000_000 picoseconds. + Weight::from_parts(598_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1000)) + } + /// 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: `55 + p * (69 ±0)` + // Estimated: `69609` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(1_091_000_000, 0) + .saturating_add(Weight::from_parts(0, 69609)) + .saturating_add(T::DbWeight::get().reads(1000)) + .saturating_add(T::DbWeight::get().writes(1000)) + } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f1050b3ae636261ff21674c3bb34c05bf6d232c5 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Expose the auto generated weight files. + +pub mod block_weights; +pub mod cumulus_pallet_parachain_system; +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; +pub mod pallet_session; +pub mod pallet_timestamp; +pub mod pallet_utility; +pub mod pallet_xcm; +pub mod paritydb_weights; +pub mod rocksdb_weights; +pub mod xcm; + +pub use block_weights::constants::BlockExecutionWeight; +pub use extrinsic_weights::constants::ExtrinsicBaseWeight; +pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee12da7c436fb24cfd208a7398b0a018312e5390 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_balances.rs @@ -0,0 +1,147 @@ +// 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_balances` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_balances +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_balances`. +pub struct WeightInfo(PhantomData); +impl pallet_balances::WeightInfo for WeightInfo { + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 55_000_000 picoseconds. + Weight::from_parts(55_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 42_000_000 picoseconds. + Weight::from_parts(42_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `3593` + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(15_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `3593` + // Minimum execution time: 21_000_000 picoseconds. + Weight::from_parts(21_000_000, 0) + .saturating_add(Weight::from_parts(0, 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`) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 66_000_000 picoseconds. + Weight::from_parts(66_000_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_all() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 53_000_000 picoseconds. + Weight::from_parts(53_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `3593` + // Minimum execution time: 22_000_000 picoseconds. + Weight::from_parts(22_000_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `System::Account` (r:1000 w:1000) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(_u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (135 ±0)` + // Estimated: `2603990` + // Minimum execution time: 20_000_000 picoseconds. + Weight::from_parts(14_684_000_000, 0) + .saturating_add(Weight::from_parts(0, 2603990)) + .saturating_add(T::DbWeight::get().reads(1000)) + .saturating_add(T::DbWeight::get().writes(1000)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs new file mode 100644 index 0000000000000000000000000000000000000000..665b84e32ccc0a19668196341440e56354d6374b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -0,0 +1,511 @@ +// 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 4.0.0-dev +//! DATE: 2023-12-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-q7z7ruxr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_broker +// --chain=coretime-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_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: 2_403_000 picoseconds. + Weight::from_parts(2_504_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: 22_025_000 picoseconds. + Weight::from_parts(22_799_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: 21_012_000 picoseconds. + Weight::from_parts(21_567_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(401), added: 896, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `466` + // Estimated: `1951` + // Minimum execution time: 10_767_000 picoseconds. + Weight::from_parts(11_364_000, 0) + .saturating_add(Weight::from_parts(0, 1951)) + .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: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`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(401), added: 896, 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:60) + /// 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: `12567` + // Estimated: `14052` + // Minimum execution time: 112_187_000 picoseconds. + Weight::from_parts(115_233_014, 0) + .saturating_add(Weight::from_parts(0, 14052)) + // Standard Error: 162 + .saturating_add(Weight::from_parts(539, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(66)) + } + /// 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: 32_469_000 picoseconds. + Weight::from_parts(33_443_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: 59_488_000 picoseconds. + Weight::from_parts(64_711_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: 13_370_000 picoseconds. + Weight::from_parts(13_938_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_592_000 picoseconds. + Weight::from_parts(15_235_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:2) + /// 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: 14_880_000 picoseconds. + Weight::from_parts(15_274_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::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: `936` + // Estimated: `4681` + // Minimum execution time: 24_786_000 picoseconds. + Weight::from_parts(26_047_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: `1002` + // Estimated: `5996` + // Minimum execution time: 31_159_000 picoseconds. + Weight::from_parts(31_770_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: 56_524_000 picoseconds. + Weight::from_parts(58_065_019, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 41_840 + .saturating_add(Weight::from_parts(1_322_201, 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: 60_923_000 picoseconds. + Weight::from_parts(62_721_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_683_000 picoseconds. + Weight::from_parts(55_799_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: 90_833_000 picoseconds. + Weight::from_parts(97_249_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: 93_311_000 picoseconds. + Weight::from_parts(105_496_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: `957` + // Estimated: `4698` + // Minimum execution time: 44_597_000 picoseconds. + Weight::from_parts(48_739_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: 22_114_000 picoseconds. + Weight::from_parts(23_031_633, 0) + .saturating_add(Weight::from_parts(0, 3539)) + // Standard Error: 60 + .saturating_add(Weight::from_parts(60, 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: 6_020_000 picoseconds. + Weight::from_parts(6_540_421, 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: 38_744_000 picoseconds. + Weight::from_parts(40_572_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(401), added: 896, 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:60) + /// 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: `12514` + // Estimated: `13506` + // Minimum execution time: 94_727_000 picoseconds. + Weight::from_parts(97_766_746, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(65)) + } + /// 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: 6_496_000 picoseconds. + Weight::from_parts(6_757_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: 33_164_000 picoseconds. + Weight::from_parts(33_800_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: 16_884_000 picoseconds. + Weight::from_parts(17_315_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`) + /// The range of component `n` is `[0, 1000]`. + fn notify_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_017_000 picoseconds. + Weight::from_parts(2_210_693, 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: 12_118_000 picoseconds. + Weight::from_parts(12_541_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-rococo/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_collator_selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca740bc3550f3761a32489bf59220d5d155b5233 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_collator_selection.rs @@ -0,0 +1,265 @@ +// 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_collator_selection` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_collator_selection +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_collator_selection`. +pub struct WeightInfo(PhantomData); +impl pallet_collator_selection::WeightInfo for WeightInfo { + /// 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: `162 + b * (79 ±0)` + // Estimated: `52242` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(74_000_000, 0) + .saturating_add(Weight::from_parts(0, 52242)) + .saturating_add(T::DbWeight::get().reads(20)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: `796 + b * (32 ±0) + c * (52 ±0)` + // Estimated: `6287 + b * (32 ±0) + c * (53 ±0)` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(37_903_628, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 96_225 + .saturating_add(Weight::from_parts(55_555, 0).saturating_mul(b.into())) + // Standard Error: 17_673 + .saturating_add(Weight::from_parts(40_816, 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, 32).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: 12_000_000 picoseconds. + Weight::from_parts(12_000_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: 6_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: `0 + c * (179 ±0) + k * (130 ±0)` + // Estimated: `261290` + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(11_000_000, 0) + .saturating_add(Weight::from_parts(0, 261290)) + // Standard Error: 5_514_936 + .saturating_add(Weight::from_parts(6_438_000, 0).saturating_mul(c.into())) + // Standard Error: 5_514_936 + .saturating_add(Weight::from_parts(6_368_000, 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()))) + } + /// 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: `243 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 26_000_000 picoseconds. + Weight::from_parts(36_000_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + .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: `441 + c * (54 ±0)` + // Estimated: `9299` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(40_000_000, 0) + .saturating_add(Weight::from_parts(0, 9299)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// 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: `589 + c * (54 ±0)` + // Estimated: `9521` + // Minimum execution time: 54_000_000 picoseconds. + Weight::from_parts(59_000_000, 0) + .saturating_add(Weight::from_parts(0, 9521)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// 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: `268 + c * (48 ±0)` + // Estimated: `6287` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(38_000_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + .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`) + fn note_author() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(49_000_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// 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:1 w:1) + /// 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: `340 + c * (97 ±0)` + // Estimated: `6287 + c * (2519 ±0)` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(11_136_363, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 323_666 + .saturating_add(Weight::from_parts(35_353, 0).saturating_mul(r.into())) + // Standard Error: 323_666 + .saturating_add(Weight::from_parts(4_328_282, 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(4)) + .saturating_add(Weight::from_parts(0, 2519).saturating_mul(c.into())) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0e8c79ca17fceabbf794832a0f5e0736a754d72 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_message_queue.rs @@ -0,0 +1,178 @@ +// 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_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_message_queue +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_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`) + fn ready_ring_knit() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `6044` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(13_000_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`) + fn ready_ring_unknit() -> Weight { + // Proof Size summary in bytes: + // Measured: `218` + // Estimated: `6044` + // Minimum execution time: 12_000_000 picoseconds. + Weight::from_parts(12_000_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`) + fn service_queue_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `6` + // Estimated: `3517` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_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`) + fn service_page_base_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(7_000_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`) + fn service_page_base_no_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(7_000_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 65_000_000 picoseconds. + Weight::from_parts(65_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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: `171` + // Estimated: `3517` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_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`) + fn reap_page() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 73_000_000 picoseconds. + Weight::from_parts(73_000_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`) + fn execute_overweight_page_removed() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 74_000_000 picoseconds. + Weight::from_parts(74_000_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`) + fn execute_overweight_page_updated() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `69050` + // Minimum execution time: 109_000_000 picoseconds. + Weight::from_parts(109_000_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs new file mode 100644 index 0000000000000000000000000000000000000000..421fc033e7c47dd4dd6d3932f8291a0607684f69 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs @@ -0,0 +1,150 @@ +// 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_multisig` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_multisig +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_multisig`. +pub struct WeightInfo(PhantomData); +impl pallet_multisig::WeightInfo for WeightInfo { + /// The range of component `z` is `[0, 10000]`. + fn as_multi_threshold_1(_z: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(16_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. + fn as_multi_create(_s: u32, z: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `172 + s * (3 ±0)` + // Estimated: `6811` + // Minimum execution time: 35_000_000 picoseconds. + Weight::from_parts(36_530_612, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 259 + .saturating_add(Weight::from_parts(1_650, 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`) + /// 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: 21_000_000 picoseconds. + Weight::from_parts(18_422_680, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 8_928 + .saturating_add(Weight::from_parts(25_773, 0).saturating_mul(s.into())) + // Standard Error: 86 + .saturating_add(Weight::from_parts(1_250, 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`) + /// 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: `315 + s * (34 ±0)` + // Estimated: `6811` + // Minimum execution time: 53_000_000 picoseconds. + Weight::from_parts(56_571_428, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 86 + .saturating_add(Weight::from_parts(150, 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`) + /// The range of component `s` is `[2, 100]`. + fn approve_as_multi_create(_s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `172 + s * (3 ±0)` + // Estimated: `6811` + // Minimum execution time: 32_000_000 picoseconds. + Weight::from_parts(35_000_000, 0) + .saturating_add(Weight::from_parts(0, 6811)) + .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`) + /// 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: 17_000_000 picoseconds. + Weight::from_parts(21_000_000, 0) + .saturating_add(Weight::from_parts(0, 6811)) + .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`) + /// The range of component `s` is `[2, 100]`. + fn cancel_as_multi(_s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `379 + s * (2 ±0)` + // Estimated: `6811` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(40_000_000, 0) + .saturating_add(Weight::from_parts(0, 6811)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs new file mode 100644 index 0000000000000000000000000000000000000000..5151bcaa9e4eb4e9bec6baeee1c51d3d920d1474 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_session.rs @@ -0,0 +1,78 @@ +// 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_session` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_session +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_session`. +pub struct WeightInfo(PhantomData); +impl pallet_session::WeightInfo for WeightInfo { + /// 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: `270` + // Estimated: `3735` + // Minimum execution time: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 0) + .saturating_add(Weight::from_parts(0, 3735)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// 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: `242` + // Estimated: `3707` + // Minimum execution time: 13_000_000 picoseconds. + Weight::from_parts(13_000_000, 0) + .saturating_add(Weight::from_parts(0, 3707)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2a23bf2a73b2ff2704b640d827efb60dfad61ca --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_timestamp.rs @@ -0,0 +1,72 @@ +// 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_timestamp` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_timestamp +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_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`) + fn set() -> Weight { + // Proof Size summary in bytes: + // Measured: `49` + // Estimated: `1493` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_000, 0) + .saturating_add(Weight::from_parts(0, 1493)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `57` + // Estimated: `0` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs new file mode 100644 index 0000000000000000000000000000000000000000..cf3cc98b593f8457b9d392c132e14a738f392a71 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_utility.rs @@ -0,0 +1,93 @@ +// 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_utility` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_utility +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_utility`. +pub struct WeightInfo(PhantomData); +impl pallet_utility::WeightInfo for WeightInfo { + /// The range of component `c` is `[0, 1000]`. + fn batch(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(4_117_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn as_derivative() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(7_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn batch_all(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(4_519_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(_c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(4_114_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs new file mode 100644 index 0000000000000000000000000000000000000000..538401ef2c577c5b82de14acc7d58ceffd6a1668 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs @@ -0,0 +1,333 @@ +// 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_xcm` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-12-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `dagda.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-rococo-dev +// --wasm-execution=compiled +// --pallet=pallet_xcm +// --extrinsic=* +// --steps=2 +// --repeat=1 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-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_xcm`. +pub struct WeightInfo(PhantomData); +impl pallet_xcm::WeightInfo for WeightInfo { + /// 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: `74` + // Estimated: `3539` + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(37_000_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: `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: `106` + // Estimated: `3571` + // Minimum execution time: 86_000_000 picoseconds. + Weight::from_parts(86_000_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: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn reserve_transfer_assets() -> Weight { + // Proof Size summary in bytes: + // 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: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn transfer_assets() -> Weight { + // Proof Size summary in bytes: + // 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: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn execute() -> Weight { + // Proof Size summary in bytes: + // 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: `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: 13_000_000 picoseconds. + Weight::from_parts(13_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// 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: `74` + // Estimated: `3539` + // Minimum execution time: 37_000_000 picoseconds. + Weight::from_parts(37_000_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: `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: `292` + // Estimated: `3757` + // Minimum execution time: 72_000_000 picoseconds. + Weight::from_parts(72_000_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: `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: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:4 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: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifiers` (r:4 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: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 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: 22_000_000 picoseconds. + Weight::from_parts(22_000_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(5)) + } + /// 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: `142` + // Estimated: `6082` + // Minimum execution time: 32_000_000 picoseconds. + Weight::from_parts(32_000_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: `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: 12_000_000 picoseconds. + Weight::from_parts(12_000_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) + .saturating_add(T::DbWeight::get().reads(3)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 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: 19_000_000 picoseconds. + Weight::from_parts(19_000_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 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: `148` + // Estimated: `11038` + // Minimum execution time: 39_000_000 picoseconds. + Weight::from_parts(39_000_000, 0) + .saturating_add(Weight::from_parts(0, 11038)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`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 new_query() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(5_000_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::Queries` (r:1 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn take_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `7669` + // Estimated: `11134` + // Minimum execution time: 31_000_000 picoseconds. + Weight::from_parts(31_000_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-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..4338d928d807a41cc60ec91d86e91c81bb253631 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/paritydb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d115d963facb39fe29d6258918fda3bc8d94900 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/rocksdb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..2319c2e3a5b2ec60ebb77065106991a093e20f08 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -0,0 +1,244 @@ +// 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 . + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, DoubleEncoded}; + +trait WeighMultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight; +} + +const MAX_ASSETS: u64 = 100; + +impl WeighMultiAssets for MultiAssetFilter { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighMultiAssets for MultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct CoretimeRococoXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for CoretimeRococoXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + } + fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + } + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() + } + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset( + assets: &MultiAssets, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _require_weight_at_most: &Weight, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() + } + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()) + } + fn deposit_reserve_asset( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &MultiAssetFilter, + _reserve: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + } + fn initiate_teleport( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::::expect_transact_status() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + XcmGeneric::::universal_origin() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 0000000000000000000000000000000000000000..7fab35842509deceba14e89e5bbf6bebe2240528 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,201 @@ +// 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_xcm_benchmarks::fungible` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::fungible +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn withdraw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 21_643_000 picoseconds. + Weight::from_parts(22_410_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`) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `6196` + // Minimum execution time: 43_758_000 picoseconds. + Weight::from_parts(44_654_000, 6196) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `System::Account` (r:3 w:3) + // 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: `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 transfer_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `246` + // Estimated: `8799` + // Minimum execution time: 87_978_000 picoseconds. + Weight::from_parts(88_517_000, 8799) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(5)) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + pub fn reserve_asset_deposited() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1489` + // Minimum execution time: 6_883_000 picoseconds. + Weight::from_parts(6_979_000, 1489) + .saturating_add(T::DbWeight::get().reads(1)) + } + // 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: 198_882_000 picoseconds. + Weight::from_parts(199_930_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn receive_teleported_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_343_000 picoseconds. + Weight::from_parts(3_487_000, 0) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 19_399_000 picoseconds. + Weight::from_parts(19_659_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: `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: 59_017_000 picoseconds. + Weight::from_parts(60_543_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .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: `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: 45_409_000 picoseconds. + Weight::from_parts(47_041_000, 3610) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..4454494badcbfe9b4f429312e24b63786b83ef75 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,355 @@ +// 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_xcm_benchmarks::generic` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +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: 440_298_000 picoseconds. + Weight::from_parts(446_508_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn buy_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_313_000 picoseconds. + Weight::from_parts(3_422_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: 9_691_000 picoseconds. + Weight::from_parts(9_948_000, 3568) + .saturating_add(T::DbWeight::get().reads(1)) + } + pub fn transact() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 10_384_000 picoseconds. + Weight::from_parts(11_085_000, 0) + } + pub fn refund_surplus() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_438_000 picoseconds. + Weight::from_parts(3_577_000, 0) + } + pub fn set_error_handler() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_126_000 picoseconds. + Weight::from_parts(2_243_000, 0) + } + pub fn set_appendix() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_126_000 picoseconds. + Weight::from_parts(2_207_000, 0) + } + pub fn clear_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_105_000 picoseconds. + Weight::from_parts(2_193_000, 0) + } + pub fn descend_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_999_000 picoseconds. + Weight::from_parts(3_056_000, 0) + } + pub fn clear_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_091_000 picoseconds. + Weight::from_parts(2_176_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: 55_728_000 picoseconds. + Weight::from_parts(56_704_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // 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_839_000 picoseconds. + Weight::from_parts(13_457_000, 3625) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_116_000 picoseconds. + Weight::from_parts(2_219_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) + // 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`) + pub fn subscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `145` + // Estimated: `3610` + // Minimum execution time: 24_891_000 picoseconds. + Weight::from_parts(25_583_000, 3610) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) + // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn unsubscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_968_000 picoseconds. + Weight::from_parts(4_122_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: 136_220_000 picoseconds. + Weight::from_parts(137_194_000, 0) + } + pub fn expect_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_343_000 picoseconds. + Weight::from_parts(12_635_000, 0) + } + pub fn expect_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_237_000 picoseconds. + Weight::from_parts(2_315_000, 0) + } + pub fn expect_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_094_000 picoseconds. + Weight::from_parts(2_231_000, 0) + } + pub fn expect_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_379_000 picoseconds. + Weight::from_parts(2_455_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: 60_734_000 picoseconds. + Weight::from_parts(61_964_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn expect_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_500_000 picoseconds. + Weight::from_parts(5_720_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: 55_767_000 picoseconds. + Weight::from_parts(56_790_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn clear_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_201_000 picoseconds. + Weight::from_parts(2_291_000, 0) + } + pub fn set_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_164_000 picoseconds. + Weight::from_parts(2_241_000, 0) + } + pub fn clear_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_127_000 picoseconds. + Weight::from_parts(2_236_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` + // Estimated: `0` + // Minimum execution time: 2_132_000 picoseconds. + Weight::from_parts(2_216_000, 0) + } + pub fn unpaid_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_265_000 picoseconds. + Weight::from_parts(2_332_000, 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 new file mode 100644 index 0000000000000000000000000000000000000000..00bbe5b5037f9ebfcb1f519c79725f91c80b7e03 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -0,0 +1,295 @@ +// Copyright 2023 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 super::{ + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, WeightToFee, XcmpQueue, +}; +use frame_support::{ + match_types, parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, + TREASURY_PALLET_ID, +}; +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, IsConcrete, 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 RocRelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Rococo); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); + pub const FellowshipLocation: MultiLocation = MultiLocation::parent(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// Means for transacting the native currency on this chain. +#[allow(deprecated)] +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an `AccountId32` `MultiLocation` into a native chain + // `AccountId`: + LocationToAccountId, + // Our chain's `AccountId` type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// 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. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain that they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `RuntimeOrigin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // XCM origins can be represented natively under the XCM pallet's `Xcm` origin. + XcmPassthrough, +); + +match_types! { + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; +} + +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + matches!( + call, + RuntimeCall::PolkadotXcm( + pallet_xcm::Call::force_xcm_version { .. } | + 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 { .. } | + // 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::Sudo(..) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::XcmpQueue(..) | + RuntimeCall::Broker(..) + ) + } +} + +pub type Barrier = TrailingSetTopicAsId< + DenyThenTry< + DenyReserveTransferToRelayChain, + ( + // Allow local users to buy weight credit. + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Parent and its pluralities (i.e. governance bodies) get free execution. + AllowExplicitUnpaidExecutionFrom, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, +>; + +parameter_types! { + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); +} + +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. +/// We only waive fees for system functions, which these locations represent. +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = CurrencyTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + // 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 ROC. + type IsTeleporter = ConcreteAssetFromSystem; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CoretimeRococoXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = + UsingComponents>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; + type Aliasers = Nothing; +} + +/// Converts a local signed origin into an XCM multilocation. Forms the basis for local origins +/// sending/executing XCMs. +pub type LocalOriginToLocation = SignedToAccountId32; + +pub type PriceForParentDelivery = + ExponentialPrice; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +)>; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We want to disallow users sending (arbitrary) XCM programs from this chain. + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // We support local origins dispatching XCM executions in principle... + type ExecuteXcmOrigin = EnsureXcmOrigin; + // ... but disallow generic XCM execution. As a result only teleports are allowed. + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type Weigher = WeightInfoBounds< + crate::weights::xcm::CoretimeRococoXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + type AdminOrigin = EnsureRoot; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..02e130613f9c63896c54c075cc4c326586c14e37 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -0,0 +1,192 @@ +[package] +name = "coretime-westend-runtime" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Westend's Coretime parachain runtime" +license = "Apache-2.0" + +[lints] +workspace = true + +[build-dependencies] +substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } + +[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 } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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-message-queue = { path = "../../../../../substrate/frame/message-queue", 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 } +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-inherents = { path = "../../../../../substrate/primitives/inherents", default-features = false } +sp-genesis-builder = { path = "../../../../../substrate/primitives/genesis-builder", default-features = false } +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 } +westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants", 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 = "../../../../pallets/aura-ext", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +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-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 } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../common", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-utility/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/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", + "pallet-utility/std", + "pallet-xcm-benchmarks?/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", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", + "westend-runtime-constants/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/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", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/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", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +experimental = ["pallet-aura/experimental"] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..60f8a125129ff1344a1799246e931acdb1d139d5 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs @@ -0,0 +1,26 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "std")] +fn main() { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..742b3a29275cadcf51d8fd9a2d9af710dd6e6707 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -0,0 +1,850 @@ +// 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)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +// Make the WASM binary available. +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::{ + construct_runtime, derive_impl, + dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, + parameter_types, + traits::{ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin}, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + westend::{consensus::*, currency::*, fee::WeightToFee}, + AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, + AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, +}; +use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use sp_api::impl_runtime_apis; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, MultiAddress, Perbill, +}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::*; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, WndRelayLocation, XcmOriginToTransactDispatchOrigin, +}; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; + +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +/// Migrations to apply on runtime upgrade. +pub type Migrations = (); + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[sp_version::runtime_version] +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_005_000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 0, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u8 = 42; +} + +// Configure FRAME pallets to include in runtime. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The nonce type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The block type. + type Block = Block; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Runtime version. + type Version = Version; + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = weights::frame_system::WeightInfo; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = RuntimeBlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = RuntimeBlockLength; + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = weights::pallet_timestamp::WeightInfo; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = weights::pallet_balances::WeightInfo; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10 + pub const TransactionByteFee: Balance = MILLICENTS; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + type OutboundXcmpMessageSource = XcmpQueue; + 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, + >; +} + +impl parachain_info::Config for Runtime {} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = weights::pallet_message_queue::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< + cumulus_primitives_core::AggregateMessageOrigin, + >; + #[cfg(not(feature = "runtime-benchmarks"))] + type MessageProcessor = xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >; + type Size = u32; + // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: + type QueueChangeHandler = NarrowOriginToSibling; + type QueuePausedQuery = NarrowOriginToSibling; + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; +} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +parameter_types! { + /// Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +/// Privileged origin that represents Root or Fellows pluralistic body. +pub type RootOrFellows = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +parameter_types! { + /// The asset ID for the asset that we use to pay for message delivery fees. + pub FeeAssetId: AssetId = Concrete(WndRelayLocation::get()); + /// The base fee for the message delivery fees. + pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); +} + +pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + XcmpQueue, +>; + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChannelInfo = ParachainSystem; + type VersionWrapper = PolkadotXcm; + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; + type ControllerOrigin = RootOrFellows; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; +} + +pub const PERIOD: u32 = 6 * HOURS; +pub const OFFSET: u32 = 0; + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU32>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU32>; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session::WeightInfo; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + /// StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow Root and the `StakingAdmin` to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // should be a multiple of session or things will get inconsistent + type KickThreshold = ConstU32; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = weights::pallet_collator_selection::WeightInfo; +} + +parameter_types! { + /// One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. + pub const DepositBase: Balance = deposit(1, 88); + /// Additional storage item size of 32 bytes. + pub const DepositFactor: Balance = deposit(0, 32); +} + +impl pallet_multisig::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositFactor = DepositFactor; + type MaxSignatories = ConstU32<100>; + type WeightInfo = weights::pallet_multisig::WeightInfo; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + 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 + { + // System support stuff. + System: frame_system = 0, + ParachainSystem: cumulus_pallet_parachain_system = 1, + Timestamp: pallet_timestamp = 3, + ParachainInfo: parachain_info = 4, + + // Monetary stuff. + Balances: pallet_balances = 10, + TransactionPayment: pallet_transaction_payment = 11, + + // Collator support. The order of these 5 are important and shall not change. + Authorship: pallet_authorship = 20, + CollatorSelection: pallet_collator_selection = 21, + Session: pallet_session = 22, + Aura: pallet_aura = 23, + AuraExt: cumulus_pallet_aura_ext = 24, + + // XCM & related + XcmpQueue: cumulus_pallet_xcmp_queue = 30, + PolkadotXcm: pallet_xcm = 31, + CumulusXcm: cumulus_pallet_xcm = 32, + MessageQueue: pallet_message_queue = 34, + + // Handy utilities. + Utility: pallet_utility = 40, + Multisig: pallet_multisig = 41, + + // Sudo + Sudo: pallet_sudo = 100, + } +); + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + [frame_system, SystemBench::] + [cumulus_pallet_parachain_system, ParachainSystem] + [pallet_timestamp, Timestamp] + [pallet_balances, Balances] + [pallet_collator_selection, CollatorSelection] + [pallet_session, SessionBench::] + [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + [pallet_message_queue, MessageQueue] + [pallet_multisig, Multisig] + [pallet_utility, Utility] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + ); +} + +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()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; + use sp_storage::TrackedStorageKey; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime { + fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), BenchmarkError> { + ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); + Ok(()) + } + + fn verify_set_code() { + System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); + } + } + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + use xcm::latest::prelude::*; + use xcm_config::WndRelayLocation; + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Relay/native token can be teleported between AH and Relay. + Some(( + MultiAsset { + fun: Fungible(EXISTENTIAL_DEPOSIT), + id: Concrete(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Reserve transfers are disabled + None + } + } + + parameter_types! { + pub ExistentialDepositMultiAsset: Option = Some(( + WndRelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositMultiAsset, + xcm_config::PriceForParentDelivery, + >; + type AccountIdConverter = xcm_config::LocationToAccountId; + fn valid_destination() -> Result { + Ok(WndRelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + MultiAsset { + id: Concrete(WndRelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + WndRelayLocation::get(), + MultiAsset { fun: Fungible(UNITS), id: Concrete(WndRelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_multi_asset() -> MultiAsset { + MultiAsset { + id: Concrete(WndRelayLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type RuntimeCall = RuntimeCall; + type TransactAsset = Balances; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((WndRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(WndRelayLocation::get()) + } + + fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + let origin = WndRelayLocation::get(); + let assets: MultiAssets = (Concrete(WndRelayLocation::get()), 1_000 * UNITS).into(); + let ticket = MultiLocation { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + create_default_config::() + } + + fn build_config(config: Vec) -> sp_genesis_builder::Result { + build_config::(config) + } + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..2bd7975bf98c36996520716c9dc11822d8287234 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/block_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Importing a block with 0 Extrinsics. + pub const BlockExecutionWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(5_000_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } + } +} diff --git a/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 new file mode 100644 index 0000000000000000000000000000000000000000..0303151d7f83dfc5957e7346b1c4ef2950b6dc01 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,53 @@ +// Copyright 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 . + +#![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 `cumulus_pallet_xcmp_queue`. +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) + /// 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)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..124571118aa129e1489aaaf1ebeabbde41ed13c4 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,151 @@ +// Copyright 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 `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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=cumulus_pallet_xcmp_queue +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs + +#![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 `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) + 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) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`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: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn enqueue_xcmp_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3517` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn suspend_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn resume_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn take_first_concatenated_xcm() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 44_000_000 picoseconds. + Weight::from_parts(45_000_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: `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: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65711` + // Estimated: `69176` + // Minimum execution time: 67_000_000 picoseconds. + Weight::from_parts(73_000_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 { + // Proof Size summary in bytes: + // Measured: `65710` + // Estimated: `69175` + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(55_000_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/extrinsic_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..898d72ec5b19519a77ec0b75bb65d757213b35d4 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/extrinsic_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Executing a NO-OP `System::remarks` Extrinsic. + pub const ExtrinsicBaseWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(125_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..46f8113939e4d4fa3f26ff03d665eec6b4120a6b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs @@ -0,0 +1,161 @@ +// Copyright 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 `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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=frame_system +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs + +#![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 `frame_system`. +pub struct WeightInfo(PhantomData); +impl frame_system::WeightInfo for WeightInfo { + /// The range of component `b` is `[0, 3932160]`. + fn remark(b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_432_000 picoseconds. + Weight::from_parts(2_458_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(367, 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) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_405, 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) + 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) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn set_code() -> Weight { + Weight::from_parts(1_000_000, 0) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn set_storage(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_493_000 picoseconds. + Weight::from_parts(2_523_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())) + .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) + /// 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) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 784 + .saturating_add(Weight::from_parts(493_844, 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) + /// 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())) + .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())) + } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..28e708f0e2a33da7419fe821ab1fb72806c273b1 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -0,0 +1,39 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Expose the auto generated weight files. + +pub mod block_weights; +pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_xcmp_queue; +pub mod extrinsic_weights; +pub mod frame_system; +pub mod pallet_balances; +pub mod pallet_collator_selection; +pub mod pallet_message_queue; +pub mod pallet_multisig; +pub mod pallet_session; +pub mod pallet_timestamp; +pub mod pallet_utility; +pub mod pallet_xcm; +pub mod paritydb_weights; +pub mod rocksdb_weights; +pub mod xcm; + +pub use block_weights::constants::BlockExecutionWeight; +pub use extrinsic_weights::constants::ExtrinsicBaseWeight; +pub use rocksdb_weights::constants::RocksDbWeight; 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 new file mode 100644 index 0000000000000000000000000000000000000000..65d5a55c72eab716b3688bdd50fae38c67587287 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs @@ -0,0 +1,151 @@ +// Copyright 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_balances` +//! +//! 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: `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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_balances +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs + +#![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_balances`. +pub struct WeightInfo(PhantomData); +impl pallet_balances::WeightInfo for WeightInfo { + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 59_580_000 picoseconds. + Weight::from_parts(60_317_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 45_490_000 picoseconds. + Weight::from_parts(45_910_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 17_353_000 picoseconds. + Weight::from_parts(17_676_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 25_017_000 picoseconds. + Weight::from_parts(25_542_000, 0) + .saturating_add(Weight::from_parts(0, 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) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 61_161_000 picoseconds. + Weight::from_parts(61_665_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_all() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 55_422_000 picoseconds. + Weight::from_parts(55_880_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 20_477_000 picoseconds. + Weight::from_parts(20_871_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:999 w:999) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (136 ±0)` + // Estimated: `990 + u * (2603 ±0)` + // Minimum execution time: 19_501_000 picoseconds. + Weight::from_parts(19_726_000, 0) + .saturating_add(Weight::from_parts(0, 990)) + // Standard Error: 9_495 + .saturating_add(Weight::from_parts(15_658_957, 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())) + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..2adddecab264945884d8b4620b9dff9868bfc4f0 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs @@ -0,0 +1,243 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_collator_selection +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs + +#![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_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]`. + 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())) + .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())) + } + /// 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: 6_977_000 picoseconds. + Weight::from_parts(7_246_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .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 { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_937_000 picoseconds. + Weight::from_parts(8_161_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 { + // 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)) + } + fn update_bond(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn take_candidate_slot(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 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) + fn note_author() -> Weight { + // Proof Size summary in bytes: + // Measured: `155` + // Estimated: `6196` + // Minimum execution time: 44_415_000 picoseconds. + Weight::from_parts(44_732_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]`. + 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)) + .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())) + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..651f27e10e5c7b5d941d2bec5197fc06ed035fda --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,155 @@ +// Copyright 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 . + +#![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; + +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) + 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)) + .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) + 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)) + .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) + 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) + .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) + 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) + .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) + 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) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + 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) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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)) + .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) + 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)) + .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) + 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)) + .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) + 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)) + .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 new file mode 100644 index 0000000000000000000000000000000000000000..4130e05bf7c4233b5dfdd6fcf0df1295ce77db61 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -0,0 +1,163 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_multisig +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs + +#![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_multisig`. +pub struct WeightInfo(PhantomData); +impl pallet_multisig::WeightInfo for WeightInfo { + /// The range of component `z` is `[0, 10000]`. + fn as_multi_threshold_1(z: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 11_337_000 picoseconds. + Weight::from_parts(11_960_522, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 9 + .saturating_add(Weight::from_parts(504, 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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 41_128_000 picoseconds. + Weight::from_parts(35_215_592, 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())) + .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) + /// 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) + .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())) + .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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 45_716_000 picoseconds. + Weight::from_parts(38_332_947, 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())) + .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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 487 + .saturating_add(Weight::from_parts(67_443, 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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 434 + .saturating_add(Weight::from_parts(62_989, 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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 585 + .saturating_add(Weight::from_parts(69_979, 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 new file mode 100644 index 0000000000000000000000000000000000000000..d132ef17bbdb2295dcd4ee812ab62ae51fd6ff3a --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs @@ -0,0 +1,79 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_session +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs + +#![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_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) + 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)) + .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) + 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)) + .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 new file mode 100644 index 0000000000000000000000000000000000000000..722858a3a4655881cdbedbe8e6cae419baefc190 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs @@ -0,0 +1,73 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_timestamp +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs + +#![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_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) + fn set() -> Weight { + // Proof Size summary in bytes: + // Measured: `49` + // Estimated: `1493` + // Minimum execution time: 7_986_000 picoseconds. + Weight::from_parts(8_134_000, 0) + .saturating_add(Weight::from_parts(0, 1493)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `57` + // Estimated: `0` + // Minimum execution time: 3_257_000 picoseconds. + Weight::from_parts(3_366_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 new file mode 100644 index 0000000000000000000000000000000000000000..dacd469ebb7ab62eb7fcd7740bf6bf230aace7ee --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs @@ -0,0 +1,100 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_utility +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs + +#![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_utility`. +pub struct WeightInfo(PhantomData); +impl pallet_utility::WeightInfo for WeightInfo { + /// The range of component `c` is `[0, 1000]`. + fn batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_697_000 picoseconds. + Weight::from_parts(11_859_145, 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())) + } + 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) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn batch_all(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_741_000 picoseconds. + Weight::from_parts(15_928_547, 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())) + } + 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) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_814_000 picoseconds. + Weight::from_parts(13_920_831, 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())) + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..d96ee43463a316d3b8a0c5c8351045d7c340cd13 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -0,0 +1,323 @@ +// Copyright 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_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: `[]` +//! 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 + +// Executed Command: +// ./artifacts/westend-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs + +#![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_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) + 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)) + .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) + 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)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn reserve_transfer_assets() -> Weight { + // Proof Size summary in bytes: + // 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)) + } + 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)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn execute() -> Weight { + // Proof Size summary in bytes: + // 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: PolkadotXcm SupportedVersion (r:0 w:1) + /// Proof Skipped: 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) + .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) + 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) + .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) + 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)) + .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) + 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)) + .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) + 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) + .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) + 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)) + .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) + 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)) + .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) + 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)) + } + /// 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) + 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)) + .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) + 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)) + } + /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) + /// Proof Skipped: 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)) + .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) + 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)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`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 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)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::Queries` (r:1 w:1) + /// 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)) + .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/paritydb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..1c6d2ebe568cc81e91167ec723102eebde49259c --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/paritydb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa0cb2b4bc377bae5bce9b18cbb78820c91f344d --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/rocksdb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..3dc7b82efc2dbf8bc0704791974d0759e6ae5328 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -0,0 +1,244 @@ +// 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. + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, DoubleEncoded}; + +trait WeighMultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight; +} + +const MAX_ASSETS: u64 = 100; + +impl WeighMultiAssets for MultiAssetFilter { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighMultiAssets for MultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct CoretimeWestendXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for CoretimeWestendXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + } + fn reserve_asset_deposited(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::reserve_asset_deposited()) + } + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() + } + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset( + assets: &MultiAssets, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _require_weight_at_most: &Weight, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() + } + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()) + } + fn deposit_reserve_asset( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &MultiAssetFilter, + _reserve: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + } + fn initiate_teleport( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::::expect_transact_status() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + XcmGeneric::::universal_origin() + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..eaf07aac52cefa88f524e6f3a2180ab9faf2b088 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,200 @@ +// 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_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: `[]` +//! 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 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::fungible +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn withdraw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 20_295_000 picoseconds. + Weight::from_parts(21_142_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`) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `6196` + // Minimum execution time: 42_356_000 picoseconds. + Weight::from_parts(43_552_000, 6196) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `System::Account` (r:3 w:3) + // 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: `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 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)) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + 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)) + } + // 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)) + } + 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) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 18_583_000 picoseconds. + Weight::from_parts(19_057_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: `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)) + } + // 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)) + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..fc196abea0f5e61d746760e2b2bf5a7d8d0a476b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,354 @@ +// 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_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: `[]` +//! 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 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +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)) + } + 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) + } + // 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) + .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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + 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) + } + // 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)) + } + // 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) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_933_000 picoseconds. + Weight::from_parts(1_983_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) + // 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`) + 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)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) + // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn unsubscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_850_000 picoseconds. + Weight::from_parts(4_082_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) + } + 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) + } + 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) + } + 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) + } + 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) + } + // 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)) + } + 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) + } + // 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)) + } + 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) + } + 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) + } + 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)) + } + 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) + } + 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) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..59d76d10d902306a7c5c95f8cac032034d38f8b4 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -0,0 +1,296 @@ +// Copyright 2023 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 super::{ + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, + ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + TransactionByteFee, WeightToFee, XcmpQueue, +}; +use frame_support::{ + match_types, parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, + }, + TREASURY_PALLET_ID, +}; +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, IsConcrete, 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: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Westend); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub FellowshipLocation: MultiLocation = MultiLocation::new(1, Parachain(1001)); + pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); +} + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// Means for transacting the native currency on this chain. +#[allow(deprecated)] +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an `AccountId32` `MultiLocation` into a native chain + // `AccountId`: + LocationToAccountId, + // Our chain's `AccountId` type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// 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. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain that they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `RuntimeOrigin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // XCM origins can be represented natively under the XCM pallet's `Xcm` origin. + XcmPassthrough, +); + +match_types! { + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; + pub type FellowsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: X2(Parachain(1001), Plurality { id: BodyId::Technical, ..}) } + }; +} + +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + matches!( + call, + RuntimeCall::PolkadotXcm( + pallet_xcm::Call::force_xcm_version { .. } | + 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 { .. }, + ) | RuntimeCall::ParachainSystem(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Balances(..) | + RuntimeCall::CollatorSelection(..) | + RuntimeCall::Sudo(..) | + RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::XcmpQueue(..) + ) + } +} + +pub type Barrier = TrailingSetTopicAsId< + DenyThenTry< + DenyReserveTransferToRelayChain, + ( + // Allow local users to buy weight credit. + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Parent, its pluralities (i.e. governance bodies), and the Fellows plurality + // get free execution. + AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, +>; + +parameter_types! { + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: MultiLocation = (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); +} + +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. +/// We only waive fees for system functions, which these locations represent. +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = CurrencyTransactor; + type OriginConverter = XcmOriginToTransactDispatchOrigin; + // Coretime chain does not recognize a reserve location for any asset. Users must teleport WND + // where allowed (e.g. with the Relay Chain). + type IsReserve = (); + /// Only allow teleportation of WND. + type IsTeleporter = ConcreteAssetFromSystem; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CoretimeWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = + UsingComponents>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; + type Aliasers = Nothing; +} + +/// Converts a local signed origin into an XCM multilocation. Forms the basis for local origins +/// sending/executing XCMs. +pub type LocalOriginToLocation = SignedToAccountId32; + +pub type PriceForParentDelivery = + ExponentialPrice; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +)>; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We want to disallow users sending (arbitrary) XCM programs from this chain. + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // We support local origins dispatching XCM executions in principle... + type ExecuteXcmOrigin = EnsureXcmOrigin; + // ... but disallow generic XCM execution. As a result only teleports are allowed. + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type Weigher = WeightInfoBounds< + crate::weights::xcm::CoretimeWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + type AdminOrigin = EnsureRoot; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index b8efc4fbbcff24c7aaa0d865658df8a37ed75eda..831e3242766418fca9c6ed4d9a97e6ae037c4193 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Glutton parachain runtime." +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index c9dc5131644671e218b92ad35dce16cb45c422da..2c51791c0740553ea798c58c5157d3b7ab9f73c7 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -99,7 +99,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_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs index 6f8cf4f39dfdf894e4162ef6f629f88f27e97477..b68f16c9865894170b4e8aba3e524e4771fde2fc 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system.rs @@ -150,4 +150,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/cumulus/parachains/runtimes/people/README.md b/cumulus/parachains/runtimes/people/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cc196513e2ffce88e47ae1bcccac27f4fd93f11c --- /dev/null +++ b/cumulus/parachains/runtimes/people/README.md @@ -0,0 +1,5 @@ +# People System Chain + +The People Chain is a parachain to host the Identity pallet and serve as a location to which to +migrate identity-related information from the Relay Chain. It is part of the implementation of +[Fellowship RFC 32](https://github.com/polkadot-fellows/RFCs/blob/main/text/0032-minimal-relay.md). diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5086697b0f1ee08bcca101e517386bda0d83e2ce --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -0,0 +1,195 @@ +[package] +name = "people-rococo-runtime" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Rococo's People parachain runtime" +license = "Apache-2.0" + +[build-dependencies] +substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } + +[dependencies] +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 } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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-identity = { path = "../../../../../substrate/frame/identity", default-features = false } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } +pallet-session = { path = "../../../../../substrate/frame/session", 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-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 } +rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", 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 = "../../../../pallets/aura-ext", default-features = false } +cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +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-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 } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../common", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-dmp-queue/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-utility/std", + "enumflags2/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-collator-selection/std", + "pallet-identity/std", + "pallet-message-queue/std", + "pallet-multisig/std", + "pallet-session/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-utility/std", + "pallet-xcm-benchmarks?/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", + "scale-info/std", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "cumulus-pallet-dmp-queue/runtime-benchmarks", + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-multisig/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-dmp-queue/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-identity/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-multisig/try-runtime", + "pallet-session/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-utility/try-runtime", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +experimental = ["pallet-aura/experimental"] diff --git a/cumulus/parachains/runtimes/people/people-rococo/build.rs b/cumulus/parachains/runtimes/people/people-rococo/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..60f8a125129ff1344a1799246e931acdb1d139d5 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/build.rs @@ -0,0 +1,26 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "std")] +fn main() { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..7805e0ad982924603933bdbf17a5825af543b88a --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -0,0 +1,845 @@ +// 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)] +#![recursion_limit = "256"] +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +pub mod people; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::{ + construct_runtime, derive_impl, + dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, + parameter_types, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse, EverythingBut, + TransformOrigin, + }, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + rococo::{consensus::*, currency::*, fee::WeightToFee}, + AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, + HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, +}; +use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; +use sp_api::impl_runtime_apis; +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; +pub use sp_runtime::{MultiAddress, Perbill, Permill}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::BodyId; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, PriceForSiblingParachainDelivery, XcmConfig, + XcmOriginToTransactDispatchOrigin, +}; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// A Block signed with an [`sp_runtime::Justification`]. +pub type SignedBlock = generic::SignedBlock; + +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +/// Migrations to apply on runtime upgrade. +pub type Migrations = (); + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[sp_version::runtime_version] +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_000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 0, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u8 = 42; +} + +pub struct IdentityCalls; +impl Contains for IdentityCalls { + fn contains(c: &RuntimeCall) -> bool { + matches!(c, RuntimeCall::Identity(_)) + } +} + +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = EverythingBut; + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; + type AccountId = AccountId; + type Nonce = Nonce; + type Hash = Hash; + type Block = Block; + type BlockHashCount = BlockHashCount; + type DbWeight = RocksDbWeight; + type Version = Version; + type AccountData = pallet_balances::AccountData; + type SystemWeightInfo = weights::frame_system::WeightInfo; + type SS58Prefix = SS58Prefix; + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = weights::pallet_timestamp::WeightInfo; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = weights::pallet_balances::WeightInfo; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeHoldReason = RuntimeHoldReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10. + pub const TransactionByteFee: Balance = MILLICENTS; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + 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 WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; +} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = + Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + #[cfg(feature = "runtime-benchmarks")] + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< + cumulus_primitives_core::AggregateMessageOrigin, + >; + #[cfg(not(feature = "runtime-benchmarks"))] + type MessageProcessor = xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >; + type Size = u32; + // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: + type QueueChangeHandler = NarrowOriginToSibling; + type QueuePausedQuery = NarrowOriginToSibling; + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; + type WeightInfo = weights::pallet_message_queue::WeightInfo; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +parameter_types! { + // Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +/// Privileged origin that represents Root or Fellows pluralistic body. +pub type RootOrFellows = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChannelInfo = ParachainSystem; + type VersionWrapper = PolkadotXcm; + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; + type ControllerOrigin = RootOrFellows; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; +} + +pub const PERIOD: u32 = 6 * HOURS; +pub const OFFSET: u32 = 0; + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU32>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU32>; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session::WeightInfo; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + // StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow Root and the `StakingAdmin` to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // should be a multiple of session or things will get inconsistent + type KickThreshold = ConstU32; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = weights::pallet_collator_selection::WeightInfo; +} + +parameter_types! { + // One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. + pub const DepositBase: Balance = deposit(1, 88); + // Additional storage item size of 32 bytes. + pub const DepositFactor: Balance = deposit(0, 32); +} + +impl pallet_multisig::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositFactor = DepositFactor; + type MaxSignatories = ConstU32<100>; + type WeightInfo = weights::pallet_multisig::WeightInfo; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = weights::pallet_utility::WeightInfo; +} + +// To be removed after migration is complete. +impl identity_migrator::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reaper = EnsureRoot; + type ReapIdentityHandler = (); + type WeightInfo = weights::polkadot_runtime_common_identity_migrator::WeightInfo; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub enum Runtime + { + // System support stuff. + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + ParachainSystem: cumulus_pallet_parachain_system::{ + Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + } = 1, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 2, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 3, + + // Monetary stuff. + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + + // Collator support. The order of these 5 are important and shall not change. + Authorship: pallet_authorship::{Pallet, Storage} = 20, + CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event, Config} = 21, + Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 22, + Aura: pallet_aura::{Pallet, Storage, Config} = 23, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 24, + + // XCM & related + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin, Config} = 31, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 34, + + // Handy utilities. + Utility: pallet_utility::{Pallet, Call, Event} = 40, + Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, + + // The main stage. + Identity: pallet_identity::{Pallet, Call, Storage, Event} = 50, + + // To migrate deposits + IdentityMigrator: identity_migrator::{Pallet, Call, Event} = 248, + } +); + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + // Substrate + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_identity, Identity] + [pallet_multisig, Multisig] + [pallet_session, SessionBench::] + [pallet_utility, Utility] + [pallet_timestamp, Timestamp] + // Polkadot + [polkadot_runtime_common::identity_migrator, IdentityMigrator] + // Cumulus + [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_collator_selection, CollatorSelection] + // XCM + [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + ); +} + +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()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; + + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; + use sp_storage::TrackedStorageKey; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime { + fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), BenchmarkError> { + ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); + Ok(()) + } + + fn verify_set_code() { + System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); + } + } + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Relay/native token can be teleported between People and Relay. + Some(( + MultiAsset { + fun: Fungible(EXISTENTIAL_DEPOSIT), + id: Concrete(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + None + } + } + + use xcm::latest::prelude::*; + use xcm_config::{PriceForParentDelivery, RelayLocation}; + + parameter_types! { + pub ExistentialDepositMultiAsset: Option = Some(( + RelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + XcmConfig, + ExistentialDepositMultiAsset, + PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(RelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + MultiAsset { + id: Concrete(RelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + RelayLocation::get(), + MultiAsset { fun: Fungible(UNITS), id: Concrete(RelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_multi_asset() -> MultiAsset { + MultiAsset { + id: Concrete(RelayLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type RuntimeCall = RuntimeCall; + type TransactAsset = Balances; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((RelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(RelayLocation::get()) + } + + fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + let origin = RelayLocation::get(); + let assets: MultiAssets = (Concrete(RelayLocation::get()), 1_000 * UNITS).into(); + let ticket = MultiLocation { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + create_default_config::() + } + + fn build_config(config: Vec) -> sp_genesis_builder::Result { + build_config::(config) + } + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/people.rs b/cumulus/parachains/runtimes/people/people-rococo/src/people.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ff249318d92205ab74804352067ecc7dcc79c53 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/people.rs @@ -0,0 +1,204 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::xcm_config::LocationToAccountId; +use codec::{Decode, Encode, MaxEncodedLen}; +use enumflags2::{bitflags, BitFlags}; +use frame_support::{ + parameter_types, traits::ConstU32, CloneNoBound, EqNoBound, PartialEqNoBound, + RuntimeDebugNoBound, +}; +use pallet_identity::{Data, IdentityInformationProvider}; +use parachains_common::impls::ToParentTreasury; +use scale_info::TypeInfo; +use sp_runtime::{traits::AccountIdConversion, RuntimeDebug}; +use sp_std::prelude::*; + +parameter_types! { + // 27 | Min encoded size of `Registration` + // - 10 | Min encoded size of `IdentityInfo` + // -----| + // 17 | Min size without `IdentityInfo` (accounted for in byte deposit) + pub const BasicDeposit: Balance = deposit(1, 17); + pub const ByteDeposit: Balance = deposit(0, 1); + pub const SubAccountDeposit: Balance = deposit(1, 53); + pub RelayTreasuryAccount: AccountId = + parachains_common::TREASURY_PALLET_ID.into_account_truncating(); +} + +impl pallet_identity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BasicDeposit = BasicDeposit; + type ByteDeposit = ByteDeposit; + type SubAccountDeposit = SubAccountDeposit; + type MaxSubAccounts = ConstU32<100>; + type IdentityInformation = IdentityInfo; + type MaxRegistrars = ConstU32<20>; + type Slashed = ToParentTreasury; + type ForceOrigin = EnsureRoot; + type RegistrarOrigin = EnsureRoot; + type WeightInfo = weights::pallet_identity::WeightInfo; +} + +/// The fields that we use to identify the owner of an account with. Each corresponds to a field +/// in the `IdentityInfo` struct. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub enum IdentityField { + Display, + Legal, + Web, + Matrix, + Email, + PgpFingerprint, + Image, + Twitter, + GitHub, + Discord, +} + +/// Information concerning the identity of the controller of an account. +#[derive( + CloneNoBound, + Encode, + Decode, + EqNoBound, + MaxEncodedLen, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, +)] +#[codec(mel_bound())] +#[cfg_attr(test, derive(frame_support::DefaultNoBound))] +pub struct IdentityInfo { + /// A reasonable display name for the controller of the account. This should be whatever the + /// account is typically known as and should not be confusable with other entities, given + /// reasonable context. + /// + /// Stored as UTF-8. + pub display: Data, + + /// The full legal name in the local jurisdiction of the entity. This might be a bit + /// long-winded. + /// + /// Stored as UTF-8. + pub legal: Data, + + /// A representative website held by the controller of the account. + /// + /// NOTE: `https://` is automatically prepended. + /// + /// Stored as UTF-8. + pub web: Data, + + /// The Matrix (e.g. for Element) handle held by the controller of the account. Previously, + /// this was called `riot`. + /// + /// Stored as UTF-8. + pub matrix: Data, + + /// The email address of the controller of the account. + /// + /// Stored as UTF-8. + pub email: Data, + + /// The PGP/GPG public key of the controller of the account. + pub pgp_fingerprint: Option<[u8; 20]>, + + /// A graphic image representing the controller of the account. Should be a company, + /// organization or project logo or a headshot in the case of a human. + pub image: Data, + + /// The Twitter identity. The leading `@` character may be elided. + pub twitter: Data, + + /// The GitHub username of the controller of the account. + pub github: Data, + + /// The Discord username of the controller of the account. + pub discord: Data, +} + +impl IdentityInformationProvider for IdentityInfo { + type FieldsIdentifier = u64; + + fn has_identity(&self, fields: Self::FieldsIdentifier) -> bool { + self.fields().bits() & fields == fields + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_identity_info() -> Self { + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + + IdentityInfo { + display: data.clone(), + legal: data.clone(), + web: data.clone(), + matrix: data.clone(), + email: data.clone(), + pgp_fingerprint: Some([0; 20]), + image: data.clone(), + twitter: data.clone(), + github: data.clone(), + discord: data, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn all_fields() -> Self::FieldsIdentifier { + use enumflags2::BitFlag; + IdentityField::all().bits() + } +} + +impl IdentityInfo { + pub(crate) fn fields(&self) -> BitFlags { + let mut res = >::empty(); + if !self.display.is_none() { + res.insert(IdentityField::Display); + } + if !self.legal.is_none() { + res.insert(IdentityField::Legal); + } + if !self.web.is_none() { + res.insert(IdentityField::Web); + } + if !self.matrix.is_none() { + res.insert(IdentityField::Matrix); + } + if !self.email.is_none() { + res.insert(IdentityField::Email); + } + if self.pgp_fingerprint.is_some() { + res.insert(IdentityField::PgpFingerprint); + } + if !self.image.is_none() { + res.insert(IdentityField::Image); + } + if !self.twitter.is_none() { + res.insert(IdentityField::Twitter); + } + if !self.github.is_none() { + res.insert(IdentityField::GitHub); + } + if !self.discord.is_none() { + res.insert(IdentityField::Discord); + } + res + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2092d875c8328210667da4cbb95de0642e60ae3 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/block_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Importing a block with 0 Extrinsics. + pub const BlockExecutionWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(5_000_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcea5fd1bf679c803509dd4529b45e4994a7438c --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,53 @@ +// 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. + +//! Need to rerun + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// 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) + /// 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_622_000 picoseconds. + Weight::from_parts(1_709_000, 0) + .saturating_add(Weight::from_parts(0, 8013)) + // Standard Error: 22_138 + .saturating_add(Weight::from_parts(23_923_169, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..71ac6ef518059d89af3fbf1bb46431cbe65c61b5 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,129 @@ +// 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. + +//! Need to rerun + +#![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 `cumulus_pallet_xcmp_queue`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { + /// 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_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`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: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn enqueue_xcmp_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3517` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn suspend_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn resume_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn take_first_concatenated_xcm() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 44_000_000 picoseconds. + Weight::from_parts(45_000_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: `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: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65711` + // Estimated: `69176` + // Minimum execution time: 67_000_000 picoseconds. + Weight::from_parts(73_000_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 { + // Proof Size summary in bytes: + // Measured: `65710` + // Estimated: `69175` + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(55_000_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/people/people-rococo/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..332c3b324bb9c1b386257bf7953d37aba8f5af13 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/extrinsic_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Executing a NO-OP `System::remarks` Extrinsic. + pub const ExtrinsicBaseWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(125_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..495903a4669e8e83964c71903956a24c2458faed --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system.rs @@ -0,0 +1,160 @@ +// 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 `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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=frame_system +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/frame_system.rs + +#![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 `frame_system`. +pub struct WeightInfo(PhantomData); +impl frame_system::WeightInfo for WeightInfo { + /// The range of component `b` is `[0, 3932160]`. + fn remark(b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_356_000 picoseconds. + Weight::from_parts(1_100_689, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(412, 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_879_000 picoseconds. + Weight::from_parts(8_041_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_451, 0).saturating_mul(b.into())) + } + fn set_code() -> Weight { + Weight::from_parts(1_000_000, 0) + } + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: unknown `0x3a686561707061676573` (r:0 w:1) + /// Proof Skipped: unknown `0x3a686561707061676573` (r:0 w:1) + fn set_heap_pages() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 4_358_000 picoseconds. + Weight::from_parts(4_537_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn set_storage(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_373_000 picoseconds. + Weight::from_parts(2_395_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 1_727 + .saturating_add(Weight::from_parts(690_266, 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) + /// 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_513_000 picoseconds. + Weight::from_parts(2_540_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 815 + .saturating_add(Weight::from_parts(505_090, 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) + /// 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_242_000 picoseconds. + Weight::from_parts(4_308_000, 0) + .saturating_add(Weight::from_parts(0, 66)) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(1_032_054, 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())) + } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3396a8caea0599574da40135c74bc19f9cf52125 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs @@ -0,0 +1,39 @@ +// 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. + +//! Expose the auto generated weight files. + +pub mod block_weights; +pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_xcmp_queue; +pub mod extrinsic_weights; +pub mod frame_system; +pub mod pallet_balances; +pub mod pallet_collator_selection; +pub mod pallet_identity; +pub mod pallet_message_queue; +pub mod pallet_multisig; +pub mod pallet_session; +pub mod pallet_timestamp; +pub mod pallet_utility; +pub mod pallet_xcm; +pub mod paritydb_weights; +pub mod polkadot_runtime_common_identity_migrator; +pub mod rocksdb_weights; +pub mod xcm; + +pub use block_weights::constants::BlockExecutionWeight; +pub use extrinsic_weights::constants::ExtrinsicBaseWeight; +pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_balances.rs new file mode 100644 index 0000000000000000000000000000000000000000..64d6cf4ece8c14c9dd9c090c00d4666a7d5b52fd --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_balances.rs @@ -0,0 +1,150 @@ +// 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_balances` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_balances +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_balances.rs + +#![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_balances`. +pub struct WeightInfo(PhantomData); +impl pallet_balances::WeightInfo for WeightInfo { + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 63_775_000 picoseconds. + Weight::from_parts(64_181_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 47_986_000 picoseconds. + Weight::from_parts(48_308_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 18_083_000 picoseconds. + Weight::from_parts(18_380_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 26_341_000 picoseconds. + Weight::from_parts(26_703_000, 0) + .saturating_add(Weight::from_parts(0, 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) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 66_227_000 picoseconds. + Weight::from_parts(67_321_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_all() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 59_472_000 picoseconds. + Weight::from_parts(60_842_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 21_497_000 picoseconds. + Weight::from_parts(21_684_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:999 w:999) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (136 ±0)` + // Estimated: `990 + u * (2603 ±0)` + // Minimum execution time: 20_385_000 picoseconds. + Weight::from_parts(20_587_000, 0) + .saturating_add(Weight::from_parts(0, 990)) + // Standard Error: 10_001 + .saturating_add(Weight::from_parts(16_801_557, 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())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_collator_selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6c0f5ffebd1078c953296dc0454970e30e5f37f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_collator_selection.rs @@ -0,0 +1,242 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_collator_selection +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_collator_selection.rs + +#![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_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]`. + 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_702_000 picoseconds. + Weight::from_parts(14_995_989, 0) + .saturating_add(Weight::from_parts(0, 1203)) + // Standard Error: 2_975 + .saturating_add(Weight::from_parts(2_630_139, 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())) + } + /// 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: 6_916_000 picoseconds. + Weight::from_parts(7_224_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .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 { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_388_000 picoseconds. + Weight::from_parts(7_677_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_377_000 picoseconds. + Weight::from_parts(34_785_115, 0) + .saturating_add(Weight::from_parts(0, 49487)) + // Standard Error: 1_226 + .saturating_add(Weight::from_parts(101_867, 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 { + // Proof Size summary in bytes: + // Measured: `428 + c * (48 ±0)` + // Estimated: `49487` + // Minimum execution time: 33_648_000 picoseconds. + Weight::from_parts(24_533_176, 0) + .saturating_add(Weight::from_parts(0, 49487)) + // Standard Error: 1_388 + .saturating_add(Weight::from_parts(103_733, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .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) + fn note_author() -> Weight { + // Proof Size summary in bytes: + // Measured: `155` + // Estimated: `6196` + // Minimum execution time: 44_705_000 picoseconds. + Weight::from_parts(45_288_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())) + } + fn update_bond(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn take_candidate_slot(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// 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]`. + 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_845_000 picoseconds. + Weight::from_parts(16_962_000, 0) + .saturating_add(Weight::from_parts(0, 49487)) + // Standard Error: 858_960 + .saturating_add(Weight::from_parts(30_464_644, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .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())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..65cac7875bada8355442b60adce1887f8a54786a --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs @@ -0,0 +1,315 @@ +// 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. + +//! Taken from Rococo Relay Chain. Needs to rerun. + +#![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_identity`. +pub struct WeightInfo(PhantomData); +impl pallet_identity::WeightInfo for WeightInfo { + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn add_registrar(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 12_290_000 picoseconds. + Weight::from_parts(12_664_362, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_347 + .saturating_add(Weight::from_parts(88_179, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn set_identity(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `442 + r * (5 ±0)` + // Estimated: `11003` + // Minimum execution time: 31_373_000 picoseconds. + Weight::from_parts(30_435_545, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_307 + .saturating_add(Weight::from_parts(92_753, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:100 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 100]`. + fn set_subs_new(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `11003 + s * (2589 ±0)` + // Minimum execution time: 9_251_000 picoseconds. + Weight::from_parts(22_039_210, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 40_779 + .saturating_add(Weight::from_parts(2_898_525, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(s.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 2589).saturating_mul(s.into())) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `p` is `[0, 100]`. + fn set_subs_old(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `194 + p * (32 ±0)` + // Estimated: `11003` + // Minimum execution time: 9_329_000 picoseconds. + Weight::from_parts(24_055_061, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 3_428 + .saturating_add(Weight::from_parts(1_130_604, 0).saturating_mul(p.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(p.into()))) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + fn clear_identity(_r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `469 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 53_365_000 picoseconds. + Weight::from_parts(35_391_422, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_353 + .saturating_add(Weight::from_parts(1_074_019, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn request_judgement(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `367 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_509_000 picoseconds. + Weight::from_parts(31_745_585, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_214 + .saturating_add(Weight::from_parts(83_822, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn cancel_request(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `398 + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 29_609_000 picoseconds. + Weight::from_parts(28_572_602, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_528 + .saturating_add(Weight::from_parts(85_593, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_fee(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_793_000 picoseconds. + Weight::from_parts(8_173_888, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_569 + .saturating_add(Weight::from_parts(72_367, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_account_id(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_708_000 picoseconds. + Weight::from_parts(8_091_149, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 869 + .saturating_add(Weight::from_parts(87_993, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_fields(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_601_000 picoseconds. + Weight::from_parts(8_038_414, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_041 + .saturating_add(Weight::from_parts(82_588, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn provide_judgement(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `445 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 23_114_000 picoseconds. + Weight::from_parts(22_076_548, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_881 + .saturating_add(Weight::from_parts(109_812, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 70_007_000 picoseconds. + Weight::from_parts(50_186_495, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 6_533 + .saturating_add(Weight::from_parts(15_486, 0).saturating_mul(r.into())) + // Standard Error: 1_275 + .saturating_add(Weight::from_parts(1_085_117, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 99]`. + fn add_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `475 + s * (36 ±0)` + // Estimated: `11003` + // Minimum execution time: 28_453_000 picoseconds. + Weight::from_parts(33_165_934, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_217 + .saturating_add(Weight::from_parts(65_401, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `s` is `[1, 100]`. + fn rename_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `591 + s * (3 ±0)` + // Estimated: `11003` + // Minimum execution time: 12_846_000 picoseconds. + Weight::from_parts(14_710_284, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 496 + .saturating_add(Weight::from_parts(19_539, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// The range of component `s` is `[1, 100]`. + fn remove_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `638 + s * (35 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_183_000 picoseconds. + Weight::from_parts(35_296_731, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 854 + .saturating_add(Weight::from_parts(52_028, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 99]`. + fn quit_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `704 + s * (37 ±0)` + // Estimated: `6723` + // Minimum execution time: 24_941_000 picoseconds. + Weight::from_parts(27_433_059, 0) + .saturating_add(Weight::from_parts(0, 6723)) + // Standard Error: 856 + .saturating_add(Weight::from_parts(57_463, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe1911b77a72dbfed0309722a1c59f37b56cb40f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_message_queue.rs @@ -0,0 +1,156 @@ +// 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. + +//! Need to rerun + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::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) + fn ready_ring_knit() -> Weight { + // Proof Size summary in bytes: + // Measured: `189` + // Estimated: `7534` + // Minimum execution time: 13_668_000 picoseconds. + Weight::from_parts(13_668_000, 0) + .saturating_add(Weight::from_parts(0, 7534)) + .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) + fn ready_ring_unknit() -> Weight { + // Proof Size summary in bytes: + // Measured: `184` + // Estimated: `7534` + // Minimum execution time: 11_106_000 picoseconds. + Weight::from_parts(11_106_000, 0) + .saturating_add(Weight::from_parts(0, 7534)) + .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) + fn service_queue_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `6` + // Estimated: `3517` + // Minimum execution time: 4_921_000 picoseconds. + Weight::from_parts(4_921_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) + fn service_page_base_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 6_879_000 picoseconds. + Weight::from_parts(6_879_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) + fn service_page_base_no_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 7_564_000 picoseconds. + Weight::from_parts(7_564_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 59_963_000 picoseconds. + Weight::from_parts(59_963_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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: 7_200_000 picoseconds. + Weight::from_parts(7_200_000, 0) + .saturating_add(Weight::from_parts(0, 5007)) + .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) + fn reap_page() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 41_366_000 picoseconds. + Weight::from_parts(41_366_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .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) + fn execute_overweight_page_removed() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 60_538_000 picoseconds. + Weight::from_parts(60_538_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .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) + fn execute_overweight_page_updated() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 73_665_000 picoseconds. + Weight::from_parts(73_665_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs new file mode 100644 index 0000000000000000000000000000000000000000..73abb62b0482cc444fb2066e2eb6c4831ff5f159 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs @@ -0,0 +1,162 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_multisig +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_multisig.rs + +#![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_multisig`. +pub struct WeightInfo(PhantomData); +impl pallet_multisig::WeightInfo for WeightInfo { + /// The range of component `z` is `[0, 10000]`. + fn as_multi_threshold_1(z: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 11_056_000 picoseconds. + Weight::from_parts(11_510_137, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 1 + .saturating_add(Weight::from_parts(528, 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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 41_105_000 picoseconds. + Weight::from_parts(34_947_072, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 499 + .saturating_add(Weight::from_parts(67_375, 0).saturating_mul(s.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(1_227, 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) + /// 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_640_000 picoseconds. + Weight::from_parts(21_515_344, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 943 + .saturating_add(Weight::from_parts(58_769, 0).saturating_mul(s.into())) + // Standard Error: 9 + .saturating_add(Weight::from_parts(1_233, 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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 45_875_000 picoseconds. + Weight::from_parts(38_052_994, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 507 + .saturating_add(Weight::from_parts(82_957, 0).saturating_mul(s.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(1_277, 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) + /// 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_359_000 picoseconds. + Weight::from_parts(33_845_761, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 623 + .saturating_add(Weight::from_parts(69_809, 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) + /// 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_791_000 picoseconds. + Weight::from_parts(20_017_375, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 466 + .saturating_add(Weight::from_parts(64_780, 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) + /// 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: 33_132_000 picoseconds. + Weight::from_parts(34_485_734, 0) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 601 + .saturating_add(Weight::from_parts(70_191, 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/people/people-rococo/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_session.rs new file mode 100644 index 0000000000000000000000000000000000000000..a6b715e6e6e93a22480741706ce4d42849ed9357 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_session.rs @@ -0,0 +1,78 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_session +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_session.rs + +#![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_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) + fn set_keys() -> Weight { + // Proof Size summary in bytes: + // Measured: `297` + // Estimated: `3762` + // Minimum execution time: 17_809_000 picoseconds. + Weight::from_parts(18_215_000, 0) + .saturating_add(Weight::from_parts(0, 3762)) + .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) + fn purge_keys() -> Weight { + // Proof Size summary in bytes: + // Measured: `279` + // Estimated: `3744` + // Minimum execution time: 13_565_000 picoseconds. + Weight::from_parts(13_841_000, 0) + .saturating_add(Weight::from_parts(0, 3744)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_timestamp.rs new file mode 100644 index 0000000000000000000000000000000000000000..c85e7fb8c3207ecb14a0089a92306c93e27ed2f8 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_timestamp.rs @@ -0,0 +1,72 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_timestamp +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_timestamp.rs + +#![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_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) + fn set() -> Weight { + // Proof Size summary in bytes: + // Measured: `49` + // Estimated: `1493` + // Minimum execution time: 7_796_000 picoseconds. + Weight::from_parts(8_128_000, 0) + .saturating_add(Weight::from_parts(0, 1493)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `57` + // Estimated: `0` + // Minimum execution time: 3_268_000 picoseconds. + Weight::from_parts(3_351_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_utility.rs new file mode 100644 index 0000000000000000000000000000000000000000..134bd1fbbc58f73f8f0e411aeed4b61e59e53283 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_utility.rs @@ -0,0 +1,99 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_utility +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_utility.rs + +#![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_utility`. +pub struct WeightInfo(PhantomData); +impl pallet_utility::WeightInfo for WeightInfo { + /// The range of component `c` is `[0, 1000]`. + fn batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_032_000 picoseconds. + Weight::from_parts(7_713_695, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 2_526 + .saturating_add(Weight::from_parts(4_329_716, 0).saturating_mul(c.into())) + } + fn as_derivative() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_961_000 picoseconds. + Weight::from_parts(5_064_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn batch_all(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_955_000 picoseconds. + Weight::from_parts(17_856_282, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 3_463 + .saturating_add(Weight::from_parts(4_554_734, 0).saturating_mul(c.into())) + } + fn dispatch_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_841_000 picoseconds. + Weight::from_parts(9_004_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_737_000 picoseconds. + Weight::from_parts(7_653_355, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 3_915 + .saturating_add(Weight::from_parts(4_372_646, 0).saturating_mul(c.into())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs new file mode 100644 index 0000000000000000000000000000000000000000..0f793524de9f5ef7da835090f9d81008ba756d59 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs @@ -0,0 +1,342 @@ +// 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_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: `[]` +//! 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("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_xcm.rs + +#![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_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) + fn send() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 25_931_000 picoseconds. + Weight::from_parts(26_340_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .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) + fn teleport_assets() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `1489` + // Minimum execution time: 25_691_000 picoseconds. + Weight::from_parts(25_971_000, 0) + .saturating_add(Weight::from_parts(0, 1489)) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn reserve_transfer_assets() -> Weight { + // Proof Size summary in bytes: + // 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: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `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: `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 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)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn execute() -> Weight { + // Proof Size summary in bytes: + // 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: PolkadotXcm SupportedVersion (r:0 w:1) + /// Proof Skipped: 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_572_000 picoseconds. + Weight::from_parts(9_924_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) + fn force_default_xcm_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_997_000 picoseconds. + Weight::from_parts(3_136_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) + fn force_subscribe_version_notify() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 30_271_000 picoseconds. + Weight::from_parts(30_819_000, 0) + .saturating_add(Weight::from_parts(0, 3503)) + .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) + fn force_unsubscribe_version_notify() -> Weight { + // Proof Size summary in bytes: + // Measured: `220` + // Estimated: `3685` + // Minimum execution time: 32_302_000 picoseconds. + Weight::from_parts(32_807_000, 0) + .saturating_add(Weight::from_parts(0, 3685)) + .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) + fn force_suspension() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_960_000 picoseconds. + Weight::from_parts(3_094_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) + fn migrate_supported_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `95` + // Estimated: `10985` + // Minimum execution time: 14_877_000 picoseconds. + Weight::from_parts(15_296_000, 0) + .saturating_add(Weight::from_parts(0, 10985)) + .saturating_add(T::DbWeight::get().reads(4)) + .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) + fn migrate_version_notifiers() -> Weight { + // Proof Size summary in bytes: + // Measured: `99` + // Estimated: `10989` + // Minimum execution time: 14_835_000 picoseconds. + Weight::from_parts(15_115_000, 0) + .saturating_add(Weight::from_parts(0, 10989)) + .saturating_add(T::DbWeight::get().reads(4)) + .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) + fn already_notified_target() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `13471` + // Minimum execution time: 15_368_000 picoseconds. + Weight::from_parts(15_596_000, 0) + .saturating_add(Weight::from_parts(0, 13471)) + .saturating_add(T::DbWeight::get().reads(5)) + } + /// 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) + fn notify_current_targets() -> Weight { + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `6046` + // Minimum execution time: 28_025_000 picoseconds. + Weight::from_parts(28_524_000, 0) + .saturating_add(Weight::from_parts(0, 6046)) + .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) + fn notify_target_migration_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `8551` + // Minimum execution time: 8_166_000 picoseconds. + Weight::from_parts(8_314_000, 0) + .saturating_add(Weight::from_parts(0, 8551)) + .saturating_add(T::DbWeight::get().reads(3)) + } + /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) + /// Proof Skipped: 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_871_000 picoseconds. + Weight::from_parts(15_374_000, 0) + .saturating_add(Weight::from_parts(0, 10996)) + .saturating_add(T::DbWeight::get().reads(4)) + .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) + fn migrate_and_notify_old_targets() -> Weight { + // Proof Size summary in bytes: + // Measured: `112` + // Estimated: `11002` + // Minimum execution time: 33_611_000 picoseconds. + Weight::from_parts(34_008_000, 0) + .saturating_add(Weight::from_parts(0, 11002)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`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 new_query() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 5_496_000 picoseconds. + Weight::from_parts(5_652_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::Queries` (r:1 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn take_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `7740` + // Estimated: `11205` + // Minimum execution time: 26_140_000 picoseconds. + Weight::from_parts(26_824_000, 0) + .saturating_add(Weight::from_parts(0, 11205)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..4338d928d807a41cc60ec91d86e91c81bb253631 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/paritydb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/polkadot_runtime_common_identity_migrator.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/polkadot_runtime_common_identity_migrator.rs new file mode 100644 index 0000000000000000000000000000000000000000..4449c8f2b020adedffe2f97e2c24b338c4c0e623 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/polkadot_runtime_common_identity_migrator.rs @@ -0,0 +1,97 @@ +// 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 `polkadot_runtime_common::identity_migrator` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `sbtb`, CPU: `13th Gen Intel(R) Core(TM) i7-1365U` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot +// benchmark +// pallet +// --chain=rococo-dev +// --steps=2 +// --repeat=1 +// --pallet=polkadot_runtime_common::identity_migrator +// --extrinsic=* +// --output=./migrator-release.rs + +#![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 `polkadot_runtime_common::identity_migrator`. +pub struct WeightInfo(PhantomData); +impl polkadot_runtime_common::identity_migrator::WeightInfo for WeightInfo { + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`) + /// Storage: `Identity::SubsOf` (r:1 w:1) + /// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) + /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) + /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Identity::SuperOf` (r:0 w:100) + /// Proof: `Identity::SuperOf` (`max_values`: None, `max_size`: Some(114), added: 2589, mode: `MaxEncodedLen`) + /// The range of component `r` is `[0, 20]`. + /// The range of component `s` is `[0, 100]`. + fn reap_identity(r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `7292 + r * (8 ±0) + s * (32 ±0)` + // Estimated: `11003 + r * (8 ±0) + s * (33 ±0)` + // Minimum execution time: 163_756_000 picoseconds. + Weight::from_parts(158_982_500, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_143_629 + .saturating_add(Weight::from_parts(238_675, 0).saturating_mul(r.into())) + // Standard Error: 228_725 + .saturating_add(Weight::from_parts(1_529_645, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 33).saturating_mul(s.into())) + } + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Identity::SubsOf` (r:1 w:1) + /// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`) + fn poke_deposit() -> Weight { + // Proof Size summary in bytes: + // Measured: `7229` + // Estimated: `11003` + // Minimum execution time: 137_570_000 picoseconds. + Weight::from_parts(137_570_000, 0) + .saturating_add(Weight::from_parts(0, 11003)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d115d963facb39fe29d6258918fda3bc8d94900 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/rocksdb_weights.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..c90a96c7f8220a1d17f3f2f5dda67849ddecbef2 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs @@ -0,0 +1,249 @@ +// 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. + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, DoubleEncoded}; + +trait WeighMultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight; +} + +const MAX_ASSETS: u64 = 100; + +impl WeighMultiAssets for MultiAssetFilter { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighMultiAssets for MultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct PeopleRococoXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for PeopleRococoXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + } + // Currently there is no trusted reserve + fn reserve_asset_deposited(_assets: &MultiAssets) -> Weight { + // TODO: hardcoded - fix https://github.com/paritytech/cumulus/issues/1974 + Weight::from_parts(1_000_000_000_u64, 0) + } + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() + } + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset( + assets: &MultiAssets, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _require_weight_at_most: &Weight, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() + } + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_parts(1_000_000_000_u64, 0); + let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); + hardcoded_weight.min(weight) + } + fn deposit_reserve_asset( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &MultiAssetFilter, + _reserve: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) + } + fn initiate_teleport( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::::expect_transact_status() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 0000000000000000000000000000000000000000..b279399e7a96b68ac68f7e12ed165efb324fab9b --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,157 @@ +// 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_xcm_benchmarks::fungible` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::fungible +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + pub fn withdraw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 23_309_000 picoseconds. + Weight::from_parts(23_777_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) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `153` + // Estimated: `6196` + // Minimum execution time: 48_808_000 picoseconds. + Weight::from_parts(49_427_000, 6196) + .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: 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 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) + pub fn transfer_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `6196` + // Minimum execution time: 71_204_000 picoseconds. + Weight::from_parts(72_121_000, 6196) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn receive_teleported_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_559_000 picoseconds. + Weight::from_parts(3_616_000, 0) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3593` + // Minimum execution time: 25_042_000 picoseconds. + Weight::from_parts(25_630_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // 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: 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) + pub fn deposit_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `122` + // Estimated: `3593` + // Minimum execution time: 49_030_000 picoseconds. + Weight::from_parts(49_828_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: 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) + pub fn initiate_teleport() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 27_142_000 picoseconds. + Weight::from_parts(27_416_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs new file mode 100644 index 0000000000000000000000000000000000000000..e2be324ee2d48c42a8d7340c8cbcd3a52b161f19 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,347 @@ +// 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_xcm_benchmarks::generic` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=people-kusama-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::generic +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/xcm/pallet_xcm_benchmarks_generic.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +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: 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) + pub fn report_holding() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 30_210_000 picoseconds. + Weight::from_parts(30_864_000, 3535) + .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: 2_808_000 picoseconds. + Weight::from_parts(2_848_000, 0) + } + // Storage: PolkadotXcm Queries (r:1 w:0) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + pub fn query_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 10_353_000 picoseconds. + Weight::from_parts(10_569_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: 12_074_000 picoseconds. + Weight::from_parts(12_280_000, 0) + } + pub fn refund_surplus() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_080_000 picoseconds. + Weight::from_parts(3_161_000, 0) + } + pub fn set_error_handler() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_649_000 picoseconds. + Weight::from_parts(2_732_000, 0) + } + pub fn set_appendix() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_652_000 picoseconds. + Weight::from_parts(2_749_000, 0) + } + pub fn clear_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_642_000 picoseconds. + Weight::from_parts(2_704_000, 0) + } + pub fn descend_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_438_000 picoseconds. + Weight::from_parts(3_508_000, 0) + } + pub fn clear_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_626_000 picoseconds. + Weight::from_parts(2_701_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: 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) + pub fn report_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 24_737_000 picoseconds. + Weight::from_parts(25_106_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: PolkadotXcm AssetTraps (r:1 w:1) + // Proof Skipped: PolkadotXcm AssetTraps (max_values: None, max_size: None, mode: Measured) + pub fn claim_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 14_712_000 picoseconds. + Weight::from_parts(14_976_000, 3555) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_689_000 picoseconds. + Weight::from_parts(2_739_000, 0) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:1 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) + pub fn subscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 26_478_000 picoseconds. + Weight::from_parts(26_695_000, 3503) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) + // Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + pub fn unsubscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_811_000 picoseconds. + Weight::from_parts(5_062_000, 0) + .saturating_add(T::DbWeight::get().writes(1)) + } + // 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 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) + pub fn initiate_reserve_withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 26_945_000 picoseconds. + Weight::from_parts(28_093_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn burn_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_144_000 picoseconds. + Weight::from_parts(4_217_000, 0) + } + pub fn expect_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_726_000 picoseconds. + Weight::from_parts(2_802_000, 0) + } + pub fn expect_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_719_000 picoseconds. + Weight::from_parts(2_790_000, 0) + } + pub fn expect_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_660_000 picoseconds. + Weight::from_parts(2_742_000, 0) + } + pub fn expect_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_874_000 picoseconds. + Weight::from_parts(2_940_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: 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) + pub fn query_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 27_235_000 picoseconds. + Weight::from_parts(27_811_000, 3535) + .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: 4_807_000 picoseconds. + Weight::from_parts(4_918_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: 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) + pub fn report_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 24_698_000 picoseconds. + Weight::from_parts(25_077_000, 3535) + .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: 2_613_000 picoseconds. + Weight::from_parts(2_703_000, 0) + } + pub fn set_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_602_000 picoseconds. + Weight::from_parts(2_661_000, 0) + } + pub fn clear_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_557_000 picoseconds. + Weight::from_parts(2_655_000, 0) + } + pub fn set_fees_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_724_000 picoseconds. + Weight::from_parts(2_760_000, 0) + } + pub fn unpaid_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_764_000 picoseconds. + Weight::from_parts(2_872_000, 0) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..7a2f28aa813e536688d0b0602047c55716c23b26 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -0,0 +1,320 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, +}; +use crate::{TransactionByteFee, CENTS}; +use frame_support::{ + match_types, parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, 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, HashedDescription, IsConcrete, + 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 RootLocation: MultiLocation = MultiLocation::here(); + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Rococo); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); + pub const FellowshipLocation: MultiLocation = MultiLocation::parent(); + /// The asset ID for the asset that we use to pay for message delivery fees. Just ROC. + pub FeeAssetId: AssetId = Concrete(RelayLocation::get()); + /// The base fee for the message delivery fees. + pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: MultiLocation = + (Parent, PalletInstance(rococo_runtime_constants::TREASURY_PALLET_ID)).into(); +} + +pub type PriceForParentDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + ParachainSystem, +>; + +pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + XcmpQueue, +>; + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, + // Here/local root location to `AccountId`. + HashedDescription, +); + +/// Means for transacting the native currency on this chain. +#[allow(deprecated)] +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an `AccountId32` `MultiLocation` into a native chain + // `AccountId`: + LocationToAccountId, + // Our chain's `AccountId` type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// 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. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain that they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `RuntimeOrigin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // XCM origins can be represented natively under the XCM pallet's `Xcm` origin. + XcmPassthrough, +); + +match_types! { + pub type LocalPlurality: impl Contains = { + MultiLocation { parents: 0, interior: X1(Plurality { .. }) } + }; + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; + pub type ParentOrSiblings: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Parachain(_)) } + }; +} + +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + matches!( + call, + RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_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::kill_prefix { .. }, + ) | RuntimeCall::ParachainSystem(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Balances(..) | + RuntimeCall::CollatorSelection( + pallet_collator_selection::Call::set_desired_candidates { .. } | + pallet_collator_selection::Call::set_candidacy_bond { .. } | + pallet_collator_selection::Call::register_as_candidate { .. } | + pallet_collator_selection::Call::leave_intent { .. } | + pallet_collator_selection::Call::set_invulnerables { .. } | + pallet_collator_selection::Call::add_invulnerable { .. } | + pallet_collator_selection::Call::remove_invulnerable { .. }, + ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::XcmpQueue(..) | + RuntimeCall::MessageQueue(..) | + RuntimeCall::Identity(..) | + RuntimeCall::IdentityMigrator(..) + ) + } +} + +pub type Barrier = TrailingSetTopicAsId< + DenyThenTry< + DenyReserveTransferToRelayChain, + ( + // Allow local users to buy weight credit. + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Parent and its pluralities (i.e. governance bodies) get free execution. + AllowExplicitUnpaidExecutionFrom, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, +>; + +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. We +/// only waive fees for system functions, which these locations represent. +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, + Equals, + LocalPlurality, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = CurrencyTransactor; + 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). + type IsReserve = (); + /// Only allow teleportation of ROC. + type IsTeleporter = ConcreteAssetFromSystem; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::PeopleRococoXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = + UsingComponents>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; + type Aliasers = Nothing; +} + +/// Converts a local signed origin into an XCM multilocation. Forms the basis for local origins +/// sending/executing XCMs. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +)>; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We want to disallow users sending (arbitrary) XCM programs from this chain. + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // We support local origins dispatching XCM executions in principle... + type ExecuteXcmOrigin = EnsureXcmOrigin; + // ... but disallow generic XCM execution. As a result only teleports are allowed. + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type Weigher = WeightInfoBounds< + crate::weights::xcm::PeopleRococoXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + type AdminOrigin = EnsureRoot; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..38386779a0e28d388f84211ae3a50294879ac320 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -0,0 +1,195 @@ +[package] +name = "people-westend-runtime" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +description = "Westend's People parachain runtime" +license = "Apache-2.0" + +[build-dependencies] +substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", optional = true } + +[dependencies] +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 } +scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } +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-identity = { path = "../../../../../substrate/frame/identity", default-features = false } +pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } +pallet-session = { path = "../../../../../substrate/frame/session", 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-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 } +westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants", 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 = "../../../../pallets/aura-ext", default-features = false } +cumulus-pallet-dmp-queue = { path = "../../../../pallets/dmp-queue", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +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-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 } +parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../common", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-dmp-queue/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-core/std", + "cumulus-primitives-utility/std", + "enumflags2/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-collator-selection/std", + "pallet-identity/std", + "pallet-message-queue/std", + "pallet-multisig/std", + "pallet-session/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-utility/std", + "pallet-xcm-benchmarks?/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", + "serde", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-storage/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", + "westend-runtime-constants/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", +] + +runtime-benchmarks = [ + "cumulus-pallet-dmp-queue/runtime-benchmarks", + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-multisig/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] + +try-runtime = [ + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-dmp-queue/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-identity/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-multisig/try-runtime", + "pallet-session/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-utility/try-runtime", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +experimental = ["pallet-aura/experimental"] diff --git a/cumulus/parachains/runtimes/people/people-westend/build.rs b/cumulus/parachains/runtimes/people/people-westend/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..60f8a125129ff1344a1799246e931acdb1d139d5 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/build.rs @@ -0,0 +1,26 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[cfg(feature = "std")] +fn main() { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .build() +} + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..8ea29c8aa218bb9d5686c7f2ad7dfda766a8e218 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -0,0 +1,845 @@ +// 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)] +#![recursion_limit = "256"] +#[cfg(feature = "std")] +include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); + +pub mod people; +mod weights; +pub mod xcm_config; + +use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::{ + construct_runtime, derive_impl, + dispatch::DispatchClass, + genesis_builder_helper::{build_config, create_default_config}, + parameter_types, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse, EverythingBut, + TransformOrigin, + }, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use parachains_common::{ + impls::DealWithFees, + message_queue::{NarrowOriginToSibling, ParaIdToSibling}, + westend::{consensus::*, currency::*, fee::WeightToFee}, + AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, + HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, +}; +use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; +use sp_api::impl_runtime_apis; +pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +use sp_runtime::{ + create_runtime_str, generic, impl_opaque_keys, + traits::Block as BlockT, + transaction_validity::{TransactionSource, TransactionValidity}, + ApplyExtrinsicResult, +}; +pub use sp_runtime::{MultiAddress, Perbill, Permill}; +use sp_std::prelude::*; +#[cfg(feature = "std")] +use sp_version::NativeVersion; +use sp_version::RuntimeVersion; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; +use xcm::latest::prelude::BodyId; +use xcm_config::{ + FellowshipLocation, GovernanceLocation, PriceForSiblingParachainDelivery, XcmConfig, + XcmOriginToTransactDispatchOrigin, +}; + +/// The address format for describing accounts. +pub type Address = MultiAddress; + +/// Block type as expected by this runtime. +pub type Block = generic::Block; + +/// A Block signed with an [`sp_runtime::Justification`]. +pub type SignedBlock = generic::SignedBlock; + +/// BlockId type as expected by this runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); + +/// Unchecked extrinsic type as expected by this runtime. +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +/// Migrations to apply on runtime upgrade. +pub type Migrations = (); + +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + Migrations, +>; + +impl_opaque_keys! { + pub struct SessionKeys { + pub aura: Aura, + } +} + +#[sp_version::runtime_version] +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_000, + impl_version: 0, + apis: RUNTIME_API_VERSIONS, + transaction_version: 0, + state_version: 1, +}; + +/// The version information used to identify this runtime when compiled natively. +#[cfg(feature = "std")] +pub fn native_version() -> NativeVersion { + NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } +} + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u8 = 42; +} + +pub struct IdentityCalls; +impl Contains for IdentityCalls { + fn contains(c: &RuntimeCall) -> bool { + matches!(c, RuntimeCall::Identity(_)) + } +} + +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = EverythingBut; + type BlockWeights = RuntimeBlockWeights; + type BlockLength = RuntimeBlockLength; + type AccountId = AccountId; + type Nonce = Nonce; + type Hash = Hash; + type Block = Block; + type BlockHashCount = BlockHashCount; + type DbWeight = RocksDbWeight; + type Version = Version; + type AccountData = pallet_balances::AccountData; + type SystemWeightInfo = weights::frame_system::WeightInfo; + type SS58Prefix = SS58Prefix; + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; + type WeightInfo = weights::pallet_timestamp::WeightInfo; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = weights::pallet_balances::WeightInfo; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeHoldReason = RuntimeHoldReason; + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10. + pub const TransactionByteFee: Balance = MILLICENTS; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = + pallet_transaction_payment::CurrencyAdapter>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + 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 WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; +} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = + Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + #[cfg(feature = "runtime-benchmarks")] + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< + cumulus_primitives_core::AggregateMessageOrigin, + >; + #[cfg(not(feature = "runtime-benchmarks"))] + type MessageProcessor = xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >; + type Size = u32; + // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: + type QueueChangeHandler = NarrowOriginToSibling; + type QueuePausedQuery = NarrowOriginToSibling; + type HeapSize = sp_core::ConstU32<{ 64 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; + type WeightInfo = weights::pallet_message_queue::WeightInfo; +} + +impl parachain_info::Config for Runtime {} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +parameter_types! { + // Fellows pluralistic body. + pub const FellowsBodyId: BodyId = BodyId::Technical; +} + +/// Privileged origin that represents Root or Fellows. +pub type RootOrFellows = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChannelInfo = ParachainSystem; + type VersionWrapper = PolkadotXcm; + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; + type ControllerOrigin = RootOrFellows; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = weights::cumulus_pallet_xcmp_queue::WeightInfo; + type PriceForSiblingDelivery = PriceForSiblingParachainDelivery; +} + +pub const PERIOD: u32 = 6 * HOURS; +pub const OFFSET: u32 = 0; + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions, ConstU32>; + type NextSessionRotation = pallet_session::PeriodicSessions, ConstU32>; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = weights::pallet_session::WeightInfo; +} + +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + #[cfg(feature = "experimental")] + type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + // StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow Root and the `StakingAdmi` to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // should be a multiple of session or things will get inconsistent + type KickThreshold = ConstU32; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = weights::pallet_collator_selection::WeightInfo; +} + +parameter_types! { + // One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. + pub const DepositBase: Balance = deposit(1, 88); + // Additional storage item size of 32 bytes. + pub const DepositFactor: Balance = deposit(0, 32); +} + +impl pallet_multisig::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type DepositBase = DepositBase; + type DepositFactor = DepositFactor; + type MaxSignatories = ConstU32<100>; + type WeightInfo = weights::pallet_multisig::WeightInfo; +} + +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = weights::pallet_utility::WeightInfo; +} + +// To be removed after migration is complete. +impl identity_migrator::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Reaper = EnsureRoot; + type ReapIdentityHandler = (); + type WeightInfo = weights::polkadot_runtime_common_identity_migrator::WeightInfo; +} + +// Create the runtime by composing the FRAME pallets that were previously configured. +construct_runtime!( + pub enum Runtime + { + // System support stuff. + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + ParachainSystem: cumulus_pallet_parachain_system::{ + Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned, + } = 1, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent} = 2, + ParachainInfo: parachain_info::{Pallet, Storage, Config} = 3, + + // Monetary stuff. + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 10, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, + + // Collator support. The order of these 5 are important and shall not change. + Authorship: pallet_authorship::{Pallet, Storage} = 20, + CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event, Config} = 21, + Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 22, + Aura: pallet_aura::{Pallet, Storage, Config} = 23, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Storage, Config} = 24, + + // XCM helpers. + XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 30, + PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin, Config} = 31, + CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 32, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 34, + + // Handy utilities. + Utility: pallet_utility::{Pallet, Call, Event} = 40, + Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 41, + + // The main stage. + Identity: pallet_identity::{Pallet, Call, Storage, Event} = 50, + + // To migrate deposits + IdentityMigrator: identity_migrator::{Pallet, Call, Event} = 248, + } +); + +#[cfg(feature = "runtime-benchmarks")] +#[macro_use] +extern crate frame_benchmarking; + +#[cfg(feature = "runtime-benchmarks")] +mod benches { + define_benchmarks!( + // Substrate + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_identity, Identity] + [pallet_multisig, Multisig] + [pallet_session, SessionBench::] + [pallet_utility, Utility] + [pallet_timestamp, Timestamp] + // Polkadot + [polkadot_runtime_common::identity_migrator, IdentityMigrator] + // Cumulus + [cumulus_pallet_xcmp_queue, XcmpQueue] + [pallet_collator_selection, CollatorSelection] + // XCM + [pallet_xcm, PalletXcmExtrinsiscsBenchmark::] + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] + ); +} + +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()) + } + + fn authorities() -> Vec { + Aura::authorities().into_inner() + } + } + + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block) + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + + fn metadata_at_version(version: u32) -> Option { + Runtime::metadata_at_version(version) + } + + fn metadata_versions() -> sp_std::vec::Vec { + Runtime::metadata_versions() + } + } + + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } + + fn finalize_block() -> ::Header { + Executive::finalize_block() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + data.create_extrinsics() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + data.check_extrinsics(&block) + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + tx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + Executive::validate_transaction(source, tx, block_hash) + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + Executive::offchain_worker(header) + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(seed: Option>) -> Vec { + SessionKeys::generate(seed) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + TransactionPayment::query_info(uxt, len) + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_fee_details(uxt, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + fn query_weight_to_fee(weight: Weight) -> Balance { + TransactionPayment::weight_to_fee(weight) + } + fn query_length_to_fee(length: u32) -> Balance { + TransactionPayment::length_to_fee(length) + } + } + + impl cumulus_primitives_core::CollectCollationInfo for Runtime { + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) + } + } + + #[cfg(feature = "try-runtime")] + impl frame_try_runtime::TryRuntime for Runtime { + fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { + let weight = Executive::try_runtime_upgrade(checks).unwrap(); + (weight, RuntimeBlockWeights::get().max_block) + } + + fn execute_block( + block: Block, + state_root_check: bool, + signature_check: bool, + select: frame_try_runtime::TryStateSelect, + ) -> Weight { + // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to + // have a backtrace here. + Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() + } + } + + #[cfg(feature = "runtime-benchmarks")] + impl frame_benchmarking::Benchmark for Runtime { + fn benchmark_metadata(extra: bool) -> ( + Vec, + Vec, + ) { + use frame_benchmarking::{Benchmarking, BenchmarkList}; + use frame_support::traits::StorageInfoTrait; + use frame_system_benchmarking::Pallet as SystemBench; + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; + + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let mut list = Vec::::new(); + list_benchmarks!(list, extra); + + let storage_info = AllPalletsWithSystem::storage_info(); + (list, storage_info) + } + + fn dispatch_benchmark( + config: frame_benchmarking::BenchmarkConfig + ) -> Result, sp_runtime::RuntimeString> { + use frame_benchmarking::{Benchmarking, BenchmarkBatch, BenchmarkError}; + use sp_storage::TrackedStorageKey; + + use frame_system_benchmarking::Pallet as SystemBench; + impl frame_system_benchmarking::Config for Runtime { + fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), BenchmarkError> { + ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); + Ok(()) + } + + fn verify_set_code() { + System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); + } + } + + use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + impl cumulus_pallet_session_benchmarking::Config for Runtime {} + + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; + impl pallet_xcm::benchmarking::Config for Runtime { + fn reachable_dest() -> Option { + Some(Parent.into()) + } + + fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + // Relay/native token can be teleported between People and Relay. + Some(( + MultiAsset { + fun: Fungible(EXISTENTIAL_DEPOSIT), + id: Concrete(Parent.into()) + }, + Parent.into(), + )) + } + + fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { + None + } + } + + use xcm::latest::prelude::*; + use xcm_config::{PriceForParentDelivery, RelayLocation}; + + parameter_types! { + pub ExistentialDepositMultiAsset: Option = Some(( + RelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + XcmConfig, + ExistentialDepositMultiAsset, + PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(RelayLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> MultiAssets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + MultiAsset { + id: Concrete(RelayLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(MultiLocation, MultiAsset)> = Some(( + RelayLocation::get(), + MultiAsset { fun: Fungible(UNITS), id: Concrete(RelayLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(MultiLocation, MultiAsset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_multi_asset() -> MultiAsset { + MultiAsset { + id: Concrete(RelayLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type RuntimeCall = RuntimeCall; + type TransactAsset = Balances; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(MultiAssets, MultiAssets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(MultiLocation, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(MultiLocation, RuntimeCall), BenchmarkError> { + Ok((RelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(RelayLocation::get()) + } + + fn claimable_asset() -> Result<(MultiLocation, MultiLocation, MultiAssets), BenchmarkError> { + let origin = RelayLocation::get(); + let assets: MultiAssets = (Concrete(RelayLocation::get()), 1_000 * UNITS).into(); + let ticket = MultiLocation { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn unlockable_asset() -> Result<(MultiLocation, MultiLocation, MultiAsset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(MultiLocation, NetworkId, InteriorMultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(MultiLocation, MultiLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; + + let mut batches = Vec::::new(); + let params = (&config, &whitelist); + add_benchmarks!(params, batches); + + Ok(batches) + } + } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn create_default_config() -> Vec { + create_default_config::() + } + + fn build_config(config: Vec) -> sp_genesis_builder::Result { + build_config::(config) + } + } +} + +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/people.rs b/cumulus/parachains/runtimes/people/people-westend/src/people.rs new file mode 100644 index 0000000000000000000000000000000000000000..d279be65110a8a925639730baaac5cefc71f0563 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/people.rs @@ -0,0 +1,204 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::xcm_config::LocationToAccountId; +use codec::{Decode, Encode, MaxEncodedLen}; +use enumflags2::{bitflags, BitFlags}; +use frame_support::{ + parameter_types, traits::ConstU32, CloneNoBound, EqNoBound, PartialEqNoBound, + RuntimeDebugNoBound, +}; +use pallet_identity::{Data, IdentityInformationProvider}; +use parachains_common::impls::ToParentTreasury; +use scale_info::TypeInfo; +use sp_runtime::{traits::AccountIdConversion, RuntimeDebug}; +use sp_std::prelude::*; + +parameter_types! { + // 27 | Min encoded size of `Registration` + // - 10 | Min encoded size of `IdentityInfo` + // -----| + // 17 | Min size without `IdentityInfo` (accounted for in byte deposit) + pub const BasicDeposit: Balance = deposit(1, 17); + pub const ByteDeposit: Balance = deposit(0, 1); + pub const SubAccountDeposit: Balance = deposit(1, 53); + pub RelayTreasuryAccount: AccountId = + parachains_common::TREASURY_PALLET_ID.into_account_truncating(); +} + +impl pallet_identity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BasicDeposit = BasicDeposit; + type ByteDeposit = ByteDeposit; + type SubAccountDeposit = SubAccountDeposit; + type MaxSubAccounts = ConstU32<100>; + type IdentityInformation = IdentityInfo; + type MaxRegistrars = ConstU32<20>; + type Slashed = ToParentTreasury; + type ForceOrigin = EnsureRoot; + type RegistrarOrigin = EnsureRoot; + type WeightInfo = weights::pallet_identity::WeightInfo; +} + +/// The fields that we use to identify the owner of an account with. Each corresponds to a field +/// in the `IdentityInfo` struct. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug)] +pub enum IdentityField { + Display, + Legal, + Web, + Matrix, + Email, + PgpFingerprint, + Image, + Twitter, + GitHub, + Discord, +} + +/// Information concerning the identity of the controller of an account. +#[derive( + CloneNoBound, + Encode, + Decode, + EqNoBound, + MaxEncodedLen, + PartialEqNoBound, + RuntimeDebugNoBound, + TypeInfo, +)] +#[codec(mel_bound())] +#[cfg_attr(test, derive(frame_support::DefaultNoBound))] +pub struct IdentityInfo { + /// A reasonable display name for the controller of the account. This should be whatever it is + /// that it is typically known as and should not be confusable with other entities, given + /// reasonable context. + /// + /// Stored as UTF-8. + pub display: Data, + + /// The full legal name in the local jurisdiction of the entity. This might be a bit + /// long-winded. + /// + /// Stored as UTF-8. + pub legal: Data, + + /// A representative website held by the controller of the account. + /// + /// NOTE: `https://` is automatically prepended. + /// + /// Stored as UTF-8. + pub web: Data, + + /// The Matrix (e.g. for Element) handle held by the controller of the account. Previously, + /// this was called `riot`. + /// + /// Stored as UTF-8. + pub matrix: Data, + + /// The email address of the controller of the account. + /// + /// Stored as UTF-8. + pub email: Data, + + /// The PGP/GPG public key of the controller of the account. + pub pgp_fingerprint: Option<[u8; 20]>, + + /// A graphic image representing the controller of the account. Should be a company, + /// organization or project logo or a headshot in the case of a human. + pub image: Data, + + /// The Twitter identity. The leading `@` character may be elided. + pub twitter: Data, + + /// The GitHub username of the controller of the account. + pub github: Data, + + /// The Discord username of the controller of the account. + pub discord: Data, +} + +impl IdentityInformationProvider for IdentityInfo { + type FieldsIdentifier = u64; + + fn has_identity(&self, fields: Self::FieldsIdentifier) -> bool { + self.fields().bits() & fields == fields + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_identity_info() -> Self { + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + + IdentityInfo { + display: data.clone(), + legal: data.clone(), + web: data.clone(), + matrix: data.clone(), + email: data.clone(), + pgp_fingerprint: Some([0; 20]), + image: data.clone(), + twitter: data.clone(), + github: data.clone(), + discord: data, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn all_fields() -> Self::FieldsIdentifier { + use enumflags2::BitFlag; + IdentityField::all().bits() + } +} + +impl IdentityInfo { + pub(crate) fn fields(&self) -> BitFlags { + let mut res = >::empty(); + if !self.display.is_none() { + res.insert(IdentityField::Display); + } + if !self.legal.is_none() { + res.insert(IdentityField::Legal); + } + if !self.web.is_none() { + res.insert(IdentityField::Web); + } + if !self.matrix.is_none() { + res.insert(IdentityField::Matrix); + } + if !self.email.is_none() { + res.insert(IdentityField::Email); + } + if self.pgp_fingerprint.is_some() { + res.insert(IdentityField::PgpFingerprint); + } + if !self.image.is_none() { + res.insert(IdentityField::Image); + } + if !self.twitter.is_none() { + res.insert(IdentityField::Twitter); + } + if !self.github.is_none() { + res.insert(IdentityField::GitHub); + } + if !self.discord.is_none() { + res.insert(IdentityField::Discord); + } + res + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..2bd7975bf98c36996520716c9dc11822d8287234 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/block_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Importing a block with 0 Extrinsics. + pub const BlockExecutionWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(5_000_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::BlockExecutionWeight::get(); + + // At least 100 µs. + assert!( + w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 50 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..fcea5fd1bf679c803509dd4529b45e4994a7438c --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -0,0 +1,53 @@ +// 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. + +//! Need to rerun + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// 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) + /// 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_622_000 picoseconds. + Weight::from_parts(1_709_000, 0) + .saturating_add(Weight::from_parts(0, 8013)) + // Standard Error: 22_138 + .saturating_add(Weight::from_parts(23_923_169, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_xcmp_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..71ac6ef518059d89af3fbf1bb46431cbe65c61b5 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -0,0 +1,129 @@ +// 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. + +//! Need to rerun + +#![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 `cumulus_pallet_xcmp_queue`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { + /// 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_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`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: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn enqueue_xcmp_message() -> Weight { + // Proof Size summary in bytes: + // Measured: `82` + // Estimated: `3517` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn suspend_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1561` + // Minimum execution time: 3_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + .saturating_add(Weight::from_parts(0, 1561)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn resume_channel() -> Weight { + // Proof Size summary in bytes: + // Measured: `111` + // Estimated: `1596` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 0) + .saturating_add(Weight::from_parts(0, 1596)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn take_first_concatenated_xcm() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 44_000_000 picoseconds. + Weight::from_parts(45_000_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: `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: `XcmpQueue::QueueConfig` (r:1 w:0) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `XcmpQueue::InboundXcmpSuspended` (r:1 w:0) + /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) + fn on_idle_good_msg() -> Weight { + // Proof Size summary in bytes: + // Measured: `65711` + // Estimated: `69176` + // Minimum execution time: 67_000_000 picoseconds. + Weight::from_parts(73_000_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 { + // Proof Size summary in bytes: + // Measured: `65710` + // Estimated: `69175` + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(55_000_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/people/people-westend/src/weights/extrinsic_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..898d72ec5b19519a77ec0b75bb65d757213b35d4 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/extrinsic_weights.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod constants { + use frame_support::{ + parameter_types, + weights::{constants, Weight}, + }; + + parameter_types! { + /// Executing a NO-OP `System::remarks` Extrinsic. + pub const ExtrinsicBaseWeight: Weight = + Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(125_000), 0); + } + + #[cfg(test)] + mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::constants::ExtrinsicBaseWeight::get(); + + // At least 10 µs. + assert!( + w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system.rs new file mode 100644 index 0000000000000000000000000000000000000000..d763fe1c4261eaa53960129e7fb83fa3ee7844a5 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system.rs @@ -0,0 +1,160 @@ +// 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 `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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=frame_system +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/frame_system.rs + +#![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 `frame_system`. +pub struct WeightInfo(PhantomData); +impl frame_system::WeightInfo for WeightInfo { + /// The range of component `b` is `[0, 3932160]`. + fn remark(b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_432_000 picoseconds. + Weight::from_parts(2_458_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(367, 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) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_405, 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) + 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) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn set_code() -> Weight { + Weight::from_parts(1_000_000, 0) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn set_storage(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_493_000 picoseconds. + Weight::from_parts(2_523_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())) + .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) + /// 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) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 784 + .saturating_add(Weight::from_parts(493_844, 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) + /// 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())) + .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())) + } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3396a8caea0599574da40135c74bc19f9cf52125 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs @@ -0,0 +1,39 @@ +// 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. + +//! Expose the auto generated weight files. + +pub mod block_weights; +pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_xcmp_queue; +pub mod extrinsic_weights; +pub mod frame_system; +pub mod pallet_balances; +pub mod pallet_collator_selection; +pub mod pallet_identity; +pub mod pallet_message_queue; +pub mod pallet_multisig; +pub mod pallet_session; +pub mod pallet_timestamp; +pub mod pallet_utility; +pub mod pallet_xcm; +pub mod paritydb_weights; +pub mod polkadot_runtime_common_identity_migrator; +pub mod rocksdb_weights; +pub mod xcm; + +pub use block_weights::constants::BlockExecutionWeight; +pub use extrinsic_weights::constants::ExtrinsicBaseWeight; +pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_balances.rs new file mode 100644 index 0000000000000000000000000000000000000000..e53c8878dd1762b927815c9d6c55819a9f1a2f74 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_balances.rs @@ -0,0 +1,150 @@ +// 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_balances` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_balances +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_balances.rs + +#![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_balances`. +pub struct WeightInfo(PhantomData); +impl pallet_balances::WeightInfo for WeightInfo { + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 59_580_000 picoseconds. + Weight::from_parts(60_317_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_keep_alive() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 45_490_000 picoseconds. + Weight::from_parts(45_910_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 17_353_000 picoseconds. + Weight::from_parts(17_676_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 25_017_000 picoseconds. + Weight::from_parts(25_542_000, 0) + .saturating_add(Weight::from_parts(0, 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) + fn force_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 61_161_000 picoseconds. + Weight::from_parts(61_665_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn transfer_all() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 55_422_000 picoseconds. + Weight::from_parts(55_880_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn force_unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 20_477_000 picoseconds. + Weight::from_parts(20_871_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: System Account (r:999 w:999) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (136 ±0)` + // Estimated: `990 + u * (2603 ±0)` + // Minimum execution time: 19_501_000 picoseconds. + Weight::from_parts(19_726_000, 0) + .saturating_add(Weight::from_parts(0, 990)) + // Standard Error: 9_495 + .saturating_add(Weight::from_parts(15_658_957, 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())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_collator_selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..811e2b7ad87ed8e25ddf0feffdec5d0edf1ae4c6 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_collator_selection.rs @@ -0,0 +1,242 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_collator_selection +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_collator_selection.rs + +#![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_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]`. + 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())) + .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())) + } + /// 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: 6_977_000 picoseconds. + Weight::from_parts(7_246_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .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 { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_388_000 picoseconds. + Weight::from_parts(7_677_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 { + // 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)) + } + /// 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` + // Estimated: `6196` + // Minimum execution time: 44_415_000 picoseconds. + Weight::from_parts(44_732_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())) + } + fn update_bond(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + fn take_candidate_slot(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `306 + c * (50 ±0)` + // Estimated: `6287` + // Minimum execution time: 34_814_000 picoseconds. + Weight::from_parts(36_371_520, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_391 + .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// 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]`. + 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)) + .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())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs new file mode 100644 index 0000000000000000000000000000000000000000..65cac7875bada8355442b60adce1887f8a54786a --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs @@ -0,0 +1,315 @@ +// 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. + +//! Taken from Rococo Relay Chain. Needs to rerun. + +#![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_identity`. +pub struct WeightInfo(PhantomData); +impl pallet_identity::WeightInfo for WeightInfo { + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn add_registrar(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 12_290_000 picoseconds. + Weight::from_parts(12_664_362, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_347 + .saturating_add(Weight::from_parts(88_179, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn set_identity(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `442 + r * (5 ±0)` + // Estimated: `11003` + // Minimum execution time: 31_373_000 picoseconds. + Weight::from_parts(30_435_545, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_307 + .saturating_add(Weight::from_parts(92_753, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:100 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 100]`. + fn set_subs_new(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `11003 + s * (2589 ±0)` + // Minimum execution time: 9_251_000 picoseconds. + Weight::from_parts(22_039_210, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 40_779 + .saturating_add(Weight::from_parts(2_898_525, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(s.into()))) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 2589).saturating_mul(s.into())) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `p` is `[0, 100]`. + fn set_subs_old(p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `194 + p * (32 ±0)` + // Estimated: `11003` + // Minimum execution time: 9_329_000 picoseconds. + Weight::from_parts(24_055_061, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 3_428 + .saturating_add(Weight::from_parts(1_130_604, 0).saturating_mul(p.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(p.into()))) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + fn clear_identity(_r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `469 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 53_365_000 picoseconds. + Weight::from_parts(35_391_422, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_353 + .saturating_add(Weight::from_parts(1_074_019, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn request_judgement(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `367 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_509_000 picoseconds. + Weight::from_parts(31_745_585, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_214 + .saturating_add(Weight::from_parts(83_822, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + fn cancel_request(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `398 + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 29_609_000 picoseconds. + Weight::from_parts(28_572_602, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_528 + .saturating_add(Weight::from_parts(85_593, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_fee(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_793_000 picoseconds. + Weight::from_parts(8_173_888, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_569 + .saturating_add(Weight::from_parts(72_367, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_account_id(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_708_000 picoseconds. + Weight::from_parts(8_091_149, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 869 + .saturating_add(Weight::from_parts(87_993, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn set_fields(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_601_000 picoseconds. + Weight::from_parts(8_038_414, 0) + .saturating_add(Weight::from_parts(0, 2626)) + // Standard Error: 1_041 + .saturating_add(Weight::from_parts(82_588, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 19]`. + fn provide_judgement(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `445 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 23_114_000 picoseconds. + Weight::from_parts(22_076_548, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 2_881 + .saturating_add(Weight::from_parts(109_812, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `676 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 70_007_000 picoseconds. + Weight::from_parts(50_186_495, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 6_533 + .saturating_add(Weight::from_parts(15_486, 0).saturating_mul(r.into())) + // Standard Error: 1_275 + .saturating_add(Weight::from_parts(1_085_117, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 99]`. + fn add_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `475 + s * (36 ±0)` + // Estimated: `11003` + // Minimum execution time: 28_453_000 picoseconds. + Weight::from_parts(33_165_934, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_217 + .saturating_add(Weight::from_parts(65_401, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// The range of component `s` is `[1, 100]`. + fn rename_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `591 + s * (3 ±0)` + // Estimated: `11003` + // Minimum execution time: 12_846_000 picoseconds. + Weight::from_parts(14_710_284, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 496 + .saturating_add(Weight::from_parts(19_539, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// The range of component `s` is `[1, 100]`. + fn remove_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `638 + s * (35 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_183_000 picoseconds. + Weight::from_parts(35_296_731, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 854 + .saturating_add(Weight::from_parts(52_028, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `s` is `[0, 99]`. + fn quit_sub(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `704 + s * (37 ±0)` + // Estimated: `6723` + // Minimum execution time: 24_941_000 picoseconds. + Weight::from_parts(27_433_059, 0) + .saturating_add(Weight::from_parts(0, 6723)) + // Standard Error: 856 + .saturating_add(Weight::from_parts(57_463, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_message_queue.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe1911b77a72dbfed0309722a1c59f37b56cb40f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_message_queue.rs @@ -0,0 +1,156 @@ +// 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. + +//! Need to rerun + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::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) + fn ready_ring_knit() -> Weight { + // Proof Size summary in bytes: + // Measured: `189` + // Estimated: `7534` + // Minimum execution time: 13_668_000 picoseconds. + Weight::from_parts(13_668_000, 0) + .saturating_add(Weight::from_parts(0, 7534)) + .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) + fn ready_ring_unknit() -> Weight { + // Proof Size summary in bytes: + // Measured: `184` + // Estimated: `7534` + // Minimum execution time: 11_106_000 picoseconds. + Weight::from_parts(11_106_000, 0) + .saturating_add(Weight::from_parts(0, 7534)) + .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) + fn service_queue_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `6` + // Estimated: `3517` + // Minimum execution time: 4_921_000 picoseconds. + Weight::from_parts(4_921_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) + fn service_page_base_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 6_879_000 picoseconds. + Weight::from_parts(6_879_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) + fn service_page_base_no_completion() -> Weight { + // Proof Size summary in bytes: + // Measured: `72` + // Estimated: `69050` + // Minimum execution time: 7_564_000 picoseconds. + Weight::from_parts(7_564_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn service_page_item() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 59_963_000 picoseconds. + Weight::from_parts(59_963_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + /// 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: 7_200_000 picoseconds. + Weight::from_parts(7_200_000, 0) + .saturating_add(Weight::from_parts(0, 5007)) + .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) + fn reap_page() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 41_366_000 picoseconds. + Weight::from_parts(41_366_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .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) + fn execute_overweight_page_removed() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 60_538_000 picoseconds. + Weight::from_parts(60_538_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .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) + fn execute_overweight_page_updated() -> Weight { + // Proof Size summary in bytes: + // Measured: `65667` + // Estimated: `72567` + // Minimum execution time: 73_665_000 picoseconds. + Weight::from_parts(73_665_000, 0) + .saturating_add(Weight::from_parts(0, 72567)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs new file mode 100644 index 0000000000000000000000000000000000000000..70809dea2366cc71e5ffb6bfe62feeb6d546f46f --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs @@ -0,0 +1,162 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_multisig +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_multisig.rs + +#![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_multisig`. +pub struct WeightInfo(PhantomData); +impl pallet_multisig::WeightInfo for WeightInfo { + /// The range of component `z` is `[0, 10000]`. + fn as_multi_threshold_1(z: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 11_337_000 picoseconds. + Weight::from_parts(11_960_522, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 9 + .saturating_add(Weight::from_parts(504, 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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 41_128_000 picoseconds. + Weight::from_parts(35_215_592, 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())) + .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) + /// 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) + .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())) + .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) + /// 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)` + // Estimated: `6811` + // Minimum execution time: 45_716_000 picoseconds. + Weight::from_parts(38_332_947, 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())) + .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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 487 + .saturating_add(Weight::from_parts(67_443, 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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 434 + .saturating_add(Weight::from_parts(62_989, 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) + /// 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) + .saturating_add(Weight::from_parts(0, 6811)) + // Standard Error: 585 + .saturating_add(Weight::from_parts(69_979, 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/people/people-westend/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_session.rs new file mode 100644 index 0000000000000000000000000000000000000000..872d3f1373659ddffa906d046ad8eaac5b10d5bf --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_session.rs @@ -0,0 +1,78 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_session +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_session.rs + +#![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_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) + 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)) + .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) + 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)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_timestamp.rs new file mode 100644 index 0000000000000000000000000000000000000000..2eb3173099ddb079910f219b13d6af0f38b17566 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_timestamp.rs @@ -0,0 +1,72 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_timestamp +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_timestamp.rs + +#![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_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) + fn set() -> Weight { + // Proof Size summary in bytes: + // Measured: `49` + // Estimated: `1493` + // Minimum execution time: 7_986_000 picoseconds. + Weight::from_parts(8_134_000, 0) + .saturating_add(Weight::from_parts(0, 1493)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + fn on_finalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `57` + // Estimated: `0` + // Minimum execution time: 3_257_000 picoseconds. + Weight::from_parts(3_366_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_utility.rs new file mode 100644 index 0000000000000000000000000000000000000000..782b0ad6de8d677a2363ff9f22e3d2d0991757ad --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_utility.rs @@ -0,0 +1,99 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_utility +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_utility.rs + +#![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_utility`. +pub struct WeightInfo(PhantomData); +impl pallet_utility::WeightInfo for WeightInfo { + /// The range of component `c` is `[0, 1000]`. + fn batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_697_000 picoseconds. + Weight::from_parts(11_859_145, 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())) + } + 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) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn batch_all(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_741_000 picoseconds. + Weight::from_parts(15_928_547, 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())) + } + 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) + .saturating_add(Weight::from_parts(0, 0)) + } + /// The range of component `c` is `[0, 1000]`. + fn force_batch(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_814_000 picoseconds. + Weight::from_parts(13_920_831, 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())) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3b60471b850e0fffbad01915aaf461be3d48ae0 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs @@ -0,0 +1,342 @@ +// 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_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: `[]` +//! 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("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_xcm.rs + +#![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_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) + 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)) + .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) + 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)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn reserve_transfer_assets() -> Weight { + // Proof Size summary in bytes: + // 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: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `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: `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 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)) + } + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + fn execute() -> Weight { + // Proof Size summary in bytes: + // 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: PolkadotXcm SupportedVersion (r:0 w:1) + /// Proof Skipped: 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) + .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) + 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) + .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) + 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)) + .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) + 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)) + .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) + 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) + .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) + 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)) + .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) + 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)) + .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) + 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)) + } + /// 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) + 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)) + .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) + 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)) + } + /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) + /// Proof Skipped: 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)) + .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) + 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)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`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 new_query() -> Weight { + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 5_496_000 picoseconds. + Weight::from_parts(5_652_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `PolkadotXcm::Queries` (r:1 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn take_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `7740` + // Estimated: `11205` + // Minimum execution time: 26_140_000 picoseconds. + Weight::from_parts(26_824_000, 0) + .saturating_add(Weight::from_parts(0, 11205)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/paritydb_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/paritydb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..2699f3abbb1f652273ae09b9600ccc1a8d417ab4 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/paritydb_weights.rs @@ -0,0 +1,61 @@ +// 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 mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights + /// are available for brave runtime engineers who may want to try this out as default. + pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::ParityDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/polkadot_runtime_common_identity_migrator.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/polkadot_runtime_common_identity_migrator.rs new file mode 100644 index 0000000000000000000000000000000000000000..4449c8f2b020adedffe2f97e2c24b338c4c0e623 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/polkadot_runtime_common_identity_migrator.rs @@ -0,0 +1,97 @@ +// 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 `polkadot_runtime_common::identity_migrator` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-07, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `sbtb`, CPU: `13th Gen Intel(R) Core(TM) i7-1365U` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/polkadot +// benchmark +// pallet +// --chain=rococo-dev +// --steps=2 +// --repeat=1 +// --pallet=polkadot_runtime_common::identity_migrator +// --extrinsic=* +// --output=./migrator-release.rs + +#![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 `polkadot_runtime_common::identity_migrator`. +pub struct WeightInfo(PhantomData); +impl polkadot_runtime_common::identity_migrator::WeightInfo for WeightInfo { + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`) + /// Storage: `Identity::SubsOf` (r:1 w:1) + /// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) + /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) + /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) + /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Identity::SuperOf` (r:0 w:100) + /// Proof: `Identity::SuperOf` (`max_values`: None, `max_size`: Some(114), added: 2589, mode: `MaxEncodedLen`) + /// The range of component `r` is `[0, 20]`. + /// The range of component `s` is `[0, 100]`. + fn reap_identity(r: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `7292 + r * (8 ±0) + s * (32 ±0)` + // Estimated: `11003 + r * (8 ±0) + s * (33 ±0)` + // Minimum execution time: 163_756_000 picoseconds. + Weight::from_parts(158_982_500, 0) + .saturating_add(Weight::from_parts(0, 11003)) + // Standard Error: 1_143_629 + .saturating_add(Weight::from_parts(238_675, 0).saturating_mul(r.into())) + // Standard Error: 228_725 + .saturating_add(Weight::from_parts(1_529_645, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 33).saturating_mul(s.into())) + } + /// Storage: `Identity::IdentityOf` (r:1 w:1) + /// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7538), added: 10013, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Identity::SubsOf` (r:1 w:1) + /// Proof: `Identity::SubsOf` (`max_values`: None, `max_size`: Some(3258), added: 5733, mode: `MaxEncodedLen`) + fn poke_deposit() -> Weight { + // Proof Size summary in bytes: + // Measured: `7229` + // Estimated: `11003` + // Minimum execution time: 137_570_000 picoseconds. + Weight::from_parts(137_570_000, 0) + .saturating_add(Weight::from_parts(0, 11003)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/rocksdb_weights.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/rocksdb_weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..61b48fb2350ebabad6f66222eaf9e97bfa7a7939 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/rocksdb_weights.rs @@ -0,0 +1,61 @@ +// 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 mod constants { + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; + + parameter_types! { + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout + /// the runtime. + pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, + }; + } + + #[cfg(test)] + mod test_db_weights { + use super::constants::RocksDbWeight as W; + use frame_support::weights::constants; + + /// Checks that all weights exist and have sane values. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + // At least 1 µs. + assert!( + W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Read weight should be at least 1 µs." + ); + assert!( + W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, + "Write weight should be at least 1 µs." + ); + // At most 1 ms. + assert!( + W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Read weight should be at most 1 ms." + ); + assert!( + W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, + "Write weight should be at most 1 ms." + ); + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5d6f90e9f1bc211a92040dbc5043d0b8e2cecb73 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -0,0 +1,252 @@ +// 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. + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_std::prelude::*; +use xcm::{latest::prelude::*, DoubleEncoded}; + +trait WeighMultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight; +} + +const MAX_ASSETS: u64 = 100; + +impl WeighMultiAssets for MultiAssetFilter { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighMultiAssets for MultiAssets { + fn weigh_multi_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct PeopleWestendXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for PeopleWestendXcmWeight { + fn withdraw_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::withdraw_asset()) + } + // Currently there is no trusted reserve + fn reserve_asset_deposited(_assets: &MultiAssets) -> Weight { + // TODO: hardcoded - fix https://github.com/paritytech/cumulus/issues/1974 + Weight::from_parts(1_000_000_000_u64, 0) + } + fn receive_teleported_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() + } + fn transfer_asset(assets: &MultiAssets, _dest: &MultiLocation) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset( + assets: &MultiAssets, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _require_weight_at_most: &Weight, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() + } + fn descend_origin(_who: &InteriorMultiLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + + fn deposit_asset(assets: &MultiAssetFilter, _dest: &MultiLocation) -> Weight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_parts(1_000_000_000_u64, 0); + let weight = assets.weigh_multi_assets(XcmFungibleWeight::::deposit_asset()); + hardcoded_weight.min(weight) + } + fn deposit_reserve_asset( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &MultiAssetFilter, _receive: &MultiAssets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &MultiAssetFilter, + _reserve: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::initiate_reserve_withdraw()) + } + fn initiate_teleport( + assets: &MultiAssetFilter, + _dest: &MultiLocation, + _xcm: &Xcm<()>, + ) -> Weight { + // Hardcoded till the XCM pallet is fixed + let hardcoded_weight = Weight::from_parts(200_000_000_u64, 0); + let weight = assets.weigh_multi_assets(XcmFungibleWeight::::initiate_teleport()); + hardcoded_weight.min(weight) + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &MultiAssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &MultiAsset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn claim_asset(_assets: &MultiAssets, _ticket: &MultiLocation) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &MultiAssets) -> Weight { + assets.weigh_multi_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::::expect_transact_status() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn request_unlock(_: &MultiAsset, _: &MultiLocation) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &MultiLocation) -> Weight { + // XCM Executor does not currently support alias origin operations + Weight::MAX + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 0000000000000000000000000000000000000000..efffd318817106baf4e2f7661f4cd0a8c333b722 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,157 @@ +// 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_xcm_benchmarks::fungible` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::fungible +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + pub fn withdraw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 23_363_000 picoseconds. + Weight::from_parts(23_663_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) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `153` + // Estimated: `6196` + // Minimum execution time: 49_093_000 picoseconds. + Weight::from_parts(49_719_000, 6196) + .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: 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 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) + pub fn transfer_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `6196` + // Minimum execution time: 74_134_000 picoseconds. + Weight::from_parts(74_719_000, 6196) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + pub fn receive_teleported_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_726_000 picoseconds. + Weight::from_parts(3_881_000, 0) + } + // Storage: System Account (r:1 w:1) + // Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3593` + // Minimum execution time: 25_903_000 picoseconds. + Weight::from_parts(26_150_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // 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: 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) + pub fn deposit_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `122` + // Estimated: `3593` + // Minimum execution time: 51_084_000 picoseconds. + Weight::from_parts(51_859_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: 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) + pub fn initiate_teleport() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 28_038_000 picoseconds. + Weight::from_parts(28_438_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7b10f95c792a9874bd98ccf4eb20bdf87e20bb0 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,347 @@ +// 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_xcm_benchmarks::generic` +//! +//! 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: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 + +// Executed Command: +// ./artifacts/polkadot-parachain +// benchmark +// pallet +// --template=./templates/xcm-bench-template.hbs +// --chain=people-polkadot-dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_xcm_benchmarks::generic +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/xcm/pallet_xcm_benchmarks_generic.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +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: 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) + pub fn report_holding() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 30_819_000 picoseconds. + Weight::from_parts(31_157_000, 3535) + .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: 2_869_000 picoseconds. + Weight::from_parts(2_920_000, 0) + } + // Storage: PolkadotXcm Queries (r:1 w:0) + // Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + pub fn query_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 10_268_000 picoseconds. + Weight::from_parts(10_496_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: 11_990_000 picoseconds. + Weight::from_parts(12_206_000, 0) + } + pub fn refund_surplus() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_170_000 picoseconds. + Weight::from_parts(3_308_000, 0) + } + pub fn set_error_handler() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_650_000 picoseconds. + Weight::from_parts(2_783_000, 0) + } + pub fn set_appendix() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_681_000 picoseconds. + Weight::from_parts(2_829_000, 0) + } + pub fn clear_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_622_000 picoseconds. + Weight::from_parts(2_688_000, 0) + } + pub fn descend_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_385_000 picoseconds. + Weight::from_parts(3_538_000, 0) + } + pub fn clear_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_630_000 picoseconds. + Weight::from_parts(2_720_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: 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) + pub fn report_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 24_446_000 picoseconds. + Weight::from_parts(24_854_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: PolkadotXcm AssetTraps (r:1 w:1) + // Proof Skipped: PolkadotXcm AssetTraps (max_values: None, max_size: None, mode: Measured) + pub fn claim_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 14_713_000 picoseconds. + Weight::from_parts(15_010_000, 3555) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_702_000 picoseconds. + Weight::from_parts(2_744_000, 0) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:1 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) + pub fn subscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 25_955_000 picoseconds. + Weight::from_parts(26_632_000, 3503) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: PolkadotXcm VersionNotifyTargets (r:0 w:1) + // Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + pub fn unsubscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_965_000 picoseconds. + Weight::from_parts(5_168_000, 0) + .saturating_add(T::DbWeight::get().writes(1)) + } + // 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 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) + pub fn initiate_reserve_withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 27_707_000 picoseconds. + Weight::from_parts(28_081_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn burn_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 4_215_000 picoseconds. + Weight::from_parts(4_362_000, 0) + } + pub fn expect_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_843_000 picoseconds. + Weight::from_parts(2_957_000, 0) + } + pub fn expect_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_751_000 picoseconds. + Weight::from_parts(2_809_000, 0) + } + pub fn expect_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_674_000 picoseconds. + Weight::from_parts(2_737_000, 0) + } + pub fn expect_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_891_000 picoseconds. + Weight::from_parts(2_952_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: 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) + pub fn query_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 28_600_000 picoseconds. + Weight::from_parts(29_001_000, 3535) + .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: 4_748_000 picoseconds. + Weight::from_parts(4_813_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: 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) + pub fn report_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 25_483_000 picoseconds. + Weight::from_parts(25_737_000, 3535) + .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: 2_755_000 picoseconds. + Weight::from_parts(2_817_000, 0) + } + pub fn set_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_700_000 picoseconds. + Weight::from_parts(2_773_000, 0) + } + pub fn clear_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_670_000 picoseconds. + Weight::from_parts(2_711_000, 0) + } + pub fn set_fees_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_710_000 picoseconds. + Weight::from_parts(2_762_000, 0) + } + pub fn unpaid_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_839_000 picoseconds. + Weight::from_parts(2_931_000, 0) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a51cf72d5b45d09946974ffbb077605449009af --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -0,0 +1,324 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{ + AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, + Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, +}; +use crate::{TransactionByteFee, CENTS}; +use frame_support::{ + match_types, parameter_types, + traits::{ConstU32, Contains, Equals, Everything, Nothing}, +}; +use frame_system::EnsureRoot; +use pallet_xcm::XcmPassthrough; +use parachains_common::{ + impls::ToStakingPot, + xcm_config::{ + AllSiblingSystemParachains, ConcreteAssetFromSystem, 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, HashedDescription, IsConcrete, + 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 RootLocation: MultiLocation = MultiLocation::here(); + pub const RelayLocation: MultiLocation = MultiLocation::parent(); + pub const RelayNetwork: Option = Some(NetworkId::Westend); + pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); + pub UniversalLocation: InteriorMultiLocation = + X2(GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())); + pub const MaxInstructions: u32 = 100; + pub const MaxAssetsIntoHolding: u32 = 64; + pub FellowshipLocation: MultiLocation = MultiLocation::new(1, Parachain(1001)); + pub const GovernanceLocation: MultiLocation = MultiLocation::parent(); + /// The asset ID for the asset that we use to pay for message delivery fees. Just WND. + pub FeeAssetId: AssetId = Concrete(RelayLocation::get()); + /// The base fee for the message delivery fees. + pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); + pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating(); + pub RelayTreasuryLocation: MultiLocation = + (Parent, PalletInstance(westend_runtime_constants::TREASURY_PALLET_ID)).into(); +} + +pub type PriceForParentDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + ParachainSystem, +>; + +pub type PriceForSiblingParachainDelivery = polkadot_runtime_common::xcm_sender::ExponentialPrice< + FeeAssetId, + BaseDeliveryFee, + TransactionByteFee, + XcmpQueue, +>; + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, + // Here/local root location to `AccountId`. + HashedDescription, +); + +/// Means for transacting the native currency on this chain. +#[allow(deprecated)] +pub type CurrencyTransactor = CurrencyAdapter< + // Use this currency: + Balances, + // Use this currency when it is a fungible asset matching the given location or name: + IsConcrete, + // Do a simple punn to convert an `AccountId32` `MultiLocation` into a native chain + // `AccountId`: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports of `Balances`. + (), +>; + +/// 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. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain that they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Superuser converter for the Relay-chain (Parent) location. This will allow it to issue a + // transaction from the Root origin. + ParentAsSuperuser, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `RuntimeOrigin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // XCM origins can be represented natively under the XCM pallet's `Xcm` origin. + XcmPassthrough, +); + +match_types! { + pub type LocalPlurality: impl Contains = { + MultiLocation { parents: 0, interior: X1(Plurality { .. }) } + }; + pub type ParentOrParentsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Plurality { .. }) } + }; + pub type ParentOrSiblings: impl Contains = { + MultiLocation { parents: 1, interior: Here } | + MultiLocation { parents: 1, interior: X1(Parachain(_)) } + }; + pub type FellowsPlurality: impl Contains = { + MultiLocation { parents: 1, interior: X2(Parachain(1001), Plurality { id: BodyId::Technical, ..}) } + }; +} + +/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly +/// account for proof size weights. +/// +/// Calls that are allowed through this filter must: +/// 1. Have a fixed weight; +/// 2. Cannot lead to another call being made; +/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters. +pub struct SafeCallFilter; +impl Contains for SafeCallFilter { + fn contains(call: &RuntimeCall) -> bool { + #[cfg(feature = "runtime-benchmarks")] + { + if matches!(call, RuntimeCall::System(frame_system::Call::remark_with_event { .. })) { + return true + } + } + + matches!( + call, + RuntimeCall::PolkadotXcm(pallet_xcm::Call::force_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::kill_prefix { .. }, + ) | RuntimeCall::ParachainSystem(..) | + RuntimeCall::Timestamp(..) | + RuntimeCall::Balances(..) | + RuntimeCall::CollatorSelection( + pallet_collator_selection::Call::set_desired_candidates { .. } | + pallet_collator_selection::Call::set_candidacy_bond { .. } | + pallet_collator_selection::Call::register_as_candidate { .. } | + pallet_collator_selection::Call::leave_intent { .. } | + pallet_collator_selection::Call::set_invulnerables { .. } | + pallet_collator_selection::Call::add_invulnerable { .. } | + pallet_collator_selection::Call::remove_invulnerable { .. }, + ) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | + RuntimeCall::XcmpQueue(..) | + RuntimeCall::MessageQueue(..) | + RuntimeCall::Identity(..) | + RuntimeCall::IdentityMigrator(..) + ) + } +} + +pub type Barrier = TrailingSetTopicAsId< + DenyThenTry< + DenyReserveTransferToRelayChain, + ( + // Allow local users to buy weight credit. + TakeWeightCredit, + // Expected responses are OK. + AllowKnownQueryResponses, + WithComputedOrigin< + ( + // If the message is one that immediately attemps to pay for execution, then + // allow it. + AllowTopLevelPaidExecutionFrom, + // Parent, its pluralities (i.e. governance bodies), and the Fellows plurality + // get free execution. + AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, + ), + UniversalLocation, + ConstU32<8>, + >, + ), + >, +>; + +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. We +/// only waive fees for system functions, which these locations represent. +pub type WaivedLocations = ( + RelayOrOtherSystemParachains, + Equals, + Equals, + LocalPlurality, +); + +pub struct XcmConfig; +impl xcm_executor::Config for XcmConfig { + type RuntimeCall = RuntimeCall; + type XcmSender = XcmRouter; + type AssetTransactor = CurrencyTransactor; + 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). + type IsReserve = (); + /// Only allow teleportation of WND amongst the system. + type IsTeleporter = ConcreteAssetFromSystem; + type UniversalLocation = UniversalLocation; + type Barrier = Barrier; + type Weigher = WeightInfoBounds< + crate::weights::xcm::PeopleWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type Trader = + UsingComponents>; + type ResponseHandler = PolkadotXcm; + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; + type SubscriptionService = PolkadotXcm; + type PalletInstancesInfo = AllPalletsWithSystem; + type MaxAssetsIntoHolding = MaxAssetsIntoHolding; + type AssetLocker = (); + type AssetExchanger = (); + type FeeManager = XcmFeeManagerFromComponents< + WaivedLocations, + XcmFeeToAccount, + >; + type MessageExporter = (); + type UniversalAliases = Nothing; + type CallDispatcher = WithOriginFilter; + type SafeCallFilter = SafeCallFilter; + type Aliasers = Nothing; +} + +/// Converts a local signed origin into an XCM multilocation. Forms the basis for local origins +/// sending/executing XCMs. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = WithUniqueTopic<( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +)>; + +impl pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We want to disallow users sending (arbitrary) XCMs from this chain. + type SendXcmOrigin = EnsureXcmOrigin; + type XcmRouter = XcmRouter; + // We support local origins dispatching XCM executions in principle... + type ExecuteXcmOrigin = EnsureXcmOrigin; + // ... but disallow generic XCM execution. As a result only teleports are allowed. + type XcmExecuteFilter = Nothing; + type XcmExecutor = XcmExecutor; + type XcmTeleportFilter = Everything; + type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. + type Weigher = WeightInfoBounds< + crate::weights::xcm::PeopleWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; + type UniversalLocation = UniversalLocation; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; + type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; + type Currency = Balances; + type CurrencyMatcher = (); + type TrustedLockers = (); + type SovereignAccountOf = LocationToAccountId; + type MaxLockers = ConstU32<8>; + type WeightInfo = crate::weights::pallet_xcm::WeightInfo; + type AdminOrigin = EnsureRoot; + type MaxRemoteLockConsumers = ConstU32<0>; + type RemoteLockConsumerIdentifier = (); +} + +impl cumulus_pallet_xcm::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type XcmExecutor = XcmExecutor; +} diff --git a/cumulus/parachains/runtimes/starters/seedling/Cargo.toml b/cumulus/parachains/runtimes/starters/seedling/Cargo.toml index 23312172bd7d49fd66f9a98435ede70cbb6ad706..37a3bb4ca26ff6362e064503de8c331b8e973629 100644 --- a/cumulus/parachains/runtimes/starters/seedling/Cargo.toml +++ b/cumulus/parachains/runtimes/starters/seedling/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/starters/shell/Cargo.toml b/cumulus/parachains/runtimes/starters/shell/Cargo.toml index a285d3d977e96deabd3682b82829b97da506cce2..3d7042ecd49fb0d3e046e136b70f069770848bbc 100644 --- a/cumulus/parachains/runtimes/starters/shell/Cargo.toml +++ b/cumulus/parachains/runtimes/starters/shell/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index b4453e7f1a6cef5c19d344e5d7df16a758ad8a70..cd100c472ce58d2dc5bfeb874ffeebef25c91753 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Utils for Runtimes testing" license = "Apache-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index e2a6fb45aec33acb6e2e5d729674b9939ce682aa..6d43875a8868502a4bfc7afc5d72972c0651653f 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -47,6 +47,7 @@ pub mod test_cases; pub type BalanceOf = ::Balance; pub type AccountIdOf = ::AccountId; +pub type RuntimeCallOf = ::RuntimeCall; pub type ValidatorIdOf = ::ValidatorId; pub type SessionKeysOf = ::Keys; @@ -114,35 +115,48 @@ impl BasicParachainRuntime for T +where + T: frame_system::Config + pallet_balances::Config + pallet_session::Config + pallet_xcm::Config - + parachain_info::Config, -> { + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config, + ValidatorIdOf: From>, +{ +} + +/// Basic builder based on balances, collators and pallet_session. +pub struct ExtBuilder { // endowed accounts with balances balances: Vec<(AccountIdOf, BalanceOf)>, // collators to test block prod collators: Vec>, // keys added to pallet session keys: Vec<(AccountIdOf, ValidatorIdOf, SessionKeysOf)>, - // safe xcm version for pallet_xcm + // safe XCM version for pallet_xcm safe_xcm_version: Option, // para id para_id: Option, _runtime: PhantomData, } -impl< - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config, - > Default for ExtBuilder -{ +impl Default for ExtBuilder { fn default() -> ExtBuilder { ExtBuilder { balances: vec![], @@ -155,14 +169,7 @@ impl< } } -impl< - Runtime: frame_system::Config - + pallet_balances::Config - + pallet_session::Config - + pallet_xcm::Config - + parachain_info::Config, - > ExtBuilder -{ +impl ExtBuilder { pub fn with_balances( mut self, balances: Vec<(AccountIdOf, BalanceOf)>, @@ -198,12 +205,7 @@ impl< self } - pub fn build(self) -> sp_io::TestExternalities - where - Runtime: - pallet_collator_selection::Config + pallet_balances::Config + pallet_session::Config, - ValidatorIdOf: From>, - { + pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_xcm::GenesisConfig:: { diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 6e044319d2e2b3c3fa916c75c6b4dd5ae457427d..a21023a933137448bcc84e6e6151f8a932c7f998 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true edition.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -75,10 +78,13 @@ cumulus-primitives-utility = { path = "../../../../primitives/utility", default- pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } parachain-info = { package = "staging-parachain-info", path = "../../../pallets/parachain-info", default-features = false } parachains-common = { path = "../../../common", default-features = false } +assets-common = { path = "../../assets/common", default-features = false } +snowbridge-rococo-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/rococo-common", default-features = false } [features] default = ["std"] std = [ + "assets-common/std", "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-dmp-queue/std", @@ -115,6 +121,7 @@ std = [ "polkadot-primitives/std", "polkadot-runtime-common/std", "scale-info/std", + "snowbridge-rococo-common/std", "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", @@ -135,6 +142,7 @@ std = [ ] runtime-benchmarks = [ + "assets-common/runtime-benchmarks", "cumulus-pallet-dmp-queue/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", @@ -158,6 +166,7 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", + "snowbridge-rococo-common/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 9387c454715f46d63a9ca71b730ecea22962cedc..541bcd05644f58474b2f8bd51f6fb7ac0444823f 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -32,6 +32,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod weights; pub mod xcm_config; +use assets_common::MultiLocationForAssetId; use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ @@ -458,6 +459,41 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = (); } +parameter_types! { + // we just reuse the same deposits + pub const ForeignAssetsAssetDeposit: Balance = AssetDeposit::get(); + pub const ForeignAssetsAssetAccountDeposit: Balance = AssetAccountDeposit::get(); + pub const ForeignAssetsApprovalDeposit: Balance = ApprovalDeposit::get(); + pub const ForeignAssetsAssetsStringLimit: u32 = AssetsStringLimit::get(); + pub const ForeignAssetsMetadataDepositBase: Balance = MetadataDepositBase::get(); + pub const ForeignAssetsMetadataDepositPerByte: Balance = MetadataDepositPerByte::get(); +} + +/// Another pallet assets instance to store foreign assets from bridgehub. +pub type ForeignAssetsInstance = pallet_assets::Instance2; +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = MultiLocationForAssetId; + type AssetIdParameter = MultiLocationForAssetId; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ForeignAssetsAssetDeposit; + type MetadataDepositBase = ForeignAssetsMetadataDepositBase; + type MetadataDepositPerByte = ForeignAssetsMetadataDepositPerByte; + type ApprovalDeposit = ForeignAssetsApprovalDeposit; + type StringLimit = ForeignAssetsAssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; + type CallbackHandle = (); + type AssetAccountDeposit = ForeignAssetsAssetAccountDeposit; + type RemoveItemsLimit = frame_support::traits::ConstU32<1000>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = xcm_config::XcmBenchmarkHelper; +} + parameter_types! { pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); @@ -626,6 +662,7 @@ construct_runtime!( // The main stage. Assets: pallet_assets::::{Pallet, Call, Storage, Event} = 50, + ForeignAssets: pallet_assets::::{Pallet, Call, Storage, Event} = 51, Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config} = 255, } diff --git a/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs b/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs index 30fa2c4060689ff98cc427c84f81866172845e52..b473d49e20e67329d893e1e565330cbe9290c64f 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/weights/mod.rs @@ -24,5 +24,4 @@ pub mod rocksdb_weights; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use paritydb_weights::constants::ParityDbWeight; pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 74d9a0b071d8551aadf20a3c2d9f8f58611950ef..ed405aeddb38a04377660c185cdcb445000fce90 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -24,8 +24,8 @@ //! soon. use super::{ AccountId, AllPalletsWithSystem, AssetId as AssetIdPalletAssets, Assets, Balance, Balances, - ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - WeightToFee, XcmpQueue, + ForeignAssets, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, WeightToFee, XcmpQueue, }; use core::marker::PhantomData; use frame_support::{ @@ -42,17 +42,19 @@ use pallet_assets::Instance1; use pallet_xcm::XcmPassthrough; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; +use snowbridge_rococo_common::EthereumNetwork; use sp_runtime::traits::Zero; use xcm::latest::prelude::*; +#[allow(deprecated)] use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, ConvertedConcreteId, CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, + SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::{traits::JustTry, XcmExecutor}; @@ -76,6 +78,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -94,12 +97,24 @@ pub type FungiblesTransactor = FungiblesAdapter< // Use this fungibles implementation: Assets, // Use this currency when it is a fungible asset matching the given location or name: - ConvertedConcreteId< - AssetIdPalletAssets, - Balance, - AsPrefixedGeneralIndex, - JustTry, - >, + ( + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex, + JustTry, + >, + ConvertedConcreteId< + AssetIdPalletAssets, + Balance, + AsPrefixedGeneralIndex< + SystemAssetHubAssetsPalletLocation, + AssetIdPalletAssets, + JustTry, + >, + JustTry, + >, + ), // Convert an XCM MultiLocation into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): @@ -111,8 +126,28 @@ pub type FungiblesTransactor = FungiblesAdapter< CheckingAccount, >; +/// `AssetId/Balance` converter for `TrustBackedAssets` +pub type ForeignAssetsConvertedConcreteId = + assets_common::ForeignAssetsConvertedConcreteId, Balance>; + +/// Means for transacting foreign assets from different global consensus. +pub type ForeignFungiblesTransactor = FungiblesAdapter< + // Use this fungibles implementation: + 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: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We dont need to check teleports here. + NoChecking, + // The account to use for tracking teleports. + CheckingAccount, +>; + /// Means for transacting assets on this chain. -pub type AssetTransactors = (CurrencyTransactor, FungiblesTransactor); +pub type AssetTransactors = (CurrencyTransactor, ForeignFungiblesTransactor, FungiblesTransactor); /// 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` which can @@ -188,16 +223,22 @@ pub type Barrier = TrailingSetTopicAsId< pub type AccountIdOf = ::AccountId; /// Asset filter that allows all assets from a certain location matching asset id. -pub struct AssetsFrom(PhantomData); -impl> ContainsPair for AssetsFrom { +pub struct AssetPrefixFrom(PhantomData<(Prefix, Origin)>); +impl ContainsPair for AssetPrefixFrom +where + Prefix: Get, + Origin: Get, +{ fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { - let loc = T::get(); + let loc = Origin::get(); &loc == origin && matches!(asset, MultiAsset { id: AssetId::Concrete(asset_loc), fun: Fungible(_a) } - if asset_loc.starts_with(&loc)) + if asset_loc.starts_with(&Prefix::get())) } } +type AssetsFrom = AssetPrefixFrom; + /// Asset filter that allows native/relay asset if coming from a certain location. pub struct NativeAssetFrom(PhantomData); impl> ContainsPair for NativeAssetFrom { @@ -237,6 +278,8 @@ where } } +// This asset can be added to AH as ForeignAsset and teleported between Penpal and AH +pub const TELEPORTABLE_ASSET_ID: u32 = 2; parameter_types! { /// The location that this chain recognizes as the Relay network's Asset Hub. pub SystemAssetHubLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(1000))); @@ -244,11 +287,35 @@ parameter_types! { // the Relay Chain's Asset Hub's Assets pallet index pub SystemAssetHubAssetsPalletLocation: MultiLocation = MultiLocation::new(1, X2(Parachain(1000), PalletInstance(50))); + pub AssetsPalletLocation: MultiLocation = + MultiLocation::new(0, X1(PalletInstance(50))); pub CheckingAccount: AccountId = PolkadotXcm::check_account(); + pub LocalTeleportableToAssetHub: MultiLocation = MultiLocation::new( + 0, + X2(PalletInstance(50), GeneralIndex(TELEPORTABLE_ASSET_ID.into())) + ); + pub EthereumLocation: MultiLocation = MultiLocation::new(2, X1(GlobalConsensus(EthereumNetwork::get()))); } -pub type Reserves = - (NativeAsset, AssetsFrom, NativeAssetFrom); +/// Accepts asset with ID `AssetLocation` and is coming from `Origin` chain. +pub struct AssetFromChain(PhantomData<(AssetLocation, Origin)>); +impl, Origin: Get> + ContainsPair for AssetFromChain +{ + fn contains(asset: &MultiAsset, origin: &MultiLocation) -> bool { + log::trace!(target: "xcm::contains", "AssetFromChain asset: {:?}, origin: {:?}", asset, origin); + *origin == Origin::get() && matches!(asset.id, Concrete(id) if id == AssetLocation::get()) + } +} + +pub type Reserves = ( + NativeAsset, + AssetsFrom, + NativeAssetFrom, + AssetPrefixFrom, +); +pub type TrustedTeleporters = + (AssetFromChain,); pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { @@ -259,7 +326,7 @@ impl xcm_executor::Config for XcmConfig { type OriginConverter = XcmOriginToTransactDispatchOrigin; type IsReserve = Reserves; // no teleport trust established with other chains - type IsTeleporter = NativeAsset; + type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = FixedWeightBounds; @@ -327,3 +394,12 @@ impl cumulus_pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmExecutor = XcmExecutor; } + +/// Simple conversion of `u32` into an `AssetId` for use in benchmarking. +pub struct XcmBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_assets::BenchmarkHelper for XcmBenchmarkHelper { + fn create_asset_id_parameter(id: u32) -> MultiLocation { + MultiLocation { parents: 1, interior: X1(Parachain(id)) } + } +} diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index 3903bbbe31ec88a98e27236dbe9ba87cfbbf7e46..a23b7558bcec00a1eda4c4435e0545e42844a389 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Simple runtime used by the rococo parachain(s)" license = "Apache-2.0" +[lints] +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"] } diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 9d2ba98a67c4ea52b6e4f7c587794aebdcf94024..7faee4258f65e1cc5ed607dd28102424459a89c2 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -83,12 +83,14 @@ 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, - CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, + EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + UsingComponents, }; use xcm_executor::XcmExecutor; @@ -106,7 +108,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_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -227,6 +229,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 = (); } @@ -345,6 +350,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. +#[allow(deprecated)] pub type CurrencyTransactor = CurrencyAdapter< // Use this currency: Balances, @@ -754,7 +760,7 @@ impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) } fn authorities() -> Vec { diff --git a/cumulus/parachains/testnets-common/Cargo.toml b/cumulus/parachains/testnets-common/Cargo.toml deleted file mode 100644 index 87c570d10ea349a983da776b7fb73acff2104fea..0000000000000000000000000000000000000000 --- a/cumulus/parachains/testnets-common/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "testnets-common" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -description = "Logic and configuration specific to testnet parachain runtimes" -license = "Apache-2.0" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -smallvec = "1.11.0" - -# Substrate -frame-support = { path = "../../../substrate/frame/support", default-features = false } -sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } - -# Polkadot -rococo-runtime-constants = { path = "../../../polkadot/runtime/rococo/constants", default-features = false } -westend-runtime-constants = { path = "../../../polkadot/runtime/westend/constants", default-features = false } -polkadot-core-primitives = { path = "../../../polkadot/core-primitives", default-features = false } - -# Cumulus - -[dev-dependencies] - -[build-dependencies] -substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } - -[features] -default = ["std"] -std = [ - "frame-support/std", - "polkadot-core-primitives/std", - "rococo-runtime-constants/std", - "sp-runtime/std", - "westend-runtime-constants/std", -] - -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] diff --git a/cumulus/parachains/testnets-common/src/lib.rs b/cumulus/parachains/testnets-common/src/lib.rs deleted file mode 100644 index 42d367bff27d64e05a9da4a2fa3eaf8e7f14cbb9..0000000000000000000000000000000000000000 --- a/cumulus/parachains/testnets-common/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-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)] - -/// Since the parachains-common package is now published to crates.io, SP runtimes for testnets -/// will be adapted to use this package, and their config removed from the published common -/// package. Only the configs specific to rococo, westend and wococo will be moved here, and the -/// truly common logic will still be sourced from the parachains-common package. -/// -/// In practice this just means that instead of using e.g. `[parachains_common::westend::*]`, now -/// the westend configs will be in `[testnets_common::westend::*]`. -/// -/// TODO: edit all runtimes to remove the testnet configs as part of PR #1737 -/// -pub mod rococo; -pub mod westend; -pub mod wococo; diff --git a/cumulus/parachains/testnets-common/src/rococo.rs b/cumulus/parachains/testnets-common/src/rococo.rs deleted file mode 100644 index 6e31def4b55b923f1596793e6cb114163551c017..0000000000000000000000000000000000000000 --- a/cumulus/parachains/testnets-common/src/rococo.rs +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod currency { - use polkadot_core_primitives::Balance; - use rococo_runtime_constants as constants; - - /// The existential deposit. Set to 1/10 of its parent Relay Chain (v9010). - pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; - - pub const UNITS: Balance = constants::currency::UNITS; - pub const CENTS: Balance = constants::currency::CENTS; - pub const MILLICENTS: Balance = constants::currency::MILLICENTS; - - pub const fn deposit(items: u32, bytes: u32) -> Balance { - // map to 1/100 of what the rococo relay chain charges - constants::currency::deposit(items, bytes) / 100 - } -} - -pub mod fee { - use frame_support::{ - pallet_prelude::Weight, - weights::{ - constants::ExtrinsicBaseWeight, FeePolynomial, WeightToFeeCoefficient, - WeightToFeeCoefficients, WeightToFeePolynomial, - }, - }; - use polkadot_core_primitives::Balance; - use smallvec::smallvec; - pub use sp_runtime::Perbill; - - /// The block saturation level. Fees will be updates based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the - /// node's balance type. - /// - /// This should typically create a mapping between the following ranges: - /// - `[0, MAXIMUM_BLOCK_WEIGHT]` - /// - `[Balance::min, Balance::max]` - /// - /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: - /// - Setting it to `0` will essentially disable the weight fee. - /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. - pub struct WeightToFee; - impl frame_support::weights::WeightToFee for WeightToFee { - type Balance = Balance; - - fn weight_to_fee(weight: &Weight) -> Self::Balance { - let time_poly: FeePolynomial = RefTimeToFee::polynomial().into(); - let proof_poly: FeePolynomial = ProofSizeToFee::polynomial().into(); - - // Take the maximum instead of the sum to charge by the more scarce resource. - time_poly.eval(weight.ref_time()).max(proof_poly.eval(weight.proof_size())) - } - } - - /// Maps the reference time component of `Weight` to a fee. - pub struct RefTimeToFee; - impl WeightToFeePolynomial for RefTimeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // In Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: - // The standard system parachain configuration is 1/10 of that, as in 1/100 CENT. - let p = super::currency::CENTS; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } - - /// Maps the proof size component of `Weight` to a fee. - pub struct ProofSizeToFee; - impl WeightToFeePolynomial for ProofSizeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // Map 10kb proof to 1 CENT. - let p = super::currency::CENTS; - let q = 10_000; - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } -} - -/// Consensus-related. -pub mod consensus { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included - /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; - /// 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; -} diff --git a/cumulus/parachains/testnets-common/src/westend.rs b/cumulus/parachains/testnets-common/src/westend.rs deleted file mode 100644 index 0ae21e234549cce2e73a097836df85740993911f..0000000000000000000000000000000000000000 --- a/cumulus/parachains/testnets-common/src/westend.rs +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-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. - -/// Universally recognized accounts. -pub mod account { - use frame_support::PalletId; - - /// Westend treasury pallet id, used to convert into AccountId - in Westend as a destination for - /// slashed funds. - pub const WESTEND_TREASURY_PALLET_ID: PalletId = PalletId(*b"py/trsry"); - /// Alliance pallet ID - used as a temporary place to deposit a slashed imbalance before the - /// teleport to the Treasury. - pub const ALLIANCE_PALLET_ID: PalletId = PalletId(*b"py/allia"); - /// Referenda pallet ID - used as a temporary place to deposit a slashed imbalance before the - /// teleport to the Treasury. - pub const REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/refer"); - /// Ambassador Referenda pallet ID - used as a temporary place to deposit a slashed imbalance - /// before the teleport to the Treasury. - pub const AMBASSADOR_REFERENDA_PALLET_ID: PalletId = PalletId(*b"py/amref"); -} - -pub mod currency { - use polkadot_core_primitives::Balance; - use westend_runtime_constants as constants; - - /// The existential deposit. Set to 1/10 of its parent Relay Chain. - pub const EXISTENTIAL_DEPOSIT: Balance = constants::currency::EXISTENTIAL_DEPOSIT / 10; - - pub const UNITS: Balance = constants::currency::UNITS; - pub const DOLLARS: Balance = UNITS; // 1_000_000_000_000 - pub const CENTS: Balance = constants::currency::CENTS; - pub const MILLICENTS: Balance = constants::currency::MILLICENTS; - pub const GRAND: Balance = constants::currency::GRAND; - - pub const fn deposit(items: u32, bytes: u32) -> Balance { - // 1/100 of Westend testnet - constants::currency::deposit(items, bytes) / 100 - } -} - -/// Fee-related. -pub mod fee { - use frame_support::{ - pallet_prelude::Weight, - weights::{ - constants::ExtrinsicBaseWeight, FeePolynomial, WeightToFeeCoefficient, - WeightToFeeCoefficients, WeightToFeePolynomial, - }, - }; - use polkadot_core_primitives::Balance; - use smallvec::smallvec; - pub use sp_runtime::Perbill; - - /// The block saturation level. Fees will be updated based on this value. - pub const TARGET_BLOCK_FULLNESS: Perbill = Perbill::from_percent(25); - - /// Handles converting a weight scalar to a fee value, based on the scale and granularity of the - /// node's balance type. - /// - /// This should typically create a mapping between the following ranges: - /// - [0, MAXIMUM_BLOCK_WEIGHT] - /// - [Balance::min, Balance::max] - /// - /// Yet, it can be used for any other sort of change to weight-fee. Some examples being: - /// - Setting it to `0` will essentially disable the weight fee. - /// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. - pub struct WeightToFee; - impl frame_support::weights::WeightToFee for WeightToFee { - type Balance = Balance; - - fn weight_to_fee(weight: &Weight) -> Self::Balance { - let time_poly: FeePolynomial = RefTimeToFee::polynomial().into(); - let proof_poly: FeePolynomial = ProofSizeToFee::polynomial().into(); - - // Take the maximum instead of the sum to charge by the more scarce resource. - time_poly.eval(weight.ref_time()).max(proof_poly.eval(weight.proof_size())) - } - } - - /// Maps the reference time component of `Weight` to a fee. - pub struct RefTimeToFee; - impl WeightToFeePolynomial for RefTimeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // In Westend, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT: - // The standard system parachain configuration is 1/10 of that, as in 1/100 CENT. - let p = super::currency::CENTS; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } - - /// Maps the proof size component of `Weight` to a fee. - pub struct ProofSizeToFee; - impl WeightToFeePolynomial for ProofSizeToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // Map 10kb proof to 1 CENT. - let p = super::currency::CENTS; - let q = 10_000; - - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } - } -} - -/// Consensus-related. -pub mod consensus { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the - /// relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; - /// 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; -} diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 67ebc244adf5625446deebd7cee77440d0fa69bc..a9b48a7984b67c3bc139aeb37f17306f1f8ffea5 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -1,25 +1,28 @@ [package] name = "polkadot-parachain-bin" -version = "1.1.0" +version = "1.5.0" authors.workspace = true build = "build.rs" edition.workspace = true description = "Runs a polkadot parachain node which could be a collator." license = "Apache-2.0" +[lints] +workspace = true + [[bin]] name = "polkadot-parachain" path = "src/main.rs" [dependencies] -async-trait = "0.1.73" -clap = { version = "4.4.10", features = ["derive"] } +async-trait = "0.1.74" +clap = { version = "4.4.13", 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.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" # Local rococo-parachain-runtime = { path = "../parachains/runtimes/testing/rococo-parachain" } @@ -31,8 +34,12 @@ asset-hub-westend-runtime = { path = "../parachains/runtimes/assets/asset-hub-we collectives-westend-runtime = { path = "../parachains/runtimes/collectives/collectives-westend" } contracts-rococo-runtime = { path = "../parachains/runtimes/contracts/contracts-rococo" } bridge-hub-rococo-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-rococo" } +coretime-rococo-runtime = { path = "../parachains/runtimes/coretime/coretime-rococo" } +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" } +people-rococo-runtime = { path = "../parachains/runtimes/people/people-rococo" } +people-westend-runtime = { path = "../parachains/runtimes/people/people-westend" } jsonrpsee = { version = "0.16.2", features = ["server"] } parachains-common = { path = "../parachains/common" } @@ -95,10 +102,10 @@ cumulus-client-consensus-aura = { path = "../client/consensus/aura" } cumulus-client-consensus-relay-chain = { path = "../client/consensus/relay-chain" } cumulus-client-consensus-common = { path = "../client/consensus/common" } cumulus-client-consensus-proposer = { path = "../client/consensus/proposer" } +cumulus-client-parachain-inherent = { path = "../client/parachain-inherent" } cumulus-client-service = { path = "../client/service" } cumulus-primitives-aura = { path = "../primitives/aura" } cumulus-primitives-core = { path = "../primitives/core" } -cumulus-primitives-parachain-inherent = { path = "../primitives/parachain-inherent" } cumulus-relay-chain-interface = { path = "../client/relay-chain-interface" } color-print = "0.3.4" @@ -121,6 +128,8 @@ runtime-benchmarks = [ "bridge-hub-westend-runtime/runtime-benchmarks", "collectives-westend-runtime/runtime-benchmarks", "contracts-rococo-runtime/runtime-benchmarks", + "coretime-rococo-runtime/runtime-benchmarks", + "coretime-westend-runtime/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking-cli/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", @@ -128,6 +137,8 @@ runtime-benchmarks = [ "glutton-westend-runtime/runtime-benchmarks", "parachains-common/runtime-benchmarks", "penpal-runtime/runtime-benchmarks", + "people-rococo-runtime/runtime-benchmarks", + "people-westend-runtime/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-service/runtime-benchmarks", @@ -142,11 +153,15 @@ try-runtime = [ "bridge-hub-westend-runtime/try-runtime", "collectives-westend-runtime/try-runtime", "contracts-rococo-runtime/try-runtime", + "coretime-rococo-runtime/try-runtime", + "coretime-westend-runtime/try-runtime", "frame-support/try-runtime", "frame-try-runtime/try-runtime", "glutton-westend-runtime/try-runtime", "pallet-transaction-payment/try-runtime", "penpal-runtime/try-runtime", + "people-rococo-runtime/try-runtime", + "people-westend-runtime/try-runtime", "polkadot-cli/try-runtime", "polkadot-service/try-runtime", "shell-runtime/try-runtime", diff --git a/cumulus/polkadot-parachain/chain-specs/people-rococo.json b/cumulus/polkadot-parachain/chain-specs/people-rococo.json new file mode 120000 index 0000000000000000000000000000000000000000..2e88dafed4847207b6e6c7245746ee06f3cc42f5 --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/people-rococo.json @@ -0,0 +1 @@ +../../parachains/chain-specs/people-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/people-westend.json b/cumulus/polkadot-parachain/chain-specs/people-westend.json new file mode 120000 index 0000000000000000000000000000000000000000..3eb6c240aea9d0c13bcb87d22b3d8c51e55e468a --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/people-westend.json @@ -0,0 +1 @@ +../../parachains/chain-specs/people-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 8dab692c1cd07cfe26e05a25a7048191305e71c6..1f43edf2243c0436bb9564c1724a1860ce404624 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -231,6 +231,10 @@ pub mod rococo { "bridgeWestendMessages": { "owner": bridges_pallet_owner.clone(), }, + "ethereumSystem": { + "paraId": id, + "assetHubParaId": 1000 + } }) } } diff --git a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..958336c03b5694876d48a75b2bd961200667e25f --- /dev/null +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -0,0 +1,282 @@ +// Copyright 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::chain_spec::GenericChainSpec; +use cumulus_primitives_core::ParaId; +use sc_chain_spec::{ChainSpec, ChainType}; +use std::{borrow::Cow, str::FromStr}; + +/// Collects all supported Coretime configurations. +#[derive(Debug, PartialEq, Clone)] +pub enum CoretimeRuntimeType { + // Live + Rococo, + // Local + RococoLocal, + // Benchmarks + RococoDevelopment, + + // Local + WestendLocal, + // Benchmarks + WestendDevelopment, +} + +impl FromStr for CoretimeRuntimeType { + type Err = String; + + fn from_str(value: &str) -> Result { + match value { + rococo::CORETIME_ROCOCO => Ok(CoretimeRuntimeType::Rococo), + rococo::CORETIME_ROCOCO_LOCAL => Ok(CoretimeRuntimeType::RococoLocal), + rococo::CORETIME_ROCOCO_DEVELOPMENT => Ok(CoretimeRuntimeType::RococoDevelopment), + westend::CORETIME_WESTEND_LOCAL => Ok(CoretimeRuntimeType::WestendLocal), + westend::CORETIME_WESTEND_DEVELOPMENT => Ok(CoretimeRuntimeType::WestendDevelopment), + _ => Err(format!("Value '{}' is not configured yet", value)), + } + } +} + +impl From for &str { + fn from(runtime_type: CoretimeRuntimeType) -> Self { + match runtime_type { + CoretimeRuntimeType::Rococo => rococo::CORETIME_ROCOCO, + CoretimeRuntimeType::RococoLocal => rococo::CORETIME_ROCOCO_LOCAL, + CoretimeRuntimeType::RococoDevelopment => rococo::CORETIME_ROCOCO_DEVELOPMENT, + CoretimeRuntimeType::WestendLocal => westend::CORETIME_WESTEND_LOCAL, + CoretimeRuntimeType::WestendDevelopment => westend::CORETIME_WESTEND_DEVELOPMENT, + } + } +} + +impl From for ChainType { + fn from(runtime_type: CoretimeRuntimeType) -> Self { + match runtime_type { + CoretimeRuntimeType::Rococo => ChainType::Live, + CoretimeRuntimeType::RococoLocal | CoretimeRuntimeType::WestendLocal => + ChainType::Local, + CoretimeRuntimeType::RococoDevelopment | CoretimeRuntimeType::WestendDevelopment => + ChainType::Development, + } + } +} + +pub const CORETIME_PARA_ID: ParaId = ParaId::new(1005); + +impl CoretimeRuntimeType { + pub const ID_PREFIX: &'static str = "coretime"; + + 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")[..], + )?)), + CoretimeRuntimeType::RococoLocal => + Ok(Box::new(rococo::local_config(self, "rococo-local"))), + CoretimeRuntimeType::RococoDevelopment => + Ok(Box::new(rococo::local_config(self, "rococo-dev"))), + CoretimeRuntimeType::WestendLocal => + Ok(Box::new(westend::local_config(self, "westend-local"))), + CoretimeRuntimeType::WestendDevelopment => + Ok(Box::new(westend::local_config(self, "westend-dev"))), + } + } +} + +/// Generate the name directly from the ChainType +pub fn chain_type_name(chain_type: &ChainType) -> Cow { + match chain_type { + ChainType::Development => "Development", + ChainType::Local => "Local", + ChainType::Live => "Live", + ChainType::Custom(name) => name, + } + .into() +} + +/// Sub-module for Rococo setup. +pub mod rococo { + use super::{chain_type_name, CoretimeRuntimeType, GenericChainSpec, ParaId}; + use crate::chain_spec::{ + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + }; + use parachains_common::{AccountId, AuraId, Balance}; + use sp_core::sr25519; + + pub(crate) const CORETIME_ROCOCO: &str = "coretime-rococo"; + pub(crate) const CORETIME_ROCOCO_LOCAL: &str = "coretime-rococo-local"; + pub(crate) const CORETIME_ROCOCO_DEVELOPMENT: &str = "coretime-rococo-dev"; + const CORETIME_ROCOCO_ED: Balance = parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT; + + pub fn local_config(runtime_type: &CoretimeRuntimeType, relay_chain: &str) -> GenericChainSpec { + // Rococo defaults + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "ROC".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + let chain_type = runtime_type.clone().into(); + let chain_name = format!("Coretime Rococo {}", chain_type_name(&chain_type)); + let para_id = super::CORETIME_PARA_ID; + + GenericChainSpec::builder( + coretime_rococo_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + .with_name(&chain_name) + .with_id(runtime_type.clone().into()) + .with_chain_type(chain_type) + .with_genesis_config_patch(genesis( + // initial collators. + vec![( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + )], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + para_id, + )) + .with_properties(properties) + .build() + } + + fn genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, CORETIME_ROCOCO_ED * 4096)).collect::>(), + }, + "parachainInfo": { + "parachainId": id, + }, + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": CORETIME_ROCOCO_ED * 16, + }, + "session": { + "keys": invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + coretime_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect::>(), + }, + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), + }, + "sudo": { + "key": Some(get_account_id_from_seed::("Alice")), + }, + }) + } +} + +/// Sub-module for Westend setup. +pub mod westend { + use super::{chain_type_name, CoretimeRuntimeType, GenericChainSpec, ParaId}; + use crate::chain_spec::{ + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, SAFE_XCM_VERSION, + }; + use parachains_common::{AccountId, AuraId, Balance}; + use sp_core::sr25519; + + 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 = parachains_common::westend::currency::EXISTENTIAL_DEPOSIT; + + pub fn local_config(runtime_type: &CoretimeRuntimeType, relay_chain: &str) -> GenericChainSpec { + // westend defaults + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "WND".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + let chain_type = runtime_type.clone().into(); + let chain_name = format!("Coretime Westend {}", chain_type_name(&chain_type)); + let para_id = super::CORETIME_PARA_ID; + + GenericChainSpec::builder( + coretime_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + .with_name(&chain_name) + .with_id(runtime_type.clone().into()) + .with_chain_type(chain_type) + .with_genesis_config_patch(genesis( + // initial collators. + vec![( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + )], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + para_id, + )) + .with_properties(properties) + .build() + } + + fn genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts.iter().cloned().map(|k| (k, CORETIME_WESTEND_ED * 4096)).collect::>(), + }, + "parachainInfo": { + "parachainId": id, + }, + "collatorSelection": { + "invulnerables": invulnerables.iter().cloned().map(|(acc, _)| acc).collect::>(), + "candidacyBond": CORETIME_WESTEND_ED * 16, + }, + "session": { + "keys": invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + coretime_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect::>(), + }, + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), + } + }) + } +} diff --git a/cumulus/polkadot-parachain/src/chain_spec/mod.rs b/cumulus/polkadot-parachain/src/chain_spec/mod.rs index e8ed8a74ed7990d3d562f4b586e6ec7351159f04..bbda334e4c66e0d9f8fcb7434bc4f218d81dc6e5 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/mod.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/mod.rs @@ -24,8 +24,10 @@ pub mod asset_hubs; pub mod bridge_hubs; pub mod collectives; pub mod contracts; +pub mod coretime; pub mod glutton; pub mod penpal; +pub mod people; pub mod rococo_parachain; pub mod seedling; pub mod shell; diff --git a/cumulus/polkadot-parachain/src/chain_spec/people.rs b/cumulus/polkadot-parachain/src/chain_spec/people.rs new file mode 100644 index 0000000000000000000000000000000000000000..aa78bfdb76f0f7067580ff3fc29acb3f75057280 --- /dev/null +++ b/cumulus/polkadot-parachain/src/chain_spec/people.rs @@ -0,0 +1,320 @@ +// Copyright 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::chain_spec::GenericChainSpec; +use cumulus_primitives_core::ParaId; +use parachains_common::Balance as PeopleBalance; +use sc_chain_spec::ChainSpec; +use std::str::FromStr; + +/// Collects all supported People configurations. +#[derive(Debug, PartialEq)] +pub enum PeopleRuntimeType { + Rococo, + RococoLocal, + RococoDevelopment, + Westend, + WestendLocal, + WestendDevelopment, +} + +impl FromStr for PeopleRuntimeType { + type Err = String; + + fn from_str(value: &str) -> Result { + match value { + rococo::PEOPLE_ROCOCO => Ok(PeopleRuntimeType::Rococo), + rococo::PEOPLE_ROCOCO_LOCAL => Ok(PeopleRuntimeType::RococoLocal), + rococo::PEOPLE_ROCOCO_DEVELOPMENT => Ok(PeopleRuntimeType::RococoDevelopment), + westend::PEOPLE_WESTEND => Ok(PeopleRuntimeType::Westend), + westend::PEOPLE_WESTEND_LOCAL => Ok(PeopleRuntimeType::WestendLocal), + westend::PEOPLE_WESTEND_DEVELOPMENT => Ok(PeopleRuntimeType::WestendDevelopment), + _ => Err(format!("Value '{}' is not configured yet", value)), + } + } +} + +impl PeopleRuntimeType { + pub const ID_PREFIX: &'static str = "people"; + + pub fn load_config(&self) -> Result, String> { + match self { + PeopleRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/people-rococo.json")[..], + )?)), + PeopleRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config( + rococo::PEOPLE_ROCOCO_LOCAL, + "Rococo People Local", + "rococo-local", + ParaId::new(1004), + ))), + PeopleRuntimeType::RococoDevelopment => Ok(Box::new(rococo::local_config( + rococo::PEOPLE_ROCOCO_DEVELOPMENT, + "Rococo People Development", + "rococo-development", + ParaId::new(1004), + ))), + PeopleRuntimeType::Westend => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../chain-specs/people-westend.json")[..], + )?)), + PeopleRuntimeType::WestendLocal => Ok(Box::new(westend::local_config( + westend::PEOPLE_WESTEND_LOCAL, + "Westend People Local", + "westend-local", + ParaId::new(1004), + ))), + PeopleRuntimeType::WestendDevelopment => Ok(Box::new(westend::local_config( + westend::PEOPLE_WESTEND_DEVELOPMENT, + "Westend People Development", + "westend-development", + ParaId::new(1004), + ))), + } + } +} + +/// Check if `id` satisfies People-like format. +fn ensure_id(id: &str) -> Result<&str, String> { + if id.starts_with(PeopleRuntimeType::ID_PREFIX) { + Ok(id) + } else { + Err(format!( + "Invalid 'id' attribute ({}), should start with prefix: {}", + id, + PeopleRuntimeType::ID_PREFIX + )) + } +} + +/// Sub-module for Rococo setup. +pub mod rococo { + use super::{ParaId, PeopleBalance}; + use crate::chain_spec::{ + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, + }; + use parachains_common::{rococo::currency::EXISTENTIAL_DEPOSIT, AccountId, AuraId}; + use sc_chain_spec::ChainType; + use sp_core::sr25519; + + pub(crate) const PEOPLE_ROCOCO: &str = "people-rococo"; + pub(crate) const PEOPLE_ROCOCO_LOCAL: &str = "people-rococo-local"; + pub(crate) const PEOPLE_ROCOCO_DEVELOPMENT: &str = "people-rococo-development"; + const PEOPLE_ROCOCO_ED: PeopleBalance = EXISTENTIAL_DEPOSIT; + + pub fn local_config( + id: &str, + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> GenericChainSpec { + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "ROC".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + GenericChainSpec::builder( + people_rococo_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + .with_name(chain_name) + .with_id(super::ensure_id(id).expect("invalid id")) + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + para_id, + )) + .with_properties(properties) + .build() + } + + fn genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts + .iter() + .cloned() + .map(|k| (k, PEOPLE_ROCOCO_ED * 524_288)) + .collect::>(), + }, + "parachainInfo": { + "parachainId": id, + }, + "collatorSelection": { + "invulnerables": invulnerables + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect::>(), + "candidacyBond": PEOPLE_ROCOCO_ED * 16, + }, + "session": { + "keys": invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + people_rococo_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect::>(), + }, + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), + } + }) + } +} + +/// Sub-module for Westend setup. +pub mod westend { + use super::{ParaId, PeopleBalance}; + use crate::chain_spec::{ + get_account_id_from_seed, get_collator_keys_from_seed, Extensions, GenericChainSpec, + SAFE_XCM_VERSION, + }; + use parachains_common::{westend::currency::EXISTENTIAL_DEPOSIT, AccountId, AuraId}; + use sc_chain_spec::ChainType; + use sp_core::sr25519; + + pub(crate) const PEOPLE_WESTEND: &str = "people-westend"; + pub(crate) const PEOPLE_WESTEND_LOCAL: &str = "people-westend-local"; + pub(crate) const PEOPLE_WESTEND_DEVELOPMENT: &str = "people-westend-development"; + const PEOPLE_WESTEND_ED: PeopleBalance = EXISTENTIAL_DEPOSIT; + + pub fn local_config( + id: &str, + chain_name: &str, + relay_chain: &str, + para_id: ParaId, + ) -> GenericChainSpec { + let mut properties = sc_chain_spec::Properties::new(); + properties.insert("ss58Format".into(), 42.into()); + properties.insert("tokenSymbol".into(), "WND".into()); + properties.insert("tokenDecimals".into(), 12.into()); + + GenericChainSpec::builder( + people_westend_runtime::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + Extensions { relay_chain: relay_chain.to_string(), para_id: para_id.into() }, + ) + .with_name(chain_name) + .with_id(super::ensure_id(id).expect("invalid id")) + .with_chain_type(ChainType::Local) + .with_genesis_config_patch(genesis( + // initial collators. + vec![ + ( + get_account_id_from_seed::("Alice"), + get_collator_keys_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_collator_keys_from_seed::("Bob"), + ), + ], + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + para_id, + )) + .with_properties(properties) + .build() + } + + fn genesis( + invulnerables: Vec<(AccountId, AuraId)>, + endowed_accounts: Vec, + id: ParaId, + ) -> serde_json::Value { + serde_json::json!({ + "balances": { + "balances": endowed_accounts + .iter() + .cloned() + .map(|k| (k, PEOPLE_WESTEND_ED * 524_288)) + .collect::>(), + }, + "parachainInfo": { + "parachainId": id, + }, + "collatorSelection": { + "invulnerables": invulnerables + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect::>(), + "candidacyBond": PEOPLE_WESTEND_ED * 16, + }, + "session": { + "keys": invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + people_westend_runtime::SessionKeys { aura }, // session keys + ) + }) + .collect::>(), + }, + "polkadotXcm": { + "safeXcmVersion": Some(SAFE_XCM_VERSION), + } + }) + } +} diff --git a/cumulus/polkadot-parachain/src/cli.rs b/cumulus/polkadot-parachain/src/cli.rs index 63e4baf27aeb24bbf0d0647c5b2fda5ff34f0fa8..fec6e144e40f16a0f82ad06a0cb4d870d27948cb 100644 --- a/cumulus/polkadot-parachain/src/cli.rs +++ b/cumulus/polkadot-parachain/src/cli.rs @@ -45,7 +45,8 @@ pub enum Subcommand { PurgeChain(cumulus_client_cli::PurgeChainCmd), /// Export the genesis state of the parachain. - ExportGenesisState(cumulus_client_cli::ExportGenesisStateCommand), + #[command(alias = "export-genesis-state")] + ExportGenesisHead(cumulus_client_cli::ExportGenesisHeadCommand), /// Export the genesis wasm of the parachain. ExportGenesisWasm(cumulus_client_cli::ExportGenesisWasmCommand), diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index 98dcc2fea4a692012dc8c5395c627d9ac817e178..516bdb7685246dc0f578e236435dd337ae26cc44 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -55,6 +55,8 @@ enum Runtime { Glutton, GluttonWestend, BridgeHub(chain_spec::bridge_hubs::BridgeHubRuntimeType), + Coretime(chain_spec::coretime::CoretimeRuntimeType), + People(chain_spec::people::PeopleRuntimeType), } trait RuntimeResolver { @@ -113,10 +115,16 @@ fn runtime(id: &str) -> Runtime { id.parse::() .expect("Invalid value"), ) + } else if id.starts_with(chain_spec::coretime::CoretimeRuntimeType::ID_PREFIX) { + Runtime::Coretime( + id.parse::().expect("Invalid value"), + ) } else if id.starts_with("glutton-westend") { Runtime::GluttonWestend } else if id.starts_with("glutton") { Runtime::Glutton + } else if id.starts_with(chain_spec::people::PeopleRuntimeType::ID_PREFIX) { + Runtime::People(id.parse::().expect("Invalid value")) } else { log::warn!("No specific runtime was recognized for ChainSpec's id: '{}', so Runtime::default() will be used", id); Runtime::default() @@ -211,6 +219,15 @@ fn load_spec(id: &str) -> std::result::Result, String> { .expect("invalid value") .load_config()?, + // -- Coretime + coretime_like_id + if coretime_like_id + .starts_with(chain_spec::coretime::CoretimeRuntimeType::ID_PREFIX) => + coretime_like_id + .parse::() + .expect("invalid value") + .load_config()?, + // -- Penpal "penpal-rococo" => Box::new(chain_spec::penpal::get_penpal_chain_spec( para_id.expect("Must specify parachain id"), @@ -233,6 +250,14 @@ fn load_spec(id: &str) -> std::result::Result, String> { para_id.expect("Must specify parachain id"), )), + // -- People + people_like_id + if people_like_id.starts_with(chain_spec::people::PeopleRuntimeType::ID_PREFIX) => + people_like_id + .parse::() + .expect("invalid value") + .load_config()?, + // -- Fallback (generic chainspec) "" => { log::warn!("No ChainSpec.id specified, so using default one, based on rococo-parachain runtime"); @@ -248,6 +273,7 @@ fn load_spec(id: &str) -> std::result::Result, String> { /// (H/T to Phala for the idea) /// E.g. "penpal-kusama-2004" yields ("penpal-kusama", Some(2004)) fn extract_parachain_id(id: &str) -> (&str, &str, Option) { + const ROCOCO_TEST_PARA_PREFIX: &str = "penpal-rococo-"; const KUSAMA_TEST_PARA_PREFIX: &str = "penpal-kusama-"; const POLKADOT_TEST_PARA_PREFIX: &str = "penpal-polkadot-"; @@ -259,7 +285,10 @@ fn extract_parachain_id(id: &str) -> (&str, &str, Option) { const GLUTTON_WESTEND_PARA_LOCAL_PREFIX: &str = "glutton-westend-local-"; const GLUTTON_WESTEND_PARA_GENESIS_PREFIX: &str = "glutton-westend-genesis-"; - let (norm_id, orig_id, para) = if let Some(suffix) = id.strip_prefix(KUSAMA_TEST_PARA_PREFIX) { + let (norm_id, orig_id, para) = if let Some(suffix) = id.strip_prefix(ROCOCO_TEST_PARA_PREFIX) { + let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); + (&id[..ROCOCO_TEST_PARA_PREFIX.len() - 1], id, Some(para_id)) + } else if let Some(suffix) = id.strip_prefix(KUSAMA_TEST_PARA_PREFIX) { let para_id: u32 = suffix.parse().expect("Invalid parachain-id suffix"); (&id[..KUSAMA_TEST_PARA_PREFIX.len() - 1], id, Some(para_id)) } else if let Some(suffix) = id.strip_prefix(POLKADOT_TEST_PARA_PREFIX) { @@ -378,7 +407,9 @@ macro_rules! construct_partials { Runtime::AssetHubWestend | Runtime::BridgeHub(_) | Runtime::CollectivesPolkadot | - Runtime::CollectivesWestend => { + Runtime::CollectivesWestend | + Runtime::Coretime(_) | + Runtime::People(_) => { let $partials = new_partial::( &$config, crate::service::aura_build_import_queue::<_, AuraId>, @@ -424,12 +455,14 @@ macro_rules! construct_async_run { { $( $code )* }.map(|v| (v, task_manager)) }) }, - Runtime::AssetHubWestend | - Runtime::AssetHubRococo | Runtime::AssetHubKusama | + Runtime::AssetHubRococo | + Runtime::AssetHubWestend | + Runtime::BridgeHub(_) | Runtime::CollectivesPolkadot | Runtime::CollectivesWestend | - Runtime::BridgeHub(_) => { + Runtime::Coretime(_) | + Runtime::People(_) => { runner.async_run(|$config| { let $components = new_partial::( &$config, @@ -481,6 +514,7 @@ macro_rules! construct_async_run { /// Parse command line arguments into service configuration. pub fn run() -> Result<()> { + use Runtime::*; let cli = Cli::from_args(); match &cli.subcommand { @@ -530,10 +564,10 @@ pub fn run() -> Result<()> { cmd.run(config, polkadot_config) }) }, - Some(Subcommand::ExportGenesisState(cmd)) => { + Some(Subcommand::ExportGenesisHead(cmd)) => { let runner = cli.create_runner(cmd)?; runner.sync_run(|config| { - construct_partials!(config, |partials| cmd.run(&*config.chain_spec, &*partials.client)) + construct_partials!(config, |partials| cmd.run(partials.client)) }) }, Some(Subcommand::ExportGenesisWasm(cmd)) => { @@ -596,31 +630,31 @@ pub fn run() -> Result<()> { // that both file paths exist, the node will exit, as the user must decide (by // deleting one path) the information that they want to use as their DB. let old_name = match config.chain_spec.id() { - "asset-hub-polkadot" => Some("statemint"), - "asset-hub-kusama" => Some("statemine"), - "asset-hub-westend" => Some("westmint"), - "asset-hub-rococo" => Some("rockmine"), - _ => None, + "asset-hub-polkadot" => Some("statemint"), + "asset-hub-kusama" => Some("statemine"), + "asset-hub-westend" => Some("westmint"), + "asset-hub-rococo" => Some("rockmine"), + _ => None, }; if let Some(old_name) = old_name { - let new_path = config.base_path.config_dir(config.chain_spec.id()); - let old_path = config.base_path.config_dir(old_name); + let new_path = config.base_path.config_dir(config.chain_spec.id()); + let old_path = config.base_path.config_dir(old_name); - if old_path.exists() && new_path.exists() { - return Err(format!( + if old_path.exists() && new_path.exists() { + return Err(format!( "Found legacy {} path {} and new asset-hub path {}. Delete one path such that only one exists.", old_name, old_path.display(), new_path.display() ).into()) - } + } - if old_path.exists() { - std::fs::rename(old_path.clone(), new_path.clone())?; + if old_path.exists() { + std::fs::rename(old_path.clone(), new_path.clone())?; info!( "Statemint renamed to Asset Hub. The filepath with associated data on disk has been renamed from {} to {}.", old_path.display(), new_path.display() ); - } + } } let hwbench = (!cli.no_hardware_benchmarks).then_some( @@ -653,43 +687,26 @@ pub fn run() -> Result<()> { info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); match config.chain_spec.runtime() { - Runtime::AssetHubPolkadot => crate::service::start_asset_hub_node::< + AssetHubPolkadot => crate::service::start_asset_hub_node::< AssetHubPolkadotRuntimeApi, AssetHubPolkadotAuraId, >(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0) .map_err(Into::into), - Runtime::AssetHubKusama => crate::service::start_asset_hub_node::< - RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::AssetHubRococo => crate::service::start_asset_hub_node::< - RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::AssetHubWestend => crate::service::start_asset_hub_node::< - RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::CollectivesPolkadot => - crate::service::start_generic_aura_node::< + + AssetHubKusama | + AssetHubRococo | + AssetHubWestend => + crate::service::start_asset_hub_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0) .map_err(Into::into), - Runtime::CollectivesWestend => + + CollectivesPolkadot | CollectivesWestend => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, @@ -697,7 +714,8 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), - Runtime::Shell => + + Seedling | Shell => crate::service::start_shell_node::( config, polkadot_config, @@ -708,18 +726,8 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), - Runtime::Seedling => - crate::service::start_shell_node::( - config, - polkadot_config, - collator_options, - id, - hwbench - ) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::ContractsRococo => crate::service::start_contracts_rococo_node( + + ContractsRococo => crate::service::start_contracts_rococo_node( config, polkadot_config, collator_options, @@ -729,7 +737,8 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), - Runtime::BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { + + BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot => crate::service::start_generic_aura_node::< RuntimeApi, @@ -764,7 +773,23 @@ pub fn run() -> Result<()> { .map(|r| r.0), } .map_err(Into::into), - Runtime::Penpal(_) | Runtime::Default => + + Coretime(coretime_runtime_type) => match coretime_runtime_type { + chain_spec::coretime::CoretimeRuntimeType::Rococo | + chain_spec::coretime::CoretimeRuntimeType::RococoLocal | + chain_spec::coretime::CoretimeRuntimeType::RococoDevelopment | + chain_spec::coretime::CoretimeRuntimeType::WestendLocal | + chain_spec::coretime::CoretimeRuntimeType::WestendDevelopment => + crate::service::start_generic_aura_node::< + RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0), + } + .map_err(Into::into), + + Penpal(_) | Default => crate::service::start_rococo_parachain_node( config, polkadot_config, @@ -775,15 +800,8 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), - Runtime::GluttonWestend => - crate::service::start_basic_lookahead_node::< - RuntimeApi, - AuraId, - >(config, polkadot_config, collator_options, id, hwbench) - .await - .map(|r| r.0) - .map_err(Into::into), - Runtime::Glutton => + + Glutton | GluttonWestend => crate::service::start_basic_lookahead_node::< RuntimeApi, AuraId, @@ -791,6 +809,22 @@ pub fn run() -> Result<()> { .await .map(|r| r.0) .map_err(Into::into), + + People(people_runtime_type) => match people_runtime_type { + chain_spec::people::PeopleRuntimeType::Rococo | + chain_spec::people::PeopleRuntimeType::RococoLocal | + chain_spec::people::PeopleRuntimeType::RococoDevelopment | + chain_spec::people::PeopleRuntimeType::Westend | + chain_spec::people::PeopleRuntimeType::WestendLocal | + chain_spec::people::PeopleRuntimeType::WestendDevelopment => + crate::service::start_generic_aura_node::< + RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0), + } + .map_err(Into::into), } }) }, diff --git a/cumulus/polkadot-parachain/src/rpc.rs b/cumulus/polkadot-parachain/src/rpc.rs index d106c52a364290dad54d3d278ce76dad8ef55c26..caee14e555220fc0e704b5edbe93a5ad76762eff 100644 --- a/cumulus/polkadot-parachain/src/rpc.rs +++ b/cumulus/polkadot-parachain/src/rpc.rs @@ -22,7 +22,7 @@ use std::sync::Arc; use parachains_common::{AccountId, Balance, Block, Nonce}; use sc_client_api::AuxStore; -pub use sc_rpc::{DenyUnsafe, SubscriptionTaskExecutor}; +pub use sc_rpc::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 6280d86e9f9fc2f5d2e5db416544b05214b51da2..81d0c9d3980e697492624b86166dc13755f6fe90 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -41,7 +41,7 @@ use sp_core::Pair; use jsonrpsee::RpcModule; use crate::{fake_runtime_api::aura::RuntimeApi, rpc}; -pub use parachains_common::{AccountId, Balance, Block, BlockNumber, Hash, Header, Nonce}; +pub use parachains_common::{AccountId, Balance, Block, Hash, Header, Nonce}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; use futures::{lock::Mutex, prelude::*}; @@ -98,7 +98,6 @@ impl sc_executor::NativeExecutionDispatch for ShellRuntimeExecutor { /// Native Asset Hub Westend (Westmint) executor instance. pub struct AssetHubWestendExecutor; - impl sc_executor::NativeExecutionDispatch for AssetHubWestendExecutor { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; @@ -128,7 +127,6 @@ impl sc_executor::NativeExecutionDispatch for CollectivesWestendRuntimeExecutor /// Native BridgeHubRococo executor instance. pub struct BridgeHubRococoRuntimeExecutor; - impl sc_executor::NativeExecutionDispatch for BridgeHubRococoRuntimeExecutor { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; @@ -141,9 +139,32 @@ impl sc_executor::NativeExecutionDispatch for BridgeHubRococoRuntimeExecutor { } } +/// Native `CoretimeRococo` executor instance. +pub struct CoretimeRococoRuntimeExecutor; +impl sc_executor::NativeExecutionDispatch for CoretimeRococoRuntimeExecutor { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + fn dispatch(method: &str, data: &[u8]) -> Option> { + coretime_rococo_runtime::api::dispatch(method, data) + } + fn native_version() -> sc_executor::NativeVersion { + coretime_rococo_runtime::native_version() + } +} + +/// Native `CoretimeWestend` executor instance. +pub struct CoretimeWestendRuntimeExecutor; +impl sc_executor::NativeExecutionDispatch for CoretimeWestendRuntimeExecutor { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + fn dispatch(method: &str, data: &[u8]) -> Option> { + coretime_westend_runtime::api::dispatch(method, data) + } + fn native_version() -> sc_executor::NativeVersion { + coretime_westend_runtime::native_version() + } +} + /// Native contracts executor instance. pub struct ContractsRococoRuntimeExecutor; - impl sc_executor::NativeExecutionDispatch for ContractsRococoRuntimeExecutor { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; @@ -171,6 +192,40 @@ impl sc_executor::NativeExecutionDispatch for GluttonWestendRuntimeExecutor { } } +/// Native `PeopleWestend` executor instance. +pub struct PeopleWestendRuntimeExecutor; +impl sc_executor::NativeExecutionDispatch for PeopleWestendRuntimeExecutor { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + fn dispatch(method: &str, data: &[u8]) -> Option> { + people_westend_runtime::api::dispatch(method, data) + } + fn native_version() -> sc_executor::NativeVersion { + people_westend_runtime::native_version() + } +} + +/// Native `PeopleRococo` executor instance. +pub struct PeopleRococoRuntimeExecutor; +impl sc_executor::NativeExecutionDispatch for PeopleRococoRuntimeExecutor { + type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + fn dispatch(method: &str, data: &[u8]) -> Option> { + people_rococo_runtime::api::dispatch(method, data) + } + fn native_version() -> sc_executor::NativeVersion { + people_rococo_runtime::native_version() + } +} + +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type Service = PartialComponents< + ParachainClient, + ParachainBackend, + (), + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool>, + (ParachainBlockImport, Option, Option), +>; + /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to @@ -178,17 +233,7 @@ impl sc_executor::NativeExecutionDispatch for GluttonWestendRuntimeExecutor { pub fn new_partial( config: &Configuration, build_import_queue: BIQ, -) -> Result< - PartialComponents< - ParachainClient, - ParachainBackend, - (), - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool>, - (ParachainBlockImport, Option, Option), - >, - sc_service::Error, -> +) -> Result, sc_service::Error> where RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue @@ -1040,7 +1085,7 @@ where let relay_chain_interface = relay_chain_interface.clone(); async move { let parachain_inherent = - cumulus_primitives_parachain_inherent::ParachainInherentData::create_at( + cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( relay_parent, &relay_chain_interface, &validation_data, diff --git a/cumulus/primitives/aura/Cargo.toml b/cumulus/primitives/aura/Cargo.toml index 096ae0a9620d97d136e63359f54d0db17815fcee..6d917eea270ec2fac273e349ad4fa9521ecfe0fc 100644 --- a/cumulus/primitives/aura/Cargo.toml +++ b/cumulus/primitives/aura/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Core primitives for Aura in Cumulus" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/cumulus/primitives/core/Cargo.toml b/cumulus/primitives/core/Cargo.toml index 5f68a3546e6d5634522ee2b58ad3019257609a9f..98c3e8ab5672e87f9d63407c058290739e065472 100644 --- a/cumulus/primitives/core/Cargo.toml +++ b/cumulus/primitives/core/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Cumulus related core primitive types and traits" +[lints] +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"] } diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index 5d3c72a59f914f4844e9332fd0d4a61690dfb4f7..42a425ea176bcbeb58906daa6ca080712b8412ad 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -6,27 +6,24 @@ edition.workspace = true description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." license = "Apache-2.0" +[lints] +workspace = true + [dependencies] -async-trait = { version = "0.1.73", optional = true } +async-trait = { version = "0.1.74", optional = true } 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"] } -tracing = { version = "0.1.37", optional = true } # Substrate -sc-client-api = { path = "../../../substrate/client/api", optional = true } -sp-api = { path = "../../../substrate/primitives/api", optional = true } sp-core = { path = "../../../substrate/primitives/core", default-features = false } sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false } sp-runtime = { path = "../../../substrate/primitives/runtime", optional = true } sp-state-machine = { path = "../../../substrate/primitives/state-machine", optional = true } sp-std = { path = "../../../substrate/primitives/std", default-features = false } -sp-storage = { path = "../../../substrate/primitives/storage", optional = true } sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } # Cumulus cumulus-primitives-core = { path = "../core", default-features = false } -cumulus-relay-chain-interface = { path = "../../client/relay-chain-interface", optional = true } -cumulus-test-relay-sproof-builder = { path = "../../test/relay-sproof-builder", optional = true } [features] default = ["std"] @@ -34,17 +31,9 @@ std = [ "async-trait", "codec/std", "cumulus-primitives-core/std", - "cumulus-relay-chain-interface", - "cumulus-test-relay-sproof-builder", - "sc-client-api", "scale-info/std", - "sp-api", "sp-core/std", "sp-inherents/std", - "sp-runtime", - "sp-state-machine", "sp-std/std", - "sp-storage", "sp-trie/std", - "tracing", ] diff --git a/cumulus/primitives/parachain-inherent/src/lib.rs b/cumulus/primitives/parachain-inherent/src/lib.rs index 08407023bb4604933ff4de97a6669e45b85d6bc5..75a56693958e6a982e21851384535217157b9c82 100644 --- a/cumulus/primitives/parachain-inherent/src/lib.rs +++ b/cumulus/primitives/parachain-inherent/src/lib.rs @@ -19,11 +19,11 @@ //! The [`ParachainInherentData`] is the data that is passed by the collator to the parachain //! runtime. The runtime will use this data to execute messages from other parachains/the relay //! chain or to read data from the relay chain state. When the parachain is validated by a parachain -//! validator on the relay chain, this data is checked for correctnes. If the data passed by the +//! validator on the relay chain, this data is checked for correctness. If the data passed by the //! collator to the runtime isn't correct, the parachain candidate is considered invalid. //! -//! Use [`ParachainInherentData::create_at`] to create the [`ParachainInherentData`] at a given -//! relay chain block to include it in a parachain block. +//! To create a [`ParachainInherentData`] for a specific relay chain block, there exists the +//! `ParachainInherentDataExt` trait in `cumulus-client-parachain-inherent` that helps with this. #![cfg_attr(not(feature = "std"), no_std)] @@ -36,15 +36,6 @@ use scale_info::TypeInfo; use sp_inherents::InherentIdentifier; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; -#[cfg(feature = "std")] -mod client_side; -#[cfg(feature = "std")] -pub use client_side::*; -#[cfg(feature = "std")] -mod mock; -#[cfg(feature = "std")] -pub use mock::{MockValidationDataInherentDataProvider, MockXcmConfig}; - /// The identifier for the parachain inherent. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"sysi1337"; @@ -70,6 +61,25 @@ pub struct ParachainInherentData { pub horizontal_messages: BTreeMap>, } +#[cfg(feature = "std")] +#[async_trait::async_trait] +impl sp_inherents::InherentDataProvider for ParachainInherentData { + async fn provide_inherent_data( + &self, + inherent_data: &mut sp_inherents::InherentData, + ) -> Result<(), sp_inherents::Error> { + inherent_data.put_data(INHERENT_IDENTIFIER, &self) + } + + async fn try_handle_error( + &self, + _: &sp_inherents::InherentIdentifier, + _: &[u8], + ) -> Option> { + None + } +} + /// This struct provides ability to extend a message queue chain (MQC) and compute a new head. /// /// MQC is an instance of a [hash chain] applied to a message queue. Using a hash chain it's @@ -86,6 +96,11 @@ pub struct ParachainInherentData { pub struct MessageQueueChain(RelayHash); impl MessageQueueChain { + /// Create a new instance initialized to `hash`. + pub fn new(hash: RelayHash) -> Self { + Self(hash) + } + /// Extend the hash chain with an HRMP message. This method should be used only when /// this chain is tracking HRMP. pub fn extend_hrmp(&mut self, horizontal_message: &InboundHrmpMessage) -> &mut Self { diff --git a/cumulus/primitives/proof-size-hostfunction/Cargo.toml b/cumulus/primitives/proof-size-hostfunction/Cargo.toml index 576f7f5ae99a224c4ab7065c2cedfe8ba1c057d6..06797f86863265797f59d3f44504168f1549ecb5 100644 --- a/cumulus/primitives/proof-size-hostfunction/Cargo.toml +++ b/cumulus/primitives/proof-size-hostfunction/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Hostfunction exposing storage proof size to the runtime." license = "Apache-2.0" +[lints] +workspace = true + [dependencies] sp-runtime-interface = { path = "../../../substrate/primitives/runtime-interface", default-features = false } sp-externalities = { path = "../../../substrate/primitives/externalities", default-features = false } diff --git a/cumulus/primitives/timestamp/Cargo.toml b/cumulus/primitives/timestamp/Cargo.toml index ec5cb57419a95128c4850dec379e73c9790938b7..b07a907154dfab36a63f900852fc4d044ccee341 100644 --- a/cumulus/primitives/timestamp/Cargo.toml +++ b/cumulus/primitives/timestamp/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true description = "Provides timestamp related functionality for parachains." license = "Apache-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures = "0.3.28" diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 5f756c1e36127fa76fdab54bdc67318b4ec034b8..56b6b9284176ef13624ef3d45b2483a4861b246d 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Helper datatypes for Cumulus" +[lints] +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 } diff --git a/cumulus/scripts/bridges_common.sh b/cumulus/scripts/bridges_common.sh index 8d64c5ede52a25eaa4f0332addf1b39969ac478d..97ef8aa1259535dde929108fb7d6797b238148b9 100755 --- a/cumulus/scripts/bridges_common.sh +++ b/cumulus/scripts/bridges_common.sh @@ -187,23 +187,25 @@ function open_hrmp_channels() { ${max_message_size} } -function set_storage() { +function force_xcm_version() { local relay_url=$1 local relay_chain_seed=$2 local runtime_para_id=$3 local runtime_para_endpoint=$4 - local items=$5 - echo " calling set_storage:" + local dest=$5 + local xcm_version=$6 + echo " calling force_xcm_version:" echo " relay_url: ${relay_url}" echo " relay_chain_seed: ${relay_chain_seed}" echo " runtime_para_id: ${runtime_para_id}" echo " runtime_para_endpoint: ${runtime_para_endpoint}" - echo " items: ${items}" + echo " dest: ${dest}" + echo " xcm_version: ${xcm_version}" echo " params:" - # 1. generate data for Transact (System::set_storage) + # 1. generate data for Transact (PolkadotXcm::force_xcm_version) local tmp_output_file=$(mktemp) - generate_hex_encoded_call_data "set-storage" "${runtime_para_endpoint}" "${tmp_output_file}" "$items" + generate_hex_encoded_call_data "force-xcm-version" "${runtime_para_endpoint}" "${tmp_output_file}" "$dest" "$xcm_version" local hex_encoded_data=$(cat $tmp_output_file) # 2. trigger governance call diff --git a/cumulus/scripts/bridges_rococo_westend.sh b/cumulus/scripts/bridges_rococo_westend.sh index 9b3bd350276ff5fbaf388cc137bd6debb0a074b3..c52b72e51fc518a730e3c919cc7f2b73cf99a705 100755 --- a/cumulus/scripts/bridges_rococo_westend.sh +++ b/cumulus/scripts/bridges_rococo_westend.sh @@ -129,6 +129,7 @@ ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_ThisChain="5EHnXa ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain="5EHnXaT5BhiSGP5h9RgQci1txJ2BDbp7KBRE9k8xty3BMUSi" LANE_ID="00000002" +XCM_VERSION=3 function init_ro_wnd() { ensure_relayer @@ -215,6 +216,14 @@ case "$1" in "ws://127.0.0.1:9942" \ "//Alice" \ 1013 1000 4 524288 + # set XCM version of remote AssetHubWestend + force_xcm_version \ + "ws://127.0.0.1:9942" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9910" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } }')" \ + $XCM_VERSION ;; init-bridge-hub-rococo-local) ensure_polkadot_js_api @@ -236,6 +245,14 @@ case "$1" in "//Alice" \ "$ON_BRIDGE_HUB_ROCOCO_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhwd_BridgedChain" \ $((1000000000000 + 2000000000000)) + # set XCM version of remote BridgeHubWestend + force_xcm_version \ + "ws://127.0.0.1:9942" \ + "//Alice" \ + 1013 \ + "ws://127.0.0.1:8943" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1002 } ] } }')" \ + $XCM_VERSION ;; init-asset-hub-westend-local) ensure_polkadot_js_api @@ -264,6 +281,14 @@ case "$1" in "ws://127.0.0.1:9945" \ "//Alice" \ 1002 1000 4 524288 + # set XCM version of remote AssetHubRococo + force_xcm_version \ + "ws://127.0.0.1:9945" \ + "//Alice" \ + 1000 \ + "ws://127.0.0.1:9010" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } }')" \ + $XCM_VERSION ;; init-bridge-hub-westend-local) # SA of sibling asset hub pays for the execution @@ -284,6 +309,14 @@ case "$1" in "//Alice" \ "$ON_BRIDGE_HUB_WESTEND_SOVEREIGN_ACCOUNT_FOR_LANE_00000002_bhro_BridgedChain" \ $((1000000000000000 + 2000000000000)) + # set XCM version of remote BridgeHubRococo + force_xcm_version \ + "ws://127.0.0.1:9945" \ + "//Alice" \ + 1002 \ + "ws://127.0.0.1:8945" \ + "$(jq --null-input '{ "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1013 } ] } }')" \ + $XCM_VERSION ;; reserve-transfer-assets-from-asset-hub-rococo-local) ensure_polkadot_js_api @@ -293,7 +326,7 @@ case "$1" in "//Alice" \ "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \ "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ - "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 200000000000 } } ] }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 5000000000000 } } ] }')" \ 0 \ "Unlimited" ;; @@ -305,7 +338,7 @@ case "$1" in "//Alice" \ "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Westend" }, { "Parachain": 1000 } ] } } }')" \ "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ - "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } } }, "fun": { "Fungible": 40000000000 } } ] }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Westend" } } } }, "fun": { "Fungible": 3000000000000 } } ] }')" \ 0 \ "Unlimited" ;; @@ -317,7 +350,7 @@ case "$1" in "//Alice" \ "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \ "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ - "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 150000000000 } } ] }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 1, "interior": "Here" } }, "fun": { "Fungible": 5000000000000 } } ] }')" \ 0 \ "Unlimited" ;; @@ -329,7 +362,7 @@ case "$1" in "//Alice" \ "$(jq --null-input '{ "V3": { "parents": 2, "interior": { "X2": [ { "GlobalConsensus": "Rococo" }, { "Parachain": 1000 } ] } } }')" \ "$(jq --null-input '{ "V3": { "parents": 0, "interior": { "X1": { "AccountId32": { "id": [212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125] } } } } }')" \ - "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } } }, "fun": { "Fungible": 100000000000 } } ] }')" \ + "$(jq --null-input '{ "V3": [ { "id": { "Concrete": { "parents": 2, "interior": { "X1": { "GlobalConsensus": "Rococo" } } } }, "fun": { "Fungible": 3000000000000 } } ] }')" \ 0 \ "Unlimited" ;; diff --git a/cumulus/scripts/create_coretime_rococo_spec.sh b/cumulus/scripts/create_coretime_rococo_spec.sh new file mode 100755 index 0000000000000000000000000000000000000000..877e8ee36c7d9b8a3c2b380e2796571018ef2179 --- /dev/null +++ b/cumulus/scripts/create_coretime_rococo_spec.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +usage() { + echo Usage: + echo "$1 " + echo "$2 " + echo "e.g.: ./cumulus/scripts/create_coretime_rococo_spec.sh ./target/release/wbuild/coretime-rococo-runtime/coretime_rococo_runtime.compact.compressed.wasm 1005" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + +if [ -z "$2" ]; then + usage +fi + +set -e + +rt_path=$1 +para_id=$2 + +echo "Generating chain spec for runtime: $rt_path and para_id: $para_id" + +binary="./target/release/polkadot-parachain" + +# build the chain spec we'll manipulate +$binary build-spec --chain coretime-rococo-dev > chain-spec-plain.json + +# convert runtime to hex +cat $rt_path | od -A n -v -t x1 | tr -d ' \n' > rt-hex.txt + +# replace the runtime in the spec with the given runtime and set some values to production +# Related issue for bootNodes, invulnerables, and session keys: https://github.com/paritytech/devops/issues/2725 +cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesis.code = ("0x" + $code)' \ + | jq '.name = "Rococo Coretime"' \ + | jq '.id = "coretime-rococo"' \ + | jq '.chainType = "Live"' \ + | jq '.bootNodes = [ + "/dns/rococo-coretime-collator-node-0.polkadot.io/tcp/30333/p2p/12D3KooWHBUH9wGBx1Yq1ZePov9VL3AzxRPv5DTR4KadiCU6VKxy", + "/dns/rococo-coretime-collator-node-1.polkadot.io/tcp/30333/p2p/12D3KooWB3SKxdj6kpwTkdMnHJi6YmadojCzmEqFkeFJjxN812XX" + ]' \ + | jq '.relay_chain = "rococo"' \ + | jq --argjson para_id $para_id '.para_id = $para_id' \ + | jq --argjson para_id $para_id '.genesis.runtimeGenesis.patch.parachainInfo.parachainId = $para_id' \ + | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ + | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ + "5G6Zua7Sowmt6ziddwUyueQs7HXDUVvDLaqqJDXXFyKvQ6Y6", + "5C8aSedh7ShpWEPW8aTNEErbKkMbiibdwP8cRzVRNqLmzAWF" + ]' \ + | jq '.genesis.runtimeGenesis.patch.session.keys = [ + [ + "5G6Zua7Sowmt6ziddwUyueQs7HXDUVvDLaqqJDXXFyKvQ6Y6", + "5G6Zua7Sowmt6ziddwUyueQs7HXDUVvDLaqqJDXXFyKvQ6Y6", + { + "aura": "5G6Zua7Sowmt6ziddwUyueQs7HXDUVvDLaqqJDXXFyKvQ6Y6" + } + ], + [ + "5C8aSedh7ShpWEPW8aTNEErbKkMbiibdwP8cRzVRNqLmzAWF", + "5C8aSedh7ShpWEPW8aTNEErbKkMbiibdwP8cRzVRNqLmzAWF", + { + "aura": "5C8aSedh7ShpWEPW8aTNEErbKkMbiibdwP8cRzVRNqLmzAWF" + } + ] + ]' \ + > edited-chain-spec-plain.json + +# build a raw spec +$binary build-spec --chain edited-chain-spec-plain.json --raw > chain-spec-raw.json +cp edited-chain-spec-plain.json coretime-rococo-spec.json +cp chain-spec-raw.json ./cumulus/parachains/chain-specs/coretime-rococo.json +cp chain-spec-raw.json coretime-rococo-spec-raw.json + +# build genesis data +$binary export-genesis-state --chain chain-spec-raw.json > coretime-rococo-genesis-head-data + +# build genesis wasm +$binary export-genesis-wasm --chain chain-spec-raw.json > coretime-rococo-wasm + +# cleanup +rm -f rt-hex.txt +rm -f chain-spec-plain.json +rm -f chain-spec-raw.json +rm -f edited-chain-spec-plain.json diff --git a/cumulus/scripts/create_coretime_westend_spec.sh b/cumulus/scripts/create_coretime_westend_spec.sh new file mode 100755 index 0000000000000000000000000000000000000000..90996f4a74f47f9783a29ff2ce358920be810641 --- /dev/null +++ b/cumulus/scripts/create_coretime_westend_spec.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +usage() { + echo Usage: + echo "$1 " + echo "$2 " + echo "e.g.: ./cumulus/scripts/create_coretime_westend_spec.sh ./target/release/wbuild/coretime-westend-runtime/coretime_westend_runtime.compact.compressed.wasm 1005" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + +if [ -z "$2" ]; then + usage +fi + +set -e + +rt_path=$1 +para_id=$2 + +echo "Generating chain spec for runtime: $rt_path and para_id: $para_id" + +binary="./target/release/polkadot-parachain" + +# build the chain spec we'll manipulate +$binary build-spec --chain coretime-westend-dev > chain-spec-plain.json + +# convert runtime to hex +cat $rt_path | od -A n -v -t x1 | tr -d ' \n' > rt-hex.txt + +# replace the runtime in the spec with the given runtime and set some values to production +# Related issue for bootNodes, invulnerables, and session keys: https://github.com/paritytech/devops/issues/2725 +cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesis.code = ("0x" + $code)' \ + | jq '.name = "Westend Coretime"' \ + | jq '.id = "coretime-westend"' \ + | 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", + ]' \ + | jq '.relay_chain = "westend"' \ + | jq --argjson para_id $para_id '.para_id = $para_id' \ + | jq --argjson para_id $para_id '.genesis.runtimeGenesis.patch.parachainInfo.parachainId = $para_id' \ + | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ + | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ + "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", + "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", + "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA" + ]' \ + | jq '.genesis.runtimeGenesis.patch.session.keys = [ + [ + "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", + "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", + { + "aura": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c" + } + ], + [ + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", + { + "aura": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28" + } + ], + [ + "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", + "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", + { + "aura": "0x2c7b95155708c10616b6f1a77a84f3d92c9a0272609ed24dbb7e6bdb81b53e76" + } + ], + [ + "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", + "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", + { + "aura": "0x741cfb39ec61bc76824ccec62d61670a80a890e0e21d58817f84040d3ec54474" + } + ] + ]' \ + > edited-chain-spec-plain.json + +# build a raw spec +$binary build-spec --chain edited-chain-spec-plain.json --raw > chain-spec-raw.json +cp edited-chain-spec-plain.json coretime-westend-spec.json +cp chain-spec-raw.json ./cumulus/parachains/chain-specs/coretime-westend.json +cp chain-spec-raw.json coretime-westend-spec-raw.json + +# build genesis data +$binary export-genesis-state --chain chain-spec-raw.json > coretime-westend-genesis-head-data + +# build genesis wasm +$binary export-genesis-wasm --chain chain-spec-raw.json > coretime-westend-wasm + +# cleanup +rm -f rt-hex.txt +rm -f chain-spec-plain.json +rm -f chain-spec-raw.json +rm -f edited-chain-spec-plain.json diff --git a/cumulus/scripts/create_people_rococo_spec.sh b/cumulus/scripts/create_people_rococo_spec.sh new file mode 100755 index 0000000000000000000000000000000000000000..264408b20bce30157b8f80754abb1592a28ba697 --- /dev/null +++ b/cumulus/scripts/create_people_rococo_spec.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +usage() { + echo Usage: + echo "$1 " + echo "$2 " + echo "e.g.: ./cumulus/scripts/create_people_rococo_spec.sh ./target/release/wbuild/people-rococo-runtime/people_rococo_runtime.compact.compressed.wasm 1004" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + +if [ -z "$2" ]; then + usage +fi + +set -e + +rt_path=$1 +para_id=$2 + +echo "Generating chain spec for runtime: $rt_path and para_id: $para_id" + +binary="./target/release/polkadot-parachain" + +# build the chain spec we'll manipulate +$binary build-spec --chain people-rococo-local > chain-spec-plain.json + +# convert runtime to hex +cat $rt_path | od -A n -v -t x1 | tr -d ' \n' > rt-hex.txt + +# replace the runtime in the spec with the given runtime and set some values to production +# Boot nodes, invulnerables, and session keys from https://github.com/paritytech/devops/issues/2847 +# +# Note: This is a testnet runtime. Each invulnerable's Aura key is also used as its AccountId. This +# is not recommended in value-bearing networks. +cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesis.code = ("0x" + $code)' \ + | jq '.name = "Rococo People"' \ + | jq '.id = "people-rococo"' \ + | jq '.chainType = "Live"' \ + | jq '.bootNodes = [ + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDZg5jMYhKXTu6RU491V5sxsFnP4oaEmZJEUfcRkYzps5", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", + "/dns/rococo-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGGR5i6qQqfo7iDNp7vjDRKPWuDk53idGV6nFLwS12X5H", + "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", + "/dns/rococo-people-collator-node-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWBvA9BmBfrsVMcAcqVXGYFCpMTvkSk2igNXpmoareYbeT", + "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG", + "/dns/rococo-people-collator-node-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWQ7Q9jLcJTPXy7KEp5hSZ8YMY9pHx9CnQVz3T8TKQ81UG" + ]' \ + | jq '.relay_chain = "rococo"' \ + | jq --argjson para_id $para_id '.para_id = $para_id' \ + | jq --argjson para_id $para_id '.genesis.runtimeGenesis.patch.parachainInfo.parachainId = $para_id' \ + | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ + | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ + "5Gnjmw1iuF2kV4PecFgetJed7B8quBKfLiRM99ELcXvFH9Vn", + "5FLZRxyeRPhG69zo4ZPqCJSYboSKaRBUjBvQc1nkuWoBpZ5P", + "5DNnmPH2MT6SXpfqbJZbTz4eERmuZegssfxc4ysL8PWrHaNN", + "5DkKcSP5MboNMpXScW1CyRqaktKMXH8QLP4Mn49TwS5vhL6k" + ]' \ + | jq '.genesis.runtimeGenesis.patch.session.keys = [ + [ + "5Gnjmw1iuF2kV4PecFgetJed7B8quBKfLiRM99ELcXvFH9Vn", + "5Gnjmw1iuF2kV4PecFgetJed7B8quBKfLiRM99ELcXvFH9Vn", + { + "aura": "5Gnjmw1iuF2kV4PecFgetJed7B8quBKfLiRM99ELcXvFH9Vn" + } + ], + [ + "5FLZRxyeRPhG69zo4ZPqCJSYboSKaRBUjBvQc1nkuWoBpZ5P", + "5FLZRxyeRPhG69zo4ZPqCJSYboSKaRBUjBvQc1nkuWoBpZ5P", + { + "aura": "5FLZRxyeRPhG69zo4ZPqCJSYboSKaRBUjBvQc1nkuWoBpZ5P" + } + ], + [ + "5DNnmPH2MT6SXpfqbJZbTz4eERmuZegssfxc4ysL8PWrHaNN", + "5DNnmPH2MT6SXpfqbJZbTz4eERmuZegssfxc4ysL8PWrHaNN", + { + "aura": "5DNnmPH2MT6SXpfqbJZbTz4eERmuZegssfxc4ysL8PWrHaNN" + } + ], + [ + "5DkKcSP5MboNMpXScW1CyRqaktKMXH8QLP4Mn49TwS5vhL6k", + "5DkKcSP5MboNMpXScW1CyRqaktKMXH8QLP4Mn49TwS5vhL6k", + { + "aura": "5DkKcSP5MboNMpXScW1CyRqaktKMXH8QLP4Mn49TwS5vhL6k" + } + ] + ]' \ + > edited-chain-spec-plain.json + +# build a raw spec +$binary build-spec --chain edited-chain-spec-plain.json --raw > chain-spec-raw.json +cp edited-chain-spec-plain.json people-rococo-spec.json +cp chain-spec-raw.json ./cumulus/parachains/chain-specs/people-rococo.json +cp chain-spec-raw.json people-rococo-spec-raw.json + +# build genesis data +$binary export-genesis-state --chain chain-spec-raw.json > people-rococo-genesis-head-data + +# build genesis wasm +$binary export-genesis-wasm --chain chain-spec-raw.json > people-rococo-wasm diff --git a/cumulus/scripts/create_people_westend_spec.sh b/cumulus/scripts/create_people_westend_spec.sh new file mode 100755 index 0000000000000000000000000000000000000000..f9c3694d61e304677e54ad08c0b61ce8f1ce9013 --- /dev/null +++ b/cumulus/scripts/create_people_westend_spec.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +usage() { + echo Usage: + echo "$1 " + echo "$2 " + echo "e.g.: ./cumulus/scripts/create_people_westend_spec.sh ./target/release/wbuild/people-westend-runtime/people_westend_runtime.compact.compressed.wasm 1004" + exit 1 +} + +if [ -z "$1" ]; then + usage +fi + +if [ -z "$2" ]; then + usage +fi + +set -e + +rt_path=$1 +para_id=$2 + +echo "Generating chain spec for runtime: $rt_path and para_id: $para_id" + +binary="./target/release/polkadot-parachain" + +# build the chain spec we'll manipulate +$binary build-spec --chain people-westend-local > chain-spec-plain.json + +# convert runtime to hex +cat $rt_path | od -A n -v -t x1 | tr -d ' \n' > rt-hex.txt + +# replace the runtime in the spec with the given runtime and set some values to production +# Boot nodes, invulnerables, and session keys from https://github.com/paritytech/devops/issues/2847 +# +# Note: This is a testnet runtime. Each invulnerable's Aura key is also used as its AccountId. This +# is not recommended in value-bearing networks. +cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesis.code = ("0x" + $code)' \ + | jq '.name = "Westend People"' \ + | jq '.id = "people-westend"' \ + | jq '.chainType = "Live"' \ + | jq '.bootNodes = [ + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", + "/dns/westend-people-collator-node-0.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWDcLjDLTu9fNhmas9DTWtqdv8eUbFMWQzVwvXRK7QcjHD", + "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-1.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWM56JbKWAXsDyWh313z73aKYVMp1Hj2nSnAKY3q6MnoC9", + "/dns/westend-people-collator-node-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWGVYTVKW7tYe51JvetvGvVLDPXzqQX1mueJgz14FgkmHG", + "/dns/westend-people-collator-node-2.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWGVYTVKW7tYe51JvetvGvVLDPXzqQX1mueJgz14FgkmHG", + "/dns/westend-people-collator-node-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWCF1eA2Gap69zgXD7Df3e9DqDUsGoByocggTGejoHjK23", + "/dns/westend-people-collator-node-3.parity-testnet.parity.io/tcp/443/wss/p2p/12D3KooWCF1eA2Gap69zgXD7Df3e9DqDUsGoByocggTGejoHjK23" + ]' \ + | jq '.relay_chain = "westend"' \ + | jq --argjson para_id $para_id '.para_id = $para_id' \ + | jq --argjson para_id $para_id '.genesis.runtimeGenesis.patch.parachainInfo.parachainId = $para_id' \ + | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ + | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ + "5CFYvshLff1dHmT33jUcBc7mEKbVRJKbA9HzPqmLfjksHah6", + "5HgEdsYyVGVsyNmbE1sUxeDLrxTLJXnAKCNa2HJ9QXXEir1B", + "5EZmD6eA9wm1Y2Dy2wefLCsFJJcC7o8bVfWm7Mfbuanc8JYo", + "5EkJFfUtbo258dCaqgYSvajN1tNtXhT3SrybW8ZhygoMP3kE" + ]' \ + | jq '.genesis.runtimeGenesis.patch.session.keys = [ + [ + "5CFYvshLff1dHmT33jUcBc7mEKbVRJKbA9HzPqmLfjksHah6", + "5CFYvshLff1dHmT33jUcBc7mEKbVRJKbA9HzPqmLfjksHah6", + { + "aura": "5CFYvshLff1dHmT33jUcBc7mEKbVRJKbA9HzPqmLfjksHah6" + } + ], + [ + "5HgEdsYyVGVsyNmbE1sUxeDLrxTLJXnAKCNa2HJ9QXXEir1B", + "5HgEdsYyVGVsyNmbE1sUxeDLrxTLJXnAKCNa2HJ9QXXEir1B", + { + "aura": "5HgEdsYyVGVsyNmbE1sUxeDLrxTLJXnAKCNa2HJ9QXXEir1B" + } + ], + [ + "5EZmD6eA9wm1Y2Dy2wefLCsFJJcC7o8bVfWm7Mfbuanc8JYo", + "5EZmD6eA9wm1Y2Dy2wefLCsFJJcC7o8bVfWm7Mfbuanc8JYo", + { + "aura": "5EZmD6eA9wm1Y2Dy2wefLCsFJJcC7o8bVfWm7Mfbuanc8JYo" + } + ], + [ + "5EkJFfUtbo258dCaqgYSvajN1tNtXhT3SrybW8ZhygoMP3kE", + "5EkJFfUtbo258dCaqgYSvajN1tNtXhT3SrybW8ZhygoMP3kE", + { + "aura": "5EkJFfUtbo258dCaqgYSvajN1tNtXhT3SrybW8ZhygoMP3kE" + } + ] + ]' \ + > edited-chain-spec-plain.json + +# build a raw spec +$binary build-spec --chain edited-chain-spec-plain.json --raw > chain-spec-raw.json +cp edited-chain-spec-plain.json people-westend-spec.json +cp chain-spec-raw.json ./cumulus/parachains/chain-specs/people-westend.json +cp chain-spec-raw.json people-westend-spec-raw.json + +# build genesis data +$binary export-genesis-state --chain chain-spec-raw.json > people-westend-genesis-head-data + +# build genesis wasm +$binary export-genesis-wasm --chain chain-spec-raw.json > people-westend-wasm diff --git a/cumulus/scripts/generate_hex_encoded_call/index.js b/cumulus/scripts/generate_hex_encoded_call/index.js index 09f0e6aaf619a44642bfa4f065f3bf69f0bb7a33..30f89d754ceb7de1b24bc31413e09c862a461256 100644 --- a/cumulus/scripts/generate_hex_encoded_call/index.js +++ b/cumulus/scripts/generate_hex_encoded_call/index.js @@ -106,11 +106,11 @@ function forceCreateAsset(endpoint, outputFile, assetId, assetOwnerAccountId, is }); } -function setStorage(endpoint, outputFile, items) { - console.log(`Generating setStorage from RPC endpoint: ${endpoint} to outputFile: ${outputFile}, items: ${items}`); +function forceXcmVersion(endpoint, outputFile, dest, xcm_version) { + console.log(`Generating forceXcmVersion from RPC endpoint: ${endpoint} to outputFile: ${outputFile}, dest: ${dest}, xcm_version: ${xcm_version}`); connect(endpoint) .then((api) => { - const call = api.tx.system.setStorage(JSON.parse(items)); + const call = api.tx.polkadotXcm.forceXcmVersion(JSON.parse(dest), xcm_version); writeHexEncodedBytesToOutput(call.method, outputFile); exit(0); }) @@ -154,8 +154,8 @@ switch (type) { case 'force-create-asset': forceCreateAsset(rpcEnpoint, output, inputArgs[0], inputArgs[1], inputArgs[2], inputArgs[3]); break; - case 'set-storage': - setStorage(rpcEnpoint, output, inputArgs[0]); + case 'force-xcm-version': + forceXcmVersion(rpcEnpoint, output, inputArgs[0], inputArgs[1]); break; case 'check': console.log(`Checking nodejs installation, if you see this everything is ready!`); diff --git a/cumulus/scripts/generate_hex_encoded_call/package-lock.json b/cumulus/scripts/generate_hex_encoded_call/package-lock.json index 3383265e7796e19f3e0162ade9883144ff686a66..b2dddaa19ed1561b98422b3a28f6777308b3ba47 100644 --- a/cumulus/scripts/generate_hex_encoded_call/package-lock.json +++ b/cumulus/scripts/generate_hex_encoded_call/package-lock.json @@ -9,1204 +9,750 @@ "version": "y", "license": "MIT", "dependencies": { - "@polkadot/api": "^6.5.2", - "@polkadot/util": "^7.6.1" + "@polkadot/api": "^10.11", + "@polkadot/util": "^12.6" } }, - "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", "dependencies": { - "regenerator-runtime": "^0.13.11" + "@noble/hashes": "1.3.3" }, - "engines": { - "node": ">=6.9.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.0.0.tgz", - "integrity": "sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==" - }, - "node_modules/@noble/secp256k1": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.5.5.tgz", - "integrity": "sha512-sZ1W6gQzYnu45wPrWx8D3kwI2/U29VYTx9OjbDAd7jwRItJ0cSTMPRL/C8AWZFn9kWFLQGqEXVEE86w4Z8LpIQ==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@polkadot/api": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-6.12.1.tgz", - "integrity": "sha512-RVdTiA2WaEvproM3i6E9TKS1bfXpPd9Ly9lUG/kVLaspjKoIot9DJUDTl97TJ+7xr8LXGbXqm448Ud0hsEBV8Q==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/api-derive": "6.12.1", - "@polkadot/keyring": "^8.1.2", - "@polkadot/rpc-core": "6.12.1", - "@polkadot/rpc-provider": "6.12.1", - "@polkadot/types": "6.12.1", - "@polkadot/types-known": "6.12.1", - "@polkadot/util": "^8.1.2", - "@polkadot/util-crypto": "^8.1.2", - "eventemitter3": "^4.0.7", - "rxjs": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/api-derive": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-6.12.1.tgz", - "integrity": "sha512-5LOVlG5EBCT+ytY6aHmQ4RdEWZovZQqRoc6DLd5BLhkR7BFTHKSkLQW+89so8jd0zEtmSXBVPPnsrXS8joM35Q==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/api": "6.12.1", - "@polkadot/rpc-core": "6.12.1", - "@polkadot/types": "6.12.1", - "@polkadot/util": "^8.1.2", - "@polkadot/util-crypto": "^8.1.2", - "rxjs": "^7.4.0" - }, + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/api-derive/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" + "node": ">= 16" }, - "engines": { - "node": ">=14.0.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@polkadot/api-derive/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "node_modules/@polkadot/api": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.11.2.tgz", + "integrity": "sha512-AorCZxCWCoTtdbl4DPUZh+ACe/pbLIS1BkdQY0AFJuZllm0x/yWzjgampcPd5jQAA/O3iKShRBkZqj6Mk9yG/A==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/api-augment": "10.11.2", + "@polkadot/api-base": "10.11.2", + "@polkadot/api-derive": "10.11.2", + "@polkadot/keyring": "^12.6.2", + "@polkadot/rpc-augment": "10.11.2", + "@polkadot/rpc-core": "10.11.2", + "@polkadot/rpc-provider": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-augment": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/types-create": "10.11.2", + "@polkadot/types-known": "10.11.2", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/api-derive/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", + "node_modules/@polkadot/api-augment": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.11.2.tgz", + "integrity": "sha512-PTpnqpezc75qBqUtgrc0GYB8h9UHjfbHSRZamAbecIVAJ2/zc6CqtnldeaBlIu1IKTgBzi3FFtTyYu+ZGbNT2Q==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/api-base": "10.11.2", + "@polkadot/rpc-augment": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-augment": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/api-derive/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", + "node_modules/@polkadot/api-base": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.11.2.tgz", + "integrity": "sha512-4LIjaUfO9nOzilxo7XqzYKCNMtmUypdk8oHPdrRnSjKEsnK7vDsNi+979z2KXNXd2KFSCFHENmI523fYnMnReg==", "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/api-derive/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/@polkadot/api/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" + "@polkadot/rpc-core": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/util": "^12.6.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/api/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/api/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", + "node_modules/@polkadot/api-derive": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.11.2.tgz", + "integrity": "sha512-m3BQbPionkd1iSlknddxnL2hDtolPIsT+aRyrtn4zgMRPoLjHFmTmovvg8RaUyYofJtZeYrnjMw0mdxiSXx7eA==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/api": "10.11.2", + "@polkadot/api-augment": "10.11.2", + "@polkadot/api-base": "10.11.2", + "@polkadot/rpc-core": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/api/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/api/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/keyring": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-8.7.1.tgz", - "integrity": "sha512-t6ZgQVC+nQT7XwbWtEhkDpiAzxKVJw8Xd/gWdww6xIrawHu7jo3SGB4QNdPgkf8TvDHYAAJiupzVQYAlOIq3GA==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz", + "integrity": "sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/util": "8.7.1", - "@polkadot/util-crypto": "8.7.1" + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "8.7.1", - "@polkadot/util-crypto": "8.7.1" - } - }, - "node_modules/@polkadot/keyring/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/keyring/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/keyring/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/keyring/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2" } }, - "node_modules/@polkadot/keyring/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/networks": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-8.7.1.tgz", - "integrity": "sha512-8xAmhDW0ry5EKcEjp6VTuwoTm0DdDo/zHsmx88P6sVL87gupuFsL+B6TrsYLl8GcaqxujwrOlKB+CKTUg7qFKg==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz", + "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/util": "8.7.1", - "@substrate/ss58-registry": "^1.17.0" + "@polkadot/util": "12.6.2", + "@substrate/ss58-registry": "^1.44.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/networks/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", + "node_modules/@polkadot/rpc-augment": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.11.2.tgz", + "integrity": "sha512-9AhT0WW81/8jYbRcAC6PRmuxXqNhJje8OYiulBQHbG1DTCcjAfz+6VQBke9BwTStzPq7d526+yyBKD17O3zlAA==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" + "@polkadot/rpc-core": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/networks/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/networks/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/networks/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/networks/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/rpc-core": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-6.12.1.tgz", - "integrity": "sha512-Hb08D9zho3SB1UNlUCmG5q0gdgbOx25JKGLDfSYpD/wtD0Y1Sf2X5cfgtMoSYE3USWiRdCu4BxQkXTiRjPjzJg==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/rpc-provider": "6.12.1", - "@polkadot/types": "6.12.1", - "@polkadot/util": "^8.1.2", - "rxjs": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-core/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-core/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.11.2.tgz", + "integrity": "sha512-Ot0CFLWx8sZhLZog20WDuniPA01Bk2StNDsdAQgcFKPwZw6ShPaZQCHuKLQK6I6DodOrem9FXX7c1hvoKJP5Ww==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/rpc-augment": "10.11.2", + "@polkadot/rpc-provider": "10.11.2", + "@polkadot/types": "10.11.2", + "@polkadot/util": "^12.6.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/rpc-core/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-core/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/rpc-core/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/rpc-provider": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-6.12.1.tgz", - "integrity": "sha512-uUHD3fLTOeZYWJoc6DQlhz+MJR33rVelasV+OxFY2nSD9MSNXRwQh+9UKDQBnyxw5B4BZ2QaEGfucDeavXmVDw==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/types": "6.12.1", - "@polkadot/util": "^8.1.2", - "@polkadot/util-crypto": "^8.1.2", - "@polkadot/x-fetch": "^8.1.2", - "@polkadot/x-global": "^8.1.2", - "@polkadot/x-ws": "^8.1.2", - "eventemitter3": "^4.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.11.2.tgz", + "integrity": "sha512-he5jWMpDJp7e+vUzTZDzpkB7ps3H8psRally+/ZvZZScPvFEjfczT7I1WWY9h58s8+ImeVP/lkXjL9h/gUOt3Q==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/keyring": "^12.6.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-support": "10.11.2", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "@polkadot/x-fetch": "^12.6.2", + "@polkadot/x-global": "^12.6.2", + "@polkadot/x-ws": "^12.6.2", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.4.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "node": ">=18" }, - "engines": { - "node": ">=14.0.0" + "optionalDependencies": { + "@substrate/connect": "0.7.35" } }, - "node_modules/@polkadot/rpc-provider/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/types": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-6.12.1.tgz", - "integrity": "sha512-O37cAGUL0xiXTuO3ySweVh0OuFUD6asrd0TfuzGsEp3jAISWdElEHV5QDiftWq8J9Vf8BMgTcP2QLFbmSusxqA==", - "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/types-known": "6.12.1", - "@polkadot/util": "^8.1.2", - "@polkadot/util-crypto": "^8.1.2", - "rxjs": "^7.4.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/types-known": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-6.12.1.tgz", - "integrity": "sha512-Z8bHpPQy+mqUm0uR1tai6ra0bQIoPmgRcGFYUM+rJtW1kx/6kZLh10HAICjLpPeA1cwLRzaxHRDqH5MCU6OgXw==", + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.11.2.tgz", + "integrity": "sha512-d52j3xXni+C8GdYZVTSfu8ROAnzXFMlyRvXtor0PudUc8UQHOaC4+mYAkTBGA2gKdmL8MHSfRSbhcxHhsikY6Q==", "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/networks": "^8.1.2", - "@polkadot/types": "6.12.1", - "@polkadot/util": "^8.1.2" + "@polkadot/keyring": "^12.6.2", + "@polkadot/types-augment": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/types-create": "10.11.2", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/types-known/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", + "node_modules/@polkadot/types-augment": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.11.2.tgz", + "integrity": "sha512-8eB8ew04wZiE5GnmFvEFW1euJWmF62SGxb1O+8wL3zoUtB9Xgo1vB6w6xbTrd+HLV6jNSeXXnbbF1BEUvi9cNg==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" + "@polkadot/types": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/types-known/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "node_modules/@polkadot/types-codec": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.11.2.tgz", + "integrity": "sha512-3xjOQL+LOOMzYqlgP9ROL0FQnzU8lGflgYewzau7AsDlFziSEtb49a9BpYo6zil4koC+QB8zQ9OHGFumG08T8w==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/util": "^12.6.2", + "@polkadot/x-bigint": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/types-known/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", + "node_modules/@polkadot/types-create": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.11.2.tgz", + "integrity": "sha512-SJt23NxYvefRxVZZm6mT9ed1pR6FDoIGQ3xUpbjhTLfU2wuhpKjekMVorYQ6z/gK2JLMu2kV92Ardsz+6GX5XQ==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/types-codec": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/types-known/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" + "node": ">=18" } }, - "node_modules/@polkadot/types-known/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/@polkadot/types/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/types/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "node_modules/@polkadot/types-known": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.11.2.tgz", + "integrity": "sha512-kbEIX7NUQFxpDB0FFGNyXX/odY7jbp56RGD+Z4A731fW2xh/DgAQrI994xTzuh0c0EqPE26oQm3kATSpseqo9w==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/networks": "^12.6.2", + "@polkadot/types": "10.11.2", + "@polkadot/types-codec": "10.11.2", + "@polkadot/types-create": "10.11.2", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/types/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", + "node_modules/@polkadot/types-support": { + "version": "10.11.2", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.11.2.tgz", + "integrity": "sha512-X11hoykFYv/3efg4coZy2hUOUc97JhjQMJLzDhHniFwGLlYU8MeLnPdCVGkXx0xDDjTo4/ptS1XpZ5HYcg+gRw==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/types/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@polkadot/types/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, "node_modules/@polkadot/util": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-7.9.2.tgz", - "integrity": "sha512-6ABY6ErgkCsM4C6+X+AJSY4pBGwbKlHZmUtHftaiTvbaj4XuA4nTo3GU28jw8wY0Jh2cJZJvt6/BJ5GVkm5tBA==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz", + "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==", "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/x-textdecoder": "7.9.2", - "@polkadot/x-textencoder": "7.9.2", - "@types/bn.js": "^4.11.6", - "bn.js": "^4.12.0", - "camelcase": "^6.2.1", - "ip-regex": "^4.3.0" + "@polkadot/x-bigint": "12.6.2", + "@polkadot/x-global": "12.6.2", + "@polkadot/x-textdecoder": "12.6.2", + "@polkadot/x-textencoder": "12.6.2", + "@types/bn.js": "^5.1.5", + "bn.js": "^5.2.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@polkadot/util-crypto": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-8.7.1.tgz", - "integrity": "sha512-TaSuJ2aNrB5sYK7YXszkEv24nYJKRFqjF2OrggoMg6uYxUAECvTkldFnhtgeizMweRMxJIBu6bMHlSIutbWgjw==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz", + "integrity": "sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@noble/hashes": "1.0.0", - "@noble/secp256k1": "1.5.5", - "@polkadot/networks": "8.7.1", - "@polkadot/util": "8.7.1", - "@polkadot/wasm-crypto": "^5.1.1", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-randomvalues": "8.7.1", - "@scure/base": "1.0.0", - "ed2curve": "^0.3.0", - "tweetnacl": "^1.0.3" + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@polkadot/networks": "12.6.2", + "@polkadot/util": "12.6.2", + "@polkadot/wasm-crypto": "^7.3.2", + "@polkadot/wasm-util": "^7.3.2", + "@polkadot/x-bigint": "12.6.2", + "@polkadot/x-randomvalues": "12.6.2", + "@scure/base": "^1.1.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "8.7.1" + "@polkadot/util": "12.6.2" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/util": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-8.7.1.tgz", - "integrity": "sha512-XjY1bTo7V6OvOCe4yn8H2vifeuBciCy0gq0k5P1tlGUQLI/Yt0hvDmxcA0FEPtqg8CL+rYRG7WXGPVNjkrNvyQ==", + "node_modules/@polkadot/wasm-bridge": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz", + "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-bigint": "8.7.1", - "@polkadot/x-global": "8.7.1", - "@polkadot/x-textdecoder": "8.7.1", - "@polkadot/x-textencoder": "8.7.1", - "@types/bn.js": "^5.1.0", - "bn.js": "^5.2.0", - "ip-regex": "^4.3.0" + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-textdecoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-8.7.1.tgz", - "integrity": "sha512-ia0Ie2zi4VdQdNVD2GE2FZzBMfX//hEL4w546RMJfZM2LqDS674LofHmcyrsv5zscLnnRyCxZC1+J2dt+6MDIA==", + "node_modules/@polkadot/wasm-crypto": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz", + "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-init": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-textencoder": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-8.7.1.tgz", - "integrity": "sha512-XDO0A27Xy+eJCKSxENroB8Dcnl+UclGG4ZBei+P/BqZ9rsjskUyd2Vsl6peMXAcsxwOE7g0uTvujoGM8jpKOXw==", + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz", + "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dependencies": { - "@types/node": "*" + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/@polkadot/wasm-crypto": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-5.1.1.tgz", - "integrity": "sha512-JCcAVfH8DhYuEyd4oX1ouByxhou0TvpErKn8kHjtzt7+tRoFi0nzWlmK4z49vszsV3JJgXxV81i10C0BYlwTcQ==", + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz", + "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/wasm-crypto-asmjs": "^5.1.1", - "@polkadot/wasm-crypto-wasm": "^5.1.1" + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/wasm-crypto-asmjs": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-5.1.1.tgz", - "integrity": "sha512-1WBwc2G3pZMKW1T01uXzKE30Sg22MXmF3RbbZiWWk3H2d/Er4jZQRpjumxO5YGWan+xOb7HQQdwnrUnrPgbDhg==", + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz", + "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==", "dependencies": { - "@babel/runtime": "^7.17.8" + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*" } }, - "node_modules/@polkadot/wasm-crypto-wasm": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-5.1.1.tgz", - "integrity": "sha512-F9PZ30J2S8vUNl2oY7Myow5Xsx5z5uNVpnNlJwlmY8IXBvyucvyQ4HSdhJsrbs4W1BfFc0mHghxgp0FbBCnf/Q==", + "node_modules/@polkadot/wasm-util": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz", + "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==", "dependencies": { - "@babel/runtime": "^7.17.8" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*" } }, "node_modules/@polkadot/x-bigint": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-8.7.1.tgz", - "integrity": "sha512-ClkhgdB/KqcAKk3zA6Qw8wBL6Wz67pYTPkrAtImpvoPJmR+l4RARauv+MH34JXMUNlNb3aUwqN6lq2Z1zN+mJg==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz", + "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@polkadot/x-fetch": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-8.7.1.tgz", - "integrity": "sha512-ygNparcalYFGbspXtdtZOHvNXZBkNgmNO+um9C0JYq74K5OY9/be93uyfJKJ8JcRJtOqBfVDsJpbiRkuJ1PRfg==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz", + "integrity": "sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1", - "@types/node-fetch": "^2.6.1", - "node-fetch": "^2.6.7" + "@polkadot/x-global": "12.6.2", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@polkadot/x-global": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-8.7.1.tgz", - "integrity": "sha512-WOgUor16IihgNVdiTVGAWksYLUAlqjmODmIK1cuWrLOZtV1VBomWcb3obkO9sh5P6iWziAvCB/i+L0vnTN9ZCA==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.6.2.tgz", + "integrity": "sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g==", "dependencies": { - "@babel/runtime": "^7.17.8" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@polkadot/x-randomvalues": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-8.7.1.tgz", - "integrity": "sha512-njt17MlfN6yNyNEti7fL12lr5qM6A1aSGkWKVuqzc7XwSBesifJuW4km5u6r2gwhXjH2eHDv9SoQ7WXu8vrrkg==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz", + "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==", "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@polkadot/util": "12.6.2", + "@polkadot/wasm-util": "*" } }, "node_modules/@polkadot/x-textdecoder": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-7.9.2.tgz", - "integrity": "sha512-wfwbSHXPhrOAl12QvlIOGNkMH/N/h8PId2ytIjvM/8zPPFB5Il6DWSFLtVapOGEpIFjEWbd5t8Td4pHBVXIEbg==", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz", + "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==", "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/x-global": "7.9.2" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/x-textdecoder/node_modules/@polkadot/x-global": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-7.9.2.tgz", - "integrity": "sha512-JX5CrGWckHf1P9xKXq4vQCAuMUbL81l2hOWX7xeP8nv4caHEpmf5T1wD1iMdQBL5PFifo6Pg0V6/oZBB+bts7A==", + "node_modules/@polkadot/x-textencoder": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz", + "integrity": "sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw==", "dependencies": { - "@babel/runtime": "^7.16.3" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/x-textencoder": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-7.9.2.tgz", - "integrity": "sha512-A19wwYINuZwU2dUyQ/mMzB0ISjyfc4cISfL4zCMUAVgj7xVoXMYV2GfjNdMpA8Wsjch3su6pxLbtJ2wU03sRTQ==", + "node_modules/@polkadot/x-ws": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.6.2.tgz", + "integrity": "sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw==", "dependencies": { - "@babel/runtime": "^7.16.3", - "@polkadot/x-global": "7.9.2" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2", + "ws": "^8.15.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, - "node_modules/@polkadot/x-textencoder/node_modules/@polkadot/x-global": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-7.9.2.tgz", - "integrity": "sha512-JX5CrGWckHf1P9xKXq4vQCAuMUbL81l2hOWX7xeP8nv4caHEpmf5T1wD1iMdQBL5PFifo6Pg0V6/oZBB+bts7A==", - "dependencies": { - "@babel/runtime": "^7.16.3" - }, - "engines": { - "node": ">=14.0.0" + "node_modules/@scure/base": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@polkadot/x-ws": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-8.7.1.tgz", - "integrity": "sha512-Mt0tcNzGXyKnN3DQ06alkv+JLtTfXWu6zSypFrrKHSQe3u79xMQ1nSicmpT3gWLhIa8YF+8CYJXMrqaXgCnDhw==", + "node_modules/@substrate/connect": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.35.tgz", + "integrity": "sha512-Io8vkalbwaye+7yXfG1Nj52tOOoJln2bMlc7Q9Yy3vEWqZEVkgKmcPVzbwV0CWL3QD+KMPDA2Dnw/X7EdwgoLw==", + "hasInstallScript": true, + "optional": true, "dependencies": { - "@babel/runtime": "^7.17.8", - "@polkadot/x-global": "8.7.1", - "@types/websocket": "^1.0.5", - "websocket": "^1.0.34" - }, - "engines": { - "node": ">=14.0.0" + "@substrate/connect-extension-protocol": "^1.0.1", + "smoldot": "2.0.7" } }, - "node_modules/@scure/base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.0.0.tgz", - "integrity": "sha512-gIVaYhUsy+9s58m/ETjSJVKHhKTBMmcRb9cEV5/5dwvfDlfORjKrFsDeDHWRrm6RjcPvCLZFwGJjAjLj1gg4HA==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "node_modules/@substrate/connect-extension-protocol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz", + "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==", + "optional": true }, "node_modules/@substrate/ss58-registry": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.38.0.tgz", - "integrity": "sha512-sHiVRWekGMRZAjPukN9/W166NM6D5wtHcK6RVyLy66kg3CHNZ1BXfpXcjOiXSwhbd7guQFDEwnOVaDrbk1XL1g==" + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz", + "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A==" }, "node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "18.11.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", - "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==" - }, - "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", + "version": "20.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz", + "integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==", "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/websocket": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.5.tgz", - "integrity": "sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==", - "dependencies": { - "@types/node": "*" + "undici-types": "~5.26.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, - "node_modules/bufferutil": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz", - "integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "engines": { - "node": ">=6.14.2" + "node": ">= 12" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "delayed-stream": "~1.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, "engines": { - "node": ">=0.4.0" + "node": "^12.20 || >= 14.13" } }, - "node_modules/ed2curve": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ed2curve/-/ed2curve-0.3.0.tgz", - "integrity": "sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dependencies": { - "tweetnacl": "1.x.x" - } - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "node": ">=12.20.0" } }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dependencies": { - "type": "^2.7.2" + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "engines": { + "node": ">= 8" } }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "node_modules/nock": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.4.0.tgz", + "integrity": "sha512-W8NVHjO/LCTNA64yxAPHV/K47LpGYcVzgKd3Q0n6owhwvD0Dgoterc25R4rnZbckJEb6Loxz1f5QMuJpJnbSyQ==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/ip-regex": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", - "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", - "engines": { - "node": ">=8" + "node": ">= 10.13" } }, - "node_modules/is-typedarray": { + "node_modules/node-domexception": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], "engines": { - "node": ">= 0.6" + "node": ">=10.5.0" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" - }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "engines": { + "node": ">= 8" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dependencies": { "tslib": "^2.1.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/smoldot": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.7.tgz", + "integrity": "sha512-VAOBqEen6vises36/zgrmAT1GWk2qE3X8AGnO7lmQFdskbKx8EovnwS22rtPAG+Y1Rk23/S22kDJUdPANyPkBA==", + "optional": true, "dependencies": { - "is-typedarray": "^1.0.0" + "ws": "^8.8.1" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, - "node_modules/websocket": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz", - "integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==", - "dependencies": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.50", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", "engines": { - "node": ">=4.0.0" + "node": ">= 8" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", "engines": { - "node": ">=0.10.32" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } } } diff --git a/cumulus/scripts/generate_hex_encoded_call/package.json b/cumulus/scripts/generate_hex_encoded_call/package.json index 1c68924db24400bc540d9b32f16cfe5573277834..ecf0a2483db100e688e06da4233f036c2383b3a6 100644 --- a/cumulus/scripts/generate_hex_encoded_call/package.json +++ b/cumulus/scripts/generate_hex_encoded_call/package.json @@ -5,7 +5,7 @@ "main": "index.js", "license": "MIT", "dependencies": { - "@polkadot/api": "^6.5.2", - "@polkadot/util": "^7.6.1" + "@polkadot/api": "^10.11", + "@polkadot/util": "^12.6" } } diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 037b8600db6324312686a6780b082cfc0396af48..7190172101cb509f7dd7c19ad25dc6d4d54036e7 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -5,6 +5,9 @@ authors.workspace = true edition.workspace = true publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/cumulus/test/relay-sproof-builder/Cargo.toml b/cumulus/test/relay-sproof-builder/Cargo.toml index 262a4ff92b5c7f111677d9b6467431cbd1ace45d..02a9750d78ec09d674f37833a61a03bf9dc6daf0 100644 --- a/cumulus/test/relay-sproof-builder/Cargo.toml +++ b/cumulus/test/relay-sproof-builder/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license = "Apache-2.0" description = "Mocked relay state proof builder for testing Cumulus." +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 7bdb69df2c2e6df81ef066010806c5592bfcdc9c..5902a62512bed772318145ccdb954ff2dfef4c92 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -5,6 +5,9 @@ authors.workspace = true edition.workspace = true publish = false +[lints] +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"] } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 271c450539bfd24f8b9753a3aace415dd312fac0..b5294ec038a95121fd3e93ccc751483524c63578 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -5,19 +5,22 @@ authors.workspace = true edition.workspace = true publish = false +[lints] +workspace = true + [[bin]] name = "test-parachain" path = "src/main.rs" [dependencies] -async-trait = "0.1.73" -clap = { version = "4.4.10", features = ["derive"] } +async-trait = "0.1.74" +clap = { version = "4.4.13", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } jsonrpsee = { version = "0.16.2", features = ["server"] } rand = "0.8.5" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" tokio = { version = "1.32.0", features = ["macros"] } tracing = "0.1.37" url = "2.4.0" @@ -68,9 +71,9 @@ cumulus-client-cli = { path = "../../client/cli" } parachains-common = { path = "../../parachains/common" } cumulus-client-consensus-common = { path = "../../client/consensus/common" } cumulus-client-consensus-relay-chain = { path = "../../client/consensus/relay-chain" } +cumulus-client-parachain-inherent = { path = "../../client/parachain-inherent" } cumulus-client-service = { path = "../../client/service" } cumulus-primitives-core = { path = "../../primitives/core" } -cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } cumulus-relay-chain-inprocess-interface = { path = "../../client/relay-chain-inprocess-interface" } cumulus-relay-chain-interface = { path = "../../client/relay-chain-interface" } cumulus-test-runtime = { path = "../runtime" } diff --git a/cumulus/test/service/src/bench_utils.rs b/cumulus/test/service/src/bench_utils.rs index 1894835caec81e3176b3c9c037d70d5770a47f3f..4ace894b392aa2393741572249ed35f0a7101845 100644 --- a/cumulus/test/service/src/bench_utils.rs +++ b/cumulus/test/service/src/bench_utils.rs @@ -19,8 +19,8 @@ use codec::Encode; use sc_block_builder::BlockBuilderBuilder; use crate::{construct_extrinsic, Client as TestClient}; +use cumulus_client_parachain_inherent::ParachainInherentData; use cumulus_primitives_core::{relay_chain::AccountId, PersistedValidationData}; -use cumulus_primitives_parachain_inherent::ParachainInherentData; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use cumulus_test_runtime::{ BalancesCall, GluttonCall, NodeBlock, SudoCall, UncheckedExtrinsic, WASM_BINARY, diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index ef1159a3c1f8534a0dbfa5ae09859c8e1dadc626..3dc5b8e31016bf792f47003c03089a136c32c711 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -16,6 +16,7 @@ use std::{net::SocketAddr, path::PathBuf}; +use cumulus_client_cli::{ExportGenesisHeadCommand, ExportGenesisWasmCommand}; use polkadot_service::{ChainSpec, ParaId, PrometheusConfig}; use sc_cli::{ CliConfiguration, DefaultConfigurationValues, ImportParams, KeystoreParams, NetworkParams, @@ -60,45 +61,13 @@ pub enum Subcommand { BuildSpec(sc_cli::BuildSpecCmd), /// Export the genesis state of the parachain. - ExportGenesisState(ExportGenesisStateCommand), + #[command(alias = "export-genesis-state")] + ExportGenesisHead(ExportGenesisHeadCommand), /// Export the genesis wasm of the parachain. ExportGenesisWasm(ExportGenesisWasmCommand), } -#[derive(Debug, clap::Parser)] -#[group(skip)] -pub struct ExportGenesisStateCommand { - #[arg(default_value_t = 2000u32)] - pub parachain_id: u32, - - #[command(flatten)] - pub base: cumulus_client_cli::ExportGenesisStateCommand, -} - -impl CliConfiguration for ExportGenesisStateCommand { - fn shared_params(&self) -> &SharedParams { - &self.base.shared_params - } -} - -/// Command for exporting the genesis wasm file. -#[derive(Debug, clap::Parser)] -#[group(skip)] -pub struct ExportGenesisWasmCommand { - #[arg(default_value_t = 2000u32)] - pub parachain_id: u32, - - #[command(flatten)] - pub base: cumulus_client_cli::ExportGenesisWasmCommand, -} - -impl CliConfiguration for ExportGenesisWasmCommand { - fn shared_params(&self) -> &SharedParams { - &self.base.shared_params - } -} - #[derive(Debug)] pub struct RelayChainCli { /// The actual relay chain cli object. diff --git a/cumulus/test/service/src/genesis.rs b/cumulus/test/service/src/genesis.rs index d4a9a22562646d09cc793fa367396f5ab33cf118..be4b0427b2ee50ed4db16340571dd8d46df28ed2 100644 --- a/cumulus/test/service/src/genesis.rs +++ b/cumulus/test/service/src/genesis.rs @@ -15,11 +15,50 @@ // along with Cumulus. If not, see . use codec::Encode; -use cumulus_client_cli::generate_genesis_block; use cumulus_primitives_core::ParaId; use cumulus_test_runtime::Block; use polkadot_primitives::HeadData; -use sp_runtime::traits::Block as BlockT; +use sc_chain_spec::ChainSpec; +use sp_runtime::{ + traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + StateVersion, +}; + +/// Generate a simple test genesis block from a given ChainSpec. +pub fn generate_genesis_block( + chain_spec: &dyn ChainSpec, + genesis_state_version: StateVersion, +) -> Result { + let storage = chain_spec.build_storage()?; + + let child_roots = storage.children_default.iter().map(|(sk, child_content)| { + let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + child_content.data.clone().into_iter().collect(), + genesis_state_version, + ); + (sk.clone(), state_root.encode()) + }); + let state_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + storage.top.clone().into_iter().chain(child_roots).collect(), + genesis_state_version, + ); + + let extrinsics_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( + Vec::new(), + genesis_state_version, + ); + + Ok(Block::new( + <::Header as HeaderT>::new( + Zero::zero(), + extrinsics_root, + state_root, + Default::default(), + Default::default(), + ), + Default::default(), + )) +} /// Returns the initial head data for a parachain ID. pub fn initial_head_data(para_id: ParaId) -> HeadData { diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 627d060d8a0cd024b8131a4f52bf2ff80842258b..1de3af1bc9d7f541e5eac1ac67e6229c1cfa9229 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -22,7 +22,9 @@ pub mod bench_utils; pub mod chain_spec; -mod genesis; + +/// Utilities for creating test genesis block and head data +pub mod genesis; use runtime::AccountId; use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY}; @@ -181,6 +183,16 @@ impl RecoveryHandle for FailingRecoveryHandle { } } +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type Service = PartialComponents< + Client, + Backend, + (), + sc_consensus::import_queue::BasicQueue, + sc_transaction_pool::FullPool, + ParachainBlockImport, +>; + /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to @@ -188,17 +200,7 @@ impl RecoveryHandle for FailingRecoveryHandle { pub fn new_partial( config: &mut Configuration, enable_import_proof_record: bool, -) -> Result< - PartialComponents< - Client, - Backend, - (), - sc_consensus::import_queue::BasicQueue, - sc_transaction_pool::FullPool, - ParachainBlockImport, - >, - sc_service::Error, -> { +) -> Result { let heap_pages = config .default_heap_pages .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ }); @@ -445,7 +447,7 @@ where let relay_chain_interface = relay_chain_interface_for_closure.clone(); async move { let parachain_inherent = - cumulus_primitives_parachain_inherent::ParachainInherentData::create_at( + cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at( relay_parent, &relay_chain_interface, &validation_data, diff --git a/cumulus/test/service/src/main.rs b/cumulus/test/service/src/main.rs index 55a0f12d671a27e180974fc2c591b1e724548ce6..aace92ca965dcfeaa85b58b2b0f8d8c14431af47 100644 --- a/cumulus/test/service/src/main.rs +++ b/cumulus/test/service/src/main.rs @@ -16,16 +16,14 @@ mod cli; -use std::{io::Write, sync::Arc}; +use std::sync::Arc; use cli::{RelayChainCli, Subcommand, TestCollatorCli}; -use cumulus_client_cli::generate_genesis_block; use cumulus_primitives_core::{relay_chain::CollatorPair, ParaId}; -use cumulus_test_service::AnnounceBlockFn; +use cumulus_test_service::{new_partial, AnnounceBlockFn}; use polkadot_service::runtime_traits::AccountIdConversion; use sc_cli::{CliConfiguration, SubstrateCli}; -use sp_core::{hexdisplay::HexDisplay, Encode, Pair}; -use sp_runtime::traits::Block; +use sp_core::Pair; pub fn wrap_announce_block() -> Box AnnounceBlockFn> { tracing::info!("Block announcements disabled."); @@ -44,38 +42,16 @@ fn main() -> Result<(), sc_cli::Error> { runner.sync_run(|config| cmd.run(config.chain_spec, config.network)) }, - Some(Subcommand::ExportGenesisState(params)) => { - let mut builder = sc_cli::LoggerBuilder::new(""); - builder.with_profiling(sc_tracing::TracingReceiver::Log, ""); - let _ = builder.init(); - - let spec = - cli.load_spec(¶ms.base.shared_params.chain.clone().unwrap_or_default())?; - let state_version = cumulus_test_service::runtime::VERSION.state_version(); - - let block: parachains_common::Block = generate_genesis_block(&*spec, state_version)?; - let raw_header = block.header().encode(); - let output_buf = if params.base.raw { - raw_header - } else { - format!("0x{:?}", HexDisplay::from(&block.header().encode())).into_bytes() - }; - - if let Some(output) = ¶ms.base.output { - std::fs::write(output, output_buf)?; - } else { - std::io::stdout().write_all(&output_buf)?; - } - - Ok(()) + Some(Subcommand::ExportGenesisHead(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|mut config| { + let partial = new_partial(&mut config, false)?; + cmd.run(partial.client) + }) }, Some(Subcommand::ExportGenesisWasm(cmd)) => { let runner = cli.create_runner(cmd)?; - runner.sync_run(|_config| { - let parachain_id = ParaId::from(cmd.parachain_id); - let spec = cumulus_test_service::get_chain_spec(Some(parachain_id)); - cmd.base.run(&spec) - }) + runner.sync_run(|config| cmd.run(&*config.chain_spec)) }, None => { let log_filters = cli.run.normalize().log_filters(); diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index 2f851f1bcde06cb57107019b1f477ad747fc6ef6..0f10221d6006abce96b4dfb69445678957810693 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } paste = "1.0.14" diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index f2e4ff397c45cb228fcb80d39972c7f8ceac4a99..c9e4de21d43cafe4208d6997b23b69b9029cef54 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -pub use codec::{Decode, Encode, EncodeLike}; +pub use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; pub use lazy_static::lazy_static; pub use log; pub use paste; @@ -59,9 +59,13 @@ pub use polkadot_runtime_parachains::inclusion::{AggregateMessageOrigin, UmpQueu // Polkadot pub use polkadot_parachain_primitives::primitives::RelayChainBlockNumber; -pub use xcm::v3::prelude::{ - Ancestor, MultiAssets, MultiLocation, Parachain as ParachainJunction, Parent, WeightLimit, - XcmHash, X1, +use sp_core::crypto::AccountId32; +pub use xcm::{ + prelude::{AccountId32 as AccountId32Junction, Here}, + v3::prelude::{ + Ancestor, MultiAssets, MultiLocation, Parachain as ParachainJunction, Parent, WeightLimit, + XcmHash, X1, + }, }; pub use xcm_executor::traits::ConvertLocation; @@ -245,7 +249,7 @@ pub trait Parachain: Chain { type LocationToAccountId: ConvertLocation>; type ParachainInfo: Get; type ParachainSystem; - type MessageProcessor: ProcessMessage + ServiceQueues; + type MessageProcessor: ProcessMessage + ServiceQueues; fn init(); @@ -576,7 +580,7 @@ macro_rules! decl_test_parachains { XcmpMessageHandler: $xcmp_message_handler:path, LocationToAccountId: $location_to_account:path, ParachainInfo: $parachain_info:path, - // MessageProcessor: $message_processor:path, + MessageOrigin: $message_origin:path, }, pallets = { $($pallet_name:ident: $pallet_path:path,)* @@ -615,7 +619,7 @@ macro_rules! decl_test_parachains { type LocationToAccountId = $location_to_account; type ParachainSystem = $crate::ParachainSystemPallet<::Runtime>; type ParachainInfo = $parachain_info; - type MessageProcessor = $crate::DefaultParaMessageProcessor<$name>; + type MessageProcessor = $crate::DefaultParaMessageProcessor<$name, $message_origin>; // We run an empty block during initialisation to open HRMP channels // and have them ready for the next block @@ -1007,7 +1011,7 @@ macro_rules! decl_test_networks { <$parachain>::ext_wrapper(|| { let _ = <$parachain as Parachain>::MessageProcessor::process_message( &msg[..], - $crate::CumulusAggregateMessageOrigin::Parent, + $crate::CumulusAggregateMessageOrigin::Parent.into(), &mut weight_meter, &mut msg.using_encoded($crate::blake2_256), ); @@ -1313,17 +1317,23 @@ macro_rules! decl_test_sender_receiver_accounts_parameter_types { }; } -pub struct DefaultParaMessageProcessor(PhantomData); +pub struct DefaultParaMessageProcessor(PhantomData<(T, M)>); // Process HRMP messages from sibling paraids -impl ProcessMessage for DefaultParaMessageProcessor +impl ProcessMessage for DefaultParaMessageProcessor where + M: codec::FullCodec + + MaxEncodedLen + + Clone + + Eq + + PartialEq + + frame_support::pallet_prelude::TypeInfo + + Debug, T: Parachain, T::Runtime: MessageQueueConfig, - <::MessageProcessor as ProcessMessage>::Origin: - PartialEq, - MessageQueuePallet: EnqueueMessage + ServiceQueues, + <::MessageProcessor as ProcessMessage>::Origin: PartialEq, + MessageQueuePallet: EnqueueMessage + ServiceQueues, { - type Origin = CumulusAggregateMessageOrigin; + type Origin = M; fn process_message( msg: &[u8], @@ -1340,13 +1350,13 @@ where Ok(true) } } -impl ServiceQueues for DefaultParaMessageProcessor +impl ServiceQueues for DefaultParaMessageProcessor where + M: MaxEncodedLen, T: Parachain, T::Runtime: MessageQueueConfig, - <::MessageProcessor as ProcessMessage>::Origin: - PartialEq, - MessageQueuePallet: EnqueueMessage + ServiceQueues, + <::MessageProcessor as ProcessMessage>::Origin: PartialEq, + MessageQueuePallet: EnqueueMessage + ServiceQueues, { type OverweightMessageAddress = (); @@ -1431,6 +1441,41 @@ pub struct TestArgs { pub weight_limit: WeightLimit, } +impl TestArgs { + /// Returns a [`TestArgs`] instance to be used for the Relay Chain across integration tests. + pub fn new_relay(dest: MultiLocation, beneficiary_id: AccountId32, amount: Balance) -> Self { + Self { + dest, + beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), + amount, + assets: (Here, amount).into(), + asset_id: None, + fee_asset_item: 0, + weight_limit: WeightLimit::Unlimited, + } + } + + /// Returns a [`TestArgs`] instance to be used for parachains across integration tests. + pub fn new_para( + dest: MultiLocation, + beneficiary_id: AccountId32, + amount: Balance, + assets: MultiAssets, + asset_id: Option, + fee_asset_item: u32, + ) -> Self { + Self { + dest, + beneficiary: AccountId32Junction { network: None, id: beneficiary_id.into() }.into(), + amount, + assets, + asset_id, + fee_asset_item, + weight_limit: WeightLimit::Unlimited, + } + } +} + /// Auxiliar struct to help creating a new `Test` instance pub struct TestContext { pub sender: AccountIdOf, diff --git a/docs/contributor/CONTRIBUTING.md b/docs/contributor/CONTRIBUTING.md index 3350d1344149526f6d285bc21ccc47b0f638c7a0..96dc86e9780561e33e24fbc6f0346572d26598b7 100644 --- a/docs/contributor/CONTRIBUTING.md +++ b/docs/contributor/CONTRIBUTING.md @@ -152,3 +152,9 @@ We use [zepter](https://github.com/ggwpez/zepter) to enforce features are propag If you're member of **paritytech** org - you can use command-bot to run various of common commands in CI: Start with comment in PR: `bot help` to see the list of available commands. + + +## Deprecating code + +When deprecating and removing code you need to be mindful of how this could impact downstream developers. In order to +mitigate this impact, it is recommended to adhere to the steps outlined in the [Deprecation Checklist](./DEPRECATION_CHECKLIST.md). diff --git a/docs/contributor/DEPRECATION_CHECKLIST.md b/docs/contributor/DEPRECATION_CHECKLIST.md index ffb99e1ec3f7a5d92283f0e3a9b9edd5d4d7b9b2..687c0a7cd7da040f40c120d00d75069db76ba27e 100644 --- a/docs/contributor/DEPRECATION_CHECKLIST.md +++ b/docs/contributor/DEPRECATION_CHECKLIST.md @@ -1,9 +1,7 @@ # Deprecation Checklist -This deprecation checklist makes sense while we don’t use [SemVer](https://semver.org/). -After that, this document will most likely change. -As deprecation and removal of existing code can happen on any release, we need to be mindful that external builders -could be impacted by the changes we make. +Polkadot SDK is under constant development and improvement, thus deprecation and removal of existing code happen often. +When creating a breaking change we need to be mindful that external builders could be impacted by this. The deprecation checklist tries to mitigate this impact, while still keeping the developer experience, the DevEx, as smooth as possible. diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index f5bfd80fd10d3db93d0d0376f0db24ca31bfd0b4..246da2cd68c6e386bbacf039768767aae70d26dd 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -10,6 +10,9 @@ edition.workspace = true publish = false version = "0.0.1" +[lints] +workspace = true + [dependencies] # Needed for all FRAME-based code parity-scale-codec = { version = "3.0.0", default-features = false } @@ -19,10 +22,10 @@ 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", branch = "main" } +simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b" } docify = "0.2.6" -# Polkadot SDK deps, typically all should only be scope such that we can link to their doc item. +# Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. node-cli = { package = "staging-node-cli", path = "../../substrate/bin/node/cli" } kitchensink-runtime = { path = "../../substrate/bin/node/runtime" } chain-spec-builder = { package = "staging-chain-spec-builder", path = "../../substrate/bin/utils/chain-spec-builder" } diff --git a/docs/sdk/src/guides/mod.rs b/docs/sdk/src/guides/mod.rs index 68ca9317b907e8bab74ad55e0bfb391c103251f6..3120f25331099263087d3804d324e71555c0813d 100644 --- a/docs/sdk/src/guides/mod.rs +++ b/docs/sdk/src/guides/mod.rs @@ -1,7 +1,7 @@ //! # Polkadot SDK Docs Guides //! //! This crate contains a collection of guides that are foundational to the developers of -//! Polkadot SDK. They common user-journeys that are traversed in the Polkadot ecosystem. +//! Polkadot SDK. They are common user-journeys that are traversed in the Polkadot ecosystem. /// Write your first simple pallet, learning the most most basic features of FRAME along the way. pub mod your_first_pallet; diff --git a/docs/sdk/src/guides/your_first_pallet/mod.rs b/docs/sdk/src/guides/your_first_pallet/mod.rs index 855b390aba9d4a54220c9a1ef13567a39b4d0cd9..c886bc9af842d831f7a0869c998242525068c35c 100644 --- a/docs/sdk/src/guides/your_first_pallet/mod.rs +++ b/docs/sdk/src/guides/your_first_pallet/mod.rs @@ -12,12 +12,11 @@ //! > This guide will build a currency pallet from scratch using only the lowest primitives of //! > FRAME, and is mainly intended for education, not *applicability*. For example, almost all //! > FRAME-based runtimes use various techniques to re-use a currency pallet instead of writing -//! > one. Further advance FRAME related topics are discussed in [`crate::reference_docs`]. +//! > one. Further advanced FRAME related topics are discussed in [`crate::reference_docs`]. //! //! ## Topics Covered //! -//! The following FRAME topics are covered in this guide. See the documentation of the -//! associated items to know more. +//! The following FRAME topics are covered in this guide: //! //! - [Storage](frame::pallet_macros::storage) //! - [Call](frame::pallet_macros::call) @@ -50,8 +49,10 @@ //! //! One should be a mapping from account-ids to a balance type, and one value that is the total //! issuance. -// -// For the rest of this guide, we will opt for a balance type of u128. +//! +//! > For the rest of this guide, we will opt for a balance type of `u128`. For the sake of +//! > simplicity, we are hardcoding this type. In a real pallet is best practice to define it as a +//! > generic bounded type in the `Config` trait, and then specify it in the implementation. #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", Balance)] //! //! The definition of these two storage items, based on [`frame::pallet_macros::storage`] details, @@ -160,13 +161,12 @@ #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", first_test)] //! //! In the first test, we simply assert that there is no total issuance, and no balance associated -//! with account `1`. Then, we mint some balance into `1`, and re-check. +//! with Alice's account. Then, we mint some balance into Alice's, and re-check. //! //! As noted above, the `T::AccountId` is now `u64`. Moreover, `Runtime` is replacing ``. //! This is why for example you see `Balances::::get(..)`. Finally, notice that the //! dispatchables are simply functions that can be called on top of the `Pallet` struct. -//! -//! TODO: hard to explain exactly `RuntimeOrigin::signed(1)` at this point. +// TODO: hard to explain exactly `RuntimeOrigin::signed(ALICE)` at this point. //! //! Congratulations! You have written your first pallet and tested it! Next, we learn a few optional //! steps to improve our pallet. @@ -186,8 +186,8 @@ #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", StateBuilder)] //! //! This struct is meant to contain the same list of accounts and balances that we want to have at -//! the beginning of each block. We hardcoded this to `let accounts = vec![(1, 100), (2, 100)];` so -//! far. Then, if desired, we attach a default value for this struct. +//! the beginning of each block. We hardcoded this to `let accounts = vec![(ALICE, 100), (2, 100)];` +//! so far. Then, if desired, we attach a default value for this struct. #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", default_state_builder)] //! //! Like any other builder pattern, we attach functions to the type to mutate its internal @@ -222,7 +222,7 @@ //! "success path" of a dispatchable, and one test for each "failure path", such as: #![doc = docify::embed!("./src/guides/your_first_pallet/mod.rs", transfer_from_non_existent_fails)] //! -//! We leave it up to you to write a test that triggers to `InsufficientBalance` error. +//! We leave it up to you to write a test that triggers the `InsufficientBalance` error. //! //! ### Event and Error //! @@ -230,21 +230,24 @@ //! Errors. First, let's understand what each is. //! //! - **Error**: The static string-based error scheme we used so far is good for readability, but it -//! has a few drawbacks. These string literals will bloat the final wasm blob, and are relatively -//! heavy to transmit and encode/decode. Moreover, it is easy to mistype them by one character. -//! FRAME errors are exactly a solution to maintain readability, whilst fixing the drawbacks -//! mentioned. In short, we use an enum to represent different variants of our error. These -//! variants are then mapped in an efficient way (using only `u8` indices) to -//! [`sp_runtime::DispatchError::Module`] Read more about this in [`frame::pallet_macros::error`]. -//! -//! - **Event**: Events are akin to the return type of dispatchables. They should represent what -//! happened at the end of a dispatch operation. Therefore, the convention is to use passive tense -//! for event names (eg. `SomethingHappened`). This allows other sub-systems or external parties -//! (eg. a light-node, a DApp) to listen to particular events happening, without needing to -//! re-execute the whole state transition function. -//! -//! TODO: both need to be improved a lot at the pallet-macro rust-doc level. Also my explanation -//! of event is probably not the best. +//! has a few drawbacks. The biggest problem with strings are that they are not type safe, e.g. a +//! match statement cannot be exhaustive. These string literals will bloat the final wasm blob, +//! and are relatively heavy to transmit and encode/decode. Moreover, it is easy to mistype them +//! by one character. FRAME errors are exactly a solution to maintain readability, whilst fixing +//! the drawbacks mentioned. In short, we use an enum to represent different variants of our +//! error. These variants are then mapped in an efficient way (using only `u8` indices) to +//! [`sp_runtime::DispatchError::Module`]. Read more about this in +//! [`frame::pallet_macros::error`]. +//! +//! - **Event**: Events are akin to the return type of dispatchables. They are mostly data blobs +//! emitted by the runtime to let outside world know what is happening inside the pallet. Since +//! otherwise, the outside world does not have an easy access to the state changes. They should +//! represent what happened at the end of a dispatch operation. Therefore, the convention is to +//! use passive tense for event names (eg. `SomethingHappened`). This allows other sub-systems or +//! external parties (eg. a light-node, a DApp) to listen to particular events happening, without +//! needing to re-execute the whole state transition function. +// TODO: both need to be improved a lot at the pallet-macro rust-doc level. Also my explanation +// of event is probably not the best. //! //! With the explanation out of the way, let's see how these components can be added. Both follow a //! fairly familiar syntax: normal Rust enums, with an extra `#[frame::event/error]` attribute @@ -413,6 +416,9 @@ pub mod pallet { pub(crate) mod tests { use crate::guides::your_first_pallet::pallet::*; use frame::testing_prelude::*; + const ALICE: u64 = 1; + const BOB: u64 = 2; + const CHARLIE: u64 = 3; #[docify::export] mod runtime { @@ -447,7 +453,7 @@ pub mod pallet { #[docify::export] fn new_test_state_basic() -> TestState { let mut state = TestState::new_empty(); - let accounts = vec![(1, 100), (2, 100)]; + let accounts = vec![(ALICE, 100), (BOB, 100)]; state.execute_with(|| { for (who, amount) in &accounts { Balances::::insert(who, amount); @@ -466,7 +472,7 @@ pub mod pallet { #[docify::export(default_state_builder)] impl Default for StateBuilder { fn default() -> Self { - Self { balances: vec![(1, 100), (2, 100)] } + Self { balances: vec![(ALICE, 100), (BOB, 100)] } } } @@ -509,15 +515,19 @@ pub mod pallet { #[test] fn first_test() { TestState::new_empty().execute_with(|| { - // We expect account 1 to have no funds. - assert_eq!(Balances::::get(&1), None); + // We expect Alice's account to have no funds. + assert_eq!(Balances::::get(&ALICE), None); assert_eq!(TotalIssuance::::get(), None); - // mint some funds into 1 - assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(1), 1, 100)); + // mint some funds into Alice's account. + assert_ok!(Pallet::::mint_unsafe( + RuntimeOrigin::signed(ALICE), + ALICE, + 100 + )); // re-check the above - assert_eq!(Balances::::get(&1), Some(100)); + assert_eq!(Balances::::get(&ALICE), Some(100)); assert_eq!(TotalIssuance::::get(), Some(100)); }) } @@ -526,9 +536,9 @@ pub mod pallet { #[test] fn state_builder_works() { StateBuilder::default().build_and_execute(|| { - assert_eq!(Balances::::get(&1), Some(100)); - assert_eq!(Balances::::get(&2), Some(100)); - assert_eq!(Balances::::get(&3), None); + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(200)); }); } @@ -536,8 +546,8 @@ pub mod pallet { #[docify::export] #[test] fn state_builder_add_balance() { - StateBuilder::default().add_balance(3, 42).build_and_execute(|| { - assert_eq!(Balances::::get(&3), Some(42)); + StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| { + assert_eq!(Balances::::get(&CHARLIE), Some(42)); assert_eq!(TotalIssuance::::get(), Some(242)); }) } @@ -546,10 +556,10 @@ pub mod pallet { #[should_panic] fn state_builder_duplicate_genesis_fails() { StateBuilder::default() - .add_balance(3, 42) - .add_balance(3, 43) + .add_balance(CHARLIE, 42) + .add_balance(CHARLIE, 43) .build_and_execute(|| { - assert_eq!(Balances::::get(&3), None); + assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(242)); }) } @@ -559,17 +569,21 @@ pub mod pallet { fn mint_works() { StateBuilder::default().build_and_execute(|| { // given the initial state, when: - assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(1), 2, 100)); + assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100)); // then: - assert_eq!(Balances::::get(&2), Some(200)); + assert_eq!(Balances::::get(&BOB), Some(200)); assert_eq!(TotalIssuance::::get(), Some(300)); // given: - assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(1), 3, 100)); + assert_ok!(Pallet::::mint_unsafe( + RuntimeOrigin::signed(ALICE), + CHARLIE, + 100 + )); // then: - assert_eq!(Balances::::get(&3), Some(100)); + assert_eq!(Balances::::get(&CHARLIE), Some(100)); assert_eq!(TotalIssuance::::get(), Some(400)); }); } @@ -579,19 +593,19 @@ pub mod pallet { fn transfer_works() { StateBuilder::default().build_and_execute(|| { // given the the initial state, when: - assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(1), 2, 50)); + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); // then: - assert_eq!(Balances::::get(&1), Some(50)); - assert_eq!(Balances::::get(&2), Some(150)); + assert_eq!(Balances::::get(&ALICE), Some(50)); + assert_eq!(Balances::::get(&BOB), Some(150)); assert_eq!(TotalIssuance::::get(), Some(200)); // when: - assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(2), 1, 50)); + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(BOB), ALICE, 50)); // then: - assert_eq!(Balances::::get(&1), Some(100)); - assert_eq!(Balances::::get(&2), Some(100)); + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); assert_eq!(TotalIssuance::::get(), Some(200)); }); } @@ -602,14 +616,14 @@ pub mod pallet { StateBuilder::default().build_and_execute(|| { // given the the initial state, when: assert_err!( - Pallet::::transfer(RuntimeOrigin::signed(3), 1, 10), + Pallet::::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10), "NonExistentAccount" ); // then nothing has changed. - assert_eq!(Balances::::get(&1), Some(100)); - assert_eq!(Balances::::get(&2), Some(100)); - assert_eq!(Balances::::get(&3), None); + assert_eq!(Balances::::get(&ALICE), Some(100)); + assert_eq!(Balances::::get(&BOB), Some(100)); + assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(200)); }); } @@ -685,6 +699,8 @@ pub mod pallet_v2 { pub mod tests { use super::{super::pallet::tests::StateBuilder, *}; use frame::testing_prelude::*; + const ALICE: u64 = 1; + const BOB: u64 = 2; #[docify::export] pub mod runtime_v2 { @@ -717,20 +733,20 @@ pub mod pallet_v2 { StateBuilder::default().build_and_execute(|| { // skip the genesis block, as events are not deposited there and we need them for // the final assertion. - System::set_block_number(1); + System::set_block_number(ALICE); // given the the initial state, when: - assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(1), 2, 50)); + assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); // then: - assert_eq!(Balances::::get(&1), Some(50)); - assert_eq!(Balances::::get(&2), Some(150)); + assert_eq!(Balances::::get(&ALICE), Some(50)); + assert_eq!(Balances::::get(&BOB), Some(150)); assert_eq!(TotalIssuance::::get(), Some(200)); // now we can also check that an event has been deposited: assert_eq!( System::read_events_for_pallet::>(), - vec![Event::Transferred { from: 1, to: 2, amount: 50 }] + vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }] ); }); } diff --git a/docs/sdk/src/lib.rs b/docs/sdk/src/lib.rs index b0abb50b52dae32a8e8b19f4248035b591c908c4..075d9ddaffe5bd95bafa4ca1d06667c253d4a21b 100644 --- a/docs/sdk/src/lib.rs +++ b/docs/sdk/src/lib.rs @@ -23,8 +23,6 @@ //! //! This section paints a picture over the high-level information architecture of this crate. #![doc = simple_mermaid::mermaid!("../../mermaid/IA.mmd")] -#![allow(rustdoc::invalid_html_tags)] // TODO: remove later. https://github.com/paritytech/polkadot-sdk-docs/issues/65 -#![allow(rustdoc::bare_urls)] // TODO: remove later. https://github.com/paritytech/polkadot-sdk-docs/issues/65 #![warn(rustdoc::broken_intra_doc_links)] #![warn(rustdoc::private_intra_doc_links)] diff --git a/docs/sdk/src/polkadot_sdk/polkadot.rs b/docs/sdk/src/polkadot_sdk/polkadot.rs index d157a660e5648926f9764957015bcf6c54aff356..61a6877696cb9298ed831562923d96d4a701bcc4 100644 --- a/docs/sdk/src/polkadot_sdk/polkadot.rs +++ b/docs/sdk/src/polkadot_sdk/polkadot.rs @@ -23,7 +23,7 @@ //! //! ## Platform //! -//! In this section, we examine what what platform Polkadot exactly provides to developers. +//! In this section, we examine what platform Polkadot exactly provides to developers. //! //! ### Polkadot White Paper //! @@ -47,10 +47,12 @@ //! Chain*"). //! * (heterogenous) Sharded Execution: Yet, each parachain is free to have its own execution logic //! (runtime), which also encompasses governance and sovereignty. Moreover, Polkadot ensures the -//! correct execution of all parachain, without having all of its validators re-execute all -//! parachain blocks. When seen from this perspective, the fact that Polkadot executes different -//! parachains means it is a platform that has fully delivered (the holy grail of) "Full Execution -//! Sharding". TODO: link to approval checking article. https://github.com/paritytech/polkadot-sdk-docs/issues/66 +//! correct execution of all parachains, without having all of its validators re-execute all +//! parachain blocks. When seen from this perspective, Polkadot achieves the ability to verify +//! the validity of the block execution of multiple parachains using the same set of validators as +//! the Relay Chain. In practice, this means that the shards (parachains) share the same economic +//! security as the Relay Chain. +//! Learn about this process called [Approval Checking](https://polkadot.network/blog/polkadot-v1-0-sharding-and-economic-security#approval-checking-and-finality). //! * A framework to build blockchains: In order to materialize the ecosystem of parachains, an easy //! blockchain framework must exist. This is [Substrate](crate::polkadot_sdk::substrate), //! [FRAME](crate::polkadot_sdk::frame_runtime) and [Cumulus](crate::polkadot_sdk::cumulus). @@ -60,7 +62,12 @@ //! //! > Note that the interoperability promised by Polkadot is unparalleled in that any two parachains //! > connected to Polkadot have the same security and can have much better guarantees about the -//! > security of the recipient of any message. TODO: weakest link in bridges systems. https://github.com/paritytech/polkadot-sdk-docs/issues/66 +//! > security of the recipient of any message. +//! > Bridges enable transaction and information flow between different consensus systems, crucial +//! > for Polkadot's multi-chain architecture. However, they can become the network's most +//! > vulnerable points. If a bridge's security measures are weaker than those of the connected +//! > blockchains, it poses a significant risk. Attackers might exploit these weaknesses to launch +//! > attacks such as theft or disruption of services. //! //! Polkadot delivers the above vision, alongside a flexible means for parachains to schedule //! themselves with the Relay Chain. To achieve this, Polkadot has been developed with an @@ -83,5 +90,5 @@ //! Agile periodic-sale-based model for assigning Coretime on the Polkadot Ubiquitous Computer. //! - RFC#5: [Coretime-interface](https://github.com/polkadot-fellows/RFCs/blob/main/text/0005-coretime-interface.md): //! Interface for manipulating the usage of cores on the Polkadot Ubiquitous Computer. -// TODO: add more context and explanations about Polkadot as the Ubiquitous Computer and related -// tech. https://github.com/paritytech/polkadot-sdk-docs/issues/66 +//! +//! Learn more about [Polkadot as a Computational Resource](https://wiki.polkadot.network/docs/polkadot-direction#polkadot-as-a-computational-resource). diff --git a/docs/sdk/src/polkadot_sdk/smart_contracts.rs b/docs/sdk/src/polkadot_sdk/smart_contracts.rs index a4916f9c9218007111b0ec32d677cdc4e4e7e867..4052c785f417b83ce5f7149aea38c2f72689a442 100644 --- a/docs/sdk/src/polkadot_sdk/smart_contracts.rs +++ b/docs/sdk/src/polkadot_sdk/smart_contracts.rs @@ -1,9 +1,9 @@ //! # Smart Contracts //! -//! TODO: @cmichi https://github.com/paritytech/polkadot-sdk-docs/issues/56 +//! TODO: @cmichi //! //! - WASM and EVM based, pallet-contracts and pallet-evm. //! - single-daap-chain, transition from ink! to FRAME. //! - Link to `use.ink` //! - Link to [`crate::reference_docs::runtime_vs_smart_contract`]. -//! - https://use.ink/migrate-ink-contracts-to-polkadot-frame-parachain/ +//! - diff --git a/docs/sdk/src/polkadot_sdk/substrate.rs b/docs/sdk/src/polkadot_sdk/substrate.rs index fd172f71469fc5fa42607af078da524c79099f9b..5021c55e581f3d9fcd06f8578998ab60db0abb71 100644 --- a/docs/sdk/src/polkadot_sdk/substrate.rs +++ b/docs/sdk/src/polkadot_sdk/substrate.rs @@ -143,7 +143,7 @@ //! - [`sc_consensus_aura`] //! - [`sc_consensus_babe`] //! - [`sc_consensus_grandpa`] -//! - [`sc_consensus_beefy`] (TODO: @adrian, add some high level docs https://github.com/paritytech/polkadot-sdk-docs/issues/57) +//! - [`sc_consensus_beefy`] (TODO: @adrian, add some high level docs ) //! - [`sc_consensus_manual_seal`] //! - [`sc_consensus_pow`] diff --git a/docs/sdk/src/polkadot_sdk/xcm.rs b/docs/sdk/src/polkadot_sdk/xcm.rs index 0d600f751c8b1e2ca74fdac9d4d47331fb0233c1..fd4d7f62aa702f5808552936404995ad67efaed9 100644 --- a/docs/sdk/src/polkadot_sdk/xcm.rs +++ b/docs/sdk/src/polkadot_sdk/xcm.rs @@ -2,4 +2,4 @@ //! //! @KiChjang @franciscoaguirre //! TODO: RFCs, xcm-spec, the future of the repo, minimal example perhaps, forward to where actual -//! docs are hosted. https://github.com/paritytech/polkadot-sdk-docs/issues/58 +//! docs are hosted. diff --git a/docs/sdk/src/reference_docs/cli.rs b/docs/sdk/src/reference_docs/cli.rs index 9274e86b04eff78daa152d5207dbb3a7075f26b3..5779e0f8d04954e9fe1d245e0af4761fb0246805 100644 --- a/docs/sdk/src/reference_docs/cli.rs +++ b/docs/sdk/src/reference_docs/cli.rs @@ -1,7 +1,104 @@ -//! # Command Line Arguments +//! # Substrate CLI //! +//! Let's see some examples of typical CLI arguments used when setting up and running a +//! Substrate-based blockchain. We use the [`substrate-node-template`](https://github.com/substrate-developer-hub/substrate-node-template) +//! on these examples. //! -//! Notes: +//! #### Checking the available CLI arguments +//! ```bash +//! ./target/debug/node-template --help +//! ``` +//! - `--help`: Displays the available CLI arguments. //! -//! - Command line arguments of a typical substrate based chain, and how to find and learn them. -//! - How to extend them with your custom stuff. +//! #### Starting a Local Substrate Node in Development Mode +//! ```bash +//! ./target/release/node-template \ +//! --dev +//! ``` +//! - `--dev`: Runs the node in development mode, using a pre-defined development chain +//! specification. +//! This mode ensures a fresh state by deleting existing data on restart. +//! +//! #### Generating Custom Chain Specification +//! ```bash +//! ./target/debug/node-template \ +//! build-spec \ +//! --disable-default-bootnode \ +//! --chain local \ +//! > customSpec.json +//! ``` +//! +//! - `build-spec`: A subcommand to generate a chain specification file. +//! - `--disable-default-bootnode`: Disables the default bootnodes in the node template. +//! - `--chain local`: Indicates the chain specification is for a local development chain. +//! - `> customSpec.json`: Redirects the output into a customSpec.json file. +//! +//! #### Converting Chain Specification to Raw Format +//! ```bash +//! ./target/debug/node-template build-spec \ +//! --chain=customSpec.json \ +//! --raw \ +//! --disable-default-bootnode \ +//! > customSpecRaw.json +//! ``` +//! +//! - `--chain=customSpec.json`: Uses the custom chain specification as input. +//! - `--disable-default-bootnode`: Disables the default bootnodes in the node template. +//! - `--raw`: Converts the chain specification into a raw format with encoded storage keys. +//! - `> customSpecRaw.json`: Outputs to customSpecRaw.json. +//! +//! #### Starting the First Node in a Private Network +//! ```bash +//! ./target/debug/node-template \ +//! --base-path /tmp/node01 \ +//! --chain ./customSpecRaw.json \ +//! --port 30333 \ +//! --ws-port 9945 \ +//! --rpc-port 9933 \ +//! --telemetry-url "wss://telemetry.polkadot.io/submit/ 0" \ +//! --validator \ +//! --rpc-methods Unsafe \ +//! --name MyNode01 +//! ``` +//! +//! - `--base-path`: Sets the directory for node data. +//! - `--chain`: Specifies the chain specification file. +//! - `--port`: TCP port for peer-to-peer communication. +//! - `--ws-port`: WebSocket port for RPC. +//! - `--rpc-port`: HTTP port for JSON-RPC. +//! - `--telemetry-url`: Endpoint for sending telemetry data. +//! - `--validator`: Indicates the node’s participation in block production. +//! - `--rpc-methods Unsafe`: Allows potentially unsafe RPC methods. +//! - `--name`: Sets a human-readable name for the node. +//! +//! #### Adding a Second Node to the Network +//! ```bash +//! ./target/release/node-template \ +//! --base-path /tmp/bob \ +//! --chain local \ +//! --bob \ +//! --port 30334 \ +//! --rpc-port 9946 \ +//! --telemetry-url "wss://telemetry.polkadot.io/submit/ 0" \ +//! --validator \ +//! --bootnodes /ip4/127.0.0.1/tcp/30333/p2p/12D3KooWEyoppNCUx8Yx66oV9fJnriXwCcXwDDUA2kj6vnc6iDEp +//! ``` +//! +//! - `--base-path`: Sets the directory for node data. +//! - `--chain`: Specifies the chain specification file. +//! - `--bob`: Initializes the node with the session keys of the "Bob" account. +//! - `--port`: TCP port for peer-to-peer communication. +//! - `--rpc-port`: HTTP port for JSON-RPC. +//! - `--telemetry-url`: Endpoint for sending telemetry data. +//! - `--validator`: Indicates the node’s participation in block production. +//! - `--bootnodes`: Specifies the address of the first node for peer discovery. Nodes should find +//! each other using mDNS. This command needs to be used if they don't find each other. +//! +//! --- +//! +//! > If you are interested in learning how to extend the CLI with your custom arguments, you can +//! > check out the [Customize your Substrate chain CLI](https://www.youtube.com/watch?v=IVifko1fqjw) +//! > seminar. +//! > Please note that the seminar is based on an older version of Substrate, and [Clap](https://docs.rs/clap/latest/clap/) +//! > is now used instead of [StructOpt](https://docs.rs/structopt/latest/structopt/) for parsing +//! > CLI arguments. diff --git a/docs/sdk/src/reference_docs/development_environment_advice.rs b/docs/sdk/src/reference_docs/development_environment_advice.rs new file mode 100644 index 0000000000000000000000000000000000000000..27dd463860476b80238f0857213de80a40b9c84b --- /dev/null +++ b/docs/sdk/src/reference_docs/development_environment_advice.rs @@ -0,0 +1,113 @@ +//! # Development Environment Advice +//! +//! Large Rust projects are known for sometimes long compile times and sluggish dev tooling, and +//! polkadot-sdk is no exception. +//! +//! This page contains some advice to improve your workflow when using common tooling. +//! +//! ## Rust Analyzer Configuration +//! +//! [Rust Analyzer](https://rust-analyzer.github.io/) is the defacto [LSP](https://langserver.org/) for Rust. Its default +//! settings are fine for smaller projects, but not well optimised for polkadot-sdk. +//! +//! Below is a suggested configuration for VSCode: +//! +//! ```json +//! { +//! // Use a separate target dir for Rust Analyzer. Helpful if you want to use Rust +//! // Analyzer and cargo on the command line at the same time. +//! "rust-analyzer.rust.analyzerTargetDir": "target/vscode-rust-analyzer", +//! // Improve stability +//! "rust-analyzer.server.extraEnv": { +//! "CHALK_OVERFLOW_DEPTH": "100000000", +//! "CHALK_SOLVER_MAX_SIZE": "10000000" +//! }, +//! // Check feature-gated code +//! "rust-analyzer.cargo.features": "all", +//! "rust-analyzer.cargo.extraEnv": { +//! // Skip building WASM, there is never need for it here +//! "SKIP_WASM_BUILD": "1" +//! }, +//! // Don't expand some problematic proc_macros +//! "rust-analyzer.procMacro.ignored": { +//! "async-trait": ["async_trait"], +//! "napi-derive": ["napi"], +//! "async-recursion": ["async_recursion"], +//! "async-std": ["async_std"] +//! }, +//! // Use nightly formatting. +//! // See the polkadot-sdk CI job that checks formatting for the current version used in +//! // polkadot-sdk. +//! "rust-analyzer.rustfmt.extraArgs": ["+nightly-2023-11-01"], +//! } +//! ``` +//! +//! and the same in Lua for `neovim/nvim-lspconfig`: +//! +//! ```lua +//! ["rust-analyzer"] = { +//! rust = { +//! # Use a separate target dir for Rust Analyzer. Helpful if you want to use Rust +//! # Analyzer and cargo on the command line at the same time. +//! analyzerTargetDir = "target/nvim-rust-analyzer", +//! }, +//! server = { +//! # Improve stability +//! extraEnv = { +//! ["CHALK_OVERFLOW_DEPTH"] = "100000000", +//! ["CHALK_SOLVER_MAX_SIZE"] = "100000000", +//! }, +//! }, +//! cargo = { +//! # Check feature-gated code +//! features = "all", +//! extraEnv = { +//! # Skip building WASM, there is never need for it here +//! ["SKIP_WASM_BUILD"] = "1", +//! }, +//! }, +//! procMacro = { +//! # Don't expand some problematic proc_macros +//! ignored = { +//! ["async-trait"] = { "async_trait" }, +//! ["napi-derive"] = { "napi" }, +//! ["async-recursion"] = { "async_recursion" }, +//! ["async-std"] = { "async_std" }, +//! }, +//! }, +//! rustfmt = { +//! # Use nightly formatting. +//! # See the polkadot-sdk CI job that checks formatting for the current version used in +//! # polkadot-sdk. +//! extraArgs = { "+nightly-2023-11-01" }, +//! }, +//! }, +//! ``` +//! +//! For the full set of configuration options see . +//! +//! ## Cargo Usage +//! +//! ### Using `--package` (a.k.a. `-p`) +//! +//! polkadot-sdk is a monorepo containing many crates. When you run a cargo command without +//! `-p`, you will almost certainly compile crates outside of the scope you are working. +//! +//! Instead, you should identify the name of the crate you are working on by checking the `name` +//! field in the closest `Cargo.toml` file. Then, use `-p` with your cargo commands to only compile +//! that crate. +//! +//! ### `SKIP_WASM_BUILD=1` environment variable +//! +//! When cargo touches a runtime crate, by default it will also compile the WASM binary, +//! approximately doubling the compilation time. +//! +//! The WASM binary is usually not needed, especially when running `check` or `test`. To skip the +//! WASM build, set the `SKIP_WASM_BUILD` environment variable to `1`. For example: +//! `SKIP_WASM_BUILD=1 cargo check -p frame-support`. +//! +//! ### Cargo Remote +//! +//! If you have a powerful remote server available, you may consider using +//! [cargo-remote](https://github.com/sgeisler/cargo-remote) to execute cargo commands on it, +//! freeing up local resources for other tasks like `rust-analyzer`. diff --git a/docs/sdk/src/reference_docs/fee_less_runtime.rs b/docs/sdk/src/reference_docs/fee_less_runtime.rs index 43a761a6c52cc6332fa38fef154ac3e12c846aa9..1213c26282537fdee2d04ebd452289f0e6bc759f 100644 --- a/docs/sdk/src/reference_docs/fee_less_runtime.rs +++ b/docs/sdk/src/reference_docs/fee_less_runtime.rs @@ -9,4 +9,4 @@ //! and some kind of rate limiting (eg. any account gets 5 free tx per day). //! - The rule of thumb is that as long as the unsigned validate does one storage read, similar to //! nonce, it is fine. -//! - This could possibly be a good tutorial/template, rather than a reference doc. +//! - This could possibly be a good guide/template, rather than a reference doc. diff --git a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs index f65f4174ec66265a6f1ad1b52099a235d7ed9bfa..7ebad37ecad8491c4b3024b54030a8a3081df5b1 100644 --- a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs +++ b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs @@ -20,4 +20,4 @@ //! - how to write benchmarks, how you must think of worst case. //! - how to run benchmarks. //! -//! - https://www.shawntabrizi.com/substrate/substrate-storage-deep-dive/ +//! - diff --git a/docs/sdk/src/reference_docs/frame_currency.rs b/docs/sdk/src/reference_docs/frame_currency.rs index ba181373062f05e4645931739f60630ae00109f6..6987d51aec824d370fb4706ae7d5b942a2667a64 100644 --- a/docs/sdk/src/reference_docs/frame_currency.rs +++ b/docs/sdk/src/reference_docs/frame_currency.rs @@ -5,4 +5,4 @@ //! - History, `Currency` trait. //! - `Hold` and `Freeze` with diagram. //! - `HoldReason` and `FreezeReason` -//! - This footgun: https://github.com/paritytech/polkadot-sdk/pull/1900#discussion_r1363783609 +//! - This footgun: diff --git a/docs/sdk/src/reference_docs/light_nodes.rs b/docs/sdk/src/reference_docs/light_nodes.rs index a6a0a828ef58a20593fbf55cbcce4e4310e1cabd..d6670bf03ab1a8e36ef1d8b80717d1b3833daff5 100644 --- a/docs/sdk/src/reference_docs/light_nodes.rs +++ b/docs/sdk/src/reference_docs/light_nodes.rs @@ -3,5 +3,5 @@ //! //! Notes: should contain only high level information about light clients, then link to how to set //! it up in PAPI and SubXT -//! https://docs.substrate.io/learn/light-clients-in-substrate-connect/ -//! https://github.com/substrate-developer-hub/substrate-front-end-template/pull/277 +//! +//! diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index 44284394000d3bac718a81ac95928c4d94e3dc2b..c16122ee4287b17614f5d61acc9f26df0429c2cd 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -69,6 +69,9 @@ pub mod frame_system_accounts; /// Learn about the currency-related abstractions provided in FRAME. pub mod frame_currency; +/// Advice for configuring your development environment for Substrate development. +pub mod development_environment_advice; + /// Learn about benchmarking and weight. // TODO: @shawntabrizi @ggwpez https://github.com/paritytech/polkadot-sdk-docs/issues/50 pub mod frame_benchmarking_weight; diff --git a/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs index 7f96fa1800ae34536aa7b275d874d9a3d3bee860..099512cf4ee1254b9b1e522395b9cff9783d3e3d 100644 --- a/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs +++ b/docs/sdk/src/reference_docs/runtime_vs_smart_contract.rs @@ -1,6 +1,216 @@ -//! Runtime vs. Smart Contracts +//! # Runtime vs. Smart Contracts //! -//! Notes: +//! *TL;DR*: If you need to create a *Blockchain*, then write a runtime. If you need to create a +//! *DApp*, then write a Smart Contract. //! -//! Why one can be weighed, and one MUST be metered. -//! https://forum.polkadot.network/t/where-contracts-fail-and-runtimes-chains-are-needed/4464/3 +//! This is a comparative analysis of Substrate-based Runtimes and Smart Contracts, highlighting +//! their main differences. Our aim is to equip you with a clear understanding of how these two +//! methods of deploying on-chain logic diverge in their design, usage, and implications. +//! +//! Both Runtimes and Smart Contracts serve distinct purposes. Runtimes offer deep customization for +//! blockchain development, while Smart Contracts provide a more accessible approach for +//! decentralized applications. Understanding their differences is crucial in choosing the right +//! approach for a specific solution. +//! +//! ## Substrate +//! Substrate is a modular framework that enables the creation of purpose-specific blockchains. In +//! the Polkadot ecosystem you can find two distinct approaches for on-chain code execution: +//! [Runtime Development](#runtime-in-substrate) and [Smart Contracts](#smart-contracts). +//! +//! #### Smart Contracts in Substrate +//! Smart Contracts are autonomous, programmable constructs deployed on the blockchain. +//! In [FRAME](frame), Smart Contracts infrastructure is implemented by the +//! [`pallet_contracts`](../../../pallet_contracts/index.html) for WASM-based contracts or the +//! [`pallet_evm`](../../../pallet_evm/index.html) for EVM-compatible contracts. These pallets +//! enable Smart Contract developers to build applications and systems on top of a Substrate-based +//! blockchain. +//! +//! #### Runtime in Substrate +//! The Runtime is the state transition function of a Substrate-based blockchain. It defines the +//! rules for processing transactions and blocks, essentially governing the behavior and +//! capabilities of a blockchain. +//! +//! ## Comparative Table +//! +//! | Aspect | Runtime +//! | Smart Contracts | +//! |-----------------------|-------------------------------------------------------------------------|----------------------------------------------------------------------| +//! | **Design Philosophy** | Core logic of a blockchain, allowing broad and deep customization. +//! | Designed for DApps deployed on the blockchain runtime.| | **Development Complexity** | Requires in-depth knowledge of Rust and Substrate. Suitable for complex blockchain architectures. | Easier to develop with knowledge of Smart Contract languages like Solidity or [ink!](https://use.ink/). | +//! | **Upgradeability and Flexibility** | Offers comprehensive upgradeability with migration logic +//! and on-chain governance, allowing modifications to the entire blockchain logic without hard +//! forks. | Less flexible in upgrade migrations but offers more straightforward deployment and +//! iteration. | | **Performance and Efficiency** | More efficient, optimized for specific needs of +//! the blockchain. | Can be less efficient due to its generic nature (e.g. the overhead of a +//! virtual machine). | | **Security Considerations** | Security flaws can affect the entire +//! blockchain. | Security risks usually localized to the individual +//! contract. | | **Weighing and Metering** | Operations can be weighed, allowing for precise +//! benchmarking. | Execution is metered, allowing for measurement of resource +//! consumption. | +//! +//! We will now explore these differences in more detail. +//! +//! ## Design Philosophy +//! Runtimes and Smart Contracts are designed for different purposes. Runtimes are the core logic +//! of a blockchain, while Smart Contracts are designed for DApps on top of the blockchain. +//! Runtimes can be more complex, but also more flexible and efficient, while Smart Contracts are +//! easier to develop and deploy. +//! +//! #### Runtime Design Philosophy +//! - **Core Blockchain Logic**: Runtimes are essentially the backbone of a blockchain. They define +//! the fundamental rules, operations, and state transitions of the blockchain network. +//! - **Broad and Deep Customization**: Runtimes allow for extensive customization and flexibility. +//! Developers can tailor the most fundamental aspects of the blockchain, like introducing an +//! efficient transaction fee model to eliminating transaction fees completely. This level of +//! control is essential for creating specialized or application-specific blockchains. +//! +//! #### Smart Contract Design Philosophy +//! - **DApps Development**: Smart contracts are designed primarily for developing DApps. They +//! operate on top of the blockchain's infrastructure. +//! - **Modularity and Isolation**: Smart contracts offer a more modular approach. Each contract is +//! an isolated piece of code, executing predefined operations when triggered. This isolation +//! simplifies development and enhances security, as flaws in one contract do not directly +//! compromise the entire network. +//! +//! ## Development Complexity +//! Runtimes and Smart Contracts differ in their development complexity, largely due to their +//! differing purposes and technical requirements. +//! +//! #### Runtime Development Complexity +//! - **In-depth Knowledge Requirements**: Developing a Runtime in Substrate requires a +//! comprehensive understanding of Rust, Substrate's framework, and blockchain principles. +//! - **Complex Blockchain Architectures**: Runtime development is suitable for creating complex +//! blockchain architectures. Developers must consider aspects like security, scalability, and +//! network efficiency. +//! +//! #### Smart Contract Development Complexity +//! - **Accessibility**: Smart Contract development is generally more accessible, especially for +//! those already familiar with programming concepts. Knowledge of smart contract-specific +//! languages like Solidity or ink! is required. +//! - **Focused on Application Logic**: The development here is focused on the application logic +//! only. This includes writing functions that execute when certain conditions are met, managing +//! state within the contract, and ensuring security against common Smart Contract +//! vulnerabilities. +//! +//! ## Upgradeability and Flexibility +//! Runtimes and Smart Contracts differ significantly in how they handle upgrades and flexibility, +//! each with its own advantages and constraints. Runtimes are more flexible, allowing for writing +//! migration logic for upgrades, while Smart Contracts are less flexible but offer easier +//! deployment and iteration. +//! +//! #### Runtime Upgradeability and Flexibility +//! - **Migration Logic**: One of the key strengths of runtime development is the ability to define +//! migration logic. This allows developers to implement changes in the state or structure of the +//! blockchain during an upgrade. Such migrations can adapt the existing state to fit new +//! requirements or features seamlessly. +//! - **On-Chain Governance**: Upgrades in a Runtime environment are typically governed on-chain, +//! involving validators or a governance mechanism. This allows for a democratic and transparent +//! process for making substantial changes to the blockchain. +//! - **Broad Impact of Changes**: Changes made in Runtime affect the entire blockchain. This gives +//! developers the power to introduce significant improvements or changes but also necessitates a +//! high level of responsibility and scrutiny, we will talk further about it in the [Security +//! Considerations](#security-considerations) section. +//! +//! #### Smart Contract Upgradeability and Flexibility +//! - **Deployment and Iteration**: Smart Contracts, by nature, are designed for more +//! straightforward deployment and iteration. Developers can quickly deploy contracts. +//! - **Contract Code Updates**: Once deployed, although typically immutable, Smart Contracts can be +//! upgraded, but lack of migration logic. The [pallet_contracts](../../../pallet_contracts/index.html) +//! allows for contracts to be upgraded by exposing the `set_code` dispatchable. More details on this +//! can be found in [Ink! documentation on upgradeable contracts](https://use.ink/5.x/basics/upgradeable-contracts). +//! - **Isolated Impact**: Upgrades or changes to a smart contract generally impact only that +//! contract and its users, unlike Runtime upgrades that have a network-wide effect. +//! - **Simplicity and Rapid Development**: The development cycle for Smart Contracts is usually +//! faster and less complex than Runtime development, allowing for rapid prototyping and +//! deployment. +//! +//! ## Performance and Efficiency +//! Runtimes and Smart Contracts have distinct characteristics in terms of performance and +//! efficiency due to their inherent design and operational contexts. Runtimes are more efficient +//! and optimized for specific needs, while Smart Contracts are more generic and less efficient. +//! +//! #### Runtime Performance and Efficiency +//! - **Optimized for Specific Needs**: Runtime modules in Substrate are tailored to meet the +//! specific needs of the blockchain. They are integrated directly into the blockchain's core, +//! allowing them to operate with high efficiency and minimal overhead. +//! - **Direct Access to Blockchain State**: Runtime has direct access to the blockchain's state. +//! This direct access enables more efficient data processing and transaction handling, as there +//! is no additional layer between the runtime logic and the blockchain's core. +//! - **Resource Management**: Resource management is integral to runtime development to ensure that +//! the blockchain operates smoothly and efficiently. +//! +//! #### Smart Contract Performance and Efficiency +//! - **Generic Nature and Overhead**: Smart Contracts, particularly those running in virtual +//! machine environments, can be less efficient due to the generic nature of their execution +//! environment. The overhead of the virtual machine can lead to increased computational and +//! resource costs. +//! - **Isolation and Security Constraints**: Smart Contracts operate in an isolated environment to +//! ensure security and prevent unwanted interactions with the blockchain's state. This isolation, +//! while crucial for security, can introduce additional computational overhead. +//! - **Gas Mechanism and Metering**: The gas mechanism in Smart Contracts, used for metering +//! computational resources, ensures that contracts don't consume excessive resources. However, +//! this metering itself requires computational power, adding to the overall cost of contract +//! execution. +//! +//! ## Security Considerations +//! These two methodologies, while serving different purposes, come with their own unique security +//! considerations. +//! +//! #### Runtime Security Aspects +//! Runtimes, being at the core of blockchain functionality, have profound implications for the +//! security of the entire network: +//! +//! - **Broad Impact**: Security flaws in the runtime can compromise the entire blockchain, +//! affecting all network participants. +//! - **Governance and Upgradeability**: Runtime upgrades, while powerful, need rigorous governance +//! and testing to ensure security. Improperly executed upgrades can introduce vulnerabilities or +//! disrupt network operations. +//! - **Complexity and Expertise**: Developing and maintaining runtime requires a higher level of +//! expertise in blockchain architecture and security, as mistakes can be far-reaching. +//! +//! #### Smart Contract Security Aspects +//! Smart contracts, while more isolated, bring their own set of security challenges: +//! +//! - **Isolated Impact**: Security issues in a smart contract typically affect the contract itself +//! and its users, rather than the whole network. +//! - **Contract-specific Risks**: Common issues like reentrancy +//! attacks, improper handling of external calls, and gas limit vulnerabilities are specific to +//! smart contract development. +//! - **Permissionless Deployment**: Since anyone can deploy a smart contract, +//! the ecosystem is more open to potentially malicious or vulnerable code. +//! +//! ## Weighing and Metering +//! Weighing and metering are mechanisms designed to limit the resources used by external actors. +//! However, there are fundamental differences in how these resources are handled in FRAME-based +//! Runtimes and how they are handled in Smart Contracts, while Runtime operations are weighed, +//! Smart Contract executions must be metered. +//! +//! #### Weighing +//! In FRAME-based Runtimes, operations are *weighed*. This means that each operation in the Runtime +//! has a fixed upper cost, known in advance, determined through +//! [benchmarking](crate::reference_docs::frame_benchmarking_weight). Weighing is practical here +//! because: +//! +//! - *Predictability*: Runtime operations are part of the blockchain's core logic, which is static +//! until an upgrade occurs. This predictability allows for precise +//! [benchmarking](crate::reference_docs::frame_benchmarking_weight). +//! - *Prevention of Abuse*: By having a fixed upper cost that corresponds to the worst-case +//! complexity scenario of its execution (and a mechanism to refund unused weight), it becomes +//! infeasible for an attacker to create transactions that could unpredictably consume excessive +//! resources. +//! +//! #### Metering +//! For Smart Contracts resource consumption is metered. This is essential due to: +//! +//! - **Untrusted Nature**: Unlike Runtime operations, Smart Contracts can be deployed by any user, +//! and their behavior isn’t known in advance. Metering dynamically measures resource consumption +//! as the contract executes. +//! - **Safety Against Infinite Loops**: Metering protects the blockchain from poorly designed +//! contracts that might run into infinite loops, consuming an indefinite amount of resources. +//! +//! #### Implications for Developers and Users +//! - **For Runtime Developers**: Understanding the cost of each operation is essential. Misjudging +//! the weight of operations can lead to network congestion or vulnerability exploitation. +//! - **For Smart Contract Developers**: Being mindful of the gas cost associated with contract +//! execution is crucial. Efficiently written contracts save costs and are less likely to hit gas +//! limits, ensuring smoother execution on the blockchain. diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml index c0227823c6b247c8c351dfde39b15f8258b52836..d769957490e97d653af2e4ff6682dc5e2901c7fe 100644 --- a/polkadot/Cargo.toml +++ b/polkadot/Cargo.toml @@ -18,9 +18,12 @@ rust-version = "1.64.0" readme = "README.md" authors.workspace = true edition.workspace = true -version = "1.1.0" +version = "1.5.0" default-run = "polkadot" +[lints] +workspace = true + [dependencies] color-eyre = { version = "0.6.1", default-features = false } tikv-jemallocator = { version = "0.5.0", optional = true, features = ["unprefixed_malloc_on_supported_platforms"] } @@ -64,7 +67,6 @@ jemalloc-allocator = [ "polkadot-node-core-pvf/jemalloc-allocator", "polkadot-overseer/jemalloc-allocator", ] -network-protocol-staging = ["polkadot-cli/network-protocol-staging"] # Enables timeout-based tests supposed to be run only in CI environment as they may be flaky diff --git a/polkadot/README.md b/polkadot/README.md index f27fc86df27c987582439e0b207e0336a6d612d8..d7435f27b946f096dc6902a15e556c2b3d58799e 100644 --- a/polkadot/README.md +++ b/polkadot/README.md @@ -9,9 +9,11 @@ guides, like how to run a validator node, see the [Polkadot Wiki](https://wiki.p ### Using a pre-compiled binary -If you just wish to run a Polkadot node without compiling it yourself, you may either run the latest -binary from our [releases](https://github.com/paritytech/polkadot-sdk/releases) page, or install -Polkadot from one of our package repositories. +If you just wish to run a Polkadot node without compiling it yourself, you may either: + +- run the latest binary from our [releases](https://github.com/paritytech/polkadot-sdk/releases) page (make sure to also + download all the `worker` binaries and put them in the same directory as `polkadot`), or +- install Polkadot from one of our package repositories. ### Debian-based (Debian, Ubuntu) diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 72b2a18f36b343b5620ed37bd9d4b20725780c60..0b47da4d45cb176a87e8ef04b48282ee57c800d2 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on Linux, see # https://github.com/rustwasm/wasm-pack/issues/781 etc. @@ -16,7 +19,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = "1.0" -clap = { version = "4.4.10", features = ["derive"], optional = true } +clap = { version = "4.4.13", features = ["derive"], optional = true } log = "0.4.17" thiserror = "1.0.48" futures = "0.3.21" @@ -75,5 +78,3 @@ runtime-metrics = [ "polkadot-node-metrics/runtime-metrics", "service/runtime-metrics", ] - -network-protocol-staging = ["service/network-protocol-staging"] diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 018400fbcf8bf0513ebd3cae3c6ae6e6a0230cd3..9290ec584e47032e6c2af955af536c2f3f646574 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -17,7 +17,7 @@ use crate::cli::{Cli, Subcommand, NODE_VERSION}; use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; use futures::future::TryFutureExt; -use log::{info, warn}; +use log::info; use sc_cli::SubstrateCli; use service::{ self, @@ -196,22 +196,7 @@ where let chain_spec = &runner.config().chain_spec; // By default, enable BEEFY on all networks, unless explicitly disabled through CLI. - let mut enable_beefy = !cli.run.no_beefy; - // BEEFY doesn't (yet) support warp sync: - // Until we implement https://github.com/paritytech/substrate/issues/14756 - // - disallow warp sync for validators, - // - disable BEEFY when warp sync for non-validators. - if enable_beefy && runner.config().network.sync_mode.is_warp() { - if runner.config().role.is_authority() { - return Err(Error::Other( - "Warp sync not supported for validator nodes running BEEFY.".into(), - )) - } else { - // disable BEEFY for non-validator nodes that are warp syncing - warn!("🥩 BEEFY not supported when warp syncing. Disabling BEEFY."); - enable_beefy = false; - } - } + let enable_beefy = !cli.run.no_beefy; set_default_ss58_version(chain_spec); diff --git a/polkadot/core-primitives/Cargo.toml b/polkadot/core-primitives/Cargo.toml index 1b8da759c153d6d0fa640c44c27affec07e23ece..32ee8d3ff3fbf149b452c7801edbeff876b400aa 100644 --- a/polkadot/core-primitives/Cargo.toml +++ b/polkadot/core-primitives/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] sp-core = { path = "../../substrate/primitives/core", default-features = false } sp-std = { path = "../../substrate/primitives/std", default-features = false } diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index c965f5d70aad3577d3ad3d4c57ec98f769904724..f174f8ad0cf4b82e2dd48d4d9d602c6a024c4275 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] polkadot-primitives = { path = "../primitives" } polkadot-node-primitives = { package = "polkadot-node-primitives", path = "../node/primitives" } diff --git a/polkadot/erasure-coding/fuzzer/Cargo.toml b/polkadot/erasure-coding/fuzzer/Cargo.toml index 862b148cc5b136528af1bce8df5c5fccb903b0d9..4e5ef9d229d82db298f57cd4f853042079b5a1f8 100644 --- a/polkadot/erasure-coding/fuzzer/Cargo.toml +++ b/polkadot/erasure-coding/fuzzer/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true publish = false +[lints] +workspace = true + [dependencies] polkadot-erasure-coding = { path = ".." } honggfuzz = "0.5" diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index e0c86d233f9142945df4c1099c692483dadbf010..366c08a6c6705dedf27d212b284b8b848769d0eb 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Collator-side subsystem that handles incoming candidate submissions from the parachain." +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../gum" } diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index 9516dc52a309d9c032a1f22153e4b0f7b75d9193..5fbcec50cd3d60bf13de66d3f0e3f8c9713ae196 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Approval Voting Subsystem of the Polkadot node" +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" @@ -13,8 +16,8 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ gum = { package = "tracing-gum", path = "../../gum" } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } schnellru = "0.2.1" -merlin = "2.0" -schnorrkel = "0.9.1" +merlin = "3.0" +schnorrkel = "0.11.4" kvdb = "0.13.0" derive_more = "0.99.17" thiserror = "1.0.48" @@ -32,15 +35,14 @@ sp-consensus = { path = "../../../../substrate/primitives/consensus/common", def sp-consensus-slots = { path = "../../../../substrate/primitives/consensus/slots", default-features = false } sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto", default-features = false, features = ["full_crypto"] } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -rand_core = "0.5.1" +# should match schnorrkel +rand_core = "0.6.2" rand_chacha = { version = "0.3.1" } rand = "0.8.5" [dev-dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" parking_lot = "0.12.0" -# rand_core should match schnorrkel -rand_core = "0.5.1" sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } sp-core = { path = "../../../../substrate/primitives/core" } diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs index 5d24ff164193de287893b5dc6bcb156f6bcda578..0aa6102fbd6d243b793e889fefb80297a7bb1d89 100644 --- a/polkadot/node/core/approval-voting/src/approval_checking.rs +++ b/polkadot/node/core/approval-voting/src/approval_checking.rs @@ -25,6 +25,15 @@ use crate::{ time::Tick, }; +/// Result of counting the necessary tranches needed for approving a block. +#[derive(Debug, PartialEq, Clone)] +pub struct TranchesToApproveResult { + /// The required tranches for approving this block + pub required_tranches: RequiredTranches, + /// The total number of no_shows at the moment we are doing the counting. + pub total_observed_no_shows: usize, +} + /// The required tranches of assignments needed to determine whether a candidate is approved. #[derive(Debug, PartialEq, Clone)] pub enum RequiredTranches { @@ -64,7 +73,7 @@ pub enum RequiredTranches { } /// The result of a check. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Check { /// The candidate is unapproved. Unapproved, @@ -178,6 +187,7 @@ struct State { next_no_show: Option, /// The last tick at which a considered assignment was received. last_assignment_tick: Option, + total_observed_no_shows: usize, } impl State { @@ -187,41 +197,53 @@ impl State { needed_approvals: usize, n_validators: usize, no_show_duration: Tick, - ) -> RequiredTranches { + ) -> TranchesToApproveResult { let covering = if self.depth == 0 { 0 } else { self.covering }; if self.depth != 0 && self.assignments + covering + self.uncovered >= n_validators { - return RequiredTranches::All + return TranchesToApproveResult { + required_tranches: RequiredTranches::All, + total_observed_no_shows: self.total_observed_no_shows, + } } // If we have enough assignments and all no-shows are covered, we have reached the number // of tranches that we need to have. if self.assignments >= needed_approvals && (covering + self.uncovered) == 0 { - return RequiredTranches::Exact { - needed: tranche, - tolerated_missing: self.covered, - next_no_show: self.next_no_show, - last_assignment_tick: self.last_assignment_tick, + return TranchesToApproveResult { + required_tranches: RequiredTranches::Exact { + needed: tranche, + tolerated_missing: self.covered, + next_no_show: self.next_no_show, + last_assignment_tick: self.last_assignment_tick, + }, + total_observed_no_shows: self.total_observed_no_shows, } } // We're pending more assignments and should look at more tranches. let clock_drift = self.clock_drift(no_show_duration); if self.depth == 0 { - RequiredTranches::Pending { - considered: tranche, - next_no_show: self.next_no_show, - // during the initial assignment-gathering phase, we want to accept assignments - // from any tranche. Note that honest validators will still not broadcast their - // assignment until it is time to do so, regardless of this value. - maximum_broadcast: DelayTranche::max_value(), - clock_drift, + TranchesToApproveResult { + required_tranches: RequiredTranches::Pending { + considered: tranche, + next_no_show: self.next_no_show, + // during the initial assignment-gathering phase, we want to accept assignments + // from any tranche. Note that honest validators will still not broadcast their + // assignment until it is time to do so, regardless of this value. + maximum_broadcast: DelayTranche::max_value(), + clock_drift, + }, + total_observed_no_shows: self.total_observed_no_shows, } } else { - RequiredTranches::Pending { - considered: tranche, - next_no_show: self.next_no_show, - maximum_broadcast: tranche + (covering + self.uncovered) as DelayTranche, - clock_drift, + TranchesToApproveResult { + required_tranches: RequiredTranches::Pending { + considered: tranche, + next_no_show: self.next_no_show, + maximum_broadcast: tranche + (covering + self.uncovered) as DelayTranche, + clock_drift, + }, + total_observed_no_shows: self.total_observed_no_shows, } } } @@ -276,6 +298,7 @@ impl State { uncovered, next_no_show, last_assignment_tick, + total_observed_no_shows: self.total_observed_no_shows + new_no_shows, } } } @@ -372,7 +395,7 @@ pub fn tranches_to_approve( block_tick: Tick, no_show_duration: Tick, needed_approvals: usize, -) -> RequiredTranches { +) -> TranchesToApproveResult { let tick_now = tranche_now as Tick + block_tick; let n_validators = approval_entry.n_validators(); @@ -384,6 +407,7 @@ pub fn tranches_to_approve( uncovered: 0, next_no_show: None, last_assignment_tick: None, + total_observed_no_shows: 0, }; // The `ApprovalEntry` doesn't have any data for empty tranches. We still want to iterate over @@ -434,7 +458,7 @@ pub fn tranches_to_approve( let s = s.advance(n_assignments, no_shows, next_no_show, last_assignment_tick); let output = s.output(tranche, needed_approvals, n_validators, no_show_duration); - *state = match output { + *state = match output.required_tranches { RequiredTranches::Exact { .. } | RequiredTranches::All => { // Wipe the state clean so the next iteration of this closure will terminate // the iterator. This guarantees that we can call `last` further down to see @@ -464,15 +488,17 @@ mod tests { #[test] fn pending_is_not_approved() { - let candidate = approval_db::v1::CandidateEntry { - candidate: dummy_candidate_receipt(dummy_hash()), - session: 0, - block_assignments: BTreeMap::default(), - approvals: BitVec::default(), - } - .into(); + let candidate = CandidateEntry::from_v1( + approval_db::v1::CandidateEntry { + candidate: dummy_candidate_receipt(dummy_hash()), + session: 0, + block_assignments: BTreeMap::default(), + approvals: BitVec::default(), + }, + 0, + ); - let approval_entry = approval_db::v2::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: BitVec::default(), our_assignment: None, @@ -497,29 +523,31 @@ mod tests { #[test] fn exact_takes_only_assignments_up_to() { - let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry { - candidate: dummy_candidate_receipt(dummy_hash()), - session: 0, - block_assignments: BTreeMap::default(), - approvals: bitvec![u8, BitOrderLsb0; 0; 10], - } - .into(); + let mut candidate: CandidateEntry = CandidateEntry::from_v1( + approval_db::v1::CandidateEntry { + candidate: dummy_candidate_receipt(dummy_hash()), + session: 0, + block_assignments: BTreeMap::default(), + approvals: bitvec![u8, BitOrderLsb0; 0; 10], + }, + 0, + ); for i in 0..3 { candidate.mark_approval(ValidatorIndex(i)); } - let approval_entry = approval_db::v2::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 0, assignments: (0..2).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 2, assignments: (5..10).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, @@ -569,29 +597,31 @@ mod tests { #[test] fn one_honest_node_always_approves() { - let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry { - candidate: dummy_candidate_receipt(dummy_hash()), - session: 0, - block_assignments: BTreeMap::default(), - approvals: bitvec![u8, BitOrderLsb0; 0; 10], - } - .into(); + let mut candidate: CandidateEntry = CandidateEntry::from_v1( + approval_db::v1::CandidateEntry { + candidate: dummy_candidate_receipt(dummy_hash()), + session: 0, + block_assignments: BTreeMap::default(), + approvals: bitvec![u8, BitOrderLsb0; 0; 10], + }, + 0, + ); for i in 0..3 { candidate.mark_approval(ValidatorIndex(i)); } - let approval_entry = approval_db::v2::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 0, assignments: (0..4).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (4..6).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 2, assignments: (6..10).map(|i| (ValidatorIndex(i), 0.into())).collect(), }, @@ -647,7 +677,7 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; 5], our_assignment: None, @@ -675,7 +705,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 1, tolerated_missing: 0, @@ -691,7 +722,7 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10], our_assignment: None, @@ -715,7 +746,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 2, next_no_show: Some(block_tick + no_show_duration), @@ -731,7 +763,7 @@ mod tests { let no_show_duration = 10; let needed_approvals = 4; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; 10], our_assignment: None, @@ -759,7 +791,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 11, next_no_show: None, @@ -776,7 +809,7 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, @@ -807,7 +840,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 1, next_no_show: None, @@ -826,7 +860,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 1, next_no_show: None, @@ -843,7 +878,7 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, @@ -879,7 +914,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 1, tolerated_missing: 0, @@ -898,7 +934,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 2, tolerated_missing: 1, @@ -917,7 +954,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 2, next_no_show: None, @@ -934,7 +972,7 @@ mod tests { let needed_approvals = 4; let n_validators = 8; - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), assigned_validators: bitvec![u8, BitOrderLsb0; 0; n_validators], our_assignment: None, @@ -970,7 +1008,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 2, tolerated_missing: 1, @@ -992,7 +1031,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 2, next_no_show: None, @@ -1013,7 +1053,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Exact { needed: 3, tolerated_missing: 2, @@ -1029,22 +1070,24 @@ mod tests { let no_show_duration = 10; let needed_approvals = 3; - let mut candidate: CandidateEntry = approval_db::v1::CandidateEntry { - candidate: dummy_candidate_receipt(dummy_hash()), - session: 0, - block_assignments: BTreeMap::default(), - approvals: bitvec![u8, BitOrderLsb0; 0; 3], - } - .into(); + let mut candidate: CandidateEntry = CandidateEntry::from_v1( + approval_db::v1::CandidateEntry { + candidate: dummy_candidate_receipt(dummy_hash()), + session: 0, + block_assignments: BTreeMap::default(), + approvals: bitvec![u8, BitOrderLsb0; 0; 3], + }, + 0, + ); for i in 0..3 { candidate.mark_approval(ValidatorIndex(i)); } - let approval_entry = approval_db::v2::ApprovalEntry { + let approval_entry = approval_db::v3::ApprovalEntry { tranches: vec![ // Assignments with invalid validator indexes. - approval_db::v2::TrancheEntry { + approval_db::v3::TrancheEntry { tranche: 1, assignments: (2..5).map(|i| (ValidatorIndex(i), 1.into())).collect(), }, @@ -1068,7 +1111,8 @@ mod tests { block_tick, no_show_duration, needed_approvals, - ), + ) + .required_tranches, RequiredTranches::Pending { considered: 10, next_no_show: None, @@ -1094,7 +1138,7 @@ mod tests { ]; for test_tranche in test_tranches { - let mut approval_entry: ApprovalEntry = approval_db::v2::ApprovalEntry { + let mut approval_entry: ApprovalEntry = approval_db::v3::ApprovalEntry { tranches: Vec::new(), backing_group: GroupIndex(0), our_assignment: None, @@ -1345,10 +1389,11 @@ mod tests { uncovered: 0, next_no_show: None, last_assignment_tick: None, + total_observed_no_shows: 0, }; assert_eq!( - state.output(0, 10, 10, 20), + state.output(0, 10, 10, 20).required_tranches, RequiredTranches::Pending { considered: 0, next_no_show: None, @@ -1368,10 +1413,11 @@ mod tests { uncovered: 0, next_no_show: None, last_assignment_tick: None, + total_observed_no_shows: 0, }; assert_eq!( - state.output(0, 10, 10, 20), + state.output(0, 10, 10, 20).required_tranches, RequiredTranches::Exact { needed: 0, tolerated_missing: 0, diff --git a/polkadot/node/core/approval-voting/src/approval_db/common/migration_helpers.rs b/polkadot/node/core/approval-voting/src/approval_db/common/migration_helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..747bbdb2064ef1467c92993ccb825dece0dfb262 --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/common/migration_helpers.rs @@ -0,0 +1,40 @@ +// 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 bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; + +use polkadot_node_primitives::approval::{ + v1::{AssignmentCert, AssignmentCertKind, VrfProof, VrfSignature, RELAY_VRF_MODULO_CONTEXT}, + v2::VrfPreOutput, +}; + +pub fn make_bitvec(len: usize) -> BitVec { + bitvec::bitvec![u8, BitOrderLsb0; 0; len] +} + +pub fn dummy_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert { + let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); + let msg = b"test-garbage"; + let mut prng = rand_core::OsRng; + let keypair = schnorrkel::Keypair::generate_with(&mut prng); + let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); + let preout = inout.to_preout(); + + AssignmentCert { + kind, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + } +} diff --git a/polkadot/node/core/approval-voting/src/approval_db/common/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/common/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..249dcf912df50530e87f732edf2848664df1136e --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/common/mod.rs @@ -0,0 +1,293 @@ +// 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 . + +//! Common helper functions for all versions of approval-voting database. +use std::sync::Arc; + +use parity_scale_codec::{Decode, Encode}; +use polkadot_node_subsystem::{SubsystemError, SubsystemResult}; +use polkadot_node_subsystem_util::database::{DBTransaction, Database}; +use polkadot_primitives::{BlockNumber, CandidateHash, CandidateIndex, Hash}; + +use crate::{ + backend::{Backend, BackendWriteOp, V1ReadBackend, V2ReadBackend}, + persisted_entries, +}; + +use super::{ + v2::{load_block_entry_v1, load_candidate_entry_v1}, + v3::{load_block_entry_v2, load_candidate_entry_v2, BlockEntry, CandidateEntry}, +}; + +pub mod migration_helpers; + +const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; + +/// A range from earliest..last block number stored within the DB. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct StoredBlockRange(pub BlockNumber, pub BlockNumber); +/// The database config. +#[derive(Debug, Clone, Copy)] +pub struct Config { + /// The column family in the database where data is stored. + pub col_approval_data: u32, +} + +/// `DbBackend` is a concrete implementation of the higher-level Backend trait +pub struct DbBackend { + inner: Arc, + config: Config, +} + +impl DbBackend { + /// Create a new [`DbBackend`] with the supplied key-value store and + /// config. + pub fn new(db: Arc, config: Config) -> Self { + DbBackend { inner: db, config } + } +} + +/// Errors while accessing things from the DB. +#[derive(Debug, derive_more::From, derive_more::Display)] +pub enum Error { + Io(std::io::Error), + InvalidDecoding(parity_scale_codec::Error), + InternalError(SubsystemError), +} + +impl std::error::Error for Error {} + +/// Result alias for DB errors. +pub type Result = std::result::Result; + +impl Backend for DbBackend { + fn load_block_entry( + &self, + block_hash: &Hash, + ) -> SubsystemResult> { + load_block_entry(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into)) + } + + fn load_candidate_entry( + &self, + candidate_hash: &CandidateHash, + ) -> SubsystemResult> { + load_candidate_entry(&*self.inner, &self.config, candidate_hash).map(|e| e.map(Into::into)) + } + + fn load_blocks_at_height(&self, block_height: &BlockNumber) -> SubsystemResult> { + load_blocks_at_height(&*self.inner, &self.config, block_height) + } + + fn load_all_blocks(&self) -> SubsystemResult> { + load_all_blocks(&*self.inner, &self.config) + } + + fn load_stored_blocks(&self) -> SubsystemResult> { + load_stored_blocks(&*self.inner, &self.config) + } + + /// Atomically write the list of operations, with later operations taking precedence over prior. + fn write(&mut self, ops: I) -> SubsystemResult<()> + where + I: IntoIterator, + { + let mut tx = DBTransaction::new(); + for op in ops { + match op { + BackendWriteOp::WriteStoredBlockRange(stored_block_range) => { + tx.put_vec( + self.config.col_approval_data, + &STORED_BLOCKS_KEY, + stored_block_range.encode(), + ); + }, + BackendWriteOp::DeleteStoredBlockRange => { + tx.delete(self.config.col_approval_data, &STORED_BLOCKS_KEY); + }, + BackendWriteOp::WriteBlocksAtHeight(h, blocks) => { + tx.put_vec( + self.config.col_approval_data, + &blocks_at_height_key(h), + blocks.encode(), + ); + }, + BackendWriteOp::DeleteBlocksAtHeight(h) => { + tx.delete(self.config.col_approval_data, &blocks_at_height_key(h)); + }, + BackendWriteOp::WriteBlockEntry(block_entry) => { + let block_entry: BlockEntry = block_entry.into(); + tx.put_vec( + self.config.col_approval_data, + &block_entry_key(&block_entry.block_hash), + block_entry.encode(), + ); + }, + BackendWriteOp::DeleteBlockEntry(hash) => { + tx.delete(self.config.col_approval_data, &block_entry_key(&hash)); + }, + BackendWriteOp::WriteCandidateEntry(candidate_entry) => { + let candidate_entry: CandidateEntry = candidate_entry.into(); + tx.put_vec( + self.config.col_approval_data, + &candidate_entry_key(&candidate_entry.candidate.hash()), + candidate_entry.encode(), + ); + }, + BackendWriteOp::DeleteCandidateEntry(candidate_hash) => { + tx.delete(self.config.col_approval_data, &candidate_entry_key(&candidate_hash)); + }, + } + } + + self.inner.write(tx).map_err(|e| e.into()) + } +} + +impl V1ReadBackend for DbBackend { + fn load_candidate_entry_v1( + &self, + candidate_hash: &CandidateHash, + candidate_index: CandidateIndex, + ) -> SubsystemResult> { + load_candidate_entry_v1(&*self.inner, &self.config, candidate_hash) + .map(|e| e.map(|e| persisted_entries::CandidateEntry::from_v1(e, candidate_index))) + } + + fn load_block_entry_v1( + &self, + block_hash: &Hash, + ) -> SubsystemResult> { + load_block_entry_v1(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into)) + } +} + +impl V2ReadBackend for DbBackend { + fn load_candidate_entry_v2( + &self, + candidate_hash: &CandidateHash, + candidate_index: CandidateIndex, + ) -> SubsystemResult> { + load_candidate_entry_v2(&*self.inner, &self.config, candidate_hash) + .map(|e| e.map(|e| persisted_entries::CandidateEntry::from_v2(e, candidate_index))) + } + + fn load_block_entry_v2( + &self, + block_hash: &Hash, + ) -> SubsystemResult> { + load_block_entry_v2(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into)) + } +} + +pub(crate) fn load_decode( + store: &dyn Database, + col_approval_data: u32, + key: &[u8], +) -> Result> { + match store.get(col_approval_data, key)? { + None => Ok(None), + Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into), + } +} + +/// The key a given block entry is stored under. +pub(crate) fn block_entry_key(block_hash: &Hash) -> [u8; 46] { + const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(block_hash.as_ref()); + + key +} + +/// The key a given candidate entry is stored under. +pub(crate) fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { + const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(candidate_hash.0.as_ref()); + + key +} + +/// The key a set of block hashes corresponding to a block number is stored under. +pub(crate) fn blocks_at_height_key(block_number: BlockNumber) -> [u8; 16] { + const BLOCKS_AT_HEIGHT_PREFIX: [u8; 12] = *b"Approvals_at"; + + let mut key = [0u8; 12 + 4]; + key[0..12].copy_from_slice(&BLOCKS_AT_HEIGHT_PREFIX); + block_number.using_encoded(|s| key[12..16].copy_from_slice(s)); + + key +} + +/// Return all blocks which have entries in the DB, ascending, by height. +pub fn load_all_blocks(store: &dyn Database, config: &Config) -> SubsystemResult> { + let mut hashes = Vec::new(); + if let Some(stored_blocks) = load_stored_blocks(store, config)? { + for height in stored_blocks.0..stored_blocks.1 { + let blocks = load_blocks_at_height(store, config, &height)?; + hashes.extend(blocks); + } + } + + Ok(hashes) +} + +/// Load the stored-blocks key from the state. +pub fn load_stored_blocks( + store: &dyn Database, + config: &Config, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, STORED_BLOCKS_KEY) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} + +/// Load a blocks-at-height entry for a given block number. +pub fn load_blocks_at_height( + store: &dyn Database, + config: &Config, + block_number: &BlockNumber, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, &blocks_at_height_key(*block_number)) + .map(|x| x.unwrap_or_default()) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} + +/// Load a block entry from the aux store. +pub fn load_block_entry( + store: &dyn Database, + config: &Config, + block_hash: &Hash, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, &block_entry_key(block_hash)) + .map(|u: Option| u.map(|v| v.into())) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} + +/// Load a candidate entry from the aux store in current version format. +pub fn load_candidate_entry( + store: &dyn Database, + config: &Config, + candidate_hash: &CandidateHash, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash)) + .map(|u: Option| u.map(|v| v.into())) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} diff --git a/polkadot/node/core/approval-voting/src/approval_db/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/mod.rs index 20fb6aa82d8d902885e974d59990b29bf683f55d..78942a507f4b0fa334882d893168794443966932 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/mod.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/mod.rs @@ -30,5 +30,7 @@ //! In the future, we may use a temporary DB which doesn't need to be wiped, but for the //! time being we share the same DB with the rest of Substrate. +pub mod common; pub mod v1; pub mod v2; +pub mod v3; diff --git a/polkadot/node/core/approval-voting/src/approval_db/v1/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v1/tests.rs index 07d8242b772ea9e107edf10a83cf1acd697d031f..b979cb7ef45f6bf6af02acd8fde1d04e6bee8206 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v1/tests.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v1/tests.rs @@ -40,10 +40,6 @@ fn make_db() -> (DbBackend, Arc) { (DbBackend::new(db_writer.clone(), TEST_CONFIG), db_writer) } -fn make_bitvec(len: usize) -> BitVec { - bitvec::bitvec![u8, BitOrderLsb0; 0; len] -} - fn make_block_entry( block_hash: Hash, parent_hash: Hash, diff --git a/polkadot/node/core/approval-voting/src/approval_db/v2/migration_helpers.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/migration_helpers.rs index 74e997c7af8422d6075a4b67747aa66013dd1ed4..df6e4754dbd63ba81e736495297bfaf017a4e3dc 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v2/migration_helpers.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/migration_helpers.rs @@ -16,25 +16,19 @@ //! Approval DB migration helpers. use super::*; -use crate::backend::Backend; -use polkadot_node_primitives::approval::v1::{ - AssignmentCert, AssignmentCertKind, VrfOutput, VrfProof, VrfSignature, RELAY_VRF_MODULO_CONTEXT, +use crate::{ + approval_db::common::{ + migration_helpers::{dummy_assignment_cert, make_bitvec}, + Error, Result, StoredBlockRange, + }, + backend::Backend, }; + +use polkadot_node_primitives::approval::v1::AssignmentCertKind; use polkadot_node_subsystem_util::database::Database; use sp_application_crypto::sp_core::H256; use std::{collections::HashSet, sync::Arc}; -fn dummy_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert { - let ctx = schnorrkel::signing_context(RELAY_VRF_MODULO_CONTEXT); - let msg = b"test-garbage"; - let mut prng = rand_core::OsRng; - let keypair = schnorrkel::Keypair::generate_with(&mut prng); - let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); - let out = inout.to_output(); - - AssignmentCert { kind, vrf: VrfSignature { output: VrfOutput(out), proof: VrfProof(proof) } } -} - fn make_block_entry_v1( block_hash: Hash, parent_hash: Hash, @@ -54,14 +48,10 @@ fn make_block_entry_v1( } } -fn make_bitvec(len: usize) -> BitVec { - bitvec::bitvec![u8, BitOrderLsb0; 0; len] -} - /// Migrates `OurAssignment`, `CandidateEntry` and `ApprovalEntry` to version 2. /// Returns on any error. /// Must only be used in parachains DB migration code - `polkadot-service` crate. -pub fn v1_to_v2(db: Arc, config: Config) -> Result<()> { +pub fn v1_to_latest(db: Arc, config: Config) -> Result<()> { let mut backend = crate::DbBackend::new(db, config); let all_blocks = backend .load_all_blocks() @@ -85,11 +75,13 @@ pub fn v1_to_v2(db: Arc, config: Config) -> Result<()> { let mut counter = 0; // Get all candidate entries, approval entries and convert each of them. for block in all_blocks { - for (_core_index, candidate_hash) in block.candidates() { + for (candidate_index, (_core_index, candidate_hash)) in + block.candidates().iter().enumerate() + { // Loading the candidate will also perform the conversion to the updated format and // return that represantation. if let Some(candidate_entry) = backend - .load_candidate_entry_v1(&candidate_hash) + .load_candidate_entry_v1(&candidate_hash, candidate_index as CandidateIndex) .map_err(|e| Error::InternalError(e))? { // Write the updated representation. @@ -109,42 +101,8 @@ pub fn v1_to_v2(db: Arc, config: Config) -> Result<()> { Ok(()) } -// Checks if the migration doesn't leave the DB in an unsane state. -// This function is to be used in tests. -pub fn v1_to_v2_sanity_check( - db: Arc, - config: Config, - expected_candidates: HashSet, -) -> Result<()> { - let backend = crate::DbBackend::new(db, config); - - let all_blocks = backend - .load_all_blocks() - .unwrap() - .iter() - .map(|block_hash| backend.load_block_entry(block_hash).unwrap().unwrap()) - .collect::>(); - - let mut candidates = HashSet::new(); - - // Iterate all blocks and approval entries. - for block in all_blocks { - for (_core_index, candidate_hash) in block.candidates() { - // Loading the candidate will also perform the conversion to the updated format and - // return that represantation. - if let Some(candidate_entry) = backend.load_candidate_entry(&candidate_hash).unwrap() { - candidates.insert(candidate_entry.candidate.hash()); - } - } - } - - assert_eq!(candidates, expected_candidates); - - Ok(()) -} - // Fills the db with dummy data in v1 scheme. -pub fn v1_to_v2_fill_test_data( +pub fn v1_fill_test_data( db: Arc, config: Config, dummy_candidate_create: F, diff --git a/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs index 66df6ee8f653a992f09428a9c0b2a418aef2209d..da42fc5be485caabdd8f8428bdf15c9bf6eb08e9 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/mod.rs @@ -21,145 +21,23 @@ use polkadot_node_primitives::approval::{v1::DelayTranche, v2::AssignmentCertV2} use polkadot_node_subsystem::{SubsystemError, SubsystemResult}; use polkadot_node_subsystem_util::database::{DBTransaction, Database}; use polkadot_primitives::{ - BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex, - ValidatorIndex, ValidatorSignature, + BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash, + SessionIndex, ValidatorIndex, ValidatorSignature, }; use sp_consensus_slots::Slot; use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec}; -use std::{collections::BTreeMap, sync::Arc}; +use std::collections::BTreeMap; -use crate::{ - backend::{Backend, BackendWriteOp, V1ReadBackend}, - persisted_entries, -}; +use crate::backend::V1ReadBackend; -const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; +use super::common::{block_entry_key, candidate_entry_key, load_decode, Config}; pub mod migration_helpers; #[cfg(test)] pub mod tests; -/// `DbBackend` is a concrete implementation of the higher-level Backend trait -pub struct DbBackend { - inner: Arc, - config: Config, -} - -impl DbBackend { - /// Create a new [`DbBackend`] with the supplied key-value store and - /// config. - pub fn new(db: Arc, config: Config) -> Self { - DbBackend { inner: db, config } - } -} - -impl V1ReadBackend for DbBackend { - fn load_candidate_entry_v1( - &self, - candidate_hash: &CandidateHash, - ) -> SubsystemResult> { - load_candidate_entry_v1(&*self.inner, &self.config, candidate_hash) - .map(|e| e.map(Into::into)) - } - - fn load_block_entry_v1( - &self, - block_hash: &Hash, - ) -> SubsystemResult> { - load_block_entry_v1(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into)) - } -} - -impl Backend for DbBackend { - fn load_block_entry( - &self, - block_hash: &Hash, - ) -> SubsystemResult> { - load_block_entry(&*self.inner, &self.config, block_hash).map(|e| e.map(Into::into)) - } - - fn load_candidate_entry( - &self, - candidate_hash: &CandidateHash, - ) -> SubsystemResult> { - load_candidate_entry(&*self.inner, &self.config, candidate_hash).map(|e| e.map(Into::into)) - } - - fn load_blocks_at_height(&self, block_height: &BlockNumber) -> SubsystemResult> { - load_blocks_at_height(&*self.inner, &self.config, block_height) - } - - fn load_all_blocks(&self) -> SubsystemResult> { - load_all_blocks(&*self.inner, &self.config) - } - - fn load_stored_blocks(&self) -> SubsystemResult> { - load_stored_blocks(&*self.inner, &self.config) - } - - /// Atomically write the list of operations, with later operations taking precedence over prior. - fn write(&mut self, ops: I) -> SubsystemResult<()> - where - I: IntoIterator, - { - let mut tx = DBTransaction::new(); - for op in ops { - match op { - BackendWriteOp::WriteStoredBlockRange(stored_block_range) => { - tx.put_vec( - self.config.col_approval_data, - &STORED_BLOCKS_KEY, - stored_block_range.encode(), - ); - }, - BackendWriteOp::DeleteStoredBlockRange => { - tx.delete(self.config.col_approval_data, &STORED_BLOCKS_KEY); - }, - BackendWriteOp::WriteBlocksAtHeight(h, blocks) => { - tx.put_vec( - self.config.col_approval_data, - &blocks_at_height_key(h), - blocks.encode(), - ); - }, - BackendWriteOp::DeleteBlocksAtHeight(h) => { - tx.delete(self.config.col_approval_data, &blocks_at_height_key(h)); - }, - BackendWriteOp::WriteBlockEntry(block_entry) => { - let block_entry: BlockEntry = block_entry.into(); - tx.put_vec( - self.config.col_approval_data, - &block_entry_key(&block_entry.block_hash), - block_entry.encode(), - ); - }, - BackendWriteOp::DeleteBlockEntry(hash) => { - tx.delete(self.config.col_approval_data, &block_entry_key(&hash)); - }, - BackendWriteOp::WriteCandidateEntry(candidate_entry) => { - let candidate_entry: CandidateEntry = candidate_entry.into(); - tx.put_vec( - self.config.col_approval_data, - &candidate_entry_key(&candidate_entry.candidate.hash()), - candidate_entry.encode(), - ); - }, - BackendWriteOp::DeleteCandidateEntry(candidate_hash) => { - tx.delete(self.config.col_approval_data, &candidate_entry_key(&candidate_hash)); - }, - } - } - - self.inner.write(tx).map_err(|e| e.into()) - } -} - -/// A range from earliest..last block number stored within the DB. -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -pub struct StoredBlockRange(pub BlockNumber, pub BlockNumber); - // slot_duration * 2 + DelayTranche gives the number of delay tranches since the // unix epoch. #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq)] @@ -168,13 +46,6 @@ pub struct Tick(u64); /// Convenience type definition pub type Bitfield = BitVec; -/// The database config. -#[derive(Debug, Clone, Copy)] -pub struct Config { - /// The column family in the database where data is stored. - pub col_approval_data: u32, -} - /// Details pertaining to our assignment on a block. #[derive(Encode, Decode, Debug, Clone, PartialEq)] pub struct OurAssignment { @@ -259,118 +130,6 @@ impl From for crate::Tick { } } -/// Errors while accessing things from the DB. -#[derive(Debug, derive_more::From, derive_more::Display)] -pub enum Error { - Io(std::io::Error), - InvalidDecoding(parity_scale_codec::Error), - InternalError(SubsystemError), -} - -impl std::error::Error for Error {} - -/// Result alias for DB errors. -pub type Result = std::result::Result; - -pub(crate) fn load_decode( - store: &dyn Database, - col_approval_data: u32, - key: &[u8], -) -> Result> { - match store.get(col_approval_data, key)? { - None => Ok(None), - Some(raw) => D::decode(&mut &raw[..]).map(Some).map_err(Into::into), - } -} - -/// The key a given block entry is stored under. -pub(crate) fn block_entry_key(block_hash: &Hash) -> [u8; 46] { - const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck"; - - let mut key = [0u8; 14 + 32]; - key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX); - key[14..][..32].copy_from_slice(block_hash.as_ref()); - - key -} - -/// The key a given candidate entry is stored under. -pub(crate) fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { - const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; - - let mut key = [0u8; 14 + 32]; - key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX); - key[14..][..32].copy_from_slice(candidate_hash.0.as_ref()); - - key -} - -/// The key a set of block hashes corresponding to a block number is stored under. -pub(crate) fn blocks_at_height_key(block_number: BlockNumber) -> [u8; 16] { - const BLOCKS_AT_HEIGHT_PREFIX: [u8; 12] = *b"Approvals_at"; - - let mut key = [0u8; 12 + 4]; - key[0..12].copy_from_slice(&BLOCKS_AT_HEIGHT_PREFIX); - block_number.using_encoded(|s| key[12..16].copy_from_slice(s)); - - key -} - -/// Return all blocks which have entries in the DB, ascending, by height. -pub fn load_all_blocks(store: &dyn Database, config: &Config) -> SubsystemResult> { - let mut hashes = Vec::new(); - if let Some(stored_blocks) = load_stored_blocks(store, config)? { - for height in stored_blocks.0..stored_blocks.1 { - let blocks = load_blocks_at_height(store, config, &height)?; - hashes.extend(blocks); - } - } - - Ok(hashes) -} - -/// Load the stored-blocks key from the state. -pub fn load_stored_blocks( - store: &dyn Database, - config: &Config, -) -> SubsystemResult> { - load_decode(store, config.col_approval_data, STORED_BLOCKS_KEY) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) -} - -/// Load a blocks-at-height entry for a given block number. -pub fn load_blocks_at_height( - store: &dyn Database, - config: &Config, - block_number: &BlockNumber, -) -> SubsystemResult> { - load_decode(store, config.col_approval_data, &blocks_at_height_key(*block_number)) - .map(|x| x.unwrap_or_default()) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) -} - -/// Load a block entry from the aux store. -pub fn load_block_entry( - store: &dyn Database, - config: &Config, - block_hash: &Hash, -) -> SubsystemResult> { - load_decode(store, config.col_approval_data, &block_entry_key(block_hash)) - .map(|u: Option| u.map(|v| v.into())) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) -} - -/// Load a candidate entry from the aux store in current version format. -pub fn load_candidate_entry( - store: &dyn Database, - config: &Config, - candidate_hash: &CandidateHash, -) -> SubsystemResult> { - load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash)) - .map(|u: Option| u.map(|v| v.into())) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) -} - /// Load a candidate entry from the aux store in v1 format. pub fn load_candidate_entry_v1( store: &dyn Database, diff --git a/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs index 50a5a924ca8dba696e1f3e2d0465a91e837194ec..6021b44c2765ff12a03e1ad85bfb8117c9fcfb03 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v2/tests.rs @@ -16,13 +16,22 @@ //! Tests for the aux-schema of approval voting. -use super::{DbBackend, StoredBlockRange, *}; use crate::{ + approval_db::{ + common::{migration_helpers::make_bitvec, DbBackend, StoredBlockRange, *}, + v2::*, + v3::{load_block_entry_v2, load_candidate_entry_v2}, + }, backend::{Backend, OverlayedBackend}, ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo}, }; +use polkadot_primitives::{ + BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, +}; + use polkadot_node_subsystem_util::database::Database; use polkadot_primitives::Id as ParaId; +use sp_consensus_slots::Slot; use std::{collections::HashMap, sync::Arc}; use ::test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash}; @@ -60,10 +69,6 @@ fn make_block_entry( } } -fn make_bitvec(len: usize) -> BitVec { - bitvec::bitvec![u8, BitOrderLsb0; 0; len] -} - fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt { let mut c = dummy_candidate_receipt(dummy_hash()); @@ -110,7 +115,10 @@ fn read_write() { overlay_db.write_stored_block_range(range.clone()); overlay_db.write_blocks_at_height(1, at_height.clone()); overlay_db.write_block_entry(block_entry.clone().into()); - overlay_db.write_candidate_entry(candidate_entry.clone().into()); + overlay_db.write_candidate_entry(crate::persisted_entries::CandidateEntry::from_v2( + candidate_entry.clone(), + 0, + )); let write_ops = overlay_db.into_write_ops(); db.write(write_ops).unwrap(); @@ -118,11 +126,11 @@ fn read_write() { assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), Some(range)); assert_eq!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap(), at_height); assert_eq!( - load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(), + load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(), Some(block_entry.into()) ); assert_eq!( - load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(), + load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(), Some(candidate_entry.into()), ); @@ -134,8 +142,8 @@ fn read_write() { db.write(write_ops).unwrap(); assert!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap().is_empty()); - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none()); - assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none()); + assert!(load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash) .unwrap() .is_none()); } @@ -196,25 +204,27 @@ fn add_block_entry_works() { db.write(write_ops).unwrap(); assert_eq!( - load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a.into()) ); assert_eq!( - load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), + load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b.into()) ); - let candidate_entry_a = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_a) - .unwrap() - .unwrap(); + let candidate_entry_a = + load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash_a) + .unwrap() + .unwrap(); assert_eq!( candidate_entry_a.block_assignments.keys().collect::>(), vec![&block_hash_a, &block_hash_b] ); - let candidate_entry_b = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_b) - .unwrap() - .unwrap(); + let candidate_entry_b = + load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &candidate_hash_b) + .unwrap() + .unwrap(); assert_eq!(candidate_entry_b.block_assignments.keys().collect::>(), vec![&block_hash_b]); } @@ -243,11 +253,11 @@ fn add_block_entry_adds_child() { block_entry_a.children.push(block_hash_b); assert_eq!( - load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), Some(block_entry_a.into()) ); assert_eq!( - load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), + load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), Some(block_entry_b.into()) ); } @@ -365,13 +375,15 @@ fn canonicalize_works() { for (c_hash, in_blocks) in expected { let (entry, in_blocks) = match in_blocks { None => { - assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash) + assert!(load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &c_hash) .unwrap() .is_none()); continue }, Some(i) => ( - load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().unwrap(), + load_candidate_entry_v2(store.as_ref(), &TEST_CONFIG, &c_hash) + .unwrap() + .unwrap(), i, ), }; @@ -388,13 +400,13 @@ fn canonicalize_works() { for (hash, with_candidates) in expected { let (entry, with_candidates) = match with_candidates { None => { - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash) .unwrap() .is_none()); continue }, Some(i) => - (load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i), + (load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i), }; assert_eq!(entry.candidates.len(), with_candidates.len()); @@ -510,22 +522,22 @@ fn force_approve_works() { let write_ops = overlay_db.into_write_ops(); db.write(write_ops).unwrap(); - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a,) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_a,) .unwrap() .unwrap() .approved_bitfield .all()); - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b,) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_b,) .unwrap() .unwrap() .approved_bitfield .all()); - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_c,) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_c,) .unwrap() .unwrap() .approved_bitfield .not_any()); - assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_d,) + assert!(load_block_entry_v2(store.as_ref(), &TEST_CONFIG, &block_hash_d,) .unwrap() .unwrap() .approved_bitfield diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/migration_helpers.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/migration_helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..ad5e89ef3de84035ff3e9c79533edfc07bf8d4c5 --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v3/migration_helpers.rs @@ -0,0 +1,237 @@ +// 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 . + +//! Approval DB migration helpers. +use super::*; +use crate::{ + approval_db::common::{ + block_entry_key, candidate_entry_key, + migration_helpers::{dummy_assignment_cert, make_bitvec}, + Config, Error, Result, StoredBlockRange, + }, + backend::{Backend, V2ReadBackend}, +}; +use polkadot_node_primitives::approval::v1::AssignmentCertKind; +use polkadot_node_subsystem_util::database::Database; +use sp_application_crypto::sp_core::H256; +use std::{collections::HashSet, sync::Arc}; + +/// Migrates `BlockEntry`, `CandidateEntry`, `ApprovalEntry` and `OurApproval` to version 3. +/// Returns on any error. +/// Must only be used in parachains DB migration code - `polkadot-service` crate. +pub fn v2_to_latest(db: Arc, config: Config) -> Result<()> { + let mut backend = crate::DbBackend::new(db, config); + let all_blocks = backend + .load_all_blocks() + .map_err(|e| Error::InternalError(e))? + .iter() + .filter_map(|block_hash| { + backend + .load_block_entry_v2(block_hash) + .map_err(|e| Error::InternalError(e)) + .ok()? + }) + .collect::>(); + + gum::info!( + target: crate::LOG_TARGET, + "Migrating candidate entries on top of {} blocks", + all_blocks.len() + ); + + let mut overlay = crate::OverlayedBackend::new(&backend); + let mut counter = 0; + // Get all candidate entries, approval entries and convert each of them. + for block in all_blocks { + for (candidate_index, (_core_index, candidate_hash)) in + block.candidates().iter().enumerate() + { + // Loading the candidate will also perform the conversion to the updated format and + // return that represantation. + if let Some(candidate_entry) = backend + .load_candidate_entry_v2(&candidate_hash, candidate_index as CandidateIndex) + .map_err(|e| Error::InternalError(e))? + { + // Write the updated representation. + overlay.write_candidate_entry(candidate_entry); + counter += 1; + } + } + overlay.write_block_entry(block); + } + + gum::info!(target: crate::LOG_TARGET, "Migrated {} entries", counter); + + // Commit all changes to DB. + let write_ops = overlay.into_write_ops(); + backend.write(write_ops).unwrap(); + + Ok(()) +} + +// Checks if the migration doesn't leave the DB in an unsane state. +// This function is to be used in tests. +pub fn v1_to_latest_sanity_check( + db: Arc, + config: Config, + expected_candidates: HashSet, +) -> Result<()> { + let backend = crate::DbBackend::new(db, config); + + let all_blocks = backend + .load_all_blocks() + .unwrap() + .iter() + .map(|block_hash| backend.load_block_entry(block_hash).unwrap().unwrap()) + .collect::>(); + + let mut candidates = HashSet::new(); + + // Iterate all blocks and approval entries. + for block in all_blocks { + for (_core_index, candidate_hash) in block.candidates() { + // Loading the candidate will also perform the conversion to the updated format and + // return that represantation. + if let Some(candidate_entry) = backend.load_candidate_entry(&candidate_hash).unwrap() { + candidates.insert(candidate_entry.candidate.hash()); + } + } + } + + assert_eq!(candidates, expected_candidates); + + Ok(()) +} + +// Fills the db with dummy data in v2 scheme. +pub fn v2_fill_test_data( + db: Arc, + config: Config, + dummy_candidate_create: F, +) -> Result> +where + F: Fn(H256) -> CandidateReceipt, +{ + let mut backend = crate::DbBackend::new(db.clone(), config); + let mut overlay_db = crate::OverlayedBackend::new(&backend); + let mut expected_candidates = HashSet::new(); + + const RELAY_BLOCK_COUNT: u32 = 10; + + let range = StoredBlockRange(1, 11); + overlay_db.write_stored_block_range(range.clone()); + + for relay_number in 1..=RELAY_BLOCK_COUNT { + let relay_hash = Hash::repeat_byte(relay_number as u8); + let assignment_core_index = CoreIndex(relay_number); + let candidate = dummy_candidate_create(relay_hash); + let candidate_hash = candidate.hash(); + + let at_height = vec![relay_hash]; + + let block_entry = make_block_entry_v2( + relay_hash, + Default::default(), + relay_number, + vec![(assignment_core_index, candidate_hash)], + ); + + let dummy_assignment = crate::approval_db::v2::OurAssignment { + cert: dummy_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }).into(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + }; + + let candidate_entry = crate::approval_db::v2::CandidateEntry { + candidate, + session: 123, + block_assignments: vec![( + relay_hash, + crate::approval_db::v2::ApprovalEntry { + tranches: Vec::new(), + backing_group: GroupIndex(1), + our_assignment: Some(dummy_assignment), + our_approval_sig: None, + approved: false, + assigned_validators: make_bitvec(1), + }, + )] + .into_iter() + .collect(), + approvals: Default::default(), + }; + + overlay_db.write_blocks_at_height(relay_number, at_height.clone()); + expected_candidates.insert(candidate_entry.candidate.hash()); + + db.write(write_candidate_entry_v2(candidate_entry, config)).unwrap(); + db.write(write_block_entry_v2(block_entry, config)).unwrap(); + } + + let write_ops = overlay_db.into_write_ops(); + backend.write(write_ops).unwrap(); + + Ok(expected_candidates) +} + +fn make_block_entry_v2( + block_hash: Hash, + parent_hash: Hash, + block_number: BlockNumber, + candidates: Vec<(CoreIndex, CandidateHash)>, +) -> crate::approval_db::v2::BlockEntry { + crate::approval_db::v2::BlockEntry { + block_hash, + parent_hash, + block_number, + session: 1, + slot: Slot::from(1), + relay_vrf_story: [0u8; 32], + approved_bitfield: make_bitvec(candidates.len()), + distributed_assignments: make_bitvec(candidates.len()), + candidates, + children: Vec::new(), + } +} + +// Low level DB helper to write a candidate entry in v1 scheme. +fn write_candidate_entry_v2( + candidate_entry: crate::approval_db::v2::CandidateEntry, + config: Config, +) -> DBTransaction { + let mut tx = DBTransaction::new(); + tx.put_vec( + config.col_approval_data, + &candidate_entry_key(&candidate_entry.candidate.hash()), + candidate_entry.encode(), + ); + tx +} + +// Low level DB helper to write a block entry in v1 scheme. +fn write_block_entry_v2( + block_entry: crate::approval_db::v2::BlockEntry, + config: Config, +) -> DBTransaction { + let mut tx = DBTransaction::new(); + tx.put_vec( + config.col_approval_data, + &block_entry_key(&block_entry.block_hash), + block_entry.encode(), + ); + tx +} diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e4f4302195256205905d3f931039cbf9631824c --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v3/mod.rs @@ -0,0 +1,137 @@ +// 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 . + +//! Version 3 of the DB schema. +//! +//! Version 3 modifies the `our_approval` format of `ApprovalEntry` +//! and adds a new field `pending_signatures` for `BlockEntry` + +use parity_scale_codec::{Decode, Encode}; +use polkadot_node_primitives::approval::v2::CandidateBitfield; +use polkadot_node_subsystem::SubsystemResult; +use polkadot_node_subsystem_util::database::{DBTransaction, Database}; +use polkadot_overseer::SubsystemError; +use polkadot_primitives::{ + BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash, + SessionIndex, ValidatorIndex, ValidatorSignature, +}; + +use sp_consensus_slots::Slot; + +use std::collections::BTreeMap; + +use super::common::{block_entry_key, candidate_entry_key, load_decode, Config}; + +/// Re-export this structs as v3 since they did not change between v2 and v3. +pub use super::v2::{Bitfield, OurAssignment, Tick, TrancheEntry}; + +pub mod migration_helpers; + +#[cfg(test)] +pub mod tests; + +/// Metadata about our approval signature +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct OurApproval { + /// The signature for the candidates hashes pointed by indices. + pub signature: ValidatorSignature, + /// The indices of the candidates signed in this approval. + pub signed_candidates_indices: CandidateBitfield, +} + +/// Metadata regarding approval of a particular candidate within the context of some +/// particular block. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct ApprovalEntry { + pub tranches: Vec, + pub backing_group: GroupIndex, + pub our_assignment: Option, + pub our_approval_sig: Option, + // `n_validators` bits. + pub assigned_validators: Bitfield, + pub approved: bool, +} + +/// Metadata regarding approval of a particular candidate. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct CandidateEntry { + pub candidate: CandidateReceipt, + pub session: SessionIndex, + // Assignments are based on blocks, so we need to track assignments separately + // based on the block we are looking at. + pub block_assignments: BTreeMap, + pub approvals: Bitfield, +} + +/// Metadata regarding approval of a particular block, by way of approval of the +/// candidates contained within it. +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub struct BlockEntry { + pub block_hash: Hash, + pub block_number: BlockNumber, + pub parent_hash: Hash, + pub session: SessionIndex, + pub slot: Slot, + /// Random bytes derived from the VRF submitted within the block by the block + /// author as a credential and used as input to approval assignment criteria. + pub relay_vrf_story: [u8; 32], + // The candidates included as-of this block and the index of the core they are + // leaving. Sorted ascending by core index. + pub candidates: Vec<(CoreIndex, CandidateHash)>, + // A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`. + // The i'th bit is `true` iff the candidate has been approved in the context of this + // block. The block can be considered approved if the bitfield has all bits set to `true`. + pub approved_bitfield: Bitfield, + pub children: Vec, + // A list of candidates we have checked, but didn't not sign and + // advertise the vote yet. + pub candidates_pending_signature: BTreeMap, + // Assignments we already distributed. A 1 bit means the candidate index for which + // we already have sent out an assignment. We need this to avoid distributing + // multiple core assignments more than once. + pub distributed_assignments: Bitfield, +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +/// Context needed for creating an approval signature for a given candidate. +pub struct CandidateSigningContext { + /// The candidate hash, to be included in the signature. + pub candidate_hash: CandidateHash, + /// The latest tick we have to create and send the approval. + pub sign_no_later_than_tick: Tick, +} + +/// Load a candidate entry from the aux store in v2 format. +pub fn load_candidate_entry_v2( + store: &dyn Database, + config: &Config, + candidate_hash: &CandidateHash, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, &candidate_entry_key(candidate_hash)) + .map(|u: Option| u.map(|v| v.into())) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} + +/// Load a block entry from the aux store in v2 format. +pub fn load_block_entry_v2( + store: &dyn Database, + config: &Config, + block_hash: &Hash, +) -> SubsystemResult> { + load_decode(store, config.col_approval_data, &block_entry_key(block_hash)) + .map(|u: Option| u.map(|v| v.into())) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) +} diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..08c65461bca80aafb758e42c80b509bc37c47ece --- /dev/null +++ b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs @@ -0,0 +1,575 @@ +// 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 . + +//! Tests for the aux-schema of approval voting. + +use crate::{ + approval_db::{ + common::{migration_helpers::make_bitvec, DbBackend, StoredBlockRange, *}, + v3::*, + }, + backend::{Backend, OverlayedBackend}, + ops::{add_block_entry, canonicalize, force_approve, NewCandidateInfo}, +}; +use polkadot_primitives::{ + BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, +}; + +use polkadot_node_subsystem_util::database::Database; +use polkadot_primitives::Id as ParaId; +use sp_consensus_slots::Slot; +use std::{collections::HashMap, sync::Arc}; + +use ::test_helpers::{dummy_candidate_receipt, dummy_candidate_receipt_bad_sig, dummy_hash}; + +const DATA_COL: u32 = 0; + +const NUM_COLUMNS: u32 = 1; + +const TEST_CONFIG: Config = Config { col_approval_data: DATA_COL }; + +fn make_db() -> (DbBackend, Arc) { + let db = kvdb_memorydb::create(NUM_COLUMNS); + let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + let db_writer: Arc = Arc::new(db); + (DbBackend::new(db_writer.clone(), TEST_CONFIG), db_writer) +} + +fn make_block_entry( + block_hash: Hash, + parent_hash: Hash, + block_number: BlockNumber, + candidates: Vec<(CoreIndex, CandidateHash)>, +) -> BlockEntry { + BlockEntry { + block_hash, + parent_hash, + block_number, + session: 1, + slot: Slot::from(1), + relay_vrf_story: [0u8; 32], + approved_bitfield: make_bitvec(candidates.len()), + candidates, + children: Vec::new(), + candidates_pending_signature: Default::default(), + distributed_assignments: Default::default(), + } +} + +fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt { + let mut c = dummy_candidate_receipt(dummy_hash()); + + c.descriptor.para_id = para_id; + c.descriptor.relay_parent = relay_parent; + + c +} + +#[test] +fn read_write() { + let (mut db, store) = make_db(); + + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + let candidate_hash = dummy_candidate_receipt_bad_sig(dummy_hash(), None).hash(); + + let range = StoredBlockRange(10, 20); + let at_height = vec![hash_a, hash_b]; + + let block_entry = + make_block_entry(hash_a, Default::default(), 1, vec![(CoreIndex(0), candidate_hash)]); + + let candidate_entry = CandidateEntry { + candidate: dummy_candidate_receipt_bad_sig(dummy_hash(), None), + session: 5, + block_assignments: vec![( + hash_a, + ApprovalEntry { + tranches: Vec::new(), + backing_group: GroupIndex(1), + our_assignment: None, + our_approval_sig: None, + assigned_validators: Default::default(), + approved: false, + }, + )] + .into_iter() + .collect(), + approvals: Default::default(), + }; + + let mut overlay_db = OverlayedBackend::new(&db); + overlay_db.write_stored_block_range(range.clone()); + overlay_db.write_blocks_at_height(1, at_height.clone()); + overlay_db.write_block_entry(block_entry.clone().into()); + overlay_db.write_candidate_entry(candidate_entry.clone().into()); + + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!(load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), Some(range)); + assert_eq!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap(), at_height); + assert_eq!( + load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap(), + Some(block_entry.into()) + ); + assert_eq!( + load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash).unwrap(), + Some(candidate_entry.into()), + ); + + let mut overlay_db = OverlayedBackend::new(&db); + overlay_db.delete_blocks_at_height(1); + overlay_db.delete_block_entry(&hash_a); + overlay_db.delete_candidate_entry(&candidate_hash); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert!(load_blocks_at_height(store.as_ref(), &TEST_CONFIG, &1).unwrap().is_empty()); + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash_a).unwrap().is_none()); + assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash) + .unwrap() + .is_none()); +} + +#[test] +fn add_block_entry_works() { + let (mut db, store) = make_db(); + + let parent_hash = Hash::repeat_byte(1); + let block_hash_a = Hash::repeat_byte(2); + let block_hash_b = Hash::repeat_byte(69); + + let candidate_receipt_a = make_candidate(ParaId::from(1_u32), parent_hash); + let candidate_receipt_b = make_candidate(ParaId::from(2_u32), parent_hash); + + let candidate_hash_a = candidate_receipt_a.hash(); + let candidate_hash_b = candidate_receipt_b.hash(); + + let block_number = 10; + + let block_entry_a = make_block_entry( + block_hash_a, + parent_hash, + block_number, + vec![(CoreIndex(0), candidate_hash_a)], + ); + + let block_entry_b = make_block_entry( + block_hash_b, + parent_hash, + block_number, + vec![(CoreIndex(0), candidate_hash_a), (CoreIndex(1), candidate_hash_b)], + ); + + let n_validators = 10; + + let mut new_candidate_info = HashMap::new(); + new_candidate_info + .insert(candidate_hash_a, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(0), None)); + + let mut overlay_db = OverlayedBackend::new(&db); + add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |h| { + new_candidate_info.get(h).map(|x| x.clone()) + }) + .unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + new_candidate_info + .insert(candidate_hash_b, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(1), None)); + + let mut overlay_db = OverlayedBackend::new(&db); + add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |h| { + new_candidate_info.get(h).map(|x| x.clone()) + }) + .unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!( + load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + Some(block_entry_a.into()) + ); + assert_eq!( + load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), + Some(block_entry_b.into()) + ); + + let candidate_entry_a = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_a) + .unwrap() + .unwrap(); + assert_eq!( + candidate_entry_a.block_assignments.keys().collect::>(), + vec![&block_hash_a, &block_hash_b] + ); + + let candidate_entry_b = load_candidate_entry(store.as_ref(), &TEST_CONFIG, &candidate_hash_b) + .unwrap() + .unwrap(); + assert_eq!(candidate_entry_b.block_assignments.keys().collect::>(), vec![&block_hash_b]); +} + +#[test] +fn add_block_entry_adds_child() { + let (mut db, store) = make_db(); + + let parent_hash = Hash::repeat_byte(1); + let block_hash_a = Hash::repeat_byte(2); + let block_hash_b = Hash::repeat_byte(69); + + let mut block_entry_a = make_block_entry(block_hash_a, parent_hash, 1, Vec::new()); + + let block_entry_b = make_block_entry(block_hash_b, block_hash_a, 2, Vec::new()); + + let n_validators = 10; + + let mut overlay_db = OverlayedBackend::new(&db); + add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap(); + + add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap(); + + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + block_entry_a.children.push(block_hash_b); + + assert_eq!( + load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a).unwrap(), + Some(block_entry_a.into()) + ); + assert_eq!( + load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b).unwrap(), + Some(block_entry_b.into()) + ); +} + +#[test] +fn canonicalize_works() { + let (mut db, store) = make_db(); + + // -> B1 -> C1 -> D1 + // A -> B2 -> C2 -> D2 + // + // We'll canonicalize C1. Everytning except D1 should disappear. + // + // Candidates: + // Cand1 in B2 + // Cand2 in C2 + // Cand3 in C2 and D1 + // Cand4 in D1 + // Cand5 in D2 + // Only Cand3 and Cand4 should remain after canonicalize. + + let n_validators = 10; + + let mut overlay_db = OverlayedBackend::new(&db); + overlay_db.write_stored_block_range(StoredBlockRange(1, 5)); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + let genesis = Hash::repeat_byte(0); + + let block_hash_a = Hash::repeat_byte(1); + let block_hash_b1 = Hash::repeat_byte(2); + let block_hash_b2 = Hash::repeat_byte(3); + let block_hash_c1 = Hash::repeat_byte(4); + let block_hash_c2 = Hash::repeat_byte(5); + let block_hash_d1 = Hash::repeat_byte(6); + let block_hash_d2 = Hash::repeat_byte(7); + + let candidate_receipt_genesis = make_candidate(ParaId::from(1_u32), genesis); + let candidate_receipt_a = make_candidate(ParaId::from(2_u32), block_hash_a); + let candidate_receipt_b = make_candidate(ParaId::from(3_u32), block_hash_a); + let candidate_receipt_b1 = make_candidate(ParaId::from(4_u32), block_hash_b1); + let candidate_receipt_c1 = make_candidate(ParaId::from(5_u32), block_hash_c1); + + let cand_hash_1 = candidate_receipt_genesis.hash(); + let cand_hash_2 = candidate_receipt_a.hash(); + let cand_hash_3 = candidate_receipt_b.hash(); + let cand_hash_4 = candidate_receipt_b1.hash(); + let cand_hash_5 = candidate_receipt_c1.hash(); + + let block_entry_a = make_block_entry(block_hash_a, genesis, 1, Vec::new()); + let block_entry_b1 = make_block_entry(block_hash_b1, block_hash_a, 2, Vec::new()); + let block_entry_b2 = + make_block_entry(block_hash_b2, block_hash_a, 2, vec![(CoreIndex(0), cand_hash_1)]); + let block_entry_c1 = make_block_entry(block_hash_c1, block_hash_b1, 3, Vec::new()); + let block_entry_c2 = make_block_entry( + block_hash_c2, + block_hash_b2, + 3, + vec![(CoreIndex(0), cand_hash_2), (CoreIndex(1), cand_hash_3)], + ); + let block_entry_d1 = make_block_entry( + block_hash_d1, + block_hash_c1, + 4, + vec![(CoreIndex(0), cand_hash_3), (CoreIndex(1), cand_hash_4)], + ); + let block_entry_d2 = + make_block_entry(block_hash_d2, block_hash_c2, 4, vec![(CoreIndex(0), cand_hash_5)]); + + let candidate_info = { + let mut candidate_info = HashMap::new(); + candidate_info.insert( + cand_hash_1, + NewCandidateInfo::new(candidate_receipt_genesis, GroupIndex(1), None), + ); + + candidate_info + .insert(cand_hash_2, NewCandidateInfo::new(candidate_receipt_a, GroupIndex(2), None)); + + candidate_info + .insert(cand_hash_3, NewCandidateInfo::new(candidate_receipt_b, GroupIndex(3), None)); + + candidate_info + .insert(cand_hash_4, NewCandidateInfo::new(candidate_receipt_b1, GroupIndex(4), None)); + + candidate_info + .insert(cand_hash_5, NewCandidateInfo::new(candidate_receipt_c1, GroupIndex(5), None)); + + candidate_info + }; + + // now insert all the blocks. + let blocks = vec![ + block_entry_a.clone(), + block_entry_b1.clone(), + block_entry_b2.clone(), + block_entry_c1.clone(), + block_entry_c2.clone(), + block_entry_d1.clone(), + block_entry_d2.clone(), + ]; + + let mut overlay_db = OverlayedBackend::new(&db); + for block_entry in blocks { + add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| { + candidate_info.get(h).map(|x| x.clone()) + }) + .unwrap(); + } + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + let check_candidates_in_store = |expected: Vec<(CandidateHash, Option>)>| { + for (c_hash, in_blocks) in expected { + let (entry, in_blocks) = match in_blocks { + None => { + assert!(load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash) + .unwrap() + .is_none()); + continue + }, + Some(i) => ( + load_candidate_entry(store.as_ref(), &TEST_CONFIG, &c_hash).unwrap().unwrap(), + i, + ), + }; + + assert_eq!(entry.block_assignments.len(), in_blocks.len()); + + for x in in_blocks { + assert!(entry.block_assignments.contains_key(&x)); + } + } + }; + + let check_blocks_in_store = |expected: Vec<(Hash, Option>)>| { + for (hash, with_candidates) in expected { + let (entry, with_candidates) = match with_candidates { + None => { + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &hash) + .unwrap() + .is_none()); + continue + }, + Some(i) => + (load_block_entry(store.as_ref(), &TEST_CONFIG, &hash).unwrap().unwrap(), i), + }; + + assert_eq!(entry.candidates.len(), with_candidates.len()); + + for x in with_candidates { + assert!(entry.candidates.iter().any(|(_, c)| c == &x)); + } + } + }; + + check_candidates_in_store(vec![ + (cand_hash_1, Some(vec![block_hash_b2])), + (cand_hash_2, Some(vec![block_hash_c2])), + (cand_hash_3, Some(vec![block_hash_c2, block_hash_d1])), + (cand_hash_4, Some(vec![block_hash_d1])), + (cand_hash_5, Some(vec![block_hash_d2])), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, Some(vec![])), + (block_hash_b1, Some(vec![])), + (block_hash_b2, Some(vec![cand_hash_1])), + (block_hash_c1, Some(vec![])), + (block_hash_c2, Some(vec![cand_hash_2, cand_hash_3])), + (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_d2, Some(vec![cand_hash_5])), + ]); + + let mut overlay_db = OverlayedBackend::new(&db); + canonicalize(&mut overlay_db, 3, block_hash_c1).unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!( + load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), + StoredBlockRange(4, 5) + ); + + check_candidates_in_store(vec![ + (cand_hash_1, None), + (cand_hash_2, None), + (cand_hash_3, Some(vec![block_hash_d1])), + (cand_hash_4, Some(vec![block_hash_d1])), + (cand_hash_5, None), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, None), + (block_hash_b1, None), + (block_hash_b2, None), + (block_hash_c1, None), + (block_hash_c2, None), + (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_d2, None), + ]); +} + +#[test] +fn force_approve_works() { + let (mut db, store) = make_db(); + let n_validators = 10; + + let mut overlay_db = OverlayedBackend::new(&db); + overlay_db.write_stored_block_range(StoredBlockRange(1, 4)); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + let candidate_hash = CandidateHash(Hash::repeat_byte(42)); + let single_candidate_vec = vec![(CoreIndex(0), candidate_hash)]; + let candidate_info = { + let mut candidate_info = HashMap::new(); + candidate_info.insert( + candidate_hash, + NewCandidateInfo::new( + make_candidate(ParaId::from(1_u32), Default::default()), + GroupIndex(1), + None, + ), + ); + + candidate_info + }; + + let block_hash_a = Hash::repeat_byte(1); // 1 + let block_hash_b = Hash::repeat_byte(2); + let block_hash_c = Hash::repeat_byte(3); + let block_hash_d = Hash::repeat_byte(4); // 4 + + let block_entry_a = + make_block_entry(block_hash_a, Default::default(), 1, single_candidate_vec.clone()); + let block_entry_b = + make_block_entry(block_hash_b, block_hash_a, 2, single_candidate_vec.clone()); + let block_entry_c = + make_block_entry(block_hash_c, block_hash_b, 3, single_candidate_vec.clone()); + let block_entry_d = + make_block_entry(block_hash_d, block_hash_c, 4, single_candidate_vec.clone()); + + let blocks = vec![ + block_entry_a.clone(), + block_entry_b.clone(), + block_entry_c.clone(), + block_entry_d.clone(), + ]; + + let mut overlay_db = OverlayedBackend::new(&db); + for block_entry in blocks { + add_block_entry(&mut overlay_db, block_entry.into(), n_validators, |h| { + candidate_info.get(h).map(|x| x.clone()) + }) + .unwrap(); + } + let approved_hashes = force_approve(&mut overlay_db, block_hash_d, 2).unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_a,) + .unwrap() + .unwrap() + .approved_bitfield + .all()); + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_b,) + .unwrap() + .unwrap() + .approved_bitfield + .all()); + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_c,) + .unwrap() + .unwrap() + .approved_bitfield + .not_any()); + assert!(load_block_entry(store.as_ref(), &TEST_CONFIG, &block_hash_d,) + .unwrap() + .unwrap() + .approved_bitfield + .not_any()); + assert_eq!(approved_hashes, vec![block_hash_b, block_hash_a]); +} + +#[test] +fn load_all_blocks_works() { + let (mut db, store) = make_db(); + + let parent_hash = Hash::repeat_byte(1); + let block_hash_a = Hash::repeat_byte(2); + let block_hash_b = Hash::repeat_byte(69); + let block_hash_c = Hash::repeat_byte(42); + + let block_number = 10; + + let block_entry_a = make_block_entry(block_hash_a, parent_hash, block_number, vec![]); + + let block_entry_b = make_block_entry(block_hash_b, parent_hash, block_number, vec![]); + + let block_entry_c = make_block_entry(block_hash_c, block_hash_a, block_number + 1, vec![]); + + let n_validators = 10; + + let mut overlay_db = OverlayedBackend::new(&db); + add_block_entry(&mut overlay_db, block_entry_a.clone().into(), n_validators, |_| None).unwrap(); + + // add C before B to test sorting. + add_block_entry(&mut overlay_db, block_entry_c.clone().into(), n_validators, |_| None).unwrap(); + + add_block_entry(&mut overlay_db, block_entry_b.clone().into(), n_validators, |_| None).unwrap(); + + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!( + load_all_blocks(store.as_ref(), &TEST_CONFIG).unwrap(), + vec![block_hash_a, block_hash_b, block_hash_c], + ) +} diff --git a/polkadot/node/core/approval-voting/src/backend.rs b/polkadot/node/core/approval-voting/src/backend.rs index d98f3c5fd202eaf94371210f6366c61aa98017c5..9ce25334c0fadf526162b3735d42e03c87762bc5 100644 --- a/polkadot/node/core/approval-voting/src/backend.rs +++ b/polkadot/node/core/approval-voting/src/backend.rs @@ -22,12 +22,12 @@ //! before any commit to the underlying storage is made. use polkadot_node_subsystem::SubsystemResult; -use polkadot_primitives::{BlockNumber, CandidateHash, Hash}; +use polkadot_primitives::{BlockNumber, CandidateHash, CandidateIndex, Hash}; use std::collections::HashMap; use super::{ - approval_db::v2::StoredBlockRange, + approval_db::common::StoredBlockRange, persisted_entries::{BlockEntry, CandidateEntry}, }; @@ -72,12 +72,26 @@ pub trait V1ReadBackend: Backend { fn load_candidate_entry_v1( &self, candidate_hash: &CandidateHash, + candidate_index: CandidateIndex, ) -> SubsystemResult>; /// Load a block entry from the DB with scheme version 1. fn load_block_entry_v1(&self, block_hash: &Hash) -> SubsystemResult>; } +/// A read only backend to enable db migration from version 2 of DB. +pub trait V2ReadBackend: Backend { + /// Load a candidate entry from the DB with scheme version 1. + fn load_candidate_entry_v2( + &self, + candidate_hash: &CandidateHash, + candidate_index: CandidateIndex, + ) -> SubsystemResult>; + + /// Load a block entry from the DB with scheme version 1. + fn load_block_entry_v2(&self, block_hash: &Hash) -> SubsystemResult>; +} + // Status of block range in the `OverlayedBackend`. #[derive(PartialEq)] enum BlockRangeStatus { diff --git a/polkadot/node/core/approval-voting/src/criteria.rs b/polkadot/node/core/approval-voting/src/criteria.rs index 2bb5a151fe23bfc22478623b59d30af9f372dea4..1af61e72d7affa81744d8fcdb48ed5b1e80ae4d6 100644 --- a/polkadot/node/core/approval-voting/src/criteria.rs +++ b/polkadot/node/core/approval-voting/src/criteria.rs @@ -21,7 +21,9 @@ use parity_scale_codec::{Decode, Encode}; use polkadot_node_primitives::approval::{ self as approval_types, v1::{AssignmentCert, AssignmentCertKind, DelayTranche, RelayVRFStory}, - v2::{AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, VrfOutput, VrfProof, VrfSignature}, + v2::{ + AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, VrfPreOutput, VrfProof, VrfSignature, + }, }; use polkadot_primitives::{ AssignmentId, AssignmentPair, CandidateHash, CoreIndex, GroupIndex, IndexedVec, SessionInfo, @@ -259,6 +261,7 @@ pub(crate) trait AssignmentCriteria { relay_vrf_story: RelayVRFStory, config: &Config, leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>, + enable_v2_assignments: bool, ) -> HashMap; fn check_assignment_cert( @@ -282,8 +285,9 @@ impl AssignmentCriteria for RealAssignmentCriteria { relay_vrf_story: RelayVRFStory, config: &Config, leaving_cores: Vec<(CandidateHash, CoreIndex, GroupIndex)>, + enable_v2_assignments: bool, ) -> HashMap { - compute_assignments(keystore, relay_vrf_story, config, leaving_cores, false) + compute_assignments(keystore, relay_vrf_story, config, leaving_cores, enable_v2_assignments) } fn check_assignment_cert( @@ -459,7 +463,7 @@ fn compute_relay_vrf_modulo_assignments_v1( let cert = AssignmentCert { kind: AssignmentCertKind::RelayVRFModulo { sample: rvm_sample }, vrf: VrfSignature { - output: VrfOutput(vrf_in_out.to_output()), + pre_output: VrfPreOutput(vrf_in_out.to_preout()), proof: VrfProof(vrf_proof), }, }; @@ -539,7 +543,7 @@ fn compute_relay_vrf_modulo_assignments_v2( core_bitfield: assignment_bitfield.clone(), }, vrf: VrfSignature { - output: VrfOutput(vrf_in_out.to_output()), + pre_output: VrfPreOutput(vrf_in_out.to_preout()), proof: VrfProof(vrf_proof), }, }; @@ -574,7 +578,7 @@ fn compute_relay_vrf_delay_assignments( let cert = AssignmentCertV2 { kind: AssignmentCertKindV2::RelayVRFDelay { core_index: core }, vrf: VrfSignature { - output: VrfOutput(vrf_in_out.to_output()), + pre_output: VrfPreOutput(vrf_in_out.to_preout()), proof: VrfProof(vrf_proof), }, }; @@ -689,7 +693,7 @@ pub(crate) fn check_assignment_cert( } } - let vrf_output = &assignment.vrf.output; + let vrf_pre_output = &assignment.vrf.pre_output; let vrf_proof = &assignment.vrf.proof; let first_claimed_core_index = claimed_core_indices.first_one().expect("Checked above; qed") as u32; @@ -704,7 +708,7 @@ pub(crate) fn check_assignment_cert( let (vrf_in_out, _) = public .vrf_verify_extra( relay_vrf_modulo_transcript_v2(relay_vrf_story), - &vrf_output.0, + &vrf_pre_output.0, &vrf_proof.0, assigned_cores_transcript(core_bitfield), ) @@ -753,7 +757,7 @@ pub(crate) fn check_assignment_cert( let (vrf_in_out, _) = public .vrf_verify_extra( relay_vrf_modulo_transcript_v1(relay_vrf_story, *sample), - &vrf_output.0, + &vrf_pre_output.0, &vrf_proof.0, assigned_core_transcript(CoreIndex(first_claimed_core_index)), ) @@ -791,7 +795,7 @@ pub(crate) fn check_assignment_cert( let (vrf_in_out, _) = public .vrf_verify( relay_vrf_delay_transcript(relay_vrf_story, *core_index), - &vrf_output.0, + &vrf_pre_output.0, &vrf_proof.0, ) .map_err(|_| InvalidAssignment(Reason::VRFDelayOutputMismatch))?; diff --git a/polkadot/node/core/approval-voting/src/import.rs b/polkadot/node/core/approval-voting/src/import.rs index d7667e8e405a4cd8c5f437ff3374b4da0ebc4ec2..7a56e9fd11293d1a96debd5e98b719a19c48045f 100644 --- a/polkadot/node/core/approval-voting/src/import.rs +++ b/polkadot/node/core/approval-voting/src/import.rs @@ -45,8 +45,8 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_util::{determine_new_blocks, runtime::RuntimeInfo}; use polkadot_primitives::{ - BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, ConsensusLog, CoreIndex, - GroupIndex, Hash, Header, SessionIndex, + vstaging::node_features, BlockNumber, CandidateEvent, CandidateHash, CandidateReceipt, + ConsensusLog, CoreIndex, GroupIndex, Hash, Header, SessionIndex, }; use sc_keystore::LocalKeystore; use sp_consensus_slots::Slot; @@ -56,11 +56,11 @@ use futures::{channel::oneshot, prelude::*}; use std::collections::HashMap; -use super::approval_db::v2; +use super::approval_db::v3; use crate::{ backend::{Backend, OverlayedBackend}, criteria::{AssignmentCriteria, OurAssignment}, - get_session_info, + get_extended_session_info, get_session_info, persisted_entries::CandidateEntry, time::{slot_number_to_tick, Tick}, }; @@ -214,10 +214,21 @@ async fn imported_block_info( } }; + let extended_session_info = + get_extended_session_info(env.runtime_info, ctx.sender(), block_hash, session_index).await; + let enable_v2_assignments = extended_session_info.map_or(false, |extended_session_info| { + *extended_session_info + .node_features + .get(node_features::FeatureIndex::EnableAssignmentsV2 as usize) + .as_deref() + .unwrap_or(&false) + }); + let session_info = get_session_info(env.runtime_info, ctx.sender(), block_hash, session_index) .await .ok_or(ImportedBlockInfoError::SessionInfoUnavailable)?; + gum::debug!(target: LOG_TARGET, ?enable_v2_assignments, "V2 assignments"); let (assignments, slot, relay_vrf_story) = { let unsafe_vrf = approval_types::v1::babe_unsafe_vrf_info(&block_header); @@ -239,6 +250,7 @@ async fn imported_block_info( .iter() .map(|(c_hash, _, core, group)| (*c_hash, *core, *group)) .collect(), + enable_v2_assignments, ); (assignments, slot, relay_vrf) @@ -500,7 +512,7 @@ pub(crate) async fn handle_new_head( ctx.send_message(ChainSelectionMessage::Approved(block_hash)).await; } - let block_entry = v2::BlockEntry { + let block_entry = v3::BlockEntry { block_hash, parent_hash: block_header.parent_hash, block_number: block_header.number, @@ -513,6 +525,7 @@ pub(crate) async fn handle_new_head( .collect(), approved_bitfield, children: Vec::new(), + candidates_pending_signature: Default::default(), distributed_assignments: Default::default(), }; @@ -592,7 +605,10 @@ pub(crate) async fn handle_new_head( #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::{approval_db::v2::DbBackend, RuntimeInfo, RuntimeInfoConfig}; + use crate::{ + approval_db::common::{load_block_entry, DbBackend}, + RuntimeInfo, RuntimeInfoConfig, + }; use ::test_helpers::{dummy_candidate_receipt, dummy_hash}; use assert_matches::assert_matches; use polkadot_node_primitives::{ @@ -603,6 +619,7 @@ pub(crate) mod tests { use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_node_subsystem_util::database::Database; use polkadot_primitives::{ + vstaging::{node_features::FeatureIndex, NodeFeatures}, ExecutorParams, Id as ParaId, IndexedVec, SessionInfo, ValidatorId, ValidatorIndex, }; pub(crate) use sp_consensus_babe::{ @@ -614,7 +631,7 @@ pub(crate) mod tests { pub(crate) use sp_runtime::{Digest, DigestItem}; use std::{pin::Pin, sync::Arc}; - use crate::{approval_db::v2::Config as DatabaseConfig, criteria, BlockEntry}; + use crate::{approval_db::common::Config as DatabaseConfig, criteria, BlockEntry}; const DATA_COL: u32 = 0; @@ -639,7 +656,7 @@ pub(crate) mod tests { keystore: Arc::new(LocalKeystore::in_memory()), slot_duration_millis: 6_000, clock: Box::new(MockClock::default()), - assignment_criteria: Box::new(MockAssignmentCriteria), + assignment_criteria: Box::new(MockAssignmentCriteria::default()), spans: HashMap::new(), } } @@ -654,7 +671,10 @@ pub(crate) mod tests { ) } - struct MockAssignmentCriteria; + #[derive(Default)] + struct MockAssignmentCriteria { + enable_v2: bool, + } impl AssignmentCriteria for MockAssignmentCriteria { fn compute_assignments( @@ -667,7 +687,9 @@ pub(crate) mod tests { polkadot_primitives::CoreIndex, polkadot_primitives::GroupIndex, )>, + enable_assignments_v2: bool, ) -> HashMap { + assert_eq!(enable_assignments_v2, self.enable_v2); HashMap::new() } @@ -711,154 +733,164 @@ pub(crate) mod tests { #[test] fn imported_block_info_is_good() { - let pool = TaskExecutor::new(); - let (mut ctx, mut handle) = - make_subsystem_context::(pool.clone()); - - let session = 5; - let session_info = dummy_session_info(session); - - let slot = Slot::from(10); - - let header = Header { - digest: { - let mut d = Digest::default(); - let vrf_signature = garbage_vrf_signature(); - d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF( - SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature }, - ))); - - d - }, - extrinsics_root: Default::default(), - number: 5, - state_root: Default::default(), - parent_hash: Default::default(), - }; - - let hash = header.hash(); - let make_candidate = |para_id| { - let mut r = dummy_candidate_receipt(dummy_hash()); - r.descriptor.para_id = para_id; - r.descriptor.relay_parent = hash; - r - }; - let candidates = vec![ - (make_candidate(1.into()), CoreIndex(0), GroupIndex(2)), - (make_candidate(2.into()), CoreIndex(1), GroupIndex(3)), - ]; + for enable_v2 in [false, true] { + let pool = TaskExecutor::new(); + let (mut ctx, mut handle) = + make_subsystem_context::(pool.clone()); + + let session = 5; + let session_info = dummy_session_info(session); + + let slot = Slot::from(10); + let header = Header { + digest: { + let mut d = Digest::default(); + let vrf_signature = garbage_vrf_signature(); + d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF( + SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature }, + ))); + + d + }, + extrinsics_root: Default::default(), + number: 5, + state_root: Default::default(), + parent_hash: Default::default(), + }; - let inclusion_events = candidates - .iter() - .cloned() - .map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g)) - .collect::>(); + let hash = header.hash(); + let make_candidate = |para_id| { + let mut r = dummy_candidate_receipt(dummy_hash()); + r.descriptor.para_id = para_id; + r.descriptor.relay_parent = hash; + r + }; + let candidates = vec![ + (make_candidate(1.into()), CoreIndex(0), GroupIndex(2)), + (make_candidate(2.into()), CoreIndex(1), GroupIndex(3)), + ]; - let test_fut = { - let included_candidates = candidates + let inclusion_events = candidates .iter() - .map(|(r, c, g)| (r.hash(), r.clone(), *c, *g)) + .cloned() + .map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g)) .collect::>(); - let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig { - keystore: None, - session_cache_lru_size: DISPUTE_WINDOW.get(), - }); + let test_fut = { + let included_candidates = candidates + .iter() + .map(|(r, c, g)| (r.hash(), r.clone(), *c, *g)) + .collect::>(); + + let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig { + keystore: None, + session_cache_lru_size: DISPUTE_WINDOW.get(), + }); + + let header = header.clone(); + Box::pin(async move { + let env = ImportedBlockInfoEnv { + runtime_info: &mut runtime_info, + assignment_criteria: &MockAssignmentCriteria { enable_v2 }, + keystore: &LocalKeystore::in_memory(), + }; - let header = header.clone(); - Box::pin(async move { - let env = ImportedBlockInfoEnv { - runtime_info: &mut runtime_info, - assignment_criteria: &MockAssignmentCriteria, - keystore: &LocalKeystore::in_memory(), - }; + let info = + imported_block_info(&mut ctx, env, hash, &header, &Some(4)).await.unwrap(); - let info = - imported_block_info(&mut ctx, env, hash, &header, &Some(4)).await.unwrap(); + assert_eq!(info.included_candidates, included_candidates); + assert_eq!(info.session_index, session); + assert!(info.assignments.is_empty()); + assert_eq!(info.n_validators, 0); + assert_eq!(info.slot, slot); + assert!(info.force_approve.is_none()); + }) + }; - assert_eq!(info.included_candidates, included_candidates); - assert_eq!(info.session_index, session); - assert!(info.assignments.is_empty()); - assert_eq!(info.n_validators, 0); - assert_eq!(info.slot, slot); - assert!(info.force_approve.is_none()); - }) - }; + let aux_fut = Box::pin(async move { + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + h, + RuntimeApiRequest::CandidateEvents(c_tx), + )) => { + assert_eq!(h, hash); + let _ = c_tx.send(Ok(inclusion_events)); + } + ); - let aux_fut = Box::pin(async move { - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - h, - RuntimeApiRequest::CandidateEvents(c_tx), - )) => { - assert_eq!(h, hash); - let _ = c_tx.send(Ok(inclusion_events)); - } - ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + h, + RuntimeApiRequest::SessionIndexForChild(c_tx), + )) => { + assert_eq!(h, header.parent_hash); + let _ = c_tx.send(Ok(session)); + } + ); - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - h, - RuntimeApiRequest::SessionIndexForChild(c_tx), - )) => { - assert_eq!(h, header.parent_hash); - let _ = c_tx.send(Ok(session)); - } - ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + h, + RuntimeApiRequest::CurrentBabeEpoch(c_tx), + )) => { + assert_eq!(h, hash); + let _ = c_tx.send(Ok(BabeEpoch { + epoch_index: session as _, + start_slot: Slot::from(0), + duration: 200, + authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)], + randomness: [0u8; 32], + config: BabeEpochConfiguration { + c: (1, 4), + allowed_slots: AllowedSlots::PrimarySlots, + }, + })); + } + ); - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - h, - RuntimeApiRequest::CurrentBabeEpoch(c_tx), - )) => { - assert_eq!(h, hash); - let _ = c_tx.send(Ok(BabeEpoch { - epoch_index: session as _, - start_slot: Slot::from(0), - duration: 200, - authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)], - randomness: [0u8; 32], - config: BabeEpochConfiguration { - c: (1, 4), - allowed_slots: AllowedSlots::PrimarySlots, - }, - })); - } - ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + req_block_hash, + RuntimeApiRequest::SessionInfo(idx, si_tx), + ) + ) => { + assert_eq!(session, idx); + assert_eq!(req_block_hash, hash); + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request( - req_block_hash, - RuntimeApiRequest::SessionInfo(idx, si_tx), - ) - ) => { - assert_eq!(session, idx); - assert_eq!(req_block_hash, hash); - si_tx.send(Ok(Some(session_info.clone()))).unwrap(); - } - ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + req_block_hash, + RuntimeApiRequest::SessionExecutorParams(idx, si_tx), + ) + ) => { + assert_eq!(session, idx); + assert_eq!(req_block_hash, hash); + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request( - req_block_hash, - RuntimeApiRequest::SessionExecutorParams(idx, si_tx), - ) - ) => { - assert_eq!(session, idx); - assert_eq!(req_block_hash, hash); - si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); - } - ); - }); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::repeat(enable_v2, FeatureIndex::EnableAssignmentsV2 as usize + 1))).unwrap(); + } + ); + }); - futures::executor::block_on(futures::future::join(test_fut, aux_fut)); + futures::executor::block_on(futures::future::join(test_fut, aux_fut)); + } } #[test] @@ -906,7 +938,7 @@ pub(crate) mod tests { Box::pin(async move { let env = ImportedBlockInfoEnv { runtime_info: &mut runtime_info, - assignment_criteria: &MockAssignmentCriteria, + assignment_criteria: &MockAssignmentCriteria::default(), keystore: &LocalKeystore::in_memory(), }; @@ -987,6 +1019,15 @@ pub(crate) mod tests { si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); } ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); }); futures::executor::block_on(futures::future::join(test_fut, aux_fut)); @@ -1036,7 +1077,7 @@ pub(crate) mod tests { Box::pin(async move { let env = ImportedBlockInfoEnv { runtime_info: &mut runtime_info, - assignment_criteria: &MockAssignmentCriteria, + assignment_criteria: &MockAssignmentCriteria::default(), keystore: &LocalKeystore::in_memory(), }; @@ -1134,7 +1175,7 @@ pub(crate) mod tests { Box::pin(async move { let env = ImportedBlockInfoEnv { runtime_info: &mut runtime_info, - assignment_criteria: &MockAssignmentCriteria, + assignment_criteria: &MockAssignmentCriteria::default(), keystore: &LocalKeystore::in_memory(), }; @@ -1221,6 +1262,15 @@ pub(crate) mod tests { si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); } ); + + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); }); futures::executor::block_on(futures::future::join(test_fut, aux_fut)); @@ -1301,7 +1351,7 @@ pub(crate) mod tests { let (state, mut session_info_provider) = single_session_state(); overlay_db.write_block_entry( - v2::BlockEntry { + v3::BlockEntry { block_hash: parent_hash, parent_hash: Default::default(), block_number: 4, @@ -1311,6 +1361,7 @@ pub(crate) mod tests { candidates: Vec::new(), approved_bitfield: Default::default(), children: Vec::new(), + candidates_pending_signature: Default::default(), distributed_assignments: Default::default(), } .into(), @@ -1343,11 +1394,10 @@ pub(crate) mod tests { assert_eq!(candidates[1].1.approvals().len(), 6); // the first candidate should be insta-approved // the second should not - let entry: BlockEntry = - v2::load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash) - .unwrap() - .unwrap() - .into(); + let entry: BlockEntry = load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash) + .unwrap() + .unwrap() + .into(); assert!(entry.is_candidate_approved(&candidates[0].0)); assert!(!entry.is_candidate_approved(&candidates[1].0)); }) @@ -1438,6 +1488,15 @@ pub(crate) mod tests { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + assert_matches!( handle.recv().await, AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index ef01727b7eb681a180608144cee26d91d0bbda44..af76b576d7cab9c0f9c9242973d33e0beba6c422 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,14 +21,15 @@ //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. +use itertools::Itertools; use jaeger::{hash_to_trace_identifier, PerLeafSpan}; use polkadot_node_jaeger as jaeger; use polkadot_node_primitives::{ approval::{ - v1::{BlockApprovalMeta, DelayTranche, IndirectSignedApprovalVote}, + v1::{BlockApprovalMeta, DelayTranche}, v2::{ AssignmentCertKindV2, BitfieldError, CandidateBitfield, CoreBitfield, - IndirectAssignmentCertV2, + IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2, }, }, ValidationResult, DISPUTE_WINDOW, @@ -53,9 +54,10 @@ use polkadot_node_subsystem_util::{ TimeoutExt, }; use polkadot_primitives::{ - ApprovalVote, BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, DisputeStatement, - ExecutorParams, GroupIndex, Hash, PvfExecKind, SessionIndex, SessionInfo, - ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, + vstaging::{ApprovalVoteMultipleCandidates, ApprovalVotingParams}, + BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, DisputeStatement, ExecutorParams, + GroupIndex, Hash, PvfExecKind, SessionIndex, SessionInfo, ValidDisputeStatementKind, + ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, }; use sc_keystore::LocalKeystore; use sp_application_crypto::Pair; @@ -67,9 +69,11 @@ use futures::{ future::{BoxFuture, RemoteHandle}, prelude::*, stream::FuturesUnordered, + StreamExt, }; use std::{ + cmp::min, collections::{ btree_map::Entry as BTMEntry, hash_map::Entry as HMEntry, BTreeMap, HashMap, HashSet, }, @@ -83,7 +87,7 @@ use approval_checking::RequiredTranches; use bitvec::{order::Lsb0, vec::BitVec}; use criteria::{AssignmentCriteria, RealAssignmentCriteria}; use persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry}; -use time::{slot_number_to_tick, Clock, ClockExt, SystemClock, Tick}; +use time::{slot_number_to_tick, Clock, ClockExt, DelayedApprovalTimer, SystemClock, Tick}; mod approval_checking; pub mod approval_db; @@ -95,9 +99,11 @@ mod persisted_entries; mod time; use crate::{ - approval_db::v2::{Config as DatabaseConfig, DbBackend}, + approval_checking::{Check, TranchesToApproveResult}, + approval_db::common::{Config as DatabaseConfig, DbBackend}, backend::{Backend, OverlayedBackend}, criteria::InvalidAssignmentReason, + persisted_entries::OurApproval, }; #[cfg(test)] @@ -115,6 +121,9 @@ const TICK_TOO_FAR_IN_FUTURE: Tick = 20; // 10 seconds. const APPROVAL_DELAY: Tick = 2; pub(crate) const LOG_TARGET: &str = "parachain::approval-voting"; +// The max number of ticks we delay sending the approval after we are ready to issue the approval +const MAX_APPROVAL_COALESCE_WAIT_TICKS: Tick = 12; + /// Configuration for the approval voting subsystem #[derive(Debug, Clone)] pub struct Config { @@ -158,7 +167,14 @@ struct MetricsInner { assignments_produced: prometheus::Histogram, approvals_produced_total: prometheus::CounterVec, no_shows_total: prometheus::Counter, + // The difference from `no_shows_total` is that this counts all observed no-shows at any + // moment in time. While `no_shows_total` catches that the no-shows at the moment the candidate + // is approved, approvals might arrive late and `no_shows_total` wouldn't catch that number. + observed_no_shows: prometheus::Counter, + approved_by_one_third: prometheus::Counter, wakeups_triggered_total: prometheus::Counter, + coalesced_approvals_buckets: prometheus::Histogram, + coalesced_approvals_delay: prometheus::Histogram, candidate_approval_time_ticks: prometheus::Histogram, block_approval_time_ticks: prometheus::Histogram, time_db_transaction: prometheus::Histogram, @@ -184,6 +200,22 @@ impl Metrics { } } + fn on_approval_coalesce(&self, num_coalesced: u32) { + if let Some(metrics) = &self.0 { + // Count how many candidates we covered with this coalesced approvals, + // so that the heat-map really gives a good understanding of the scales. + for _ in 0..num_coalesced { + metrics.coalesced_approvals_buckets.observe(num_coalesced as f64) + } + } + } + + fn on_delayed_approval(&self, delayed_ticks: u64) { + if let Some(metrics) = &self.0 { + metrics.coalesced_approvals_delay.observe(delayed_ticks as f64) + } + } + fn on_approval_stale(&self) { if let Some(metrics) = &self.0 { metrics.approvals_produced_total.with_label_values(&["stale"]).inc() @@ -220,6 +252,18 @@ impl Metrics { } } + fn on_observed_no_shows(&self, n: usize) { + if let Some(metrics) = &self.0 { + metrics.observed_no_shows.inc_by(n as u64); + } + } + + fn on_approved_by_one_third(&self) { + if let Some(metrics) = &self.0 { + metrics.approved_by_one_third.inc(); + } + } + fn on_wakeup(&self) { if let Some(metrics) = &self.0 { metrics.wakeups_triggered_total.inc(); @@ -297,6 +341,13 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + observed_no_shows: prometheus::register( + prometheus::Counter::new( + "polkadot_parachain_approvals_observed_no_shows_total", + "Number of observed no shows at any moment in time", + )?, + registry, + )?, wakeups_triggered_total: prometheus::register( prometheus::Counter::new( "polkadot_parachain_approvals_wakeups_total", @@ -313,6 +364,31 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + coalesced_approvals_buckets: prometheus::register( + prometheus::Histogram::with_opts( + prometheus::HistogramOpts::new( + "polkadot_parachain_approvals_coalesced_approvals_buckets", + "Number of coalesced approvals.", + ).buckets(vec![1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]), + )?, + registry, + )?, + coalesced_approvals_delay: prometheus::register( + prometheus::Histogram::with_opts( + prometheus::HistogramOpts::new( + "polkadot_parachain_approvals_coalescing_delay", + "Number of ticks we delay the sending of a candidate approval", + ).buckets(vec![1.1, 2.1, 3.1, 4.1, 6.1, 8.1, 12.1, 20.1, 32.1]), + )?, + registry, + )?, + approved_by_one_third: prometheus::register( + prometheus::Counter::new( + "polkadot_parachain_approved_by_one_third", + "Number of candidates where more than one third had to vote ", + )?, + registry, + )?, block_approval_time_ticks: prometheus::register( prometheus::Histogram::with_opts( prometheus::HistogramOpts::new( @@ -383,8 +459,8 @@ impl ApprovalVotingSubsystem { /// The operation is not allowed for blocks older than the last finalized one. pub fn revert_to(&self, hash: Hash) -> Result<(), SubsystemError> { let config = - approval_db::v2::Config { col_approval_data: self.db_config.col_approval_data }; - let mut backend = approval_db::v2::DbBackend::new(self.db.clone(), config); + approval_db::common::Config { col_approval_data: self.db_config.col_approval_data }; + let mut backend = approval_db::common::DbBackend::new(self.db.clone(), config); let mut overlay = OverlayedBackend::new(&backend); ops::revert_to(&mut overlay, hash)?; @@ -559,6 +635,7 @@ struct ApprovalStatus { required_tranches: RequiredTranches, tranche_now: DelayTranche, block_tick: Tick, + last_no_shows: usize, } #[derive(Copy, Clone)] @@ -733,22 +810,73 @@ impl State { ); if let Some(approval_entry) = candidate_entry.approval_entry(&block_hash) { - let required_tranches = approval_checking::tranches_to_approve( - approval_entry, - candidate_entry.approvals(), - tranche_now, - block_tick, - no_show_duration, - session_info.needed_approvals as _, - ); + let TranchesToApproveResult { required_tranches, total_observed_no_shows } = + approval_checking::tranches_to_approve( + approval_entry, + candidate_entry.approvals(), + tranche_now, + block_tick, + no_show_duration, + session_info.needed_approvals as _, + ); - let status = ApprovalStatus { required_tranches, block_tick, tranche_now }; + let status = ApprovalStatus { + required_tranches, + block_tick, + tranche_now, + last_no_shows: total_observed_no_shows, + }; Some((approval_entry, status)) } else { None } } + + // Returns the approval voting params from the RuntimeApi. + #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] + async fn get_approval_voting_params_or_default( + &self, + ctx: &mut Context, + session_index: SessionIndex, + block_hash: Hash, + ) -> Option { + let (s_tx, s_rx) = oneshot::channel(); + + ctx.send_message(RuntimeApiMessage::Request( + block_hash, + RuntimeApiRequest::ApprovalVotingParams(session_index, s_tx), + )) + .await; + + match s_rx.await { + Ok(Ok(params)) => { + gum::trace!( + target: LOG_TARGET, + approval_voting_params = ?params, + session = ?session_index, + "Using the following subsystem params" + ); + Some(params) + }, + Ok(Err(err)) => { + gum::debug!( + target: LOG_TARGET, + ?err, + "Could not request approval voting params from runtime" + ); + None + }, + Err(err) => { + gum::debug!( + target: LOG_TARGET, + ?err, + "Could not request approval voting params from runtime" + ); + None + }, + } + } } #[derive(Debug, Clone)] @@ -807,6 +935,7 @@ where }); let mut wakeups = Wakeups::default(); let mut currently_checking_set = CurrentlyCheckingSet::default(); + let mut delayed_approvals_timers = DelayedApprovalTimer::default(); let mut approvals_cache = LruMap::new(ByLength::new(APPROVAL_CACHE_SIZE)); let mut last_finalized_height: Option = { @@ -885,17 +1014,49 @@ where } actions + }, + (block_hash, validator_index) = delayed_approvals_timers.select_next_some() => { + gum::debug!( + target: LOG_TARGET, + ?block_hash, + ?validator_index, + "Sign approval for multiple candidates", + ); + + match maybe_create_signature( + &mut overlayed_db, + &mut session_info_provider, + &state, + &mut ctx, + block_hash, + validator_index, + &subsystem.metrics, + ).await { + Ok(Some(next_wakeup)) => { + delayed_approvals_timers.maybe_arm_timer(next_wakeup, state.clock.as_ref(), block_hash, validator_index); + }, + Ok(None) => {} + Err(err) => { + gum::error!( + target: LOG_TARGET, + ?err, + "Failed to create signature", + ); + } + } + vec![] } }; if handle_actions( &mut ctx, - &state, + &mut state, &mut overlayed_db, &mut session_info_provider, &subsystem.metrics, &mut wakeups, &mut currently_checking_set, + &mut delayed_approvals_timers, &mut approvals_cache, &mut subsystem.mode, actions, @@ -937,12 +1098,13 @@ where #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] async fn handle_actions( ctx: &mut Context, - state: &State, + state: &mut State, overlayed_db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, metrics: &Metrics, wakeups: &mut Wakeups, currently_checking_set: &mut CurrentlyCheckingSet, + delayed_approvals_timers: &mut DelayedApprovalTimer, approvals_cache: &mut LruMap, mode: &mut Mode, actions: Vec, @@ -973,6 +1135,7 @@ async fn handle_actions( session_info_provider, metrics, candidate_hash, + delayed_approvals_timers, approval_request, ) .await? @@ -1075,7 +1238,11 @@ async fn handle_actions( Action::BecomeActive => { *mode = Mode::Active; - let messages = distribution_messages_for_activation(overlayed_db, state)?; + let messages = distribution_messages_for_activation( + overlayed_db, + state, + delayed_approvals_timers, + )?; ctx.send_messages(messages.into_iter()).await; }, @@ -1101,7 +1268,7 @@ fn cores_to_candidate_indices( .iter() .position(|(core_index, _)| core_index.0 == claimed_core_index as u32) { - candidate_indices.push(candidate_index as CandidateIndex); + candidate_indices.push(candidate_index as _); } } @@ -1134,6 +1301,7 @@ fn get_assignment_core_indices( fn distribution_messages_for_activation( db: &OverlayedBackend<'_, impl Backend>, state: &State, + delayed_approvals_timers: &mut DelayedApprovalTimer, ) -> SubsystemResult> { let all_blocks: Vec = db.load_all_blocks()?; @@ -1172,8 +1340,8 @@ fn distribution_messages_for_activation( slot: block_entry.slot(), session: block_entry.session(), }); - - for (i, (_, candidate_hash)) in block_entry.candidates().iter().enumerate() { + let mut signatures_queued = HashSet::new(); + for (_, candidate_hash) in block_entry.candidates() { let _candidate_span = distribution_message_span.child("candidate").with_candidate(*candidate_hash); let candidate_entry = match db.load_candidate_entry(&candidate_hash)? { @@ -1200,6 +1368,15 @@ fn distribution_messages_for_activation( &candidate_hash, &block_entry, ) { + if block_entry.has_candidates_pending_signature() { + delayed_approvals_timers.maybe_arm_timer( + state.clock.tick_now(), + state.clock.as_ref(), + block_entry.block_hash(), + assignment.validator_index(), + ) + } + match cores_to_candidate_indices( &claimed_core_indices, &block_entry, @@ -1267,15 +1444,19 @@ fn distribution_messages_for_activation( continue }, } - - messages.push(ApprovalDistributionMessage::DistributeApproval( - IndirectSignedApprovalVote { - block_hash, - candidate_index: i as _, - validator: assignment.validator_index(), - signature: approval_sig, - }, - )); + if signatures_queued + .insert(approval_sig.signed_candidates_indices.clone()) + { + messages.push(ApprovalDistributionMessage::DistributeApproval( + IndirectSignedApprovalVoteV2 { + block_hash, + candidate_indices: approval_sig + .signed_candidates_indices, + validator: assignment.validator_index(), + signature: approval_sig.signature, + }, + )) + }; } else { gum::warn!( target: LOG_TARGET, @@ -1481,7 +1662,7 @@ async fn get_approval_signatures_for_candidate( ctx: &mut Context, db: &OverlayedBackend<'_, impl Backend>, candidate_hash: CandidateHash, - tx: oneshot::Sender>, + tx: oneshot::Sender, ValidatorSignature)>>, ) -> SubsystemResult<()> { let send_votes = |votes| { if let Err(_) = tx.send(votes) { @@ -1507,6 +1688,11 @@ async fn get_approval_signatures_for_candidate( let relay_hashes = entry.block_assignments.keys(); let mut candidate_indices = HashSet::new(); + let mut candidate_indices_to_candidate_hashes: HashMap< + Hash, + HashMap, + > = HashMap::new(); + // Retrieve `CoreIndices`/`CandidateIndices` as required by approval-distribution: for hash in relay_hashes { let entry = match db.load_block_entry(hash)? { @@ -1524,8 +1710,11 @@ async fn get_approval_signatures_for_candidate( for (candidate_index, (_core_index, c_hash)) in entry.candidates().iter().enumerate() { if c_hash == &candidate_hash { candidate_indices.insert((*hash, candidate_index as u32)); - break } + candidate_indices_to_candidate_hashes + .entry(*hash) + .or_default() + .insert(candidate_index as _, *c_hash); } } @@ -1550,7 +1739,55 @@ async fn get_approval_signatures_for_candidate( target: LOG_TARGET, "Request for approval signatures got cancelled by `approval-distribution`." ), - Some(Ok(votes)) => send_votes(votes), + Some(Ok(votes)) => { + let votes = votes + .into_iter() + .filter_map(|(validator_index, (hash, signed_candidates_indices, signature))| { + let candidates_hashes = candidate_indices_to_candidate_hashes.get(&hash); + + if candidates_hashes.is_none() { + gum::warn!( + target: LOG_TARGET, + ?hash, + "Possible bug! Could not find map of candidate_hashes for block hash received from approval-distribution" + ); + } + + let num_signed_candidates = signed_candidates_indices.len(); + + let signed_candidates_hashes: Vec = + signed_candidates_indices + .into_iter() + .filter_map(|candidate_index| { + candidates_hashes.and_then(|candidate_hashes| { + if let Some(candidate_hash) = + candidate_hashes.get(&candidate_index) + { + Some(*candidate_hash) + } else { + gum::warn!( + target: LOG_TARGET, + ?candidate_index, + "Possible bug! Could not find candidate hash for candidate_index coming from approval-distribution" + ); + None + } + }) + }) + .collect(); + if num_signed_candidates == signed_candidates_hashes.len() { + Some((validator_index, (signed_candidates_hashes, signature))) + } else { + gum::warn!( + target: LOG_TARGET, + "Possible bug! Could not find all hashes for candidates coming from approval-distribution" + ); + None + } + }) + .collect(); + send_votes(votes) + }, } }; @@ -2184,7 +2421,7 @@ async fn check_and_import_approval( db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, metrics: &Metrics, - approval: IndirectSignedApprovalVote, + approval: IndirectSignedApprovalVoteV2, with_response: impl FnOnce(ApprovalCheckResult) -> T, ) -> SubsystemResult<(Vec, T)> where @@ -2196,13 +2433,12 @@ where return Ok((Vec::new(), t)) }}; } - let mut span = state .spans .get(&approval.block_hash) .map(|span| span.child("check-and-import-approval")) .unwrap_or_else(|| jaeger::Span::new(approval.block_hash, "check-and-import-approval")) - .with_uint_tag("candidate-index", approval.candidate_index as u64) + .with_string_fmt_debug_tag("candidate-index", approval.candidate_indices.clone()) .with_relay_parent(approval.block_hash) .with_stage(jaeger::Stage::ApprovalChecking); @@ -2215,105 +2451,163 @@ where }, }; - let session_info = match get_session_info( - session_info_provider, - sender, - approval.block_hash, - block_entry.session(), - ) - .await - { - Some(s) => s, - None => { - respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownSessionIndex( - block_entry.session() - ),)) - }, - }; + let approved_candidates_info: Result, ApprovalCheckError> = + approval + .candidate_indices + .iter_ones() + .map(|candidate_index| { + block_entry + .candidate(candidate_index) + .ok_or(ApprovalCheckError::InvalidCandidateIndex(candidate_index as _)) + .map(|candidate| (candidate_index as _, candidate.1)) + }) + .collect(); - let approved_candidate_hash = match block_entry.candidate(approval.candidate_index as usize) { - Some((_, h)) => *h, - None => respond_early!(ApprovalCheckResult::Bad( - ApprovalCheckError::InvalidCandidateIndex(approval.candidate_index), - )), + let approved_candidates_info = match approved_candidates_info { + Ok(approved_candidates_info) => approved_candidates_info, + Err(err) => { + respond_early!(ApprovalCheckResult::Bad(err)) + }, }; - span.add_string_tag("candidate-hash", format!("{:?}", approved_candidate_hash)); + span.add_string_tag("candidate-hashes", format!("{:?}", approved_candidates_info)); span.add_string_tag( - "traceID", - format!("{:?}", hash_to_trace_identifier(approved_candidate_hash.0)), + "traceIDs", + format!( + "{:?}", + approved_candidates_info + .iter() + .map(|(_, approved_candidate_hash)| hash_to_trace_identifier( + approved_candidate_hash.0 + )) + .collect_vec() + ), ); - let pubkey = match session_info.validators.get(approval.validator) { - Some(k) => k, - None => respond_early!(ApprovalCheckResult::Bad( - ApprovalCheckError::InvalidValidatorIndex(approval.validator), - )), - }; + { + let session_info = match get_session_info( + session_info_provider, + sender, + approval.block_hash, + block_entry.session(), + ) + .await + { + Some(s) => s, + None => { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownSessionIndex( + block_entry.session() + ),)) + }, + }; - // Signature check: - match DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking).check_signature( - &pubkey, - approved_candidate_hash, - block_entry.session(), - &approval.signature, - ) { - Err(_) => respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidSignature( - approval.validator - ),)), - Ok(()) => {}, - }; + let pubkey = match session_info.validators.get(approval.validator) { + Some(k) => k, + None => respond_early!(ApprovalCheckResult::Bad( + ApprovalCheckError::InvalidValidatorIndex(approval.validator), + )), + }; - let candidate_entry = match db.load_candidate_entry(&approved_candidate_hash)? { - Some(c) => c, - None => { - respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidCandidate( - approval.candidate_index, - approved_candidate_hash - ),)) - }, - }; + gum::trace!( + target: LOG_TARGET, + "Received approval for num_candidates {:}", + approval.candidate_indices.count_ones() + ); - // Don't accept approvals until assignment. - match candidate_entry.approval_entry(&approval.block_hash) { - None => { - respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::Internal( - approval.block_hash, - approved_candidate_hash - ),)) - }, - Some(e) if !e.is_assigned(approval.validator) => { - respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::NoAssignment( - approval.validator - ),)) - }, - _ => {}, + let candidate_hashes: Vec = + approved_candidates_info.iter().map(|candidate| candidate.1).collect(); + // Signature check: + match DisputeStatement::Valid( + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone()), + ) + .check_signature( + &pubkey, + if let Some(candidate_hash) = candidate_hashes.first() { + *candidate_hash + } else { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidValidatorIndex( + approval.validator + ),)) + }, + block_entry.session(), + &approval.signature, + ) { + Err(_) => { + gum::error!( + target: LOG_TARGET, + "Error while checking signature {:}", + approval.candidate_indices.count_ones() + ); + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidSignature( + approval.validator + ),)) + }, + Ok(()) => {}, + }; } - // importing the approval can be heavy as it may trigger acceptance for a series of blocks. - let t = with_response(ApprovalCheckResult::Accepted); + let mut actions = Vec::new(); + for (approval_candidate_index, approved_candidate_hash) in approved_candidates_info { + let block_entry = match db.load_block_entry(&approval.block_hash)? { + Some(b) => b, + None => { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock( + approval.block_hash + ),)) + }, + }; - gum::trace!( - target: LOG_TARGET, - validator_index = approval.validator.0, - validator = ?pubkey, - candidate_hash = ?approved_candidate_hash, - para_id = ?candidate_entry.candidate_receipt().descriptor.para_id, - "Importing approval vote", - ); + let candidate_entry = match db.load_candidate_entry(&approved_candidate_hash)? { + Some(c) => c, + None => { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidCandidate( + approval_candidate_index, + approved_candidate_hash + ),)) + }, + }; - let actions = advance_approval_state( - sender, - state, - db, - session_info_provider, - &metrics, - block_entry, - approved_candidate_hash, - candidate_entry, - ApprovalStateTransition::RemoteApproval(approval.validator), - ) - .await; + // Don't accept approvals until assignment. + match candidate_entry.approval_entry(&approval.block_hash) { + None => { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::Internal( + approval.block_hash, + approved_candidate_hash + ),)) + }, + Some(e) if !e.is_assigned(approval.validator) => { + respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::NoAssignment( + approval.validator + ),)) + }, + _ => {}, + } + + gum::debug!( + target: LOG_TARGET, + validator_index = approval.validator.0, + candidate_hash = ?approved_candidate_hash, + para_id = ?candidate_entry.candidate_receipt().descriptor.para_id, + "Importing approval vote", + ); + + let new_actions = advance_approval_state( + sender, + state, + db, + session_info_provider, + &metrics, + block_entry, + approved_candidate_hash, + candidate_entry, + ApprovalStateTransition::RemoteApproval(approval.validator), + ) + .await; + actions.extend(new_actions); + } + + // importing the approval can be heavy as it may trigger acceptance for a series of blocks. + let t = with_response(ApprovalCheckResult::Accepted); Ok((actions, t)) } @@ -2321,7 +2615,7 @@ where #[derive(Debug)] enum ApprovalStateTransition { RemoteApproval(ValidatorIndex), - LocalApproval(ValidatorIndex, ValidatorSignature), + LocalApproval(ValidatorIndex), WakeupProcessed, } @@ -2329,7 +2623,7 @@ impl ApprovalStateTransition { fn validator_index(&self) -> Option { match *self { ApprovalStateTransition::RemoteApproval(v) | - ApprovalStateTransition::LocalApproval(v, _) => Some(v), + ApprovalStateTransition::LocalApproval(v) => Some(v), ApprovalStateTransition::WakeupProcessed => None, } } @@ -2337,7 +2631,7 @@ impl ApprovalStateTransition { fn is_local_approval(&self) -> bool { match *self { ApprovalStateTransition::RemoteApproval(_) => false, - ApprovalStateTransition::LocalApproval(_, _) => true, + ApprovalStateTransition::LocalApproval(_) => true, ApprovalStateTransition::WakeupProcessed => false, } } @@ -2404,7 +2698,16 @@ where // assignment tick of `now - APPROVAL_DELAY` - that is, that // all counted assignments are at least `APPROVAL_DELAY` ticks old. let is_approved = check.is_approved(tick_now.saturating_sub(APPROVAL_DELAY)); - + if status.last_no_shows != 0 { + metrics.on_observed_no_shows(status.last_no_shows); + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?block_hash, + last_no_shows = ?status.last_no_shows, + "Observed no_shows", + ); + } if is_approved { gum::trace!( target: LOG_TARGET, @@ -2422,6 +2725,12 @@ where if no_shows != 0 { metrics.on_no_shows(no_shows); } + if check == Check::ApprovedOneThird { + // No-shows are not counted when more than one third of validators approve a + // candidate, so count candidates where more than one third of validators had to + // approve it, this is indicative of something breaking. + metrics.on_approved_by_one_third() + } metrics.on_candidate_approved(status.tranche_now as _); @@ -2430,6 +2739,10 @@ where actions.push(Action::NoteApprovedInChainSelection(block_hash)); } + db.write_block_entry(block_entry.into()); + } else if transition.is_local_approval() { + // Local approvals always update the block_entry, so we need to flush it to + // the database. db.write_block_entry(block_entry.into()); } @@ -2458,10 +2771,6 @@ where approval_entry.mark_approved(); } - if let ApprovalStateTransition::LocalApproval(_, ref sig) = transition { - approval_entry.import_approval_sig(sig.clone()); - } - actions.extend(schedule_wakeup_action( &approval_entry, block_hash, @@ -2599,7 +2908,7 @@ async fn process_wakeup( let should_trigger = should_trigger_assignment( &approval_entry, &candidate_entry, - tranches_to_approve, + tranches_to_approve.required_tranches, tranche_now, ); @@ -2924,11 +3233,12 @@ async fn launch_approval( #[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] async fn issue_approval( ctx: &mut Context, - state: &State, + state: &mut State, db: &mut OverlayedBackend<'_, impl Backend>, session_info_provider: &mut RuntimeInfo, metrics: &Metrics, candidate_hash: CandidateHash, + delayed_approvals_timers: &mut DelayedApprovalTimer, ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest, ) -> SubsystemResult> { let mut issue_approval_span = state @@ -2942,7 +3252,7 @@ async fn issue_approval( .with_validator_index(validator_index) .with_stage(jaeger::Stage::ApprovalChecking); - let block_entry = match db.load_block_entry(&block_hash)? { + let mut block_entry = match db.load_block_entry(&block_hash)? { Some(b) => b, None => { // not a cause for alarm - just lost a race with pruning, most likely. @@ -2968,21 +3278,6 @@ async fn issue_approval( }; issue_approval_span.add_int_tag("candidate_index", candidate_index as i64); - let session_info = match get_session_info( - session_info_provider, - ctx.sender(), - block_entry.parent_hash(), - block_entry.session(), - ) - .await - { - Some(s) => s, - None => { - metrics.on_approval_error(); - return Ok(Vec::new()) - }, - }; - let candidate_hash = match block_entry.candidate(candidate_index as usize) { Some((_, h)) => *h, None => { @@ -3013,10 +3308,149 @@ async fn issue_approval( }, }; + let session_info = match get_session_info( + session_info_provider, + ctx.sender(), + block_entry.parent_hash(), + block_entry.session(), + ) + .await + { + Some(s) => s, + None => return Ok(Vec::new()), + }; + + if block_entry + .defer_candidate_signature( + candidate_index as _, + candidate_hash, + compute_delayed_approval_sending_tick( + state, + &block_entry, + &candidate_entry, + session_info, + &metrics, + ), + ) + .is_some() + { + gum::error!( + target: LOG_TARGET, + ?candidate_hash, + ?block_hash, + validator_index = validator_index.0, + "Possible bug, we shouldn't have to defer a candidate more than once", + ); + } + + gum::info!( + target: LOG_TARGET, + ?candidate_hash, + ?block_hash, + validator_index = validator_index.0, + "Ready to issue approval vote", + ); + + let actions = advance_approval_state( + ctx.sender(), + state, + db, + session_info_provider, + metrics, + block_entry, + candidate_hash, + candidate_entry, + ApprovalStateTransition::LocalApproval(validator_index as _), + ) + .await; + + if let Some(next_wakeup) = maybe_create_signature( + db, + session_info_provider, + state, + ctx, + block_hash, + validator_index, + metrics, + ) + .await? + { + delayed_approvals_timers.maybe_arm_timer( + next_wakeup, + state.clock.as_ref(), + block_hash, + validator_index, + ); + } + Ok(actions) +} + +// Create signature for the approved candidates pending signatures +#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)] +async fn maybe_create_signature( + db: &mut OverlayedBackend<'_, impl Backend>, + session_info_provider: &mut RuntimeInfo, + state: &State, + ctx: &mut Context, + block_hash: Hash, + validator_index: ValidatorIndex, + metrics: &Metrics, +) -> SubsystemResult> { + let mut block_entry = match db.load_block_entry(&block_hash)? { + Some(b) => b, + None => { + // not a cause for alarm - just lost a race with pruning, most likely. + metrics.on_approval_stale(); + gum::debug!( + target: LOG_TARGET, + "Could not find block that needs signature {:}", block_hash + ); + return Ok(None) + }, + }; + + let approval_params = state + .get_approval_voting_params_or_default(ctx, block_entry.session(), block_hash) + .await + .unwrap_or_default(); + + gum::trace!( + target: LOG_TARGET, + "Candidates pending signatures {:}", block_entry.num_candidates_pending_signature() + ); + let tick_now = state.clock.tick_now(); + + let (candidates_to_sign, sign_no_later_then) = block_entry + .get_candidates_that_need_signature(tick_now, approval_params.max_approval_coalesce_count); + + let (candidates_hashes, candidates_indices) = match candidates_to_sign { + Some(candidates_to_sign) => candidates_to_sign, + None => return Ok(sign_no_later_then), + }; + + let session_info = match get_session_info( + session_info_provider, + ctx.sender(), + block_entry.parent_hash(), + block_entry.session(), + ) + .await + { + Some(s) => s, + None => { + metrics.on_approval_error(); + gum::error!( + target: LOG_TARGET, + "Could not retrieve the session" + ); + return Ok(None) + }, + }; + let validator_pubkey = match session_info.validators.get(validator_index) { Some(p) => p, None => { - gum::warn!( + gum::error!( target: LOG_TARGET, "Validator index {} out of bounds in session {}", validator_index.0, @@ -3024,72 +3458,89 @@ async fn issue_approval( ); metrics.on_approval_error(); - return Ok(Vec::new()) + return Ok(None) }, }; - let session = block_entry.session(); - let sig = match sign_approval(&state.keystore, &validator_pubkey, candidate_hash, session) { + let signature = match sign_approval( + &state.keystore, + &validator_pubkey, + &candidates_hashes, + block_entry.session(), + ) { Some(sig) => sig, None => { - gum::warn!( + gum::error!( target: LOG_TARGET, validator_index = ?validator_index, - session, + session = ?block_entry.session(), "Could not issue approval signature. Assignment key present but not validator key?", ); metrics.on_approval_error(); - return Ok(Vec::new()) + return Ok(None) }, }; + metrics.on_approval_coalesce(candidates_hashes.len() as u32); - gum::trace!( - target: LOG_TARGET, - ?candidate_hash, - ?block_hash, - validator_index = validator_index.0, - "Issuing approval vote", - ); + let candidate_entries = candidates_hashes + .iter() + .map(|candidate_hash| db.load_candidate_entry(candidate_hash)) + .collect::>>>()?; - let actions = advance_approval_state( - ctx.sender(), - state, - db, - session_info_provider, - metrics, - block_entry, - candidate_hash, - candidate_entry, - ApprovalStateTransition::LocalApproval(validator_index as _, sig.clone()), - ) - .await; + for mut candidate_entry in candidate_entries { + let approval_entry = candidate_entry.as_mut().and_then(|candidate_entry| { + candidate_entry.approval_entry_mut(&block_entry.block_hash()) + }); + + match approval_entry { + Some(approval_entry) => approval_entry.import_approval_sig(OurApproval { + signature: signature.clone(), + signed_candidates_indices: candidates_indices.clone(), + }), + None => { + gum::error!( + target: LOG_TARGET, + candidate_entry = ?candidate_entry, + "Candidate scheduled for signing approval entry should not be None" + ); + }, + }; + candidate_entry.map(|candidate_entry| db.write_candidate_entry(candidate_entry)); + } metrics.on_approval_produced(); - // dispatch to approval distribution. ctx.send_unbounded_message(ApprovalDistributionMessage::DistributeApproval( - IndirectSignedApprovalVote { - block_hash, - candidate_index: candidate_index as _, + IndirectSignedApprovalVoteV2 { + block_hash: block_entry.block_hash(), + candidate_indices: candidates_indices, validator: validator_index, - signature: sig, + signature, }, )); - Ok(actions) + gum::trace!( + target: LOG_TARGET, + ?block_hash, + signed_candidates = ?block_entry.num_candidates_pending_signature(), + "Issue approval votes", + ); + block_entry.issued_approval(); + db.write_block_entry(block_entry.into()); + Ok(None) } // Sign an approval vote. Fails if the key isn't present in the store. fn sign_approval( keystore: &LocalKeystore, public: &ValidatorId, - candidate_hash: CandidateHash, + candidate_hashes: &[CandidateHash], session_index: SessionIndex, ) -> Option { let key = keystore.key_pair::(public).ok().flatten()?; - let payload = ApprovalVote(candidate_hash).signing_payload(session_index); + let payload = ApprovalVoteMultipleCandidates(candidate_hashes).signing_payload(session_index); Some(key.sign(&payload[..])) } @@ -3119,3 +3570,38 @@ fn issue_local_invalid_statement( false, )); } + +// Computes what is the latest tick we can send an approval +fn compute_delayed_approval_sending_tick( + state: &State, + block_entry: &BlockEntry, + candidate_entry: &CandidateEntry, + session_info: &SessionInfo, + metrics: &Metrics, +) -> Tick { + let current_block_tick = slot_number_to_tick(state.slot_duration_millis, block_entry.slot()); + let assignment_tranche = candidate_entry + .approval_entry(&block_entry.block_hash()) + .and_then(|approval_entry| approval_entry.our_assignment()) + .map(|our_assignment| our_assignment.tranche()) + .unwrap_or_default(); + + let assignment_triggered_tick = current_block_tick + assignment_tranche as Tick; + + let no_show_duration_ticks = slot_number_to_tick( + state.slot_duration_millis, + Slot::from(u64::from(session_info.no_show_slots)), + ); + let tick_now = state.clock.tick_now(); + + let sign_no_later_than = min( + tick_now + MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick, + // We don't want to accidentally cause no-shows, so if we are past + // the second half of the no show time, force the sending of the + // approval immediately. + assignment_triggered_tick + no_show_duration_ticks / 2, + ); + + metrics.on_delayed_approval(sign_no_later_than.checked_sub(tick_now).unwrap_or_default()); + sign_no_later_than +} diff --git a/polkadot/node/core/approval-voting/src/ops.rs b/polkadot/node/core/approval-voting/src/ops.rs index a6f0ecf9d1f027ee4f4ef1d0d5480fe26f175607..2a8fdba5aa3642f5b702e72bc2641d58106faa7a 100644 --- a/polkadot/node/core/approval-voting/src/ops.rs +++ b/polkadot/node/core/approval-voting/src/ops.rs @@ -25,7 +25,7 @@ use polkadot_primitives::{BlockNumber, CandidateHash, CandidateReceipt, GroupInd use std::collections::{hash_map::Entry, BTreeMap, HashMap}; use super::{ - approval_db::v2::{OurAssignment, StoredBlockRange}, + approval_db::{common::StoredBlockRange, v2::OurAssignment}, backend::{Backend, OverlayedBackend}, persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry}, LOG_TARGET, diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs index 9cfe1c4cf8da9bb62ec16822ef1bb419f71eb96f..ef47bdb2213a153dc7223c1018ba8ec9b341a5aa 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -20,13 +20,14 @@ //! Within that context, things are plain-old-data. Within this module, //! data and logic are intertwined. +use itertools::Itertools; use polkadot_node_primitives::approval::{ v1::{DelayTranche, RelayVRFStory}, v2::{AssignmentCertV2, CandidateBitfield}, }; use polkadot_primitives::{ - BlockNumber, CandidateHash, CandidateReceipt, CoreIndex, GroupIndex, Hash, SessionIndex, - ValidatorIndex, ValidatorSignature, + BlockNumber, CandidateHash, CandidateIndex, CandidateReceipt, CoreIndex, GroupIndex, Hash, + SessionIndex, ValidatorIndex, ValidatorSignature, }; use sp_consensus_slots::Slot; @@ -76,6 +77,45 @@ impl From for crate::approval_db::v2::TrancheEntry { } } +impl From for OurApproval { + fn from(approval: crate::approval_db::v3::OurApproval) -> Self { + Self { + signature: approval.signature, + signed_candidates_indices: approval.signed_candidates_indices, + } + } +} +impl From for crate::approval_db::v3::OurApproval { + fn from(approval: OurApproval) -> Self { + Self { + signature: approval.signature, + signed_candidates_indices: approval.signed_candidates_indices, + } + } +} + +/// Metadata about our approval signature +#[derive(Debug, Clone, PartialEq)] +pub struct OurApproval { + /// The signature for the candidates hashes pointed by indices. + pub signature: ValidatorSignature, + /// The indices of the candidates signed in this approval. + pub signed_candidates_indices: CandidateBitfield, +} + +impl OurApproval { + /// Converts a ValidatorSignature to an OurApproval. + /// It used in converting the database from v1 to latest. + pub fn from_v1(value: ValidatorSignature, candidate_index: CandidateIndex) -> Self { + Self { signature: value, signed_candidates_indices: candidate_index.into() } + } + + /// Converts a ValidatorSignature to an OurApproval. + /// It used in converting the database from v2 to latest. + pub fn from_v2(value: ValidatorSignature, candidate_index: CandidateIndex) -> Self { + Self::from_v1(value, candidate_index) + } +} /// Metadata regarding approval of a particular candidate within the context of some /// particular block. #[derive(Debug, Clone, PartialEq)] @@ -83,7 +123,7 @@ pub struct ApprovalEntry { tranches: Vec, backing_group: GroupIndex, our_assignment: Option, - our_approval_sig: Option, + our_approval_sig: Option, // `n_validators` bits. assigned_validators: Bitfield, approved: bool, @@ -95,7 +135,7 @@ impl ApprovalEntry { tranches: Vec, backing_group: GroupIndex, our_assignment: Option, - our_approval_sig: Option, + our_approval_sig: Option, // `n_validators` bits. assigned_validators: Bitfield, approved: bool, @@ -137,7 +177,7 @@ impl ApprovalEntry { } /// Import our local approval vote signature for this candidate. - pub fn import_approval_sig(&mut self, approval_sig: ValidatorSignature) { + pub fn import_approval_sig(&mut self, approval_sig: OurApproval) { self.our_approval_sig = Some(approval_sig); } @@ -224,7 +264,7 @@ impl ApprovalEntry { /// Get the assignment cert & approval signature. /// /// The approval signature will only be `Some` if the assignment is too. - pub fn local_statements(&self) -> (Option, Option) { + pub fn local_statements(&self) -> (Option, Option) { let approval_sig = self.our_approval_sig.clone(); if let Some(our_assignment) = self.our_assignment.as_ref().filter(|a| a.triggered()) { (Some(our_assignment.clone()), approval_sig) @@ -232,10 +272,44 @@ impl ApprovalEntry { (None, None) } } + + // Convert an ApprovalEntry from v1 version to latest version + pub fn from_v1( + value: crate::approval_db::v1::ApprovalEntry, + candidate_index: CandidateIndex, + ) -> Self { + ApprovalEntry { + tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(), + backing_group: value.backing_group, + our_assignment: value.our_assignment.map(|assignment| assignment.into()), + our_approval_sig: value + .our_approval_sig + .map(|sig| OurApproval::from_v1(sig, candidate_index)), + assigned_validators: value.assignments, + approved: value.approved, + } + } + + // Convert an ApprovalEntry from v1 version to latest version + pub fn from_v2( + value: crate::approval_db::v2::ApprovalEntry, + candidate_index: CandidateIndex, + ) -> Self { + ApprovalEntry { + tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(), + backing_group: value.backing_group, + our_assignment: value.our_assignment.map(|assignment| assignment.into()), + our_approval_sig: value + .our_approval_sig + .map(|sig| OurApproval::from_v2(sig, candidate_index)), + assigned_validators: value.assigned_validators, + approved: value.approved, + } + } } -impl From for ApprovalEntry { - fn from(entry: crate::approval_db::v2::ApprovalEntry) -> Self { +impl From for ApprovalEntry { + fn from(entry: crate::approval_db::v3::ApprovalEntry) -> Self { ApprovalEntry { tranches: entry.tranches.into_iter().map(Into::into).collect(), backing_group: entry.backing_group, @@ -247,7 +321,7 @@ impl From for ApprovalEntry { } } -impl From for crate::approval_db::v2::ApprovalEntry { +impl From for crate::approval_db::v3::ApprovalEntry { fn from(entry: ApprovalEntry) -> Self { Self { tranches: entry.tranches.into_iter().map(Into::into).collect(), @@ -303,10 +377,44 @@ impl CandidateEntry { pub fn approval_entry(&self, block_hash: &Hash) -> Option<&ApprovalEntry> { self.block_assignments.get(block_hash) } + + /// Convert a CandidateEntry from a v1 to its latest equivalent. + pub fn from_v1( + value: crate::approval_db::v1::CandidateEntry, + candidate_index: CandidateIndex, + ) -> Self { + Self { + approvals: value.approvals, + block_assignments: value + .block_assignments + .into_iter() + .map(|(h, ae)| (h, ApprovalEntry::from_v1(ae, candidate_index))) + .collect(), + candidate: value.candidate, + session: value.session, + } + } + + /// Convert a CandidateEntry from a v2 to its latest equivalent. + pub fn from_v2( + value: crate::approval_db::v2::CandidateEntry, + candidate_index: CandidateIndex, + ) -> Self { + Self { + approvals: value.approvals, + block_assignments: value + .block_assignments + .into_iter() + .map(|(h, ae)| (h, ApprovalEntry::from_v2(ae, candidate_index))) + .collect(), + candidate: value.candidate, + session: value.session, + } + } } -impl From for CandidateEntry { - fn from(entry: crate::approval_db::v2::CandidateEntry) -> Self { +impl From for CandidateEntry { + fn from(entry: crate::approval_db::v3::CandidateEntry) -> Self { CandidateEntry { candidate: entry.candidate, session: entry.session, @@ -320,7 +428,7 @@ impl From for CandidateEntry { } } -impl From for crate::approval_db::v2::CandidateEntry { +impl From for crate::approval_db::v3::CandidateEntry { fn from(entry: CandidateEntry) -> Self { Self { candidate: entry.candidate, @@ -353,12 +461,21 @@ pub struct BlockEntry { // block. The block can be considered approved if the bitfield has all bits set to `true`. pub approved_bitfield: Bitfield, pub children: Vec, + // A list of candidates we have checked, but didn't not sign and + // advertise the vote yet. + candidates_pending_signature: BTreeMap, // A list of assignments for which we already distributed the assignment. // We use this to ensure we don't distribute multiple core assignments twice as we track // individual wakeups for each core. distributed_assignments: Bitfield, } +#[derive(Debug, Clone, PartialEq)] +pub struct CandidateSigningContext { + pub candidate_hash: CandidateHash, + pub sign_no_later_than_tick: Tick, +} + impl BlockEntry { /// Mark a candidate as fully approved in the bitfield. pub fn mark_approved_by_hash(&mut self, candidate_hash: &CandidateHash) { @@ -447,10 +564,97 @@ impl BlockEntry { distributed } + + /// Defer signing and issuing an approval for a candidate no later than the specified tick + pub fn defer_candidate_signature( + &mut self, + candidate_index: CandidateIndex, + candidate_hash: CandidateHash, + sign_no_later_than_tick: Tick, + ) -> Option { + self.candidates_pending_signature.insert( + candidate_index, + CandidateSigningContext { candidate_hash, sign_no_later_than_tick }, + ) + } + + /// Returns the number of candidates waiting for an approval to be issued. + pub fn num_candidates_pending_signature(&self) -> usize { + self.candidates_pending_signature.len() + } + + /// Return if we have candidates waiting for signature to be issued + pub fn has_candidates_pending_signature(&self) -> bool { + !self.candidates_pending_signature.is_empty() + } + + /// Candidate hashes for candidates pending signatures + fn candidate_hashes_pending_signature(&self) -> Vec { + self.candidates_pending_signature + .values() + .map(|unsigned_approval| unsigned_approval.candidate_hash) + .collect() + } + + /// Candidate indices for candidates pending signature + fn candidate_indices_pending_signature(&self) -> Option { + self.candidates_pending_signature + .keys() + .map(|val| *val) + .collect_vec() + .try_into() + .ok() + } + + /// Returns a list of candidates hashes that need need signature created at the current tick: + /// This might happen in other of the two reasons: + /// 1. We queued more than max_approval_coalesce_count candidates. + /// 2. We have candidates that waiting in the queue past their `sign_no_later_than_tick` + /// + /// Additionally, we also return the first tick when we will have to create a signature, + /// so that the caller can arm the timer if it is not already armed. + pub fn get_candidates_that_need_signature( + &self, + tick_now: Tick, + max_approval_coalesce_count: u32, + ) -> (Option<(Vec, CandidateBitfield)>, Option) { + let sign_no_later_than_tick = self + .candidates_pending_signature + .values() + .min_by(|a, b| a.sign_no_later_than_tick.cmp(&b.sign_no_later_than_tick)) + .map(|val| val.sign_no_later_than_tick); + + if let Some(sign_no_later_than_tick) = sign_no_later_than_tick { + if sign_no_later_than_tick <= tick_now || + self.num_candidates_pending_signature() >= max_approval_coalesce_count as usize + { + ( + self.candidate_indices_pending_signature().and_then(|candidate_indices| { + Some((self.candidate_hashes_pending_signature(), candidate_indices)) + }), + Some(sign_no_later_than_tick), + ) + } else { + // We can still wait for other candidates to queue in, so just make sure + // we wake up at the tick we have to sign the longest waiting candidate. + (Default::default(), Some(sign_no_later_than_tick)) + } + } else { + // No cached candidates, nothing to do here, this just means the timer fired, + // but the signatures were already sent because we gathered more than + // max_approval_coalesce_count. + (Default::default(), sign_no_later_than_tick) + } + } + + /// Clears the candidates pending signature because the approval was issued. + pub fn issued_approval(&mut self) { + self.candidates_pending_signature.clear(); + } } -impl From for BlockEntry { - fn from(entry: crate::approval_db::v2::BlockEntry) -> Self { +impl From for BlockEntry { + fn from(entry: crate::approval_db::v3::BlockEntry) -> Self { BlockEntry { block_hash: entry.block_hash, parent_hash: entry.parent_hash, @@ -461,6 +665,11 @@ impl From for BlockEntry { candidates: entry.candidates, approved_bitfield: entry.approved_bitfield, children: entry.children, + candidates_pending_signature: entry + .candidates_pending_signature + .into_iter() + .map(|(candidate_index, signing_context)| (candidate_index, signing_context.into())) + .collect(), distributed_assignments: entry.distributed_assignments, } } @@ -479,11 +688,30 @@ impl From for BlockEntry { approved_bitfield: entry.approved_bitfield, children: entry.children, distributed_assignments: Default::default(), + candidates_pending_signature: Default::default(), } } } -impl From for crate::approval_db::v2::BlockEntry { +impl From for BlockEntry { + fn from(entry: crate::approval_db::v2::BlockEntry) -> Self { + BlockEntry { + block_hash: entry.block_hash, + parent_hash: entry.parent_hash, + block_number: entry.block_number, + session: entry.session, + slot: entry.slot, + relay_vrf_story: RelayVRFStory(entry.relay_vrf_story), + candidates: entry.candidates, + approved_bitfield: entry.approved_bitfield, + children: entry.children, + distributed_assignments: entry.distributed_assignments, + candidates_pending_signature: Default::default(), + } + } +} + +impl From for crate::approval_db::v3::BlockEntry { fn from(entry: BlockEntry) -> Self { Self { block_hash: entry.block_hash, @@ -495,36 +723,30 @@ impl From for crate::approval_db::v2::BlockEntry { candidates: entry.candidates, approved_bitfield: entry.approved_bitfield, children: entry.children, + candidates_pending_signature: entry + .candidates_pending_signature + .into_iter() + .map(|(candidate_index, signing_context)| (candidate_index, signing_context.into())) + .collect(), distributed_assignments: entry.distributed_assignments, } } } -/// Migration helpers. -impl From for CandidateEntry { - fn from(value: crate::approval_db::v1::CandidateEntry) -> Self { +impl From for CandidateSigningContext { + fn from(signing_context: crate::approval_db::v3::CandidateSigningContext) -> Self { Self { - approvals: value.approvals, - block_assignments: value - .block_assignments - .into_iter() - .map(|(h, ae)| (h, ae.into())) - .collect(), - candidate: value.candidate, - session: value.session, + candidate_hash: signing_context.candidate_hash, + sign_no_later_than_tick: signing_context.sign_no_later_than_tick.into(), } } } -impl From for ApprovalEntry { - fn from(value: crate::approval_db::v1::ApprovalEntry) -> Self { - ApprovalEntry { - tranches: value.tranches.into_iter().map(|tranche| tranche.into()).collect(), - backing_group: value.backing_group, - our_assignment: value.our_assignment.map(|assignment| assignment.into()), - our_approval_sig: value.our_approval_sig, - assigned_validators: value.assignments, - approved: value.approved, +impl From for crate::approval_db::v3::CandidateSigningContext { + fn from(signing_context: CandidateSigningContext) -> Self { + Self { + candidate_hash: signing_context.candidate_hash, + sign_no_later_than_tick: signing_context.sign_no_later_than_tick.into(), } } } diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 11bcba9c388277fa47a305106f30aac253913045..7a0bde6a55e28135036dbc9b8ee8137a0485aa28 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -20,7 +20,7 @@ use crate::backend::V1ReadBackend; use polkadot_node_primitives::{ approval::{ v1::{ - AssignmentCert, AssignmentCertKind, DelayTranche, VrfOutput, VrfProof, VrfSignature, + AssignmentCert, AssignmentCertKind, DelayTranche, VrfPreOutput, VrfProof, VrfSignature, RELAY_VRF_MODULO_CONTEXT, }, v2::{AssignmentCertKindV2, AssignmentCertV2}, @@ -37,8 +37,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_overseer::HeadSupportsParachains; use polkadot_primitives::{ - CandidateCommitments, CandidateEvent, CoreIndex, GroupIndex, Header, Id as ParaId, IndexedVec, - ValidationCode, ValidatorSignature, + vstaging::NodeFeatures, ApprovalVote, CandidateCommitments, CandidateEvent, CoreIndex, + GroupIndex, Header, Id as ParaId, IndexedVec, ValidationCode, ValidatorSignature, }; use std::time::Duration; @@ -56,7 +56,7 @@ use std::{ }; use super::{ - approval_db::v2::StoredBlockRange, + approval_db::common::StoredBlockRange, backend::BackendWriteOp, import::tests::{ garbage_vrf_signature, AllowedSlots, BabeEpoch, BabeEpochConfiguration, @@ -116,7 +116,7 @@ fn make_sync_oracle(val: bool) -> (Box, TestSyncOracleHan #[cfg(test)] pub mod test_constants { - use crate::approval_db::v2::Config as DatabaseConfig; + use crate::approval_db::common::Config as DatabaseConfig; const DATA_COL: u32 = 0; pub(crate) const NUM_COLUMNS: u32 = 1; @@ -243,6 +243,7 @@ where polkadot_primitives::CoreIndex, polkadot_primitives::GroupIndex, )>, + _enable_assignments_v2: bool, ) -> HashMap { self.0() } @@ -280,6 +281,7 @@ impl V1ReadBackend for TestStoreInner { fn load_candidate_entry_v1( &self, candidate_hash: &CandidateHash, + _candidate_index: CandidateIndex, ) -> SubsystemResult> { self.load_candidate_entry(candidate_hash) } @@ -363,6 +365,7 @@ impl V1ReadBackend for TestStore { fn load_candidate_entry_v1( &self, candidate_hash: &CandidateHash, + _candidate_index: CandidateIndex, ) -> SubsystemResult> { self.load_candidate_entry(candidate_hash) } @@ -415,9 +418,12 @@ fn garbage_assignment_cert(kind: AssignmentCertKind) -> AssignmentCert { let mut prng = rand_core::OsRng; let keypair = schnorrkel::Keypair::generate_with(&mut prng); let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); - let out = inout.to_output(); + let preout = inout.to_preout(); - AssignmentCert { kind, vrf: VrfSignature { output: VrfOutput(out), proof: VrfProof(proof) } } + AssignmentCert { + kind, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + } } fn garbage_assignment_cert_v2(kind: AssignmentCertKindV2) -> AssignmentCertV2 { @@ -426,9 +432,12 @@ fn garbage_assignment_cert_v2(kind: AssignmentCertKindV2) -> AssignmentCertV2 { let mut prng = rand_core::OsRng; let keypair = schnorrkel::Keypair::generate_with(&mut prng); let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); - let out = inout.to_output(); + let preout = inout.to_preout(); - AssignmentCertV2 { kind, vrf: VrfSignature { output: VrfOutput(out), proof: VrfProof(proof) } } + AssignmentCertV2 { + kind, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, + } } fn sign_approval( @@ -439,6 +448,15 @@ fn sign_approval( key.sign(&ApprovalVote(candidate_hash).signing_payload(session_index)).into() } +fn sign_approval_multiple_candidates( + key: Sr25519Keyring, + candidate_hashes: Vec, + session_index: SessionIndex, +) -> ValidatorSignature { + key.sign(&ApprovalVoteMultipleCandidates(&candidate_hashes).signing_payload(session_index)) + .into() +} + type VirtualOverseer = test_helpers::TestSubsystemContextHandle; #[derive(Default)] @@ -634,7 +652,12 @@ async fn check_and_import_approval( overseer, FromOrchestra::Communication { msg: ApprovalVotingMessage::CheckAndImportApproval( - IndirectSignedApprovalVote { block_hash, candidate_index, validator, signature }, + IndirectSignedApprovalVoteV2 { + block_hash, + candidate_indices: candidate_index.into(), + validator, + signature, + }, tx, ), }, @@ -997,6 +1020,15 @@ async fn import_block( si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); } ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); } assert_matches!( @@ -1998,6 +2030,91 @@ fn forkful_import_at_same_height_act_on_leaf() { }); } +#[test] +fn test_signing_a_single_candidate_is_backwards_compatible() { + let session_index = 1; + let block_hash = Hash::repeat_byte(0x01); + let candidate_descriptors = (1..10) + .into_iter() + .map(|val| make_candidate(ParaId::from(val as u32), &block_hash)) + .collect::>(); + + let candidate_hashes = candidate_descriptors + .iter() + .map(|candidate_descriptor| candidate_descriptor.hash()) + .collect_vec(); + + let first_descriptor = candidate_descriptors.first().unwrap(); + + let candidate_hash = first_descriptor.hash(); + + let sig_a = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index); + + let sig_b = sign_approval(Sr25519Keyring::Alice, candidate_hash, session_index); + + assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) + .check_signature( + &Sr25519Keyring::Alice.public().into(), + candidate_hash, + session_index, + &sig_a, + ) + .is_ok()); + + assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) + .check_signature( + &Sr25519Keyring::Alice.public().into(), + candidate_hash, + session_index, + &sig_b, + ) + .is_ok()); + + let sig_c = sign_approval_multiple_candidates( + Sr25519Keyring::Alice, + vec![candidate_hash], + session_index, + ); + + assert!(DisputeStatement::Valid( + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash]) + ) + .check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_c,) + .is_ok()); + + assert!(DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) + .check_signature( + &Sr25519Keyring::Alice.public().into(), + candidate_hash, + session_index, + &sig_c, + ) + .is_ok()); + + assert!(DisputeStatement::Valid( + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(vec![candidate_hash]) + ) + .check_signature(&Sr25519Keyring::Alice.public().into(), candidate_hash, session_index, &sig_a,) + .is_ok()); + + let sig_all = sign_approval_multiple_candidates( + Sr25519Keyring::Alice, + candidate_hashes.clone(), + session_index, + ); + + assert!(DisputeStatement::Valid( + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone()) + ) + .check_signature( + &Sr25519Keyring::Alice.public().into(), + *candidate_hashes.first().expect("test"), + session_index, + &sig_all, + ) + .is_ok()); +} + #[test] fn import_checked_approval_updates_entries_and_schedules() { let config = HarnessConfig::default(); @@ -2714,11 +2831,29 @@ async fn handle_double_assignment_import( } ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) ); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) @@ -3453,3 +3588,455 @@ fn waits_until_approving_assignments_are_old_enough() { virtual_overseer }); } + +#[test] +fn test_approval_is_sent_on_max_approval_coalesce_count() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }) + .into(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let assignments_cert = + garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)] + .try_into() + .unwrap(), + }); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: assignments_cert.clone(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: assignments_cert.clone(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } = + test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + + let candidate_commitments = CandidateCommitments::default(); + + let candidate_receipt1 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(1_u32); + receipt.commitments_hash = candidate_commitments.hash(); + receipt + }; + + let candidate_hash1 = candidate_receipt1.hash(); + + let candidate_receipt2 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(2_u32); + receipt.commitments_hash = candidate_commitments.hash(); + receipt + }; + + let slot = Slot::from(1); + let candidate_index1 = 0; + let candidate_index2 = 1; + + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + let session_info = SessionInfo { + validator_groups: IndexedVec::>::from(vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ]), + ..session_info(&validators) + }; + + let candidates = Some(vec![ + (candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)), + (candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)), + ]); + ChainBuilder::new() + .add_block( + block_hash, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates: candidates.clone(), + session_info: Some(session_info.clone()), + }, + ) + .build(&mut virtual_overseer) + .await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot))); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + + handle_approval_on_max_coalesce_count( + &mut virtual_overseer, + vec![candidate_index1, candidate_index2], + ) + .await; + + virtual_overseer + }); +} + +async fn handle_approval_on_max_coalesce_count( + virtual_overseer: &mut VirtualOverseer, + candidate_indicies: Vec, +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + c_indices, + )) => { + assert_eq!(TryInto::::try_into(candidate_indicies.clone()).unwrap(), c_indices); + } + ); + + for _ in &candidate_indicies { + recover_available_data(virtual_overseer).await; + fetch_validation_code(virtual_overseer).await; + } + + for _ in &candidate_indicies { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + } + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => { + assert_eq!(TryInto::::try_into(candidate_indicies).unwrap(), vote.candidate_indices); + } + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); +} + +async fn handle_approval_on_max_wait_time( + virtual_overseer: &mut VirtualOverseer, + candidate_indicies: Vec, + clock: Box, +) { + const TICK_NOW_BEGIN: u64 = 1; + const MAX_COALESCE_COUNT: u32 = 3; + + clock.inner.lock().set_tick(TICK_NOW_BEGIN); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + c_indices, + )) => { + assert_eq!(TryInto::::try_into(candidate_indicies.clone()).unwrap(), c_indices); + } + ); + + for _ in &candidate_indicies { + recover_available_data(virtual_overseer).await; + fetch_validation_code(virtual_overseer).await; + } + + for _ in &candidate_indicies { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive{exec_kind, response_sender, ..}) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + } + + // First time we fetch the configuration when we are ready to approve the first candidate + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: MAX_COALESCE_COUNT, + })); + } + ); + + // Second time we fetch the configuration when we are ready to approve the second candidate + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: MAX_COALESCE_COUNT, + })); + } + ); + + assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + // Move the clock just before we should send the approval + clock + .inner + .lock() + .set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN - 1); + + assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + // Move the clock tick, so we can trigger a force sending of the approvals + clock + .inner + .lock() + .set_tick(MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick + TICK_NOW_BEGIN); + + // Third time we fetch the configuration when timer expires and we are ready to sent the + // approval + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 3, + })); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(vote)) => { + assert_eq!(TryInto::::try_into(candidate_indicies).unwrap(), vote.candidate_indices); + } + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); +} + +#[test] +fn test_approval_is_sent_on_max_approval_coalesce_wait() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert(AssignmentCertKind::RelayVRFModulo { sample: 0 }) + .into(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let assignments_cert = + garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(1), CoreIndex(2)] + .try_into() + .unwrap(), + }); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: assignments_cert.clone(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: assignments_cert.clone(), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle: _sync_oracle_handle } = + test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + + let candidate_commitments = CandidateCommitments::default(); + + let candidate_receipt1 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(1_u32); + receipt.commitments_hash = candidate_commitments.hash(); + receipt + }; + + let candidate_hash1 = candidate_receipt1.hash(); + + let candidate_receipt2 = { + let mut receipt = dummy_candidate_receipt(block_hash); + receipt.descriptor.para_id = ParaId::from(2_u32); + receipt.commitments_hash = candidate_commitments.hash(); + receipt + }; + + let slot = Slot::from(1); + let candidate_index1 = 0; + let candidate_index2 = 1; + + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + let session_info = SessionInfo { + validator_groups: IndexedVec::>::from(vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ]), + ..session_info(&validators) + }; + + let candidates = Some(vec![ + (candidate_receipt1.clone(), CoreIndex(0), GroupIndex(0)), + (candidate_receipt2.clone(), CoreIndex(1), GroupIndex(1)), + ]); + ChainBuilder::new() + .add_block( + block_hash, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates: candidates.clone(), + session_info: Some(session_info.clone()), + }, + ) + .build(&mut virtual_overseer) + .await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot))); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash1).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + + handle_approval_on_max_wait_time( + &mut virtual_overseer, + vec![candidate_index1, candidate_index2], + clock, + ) + .await; + + virtual_overseer + }); +} diff --git a/polkadot/node/core/approval-voting/src/time.rs b/polkadot/node/core/approval-voting/src/time.rs index a45866402c827e7e91ad6fd44bc7561be35e5d30..61091f3c34cdab4aed00c24bcbc8a40d6a77a116 100644 --- a/polkadot/node/core/approval-voting/src/time.rs +++ b/polkadot/node/core/approval-voting/src/time.rs @@ -16,14 +16,23 @@ //! Time utilities for approval voting. -use futures::prelude::*; +use futures::{ + future::BoxFuture, + prelude::*, + stream::{FusedStream, FuturesUnordered}, + Stream, StreamExt, +}; + use polkadot_node_primitives::approval::v1::DelayTranche; use sp_consensus_slots::Slot; use std::{ + collections::HashSet, pin::Pin, + task::Poll, time::{Duration, SystemTime}, }; +use polkadot_primitives::{Hash, ValidatorIndex}; const TICK_DURATION_MILLIS: u64 = 500; /// A base unit of time, starting from the Unix epoch, split into half-second intervals. @@ -88,3 +97,157 @@ pub(crate) 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 } + +/// 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. +#[derive(Default)] +pub struct DelayedApprovalTimer { + timers: FuturesUnordered>, + blocks: HashSet, +} + +impl DelayedApprovalTimer { + /// Starts a single timer per block hash + /// + /// Guarantees that if a timer already exits for the give block hash, + /// no additional timer is started. + pub(crate) fn maybe_arm_timer( + &mut self, + wait_untill: Tick, + clock: &dyn Clock, + block_hash: Hash, + validator_index: ValidatorIndex, + ) { + if self.blocks.insert(block_hash) { + let clock_wait = clock.wait(wait_untill); + self.timers.push(Box::pin(async move { + clock_wait.await; + (block_hash, validator_index) + })); + } + } +} + +impl Stream for DelayedApprovalTimer { + type Item = (Hash, ValidatorIndex); + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let poll_result = self.timers.poll_next_unpin(cx); + match poll_result { + Poll::Ready(Some(result)) => { + self.blocks.remove(&result.0); + Poll::Ready(Some(result)) + }, + _ => poll_result, + } + } +} + +impl FusedStream for DelayedApprovalTimer { + fn is_terminated(&self) -> bool { + self.timers.is_terminated() + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use futures::{executor::block_on, FutureExt, StreamExt}; + use futures_timer::Delay; + use polkadot_primitives::{Hash, ValidatorIndex}; + + use crate::time::{Clock, SystemClock}; + + use super::DelayedApprovalTimer; + + #[test] + fn test_select_empty_timer() { + block_on(async move { + let mut timer = DelayedApprovalTimer::default(); + + for _ in 1..10 { + let result = futures::select!( + _ = timer.select_next_some() => { + 0 + } + // Only this arm should fire + _ = Delay::new(Duration::from_millis(100)).fuse() => { + 1 + } + ); + + assert_eq!(result, 1); + } + }); + } + + #[test] + fn test_timer_functionality() { + block_on(async move { + let mut timer = DelayedApprovalTimer::default(); + let test_hashes = + vec![Hash::repeat_byte(0x01), Hash::repeat_byte(0x02), Hash::repeat_byte(0x03)]; + for (index, hash) in test_hashes.iter().enumerate() { + timer.maybe_arm_timer( + SystemClock.tick_now() + index as u64, + &SystemClock, + *hash, + ValidatorIndex::from(2), + ); + timer.maybe_arm_timer( + SystemClock.tick_now() + index as u64, + &SystemClock, + *hash, + ValidatorIndex::from(2), + ); + } + let timeout_hash = Hash::repeat_byte(0x02); + for i in 0..test_hashes.len() * 2 { + let result = futures::select!( + (hash, _) = timer.select_next_some() => { + hash + } + // Timers should fire only once, so for the rest of the iterations we should timeout through here. + _ = Delay::new(Duration::from_secs(2)).fuse() => { + timeout_hash + } + ); + assert_eq!(test_hashes.get(i).cloned().unwrap_or(timeout_hash), result); + } + + // Now check timer can be restarted if already fired + for (index, hash) in test_hashes.iter().enumerate() { + timer.maybe_arm_timer( + SystemClock.tick_now() + index as u64, + &SystemClock, + *hash, + ValidatorIndex::from(2), + ); + timer.maybe_arm_timer( + SystemClock.tick_now() + index as u64, + &SystemClock, + *hash, + ValidatorIndex::from(2), + ); + } + + for i in 0..test_hashes.len() * 2 { + let result = futures::select!( + (hash, _) = timer.select_next_some() => { + hash + } + // Timers should fire only once, so for the rest of the iterations we should timeout through here. + _ = Delay::new(Duration::from_secs(2)).fuse() => { + timeout_hash + } + ); + assert_eq!(test_hashes.get(i).cloned().unwrap_or(timeout_hash), result); + } + }); + } +} diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 3fa81d064a883608b556343b5c07f4e55af1b8c9..4b2baf3fc55421a0d3f07fc3996972e1bd7dc2ac 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index 7a6ce5de8cb18777c8832c561327aa15d127b9cc..16ed11e7eec9a2aa7a97ad9a55585ebbef95c98c 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates." +[lints] +workspace = true + [dependencies] futures = "0.3.21" sp-keystore = { path = "../../../../substrate/primitives/keystore" } diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index 712a01b46b1cf9df13220accaad7ca1dde6cbee2..880273c0e7f3cc4a30d979edaad0d57dbb6ac523 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Bitfield signing subsystem for the Polkadot node" +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index a2e88778532f754cd429c5c156fe27cf5dd88d75..4f0ad67dbf1c84dd07d170c0ed00b7255ed275fa 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -6,8 +6,11 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 5c4e449b2c9025ec1b22ee4fda36331c245d3551..18c2796891588d599120c7143c197a151b7a6485 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -773,21 +773,21 @@ trait ValidationBackend { if num_death_retries_left > 0 { num_death_retries_left -= 1; } else { - break; + break }, Err(ValidationError::PossiblyInvalid(PossiblyInvalidError::JobError(_))) => if num_job_error_retries_left > 0 { num_job_error_retries_left -= 1; } else { - break; + break }, Err(ValidationError::Internal(_)) => if num_internal_retries_left > 0 { num_internal_retries_left -= 1; } else { - break; + break }, Ok(_) | Err(ValidationError::Invalid(_) | ValidationError::Preparation(_)) => break, diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 11078580465263a35588fc860b6f4f7a53161cd3..f646f8535495b74128f829359214018c4fb01ac2 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -726,7 +726,7 @@ fn candidate_validation_retry_on_error_helper( ExecutorParams::default(), exec_kind, &Default::default(), - )); + )) } #[test] diff --git a/polkadot/node/core/chain-api/Cargo.toml b/polkadot/node/core/chain-api/Cargo.toml index fa824e78ffee38671bab717018abc47ed280991a..32962c9bda43f0c0f0709712930b90d8573ac92c 100644 --- a/polkadot/node/core/chain-api/Cargo.toml +++ b/polkadot/node/core/chain-api/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "The Chain API subsystem provides access to chain related utility functions like block number to hash conversions." +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 7678379870e0371de28eeb3d3807b90820504b87..6056ddd41cd710e5fdbb5f4ecbfd7f6819124f5e 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3" diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index e2086db708f9a616ee348738fcf706aef86ccd92..8ec9bcbe07070cf01b077bf717a98cf835aa3176 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/core/dispute-coordinator/src/import.rs b/polkadot/node/core/dispute-coordinator/src/import.rs index 837ad7856e735c47f38b90d85a98dbf3a39f5122..98c12bd509b4b13d0a49380baf55e98e1bd4ef1f 100644 --- a/polkadot/node/core/dispute-coordinator/src/import.rs +++ b/polkadot/node/core/dispute-coordinator/src/import.rs @@ -34,9 +34,9 @@ use polkadot_node_primitives::{ use polkadot_node_subsystem::overseer; use polkadot_node_subsystem_util::runtime::RuntimeInfo; use polkadot_primitives::{ - CandidateReceipt, DisputeStatement, ExecutorParams, Hash, IndexedVec, SessionIndex, - SessionInfo, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorPair, - ValidatorSignature, + CandidateHash, CandidateReceipt, DisputeStatement, ExecutorParams, Hash, IndexedVec, + SessionIndex, SessionInfo, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, + ValidatorPair, ValidatorSignature, }; use sc_keystore::LocalKeystore; @@ -126,7 +126,9 @@ impl OwnVoteState { let our_valid_votes = controlled_indices .iter() .filter_map(|i| votes.valid.raw().get_key_value(i)) - .map(|(index, (kind, sig))| (*index, (DisputeStatement::Valid(*kind), sig.clone()))); + .map(|(index, (kind, sig))| { + (*index, (DisputeStatement::Valid(kind.clone()), sig.clone())) + }); let our_invalid_votes = controlled_indices .iter() .filter_map(|i| votes.invalid.get_key_value(i)) @@ -305,7 +307,7 @@ impl CandidateVoteState { DisputeStatement::Valid(valid_kind) => { let fresh = votes.valid.insert_vote( val_index, - *valid_kind, + valid_kind.clone(), statement.into_validator_signature(), ); if fresh { @@ -511,7 +513,7 @@ impl ImportResult { pub fn import_approval_votes( self, env: &CandidateEnvironment, - approval_votes: HashMap, + approval_votes: HashMap, ValidatorSignature)>, now: Timestamp, ) -> Self { let Self { @@ -525,19 +527,33 @@ impl ImportResult { let (mut votes, _) = new_state.into_old_state(); - for (index, sig) in approval_votes.into_iter() { + for (index, (candidate_hashes, sig)) in approval_votes.into_iter() { debug_assert!( { let pub_key = &env.session_info().validators.get(index).expect("indices are validated by approval-voting subsystem; qed"); - let candidate_hash = votes.candidate_receipt.hash(); let session_index = env.session_index(); - DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) - .check_signature(pub_key, candidate_hash, session_index, &sig) + candidate_hashes.contains(&votes.candidate_receipt.hash()) && DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes.clone())) + .check_signature(pub_key, *candidate_hashes.first().expect("Valid votes have at least one candidate; qed"), session_index, &sig) .is_ok() }, "Signature check for imported approval votes failed! This is a serious bug. Session: {:?}, candidate hash: {:?}, validator index: {:?}", env.session_index(), votes.candidate_receipt.hash(), index ); - if votes.valid.insert_vote(index, ValidDisputeStatementKind::ApprovalChecking, sig) { + if votes.valid.insert_vote( + index, + // There is a hidden dependency here between approval-voting and this subsystem. + // We should be able to start emitting + // ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates only after: + // 1. Runtime have been upgraded to know about the new format. + // 2. All nodes have been upgraded to know about the new format. + // Once those two requirements have been met we should be able to increase + // max_approval_coalesce_count to values greater than 1. + if candidate_hashes.len() > 1 { + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes) + } else { + ValidDisputeStatementKind::ApprovalChecking + }, + sig, + ) { imported_valid_votes += 1; imported_approval_votes += 1; } diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index e44530b3f1bbab0995f850af7e951984bd1dc55b..d9cd4e39d3cb30c7ccb06bc9e9b28704a3a24c73 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -642,7 +642,7 @@ impl Initialized { }; debug_assert!( SignedDisputeStatement::new_checked( - DisputeStatement::Valid(valid_statement_kind), + DisputeStatement::Valid(valid_statement_kind.clone()), candidate_hash, session, validator_public.clone(), @@ -656,7 +656,7 @@ impl Initialized { ); let signed_dispute_statement = SignedDisputeStatement::new_unchecked_from_trusted_source( - DisputeStatement::Valid(valid_statement_kind), + DisputeStatement::Valid(valid_statement_kind.clone()), candidate_hash, session, validator_public, diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index e96fee8124099bf000b09863b1a46da98256c5e6..5067d3673da9b23c79edff0ef83a449359d78a2a 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -576,7 +576,7 @@ pub fn make_dispute_message( .next() .ok_or(DisputeMessageCreationError::NoOppositeVote)?; let other_vote = SignedDisputeStatement::new_checked( - DisputeStatement::Valid(*statement_kind), + DisputeStatement::Valid(statement_kind.clone()), *our_vote.candidate_hash(), our_vote.session_index(), validators diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index 9254c2a851cea502ed8daccf434a68f48863de94..da449773fe8ff4bc1cbfac5d33a923f8d1b0426f 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -61,10 +61,11 @@ use polkadot_node_subsystem_test_helpers::{ make_buffered_subsystem_context, mock::new_leaf, TestSubsystemContextHandle, }; use polkadot_primitives::{ - ApprovalVote, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, - CandidateReceipt, CoreIndex, DisputeStatement, ExecutorParams, GroupIndex, Hash, HeadData, - Header, IndexedVec, MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, SessionInfo, - SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature, + vstaging::NodeFeatures, ApprovalVote, BlockNumber, CandidateCommitments, CandidateEvent, + CandidateHash, CandidateReceipt, CoreIndex, DisputeStatement, ExecutorParams, GroupIndex, Hash, + HeadData, Header, IndexedVec, MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, + SessionInfo, SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, + ValidatorSignature, }; use crate::{ @@ -352,6 +353,15 @@ impl TestState { let _ = tx.send(Ok(Some(ExecutorParams::default()))); } ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); } } @@ -651,7 +661,7 @@ fn make_candidate_included_event(candidate_receipt: CandidateReceipt) -> Candida pub async fn handle_approval_vote_request( ctx_handle: &mut VirtualOverseer, expected_hash: &CandidateHash, - votes_to_send: HashMap, + votes_to_send: HashMap, ValidatorSignature)>, ) { assert_matches!( ctx_handle.recv().await, @@ -858,9 +868,12 @@ fn approval_vote_import_works() { .await; gum::trace!("After sending `ImportStatements`"); - let approval_votes = [(ValidatorIndex(4), approval_vote.into_validator_signature())] - .into_iter() - .collect(); + let approval_votes = [( + ValidatorIndex(4), + (vec![candidate_receipt1.hash()], approval_vote.into_validator_signature()), + )] + .into_iter() + .collect(); handle_approval_vote_request(&mut virtual_overseer, &candidate_hash1, approval_votes) .await; @@ -3492,6 +3505,14 @@ fn session_info_is_requested_only_once() { let _ = tx.send(Ok(Some(ExecutorParams::default()))); } ); + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); test_state }) }); @@ -3552,6 +3573,15 @@ fn session_info_big_jump_works() { let _ = tx.send(Ok(Some(ExecutorParams::default()))); } ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); } test_state }) @@ -3612,6 +3642,14 @@ fn session_info_small_jump_works() { let _ = tx.send(Ok(Some(ExecutorParams::default()))); } ); + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); } test_state }) diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index c783f21e24df335838259952d6cc0831193e6184..2384020025181d9dd0cae04c54246193fb6278d3 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -6,12 +6,15 @@ edition.workspace = true license.workspace = true description = "Parachains inherent data provider for Polkadot node" +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } thiserror = "1.0.48" -async-trait = "0.1.57" +async-trait = "0.1.74" polkadot-node-subsystem = { path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } polkadot-primitives = { path = "../../../primitives" } diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index 9db1259e61d0105d1156f68fe1702f2505bf30c6..e6b6aa5e15d72e758a5acd30a6771756e6ee78a9 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "The Prospective Parachains subsystem. Tracks and handles prospective parachain fragments." +[lints] +workspace = true + [dependencies] futures = "0.3.19" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index d2cd23fe95fc1d8638238c8e30b5227da7e883a0..7e369245c0e1587b405eb4516343610aa8c9a320 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -101,11 +101,8 @@ fn test_harness>( let mut view = View::new(); let subsystem = async move { - loop { - match run_iteration(&mut context, &mut view, &Metrics(None)).await { - Ok(()) => break, - Err(e) => panic!("{:?}", e), - } + if let Err(e) = run_iteration(&mut context, &mut view, &Metrics(None)).await { + panic!("{:?}", e); } view diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index d27e2343925f7b34d95c3006763d04f2ebb76d65..2d18bd29c1c097cccf5a94515a55d232b9263032 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } futures = "0.3.21" diff --git a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/mod.rs b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/mod.rs index 096b73d271a8de637856b85836761c90593d96ee..cb55ce39bc89f3eeee3d1cb319351f659eace478 100644 --- a/polkadot/node/core/provisioner/src/disputes/prioritized_selection/mod.rs +++ b/polkadot/node/core/provisioner/src/disputes/prioritized_selection/mod.rs @@ -221,7 +221,7 @@ where votes.valid.retain(|validator_idx, (statement_kind, _)| { is_vote_worth_to_keep( validator_idx, - DisputeStatement::Valid(*statement_kind), + DisputeStatement::Valid(statement_kind.clone()), &onchain_state, ) }); diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 0326a20e5a52e7a07fe96a8c721af1c6ac86e5ef..274d8ee43bf1338094306aa78bb1991da5ff7477 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" thiserror = "1.0.48" diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index a1e70eabc0e7110528aab78a23344a04470b4292..2642377b6e6266c4804ba75cfe15044a1cb2e4e4 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] always-assert = "0.1" blake3 = "1.5" diff --git a/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs index 368649a8d71358c6dd02bdec7427e7b4bc7b696b..2aea21361a3e8fcb4114eb533539f09b1d660fc7 100644 --- a/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs +++ b/polkadot/node/core/pvf/benches/host_prepare_rococo_runtime.rs @@ -28,7 +28,8 @@ use tokio::{runtime::Handle, sync::Mutex}; const TEST_PREPARATION_TIMEOUT: Duration = Duration::from_secs(30); struct TestHost { - // Keep a reference to the tempdir as it gets deleted on drop. + // Keep a reference to the tempdir otherwise it gets deleted on drop. + #[allow(dead_code)] cache_dir: tempfile::TempDir, host: Mutex, } diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index bfe1be9156fc2980a35b74f1c15a11a011605774..c5c09300e8af951513ab798cf60312800b309bbf 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] cfg-if = "1.0" cpu-time = "1.0.0" diff --git a/polkadot/node/core/pvf/common/src/execute.rs b/polkadot/node/core/pvf/common/src/execute.rs index aa1c1c5396823c5f313f007724522adb67ae44cb..5ba5b443e6a1b09a9956aed99f85cb0f8e42d9aa 100644 --- a/polkadot/node/core/pvf/common/src/execute.rs +++ b/polkadot/node/core/pvf/common/src/execute.rs @@ -96,4 +96,6 @@ pub enum JobError { CouldNotSpawnThread(String), #[error("An error occurred in the CPU time monitor thread: {0}")] CpuTimeMonitorThread(String), + #[error("Could not set pdeathsig: {0}")] + CouldNotSetPdeathsig(String), } diff --git a/polkadot/node/core/pvf/execute-worker/Cargo.toml b/polkadot/node/core/pvf/execute-worker/Cargo.toml index 6e6206cf1b9e253c2021931f14b275f9686bf23f..97dde59ebc2e471a411119c34988a9498c13de7b 100644 --- a/polkadot/node/core/pvf/execute-worker/Cargo.toml +++ b/polkadot/node/core/pvf/execute-worker/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] cpu-time = "1.0.0" gum = { package = "tracing-gum", path = "../../../gum" } diff --git a/polkadot/node/core/pvf/execute-worker/src/lib.rs b/polkadot/node/core/pvf/execute-worker/src/lib.rs index b33a9d5069dffaa0d4264897022e1e7709577991..cff6e0ac13ab5e380bc62a287b6e59288ef6ae76 100644 --- a/polkadot/node/core/pvf/execute-worker/src/lib.rs +++ b/polkadot/node/core/pvf/execute-worker/src/lib.rs @@ -277,6 +277,15 @@ fn handle_child_process( params: Vec, execution_timeout: Duration, ) -> ! { + // Terminate if the parent thread dies. Parent thread == worker process (it is single-threaded). + // + // RACE: the worker may die before we install the death signal. In practice this is unlikely, + // and most of the time the job process should terminate on its own when it completes. + #[cfg(target_os = "linux")] + nix::sys::prctl::set_pdeathsig(nix::sys::signal::Signal::SIGTERM).unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(JobError::CouldNotSetPdeathsig(err.to_string()))) + }); + gum::debug!( target: LOG_TARGET, worker_job_pid = %process::id(), diff --git a/polkadot/node/core/pvf/prepare-worker/Cargo.toml b/polkadot/node/core/pvf/prepare-worker/Cargo.toml index 4e53f7f46ca93ed55e5a8c260f7fb2067d53d6de..81e887afe4d0b864ede9184bbe48a366c22e1522 100644 --- a/polkadot/node/core/pvf/prepare-worker/Cargo.toml +++ b/polkadot/node/core/pvf/prepare-worker/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] blake3 = "1.5" cfg-if = "1.0" diff --git a/polkadot/node/core/pvf/prepare-worker/src/lib.rs b/polkadot/node/core/pvf/prepare-worker/src/lib.rs index af5ac8c5974900055a9623fe5ec44242d2d77a2d..f77eed871ec9cd6690d06531aaa5c3e41e78acad 100644 --- a/polkadot/node/core/pvf/prepare-worker/src/lib.rs +++ b/polkadot/node/core/pvf/prepare-worker/src/lib.rs @@ -334,6 +334,15 @@ fn handle_child_process( prepare_job_kind: PrepareJobKind, executor_params: Arc, ) -> ! { + // Terminate if the parent thread dies. Parent thread == worker process (it is single-threaded). + // + // RACE: the worker may die before we install the death signal. In practice this is unlikely, + // and most of the time the job process should terminate on its own when it completes. + #[cfg(target_os = "linux")] + nix::sys::prctl::set_pdeathsig(nix::sys::signal::Signal::SIGTERM).unwrap_or_else(|err| { + send_child_response(&mut pipe_write, Err(PrepareError::IoErr(err.to_string()))) + }); + let worker_job_pid = process::id(); gum::debug!( target: LOG_TARGET, diff --git a/polkadot/node/core/pvf/src/artifacts.rs b/polkadot/node/core/pvf/src/artifacts.rs index 710e266841f510f6b8c16352a64bbb55134f41b1..17ce5b443e3387600153a6d5fe5703650bf3a2d0 100644 --- a/polkadot/node/core/pvf/src/artifacts.rs +++ b/polkadot/node/core/pvf/src/artifacts.rs @@ -201,13 +201,20 @@ impl Artifacts { /// Create an empty table and populate it with valid artifacts as [`ArtifactState::Prepared`], /// if any. The existing caches will be checked by their file name to determine whether they are /// valid, e.g., matching the current node version. The ones deemed invalid will be pruned. + /// + /// Create the cache directory on-disk if it doesn't exist. pub async fn new_and_prune(cache_path: &Path) -> Self { let mut artifacts = Self { inner: HashMap::new() }; - artifacts.insert_and_prune(cache_path).await; + let _ = artifacts.insert_and_prune(cache_path).await.map_err(|err| { + gum::error!( + target: LOG_TARGET, + "could not initialize artifacts cache: {err}", + ) + }); artifacts } - async fn insert_and_prune(&mut self, cache_path: &Path) { + async fn insert_and_prune(&mut self, cache_path: &Path) -> Result<(), String> { async fn is_corrupted(path: &Path) -> bool { let checksum = match tokio::fs::read(path).await { Ok(bytes) => blake3::hash(&bytes), @@ -237,24 +244,16 @@ impl Artifacts { artifacts: &mut Artifacts, entry: &tokio::fs::DirEntry, cache_path: &Path, - ) { + ) -> Result<(), String> { let file_type = entry.file_type().await; let file_name = entry.file_name(); match file_type { Ok(file_type) => if !file_type.is_file() { - return + return Ok(()) }, - Err(err) => { - gum::warn!( - target: LOG_TARGET, - ?err, - "unable to get file type for {:?}", - file_name, - ); - return - }, + Err(err) => return Err(format!("unable to get file type for {file_name:?}: {err}")), } if let Some(file_name) = file_name.to_str() { @@ -262,73 +261,51 @@ impl Artifacts { let path = cache_path.join(file_name); if id.is_none() || is_corrupted(&path).await { - gum::warn!( - target: LOG_TARGET, - "discarding invalid artifact {:?}", - &path, - ); let _ = tokio::fs::remove_file(&path).await; - return + return Err(format!("invalid artifact {path:?}, file deleted")) } - if let Some(id) = id { - gum::debug!( - target: LOG_TARGET, - "reusing existing {:?} for node version v{}", - &path, - NODE_VERSION, - ); - artifacts.insert_prepared(id, path, SystemTime::now(), Default::default()); - } - } else { - gum::warn!( + let id = id.expect("checked is_none() above; qed"); + gum::debug!( target: LOG_TARGET, - "non-Unicode file name {:?} found in {:?}", - file_name, - cache_path, + "reusing existing {:?} for node version v{}", + &path, + NODE_VERSION, ); + artifacts.insert_prepared(id, path, SystemTime::now(), Default::default()); + + Ok(()) + } else { + Err(format!("non-Unicode file name {file_name:?} found in {cache_path:?}")) } } // Make sure that the cache path directory and all its parents are created. if let Err(err) = tokio::fs::create_dir_all(cache_path).await { if err.kind() != io::ErrorKind::AlreadyExists { - gum::error!( - target: LOG_TARGET, - ?err, - "failed to create dir {:?}", - cache_path, - ); - return + return Err(format!("failed to create dir {cache_path:?}: {err}")) } } - let mut dir = match tokio::fs::read_dir(cache_path).await { - Ok(dir) => dir, - Err(err) => { - gum::error!( - target: LOG_TARGET, - ?err, - "failed to read dir {:?}", - cache_path, - ); - return - }, - }; + let mut dir = tokio::fs::read_dir(cache_path) + .await + .map_err(|err| format!("failed to read dir {cache_path:?}: {err}"))?; loop { match dir.next_entry().await { - Ok(Some(entry)) => insert_or_prune(self, &entry, cache_path).await, - Ok(None) => break, - Err(err) => { - gum::warn!( - target: LOG_TARGET, - ?err, - "error processing artifacts in {:?}", - cache_path, - ); - break - }, + Ok(Some(entry)) => + if let Err(err) = insert_or_prune(self, &entry, cache_path).await { + gum::warn!( + target: LOG_TARGET, + ?cache_path, + "could not insert entry {:?} into the artifact cache: {}", + entry, + err, + ) + }, + Ok(None) => return Ok(()), + Err(err) => + return Err(format!("error processing artifacts in {cache_path:?}: {err}")), } } } diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index f7817853dd1be261a9d52df978521f1459057617..d17a4d918e00469405b2185aec2ac3467f864c0c 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -217,6 +217,9 @@ pub async fn start( ) -> SubsystemResult<(ValidationHost, impl Future)> { gum::debug!(target: LOG_TARGET, ?config, "starting PVF validation host"); + // Make sure the cache is initialized before doing anything else. + let artifacts = Artifacts::new_and_prune(&config.cache_path).await; + // 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 { @@ -260,8 +263,6 @@ pub async fn start( let run_sweeper = sweeper_task(to_sweeper_rx); let run_host = async move { - let artifacts = Artifacts::new_and_prune(&config.cache_path).await; - run(Inner { cleanup_pulse_interval: Duration::from_secs(3600), artifact_ttl: Duration::from_secs(3600 * 24), diff --git a/polkadot/node/core/pvf/src/worker_interface.rs b/polkadot/node/core/pvf/src/worker_interface.rs index c68ff92b06eb35216a2d9f661d1b60d09847042c..456c20bd27b21ad7004c298186ab71b848ce3720 100644 --- a/polkadot/node/core/pvf/src/worker_interface.rs +++ b/polkadot/node/core/pvf/src/worker_interface.rs @@ -105,9 +105,9 @@ pub async fn spawn_with_program_path( gum::warn!( target: LOG_TARGET, %debug_id, - ?program_path_clone, - ?extra_args_clone, - ?worker_dir_clone, + program_path = ?program_path_clone, + extra_args = ?extra_args_clone, + worker_dir = ?worker_dir_clone, "error spawning worker: {}", err, ); diff --git a/polkadot/node/core/pvf/tests/it/main.rs b/polkadot/node/core/pvf/tests/it/main.rs index e82ade5edfa1e51c827abeaf00989edcb3743acb..09f975b706d24d2eb3f75f733f73e37afe6ec0cc 100644 --- a/polkadot/node/core/pvf/tests/it/main.rs +++ b/polkadot/node/core/pvf/tests/it/main.rs @@ -445,3 +445,21 @@ async fn all_security_features_work() { } ); } + +// Regression test to make sure the unshare-pivot-root capability does not depend on the PVF +// artifacts cache existing. +#[cfg(all(feature = "ci-only-tests", target_os = "linux"))] +#[tokio::test] +async fn nonexistant_cache_dir() { + let host = TestHost::new_with_config(|cfg| { + cfg.cache_path = cfg.cache_path.join("nonexistant_cache_dir"); + }) + .await; + + assert!(host.security_status().await.can_unshare_user_namespace_and_change_root); + + let _stats = host + .precheck_pvf(::adder::wasm_binary_unwrap(), Default::default()) + .await + .unwrap(); +} diff --git a/polkadot/node/core/pvf/tests/it/process.rs b/polkadot/node/core/pvf/tests/it/process.rs index b742acb15d028caf64f2e4c4d147fa31307e6e8d..3ea03339a8398999b5e26380683831323f771faf 100644 --- a/polkadot/node/core/pvf/tests/it/process.rs +++ b/polkadot/node/core/pvf/tests/it/process.rs @@ -18,14 +18,18 @@ //! spawned by the host) and job processes (spawned by the workers to securely perform PVF jobs). use super::TestHost; +use adder::{hash_state, BlockData, HeadData}; use assert_matches::assert_matches; +use parity_scale_codec::Encode; use polkadot_node_core_pvf::{ InvalidCandidate, PossiblyInvalidError, PrepareError, ValidationError, }; -use polkadot_parachain_primitives::primitives::{BlockData, ValidationParams}; +use polkadot_parachain_primitives::primitives::{ + BlockData as GenericBlockData, HeadData as GenericHeadData, ValidationParams, +}; use procfs::process; use rusty_fork::rusty_fork_test; -use std::time::Duration; +use std::{future::Future, sync::Arc, time::Duration}; const PREPARE_PROCESS_NAME: &'static str = "polkadot-prepare-worker"; const EXECUTE_PROCESS_NAME: &'static str = "polkadot-execute-worker"; @@ -39,11 +43,13 @@ fn send_signal_by_sid_and_name( is_direct_child: bool, signal: i32, ) { - let process = find_process_by_sid_and_name(sid, exe_name, is_direct_child); + let process = find_process_by_sid_and_name(sid, exe_name, is_direct_child) + .expect("Should have found the expected process"); assert_eq!(unsafe { libc::kill(process.pid(), signal) }, 0); } fn get_num_threads_by_sid_and_name(sid: i32, exe_name: &'static str, is_direct_child: bool) -> i64 { - let process = find_process_by_sid_and_name(sid, exe_name, is_direct_child); + let process = find_process_by_sid_and_name(sid, exe_name, is_direct_child) + .expect("Should have found the expected process"); process.stat().unwrap().num_threads } @@ -51,7 +57,7 @@ fn find_process_by_sid_and_name( sid: i32, exe_name: &'static str, is_direct_child: bool, -) -> process::Process { +) -> Option { let all_processes: Vec = process::all_processes() .expect("Can't read /proc") .filter_map(|p| match p { @@ -68,7 +74,7 @@ fn find_process_by_sid_and_name( let mut found = None; for process in all_processes { - let stat = process.stat().unwrap(); + let stat = process.stat().expect("/proc existed above. Potential race occurred"); if stat.session != sid || !process.exe().unwrap().to_str().unwrap().contains(exe_name) { continue @@ -85,24 +91,68 @@ fn find_process_by_sid_and_name( } found = Some(process); } - found.expect("Should have found the expected process") + found +} + +/// Sets up the test and makes sure everything gets cleaned up after. +/// +/// We run the runtime manually because `#[tokio::test]` doesn't work in `rusty_fork_test!`. +fn test_wrapper(f: F) +where + F: FnOnce(Arc, i32) -> Fut, + Fut: Future, +{ + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let host = Arc::new(TestHost::new().await); + + // Create a new session and get the session ID. + let sid = unsafe { libc::setsid() }; + assert!(sid > 0); + + // Pass a clone of the host so that it does not get dropped after. + f(host.clone(), sid).await; + + // Sleep to give processes a chance to get cleaned up, preventing races in the next step. + tokio::time::sleep(Duration::from_millis(500)).await; + + // Make sure job processes got cleaned up. Pass `is_direct_child: false` to target the + // job processes. + assert!(find_process_by_sid_and_name(sid, PREPARE_PROCESS_NAME, false).is_none()); + assert!(find_process_by_sid_and_name(sid, EXECUTE_PROCESS_NAME, false).is_none()); + }); } // Run these tests in their own processes with rusty-fork. They work by each creating a new session, -// then doing something with the child process that matches the session ID and expected process -// name. +// then finding the child process that matches the session ID and expected process name and doing +// something with that child. rusty_fork_test! { + // Everything succeeded. All created subprocesses for jobs should get cleaned up, to avoid memory leaks. + #[test] + fn successful_prepare_and_validate() { + test_wrapper(|host, _sid| async move { + let parent_head = HeadData { number: 0, parent_hash: [0; 32], post_state: hash_state(0) }; + let block_data = BlockData { state: 0, add: 512 }; + host + .validate_candidate( + adder::wasm_binary_unwrap(), + ValidationParams { + parent_head: GenericHeadData(parent_head.encode()), + block_data: GenericBlockData(block_data.encode()), + relay_parent_number: 1, + relay_parent_storage_root: Default::default(), + }, + Default::default(), + ) + .await + .unwrap(); + }) + } + // What happens when the prepare worker (not the job) times out? #[test] fn prepare_worker_timeout() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { let (result, _) = futures::join!( // Choose a job that would normally take the entire timeout. host.precheck_pvf(rococo_runtime::WASM_BINARY.unwrap(), Default::default()), @@ -120,14 +170,7 @@ rusty_fork_test! { // What happens when the execute worker (not the job) times out? #[test] fn execute_worker_timeout() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { // Prepare the artifact ahead of time. let binary = halt::wasm_binary_unwrap(); host.precheck_pvf(binary, Default::default()).await.unwrap(); @@ -137,7 +180,7 @@ rusty_fork_test! { host.validate_candidate( binary, ValidationParams { - block_data: BlockData(Vec::new()), + block_data: GenericBlockData(Vec::new()), parent_head: Default::default(), relay_parent_number: 1, relay_parent_storage_root: Default::default(), @@ -161,14 +204,7 @@ rusty_fork_test! { // What happens when the prepare worker dies in the middle of a job? #[test] fn prepare_worker_killed_during_job() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { let (result, _) = futures::join!( // Choose a job that would normally take the entire timeout. host.precheck_pvf(rococo_runtime::WASM_BINARY.unwrap(), Default::default()), @@ -186,14 +222,7 @@ rusty_fork_test! { // What happens when the execute worker dies in the middle of a job? #[test] fn execute_worker_killed_during_job() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { // Prepare the artifact ahead of time. let binary = halt::wasm_binary_unwrap(); host.precheck_pvf(binary, Default::default()).await.unwrap(); @@ -203,7 +232,7 @@ rusty_fork_test! { host.validate_candidate( binary, ValidationParams { - block_data: BlockData(Vec::new()), + block_data: GenericBlockData(Vec::new()), parent_head: Default::default(), relay_parent_number: 1, relay_parent_storage_root: Default::default(), @@ -227,14 +256,7 @@ rusty_fork_test! { // What happens when the forked prepare job dies in the middle of its job? #[test] fn forked_prepare_job_killed_during_job() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { let (result, _) = futures::join!( // Choose a job that would normally take the entire timeout. host.precheck_pvf(rococo_runtime::WASM_BINARY.unwrap(), Default::default()), @@ -256,14 +278,7 @@ rusty_fork_test! { // What happens when the forked execute job dies in the middle of its job? #[test] fn forked_execute_job_killed_during_job() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { // Prepare the artifact ahead of time. let binary = halt::wasm_binary_unwrap(); host.precheck_pvf(binary, Default::default()).await.unwrap(); @@ -273,7 +288,7 @@ rusty_fork_test! { host.validate_candidate( binary, ValidationParams { - block_data: BlockData(Vec::new()), + block_data: GenericBlockData(Vec::new()), parent_head: Default::default(), relay_parent_number: 1, relay_parent_storage_root: Default::default(), @@ -301,14 +316,7 @@ rusty_fork_test! { // See `run_worker` for why we need this invariant. #[test] fn ensure_prepare_processes_have_correct_num_threads() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { let _ = futures::join!( // Choose a job that would normally take the entire timeout. host.precheck_pvf(rococo_runtime::WASM_BINARY.unwrap(), Default::default()), @@ -338,14 +346,7 @@ rusty_fork_test! { // See `run_worker` for why we need this invariant. #[test] fn ensure_execute_processes_have_correct_num_threads() { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - let host = TestHost::new().await; - - // Create a new session and get the session ID. - let sid = unsafe { libc::setsid() }; - assert!(sid > 0); - + test_wrapper(|host, sid| async move { // Prepare the artifact ahead of time. let binary = halt::wasm_binary_unwrap(); host.precheck_pvf(binary, Default::default()).await.unwrap(); @@ -355,7 +356,7 @@ rusty_fork_test! { host.validate_candidate( binary, ValidationParams { - block_data: BlockData(Vec::new()), + block_data: GenericBlockData(Vec::new()), parent_head: Default::default(), relay_parent_number: 1, relay_parent_storage_root: Default::default(), diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml index 965b280a747ab0524337b24fd0d6b2161705e746..07be4d128c25f6f71b8e39803f60857234f04198 100644 --- a/polkadot/node/core/runtime-api/Cargo.toml +++ b/polkadot/node/core/runtime-api/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } @@ -22,7 +25,7 @@ polkadot-node-subsystem-types = { path = "../../subsystem-types" } sp-api = { path = "../../../../substrate/primitives/api" } sp-core = { path = "../../../../substrate/primitives/core" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } -async-trait = "0.1.57" +async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-node-primitives = { path = "../../primitives" } diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index 8a7a3dc08b8173e7029158d3a0ca6030d898e3f7..5eca551db0a69f0edcab3cbba87c2029274b50ef 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -20,12 +20,13 @@ use schnellru::{ByLength, LruMap}; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - async_backing, slashing, vstaging, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, - CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, - ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, - ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, ValidatorSignature, + async_backing, slashing, + vstaging::{self, ApprovalVotingParams}, + AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, + PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, + ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; /// For consistency we have the same capacity for all caches. We use 128 as we'll only need that @@ -68,6 +69,7 @@ pub(crate) struct RequestResultCache { para_backing_state: LruMap<(Hash, ParaId), Option>, async_backing_params: LruMap, node_features: LruMap, + approval_voting_params: LruMap, } impl Default for RequestResultCache { @@ -98,6 +100,7 @@ impl Default for RequestResultCache { unapplied_slashes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), key_ownership_proof: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), minimum_backing_votes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + approval_voting_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), disabled_validators: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), para_backing_state: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), @@ -507,6 +510,21 @@ impl RequestResultCache { ) { self.async_backing_params.insert(key, value); } + + pub(crate) fn approval_voting_params( + &mut self, + key: (Hash, SessionIndex), + ) -> Option<&ApprovalVotingParams> { + self.approval_voting_params.get(&key.1).map(|v| &*v) + } + + pub(crate) fn cache_approval_voting_params( + &mut self, + session_index: SessionIndex, + value: ApprovalVotingParams, + ) { + self.approval_voting_params.insert(session_index, value); + } } pub(crate) enum RequestResult { @@ -554,6 +572,7 @@ pub(crate) enum RequestResult { slashing::OpaqueKeyOwnershipProof, Option<()>, ), + ApprovalVotingParams(Hash, SessionIndex, ApprovalVotingParams), DisabledValidators(Hash, Vec), ParaBackingState(Hash, ParaId, Option), AsyncBackingParams(Hash, async_backing::AsyncBackingParams), diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index 8689355c4139af263d393cdd46d62f17abde66c1..4bedfd827340bc60b0101f1c854f207705bc0b31 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -165,6 +165,8 @@ where KeyOwnershipProof(relay_parent, validator_id, key_ownership_proof) => self .requests_cache .cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof), + RequestResult::ApprovalVotingParams(_relay_parent, session_index, params) => + self.requests_cache.cache_approval_voting_params(session_index, params), SubmitReportDisputeLost(_, _, _, _) => {}, DisabledValidators(relay_parent, disabled_validators) => self.requests_cache.cache_disabled_validators(relay_parent, disabled_validators), @@ -300,6 +302,9 @@ where Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) }, ), + Request::ApprovalVotingParams(session_index, sender) => + query!(approval_voting_params(session_index), sender) + .map(|sender| Request::ApprovalVotingParams(session_index, sender)), Request::DisabledValidators(sender) => query!(disabled_validators(), sender) .map(|sender| Request::DisabledValidators(sender)), Request::ParaBackingState(para, sender) => query!(para_backing_state(para), sender) @@ -571,6 +576,14 @@ where ver = Request::KEY_OWNERSHIP_PROOF_RUNTIME_REQUIREMENT, sender ), + Request::ApprovalVotingParams(session_index, sender) => { + query!( + ApprovalVotingParams, + approval_voting_params(session_index), + ver = Request::APPROVAL_VOTING_PARAMS_REQUIREMENT, + sender + ) + }, Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) => query!( SubmitReportDisputeLost, submit_report_dispute_lost(dispute_proof, key_ownership_proof), diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index b939bffb0e7f8c896c35f51d75ffa9e43e60df79..f91723b3d39e9a6548d94ffde1da534d2e7592ab 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -20,12 +20,13 @@ use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfigurati use polkadot_node_subsystem::SpawnGlue; use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_primitives::{ - async_backing, slashing, vstaging::NodeFeatures, AuthorityDiscoveryId, BlockNumber, - CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, - DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, - ScrapedOnChainVotes, SessionIndex, SessionInfo, Slot, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, ValidatorSignature, + async_backing, slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, + PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, + Slot, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; use sp_api::ApiError; use sp_core::testing::TaskExecutor; @@ -242,6 +243,15 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { todo!("Not required for tests") } + /// Approval voting configuration parameters + async fn approval_voting_params( + &self, + _: Hash, + _: SessionIndex, + ) -> Result { + todo!("Not required for tests") + } + async fn current_epoch(&self, _: Hash) -> Result { Ok(self.babe_epoch.as_ref().unwrap().clone()) } diff --git a/polkadot/node/gum/Cargo.toml b/polkadot/node/gum/Cargo.toml index acee9efd0e098d2e9c31c3ce250ca06484ae5b38..ccb21f64e6375547409f32b6de5257d2ff39b88d 100644 --- a/polkadot/node/gum/Cargo.toml +++ b/polkadot/node/gum/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Stick logs together with the TraceID as provided by tempo" +[lints] +workspace = true + [dependencies] coarsetime = "0.1.22" tracing = "0.1.35" diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index 731798cc01e2c7d40b4b84a023686ee4faab980f..1f9c5b1b9186358637c58cb9a65adc8fc1292b3c 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Generate an overseer including builder pattern and message wrapper from a single annotated struct definition." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -13,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.39", features = ["extra-traits", "full"] } +syn = { version = "2.0.48", features = ["extra-traits", "full"] } quote = "1.0.28" proc-macro2 = "1.0.56" -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" expander = "2.0.0" [dev-dependencies] diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index fcfbbaec611ef22ae593ad05bcb058a2316a0e9b..81947f4f6a4acfeb8623ccf1f5454c94e8067254 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Polkadot Jaeger primitives, but equally useful for Grafana/Tempo" +[lints] +workspace = true + [dependencies] mick-jaeger = "0.1.8" lazy_static = "1.4" diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 1958bcf4620aff190f7dacdae54f6e694631a2c1..f555cbd5dac2db81c9efb4086b5d019f21fa9850 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -8,6 +8,9 @@ license.workspace = true readme = "README.md" publish = false +[lints] +workspace = true + [[bin]] name = "malus" path = "src/malus.rs" @@ -37,10 +40,10 @@ polkadot-node-primitives = { path = "../primitives" } polkadot-primitives = { path = "../../primitives" } color-eyre = { version = "0.6.1", default-features = false } assert_matches = "1.5" -async-trait = "0.1.57" +async-trait = "0.1.74" sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } diff --git a/polkadot/node/malus/src/interceptor.rs b/polkadot/node/malus/src/interceptor.rs index 04ee0905deeb0758f222fce2cc6d3075b5a6000f..e994319beb9637cf4774468f68faa4e8a331d6b8 100644 --- a/polkadot/node/malus/src/interceptor.rs +++ b/polkadot/node/malus/src/interceptor.rs @@ -21,7 +21,7 @@ //! messages on the overseer level. use polkadot_node_subsystem::*; -pub use polkadot_node_subsystem::{messages, messages::*, overseer, FromOrchestra}; +pub use polkadot_node_subsystem::{messages::*, overseer, FromOrchestra}; use std::{future::Future, pin::Pin}; /// Filter incoming and outgoing messages. diff --git a/polkadot/node/malus/src/malus.rs b/polkadot/node/malus/src/malus.rs index b8a83e54d4f5200df3d28822d6ecec11eecc9802..7a9e320e27368e51da0508910fea5b7c54f2d6c7 100644 --- a/polkadot/node/malus/src/malus.rs +++ b/polkadot/node/malus/src/malus.rs @@ -32,6 +32,8 @@ use variants::*; enum NemesisVariant { /// Suggest a candidate with an invalid proof of validity. SuggestGarbageCandidate(SuggestGarbageCandidateOptions), + /// Support disabled validators in backing and statement distribution. + SupportDisabled(SupportDisabledOptions), /// Back a candidate with a specifically crafted proof of validity. BackGarbageCandidate(BackGarbageCandidateOptions), /// Delayed disputing of ancestors that are perfectly fine. @@ -68,6 +70,11 @@ impl MalusCli { finality_delay, )? }, + NemesisVariant::SupportDisabled(opts) => { + let SupportDisabledOptions { cli } = opts; + + polkadot_cli::run_node(cli, SupportDisabled, finality_delay)? + }, NemesisVariant::DisputeAncestor(opts) => { let DisputeAncestorOptions { fake_validation, diff --git a/polkadot/node/malus/src/variants/common.rs b/polkadot/node/malus/src/variants/common.rs index 92264cd653d052a6da0e4389297496a40404c5e9..011fcc80e37331909434ccd2fa1bfa1e96f48f35 100644 --- a/polkadot/node/malus/src/variants/common.rs +++ b/polkadot/node/malus/src/variants/common.rs @@ -188,7 +188,7 @@ where let _candidate_descriptor = candidate_descriptor.clone(); let mut subsystem_sender = subsystem_sender.clone(); let (sender, receiver) = std::sync::mpsc::channel(); - self.spawner.spawn( + self.spawner.spawn_blocking( "malus-get-validation-data", Some("malus"), Box::pin(async move { diff --git a/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs index 113ab026879d479a425496c74fc920c5017a6614..7f83c386090ed9d74d543201c984141e9c9e6152 100644 --- a/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs +++ b/polkadot/node/malus/src/variants/dispute_finalized_candidates.rs @@ -95,7 +95,7 @@ where let dispute_offset = self.dispute_offset; let mut sender = subsystem_sender.clone(); - self.spawner.spawn( + self.spawner.spawn_blocking( "malus-dispute-finalized-block", Some("malus"), Box::pin(async move { diff --git a/polkadot/node/malus/src/variants/mod.rs b/polkadot/node/malus/src/variants/mod.rs index bb4971c145cee8e78494096869877eb79e5b030e..3ca1bf4b4696843476c3de2b6dc441224b0ea9bd 100644 --- a/polkadot/node/malus/src/variants/mod.rs +++ b/polkadot/node/malus/src/variants/mod.rs @@ -21,11 +21,13 @@ mod common; mod dispute_finalized_candidates; mod dispute_valid_candidates; mod suggest_garbage_candidate; +mod support_disabled; pub(crate) use self::{ back_garbage_candidate::{BackGarbageCandidateOptions, BackGarbageCandidates}, dispute_finalized_candidates::{DisputeFinalizedCandidates, DisputeFinalizedCandidatesOptions}, dispute_valid_candidates::{DisputeAncestorOptions, DisputeValidCandidates}, suggest_garbage_candidate::{SuggestGarbageCandidateOptions, SuggestGarbageCandidates}, + support_disabled::{SupportDisabled, SupportDisabledOptions}, }; pub(crate) use common::*; diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs index 817afb58437e16780a0db79173b4e9dc2f7f3e14..cf0ff5f809d8c31df07a13fb3eac0661f8486917 100644 --- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs @@ -113,7 +113,7 @@ where let (sender, receiver) = std::sync::mpsc::channel(); let mut new_sender = subsystem_sender.clone(); let _candidate = candidate.clone(); - self.spawner.spawn( + self.spawner.spawn_blocking( "malus-get-validation-data", Some("malus"), Box::pin(async move { diff --git a/polkadot/node/malus/src/variants/support_disabled.rs b/polkadot/node/malus/src/variants/support_disabled.rs new file mode 100644 index 0000000000000000000000000000000000000000..5fb53be7774b3d6093f33916565731cff6bc863c --- /dev/null +++ b/polkadot/node/malus/src/variants/support_disabled.rs @@ -0,0 +1,98 @@ +// 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 . + +//! This variant of Malus overrides the `disabled_validators` runtime API +//! to always return an empty set of disabled validators. + +use polkadot_cli::{ + prepared_overseer_builder, + service::{ + AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, HeaderBackend, Overseer, + OverseerConnector, OverseerGen, OverseerGenArgs, OverseerHandle, ParachainHost, + ProvideRuntimeApi, + }, + Cli, +}; +use polkadot_node_subsystem::SpawnGlue; +use polkadot_node_subsystem_types::DefaultSubsystemClient; +use sp_core::traits::SpawnNamed; + +use crate::interceptor::*; + +use std::sync::Arc; + +#[derive(Debug, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct SupportDisabledOptions { + #[clap(flatten)] + pub cli: Cli, +} + +/// Generates an overseer with a custom runtime API subsystem. +pub(crate) struct SupportDisabled; + +impl OverseerGen for SupportDisabled { + fn generate( + &self, + connector: OverseerConnector, + args: OverseerGenArgs<'_, Spawner, RuntimeClient>, + ) -> Result< + (Overseer, Arc>>, OverseerHandle), + Error, + > + where + RuntimeClient: 'static + ProvideRuntimeApi + HeaderBackend + AuxStore, + RuntimeClient::Api: ParachainHost + BabeApi + AuthorityDiscoveryApi, + Spawner: 'static + SpawnNamed + Clone + Unpin, + { + prepared_overseer_builder(args)? + .replace_runtime_api(move |ra_subsystem| { + InterceptedSubsystem::new(ra_subsystem, IgnoreDisabled) + }) + .build_with_connector(connector) + .map_err(|e| e.into()) + } +} + +#[derive(Clone)] +struct IgnoreDisabled; + +impl MessageInterceptor for IgnoreDisabled +where + Sender: overseer::RuntimeApiSenderTrait + Clone + Send + 'static, +{ + type Message = RuntimeApiMessage; + + /// Intercept incoming runtime api requests. + fn intercept_incoming( + &self, + _subsystem_sender: &mut Sender, + msg: FromOrchestra, + ) -> Option> { + match msg { + FromOrchestra::Communication { + msg: + RuntimeApiMessage::Request(_relay_parent, RuntimeApiRequest::DisabledValidators(tx)), + } => { + let _ = tx.send(Ok(Vec::new())); + None + }, + FromOrchestra::Communication { msg } => Some(FromOrchestra::Communication { msg }), + FromOrchestra::Signal(signal) => Some(FromOrchestra::Signal(signal)), + } + } +} diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index e8e00a64c0569b4242f66f9ad1c9e6eeeb34f6a6..e9a4d463f4d907f197ed1bf7ad83f2b5243c8fc3 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index 7db4aa77b7a638e2acc9029d57dec2628c3225e7..6f261ae770011c0933f1775fb8b92f1f455baea7 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] polkadot-node-metrics = { path = "../../metrics" } polkadot-node-network-protocol = { path = "../protocol" } @@ -30,9 +33,9 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } assert_matches = "1.4.0" -schnorrkel = { version = "0.9.1", default-features = false } +schnorrkel = { version = "0.11.4", default-features = false } # rand_core should match schnorrkel -rand_core = "0.5.1" +rand_core = "0.6.2" rand_chacha = "0.3.1" env_logger = "0.9.0" log = "0.4.17" diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 47482eef764096427a1a227810b386491ba49f11..d520febaef51fa2e7a7da34d0e5be8336c673c57 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -32,14 +32,15 @@ use polkadot_node_network_protocol::{ self as net_protocol, filter_by_peer_version, grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology}, peer_set::MAX_NOTIFICATION_SIZE, - v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, PeerId, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, PeerId, UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_primitives::approval::{ - v1::{ - AssignmentCertKind, BlockApprovalMeta, IndirectAssignmentCert, IndirectSignedApprovalVote, + v1::{AssignmentCertKind, BlockApprovalMeta, IndirectAssignmentCert}, + v2::{ + AsBitIndex, AssignmentCertKindV2, CandidateBitfield, IndirectAssignmentCertV2, + IndirectSignedApprovalVoteV2, }, - v2::{AsBitIndex, AssignmentCertKindV2, CandidateBitfield, IndirectAssignmentCertV2}, }; use polkadot_node_subsystem::{ messages::{ @@ -113,6 +114,14 @@ struct ApprovalRouting { required_routing: RequiredRouting, local: bool, random_routing: RandomRouting, + peers_randomly_routed: Vec, +} + +impl ApprovalRouting { + fn mark_randomly_sent(&mut self, peer: PeerId) { + self.random_routing.inc_sent(); + self.peers_randomly_routed.push(peer); + } } // This struct is responsible for tracking the full state of an assignment and grid routing @@ -121,9 +130,9 @@ struct ApprovalEntry { // The assignment certificate. assignment: IndirectAssignmentCertV2, // The candidates claimed by the certificate. A mapping between bit index and candidate index. - candidates: CandidateBitfield, + assignment_claimed_candidates: CandidateBitfield, // The approval signatures for each `CandidateIndex` claimed by the assignment certificate. - approvals: HashMap, + approvals: HashMap, // The validator index of the assignment signer. validator_index: ValidatorIndex, // Information required for gossiping to other peers using the grid topology. @@ -136,6 +145,8 @@ enum ApprovalEntryError { CandidateIndexOutOfBounds, InvalidCandidateIndex, DuplicateApproval, + UnknownAssignment, + AssignmentsFollowedDifferentPaths(RequiredRouting, RequiredRouting), } impl ApprovalEntry { @@ -148,7 +159,7 @@ impl ApprovalEntry { validator_index: assignment.validator, assignment, approvals: HashMap::with_capacity(candidates.len()), - candidates, + assignment_claimed_candidates: candidates, routing_info, } } @@ -156,23 +167,15 @@ impl ApprovalEntry { // Create a `MessageSubject` to reference the assignment. pub fn create_assignment_knowledge(&self, block_hash: Hash) -> (MessageSubject, MessageKind) { ( - MessageSubject(block_hash, self.candidates.clone(), self.validator_index), + MessageSubject( + block_hash, + self.assignment_claimed_candidates.clone(), + self.validator_index, + ), MessageKind::Assignment, ) } - // Create a `MessageSubject` to reference the approval. - pub fn create_approval_knowledge( - &self, - block_hash: Hash, - candidate_index: CandidateIndex, - ) -> (MessageSubject, MessageKind) { - ( - MessageSubject(block_hash, candidate_index.into(), self.validator_index), - MessageKind::Approval, - ) - } - // Updates routing information and returns the previous information if any. pub fn routing_info_mut(&mut self) -> &mut ApprovalRouting { &mut self.routing_info @@ -188,11 +191,21 @@ impl ApprovalEntry { self.routing_info.required_routing = required_routing; } + // Tells if this entry assignment covers at least one candidate in the approval + pub fn includes_approval_candidates(&self, approval: &IndirectSignedApprovalVoteV2) -> bool { + for candidate_index in approval.candidate_indices.iter_ones() { + if self.assignment_claimed_candidates.bit_at((candidate_index).as_bit_index()) { + return true + } + } + return false + } + // Records a new approval. Returns error if the claimed candidate is not found or we already // have received the approval. pub fn note_approval( &mut self, - approval: IndirectSignedApprovalVote, + approval: IndirectSignedApprovalVoteV2, ) -> Result<(), ApprovalEntryError> { // First do some sanity checks: // - check validator index matches @@ -202,37 +215,29 @@ impl ApprovalEntry { return Err(ApprovalEntryError::InvalidValidatorIndex) } - if self.candidates.len() <= approval.candidate_index as usize { - return Err(ApprovalEntryError::CandidateIndexOutOfBounds) - } - - if !self.candidates.bit_at(approval.candidate_index.as_bit_index()) { + // We need at least one of the candidates in the approval to be in this assignment + if !self.includes_approval_candidates(&approval) { return Err(ApprovalEntryError::InvalidCandidateIndex) } - if self.approvals.contains_key(&approval.candidate_index) { + if self.approvals.contains_key(&approval.candidate_indices) { return Err(ApprovalEntryError::DuplicateApproval) } - self.approvals.insert(approval.candidate_index, approval); + self.approvals.insert(approval.candidate_indices.clone(), approval.clone()); Ok(()) } // Get the assignment certiticate and claimed candidates. pub fn assignment(&self) -> (IndirectAssignmentCertV2, CandidateBitfield) { - (self.assignment.clone(), self.candidates.clone()) + (self.assignment.clone(), self.assignment_claimed_candidates.clone()) } // Get all approvals for all candidates claimed by the assignment. - pub fn approvals(&self) -> Vec { + pub fn approvals(&self) -> Vec { self.approvals.values().cloned().collect::>() } - // Get the approval for a specific candidate index. - pub fn approval(&self, candidate_index: CandidateIndex) -> Option { - self.approvals.get(&candidate_index).cloned() - } - // Get validator index. pub fn validator_index(&self) -> ValidatorIndex { self.validator_index @@ -430,6 +435,41 @@ impl PeerKnowledge { fn contains(&self, message: &MessageSubject, kind: MessageKind) -> bool { self.sent.contains(message, kind) || self.received.contains(message, kind) } + + // Generate the knowledge keys for querying if all assignments of an approval are known + // by this peer. + fn generate_assignments_keys( + approval: &IndirectSignedApprovalVoteV2, + ) -> Vec<(MessageSubject, MessageKind)> { + approval + .candidate_indices + .iter_ones() + .map(|candidate_index| { + ( + MessageSubject( + approval.block_hash, + (candidate_index as CandidateIndex).into(), + approval.validator, + ), + MessageKind::Assignment, + ) + }) + .collect_vec() + } + + // Generate the knowledge keys for querying if an approval is known by peer. + fn generate_approval_key( + approval: &IndirectSignedApprovalVoteV2, + ) -> (MessageSubject, MessageKind) { + ( + MessageSubject( + approval.block_hash, + approval.candidate_indices.clone(), + approval.validator, + ), + MessageKind::Approval, + ) + } } /// Information about blocks in our current view as well as whether peers know of them. @@ -462,13 +502,13 @@ impl BlockEntry { // First map one entry per candidate to the same key we will use in `approval_entries`. // Key is (Validator_index, CandidateBitfield) that links the `ApprovalEntry` to the (K,V) // entry in `candidate_entry.messages`. - for claimed_candidate_index in entry.candidates.iter_ones() { + for claimed_candidate_index in entry.assignment_claimed_candidates.iter_ones() { match self.candidates.get_mut(claimed_candidate_index) { Some(candidate_entry) => { candidate_entry - .messages + .assignments .entry(entry.validator_index()) - .or_insert(entry.candidates.clone()); + .or_insert(entry.assignment_claimed_candidates.clone()); }, None => { // This should never happen, but if it happens, it means the subsystem is @@ -484,50 +524,107 @@ impl BlockEntry { } self.approval_entries - .entry((entry.validator_index, entry.candidates.clone())) + .entry((entry.validator_index, entry.assignment_claimed_candidates.clone())) .or_insert(entry) } - // Returns a mutable reference of `ApprovalEntry` for `candidate_index` from validator - // `validator_index`. - pub fn approval_entry( + // Tels if all candidate_indices are valid candidates + pub fn contains_candidates(&self, candidate_indices: &CandidateBitfield) -> bool { + candidate_indices + .iter_ones() + .all(|candidate_index| self.candidates.get(candidate_index as usize).is_some()) + } + + // Saves the given approval in all ApprovalEntries that contain an assignment for any of the + // candidates in the approval. + // + // Returns the required routing needed for this approval and the lit of random peers the + // covering assignments were sent. + pub fn note_approval( &mut self, - candidate_index: CandidateIndex, - validator_index: ValidatorIndex, - ) -> Option<&mut ApprovalEntry> { - self.candidates - .get(candidate_index as usize) - .map_or(None, |candidate_entry| candidate_entry.messages.get(&validator_index)) - .map_or(None, |candidate_indices| { - self.approval_entries.get_mut(&(validator_index, candidate_indices.clone())) + approval: IndirectSignedApprovalVoteV2, + ) -> Result<(RequiredRouting, HashSet), ApprovalEntryError> { + let mut required_routing = None; + let mut peers_randomly_routed_to = HashSet::new(); + + if self.candidates.len() < approval.candidate_indices.len() as usize { + return Err(ApprovalEntryError::CandidateIndexOutOfBounds) + } + + // First determine all assignments bitfields that might be covered by this approval + let covered_assignments_bitfields: HashSet = approval + .candidate_indices + .iter_ones() + .filter_map(|candidate_index| { + self.candidates.get_mut(candidate_index).map_or(None, |candidate_entry| { + candidate_entry.assignments.get(&approval.validator).cloned() + }) }) - } + .collect(); - // Get all approval entries for a given candidate. - pub fn approval_entries(&self, candidate_index: CandidateIndex) -> Vec<&ApprovalEntry> { - // Get the keys for fetching `ApprovalEntry` from `self.approval_entries`, - let approval_entry_keys = self - .candidates - .get(candidate_index as usize) - .map(|candidate_entry| &candidate_entry.messages); - - if let Some(approval_entry_keys) = approval_entry_keys { - // Ensure no duplicates. - let approval_entry_keys = approval_entry_keys.iter().unique().collect::>(); - - let mut entries = Vec::new(); - for (validator_index, candidate_indices) in approval_entry_keys { - if let Some(entry) = - self.approval_entries.get(&(*validator_index, candidate_indices.clone())) - { - entries.push(entry); + // Mark the vote in all approval entries + for assignment_bitfield in covered_assignments_bitfields { + if let Some(approval_entry) = + self.approval_entries.get_mut(&(approval.validator, assignment_bitfield)) + { + approval_entry.note_approval(approval.clone())?; + peers_randomly_routed_to + .extend(approval_entry.routing_info().peers_randomly_routed.iter()); + + if let Some(required_routing) = required_routing { + if required_routing != approval_entry.routing_info().required_routing { + // This shouldn't happen since the required routing is computed based on the + // validator_index, so two assignments from the same validators will have + // the same required routing. + return Err(ApprovalEntryError::AssignmentsFollowedDifferentPaths( + required_routing, + approval_entry.routing_info().required_routing, + )) + } + } else { + required_routing = Some(approval_entry.routing_info().required_routing) } } - entries + } + + if let Some(required_routing) = required_routing { + Ok((required_routing, peers_randomly_routed_to)) } else { - vec![] + Err(ApprovalEntryError::UnknownAssignment) } } + + /// Returns the list of approval votes covering this candidate + pub fn approval_votes( + &self, + candidate_index: CandidateIndex, + ) -> Vec { + let result: Option< + HashMap<(ValidatorIndex, CandidateBitfield), IndirectSignedApprovalVoteV2>, + > = self.candidates.get(candidate_index as usize).map(|candidate_entry| { + candidate_entry + .assignments + .iter() + .filter_map(|(validator, assignment_bitfield)| { + self.approval_entries.get(&(*validator, assignment_bitfield.clone())) + }) + .flat_map(|approval_entry| { + approval_entry + .approvals + .clone() + .into_iter() + .filter(|(approved_candidates, _)| { + approved_candidates.bit_at(candidate_index.as_bit_index()) + }) + .map(|(approved_candidates, vote)| { + ((approval_entry.validator_index, approved_candidates), vote) + }) + }) + .collect() + }); + + result.map(|result| result.into_values().collect_vec()).unwrap_or_default() + } } // Information about candidates in the context of a particular block they are included in. @@ -537,7 +634,7 @@ impl BlockEntry { struct CandidateEntry { // The value represents part of the lookup key in `approval_entries` to fetch the assignment // and existing votes. - messages: HashMap, + assignments: HashMap, } #[derive(Debug, Clone, PartialEq)] @@ -557,7 +654,7 @@ impl MessageSource { enum PendingMessage { Assignment(IndirectAssignmentCertV2, CandidateBitfield), - Approval(IndirectSignedApprovalVote), + Approval(IndirectSignedApprovalVoteV2), } #[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)] @@ -830,6 +927,49 @@ impl State { } } + // Entry point for processing an approval coming from a peer. + async fn process_incoming_approvals( + &mut self, + ctx: &mut Context, + metrics: &Metrics, + peer_id: PeerId, + approvals: Vec, + ) { + gum::trace!( + target: LOG_TARGET, + peer_id = %peer_id, + num = approvals.len(), + "Processing approvals from a peer", + ); + for approval_vote in approvals.into_iter() { + if let Some(pending) = self.pending_known.get_mut(&approval_vote.block_hash) { + let block_hash = approval_vote.block_hash; + let validator_index = approval_vote.validator; + + gum::trace!( + target: LOG_TARGET, + %peer_id, + ?block_hash, + ?validator_index, + "Pending assignment candidates {:?}", + approval_vote.candidate_indices, + ); + + pending.push((peer_id, PendingMessage::Approval(approval_vote))); + + continue + } + + self.import_and_circulate_approval( + ctx, + metrics, + MessageSource::Peer(peer_id), + approval_vote, + ) + .await; + } + } + async fn process_incoming_peer_message( &mut self, ctx: &mut Context, @@ -838,16 +978,14 @@ impl State { msg: Versioned< protocol_v1::ApprovalDistributionMessage, protocol_v2::ApprovalDistributionMessage, - protocol_vstaging::ApprovalDistributionMessage, + protocol_v3::ApprovalDistributionMessage, >, rng: &mut R, ) where R: CryptoRng + Rng, { match msg { - Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Assignments( - assignments, - )) => { + Versioned::V3(protocol_v3::ApprovalDistributionMessage::Assignments(assignments)) => { gum::trace!( target: LOG_TARGET, peer_id = %peer_id, @@ -887,45 +1025,18 @@ impl State { ) .await; }, - Versioned::VStaging(protocol_vstaging::ApprovalDistributionMessage::Approvals( - approvals, - )) | + Versioned::V3(protocol_v3::ApprovalDistributionMessage::Approvals(approvals)) => { + self.process_incoming_approvals(ctx, metrics, peer_id, approvals).await; + }, Versioned::V1(protocol_v1::ApprovalDistributionMessage::Approvals(approvals)) | Versioned::V2(protocol_v2::ApprovalDistributionMessage::Approvals(approvals)) => { - gum::trace!( - target: LOG_TARGET, - peer_id = %peer_id, - num = approvals.len(), - "Processing approvals from a peer", - ); - for approval_vote in approvals.into_iter() { - if let Some(pending) = self.pending_known.get_mut(&approval_vote.block_hash) { - let block_hash = approval_vote.block_hash; - let candidate_index = approval_vote.candidate_index; - let validator_index = approval_vote.validator; - - gum::trace!( - target: LOG_TARGET, - %peer_id, - ?block_hash, - ?candidate_index, - ?validator_index, - "Pending assignment", - ); - - pending.push((peer_id, PendingMessage::Approval(approval_vote))); - - continue - } - - self.import_and_circulate_approval( - ctx, - metrics, - MessageSource::Peer(peer_id), - approval_vote, - ) - .await; - } + self.process_incoming_approvals( + ctx, + metrics, + peer_id, + approvals.into_iter().map(|approval| approval.into()).collect::>(), + ) + .await; }, } } @@ -1071,8 +1182,11 @@ impl State { COST_UNEXPECTED_MESSAGE, ) .await; + gum::debug!(target: LOG_TARGET, "Received assignment for invalid block"); + metrics.on_assignment_recent_outdated(); } } + metrics.on_assignment_invalid_block(); return }, }; @@ -1105,6 +1219,7 @@ impl State { COST_DUPLICATE_MESSAGE, ) .await; + metrics.on_assignment_duplicate(); } else { gum::trace!( target: LOG_TARGET, @@ -1132,6 +1247,7 @@ impl State { COST_UNEXPECTED_MESSAGE, ) .await; + metrics.on_assignment_out_of_view(); }, } @@ -1148,6 +1264,7 @@ impl State { gum::trace!(target: LOG_TARGET, ?peer_id, ?message_subject, "Known assignment"); peer_knowledge.received.insert(message_subject, message_kind); } + metrics.on_assignment_good_known(); return } @@ -1204,6 +1321,8 @@ impl State { ?peer_id, "Got an `AcceptedDuplicate` assignment", ); + metrics.on_assignment_duplicatevoting(); + return }, AssignmentCheckResult::TooFarInFuture => { @@ -1220,6 +1339,8 @@ impl State { COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE, ) .await; + metrics.on_assignment_far(); + return }, AssignmentCheckResult::Bad(error) => { @@ -1237,6 +1358,7 @@ impl State { COST_INVALID_MESSAGE, ) .await; + metrics.on_assignment_bad(); return }, } @@ -1275,7 +1397,12 @@ impl State { let approval_entry = entry.insert_approval_entry(ApprovalEntry::new( assignment.clone(), claimed_candidate_indices.clone(), - ApprovalRouting { required_routing, local, random_routing: Default::default() }, + ApprovalRouting { + required_routing, + local, + random_routing: Default::default(), + peers_randomly_routed: Default::default(), + }, )); // Dispatch the message to all peers in the routing set which @@ -1305,6 +1432,10 @@ impl State { continue } + if !topology.map(|topology| topology.is_validator(&peer)).unwrap_or(false) { + continue + } + // Note: at this point, we haven't received the message from any peers // other than the source peer, and we just got it, so we haven't sent it // to any peers either. @@ -1312,7 +1443,7 @@ impl State { approval_entry.routing_info().random_routing.sample(n_peers_total, rng); if route_random { - approval_entry.routing_info_mut().random_routing.inc_sent(); + approval_entry.routing_info_mut().mark_randomly_sent(peer); peers.push(peer); } } @@ -1346,12 +1477,94 @@ impl State { } } + // Checks if an approval can be processed. + // Returns true if we can continue with processing the approval and false otherwise. + async fn check_approval_can_be_processed( + ctx: &mut Context, + assignments_knowledge_key: &Vec<(MessageSubject, MessageKind)>, + approval_knowledge_key: &(MessageSubject, MessageKind), + entry: &mut BlockEntry, + reputation: &mut ReputationAggregator, + peer_id: PeerId, + metrics: &Metrics, + ) -> bool { + for message_subject in assignments_knowledge_key { + if !entry.knowledge.contains(&message_subject.0, message_subject.1) { + gum::trace!( + target: LOG_TARGET, + ?peer_id, + ?message_subject, + "Unknown approval assignment", + ); + modify_reputation(reputation, ctx.sender(), peer_id, COST_UNEXPECTED_MESSAGE).await; + metrics.on_approval_unknown_assignment(); + return false + } + } + + // check if our knowledge of the peer already contains this approval + match entry.known_by.entry(peer_id) { + hash_map::Entry::Occupied(mut knowledge) => { + let peer_knowledge = knowledge.get_mut(); + if peer_knowledge.contains(&approval_knowledge_key.0, approval_knowledge_key.1) { + if !peer_knowledge + .received + .insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1) + { + gum::trace!( + target: LOG_TARGET, + ?peer_id, + ?approval_knowledge_key, + "Duplicate approval", + ); + + modify_reputation( + reputation, + ctx.sender(), + peer_id, + COST_DUPLICATE_MESSAGE, + ) + .await; + metrics.on_approval_duplicate(); + } + return false + } + }, + hash_map::Entry::Vacant(_) => { + gum::debug!( + target: LOG_TARGET, + ?peer_id, + ?approval_knowledge_key, + "Approval from a peer is out of view", + ); + modify_reputation(reputation, ctx.sender(), peer_id, COST_UNEXPECTED_MESSAGE).await; + metrics.on_approval_out_of_view(); + }, + } + + if entry.knowledge.contains(&approval_knowledge_key.0, approval_knowledge_key.1) { + if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) { + peer_knowledge + .received + .insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1); + } + + // We already processed this approval no need to continue. + gum::trace!(target: LOG_TARGET, ?peer_id, ?approval_knowledge_key, "Known approval"); + metrics.on_approval_good_known(); + modify_reputation(reputation, ctx.sender(), peer_id, BENEFIT_VALID_MESSAGE).await; + false + } else { + true + } + } + async fn import_and_circulate_approval( &mut self, ctx: &mut Context, metrics: &Metrics, source: MessageSource, - vote: IndirectSignedApprovalVote, + vote: IndirectSignedApprovalVoteV2, ) { let _span = self .spans @@ -1370,10 +1583,9 @@ impl State { let block_hash = vote.block_hash; let validator_index = vote.validator; - let candidate_index = vote.candidate_index; - + let candidate_indices = &vote.candidate_indices; let entry = match self.blocks.get_mut(&block_hash) { - Some(entry) if entry.candidates.get(candidate_index as usize).is_some() => entry, + Some(entry) if entry.contains_candidates(&vote.candidate_indices) => entry, _ => { if let Some(peer_id) = source.peer_id() { if !self.recent_outdated_blocks.is_recent_outdated(&block_hash) { @@ -1382,7 +1594,7 @@ impl State { ?peer_id, ?block_hash, ?validator_index, - ?candidate_index, + ?candidate_indices, "Approval from a peer is out of view", ); modify_reputation( @@ -1392,6 +1604,9 @@ impl State { COST_UNEXPECTED_MESSAGE, ) .await; + metrics.on_approval_invalid_block(); + } else { + metrics.on_approval_recent_outdated(); } } return @@ -1399,81 +1614,21 @@ impl State { }; // compute metadata on the assignment. - let message_subject = MessageSubject(block_hash, candidate_index.into(), validator_index); - let message_kind = MessageKind::Approval; + let assignments_knowledge_keys = PeerKnowledge::generate_assignments_keys(&vote); + let approval_knwowledge_key = PeerKnowledge::generate_approval_key(&vote); if let Some(peer_id) = source.peer_id() { - if !entry.knowledge.contains(&message_subject, MessageKind::Assignment) { - gum::debug!( - target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Unknown approval assignment", - ); - modify_reputation( - &mut self.reputation, - ctx.sender(), - peer_id, - COST_UNEXPECTED_MESSAGE, - ) - .await; - return - } - - // check if our knowledge of the peer already contains this approval - match entry.known_by.entry(peer_id) { - hash_map::Entry::Occupied(mut knowledge) => { - let peer_knowledge = knowledge.get_mut(); - if peer_knowledge.contains(&message_subject, message_kind) { - if !peer_knowledge.received.insert(message_subject.clone(), message_kind) { - gum::debug!( - target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Duplicate approval", - ); - - modify_reputation( - &mut self.reputation, - ctx.sender(), - peer_id, - COST_DUPLICATE_MESSAGE, - ) - .await; - } - return - } - }, - hash_map::Entry::Vacant(_) => { - gum::debug!( - target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Approval from a peer is out of view", - ); - modify_reputation( - &mut self.reputation, - ctx.sender(), - peer_id, - COST_UNEXPECTED_MESSAGE, - ) - .await; - }, - } - - // if the approval is known to be valid, reward the peer - if entry.knowledge.contains(&message_subject, message_kind) { - gum::trace!(target: LOG_TARGET, ?peer_id, ?message_subject, "Known approval"); - modify_reputation( - &mut self.reputation, - ctx.sender(), - peer_id, - BENEFIT_VALID_MESSAGE, - ) - .await; - if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) { - peer_knowledge.received.insert(message_subject.clone(), message_kind); - } + if !Self::check_approval_can_be_processed( + ctx, + &assignments_knowledge_keys, + &approval_knwowledge_key, + entry, + &mut self.reputation, + peer_id, + metrics, + ) + .await + { return } @@ -1495,8 +1650,8 @@ impl State { gum::trace!( target: LOG_TARGET, ?peer_id, - ?message_subject, ?result, + ?vote, "Checked approval", ); match result { @@ -1509,9 +1664,13 @@ impl State { ) .await; - entry.knowledge.insert(message_subject.clone(), message_kind); + entry + .knowledge + .insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1); if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) { - peer_knowledge.received.insert(message_subject.clone(), message_kind); + peer_knowledge + .received + .insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1); } }, ApprovalCheckResult::Bad(error) => { @@ -1528,74 +1687,55 @@ impl State { %error, "Got a bad approval from peer", ); + metrics.on_approval_bad(); return }, } } else { - if !entry.knowledge.insert(message_subject.clone(), message_kind) { - // if we already imported an approval, there is no need to distribute it again + if !entry + .knowledge + .insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1) + { + // if we already imported all approvals, there is no need to distribute it again gum::warn!( target: LOG_TARGET, - ?message_subject, "Importing locally an already known approval", ); return } else { gum::debug!( target: LOG_TARGET, - ?message_subject, "Importing locally a new approval", ); } } - let required_routing = match entry.approval_entry(candidate_index, validator_index) { - Some(approval_entry) => { - // Invariant: to our knowledge, none of the peers except for the `source` know about - // the approval. - metrics.on_approval_imported(); - - if let Err(err) = approval_entry.note_approval(vote.clone()) { - // this would indicate a bug in approval-voting: - // - validator index mismatch - // - candidate index mismatch - // - duplicate approval - gum::warn!( - target: LOG_TARGET, - hash = ?block_hash, - ?candidate_index, - ?validator_index, - ?err, - "Possible bug: Vote import failed", - ); - - return - } - - approval_entry.routing_info().required_routing - }, - None => { - let peer_id = source.peer_id(); - // This indicates a bug in approval-distribution, since we check the knowledge at - // the begining of the function. + let (required_routing, peers_randomly_routed_to) = match entry.note_approval(vote.clone()) { + Ok(required_routing) => required_routing, + Err(err) => { gum::warn!( target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Unknown approval assignment", + hash = ?block_hash, + validator_index = ?vote.validator, + candidate_bitfield = ?vote.candidate_indices, + ?err, + "Possible bug: Vote import failed", ); - // No rep change as this is caused by an issue + metrics.on_approval_bug(); return }, }; + // Invariant: to our knowledge, none of the peers except for the `source` know about the + // approval. + metrics.on_approval_imported(); + // Dispatch a ApprovalDistributionV1Message::Approval(vote) // to all peers required by the topology, with the exception of the source peer. let topology = self.topologies.get_topology(entry.session); let source_peer = source.peer_id(); - let message_subject = &message_subject; - let peer_filter = move |peer, knowledge: &PeerKnowledge| { + let peer_filter = move |peer| { if Some(peer) == source_peer.as_ref() { return false } @@ -1611,13 +1751,13 @@ impl State { // 3. Any randomly selected peers have been sent the assignment already. let in_topology = topology .map_or(false, |t| t.local_grid_neighbors().route_to_peer(required_routing, peer)); - in_topology || knowledge.sent.contains(message_subject, MessageKind::Assignment) + in_topology || peers_randomly_routed_to.contains(peer) }; let peers = entry .known_by .iter() - .filter(|(p, k)| peer_filter(p, k)) + .filter(|(p, _)| peer_filter(p)) .filter_map(|(p, _)| self.peer_views.get(p).map(|entry| (*p, entry.version))) .collect::>(); @@ -1625,7 +1765,7 @@ impl State { for peer in peers.iter() { // we already filtered peers above, so this should always be Some if let Some(entry) = entry.known_by.get_mut(&peer.0) { - entry.sent.insert(message_subject.clone(), message_kind); + entry.sent.insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1); } } @@ -1634,7 +1774,6 @@ impl State { gum::trace!( target: LOG_TARGET, ?block_hash, - ?candidate_index, local = source.peer_id().is_none(), num_peers = peers.len(), "Sending an approval to peers", @@ -1647,7 +1786,7 @@ impl State { fn get_approval_signatures( &mut self, indices: HashSet<(Hash, CandidateIndex)>, - ) -> HashMap { + ) -> HashMap, ValidatorSignature)> { let mut all_sigs = HashMap::new(); for (hash, index) in indices { let _span = self @@ -1670,11 +1809,20 @@ impl State { Some(e) => e, }; - let sigs = block_entry - .approval_entries(index) - .into_iter() - .filter_map(|approval_entry| approval_entry.approval(index)) - .map(|approval| (approval.validator, approval.signature)); + let sigs = block_entry.approval_votes(index).into_iter().map(|approval| { + ( + approval.validator, + ( + hash, + approval + .candidate_indices + .iter_ones() + .map(|val| val as CandidateIndex) + .collect_vec(), + approval.signature, + ), + ) + }); all_sigs.extend(sigs); } all_sigs @@ -1718,23 +1866,31 @@ impl State { let peer_knowledge = entry.known_by.entry(peer_id).or_default(); let topology = topologies.get_topology(entry.session); - // We want to iterate the `approval_entries` of the block entry as these contain all - // assignments that also link all approval votes. + // We want to iterate the `approval_entries` of the block entry as these contain + // all assignments that also link all approval votes. for approval_entry in entry.approval_entries.values_mut() { // Propagate the message to all peers in the required routing set OR // randomly sample peers. { let required_routing = approval_entry.routing_info().required_routing; - let random_routing = &mut approval_entry.routing_info_mut().random_routing; + let routing_info = &mut approval_entry.routing_info_mut(); let rng = &mut *rng; let mut peer_filter = move |peer_id| { let in_topology = topology.as_ref().map_or(false, |t| { t.local_grid_neighbors().route_to_peer(required_routing, peer_id) }); in_topology || { - let route_random = random_routing.sample(total_peers, rng); + if !topology + .map(|topology| topology.is_validator(peer_id)) + .unwrap_or(false) + { + return false + } + + let route_random = + routing_info.random_routing.sample(total_peers, rng); if route_random { - random_routing.inc_sent(); + routing_info.mark_randomly_sent(*peer_id); } route_random @@ -1751,7 +1907,8 @@ impl State { let (assignment_knowledge, message_kind) = approval_entry.create_assignment_knowledge(block); - // Only send stuff a peer doesn't know in the context of a relay chain block. + // Only send stuff a peer doesn't know in the context of a relay chain + // block. if !peer_knowledge.contains(&assignment_knowledge, message_kind) { peer_knowledge.sent.insert(assignment_knowledge, message_kind); assignments_to_send.push(assignment_message); @@ -1759,12 +1916,12 @@ impl State { // Filter approval votes. for approval_message in approval_messages { - let (approval_knowledge, message_kind) = approval_entry - .create_approval_knowledge(block, approval_message.candidate_index); + let approval_knowledge = + PeerKnowledge::generate_approval_key(&approval_message); - if !peer_knowledge.contains(&approval_knowledge, message_kind) { - peer_knowledge.sent.insert(approval_knowledge, message_kind); + if !peer_knowledge.contains(&approval_knowledge.0, approval_knowledge.1) { approvals_to_send.push(approval_message); + peer_knowledge.sent.insert(approval_knowledge.0, approval_knowledge.1); } } } @@ -1937,6 +2094,7 @@ impl State { // Punish the peer for the invalid message. modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD) .await; + gum::error!(target: LOG_TARGET, block_hash = ?cert.block_hash, ?candidate_index, validator_index = ?cert.validator, kind = ?cert.cert.kind, "Bad assignment v1"); } else { sanitized_assignments.push((cert.into(), candidate_index.into())) } @@ -1979,6 +2137,9 @@ impl State { // Punish the peer for the invalid message. modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD) .await; + for candidate_index in candidate_bitfield.iter_ones() { + gum::error!(target: LOG_TARGET, block_hash = ?cert.block_hash, ?candidate_index, validator_index = ?cert.validator, "Bad assignment v2"); + } } else { sanitized_assignments.push((cert, candidate_bitfield)) } @@ -2066,11 +2227,10 @@ async fn adjust_required_routing_and_propagate { gum::debug!( target: LOG_TARGET, - "Distributing our approval vote on candidate (block={}, index={})", + "Distributing our approval vote on candidate (block={}, index={:?})", vote.block_hash, - vote.candidate_index, + vote.candidate_indices, ); state @@ -2296,7 +2456,7 @@ pub const MAX_ASSIGNMENT_BATCH_SIZE: usize = ensure_size_not_zero( /// The maximum amount of approvals per batch is 33% of maximum allowed by protocol. pub const MAX_APPROVAL_BATCH_SIZE: usize = ensure_size_not_zero( - MAX_NOTIFICATION_SIZE as usize / std::mem::size_of::() / 3, + MAX_NOTIFICATION_SIZE as usize / std::mem::size_of::() / 3, ); // Low level helper for sending assignments. @@ -2306,12 +2466,12 @@ async fn send_assignments_batched_inner( peers: Vec, peer_version: ValidationVersion, ) { - if peer_version == ValidationVersion::VStaging { + if peer_version == ValidationVersion::V3 { sender .send_message(NetworkBridgeTxMessage::SendValidationMessage( peers, - Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Assignments( + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments( batch.into_iter().collect(), ), )), @@ -2362,7 +2522,7 @@ pub(crate) async fn send_assignments_batched( ) { let v1_peers = filter_by_peer_version(peers, ValidationVersion::V1.into()); let v2_peers = filter_by_peer_version(peers, ValidationVersion::V2.into()); - let vstaging_peers = filter_by_peer_version(peers, ValidationVersion::VStaging.into()); + let v3_peers = filter_by_peer_version(peers, ValidationVersion::V3.into()); // V1 and V2 validation protocol do not have any changes with regard to // ApprovalDistributionMessage so they can be treated the same. @@ -2400,18 +2560,13 @@ pub(crate) async fn send_assignments_batched( } } - if !vstaging_peers.is_empty() { - let mut vstaging = v2_assignments.into_iter().peekable(); + if !v3_peers.is_empty() { + let mut v3 = v2_assignments.into_iter().peekable(); - while vstaging.peek().is_some() { - let batch = vstaging.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect::>(); - send_assignments_batched_inner( - sender, - batch, - vstaging_peers.clone(), - ValidationVersion::VStaging, - ) - .await; + while v3.peek().is_some() { + let batch = v3.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect::>(); + send_assignments_batched_inner(sender, batch, v3_peers.clone(), ValidationVersion::V3) + .await; } } } @@ -2419,15 +2574,20 @@ pub(crate) async fn send_assignments_batched( /// Send approvals while honoring the `max_notification_size` of the protocol and peer version. pub(crate) async fn send_approvals_batched( sender: &mut impl overseer::ApprovalDistributionSenderTrait, - approvals: impl IntoIterator + Clone, + approvals: impl IntoIterator + Clone, peers: &[(PeerId, ProtocolVersion)], ) { let v1_peers = filter_by_peer_version(peers, ValidationVersion::V1.into()); let v2_peers = filter_by_peer_version(peers, ValidationVersion::V2.into()); - let vstaging_peers = filter_by_peer_version(peers, ValidationVersion::VStaging.into()); + let v3_peers = filter_by_peer_version(peers, ValidationVersion::V3.into()); if !v1_peers.is_empty() || !v2_peers.is_empty() { - let mut batches = approvals.clone().into_iter().peekable(); + let mut batches = approvals + .clone() + .into_iter() + .filter(|approval| approval.candidate_indices.count_ones() == 1) + .filter_map(|val| val.try_into().ok()) + .peekable(); while batches.peek().is_some() { let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect(); @@ -2456,7 +2616,7 @@ pub(crate) async fn send_approvals_batched( } } - if !vstaging_peers.is_empty() { + if !v3_peers.is_empty() { let mut batches = approvals.into_iter().peekable(); while batches.peek().is_some() { @@ -2464,12 +2624,10 @@ pub(crate) async fn send_approvals_batched( sender .send_message(NetworkBridgeTxMessage::SendValidationMessage( - vstaging_peers.clone(), - Versioned::VStaging( - protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Approvals(batch), - ), - ), + v3_peers.clone(), + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(batch), + )), )) .await; } diff --git a/polkadot/node/network/approval-distribution/src/metrics.rs b/polkadot/node/network/approval-distribution/src/metrics.rs index 6864259e6fdb90ce3be3553d101afe1023f1b523..0642b1b2e0cdcea51d4a34263bad87ad6612f035 100644 --- a/polkadot/node/network/approval-distribution/src/metrics.rs +++ b/polkadot/node/network/approval-distribution/src/metrics.rs @@ -31,6 +31,8 @@ struct MetricsInner { time_unify_with_peer: prometheus::Histogram, time_import_pending_now_known: prometheus::Histogram, time_awaiting_approval_voting: prometheus::Histogram, + assignments_received_result: prometheus::CounterVec, + approvals_received_result: prometheus::CounterVec, } trait AsLabel { @@ -78,6 +80,132 @@ impl Metrics { .map(|metrics| metrics.time_import_pending_now_known.start_timer()) } + pub fn on_approval_already_known(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["known"]).inc() + } + } + + pub fn on_approval_entry_not_found(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["noapprovalentry"]).inc() + } + } + + pub fn on_approval_recent_outdated(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["outdated"]).inc() + } + } + + pub fn on_approval_invalid_block(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["invalidblock"]).inc() + } + } + + pub fn on_approval_unknown_assignment(&self) { + if let Some(metrics) = &self.0 { + metrics + .approvals_received_result + .with_label_values(&["unknownassignment"]) + .inc() + } + } + + pub fn on_approval_duplicate(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["duplicate"]).inc() + } + } + + pub fn on_approval_out_of_view(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["outofview"]).inc() + } + } + + pub fn on_approval_good_known(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["goodknown"]).inc() + } + } + + pub fn on_approval_bad(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["bad"]).inc() + } + } + + pub fn on_approval_unexpected(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["unexpected"]).inc() + } + } + + pub fn on_approval_bug(&self) { + if let Some(metrics) = &self.0 { + metrics.approvals_received_result.with_label_values(&["bug"]).inc() + } + } + + pub fn on_assignment_already_known(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["known"]).inc() + } + } + + pub fn on_assignment_recent_outdated(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["outdated"]).inc() + } + } + + pub fn on_assignment_invalid_block(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["invalidblock"]).inc() + } + } + + pub fn on_assignment_duplicate(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["duplicate"]).inc() + } + } + + pub fn on_assignment_out_of_view(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["outofview"]).inc() + } + } + + pub fn on_assignment_good_known(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["goodknown"]).inc() + } + } + + pub fn on_assignment_bad(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["bad"]).inc() + } + } + + pub fn on_assignment_duplicatevoting(&self) { + if let Some(metrics) = &self.0 { + metrics + .assignments_received_result + .with_label_values(&["duplicatevoting"]) + .inc() + } + } + + pub fn on_assignment_far(&self) { + if let Some(metrics) = &self.0 { + metrics.assignments_received_result.with_label_values(&["far"]).inc() + } + } + pub(crate) fn time_awaiting_approval_voting( &self, ) -> Option { @@ -167,6 +295,26 @@ impl MetricsTrait for Metrics { ).buckets(vec![0.0001, 0.0004, 0.0016, 0.0064, 0.0256, 0.1024, 0.4096, 1.6384, 3.2768, 4.9152, 6.5536,]))?, registry, )?, + assignments_received_result: prometheus::register( + prometheus::CounterVec::new( + prometheus::Opts::new( + "polkadot_parachain_assignments_received_result", + "Result of a processed assignement", + ), + &["status"] + )?, + registry, + )?, + approvals_received_result: prometheus::register( + prometheus::CounterVec::new( + prometheus::Opts::new( + "polkadot_parachain_approvals_received_result", + "Result of a processed approval", + ), + &["status"] + )?, + registry, + )?, }; Ok(Metrics(Some(metrics))) } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 33c38c7c5dfa309ae7dc52ec38dd541bba52a3d4..7d933e2047f26033a3c23cbee2bc82595a62f839 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -25,8 +25,8 @@ use polkadot_node_network_protocol::{ }; use polkadot_node_primitives::approval::{ v1::{ - AssignmentCert, AssignmentCertKind, IndirectAssignmentCert, VrfOutput, VrfProof, - VrfSignature, + AssignmentCert, AssignmentCertKind, IndirectAssignmentCert, IndirectSignedApprovalVote, + VrfPreOutput, VrfProof, VrfSignature, }, v2::{ AssignmentCertKindV2, AssignmentCertV2, CoreBitfield, IndirectAssignmentCertV2, @@ -133,14 +133,13 @@ fn make_gossip_topology( all_peers: &[(PeerId, AuthorityDiscoveryId)], neighbors_x: &[usize], neighbors_y: &[usize], + local_index: usize, ) -> network_bridge_event::NewGossipTopology { // This builds a grid topology which is a square matrix. // The local validator occupies the top left-hand corner. // The X peers occupy the same row and the Y peers occupy // the same column. - let local_index = 1; - assert_eq!( neighbors_x.len(), neighbors_y.len(), @@ -277,16 +276,16 @@ async fn send_message_from_peer_v2( .await; } -async fn send_message_from_peer_vstaging( +async fn send_message_from_peer_v3( virtual_overseer: &mut VirtualOverseer, peer_id: &PeerId, - msg: protocol_vstaging::ApprovalDistributionMessage, + msg: protocol_v3::ApprovalDistributionMessage, ) { overseer_send( virtual_overseer, ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage( *peer_id, - Versioned::VStaging(msg), + Versioned::V3(msg), )), ) .await; @@ -298,14 +297,14 @@ fn fake_assignment_cert(block_hash: Hash, validator: ValidatorIndex) -> Indirect let mut prng = rand_core::OsRng; let keypair = schnorrkel::Keypair::generate_with(&mut prng); let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); - let out = inout.to_output(); + let preout = inout.to_preout(); IndirectAssignmentCert { block_hash, validator, cert: AssignmentCert { kind: AssignmentCertKind::RelayVRFModulo { sample: 1 }, - vrf: VrfSignature { output: VrfOutput(out), proof: VrfProof(proof) }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, }, } } @@ -320,14 +319,14 @@ fn fake_assignment_cert_v2( let mut prng = rand_core::OsRng; let keypair = schnorrkel::Keypair::generate_with(&mut prng); let (inout, proof, _) = keypair.vrf_sign(ctx.bytes(msg)); - let out = inout.to_output(); + let preout = inout.to_preout(); IndirectAssignmentCertV2 { block_hash, validator, cert: AssignmentCertV2 { kind: AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield }, - vrf: VrfSignature { output: VrfOutput(out), proof: VrfProof(proof) }, + vrf: VrfSignature { pre_output: VrfPreOutput(preout), proof: VrfProof(proof) }, }, } } @@ -380,10 +379,11 @@ fn state_with_reputation_delay() -> State { /// the new peer sends us the same assignment #[test] fn try_import_the_same_assignment() { - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - let peer_d = PeerId::random(); + let peers = make_peers_and_authority_ids(15); + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; + let peer_d = peers.get(4).unwrap().0; let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); @@ -394,6 +394,10 @@ fn try_import_the_same_assignment() { setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V1).await; + // Set up a gossip topology, where a, b, c and d are topology neighboors to the node under + // testing. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { hash, @@ -446,7 +450,7 @@ fn try_import_the_same_assignment() { ); // setup new peer with V2 - setup_peer_with_view(overseer, &peer_d, view![], ValidationVersion::VStaging).await; + setup_peer_with_view(overseer, &peer_d, view![], ValidationVersion::V3).await; // send the same assignment from peer_d let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments); @@ -464,19 +468,24 @@ fn try_import_the_same_assignment() { /// cores. #[test] fn try_import_the_same_assignment_v2() { - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - let peer_d = PeerId::random(); + let peers = make_peers_and_authority_ids(15); + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; + let peer_d = peers.get(4).unwrap().0; let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; // setup peers - setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::VStaging).await; - setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::VStaging).await; - setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::VStaging).await; + setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V3).await; + + // Set up a gossip topology, where a, b, c and d are topology neighboors to the node under + // testing. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { @@ -503,8 +512,8 @@ fn try_import_the_same_assignment_v2() { let cert = fake_assignment_cert_v2(hash, validator_index, core_bitfield.clone()); let assignments = vec![(cert.clone(), cores.clone().try_into().unwrap())]; - let msg = protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments.clone()); - send_message_from_peer_vstaging(overseer, &peer_a, msg).await; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(assignments.clone()); + send_message_from_peer_v3(overseer, &peer_a, msg).await; expect_reputation_change(overseer, &peer_a, COST_UNEXPECTED_MESSAGE).await; @@ -528,8 +537,8 @@ fn try_import_the_same_assignment_v2() { overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( peers, - Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments) + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) )) )) => { assert_eq!(peers.len(), 2); @@ -538,11 +547,11 @@ fn try_import_the_same_assignment_v2() { ); // setup new peer - setup_peer_with_view(overseer, &peer_d, view![], ValidationVersion::VStaging).await; + setup_peer_with_view(overseer, &peer_d, view![], ValidationVersion::V3).await; // send the same assignment from peer_d - let msg = protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments); - send_message_from_peer_vstaging(overseer, &peer_d, msg).await; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(assignments); + send_message_from_peer_v3(overseer, &peer_d, msg).await; expect_reputation_change(overseer, &peer_d, COST_UNEXPECTED_MESSAGE).await; expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE).await; @@ -705,14 +714,19 @@ fn spam_attack_results_in_negative_reputation_change() { #[test] fn peer_sending_us_the_same_we_just_sent_them_is_ok() { let parent_hash = Hash::repeat_byte(0xFF); - let peer_a = PeerId::random(); let hash = Hash::repeat_byte(0xAA); + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; + let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; let peer = &peer_a; setup_peer_with_view(overseer, peer, view![], ValidationVersion::V1).await; + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0], &[2], 1)).await; + // new block `hash` with 1 candidates let meta = BlockApprovalMeta { hash, @@ -780,10 +794,12 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { } #[test] -fn import_approval_happy_path() { - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); +fn import_approval_happy_path_v1_v2_peers() { + let peers = make_peers_and_authority_ids(15); + + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); @@ -791,7 +807,7 @@ fn import_approval_happy_path() { let overseer = &mut virtual_overseer; // setup peers with V1 and V2 protocol versions setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V1).await; - setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::VStaging).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V3).await; setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V1).await; // new block `hash_a` with 1 candidates @@ -806,6 +822,9 @@ fn import_approval_happy_path() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + // Set up a gossip topology, where a, b, and c are topology neighboors to the node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + // import an assignment related to `hash` locally let validator_index = ValidatorIndex(0); let candidate_index = 0u32; @@ -838,8 +857,8 @@ fn import_approval_happy_path() { overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( peers, - Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Assignments(assignments) + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) )) )) => { assert_eq!(peers.len(), 1); @@ -848,14 +867,15 @@ fn import_approval_happy_path() { ); // send the an approval from peer_b - let approval = IndirectSignedApprovalVote { + let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, - candidate_index, + candidate_indices: candidate_index.into(), validator: validator_index, signature: dummy_signature(), }; - let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, &peer_b, msg).await; + let msg: protocol_v3::ApprovalDistributionMessage = + protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_b, msg).await; assert_matches!( overseer_recv(overseer).await, @@ -886,6 +906,474 @@ fn import_approval_happy_path() { }); } +// Test a v2 approval that signs multiple candidate is correctly processed. +#[test] +fn import_approval_happy_path_v2() { + let peers = make_peers_and_authority_ids(15); + + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; + let parent_hash = Hash::repeat_byte(0xFF); + let hash = Hash::repeat_byte(0xAA); + + let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // setup peers with V2 protocol versions + setup_peer_with_view(overseer, &peer_a, view![], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V3).await; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 2], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // Set up a gossip topology, where a, b, and c are topology neighboors to the node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + + // import an assignment related to `hash` locally + let validator_index = ValidatorIndex(0); + let candidate_indices: CandidateBitfield = + vec![0 as CandidateIndex, 1 as CandidateIndex].try_into().unwrap(); + let candidate_bitfields = vec![CoreIndex(0), CoreIndex(1)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, candidate_bitfields); + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_indices.clone(), + ), + ) + .await; + + // 1 peer is v2 + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 2); + assert_eq!(assignments.len(), 1); + } + ); + + // send the an approval from peer_b + let approval = IndirectSignedApprovalVoteV2 { + block_hash: hash, + candidate_indices, + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_b, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval( + vote, + tx, + )) => { + assert_eq!(vote, approval); + tx.send(ApprovalCheckResult::Accepted).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_b, BENEFIT_VALID_MESSAGE_FIRST).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(approvals.len(), 1); + } + ); + virtual_overseer + }); +} + +// Tests that votes that cover multiple assignments candidates are correctly processed on importing +#[test] +fn multiple_assignments_covered_with_one_approval_vote() { + let peers = make_peers_and_authority_ids(15); + + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; + let peer_d = peers.get(4).unwrap().0; + let parent_hash = Hash::repeat_byte(0xFF); + let hash = Hash::repeat_byte(0xAA); + + let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // setup peers with V2 protocol versions + setup_peer_with_view(overseer, &peer_a, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_d, view![hash], ValidationVersion::V3).await; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 2], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // Set up a gossip topology, where a, b, and c, d are topology neighboors to the node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + + // import an assignment related to `hash` locally + let validator_index = ValidatorIndex(2); // peer_c is the originator + let candidate_indices: CandidateBitfield = + vec![0 as CandidateIndex, 1 as CandidateIndex].try_into().unwrap(); + + let core_bitfields = vec![CoreIndex(0)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, core_bitfields); + + // send the candidate 0 assignment from peer_b + let assignment = IndirectAssignmentCertV2 { + block_hash: hash, + validator: validator_index, + cert: cert.cert, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment, + (0 as CandidateIndex).into(), + )]); + send_message_from_peer_v3(overseer, &peer_d, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + _, _, + tx, + )) => { + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE_FIRST).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert!(peers.len() >= 2); + assert!(peers.contains(&peer_a)); + assert!(peers.contains(&peer_b)); + assert_eq!(assignments.len(), 1); + } + ); + + let candidate_bitfields = vec![CoreIndex(1)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, candidate_bitfields); + + // send the candidate 1 assignment from peer_c + let assignment = IndirectAssignmentCertV2 { + block_hash: hash, + validator: validator_index, + cert: cert.cert, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment, + (1 as CandidateIndex).into(), + )]); + + send_message_from_peer_v3(overseer, &peer_c, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + _, _, + tx, + )) => { + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + expect_reputation_change(overseer, &peer_c, BENEFIT_VALID_MESSAGE_FIRST).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert!(peers.len() >= 2); + assert!(peers.contains(&peer_b)); + assert!(peers.contains(&peer_a)); + assert_eq!(assignments.len(), 1); + } + ); + + // send an approval from peer_b + let approval = IndirectSignedApprovalVoteV2 { + block_hash: hash, + candidate_indices, + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_d, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval( + vote, + tx, + )) => { + assert_eq!(vote, approval); + tx.send(ApprovalCheckResult::Accepted).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE_FIRST).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) + )) + )) => { + assert!(peers.len() >= 2); + assert!(peers.contains(&peer_b)); + assert!(peers.contains(&peer_a)); + assert_eq!(approvals.len(), 1); + } + ); + for candidate_index in 0..1 { + let (tx_distribution, rx_distribution) = oneshot::channel(); + let mut candidates_requesting_signatures = HashSet::new(); + candidates_requesting_signatures.insert((hash, candidate_index)); + overseer_send( + overseer, + ApprovalDistributionMessage::GetApprovalSignatures( + candidates_requesting_signatures, + tx_distribution, + ), + ) + .await; + let signatures = rx_distribution.await.unwrap(); + + assert_eq!(signatures.len(), 1); + for (signing_validator, signature) in signatures { + assert_eq!(validator_index, signing_validator); + assert_eq!(signature.0, hash); + assert_eq!(signature.2, approval.signature); + assert_eq!(signature.1, vec![0, 1]); + } + } + virtual_overseer + }); +} + +// Tests that votes that cover multiple assignments candidates are correctly processed when unify +// with peer view +#[test] +fn unify_with_peer_multiple_assignments_covered_with_one_approval_vote() { + let peers = make_peers_and_authority_ids(15); + + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_d = peers.get(4).unwrap().0; + let parent_hash = Hash::repeat_byte(0xFF); + let hash = Hash::repeat_byte(0xAA); + + let _ = test_harness(state_without_reputation_delay(), |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + setup_peer_with_view(overseer, &peer_d, view![hash], ValidationVersion::V3).await; + + // new block `hash_a` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 2], + slot: 1.into(), + session: 1, + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // Set up a gossip topology, where a, b, and c, d are topology neighboors to the node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + + // import an assignment related to `hash` locally + let validator_index = ValidatorIndex(2); // peer_c is the originator + let candidate_indices: CandidateBitfield = + vec![0 as CandidateIndex, 1 as CandidateIndex].try_into().unwrap(); + + let core_bitfields = vec![CoreIndex(0)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, core_bitfields); + + // send the candidate 0 assignment from peer_b + let assignment = IndirectAssignmentCertV2 { + block_hash: hash, + validator: validator_index, + cert: cert.cert, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment, + (0 as CandidateIndex).into(), + )]); + send_message_from_peer_v3(overseer, &peer_d, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + _, _, + tx, + )) => { + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE_FIRST).await; + + let candidate_bitfields = vec![CoreIndex(1)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, candidate_bitfields); + + // send the candidate 1 assignment from peer_c + let assignment = IndirectAssignmentCertV2 { + block_hash: hash, + validator: validator_index, + cert: cert.cert, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment, + (1 as CandidateIndex).into(), + )]); + + send_message_from_peer_v3(overseer, &peer_d, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportAssignment( + _, _, + tx, + )) => { + tx.send(AssignmentCheckResult::Accepted).unwrap(); + } + ); + expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE_FIRST).await; + + // send an approval from peer_b + let approval = IndirectSignedApprovalVoteV2 { + block_hash: hash, + candidate_indices, + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_d, msg).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::ApprovalVoting(ApprovalVotingMessage::CheckAndImportApproval( + vote, + tx, + )) => { + assert_eq!(vote, approval); + tx.send(ApprovalCheckResult::Accepted).unwrap(); + } + ); + + expect_reputation_change(overseer, &peer_d, BENEFIT_VALID_MESSAGE_FIRST).await; + + // setup peers with V2 protocol versions + setup_peer_with_view(overseer, &peer_a, view![hash], ValidationVersion::V3).await; + setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V3).await; + let mut expected_peers_assignments = vec![peer_a, peer_b]; + let mut expected_peers_approvals = vec![peer_a, peer_b]; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert!(peers.len() == 1); + assert!(expected_peers_assignments.contains(peers.first().unwrap())); + expected_peers_assignments.retain(|peer| peer != peers.first().unwrap()); + assert_eq!(assignments.len(), 2); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) + )) + )) => { + assert!(peers.len() == 1); + assert!(expected_peers_approvals.contains(peers.first().unwrap())); + expected_peers_approvals.retain(|peer| peer != peers.first().unwrap()); + assert_eq!(approvals.len(), 1); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert!(peers.len() == 1); + assert!(expected_peers_assignments.contains(peers.first().unwrap())); + expected_peers_assignments.retain(|peer| peer != peers.first().unwrap()); + assert_eq!(assignments.len(), 2); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) + )) + )) => { + assert!(peers.len() == 1); + assert!(expected_peers_approvals.contains(peers.first().unwrap())); + expected_peers_approvals.retain(|peer| peer != peers.first().unwrap()); + assert_eq!(approvals.len(), 1); + } + ); + + virtual_overseer + }); +} + #[test] fn import_approval_bad() { let peer_a = PeerId::random(); @@ -916,14 +1404,14 @@ fn import_approval_bad() { let cert = fake_assignment_cert(hash, validator_index); // send the an approval from peer_b, we don't have an assignment yet - let approval = IndirectSignedApprovalVote { + let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, - candidate_index, + candidate_indices: candidate_index.into(), validator: validator_index, signature: dummy_signature(), }; - let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, &peer_b, msg).await; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_b, msg).await; expect_reputation_change(overseer, &peer_b, COST_UNEXPECTED_MESSAGE).await; @@ -948,8 +1436,8 @@ fn import_approval_bad() { expect_reputation_change(overseer, &peer_b, BENEFIT_VALID_MESSAGE_FIRST).await; // and try again - let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, &peer_b, msg).await; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, &peer_b, msg).await; assert_matches!( overseer_recv(overseer).await, @@ -1048,7 +1536,8 @@ fn update_peer_view() { let hash_b = Hash::repeat_byte(0xBB); let hash_c = Hash::repeat_byte(0xCC); let hash_d = Hash::repeat_byte(0xDD); - let peer_a = PeerId::random(); + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; let peer = &peer_a; let state = test_harness(State::default(), |mut virtual_overseer| async move { @@ -1082,6 +1571,9 @@ fn update_peer_view() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta_a, meta_b, meta_c]); overseer_send(overseer, msg).await; + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0], &[2], 1)).await; + let cert_a = fake_assignment_cert(hash_a, ValidatorIndex(0)); let cert_b = fake_assignment_cert(hash_b, ValidatorIndex(0)); @@ -1264,14 +1756,14 @@ fn import_remotely_then_locally() { assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent"); // send the approval remotely - let approval = IndirectSignedApprovalVote { + let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, - candidate_index, + candidate_indices: candidate_index.into(), validator: validator_index, signature: dummy_signature(), }; - let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); - send_message_from_peer(overseer, peer, msg).await; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + send_message_from_peer_v3(overseer, peer, msg).await; assert_matches!( overseer_recv(overseer).await, @@ -1295,7 +1787,8 @@ fn import_remotely_then_locally() { #[test] fn sends_assignments_even_when_state_is_approved() { - let peer_a = PeerId::random(); + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); let peer = &peer_a; @@ -1315,6 +1808,9 @@ fn sends_assignments_even_when_state_is_approved() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0], &[2], 1)).await; + let validator_index = ValidatorIndex(0); let candidate_index = 0u32; @@ -1336,8 +1832,11 @@ fn sends_assignments_even_when_state_is_approved() { ) .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone())) - .await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone().into()), + ) + .await; // connect the peer. setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V1).await; @@ -1380,7 +1879,8 @@ fn sends_assignments_even_when_state_is_approved() { /// assignemnts. #[test] fn sends_assignments_even_when_state_is_approved_v2() { - let peer_a = PeerId::random(); + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); let peer = &peer_a; @@ -1400,6 +1900,9 @@ fn sends_assignments_even_when_state_is_approved_v2() { let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); overseer_send(overseer, msg).await; + // Setup a topology where peer_a is neigboor to current node. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0], &[2], 1)).await; + let validator_index = ValidatorIndex(0); let cores = vec![0, 1, 2, 3]; let candidate_bitfield: CandidateBitfield = cores.clone().try_into().unwrap(); @@ -1416,9 +1919,9 @@ fn sends_assignments_even_when_state_is_approved_v2() { // Assumes candidate index == core index. let approvals = cores .iter() - .map(|core| IndirectSignedApprovalVote { + .map(|core| IndirectSignedApprovalVoteV2 { block_hash: hash, - candidate_index: *core, + candidate_indices: (*core).into(), validator: validator_index, signature: dummy_signature(), }) @@ -1442,7 +1945,7 @@ fn sends_assignments_even_when_state_is_approved_v2() { } // connect the peer. - setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::VStaging).await; + setup_peer_with_view(overseer, peer, view![hash], ValidationVersion::V3).await; let assignments = vec![(cert.clone(), candidate_bitfield.clone())]; @@ -1450,8 +1953,8 @@ fn sends_assignments_even_when_state_is_approved_v2() { overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( peers, - Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Assignments(sent_assignments) + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(sent_assignments) )) )) => { assert_eq!(peers, vec![*peer]); @@ -1463,14 +1966,14 @@ fn sends_assignments_even_when_state_is_approved_v2() { overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( peers, - Versioned::VStaging(protocol_vstaging::ValidationProtocol::ApprovalDistribution( - protocol_vstaging::ApprovalDistributionMessage::Approvals(sent_approvals) + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Approvals(sent_approvals) )) )) => { // Construct a hashmaps of approvals for comparison. Approval distribution reorders messages because they are kept in a // hashmap as well. - let sent_approvals = sent_approvals.into_iter().map(|approval| (approval.candidate_index, approval)).collect::>(); - let approvals = approvals.into_iter().map(|approval| (approval.candidate_index, approval)).collect::>(); + let sent_approvals = sent_approvals.into_iter().map(|approval| (approval.candidate_indices.clone(), approval)).collect::>(); + let approvals = approvals.into_iter().map(|approval| (approval.candidate_indices.clone(), approval)).collect::>(); assert_eq!(peers, vec![*peer]); assert_eq!(sent_approvals, approvals); @@ -1580,13 +2083,19 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology( + 1, + &peers, + &[0, 10, 20, 30, 40, 60, 70, 80], + &[50, 51, 52, 53, 54, 55, 56, 57], + 1, + ), ) .await; let expected_indices = [ // Both dimensions in the gossip topology - 0, 10, 20, 30, 50, 51, 52, 53, + 0, 10, 20, 30, 40, 60, 70, 80, 50, 51, 52, 53, 54, 55, 56, 57, ]; // new block `hash_a` with 1 candidates @@ -1623,8 +2132,11 @@ fn propagates_locally_generated_assignment_to_both_dimensions() { ) .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone())) - .await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone().into()), + ) + .await; let assignments = vec![(cert.clone(), candidate_index)]; let approvals = vec![approval.clone()]; @@ -1688,7 +2200,7 @@ fn propagates_assignments_along_unshared_dimension() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -1831,13 +2343,19 @@ fn propagates_to_required_after_connect() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology( + 1, + &peers, + &[0, 10, 20, 30, 40, 60, 70, 80], + &[50, 51, 52, 53, 54, 55, 56, 57], + 1, + ), ) .await; let expected_indices = [ // Both dimensions in the gossip topology, minus omitted. - 20, 30, 52, 53, + 20, 30, 40, 60, 70, 80, 52, 53, 54, 55, 56, 57, ]; // new block `hash_a` with 1 candidates @@ -1874,8 +2392,11 @@ fn propagates_to_required_after_connect() { ) .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone())) - .await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone().into()), + ) + .await; let assignments = vec![(cert.clone(), candidate_index)]; let approvals = vec![approval.clone()]; @@ -2002,53 +2523,21 @@ fn sends_to_more_peers_after_getting_topology() { ) .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone())) - .await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone().into()), + ) + .await; let assignments = vec![(cert.clone(), candidate_index)]; let approvals = vec![approval.clone()]; - let mut expected_indices = vec![0, 10, 20, 30, 50, 51, 52, 53]; - let assignment_sent_peers = assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( - sent_peers, - Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( - protocol_v1::ApprovalDistributionMessage::Assignments(sent_assignments) - )) - )) => { - // Only sends to random peers. - assert_eq!(sent_peers.len(), 4); - for peer in &sent_peers { - let i = peers.iter().position(|p| peer == &p.0).unwrap(); - // Random gossip before topology can send to topology-targeted peers. - // Remove them from the expected indices so we don't expect - // them to get the messages again after the assignment. - expected_indices.retain(|&i2| i2 != i); - } - assert_eq!(sent_assignments, assignments); - sent_peers - } - ); - - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( - sent_peers, - Versioned::V1(protocol_v1::ValidationProtocol::ApprovalDistribution( - protocol_v1::ApprovalDistributionMessage::Approvals(sent_approvals) - )) - )) => { - // Random sampling is reused from the assignment. - assert_eq!(sent_peers, assignment_sent_peers); - assert_eq!(sent_approvals, approvals); - } - ); + let expected_indices = vec![0, 10, 20, 30, 50, 51, 52, 53]; // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -2151,7 +2640,7 @@ fn originator_aggression_l1() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -2164,8 +2653,11 @@ fn originator_aggression_l1() { ) .await; - overseer_send(overseer, ApprovalDistributionMessage::DistributeApproval(approval.clone())) - .await; + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeApproval(approval.clone().into()), + ) + .await; let assignments = vec![(cert.clone(), candidate_index)]; let approvals = vec![approval.clone()]; @@ -2307,7 +2799,7 @@ fn non_originator_aggression_l1() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -2412,7 +2904,7 @@ fn non_originator_aggression_l2() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -2558,7 +3050,7 @@ fn resends_messages_periodically() { // Set up a gossip topology. setup_gossip_topology( overseer, - make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53]), + make_gossip_topology(1, &peers, &[0, 10, 20, 30], &[50, 51, 52, 53], 1), ) .await; @@ -2681,12 +3173,13 @@ fn resends_messages_periodically() { /// Tests that peers correctly receive versioned messages. #[test] fn import_versioned_approval() { - let peer_a = PeerId::random(); - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); + let peers = make_peers_and_authority_ids(15); + let peer_a = peers.get(0).unwrap().0; + let peer_b = peers.get(1).unwrap().0; + let peer_c = peers.get(2).unwrap().0; + let parent_hash = Hash::repeat_byte(0xFF); let hash = Hash::repeat_byte(0xAA); - let state = state_without_reputation_delay(); let _ = test_harness(state, |mut virtual_overseer| async move { let overseer = &mut virtual_overseer; @@ -2695,6 +3188,10 @@ fn import_versioned_approval() { setup_peer_with_view(overseer, &peer_b, view![hash], ValidationVersion::V1).await; setup_peer_with_view(overseer, &peer_c, view![hash], ValidationVersion::V2).await; + // Set up a gossip topology, where a, b, c and d are topology neighboors to the node under + // testing. + setup_gossip_topology(overseer, make_gossip_topology(1, &peers, &[0, 1], &[2, 4], 3)).await; + // new block `hash_a` with 1 candidates let meta = BlockApprovalMeta { hash, @@ -2762,7 +3259,7 @@ fn import_versioned_approval() { vote, tx, )) => { - assert_eq!(vote, approval); + assert_eq!(vote, approval.into()); tx.send(ApprovalCheckResult::Accepted).unwrap(); } ); @@ -2782,6 +3279,7 @@ fn import_versioned_approval() { assert_eq!(approvals.len(), 1); } ); + assert_matches!( overseer_recv(overseer).await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( @@ -2821,9 +3319,9 @@ fn batch_test_round(message_count: usize) { .collect(); let approvals: Vec<_> = validators - .map(|index| IndirectSignedApprovalVote { + .map(|index| IndirectSignedApprovalVoteV2 { block_hash: Hash::zero(), - candidate_index: 0, + candidate_indices: 0u32.into(), validator: ValidatorIndex(index as u32), signature: dummy_signature(), }) @@ -2890,7 +3388,7 @@ fn batch_test_round(message_count: usize) { assert_eq!(peers.len(), 1); for (message_index, approval) in sent_approvals.iter().enumerate() { - assert_eq!(approval, &approvals[approval_index + message_index]); + assert_eq!(approval, &approvals[approval_index + message_index].clone().try_into().unwrap()); } } ); diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index 91ed1026e41e8e2abed16489edbe1cd15258adce..0d52c013a33c3f22e6a8e82b75ec9e6331f9e28d 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } diff --git a/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs b/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs index 12a97a1fb5a18330be6d94c43ef6a59b14772b40..6f9ef9f6a9f83ee9b6d1e50f5d1c61145b6ce0a1 100644 --- a/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs +++ b/polkadot/node/network/availability-distribution/src/pov_requester/mod.rs @@ -146,7 +146,9 @@ mod tests { AllMessages, AvailabilityDistributionMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_node_subsystem_test_helpers as test_helpers; - use polkadot_primitives::{CandidateHash, ExecutorParams, Hash, ValidatorIndex}; + use polkadot_primitives::{ + vstaging::NodeFeatures, CandidateHash, ExecutorParams, Hash, ValidatorIndex, + }; use test_helpers::mock::make_ferdie_keystore; use super::*; @@ -214,6 +216,12 @@ mod tests { )) => { tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::NodeFeatures(_, si_tx), + )) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + }, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendRequests( mut reqs, _, diff --git a/polkadot/node/network/availability-distribution/src/requester/tests.rs b/polkadot/node/network/availability-distribution/src/requester/tests.rs index c4252b4e439e8a58a5ed54e2039999fb71dfde60..2f5d900b037e322e92f0e203fbb8f2a1fcf43492 100644 --- a/polkadot/node/network/availability-distribution/src/requester/tests.rs +++ b/polkadot/node/network/availability-distribution/src/requester/tests.rs @@ -25,8 +25,8 @@ use polkadot_node_primitives::{BlockData, ErasureChunk, PoV}; use polkadot_node_subsystem_test_helpers::mock::new_leaf; use polkadot_node_subsystem_util::runtime::RuntimeInfo; use polkadot_primitives::{ - BlockNumber, CoreState, ExecutorParams, GroupIndex, Hash, Id as ParaId, ScheduledCore, - SessionIndex, SessionInfo, + vstaging::NodeFeatures, BlockNumber, CoreState, ExecutorParams, GroupIndex, Hash, Id as ParaId, + ScheduledCore, SessionIndex, SessionInfo, }; use sp_core::traits::SpawnNamed; @@ -125,6 +125,10 @@ fn spawn_virtual_overseer( tx.send(Ok(Some(ExecutorParams::default()))) .expect("Receiver should be alive."); }, + RuntimeApiRequest::NodeFeatures(_, tx) => { + tx.send(Ok(NodeFeatures::EMPTY)) + .expect("Receiver should be alive."); + }, RuntimeApiRequest::AvailabilityCores(tx) => { let para_id = ParaId::from(1_u32); let maybe_block_position = diff --git a/polkadot/node/network/availability-distribution/src/tests/state.rs b/polkadot/node/network/availability-distribution/src/tests/state.rs index 101d917c0db5bbd572b86647a03c4b7b2bedf969..e95c1c3a27c2fb01e2a90b075caf6dd67f531e6d 100644 --- a/polkadot/node/network/availability-distribution/src/tests/state.rs +++ b/polkadot/node/network/availability-distribution/src/tests/state.rs @@ -46,8 +46,8 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ - CandidateHash, CoreState, ExecutorParams, GroupIndex, Hash, Id as ParaId, ScheduledCore, - SessionInfo, ValidatorIndex, + vstaging::NodeFeatures, CandidateHash, CoreState, ExecutorParams, GroupIndex, Hash, + Id as ParaId, ScheduledCore, SessionInfo, ValidatorIndex, }; use test_helpers::mock::{make_ferdie_keystore, new_leaf}; @@ -264,6 +264,9 @@ impl TestState { tx.send(Ok(Some(ExecutorParams::default()))) .expect("Receiver should be alive."); }, + RuntimeApiRequest::NodeFeatures(_, si_tx) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).expect("Receiver should be alive."); + }, RuntimeApiRequest::AvailabilityCores(tx) => { gum::trace!(target: LOG_TARGET, cores= ?self.cores[&hash], hash = ?hash, "Sending out cores for hash"); tx.send(Ok(self.cores[&hash].clone())) diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 6048e6323cb45e4ef82256a94414e04bfda3c263..ec1cf475302b353a85021d637ff319dba61bb733 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -6,13 +6,17 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" +tokio = "1.24.2" schnellru = "0.2.1" rand = "0.8.5" fatality = "0.0.6" thiserror = "1.0.48" -async-trait = "0.1.73" +async-trait = "0.1.74" gum = { package = "tracing-gum", path = "../../gum" } polkadot-erasure-coding = { path = "../../../erasure-coding" } @@ -37,3 +41,6 @@ sc-network = { path = "../../../../substrate/client/network" } polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } + +[features] +subsystem-benchmarks = [] diff --git a/polkadot/node/network/availability-recovery/src/lib.rs b/polkadot/node/network/availability-recovery/src/lib.rs index 4a658449f09c3ac195cc35846493f44fb353e533..fb8064878f4f6c02236afbede9f8e91b8f2dd594 100644 --- a/polkadot/node/network/availability-recovery/src/lib.rs +++ b/polkadot/node/network/availability-recovery/src/lib.rs @@ -65,7 +65,7 @@ mod error; mod futures_undead; mod metrics; mod task; -use metrics::Metrics; +pub use metrics::Metrics; #[cfg(test)] mod tests; @@ -603,7 +603,8 @@ impl AvailabilityRecoverySubsystem { } } - async fn run(self, mut ctx: Context) -> SubsystemResult<()> { + /// Starts the inner subsystem loop. + pub async fn run(self, mut ctx: Context) -> SubsystemResult<()> { let mut state = State::default(); let Self { mut req_receiver, @@ -681,6 +682,7 @@ impl AvailabilityRecoverySubsystem { &mut state, signal, ).await? { + gum::debug!(target: LOG_TARGET, "subsystem concluded"); return Ok(()); } FromOrchestra::Communication { msg } => { @@ -845,12 +847,17 @@ async fn erasure_task_thread( let _ = sender.send(maybe_data); }, None => { - gum::debug!( + gum::trace!( target: LOG_TARGET, "Erasure task channel closed. Node shutting down ?", ); break }, } + + // In benchmarks this is a very hot loop not yielding at all. + // To update CPU metrics for the task we need to yield. + #[cfg(feature = "subsystem-benchmarks")] + tokio::task::yield_now().await; } } diff --git a/polkadot/node/network/availability-recovery/src/metrics.rs b/polkadot/node/network/availability-recovery/src/metrics.rs index aa7216739507668c758f3c335e00da2772311789..d82a8f9ae5faf662e05c7a8dcaf731e17756a636 100644 --- a/polkadot/node/network/availability-recovery/src/metrics.rs +++ b/polkadot/node/network/availability-recovery/src/metrics.rs @@ -29,7 +29,10 @@ struct MetricsInner { /// /// Gets incremented on each sent chunk requests. chunk_requests_issued: Counter, - + /// Total number of bytes recovered + /// + /// Gets incremented on each succesful recovery + recovered_bytes_total: Counter, /// A counter for finished chunk requests. /// /// Split by result: @@ -133,9 +136,10 @@ impl Metrics { } /// A full recovery succeeded. - pub fn on_recovery_succeeded(&self) { + pub fn on_recovery_succeeded(&self, bytes: usize) { if let Some(metrics) = &self.0 { - metrics.full_recoveries_finished.with_label_values(&["success"]).inc() + metrics.full_recoveries_finished.with_label_values(&["success"]).inc(); + metrics.recovered_bytes_total.inc_by(bytes as u64) } } @@ -171,6 +175,13 @@ impl metrics::Metrics for Metrics { )?, registry, )?, + recovered_bytes_total: prometheus::register( + Counter::new( + "polkadot_parachain_availability_recovery_bytes_total", + "Total number of bytes recovered", + )?, + registry, + )?, chunk_requests_finished: prometheus::register( CounterVec::new( Opts::new( diff --git a/polkadot/node/network/availability-recovery/src/task.rs b/polkadot/node/network/availability-recovery/src/task.rs index f705d5c0e4cfb15eae8fd873312fc1946d55a216..c300c221da5c6da8f40e8a6db3dede59ba207a58 100644 --- a/polkadot/node/network/availability-recovery/src/task.rs +++ b/polkadot/node/network/availability-recovery/src/task.rs @@ -23,6 +23,7 @@ use crate::{ PostRecoveryCheck, LOG_TARGET, }; use futures::{channel::oneshot, SinkExt}; +use parity_scale_codec::Encode; #[cfg(not(test))] use polkadot_node_network_protocol::request_response::CHUNK_REQUEST_TIMEOUT; use polkadot_node_network_protocol::request_response::{ @@ -432,7 +433,7 @@ where return Err(err) }, Ok(data) => { - self.params.metrics.on_recovery_succeeded(); + self.params.metrics.on_recovery_succeeded(data.encoded_size()); return Ok(data) }, } diff --git a/polkadot/node/network/availability-recovery/src/tests.rs b/polkadot/node/network/availability-recovery/src/tests.rs index 63ccf0e94f91ebad2a8cba3158d6db24159683c0..1cb52757bac92da93bebfcc72e682a9e2b2027df 100644 --- a/polkadot/node/network/availability-recovery/src/tests.rs +++ b/polkadot/node/network/availability-recovery/src/tests.rs @@ -24,12 +24,12 @@ use parity_scale_codec::Encode; use polkadot_node_network_protocol::request_response::{ self as req_res, IncomingRequest, Recipient, ReqProtocolNames, Requests, }; +use polkadot_node_subsystem_test_helpers::derive_erasure_chunks_with_proofs_and_root; use super::*; use sc_network::{config::RequestResponseConfig, IfDisconnected, OutboundFailure, RequestFailure}; -use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks}; use polkadot_node_primitives::{BlockData, PoV, Proof}; use polkadot_node_subsystem::messages::{ AllMessages, NetworkBridgeTxMessage, RuntimeApiMessage, RuntimeApiRequest, @@ -456,33 +456,6 @@ fn validator_authority_id(val_ids: &[Sr25519Keyring]) -> Vec), -) -> (Vec, Hash) { - let mut chunks: Vec> = obtain_chunks(n_validators, available_data).unwrap(); - - for (i, chunk) in chunks.iter_mut().enumerate() { - alter_chunk(i, chunk) - } - - // create proofs for each erasure chunk - let branches = branches(chunks.as_ref()); - - let root = branches.root(); - let erasure_chunks = branches - .enumerate() - .map(|(index, (proof, chunk))| ErasureChunk { - chunk: chunk.to_vec(), - index: ValidatorIndex(index as _), - proof: Proof::try_from(proof).unwrap(), - }) - .collect::>(); - - (erasure_chunks, root) -} - impl Default for TestState { fn default() -> Self { let validators = vec![ diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 0e61e9cf6209a4e99d2e51d05a0315b4128aff25..5c5bd875a96f82e1aee3d08ad5df73d87913c650 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] always-assert = "0.1" futures = "0.3.21" diff --git a/polkadot/node/network/bitfield-distribution/src/lib.rs b/polkadot/node/network/bitfield-distribution/src/lib.rs index 9cc79aee8490705893a5065cb1fbde6124e4f61c..76baf499cad7a63263a7bbd42968e44328c29387 100644 --- a/polkadot/node/network/bitfield-distribution/src/lib.rs +++ b/polkadot/node/network/bitfield-distribution/src/lib.rs @@ -32,7 +32,7 @@ use polkadot_node_network_protocol::{ GridNeighbors, RandomRouting, RequiredRouting, SessionBoundGridTopologyStorage, }, peer_set::{ProtocolVersion, ValidationVersion}, - v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, OurView, PeerId, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, OurView, PeerId, UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_subsystem::{ @@ -102,8 +102,8 @@ impl BitfieldGossipMessage { self.relay_parent, self.signed_availability.into(), )), - Some(ValidationVersion::VStaging) => - Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield( + Some(ValidationVersion::V3) => + Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield( self.relay_parent, self.signed_availability.into(), )), @@ -503,8 +503,8 @@ async fn relay_message( let v2_interested_peers = filter_by_peer_version(&interested_peers, ValidationVersion::V2.into()); - let vstaging_interested_peers = - filter_by_peer_version(&interested_peers, ValidationVersion::VStaging.into()); + let v3_interested_peers = + filter_by_peer_version(&interested_peers, ValidationVersion::V3.into()); if !v1_interested_peers.is_empty() { ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( @@ -522,10 +522,10 @@ async fn relay_message( .await } - if !vstaging_interested_peers.is_empty() { + if !v3_interested_peers.is_empty() { ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - vstaging_interested_peers, - message.into_validation_protocol(ValidationVersion::VStaging.into()), + v3_interested_peers, + message.into_validation_protocol(ValidationVersion::V3.into()), )) .await } @@ -551,7 +551,7 @@ async fn process_incoming_peer_message( relay_parent, bitfield, )) | - Versioned::VStaging(protocol_vstaging::BitfieldDistributionMessage::Bitfield( + Versioned::V3(protocol_v3::BitfieldDistributionMessage::Bitfield( relay_parent, bitfield, )) => (relay_parent, bitfield), diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index 6ae765c252f2d53b80da52c7509e725b2a9d19a8..a2a4735d7a19f6726c0b5795a593412a043b4849 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -6,9 +6,12 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] always-assert = "0.1" -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } polkadot-primitives = { path = "../../../primitives" } diff --git a/polkadot/node/network/bridge/src/network.rs b/polkadot/node/network/bridge/src/network.rs index a9339a5c443c118d77874a9f898a2be3667485e7..2fcf5cec489da07e034baa90f145d63f1c3af310 100644 --- a/polkadot/node/network/bridge/src/network.rs +++ b/polkadot/node/network/bridge/src/network.rs @@ -33,7 +33,7 @@ use sc_network::{ use polkadot_node_network_protocol::{ peer_set::{CollationVersion, PeerSet, ProtocolVersion, ValidationVersion}, request_response::{OutgoingRequest, Recipient, ReqProtocolNames, Requests}, - v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, PeerId, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, PeerId, }; use polkadot_primitives::{AuthorityDiscoveryId, Block, Hash}; @@ -62,20 +62,20 @@ pub(crate) fn send_validation_message_v1( ); } -// Helper function to send a validation vstaging message to a list of peers. +// Helper function to send a validation v3 message to a list of peers. // Messages are always sent via the main protocol, even legacy protocol messages. -pub(crate) fn send_validation_message_vstaging( +pub(crate) fn send_validation_message_v3( peers: Vec, - message: WireMessage, + message: WireMessage, metrics: &Metrics, notification_sinks: &Arc>>>, ) { - gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation vstaging message to peers",); + gum::trace!(target: LOG_TARGET, ?peers, ?message, "Sending validation v3 message to peers",); send_message( peers, PeerSet::Validation, - ValidationVersion::VStaging.into(), + ValidationVersion::V3.into(), message, metrics, notification_sinks, diff --git a/polkadot/node/network/bridge/src/rx/mod.rs b/polkadot/node/network/bridge/src/rx/mod.rs index 40cd167a968ba9c2fdecf28e763de5504b289415..11ac73259e3a178f418f4a6aa316e337acb31322 100644 --- a/polkadot/node/network/bridge/src/rx/mod.rs +++ b/polkadot/node/network/bridge/src/rx/mod.rs @@ -37,8 +37,8 @@ use polkadot_node_network_protocol::{ CollationVersion, PeerSet, PeerSetProtocolNames, PerPeerSet, ProtocolVersion, ValidationVersion, }, - v1 as protocol_v1, v2 as protocol_v2, vstaging as protocol_vstaging, ObservedRole, OurView, - PeerId, UnifiedReputationChange as Rep, View, + v1 as protocol_v1, v2 as protocol_v2, v3 as protocol_v3, ObservedRole, OurView, PeerId, + UnifiedReputationChange as Rep, View, }; use polkadot_node_subsystem::{ @@ -53,11 +53,6 @@ use polkadot_node_subsystem::{ use polkadot_primitives::{AuthorityDiscoveryId, BlockNumber, Hash, ValidatorIndex}; -/// Peer set info for network initialization. -/// -/// To be passed to [`FullNetworkConfiguration::add_notification_protocol`](). -pub use polkadot_node_network_protocol::peer_set::{peer_sets_info, IsAuthority}; - use std::{ collections::{hash_map, HashMap}, iter::ExactSizeIterator, @@ -70,7 +65,7 @@ use super::validator_discovery; /// Defines the `Network` trait with an implementation for an `Arc`. use crate::network::{ send_collation_message_v1, send_collation_message_v2, send_validation_message_v1, - send_validation_message_v2, send_validation_message_vstaging, Network, + send_validation_message_v2, send_validation_message_v3, Network, }; use crate::{network::get_peer_id_by_authority_id, WireMessage}; @@ -294,9 +289,9 @@ async fn handle_validation_message( metrics, notification_sinks, ), - ValidationVersion::VStaging => send_validation_message_vstaging( + ValidationVersion::V3 => send_validation_message_v3( vec![peer], - WireMessage::::ViewUpdate(local_view), + WireMessage::::ViewUpdate(local_view), metrics, notification_sinks, ), @@ -360,48 +355,47 @@ async fn handle_validation_message( ?peer, ); - let (events, reports) = - if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V1.into()) { - handle_peer_messages::( - peer, - PeerSet::Validation, - &mut shared.0.lock().validation_peers, - vec![notification.into()], - metrics, - ) - } else if expected_versions[PeerSet::Validation] == - Some(ValidationVersion::V2.into()) - { - handle_peer_messages::( - peer, - PeerSet::Validation, - &mut shared.0.lock().validation_peers, - vec![notification.into()], - metrics, - ) - } else if expected_versions[PeerSet::Validation] == - Some(ValidationVersion::VStaging.into()) - { - handle_peer_messages::( - peer, - PeerSet::Validation, - &mut shared.0.lock().validation_peers, - vec![notification.into()], - metrics, - ) - } else { - gum::warn!( - target: LOG_TARGET, - version = ?expected_versions[PeerSet::Validation], - "Major logic bug. Peer somehow has unsupported validation protocol version." - ); + let (events, reports) = if expected_versions[PeerSet::Validation] == + Some(ValidationVersion::V1.into()) + { + handle_peer_messages::( + peer, + PeerSet::Validation, + &mut shared.0.lock().validation_peers, + vec![notification.into()], + metrics, + ) + } else if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V2.into()) { + handle_peer_messages::( + peer, + PeerSet::Validation, + &mut shared.0.lock().validation_peers, + vec![notification.into()], + metrics, + ) + } else if expected_versions[PeerSet::Validation] == Some(ValidationVersion::V3.into()) { + handle_peer_messages::( + peer, + PeerSet::Validation, + &mut shared.0.lock().validation_peers, + vec![notification.into()], + metrics, + ) + } else { + gum::warn!( + target: LOG_TARGET, + version = ?expected_versions[PeerSet::Validation], + "Major logic bug. Peer somehow has unsupported validation protocol version." + ); - never!("Only versions 1 and 2 are supported; peer set connection checked above; qed"); + never!( + "Only versions 1 and 2 are supported; peer set connection checked above; qed" + ); - // If a peer somehow triggers this, we'll disconnect them - // eventually. - (Vec::new(), vec![UNCONNECTED_PEERSET_COST]) - }; + // If a peer somehow triggers this, we'll disconnect them + // eventually. + (Vec::new(), vec![UNCONNECTED_PEERSET_COST]) + }; for report in reports { network_service.report_peer(peer, report.into()); @@ -980,8 +974,8 @@ fn update_our_view( filter_by_peer_version(&validation_peers, ValidationVersion::V2.into()); let v2_collation_peers = filter_by_peer_version(&collation_peers, CollationVersion::V2.into()); - let vstaging_validation_peers = - filter_by_peer_version(&validation_peers, ValidationVersion::VStaging.into()); + let v3_validation_peers = + filter_by_peer_version(&validation_peers, ValidationVersion::V3.into()); send_validation_message_v1( v1_validation_peers, @@ -1011,8 +1005,8 @@ fn update_our_view( notification_sinks, ); - send_validation_message_vstaging( - vstaging_validation_peers, + send_validation_message_v3( + v3_validation_peers, WireMessage::ViewUpdate(new_view.clone()), metrics, notification_sinks, diff --git a/polkadot/node/network/bridge/src/rx/tests.rs b/polkadot/node/network/bridge/src/rx/tests.rs index e0b86feb64421bf542fbbc95695c5a9260fda840..6847b8a7e24db5b13df2873d1b54f37506e76831 100644 --- a/polkadot/node/network/bridge/src/rx/tests.rs +++ b/polkadot/node/network/bridge/src/rx/tests.rs @@ -224,7 +224,7 @@ impl TestNetworkHandle { PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/1")), PeerSet::Collation => Some(ProtocolName::from("/polkadot/collation/1")), }, - ValidationVersion::VStaging => match peer_set { + ValidationVersion::V3 => match peer_set { PeerSet::Validation => Some(ProtocolName::from("/polkadot/validation/3")), PeerSet::Collation => unreachable!(), }, @@ -1433,8 +1433,8 @@ fn network_protocol_versioning_view_update() { ValidationVersion::V2 => WireMessage::::ViewUpdate(view.clone()) .encode(), - ValidationVersion::VStaging => - WireMessage::::ViewUpdate(view.clone()) + ValidationVersion::V3 => + WireMessage::::ViewUpdate(view.clone()) .encode(), }; assert_network_actions_contains( @@ -1469,7 +1469,7 @@ fn network_protocol_versioning_subsystem_msg() { NetworkBridgeEvent::PeerConnected( peer, ObservedRole::Full, - ValidationVersion::V2.into(), + ValidationVersion::V3.into(), None, ), &mut virtual_overseer, @@ -1484,9 +1484,9 @@ fn network_protocol_versioning_subsystem_msg() { } let approval_distribution_message = - protocol_v2::ApprovalDistributionMessage::Approvals(Vec::new()); + protocol_v3::ApprovalDistributionMessage::Approvals(Vec::new()); - let msg = protocol_v2::ValidationProtocol::ApprovalDistribution( + let msg = protocol_v3::ValidationProtocol::ApprovalDistribution( approval_distribution_message.clone(), ); @@ -1502,7 +1502,7 @@ fn network_protocol_versioning_subsystem_msg() { virtual_overseer.recv().await, AllMessages::ApprovalDistribution( ApprovalDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage(p, Versioned::V2(m)) + NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m)) ) ) => { assert_eq!(p, peer); @@ -1536,7 +1536,7 @@ fn network_protocol_versioning_subsystem_msg() { virtual_overseer.recv().await, AllMessages::StatementDistribution( StatementDistributionMessage::NetworkBridgeUpdate( - NetworkBridgeEvent::PeerMessage(p, Versioned::V2(m)) + NetworkBridgeEvent::PeerMessage(p, Versioned::V3(m)) ) ) => { assert_eq!(p, peer); diff --git a/polkadot/node/network/bridge/src/tx/mod.rs b/polkadot/node/network/bridge/src/tx/mod.rs index bdcd1574e335a846e5fd0b8ac5f0bd801c5baa5a..d5be6f01c33737a2b09bd39f40640d29c99ca94c 100644 --- a/polkadot/node/network/bridge/src/tx/mod.rs +++ b/polkadot/node/network/bridge/src/tx/mod.rs @@ -27,10 +27,6 @@ use polkadot_node_subsystem::{ overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, }; -/// Peer set info for network initialization. -/// -/// To be passed to [`FullNetworkConfiguration::add_notification_protocol`](). -pub use polkadot_node_network_protocol::peer_set::{peer_sets_info, IsAuthority}; use polkadot_node_network_protocol::request_response::Requests; use sc_network::{MessageSink, ReputationChange}; @@ -41,7 +37,7 @@ use crate::validator_discovery; /// Defines the `Network` trait with an implementation for an `Arc`. use crate::network::{ send_collation_message_v1, send_collation_message_v2, send_validation_message_v1, - send_validation_message_v2, send_validation_message_vstaging, Network, + send_validation_message_v2, send_validation_message_v3, Network, }; use crate::metrics::Metrics; @@ -205,7 +201,7 @@ where &metrics, notification_sinks, ), - Versioned::VStaging(msg) => send_validation_message_vstaging( + Versioned::V3(msg) => send_validation_message_v3( peers, WireMessage::ProtocolMessage(msg), &metrics, @@ -235,7 +231,7 @@ where &metrics, notification_sinks, ), - Versioned::VStaging(msg) => send_validation_message_vstaging( + Versioned::V3(msg) => send_validation_message_v3( peers, WireMessage::ProtocolMessage(msg), &metrics, @@ -264,7 +260,7 @@ where &metrics, notification_sinks, ), - Versioned::V2(msg) | Versioned::VStaging(msg) => send_collation_message_v2( + Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2( peers, WireMessage::ProtocolMessage(msg), &metrics, @@ -287,7 +283,7 @@ where &metrics, notification_sinks, ), - Versioned::V2(msg) | Versioned::VStaging(msg) => send_collation_message_v2( + Versioned::V2(msg) | Versioned::V3(msg) => send_collation_message_v2( peers, WireMessage::ProtocolMessage(msg), &metrics, diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 367a97f35d994c566564247cf093e73e15ec4396..bcf4f74132fc0dd0ac85c84e8655abc37d5218ac 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } futures = "0.3.21" diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index b3a396e1be3488e34424681132e1ffde11097717..8fb0bb2154445f99000f329ff9aeb30e29c02092 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -882,7 +882,7 @@ async fn handle_incoming_peer_message( match msg { Versioned::V1(V1::Declare(..)) | Versioned::V2(V2::Declare(..)) | - Versioned::VStaging(V2::Declare(..)) => { + Versioned::V3(V2::Declare(..)) => { gum::trace!( target: LOG_TARGET, ?origin, @@ -895,7 +895,7 @@ async fn handle_incoming_peer_message( }, Versioned::V1(V1::AdvertiseCollation(_)) | Versioned::V2(V2::AdvertiseCollation { .. }) | - Versioned::VStaging(V2::AdvertiseCollation { .. }) => { + Versioned::V3(V2::AdvertiseCollation { .. }) => { gum::trace!( target: LOG_TARGET, ?origin, @@ -911,7 +911,7 @@ async fn handle_incoming_peer_message( }, Versioned::V1(V1::CollationSeconded(relay_parent, statement)) | Versioned::V2(V2::CollationSeconded(relay_parent, statement)) | - Versioned::VStaging(V2::CollationSeconded(relay_parent, statement)) => { + Versioned::V3(V2::CollationSeconded(relay_parent, statement)) => { if !matches!(statement.unchecked_payload(), Statement::Seconded(_)) { gum::warn!( target: LOG_TARGET, diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs index 7dd2287dab684debb39f68f94098c1b225024285..1b1194c72706703283d0002a149a59d950d33975 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/mod.rs @@ -45,8 +45,9 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ - AuthorityDiscoveryId, CollatorPair, ExecutorParams, GroupIndex, GroupRotationInfo, IndexedVec, - ScheduledCore, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, + vstaging::NodeFeatures, AuthorityDiscoveryId, CollatorPair, ExecutorParams, GroupIndex, + GroupRotationInfo, IndexedVec, ScheduledCore, SessionIndex, SessionInfo, ValidatorId, + ValidatorIndex, }; use polkadot_primitives_test_helpers::TestCandidateBuilder; use test_helpers::mock::new_leaf; @@ -406,7 +407,12 @@ async fn distribute_collation_with_receipt( tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); }, - + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::NodeFeatures(_, si_tx), + )) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + }, AllMessages::RuntimeApi(RuntimeApiMessage::Request( _relay_parent, RuntimeApiRequest::ValidatorGroups(tx), diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 20b3b9ea1d265ba242e6dd6dabf5999ae46b286c..48ad3c711a6db9c5e9e09c26d1a6290ffbed60f4 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -777,7 +777,7 @@ async fn process_incoming_peer_message( match msg { Versioned::V1(V1::Declare(collator_id, para_id, signature)) | Versioned::V2(V2::Declare(collator_id, para_id, signature)) | - Versioned::VStaging(V2::Declare(collator_id, para_id, signature)) => { + Versioned::V3(V2::Declare(collator_id, para_id, signature)) => { if collator_peer_id(&state.peer_data, &collator_id).is_some() { modify_reputation( &mut state.reputation, @@ -894,7 +894,7 @@ async fn process_incoming_peer_message( candidate_hash, parent_head_data_hash, }) | - Versioned::VStaging(V2::AdvertiseCollation { + Versioned::V3(V2::AdvertiseCollation { relay_parent, candidate_hash, parent_head_data_hash, @@ -923,7 +923,7 @@ async fn process_incoming_peer_message( }, Versioned::V1(V1::CollationSeconded(..)) | Versioned::V2(V2::CollationSeconded(..)) | - Versioned::VStaging(V2::CollationSeconded(..)) => { + Versioned::V3(V2::CollationSeconded(..)) => { gum::warn!( target: LOG_TARGET, peer_id = ?origin, diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index f4ea358c41b513c29c24e477bd9ba009f6c55b9a..6b494c65336de7806019cd2ea4465e93c818f212 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" @@ -28,7 +31,7 @@ indexmap = "1.9.1" [dev-dependencies] async-channel = "1.8.0" -async-trait = "0.1.57" +async-trait = "0.1.74" polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-tracing = { path = "../../../../substrate/primitives/tracing" } diff --git a/polkadot/node/network/dispute-distribution/src/tests/mod.rs b/polkadot/node/network/dispute-distribution/src/tests/mod.rs index 96f045cbf769219e737b4366fb0c96201c9c3b6e..a3520bf35f8023e8931a33f29f0bb9e83e72a62c 100644 --- a/polkadot/node/network/dispute-distribution/src/tests/mod.rs +++ b/polkadot/node/network/dispute-distribution/src/tests/mod.rs @@ -57,8 +57,8 @@ use polkadot_node_subsystem_test_helpers::{ subsystem_test_harness, TestSubsystemContextHandle, }; use polkadot_primitives::{ - AuthorityDiscoveryId, CandidateHash, CandidateReceipt, ExecutorParams, Hash, SessionIndex, - SessionInfo, + vstaging::NodeFeatures, AuthorityDiscoveryId, CandidateHash, CandidateReceipt, ExecutorParams, + Hash, SessionIndex, SessionInfo, }; use self::mock::{ @@ -646,6 +646,16 @@ async fn nested_network_dispute_request<'a, F, O>( }, unexpected => panic!("Unexpected message {:?}", unexpected), } + + match handle.recv().await { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::NodeFeatures(_, si_tx), + )) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + }, + unexpected => panic!("Unexpected message {:?}", unexpected), + } } // Import should get initiated: @@ -773,6 +783,14 @@ async fn activate_leaf( tx.send(Ok(Some(ExecutorParams::default()))).expect("Receiver should stay alive."); } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); } assert_matches!( diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index 64a9743d1db2d2cf221f66c5e996ffe9dd74e79a..9ad7292b0fdcf979897663bff03e36eaa36acb4b 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } @@ -33,6 +36,6 @@ sp-authority-discovery = { path = "../../../../substrate/primitives/authority-di polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" -async-trait = "0.1.57" +async-trait = "0.1.74" 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 0d1b04f2ba23889fda3d530b62706775e19c0157..22417795d5ea55ff65d35ffb7d0f4b960582c570 100644 --- a/polkadot/node/network/gossip-support/src/lib.rs +++ b/polkadot/node/network/gossip-support/src/lib.rs @@ -477,7 +477,7 @@ where match message { Versioned::V1(m) => match m {}, Versioned::V2(m) => match m {}, - Versioned::VStaging(m) => match m {}, + Versioned::V3(m) => match m {}, } }, } diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index c33b9eae3252606e8d2fcbd954a0e180f4a47acb..e683c662fbe78085c192786a2c177e93f8418fae 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -6,9 +6,12 @@ edition.workspace = true license.workspace = true description = "Primitives types for the Node-side" +[lints] +workspace = true + [dependencies] async-channel = "1.8.0" -async-trait = "0.1.57" +async-trait = "0.1.74" hex = "0.4.3" polkadot-primitives = { path = "../../../primitives" } polkadot-node-primitives = { path = "../../primitives" } @@ -27,6 +30,3 @@ bitvec = "1" [dev-dependencies] rand_chacha = "0.3.1" - -[features] -network-protocol-staging = [] diff --git a/polkadot/node/network/protocol/src/grid_topology.rs b/polkadot/node/network/protocol/src/grid_topology.rs index 99dd513c4d7909012162a075f6bbc7ce041377fb..8bd9adbc17c1089bdaab9d16b271b9ae4bc8e708 100644 --- a/polkadot/node/network/protocol/src/grid_topology.rs +++ b/polkadot/node/network/protocol/src/grid_topology.rs @@ -73,12 +73,20 @@ pub struct SessionGridTopology { shuffled_indices: Vec, /// The canonical shuffling of validators for the session. canonical_shuffling: Vec, + /// The list of peer-ids in an efficient way to search. + peer_ids: HashSet, } impl SessionGridTopology { /// Create a new session grid topology. pub fn new(shuffled_indices: Vec, canonical_shuffling: Vec) -> Self { - SessionGridTopology { shuffled_indices, canonical_shuffling } + let mut peer_ids = HashSet::new(); + for peer_info in canonical_shuffling.iter() { + for peer_id in peer_info.peer_ids.iter() { + peer_ids.insert(*peer_id); + } + } + SessionGridTopology { shuffled_indices, canonical_shuffling, peer_ids } } /// Produces the outgoing routing logic for a particular peer. @@ -111,6 +119,11 @@ impl SessionGridTopology { Some(grid_subset) } + + /// Tells if a given peer id is validator in a session + pub fn is_validator(&self, peer: &PeerId) -> bool { + self.peer_ids.contains(peer) + } } struct MatrixNeighbors { @@ -273,6 +286,11 @@ impl SessionGridTopologyEntry { pub fn get(&self) -> &SessionGridTopology { &self.topology } + + /// Tells if a given peer id is validator in a session + pub fn is_validator(&self, peer: &PeerId) -> bool { + self.topology.is_validator(peer) + } } /// A set of topologies indexed by session @@ -347,6 +365,7 @@ impl Default for SessionBoundGridTopologyStorage { topology: SessionGridTopology { shuffled_indices: Vec::new(), canonical_shuffling: Vec::new(), + peer_ids: Default::default(), }, local_neighbors: GridNeighbors::empty(), }, diff --git a/polkadot/node/network/protocol/src/lib.rs b/polkadot/node/network/protocol/src/lib.rs index 9aeeb98ea9d6f6217668aa5dc3376cadc3acfb53..ae72230ee43d506549482e9a063aef5412b1283c 100644 --- a/polkadot/node/network/protocol/src/lib.rs +++ b/polkadot/node/network/protocol/src/lib.rs @@ -253,29 +253,29 @@ impl View { /// A protocol-versioned type. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Versioned { +pub enum Versioned { /// V1 type. V1(V1), /// V2 type. V2(V2), - /// VStaging type - VStaging(VStaging), + /// V3 type + V3(V3), } -impl Versioned<&'_ V1, &'_ V2, &'_ VStaging> { +impl Versioned<&'_ V1, &'_ V2, &'_ V3> { /// Convert to a fully-owned version of the message. - pub fn clone_inner(&self) -> Versioned { + pub fn clone_inner(&self) -> Versioned { match *self { Versioned::V1(inner) => Versioned::V1(inner.clone()), Versioned::V2(inner) => Versioned::V2(inner.clone()), - Versioned::VStaging(inner) => Versioned::VStaging(inner.clone()), + Versioned::V3(inner) => Versioned::V3(inner.clone()), } } } /// All supported versions of the validation protocol message. pub type VersionedValidationProtocol = - Versioned; + Versioned; impl From for VersionedValidationProtocol { fn from(v1: v1::ValidationProtocol) -> Self { @@ -289,9 +289,9 @@ impl From for VersionedValidationProtocol { } } -impl From for VersionedValidationProtocol { - fn from(vstaging: vstaging::ValidationProtocol) -> Self { - VersionedValidationProtocol::VStaging(vstaging) +impl From for VersionedValidationProtocol { + fn from(v3: v3::ValidationProtocol) -> Self { + VersionedValidationProtocol::V3(v3) } } @@ -317,7 +317,7 @@ macro_rules! impl_versioned_full_protocol_from { match versioned_from { Versioned::V1(x) => Versioned::V1(x.into()), Versioned::V2(x) => Versioned::V2(x.into()), - Versioned::VStaging(x) => Versioned::VStaging(x.into()), + Versioned::V3(x) => Versioned::V3(x.into()), } } } @@ -331,7 +331,7 @@ macro_rules! impl_versioned_try_from { $out:ty, $v1_pat:pat => $v1_out:expr, $v2_pat:pat => $v2_out:expr, - $vstaging_pat:pat => $vstaging_out:expr + $v3_pat:pat => $v3_out:expr ) => { impl TryFrom<$from> for $out { type Error = crate::WrongVariant; @@ -341,7 +341,7 @@ macro_rules! impl_versioned_try_from { match x { Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out)), Versioned::V2($v2_pat) => Ok(Versioned::V2($v2_out)), - Versioned::VStaging($vstaging_pat) => Ok(Versioned::VStaging($vstaging_out)), + Versioned::V3($v3_pat) => Ok(Versioned::V3($v3_out)), _ => Err(crate::WrongVariant), } } @@ -355,8 +355,7 @@ macro_rules! impl_versioned_try_from { match x { Versioned::V1($v1_pat) => Ok(Versioned::V1($v1_out.clone())), Versioned::V2($v2_pat) => Ok(Versioned::V2($v2_out.clone())), - Versioned::VStaging($vstaging_pat) => - Ok(Versioned::VStaging($vstaging_out.clone())), + Versioned::V3($v3_pat) => Ok(Versioned::V3($v3_out.clone())), _ => Err(crate::WrongVariant), } } @@ -368,7 +367,7 @@ macro_rules! impl_versioned_try_from { pub type BitfieldDistributionMessage = Versioned< v1::BitfieldDistributionMessage, v2::BitfieldDistributionMessage, - vstaging::BitfieldDistributionMessage, + v3::BitfieldDistributionMessage, >; impl_versioned_full_protocol_from!( BitfieldDistributionMessage, @@ -380,14 +379,14 @@ impl_versioned_try_from!( BitfieldDistributionMessage, v1::ValidationProtocol::BitfieldDistribution(x) => x, v2::ValidationProtocol::BitfieldDistribution(x) => x, - vstaging::ValidationProtocol::BitfieldDistribution(x) => x + v3::ValidationProtocol::BitfieldDistribution(x) => x ); /// Version-annotated messages used by the statement distribution subsystem. pub type StatementDistributionMessage = Versioned< v1::StatementDistributionMessage, v2::StatementDistributionMessage, - vstaging::StatementDistributionMessage, + v3::StatementDistributionMessage, >; impl_versioned_full_protocol_from!( StatementDistributionMessage, @@ -399,14 +398,14 @@ impl_versioned_try_from!( StatementDistributionMessage, v1::ValidationProtocol::StatementDistribution(x) => x, v2::ValidationProtocol::StatementDistribution(x) => x, - vstaging::ValidationProtocol::StatementDistribution(x) => x + v3::ValidationProtocol::StatementDistribution(x) => x ); /// Version-annotated messages used by the approval distribution subsystem. pub type ApprovalDistributionMessage = Versioned< v1::ApprovalDistributionMessage, v2::ApprovalDistributionMessage, - vstaging::ApprovalDistributionMessage, + v3::ApprovalDistributionMessage, >; impl_versioned_full_protocol_from!( ApprovalDistributionMessage, @@ -418,7 +417,7 @@ impl_versioned_try_from!( ApprovalDistributionMessage, v1::ValidationProtocol::ApprovalDistribution(x) => x, v2::ValidationProtocol::ApprovalDistribution(x) => x, - vstaging::ValidationProtocol::ApprovalDistribution(x) => x + v3::ValidationProtocol::ApprovalDistribution(x) => x ); @@ -426,7 +425,7 @@ impl_versioned_try_from!( pub type GossipSupportNetworkMessage = Versioned< v1::GossipSupportNetworkMessage, v2::GossipSupportNetworkMessage, - vstaging::GossipSupportNetworkMessage, + v3::GossipSupportNetworkMessage, >; // This is a void enum placeholder, so never gets sent over the wire. @@ -871,19 +870,17 @@ pub mod v2 { } } -/// vstaging network protocol types, intended to become v3. -/// Initial purpose is for chaning ApprovalDistributionMessage to -/// include more than one assignment in the message. -pub mod vstaging { +/// v3 network protocol types. +/// Purpose is for chaning ApprovalDistributionMessage to +/// include more than one assignment and approval in a message. +pub mod v3 { use parity_scale_codec::{Decode, Encode}; - use polkadot_node_primitives::approval::{ - v1::IndirectSignedApprovalVote, - v2::{CandidateBitfield, IndirectAssignmentCertV2}, + use polkadot_node_primitives::approval::v2::{ + CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2, }; - /// This parts of the protocol did not change from v2, so just alias them in vstaging, - /// no reason why they can't be change untill vstaging becomes v3 and is released. + /// This parts of the protocol did not change from v2, so just alias them in v3. pub use super::v2::{ declare_signature_payload, BackedCandidateAcknowledgement, BackedCandidateManifest, BitfieldDistributionMessage, GossipSupportNetworkMessage, StatementDistributionMessage, @@ -903,7 +900,7 @@ pub mod vstaging { Assignments(Vec<(IndirectAssignmentCertV2, CandidateBitfield)>), /// Approvals for candidates in some recent, unfinalized block. #[codec(index = 1)] - Approvals(Vec), + Approvals(Vec), } /// All network messages on the validation peer-set. diff --git a/polkadot/node/network/protocol/src/peer_set.rs b/polkadot/node/network/protocol/src/peer_set.rs index 7e257d508b5bb7c26a3291b5c52d9a6835620eac..cb329607ad6127024af9e6d6bc3c75c7e813c0e3 100644 --- a/polkadot/node/network/protocol/src/peer_set.rs +++ b/polkadot/node/network/protocol/src/peer_set.rs @@ -73,7 +73,11 @@ impl PeerSet { // Networking layer relies on `get_main_name()` being the main name of the protocol // for peersets and connection management. let protocol = peerset_protocol_names.get_main_name(self); - let fallback_names = PeerSetProtocolNames::get_fallback_names(self); + let fallback_names = PeerSetProtocolNames::get_fallback_names( + self, + &peerset_protocol_names.genesis_hash, + peerset_protocol_names.fork_id.as_deref(), + ); let max_notification_size = self.get_max_notification_size(is_authority); match self { @@ -127,15 +131,8 @@ impl PeerSet { /// Networking layer relies on `get_main_version()` being the version /// of the main protocol name reported by [`PeerSetProtocolNames::get_main_name()`]. pub fn get_main_version(self) -> ProtocolVersion { - #[cfg(not(feature = "network-protocol-staging"))] - match self { - PeerSet::Validation => ValidationVersion::V2.into(), - PeerSet::Collation => CollationVersion::V2.into(), - } - - #[cfg(feature = "network-protocol-staging")] match self { - PeerSet::Validation => ValidationVersion::VStaging.into(), + PeerSet::Validation => ValidationVersion::V3.into(), PeerSet::Collation => CollationVersion::V2.into(), } } @@ -163,7 +160,7 @@ impl PeerSet { Some("validation/1") } else if version == ValidationVersion::V2.into() { Some("validation/2") - } else if version == ValidationVersion::VStaging.into() { + } else if version == ValidationVersion::V3.into() { Some("validation/3") } else { None @@ -236,9 +233,10 @@ pub enum ValidationVersion { V1 = 1, /// The second version. V2 = 2, - /// The staging version to gather changes - /// that before the release become v3. - VStaging = 3, + /// The third version where changes to ApprovalDistributionMessage had been made. + /// The changes are translatable to V2 format untill assignments v2 and approvals + /// coalescing is enabled through a runtime upgrade. + V3 = 3, } /// Supported collation protocol versions. Only versions defined here must be used in the codebase. @@ -299,6 +297,8 @@ impl From for ProtocolVersion { pub struct PeerSetProtocolNames { protocols: HashMap, names: HashMap<(PeerSet, ProtocolVersion), ProtocolName>, + genesis_hash: Hash, + fork_id: Option, } impl PeerSetProtocolNames { @@ -333,7 +333,7 @@ impl PeerSetProtocolNames { } Self::register_legacy_protocol(&mut protocols, protocol); } - Self { protocols, names } + Self { protocols, names, genesis_hash, fork_id: fork_id.map(|fork_id| fork_id.into()) } } /// Helper function to register main protocol. @@ -437,9 +437,30 @@ impl PeerSetProtocolNames { } /// Get the protocol fallback names. Currently only holds the legacy name - /// for `LEGACY_PROTOCOL_VERSION` = 1. - fn get_fallback_names(protocol: PeerSet) -> Vec { - std::iter::once(Self::get_legacy_name(protocol)).collect() + /// for `LEGACY_PROTOCOL_VERSION` = 1 and v2 for validation. + fn get_fallback_names( + protocol: PeerSet, + genesis_hash: &Hash, + fork_id: Option<&str>, + ) -> Vec { + let mut fallbacks = vec![Self::get_legacy_name(protocol)]; + match protocol { + PeerSet::Validation => { + // Fallbacks are tried one by one, till one matches so push v2 at the top, so + // that it is used ahead of the legacy one(v1). + fallbacks.insert( + 0, + Self::generate_name( + genesis_hash, + fork_id, + protocol, + ValidationVersion::V2.into(), + ), + ) + }, + PeerSet::Collation => {}, + }; + fallbacks } } diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index e251abc445d61fbbf61a18725bfb88b1696a45c0..85d2c75aa797826959a0b5ae447bd2aa82164106 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" futures-timer = "3.0.2" diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs index d9866af1ee233627911fc2a6b2ff2e5a63a3d93e..93f97fe1dd6ede274c4109f4ae7a74765d9ee649 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/mod.rs @@ -22,8 +22,8 @@ use polkadot_node_network_protocol::{ grid_topology::{GridNeighbors, RequiredRouting, SessionBoundGridTopologyStorage}, peer_set::{IsAuthority, PeerSet, ValidationVersion}, v1::{self as protocol_v1, StatementMetadata}, - v2 as protocol_v2, vstaging as protocol_vstaging, IfDisconnected, PeerId, - UnifiedReputationChange as Rep, Versioned, View, + v2 as protocol_v2, v3 as protocol_v3, IfDisconnected, PeerId, UnifiedReputationChange as Rep, + Versioned, View, }; use polkadot_node_primitives::{ SignedFullStatement, Statement, StatementWithPVD, UncheckedSignedFullStatement, @@ -1075,7 +1075,7 @@ async fn circulate_statement<'a, Context>( }) .partition::, _>(|(_, _, version)| match version { ValidationVersion::V1 => true, - ValidationVersion::V2 | ValidationVersion::VStaging => false, + ValidationVersion::V2 | ValidationVersion::V3 => false, }); // partition is handy here but not if we add more protocol versions let payload = v1_statement_message(relay_parent, stored.statement.clone(), metrics); @@ -1108,8 +1108,7 @@ async fn circulate_statement<'a, Context>( .collect(); let v2_peers_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V2.into()); - let vstaging_to_send = - filter_by_peer_version(&peers_to_send, ValidationVersion::VStaging.into()); + let v3_to_send = filter_by_peer_version(&peers_to_send, ValidationVersion::V3.into()); if !v2_peers_to_send.is_empty() { gum::trace!( @@ -1126,17 +1125,17 @@ async fn circulate_statement<'a, Context>( .await; } - if !vstaging_to_send.is_empty() { + if !v3_to_send.is_empty() { gum::trace!( target: LOG_TARGET, - ?vstaging_to_send, + ?v3_to_send, ?relay_parent, statement = ?stored.statement, - "Sending statement to vstaging peers", + "Sending statement to v3 peers", ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - vstaging_to_send, - compatible_v1_message(ValidationVersion::VStaging, payload.clone()).into(), + v3_to_send, + compatible_v1_message(ValidationVersion::V3, payload.clone()).into(), )) .await; } @@ -1472,10 +1471,8 @@ async fn handle_incoming_message<'a, Context>( let message = match message { Versioned::V1(m) => m, Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(m)) | - Versioned::VStaging(protocol_vstaging::StatementDistributionMessage::V1Compatibility( - m, - )) => m, - Versioned::V2(_) | Versioned::VStaging(_) => { + Versioned::V3(protocol_v3::StatementDistributionMessage::V1Compatibility(m)) => m, + Versioned::V2(_) | Versioned::V3(_) => { // The higher-level subsystem code is supposed to filter out // all non v1 messages. gum::debug!( @@ -2201,8 +2198,7 @@ fn compatible_v1_message( ValidationVersion::V1 => Versioned::V1(message), ValidationVersion::V2 => Versioned::V2(protocol_v2::StatementDistributionMessage::V1Compatibility(message)), - ValidationVersion::VStaging => Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::V1Compatibility(message), - ), + ValidationVersion::V3 => + Versioned::V3(protocol_v3::StatementDistributionMessage::V1Compatibility(message)), } } diff --git a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs index ca3038f9b3f3a4250b5337e70f9e009699afbc3b..8ac9895ec5ad27ea271d07059612e85806a334b9 100644 --- a/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs +++ b/polkadot/node/network/statement-distribution/src/legacy_v1/tests.rs @@ -43,8 +43,8 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers::mock::{make_ferdie_keystore, new_leaf}; use polkadot_primitives::{ - ExecutorParams, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, SessionInfo, - ValidationCode, + vstaging::NodeFeatures, ExecutorParams, GroupIndex, Hash, HeadData, Id as ParaId, IndexedVec, + SessionInfo, ValidationCode, }; use polkadot_primitives_test_helpers::{ dummy_committed_candidate_receipt, dummy_hash, AlwaysZeroRng, @@ -834,6 +834,15 @@ fn receiving_from_one_sends_to_another_and_to_candidate_backing() { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + // notify of peers and view handle .send(FromOrchestra::Communication { @@ -1074,6 +1083,15 @@ fn receiving_large_statement_from_one_sends_to_another_and_to_candidate_backing( } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + // notify of peers and view handle .send(FromOrchestra::Communication { @@ -1604,6 +1622,15 @@ fn delay_reputation_changes() { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + // notify of peers and view handle .send(FromOrchestra::Communication { @@ -2084,6 +2111,15 @@ fn share_prioritizes_backing_group() { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + // notify of dummy peers and view for (peer, pair) in dummy_peers.clone().into_iter().zip(dummy_pairs) { handle @@ -2406,6 +2442,15 @@ fn peer_cant_flood_with_large_statements() { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + // notify of peers and view handle .send(FromOrchestra::Communication { @@ -2631,6 +2676,14 @@ fn handle_multiple_seconded_statements() { } ); + assert_matches!( + handle.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); // notify of peers and view for peer in all_peers.iter() { handle diff --git a/polkadot/node/network/statement-distribution/src/lib.rs b/polkadot/node/network/statement-distribution/src/lib.rs index ef1fc7cd78b5f6e84b900d17907bf412908e712d..a1ba1137b5acf119edbd9d228db3fa41e57afbc3 100644 --- a/polkadot/node/network/statement-distribution/src/lib.rs +++ b/polkadot/node/network/statement-distribution/src/lib.rs @@ -27,7 +27,7 @@ use std::time::Duration; use polkadot_node_network_protocol::{ request_response::{v1 as request_v1, v2::AttestedCandidateRequest, IncomingRequestReceiver}, - v2 as protocol_v2, vstaging as protocol_vstaging, Versioned, + v2 as protocol_v2, v3 as protocol_v3, Versioned, }; use polkadot_node_primitives::StatementWithPVD; use polkadot_node_subsystem::{ @@ -400,11 +400,11 @@ impl StatementDistributionSubsystem { Versioned::V2( protocol_v2::StatementDistributionMessage::V1Compatibility(_), ) | - Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::V1Compatibility(_), + Versioned::V3( + protocol_v3::StatementDistributionMessage::V1Compatibility(_), ) => VersionTarget::Legacy, Versioned::V1(_) => VersionTarget::Legacy, - Versioned::V2(_) | Versioned::VStaging(_) => VersionTarget::Current, + Versioned::V2(_) | Versioned::V3(_) => VersionTarget::Current, }, _ => VersionTarget::Both, }; diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 406f1130590902f12caa9058c55a80b0fbb54c8a..2f06d3685b8149416960da4c94ed4b52f06d2c5b 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -29,8 +29,7 @@ use polkadot_node_network_protocol::{ MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS, }, v2::{self as protocol_v2, StatementFilter}, - vstaging as protocol_vstaging, IfDisconnected, PeerId, UnifiedReputationChange as Rep, - Versioned, View, + v3 as protocol_v3, IfDisconnected, PeerId, UnifiedReputationChange as Rep, Versioned, View, }; use polkadot_node_primitives::{ SignedFullStatementWithPVD, StatementWithPVD as FullStatementWithPVD, @@ -366,7 +365,7 @@ pub(crate) async fn handle_network_update( gum::trace!(target: LOG_TARGET, ?peer_id, ?role, ?protocol_version, "Peer connected"); let versioned_protocol = if protocol_version != ValidationVersion::V2.into() && - protocol_version != ValidationVersion::VStaging.into() + protocol_version != ValidationVersion::V3.into() { return } else { @@ -432,28 +431,28 @@ pub(crate) async fn handle_network_update( net_protocol::StatementDistributionMessage::V2( protocol_v2::StatementDistributionMessage::V1Compatibility(_), ) | - net_protocol::StatementDistributionMessage::VStaging( - protocol_vstaging::StatementDistributionMessage::V1Compatibility(_), + net_protocol::StatementDistributionMessage::V3( + protocol_v3::StatementDistributionMessage::V1Compatibility(_), ) => return, net_protocol::StatementDistributionMessage::V2( protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement), ) | - net_protocol::StatementDistributionMessage::VStaging( - protocol_vstaging::StatementDistributionMessage::Statement(relay_parent, statement), + net_protocol::StatementDistributionMessage::V3( + protocol_v3::StatementDistributionMessage::Statement(relay_parent, statement), ) => handle_incoming_statement(ctx, state, peer_id, relay_parent, statement, reputation) .await, net_protocol::StatementDistributionMessage::V2( protocol_v2::StatementDistributionMessage::BackedCandidateManifest(inner), ) | - net_protocol::StatementDistributionMessage::VStaging( - protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(inner), + net_protocol::StatementDistributionMessage::V3( + protocol_v3::StatementDistributionMessage::BackedCandidateManifest(inner), ) => handle_incoming_manifest(ctx, state, peer_id, inner, reputation).await, net_protocol::StatementDistributionMessage::V2( protocol_v2::StatementDistributionMessage::BackedCandidateKnown(inner), ) | - net_protocol::StatementDistributionMessage::VStaging( - protocol_vstaging::StatementDistributionMessage::BackedCandidateKnown(inner), + net_protocol::StatementDistributionMessage::V3( + protocol_v3::StatementDistributionMessage::BackedCandidateKnown(inner), ) => handle_incoming_acknowledgement(ctx, state, peer_id, inner, reputation).await, }, NetworkBridgeEvent::PeerViewChange(peer_id, view) => @@ -806,13 +805,13 @@ fn pending_statement_network_message( protocol_v2::StatementDistributionMessage::Statement(relay_parent, signed) }) .map(|msg| (vec![peer.0], Versioned::V2(msg).into())), - ValidationVersion::VStaging => statement_store + ValidationVersion::V3 => statement_store .validator_statement(originator, compact) .map(|s| s.as_unchecked().clone()) .map(|signed| { - protocol_vstaging::StatementDistributionMessage::Statement(relay_parent, signed) + protocol_v3::StatementDistributionMessage::Statement(relay_parent, signed) }) - .map(|msg| (vec![peer.0], Versioned::VStaging(msg).into())), + .map(|msg| (vec![peer.0], Versioned::V3(msg).into())), ValidationVersion::V1 => { gum::error!( target: LOG_TARGET, @@ -945,10 +944,10 @@ async fn send_pending_grid_messages( ) .into(), )), - ValidationVersion::VStaging => messages.push(( + ValidationVersion::V3 => messages.push(( vec![peer_id.0], - Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest( + Versioned::V3( + protocol_v3::StatementDistributionMessage::BackedCandidateManifest( manifest, ), ) @@ -960,7 +959,7 @@ async fn send_pending_grid_messages( "Bug ValidationVersion::V1 should not be used in statement-distribution v2, legacy should have handled this" ); - } + }, }; }, grid::ManifestKind::Acknowledgement => { @@ -1308,8 +1307,8 @@ async fn circulate_statement( let statement_to_v2_peers = filter_by_peer_version(&statement_to_peers, ValidationVersion::V2.into()); - let statement_to_vstaging_peers = - filter_by_peer_version(&statement_to_peers, ValidationVersion::VStaging.into()); + let statement_to_v3_peers = + filter_by_peer_version(&statement_to_peers, ValidationVersion::V3.into()); // ship off the network messages to the network bridge. if !statement_to_v2_peers.is_empty() { @@ -1331,17 +1330,17 @@ async fn circulate_statement( .await; } - if !statement_to_vstaging_peers.is_empty() { + if !statement_to_v3_peers.is_empty() { gum::debug!( target: LOG_TARGET, ?compact_statement, n_peers = ?statement_to_peers.len(), - "Sending statement to vstaging peers", + "Sending statement to v3 peers", ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - statement_to_vstaging_peers, - Versioned::VStaging(protocol_vstaging::StatementDistributionMessage::Statement( + statement_to_v3_peers, + Versioned::V3(protocol_v3::StatementDistributionMessage::Statement( relay_parent, statement.as_unchecked().clone(), )) @@ -1887,8 +1886,7 @@ async fn provide_candidate_to_grid( } let manifest_peers_v2 = filter_by_peer_version(&manifest_peers, ValidationVersion::V2.into()); - let manifest_peers_vstaging = - filter_by_peer_version(&manifest_peers, ValidationVersion::VStaging.into()); + let manifest_peers_v3 = filter_by_peer_version(&manifest_peers, ValidationVersion::V3.into()); if !manifest_peers_v2.is_empty() { gum::debug!( target: LOG_TARGET, @@ -1908,27 +1906,27 @@ async fn provide_candidate_to_grid( .await; } - if !manifest_peers_vstaging.is_empty() { + if !manifest_peers_v3.is_empty() { gum::debug!( target: LOG_TARGET, ?candidate_hash, local_validator = ?per_session.local_validator, - n_peers = manifest_peers_vstaging.len(), - "Sending manifest to vstaging peers" + n_peers = manifest_peers_v3.len(), + "Sending manifest to v3 peers" ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - manifest_peers_vstaging, - Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(manifest), - ) + manifest_peers_v3, + Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateManifest( + manifest, + )) .into(), )) .await; } let ack_peers_v2 = filter_by_peer_version(&ack_peers, ValidationVersion::V2.into()); - let ack_peers_vstaging = filter_by_peer_version(&ack_peers, ValidationVersion::VStaging.into()); + let ack_peers_v3 = filter_by_peer_version(&ack_peers, ValidationVersion::V3.into()); if !ack_peers_v2.is_empty() { gum::debug!( target: LOG_TARGET, @@ -1948,22 +1946,20 @@ async fn provide_candidate_to_grid( .await; } - if !ack_peers_vstaging.is_empty() { + if !ack_peers_v3.is_empty() { gum::debug!( target: LOG_TARGET, ?candidate_hash, local_validator = ?per_session.local_validator, - n_peers = ack_peers_vstaging.len(), - "Sending acknowledgement to vstaging peers" + n_peers = ack_peers_v3.len(), + "Sending acknowledgement to v3 peers" ); ctx.send_message(NetworkBridgeTxMessage::SendValidationMessage( - ack_peers_vstaging, - Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::BackedCandidateKnown( - acknowledgement, - ), - ) + ack_peers_v3, + Versioned::V3(protocol_v3::StatementDistributionMessage::BackedCandidateKnown( + acknowledgement, + )) .into(), )) .await; @@ -2293,8 +2289,8 @@ fn post_acknowledgement_statement_messages( ) .into(), )), - ValidationVersion::VStaging => messages.push(Versioned::VStaging( - protocol_vstaging::StatementDistributionMessage::Statement( + ValidationVersion::V3 => messages.push(Versioned::V3( + protocol_v3::StatementDistributionMessage::Statement( relay_parent, statement.as_unchecked().clone(), ) @@ -2441,9 +2437,9 @@ fn acknowledgement_and_statement_messages( let mut messages = match peer.1 { ValidationVersion::V2 => vec![(vec![peer.0], msg_v2.into())], - ValidationVersion::VStaging => vec![( + ValidationVersion::V3 => vec![( vec![peer.0], - Versioned::VStaging(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( + Versioned::V3(protocol_v2::StatementDistributionMessage::BackedCandidateKnown( acknowledgement, )) .into(), diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs index 116116659cb136ed088995a0c44a0fc22eec55ef..aa1a473b833f4c4dd6a3c01cf80eff205e0b5e8e 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs @@ -2830,7 +2830,7 @@ fn inactive_local_participates_in_grid() { send_peer_message( &mut overseer, peer_a.clone(), - protocol_vstaging::StatementDistributionMessage::BackedCandidateManifest(manifest), + protocol_v3::StatementDistributionMessage::BackedCandidateManifest(manifest), ) .await; diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml index d9266055a392ce3c85de0ffcec8bd13e8a5e7c2b..132a2ed48323289a6c207346caedcedf396db934 100644 --- a/polkadot/node/overseer/Cargo.toml +++ b/polkadot/node/overseer/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "System overseer of the Polkadot node" +[lints] +workspace = true + [dependencies] client = { package = "sc-client-api", path = "../../../substrate/client/api" } sp-api = { path = "../../../substrate/primitives/api" } @@ -20,7 +23,7 @@ polkadot-primitives = { path = "../../primitives" } orchestra = { version = "0.3.3", default-features = false, features = ["futures_channel"] } gum = { package = "tracing-gum", path = "../gum" } sp-core = { path = "../../../substrate/primitives/core" } -async-trait = "0.1.57" +async-trait = "0.1.74" tikv-jemalloc-ctl = { version = "0.5.0", optional = true } [dev-dependencies] @@ -37,7 +40,6 @@ tikv-jemalloc-ctl = "0.5.0" [features] default = ["futures_channel"] -dotgraph = ["orchestra/dotgraph"] expand = ["orchestra/expand"] futures_channel = ["metered/futures_channel", "orchestra/futures_channel"] jemalloc-allocator = ["dep:tikv-jemalloc-ctl"] diff --git a/polkadot/node/overseer/src/lib.rs b/polkadot/node/overseer/src/lib.rs index da99546a44f75b8f400a3ed57b499b5aa188c10a..f4eddf1f41ceb90d61391ac5140941bdca8b0bf1 100644 --- a/polkadot/node/overseer/src/lib.rs +++ b/polkadot/node/overseer/src/lib.rs @@ -276,6 +276,7 @@ impl From> for BlockInfo { /// An event from outside the overseer scope, such /// as the substrate framework or user interaction. +#[derive(Debug)] pub enum Event { /// A new block was imported. /// @@ -300,6 +301,7 @@ pub enum Event { } /// Some request from outer world. +#[derive(Debug)] pub enum ExternalRequest { /// Wait for the activation of a particular hash /// and be notified by means of the return channel. diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 6c37ebb986f31d54e8cce6bc592099b35029e875..74152b5b7da9a5bd09a741e736c7001a5b76e1ce 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bounded-vec = "0.7" futures = "0.3.21" @@ -18,10 +21,10 @@ sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } sp-runtime = { path = "../../../substrate/primitives/runtime" } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } -schnorrkel = "0.9.1" +schnorrkel = "0.11.4" thiserror = "1.0.48" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/primitives/src/approval.rs b/polkadot/node/primitives/src/approval.rs index e5ae24f7a51e2a3f000b25a11e6c62d4592ae904..f2a79e025affe3de108f0ce8abd985e53c862a5c 100644 --- a/polkadot/node/primitives/src/approval.rs +++ b/polkadot/node/primitives/src/approval.rs @@ -20,7 +20,7 @@ pub mod v1 { use sp_consensus_babe as babe_primitives; pub use sp_consensus_babe::{ - Randomness, Slot, VrfOutput, VrfProof, VrfSignature, VrfTranscript, + Randomness, Slot, VrfPreOutput, VrfProof, VrfSignature, VrfTranscript, }; use parity_scale_codec::{Decode, Encode}; @@ -145,14 +145,14 @@ pub mod v1 { AuthorityOutOfBounds(usize), } - /// An unsafe VRF output. Provide BABE Epoch info to create a `RelayVRFStory`. - pub struct UnsafeVRFOutput { - vrf_output: VrfOutput, + /// An unsafe VRF pre-output. Provide BABE Epoch info to create a `RelayVRFStory`. + pub struct UnsafeVRFPreOutput { + vrf_pre_output: VrfPreOutput, slot: Slot, authority_index: u32, } - impl UnsafeVRFOutput { + impl UnsafeVRFPreOutput { /// Get the slot. pub fn slot(&self) -> Slot { self.slot @@ -177,7 +177,7 @@ pub mod v1 { sp_consensus_babe::make_vrf_transcript(randomness, self.slot, epoch_index); let inout = self - .vrf_output + .vrf_pre_output .0 .attach_input_hash(&pubkey, transcript.0) .map_err(ApprovalError::SchnorrkelSignature)?; @@ -190,7 +190,7 @@ pub mod v1 { /// This fails if either there is no BABE `PreRuntime` digest or /// the digest has type `SecondaryPlain`, which Substrate nodes do /// not produce or accept anymore. - pub fn babe_unsafe_vrf_info(header: &Header) -> Option { + pub fn babe_unsafe_vrf_info(header: &Header) -> Option { use babe_primitives::digests::CompatibleDigestItem; for digest in &header.digest.logs { @@ -198,8 +198,8 @@ pub mod v1 { let slot = pre.slot(); let authority_index = pre.authority_index(); - return pre.vrf_signature().map(|sig| UnsafeVRFOutput { - vrf_output: sig.output.clone(), + return pre.vrf_signature().map(|sig| UnsafeVRFPreOutput { + vrf_pre_output: sig.pre_output.clone(), slot, authority_index, }) @@ -214,12 +214,14 @@ pub mod v1 { pub mod v2 { use parity_scale_codec::{Decode, Encode}; pub use sp_consensus_babe::{ - Randomness, Slot, VrfOutput, VrfProof, VrfSignature, VrfTranscript, + Randomness, Slot, VrfPreOutput, VrfProof, VrfSignature, VrfTranscript, }; use std::ops::BitOr; use bitvec::{prelude::Lsb0, vec::BitVec}; - use polkadot_primitives::{CandidateIndex, CoreIndex, Hash, ValidatorIndex}; + use polkadot_primitives::{ + CandidateIndex, CoreIndex, Hash, ValidatorIndex, ValidatorSignature, + }; /// A static context associated with producing randomness for a core. pub const CORE_RANDOMNESS_CONTEXT: &[u8] = b"A&V CORE v2"; @@ -473,6 +475,59 @@ pub mod v2 { }) } } + + impl From for IndirectSignedApprovalVoteV2 { + fn from(value: super::v1::IndirectSignedApprovalVote) -> Self { + Self { + block_hash: value.block_hash, + validator: value.validator, + candidate_indices: value.candidate_index.into(), + signature: value.signature, + } + } + } + + /// Errors that can occur when trying to convert to/from approvals v1/v2 + #[derive(Debug)] + pub enum ApprovalConversionError { + /// More than one candidate was signed. + MoreThanOneCandidate(usize), + } + + impl TryFrom for super::v1::IndirectSignedApprovalVote { + type Error = ApprovalConversionError; + + fn try_from(value: IndirectSignedApprovalVoteV2) -> Result { + if value.candidate_indices.count_ones() != 1 { + return Err(ApprovalConversionError::MoreThanOneCandidate( + value.candidate_indices.count_ones(), + )) + } + Ok(Self { + block_hash: value.block_hash, + validator: value.validator, + candidate_index: value.candidate_indices.first_one().expect("Qed we checked above") + as u32, + signature: value.signature, + }) + } + } + + /// A signed approval vote which references the candidate indirectly via the block. + /// + /// In practice, we have a look-up from block hash and candidate index to candidate hash, + /// so this can be transformed into a `SignedApprovalVote`. + #[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] + pub struct IndirectSignedApprovalVoteV2 { + /// A block hash where the candidate appears. + pub block_hash: Hash, + /// The index of the candidate in the list of candidates fully included as-of the block. + pub candidate_indices: CandidateBitfield, + /// The validator index. + pub validator: ValidatorIndex, + /// The signature by the validator. + pub signature: ValidatorSignature, + } } #[cfg(test)] diff --git a/polkadot/node/primitives/src/disputes/message.rs b/polkadot/node/primitives/src/disputes/message.rs index 89d3ea6c0af9023ad74fba97f4f2abd73cf84ad8..31fe73a7ba1c4dec821762714195f3c1792beac8 100644 --- a/polkadot/node/primitives/src/disputes/message.rs +++ b/polkadot/node/primitives/src/disputes/message.rs @@ -170,7 +170,7 @@ impl DisputeMessage { let valid_vote = ValidDisputeVote { validator_index: valid_index, signature: valid_statement.validator_signature().clone(), - kind: *valid_kind, + kind: valid_kind.clone(), }; let invalid_vote = InvalidDisputeVote { diff --git a/polkadot/node/primitives/src/disputes/mod.rs b/polkadot/node/primitives/src/disputes/mod.rs index 500b705be9574868c4b173333b6f84dd427d470c..768b95f65537b7ebfe5e4a8baadfd9eec685af4f 100644 --- a/polkadot/node/primitives/src/disputes/mod.rs +++ b/polkadot/node/primitives/src/disputes/mod.rs @@ -46,6 +46,15 @@ pub struct SignedDisputeStatement { session_index: SessionIndex, } +/// Errors encountered while signing a dispute statement +#[derive(Debug)] +pub enum SignedDisputeStatementError { + /// Encountered a keystore error while signing + KeyStoreError(KeystoreError), + /// Could not generate signing payload + PayloadError, +} + /// Tracked votes on candidates, for the purposes of dispute resolution. #[derive(Debug, Clone)] pub struct CandidateVotes { @@ -107,8 +116,9 @@ impl ValidCandidateVotes { ValidDisputeStatementKind::BackingValid(_) | ValidDisputeStatementKind::BackingSeconded(_) => false, ValidDisputeStatementKind::Explicit | - ValidDisputeStatementKind::ApprovalChecking => { - occupied.insert((kind, sig)); + ValidDisputeStatementKind::ApprovalChecking | + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_) => { + occupied.insert((kind.clone(), sig)); kind != occupied.get().0 }, }, @@ -213,16 +223,19 @@ impl SignedDisputeStatement { candidate_hash: CandidateHash, session_index: SessionIndex, validator_public: ValidatorId, - ) -> Result, KeystoreError> { + ) -> Result, SignedDisputeStatementError> { let dispute_statement = if valid { DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) } else { DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) }; - let data = dispute_statement.payload_data(candidate_hash, session_index); + let data = dispute_statement + .payload_data(candidate_hash, session_index) + .map_err(|_| SignedDisputeStatementError::PayloadError)?; let signature = keystore - .sr25519_sign(ValidatorId::ID, validator_public.as_ref(), &data)? + .sr25519_sign(ValidatorId::ID, validator_public.as_ref(), &data) + .map_err(SignedDisputeStatementError::KeyStoreError)? .map(|sig| Self { dispute_statement, candidate_hash, diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index be62145b999176a339586de659c7072eccae66fb..6ac6b82c223dff54d0fb82d96b3d5200e4f9a58b 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.1.0"; +pub const NODE_VERSION: &'static str = "1.5.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 b48302edf520e1adceef34d90c5dbf518f405c96..da3f03193774b0aa3249b0abd81d7e915711933f 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true license.workspace = true description = "Utils to tie different Polkadot components together and allow instantiation of a node." +[lints] +workspace = true + [dependencies] # Substrate Client sc-authority-discovery = { path = "../../../substrate/client/authority-discovery" } @@ -75,19 +78,19 @@ frame-benchmarking-cli = { path = "../../../substrate/utils/frame/benchmarking-c frame-benchmarking = { path = "../../../substrate/frame/benchmarking" } # External Crates -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" hex-literal = "0.4.1" is_executable = "1.0.1" gum = { package = "tracing-gum", path = "../gum" } log = "0.4.17" schnellru = "0.2.1" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" thiserror = "1.0.48" kvdb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } -parity-db = { version = "0.4.8", optional = true } +parity-db = { version = "0.4.12", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } parking_lot = "0.12.1" @@ -225,7 +228,3 @@ runtime-metrics = [ "rococo-runtime?/runtime-metrics", "westend-runtime?/runtime-metrics", ] - -network-protocol-staging = [ - "polkadot-node-network-protocol/network-protocol-staging", -] diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index e92e15fc0e0058c4318df3ad88b5f7259dd94774..ff70dbde73490b60b12f3e24f38af510c28262f4 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -239,7 +239,7 @@ pub enum Error { InvalidWorkerBinaries { prep_worker_path: PathBuf, exec_worker_path: PathBuf }, #[cfg(feature = "full-node")] - #[error("Worker binaries could not be found, make sure polkadot was built/installed correctly. If you ran with `cargo run`, please run `cargo build` first. Searched given workers path ({given_workers_path:?}), polkadot binary path ({current_exe_path:?}), and lib path (/usr/lib/polkadot), workers names: {workers_names:?}")] + #[error("Worker binaries could not be found, make sure polkadot was built and installed correctly. Please see the readme for the latest instructions (https://github.com/paritytech/polkadot-sdk/tree/master/polkadot). If you ran with `cargo run`, please run `cargo build` first. Searched given workers path ({given_workers_path:?}), polkadot binary path ({current_exe_path:?}), and lib path (/usr/lib/polkadot), workers names: {workers_names:?}")] MissingWorkerBinaries { given_workers_path: Option, current_exe_path: PathBuf, diff --git a/polkadot/node/service/src/parachains_db/mod.rs b/polkadot/node/service/src/parachains_db/mod.rs index 92f3f167f22fb6468b53681b4a7c33283cdb29c6..59af30dceeb90f47ca6a4263e2c1428fc74ac436 100644 --- a/polkadot/node/service/src/parachains_db/mod.rs +++ b/polkadot/node/service/src/parachains_db/mod.rs @@ -43,10 +43,7 @@ pub(crate) mod columns { // Version 4 only changed structures in approval voting, so we can re-export the v4 definitions. pub mod v3 { - pub use super::v4::{ - COL_APPROVAL_DATA, COL_AVAILABILITY_DATA, COL_AVAILABILITY_META, - COL_CHAIN_SELECTION_DATA, COL_DISPUTE_COORDINATOR_DATA, NUM_COLUMNS, ORDERED_COL, - }; + pub use super::v4::{NUM_COLUMNS, ORDERED_COL}; } pub mod v4 { diff --git a/polkadot/node/service/src/parachains_db/upgrade.rs b/polkadot/node/service/src/parachains_db/upgrade.rs index 1d76c79d3e32320e29dd781d99eedbd9669320d9..d22eebb5c8d4edebdd2174f3cb1ba144fea0b130 100644 --- a/polkadot/node/service/src/parachains_db/upgrade.rs +++ b/polkadot/node/service/src/parachains_db/upgrade.rs @@ -20,10 +20,16 @@ use std::{ fs, io, path::{Path, PathBuf}, str::FromStr, + sync::Arc, }; -use polkadot_node_core_approval_voting::approval_db::v2::{ - migration_helpers::v1_to_v2, Config as ApprovalDbConfig, +use polkadot_node_core_approval_voting::approval_db::{ + common::{Config as ApprovalDbConfig, Result as ApprovalDbResult}, + v2::migration_helpers::v1_to_latest, + v3::migration_helpers::v2_to_latest, +}; +use polkadot_node_subsystem_util::database::{ + kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter, Database, }; type Version = u32; @@ -32,7 +38,9 @@ const VERSION_FILE_NAME: &'static str = "parachain_db_version"; /// Current db version. /// Version 4 changes approval db format for `OurAssignment`. -pub(crate) const CURRENT_VERSION: Version = 4; +/// Version 5 changes approval db format to hold some additional +/// information about delayed approvals. +pub(crate) const CURRENT_VERSION: Version = 5; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -101,7 +109,8 @@ pub(crate) fn try_upgrade_db_to_next_version( // 2 -> 3 migration Some(2) => migrate_from_version_2_to_3(db_path, db_kind)?, // 3 -> 4 migration - Some(3) => migrate_from_version_3_to_4(db_path, db_kind)?, + Some(3) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v1_to_latest)?, + Some(4) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v2_to_latest)?, // Already at current version, do nothing. Some(CURRENT_VERSION) => CURRENT_VERSION, // This is an arbitrary future version, we don't handle it. @@ -174,14 +183,19 @@ fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result Result { +fn migrate_from_version_3_or_4_to_5( + path: &Path, + db_kind: DatabaseKind, + migration_function: F, +) -> Result +where + F: Fn(Arc, ApprovalDbConfig) -> ApprovalDbResult<()>, +{ gum::info!(target: LOG_TARGET, "Migrating parachains db from version 3 to version 4 ..."); - use polkadot_node_subsystem_util::database::{ - kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter, - }; - use std::sync::Arc; let approval_db_config = ApprovalDbConfig { col_approval_data: super::REAL_COLUMNS.col_approval_data }; @@ -194,7 +208,8 @@ fn migrate_from_version_3_to_4(path: &Path, db_kind: DatabaseKind) -> Result { let db_path = path @@ -207,7 +222,8 @@ fn migrate_from_version_3_to_4(path: &Path, db_kind: DatabaseKind) -> Result + +Commands: + data-availability-read Benchmark availability recovery strategies + +``` + +Note: `test-sequence` is a special test objective that wraps up an arbitrary number of test objectives. It is tipically +used to run a suite of tests defined in a `yaml` file like in this [example](examples/availability_read.yaml). + +### Standard test options + +``` +Options: + --network The type of network to be emulated [default: ideal] [possible values: + ideal, healthy, degraded] + --n-cores Number of cores to fetch availability for [default: 100] + --n-validators Number of validators to fetch chunks from [default: 500] + --min-pov-size The minimum pov size in KiB [default: 5120] + --max-pov-size The maximum pov size bytes [default: 5120] + -n, --num-blocks The number of blocks the test is going to run [default: 1] + -p, --peer-bandwidth The bandwidth of simulated remote peers in KiB + -b, --bandwidth The bandwidth of our simulated node in KiB + --peer-error Simulated conection error ratio [0-100] + --peer-min-latency Minimum remote peer latency in milliseconds [0-5000] + --peer-max-latency Maximum remote peer latency in milliseconds [0-5000] + --profile Enable CPU Profiling with Pyroscope + --pyroscope-url Pyroscope Server URL [default: http://localhost:4040] + --pyroscope-sample-rate Pyroscope Sample Rate [default: 113] + -h, --help Print help + -V, --version Print version +``` + +These apply to all test objectives, except `test-sequence` which relies on the values being specified in a file. + +### Test objectives + +Each test objective can have it's specific configuration options, in contrast with the standard test options. + +For `data-availability-read` the recovery strategy to be used is configurable. + +``` +target/testnet/subsystem-bench data-availability-read --help +Benchmark availability recovery strategies + +Usage: subsystem-bench data-availability-read [OPTIONS] + +Options: + -f, --fetch-from-backers Turbo boost AD Read by fetching the full availability datafrom backers first. Saves CPU + as we don't need to re-construct from chunks. Tipically this is only faster if nodes + have enough bandwidth + -h, --help Print help +``` + +### Understanding the test configuration + +A single test configuration `TestConfiguration` struct applies to a single run of a certain test objective. + +The configuration describes the following important parameters that influence the test duration and resource +usage: + +- how many validators are on the emulated network (`n_validators`) +- how many cores per block the subsystem will have to do work on (`n_cores`) +- for how many blocks the test should run (`num_blocks`) + +From the perspective of the subsystem under test, this means that it will receive an `ActiveLeavesUpdate` signal +followed by an arbitrary amount of messages. This process repeats itself for `num_blocks`. The messages are generally +test payloads pre-generated before the test run, or constructed on pre-genereated payloads. For example the +`AvailabilityRecoveryMessage::RecoverAvailableData` message includes a `CandidateReceipt` which is generated before +the test is started. + +### Example run + +Let's run an availabilty read test which will recover availability for 10 cores with max PoV size on a 500 +node validator network. + +``` + target/testnet/subsystem-bench --n-cores 10 data-availability-read +[2023-11-28T09:01:59Z INFO subsystem_bench::core::display] n_validators = 500, n_cores = 10, pov_size = 5120 - 5120, + error = 0, latency = None +[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Generating template candidate index=0 pov_size=5242880 +[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Created test environment. +[2023-11-28T09:01:59Z INFO subsystem-bench::availability] Pre-generating 10 candidates. +[2023-11-28T09:02:01Z INFO subsystem-bench::core] Initializing network emulation for 500 peers. +[2023-11-28T09:02:01Z INFO substrate_prometheus_endpoint] 〽️ Prometheus exporter started at 127.0.0.1:9999 +[2023-11-28T09:02:01Z INFO subsystem-bench::availability] Current block 1/1 +[2023-11-28T09:02:01Z INFO subsystem_bench::availability] 10 recoveries pending +[2023-11-28T09:02:04Z INFO subsystem_bench::availability] Block time 3231ms +[2023-11-28T09:02:04Z INFO subsystem-bench::availability] Sleeping till end of block (2768ms) +[2023-11-28T09:02:07Z INFO subsystem_bench::availability] All blocks processed in 6001ms +[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Throughput: 51200 KiB/block +[2023-11-28T09:02:07Z INFO subsystem_bench::availability] Block time: 6001 ms +[2023-11-28T09:02:07Z INFO subsystem_bench::availability] + + Total received from network: 66 MiB + Total sent to network: 58 KiB + Total subsystem CPU usage 4.16s + CPU usage per block 4.16s + Total test environment CPU usage 0.00s + CPU usage per block 0.00s +``` + +`Block time` in the context of `data-availability-read` has a different meaning. It measures the amount of time it +took the subsystem to finish processing all of the messages sent in the context of the current test block. + +### Test logs + +You can select log target, subtarget and verbosity just like with Polkadot node CLI, simply setting +`RUST_LOOG="parachain=debug"` turns on debug logs for all parachain consensus subsystems in the test. + +### View test metrics + +Assuming the Grafana/Prometheus stack installation steps completed succesfully, you should be able to +view the test progress in real time by accessing [this link](http://localhost:3000/goto/SM5B8pNSR?orgId=1). + +Now run +`target/testnet/subsystem-bench test-sequence --path polkadot/node/subsystem-bench/examples/availability_read.yaml` +and view the metrics in real time and spot differences between different `n_validators` values. +## Create new test objectives + +This tool is intended to make it easy to write new test objectives that focus individual subsystems, +or even multiple subsystems (for example `approval-distribution` and `approval-voting`). + +A special kind of test objectives are performance regression tests for the CI pipeline. These should be sequences +of tests that check the performance characteristics (such as CPU usage, speed) of the subsystem under test in both +happy and negative scenarios (low bandwidth, network errors and low connectivity). + +### Reusable test components + +To faster write a new test objective you need to use some higher level wrappers and logic: `TestEnvironment`, +`TestConfiguration`, `TestAuthorities`, `NetworkEmulator`. To create the `TestEnvironment` you will +need to also build an `Overseer`, but that should be easy using the mockups for subsystems in`core::mock`. + +### Mocking + +Ideally we want to have a single mock implementation for subsystems that can be minimally configured to +be used in different tests. A good example is `runtime-api` which currently only responds to session information +requests based on static data. It can be easily extended to service other requests. diff --git a/polkadot/node/subsystem-bench/docker/docker-compose.yml b/polkadot/node/subsystem-bench/docker/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..fc5eb1f634e64a2c0a527ec7b3fd1cd25083c925 --- /dev/null +++ b/polkadot/node/subsystem-bench/docker/docker-compose.yml @@ -0,0 +1,35 @@ +services: + grafana: + image: grafana/grafana-enterprise:latest + container_name: grafana + restart: always + networks: + - subsystem-bench + ports: + - "3000:3000" + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + restart: always + networks: + - subsystem-bench + volumes: + - ./prometheus:/etc/prometheus + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "9090:9090" + - "9999:9999" + + pyroscope: + container_name: pyroscope + image: grafana/pyroscope:latest + restart: always + networks: + - subsystem-bench + ports: + - "4040:4040" + +networks: + subsystem-bench: diff --git a/polkadot/node/subsystem-bench/docker/prometheus/prometheus.yml b/polkadot/node/subsystem-bench/docker/prometheus/prometheus.yml new file mode 100644 index 0000000000000000000000000000000000000000..0bb25cfcb36c667b7609fcfc650ee81092baf28a --- /dev/null +++ b/polkadot/node/subsystem-bench/docker/prometheus/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 5s + +scrape_configs: + - job_name: "prometheus" + static_configs: + - targets: ["localhost:9090"] + - job_name: "subsystem-bench" + scrape_interval: 0s500ms + static_configs: + - targets: ['host.docker.internal:9999'] diff --git a/polkadot/node/subsystem-bench/examples/availability_read.yaml b/polkadot/node/subsystem-bench/examples/availability_read.yaml new file mode 100644 index 0000000000000000000000000000000000000000..311ea972141fc339367d30234cdf0c60911dd824 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/availability_read.yaml @@ -0,0 +1,57 @@ +TestConfiguration: +# Test 1 +- objective: !DataAvailabilityRead + fetch_from_backers: false + n_validators: 300 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + min_latency: + secs: 0 + nanos: 1000000 + max_latency: + secs: 0 + nanos: 100000000 + error: 3 + num_blocks: 3 + +# Test 2 +- objective: !DataAvailabilityRead + fetch_from_backers: false + n_validators: 500 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + min_latency: + secs: 0 + nanos: 1000000 + max_latency: + secs: 0 + nanos: 100000000 + error: 3 + num_blocks: 3 + +# Test 3 +- objective: !DataAvailabilityRead + fetch_from_backers: false + n_validators: 1000 + n_cores: 20 + min_pov_size: 5120 + max_pov_size: 5120 + peer_bandwidth: 52428800 + bandwidth: 52428800 + latency: + min_latency: + secs: 0 + nanos: 1000000 + max_latency: + secs: 0 + nanos: 100000000 + error: 3 + num_blocks: 3 diff --git a/polkadot/node/subsystem-bench/grafana/availability-read.json b/polkadot/node/subsystem-bench/grafana/availability-read.json new file mode 100644 index 0000000000000000000000000000000000000000..31c4ad3c795230402ec54d5558c24a3ab9664db4 --- /dev/null +++ b/polkadot/node/subsystem-bench/grafana/availability-read.json @@ -0,0 +1,1874 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Subsystem and test environment metrics", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 2, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": 60000, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 90, + "interval": "1s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "subsystem_benchmark_n_validators{}", + "instant": false, + "legendFormat": "n_vaidators", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "subsystem_benchmark_n_cores{}", + "hide": false, + "instant": false, + "legendFormat": "n_cores", + "range": true, + "refId": "B" + } + ], + "title": "Test configuration", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 31, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Overview", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "$data_source" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 57, + "interval": "1s", + "options": { + "legend": { + "calcs": [ + "mean", + "min", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.2", + "repeat": "nodename", + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "sum(rate(substrate_tasks_polling_duration_sum{}[2s])) by ($cpu_group_by)", + "interval": "", + "legendFormat": "{{task_group}}", + "range": true, + "refId": "A" + } + ], + "title": "All tasks CPU usage breakdown", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "$data_source" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 6 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 93, + "interval": "1s", + "options": { + "legend": { + "calcs": [ + "mean", + "min", + "max" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Mean", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.2", + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "increase(substrate_tasks_polling_duration_sum{task_group=\"availability-recovery\"}[6s])", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Availability subsystem CPU usage per block", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "$data_source" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 94, + "interval": "1s", + "options": { + "legend": { + "calcs": [ + "last" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.2", + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "sum(substrate_tasks_polling_duration_sum{}) by ($cpu_group_by)", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Total CPU burn", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "$data_source" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "area" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "dark-red", + "value": 6000 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 0, + "y": 40 + }, + "id": 95, + "interval": "1s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true, + "sortBy": "Last", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "10.0.2", + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "subsystem_benchmark_block_time", + "interval": "", + "legendFormat": "Instant block time", + "range": true, + "refId": "A" + } + ], + "title": "All candidates in block recovery time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 100, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 2, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 12, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 89, + "interval": "1s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "sum(rate(subsystem_benchmark_network_peer_total_bytes_received{}[5s]))", + "instant": false, + "legendFormat": "Received", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "sum(rate(subsystem_benchmark_network_peer_total_bytes_sent{}[5s]))", + "hide": false, + "instant": false, + "legendFormat": "Sent", + "range": true, + "refId": "B" + } + ], + "title": "Emulated network throughput ", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 12, + "x": 0, + "y": 52 + }, + "id": 88, + "interval": "1s", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "rate(subsystem_benchmark_network_peer_total_bytes_received{}[10s])", + "instant": false, + "legendFormat": "Received by {{peer}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "editorMode": "code", + "expr": "rate(subsystem_benchmark_network_peer_total_bytes_sent{}[10s])", + "hide": false, + "instant": false, + "legendFormat": "Sent by {{peer}}", + "range": true, + "refId": "B" + } + ], + "title": "Emulated peer throughput", + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateInferno", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 15, + "w": 12, + "x": 12, + "y": 52 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 92, + "interval": "1s", + "legend": { + "show": true + }, + "maxDataPoints": 1340, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": { + "decimals": 0 + }, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Inferno", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "bytes" + } + }, + "pluginVersion": "10.1.1", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(subsystem_benchmark_pov_size_bucket{}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Recovered PoV sizes", + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 0, + "transformations": [], + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "Number of erasure-encoded chunks of data belonging to candidate blocks. ", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "chunks/s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 43, + "interval": "1s", + "maxDataPoints": 1340, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(rate(polkadot_parachain_availability_recovery_chunk_requests_issued{}[10s]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Chunks requested", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Availability", + "transformations": [], + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 35, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Availability subystem metrics", + "type": "row" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateInferno", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 78 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 68, + "interval": "1s", + "legend": { + "show": true + }, + "maxDataPoints": 1340, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": { + "decimals": 0 + }, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Inferno", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.1.1", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(polkadot_parachain_availability_recovery_time_total_bucket{}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Time to recover a PoV", + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 0, + "transformations": [], + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateInferno", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 78 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 67, + "interval": "1s", + "legend": { + "show": true + }, + "maxDataPoints": 1340, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": { + "decimals": 0 + }, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Inferno", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.1.1", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(polkadot_parachain_availability_recovery_time_chunk_request_bucket{}[$__rate_interval])) by (le)", + "format": "heatmap", + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Chunk request duration", + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 0, + "transformations": [], + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "bitfields", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 88 + }, + "id": 85, + "interval": "1s", + "maxDataPoints": 1340, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "rate(polkadot_parachain_availability_recovery_bytes_total{}[30s])", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Bytes recovered", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Recovery throughtput", + "transformations": [], + "type": "timeseries" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateInferno", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 88 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 84, + "interval": "1s", + "legend": { + "show": true + }, + "maxDataPoints": 1340, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": { + "decimals": 0 + }, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Inferno", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.1.1", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(polkadot_parachain_availability_reencode_chunks_bucket{}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Re-encoding chunks timing", + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 0, + "transformations": [], + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "cards": {}, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateInferno", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "tsbuckets", + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 98 + }, + "heatmap": {}, + "hideZeroBuckets": true, + "highlightCards": true, + "id": 83, + "interval": "1s", + "legend": { + "show": true + }, + "maxDataPoints": 1340, + "options": { + "calculate": false, + "calculation": {}, + "cellGap": 2, + "cellValues": { + "decimals": 0 + }, + "color": { + "exponent": 0.5, + "fill": "#b4ff00", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Inferno", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "showValue": "never", + "tooltip": { + "show": true, + "yHistogram": true + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 0, + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.1.1", + "reverseYBuckets": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(increase(polkadot_parachain_availability_recovery_time_erasure_recovery_bucket{}[$__rate_interval])) by (le)", + "format": "heatmap", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "{{le}}", + "queryType": "randomWalk", + "refId": "B" + } + ], + "title": "Erasure recovery (no I/O)", + "tooltip": { + "show": true, + "showHistogram": true + }, + "tooltipDecimals": 0, + "transformations": [], + "type": "heatmap", + "xAxis": { + "show": true + }, + "yAxis": { + "decimals": 0, + "format": "s", + "logBase": 1, + "show": true + }, + "yBucketBound": "auto" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "description": "Number of erasure-encoded chunks of data belonging to candidate blocks. ", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "cps" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 108 + }, + "id": 86, + "interval": "1s", + "maxDataPoints": 1340, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.2.2", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(rate(polkadot_parachain_availability_recovery_recoveries_finished{}[1s]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Finished", + "queryType": "randomWalk", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${data_source}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "sum(rate(polkadot_parachain_availability_recovery_recovieries_started{}[1s]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Started", + "queryType": "randomWalk", + "refId": "A" + } + ], + "title": "Recoveries", + "transformations": [], + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 118 + }, + "id": 2, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Approval voting", + "type": "row" + } + ], + "refresh": false, + "schemaVersion": 38, + "style": "dark", + "tags": [ + "subsystem", + "benchmark" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "Prometheus", + "value": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "hide": 0, + "includeAll": false, + "label": "Source of data", + "multi": false, + "name": "data_source", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": { + "selected": true, + "text": "task_name", + "value": "task_name" + }, + "description": "Sum CPU usage by task name or task group.", + "hide": 0, + "includeAll": false, + "label": "Group CPU usage", + "multi": false, + "name": "cpu_group_by", + "options": [ + { + "selected": true, + "text": "task_name", + "value": "task_name" + }, + { + "selected": false, + "text": "task_group", + "value": "task_group" + } + ], + "query": "task_name, task_group", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "2023-11-28T13:05:32.794Z", + "to": "2023-11-28T13:06:56.173Z" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s" + ] + }, + "timezone": "utc", + "title": "Data Availability Read", + "uid": "asdadasd1", + "version": 58, + "weekStart": "" +} \ No newline at end of file diff --git a/polkadot/node/subsystem-bench/grafana/cpu-profiling.json b/polkadot/node/subsystem-bench/grafana/cpu-profiling.json new file mode 100644 index 0000000000000000000000000000000000000000..0d53a1b9365762f60154a28188d03aab1857ddb4 --- /dev/null +++ b/polkadot/node/subsystem-bench/grafana/cpu-profiling.json @@ -0,0 +1,70 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "bc3bc04f-85f9-464b-8ae3-fbe0949063f6" + }, + "gridPos": { + "h": 18, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "targets": [ + { + "datasource": { + "type": "grafana-pyroscope-datasource", + "uid": "bc3bc04f-85f9-464b-8ae3-fbe0949063f6" + }, + "groupBy": [], + "labelSelector": "{service_name=\"subsystem-bench\"}", + "profileTypeId": "process_cpu:cpu:nanoseconds:cpu:nanoseconds", + "queryType": "profile", + "refId": "A" + } + ], + "title": "CPU Profiling", + "type": "flamegraph" + } + ], + "refresh": "", + "schemaVersion": 38, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "CPU Profiling", + "uid": "c31191d5-fe2b-49e2-8b1c-1451f31d1628", + "version": 1, + "weekStart": "" + } diff --git a/polkadot/node/subsystem-bench/grafana/task-cpu-usage.json b/polkadot/node/subsystem-bench/grafana/task-cpu-usage.json new file mode 100644 index 0000000000000000000000000000000000000000..90763444abf195dd62379ac518e4473d04c12a04 --- /dev/null +++ b/polkadot/node/subsystem-bench/grafana/task-cpu-usage.json @@ -0,0 +1,755 @@ +{ + "annotations": { + "list": [ + { + "$$hashKey": "object:326", + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "name": "Annotations & Alerts", + "showIn": 0, + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + }, + { + "$$hashKey": "object:327", + "datasource": { + "uid": "$data_source" + }, + "enable": true, + "expr": "increase(${metric_namespace}_tasks_ended_total{reason=\"panic\", node=~\"${nodename}\"}[10m])", + "hide": true, + "iconColor": "rgba(255, 96, 96, 1)", + "limit": 100, + "name": "Task panics", + "rawQuery": "SELECT\n extract(epoch from time_column) AS time,\n text_column as text,\n tags_column as tags\nFROM\n metric_table\nWHERE\n $__timeFilter(time_column)\n", + "showIn": 0, + "step": "10m", + "tags": [], + "textFormat": "{{node}} - {{task_name}}", + "titleFormat": "Panic!", + "type": "tags" + }, + { + "$$hashKey": "object:621", + "datasource": { + "uid": "$data_source" + }, + "enable": true, + "expr": "changes(${metric_namespace}_process_start_time_seconds{node=~\"${nodename}\"}[10m])", + "hide": false, + "iconColor": "#8AB8FF", + "name": "Node reboots", + "showIn": 0, + "step": "10m", + "textFormat": "{{node}}", + "titleFormat": "Reboots" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 29, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Tasks", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 3, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 1 + }, + "hiddenSeries": false, + "id": 11, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sort": "avg", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "sum(rate(substrate_tasks_polling_duration_sum{}[$__rate_interval])) by (task_name)", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "CPU time spent on each task", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2721", + "format": "percentunit", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:2722", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 3, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 30, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "rate(substrate_tasks_polling_duration_count{}[$__rate_interval])", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Task polling rate per second", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2571", + "format": "cps", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:2572", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 43, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "hideEmpty": true, + "hideZero": false, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "increase(substrate_tasks_polling_duration_sum{}[$__rate_interval]) / increase(substrate_tasks_polling_duration_count{}[$__rate_interval])", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Average time it takes to call Future::poll()", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2571", + "format": "s", + "logBase": 1, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:2572", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 22 + }, + "hiddenSeries": false, + "id": 15, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": true, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "increase(substrate_tasks_spawned_total{}[$__rate_interval])", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Number of tasks started", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:771", + "format": "short", + "logBase": 10, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:772", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 28 + }, + "hiddenSeries": false, + "id": 2, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": true, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "substrate_tasks_spawned_total{} - sum(substrate_tasks_ended_total{}) without(reason)\n\n# Fallback if tasks_ended_total is null for that task\nor on(task_name) substrate_tasks_spawned_total{}", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Number of tasks running", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:919", + "format": "short", + "logBase": 1, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:920", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "e56e7dd2-a992-4eec-aa96-e47b21c9020b" + }, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 34 + }, + "hiddenSeries": false, + "id": 7, + "interval": "1s", + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "hideEmpty": true, + "hideZero": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "10.1.1", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "nodename", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": true, + "targets": [ + { + "datasource": { + "uid": "$data_source" + }, + "editorMode": "code", + "expr": "irate(substrate_tasks_polling_duration_bucket{le=\"+Inf\"}[$__rate_interval])\n - ignoring(le)\n irate(substrate_tasks_polling_duration_bucket{le=\"1.024\"}[$__rate_interval]) > 0", + "interval": "", + "legendFormat": "{{task_name}}", + "range": true, + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Number of calls to `Future::poll` that took more than one second", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "cumulative" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:3040", + "format": "cps", + "label": "Calls to `Future::poll`/second", + "logBase": 1, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:3041", + "format": "short", + "logBase": 1, + "show": false + } + ], + "yaxis": { + "align": false + } + }, + { + "collapsed": false, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 40 + }, + "id": 27, + "panels": [], + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Unbounded Channels", + "type": "row" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Substrate Service Tasks with substrate prefix", + "uid": "S7sc-M_Gk", + "version": 17, + "weekStart": "" + } \ No newline at end of file diff --git a/polkadot/node/subsystem-bench/src/availability/cli.rs b/polkadot/node/subsystem-bench/src/availability/cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..65df8c1552aa8266497eb2738cc562f656050b68 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/availability/cli.rs @@ -0,0 +1,37 @@ +// 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 serde::{Deserialize, Serialize}; + +#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq)] +#[value(rename_all = "kebab-case")] +#[non_exhaustive] +pub enum NetworkEmulation { + Ideal, + Healthy, + Degraded, +} + +#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct DataAvailabilityReadOptions { + #[clap(short, long, default_value_t = false)] + /// Turbo boost AD Read by fetching the full availability datafrom backers first. Saves CPU as + /// we don't need to re-construct from chunks. Tipically this is only faster if nodes have + /// enough bandwidth. + pub fetch_from_backers: bool, +} diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..7c81b9313659771889f52ceb063089a26fc079c7 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -0,0 +1,339 @@ +// 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 itertools::Itertools; +use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; + +use crate::TestEnvironment; +use polkadot_node_subsystem::{Overseer, OverseerConnector, SpawnGlue}; +use polkadot_node_subsystem_test_helpers::derive_erasure_chunks_with_proofs_and_root; +use polkadot_overseer::Handle as OverseerHandle; +use sc_network::request_responses::ProtocolConfig; + +use colored::Colorize; + +use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; +use polkadot_node_metrics::metrics::Metrics; + +use polkadot_availability_recovery::AvailabilityRecoverySubsystem; + +use crate::GENESIS_HASH; +use parity_scale_codec::Encode; +use polkadot_node_network_protocol::request_response::{IncomingRequest, ReqProtocolNames}; +use polkadot_node_primitives::{BlockData, PoV}; +use polkadot_node_subsystem::messages::{AllMessages, AvailabilityRecoveryMessage}; + +use crate::core::{ + environment::TestEnvironmentDependencies, + mock::{ + av_store, + network_bridge::{self, MockNetworkBridgeTx, NetworkAvailabilityState}, + runtime_api, MockAvailabilityStore, MockRuntimeApi, + }, +}; + +use super::core::{configuration::TestConfiguration, mock::dummy_builder, network::*}; + +const LOG_TARGET: &str = "subsystem-bench::availability"; + +use polkadot_node_primitives::{AvailableData, ErasureChunk}; + +use super::{cli::TestObjective, core::mock::AlwaysSupportsParachains}; +use polkadot_node_subsystem_test_helpers::mock::new_block_import_info; +use polkadot_primitives::{ + CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData, PersistedValidationData, +}; +use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash}; +use sc_service::SpawnTaskHandle; + +mod cli; +pub use cli::{DataAvailabilityReadOptions, NetworkEmulation}; + +fn build_overseer( + spawn_task_handle: SpawnTaskHandle, + runtime_api: MockRuntimeApi, + av_store: MockAvailabilityStore, + network_bridge: MockNetworkBridgeTx, + availability_recovery: AvailabilityRecoverySubsystem, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { + let overseer_connector = OverseerConnector::with_event_capacity(64000); + let dummy = dummy_builder!(spawn_task_handle); + let builder = dummy + .replace_runtime_api(|_| runtime_api) + .replace_availability_store(|_| av_store) + .replace_network_bridge_tx(|_| network_bridge) + .replace_availability_recovery(|_| availability_recovery); + + let (overseer, raw_handle) = + builder.build_with_connector(overseer_connector).expect("Should not fail"); + + (overseer, OverseerHandle::new(raw_handle)) +} + +/// Takes a test configuration and uses it to creates the `TestEnvironment`. +pub fn prepare_test( + config: TestConfiguration, + state: &mut TestState, +) -> (TestEnvironment, ProtocolConfig) { + prepare_test_inner(config, state, TestEnvironmentDependencies::default()) +} + +fn prepare_test_inner( + config: TestConfiguration, + state: &mut TestState, + dependencies: TestEnvironmentDependencies, +) -> (TestEnvironment, ProtocolConfig) { + // Generate test authorities. + let test_authorities = config.generate_authorities(); + + let runtime_api = runtime_api::MockRuntimeApi::new(config.clone(), test_authorities.clone()); + + let av_store = + av_store::MockAvailabilityStore::new(state.chunks.clone(), state.candidate_hashes.clone()); + + let availability_state = NetworkAvailabilityState { + candidate_hashes: state.candidate_hashes.clone(), + available_data: state.available_data.clone(), + chunks: state.chunks.clone(), + }; + + let network = NetworkEmulator::new(&config, &dependencies, &test_authorities); + + let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new( + config.clone(), + availability_state, + network.clone(), + ); + + let use_fast_path = match &state.config().objective { + TestObjective::DataAvailabilityRead(options) => options.fetch_from_backers, + _ => panic!("Unexpected objective"), + }; + + let (collation_req_receiver, req_cfg) = + IncomingRequest::get_config_receiver(&ReqProtocolNames::new(GENESIS_HASH, None)); + + let subsystem = if use_fast_path { + AvailabilityRecoverySubsystem::with_fast_path( + collation_req_receiver, + Metrics::try_register(&dependencies.registry).unwrap(), + ) + } else { + AvailabilityRecoverySubsystem::with_chunks_only( + collation_req_receiver, + Metrics::try_register(&dependencies.registry).unwrap(), + ) + }; + + let (overseer, overseer_handle) = build_overseer( + dependencies.task_manager.spawn_handle(), + runtime_api, + av_store, + network_bridge_tx, + subsystem, + ); + + (TestEnvironment::new(dependencies, config, network, overseer, overseer_handle), req_cfg) +} + +#[derive(Clone)] +pub struct TestState { + // Full test configuration + config: TestConfiguration, + // A cycle iterator on all PoV sizes used in the test. + pov_sizes: Cycle>, + // Generated candidate receipts to be used in the test + candidates: Cycle>, + // Map from pov size to candidate index + pov_size_to_candidate: HashMap, + // Map from generated candidate hashes to candidate index in `available_data` + // and `chunks`. + candidate_hashes: HashMap, + // Per candidate index receipts. + candidate_receipt_templates: Vec, + // Per candidate index `AvailableData` + available_data: Vec, + // Per candiadte index chunks + chunks: Vec>, +} + +impl TestState { + fn config(&self) -> &TestConfiguration { + &self.config + } + + pub fn next_candidate(&mut self) -> Option { + let candidate = self.candidates.next(); + let candidate_hash = candidate.as_ref().unwrap().hash(); + gum::trace!(target: LOG_TARGET, "Next candidate selected {:?}", candidate_hash); + candidate + } + + /// Generate candidates to be used in the test. + fn generate_candidates(&mut self) { + let count = self.config.n_cores * self.config.num_blocks; + gum::info!(target: LOG_TARGET,"{}", format!("Pre-generating {} candidates.", count).bright_blue()); + + // Generate all candidates + self.candidates = (0..count) + .map(|index| { + let pov_size = self.pov_sizes.next().expect("This is a cycle; qed"); + let candidate_index = *self + .pov_size_to_candidate + .get(&pov_size) + .expect("pov_size always exists; qed"); + let mut candidate_receipt = + self.candidate_receipt_templates[candidate_index].clone(); + + // Make it unique. + candidate_receipt.descriptor.relay_parent = Hash::from_low_u64_be(index as u64); + // Store the new candidate in the state + self.candidate_hashes.insert(candidate_receipt.hash(), candidate_index); + + gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_receipt.hash(), "new candidate"); + + candidate_receipt + }) + .collect::>() + .into_iter() + .cycle(); + } + + pub fn new(config: &TestConfiguration) -> Self { + let config = config.clone(); + + let mut chunks = Vec::new(); + let mut available_data = Vec::new(); + let mut candidate_receipt_templates = Vec::new(); + let mut pov_size_to_candidate = HashMap::new(); + + // we use it for all candidates. + let persisted_validation_data = PersistedValidationData { + parent_head: HeadData(vec![7, 8, 9]), + relay_parent_number: Default::default(), + max_pov_size: 1024, + relay_parent_storage_root: Default::default(), + }; + + // For each unique pov we create a candidate receipt. + for (index, pov_size) in config.pov_sizes().iter().cloned().unique().enumerate() { + gum::info!(target: LOG_TARGET, index, pov_size, "{}", "Generating template candidate".bright_blue()); + + let mut candidate_receipt = dummy_candidate_receipt(dummy_hash()); + let pov = PoV { block_data: BlockData(vec![index as u8; pov_size]) }; + + let new_available_data = AvailableData { + validation_data: persisted_validation_data.clone(), + pov: Arc::new(pov), + }; + + let (new_chunks, erasure_root) = derive_erasure_chunks_with_proofs_and_root( + config.n_validators, + &new_available_data, + |_, _| {}, + ); + + candidate_receipt.descriptor.erasure_root = erasure_root; + + chunks.push(new_chunks); + available_data.push(new_available_data); + pov_size_to_candidate.insert(pov_size, index); + candidate_receipt_templates.push(candidate_receipt); + } + + let pov_sizes = config.pov_sizes().to_owned(); + let pov_sizes = pov_sizes.into_iter().cycle(); + gum::info!(target: LOG_TARGET, "{}","Created test environment.".bright_blue()); + + let mut _self = Self { + config, + available_data, + candidate_receipt_templates, + chunks, + pov_size_to_candidate, + pov_sizes, + candidate_hashes: HashMap::new(), + candidates: Vec::new().into_iter().cycle(), + }; + + _self.generate_candidates(); + _self + } +} + +pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: TestState) { + let config = env.config().clone(); + + env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await; + + let start_marker = Instant::now(); + let mut batch = FuturesUnordered::new(); + let mut availability_bytes = 0u128; + + env.metrics().set_n_validators(config.n_validators); + env.metrics().set_n_cores(config.n_cores); + + for block_num in 0..env.config().num_blocks { + 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(); + for candidate_num in 0..config.n_cores as u64 { + let candidate = + state.next_candidate().expect("We always send up to n_cores*num_blocks; qed"); + let (tx, rx) = oneshot::channel(); + batch.push(rx); + + let message = AllMessages::AvailabilityRecovery( + AvailabilityRecoveryMessage::RecoverAvailableData( + candidate.clone(), + 1, + Some(GroupIndex( + candidate_num as u32 % (std::cmp::max(5, config.n_cores) / 5) as u32, + )), + tx, + ), + ); + env.send_message(message).await; + } + + gum::info!("{}", format!("{} recoveries pending", batch.len()).bright_black()); + while let Some(completed) = batch.next().await { + let available_data = completed.unwrap().unwrap(); + env.metrics().on_pov_size(available_data.encoded_size()); + availability_bytes += available_data.encoded_size() as u128; + } + + let block_time = Instant::now().sub(block_start_ts).as_millis() as u64; + env.metrics().set_block_time(block_time); + gum::info!("All work for block completed in {}", format!("{:?}ms", block_time).cyan()); + } + + let duration: u128 = start_marker.elapsed().as_millis(); + let availability_bytes = availability_bytes / 1024; + gum::info!("All blocks processed in {}", format!("{:?}ms", duration).cyan()); + gum::info!( + "Throughput: {}", + format!("{} KiB/block", availability_bytes / env.config().num_blocks as u128).bright_red() + ); + gum::info!( + "Block time: {}", + format!("{} ms", start_marker.elapsed().as_millis() / env.config().num_blocks as u128) + .red() + ); + + gum::info!("{}", &env); + env.stop().await; +} diff --git a/polkadot/node/subsystem-bench/src/cli.rs b/polkadot/node/subsystem-bench/src/cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..3352f33a3503bcdb53cd4ba5f0bc789b9d4cf159 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/cli.rs @@ -0,0 +1,60 @@ +// 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, +} + +/// Define the supported benchmarks targets +#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)] +#[command(rename_all = "kebab-case")] +pub enum TestObjective { + /// Benchmark availability recovery strategies. + DataAvailabilityRead(DataAvailabilityReadOptions), + /// 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 new file mode 100644 index 0000000000000000000000000000000000000000..164addb51900656a278dba2eafc19a7ef558037b --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/configuration.rs @@ -0,0 +1,262 @@ +// 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 . +// +//! Test configuration definition and helpers. +use super::*; +use keyring::Keyring; +use std::{path::Path, time::Duration}; + +pub use crate::cli::TestObjective; +use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId}; +use rand::{distributions::Uniform, prelude::Distribution, thread_rng}; +use serde::{Deserialize, Serialize}; + +pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize { + random_uniform_sample(min_pov_size, max_pov_size) +} + +fn random_uniform_sample + From>(min_value: T, max_value: T) -> T { + Uniform::from(min_value.into()..=max_value.into()) + .sample(&mut thread_rng()) + .into() +} + +/// Peer response latency configuration. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct PeerLatency { + /// Min latency for `NetworkAction` completion. + pub min_latency: Duration, + /// Max latency or `NetworkAction` completion. + pub max_latency: Duration, +} + +// Default PoV size in KiB. +fn default_pov_size() -> usize { + 5120 +} + +// Default bandwidth in bytes +fn default_bandwidth() -> usize { + 52428800 +} + +// Default connectivity percentage +fn default_connectivity() -> usize { + 100 +} + +/// The test input parameters +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestConfiguration { + /// The test objective + pub objective: TestObjective, + /// Number of validators + pub n_validators: usize, + /// Number of cores + pub n_cores: usize, + /// The min PoV size + #[serde(default = "default_pov_size")] + pub min_pov_size: usize, + /// The max PoV size, + #[serde(default = "default_pov_size")] + pub max_pov_size: usize, + /// Randomly sampled pov_sizes + #[serde(skip)] + pov_sizes: Vec, + /// The amount of bandiwdth remote validators have. + #[serde(default = "default_bandwidth")] + pub peer_bandwidth: usize, + /// The amount of bandiwdth our node has. + #[serde(default = "default_bandwidth")] + pub bandwidth: usize, + /// Optional peer emulation latency + #[serde(default)] + pub latency: Option, + /// Error probability, applies to sending messages to the emulated network peers + #[serde(default)] + pub error: usize, + /// Connectivity ratio, the percentage of peers we are not connected to, but ar part of + /// the topology. + #[serde(default = "default_connectivity")] + pub connectivity: usize, + /// Number of blocks to run the test for + pub num_blocks: usize, +} + +fn generate_pov_sizes(count: usize, min_kib: usize, max_kib: usize) -> Vec { + (0..count).map(|_| random_pov_size(min_kib * 1024, max_kib * 1024)).collect() +} + +#[derive(Serialize, Deserialize)] +pub struct TestSequence { + #[serde(rename(serialize = "TestConfiguration", deserialize = "TestConfiguration"))] + test_configurations: Vec, +} + +impl TestSequence { + pub fn into_vec(self) -> Vec { + self.test_configurations + .into_iter() + .map(|mut config| { + config.pov_sizes = + generate_pov_sizes(config.n_cores, config.min_pov_size, config.max_pov_size); + config + }) + .collect() + } +} + +impl TestSequence { + pub fn new_from_file(path: &Path) -> std::io::Result { + let string = String::from_utf8(std::fs::read(path)?).expect("File is valid UTF8"); + Ok(serde_yaml::from_str(&string).expect("File is valid test sequence YA")) + } +} + +/// Helper struct for authority related state. +#[derive(Clone)] +pub struct TestAuthorities { + pub keyrings: Vec, + pub validator_public: Vec, + pub validator_authority_id: Vec, +} + +impl TestConfiguration { + #[allow(unused)] + pub fn write_to_disk(&self) { + // Serialize a slice of configurations + let yaml = serde_yaml::to_string(&TestSequence { test_configurations: vec![self.clone()] }) + .unwrap(); + std::fs::write("last_test.yaml", yaml).unwrap(); + } + + pub fn pov_sizes(&self) -> &[usize] { + &self.pov_sizes + } + + /// Generates the authority keys we need for the network emulation. + pub fn generate_authorities(&self) -> TestAuthorities { + let keyrings = (0..self.n_validators) + .map(|peer_index| Keyring::new(format!("Node{}", peer_index))) + .collect::>(); + + // Generate `AuthorityDiscoveryId`` for each peer + let validator_public: Vec = keyrings + .iter() + .map(|keyring: &Keyring| keyring.clone().public().into()) + .collect::>(); + + let validator_authority_id: Vec = keyrings + .iter() + .map(|keyring| keyring.clone().public().into()) + .collect::>(); + + TestAuthorities { keyrings, 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, + 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, + error: 0, + 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, + 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 { + min_latency: Duration::from_millis(1), + max_latency: Duration::from_millis(100), + }), + error: 3, + num_blocks, + min_pov_size, + max_pov_size, + connectivity: 95, + } + } + + 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, + 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 { + min_latency: Duration::from_millis(10), + max_latency: Duration::from_millis(500), + }), + error: 33, + num_blocks, + min_pov_size, + max_pov_size, + connectivity: 67, + } + } +} + +/// Produce a randomized duration between `min` and `max`. +pub fn random_latency(maybe_peer_latency: Option<&PeerLatency>) -> Option { + maybe_peer_latency.map(|peer_latency| { + Uniform::from(peer_latency.min_latency..=peer_latency.max_latency).sample(&mut thread_rng()) + }) +} + +/// Generate a random error based on `probability`. +/// `probability` should be a number between 0 and 100. +pub fn random_error(probability: usize) -> bool { + Uniform::from(0..=99).sample(&mut thread_rng()) < probability +} diff --git a/polkadot/node/subsystem-bench/src/core/display.rs b/polkadot/node/subsystem-bench/src/core/display.rs new file mode 100644 index 0000000000000000000000000000000000000000..d600cc484c14a45361c19621213dbf666475a778 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/display.rs @@ -0,0 +1,191 @@ +// 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 . +// +//! 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 colored::Colorize; +use prometheus::{ + proto::{MetricFamily, MetricType}, + Registry, +}; +use std::fmt::Display; + +#[derive(Default)] +pub struct MetricCollection(Vec); + +impl From> for MetricCollection { + fn from(metrics: Vec) -> Self { + MetricCollection(metrics) + } +} + +impl MetricCollection { + pub fn all(&self) -> &Vec { + &self.0 + } + + /// Sums up all metrics with the given name in the collection + pub fn sum_by(&self, name: &str) -> f64 { + self.all() + .iter() + .filter(|metric| metric.name == name) + .map(|metric| metric.value) + .sum() + } + + pub fn subset_with_label_value(&self, label_name: &str, label_value: &str) -> MetricCollection { + self.0 + .iter() + .filter_map(|metric| { + if let Some(index) = metric.label_names.iter().position(|label| label == label_name) + { + if Some(&String::from(label_value)) == metric.label_values.get(index) { + Some(metric.clone()) + } else { + None + } + } else { + None + } + }) + .collect::>() + .into() + } +} + +impl Display for MetricCollection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f)?; + let metrics = self.all(); + for metric in metrics { + writeln!(f, "{}", metric)?; + } + Ok(()) + } +} +#[derive(Debug, Clone)] +pub struct TestMetric { + name: String, + label_names: Vec, + label_values: Vec, + value: f64, +} + +impl Display for TestMetric { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "({} = {}) [{:?}, {:?}]", + self.name.cyan(), + format!("{}", self.value).white(), + self.label_names, + self.label_values + ) + } +} + +// Returns `false` if metric should be skipped. +fn check_metric_family(mf: &MetricFamily) -> bool { + if mf.get_metric().is_empty() { + gum::error!(target: LOG_TARGET, "MetricFamily has no metrics: {:?}", mf); + return false + } + if mf.get_name().is_empty() { + gum::error!(target: LOG_TARGET, "MetricFamily has no name: {:?}", mf); + return false + } + + true +} + +pub fn parse_metrics(registry: &Registry) -> MetricCollection { + let metric_families = registry.gather(); + let mut test_metrics = Vec::new(); + for mf in metric_families { + if !check_metric_family(&mf) { + continue + } + + let name: String = mf.get_name().into(); + let metric_type = mf.get_field_type(); + for m in mf.get_metric() { + let (label_names, label_values): (Vec, Vec) = m + .get_label() + .iter() + .map(|pair| (String::from(pair.get_name()), String::from(pair.get_value()))) + .unzip(); + + match metric_type { + MetricType::COUNTER => { + test_metrics.push(TestMetric { + name: name.clone(), + label_names, + label_values, + value: m.get_counter().get_value(), + }); + }, + MetricType::GAUGE => { + test_metrics.push(TestMetric { + name: name.clone(), + label_names, + label_values, + value: m.get_gauge().get_value(), + }); + }, + MetricType::HISTOGRAM => { + let h = m.get_histogram(); + let h_name = name.clone() + "_sum"; + test_metrics.push(TestMetric { + name: h_name, + label_names: label_names.clone(), + label_values: label_values.clone(), + value: h.get_sample_sum(), + }); + + let h_name = name.clone() + "_count"; + test_metrics.push(TestMetric { + name: h_name, + label_names, + label_values, + value: h.get_sample_sum(), + }); + }, + MetricType::SUMMARY => { + unimplemented!(); + }, + MetricType::UNTYPED => { + unimplemented!(); + }, + } + } + } + test_metrics.into() +} + +pub fn display_configuration(test_config: &TestConfiguration) { + gum::info!( + "{}, {}, {}, {}, {}", + format!("n_validators = {}", test_config.n_validators).blue(), + format!("n_cores = {}", test_config.n_cores).blue(), + format!("pov_size = {} - {}", test_config.min_pov_size, test_config.max_pov_size) + .bright_black(), + format!("error = {}", test_config.error).bright_black(), + format!("latency = {:?}", test_config.latency).bright_black(), + ); +} diff --git a/polkadot/node/subsystem-bench/src/core/environment.rs b/polkadot/node/subsystem-bench/src/core/environment.rs new file mode 100644 index 0000000000000000000000000000000000000000..247596474078ef73a74f1762c30b56b52ce4417f --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/environment.rs @@ -0,0 +1,333 @@ +// 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 . +//! Test environment implementation +use crate::{ + core::{mock::AlwaysSupportsParachains, network::NetworkEmulator}, + TestConfiguration, +}; +use colored::Colorize; +use core::time::Duration; +use futures::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 sc_network::peer_store::LOG_TARGET; +use sc_service::{SpawnTaskHandle, TaskManager}; +use std::{ + fmt::Display, + net::{Ipv4Addr, SocketAddr}, +}; +use tokio::runtime::Handle; + +const MIB: f64 = 1024.0 * 1024.0; + +/// Test environment/configuration metrics +#[derive(Clone)] +pub struct TestEnvironmentMetrics { + /// Number of bytes sent per peer. + n_validators: Gauge, + /// Number of received sent per peer. + n_cores: Gauge, + /// PoV size + pov_size: Histogram, + /// Current block + current_block: Gauge, + /// Current block + block_time: Gauge, +} + +impl TestEnvironmentMetrics { + pub fn new(registry: &Registry) -> Result { + let mut buckets = prometheus::exponential_buckets(16384.0, 2.0, 9) + .expect("arguments are always valid; qed"); + buckets.extend(vec![5.0 * MIB, 6.0 * MIB, 7.0 * MIB, 8.0 * MIB, 9.0 * MIB, 10.0 * MIB]); + + Ok(Self { + n_validators: prometheus::register( + Gauge::new( + "subsystem_benchmark_n_validators", + "Total number of validators in the test", + )?, + registry, + )?, + n_cores: prometheus::register( + Gauge::new( + "subsystem_benchmark_n_cores", + "Number of cores we fetch availability for each block", + )?, + registry, + )?, + current_block: prometheus::register( + Gauge::new("subsystem_benchmark_current_block", "The current test block")?, + registry, + )?, + block_time: prometheus::register( + Gauge::new("subsystem_benchmark_block_time", "The time it takes for the target subsystems(s) to complete all the requests in a block")?, + registry, + )?, + pov_size: prometheus::register( + Histogram::with_opts( + prometheus::HistogramOpts::new( + "subsystem_benchmark_pov_size", + "The compressed size of the proof of validity of a candidate", + ) + .buckets(buckets), + )?, + registry, + )?, + }) + } + + pub fn set_n_validators(&self, n_validators: usize) { + self.n_validators.set(n_validators as u64); + } + + pub fn set_n_cores(&self, n_cores: usize) { + self.n_cores.set(n_cores as u64); + } + + pub fn set_current_block(&self, current_block: usize) { + self.current_block.set(current_block as u64); + } + + pub fn set_block_time(&self, block_time_ms: u64) { + self.block_time.set(block_time_ms); + } + + pub fn on_pov_size(&self, pov_size: usize) { + self.pov_size.observe(pov_size as f64); + } +} + +fn new_runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_multi_thread() + .thread_name("subsystem-bench") + .enable_all() + .thread_stack_size(3 * 1024 * 1024) + .build() + .unwrap() +} + +/// Wrapper for dependencies +pub struct TestEnvironmentDependencies { + pub registry: Registry, + pub task_manager: TaskManager, + pub runtime: tokio::runtime::Runtime, +} + +impl Default for TestEnvironmentDependencies { + fn default() -> Self { + let runtime = new_runtime(); + let registry = Registry::new(); + let task_manager: TaskManager = + TaskManager::new(runtime.handle().clone(), Some(®istry)).unwrap(); + + Self { runtime, registry, task_manager } + } +} + +// A dummy genesis hash +pub const GENESIS_HASH: Hash = Hash::repeat_byte(0xff); + +// We use this to bail out sending messages to the subsystem if it is overloaded such that +// the time of flight is breaches 5s. +// This should eventually be a test parameter. +const MAX_TIME_OF_FLIGHT: Duration = Duration::from_millis(5000); + +/// The test environment is the high level wrapper of all things required to test +/// a certain subsystem. +/// +/// ## Mockups +/// The overseer is passed in during construction and it can host an arbitrary number of +/// real subsystems instances and the corresponding mocked instances such that the real +/// subsystems can get their messages answered. +/// +/// As the subsystem's performance depends on network connectivity, the test environment +/// emulates validator nodes on the network, see `NetworkEmulator`. The network emulation +/// is configurable in terms of peer bandwidth, latency and connection error rate using +/// uniform distribution sampling. +/// +/// +/// ## Usage +/// `TestEnvironment` is used in tests to send `Overseer` messages or signals to the subsystem +/// under test. +/// +/// ## Collecting test metrics +/// +/// ### Prometheus +/// A prometheus endpoint is exposed while the test is running. A local Prometheus instance +/// can scrape it every 1s and a Grafana dashboard is the preferred way of visualizing +/// the performance characteristics of the subsystem. +/// +/// ### CLI +/// A subset of the Prometheus metrics are printed at the end of the test. +pub struct TestEnvironment { + /// Test dependencies + dependencies: TestEnvironmentDependencies, + /// A runtime handle + runtime_handle: tokio::runtime::Handle, + /// A handle to the lovely overseer + overseer_handle: OverseerHandle, + /// The test configuration. + config: TestConfiguration, + /// A handle to the network emulator. + network: NetworkEmulator, + /// Configuration/env metrics + metrics: TestEnvironmentMetrics, +} + +impl TestEnvironment { + /// Create a new test environment + pub fn new( + dependencies: TestEnvironmentDependencies, + config: TestConfiguration, + network: NetworkEmulator, + overseer: Overseer, AlwaysSupportsParachains>, + overseer_handle: OverseerHandle, + ) -> Self { + let metrics = TestEnvironmentMetrics::new(&dependencies.registry) + .expect("Metrics need to be registered"); + + let spawn_handle = dependencies.task_manager.spawn_handle(); + spawn_handle.spawn_blocking("overseer", "overseer", overseer.run().boxed()); + + let registry_clone = dependencies.registry.clone(); + dependencies.task_manager.spawn_handle().spawn_blocking( + "prometheus", + "test-environment", + async move { + prometheus_endpoint::init_prometheus( + SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), 9999), + registry_clone, + ) + .await + .unwrap(); + }, + ); + + TestEnvironment { + runtime_handle: dependencies.runtime.handle().clone(), + dependencies, + overseer_handle, + config, + network, + metrics, + } + } + + pub fn config(&self) -> &TestConfiguration { + &self.config + } + + pub fn network(&self) -> &NetworkEmulator { + &self.network + } + + pub fn registry(&self) -> &Registry { + &self.dependencies.registry + } + + pub fn metrics(&self) -> &TestEnvironmentMetrics { + &self.metrics + } + + pub fn runtime(&self) -> Handle { + self.runtime_handle.clone() + } + + // Send a message to the subsystem under test environment. + pub async fn send_message(&mut self, msg: AllMessages) { + self.overseer_handle + .send_msg(msg, LOG_TARGET) + .timeout(MAX_TIME_OF_FLIGHT) + .await + .unwrap_or_else(|| { + panic!("{}ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis()) + }); + } + + // Send an `ActiveLeavesUpdate` signal to all subsystems under test. + pub async fn import_block(&mut self, block: BlockInfo) { + self.overseer_handle + .block_imported(block) + .timeout(MAX_TIME_OF_FLIGHT) + .await + .unwrap_or_else(|| { + panic!("{}ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis()) + }); + } + + // Stop overseer and subsystems. + pub async fn stop(&mut self) { + self.overseer_handle.stop().await; + } +} + +impl Display for TestEnvironment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let stats = self.network().stats(); + + writeln!(f, "\n")?; + writeln!( + f, + "Total received from network: {}", + format!( + "{} MiB", + stats + .iter() + .enumerate() + .map(|(_index, stats)| stats.tx_bytes_total as u128) + .sum::() / (1024 * 1024) + ) + .cyan() + )?; + writeln!( + f, + "Total sent to network: {}", + format!("{} KiB", stats[0].tx_bytes_total / (1024)).cyan() + )?; + + let test_metrics = super::display::parse_metrics(self.registry()); + let subsystem_cpu_metrics = + test_metrics.subset_with_label_value("task_group", "availability-recovery"); + let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); + writeln!(f, "Total subsystem CPU usage {}", format!("{:.2}s", total_cpu).bright_purple())?; + writeln!( + f, + "CPU usage per block {}", + format!("{:.2}s", total_cpu / self.config().num_blocks as f64).bright_purple() + )?; + + 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"); + writeln!( + f, + "Total test environment CPU usage {}", + format!("{:.2}s", total_cpu).bright_purple() + )?; + writeln!( + f, + "CPU usage per block {}", + format!("{:.2}s", total_cpu / self.config().num_blocks as f64).bright_purple() + ) + } +} diff --git a/polkadot/node/subsystem-bench/src/core/keyring.rs b/polkadot/node/subsystem-bench/src/core/keyring.rs new file mode 100644 index 0000000000000000000000000000000000000000..68e78069a918b602f2e5d03845e00b9614bd3ba6 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/keyring.rs @@ -0,0 +1,39 @@ +// 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 sp_core::{ + sr25519::{Pair, Public}, + Pair as PairT, +}; +/// Set of test accounts. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Keyring { + name: String, +} + +impl Keyring { + pub fn new(name: String) -> Keyring { + Self { name } + } + + pub fn pair(self) -> Pair { + Pair::from_string(&format!("//{}", self.name), None).expect("input is always good; qed") + } + + pub fn public(self) -> Public { + self.pair().public() + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mock/av_store.rs b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs new file mode 100644 index 0000000000000000000000000000000000000000..a471230f1b3f0e5be27494988f04590ff4aaa78e --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs @@ -0,0 +1,137 @@ +// 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 . +//! +//! A generic av store subsystem mockup suitable to be used in benchmarks. + +use parity_scale_codec::Encode; +use polkadot_primitives::CandidateHash; + +use std::collections::HashMap; + +use futures::{channel::oneshot, FutureExt}; + +use polkadot_node_primitives::ErasureChunk; + +use polkadot_node_subsystem::{ + messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError, +}; + +use polkadot_node_subsystem_types::OverseerSignal; + +pub struct AvailabilityStoreState { + candidate_hashes: HashMap, + chunks: Vec>, +} + +const LOG_TARGET: &str = "subsystem-bench::av-store-mock"; + +/// A mock of the availability store subsystem. This one also generates all the +/// candidates that a +pub struct MockAvailabilityStore { + state: AvailabilityStoreState, +} + +impl MockAvailabilityStore { + pub fn new( + chunks: Vec>, + candidate_hashes: HashMap, + ) -> MockAvailabilityStore { + Self { state: AvailabilityStoreState { chunks, candidate_hashes } } + } + + async fn respond_to_query_all_request( + &self, + candidate_hash: CandidateHash, + send_chunk: impl Fn(usize) -> bool, + tx: oneshot::Sender>, + ) { + let candidate_index = self + .state + .candidate_hashes + .get(&candidate_hash) + .expect("candidate was generated previously; qed"); + gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index"); + + let v = self + .state + .chunks + .get(*candidate_index) + .unwrap() + .iter() + .filter(|c| send_chunk(c.index.0 as usize)) + .cloned() + .collect(); + + let _ = tx.send(v); + } +} + +#[overseer::subsystem(AvailabilityStore, error=SubsystemError, prefix=self::overseer)] +impl MockAvailabilityStore { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "test-environment", future } + } +} + +#[overseer::contextbounds(AvailabilityStore, prefix = self::overseer)] +impl MockAvailabilityStore { + async fn run(self, mut ctx: Context) { + gum::debug!(target: LOG_TARGET, "Subsystem running"); + loop { + let msg = ctx.recv().await.expect("Overseer never fails us"); + + match msg { + orchestra::FromOrchestra::Signal(signal) => + if signal == OverseerSignal::Conclude { + return + }, + orchestra::FromOrchestra::Communication { msg } => match msg { + AvailabilityStoreMessage::QueryAvailableData(candidate_hash, tx) => { + gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryAvailableData"); + + // We never have the full available data. + let _ = tx.send(None); + }, + AvailabilityStoreMessage::QueryAllChunks(candidate_hash, tx) => { + // We always have our own chunk. + gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryAllChunks"); + self.respond_to_query_all_request(candidate_hash, |index| index == 0, tx) + .await; + }, + AvailabilityStoreMessage::QueryChunkSize(candidate_hash, tx) => { + gum::debug!(target: LOG_TARGET, candidate_hash = ?candidate_hash, "Responding to QueryChunkSize"); + + let candidate_index = self + .state + .candidate_hashes + .get(&candidate_hash) + .expect("candidate was generated previously; qed"); + gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index"); + + let chunk_size = + self.state.chunks.get(*candidate_index).unwrap()[0].encoded_size(); + let _ = tx.send(Some(chunk_size)); + }, + _ => { + unimplemented!("Unexpected av-store message") + }, + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mock/dummy.rs b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs new file mode 100644 index 0000000000000000000000000000000000000000..0628368a49c08af69077ba558b5dc8b34f8b57bd --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs @@ -0,0 +1,98 @@ +// 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 . +//! Dummy subsystem mocks. +use paste::paste; + +use futures::FutureExt; +use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; +use std::time::Duration; +use tokio::time::sleep; + +const LOG_TARGET: &str = "subsystem-bench::mockery"; + +macro_rules! mock { + // Just query by relay parent + ($subsystem_name:ident) => { + paste! { + pub struct [] {} + #[overseer::subsystem($subsystem_name, error=SubsystemError, prefix=self::overseer)] + impl [] { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + // The name will appear in substrate CPU task metrics as `task_group`.` + SpawnedSubsystem { name: "test-environment", future } + } + } + + #[overseer::contextbounds($subsystem_name, prefix = self::overseer)] + impl [] { + async fn run(self, mut ctx: Context) { + let mut count_total_msg = 0; + loop { + futures::select!{ + msg = ctx.recv().fuse() => { + match msg.unwrap() { + orchestra::FromOrchestra::Signal(signal) => { + match signal { + polkadot_node_subsystem_types::OverseerSignal::Conclude => {return}, + _ => {} + } + }, + orchestra::FromOrchestra::Communication { msg } => { + gum::debug!(target: LOG_TARGET, msg = ?msg, "mocked subsystem received message"); + } + } + + count_total_msg +=1; + } + _ = sleep(Duration::from_secs(6)).fuse() => { + if count_total_msg > 0 { + gum::trace!(target: LOG_TARGET, "Subsystem {} processed {} messages since last time", stringify!($subsystem_name), count_total_msg); + } + count_total_msg = 0; + } + } + } + } + } + } + }; +} + +mock!(AvailabilityStore); +mock!(StatementDistribution); +mock!(BitfieldSigning); +mock!(BitfieldDistribution); +mock!(Provisioner); +mock!(NetworkBridgeRx); +mock!(CollationGeneration); +mock!(CollatorProtocol); +mock!(GossipSupport); +mock!(DisputeDistribution); +mock!(DisputeCoordinator); +mock!(ProspectiveParachains); +mock!(PvfChecker); +mock!(CandidateBacking); +mock!(AvailabilityDistribution); +mock!(CandidateValidation); +mock!(AvailabilityRecovery); +mock!(NetworkBridgeTx); +mock!(ChainApi); +mock!(ChainSelection); +mock!(ApprovalVoting); +mock!(ApprovalDistribution); +mock!(RuntimeApi); diff --git a/polkadot/node/subsystem-bench/src/core/mock/mod.rs b/polkadot/node/subsystem-bench/src/core/mock/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..76fd581c3fb65df4b5d5c02b5314801ada4087d4 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mock/mod.rs @@ -0,0 +1,76 @@ +// 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 polkadot_node_subsystem::HeadSupportsParachains; +use polkadot_node_subsystem_types::Hash; + +pub mod av_store; +pub mod dummy; +pub mod network_bridge; +pub mod runtime_api; + +pub use av_store::*; +pub use runtime_api::*; + +pub struct AlwaysSupportsParachains {} +#[async_trait::async_trait] +impl HeadSupportsParachains for AlwaysSupportsParachains { + async fn head_supports_parachains(&self, _head: &Hash) -> bool { + true + } +} + +// An orchestra with dummy subsystems +macro_rules! dummy_builder { + ($spawn_task_handle: ident) => {{ + use super::core::mock::dummy::*; + + // Initialize a mock overseer. + // All subsystem except approval_voting and approval_distribution are mock subsystems. + Overseer::builder() + .approval_voting(MockApprovalVoting {}) + .approval_distribution(MockApprovalDistribution {}) + .availability_recovery(MockAvailabilityRecovery {}) + .candidate_validation(MockCandidateValidation {}) + .chain_api(MockChainApi {}) + .chain_selection(MockChainSelection {}) + .dispute_coordinator(MockDisputeCoordinator {}) + .runtime_api(MockRuntimeApi {}) + .network_bridge_tx(MockNetworkBridgeTx {}) + .availability_distribution(MockAvailabilityDistribution {}) + .availability_store(MockAvailabilityStore {}) + .pvf_checker(MockPvfChecker {}) + .candidate_backing(MockCandidateBacking {}) + .statement_distribution(MockStatementDistribution {}) + .bitfield_signing(MockBitfieldSigning {}) + .bitfield_distribution(MockBitfieldDistribution {}) + .provisioner(MockProvisioner {}) + .network_bridge_rx(MockNetworkBridgeRx {}) + .collation_generation(MockCollationGeneration {}) + .collator_protocol(MockCollatorProtocol {}) + .gossip_support(MockGossipSupport {}) + .dispute_distribution(MockDisputeDistribution {}) + .prospective_parachains(MockProspectiveParachains {}) + .activation_external_listeners(Default::default()) + .span_per_active_leaf(Default::default()) + .active_leaves(Default::default()) + .metrics(Default::default()) + .supports_parachains(AlwaysSupportsParachains {}) + .spawner(SpawnGlue($spawn_task_handle)) + }}; +} + +pub(crate) use dummy_builder; diff --git a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs new file mode 100644 index 0000000000000000000000000000000000000000..b106b832011a81e69c7ea9258b9f4d72cf71ae84 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs @@ -0,0 +1,323 @@ +// 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 . +//! +//! A generic av store subsystem mockup suitable to be used in benchmarks. + +use futures::Future; +use parity_scale_codec::Encode; +use polkadot_node_subsystem_types::OverseerSignal; +use std::{collections::HashMap, pin::Pin}; + +use futures::FutureExt; + +use polkadot_node_primitives::{AvailableData, ErasureChunk}; + +use polkadot_primitives::CandidateHash; +use sc_network::{OutboundFailure, RequestFailure}; + +use polkadot_node_subsystem::{ + messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, +}; + +use polkadot_node_network_protocol::request_response::{ + self as req_res, v1::ChunkResponse, Requests, +}; +use polkadot_primitives::AuthorityDiscoveryId; + +use crate::core::{ + configuration::{random_error, random_latency, TestConfiguration}, + network::{NetworkAction, NetworkEmulator, RateLimit}, +}; + +/// The availability store state of all emulated peers. +/// The network bridge tx mock will respond to requests as if the request is being serviced +/// by a remote peer on the network +pub struct NetworkAvailabilityState { + pub candidate_hashes: HashMap, + pub available_data: Vec, + pub chunks: Vec>, +} + +const LOG_TARGET: &str = "subsystem-bench::network-bridge-tx-mock"; + +/// A mock of the network bridge tx subsystem. +pub struct MockNetworkBridgeTx { + /// The test configurationg + config: TestConfiguration, + /// The network availability state + availabilty: NetworkAvailabilityState, + /// A network emulator instance + network: NetworkEmulator, +} + +impl MockNetworkBridgeTx { + pub fn new( + config: TestConfiguration, + availabilty: NetworkAvailabilityState, + network: NetworkEmulator, + ) -> MockNetworkBridgeTx { + Self { config, availabilty, network } + } + + fn not_connected_response( + &self, + authority_discovery_id: &AuthorityDiscoveryId, + future: Pin + Send>>, + ) -> NetworkAction { + // The network action will send the error after a random delay expires. + return NetworkAction::new( + authority_discovery_id.clone(), + future, + 0, + // Generate a random latency based on configuration. + random_latency(self.config.latency.as_ref()), + ) + } + /// Returns an `NetworkAction` corresponding to the peer sending the response. If + /// the peer is connected, the error is sent with a randomized latency as defined in + /// configuration. + fn respond_to_send_request( + &mut self, + request: Requests, + ingress_tx: &mut tokio::sync::mpsc::UnboundedSender, + ) -> NetworkAction { + let ingress_tx = ingress_tx.clone(); + + match request { + Requests::ChunkFetchingV1(outgoing_request) => { + let authority_discovery_id = match outgoing_request.peer { + req_res::Recipient::Authority(authority_discovery_id) => authority_discovery_id, + _ => unimplemented!("Peer recipient not supported yet"), + }; + // Account our sent request bytes. + self.network.peer_stats(0).inc_sent(outgoing_request.payload.encoded_size()); + + // If peer is disconnected return an error + if !self.network.is_peer_connected(&authority_discovery_id) { + // We always send `NotConnected` error and we ignore `IfDisconnected` value in + // the caller. + let future = async move { + let _ = outgoing_request + .pending_response + .send(Err(RequestFailure::NotConnected)); + } + .boxed(); + return self.not_connected_response(&authority_discovery_id, future) + } + + // Account for remote received request bytes. + self.network + .peer_stats_by_id(&authority_discovery_id) + .inc_received(outgoing_request.payload.encoded_size()); + + let validator_index: usize = outgoing_request.payload.index.0 as usize; + let candidate_hash = outgoing_request.payload.candidate_hash; + + let candidate_index = self + .availabilty + .candidate_hashes + .get(&candidate_hash) + .expect("candidate was generated previously; qed"); + gum::warn!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index"); + + let chunk: ChunkResponse = self.availabilty.chunks.get(*candidate_index).unwrap() + [validator_index] + .clone() + .into(); + let mut size = chunk.encoded_size(); + + let response = if random_error(self.config.error) { + // Error will not account to any bandwidth used. + size = 0; + Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)) + } else { + Ok(req_res::v1::ChunkFetchingResponse::from(Some(chunk)).encode()) + }; + + let authority_discovery_id_clone = authority_discovery_id.clone(); + + let future = async move { + let _ = outgoing_request.pending_response.send(response); + } + .boxed(); + + let future_wrapper = async move { + // Forward the response to the ingress channel of our node. + // On receive side we apply our node receiving rate limit. + let action = + NetworkAction::new(authority_discovery_id_clone, future, size, None); + ingress_tx.send(action).unwrap(); + } + .boxed(); + + NetworkAction::new( + authority_discovery_id, + future_wrapper, + size, + // Generate a random latency based on configuration. + random_latency(self.config.latency.as_ref()), + ) + }, + Requests::AvailableDataFetchingV1(outgoing_request) => { + let candidate_hash = outgoing_request.payload.candidate_hash; + let candidate_index = self + .availabilty + .candidate_hashes + .get(&candidate_hash) + .expect("candidate was generated previously; qed"); + gum::debug!(target: LOG_TARGET, ?candidate_hash, candidate_index, "Candidate mapped to index"); + + let authority_discovery_id = match outgoing_request.peer { + req_res::Recipient::Authority(authority_discovery_id) => authority_discovery_id, + _ => unimplemented!("Peer recipient not supported yet"), + }; + + // Account our sent request bytes. + self.network.peer_stats(0).inc_sent(outgoing_request.payload.encoded_size()); + + // If peer is disconnected return an error + if !self.network.is_peer_connected(&authority_discovery_id) { + let future = async move { + let _ = outgoing_request + .pending_response + .send(Err(RequestFailure::NotConnected)); + } + .boxed(); + return self.not_connected_response(&authority_discovery_id, future) + } + + // Account for remote received request bytes. + self.network + .peer_stats_by_id(&authority_discovery_id) + .inc_received(outgoing_request.payload.encoded_size()); + + let available_data = + self.availabilty.available_data.get(*candidate_index).unwrap().clone(); + + let size = available_data.encoded_size(); + + let response = if random_error(self.config.error) { + Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)) + } else { + Ok(req_res::v1::AvailableDataFetchingResponse::from(Some(available_data)) + .encode()) + }; + + let future = async move { + let _ = outgoing_request.pending_response.send(response); + } + .boxed(); + + let authority_discovery_id_clone = authority_discovery_id.clone(); + + let future_wrapper = async move { + // Forward the response to the ingress channel of our node. + // On receive side we apply our node receiving rate limit. + let action = + NetworkAction::new(authority_discovery_id_clone, future, size, None); + ingress_tx.send(action).unwrap(); + } + .boxed(); + + NetworkAction::new( + authority_discovery_id, + future_wrapper, + size, + // Generate a random latency based on configuration. + random_latency(self.config.latency.as_ref()), + ) + }, + _ => panic!("received an unexpected request"), + } + } +} + +#[overseer::subsystem(NetworkBridgeTx, error=SubsystemError, prefix=self::overseer)] +impl MockNetworkBridgeTx { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "test-environment", future } + } +} + +#[overseer::contextbounds(NetworkBridgeTx, prefix = self::overseer)] +impl MockNetworkBridgeTx { + async fn run(mut self, mut ctx: Context) { + let (mut ingress_tx, mut ingress_rx) = + tokio::sync::mpsc::unbounded_channel::(); + + // Initialize our node bandwidth limits. + let mut rx_limiter = RateLimit::new(10, self.config.bandwidth); + + let our_network = self.network.clone(); + + // This task will handle node messages receipt from the simulated network. + ctx.spawn_blocking( + "network-receive", + async move { + while let Some(action) = ingress_rx.recv().await { + let size = action.size(); + + // account for our node receiving the data. + our_network.inc_received(size); + rx_limiter.reap(size).await; + action.run().await; + } + } + .boxed(), + ) + .expect("We never fail to spawn tasks"); + + // Main subsystem loop. + loop { + let msg = ctx.recv().await.expect("Overseer never fails us"); + + match msg { + orchestra::FromOrchestra::Signal(signal) => + if signal == OverseerSignal::Conclude { + return + }, + orchestra::FromOrchestra::Communication { msg } => match msg { + NetworkBridgeTxMessage::SendRequests(requests, _if_disconnected) => { + for request in requests { + gum::debug!(target: LOG_TARGET, request = ?request, "Processing request"); + self.network.inc_sent(request_size(&request)); + let action = self.respond_to_send_request(request, &mut ingress_tx); + + // Will account for our node sending the request over the emulated + // network. + self.network.submit_peer_action(action.peer(), action); + } + }, + _ => { + unimplemented!("Unexpected network bridge message") + }, + }, + } + } + } +} + +// A helper to determine the request payload size. +fn request_size(request: &Requests) -> usize { + match request { + Requests::ChunkFetchingV1(outgoing_request) => outgoing_request.payload.encoded_size(), + Requests::AvailableDataFetchingV1(outgoing_request) => + outgoing_request.payload.encoded_size(), + _ => unimplemented!("received an unexpected request"), + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs new file mode 100644 index 0000000000000000000000000000000000000000..d664ebead3cc416c502d32c0a1922b49b408eb29 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs @@ -0,0 +1,110 @@ +// 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 . +//! +//! A generic runtime api subsystem mockup suitable to be used in benchmarks. + +use polkadot_primitives::{GroupIndex, IndexedVec, SessionInfo, ValidatorIndex}; + +use polkadot_node_subsystem::{ + messages::{RuntimeApiMessage, RuntimeApiRequest}, + overseer, SpawnedSubsystem, SubsystemError, +}; +use polkadot_node_subsystem_types::OverseerSignal; + +use crate::core::configuration::{TestAuthorities, TestConfiguration}; +use futures::FutureExt; + +const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock"; + +pub struct RuntimeApiState { + authorities: TestAuthorities, +} + +pub struct MockRuntimeApi { + state: RuntimeApiState, + config: TestConfiguration, +} + +impl MockRuntimeApi { + pub fn new(config: TestConfiguration, authorities: TestAuthorities) -> MockRuntimeApi { + Self { state: RuntimeApiState { authorities }, 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(5).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], + } + } +} + +#[overseer::subsystem(RuntimeApi, error=SubsystemError, prefix=self::overseer)] +impl MockRuntimeApi { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "test-environment", future } + } +} + +#[overseer::contextbounds(RuntimeApi, prefix = self::overseer)] +impl MockRuntimeApi { + async fn run(self, mut ctx: Context) { + loop { + let msg = ctx.recv().await.expect("Overseer never fails us"); + + match msg { + orchestra::FromOrchestra::Signal(signal) => + if signal == OverseerSignal::Conclude { + return + }, + orchestra::FromOrchestra::Communication { msg } => { + gum::debug!(target: LOG_TARGET, msg=?msg, "recv message"); + + match msg { + RuntimeApiMessage::Request( + _request, + RuntimeApiRequest::SessionInfo(_session_index, sender), + ) => { + let _ = sender.send(Ok(Some(self.session_info()))); + }, + // Long term TODO: implement more as needed. + _ => { + unimplemented!("Unexpected runtime-api message") + }, + } + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mod.rs b/polkadot/node/subsystem-bench/src/core/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..282788d143b44a9a2444533f1eda756e0385c0a2 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/mod.rs @@ -0,0 +1,24 @@ +// 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 . + +const LOG_TARGET: &str = "subsystem-bench::core"; + +pub mod configuration; +pub mod display; +pub mod environment; +pub mod keyring; +pub mod mock; +pub mod network; diff --git a/polkadot/node/subsystem-bench/src/core/network.rs b/polkadot/node/subsystem-bench/src/core/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..c4e20b421d342fc50a0fa36fb7c8ab6d959a5fff --- /dev/null +++ b/polkadot/node/subsystem-bench/src/core/network.rs @@ -0,0 +1,485 @@ +// 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::{ + configuration::{TestAuthorities, TestConfiguration}, + environment::TestEnvironmentDependencies, + *, +}; +use colored::Colorize; +use polkadot_primitives::AuthorityDiscoveryId; +use prometheus_endpoint::U64; +use rand::{seq::SliceRandom, thread_rng}; +use sc_service::SpawnTaskHandle; +use std::{ + collections::HashMap, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + time::{Duration, Instant}, +}; +use tokio::sync::mpsc::UnboundedSender; + +// An emulated node egress traffic rate_limiter. +#[derive(Debug)] +pub struct RateLimit { + // How often we refill credits in buckets + tick_rate: usize, + // Total ticks + total_ticks: usize, + // Max refill per tick + max_refill: usize, + // Available credit. We allow for bursts over 1/tick_rate of `cps` budget, but we + // account it by negative credit. + credits: isize, + // When last refilled. + last_refill: Instant, +} + +impl RateLimit { + // Create a new `RateLimit` from a `cps` (credits per second) budget and + // `tick_rate`. + pub fn new(tick_rate: usize, cps: usize) -> Self { + // Compute how much refill for each tick + let max_refill = cps / tick_rate; + RateLimit { + tick_rate, + total_ticks: 0, + max_refill, + // A fresh start + credits: max_refill as isize, + last_refill: Instant::now(), + } + } + + pub async fn refill(&mut self) { + // If this is called to early, we need to sleep until next tick. + let now = Instant::now(); + let next_tick_delta = + (self.last_refill + Duration::from_millis(1000 / self.tick_rate as u64)) - now; + + // Sleep until next tick. + if !next_tick_delta.is_zero() { + gum::trace!(target: LOG_TARGET, "need to sleep {}ms", next_tick_delta.as_millis()); + tokio::time::sleep(next_tick_delta).await; + } + + self.total_ticks += 1; + self.credits += self.max_refill as isize; + self.last_refill = Instant::now(); + } + + // Reap credits from the bucket. + // Blocks if credits budged goes negative during call. + pub async fn reap(&mut self, amount: usize) { + self.credits -= amount as isize; + + if self.credits >= 0 { + return + } + + while self.credits < 0 { + gum::trace!(target: LOG_TARGET, "Before refill: {:?}", &self); + self.refill().await; + gum::trace!(target: LOG_TARGET, "After refill: {:?}", &self); + } + } +} + +#[cfg(test)] +mod tests { + use std::time::Instant; + + use super::RateLimit; + + #[tokio::test] + async fn test_expected_rate() { + let tick_rate = 200; + let budget = 1_000_000; + // rate must not exceeed 100 credits per second + let mut rate_limiter = RateLimit::new(tick_rate, budget); + let mut total_sent = 0usize; + let start = Instant::now(); + + let mut reap_amount = 0; + while rate_limiter.total_ticks < tick_rate { + reap_amount += 1; + reap_amount %= 100; + + rate_limiter.reap(reap_amount).await; + total_sent += reap_amount; + } + + let end = Instant::now(); + + println!("duration: {}", (end - start).as_millis()); + + // Allow up to `budget/max_refill` error tolerance + let lower_bound = budget as u128 * ((end - start).as_millis() / 1000u128); + let upper_bound = budget as u128 * + ((end - start).as_millis() / 1000u128 + rate_limiter.max_refill as u128); + assert!(total_sent as u128 >= lower_bound); + assert!(total_sent as u128 <= upper_bound); + } +} + +// A network peer emulator. It spawns a task that accepts `NetworkActions` and +// executes them with a configurable delay and bandwidth constraints. Tipically +// these actions wrap a future that performs a channel send to the subsystem(s) under test. +#[derive(Clone)] +struct PeerEmulator { + // The queue of requests waiting to be served by the emulator + actions_tx: UnboundedSender, +} + +impl PeerEmulator { + pub fn new( + bandwidth: usize, + spawn_task_handle: SpawnTaskHandle, + stats: Arc, + ) -> Self { + let (actions_tx, mut actions_rx) = tokio::sync::mpsc::unbounded_channel(); + + spawn_task_handle + .clone() + .spawn("peer-emulator", "test-environment", async move { + // Rate limit peer send. + let mut rate_limiter = RateLimit::new(10, bandwidth); + loop { + let stats_clone = stats.clone(); + let maybe_action: Option = actions_rx.recv().await; + if let Some(action) = maybe_action { + let size = action.size(); + rate_limiter.reap(size).await; + if let Some(latency) = action.latency { + spawn_task_handle.spawn( + "peer-emulator-latency", + "test-environment", + async move { + tokio::time::sleep(latency).await; + action.run().await; + stats_clone.inc_sent(size); + }, + ) + } else { + action.run().await; + stats_clone.inc_sent(size); + } + } else { + break + } + } + }); + + Self { actions_tx } + } + + // Queue a send request from the emulated peer. + pub fn send(&mut self, action: NetworkAction) { + self.actions_tx.send(action).expect("peer emulator task lives"); + } +} + +pub type ActionFuture = std::pin::Pin + std::marker::Send>>; +/// An network action to be completed by the emulator task. +pub struct NetworkAction { + // The function that performs the action + run: ActionFuture, + // The payload size that we simulate sending/receiving from a peer + size: usize, + // Peer which should run the action. + peer: AuthorityDiscoveryId, + // The amount of time to delay the polling `run` + latency: Option, +} + +unsafe impl Send for NetworkAction {} + +/// Book keeping of sent and received bytes. +pub struct PeerEmulatorStats { + rx_bytes_total: AtomicU64, + tx_bytes_total: AtomicU64, + metrics: Metrics, + peer_index: usize, +} + +impl PeerEmulatorStats { + pub(crate) fn new(peer_index: usize, metrics: Metrics) -> Self { + Self { + metrics, + rx_bytes_total: AtomicU64::from(0), + tx_bytes_total: AtomicU64::from(0), + peer_index, + } + } + + pub fn inc_sent(&self, bytes: usize) { + self.tx_bytes_total.fetch_add(bytes as u64, Ordering::Relaxed); + self.metrics.on_peer_sent(self.peer_index, bytes); + } + + pub fn inc_received(&self, bytes: usize) { + self.rx_bytes_total.fetch_add(bytes as u64, Ordering::Relaxed); + self.metrics.on_peer_received(self.peer_index, bytes); + } + + pub fn sent(&self) -> u64 { + self.tx_bytes_total.load(Ordering::Relaxed) + } + + pub fn received(&self) -> u64 { + self.rx_bytes_total.load(Ordering::Relaxed) + } +} + +#[derive(Debug, Default)] +pub struct PeerStats { + pub rx_bytes_total: u64, + pub tx_bytes_total: u64, +} +impl NetworkAction { + pub fn new( + peer: AuthorityDiscoveryId, + run: ActionFuture, + size: usize, + latency: Option, + ) -> Self { + Self { run, size, peer, latency } + } + + pub fn size(&self) -> usize { + self.size + } + + pub async fn run(self) { + self.run.await; + } + + pub fn peer(&self) -> AuthorityDiscoveryId { + self.peer.clone() + } +} + +/// The state of a peer on the emulated network. +#[derive(Clone)] +enum Peer { + Connected(PeerEmulator), + Disconnected(PeerEmulator), +} + +impl Peer { + pub fn disconnect(&mut self) { + let new_self = match self { + Peer::Connected(peer) => Peer::Disconnected(peer.clone()), + _ => return, + }; + *self = new_self; + } + + pub fn is_connected(&self) -> bool { + matches!(self, Peer::Connected(_)) + } + + pub fn emulator(&mut self) -> &mut PeerEmulator { + match self { + Peer::Connected(ref mut emulator) => emulator, + Peer::Disconnected(ref mut emulator) => emulator, + } + } +} + +/// Mocks the network bridge and an arbitrary number of connected peer nodes. +/// Implements network latency, bandwidth and connection errors. +#[derive(Clone)] +pub struct NetworkEmulator { + // Per peer network emulation. + peers: Vec, + /// Per peer stats. + stats: Vec>, + /// Each emulated peer is a validator. + validator_authority_ids: HashMap, +} + +impl NetworkEmulator { + pub fn new( + config: &TestConfiguration, + dependencies: &TestEnvironmentDependencies, + authorities: &TestAuthorities, + ) -> Self { + let n_peers = config.n_validators; + gum::info!(target: LOG_TARGET, "{}",format!("Initializing emulation for a {} peer network.", n_peers).bright_blue()); + gum::info!(target: LOG_TARGET, "{}",format!("connectivity {}%, error {}%", config.connectivity, config.error).bright_black()); + + let metrics = + Metrics::new(&dependencies.registry).expect("Metrics always register succesfully"); + let mut validator_authority_id_mapping = HashMap::new(); + + // Create a `PeerEmulator` for each peer. + let (stats, mut peers): (_, Vec<_>) = (0..n_peers) + .zip(authorities.validator_authority_id.clone()) + .map(|(peer_index, authority_id)| { + validator_authority_id_mapping.insert(authority_id, peer_index); + let stats = Arc::new(PeerEmulatorStats::new(peer_index, metrics.clone())); + ( + stats.clone(), + Peer::Connected(PeerEmulator::new( + config.peer_bandwidth, + dependencies.task_manager.spawn_handle(), + stats, + )), + ) + }) + .unzip(); + + let connected_count = config.n_validators as f64 / (100.0 / config.connectivity as f64); + + let (_connected, to_disconnect) = + peers.partial_shuffle(&mut thread_rng(), connected_count as usize); + + for peer in to_disconnect { + peer.disconnect(); + } + + gum::info!(target: LOG_TARGET, "{}",format!("Network created, connected validator count {}", connected_count).bright_black()); + + Self { peers, stats, validator_authority_ids: validator_authority_id_mapping } + } + + pub fn is_peer_connected(&self, peer: &AuthorityDiscoveryId) -> bool { + self.peer(peer).is_connected() + } + + pub fn submit_peer_action(&mut self, peer: AuthorityDiscoveryId, action: NetworkAction) { + let index = self + .validator_authority_ids + .get(&peer) + .expect("all test authorities are valid; qed"); + + let peer = self.peers.get_mut(*index).expect("We just retrieved the index above; qed"); + + // Only actions of size 0 are allowed on disconnected peers. + // Typically this are delayed error response sends. + if action.size() > 0 && !peer.is_connected() { + gum::warn!(target: LOG_TARGET, peer_index = index, "Attempted to send data from a disconnected peer, operation ignored"); + return + } + + peer.emulator().send(action); + } + + // Returns the sent/received stats for `peer_index`. + pub fn peer_stats(&self, peer_index: usize) -> Arc { + self.stats[peer_index].clone() + } + + // Helper to get peer index by `AuthorityDiscoveryId` + fn peer_index(&self, peer: &AuthorityDiscoveryId) -> usize { + *self + .validator_authority_ids + .get(peer) + .expect("all test authorities are valid; qed") + } + + // Return the Peer entry for a given `AuthorityDiscoveryId`. + fn peer(&self, peer: &AuthorityDiscoveryId) -> &Peer { + &self.peers[self.peer_index(peer)] + } + // Returns the sent/received stats for `peer`. + pub fn peer_stats_by_id(&mut self, peer: &AuthorityDiscoveryId) -> Arc { + let peer_index = self.peer_index(peer); + + self.stats[peer_index].clone() + } + + // Returns the sent/received stats for all peers. + pub fn stats(&self) -> Vec { + let r = self + .stats + .iter() + .map(|stats| PeerStats { + rx_bytes_total: stats.received(), + tx_bytes_total: stats.sent(), + }) + .collect::>(); + r + } + + // Increment bytes sent by our node (the node that contains the subsystem under test) + pub fn inc_sent(&self, bytes: usize) { + // Our node always is peer 0. + self.peer_stats(0).inc_sent(bytes); + } + + // Increment bytes received by our node (the node that contains the subsystem under test) + pub fn inc_received(&self, bytes: usize) { + // Our node always is peer 0. + self.peer_stats(0).inc_received(bytes); + } +} + +use polkadot_node_subsystem_util::metrics::prometheus::{ + self, CounterVec, Opts, PrometheusError, Registry, +}; + +/// Emulated network metrics. +#[derive(Clone)] +pub(crate) struct Metrics { + /// Number of bytes sent per peer. + peer_total_sent: CounterVec, + /// Number of received sent per peer. + peer_total_received: CounterVec, +} + +impl Metrics { + pub fn new(registry: &Registry) -> Result { + Ok(Self { + peer_total_sent: prometheus::register( + CounterVec::new( + Opts::new( + "subsystem_benchmark_network_peer_total_bytes_sent", + "Total number of bytes a peer has sent.", + ), + &["peer"], + )?, + registry, + )?, + peer_total_received: prometheus::register( + CounterVec::new( + Opts::new( + "subsystem_benchmark_network_peer_total_bytes_received", + "Total number of bytes a peer has received.", + ), + &["peer"], + )?, + registry, + )?, + }) + } + + /// Increment total sent for a peer. + pub fn on_peer_sent(&self, peer_index: usize, bytes: usize) { + self.peer_total_sent + .with_label_values(vec![format!("node{}", peer_index).as_str()].as_slice()) + .inc_by(bytes as u64); + } + + /// Increment total receioved for a peer. + pub fn on_peer_received(&self, peer_index: usize, bytes: usize) { + self.peer_total_received + .with_label_values(vec![format!("node{}", peer_index).as_str()].as_slice()) + .inc_by(bytes as u64); + } +} diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs new file mode 100644 index 0000000000000000000000000000000000000000..29b62b27855a2f4867540ccd5dc19d1fa72cd5bd --- /dev/null +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -0,0 +1,215 @@ +// 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 . + +//! A tool for running subsystem benchmark tests designed for development and +//! CI regression testing. +use clap::Parser; +use color_eyre::eyre; +use pyroscope::PyroscopeAgent; +use pyroscope_pprofrs::{pprof_backend, PprofConfig}; + +use colored::Colorize; +use std::{path::Path, time::Duration}; + +pub(crate) mod availability; +pub(crate) mod cli; +pub(crate) mod core; + +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) +} + +fn le_5000(s: &str) -> Result { + number_range(s, 0, 5000) +} + +#[derive(Debug, Parser)] +#[allow(missing_docs)] +struct BenchCli { + #[arg(long, value_enum, ignore_case = true, default_value_t = NetworkEmulation::Ideal)] + /// The type of network to be emulated + pub network: NetworkEmulation, + + #[clap(flatten)] + pub standard_configuration: cli::StandardTestOptions, + + #[clap(short, long)] + /// The bandwidth of simulated remote peers in KiB + pub peer_bandwidth: Option, + + #[clap(short, long)] + /// The bandwidth of our simulated node in KiB + pub bandwidth: Option, + + #[clap(long, value_parser=le_100)] + /// Simulated conection error ratio [0-100]. + pub peer_error: Option, + + #[clap(long, value_parser=le_5000)] + /// Minimum remote peer latency in milliseconds [0-5000]. + pub peer_min_latency: Option, + + #[clap(long, value_parser=le_5000)] + /// Maximum remote peer latency in milliseconds [0-5000]. + pub peer_max_latency: Option, + + #[clap(long, default_value_t = false)] + /// Enable CPU Profiling with Pyroscope + pub profile: bool, + + #[clap(long, requires = "profile", default_value_t = String::from("http://localhost:4040"))] + /// Pyroscope Server URL + pub pyroscope_url: String, + + #[clap(long, requires = "profile", default_value_t = 113)] + /// Pyroscope Sample Rate + pub pyroscope_sample_rate: u32, + + #[command(subcommand)] + pub objective: cli::TestObjective, +} + +impl BenchCli { + fn launch(self) -> eyre::Result<()> { + let agent_running = if self.profile { + let agent = PyroscopeAgent::builder(self.pyroscope_url.as_str(), "subsystem-bench") + .backend(pprof_backend(PprofConfig::new().sample_rate(self.pyroscope_sample_rate))) + .build()?; + + Some(agent.start()?) + } else { + None + }; + + let configuration = self.standard_configuration; + 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!("{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); + display_configuration(&test_config); + + 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)); + } + return Ok(()) + }, + TestObjective::DataAvailabilityRead(ref _options) => match self.network { + NetworkEmulation::Healthy => TestConfiguration::healthy_network( + self.objective, + configuration.num_blocks, + configuration.n_validators, + configuration.n_cores, + configuration.min_pov_size, + configuration.max_pov_size, + ), + NetworkEmulation::Degraded => TestConfiguration::degraded_network( + self.objective, + configuration.num_blocks, + configuration.n_validators, + configuration.n_cores, + configuration.min_pov_size, + configuration.max_pov_size, + ), + NetworkEmulation::Ideal => TestConfiguration::ideal_network( + self.objective, + configuration.num_blocks, + configuration.n_validators, + configuration.n_cores, + configuration.min_pov_size, + configuration.max_pov_size, + ), + }, + }; + + let mut latency_config = test_config.latency.clone().unwrap_or_default(); + + if let Some(latency) = self.peer_min_latency { + latency_config.min_latency = Duration::from_millis(latency); + } + + if let Some(latency) = self.peer_max_latency { + latency_config.max_latency = Duration::from_millis(latency); + } + + if let Some(error) = self.peer_error { + test_config.error = error; + } + + 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); + // test_config.write_to_disk(); + env.runtime() + .block_on(availability::benchmark_availability_read(&mut env, state)); + + if let Some(agent_running) = agent_running { + let agent_ready = agent_running.stop()?; + agent_ready.shutdown(); + } + + Ok(()) + } +} + +fn main() -> eyre::Result<()> { + color_eyre::install()?; + env_logger::builder() + .filter(Some("hyper"), log::LevelFilter::Info) + // Avoid `Terminating due to subsystem exit subsystem` warnings + .filter(Some("polkadot_overseer"), log::LevelFilter::Error) + .filter(None, log::LevelFilter::Info) + // .filter(None, log::LevelFilter::Trace) + .try_init() + .unwrap(); + + let cli: BenchCli = BenchCli::parse(); + cli.launch()?; + Ok(()) +} diff --git a/polkadot/node/subsystem-test-helpers/Cargo.toml b/polkadot/node/subsystem-test-helpers/Cargo.toml index 9087ca11f5d22ee5307fbc3877c88e1be5a72e27..d0be9af4ed639a70d3fbba59cba523d04e857072 100644 --- a/polkadot/node/subsystem-test-helpers/Cargo.toml +++ b/polkadot/node/subsystem-test-helpers/Cargo.toml @@ -7,13 +7,19 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" parking_lot = "0.12.0" polkadot-node-subsystem = { path = "../subsystem" } +polkadot-erasure-coding = { path = "../../erasure-coding" } polkadot-node-subsystem-util = { path = "../subsystem-util" } polkadot-primitives = { path = "../../primitives" } +polkadot-node-primitives = { path = "../primitives" } + sc-client-api = { path = "../../../substrate/client/api" } sc-utils = { path = "../../../substrate/client/utils" } sp-core = { path = "../../../substrate/primitives/core" } diff --git a/polkadot/node/subsystem-test-helpers/src/lib.rs b/polkadot/node/subsystem-test-helpers/src/lib.rs index 3f92513498c4129f418690946c2a2e2ac85605cc..dfa78e04b8c963c10f8a0ce0e4d6e3d361935810 100644 --- a/polkadot/node/subsystem-test-helpers/src/lib.rs +++ b/polkadot/node/subsystem-test-helpers/src/lib.rs @@ -18,11 +18,14 @@ #![warn(missing_docs)] +use polkadot_erasure_coding::{branches, obtain_chunks_v1 as obtain_chunks}; +use polkadot_node_primitives::{AvailableData, ErasureChunk, Proof}; use polkadot_node_subsystem::{ messages::AllMessages, overseer, FromOrchestra, OverseerSignal, SpawnGlue, SpawnedSubsystem, SubsystemError, SubsystemResult, TrySendError, }; use polkadot_node_subsystem_util::TimeoutExt; +use polkadot_primitives::{Hash, ValidatorIndex}; use futures::{channel::mpsc, poll, prelude::*}; use parking_lot::Mutex; @@ -440,6 +443,34 @@ impl Future for Yield { } } +/// Helper for chunking available data. +pub fn derive_erasure_chunks_with_proofs_and_root( + n_validators: usize, + available_data: &AvailableData, + alter_chunk: impl Fn(usize, &mut Vec), +) -> (Vec, Hash) { + let mut chunks: Vec> = obtain_chunks(n_validators, available_data).unwrap(); + + for (i, chunk) in chunks.iter_mut().enumerate() { + alter_chunk(i, chunk) + } + + // create proofs for each erasure chunk + let branches = branches(chunks.as_ref()); + + let root = branches.root(); + let erasure_chunks = branches + .enumerate() + .map(|(index, (proof, chunk))| ErasureChunk { + chunk: chunk.to_vec(), + index: ValidatorIndex(index as _), + proof: Proof::try_from(proof).unwrap(), + }) + .collect::>(); + + (erasure_chunks, root) +} + #[cfg(test)] mod tests { use super::*; diff --git a/polkadot/node/subsystem-test-helpers/src/mock.rs b/polkadot/node/subsystem-test-helpers/src/mock.rs index 522bc3c2cc4f4ec91357de9b373f3588a96bde33..14026960ac13ec285c0dc972c26fbf7dd1b9b4b9 100644 --- a/polkadot/node/subsystem-test-helpers/src/mock.rs +++ b/polkadot/node/subsystem-test-helpers/src/mock.rs @@ -16,7 +16,7 @@ use std::sync::Arc; -use polkadot_node_subsystem::{jaeger, ActivatedLeaf}; +use polkadot_node_subsystem::{jaeger, ActivatedLeaf, BlockInfo}; use sc_client_api::UnpinHandle; use sc_keystore::LocalKeystore; use sc_utils::mpsc::tracing_unbounded; @@ -59,3 +59,8 @@ pub fn new_leaf(hash: Hash, number: BlockNumber) -> ActivatedLeaf { span: Arc::new(jaeger::Span::Disabled), } } + +/// Create a new leaf with the given hash and number. +pub fn new_block_import_info(hash: Hash, number: BlockNumber) -> BlockInfo { + BlockInfo { hash, parent_hash: Hash::default(), number, unpin_handle: dummy_unpin_handle(hash) } +} diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index dfda6c1b3c51850c5e96b6514cb2d317b6f59373..6713e9031234aad219d780918eb18af1b8b08bc8 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] derive_more = "0.99.17" futures = "0.3.21" @@ -26,5 +29,5 @@ sc-transaction-pool-api = { path = "../../../substrate/client/transaction-pool/a smallvec = "1.8.0" substrate-prometheus-endpoint = { path = "../../../substrate/utils/prometheus" } thiserror = "1.0.48" -async-trait = "0.1.57" +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 44c6f27b17cca227b4517c4d6ff47cb3e13f05db..c7675c84b91c007eb05136ef25e900b748372b51 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -33,8 +33,8 @@ use polkadot_node_network_protocol::{ }; use polkadot_node_primitives::{ approval::{ - v1::{BlockApprovalMeta, IndirectSignedApprovalVote}, - v2::{CandidateBitfield, IndirectAssignmentCertV2}, + v1::BlockApprovalMeta, + v2::{CandidateBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2}, }, AvailableData, BabeEpoch, BlockWeight, CandidateVotes, CollationGenerationConfig, CollationSecondedSignal, DisputeMessage, DisputeStatus, ErasureChunk, PoV, @@ -42,14 +42,15 @@ use polkadot_node_primitives::{ ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, vstaging::NodeFeatures, AuthorityDiscoveryId, BackedCandidate, - BlockNumber, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CollatorId, - CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupIndex, - GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, MultiDisputeStatementSet, OccupiedCoreAssumption, PersistedValidationData, - PvfCheckStatement, PvfExecKind, SessionIndex, SessionInfo, SignedAvailabilityBitfield, - SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, - ValidatorSignature, + async_backing, slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash, + CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState, + DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, + OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, PvfExecKind, SessionIndex, + SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; use polkadot_statement_table::v2::Misbehavior; use std::{ @@ -720,6 +721,9 @@ pub enum RuntimeApiRequest { AsyncBackingParams(RuntimeApiSender), /// Get the node features. NodeFeatures(SessionIndex, RuntimeApiSender), + /// Approval voting params + /// `V10` + ApprovalVotingParams(SessionIndex, RuntimeApiSender), } impl RuntimeApiRequest { @@ -751,6 +755,9 @@ impl RuntimeApiRequest { /// `Node features` pub const NODE_FEATURES_RUNTIME_REQUIREMENT: u32 = 9; + + /// `approval_voting_params` + pub const APPROVAL_VOTING_PARAMS_REQUIREMENT: u32 = 10; } /// A message to the Runtime API subsystem. @@ -936,7 +943,7 @@ pub enum ApprovalVotingMessage { /// protocol. /// /// Should not be sent unless the block hash within the indirect vote is known. - CheckAndImportApproval(IndirectSignedApprovalVote, oneshot::Sender), + CheckAndImportApproval(IndirectSignedApprovalVoteV2, oneshot::Sender), /// Returns the highest possible ancestor hash of the provided block hash which is /// acceptable to vote on finality for. /// The `BlockNumber` provided is the number of the block's ancestor which is the @@ -952,7 +959,7 @@ pub enum ApprovalVotingMessage { /// requires calling into `approval-distribution`: Calls should be infrequent and bounded. GetApprovalSignaturesForCandidate( CandidateHash, - oneshot::Sender>, + oneshot::Sender, ValidatorSignature)>>, ), } @@ -968,7 +975,7 @@ pub enum ApprovalDistributionMessage { /// Distribute an approval vote for the local validator. The approval vote is assumed to be /// valid, relevant, and the corresponding approval already issued. /// If not, the subsystem is free to drop the message. - DistributeApproval(IndirectSignedApprovalVote), + DistributeApproval(IndirectSignedApprovalVoteV2), /// An update from the network bridge. #[from] NetworkBridgeUpdate(NetworkBridgeEvent), @@ -976,7 +983,7 @@ pub enum ApprovalDistributionMessage { /// Get all approval signatures for all chains a candidate appeared in. GetApprovalSignatures( HashSet<(Hash, CandidateIndex)>, - oneshot::Sender>, + oneshot::Sender, ValidatorSignature)>>, ), /// Approval checking lag update measured in blocks. ApprovalCheckingLagUpdate(BlockNumber), diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 21df1483b9e6b678fefc6d852dfa2c2a9bc740a6..7f6183076101b4474e8059435b42a69b108fbb05 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -16,12 +16,15 @@ use async_trait::async_trait; use polkadot_primitives::{ - async_backing, runtime_api::ParachainHost, slashing, vstaging, Block, BlockNumber, - CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, - DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage, - InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, - ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, ValidatorSignature, + async_backing, + runtime_api::ParachainHost, + slashing, + vstaging::{self, ApprovalVotingParams}, + Block, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, + Header, Id, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, + PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, + ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, }; use sc_client_api::HeaderBackend; use sc_transaction_pool_api::OffchainTransactionPoolFactory; @@ -316,9 +319,16 @@ pub trait RuntimeApiSubsystemClient { async fn disabled_validators(&self, at: Hash) -> Result, ApiError>; // === v9 === - /// Get the node features. async fn node_features(&self, at: Hash) -> Result; + + // == v10: Approval voting params == + /// Approval voting configuration parameters + async fn approval_voting_params( + &self, + at: Hash, + session_index: SessionIndex, + ) -> Result; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -575,4 +585,13 @@ where async fn disabled_validators(&self, at: Hash) -> Result, ApiError> { self.client.runtime_api().disabled_validators(at) } + + /// Approval voting configuration parameters + async fn approval_voting_params( + &self, + at: Hash, + _session_index: SessionIndex, + ) -> Result { + self.client.runtime_api().approval_voting_params(at) + } } diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index 94e1f5a3f128aee7cfd636c80f5f002eee569695..6668430d3b71857248e7428679c8ea0c3ab30511 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -6,8 +6,11 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" futures-channel = "0.3.23" itertools = "0.10" @@ -37,7 +40,7 @@ sp-keystore = { path = "../../../substrate/primitives/keystore" } sc-client-api = { path = "../../../substrate/client/api" } kvdb = "0.13.0" -parity-db = { version = "0.4.8" } +parity-db = { version = "0.4.12" } [dev-dependencies] assert_matches = "1.4.0" diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index aada7a5d77abb5470294e220604a7b4e0c523773..0e44423b4e34338b0de2f56710695928c0ef89c3 100644 --- a/polkadot/node/subsystem-util/src/runtime/mod.rs +++ b/polkadot/node/subsystem-util/src/runtime/mod.rs @@ -30,10 +30,12 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_types::UnpinHandle; use polkadot_primitives::{ - slashing, vstaging::NodeFeatures, AsyncBackingParams, CandidateEvent, CandidateHash, CoreState, - EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, IndexedVec, OccupiedCore, - ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed, SigningContext, UncheckedSigned, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, LEGACY_MIN_BACKING_VOTES, + slashing, + vstaging::{node_features::FeatureIndex, NodeFeatures}, + AsyncBackingParams, CandidateEvent, CandidateHash, CoreState, EncodeAs, ExecutorParams, + GroupIndex, GroupRotationInfo, Hash, IndexedVec, OccupiedCore, ScrapedOnChainVotes, + SessionIndex, SessionInfo, Signed, SigningContext, UncheckedSigned, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, LEGACY_MIN_BACKING_VOTES, }; use crate::{ @@ -92,6 +94,8 @@ pub struct ExtendedSessionInfo { pub validator_info: ValidatorInfo, /// Session executor parameters pub executor_params: ExecutorParams, + /// Node features + pub node_features: NodeFeatures, } /// Information about ourselves, in case we are an `Authority`. @@ -202,7 +206,20 @@ impl RuntimeInfo { let validator_info = self.get_validator_info(&session_info)?; - let full_info = ExtendedSessionInfo { session_info, validator_info, executor_params }; + let node_features = request_node_features(parent, session_index, sender) + .await? + .unwrap_or(NodeFeatures::EMPTY); + let last_set_index = node_features.iter_ones().last().unwrap_or_default(); + if last_set_index >= FeatureIndex::FirstUnassigned as usize { + gum::warn!(target: LOG_TARGET, "Runtime requires feature bit {} that node doesn't support, please upgrade node version", last_set_index); + } + + let full_info = ExtendedSessionInfo { + session_info, + validator_info, + executor_params, + node_features, + }; self.session_info_cache.insert(session_index, full_info); } diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index 9b77359517c9926511eda509c7976386540619b8..b0b396d7f62b91fc1c97e34a743264d0c11f9667 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] polkadot-overseer = { path = "../overseer" } polkadot-node-subsystem-types = { path = "../subsystem-types" } diff --git a/polkadot/node/test/client/Cargo.toml b/polkadot/node/test/client/Cargo.toml index 646f1ea973253448b84b3ac2507e8204be04db88..36748c3b455b90315ab445c7c8b612b5a0d4ab0b 100644 --- a/polkadot/node/test/client/Cargo.toml +++ b/polkadot/node/test/client/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index aa143f40300d10d6e2185e75cc78182a5940e967..f04108537995f1caa0eb72833af2ca558fe931ec 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -6,12 +6,15 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] futures = "0.3.21" hex = "0.4.3" gum = { package = "tracing-gum", path = "../../gum" } rand = "0.8.5" -serde_json = "1.0.108" +serde_json = "1.0.111" tempfile = "3.2.0" tokio = "1.24.2" diff --git a/polkadot/node/tracking-allocator/Cargo.toml b/polkadot/node/tracking-allocator/Cargo.toml index b1b330913440bdae890c4b0a6092d0c191ff0ced..486346e1fe1c00c7d5fa37b4a2a58540b003bc62 100644 --- a/polkadot/node/tracking-allocator/Cargo.toml +++ b/polkadot/node/tracking-allocator/Cargo.toml @@ -5,3 +5,6 @@ version = "1.0.0" authors.workspace = true edition.workspace = true license.workspace = true + +[lints] +workspace = true diff --git a/polkadot/node/tracking-allocator/src/lib.rs b/polkadot/node/tracking-allocator/src/lib.rs index ab8597b5c382d80dc90e55d7f2a2e0ef2c906ea6..33f110ce711978c83474550b0f436ca712a413ed 100644 --- a/polkadot/node/tracking-allocator/src/lib.rs +++ b/polkadot/node/tracking-allocator/src/lib.rs @@ -226,7 +226,7 @@ unsafe impl GlobalAlloc for TrackingAllocator { } #[inline] - unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) -> () { + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { let guard = ALLOCATOR_DATA.lock(); TrackingAllocatorData::track_and_check_limits(guard, -(layout.size() as isize)); self.0.dealloc(ptr, layout) diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index c1b08b4a2bb94a0a5fce172f54f64dcdf3cd653c..82c6f2532cac7d3a939bc24b47c821281c2d0cfe 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -8,6 +8,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] tokio = { version = "1.24.2", default-features = false, features = ["macros", "net", "rt-multi-thread", "sync"] } url = "2.3.1" @@ -19,4 +22,4 @@ reqwest = { version = "0.11", features = ["rustls-tls"], default-features = fals thiserror = "1.0.48" gum = { package = "tracing-gum", path = "../gum" } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.108" +serde_json = "1.0.111" diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index 7c8935d987e543cf13f2a6872b3de1ee50fe09b1..418fe0e6117350fc814cd79bf8718444b86b1b01 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true version = "1.0.0" +[lints] +workspace = true + [dependencies] # note: special care is taken to avoid inclusion of `sp-io` externals when compiling # this crate for WASM. This is critical to avoid forcing all parachain WASM into implementing @@ -21,7 +24,7 @@ derive_more = "0.99.11" bounded-collections = { version = "0.1.8", default-features = false, features = ["serde"] } # all optional crates. -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } [features] default = ["std"] diff --git a/polkadot/parachain/test-parachains/Cargo.toml b/polkadot/parachain/test-parachains/Cargo.toml index 7bbeda4893baddbd635c0aa5da75a15f5c1e76ea..6acdedf67ff2e4be34da2caa70c572a41f861eca 100644 --- a/polkadot/parachain/test-parachains/Cargo.toml +++ b/polkadot/parachain/test-parachains/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true license.workspace = true publish = false +[lints] +workspace = true + [dependencies] tiny-keccak = { version = "2.0.2", features = ["keccak"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/parachain/test-parachains/adder/Cargo.toml b/polkadot/parachain/test-parachains/adder/Cargo.toml index ee0f6f551afb92f6cf4a624cccf6e46d8182f7b6..eec19ef788aad510d7ea9ef6d2ab61d7c6aeb8f9 100644 --- a/polkadot/parachain/test-parachains/adder/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/Cargo.toml @@ -8,6 +8,9 @@ version = "1.0.0" authors.workspace = true publish = false +[lints] +workspace = true + [dependencies] parachain = { package = "polkadot-parachain-primitives", path = "../..", default-features = false, features = ["wasm-api"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index eeb367f8aee51f3554a5aab9bc5e8829902b63a1..a6517608b26faf178ae98f33bf2175784d24fc40 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -7,13 +7,16 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [[bin]] name = "adder-collator" path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = "0.4.17" diff --git a/polkadot/parachain/test-parachains/adder/collator/src/cli.rs b/polkadot/parachain/test-parachains/adder/collator/src/cli.rs index 14b259706835f3649d93af8b3b814f19755f8e62..f81e4cc0fff62dae630c48b932a87bbc4eca904a 100644 --- a/polkadot/parachain/test-parachains/adder/collator/src/cli.rs +++ b/polkadot/parachain/test-parachains/adder/collator/src/cli.rs @@ -24,16 +24,16 @@ use sc_cli::SubstrateCli; pub enum Subcommand { /// Export the genesis state of the parachain. #[command(name = "export-genesis-state")] - ExportGenesisState(ExportGenesisStateCommand), + ExportGenesisState(ExportGenesisHeadCommand), /// Export the genesis wasm of the parachain. #[command(name = "export-genesis-wasm")] ExportGenesisWasm(ExportGenesisWasmCommand), } -/// Command for exporting the genesis state of the parachain +/// Command for exporting the genesis head data of the parachain #[derive(Debug, Parser)] -pub struct ExportGenesisStateCommand {} +pub struct ExportGenesisHeadCommand {} /// Command for exporting the genesis wasm file. #[derive(Debug, Parser)] diff --git a/polkadot/parachain/test-parachains/halt/Cargo.toml b/polkadot/parachain/test-parachains/halt/Cargo.toml index 428f33f730ed3d4c9f04a117fd8a6ae143409367..1bdd4392ad313dbdcf62d36bd04cab7330fdf3fb 100644 --- a/polkadot/parachain/test-parachains/halt/Cargo.toml +++ b/polkadot/parachain/test-parachains/halt/Cargo.toml @@ -8,6 +8,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] [build-dependencies] diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index e763b65cfdde043469a60a28568f0ce9d23f04a0..19e1261db1e7c4f17308061929d559df34943159 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -8,6 +8,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] parachain = { package = "polkadot-parachain-primitives", path = "../..", default-features = false, features = ["wasm-api"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 0de349eac0111db6a26b8a60e9f8d60d17e73216..d34a855ab9d5303e25470d30db986632195105eb 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -7,13 +7,16 @@ version = "1.0.0" authors.workspace = true publish = false +[lints] +workspace = true + [[bin]] name = "undying-collator" path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" log = "0.4.17" @@ -39,6 +42,3 @@ sc-service = { path = "../../../../../substrate/client/service" } sp-keyring = { path = "../../../../../substrate/primitives/keyring" } tokio = { version = "1.24.2", features = ["macros"] } - -[features] -network-protocol-staging = ["polkadot-cli/network-protocol-staging"] diff --git a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs index d04122f2f6898a053d5cb74902fa840341465038..9572887a51a2a195a01e6ceced60ee711288ead2 100644 --- a/polkadot/parachain/test-parachains/undying/collator/src/cli.rs +++ b/polkadot/parachain/test-parachains/undying/collator/src/cli.rs @@ -25,16 +25,16 @@ use std::path::PathBuf; pub enum Subcommand { /// Export the genesis state of the parachain. #[command(name = "export-genesis-state")] - ExportGenesisState(ExportGenesisStateCommand), + ExportGenesisState(ExportGenesisHeadCommand), /// Export the genesis wasm of the parachain. #[command(name = "export-genesis-wasm")] ExportGenesisWasm(ExportGenesisWasmCommand), } -/// Command for exporting the genesis state of the parachain +/// Command for exporting the genesis head data of the parachain #[derive(Debug, Parser)] -pub struct ExportGenesisStateCommand { +pub struct ExportGenesisHeadCommand { /// Output file name or stdout if unspecified. #[arg()] pub output: Option, diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index 5e746c622cf22d70eb457b06508cd07a94f945e6..2552b9abc40b09902da255a8fe6f1baa4416e7c9 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -6,12 +6,15 @@ edition.workspace = true license.workspace = true description = "Shared primitives used by Polkadot runtime" +[lints] +workspace = true + [dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc", "serde"] } 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.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } 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 } diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 331728b25902fee55572523ded6344b4cf7c50ba..d661005e32ffc9ce146e96ad984fb3f60025ca4c 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -114,12 +114,14 @@ //! separated from the stable primitives. use crate::{ - async_backing, slashing, vstaging, AsyncBackingParams, BlockNumber, CandidateCommitments, - CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, - ExecutorParams, GroupRotationInfo, Hash, OccupiedCoreAssumption, PersistedValidationData, - PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, - ValidatorSignature, + async_backing, slashing, + vstaging::{self, ApprovalVotingParams}, + AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, + OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, + SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorSignature, }; + use polkadot_core_primitives as pcp; use polkadot_parachain_primitives::primitives as ppp; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -274,5 +276,10 @@ sp_api::decl_runtime_apis! { /// This is a staging method! Do not use on production runtimes! #[api_version(9)] fn node_features() -> vstaging::NodeFeatures; + + /***** Added in v10 *****/ + /// Approval voting configuration parameters + #[api_version(10)] + fn approval_voting_params() -> ApprovalVotingParams; } } diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index 83b590dc32032c4c366ea0f8723f1cc76837effa..c3a947644fff60a2958f5e9b618e584a18688dcc 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -1070,6 +1070,26 @@ impl ApprovalVote { } } +/// A vote of approval for multiple candidates. +#[derive(Clone, RuntimeDebug)] +pub struct ApprovalVoteMultipleCandidates<'a>(pub &'a [CandidateHash]); + +impl<'a> ApprovalVoteMultipleCandidates<'a> { + /// Yields the signing payload for this approval vote. + pub fn signing_payload(&self, session_index: SessionIndex) -> Vec { + const MAGIC: [u8; 4] = *b"APPR"; + // Make this backwards compatible with `ApprovalVote` so if we have just on candidate the + // signature will look the same. + // This gives us the nice benefit that old nodes can still check signatures when len is 1 + // and the new node can check the signature coming from old nodes. + if self.0.len() == 1 { + (MAGIC, self.0.first().expect("QED: we just checked"), session_index).encode() + } else { + (MAGIC, &self.0, session_index).encode() + } + } +} + /// Custom validity errors used in Polkadot while validating transactions. #[repr(u8)] pub enum ValidityError { @@ -1246,25 +1266,42 @@ pub enum DisputeStatement { impl DisputeStatement { /// Get the payload data for this type of dispute statement. - pub fn payload_data(&self, candidate_hash: CandidateHash, session: SessionIndex) -> Vec { - match *self { + /// + /// Returns Error if the candidate_hash is not included in the list of signed + /// candidate from ApprovalCheckingMultipleCandidate. + pub fn payload_data( + &self, + candidate_hash: CandidateHash, + session: SessionIndex, + ) -> Result, ()> { + match self { DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) => - ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(), + Ok(ExplicitDisputeStatement { valid: true, candidate_hash, session } + .signing_payload()), DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded( inclusion_parent, - )) => CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext { + )) => Ok(CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext { session_index: session, - parent_hash: inclusion_parent, - }), + parent_hash: *inclusion_parent, + })), DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) => - CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext { + Ok(CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext { session_index: session, - parent_hash: inclusion_parent, - }), + parent_hash: *inclusion_parent, + })), DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) => - ApprovalVote(candidate_hash).signing_payload(session), + Ok(ApprovalVote(candidate_hash).signing_payload(session)), + DisputeStatement::Valid( + ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(candidate_hashes), + ) => + if candidate_hashes.contains(&candidate_hash) { + Ok(ApprovalVoteMultipleCandidates(candidate_hashes).signing_payload(session)) + } else { + Err(()) + }, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) => - ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(), + Ok(ExplicitDisputeStatement { valid: false, candidate_hash, session } + .signing_payload()), } } @@ -1276,7 +1313,7 @@ impl DisputeStatement { session: SessionIndex, validator_signature: &ValidatorSignature, ) -> Result<(), ()> { - let payload = self.payload_data(candidate_hash, session); + let payload = self.payload_data(candidate_hash, session)?; if validator_signature.verify(&payload[..], &validator_public) { Ok(()) @@ -1308,13 +1345,14 @@ impl DisputeStatement { Self::Valid(ValidDisputeStatementKind::BackingValid(_)) => true, Self::Valid(ValidDisputeStatementKind::Explicit) | Self::Valid(ValidDisputeStatementKind::ApprovalChecking) | + Self::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(_)) | Self::Invalid(_) => false, } } } /// Different kinds of statements of validity on a candidate. -#[derive(Encode, Decode, Copy, Clone, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] pub enum ValidDisputeStatementKind { /// An explicit statement issued as part of a dispute. #[codec(index = 0)] @@ -1328,6 +1366,12 @@ pub enum ValidDisputeStatementKind { /// An approval vote from the approval checking phase. #[codec(index = 3)] ApprovalChecking, + /// An approval vote from the new version. + /// We can't create this version untill all nodes + /// have been updated to support it and max_approval_coalesce_count + /// is set to more than 1. + #[codec(index = 4)] + ApprovalCheckingMultipleCandidates(Vec), } /// Different kinds of statements of invalidity on a candidate. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 083e0f42d56468b4cdc0e8b6adeb656c6a61a1ff..630bcf8679ad3046ce734042a1557058ea440110 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -17,8 +17,56 @@ //! Staging Primitives. // Put any primitives used by staging APIs functions here +pub use crate::v6::*; +use sp_std::prelude::*; + +use parity_scale_codec::{Decode, Encode}; +use primitives::RuntimeDebug; +use scale_info::TypeInfo; + +/// Approval voting configuration parameters +#[derive( + RuntimeDebug, + Copy, + Clone, + PartialEq, + Encode, + Decode, + TypeInfo, + serde::Serialize, + serde::Deserialize, +)] +pub struct ApprovalVotingParams { + /// The maximum number of candidates `approval-voting` can vote for with + /// a single signatures. + /// + /// Setting it to 1, means we send the approval as soon as we have it available. + pub max_approval_coalesce_count: u32, +} + +impl Default for ApprovalVotingParams { + fn default() -> Self { + Self { max_approval_coalesce_count: 1 } + } +} use bitvec::vec::BitVec; /// Bit indices in the `HostConfiguration.node_features` that correspond to different node features. pub type NodeFeatures = BitVec; + +/// Module containing feature-specific bit indices into the `NodeFeatures` bitvec. +pub mod node_features { + /// A feature index used to indentify a bit into the node_features array stored + /// in the HostConfiguration. + #[repr(u8)] + pub enum FeatureIndex { + /// Tells if tranch0 assignments could be sent in a single certificate. + /// Reserved for: `` + EnableAssignmentsV2 = 0, + /// First unassigned feature bit. + /// Every time a new feature flag is assigned it should take this value. + /// and this should be incremented. + FirstUnassigned = 1, + } +} diff --git a/polkadot/primitives/test-helpers/Cargo.toml b/polkadot/primitives/test-helpers/Cargo.toml index 8215b842ba47aba53162427dd6807762cd8b2ca6..fab9480cfdeb9876c2556ae78a690775bf16d7a8 100644 --- a/polkadot/primitives/test-helpers/Cargo.toml +++ b/polkadot/primitives/test-helpers/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] sp-keyring = { path = "../../../substrate/primitives/keyring" } sp-application-crypto = { package = "sp-application-crypto", path = "../../../substrate/primitives/application-crypto", default-features = false } diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md index 1a17f90d9ba37e1eb8722a28cee22669d08d948e..345b3d2e6970403f3096272cc51f903e0566a22e 100644 --- a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md +++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md @@ -4,10 +4,13 @@ Reading the [section on the approval protocol](../../protocol-approval.md) will aims of this subsystem. Approval votes are split into two parts: Assignments and Approvals. Validators first broadcast their assignment to -indicate intent to check a candidate. Upon successfully checking, they broadcast an approval vote. If a validator -doesn't broadcast their approval vote shortly after issuing an assignment, this is an indication that they are being -prevented from recovering or validating the block data and that more validators should self-select to check the -candidate. This is known as a "no-show". +indicate intent to check a candidate. Upon successfully checking, they don't immediately send the vote instead +they queue the check for a short period of time `MAX_APPROVAL_COALESCE_WAIT_TICKS` to give the opportunity of the +validator to vote for more than one candidate. Once MAX_APPROVAL_COALESCE_WAIT_TICKS have passed or at least +`MAX_APPROVAL_COALESCE_COUNT` are ready they broadcast an approval vote for all candidates. If a validator +doesn't broadcast their approval vote shortly after issuing an assignment, this is an indication that they are +being prevented from recovering or validating the block data and that more validators should self-select to +check the candidate. This is known as a "no-show". The core of this subsystem is a Tick-based timer loop, where Ticks are 500ms. We also reason about time in terms of `DelayTranche`s, which measure the number of ticks elapsed since a block was produced. We track metadata for all @@ -120,6 +123,13 @@ struct BlockEntry { // this block. The block can be considered approved has all bits set to 1 approved_bitfield: Bitfield, children: Vec, + // A list of candidates we have checked, but didn't not sign and + // advertise the vote yet. + candidates_pending_signature: BTreeMap, + // Assignments we already distributed. A 1 bit means the candidate index for which + // we already have sent out an assignment. We need this to avoid distributing + // multiple core assignments more than once. + distributed_assignments: Bitfield, } // slot_duration * 2 + DelayTranche gives the number of delay tranches since the @@ -303,12 +313,12 @@ entry. The cert itself contains information necessary to determine the candidate On receiving a `CheckAndImportApproval(indirect_approval_vote, response_channel)` message: * Fetch the `BlockEntry` from the indirect approval vote's `block_hash`. If none, return `ApprovalCheckResult::Bad`. - * Fetch the `CandidateEntry` from the indirect approval vote's `candidate_index`. If the block did not trigger + * Fetch all `CandidateEntry` from the indirect approval vote's `candidate_indices`. If the block did not trigger inclusion of enough candidates, return `ApprovalCheckResult::Bad`. - * Construct a `SignedApprovalVote` using the candidate hash and check against the validator's approval key, based on - the session info of the block. If invalid or no such validator, return `ApprovalCheckResult::Bad`. + * Construct a `SignedApprovalVote` using the candidates hashes and check against the validator's approval key, + based on the session info of the block. If invalid or no such validator, return `ApprovalCheckResult::Bad`. * Send `ApprovalCheckResult::Accepted` - * [Import the checked approval vote](#import-checked-approval) + * [Import the checked approval vote](#import-checked-approval) for all candidates #### `ApprovalVotingMessage::ApprovedAncestor` @@ -402,10 +412,25 @@ On receiving an `ApprovedAncestor(Hash, BlockNumber, response_channel)`: #### Issue Approval Vote * Fetch the block entry and candidate entry. Ignore if `None` - we've probably just lost a race with finality. - * Construct a `SignedApprovalVote` with the validator index for the session. * [Import the checked approval vote](#import-checked-approval). It is "checked" as we've just issued the signature. - * Construct a `IndirectSignedApprovalVote` using the information about the vote. - * Dispatch `ApprovalDistributionMessage::DistributeApproval`. + * IF `MAX_APPROVAL_COALESCE_COUNT` candidates are in the waiting queue + * Construct a `SignedApprovalVote` with the validator index for the session and all candidate hashes in the waiting queue. + * Construct a `IndirectSignedApprovalVote` using the information about the vote. + * Dispatch `ApprovalDistributionMessage::DistributeApproval`. + * ELSE + * Queue the candidate in the `BlockEntry::candidates_pending_signature` + * Arm a per BlockEntry timer with latest tick we can send the vote. + +### Delayed vote distribution + * [Issue Approval Vote](#issue-approval-vote) arms once a per block timer if there are no requirements to send the + vote immediately. + * When the timer wakes up it will either: + * IF there is a candidate in the queue past its sending tick: + * Construct a `SignedApprovalVote` with the validator index for the session and all candidate hashes in the waiting queue. + * Construct a `IndirectSignedApprovalVote` using the information about the vote. + * Dispatch `ApprovalDistributionMessage::DistributeApproval`. + * ELSE + * Re-arm the timer with latest tick we have the send a the vote. ### Determining Approval of Candidate diff --git a/polkadot/roadmap/implementers-guide/src/protocol-approval.md b/polkadot/roadmap/implementers-guide/src/protocol-approval.md index 70bc0233d65a09edbf3bddff960d52982aa54581..b6aa16646ad25f339af3a6b3db9d7b2d65997c4d 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-approval.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-approval.md @@ -296,6 +296,18 @@ provide somewhat more security. TODO: When? Is this optimal for the network? etc. +## Approval coalescing +To reduce the necessary network bandwidth and cpu time when a validator has more than one candidate to approve we are +doing our best effort to send a single message that approves all available candidates with a single signature. +The implemented heuristic, is that each time we are ready to create a signature and send a vote for a candidate we +delay sending it until one of three things happen: +- We gathered a maximum of `MAX_APPROVAL_COALESCE_COUNT` candidates that we have already checked and we are + ready to sign approval for. +- `MAX_APPROVAL_COALESCE_WAIT_TICKS` have passed since checking oldest candidate and we were ready to sign + and send the approval message. +- We are already in the last third of the no-show period in order to avoid creating accidental no-shows, which in + turn might trigger other assignments. + ## On-chain verification We should verify approval on-chain to reward approval checkers. We therefore require the "no show" timeout to be longer diff --git a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md index 26058c446cb927fa78abd892d416597364a04ab0..32a7fe652dbcb0ee60aa7d828d3a8226dcb80730 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md @@ -182,6 +182,7 @@ struct CoreAssignment { core: CoreIndex, para_id: ParaId, kind: AssignmentKind, + group_idx: GroupIndex, } // reasons a core might be freed. enum FreedReason { diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index ce11b26e5549719e7005e026239b034478e5c2dc..dcb7a13f6f31147123fe855b4d302205d8347a61 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Polkadot specific RPC functionality." +[lints] +workspace = true + [dependencies] jsonrpsee = { version = "0.16.2", features = ["server"] } polkadot-primitives = { path = "../primitives" } @@ -18,6 +21,7 @@ sp-consensus = { path = "../../substrate/primitives/consensus/common" } sp-consensus-babe = { path = "../../substrate/primitives/consensus/babe" } sc-chain-spec = { path = "../../substrate/client/chain-spec" } sc-rpc = { path = "../../substrate/client/rpc" } +sc-rpc-spec-v2 = { path = "../../substrate/client/rpc-spec-v2" } sc-consensus-babe = { path = "../../substrate/client/consensus/babe" } sc-consensus-babe-rpc = { path = "../../substrate/client/consensus/babe/rpc" } sc-consensus-beefy = { path = "../../substrate/client/consensus/beefy" } diff --git a/polkadot/rpc/src/lib.rs b/polkadot/rpc/src/lib.rs index bf9daddba505e9f9bd61bdf74429ef8654101d8d..4455efd3b5337be85fb975f368af9475b20b0b89 100644 --- a/polkadot/rpc/src/lib.rs +++ b/polkadot/rpc/src/lib.rs @@ -121,6 +121,7 @@ where use sc_consensus_babe_rpc::{Babe, BabeApiServer}; use sc_consensus_beefy_rpc::{Beefy, BeefyApiServer}; use sc_consensus_grandpa_rpc::{Grandpa, GrandpaApiServer}; + use sc_rpc_spec_v2::chain_spec::{ChainSpec, ChainSpecApiServer}; use sc_sync_state_rpc::{SyncState, SyncStateApiServer}; use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; @@ -134,6 +135,11 @@ where finality_provider, } = grandpa; + let chain_name = chain_spec.name().to_string(); + let genesis_hash = client.hash(0).ok().flatten().expect("Genesis block exists; qed"); + let properties = chain_spec.properties(); + + io.merge(ChainSpec::new(chain_name, genesis_hash, properties).into_rpc())?; io.merge(StateMigration::new(client.clone(), backend.clone(), deny_unsafe).into_rpc())?; io.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 7e8461c73efaedeb0e1084c93058af986b0c4789..f05963091dd65f87ef6837dd4d8df46ad154ddb4 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } @@ -13,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { version = "0.4.17", default-features = false } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["alloc"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc"] } serde_derive = { version = "1.0.117" } static_assertions = "1.1.0" @@ -29,6 +32,7 @@ sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", def pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } pallet-balances = { path = "../../../substrate/frame/balances", default-features = false } +pallet-broker = { path = "../../../substrate/frame/broker", default-features = false } pallet-fast-unstake = { path = "../../../substrate/frame/fast-unstake", default-features = false } pallet-identity = { path = "../../../substrate/frame/identity", default-features = false } pallet-session = { path = "../../../substrate/frame/session", default-features = false } @@ -65,7 +69,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.108" +serde_json = "1.0.111" libsecp256k1 = "0.7.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } @@ -84,6 +88,7 @@ std = [ "pallet-asset-rate?/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-broker/std", "pallet-election-provider-multi-phase/std", "pallet-fast-unstake/std", "pallet-identity/std", @@ -124,6 +129,7 @@ runtime-benchmarks = [ "pallet-asset-rate/runtime-benchmarks", "pallet-babe/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-broker/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", "pallet-identity/runtime-benchmarks", @@ -148,6 +154,7 @@ try-runtime = [ "pallet-authorship/try-runtime", "pallet-babe?/try-runtime", "pallet-balances/try-runtime", + "pallet-broker/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-identity/try-runtime", diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml index f31811c12725980b0fda6f6fed7bd8e3dc75b1e8..3a402d011961f041214f82ca0d40d20318a9d88f 100644 --- a/polkadot/runtime/common/slot_range_helper/Cargo.toml +++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Helper crate for generating slot ranges for the Polkadot runtime." +[lints] +workspace = true + [dependencies] paste = "1.0" enumn = "0.1.12" diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index f5e3aaef6324baa5cd3b5ac8ec2533eb2536ac5d..cb56cb8a118c4dff7d8e0c9830ee6f5d1605d838 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -743,6 +743,7 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl parachains_shared::Config for Test {} diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs index 4f04a79be550bec1bb40560d5d85721ea73f2b4c..d15e04a660f736608b836917e3de3ac7f77eed0a 100644 --- a/polkadot/runtime/common/src/claims.rs +++ b/polkadot/runtime/common/src/claims.rs @@ -561,7 +561,7 @@ impl Pallet { } // We first need to deposit the balance to ensure that the account exists. - CurrencyOf::::deposit_creating(&dest, balance_due); + let _ = CurrencyOf::::deposit_creating(&dest, balance_due); // Check if this claim should have a vesting schedule. if let Some(vs) = vesting { @@ -801,6 +801,7 @@ mod tests { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } diff --git a/polkadot/runtime/common/src/identity_migrator.rs b/polkadot/runtime/common/src/identity_migrator.rs index cc2c3ce7773c250efb72f9d26ed7604a6bd756e9..0dfb03b06ba32b65941165ec993bc694b84a1389 100644 --- a/polkadot/runtime/common/src/identity_migrator.rs +++ b/polkadot/runtime/common/src/identity_migrator.rs @@ -271,7 +271,8 @@ mod benchmarks { let _ = Identity::::set_identity_no_deposit(&target, info.clone()); let sub_account: T::AccountId = account("sub", 0, SEED); - let _ = Identity::::set_sub_no_deposit(&target, sub_account.clone()); + let name = Data::Raw(b"benchsub".to_vec().try_into().unwrap()); + let _ = Identity::::set_subs_no_deposit(&target, vec![(sub_account.clone(), name)]); // expected deposits let expected_id_deposit = ::BasicDeposit::get() diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index 60e631a03ee2e6bbb508c676581c0d9961252c20..d71c626cd98dd70e01633b82517604c81510cb17 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -149,8 +149,11 @@ impl TryConvert<&VersionedMultiLocation, xcm::latest::MultiLocation> #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks { use super::VersionedLocatableAsset; + use core::marker::PhantomData; + use frame_support::traits::Get; use pallet_asset_rate::AssetKindFactory; use pallet_treasury::ArgumentsFactory as TreasuryArgumentsFactory; + use sp_core::{ConstU32, ConstU8}; use xcm::prelude::*; /// Provides a factory method for the [`VersionedLocatableAsset`]. @@ -172,12 +175,22 @@ pub mod benchmarks { /// Provide factory methods for the [`VersionedLocatableAsset`] and the `Beneficiary` of the /// [`VersionedMultiLocation`]. The location of the asset is determined as a Parachain with an /// ID equal to the passed seed. - pub struct TreasuryArguments; - impl TreasuryArgumentsFactory - for TreasuryArguments + pub struct TreasuryArguments, ParaId = ConstU32<0>>( + PhantomData<(Parents, ParaId)>, + ); + impl, ParaId: Get> + TreasuryArgumentsFactory + for TreasuryArguments { fn create_asset_kind(seed: u32) -> VersionedLocatableAsset { - AssetRateArguments::create_asset_kind(seed) + VersionedLocatableAsset::V3 { + location: xcm::v3::MultiLocation::new(Parents::get(), X1(Parachain(ParaId::get()))), + asset_id: xcm::v3::MultiLocation::new( + 0, + X2(PalletInstance(seed.try_into().unwrap()), GeneralIndex(seed.into())), + ) + .into(), + } } fn create_beneficiary(seed: [u8; 32]) -> VersionedMultiLocation { VersionedMultiLocation::V3(xcm::v3::MultiLocation::new( diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index b0d277a702d64f0a81cc0fdda3f4f9c9547ccb7b..4870432d22f93e100c31766d6db1b0a406b1f992 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -212,6 +212,7 @@ impl paras::Config for Test { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } parameter_types! { diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 12376ae6f1ff7a488a0131e83417013bd42baac3..9719f02677dc3d732369dfad9969b21cd9471154 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -814,6 +814,7 @@ mod tests { type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl configuration::Config for Test { diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index 0fc2644b2a0b0e21b07e8dca719e7c702c9f40a4..4735c176329192abc586e1765b08a0712cbe6fc0 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -23,7 +23,7 @@ use parity_scale_codec::Encode; use primitives::Id as ParaId; use runtime_parachains::{ configuration, dmp, hrmp, - paras::{self, ParaGenesisArgs}, + paras::{self, AssignCoretime, ParaGenesisArgs}, ParaLifecycle, }; use sp_std::boxed::Box; @@ -58,6 +58,8 @@ pub mod pallet { CannotUpgrade, /// Cannot downgrade lease holding parachain to on-demand. CannotDowngrade, + /// There are more cores than supported by the runtime. + TooManyCores, } #[pallet::hooks] @@ -66,6 +68,10 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Schedule a para to be initialized at the start of the next session. + /// + /// This should only be used for TESTING and not on PRODUCTION chains. It automatically + /// assigns Coretime to the chain and increases the number of cores. Thus, there is no + /// running coretime chain required. #[pallet::call_index(0)] #[pallet::weight((1_000, DispatchClass::Operational))] pub fn sudo_schedule_para_initialize( @@ -76,6 +82,9 @@ pub mod pallet { ensure_root(origin)?; runtime_parachains::schedule_para_initialize::(id, genesis) .map_err(|_| Error::::ParaAlreadyExists)?; + + T::AssignCoretime::assign_coretime(id)?; + Ok(()) } diff --git a/polkadot/runtime/common/src/purchase.rs b/polkadot/runtime/common/src/purchase.rs index 06b0a0a021161a4cb34ed6be4b3bfbcc1a2e96f3..f43f16b838cbc91745889089df3d29525bce2180 100644 --- a/polkadot/runtime/common/src/purchase.rs +++ b/polkadot/runtime/common/src/purchase.rs @@ -573,6 +573,7 @@ mod tests { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index c3aaf8b51b8157ce8e289a48f596c6a0f1ee17d7..58bd1d53aed6de3cf91689e413e4fef1e1c16702 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -452,9 +452,9 @@ impl Leaser> for Pallet { // Note that blocks before `LeaseOffset` do not count as any lease period. let offset_block_now = b.checked_sub(&T::LeaseOffset::get())?; let lease_period = offset_block_now / T::LeasePeriod::get(); - let first_block = (offset_block_now % T::LeasePeriod::get()).is_zero(); + let at_begin = (offset_block_now % T::LeasePeriod::get()).is_zero(); - Some((lease_period, first_block)) + Some((lease_period, at_begin)) } fn already_leased( diff --git a/polkadot/runtime/metrics/Cargo.toml b/polkadot/runtime/metrics/Cargo.toml index ad4a2fa9207f915cd1bbd7acdc77d206b6f31146..9a16749bf602f9c27bcc39c6ebe71138eeb4cc07 100644 --- a/polkadot/runtime/metrics/Cargo.toml +++ b/polkadot/runtime/metrics/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Runtime metric interface for the Polkadot node" +[lints] +workspace = true + [dependencies] sp-std = { package = "sp-std", path = "../../../substrate/primitives/std", default-features = false } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 2627bc9ef4986ba8f852487607efc0cf081be55d..dcfb7108dd25c7b6ac2324a70d6bebc2373d0fc2 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } @@ -13,7 +16,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ log = { version = "0.4.17", default-features = false } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } derive_more = "0.99.17" bitflags = "1.3.2" @@ -28,11 +31,13 @@ sp-core = { path = "../../../substrate/primitives/core", default-features = fals sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true } sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false, optional = true } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false, optional = true } +sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false } pallet-authority-discovery = { path = "../../../substrate/frame/authority-discovery", default-features = false } pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false } pallet-balances = { path = "../../../substrate/frame/balances", default-features = false } pallet-babe = { path = "../../../substrate/frame/babe", default-features = false } +pallet-broker = { path = "../../../substrate/frame/broker", default-features = false } pallet-message-queue = { path = "../../../substrate/frame/message-queue", default-features = false } pallet-session = { path = "../../../substrate/frame/session", default-features = false } pallet-staking = { path = "../../../substrate/frame/staking", default-features = false } @@ -63,7 +68,7 @@ test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../pri sp-tracing = { path = "../../../substrate/primitives/tracing" } thousands = "0.2.0" assert_matches = "1" -serde_json = "1.0.108" +serde_json = "1.0.111" [features] default = ["std"] @@ -79,6 +84,7 @@ std = [ "pallet-authorship/std", "pallet-babe/std", "pallet-balances/std", + "pallet-broker/std", "pallet-message-queue/std", "pallet-session/std", "pallet-staking/std", @@ -96,6 +102,7 @@ std = [ "serde/std", "sp-api/std", "sp-application-crypto?/std", + "sp-arithmetic/std", "sp-core/std", "sp-io/std", "sp-keystore", @@ -112,6 +119,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-babe/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-broker/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -132,6 +140,7 @@ try-runtime = [ "pallet-authorship/try-runtime", "pallet-babe/try-runtime", "pallet-balances/try-runtime", + "pallet-broker/try-runtime", "pallet-message-queue/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", diff --git a/polkadot/runtime/parachains/src/assigner.rs b/polkadot/runtime/parachains/src/assigner.rs deleted file mode 100644 index 9e408df61dc18d3da7205a7a4ef5f8b0d9386c03..0000000000000000000000000000000000000000 --- a/polkadot/runtime/parachains/src/assigner.rs +++ /dev/null @@ -1,112 +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 . - -//! The Polkadot multiplexing assignment provider. -//! Provides blockspace assignments for both bulk and on demand parachains. -use frame_system::pallet_prelude::BlockNumberFor; -use primitives::{CoreIndex, Id as ParaId}; - -use crate::{ - configuration, paras, - scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, -}; - -pub use pallet::*; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config + configuration::Config + paras::Config { - type ParachainsAssignmentProvider: AssignmentProvider>; - type OnDemandAssignmentProvider: AssignmentProvider>; - } -} - -// Aliases to make the impl more readable. -type ParachainAssigner = ::ParachainsAssignmentProvider; -type OnDemandAssigner = ::OnDemandAssignmentProvider; - -impl Pallet { - // Helper fn for the AssignmentProvider implementation. - // Assumes that the first allocation of cores is to bulk parachains. - // This function will return false if there are no cores assigned to the bulk parachain - // assigner. - fn is_bulk_core(core_idx: &CoreIndex) -> bool { - let parachain_cores = - as AssignmentProvider>>::session_core_count(); - - core_idx.0 < parachain_cores - } -} - -impl AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - let parachain_cores = - as AssignmentProvider>>::session_core_count(); - let on_demand_cores = - as AssignmentProvider>>::session_core_count(); - - parachain_cores.saturating_add(on_demand_cores) - } - - /// Pops an `Assignment` from a specified `CoreIndex` - fn pop_assignment_for_core( - core_idx: CoreIndex, - concluded_para: Option, - ) -> Option { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::pop_assignment_for_core( - core_idx, - concluded_para, - ) - } else { - as AssignmentProvider>>::pop_assignment_for_core( - core_idx, - concluded_para, - ) - } - } - - fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment) { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::push_assignment_for_core( - core_idx, assignment, - ) - } else { - as AssignmentProvider>>::push_assignment_for_core( - core_idx, assignment, - ) - } - } - - fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig> { - if Pallet::::is_bulk_core(&core_idx) { - as AssignmentProvider>>::get_provider_config( - core_idx, - ) - } else { - as AssignmentProvider>>::get_provider_config( - core_idx, - ) - } - } -} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..71c3f1fa39f7c6c127ac954b8299fb16a323e72d --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mock_helpers.rs @@ -0,0 +1,87 @@ +// 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 . + +//! Helper functions for tests, also used in runtime-benchmarks. + +#![cfg(test)] + +use super::*; + +use crate::{ + mock::MockGenesisConfig, + paras::{ParaGenesisArgs, ParaKind}, +}; +use sp_runtime::Perbill; + +use primitives::{Balance, HeadData, ValidationCode}; + +fn default_genesis_config() -> MockGenesisConfig { + MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: crate::configuration::HostConfiguration { ..Default::default() }, + }, + ..Default::default() + } +} + +#[derive(Debug)] +pub struct GenesisConfigBuilder { + pub on_demand_cores: u32, + pub on_demand_base_fee: Balance, + pub on_demand_fee_variability: Perbill, + pub on_demand_max_queue_size: u32, + pub on_demand_target_queue_utilization: Perbill, + pub onboarded_on_demand_chains: Vec, +} + +impl Default for GenesisConfigBuilder { + fn default() -> Self { + Self { + on_demand_cores: 10, + on_demand_base_fee: 10_000, + on_demand_fee_variability: Perbill::from_percent(1), + on_demand_max_queue_size: 100, + on_demand_target_queue_utilization: Perbill::from_percent(25), + onboarded_on_demand_chains: vec![], + } + } +} + +impl GenesisConfigBuilder { + pub(super) fn build(self) -> MockGenesisConfig { + let mut genesis = default_genesis_config(); + let config = &mut genesis.configuration.config; + config.coretime_cores = self.on_demand_cores; + config.on_demand_base_fee = self.on_demand_base_fee; + config.on_demand_fee_variability = self.on_demand_fee_variability; + config.on_demand_queue_max_size = self.on_demand_max_queue_size; + config.on_demand_target_queue_utilization = self.on_demand_target_queue_utilization; + + let paras = &mut genesis.paras.paras; + for para_id in self.onboarded_on_demand_chains { + paras.push(( + para_id, + ParaGenesisArgs { + genesis_head: HeadData::from(vec![0u8]), + validation_code: ValidationCode::from(vec![0u8]), + para_kind: ParaKind::Parathread, + }, + )) + } + + genesis + } +} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/mod.rs b/polkadot/runtime/parachains/src/assigner_coretime/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..9da81dc816cabeb7019e44b8f88c0f526582830d --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/mod.rs @@ -0,0 +1,496 @@ +// 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 . + +//! The parachain coretime assignment module. +//! +//! Handles scheduling of assignments coming from the coretime/broker chain. For on-demand +//! assignments it relies on the separate on-demand assignment provider, where it forwards requests +//! to. +//! +//! `CoreDescriptor` contains pointers to the begin and the end of a list of schedules, together +//! with the currently active assignments. + +mod mock_helpers; +#[cfg(test)] +mod tests; + +use crate::{ + assigner_on_demand, configuration, + paras::AssignCoretime, + scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, + ParaId, +}; + +use frame_support::{defensive, pallet_prelude::*}; +use frame_system::pallet_prelude::*; +use pallet_broker::CoreAssignment; +use primitives::CoreIndex; +use sp_runtime::traits::{One, Saturating}; + +use sp_std::prelude::*; + +pub use pallet::*; + +/// Fraction expressed as a nominator with an assumed denominator of 57,600. +#[derive(RuntimeDebug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, TypeInfo)] +pub struct PartsOf57600(u16); + +impl PartsOf57600 { + pub const ZERO: Self = Self(0); + pub const FULL: Self = Self(57600); + + pub fn new_saturating(v: u16) -> Self { + Self::ZERO.saturating_add(Self(v)) + } + + pub fn is_full(&self) -> bool { + *self == Self::FULL + } + + pub fn saturating_add(self, rhs: Self) -> Self { + let inner = self.0.saturating_add(rhs.0); + if inner > 57600 { + Self(57600) + } else { + Self(inner) + } + } + + pub fn saturating_sub(self, rhs: Self) -> Self { + Self(self.0.saturating_sub(rhs.0)) + } + + pub fn checked_add(self, rhs: Self) -> Option { + let inner = self.0.saturating_add(rhs.0); + if inner > 57600 { + None + } else { + Some(Self(inner)) + } + } +} + +/// Assignments as they are scheduled by block number +/// +/// for a particular core. +#[derive(Encode, Decode, TypeInfo)] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug))] +struct Schedule { + // Original assignments + assignments: Vec<(CoreAssignment, PartsOf57600)>, + /// When do our assignments become invalid, if at all? + /// + /// If this is `Some`, then this `CoreState` will be dropped at that block number. If this is + /// `None`, then we will keep serving our core assignments in a circle until a new set of + /// assignments is scheduled. + end_hint: Option, + + /// The next queued schedule for this core. + /// + /// Schedules are forming a queue. + next_schedule: Option, +} + +/// Descriptor for a core. +/// +/// Contains pointers to first and last schedule into `CoreSchedules` for that core and keeps track +/// of the currently active work as well. +#[derive(Encode, Decode, TypeInfo, Default)] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone))] +struct CoreDescriptor { + /// Meta data about the queued schedules for this core. + queue: Option>, + /// Currently performed work. + current_work: Option>, +} + +/// Pointers into `CoreSchedules` for a particular core. +/// +/// Schedules in `CoreSchedules` form a queue. `Schedule::next_schedule` always pointing to the next +/// item. +#[derive(Encode, Decode, TypeInfo, Copy, Clone)] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug))] +struct QueueDescriptor { + /// First scheduled item, that is not yet active. + first: N, + /// Last scheduled item. + last: N, +} + +#[derive(Encode, Decode, TypeInfo)] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone))] +struct WorkState { + /// Assignments with current state. + /// + /// Assignments and book keeping on how much has been served already. We keep track of serviced + /// assignments in order to adhere to the specified ratios. + assignments: Vec<(CoreAssignment, AssignmentState)>, + /// When do our assignments become invalid if at all? + /// + /// If this is `Some`, then this `CoreState` will be dropped at that block number. If this is + /// `None`, then we will keep serving our core assignments in a circle until a new set of + /// assignments is scheduled. + end_hint: Option, + /// Position in the assignments we are currently in. + /// + /// Aka which core assignment will be popped next on + /// `AssignmentProvider::pop_assignment_for_core`. + pos: u16, + /// Step width + /// + /// How much we subtract from `AssignmentState::remaining` for a core served. + step: PartsOf57600, +} + +#[derive(Encode, Decode, TypeInfo)] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug, Clone, Copy))] +struct AssignmentState { + /// Ratio of the core this assignment has. + /// + /// As initially received via `assign_core`. + ratio: PartsOf57600, + /// How many parts are remaining in this round? + /// + /// At the end of each round (in preparation for the next), ratio will be added to remaining. + /// Then every time we get scheduled we subtract a core worth of points. Once we reach 0 or a + /// number lower than what a core is worth (`CoreState::step` size), we move on to the next + /// item in the `Vec`. + /// + /// The first round starts with remaining = ratio. + remaining: PartsOf57600, +} + +impl From> for WorkState { + fn from(schedule: Schedule) -> Self { + let Schedule { assignments, end_hint, next_schedule: _ } = schedule; + let step = + if let Some(min_step_assignment) = assignments.iter().min_by(|a, b| a.1.cmp(&b.1)) { + min_step_assignment.1 + } else { + // Assignments empty, should not exist. In any case step size does not matter here: + log::debug!("assignments of a `Schedule` should never be empty."); + PartsOf57600(1) + }; + let assignments = assignments + .into_iter() + .map(|(a, ratio)| (a, AssignmentState { ratio, remaining: ratio })) + .collect(); + + Self { assignments, end_hint, pos: 0, step } + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + frame_system::Config + configuration::Config + assigner_on_demand::Config + { + } + + /// Scheduled assignment sets. + /// + /// Assignments as of the given block number. They will go into state once the block number is + /// reached (and replace whatever was in there before). + #[pallet::storage] + pub(super) type CoreSchedules = StorageMap< + _, + Twox256, + (BlockNumberFor, CoreIndex), + Schedule>, + OptionQuery, + >; + + /// Assignments which are currently active. + /// + /// They will be picked from `PendingAssignments` once we reach the scheduled block number in + /// `PendingAssignments`. + #[pallet::storage] + pub(super) type CoreDescriptors = StorageMap< + _, + Twox256, + CoreIndex, + CoreDescriptor>, + ValueQuery, + GetDefault, + >; + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::error] + pub enum Error { + AssignmentsEmpty, + /// Assignments together exceeded 57600. + OverScheduled, + /// Assignments together less than 57600 + UnderScheduled, + /// assign_core is only allowed to append new assignments at the end of already existing + /// ones. + DisallowedInsert, + /// Tried to insert a schedule for the same core and block number as an existing schedule + DuplicateInsert, + /// Tried to add an unsorted set of assignments + AssignmentsNotSorted, + } +} + +impl AssignmentProvider> for Pallet { + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { + let now = >::block_number(); + + CoreDescriptors::::mutate(core_idx, |core_state| { + Self::ensure_workload(now, core_idx, core_state); + + let work_state = core_state.current_work.as_mut()?; + + // Wrap around: + work_state.pos = work_state.pos % work_state.assignments.len() as u16; + let (a_type, a_state) = &mut work_state + .assignments + .get_mut(work_state.pos as usize) + .expect("We limited pos to the size of the vec one line above. qed"); + + // advance for next pop: + a_state.remaining = a_state.remaining.saturating_sub(work_state.step); + if a_state.remaining < work_state.step { + // Assignment exhausted, need to move to the next and credit remaining for + // next round. + work_state.pos += 1; + // Reset to ratio + still remaining "credits": + a_state.remaining = a_state.remaining.saturating_add(a_state.ratio); + } + + match a_type { + CoreAssignment::Idle => None, + CoreAssignment::Pool => + assigner_on_demand::Pallet::::pop_assignment_for_core(core_idx), + CoreAssignment::Task(para_id) => Some(Assignment::Bulk((*para_id).into())), + } + }) + } + + fn report_processed(assignment: Assignment) { + match assignment { + Assignment::Pool { para_id, core_index } => + assigner_on_demand::Pallet::::report_processed(para_id, core_index), + Assignment::Bulk(_) => {}, + } + } + + /// Push an assignment back to the front of the queue. + /// + /// The assignment has not been processed yet. Typically used on session boundaries. + /// Parameters: + /// - `assignment`: The on demand assignment. + fn push_back_assignment(assignment: Assignment) { + match assignment { + Assignment::Pool { para_id, core_index } => + assigner_on_demand::Pallet::::push_back_assignment(para_id, core_index), + Assignment::Bulk(_) => { + // Session changes are rough. We just drop assignments that did not make it on a + // session boundary. This seems sensible as bulk is region based. Meaning, even if + // we made the effort catching up on those dropped assignments, this would very + // likely lead to other assignments not getting served at the "end" (when our + // assignment set gets replaced). + }, + } + } + + fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { + let config = >::config(); + AssignmentProviderConfig { + max_availability_timeouts: config.on_demand_retries, + ttl: config.on_demand_ttl, + } + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn get_mock_assignment(_: CoreIndex, para_id: primitives::Id) -> Assignment { + // Given that we are not tracking anything in `Bulk` assignments, it is safe to always + // return a bulk assignment. + Assignment::Bulk(para_id) + } + + fn session_core_count() -> u32 { + let config = >::config(); + config.coretime_cores + } +} + +impl Pallet { + /// Ensure given workload for core is up to date. + fn ensure_workload( + now: BlockNumberFor, + core_idx: CoreIndex, + descriptor: &mut CoreDescriptor>, + ) { + // Workload expired? + if descriptor + .current_work + .as_ref() + .and_then(|w| w.end_hint) + .map_or(false, |e| e <= now) + { + descriptor.current_work = None; + } + + let Some(queue) = descriptor.queue else { + // No queue. + return + }; + + let mut next_scheduled = queue.first; + + if next_scheduled > now { + // Not yet ready. + return + } + + // Update is needed: + let update = loop { + let Some(update) = CoreSchedules::::take((next_scheduled, core_idx)) else { + break None + }; + // Still good? + if update.end_hint.map_or(true, |e| e > now) { + break Some(update) + } + // Move on if possible: + if let Some(n) = update.next_schedule { + next_scheduled = n; + } else { + break None + } + }; + + let new_first = update.as_ref().and_then(|u| u.next_schedule); + descriptor.current_work = update.map(Into::into); + + descriptor.queue = new_first.map(|new_first| { + QueueDescriptor { + first: new_first, + // `last` stays unaffected, if not empty: + last: queue.last, + } + }); + } + + /// Append another assignment for a core. + /// + /// Important only appending is allowed. Meaning, all already existing assignments must have a + /// begin smaller than the one passed here. This restriction exists, because it makes the + /// insertion O(1) and the author could not think of a reason, why this restriction should be + /// causing any problems. Inserting arbitrarily causes a `DispatchError::DisallowedInsert` + /// error. This restriction could easily be lifted if need be and in fact an implementation is + /// available + /// [here](https://github.com/paritytech/polkadot-sdk/pull/1694/commits/c0c23b01fd2830910cde92c11960dad12cdff398#diff-0c85a46e448de79a5452395829986ee8747e17a857c27ab624304987d2dde8baR386). + /// The problem is that insertion complexity then depends on the size of the existing queue, + /// which makes determining weights hard and could lead to issues like overweight blocks (at + /// least in theory). + pub fn assign_core( + core_idx: CoreIndex, + begin: BlockNumberFor, + assignments: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) -> Result<(), DispatchError> { + // There should be at least one assignment. + ensure!(!assignments.is_empty(), Error::::AssignmentsEmpty); + + // Checking for sort and unique manually, since we don't have access to iterator tools. + // This way of checking uniqueness only works since we also check sortedness. + assignments.iter().map(|x| &x.0).try_fold(None, |prev, cur| { + if prev.map_or(false, |p| p >= cur) { + Err(Error::::AssignmentsNotSorted) + } else { + Ok(Some(cur)) + } + })?; + + // Check that the total parts between all assignments are equal to 57600 + let parts_sum = assignments + .iter() + .map(|assignment| assignment.1) + .try_fold(PartsOf57600::ZERO, |sum, parts| { + sum.checked_add(parts).ok_or(Error::::OverScheduled) + })?; + ensure!(parts_sum.is_full(), Error::::UnderScheduled); + + CoreDescriptors::::mutate(core_idx, |core_descriptor| { + let new_queue = match core_descriptor.queue { + Some(queue) => { + ensure!(begin > queue.last, Error::::DisallowedInsert); + + CoreSchedules::::try_mutate((queue.last, core_idx), |schedule| { + if let Some(schedule) = schedule.as_mut() { + debug_assert!(schedule.next_schedule.is_none(), "queue.end was supposed to be the end, so the next item must be `None`!"); + schedule.next_schedule = Some(begin); + } else { + defensive!("Queue end entry does not exist?"); + } + CoreSchedules::::try_mutate((begin, core_idx), |schedule| { + // It should already be impossible to overwrite an existing schedule due + // to strictly increasing block number. But we check here for safety and + // in case the design changes. + ensure!(schedule.is_none(), Error::::DuplicateInsert); + *schedule = + Some(Schedule { assignments, end_hint, next_schedule: None }); + Ok::<(), DispatchError>(()) + })?; + Ok::<(), DispatchError>(()) + })?; + + QueueDescriptor { first: queue.first, last: begin } + }, + None => { + // Queue empty, just insert: + CoreSchedules::::insert( + (begin, core_idx), + Schedule { assignments, end_hint, next_schedule: None }, + ); + QueueDescriptor { first: begin, last: begin } + }, + }; + core_descriptor.queue = Some(new_queue); + Ok(()) + }) + } +} + +impl AssignCoretime for Pallet { + fn assign_coretime(id: ParaId) -> DispatchResult { + let current_block = frame_system::Pallet::::block_number(); + + // Add a new core and assign the para to it. + let mut config = >::config(); + let core = config.coretime_cores; + config.coretime_cores.saturating_inc(); + + // `assign_coretime` is only called at genesis or by root, so setting the active + // config here is fine. + configuration::Pallet::::force_set_active_config(config); + + let begin = current_block + One::one(); + let assignment = vec![(pallet_broker::CoreAssignment::Task(id.into()), PartsOf57600::FULL)]; + Pallet::::assign_core(CoreIndex(core), begin, assignment, None) + } +} diff --git a/polkadot/runtime/parachains/src/assigner_coretime/tests.rs b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..998e39670f97d6f6e48d079e4544e547d702509b --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs @@ -0,0 +1,817 @@ +// 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::*; + +use crate::{ + assigner_coretime::{mock_helpers::GenesisConfigBuilder, pallet::Error, Schedule}, + initializer::SessionChangeNotification, + mock::{ + new_test_ext, Balances, CoretimeAssigner, OnDemandAssigner, Paras, ParasShared, + RuntimeOrigin, Scheduler, System, Test, + }, + paras::{ParaGenesisArgs, ParaKind}, + scheduler::common::Assignment, +}; +use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency}; +use pallet_broker::TaskId; +use primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode}; +use sp_std::collections::btree_map::BTreeMap; + +fn schedule_blank_para(id: ParaId, parakind: ParaKind) { + let validation_code: ValidationCode = vec![1, 2, 3].into(); + assert_ok!(Paras::schedule_para_initialize( + id, + ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: validation_code.clone(), + para_kind: parakind, + } + )); + + assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code)); +} + +fn run_to_block( + to: BlockNumber, + new_session: impl Fn(BlockNumber) -> Option>, +) { + while System::block_number() < to { + let b = System::block_number(); + + Scheduler::initializer_finalize(); + Paras::initializer_finalize(b); + + if let Some(notification) = new_session(b + 1) { + let mut notification_with_session_index = notification; + // We will make every session change trigger an action queue. Normally this may require + // 2 or more session changes. + if notification_with_session_index.session_index == SessionIndex::default() { + notification_with_session_index.session_index = ParasShared::scheduled_session(); + } + Paras::initializer_on_new_session(¬ification_with_session_index); + Scheduler::initializer_on_new_session(¬ification_with_session_index); + } + + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + Paras::initializer_initialize(b + 1); + Scheduler::initializer_initialize(b + 1); + + // In the real runtime this is expected to be called by the `InclusionInherent` pallet. + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); + } +} + +fn default_test_assignments() -> Vec<(CoreAssignment, PartsOf57600)> { + vec![(CoreAssignment::Idle, PartsOf57600::FULL)] +} + +fn default_test_schedule() -> Schedule> { + Schedule { assignments: default_test_assignments(), end_hint: None, next_schedule: None } +} + +#[test] +// Should create new QueueDescriptor and add new schedule to CoreSchedules +fn assign_core_works_with_no_prior_schedule() { + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Call assign_core + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + default_test_assignments(), + None, + )); + + // Check CoreSchedules + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(11u32), core_idx)), + Some(default_test_schedule()) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(BlockNumberFor::::from(11u32)) + ); + assert_eq!( + CoreDescriptors::::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)), + Some(BlockNumberFor::::from(11u32)) + ); + }); +} + +#[test] +fn end_hint_is_properly_honored() { + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + vec![(CoreAssignment::Task(1), PartsOf57600::FULL)], + Some(15u32), + )); + + assert!( + CoretimeAssigner::pop_assignment_for_core(core_idx).is_none(), + "No assignment yet in effect" + ); + + run_to_block(11, |_| None); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(1.into())), + "Assignment should now be present" + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(1.into())), + "Nothing changed, assignment should still be present" + ); + + run_to_block(15, |_| None); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + None, + "Assignment should now be gone" + ); + + // Insert assignment that is already dead: + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + vec![(CoreAssignment::Task(1), PartsOf57600::FULL)], + Some(15u32), + )); + + // Core should still be empty: + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + None, + "Assignment should now be gone" + ); + }); +} + +#[test] +// Should update last in QueueDescriptor and add new schedule to CoreSchedules +fn assign_core_works_with_prior_schedule() { + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + let default_with_next_schedule = + Schedule { next_schedule: Some(15u32), ..default_test_schedule() }; + + // Call assign_core twice + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(15u32), + default_test_assignments(), + None, + )); + + // Check CoreSchedules for two entries + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(11u32), core_idx)), + Some(default_with_next_schedule) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(15u32), core_idx)), + Some(default_test_schedule()) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(BlockNumberFor::::from(11u32)) + ); + assert_eq!( + CoreDescriptors::::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)), + Some(BlockNumberFor::::from(15u32)) + ); + }); +} + +#[test] +// Invariants: We assume that CoreSchedules is append only and consumed. In other words new +// schedules inserted for a core must have a higher block number than all of the already existing +// schedules. +fn assign_core_enforces_higher_block_number() { + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Call assign core twice to establish some schedules + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(12u32), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(15u32), + default_test_assignments(), + None, + )); + + // Call assign core with block number before QueueDescriptor first, expecting an error + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + default_test_assignments(), + None, + ), + Error::::DisallowedInsert + ); + + // Call assign core with block number between already scheduled assignments, expecting an + // error + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(13u32), + default_test_assignments(), + None, + ), + Error::::DisallowedInsert + ); + }); +} + +#[test] +fn assign_core_enforces_well_formed_schedule() { + let para_id = ParaId::from(1u32); + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + let empty_assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![]; + let overscheduled = vec![ + (CoreAssignment::Pool, PartsOf57600::FULL), + (CoreAssignment::Task(para_id.into()), PartsOf57600::FULL), + ]; + let underscheduled = vec![(CoreAssignment::Pool, PartsOf57600(30000))]; + let not_unique = vec![ + (CoreAssignment::Pool, PartsOf57600::FULL / 2), + (CoreAssignment::Pool, PartsOf57600::FULL / 2), + ]; + let not_sorted = vec![ + (CoreAssignment::Task(para_id.into()), PartsOf57600(19200)), + (CoreAssignment::Pool, PartsOf57600(19200)), + (CoreAssignment::Idle, PartsOf57600(19200)), + ]; + + // Attempting assign_core with malformed assignments such that all error cases + // are tested + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + empty_assignments, + None, + ), + Error::::AssignmentsEmpty + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + overscheduled, + None, + ), + Error::::OverScheduled + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + underscheduled, + None, + ), + Error::::UnderScheduled + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + not_unique, + None, + ), + Error::::AssignmentsNotSorted + ); + assert_noop!( + CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + not_sorted, + None, + ), + Error::::AssignmentsNotSorted + ); + }); +} + +#[test] +fn next_schedule_always_points_to_next_work_plan_item() { + let core_idx = CoreIndex(0); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + let start_1 = 15u32; + let start_2 = 20u32; + let start_3 = 25u32; + let start_4 = 30u32; + let start_5 = 35u32; + + let expected_schedule_3 = + Schedule { next_schedule: Some(start_4), ..default_test_schedule() }; + let expected_schedule_4 = + Schedule { next_schedule: Some(start_5), ..default_test_schedule() }; + let expected_schedule_5 = default_test_schedule(); + + // Call assign_core for each of five schedules + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_1), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_2), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_3), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_4), + default_test_assignments(), + None, + )); + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(start_5), + default_test_assignments(), + None, + )); + + // Rotate through the first two schedules + run_to_block(start_1, |n| if n == start_1 { Some(Default::default()) } else { None }); + CoretimeAssigner::pop_assignment_for_core(core_idx); + run_to_block(start_2, |n| if n == start_2 { Some(Default::default()) } else { None }); + CoretimeAssigner::pop_assignment_for_core(core_idx); + + // Use saved starting block numbers to check that schedules chain + // together correctly + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(start_3), core_idx)), + Some(expected_schedule_3) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(start_4), core_idx)), + Some(expected_schedule_4) + ); + assert_eq!( + CoreSchedules::::get((BlockNumberFor::::from(start_5), core_idx)), + Some(expected_schedule_5) + ); + + // Check QueueDescriptor + assert_eq!( + CoreDescriptors::::get(core_idx) + .queue + .as_ref() + .and_then(|q| Some(q.first)), + Some(start_3) + ); + assert_eq!( + CoreDescriptors::::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)), + Some(start_5) + ); + }); +} + +#[test] +fn ensure_workload_works() { + let core_idx = CoreIndex(0); + let test_assignment_state = + AssignmentState { ratio: PartsOf57600::FULL, remaining: PartsOf57600::FULL }; + + let empty_descriptor: CoreDescriptor> = + CoreDescriptor { queue: None, current_work: None }; + let assignments_queued_descriptor = CoreDescriptor { + queue: Some(QueueDescriptor { + first: BlockNumberFor::::from(11u32), + last: BlockNumberFor::::from(11u32), + }), + current_work: None, + }; + let assignments_active_descriptor = CoreDescriptor { + queue: None, + current_work: Some(WorkState { + assignments: vec![(CoreAssignment::Pool, test_assignment_state)], + end_hint: Some(BlockNumberFor::::from(15u32)), + pos: 0, + step: PartsOf57600::FULL, + }), + }; + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + let mut core_descriptor: CoreDescriptor> = empty_descriptor.clone(); + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Case 1: No new schedule in CoreSchedules for core + CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor); + assert_eq!(core_descriptor, empty_descriptor); + + // Case 2: New schedule exists in CoreSchedules for core, but new + // schedule start is not yet reached. + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + vec![(CoreAssignment::Pool, PartsOf57600::FULL)], + Some(BlockNumberFor::::from(15u32)), + )); + + // Propagate changes from storage to Core_Descriptor handle. Normally + // pop_assignment_for_core would handle this. + core_descriptor = CoreDescriptors::::get(core_idx); + + CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor); + assert_eq!(core_descriptor, assignments_queued_descriptor); + + // Case 3: Next schedule exists in CoreSchedules for core. Next starting + // block has been reached. Swaps new WorkState into CoreDescriptors from + // CoreSchedules. + CoretimeAssigner::ensure_workload(11u32, core_idx, &mut core_descriptor); + assert_eq!(core_descriptor, assignments_active_descriptor); + + // Case 4: end_hint reached but new schedule start not yet reached. WorkState in + // CoreDescriptor is cleared + CoretimeAssigner::ensure_workload(15u32, core_idx, &mut core_descriptor); + assert_eq!(core_descriptor, empty_descriptor); + }); +} + +#[test] +fn pop_assignment_for_core_works() { + let para_id = ParaId::from(1); + let core_idx = CoreIndex(0); + let alice = 1u64; + let amt = 10_000_000u128; + + let assignments_pool = vec![(CoreAssignment::Pool, PartsOf57600::FULL)]; + let assignments_task = vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)]; + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // Initialize the parathread, wait for it to be ready, then add an + // on demand order to later pop with our Coretime assigner. + schedule_blank_para(para_id, ParaKind::Parathread); + Balances::make_free_balance_be(&alice, amt); + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + assert_ok!(OnDemandAssigner::place_order_allow_death( + RuntimeOrigin::signed(alice), + amt, + para_id + )); + + // Case 1: Assignment idle + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + default_test_assignments(), // Default is Idle + None, + )); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + assert_eq!(CoretimeAssigner::pop_assignment_for_core(core_idx), None); + + // Case 2: Assignment pool + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(21u32), + assignments_pool, + None, + )); + + run_to_block(21, |n| if n == 21 { Some(Default::default()) } else { None }); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Pool { para_id, core_index: 0.into() }) + ); + + // Case 3: Assignment task + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(31u32), + assignments_task, + None, + )); + + run_to_block(31, |n| if n == 31 { Some(Default::default()) } else { None }); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(para_id)) + ); + }); +} + +#[test] +fn assignment_proportions_in_core_state_work() { + let core_idx = CoreIndex(0); + let task_1 = TaskId::from(1u32); + let task_2 = TaskId::from(2u32); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Task 1 gets 2/3 core usage, while task 2 gets 1/3 + let test_assignments = vec![ + (CoreAssignment::Task(task_1), PartsOf57600::FULL / 3 * 2), + (CoreAssignment::Task(task_2), PartsOf57600::FULL / 3), + ]; + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + test_assignments, + None, + )); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + // Case 1: Current assignment remaining >= step after pop + { + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.pos)), + Some(0u16) + ); + // Consumed step should be 1/3 of core parts, leaving 1/3 remaining + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.assignments[0].1.remaining)), + Some(PartsOf57600::FULL / 3) + ); + } + + // Case 2: Current assignment remaning < step after pop + { + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + // Pos should have incremented, as assignment had remaining < step + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.pos)), + Some(1u16) + ); + // Remaining should have started at 1/3 of core work parts. We then subtract + // step (1/3) and add back ratio (2/3), leaving us with 2/3 of core work parts. + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.assignments[0].1.remaining)), + Some(PartsOf57600::FULL / 3 * 2) + ); + } + + // Final check, task 2's turn to be served + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + }); +} + +#[test] +fn equal_assignments_served_equally() { + let core_idx = CoreIndex(0); + let task_1 = TaskId::from(1u32); + let task_2 = TaskId::from(2u32); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Tasks 1 and 2 get equal work parts + let test_assignments = vec![ + (CoreAssignment::Task(task_1), PartsOf57600::FULL / 2), + (CoreAssignment::Task(task_2), PartsOf57600::FULL / 2), + ]; + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + test_assignments, + None, + )); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + // Test that popped assignments alternate between tasks 1 and 2 + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + }); +} + +#[test] +// Checks that core is shared fairly, even in case of `ratio` not being +// divisible by `step` (over multiple rounds). +fn assignment_proportions_indivisible_by_step_work() { + let core_idx = CoreIndex(0); + let task_1 = TaskId::from(1u32); + let ratio_1 = PartsOf57600::FULL / 5 * 3; + let ratio_2 = PartsOf57600::FULL / 5 * 2; + let task_2 = TaskId::from(2u32); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None }); + + // Task 1 gets 3/5 core usage, while task 2 gets 2/5. That way + // step is set to 2/5 and task 1 is indivisible by step. + let test_assignments = + vec![(CoreAssignment::Task(task_1), ratio_1), (CoreAssignment::Task(task_2), ratio_2)]; + + assert_ok!(CoretimeAssigner::assign_core( + core_idx, + BlockNumberFor::::from(11u32), + test_assignments, + None, + )); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + // Pop 5 assignments. Should Result in the the following work ordering: + // 1, 2, 1, 1, 2. The remaining parts for each assignment should be same + // at the end as in the beginning. + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_1.into())) + ); + + assert_eq!( + CoretimeAssigner::pop_assignment_for_core(core_idx), + Some(Assignment::Bulk(task_2.into())) + ); + + // Remaining should equal ratio for both assignments. + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.assignments[0].1.remaining)), + Some(ratio_1) + ); + assert_eq!( + CoreDescriptors::::get(core_idx) + .current_work + .as_ref() + .and_then(|w| Some(w.assignments[1].1.remaining)), + Some(ratio_2) + ); + }); +} + +#[cfg(test)] +impl std::ops::Div for PartsOf57600 { + type Output = Self; + + fn div(self, rhs: u16) -> Self::Output { + if rhs == 0 { + panic!("Cannot divide by zero!"); + } + + Self(self.0 / rhs) + } +} + +#[cfg(test)] +impl std::ops::Mul for PartsOf57600 { + type Output = Self; + + fn mul(self, rhs: u16) -> Self { + Self(self.0 * rhs) + } +} + +#[test] +fn parts_of_57600_ops() { + assert!(PartsOf57600::new_saturating(57601).is_full()); + assert!(PartsOf57600::FULL.saturating_add(PartsOf57600(1)).is_full()); + assert_eq!(PartsOf57600::ZERO.saturating_sub(PartsOf57600(1)), PartsOf57600::ZERO); + assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(0)), Some(PartsOf57600::FULL)); + assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(1)), None); +} diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs b/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs index 42ca94d5185fc99c10930d9a242f53dfe25a4cdb..5a6060cd2b4eab88867088dee30b6fb1047bdf20 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/benchmarking.rs @@ -43,7 +43,7 @@ where { ParasShared::::set_session_index(SESSION_INDEX); let mut config = HostConfiguration::default(); - config.on_demand_cores = 1; + config.coretime_cores = 1; ConfigurationPallet::::force_set_active_config(config); let mut parachains = ParachainsCache::new(); ParasPallet::::initialize_para_now( @@ -70,11 +70,10 @@ mod benchmarks { let para_id = ParaId::from(111u32); init_parathread::(para_id); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); for _ in 0..s { - Pallet::::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back) - .unwrap(); + Pallet::::add_on_demand_order(order.clone(), QueuePushDirection::Back).unwrap(); } #[extrinsic_call] @@ -88,11 +87,10 @@ mod benchmarks { let para_id = ParaId::from(111u32); init_parathread::(para_id); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); for _ in 0..s { - Pallet::::add_on_demand_assignment(assignment.clone(), QueuePushDirection::Back) - .unwrap(); + Pallet::::add_on_demand_order(order.clone(), QueuePushDirection::Back).unwrap(); } #[extrinsic_call] diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs index acfb24cbf1943e9cc40e67338b448814c3ab7adf..de30330ac84e0a7715799d71d26fb42ce48efff8 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/mock_helpers.rs @@ -27,7 +27,7 @@ use crate::{ use primitives::{Balance, HeadData, ValidationCode}; -pub fn default_genesis_config() -> MockGenesisConfig { +fn default_genesis_config() -> MockGenesisConfig { MockGenesisConfig { configuration: crate::configuration::GenesisConfig { config: crate::configuration::HostConfiguration { ..Default::default() }, @@ -63,7 +63,7 @@ impl GenesisConfigBuilder { pub(super) fn build(self) -> MockGenesisConfig { let mut genesis = default_genesis_config(); let config = &mut genesis.configuration.config; - config.on_demand_cores = self.on_demand_cores; + config.coretime_cores = self.on_demand_cores; config.on_demand_base_fee = self.on_demand_base_fee; config.on_demand_fee_variability = self.on_demand_fee_variability; config.on_demand_queue_max_size = self.on_demand_max_queue_size; diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs index 75c29bd6fbe4f79532823468a4c0a596942491a6..1b746e88694c9f105db119d351a76e336fd3fdba 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/mod.rs @@ -32,10 +32,7 @@ mod mock_helpers; #[cfg(test)] mod tests; -use crate::{ - configuration, paras, - scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, -}; +use crate::{configuration, paras, scheduler::common::Assignment}; use frame_support::{ pallet_prelude::*, @@ -79,7 +76,7 @@ impl WeightInfo for TestWeightInfo { /// Keeps track of how many assignments a scheduler currently has at a specific `CoreIndex` for a /// specific `ParaId`. #[derive(Encode, Decode, Default, Clone, Copy, TypeInfo)] -#[cfg_attr(test, derive(PartialEq, Debug))] +#[cfg_attr(test, derive(PartialEq, RuntimeDebug))] pub struct CoreAffinityCount { core_idx: CoreIndex, count: u32, @@ -107,6 +104,18 @@ pub enum SpotTrafficCalculationErr { Division, } +/// Internal representation of an order after it has been enqueued already. +#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone)] +pub(super) struct EnqueuedOrder { + pub para_id: ParaId, +} + +impl EnqueuedOrder { + pub fn new(para_id: ParaId) -> Self { + Self { para_id } + } +} + #[frame_support::pallet] pub mod pallet { @@ -140,7 +149,7 @@ pub mod pallet { /// Creates an empty on demand queue if one isn't present in storage already. #[pallet::type_value] - pub fn OnDemandQueueOnEmpty() -> VecDeque { + pub(super) fn OnDemandQueueOnEmpty() -> VecDeque { VecDeque::new() } @@ -153,8 +162,8 @@ pub mod pallet { /// The order storage entry. Uses a VecDeque to be able to push to the front of the /// queue from the scheduler on session boundaries. #[pallet::storage] - pub type OnDemandQueue = - StorageValue<_, VecDeque, ValueQuery, OnDemandQueueOnEmpty>; + pub(super) type OnDemandQueue = + StorageValue<_, VecDeque, ValueQuery, OnDemandQueueOnEmpty>; /// Maps a `ParaId` to `CoreIndex` and keeps track of how many assignments the scheduler has in /// it's lookahead. Keeping track of this affinity prevents parallel execution of the same @@ -182,9 +191,6 @@ pub mod pallet { /// The current spot price is higher than the max amount specified in the `place_order` /// call, making it invalid. SpotPriceHigherThanMaxAmount, - /// There are no on demand cores available. `place_order` will not add anything to the - /// queue. - NoOnDemandCores, } #[pallet::hooks] @@ -248,7 +254,6 @@ pub mod pallet { /// - `InvalidParaId` /// - `QueueFull` /// - `SpotPriceHigherThanMaxAmount` - /// - `NoOnDemandCores` /// /// Events: /// - `SpotOrderPlaced` @@ -276,7 +281,6 @@ pub mod pallet { /// - `InvalidParaId` /// - `QueueFull` /// - `SpotPriceHigherThanMaxAmount` - /// - `NoOnDemandCores` /// /// Events: /// - `SpotOrderPlaced` @@ -311,7 +315,6 @@ where /// - `InvalidParaId` /// - `QueueFull` /// - `SpotPriceHigherThanMaxAmount` - /// - `NoOnDemandCores` /// /// Events: /// - `SpotOrderPlaced` @@ -323,9 +326,6 @@ where ) -> DispatchResult { let config = >::config(); - // Are there any schedulable cores in this session - ensure!(config.on_demand_cores > 0, Error::::NoOnDemandCores); - // Traffic always falls back to 1.0 let traffic = SpotTraffic::::get(); @@ -337,19 +337,22 @@ where ensure!(spot_price.le(&max_amount), Error::::SpotPriceHigherThanMaxAmount); // Charge the sending account the spot price - T::Currency::withdraw(&sender, spot_price, WithdrawReasons::FEE, existence_requirement)?; + let _ = T::Currency::withdraw( + &sender, + spot_price, + WithdrawReasons::FEE, + existence_requirement, + )?; - let assignment = Assignment::new(para_id); + let order = EnqueuedOrder::new(para_id); - let res = Pallet::::add_on_demand_assignment(assignment, QueuePushDirection::Back); + let res = Pallet::::add_on_demand_order(order, QueuePushDirection::Back); - match res { - Ok(_) => { - Pallet::::deposit_event(Event::::OnDemandOrderPlaced { para_id, spot_price }); - return Ok(()) - }, - Err(err) => return Err(err), + if res.is_ok() { + Pallet::::deposit_event(Event::::OnDemandOrderPlaced { para_id, spot_price }); } + + res } /// The spot price multiplier. This is based on the transaction fee calculations defined in: @@ -423,10 +426,10 @@ where } } - /// Adds an assignment to the on demand queue. + /// Adds an order to the on demand queue. /// /// Paramenters: - /// - `assignment`: The on demand assignment to add to the queue. + /// - `order`: The `EnqueuedOrder` to add to the queue. /// - `location`: Whether to push this entry to the back or the front of the queue. Pushing an /// entry to the front of the queue is only used when the scheduler wants to push back an /// entry it has already popped. @@ -436,12 +439,12 @@ where /// Errors: /// - `InvalidParaId` /// - `QueueFull` - pub fn add_on_demand_assignment( - assignment: Assignment, + fn add_on_demand_order( + order: EnqueuedOrder, location: QueuePushDirection, ) -> Result<(), DispatchError> { // Only parathreads are valid paraids for on the go parachains. - ensure!(>::is_parathread(assignment.para_id), Error::::InvalidParaId); + ensure!(>::is_parathread(order.para_id), Error::::InvalidParaId); let config = >::config(); @@ -449,8 +452,8 @@ where // Abort transaction if queue is too large ensure!(Self::queue_size() < config.on_demand_queue_max_size, Error::::QueueFull); match location { - QueuePushDirection::Back => queue.push_back(assignment), - QueuePushDirection::Front => queue.push_front(assignment), + QueuePushDirection::Back => queue.push_back(order), + QueuePushDirection::Front => queue.push_front(order), }; Ok(()) }) @@ -475,7 +478,8 @@ where } /// Getter for the order queue. - pub fn get_queue() -> VecDeque { + #[cfg(test)] + fn get_queue() -> VecDeque { OnDemandQueue::::get() } @@ -523,12 +527,7 @@ where } } -impl AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - let config = >::config(); - config.on_demand_cores - } - +impl Pallet { /// Take the next queued entry that is available for a given core index. /// Invalidates and removes orders with a `para_id` that is not `ParaLifecycle::Parathread` /// but only in [0..P] range slice of the order queue, where P is the element that is @@ -536,20 +535,8 @@ impl AssignmentProvider> for Pallet { /// /// Parameters: /// - `core_idx`: The core index - /// - `previous_paraid`: Which paraid was previously processed on the requested core. Is None if - /// nothing was processed on the core. - fn pop_assignment_for_core( - core_idx: CoreIndex, - previous_para: Option, - ) -> Option { - // Only decrease the affinity of the previous para if it exists. - // A nonexistant `ParaId` indicates that the scheduler has not processed any - // `ParaId` this session. - if let Some(previous_para_id) = previous_para { - Pallet::::decrease_affinity(previous_para_id, core_idx) - } - - let mut queue: VecDeque = OnDemandQueue::::get(); + pub fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { + let mut queue: VecDeque = OnDemandQueue::::get(); let mut invalidated_para_id_indexes: Vec = vec![]; @@ -586,28 +573,28 @@ impl AssignmentProvider> for Pallet { // Write changes to storage. OnDemandQueue::::set(queue); - popped + popped.map(|p| Assignment::Pool { para_id: p.para_id, core_index: core_idx }) } - /// Push an assignment back to the queue. - /// Typically used on session boundaries. + /// Report that the `para_id` & `core_index` combination was processed. + pub fn report_processed(para_id: ParaId, core_index: CoreIndex) { + Pallet::::decrease_affinity(para_id, core_index) + } + + /// Push an assignment back to the front of the queue. + /// + /// The assignment has not been processed yet. Typically used on session boundaries. /// Parameters: - /// - `core_idx`: The core index /// - `assignment`: The on demand assignment. - fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment) { - Pallet::::decrease_affinity(assignment.para_id, core_idx); + pub fn push_back_assignment(para_id: ParaId, core_index: CoreIndex) { + Pallet::::decrease_affinity(para_id, core_index); // Skip the queue on push backs from scheduler - match Pallet::::add_on_demand_assignment(assignment, QueuePushDirection::Front) { + match Pallet::::add_on_demand_order( + EnqueuedOrder::new(para_id), + QueuePushDirection::Front, + ) { Ok(_) => {}, Err(_) => {}, } } - - fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { - let config = >::config(); - AssignmentProviderConfig { - max_availability_timeouts: config.on_demand_retries, - ttl: config.on_demand_ttl, - } - } } diff --git a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs index d07964b691654b9de57b3f108b6439e2c720514f..8404700780c84e493d6436c5f3174f814c1082ef 100644 --- a/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs +++ b/polkadot/runtime/parachains/src/assigner_on_demand/tests.rs @@ -24,7 +24,6 @@ use crate::{ System, Test, }, paras::{ParaGenesisArgs, ParaKind}, - scheduler::common::Assignment, }; use frame_support::{assert_noop, assert_ok, error::BadOrigin}; use pallet_balances::Error as BalancesError; @@ -75,7 +74,7 @@ fn run_to_block( Scheduler::initializer_initialize(b + 1); // In the real runtime this is expected to be called by the `InclusionInherent` pallet. - Scheduler::update_claimqueue(BTreeMap::new(), b + 1); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); } } @@ -280,9 +279,9 @@ fn place_order_keep_alive_keeps_alive() { } #[test] -fn add_on_demand_assignment_works() { +fn add_on_demand_order_works() { let para_a = ParaId::from(111); - let assignment = Assignment::new(para_a); + let order = EnqueuedOrder::new(para_a); let mut genesis = GenesisConfigBuilder::default(); genesis.on_demand_max_queue_size = 1; @@ -292,10 +291,7 @@ fn add_on_demand_assignment_works() { // `para_a` is not onboarded as a parathread yet. assert_noop!( - OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - ), + OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back), Error::::InvalidParaId ); @@ -304,14 +300,11 @@ fn add_on_demand_assignment_works() { assert!(Paras::is_parathread(para_a)); // `para_a` is now onboarded as a valid parathread. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - )); + assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back)); // Max queue size is 1, queue should be full. assert_noop!( - OnDemandAssigner::add_on_demand_assignment(assignment, QueuePushDirection::Back), + OnDemandAssigner::add_on_demand_order(order, QueuePushDirection::Back), Error::::QueueFull ); }); @@ -330,29 +323,131 @@ fn spotqueue_push_directions() { run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); - let assignment_a = Assignment { para_id: para_a }; - let assignment_b = Assignment { para_id: para_b }; - let assignment_c = Assignment { para_id: para_c }; + let order_a = EnqueuedOrder::new(para_a); + let order_b = EnqueuedOrder::new(para_b); + let order_c = EnqueuedOrder::new(para_c); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), + assert_ok!(OnDemandAssigner::add_on_demand_order( + order_a.clone(), QueuePushDirection::Front )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b.clone(), + assert_ok!(OnDemandAssigner::add_on_demand_order( + order_b.clone(), QueuePushDirection::Front )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_c.clone(), + assert_ok!(OnDemandAssigner::add_on_demand_order( + order_c.clone(), QueuePushDirection::Back )); assert_eq!(OnDemandAssigner::queue_size(), 3); + assert_eq!(OnDemandAssigner::get_queue(), VecDeque::from(vec![order_b, order_a, order_c])) + }); +} + +#[test] +fn pop_assignment_for_core_works() { + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + let para_a = ParaId::from(111); + let para_b = ParaId::from(110); + schedule_blank_para(para_a, ParaKind::Parathread); + schedule_blank_para(para_b, ParaKind::Parathread); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + let order_a = EnqueuedOrder::new(para_a); + let order_b = EnqueuedOrder::new(para_b); + let assignment_a = Assignment::Pool { para_id: para_a, core_index: CoreIndex(0) }; + let assignment_b = Assignment::Pool { para_id: para_b, core_index: CoreIndex(1) }; + + // Pop should return none with empty queue + assert_eq!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)), None); + + // Add enough assignments to the order queue. + for _ in 0..2 { + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) + .expect("Invalid paraid or queue full"); + + OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back) + .expect("Invalid paraid or queue full"); + } + + // Queue should contain orders a, b, a, b + { + let queue: Vec = OnDemandQueue::::get().into_iter().collect(); + assert_eq!( + queue, + vec![order_a.clone(), order_b.clone(), order_a.clone(), order_b.clone()] + ); + } + + // Popped assignments should be for the correct paras and cores assert_eq!( - OnDemandAssigner::get_queue(), - VecDeque::from(vec![assignment_b, assignment_a, assignment_c]) - ) + OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)), + Some(assignment_a.clone()) + ); + assert_eq!( + OnDemandAssigner::pop_assignment_for_core(CoreIndex(1)), + Some(assignment_b.clone()) + ); + assert_eq!( + OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)), + Some(assignment_a.clone()) + ); + + // Queue should contain one left over order + { + let queue: Vec = OnDemandQueue::::get().into_iter().collect(); + assert_eq!(queue, vec![order_b.clone(),]); + } + }); +} + +#[test] +fn push_back_assignment_works() { + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + let para_a = ParaId::from(111); + let para_b = ParaId::from(110); + schedule_blank_para(para_a, ParaKind::Parathread); + schedule_blank_para(para_b, ParaKind::Parathread); + + run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); + + let order_a = EnqueuedOrder::new(para_a); + let order_b = EnqueuedOrder::new(para_b); + + // Add enough assignments to the order queue. + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) + .expect("Invalid paraid or queue full"); + + OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back) + .expect("Invalid paraid or queue full"); + + // Pop order a + OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)); + + // Para a should have affinity for core 0 + assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1); + assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().core_idx, CoreIndex(0)); + + // Queue should still contain order b + { + let queue: Vec = OnDemandQueue::::get().into_iter().collect(); + assert_eq!(queue, vec![order_b.clone()]); + } + + // Push back order a + OnDemandAssigner::push_back_assignment(para_a, CoreIndex(0)); + + // Para a should have no affinity + assert_eq!(OnDemandAssigner::get_affinity_map(para_a).is_none(), true); + + // Queue should contain orders a, b. A in front of b. + { + let queue: Vec = OnDemandQueue::::get().into_iter().collect(); + assert_eq!(queue, vec![order_a.clone(), order_b.clone()]); + } }); } @@ -360,39 +455,38 @@ fn spotqueue_push_directions() { fn affinity_changes_work() { new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { let para_a = ParaId::from(111); + let core_index = CoreIndex(0); schedule_blank_para(para_a, ParaKind::Parathread); + let order_a = EnqueuedOrder::new(para_a); run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); - let assignment_a = Assignment { para_id: para_a }; // There should be no affinity before starting. assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); // Add enough assignments to the order queue. for _ in 0..10 { - OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Front, - ) - .expect("Invalid paraid or queue full"); + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Front) + .expect("Invalid paraid or queue full"); } // There should be no affinity before the scheduler pops. assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None); + OnDemandAssigner::pop_assignment_for_core(core_index); // Affinity count is 1 after popping. assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1); - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::pop_assignment_for_core(core_index); // Affinity count is 1 after popping with a previous para. assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().count, 1); assert_eq!(OnDemandAssigner::queue_size(), 8); for _ in 0..3 { - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None); + OnDemandAssigner::pop_assignment_for_core(core_index); } // Affinity count is 4 after popping 3 times without a previous para. @@ -400,7 +494,8 @@ fn affinity_changes_work() { assert_eq!(OnDemandAssigner::queue_size(), 5); for _ in 0..5 { - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::pop_assignment_for_core(core_index); } // Affinity count should still be 4 but queue should be empty. @@ -409,12 +504,14 @@ fn affinity_changes_work() { // Pop 4 times and get to exactly 0 (None) affinity. for _ in 0..4 { - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::pop_assignment_for_core(core_index); } assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); // Decreasing affinity beyond 0 should still be None. - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::pop_assignment_for_core(core_index); assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); }); } @@ -430,28 +527,28 @@ fn affinity_prohibits_parallel_scheduling() { run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None }); - let assignment_a = Assignment { para_id: para_a }; - let assignment_b = Assignment { para_id: para_b }; + let order_a = EnqueuedOrder::new(para_a); + let order_b = EnqueuedOrder::new(para_b); // There should be no affinity before starting. assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); assert!(OnDemandAssigner::get_affinity_map(para_b).is_none()); // Add 2 assignments for para_a for every para_b. - OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); - OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); - OnDemandAssigner::add_on_demand_assignment(assignment_b.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); assert_eq!(OnDemandAssigner::queue_size(), 3); // Approximate having 1 core. for _ in 0..3 { - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None); + OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)); } // Affinity on one core is meaningless. @@ -463,24 +560,25 @@ fn affinity_prohibits_parallel_scheduling() { ); // Clear affinity - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_a)); - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_b)); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_b, 0.into()); // Add 2 assignments for para_a for every para_b. - OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); - OnDemandAssigner::add_on_demand_assignment(assignment_a.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_a.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); - OnDemandAssigner::add_on_demand_assignment(assignment_b.clone(), QueuePushDirection::Back) + OnDemandAssigner::add_on_demand_order(order_b.clone(), QueuePushDirection::Back) .expect("Invalid paraid or queue full"); - // Approximate having 2 cores. + // Approximate having 3 cores. CoreIndex 2 should be unable to obtain an assignment for _ in 0..3 { - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None); - OnDemandAssigner::pop_assignment_for_core(CoreIndex(1), None); + OnDemandAssigner::pop_assignment_for_core(CoreIndex(0)); + OnDemandAssigner::pop_assignment_for_core(CoreIndex(1)); + assert_eq!(None, OnDemandAssigner::pop_assignment_for_core(CoreIndex(2))); } // Affinity should be the same as before, but on different cores. @@ -488,38 +586,23 @@ fn affinity_prohibits_parallel_scheduling() { assert_eq!(OnDemandAssigner::get_affinity_map(para_b).unwrap().count, 1); assert_eq!(OnDemandAssigner::get_affinity_map(para_a).unwrap().core_idx, CoreIndex(0)); assert_eq!(OnDemandAssigner::get_affinity_map(para_b).unwrap().core_idx, CoreIndex(1)); - }); -} - -#[test] -fn cannot_place_order_when_no_on_demand_cores() { - let mut genesis = GenesisConfigBuilder::default(); - genesis.on_demand_cores = 0; - let para_id = ParaId::from(10); - let alice = 1u64; - let amt = 10_000_000u128; - - new_test_ext(genesis.build()).execute_with(|| { - schedule_blank_para(para_id, ParaKind::Parathread); - Balances::make_free_balance_be(&alice, amt); - assert!(!Paras::is_parathread(para_id)); - - run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); - - assert!(Paras::is_parathread(para_id)); + // Clear affinity + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_a, 0.into()); + OnDemandAssigner::report_processed(para_b, 1.into()); - assert_noop!( - OnDemandAssigner::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id), - Error::::NoOnDemandCores - ); + // There should be no affinity after clearing. + assert!(OnDemandAssigner::get_affinity_map(para_a).is_none()); + assert!(OnDemandAssigner::get_affinity_map(para_b).is_none()); }); } #[test] fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() { let para_id = ParaId::from(10); - let assignment = Assignment { para_id }; + let core_index = CoreIndex(0); + let order = EnqueuedOrder::new(para_id); new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { // Register the para_id as a parathread @@ -530,17 +613,14 @@ fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() { assert!(Paras::is_parathread(para_id)); // Add two assignments for a para_id with a valid lifecycle. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - )); + assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back)); + assert_ok!(OnDemandAssigner::add_on_demand_order(order.clone(), QueuePushDirection::Back)); // First pop is fine - assert!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), None) == Some(assignment)); + assert!( + OnDemandAssigner::pop_assignment_for_core(core_index) == + Some(Assignment::Pool { para_id, core_index }) + ); // Deregister para assert_ok!(Paras::schedule_para_cleanup(para_id)); @@ -551,6 +631,7 @@ fn on_demand_orders_cannot_be_popped_if_lifecycle_changes() { assert!(!Paras::is_parathread(para_id)); // Second pop should be None. - assert!(OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(para_id)) == None); + OnDemandAssigner::report_processed(para_id, core_index); + assert_eq!(OnDemandAssigner::pop_assignment_for_core(core_index), None); }); } diff --git a/polkadot/runtime/parachains/src/assigner_parachains.rs b/polkadot/runtime/parachains/src/assigner_parachains.rs index 866e8290052a8ebdaf7f154def8ab073005978d8..34b5d3c1ec51811e8e4c50255592b1e9344014d8 100644 --- a/polkadot/runtime/parachains/src/assigner_parachains.rs +++ b/polkadot/runtime/parachains/src/assigner_parachains.rs @@ -17,13 +17,20 @@ //! The bulk (parachain slot auction) blockspace assignment provider. //! This provider is tightly coupled with the configuration and paras modules. +#[cfg(test)] +mod mock_helpers; +#[cfg(test)] +mod tests; + +use frame_system::pallet_prelude::BlockNumberFor; +use primitives::CoreIndex; + use crate::{ configuration, paras, scheduler::common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, }; -use frame_system::pallet_prelude::BlockNumberFor; + pub use pallet::*; -use primitives::{CoreIndex, Id as ParaId}; #[frame_support::pallet] pub mod pallet { @@ -38,23 +45,18 @@ pub mod pallet { } impl AssignmentProvider> for Pallet { - fn session_core_count() -> u32 { - paras::Parachains::::decode_len().unwrap_or(0) as u32 - } - - fn pop_assignment_for_core( - core_idx: CoreIndex, - _concluded_para: Option, - ) -> Option { + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option { >::parachains() .get(core_idx.0 as usize) .copied() - .map(|para_id| Assignment::new(para_id)) + .map(Assignment::Bulk) } + fn report_processed(_: Assignment) {} + /// Bulk assignment has no need to push the assignment back on a session change, /// this is a no-op in the case of a bulk assignment slot. - fn push_assignment_for_core(_: CoreIndex, _: Assignment) {} + fn push_back_assignment(_: Assignment) {} fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig> { AssignmentProviderConfig { @@ -65,4 +67,13 @@ impl AssignmentProvider> for Pallet { ttl: 10u32.into(), } } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn get_mock_assignment(_: CoreIndex, para_id: primitives::Id) -> Assignment { + Assignment::Bulk(para_id) + } + + fn session_core_count() -> u32 { + paras::Parachains::::decode_len().unwrap_or(0) as u32 + } } diff --git a/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs b/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6e9fb074aa97e87e6fe92819cc57e5bfa2ca656 --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_parachains/mock_helpers.rs @@ -0,0 +1,83 @@ +// 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 . + +//! Helper functions for tests + +use crate::{ + mock::MockGenesisConfig, + paras::{ParaGenesisArgs, ParaKind}, +}; + +use primitives::{Balance, HeadData, ValidationCode}; +use sp_runtime::Perbill; + +fn default_genesis_config() -> MockGenesisConfig { + MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: crate::configuration::HostConfiguration { ..Default::default() }, + }, + ..Default::default() + } +} + +#[derive(Debug)] +pub struct GenesisConfigBuilder { + pub on_demand_cores: u32, + pub on_demand_base_fee: Balance, + pub on_demand_fee_variability: Perbill, + pub on_demand_max_queue_size: u32, + pub on_demand_target_queue_utilization: Perbill, + pub onboarded_on_demand_chains: Vec, +} + +impl Default for GenesisConfigBuilder { + fn default() -> Self { + Self { + on_demand_cores: 10, + on_demand_base_fee: 10_000, + on_demand_fee_variability: Perbill::from_percent(1), + on_demand_max_queue_size: 100, + on_demand_target_queue_utilization: Perbill::from_percent(25), + onboarded_on_demand_chains: vec![], + } + } +} + +impl GenesisConfigBuilder { + pub(super) fn build(self) -> MockGenesisConfig { + let mut genesis = default_genesis_config(); + let config = &mut genesis.configuration.config; + config.coretime_cores = self.on_demand_cores; + config.on_demand_base_fee = self.on_demand_base_fee; + config.on_demand_fee_variability = self.on_demand_fee_variability; + config.on_demand_queue_max_size = self.on_demand_max_queue_size; + config.on_demand_target_queue_utilization = self.on_demand_target_queue_utilization; + + let paras = &mut genesis.paras.paras; + for para_id in self.onboarded_on_demand_chains { + paras.push(( + para_id, + ParaGenesisArgs { + genesis_head: HeadData::from(vec![0u8]), + validation_code: ValidationCode::from(vec![0u8]), + para_kind: ParaKind::Parathread, + }, + )) + } + + genesis + } +} diff --git a/polkadot/runtime/parachains/src/assigner_parachains/tests.rs b/polkadot/runtime/parachains/src/assigner_parachains/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..a110686aaeb08d6bcbb77c0bf42ba2ef4ab7adbf --- /dev/null +++ b/polkadot/runtime/parachains/src/assigner_parachains/tests.rs @@ -0,0 +1,112 @@ +// 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::*; +use crate::{ + assigner_parachains::mock_helpers::GenesisConfigBuilder, + initializer::SessionChangeNotification, + mock::{ + new_test_ext, ParachainsAssigner, Paras, ParasShared, RuntimeOrigin, Scheduler, System, + }, + paras::{ParaGenesisArgs, ParaKind}, +}; +use frame_support::{assert_ok, pallet_prelude::*}; +use primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode}; +use sp_std::collections::btree_map::BTreeMap; + +fn schedule_blank_para(id: ParaId, parakind: ParaKind) { + let validation_code: ValidationCode = vec![1, 2, 3].into(); + assert_ok!(Paras::schedule_para_initialize( + id, + ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: validation_code.clone(), + para_kind: parakind, + } + )); + + assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code)); +} + +fn run_to_block( + to: BlockNumber, + new_session: impl Fn(BlockNumber) -> Option>, +) { + while System::block_number() < to { + let b = System::block_number(); + + Scheduler::initializer_finalize(); + Paras::initializer_finalize(b); + + if let Some(notification) = new_session(b + 1) { + let mut notification_with_session_index = notification; + // We will make every session change trigger an action queue. Normally this may require + // 2 or more session changes. + if notification_with_session_index.session_index == SessionIndex::default() { + notification_with_session_index.session_index = ParasShared::scheduled_session(); + } + Paras::initializer_on_new_session(¬ification_with_session_index); + Scheduler::initializer_on_new_session(¬ification_with_session_index); + } + + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + Paras::initializer_initialize(b + 1); + Scheduler::initializer_initialize(b + 1); + + // In the real runtime this is expected to be called by the `InclusionInherent` pallet. + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); + } +} + +// This and the scheduler test schedule_schedules_including_just_freed together +// ensure that next_up_on_available and next_up_on_time_out will always be +// filled with scheduler claims for lease holding parachains. (Removes the need +// for two other scheduler tests) +#[test] +fn parachains_assigner_pop_assignment_is_always_some() { + let core_index = CoreIndex(0); + let para_id = ParaId::from(10); + let expected_assignment = Assignment::Bulk(para_id); + + new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| { + // Register the para_id as a lease holding parachain + schedule_blank_para(para_id, ParaKind::Parachain); + + assert!(!Paras::is_parachain(para_id)); + run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); + assert!(Paras::is_parachain(para_id)); + + for _ in 0..20 { + assert!( + ParachainsAssigner::pop_assignment_for_core(core_index) == + Some(expected_assignment.clone()) + ); + } + + run_to_block(20, |n| if n == 20 { Some(Default::default()) } else { None }); + + for _ in 0..20 { + assert!( + ParachainsAssigner::pop_assignment_for_core(core_index) == + Some(expected_assignment.clone()) + ); + } + }); +} diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index dced24df0aec83d1f3aba619426f607f0dd88d1b..016b3fca589a5b845110d9f25199cc8b8aef5bfe 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -20,7 +20,7 @@ use crate::{ paras_inherent, scheduler::{ self, - common::{Assignment, AssignmentProviderConfig}, + common::{AssignmentProvider, AssignmentProviderConfig}, CoreOccupied, ParasEntry, }, session_info, shared, @@ -96,6 +96,8 @@ pub(crate) struct BenchBuilder { /// Make every candidate include a code upgrade by setting this to `Some` where the interior /// value is the byte length of the new code. code_upgrade: Option, + /// Specifies whether the claimqueue should be filled. + fill_claimqueue: bool, _phantom: sp_std::marker::PhantomData, } @@ -122,6 +124,7 @@ impl BenchBuilder { dispute_sessions: Default::default(), backed_and_concluding_cores: Default::default(), code_upgrade: None, + fill_claimqueue: true, _phantom: sp_std::marker::PhantomData::, } } @@ -225,6 +228,13 @@ impl BenchBuilder { self.max_validators() / self.max_validators_per_core() } + /// Set whether the claim queue should be filled. + #[cfg(not(feature = "runtime-benchmarks"))] + pub(crate) fn set_fill_claimqueue(mut self, f: bool) -> Self { + self.fill_claimqueue = f; + self + } + /// Get the minimum number of validity votes in order for a backed candidate to be included. #[cfg(feature = "runtime-benchmarks")] pub(crate) fn fallback_min_validity_votes() -> u32 { @@ -636,14 +646,14 @@ impl BenchBuilder { } else { DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) }; - let data = dispute_statement.payload_data(candidate_hash, session); + let data = dispute_statement.payload_data(candidate_hash, session).unwrap(); let statement_sig = validator_public.sign(&data).unwrap(); (dispute_statement, ValidatorIndex(validator_index), statement_sig) }) .collect(); - DisputeStatementSet { candidate_hash: candidate_hash, session, statements } + DisputeStatementSet { candidate_hash, session, statements } }) .collect() } @@ -663,14 +673,18 @@ impl BenchBuilder { inclusion::PendingAvailability::::remove_all(None); // We don't allow a core to have both disputes and be marked fully available at this block. - let cores = self.max_cores(); + let max_cores = self.max_cores(); let used_cores = (self.dispute_sessions.len() + self.backed_and_concluding_cores.len()) as u32; - assert!(used_cores <= cores); + assert!(used_cores <= max_cores); + let fill_claimqueue = self.fill_claimqueue; // NOTE: there is an n+2 session delay for these actions to take effect. // We are currently in Session 0, so these changes will take effect in Session 2. Self::setup_para_ids(used_cores); + configuration::ActiveConfig::::mutate(|c| { + c.coretime_cores = used_cores; + }); let validator_ids = Self::generate_validator_pairs(self.max_validators()); let target_session = SessionIndex::from(self.target_session); @@ -702,13 +716,33 @@ impl BenchBuilder { .map(|i| { let AssignmentProviderConfig { ttl, .. } = scheduler::Pallet::::assignment_provider_config(CoreIndex(i)); - CoreOccupied::Paras(ParasEntry::new( - Assignment::new(ParaId::from(i as u32)), - now + ttl, - )) + // Load an assignment into provider so that one is present to pop + let assignment = ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + CoreOccupied::Paras(ParasEntry::new(assignment, now + ttl)) }) .collect(); scheduler::AvailabilityCores::::set(cores); + if fill_claimqueue { + // Add items to claim queue as well: + 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); + } Bench:: { data: ParachainsInherentData { diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index bff9cc34b4fbb84c8e2b4add40d082ca3fcbbe91..4619313590ebc2d5d92dfc85a4e845c381c61607 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -26,8 +26,9 @@ use polkadot_parachain_primitives::primitives::{ MAX_HORIZONTAL_MESSAGE_NUM, MAX_UPWARD_MESSAGE_NUM, }; use primitives::{ - vstaging::NodeFeatures, AsyncBackingParams, Balance, ExecutorParamError, ExecutorParams, - SessionIndex, LEGACY_MIN_BACKING_VOTES, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AsyncBackingParams, Balance, ExecutorParamError, ExecutorParams, SessionIndex, + LEGACY_MIN_BACKING_VOTES, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, }; use sp_runtime::{traits::Zero, Perbill}; @@ -171,8 +172,8 @@ pub struct HostConfiguration { /// How long to keep code on-chain, in blocks. This should be sufficiently long that disputes /// have concluded. pub code_retention_period: BlockNumber, - /// The amount of execution cores to dedicate to on demand execution. - pub on_demand_cores: u32, + /// How many cores are managed by the coretime chain. + pub coretime_cores: u32, /// The number of retries that a on demand author has to submit their block. pub on_demand_retries: u32, /// The maximum queue size of the pay as you go module. @@ -263,6 +264,8 @@ pub struct HostConfiguration { pub minimum_backing_votes: u32, /// Node features enablement. pub node_features: NodeFeatures, + /// Params used by approval-voting + pub approval_voting_params: ApprovalVotingParams, } impl> Default for HostConfiguration { @@ -281,7 +284,7 @@ impl> Default for HostConfiguration> Default for HostConfiguration /// v8-v9: /// v9-v10: - const STORAGE_VERSION: StorageVersion = StorageVersion::new(10); + /// v10-11: + const STORAGE_VERSION: StorageVersion = StorageVersion::new(11); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -660,17 +665,18 @@ pub mod pallet { }) } - /// Set the number of on demand execution cores. + /// Set the number of coretime execution cores. + /// + /// Note that this configuration is managed by the coretime chain. Only manually change + /// this, if you really know what you are doing! #[pallet::call_index(6)] #[pallet::weight(( T::WeightInfo::set_config_with_u32(), DispatchClass::Operational, ))] - pub fn set_on_demand_cores(origin: OriginFor, new: u32) -> DispatchResult { + pub fn set_coretime_cores(origin: OriginFor, new: u32) -> DispatchResult { ensure_root(origin)?; - Self::schedule_config_update(|config| { - config.on_demand_cores = new; - }) + Self::set_coretime_cores_unchecked(new) } /// Set the number of retries for a particular on demand. @@ -1191,6 +1197,7 @@ pub mod pallet { config.on_demand_ttl = new; }) } + /// Set the minimum backing votes threshold. #[pallet::call_index(52)] #[pallet::weight(( @@ -1203,6 +1210,7 @@ pub mod pallet { config.minimum_backing_votes = new; }) } + /// Set/Unset a node feature. #[pallet::call_index(53)] #[pallet::weight(( @@ -1220,6 +1228,33 @@ pub mod pallet { config.node_features.set(index, value); }) } + + /// Set approval-voting-params. + #[pallet::call_index(54)] + #[pallet::weight(( + T::WeightInfo::set_config_with_executor_params(), + DispatchClass::Operational, + ))] + pub fn set_approval_voting_params( + origin: OriginFor, + new: ApprovalVotingParams, + ) -> DispatchResult { + ensure_root(origin)?; + Self::schedule_config_update(|config| { + config.approval_voting_params = new; + }) + } + } + + impl Pallet { + /// Set coretime cores. + /// + /// To be used if authorization is checked otherwise. + pub fn set_coretime_cores_unchecked(new: u32) -> DispatchResult { + Self::schedule_config_update(|config| { + config.coretime_cores = new; + }) + } } #[pallet::hooks] diff --git a/polkadot/runtime/parachains/src/configuration/migration.rs b/polkadot/runtime/parachains/src/configuration/migration.rs index db323d3aad93358eeecb133aaf63257d7c973c3a..2838b73092dbab4a029a684948a85123aa489906 100644 --- a/polkadot/runtime/parachains/src/configuration/migration.rs +++ b/polkadot/runtime/parachains/src/configuration/migration.rs @@ -17,6 +17,7 @@ //! A module that is responsible for migration of storage. pub mod v10; +pub mod v11; pub mod v6; pub mod v7; pub mod v8; diff --git a/polkadot/runtime/parachains/src/configuration/migration/v10.rs b/polkadot/runtime/parachains/src/configuration/migration/v10.rs index 3c934082dc1e43095b3729347eafccde032427f1..cf228610e5c9cec1dde8cfb31880fa2b1f68821f 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v10.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v10.rs @@ -16,17 +16,121 @@ //! A module that is responsible for migration of storage. -use crate::configuration::{self, Config, Pallet}; +use crate::configuration::{Config, Pallet}; use frame_support::{pallet_prelude::*, traits::Defensive, weights::Weight}; use frame_system::pallet_prelude::BlockNumberFor; -use primitives::{vstaging::NodeFeatures, SessionIndex}; +use primitives::{ + vstaging::NodeFeatures, AsyncBackingParams, Balance, ExecutorParams, SessionIndex, + LEGACY_MIN_BACKING_VOTES, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, +}; +use sp_runtime::Perbill; use sp_std::vec::Vec; use frame_support::traits::OnRuntimeUpgrade; use super::v9::V9HostConfiguration; +// All configuration of the runtime with respect to paras. +#[derive(Clone, Encode, PartialEq, Decode, Debug)] +pub struct V10HostConfiguration { + pub max_code_size: u32, + pub max_head_data_size: u32, + pub max_upward_queue_count: u32, + pub max_upward_queue_size: u32, + pub max_upward_message_size: u32, + pub max_upward_message_num_per_candidate: u32, + pub hrmp_max_message_num_per_candidate: u32, + pub validation_upgrade_cooldown: BlockNumber, + pub validation_upgrade_delay: BlockNumber, + pub async_backing_params: AsyncBackingParams, + pub max_pov_size: u32, + pub max_downward_message_size: u32, + pub hrmp_max_parachain_outbound_channels: u32, + pub hrmp_sender_deposit: Balance, + pub hrmp_recipient_deposit: Balance, + pub hrmp_channel_max_capacity: u32, + pub hrmp_channel_max_total_size: u32, + pub hrmp_max_parachain_inbound_channels: u32, + pub hrmp_channel_max_message_size: u32, + pub executor_params: ExecutorParams, + pub code_retention_period: BlockNumber, + pub on_demand_cores: u32, + pub on_demand_retries: u32, + pub on_demand_queue_max_size: u32, + pub on_demand_target_queue_utilization: Perbill, + pub on_demand_fee_variability: Perbill, + pub on_demand_base_fee: Balance, + pub on_demand_ttl: BlockNumber, + pub group_rotation_frequency: BlockNumber, + pub paras_availability_period: BlockNumber, + pub scheduling_lookahead: u32, + pub max_validators_per_core: Option, + pub max_validators: Option, + pub dispute_period: SessionIndex, + pub dispute_post_conclusion_acceptance_period: BlockNumber, + pub no_show_slots: u32, + pub n_delay_tranches: u32, + pub zeroth_delay_tranche_width: u32, + pub needed_approvals: u32, + pub relay_vrf_modulo_samples: u32, + pub pvf_voting_ttl: SessionIndex, + pub minimum_validation_upgrade_delay: BlockNumber, + pub minimum_backing_votes: u32, + pub node_features: NodeFeatures, +} -type V10HostConfiguration = configuration::HostConfiguration; +impl> Default for V10HostConfiguration { + fn default() -> Self { + Self { + async_backing_params: AsyncBackingParams { + max_candidate_depth: 0, + allowed_ancestry_len: 0, + }, + group_rotation_frequency: 1u32.into(), + paras_availability_period: 1u32.into(), + no_show_slots: 1u32.into(), + validation_upgrade_cooldown: Default::default(), + validation_upgrade_delay: 2u32.into(), + code_retention_period: Default::default(), + max_code_size: Default::default(), + max_pov_size: Default::default(), + max_head_data_size: Default::default(), + on_demand_cores: Default::default(), + on_demand_retries: Default::default(), + scheduling_lookahead: 1, + max_validators_per_core: Default::default(), + max_validators: None, + dispute_period: 6, + dispute_post_conclusion_acceptance_period: 100.into(), + n_delay_tranches: Default::default(), + zeroth_delay_tranche_width: Default::default(), + needed_approvals: Default::default(), + relay_vrf_modulo_samples: Default::default(), + max_upward_queue_count: Default::default(), + max_upward_queue_size: Default::default(), + max_downward_message_size: Default::default(), + max_upward_message_size: Default::default(), + max_upward_message_num_per_candidate: Default::default(), + hrmp_sender_deposit: Default::default(), + hrmp_recipient_deposit: Default::default(), + hrmp_channel_max_capacity: Default::default(), + hrmp_channel_max_total_size: Default::default(), + hrmp_max_parachain_inbound_channels: Default::default(), + hrmp_channel_max_message_size: Default::default(), + hrmp_max_parachain_outbound_channels: Default::default(), + hrmp_max_message_num_per_candidate: Default::default(), + pvf_voting_ttl: 2u32.into(), + minimum_validation_upgrade_delay: 2.into(), + executor_params: Default::default(), + on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + on_demand_base_fee: 10_000_000u128, + on_demand_fee_variability: Perbill::from_percent(3), + on_demand_target_queue_utilization: Perbill::from_percent(25), + on_demand_ttl: 5u32.into(), + minimum_backing_votes: LEGACY_MIN_BACKING_VOTES, + node_features: NodeFeatures::EMPTY, + } + } +} mod v9 { use super::*; diff --git a/polkadot/runtime/parachains/src/configuration/migration/v11.rs b/polkadot/runtime/parachains/src/configuration/migration/v11.rs new file mode 100644 index 0000000000000000000000000000000000000000..f4db9196b1a089723cd061897f4a6638ce1c615f --- /dev/null +++ b/polkadot/runtime/parachains/src/configuration/migration/v11.rs @@ -0,0 +1,329 @@ +// 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 . + +//! A module that is responsible for migration of storage. + +use crate::configuration::{self, Config, Pallet}; +use frame_support::{ + migrations::VersionedMigration, pallet_prelude::*, traits::Defensive, weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use primitives::{vstaging::ApprovalVotingParams, SessionIndex}; +use sp_std::vec::Vec; + +use frame_support::traits::OnRuntimeUpgrade; + +use super::v10::V10HostConfiguration; +type V11HostConfiguration = configuration::HostConfiguration; + +mod v10 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V10HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V10HostConfiguration>)>, + OptionQuery, + >; +} + +mod v11 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V11HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V11HostConfiguration>)>, + OptionQuery, + >; +} + +pub type MigrateToV11 = VersionedMigration< + 10, + 11, + UncheckedMigrateToV11, + Pallet, + ::DbWeight, +>; + +pub struct UncheckedMigrateToV11(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for UncheckedMigrateToV11 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV11"); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV11 started"); + let weight_consumed = migrate_to_v11::(); + + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV11 executed successfully"); + + weight_consumed + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade() for HostConfiguration MigrateToV11"); + ensure!( + StorageVersion::get::>() >= 11, + "Storage version should be >= 11 after the migration" + ); + + Ok(()) + } +} + +fn migrate_to_v11() -> Weight { + // Unusual formatting is justified: + // - make it easier to verify that fields assign what they supposed to assign. + // - this code is transient and will be removed after all migrations are done. + // - this code is important enough to optimize for legibility sacrificing consistency. + #[rustfmt::skip] + let translate = + |pre: V10HostConfiguration>| -> + V11HostConfiguration> + { + V11HostConfiguration { +max_code_size : pre.max_code_size, +max_head_data_size : pre.max_head_data_size, +max_upward_queue_count : pre.max_upward_queue_count, +max_upward_queue_size : pre.max_upward_queue_size, +max_upward_message_size : pre.max_upward_message_size, +max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate, +hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate, +validation_upgrade_cooldown : pre.validation_upgrade_cooldown, +validation_upgrade_delay : pre.validation_upgrade_delay, +max_pov_size : pre.max_pov_size, +max_downward_message_size : pre.max_downward_message_size, +hrmp_sender_deposit : pre.hrmp_sender_deposit, +hrmp_recipient_deposit : pre.hrmp_recipient_deposit, +hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity, +hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size, +hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels, +hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels, +hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size, +code_retention_period : pre.code_retention_period, +coretime_cores : pre.on_demand_cores, +on_demand_retries : pre.on_demand_retries, +group_rotation_frequency : pre.group_rotation_frequency, +paras_availability_period : pre.paras_availability_period, +scheduling_lookahead : pre.scheduling_lookahead, +max_validators_per_core : pre.max_validators_per_core, +max_validators : pre.max_validators, +dispute_period : pre.dispute_period, +dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period, +no_show_slots : pre.no_show_slots, +n_delay_tranches : pre.n_delay_tranches, +zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width, +needed_approvals : pre.needed_approvals, +relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples, +pvf_voting_ttl : pre.pvf_voting_ttl, +minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay, +async_backing_params : pre.async_backing_params, +executor_params : pre.executor_params, +on_demand_queue_max_size : pre.on_demand_queue_max_size, +on_demand_base_fee : pre.on_demand_base_fee, +on_demand_fee_variability : pre.on_demand_fee_variability, +on_demand_target_queue_utilization : pre.on_demand_target_queue_utilization, +on_demand_ttl : pre.on_demand_ttl, +minimum_backing_votes : pre.minimum_backing_votes, +node_features : pre.node_features, +approval_voting_params : ApprovalVotingParams { + max_approval_coalesce_count: 1, + } + } + }; + + let v10 = v10::ActiveConfig::::get() + .defensive_proof("Could not decode old config") + .unwrap_or_default(); + let v11 = translate(v10); + v11::ActiveConfig::::set(Some(v11)); + + // Allowed to be empty. + let pending_v9 = v10::PendingConfigs::::get().unwrap_or_default(); + let mut pending_v10 = Vec::new(); + + for (session, v10) in pending_v9.into_iter() { + let v11 = translate(v10); + pending_v10.push((session, v11)); + } + v11::PendingConfigs::::set(Some(pending_v10.clone())); + + let num_configs = (pending_v10.len() + 1) as u64; + T::DbWeight::get().reads_writes(num_configs, num_configs) +} + +#[cfg(test)] +mod tests { + use primitives::LEGACY_MIN_BACKING_VOTES; + + use super::*; + use crate::mock::{new_test_ext, Test}; + + #[test] + fn v11_deserialized_from_actual_data() { + // Example how to get new `raw_config`: + // We'll obtain the raw_config at a specified a block + // Steps: + // 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate + // 2. Set these parameters: + // 2.1. selected state query: configuration; activeConfig(): + // PolkadotRuntimeParachainsConfigurationHostConfiguration + // 2.2. blockhash to query at: + // 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of + // the block) + // 2.3. Note the value of encoded storage key -> + // 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the + // referenced block. + // 2.4. You'll also need the decoded values to update the test. + // 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage + // 3.1 Enter the encoded storage key and you get the raw config. + + // This exceeds the maximal line width length, but that's fine, since this is not code and + // doesn't need to be read and also leaving it as one line allows to easily copy it. + let raw_config = + hex_literal::hex![" + 0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c9018096980000000000000000000000000005000000140000000400000001000000010100000000060000006400000002000000190000000000000002000000020000000200000005000000020000000001000000" + ]; + + let v11 = + V11HostConfiguration::::decode(&mut &raw_config[..]).unwrap(); + + // We check only a sample of the values here. If we missed any fields or messed up data + // types that would skew all the fields coming after. + assert_eq!(v11.max_code_size, 3_145_728); + assert_eq!(v11.validation_upgrade_cooldown, 2); + assert_eq!(v11.max_pov_size, 5_242_880); + assert_eq!(v11.hrmp_channel_max_message_size, 1_048_576); + assert_eq!(v11.n_delay_tranches, 25); + assert_eq!(v11.minimum_validation_upgrade_delay, 5); + assert_eq!(v11.group_rotation_frequency, 20); + assert_eq!(v11.coretime_cores, 0); + assert_eq!(v11.on_demand_base_fee, 10_000_000); + assert_eq!(v11.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES); + assert_eq!(v11.approval_voting_params.max_approval_coalesce_count, 1); + } + + #[test] + fn test_migrate_to_v11() { + // Host configuration has lots of fields. However, in this migration we only add one + // field. The most important part to check are a couple of the last fields. We also pick + // extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and + // also their type. + // + // We specify only the picked fields and the rest should be provided by the `Default` + // implementation. That implementation is copied over between the two types and should work + // fine. + let v10 = V10HostConfiguration:: { + needed_approvals: 69, + paras_availability_period: 55, + hrmp_recipient_deposit: 1337, + max_pov_size: 1111, + minimum_validation_upgrade_delay: 20, + ..Default::default() + }; + + let mut pending_configs = Vec::new(); + pending_configs.push((100, v10.clone())); + pending_configs.push((300, v10.clone())); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v10 version in the state. + v10::ActiveConfig::::set(Some(v10)); + v10::PendingConfigs::::set(Some(pending_configs)); + + migrate_to_v11::(); + + let v11 = v11::ActiveConfig::::get().unwrap(); + assert_eq!(v11.approval_voting_params.max_approval_coalesce_count, 1); + + let mut configs_to_check = v11::PendingConfigs::::get().unwrap(); + configs_to_check.push((0, v11.clone())); + + for (_, v10) in configs_to_check { + #[rustfmt::skip] + { + assert_eq!(v10.max_code_size , v11.max_code_size); + assert_eq!(v10.max_head_data_size , v11.max_head_data_size); + assert_eq!(v10.max_upward_queue_count , v11.max_upward_queue_count); + assert_eq!(v10.max_upward_queue_size , v11.max_upward_queue_size); + assert_eq!(v10.max_upward_message_size , v11.max_upward_message_size); + assert_eq!(v10.max_upward_message_num_per_candidate , v11.max_upward_message_num_per_candidate); + assert_eq!(v10.hrmp_max_message_num_per_candidate , v11.hrmp_max_message_num_per_candidate); + assert_eq!(v10.validation_upgrade_cooldown , v11.validation_upgrade_cooldown); + assert_eq!(v10.validation_upgrade_delay , v11.validation_upgrade_delay); + assert_eq!(v10.max_pov_size , v11.max_pov_size); + assert_eq!(v10.max_downward_message_size , v11.max_downward_message_size); + assert_eq!(v10.hrmp_max_parachain_outbound_channels , v11.hrmp_max_parachain_outbound_channels); + assert_eq!(v10.hrmp_sender_deposit , v11.hrmp_sender_deposit); + assert_eq!(v10.hrmp_recipient_deposit , v11.hrmp_recipient_deposit); + assert_eq!(v10.hrmp_channel_max_capacity , v11.hrmp_channel_max_capacity); + assert_eq!(v10.hrmp_channel_max_total_size , v11.hrmp_channel_max_total_size); + 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_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); + assert_eq!(v10.scheduling_lookahead , v11.scheduling_lookahead); + assert_eq!(v10.max_validators_per_core , v11.max_validators_per_core); + assert_eq!(v10.max_validators , v11.max_validators); + assert_eq!(v10.dispute_period , v11.dispute_period); + assert_eq!(v10.no_show_slots , v11.no_show_slots); + assert_eq!(v10.n_delay_tranches , v11.n_delay_tranches); + assert_eq!(v10.zeroth_delay_tranche_width , v11.zeroth_delay_tranche_width); + assert_eq!(v10.needed_approvals , v11.needed_approvals); + assert_eq!(v10.relay_vrf_modulo_samples , v11.relay_vrf_modulo_samples); + assert_eq!(v10.pvf_voting_ttl , v11.pvf_voting_ttl); + 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); + }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. + } + }); + } + + // Test that migration doesn't panic in case there're no pending configurations upgrades in + // pallet's storage. + #[test] + fn test_migrate_to_v11_no_pending() { + let v10 = V10HostConfiguration::::default(); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v10 version in the state. + v10::ActiveConfig::::set(Some(v10)); + // Ensure there're no pending configs. + v11::PendingConfigs::::set(None); + + // Shouldn't fail. + migrate_to_v11::(); + }); + } +} diff --git a/polkadot/runtime/parachains/src/configuration/migration/v8.rs b/polkadot/runtime/parachains/src/configuration/migration/v8.rs index d1bc9005112529d55749a82de78fe17ff28391f7..537dfa9abd77040f7017be6bd46150aa29bfef3c 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v8.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v8.rs @@ -250,7 +250,7 @@ on_demand_fee_variability : Perbill::from_percent(3), on_demand_target_queue_utilization : Perbill::from_percent(25), on_demand_ttl : 5u32.into(), } - }; +}; let v7 = v7::ActiveConfig::::get() .defensive_proof("Could not decode old config") diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index b62a45355e1dfb486baa3585242bcce1b26fdd12..c915eb12a0ca1712a1d923d126daac959a40cd09 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -283,7 +283,7 @@ fn setting_pending_config_members() { max_code_size: 100_000, max_pov_size: 1024, max_head_data_size: 1_000, - on_demand_cores: 2, + coretime_cores: 2, on_demand_retries: 5, group_rotation_frequency: 20, paras_availability_period: 10, @@ -313,6 +313,7 @@ fn setting_pending_config_members() { pvf_voting_ttl: 3, minimum_validation_upgrade_delay: 20, executor_params: Default::default(), + approval_voting_params: ApprovalVotingParams { max_approval_coalesce_count: 1 }, on_demand_queue_max_size: 10_000u32, on_demand_base_fee: 10_000_000u128, on_demand_fee_variability: Perbill::from_percent(3), @@ -341,7 +342,7 @@ fn setting_pending_config_members() { Configuration::set_max_pov_size(RuntimeOrigin::root(), new_config.max_pov_size).unwrap(); Configuration::set_max_head_data_size(RuntimeOrigin::root(), new_config.max_head_data_size) .unwrap(); - Configuration::set_on_demand_cores(RuntimeOrigin::root(), new_config.on_demand_cores) + Configuration::set_coretime_cores(RuntimeOrigin::root(), new_config.coretime_cores) .unwrap(); Configuration::set_on_demand_retries(RuntimeOrigin::root(), new_config.on_demand_retries) .unwrap(); diff --git a/polkadot/runtime/parachains/src/coretime/benchmarking.rs b/polkadot/runtime/parachains/src/coretime/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..d1ac71f580ee0e70015bf130b6836519005ee280 --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/benchmarking.rs @@ -0,0 +1,73 @@ +// 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 . + +//! On demand assigner pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::v2::*; +use frame_support::traits::OriginTrait; +use pallet_broker::CoreIndex as BrokerCoreIndex; + +#[benchmarks] +mod benchmarks { + use super::*; + use assigner_coretime::PartsOf57600; + + #[benchmark] + fn request_core_count() { + // Setup + let root_origin = ::RuntimeOrigin::root(); + + #[extrinsic_call] + _( + root_origin as ::RuntimeOrigin, + // random core count + 100, + ) + } + + #[benchmark] + fn assign_core(s: Linear<1, 100>) { + // Setup + let root_origin = ::RuntimeOrigin::root(); + + // Use parameterized assignment count + let mut assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![0u16; s as usize - 1] + .into_iter() + .enumerate() + .map(|(index, parts)| { + (CoreAssignment::Task(index as u32), PartsOf57600::new_saturating(parts)) + }) + .collect(); + // Parts must add up to exactly 57600. Here we add all the parts in one assignment, as + // it won't effect the weight and splitting up the parts into even groupings may not + // work for every value `s`. + assignments.push((CoreAssignment::Task(s as u32), PartsOf57600::FULL)); + + let core_index: BrokerCoreIndex = 0; + + #[extrinsic_call] + _( + root_origin as ::RuntimeOrigin, + core_index, + BlockNumberFor::::from(5u32), + assignments, + Some(BlockNumberFor::::from(20u32)), + ) + } +} diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs new file mode 100644 index 0000000000000000000000000000000000000000..64c10f731988902987b47768d69696e8b96fc72a --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -0,0 +1,285 @@ +// 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 . + +//! Migrations for the Coretime pallet. + +pub use v_coretime::{GetLegacyLease, MigrateToCoretime}; + +mod v_coretime { + #[cfg(feature = "try-runtime")] + use crate::scheduler::common::AssignmentProvider; + use crate::{ + assigner_coretime, configuration, + coretime::{mk_coretime_call, Config, PartsOf57600, WeightInfo}, + paras, + }; + #[cfg(feature = "try-runtime")] + use frame_support::ensure; + use frame_support::{ + traits::{OnRuntimeUpgrade, PalletInfoAccess, StorageVersion}, + weights::Weight, + }; + use frame_system::pallet_prelude::BlockNumberFor; + use pallet_broker::{CoreAssignment, CoreMask, ScheduleItem}; + #[cfg(feature = "try-runtime")] + use parity_scale_codec::Decode; + #[cfg(feature = "try-runtime")] + use parity_scale_codec::Encode; + use polkadot_parachain_primitives::primitives::IsSystem; + use primitives::{CoreIndex, Id as ParaId}; + use sp_arithmetic::traits::SaturatedConversion; + use sp_core::Get; + use sp_runtime::BoundedVec; + #[cfg(feature = "try-runtime")] + use sp_std::vec::Vec; + use sp_std::{iter, prelude::*, result}; + use xcm::v3::{ + send_xcm, Instruction, Junction, Junctions, MultiLocation, SendError, WeightLimit, Xcm, + }; + + /// Return information about a legacy lease of a parachain. + pub trait GetLegacyLease { + /// If parachain is a lease holding parachain, return the block at which the lease expires. + fn get_parachain_lease_in_blocks(para: ParaId) -> Option; + } + + /// Migrate a chain to use coretime. + /// + /// This assumes that the `Coretime` and the `AssignerCoretime` pallets are added at the same + /// time to a runtime. + pub struct MigrateToCoretime( + sp_std::marker::PhantomData<(T, SendXcm, LegacyLease)>, + ); + + impl>> + MigrateToCoretime + { + fn already_migrated() -> bool { + // We are using the assigner coretime because the coretime pallet doesn't has any + // storage data. But both pallets are introduced at the same time, so this is fine. + let name_hash = assigner_coretime::Pallet::::name_hash(); + let mut next_key = name_hash.to_vec(); + let storage_version_key = StorageVersion::storage_key::>(); + + loop { + match sp_io::storage::next_key(&next_key) { + // StorageVersion is initialized before, so we need to ingore it. + Some(key) if &key == &storage_version_key => { + next_key = key; + }, + // If there is any other key with the prefix of the pallet, + // we already have executed the migration. + Some(key) if key.starts_with(&name_hash) => { + log::info!("`MigrateToCoretime` already executed!"); + return true + }, + // Any other key/no key means that we did not yet have migrated. + None | Some(_) => return false, + } + } + } + } + + impl< + T: Config + crate::dmp::Config, + SendXcm: xcm::v3::SendXcm, + LegacyLease: GetLegacyLease>, + > OnRuntimeUpgrade for MigrateToCoretime + { + fn on_runtime_upgrade() -> Weight { + if Self::already_migrated() { + return Weight::zero() + } + + log::info!("Migrating existing parachains to coretime."); + migrate_to_coretime::() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + if Self::already_migrated() { + return Ok(Vec::new()) + } + + let legacy_paras = paras::Parachains::::get(); + let config = >::config(); + let total_core_count = config.coretime_cores + legacy_paras.len() as u32; + + let dmp_queue_size = + crate::dmp::Pallet::::dmq_contents(T::BrokerId::get().into()).len() as u32; + + let total_core_count = total_core_count as u32; + + Ok((total_core_count, dmp_queue_size).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::DispatchError> { + if state.is_empty() { + return Ok(()) + } + + log::trace!("Running post_upgrade()"); + + let (prev_core_count, prev_dmp_queue_size) = + <(u32, u32)>::decode(&mut &state[..]).unwrap(); + + let dmp_queue_size = + crate::dmp::Pallet::::dmq_contents(T::BrokerId::get().into()).len() as u32; + let new_core_count = assigner_coretime::Pallet::::session_core_count(); + ensure!(new_core_count == prev_core_count, "Total number of cores need to not change."); + ensure!( + dmp_queue_size == prev_dmp_queue_size + 1, + "There should have been enqueued one DMP message." + ); + + Ok(()) + } + } + + // Migrate to Coretime. + // + // NOTE: Also migrates coretime_cores config value in configuration::ActiveConfig. + fn migrate_to_coretime< + T: Config, + SendXcm: xcm::v3::SendXcm, + LegacyLease: GetLegacyLease>, + >() -> Weight { + let legacy_paras = paras::Pallet::::parachains(); + let legacy_count = legacy_paras.len() as u32; + let now = >::block_number(); + for (core, para_id) in legacy_paras.into_iter().enumerate() { + let r = assigner_coretime::Pallet::::assign_core( + CoreIndex(core as u32), + now, + vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)], + None, + ); + if let Err(err) = r { + log::error!( + "Creating assignment for existing para failed: {:?}, error: {:?}", + para_id, + err + ); + } + } + + let config = >::config(); + // coretime_cores was on_demand_cores until now: + for on_demand in 0..config.coretime_cores { + let core = CoreIndex(legacy_count.saturating_add(on_demand as _)); + let r = assigner_coretime::Pallet::::assign_core( + core, + now, + vec![(CoreAssignment::Pool, PartsOf57600::FULL)], + None, + ); + if let Err(err) = r { + log::error!("Creating assignment for existing on-demand core, failed: {:?}", err); + } + } + let total_cores = config.coretime_cores + legacy_count; + configuration::ActiveConfig::::mutate(|c| { + c.coretime_cores = total_cores; + }); + + if let Err(err) = migrate_send_assignments_to_coretime_chain::() { + log::error!("Sending legacy chain data to coretime chain failed: {:?}", err); + } + + let single_weight = ::WeightInfo::assign_core(1); + single_weight + .saturating_mul(u64::from(legacy_count.saturating_add(config.coretime_cores))) + // Second read from sending assignments to the coretime chain. + .saturating_add(T::DbWeight::get().reads_writes(2, 1)) + } + + fn migrate_send_assignments_to_coretime_chain< + T: Config, + SendXcm: xcm::v3::SendXcm, + LegacyLease: GetLegacyLease>, + >() -> result::Result<(), SendError> { + let legacy_paras = paras::Pallet::::parachains(); + let legacy_paras_count = legacy_paras.len(); + let (system_chains, lease_holding): (Vec<_>, Vec<_>) = + legacy_paras.into_iter().partition(IsSystem::is_system); + + let reservations = system_chains.into_iter().map(|p| { + let schedule = BoundedVec::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: CoreAssignment::Task(p.into()), + }]); + mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule)) + }); + + let leases = lease_holding.into_iter().filter_map(|p| { + log::trace!(target: "coretime-migration", "Preparing sending of lease holding para {:?}", p); + let Some(valid_until) = LegacyLease::get_parachain_lease_in_blocks(p) else { + log::error!("Lease holding chain with no lease information?!"); + return None + }; + let valid_until: u32 = match valid_until.try_into() { + Ok(val) => val, + Err(_) => { + log::error!("Converting block number to u32 failed!"); + return None + }, + }; + // We assume the coretime chain set this parameter to the recommened value in RFC-1: + const TIME_SLICE_PERIOD: u32 = 80; + let round_up = if valid_until % TIME_SLICE_PERIOD > 0 { 1 } else { 0 }; + let time_slice = valid_until / TIME_SLICE_PERIOD + TIME_SLICE_PERIOD * round_up; + log::trace!(target: "coretime-migration", "Sending of lease holding para {:?}, valid_until: {:?}, time_slice: {:?}", p, valid_until, time_slice); + Some(mk_coretime_call(crate::coretime::CoretimeCalls::SetLease(p.into(), time_slice))) + }); + + let core_count: u16 = configuration::Pallet::::config().coretime_cores.saturated_into(); + let set_core_count = iter::once(mk_coretime_call( + crate::coretime::CoretimeCalls::NotifyCoreCount(core_count), + )); + + let pool = (legacy_paras_count..core_count.into()).map(|_| { + let schedule = BoundedVec::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: CoreAssignment::Pool, + }]); + // Reserved cores will come before lease cores, so cores will change their assignments + // when coretime chain sends us their assign_core calls -> Good test. + mk_coretime_call(crate::coretime::CoretimeCalls::Reserve(schedule)) + }); + + let message_content = iter::once(Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }) + .chain(reservations) + .chain(pool) + .chain(leases) + .chain(set_core_count) + .collect(); + + let message = Xcm(message_content); + + send_xcm::( + MultiLocation { + parents: 0, + interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())), + }, + message, + )?; + Ok(()) + } +} diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8da6ccf07ca7e5e33513c1376c236c93b679b4d5 --- /dev/null +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -0,0 +1,253 @@ +// 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 . + +//! Extrinsics implementing the relay chain side of the Coretime interface. +//! +//! + +use sp_std::{prelude::*, result}; + +use frame_support::{pallet_prelude::*, traits::Currency}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use pallet_broker::{CoreAssignment, CoreIndex as BrokerCoreIndex}; +use primitives::{CoreIndex, Id as ParaId}; +use sp_arithmetic::traits::SaturatedConversion; +use xcm::v3::{ + send_xcm, Instruction, Junction, Junctions, MultiLocation, OriginKind, SendXcm, Xcm, +}; + +use crate::{ + assigner_coretime::{self, PartsOf57600}, + initializer::{OnNewSession, SessionChangeNotification}, + origin::{ensure_parachain, Origin}, +}; + +mod benchmarking; +pub mod migration; + +pub trait WeightInfo { + fn request_core_count() -> Weight; + //fn request_revenue_info_at() -> Weight; + //fn credit_account() -> Weight; + fn assign_core(s: u32) -> Weight; +} + +/// A weight info that is only suitable for testing. +pub struct TestWeightInfo; + +impl WeightInfo for TestWeightInfo { + fn request_core_count() -> Weight { + Weight::MAX + } + // TODO: Add real benchmarking functionality for each of these to + // benchmarking.rs, then uncomment here and in trait definition. + /*fn request_revenue_info_at() -> Weight { + Weight::MAX + } + fn credit_account() -> Weight { + Weight::MAX + }*/ + fn assign_core(_s: u32) -> Weight { + Weight::MAX + } +} + +/// Broker pallet index on the coretime chain. Used to +/// +/// construct remote calls. The codec index must correspond to the index of `Broker` in the +/// `construct_runtime` of the coretime chain. +#[derive(Encode, Decode)] +enum BrokerRuntimePallets { + #[codec(index = 50)] + Broker(CoretimeCalls), +} + +/// Call encoding for the calls needed from the Broker pallet. +#[derive(Encode, Decode)] +enum CoretimeCalls { + #[codec(index = 1)] + Reserve(pallet_broker::Schedule), + #[codec(index = 3)] + SetLease(pallet_broker::TaskId, pallet_broker::Timeslice), + #[codec(index = 19)] + NotifyCoreCount(u16), +} + +#[frame_support::pallet] +pub mod pallet { + use crate::configuration; + + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + assigner_coretime::Config { + type RuntimeOrigin: From<::RuntimeOrigin> + + Into::RuntimeOrigin>>; + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The runtime's definition of a Currency. + type Currency: Currency; + /// The ParaId of the broker system parachain. + #[pallet::constant] + type BrokerId: Get; + /// Something that provides the weight of this pallet. + type WeightInfo: WeightInfo; + type SendXcm: SendXcm; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The broker chain has asked for revenue information for a specific block. + RevenueInfoRequested { when: BlockNumberFor }, + /// A core has received a new assignment from the broker chain. + CoreAssigned { core: CoreIndex }, + } + + #[pallet::error] + pub enum Error { + /// The paraid making the call is not the coretime brokerage system parachain. + NotBroker, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(::WeightInfo::request_core_count())] + #[pallet::call_index(1)] + pub fn request_core_count(origin: OriginFor, count: u16) -> DispatchResult { + // Ignore requests not coming from the broker parachain or root. + Self::ensure_root_or_para(origin, ::BrokerId::get().into())?; + + configuration::Pallet::::set_coretime_cores_unchecked(u32::from(count)) + } + + //// TODO Impl me! + ////#[pallet::weight(::WeightInfo::request_revenue_info_at())] + //#[pallet::call_index(2)] + //pub fn request_revenue_info_at( + // origin: OriginFor, + // _when: BlockNumberFor, + //) -> DispatchResult { + // // Ignore requests not coming from the broker parachain or root. + // Self::ensure_root_or_para(origin, ::BrokerId::get().into())?; + // Ok(()) + //} + + //// TODO Impl me! + ////#[pallet::weight(::WeightInfo::credit_account())] + //#[pallet::call_index(3)] + //pub fn credit_account( + // origin: OriginFor, + // _who: T::AccountId, + // _amount: BalanceOf, + //) -> DispatchResult { + // // Ignore requests not coming from the broker parachain or root. + // Self::ensure_root_or_para(origin, ::BrokerId::get().into())?; + // Ok(()) + //} + + /// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is + /// to be used. + /// + /// Parameters: + /// -`origin`: The `ExternalBrokerOrigin`, assumed to be the Broker system parachain. + /// -`core`: The core that should be scheduled. + /// -`begin`: The starting blockheight of the instruction. + /// -`assignment`: How the blockspace should be utilised. + /// -`end_hint`: An optional hint as to when this particular set of instructions will end. + // The broker pallet's `CoreIndex` definition is `u16` but on the relay chain it's `struct + // CoreIndex(u32)` + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::assign_core(assignment.len() as u32))] + pub fn assign_core( + origin: OriginFor, + core: BrokerCoreIndex, + begin: BlockNumberFor, + assignment: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) -> DispatchResult { + // Ignore requests not coming from the broker parachain or root. + Self::ensure_root_or_para(origin, T::BrokerId::get().into())?; + + let core = u32::from(core).into(); + + >::assign_core(core, begin, assignment, end_hint)?; + Self::deposit_event(Event::::CoreAssigned { core }); + Ok(()) + } + } +} + +impl Pallet { + /// Ensure the origin is one of Root or the `para` itself. + fn ensure_root_or_para( + origin: ::RuntimeOrigin, + id: ParaId, + ) -> DispatchResult { + if let Ok(caller_id) = ensure_parachain(::RuntimeOrigin::from(origin.clone())) + { + // Check if matching para id... + ensure!(caller_id == id, Error::::NotBroker); + } else { + // Check if root... + ensure_root(origin.clone())?; + } + Ok(()) + } + + pub fn initializer_on_new_session(notification: &SessionChangeNotification>) { + let old_core_count = notification.prev_config.coretime_cores; + let new_core_count = notification.new_config.coretime_cores; + if new_core_count != old_core_count { + let core_count: u16 = new_core_count.saturated_into(); + let message = Xcm(vec![mk_coretime_call( + crate::coretime::CoretimeCalls::NotifyCoreCount(core_count), + )]); + if let Err(err) = send_xcm::( + MultiLocation { + parents: 0, + interior: Junctions::X1(Junction::Parachain(T::BrokerId::get())), + }, + message, + ) { + log::error!("Sending `NotifyCoreCount` to coretime chain failed: {:?}", err); + } + } + } +} + +impl OnNewSession> for Pallet { + fn on_new_session(notification: &SessionChangeNotification>) { + Self::initializer_on_new_session(notification); + } +} + +fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruction<()> { + Instruction::Transact { + origin_kind: OriginKind::Superuser, + // Largest call is set_lease with 1526 byte: + // Longest call is reserve() with 31_000_000 + require_weight_at_most: Weight::from_parts(100_000_000, 20_000), + call: BrokerRuntimePallets::Broker(call).encode().into(), + } +} diff --git a/polkadot/runtime/parachains/src/disputes.rs b/polkadot/runtime/parachains/src/disputes.rs index cf2e99e7359abf59d47d3d0c03667a860ab57559..c2383dad3053882b7abec959446b23d149aa4a5f 100644 --- a/polkadot/runtime/parachains/src/disputes.rs +++ b/polkadot/runtime/parachains/src/disputes.rs @@ -25,11 +25,11 @@ use frame_system::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use polkadot_runtime_metrics::get_current_time; use primitives::{ - byzantine_threshold, supermajority_threshold, ApprovalVote, CandidateHash, - CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CompactStatement, ConsensusLog, - DisputeState, DisputeStatement, DisputeStatementSet, ExplicitDisputeStatement, - InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, SigningContext, - ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature, + byzantine_threshold, supermajority_threshold, vstaging::ApprovalVoteMultipleCandidates, + ApprovalVote, CandidateHash, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, + CompactStatement, ConsensusLog, DisputeState, DisputeStatement, DisputeStatementSet, + ExplicitDisputeStatement, InvalidDisputeStatementKind, MultiDisputeStatementSet, SessionIndex, + SigningContext, ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorSignature, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -952,6 +952,8 @@ impl Pallet { None => return StatementSetFilter::RemoveAll, }; + let config = >::config(); + let n_validators = session_info.validators.len(); // Check for ancient. @@ -1015,7 +1017,14 @@ impl Pallet { set.session, statement, signature, + // This is here to prevent malicious nodes of generating + // `ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates` before that + // is enabled, via setting `max_approval_coalesce_count` in the parachain host + // config. + config.approval_voting_params.max_approval_coalesce_count > 1, ) { + log::warn!("Failed to check dispute signature"); + importer.undo(undo); filter.remove_index(i); continue @@ -1260,22 +1269,31 @@ fn check_signature( session: SessionIndex, statement: &DisputeStatement, validator_signature: &ValidatorSignature, + approval_multiple_candidates_enabled: bool, ) -> Result<(), ()> { - let payload = match *statement { + let payload = match statement { DisputeStatement::Valid(ValidDisputeStatementKind::Explicit) => ExplicitDisputeStatement { valid: true, candidate_hash, session }.signing_payload(), DisputeStatement::Valid(ValidDisputeStatementKind::BackingSeconded(inclusion_parent)) => CompactStatement::Seconded(candidate_hash).signing_payload(&SigningContext { session_index: session, - parent_hash: inclusion_parent, + parent_hash: *inclusion_parent, }), DisputeStatement::Valid(ValidDisputeStatementKind::BackingValid(inclusion_parent)) => CompactStatement::Valid(candidate_hash).signing_payload(&SigningContext { session_index: session, - parent_hash: inclusion_parent, + parent_hash: *inclusion_parent, }), DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalChecking) => ApprovalVote(candidate_hash).signing_payload(session), + DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates( + candidates, + )) => + if approval_multiple_candidates_enabled && candidates.contains(&candidate_hash) { + ApprovalVoteMultipleCandidates(candidates).signing_payload(session) + } else { + return Err(()) + }, DisputeStatement::Invalid(InvalidDisputeStatementKind::Explicit) => ExplicitDisputeStatement { valid: false, candidate_hash, session }.signing_payload(), }; diff --git a/polkadot/runtime/parachains/src/disputes/tests.rs b/polkadot/runtime/parachains/src/disputes/tests.rs index 0757084084f64349e14e9f9df192fb3a479db7db..1f3f00132d680ce16b9fdf65e21a5af92f89caea 100644 --- a/polkadot/runtime/parachains/src/disputes/tests.rs +++ b/polkadot/runtime/parachains/src/disputes/tests.rs @@ -1500,7 +1500,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_1 + &signed_1, + true, ) .is_ok()); assert!(check_signature( @@ -1508,7 +1509,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_1 + &signed_1, + true ) .is_err()); assert!(check_signature( @@ -1516,7 +1518,8 @@ fn test_check_signature() { wrong_candidate_hash, session, &statement_1, - &signed_1 + &signed_1, + true, ) .is_err()); assert!(check_signature( @@ -1524,7 +1527,8 @@ fn test_check_signature() { candidate_hash, wrong_session, &statement_1, - &signed_1 + &signed_1, + true ) .is_err()); assert!(check_signature( @@ -1532,7 +1536,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_1 + &signed_1, + true, ) .is_err()); assert!(check_signature( @@ -1540,7 +1545,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_1 + &signed_1, + true ) .is_err()); assert!(check_signature( @@ -1548,7 +1554,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_1 + &signed_1, + true ) .is_err()); assert!(check_signature( @@ -1556,7 +1563,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_1 + &signed_1, + true, ) .is_err()); @@ -1565,7 +1573,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_2 + &signed_2, + true, ) .is_ok()); assert!(check_signature( @@ -1573,7 +1582,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1581,7 +1591,8 @@ fn test_check_signature() { wrong_candidate_hash, session, &statement_2, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1589,7 +1600,8 @@ fn test_check_signature() { candidate_hash, wrong_session, &statement_2, - &signed_2 + &signed_2, + true ) .is_err()); assert!(check_signature( @@ -1597,7 +1609,8 @@ fn test_check_signature() { candidate_hash, session, &wrong_statement_2, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1605,7 +1618,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1613,7 +1627,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1621,7 +1636,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_2 + &signed_2, + true, ) .is_err()); assert!(check_signature( @@ -1629,7 +1645,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_2 + &signed_2, + true, ) .is_err()); @@ -1638,7 +1655,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_3 + &signed_3, + true, ) .is_ok()); assert!(check_signature( @@ -1646,7 +1664,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1654,7 +1673,8 @@ fn test_check_signature() { wrong_candidate_hash, session, &statement_3, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1662,7 +1682,8 @@ fn test_check_signature() { candidate_hash, wrong_session, &statement_3, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1670,7 +1691,8 @@ fn test_check_signature() { candidate_hash, session, &wrong_statement_3, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1678,7 +1700,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1686,7 +1709,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_3 + &signed_3, + true ) .is_err()); assert!(check_signature( @@ -1694,7 +1718,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_3 + &signed_3, + true, ) .is_err()); assert!(check_signature( @@ -1702,7 +1727,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_3 + &signed_3, + true, ) .is_err()); @@ -1711,7 +1737,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_4 + &signed_4, + true, ) .is_ok()); assert!(check_signature( @@ -1719,7 +1746,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1727,7 +1755,8 @@ fn test_check_signature() { wrong_candidate_hash, session, &statement_4, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1735,7 +1764,8 @@ fn test_check_signature() { candidate_hash, wrong_session, &statement_4, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1743,7 +1773,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1751,7 +1782,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1759,7 +1791,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_4 + &signed_4, + true, ) .is_err()); assert!(check_signature( @@ -1767,7 +1800,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_4 + &signed_4, + true, ) .is_err()); @@ -1776,7 +1810,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_5 + &signed_5, + true, ) .is_ok()); assert!(check_signature( @@ -1784,7 +1819,8 @@ fn test_check_signature() { candidate_hash, session, &statement_5, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1792,7 +1828,8 @@ fn test_check_signature() { wrong_candidate_hash, session, &statement_5, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1800,7 +1837,8 @@ fn test_check_signature() { candidate_hash, wrong_session, &statement_5, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1808,7 +1846,8 @@ fn test_check_signature() { candidate_hash, session, &statement_1, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1816,7 +1855,8 @@ fn test_check_signature() { candidate_hash, session, &statement_2, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1824,7 +1864,8 @@ fn test_check_signature() { candidate_hash, session, &statement_3, - &signed_5 + &signed_5, + true, ) .is_err()); assert!(check_signature( @@ -1832,7 +1873,8 @@ fn test_check_signature() { candidate_hash, session, &statement_4, - &signed_5 + &signed_5, + true, ) .is_err()); } diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 6bb731671f6f8afbb350dff2a2821f6ab31da5a0..232e65d78ed2aef86aa7903d88cbe99236ba9ec1 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -47,7 +47,7 @@ use test_helpers::{dummy_collator, dummy_collator_signature, dummy_validation_co fn default_config() -> HostConfiguration { let mut config = HostConfiguration::default(); - config.on_demand_cores = 1; + config.coretime_cores = 1; config.max_code_size = 0b100000; config.max_head_data_size = 0b100000; config.group_rotation_frequency = u32::MAX; @@ -218,7 +218,7 @@ pub(crate) fn run_to_block( } pub(crate) fn expected_bits() -> usize { - Paras::parachains().len() + Configuration::config().on_demand_cores as usize + Paras::parachains().len() + Configuration::config().coretime_cores as usize } fn default_bitfield() -> AvailabilityBitfield { diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs index b4f8721be5188e6fd8840e18accdfcaababdf61a..3c8ab7c4726fe1687cebd7ca516584f6cd35e431 100644 --- a/polkadot/runtime/parachains/src/initializer.rs +++ b/polkadot/runtime/parachains/src/initializer.rs @@ -60,6 +60,16 @@ pub struct SessionChangeNotification { pub session_index: SessionIndex, } +/// Inform something about a new session. +pub trait OnNewSession { + /// A new session was started. + fn on_new_session(notification: &SessionChangeNotification); +} + +impl OnNewSession for () { + fn on_new_session(_: &SessionChangeNotification) {} +} + /// Number of validators (not only parachain) in a session. pub type ValidatorSetCount = u32; @@ -120,6 +130,10 @@ pub mod pallet { type Randomness: Randomness>; /// An origin which is allowed to force updates to parachains. type ForceOrigin: EnsureOrigin<::RuntimeOrigin>; + /// Temporary hack to call `Coretime::on_new_session` on chains that support `Coretime` or + /// to disable it on the ones that don't support it. Can be removed and replaced by a simple + /// bound to `coretime::Config` once all chains support it. + type CoretimeOnNewSession: OnNewSession>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -271,6 +285,7 @@ impl Pallet { T::SlashingHandler::initializer_on_new_session(session_index); dmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); hrmp::Pallet::::initializer_on_new_session(¬ification, &outgoing_paras); + T::CoretimeOnNewSession::on_new_session(¬ification); } /// Should be called when a new session occurs. Buffers the session notification to be applied diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index 2509edbee3cbe00420bf734fe0df920d122460cc..b0dc27b72863fe76d5d23a9bf18408907d2b89dd 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -23,10 +23,11 @@ #![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "256")] #![cfg_attr(not(feature = "std"), no_std)] -pub mod assigner; +pub mod assigner_coretime; pub mod assigner_on_demand; pub mod assigner_parachains; pub mod configuration; +pub mod coretime; pub mod disputes; pub mod dmp; pub mod hrmp; diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index 222942922f911c9043b5b5e86d023937310b1194..fbaab1d24aafcf686582789ec06988f3179d267b 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -17,12 +17,17 @@ //! Mocks for all the traits. use crate::{ - assigner, assigner_on_demand, assigner_parachains, configuration, disputes, dmp, hrmp, + assigner_coretime, assigner_on_demand, assigner_parachains, configuration, coretime, disputes, + dmp, hrmp, inclusion::{self, AggregateMessageOrigin, UmpQueueId}, initializer, origin, paras, paras::ParaKind, - paras_inherent, scheduler, session_info, shared, ParaId, + paras_inherent, scheduler, + scheduler::common::{AssignmentProvider, AssignmentProviderConfig}, + session_info, shared, ParaId, }; +use frame_support::pallet_prelude::*; +use primitives::CoreIndex; use frame_support::{ assert_ok, derive_impl, parameter_types, @@ -45,7 +50,9 @@ use sp_runtime::{ transaction_validity::TransactionPriority, BuildStorage, FixedU128, Perbill, Permill, }; +use sp_std::collections::vec_deque::VecDeque; use std::{cell::RefCell, collections::HashMap}; +use xcm::v3::{MultiAssets, MultiLocation, SendError, SendResult, SendXcm, Xcm, XcmHash}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlockU32; @@ -62,9 +69,11 @@ frame_support::construct_runtime!( ParaInclusion: inclusion, ParaInherent: paras_inherent, Scheduler: scheduler, - Assigner: assigner, - OnDemandAssigner: assigner_on_demand, + MockAssigner: mock_assigner, ParachainsAssigner: assigner_parachains, + OnDemandAssigner: assigner_on_demand, + CoretimeAssigner: assigner_coretime, + Coretime: coretime, Initializer: initializer, Dmp: dmp, Hrmp: hrmp, @@ -178,6 +187,7 @@ impl crate::initializer::Config for Test { type Randomness = TestRandomness; type ForceOrigin = frame_system::EnsureRoot; type WeightInfo = (); + type CoretimeOnNewSession = Coretime; } impl crate::configuration::Config for Test { @@ -217,6 +227,7 @@ impl crate::paras::Config for Test { type QueueFootprinter = ParaInclusion; type NextSessionRotation = TestNextSessionRotation; type OnNewHead = (); + type AssignCoretime = (); } impl crate::dmp::Config for Test {} @@ -288,7 +299,7 @@ impl crate::disputes::SlashingHandler for Test { } impl crate::scheduler::Config for Test { - type AssignmentProvider = Assigner; + type AssignmentProvider = MockAssigner; } pub struct TestMessageQueueWeight; @@ -342,17 +353,12 @@ impl pallet_message_queue::Config for Test { type ServiceWeight = MessageQueueServiceWeight; } -impl assigner::Config for Test { - type ParachainsAssignmentProvider = ParachainsAssigner; - type OnDemandAssignmentProvider = OnDemandAssigner; -} - -impl assigner_parachains::Config for Test {} - parameter_types! { pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1); } +impl assigner_parachains::Config for Test {} + impl assigner_on_demand::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -360,6 +366,37 @@ impl assigner_on_demand::Config for Test { type WeightInfo = crate::assigner_on_demand::TestWeightInfo; } +impl assigner_coretime::Config for Test {} + +parameter_types! { + pub const BrokerId: u32 = 10u32; +} + +impl coretime::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = pallet_balances::Pallet; + type BrokerId = BrokerId; + type WeightInfo = crate::coretime::TestWeightInfo; + type SendXcm = DummyXcmSender; +} + +pub struct DummyXcmSender; +impl SendXcm for DummyXcmSender { + type Ticket = (); + fn validate( + _: &mut Option, + _: &mut Option>, + ) -> SendResult { + Ok(((), MultiAssets::new())) + } + + /// Actually carry out the delivery operation for a previously validated message sending. + fn deliver(_ticket: Self::Ticket) -> Result { + Ok([0u8; 32]) + } +} + impl crate::inclusion::Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; @@ -390,6 +427,104 @@ impl ValidatorSetWithIdentification for MockValidatorSet { type IdentificationOf = FoolIdentificationOf; } +/// A mock assigner which acts as the scheduler's `AssignmentProvider` for tests. The mock +/// assigner provides bare minimum functionality to test scheduler internals. Since they +/// have no direct effect on scheduler state, AssignmentProvider functions such as +/// `push_back_assignment` can be left empty. +pub mod mock_assigner { + use crate::scheduler::common::Assignment; + + use super::*; + pub use pallet::*; + + #[frame_support::pallet] + pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + configuration::Config + paras::Config {} + + #[pallet::storage] + pub(super) type MockAssignmentQueue = + StorageValue<_, VecDeque, ValueQuery>; + + #[pallet::storage] + pub(super) type MockProviderConfig = + StorageValue<_, AssignmentProviderConfig, OptionQuery>; + + #[pallet::storage] + pub(super) type MockCoreCount = StorageValue<_, u32, OptionQuery>; + } + + impl Pallet { + /// Adds a claim to the `MockAssignmentQueue` this claim can later be popped by the + /// scheduler when filling the claim queue for tests. + pub fn add_test_assignment(assignment: Assignment) { + MockAssignmentQueue::::mutate(|queue| queue.push_back(assignment)); + } + + // This configuration needs to be customized to service `get_provider_config` in + // scheduler tests. + pub fn set_assignment_provider_config(config: AssignmentProviderConfig) { + MockProviderConfig::::set(Some(config)); + } + + // Allows for customized core count in scheduler tests, rather than a core count + // derived from on-demand config + parachain count. + pub fn set_core_count(count: u32) { + MockCoreCount::::set(Some(count)); + } + } + + impl AssignmentProvider for Pallet { + // With regards to popping_assignments, the scheduler just needs to be tested under + // the following two conditions: + // 1. An assignment is provided + // 2. No assignment is provided + // A simple assignment queue populated to fit each test fulfills these needs. + fn pop_assignment_for_core(_core_idx: CoreIndex) -> Option { + let mut queue: VecDeque = MockAssignmentQueue::::get(); + let front = queue.pop_front(); + // Write changes to storage. + MockAssignmentQueue::::set(queue); + front + } + + // We don't care about core affinity in the test assigner + fn report_processed(_assignment: Assignment) {} + + // The results of this are tested in assigner_on_demand tests. No need to represent it + // in the mock assigner. + fn push_back_assignment(_assignment: Assignment) {} + + // Gets the provider config we set earlier using `set_assignment_provider_config`, falling + // back to the on demand parachain configuration if none was set. + fn get_provider_config(_core_idx: CoreIndex) -> AssignmentProviderConfig { + match MockProviderConfig::::get() { + Some(config) => config, + None => AssignmentProviderConfig { + max_availability_timeouts: 1, + ttl: BlockNumber::from(5u32), + }, + } + } + #[cfg(any(feature = "runtime-benchmarks", test))] + fn get_mock_assignment(_: CoreIndex, para_id: ParaId) -> Assignment { + Assignment::Bulk(para_id) + } + + fn session_core_count() -> u32 { + MockCoreCount::::get().unwrap_or(5) + } + } +} + +impl mock_assigner::pallet::Config for Test {} + pub struct FoolIdentificationOf; impl sp_runtime::traits::Convert> for FoolIdentificationOf { fn convert(_: AccountId) -> Option<()> { diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index ef9dfedd735078a61849b9543d51b4721bb494c6..e97df8e4a2b3aaeee4e4d69c3c39262c23780588 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -506,6 +506,21 @@ impl OnNewHead for Tuple { } } +/// Assign coretime to some parachain. +/// +/// This assigns coretime to a parachain without using the coretime chain. Thus, this should only be +/// used for testing purposes. +pub trait AssignCoretime { + /// ONLY USE FOR TESTING OR GENESIS. + fn assign_coretime(id: ParaId) -> DispatchResult; +} + +impl AssignCoretime for () { + fn assign_coretime(_: ParaId) -> DispatchResult { + Ok(()) + } +} + pub trait WeightInfo { fn force_set_current_code(c: u32) -> Weight; fn force_set_current_head(s: u32) -> Weight; @@ -605,6 +620,13 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Runtime hook for assigning coretime for a given parachain. + /// + /// This is only used at genesis or by root. + /// + /// TODO: Remove once coretime is the standard accross all chains. + type AssignCoretime: AssignCoretime; } #[pallet::event] @@ -838,6 +860,8 @@ pub mod pallet { panic!("empty validation code is not allowed in genesis"); } Pallet::::initialize_para_now(&mut parachains, *id, genesis_args); + T::AssignCoretime::assign_coretime(*id) + .expect("Assigning coretime works at genesis; qed"); } // parachains are flushed on drop } diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index 3043127c3174660b72a55ce850bb9d05c2330ad2..0f6b23ae1b39213f8afbd37798f8f7f31a024cf9 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -18,7 +18,9 @@ use super::*; use crate::{inclusion, ParaId}; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; use frame_system::RawOrigin; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::{cmp::min, collections::btree_map::BTreeMap}; + +use primitives::v6::GroupIndex; use crate::builder::BenchBuilder; @@ -116,7 +118,9 @@ benchmarks! { // There is 1 backed, assert_eq!(benchmark.backed_candidates.len(), 1); // with `v` validity votes. - assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), v as usize); + // 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); benchmark.bitfields.clear(); benchmark.disputes.clear(); @@ -132,13 +136,13 @@ benchmarks! { // Ensure that the votes are for the correct session assert_eq!(vote.session, scenario._session); // Ensure that there are an expected number of candidates - let header = BenchBuilder::::header(scenario._block_number.clone()); + let header = BenchBuilder::::header(scenario._block_number); // Traverse candidates and assert descriptors are as expected for (para_id, backing_validators) in vote.backing_validators_per_candidate.iter().enumerate() { let descriptor = backing_validators.0.descriptor(); assert_eq!(ParaId::from(para_id), descriptor.para_id); assert_eq!(header.hash(), descriptor.relay_parent); - assert_eq!(backing_validators.1.len(), v as usize); + assert_eq!(backing_validators.1.len(), votes); } assert_eq!( @@ -167,11 +171,14 @@ benchmarks! { let mut benchmark = scenario.data.clone(); + // let votes = BenchBuilder::::fallback_min_validity_votes() as usize; + let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), BenchBuilder::::fallback_min_validity_votes() as usize); + // There is 1 backed assert_eq!(benchmark.backed_candidates.len(), 1); assert_eq!( - benchmark.backed_candidates.get(0).unwrap().validity_votes.len() as u32, - BenchBuilder::::fallback_min_validity_votes() + benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), + votes, ); benchmark.bitfields.clear(); @@ -189,7 +196,7 @@ benchmarks! { // Ensure that the votes are for the correct session assert_eq!(vote.session, scenario._session); // Ensure that there are an expected number of candidates - let header = BenchBuilder::::header(scenario._block_number.clone()); + let header = BenchBuilder::::header(scenario._block_number); // Traverse candidates and assert descriptors are as expected for (para_id, backing_validators) in vote.backing_validators_per_candidate.iter().enumerate() { @@ -197,8 +204,8 @@ benchmarks! { assert_eq!(ParaId::from(para_id), descriptor.para_id); assert_eq!(header.hash(), descriptor.relay_parent); assert_eq!( - backing_validators.1.len() as u32, - BenchBuilder::::fallback_min_validity_votes() + backing_validators.1.len(), + votes, ); } diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 8e918d35d5ff0d8af9ae77408b92fc53f9670853..8c33199c092371060aca56ad89d8d888310513c8 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -548,7 +548,7 @@ impl Pallet { let disputed_bitfield = create_disputed_bitfield(expected_bits, freed_disputed.keys()); if !freed_disputed.is_empty() { - >::update_claimqueue(freed_disputed.clone(), now); + >::free_cores_and_fill_claimqueue(freed_disputed.clone(), now); } let bitfields = sanitize_bitfields::( @@ -580,7 +580,7 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); - >::update_claimqueue(freed, now); + >::free_cores_and_fill_claimqueue(freed, now); let scheduled = >::scheduled_paras() .map(|(core_idx, para_id)| (para_id, core_idx)) .collect(); diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 4fc60792e34683d223873a6e0ee39d70d380709e..e62d1cb68ffea39ced5edb3c0034fa5451744ef5 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -25,7 +25,8 @@ mod enter { use super::*; use crate::{ builder::{Bench, BenchBuilder}, - mock::{new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test}, + mock::{mock_assigner, new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test}, + scheduler::common::Assignment, }; use assert_matches::assert_matches; use frame_support::assert_ok; @@ -39,6 +40,7 @@ mod enter { backed_and_concluding: BTreeMap, num_validators_per_core: u32, code_upgrade: Option, + fill_claimqueue: bool, } fn make_inherent_data( @@ -48,6 +50,7 @@ mod enter { backed_and_concluding, num_validators_per_core, code_upgrade, + fill_claimqueue, }: TestConfig, ) -> Bench { let builder = BenchBuilder::::new() @@ -58,7 +61,15 @@ mod enter { .set_max_validators_per_core(num_validators_per_core) .set_dispute_statements(dispute_statements) .set_backed_and_concluding_cores(backed_and_concluding) - .set_dispute_sessions(&dispute_sessions[..]); + .set_dispute_sessions(&dispute_sessions[..]) + .set_fill_claimqueue(fill_claimqueue); + + // Setup some assignments as needed: + mock_assigner::Pallet::::set_core_count(builder.max_cores()); + for core_index in 0..builder.max_cores() { + // Core index == para_id in this case + mock_assigner::Pallet::::add_test_assignment(Assignment::Bulk(core_index.into())); + } if let Some(code_size) = code_upgrade { builder.set_code_upgrade(code_size).build() @@ -88,6 +99,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 1, code_upgrade: None, + fill_claimqueue: false, }); // We expect the scenario to have cores 0 & 1 with pending availability. The backed @@ -238,6 +250,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -308,6 +321,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 6, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -376,6 +390,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 4, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -460,6 +475,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -544,6 +560,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -627,6 +644,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -666,15 +684,9 @@ mod enter { // * 3 disputes. assert_eq!(limit_inherent_data.disputes.len(), 2); - assert_ok!(Pallet::::enter( - frame_system::RawOrigin::None.into(), - limit_inherent_data, - )); - - // TODO [now]: this assertion fails with async backing runtime. assert_eq!( - // The length of this vec is equal to the number of candidates, so we know our 2 - // backed candidates did not get filtered out + // The length of this vec is equal to the number of candidates, so we know 1 + // candidate got filtered out Pallet::::on_chain_votes().unwrap().backing_validators_per_candidate.len(), 1 ); @@ -684,6 +696,11 @@ mod enter { Pallet::::on_chain_votes().unwrap().session, 2 ); + + assert_ok!(Pallet::::enter( + frame_system::RawOrigin::None.into(), + limit_inherent_data, + )); }); } @@ -713,6 +730,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -778,6 +796,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -841,6 +860,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); @@ -905,6 +925,7 @@ mod enter { backed_and_concluding, num_validators_per_core: 5, code_upgrade: None, + fill_claimqueue: false, }); let expected_para_inherent_data = scenario.data.clone(); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs index 4d0bbc6a8960fc1c1d3e70abff2e0175e7898dac..b3a060e1cb8a05439de7f48fdd6ff1a84c554496 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs @@ -62,7 +62,7 @@ pub fn availability_cores() -> Vec>::update_claimqueue(Vec::new(), now); + >::free_cores_and_fill_claimqueue(Vec::new(), now); let time_out_for = >::availability_timeout_predicate(); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index 200fd57915f9b8ac03fd8241e6b2e69d4194bad0..0da50f6a5373e16b8e4164440b7f4fa9deff52cb 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -17,7 +17,10 @@ //! Put implementations of functions from staging APIs here. use crate::{configuration, initializer, shared}; -use primitives::{vstaging::NodeFeatures, ValidatorIndex}; +use primitives::{ + vstaging::{ApprovalVotingParams, NodeFeatures}, + ValidatorIndex, +}; use sp_std::{collections::btree_map::BTreeMap, prelude::Vec}; /// Implementation for `DisabledValidators` @@ -47,3 +50,9 @@ where pub fn node_features() -> NodeFeatures { >::config().node_features } + +/// Approval voting subsystem configuration parameteres +pub fn approval_voting_params() -> ApprovalVotingParams { + let config = >::config(); + config.approval_voting_params +} diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index b81b68b5745ee57ce0736426abda5081aeb619f2..08ce656b2b284f3ecc5fa8ad0d451369ed22c453 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -65,7 +65,7 @@ pub mod migration; pub mod pallet { use super::*; - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] #[pallet::without_storage_info] @@ -99,15 +99,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn availability_cores)] pub(crate) type AvailabilityCores = - StorageValue<_, Vec>>, ValueQuery>; + StorageValue<_, Vec>, ValueQuery>; /// Representation of a core in `AvailabilityCores`. /// /// This is not to be confused with `CoreState` which is an enriched variant of this and exposed /// to the node side. It also provides information about scheduled/upcoming assignments for /// example and is computed on the fly in the `availability_cores` runtime call. - #[derive(Clone, Encode, Decode, TypeInfo, RuntimeDebug)] - #[cfg_attr(feature = "std", derive(PartialEq))] + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] pub enum CoreOccupied { /// No candidate is waiting availability on this core right now (the core is not occupied). Free, @@ -115,6 +114,9 @@ pub mod pallet { Paras(ParasEntry), } + /// Conveninece type alias for `CoreOccupied`. + pub type CoreOccupiedType = CoreOccupied>; + impl CoreOccupied { /// Is core free? pub fn is_free(&self) -> bool { @@ -149,16 +151,13 @@ pub mod pallet { /// a block. Runtime APIs should be used to determine scheduled cores/ for the upcoming block. #[pallet::storage] #[pallet::getter(fn claimqueue)] - pub(crate) type ClaimQueue = StorageValue< - _, - BTreeMap>>>>, - ValueQuery, - >; + pub(crate) type ClaimQueue = + StorageValue<_, BTreeMap>>, ValueQuery>; /// Assignments as tracked in the claim queue. - #[derive(Clone, Encode, Decode, TypeInfo, PartialEq, RuntimeDebug)] - pub struct ParasEntry { - /// The underlying `Assignment` + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq, Clone)] + pub struct ParasEntry { + /// The underlying [`Assignment`]. pub assignment: Assignment, /// The number of times the entry has timed out in availability already. pub availability_timeouts: u32, @@ -169,37 +168,18 @@ pub mod pallet { pub ttl: N, } - impl ParasEntry { - /// Return `Id` from the underlying `Assignment`. - pub fn para_id(&self) -> ParaId { - self.assignment.para_id - } + /// Convenience type declaration for `ParasEntry`. + pub type ParasEntryType = ParasEntry>; + impl ParasEntry { /// Create a new `ParasEntry`. pub fn new(assignment: Assignment, now: N) -> Self { ParasEntry { assignment, availability_timeouts: 0, ttl: now } } - } - /// How a core is mapped to a backing group and a `ParaId` - #[derive(Clone, Encode, Decode, PartialEq, TypeInfo)] - #[cfg_attr(feature = "std", derive(Debug))] - pub struct CoreAssignment { - /// The core that is assigned. - pub core: CoreIndex, - /// The para id and accompanying information needed to collate and back a parablock. - pub paras_entry: ParasEntry, - } - - impl CoreAssignment { - /// Returns the [`ParaId`] of the assignment. + /// Return `Id` from the underlying `Assignment`. pub fn para_id(&self) -> ParaId { - self.paras_entry.para_id() - } - - /// Returns the inner [`ParasEntry`] of the assignment. - pub fn to_paras_entry(self) -> ParasEntry { - self.paras_entry + self.assignment.para_id() } } @@ -219,8 +199,6 @@ pub mod pallet { } type PositionInClaimqueue = u32; -type TimedoutParas = BTreeMap>>; -type ConcludedParas = BTreeMap; impl Pallet { /// Called by the initializer to initialize the scheduler pallet. @@ -253,7 +231,7 @@ impl Pallet { ); AvailabilityCores::::mutate(|cores| { - cores.resize(n_cores as _, CoreOccupied::Free); + cores.resize_with(n_cores as _, || CoreOccupied::Free); }); // shuffle validators into groups. @@ -298,9 +276,8 @@ impl Pallet { /// with the reason for them being freed. Returns a tuple of concluded and timedout paras. fn free_cores( just_freed_cores: impl IntoIterator, - ) -> (ConcludedParas, TimedoutParas) { - let mut timedout_paras: BTreeMap>> = - BTreeMap::new(); + ) -> (BTreeMap, BTreeMap>) { + let mut timedout_paras: BTreeMap> = BTreeMap::new(); let mut concluded_paras = BTreeMap::new(); AvailabilityCores::::mutate(|cores| { @@ -310,21 +287,22 @@ impl Pallet { .into_iter() .filter(|(freed_index, _)| (freed_index.0 as usize) < c_len) .for_each(|(freed_index, freed_reason)| { - match &cores[freed_index.0 as usize] { + match sp_std::mem::replace( + &mut cores[freed_index.0 as usize], + CoreOccupied::Free, + ) { CoreOccupied::Free => {}, CoreOccupied::Paras(entry) => { match freed_reason { FreedReason::Concluded => { - concluded_paras.insert(freed_index, entry.para_id()); + concluded_paras.insert(freed_index, entry.assignment); }, FreedReason::TimedOut => { - timedout_paras.insert(freed_index, entry.clone()); + timedout_paras.insert(freed_index, entry); }, }; }, }; - - cores[freed_index.0 as usize] = CoreOccupied::Free; }) }); @@ -379,30 +357,36 @@ impl Pallet { for (idx, _) in (0u32..).zip(availability_cores) { let core_idx = CoreIndex(idx); if let Some(core_claimqueue) = cq.get_mut(&core_idx) { - let mut dropped_claims: Vec> = vec![]; - core_claimqueue.retain(|maybe_entry| { - if let Some(entry) = maybe_entry { + let mut i = 0; + let mut num_dropped = 0; + while i < core_claimqueue.len() { + let maybe_dropped = if let Some(entry) = core_claimqueue.get(i) { if entry.ttl < now { - dropped_claims.push(Some(entry.para_id())); - return false + core_claimqueue.remove(i) + } else { + None } + } else { + None + }; + + if let Some(dropped) = maybe_dropped { + num_dropped += 1; + T::AssignmentProvider::report_processed(dropped.assignment); + } else { + i += 1; } - true - }); - - // For all claims dropped due to TTL, attempt to pop a new entry to - // the back of the claimqueue. - for drop in dropped_claims { - match T::AssignmentProvider::pop_assignment_for_core(core_idx, drop) { - Some(assignment) => { - let AssignmentProviderConfig { ttl, .. } = - T::AssignmentProvider::get_provider_config(core_idx); - core_claimqueue.push_back(Some(ParasEntry::new( - assignment.clone(), - now + ttl, - ))); - }, - None => (), + } + + for _ in 0..num_dropped { + // For all claims dropped due to TTL, attempt to pop a new entry to + // the back of the claimqueue. + if let Some(assignment) = + T::AssignmentProvider::pop_assignment_for_core(core_idx) + { + let AssignmentProviderConfig { ttl, .. } = + T::AssignmentProvider::get_provider_config(core_idx); + core_claimqueue.push_back(ParasEntry::new(assignment, now + ttl)); } } } @@ -446,7 +430,7 @@ impl Pallet { } let rotations_since_session_start: BlockNumberFor = - (at - session_start_block) / config.group_rotation_frequency.into(); + (at - session_start_block) / config.group_rotation_frequency; let rotations_since_session_start = as TryInto>::try_into(rotations_since_session_start) @@ -514,14 +498,12 @@ impl Pallet { /// Return the next thing that will be scheduled on this core assuming it is currently /// occupied and the candidate occupying it became available. pub(crate) fn next_up_on_available(core: CoreIndex) -> Option { - ClaimQueue::::get().get(&core).and_then(|a| { - a.iter() - .find_map(|e| e.as_ref()) - .map(|pe| Self::paras_entry_to_scheduled_core(pe)) - }) + ClaimQueue::::get() + .get(&core) + .and_then(|a| a.front().map(|pe| Self::paras_entry_to_scheduled_core(pe))) } - fn paras_entry_to_scheduled_core(pe: &ParasEntry>) -> ScheduledCore { + fn paras_entry_to_scheduled_core(pe: &ParasEntryType) -> ScheduledCore { ScheduledCore { para_id: pe.para_id(), collator: None } } @@ -552,35 +534,33 @@ impl Pallet { /// Pushes occupied cores to the assignment provider. fn push_occupied_cores_to_assignment_provider() { AvailabilityCores::::mutate(|cores| { - for (core_idx, core) in cores.iter_mut().enumerate() { - match core { + for core in cores.iter_mut() { + match sp_std::mem::replace(core, CoreOccupied::Free) { CoreOccupied::Free => continue, CoreOccupied::Paras(entry) => { - let core_idx = CoreIndex::from(core_idx as u32); - Self::maybe_push_assignment(core_idx, entry.clone()); + Self::maybe_push_assignment(entry); }, } - *core = CoreOccupied::Free; } }); } // on new session fn push_claimqueue_items_to_assignment_provider() { - for (core_idx, core_claimqueue) in ClaimQueue::::take() { + for (_, claim_queue) in ClaimQueue::::take() { // Push back in reverse order so that when we pop from the provider again, // the entries in the claimqueue are in the same order as they are right now. - for para_entry in core_claimqueue.into_iter().flatten().rev() { - Self::maybe_push_assignment(core_idx, para_entry); + for para_entry in claim_queue.into_iter().rev() { + Self::maybe_push_assignment(para_entry); } } } /// Push assignments back to the provider on session change unless the paras /// timed out on availability before. - fn maybe_push_assignment(core_idx: CoreIndex, pe: ParasEntry>) { + fn maybe_push_assignment(pe: ParasEntryType) { if pe.availability_timeouts == 0 { - T::AssignmentProvider::push_assignment_for_core(core_idx, pe.assignment); + T::AssignmentProvider::push_back_assignment(pe.assignment); } } @@ -591,31 +571,8 @@ impl Pallet { >::config().scheduling_lookahead } - /// Updates the claimqueue by moving it to the next paras and filling empty spots with new - /// paras. - pub(crate) fn update_claimqueue( - just_freed_cores: impl IntoIterator, - now: BlockNumberFor, - ) { - Self::move_claimqueue_forward(); - Self::free_cores_and_fill_claimqueue(just_freed_cores, now) - } - - /// Moves all elements in the claimqueue forward. - fn move_claimqueue_forward() { - let mut cq = ClaimQueue::::get(); - for core_queue in cq.values_mut() { - // First pop the finished claims from the front. - if let Some(None) = core_queue.front() { - core_queue.pop_front(); - } - } - - ClaimQueue::::set(cq); - } - /// Frees cores and fills the free claimqueue spots by popping from the `AssignmentProvider`. - fn free_cores_and_fill_claimqueue( + pub fn free_cores_and_fill_claimqueue( just_freed_cores: impl IntoIterator, now: BlockNumberFor, ) { @@ -651,19 +608,19 @@ impl Pallet { } else { // Consider timed out assignments for on demand parachains as concluded for // the assignment provider - let ret = concluded_paras.insert(core_idx, entry.para_id()); + let ret = concluded_paras.insert(core_idx, entry.assignment); debug_assert!(ret.is_none()); } } - // We consider occupied cores to be part of the claimqueue + if let Some(concluded_para) = concluded_paras.remove(&core_idx) { + T::AssignmentProvider::report_processed(concluded_para); + } + // We consider occupied cores to be part of the claimqueue let n_lookahead_used = cq.get(&core_idx).map_or(0, |v| v.len() as u32) + if Self::is_core_occupied(core_idx) { 1 } else { 0 }; for _ in n_lookahead_used..n_lookahead { - let concluded_para = concluded_paras.remove(&core_idx); - if let Some(assignment) = - T::AssignmentProvider::pop_assignment_for_core(core_idx, concluded_para) - { + if let Some(assignment) = T::AssignmentProvider::pop_assignment_for_core(core_idx) { Self::add_to_claimqueue(core_idx, ParasEntry::new(assignment, now + ttl)); } } @@ -680,9 +637,9 @@ impl Pallet { } } - fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntry>) { + fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntryType) { ClaimQueue::::mutate(|la| { - la.entry(core_idx).or_default().push_back(Some(pe)); + la.entry(core_idx).or_default().push_back(pe); }); } @@ -690,19 +647,16 @@ impl Pallet { fn remove_from_claimqueue( core_idx: CoreIndex, para_id: ParaId, - ) -> Result<(PositionInClaimqueue, ParasEntry>), &'static str> { + ) -> Result<(PositionInClaimqueue, ParasEntryType), &'static str> { ClaimQueue::::mutate(|cq| { let core_claims = cq.get_mut(&core_idx).ok_or("core_idx not found in lookahead")?; let pos = core_claims .iter() - .position(|a| a.as_ref().map_or(false, |pe| pe.para_id() == para_id)) + .position(|pe| pe.para_id() == para_id) .ok_or("para id not found at core_idx lookahead")?; - let pe = core_claims - .remove(pos) - .ok_or("remove returned None")? - .ok_or("Element in Claimqueue was None.")?; + let pe = core_claims.remove(pos).ok_or("remove returned None")?; Ok((pos as u32, pe)) }) @@ -710,16 +664,10 @@ impl Pallet { /// Paras scheduled next in the claim queue. pub(crate) fn scheduled_paras() -> impl Iterator { - Self::scheduled_entries().map(|(core_idx, e)| (core_idx, e.assignment.para_id)) - } - - /// Internal access to entries at the top of the claim queue. - fn scheduled_entries() -> impl Iterator>)> { let claimqueue = ClaimQueue::::get(); - claimqueue .into_iter() - .filter_map(|(core_idx, v)| v.front().cloned().flatten().map(|e| (core_idx, e))) + .filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.assignment.para_id()))) } #[cfg(any(feature = "runtime-benchmarks", test))] diff --git a/polkadot/runtime/parachains/src/scheduler/common.rs b/polkadot/runtime/parachains/src/scheduler/common.rs index 316e8e3b760cc6a73c022f693e12ca537bf3443b..2eb73385803c6e62a88fc55526e3ba18218cf06d 100644 --- a/polkadot/runtime/parachains/src/scheduler/common.rs +++ b/polkadot/runtime/parachains/src/scheduler/common.rs @@ -16,29 +16,39 @@ //! Common traits and types used by the scheduler and assignment providers. -use frame_support::pallet_prelude::*; -use primitives::{CoreIndex, Id as ParaId}; use scale_info::TypeInfo; -use sp_std::prelude::*; +use sp_runtime::{ + codec::{Decode, Encode}, + RuntimeDebug, +}; -// Only used to link to configuration documentation. -#[allow(unused)] -use crate::configuration::HostConfiguration; +use primitives::{CoreIndex, Id as ParaId}; -/// An assignment for a parachain scheduled to be backed and included in a relay chain block. -#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebug)] -pub struct Assignment { - /// Assignment's ParaId - pub para_id: ParaId, +/// Assignment (ParaId -> CoreIndex). +#[derive(Encode, Decode, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum Assignment { + /// A pool assignment. + Pool { + /// The assigned para id. + para_id: ParaId, + /// The core index the para got assigned to. + core_index: CoreIndex, + }, + /// A bulk assignment. + Bulk(ParaId), } impl Assignment { - /// Create a new `Assignment`. - pub fn new(para_id: ParaId) -> Self { - Self { para_id } + /// Returns the [`ParaId`] this assignment is associated to. + pub fn para_id(&self) -> ParaId { + match self { + Self::Pool { para_id, .. } => *para_id, + Self::Bulk(para_id) => *para_id, + } } } +#[derive(Encode, Decode, TypeInfo)] /// A set of variables required by the scheduler in order to operate. pub struct AssignmentProviderConfig { /// How many times a collation can time out on availability. @@ -51,22 +61,42 @@ pub struct AssignmentProviderConfig { } pub trait AssignmentProvider { - /// How many cores are allocated to this provider. - fn session_core_count() -> u32; - /// Pops an [`Assignment`] from the provider for a specified [`CoreIndex`]. - /// The `concluded_para` field makes the caller report back to the provider - /// which [`ParaId`] it processed last on the supplied [`CoreIndex`]. - fn pop_assignment_for_core( - core_idx: CoreIndex, - concluded_para: Option, - ) -> Option; - - /// Push back an already popped assignment. Intended for provider implementations - /// that need to be able to keep track of assignments over session boundaries, - /// such as the on demand assignment provider. - fn push_assignment_for_core(core_idx: CoreIndex, assignment: Assignment); + /// + /// This is where assignments come into existance. + fn pop_assignment_for_core(core_idx: CoreIndex) -> Option; + + /// A previously popped `Assignment` has been fully processed. + /// + /// Report back to the assignment provider that an assignment is done and no longer present in + /// the scheduler. + /// + /// This is one way of the life of an assignment coming to an end. + fn report_processed(assignment: Assignment); + + /// Push back a previously popped assignment. + /// + /// If the assignment could not be processed within the current session, it can be pushed back + /// to the assignment provider in order to be poppped again later. + /// + /// This is the second way the life of an assignment can come to an end. + fn push_back_assignment(assignment: Assignment); /// Returns a set of variables needed by the scheduler fn get_provider_config(core_idx: CoreIndex) -> AssignmentProviderConfig; + + /// Push some assignment for mocking/benchmarks purposes. + /// + /// Useful for benchmarks and testing. The returned assignment is "valid" and can if need be + /// passed into `report_processed` for example. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn get_mock_assignment(core_idx: CoreIndex, para_id: ParaId) -> Assignment; + + /// How many cores are allocated to this provider. + /// + /// As the name suggests the core count has to be session buffered: + /// + /// - Core count has to be predetermined for the next session in the current session. + /// - Core count must not change during a session. + fn session_core_count() -> u32; } diff --git a/polkadot/runtime/parachains/src/scheduler/migration.rs b/polkadot/runtime/parachains/src/scheduler/migration.rs index bb9a647e955ca7e6bf7b7e7a78abd41e084550d0..4c0a07d73674205dbc4fc80090f1331a8858633e 100644 --- a/polkadot/runtime/parachains/src/scheduler/migration.rs +++ b/polkadot/runtime/parachains/src/scheduler/migration.rs @@ -22,9 +22,18 @@ use frame_support::{ traits::OnRuntimeUpgrade, weights::Weight, }; +/// Old/legacy assignment representation (v0). +/// +/// `Assignment` used to be a concrete type with the same layout V0Assignment, idential on all +/// assignment providers. This can be removed once storage has been migrated. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Clone)] +struct V0Assignment { + pub para_id: ParaId, +} + +/// Old scheduler with explicit parathreads and `Scheduled` storage instead of `ClaimQueue`. mod v0 { use super::*; - use primitives::{CollatorId, Id}; #[storage_alias] @@ -90,29 +99,123 @@ mod v0 { } } -pub mod v1 { +// `ClaimQueue` got introduced. +// +// - Items are `Option` for some weird reason. +// - Assignments only consist of `ParaId`, `Assignment` is a concrete type (Same as V0Assignment). +mod v1 { + use frame_support::{ + pallet_prelude::ValueQuery, storage_alias, traits::OnRuntimeUpgrade, weights::Weight, + }; + use frame_system::pallet_prelude::BlockNumberFor; + use super::*; use crate::scheduler; - #[allow(deprecated)] - pub type MigrateToV1 = VersionedMigration< - 0, - 1, - UncheckedMigrateToV1, + #[storage_alias] + pub(super) type ClaimQueue = StorageValue< Pallet, - ::DbWeight, + BTreeMap>>>>, + ValueQuery, >; - #[deprecated(note = "Use MigrateToV1 instead")] + #[storage_alias] + pub(super) type AvailabilityCores = + StorageValue, Vec>>, ValueQuery>; + + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(super) enum CoreOccupied { + /// No candidate is waiting availability on this core right now (the core is not occupied). + Free, + /// A para is currently waiting for availability/inclusion on this core. + Paras(ParasEntry), + } + + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(super) struct ParasEntry { + /// The underlying `Assignment` + pub(super) assignment: V0Assignment, + /// The number of times the entry has timed out in availability already. + pub(super) availability_timeouts: u32, + /// The block height until this entry needs to be backed. + /// + /// If missed the entry will be removed from the claim queue without ever having occupied + /// the core. + pub(super) ttl: N, + } + + impl ParasEntry { + /// Create a new `ParasEntry`. + pub(super) fn new(assignment: V0Assignment, now: N) -> Self { + ParasEntry { assignment, availability_timeouts: 0, ttl: now } + } + + /// Return `Id` from the underlying `Assignment`. + pub(super) fn para_id(&self) -> ParaId { + self.assignment.para_id + } + } + + fn add_to_claimqueue(core_idx: CoreIndex, pe: ParasEntry>) { + ClaimQueue::::mutate(|la| { + la.entry(core_idx).or_default().push_back(Some(pe)); + }); + } + + /// Migration to V1 pub struct UncheckedMigrateToV1(sp_std::marker::PhantomData); - #[allow(deprecated)] impl OnRuntimeUpgrade for UncheckedMigrateToV1 { fn on_runtime_upgrade() -> Weight { - let weight_consumed = migrate_to_v1::(); + let mut weight: Weight = Weight::zero(); + + v0::ParathreadQueue::::kill(); + v0::ParathreadClaimIndex::::kill(); + + let now = >::block_number(); + let scheduled = v0::Scheduled::::take(); + let sched_len = scheduled.len() as u64; + for core_assignment in scheduled { + let core_idx = core_assignment.core; + let assignment = V0Assignment { para_id: core_assignment.para_id }; + let pe = v1::ParasEntry::new(assignment, now); + v1::add_to_claimqueue::(core_idx, pe); + } + + let parachains = paras::Pallet::::parachains(); + let availability_cores = v0::AvailabilityCores::::take(); + let mut new_availability_cores = Vec::new(); + + for (core_index, core) in availability_cores.into_iter().enumerate() { + let new_core = if let Some(core) = core { + match core { + v0::CoreOccupied::Parachain => + v1::CoreOccupied::Paras(v1::ParasEntry::new( + V0Assignment { para_id: parachains[core_index] }, + now, + )), + v0::CoreOccupied::Parathread(entry) => v1::CoreOccupied::Paras( + v1::ParasEntry::new(V0Assignment { para_id: entry.claim.0 }, now), + ), + } + } else { + v1::CoreOccupied::Free + }; + + new_availability_cores.push(new_core); + } + + v1::AvailabilityCores::::set(new_availability_cores); - log::info!(target: scheduler::LOG_TARGET, "Migrating para scheduler storage to v1"); + // 2x as once for Scheduled and once for Claimqueue + weight.saturating_accrue(T::DbWeight::get().reads_writes(2 * sched_len, 2 * sched_len)); + // reading parachains + availability_cores, writing AvailabilityCores + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + // 2x kill + weight.saturating_accrue(T::DbWeight::get().writes(2)); - weight_consumed + log::info!(target: scheduler::LOG_TARGET, "Migrated para scheduler storage to v1"); + + weight } #[cfg(feature = "try-runtime")] @@ -138,9 +241,9 @@ pub mod v1 { ); let expected_len = u32::decode(&mut &state[..]).unwrap(); - let availability_cores_waiting = super::AvailabilityCores::::get() - .iter() - .filter(|c| !matches!(c, CoreOccupied::Free)) + let availability_cores_waiting = v1::AvailabilityCores::::get() + .into_iter() + .filter(|c| !matches!(c, v1::CoreOccupied::Free)) .count(); ensure!( @@ -154,51 +257,150 @@ pub mod v1 { } } -pub fn migrate_to_v1() -> Weight { - let mut weight: Weight = Weight::zero(); +/// Migrate `V0` to `V1` of the storage format. +pub type MigrateV0ToV1 = VersionedMigration< + 0, + 1, + v1::UncheckedMigrateToV1, + Pallet, + ::DbWeight, +>; - v0::ParathreadQueue::::kill(); - v0::ParathreadClaimIndex::::kill(); +mod v2 { + use super::*; + use crate::scheduler; - let now = >::block_number(); - let scheduled = v0::Scheduled::::take(); - let sched_len = scheduled.len() as u64; - for core_assignment in scheduled { - let core_idx = core_assignment.core; - let assignment = Assignment::new(core_assignment.para_id); - let pe = ParasEntry::new(assignment, now); - Pallet::::add_to_claimqueue(core_idx, pe); + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(crate) enum CoreOccupied { + Free, + Paras(ParasEntry), } - let parachains = paras::Pallet::::parachains(); - let availability_cores = v0::AvailabilityCores::::take(); - let mut new_availability_cores = Vec::new(); - - for (core_index, core) in availability_cores.into_iter().enumerate() { - let new_core = if let Some(core) = core { - match core { - v0::CoreOccupied::Parachain => CoreOccupied::Paras(ParasEntry::new( - Assignment::new(parachains[core_index]), - now, - )), - v0::CoreOccupied::Parathread(entry) => - CoreOccupied::Paras(ParasEntry::new(Assignment::new(entry.claim.0), now)), - } - } else { - CoreOccupied::Free - }; + #[derive(Encode, Decode, TypeInfo, RuntimeDebug, PartialEq)] + pub(crate) struct ParasEntry { + pub assignment: Assignment, + pub availability_timeouts: u32, + pub ttl: N, + } - new_availability_cores.push(new_core); + // V2 (no Option wrapper) and new [`Assignment`]. + #[storage_alias] + pub(crate) type ClaimQueue = StorageValue< + Pallet, + BTreeMap>>>, + ValueQuery, + >; + + #[storage_alias] + pub(crate) type AvailabilityCores = + StorageValue, Vec>>, ValueQuery>; + + fn is_bulk(core_index: CoreIndex) -> bool { + core_index.0 < paras::Parachains::::decode_len().unwrap_or(0) as u32 } - super::AvailabilityCores::::set(new_availability_cores); + /// Migration to V2 + pub struct UncheckedMigrateToV2(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrateToV2 { + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + + let old = v1::ClaimQueue::::take(); + let new = old + .into_iter() + .map(|(k, v)| { + ( + k, + v.into_iter() + .flatten() + .map(|p| { + let assignment = if is_bulk::(k) { + Assignment::Bulk(p.para_id()) + } else { + Assignment::Pool { para_id: p.para_id(), core_index: k } + }; + + ParasEntry { + assignment, + availability_timeouts: p.availability_timeouts, + ttl: p.ttl, + } + }) + .collect::>(), + ) + }) + .collect::>>>>(); + + ClaimQueue::::put(new); + + let old = v1::AvailabilityCores::::get(); + + let new = old + .into_iter() + .enumerate() + .map(|(k, a)| match a { + v1::CoreOccupied::Free => CoreOccupied::Free, + v1::CoreOccupied::Paras(paras) => { + let assignment = if is_bulk::((k as u32).into()) { + Assignment::Bulk(paras.para_id()) + } else { + Assignment::Pool { + para_id: paras.para_id(), + core_index: (k as u32).into(), + } + }; + + CoreOccupied::Paras(ParasEntry { + assignment, + availability_timeouts: paras.availability_timeouts, + ttl: paras.ttl, + }) + }, + }) + .collect::>(); + AvailabilityCores::::put(new); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + log::info!(target: scheduler::LOG_TARGET, "Migrating para scheduler storage to v2"); + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + log::trace!( + target: crate::scheduler::LOG_TARGET, + "ClaimQueue before migration: {}", + v1::ClaimQueue::::get().len() + ); + + let bytes = u32::to_be_bytes(v1::ClaimQueue::::get().len() as u32); - // 2x as once for Scheduled and once for Claimqueue - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2 * sched_len, 2 * sched_len)); - // reading parachains + availability_cores, writing AvailabilityCores - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); - // 2x kill - weight = weight.saturating_add(T::DbWeight::get().writes(2)); + Ok(bytes.to_vec()) + } - weight + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::DispatchError> { + log::trace!(target: crate::scheduler::LOG_TARGET, "Running post_upgrade()"); + + let old_len = u32::from_be_bytes(state.try_into().unwrap()); + ensure!( + v2::ClaimQueue::::get().len() as u32 == old_len, + "Old ClaimQueue completely moved to new ClaimQueue after migration" + ); + + Ok(()) + } + } } + +/// Migrate `V1` to `V2` of the storage format. +pub type MigrateV1ToV2 = VersionedMigration< + 1, + 2, + v2::UncheckedMigrateToV2, + Pallet, + ::DbWeight, +>; diff --git a/polkadot/runtime/parachains/src/scheduler/tests.rs b/polkadot/runtime/parachains/src/scheduler/tests.rs index 108f365d6b5c39567b57dc21243a30d0548a89bf..9af23ce64bd67ab0901dd1a03e849d51cfffe342 100644 --- a/polkadot/runtime/parachains/src/scheduler/tests.rs +++ b/polkadot/runtime/parachains/src/scheduler/tests.rs @@ -22,24 +22,24 @@ use primitives::{BlockNumber, SessionIndex, ValidationCode, ValidatorId}; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use crate::{ - assigner_on_demand::QueuePushDirection, configuration::HostConfiguration, initializer::SessionChangeNotification, mock::{ - new_test_ext, MockGenesisConfig, OnDemandAssigner, Paras, ParasShared, RuntimeOrigin, + new_test_ext, MockAssigner, MockGenesisConfig, Paras, ParasShared, RuntimeOrigin, Scheduler, System, Test, }, paras::{ParaGenesisArgs, ParaKind}, + scheduler::{common::Assignment, ClaimQueue}, }; -fn schedule_blank_para(id: ParaId, parakind: ParaKind) { +fn schedule_blank_para(id: ParaId) { let validation_code: ValidationCode = vec![1, 2, 3].into(); assert_ok!(Paras::schedule_para_initialize( id, ParaGenesisArgs { genesis_head: Vec::new().into(), validation_code: validation_code.clone(), - para_kind: parakind, + para_kind: ParaKind::Parathread, // This most closely mimics our test assigner } )); @@ -78,7 +78,7 @@ fn run_to_block( Scheduler::initializer_initialize(b + 1); // In the real runtime this is expected to be called by the `InclusionInherent` pallet. - Scheduler::update_claimqueue(BTreeMap::new(), b + 1); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1); } } @@ -103,11 +103,10 @@ fn run_to_end_of_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_cores: 3, + coretime_cores: 3, group_rotation_frequency: 10, paras_availability_period: 3, scheduling_lookahead: 2, - on_demand_retries: 1, // This field does not affect anything that scheduler does. However, `HostConfiguration` // is still a subject to consistency test. It requires that // `minimum_validation_upgrade_delay` is greater than `chain_availability_period` and @@ -124,29 +123,16 @@ fn genesis_config(config: &HostConfiguration) -> MockGenesisConfig } } -pub(crate) fn claimqueue_contains_only_none() -> bool { - let mut cq = Scheduler::claimqueue(); - for (_, v) in cq.iter_mut() { - v.retain(|e| e.is_some()); - } - - cq.values().map(|v| v.len()).sum::() == 0 -} - -pub(crate) fn claimqueue_contains_para_ids(pids: Vec) -> bool { +fn claimqueue_contains_para_ids(pids: Vec) -> bool { let set: BTreeSet = ClaimQueue::::get() .into_iter() - .flat_map(|(_, assignments)| { - assignments - .into_iter() - .filter_map(|assignment| assignment.and_then(|pe| Some(pe.para_id()))) - }) + .flat_map(|(_, paras_entries)| paras_entries.into_iter().map(|pe| pe.assignment.para_id())) .collect(); pids.into_iter().all(|pid| set.contains(&pid)) } -pub(crate) fn availability_cores_contains_para_ids(pids: Vec) -> bool { +fn availability_cores_contains_para_ids(pids: Vec) -> bool { let set: BTreeSet = AvailabilityCores::::get() .into_iter() .filter_map(|core| match core { @@ -158,6 +144,14 @@ pub(crate) fn availability_cores_contains_para_ids(pids: Vec) pids.into_iter().all(|pid| set.contains(&pid)) } +/// Internal access to entries at the top of the claim queue. +fn scheduled_entries() -> impl Iterator>)> { + let claimqueue = ClaimQueue::::get(); + claimqueue + .into_iter() + .filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.clone()))) +} + #[test] fn claimqueue_ttl_drop_fn_works() { let mut config = default_config(); @@ -169,13 +163,14 @@ fn claimqueue_ttl_drop_fn_works() { let mut now = 10; new_test_ext(genesis_config).execute_with(|| { - assert!(default_config().on_demand_ttl == 5); + let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl; + assert!(assignment_provider_ttl == 5); // Register and run to a blockheight where the para is in a valid state. - schedule_blank_para(para_id, ParaKind::Parathread); - run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); + schedule_blank_para(para_id); + run_to_block(now, |n| if n == now { Some(Default::default()) } else { None }); // Add a claim on core 0 with a ttl in the past. - let paras_entry = ParasEntry::new(Assignment::new(para_id), now - 5); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now - 5 as u32); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue prior to call. @@ -186,7 +181,7 @@ fn claimqueue_ttl_drop_fn_works() { assert!(!claimqueue_contains_para_ids::(vec![para_id])); // Add a claim on core 0 with a ttl in the future (15). - let paras_entry = ParasEntry::new(Assignment::new(para_id), now + 5); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now + 5); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue post call. @@ -201,7 +196,7 @@ fn claimqueue_ttl_drop_fn_works() { assert!(!claimqueue_contains_para_ids::(vec![para_id])); // Add a claim on core 0 with a ttl == now (16) - let paras_entry = ParasEntry::new(Assignment::new(para_id), now); + let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now); Scheduler::add_to_claimqueue(core_idx, paras_entry.clone()); // Claim is in queue post call. @@ -215,8 +210,8 @@ fn claimqueue_ttl_drop_fn_works() { Scheduler::drop_expired_claims_from_claimqueue(); // Add a claim on core 0 with a ttl == now (17) - let paras_entry_non_expired = ParasEntry::new(Assignment::new(para_id), now); - let paras_entry_expired = ParasEntry::new(Assignment::new(para_id), now - 2); + let paras_entry_non_expired = ParasEntry::new(Assignment::Bulk(para_id), now); + let paras_entry_expired = ParasEntry::new(Assignment::Bulk(para_id), now - 2); // ttls = [17, 15, 17] Scheduler::add_to_claimqueue(core_idx, paras_entry_non_expired.clone()); Scheduler::add_to_claimqueue(core_idx, paras_entry_expired.clone()); @@ -224,18 +219,10 @@ fn claimqueue_ttl_drop_fn_works() { let cq = Scheduler::claimqueue(); assert!(cq.get(&core_idx).unwrap().len() == 3); - // Add claims to on demand assignment provider. - let assignment = Assignment::new(para_id); + // Add a claim to the test assignment provider. + let assignment = Assignment::Bulk(para_id); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment.clone(), - QueuePushDirection::Back - )); - - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment, - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment.clone()); // Drop expired claim. Scheduler::drop_expired_claims_from_claimqueue(); @@ -248,58 +235,25 @@ fn claimqueue_ttl_drop_fn_works() { // The first 2 claims in the queue should have a ttl of 17, // being the ones set up prior in this test as claims 1 and 3. // The third claim is popped from the assignment provider and - // has a new ttl set by the scheduler of now + config.on_demand_ttl. - // ttls = [17, 17, 22] + // has a new ttl set by the scheduler of now + + // assignment_provider_ttl. ttls = [17, 17, 22] assert!(cqc.iter().enumerate().all(|(index, entry)| { match index { - 0 | 1 => return entry.clone().unwrap().ttl == 17, - 2 => return entry.clone().unwrap().ttl == 22, - _ => return false, + 0 | 1 => entry.clone().ttl == 17, + 2 => entry.clone().ttl == 22, + _ => false, } })) }); } -// Pretty useless here. Should be on parathread assigner... if at all -#[test] -fn add_parathread_claim_works() { - let genesis_config = genesis_config(&default_config()); - - let thread_id = ParaId::from(10); - let core_index = CoreIndex::from(0); - let entry_ttl = 10_000; - - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_id, ParaKind::Parathread); - - assert!(!Paras::is_parathread(thread_id)); - - run_to_block(10, |n| if n == 10 { Some(Default::default()) } else { None }); - - assert!(Paras::is_parathread(thread_id)); - - let pe = ParasEntry::new(Assignment::new(thread_id), entry_ttl); - Scheduler::add_to_claimqueue(core_index, pe.clone()); - - let cq = Scheduler::claimqueue(); - assert_eq!(Scheduler::claimqueue_len(), 1); - assert_eq!(*(cq.get(&core_index).unwrap().front().unwrap()), Some(pe)); - }) -} - #[test] fn session_change_shuffles_validators() { let genesis_config = genesis_config(&default_config()); - assert_eq!(default_config().on_demand_cores, 3); new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - + // Need five cores for this test + MockAssigner::set_core_count(5); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -336,7 +290,6 @@ fn session_change_shuffles_validators() { fn session_change_takes_only_max_per_core() { let config = { let mut config = default_config(); - config.on_demand_cores = 0; config.max_validators_per_core = Some(1); config }; @@ -344,14 +297,8 @@ fn session_change_takes_only_max_per_core() { let genesis_config = genesis_config(&config); new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - let chain_c = ParaId::from(3_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - schedule_blank_para(chain_c, ParaKind::Parathread); + // Simulate 2 cores between all usage types + MockAssigner::set_core_count(2); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -374,7 +321,7 @@ fn session_change_takes_only_max_per_core() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 7); - // Every validator gets its own group, even though there are 2 paras. + // Every validator gets its own group, even though there are 2 cores. for i in 0..7 { assert_eq!(groups[i].len(), 1); } @@ -385,31 +332,25 @@ fn session_change_takes_only_max_per_core() { fn fill_claimqueue_fills() { let genesis_config = genesis_config(&default_config()); - let lookahead = genesis_config.configuration.config.scheduling_lookahead as usize; - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - let thread_a = ParaId::from(3_u32); - let thread_b = ParaId::from(4_u32); - let thread_c = ParaId::from(5_u32); + let para_a = ParaId::from(3_u32); + let para_b = ParaId::from(4_u32); + let para_c = ParaId::from(5_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; - let assignment_c = Assignment { para_id: thread_c }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(2); + let AssignmentProviderConfig { ttl: config_ttl, .. } = + MockAssigner::get_provider_config(CoreIndex(0)); - // register 2 lease holding parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add 3 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); - // and 3 parathreads (on-demand parachains) - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); - schedule_blank_para(thread_c, ParaKind::Parathread); - - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -417,107 +358,47 @@ fn fill_claimqueue_fills() { ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), _ => None, }); - { - assert_eq!(Scheduler::claimqueue_len(), 2 * lookahead); - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); - - // Cannot assert on indices anymore as they depend on the assignment providers - assert!(claimqueue_contains_para_ids::(vec![chain_a, chain_b])); - - assert_eq!( - scheduled.get(&CoreIndex(0)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - ); - - assert_eq!( - scheduled.get(&CoreIndex(1)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - ); - } - - // add a couple of parathread assignments. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_c, - QueuePushDirection::Back - )); + // add some para assignments. + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); run_to_block(2, |_| None); - // cores 0 and 1 should be occupied. mark them as such. - Scheduler::occupied( - vec![(CoreIndex(0), chain_a), (CoreIndex(1), chain_b)].into_iter().collect(), - ); - - run_to_block(3, |_| None); { - assert_eq!(Scheduler::claimqueue_len(), 5); - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); - - assert_eq!( - scheduled.get(&CoreIndex(0)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 6 - }, - ); - assert_eq!( - scheduled.get(&CoreIndex(1)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: chain_b }, - availability_timeouts: 0, - ttl: 6 - }, - ); + assert_eq!(Scheduler::claimqueue_len(), 3); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); // Was added a block later, note the TTL. assert_eq!( - scheduled.get(&CoreIndex(2)).unwrap(), + scheduled.get(&CoreIndex(0)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_a }, + assignment: assignment_a.clone(), availability_timeouts: 0, - ttl: 7 + ttl: 2 + config_ttl }, ); - // Sits on the same core as `thread_a` + // Sits on the same core as `para_a` assert_eq!( - Scheduler::claimqueue().get(&CoreIndex(2)).unwrap()[1], - Some(ParasEntry { - assignment: Assignment { para_id: thread_b }, + Scheduler::claimqueue().get(&CoreIndex(0)).unwrap()[1], + ParasEntry { + assignment: assignment_b.clone(), availability_timeouts: 0, - ttl: 7 - }) + ttl: 2 + config_ttl + } ); assert_eq!( - scheduled.get(&CoreIndex(3)).unwrap(), + scheduled.get(&CoreIndex(1)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_c }, + assignment: assignment_c.clone(), availability_timeouts: 0, - ttl: 7 + ttl: 2 + config_ttl }, ); } @@ -532,36 +413,29 @@ fn schedule_schedules_including_just_freed() { config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - let thread_a = ParaId::from(3_u32); - let thread_b = ParaId::from(4_u32); - let thread_c = ParaId::from(5_u32); - let thread_d = ParaId::from(6_u32); - let thread_e = ParaId::from(7_u32); + let para_a = ParaId::from(3_u32); + let para_b = ParaId::from(4_u32); + let para_c = ParaId::from(5_u32); + let para_d = ParaId::from(6_u32); + let para_e = ParaId::from(7_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; - let assignment_c = Assignment { para_id: thread_c }; - let assignment_d = Assignment { para_id: thread_d }; - let assignment_e = Assignment { para_id: thread_e }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); + let assignment_d = Assignment::Bulk(para_d); + let assignment_e = Assignment::Bulk(para_e); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(3); - // register 2 lease holding parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // add 5 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); + schedule_blank_para(para_d); + schedule_blank_para(para_e); - // and 5 parathreads (on-demand parachains) - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); - schedule_blank_para(thread_c, ParaKind::Parathread); - schedule_blank_para(thread_d, ParaKind::Parathread); - schedule_blank_para(thread_e, ParaKind::Parathread); - - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: default_config(), @@ -569,153 +443,113 @@ fn schedule_schedules_including_just_freed() { ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), _ => None, }); - // add a couple of parathread claims now that the parathreads are live. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_c, - QueuePushDirection::Back - )); + // add a couple of para claims now that paras are live + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); let mut now = 2; run_to_block(now, |_| None); - assert_eq!(Scheduler::scheduled_paras().collect::>().len(), 4); + assert_eq!(Scheduler::scheduled_paras().collect::>().len(), 2); - // cores 0, 1, 2, and 3 should be occupied. mark them as such. + // cores 0, 1 should be occupied. mark them as such. let mut occupied_map: BTreeMap = BTreeMap::new(); - occupied_map.insert(CoreIndex(0), chain_a); - occupied_map.insert(CoreIndex(1), chain_b); - occupied_map.insert(CoreIndex(2), thread_a); - occupied_map.insert(CoreIndex(3), thread_c); + occupied_map.insert(CoreIndex(0), para_a); + occupied_map.insert(CoreIndex(1), para_c); Scheduler::occupied(occupied_map); { let cores = AvailabilityCores::::get(); - // cores 0, 1, 2, and 3 are all `CoreOccupied::Paras(ParasEntry...)` + // cores 0, 1 are `CoreOccupied::Paras(ParasEntry...)` assert!(cores[0] != CoreOccupied::Free); assert!(cores[1] != CoreOccupied::Free); - assert!(cores[2] != CoreOccupied::Free); - assert!(cores[3] != CoreOccupied::Free); - // core 4 is free - assert!(cores[4] == CoreOccupied::Free); + // core 2 is free + assert!(cores[2] == CoreOccupied::Free); assert!(Scheduler::scheduled_paras().collect::>().is_empty()); - // All core index entries in the claimqueue should have `None` in them. - Scheduler::claimqueue().iter().for_each(|(_core_idx, core_queue)| { - assert!(core_queue.iter().all(|claim| claim.is_none())) - }) + // All `core_queue`s should be empty + Scheduler::claimqueue() + .iter() + .for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0)) } - // add a couple more parathread claims - the claim on `b` will go to the 3rd parathread core - // (4) and the claim on `d` will go back to the 1st parathread core (2). The claim on `e` - // then will go for core `3`. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_d, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_e.clone(), - QueuePushDirection::Back - )); + // add a couple more para claims - the claim on `b` will go to the 3rd core + // (2) and the claim on `d` will go back to the 1st para core (0). The claim on `e` + // then will go for core `1`. + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_d.clone()); + MockAssigner::add_test_assignment(assignment_e.clone()); now = 3; run_to_block(now, |_| None); { - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); - // cores 0 and 1 are occupied by lease holding parachains. cores 2 and 3 are occupied by - // on-demand parachain claims. core 4 was free. + // cores 0 and 1 are occupied by claims. core 2 was free. assert_eq!(scheduled.len(), 1); assert_eq!( - scheduled.get(&CoreIndex(4)).unwrap(), + scheduled.get(&CoreIndex(2)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_b }, + assignment: Assignment::Bulk(para_b), availability_timeouts: 0, ttl: 8 }, ); } - // now note that cores 0, 2, and 3 were freed. + // now note that cores 0 and 1 were freed. let just_updated: BTreeMap = vec![ (CoreIndex(0), FreedReason::Concluded), - (CoreIndex(2), FreedReason::Concluded), - (CoreIndex(3), FreedReason::TimedOut), // should go back on queue. + (CoreIndex(1), FreedReason::TimedOut), // should go back on queue. ] .into_iter() .collect(); - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); { - let scheduled: BTreeMap<_, _> = Scheduler::scheduled_entries().collect(); + let scheduled: BTreeMap<_, _> = scheduled_entries().collect(); - // 1 thing scheduled before, + 3 cores freed. - assert_eq!(scheduled.len(), 4); + // 1 thing scheduled before, + 2 cores freed. + assert_eq!(scheduled.len(), 3); assert_eq!( scheduled.get(&CoreIndex(0)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: chain_a }, - availability_timeouts: 0, - ttl: 8 - }, - ); - assert_eq!( - scheduled.get(&CoreIndex(2)).unwrap(), - &ParasEntry { - assignment: Assignment { para_id: thread_d }, + assignment: Assignment::Bulk(para_d), availability_timeouts: 0, ttl: 8 }, ); - // Although C was descheduled, the core `4` was occupied so C goes back to the queue. + // Although C was descheduled, the core `2` was occupied so C goes back to the queue. assert_eq!( - scheduled.get(&CoreIndex(3)).unwrap(), + scheduled.get(&CoreIndex(1)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_c }, + assignment: Assignment::Bulk(para_c), availability_timeouts: 1, ttl: 8 }, ); assert_eq!( - scheduled.get(&CoreIndex(4)).unwrap(), + scheduled.get(&CoreIndex(2)).unwrap(), &ParasEntry { - assignment: Assignment { para_id: thread_b }, + assignment: Assignment::Bulk(para_b), availability_timeouts: 0, ttl: 8 }, ); - // The only assignment yet to be popped on to the claim queue is `thread_e`. - // This is due to `thread_c` timing out. - let order_queue = OnDemandAssigner::get_queue(); - assert!(order_queue.len() == 1); - assert!(order_queue[0] == assignment_e); - - // Chain B's core was not marked concluded or timed out, it should be on an - // availability core - assert!(availability_cores_contains_para_ids::(vec![chain_b])); - // Thread A claim should have been wiped, but thread C claim should remain. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(claimqueue_contains_para_ids::(vec![thread_c])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a, thread_c])); + // Para A claim should have been wiped, but para C claim should remain. + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(claimqueue_contains_para_ids::(vec![para_c])); + assert!(!availability_cores_contains_para_ids::(vec![para_a, para_c])); } }); } @@ -726,28 +560,35 @@ fn schedule_clears_availability_cores() { config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - let chain_c = ParaId::from(3_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); + let para_c = ParaId::from(3_u32); + + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); + let assignment_c = Assignment::Bulk(para_c); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(3); + + // register 3 paras + schedule_blank_para(para_a); + schedule_blank_para(para_b); + schedule_blank_para(para_c); - // register 3 parachains - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); - schedule_blank_para(chain_c, ParaKind::Parachain); + // Adding assignments then running block to populate claim queue + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 3 validators for 3 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: default_config(), + new_config: config.clone(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Bob.public()), ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), ], ..Default::default() }), @@ -760,7 +601,7 @@ fn schedule_clears_availability_cores() { // cores 0, 1, and 2 should be occupied. mark them as such. Scheduler::occupied( - vec![(CoreIndex(0), chain_a), (CoreIndex(1), chain_b), (CoreIndex(2), chain_c)] + vec![(CoreIndex(0), para_a), (CoreIndex(1), para_b), (CoreIndex(2), para_c)] .into_iter() .collect(), ); @@ -772,9 +613,16 @@ fn schedule_clears_availability_cores() { assert_eq!(cores[1].is_free(), false); assert_eq!(cores[2].is_free(), false); - assert!(claimqueue_contains_only_none()); + // All `core_queue`s should be empty + Scheduler::claimqueue() + .iter() + .for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0)) } + // Add more assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_c.clone()); + run_to_block(3, |_| None); // now note that cores 0 and 2 were freed. @@ -786,20 +634,18 @@ fn schedule_clears_availability_cores() { ); { - let claimqueue = Scheduler::claimqueue(); + let claimqueue = ClaimQueue::::get(); let claimqueue_0 = claimqueue.get(&CoreIndex(0)).unwrap().clone(); let claimqueue_2 = claimqueue.get(&CoreIndex(2)).unwrap().clone(); let entry_ttl = 8; assert_eq!(claimqueue_0.len(), 1); assert_eq!(claimqueue_2.len(), 1); - assert_eq!( - claimqueue_0, - vec![Some(ParasEntry::new(Assignment::new(chain_a), entry_ttl))], - ); - assert_eq!( - claimqueue_2, - vec![Some(ParasEntry::new(Assignment::new(chain_c), entry_ttl))], - ); + let queue_0_expectation: VecDeque> = + vec![ParasEntry::new(assignment_a, entry_ttl as u32)].into_iter().collect(); + let queue_2_expectation: VecDeque> = + vec![ParasEntry::new(assignment_c, entry_ttl as u32)].into_iter().collect(); + assert_eq!(claimqueue_0, queue_0_expectation); + assert_eq!(claimqueue_2, queue_2_expectation); // The freed cores should be `Free` in `AvailabilityCores`. let cores = AvailabilityCores::::get(); @@ -813,32 +659,28 @@ fn schedule_clears_availability_cores() { fn schedule_rotates_groups() { let config = { let mut config = default_config(); - - // make sure on demand requests don't retry-out - config.on_demand_retries = config.group_rotation_frequency * 3; - config.on_demand_cores = 2; config.scheduling_lookahead = 1; config }; let rotation_frequency = config.group_rotation_frequency; - let on_demand_cores = config.on_demand_cores; + let on_demand_cores = 2; let genesis_config = genesis_config(&config); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - assert_eq!(default_config().on_demand_cores, 3); + MockAssigner::set_core_count(on_demand_cores); - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { new_config: config.clone(), @@ -854,14 +696,8 @@ fn schedule_rotates_groups() { let session_start_block = Scheduler::session_start_block(); assert_eq!(session_start_block, 1); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a, - QueuePushDirection::Back - )); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b, - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); let mut now = 2; run_to_block(now, |_| None); @@ -909,16 +745,20 @@ fn on_demand_claims_are_pruned_after_timing_out() { let max_retries = 20; let mut config = default_config(); config.scheduling_lookahead = 1; - config.on_demand_cores = 2; - config.on_demand_retries = max_retries; let genesis_config = genesis_config(&config); - let thread_a = ParaId::from(1_u32); + let para_a = ParaId::from(1_u32); - let assignment_a = Assignment { para_id: thread_a }; + let assignment_a = Assignment::Bulk(para_a); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); + MockAssigner::set_core_count(2); + // Need more timeouts for this test + MockAssigner::set_assignment_provider_config(AssignmentProviderConfig { + max_availability_timeouts: max_retries, + ttl: BlockNumber::from(5u32), + }); + schedule_blank_para(para_a); // #1 let mut now = 1; @@ -934,23 +774,20 @@ fn on_demand_claims_are_pruned_after_timing_out() { _ => None, }); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); // #2 now += 1; run_to_block(now, |_| None); assert_eq!(Scheduler::claimqueue().len(), 1); // ParaId a is in the claimqueue. - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); - Scheduler::occupied(vec![(CoreIndex(0), thread_a)].into_iter().collect()); + Scheduler::occupied(vec![(CoreIndex(0), para_a)].into_iter().collect()); // ParaId a is no longer in the claimqueue. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); // It is in availability cores. - assert!(availability_cores_contains_para_ids::(vec![thread_a])); + assert!(availability_cores_contains_para_ids::(vec![para_a])); // #3 now += 1; @@ -966,36 +803,32 @@ fn on_demand_claims_are_pruned_after_timing_out() { ] .into_iter() .collect(); - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); // ParaId a exists in the claim queue until max_retries is reached. if n < max_retries + now { - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); } else { - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); } let core_assignments = Scheduler::scheduled_paras().collect(); - // Occupy the cores based on the result of update_claimqueue. Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after // threshold has been reached. - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(!availability_cores_contains_para_ids::(vec![para_a])); // #25 now += max_retries + 2; // Add assignment back to the mix. - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); run_to_block(now, |_| None); - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); // #26 now += 1; @@ -1017,24 +850,23 @@ fn on_demand_claims_are_pruned_after_timing_out() { } } - Scheduler::update_claimqueue(just_updated, now); + Scheduler::free_cores_and_fill_claimqueue(just_updated, now); // ParaId a exists in the claim queue until groups are rotated. if n < 31 { - assert!(claimqueue_contains_para_ids::(vec![thread_a])); + assert!(claimqueue_contains_para_ids::(vec![para_a])); } else { - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); } let core_assignments = Scheduler::scheduled_paras().collect(); - // Occupy the cores based on the result of update_claimqueue. Scheduler::occupied(core_assignments); } // ParaId a does not exist in the claimqueue/availability_cores after // being concluded - assert!(!claimqueue_contains_para_ids::(vec![thread_a])); - assert!(!availability_cores_contains_para_ids::(vec![thread_a])); + assert!(!claimqueue_contains_para_ids::(vec![para_a])); + assert!(!availability_cores_contains_para_ids::(vec![para_a])); }); } @@ -1047,40 +879,7 @@ fn availability_predicate_works() { assert!(paras_availability_period < group_rotation_frequency); - let chain_a = ParaId::from(1_u32); - let thread_a = ParaId::from(2_u32); - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(thread_a, ParaKind::Parathread); - - // start a new session with our chain registered. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: default_config(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Bob.public()), - ValidatorId::from(Sr25519Keyring::Charlie.public()), - ValidatorId::from(Sr25519Keyring::Dave.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - // assign some availability cores. - { - let entry_ttl = 10_000; - AvailabilityCores::::mutate(|cores| { - cores[0] = - CoreOccupied::Paras(ParasEntry::new(Assignment::new(chain_a), entry_ttl)); - cores[1] = - CoreOccupied::Paras(ParasEntry::new(Assignment::new(thread_a), entry_ttl)); - }); - } - run_to_block(1 + paras_availability_period, |_| None); assert!(!Scheduler::availability_timeout_check_required()); @@ -1103,29 +902,25 @@ fn availability_predicate_works() { // check the threshold is exact. assert!(!pred(would_be_timed_out + 1).timed_out); } - - run_to_block(1 + group_rotation_frequency + paras_availability_period, |_| None); }); } #[test] -fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { - let mut config = default_config(); - config.on_demand_cores = 1; - - let genesis_config = genesis_config(&config); +fn next_up_on_available_uses_next_scheduled_or_none() { + let genesis_config = genesis_config(&default_config()); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + MockAssigner::set_core_count(1); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: config.clone(), + new_config: default_config(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Eve.public()), @@ -1135,18 +930,18 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { _ => None, }); - let thread_entry_a = ParasEntry { - assignment: Assignment { para_id: thread_a }, - availability_timeouts: 0, - ttl: 5, + let entry_a = ParasEntry { + assignment: Assignment::Bulk(para_a), + availability_timeouts: 0 as u32, + ttl: 5 as u32, }; - let thread_entry_b = ParasEntry { - assignment: Assignment { para_id: thread_b }, - availability_timeouts: 0, - ttl: 5, + let entry_b = ParasEntry { + assignment: Assignment::Bulk(para_b), + availability_timeouts: 0 as u32, + ttl: 5 as u32, }; - Scheduler::add_to_claimqueue(CoreIndex(0), thread_entry_a.clone()); + Scheduler::add_to_claimqueue(CoreIndex(0), entry_a.clone()); run_to_block(2, |_| None); @@ -1155,22 +950,22 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { assert_eq!(Scheduler::availability_cores().len(), 1); let mut map = BTreeMap::new(); - map.insert(CoreIndex(0), thread_a); + map.insert(CoreIndex(0), para_a); Scheduler::occupied(map); let cores = Scheduler::availability_cores(); match &cores[0] { - CoreOccupied::Paras(entry) => assert_eq!(entry, &thread_entry_a), - _ => panic!("with no chains, only core should be a thread core"), + CoreOccupied::Paras(entry) => assert_eq!(entry, &entry_a), + _ => panic!("There should only be one test assigner core"), } assert!(Scheduler::next_up_on_available(CoreIndex(0)).is_none()); - Scheduler::add_to_claimqueue(CoreIndex(0), thread_entry_b); + Scheduler::add_to_claimqueue(CoreIndex(0), entry_b); assert_eq!( Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_b, collator: None } + ScheduledCore { para_id: para_b, collator: None } ); } }); @@ -1178,25 +973,23 @@ fn next_up_on_available_uses_next_scheduled_or_none_for_thread() { #[test] fn next_up_on_time_out_reuses_claim_if_nothing_queued() { - let mut config = default_config(); - config.on_demand_cores = 1; - - let genesis_config = genesis_config(&config); + let genesis_config = genesis_config(&default_config()); - let thread_a = ParaId::from(1_u32); - let thread_b = ParaId::from(2_u32); + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); - let assignment_a = Assignment { para_id: thread_a }; - let assignment_b = Assignment { para_id: thread_b }; + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(thread_a, ParaKind::Parathread); - schedule_blank_para(thread_b, ParaKind::Parathread); + MockAssigner::set_core_count(1); + schedule_blank_para(para_a); + schedule_blank_para(para_b); - // start a new session to activate, 5 validators for 5 cores. + // start a new session to activate, 2 validators for 2 cores. run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { - new_config: config.clone(), + new_config: default_config(), validators: vec![ ValidatorId::from(Sr25519Keyring::Alice.public()), ValidatorId::from(Sr25519Keyring::Eve.public()), @@ -1206,10 +999,7 @@ fn next_up_on_time_out_reuses_claim_if_nothing_queued() { _ => None, }); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_a.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_a.clone()); run_to_block(2, |_| None); @@ -1218,150 +1008,62 @@ fn next_up_on_time_out_reuses_claim_if_nothing_queued() { assert_eq!(Scheduler::availability_cores().len(), 1); let mut map = BTreeMap::new(); - map.insert(CoreIndex(0), thread_a); + map.insert(CoreIndex(0), para_a); Scheduler::occupied(map); let cores = Scheduler::availability_cores(); match cores.get(0).unwrap() { - CoreOccupied::Paras(entry) => assert_eq!(entry.assignment, assignment_a.clone()), - _ => panic!("with no chains, only core should be a thread core"), + CoreOccupied::Paras(entry) => { + assert_eq!(entry.assignment, assignment_a.clone()); + }, + _ => panic!("There should only be a single test assigner core"), } // There's nothing more to pop for core 0 from the assignment provider. - assert!( - OnDemandAssigner::pop_assignment_for_core(CoreIndex(0), Some(thread_a)).is_none() - ); + assert!(MockAssigner::pop_assignment_for_core(CoreIndex(0)).is_none()); assert_eq!( Scheduler::next_up_on_time_out(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_a, collator: None } + ScheduledCore { para_id: para_a, collator: None } ); - assert_ok!(OnDemandAssigner::add_on_demand_assignment( - assignment_b.clone(), - QueuePushDirection::Back - )); + MockAssigner::add_test_assignment(assignment_b.clone()); // Pop assignment_b into the claimqueue - Scheduler::update_claimqueue(BTreeMap::new(), 2); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 2); //// Now that there is an earlier next-up, we use that. assert_eq!( Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: thread_b, collator: None } + ScheduledCore { para_id: para_b, collator: None } ); } }); } #[test] -fn next_up_on_available_is_parachain_always() { +fn session_change_requires_reschedule_dropping_removed_paras() { let mut config = default_config(); - config.on_demand_cores = 0; + config.scheduling_lookahead = 1; let genesis_config = genesis_config(&config); - let chain_a = ParaId::from(1_u32); - - new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - - // start a new session to activate, 5 validators for 5 cores. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: config.clone(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - run_to_block(2, |_| None); - - { - assert_eq!(Scheduler::claimqueue().len(), 1); - assert_eq!(Scheduler::availability_cores().len(), 1); - - Scheduler::occupied(vec![(CoreIndex(0), chain_a)].into_iter().collect()); - - let cores = Scheduler::availability_cores(); - match &cores[0] { - CoreOccupied::Paras(pe) if pe.para_id() == chain_a => {}, - _ => panic!("with no threads, only core should be a chain core"), - } - // Now that there is an earlier next-up, we use that. - assert_eq!( - Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: chain_a, collator: None } - ); - } - }); -} + let para_a = ParaId::from(1_u32); + let para_b = ParaId::from(2_u32); -#[test] -fn next_up_on_time_out_is_parachain_always() { - let mut config = default_config(); - config.on_demand_cores = 0; - - let genesis_config = genesis_config(&config); - - let chain_a = ParaId::from(1_u32); + let assignment_a = Assignment::Bulk(para_a); + let assignment_b = Assignment::Bulk(para_b); new_test_ext(genesis_config).execute_with(|| { - schedule_blank_para(chain_a, ParaKind::Parachain); - - // start a new session to activate, 5 validators for 5 cores. - run_to_block(1, |number| match number { - 1 => Some(SessionChangeNotification { - new_config: config.clone(), - validators: vec![ - ValidatorId::from(Sr25519Keyring::Alice.public()), - ValidatorId::from(Sr25519Keyring::Eve.public()), - ], - ..Default::default() - }), - _ => None, - }); - - run_to_block(2, |_| None); + // Setting explicit core count + MockAssigner::set_core_count(5); + let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl; - { - assert_eq!(Scheduler::claimqueue().len(), 1); - assert_eq!(Scheduler::availability_cores().len(), 1); - - Scheduler::occupied(vec![(CoreIndex(0), chain_a)].into_iter().collect()); - - let cores = Scheduler::availability_cores(); - match &cores[0] { - CoreOccupied::Paras(pe) if pe.para_id() == chain_a => {}, - _ => panic!("Core should be occupied by chain_a ParaId"), - } - - // Now that there is an earlier next-up, we use that. - assert_eq!( - Scheduler::next_up_on_available(CoreIndex(0)).unwrap(), - ScheduledCore { para_id: chain_a, collator: None } - ); - } - }); -} + schedule_blank_para(para_a); + schedule_blank_para(para_b); -#[test] -fn session_change_requires_reschedule_dropping_removed_paras() { - let mut config = default_config(); - config.scheduling_lookahead = 1; - let genesis_config = genesis_config(&config); - - assert_eq!(default_config().on_demand_cores, 3); - new_test_ext(genesis_config).execute_with(|| { - let chain_a = ParaId::from(1_u32); - let chain_b = ParaId::from(2_u32); - - // ensure that we have 5 groups by registering 2 parachains. - schedule_blank_para(chain_a, ParaKind::Parachain); - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); run_to_block(1, |number| match number { 1 => Some(SessionChangeNotification { @@ -1386,7 +1088,11 @@ fn session_change_requires_reschedule_dropping_removed_paras() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 5); - assert_ok!(Paras::schedule_para_cleanup(chain_b)); + assert_ok!(Paras::schedule_para_cleanup(para_b)); + + // Add assignment + MockAssigner::add_test_assignment(assignment_a.clone()); + run_to_end_of_block(2, |number| match number { 2 => Some(SessionChangeNotification { new_config: default_config(), @@ -1405,17 +1111,17 @@ fn session_change_requires_reschedule_dropping_removed_paras() { _ => None, }); - Scheduler::update_claimqueue(BTreeMap::new(), 3); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 3); assert_eq!( Scheduler::claimqueue(), vec![( CoreIndex(0), - vec![Some(ParasEntry::new( - Assignment::new(chain_a), + vec![ParasEntry::new( + Assignment::Bulk(para_a), // At end of block 2 - config.on_demand_ttl + 2 - ))] + assignment_provider_ttl + 2 + )] .into_iter() .collect() )] @@ -1423,8 +1129,12 @@ fn session_change_requires_reschedule_dropping_removed_paras() { .collect() ); - // Add parachain back - schedule_blank_para(chain_b, ParaKind::Parachain); + // Add para back + schedule_blank_para(para_b); + + // Add assignments + MockAssigner::add_test_assignment(assignment_a.clone()); + MockAssigner::add_test_assignment(assignment_b.clone()); run_to_block(3, |number| match number { 3 => Some(SessionChangeNotification { @@ -1449,28 +1159,28 @@ fn session_change_requires_reschedule_dropping_removed_paras() { let groups = ValidatorGroups::::get(); assert_eq!(groups.len(), 5); - Scheduler::update_claimqueue(BTreeMap::new(), 4); + Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 4); assert_eq!( Scheduler::claimqueue(), vec![ ( CoreIndex(0), - vec![Some(ParasEntry::new( - Assignment::new(chain_a), + vec![ParasEntry::new( + Assignment::Bulk(para_a), // At block 3 - config.on_demand_ttl + 3 - ))] + assignment_provider_ttl + 3 + )] .into_iter() .collect() ), ( CoreIndex(1), - vec![Some(ParasEntry::new( - Assignment::new(chain_b), + vec![ParasEntry::new( + Assignment::Bulk(para_b), // At block 3 - config.on_demand_ttl + 3 - ))] + assignment_provider_ttl + 3 + )] .into_iter() .collect() ), diff --git a/polkadot/runtime/parachains/src/session_info/tests.rs b/polkadot/runtime/parachains/src/session_info/tests.rs index 727b7c79fbaeae18be2c2a40f4f38f562d1481dd..92a50575deda8413abb18f892680d693c148d0cb 100644 --- a/polkadot/runtime/parachains/src/session_info/tests.rs +++ b/polkadot/runtime/parachains/src/session_info/tests.rs @@ -62,7 +62,7 @@ fn run_to_block( fn default_config() -> HostConfiguration { HostConfiguration { - on_demand_cores: 1, + coretime_cores: 1, dispute_period: 2, needed_approvals: 3, ..Default::default() diff --git a/polkadot/runtime/parachains/src/ump_tests.rs b/polkadot/runtime/parachains/src/ump_tests.rs index def608882050ab57a4a95fe8c5b14a4852fbebb4..426993ffa65a73b83d4077ee31b64217c41fb168 100644 --- a/polkadot/runtime/parachains/src/ump_tests.rs +++ b/polkadot/runtime/parachains/src/ump_tests.rs @@ -523,21 +523,21 @@ fn overweight_queue_works() { assert_last_events( [ pallet_message_queue::Event::::Processed { - id: hash_1, + id: hash_1.into(), origin: Ump(UmpQueueId::Para(para_a)), weight_used: Weight::from_parts(301, 301), success: true, } .into(), pallet_message_queue::Event::::OverweightEnqueued { - id: hash_2, + id: hash_2.into(), origin: Ump(UmpQueueId::Para(para_a)), page_index: 0, message_index: 1, } .into(), pallet_message_queue::Event::::OverweightEnqueued { - id: hash_3, + id: hash_3.into(), origin: Ump(UmpQueueId::Para(para_a)), page_index: 0, message_index: 2, @@ -565,7 +565,7 @@ fn overweight_queue_works() { )); assert_last_event( pallet_message_queue::Event::::Processed { - id: hash_3, + id: hash_3.into(), origin: Ump(UmpQueueId::Para(para_a)), weight_used: Weight::from_parts(501, 501), success: true, diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 1edce2aa44d31af08adf9f6b894cded82ab0fff5..992cc0b708afa2114aa9e71433cc2d87295c9e01 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -7,11 +7,14 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +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.193", default-features = false } +serde = { version = "1.0.195", default-features = false } serde_derive = { version = "1.0.117", optional = true } static_assertions = "1.1.0" smallvec = "1.8.0" @@ -108,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.108" +serde_json = "1.0.111" sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/runtime/rococo/constants/Cargo.toml b/polkadot/runtime/rococo/constants/Cargo.toml index e052c89311f269a9c8cb27c1ec1aa489c4df746f..1e6b0a5f903c7880b2e69665a700ee1c2dcaeb5b 100644 --- a/polkadot/runtime/rococo/constants/Cargo.toml +++ b/polkadot/runtime/rococo/constants/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] smallvec = "1.8.0" @@ -17,6 +20,7 @@ sp-weights = { path = "../../../../substrate/primitives/weights", default-featur sp-core = { path = "../../../../substrate/primitives/core", default-features = false } xcm = { package = "staging-xcm", path = "../../../xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../xcm/xcm-builder", default-features = false } [features] default = ["std"] @@ -27,6 +31,7 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-weights/std", + "xcm-builder/std", "xcm/std", ] diff --git a/polkadot/runtime/rococo/constants/src/lib.rs b/polkadot/runtime/rococo/constants/src/lib.rs index 2f641d60fc8b098274f39a5e72ef2e42ede50c27..9209045364c28bc585c548d6d2b30176bd52bb20 100644 --- a/polkadot/runtime/rococo/constants/src/lib.rs +++ b/polkadot/runtime/rococo/constants/src/lib.rs @@ -103,7 +103,8 @@ pub mod fee { /// System Parachains. pub mod system_parachain { - use xcm::latest::prelude::*; + use primitives::Id; + use xcm_builder::IsChildSystemParachain; /// Network's Asset Hub parachain ID. pub const ASSET_HUB_ID: u32 = 1000; @@ -111,14 +112,15 @@ pub mod system_parachain { pub const CONTRACTS_ID: u32 = 1002; /// Encointer parachain ID. pub const ENCOINTER_ID: u32 = 1003; + /// People parachain ID. + pub const PEOPLE_ID: u32 = 1004; /// BridgeHub parachain ID. pub const BRIDGE_HUB_ID: u32 = 1013; + /// Brokerage parachain ID. + pub const BROKER_ID: u32 = 1005; - frame_support::match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(ASSET_HUB_ID | CONTRACTS_ID | ENCOINTER_ID | BRIDGE_HUB_ID)) } - }; - } + /// All system parachains of Rococo. + pub type SystemParachains = IsChildSystemParachain; } /// Rococo Treasury pallet instance. diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 21d4088df19cff49aaa804f03fe0c602a80fa00f..67caa347bc384919f1e009f371efa8876cd5b703 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -23,28 +23,32 @@ use pallet_nis::WithMaximumOf; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - slashing, vstaging::NodeFeatures, AccountId, AccountIndex, Balance, BlockNumber, - CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, - ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, - ScrapedOnChainVotes, SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, PARACHAIN_KEY_TYPE_ID, + slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, + OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionInfo, Signature, + ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID, }; +use rococo_runtime_constants::system_parachain::BROKER_ID; use runtime_common::{ assigned_slots, auctions, claims, crowdloan, identity_migrator, impl_runtime_weights, impls::{ LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter, }, - paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BlockHashCount, BlockLength, - SlowAdjustingFeeUpdate, + paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, + traits::Leaser, + BlockHashCount, BlockLength, SlowAdjustingFeeUpdate, }; use scale_info::TypeInfo; use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*}; use runtime_parachains::{ - assigner as parachains_assigner, assigner_on_demand as parachains_assigner_on_demand, + assigner_coretime as parachains_assigner_coretime, + assigner_on_demand as parachains_assigner_on_demand, assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, disputes as parachains_disputes, + 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}, @@ -149,10 +153,10 @@ 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_004_000, + spec_version: 1_005_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 22, + transaction_version: 24, state_version: 1, }; @@ -276,8 +280,7 @@ impl pallet_babe::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; type MaxNominators = ConstU32<0>; - type KeyOwnerProof = - >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_babe::EquivocationReportSystem; } @@ -565,7 +568,7 @@ impl pallet_grandpa::Config for Runtime { type MaxAuthorities = MaxAuthorities; type MaxNominators = ConstU32<0>; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem; } @@ -745,6 +748,7 @@ impl pallet_vesting::Config for Runtime { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = weights::pallet_vesting::WeightInfo; type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } @@ -934,6 +938,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = Registrar; + type AssignCoretime = CoretimeAssignmentProvider; } parameter_types! { @@ -1000,7 +1005,22 @@ impl parachains_paras_inherent::Config for Runtime { } impl parachains_scheduler::Config for Runtime { - type AssignmentProvider = ParaAssignmentProvider; + // If you change this, make sure the `Assignment` type of the new provider is binary compatible, + // otherwise provide a migration. + type AssignmentProvider = CoretimeAssignmentProvider; +} + +parameter_types! { + pub const BrokerId: u32 = BROKER_ID; +} + +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! { @@ -1016,15 +1036,13 @@ impl parachains_assigner_on_demand::Config for Runtime { impl parachains_assigner_parachains::Config for Runtime {} -impl parachains_assigner::Config for Runtime { - type OnDemandAssignmentProvider = OnDemandAssignmentProvider; - type ParachainsAssignmentProvider = ParachainsAssignmentProvider; -} +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 = Coretime; } impl parachains_disputes::Config for Runtime { @@ -1219,19 +1237,6 @@ impl pallet_mmr::Config for Runtime { } parameter_types! { - /// Version of the produced MMR leaf. - /// - /// The version consists of two parts; - /// - `major` (3 bits) - /// - `minor` (5 bits) - /// - /// `major` should be updated only if decoding the previous MMR Leaf format from the payload - /// is not possible (i.e. backward incompatible change). - /// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE - /// encoding does not prevent old leafs from being decoded. - /// - /// Hence we expect `major` to be changed really rarely (think never). - /// See [`MmrLeafVersion`] type documentation for more details. pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0); } @@ -1404,15 +1409,16 @@ construct_runtime! { ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event} = 62, ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 63, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event} = 64, - ParaAssignmentProvider: parachains_assigner::{Pallet, Storage} = 65, OnDemandAssignmentProvider: parachains_assigner_on_demand::{Pallet, Call, Storage, Event} = 66, ParachainsAssignmentProvider: parachains_assigner_parachains::{Pallet} = 67, + CoretimeAssignmentProvider: parachains_assigner_coretime::{Pallet, Storage} = 68, // Parachain Onboarding Pallets. Start indices at 70 to leave room. Registrar: paras_registrar::{Pallet, Call, Storage, Event, Config} = 70, Slots: slots::{Pallet, Call, Storage, Event} = 71, Auctions: auctions::{Pallet, Call, Storage, Event} = 72, Crowdloan: crowdloan::{Pallet, Call, Storage, Event} = 73, + Coretime: coretime::{Pallet, Call, Event} = 74, // Pallet for sending XCM. XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin, Config} = 99, @@ -1479,6 +1485,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 DemocracyPalletName: &'static str = "Democracy"; pub const CouncilPalletName: &'static str = "Council"; @@ -1601,7 +1625,7 @@ pub mod migrations { pallet_society::migrations::MigrateToV2, parachains_configuration::migration::v7::MigrateToV7, assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, paras_registrar::migration::MigrateToV1, @@ -1631,6 +1655,9 @@ pub mod migrations { // Remove `im-online` pallet on-chain storage frame_support::migrations::RemovePallet::DbWeight>, + parachains_configuration::migration::v11::MigrateToV11, + // This needs to come after the `parachains_configuration` above as we are reading the configuration. + coretime::migration::MigrateToCoretime, ); } @@ -1679,6 +1706,7 @@ mod benches { // the that path resolves correctly in the generated file. [runtime_common::assigned_slots, AssignedSlots] [runtime_common::auctions, Auctions] + [runtime_common::coretime, Coretime] [runtime_common::crowdloan, Crowdloan] [runtime_common::claims, Claims] [runtime_common::identity_migrator, IdentityMigrator] @@ -1792,7 +1820,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(9)] + #[api_version(10)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -1936,6 +1964,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::async_backing_params::() } + fn approval_voting_params() -> ApprovalVotingParams { + parachains_staging_runtime_api_impl::approval_voting_params::() + } + fn disabled_validators() -> Vec { parachains_staging_runtime_api_impl::disabled_validators::() } @@ -2261,6 +2293,20 @@ sp_api::impl_runtime_apis! { Parachain(43211234).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Relay supports only native token, either reserve transfer it to non-system parachains, + // or teleport it to system parachain. Use the teleport case for benchmarking as it's + // slightly heavier. + // Relay/native token can be teleported to/from AH. + let native_location = Here.into(); + let dest = crate::xcm_config::AssetHub::get(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = XcmConfig; diff --git a/polkadot/runtime/rococo/src/weights/frame_system.rs b/polkadot/runtime/rococo/src/weights/frame_system.rs index 7765d669a577cc110722b4453dfa33b9dbe6bd35..2e49483dcc62728f3554bb1364efd740f8b03fd2 100644 --- a/polkadot/runtime/rococo/src/weights/frame_system.rs +++ b/polkadot/runtime/rococo/src/weights/frame_system.rs @@ -141,4 +141,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index bd2079ce8277ef511f38cfde8251ddeb9087d446..3613fb4305ba0f7a35190a7ef788979c2f241207 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -51,6 +51,7 @@ pub mod runtime_common_paras_registrar; pub mod runtime_common_slots; pub mod runtime_parachains_assigner_on_demand; pub mod runtime_parachains_configuration; +pub mod runtime_parachains_coretime; pub mod runtime_parachains_disputes; pub mod runtime_parachains_hrmp; pub mod runtime_parachains_inclusion; diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index aafded3f7319fd3717f8e9a04ca4af80bd34547a..177407ef7088b5c9bdcc460f36cb7d6485743a71 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -48,6 +48,10 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + fn transfer_assets() -> Weight { + // TODO: run benchmarks + Weight::zero() + } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) diff --git a/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..d9f2d45207b923e3afe661a6021629cb8441970e --- /dev/null +++ b/polkadot/runtime/rococo/src/weights/runtime_parachains_coretime.rs @@ -0,0 +1,73 @@ +// 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 `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: `[]` +//! 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 + +// Executed Command: +// target/production/polkadot +// 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=runtime_common::coretime +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/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; + +use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo}; + +/// Weight functions for `runtime_common::coretime`. +pub struct WeightInfo(PhantomData); +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + fn request_core_count() -> Weight { + ::WeightInfo::set_config_with_u32() + } + /// 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())) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index c8f8f59dae9983f9b2f3dfe9248445710e7d1cb6..f3a2ca6f3a6cec34d2218aaa8c4b245d8a475c70 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -25,7 +25,7 @@ use crate::governance::StakingAdmin; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing}, + traits::{Equals, Everything, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; @@ -36,19 +36,21 @@ 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, CurrencyAdapter as XcmCurrencyAdapter, DescribeBodyTerminal, - DescribeFamily, FixedWeightBounds, HashedDescription, IsChildSystemParachain, IsConcrete, - MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, FixedWeightBounds, + HashedDescription, IsChildSystemParachain, IsConcrete, MintLocation, OriginToPluralityVoice, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { + pub const RootLocation: MultiLocation = MultiLocation::here(); pub const TokenLocation: MultiLocation = Here.into_location(); pub const ThisNetwork: NetworkId = NetworkId::Rococo; pub UniversalLocation: InteriorMultiLocation = ThisNetwork::get().into(); @@ -70,6 +72,7 @@ pub type LocationConverter = ( /// point of view of XCM-only concepts like `MultiLocation` and `MultiAsset`. /// /// Ours is only aware of the Balances pallet, which is mapped to `RocLocation`. +#[allow(deprecated)] pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, @@ -118,6 +121,8 @@ parameter_types! { pub const Contracts: MultiLocation = Parachain(CONTRACTS_ID).into_location(); pub const Encointer: MultiLocation = Parachain(ENCOINTER_ID).into_location(); pub const BridgeHub: MultiLocation = Parachain(BRIDGE_HUB_ID).into_location(); + pub const People: MultiLocation = Parachain(PEOPLE_ID).into_location(); + pub const Broker: MultiLocation = Parachain(BROKER_ID).into_location(); pub const Tick: MultiLocation = Parachain(100).into_location(); pub const Trick: MultiLocation = Parachain(110).into_location(); pub const Track: MultiLocation = Parachain(120).into_location(); @@ -128,6 +133,8 @@ parameter_types! { pub const RocForContracts: (MultiAssetFilter, MultiLocation) = (Roc::get(), Contracts::get()); pub const RocForEncointer: (MultiAssetFilter, MultiLocation) = (Roc::get(), Encointer::get()); pub const RocForBridgeHub: (MultiAssetFilter, MultiLocation) = (Roc::get(), BridgeHub::get()); + pub const RocForPeople: (MultiAssetFilter, MultiLocation) = (Roc::get(), People::get()); + pub const RocForBroker: (MultiAssetFilter, MultiLocation) = (Roc::get(), Broker::get()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } @@ -139,12 +146,17 @@ pub type TrustedTeleporters = ( xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, + xcm_builder::Case, ); match_types! { pub type OnlyParachains: impl Contains = { MultiLocation { parents: 0, interior: X1(Parachain(_)) } }; + pub type LocalPlurality: impl Contains = { + MultiLocation { parents: 0, interior: X1(Plurality { .. }) } + }; } /// The barriers one of which must be passed for an XCM message to be executed. @@ -167,6 +179,10 @@ pub type Barrier = TrailingSetTopicAsId<( >, )>; +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. +/// We only waive fees for system functions, which these locations represent. +pub type WaivedLocations = (SystemParachains, Equals, LocalPlurality); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -193,7 +209,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type FeeManager = XcmFeeManagerFromComponents< - SystemParachains, + WaivedLocations, XcmFeeToAccount, >; type MessageExporter = (); diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 850047c83bc3ba14a64d5663f496e819ad113b65..9778ff82439494767b83d5acb6f7f9f57ac89619 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -7,13 +7,16 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +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 } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false } +serde = { version = "1.0.195", default-features = false } serde_derive = { version = "1.0.117", optional = true } smallvec = "1.8.0" @@ -71,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.108" +serde_json = "1.0.111" [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } diff --git a/polkadot/runtime/test-runtime/constants/Cargo.toml b/polkadot/runtime/test-runtime/constants/Cargo.toml index 88cd441f73b4938dc955c406883d21a309079f4a..2b387bbd3072a91bc00afa7326d93f66a644f39e 100644 --- a/polkadot/runtime/test-runtime/constants/Cargo.toml +++ b/polkadot/runtime/test-runtime/constants/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] smallvec = "1.8.0" diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 81b9b63f75b94a886aef2e689bf121f4237de047..f472b619ba759cca80ebaf51056cb5a20f6eb165 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -74,7 +74,7 @@ use sp_runtime::{ SaturatedConversion, StaticLookup, Verify, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, KeyTypeId, Perbill, + ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, }; use sp_staking::SessionIndex; #[cfg(any(feature = "std", test))] @@ -356,6 +356,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota; type MaxUnlockingChunks = frame_support::traits::ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<5900>; type HistoryDepth = frame_support::traits::ConstU32<84>; type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig; type EventListeners = (); @@ -463,6 +464,7 @@ impl pallet_vesting::Config for Runtime { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } @@ -518,6 +520,7 @@ impl parachains_initializer::Config for Runtime { type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type ForceOrigin = frame_system::EnsureRoot; type WeightInfo = (); + type CoretimeOnNewSession = (); } impl parachains_session_info::Config for Runtime { @@ -535,6 +538,15 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = (); + type AssignCoretime = (); +} + +parameter_types! { + pub const BrokerId: u32 = 10u32; +} + +parameter_types! { + pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1); } impl parachains_dmp::Config for Runtime {} diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 418db22cf92ec6524fe42c16e83e4992d1b3b349..1f630a7d6d09ecfbf4442b936b7d612387e99e93 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -7,13 +7,16 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +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", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } rustc-hex = { version = "2.1.0", default-features = false } -serde = { version = "1.0.193", default-features = false } +serde = { version = "1.0.195", default-features = false } serde_derive = { version = "1.0.117", optional = true } smallvec = "1.8.0" @@ -117,7 +120,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.108" +serde_json = "1.0.111" 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/Cargo.toml b/polkadot/runtime/westend/constants/Cargo.toml index 24f0da63b0471cce84f892176a6266bf35f3eded..d2e86970e509389d491b29298b7f66f790696d45 100644 --- a/polkadot/runtime/westend/constants/Cargo.toml +++ b/polkadot/runtime/westend/constants/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] smallvec = "1.8.0" @@ -17,6 +20,7 @@ sp-weights = { path = "../../../../substrate/primitives/weights", default-featur sp-core = { path = "../../../../substrate/primitives/core", default-features = false } xcm = { package = "staging-xcm", path = "../../../xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../xcm/xcm-builder", default-features = false } [features] default = ["std"] @@ -27,5 +31,6 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-weights/std", + "xcm-builder/std", "xcm/std", ] diff --git a/polkadot/runtime/westend/constants/src/lib.rs b/polkadot/runtime/westend/constants/src/lib.rs index a06b3ba602a365c0c9d2fe006ae27ffc7003ae97..848cccd559dc3e50cbd2a50c4df6a643910fe35c 100644 --- a/polkadot/runtime/westend/constants/src/lib.rs +++ b/polkadot/runtime/westend/constants/src/lib.rs @@ -98,7 +98,8 @@ pub mod fee { /// System Parachains. pub mod system_parachain { - use xcm::latest::prelude::*; + use primitives::Id; + use xcm_builder::IsChildSystemParachain; /// Network's Asset Hub parachain ID. pub const ASSET_HUB_ID: u32 = 1000; @@ -106,12 +107,13 @@ pub mod system_parachain { pub const COLLECTIVES_ID: u32 = 1001; /// BridgeHub parachain ID. pub const BRIDGE_HUB_ID: u32 = 1002; + /// People Chain parachain ID. + pub const PEOPLE_ID: u32 = 1004; + /// Brokerage parachain ID. + pub const BROKER_ID: u32 = 1005; - frame_support::match_types! { - pub type SystemParachains: impl Contains = { - MultiLocation { parents: 0, interior: X1(Parachain(ASSET_HUB_ID | COLLECTIVES_ID | BRIDGE_HUB_ID ))} - }; - } + /// All system parachains of Westend. + pub type SystemParachains = IsChildSystemParachain; } /// Westend Treasury pallet instance. @@ -126,6 +128,7 @@ pub mod xcm { const ROOT_INDEX: u32 = 0; // The bodies corresponding to the Polkadot OpenGov Origins. pub const FELLOWSHIP_ADMIN_INDEX: u32 = 1; + pub const TREASURER_INDEX: u32 = 2; } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9c5231b2945453e723231e29da0f4282c1ef75ac..0de9621baa498db7b9070a79d9776fcd4ae91a3c 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -45,12 +45,14 @@ use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - slashing, vstaging::NodeFeatures, AccountId, AccountIndex, Balance, BlockNumber, - CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, - ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, - PvfCheckStatement, ScrapedOnChainVotes, SessionInfo, Signature, ValidationCode, - ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, PARACHAIN_KEY_TYPE_ID, + slashing, + vstaging::{ApprovalVotingParams, NodeFeatures}, + AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, + Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, + OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, + SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + ValidatorSignature, PARACHAIN_KEY_TYPE_ID, }; use runtime_common::{ assigned_slots, auctions, crowdloan, @@ -112,7 +114,7 @@ use sp_runtime::traits::Get; pub use sp_runtime::BuildStorage; /// Constant values used within the runtime. -use westend_runtime_constants::{currency::*, fee::*, time::*}; +use westend_runtime_constants::{currency::*, fee::*, system_parachain::BROKER_ID, time::*}; mod bag_thresholds; mod weights; @@ -144,10 +146,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 1_004_000, + spec_version: 1_005_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 22, + transaction_version: 24, state_version: 1, }; @@ -263,8 +265,7 @@ impl pallet_babe::Config for Runtime { type MaxAuthorities = MaxAuthorities; type MaxNominators = MaxNominators; - type KeyOwnerProof = - >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_babe::EquivocationReportSystem; @@ -316,7 +317,7 @@ impl pallet_beefy::Config for Runtime { type MaxSetIdSessionEntries = BeefySetIdSessionEntries; type OnNewValidatorSet = BeefyMmrLeaf; type WeightInfo = (); - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_beefy::EquivocationReportSystem; } @@ -340,19 +341,6 @@ mod mmr { } parameter_types! { - /// Version of the produced MMR leaf. - /// - /// The version consists of two parts; - /// - `major` (3 bits) - /// - `minor` (5 bits) - /// - /// `major` should be updated only if decoding the previous MMR Leaf format from the payload - /// is not possible (i.e. backward incompatible change). - /// `minor` should be updated if fields are added to the previous MMR Leaf, which given SCALE - /// encoding does not prevent old leafs from being decoded. - /// - /// Hence we expect `major` to be changed really rarely (think never). - /// See [`MmrLeafVersion`] type documentation for more details. pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0); } @@ -529,7 +517,6 @@ parameter_types! { pub const SignedDepositByte: Balance = deposit(0, 10) / 1024; // Each good submission will get 1 WND as reward pub SignedRewardBase: Balance = 1 * UNITS; - pub BetterUnsignedThreshold: Perbill = Perbill::from_rational(5u32, 10_000); // 1 hour session, 15 minutes unsigned phase, 4 offchain executions. pub OffchainRepeat: BlockNumber = UnsignedPhase::get() / 4; @@ -606,7 +593,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type MinerConfig = Self; type SlashHandler = (); // burn slashes type RewardHandler = (); // nothing to do upon rewards - type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; type MinerTxPriority = NposSolutionPriority; @@ -689,6 +675,7 @@ parameter_types! { pub const MaxNominators: u32 = 64; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub const MaxNominations: u32 = ::LIMIT as u32; + pub const MaxControllersInDeprecationBatch: u32 = 751; } impl pallet_staking::Config for Runtime { @@ -703,7 +690,7 @@ impl pallet_staking::Config for Runtime { type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; - type AdminOrigin = EnsureRoot; + type AdminOrigin = EitherOf, StakingAdmin>; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type MaxExposurePageSize = MaxExposurePageSize; @@ -716,6 +703,7 @@ impl pallet_staking::Config for Runtime { type NominationsQuota = pallet_staking::FixedNominationsQuota<{ MaxNominations::get() }>; type MaxUnlockingChunks = frame_support::traits::ConstU32<32>; type HistoryDepth = frame_support::traits::ConstU32<84>; + type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig; type EventListeners = (StakeTracker, NominationPools); type WeightInfo = weights::pallet_staking::WeightInfo; @@ -817,7 +805,7 @@ impl pallet_grandpa::Config for Runtime { type MaxNominators = MaxNominators; type MaxSetIdSessionEntries = MaxSetIdSessionEntries; - type KeyOwnerProof = >::Proof; + type KeyOwnerProof = sp_session::MembershipProof; type EquivocationReportSystem = pallet_grandpa::EquivocationReportSystem; @@ -965,6 +953,7 @@ impl pallet_vesting::Config for Runtime { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = weights::pallet_vesting::WeightInfo; type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; const MAX_VESTING_SCHEDULES: u32 = 28; } @@ -1165,6 +1154,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = (); + type AssignCoretime = (); } parameter_types! { @@ -1231,7 +1221,13 @@ impl parachains_paras_inherent::Config for Runtime { } impl parachains_scheduler::Config for Runtime { - type AssignmentProvider = ParaAssignmentProvider; + // If you change this, make sure the `Assignment` type of the new provider is binary compatible, + // otherwise provide a migration. + type AssignmentProvider = ParachainsAssignmentProvider; +} + +parameter_types! { + pub const BrokerId: u32 = BROKER_ID; } impl parachains_assigner_parachains::Config for Runtime {} @@ -1240,6 +1236,7 @@ impl parachains_initializer::Config for Runtime { type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; + type CoretimeOnNewSession = (); } impl paras_sudo_wrapper::Config for Runtime {} @@ -1507,7 +1504,7 @@ construct_runtime! { ParaSessionInfo: parachains_session_info::{Pallet, Storage} = 52, ParasDisputes: parachains_disputes::{Pallet, Call, Storage, Event} = 53, ParasSlashing: parachains_slashing::{Pallet, Call, Storage, ValidateUnsigned} = 54, - ParaAssignmentProvider: parachains_assigner_parachains::{Pallet, Storage} = 55, + ParachainsAssignmentProvider: parachains_assigner_parachains::{Pallet} = 55, // Parachain Onboarding Pallets. Start indices at 60 to leave room. Registrar: paras_registrar::{Pallet, Call, Storage, Event, Config} = 60, @@ -1658,7 +1655,7 @@ pub mod migrations { parachains_configuration::migration::v7::MigrateToV7, pallet_staking::migrations::v14::MigrateToV14, assigned_slots::migration::v1::MigrateToV1, - parachains_scheduler::migration::v1::MigrateToV1, + parachains_scheduler::migration::MigrateV1ToV2, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, paras_registrar::migration::MigrateToV1, @@ -1671,6 +1668,7 @@ pub mod migrations { ImOnlinePalletName, ::DbWeight, >, + parachains_configuration::migration::v11::MigrateToV11, ); } @@ -1813,7 +1811,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(9)] + #[api_version(10)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -1957,6 +1955,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::async_backing_params::() } + fn approval_voting_params() -> ApprovalVotingParams { + parachains_staging_runtime_api_impl::approval_voting_params::() + } + fn disabled_validators() -> Vec { parachains_staging_runtime_api_impl::disabled_validators::() } @@ -2321,6 +2323,21 @@ sp_api::impl_runtime_apis! { crate::Junction::Parachain(43211234).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + // Relay supports only native token, either reserve transfer it to non-system parachains, + // or teleport it to system parachain. Use the teleport case for benchmarking as it's + // slightly heavier. + + // Relay/native token can be teleported to/from AH. + let native_location = Here.into(); + let dest = crate::xcm_config::AssetHub::get(); + pallet_xcm::benchmarking::helpers::native_teleport_as_asset_transfer::( + native_location, + dest + ) + } } impl frame_system_benchmarking::Config for Runtime {} impl pallet_nomination_pools_benchmarking::Config for Runtime {} diff --git a/polkadot/runtime/westend/src/weights/frame_system.rs b/polkadot/runtime/westend/src/weights/frame_system.rs index deef0959363c6431081ed154980c99f9f9c49e56..f679be5171517affeea382f530b28e72e540be5e 100644 --- a/polkadot/runtime/westend/src/weights/frame_system.rs +++ b/polkadot/runtime/westend/src/weights/frame_system.rs @@ -144,4 +144,31 @@ impl frame_system::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index 3841579088a91d9b6c07efe1686409d1bf35b959..d8a2ae5d2da6fab158898e6cb6548f5f56aa612c 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -49,7 +49,9 @@ pub mod runtime_common_crowdloan; pub mod runtime_common_identity_migrator; pub mod runtime_common_paras_registrar; pub mod runtime_common_slots; +pub mod runtime_parachains_assigner_on_demand; pub mod runtime_parachains_configuration; +pub mod runtime_parachains_coretime; pub mod runtime_parachains_disputes; pub mod runtime_parachains_disputes_slashing; pub mod runtime_parachains_hrmp; diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 87b603621e8d418c2fa309b208959e8a1e744e56..1ecd44747ef5140b0f83f5226d8368a9231085d0 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-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_052_000 picoseconds. - Weight::from_parts(39_303_000, 0) + // Minimum execution time: 38_316_000 picoseconds. + Weight::from_parts(40_022_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_690_000 picoseconds. - Weight::from_parts(83_889_000, 0) + // Minimum execution time: 81_027_000 picoseconds. + Weight::from_parts(83_964_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: 84_409_000 picoseconds. - Weight::from_parts(87_330_000, 0) + // Minimum execution time: 85_585_000 picoseconds. + Weight::from_parts(87_256_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_770_000 picoseconds. - Weight::from_parts(40_828_632, 0) + // Minimum execution time: 39_520_000 picoseconds. + Weight::from_parts(41_551_548, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 824 - .saturating_add(Weight::from_parts(51_107, 0).saturating_mul(s.into())) + // Standard Error: 1_094 + .saturating_add(Weight::from_parts(50_426, 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_500_000 picoseconds. - Weight::from_parts(90_099_121, 0) + // Minimum execution time: 82_915_000 picoseconds. + Weight::from_parts(89_597_160, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_280 - .saturating_add(Weight::from_parts(1_273_212, 0).saturating_mul(s.into())) + // Standard Error: 3_146 + .saturating_add(Weight::from_parts(1_228_061, 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_236_000 picoseconds. - Weight::from_parts(49_518_000, 0) + // Minimum execution time: 48_070_000 picoseconds. + Weight::from_parts(49_226_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: 28_280_000 picoseconds. - Weight::from_parts(29_182_740, 0) + // Minimum execution time: 29_140_000 picoseconds. + Weight::from_parts(30_225_579, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 6_102 - .saturating_add(Weight::from_parts(6_412_107, 0).saturating_mul(k.into())) + // Standard Error: 5_394 + .saturating_add(Weight::from_parts(6_401_367, 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_846_000 picoseconds. - Weight::from_parts(58_029_857, 0) + // Minimum execution time: 59_287_000 picoseconds. + Weight::from_parts(58_285_052, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 15_967 - .saturating_add(Weight::from_parts(3_898_764, 0).saturating_mul(n.into())) + // Standard Error: 14_556 + .saturating_add(Weight::from_parts(3_863_008, 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_223_000 picoseconds. - Weight::from_parts(52_310_000, 0) + // Minimum execution time: 51_035_000 picoseconds. + Weight::from_parts(52_163_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_762_000 picoseconds. - Weight::from_parts(16_381_000, 0) + // Minimum execution time: 15_809_000 picoseconds. + Weight::from_parts(16_451_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_904_000 picoseconds. - Weight::from_parts(22_373_000, 0) + // Minimum execution time: 21_695_000 picoseconds. + Weight::from_parts(22_351_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_869_000 picoseconds. - Weight::from_parts(19_422_000, 0) + // Minimum execution time: 18_548_000 picoseconds. + Weight::from_parts(19_205_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_205_000 picoseconds. - Weight::from_parts(2_320_000, 0) + // Minimum execution time: 2_193_000 picoseconds. + Weight::from_parts(2_408_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_179_000 picoseconds. - Weight::from_parts(7_843_000, 0) + // Minimum execution time: 7_475_000 picoseconds. + Weight::from_parts(7_874_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_206_000 picoseconds. - Weight::from_parts(7_829_000, 0) + // Minimum execution time: 7_393_000 picoseconds. + Weight::from_parts(7_643_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_414_000 picoseconds. - Weight::from_parts(7_770_000, 0) + // Minimum execution time: 7_474_000 picoseconds. + Weight::from_parts(7_814_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -393,13 +393,33 @@ 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_645_840, 0) + // Minimum execution time: 2_358_000 picoseconds. + Weight::from_parts(2_589_423, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 37 - .saturating_add(Weight::from_parts(10_207, 0).saturating_mul(v.into())) + // Standard Error: 81 + .saturating_add(Weight::from_parts(13_612, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Staking::Ledger` (r:751 w:1502) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:751 w:0) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:0 w:751) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// The range of component `i` is `[0, 751]`. + fn deprecate_controller_batch(i: u32, ) -> Weight { + // 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) + .saturating_add(Weight::from_parts(0, 990)) + // Standard Error: 19_129 + .saturating_add(Weight::from_parts(13_231_580, 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())) + } /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -433,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: 81_032_000 picoseconds. - Weight::from_parts(88_297_596, 0) + // Minimum execution time: 80_290_000 picoseconds. + Weight::from_parts(87_901_664, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_070 - .saturating_add(Weight::from_parts(1_207_207, 0).saturating_mul(s.into())) + // Standard Error: 2_960 + .saturating_add(Weight::from_parts(1_195_050, 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()))) @@ -450,11 +470,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `66639` // Estimated: `70104` - // Minimum execution time: 131_456_000 picoseconds. - Weight::from_parts(935_254_517, 0) + // Minimum execution time: 132_682_000 picoseconds. + Weight::from_parts(932_504_297, 0) .saturating_add(Weight::from_parts(0, 70104)) - // Standard Error: 57_806 - .saturating_add(Weight::from_parts(4_823_189, 0).saturating_mul(s.into())) + // Standard Error: 57_593 + .saturating_add(Weight::from_parts(4_829_705, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -490,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 ±0)` - // Minimum execution time: 129_233_000 picoseconds. - Weight::from_parts(165_096_042, 0) + // Estimated: `10779 + n * (3774 ±3)` + // Minimum execution time: 129_091_000 picoseconds. + Weight::from_parts(166_186_167, 0) .saturating_add(Weight::from_parts(0, 10779)) - // Standard Error: 29_598 - .saturating_add(Weight::from_parts(40_716_425, 0).saturating_mul(n.into())) + // Standard Error: 36_242 + .saturating_add(Weight::from_parts(40_467_481, 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)) @@ -519,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_223_000 picoseconds. - Weight::from_parts(80_026_259, 0) + // Minimum execution time: 77_461_000 picoseconds. + Weight::from_parts(80_118_021, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 4_493 - .saturating_add(Weight::from_parts(52_909, 0).saturating_mul(l.into())) + // Standard Error: 4_343 + .saturating_add(Weight::from_parts(59_113, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -558,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_871_000 picoseconds. - Weight::from_parts(92_313_331, 0) + // Minimum execution time: 89_366_000 picoseconds. + Weight::from_parts(91_964_557, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_321 - .saturating_add(Weight::from_parts(1_243_347, 0).saturating_mul(s.into())) + // Standard Error: 2_799 + .saturating_add(Weight::from_parts(1_206_123, 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()))) @@ -607,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 ±0) + v * (3566 ±0)` - // Minimum execution time: 518_819_000 picoseconds. - Weight::from_parts(522_108_000, 0) + // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` + // Minimum execution time: 520_430_000 picoseconds. + Weight::from_parts(527_125_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 1_987_848 - .saturating_add(Weight::from_parts(64_855_377, 0).saturating_mul(v.into())) - // Standard Error: 198_078 - .saturating_add(Weight::from_parts(18_343_485, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -645,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: 34_976_277_000 picoseconds. - Weight::from_parts(35_245_501_000, 0) + // Minimum execution time: 33_917_323_000 picoseconds. + Weight::from_parts(34_173_565_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 386_461 - .saturating_add(Weight::from_parts(5_145_210, 0).saturating_mul(v.into())) - // Standard Error: 386_461 - .saturating_add(Weight::from_parts(3_762_623, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -668,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_577_411_000 picoseconds. - Weight::from_parts(86_073_486, 0) + // Minimum execution time: 2_447_197_000 picoseconds. + Weight::from_parts(13_003_614, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 8_363 - .saturating_add(Weight::from_parts(5_074_828, 0).saturating_mul(v.into())) + // Standard Error: 9_738 + .saturating_add(Weight::from_parts(4_953_442, 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())) @@ -693,8 +713,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_539_000 picoseconds. - Weight::from_parts(3_903_000, 0) + // Minimum execution time: 3_714_000 picoseconds. + Weight::from_parts(3_956_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -714,11 +734,13 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_244_000 picoseconds. - Weight::from_parts(3_450_000, 0) + // Minimum execution time: 3_361_000 picoseconds. + Weight::from_parts(3_632_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -741,12 +763,12 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1704` + // Measured: `1870` // Estimated: `6248` - // Minimum execution time: 62_606_000 picoseconds. - Weight::from_parts(64_678_000, 0) + // Minimum execution time: 65_329_000 picoseconds. + Weight::from_parts(67_247_000, 0) .saturating_add(Weight::from_parts(0, 6248)) - .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Staking::MinCommission` (r:1 w:0) @@ -757,8 +779,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `658` // Estimated: `3510` - // Minimum execution time: 11_490_000 picoseconds. - Weight::from_parts(11_867_000, 0) + // Minimum execution time: 11_760_000 picoseconds. + Weight::from_parts(12_095_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -769,8 +791,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_125_000 picoseconds. - Weight::from_parts(2_337_000, 0) + // Minimum execution time: 2_256_000 picoseconds. + Weight::from_parts(2_378_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index cca4bdbd91e309f29d5d09c730c1a95c4e34138d..493acd0f9e7bdfbfd1e716b7e82a474643f1050b 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -50,6 +50,10 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + fn transfer_assets() -> Weight { + // TODO: run benchmarks + Weight::zero() + } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs new file mode 100644 index 0000000000000000000000000000000000000000..ac0f05301b486dbdbb8c0ca004e195ab47171ff3 --- /dev/null +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_assigner_on_demand.rs @@ -0,0 +1,91 @@ +// 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 `runtime_parachains::assigner_on_demand` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-08-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-fljshgub-project-163-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 +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json +// --pallet=runtime_parachains::assigner_on_demand +// --chain=rococo-dev +// --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 `runtime_parachains::assigner_on_demand`. +pub struct WeightInfo(PhantomData); +impl runtime_parachains::assigner_on_demand::WeightInfo for WeightInfo { + /// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0) + /// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Paras::ParaLifecycles` (r:1 w:0) + /// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1) + /// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `s` is `[1, 9999]`. + fn place_order_keep_alive(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `297 + s * (4 ±0)` + // Estimated: `3762 + s * (4 ±0)` + // Minimum execution time: 33_522_000 picoseconds. + Weight::from_parts(35_436_835, 0) + .saturating_add(Weight::from_parts(0, 3762)) + // Standard Error: 129 + .saturating_add(Weight::from_parts(14_041, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) + } + /// Storage: `OnDemandAssignmentProvider::SpotTraffic` (r:1 w:0) + /// Proof: `OnDemandAssignmentProvider::SpotTraffic` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Paras::ParaLifecycles` (r:1 w:0) + /// Proof: `Paras::ParaLifecycles` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `OnDemandAssignmentProvider::OnDemandQueue` (r:1 w:1) + /// Proof: `OnDemandAssignmentProvider::OnDemandQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `s` is `[1, 9999]`. + fn place_order_allow_death(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `297 + s * (4 ±0)` + // Estimated: `3762 + s * (4 ±0)` + // Minimum execution time: 33_488_000 picoseconds. + Weight::from_parts(34_848_934, 0) + .saturating_add(Weight::from_parts(0, 3762)) + // Standard Error: 143 + .saturating_add(Weight::from_parts(14_215, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) + } +} diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..d9f2d45207b923e3afe661a6021629cb8441970e --- /dev/null +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs @@ -0,0 +1,73 @@ +// 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 `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: `[]` +//! 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 + +// Executed Command: +// target/production/polkadot +// 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=runtime_common::coretime +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/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; + +use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo}; + +/// Weight functions for `runtime_common::coretime`. +pub struct WeightInfo(PhantomData); +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + fn request_core_count() -> Weight { + ::WeightInfo::set_config_with_u32() + } + /// 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())) + .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 9ab6470f6dae97cc4c436a0862c73ddafe6a8cb3..e4b4f50f663b6950de9e83efecadbe9ac3bbdb6c 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -21,10 +21,10 @@ use super::{ GeneralAdmin, ParaId, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, StakingAdmin, TransactionByteFee, Treasury, WeightToFee, XcmPallet, }; - +use crate::governance::pallet_custom_origins::Treasurer; use frame_support::{ match_types, parameter_types, - traits::{Everything, Nothing}, + traits::{Equals, Everything, Nothing}, }; use frame_system::EnsureRoot; use pallet_xcm::XcmPassthrough; @@ -34,21 +34,26 @@ use runtime_common::{ }; use sp_core::ConstU32; use westend_runtime_constants::{ - currency::CENTS, system_parachain::*, xcm::body::FELLOWSHIP_ADMIN_INDEX, + currency::CENTS, + system_parachain::*, + 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, CurrencyAdapter as XcmCurrencyAdapter, DescribeBodyTerminal, - DescribeFamily, HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, HashedDescription, IsConcrete, + MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::XcmExecutor; parameter_types! { + pub const RootLocation: MultiLocation = MultiLocation::here(); pub const TokenLocation: MultiLocation = Here.into_location(); pub const ThisNetwork: NetworkId = Westend; pub const UniversalLocation: InteriorMultiLocation = X1(GlobalConsensus(ThisNetwork::get())); @@ -70,6 +75,7 @@ pub type LocationConverter = ( HashedDescription>, ); +#[allow(deprecated)] pub type LocalAssetTransactor = XcmCurrencyAdapter< // Use this currency: Balances, @@ -111,10 +117,12 @@ parameter_types! { pub const AssetHub: MultiLocation = Parachain(ASSET_HUB_ID).into_location(); pub const Collectives: MultiLocation = Parachain(COLLECTIVES_ID).into_location(); pub const BridgeHub: MultiLocation = Parachain(BRIDGE_HUB_ID).into_location(); + pub const People: MultiLocation = Parachain(PEOPLE_ID).into_location(); pub const Wnd: MultiAssetFilter = Wild(AllOf { fun: WildFungible, id: Concrete(TokenLocation::get()) }); pub const WndForAssetHub: (MultiAssetFilter, MultiLocation) = (Wnd::get(), AssetHub::get()); pub const WndForCollectives: (MultiAssetFilter, MultiLocation) = (Wnd::get(), Collectives::get()); pub const WndForBridgeHub: (MultiAssetFilter, MultiLocation) = (Wnd::get(), BridgeHub::get()); + pub const WndForPeople: (MultiAssetFilter, MultiLocation) = (Wnd::get(), People::get()); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; } @@ -123,6 +131,7 @@ pub type TrustedTeleporters = ( xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, ); match_types! { @@ -133,6 +142,9 @@ match_types! { MultiLocation { parents: 0, interior: X1(Parachain(COLLECTIVES_ID)) } | MultiLocation { parents: 0, interior: X2(Parachain(COLLECTIVES_ID), Plurality { id: BodyId::Technical, .. }) } }; + pub type LocalPlurality: impl Contains = { + MultiLocation { parents: 0, interior: X1(Plurality { .. }) } + }; } /// The barriers one of which must be passed for an XCM message to be executed. @@ -155,6 +167,10 @@ pub type Barrier = TrailingSetTopicAsId<( >, )>; +/// Locations that will not be charged fees in the executor, neither for execution nor delivery. +/// We only waive fees for system functions, which these locations represent. +pub type WaivedLocations = (SystemParachains, Equals, LocalPlurality); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -181,7 +197,7 @@ impl xcm_executor::Config for XcmConfig { type PalletInstancesInfo = AllPalletsWithSystem; type MaxAssetsIntoHolding = MaxAssetsIntoHolding; type FeeManager = XcmFeeManagerFromComponents< - SystemParachains, + WaivedLocations, XcmFeeToAccount, >; type MessageExporter = (); @@ -198,6 +214,8 @@ parameter_types! { pub const StakingAdminBodyId: BodyId = BodyId::Defense; // FellowshipAdmin pluralistic body. pub const FellowshipAdminBodyId: BodyId = BodyId::Index(FELLOWSHIP_ADMIN_INDEX); + // `Treasurer` pluralistic body. + pub const TreasurerBodyId: BodyId = BodyId::Index(TREASURER_INDEX); } /// Type to convert the `GeneralAdmin` origin to a Plurality `MultiLocation` value. @@ -220,6 +238,9 @@ pub type StakingAdminToPlurality = pub type FellowshipAdminToPlurality = OriginToPluralityVoice; +/// Type to convert the `Treasurer` origin to a Plurality `MultiLocation` value. +pub type TreasurerToPlurality = OriginToPluralityVoice; + /// Type to convert a pallet `Origin` type value into a `MultiLocation` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( @@ -229,6 +250,8 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // FellowshipAdmin origin to be used in XCM as a corresponding Plurality `MultiLocation` value. FellowshipAdminToPlurality, + // `Treasurer` origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index d2518591d26c8062b87f4b51ab20a5400012c544..9a313882da71ca2c4dc9a7c3f171bc7bab1f05bf 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true description = "Stores messages other authorities issue about candidates in Polkadot." +[lints] +workspace = true + [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } sp-core = { path = "../../substrate/primitives/core" } diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index 1cd7b057c87df9a1f71923a9a6e6e2ac17f5b21d..8ec828ba0727e6706ecf271b011b82bd06725408 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -6,8 +6,11 @@ edition.workspace = true license.workspace = true description = "CLI to generate voter bags for Polkadot runtimes" +[lints] +workspace = true + [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", 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 7f0c49f0c2678ac9a02b5aab6b999b5596efe26f..8ee277e64b5d860601dd7e7e13e05770eee35ee6 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] westend-runtime = { path = "../../../runtime/westend" } westend-runtime-constants = { path = "../../../runtime/westend/constants" } @@ -15,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.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } log = "0.4.17" tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 8fa0d1289508a31b6126c7e45a89e8fe17726619..89e3728a36153e494a61261987b527b61446f52c 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -6,15 +6,20 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] -bounded-collections = { version = "0.1.8", default-features = false, features = ["serde"] } +array-bytes = "6.1" +bounded-collections = { version = "0.1.9", 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 } 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.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +schemars = { version = "0.8.13", default-features = true, optional = true } xcm-procedural = { path = "procedural" } environmental = { version = "1.1.4", default-features = false } @@ -35,3 +40,4 @@ std = [ "serde/std", "sp-weights/std", ] +json-schema = ["bounded-collections/json-schema", "dep:schemars", "sp-weights/json-schema"] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index 5438279c6731ba3823e8ad1156a5b12fc8949b41..d9cc7e34c06c22e5a03a994466622a38449e73a7 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -6,6 +6,9 @@ license.workspace = true version = "1.0.0" description = "Benchmarks for the XCM pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index 4d566fd585db0fef9bb3c4507e7c795f881c4ad0..43892c31c7cd8745e2cf178a6ce7a1c74ebc8ec0 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -103,6 +103,7 @@ impl xcm_executor::traits::MatchesFungible for MatchAnyFungible { } // Use balances as the asset transactor. +#[allow(deprecated)] pub type AssetTransactor = xcm_builder::CurrencyAdapter< Balances, MatchAnyFungible, diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index f1c48ba9b83040af5d14086924b35e1acb7acd1e..50a7fe45e23122b6f5d68d5f55094c0d2ce2a9d2 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -175,7 +175,7 @@ benchmarks! { descend_origin { let mut executor = new_executor::(Default::default()); let who = X2(OnlyChild, OnlyChild); - let instruction = Instruction::DescendOrigin(who.clone()); + let instruction = Instruction::DescendOrigin(who); let xcm = Xcm(vec![instruction]); } : { executor.bench_process(xcm)?; @@ -242,7 +242,7 @@ benchmarks! { &origin, assets.clone().into(), &XcmContext { - origin: Some(origin.clone()), + origin: Some(origin), message_id: [0; 32], topic: None, }, @@ -279,7 +279,7 @@ benchmarks! { let origin = T::subscribe_origin()?; let query_id = Default::default(); let max_response_weight = Default::default(); - let mut executor = new_executor::(origin.clone()); + let mut executor = new_executor::(origin); let instruction = Instruction::SubscribeVersion { query_id, max_response_weight }; let xcm = Xcm(vec![instruction]); } : { @@ -299,14 +299,14 @@ benchmarks! { query_id, max_response_weight, &XcmContext { - origin: Some(origin.clone()), + origin: Some(origin), message_id: [0; 32], topic: None, }, ).map_err(|_| "Could not start subscription")?; assert!(::SubscriptionService::is_subscribed(&origin)); - let mut executor = new_executor::(origin.clone()); + let mut executor = new_executor::(origin); let instruction = Instruction::UnsubscribeVersion; let xcm = Xcm(vec![instruction]); } : { @@ -538,7 +538,7 @@ benchmarks! { let mut executor = new_executor::(origin); - let instruction = Instruction::UniversalOrigin(alias.clone()); + let instruction = Instruction::UniversalOrigin(alias); let xcm = Xcm(vec![instruction]); }: { executor.bench_process(xcm)?; @@ -632,13 +632,13 @@ benchmarks! { let (unlocker, owner, asset) = T::unlockable_asset()?; - let mut executor = new_executor::(unlocker.clone()); + let mut executor = new_executor::(unlocker); // We first place the asset in lock first... ::AssetLocker::prepare_lock( unlocker, asset.clone(), - owner.clone(), + owner, ) .map_err(|_| BenchmarkError::Skip)? .enact() @@ -658,13 +658,13 @@ benchmarks! { let (unlocker, owner, asset) = T::unlockable_asset()?; - let mut executor = new_executor::(unlocker.clone()); + let mut executor = new_executor::(unlocker); // We first place the asset in lock first... ::AssetLocker::prepare_lock( unlocker, asset.clone(), - owner.clone(), + owner, ) .map_err(|_| BenchmarkError::Skip)? .enact() @@ -686,9 +686,9 @@ benchmarks! { // We first place the asset in lock first... ::AssetLocker::prepare_lock( - locker.clone(), + locker, asset.clone(), - owner.clone(), + owner, ) .map_err(|_| BenchmarkError::Skip)? .enact() @@ -739,7 +739,7 @@ benchmarks! { let mut executor = new_executor::(origin); - let instruction = Instruction::AliasOrigin(target.clone()); + let instruction = Instruction::AliasOrigin(target); let xcm = Xcm(vec![instruction]); }: { executor.bench_process(xcm)?; diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index 645ac8f99418b531969bea19b893da7bcd69b5d6..b3e57d1d091ffdc137141d1fa55128e254b1a5bf 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -6,11 +6,14 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] bounded-collections = { version = "0.1.8", 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.193", optional = true, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } log = { version = "0.4.17", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index f02895123998978d71c18837bc3190ba29755d56..28a198f40a052bc06ca161cbf05c91cecba194bd 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -44,7 +44,7 @@ pub trait Config: crate::Config { /// /// Implementation should also make sure `dest` is reachable/connected. /// - /// If `None`, the benchmarks that depend on this will be skipped. + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. fn teleportable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { None } @@ -54,10 +54,27 @@ pub trait Config: crate::Config { /// /// Implementation should also make sure `dest` is reachable/connected. /// - /// If `None`, the benchmarks that depend on this will be skipped. + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. fn reserve_transferable_asset_and_dest() -> Option<(MultiAsset, MultiLocation)> { None } + + /// Sets up a complex transfer (usually consisting of a teleport and reserve-based transfer), so + /// that runtime can properly benchmark `transfer_assets()` extrinsic. Should return a tuple + /// `(MultiAsset, u32, MultiLocation, dyn FnOnce())` representing the assets to transfer, the + /// `u32` index of the asset to be used for fees, the destination chain for the transfer, and a + /// `verify()` closure to verify the intended transfer side-effects. + /// + /// Implementation should make sure the provided assets can be transacted by the runtime, there + /// are enough balances in the involved accounts, and that `dest` is reachable/connected. + /// + /// Used only in benchmarks. + /// + /// If `None`, the benchmarks that depend on this will default to `Weight::MAX`. + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + None + } } benchmarks! { @@ -158,6 +175,23 @@ benchmarks! { assert!(pallet_balances::Pallet::::free_balance(&caller) <= balance - transferred_amount); } + transfer_assets { + let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or( + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), + )?; + let caller: T::AccountId = whitelisted_caller(); + let send_origin = RawOrigin::Signed(caller.clone()); + let recipient = [0u8; 32]; + let versioned_dest: VersionedMultiLocation = destination.into(); + let versioned_beneficiary: VersionedMultiLocation = + AccountId32 { network: None, id: recipient.into() }.into(); + let versioned_assets: VersionedMultiAssets = assets.into(); + }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited) + verify { + // run provided verification function + verify(); + } + execute { let execute_origin = T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; @@ -302,3 +336,36 @@ benchmarks! { crate::mock::Test ); } + +pub mod helpers { + use super::*; + pub fn native_teleport_as_asset_transfer( + native_asset_location: MultiLocation, + destination: MultiLocation, + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> + where + T: Config + pallet_balances::Config, + u128: From<::Balance>, + { + // Relay/native token can be teleported to/from AH. + let amount = T::ExistentialDeposit::get() * 100u32.into(); + let assets: MultiAssets = + MultiAsset { fun: Fungible(amount.into()), id: Concrete(native_asset_location) }.into(); + let fee_index = 0u32; + + // Give some multiple of transferred amount + let balance = amount * 10u32.into(); + let who = whitelisted_caller(); + let _ = + as frame_support::traits::Currency<_>>::make_free_balance_be(&who, balance); + // verify initial balance + assert_eq!(pallet_balances::Pallet::::free_balance(&who), balance); + + // verify transferred successfully + let verify = Box::new(move || { + // verify balance after transfer, decreased by transferred amount (and delivery fees) + assert!(pallet_balances::Pallet::::free_balance(&who) <= balance - amount); + }); + Some((assets, fee_index, destination, verify)) + } +} diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 74a24b132da7ec6d2bab3db3b6ae17a022250364..2848527f1502f9017db2e2d69be5ab523bb31e0d 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -66,6 +66,7 @@ pub trait WeightInfo { fn send() -> Weight; fn teleport_assets() -> Weight; fn reserve_transfer_assets() -> Weight; + fn transfer_assets() -> Weight; fn execute() -> Weight; fn force_xcm_version() -> Weight; fn force_default_xcm_version() -> Weight; @@ -98,6 +99,10 @@ impl WeightInfo for TestWeightInfo { Weight::from_parts(100_000_000, 0) } + fn transfer_assets() -> Weight { + Weight::from_parts(100_000_000, 0) + } + fn execute() -> Weight { Weight::from_parts(100_000_000, 0) } @@ -905,8 +910,8 @@ pub mod pallet { /// from relay to parachain. /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. - /// - `assets`: The assets to be withdrawn. The first item should be the currency used to to - /// pay the fee on the `dest` side. May not be empty. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` chain. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. #[pallet::call_index(1)] @@ -937,8 +942,19 @@ pub mod pallet { Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited) } - /// Transfer some assets from the local chain to the sovereign account of a destination - /// chain and forward a notification XCM. + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve. + /// + /// `assets` must have same reserve location and may not be teleportable to `dest`. + /// - `assets` have local reserve: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `assets` have destination reserve: burn local assets and forward a notification to + /// `dest` chain to withdraw the reserve assets from this chain's sovereign account and + /// deposit them to `beneficiary`. + /// - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move + /// reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` + /// to mint and deposit reserve-based assets to `beneficiary`. /// /// **This function is deprecated: Use `limited_reserve_transfer_assets` instead.** /// @@ -953,7 +969,7 @@ pub mod pallet { /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the - /// fee on the `dest` side. + /// fee on the `dest` (and possibly reserve) chains. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. #[pallet::call_index(2)] @@ -1105,8 +1121,19 @@ pub mod pallet { }) } - /// Transfer some assets from the local chain to the sovereign account of a destination - /// chain and forward a notification XCM. + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve. + /// + /// `assets` must have same reserve location and may not be teleportable to `dest`. + /// - `assets` have local reserve: transfer assets to sovereign account of destination + /// chain and forward a notification XCM to `dest` to mint and deposit reserve-based + /// assets to `beneficiary`. + /// - `assets` have destination reserve: burn local assets and forward a notification to + /// `dest` chain to withdraw the reserve assets from this chain's sovereign account and + /// deposit them to `beneficiary`. + /// - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move + /// reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` + /// to mint and deposit reserve-based assets to `beneficiary`. /// /// Fee payment on the destination side is made from the asset in the `assets` vector of /// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight @@ -1120,7 +1147,7 @@ pub mod pallet { /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the - /// fee on the `dest` side. + /// fee on the `dest` (and possibly reserve) chains. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. @@ -1173,8 +1200,8 @@ pub mod pallet { /// from relay to parachain. /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will /// generally be an `AccountId32` value. - /// - `assets`: The assets to be withdrawn. The first item should be the currency used to to - /// pay the fee on the `dest` side. May not be empty. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` chain. /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay /// fees. /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. @@ -1225,12 +1252,162 @@ pub mod pallet { XcmExecutionSuspended::::set(suspended); Ok(()) } + + /// Transfer some assets from the local chain to the destination chain through their local, + /// destination or remote reserve, or through teleports. + /// + /// Fee payment on the destination side is made from the asset in the `assets` vector of + /// index `fee_asset_item` (hence referred to as `fees`), up to enough to pay for + /// `weight_limit` of weight. If more weight is needed than `weight_limit`, then the + /// operation will fail and the assets sent may be at risk. + /// + /// `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable + /// to `dest`, no limitations imposed on `fees`. + /// - for local reserve: transfer assets to sovereign account of destination chain and + /// forward a notification XCM to `dest` to mint and deposit reserve-based assets to + /// `beneficiary`. + /// - for destination reserve: burn local assets and forward a notification to `dest` chain + /// to withdraw the reserve assets from this chain's sovereign account and deposit them + /// to `beneficiary`. + /// - for remote reserve: burn local assets, forward XCM to reserve chain to move reserves + /// from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint + /// and deposit reserve-based assets to `beneficiary`. + /// - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport + /// assets and deposit them to `beneficiary`. + /// + /// - `origin`: Must be capable of withdrawing the `assets` and executing XCM. + /// - `dest`: Destination context for the assets. Will typically be `X2(Parent, + /// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send + /// from relay to parachain. + /// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will + /// generally be an `AccountId32` value. + /// - `assets`: The assets to be withdrawn. This should include the assets used to pay the + /// fee on the `dest` (and possibly reserve) chains. + /// - `fee_asset_item`: The index into `assets` of the item which should be used to pay + /// fees. + /// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase. + #[pallet::call_index(11)] + #[pallet::weight({ + let maybe_assets: Result = (*assets.clone()).try_into(); + let maybe_dest: Result = (*dest.clone()).try_into(); + match (maybe_assets, maybe_dest) { + (Ok(assets), Ok(dest)) => { + use sp_std::vec; + // heaviest version of locally executed XCM program: equivalent in weight to withdrawing fees, + // burning them, transferring rest of assets to SA, reanchoring them, extending XCM program, + // and sending onward XCM + let mut message = Xcm(vec![ + SetFeesMode { jit_withdraw: true }, + WithdrawAsset(assets.clone()), + BurnAsset(assets.clone()), + TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) } + ]); + T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::transfer_assets().saturating_add(w)) + } + _ => Weight::MAX, + } + })] + pub fn transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, + ) -> DispatchResult { + let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let dest = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + let beneficiary: MultiLocation = + (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; + let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::transfer_assets", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", + origin, dest, beneficiary, assets, fee_asset_item, weight_limit, + ); + + ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); + let mut assets = assets.into_inner(); + let fee_asset_item = fee_asset_item as usize; + let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); + // Find transfer types for fee and non-fee assets. + let (fees_transfer_type, assets_transfer_type) = + Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?; + + // local and remote XCM programs to potentially handle fees separately + let fees = if fees_transfer_type == assets_transfer_type { + // no need for custom fees instructions, fees are batched with assets + FeesHandling::Batched { fees } + } else { + // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered + // by branch above). The reason for this is that we'd need to send XCMs to separate + // chains with no guarantee of delivery order on final destination; therefore we + // cannot guarantee to have fees in place on final destination chain to pay for + // assets transfer. + ensure!( + !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), + Error::::InvalidAssetUnsupportedReserve + ); + let weight_limit = weight_limit.clone(); + // remove `fees` from `assets` and build separate fees transfer instructions to be + // added to assets transfers XCM programs + let fees = assets.remove(fee_asset_item); + let (local_xcm, remote_xcm) = match fees_transfer_type { + TransferType::LocalReserve => + Self::local_reserve_fees_instructions(origin, dest, fees, weight_limit)?, + TransferType::DestinationReserve => + Self::destination_reserve_fees_instructions( + origin, + dest, + fees, + weight_limit, + )?, + TransferType::Teleport => + Self::teleport_fees_instructions(origin, dest, fees, weight_limit)?, + TransferType::RemoteReserve(_) => + return Err(Error::::InvalidAssetUnsupportedReserve.into()), + }; + FeesHandling::Separate { local_xcm, remote_xcm } + }; + + Self::build_and_execute_xcm_transfer_type( + origin, + dest, + beneficiary, + assets, + assets_transfer_type, + fees, + weight_limit, + ) + } } } /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic. const MAX_ASSETS_FOR_TRANSFER: usize = 2; +/// Specify how assets used for fees are handled during asset transfers. +#[derive(Clone, PartialEq)] +enum FeesHandling { + /// `fees` asset can be batch-transferred with rest of assets using same XCM instructions. + Batched { fees: MultiAsset }, + /// fees cannot be batched, they are handled separately using XCM programs here. + Separate { local_xcm: Xcm<::RuntimeCall>, remote_xcm: Xcm<()> }, +} + +impl sp_std::fmt::Debug for FeesHandling { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees), + Self::Separate { local_xcm, remote_xcm } => write!( + f, + "FeesHandling::Separate(local: {:?}, remote: {:?})", + local_xcm, remote_xcm + ), + } + } +} + impl QueryHandler for Pallet { type QueryId = u64; type BlockNumber = BlockNumberFor; @@ -1292,31 +1469,45 @@ impl QueryHandler for Pallet { } impl Pallet { - /// Validate `assets` to be reserve-transferred and return their reserve location. - fn validate_assets_and_find_reserve( + /// Find `TransferType`s for `assets` and fee identified through `fee_asset_item`, when + /// transferring to `dest`. + /// + /// Validate `assets` to all have same `TransferType`. + fn find_fee_and_assets_transfer_types( assets: &[MultiAsset], + fee_asset_item: usize, dest: &MultiLocation, - ) -> Result> { - let mut reserve = None; - for asset in assets.iter() { + ) -> Result<(TransferType, TransferType), Error> { + let mut fees_transfer_type = None; + let mut assets_transfer_type = None; + for (idx, asset) in assets.iter().enumerate() { if let Fungible(x) = asset.fun { // If fungible asset, ensure non-zero amount. ensure!(!x.is_zero(), Error::::Empty); } let transfer_type = T::XcmExecutor::determine_for(&asset, dest).map_err(Error::::from)?; - // Ensure asset is not teleportable to `dest`. - ensure!(transfer_type != TransferType::Teleport, Error::::Filtered); - if let Some(reserve) = reserve.as_ref() { - // Ensure transfer for multiple assets uses same reserve location (only fee may have - // different reserve location) - ensure!(reserve == &transfer_type, Error::::TooManyReserves); + if idx == fee_asset_item { + fees_transfer_type = Some(transfer_type); } else { - // asset reserve identified - reserve = Some(transfer_type); + if let Some(existing) = assets_transfer_type.as_ref() { + // Ensure transfer for multiple assets uses same transfer type (only fee may + // have different transfer type/path) + ensure!(existing == &transfer_type, Error::::TooManyReserves); + } else { + // asset reserve identified + assets_transfer_type = Some(transfer_type); + } } } - reserve.ok_or(Error::::Empty) + // single asset also marked as fee item + if assets.len() == 1 { + assets_transfer_type = fees_transfer_type + } + Ok(( + fees_transfer_type.ok_or(Error::::Empty)?, + assets_transfer_type.ok_or(Error::::Empty)?, + )) } fn do_reserve_transfer_assets( @@ -1332,7 +1523,7 @@ impl Pallet { let beneficiary: MultiLocation = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; - log::trace!( + log::debug!( target: "xcm::pallet_xcm::do_reserve_transfer_assets", "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}", origin_location, dest, beneficiary, assets, fee_asset_item, @@ -1341,64 +1532,26 @@ impl Pallet { ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); let value = (origin_location, assets.into_inner()); ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); - let (origin_location, mut assets) = value; + let (origin, assets) = value; - if fee_asset_item as usize >= assets.len() { - return Err(Error::::Empty.into()) - } - let fees = assets.swap_remove(fee_asset_item as usize); - let fees_transfer_type = - T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::::from)?; - let assets_transfer_type = if assets.is_empty() { - // Single asset to transfer (one used for fees where transfer type is determined above). - ensure!(fees_transfer_type != TransferType::Teleport, Error::::Filtered); - fees_transfer_type - } else { - // Find reserve for non-fee assets. - Self::validate_assets_and_find_reserve(&assets, &dest)? - }; + let fee_asset_item = fee_asset_item as usize; + let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); - // local and remote XCM programs to potentially handle fees separately - let separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>; - if fees_transfer_type == assets_transfer_type { - // Same reserve location (fees not teleportable), we can batch together fees and assets - // in same reserve-based-transfer. - assets.push(fees.clone()); - // no need for custom fees instructions, fees are batched with assets - separate_fees_instructions = None; - } else { - // Disallow _remote reserves_ unless assets & fees have same remote reserve (covered by - // branch above). The reason for this is that we'd need to send XCMs to separate chains - // with no guarantee of delivery order on final destination; therefore we cannot - // guarantee to have fees in place on final destination chain to pay for assets - // transfer. - ensure!( - !matches!(assets_transfer_type, TransferType::RemoteReserve(_)), - Error::::InvalidAssetUnsupportedReserve - ); - let fees = fees.clone(); - let weight_limit = weight_limit.clone(); - // build fees transfer instructions to be added to assets transfers XCM programs - separate_fees_instructions = Some(match fees_transfer_type { - TransferType::LocalReserve => - Self::local_reserve_fees_instructions(dest, fees, weight_limit)?, - TransferType::DestinationReserve => - Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?, - TransferType::Teleport => - Self::teleport_fees_instructions(origin_location, dest, fees, weight_limit)?, - TransferType::RemoteReserve(_) => - return Err(Error::::InvalidAssetUnsupportedReserve.into()), - }); - }; + // Find transfer types for fee and non-fee assets. + let (fees_transfer_type, assets_transfer_type) = + Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?; + // Ensure assets (and fees according to check below) are not teleportable to `dest`. + ensure!(assets_transfer_type != TransferType::Teleport, Error::::Filtered); + // Ensure all assets (including fees) have same reserve location. + ensure!(assets_transfer_type == fees_transfer_type, Error::::TooManyReserves); Self::build_and_execute_xcm_transfer_type( - origin_location, + origin, dest, beneficiary, assets, assets_transfer_type, - fees, - separate_fees_instructions, + FeesHandling::Batched { fees }, weight_limit, ) } @@ -1416,6 +1569,11 @@ impl Pallet { let beneficiary: MultiLocation = (*beneficiary).try_into().map_err(|()| Error::::BadVersion)?; let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::::BadVersion)?; + log::debug!( + target: "xcm::pallet_xcm::do_teleport_assets", + "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}", + origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit, + ); ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::::TooManyAssets); let value = (origin_location, assets.into_inner()); @@ -1424,7 +1582,7 @@ impl Pallet { for asset in assets.iter() { let transfer_type = T::XcmExecutor::determine_for(asset, &dest).map_err(Error::::from)?; - ensure!(matches!(transfer_type, TransferType::Teleport), Error::::Filtered); + ensure!(transfer_type == TransferType::Teleport, Error::::Filtered); } let fees = assets.get(fee_asset_item as usize).ok_or(Error::::Empty)?.clone(); @@ -1434,8 +1592,7 @@ impl Pallet { beneficiary, assets, TransferType::Teleport, - fees, - None, + FeesHandling::Batched { fees }, weight_limit, ) } @@ -1446,54 +1603,65 @@ impl Pallet { beneficiary: MultiLocation, assets: Vec, transfer_type: TransferType, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling, weight_limit: WeightLimit, ) -> DispatchResult { - log::trace!( + log::debug!( target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type", "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \ - fees {:?}, fees_xcm: {:?}, weight_limit: {:?}", - origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit, + fees_handling {:?}, weight_limit: {:?}", + origin, dest, beneficiary, assets, transfer_type, fees, weight_limit, ); let (mut local_xcm, remote_xcm) = match transfer_type { TransferType::LocalReserve => { let (local, remote) = Self::local_reserve_transfer_programs( + origin, dest, beneficiary, assets, fees, - separate_fees_instructions, weight_limit, )?; (local, Some(remote)) }, TransferType::DestinationReserve => { let (local, remote) = Self::destination_reserve_transfer_programs( + origin, dest, beneficiary, assets, fees, - separate_fees_instructions, weight_limit, )?; (local, Some(remote)) }, - TransferType::RemoteReserve(reserve) => ( - Self::remote_reserve_transfer_program( + TransferType::RemoteReserve(reserve) => { + let fees = match fees { + FeesHandling::Batched { fees } => fees, + _ => return Err(Error::::InvalidAssetUnsupportedReserve.into()), + }; + let local = Self::remote_reserve_transfer_program( + origin, reserve, dest, beneficiary, assets, fees, weight_limit, - )?, - None, - ), - TransferType::Teleport => ( - Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?, - None, - ), + )?; + (local, None) + }, + TransferType::Teleport => { + let (local, remote) = Self::teleport_assets_program( + origin, + dest, + beneficiary, + assets, + fees, + weight_limit, + )?; + (local, Some(remote)) + }, }; let weight = T::Weigher::weight(&mut local_xcm).map_err(|()| Error::::UnweighableMessage)?; @@ -1529,11 +1697,45 @@ impl Pallet { Ok(()) } + fn add_fees_to_xcm( + dest: MultiLocation, + fees: FeesHandling, + weight_limit: WeightLimit, + local: &mut Xcm<::RuntimeCall>, + remote: &mut Xcm<()>, + ) -> Result<(), Error> { + match fees { + FeesHandling::Batched { fees } => { + let context = T::UniversalLocation::get(); + // no custom fees instructions, they are batched together with `assets` transfer; + // BuyExecution happens after receiving all `assets` + let reanchored_fees = + fees.reanchored(&dest, context).map_err(|_| Error::::CannotReanchor)?; + // buy execution using `fees` batched together with above `reanchored_assets` + remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit }); + }, + FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => { + // fees are handled by separate XCM instructions, prepend fees instructions (for + // remote XCM they have to be prepended instead of appended to pass barriers). + sp_std::mem::swap(local, &mut local_fees); + sp_std::mem::swap(remote, &mut remote_fees); + // these are now swapped so fees actually go first + local.inner_mut().append(&mut local_fees.into_inner()); + remote.inner_mut().append(&mut remote_fees.into_inner()); + }, + } + Ok(()) + } + fn local_reserve_fees_instructions( + origin: MultiLocation, dest: MultiLocation, fees: MultiAsset, weight_limit: WeightLimit, ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, vec![fees.clone()]); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let context = T::UniversalLocation::get(); let reanchored_fees = fees .clone() @@ -1554,16 +1756,20 @@ impl Pallet { } fn local_reserve_transfer_programs( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling, weight_limit: WeightLimit, ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) let max_assets = - assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0); + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: MultiAssets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1571,45 +1777,37 @@ impl Pallet { .reanchor(&dest, context) .map_err(|_| Error::::CannotReanchor)?; - // fees are either handled through dedicated instructions, or batched together with assets - let fees_already_handled = separate_fees_instructions.is_some(); - let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions - .map(|(local, remote)| (local.into_inner(), remote.into_inner())) - .unwrap_or_default(); - - // start off with any necessary local fees specific instructions - let mut local_execute_xcm = fees_local_xcm; - // move `assets` to `dest`s local sovereign account - local_execute_xcm.push(TransferAsset { assets, beneficiary: dest }); - - // on destination chain, start off with custom fee instructions - let mut xcm_on_dest = fees_remote_xcm; - // continue with rest of assets - xcm_on_dest.extend_from_slice(&[ + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ + // locally move `assets` to `dest`s local sovereign account + TransferAsset { assets, beneficiary: dest }, + ]); + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ // let (dest) chain know assets are in its SA on reserve ReserveAssetDeposited(reanchored_assets), // following instructions are not exec'ed on behalf of origin chain anymore ClearOrigin, ]); - if !fees_already_handled { - // no custom fees instructions, they are batched together with `assets` transfer; - // BuyExecution happens after receiving all `assets` - let reanchored_fees = - fees.reanchored(&dest, context).map_err(|_| Error::::CannotReanchor)?; - // buy execution using `fees` batched together with above `reanchored_assets` - xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit }); - } + // handle fees + Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + xcm_on_dest + .inner_mut() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); - Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest))) + Ok((local_execute_xcm, xcm_on_dest)) } fn destination_reserve_fees_instructions( + origin: MultiLocation, dest: MultiLocation, fees: MultiAsset, weight_limit: WeightLimit, ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, vec![fees.clone()]); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let context = T::UniversalLocation::get(); let reanchored_fees = fees .clone() @@ -1633,16 +1831,20 @@ impl Pallet { } fn destination_reserve_transfer_programs( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec, - fees: MultiAsset, - separate_fees_instructions: Option<(Xcm<::RuntimeCall>, Xcm<()>)>, + fees: FeesHandling, weight_limit: WeightLimit, ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let (_, assets) = value; + // max assets is `assets` (+ potentially separately handled fee) let max_assets = - assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0); + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let assets: MultiAssets = assets.into(); let context = T::UniversalLocation::get(); let mut reanchored_assets = assets.clone(); @@ -1650,47 +1852,33 @@ impl Pallet { .reanchor(&dest, context) .map_err(|_| Error::::CannotReanchor)?; - // fees are either handled through dedicated instructions, or batched together with assets - let fees_already_handled = separate_fees_instructions.is_some(); - let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions - .map(|(local, remote)| (local.into_inner(), remote.into_inner())) - .unwrap_or_default(); - - // start off with any necessary local fees specific instructions - let mut local_execute_xcm = fees_local_xcm; - // continue with rest of assets - local_execute_xcm.extend_from_slice(&[ + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ // withdraw reserve-based assets WithdrawAsset(assets.clone()), // burn reserve-based assets BurnAsset(assets), ]); - - // on destination chain, start off with custom fee instructions - let mut xcm_on_dest = fees_remote_xcm; - // continue with rest of assets - xcm_on_dest.extend_from_slice(&[ + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ // withdraw `assets` from origin chain's sovereign account WithdrawAsset(reanchored_assets), // following instructions are not exec'ed on behalf of origin chain anymore ClearOrigin, ]); - if !fees_already_handled { - // no custom fees instructions, they are batched together with `assets` transfer; - // BuyExecution happens after receiving all `assets` - let reanchored_fees = - fees.reanchored(&dest, context).map_err(|_| Error::::CannotReanchor)?; - // buy execution using `fees` batched together with above `reanchored_assets` - xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit }); - } + // handle fees + Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; // deposit all remaining assets in holding to `beneficiary` location - xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + xcm_on_dest + .inner_mut() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); - Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest))) + Ok((local_execute_xcm, xcm_on_dest)) } // function assumes fees and assets have the same remote reserve fn remote_reserve_transfer_program( + origin: MultiLocation, reserve: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, @@ -1698,6 +1886,10 @@ impl Pallet { fees: MultiAsset, weight_limit: WeightLimit, ) -> Result::RuntimeCall>, Error> { + let value = (origin, assets); + ensure!(T::XcmReserveTransferFilter::contains(&value), Error::::Filtered); + let (_, assets) = value; + let max_assets = assets.len() as u32; let context = T::UniversalLocation::get(); // we spend up to half of fees for execution on reserve and other half for execution on @@ -1760,6 +1952,8 @@ impl Pallet { &dummy_context, ) .map_err(|_| Error::::CannotCheckOutTeleport)?; + // safe to do this here, we're in a transactional call that will be reverted on any + // errors down the line ::AssetTransactor::check_out( &dest, &fees, @@ -1783,24 +1977,74 @@ impl Pallet { } fn teleport_assets_program( + origin: MultiLocation, dest: MultiLocation, beneficiary: MultiLocation, assets: Vec, - mut fees: MultiAsset, + fees: FeesHandling, weight_limit: WeightLimit, - ) -> Result::RuntimeCall>, Error> { + ) -> Result<(Xcm<::RuntimeCall>, Xcm<()>), Error> { + let value = (origin, assets); + ensure!(T::XcmTeleportFilter::contains(&value), Error::::Filtered); + let (_, assets) = value; + + // max assets is `assets` (+ potentially separately handled fee) + let max_assets = + assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 }; let context = T::UniversalLocation::get(); - fees.reanchor(&dest, context).map_err(|_| Error::::CannotReanchor)?; - let max_assets = assets.len() as u32; - let xcm_on_dest = Xcm(vec![ - BuyExecution { fees, weight_limit }, - DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + let assets: MultiAssets = assets.into(); + let mut reanchored_assets = assets.clone(); + reanchored_assets + .reanchor(&dest, context) + .map_err(|_| Error::::CannotReanchor)?; + + // XcmContext irrelevant in teleports checks + let dummy_context = + XcmContext { origin: None, message_id: Default::default(), topic: None }; + for asset in assets.inner() { + // We should check that the asset can actually be teleported out (for this to + // be in error, there would need to be an accounting violation by ourselves, + // so it's unlikely, but we don't want to allow that kind of bug to leak into + // a trusted chain. + ::AssetTransactor::can_check_out( + &dest, + asset, + &dummy_context, + ) + .map_err(|_| Error::::CannotCheckOutTeleport)?; + } + for asset in assets.inner() { + // safe to do this here, we're in a transactional call that will be reverted on any + // errors down the line + ::AssetTransactor::check_out( + &dest, + asset, + &dummy_context, + ); + } + + // XCM instructions to be executed on local chain + let mut local_execute_xcm = Xcm(vec![ + // withdraw assets to be teleported + WithdrawAsset(assets.clone()), + // burn assets on local chain + BurnAsset(assets), ]); - Ok(Xcm(vec![ - WithdrawAsset(assets.into()), - SetFeesMode { jit_withdraw: true }, - InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, - ])) + // XCM instructions to be executed on destination chain + let mut xcm_on_dest = Xcm(vec![ + // teleport `assets` in from origin chain + ReceiveTeleportedAsset(reanchored_assets), + // following instructions are not exec'ed on behalf of origin chain anymore + ClearOrigin, + ]); + // handle fees + Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?; + // deposit all remaining assets in holding to `beneficiary` location + xcm_on_dest + .inner_mut() + .push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }); + + Ok((local_execute_xcm, xcm_on_dest)) } /// Halve `fees` fungible amount. @@ -2347,7 +2591,7 @@ impl WrapVersion for Pallet { dest: &MultiLocation, xcm: impl Into>, ) -> Result, ()> { - SupportedVersion::::get(XCM_VERSION, LatestVersionedMultiLocation(dest)) + Self::get_version_for(dest) .or_else(|| { Self::note_unknown_version(dest); SafeXcmVersion::::get() @@ -2364,6 +2608,12 @@ impl WrapVersion for Pallet { } } +impl GetVersion for Pallet { + fn get_version_for(dest: &MultiLocation) -> Option { + SupportedVersion::::get(XCM_VERSION, LatestVersionedMultiLocation(dest)) + } +} + impl VersionChangeNotifier for Pallet { /// Start notifying `location` should the XCM version of this chain change. /// diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 606d51bb8bce2410d7576bb970ba0ac38a895c13..0ac4205ed949fc2308d4c02839e9f37d118f4408 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -28,17 +28,17 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::origin; use sp_core::H256; use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; -pub use sp_std::{ - cell::RefCell, collections::btree_map::BTreeMap, fmt::Debug, marker::PhantomData, -}; +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, CurrencyAdapter as XcmCurrencyAdapter, DescribeAllTerminal, - FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, HashedDescription, IsConcrete, - MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, FixedRateOfFungible, FixedWeightBounds, + FungiblesAdapter, HashedDescription, IsConcrete, MatchedConvertedConcreteId, NoChecking, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{ traits::{Identity, JustTry}, @@ -428,6 +428,7 @@ pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId< JustTry, >; +#[allow(deprecated)] pub type AssetTransactors = ( XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>, FungiblesAdapter< @@ -579,6 +580,57 @@ impl super::benchmarking::Config for Test { Parachain(OTHER_PARA_ID).into(), )) } + + fn set_up_complex_asset_transfer( + ) -> Option<(MultiAssets, u32, MultiLocation, Box)> { + use crate::tests::assets_transfer::{into_multiassets_checked, set_up_foreign_asset}; + // Transfer native asset (local reserve) to `USDT_PARA_ID`. Using teleport-trusted USDT for + // fees. + + let asset_amount = 10u128; + let fee_amount = 2u128; + + let existential_deposit = ExistentialDeposit::get(); + let caller = frame_benchmarking::whitelisted_caller(); + + // Give some multiple of the existential deposit + let balance = asset_amount + existential_deposit * 1000; + let _ = >::make_free_balance_be( + &caller, balance, + ); + // create sufficient foreign asset USDT + let usdt_initial_local_amount = fee_amount * 10; + let (usdt_chain, _, usdt_id_multilocation) = set_up_foreign_asset( + USDT_PARA_ID, + None, + caller.clone(), + usdt_initial_local_amount, + true, + ); + + // native assets transfer destination is USDT chain (teleport trust only for USDT) + let dest = usdt_chain; + let (assets, fee_index, _, _) = into_multiassets_checked( + // USDT for fees (is sufficient on local chain too) - teleported + (usdt_id_multilocation, fee_amount).into(), + // native asset to transfer (not used for fees) - local reserve + (MultiLocation::here(), asset_amount).into(), + ); + // verify initial balances + assert_eq!(Balances::free_balance(&caller), balance); + assert_eq!(Assets::balance(usdt_id_multilocation, &caller), usdt_initial_local_amount); + + // verify transferred successfully + let verify = Box::new(move || { + // verify balances after transfer, decreased by transferred amounts + assert_eq!(Balances::free_balance(&caller), balance - asset_amount); + assert_eq!( + Assets::balance(usdt_id_multilocation, &caller), + usdt_initial_local_amount - fee_amount + ); + }); + Some((assets, fee_index as u32, dest, verify)) + } } pub(crate) fn last_event() -> RuntimeEvent { @@ -604,6 +656,17 @@ pub(crate) fn buy_limited_execution( pub(crate) fn new_test_ext_with_balances( balances: Vec<(AccountId, Balance)>, +) -> sp_io::TestExternalities { + new_test_ext_with_balances_and_xcm_version( + balances, + // By default set actual latest XCM version + Some(XCM_VERSION), + ) +} + +pub(crate) fn new_test_ext_with_balances_and_xcm_version( + balances: Vec<(AccountId, Balance)>, + safe_xcm_version: Option, ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); @@ -611,7 +674,7 @@ pub(crate) fn new_test_ext_with_balances( .assimilate_storage(&mut t) .unwrap(); - pallet_xcm::GenesisConfig:: { safe_xcm_version: Some(2), ..Default::default() } + pallet_xcm::GenesisConfig:: { safe_xcm_version, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs index d1b298765e229a2c44e1478c68fe4ab49d8c3959..6893bae2b6c17d6d76abe28aa44d8b89a74a627c 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/assets_transfer.rs @@ -19,6 +19,7 @@ use crate::{ mock::*, tests::{ALICE, BOB, FEE_AMOUNT, INITIAL_BALANCE, SEND_AMOUNT}, + DispatchResult, OriginFor, }; use frame_support::{ assert_ok, @@ -32,6 +33,7 @@ use xcm_executor::traits::ConvertLocation; // Helper function to deduplicate testing different teleport types. fn do_test_and_verify_teleport_assets( + origin_location: MultiLocation, expected_beneficiary: MultiLocation, call: Call, expected_weight_limit: WeightLimit, @@ -40,8 +42,9 @@ fn do_test_and_verify_teleport_assets( (ALICE, INITIAL_BALANCE), (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; + let dest = RelayLocation::get().into(); new_test_ext_with_balances(balances).execute_with(|| { - let weight = BaseXcmWeight::get() * 3; + let weight = BaseXcmWeight::get() * 2; assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); // call extrinsic call(); @@ -49,7 +52,7 @@ fn do_test_and_verify_teleport_assets( assert_eq!( sent_xcm(), vec![( - RelayLocation::get().into(), + dest, Xcm(vec![ ReceiveTeleportedAsset((Here, SEND_AMOUNT).into()), ClearOrigin, @@ -63,10 +66,23 @@ fn do_test_and_verify_teleport_assets( ); let versioned_sent = VersionedXcm::from(sent_xcm().into_iter().next().unwrap().1); let _check_v2_ok: xcm::v2::Xcm<()> = versioned_sent.try_into().unwrap(); + + let mut last_events = last_events(3).into_iter(); assert_eq!( - last_event(), + last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); }); } @@ -76,8 +92,10 @@ fn do_test_and_verify_teleport_assets( /// local effects. #[test] fn teleport_assets_works() { + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); do_test_and_verify_teleport_assets( + origin_location, beneficiary, || { assert_ok!(XcmPallet::teleport_assets( @@ -98,10 +116,12 @@ fn teleport_assets_works() { /// local effects. #[test] fn limited_teleport_assets_works() { + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); let weight_limit = WeightLimit::Limited(Weight::from_parts(5000, 5000)); let expected_weight_limit = weight_limit.clone(); do_test_and_verify_teleport_assets( + origin_location, beneficiary, || { assert_ok!(XcmPallet::limited_teleport_assets( @@ -160,7 +180,7 @@ fn reserve_transfer_assets_with_paid_router_works() { let xcm_router_fee_amount = Para3000PaymentAmount::get(); let weight = BaseXcmWeight::get(); let dest: MultiLocation = - Junction::AccountId32 { network: None, id: user_account.clone().into() }.into(); + AccountId32 { network: None, id: user_account.clone().into() }.into(); assert_eq!(Balances::total_balance(&user_account), INITIAL_BALANCE); assert_ok!(XcmPallet::reserve_transfer_assets( RuntimeOrigin::signed(user_account.clone()), @@ -221,9 +241,10 @@ fn reserve_transfer_assets_with_paid_router_works() { }); } -fn set_up_foreign_asset( +pub(crate) fn set_up_foreign_asset( reserve_para_id: u32, inner_junction: Option, + benficiary: AccountId, initial_amount: u128, is_sufficient: bool, ) -> (MultiLocation, AccountId, MultiLocation) { @@ -238,7 +259,7 @@ fn set_up_foreign_asset( reserve_location }; - // create sufficient (to be used as fees as well) foreign asset (0 total issuance) + // create sufficient (to be used as fees as well) foreign asset assert_ok!(Assets::force_create( RuntimeOrigin::root(), foreign_asset_id_multilocation, @@ -251,7 +272,7 @@ fn set_up_foreign_asset( assert_ok!(Assets::mint( RuntimeOrigin::signed(BOB), foreign_asset_id_multilocation, - ALICE, + benficiary, initial_amount )); @@ -260,7 +281,7 @@ fn set_up_foreign_asset( // Helper function that provides correct `fee_index` after `sort()` done by // `vec![MultiAsset, MultiAsset].into()`. -fn into_multiassets_checked( +pub(crate) fn into_multiassets_checked( fee_asset: MultiAsset, transfer_asset: MultiAsset, ) -> (MultiAssets, usize, MultiAsset, MultiAsset) { @@ -269,29 +290,31 @@ fn into_multiassets_checked( (assets, fee_index, fee_asset, transfer_asset) } -/// Test `limited_reserve_transfer_assets` with local asset reserve and local fee reserve. +/// Test `tested_call` with local asset reserve and local fee reserve. /// /// Transferring native asset (local reserve) to some `OTHER_PARA_ID` (no teleport trust). /// Using native asset for fees as well. /// -/// ```nocompile -/// Here (source) OTHER_PARA_ID (destination) -/// | `assets` reserve -/// | `fees` reserve -/// | -/// | 1. execute `TransferReserveAsset(assets_and_fees_batched_together)` -/// | \--> sends `ReserveAssetDeposited(both), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { +/// Verifies `expected_result` +fn local_asset_reserve_and_local_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![ (ALICE, INITIAL_BALANCE), (ParaId::from(OTHER_PARA_ID).into_account_truncating(), INITIAL_BALANCE), ]; - - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); let weight_limit = WeightLimit::Limited(Weight::from_parts(5000, 5000)); let expected_weight_limit = weight_limit.clone(); let expected_beneficiary = beneficiary; @@ -301,14 +324,19 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv let weight = BaseXcmWeight::get(); assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); // call extrinsic - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new((Here, SEND_AMOUNT).into()), 0, weight_limit, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } // Alice spent amount assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - SEND_AMOUNT); // Destination account (parachain account) has amount @@ -337,7 +365,7 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: expected_beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -348,38 +376,72 @@ fn limited_reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserv }); } -/// Test `reserve_transfer_assets` with destination asset reserve and local fee reserve. +/// Test `transfer_assets` with local asset reserve and local fee reserve works. +#[test] +fn transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_local_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and local fee reserve works. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and local fee reserve disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and local fee reserve. /// /// Transferring foreign asset (`FOREIGN_ASSET_RESERVE_PARA_ID` reserve) to /// `FOREIGN_ASSET_RESERVE_PARA_ID` (no teleport trust). /// Using native asset (local reserve) for fees. /// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` reserve `assets` reserve -/// | -/// | 1. execute `TransferReserveAsset(fees)` -/// | \-> sends `ReserveAssetDeposited(fees), ClearOrigin, BuyExecution(fees), DepositAsset` -/// | 2. execute `InitiateReserveWithdraw(assets)` -/// | \--> sends `WithdrawAsset(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -/// /// Asserts that the sender's balance is decreased and the beneficiary's balance /// is increased. Verifies the correct message is sent and event is emitted. -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_works() { +/// +/// Verifies `expected_result`. +fn destination_asset_reserve_and_local_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let weight = BaseXcmWeight::get() * 3; let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, reserve_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -404,14 +466,19 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -454,7 +521,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -465,21 +532,72 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_ }); } -/// Test `reserve_transfer_assets` with remote asset reserve and local fee reserve. +/// Test `transfer_assets` with destination asset reserve and local fee reserve. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and local fee reserve +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and local fee reserve +/// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and local fee reserve is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to `OTHER_PARA_ID`. /// Using native (local reserve) as fee should be disallowed. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { +fn remote_asset_reserve_and_local_fee_reserve_call_disallowed( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, _, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -500,7 +618,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // try the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -508,14 +626,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice transferred nothing assert_eq!(Assets::balance(foreign_asset_id_multilocation, ALICE), foreign_initial_amount); @@ -529,33 +640,82 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disal }); } -/// Test `reserve_transfer_assets` with local asset reserve and destination fee reserve. +/// Test `transfer_assets` with remote asset reserve and local fee reserve is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and local fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and local fee reserve is disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_local_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and destination fee reserve. /// /// Transferring native asset (local reserve) to `USDC_RESERVE_PARA_ID` (no teleport trust). Using /// foreign asset (`USDC_RESERVE_PARA_ID` reserve) for fees. /// -/// ```nocompile -/// Here (source) USDC_RESERVE_PARA_ID (destination) -/// | `assets` reserve `fees` reserve -/// | -/// | 1. execute `InitiateReserveWithdraw(fees)` -/// | \--> sends `WithdrawAsset(fees), ClearOrigin, BuyExecution(fees), DepositAsset` -/// | 2. execute `TransferReserveAsset(assets)` -/// | \-> sends `ReserveAssetDeposited(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_works() { +/// Asserts that the sender's balance is decreased and the beneficiary's balance +/// is increased. Verifies the correct message is sent and event is emitted. +/// +/// Verifies `expected_result`. +fn local_asset_reserve_and_destination_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (usdc_reserve_location, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); @@ -580,14 +740,20 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 3; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -597,7 +763,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -642,22 +808,64 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_ }); } -/// Test `reserve_transfer_assets` with destination asset reserve and destination fee reserve. -/// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` reserve -/// | `assets` reserve -/// | -/// | 1. execute `InitiateReserveWithdraw(assets_and_fees_batched_together)` -/// | \--> sends `WithdrawAsset(batch), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` +/// Test `transfer_assets` with local asset reserve and destination fee reserve. #[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { +fn transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and destination fee reserve +/// disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and destination fee reserve disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and destination fee reserve. +/// +/// Verifies `expected_result` +fn destination_asset_reserve_and_destination_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { // we'll send just this foreign asset back to its reserve location and use it for fees as // well @@ -666,6 +874,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, true, ); @@ -684,14 +893,19 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } let weight = BaseXcmWeight::get() * 2; let mut last_events = last_events(3).into_iter(); @@ -702,7 +916,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -743,31 +957,78 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_re }); } -/// Test `reserve_transfer_assets` with remote asset reserve and destination fee reserve is +/// Test `transfer_assets` with destination asset reserve and destination fee reserve. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and destination fee +/// reserve. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_destination_fee_reserve_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and destination fee reserve /// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_destination_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `transfer_assets` with remote asset reserve and destination fee reserve is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to /// `USDC_RESERVE_PARA_ID`. Using USDC (destination reserve) as fee. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { +fn remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 42; let (usdc_chain, _, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, _, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -789,7 +1050,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -797,14 +1058,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); @@ -819,21 +1073,76 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve }); } -/// Test `reserve_transfer_assets` with local asset reserve and remote fee reserve is disallowed. +/// Test `transfer_assets` with remote asset reserve and destination fee reserve is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and destination fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and destination fee reserve is +/// disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_destination_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_destination_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and remote fee reserve is disallowed. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { +fn local_asset_reserve_and_remote_fee_reserve_call_disallowed( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); @@ -854,7 +1163,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -862,14 +1171,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); assert_eq!(Assets::balance(usdc_id_multilocation, ALICE), usdc_initial_local_amount); assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // Sovereign account of reserve parachain is unchanged @@ -882,32 +1184,86 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disal }); } -/// Test `reserve_transfer_assets` with destination asset reserve and remote fee reserve is +/// Test `transfer_assets` with local asset reserve and remote fee reserve is disallowed. +#[test] +fn transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and remote fee reserve is /// disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and remote fee reserve is disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and remote fee reserve is disallowed. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { +fn destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 42; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -928,7 +1284,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -936,14 +1292,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); assert_eq!(Assets::balance(usdc_id_multilocation, ALICE), usdc_initial_local_amount); @@ -961,7 +1310,51 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve }); } -/// Test `reserve_transfer_assets` with remote asset reserve and (same) remote fee reserve. +/// Test `transfer_assets` with destination asset reserve and remote fee reserve is disallowed. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and remote fee reserve is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and remote fee reserve is +/// disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_remote_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and (same) remote fee reserve. /// /// Transferring native asset (local reserve) to `OTHER_PARA_ID` (no teleport trust). Using foreign /// asset (`USDC_RESERVE_PARA_ID` remote reserve) for fees. @@ -976,18 +1369,31 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_remote_fee_reserve /// | -----------------> `C` executes `DepositReserveAsset(both)` dest `B` /// | --------------------------> `DepositAsset(both)` /// ``` -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { +/// +/// Verifies `expected_result` +fn remote_asset_reserve_and_remote_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (usdc_chain, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); @@ -996,12 +1402,12 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work let dest = RelayLocation::get().pushed_with_interior(Parachain(OTHER_PARA_ID)).unwrap(); let assets: MultiAssets = vec![(usdc_id_multilocation, SEND_AMOUNT).into()].into(); - let fee_index = 0; + let fee_index = 0u32; // reanchor according to test-case let context = UniversalLocation::get(); let expected_dest_on_reserve = dest.reanchored(&usdc_chain, context).unwrap(); - let fees = assets.get(fee_index).unwrap().clone(); + let fees = assets.get(fee_index as usize).unwrap().clone(); let (fees_half_1, fees_half_2) = XcmPallet::halve_fees(fees).unwrap(); let mut expected_assets_on_reserve = assets.clone(); expected_assets_on_reserve.reanchor(&usdc_chain, context).unwrap(); @@ -1013,14 +1419,20 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), - fee_index as u32, + fee_index, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + assert!(matches!( last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(_) }) @@ -1067,31 +1479,65 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_work }); } -/// Test `reserve_transfer_assets` with local asset reserve and teleported fee. +/// Test `transfer_assets` with remote asset reserve and (same) remote fee reserve. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { + let expected_result = Ok(()); + remote_asset_reserve_and_remote_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and (same) remote fee reserve. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_remote_fee_reserve_works() { + let expected_result = Ok(()); + remote_asset_reserve_and_remote_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and (same) remote fee reserve +/// disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_remote_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_remote_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with local asset reserve and teleported fee. /// /// Transferring native asset (local reserve) to `USDT_PARA_ID`. Using teleport-trusted USDT for /// fees. /// -/// ```nocompile -/// Here (source) USDT_PARA_ID (destination) -/// | `assets` reserve `fees` teleport-trust -/// | -/// | 1. execute `InitiateTeleport(fees)` -/// | \--> sends `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)` -/// | 2. execute `TransferReserveAsset(assets)` -/// | \-> sends `ReserveAssetDeposited(assets), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { +/// Verifies `expected_result` +fn local_asset_reserve_and_teleported_fee_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = - set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); // native assets transfer destination is USDT chain (teleport trust only for USDT) let dest = usdt_chain; @@ -1113,14 +1559,20 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 3; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -1130,7 +1582,7 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -1173,39 +1625,76 @@ fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { }); } -/// Test `reserve_transfer_assets` with destination asset reserve and teleported fee. +/// Test `transfer_assets` with local asset reserve and teleported fee. +#[test] +fn transfer_assets_with_local_asset_reserve_and_teleported_fee_works() { + let expected_result = Ok(()); + local_asset_reserve_and_teleported_fee_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with local asset reserve and teleported fee disallowed. +#[test] +fn reserve_transfer_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + local_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with local asset reserve and teleported fee disallowed. +#[test] +fn teleport_assets_with_local_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + local_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with destination asset reserve and teleported fee. /// /// Transferring foreign asset (destination reserve) to `FOREIGN_ASSET_RESERVE_PARA_ID`. Using /// teleport-trusted USDT for fees. /// -/// ```nocompile -/// Here (source) FOREIGN_ASSET_RESERVE_PARA_ID (destination) -/// | `fees` (USDT) teleport-trust -/// | `assets` reserve -/// | -/// | 1. execute `InitiateTeleport(fees)` -/// | \--> sends `ReceiveTeleportedAsset(fees), .., DepositAsset(fees)` -/// | 2. execute `InitiateReserveWithdraw(assets)` -/// | \--> sends `WithdrawAsset(asset), ClearOrigin, BuyExecution(fees), DepositAsset` -/// \------------------------------------------> -/// ``` -#[test] -fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_works() { +/// Verifies `expected_result` +fn destination_asset_reserve_and_teleported_fee_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (_, usdt_chain_sovereign_account, usdt_id_multilocation) = - set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -1231,14 +1720,20 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); // do the transfer - assert_ok!(XcmPallet::limited_reserve_transfer_assets( + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), Box::new(assets.into()), fee_index as u32, Unlimited, - )); + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + let weight = BaseXcmWeight::get() * 4; let mut last_events = last_events(3).into_iter(); assert_eq!( @@ -1248,7 +1743,7 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor assert_eq!( last_events.next().unwrap(), RuntimeEvent::XcmPallet(crate::Event::FeesPaid { - paying: beneficiary, + paying: origin_location, fees: MultiAssets::new(), }) ); @@ -1303,26 +1798,73 @@ fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_wor }); } -/// Test `reserve_transfer_assets` with remote asset reserve and teleported fee is disallowed. +/// Test `transfer_assets` with destination asset reserve and teleported fee. +#[test] +fn transfer_assets_with_destination_asset_reserve_and_teleported_fee_works() { + let expected_result = Ok(()); + destination_asset_reserve_and_teleported_fee_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with destination asset reserve and teleported fee +/// disallowed. +#[test] +fn reserve_transfer_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + destination_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with destination asset reserve and teleported fee disallowed. +#[test] +fn teleport_assets_with_destination_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + destination_asset_reserve_and_teleported_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with remote asset reserve and teleported fee is disallowed. /// /// Transferring foreign asset (reserve on `FOREIGN_ASSET_RESERVE_PARA_ID`) to `USDT_PARA_ID`. /// Using teleport-trusted USDT for fees. -#[test] -fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { +fn remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = - set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); - // create non-sufficient foreign asset BLA (0 total issuance) + // create non-sufficient foreign asset BLA let foreign_initial_amount = 142; let (_, reserve_sovereign_account, foreign_asset_id_multilocation) = set_up_foreign_asset( FOREIGN_ASSET_RESERVE_PARA_ID, Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, foreign_initial_amount, false, ); @@ -1341,8 +1883,8 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); - // do the transfer - let result = XcmPallet::limited_reserve_transfer_assets( + // try the transfer + let result = tested_call( RuntimeOrigin::signed(ALICE), Box::new(dest.into()), Box::new(beneficiary.into()), @@ -1350,14 +1892,7 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow fee_index as u32, Unlimited, ); - assert_eq!( - result, - Err(DispatchError::Module(ModuleError { - index: 4, - error: [22, 0, 0, 0], - message: Some("InvalidAssetUnsupportedReserve") - })) - ); + assert_eq!(result, expected_result); // Alice native asset untouched assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); @@ -1375,20 +1910,62 @@ fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallow }); } +/// Test `transfer_assets` with remote asset reserve and teleported fee is disallowed. +#[test] +fn transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [22, 0, 0, 0], + message: Some("InvalidAssetUnsupportedReserve"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with remote asset reserve and teleported fee is +/// disallowed. +#[test] +fn reserve_transfer_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [23, 0, 0, 0], + message: Some("TooManyReserves"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with remote asset reserve and teleported fee is disallowed. +#[test] +fn teleport_assets_with_remote_asset_reserve_and_teleported_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + remote_asset_reserve_and_teleported_fee_reserve_call_disallowed( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + /// Test `reserve_transfer_assets` single asset which is teleportable - should fail. /// /// Attempting to reserve-transfer teleport-trusted USDT to `USDT_PARA_ID` should fail. #[test] -fn reserve_transfer_assets_with_teleportable_asset_fails() { +fn reserve_transfer_assets_with_teleportable_asset_disallowed() { let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDT (0 total issuance) + // create sufficient foreign asset USDT let usdt_initial_local_amount = 42; let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = - set_up_foreign_asset(USDT_PARA_ID, None, usdt_initial_local_amount, true); + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, true); // transfer destination is USDT chain (foreign asset needs to go through its reserve chain) let dest = usdt_chain; @@ -1428,9 +2005,9 @@ fn reserve_transfer_assets_with_teleportable_asset_fails() { }); } -/// Test `reserve_transfer_assets` with teleportable fee that is filtered - should fail. +/// Test `transfer_assets` with teleportable fee that is filtered - should fail. #[test] -fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { +fn transfer_assets_with_filtered_teleported_fee_disallowed() { let beneficiary: MultiLocation = AccountId32 { network: None, id: BOB.into() }.into(); new_test_ext_with_balances(vec![(ALICE, INITIAL_BALANCE)]).execute_with(|| { let (assets, fee_index, _, _) = into_multiassets_checked( @@ -1439,7 +2016,7 @@ fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { // native asset to transfer (not used for fees) - local reserve (MultiLocation::here(), SEND_AMOUNT).into(), ); - let result = XcmPallet::limited_reserve_transfer_assets( + let result = XcmPallet::transfer_assets( RuntimeOrigin::signed(ALICE), Box::new(FilteredTeleportLocation::get().into()), Box::new(beneficiary.into()), @@ -1466,14 +2043,14 @@ fn reserve_transfer_assets_with_filtered_teleported_fee_disallowed() { #[test] fn intermediary_error_reverts_side_effects() { let balances = vec![(ALICE, INITIAL_BALANCE)]; - let beneficiary: MultiLocation = - Junction::AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); new_test_ext_with_balances(balances).execute_with(|| { - // create sufficient foreign asset USDC (0 total issuance) + // create sufficient foreign asset USDC let usdc_initial_local_amount = 142; let (_, usdc_chain_sovereign_account, usdc_id_multilocation) = set_up_foreign_asset( USDC_RESERVE_PARA_ID, Some(USDC_INNER_JUNCTION), + ALICE, usdc_initial_local_amount, true, ); @@ -1515,3 +2092,330 @@ fn intermediary_error_reverts_side_effects() { assert_eq!(sent_xcm(), vec![]); }); } + +/// Test `tested_call` with teleportable asset and local fee reserve. +/// +/// Transferring USDT to `USDT_PARA_ID` (teleport trust). Using native asset (local reserve) for +/// fees. +/// +/// Verifies `expected_result` +fn teleport_asset_using_local_fee_reserve_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ + let weight = BaseXcmWeight::get() * 3; + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create non-sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (usdt_chain, usdt_chain_sovereign_account, usdt_id_multilocation) = + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, false); + + // transfer destination is reserve location (no teleport trust) + let dest = usdt_chain; + + let (assets, fee_index, fee_asset, xfer_asset) = into_multiassets_checked( + // native asset for fee - local reserve + (MultiLocation::here(), FEE_AMOUNT).into(), + // USDT to transfer - destination reserve + (usdt_id_multilocation, SEND_AMOUNT).into(), + ); + + // reanchor according to test-case + let context = UniversalLocation::get(); + let expected_fee = fee_asset.reanchored(&dest, context).unwrap(); + let expected_asset = xfer_asset.reanchored(&dest, context).unwrap(); + + // balances checks before + assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + + let mut last_events = last_events(3).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) + ); + + // Alice spent (transferred) amount + assert_eq!( + Assets::balance(usdt_id_multilocation, ALICE), + usdt_initial_local_amount - SEND_AMOUNT + ); + // Alice used native asset for fees + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE - FEE_AMOUNT); + // Destination account (parachain account) added native reserve to balances + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), FEE_AMOUNT); + assert_eq!(Assets::balance(usdt_id_multilocation, usdt_chain_sovereign_account), 0); + // Verify total and active issuance of foreign BLA have decreased (burned on + // reserve-withdraw) + let expected_issuance = usdt_initial_local_amount - SEND_AMOUNT; + assert_eq!(Assets::total_issuance(usdt_id_multilocation), expected_issuance); + assert_eq!(Assets::active_issuance(usdt_id_multilocation), expected_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + // `fees` are being sent through local-reserve transfer because fee reserve is + // local chain; `assets` are burned on source and withdrawn from SA here + Xcm(vec![ + ReserveAssetDeposited(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + ReceiveTeleportedAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + }); +} + +/// Test `transfer_assets` with teleportable asset and local fee reserve. +#[test] +fn transfer_assets_with_teleportable_asset_and_local_fee_reserve_works() { + let expected_result = Ok(()); + teleport_asset_using_local_fee_reserve_call(XcmPallet::transfer_assets, expected_result); +} + +/// Test `limited_reserve_transfer_assets` with teleportable asset and local fee reserve disallowed. +#[test] +fn reserve_transfer_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleport_asset_using_local_fee_reserve_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with teleportable asset and local fee reserve disallowed. +#[test] +fn teleport_assets_with_teleportable_asset_and_local_fee_reserve_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleport_asset_using_local_fee_reserve_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} + +/// Test `tested_call` with teleported asset reserve and destination fee. +/// +/// Transferring USDT to `FOREIGN_ASSET_RESERVE_PARA_ID` (teleport trust). Using foreign asset +/// (destination reserve) for fees. +/// +/// Verifies `expected_result` +fn teleported_asset_using_destination_reserve_fee_call( + tested_call: Call, + expected_result: DispatchResult, +) where + Call: FnOnce( + OriginFor, + Box, + Box, + Box, + u32, + WeightLimit, + ) -> DispatchResult, +{ + let balances = vec![(ALICE, INITIAL_BALANCE)]; + let origin_location: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + let beneficiary: MultiLocation = AccountId32 { network: None, id: ALICE.into() }.into(); + new_test_ext_with_balances(balances).execute_with(|| { + // create sufficient foreign asset BLA to be used for fees + let foreign_initial_amount = 142; + let (reserve_location, foreign_sovereign_account, foreign_asset_id_multilocation) = + set_up_foreign_asset( + FOREIGN_ASSET_RESERVE_PARA_ID, + Some(FOREIGN_ASSET_INNER_JUNCTION), + ALICE, + foreign_initial_amount, + true, + ); + + // create non-sufficient foreign asset USDT + let usdt_initial_local_amount = 42; + let (_, usdt_chain_sovereign_account, usdt_id_multilocation) = + set_up_foreign_asset(USDT_PARA_ID, None, ALICE, usdt_initial_local_amount, false); + + // transfer destination is BLA reserve location + let dest = reserve_location; + let dest_sovereign_account = foreign_sovereign_account; + + let (assets, fee_index, fee_asset, xfer_asset) = into_multiassets_checked( + // foreign asset BLA used for fees - destination reserve + (foreign_asset_id_multilocation, FEE_AMOUNT).into(), + // USDT to transfer - teleported + (usdt_id_multilocation, SEND_AMOUNT).into(), + ); + + // reanchor according to test-case + let context = UniversalLocation::get(); + let expected_fee = fee_asset.reanchored(&dest, context).unwrap(); + let expected_asset = xfer_asset.reanchored(&dest, context).unwrap(); + + // balances checks before + assert_eq!(Assets::balance(usdt_id_multilocation, ALICE), usdt_initial_local_amount); + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + + // do the transfer + let result = tested_call( + RuntimeOrigin::signed(ALICE), + Box::new(dest.into()), + Box::new(beneficiary.into()), + Box::new(assets.into()), + fee_index as u32, + Unlimited, + ); + assert_eq!(result, expected_result); + if expected_result.is_err() { + // short-circuit here for tests where we expect failure + return + } + + let weight = BaseXcmWeight::get() * 4; + let mut last_events = last_events(3).into_iter(); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome: Outcome::Complete(weight) }) + ); + assert_eq!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::FeesPaid { + paying: origin_location, + fees: MultiAssets::new(), + }) + ); + assert!(matches!( + last_events.next().unwrap(), + RuntimeEvent::XcmPallet(crate::Event::Sent { .. }) + )); + // Alice native asset untouched + assert_eq!(Balances::free_balance(ALICE), INITIAL_BALANCE); + // Alice spent USDT for fees + assert_eq!( + Assets::balance(usdt_id_multilocation, ALICE), + usdt_initial_local_amount - SEND_AMOUNT + ); + // Alice transferred BLA + assert_eq!( + Assets::balance(foreign_asset_id_multilocation, ALICE), + foreign_initial_amount - FEE_AMOUNT + ); + // Verify balances of USDT reserve parachain + assert_eq!(Balances::free_balance(usdt_chain_sovereign_account.clone()), 0); + assert_eq!(Assets::balance(usdt_id_multilocation, usdt_chain_sovereign_account), 0); + // Verify balances of transferred-asset reserve parachain + assert_eq!(Balances::free_balance(dest_sovereign_account.clone()), 0); + assert_eq!(Assets::balance(foreign_asset_id_multilocation, dest_sovereign_account), 0); + // Verify total and active issuance of USDT have decreased (teleported) + let expected_usdt_issuance = usdt_initial_local_amount - SEND_AMOUNT; + assert_eq!(Assets::total_issuance(usdt_id_multilocation), expected_usdt_issuance); + assert_eq!(Assets::active_issuance(usdt_id_multilocation), expected_usdt_issuance); + // Verify total and active issuance of foreign BLA asset have decreased (burned on + // reserve-withdraw) + let expected_bla_issuance = foreign_initial_amount - FEE_AMOUNT; + assert_eq!(Assets::total_issuance(foreign_asset_id_multilocation), expected_bla_issuance); + assert_eq!(Assets::active_issuance(foreign_asset_id_multilocation), expected_bla_issuance); + + // Verify sent XCM program + assert_eq!( + sent_xcm(), + vec![( + dest, + Xcm(vec![ + // fees are withdrawn from origin's local SA + WithdrawAsset(expected_fee.clone().into()), + buy_limited_execution(expected_fee, Unlimited), + // assets are teleported to destination chain + ReceiveTeleportedAsset(expected_asset.into()), + ClearOrigin, + DepositAsset { assets: AllCounted(2).into(), beneficiary }, + ]) + )] + ); + }); +} + +/// Test `transfer_assets` with teleported asset reserve and destination fee. +#[test] +fn transfer_teleported_assets_using_destination_reserve_fee_works() { + let expected_result = Ok(()); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::transfer_assets, + expected_result, + ); +} + +/// Test `limited_reserve_transfer_assets` with teleported asset reserve and destination fee +/// disallowed. +#[test] +fn reserve_transfer_teleported_assets_using_destination_reserve_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::limited_reserve_transfer_assets, + expected_result, + ); +} + +/// Test `limited_teleport_assets` with teleported asset reserve and destination fee disallowed. +#[test] +fn teleport_assets_using_destination_reserve_fee_disallowed() { + let expected_result = Err(DispatchError::Module(ModuleError { + index: 4, + error: [2, 0, 0, 0], + message: Some("Filtered"), + })); + teleported_asset_using_destination_reserve_fee_call( + XcmPallet::limited_teleport_assets, + expected_result, + ); +} diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 056c7dcc1968e11cbd2c990b379eaa7bd99918a2..e7a6fdc9dcede69c7ed520f396c2d53225706a85 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -16,7 +16,7 @@ #![cfg(test)] -mod assets_transfer; +pub(crate) mod assets_transfer; use crate::{ mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries, @@ -774,12 +774,13 @@ fn subscription_side_upgrades_work_without_notify() { #[test] fn subscriber_side_subscription_works() { - new_test_ext_with_balances(vec![]).execute_with(|| { + new_test_ext_with_balances_and_xcm_version(vec![], Some(XCM_VERSION)).execute_with(|| { let remote: MultiLocation = Parachain(1000).into(); assert_ok!(XcmPallet::force_subscribe_version_notify( RuntimeOrigin::root(), Box::new(remote.into()), )); + assert_eq!(XcmPallet::get_version_for(&remote), None); take_sent_xcm(); // Assume subscription target is working ok. @@ -798,6 +799,7 @@ fn subscriber_side_subscription_works() { let r = XcmExecutor::::execute_xcm(remote, message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); assert_eq!(take_sent_xcm(), vec![]); + assert_eq!(XcmPallet::get_version_for(&remote), Some(1)); // This message cannot be sent to a v2 remote. let v2_msg = xcm::v2::Xcm::<()>(vec![xcm::v2::Instruction::Trap(0)]); @@ -815,6 +817,8 @@ fn subscriber_side_subscription_works() { let hash = fake_message_hash(&message); let r = XcmExecutor::::execute_xcm(remote, message, hash, weight); assert_eq!(r, Outcome::Complete(weight)); + assert_eq!(take_sent_xcm(), vec![]); + assert_eq!(XcmPallet::get_version_for(&remote), Some(2)); // This message can now be sent to remote as it's v2. assert_eq!( @@ -827,7 +831,7 @@ fn subscriber_side_subscription_works() { /// We should auto-subscribe when we don't know the remote's version. #[test] fn auto_subscription_works() { - new_test_ext_with_balances(vec![]).execute_with(|| { + new_test_ext_with_balances_and_xcm_version(vec![], None).execute_with(|| { let remote_v2: MultiLocation = Parachain(1000).into(); let remote_v3: MultiLocation = Parachain(1001).into(); @@ -995,3 +999,68 @@ fn subscription_side_upgrades_work_with_multistage_notify() { ); }); } + +#[test] +fn get_and_wrap_version_works() { + new_test_ext_with_balances_and_xcm_version(vec![], None).execute_with(|| { + let remote_a: MultiLocation = Parachain(1000).into(); + let remote_b: MultiLocation = Parachain(1001).into(); + let remote_c: MultiLocation = Parachain(1002).into(); + + // no `safe_xcm_version` version at `GenesisConfig` + assert_eq!(XcmPallet::get_version_for(&remote_a), None); + assert_eq!(XcmPallet::get_version_for(&remote_b), None); + assert_eq!(XcmPallet::get_version_for(&remote_c), None); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![]); + + // set default XCM version (a.k.a. `safe_xcm_version`) + assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(1))); + assert_eq!(XcmPallet::get_version_for(&remote_a), None); + assert_eq!(XcmPallet::get_version_for(&remote_b), None); + assert_eq!(XcmPallet::get_version_for(&remote_c), None); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![]); + + // set XCM version only for `remote_a` + assert_ok!(XcmPallet::force_xcm_version( + RuntimeOrigin::root(), + Box::new(remote_a), + XCM_VERSION + )); + assert_eq!(XcmPallet::get_version_for(&remote_a), Some(XCM_VERSION)); + assert_eq!(XcmPallet::get_version_for(&remote_b), None); + assert_eq!(XcmPallet::get_version_for(&remote_c), None); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![]); + + let xcm = Xcm::<()>::default(); + + // wrap version - works because remote_a has `XCM_VERSION` + assert_eq!( + XcmPallet::wrap_version(&remote_a, xcm.clone()), + Ok(VersionedXcm::from(xcm.clone())) + ); + // does not work because remote_b has unknown version and default is set to 1, and + // `XCM_VERSION` cannot be wrapped to the `1` + assert_eq!(XcmPallet::wrap_version(&remote_b, xcm.clone()), Err(())); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![(remote_b.into(), 1)]); + + // set default to the `XCM_VERSION` + assert_ok!(XcmPallet::force_default_xcm_version(RuntimeOrigin::root(), Some(XCM_VERSION))); + assert_eq!(XcmPallet::get_version_for(&remote_b), None); + assert_eq!(XcmPallet::get_version_for(&remote_c), None); + + // now works, because default is `XCM_VERSION` + assert_eq!( + XcmPallet::wrap_version(&remote_b, xcm.clone()), + Ok(VersionedXcm::from(xcm.clone())) + ); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![(remote_b.into(), 2)]); + + // change remote_c to `1` + assert_ok!(XcmPallet::force_xcm_version(RuntimeOrigin::root(), Box::new(remote_c), 1)); + + // does not work because remote_c has `1` and default is `XCM_VERSION` which cannot be + // wrapped to the `1` + assert_eq!(XcmPallet::wrap_version(&remote_c, xcm.clone()), Err(())); + assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![(remote_b.into(), 2)]); + }) +} diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index b42f69d443817a1a223e8a2d95016e204c029aa6..74990f873abcc04c15199271216e47ae56dd85d5 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -7,13 +7,16 @@ license.workspace = true version = "1.0.0" publish = true +[lints] +workspace = true + [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = "1.0.28" -syn = "2.0.39" +syn = "2.0.48" Inflector = "0.11.4" [dev-dependencies] diff --git a/polkadot/xcm/procedural/src/builder_pattern.rs b/polkadot/xcm/procedural/src/builder_pattern.rs index 1cb795ea9b208eb2b1985c6ce48132855a83c5f8..e58c51103497a23a0b97d7274deba1130bfaf28a 100644 --- a/polkadot/xcm/procedural/src/builder_pattern.rs +++ b/polkadot/xcm/procedural/src/builder_pattern.rs @@ -87,8 +87,8 @@ fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 let methods = data_enum.variants.iter().map(|variant| { let variant_name = &variant.ident; let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(&method_name_string, variant_name.span()); - let docs = get_doc_comments(&variant); + let method_name = syn::Ident::new(method_name_string, variant_name.span()); + let docs = get_doc_comments(variant); let method = match &variant.fields { Fields::Unit => { quote! { @@ -148,9 +148,7 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { - return list.path.is_ident("builder"); - }, + Meta::List(ref list) => list.path.is_ident("builder"), _ => false, }); let builder_attr = match maybe_builder_attr { @@ -159,7 +157,7 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result Result { let arg_names: Vec<_> = fields @@ -217,7 +215,7 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result return Err(Error::new_spanned( - &variant, + variant, "Instructions that load the holding register should take operands", )), }; @@ -235,14 +233,14 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { let arg_names: Vec<_> = @@ -263,7 +261,7 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result return Err(Error::new_spanned( - &variant, + variant, "BuyExecution should have named fields", )), }; @@ -289,19 +287,19 @@ fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result fields, _ => return Err(Error::new_spanned( - &unpaid_execution_variant, + unpaid_execution_variant, "UnpaidExecution should have named fields", )), }; diff --git a/polkadot/xcm/src/double_encoded.rs b/polkadot/xcm/src/double_encoded.rs index 875b811da3f71c7f74762ec24acc05b8380e2110..320cccf9b1f08dafa58497995a74943233d26c4b 100644 --- a/polkadot/xcm/src/double_encoded.rs +++ b/polkadot/xcm/src/double_encoded.rs @@ -25,6 +25,7 @@ use parity_scale_codec::{Decode, DecodeLimit, Encode}; #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(T))] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct DoubleEncoded { encoded: Vec, #[codec(skip)] @@ -46,7 +47,7 @@ impl Eq for DoubleEncoded {} impl core::fmt::Debug for DoubleEncoded { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - self.encoded.fmt(f) + array_bytes::bytes2hex("0x", &self.encoded).fmt(f) } } diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index d804e4bf7351b3fffe5df2e4bfcba35d52b7704d..ddad0b5303beee8d318558d97485d1849f8ab462 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -373,6 +373,12 @@ pub trait WrapVersion { ) -> Result, ()>; } +/// Check and return the `Version` that should be used for the `Xcm` datum for the destination +/// `MultiLocation`, which will interpret it. +pub trait GetVersion { + fn get_version_for(dest: &latest::MultiLocation) -> Option; +} + /// `()` implementation does nothing with the XCM, just sending with whatever version it was /// authored as. impl WrapVersion for () { @@ -395,6 +401,11 @@ impl WrapVersion for AlwaysV2 { Ok(VersionedXcm::::V2(xcm.into().try_into()?)) } } +impl GetVersion for AlwaysV2 { + fn get_version_for(_dest: &latest::MultiLocation) -> Option { + Some(v2::VERSION) + } +} /// `WrapVersion` implementation which attempts to always convert the XCM to version 3 before /// wrapping it. @@ -407,6 +418,11 @@ impl WrapVersion for AlwaysV3 { Ok(VersionedXcm::::V3(xcm.into().try_into()?)) } } +impl GetVersion for AlwaysV3 { + fn get_version_for(_dest: &latest::MultiLocation) -> Option { + Some(v3::VERSION) + } +} /// `WrapVersion` implementation which attempts to always convert the XCM to the latest version /// before wrapping it. @@ -418,8 +434,8 @@ pub type AlwaysLts = AlwaysV3; pub mod prelude { pub use super::{ - latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV2, AlwaysV3, IntoVersion, Unsupported, - Version as XcmVersion, VersionedAssetId, VersionedInteriorMultiLocation, + latest::prelude::*, AlwaysLatest, AlwaysLts, AlwaysV2, AlwaysV3, GetVersion, IntoVersion, + Unsupported, Version as XcmVersion, VersionedAssetId, VersionedInteriorMultiLocation, VersionedMultiAsset, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse, VersionedXcm, WrapVersion, }; diff --git a/polkadot/xcm/src/v2/mod.rs b/polkadot/xcm/src/v2/mod.rs index 7f654ebfd9e9b242c920c7e88d02c2ce69714874..188b7f0b5c9384b7395ed08b27c4c8378719be8f 100644 --- a/polkadot/xcm/src/v2/mod.rs +++ b/polkadot/xcm/src/v2/mod.rs @@ -82,6 +82,7 @@ pub use traits::{Error, ExecuteXcm, GetWeight, Outcome, Result, SendError, SendR /// Basically just the XCM (more general) version of `ParachainDispatchOrigin`. #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum OriginKind { /// Origin should just be the native dispatch origin representation for the sender in the /// local runtime framework. For Cumulus/Frame chains this is the `Parachain` or `Relay` origin diff --git a/polkadot/xcm/src/v3/junction.rs b/polkadot/xcm/src/v3/junction.rs index 5e6e4ab903bde9588b8373f76e9d3b936bc28ac3..6ae339db2ae65aba72cb15ff438ffc8f577b875c 100644 --- a/polkadot/xcm/src/v3/junction.rs +++ b/polkadot/xcm/src/v3/junction.rs @@ -49,6 +49,7 @@ use serde::{Deserialize, Serialize}; Serialize, Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum NetworkId { /// Network specified by the first 32 bytes of its genesis block. @@ -119,6 +120,7 @@ impl TryFrom for NetworkId { Serialize, Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum BodyId { /// The only body in its context. @@ -190,6 +192,7 @@ impl TryFrom for BodyId { Serialize, Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum BodyPart { /// The body's declaration, under whatever means it decides. @@ -266,6 +269,7 @@ impl TryFrom for BodyPart { Serialize, Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum Junction { /// An indexed parachain belonging to and operated by the context. diff --git a/polkadot/xcm/src/v3/junctions.rs b/polkadot/xcm/src/v3/junctions.rs index d1cbc2dbed42cddd4fae2eae398a120e2272679b..88da20cb1a11e2824a927fbdf7bc8aead4ee58cc 100644 --- a/polkadot/xcm/src/v3/junctions.rs +++ b/polkadot/xcm/src/v3/junctions.rs @@ -44,6 +44,7 @@ pub(crate) const MAX_JUNCTIONS: usize = 8; serde::Serialize, serde::Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum Junctions { /// The interpreting consensus system. diff --git a/polkadot/xcm/src/v3/mod.rs b/polkadot/xcm/src/v3/mod.rs index bbdd504ceb0ff0415d73ed7891958a4cad7b3b67..50b7a539122d66c02d5d47c2efe119097313d5d6 100644 --- a/polkadot/xcm/src/v3/mod.rs +++ b/polkadot/xcm/src/v3/mod.rs @@ -69,6 +69,7 @@ pub type QueryId = u64; #[codec(encode_bound())] #[scale_info(bounds(), skip_type_params(Call))] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Xcm(pub Vec>); /// The maximal number of instructions in an XCM before decoding fails. @@ -232,15 +233,19 @@ pub mod prelude { } parameter_types! { + #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub MaxPalletNameLen: u32 = 48; /// Maximum size of the encoded error code coming from a `Dispatch` result, used for /// `MaybeErrorCode`. This is not (yet) enforced, so it's just an indication of expectation. + #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub MaxDispatchErrorLen: u32 = 128; + #[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub MaxPalletsInfo: u32 = 64; } #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct PalletInfo { #[codec(compact)] pub index: u32, @@ -272,6 +277,7 @@ impl PalletInfo { #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum MaybeErrorCode { Success, Error(BoundedVec), @@ -296,6 +302,7 @@ impl Default for MaybeErrorCode { /// Response data to a query. #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Response { /// No response. Serves as a neutral default. Null, @@ -320,6 +327,7 @@ impl Default for Response { /// Information regarding the composition of a query response. #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct QueryResponseInfo { /// The destination to which the query response message should be send. pub destination: MultiLocation, @@ -333,6 +341,7 @@ pub struct QueryResponseInfo { /// An optional weight limit. #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum WeightLimit { /// No weight limit imposed. Unlimited, @@ -417,6 +426,7 @@ impl XcmContext { #[codec(decode_bound())] #[scale_info(bounds(), skip_type_params(Call))] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Instruction { /// Withdraw asset(s) (`assets`) from the ownership of `origin` and place them into the Holding /// Register. diff --git a/polkadot/xcm/src/v3/multiasset.rs b/polkadot/xcm/src/v3/multiasset.rs index 454120a1a7b9c701423236004c36aea55a6425a7..c8801f5a461da249b44cf45746a2db72f80be5c2 100644 --- a/polkadot/xcm/src/v3/multiasset.rs +++ b/polkadot/xcm/src/v3/multiasset.rs @@ -47,6 +47,7 @@ use scale_info::TypeInfo; Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, )] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum AssetInstance { /// Undefined - used if the non-fungible asset class has only one instance. @@ -243,6 +244,7 @@ impl TryFrom for u128 { /// instance. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum Fungibility { /// A fungible asset; we record a number of units, as a `u128` in the inner item. @@ -313,6 +315,7 @@ impl TryFrom for Fungibility { Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, )] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum WildFungibility { /// The asset is fungible. @@ -337,6 +340,7 @@ impl TryFrom for WildFungibility { Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, )] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum AssetId { /// A specific location identifying an asset. @@ -412,6 +416,7 @@ impl AssetId { /// Either an amount of a single fungible asset, or a single well-identified non-fungible asset. #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub struct MultiAsset { /// The overall asset identity (aka *class*, in the case of a non-fungible). @@ -510,6 +515,7 @@ impl TryFrom for MultiAsset { /// - The number of items should grow no larger than `MAX_ITEMS_IN_MULTIASSETS`. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, TypeInfo, Default)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub struct MultiAssets(Vec); @@ -710,6 +716,7 @@ impl MultiAssets { /// A wildcard representing a set of assets. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum WildMultiAsset { /// All assets in Holding. @@ -823,6 +830,7 @@ impl, B: Into> From<(A, B)> for WildMultiAsset /// `MultiAsset` collection, defined either by a number of `MultiAssets` or a single wildcard. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] #[scale_info(replace_segment("staging_xcm", "xcm"))] pub enum MultiAssetFilter { /// Specify the filter as being everything contained by the given `MultiAssets` inner. diff --git a/polkadot/xcm/src/v3/multilocation.rs b/polkadot/xcm/src/v3/multilocation.rs index 89e2598444382c8bb4ec9a2ab62e19638e1441f7..9649b1b3207341dcbaaaa33c7b5f7715c3ca323c 100644 --- a/polkadot/xcm/src/v3/multilocation.rs +++ b/polkadot/xcm/src/v3/multilocation.rs @@ -66,6 +66,7 @@ use scale_info::TypeInfo; serde::Serialize, serde::Deserialize, )] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct MultiLocation { /// The number of parent junctions at the beginning of this `MultiLocation`. pub parents: u8, diff --git a/polkadot/xcm/src/v3/traits.rs b/polkadot/xcm/src/v3/traits.rs index 6054bf1456a5dedbb7409c598f22751fa95c9745..29bd40a6a2d8d1accf8c09e0bf7efded446a8ae1 100644 --- a/polkadot/xcm/src/v3/traits.rs +++ b/polkadot/xcm/src/v3/traits.rs @@ -30,6 +30,7 @@ use super::*; /// they will retain the same index over time. #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo)] #[scale_info(replace_segment("staging_xcm", "xcm"))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub enum Error { // Errors that happen due to instructions being executed. These alone are defined in the // XCM specification. diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 53743066720ca97a03d6f4eab5f79e608bc1c089..ff528d7d07522dc4a64120029609277c87bd1364 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true version = "1.0.0" +[lints] +workspace = true + [dependencies] impl-trait-for-tuples = "0.2.1" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/xcm/xcm-builder/src/currency_adapter.rs b/polkadot/xcm/xcm-builder/src/currency_adapter.rs index 8ecf1dee72db00695a0a48ca7b2f489ec6f8bf4c..68ca0111174f5e35d636c95174655e1b50003b19 100644 --- a/polkadot/xcm/xcm-builder/src/currency_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/currency_adapter.rs @@ -16,6 +16,8 @@ //! Adapters to work with `frame_support::traits::Currency` through XCM. +#![allow(deprecated)] + use super::MintLocation; use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons}; use sp_runtime::traits::CheckedSub; @@ -85,6 +87,7 @@ impl From for XcmError { /// CheckingAccount, /// >; /// ``` +#[deprecated = "Use `FungibleAdapter` instead"] pub struct CurrencyAdapter( PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>, ); @@ -113,7 +116,7 @@ impl< .map_err(|_| XcmError::NotWithdrawable) } fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) { - Currency::deposit_creating(&checked_account, amount); + let _ = Currency::deposit_creating(&checked_account, amount); Currency::deactivate(amount); } fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) { @@ -215,7 +218,7 @@ impl< let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?; let who = AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?; - Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath) + let _ = Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath) .map_err(|e| XcmError::FailedToTransactAsset(e.into()))?; Ok(what.clone().into()) } diff --git a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs new file mode 100644 index 0000000000000000000000000000000000000000..90608faa44778c37e2792e196b5a77b993634a3a --- /dev/null +++ b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs @@ -0,0 +1,317 @@ +// 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 . + +//! Adapters to work with [`frame_support::traits::fungible`] through XCM. + +use super::MintLocation; +use frame_support::traits::{ + tokens::{ + fungible, Fortitude::Polite, Precision::Exact, Preservation::Preserve, Provenance::Minted, + }, + Get, +}; +use sp_std::{marker::PhantomData, prelude::*, result}; +use xcm::latest::prelude::*; +use xcm_executor::traits::{ConvertLocation, Error as MatchError, MatchesFungible, TransactAsset}; + +/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for +/// handling an asset in the XCM executor. +/// Only works for transfers. +pub struct FungibleTransferAdapter( + PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId)>, +); +impl< + Fungible: fungible::Mutate, + Matcher: MatchesFungible, + AccountIdConverter: ConvertLocation, + AccountId: Eq + Clone, + > TransactAsset for FungibleTransferAdapter +{ + fn internal_transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + _context: &XcmContext, + ) -> result::Result { + log::trace!( + target: "xcm::fungible_adapter", + "internal_transfer_asset what: {:?}, from: {:?}, to: {:?}", + what, from, to + ); + // Check we handle the asset + let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; + let source = AccountIdConverter::convert_location(from) + .ok_or(MatchError::AccountIdConversionFailed)?; + let dest = AccountIdConverter::convert_location(to) + .ok_or(MatchError::AccountIdConversionFailed)?; + Fungible::transfer(&source, &dest, amount, Preserve) + .map_err(|error| XcmError::FailedToTransactAsset(error.into()))?; + Ok(what.clone().into()) + } +} + +/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for +/// handling an asset in the XCM executor. +/// Works for everything but transfers. +pub struct FungibleMutateAdapter( + PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>, +); + +impl< + Fungible: fungible::Mutate, + Matcher: MatchesFungible, + AccountIdConverter: ConvertLocation, + AccountId: Eq + Clone, + CheckingAccount: Get>, + > FungibleMutateAdapter +{ + fn can_accrue_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult { + Fungible::can_deposit(&checking_account, amount, Minted) + .into_result() + .map_err(|_| XcmError::NotDepositable) + } + + fn can_reduce_checked(checking_account: AccountId, amount: Fungible::Balance) -> XcmResult { + Fungible::can_withdraw(&checking_account, amount) + .into_result(false) + .map_err(|_| XcmError::NotWithdrawable) + .map(|_| ()) + } + + fn accrue_checked(checking_account: AccountId, amount: Fungible::Balance) { + let ok = Fungible::mint_into(&checking_account, amount).is_ok(); + debug_assert!(ok, "`can_accrue_checked` must have returned `true` immediately prior; qed"); + } + + fn reduce_checked(checking_account: AccountId, amount: Fungible::Balance) { + let ok = Fungible::burn_from(&checking_account, amount, Exact, Polite).is_ok(); + debug_assert!(ok, "`can_reduce_checked` must have returned `true` immediately prior; qed"); + } +} + +impl< + Fungible: fungible::Mutate, + Matcher: MatchesFungible, + AccountIdConverter: ConvertLocation, + AccountId: Eq + Clone, + CheckingAccount: Get>, + > TransactAsset + for FungibleMutateAdapter +{ + fn can_check_in( + _origin: &MultiLocation, + what: &MultiAsset, + _context: &XcmContext, + ) -> XcmResult { + log::trace!( + target: "xcm::fungible_adapter", + "can_check_in origin: {:?}, what: {:?}", + _origin, what + ); + // Check we handle this asset + let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; + match CheckingAccount::get() { + Some((checking_account, MintLocation::Local)) => + Self::can_reduce_checked(checking_account, amount), + Some((checking_account, MintLocation::NonLocal)) => + Self::can_accrue_checked(checking_account, amount), + None => Ok(()), + } + } + + fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + log::trace!( + target: "xcm::fungible_adapter", + "check_in origin: {:?}, what: {:?}", + _origin, what + ); + if let Some(amount) = Matcher::matches_fungible(what) { + match CheckingAccount::get() { + Some((checking_account, MintLocation::Local)) => + Self::reduce_checked(checking_account, amount), + Some((checking_account, MintLocation::NonLocal)) => + Self::accrue_checked(checking_account, amount), + None => (), + } + } + } + + fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> XcmResult { + log::trace!( + target: "xcm::fungible_adapter", + "check_out dest: {:?}, what: {:?}", + _dest, + what + ); + let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; + match CheckingAccount::get() { + Some((checking_account, MintLocation::Local)) => + Self::can_accrue_checked(checking_account, amount), + Some((checking_account, MintLocation::NonLocal)) => + Self::can_reduce_checked(checking_account, amount), + None => Ok(()), + } + } + + fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) { + log::trace!( + target: "xcm::fungible_adapter", + "check_out dest: {:?}, what: {:?}", + _dest, + what + ); + if let Some(amount) = Matcher::matches_fungible(what) { + match CheckingAccount::get() { + Some((checking_account, MintLocation::Local)) => + Self::accrue_checked(checking_account, amount), + Some((checking_account, MintLocation::NonLocal)) => + Self::reduce_checked(checking_account, amount), + None => (), + } + } + } + + fn deposit_asset( + what: &MultiAsset, + who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> XcmResult { + log::trace!( + target: "xcm::fungible_adapter", + "deposit_asset what: {:?}, who: {:?}", + what, who, + ); + let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + Fungible::mint_into(&who, amount) + .map_err(|error| XcmError::FailedToTransactAsset(error.into()))?; + Ok(()) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + _context: Option<&XcmContext>, + ) -> result::Result { + log::trace!( + target: "xcm::fungible_adapter", + "deposit_asset what: {:?}, who: {:?}", + what, who, + ); + let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; + let who = AccountIdConverter::convert_location(who) + .ok_or(MatchError::AccountIdConversionFailed)?; + Fungible::burn_from(&who, amount, Exact, Polite) + .map_err(|error| XcmError::FailedToTransactAsset(error.into()))?; + Ok(what.clone().into()) + } +} + +/// [`TransactAsset`] implementation that allows the use of a [`fungible`] implementation for +/// handling an asset in the XCM executor. +/// Works for everything, transfers and teleport bookkeeping. +pub struct FungibleAdapter( + PhantomData<(Fungible, Matcher, AccountIdConverter, AccountId, CheckingAccount)>, +); +impl< + Fungible: fungible::Mutate, + Matcher: MatchesFungible, + AccountIdConverter: ConvertLocation, + AccountId: Eq + Clone, + CheckingAccount: Get>, + > TransactAsset + for FungibleAdapter +{ + fn can_check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_in(origin, what, context) + } + + fn check_in(origin: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_in(origin, what, context) + } + + fn can_check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::can_check_out(dest, what, context) + } + + fn check_out(dest: &MultiLocation, what: &MultiAsset, context: &XcmContext) { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::check_out(dest, what, context) + } + + fn deposit_asset( + what: &MultiAsset, + who: &MultiLocation, + context: Option<&XcmContext>, + ) -> XcmResult { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::deposit_asset(what, who, context) + } + + fn withdraw_asset( + what: &MultiAsset, + who: &MultiLocation, + maybe_context: Option<&XcmContext>, + ) -> result::Result { + FungibleMutateAdapter::< + Fungible, + Matcher, + AccountIdConverter, + AccountId, + CheckingAccount, + >::withdraw_asset(what, who, maybe_context) + } + + fn internal_transfer_asset( + what: &MultiAsset, + from: &MultiLocation, + to: &MultiLocation, + context: &XcmContext, + ) -> result::Result { + FungibleTransferAdapter::::internal_transfer_asset( + what, from, to, context + ) + } +} diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 455f17a5348cd9b84acb7cd6fea9581babd903b2..e7431ae0254533aa2acd8ae7e274cedb549c0564 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -65,6 +65,7 @@ mod process_xcm_message; pub use process_xcm_message::ProcessXcmMessage; mod currency_adapter; +#[allow(deprecated)] pub use currency_adapter::CurrencyAdapter; mod fee_handling; @@ -72,6 +73,9 @@ pub use fee_handling::{ deposit_or_burn_fee, HandleFee, XcmFeeManagerFromComponents, XcmFeeToAccount, }; +mod fungible_adapter; +pub use fungible_adapter::{FungibleAdapter, FungibleMutateAdapter, FungibleTransferAdapter}; + mod fungibles_adapter; pub use fungibles_adapter::{ AssetChecking, DualMint, FungiblesAdapter, FungiblesMutateAdapter, FungiblesTransferAdapter, diff --git a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs index 330ff40aac0fe4bccf3317171f5af8fa50dfb931..7334bcd20109e6d9b29172786ffa0facb5980316 100644 --- a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs +++ b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs @@ -16,16 +16,15 @@ //! Implementation of `ProcessMessage` for an `ExecuteXcm` implementation. -use frame_support::{ - ensure, - traits::{ProcessMessage, ProcessMessageError}, -}; +use frame_support::traits::{ProcessMessage, ProcessMessageError}; use parity_scale_codec::{Decode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::{fmt::Debug, marker::PhantomData}; use sp_weights::{Weight, WeightMeter}; use xcm::prelude::*; +const LOG_TARGET: &str = "xcm::process-message"; + /// A message processor that delegates execution to an `XcmExecutor`. pub struct ProcessXcmMessage( PhantomData<(MessageOrigin, XcmExecutor, Call)>, @@ -45,21 +44,68 @@ impl< meter: &mut WeightMeter, id: &mut XcmHash, ) -> Result { - let versioned_message = VersionedXcm::::decode(&mut &message[..]) - .map_err(|_| ProcessMessageError::Corrupt)?; - let message = Xcm::::try_from(versioned_message) - .map_err(|_| ProcessMessageError::Unsupported)?; - let pre = XcmExecutor::prepare(message).map_err(|_| ProcessMessageError::Unsupported)?; + let versioned_message = VersionedXcm::::decode(&mut &message[..]).map_err(|e| { + log::trace!( + target: LOG_TARGET, + "`VersionedXcm` failed to decode: {e:?}", + ); + + ProcessMessageError::Corrupt + })?; + let message = Xcm::::try_from(versioned_message).map_err(|_| { + log::trace!( + target: LOG_TARGET, + "Failed to convert `VersionedXcm` into `XcmV3`.", + ); + + ProcessMessageError::Unsupported + })?; + let pre = XcmExecutor::prepare(message).map_err(|_| { + log::trace!( + target: LOG_TARGET, + "Failed to prepare message.", + ); + + ProcessMessageError::Unsupported + })?; // The worst-case weight: let required = pre.weight_of(); - ensure!(meter.can_consume(required), ProcessMessageError::Overweight(required)); + if !meter.can_consume(required) { + log::trace!( + target: LOG_TARGET, + "Xcm required {required} more than remaining {}", + meter.remaining(), + ); + + return Err(ProcessMessageError::Overweight(required)) + } let (consumed, result) = match XcmExecutor::execute(origin.into(), pre, id, Weight::zero()) { - Outcome::Complete(w) => (w, Ok(true)), - Outcome::Incomplete(w, _) => (w, Ok(false)), + Outcome::Complete(w) => { + log::trace!( + target: LOG_TARGET, + "XCM message execution complete, used weight: {w}", + ); + (w, Ok(true)) + }, + Outcome::Incomplete(w, e) => { + log::trace!( + target: LOG_TARGET, + "XCM message execution incomplete, used weight: {w}, error: {e:?}", + ); + + (w, Ok(false)) + }, // In the error-case we assume the worst case and consume all possible weight. - Outcome::Error(_) => (required, Err(ProcessMessageError::Unsupported)), + Outcome::Error(e) => { + log::trace!( + target: LOG_TARGET, + "XCM message execution error: {e:?}", + ); + + (required, Err(ProcessMessageError::Unsupported)) + }, }; meter.consume(consumed); result diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs index de08dbee953aa3265c584c87a65911ec015982b6..b1361cc85777e9e39fc337cc4b0fa0f619870654 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs @@ -23,11 +23,16 @@ use super::*; parameter_types! { pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); pub RemoteUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub RemoteNetwork: MultiLocation = AncestorThen(2, GlobalConsensus(Remote::get())).into(); } type TheBridge = TestBridge>; -type Router = - TestTopic, UniversalLocation>>; +type Router = TestTopic< + UnpaidLocalExporter< + HaulBlobExporter, + UniversalLocation, + >, +>; /// ```nocompile /// local | remote diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs index 8433b6e02129474403892f8dd2dbb10c82988078..5371abccf666b9d330343a6fd973e5c5fecc3186 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs @@ -23,11 +23,16 @@ use super::*; parameter_types! { pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub RemoteNetwork: MultiLocation = AncestorThen(1, GlobalConsensus(Remote::get())).into(); } type TheBridge = TestBridge>; -type Router = - TestTopic, UniversalLocation>>; +type Router = TestTopic< + UnpaidLocalExporter< + HaulBlobExporter, + UniversalLocation, + >, +>; /// ```nocompile /// local | remote diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs index 45630dbfc2484c23c14a1cbc283c5403a9f56d62..0c749b66da61e2250c7c4bc23a5ee1abc38f9dd2 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs @@ -20,6 +20,7 @@ use super::mock::*; use crate::{universal_exports::*, WithTopicSource}; use frame_support::{parameter_types, traits::Get}; use std::{cell::RefCell, marker::PhantomData}; +use xcm::AlwaysLatest; use xcm_executor::{ traits::{export_xcm, validate_export}, XcmExecutor, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs index 23d6eb99a90915ffa5b4c1af18c938e0735ba1ff..079eb0175d71f8fabcf9c94d3e5cd3098c3fc6f2 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/paid_remote_relay_relay.rs @@ -30,6 +30,7 @@ parameter_types! { pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(100)); pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub RemoteNetwork: MultiLocation = AncestorThen(1, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), @@ -41,7 +42,7 @@ parameter_types! { } type TheBridge = TestBridge>; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = ExecutingRouter; type LocalBridgeRouter = SovereignPaidRemoteExporter< NetworkExportTable, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs index f11143ab9f6fc9095bc6bdcc220e5742e86a9bcc..fb6c5da3eb010df30a34c86a1c1d279f5fea8be3 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para.rs @@ -24,6 +24,7 @@ parameter_types! { pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000)); pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub RemoteNetwork: MultiLocation = AncestorThen(2, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), @@ -36,7 +37,7 @@ parameter_types! { type TheBridge = TestBridge< BridgeBlobDispatcher, >; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = UnpaidExecutingRouter; type LocalBridgingRouter = diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs index 7218e0a04880fdb2a49ec470385e551f1ab884ac..0b6dc01e2bf1362b3729b8bbf800025221b56ab7 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_para_para_via_relay.rs @@ -24,6 +24,7 @@ parameter_types! { pub UniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); pub ParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1)); pub RemoteParaBridgeUniversalLocation: Junctions = X2(GlobalConsensus(Remote::get()), Parachain(1)); + pub RemoteNetwork: MultiLocation = AncestorThen(2, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), @@ -36,7 +37,7 @@ parameter_types! { type TheBridge = TestBridge< BridgeBlobDispatcher, >; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = UnpaidExecutingRouter; type LocalBridgingRouter = diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs index 45b5efbc44c549b9a68fbcfc968a6589854d74df..e33c7b15b0af8383742d52e4c44af392ea3ba01f 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/remote_relay_relay.rs @@ -24,6 +24,7 @@ parameter_types! { pub UniversalLocation: Junctions = X2(GlobalConsensus(Local::get()), Parachain(1000)); pub RelayUniversalLocation: Junctions = X1(GlobalConsensus(Local::get())); pub RemoteUniversalLocation: Junctions = X1(GlobalConsensus(Remote::get())); + pub RemoteNetwork: MultiLocation = AncestorThen(1, GlobalConsensus(Remote::get())).into(); pub BridgeTable: Vec = vec![ NetworkExportTableItem::new( Remote::get(), @@ -35,7 +36,7 @@ parameter_types! { } type TheBridge = TestBridge>; -type RelayExporter = HaulBlobExporter; +type RelayExporter = HaulBlobExporter; type LocalInnerRouter = UnpaidExecutingRouter; type LocalBridgeRouter = diff --git a/polkadot/xcm/xcm-builder/src/tests/mock.rs b/polkadot/xcm/xcm-builder/src/tests/mock.rs index 189274eb5f5b8034f5bf0aa5ccfc26700fb3aa95..c1b3f0cf224ea52a49d061ccd9214ac9035e9c8d 100644 --- a/polkadot/xcm/xcm-builder/src/tests/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/mock.rs @@ -27,20 +27,16 @@ pub use crate::{ }; use frame_support::traits::{ContainsPair, Everything}; pub use frame_support::{ - dispatch::{ - DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, Parameter, PostDispatchInfo, - }, + dispatch::{DispatchInfo, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo}, ensure, match_types, parameter_types, sp_runtime::{traits::Dispatchable, DispatchError, DispatchErrorWithPostInfo}, - traits::{ConstU32, Contains, Get, IsInVec}, + traits::{Contains, Get, IsInVec}, }; pub use parity_scale_codec::{Decode, Encode}; -pub use sp_io::hashing::blake2_256; pub use sp_std::{ cell::{Cell, RefCell}, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, fmt::Debug, - marker::PhantomData, }; pub use xcm::latest::{prelude::*, Weight}; use xcm_executor::traits::{Properties, QueryHandler, QueryResponseStatus}; @@ -52,6 +48,7 @@ pub use xcm_executor::{ Assets, Config, }; +#[derive(Debug)] pub enum TestOrigin { Root, Relay, diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 8e2cf88b3c32a1d25ca9d1c48ce2a8717de00c69..4aa6a0ef7a50fc9dadecd6e46bacf4957ea248e3 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -422,11 +422,25 @@ impl< } } -pub struct HaulBlobExporter( - PhantomData<(Bridge, BridgedNetwork, Price)>, +pub struct HaulBlobExporter( + PhantomData<(Bridge, BridgedNetwork, DestinationVersion, Price)>, ); -impl, Price: Get> ExportXcm - for HaulBlobExporter +/// `ExportXcm` implementation for `HaulBlobExporter`. +/// +/// # Type Parameters +/// +/// ```text +/// - Bridge: Implements `HaulBlob`. +/// - BridgedNetwork: The relative location of the bridged consensus system with the expected `GlobalConsensus` junction. +/// - DestinationVersion: Implements `GetVersion` for retrieving XCM version for the destination. +/// - Price: potential fees for exporting. +/// ``` +impl< + Bridge: HaulBlob, + BridgedNetwork: Get, + DestinationVersion: GetVersion, + Price: Get, + > ExportXcm for HaulBlobExporter { type Ticket = (Vec, XcmHash); @@ -437,17 +451,35 @@ impl, Price: Get> destination: &mut Option, message: &mut Option>, ) -> Result<((Vec, XcmHash), MultiAssets), SendError> { - let bridged_network = BridgedNetwork::get(); + let (bridged_network, bridged_network_location_parents) = { + let MultiLocation { parents, interior: mut junctions } = BridgedNetwork::get(); + match junctions.take_first() { + Some(GlobalConsensus(network)) => (network, parents), + _ => return Err(SendError::NotApplicable), + } + }; ensure!(&network == &bridged_network, SendError::NotApplicable); // We don't/can't use the `channel` for this adapter. let dest = destination.take().ok_or(SendError::MissingArgument)?; - let universal_dest = match dest.pushed_front_with(GlobalConsensus(bridged_network)) { - Ok(d) => d.into(), - Err((dest, _)) => { - *destination = Some(dest); - return Err(SendError::NotApplicable) - }, - }; + + // Let's resolve the known/supported XCM version for the destination because we don't know + // if it supports the same/latest version. + let (universal_dest, version) = + match dest.pushed_front_with(GlobalConsensus(bridged_network)) { + Ok(d) => { + let version = DestinationVersion::get_version_for(&MultiLocation::from( + AncestorThen(bridged_network_location_parents, d), + )) + .ok_or(SendError::DestinationUnsupported)?; + (d, version) + }, + Err((dest, _)) => { + *destination = Some(dest); + return Err(SendError::NotApplicable) + }, + }; + + // Let's adjust XCM with `UniversalOrigin`, `DescendOrigin` and`SetTopic`. let (local_net, local_sub) = universal_source .take() .ok_or(SendError::MissingArgument)? @@ -462,7 +494,17 @@ impl, Price: Get> if local_sub != Here { message.0.insert(1, DescendOrigin(local_sub)); } - let message = VersionedXcm::from(message); + + // We cannot use the latest `Versioned` because we don't know if the target chain already + // supports the same version. Therefore, we better control the destination version with best + // efforts. + let message = VersionedXcm::from(message) + .into_version(version) + .map_err(|()| SendError::DestinationUnsupported)?; + let universal_dest = VersionedInteriorMultiLocation::from(universal_dest) + .into_version(version) + .map_err(|()| SendError::DestinationUnsupported)?; + let id = maybe_id.unwrap_or_else(|| message.using_encoded(sp_io::hashing::blake2_256)); let blob = BridgeMessage { universal_dest, message }.encode(); Ok(((blob, id), Price::get())) diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 968b294c6a434e502139dd5c18f3b7d8fe213deb..6b4d893f73c7bf3688b19de4d8f5cb364b496316 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -32,12 +32,14 @@ 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, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, - IsChildSystemParachain, IsConcrete, MintLocation, RespectSuspension, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + FixedRateOfFungible, FixedWeightBounds, IsChildSystemParachain, IsConcrete, MintLocation, + RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + TakeWeightCredit, }; pub type AccountId = AccountId32; @@ -143,6 +145,7 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); +#[allow(deprecated)] pub type LocalCurrencyAdapter = XcmCurrencyAdapter< Balances, IsConcrete, diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index b435c2d510a99bc52e2eb31009bfec9cc25b52a3..32fa6669c0abc6b96e2cb50d201f87b749240306 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true version = "1.0.0" +[lints] +workspace = true + [dependencies] impl-trait-for-tuples = "0.2.2" environmental = { version = "1.1.4", default-features = false } diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index 0818d16a262ad98638c507924210aab08f3cfb9c..cafe12dc587f883a31ac3539aced38e8de29a89e 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -7,6 +7,9 @@ license.workspace = true version = "1.0.0" publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } frame-support = { path = "../../../../substrate/frame/support", default-features = false } diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index ac256ea14899c1a7305903b575a1178d6377b2b4..665b051c130606b2826ab1975e047c4621e133ed 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -24,7 +24,7 @@ use frame_support::{ use parity_scale_codec::{Decode, Encode}; use sp_core::defer; use sp_io::hashing::blake2_128; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use sp_weights::Weight; use xcm::latest::prelude::*; @@ -199,10 +199,7 @@ impl ExecuteXcm for XcmExecutor ExecuteXcm for XcmExecutor ExecuteXcm for XcmExecutor XcmExecutor { } } - #[cfg(feature = "runtime-benchmarks")] - pub fn bench_process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { - self.process(xcm) - } - - fn process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { - log::trace!( - target: "xcm::process", - "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", - self.origin_ref(), - self.total_surplus, - self.total_refunded, - self.error_handler_weight, - ); - let mut result = Ok(()); - for (i, instr) in xcm.0.into_iter().enumerate() { - match &mut result { - r @ Ok(()) => { - // Initialize the recursion count only the first time we hit this code in our - // potential recursive execution. - let inst_res = recursion_count::using_once(&mut 1, || { - recursion_count::with(|count| { - if *count > RECURSION_LIMIT { - return Err(XcmError::ExceedsStackLimit) - } - *count = count.saturating_add(1); - Ok(()) - }) - // This should always return `Some`, but let's play it safe. - .unwrap_or(Ok(()))?; - - // Ensure that we always decrement the counter whenever we finish processing - // the instruction. - defer! { - recursion_count::with(|count| { - *count = count.saturating_sub(1); - }); - } - - self.process_instruction(instr) - }); - if let Err(e) = inst_res { - log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); - *r = Err(ExecutorError { - index: i as u32, - xcm_error: e, - weight: Weight::zero(), - }); - } - }, - Err(ref mut error) => - if let Ok(x) = Config::Weigher::instr_weight(&instr) { - error.weight.saturating_accrue(x) - }, - } - } - result - } - /// Execute any final operations after having executed the XCM message. /// This includes refunding surplus weight, trapping extra holding funds, and returning any /// errors during execution. @@ -468,6 +403,154 @@ impl XcmExecutor { Ok(()) } + fn take_fee(&mut self, fee: MultiAssets, reason: FeeReason) -> XcmResult { + if Config::FeeManager::is_waived(self.origin_ref(), reason) { + return Ok(()) + } + log::trace!( + target: "xcm::fees", + "taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", + fee, + self.origin_ref(), + self.fees_mode, + reason, + ); + let paid = if self.fees_mode.jit_withdraw { + let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; + for asset in fee.inner() { + Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; + } + fee + } else { + self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() + }; + Config::FeeManager::handle_fee(paid, Some(&self.context), reason); + Ok(()) + } + + /// Calculates what `local_querier` would be from the perspective of `destination`. + fn to_querier( + local_querier: Option, + destination: &MultiLocation, + ) -> Result, XcmError> { + Ok(match local_querier { + None => None, + Some(q) => Some( + q.reanchored(&destination, Config::UniversalLocation::get()) + .map_err(|_| XcmError::ReanchorFailed)?, + ), + }) + } + + /// Send a bare `QueryResponse` message containing `response` informed by the given `info`. + /// + /// The `local_querier` argument is the querier (if any) specified from the *local* perspective. + fn respond( + &mut self, + local_querier: Option, + response: Response, + info: QueryResponseInfo, + fee_reason: FeeReason, + ) -> Result { + let querier = Self::to_querier(local_querier, &info.destination)?; + let QueryResponseInfo { destination, query_id, max_weight } = info; + let instruction = QueryResponse { query_id, response, max_weight, querier }; + let message = Xcm(vec![instruction]); + self.send(destination, message, fee_reason) + } + + fn try_reanchor( + asset: MultiAsset, + destination: &MultiLocation, + ) -> Result<(MultiAsset, InteriorMultiLocation), XcmError> { + let reanchor_context = Config::UniversalLocation::get(); + let asset = asset + .reanchored(&destination, reanchor_context) + .map_err(|()| XcmError::ReanchorFailed)?; + Ok((asset, reanchor_context)) + } + + fn try_reanchor_multilocation( + location: MultiLocation, + destination: &MultiLocation, + ) -> Result<(MultiLocation, InteriorMultiLocation), XcmError> { + let reanchor_context = Config::UniversalLocation::get(); + let location = location + .reanchored(&destination, reanchor_context) + .map_err(|_| XcmError::ReanchorFailed)?; + Ok((location, reanchor_context)) + } + + /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. + fn reanchored( + mut assets: Assets, + dest: &MultiLocation, + maybe_failed_bin: Option<&mut Assets>, + ) -> MultiAssets { + let reanchor_context = Config::UniversalLocation::get(); + assets.reanchor(dest, reanchor_context, maybe_failed_bin); + assets.into_assets_iter().collect::>().into() + } + + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + self.process(xcm) + } + + fn process(&mut self, xcm: Xcm) -> Result<(), ExecutorError> { + log::trace!( + target: "xcm::process", + "origin: {:?}, total_surplus/refunded: {:?}/{:?}, error_handler_weight: {:?}", + self.origin_ref(), + self.total_surplus, + self.total_refunded, + self.error_handler_weight, + ); + let mut result = Ok(()); + for (i, instr) in xcm.0.into_iter().enumerate() { + match &mut result { + r @ Ok(()) => { + // Initialize the recursion count only the first time we hit this code in our + // potential recursive execution. + let inst_res = recursion_count::using_once(&mut 1, || { + recursion_count::with(|count| { + if *count > RECURSION_LIMIT { + return Err(XcmError::ExceedsStackLimit) + } + *count = count.saturating_add(1); + Ok(()) + }) + // This should always return `Some`, but let's play it safe. + .unwrap_or(Ok(()))?; + + // Ensure that we always decrement the counter whenever we finish processing + // the instruction. + defer! { + recursion_count::with(|count| { + *count = count.saturating_sub(1); + }); + } + + self.process_instruction(instr) + }); + if let Err(e) = inst_res { + log::trace!(target: "xcm::execute", "!!! ERROR: {:?}", e); + *r = Err(ExecutorError { + index: i as u32, + xcm_error: e, + weight: Weight::zero(), + }); + } + }, + Err(ref mut error) => + if let Ok(x) = Config::Weigher::instr_weight(&instr) { + error.weight.saturating_accrue(x) + }, + } + } + result + } + /// Process a single XCM instruction, mutating the state of the XCM virtual machine. fn process_instruction( &mut self, @@ -551,22 +634,81 @@ impl XcmExecutor { }, Transact { origin_kind, require_weight_at_most, mut call } => { // We assume that the Relay-chain is allowed to use transact on this parachain. - let origin = *self.origin_ref().ok_or(XcmError::BadOrigin)?; + let origin = *self.origin_ref().ok_or_else(|| { + log::trace!( + target: "xcm::process_instruction::transact", + "No origin provided", + ); + + XcmError::BadOrigin + })?; // TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain - let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?; - ensure!(Config::SafeCallFilter::contains(&message_call), XcmError::NoPermission); + let message_call = call.take_decoded().map_err(|_| { + log::trace!( + target: "xcm::process_instruction::transact", + "Failed to decode call", + ); + + XcmError::FailedToDecode + })?; + + log::trace!( + target: "xcm::process_instruction::transact", + "Processing call: {message_call:?}", + ); + + if !Config::SafeCallFilter::contains(&message_call) { + log::trace!( + target: "xcm::process_instruction::transact", + "Call filtered by `SafeCallFilter`", + ); + + return Err(XcmError::NoPermission) + } + let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind) - .map_err(|_| XcmError::BadOrigin)?; + .map_err(|_| { + log::trace!( + target: "xcm::process_instruction::transact", + "Failed to convert origin {origin:?} and origin kind {origin_kind:?} to a local origin." + ); + + XcmError::BadOrigin + })?; + + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatching with origin: {dispatch_origin:?}", + ); + let weight = message_call.get_dispatch_info().weight; - ensure!(weight.all_lte(require_weight_at_most), XcmError::MaxWeightInvalid); + + if !weight.all_lte(require_weight_at_most) { + log::trace!( + target: "xcm::process_instruction::transact", + "Max {weight} bigger than require at most {require_weight_at_most}", + ); + + return Err(XcmError::MaxWeightInvalid) + } + let maybe_actual_weight = match Config::CallDispatcher::dispatch(message_call, dispatch_origin) { Ok(post_info) => { + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatch successful: {post_info:?}" + ); self.transact_status = MaybeErrorCode::Success; post_info.actual_weight }, Err(error_and_info) => { + log::trace!( + target: "xcm::process_instruction::transact", + "Dispatch failed {error_and_info:?}" + ); + self.transact_status = error_and_info.error.encode().into(); error_and_info.post_info.actual_weight }, @@ -946,93 +1088,4 @@ impl XcmExecutor { HrmpChannelClosing { .. } => Err(XcmError::Unimplemented), } } - - fn take_fee(&mut self, fee: MultiAssets, reason: FeeReason) -> XcmResult { - if Config::FeeManager::is_waived(self.origin_ref(), reason) { - return Ok(()) - } - log::trace!( - target: "xcm::fees", - "taking fee: {:?} from origin_ref: {:?} in fees_mode: {:?} for a reason: {:?}", - fee, - self.origin_ref(), - self.fees_mode, - reason, - ); - let paid = if self.fees_mode.jit_withdraw { - let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?; - for asset in fee.inner() { - Config::AssetTransactor::withdraw_asset(&asset, origin, Some(&self.context))?; - } - fee - } else { - self.holding.try_take(fee.into()).map_err(|_| XcmError::NotHoldingFees)?.into() - }; - Config::FeeManager::handle_fee(paid, Some(&self.context), reason); - Ok(()) - } - - /// Calculates what `local_querier` would be from the perspective of `destination`. - fn to_querier( - local_querier: Option, - destination: &MultiLocation, - ) -> Result, XcmError> { - Ok(match local_querier { - None => None, - Some(q) => Some( - q.reanchored(&destination, Config::UniversalLocation::get()) - .map_err(|_| XcmError::ReanchorFailed)?, - ), - }) - } - - /// Send a bare `QueryResponse` message containing `response` informed by the given `info`. - /// - /// The `local_querier` argument is the querier (if any) specified from the *local* perspective. - fn respond( - &mut self, - local_querier: Option, - response: Response, - info: QueryResponseInfo, - fee_reason: FeeReason, - ) -> Result { - let querier = Self::to_querier(local_querier, &info.destination)?; - let QueryResponseInfo { destination, query_id, max_weight } = info; - let instruction = QueryResponse { query_id, response, max_weight, querier }; - let message = Xcm(vec![instruction]); - self.send(destination, message, fee_reason) - } - - fn try_reanchor( - asset: MultiAsset, - destination: &MultiLocation, - ) -> Result<(MultiAsset, InteriorMultiLocation), XcmError> { - let reanchor_context = Config::UniversalLocation::get(); - let asset = asset - .reanchored(&destination, reanchor_context) - .map_err(|()| XcmError::ReanchorFailed)?; - Ok((asset, reanchor_context)) - } - - fn try_reanchor_multilocation( - location: MultiLocation, - destination: &MultiLocation, - ) -> Result<(MultiLocation, InteriorMultiLocation), XcmError> { - let reanchor_context = Config::UniversalLocation::get(); - let location = location - .reanchored(&destination, reanchor_context) - .map_err(|_| XcmError::ReanchorFailed)?; - Ok((location, reanchor_context)) - } - - /// NOTE: Any assets which were unable to be reanchored are introduced into `failed_bin`. - fn reanchored( - mut assets: Assets, - dest: &MultiLocation, - maybe_failed_bin: Option<&mut Assets>, - ) -> MultiAssets { - let reanchor_context = Config::UniversalLocation::get(); - assets.reanchor(dest, reanchor_context, maybe_failed_bin); - assets.into_assets_iter().collect::>().into() - } } diff --git a/polkadot/xcm/xcm-simulator/Cargo.toml b/polkadot/xcm/xcm-simulator/Cargo.toml index eedcfa0032af41d2cb69844c54048d8abb5137c9..051e9752f6e485333a6b3d0507f4360000c9a8de 100644 --- a/polkadot/xcm/xcm-simulator/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/Cargo.toml @@ -6,6 +6,9 @@ authors.workspace = true edition.workspace = true license.workspace = true +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } paste = "1.0.7" diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index f0caa5ab48ec821debdcac62f8de6a860caa1d2c..522b7855837008a77f202fe0b2cbd4d65cb2d3f6 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -6,6 +6,9 @@ edition.workspace = true license.workspace = true version = "1.0.0" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", features = ["derive"] } diff --git a/polkadot/xcm/xcm-simulator/example/src/parachain.rs b/polkadot/xcm/xcm-simulator/example/src/parachain.rs index 951e946372da715ebdfe3a53079d287604ea1a7c..69db81deff4fa568d8eb4a83814d07bbb6ee5aaf 100644 --- a/polkadot/xcm/xcm-simulator/example/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/example/src/parachain.rs @@ -40,10 +40,9 @@ use polkadot_parachain_primitives::primitives::{ use xcm::{latest::prelude::*, VersionedXcm}; use xcm_builder::{ Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, ConvertedConcreteId, - CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - IsConcrete, NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, + EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsConcrete, + NativeAsset, NoChecking, NonFungiblesAdapter, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{ traits::{ConvertLocation, JustTry}, @@ -165,7 +164,7 @@ impl EnsureOriginWithArg for ForeignCreators { #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin(a: &MultiLocation) -> Result { - Ok(pallet_xcm::Origin::Xcm(a.clone()).into()) + Ok(pallet_xcm::Origin::Xcm(*a).into()) } } @@ -202,7 +201,7 @@ parameter_types! { } pub type LocalAssetTransactor = ( - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + FungibleAdapter, LocationToAccountId, AccountId, ()>, NonFungiblesAdapter< ForeignUniques, ConvertedConcreteId, diff --git a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs index 20070d192b5455e67089609acb29295a02080a0f..24fc56eb7174ba7d8a35ca170b7994c884cf38f6 100644 --- a/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/example/src/relay_chain.rs @@ -36,9 +36,9 @@ use xcm::latest::prelude::*; use xcm_builder::{ Account32Hash, AccountId32Aliases, AllowUnpaidExecutionFrom, AsPrefixedGeneralIndex, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - ConvertedConcreteId, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, - FixedWeightBounds, IsConcrete, NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + ConvertedConcreteId, FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsConcrete, + NoChecking, NonFungiblesAdapter, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -141,7 +141,7 @@ pub type LocationToAccountId = ( ); pub type LocalAssetTransactor = ( - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>, + FungibleAdapter, LocationToAccountId, AccountId, ()>, NonFungiblesAdapter< Uniques, ConvertedConcreteId, JustTry>, diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index acf28bec4f19480ae8135cb4e7f1845fc8d76566..1d13c76f17103ed84346d15b7495665fbdbd46d7 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true license.workspace = true publish = false +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } honggfuzz = "0.5.55" diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index 0aa1b54f5e7147fc1a9eee39f08fb83e0b5140b0..2262d18e86044990c1553d938833e08b77e74047 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -37,11 +37,12 @@ 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, CurrencyAdapter as XcmCurrencyAdapter, - EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, IsConcrete, NativeAsset, - ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, + AccountId32Aliases, AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, + FixedWeightBounds, IsConcrete, NativeAsset, ParentIsPreset, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -132,6 +133,7 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } +#[allow(deprecated)] pub type LocalAssetTransactor = XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 085773f30737ba76f4740361d0ae867648dcd83f..bbf4f1e6cc5b10a9eb2d3723005bbbe25e324fb4 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -33,11 +33,13 @@ 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, - CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, + ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, FixedRateOfFungible, + FixedWeightBounds, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; @@ -114,6 +116,7 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); +#[allow(deprecated)] pub type LocalAssetTransactor = XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; diff --git a/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl b/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl index 135999a092a7a54a212d3a82eb2b58ba92aa955e..3e1d8ba771c43a69c917b5357afe36f1ebcfb6f6 100644 --- a/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl +++ b/polkadot/zombienet_tests/functional/0001-parachains-pvf.zndsl @@ -32,6 +32,8 @@ alice: parachain 2005 block height is at least 10 within 300 seconds alice: parachain 2006 block height is at least 10 within 300 seconds alice: parachain 2007 block height is at least 10 within 300 seconds +alice: reports substrate_block_height{status="finalized"} is at least 30 within 400 seconds + # Check preparation time is under 10s. # Check all buckets <= 10. alice: reports histogram polkadot_pvf_preparation_time has at least 1 samples in buckets ["0.1", "0.5", "1", "2", "3", "10"] within 10 seconds diff --git a/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml b/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml index e70322e13e6bc4a1eabb477f22afff0cc8ed2afb..27cd81dface5ec21ba9650167a1cca62dc8d1489 100644 --- a/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml +++ b/polkadot/zombienet_tests/functional/0002-parachains-disputes.toml @@ -5,6 +5,10 @@ timeout = 1000 max_validators_per_core = 5 needed_approvals = 8 +[relaychain.genesis.runtime.runtime_genesis_config.configuration.config.approval_voting_params] + max_approval_coalesce_count = 5 + + [relaychain] default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" chain = "rococo-local" diff --git a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl index a3f1f0669ac9e85cea978ac2b62991cb3a092297..d92820391d53d85fb549878e621000829f570452 100644 --- a/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl +++ b/polkadot/zombienet_tests/functional/0005-parachains-disputes-past-session.zndsl @@ -32,6 +32,10 @@ honest-flaky-validator-1: reports parachain_candidate_disputes_total is at least honest-flaky-validator-1: pause # Wait for 1 full session to pass after the last unconcluded dispute. +# +# TODO: replace with assertion for "New session detected" in logs. I think that +# would match on previous log lines, so we may need to programmatically wait for +# a specific session, requiring zombienet v2. sleep 110 seconds # Now resume flaky validators diff --git a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml new file mode 100644 index 0000000000000000000000000000000000000000..19c7015403d7d86b3ece2e7006995e86fc9c0ab7 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.toml @@ -0,0 +1,115 @@ +[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 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + max_approval_coalesce_count = 5 + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "alice" + args = [ "-lparachain=trace,runtime=debug" ] + count = 13 + +[[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 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=10" + + [parachains.collator] + name = "collator02" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2001", "--pvf-complexity=10"] + +[[parachains]] +id = 2002 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=100" + + [parachains.collator] + name = "collator03" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2002", "--pvf-complexity=100"] + +[[parachains]] +id = 2003 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=300" + + [parachains.collator] + name = "collator04" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--parachain-id=2003", "--pvf-complexity=300"] + +[[parachains]] +id = 2004 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator05" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--parachain-id=2004", "--pvf-complexity=300"] + +[[parachains]] +id = 2005 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=20000 --pvf-complexity=400" + + [parachains.collator] + name = "collator06" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=20000", "--pvf-complexity=400", "--parachain-id=2005"] + +[[parachains]] +id = 2006 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator07" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2006"] + +[[parachains]] +id = 2007 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=300" + + [parachains.collator] + name = "collator08" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=300", "--parachain-id=2007"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.zndsl b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..1fc4f678446008d62e94b221fe1ad7344216a49a --- /dev/null +++ b/polkadot/zombienet_tests/functional/0009-approval-voting-coalescing.zndsl @@ -0,0 +1,32 @@ +Description: Approval voting coalescing does not lag finality +Network: ./0009-approval-voting-coalescing.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 +alice: parachain 2002 is registered within 60 seconds +alice: parachain 2003 is registered within 60 seconds +alice: parachain 2004 is registered within 60 seconds +alice: parachain 2005 is registered within 60 seconds +alice: parachain 2006 is registered within 60 seconds +alice: parachain 2007 is registered within 60 seconds + +# Ensure parachains made progress. +alice: parachain 2000 block height is at least 10 within 300 seconds +alice: parachain 2001 block height is at least 10 within 300 seconds +alice: parachain 2002 block height is at least 10 within 300 seconds +alice: parachain 2003 block height is at least 10 within 300 seconds +alice: parachain 2004 block height is at least 10 within 300 seconds +alice: parachain 2005 block height is at least 10 within 300 seconds +alice: parachain 2006 block height is at least 10 within 300 seconds +alice: parachain 2007 block height is at least 10 within 300 seconds + +alice: reports substrate_block_height{status="finalized"} is at least 30 within 400 seconds + +alice: reports polkadot_parachain_approval_checking_finality_lag < 3 + +alice: reports polkadot_parachain_approvals_no_shows_total < 3 within 10 seconds diff --git a/polkadot/zombienet_tests/misc/0002-upgrade-node.toml b/polkadot/zombienet_tests/misc/0002-upgrade-node.toml index b6fff6c8cb834ce97724cfd11df3ec761cac1f3e..1edb18abcececa32cadcf3756ac11e66be5f12c6 100644 --- a/polkadot/zombienet_tests/misc/0002-upgrade-node.toml +++ b/polkadot/zombienet_tests/misc/0002-upgrade-node.toml @@ -9,12 +9,10 @@ chain = "rococo-local" [[relaychain.nodes]] name = "alice" args = [ "-lparachain=debug,runtime=debug", "--db paritydb" ] - substrate_cli_args_version = 1 [[relaychain.nodes]] name = "bob" args = [ "-lparachain=debug,runtime=debug", "--db rocksdb" ] - substrate_cli_args_version = 1 [[relaychain.nodes]] name = "charlie" diff --git a/polkadot/zombienet_tests/smoke/0004-configure-broker.js b/polkadot/zombienet_tests/smoke/0004-configure-broker.js new file mode 100644 index 0000000000000000000000000000000000000000..a4939ffe1cb83dea531a362a0015e0b3d7ddb9df --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-broker.js @@ -0,0 +1,66 @@ +const assert = require("assert"); + +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"); + + const calls = [ + // Default broker configuration + api.tx.broker.configure({ + advanceNotice: 2, + interludeLength: 1, + leadinLength: 1, + regionLength: 3, + idealBulkProportion: 100, + limitCoresOffered: null, + renewalBump: 10, + contributionTimeout: 5, + }), + // Make reservation for ParaId 100 (adder-a) every other block + // and ParaId 101 (adder-b) every other block. + api.tx.broker.reserve([ + { + mask: [255, 0, 255, 0, 255, 0, 255, 0, 255, 0], + assignment: { Task: 100 }, + }, + { + mask: [0, 255, 0, 255, 0, 255, 0, 255, 0, 255], + assignment: { Task: 101 }, + }, + ]), + // Start sale with 1 core starting at 1 planck + api.tx.broker.startSales(1, 1), + ]; + const sudo_batch = api.tx.sudo.sudo(api.tx.utility.batch(calls)); + + await new Promise(async (resolve, reject) => { + const unsub = await sudo_batch.signAndSend(alice, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + return resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + unsub(); + return reject(); + } + }); + }); + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/smoke/0004-configure-relay.js b/polkadot/zombienet_tests/smoke/0004-configure-relay.js new file mode 100644 index 0000000000000000000000000000000000000000..9ca23d86a561709bc6a0a4082f25ed7faa450048 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-configure-relay.js @@ -0,0 +1,43 @@ +const assert = require("assert"); + +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"); + + const calls = [ + api.tx.configuration.setCoretimeCores({ new: 1 }), + api.tx.coretime.assignCore(0, 20,[[ { task: 1005 }, 57600 ]], null) + ]; + const sudo_batch = api.tx.sudo.sudo(api.tx.utility.batch(calls)); + + await new Promise(async (resolve, reject) => { + const unsub = await sudo_batch.signAndSend(alice, (result) => { + console.log(`Current status is ${result.status}`); + if (result.status.isInBlock) { + console.log( + `Transaction included at blockHash ${result.status.asInBlock}` + ); + } else if (result.status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${result.status.asFinalized}` + ); + unsub(); + return resolve(); + } else if (result.isError) { + console.log(`Transaction Error`); + unsub(); + return reject(); + } + }); + }); + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml new file mode 100644 index 0000000000000000000000000000000000000000..3bcd0bee3c71b2c2ca369c1029af6f3024367cdf --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.toml @@ -0,0 +1,58 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +command = "polkadot" + + [[relaychain.nodes]] + name = "alice" + args = ["-lruntime=debug,parachain=trace" ] + + [[relaychain.nodes]] + name = "bob" + args = ["-lruntime=debug,parachain=trace" ] + + [[relaychain.nodes]] + name = "charlie" + args = ["-lruntime=debug,parachain=trace" ] + +[[parachains]] +id = 1005 +chain = "coretime-rococo-local" + + [parachains.collator] + name = "coretime-collator" + image = "{{COL_IMAGE}}" + command = "polkadot-parachain" + args = [ "-lruntime=debug,parachain=trace" ] + +[[parachains]] +id = 100 +add_to_genesis = false +register_para = true +onboard_as_parachain = false + + [parachains.collator] + name = "adder-a" + image = "{{COL_IMAGE}}" + command = "adder-collator" + args = [ "-lruntime=debug,parachain=trace" ] + +[[parachains]] +id = 101 +add_to_genesis = false +register_para = true +onboard_as_parachain = false + + [parachains.collator] + name = "adder-b" + image = "{{COL_IMAGE}}" + command = "adder-collator" + args = [ "-lruntime=debug,parachain=trace" ] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" diff --git a/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..45e000e0bf8513a7284b1d8082ca5a042d6aa6f0 --- /dev/null +++ b/polkadot/zombienet_tests/smoke/0004-coretime-smoke-test.zndsl @@ -0,0 +1,19 @@ +Description: Bulk core assignment Smoke +Network: ./0004-coretime-smoke-test.toml +Creds: config + +alice: is up +coretime-collator: is up + +alice: reports block height is at least 3 within 30 seconds +# configure relay chain +alice: js-script ./0004-configure-relay.js with "" return is 0 within 600 secs + +# Wait 2 sessions. The parachain doesn't start block production immediately. +alice: log line contains "New session detected session_index=2" within 600 seconds + +# configure broker chain +coretime-collator: js-script ./0004-configure-broker.js with "" return is 0 within 600 secs + +# TODO: Fix this +# alice: parachain 100 block height is at least 10 within 600 seconds diff --git a/prdoc/pr_1234.prdoc b/prdoc/1.3.0/pr_1234.prdoc similarity index 100% rename from prdoc/pr_1234.prdoc rename to prdoc/1.3.0/pr_1234.prdoc diff --git a/prdoc/pr_1255.prdoc b/prdoc/1.3.0/pr_1255.prdoc similarity index 100% rename from prdoc/pr_1255.prdoc rename to prdoc/1.3.0/pr_1255.prdoc diff --git a/prdoc/pr_1818.prdoc b/prdoc/1.3.0/pr_1818.prdoc similarity index 100% rename from prdoc/pr_1818.prdoc rename to prdoc/1.3.0/pr_1818.prdoc diff --git a/prdoc/pr_1873.prdoc b/prdoc/1.3.0/pr_1873.prdoc similarity index 100% rename from prdoc/pr_1873.prdoc rename to prdoc/1.3.0/pr_1873.prdoc diff --git a/prdoc/pr_1913.prdoc b/prdoc/1.3.0/pr_1913.prdoc similarity index 100% rename from prdoc/pr_1913.prdoc rename to prdoc/1.3.0/pr_1913.prdoc diff --git a/prdoc/pr_1921.prdoc b/prdoc/1.3.0/pr_1921.prdoc similarity index 100% rename from prdoc/pr_1921.prdoc rename to prdoc/1.3.0/pr_1921.prdoc diff --git a/prdoc/1.3.0/readme.md b/prdoc/1.3.0/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..3d74fa34247c2708fdbb10c4e49c83e4176f7138 --- /dev/null +++ b/prdoc/1.3.0/readme.md @@ -0,0 +1,2 @@ +Version 1.3.0 does not support `prddoc` yet. +Some prdoc files are provided but the list is NOT complete. diff --git a/prdoc/pr_1178.prdoc b/prdoc/1.4.0/pr_1178.prdoc similarity index 96% rename from prdoc/pr_1178.prdoc rename to prdoc/1.4.0/pr_1178.prdoc index 36c3b05c7a3f0166e606fe2c2ce1c8801ee0a0c2..528e523c4328c3f18551ea4ce3d6c0955dd5a63a 100644 --- a/prdoc/pr_1178.prdoc +++ b/prdoc/1.4.0/pr_1178.prdoc @@ -6,9 +6,9 @@ doc: Changed approval-voting, approval-distribution to send all messages tranche0 assignments in one message. This required: * A new parachains_db version. - * A new validation protocol to support the new message types. + * A new validation protocol to support the new message types. The new logic will be disabled and will be enabled at a later date after all validators have upgraded. - + migrations: db: - name: Parachains database change from v3 to v4. @@ -18,6 +18,5 @@ migrations: crates: - name: "polkadot" - semver: patch host_functions: [] diff --git a/prdoc/pr_1246.prdoc b/prdoc/1.4.0/pr_1246.prdoc similarity index 87% rename from prdoc/pr_1246.prdoc rename to prdoc/1.4.0/pr_1246.prdoc index f9c867812331bfdd18f7c14a38a1dcc735cd2c9c..a4d270c45cb5915760ddfbd60e5e4b3b7c08cd4a 100644 --- a/prdoc/pr_1246.prdoc +++ b/prdoc/1.4.0/pr_1246.prdoc @@ -1,17 +1,17 @@ title: Use the `Message Queue` Pallet for DMP and XCMP dispatch queueing doc: - - audience: Parachain Dev + - audience: Runtime Dev description: Replaces the queueing capabilities of the `DMP and `XCMP-Queue` pallet for incoming messages with the `MessageQueue` pallet. This simplifies the code and improves security. migrations: runtime: - - pallet: "cumulus_pallet_dmp_queue" + reference: cumulus_pallet_dmp_queue 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/pr_1256.prdoc b/prdoc/1.4.0/pr_1256.prdoc similarity index 100% rename from prdoc/pr_1256.prdoc rename to prdoc/1.4.0/pr_1256.prdoc diff --git a/prdoc/pr_1805.prdoc b/prdoc/1.4.0/pr_1805.prdoc similarity index 87% rename from prdoc/pr_1805.prdoc rename to prdoc/1.4.0/pr_1805.prdoc index 8a8e6c2fde2665eca306ff3adc8a2e701cc29a7d..30f0fbea3070f1e819df2f12f866d6a6f5a2a78f 100644 --- a/prdoc/pr_1805.prdoc +++ b/prdoc/1.4.0/pr_1805.prdoc @@ -1,7 +1,7 @@ title: Introduce state decoding check after runtime upgrades. doc: - - audience: Core Dev + - audience: Runtime Dev description: | Adds a check to the try-runtime logic that will verify that all pallet on-chain storage still decodes. This can help to spot missing migrations before they become a problem. The check is enabled as soon as the `--checks` option of the `try-runtime` CLI is not `None`. @@ -10,10 +10,8 @@ migrations: runtime: [] -crates: +crates: - name: frame-support - semver: minor - name: frame-support-procedural - semver: minor host_functions: [] diff --git a/prdoc/pr_1926.prdoc b/prdoc/1.4.0/pr_1926.prdoc similarity index 59% rename from prdoc/pr_1926.prdoc rename to prdoc/1.4.0/pr_1926.prdoc index 9dc656f1260d068b3632b381302e8157e2c47b09..e7c4293d9fd452017af8e1fe9cd4e623558a8d88 100644 --- a/prdoc/pr_1926.prdoc +++ b/prdoc/1.4.0/pr_1926.prdoc @@ -1,10 +1,10 @@ title: Adds syntax for marking calls feeless doc: - - audience: Core Dev + - audience: Runtime Dev description: | 1. Adds an attribute `#[pallet::feeless_if]` that can be optionally attached to a `pallet::call`. - 2. Adds a signed extension SkipCheckIfFeeless that wraps a transaction + 2. Adds a signed extension SkipCheckIfFeeless that wraps a transaction payment processor to potentially skip payment fees for such calls. Note that both the attribute and the signed extension are needed to make the call feeless. @@ -14,17 +14,11 @@ migrations: runtime: [] crates: - - name: "frame-support-procedural" - semver: minor - - name: "pallet-skip-feeless-payment" - semver: major - - pallet-example-kitchensink - semver: patch - - kitchensink-runtime - semver: major - - node-testing - semver: patch - - node-cli - semver: patch + - name: frame-support-procedural + - name: pallet-skip-feeless-payment + - name: pallet-example-kitchensink + - name: kitchensink-runtime + - name: node-testing + - name: node-cli host_functions: [] diff --git a/prdoc/pr_2086.prdoc b/prdoc/1.4.0/pr_2086.prdoc similarity index 70% rename from prdoc/pr_2086.prdoc rename to prdoc/1.4.0/pr_2086.prdoc index a9bbd0729d5b08b8b2d482ba062ece9e443458a3..3bd568cc139883d70fe7556c18b017f1fc12965a 100644 --- a/prdoc/pr_2086.prdoc +++ b/prdoc/1.4.0/pr_2086.prdoc @@ -1,15 +1,12 @@ title: "Contracts: Add XCM traits to interface with contracts" doc: - - audience: Core Dev + - audience: Runtime Dev description: | We are introducing a new set of `XcmController` traits in `pallet-xcm`. - These traits extract functionality from `pallet-xcm` and provide high-level interaction with XCM. + These traits extract functionality from `pallet-xcm` and provide high-level interaction with XCM. They enable other pallets, like `pallet_contracts`, to rely on these traits instead of tight coupling to `pallet-xcm` itself. crates: - - name: "pallet-xcm" - semver: patch - - name: "xcm-executor" - semver: patch - + - name: pallet-xcm + - name: xcm-executor diff --git a/prdoc/pr_2107.prdoc b/prdoc/1.4.0/pr_2107.prdoc similarity index 91% rename from prdoc/pr_2107.prdoc rename to prdoc/1.4.0/pr_2107.prdoc index 0e33680555ace33797db4dcc73f3d003eb1d5a23..be71828cbad5cf76e4ab9c1a5c9d11e2aa61a48c 100644 --- a/prdoc/pr_2107.prdoc +++ b/prdoc/1.4.0/pr_2107.prdoc @@ -4,14 +4,16 @@ title: Add a builder pattern to create XCM programs doc: - - audience: Core Dev + - audience: Runtime Dev description: | XCMs can now be built using a builder pattern like so: + ``` Xcm::builder() .withdraw_asset(assets) .buy_execution(fees, weight_limit) .deposit_asset(assets, beneficiary) .build(); + ``` migrations: db: [] diff --git a/prdoc/pr_2165.prdoc b/prdoc/1.4.0/pr_2165.prdoc similarity index 80% rename from prdoc/pr_2165.prdoc rename to prdoc/1.4.0/pr_2165.prdoc index 31cb691c43aabb58c81de10ca91abedaeaae0bb0..3b10bcfe6a5210bf9dc60ce1d1edf6b653e71b82 100644 --- a/prdoc/pr_2165.prdoc +++ b/prdoc/1.4.0/pr_2165.prdoc @@ -1,7 +1,7 @@ -title: Add sudo::remove_key +title: Add `sudo::remove_key` doc: - - audience: Core Dev + - audience: Runtime User description: | Pallet `Sudo` now has the ability to remove the sudo key via `remove_key`. This is a less-invasive way of rendering the sudo pallet useless without needing a code upgrade. @@ -12,6 +12,5 @@ migrations: crates: - name: pallet-sudo - semver: minor host_functions: [] diff --git a/prdoc/1.4.0/readme.md b/prdoc/1.4.0/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..e1a1055d9185eae47dff4c39269aa14f0e0e0d07 --- /dev/null +++ b/prdoc/1.4.0/readme.md @@ -0,0 +1,2 @@ +Version 1.4.0 does not support `prddoc` yet. +Some prdoc files are provided but the list is NOT complete. diff --git a/prdoc/1.5.0/pr_1370_special.prdoc b/prdoc/1.5.0/pr_1370_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..692a6e03170bfeafcfccdd82edab22e5d44cf905 --- /dev/null +++ b/prdoc/1.5.0/pr_1370_special.prdoc @@ -0,0 +1,9 @@ +title: Rework the event system of `sc-network` +author: altonen +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/pr_1408_prodc-introduction.prdoc b/prdoc/1.5.0/pr_1408_prodc-introduction.prdoc similarity index 89% rename from prdoc/pr_1408_prodc-introduction.prdoc rename to prdoc/1.5.0/pr_1408_prodc-introduction.prdoc index 85b4661b127ecb102c0dfa0fb71d9181f269002f..46f56068e271b6ee1d2810c0434ad8d7f363eb9e 100644 --- a/prdoc/pr_1408_prodc-introduction.prdoc +++ b/prdoc/1.5.0/pr_1408_prodc-introduction.prdoc @@ -1,6 +1,9 @@ # This PR does not need a prdoc but it is provided in order to test title: PRdoc check +author: chevdor +topic: documentation + doc: - audience: Node Dev description: | diff --git a/prdoc/1.5.0/pr_1497_special.prdoc b/prdoc/1.5.0/pr_1497_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3d60354826073a7888d56fff3a5db82605d9af04 --- /dev/null +++ b/prdoc/1.5.0/pr_1497_special.prdoc @@ -0,0 +1,9 @@ +title: Update tick collator for async backing +author: Sophia-Gold +topic: Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_1918_special.prdoc b/prdoc/1.5.0/pr_1918_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9220ee970bcb8681106f991ca543bdff46abdf83 --- /dev/null +++ b/prdoc/1.5.0/pr_1918_special.prdoc @@ -0,0 +1,9 @@ +title: Preserve artifact cache unless stale +author: eagr +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/pr_1946_prdoc_new_schema.prdoc b/prdoc/1.5.0/pr_1946_prdoc_new_schema.prdoc similarity index 90% rename from prdoc/pr_1946_prdoc_new_schema.prdoc rename to prdoc/1.5.0/pr_1946_prdoc_new_schema.prdoc index c06321777382364d64f96b7917748268da73fad7..fae063f6b1ecd349b9d252d6014a52bdd424051a 100644 --- a/prdoc/pr_1946_prdoc_new_schema.prdoc +++ b/prdoc/1.5.0/pr_1946_prdoc_new_schema.prdoc @@ -3,6 +3,9 @@ title: New PRDoc Schema +author: chevdor +topic: documentation + doc: - audience: Node Dev description: &desc | diff --git a/prdoc/1.5.0/pr_1985_special.prdoc b/prdoc/1.5.0/pr_1985_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c4305d6bb295b74ce182f732d160dbccf7eeed87 --- /dev/null +++ b/prdoc/1.5.0/pr_1985_special.prdoc @@ -0,0 +1,9 @@ +title: Enable parallel key scraping +author: eagr +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2001_special.prdoc b/prdoc/1.5.0/pr_2001_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..366b5fddb8b8d3e6f72d756eb5d58c69c7997cfc --- /dev/null +++ b/prdoc/1.5.0/pr_2001_special.prdoc @@ -0,0 +1,9 @@ +title: "cumulus-consensus-common: block import: `delayed_best_block` flag added" +author: michalkucharczyk +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2058_special.prdoc b/prdoc/1.5.0/pr_2058_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6e3c83b09fa1b80469b16eb8d504f9ec0846f08f --- /dev/null +++ b/prdoc/1.5.0/pr_2058_special.prdoc @@ -0,0 +1,9 @@ +title: "PVF: Add test instructions" +author: mrcnski +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/pr_2142.prdoc b/prdoc/1.5.0/pr_2142.prdoc similarity index 81% rename from prdoc/pr_2142.prdoc rename to prdoc/1.5.0/pr_2142.prdoc index ae2ac3db9ddc9627ee14fb23e2d6374934be982c..9cd1b23906d05734adddbb4cad7f146ba52481da 100644 --- a/prdoc/pr_2142.prdoc +++ b/prdoc/1.5.0/pr_2142.prdoc @@ -1,12 +1,15 @@ title: Cleanup XCMP `QueueConfigData` +author: serban300 +topic: runtime + doc: - - audience: Parachain Dev + - audience: Runtime Dev description: Removes obsolete fields from the `QueueConfigData` structure. For the remaining fields, if they use the old defaults, we replace them with the new defaults. migrations: runtime: - - pallet: "cumulus_pallet_xcmp_queue" + - reference: cumulus_pallet_xcmp_queue description: "v4: Removes obsolete fields from the `QueueConfigData` structure. For the remaining fields, if they use the old defaults, we replace them with the new defaults." crates: [] diff --git a/prdoc/1.5.0/pr_2167_special.prdoc b/prdoc/1.5.0/pr_2167_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7bbde7002a2a8ef4f17d70de68acedc16ecf3648 --- /dev/null +++ b/prdoc/1.5.0/pr_2167_special.prdoc @@ -0,0 +1,9 @@ +title: "add pallet nomination-pools versioned migration to kitchensink" +author: brunopgalvao +topic: Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2174_special.prdoc b/prdoc/1.5.0/pr_2174_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f23d2803e962c38f248c33a4e58834f7abc97e7f --- /dev/null +++ b/prdoc/1.5.0/pr_2174_special.prdoc @@ -0,0 +1,9 @@ +title: "chain-spec-builder: cleanup" +author: michalkucharczyk +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2182_special.prdoc b/prdoc/1.5.0/pr_2182_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ad57bf6491634f7531ba1ee0da339bcecbcb0286 --- /dev/null +++ b/prdoc/1.5.0/pr_2182_special.prdoc @@ -0,0 +1,9 @@ +title: "remove retry from backers on failed candidate validation" +author: Jpserrat +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2184_special.prdoc b/prdoc/1.5.0/pr_2184_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b838bf41ba1570e03ed78411390d580ea8a66ddf --- /dev/null +++ b/prdoc/1.5.0/pr_2184_special.prdoc @@ -0,0 +1,9 @@ +title: Zombienet tests - disputes on finalized blocks +author: Overkillus +topic: Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2221_special.prdoc b/prdoc/1.5.0/pr_2221_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dbd8c4a1fc14df1faf80f6d03336ab1d48ad243d --- /dev/null +++ b/prdoc/1.5.0/pr_2221_special.prdoc @@ -0,0 +1,9 @@ +title: "PVF worker: switch on seccomp networking restrictions" +author: mrcnski +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2250_special.prdoc b/prdoc/1.5.0/pr_2250_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d3f87b81b92478bb6b10a648c566ce2edd458ccb --- /dev/null +++ b/prdoc/1.5.0/pr_2250_special.prdoc @@ -0,0 +1,9 @@ +title: "crypto: `lazy_static` removed, light parser for address URI added" +author: michalkucharczyk +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/pr_2253.prdoc b/prdoc/1.5.0/pr_2253.prdoc similarity index 92% rename from prdoc/pr_2253.prdoc rename to prdoc/1.5.0/pr_2253.prdoc index 398b0a29066baa1dd2cd353177a96d4cff895b47..3f69bc2461e410518bf63ba168afdc82b8f7188f 100644 --- a/prdoc/pr_2253.prdoc +++ b/prdoc/1.5.0/pr_2253.prdoc @@ -3,8 +3,11 @@ title: Different builder pattern constructors for XCM +author: franciscoaguirre +topic: runtime + doc: - - audience: Core Dev + - audience: Runtime Dev description: | The `builder()` constructor for XCM programs now only allows building messages that pay for fees, i.e. messages that would pass the `AllowTopLevelPaidExecutionFrom` barrier. diff --git a/prdoc/1.5.0/pr_2265_special.prdoc b/prdoc/1.5.0/pr_2265_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..336adec03abed0bd646cf1806c372d2537799fbd --- /dev/null +++ b/prdoc/1.5.0/pr_2265_special.prdoc @@ -0,0 +1,9 @@ +title: Remove im-online pallet from Rococo and Westend +author: s0me0ne-unkn0wn +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2300_special.prdoc b/prdoc/1.5.0/pr_2300_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..407f07663254db09c894cbe812cdbaaa9ed9190a --- /dev/null +++ b/prdoc/1.5.0/pr_2300_special.prdoc @@ -0,0 +1,9 @@ +title: '[testnet] Remove Wococo stuff from BridgeHubRococo/AssetHubRococo' +author: bkontur  +topic: Bridges + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2351_special.prdoc b/prdoc/1.5.0/pr_2351_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..16f9e5d15a797390105d6875107eaf4ee25aa74b --- /dev/null +++ b/prdoc/1.5.0/pr_2351_special.prdoc @@ -0,0 +1,9 @@ +title: "frame-system: Add last_runtime_upgrade_spec_version" +author: bkchr +topic: Frame + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2354_special.prdoc b/prdoc/1.5.0/pr_2354_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5fbedef036159cc73e12d79922174f539ae27067 --- /dev/null +++ b/prdoc/1.5.0/pr_2354_special.prdoc @@ -0,0 +1,9 @@ +title: "Fix Typo: `PalletXcmExtrinsicsBenchmark`" +author: joepetrowski +topic: Benchmarks + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2361_special.prdoc b/prdoc/1.5.0/pr_2361_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d44b87287c431b2bd4e4198ed054439127a7fdb1 --- /dev/null +++ b/prdoc/1.5.0/pr_2361_special.prdoc @@ -0,0 +1,9 @@ +title: "[ci] Enable zombienet jobs in PRs" +author: alvicsam +topic: Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2368_special.prdoc b/prdoc/1.5.0/pr_2368_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e8ebcb38d30a197af9aa523a512fc4587d2f4074 --- /dev/null +++ b/prdoc/1.5.0/pr_2368_special.prdoc @@ -0,0 +1,9 @@ +title: "implementers-guide: update github link" +author: ordian +topic: Documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2369_special.prdoc b/prdoc/1.5.0/pr_2369_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ebcc533712da84f649b84903c6cc12570a3557df --- /dev/null +++ b/prdoc/1.5.0/pr_2369_special.prdoc @@ -0,0 +1,9 @@ +title: "[NPoS] Check if staker is exposed in paged exposure storage entries" +author: Ank4n +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2377_special.prdoc b/prdoc/1.5.0/pr_2377_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2985db6f3f82d934ab79a0351a4189514de53332 --- /dev/null +++ b/prdoc/1.5.0/pr_2377_special.prdoc @@ -0,0 +1,9 @@ +title: "fix typo" +author: cuteolaf +topic: Documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2378_special.prdoc b/prdoc/1.5.0/pr_2378_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..bdc965000945fecb9a6aeb59231f8da48740d13d --- /dev/null +++ b/prdoc/1.5.0/pr_2378_special.prdoc @@ -0,0 +1,9 @@ +title: "Beefy: small fixes" +author: serban300 +topic: Bridges + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2380_special.prdoc b/prdoc/1.5.0/pr_2380_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..058be28bf5ddf7c7e08b826724e3cb88aeeede53 --- /dev/null +++ b/prdoc/1.5.0/pr_2380_special.prdoc @@ -0,0 +1,9 @@ +title: Deprecate `RewardDestination::Controller` +author: rossbulat +topic: XCM + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2381_special.prdoc b/prdoc/1.5.0/pr_2381_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eb4020424d7f60abc0c850b464545a64dd28d982 --- /dev/null +++ b/prdoc/1.5.0/pr_2381_special.prdoc @@ -0,0 +1,9 @@ +title: Make collator RPC mode non-experimental +author: skunert +topic: Cumulus + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2385_special.prdoc b/prdoc/1.5.0/pr_2385_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a5239d30652c47dd7de91dab03e1f337798cd1dc --- /dev/null +++ b/prdoc/1.5.0/pr_2385_special.prdoc @@ -0,0 +1,9 @@ +title: "Relax `force_default_xcm_version` for testnet system parachains" +author: bkontur +topic: Cumulus + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2388.prdoc b/prdoc/1.5.0/pr_2388.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8f79097b8f60c37dd446ea5b757efb2cfb2e2c9f --- /dev/null +++ b/prdoc/1.5.0/pr_2388.prdoc @@ -0,0 +1,30 @@ +# Schema: Parity PR Documentation Schema (prdoc) +# See doc at https://github.com/paritytech/prdoc + +title: Add new flexible `pallet_xcm::transfer_assets()` call/extrinsic + +author: acatangiu +topic: runtime + +doc: + - audience: Runtime Dev + description: | + For complex combinations of asset transfers where assets and fees may have different reserves or + different reserve/teleport trust configurations, users can use the newly added `transfer_assets()` + extrinsic which is more flexible in allowing more complex scenarios. + The new extrinsic enables, for example, a (non-system) parachain to teleport their `ForeignAssets` + assets to `AssetHub` while using (reserve-based) `DOT` to pay fees. + notes: + - Now `(limited_)reserve_transfer_assets()` only allow reserve-based transfers for all assets + including fees, similarly `(limited_)teleport_assets()` only allows teleports for all assets + including fees. + +migrations: + db: [] + + runtime: [] + +crates: + - name: pallet-xcm + +host_functions: [] diff --git a/prdoc/1.5.0/pr_2397_special.prdoc b/prdoc/1.5.0/pr_2397_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5f07b269b1e236607a5ff24b7c12c0ca03ba1d60 --- /dev/null +++ b/prdoc/1.5.0/pr_2397_special.prdoc @@ -0,0 +1,9 @@ +title: "Pools: Add `MaxUnbonding` to metadata" +author: rossbulat +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2406_special.prdoc b/prdoc/1.5.0/pr_2406_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3fdb7ad8cf2f3793f099d0e5387c4ae97fcb55a0 --- /dev/null +++ b/prdoc/1.5.0/pr_2406_special.prdoc @@ -0,0 +1,9 @@ +title: Refactor ValidationError +author: eagr +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2411_special.prdoc b/prdoc/1.5.0/pr_2411_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0bc01e66903ab3a9fe6dbed87f37b4baeb931e50 --- /dev/null +++ b/prdoc/1.5.0/pr_2411_special.prdoc @@ -0,0 +1,9 @@ +title: "polkadot-node-subsystems: `ChainApiBackend` added + polkadot-debug image version fixed" +author: michalkucharczyk +topic: Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2413_special.prdoc b/prdoc/1.5.0/pr_2413_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..38083ba845b7fc57d666a8fb6a02bf663bae2998 --- /dev/null +++ b/prdoc/1.5.0/pr_2413_special.prdoc @@ -0,0 +1,9 @@ +title: "Update documentation for `SafeMode` and `TxPause` Pallets" +author: wilwade +topic: Documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2426_special.prdoc b/prdoc/1.5.0/pr_2426_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a0f5ab8ac5b8cdb7100893570b4f38ba23d49b9e --- /dev/null +++ b/prdoc/1.5.0/pr_2426_special.prdoc @@ -0,0 +1,9 @@ +title: "PVF: Fix unshare `no such file or directory` error" +author: mrcnski +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2435_special.prdoc b/prdoc/1.5.0/pr_2435_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b2bb7a2b8155ebb2b3f7fbc304f0ceaed61e9c1f --- /dev/null +++ b/prdoc/1.5.0/pr_2435_special.prdoc @@ -0,0 +1,9 @@ +title: "pallet-staking: Converts all math operations to safe" +author: gpestanaar +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2442_special.prdoc b/prdoc/1.5.0/pr_2442_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..52e672e765fe9322242340b64097a903d9aac9c2 --- /dev/null +++ b/prdoc/1.5.0/pr_2442_special.prdoc @@ -0,0 +1,9 @@ +title: "Fixes cumulus README instructions" +author: gpestana +topic: Documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2446_special.prdoc b/prdoc/1.5.0/pr_2446_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9fec1ad139ccef59b36fcfb7c7785c83250e8d07 --- /dev/null +++ b/prdoc/1.5.0/pr_2446_special.prdoc @@ -0,0 +1,9 @@ +title: "sp-api: Move macro related re-exports to `__private`" +author: bkchr +topic: Runtime API + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2450_special.prdoc b/prdoc/1.5.0/pr_2450_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..343e71fbf6d77c52dda28a050b875aed0f21913a --- /dev/null +++ b/prdoc/1.5.0/pr_2450_special.prdoc @@ -0,0 +1,9 @@ +title: Adapt test worker to profile flag +author: eagr +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2455_special.prdoc b/prdoc/1.5.0/pr_2455_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..928b84678074b94d27fe316b171fe4ea195c8d71 --- /dev/null +++ b/prdoc/1.5.0/pr_2455_special.prdoc @@ -0,0 +1,9 @@ +title: "Remove `RuntimeApi` dependency on system parachain runtime code" +author: seadanda +topic: "System Parachains" + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2457_special.prdoc b/prdoc/1.5.0/pr_2457_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ca6401206f468d6a6fd4c93aff3117a00e2a0bba --- /dev/null +++ b/prdoc/1.5.0/pr_2457_special.prdoc @@ -0,0 +1,9 @@ +title: "polkadot-parachain: one chain-spec for all" +author: michalkucharczyk +topic: "System Parachains" + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2459_special.prdoc b/prdoc/1.5.0/pr_2459_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..125f390f4ac9f58f5f0d4c8b6f5869ae47a90809 --- /dev/null +++ b/prdoc/1.5.0/pr_2459_special.prdoc @@ -0,0 +1,9 @@ +title: '[NPoS] Use `EraInfo` to manipulate exposure in fast-unstake tests' +author: Ank4n +topic: Pallets,Tests + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2461_special.prdoc b/prdoc/1.5.0/pr_2461_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..60a46714ca415ce18d8db311ea70df3ea9150808 --- /dev/null +++ b/prdoc/1.5.0/pr_2461_special.prdoc @@ -0,0 +1,9 @@ +title: "PVF: remove audit log access" +author: mrcnski +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2462_special.prdoc b/prdoc/1.5.0/pr_2462_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ae1f148632762d15955cd2bdff94835acab5043c --- /dev/null +++ b/prdoc/1.5.0/pr_2462_special.prdoc @@ -0,0 +1,9 @@ +title: "relay-chain-consensus: set a fork_choice" +author: michalkucharczyk +topic: Node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2463_special.prdoc b/prdoc/1.5.0/pr_2463_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0f35d50036f07df927ae9656ecfafc075d83d224 --- /dev/null +++ b/prdoc/1.5.0/pr_2463_special.prdoc @@ -0,0 +1,9 @@ +title: Add `on-chain-release-build` feature for Collectives Westend +author: liamaharon +topic: System Parachains + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2474_special.prdoc b/prdoc/1.5.0/pr_2474_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..42d67b5efa669bf195346ffbdbf6c048161eb378 --- /dev/null +++ b/prdoc/1.5.0/pr_2474_special.prdoc @@ -0,0 +1,9 @@ +title: "Pools: Add ability to configure commission claiming permissions" +author: rossbulat +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2483_special.prdoc b/prdoc/1.5.0/pr_2483_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..21fb045cae88f86c00f12f2b8c3484a8b4bdab26 --- /dev/null +++ b/prdoc/1.5.0/pr_2483_special.prdoc @@ -0,0 +1,9 @@ +title: Remove `dmp-queue`` pallet from Rococo Asset Hub and Bridge Hub +author: liamaharon +topic: Frame + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/pr_2486.prdoc b/prdoc/1.5.0/pr_2486.prdoc similarity index 95% rename from prdoc/pr_2486.prdoc rename to prdoc/1.5.0/pr_2486.prdoc index 0d50a7279d10e063ccda9b389ec16632b00207b8..c716f71c34e5a3a62a2fcba48faaf0fc804d307f 100644 --- a/prdoc/pr_2486.prdoc +++ b/prdoc/1.5.0/pr_2486.prdoc @@ -1,5 +1,8 @@ title: "PVF: Add Secure Validator Mode" +author: mrcnski +topic: node + doc: - audience: Node Operator description: | diff --git a/prdoc/1.5.0/pr_2487_special.prdoc b/prdoc/1.5.0/pr_2487_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3d6a2e11e268caf8ab46ad52c4c09ab67ee39614 --- /dev/null +++ b/prdoc/1.5.0/pr_2487_special.prdoc @@ -0,0 +1,9 @@ +title: "Do not pollute global base path with export genesis/wasm" +author: bkchr +topic: Cumulus + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2501_special.prdoc b/prdoc/1.5.0/pr_2501_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..125b9452c984d93a198930f537e64cd4cbc8bb87 --- /dev/null +++ b/prdoc/1.5.0/pr_2501_special.prdoc @@ -0,0 +1,9 @@ +title: "Staking: `chill_other` takes stash instead of controller" +author: rossbulat +topic: Pallets + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2509_special.prdoc b/prdoc/1.5.0/pr_2509_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..03ebfd80c96d82ff09ceaf78f1b4cb7d7035ecfd --- /dev/null +++ b/prdoc/1.5.0/pr_2509_special.prdoc @@ -0,0 +1,9 @@ +title: "Breaking: Remove long deprecated `AllPalletsWithoutSystemReversed`" +author: skunert +topic: Frame + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2515_special.prdoc b/prdoc/1.5.0/pr_2515_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4664058f86c82acaae2088a79ca1919507f782a4 --- /dev/null +++ b/prdoc/1.5.0/pr_2515_special.prdoc @@ -0,0 +1,9 @@ +title: Set `frame_system::LastRuntimeUpgrade` after running `try-runtime migrations` +author: liamaharon +topic: Frame + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2516_special.prdoc b/prdoc/1.5.0/pr_2516_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5d452b63e59601b859a8550cd8621b9fbf9032dd --- /dev/null +++ b/prdoc/1.5.0/pr_2516_special.prdoc @@ -0,0 +1,9 @@ +title: Remove `dmp_queue pallet` from Westend SP runtimes +author: liamaharon +topic: Frame + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2521_special.prdoc b/prdoc/1.5.0/pr_2521_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3b70150619e577485d434936ecbbbcc858502fc6 --- /dev/null +++ b/prdoc/1.5.0/pr_2521_special.prdoc @@ -0,0 +1,10 @@ +title: 'substrate-node: `NativeElseWasmExecutor` is no longer used' + +author: michalkucharczyk +topic: node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2526_special.prdoc b/prdoc/1.5.0/pr_2526_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..6008d7bfa9d570b1a992f61806df5d1b11320afe --- /dev/null +++ b/prdoc/1.5.0/pr_2526_special.prdoc @@ -0,0 +1,10 @@ +title: Remove `pov-recovery` race condition/Improve zombienet test + +author: skunert +topic: testing + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2552_special.prdoc b/prdoc/1.5.0/pr_2552_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9f0140c8142163489d2f0222764c7944c5057986 --- /dev/null +++ b/prdoc/1.5.0/pr_2552_special.prdoc @@ -0,0 +1,10 @@ +title: Withdraw Assets Before Checking Out in OnReapIdentity impl + +author: joepetrowski +topic: xcm + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2555_special.prdoc b/prdoc/1.5.0/pr_2555_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f817810f433e02c279d4ec2d96b21afdb467c14d --- /dev/null +++ b/prdoc/1.5.0/pr_2555_special.prdoc @@ -0,0 +1,10 @@ +title: Remove dependency on rand's SliceRandom shuffle implementation in `gossip-support` + +author: rphmeier +topic: node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2572_special.prdoc b/prdoc/1.5.0/pr_2572_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9d4c285798cc653d7ae91df9dea62773764621a2 --- /dev/null +++ b/prdoc/1.5.0/pr_2572_special.prdoc @@ -0,0 +1,10 @@ +title: Add missing glossary to ref docs + +author: juangirini +topic: documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2579_special.prdoc b/prdoc/1.5.0/pr_2579_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2992c92a8c5257ad6dca35eec82dc20b52a19101 --- /dev/null +++ b/prdoc/1.5.0/pr_2579_special.prdoc @@ -0,0 +1,10 @@ +title: "impl guide: update PVF host page; add diagrams" + +author: mrcnsk +topic: documentation + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2581_special.prdoc b/prdoc/1.5.0/pr_2581_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ebe5855b401604f4e89ff3909854b6c118096a19 --- /dev/null +++ b/prdoc/1.5.0/pr_2581_special.prdoc @@ -0,0 +1,10 @@ +title: 'Bandersnatch: `ring-context` generic over domain size' + +author: davxy +topic: node + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2591.prdoc b/prdoc/1.5.0/pr_2591.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f827e70af8bc97fbd2c035b23d22f983e1e86aaf --- /dev/null +++ b/prdoc/1.5.0/pr_2591.prdoc @@ -0,0 +1,12 @@ +title: Ensure to cleanup state in `remove_member` + +author: bkchr +topic: runtime + +doc: + - audience: Runtime Dev + description: | + Cleans up the state properly if a member of a ranked collective is removed. + +crates: + - name: pallet-ranked-collective diff --git a/prdoc/1.5.0/pr_2602_special.prdoc b/prdoc/1.5.0/pr_2602_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..56896348b4f7fc9d12f8e45d65fb955f166336d9 --- /dev/null +++ b/prdoc/1.5.0/pr_2602_special.prdoc @@ -0,0 +1,10 @@ +title: 'Bridges subtree update' + +author: bkontur +topic: bridges + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/pr_2625_special.prdoc b/prdoc/1.5.0/pr_2625_special.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3ffcf5986602cc682f43b27412db2f8381aae66c --- /dev/null +++ b/prdoc/1.5.0/pr_2625_special.prdoc @@ -0,0 +1,10 @@ +title: Improved `ExportXcm::validate` implementation for BridgeHubs + +author: bkontur +topic: bridges + +doc: + - audience: Runtime Dev + description: n/a + +crates: [] diff --git a/prdoc/1.5.0/readme.md b/prdoc/1.5.0/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..14b6d60331477d757cde68116e83d6f21ca4e036 --- /dev/null +++ b/prdoc/1.5.0/readme.md @@ -0,0 +1,2 @@ +Version 1.5.0 does not fully support `prddoc` yet. +While the list is complete, not all prdoc files have a valid or accurate content. diff --git a/prdoc/pr_1191.prdoc b/prdoc/pr_1191.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..26626731be46864e1fc383a02aab1646364c1966 --- /dev/null +++ b/prdoc/pr_1191.prdoc @@ -0,0 +1,21 @@ +title: Approve multiple candidates with a single signature + +doc: + - audience: Node Operator + description: | + Changed approval-voting, approval-distribution to approve multiple candidate with a single message, it adds: + * A new parachains_db version. + * A new validation protocol to support the new message types. + The new logic will be disabled and will be enabled at a later date after all validators have upgraded. + +migrations: + db: + - name: Parachains database change from v4 to v5. + description: | + Approval-voting column format has been updated with several new fields. All existing data will be automatically + be migrated to the new values. + +crates: + - name: "polkadot" + +host_functions: [] diff --git a/prdoc/pr_1289.prdoc b/prdoc/pr_1289.prdoc index f3d8801d9d82efc4be2f9f0b0ee45c1c593ae9f7..059d7608ba6380b5b8d1b0c9edf93b2c619aa34a 100644 --- a/prdoc/pr_1289.prdoc +++ b/prdoc/pr_1289.prdoc @@ -4,25 +4,26 @@ title: Supporting paged rewards allowing all nominators to be rewarded doc: - - audience: Validator + - audience: Node Operator description: | We used to clip top `MaxNominatorRewardedPerValidator` nominators by stake that are eligible for staking reward. This was done to limit computation cost of paying out rewards. This PR introduces paging to reward payouts, meaning we still clip nominators upto MaxExposurePageSize per page and there could be multiple pages of rewards to be paid out. Validators get commission pro-rata to the amount of reward that is paid out for the page. - notes: - - payout_stakers should be called multiple times, once for each page of nominators. - - payout_stakers_by_page can be used to pay out rewards for a specific page. - - Some old non-paged era storage items are deprecated, and can be removed in a future upgrade. + notes: + - payout_stakers should be called multiple times, once for each page of nominators. + - payout_stakers_by_page can be used to pay out rewards for a specific page. + - Some old non-paged era storage items are deprecated, and can be removed in a future upgrade. migrations: db: [] runtime: - - { pallet: "pallet-staking", description: "v14: Migration of era exposure storage items to paged exposures."} + - reference: pallet-staking + description: "v14: Migration of era exposure storage items to paged exposures." crates: - name: pallet-staking -host_functions: [] \ No newline at end of file +host_functions: [] diff --git a/prdoc/pr_1343.prdoc b/prdoc/pr_1343.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..84168230e0afba483db2f653ad5bfe0a934ebf69 --- /dev/null +++ b/prdoc/pr_1343.prdoc @@ -0,0 +1,29 @@ +title: Tasks API - A general system for recognizing and executing service work + +doc: + - audience: Runtime Dev + description: | + The Tasks API allows you to define some service work that can be recognized by a script or an off-chain worker. + Such a script can then create and submit all such work items at any given time. + `#[pallet:tasks_experimental]` provides a convenient way to define such work items. It can be attached to an + `impl` block inside a pallet, whose functions can then be annotated by the following attributes: + 1. `#[pallet::task_list]`: Define an iterator over the available work items for a task + 2. `#[pallet::task_condition]`: Define the conditions for a given work item to be valid + 3. `#[pallet::task_weight]`: Define the weight of a given work item + 4. `#[pallet::task_index]`: Define the index of a given work item + Each such function becomes a variant of the autogenerated enum `Task` for this pallet. + All such enums are aggregated into a `RuntimeTask` by `construct_runtime`. + An example pallet that uses the Tasks API is available at `substrate/frame/example/tasks`. + +migrations: + db: [] + + runtime: [] + +crates: + - name: frame-system + - name: frame-support + - name: frame-support-procedural + - name: pallet-example-tasks + +host_functions: [] diff --git a/prdoc/pr_1454.prdoc b/prdoc/pr_1454.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c96bfcde6cfe83067695ee04654f5a106d23286a --- /dev/null +++ b/prdoc/pr_1454.prdoc @@ -0,0 +1,10 @@ +title: Support XCM as part of Cosmos CosmWasm contract messages + +doc: + - audience: Runtime Dev + description: | + Made XCM JSON schema behind flag, bumped bounded-collection so to ensure it has that flag too. + +crates: + - name: staging-xcm + - name: sp-weights diff --git a/prdoc/pr_1479.prdoc b/prdoc/pr_1479.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..33b798290f8327780f4eb172ecba1daa96e38460 --- /dev/null +++ b/prdoc/pr_1479.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: Rococo/Westend Coretime Runtime + +doc: + - audience: Runtime User + description: | + Rococo/Westend runtime for the Coretime Chain (a.k.a. "Broker Chain") described in RFC-1. + +crates: [ ] \ No newline at end of file diff --git a/prdoc/pr_1677.prdoc b/prdoc/pr_1677.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9c5bee386ae34adf26be30d44ecb42697c94cc62 --- /dev/null +++ b/prdoc/pr_1677.prdoc @@ -0,0 +1,22 @@ +# 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-asset-conversion: Swap Credit" + +doc: + - audience: Runtime Dev + description: | + Introduces a swap implementation that allows the exchange of a credit (aka Negative Imbalance) of one asset for a credit of another asset. + + This is particularly useful when a credit swap is required but may not have sufficient value to meet the ED constraint, hence cannot be deposited to temp account before. An example use case is when XCM fees are paid using an asset held in the XCM executor registry and has to be swapped for native currency. + + Additional Updates: + - encapsulates the existing `Swap` trait impl within a transactional context, since partial storage mutation is possible when an error occurs; + - supplied `Currency` and `Assets` impls must be implemented over the same `Balance` type, the `AssetBalance` generic type is dropped. This helps to avoid numerous type conversion and overflow cases. If those types are different it should be handled outside of the pallet; + - `Box` asset kind on a pallet level, unbox on a runtime level - here [why](https://substrate.stackexchange.com/questions/10039/boxed-argument-of-a-dispatchable/10103#10103); + - `path` uses `Vec` now, instead of `BoundedVec` since it is never used in PoV; + - removes the `Transfer` event due to it's redundancy with the events emitted by `fungible/s` implementations; + - modifies the `SwapExecuted` event type; + +crates: [ ] + diff --git a/prdoc/pr_1694.prdoc b/prdoc/pr_1694.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..24797630efc992224d81ce06b4e6e22c8de1249f --- /dev/null +++ b/prdoc/pr_1694.prdoc @@ -0,0 +1,24 @@ +# 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: Agile Coretime Base Relaychain Functionality + +doc: + - audience: Runtime User + description: | + The relay chain is now capable of receiving assignments from the coretime + chain and will schedule parachains and on-demand orders accordingly. + Existing leases and system chains are preserved. They get a reserved + coretime core via a migration. +migrations: + db: [] + runtime: + - reference: polkadot-runtime-parachains + description: | + Claim queue in scheduler now no longer contains Option values and + assignments now contain information necessary to accomodate for coretime + features. Also all existing parachains are converted to coretime + assignments. + +crates: + - name: polkadot-runtime-parachains diff --git a/prdoc/pr_2031.prdoc b/prdoc/pr_2031.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fc2695df52e1b9205e77b22069938c4df99ec773 --- /dev/null +++ b/prdoc/pr_2031.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: "pallet-asset-conversion: Decoupling Native Currency Dependancy" + +doc: + - audience: Runtime Dev + description: | + Decoupling Pallet from the Concept of Native Currency + + Currently, the pallet used to intrinsically linked with the concept of native currency, requiring users to provide implementations of the `fungible::*` and `fungibles::*` traits to interact with native and non native assets. This incapsulates some non-related to the pallet complexity and makes it less adaptable in contexts where the native currency concept is absent. + + With this PR, the dependence on `fungible::*` for liquidity-supplying assets has been removed. Instead, the native and non-native currencies' handling is now overseen by a single type that implements the `fungibles::*` traits. To simplify this integration, types have been introduced to facilitate the creation of a union between `fungible::*` and `fungibles::*` implementations, producing a unified `fungibles::*` type. + + One of the reasons driving these changes is the ambition to create a more user-friendly API for the `SwapCredit` implementation. Given that it interacts with two distinct credit types from `fungible` and `fungibles`, a unified type was introduced. Clients now manage potential conversion failures for those credit types. In certain contexts, it's vital to guarantee that operations are fail-safe, like in this impl - [PR](https://github.com/paritytech/polkadot-sdk/pull/1845), place in [code](https://github.com/paritytech/polkadot-sdk/blob/20b85a5fada8f55c98ba831964f5866ffeadf4da/cumulus/primitives/utility/src/lib.rs#L429). + + Additional Updates: + - abstracted the pool ID and its account derivation logic via trait bounds, along with common implementation offerings; + - removed `inc_providers` on a pool creation for the pool account; + - benchmarks: + -- swap complexity is N, not const; + -- removed `From + Into` bound from `T::Balance`; + -- removed swap/liquidity/.. amount constants, resolve them dynamically based on pallet configuration; + -- migrated to v2 API; + - `OnUnbalanced` handler for the pool creation fee, replacing direct transfers to a specified account ID; + - renamed `MultiAssetId` to `AssetKind` aligning with naming across frame crates; + +crates: + - name: pallet-asset-conversion diff --git a/prdoc/pr_2033.prdoc b/prdoc/pr_2033.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eeb7ff2b4eed162ae435ff93e9a14f4442a5aacb --- /dev/null +++ b/prdoc/pr_2033.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: "`UnionOf` types for merged `fungible` and `fungibles` implementations" + +doc: + - audience: Runtime Dev + description: | + Introduces `UnionOf` types, crafted to merge `fungible` and `fungibles` implementations or two + `fungibles` implementations into a single type implementing `fungibles`. This also addresses + an issue where `ItemOf` initiates a double drop for an imbalance type, leading to inaccurate + total issuance accounting. + +crates: [ ] diff --git a/prdoc/pr_2281.prdoc b/prdoc/pr_2281.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c5453a08f2a352e63e1ea0321218b41375689fa2 --- /dev/null +++ b/prdoc/pr_2281.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: Rococo and Westend People Chain Runtimes + +doc: + - audience: Runtime User + description: | + Rococo and Westend runtimes for the "People Chain". This chain contains the Identity pallet + with plans to migrate all related data from the Relay Chain. Changes `IdentityInfo` fields. + +crates: [ ] diff --git a/prdoc/pr_2331.prdoc b/prdoc/pr_2331.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e3daf4c45bd414337535edc4c620a8d553c17f80 --- /dev/null +++ b/prdoc/pr_2331.prdoc @@ -0,0 +1,17 @@ +title: Rename `ExportGenesisStateCommand` to `ExportGenesisHeadCommand` + +doc: + - audience: Node Operator + description: | + The `export-genesis-state` subcommand is now called `export-gensis-head`, but + `export-genesis-state` stays as an alias to not break any scripts. + + - audience: Node Dev + description: | + The struct `ExportGenesisStateCommand` is now called `ExportGenesisHeadCommand`. + So, you only need to rename the import and usage. The `run` function is now + taking only a `client` as argument to fetch the genesis header. This way + the exported genesis head is respecting custom genesis block builders. + +crates: + - name: "cumulus-client-cli" diff --git a/prdoc/pr_2403.prdoc b/prdoc/pr_2403.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f1c4d3ecbaf10785a63df49a606644a06e9e2d73 --- /dev/null +++ b/prdoc/pr_2403.prdoc @@ -0,0 +1,9 @@ +title: Configurable block number provider in pallet-vesting + +doc: + - audience: Runtime Dev + description: | + Adds `BlockNumberProvider` type to pallet-vesting Config trait, allowing for custom providers instead of hardcoding frame-system. + This is particularly useful for parachains wanting to use `cumulus_pallet_parachain_system::RelaychainDataProvider` with `pallet-vesting`. + +crates: [ ] diff --git a/prdoc/pr_2481.prdoc b/prdoc/pr_2481.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d8736b1afd6eb76c340b1a4f86434be4a8c3d6df --- /dev/null +++ b/prdoc/pr_2481.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: "xcm-builder: `HaulBlobExporter` with improved XCM version check." + +doc: + - audience: Runtime Dev + description: | + Version check in `HaulBlobExporter` uses new trait `CheckVersion` to check known/configured destination versions, + ensuring compatibility. `HaulBlobExporter` will attempt to downgrade the message to destination's known version + instead of using the latest version. + +crates: [ ] diff --git a/prdoc/pr_2522.prdoc b/prdoc/pr_2522.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9a98f984bacb510bfd10b888a49e711d5ccfad3a --- /dev/null +++ b/prdoc/pr_2522.prdoc @@ -0,0 +1,12 @@ +title: "Adds Snowbridge to Rococo runtime" + +doc: + - audience: Runtime Dev + description: | + Adds the snowbridge pallets as a git subtree under the bridges directory. Adds Snowbridge + to the Rococo Asset Hub and Bridge Hub runtimes. + + +crates: + - name: asset-hub-rococo-runtime + - name: bridge-hub-rococo-runtime diff --git a/prdoc/pr_2532.prdoc b/prdoc/pr_2532.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d0df0ee4aca95527dc4cc4b8ee77365d9b0ffdbb --- /dev/null +++ b/prdoc/pr_2532.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 Fellowship Treasury + +doc: + - audience: Runtime User + description: | + Treasury Pallet Instance for the Fellowship in Westend Collectives. + +crates: [ ] diff --git a/prdoc/pr_2591.prdoc b/prdoc/pr_2591.prdoc deleted file mode 100644 index fe967cb678592edf337ad537a2d549e4afa78b14..0000000000000000000000000000000000000000 --- a/prdoc/pr_2591.prdoc +++ /dev/null @@ -1,9 +0,0 @@ -title: Ensure to cleanup state in remove_member - -doc: - - audience: Runtime Dev - description: | - Cleanes up the state properly if a member of a ranked collective is removed. - -crates: - - name: pallet-ranked-collective diff --git a/prdoc/pr_2597.prdoc b/prdoc/pr_2597.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..33d8505318416b331c686c1d95ca108f55c46cdd --- /dev/null +++ b/prdoc/pr_2597.prdoc @@ -0,0 +1,17 @@ +title: Make crate visible methods of `OverlayedChanges` public. + +doc: + - audience: Node Dev + description: | + Make some methods of `OverlayedChanges` namely `set_child_storage`, `clear_child_storage`, `clear_prefix` + and `clear_child_prefix` public which only had crate level visibility. + +migrations: + db: [] + + runtime: [] + +crates: + - name: sp-state-machine + +host_functions: [] diff --git a/prdoc/pr_2656.prdoc b/prdoc/pr_2656.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..563218dbde62bbb2c6cd565a2e7e427c9d8845d4 --- /dev/null +++ b/prdoc/pr_2656.prdoc @@ -0,0 +1,10 @@ +title: "pallet-broker: Small improvements to the origin checks" + +doc: + - audience: Runtime User + description: | + Change the permissionless calls `drop_region`, `drop_contribution`, `drop_history` and + `drop_renewal` to allow any kind of origin. + +crates: + - name: "pallet-broker" diff --git a/prdoc/pr_2663-fix-could-not-create-temporary-drectory.prdoc b/prdoc/pr_2663-fix-could-not-create-temporary-drectory.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2119599fce11cdf7070499d6e0be0fd4e859d14a --- /dev/null +++ b/prdoc/pr_2663-fix-could-not-create-temporary-drectory.prdoc @@ -0,0 +1,17 @@ +title: "PVF: fix unshare 'could not create temporary directory'" + +doc: + - audience: Node Operator + description: | + For validators: fixes the potential warning/error: + "Cannot unshare user namespace and change root, which are Linux-specific kernel security features: could not create a temporary directory in "/tmp/.tmpIcLriO". + +migrations: + db: [] + + runtime: [] + +crates: + - name: polkadot-node-core-pvf + +host_functions: [] diff --git a/prdoc/pr_2666.prdoc b/prdoc/pr_2666.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d7fbbda510853eff129629f89c2abbfa688c18b6 --- /dev/null +++ b/prdoc/pr_2666.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: Remove kusama and polkadot SP constants from parachains-common + +doc: + - audience: Runtime Dev + description: | + The constants for System Parachains in Kusama and Polkadot are now added to a new package in + the fellowship repo. This PR removes them from Polkadot-SDK. They are now accessible from the + `system-parachains-constants` package. + +crates: + - name: parachains-common diff --git a/prdoc/pr_2682.prdoc b/prdoc/pr_2682.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eaa5f5a4a9a69782806c0805d3accddd6a50d182 --- /dev/null +++ b/prdoc/pr_2682.prdoc @@ -0,0 +1,21 @@ +# 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 Authorize Upgrade Pattern to Frame System" + +doc: + - audience: Runtime User + description: | + Adds the `authorize_upgrade` -> `enact_authorized_upgrade` pattern to `frame-system`. This + will be useful for upgrading bridged chains that are under the governance of Polkadot without + passing entire runtime Wasm blobs over a bridge. + + Notes: + + - Changed `enact_authorized_upgrade` to `apply_authorized_upgrade`. + - Left calls in `parachain-system` and marked as deprecated to prevent breaking the API. They + just call into the `frame-system` functions. + - Deprecated calls will be removed no earlier than June 2024. + - Updated `frame-system` benchmarks to v2 syntax. + +crates: [ ] diff --git a/prdoc/pr_2684.prdoc b/prdoc/pr_2684.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..8960b6460f0dafe8cdf0c382aa6b1a510a74c5d3 --- /dev/null +++ b/prdoc/pr_2684.prdoc @@ -0,0 +1,14 @@ +title: Add XCM FungibleAdapter + +doc: + - audience: Runtime Dev + description: | + A new AssetTransactor has been added to xcm-builder: FungibleAdapter. + It's meant to be used instead of the old CurrencyAdapter for configuring the XCM executor + to handle only one asset. + +crates: + - name: "xcm-builder" + +migrations: [] +host_functions: [] diff --git a/prdoc/pr_2687.prdoc b/prdoc/pr_2687.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..90e635d80529c0693254e6ffa07db5a6c31cbc3e --- /dev/null +++ b/prdoc/pr_2687.prdoc @@ -0,0 +1,18 @@ +title: "pallet-uniques: Move migration over to `VersionedMigration`" + +doc: + - audience: Runtime Dev + description: | + Moves the migration over to `VersionedMigration`. Thus, if you had + used `migrate_to_v1` before in a custom `OnRuntimeUpgrade` implementation + you can now directly use the `MigrateV0ToV1`. + +migrations: + runtime: + - reference: MigrateV0ToV1 + description: | + Migrate the pallet storage from `0` to `1` by initializing + the `CollectionAccount` storage entry from all collections. + +crates: + - name: "pallet-uniques" diff --git a/prdoc/pr_2689.prdoc b/prdoc/pr_2689.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..847c3e8026cef9dfdf63f044a1b773be85b12921 --- /dev/null +++ b/prdoc/pr_2689.prdoc @@ -0,0 +1,13 @@ +# 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 + +doc: + - audience: Node Operator + description: | + BEEFY can now sync itself even when using Warp Sync to sync the node. This removes the limitation of not + being able to run BEEFY when warp syncing. Validators are now again able to warp sync. + +crates: + - name: sc-consensus-beefy diff --git a/prdoc/pr_2694.prdoc b/prdoc/pr_2694.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c393dcfeb9a8008d1e8921a2bfa88db79e6a414c --- /dev/null +++ b/prdoc/pr_2694.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-election-provider-multi-phase: Removes `BetterUnsignedThreshold` from pallet config" + +doc: + - audience: Runtime Dev + description: | + Removes thresholding for accepting solutions better than the last queued for unsigned phase. This is unnecessary + as even without thresholding, the number of solutions that can be submitted to on-chain which is better than the + previous one is limited. + +crates: + - name: "pallet-election-provider-multi-phase" diff --git a/prdoc/pr_2758.prdoc b/prdoc/pr_2758.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d8cb0557e9b65888bd67801580cb5c8c50b9ee13 --- /dev/null +++ b/prdoc/pr_2758.prdoc @@ -0,0 +1,10 @@ +title: Fix vote weights of ranked members in the Society pallet + +doc: + - audience: Runtime User + description: | + Fixes a bug in the tally accrual of approvals/rejections when + ranked members vote for Candidates and Defender in the Society pallet. + +crates: + - name: pallet-society diff --git a/prdoc/pr_2767.prdoc b/prdoc/pr_2767.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c2cd466c0097ac17882909c1f010a6fee54cecb3 --- /dev/null +++ b/prdoc/pr_2767.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: Extract PartialComponents into type alias `Service` + +doc: + - audience: Node Dev + description: | + Simplifies service definitions by extraction of a complicated type into a type alias. No breaking changes. + +crates: + - name: "sc-service" + - name: "node-template" + - name: "minimal-node" + - name: "cumulus-test-service" + - name: "polkadot-parachain-bin" + - name: "parachain-template-node" diff --git a/prdoc/pr_2783.prdoc b/prdoc/pr_2783.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0e4c9906541494b1e61984ed8576600f8fd53cb4 --- /dev/null +++ b/prdoc/pr_2783.prdoc @@ -0,0 +1,12 @@ +title: "Accept Root origin as valid sudo" + +doc: + - audience: Runtime User + description: | + Dispatchables of `pallet-sudo` will now also accept the `Root` origin + as valid `sudo` origin. This enhancement is useful for parachains that + allow the relay chain as a superuser. It enables the relay chain to send + an XCM message to initialize the sudo key. + +crates: + - name: "pallet-sudo" diff --git a/prdoc/pr_2799.prdoc b/prdoc/pr_2799.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..436dea643e20b3d132105fc25cb003ffe2261830 --- /dev/null +++ b/prdoc/pr_2799.prdoc @@ -0,0 +1,10 @@ +title: Improve XCM debuggability + +doc: + - audience: Runtime User + description: | + Adds more logging to XCM execution to improve its debuggability. + +crates: + - name: "staging-xcm-builder" + - name: "staging-xcm-executor" diff --git a/prdoc/pr_2803.prdoc b/prdoc/pr_2803.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ddd3dd677a11e96385e238be991f9bbe5ff91e8 --- /dev/null +++ b/prdoc/pr_2803.prdoc @@ -0,0 +1,19 @@ +title: "cumulus-primitives-parachain-inherent: Split into two crates" + +doc: + - audience: Node Dev + description: | + This splits `cumulus-primitives-parachain-inherent` into two crates. The new crate is called + `cumulus-client-parachain-inherent`. This is done to improve the compile time for runtimes, + as they are not required anymore to pull in half of the node side at compile time. + + To migrate your code you need to change + `cumulus_primitives_parachain_inherent::ParachainInherentData::create_at` to + `cumulus_client_parachain_inherent::ParachainInherentDataProvider::create_at`. + Any other code should be compatible. The mocking code also moved to the new client crate and + you may need to adapt your imports accordingly. Generally, replacing the old crate with the new + crate fix most compile errors resulting from this pull request. + +crates: + - name: "cumulus-primitives-parachain-inherent" + - name: "cumulus-client-parachain-inherent" diff --git a/prdoc/pr_2804.prdoc b/prdoc/pr_2804.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..456120741d93b3012018ee9c1066e6e7475eb718 --- /dev/null +++ b/prdoc/pr_2804.prdoc @@ -0,0 +1,9 @@ +title: Fix malus implementation. + +doc: + - audience: Node Dev + description: | + The malus implementation is used to test security of Polkadot. + It was broken. This fixes it. + +crates: [ ] diff --git a/prdoc/pr_2811.prdoc b/prdoc/pr_2811.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..647fb4c8ccd42005b482c274b2eb4c4d3f849de4 --- /dev/null +++ b/prdoc/pr_2811.prdoc @@ -0,0 +1,13 @@ +title: "Interlacing removes the region on which it is performed." + +doc: + - audience: Runtime User + description: | + The current implementation of the broker pallet does not remove + the region on which the interlacing is performed. This can create + a vulnerability, as the original region owner is still allowed to + assign a task to the region even after transferring an interlaced + part of it. + +crates: + - name: "pallet-broker" diff --git a/prdoc/pr_2813.prdoc b/prdoc/pr_2813.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..ff6e5cf5cf6bfd56670c1ed9b8001fc446d3d694 --- /dev/null +++ b/prdoc/pr_2813.prdoc @@ -0,0 +1,11 @@ +title: "Implement only sending one notification at a time as per RFC 56" + +doc: + - audience: Node Dev + description: | + Transactions are now gossiped one at a time instead of as batches, as per RFC 56. This + allows decoding notifications without knowing how to decode individual transactions, and + allows for a more fine grained backpressure. + +crates: + - name: "sc-network-transactions" diff --git a/prdoc/pr_2823.prdoc b/prdoc/pr_2823.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..64a309969efb353fe7d4d93b6a3e485e024f11f7 --- /dev/null +++ b/prdoc/pr_2823.prdoc @@ -0,0 +1,11 @@ +title: "`fungible::Unbalanced::decrease_balance`: Handle `precision` properly" + +doc: + - audience: Runtime Dev + description: | + `fungible::Unbalanced::decrease_balance` will now handle `precision` properly. This means when + passing `Exact`, it will ensure that the available balance is bigger or equal to the `amount` + that should be deducted. + +crates: + - name: "frame-support" diff --git a/prdoc/pr_2834.prdoc b/prdoc/pr_2834.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3a5881659de657dcf55254792bf6db61451ff09d --- /dev/null +++ b/prdoc/pr_2834.prdoc @@ -0,0 +1,13 @@ +title: "proposer: return optional block" + +doc: + - audience: Node Dev + description: | + The `ProposerInterface` trait now returns an optional `Proposal`, allowing + for no block to be created. This is a breaking change that only impacts custom + `ProposerInterface` implementations. The change allows more flexibility in choosing + when to create blocks. + +crates: + - name: "cumulus-client-consensus-aura" + - name: "cumulus-client-consensus-proposer" diff --git a/prdoc/pr_2835.prdoc b/prdoc/pr_2835.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..037e9b8ec7701de0ab5db102de3fcc8f24604f49 --- /dev/null +++ b/prdoc/pr_2835.prdoc @@ -0,0 +1,9 @@ +title: New malus variant `support-disabled` + +doc: + - audience: Node Dev + description: | + A new malicious flavor added to pretend that nobody + is disabled onchain. + +crates: [ ] diff --git a/prdoc/pr_2862.prdoc b/prdoc/pr_2862.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..fa136b5d98aca8e41ba3a6f6d993640ce3c6a50d --- /dev/null +++ b/prdoc/pr_2862.prdoc @@ -0,0 +1,11 @@ +title: Return latest known relay chain block number in `on_initialize` etc. + +doc: + - audience: Runtime Dev + description: | + `RelaychainDataProvider` and `RelaychainBlockNumberProvider` will now return the latest known + relay chain block number in `on_initialize`, aka when `validation_data` wasn't yet set by + the inherent. + +crates: + - name: "cumulus-pallet-parachain-system" diff --git a/prdoc/schema_user.json b/prdoc/schema_user.json index 60ff28d36264321b5465c5940573993f70172342..82215d51866b35895b5e840a8f3a900b161a9cf6 100644 --- a/prdoc/schema_user.json +++ b/prdoc/schema_user.json @@ -17,6 +17,16 @@ "type": "string", "description": "Title for the PR. This is what will show up in the release notes.\nif needed, you may provide a different title override for each audience in the `doc` property." }, + "author": { + "title": "Author handle", + "type": "string", + "description": "Author handle" + }, + "topic": { + "title": "Topic", + "type": "string", + "description": "Topic" + }, "doc": { "type": "array", diff --git a/scripts/release/build-changelogs.sh b/scripts/release/build-changelogs.sh new file mode 100755 index 0000000000000000000000000000000000000000..a9275f45a50c479d27ff3cfffcb5bd82f0b815cf --- /dev/null +++ b/scripts/release/build-changelogs.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +export PRODUCT=polkadot +export VERSION=${VERSION:-1.5.0} + +PROJECT_ROOT=`git rev-parse --show-toplevel` +echo $PROJECT_ROOT + +TMP=$(mktemp -d) +TEMPLATE_AUDIENCE="${PROJECT_ROOT}/scripts/release/templates/audience.md.tera" +TEMPLATE_CHANGELOG="${PROJECT_ROOT}/scripts/release/templates/changelog.md.tera" + +DATA_JSON="${TMP}/data.json" +CONTEXT_JSON="${TMP}/context.json" +echo -e "TEMPLATE_AUDIENCE: \t$TEMPLATE_AUDIENCE" +echo -e "DATA_JSON: \t\t$DATA_JSON" +echo -e "CONTEXT_JSON: \t\t$CONTEXT_JSON" + +# Create output folder +OUTPUT="${TMP}/changelogs/$PRODUCT/$VERSION" +echo -e "OUTPUT: \t\t$OUTPUT" +mkdir -p $OUTPUT + +prdoc load -d "$PROJECT_ROOT/prdoc/$VERSION" --json > $DATA_JSON +# ls -al $DATA_JSON + +cat $DATA_JSON | jq ' { "prdoc" : .}' > $CONTEXT_JSON +# ls -al $CONTEXT_JSON + +# Fetch the list of valid audiences +SCHEMA_URL=https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json +SCHEMA=$(curl -s $SCHEMA_URL | sed 's|^//.*||') +AUDIENCE_ARRAY=$(echo -E $SCHEMA | jq -r '."$defs".audience.oneOf[] | .const') + +readarray -t audiences < <(echo "$AUDIENCE_ARRAY") +declare -p audiences + + +# Generate a changelog +echo "Generating changelog..." +tera -t "${TEMPLATE_CHANGELOG}" --env --env-key env "${CONTEXT_JSON}" > "$OUTPUT/changelog.md" +echo "Changelog ready in $OUTPUT/changelog.md" + +# Generate a release notes doc per audience +for audience in "${audiences[@]}"; do + audience_id="$(tr [A-Z] [a-z] <<< "$audience")" + audience_id="$(tr ' ' '_' <<< "$audience_id")" + echo "Processing audience: $audience ($audience_id)" + export TARGET_AUDIENCE=$audience + tera -t "${TEMPLATE_AUDIENCE}" --env --env-key env "${CONTEXT_JSON}" > "$OUTPUT/relnote_${audience_id}.md" +done + +# Show the files +tree -s -h -c $OUTPUT/ diff --git a/scripts/release/templates/audience.md.tera b/scripts/release/templates/audience.md.tera new file mode 100644 index 0000000000000000000000000000000000000000..dc507053dd5a1fb0f56c788e0f2b02408ba8221c --- /dev/null +++ b/scripts/release/templates/audience.md.tera @@ -0,0 +1,13 @@ +## Release {{ env.PRODUCT }} {{ env.VERSION }} + +Changelog for `{{ env.TARGET_AUDIENCE }}`. + +{% for file in prdoc -%} +#### PR #{{file.doc_filename.number}}: {{ file.content.title }} +{% for doc_item in file.content.doc %} +{%- if doc_item.audience == env.TARGET_AUDIENCE %} +{{ doc_item.description }} +{% endif -%} + +{%- endfor %} +{%- endfor %} diff --git a/scripts/release/templates/changelog.md.tera b/scripts/release/templates/changelog.md.tera new file mode 100644 index 0000000000000000000000000000000000000000..aaba761e8e47fa567db20c125ed9893c733da5dd --- /dev/null +++ b/scripts/release/templates/changelog.md.tera @@ -0,0 +1,7 @@ +## Changelog for `{{ env.PRODUCT | capitalize }} v{{ env.VERSION }}` + +{% for file in prdoc | sort(attribute="doc_filename.number") -%} +{%- set author= file.content.author | default(value="n/a") -%} +{%- set topic= file.content.topic | default(value="n/a") -%} +- #{{file.doc_filename.number}}: {{ file.content.title }} (@{{ author }}) [{{ topic | capitalize }}] +{% endfor -%} diff --git a/scripts/snowbridge_update_subtree.sh b/scripts/snowbridge_update_subtree.sh new file mode 100755 index 0000000000000000000000000000000000000000..2276bb35469f1e13cd58085e36fa49069123bcef --- /dev/null +++ b/scripts/snowbridge_update_subtree.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# A script to udpate bridges repo as subtree to Cumulus +# Usage: +# ./scripts/update_subtree_snowbridge.sh fetch +# ./scripts/update_subtree_snowbridge.sh patch + +set -e + +SNOWBRIDGE_BRANCH="${SNOWBRIDGE_BRANCH:-main}" +POLKADOT_SDK_BRANCH="${POLKADOT_SDK_BRANCH:-master}" +SNOWBRIDGE_TARGET_DIR="${TARGET_DIR:-bridges/snowbridge}" + +function fetch() { + # the script is able to work only on clean git copy + [[ -z "$(git status --porcelain)" ]] || { + echo >&2 "The git copy must be clean (stash all your changes):"; + git status --porcelain + exit 1; + } + + local snowbridge_remote=$(git remote -v | grep "snowbridge.git (fetch)" | head -n1 | awk '{print $1;}') + if [ -z "$snowbridge_remote" ]; then + echo "Adding new remote: 'snowbridge' repo..." + git remote add -f snowbridge https://github.com/Snowfork/snowbridge.git + snowbridge_remote="snowbridge" + else + echo "Fetching remote: '${snowbridge_remote}' repo..." + git fetch https://github.com/Snowfork/snowbridge.git --prune + fi + + echo "Syncing/updating subtree with remote branch '${snowbridge_remote}/$SNOWBRIDGE_BRANCH' to target directory: '$SNOWBRIDGE_TARGET_DIR'" + git subtree pull --prefix=$SNOWBRIDGE_TARGET_DIR ${snowbridge_remote} $SNOWBRIDGE_BRANCH --squash +} + +function clean() { + echo "Patching/removing unneeded stuff from subtree in target directory: '$SNOWBRIDGE_TARGET_DIR'" + chmod +x $SNOWBRIDGE_TARGET_DIR/parachain/scripts/verify-pallets-build.sh + $SNOWBRIDGE_TARGET_DIR/parachain/scripts/verify-pallets-build.sh --ignore-git-state --no-revert +} + +function create_patch() { + [[ -z "$(git status --porcelain)" ]] || { + echo >&2 "The git copy must be clean (stash all your changes):"; + git status --porcelain + exit 1; + } + echo "Creating diff patch file to apply to snowbridge. No Cargo.toml files will be included in the patch." + git diff snowbridge/$SNOWBRIDGE_BRANCH $POLKADOT_SDK_BRANCH:bridges/snowbridge --diff-filter=ACM -- . ':(exclude)*/Cargo.toml' > snowbridge.patch +} + +case "$1" in + fetch) + fetch + ;; + clean) + clean + ;; + create_patch) + create_patch + ;; + update) + fetch + clean + ;; +esac diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index d8c8c7740b0461ed47e03369f311107e8f72665a..42eced506c4f81fc77752849571e75a8ff8c7b0f 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -10,6 +10,9 @@ publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" build = "build.rs" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,11 +20,11 @@ targets = ["x86_64-unknown-linux-gnu"] name = "minimal-node" [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" jsonrpsee = { version = "0.16.2", features = ["server"] } -serde_json = "1.0.108" +serde_json = "1.0.111" sc-cli = { path = "../../../client/cli" } sc-executor = { path = "../../../client/executor" } diff --git a/substrate/bin/minimal/node/src/service.rs b/substrate/bin/minimal/node/src/service.rs index b6369c44dda95b08431c267c631692e5d7761be9..08db8b5936148c45870489ae11b0c9263078a8f6 100644 --- a/substrate/bin/minimal/node/src/service.rs +++ b/substrate/bin/minimal/node/src/service.rs @@ -38,19 +38,17 @@ pub(crate) type FullClient = type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; -pub fn new_partial( - config: &Configuration, -) -> Result< - sc_service::PartialComponents< - FullClient, - FullBackend, - FullSelectChain, - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, - Option, - >, - ServiceError, -> { +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type Service = sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + Option, +>; + +pub fn new_partial(config: &Configuration) -> Result { let telemetry = config .telemetry_endpoints .clone() diff --git a/substrate/bin/minimal/runtime/Cargo.toml b/substrate/bin/minimal/runtime/Cargo.toml index f7685642d274ebb1e66d6e2bfb0a009588bb4798..296106544bbfdbbb1f7f76b86d5c8ddefb54f917 100644 --- a/substrate/bin/minimal/runtime/Cargo.toml +++ b/substrate/bin/minimal/runtime/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true license.workspace = true publish = false +[lints] +workspace = true + [dependencies] parity-scale-codec = { version = "3.0.0", default-features = false } scale-info = { version = "2.6.0", default-features = false } diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index a76aaf2a6315bddd484ffec7489c51ca767c85fe..6a600364c4d8b0e872ed148ef47e24784ea240da 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -10,6 +10,9 @@ publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" build = "build.rs" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,9 +20,9 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } -serde_json = "1.0.108" +serde_json = "1.0.111" sc-cli = { path = "../../../client/cli" } sp-core = { path = "../../../primitives/core" } diff --git a/substrate/bin/node-template/node/src/rpc.rs b/substrate/bin/node-template/node/src/rpc.rs index f4f1540f732f784317c3c657d170805a5f55c20e..246391adcbbe88a03b6cf9cf9043d82b8de18b60 100644 --- a/substrate/bin/node-template/node/src/rpc.rs +++ b/substrate/bin/node-template/node/src/rpc.rs @@ -53,5 +53,12 @@ where // to call into the runtime. // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` + // You probably want to enable the `rpc v2 chainSpec` API as well + // + // let chain_name = chain_spec.name().to_string(); + // let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + // let properties = chain_spec.properties(); + // module.merge(ChainSpec::new(chain_name, genesis_hash, properties).into_rpc())?; + Ok(module) } diff --git a/substrate/bin/node-template/node/src/service.rs b/substrate/bin/node-template/node/src/service.rs index c4a2b2f39d2156339553515de6ad994659eae846..25cd651178411c6338a2b0d1e7836804a3a5b676 100644 --- a/substrate/bin/node-template/node/src/service.rs +++ b/substrate/bin/node-template/node/src/service.rs @@ -23,29 +23,20 @@ type FullSelectChain = sc_consensus::LongestChain; /// imported and generated. const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; -#[allow(clippy::type_complexity)] -pub fn new_partial( - config: &Configuration, -) -> Result< - sc_service::PartialComponents< - FullClient, - FullBackend, - FullSelectChain, - sc_consensus::DefaultImportQueue, - sc_transaction_pool::FullPool, - ( - sc_consensus_grandpa::GrandpaBlockImport< - FullBackend, - Block, - FullClient, - FullSelectChain, - >, - sc_consensus_grandpa::LinkHalf, - Option, - ), - >, - ServiceError, -> { +pub type Service = sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + ( + sc_consensus_grandpa::GrandpaBlockImport, + sc_consensus_grandpa::LinkHalf, + Option, + ), +>; + +pub fn new_partial(config: &Configuration) -> Result { let telemetry = config .telemetry_endpoints .clone() diff --git a/substrate/bin/node-template/pallets/template/Cargo.toml b/substrate/bin/node-template/pallets/template/Cargo.toml index 405d9c229f88f6be3d555efc7d8e1277ef25b1f8..51410a71c7bcee0267f36bbfcf20c616a5537ce3 100644 --- a/substrate/bin/node-template/pallets/template/Cargo.toml +++ b/substrate/bin/node-template/pallets/template/Cargo.toml @@ -9,6 +9,9 @@ license = "MIT-0" publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/bin/node-template/runtime/Cargo.toml b/substrate/bin/node-template/runtime/Cargo.toml index 55fb03159ab15d505c9b2a26accf9832e7c50094..a7b93a230ca85dd0b400884b4788a50d24db0402 100644 --- a/substrate/bin/node-template/runtime/Cargo.toml +++ b/substrate/bin/node-template/runtime/Cargo.toml @@ -9,6 +9,9 @@ license = "MIT-0" publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -39,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.108", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } 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 5ce91dc3c440b52eb93a84c52ddbcc4df48190de..e90b7070396b35ca71c621d040a05d43a5cfba7a 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] array-bytes = "6.1" -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } log = "0.4.17" node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } @@ -21,8 +24,8 @@ 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.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" kvdb-rocksdb = "0.19.0" @@ -38,7 +41,7 @@ tempfile = "3.1.0" fs_extra = "1" rand = { version = "0.8.5", features = ["small_rng"] } lazy_static = "1.4.0" -parity-db = "0.4.8" +parity-db = "0.4.12" sc-transaction-pool = { path = "../../../client/transaction-pool" } sc-transaction-pool-api = { path = "../../../client/transaction-pool/api" } futures = { version = "0.3.21", features = ["thread-pool"] } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index e511633ff50523ab7d4871acfb5484b542119096..3a80dc4e0ccb429d81e271a7b847e483bebefdde 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -11,6 +11,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.wasm-pack.profile.release] # `wasm-opt` has some problems on linux, see # https://github.com/rustwasm/wasm-pack/issues/781 etc. @@ -38,9 +41,9 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies array-bytes = "6.1" -clap = { version = "4.4.10", features = ["derive"], optional = true } +clap = { version = "4.4.13", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } jsonrpsee = { version = "0.16.2", features = ["server"] } futures = "0.3.21" log = "0.4.17" @@ -49,6 +52,7 @@ rand = "0.8" # primitives sp-authority-discovery = { path = "../../../primitives/authority-discovery" } sp-consensus-babe = { path = "../../../primitives/consensus/babe" } +beefy-primitives = { package = "sp-consensus-beefy", path = "../../../primitives/consensus/beefy" } grandpa-primitives = { package = "sp-consensus-grandpa", path = "../../../primitives/consensus/grandpa" } sp-api = { path = "../../../primitives/api" } sp-core = { path = "../../../primitives/core" } @@ -61,6 +65,7 @@ sp-consensus = { path = "../../../primitives/consensus/common" } sp-transaction-storage-proof = { path = "../../../primitives/transaction-storage-proof" } sp-io = { path = "../../../primitives/io" } sp-mixnet = { path = "../../../primitives/mixnet" } +sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" } sp-statement-store = { path = "../../../primitives/statement-store" } # client dependencies @@ -76,7 +81,9 @@ sc-network-sync = { path = "../../../client/network/sync" } sc-network-statement = { path = "../../../client/network/statement" } sc-consensus-slots = { path = "../../../client/consensus/slots" } sc-consensus-babe = { path = "../../../client/consensus/babe" } +beefy = { package = "sc-consensus-beefy", path = "../../../client/consensus/beefy" } grandpa = { package = "sc-consensus-grandpa", path = "../../../client/consensus/grandpa" } +mmr-gadget = { path = "../../../client/merkle-mountain-range" } sc-rpc = { path = "../../../client/rpc" } sc-basic-authorship = { path = "../../../client/basic-authorship" } sc-service = { path = "../../../client/service", default-features = false } @@ -109,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.108" +serde_json = "1.0.111" [dev-dependencies] sc-keystore = { path = "../../../client/keystore" } @@ -151,13 +158,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.108" +serde_json = "1.0.111" 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.10", optional = true } +clap = { version = "4.4.13", 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/src/chain_spec.rs b/substrate/bin/node/cli/src/chain_spec.rs index 3559348d188c778a9c8838cbd2cf840d87521a03..b6e8fb8a14edfa22a4d221515185b2748bc733c7 100644 --- a/substrate/bin/node/cli/src/chain_spec.rs +++ b/substrate/bin/node/cli/src/chain_spec.rs @@ -18,6 +18,7 @@ //! Substrate chain configurations. +use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; use grandpa_primitives::AuthorityId as GrandpaId; use kitchensink_runtime::{ constants::currency::*, wasm_binary_unwrap, Block, MaxNominations, SessionKeys, StakerStatus, @@ -73,23 +74,37 @@ fn session_keys( im_online: ImOnlineId, authority_discovery: AuthorityDiscoveryId, mixnet: MixnetId, + beefy: BeefyId, ) -> SessionKeys { - SessionKeys { grandpa, babe, im_online, authority_discovery, mixnet } + SessionKeys { grandpa, babe, im_online, authority_discovery, mixnet, beefy } } fn configure_accounts_for_staging_testnet() -> ( - Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId)>, + Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + MixnetId, + BeefyId, + )>, AccountId, Vec, ) { #[rustfmt::skip] - // stash, controller, session-key + // stash, controller, session-key, beefy id // generated with secret: // for i in 1 2 3 4 ; do for j in stash controller; do subkey inspect "$secret"/fir/$j/$i; done; done // // and // - // for i in 1 2 3 4 ; do for j in session; do subkey --ed25519 inspect "$secret"//fir//$j//$i; done; done + // for i in 1 2 3 4 ; do for j in session; do subkey inspect --scheme ed25519 "$secret"//fir//$j//$i; done; done + // + // and + // + // for i in 1 2 3 4 ; do for j in session; do subkey inspect --scheme ecdsa "$secret"//fir//$j//$i; done; done let initial_authorities: Vec<( AccountId, @@ -99,6 +114,7 @@ fn configure_accounts_for_staging_testnet() -> ( ImOnlineId, AuthorityDiscoveryId, MixnetId, + BeefyId, )> = vec![ ( // 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy @@ -120,6 +136,9 @@ fn configure_accounts_for_staging_testnet() -> ( // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 array_bytes::hex2array_unchecked("6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106") .unchecked_into(), + // 5DMLFcDdLLQbw696YfHaWBpQR99HwR456ycSCfr6L7KXGYK8 + array_bytes::hex2array_unchecked("035560fafa241739869360aa4b32bc98953172ceb41a19c6cc1a27962fb3d1ecec") + .unchecked_into(), ), ( // 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2 @@ -141,6 +160,9 @@ fn configure_accounts_for_staging_testnet() -> ( // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ array_bytes::hex2array_unchecked("482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e") .unchecked_into(), + // 5FYk11kNtB4178wLKJ2RNoUzzcjgRUciFe3SJDVZXhqX4dzG + array_bytes::hex2array_unchecked("02da1ab255ed888ee3e19b73d335fc13160b3eb10456c2d17c6a8ea7de403d2445") + .unchecked_into(), ), ( // 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp @@ -162,6 +184,9 @@ fn configure_accounts_for_staging_testnet() -> ( // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH array_bytes::hex2array_unchecked("482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a") .unchecked_into(), + // 5GQx4FToRBPqfani6o7owFJE1UstiviqbPP7HPWyvtXWWukn + array_bytes::hex2array_unchecked("036a818b3f59579c5fbbe4fede64f49dbf090ba883eb2a175d5ca90e5adb5f0b3e") + .unchecked_into(), ), ( // 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9 @@ -183,6 +208,9 @@ fn configure_accounts_for_staging_testnet() -> ( // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x array_bytes::hex2array_unchecked("00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378") .unchecked_into(), + // 5FCu2pY928VVHPgnNVJssvxFJZECyNe1CyH3WTG79Wisx58B + array_bytes::hex2array_unchecked("020ce02b963548f9f8ade8765f7a4a06638c17819c78422a1cc35b647873583eef") + .unchecked_into(), ), ]; @@ -234,7 +262,8 @@ where /// Helper function to generate stash, controller and session key from seed. pub fn authority_keys_from_seed( seed: &str, -) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId) { +) -> (AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId, BeefyId) +{ ( get_account_id_from_seed::(&format!("{}//stash", seed)), get_account_id_from_seed::(seed), @@ -243,6 +272,7 @@ pub fn authority_keys_from_seed( get_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), + get_from_seed::(seed), ) } @@ -255,12 +285,22 @@ fn configure_accounts( ImOnlineId, AuthorityDiscoveryId, MixnetId, + BeefyId, )>, initial_nominators: Vec, endowed_accounts: Option>, stash: Balance, ) -> ( - Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId, MixnetId)>, + Vec<( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, + MixnetId, + BeefyId, + )>, Vec, usize, Vec<(AccountId, AccountId, Balance, StakerStatus)>, @@ -326,6 +366,7 @@ pub fn testnet_genesis( ImOnlineId, AuthorityDiscoveryId, MixnetId, + BeefyId, )>, initial_nominators: Vec, root_key: AccountId, @@ -351,6 +392,7 @@ pub fn testnet_genesis( x.4.clone(), x.5.clone(), x.6.clone(), + x.7.clone(), ), ) }) diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 4f8c6198cdce72c49f22d1d089f3e92a95182fa4..5e6e794f73283467b7dd3e4194d08f0e5e17c412 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -63,6 +63,8 @@ type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; type FullGrandpaBlockImport = grandpa::GrandpaBlockImport; +type FullBeefyBlockImport = + beefy::import::BeefyBlockImport; /// The transaction pool type definition. pub type TransactionPool = sc_transaction_pool::FullPool; @@ -165,9 +167,14 @@ pub fn new_partial( sc_rpc::SubscriptionTaskExecutor, ) -> Result, sc_service::Error>, ( - sc_consensus_babe::BabeBlockImport, + sc_consensus_babe::BabeBlockImport< + Block, + FullClient, + FullBeefyBlockImport, + >, grandpa::LinkHalf, sc_consensus_babe::BabeLink, + beefy::BeefyVoterLinks, ), grandpa::SharedVoterState, Option, @@ -222,9 +229,17 @@ pub fn new_partial( )?; let justification_import = grandpa_block_import.clone(); + let (beefy_block_import, beefy_voter_links, beefy_rpc_links) = + beefy::beefy_block_import_and_links( + grandpa_block_import, + backend.clone(), + client.clone(), + config.prometheus_registry().cloned(), + ); + let (block_import, babe_link) = sc_consensus_babe::block_import( sc_consensus_babe::configuration(&*client)?, - grandpa_block_import, + beefy_block_import, client.clone(), )?; @@ -253,7 +268,7 @@ pub fn new_partial( offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), })?; - let import_setup = (block_import, grandpa_link, babe_link); + let import_setup = (block_import, grandpa_link, babe_link, beefy_voter_links); let statement_store = sc_statement_store::Store::new_shared( &config.data_path, @@ -268,7 +283,7 @@ pub fn new_partial( let (mixnet_api, mixnet_api_backend) = mixnet_config.map(sc_mixnet::Api::new).unzip(); let (rpc_extensions_builder, rpc_setup) = { - let (_, grandpa_link, _) = &import_setup; + let (_, grandpa_link, _, _) = &import_setup; let justification_stream = grandpa_link.justification_stream(); let shared_authority_set = grandpa_link.shared_authority_set().clone(); @@ -288,31 +303,41 @@ pub fn new_partial( let rpc_backend = backend.clone(); let rpc_statement_store = statement_store.clone(); - let rpc_extensions_builder = move |deny_unsafe, subscription_executor| { - let deps = node_rpc::FullDeps { - client: client.clone(), - pool: pool.clone(), - select_chain: select_chain.clone(), - chain_spec: chain_spec.cloned_box(), - deny_unsafe, - babe: node_rpc::BabeDeps { - keystore: keystore.clone(), - babe_worker_handle: babe_worker_handle.clone(), - }, - grandpa: node_rpc::GrandpaDeps { - shared_voter_state: shared_voter_state.clone(), - shared_authority_set: shared_authority_set.clone(), - justification_stream: justification_stream.clone(), - subscription_executor, - finality_provider: finality_proof_provider.clone(), - }, - statement_store: rpc_statement_store.clone(), - backend: rpc_backend.clone(), - mixnet_api: mixnet_api.as_ref().cloned(), - }; + let rpc_extensions_builder = + move |deny_unsafe, subscription_executor: node_rpc::SubscriptionTaskExecutor| { + let deps = node_rpc::FullDeps { + client: client.clone(), + pool: pool.clone(), + select_chain: select_chain.clone(), + chain_spec: chain_spec.cloned_box(), + deny_unsafe, + babe: node_rpc::BabeDeps { + keystore: keystore.clone(), + babe_worker_handle: babe_worker_handle.clone(), + }, + grandpa: node_rpc::GrandpaDeps { + shared_voter_state: shared_voter_state.clone(), + shared_authority_set: shared_authority_set.clone(), + justification_stream: justification_stream.clone(), + subscription_executor: subscription_executor.clone(), + finality_provider: finality_proof_provider.clone(), + }, + beefy: node_rpc::BeefyDeps { + beefy_finality_proof_stream: beefy_rpc_links + .from_voter_justif_stream + .clone(), + beefy_best_block_stream: beefy_rpc_links + .from_voter_best_beefy_stream + .clone(), + subscription_executor, + }, + statement_store: rpc_statement_store.clone(), + backend: rpc_backend.clone(), + mixnet_api: mixnet_api.as_ref().cloned(), + }; - node_rpc::create_full(deps).map_err(Into::into) - }; + node_rpc::create_full(deps).map_err(Into::into) + }; (rpc_extensions_builder, shared_voter_state2) }; @@ -358,10 +383,24 @@ pub fn new_full_base( mixnet_config: Option, disable_hardware_benchmarks: bool, with_startup_data: impl FnOnce( - &sc_consensus_babe::BabeBlockImport, + &sc_consensus_babe::BabeBlockImport< + Block, + FullClient, + FullBeefyBlockImport, + >, &sc_consensus_babe::BabeLink, ), ) -> Result { + let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled; + let role = config.role.clone(); + let force_authoring = config.force_authoring; + let backoff_authoring_blocks = + Some(sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default()); + let name = config.network.node_name.clone(); + let enable_grandpa = !config.disable_grandpa; + let prometheus_registry = config.prometheus_registry().cloned(); + let enable_offchain_worker = config.offchain_worker.enabled; + let hwbench = (!disable_hardware_benchmarks) .then_some(config.database.path().map(|database_path| { let _ = std::fs::create_dir_all(&database_path); @@ -391,6 +430,24 @@ pub fn new_full_base( grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone()); net_config.add_notification_protocol(grandpa_protocol_config); + let beefy_gossip_proto_name = + beefy::gossip_protocol_name(&genesis_hash, config.chain_spec.fork_id()); + // `beefy_on_demand_justifications_handler` is given to `beefy-gadget` task to be run, + // while `beefy_req_resp_cfg` is added to `config.network.request_response_protocols`. + let (beefy_on_demand_justifications_handler, beefy_req_resp_cfg) = + beefy::communication::request_response::BeefyJustifsRequestHandler::new( + &genesis_hash, + config.chain_spec.fork_id(), + client.clone(), + prometheus_registry.clone(), + ); + + let (beefy_notification_config, beefy_notification_service) = + beefy::communication::beefy_peers_set_config(beefy_gossip_proto_name.clone()); + + net_config.add_notification_protocol(beefy_notification_config); + net_config.add_request_response_protocol(beefy_req_resp_cfg); + let (statement_handler_proto, statement_config) = sc_network_statement::StatementHandlerPrototype::new( genesis_hash, @@ -442,15 +499,6 @@ pub fn new_full_base( task_manager.spawn_handle().spawn("mixnet", None, mixnet); } - let role = config.role.clone(); - let force_authoring = config.force_authoring; - let backoff_authoring_blocks = - Some(sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default()); - let name = config.network.node_name.clone(); - let enable_grandpa = !config.disable_grandpa; - let prometheus_registry = config.prometheus_registry().cloned(); - let enable_offchain_worker = config.offchain_worker.enabled; - let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { config, backend: backend.clone(), @@ -488,7 +536,7 @@ pub fn new_full_base( } } - let (block_import, grandpa_link, babe_link) = import_setup; + let (block_import, grandpa_link, babe_link, beefy_links) = import_setup; (with_startup_data)(&block_import, &babe_link); @@ -582,6 +630,47 @@ pub fn new_full_base( // need a keystore, regardless of which protocol we use below. let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; + // beefy is enabled if its notification service exists + let network_params = beefy::BeefyNetworkParams { + network: network.clone(), + sync: sync_service.clone(), + gossip_protocol_name: beefy_gossip_proto_name, + justifications_protocol_name: beefy_on_demand_justifications_handler.protocol_name(), + notification_service: beefy_notification_service, + _phantom: core::marker::PhantomData::, + }; + let beefy_params = beefy::BeefyParams { + client: client.clone(), + backend: backend.clone(), + payload_provider: beefy_primitives::mmr::MmrRootProvider::new(client.clone()), + runtime: client.clone(), + key_store: keystore.clone(), + network_params, + min_block_delta: 8, + prometheus_registry: prometheus_registry.clone(), + links: beefy_links, + on_demand_justifications_handler: beefy_on_demand_justifications_handler, + }; + + let beefy_gadget = beefy::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params); + // BEEFY is part of consensus, if it fails we'll bring the node down with it to make sure it + // is noticed. + task_manager + .spawn_essential_handle() + .spawn_blocking("beefy-gadget", None, beefy_gadget); + // When offchain indexing is enabled, MMR gadget should also run. + if is_offchain_indexing_enabled { + task_manager.spawn_essential_handle().spawn_blocking( + "mmr-gadget", + None, + mmr_gadget::MmrGadget::start( + client.clone(), + backend.clone(), + sp_mmr_primitives::INDEXING_PREFIX.to_vec(), + ), + ); + } + let grandpa_config = grandpa::Config { // FIXME #1578 make this available through chainspec gossip_duration: std::time::Duration::from_millis(333), 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 caf12a443d36df70c109ffa9c68b552e3e36675e..1465a6497cadb3d79f75910dc2e084c168559c62 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -45,6 +45,10 @@ "grandpa": { "authorities": [] }, + "beefy": { + "authorities": [], + "genesisBlock": 1 + }, "treasury": {}, "sudo": { "key": null diff --git a/substrate/bin/node/cli/tests/websocket_server.rs b/substrate/bin/node/cli/tests/websocket_server.rs index 432a4871cd3785a171dcd6f8d20135dd8072a33c..b34fc82b8be85b133374aed65c9ff28a32cdf163 100644 --- a/substrate/bin/node/cli/tests/websocket_server.rs +++ b/substrate/bin/node/cli/tests/websocket_server.rs @@ -205,8 +205,7 @@ impl WsServer { Ok(soketto::Data::Text(len)) => String::from_utf8(buf[..len].to_vec()) .map(Message::Text) .map_err(|err| Box::new(err) as Box<_>), - Ok(soketto::Data::Binary(len)) => Ok(buf[..len].to_vec()) - .map(Message::Binary), + Ok(soketto::Data::Binary(len)) => Ok(Message::Binary(buf[..len].to_vec())), Err(err) => Err(Box::new(err) as Box<_>), }; Some((ret, (receiver, buf))) diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index cdf4b1ff146e3d0f30f3e0829fe604267444608d..d14cc8ff7605ffa1c7307ce1eedc859e78506c17 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -8,11 +8,14 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } thiserror = "1.0" sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/node/primitives/Cargo.toml b/substrate/bin/node/primitives/Cargo.toml index 40735ff21d44e41f14048adfabec7450857f1c7d..24279ad09c3d9f4576a212d7c67ac24be27b8e22 100644 --- a/substrate/bin/node/primitives/Cargo.toml +++ b/substrate/bin/node/primitives/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 43db4ab9d34f709c43809e5bf909b979eebba6c5..66bd6e9a0a53decb48a694c0dee791c922c884f8 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,6 +24,8 @@ sc-chain-spec = { path = "../../../client/chain-spec" } sc-client-api = { path = "../../../client/api" } sc-consensus-babe = { path = "../../../client/consensus/babe" } sc-consensus-babe-rpc = { path = "../../../client/consensus/babe/rpc" } +sc-consensus-beefy = { path = "../../../client/consensus/beefy" } +sc-consensus-beefy-rpc = { path = "../../../client/consensus/beefy/rpc" } sc-consensus-grandpa = { path = "../../../client/consensus/grandpa" } sc-consensus-grandpa-rpc = { path = "../../../client/consensus/grandpa/rpc" } sc-mixnet = { path = "../../../client/mixnet" } diff --git a/substrate/bin/node/rpc/src/lib.rs b/substrate/bin/node/rpc/src/lib.rs index acc58777e912d546b337dc4ca8a664095a8c33a7..4646524a25babfd7cb1276326d4704abdcc65622 100644 --- a/substrate/bin/node/rpc/src/lib.rs +++ b/substrate/bin/node/rpc/src/lib.rs @@ -37,10 +37,13 @@ use jsonrpsee::RpcModule; use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Nonce}; use sc_client_api::AuxStore; use sc_consensus_babe::BabeWorkerHandle; +use sc_consensus_beefy::communication::notification::{ + BeefyBestBlockStream, BeefyVersionedFinalityProofStream, +}; use sc_consensus_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; -use sc_rpc::SubscriptionTaskExecutor; +pub use sc_rpc::SubscriptionTaskExecutor; pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; @@ -72,6 +75,16 @@ pub struct GrandpaDeps { pub finality_provider: Arc>, } +/// Dependencies for BEEFY +pub struct BeefyDeps { + /// Receives notifications about finality proof events from BEEFY. + pub beefy_finality_proof_stream: BeefyVersionedFinalityProofStream, + /// Receives notifications about best block events from BEEFY. + pub beefy_best_block_stream: BeefyBestBlockStream, + /// Executor to drive the subscription manager in the BEEFY RPC handler. + pub subscription_executor: SubscriptionTaskExecutor, +} + /// Full client dependencies. pub struct FullDeps { /// The client instance to use. @@ -88,6 +101,8 @@ pub struct FullDeps { pub babe: BabeDeps, /// GRANDPA specific dependencies. pub grandpa: GrandpaDeps, + /// BEEFY specific dependencies. + pub beefy: BeefyDeps, /// Shared statement store reference. pub statement_store: Arc, /// The backend used by the node. @@ -106,6 +121,7 @@ pub fn create_full( deny_unsafe, babe, grandpa, + beefy, statement_store, backend, mixnet_api, @@ -133,6 +149,7 @@ where use mmr_rpc::{Mmr, MmrApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; + use sc_consensus_beefy_rpc::{Beefy, BeefyApiServer}; use sc_consensus_grandpa_rpc::{Grandpa, GrandpaApiServer}; use sc_rpc::{ dev::{Dev, DevApiServer}, @@ -205,5 +222,14 @@ where io.merge(mixnet)?; } + io.merge( + Beefy::::new( + beefy.beefy_finality_proof_stream, + beefy.beefy_best_block_stream, + beefy.subscription_executor, + )? + .into_rpc(), + )?; + Ok(io) } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index e53646c0ef4ce19e8f82bcdfc4d918e24d0a413b..4bb5fed2b09a210ff4915609b1b825746ed4f514 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -10,6 +10,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -23,7 +26,7 @@ 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.108", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } @@ -31,6 +34,7 @@ primitive-types = { version = "0.12.0", default-features = false, features = ["c # primitives sp-authority-discovery = { path = "../../../primitives/authority-discovery", default-features = false, features = ["serde"] } sp-consensus-babe = { path = "../../../primitives/consensus/babe", default-features = false, features = ["serde"] } +sp-consensus-beefy = { path = "../../../primitives/consensus/beefy", default-features = false } sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa", default-features = false, features = ["serde"] } sp-block-builder = { path = "../../../primitives/block-builder", default-features = false } sp-genesis-builder = { default-features = false, path = "../../../primitives/genesis-builder" } @@ -69,6 +73,8 @@ pallet-authorship = { path = "../../../frame/authorship", default-features = fal pallet-babe = { path = "../../../frame/babe", default-features = false } pallet-bags-list = { path = "../../../frame/bags-list", default-features = false } pallet-balances = { path = "../../../frame/balances", default-features = false } +pallet-beefy = { path = "../../../frame/beefy", default-features = false } +pallet-beefy-mmr = { path = "../../../frame/beefy-mmr", default-features = false } pallet-bounties = { path = "../../../frame/bounties", default-features = false } pallet-broker = { path = "../../../frame/broker", default-features = false } pallet-child-bounties = { path = "../../../frame/child-bounties", default-features = false } @@ -80,6 +86,7 @@ pallet-democracy = { path = "../../../frame/democracy", default-features = false pallet-election-provider-multi-phase = { path = "../../../frame/election-provider-multi-phase", default-features = false } pallet-election-provider-support-benchmarking = { path = "../../../frame/election-provider-support/benchmarking", default-features = false, optional = true } pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", default-features = false } +pallet-example-tasks = { path = "../../../frame/examples/tasks", default-features = false } pallet-fast-unstake = { path = "../../../frame/fast-unstake", default-features = false } pallet-nis = { path = "../../../frame/nis", default-features = false } pallet-grandpa = { path = "../../../frame/grandpa", default-features = false } @@ -166,6 +173,8 @@ std = [ "pallet-babe/std", "pallet-bags-list/std", "pallet-balances/std", + "pallet-beefy-mmr/std", + "pallet-beefy/std", "pallet-bounties/std", "pallet-broker/std", "pallet-child-bounties/std", @@ -177,6 +186,7 @@ std = [ "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen/std", + "pallet-example-tasks/std", "pallet-fast-unstake/std", "pallet-glutton/std", "pallet-grandpa/std", @@ -236,6 +246,7 @@ std = [ "sp-authority-discovery/std", "sp-block-builder/std", "sp-consensus-babe/std", + "sp-consensus-beefy/std", "sp-consensus-grandpa/std", "sp-core/std", "sp-genesis-builder/std", @@ -279,6 +290,7 @@ runtime-benchmarks = [ "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", + "pallet-example-tasks/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", "pallet-glutton/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", @@ -343,6 +355,8 @@ try-runtime = [ "pallet-babe/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-beefy-mmr/try-runtime", + "pallet-beefy/try-runtime", "pallet-bounties/try-runtime", "pallet-broker/try-runtime", "pallet-child-bounties/try-runtime", @@ -353,6 +367,7 @@ try-runtime = [ "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", + "pallet-example-tasks/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-glutton/try-runtime", "pallet-grandpa/try-runtime", @@ -400,3 +415,8 @@ try-runtime = [ "pallet-whitelist/try-runtime", "sp-runtime/try-runtime", ] +experimental = [ + "frame-support/experimental", + "frame-system/experimental", + "pallet-example-tasks/experimental", +] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 1f4f22f224ac500f4681070a03c392b51af96802..6e7bfb4f4b42c310d420b27f5158b42ad18a8d9b 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -36,8 +36,13 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::{Balanced, Credit, HoldConsideration, ItemOf}, - tokens::{nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount}, + fungible::{ + Balanced, Credit, HoldConsideration, ItemOf, NativeFromLeft, NativeOrWithId, UnionOf, + }, + tokens::{ + imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, + GetSalary, PayFromAccount, + }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency, EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, @@ -57,7 +62,7 @@ use frame_system::{ }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce}; -use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter}; +use pallet_asset_conversion::{Ascending, Chain, WithFirstAsset}; use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600}; use pallet_election_provider_multi_phase::{GeometricDepositBase, SolutionAccuracyOf}; use pallet_identity::legacy::IdentityInfo; @@ -70,6 +75,10 @@ use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use pallet_tx_pause::RuntimeCallNameOf; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_beefy::{ + ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature}, + mmr::MmrLeafVersion, +}; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; @@ -126,7 +135,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); /// Max size for serialized extrinsic params for this testing runtime. /// This is a quite arbitrary but empirically battle tested value. #[cfg(test)] -pub const CALL_PARAMS_MAX_SIZE: usize = 208; +pub const CALL_PARAMS_MAX_SIZE: usize = 244; /// Wasm binary unwrapped. If built with `SKIP_WASM_BUILD`, the function panics. #[cfg(feature = "std")] @@ -304,6 +313,11 @@ impl frame_system::Config for Runtime { impl pallet_insecure_randomness_collective_flip::Config for Runtime {} +impl pallet_example_tasks::Config for Runtime { + type RuntimeTask = RuntimeTask; + type WeightInfo = pallet_example_tasks::weights::SubstrateWeight; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -558,8 +572,11 @@ impl pallet_asset_tx_payment::Config for Runtime { impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Fungibles = Assets; - type OnChargeAssetTransaction = - pallet_asset_conversion_tx_payment::AssetConversionAdapter; + type OnChargeAssetTransaction = pallet_asset_conversion_tx_payment::AssetConversionAdapter< + Balances, + AssetConversion, + Native, + >; } impl pallet_skip_feeless_payment::Config for Runtime { @@ -589,6 +606,7 @@ impl_opaque_keys! { pub im_online: ImOnline, pub authority_discovery: AuthorityDiscovery, pub mixnet: Mixnet, + pub beefy: Beefy, } } @@ -627,6 +645,7 @@ parameter_types! { pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxNominators: u32 = 64; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); + pub const MaxControllersInDeprecationBatch: u32 = 5900; pub OffchainRepeat: BlockNumber = 5; pub HistoryDepth: u32 = 84; } @@ -669,6 +688,7 @@ impl pallet_staking::Config for Runtime { // This a placeholder, to be introduced in the next PR as an instance of bags-list type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type HistoryDepth = HistoryDepth; type EventListeners = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; @@ -697,8 +717,6 @@ parameter_types! { pub const SignedDepositIncreaseFactor: Percent = Percent::from_percent(10); pub const SignedDepositByte: Balance = 1 * CENTS; - pub BetterUnsignedThreshold: Perbill = Perbill::from_rational(1u32, 10_000); - // miner configs pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() @@ -815,7 +833,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type EstimateCallFee = TransactionPayment; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; type MinerTxPriority = MultiPhaseUnsignedPriority; @@ -1547,6 +1564,7 @@ impl pallet_vesting::Config for Runtime { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = pallet_vesting::weights::SubstrateWeight; type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; // `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the // highest number of schedules that encodes less than 2^10. const MAX_VESTING_SCHEDULES: u32 = 28; @@ -1560,6 +1578,17 @@ impl pallet_mmr::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub LeafVersion: MmrLeafVersion = MmrLeafVersion::new(0, 0); +} + +impl pallet_beefy_mmr::Config for Runtime { + type LeafVersion = LeafVersion; + type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; + type LeafExtra = Vec; + type BeefyDataProvider = (); +} + parameter_types! { pub const LotteryPalletId: PalletId = PalletId(*b"py/lotto"); pub const MaxCalls: u32 = 10; @@ -1639,33 +1668,34 @@ impl pallet_assets::Config for Runtime { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub AllowMultiAssetPools: bool = true; pub const PoolSetupFee: Balance = 1 * DOLLARS; // should be more or equal to the existential deposit pub const MintMinLiquidity: Balance = 100; // 100 is good enough when the main currency has 10-12 decimals. - pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero. + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); + pub const Native: NativeOrWithId = NativeOrWithId::Native; } impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type AssetBalance = ::Balance; - type HigherPrecisionBalance = sp_core::U256; - type Assets = Assets; type Balance = u128; - type PoolAssets = PoolAssets; - type AssetId = >::AssetId; - type MultiAssetId = NativeOrAssetId; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = NativeOrWithId; + type Assets = UnionOf, AccountId>; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = Chain< + WithFirstAsset>, + Ascending>, + >; type PoolAssetId = >::AssetId; + type PoolAssets = PoolAssets; + type PoolSetupFee = PoolSetupFee; + type PoolSetupFeeAsset = Native; + type PoolSetupFeeTarget = ResolveAssetTo; type PalletId = AssetConversionPalletId; type LPFee = ConstU32<3>; // means 0.3% - type PoolSetupFee = PoolSetupFee; - type PoolSetupFeeReceiver = AssetConversionOrigin; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; type WeightInfo = pallet_asset_conversion::weights::SubstrateWeight; - type AllowMultiAssetPools = AllowMultiAssetPools; type MaxSwapPathLength = ConstU32<4>; type MintMinLiquidity = MintMinLiquidity; - type MultiAssetIdConverter = NativeOrAssetIdConverter; #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } @@ -1979,7 +2009,6 @@ impl OnUnbalanced> for IntoAuthor { } parameter_types! { - pub storage CoreCount: Option = None; pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; } @@ -1987,36 +2016,24 @@ pub struct CoretimeProvider; impl CoretimeInterface for CoretimeProvider { type AccountId = AccountId; type Balance = Balance; - type BlockNumber = BlockNumber; - fn latest() -> Self::BlockNumber { - System::block_number() - } + type RealyChainBlockNumberProvider = System; fn request_core_count(_count: CoreIndex) {} - fn request_revenue_info_at(_when: Self::BlockNumber) {} + fn request_revenue_info_at(_when: u32) {} fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {} fn assign_core( _core: CoreIndex, - _begin: Self::BlockNumber, + _begin: u32, _assignment: Vec<(CoreAssignment, PartsOf57600)>, - _end_hint: Option, + _end_hint: Option, ) { } - fn check_notify_core_count() -> Option { - let count = CoreCount::get(); - CoreCount::set(&None); - count - } - fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> { + fn check_notify_revenue_info() -> Option<(u32, Self::Balance)> { let revenue = CoretimeRevenue::get(); CoretimeRevenue::set(&None); revenue } #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_core_count(count: u16) { - CoreCount::set(&Some(count)); - } - #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance) { + fn ensure_notify_revenue_info(when: u32, revenue: Self::Balance) { CoretimeRevenue::set(&Some((when, revenue))); } } @@ -2076,6 +2093,11 @@ construct_runtime!( AssetConversionTxPayment: pallet_asset_conversion_tx_payment, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + Beefy: pallet_beefy::{Pallet, Call, Storage, Config, ValidateUnsigned}, + // MMR leaf construction must be before session in order to have leaf contents + // refer to block consistently. see substrate issue #11797 for details. + Mmr: pallet_mmr::{Pallet, Storage}, + MmrLeaf: pallet_beefy_mmr::{Pallet, Storage}, Session: pallet_session, Democracy: pallet_democracy, Council: pallet_collective::, @@ -2105,7 +2127,6 @@ construct_runtime!( Tips: pallet_tips, Assets: pallet_assets::, PoolAssets: pallet_assets::, - Mmr: pallet_mmr, Lottery: pallet_lottery, Nis: pallet_nis, Uniques: pallet_uniques, @@ -2135,6 +2156,7 @@ construct_runtime!( SafeMode: pallet_safe_mode, Statement: pallet_statement, Broker: pallet_broker, + TasksExample: pallet_example_tasks, Mixnet: pallet_mixnet, SkipFeelessPayment: pallet_skip_feeless_payment, } @@ -2200,6 +2222,22 @@ type EventRecord = frame_system::EventRecord< ::Hash, >; +parameter_types! { + pub const BeefySetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); +} + +impl pallet_beefy::Config for Runtime { + type BeefyId = BeefyId; + type MaxAuthorities = MaxAuthorities; + type MaxNominators = ConstU32<0>; + type MaxSetIdSessionEntries = BeefySetIdSessionEntries; + type OnNewValidatorSet = MmrLeaf; + type WeightInfo = (); + type KeyOwnerProof = >::Proof; + type EquivocationReportSystem = + pallet_beefy::EquivocationReportSystem; +} + /// MMR helper types. mod mmr { use super::Runtime; @@ -2227,6 +2265,7 @@ mod benches { [pallet_conviction_voting, ConvictionVoting] [pallet_contracts, Contracts] [pallet_core_fellowship, CoreFellowship] + [tasks_example, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] @@ -2576,20 +2615,19 @@ impl_runtime_apis! { impl pallet_asset_conversion::AssetConversionApi< Block, Balance, - u128, - NativeOrAssetId + NativeOrWithId > for Runtime { - fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + fn quote_price_exact_tokens_for_tokens(asset1: NativeOrWithId, asset2: NativeOrWithId, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) } - fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + fn quote_price_tokens_for_exact_tokens(asset1: NativeOrWithId, asset2: NativeOrWithId, amount: Balance, include_fee: bool) -> Option { AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) } - fn get_reserves(asset1: NativeOrAssetId, asset2: NativeOrAssetId) -> Option<(Balance, Balance)> { - AssetConversion::get_reserves(&asset1, &asset2).ok() + fn get_reserves(asset1: NativeOrWithId, asset2: NativeOrWithId) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(asset1, asset2).ok() } } @@ -2654,6 +2692,42 @@ impl_runtime_apis! { } } + #[api_version(3)] + impl sp_consensus_beefy::BeefyApi for Runtime { + fn beefy_genesis() -> Option { + Beefy::genesis_block() + } + + fn validator_set() -> Option> { + Beefy::validator_set() + } + + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: sp_consensus_beefy::EquivocationProof< + BlockNumber, + BeefyId, + BeefySignature, + >, + key_owner_proof: sp_consensus_beefy::OpaqueKeyOwnershipProof, + ) -> Option<()> { + let key_owner_proof = key_owner_proof.decode()?; + + Beefy::submit_unsigned_equivocation_report( + equivocation_proof, + key_owner_proof, + ) + } + + fn generate_key_ownership_proof( + _set_id: sp_consensus_beefy::ValidatorSetId, + authority_id: BeefyId, + ) -> Option { + Historical::prove((sp_consensus_beefy::KEY_TYPE, authority_id)) + .map(|p| p.encode()) + .map(sp_consensus_beefy::OpaqueKeyOwnershipProof::new) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 513cb22b6a26d721e2d93abecd2f6b78c08d75c1..76188ed446c0870229bcfebb772b6c6d98e09e64 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index eecbf64775b67460e08d27e47c00d9cf28554b19..6ec21fbe09342d1f704a4ed1a6f79b6160c27a01 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -24,7 +24,7 @@ use kitchensink_runtime::{ GrandpaConfig, IndicesConfig, RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, BABE_GENESIS_EPOCH_CONFIG, }; -use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; +use sp_keyring::Ed25519Keyring; use sp_runtime::Perbill; /// Create genesis runtime configuration for tests. @@ -52,13 +52,9 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { balances: BalancesConfig { balances: endowed }, session: SessionConfig { keys: vec![ - (alice(), dave(), to_session_keys(&Ed25519Keyring::Alice, &Sr25519Keyring::Alice)), - (bob(), eve(), to_session_keys(&Ed25519Keyring::Bob, &Sr25519Keyring::Bob)), - ( - charlie(), - ferdie(), - to_session_keys(&Ed25519Keyring::Charlie, &Sr25519Keyring::Charlie), - ), + (alice(), dave(), session_keys_from_seed(Ed25519Keyring::Alice.into())), + (bob(), eve(), session_keys_from_seed(Ed25519Keyring::Bob.into())), + (charlie(), ferdie(), session_keys_from_seed(Ed25519Keyring::Charlie.into())), ], }, staking: StakingConfig { @@ -79,6 +75,7 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { ..Default::default() }, grandpa: GrandpaConfig { authorities: vec![], _config: Default::default() }, + beefy: Default::default(), im_online: Default::default(), authority_discovery: Default::default(), democracy: Default::default(), diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index 9940077c9da638360e26d9722d61fbebdc6d124c..6c885cc039a1511f3dcf33726d13e6d81ecbddd3 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -20,8 +20,10 @@ use codec::Encode; use kitchensink_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; +use node_cli::chain_spec::get_from_seed; use node_primitives::{AccountId, Balance, Nonce}; -use sp_keyring::{AccountKeyring, Ed25519Keyring, Sr25519Keyring}; +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_keyring::AccountKeyring; use sp_runtime::generic::Era; /// Alice's account id. @@ -55,16 +57,14 @@ pub fn ferdie() -> AccountId { } /// Convert keyrings into `SessionKeys`. -pub fn to_session_keys( - ed25519_keyring: &Ed25519Keyring, - sr25519_keyring: &Sr25519Keyring, -) -> SessionKeys { +pub fn session_keys_from_seed(seed: &str) -> SessionKeys { SessionKeys { - grandpa: ed25519_keyring.to_owned().public().into(), - babe: sr25519_keyring.to_owned().public().into(), - im_online: sr25519_keyring.to_owned().public().into(), - authority_discovery: sr25519_keyring.to_owned().public().into(), - mixnet: sr25519_keyring.to_owned().public().into(), + grandpa: get_from_seed::(seed).into(), + babe: get_from_seed::(seed).into(), + im_online: get_from_seed::(seed).into(), + authority_discovery: get_from_seed::(seed).into(), + mixnet: get_from_seed::(seed).into(), + beefy: get_from_seed::(seed).into(), } } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index bfa2951cf0020a66e60e1ba8ff73120880cc4fae..3848ef915884db81f39b021d2745e103ad7a7665 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,8 +23,8 @@ name = "chain-spec-builder" crate-type = ["rlib"] [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } log = "0.4.17" sc-chain-spec = { path = "../../../client/chain-spec" } -serde_json = "1.0.108" +serde_json = "1.0.111" sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } diff --git a/substrate/bin/utils/chain-spec-builder/src/lib.rs b/substrate/bin/utils/chain-spec-builder/src/lib.rs index 60ec0f1a656b42095d760a3016efb90681f7a242..8c78030c885484b7d1f9ba3213dbfa76faa36c52 100644 --- a/substrate/bin/utils/chain-spec-builder/src/lib.rs +++ b/substrate/bin/utils/chain-spec-builder/src/lib.rs @@ -30,46 +30,52 @@ //! ## Typical use-cases. //! ##### Get default config from runtime. //! -//! Query the default genesis config from the provided `runtime.wasm` and use it in the chain -//! spec. Tool can also store runtime's default genesis config in given file: -//! ```text -//! chain-spec-builder create -r runtime.wasm default /dev/stdout +//! Query the default genesis config from the provided `runtime.wasm` and use it in the chain +//! spec. The tool allows specifying where to write the chain spec, and optionally also where the +//! write the default genesis state config (which is `/dev/stdout` in the following example): +//! ```text +//! chain-spec-builder --chain_spec_path ./my_chain_spec.json create -r runtime.wasm default /dev/stdout //! ``` -//! -//! _Note:_ [`GenesisBuilder::create_default_config`][sp-genesis-builder-create] runtime function is called. +//! +//! _Note:_ [`GenesisBuilder::create_default_config`][sp-genesis-builder-create] runtime function is +//! called. //! //! //! ##### Generate raw storage chain spec using genesis config patch. //! //! Patch the runtime's default genesis config with provided `patch.json` and generate raw //! storage (`-s`) version of chain spec: -//! ```text +//! +//! ```bash //! chain-spec-builder create -s -r runtime.wasm patch patch.json //! ``` -//! +//! //! _Note:_ [`GenesisBuilder::build_config`][sp-genesis-builder-build] runtime function is called. //! //! ##### Generate raw storage chain spec using full genesis config. //! //! Build the chain spec using provided full genesis config json file. No defaults will be used: -//! ```text +//! +//! ```bash //! chain-spec-builder create -s -r runtime.wasm full full-genesis-config.json //! ``` -//! +//! //! _Note_: [`GenesisBuilder::build_config`][sp-genesis-builder-build] runtime function is called. //! //! ##### Generate human readable chain spec using provided genesis config patch. -//! ```text +//! ```bash //! chain-spec-builder create -r runtime.wasm patch patch.json //! ``` -//! +//! //! ##### Generate human readable chain spec using provided full genesis config. -//! ```text +//! +//! ```bash //! chain-spec-builder create -r runtime.wasm full full-genesis-config.json //! ``` -//! +//! //! ##### Extra tools. -//! The `chain-spec-builder` provides also some extra utilities: [`VerifyCmd`], [`ConvertToRawCmd`], [`UpdateCodeCmd`]. +//! The `chain-spec-builder` provides also some extra utilities: [`VerifyCmd`], [`ConvertToRawCmd`], +//! [`UpdateCodeCmd`]. //! //! [`sc-chain-spec`]: ../sc_chain_spec/index.html //! [`node-cli`]: ../node_cli/index.html diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index 1769afd865bb3425ea29dcd4dc5b5798363f1c43..d4ecfda37359dcebb832a2cb9682511f0949e5c5 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,5 +20,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index 31c714180ce57b0684ebfb7a8e27f7d2b42857ea..ef13c1a4573f36665e42b84f3bcbca8436afe45d 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -10,6 +10,9 @@ description = "Collection of allocator implementations." documentation = "https://docs.rs/sc-allocator" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 57a364e791fb084eb10af2b4aa9b976ac258509d..8c50b872914419478c51ca5d498e45243b64082f 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -10,6 +10,9 @@ description = "Substrate client interfaces." documentation = "https://docs.rs/sc-client-api" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index 40c2162c7996bbec69853f552e53439fbae6a236..a8a28a501ea821c9a954dfb0256808991337d3af 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true description = "Substrate authority discovery." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.51.3", features = ["ed25519", "kad"] } +libp2p = { version = "0.51.4", features = ["ed25519", "kad"] } multihash = { version = "0.18.1", default-features = false, features = [ "sha2", "std", @@ -39,7 +42,7 @@ sp-blockchain = { path = "../../primitives/blockchain" } sp-core = { path = "../../primitives/core" } sp-keystore = { path = "../../primitives/keystore" } sp-runtime = { path = "../../primitives/runtime" } -async-trait = "0.1.56" +async-trait = "0.1.74" multihash-codetable = { version = "0.1.1", features = [ "digest", "serde", diff --git a/substrate/client/basic-authorship/Cargo.toml b/substrate/client/basic-authorship/Cargo.toml index 1d60fc7f53e3b5903153132fc48a6f8c026983dc..926909ec7b764ed4f0c90c23f5b2a0c91c101311 100644 --- a/substrate/client/basic-authorship/Cargo.toml +++ b/substrate/client/basic-authorship/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Basic implementation of block-authoring logic." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/block-builder/Cargo.toml b/substrate/client/block-builder/Cargo.toml index 852ee84f89b85c63fd1cca737d5c812550803aa8..4477f5f1d776c43ebd9129f4350216d6d638e5c9 100644 --- a/substrate/client/block-builder/Cargo.toml +++ b/substrate/client/block-builder/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate block builder" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index d041d5bfd2b7625caf3f6ce487972287108817cd..8af9e1b4758c2e067c437e40167920d583dad1aa 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -9,14 +9,17 @@ repository.workspace = true description = "Substrate chain configurations." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } memmap2 = "0.5.0" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" sc-client-api = { path = "../api" } sc-chain-spec-derive = { path = "derive" } sc-executor = { path = "../executor" } diff --git a/substrate/client/chain-spec/derive/Cargo.toml b/substrate/client/chain-spec/derive/Cargo.toml index 4dea856b03f24298f48879db84a70648b246f345..f9e291f897c9814fbff4d8de5e36657de230f240 100644 --- a/substrate/client/chain-spec/derive/Cargo.toml +++ b/substrate/client/chain-spec/derive/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Macros to derive chain spec extension traits implementation." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = "2.0.39" +syn = "2.0.48" diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index 8d97d941022978198e14015aaa365f12d9b218b6..fe8fcfda216e1fa86215daf93add2aa6adc78bd9 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -784,9 +784,7 @@ fn json_eval_value_at_key( path: &mut VecDeque<&str>, fun: &dyn Fn(&json::Value) -> bool, ) -> bool { - let Some(key) = path.pop_front() else { - return false; - }; + let Some(key) = path.pop_front() else { return false }; if path.is_empty() { doc.as_object().map_or(false, |o| o.get(key).map_or(false, |v| fun(v))) diff --git a/substrate/client/chain-spec/src/extension.rs b/substrate/client/chain-spec/src/extension.rs index 25ab011a05b323bf8874519b2f1efcb5533d4f91..f2939741535f730787846d2a641a2721f5cec9e5 100644 --- a/substrate/client/chain-spec/src/extension.rs +++ b/substrate/client/chain-spec/src/extension.rs @@ -284,7 +284,7 @@ where } } -/// A subset if the `Extension` trait that only allows for quering extensions. +/// A subset of the `Extension` trait that only allows for quering extensions. pub trait GetExtension { /// Get an extension of specific type. fn get_any(&self, t: TypeId) -> &dyn Any; diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 68f3d88604951cded7cc2aa61737183e765c7a98..d6ef99fafdd0325b0136f3013453465f361bf2bb 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -141,7 +141,7 @@ where mod tests { use super::*; use serde_json::{from_str, json}; - pub use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; + pub use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration}; #[test] fn get_default_config_works() { diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index d75eac1ac982c5d35f3c4a48b30d98d279316365..526db17fa3265d87e23800b21fb5b8c589cb7aa5 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -9,25 +9,28 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" -chrono = "0.4.27" -clap = { version = "4.4.10", features = ["derive", "string", "wrap_help"] } +chrono = "0.4.31" +clap = { version = "4.4.13", 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" -names = { version = "0.13.0", default-features = false } +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.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" thiserror = "1.0.48" bip39 = "2.0.0" tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "signal"] } diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index bc9648f683a880afe7eded0a079efdbcafcd5cc3..89a63a944166250cfa3fd6ca603ba9b7dc3fe4a3 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = "0.4.17" diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index c8cff0981b36f89e0b6f9120f4c6378f7c6793e7..40c69d5780a537fb63c37601c0403712960c0bea 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -10,17 +10,20 @@ repository.workspace = true documentation = "https://docs.rs/sc-consensus-babe" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" log = "0.4.17" num-bigint = "0.4.3" num-rational = "0.4.1" -num-traits = "0.2.8" +num-traits = "0.2.17" parking_lot = "0.12.1" thiserror = "1.0" fork-tree = { path = "../../../utils/fork-tree" } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index 913dd990fd33e3cce30576577c125dfb6fef8001..753f8fbc821d0d747e8cfcb056aad134c1162c96 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -9,13 +9,16 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } futures = "0.3.21" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" sc-consensus-babe = { path = ".." } sc-consensus-epochs = { path = "../../epochs" } @@ -30,7 +33,7 @@ sp-keystore = { path = "../../../../primitives/keystore" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" tokio = "1.22.0" sc-consensus = { path = "../../common" } sc-keystore = { path = "../../../keystore" } diff --git a/substrate/client/consensus/babe/src/authorship.rs b/substrate/client/consensus/babe/src/authorship.rs index 3580caba746141a8a9faf9e73216cb8903667acf..fb1722398012b92701f66bcbe5c6c8c292d9084e 100644 --- a/substrate/client/consensus/babe/src/authorship.rs +++ b/substrate/client/consensus/babe/src/authorship.rs @@ -249,7 +249,7 @@ fn claim_primary_slot( .make_bytes::( AUTHORING_SCORE_VRF_CONTEXT, &data.as_ref(), - &vrf_signature.output, + &vrf_signature.pre_output, ) .map(|bytes| u128::from_le_bytes(bytes) < threshold) .unwrap_or_default(); diff --git a/substrate/client/consensus/babe/src/tests.rs b/substrate/client/consensus/babe/src/tests.rs index d097f37c325790f134c879d7a38e7a9ba12c559a..38c9e1ff6ac25cc26151c668902838ec65ab6189 100644 --- a/substrate/client/consensus/babe/src/tests.rs +++ b/substrate/client/consensus/babe/src/tests.rs @@ -411,7 +411,7 @@ async fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + ' let mut net = net.lock(); net.poll(cx); for p in net.peers() { - for (h, e) in p.failed_verifications() { + if let Some((h, e)) = p.failed_verifications().into_iter().next() { panic!("Verification failed for {:?}: {}", h, e); } } @@ -580,7 +580,7 @@ fn claim_vrf_check() { }; let data = make_vrf_sign_data(&epoch.randomness.clone(), 0.into(), epoch.epoch_index); let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); - assert_eq!(pre_digest.vrf_signature.output, sign.output); + assert_eq!(pre_digest.vrf_signature.pre_output, sign.pre_output); // We expect a SecondaryVRF claim for slot 1 let pre_digest = match claim_slot(1.into(), &epoch, &keystore).unwrap().0 { @@ -589,7 +589,7 @@ fn claim_vrf_check() { }; let data = make_vrf_sign_data(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); - assert_eq!(pre_digest.vrf_signature.output, sign.output); + assert_eq!(pre_digest.vrf_signature.pre_output, sign.pre_output); // Check that correct epoch index has been used if epochs are skipped (primary VRF) let slot = Slot::from(103); @@ -601,7 +601,7 @@ fn claim_vrf_check() { let data = make_vrf_sign_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(claim.vrf_signature.output, sign.output); + assert_eq!(claim.vrf_signature.pre_output, sign.pre_output); // Check that correct epoch index has been used if epochs are skipped (secondary VRF) let slot = Slot::from(100); @@ -613,7 +613,7 @@ fn claim_vrf_check() { let data = make_vrf_sign_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(pre_digest.vrf_signature.output, sign.output); + assert_eq!(pre_digest.vrf_signature.pre_output, sign.pre_output); } // Propose and import a new BABE block on top of the given parent. diff --git a/substrate/client/consensus/babe/src/verification.rs b/substrate/client/consensus/babe/src/verification.rs index 3de5eacc2c519a0b38a9484a687e4975a2c759e6..c6e4ec0c10c135c77f778367d95103c490d9e029 100644 --- a/substrate/client/consensus/babe/src/verification.rs +++ b/substrate/client/consensus/babe/src/verification.rs @@ -185,7 +185,7 @@ fn check_primary_header( .make_bytes::( AUTHORING_SCORE_VRF_CONTEXT, &data.as_ref(), - &pre_digest.vrf_signature.output, + &pre_digest.vrf_signature.pre_output, ) .map(u128::from_le_bytes) .map_err(|_| babe_err(Error::VrfVerificationFailed))?; diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 6ee70b523bc1447a1b305918079183cbaa9c4faf..c54452faebe9659b4d40476fd7d5db0637e5cd54 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -8,10 +8,13 @@ repository.workspace = true description = "BEEFY Client gadget for substrate" homepage = "https://substrate.io" +[lints] +workspace = true + [dependencies] array-bytes = "6.1" async-channel = "1.8.0" -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } fnv = "1.0.6" futures = "0.3" @@ -36,11 +39,12 @@ sp-core = { path = "../../../primitives/core" } sp-keystore = { path = "../../../primitives/keystore" } sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" } sp-runtime = { path = "../../../primitives/runtime" } +tokio = "1.22.0" + [dev-dependencies] -serde = "1.0.193" +serde = "1.0.195" tempfile = "3.1.0" -tokio = "1.22.0" sc-block-builder = { path = "../../block-builder" } sc-network-test = { path = "../../network/test" } sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index 35041a1208fa5ad4dfafe9c6a565db93ba023ea9..198d3d81642203cfe2bb1ac909fa891d26e175a0 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -8,13 +8,16 @@ repository.workspace = true description = "RPC for the BEEFY Client gadget for substrate" homepage = "https://substrate.io" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } log = "0.4" parking_lot = "0.12.1" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" sc-consensus-beefy = { path = ".." } sp-consensus-beefy = { path = "../../../../primitives/consensus/beefy" } @@ -23,7 +26,7 @@ sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" 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/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 342cd0511a511bc0fc520c438fc23abbbd3a9897..645a10b2a1d43f9bb879a799ac9625b2f623506c 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -260,7 +260,11 @@ where /// /// Only votes for `set_id` and rounds `start <= round <= end` will be accepted. pub(crate) fn update_filter(&self, filter: GossipFilterCfg) { - debug!(target: LOG_TARGET, "🥩 New gossip filter {:?}", filter); + debug!( + target: LOG_TARGET, + "🥩 New gossip filter: start {:?}, end {:?}, validator set id {:?}", + filter.start, filter.end, filter.validator_set.id() + ); self.gossip_filter.write().update(filter); } diff --git a/substrate/client/consensus/beefy/src/import.rs b/substrate/client/consensus/beefy/src/import.rs index 5b2abb20acede2764502bfadb1948550c5c7a8b2..6eced17b58ffb275429e48db08032199373efa7a 100644 --- a/substrate/client/consensus/beefy/src/import.rs +++ b/substrate/client/consensus/beefy/src/import.rs @@ -142,6 +142,16 @@ where // Run inner block import. let inner_import_result = self.inner.import_block(block).await?; + match self.backend.state_at(hash) { + Ok(_) => {}, + Err(_) => { + // The block is imported as part of some chain sync. + // The voter doesn't need to process it now. + // It will be detected and processed as part of the voter state init. + return Ok(inner_import_result) + }, + } + match (beefy_encoded, &inner_import_result) { (Some(encoded), ImportResult::Imported(_)) => { match self.decode_and_verify(&encoded, number, hash) { diff --git a/substrate/client/consensus/beefy/src/lib.rs b/substrate/client/consensus/beefy/src/lib.rs index b3ff11add27e07ed47fcb1beea864b6b3048c78f..2e2e22288e3b18d9a6db0b0b0007467bf3d8f96c 100644 --- a/substrate/client/consensus/beefy/src/lib.rs +++ b/substrate/client/consensus/beefy/src/lib.rs @@ -33,7 +33,7 @@ use crate::{ worker::PersistedState, }; use futures::{stream::Fuse, StreamExt}; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use parking_lot::Mutex; use prometheus::Registry; use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer}; @@ -56,6 +56,7 @@ use std::{ collections::{BTreeMap, VecDeque}, marker::PhantomData, sync::Arc, + time::Duration, }; mod aux_schema; @@ -78,6 +79,8 @@ mod tests; const LOG_TARGET: &str = "beefy"; +const HEADER_SYNC_DELAY: Duration = Duration::from_secs(60); + /// A convenience BEEFY client trait that defines all the type bounds a BEEFY client /// has to satisfy. Ideally that should actually be a trait alias. Unfortunately as /// of today, Rust does not allow a type alias to be used as a trait bound. Tracking @@ -292,21 +295,29 @@ pub async fn start_beefy_gadget( // select recoverable errors. loop { // Wait for BEEFY pallet to be active before starting voter. - let persisted_state = match wait_for_runtime_pallet( + let (beefy_genesis, best_grandpa) = match wait_for_runtime_pallet( &*runtime, &mut beefy_comms.gossip_engine, &mut finality_notifications, ) .await - .and_then(|(beefy_genesis, best_grandpa)| { - load_or_init_voter_state( - &*backend, - &*runtime, - beefy_genesis, - best_grandpa, - min_block_delta, - ) - }) { + { + Ok(res) => res, + Err(e) => { + error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); + return + }, + }; + + let persisted_state = match load_or_init_voter_state( + &*backend, + &*runtime, + beefy_genesis, + best_grandpa, + min_block_delta, + ) + .await + { Ok(state) => state, Err(e) => { error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); @@ -357,7 +368,7 @@ pub async fn start_beefy_gadget( } } -fn load_or_init_voter_state( +async fn load_or_init_voter_state( backend: &BE, runtime: &R, beefy_genesis: NumberFor, @@ -371,28 +382,70 @@ where R::Api: BeefyApi, { // Initialize voter state from AUX DB if compatible. - crate::aux_schema::load_persistent(backend)? + if let Some(mut state) = crate::aux_schema::load_persistent(backend)? // Verify state pallet genesis matches runtime. .filter(|state| state.pallet_genesis() == beefy_genesis) - .and_then(|mut state| { - // Overwrite persisted state with current best GRANDPA block. - state.set_best_grandpa(best_grandpa.clone()); - // Overwrite persisted data with newly provided `min_block_delta`. - state.set_min_block_delta(min_block_delta); - info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); - Some(Ok(state)) - }) - // No valid voter-state persisted, re-initialize from pallet genesis. - .unwrap_or_else(|| { - initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta) - }) + { + // 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 header = best_grandpa.clone(); + while *header.number() > state.best_beefy() { + header = + wait_for_parent_header(backend.blockchain(), header, HEADER_SYNC_DELAY).await?; + } + return Ok(state) + } + + // No valid voter-state persisted, re-initialize from pallet genesis. + initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta).await +} + +/// Waits until the parent header of `current` is available and returns it. +/// +/// When the node uses GRANDPA warp sync it initially downloads only the mandatory GRANDPA headers. +/// The rest of the headers (gap sync) are lazily downloaded later. But the BEEFY voter also needs +/// the headers in range `[beefy_genesis..=best_grandpa]` to be available. This helper method +/// enables us to wait until these headers have been synced. +async fn wait_for_parent_header( + blockchain: &BC, + current: ::Header, + delay: Duration, +) -> ClientResult<::Header> +where + B: Block, + BC: BlockchainBackend, +{ + if *current.number() == Zero::zero() { + let msg = format!("header {} is Genesis, there is no parent for it", current.hash()); + warn!(target: LOG_TARGET, "{}", msg); + return Err(ClientError::UnknownBlock(msg)) + } + loop { + match blockchain.header(*current.parent_hash())? { + Some(parent) => return Ok(parent), + None => { + info!( + target: LOG_TARGET, + "🥩 Parent of header number {} not found. \ + BEEFY gadget waiting for header sync to finish ...", + current.number() + ); + tokio::time::sleep(delay).await; + }, + } + } } // If no persisted state present, walk back the chain from first GRANDPA notification to either: // - latest BEEFY finalized block, or if none found on the way, // - BEEFY pallet genesis; // Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to finalize. -fn initialize_voter_state( +async fn initialize_voter_state( backend: &BE, runtime: &R, beefy_genesis: NumberFor, @@ -405,6 +458,8 @@ where R: ProvideRuntimeApi, R::Api: BeefyApi, { + let blockchain = backend.blockchain(); + let beefy_genesis = runtime .runtime_api() .beefy_genesis(best_grandpa.hash()) @@ -414,7 +469,6 @@ where .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?; // Walk back the imported blocks and initialize voter either, at the last block with // a BEEFY justification, or at pallet genesis block; voter will resume from there. - let blockchain = backend.blockchain(); let mut sessions = VecDeque::new(); let mut header = best_grandpa.clone(); let state = loop { @@ -432,7 +486,7 @@ where let best_beefy = *header.number(); // If no session boundaries detected so far, just initialize new rounds here. if sessions.is_empty() { - let active_set = expect_validator_set(runtime, backend, &header)?; + let active_set = expect_validator_set(runtime, backend, &header).await?; let mut rounds = Rounds::new(best_beefy, active_set); // Mark the round as already finalized. rounds.conclude(best_beefy); @@ -451,7 +505,7 @@ where if *header.number() == beefy_genesis { // We've reached BEEFY genesis, initialize voter here. - let genesis_set = expect_validator_set(runtime, backend, &header)?; + let genesis_set = expect_validator_set(runtime, backend, &header).await?; info!( target: LOG_TARGET, "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ @@ -481,7 +535,7 @@ where } // Move up the chain. - header = blockchain.expect_header(*header.parent_hash())?; + header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; }; aux_schema::write_current_version(backend)?; @@ -532,7 +586,12 @@ where Err(ClientError::Backend(err_msg)) } -fn expect_validator_set( +/// Provides validator set active `at_header`. It tries to get it from state, otherwise falls +/// back to walk up the chain looking the validator set enactment in header digests. +/// +/// Note: function will `async::sleep()` when walking back the chain if some needed header hasn't +/// been synced yet (as it happens when warp syncing when headers are synced in the background). +async fn expect_validator_set( runtime: &R, backend: &BE, at_header: &B::Header, @@ -543,25 +602,24 @@ where R: ProvideRuntimeApi, R::Api: BeefyApi, { - debug!(target: LOG_TARGET, "🥩 Try to find validator set active at header: {:?}", at_header); - runtime - .runtime_api() - .validator_set(at_header.hash()) - .ok() - .flatten() - .or_else(|| { - // if state unavailable, fallback to walking up the chain looking for the header - // Digest emitted when validator set active 'at_header' was enacted. - let blockchain = backend.blockchain(); - let mut header = at_header.clone(); - loop { - debug!(target: LOG_TARGET, "🥩 look for auth set change digest in header number: {:?}", *header.number()); - match worker::find_authorities_change::(&header) { - Some(active) => return Some(active), - // Move up the chain. - None => header = blockchain.expect_header(*header.parent_hash()).ok()?, - } + 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); + 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) { + Some(active) => return Ok(active), + // Move up the chain. Ultimately we'll get it from chain genesis state, or error out + // there. + None => + header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?, } - }) - .ok_or_else(|| ClientError::Backend("Could not find initial validator set".into())) + } + } } diff --git a/substrate/client/consensus/beefy/src/round.rs b/substrate/client/consensus/beefy/src/round.rs index 6f400ce47843cb9e6579c35330767104419f5728..47414c60fdb5fc8fc94e60136481ebaefd4d7560 100644 --- a/substrate/client/consensus/beefy/src/round.rs +++ b/substrate/client/consensus/beefy/src/round.rs @@ -19,7 +19,7 @@ use crate::LOG_TARGET; use codec::{Decode, Encode}; -use log::debug; +use log::{debug, info}; use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, Commitment, EquivocationProof, SignedCommitment, ValidatorSet, ValidatorSetId, VoteMessage, @@ -194,7 +194,11 @@ where self.previous_votes.retain(|&(_, number), _| number > round_num); self.mandatory_done = self.mandatory_done || round_num == self.session_start; self.best_done = self.best_done.max(Some(round_num)); - debug!(target: LOG_TARGET, "🥩 Concluded round #{}", round_num); + if round_num == self.session_start { + info!(target: LOG_TARGET, "🥩 Concluded mandatory round #{}", round_num); + } else { + debug!(target: LOG_TARGET, "🥩 Concluded optional round #{}", round_num); + } } } diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 3f800166e26ab72ba338bf146e550c5d0ad56d37..17065432564268a4c91c22226083d67229cb9d9c 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -378,7 +378,7 @@ async fn voter_init_setup( ); let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap(); - load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1) + load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1).await } // Spawns beefy voters. Returns a future to spawn on the runtime. @@ -1026,7 +1026,7 @@ async fn should_initialize_voter_at_genesis() { assert_eq!(rounds.validator_set_id(), validator_set.id()); // verify next vote target is mandatory block 1 - assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_beefy(), 0); assert_eq!(persisted_state.best_grandpa_number(), 13); assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1)); @@ -1072,8 +1072,9 @@ async fn should_initialize_voter_at_custom_genesis() { ); let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let persisted_state = - load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1).unwrap(); + let persisted_state = load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1) + .await + .unwrap(); // Test initialization at session boundary. // verify voter initialized with single session starting at block `custom_pallet_genesis` (7) @@ -1085,7 +1086,7 @@ async fn should_initialize_voter_at_custom_genesis() { assert_eq!(rounds.validator_set_id(), validator_set.id()); // verify next vote target is mandatory block 7 - assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_beefy(), 0); assert_eq!(persisted_state.best_grandpa_number(), 8); assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis)); @@ -1107,7 +1108,9 @@ async fn should_initialize_voter_at_custom_genesis() { let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); let new_persisted_state = - load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1).unwrap(); + load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1) + .await + .unwrap(); // verify voter initialized with single session starting at block `new_pallet_genesis` (10) let sessions = new_persisted_state.voting_oracle().sessions(); @@ -1118,7 +1121,7 @@ async fn should_initialize_voter_at_custom_genesis() { assert_eq!(rounds.validator_set_id(), new_validator_set.id()); // verify next vote target is mandatory block 10 - assert_eq!(new_persisted_state.best_beefy_block(), 0); + assert_eq!(new_persisted_state.best_beefy(), 0); assert_eq!(new_persisted_state.best_grandpa_number(), 10); assert_eq!(new_persisted_state.voting_oracle().voting_target(), Some(new_pallet_genesis)); @@ -1171,7 +1174,7 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { assert_eq!(rounds.validator_set_id(), validator_set.id()); // verify block 10 is correctly marked as finalized - assert_eq!(persisted_state.best_beefy_block(), 10); + assert_eq!(persisted_state.best_beefy(), 10); assert_eq!(persisted_state.best_grandpa_number(), 13); // verify next vote target is diff-power-of-two block 12 assert_eq!(persisted_state.voting_oracle().voting_target(), Some(12)); @@ -1224,7 +1227,7 @@ async fn should_initialize_voter_at_latest_finalized() { assert_eq!(rounds.validator_set_id(), validator_set.id()); // verify next vote target is 13 - assert_eq!(persisted_state.best_beefy_block(), 12); + assert_eq!(persisted_state.best_beefy(), 12); assert_eq!(persisted_state.best_grandpa_number(), 13); assert_eq!(persisted_state.voting_oracle().voting_target(), Some(13)); @@ -1272,7 +1275,7 @@ async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() { assert_eq!(rounds.validator_set_id(), validator_set.id()); // verify next vote target is mandatory block 7 (genesis) - assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_beefy(), 0); assert_eq!(persisted_state.best_grandpa_number(), 30); assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis)); diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index 1fbda9740531958bfe8670c47dcfcb850ba31fc2..26f940f05f189e27acac763b0ea446116a2a9411 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -212,7 +212,7 @@ impl VoterOracle { // Accept any vote for a GRANDPA finalized block in a better round. Ok(( rounds.session_start().max(self.best_beefy_block), - (*self.best_grandpa_block_header.number()).into(), + (*self.best_grandpa_block_header.number()), )) } else { // Current session has mandatory not done. @@ -298,6 +298,10 @@ impl PersistedState { self.voting_oracle.min_block_delta = min_block_delta.max(1); } + pub fn best_beefy(&self) -> NumberFor { + self.voting_oracle.best_beefy_block + } + pub(crate) fn set_best_beefy(&mut self, best_beefy: NumberFor) { self.voting_oracle.best_beefy_block = best_beefy; } @@ -1094,10 +1098,6 @@ pub(crate) mod tests { self.voting_oracle.active_rounds() } - pub fn best_beefy_block(&self) -> NumberFor { - self.voting_oracle.best_beefy_block - } - pub fn best_grandpa_number(&self) -> NumberFor { *self.voting_oracle.best_grandpa_block_header.number() } @@ -1511,7 +1511,7 @@ pub(crate) mod tests { }; // no 'best beefy block' or finality proofs - assert_eq!(worker.persisted_state.best_beefy_block(), 0); + assert_eq!(worker.persisted_state.best_beefy(), 0); poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); @@ -1534,7 +1534,7 @@ pub(crate) mod tests { // try to finalize block #1 worker.finalize(justif.clone()).unwrap(); // verify block finalized - assert_eq!(worker.persisted_state.best_beefy_block(), 1); + assert_eq!(worker.persisted_state.best_beefy(), 1); poll_fn(move |cx| { // expect Some(hash-of-block-1) match best_block_stream.poll_next_unpin(cx) { @@ -1571,7 +1571,7 @@ pub(crate) mod tests { // new session starting at #2 is in front assert_eq!(worker.active_rounds().unwrap().session_start(), 2); // verify block finalized - assert_eq!(worker.persisted_state.best_beefy_block(), 2); + assert_eq!(worker.persisted_state.best_beefy(), 2); poll_fn(move |cx| { match best_block_stream.poll_next_unpin(cx) { // expect Some(hash-of-block-2) diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 95ee02a9262e11ce7de14a4f4849182bcaf51acb..ba0147b895245bce8f1d55919ec4de203e977e3b 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true description = "Collection of common consensus specific imlementations for Substrate (client)" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +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"] } diff --git a/substrate/client/consensus/epochs/Cargo.toml b/substrate/client/consensus/epochs/Cargo.toml index 07de83980bcf766b55b97a9c8826b26c0978e839..76e4c05a67344c7052cf673127f6e02139402d97 100644 --- a/substrate/client/consensus/epochs/Cargo.toml +++ b/substrate/client/consensus/epochs/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index e1baff3bbf2c862ce86f05a4bf3601f79df99183..a6aacd564854bcfb7f2428b2616eefef3e080736 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -10,13 +10,16 @@ description = "Integration of the GRANDPA finality gadget into substrate." documentation = "https://docs.rs/sc-consensus-grandpa" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] ahash = "0.8.2" array-bytes = "6.1" -async-trait = "0.1.57" +async-trait = "0.1.74" dyn-clone = "1.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.21" @@ -25,7 +28,7 @@ log = "0.4.17" parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" -serde_json = "1.0.108" +serde_json = "1.0.111" thiserror = "1.0" fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } @@ -53,7 +56,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.193" +serde = "1.0.195" 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 2a0d51dd616e60da69f3adb006521ad7db2e1c89..9cfc9616cbc081222af760e32db42706132b752c 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -9,13 +9,16 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" readme = "README.md" homepage = "https://substrate.io" +[lints] +workspace = true + [dependencies] finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.16" jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } log = "0.4.8" parity-scale-codec = { version = "3.6.1", features = ["derive"] } -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0" sc-client-api = { path = "../../../api" } sc-consensus-grandpa = { path = ".." } diff --git a/substrate/client/consensus/grandpa/src/communication/tests.rs b/substrate/client/consensus/grandpa/src/communication/tests.rs index b76b1af93dac630efd618c460bd5b00c650b050c..fe24fb3cb20ea055e8ebfe385a974f57af5e4396 100644 --- a/substrate/client/consensus/grandpa/src/communication/tests.rs +++ b/substrate/client/consensus/grandpa/src/communication/tests.rs @@ -75,11 +75,15 @@ impl NetworkPeers for TestNetwork { unimplemented!(); } - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - let _ = self.sender.unbounded_send(Event::Report(who, cost_benefit)); + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) { + let _ = self.sender.unbounded_send(Event::Report(peer_id, cost_benefit)); } - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) {} + fn peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() + } + + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {} fn accept_unreserved_peers(&self) { unimplemented!(); diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index b0b9c1ee6eb3ca723933f7b10dbe65a8067d4d99..77cd88dfc194a5d804e300d3bf6a3cc75a3c2c8f 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -9,13 +9,16 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } assert_matches = "1.3.0" -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index ef32425685b6f127fc67d01ec0acfc5598501897..7077fb84babec0e0a5febd6abac2a0a560e958d9 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +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" diff --git a/substrate/client/consensus/slots/Cargo.toml b/substrate/client/consensus/slots/Cargo.toml index 52c528c3028a80edbba90e13a48d22a24fd45d38..801558a276a57a471520515671a5eaebb7e44db5 100644 --- a/substrate/client/consensus/slots/Cargo.toml +++ b/substrate/client/consensus/slots/Cargo.toml @@ -10,11 +10,14 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index 867e2908d6cc873d66e9ec4c25a392d2908563e9..e833b90b3edeb2b2fd7da2e090aa9183e87ae8a1 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Client backend that uses RocksDB database as storage." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -22,7 +25,7 @@ kvdb-memorydb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } linked-hash-map = "0.5.4" log = "0.4.17" -parity-db = "0.4.8" +parity-db = "0.4.12" parking_lot = "0.12.1" sc-client-api = { path = "../api" } sc-state-db = { path = "../state-db" } diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index 50aedf8a3484d78bc2393af6fba2ddb29c7888b1..aa8e8c9abf295cabcab686d7d2dbfd5e78a41157 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -10,6 +10,9 @@ description = "A crate that provides means of executing/dispatching calls into t documentation = "https://docs.rs/sc-executor" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 5118279b43b44ad75df3b8882592640c5ec9bd11..b3db6a86a2030ee47c8d241805ee183b7d953af5 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -10,6 +10,9 @@ description = "A set of common definitions that are needed for defining executio documentation = "https://docs.rs/sc-executor-common/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/executor/runtime-test/Cargo.toml b/substrate/client/executor/runtime-test/Cargo.toml index 84ed458fb1cd44df89a0d68259df4feb1b20ed0a..82610c4f50c2841fea13c1f859cc242f8ae427c7 100644 --- a/substrate/client/executor/runtime-test/Cargo.toml +++ b/substrate/client/executor/runtime-test/Cargo.toml @@ -9,6 +9,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index b1434ef7c52da696b59d503137fd5ef3513eea5a..f8df23a026e5643129d8f373a316d60113f1a335 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Defines a `WasmRuntime` that uses the Wasmtime JIT to execute." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index 47e65df3cc1159f2c2e68714f6db9cb8736d3eb8..8373e5a54c1b3a689ecca8de9090889db07f6c9c 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index 3fd88ae8b87ed3a1d99e6d0a6c08c1c48dc0159b..8fa6221ff197d4140934870a193ad0e634745bca 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -10,13 +10,16 @@ description = "Keystore (and session key management) for ed25519 based chains li documentation = "https://docs.rs/sc-keystore" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" parking_lot = "0.12.1" -serde_json = "1.0.108" +serde_json = "1.0.111" thiserror = "1.0" sp-application-crypto = { path = "../../primitives/application-crypto" } sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 8089dbba035297f3cfe2f6309ac10b124cfa5c4d..3b29f435e2a942ffe96dd02a8009af4d33693995 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -120,18 +120,18 @@ impl LocalKeystore { Ok(sig) } - fn vrf_output( + fn vrf_pre_output( &self, key_type: KeyTypeId, public: &T::Public, input: &T::VrfInput, - ) -> std::result::Result, TraitError> { - let preout = self + ) -> std::result::Result, TraitError> { + let pre_output = self .0 .read() .key_pair_by_type::(public, key_type)? - .map(|pair| pair.vrf_output(input)); - Ok(preout) + .map(|pair| pair.vrf_pre_output(input)); + Ok(pre_output) } } @@ -188,13 +188,13 @@ impl Keystore for LocalKeystore { self.vrf_sign::(key_type, public, data) } - fn sr25519_vrf_output( + fn sr25519_vrf_pre_output( &self, key_type: KeyTypeId, public: &sr25519::Public, input: &sr25519::vrf::VrfInput, - ) -> std::result::Result, TraitError> { - self.vrf_output::(key_type, public, input) + ) -> std::result::Result, TraitError> { + self.vrf_pre_output::(key_type, public, input) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { @@ -293,13 +293,13 @@ impl Keystore for LocalKeystore { self.vrf_sign::(key_type, public, data) } - fn bandersnatch_vrf_output( + fn bandersnatch_vrf_pre_output( &self, key_type: KeyTypeId, public: &bandersnatch::Public, input: &bandersnatch::vrf::VrfInput, - ) -> std::result::Result, TraitError> { - self.vrf_output::(key_type, public, input) + ) -> std::result::Result, TraitError> { + self.vrf_pre_output::(key_type, public, input) } fn bandersnatch_ring_vrf_sign( diff --git a/substrate/client/merkle-mountain-range/Cargo.toml b/substrate/client/merkle-mountain-range/Cargo.toml index ae60fd1ce8968befae4105206e65875cb173660f..f6dbaf86c51562377c6528b3456b65abb2547fee 100644 --- a/substrate/client/merkle-mountain-range/Cargo.toml +++ b/substrate/client/merkle-mountain-range/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true description = "MMR Client gadget for substrate" homepage = "https://substrate.io" +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml index d978d3cd2edbfe829c1e129f23fe9b172e5781b5..8eb48d65f81e9ca73589a30d95d05f74d3c9be71 100644 --- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml +++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml @@ -8,13 +8,16 @@ homepage = "https://substrate.io" repository.workspace = true description = "Node-specific RPC methods for interaction with Merkle Mountain Range pallet." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core" } @@ -23,4 +26,4 @@ sp-runtime = { path = "../../../primitives/runtime" } anyhow = "1" [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" diff --git a/substrate/client/mixnet/Cargo.toml b/substrate/client/mixnet/Cargo.toml index d11cb1805ff0859b5f06ef3193ef0b57a5c52834..e8543b5bdf2cfb03a156255be29249e5811a22c4 100644 --- a/substrate/client/mixnet/Cargo.toml +++ b/substrate/client/mixnet/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/network-gossip/Cargo.toml b/substrate/client/network-gossip/Cargo.toml index 0ad9dec465176f995b6f8137e52cffcbd5f8fe06..c53c53fb1350fd9c2e213298f5853ecdee9079a4 100644 --- a/substrate/client/network-gossip/Cargo.toml +++ b/substrate/client/network-gossip/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true documentation = "https://docs.rs/sc-network-gossip" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.8.2" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = "0.51.3" +libp2p = "0.51.4" log = "0.4.17" schnellru = "0.2.1" tracing = "0.1.29" @@ -29,7 +32,7 @@ sp-runtime = { path = "../../primitives/runtime" } [dev-dependencies] tokio = "1.22.0" -async-trait = "0.1.73" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } quickcheck = { version = "1.0.3", default-features = false } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index c1bc414c3a34057815ad11b9501dc4640888820b..1d6a4bdd0c086996ed45a7074a1a51bcf34f12d9 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -394,9 +394,13 @@ mod tests { unimplemented!(); } - fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) {} + fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) {} - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + fn peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() + } + + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) { unimplemented!(); } diff --git a/substrate/client/network-gossip/src/state_machine.rs b/substrate/client/network-gossip/src/state_machine.rs index 91b56b0f097e0fec4ed187be713fd83249f07f49..069d7cdba16599b4b4da0965a5d8e4588478d633 100644 --- a/substrate/client/network-gossip/src/state_machine.rs +++ b/substrate/client/network-gossip/src/state_machine.rs @@ -621,11 +621,15 @@ mod tests { unimplemented!(); } - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - self.inner.lock().unwrap().peer_reports.push((who, cost_benefit)); + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) { + self.inner.lock().unwrap().peer_reports.push((peer_id, cost_benefit)); } - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + fn peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() + } + + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) { unimplemented!(); } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index ff8046868d5dc4debec23ad957ece0317edded3a..f2e6f54547b96cd051f4a3c9d727ec1cde8505e9 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true documentation = "https://docs.rs/sc-network" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -25,7 +28,7 @@ fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" ip_network = "0.4.1" -libp2p = { version = "0.51.3", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "request-response", "tcp", "tokio", "websocket", "yamux"] } +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" mockall = "0.11.3" @@ -33,8 +36,8 @@ parking_lot = "0.12.1" partial_sort = "0.2.0" pin-project = "1.0.12" rand = "0.8.5" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" smallvec = "1.11.0" thiserror = "1.0" tokio = { version = "1.22.0", features = ["macros", "sync"] } diff --git a/substrate/client/network/bitswap/Cargo.toml b/substrate/client/network/bitswap/Cargo.toml index f4ad4b3a0e953d71b45a66d18d3f9667a30ded14..cc919d2977ea1f4368ca93879232b341f6703507 100644 --- a/substrate/client/network/bitswap/Cargo.toml +++ b/substrate/client/network/bitswap/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-bitswap" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/network/common/Cargo.toml b/substrate/client/network/common/Cargo.toml index 65c8e1d71c721b99447e8292d036a3312f9cfc1a..8e5ad61d5e4b28f09d97ec3f5c8570a8faa5014d 100644 --- a/substrate/client/network/common/Cargo.toml +++ b/substrate/client/network/common/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-sync" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.11" [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" bitflags = "1.3.2" codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index 17b2143281128d5f9693656c13145d03a0f81d97..c75d14f0deb69531ae77a0498f1d865ca9b4ebea 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-light" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index 9f770bc3ba745dc5101b944a3aed622d95442481..745550412fc219a4ca481deb0cd3405a020e3fbe 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -39,7 +39,7 @@ use parking_lot::Mutex; use sp_runtime::traits::Block as BlockT; use std::{collections::HashSet, sync::Arc, time::Duration}; -pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, ResponseFailure}; +pub use crate::request_responses::{InboundFailure, OutboundFailure, ResponseFailure}; /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] diff --git a/substrate/client/network/src/protocol/message.rs b/substrate/client/network/src/protocol/message.rs index 247580083f99e871f85422840dfb8068fdd52cd1..5f2511fd6ddc93d4ef2018b43d7be9e3d138ba32 100644 --- a/substrate/client/network/src/protocol/message.rs +++ b/substrate/client/network/src/protocol/message.rs @@ -19,10 +19,6 @@ //! Network packet message types. These get serialized and put into the lower level protocol //! payload. -pub use self::generic::{ - RemoteCallRequest, RemoteChangesRequest, RemoteChangesResponse, RemoteHeaderRequest, - RemoteHeaderResponse, RemoteReadChildRequest, RemoteReadRequest, -}; use codec::{Decode, Encode}; use sc_client_api::StorageProof; use sc_network_common::message::RequestId; diff --git a/substrate/client/network/src/protocol/notifications/behaviour.rs b/substrate/client/network/src/protocol/notifications/behaviour.rs index cdbf2a71b932fb1d0acd6b8489919d8e904d7805..9ad41e376e824835cdb0be883b3c0f344218ed11 100644 --- a/substrate/client/network/src/protocol/notifications/behaviour.rs +++ b/substrate/client/network/src/protocol/notifications/behaviour.rs @@ -1037,7 +1037,7 @@ impl Notifications { peerset_rejected, incoming_index, }; - return self.report_reject(index).map_or((), |_| ()); + return self.report_reject(index).map_or((), |_| ()) } trace!( diff --git a/substrate/client/network/src/protocol/notifications/upgrade.rs b/substrate/client/network/src/protocol/notifications/upgrade.rs index 70c6023623f51b2c0d97379298febc8e4826dfbe..8fd837f949d8a10d744513b9225ecf53d8a74d1e 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade.rs @@ -16,12 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#[cfg(test)] +pub(crate) use self::notifications::{ + NotificationsInOpen, NotificationsInSubstreamHandshake, NotificationsOutOpen, +}; pub use self::{ collec::UpgradeCollec, notifications::{ - NotificationsHandshakeError, NotificationsIn, NotificationsInOpen, - NotificationsInSubstream, NotificationsInSubstreamHandshake, NotificationsOut, - NotificationsOutError, NotificationsOutOpen, NotificationsOutSubstream, + NotificationsIn, NotificationsInSubstream, NotificationsOut, NotificationsOutSubstream, }, }; diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs index 43a3ab09115c7e6838988a6cbf6722ddd40bdac6..06db23844d0d9d07a33e5ffb4303cdf1f9179ec8 100644 --- a/substrate/client/network/src/service.rs +++ b/substrate/client/network/src/service.rs @@ -120,6 +120,8 @@ pub struct NetworkService { local_identity: Keypair, /// Bandwidth logging system. Can be queried to know the average bandwidth consumed. bandwidth: Arc, + /// Used to query and report reputation changes. + peer_store_handle: PeerStoreHandle, /// Channel that sends messages to the actual worker. to_worker: TracingUnboundedSender, /// Protocol name -> `SetId` mapping for notification protocols. The map never changes after @@ -130,8 +132,6 @@ pub struct NetworkService { protocol_handles: Vec, /// Shortcut to sync protocol handle (`protocol_handles[0]`). sync_protocol_handle: protocol_controller::ProtocolHandle, - /// Handle to `PeerStore`. - peer_store_handle: PeerStoreHandle, /// Marker to pin the `H` generic. Serves no purpose except to not break backwards /// compatibility. _marker: PhantomData, @@ -865,12 +865,18 @@ where .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::ReportPeer(who, cost_benefit)); + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) { + self.peer_store_handle.clone().report_peer(peer_id, cost_benefit); + } + + fn peer_reputation(&self, peer_id: &PeerId) -> i32 { + self.peer_store_handle.peer_reputation(peer_id) } - fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::DisconnectPeer(who, protocol)); + fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::DisconnectPeer(peer_id, protocol)); } fn accept_unreserved_peers(&self) { @@ -1149,7 +1155,6 @@ enum ServiceToWorkerMsg { GetValue(KademliaKey), PutValue(KademliaKey, Vec), AddKnownAddress(PeerId, Multiaddr), - ReportPeer(PeerId, ReputationChange), EventStream(out_events::Sender), Request { target: PeerId, @@ -1277,8 +1282,6 @@ where self.network_service.behaviour_mut().put_value(key, value), ServiceToWorkerMsg::AddKnownAddress(peer_id, addr) => self.network_service.behaviour_mut().add_known_address(peer_id, addr), - ServiceToWorkerMsg::ReportPeer(peer_id, reputation_change) => - self.peer_store_handle.report_peer(peer_id, reputation_change), ServiceToWorkerMsg::EventStream(sender) => self.event_streams.push(sender), ServiceToWorkerMsg::Request { target, diff --git a/substrate/client/network/src/service/traits.rs b/substrate/client/network/src/service/traits.rs index f66e810be11afa78a007ea494f4f51bc88645018..d4d4a05a86f1dad6cc25120f86d8d267eb49220a 100644 --- a/substrate/client/network/src/service/traits.rs +++ b/substrate/client/network/src/service/traits.rs @@ -155,12 +155,15 @@ pub trait NetworkPeers { /// Report a given peer as either beneficial (+) or costly (-) according to the /// given scalar. - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange); + + /// Get peer reputation. + fn peer_reputation(&self, peer_id: &PeerId) -> i32; /// Disconnect from a node as soon as possible. /// /// This triggers the same effects as if the connection had closed itself spontaneously. - fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); + fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName); /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. fn accept_unreserved_peers(&self); @@ -254,16 +257,16 @@ where T::add_known_address(self, peer_id, addr) } - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - // TODO: when we get rid of `Peerset`, we'll likely need to add some kind of async - // interface to `PeerStore`, otherwise we'll have trouble calling functions accepting - // `&mut self` via `Arc`. - // See https://github.com/paritytech/substrate/issues/14170. - T::report_peer(self, who, cost_benefit) + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) { + T::report_peer(self, peer_id, cost_benefit) + } + + fn peer_reputation(&self, peer_id: &PeerId) -> i32 { + T::peer_reputation(self, peer_id) } - fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName) { - T::disconnect_peer(self, who, protocol) + fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName) { + T::disconnect_peer(self, peer_id, protocol) } fn accept_unreserved_peers(&self) { diff --git a/substrate/client/network/statement/Cargo.toml b/substrate/client/network/statement/Cargo.toml index ef974b4f33f1931b632f8760cd79f975b3970941..d3ce2a63ef14ddca0be86867dabcbd5bf541ec05 100644 --- a/substrate/client/network/statement/Cargo.toml +++ b/substrate/client/network/statement/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-statement" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,7 +20,7 @@ array-bytes = "6.1" async-channel = "1.8.0" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -libp2p = "0.51.3" +libp2p = "0.51.4" log = "0.4.17" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network-common = { path = "../common" } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index a9b8ec577e3f84a2bb998969e37b735a989e8e9d..cb19e1adbe53a3b4ef9ec0b1d380d99fc6add835 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-sync" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,11 +21,11 @@ prost-build = "0.11" [dependencies] array-bytes = "6.1" async-channel = "1.8.0" -async-trait = "0.1.58" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -libp2p = "0.51.3" +libp2p = "0.51.4" log = "0.4.17" mockall = "0.11.3" prost = "0.11" diff --git a/substrate/client/network/sync/src/service/mock.rs b/substrate/client/network/sync/src/service/mock.rs index 47986a71d012a5eb55cecba5f28966ab25448aee..6e307d8698444b78ccd12a08e8b7b41681b8ca1b 100644 --- a/substrate/client/network/sync/src/service/mock.rs +++ b/substrate/client/network/sync/src/service/mock.rs @@ -84,8 +84,9 @@ mockall::mock! { fn set_authorized_peers(&self, peers: HashSet); fn set_authorized_only(&self, reserved_only: bool); fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr); - fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); - fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); + fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange); + fn peer_reputation(&self, peer_id: &PeerId) -> i32; + fn disconnect_peer(&self, peer_id: PeerId, protocol: ProtocolName); fn accept_unreserved_peers(&self); fn deny_unreserved_peers(&self); fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>; diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index a11ed2a3ec8fc4391c7ce846e58197fdb1999045..dced6ed673057deb6959eb4b6fb001cff5be301a 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -9,15 +9,18 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] tokio = "1.22.0" -async-trait = "0.1.57" +async-trait = "0.1.74" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = "0.51.3" +libp2p = "0.51.4" log = "0.4.17" parking_lot = "0.12.1" rand = "0.8.5" diff --git a/substrate/client/network/transactions/Cargo.toml b/substrate/client/network/transactions/Cargo.toml index 2a6aa4b3a40aee615ea1cce248b2681b33339610..24b5087af1f42fab1e351d8eaa0a15bb5d2805bd 100644 --- a/substrate/client/network/transactions/Cargo.toml +++ b/substrate/client/network/transactions/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true documentation = "https://docs.rs/sc-network-transactions" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = "6.1" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -libp2p = "0.51.3" +libp2p = "0.51.4" log = "0.4.17" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network = { path = ".." } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 9758ea4c4fcba72a0dfdc38cdbe1bdb718fd6323..b2299667448ca88ecda779a66b7b773605442357 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -475,7 +475,20 @@ where propagated_to.entry(hash).or_default().push(who.to_base58()); } trace!(target: "sync", "Sending {} transactions to {}", to_send.len(), who); - let _ = self.notification_service.send_sync_notification(who, to_send.encode()); + // Historically, the format of a notification of the transactions protocol + // consisted in a (SCALE-encoded) `Vec`. + // After RFC 56, the format was modified in a backwards-compatible way to be + // a (SCALE-encoded) tuple `(Compact(1), Transaction)`, which is the same encoding + // as a `Vec` of length one. This is no coincidence, as the change was + // intentionally done in a backwards-compatible way. + // In other words, the `Vec` that is sent below **must** always have only a single + // element in it. + // See + for to_send in to_send { + let _ = self + .notification_service + .send_sync_notification(who, vec![to_send].encode()); + } } } diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 01deb32213449e65316694515ca9c87c9473bff7..b049ba0a3d89b5f7ecbca0980b4cbd7e2a258161 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,7 +24,7 @@ futures = "0.3.21" futures-timer = "3.0.2" hyper = { version = "0.14.16", features = ["http2", "stream"] } hyper-rustls = { version = "0.24.0", features = ["http2"] } -libp2p = "0.51.3" +libp2p = "0.51.4" num_cpus = "1.13" once_cell = "1.8" parking_lot = "0.12.1" diff --git a/substrate/client/offchain/src/api.rs b/substrate/client/offchain/src/api.rs index 2901bab2f2677a4d88513b4770ca09f0343e309d..65e2f3ba64dbec30f5d5a944d5cb4f357c76e756 100644 --- a/substrate/client/offchain/src/api.rs +++ b/substrate/client/offchain/src/api.rs @@ -30,7 +30,6 @@ use sp_core::{ }, OpaquePeerId, }; -pub use sp_offchain::STORAGE_PREFIX; mod http; @@ -243,11 +242,15 @@ mod tests { unimplemented!(); } - fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) { unimplemented!(); } - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + fn peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() + } + + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) { unimplemented!(); } diff --git a/substrate/client/offchain/src/lib.rs b/substrate/client/offchain/src/lib.rs index 8bcfa66a5af32346239ea3f94508beae87080bed..eb3436432f342f4de97a5325c7a14ea4ce167581 100644 --- a/substrate/client/offchain/src/lib.rs +++ b/substrate/client/offchain/src/lib.rs @@ -374,11 +374,15 @@ mod tests { unimplemented!(); } - fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + fn report_peer(&self, _peer_id: PeerId, _cost_benefit: ReputationChange) { unimplemented!(); } - fn disconnect_peer(&self, _who: PeerId, _protocol: ProtocolName) { + fn peer_reputation(&self, _peer_id: &PeerId) -> i32 { + unimplemented!() + } + + fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) { unimplemented!(); } diff --git a/substrate/client/proposer-metrics/Cargo.toml b/substrate/client/proposer-metrics/Cargo.toml index b6b4452ecc64edc466606d82f355b33c8bd7667c..664b72764a3b8b31b81cf437aa69f93c45f97b35 100644 --- a/substrate/client/proposer-metrics/Cargo.toml +++ b/substrate/client/proposer-metrics/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Basic metrics for block production." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index b5c0f70d94c7b329cb41f8a5a0bb4536ab67da9f..062d25408a14fe25e0c6d8e20c78fc742aac5664 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -9,14 +9,17 @@ repository.workspace = true description = "Substrate RPC interfaces." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] 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.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" thiserror = "1.0" sc-chain-spec = { path = "../chain-spec" } sc-mixnet = { path = "../mixnet" } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index a7cc374f97a1e011b26f58d16850eabfde57fd65..60d999863cabe6db3fb61ae527716ba62aa0d302 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "Substrate RPC servers." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" -serde_json = "1.0.108" +serde_json = "1.0.111" 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"] } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 45a1d862f04a25574c988bdf1f39db228f525a74..ba32308e6783088d4aa3ebe007d446d3db7d5dca 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate RPC interface v2." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -39,7 +42,7 @@ log = "0.4.17" futures-util = { version = "0.3.19", default-features = false } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" tokio = { version = "1.22.0", features = ["macros"] } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index b2bcc62ce317453697204b673a05694917b5005d..269962cfd7482356b56ac202f11adbb824737518 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -124,7 +124,7 @@ where let finalized_num = self.client.info().finalized_number; if finalized_num >= height { - let Ok(Some(hash)) = self.client.block_hash(height.into()) else { return Ok(vec![]) }; + let Ok(Some(hash)) = self.client.block_hash(height) else { return Ok(vec![]) }; return Ok(vec![hex_string(&hash.as_ref())]) } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 1cedcb3a6d08c1650919550d648fe63fefc3d5c7..47425c6d3549993608f35eff1643f55275a579fc 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate Client RPC" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,7 +21,7 @@ futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" parking_lot = "0.12.1" -serde_json = "1.0.108" +serde_json = "1.0.111" sc-block-builder = { path = "../block-builder" } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index ae03a5dab36067df6b2472b892310e901c428cc9..576a8aac8e49f8f8e6ef38d62685bcbd2e94d33d 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. Manages communication between them." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -34,8 +37,8 @@ log = "0.4.17" futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -serde = "1.0.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" sc-keystore = { path = "../keystore" } sp-runtime = { path = "../../primitives/runtime" } sp-trie = { path = "../../primitives/trie" } @@ -76,7 +79,7 @@ sc-tracing = { path = "../tracing" } sc-sysinfo = { path = "../sysinfo" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } -async-trait = "0.1.57" +async-trait = "0.1.74" tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "time"] } tempfile = "3.1.0" directories = "5.0.1" diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 93576be9b59774af3a634f346b8661acc1869b09..625d8286396e7778dd7271db772d143550dc6b5e 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -8,6 +8,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/state-db/Cargo.toml b/substrate/client/state-db/Cargo.toml index c5e8272637d4cadca5fdefc51048ef6b4c6d994a..001ada02ef2f8140dd4619e451517e95f91a136c 100644 --- a/substrate/client/state-db/Cargo.toml +++ b/substrate/client/state-db/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "State database maintenance. Handles canonicalization and pruning in the database." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index 371d67369166b8f8448146bd6d3dfb982d0e0dc9..adfd27a1705ad3e3ade840c81af60b23a0d02af1 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "Substrate statement store." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.17" parking_lot = "0.12.1" -parity-db = "0.4.8" +parity-db = "0.4.12" tokio = { version = "1.22.0", features = ["time"] } sp-statement-store = { path = "../../primitives/statement-store" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index c0eb9d94b929f264dda83547c99bc76312524283..37259b9234aecb3c2da0836044a06d808e00ff48 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -8,8 +8,11 @@ repository.workspace = true description = "Storage monitor service for substrate" homepage = "https://substrate.io" +[lints] +workspace = true + [dependencies] -clap = { version = "4.4.10", features = ["derive", "string"] } +clap = { version = "4.4.13", features = ["derive", "string"] } log = "0.4.17" fs4 = "0.7.0" sc-client-db = { path = "../db", default-features = false } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index 746f1c754f9cff0b28e3ff631db292aa0b89a552..b3de9585ab454da2be2e96193d0fe65af234ec6d 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -8,14 +8,17 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" thiserror = "1.0.48" sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index 4cd1b222bc6d281054ec1a3255eab60f7a4c8cb8..18ac161f1ee1a55246c6c096b22bb599e79f6fcb 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -10,6 +10,9 @@ description = "A crate that provides basic hardware and software telemetry infor documentation = "https://docs.rs/sc-sysinfo" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,8 +24,8 @@ rand = "0.8.5" rand_pcg = "0.3.1" derive_more = "0.99" regex = "1" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" sc-telemetry = { path = "../telemetry" } sp-core = { path = "../../primitives/core" } sp-io = { path = "../../primitives/io" } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index 71119df11537ee81fd54bacf516de4b163c855e3..ba597ef898e03367e94d57cefb195b0fb16e9d04 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -10,19 +10,22 @@ repository.workspace = true documentation = "https://docs.rs/sc-telemetry" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -chrono = "0.4.27" +chrono = "0.4.31" futures = "0.3.21" -libp2p = { version = "0.51.3", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } +libp2p = { version = "0.51.4", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { path = "../utils" } rand = "0.8.5" -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" thiserror = "1.0.48" wasm-timer = "0.2.5" diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 844969c5ce26a0674a5abc234e6a1101a5474211..fe288474ac95cbebdabcd0a93c7ba7cd2925a54b 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "Instrumentation implementation for substrate." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -atty = "0.2.13" -chrono = "0.4.27" +is-terminal = "0.4.9" +chrono = "0.4.31" codec = { package = "parity-scale-codec", version = "3.6.1" } lazy_static = "1.4.0" libc = "0.2.121" @@ -23,7 +26,7 @@ log = { version = "0.4.17" } parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.193" +serde = "1.0.195" thiserror = "1.0.48" tracing = "0.1.29" tracing-log = "0.1.3" diff --git a/substrate/client/tracing/proc-macro/Cargo.toml b/substrate/client/tracing/proc-macro/Cargo.toml index 1d5d638c49bcbe4a0286dcdf37965c005c6806dd..c293c1834e83b9609ddcc8973da3d2529b2d01eb 100644 --- a/substrate/client/tracing/proc-macro/Cargo.toml +++ b/substrate/client/tracing/proc-macro/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Helper macros for Substrate's client CLI" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.39", features = ["extra-traits", "full", "parsing", "proc-macro"] } +syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } diff --git a/substrate/client/tracing/src/logging/mod.rs b/substrate/client/tracing/src/logging/mod.rs index a3cf277fbd5010ff636679d3a43249e52746725e..403839390d655714e03dc794ad92cbed4044b6db 100644 --- a/substrate/client/tracing/src/logging/mod.rs +++ b/substrate/client/tracing/src/logging/mod.rs @@ -33,6 +33,7 @@ pub(crate) type DefaultLogger = stderr_writer::MakeStderrWriter; pub use directives::*; pub use sc_tracing_proc_macro::*; +use is_terminal::IsTerminal; use std::io; use tracing::Subscriber; use tracing_subscriber::{ @@ -170,7 +171,7 @@ where _ => true, } || detailed_output; - let enable_color = force_colors.unwrap_or_else(|| atty::is(atty::Stream::Stderr)); + let enable_color = force_colors.unwrap_or_else(|| io::stderr().is_terminal()); let timer = fast_local_time::FastLocalTime { with_fractional: detailed_output }; let event_format = EventFormat { @@ -179,7 +180,7 @@ where display_level: detailed_output, display_thread_name: detailed_output, enable_color, - dup_to_stdout: !atty::is(atty::Stream::Stderr) && atty::is(atty::Stream::Stdout), + dup_to_stdout: !io::stderr().is_terminal() && io::stdout().is_terminal(), }; let builder = FmtSubscriber::builder().with_env_filter(env_filter); diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index 3e90304497f36a712fd719ab2ca3bb3990156f96..8832c0bf5080d548bc1b5724b60d98e285cc5ca2 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -9,18 +9,21 @@ repository.workspace = true description = "Substrate transaction pool implementation." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" 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" parking_lot = "0.12.1" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0.48" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index 89981c2751134d18cf42302d6cec90b81e92c09d..2522739cf88707c1a8147db5a62fb899e568435f 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -8,16 +8,19 @@ homepage = "https://substrate.io" repository.workspace = true description = "Transaction pool client facing API." +[lints] +workspace = true + [dependencies] -async-trait = "0.1.57" +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.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } thiserror = "1.0.48" 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.108" +serde_json = "1.0.111" diff --git a/substrate/client/transaction-pool/src/graph/mod.rs b/substrate/client/transaction-pool/src/graph/mod.rs index 5afdddb7402d163284cfe3279b280c0fecb026a7..484a6d6cf9f07787a51cbd3a2ae6d339e157ce43 100644 --- a/substrate/client/transaction-pool/src/graph/mod.rs +++ b/substrate/client/transaction-pool/src/graph/mod.rs @@ -39,9 +39,6 @@ pub mod watcher; pub use self::{ base_pool::Transaction, - pool::{ - BlockHash, ChainApi, EventStream, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool, - TransactionFor, - }, + pool::{BlockHash, ChainApi, ExtrinsicFor, ExtrinsicHash, NumberFor, Options, Pool}, }; pub use validated_pool::{IsValidator, ValidatedTransaction}; diff --git a/substrate/client/utils/Cargo.toml b/substrate/client/utils/Cargo.toml index da618b0259eac22930b1e46446349de467444f37..a19457ac3d077f548652ee78858023f00303bff1 100644 --- a/substrate/client/utils/Cargo.toml +++ b/substrate/client/utils/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "I/O for Substrate runtimes" readme = "README.md" +[lints] +workspace = true + [dependencies] async-channel = "1.8.0" futures = "0.3.21" diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index d6953dac7b8b7ea5535cda4202656f7ade745533..083d098b22a4de15aa45f79d2657d6e3d6f04eb0 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "The single package to get you started with building frame pallets and runtimes" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] # enable `experimental` feature for docs features = ["experimental"] diff --git a/substrate/frame/alliance/Cargo.toml b/substrate/frame/alliance/Cargo.toml index 1afff8ad43e9a888dffe56b5e9257ee390e881fd..39f5a6ceb756c4659d70c9a18e4b4f33d26258af 100644 --- a/substrate/frame/alliance/Cargo.toml +++ b/substrate/frame/alliance/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "The Alliance pallet provides a collective for standard-setting industry collaboration." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/alliance/src/benchmarking.rs b/substrate/frame/alliance/src/benchmarking.rs index 37cc3314037e5175039802fc2c39b225910ac912..cb2a04f17c57f47611da5d4a63ec869c6be34c27 100644 --- a/substrate/frame/alliance/src/benchmarking.rs +++ b/substrate/frame/alliance/src/benchmarking.rs @@ -183,7 +183,7 @@ mod benchmarks { let voter = &members[j as usize]; Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, true, )?; @@ -191,12 +191,7 @@ mod benchmarks { let voter = members[m as usize - 3].clone(); // Voter votes aye without resolving the vote. - Alliance::::vote( - SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), - index, - true, - )?; + Alliance::::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?; // Voter switches vote to nay, but does not kill the vote, just updates + inserts let approve = false; @@ -206,7 +201,7 @@ mod benchmarks { frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); #[extrinsic_call] - _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve); + _(SystemOrigin::Signed(voter), last_hash, index, approve); //nothing to verify Ok(()) @@ -255,24 +250,19 @@ mod benchmarks { let voter = &members[j as usize]; Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, true, )?; } // Voter votes aye without resolving the vote. - Alliance::::vote( - SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), - index, - true, - )?; + Alliance::::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?; // Voter switches vote to nay, which kills the vote Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -282,7 +272,7 @@ mod benchmarks { frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); #[extrinsic_call] - close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage); + close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage); assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); Ok(()) @@ -330,7 +320,7 @@ mod benchmarks { // approval vote Alliance::::vote( SystemOrigin::Signed(proposer.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -340,7 +330,7 @@ mod benchmarks { let voter = &members[j as usize]; Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -349,22 +339,17 @@ mod benchmarks { // Member zero is the first aye Alliance::::vote( SystemOrigin::Signed(members[0].clone()).into(), - last_hash.clone(), + last_hash, index, true, )?; let voter = members[1].clone(); // Caller switches vote to aye, which passes the vote - Alliance::::vote( - SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), - index, - true, - )?; + Alliance::::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?; #[extrinsic_call] - close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage); + close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage); assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); Ok(()) @@ -414,7 +399,7 @@ mod benchmarks { let voter = &members[j as usize]; Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, true, )?; @@ -422,7 +407,7 @@ mod benchmarks { Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -430,7 +415,7 @@ mod benchmarks { System::::set_block_number(BlockNumberFor::::max_value()); #[extrinsic_call] - close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage); + close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage); // The last proposal is removed. assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); @@ -477,7 +462,7 @@ mod benchmarks { // The prime member votes aye, so abstentions default to aye. Alliance::::vote( SystemOrigin::Signed(proposer.clone()).into(), - last_hash.clone(), + last_hash, p - 1, true, // Vote aye. )?; @@ -489,7 +474,7 @@ mod benchmarks { let voter = &members[j as usize]; Alliance::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -499,13 +484,7 @@ mod benchmarks { System::::set_block_number(BlockNumberFor::::max_value()); #[extrinsic_call] - close( - SystemOrigin::Signed(proposer), - last_hash.clone(), - index, - Weight::MAX, - bytes_in_storage, - ); + close(SystemOrigin::Signed(proposer), last_hash, index, Weight::MAX, bytes_in_storage); assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); Ok(()) diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index ace5214f145f7683b22aa9a1ec8f9689cd97221c..01e0e01fe7ec46c4c002a3afb6bafd940617d92b 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -19,16 +19,12 @@ pub use sp_core::H256; use sp_runtime::traits::Hash; -pub use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +pub use sp_runtime::{traits::BlakeTwo256, BuildStorage}; use sp_std::convert::{TryFrom, TryInto}; pub use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, SortedMembers}, - BoundedVec, + traits::EitherOfDiverse, BoundedVec, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use pallet_identity::{ diff --git a/substrate/frame/asset-conversion/Cargo.toml b/substrate/frame/asset-conversion/Cargo.toml index 5df86d402e0e405738a771d45559078c04e706ab..0c7b06abf55d01c68dcf16f08bcdb00dcc04b0ea 100644 --- a/substrate/frame/asset-conversion/Cargo.toml +++ b/substrate/frame/asset-conversion/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME asset conversion pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/asset-conversion/src/benchmarking.rs b/substrate/frame/asset-conversion/src/benchmarking.rs index 87b541cd4744d1be32cd0908492b470c86d0f604..f0e02c802ad8e5a923cb9801c33c5539e9bd58a6 100644 --- a/substrate/frame/asset-conversion/src/benchmarking.rs +++ b/substrate/frame/asset-conversion/src/benchmarking.rs @@ -18,74 +18,142 @@ //! Asset Conversion pallet benchmarking. use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use crate::Pallet as AssetConversion; +use frame_benchmarking::{v2::*, whitelisted_caller}; use frame_support::{ assert_ok, - storage::bounded_vec::BoundedVec, traits::{ - fungible::{Inspect as InspectFungible, Mutate as MutateFungible, Unbalanced}, + fungible::NativeOrWithId, fungibles::{Create, Inspect, Mutate}, }, }; use frame_system::RawOrigin as SystemOrigin; use sp_core::Get; -use sp_runtime::traits::{Bounded, StaticLookup}; -use sp_std::{ops::Div, prelude::*}; +use sp_std::{marker::PhantomData, prelude::*}; -use crate::Pallet as AssetConversion; +/// Benchmark Helper +pub trait BenchmarkHelper { + /// Returns a valid assets pair for the pool creation. + /// + /// When a specific asset, such as the native asset, is required in every pool, it should be + /// returned for each odd-numbered seed. + fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind); +} + +impl BenchmarkHelper for () +where + AssetKind: From, +{ + fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind) { + (seed1.into(), seed2.into()) + } +} + +/// Factory for creating a valid asset pairs with [`NativeOrWithId::Native`] always leading in the +/// pair. +pub struct NativeOrWithIdFactory(PhantomData); +impl + Ord> BenchmarkHelper> + for NativeOrWithIdFactory +{ + fn create_pair(seed1: u32, seed2: u32) -> (NativeOrWithId, NativeOrWithId) { + if seed1 % 2 == 0 { + (NativeOrWithId::WithId(seed2.into()), NativeOrWithId::Native) + } else { + (NativeOrWithId::Native, NativeOrWithId::WithId(seed2.into())) + } + } +} -const INITIAL_ASSET_BALANCE: u128 = 1_000_000_000_000; -type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -type BalanceOf = - <::Currency as InspectFungible<::AccountId>>::Balance; +/// Provides a pair of amounts expected to serve as sufficient initial liquidity for a pool. +fn valid_liquidity_amount(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance) +where + T::Assets: Inspect, +{ + let l = + ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one(); + (l, l) +} -fn get_lp_token_id() -> T::PoolAssetId +/// Create the `asset` and mint the `amount` for the `caller`. +fn create_asset(caller: &T::AccountId, asset: &T::AssetKind, amount: T::Balance) where - T::PoolAssetId: Into, + T::Assets: Create + Mutate, { - let next_id: u32 = AssetConversion::::get_next_pool_asset_id().into(); - (next_id - 1).into() + if !T::Assets::asset_exists(asset.clone()) { + assert_ok!(T::Assets::create(asset.clone(), caller.clone(), true, T::Balance::one())); + } + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + amount + T::Assets::minimum_balance(asset.clone()) + )); } -fn create_asset(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf) +/// Create the designated fee asset for pool creation. +fn create_fee_asset(caller: &T::AccountId) where - T::AssetBalance: From, - T::Currency: Unbalanced, T::Assets: Create + Mutate, { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - if let MultiAssetIdConversionResult::Converted(asset_id) = - T::MultiAssetIdConverter::try_convert(asset) - { - T::Currency::set_balance(&caller, BalanceOf::::max_value().div(1000u32.into())); - assert_ok!(T::Assets::create(asset_id.clone(), caller.clone(), true, 1.into())); - assert_ok!(T::Assets::mint_into(asset_id, &caller, INITIAL_ASSET_BALANCE.into())); + let fee_asset = T::PoolSetupFeeAsset::get(); + if !T::Assets::asset_exists(fee_asset.clone()) { + assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one())); } - (caller, caller_lookup) + assert_ok!(T::Assets::mint_into( + fee_asset.clone(), + &caller, + T::Assets::minimum_balance(fee_asset) + )); } +/// Mint the fee asset for the `caller` sufficient to cover the fee for creating a new pool. +fn mint_setup_fee_asset( + caller: &T::AccountId, + asset1: &T::AssetKind, + asset2: &T::AssetKind, + lp_token: &T::PoolAssetId, +) where + T::Assets: Create + Mutate, +{ + assert_ok!(T::Assets::mint_into( + T::PoolSetupFeeAsset::get(), + &caller, + T::PoolSetupFee::get() + + T::Assets::deposit_required(asset1.clone()) + + T::Assets::deposit_required(asset2.clone()) + + T::PoolAssets::deposit_required(lp_token.clone()) + )); +} + +/// Creates a pool for a given asset pair. +/// +/// This action mints the necessary amounts of the given assets for the `caller` to provide initial +/// liquidity. It returns the LP token ID along with a pair of amounts sufficient for the pool's +/// initial liquidity. fn create_asset_and_pool( - asset1: &T::MultiAssetId, - asset2: &T::MultiAssetId, -) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf) + caller: &T::AccountId, + asset1: &T::AssetKind, + asset2: &T::AssetKind, +) -> (T::PoolAssetId, T::Balance, T::Balance) where - T::AssetBalance: From, - T::Currency: Unbalanced, T::Assets: Create + Mutate, - T::PoolAssetId: Into, { - let (_, _) = create_asset::(asset1); - let (caller, caller_lookup) = create_asset::(asset2); + let (liquidity1, liquidity2) = valid_liquidity_amount::( + T::Assets::minimum_balance(asset1.clone()), + T::Assets::minimum_balance(asset2.clone()), + ); + create_asset::(caller, asset1, liquidity1); + create_asset::(caller, asset2, liquidity2); + let lp_token = AssetConversion::::get_next_pool_asset_id(); + + mint_setup_fee_asset::(caller, asset1, asset2, &lp_token); assert_ok!(AssetConversion::::create_pool( SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone() + Box::new(asset1.clone()), + Box::new(asset2.clone()) )); - let lp_token = get_lp_token_id::(); - (lp_token, caller, caller_lookup) + (lp_token, liquidity1, liquidity2) } fn assert_last_event(generic_event: ::RuntimeEvent) { @@ -96,242 +164,198 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { assert_eq!(event, &system_event); } -benchmarks! { - where_clause { - where - T::AssetBalance: From + Into, - T::Currency: Unbalanced, - T::Balance: From + Into, - T::Assets: Create + Mutate, - T::PoolAssetId: Into, - } +#[benchmarks(where T::Assets: Create + Mutate, T::PoolAssetId: Into,)] +mod benchmarks { + use super::*; - create_pool { - let asset1 = T::MultiAssetIdConverter::get_native(); - let asset2 = T::BenchmarkHelper::multiasset_id(0); - let (caller, _) = create_asset::(&asset2); - }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone()) - verify { - let lp_token = get_lp_token_id::(); - let pool_id = (asset1.clone(), asset2.clone()); - assert_last_event::(Event::PoolCreated { - creator: caller.clone(), - pool_account: AssetConversion::::get_pool_account(&pool_id), - pool_id, - lp_token, - }.into()); - } + #[benchmark] + fn create_pool() { + let caller: T::AccountId = whitelisted_caller(); + let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); + create_asset::(&caller, &asset1, T::Assets::minimum_balance(asset1.clone())); + create_asset::(&caller, &asset2, T::Assets::minimum_balance(asset2.clone())); - add_liquidity { - let asset1 = T::MultiAssetIdConverter::get_native(); - let asset2 = T::BenchmarkHelper::multiasset_id(0); - let (lp_token, caller, _) = create_asset_and_pool::(&asset1, &asset2); - let ed: u128 = T::Currency::minimum_balance().into(); - let add_amount = 1000 + ed; - }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone(), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone()) - verify { - let pool_id = (asset1.clone(), asset2.clone()); - let lp_minted = AssetConversion::::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into(); - assert_eq!( - T::PoolAssets::balance(lp_token, &caller), - lp_minted.into() - ); - assert_eq!( - T::Currency::balance(&AssetConversion::::get_pool_account(&pool_id)), - add_amount.into() - ); - assert_eq!( - T::Assets::balance(T::BenchmarkHelper::asset_id(0), &AssetConversion::::get_pool_account(&pool_id)), - 1000.into() + let lp_token = AssetConversion::::get_next_pool_asset_id(); + create_fee_asset::(&caller); + mint_setup_fee_asset::(&caller, &asset1, &asset2, &lp_token); + + #[extrinsic_call] + _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone())); + + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap(); + let pool_account = T::PoolLocator::address(&pool_id).unwrap(); + assert_last_event::( + Event::PoolCreated { creator: caller, pool_account, pool_id, lp_token }.into(), ); } - remove_liquidity { - let asset1 = T::MultiAssetIdConverter::get_native(); - let asset2 = T::BenchmarkHelper::multiasset_id(0); - let (lp_token, caller, _) = create_asset_and_pool::(&asset1, &asset2); - let ed: u128 = T::Currency::minimum_balance().into(); - let add_amount = 100 * ed; - let lp_minted = AssetConversion::::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into(); - let remove_lp_amount = lp_minted.checked_div(10).unwrap(); + #[benchmark] + fn add_liquidity() { + let caller: T::AccountId = whitelisted_caller(); + let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), - add_amount.into(), - 1000.into(), - 0.into(), - 0.into(), + create_fee_asset::(&caller); + let (lp_token, liquidity1, liquidity2) = + create_asset_and_pool::(&caller, &asset1, &asset2); + + #[extrinsic_call] + _( + SystemOrigin::Signed(caller.clone()), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + liquidity1, + liquidity2, + T::Balance::one(), + T::Balance::zero(), caller.clone(), - )?; - let total_supply = >::total_issuance(lp_token.clone()); - }: _(SystemOrigin::Signed(caller.clone()), asset1, asset2, remove_lp_amount.into(), 0.into(), 0.into(), caller.clone()) - verify { - let new_total_supply = >::total_issuance(lp_token.clone()); - assert_eq!( - new_total_supply, - total_supply - remove_lp_amount.into() ); + + let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).unwrap(); + let lp_minted = + AssetConversion::::calc_lp_amount_for_zero_supply(&liquidity1, &liquidity2).unwrap(); + assert_eq!(T::PoolAssets::balance(lp_token, &caller), lp_minted); + assert_eq!(T::Assets::balance(asset1, &pool_account), liquidity1); + assert_eq!(T::Assets::balance(asset2, &pool_account), liquidity2); } - swap_exact_tokens_for_tokens { - let native = T::MultiAssetIdConverter::get_native(); - let asset1 = T::BenchmarkHelper::multiasset_id(1); - let asset2 = T::BenchmarkHelper::multiasset_id(2); - let (_, caller, _) = create_asset_and_pool::(&native, &asset1); - let (_, _) = create_asset::(&asset2); - let ed: u128 = T::Currency::minimum_balance().into(); + #[benchmark] + fn remove_liquidity() { + let caller: T::AccountId = whitelisted_caller(); + let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1); - AssetConversion::::add_liquidity( + create_fee_asset::(&caller); + let (lp_token, liquidity1, liquidity2) = + create_asset_and_pool::(&caller, &asset1, &asset2); + + let remove_lp_amount = T::Balance::one(); + + assert_ok!(AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset1.clone(), - (100 * ed).into(), - 200.into(), - 0.into(), - 0.into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + liquidity1, + liquidity2, + T::Balance::one(), + T::Balance::zero(), caller.clone(), - )?; - - let path; - let swap_amount; - // if we only allow the native-asset pools, then the worst case scenario would be to swap - // asset1-native-asset2 - if !T::AllowMultiAssetPools::get() { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset2.clone(), - (500 * ed).into(), - 1000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - path = vec![asset1.clone(), native.clone(), asset2.clone()]; - swap_amount = 100.into(); - } else { - let asset3 = T::BenchmarkHelper::multiasset_id(3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; - let (_, _) = create_asset::(&asset3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + )); + let total_supply = + >::total_issuance(lp_token.clone()); - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), - 200.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - AssetConversion::::add_liquidity( + #[extrinsic_call] + _( + SystemOrigin::Signed(caller.clone()), + Box::new(asset1), + Box::new(asset2), + remove_lp_amount, + T::Balance::zero(), + T::Balance::zero(), + caller.clone(), + ); + + let new_total_supply = >::total_issuance(lp_token); + assert_eq!(new_total_supply, total_supply - remove_lp_amount); + } + + #[benchmark] + fn swap_exact_tokens_for_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) { + let mut swap_amount = T::Balance::one(); + let mut path = vec![]; + + let caller: T::AccountId = whitelisted_caller(); + create_fee_asset::(&caller); + for n in 1..n { + let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n); + swap_amount = swap_amount + T::Balance::one(); + if path.len() == 0 { + path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())]; + } else { + path.push(Box::new(asset2.clone())); + } + + let (_, liquidity1, liquidity2) = create_asset_and_pool::(&caller, &asset1, &asset2); + + assert_ok!(AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset2.clone(), - asset3.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + liquidity1, + liquidity2, + T::Balance::one(), + T::Balance::zero(), caller.clone(), - )?; - path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; - swap_amount = ed.into(); + )); } - let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); - let native_balance = T::Currency::balance(&caller); - let asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller); - }: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone(), false) - verify { - if !T::AllowMultiAssetPools::get() { - let new_asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller); - assert_eq!(new_asset1_balance, asset1_balance - 100.into()); - } else { - let new_native_balance = T::Currency::balance(&caller); - assert_eq!(new_native_balance, native_balance - ed.into()); - } + let asset_in = *path.first().unwrap().clone(); + assert_ok!(T::Assets::mint_into( + asset_in.clone(), + &caller, + swap_amount + T::Balance::one() + )); + let init_caller_balance = T::Assets::balance(asset_in.clone(), &caller); + + #[extrinsic_call] + _( + SystemOrigin::Signed(caller.clone()), + path, + swap_amount, + T::Balance::one(), + caller.clone(), + true, + ); + + let actual_balance = T::Assets::balance(asset_in, &caller); + assert_eq!(actual_balance, init_caller_balance - swap_amount); } - swap_tokens_for_exact_tokens { - let native = T::MultiAssetIdConverter::get_native(); - let asset1 = T::BenchmarkHelper::multiasset_id(1); - let asset2 = T::BenchmarkHelper::multiasset_id(2); - let (_, caller, _) = create_asset_and_pool::(&native, &asset1); - let (_, _) = create_asset::(&asset2); - let ed: u128 = T::Currency::minimum_balance().into(); + #[benchmark] + fn swap_tokens_for_exact_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) { + let mut max_swap_amount = T::Balance::one(); + let mut path = vec![]; - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset1.clone(), - (1000 * ed).into(), - 500.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - - let path; - // if we only allow the native-asset pools, then the worst case scenario would be to swap - // asset1-native-asset2 - if !T::AllowMultiAssetPools::get() { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - native.clone(), - asset2.clone(), - (500 * ed).into(), - 1000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - path = vec![asset1.clone(), native.clone(), asset2.clone()]; - } else { - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; - let asset3 = T::BenchmarkHelper::multiasset_id(3); - let (_, _) = create_asset::(&asset3); - AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + let caller: T::AccountId = whitelisted_caller(); + create_fee_asset::(&caller); + for n in 1..n { + let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n); + max_swap_amount = max_swap_amount + T::Balance::one() + T::Balance::one(); + if path.len() == 0 { + path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())]; + } else { + path.push(Box::new(asset2.clone())); + } - AssetConversion::::add_liquidity( - SystemOrigin::Signed(caller.clone()).into(), - asset1.clone(), - asset2.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), - caller.clone(), - )?; - AssetConversion::::add_liquidity( + let (_, liquidity1, liquidity2) = create_asset_and_pool::(&caller, &asset1, &asset2); + + assert_ok!(AssetConversion::::add_liquidity( SystemOrigin::Signed(caller.clone()).into(), - asset2.clone(), - asset3.clone(), - 2000.into(), - 2000.into(), - 0.into(), - 0.into(), + Box::new(asset1.clone()), + Box::new(asset2.clone()), + liquidity1, + liquidity2, + T::Balance::one(), + T::Balance::zero(), caller.clone(), - )?; - path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; + )); } - let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); - let asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller); - let asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller); - }: _(SystemOrigin::Signed(caller.clone()), path.clone(), 100.into(), (1000 * ed).into(), caller.clone(), false) - verify { - if !T::AllowMultiAssetPools::get() { - let new_asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller); - assert_eq!(new_asset2_balance, asset2_balance + 100.into()); - } else { - let new_asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller); - assert_eq!(new_asset3_balance, asset3_balance + 100.into()); - } + let asset_in = *path.first().unwrap().clone(); + let asset_out = *path.last().unwrap().clone(); + assert_ok!(T::Assets::mint_into(asset_in, &caller, max_swap_amount)); + let init_caller_balance = T::Assets::balance(asset_out.clone(), &caller); + + #[extrinsic_call] + _( + SystemOrigin::Signed(caller.clone()), + path, + T::Balance::one(), + max_swap_amount, + caller.clone(), + true, + ); + + let actual_balance = T::Assets::balance(asset_out, &caller); + assert_eq!(actual_balance, init_caller_balance + T::Balance::one()); } impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index 8d811473e861f10b65c3b957afbb199a377875d3..f0695678fbddf07a4943c4ad0982ac95c4634866 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -53,63 +53,54 @@ //! (This can be run against the kitchen sync node in the `node` folder of this repo.) #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{DefensiveOption, Incrementable}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; - -mod types; -pub mod weights; - -#[cfg(test)] -mod tests; - #[cfg(test)] mod mock; +mod swap; +#[cfg(test)] +mod tests; +mod types; +pub mod weights; +#[cfg(feature = "runtime-benchmarks")] +pub use benchmarking::{BenchmarkHelper, NativeOrWithIdFactory}; +pub use pallet::*; +pub use swap::*; +pub use types::*; +pub use weights::WeightInfo; use codec::Codec; use frame_support::{ - ensure, - traits::tokens::{AssetId, Balance}, -}; -use frame_system::{ - ensure_signed, - pallet_prelude::{BlockNumberFor, OriginFor}, + storage::{with_storage_layer, with_transaction}, + traits::{ + fungibles::{Balanced, Create, Credit, Inspect, Mutate}, + tokens::{ + AssetId, Balance, + Fortitude::Polite, + Precision::Exact, + Preservation::{Expendable, Preserve}, + }, + AccountTouch, Incrementable, OnUnbalanced, + }, + PalletId, }; -pub use pallet::*; -use sp_arithmetic::traits::Unsigned; +use sp_core::Get; use sp_runtime::{ traits::{ - CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, MaybeDisplay, TrailingZeroInput, + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, IntegerSquareRoot, MaybeDisplay, + One, TrailingZeroInput, Zero, }, - DispatchError, + DispatchError, Saturating, TokenError, TransactionOutcome, }; -use sp_std::prelude::*; -pub use types::*; -pub use weights::WeightInfo; +use sp_std::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec}; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{ - fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, - fungibles::{Create, Inspect, Mutate}, - tokens::{ - Fortitude::Polite, - Precision::Exact, - Preservation::{Expendable, Preserve}, - }, - AccountTouch, ContainsPair, - }, - BoundedBTreeSet, PalletId, - }; - use sp_arithmetic::Permill; - use sp_runtime::{ - traits::{IntegerSquareRoot, One, Zero}, - Saturating, - }; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_arithmetic::{traits::Unsigned, Permill}; #[pallet::pallet] pub struct Pallet(_); @@ -119,57 +110,46 @@ pub mod pallet { /// Overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Currency type that this works on. - type Currency: InspectFungible - + MutateFungible; - - /// The `Currency::Balance` type of the native currency. + /// The type in which the assets for swapping are measured. type Balance: Balance; - /// The type used to describe the amount of fractions converted into assets. - type AssetBalance: Balance; - - /// A type used for conversions between `Balance` and `AssetBalance`. + /// A type used for calculations concerning the `Balance` type to avoid possible overflows. type HigherPrecisionBalance: IntegerSquareRoot + One + Ensure + Unsigned + From - + From + From - + TryInto + TryInto; - /// Identifier for the class of non-native asset. - /// Note: A `From` bound here would prevent `MultiLocation` from being used as an - /// `AssetId`. - type AssetId: AssetId; + /// Type of asset class, sourced from [`Config::Assets`], utilized to offer liquidity to a + /// pool. + type AssetKind: Parameter + MaxEncodedLen; - /// Type that identifies either the native currency or a token class from `Assets`. - /// `Ord` is added because of `get_pool_id`. - /// - /// The pool's `AccountId` is derived from this type. Any changes to the type may - /// necessitate a migration. - type MultiAssetId: AssetId + Ord + From; + /// Registry of assets utilized for providing liquidity to pools. + type Assets: Inspect + + Mutate + + AccountTouch + + Balanced; - /// Type to convert an `AssetId` into `MultiAssetId`. - type MultiAssetIdConverter: MultiAssetIdConverter; + /// Liquidity pool identifier. + type PoolId: Parameter + MaxEncodedLen + Ord; - /// `AssetId` to address the lp tokens by. - type PoolAssetId: AssetId + PartialOrd + Incrementable + From; + /// Provides means to resolve the [`Config::PoolId`] and it's `AccountId` from a pair + /// of [`Config::AssetKind`]s. + /// + /// Examples: [`crate::types::WithFirstAsset`], [`crate::types::Ascending`]. + type PoolLocator: PoolLocator; - /// Registry for the assets. - type Assets: Inspect - + Mutate - + AccountTouch - + ContainsPair; + /// Asset class for the lp tokens from [`Self::PoolAssets`]. + type PoolAssetId: AssetId + PartialOrd + Incrementable + From; /// Registry for the lp tokens. Ideally only this pallet should have create permissions on /// the assets. - type PoolAssets: Inspect + type PoolAssets: Inspect + Create + Mutate - + AccountTouch; + + AccountTouch; /// A % the liquidity providers will take of every swap. Represents 10ths of a percent. #[pallet::constant] @@ -179,8 +159,12 @@ pub mod pallet { #[pallet::constant] type PoolSetupFee: Get; - /// An account that receives the pool setup fee. - type PoolSetupFeeReceiver: Get; + /// Asset class from [`Config::Assets`] used to pay the [`Config::PoolSetupFee`]. + #[pallet::constant] + type PoolSetupFeeAsset: Get; + + /// Handler for the [`Config::PoolSetupFee`]. + type PoolSetupFeeTarget: OnUnbalanced>; /// A fee to withdraw the liquidity. #[pallet::constant] @@ -188,7 +172,7 @@ pub mod pallet { /// The minimum LP token amount that could be minted. Ameliorates rounding errors. #[pallet::constant] - type MintMinLiquidity: Get; + type MintMinLiquidity: Get; /// The max number of hops in a swap. #[pallet::constant] @@ -198,23 +182,19 @@ pub mod pallet { #[pallet::constant] type PalletId: Get; - /// A setting to allow creating pools with both non-native assets. - #[pallet::constant] - type AllowMultiAssetPools: Get; - /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The benchmarks need a way to create asset ids from u32s. #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper: BenchmarkHelper; + type BenchmarkHelper: BenchmarkHelper; } /// Map from `PoolAssetId` to `PoolInfo`. This establishes whether a pool has been officially /// created rather than people sending tokens directly to a pool's public account. #[pallet::storage] pub type Pools = - StorageMap<_, Blake2_128Concat, PoolIdOf, PoolInfo, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::PoolId, PoolInfo, OptionQuery>; /// Stores the `PoolAssetId` that is going to be used for the next lp token. /// This gets incremented whenever a new lp pool is created. @@ -231,7 +211,7 @@ pub mod pallet { creator: T::AccountId, /// The pool id associated with the pool. Note that the order of the assets may not be /// the same as the order specified in the create pool extrinsic. - pool_id: PoolIdOf, + pool_id: T::PoolId, /// The account ID of the pool. pool_account: T::AccountId, /// The id of the liquidity tokens that will be minted when assets are added to this @@ -246,15 +226,15 @@ pub mod pallet { /// The account that the liquidity tokens were minted to. mint_to: T::AccountId, /// The pool id of the pool that the liquidity was added to. - pool_id: PoolIdOf, + pool_id: T::PoolId, /// The amount of the first asset that was added to the pool. - amount1_provided: T::AssetBalance, + amount1_provided: T::Balance, /// The amount of the second asset that was added to the pool. - amount2_provided: T::AssetBalance, + amount2_provided: T::Balance, /// The id of the lp token that was minted. lp_token: T::PoolAssetId, /// The amount of lp tokens that were minted of that id. - lp_token_minted: T::AssetBalance, + lp_token_minted: T::Balance, }, /// A successful call of the `RemoveLiquidity` extrinsic will create this event. @@ -264,15 +244,15 @@ pub mod pallet { /// The account that the assets were transferred to. withdraw_to: T::AccountId, /// The pool id that the liquidity was removed from. - pool_id: PoolIdOf, + pool_id: T::PoolId, /// The amount of the first asset that was removed from the pool. - amount1: T::AssetBalance, + amount1: T::Balance, /// The amount of the second asset that was removed from the pool. - amount2: T::AssetBalance, + amount2: T::Balance, /// The id of the lp token that was burned. lp_token: T::PoolAssetId, /// The amount of lp tokens that were burned of that id. - lp_token_burned: T::AssetBalance, + lp_token_burned: T::Balance, /// Liquidity withdrawal fee (%). withdrawal_fee: Permill, }, @@ -283,33 +263,30 @@ pub mod pallet { who: T::AccountId, /// The account that the assets were transferred to. send_to: T::AccountId, - /// The route of asset ids that the swap went through. - /// E.g. A -> Dot -> B - path: BoundedVec, /// The amount of the first asset that was swapped. - amount_in: T::AssetBalance, + amount_in: T::Balance, /// The amount of the second asset that was received. - amount_out: T::AssetBalance, + amount_out: T::Balance, + /// The route of asset IDs with amounts that the swap went through. + /// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out) + path: BalancePath, }, - /// An amount has been transferred from one account to another. - Transfer { - /// The account that the assets were transferred from. - from: T::AccountId, - /// The account that the assets were transferred to. - to: T::AccountId, - /// The asset that was transferred. - asset: T::MultiAssetId, - /// The amount of the asset that was transferred. - amount: T::AssetBalance, + /// Assets have been converted from one to another. + SwapCreditExecuted { + /// The amount of the first asset that was swapped. + amount_in: T::Balance, + /// The amount of the second asset that was received. + amount_out: T::Balance, + /// The route of asset IDs with amounts that the swap went through. + /// E.g. (A, amount_in) -> (Dot, amount_out) -> (B, amount_out) + path: BalancePath, }, } #[pallet::error] pub enum Error { - /// Provided assets are equal. - EqualAssets, - /// Provided asset is not supported for pool. - UnsupportedAsset, + /// Provided asset pair is not supported for pool. + InvalidAssetPair, /// Pool already exists. PoolExists, /// Desired amount can't be zero. @@ -345,26 +322,18 @@ pub mod pallet { ZeroLiquidity, /// Amount can't be zero. ZeroAmount, - /// Insufficient liquidity in the pool. - InsufficientLiquidity, /// Calculated amount out is less than provided minimum amount. ProvidedMinimumNotSufficientForSwap, /// Provided maximum amount is not sufficient for swap. ProvidedMaximumNotSufficientForSwap, - /// Only pools with native on one side are valid. - PoolMustContainNativeCurrency, /// The provided path must consists of 2 assets at least. InvalidPath, - /// It was not possible to calculate path data. - PathError, /// The provided path must consists of unique assets. NonUniquePath, /// It was not possible to get or increment the Id of the pool. IncorrectPoolAssetId, - /// Unable to find an element in an array/vec that should have one-to-one correspondence - /// with another. For example, an array of assets constituting a `path` should have a - /// corresponding array of `amounts` along the path. - CorrespondenceError, + /// The destination account cannot exist with the swapped funds. + BelowMinimum, } #[pallet::hooks] @@ -388,48 +357,32 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create_pool())] pub fn create_pool( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, + asset1: Box, + asset2: Box, ) -> DispatchResult { let sender = ensure_signed(origin)?; - ensure!(asset1 != asset2, Error::::EqualAssets); + ensure!(asset1 != asset2, Error::::InvalidAssetPair); // prepare pool_id - let pool_id = Self::get_pool_id(asset1, asset2); + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; ensure!(!Pools::::contains_key(&pool_id), Error::::PoolExists); - let (asset1, asset2) = &pool_id; - if !T::AllowMultiAssetPools::get() && !T::MultiAssetIdConverter::is_native(asset1) { - Err(Error::::PoolMustContainNativeCurrency)?; - } - let pool_account = Self::get_pool_account(&pool_id); - frame_system::Pallet::::inc_providers(&pool_account); + let pool_account = + T::PoolLocator::address(&pool_id).map_err(|_| Error::::InvalidAssetPair)?; // pay the setup fee - T::Currency::transfer( - &sender, - &T::PoolSetupFeeReceiver::get(), - T::PoolSetupFee::get(), - Preserve, - )?; + let fee = + Self::withdraw(T::PoolSetupFeeAsset::get(), &sender, T::PoolSetupFee::get(), true)?; + T::PoolSetupFeeTarget::on_unbalanced(fee); - // try to convert both assets - match T::MultiAssetIdConverter::try_convert(asset1) { - MultiAssetIdConversionResult::Converted(asset) => - if !T::Assets::contains(&asset, &pool_account) { - T::Assets::touch(asset, pool_account.clone(), sender.clone())? - }, - MultiAssetIdConversionResult::Unsupported(_) => Err(Error::::UnsupportedAsset)?, - MultiAssetIdConversionResult::Native => (), - } - match T::MultiAssetIdConverter::try_convert(asset2) { - MultiAssetIdConversionResult::Converted(asset) => - if !T::Assets::contains(&asset, &pool_account) { - T::Assets::touch(asset, pool_account.clone(), sender.clone())? - }, - MultiAssetIdConversionResult::Unsupported(_) => Err(Error::::UnsupportedAsset)?, - MultiAssetIdConversionResult::Native => (), - } + if T::Assets::should_touch(*asset1.clone(), &pool_account) { + T::Assets::touch(*asset1, &pool_account, &sender)? + }; + + if T::Assets::should_touch(*asset2.clone(), &pool_account) { + T::Assets::touch(*asset2, &pool_account, &sender)? + }; let lp_token = NextPoolAssetId::::get() .or(T::PoolAssetId::initial_value()) @@ -438,7 +391,7 @@ pub mod pallet { NextPoolAssetId::::set(Some(next_lp_token_id)); T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?; - T::PoolAssets::touch(lp_token.clone(), pool_account.clone(), sender.clone())?; + T::PoolAssets::touch(lp_token.clone(), &pool_account, &sender)?; let pool_info = PoolInfo { lp_token: lp_token.clone() }; Pools::::insert(pool_id.clone(), pool_info); @@ -466,39 +419,33 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::add_liquidity())] pub fn add_liquidity( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - amount1_desired: T::AssetBalance, - amount2_desired: T::AssetBalance, - amount1_min: T::AssetBalance, - amount2_min: T::AssetBalance, + asset1: Box, + asset2: Box, + amount1_desired: T::Balance, + amount2_desired: T::Balance, + amount1_min: T::Balance, + amount2_min: T::Balance, mint_to: T::AccountId, ) -> DispatchResult { let sender = ensure_signed(origin)?; - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - // swap params if needed - let (amount1_desired, amount2_desired, amount1_min, amount2_min) = - if pool_id.0 == asset1 { - (amount1_desired, amount2_desired, amount1_min, amount2_min) - } else { - (amount2_desired, amount1_desired, amount2_min, amount1_min) - }; + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; + ensure!( amount1_desired > Zero::zero() && amount2_desired > Zero::zero(), Error::::WrongDesiredAmount ); - let maybe_pool = Pools::::get(&pool_id); - let pool = maybe_pool.as_ref().ok_or(Error::::PoolNotFound)?; - let pool_account = Self::get_pool_account(&pool_id); + let pool = Pools::::get(&pool_id).ok_or(Error::::PoolNotFound)?; + let pool_account = + T::PoolLocator::address(&pool_id).map_err(|_| Error::::InvalidAssetPair)?; - let (asset1, asset2) = &pool_id; - let reserve1 = Self::get_balance(&pool_account, asset1)?; - let reserve2 = Self::get_balance(&pool_account, asset2)?; + let reserve1 = Self::get_balance(&pool_account, *asset1.clone()); + let reserve2 = Self::get_balance(&pool_account, *asset2.clone()); - let amount1: T::AssetBalance; - let amount2: T::AssetBalance; + let amount1: T::Balance; + let amount2: T::Balance; if reserve1.is_zero() || reserve2.is_zero() { amount1 = amount1_desired; amount2 = amount2_desired; @@ -527,17 +474,21 @@ pub mod pallet { } } - Self::validate_minimal_amount(amount1.saturating_add(reserve1), asset1) - .map_err(|_| Error::::AmountOneLessThanMinimal)?; - Self::validate_minimal_amount(amount2.saturating_add(reserve2), asset2) - .map_err(|_| Error::::AmountTwoLessThanMinimal)?; + ensure!( + amount1.saturating_add(reserve1) >= T::Assets::minimum_balance(*asset1.clone()), + Error::::AmountOneLessThanMinimal + ); + ensure!( + amount2.saturating_add(reserve2) >= T::Assets::minimum_balance(*asset2.clone()), + Error::::AmountTwoLessThanMinimal + ); - Self::transfer(asset1, &sender, &pool_account, amount1, true)?; - Self::transfer(asset2, &sender, &pool_account, amount2, true)?; + T::Assets::transfer(*asset1, &sender, &pool_account, amount1, Preserve)?; + T::Assets::transfer(*asset2, &sender, &pool_account, amount2, Preserve)?; let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone()); - let lp_token_amount: T::AssetBalance; + let lp_token_amount: T::Balance; if total_supply.is_zero() { lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?; T::PoolAssets::mint_into( @@ -564,7 +515,7 @@ pub mod pallet { pool_id, amount1_provided: amount1, amount2_provided: amount2, - lp_token: pool.lp_token.clone(), + lp_token: pool.lp_token, lp_token_minted: lp_token_amount, }); @@ -578,32 +529,26 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::remove_liquidity())] pub fn remove_liquidity( origin: OriginFor, - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - lp_token_burn: T::AssetBalance, - amount1_min_receive: T::AssetBalance, - amount2_min_receive: T::AssetBalance, + asset1: Box, + asset2: Box, + lp_token_burn: T::Balance, + amount1_min_receive: T::Balance, + amount2_min_receive: T::Balance, withdraw_to: T::AccountId, ) -> DispatchResult { let sender = ensure_signed(origin)?; - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - // swap params if needed - let (amount1_min_receive, amount2_min_receive) = if pool_id.0 == asset1 { - (amount1_min_receive, amount2_min_receive) - } else { - (amount2_min_receive, amount1_min_receive) - }; - let (asset1, asset2) = pool_id.clone(); + let pool_id = T::PoolLocator::pool_id(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; ensure!(lp_token_burn > Zero::zero(), Error::::ZeroLiquidity); - let maybe_pool = Pools::::get(&pool_id); - let pool = maybe_pool.as_ref().ok_or(Error::::PoolNotFound)?; + let pool = Pools::::get(&pool_id).ok_or(Error::::PoolNotFound)?; - let pool_account = Self::get_pool_account(&pool_id); - let reserve1 = Self::get_balance(&pool_account, &asset1)?; - let reserve2 = Self::get_balance(&pool_account, &asset2)?; + let pool_account = + T::PoolLocator::address(&pool_id).map_err(|_| Error::::InvalidAssetPair)?; + let reserve1 = Self::get_balance(&pool_account, *asset1.clone()); + let reserve2 = Self::get_balance(&pool_account, *asset2.clone()); let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone()); let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn; @@ -622,16 +567,20 @@ pub mod pallet { ); let reserve1_left = reserve1.saturating_sub(amount1); let reserve2_left = reserve2.saturating_sub(amount2); - Self::validate_minimal_amount(reserve1_left, &asset1) - .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; - Self::validate_minimal_amount(reserve2_left, &asset2) - .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; + ensure!( + reserve1_left >= T::Assets::minimum_balance(*asset1.clone()), + Error::::ReserveLeftLessThanMinimal + ); + ensure!( + reserve2_left >= T::Assets::minimum_balance(*asset2.clone()), + Error::::ReserveLeftLessThanMinimal + ); // burn the provided lp token amount that includes the fee T::PoolAssets::burn_from(pool.lp_token.clone(), &sender, lp_token_burn, Exact, Polite)?; - Self::transfer(&asset1, &pool_account, &withdraw_to, amount1, false)?; - Self::transfer(&asset2, &pool_account, &withdraw_to, amount2, false)?; + T::Assets::transfer(*asset1, &pool_account, &withdraw_to, amount1, Expendable)?; + T::Assets::transfer(*asset2, &pool_account, &withdraw_to, amount2, Expendable)?; Self::deposit_event(Event::LiquidityRemoved { who: sender, @@ -639,7 +588,7 @@ pub mod pallet { pool_id, amount1, amount2, - lp_token: pool.lp_token.clone(), + lp_token: pool.lp_token, lp_token_burned: lp_token_burn, withdrawal_fee: T::LiquidityWithdrawalFee::get(), }); @@ -654,19 +603,19 @@ pub mod pallet { /// [`AssetConversionApi::quote_price_exact_tokens_for_tokens`] runtime call can be called /// for a quote. #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens())] + #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens(path.len() as u32))] pub fn swap_exact_tokens_for_tokens( origin: OriginFor, - path: BoundedVec, - amount_in: T::AssetBalance, - amount_out_min: T::AssetBalance, + path: Vec>, + amount_in: T::Balance, + amount_out_min: T::Balance, send_to: T::AccountId, keep_alive: bool, ) -> DispatchResult { let sender = ensure_signed(origin)?; Self::do_swap_exact_tokens_for_tokens( sender, - path, + path.into_iter().map(|a| *a).collect(), amount_in, Some(amount_out_min), send_to, @@ -682,19 +631,19 @@ pub mod pallet { /// [`AssetConversionApi::quote_price_tokens_for_exact_tokens`] runtime call can be called /// for a quote. #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens())] + #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens(path.len() as u32))] pub fn swap_tokens_for_exact_tokens( origin: OriginFor, - path: BoundedVec, - amount_out: T::AssetBalance, - amount_in_max: T::AssetBalance, + path: Vec>, + amount_out: T::Balance, + amount_in_max: T::Balance, send_to: T::AccountId, keep_alive: bool, ) -> DispatchResult { let sender = ensure_signed(origin)?; Self::do_swap_tokens_for_exact_tokens( sender, - path, + path.into_iter().map(|a| *a).collect(), amount_out, Some(amount_in_max), send_to, @@ -713,25 +662,27 @@ pub mod pallet { /// respecting `keep_alive`. /// /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. - pub fn do_swap_exact_tokens_for_tokens( + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_exact_tokens_for_tokens( sender: T::AccountId, - path: BoundedVec, - amount_in: T::AssetBalance, - amount_out_min: Option, + path: Vec, + amount_in: T::Balance, + amount_out_min: Option, send_to: T::AccountId, keep_alive: bool, - ) -> Result { + ) -> Result { ensure!(amount_in > Zero::zero(), Error::::ZeroAmount); if let Some(amount_out_min) = amount_out_min { ensure!(amount_out_min > Zero::zero(), Error::::ZeroAmount); } Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_in(amount_in, path)?; - let amounts = Self::get_amounts_out(&amount_in, &path)?; - let amount_out = - *amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?; - + let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; if let Some(amount_out_min) = amount_out_min { ensure!( amount_out >= amount_out_min, @@ -739,7 +690,15 @@ pub mod pallet { ); } - Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Self::swap(&sender, &path, &send_to, keep_alive)?; + + Self::deposit_event(Event::SwapExecuted { + who: sender, + send_to, + amount_in, + amount_out, + path, + }); Ok(amount_out) } @@ -751,25 +710,27 @@ pub mod pallet { /// respecting `keep_alive`. /// /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. - pub fn do_swap_tokens_for_exact_tokens( + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_tokens_for_exact_tokens( sender: T::AccountId, - path: BoundedVec, - amount_out: T::AssetBalance, - amount_in_max: Option, + path: Vec, + amount_out: T::Balance, + amount_in_max: Option, send_to: T::AccountId, keep_alive: bool, - ) -> Result { + ) -> Result { ensure!(amount_out > Zero::zero(), Error::::ZeroAmount); if let Some(amount_in_max) = amount_in_max { ensure!(amount_in_max > Zero::zero(), Error::::ZeroAmount); } Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_out(amount_out, path)?; - let amounts = Self::get_amounts_in(&amount_out, &path)?; - let amount_in = - *amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?; - + let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; if let Some(amount_in_max) = amount_in_max { ensure!( amount_in <= amount_in_max, @@ -777,198 +738,236 @@ pub mod pallet { ); } - Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Self::swap(&sender, &path, &send_to, keep_alive)?; + + Self::deposit_event(Event::SwapExecuted { + who: sender, + send_to, + amount_in, + amount_out, + path, + }); + Ok(amount_in) } - /// Transfer an `amount` of `asset_id`, respecting the `keep_alive` requirements. - fn transfer( - asset_id: &T::MultiAssetId, - from: &T::AccountId, - to: &T::AccountId, - amount: T::AssetBalance, - keep_alive: bool, - ) -> Result { - let result = match T::MultiAssetIdConverter::try_convert(asset_id) { - MultiAssetIdConversionResult::Converted(asset_id) => - T::Assets::transfer(asset_id, from, to, amount, Expendable), - MultiAssetIdConversionResult::Native => { - let preservation = match keep_alive { - true => Preserve, - false => Expendable, - }; - let amount = Self::convert_asset_balance_to_native_balance(amount)?; - Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer( - from, - to, - amount, - preservation, - )?)?) - }, - MultiAssetIdConversionResult::Unsupported(_) => - Err(Error::::UnsupportedAsset.into()), + /// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` + /// is provided and the swap can't achieve at least this amount, an error is returned. + /// + /// On a successful swap, the function returns the `credit_out` of `path[last]` obtained + /// from the `credit_in`. On failure, it returns an `Err` containing the original + /// `credit_in` and the associated error code. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_exact_credit_tokens_for_tokens( + path: Vec, + credit_in: CreditOf, + amount_out_min: Option, + ) -> Result, (CreditOf, DispatchError)> { + let amount_in = credit_in.peek(); + let inspect_path = |credit_asset| { + ensure!( + path.first().map_or(false, |a| *a == credit_asset), + Error::::InvalidPath + ); + ensure!(!amount_in.is_zero(), Error::::ZeroAmount); + ensure!(amount_out_min.map_or(true, |a| !a.is_zero()), Error::::ZeroAmount); + + Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_in(amount_in, path)?; + + let amount_out = path.last().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; + ensure!( + amount_out_min.map_or(true, |a| amount_out >= a), + Error::::ProvidedMinimumNotSufficientForSwap + ); + Ok((path, amount_out)) + }; + let (path, amount_out) = match inspect_path(credit_in.asset()) { + Ok((p, a)) => (p, a), + Err(e) => return Err((credit_in, e)), }; - if result.is_ok() { - Self::deposit_event(Event::Transfer { - from: from.clone(), - to: to.clone(), - asset: (*asset_id).clone(), - amount, - }); - } - result - } + let credit_out = Self::credit_swap(credit_in, &path)?; - /// Convert a `Balance` type to an `AssetBalance`. - pub(crate) fn convert_native_balance_to_asset_balance( - amount: T::Balance, - ) -> Result> { - T::HigherPrecisionBalance::from(amount) - .try_into() - .map_err(|_| Error::::Overflow) - } + Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path }); - /// Convert an `AssetBalance` type to a `Balance`. - pub(crate) fn convert_asset_balance_to_native_balance( - amount: T::AssetBalance, - ) -> Result> { - T::HigherPrecisionBalance::from(amount) - .try_into() - .map_err(|_| Error::::Overflow) + Ok(credit_out) } - /// Convert a `HigherPrecisionBalance` type to an `AssetBalance`. - pub(crate) fn convert_hpb_to_asset_balance( - amount: T::HigherPrecisionBalance, - ) -> Result> { - amount.try_into().map_err(|_| Error::::Overflow) + /// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of + /// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target + /// `amount_out`, or an error will occur. + /// + /// On success, the function returns a (`credit_out`, `credit_change`) tuple, where + /// `credit_out` represents the acquired amount of the `path[last]` asset, and + /// `credit_change` is the remaining portion from the `credit_in`. On failure, an `Err` with + /// the initial `credit_in` and error code is returned. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + pub(crate) fn do_swap_credit_tokens_for_exact_tokens( + path: Vec, + credit_in: CreditOf, + amount_out: T::Balance, + ) -> Result<(CreditOf, CreditOf), (CreditOf, DispatchError)> { + let amount_in_max = credit_in.peek(); + let inspect_path = |credit_asset| { + ensure!( + path.first().map_or(false, |a| a == &credit_asset), + Error::::InvalidPath + ); + ensure!(amount_in_max > Zero::zero(), Error::::ZeroAmount); + ensure!(amount_out > Zero::zero(), Error::::ZeroAmount); + + Self::validate_swap_path(&path)?; + let path = Self::balance_path_from_amount_out(amount_out, path)?; + + let amount_in = path.first().map(|(_, a)| *a).ok_or(Error::::InvalidPath)?; + ensure!( + amount_in <= amount_in_max, + Error::::ProvidedMaximumNotSufficientForSwap + ); + + Ok((path, amount_in)) + }; + let (path, amount_in) = match inspect_path(credit_in.asset()) { + Ok((p, a)) => (p, a), + Err(e) => return Err((credit_in, e)), + }; + + let (credit_in, credit_change) = credit_in.split(amount_in); + let credit_out = Self::credit_swap(credit_in, &path)?; + + Self::deposit_event(Event::SwapCreditExecuted { amount_in, amount_out, path }); + + Ok((credit_out, credit_change)) } - /// Swap assets along a `path`, depositing in `send_to`. - pub(crate) fn do_swap( - sender: T::AccountId, - amounts: &Vec, - path: BoundedVec, - send_to: T::AccountId, + /// Swap assets along the `path`, withdrawing from `sender` and depositing in `send_to`. + /// + /// Note: It's assumed that the provided `path` is valid. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + fn swap( + sender: &T::AccountId, + path: &BalancePath, + send_to: &T::AccountId, keep_alive: bool, ) -> Result<(), DispatchError> { - ensure!(amounts.len() > 1, Error::::CorrespondenceError); - if let Some([asset1, asset2]) = &path.get(0..2) { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); - // amounts should always contain a corresponding element to path. - let first_amount = amounts.first().ok_or(Error::::CorrespondenceError)?; - - Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?; - - let mut i = 0; - let path_len = path.len() as u32; - for assets_pair in path.windows(2) { - if let [asset1, asset2] = assets_pair { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); - - let amount_out = - amounts.get((i + 1) as usize).ok_or(Error::::CorrespondenceError)?; - - let to = if i < path_len - 2 { - let asset3 = path.get((i + 2) as usize).ok_or(Error::::PathError)?; - Self::get_pool_account(&Self::get_pool_id( + let (asset_in, amount_in) = path.first().ok_or(Error::::InvalidPath)?; + let credit_in = Self::withdraw(asset_in.clone(), sender, *amount_in, keep_alive)?; + + let credit_out = Self::credit_swap(credit_in, path).map_err(|(_, e)| e)?; + T::Assets::resolve(send_to, credit_out).map_err(|_| Error::::BelowMinimum)?; + + Ok(()) + } + + /// Swap assets along the specified `path`, consuming `credit_in` and producing + /// `credit_out`. + /// + /// If an error occurs, `credit_in` is returned back. + /// + /// Note: It's assumed that the provided `path` is valid and `credit_in` corresponds to the + /// first asset in the `path`. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used + /// only inside a transactional storage context and an Err result must imply a storage + /// rollback. + fn credit_swap( + credit_in: CreditOf, + path: &BalancePath, + ) -> Result, (CreditOf, DispatchError)> { + let resolve_path = || -> Result, DispatchError> { + for pos in 0..=path.len() { + if let Some([(asset1, _), (asset2, amount_out)]) = path.get(pos..=pos + 1) { + let pool_from = T::PoolLocator::pool_address(asset1, asset2) + .map_err(|_| Error::::InvalidAssetPair)?; + + if let Some((asset3, _)) = path.get(pos + 2) { + let pool_to = T::PoolLocator::pool_address(asset2, asset3) + .map_err(|_| Error::::InvalidAssetPair)?; + + T::Assets::transfer( asset2.clone(), - asset3.clone(), - )) + &pool_from, + &pool_to, + *amount_out, + Preserve, + )?; } else { - send_to.clone() - }; + let credit_out = + Self::withdraw(asset2.clone(), &pool_from, *amount_out, true)?; + return Ok(credit_out) + } + } + } + Err(Error::::InvalidPath.into()) + }; - let reserve = Self::get_balance(&pool_account, asset2)?; - let reserve_left = reserve.saturating_sub(*amount_out); - Self::validate_minimal_amount(reserve_left, asset2) - .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; + let credit_out = match resolve_path() { + Ok(c) => c, + Err(e) => return Err((credit_in, e)), + }; - Self::transfer(asset2, &pool_account, &to, *amount_out, true)?; - } - i.saturating_inc(); + let pool_to = if let Some([(asset1, _), (asset2, _)]) = path.get(0..2) { + match T::PoolLocator::pool_address(asset1, asset2) { + Ok(address) => address, + Err(_) => return Err((credit_in, Error::::InvalidAssetPair.into())), } - Self::deposit_event(Event::SwapExecuted { - who: sender, - send_to, - path, - amount_in: *first_amount, - amount_out: *amounts.last().expect("Always has more than 1 element"), - }); } else { - return Err(Error::::InvalidPath.into()) - } - Ok(()) - } + return Err((credit_in, Error::::InvalidPath.into())) + }; - /// The account ID of the pool. - /// - /// This actually does computation. If you need to keep using it, then make sure you cache - /// the value and only call this once. - pub fn get_pool_account(pool_id: &PoolIdOf) -> T::AccountId { - let encoded_pool_id = sp_io::hashing::blake2_256(&Encode::encode(pool_id)[..]); + T::Assets::resolve(&pool_to, credit_in) + .map_err(|c| (c, Error::::BelowMinimum.into()))?; - Decode::decode(&mut TrailingZeroInput::new(encoded_pool_id.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed") + Ok(credit_out) } - /// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another - /// fungible. Returns a value in the form of an `AssetBalance`. - fn get_balance( - owner: &T::AccountId, - asset: &T::MultiAssetId, - ) -> Result> { - match T::MultiAssetIdConverter::try_convert(asset) { - MultiAssetIdConversionResult::Converted(asset_id) => Ok( - <::Assets>::reducible_balance(asset_id, owner, Expendable, Polite), - ), - MultiAssetIdConversionResult::Native => - Self::convert_native_balance_to_asset_balance( - <::Currency>::reducible_balance(owner, Expendable, Polite), - ), - MultiAssetIdConversionResult::Unsupported(_) => - Err(Error::::UnsupportedAsset.into()), + /// Removes `value` balance of `asset` from `who` account if possible. + fn withdraw( + asset: T::AssetKind, + who: &T::AccountId, + value: T::Balance, + keep_alive: bool, + ) -> Result, DispatchError> { + let preservation = match keep_alive { + true => Preserve, + false => Expendable, + }; + if preservation == Preserve { + // TODO drop the ensure! when this issue addressed + // https://github.com/paritytech/polkadot-sdk/issues/1698 + let free = T::Assets::reducible_balance(asset.clone(), who, preservation, Polite); + ensure!(free >= value, TokenError::NotExpendable); } + T::Assets::withdraw(asset, who, value, Exact, preservation, Polite) } - /// Returns a pool id constructed from 2 assets. - /// 1. Native asset should be lower than the other asset ids. - /// 2. Two native or two non-native assets are compared by their `Ord` implementation. - /// - /// We expect deterministic order, so (asset1, asset2) or (asset2, asset1) returns the same - /// result. - pub fn get_pool_id(asset1: T::MultiAssetId, asset2: T::MultiAssetId) -> PoolIdOf { - match ( - T::MultiAssetIdConverter::is_native(&asset1), - T::MultiAssetIdConverter::is_native(&asset2), - ) { - (true, false) => return (asset1, asset2), - (false, true) => return (asset2, asset1), - _ => { - // else we want to be deterministic based on `Ord` implementation - if asset1 <= asset2 { - (asset1, asset2) - } else { - (asset2, asset1) - } - }, - } + /// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another + /// fungible. Returns a value in the form of an `Balance`. + fn get_balance(owner: &T::AccountId, asset: T::AssetKind) -> T::Balance { + T::Assets::reducible_balance(asset, owner, Expendable, Polite) } /// Returns the balance of each asset in the pool. /// The tuple result is in the order requested (not necessarily the same as pool order). pub fn get_reserves( - asset1: &T::MultiAssetId, - asset2: &T::MultiAssetId, - ) -> Result<(T::AssetBalance, T::AssetBalance), Error> { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); + asset1: T::AssetKind, + asset2: T::AssetKind, + ) -> Result<(T::Balance, T::Balance), Error> { + let pool_account = T::PoolLocator::pool_address(&asset1, &asset2) + .map_err(|_| Error::::InvalidAssetPair)?; - let balance1 = Self::get_balance(&pool_account, asset1)?; - let balance2 = Self::get_balance(&pool_account, asset2)?; + let balance1 = Self::get_balance(&pool_account, asset1); + let balance2 = Self::get_balance(&pool_account, asset2); if balance1.is_zero() || balance2.is_zero() { Err(Error::::PoolNotFound)?; @@ -978,56 +977,66 @@ pub mod pallet { } /// Leading to an amount at the end of a `path`, get the required amounts in. - pub(crate) fn get_amounts_in( - amount_out: &T::AssetBalance, - path: &BoundedVec, - ) -> Result, DispatchError> { - let mut amounts: Vec = vec![*amount_out]; - - for assets_pair in path.windows(2).rev() { - if let [asset1, asset2] = assets_pair { - let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; - let prev_amount = amounts.last().expect("Always has at least one element"); - let amount_in = Self::get_amount_in(prev_amount, &reserve_in, &reserve_out)?; - amounts.push(amount_in); - } + pub(crate) fn balance_path_from_amount_out( + amount_out: T::Balance, + path: Vec, + ) -> Result, DispatchError> { + let mut balance_path: BalancePath = Vec::with_capacity(path.len()); + let mut amount_in: T::Balance = amount_out; + + let mut iter = path.into_iter().rev().peekable(); + while let Some(asset2) = iter.next() { + let asset1 = match iter.peek() { + Some(a) => a, + None => { + balance_path.push((asset2, amount_in)); + break + }, + }; + let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?; + balance_path.push((asset2, amount_in)); + amount_in = Self::get_amount_in(&amount_in, &reserve_in, &reserve_out)?; } + balance_path.reverse(); - amounts.reverse(); - Ok(amounts) + Ok(balance_path) } /// Following an amount into a `path`, get the corresponding amounts out. - pub(crate) fn get_amounts_out( - amount_in: &T::AssetBalance, - path: &BoundedVec, - ) -> Result, DispatchError> { - let mut amounts: Vec = vec![*amount_in]; - - for assets_pair in path.windows(2) { - if let [asset1, asset2] = assets_pair { - let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; - let prev_amount = amounts.last().expect("Always has at least one element"); - let amount_out = Self::get_amount_out(prev_amount, &reserve_in, &reserve_out)?; - amounts.push(amount_out); - } + pub(crate) fn balance_path_from_amount_in( + amount_in: T::Balance, + path: Vec, + ) -> Result, DispatchError> { + let mut balance_path: BalancePath = Vec::with_capacity(path.len()); + let mut amount_out: T::Balance = amount_in; + + let mut iter = path.into_iter().peekable(); + while let Some(asset1) = iter.next() { + let asset2 = match iter.peek() { + Some(a) => a, + None => { + balance_path.push((asset1, amount_out)); + break + }, + }; + let (reserve_in, reserve_out) = Self::get_reserves(asset1.clone(), asset2.clone())?; + balance_path.push((asset1, amount_out)); + amount_out = Self::get_amount_out(&amount_out, &reserve_in, &reserve_out)?; } - - Ok(amounts) + Ok(balance_path) } /// Used by the RPC service to provide current prices. pub fn quote_price_exact_tokens_for_tokens( - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - amount: T::AssetBalance, + asset1: T::AssetKind, + asset2: T::AssetKind, + amount: T::Balance, include_fee: bool, - ) -> Option { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); + ) -> Option { + let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?; - let balance1 = Self::get_balance(&pool_account, &asset1).ok()?; - let balance2 = Self::get_balance(&pool_account, &asset2).ok()?; + let balance1 = Self::get_balance(&pool_account, asset1); + let balance2 = Self::get_balance(&pool_account, asset2); if !balance1.is_zero() { if include_fee { Self::get_amount_out(&amount, &balance1, &balance2).ok() @@ -1041,16 +1050,15 @@ pub mod pallet { /// Used by the RPC service to provide current prices. pub fn quote_price_tokens_for_exact_tokens( - asset1: T::MultiAssetId, - asset2: T::MultiAssetId, - amount: T::AssetBalance, + asset1: T::AssetKind, + asset2: T::AssetKind, + amount: T::Balance, include_fee: bool, - ) -> Option { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let pool_account = Self::get_pool_account(&pool_id); + ) -> Option { + let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).ok()?; - let balance1 = Self::get_balance(&pool_account, &asset1).ok()?; - let balance2 = Self::get_balance(&pool_account, &asset2).ok()?; + let balance1 = Self::get_balance(&pool_account, asset1); + let balance2 = Self::get_balance(&pool_account, asset2); if !balance1.is_zero() { if include_fee { Self::get_amount_in(&amount, &balance1, &balance2).ok() @@ -1064,18 +1072,18 @@ pub mod pallet { /// Calculates the optimal amount from the reserves. pub fn quote( - amount: &T::AssetBalance, - reserve1: &T::AssetBalance, - reserve2: &T::AssetBalance, - ) -> Result> { - // amount * reserve2 / reserve1 + amount: &T::Balance, + reserve1: &T::Balance, + reserve2: &T::Balance, + ) -> Result> { + // (amount * reserve2) / reserve1 Self::mul_div(amount, reserve2, reserve1) } pub(super) fn calc_lp_amount_for_zero_supply( - amount1: &T::AssetBalance, - amount2: &T::AssetBalance, - ) -> Result> { + amount1: &T::Balance, + amount2: &T::Balance, + ) -> Result> { let amount1 = T::HigherPrecisionBalance::from(*amount1); let amount2 = T::HigherPrecisionBalance::from(*amount2); @@ -1089,11 +1097,7 @@ pub mod pallet { result.try_into().map_err(|_| Error::::Overflow) } - fn mul_div( - a: &T::AssetBalance, - b: &T::AssetBalance, - c: &T::AssetBalance, - ) -> Result> { + fn mul_div(a: &T::Balance, b: &T::Balance, c: &T::Balance) -> Result> { let a = T::HigherPrecisionBalance::from(*a); let b = T::HigherPrecisionBalance::from(*b); let c = T::HigherPrecisionBalance::from(*c); @@ -1112,16 +1116,16 @@ pub mod pallet { /// Given an input amount of an asset and pair reserves, returns the maximum output amount /// of the other asset. pub fn get_amount_out( - amount_in: &T::AssetBalance, - reserve_in: &T::AssetBalance, - reserve_out: &T::AssetBalance, - ) -> Result> { + amount_in: &T::Balance, + reserve_in: &T::Balance, + reserve_out: &T::Balance, + ) -> Result> { let amount_in = T::HigherPrecisionBalance::from(*amount_in); let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); if reserve_in.is_zero() || reserve_out.is_zero() { - return Err(Error::::ZeroLiquidity.into()) + return Err(Error::::ZeroLiquidity) } let amount_in_with_fee = amount_in @@ -1147,20 +1151,20 @@ pub mod pallet { /// Given an output amount of an asset and pair reserves, returns a required input amount /// of the other asset. pub fn get_amount_in( - amount_out: &T::AssetBalance, - reserve_in: &T::AssetBalance, - reserve_out: &T::AssetBalance, - ) -> Result> { + amount_out: &T::Balance, + reserve_in: &T::Balance, + reserve_out: &T::Balance, + ) -> Result> { let amount_out = T::HigherPrecisionBalance::from(*amount_out); let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); if reserve_in.is_zero() || reserve_out.is_zero() { - Err(Error::::ZeroLiquidity.into())? + Err(Error::::ZeroLiquidity)? } if amount_out >= reserve_out { - Err(Error::::AmountOutTooHigh.into())? + Err(Error::::AmountOutTooHigh)? } let numerator = reserve_in @@ -1184,42 +1188,19 @@ pub mod pallet { result.try_into().map_err(|_| Error::::Overflow) } - /// Ensure that a `value` meets the minimum balance requirements of an `asset` class. - fn validate_minimal_amount( - value: T::AssetBalance, - asset: &T::MultiAssetId, - ) -> Result<(), ()> { - if T::MultiAssetIdConverter::is_native(asset) { - let ed = T::Currency::minimum_balance(); - ensure!( - T::HigherPrecisionBalance::from(value) >= T::HigherPrecisionBalance::from(ed), - () - ); - } else { - let MultiAssetIdConversionResult::Converted(asset_id) = - T::MultiAssetIdConverter::try_convert(asset) - else { - return Err(()) - }; - let minimal = T::Assets::minimum_balance(asset_id); - ensure!(value >= minimal, ()); - } - Ok(()) - } - /// Ensure that a path is valid. - fn validate_swap_path( - path: &BoundedVec, - ) -> Result<(), DispatchError> { + fn validate_swap_path(path: &Vec) -> Result<(), DispatchError> { ensure!(path.len() >= 2, Error::::InvalidPath); + ensure!(path.len() as u32 <= T::MaxSwapPathLength::get(), Error::::InvalidPath); // validate all the pools in the path are unique - let mut pools = BoundedBTreeSet::, T::MaxSwapPathLength>::new(); + let mut pools = BTreeSet::::new(); for assets_pair in path.windows(2) { if let [asset1, asset2] = assets_pair { - let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); - let new_element = - pools.try_insert(pool_id).map_err(|_| Error::::Overflow)?; + let pool_id = T::PoolLocator::pool_id(asset1, asset2) + .map_err(|_| Error::::InvalidAssetPair)?; + + let new_element = pools.insert(pool_id); if !new_element { return Err(Error::::NonUniquePath.into()) } @@ -1238,69 +1219,35 @@ pub mod pallet { } } -impl Swap for Pallet { - fn swap_exact_tokens_for_tokens( - sender: T::AccountId, - path: Vec, - amount_in: T::HigherPrecisionBalance, - amount_out_min: Option, - send_to: T::AccountId, - keep_alive: bool, - ) -> Result { - let path = path.try_into().map_err(|_| Error::::PathError)?; - let amount_out_min = amount_out_min.map(Self::convert_hpb_to_asset_balance).transpose()?; - let amount_out = Self::do_swap_exact_tokens_for_tokens( - sender, - path, - Self::convert_hpb_to_asset_balance(amount_in)?, - amount_out_min, - send_to, - keep_alive, - )?; - Ok(amount_out.into()) - } - - fn swap_tokens_for_exact_tokens( - sender: T::AccountId, - path: Vec, - amount_out: T::HigherPrecisionBalance, - amount_in_max: Option, - send_to: T::AccountId, - keep_alive: bool, - ) -> Result { - let path = path.try_into().map_err(|_| Error::::PathError)?; - let amount_in_max = amount_in_max.map(Self::convert_hpb_to_asset_balance).transpose()?; - let amount_in = Self::do_swap_tokens_for_exact_tokens( - sender, - path, - Self::convert_hpb_to_asset_balance(amount_out)?, - amount_in_max, - send_to, - keep_alive, - )?; - Ok(amount_in.into()) - } -} - sp_api::decl_runtime_apis! { /// This runtime api allows people to query the size of the liquidity pools /// and quote prices for swaps. - pub trait AssetConversionApi where - Balance: Codec + MaybeDisplay, - AssetBalance: frame_support::traits::tokens::Balance, - AssetId: Codec + pub trait AssetConversionApi + where + Balance: frame_support::traits::tokens::Balance + MaybeDisplay, + AssetId: Codec, { /// Provides a quote for [`Pallet::swap_tokens_for_exact_tokens`]. /// /// Note that the price may have changed by the time the transaction is executed. /// (Use `amount_in_max` to control slippage.) - fn quote_price_tokens_for_exact_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + fn quote_price_tokens_for_exact_tokens( + asset1: AssetId, + asset2: AssetId, + amount: Balance, + include_fee: bool, + ) -> Option; /// Provides a quote for [`Pallet::swap_exact_tokens_for_tokens`]. /// /// Note that the price may have changed by the time the transaction is executed. /// (Use `amount_out_min` to control slippage.) - fn quote_price_exact_tokens_for_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + fn quote_price_exact_tokens_for_tokens( + asset1: AssetId, + asset2: AssetId, + amount: Balance, + include_fee: bool, + ) -> Option; /// Returns the size of the liquidity pool for the given asset pair. fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>; diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index c84263b07963a51adad88b6acf6b9dc07d3d2c87..12c8fe2eb42cb4ae0a67dddce9bab40a1b121ebb 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -19,12 +19,17 @@ use super::*; use crate as pallet_asset_conversion; - use frame_support::{ construct_runtime, derive_impl, instances::{Instance1, Instance2}, ord_parameter_types, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64}, + traits::{ + tokens::{ + fungible::{NativeFromLeft, NativeOrWithId, UnionOf}, + imbalance::ResolveAssetTo, + }, + AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, + }, PalletId, }; use frame_system::{EnsureSigned, EnsureSignedBy}; @@ -34,6 +39,7 @@ use sp_runtime::{ traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, BuildStorage, }; +use sp_std::default::Default; type Block = frame_system::mocking::MockBlock; @@ -143,38 +149,37 @@ impl pallet_assets::Config for Test { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub storage AllowMultiAssetPools: bool = true; - pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); } ord_parameter_types! { pub const AssetConversionOrigin: u128 = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); } +pub type NativeAndAssets = UnionOf, u128>; +pub type AscendingLocator = Ascending>; +pub type WithFirstAssetLocator = WithFirstAsset>; + impl Config for Test { type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type AssetBalance = ::Balance; - type AssetId = u32; + type Balance = ::Balance; + type HigherPrecisionBalance = sp_core::U256; + type AssetKind = NativeOrWithId; + type Assets = NativeAndAssets; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = Chain; type PoolAssetId = u32; - type Assets = Assets; type PoolAssets = PoolAssets; + type PoolSetupFee = ConstU128<100>; // should be more or equal to the existential deposit + type PoolSetupFeeAsset = Native; + type PoolSetupFeeTarget = ResolveAssetTo; type PalletId = AssetConversionPalletId; type WeightInfo = (); type LPFee = ConstU32<3>; // means 0.3% - type PoolSetupFee = ConstU128<100>; // should be more or equal to the existential deposit - type PoolSetupFeeReceiver = AssetConversionOrigin; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; - type AllowMultiAssetPools = AllowMultiAssetPools; type MaxSwapPathLength = ConstU32<4>; type MintMinLiquidity = ConstU128<100>; // 100 is good enough when the main currency has 12 decimals. - - type Balance = u128; - type HigherPrecisionBalance = sp_core::U256; - - type MultiAssetId = NativeOrAssetId; - type MultiAssetIdConverter = NativeOrAssetIdConverter; - #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); } diff --git a/substrate/frame/asset-conversion/src/swap.rs b/substrate/frame/asset-conversion/src/swap.rs new file mode 100644 index 0000000000000000000000000000000000000000..a6154e29414767550106544585b592903c3a6f2a --- /dev/null +++ b/substrate/frame/asset-conversion/src/swap.rs @@ -0,0 +1,212 @@ +// 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. + +//! Traits and implementations for swap between the various asset classes. + +use super::*; + +/// Trait for providing methods to swap between the various asset classes. +pub trait Swap { + /// Measure units of the asset classes for swapping. + type Balance: Balance; + /// Kind of assets that are going to be swapped. + type AssetKind; + + /// Returns the upper limit on the length of the swap path. + fn max_path_len() -> u32; + + /// Swap exactly `amount_in` of asset `path[0]` for asset `path[last]`. + /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire + /// the amount desired. + /// + /// Withdraws the `path[0]` asset from `sender`, deposits the `path[last]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful, returns the amount of `path[last]` acquired for the `amount_in`. + /// + /// This operation is expected to be atomic. + fn swap_exact_tokens_for_tokens( + sender: AccountId, + path: Vec, + amount_in: Self::Balance, + amount_out_min: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; + + /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[last]`. If an + /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be + /// too costly. + /// + /// Withdraws `path[0]` asset from `sender`, deposits `path[last]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful returns the amount of the `path[0]` taken to provide `path[last]`. + /// + /// This operation is expected to be atomic. + fn swap_tokens_for_exact_tokens( + sender: AccountId, + path: Vec, + amount_out: Self::Balance, + amount_in_max: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; +} + +/// Trait providing methods to swap between the various asset classes. +pub trait SwapCredit { + /// Measure units of the asset classes for swapping. + type Balance: Balance; + /// Kind of assets that are going to be swapped. + type AssetKind; + /// Credit implying a negative imbalance in the system that can be placed into an account or + /// alter the total supply. + type Credit; + + /// Returns the upper limit on the length of the swap path. + fn max_path_len() -> u32; + + /// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` is + /// provided and the swap can't achieve at least this amount, an error is returned. + /// + /// On a successful swap, the function returns the `credit_out` of `path[last]` obtained from + /// the `credit_in`. On failure, it returns an `Err` containing the original `credit_in` and the + /// associated error code. + /// + /// This operation is expected to be atomic. + fn swap_exact_tokens_for_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out_min: Option, + ) -> Result; + + /// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of + /// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target + /// `amount_out`, or an error will occur. + /// + /// On success, the function returns a (`credit_out`, `credit_change`) tuple, where `credit_out` + /// represents the acquired amount of the `path[last]` asset, and `credit_change` is the + /// remaining portion from the `credit_in`. On failure, an `Err` with the initial `credit_in` + /// and error code is returned. + /// + /// This operation is expected to be atomic. + fn swap_tokens_for_exact_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out: Self::Balance, + ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>; +} + +impl Swap for Pallet { + type Balance = T::Balance; + type AssetKind = T::AssetKind; + + fn max_path_len() -> u32 { + T::MaxSwapPathLength::get() + } + + fn swap_exact_tokens_for_tokens( + sender: T::AccountId, + path: Vec, + amount_in: Self::Balance, + amount_out_min: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let amount_out = with_storage_layer(|| { + Self::do_swap_exact_tokens_for_tokens( + sender, + path, + amount_in, + amount_out_min, + send_to, + keep_alive, + ) + })?; + Ok(amount_out) + } + + fn swap_tokens_for_exact_tokens( + sender: T::AccountId, + path: Vec, + amount_out: Self::Balance, + amount_in_max: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let amount_in = with_storage_layer(|| { + Self::do_swap_tokens_for_exact_tokens( + sender, + path, + amount_out, + amount_in_max, + send_to, + keep_alive, + ) + })?; + Ok(amount_in) + } +} + +impl SwapCredit for Pallet { + type Balance = T::Balance; + type AssetKind = T::AssetKind; + type Credit = CreditOf; + + fn max_path_len() -> u32 { + T::MaxSwapPathLength::get() + } + + fn swap_exact_tokens_for_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out_min: Option, + ) -> Result { + let credit_asset = credit_in.asset(); + with_transaction(|| -> TransactionOutcome> { + let res = Self::do_swap_exact_credit_tokens_for_tokens(path, credit_in, amount_out_min); + match &res { + Ok(_) => TransactionOutcome::Commit(Ok(res)), + // wrapping `res` with `Ok`, since our `Err` doesn't satisfy the + // `From` bound of the `with_transaction` function. + Err(_) => TransactionOutcome::Rollback(Ok(res)), + } + }) + // should never map an error since `with_transaction` above never returns it. + .map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))? + } + + fn swap_tokens_for_exact_tokens( + path: Vec, + credit_in: Self::Credit, + amount_out: Self::Balance, + ) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> { + let credit_asset = credit_in.asset(); + with_transaction(|| -> TransactionOutcome> { + let res = Self::do_swap_credit_tokens_for_exact_tokens(path, credit_in, amount_out); + match &res { + Ok(_) => TransactionOutcome::Commit(Ok(res)), + // wrapping `res` with `Ok`, since our `Err` doesn't satisfy the + // `From` bound of the `with_transaction` function. + Err(_) => TransactionOutcome::Rollback(Ok(res)), + } + }) + // should never map an error since `with_transaction` above never returns it. + .map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))? + } +} diff --git a/substrate/frame/asset-conversion/src/tests.rs b/substrate/frame/asset-conversion/src/tests.rs index 1c1267ab87b3fad874f6a06a3a917a81ac629d2d..e69d14fcb3c4098282d229309da69c576c983865 100644 --- a/substrate/frame/asset-conversion/src/tests.rs +++ b/substrate/frame/asset-conversion/src/tests.rs @@ -17,9 +17,15 @@ use crate::{mock::*, *}; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, assert_storage_noop, instances::Instance1, - traits::{fungible::Inspect, fungibles::InspectEnumerable, Get}, + traits::{ + fungible, + fungible::{Inspect as FungibleInspect, NativeOrWithId}, + fungibles, + fungibles::{Inspect, InspectEnumerable}, + Get, + }, }; use sp_arithmetic::Permill; use sp_runtime::{DispatchError, TokenError}; @@ -42,18 +48,14 @@ fn events() -> Vec> { result } -fn pools() -> Vec> { +fn pools() -> Vec<::PoolId> { let mut s: Vec<_> = Pools::::iter().map(|x| x.0).collect(); s.sort(); s } -fn assets() -> Vec> { - // if the storage would be public: - // let mut s: Vec<_> = pallet_assets::pallet::Asset::::iter().map(|x| x.0).collect(); - let mut s: Vec<_> = <::Assets>::asset_ids() - .map(|id| NativeOrAssetId::Asset(id)) - .collect(); +fn assets() -> Vec> { + let mut s: Vec<_> = Assets::asset_ids().map(|id| NativeOrWithId::WithId(id)).collect(); s.sort(); s } @@ -64,36 +66,71 @@ fn pool_assets() -> Vec { s } -fn create_tokens(owner: u128, tokens: Vec>) { +fn create_tokens(owner: u128, tokens: Vec>) { + create_tokens_with_ed(owner, tokens, 1) +} + +fn create_tokens_with_ed(owner: u128, tokens: Vec>, ed: u128) { for token_id in tokens { - let MultiAssetIdConversionResult::Converted(asset_id) = - NativeOrAssetIdConverter::try_convert(&token_id) - else { - unreachable!("invalid token") + let asset_id = match token_id { + NativeOrWithId::WithId(id) => id, + _ => unreachable!("invalid token"), }; - assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, false, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, false, ed)); } } -fn balance(owner: u128, token_id: NativeOrAssetId) -> u128 { - match token_id { - NativeOrAssetId::Native => <::Currency>::free_balance(owner), - NativeOrAssetId::Asset(token_id) => <::Assets>::balance(token_id, owner), - } +fn balance(owner: u128, token_id: NativeOrWithId) -> u128 { + <::Assets>::balance(token_id, &owner) } fn pool_balance(owner: u128, token_id: u32) -> u128 { <::PoolAssets>::balance(token_id, owner) } -fn get_ed() -> u128 { - <::Currency>::minimum_balance() +fn get_native_ed() -> u128 { + <::Assets>::minimum_balance(NativeOrWithId::Native) } macro_rules! bvec { - ($( $x:tt )*) => { - vec![$( $x )*].try_into().unwrap() - } + ($($x:expr),+ $(,)?) => ( + vec![$( Box::new( $x ), )*] + ) +} + +#[test] +fn validate_with_first_asset_pool_id_locator() { + new_test_ext().execute_with(|| { + use NativeOrWithId::{Native, WithId}; + assert_eq!(WithFirstAssetLocator::pool_id(&Native, &WithId(2)), Ok((Native, WithId(2)))); + assert_eq!(WithFirstAssetLocator::pool_id(&WithId(2), &Native), Ok((Native, WithId(2)))); + assert_noop!(WithFirstAssetLocator::pool_id(&Native, &Native), ()); + assert_noop!(WithFirstAssetLocator::pool_id(&WithId(2), &WithId(1)), ()); + }); +} + +#[test] +fn validate_ascending_pool_id_locator() { + new_test_ext().execute_with(|| { + use NativeOrWithId::{Native, WithId}; + assert_eq!(AscendingLocator::pool_id(&Native, &WithId(2)), Ok((Native, WithId(2)))); + assert_eq!(AscendingLocator::pool_id(&WithId(2), &Native), Ok((Native, WithId(2)))); + assert_eq!(AscendingLocator::pool_id(&WithId(2), &WithId(1)), Ok((WithId(1), WithId(2)))); + assert_eq!(AscendingLocator::pool_id(&Native, &Native), Err(())); + assert_eq!(AscendingLocator::pool_id(&WithId(1), &WithId(1)), Err(())); + }); +} + +#[test] +fn validate_native_or_with_id_sorting() { + new_test_ext().execute_with(|| { + use NativeOrWithId::{Native, WithId}; + assert!(WithId(2) > WithId(1)); + assert!(WithId(1) <= WithId(1)); + assert_eq!(WithId(1), WithId(1)); + assert_eq!(Native::, Native::); + assert!(Native < WithId(1)); + }); } #[test] @@ -102,10 +139,11 @@ fn check_pool_accounts_dont_collide() { let mut map = HashSet::new(); for i in 0..1_000_000u32 { - let account = AssetConversion::get_pool_account(&( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(i), - )); + let account: u128 = ::PoolLocator::address(&( + NativeOrWithId::Native, + NativeOrWithId::WithId(i), + )) + .unwrap(); if map.contains(&account) { panic!("Collision at {}", i); } @@ -137,59 +175,67 @@ fn can_create_pool() { let asset_account_deposit: u128 = >::AssetAccountDeposit::get(); let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); - create_tokens(user, vec![token_2]); + create_tokens(user, vec![token_2.clone()]); let lp_token = AssetConversion::get_next_pool_asset_id(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_1.clone()) + )); let setup_fee = <::PoolSetupFee as Get<::Balance>>::get(); - let pool_account = <::PoolSetupFeeReceiver as Get>::get(); + let pool_account = AssetConversionOrigin::get(); assert_eq!( - balance(user, NativeOrAssetId::Native), + balance(user, NativeOrWithId::Native), 1000 - (setup_fee + asset_account_deposit) ); - assert_eq!(balance(pool_account, NativeOrAssetId::Native), setup_fee); + assert_eq!(balance(pool_account, NativeOrWithId::Native), setup_fee); assert_eq!(lp_token + 1, AssetConversion::get_next_pool_asset_id()); assert_eq!( events(), [Event::::PoolCreated { creator: user, - pool_id, - pool_account: AssetConversion::get_pool_account(&pool_id), + pool_id: pool_id.clone(), + pool_account: ::PoolLocator::address(&pool_id).unwrap(), lp_token }] ); assert_eq!(pools(), vec![pool_id]); - assert_eq!(assets(), vec![token_2]); + assert_eq!(assets(), vec![token_2.clone()]); assert_eq!(pool_assets(), vec![lp_token]); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_1), - Error::::EqualAssets + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_1.clone()) + ), + Error::::InvalidAssetPair ); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_2), - Error::::EqualAssets + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_2.clone()) + ), + Error::::InvalidAssetPair ); - // validate we can create Asset(1)/Asset(2) pool - let token_1 = NativeOrAssetId::Asset(1); - create_tokens(user, vec![token_1]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - - // validate we can force the first asset to be the Native currency only - AllowMultiAssetPools::set(&false); - let token_1 = NativeOrAssetId::Asset(3); - assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), - Error::::PoolMustContainNativeCurrency - ); + // validate we cannot create WithId(1)/WithId(2) pool + let token_1 = NativeOrWithId::WithId(1); + create_tokens(user, vec![token_1.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); }); } @@ -197,25 +243,37 @@ fn can_create_pool() { fn create_same_pool_twice_should_fail() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); + create_tokens(user, vec![token_2.clone()]); let lp_token = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_1.clone()) + )); let expected_free = lp_token + 1; assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_1.clone()) + ), Error::::PoolExists ); assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); // Try switching the same tokens around: assert_noop!( - AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), + AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + ), Error::::PoolExists ); assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); @@ -226,35 +284,43 @@ fn create_same_pool_twice_should_fail() { fn different_pools_should_have_different_lp_tokens() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let token_3 = NativeOrAssetId::Asset(3); - let pool_id_1_2 = (token_1, token_2); - let pool_id_1_3 = (token_1, token_3); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); + let pool_id_1_2 = (token_1.clone(), token_2.clone()); + let pool_id_1_3 = (token_1.clone(), token_3.clone()); - create_tokens(user, vec![token_2, token_3]); + create_tokens(user, vec![token_2.clone(), token_3.clone()]); let lp_token2_1 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_1.clone()) + )); let lp_token3_1 = AssetConversion::get_next_pool_asset_id(); assert_eq!( events(), [Event::::PoolCreated { creator: user, - pool_id: pool_id_1_2, - pool_account: AssetConversion::get_pool_account(&pool_id_1_2), + pool_id: pool_id_1_2.clone(), + pool_account: ::PoolLocator::address(&pool_id_1_2).unwrap(), lp_token: lp_token2_1 }] ); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_3, token_1)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_3.clone()), + Box::new(token_1.clone()) + )); assert_eq!( events(), [Event::::PoolCreated { creator: user, - pool_id: pool_id_1_3, - pool_account: AssetConversion::get_pool_account(&pool_id_1_3), + pool_id: pool_id_1_3.clone(), + pool_account: ::PoolLocator::address(&pool_id_1_3).unwrap(), lp_token: lp_token3_1, }] ); @@ -267,25 +333,33 @@ fn different_pools_should_have_different_lp_tokens() { fn can_add_liquidity() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let token_3 = NativeOrAssetId::Asset(3); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); - create_tokens(user, vec![token_2, token_3]); + create_tokens(user, vec![token_2.clone(), token_3.clone()]); let lp_token1 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); let lp_token2 = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_3.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 10, 10000, @@ -293,28 +367,28 @@ fn can_add_liquidity() { user, )); - let pool_id = (token_1, token_2); + let pool_id = (token_1.clone(), token_2.clone()); assert!(events().contains(&Event::::LiquidityAdded { who: user, mint_to: user, - pool_id, + pool_id: pool_id.clone(), amount1_provided: 10000, amount2_provided: 10, lp_token: lp_token1, lp_token_minted: 216, })); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - assert_eq!(balance(pallet_account, token_1), 10000); - assert_eq!(balance(pallet_account, token_2), 10); - assert_eq!(balance(user, token_1), 10000 + ed); - assert_eq!(balance(user, token_2), 1000 - 10); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + assert_eq!(balance(pallet_account, token_1.clone()), 10000); + assert_eq!(balance(pallet_account, token_2.clone()), 10); + assert_eq!(balance(user, token_1.clone()), 10000 + ed); + assert_eq!(balance(user, token_2.clone()), 1000 - 10); assert_eq!(pool_balance(user, lp_token1), 216); // try to pass the non-native - native assets, the result should be the same assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_3, - token_1, + Box::new(token_3.clone()), + Box::new(token_1.clone()), 10, 10000, 10, @@ -322,21 +396,21 @@ fn can_add_liquidity() { user, )); - let pool_id = (token_1, token_3); + let pool_id = (token_1.clone(), token_3.clone()); assert!(events().contains(&Event::::LiquidityAdded { who: user, mint_to: user, - pool_id, - amount1_provided: 10000, - amount2_provided: 10, + pool_id: pool_id.clone(), + amount1_provided: 10, + amount2_provided: 10000, lp_token: lp_token2, lp_token_minted: 216, })); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - assert_eq!(balance(pallet_account, token_1), 10000); - assert_eq!(balance(pallet_account, token_3), 10); - assert_eq!(balance(user, token_1), ed); - assert_eq!(balance(user, token_3), 1000 - 10); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + assert_eq!(balance(pallet_account, token_1.clone()), 10000); + assert_eq!(balance(pallet_account, token_3.clone()), 10); + assert_eq!(balance(user, token_1.clone()), ed); + assert_eq!(balance(user, token_3.clone()), 1000 - 10); assert_eq!(pool_balance(user, lp_token2), 216); }); } @@ -345,11 +419,15 @@ fn can_add_liquidity() { fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -357,8 +435,8 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 1, 1, 1, @@ -371,9 +449,9 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, - get_ed(), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + get_native_ed(), 1, 1, 1, @@ -388,27 +466,37 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { fn add_tiny_liquidity_directly_to_pool_address() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let token_3 = NativeOrAssetId::Asset(3); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); - create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + create_tokens(user, vec![token_2.clone(), token_3.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_3.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); - // check we're still able to add the liquidity even when the pool already has some token_1 - let pallet_account = AssetConversion::get_pool_account(&(token_1, token_2)); + // check we're still able to add the liquidity even when the pool already has some + // token_1.clone() + let pallet_account = + ::PoolLocator::address(&(token_1.clone(), token_2.clone())).unwrap(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), pallet_account, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 10, 10000, @@ -416,13 +504,11 @@ fn add_tiny_liquidity_directly_to_pool_address() { user, )); - // check the same but for token_3 (non-native token) - let pallet_account = AssetConversion::get_pool_account(&(token_1, token_3)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, pallet_account, 1)); + // check the same but for token_3.clone() (non-native token) assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_3, + Box::new(token_1.clone()), + Box::new(token_3.clone()), 10000, 10, 10000, @@ -436,21 +522,31 @@ fn add_tiny_liquidity_directly_to_pool_address() { fn can_remove_liquidity() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); - create_tokens(user, vec![token_2]); + create_tokens(user, vec![token_2.clone()]); let lp_token = AssetConversion::get_next_pool_asset_id(); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000000000)); - assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 100000)); + let ed_token_1 = >::minimum_balance(); + let ed_token_2 = >::minimum_balance(2); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + user, + 10000000000 + ed_token_1 + )); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 100000 + ed_token_2)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 1000000000, 100000, 1000000000, @@ -463,8 +559,8 @@ fn can_remove_liquidity() { assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), total_lp_received, 0, 0, @@ -474,7 +570,7 @@ fn can_remove_liquidity() { assert!(events().contains(&Event::::LiquidityRemoved { who: user, withdraw_to: user, - pool_id, + pool_id: pool_id.clone(), amount1: 899991000, amount2: 89999, lp_token, @@ -482,13 +578,16 @@ fn can_remove_liquidity() { withdrawal_fee: ::LiquidityWithdrawalFee::get() })); - let pool_account = AssetConversion::get_pool_account(&pool_id); - assert_eq!(balance(pool_account, token_1), 100009000); - assert_eq!(balance(pool_account, token_2), 10001); + let pool_account = ::PoolLocator::address(&pool_id).unwrap(); + assert_eq!(balance(pool_account, token_1.clone()), 100009000); + assert_eq!(balance(pool_account, token_2.clone()), 10001); assert_eq!(pool_balance(pool_account, lp_token), 100); - assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 899991000); - assert_eq!(balance(user, token_2), 89999); + assert_eq!( + balance(user, token_1.clone()), + 10000000000 - 1000000000 + 899991000 + ed_token_1 + ); + assert_eq!(balance(user, token_2.clone()), 89999 + ed_token_2); assert_eq!(pool_balance(user, lp_token), 0); }); } @@ -497,20 +596,28 @@ fn can_remove_liquidity() { fn can_not_redeem_more_lp_tokens_than_were_minted() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); let lp_token = AssetConversion::get_next_pool_asset_id(); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + user, + 10000 + get_native_ed() + )); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 10, 10000, @@ -524,8 +631,8 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() { assert_noop!( AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 216 + 1, // Try and redeem 10 lp tokens while only 9 minted. 0, 0, @@ -540,19 +647,23 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() { fn can_quote_price() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 200, 1, @@ -562,8 +673,8 @@ fn can_quote_price() { assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 3000, false, ), @@ -572,8 +683,8 @@ fn can_quote_price() { // including fee so should get less out... assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 3000, true, ), @@ -583,8 +694,8 @@ fn can_quote_price() { // (if the above accidentally exchanged then it would not give same quote as before) assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 3000, false, ), @@ -593,8 +704,8 @@ fn can_quote_price() { // including fee so should get less out... assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 3000, true, ), @@ -604,8 +715,8 @@ fn can_quote_price() { // Check inverse: assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, 60, false, ), @@ -614,8 +725,8 @@ fn can_quote_price() { // including fee so should get less out... assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, 60, true, ), @@ -627,8 +738,8 @@ fn can_quote_price() { // assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 60, false, ), @@ -637,8 +748,8 @@ fn can_quote_price() { // including fee so should need to put more in... assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 60, true, ), @@ -648,8 +759,8 @@ fn can_quote_price() { // (if the above accidentally exchanged then it would not give same quote as before) assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 60, false, ), @@ -658,8 +769,8 @@ fn can_quote_price() { // including fee so should need to put more in... assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), 60, true, ), @@ -669,8 +780,8 @@ fn can_quote_price() { // Check inverse: assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, 3000, false, ), @@ -679,8 +790,8 @@ fn can_quote_price() { // including fee so should need to put more in... assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, 3000, true, ), @@ -694,14 +805,14 @@ fn can_quote_price() { assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, amount_in, false, ) .and_then(|amount| AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), amount, false, )), @@ -709,14 +820,14 @@ fn can_quote_price() { ); assert_eq!( AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), amount_in, false, ) .and_then(|amount| AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, amount, false, )), @@ -725,14 +836,14 @@ fn can_quote_price() { assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, amount_in, false, ) .and_then(|amount| AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), amount, false, )), @@ -740,14 +851,14 @@ fn can_quote_price() { ); assert_eq!( AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(2), + NativeOrWithId::Native, + NativeOrWithId::WithId(2), amount_in, false, ) .and_then(|amount| AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(2), - NativeOrAssetId::Native, + NativeOrWithId::WithId(2), + NativeOrWithId::Native, amount, false, )), @@ -761,19 +872,23 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() { new_test_ext().execute_with(|| { let user = 1; let user2 = 2; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 200, 1, @@ -784,23 +899,28 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() { let amount = 1; let quoted_price = 49; assert_eq!( - AssetConversion::quote_price_exact_tokens_for_tokens(token_2, token_1, amount, true,), + AssetConversion::quote_price_exact_tokens_for_tokens( + token_2.clone(), + token_1.clone(), + amount, + true, + ), Some(quoted_price) ); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, amount)); let prior_dot_balance = 20000; - assert_eq!(prior_dot_balance, balance(user2, token_1)); + assert_eq!(prior_dot_balance, balance(user2, token_1.clone())); assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user2), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], amount, 1, user2, false, )); - assert_eq!(prior_dot_balance + quoted_price, balance(user2, token_1)); + assert_eq!(prior_dot_balance + quoted_price, balance(user2, token_1.clone())); }); } @@ -809,19 +929,23 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() { new_test_ext().execute_with(|| { let user = 1; let user2 = 2; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 200, 1, @@ -832,26 +956,31 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() { let amount = 49; let quoted_price = 1; assert_eq!( - AssetConversion::quote_price_tokens_for_exact_tokens(token_2, token_1, amount, true,), + AssetConversion::quote_price_tokens_for_exact_tokens( + token_2.clone(), + token_1.clone(), + amount, + true, + ), Some(quoted_price) ); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, amount)); let prior_dot_balance = 20000; - assert_eq!(prior_dot_balance, balance(user2, token_1)); + assert_eq!(prior_dot_balance, balance(user2, token_1.clone())); let prior_asset_balance = 49; - assert_eq!(prior_asset_balance, balance(user2, token_2)); + assert_eq!(prior_asset_balance, balance(user2, token_2.clone())); assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user2), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], amount, 1, user2, false, )); - assert_eq!(prior_dot_balance + amount, balance(user2, token_1)); - assert_eq!(prior_asset_balance - quoted_price, balance(user2, token_2)); + assert_eq!(prior_dot_balance + amount, balance(user2, token_1.clone())); + assert_eq!(prior_asset_balance - quoted_price, balance(user2, token_2.clone())); }); } @@ -859,14 +988,18 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() { fn can_swap_with_native() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -875,8 +1008,8 @@ fn can_swap_with_native() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -892,18 +1025,18 @@ fn can_swap_with_native() { assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], input_amount, 1, user, false, )); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - assert_eq!(balance(user, token_1), expect_receive + ed); - assert_eq!(balance(user, token_2), 1000 - liquidity2 - input_amount); - assert_eq!(balance(pallet_account, token_1), liquidity1 - expect_receive); - assert_eq!(balance(pallet_account, token_2), liquidity2 + input_amount); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + assert_eq!(balance(user, token_1.clone()), expect_receive + ed); + assert_eq!(balance(user, token_2.clone()), 1000 - liquidity2 - input_amount); + assert_eq!(balance(pallet_account, token_1.clone()), liquidity1 - expect_receive); + assert_eq!(balance(pallet_account, token_2.clone()), liquidity2 + input_amount); }); } @@ -911,10 +1044,14 @@ fn can_swap_with_native() { fn can_swap_with_realistic_values() { new_test_ext().execute_with(|| { let user = 1; - let dot = NativeOrAssetId::Native; - let usd = NativeOrAssetId::Asset(2); - create_tokens(user, vec![usd]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), dot, usd)); + let dot = NativeOrWithId::Native; + let usd = NativeOrWithId::WithId(2); + create_tokens(user, vec![usd.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(dot.clone()), + Box::new(usd.clone()) + )); const UNIT: u128 = 1_000_000_000; @@ -925,8 +1062,8 @@ fn can_swap_with_realistic_values() { let liquidity_usd = 1_000_000 * UNIT; assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - dot, - usd, + Box::new(dot.clone()), + Box::new(usd.clone()), liquidity_dot, liquidity_usd, 1, @@ -938,7 +1075,7 @@ fn can_swap_with_realistic_values() { assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![usd, dot], + bvec![usd.clone(), dot.clone()], input_amount, 1, user, @@ -948,9 +1085,9 @@ fn can_swap_with_realistic_values() { assert!(events().contains(&Event::::SwapExecuted { who: user, send_to: user, - path: bvec![usd, dot], amount_in: 10 * UNIT, // usd amount_out: 1_993_980_120, // About 2 dot after div by UNIT. + path: vec![(usd, 10 * UNIT), (dot, 1_993_980_120)], })); }); } @@ -959,17 +1096,21 @@ fn can_swap_with_realistic_values() { fn can_not_swap_in_pool_with_no_liquidity_added_yet() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); // Check can't swap an empty pool assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], 10, 1, user, @@ -984,15 +1125,19 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() { fn check_no_panic_when_try_swap_close_to_empty_pool() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); let lp_token = AssetConversion::get_next_pool_asset_id(); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); @@ -1001,8 +1146,8 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1014,21 +1159,21 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert!(events().contains(&Event::::LiquidityAdded { who: user, mint_to: user, - pool_id, + pool_id: pool_id.clone(), amount1_provided: liquidity1, amount2_provided: liquidity2, lp_token, lp_token_minted, })); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - assert_eq!(balance(pallet_account, token_1), liquidity1); - assert_eq!(balance(pallet_account, token_2), liquidity2); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + assert_eq!(balance(pallet_account, token_1.clone()), liquidity1); + assert_eq!(balance(pallet_account, token_2.clone()), liquidity2); assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), lp_token_minted, 1, 1, @@ -1037,33 +1182,33 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { // Now, the pool should exist but be almost empty. // Let's try and drain it. - assert_eq!(balance(pallet_account, token_1), 708); - assert_eq!(balance(pallet_account, token_2), 15); + assert_eq!(balance(pallet_account, token_1.clone()), 708); + assert_eq!(balance(pallet_account, token_2.clone()), 15); // validate the reserve should always stay above the ED assert_noop!( AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], 708 - ed + 1, // amount_out 500, // amount_in_max user, false, ), - Error::::ReserveLeftLessThanMinimal + TokenError::NotExpendable, ); assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], 608, // amount_out 500, // amount_in_max user, false, )); - let token_1_left = balance(pallet_account, token_1); - let token_2_left = balance(pallet_account, token_2); + let token_1_left = balance(pallet_account, token_1.clone()); + let token_2_left = balance(pallet_account, token_2.clone()); assert_eq!(token_1_left, 708 - 608); // The price for the last tokens should be very high @@ -1077,7 +1222,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert_noop!( AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], token_1_left - 1, // amount_out 1000, // amount_in_max user, @@ -1090,7 +1235,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { assert_noop!( AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], token_1_left, // amount_out 1000, // amount_in_max user, @@ -1105,13 +1250,21 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() { fn swap_should_not_work_if_too_much_slippage() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + user, + 10000 + get_native_ed() + )); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); let liquidity1 = 10000; @@ -1119,8 +1272,8 @@ fn swap_should_not_work_if_too_much_slippage() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1133,7 +1286,7 @@ fn swap_should_not_work_if_too_much_slippage() { assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_2, token_1], + bvec![token_2.clone(), token_1.clone()], exchange_amount, // amount_in 4000, // amount_out_min user, @@ -1148,28 +1301,32 @@ fn swap_should_not_work_if_too_much_slippage() { fn can_swap_tokens_for_exact_tokens() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - let before1 = balance(pallet_account, token_1) + balance(user, token_1); - let before2 = balance(pallet_account, token_2) + balance(user, token_2); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + let before1 = balance(pallet_account, token_1.clone()) + balance(user, token_1.clone()); + let before2 = balance(pallet_account, token_2.clone()) + balance(user, token_2.clone()); let liquidity1 = 10000; let liquidity2 = 200; assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1184,23 +1341,29 @@ fn can_swap_tokens_for_exact_tokens() { assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2], + bvec![token_1.clone(), token_2.clone()], exchange_out, // amount_out 3500, // amount_in_max user, true, )); - assert_eq!(balance(user, token_1), 10000 + ed - expect_in); - assert_eq!(balance(user, token_2), 1000 - liquidity2 + exchange_out); - assert_eq!(balance(pallet_account, token_1), liquidity1 + expect_in); - assert_eq!(balance(pallet_account, token_2), liquidity2 - exchange_out); + assert_eq!(balance(user, token_1.clone()), 10000 + ed - expect_in); + assert_eq!(balance(user, token_2.clone()), 1000 - liquidity2 + exchange_out); + assert_eq!(balance(pallet_account, token_1.clone()), liquidity1 + expect_in); + assert_eq!(balance(pallet_account, token_2.clone()), liquidity2 - exchange_out); // check invariants: // native and asset totals should be preserved. - assert_eq!(before1, balance(pallet_account, token_1) + balance(user, token_1)); - assert_eq!(before2, balance(pallet_account, token_2) + balance(user, token_2)); + assert_eq!( + before1, + balance(pallet_account, token_1.clone()) + balance(user, token_1.clone()) + ); + assert_eq!( + before2, + balance(pallet_account, token_2.clone()) + balance(user, token_2.clone()) + ); }); } @@ -1209,34 +1372,40 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { new_test_ext().execute_with(|| { let user = 1; let user2 = 2; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let pool_id = (token_1, token_2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let pool_id = (token_1.clone(), token_2.clone()); let lp_token = AssetConversion::get_next_pool_asset_id(); - create_tokens(user2, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + create_tokens(user2, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); let base1 = 10000; let base2 = 1000; assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 + ed)); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, base1 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, base2)); - let pallet_account = AssetConversion::get_pool_account(&pool_id); - let before1 = - balance(pallet_account, token_1) + balance(user, token_1) + balance(user2, token_1); - let before2 = - balance(pallet_account, token_2) + balance(user, token_2) + balance(user2, token_2); + let pallet_account = ::PoolLocator::address(&pool_id).unwrap(); + let before1 = balance(pallet_account, token_1.clone()) + + balance(user, token_1.clone()) + + balance(user2, token_1.clone()); + let before2 = balance(pallet_account, token_2.clone()) + + balance(user, token_2.clone()) + + balance(user2, token_2.clone()); let liquidity1 = 10000; let liquidity2 = 200; assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1244,8 +1413,8 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { user2, )); - assert_eq!(balance(user, token_1), base1 + ed); - assert_eq!(balance(user, token_2), 0); + assert_eq!(balance(user, token_1.clone()), base1 + ed); + assert_eq!(balance(user, token_2.clone()), 0); let exchange_out = 50; let expect_in = AssetConversion::get_amount_in(&exchange_out, &liquidity1, &liquidity2) @@ -1254,28 +1423,32 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2], + bvec![token_1.clone(), token_2.clone()], exchange_out, // amount_out 3500, // amount_in_max user, true, )); - assert_eq!(balance(user, token_1), base1 + ed - expect_in); - assert_eq!(balance(pallet_account, token_1), liquidity1 + expect_in); - assert_eq!(balance(user, token_2), exchange_out); - assert_eq!(balance(pallet_account, token_2), liquidity2 - exchange_out); + assert_eq!(balance(user, token_1.clone()), base1 + ed - expect_in); + assert_eq!(balance(pallet_account, token_1.clone()), liquidity1 + expect_in); + assert_eq!(balance(user, token_2.clone()), exchange_out); + assert_eq!(balance(pallet_account, token_2.clone()), liquidity2 - exchange_out); // check invariants: // native and asset totals should be preserved. assert_eq!( before1, - balance(pallet_account, token_1) + balance(user, token_1) + balance(user2, token_1) + balance(pallet_account, token_1.clone()) + + balance(user, token_1.clone()) + + balance(user2, token_1.clone()) ); assert_eq!( before2, - balance(pallet_account, token_2) + balance(user, token_2) + balance(user2, token_2) + balance(pallet_account, token_2.clone()) + + balance(user, token_2.clone()) + + balance(user2, token_2.clone()) ); let lp_token_minted = pool_balance(user2, lp_token); @@ -1283,8 +1456,8 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { assert_ok!(AssetConversion::remove_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), lp_token_minted, 0, 0, @@ -1298,21 +1471,26 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { new_test_ext().execute_with(|| { let user = 1; let user2 = 2; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user2, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + create_tokens(user2, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 101)); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 10000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user, 2)); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user2), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 200, 1, @@ -1323,7 +1501,7 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { assert_noop!( AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2], + bvec![token_1.clone(), token_2.clone()], 1, // amount_out 101, // amount_in_max user, @@ -1335,7 +1513,7 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2], + bvec![token_1.clone(), token_2.clone()], 51, // amount_in 1, // amount_out_min user, @@ -1343,6 +1521,197 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { ), DispatchError::Token(TokenError::NotExpendable) ); + + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2.clone(), token_1.clone()], + 51, // amount_out + 2, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_2.clone(), token_1.clone()], + 2, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + }); +} + +#[test] +fn swap_when_existential_deposit_would_cause_reaping_pool_account() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); + + let ed_assets = 100; + create_tokens_with_ed(user2, vec![token_2.clone(), token_3.clone()], ed_assets); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_3.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user2), + Box::new(token_2.clone()), + Box::new(token_3.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, 400 + ed_assets)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 3, user2, 20000 + ed_assets)); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user, 400 + ed_assets)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 3, user, 20000 + ed_assets)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + 10000, + 200, + 1, + 1, + user2, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_1.clone()), + Box::new(token_3.clone()), + 200, + 10000, + 1, + 1, + user2, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + Box::new(token_2.clone()), + Box::new(token_3.clone()), + 200, + 10000, + 1, + 1, + user2, + )); + + // causes an account removal for asset token 2 + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1.clone(), token_2.clone()], + 110, // amount_out + 20000, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for asset token 2 + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1.clone(), token_2.clone()], + 15000, // amount_in + 110, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_3.clone(), token_1.clone()], + 110, // amount_out + 20000, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_3.clone(), token_1.clone()], + 15000, // amount_in + 110, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for native token 1 locate in the middle of a swap path + let amount_in = AssetConversion::balance_path_from_amount_out( + 110, + vec![token_3.clone(), token_1.clone()], + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_3.clone(), token_1.clone(), token_2.clone()], + amount_in, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + // causes an account removal for asset token 2 locate in the middle of a swap path + let amount_in = AssetConversion::balance_path_from_amount_out( + 110, + vec![token_1.clone(), token_2.clone()], + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1.clone(), token_2.clone(), token_3.clone()], + amount_in, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); }); } @@ -1350,13 +1719,21 @@ fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + get_ed())); + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + user, + 20000 + get_native_ed() + )); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); let liquidity1 = 10000; @@ -1364,8 +1741,8 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1378,7 +1755,7 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { assert_noop!( AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2], + bvec![token_1.clone(), token_2.clone()], exchange_out, // amount_out 50, // amount_in_max just greater than slippage. user, @@ -1393,15 +1770,23 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { fn swap_exact_tokens_for_tokens_in_multi_hops() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let token_3 = NativeOrAssetId::Asset(3); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); - create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + create_tokens(user, vec![token_2.clone(), token_3.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_3.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); let base1 = 10000; let base2 = 10000; assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 * 2 + ed)); @@ -1414,8 +1799,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1424,8 +1809,8 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_2, - token_3, + Box::new(token_2.clone()), + Box::new(token_3.clone()), liquidity2, liquidity3, 1, @@ -1444,7 +1829,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1], + bvec![token_1.clone()], input_amount, 80, user, @@ -1456,7 +1841,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3, token_2], + bvec![token_1.clone(), token_2.clone(), token_3.clone(), token_2.clone()], input_amount, 80, user, @@ -1467,24 +1852,24 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3], + bvec![token_1.clone(), token_2.clone(), token_3.clone()], input_amount, // amount_in 80, // amount_out_min user, true, )); - let pool_id1 = (token_1, token_2); - let pool_id2 = (token_2, token_3); - let pallet_account1 = AssetConversion::get_pool_account(&pool_id1); - let pallet_account2 = AssetConversion::get_pool_account(&pool_id2); - - assert_eq!(balance(user, token_1), base1 + ed - input_amount); - assert_eq!(balance(pallet_account1, token_1), liquidity1 + input_amount); - assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_out2); - assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_out2); - assert_eq!(balance(pallet_account2, token_3), liquidity3 - expect_out3); - assert_eq!(balance(user, token_3), 10000 - liquidity3 + expect_out3); + let pool_id1 = (token_1.clone(), token_2.clone()); + let pool_id2 = (token_2.clone(), token_3.clone()); + let pallet_account1 = ::PoolLocator::address(&pool_id1).unwrap(); + let pallet_account2 = ::PoolLocator::address(&pool_id2).unwrap(); + + assert_eq!(balance(user, token_1.clone()), base1 + ed - input_amount); + assert_eq!(balance(pallet_account1, token_1.clone()), liquidity1 + input_amount); + assert_eq!(balance(pallet_account1, token_2.clone()), liquidity2 - expect_out2); + assert_eq!(balance(pallet_account2, token_2.clone()), liquidity2 + expect_out2); + assert_eq!(balance(pallet_account2, token_3.clone()), liquidity3 - expect_out3); + assert_eq!(balance(user, token_3.clone()), 10000 - liquidity3 + expect_out3); }); } @@ -1492,15 +1877,23 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() { fn swap_tokens_for_exact_tokens_in_multi_hops() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); - let token_3 = NativeOrAssetId::Asset(3); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); - create_tokens(user, vec![token_2, token_3]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + create_tokens(user, vec![token_2.clone(), token_3.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_2.clone()), + Box::new(token_3.clone()) + )); - let ed = get_ed(); + let ed = get_native_ed(); let base1 = 10000; let base2 = 10000; assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 * 2 + ed)); @@ -1513,8 +1906,8 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), liquidity1, liquidity2, 1, @@ -1523,8 +1916,8 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_2, - token_3, + Box::new(token_2.clone()), + Box::new(token_3.clone()), liquidity2, liquidity3, 1, @@ -1542,24 +1935,24 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_2, token_3], + bvec![token_1.clone(), token_2.clone(), token_3.clone()], exchange_out3, // amount_out 1000, // amount_in_max user, true, )); - let pool_id1 = (token_1, token_2); - let pool_id2 = (token_2, token_3); - let pallet_account1 = AssetConversion::get_pool_account(&pool_id1); - let pallet_account2 = AssetConversion::get_pool_account(&pool_id2); - - assert_eq!(balance(user, token_1), base1 + ed - expect_in1); - assert_eq!(balance(pallet_account1, token_1), liquidity1 + expect_in1); - assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_in2); - assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_in2); - assert_eq!(balance(pallet_account2, token_3), liquidity3 - exchange_out3); - assert_eq!(balance(user, token_3), 10000 - liquidity3 + exchange_out3); + let pool_id1 = (token_1.clone(), token_2.clone()); + let pool_id2 = (token_2.clone(), token_3.clone()); + let pallet_account1 = ::PoolLocator::address(&pool_id1).unwrap(); + let pallet_account2 = ::PoolLocator::address(&pool_id2).unwrap(); + + assert_eq!(balance(user, token_1.clone()), base1 + ed - expect_in1); + assert_eq!(balance(pallet_account1, token_1.clone()), liquidity1 + expect_in1); + assert_eq!(balance(pallet_account1, token_2.clone()), liquidity2 - expect_in2); + assert_eq!(balance(pallet_account2, token_2.clone()), liquidity2 + expect_in2); + assert_eq!(balance(pallet_account2, token_3.clone()), liquidity3 - exchange_out3); + assert_eq!(balance(user, token_3.clone()), 10000 - liquidity3 + exchange_out3); }); } @@ -1567,9 +1960,10 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() { fn can_not_swap_same_asset() { new_test_ext().execute_with(|| { let user = 1; - let token_1 = NativeOrAssetId::Asset(1); + let token_1 = NativeOrWithId::WithId(1); + let token_2 = NativeOrWithId::Native; - create_tokens(user, vec![token_1]); + create_tokens(user, vec![token_1.clone()]); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 1000)); let liquidity1 = 1000; @@ -1577,60 +1971,44 @@ fn can_not_swap_same_asset() { assert_noop!( AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_1, + Box::new(token_1.clone()), + Box::new(token_1.clone()), liquidity1, liquidity2, 1, 1, user, ), - Error::::PoolNotFound + Error::::InvalidAssetPair ); let exchange_amount = 10; assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![token_1, token_1], + bvec![token_1.clone(), token_1.clone()], exchange_amount, 1, user, true, ), - Error::::PoolNotFound + Error::::InvalidAssetPair ); assert_noop!( AssetConversion::swap_exact_tokens_for_tokens( RuntimeOrigin::signed(user), - bvec![NativeOrAssetId::Native, NativeOrAssetId::Native], + bvec![token_2.clone(), token_2.clone()], exchange_amount, 1, user, true, ), - Error::::PoolNotFound + Error::::InvalidAssetPair ); }); } -#[test] -fn validate_pool_id_sorting() { - new_test_ext().execute_with(|| { - use crate::NativeOrAssetId::{Asset, Native}; - assert_eq!(AssetConversion::get_pool_id(Native, Asset(2)), (Native, Asset(2))); - assert_eq!(AssetConversion::get_pool_id(Asset(2), Native), (Native, Asset(2))); - assert_eq!(AssetConversion::get_pool_id(Native, Native), (Native, Native)); - assert_eq!(AssetConversion::get_pool_id(Asset(2), Asset(1)), (Asset(1), Asset(2))); - assert!(Asset(2) > Asset(1)); - assert!(Asset(1) <= Asset(1)); - assert_eq!(Asset(1), Asset(1)); - assert_eq!(Native::, Native::); - assert!(Native < Asset(1)); - }); -} - #[test] fn cannot_block_pool_creation() { new_test_ext().execute_with(|| { @@ -1639,16 +2017,16 @@ fn cannot_block_pool_creation() { // User 2 is the attacker let attacker = 2; - let ed = get_ed(); + let ed = get_native_ed(); assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), attacker, 10000 + ed)); - // The target pool the user wants to create is Native <=> Asset(2) - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(2); + // The target pool the user wants to create is Native <=> WithId(2) + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); // Attacker computes the still non-existing pool account for the target pair let pool_account = - AssetConversion::get_pool_account(&AssetConversion::get_pool_id(token_2, token_1)); + ::PoolLocator::address(&(token_1.clone(), token_2.clone())).unwrap(); // And transfers the ED to that pool account assert_ok!(Balances::transfer_allow_death( RuntimeOrigin::signed(attacker), @@ -1657,17 +2035,21 @@ fn cannot_block_pool_creation() { )); // Then, the attacker creates 14 tokens and sends one of each to the pool account for i in 10..25 { - create_tokens(attacker, vec![NativeOrAssetId::Asset(i)]); + create_tokens(attacker, vec![NativeOrWithId::WithId(i)]); assert_ok!(Assets::mint(RuntimeOrigin::signed(attacker), i, attacker, 1000)); assert_ok!(Assets::transfer(RuntimeOrigin::signed(attacker), i, pool_account, 1)); } // User can still create the pool - create_tokens(user, vec![token_2]); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); - // User has to transfer one Asset(2) token to the pool account (otherwise add_liquidity will - // fail with `AssetTwoDepositDidNotMeetMinimum`) + // User has to transfer one WithId(2) token to the pool account (otherwise add_liquidity + // will fail with `AssetTwoDepositDidNotMeetMinimum`) assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 10000)); assert_ok!(Assets::transfer(RuntimeOrigin::signed(user), 2, pool_account, 1)); @@ -1675,8 +2057,8 @@ fn cannot_block_pool_creation() { // add_liquidity shouldn't fail because of the number of consumers assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(user), - token_1, - token_2, + Box::new(token_1.clone()), + Box::new(token_2.clone()), 10000, 100, 10000, @@ -1685,3 +2067,429 @@ fn cannot_block_pool_creation() { )); }); } + +#[test] +fn swap_transactional() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + let token_3 = NativeOrWithId::WithId(3); + + let asset_ed = 150; + create_tokens_with_ed(user, vec![token_2.clone(), token_3.clone()], asset_ed); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_3.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_3.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let pool_1 = + ::PoolLocator::address(&(token_1.clone(), token_2.clone())).unwrap(); + let pool_2 = + ::PoolLocator::address(&(token_1.clone(), token_3.clone())).unwrap(); + + assert_eq!(Balances::balance(&pool_1), liquidity1); + assert_eq!(Assets::balance(2, pool_1), liquidity2); + assert_eq!(Balances::balance(&pool_2), liquidity1); + assert_eq!(Assets::balance(3, pool_2), liquidity2); + + // the amount that would cause a transfer from the last pool in the path to fail + let expected_out = liquidity2 - asset_ed + 1; + let amount_in = AssetConversion::balance_path_from_amount_out( + expected_out, + vec![token_2.clone(), token_1.clone(), token_3.clone()], + ) + .unwrap() + .first() + .map(|(_, a)| *a) + .unwrap(); + + // swap credit with `swap_tokens_for_exact_tokens` transactional + let credit_in = NativeAndAssets::issue(token_2.clone(), amount_in); + let credit_in_err_expected = NativeAndAssets::issue(token_2.clone(), amount_in); + // avoiding drop of any credit, to assert any storage mutation from an actual call. + let error; + assert_storage_noop!( + error = >::swap_tokens_for_exact_tokens( + vec![token_2.clone(), token_1.clone(), token_3.clone()], + credit_in, + expected_out, + ) + .unwrap_err() + ); + assert_eq!(error, (credit_in_err_expected, TokenError::NotExpendable.into())); + + // swap credit with `swap_exact_tokens_for_tokens` transactional + let credit_in = NativeAndAssets::issue(token_2.clone(), amount_in); + let credit_in_err_expected = NativeAndAssets::issue(token_2.clone(), amount_in); + // avoiding drop of any credit, to assert any storage mutation from an actual call. + let error; + assert_storage_noop!( + error = >::swap_exact_tokens_for_tokens( + vec![token_2.clone(), token_1.clone(), token_3.clone()], + credit_in, + Some(expected_out), + ) + .unwrap_err() + ); + assert_eq!(error, (credit_in_err_expected, TokenError::NotExpendable.into())); + + // swap with `swap_exact_tokens_for_tokens` transactional + assert_noop!( + >::swap_exact_tokens_for_tokens( + user2, + vec![token_2.clone(), token_1.clone(), token_3.clone()], + amount_in, + Some(expected_out), + user2, + true, + ), + TokenError::NotExpendable + ); + + // swap with `swap_exact_tokens_for_tokens` transactional + assert_noop!( + >::swap_tokens_for_exact_tokens( + user2, + vec![token_2.clone(), token_1.clone(), token_3.clone()], + expected_out, + Some(amount_in), + user2, + true, + ), + TokenError::NotExpendable + ); + + assert_eq!(Balances::balance(&pool_1), liquidity1); + assert_eq!(Assets::balance(2, pool_1), liquidity2); + assert_eq!(Balances::balance(&pool_2), liquidity1); + assert_eq!(Assets::balance(3, pool_2), liquidity2); + }) +} + +#[test] +fn swap_credit_returns_change() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let expected_change = NativeAndAssets::issue(token_1.clone(), 100); + let expected_credit_out = NativeAndAssets::issue(token_2.clone(), 20); + + let amount_in_max = + AssetConversion::get_amount_in(&expected_credit_out.peek(), &liquidity1, &liquidity2) + .unwrap(); + + let credit_in = + NativeAndAssets::issue(token_1.clone(), amount_in_max + expected_change.peek()); + assert_ok!( + >::swap_tokens_for_exact_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + expected_credit_out.peek(), + ), + (expected_credit_out, expected_change) + ); + }) +} + +#[test] +fn swap_credit_insufficient_amount_bounds() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // provided `credit_in` is not sufficient to swap for desired `amount_out_min` + let amount_out_min = 20; + let amount_in = + AssetConversion::get_amount_in(&(amount_out_min - 1), &liquidity2, &liquidity1) + .unwrap(); + let credit_in = NativeAndAssets::issue(token_1.clone(), amount_in); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), amount_in); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + Some(amount_out_min), + ) + .unwrap_err(); + assert_eq!( + error, + (expected_credit_in, Error::::ProvidedMinimumNotSufficientForSwap.into()) + ); + + // provided `credit_in` is not sufficient to swap for desired `amount_out` + let amount_out = 20; + let amount_in_max = + AssetConversion::get_amount_in(&(amount_out - 1), &liquidity2, &liquidity1).unwrap(); + let credit_in = NativeAndAssets::issue(token_1.clone(), amount_in_max); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), amount_in_max); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + amount_out, + ) + .unwrap_err(); + assert_eq!( + error, + (expected_credit_in, Error::::ProvidedMaximumNotSufficientForSwap.into()) + ); + }) +} + +#[test] +fn swap_credit_zero_amount() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // swap with zero credit fails for `swap_exact_tokens_for_tokens` + let credit_in = CreditOf::::zero(token_1.clone()); + let expected_credit_in = CreditOf::::zero(token_1.clone()); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + + // swap with zero credit fails for `swap_tokens_for_exact_tokens` + let credit_in = CreditOf::::zero(token_1.clone()); + let expected_credit_in = CreditOf::::zero(token_1.clone()); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + 10, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + + // swap with zero amount_out_min fails for `swap_exact_tokens_for_tokens` + let credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + Some(0), + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + + // swap with zero amount_out fails with `swap_tokens_for_exact_tokens` fails + let credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + 0, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::ZeroAmount.into())); + }); +} + +#[test] +fn swap_credit_invalid_path() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(2); + + create_tokens(user, vec![token_2.clone()]); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); + + let ed = get_native_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user2, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + Box::new(token_1.clone()), + Box::new(token_2.clone()), + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + // swap with credit_in.asset different from path[0] asset fails + let credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_2.clone(), token_1.clone()], + credit_in, + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::InvalidPath.into())); + + // swap with credit_in.asset different from path[0] asset fails + let credit_in = NativeAndAssets::issue(token_2.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_2.clone(), 10); + let error = >::swap_tokens_for_exact_tokens( + vec![token_1.clone(), token_2.clone()], + credit_in, + 10, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::InvalidPath.into())); + + // swap with path.len < 2 fails + let credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_1.clone(), 10); + let error = >::swap_exact_tokens_for_tokens( + vec![token_2.clone()], + credit_in, + None, + ) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::InvalidPath.into())); + + // swap with path.len < 2 fails + let credit_in = NativeAndAssets::issue(token_2.clone(), 10); + let expected_credit_in = NativeAndAssets::issue(token_2.clone(), 10); + let error = + >::swap_tokens_for_exact_tokens(vec![], credit_in, 10) + .unwrap_err(); + assert_eq!(error, (expected_credit_in, Error::::InvalidPath.into())); + }); +} diff --git a/substrate/frame/asset-conversion/src/types.rs b/substrate/frame/asset-conversion/src/types.rs index ffdc63ce0ce7b75c0eb111de6fb1547161fdaccf..fd6d41a55b6139b74453861fd553f0d30d307146 100644 --- a/substrate/frame/asset-conversion/src/types.rs +++ b/substrate/frame/asset-conversion/src/types.rs @@ -16,16 +16,22 @@ // limitations under the License. use super::*; - use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_std::{cmp::Ordering, marker::PhantomData}; +use sp_std::marker::PhantomData; -/// Pool ID. +/// 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). /// -/// The pool's `AccountId` is derived from this type. Any changes to the type may necessitate a -/// migration. -pub(super) type PoolIdOf = (::MultiAssetId, ::MultiAssetId); +/// Example: +/// Given path [(asset1, amount_in), (asset2, amount_out2), (asset3, amount_out3)], can be resolved: +/// 1. `asset(asset1, amount_in)` take from `user` and move to the pool(asset1, asset2); +/// 2. `asset(asset2, amount_out2)` transfer from pool(asset1, asset2) to pool(asset2, asset3); +/// 3. `asset(asset3, amount_out3)` move from pool(asset2, asset3) to `user`. +pub(super) type BalancePath = Vec<(::AssetKind, ::Balance)>; + +/// Credit of [Config::Assets]. +pub type CreditOf = Credit<::AccountId, ::Assets>; /// Stores the lp_token asset id a particular pool has been assigned. #[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] @@ -34,155 +40,94 @@ pub struct PoolInfo { pub lp_token: PoolAssetId, } -/// A trait that converts between a MultiAssetId and either the native currency or an AssetId. -pub trait MultiAssetIdConverter { - /// Returns the MultiAssetId representing the native currency of the chain. - fn get_native() -> MultiAssetId; - - /// Returns true if the given MultiAssetId is the native currency. - fn is_native(asset: &MultiAssetId) -> bool; - - /// If it's not native, returns the AssetId for the given MultiAssetId. - fn try_convert(asset: &MultiAssetId) -> MultiAssetIdConversionResult; -} - -/// Result of `MultiAssetIdConverter::try_convert`. -#[cfg_attr(feature = "std", derive(PartialEq, Debug))] -pub enum MultiAssetIdConversionResult { - /// Input asset is successfully converted. Means that converted asset is supported. - Converted(AssetId), - /// Means that input asset is the chain's native asset, if it has one, so no conversion (see - /// `MultiAssetIdConverter::get_native`). - Native, - /// Means input asset is not supported for pool. - Unsupported(MultiAssetId), -} - -/// Benchmark Helper -#[cfg(feature = "runtime-benchmarks")] -pub trait BenchmarkHelper { - /// Returns an `AssetId` from a given integer. - fn asset_id(asset_id: u32) -> AssetId; - - /// Returns a `MultiAssetId` from a given integer. - fn multiasset_id(asset_id: u32) -> MultiAssetId; +/// Provides means to resolve the `PoolId` and `AccountId` from a pair of assets. +/// +/// Resulting `PoolId` remains consistent whether the asset pair is presented as (asset1, asset2) +/// or (asset2, asset1). The derived `AccountId` may serve as an address for liquidity provider +/// tokens. +pub trait PoolLocator { + /// Retrieves the account address associated with a valid `PoolId`. + fn address(id: &PoolId) -> Result; + /// Identifies the `PoolId` for a given pair of assets. + /// + /// Returns an error if the asset pair isn't supported. + fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result; + /// Retrieves the account address associated with a given asset pair. + /// + /// Returns an error if the asset pair isn't supported. + fn pool_address(asset1: &AssetKind, asset2: &AssetKind) -> Result { + if let Ok(id) = Self::pool_id(asset1, asset2) { + Self::address(&id) + } else { + Err(()) + } + } } -#[cfg(feature = "runtime-benchmarks")] -impl BenchmarkHelper for () +/// Pool locator that mandates the inclusion of the specified `FirstAsset` in every asset pair. +/// +/// The `PoolId` is represented as a tuple of `AssetKind`s with `FirstAsset` always positioned as +/// the first element. +pub struct WithFirstAsset( + PhantomData<(FirstAsset, AccountId, AssetKind)>, +); +impl PoolLocator + for WithFirstAsset where - AssetId: From, - MultiAssetId: From, + AssetKind: Eq + Clone + Encode, + AccountId: Decode, + FirstAsset: Get, { - fn asset_id(asset_id: u32) -> AssetId { - asset_id.into() + fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { + let first = FirstAsset::get(); + match true { + _ if asset1 == asset2 => Err(()), + _ if first == *asset1 => Ok((first, asset2.clone())), + _ if first == *asset2 => Ok((first, asset1.clone())), + _ => Err(()), + } } - - fn multiasset_id(asset_id: u32) -> MultiAssetId { - asset_id.into() + fn address(id: &(AssetKind, AssetKind)) -> Result { + let encoded = sp_io::hashing::blake2_256(&Encode::encode(id)[..]); + Decode::decode(&mut TrailingZeroInput::new(encoded.as_ref())).map_err(|_| ()) } } -/// Trait for providing methods to swap between the various asset classes. -pub trait Swap { - /// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`. - /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire - /// the amount desired. - /// - /// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`, - /// respecting `keep_alive`. - /// - /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. - fn swap_exact_tokens_for_tokens( - sender: AccountId, - path: Vec, - amount_in: Balance, - amount_out_min: Option, - send_to: AccountId, - keep_alive: bool, - ) -> Result; - - /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an - /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be - /// too costly. - /// - /// Withdraws `path[0]` asset from `sender`, deposits `path[1]` asset to `send_to`, - /// respecting `keep_alive`. - /// - /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. - fn swap_tokens_for_exact_tokens( - sender: AccountId, - path: Vec, - amount_out: Balance, - amount_in_max: Option, - send_to: AccountId, - keep_alive: bool, - ) -> Result; -} - -/// An implementation of MultiAssetId that can be either Native or an asset. -#[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, Copy, Debug)] -pub enum NativeOrAssetId +/// Pool locator where the `PoolId` is a tuple of `AssetKind`s arranged in ascending order. +pub struct Ascending(PhantomData<(AccountId, AssetKind)>); +impl PoolLocator + for Ascending where - AssetId: Ord, + AssetKind: Ord + Clone + Encode, + AccountId: Decode, { - /// Native asset. For example, on the Polkadot Asset Hub this would be DOT. - #[default] - Native, - /// A non-native asset id. - Asset(AssetId), -} - -impl From for NativeOrAssetId { - fn from(asset: AssetId) -> Self { - Self::Asset(asset) - } -} - -impl Ord for NativeOrAssetId { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (Self::Native, Self::Native) => Ordering::Equal, - (Self::Native, Self::Asset(_)) => Ordering::Less, - (Self::Asset(_), Self::Native) => Ordering::Greater, - (Self::Asset(id1), Self::Asset(id2)) => ::cmp(id1, id2), + fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { + match true { + _ if asset1 > asset2 => Ok((asset2.clone(), asset1.clone())), + _ if asset1 < asset2 => Ok((asset1.clone(), asset2.clone())), + _ => Err(()), } } -} -impl PartialOrd for NativeOrAssetId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(::cmp(self, other)) - } -} -impl PartialEq for NativeOrAssetId { - fn eq(&self, other: &Self) -> bool { - self.cmp(other) == Ordering::Equal + fn address(id: &(AssetKind, AssetKind)) -> Result { + let encoded = sp_io::hashing::blake2_256(&Encode::encode(id)[..]); + Decode::decode(&mut TrailingZeroInput::new(encoded.as_ref())).map_err(|_| ()) } } -impl Eq for NativeOrAssetId {} -/// Converts between a MultiAssetId and an AssetId (or the native currency). -pub struct NativeOrAssetIdConverter { - _phantom: PhantomData, -} - -impl MultiAssetIdConverter, AssetId> - for NativeOrAssetIdConverter +/// Pool locator that chains the `First` and `Second` implementations of [`PoolLocator`]. +/// +/// If the `First` implementation fails, it falls back to the `Second`. +pub struct Chain(PhantomData<(First, Second)>); +impl PoolLocator + for Chain +where + First: PoolLocator, + Second: PoolLocator, { - fn get_native() -> NativeOrAssetId { - NativeOrAssetId::Native - } - - fn is_native(asset: &NativeOrAssetId) -> bool { - *asset == Self::get_native() + fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> { + First::pool_id(asset1, asset2).or(Second::pool_id(asset1, asset2)) } - - fn try_convert( - asset: &NativeOrAssetId, - ) -> MultiAssetIdConversionResult, AssetId> { - match asset { - NativeOrAssetId::Asset(asset) => MultiAssetIdConversionResult::Converted(asset.clone()), - NativeOrAssetId::Native => MultiAssetIdConversionResult::Native, - } + fn address(id: &(AssetKind, AssetKind)) -> Result { + First::address(id).or(Second::address(id)) } } diff --git a/substrate/frame/asset-conversion/src/weights.rs b/substrate/frame/asset-conversion/src/weights.rs index 550878ba0be96ba13e0dc2aef6d685cd3ec257b7..a0e687f7a4168032c59daf8616249776a123c363 100644 --- a/substrate/frame/asset-conversion/src/weights.rs +++ b/substrate/frame/asset-conversion/src/weights.rs @@ -15,29 +15,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_asset_conversion +//! Autogenerated weights for `pallet_asset_conversion` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-10-30, STEPS: `5`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-gghbxkbs-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` +//! HOSTNAME: `cob`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// target/production/substrate +// ./target/debug/substrate-node // benchmark // pallet -// --steps=50 -// --repeat=20 +// --chain=dev +// --steps=5 +// --repeat=2 +// --pallet=pallet-asset-conversion // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_asset_conversion -// --chain=dev -// --header=./HEADER-APACHE2 -// --output=./frame/asset-conversion/src/weights.rs -// --template=./.maintain/frame-weight-template.hbs +// --output=./substrate/frame/asset-conversion/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,25 +45,25 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; -/// Weight functions needed for pallet_asset_conversion. +/// Weight functions needed for `pallet_asset_conversion`. pub trait WeightInfo { fn create_pool() -> Weight; fn add_liquidity() -> Weight; fn remove_liquidity() -> Weight; - fn swap_exact_tokens_for_tokens() -> Weight; - fn swap_tokens_for_exact_tokens() -> Weight; + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight; + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight; } -/// Weights for pallet_asset_conversion using the Substrate node and recommended hardware. +/// Weights for `pallet_asset_conversion` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: `AssetConversion::Pools` (r:1 w:1) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) + /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -75,20 +73,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn create_pool() -> Weight { // Proof Size summary in bytes: - // Measured: `729` - // Estimated: `6196` - // Minimum execution time: 131_688_000 picoseconds. - Weight::from_parts(134_092_000, 6196) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(8_u64)) + // Measured: `1081` + // Estimated: `6360` + // Minimum execution time: 1_576_000_000 picoseconds. + Weight::from_parts(1_668_000_000, 6360) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) + /// Storage: `Assets::Account` (r:4 w:4) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -96,20 +92,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn add_liquidity() -> Weight { // Proof Size summary in bytes: - // Measured: `1382` - // Estimated: `6208` - // Minimum execution time: 157_310_000 picoseconds. - Weight::from_parts(161_547_000, 6208) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Measured: `1761` + // Estimated: `11426` + // Minimum execution time: 1_636_000_000 picoseconds. + Weight::from_parts(1_894_000_000, 11426) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) + /// Storage: `Assets::Account` (r:4 w:4) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -117,42 +111,46 @@ impl WeightInfo for SubstrateWeight { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn remove_liquidity() -> Weight { // Proof Size summary in bytes: - // Measured: `1371` - // Estimated: `6208` - // Minimum execution time: 142_769_000 picoseconds. - Weight::from_parts(145_139_000, 6208) - .saturating_add(T::DbWeight::get().reads(7_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + // Measured: `1750` + // Estimated: `11426` + // Minimum execution time: 1_507_000_000 picoseconds. + Weight::from_parts(1_524_000_000, 11426) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:3 w:3) + /// Storage: `Assets::Asset` (r:4 w:4) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:6 w:6) + /// Storage: `Assets::Account` (r:8 w:8) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn swap_exact_tokens_for_tokens() -> Weight { + /// The range of component `n` is `[2, 4]`. + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1738` - // Estimated: `16644` - // Minimum execution time: 213_186_000 picoseconds. - Weight::from_parts(217_471_000, 16644) - .saturating_add(T::DbWeight::get().reads(10_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Measured: `0 + n * (522 ±0)` + // Estimated: `990 + n * (5218 ±0)` + // Minimum execution time: 937_000_000 picoseconds. + Weight::from_parts(941_000_000, 990) + // Standard Error: 40_863_477 + .saturating_add(Weight::from_parts(205_862_068, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } - /// Storage: `Assets::Asset` (r:3 w:3) + /// Storage: `Assets::Asset` (r:4 w:4) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:6 w:6) + /// Storage: `Assets::Account` (r:8 w:8) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn swap_tokens_for_exact_tokens() -> Weight { + /// The range of component `n` is `[2, 4]`. + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1738` - // Estimated: `16644` - // Minimum execution time: 213_793_000 picoseconds. - Weight::from_parts(218_584_000, 16644) - .saturating_add(T::DbWeight::get().reads(10_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Measured: `0 + n * (522 ±0)` + // Estimated: `990 + n * (5218 ±0)` + // Minimum execution time: 935_000_000 picoseconds. + Weight::from_parts(947_000_000, 990) + // Standard Error: 46_904_620 + .saturating_add(Weight::from_parts(218_275_862, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } } @@ -162,9 +160,9 @@ impl WeightInfo for () { /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:1 w:1) + /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -174,20 +172,18 @@ impl WeightInfo for () { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn create_pool() -> Weight { // Proof Size summary in bytes: - // Measured: `729` - // Estimated: `6196` - // Minimum execution time: 131_688_000 picoseconds. - Weight::from_parts(134_092_000, 6196) - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(8_u64)) + // Measured: `1081` + // Estimated: `6360` + // Minimum execution time: 1_576_000_000 picoseconds. + Weight::from_parts(1_668_000_000, 6360) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) + /// Storage: `Assets::Account` (r:4 w:4) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -195,20 +191,18 @@ impl WeightInfo for () { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn add_liquidity() -> Weight { // Proof Size summary in bytes: - // Measured: `1382` - // Estimated: `6208` - // Minimum execution time: 157_310_000 picoseconds. - Weight::from_parts(161_547_000, 6208) - .saturating_add(RocksDbWeight::get().reads(8_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + // Measured: `1761` + // Estimated: `11426` + // Minimum execution time: 1_636_000_000 picoseconds. + Weight::from_parts(1_894_000_000, 11426) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(9_u64)) } /// Storage: `AssetConversion::Pools` (r:1 w:0) /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:1 w:1) + /// Storage: `Assets::Asset` (r:2 w:2) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:2 w:2) + /// Storage: `Assets::Account` (r:4 w:4) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) /// Storage: `PoolAssets::Asset` (r:1 w:1) /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) @@ -216,41 +210,45 @@ impl WeightInfo for () { /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) fn remove_liquidity() -> Weight { // Proof Size summary in bytes: - // Measured: `1371` - // Estimated: `6208` - // Minimum execution time: 142_769_000 picoseconds. - Weight::from_parts(145_139_000, 6208) - .saturating_add(RocksDbWeight::get().reads(7_u64)) - .saturating_add(RocksDbWeight::get().writes(6_u64)) + // Measured: `1750` + // Estimated: `11426` + // Minimum execution time: 1_507_000_000 picoseconds. + Weight::from_parts(1_524_000_000, 11426) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Assets::Asset` (r:3 w:3) + /// Storage: `Assets::Asset` (r:4 w:4) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:6 w:6) + /// Storage: `Assets::Account` (r:8 w:8) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - fn swap_exact_tokens_for_tokens() -> Weight { + /// The range of component `n` is `[2, 4]`. + fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1738` - // Estimated: `16644` - // Minimum execution time: 213_186_000 picoseconds. - Weight::from_parts(217_471_000, 16644) - .saturating_add(RocksDbWeight::get().reads(10_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Measured: `0 + n * (522 ±0)` + // Estimated: `990 + n * (5218 ±0)` + // Minimum execution time: 937_000_000 picoseconds. + Weight::from_parts(941_000_000, 990) + // Standard Error: 40_863_477 + .saturating_add(Weight::from_parts(205_862_068, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } - /// Storage: `Assets::Asset` (r:3 w:3) + /// Storage: `Assets::Asset` (r:4 w:4) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) - /// Storage: `Assets::Account` (r:6 w:6) + /// Storage: `Assets::Account` (r:8 w:8) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn swap_tokens_for_exact_tokens() -> Weight { + /// The range of component `n` is `[2, 4]`. + fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1738` - // Estimated: `16644` - // Minimum execution time: 213_793_000 picoseconds. - Weight::from_parts(218_584_000, 16644) - .saturating_add(RocksDbWeight::get().reads(10_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Measured: `0 + n * (522 ±0)` + // Estimated: `990 + n * (5218 ±0)` + // Minimum execution time: 935_000_000 picoseconds. + Weight::from_parts(947_000_000, 990) + // Standard Error: 46_904_620 + .saturating_add(Weight::from_parts(218_275_862, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into())) } } diff --git a/substrate/frame/asset-rate/Cargo.toml b/substrate/frame/asset-rate/Cargo.toml index af2776bba5f97210e019a43b9080d4b7d74743a2..835a15e8c553ad73df116d7660ef293e6bb3f2da 100644 --- a/substrate/frame/asset-rate/Cargo.toml +++ b/substrate/frame/asset-rate/Cargo.toml @@ -8,6 +8,9 @@ edition.workspace = true license = "Apache-2.0" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 87709af27274c335b0b42a69b861dcee14efc737..7b0af2421eaad34c0c48020bb2d24022d231bc33 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME asset management pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/assets/src/benchmarking.rs b/substrate/frame/assets/src/benchmarking.rs index c9b0825542de718ddf6393a38d6819f16f6f8178..8fe5a7e2493ae112b8f93d4cf961a8031a352846 100644 --- a/substrate/frame/assets/src/benchmarking.rs +++ b/substrate/frame/assets/src/benchmarking.rs @@ -54,7 +54,7 @@ fn create_default_asset, I: 'static>( (asset_id, caller, caller_lookup) } -fn create_default_minted_asset, I: 'static>( +pub fn create_default_minted_asset, I: 'static>( is_sufficient: bool, amount: T::Balance, ) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf) { @@ -102,7 +102,7 @@ fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { fn add_approvals, I: 'static>(minter: T::AccountId, n: u32) { let asset_id = default_asset_id::(); - T::Currency::deposit_creating( + let _ = T::Currency::deposit_creating( &minter, T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(), ); diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs index c2c1b6839060e91fc8f50537d09db8b6f9c50a71..8791aaa736b350292cfce58c4d4067f3e8af101b 100644 --- a/substrate/frame/assets/src/functions.rs +++ b/substrate/frame/assets/src/functions.rs @@ -77,7 +77,7 @@ impl, I: 'static> Pallet { } } else if d.is_sufficient { frame_system::Pallet::::inc_sufficients(who); - d.sufficients += 1; + d.sufficients.saturating_inc(); ExistenceReason::Sufficient } else { frame_system::Pallet::::inc_consumers(who) diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 79e4fe3001872fe5c5471277c0888376d2f42e89..f3ae03d667b4ea22a963d31a2b1a1bb5a006da2d 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -141,7 +141,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] -mod benchmarking; +pub mod benchmarking; pub mod migration; #[cfg(test)] pub mod mock; @@ -1648,8 +1648,20 @@ pub mod pallet { T::AssetAccountDeposit::get() } - fn touch(asset: T::AssetId, who: T::AccountId, depositor: T::AccountId) -> DispatchResult { - Self::do_touch(asset, who, depositor, false) + fn should_touch(asset: T::AssetId, who: &T::AccountId) -> bool { + match Asset::::get(&asset) { + Some(info) if info.is_sufficient => false, + Some(_) => !Account::::contains_key(asset, who), + _ => true, + } + } + + fn touch( + asset: T::AssetId, + who: &T::AccountId, + depositor: &T::AccountId, + ) -> DispatchResult { + Self::do_touch(asset, who.clone(), depositor.clone(), false) } } diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index f1b116a0f4a0d4ec612aed393290f5f18adb8ddd..e09648a51eccd09408b4549cc28315c8052d9ae5 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -28,6 +28,8 @@ use pallet_balances::Error as BalancesError; use sp_io::storage; use sp_runtime::{traits::ConvertInto, TokenError}; +mod sets; + fn asset_ids() -> Vec { let mut s: Vec<_> = Assets::asset_ids().collect(); s.sort(); diff --git a/substrate/frame/assets/src/tests/sets.rs b/substrate/frame/assets/src/tests/sets.rs new file mode 100644 index 0000000000000000000000000000000000000000..bdff5175185f940eaa9bd29b6195f31088925e18 --- /dev/null +++ b/substrate/frame/assets/src/tests/sets.rs @@ -0,0 +1,346 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for [`ItemOf`], [`fungible::UnionOf`] and [`fungibles::UnionOf`] set types. + +use super::*; +use frame_support::{ + parameter_types, + traits::{ + fungible, + fungible::ItemOf, + fungibles, + tokens::{ + fungibles::{ + Balanced as FungiblesBalanced, Create as FungiblesCreate, + Inspect as FungiblesInspect, Mutate as FungiblesMutate, + }, + Fortitude, Precision, Preservation, + }, + }, +}; +use sp_runtime::{traits::ConvertToValue, Either}; + +const FIRST_ASSET: u32 = 0; +const UNKNOWN_ASSET: u32 = 10; + +parameter_types! { + pub const LeftAsset: Either<(), u32> = Either::Left(()); + pub const RightAsset: Either = Either::Right(()); + pub const RightUnitAsset: Either<(), ()> = Either::Right(()); +} + +/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a +/// single asset class from [`T`] identified by [`FIRST_ASSET`]. +type FirstFungible = ItemOf, u64>; + +/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a +/// single asset class from [`T`] identified by [`UNKNOWN_ASSET`]. +type UnknownFungible = ItemOf, u64>; + +/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes +/// the [`FirstFungible`] from the left. +type LeftFungible = fungible::UnionOf, T, ConvertToValue, (), u64>; + +/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes +/// the [`LeftFungible`] from the right. +type RightFungible = + fungible::UnionOf, LeftFungible, ConvertToValue, (), u64>; + +/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes +/// the [`RightFungible`] from the left. +type LeftFungibles = fungibles::UnionOf, T, ConvertToValue, (), u64>; + +/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes +/// the [`LeftFungibles`] from the right. +/// +/// By using this type, we can navigate through each branch of [`fungible::UnionOf`], +/// [`fungibles::UnionOf`], and [`ItemOf`] to access the underlying `fungibles::*` +/// implementation provided by the pallet. +type First = fungibles::UnionOf, ConvertToValue, (), u64>; + +#[test] +fn deposit_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1 = 0; + let account1 = 1; + let account2 = 2; + + assert_ok!(>::create(asset1, account1, true, 1)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::deposit((), &account2, 50, Precision::Exact).unwrap(); + assert_eq!(First::::balance((), &account2), 50); + assert_eq!(First::::total_issuance(()), 100); + + assert_eq!(imb.peek(), 50); + + let (imb1, imb2) = imb.split(30); + assert_eq!(imb1.peek(), 30); + assert_eq!(imb2.peek(), 20); + + drop(imb2); + assert_eq!(First::::total_issuance(()), 120); + + assert!(First::::settle(&account1, imb1, Preservation::Preserve).is_ok()); + assert_eq!(First::::balance((), &account1), 70); + assert_eq!(First::::balance((), &account2), 50); + assert_eq!(First::::total_issuance(()), 120); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn issue_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1: u32 = 0; + let account1: u64 = 1; + + assert_ok!(>::create(asset1, account1, true, 1)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + + assert_eq!(First::::balance((), &account1), 100); + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::issue((), 100); + assert_eq!(First::::total_issuance(()), 200); + assert_eq!(imb.peek(), 100); + + let (imb1, imb2) = imb.split(30); + assert_eq!(imb1.peek(), 30); + assert_eq!(imb2.peek(), 70); + + drop(imb2); + assert_eq!(First::::total_issuance(()), 130); + + assert!(First::::resolve(&account1, imb1).is_ok()); + assert_eq!(First::::balance((), &account1), 130); + assert_eq!(First::::total_issuance(()), 130); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn pair_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1: u32 = 0; + let account1: u64 = 1; + + assert_ok!(>::create(asset1, account1, true, 1)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + + assert_eq!(First::::balance((), &account1), 100); + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let (debt, credit) = First::::pair((), 100); + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(debt.peek(), 100); + assert_eq!(credit.peek(), 100); + + let (debt1, debt2) = debt.split(30); + assert_eq!(debt1.peek(), 30); + assert_eq!(debt2.peek(), 70); + + drop(debt2); + assert_eq!(First::::total_issuance(()), 170); + + assert!(First::::settle(&account1, debt1, Preservation::Preserve).is_ok()); + assert_eq!(First::::balance((), &account1), 70); + assert_eq!(First::::total_issuance(()), 170); + + let (credit1, credit2) = credit.split(40); + assert_eq!(credit1.peek(), 40); + assert_eq!(credit2.peek(), 60); + + drop(credit2); + assert_eq!(First::::total_issuance(()), 110); + + assert!(First::::resolve(&account1, credit1).is_ok()); + assert_eq!(First::::balance((), &account1), 110); + assert_eq!(First::::total_issuance(()), 110); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn rescind_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1: u32 = 0; + let account1: u64 = 1; + + assert_ok!(>::create(asset1, account1, true, 1)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::rescind((), 20); + assert_eq!(First::::total_issuance(()), 80); + + assert_eq!(imb.peek(), 20); + + let (imb1, imb2) = imb.split(15); + assert_eq!(imb1.peek(), 15); + assert_eq!(imb2.peek(), 5); + + drop(imb2); + assert_eq!(First::::total_issuance(()), 85); + + assert!(First::::settle(&account1, imb1, Preservation::Preserve).is_ok()); + assert_eq!(First::::balance((), &account1), 85); + assert_eq!(First::::total_issuance(()), 85); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn resolve_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1: u32 = 0; + let account1: u64 = 1; + let account2: u64 = 2; + let ed = 11; + + assert_ok!(>::create(asset1, account1, true, ed)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + + assert_eq!(First::::balance((), &account1), 100); + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::issue((), 100); + assert_eq!(First::::total_issuance(()), 200); + assert_eq!(imb.peek(), 100); + + let (imb1, imb2) = imb.split(10); + assert_eq!(imb1.peek(), 10); + assert_eq!(imb2.peek(), 90); + assert_eq!(First::::total_issuance(()), 200); + + // ed requirements not met. + let imb1 = First::::resolve(&account2, imb1).unwrap_err(); + assert_eq!(imb1.peek(), 10); + drop(imb1); + assert_eq!(First::::total_issuance(()), 190); + assert_eq!(First::::balance((), &account2), 0); + + // resolve to new account `2`. + assert_ok!(First::::resolve(&account2, imb2)); + assert_eq!(First::::total_issuance(()), 190); + assert_eq!(First::::balance((), &account2), 90); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn settle_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1: u32 = 0; + let account1: u64 = 1; + let account2: u64 = 2; + let ed = 11; + + assert_ok!(>::create(asset1, account1, true, ed)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + assert_ok!(Assets::mint_into(asset1, &account2, 100)); + + assert_eq!(First::::balance((), &account2), 100); + assert_eq!(First::::total_issuance(()), 200); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::rescind((), 100); + assert_eq!(First::::total_issuance(()), 100); + assert_eq!(imb.peek(), 100); + + let (imb1, imb2) = imb.split(10); + assert_eq!(imb1.peek(), 10); + assert_eq!(imb2.peek(), 90); + assert_eq!(First::::total_issuance(()), 100); + + // ed requirements not met. + let imb2 = First::::settle(&account2, imb2, Preservation::Preserve).unwrap_err(); + assert_eq!(imb2.peek(), 90); + drop(imb2); + assert_eq!(First::::total_issuance(()), 190); + assert_eq!(First::::balance((), &account2), 100); + + // settle to account `1`. + assert_ok!(First::::settle(&account2, imb1, Preservation::Preserve)); + assert_eq!(First::::total_issuance(()), 190); + assert_eq!(First::::balance((), &account2), 90); + + let imb = First::::rescind((), 85); + assert_eq!(First::::total_issuance(()), 105); + assert_eq!(imb.peek(), 85); + + // settle to account `1` and expect some dust. + let imb = First::::settle(&account2, imb, Preservation::Expendable).unwrap(); + assert_eq!(imb.peek(), 5); + assert_eq!(First::::total_issuance(()), 105); + assert_eq!(First::::balance((), &account2), 0); + + drop(imb); + assert_eq!(First::::total_issuance(()), 100); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} + +#[test] +fn withdraw_from_set_types_works() { + new_test_ext().execute_with(|| { + let asset1 = 0; + let account1 = 1; + let account2 = 2; + + assert_ok!(>::create(asset1, account1, true, 1)); + assert_ok!(Assets::mint_into(asset1, &account1, 100)); + assert_ok!(Assets::mint_into(asset1, &account2, 100)); + + assert_eq!(First::::total_issuance(()), 200); + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + + let imb = First::::withdraw( + (), + &account2, + 50, + Precision::Exact, + Preservation::Preserve, + Fortitude::Polite, + ) + .unwrap(); + assert_eq!(First::::balance((), &account2), 50); + assert_eq!(First::::total_issuance(()), 200); + + assert_eq!(imb.peek(), 50); + drop(imb); + assert_eq!(First::::total_issuance(()), 150); + assert_eq!(First::::balance((), &account2), 50); + + assert_eq!(First::::total_issuance(()), Assets::total_issuance(asset1)); + }); +} diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index 0a0f20eb8e80cb869dd0be59df3d34849f5c84ac..d34779d8bc09b23990ae1fff1f0606ca4ee2da3f 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME atomic swap pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/aura/Cargo.toml b/substrate/frame/aura/Cargo.toml index 321a19f74f965976d4c41d0021c209b9a7f25df7..e2419933a20ea3d2b41134764884128c04b33e14 100644 --- a/substrate/frame/aura/Cargo.toml +++ b/substrate/frame/aura/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME AURA consensus pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/authority-discovery/Cargo.toml b/substrate/frame/authority-discovery/Cargo.toml index 7051276ad88981ceaeb309b4fec838fec4c3e046..a18199657443c2895a21f714fa0da856aaa3eb51 100644 --- a/substrate/frame/authority-discovery/Cargo.toml +++ b/substrate/frame/authority-discovery/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for authority discovery" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/authorship/Cargo.toml b/substrate/frame/authorship/Cargo.toml index 737c8da1361c1a18965874cf905169c51d6baf6a..41d4cf139721d7cd157cb88c9d2b5a34135898ea 100644 --- a/substrate/frame/authorship/Cargo.toml +++ b/substrate/frame/authorship/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/babe/Cargo.toml b/substrate/frame/babe/Cargo.toml index defa89b4f28a47be96156071191afddf98282709..639b9544b0c5fac66f53a6b0067f37535790bd5f 100644 --- a/substrate/frame/babe/Cargo.toml +++ b/substrate/frame/babe/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/babe/src/lib.rs b/substrate/frame/babe/src/lib.rs index 57e1dbb6b53c4a543e93f955d1d71bd04f144ae3..a6e44390dbc534e15e3a1658513c6ce9fa25088e 100644 --- a/substrate/frame/babe/src/lib.rs +++ b/substrate/frame/babe/src/lib.rs @@ -384,7 +384,11 @@ pub mod pallet { }); public - .make_bytes(RANDOMNESS_VRF_CONTEXT, &transcript, &signature.output) + .make_bytes( + RANDOMNESS_VRF_CONTEXT, + &transcript, + &signature.pre_output, + ) .ok() }); diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 0003c6f9f11a1e3e1636cff00769cf114bad3d38..72abbc805db1a742b08aa3feb9b8f9b0b513600e 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -183,6 +183,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index b99726ebf2dd0ca46cbf2b4a35301b5b3560ee14..198af21be81a50e83fe89bc22afbaa0722d83783 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet bags list" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/bags-list/fuzzer/Cargo.toml b/substrate/frame/bags-list/fuzzer/Cargo.toml index f3785dd1beffb2ab40eab9a9dfbdf0228730f468..20760141b23612bee46b8a79ce40874dfdef8a7b 100644 --- a/substrate/frame/bags-list/fuzzer/Cargo.toml +++ b/substrate/frame/bags-list/fuzzer/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Fuzzer for FRAME pallet bags list" publish = false +[lints] +workspace = true + [dependencies] honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index 169dd19db9aa84cf8c6b352d603afa4bc58fdb65..fb61a9867783a32084dc363f6ac18c76ed4f3e10 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet bags list remote test" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index a148684e1fb768f16cf028bfa5162f2c00ad63bf..23fe6e5832222b23b9c8b97fcab81688327e2221 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage balances" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/balances/src/impl_fungible.rs b/substrate/frame/balances/src/impl_fungible.rs index fc8c2d71f256eebd0d77b1a675f7677fc6326b43..6737727e0a2977c7a186f4cfd845d994e4e82bc2 100644 --- a/substrate/frame/balances/src/impl_fungible.rs +++ b/substrate/frame/balances/src/impl_fungible.rs @@ -17,10 +17,13 @@ //! Implementation of `fungible` traits for Balances pallet. use super::*; -use frame_support::traits::tokens::{ - Fortitude, - Preservation::{self, Preserve, Protect}, - Provenance::{self, Minted}, +use frame_support::traits::{ + tokens::{ + Fortitude, + Preservation::{self, Preserve, Protect}, + Provenance::{self, Minted}, + }, + AccountTouch, }; impl, I: 'static> fungible::Inspect for Pallet { @@ -356,3 +359,16 @@ impl, I: 'static> fungible::Balanced for Pallet } impl, I: 'static> fungible::BalancedHold for Pallet {} + +impl, I: 'static> AccountTouch<(), T::AccountId> for Pallet { + type Balance = T::Balance; + fn deposit_required(_: ()) -> Self::Balance { + Self::Balance::zero() + } + fn should_touch(_: (), _: &T::AccountId) -> bool { + false + } + fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult { + Ok(()) + } +} diff --git a/substrate/frame/balances/src/tests/fungible_tests.rs b/substrate/frame/balances/src/tests/fungible_tests.rs index 8bf0509d8f77e00802e0378eaa4c9ec26e341534..52fbe10bedec0f7131fe35fa7e6f809968741d42 100644 --- a/substrate/frame/balances/src/tests/fungible_tests.rs +++ b/substrate/frame/balances/src/tests/fungible_tests.rs @@ -146,7 +146,7 @@ fn unbalanced_trait_decrease_balance_works_2() { assert_eq!(Balances::total_balance_on_hold(&1337), 60); assert_noop!( Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite), - Error::::InsufficientBalance + TokenError::FundsUnavailable ); assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39)); assert_eq!(>::balance(&1337), 1); @@ -468,3 +468,28 @@ fn emit_events_with_changing_freezes() { assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 15 })]); }); } + +#[test] +fn withdraw_precision_exact_works() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + // `BestEffort` will not reduce anything + assert_ok!(>::withdraw( + &1, 5, BestEffort, Preserve, Polite + )); + + assert_eq!(Balances::account(&1).free, 10); + assert_eq!(Balances::account(&1).frozen, 10); + + assert_noop!( + >::withdraw(&1, 5, Exact, Preserve, Polite), + TokenError::FundsUnavailable + ); + }); +} diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index ee336def85ccdbafe1b22183e609805b758c9a9d..f43e12d3ee7311528fa266f8c4cccfc4c3baa169 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -8,12 +8,15 @@ description = "BEEFY + MMR runtime utilities" repository.workspace = true homepage = "https://substrate.io" +[lints] +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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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 4a77dd0e2ff045df539932cfaea3a251ea1694ea..999e32ab4508d65dfeec4b1340dbad4ed49c87dc 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -8,11 +8,14 @@ repository.workspace = true description = "BEEFY FRAME pallet" homepage = "https://substrate.io" +[lints] +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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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/mock.rs b/substrate/frame/beefy/src/mock.rs index 53d523cf724d92e2cb4b307fa485f1b02a8ebc31..8828fa3621853a8f3b5a2b0828fc386859ac45fc 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -37,10 +37,7 @@ use sp_state_machine::BasicExternalities; use crate as pallet_beefy; -pub use sp_consensus_beefy::{ - ecdsa_crypto::{AuthorityId as BeefyId, AuthoritySignature as BeefySignature}, - ConsensusLog, EquivocationProof, BEEFY_ENGINE_ID, -}; +pub use sp_consensus_beefy::{ecdsa_crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -201,6 +198,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 9cfaac1abfd0817c5bd9387e947feb9ef358d41a..42001c2d08ca06bde5f7d6c0b79893b8de382666 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Macro for benchmarking a FRAME runtime." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,7 +21,7 @@ linregress = { version = "0.5.1", optional = true } log = { version = "0.4.17", default-features = false } paste = "1.0" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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/benchmarking/pov/Cargo.toml b/substrate/frame/benchmarking/pov/Cargo.toml index 1ec0282855879ba343a207d2306cabdf62e50047..7c36b2f8eec3e0bf793afea0a94e8b0e1b515b3d 100644 --- a/substrate/frame/benchmarking/pov/Cargo.toml +++ b/substrate/frame/benchmarking/pov/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Pallet for testing FRAME PoV benchmarking" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/bounties/Cargo.toml b/substrate/frame/bounties/Cargo.toml index 3b77a8448e3e34c5504baa7d86e81f1c8993e262..16da862d48847ef6b04c7c669c49cacb972f5060 100644 --- a/substrate/frame/bounties/Cargo.toml +++ b/substrate/frame/bounties/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage bounties" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/bounties/src/benchmarking.rs b/substrate/frame/bounties/src/benchmarking.rs index 6fff337cba45040b01d586c09e4c96d63ab93500..3558847c8fedd263e6c57db24195a898a3d0dd6d 100644 --- a/substrate/frame/bounties/src/benchmarking.rs +++ b/substrate/frame/bounties/src/benchmarking.rs @@ -222,7 +222,7 @@ benchmarks_instance_pallet! { ); } verify { - ensure!(missed_any == false, "Missed some"); + ensure!(!missed_any, "Missed some"); if b > 0 { ensure!(budget_remaining < BalanceOf::::max_value(), "Budget not used"); assert_last_event::(Event::BountyBecameActive { index: b - 1 }.into()) diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml index 3470cf55b7873201666290385cfa5bfe9cde21a0..77757c30463619750fca0f28d0c6229367dd891d 100644 --- a/substrate/frame/broker/Cargo.toml +++ b/substrate/frame/broker/Cargo.toml @@ -8,6 +8,9 @@ edition.workspace = true license = "Apache-2.0" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index d22f3936c3e2435665d869cd32edc0e042cc5f55..70f488e998cc8d2b06d26f2250ee8a585ab44add 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -31,7 +31,7 @@ use frame_support::{ use frame_system::{Pallet as System, RawOrigin}; use sp_arithmetic::{traits::Zero, Perbill}; use sp_core::Get; -use sp_runtime::Saturating; +use sp_runtime::{traits::BlockNumberProvider, Saturating}; use sp_std::{vec, vec::Vec}; const SEED: u32 = 0; @@ -82,6 +82,10 @@ fn setup_leases(n: u32, task: u32, until: u32) { fn advance_to(b: u32) { while System::::block_number() < b.into() { System::::set_block_number(System::::block_number().saturating_add(1u32.into())); + + let block_number: u32 = System::::block_number().try_into().ok().unwrap(); + + RCBlockNumberProviderOf::::set_block_number(block_number.into()); Broker::::on_initialize(System::::block_number()); } } @@ -182,7 +186,8 @@ mod benches { #[benchmark] fn start_sales(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> { - Configuration::::put(new_config_record::()); + let config = new_config_record::(); + Configuration::::put(config.clone()); // Assume Reservations to be filled for worst case setup_reservations::(T::MaxReservedCores::get()); @@ -190,6 +195,8 @@ mod benches { // Assume Leases to be filled for worst case setup_leases::(T::MaxLeasedCores::get(), 1, 10); + let latest_region_begin = Broker::::latest_timeslice_ready_to_commit(&config); + let initial_price = 10u32.into(); let origin = @@ -205,8 +212,8 @@ mod benches { leadin_length: 1u32.into(), start_price: 20u32.into(), regular_price: 10u32.into(), - region_begin: 4, - region_end: 7, + region_begin: latest_region_begin + config.region_length, + region_end: latest_region_begin + config.region_length * 2, ideal_cores_sold: 0, cores_offered: n .saturating_sub(T::MaxReservedCores::get()) @@ -239,7 +246,11 @@ mod benches { assert_last_event::( Event::Purchased { who: caller, - region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + region_id: RegionId { + begin: SaleInfo::::get().unwrap().region_begin, + core, + mask: CoreMask::complete(), + }, price: 10u32.into(), duration: 3u32.into(), } @@ -252,6 +263,7 @@ mod benches { #[benchmark] fn renew() -> Result<(), BenchmarkError> { setup_and_start_sale::()?; + let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); @@ -267,12 +279,12 @@ mod benches { Broker::::do_assign(region, None, 1001, Final) .map_err(|_| BenchmarkError::Weightless)?; - advance_to::(6); + advance_to::((T::TimeslicePeriod::get() * region_len.into()).try_into().ok().unwrap()); #[extrinsic_call] _(RawOrigin::Signed(caller), region.core); - let id = AllowedRenewalId { core: region.core, when: 10 }; + let id = AllowedRenewalId { core: region.core, when: region.begin + region_len * 2 }; assert!(AllowedRenewals::::get(id).is_some()); Ok(()) @@ -331,10 +343,10 @@ mod benches { assert_last_event::( Event::Partitioned { - old_region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + old_region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, new_region_ids: ( - RegionId { begin: 4, core, mask: CoreMask::complete() }, - RegionId { begin: 6, core, mask: CoreMask::complete() }, + RegionId { begin: region.begin, core, mask: CoreMask::complete() }, + RegionId { begin: region.begin + 2, core, mask: CoreMask::complete() }, ), } .into(), @@ -363,11 +375,11 @@ mod benches { assert_last_event::( Event::Interlaced { - old_region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + old_region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, new_region_ids: ( - RegionId { begin: 4, core, mask: 0x00000_fffff_fffff_00000.into() }, + RegionId { begin: region.begin, core, mask: 0x00000_fffff_fffff_00000.into() }, RegionId { - begin: 4, + begin: region.begin, core, mask: CoreMask::complete() ^ 0x00000_fffff_fffff_00000.into(), }, @@ -404,7 +416,7 @@ mod benches { assert_last_event::( Event::Assigned { - region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, task: 1000, duration: 3u32.into(), } @@ -439,7 +451,7 @@ mod benches { assert_last_event::( Event::Pooled { - region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, duration: 3u32.into(), } .into(), @@ -494,7 +506,11 @@ mod benches { who: recipient, amount: 200u32.into(), next: if m < new_config_record::().region_length { - Some(RegionId { begin: 4.saturating_add(m), core, mask: CoreMask::complete() }) + Some(RegionId { + begin: region.begin.saturating_add(m), + core, + mask: CoreMask::complete(), + }) } else { None }, @@ -541,6 +557,7 @@ mod benches { #[benchmark] fn drop_region() -> Result<(), BenchmarkError> { let core = setup_and_start_sale::()?; + let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); @@ -553,14 +570,16 @@ mod benches { let region = Broker::::do_purchase(caller.clone(), 10u32.into()) .map_err(|_| BenchmarkError::Weightless)?; - advance_to::(12); + advance_to::( + (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(), + ); #[extrinsic_call] _(RawOrigin::Signed(caller), region); assert_last_event::( Event::RegionDropped { - region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, duration: 3u32.into(), } .into(), @@ -572,6 +591,7 @@ mod benches { #[benchmark] fn drop_contribution() -> Result<(), BenchmarkError> { let core = setup_and_start_sale::()?; + let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); @@ -589,14 +609,16 @@ mod benches { Broker::::do_pool(region, None, recipient, Final) .map_err(|_| BenchmarkError::Weightless)?; - advance_to::(26); + advance_to::( + (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(), + ); #[extrinsic_call] _(RawOrigin::Signed(caller), region); assert_last_event::( Event::ContributionDropped { - region_id: RegionId { begin: 4, core, mask: CoreMask::complete() }, + region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, } .into(), ); @@ -609,8 +631,11 @@ mod benches { setup_and_start_sale::()?; let when = 5u32.into(); let revenue = 10u32.into(); + let region_len = Configuration::::get().unwrap().region_length; - advance_to::(25); + advance_to::( + (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(), + ); let caller: T::AccountId = whitelisted_caller(); InstaPoolHistory::::insert( @@ -635,8 +660,11 @@ mod benches { fn drop_renewal() -> Result<(), BenchmarkError> { let core = setup_and_start_sale::()?; let when = 5u32.into(); + let region_len = Configuration::::get().unwrap().region_length; - advance_to::(10); + advance_to::( + (T::TimeslicePeriod::get() * (region_len * 3).into()).try_into().ok().unwrap(), + ); let id = AllowedRenewalId { core, when }; let record = AllowedRenewalRecord { @@ -677,7 +705,7 @@ mod benches { let core_count = n.try_into().unwrap(); - ::ensure_notify_core_count(core_count); + CoreCountInbox::::put(core_count); let mut status = Status::::get().ok_or(BenchmarkError::Weightless)?; @@ -704,10 +732,16 @@ mod benches { ); T::Currency::set_balance(&Broker::::account_id(), T::Currency::minimum_balance()); - ::ensure_notify_revenue_info(10u32.into(), 10u32.into()); + let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); + let multiplicator = 5; + ::ensure_notify_revenue_info( + (timeslice_period * multiplicator).into(), + 10u32.into(), + ); + let timeslice = multiplicator - 1; InstaPoolHistory::::insert( - 4u32, + timeslice, InstaPoolHistoryRecord { private_contributions: 1u32.into(), system_contributions: 9u32.into(), @@ -722,7 +756,7 @@ mod benches { assert_last_event::( Event::ClaimsReady { - when: 4u32.into(), + when: timeslice.into(), system_payout: 9u32.into(), private_payout: 1u32.into(), } @@ -769,7 +803,7 @@ mod benches { #[block] { - Broker::::rotate_sale(sale, &config, &status); + Broker::::rotate_sale(sale.clone(), &config, &status); } assert!(SaleInfo::::get().is_some()); @@ -779,8 +813,8 @@ mod benches { leadin_length: 1u32.into(), start_price: 20u32.into(), regular_price: 10u32.into(), - region_begin: 4, - region_end: 7, + region_begin: sale.region_begin + config.region_length, + region_end: sale.region_end + config.region_length, ideal_cores_sold: 0, cores_offered: n .saturating_sub(T::MaxReservedCores::get()) @@ -851,6 +885,17 @@ mod benches { T::Coretime::request_revenue_info_at(rc_block); } } + #[benchmark] + fn notify_core_count() -> Result<(), BenchmarkError> { + let admin_origin = + T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(admin_origin as T::RuntimeOrigin, 100); + + assert!(CoreCountInbox::::take().is_some()); + Ok(()) + } #[benchmark] fn do_tick_base() -> Result<(), BenchmarkError> { diff --git a/substrate/frame/broker/src/coretime_interface.rs b/substrate/frame/broker/src/coretime_interface.rs index fec40b9fdd7b3d0e18216a2f634040b7bffc9279..9e853e8f3fe0b1e2f76e3fac57b9ce0b8d03ab07 100644 --- a/substrate/frame/broker/src/coretime_interface.rs +++ b/substrate/frame/broker/src/coretime_interface.rs @@ -22,7 +22,8 @@ use frame_support::Parameter; use scale_info::TypeInfo; use sp_arithmetic::traits::AtLeast32BitUnsigned; use sp_core::RuntimeDebug; -use sp_std::{fmt::Debug, vec::Vec}; +use sp_runtime::traits::BlockNumberProvider; +use sp_std::vec::Vec; /// Index of a Polkadot Core. pub type CoreIndex = u16; @@ -46,6 +47,12 @@ pub enum CoreAssignment { Task(TaskId), } +/// Relay chain block number of `T` that implements [`CoretimeInterface`]. +pub type RCBlockNumberOf = as BlockNumberProvider>::BlockNumber; + +/// Relay chain block number provider of `T` that implements [`CoretimeInterface`]. +pub type RCBlockNumberProviderOf = ::RealyChainBlockNumberProvider; + /// 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. /// @@ -57,17 +64,8 @@ pub trait CoretimeInterface { /// A (Relay-chain-side) balance. type Balance: AtLeast32BitUnsigned; - /// A (Relay-chain-side) block number. - type BlockNumber: AtLeast32BitUnsigned - + Copy - + TypeInfo - + Encode - + Decode - + MaxEncodedLen - + Debug; - - /// Return the latest block number on the Relay-chain. - fn latest() -> Self::BlockNumber; + /// A provider for the relay chain block number. + type RealyChainBlockNumberProvider: 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. @@ -81,7 +79,7 @@ pub trait CoretimeInterface { /// should be understood on a channel outside of this proposal. In the case that the request /// cannot be serviced because `when` is too old a block then a `notify_revenue` message must /// still be returned, but its `revenue` field may be `None`. - fn request_revenue_info_at(when: Self::BlockNumber); + fn request_revenue_info_at(when: RCBlockNumberOf); /// Instructs the Relay-chain to add the `amount` of DOT to the Instantaneous Coretime Market /// Credit account of `who`. @@ -104,16 +102,11 @@ pub trait CoretimeInterface { /// remain unchanged regardless of the `end_hint` value. fn assign_core( core: CoreIndex, - begin: Self::BlockNumber, + begin: RCBlockNumberOf, assignment: Vec<(CoreAssignment, PartsOf57600)>, - end_hint: Option, + end_hint: Option>, ); - /// Indicate that from this block onwards, the range of acceptable values of the `core` - /// parameter of `assign_core` message is `[0, count)`. `assign_core` will be a no-op if - /// provided with a value for `core` outside of this range. - fn check_notify_core_count() -> Option; - /// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain /// block number `last_until` to `until`, not including `until` itself. `last_until` is defined /// as being the `until` argument of the last `notify_revenue` message sent, or zero for the @@ -123,46 +116,33 @@ pub trait CoretimeInterface { /// This explicitly disregards the possibility of multiple parachains requesting and being /// notified of revenue information. The Relay-chain must be configured to ensure that only a /// single revenue information destination exists. - fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)>; - - /// Ensure that core count is updated to the provided value. - /// - /// This is only used for benchmarking. - #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_core_count(count: u16); + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)>; /// Ensure that revenue information is updated to the provided value. /// /// This is only used for benchmarking. #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance); + fn ensure_notify_revenue_info(when: RCBlockNumberOf, revenue: Self::Balance); } impl CoretimeInterface for () { type AccountId = (); type Balance = u64; - type BlockNumber = u32; - fn latest() -> Self::BlockNumber { - 0 - } + type RealyChainBlockNumberProvider = (); + fn request_core_count(_count: CoreIndex) {} - fn request_revenue_info_at(_when: Self::BlockNumber) {} + fn request_revenue_info_at(_when: RCBlockNumberOf) {} fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {} fn assign_core( _core: CoreIndex, - _begin: Self::BlockNumber, + _begin: RCBlockNumberOf, _assignment: Vec<(CoreAssignment, PartsOf57600)>, - _end_hint: Option, + _end_hint: Option>, ) { } - fn check_notify_core_count() -> Option { + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)> { None } - fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> { - None - } - #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_core_count(_count: u16) {} #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_revenue_info(_when: Self::BlockNumber, _revenue: Self::Balance) {} + fn ensure_notify_revenue_info(_when: RCBlockNumberOf, _revenue: Self::Balance) {} } diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs index 0b08a7b665b75ac1c860774762878012bf82a749..f2451013251fba144270e4009bae8cb180653b2d 100644 --- a/substrate/frame/broker/src/dispatchable_impls.rs +++ b/substrate/frame/broker/src/dispatchable_impls.rs @@ -37,6 +37,11 @@ impl Pallet { Ok(()) } + pub(crate) fn do_notify_core_count(core_count: CoreIndex) -> DispatchResult { + CoreCountInbox::::put(core_count); + Ok(()) + } + pub(crate) fn do_reserve(workload: Schedule) -> DispatchResult { let mut r = Reservations::::get(); let index = r.len() as u32; @@ -229,6 +234,9 @@ impl Pallet { ensure!(!pivot.is_void(), Error::::VoidPivot); ensure!(pivot != region_id.mask, Error::::CompletePivot); + // The old region should be removed. + Regions::::remove(®ion_id); + let one = RegionId { mask: pivot, ..region_id }; Regions::::insert(&one, ®ion); let other = RegionId { mask: region_id.mask ^ pivot, ..region_id }; diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs index 4abd041f5f397c977ca06b8dbd6c294155b09b4f..a669463aa02d97e40c4221107bf15d1d3d3525bb 100644 --- a/substrate/frame/broker/src/lib.rs +++ b/substrate/frame/broker/src/lib.rs @@ -42,9 +42,7 @@ pub use weights::WeightInfo; pub use adapt_price::*; pub use core_mask::*; pub use coretime_interface::*; -pub use nonfungible_impl::*; pub use types::*; -pub use utility_impls::*; #[frame_support::pallet] pub mod pallet { @@ -161,6 +159,10 @@ pub mod pallet { pub type InstaPoolHistory = StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf>; + /// Received core count change from the relay chain. + #[pallet::storage] + pub type CoreCountInbox = StorageValue<_, CoreIndex, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -625,7 +627,7 @@ pub mod pallet { /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`. /// - `region_id`: The Region which should become two interlaced Regions of incomplete /// regularity. - /// - `pivot`: The interlace mask of on of the two new regions (the other it its partial + /// - `pivot`: The interlace mask of one of the two new regions (the other is its partial /// complement). #[pallet::call_index(9)] pub fn interlace( @@ -716,55 +718,51 @@ pub mod pallet { /// Drop an expired Region from the chain. /// - /// - `origin`: Must be a Signed origin. + /// - `origin`: Can be any kind of origin. /// - `region_id`: The Region which has expired. #[pallet::call_index(14)] pub fn drop_region( - origin: OriginFor, + _origin: OriginFor, region_id: RegionId, ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; Self::do_drop_region(region_id)?; Ok(Pays::No.into()) } /// Drop an expired Instantaneous Pool Contribution record from the chain. /// - /// - `origin`: Must be a Signed origin. + /// - `origin`: Can be any kind of origin. /// - `region_id`: The Region identifying the Pool Contribution which has expired. #[pallet::call_index(15)] pub fn drop_contribution( - origin: OriginFor, + _origin: OriginFor, region_id: RegionId, ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; Self::do_drop_contribution(region_id)?; Ok(Pays::No.into()) } /// Drop an expired Instantaneous Pool History record from the chain. /// - /// - `origin`: Must be a Signed origin. + /// - `origin`: Can be any kind of origin. /// - `region_id`: The time of the Pool History record which has expired. #[pallet::call_index(16)] - pub fn drop_history(origin: OriginFor, when: Timeslice) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; + pub fn drop_history(_origin: OriginFor, when: Timeslice) -> DispatchResultWithPostInfo { Self::do_drop_history(when)?; Ok(Pays::No.into()) } /// Drop an expired Allowed Renewal record from the chain. /// - /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`. + /// - `origin`: Can be any kind of origin. /// - `core`: The core to which the expired renewal refers. /// - `when`: The timeslice to which the expired renewal refers. This must have passed. #[pallet::call_index(17)] pub fn drop_renewal( - origin: OriginFor, + _origin: OriginFor, core: CoreIndex, when: Timeslice, ) -> DispatchResultWithPostInfo { - let _ = ensure_signed(origin)?; Self::do_drop_renewal(core, when)?; Ok(Pays::No.into()) } @@ -780,5 +778,13 @@ pub mod pallet { Self::do_request_core_count(core_count)?; Ok(()) } + + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::notify_core_count())] + pub fn notify_core_count(origin: OriginFor, core_count: CoreIndex) -> DispatchResult { + T::AdminOrigin::ensure_origin_or_root(origin)?; + Self::do_notify_core_count(core_count)?; + Ok(()) + } } } diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index 8b8cfa55abce9b736660b73d974f027c390c560c..19c72340353c66d9bedf5d016e02dad7102116cb 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -29,9 +29,9 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_arithmetic::Perbill; -use sp_core::{ConstU16, ConstU32, ConstU64, H256}; +use sp_core::{ConstU32, ConstU64}; use sp_runtime::{ - traits::{BlakeTwo256, Identity, IdentityLookup}, + traits::{BlockNumberProvider, Identity}, BuildStorage, Saturating, }; use sp_std::collections::btree_map::BTreeMap; @@ -49,29 +49,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 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 = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } #[derive(Debug, Clone, Eq, PartialEq)] @@ -92,7 +70,6 @@ parameter_types! { pub static CoretimeWorkplan: BTreeMap<(u32, CoreIndex), Vec<(CoreAssignment, PartsOf57600)>> = Default::default(); pub static CoretimeUsage: BTreeMap> = Default::default(); pub static CoretimeInPool: CoreMaskBitCount = 0; - pub static NotifyCoreCount: Vec = Default::default(); pub static NotifyRevenueInfo: Vec<(u32, u64)> = Default::default(); } @@ -100,18 +77,20 @@ pub struct TestCoretimeProvider; impl CoretimeInterface for TestCoretimeProvider { type AccountId = u64; type Balance = u64; - type BlockNumber = u32; - fn latest() -> Self::BlockNumber { - System::block_number() as u32 - } + type RealyChainBlockNumberProvider = System; fn request_core_count(count: CoreIndex) { - NotifyCoreCount::mutate(|s| s.insert(0, count)); + CoreCountInbox::::put(count); } - fn request_revenue_info_at(when: Self::BlockNumber) { - if when > Self::latest() { - panic!("Asking for revenue info in the future {:?} {:?}", when, Self::latest()); + fn request_revenue_info_at(when: RCBlockNumberOf) { + if when > RCBlockNumberProviderOf::::current_block_number() { + panic!( + "Asking for revenue info in the future {:?} {:?}", + when, + RCBlockNumberProviderOf::::current_block_number() + ); } + let when = when as u32; let mut total = 0; CoretimeSpending::mutate(|s| { s.retain(|(n, a)| { @@ -130,27 +109,28 @@ impl CoretimeInterface for TestCoretimeProvider { } fn assign_core( core: CoreIndex, - begin: Self::BlockNumber, + begin: RCBlockNumberOf, assignment: Vec<(CoreAssignment, PartsOf57600)>, - end_hint: Option, + end_hint: Option>, ) { - CoretimeWorkplan::mutate(|p| p.insert((begin, core), assignment.clone())); - let item = (Self::latest(), AssignCore { core, begin, assignment, end_hint }); + CoretimeWorkplan::mutate(|p| p.insert((begin as u32, core), assignment.clone())); + let item = ( + RCBlockNumberProviderOf::::current_block_number() as u32, + AssignCore { + core, + begin: begin as u32, + assignment, + end_hint: end_hint.map(|v| v as u32), + }, + ); CoretimeTrace::mutate(|v| v.push(item)); } - fn check_notify_core_count() -> Option { - NotifyCoreCount::mutate(|s| s.pop()) - } - fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> { - NotifyRevenueInfo::mutate(|s| s.pop()) + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)> { + NotifyRevenueInfo::mutate(|s| s.pop()).map(|v| (v.0 as _, v.1)) } #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_core_count(count: u16) { - NotifyCoreCount::mutate(|s| s.insert(0, count)); - } - #[cfg(feature = "runtime-benchmarks")] - fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance) { - NotifyRevenueInfo::mutate(|s| s.push((when, revenue))); + fn ensure_notify_revenue_info(when: RCBlockNumberOf, revenue: Self::Balance) { + NotifyRevenueInfo::mutate(|s| s.push((when as u32, revenue))); } } impl TestCoretimeProvider { @@ -159,14 +139,16 @@ impl TestCoretimeProvider { ensure!(CoretimeInPool::get() > 0, ()); c.insert(who, c.get(&who).ok_or(())?.checked_sub(price).ok_or(())?); CoretimeCredit::set(c); - CoretimeSpending::mutate(|v| v.push((Self::latest(), price))); + CoretimeSpending::mutate(|v| { + v.push((RCBlockNumberProviderOf::::current_block_number() as u32, price)) + }); Ok(()) } pub fn bump() { let mut pool_size = CoretimeInPool::get(); let mut workplan = CoretimeWorkplan::get(); let mut usage = CoretimeUsage::get(); - let now = Self::latest(); + let now = RCBlockNumberProviderOf::::current_block_number() as u32; workplan.retain(|(when, core), assignment| { if *when <= now { if let Some(old_assignment) = usage.get(core) { @@ -209,7 +191,7 @@ impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = ItemOf, ()>, (), u64>; type OnRevenue = IntoZero; - type TimeslicePeriod = ConstU32<2>; + type TimeslicePeriod = ConstU64<2>; type MaxLeasedCores = ConstU32<5>; type MaxReservedCores = ConstU32<5>; type Coretime = TestCoretimeProvider; @@ -265,7 +247,7 @@ impl TestExt { } pub fn advance_notice(mut self, advance_notice: Timeslice) -> Self { - self.0.advance_notice = advance_notice; + self.0.advance_notice = advance_notice as u64; self } diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index e1b489bbe6e67801fcae1059eb09f85d72203365..6aa9ca84fc419c4ec94190196939a8bee7501d67 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -602,6 +602,43 @@ fn interlace_works() { }); } +#[test] +fn cant_assign_unowned_region() { + TestExt::new().endow(1, 1000).execute_with(|| { + assert_ok!(Broker::do_start_sales(100, 1)); + advance_to(2); + let region = Broker::do_purchase(1, u64::max_value()).unwrap(); + let (region1, region2) = + Broker::do_interlace(region, Some(1), CoreMask::from_chunk(0, 30)).unwrap(); + + // Transfer the interlaced region to account 2. + assert_ok!(Broker::do_transfer(region2, Some(1), 2)); + + // The initial owner should not be able to assign the non-interlaced region, since they have + // just transferred an interlaced part of it to account 2. + assert_noop!(Broker::do_assign(region, Some(1), 1001, Final), Error::::UnknownRegion); + + // Account 1 can assign only the interlaced region that they did not transfer. + assert_ok!(Broker::do_assign(region1, Some(1), 1001, Final)); + // Account 2 can assign the region they received. + assert_ok!(Broker::do_assign(region2, Some(2), 1002, Final)); + + advance_to(10); + assert_eq!( + CoretimeTrace::get(), + vec![( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(Task(1001), 21600), (Task(1002), 36000)], + end_hint: None + } + ),] + ); + }); +} + #[test] fn interlace_then_partition_works() { TestExt::new().endow(1, 1000).execute_with(|| { diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 7df8bd39d42fe13e6cf7375442d7ddab98c60d99..8b7860c8e3af6daaaa433d9b7d093c94f292a5a6 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -87,7 +87,7 @@ impl Pallet { } pub(crate) fn process_core_count(status: &mut StatusRecord) -> bool { - if let Some(core_count) = T::Coretime::check_notify_core_count() { + if let Some(core_count) = CoreCountInbox::::take() { status.core_count = core_count; Self::deposit_event(Event::::CoreCountChanged { core_count }); return true @@ -112,10 +112,16 @@ impl Pallet { } // Payout system InstaPool Cores. let total_contrib = r.system_contributions.saturating_add(r.private_contributions); - let system_payout = - revenue.saturating_mul(r.system_contributions.into()) / total_contrib.into(); - let _ = Self::charge(&Self::account_id(), system_payout); - revenue.saturating_reduce(system_payout); + let system_payout = if !total_contrib.is_zero() { + let system_payout = + revenue.saturating_mul(r.system_contributions.into()) / total_contrib.into(); + let _ = Self::charge(&Self::account_id(), system_payout); + revenue.saturating_reduce(system_payout); + + system_payout + } else { + Zero::zero() + }; if !revenue.is_zero() && r.private_contributions > 0 { r.maybe_payout = Some(revenue); diff --git a/substrate/frame/broker/src/types.rs b/substrate/frame/broker/src/types.rs index 89222ca8e95271098fe2d335142015024439a71a..7e9f351723a5d312fcd52dcf57d845ccea34a05a 100644 --- a/substrate/frame/broker/src/types.rs +++ b/substrate/frame/broker/src/types.rs @@ -16,7 +16,8 @@ // limitations under the License. use crate::{ - Config, CoreAssignment, CoreIndex, CoreMask, CoretimeInterface, TaskId, CORE_MASK_BITS, + Config, CoreAssignment, CoreIndex, CoreMask, CoretimeInterface, RCBlockNumberOf, TaskId, + CORE_MASK_BITS, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::traits::fungible::Inspect; @@ -28,7 +29,7 @@ use sp_runtime::BoundedVec; pub type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; pub type RelayBalanceOf = <::Coretime as CoretimeInterface>::Balance; -pub type RelayBlockNumberOf = <::Coretime as CoretimeInterface>::BlockNumber; +pub type RelayBlockNumberOf = RCBlockNumberOf<::Coretime>; pub type RelayAccountIdOf = <::Coretime as CoretimeInterface>::AccountId; /// Relay-chain block number with a fixed divisor of Config::TimeslicePeriod. diff --git a/substrate/frame/broker/src/utility_impls.rs b/substrate/frame/broker/src/utility_impls.rs index 2450198050b67fccc852bd06e116bcdc16628750..3dba5be5b398b8f30156179e7dd1b203fff5e311 100644 --- a/substrate/frame/broker/src/utility_impls.rs +++ b/substrate/frame/broker/src/utility_impls.rs @@ -29,17 +29,17 @@ use sp_arithmetic::{ traits::{SaturatedConversion, Saturating}, FixedPointNumber, FixedU64, }; -use sp_runtime::traits::AccountIdConversion; +use sp_runtime::traits::{AccountIdConversion, BlockNumberProvider}; impl Pallet { pub fn current_timeslice() -> Timeslice { - let latest = T::Coretime::latest(); + let latest = RCBlockNumberProviderOf::::current_block_number(); let timeslice_period = T::TimeslicePeriod::get(); (latest / timeslice_period).saturated_into() } pub fn latest_timeslice_ready_to_commit(config: &ConfigRecordOf) -> Timeslice { - let latest = T::Coretime::latest(); + let latest = RCBlockNumberProviderOf::::current_block_number(); let advanced = latest.saturating_add(config.advance_notice); let timeslice_period = T::TimeslicePeriod::get(); (advanced / timeslice_period).saturated_into() diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs index b3a151c6062c45f43f9ae1ccafa513a07f70f08f..a8f50eeee6e6ceaf284c1764a68411956753cfff 100644 --- a/substrate/frame/broker/src/weights.rs +++ b/substrate/frame/broker/src/weights.rs @@ -74,6 +74,7 @@ pub trait WeightInfo { fn process_pool() -> Weight; fn process_core_schedule() -> Weight; fn request_revenue_info_at() -> Weight; + fn notify_core_count() -> Weight; fn do_tick_base() -> Weight; } @@ -447,6 +448,9 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 147_000 picoseconds. Weight::from_parts(184_000, 0) } + fn notify_core_count() -> Weight { + T::DbWeight::get().reads_writes(1, 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) @@ -835,6 +839,10 @@ impl WeightInfo for () { // Minimum execution time: 147_000 picoseconds. Weight::from_parts(184_000, 0) } + fn notify_core_count() -> Weight { + RocksDbWeight::get().reads(1) + .saturating_add(RocksDbWeight::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) diff --git a/substrate/frame/child-bounties/Cargo.toml b/substrate/frame/child-bounties/Cargo.toml index 8e9d9c172387f2b66515ffab590271972b819c02..6c1c362dc56fe769a67d61a9b6a559c9cb934206 100644 --- a/substrate/frame/child-bounties/Cargo.toml +++ b/substrate/frame/child-bounties/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage child bounties" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/collective/Cargo.toml b/substrate/frame/collective/Cargo.toml index 672468450c26a5a99ddd1290facdb7641fc15237..fb0bace740c5d9018835b431056ac8645f096e2a 100644 --- a/substrate/frame/collective/Cargo.toml +++ b/substrate/frame/collective/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 29c39c047a3b2b47c5f0b273772f26de930666ac..4c6ca41ed56bd9ae76d129a3d03fc90facf6f708 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -11,6 +11,9 @@ description = "FRAME pallet for WASM contracts" readme = "README.md" include = ["CHANGELOG.md", "README.md", "benchmarks/**", "build.rs", "src/**/*"] +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 2e95354ddc9bae52c1fd1afdddda845ead52f487..36bc3974f5edaa7f0dc01dc03bfca93a65f45d7e 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -7,6 +7,9 @@ edition.workspace = true license.workspace = true description = "Fixtures for testing contracts pallet." +[lints] +workspace = true + [dependencies] wat = "1" frame-system = { path = "../../system" } @@ -16,7 +19,10 @@ anyhow = "1.0.0" [build-dependencies] parity-wasm = "0.45.0" tempfile = "3.8.1" -toml = "0.8.8" +toml = "0.8.2" twox-hash = "1.6.3" +polkavm-linker = { version = "0.3.0", optional = true } anyhow = "1.0.0" -cfg-if = { version = "1.0", default-features = false } + +[features] +riscv = ["polkavm-linker"] diff --git a/substrate/frame/contracts/fixtures/build.rs b/substrate/frame/contracts/fixtures/build.rs index 49deb94a7faaa1431bf26f85a1e2dfec2fcb016a..125ee7f3722dc62bcd04aa5b64c26a81a879e544 100644 --- a/substrate/frame/contracts/fixtures/build.rs +++ b/substrate/frame/contracts/fixtures/build.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Compile contracts to wasm and RISC-V binaries. -use anyhow::Result; +use anyhow::{bail, Context, Result}; use parity_wasm::elements::{deserialize_file, serialize_to_file, Internal}; use std::{ env, fs, @@ -65,25 +65,51 @@ impl Entry { .expect("name is valid unicode; qed") } + /// Return whether the contract has already been compiled. + fn is_cached(&self, out_dir: &Path) -> bool { + out_dir.join(self.name()).join(&self.hash).exists() + } + + /// Update the cache file for the contract. + fn update_cache(&self, out_dir: &Path) -> Result<()> { + let cache_dir = out_dir.join(self.name()); + + // clear the cache dir if it exists + if cache_dir.exists() { + fs::remove_dir_all(&cache_dir)?; + } + + // re-populate the cache dir with the new hash + fs::create_dir_all(&cache_dir)?; + fs::write(out_dir.join(&self.hash), "")?; + Ok(()) + } + /// Return the name of the output wasm file. fn out_wasm_filename(&self) -> String { format!("{}.wasm", self.name()) } + + /// Return the name of the RISC-V polkavm file. + #[cfg(feature = "riscv")] + fn out_riscv_filename(&self) -> String { + format!("{}.polkavm", self.name()) + } } /// Collect all contract entries from the given source directory. /// Contracts that have already been compiled are filtered out. fn collect_entries(contracts_dir: &Path, out_dir: &Path) -> Vec { - fs::read_dir(&contracts_dir) + fs::read_dir(contracts_dir) .expect("src dir exists; qed") .filter_map(|file| { let path = file.expect("file exists; qed").path(); if path.extension().map_or(true, |ext| ext != "rs") { - return None; + return None } let entry = Entry::new(path); - if out_dir.join(&entry.hash).exists() { + if entry.is_cached(out_dir) { None } else { Some(entry) @@ -98,41 +124,29 @@ fn create_cargo_toml<'a>( entries: impl Iterator, output_dir: &Path, ) -> Result<()> { - let uapi_path = fixtures_dir.join("../uapi").canonicalize()?; - let common_path = fixtures_dir.join("./contracts/common").canonicalize()?; - let mut cargo_toml: toml::Value = toml::from_str(&format!( - " -[package] -name = 'contracts' -version = '0.1.0' -edition = '2021' - -# Binary targets are injected below. -[[bin]] - -[dependencies] -uapi = {{ package = 'pallet-contracts-uapi', default-features = false, path = {uapi_path:?}}} -common = {{ package = 'pallet-contracts-fixtures-common', path = {common_path:?}}} - -[profile.release] -opt-level = 3 -lto = true -codegen-units = 1 -" - ))?; - - let binaries = entries - .map(|entry| { - let name = entry.name(); - let path = entry.path(); - toml::Value::Table(toml::toml! { - name = name - path = path + let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + let mut set_dep = |name, path| -> Result<()> { + cargo_toml["dependencies"][name]["path"] = toml::Value::String( + fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(), + ); + Ok(()) + }; + set_dep("uapi", "../uapi")?; + set_dep("common", "./contracts/common")?; + + cargo_toml["bin"] = toml::Value::Array( + entries + .map(|entry| { + let name = entry.name(); + let path = entry.path(); + toml::Value::Table(toml::toml! { + name = name + path = path + }) }) - }) - .collect::>(); + .collect::>(), + ); - cargo_toml["bin"] = toml::Value::Array(binaries); let cargo_toml = toml::to_string_pretty(&cargo_toml)?; fs::write(output_dir.join("Cargo.toml"), cargo_toml).map_err(Into::into) } @@ -145,7 +159,7 @@ fn invoke_cargo_fmt<'a>( ) -> Result<()> { // If rustfmt is not installed, skip the check. if !Command::new("rustup") - .args(&["run", "nightly", "rustfmt", "--version"]) + .args(["run", "nightly", "rustfmt", "--version"]) .output() .map_or(false, |o| o.status.success()) { @@ -153,7 +167,7 @@ fn invoke_cargo_fmt<'a>( } let fmt_res = Command::new("rustup") - .args(&["run", "nightly", "rustfmt", "--check", "--config-path"]) + .args(["run", "nightly", "rustfmt", "--check", "--config-path"]) .arg(config_path) .args(files) .output() @@ -176,8 +190,8 @@ fn invoke_cargo_fmt<'a>( anyhow::bail!("Fixtures files are not formatted") } -/// Invoke `cargo build` to compile the contracts. -fn invoke_build(current_dir: &Path) -> Result<()> { +/// Build contracts for wasm. +fn invoke_wasm_build(current_dir: &Path) -> Result<()> { let encoded_rustflags = [ "-Clink-arg=-zstack-size=65536", "-Clink-arg=--import-memory", @@ -190,7 +204,7 @@ fn invoke_build(current_dir: &Path) -> Result<()> { let build_res = Command::new(env::var("CARGO")?) .current_dir(current_dir) .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) - .args(&["build", "--release", "--target=wasm32-unknown-unknown"]) + .args(["build", "--release", "--target=wasm32-unknown-unknown"]) .output() .expect("failed to execute process"); @@ -200,12 +214,13 @@ fn invoke_build(current_dir: &Path) -> Result<()> { let stderr = String::from_utf8_lossy(&build_res.stderr); eprintln!("{}", stderr); - anyhow::bail!("Failed to build contracts"); + bail!("Failed to build wasm contracts"); } /// Post-process the compiled wasm contracts. fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> { - let mut module = deserialize_file(input_path)?; + let mut module = + deserialize_file(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; if let Some(section) = module.export_section_mut() { section.entries_mut().retain(|entry| { matches!(entry.internal(), Internal::Function(_)) && @@ -216,6 +231,52 @@ fn post_process_wasm(input_path: &Path, output_path: &Path) -> Result<()> { serialize_to_file(output_path, module).map_err(Into::into) } +/// Build contracts for RISC-V. +#[cfg(feature = "riscv")] +fn invoke_riscv_build(current_dir: &Path) -> Result<()> { + let encoded_rustflags = + ["-Crelocation-model=pie", "-Clink-arg=--emit-relocs", "-Clink-arg=-Tmemory.ld"] + .join("\x1f"); + + fs::write(current_dir.join("memory.ld"), include_bytes!("./build/riscv_memory_layout.ld"))?; + + let build_res = Command::new(env::var("CARGO")?) + .current_dir(current_dir) + .env_clear() + .env("PATH", env::var("PATH").unwrap_or_default()) + .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) + .env("RUSTUP_TOOLCHAIN", "rve-nightly") + .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) + .args(["build", "--release", "--target=riscv32em-unknown-none-elf"]) + .output() + .expect("failed to execute process"); + + if build_res.status.success() { + return Ok(()) + } + + let stderr = String::from_utf8_lossy(&build_res.stderr); + + if stderr.contains("'rve-nightly' is not installed") { + eprintln!("RISC-V toolchain is not installed.\nDownload and install toolchain from https://github.com/paritytech/rustc-rv32e-toolchain."); + eprintln!("{}", stderr); + } else { + eprintln!("{}", stderr); + } + + bail!("Failed to build contracts"); +} +/// Post-process the compiled wasm contracts. +#[cfg(feature = "riscv")] +fn post_process_riscv(input_path: &Path, output_path: &Path) -> Result<()> { + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); + let orig = fs::read(input_path).with_context(|| format!("Failed to read {:?}", input_path))?; + let linked = polkavm_linker::program_from_elf(config, orig.as_ref()) + .map_err(|err| anyhow::format_err!("Failed to link polkavm program: {}", err))?; + fs::write(output_path, linked.as_bytes()).map_err(Into::into) +} + /// Write the compiled contracts to the given output directory. fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result<()> { for entry in entries { @@ -224,7 +285,14 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result &build_dir.join("target/wasm32-unknown-unknown/release").join(&wasm_output), &out_dir.join(&wasm_output), )?; - fs::write(out_dir.join(&entry.hash), "")?; + + #[cfg(feature = "riscv")] + post_process_riscv( + &build_dir.join("target/riscv32em-unknown-none-elf/release").join(entry.name()), + &out_dir.join(entry.out_riscv_filename()), + )?; + + entry.update_cache(out_dir)?; } Ok(()) @@ -239,7 +307,7 @@ fn find_workspace_root(current_dir: &Path) -> Option { let cargo_toml_contents = std::fs::read_to_string(current_dir.join("Cargo.toml")).ok()?; if cargo_toml_contents.contains("[workspace]") { - return Some(current_dir); + return Some(current_dir) } } @@ -257,7 +325,7 @@ fn main() -> Result<()> { let entries = collect_entries(&contracts_dir, &out_dir); if entries.is_empty() { - return Ok(()); + return Ok(()) } let tmp_dir = tempfile::tempdir()?; @@ -270,8 +338,11 @@ fn main() -> Result<()> { &contracts_dir, )?; - invoke_build(tmp_dir_path)?; - write_output(tmp_dir_path, &out_dir, entries)?; + invoke_wasm_build(tmp_dir_path)?; + #[cfg(feature = "riscv")] + invoke_riscv_build(tmp_dir_path)?; + + write_output(tmp_dir_path, &out_dir, entries)?; Ok(()) } diff --git a/substrate/frame/contracts/fixtures/build/Cargo.toml b/substrate/frame/contracts/fixtures/build/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..7fd42b887e7239ea7359f3df88e517998f733e45 --- /dev/null +++ b/substrate/frame/contracts/fixtures/build/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "contracts" +version = "0.1.0" +edition = "2021" + +# Binary targets are injected dynamically by the build script. +[[bin]] + +# local path are injected dynamically by the build script. +[dependencies] +uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } +common = { package = 'pallet-contracts-fixtures-common', path = "" } +polkavm-derive = '0.2.0' + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 diff --git a/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld b/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld new file mode 100644 index 0000000000000000000000000000000000000000..89084263adaabca201a2d56e78d03981acd5fa0f --- /dev/null +++ b/substrate/frame/contracts/fixtures/build/riscv_memory_layout.ld @@ -0,0 +1,4 @@ +SECTIONS { + .text : { KEEP(*(.text.polkavm_export)) } +} + diff --git a/substrate/frame/contracts/fixtures/contracts/call.rs b/substrate/frame/contracts/fixtures/contracts/call.rs index 396b71d5e96951f7f1dfdea2a0d9190c6861bfe6..0e5f4fbd2ddd19d6143a0cb037bd70e9e5cdd7cd 100644 --- a/substrate/frame/contracts/fixtures/contracts/call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call.rs @@ -23,9 +23,11 @@ extern crate common; use uapi::{CallFlags, 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() { let mut buffer = [0u8; 40]; let callee_input = 0..4; diff --git a/substrate/frame/contracts/fixtures/contracts/dummy.rs b/substrate/frame/contracts/fixtures/contracts/dummy.rs index 98b9d494bbc67a277a6813bcb4ad06b5b5e5aee3..bde0d15e2f66e9cb96d2d07a4dbf214831e61001 100644 --- a/substrate/frame/contracts/fixtures/contracts/dummy.rs +++ b/substrate/frame/contracts/fixtures/contracts/dummy.rs @@ -20,7 +20,9 @@ extern crate common; #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn deploy() {} #[no_mangle] +#[polkavm_derive::polkavm_export] pub extern "C" fn call() {} diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index 9c6231a783a4308e6bc60d82a3383c5b73999629..7b570eed155c1b01b911ce82ca73291b9eab7dff 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "A mock network for testing pallet-contracts" +[lints] +workspace = true + [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 2ef579276aeca7e616553307b55d05cdb1f70cab..a79b7e4e2d6db7bce5d61e2a6b9d765bec16b2a7 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -37,12 +37,13 @@ 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, CurrencyAdapter as XcmCurrencyAdapter, EnsureXcmOrigin, - FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, WithComputedOrigin, + ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FungiblesAdapter, + IsConcrete, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -183,6 +184,7 @@ 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, ()>; diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index 17e36eada2598a0c5587f4cf1c998e2efffbe082..136cc2e3ed671b9d72830da955bee8701739c555 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -29,12 +29,14 @@ 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, CurrencyAdapter as XcmCurrencyAdapter, DescribeAllTerminal, - DescribeFamily, FixedRateOfFungible, FixedWeightBounds, HashedDescription, IsConcrete, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, + ChildSystemParachainAsSuperuser, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible, + FixedWeightBounds, HashedDescription, IsConcrete, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{Config, XcmExecutor}; @@ -116,6 +118,7 @@ pub type SovereignAccountOf = ( ChildParachainConvertsVia, ); +#[allow(deprecated)] pub type LocalBalancesTransactor = XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; diff --git a/substrate/frame/contracts/primitives/Cargo.toml b/substrate/frame/contracts/primitives/Cargo.toml deleted file mode 100644 index f821797c923cc6cf5a9b88410977e0a2725bd6cf..0000000000000000000000000000000000000000 --- a/substrate/frame/contracts/primitives/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "pallet-contracts-primitives" -version = "24.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -homepage = "https://substrate.io" -repository.workspace = true -description = "A crate that hosts a common definitions that are relevant for the pallet-contracts." -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -bitflags = "1.0" -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"] } - -# Substrate Dependencies (This crate should not rely on frame) -sp-std = { path = "../../../primitives/std", default-features = false } -sp-runtime = { path = "../../../primitives/runtime", default-features = false } -sp-weights = { path = "../../../primitives/weights", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", - "sp-weights/std", -] diff --git a/substrate/frame/contracts/proc-macro/Cargo.toml b/substrate/frame/contracts/proc-macro/Cargo.toml index 8aa6e6679cde656b6d9b4ef5db73117331e9bc0f..1c9802f6fde073941604e4173e2d298dbb440551 100644 --- a/substrate/frame/contracts/proc-macro/Cargo.toml +++ b/substrate/frame/contracts/proc-macro/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Procedural macros used in pallet_contracts" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,7 +20,7 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.39", features = ["full"] } +syn = { version = "2.0.48", features = ["full"] } [dev-dependencies] diff --git a/substrate/frame/contracts/proc-macro/src/lib.rs b/substrate/frame/contracts/proc-macro/src/lib.rs index 4ef02497b8ee257bed9a1c65e5c3d2cf98b5f52a..9dc34d5223b27307587bbcbf797846c7e6844fd0 100644 --- a/substrate/frame/contracts/proc-macro/src/lib.rs +++ b/substrate/frame/contracts/proc-macro/src/lib.rs @@ -74,7 +74,7 @@ fn derive_debug(input: TokenStream, fmt: impl Fn(&Ident) -> TokenStream2) -> Tok #[cfg(not(feature = "full"))] let fields = { drop(fmt); - drop(data); + let _ = data; TokenStream2::new() }; diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index c532b354ab64003e76aa30d7958550a1a3d99cfd..3ab76d6112a27e4fbe5a377549ee7e3830ad5d3c 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -1749,7 +1749,7 @@ benchmarks! { .collect::>>(); let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); let deposits_len = deposits_bytes.len() as u32; - let deposit_len = value_len.clone(); + let deposit_len = value_len; let callee_offset = value_len + deposits_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), @@ -2246,13 +2246,12 @@ benchmarks! { let message_len = message.len() as i32; let key_type = sp_core::crypto::KeyTypeId(*b"code"); let sig_params = (0..r) - .map(|i| { + .flat_map(|i| { let pub_key = sp_io::crypto::sr25519_generate(key_type, None); let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature"); let data: [u8; 96] = [AsRef::<[u8]>::as_ref(&sig), AsRef::<[u8]>::as_ref(&pub_key)].concat().try_into().unwrap(); data }) - .flatten() .collect::>(); let sig_params_len = sig_params.len() as i32; diff --git a/substrate/frame/contracts/src/migration/v10.rs b/substrate/frame/contracts/src/migration/v10.rs index f02e28f6fde325b26a041c683e03671a8f876897..22fad38739e751c9b68dfb3151751e08331f5218 100644 --- a/substrate/frame/contracts/src/migration/v10.rs +++ b/substrate/frame/contracts/src/migration/v10.rs @@ -219,7 +219,7 @@ where "Failed to transfer the base deposit, reason: {:?}", err ); - OldCurrency::deposit_creating(&deposit_account, min_balance); + let _ = OldCurrency::deposit_creating(&deposit_account, min_balance); min_balance }); diff --git a/substrate/frame/contracts/src/migration/v12.rs b/substrate/frame/contracts/src/migration/v12.rs index 4ddc57584b30eb74c0ed18f7da2989d3db2ff53c..7dee31503101ba753b0e807d11621be711d4b58b 100644 --- a/substrate/frame/contracts/src/migration/v12.rs +++ b/substrate/frame/contracts/src/migration/v12.rs @@ -317,7 +317,7 @@ where let (_, old_deposit, storage_module) = state; // CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1 // I.e. code info adds up 1 byte per record. - let info_bytes_added = items.clone(); + let info_bytes_added = items; // We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code. let storage_removed = storage_module.saturating_sub(info_bytes_added); // module+code+info - bytes diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index 448c0dd74960d20a61331fe0cbaa906490ca23a9..4650a9bc79a366e48f6064c90d0c19752ed6d9dd 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -25,13 +25,15 @@ mod runtime; pub use crate::wasm::runtime::api_doc; pub use crate::wasm::runtime::{ - AllowDeprecatedInterface, AllowUnstableInterface, Environment, ReturnErrorCode, Runtime, - RuntimeCosts, + AllowDeprecatedInterface, AllowUnstableInterface, Environment, Runtime, RuntimeCosts, }; -pub use pallet_contracts_uapi::ReturnFlags; + #[cfg(test)] pub use tests::MockExt; +#[cfg(test)] +pub use crate::wasm::runtime::ReturnErrorCode; + use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index df45872349d2da47b3ee38bd359b334c686ecc50..d3f6fc06269391fd06a2ef7e8ff9a901994a43be 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -8,6 +8,10 @@ homepage = "https://substrate.io" repository.workspace = true description = "Exposes all the host functions that a contract can import." +# [lints] +# TODO: uncomment when rve toolchain is updated to rust 1.74 or greater. +# workspace = true + [dependencies] paste = { version = "1.0", default-features = false } bitflags = "1.0" @@ -17,6 +21,9 @@ scale = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ], optional = true } +[target.'cfg(target_arch = "riscv32")'.dependencies] +polkavm-derive = '0.2.0' + [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 f8b55d3822e9fb0fd52e6fd7abaa313ddefa7b6c..8f76b21ec9f03308c79acd1456979f2f488f1617 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::{CallFlags, Result, ReturnFlags, SENTINEL}; +use crate::{CallFlags, Result, ReturnFlags}; use paste::paste; #[cfg(target_arch = "wasm32")] @@ -36,23 +36,27 @@ macro_rules! hash_fn { }; } +// TODO remove cfg once used by all targets +#[cfg(target_arch = "wasm32")] fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { debug_assert!(new_len <= output.len()); let tmp = core::mem::take(output); *output = &mut tmp[..new_len]; } +#[cfg(target_arch = "wasm32")] fn ptr_len_or_sentinel(data: &mut Option<&mut [u8]>) -> (*mut u8, u32) { match data { Some(ref mut data) => (data.as_mut_ptr(), data.len() as _), - None => (SENTINEL as _, 0), + None => (crate::SENTINEL as _, 0), } } +#[cfg(target_arch = "wasm32")] fn ptr_or_sentinel(data: &Option<&[u8]>) -> *const u8 { match data { Some(ref data) => data.as_ptr(), - None => SENTINEL as _, + None => crate::SENTINEL as _, } } @@ -201,12 +205,13 @@ pub trait HostFn { /// /// - `func_id`: The function id of the chain extension. /// - `input`: The input data buffer. - /// - `output`: A reference to the output data buffer to write the output data. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. /// /// # Return /// /// The chain extension returned value, if executed successfully. - fn call_chain_extension(func_id: u32, input: &[u8], output: &mut &mut [u8]) -> u32; + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut [u8]>) -> u32; /// Call some dispatchable of the runtime. /// @@ -324,6 +329,25 @@ pub trait HostFn { /// Returns the size of the pre-existing value at the specified key if any. fn contains_storage_v1(key: &[u8]) -> Option; + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 just drops the message with no trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + fn debug_message(str: &[u8]) -> Result; + /// Execute code in the context (storage, caller, value) of the current contract. /// /// Reentrancy protection is always disabled since the callee is allowed @@ -789,5 +813,5 @@ pub trait HostFn { #[deprecated( note = "Unstable function. Behaviour can change without further notice. Use only for testing." )] - fn xcm_send(dest: &[u8], msg: &[u8], output: &mut &mut [u8]) -> Result; + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; } diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index f58b8831f06d6cce1df4eb5ab3c7897a7fa97b40..4132768abc28d2796a08d9a45d22eeb6b3125031 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -1,3 +1,4 @@ +#![allow(unused_variables, unused_mut)] // Copyright (C) Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,3 +13,295 @@ // See the License for the specific language governing permissions and // limitations under the License. // TODO: bring up to date with wasm32.rs + +use super::{CallFlags, HostFn, HostFnImpl, Result}; +use crate::ReturnFlags; + +/// A macro to implement all Host functions with a signature of `fn(&mut &mut [u8])`. +/// +/// Example: +/// ```nocompile +// impl_wrapper_for! { +// () => [gas_left], +// (v1) => [gas_left], +// } +// ``` +// +// Expands to: +// ```nocompile +// fn gas_left(output: &mut &mut [u8]) { +// unsafe { sys::gas_left(...); } +// } +// fn gas_left_v1(output: &mut &mut [u8]) { +// unsafe { sys::v1::gas_left(...); } +// } +// ``` +macro_rules! impl_wrapper_for { + (@impl_fn $( $mod:ident )::*, $suffix_sep: literal, $suffix:tt, $name:ident) => { + paste::paste! { + fn [<$name $suffix_sep $suffix>](output: &mut &mut [u8]) { + todo!() + } + } + }; + + () => {}; + + (($mod:ident) => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys::$mod, "_", $mod, $name);)* + impl_wrapper_for!($($tail)*); + }; + + (() => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys, "", "", $name);)* + impl_wrapper_for!($($tail)*); + }; +} + +/// A macro to implement all the hash functions Apis. +macro_rules! impl_hash_fn { + ( $name:ident, $bytes_result:literal ) => { + paste::item! { + fn [](input: &[u8], output: &mut [u8; $bytes_result]) { + todo!() + } + } + }; +} + +/// A macro to implement the get_storage functions. +macro_rules! impl_get_storage { + ($fn_name:ident, $sys_get_storage:path) => { + fn $fn_name(key: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + }; +} + +impl HostFn for HostFnImpl { + fn instantiate_v1( + code_hash: &[u8], + gas: u64, + value: &[u8], + input: &[u8], + mut address: Option<&mut [u8]>, + mut output: Option<&mut [u8]>, + salt: &[u8], + ) -> Result { + todo!() + } + + fn instantiate_v2( + code_hash: &[u8], + ref_time_limit: u64, + proof_size_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input: &[u8], + mut address: Option<&mut [u8]>, + mut output: Option<&mut [u8]>, + salt: &[u8], + ) -> Result { + todo!() + } + + fn call( + callee: &[u8], + gas: u64, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn call_v1( + flags: CallFlags, + callee: &[u8], + gas: u64, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn call_v2( + flags: CallFlags, + callee: &[u8], + ref_time_limit: u64, + proof_time_limit: u64, + deposit: Option<&[u8]>, + value: &[u8], + input_data: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn caller_is_root() -> u32 { + todo!() + } + + fn delegate_call( + flags: CallFlags, + code_hash: &[u8], + input: &[u8], + mut output: Option<&mut [u8]>, + ) -> Result { + todo!() + } + + fn transfer(account_id: &[u8], value: &[u8]) -> Result { + todo!() + } + + fn deposit_event(topics: &[u8], data: &[u8]) { + todo!() + } + + fn set_storage(key: &[u8], value: &[u8]) { + todo!() + } + + fn set_storage_v1(key: &[u8], encoded_value: &[u8]) -> Option { + todo!() + } + + fn set_storage_v2(key: &[u8], encoded_value: &[u8]) -> Option { + todo!() + } + + fn clear_storage(key: &[u8]) { + todo!() + } + + fn clear_storage_v1(key: &[u8]) -> Option { + todo!() + } + + impl_get_storage!(get_storage, sys::get_storage); + impl_get_storage!(get_storage_v1, sys::v1::get_storage); + + fn take_storage(key: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + + fn contains_storage(key: &[u8]) -> Option { + todo!() + } + + fn contains_storage_v1(key: &[u8]) -> Option { + todo!() + } + + fn terminate(beneficiary: &[u8]) -> ! { + todo!() + } + + fn terminate_v1(beneficiary: &[u8]) -> ! { + todo!() + } + + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut [u8]>) -> u32 { + todo!() + } + + fn input(output: &mut &mut [u8]) { + todo!() + } + + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + todo!() + } + + fn call_runtime(call: &[u8]) -> Result { + todo!() + } + + fn debug_message(str: &[u8]) -> Result { + todo!() + } + + impl_wrapper_for! { + () => [caller, block_number, address, balance, gas_left, value_transferred, now, minimum_balance], + (v1) => [gas_left], + } + + fn weight_to_fee(gas: u64, output: &mut &mut [u8]) { + todo!() + } + + fn weight_to_fee_v1(ref_time_limit: u64, proof_size_limit: u64, output: &mut &mut [u8]) { + todo!() + } + + impl_hash_fn!(sha2_256, 32); + impl_hash_fn!(keccak_256, 32); + impl_hash_fn!(blake2_256, 32); + impl_hash_fn!(blake2_128, 16); + + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result { + todo!() + } + + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { + todo!() + } + + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { + todo!() + } + + fn is_contract(account_id: &[u8]) -> bool { + todo!() + } + + fn caller_is_origin() -> bool { + todo!() + } + + fn set_code_hash(code_hash: &[u8]) -> Result { + todo!() + } + + fn code_hash(account_id: &[u8], output: &mut [u8]) -> Result { + todo!() + } + + fn own_code_hash(output: &mut [u8]) { + todo!() + } + + fn account_reentrance_count(account: &[u8]) -> u32 { + todo!() + } + + fn add_delegate_dependency(code_hash: &[u8]) { + todo!() + } + + fn remove_delegate_dependency(code_hash: &[u8]) { + todo!() + } + + fn instantiation_nonce() -> u64 { + todo!() + } + + fn reentrance_count() -> u32 { + todo!() + } + + fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { + todo!() + } + + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { + todo!() + } +} diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index d30058daf3dff1775432d71f242357596d16021a..29fa1706a84220763d2a6c856f15063ad8be3fe6 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -69,6 +69,8 @@ mod sys { pub fn contains_storage(key_ptr: *const u8, key_len: u32) -> ReturnCode; + pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; + pub fn delegate_call( flags: u32, code_hash_ptr: *const u8, @@ -97,7 +99,6 @@ mod sys { pub fn get_storage( key_ptr: *const u8, - key_len: u32, out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; @@ -130,12 +131,7 @@ mod sys { pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; - pub fn set_storage( - key_ptr: *const u8, - key_len: u32, - value_ptr: *const u8, - value_len: u32, - ) -> ReturnCode; + pub fn set_storage(key_ptr: *const u8, value_ptr: *const u8, value_len: u32); pub fn sr25519_verify( signature_ptr: *const u8, @@ -219,7 +215,6 @@ mod sys { pub fn set_storage( key_ptr: *const u8, - key_len: u32, value_ptr: *const u8, value_len: u32, ) -> ReturnCode; @@ -280,29 +275,47 @@ mod sys { } /// A macro to implement all Host functions with a signature of `fn(&mut &mut [u8])`. +/// +/// Example: +/// ```nocompile +// impl_wrapper_for! { +// () => [gas_left], +// (v1) => [gas_left], +// } +// ``` +// +// Expands to: +// ```nocompile +// fn gas_left(output: &mut &mut [u8]) { +// unsafe { sys::gas_left(...); } +// } +// fn gas_left_v1(output: &mut &mut [u8]) { +// unsafe { sys::v1::gas_left(...); } +// } +// ``` macro_rules! impl_wrapper_for { - (@impl_fn $( $mod:ident )::*, $suffix:literal, $name:ident) => { - paste::paste! { - fn [<$name $suffix>](output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { - $( $mod )::*::$name(output.as_mut_ptr(), &mut output_len); - } - } - } - }; - - () => {}; - - (($mod:ident, $suffix:literal) => [$( $name:ident),*], $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn sys::$mod, $suffix, $name);)* - impl_wrapper_for!($($tail)*); - }; - - (() => [$( $name:ident),*], $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn sys, "", $name);)* - impl_wrapper_for!($($tail)*); - }; + (@impl_fn $( $mod:ident )::*, $suffix_sep: literal, $suffix:tt, $name:ident) => { + paste::paste! { + fn [<$name $suffix_sep $suffix>](output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { + $( $mod )::*::$name(output.as_mut_ptr(), &mut output_len); + } + } + } + }; + + () => {}; + + (($mod:ident) => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys::$mod, "_", $mod, $name);)* + impl_wrapper_for!($($tail)*); + }; + + (() => [$( $name:ident),*], $($tail:tt)*) => { + $(impl_wrapper_for!(@impl_fn sys, "", "", $name);)* + impl_wrapper_for!($($tail)*); + }; } /// A macro to implement all the hash functions Apis. @@ -322,27 +335,6 @@ macro_rules! impl_hash_fn { }; } -/// A macro to implement the get_storage functions. -macro_rules! impl_get_storage { - ($fn_name:ident, $sys_get_storage:path) => { - fn $fn_name(key: &[u8], output: &mut &mut [u8]) -> Result { - let mut output_len = output.len() as u32; - let ret_code = { - unsafe { - $sys_get_storage( - key.as_ptr(), - key.len() as u32, - output.as_mut_ptr(), - &mut output_len, - ) - } - }; - extract_from_slice(output, output_len as usize); - ret_code.into() - } - }; -} - impl HostFn for HostFnImpl { fn instantiate_v1( code_hash: &[u8], @@ -579,19 +571,12 @@ impl HostFn for HostFnImpl { } fn set_storage(key: &[u8], value: &[u8]) { - unsafe { - sys::set_storage(key.as_ptr(), key.len() as u32, value.as_ptr(), value.len() as u32) - }; + unsafe { sys::set_storage(key.as_ptr(), value.as_ptr(), value.len() as u32) }; } fn set_storage_v1(key: &[u8], encoded_value: &[u8]) -> Option { let ret_code = unsafe { - sys::v1::set_storage( - key.as_ptr(), - key.len() as u32, - encoded_value.as_ptr(), - encoded_value.len() as u32, - ) + sys::v1::set_storage(key.as_ptr(), encoded_value.as_ptr(), encoded_value.len() as u32) }; ret_code.into() } @@ -617,8 +602,29 @@ impl HostFn for HostFnImpl { ret_code.into() } - impl_get_storage!(get_storage, sys::get_storage); - impl_get_storage!(get_storage_v1, sys::v1::get_storage); + fn get_storage(key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = + { unsafe { sys::get_storage(key.as_ptr(), output.as_mut_ptr(), &mut output_len) } }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } + + fn get_storage_v1(key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::v1::get_storage( + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() + } fn take_storage(key: &[u8], output: &mut &mut [u8]) -> Result { let mut output_len = output.len() as u32; @@ -636,6 +642,11 @@ impl HostFn for HostFnImpl { ret_code.into() } + fn debug_message(str: &[u8]) -> Result { + let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; + ret_code.into() + } + fn contains_storage(key: &[u8]) -> Option { let ret_code = unsafe { sys::contains_storage(key.as_ptr(), key.len() as u32) }; ret_code.into() @@ -654,20 +665,23 @@ impl HostFn for HostFnImpl { unsafe { sys::v1::terminate(beneficiary.as_ptr()) } } - fn call_chain_extension(func_id: u32, input: &[u8], output: &mut &mut [u8]) -> u32 { - let mut output_len = output.len() as u32; + fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut [u8]>) -> u32 { + let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let ret_code = { unsafe { sys::call_chain_extension( func_id, input.as_ptr(), input.len() as u32, - output.as_mut_ptr(), + output_ptr, &mut output_len, ) } }; - extract_from_slice(output, output_len as usize); + + if let Some(ref mut output) = output { + extract_from_slice(output, output_len as usize); + } ret_code.into_u32() } @@ -690,7 +704,7 @@ impl HostFn for HostFnImpl { impl_wrapper_for! { () => [caller, block_number, address, balance, gas_left, value_transferred, now, minimum_balance], - (v1, "_v1") => [gas_left], + (v1) => [gas_left], } fn weight_to_fee(gas: u64, output: &mut &mut [u8]) { @@ -802,7 +816,7 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn xcm_send(dest: &[u8], msg: &[u8], output: &mut &mut [u8]) -> Result { + fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { sys::xcm_send(dest.as_ptr(), msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) }; diff --git a/substrate/frame/contracts/uapi/src/lib.rs b/substrate/frame/contracts/uapi/src/lib.rs index 3d384bbb85ddf8a48777d51058a8e3593f118232..99d77f504edaaa07c0464ed49f4c5df4d8d401c7 100644 --- a/substrate/frame/contracts/uapi/src/lib.rs +++ b/substrate/frame/contracts/uapi/src/lib.rs @@ -35,7 +35,7 @@ macro_rules! define_error_codes { )* ) => { /// Every error that can be returned to a contract when it calls any of the host functions. - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] #[repr(u32)] pub enum ReturnErrorCode { /// API call successful. @@ -49,7 +49,6 @@ macro_rules! define_error_codes { } impl From for Result { - #[inline] fn from(return_code: ReturnCode) -> Self { match return_code.0 { 0 => Ok(()), diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index 19eb6d09fc1292ebe1bb515f9d3fc05bfbeb9916..78f524e3e665b6b1c928bafcc95f9a86e44915cc 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for conviction voting in referenda" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,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.193", features = ["derive"], optional = true } +serde = { version = "1.0.195", features = ["derive"], optional = 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/core-fellowship/Cargo.toml b/substrate/frame/core-fellowship/Cargo.toml index c6f99cdaab26da5da7c1af4121cae2336b0913f1..d223ecd4f24c17b6afabe90fc962021ff58c87e0 100644 --- a/substrate/frame/core-fellowship/Cargo.toml +++ b/substrate/frame/core-fellowship/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Logic as per the description of The Fellowship for core Polkadot technology" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/core-fellowship/src/benchmarking.rs b/substrate/frame/core-fellowship/src/benchmarking.rs index ea0b5c6d4495f1968c2493b73641ca808278f89b..a3c410fac0a13ed6f41a5ce41b41d978136e7911 100644 --- a/substrate/frame/core-fellowship/src/benchmarking.rs +++ b/substrate/frame/core-fellowship/src/benchmarking.rs @@ -53,6 +53,19 @@ mod benchmarks { Ok(member) } + fn set_benchmark_params, I: 'static>() -> Result<(), BenchmarkError> { + let params = ParamsType { + active_salary: [100u32.into(); 9], + passive_salary: [10u32.into(); 9], + demotion_period: [100u32.into(); 9], + min_promotion_period: [100u32.into(); 9], + offboard_timeout: 1u32.into(), + }; + + CoreFellowship::::set_params(RawOrigin::Root.into(), Box::new(params))?; + Ok(()) + } + #[benchmark] fn set_params() -> Result<(), BenchmarkError> { let params = ParamsType { @@ -72,6 +85,8 @@ mod benchmarks { #[benchmark] fn bump_offboard() -> Result<(), BenchmarkError> { + set_benchmark_params::()?; + let member = make_member::(0)?; // Set it to the max value to ensure that any possible auto-demotion period has passed. @@ -89,6 +104,8 @@ mod benchmarks { #[benchmark] fn bump_demote() -> Result<(), BenchmarkError> { + set_benchmark_params::()?; + let member = make_member::(2)?; // Set it to the max value to ensure that any possible auto-demotion period has passed. diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs index 1aa53cf08d17499bedc2fd4e6d11f78240426123..2042f68e71498987230d629b8543fbbce4837c43 100644 --- a/substrate/frame/core-fellowship/src/lib.rs +++ b/substrate/frame/core-fellowship/src/lib.rs @@ -298,6 +298,11 @@ pub mod pallet { let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; params.demotion_period[rank_index] }; + + if demotion_period.is_zero() { + return Err(Error::::NothingDoing.into()) + } + let demotion_block = member.last_proof.saturating_add(demotion_period); // Ensure enough time has passed. diff --git a/substrate/frame/core-fellowship/src/tests.rs b/substrate/frame/core-fellowship/src/tests.rs index 9ac381ab7f5c8f8a7881c7b1f7ebc72c7b3d10cf..c9098f2171f401b80ecbb17c8c16c6e7f792c7b5 100644 --- a/substrate/frame/core-fellowship/src/tests.rs +++ b/substrate/frame/core-fellowship/src/tests.rs @@ -306,6 +306,28 @@ fn offboard_works() { }); } +#[test] +fn infinite_demotion_period_works() { + new_test_ext().execute_with(|| { + let params = ParamsType { + active_salary: [10; 9], + passive_salary: [10; 9], + min_promotion_period: [10; 9], + demotion_period: [0; 9], + offboard_timeout: 0, + }; + assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); + + set_rank(0, 0); + assert_ok!(CoreFellowship::import(signed(0))); + set_rank(1, 1); + assert_ok!(CoreFellowship::import(signed(1))); + + assert_noop!(CoreFellowship::bump(signed(0), 0), Error::::NothingDoing); + assert_noop!(CoreFellowship::bump(signed(0), 1), Error::::NothingDoing); + }); +} + #[test] fn proof_postpones_auto_demote() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 061eee106d447e3845bec24ea56c2e3c8cd23dce..cecb1df60d7f23b039db4ad06d85498bd021c473 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for democracy" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,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.193", features = ["derive"], optional = true } +serde = { version = "1.0.195", features = ["derive"], optional = 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/democracy/src/benchmarking.rs b/substrate/frame/democracy/src/benchmarking.rs index b4aa17726b8d28869110f44acbd6d21895da5218..aa66137ad880403a3a3934e121e358bc193d0826 100644 --- a/substrate/frame/democracy/src/benchmarking.rs +++ b/substrate/frame/democracy/src/benchmarking.rs @@ -65,7 +65,7 @@ fn add_referendum(n: u32) -> (ReferendumIndex, T::Hash, T::Hash) { 0u32.into(), ); let preimage_hash = note_preimage::(); - MetadataOf::::insert(crate::MetadataOwner::Referendum(index), preimage_hash.clone()); + MetadataOf::::insert(crate::MetadataOwner::Referendum(index), preimage_hash); (index, hash, preimage_hash) } diff --git a/substrate/frame/democracy/src/migrations/v1.rs b/substrate/frame/democracy/src/migrations/v1.rs index c27f437901b7ee1a7ce537d09eb3b3f313320292..64baea8f3af7039eb8a2422b48e897aaaa93a664 100644 --- a/substrate/frame/democracy/src/migrations/v1.rs +++ b/substrate/frame/democracy/src/migrations/v1.rs @@ -172,7 +172,7 @@ mod test { let hash = H256::repeat_byte(1); let status = ReferendumStatus { end: 1u32.into(), - proposal: hash.clone(), + proposal: hash, threshold: VoteThreshold::SuperMajorityApprove, delay: 1u32.into(), tally: Tally { ayes: 1u32.into(), nays: 1u32.into(), turnout: 1u32.into() }, @@ -187,13 +187,10 @@ mod test { // Case 3: Public proposals let hash2 = H256::repeat_byte(2); - v0::PublicProps::::put(vec![ - (3u32, hash.clone(), 123u64), - (4u32, hash2.clone(), 123u64), - ]); + v0::PublicProps::::put(vec![(3u32, hash, 123u64), (4u32, hash2, 123u64)]); // Case 4: Next external - v0::NextExternal::::put((hash.clone(), VoteThreshold::SuperMajorityApprove)); + v0::NextExternal::::put((hash, VoteThreshold::SuperMajorityApprove)); // Migrate. let state = v1::Migration::::pre_upgrade().unwrap(); diff --git a/substrate/frame/election-provider-multi-phase/Cargo.toml b/substrate/frame/election-provider-multi-phase/Cargo.toml index f1640f7cc70ffa8180b06dcd92b1089efc9598e6..be3a77065b433ee02af76879988b98b212c06546 100644 --- a/substrate/frame/election-provider-multi-phase/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "PALLET two phase election providers" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs index 05f9b24f8f9c53f833b5de3504645c40c1df6a6d..04325a40d0ada022b3a875e20df0d4143bbf0b44 100644 --- a/substrate/frame/election-provider-multi-phase/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/src/lib.rs @@ -101,9 +101,8 @@ //! unsigned transaction, thus the name _unsigned_ phase. This unsigned transaction can never be //! valid if propagated, and it acts similar to an inherent. //! -//! Validators will only submit solutions if the one that they have computed is sufficiently better -//! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the -//! weight of the solution to [`MinerConfig::MaxWeight`]. +//! Validators will only submit solutions if the one that they have computed is strictly better than +//! the best queued one and will limit the weight of the solution to [`MinerConfig::MaxWeight`]. //! //! The unsigned phase can be made passive depending on how the previous signed phase went, by //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always @@ -598,11 +597,6 @@ pub mod pallet { #[pallet::constant] type BetterSignedThreshold: Get; - /// The minimum amount of improvement to the solution score that defines a solution as - /// "better" in the Unsigned phase. - #[pallet::constant] - type BetterUnsignedThreshold: Get; - /// The repeat threshold of the offchain worker. /// /// For example, if it is 5, that means that at least 5 blocks will elapse between attempts @@ -1024,6 +1018,7 @@ pub mod pallet { // ensure solution is timely. ensure!(Self::current_phase().is_signed(), Error::::PreDispatchEarlySubmission); + ensure!(raw_solution.round == Self::round(), Error::::PreDispatchDifferentRound); // NOTE: this is the only case where having separate snapshot would have been better // because could do just decode_len. But we can create abstractions to do this. @@ -1197,6 +1192,8 @@ pub mod pallet { BoundNotMet, /// Submitted solution has too many winners TooManyWinners, + /// Sumission was prepared for a different round. + PreDispatchDifferentRound, } #[pallet::validate_unsigned] diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 7bdd329d9b041a6da270cca69fdd9bce90c122d4..e7dd8acf36b1d35732de17c0baa376a8e0b47619 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -21,7 +21,7 @@ use frame_election_provider_support::{ bounds::{DataProviderBounds, ElectionBounds}, data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; -pub use frame_support::{assert_noop, assert_ok, derive_impl, pallet_prelude::GetDefault}; +pub use frame_support::derive_impl; use frame_support::{ parameter_types, traits::{ConstU32, Hooks}, @@ -112,6 +112,15 @@ pub fn roll_to_with_ocw(n: BlockNumber) { } } +pub fn roll_to_round(n: u32) { + assert!(MultiPhase::round() <= n); + + while MultiPhase::round() != n { + roll_to_signed(); + frame_support::assert_ok!(MultiPhase::elect()); + } +} + pub struct TrimHelpers { pub voters: Vec>, pub assignments: Vec>, @@ -292,7 +301,6 @@ parameter_types! { pub static SignedMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerTxPriority: u64 = 100; pub static BetterSignedThreshold: Perbill = Perbill::zero(); - pub static BetterUnsignedThreshold: Perbill = Perbill::zero(); pub static OffchainRepeat: BlockNumber = 5; pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; @@ -390,7 +398,6 @@ impl crate::Config for Runtime { type EstimateCallFee = frame_support::traits::ConstU32<8>; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = BetterSignedThreshold; type OffchainRepeat = OffchainRepeat; type MinerTxPriority = MinerTxPriority; @@ -529,10 +536,7 @@ impl ExtBuilder { ::set(p); self } - pub fn better_unsigned_threshold(self, p: Perbill) -> Self { - ::set(p); - self - } + pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { ::set(signed); ::set(unsigned); @@ -634,9 +638,9 @@ impl ExtBuilder { #[cfg(feature = "try-runtime")] ext.execute_with(|| { - assert_ok!(>::try_state( - System::block_number() - )); + frame_support::assert_ok!( + >::try_state(System::block_number()) + ); }); } } diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs index 7e4b029ff8c80cff6a57c0868e335d11aa4a2cc6..ae830ed0382d8adfaf664e68548dfce6beddf9d0 100644 --- a/substrate/frame/election-provider-multi-phase/src/signed.rs +++ b/substrate/frame/election-provider-multi-phase/src/signed.rs @@ -571,6 +571,40 @@ mod tests { use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use sp_runtime::Percent; + #[test] + fn cannot_submit_on_different_round() { + ExtBuilder::default().build_and_execute(|| { + // roll to a few rounds ahead. + roll_to_round(5); + assert_eq!(MultiPhase::round(), 5); + + roll_to_signed(); + assert_eq!(MultiPhase::current_phase(), Phase::Signed); + + // create a temp snapshot only for this test. + MultiPhase::create_snapshot().unwrap(); + let mut solution = raw_solution(); + + // try a solution prepared in a previous round. + solution.round = MultiPhase::round() - 1; + + assert_noop!( + MultiPhase::submit(RuntimeOrigin::signed(10), Box::new(solution)), + Error::::PreDispatchDifferentRound, + ); + + // try a solution prepared in a later round (not expected to happen, but in any case). + MultiPhase::create_snapshot().unwrap(); + let mut solution = raw_solution(); + solution.round = MultiPhase::round() + 1; + + assert_noop!( + MultiPhase::submit(RuntimeOrigin::signed(10), Box::new(solution)), + Error::::PreDispatchDifferentRound, + ); + }) + } + #[test] fn cannot_submit_too_early() { ExtBuilder::default().build_and_execute(|| { diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs index e3d0ded97515b9cbac011956afebbc5d4f15e5d6..94348181334061a3c2de81b90742f53c4390ddc9 100644 --- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs +++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs @@ -384,9 +384,8 @@ impl Pallet { // ensure score is being improved. Panic henceforth. ensure!( - Self::queued_solution().map_or(true, |q: ReadySolution<_, _>| raw_solution - .score - .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), + Self::queued_solution() + .map_or(true, |q: ReadySolution<_, _>| raw_solution.score > q.score), Error::::PreDispatchWeakSubmission, ); @@ -1025,7 +1024,7 @@ mod tests { bounded_vec, offchain::storage_lock::{BlockAndTime, StorageLock}, traits::{Dispatchable, ValidateUnsigned, Zero}, - ModuleError, PerU16, Perbill, + ModuleError, PerU16, }; type Assignment = crate::unsigned::Assignment; @@ -1360,7 +1359,7 @@ mod tests { .desired_targets(1) .add_voter(7, 2, bounded_vec![10]) .add_voter(8, 5, bounded_vec![10]) - .better_unsigned_threshold(Perbill::from_percent(50)) + .add_voter(9, 1, bounded_vec![10]) .build_and_execute(|| { roll_to_unsigned(); assert!(MultiPhase::current_phase().is_unsigned()); @@ -1368,12 +1367,15 @@ mod tests { // an initial solution let result = ElectionResult { - // note: This second element of backing stake is not important here. - winners: vec![(10, 10)], - assignments: vec![Assignment { - who: 10, - distribution: vec![(10, PerU16::one())], - }], + winners: vec![(10, 12)], + assignments: vec![ + Assignment { who: 10, distribution: vec![(10, PerU16::one())] }, + Assignment { + who: 7, + // note: this percent doesn't even matter, in solution it is 100%. + distribution: vec![(10, PerU16::one())], + }, + ], }; let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); @@ -1394,9 +1396,35 @@ mod tests { Box::new(solution), witness )); - assert_eq!(MultiPhase::queued_solution().unwrap().score.minimal_stake, 10); + assert_eq!(MultiPhase::queued_solution().unwrap().score.minimal_stake, 12); - // trial 1: a solution who's score is only 2, i.e. 20% better in the first element. + // trial 1: a solution who's minimal stake is 10, i.e. worse than the first solution + // of 12. + let result = ElectionResult { + winners: vec![(10, 10)], + assignments: vec![Assignment { + who: 10, + distribution: vec![(10, PerU16::one())], + }], + }; + let (raw, score, _, _) = Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; + // 10 is not better than 12 + assert_eq!(solution.score.minimal_stake, 10); + // submitting this will actually panic. + assert_noop!( + MultiPhase::unsigned_pre_dispatch_checks(&solution), + Error::::PreDispatchWeakSubmission, + ); + + // trial 2: try resubmitting another solution with same score (12) as the queued + // solution. let result = ElectionResult { winners: vec![(10, 12)], assignments: vec![ @@ -1408,6 +1436,7 @@ mod tests { }, ], }; + let (raw, score, _, _) = Miner::::prepare_election_result_with_snapshot( result, voters.clone(), @@ -1416,15 +1445,45 @@ mod tests { ) .unwrap(); let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; - // 12 is not 50% more than 10 + // 12 is not better than 12. We need score of atleast 13 to be accepted. assert_eq!(solution.score.minimal_stake, 12); + // submitting this will panic. assert_noop!( MultiPhase::unsigned_pre_dispatch_checks(&solution), Error::::PreDispatchWeakSubmission, ); - // submitting this will actually panic. - // trial 2: a solution who's score is only 7, i.e. 70% better in the first element. + // trial 3: a solution who's minimal stake is 13, i.e. 1 better than the queued + // solution of 12. + let result = ElectionResult { + winners: vec![(10, 12)], + assignments: vec![ + Assignment { who: 10, distribution: vec![(10, PerU16::one())] }, + Assignment { who: 7, distribution: vec![(10, PerU16::one())] }, + Assignment { who: 9, distribution: vec![(10, PerU16::one())] }, + ], + }; + let (raw, score, witness, _) = + Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; + assert_eq!(solution.score.minimal_stake, 13); + + // this should work + assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); + assert_ok!(MultiPhase::submit_unsigned( + RuntimeOrigin::none(), + Box::new(solution), + witness + )); + + // trial 4: a solution who's minimal stake is 17, i.e. 4 better than the last + // soluton. let result = ElectionResult { winners: vec![(10, 12)], assignments: vec![ 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 635bd079af098c459fc1b937e8899aabe5bf6f8d..81998348e705dc88ebad657483246e5d0f4baeb1 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 @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME election provider multi phase pallet tests with staking pallet, bags-list and session pallets" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] 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 e6c2ce4a6b243480d41e86c833e929e0565829e9..af17f4c7f34f504b06e68216a20ab154699c068d 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 @@ -192,7 +192,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; type BetterSignedThreshold = (); - type BetterUnsignedThreshold = (); type OffchainRepeat = OffchainRepeat; type MinerTxPriority = TransactionPriority; type MinerConfig = Self; @@ -297,6 +296,7 @@ impl pallet_staking::Config for Runtime { type NominationsQuota = pallet_staking::FixedNominationsQuota; type TargetList = TargetBagsList; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = HistoryDepth; type EventListeners = StakeTracker; type WeightInfo = pallet_staking::weights::SubstrateWeight; diff --git a/substrate/frame/election-provider-support/Cargo.toml b/substrate/frame/election-provider-support/Cargo.toml index 7062e54cdbcabc18fa9b359b5efdea2747a4d894..8182863d79665ea9f2c026b3cdc4b2ae10d4e0c7 100644 --- a/substrate/frame/election-provider-support/Cargo.toml +++ b/substrate/frame/election-provider-support/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "election provider supporting traits" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/election-provider-support/benchmarking/Cargo.toml b/substrate/frame/election-provider-support/benchmarking/Cargo.toml index b1e3564b4d44895560d70f2603290fbe33638721..7a2ad5cafb49fff6d181a6b1ce47e562d64fb7df 100644 --- a/substrate/frame/election-provider-support/benchmarking/Cargo.toml +++ b/substrate/frame/election-provider-support/benchmarking/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Benchmarking for election provider support onchain config trait" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index 5bf84daf52d7bc65e5b9514451185195b563362a..04400147790f82b184f694b81fa6c9fac6598057 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "NPoS Solution Type" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.39", features = ["full", "visit"] } +syn = { version = "2.0.48", features = ["full", "visit"] } quote = "1.0.28" proc-macro2 = "1.0.56" -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" [dev-dependencies] parity-scale-codec = "3.6.1" 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 144ed8b18c5366348940991e6427f90a498e3a91..e21b9536727e641e446deb28a69e53265125da6e 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true description = "Fuzzer for phragmén solution type implementation." publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } diff --git a/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index ffb939fa4d21fbdcaba77e79e2d7a2efef5fa630..3c0bc56a1d04ba7f90c00f2e7421ebcb5721438b 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet based on seq-Phragmén election method." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/elections-phragmen/src/benchmarking.rs b/substrate/frame/elections-phragmen/src/benchmarking.rs index 9878f7fd41c068329574810c3fc8edff0e6b7b4a..55bb1b968fa1b11d0a34761b920b97bd7b20bae1 100644 --- a/substrate/frame/elections-phragmen/src/benchmarking.rs +++ b/substrate/frame/elections-phragmen/src/benchmarking.rs @@ -38,7 +38,7 @@ fn endowed_account(name: &'static str, index: u32) -> T::AccountId { let _ = T::Currency::make_free_balance_be(&account, amount); // important to increase the total issuance since T::CurrencyToVote will need it to be sane for // phragmen to work. - T::Currency::issue(amount); + let _ = T::Currency::issue(amount); account } diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml index 779baa432fcb08ce1911eb698103643714e4b8d6..eb6355edd312a1ff5f865c235ffd034f085a3027 100644 --- a/substrate/frame/examples/Cargo.toml +++ b/substrate/frame/examples/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "The single package with examples of various types of FRAME pallets" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,6 +23,7 @@ pallet-example-frame-crate = { path = "frame-crate", default-features = false } pallet-example-kitchensink = { path = "kitchensink", default-features = false } pallet-example-offchain-worker = { path = "offchain-worker", default-features = false } pallet-example-split = { path = "split", default-features = false } +pallet-example-tasks = { path = "tasks", default-features = false } [features] default = ["std"] @@ -31,6 +35,7 @@ std = [ "pallet-example-kitchensink/std", "pallet-example-offchain-worker/std", "pallet-example-split/std", + "pallet-example-tasks/std", ] try-runtime = [ "pallet-default-config-example/try-runtime", @@ -39,4 +44,5 @@ try-runtime = [ "pallet-example-kitchensink/try-runtime", "pallet-example-offchain-worker/try-runtime", "pallet-example-split/try-runtime", + "pallet-example-tasks/try-runtime", ] diff --git a/substrate/frame/examples/basic/Cargo.toml b/substrate/frame/examples/basic/Cargo.toml index 53da9ac2eba1c0417efc336b8a6aca382cb9f3e9..3be1a2e558d2c92aeb28ff15bf7bed03f8fe0a59 100644 --- a/substrate/frame/examples/basic/Cargo.toml +++ b/substrate/frame/examples/basic/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/default-config/Cargo.toml b/substrate/frame/examples/default-config/Cargo.toml index 0d80e6838f07f9c68c945c7510977be50fef263a..01ddf9d383446237fb6cf483a0142776ba31fa35 100644 --- a/substrate/frame/examples/default-config/Cargo.toml +++ b/substrate/frame/examples/default-config/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example pallet demonstrating derive_impl / default_config in action" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/default-config/src/lib.rs b/substrate/frame/examples/default-config/src/lib.rs index 8a1f6f9d6a82cffbd557ab2d3f1e92ad966abad0..f1611bca2ce3cd8466cfff44512d3e0d8d0888d4 100644 --- a/substrate/frame/examples/default-config/src/lib.rs +++ b/substrate/frame/examples/default-config/src/lib.rs @@ -47,6 +47,10 @@ pub mod pallet { #[pallet::no_default] // optional. `RuntimeEvent` is automatically excluded as well. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The overarching task type. + #[pallet::no_default] + type RuntimeTask: Task; + /// An input parameter to this pallet. This value can have a default, because it is not /// reliant on `frame_system::Config` or the overarching runtime in any way. type WithDefaultValue: Get; @@ -193,6 +197,7 @@ pub mod tests { impl pallet_default_config_example::Config for Runtime { // These two both cannot have defaults. type RuntimeEvent = RuntimeEvent; + type RuntimeTask = RuntimeTask; type HasNoDefault = frame_support::traits::ConstU32<1>; type CannotHaveDefault = SomeCall; diff --git a/substrate/frame/examples/dev-mode/Cargo.toml b/substrate/frame/examples/dev-mode/Cargo.toml index ce558fe087bd7cf0aa81f0eabcb3666cc76be0e9..f634d21cf2c958400a108549545adecacbd70087 100644 --- a/substrate/frame/examples/dev-mode/Cargo.toml +++ b/substrate/frame/examples/dev-mode/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index 586b6c0216e13741ca5c4b673d1a3cf8ca802a1e..93a46ba7b249cc806d2366e2bd1ee947b162157d 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example pallet with umbrella crate" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml index e63e11924582a0ae5742c12d715915a1a659de53..4255ebb66b650efb77264b250396c0a05ba0763a 100644 --- a/substrate/frame/examples/kitchensink/Cargo.toml +++ b/substrate/frame/examples/kitchensink/Cargo.toml @@ -7,6 +7,10 @@ license = "MIT-0" homepage = "https://substrate.io" repository.workspace = true description = "FRAME example kitchensink pallet" +publish = false + +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index 9d0b682ee020c17ba9e3a5f486f167e3a3dd82c1..464719375c64994d9fcbe42bb53253f769d6350a 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example pallet for offchain worker" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/split/Cargo.toml b/substrate/frame/examples/split/Cargo.toml index b6b9ea86dfef324a5dac9210bcbb911fc1be116d..97f9062f18189f30542baa0fd08de9b2d98de52d 100644 --- a/substrate/frame/examples/split/Cargo.toml +++ b/substrate/frame/examples/split/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME example splitted pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/examples/src/lib.rs b/substrate/frame/examples/src/lib.rs index 8d65639f835264c1e6220a4e19072cfe913445e0..f38bbe52dc114900e4b497cb17da6deb4406512c 100644 --- a/substrate/frame/examples/src/lib.rs +++ b/substrate/frame/examples/src/lib.rs @@ -43,4 +43,6 @@ //! - [`pallet_example_frame_crate`]: Example pallet showcasing how one can be //! built using only the `frame` umbrella crate. //! +//! - [`pallet_example_tasks`]: This pallet demonstrates the use of `Tasks` to execute service work. +//! //! **Tip**: Use `cargo doc --package --open` to view each pallet's documentation. diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..438cb60c756fa0512c9c9e2c7fb4b44d35a95ebe --- /dev/null +++ b/substrate/frame/examples/tasks/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-example-tasks" +version = "1.0.0-dev" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Pallet to demonstrate the usage of Tasks to recongnize and execute service work" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +frame-support = { path = "../../support", default-features = false } +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 } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } + +frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] +experimental = ["frame-support/experimental", "frame-system/experimental"] diff --git a/substrate/frame/examples/tasks/src/benchmarking.rs b/substrate/frame/examples/tasks/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..81f7d3d3b21c78e5b23685f5e460c1ad4edc60dd --- /dev/null +++ b/substrate/frame/examples/tasks/src/benchmarking.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking for `pallet-example-tasks`. + +#![cfg(feature = "runtime-benchmarks")] + +use crate::*; +use frame_benchmarking::v2::*; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn add_number_into_total() { + Numbers::::insert(0, 1); + + #[block] + { + Task::::add_number_into_total(0).unwrap(); + } + + assert_eq!(Numbers::::get(0), None); + } + + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::mock::Runtime); +} diff --git a/substrate/frame/examples/tasks/src/lib.rs b/substrate/frame/examples/tasks/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..c65d8095bcf6a2c2295bb87b11e4041fbab88173 --- /dev/null +++ b/substrate/frame/examples/tasks/src/lib.rs @@ -0,0 +1,78 @@ +// 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 pallet demonstrates the use of the `pallet::task` api for service work. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::dispatch::DispatchResult; +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +pub mod mock; +pub mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; +pub use weights::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::error] + pub enum Error { + /// The referenced task was not found. + NotFound, + } + + #[pallet::tasks_experimental] + impl Pallet { + /// Add a pair of numbers into the totals and remove them. + #[pallet::task_list(Numbers::::iter_keys())] + #[pallet::task_condition(|i| Numbers::::contains_key(i))] + #[pallet::task_weight(T::WeightInfo::add_number_into_total())] + #[pallet::task_index(0)] + pub fn add_number_into_total(i: u32) -> DispatchResult { + let v = Numbers::::take(i).ok_or(Error::::NotFound)?; + Total::::mutate(|(total_keys, total_values)| { + *total_keys += i; + *total_values += v; + }); + Ok(()) + } + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeTask: frame_support::traits::Task; + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// Some running total. + #[pallet::storage] + pub type Total = StorageValue<_, (u32, u32), ValueQuery>; + + /// Numbers to be added into the total. + #[pallet::storage] + pub type Numbers = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; +} diff --git a/substrate/frame/examples/tasks/src/mock.rs b/substrate/frame/examples/tasks/src/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0fbec3eb7657d68e7987231314545ece18fe4fa --- /dev/null +++ b/substrate/frame/examples/tasks/src/mock.rs @@ -0,0 +1,43 @@ +// 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. + +//! Mock runtime for `tasks-example` tests. +#![cfg(test)] + +use crate::{self as tasks_example}; +use frame_support::derive_impl; + +pub type AccountId = u32; +pub type Balance = u32; + +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub struct Runtime { + System: frame_system, + TasksExample: tasks_example, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; +} + +impl tasks_example::Config for Runtime { + type RuntimeTask = RuntimeTask; + type WeightInfo = (); +} diff --git a/substrate/frame/examples/tasks/src/tests.rs b/substrate/frame/examples/tasks/src/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc3c69f4aef95601c2a96faf1c939c27ee419fd4 --- /dev/null +++ b/substrate/frame/examples/tasks/src/tests.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for `pallet-example-tasks`. +#![cfg(test)] + +use crate::{mock::*, Numbers}; +use frame_support::traits::Task; +use sp_runtime::BuildStorage; + +#[cfg(feature = "experimental")] +use frame_support::{assert_noop, assert_ok}; + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = RuntimeGenesisConfig { + // We use default for brevity, but you can configure as desired if needed. + system: Default::default(), + } + .build_storage() + .unwrap(); + t.into() +} + +#[test] +fn task_enumerate_works() { + new_test_ext().execute_with(|| { + Numbers::::insert(0, 1); + assert_eq!(crate::pallet::Task::::iter().collect::>().len(), 1); + }); +} + +#[test] +fn runtime_task_enumerate_works_via_frame_system_config() { + new_test_ext().execute_with(|| { + Numbers::::insert(0, 1); + Numbers::::insert(1, 4); + assert_eq!( + ::RuntimeTask::iter().collect::>().len(), + 2 + ); + }); +} + +#[test] +fn runtime_task_enumerate_works_via_pallet_config() { + new_test_ext().execute_with(|| { + Numbers::::insert(1, 4); + assert_eq!( + ::RuntimeTask::iter() + .collect::>() + .len(), + 1 + ); + }); +} + +#[test] +fn task_index_works_at_pallet_level() { + new_test_ext().execute_with(|| { + assert_eq!(crate::pallet::Task::::AddNumberIntoTotal { i: 2u32 }.task_index(), 0); + }); +} + +#[test] +fn task_index_works_at_runtime_level() { + new_test_ext().execute_with(|| { + assert_eq!( + ::RuntimeTask::TasksExample(crate::pallet::Task::< + Runtime, + >::AddNumberIntoTotal { + i: 1u32 + }) + .task_index(), + 0 + ); + }); +} + +#[cfg(feature = "experimental")] +#[test] +fn task_execution_works() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Numbers::::insert(0, 1); + Numbers::::insert(1, 4); + + let task = + ::RuntimeTask::TasksExample(crate::pallet::Task::< + Runtime, + >::AddNumberIntoTotal { + i: 1u32, + }); + assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),)); + assert_eq!(Numbers::::get(0), Some(1)); + assert_eq!(Numbers::::get(1), None); + assert_eq!(crate::Total::::get(), (1, 4)); + System::assert_last_event(frame_system::Event::::TaskCompleted { task }.into()); + }); +} + +#[cfg(feature = "experimental")] +#[test] +fn task_execution_fails_for_invalid_task() { + new_test_ext().execute_with(|| { + Numbers::::insert(1, 4); + assert_noop!( + System::do_task( + RuntimeOrigin::signed(1), + ::RuntimeTask::TasksExample( + crate::pallet::Task::::AddNumberIntoTotal { i: 0u32 } + ), + ), + frame_system::Error::::InvalidTask + ); + }); +} diff --git a/substrate/frame/examples/tasks/src/weights.rs b/substrate/frame/examples/tasks/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..793af6e962201fd9f92e0260ea0e24f5bc39753d --- /dev/null +++ b/substrate/frame/examples/tasks/src/weights.rs @@ -0,0 +1,84 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_example_tasks` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-02, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `MacBook.local`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet_example_tasks +// --extrinsic +// * +// --steps +// 20 +// --repeat +// 10 +// --output +// frame/examples/tasks/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_template. +pub trait WeightInfo { + fn add_number_into_total() -> Weight; +} + +/// Weight functions for `pallet_example_kitchensink`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Kitchensink OtherFoo (r:0 w:1) + /// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured) + fn add_number_into_total() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} + +impl WeightInfo for () { + /// Storage: Kitchensink OtherFoo (r:0 w:1) + /// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured) + fn add_number_into_total() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(RocksDbWeight::get().writes(1)) + } +} diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml index c2a92ad3d729b32adb0128a0a3188d45bbeba2e1..b98ceb0ba9a57042f56ccb9d782e84535a9a3c53 100644 --- a/substrate/frame/executive/Cargo.toml +++ b/substrate/frame/executive/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME executives engine" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/executive/src/lib.rs b/substrate/frame/executive/src/lib.rs index b351819f612b025cf6ff7072ae4b62598c899be0..f2cc3b6316883c5b9e7785c45e34e5efca939c41 100644 --- a/substrate/frame/executive/src/lib.rs +++ b/substrate/frame/executive/src/lib.rs @@ -409,9 +409,10 @@ where ) -> Result<(), TryRuntimeError> { match res { Ok(bytes) => { - log::debug!( + log::info!( target: LOG_TARGET, - "decoded the entire state ({bytes} bytes)", + "✅ Entire runtime state decodes without error. {} bytes total.", + bytes ); Ok(()) diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index 4440a0c0f6482438efda1521bf918a86c7ac6cba..673a2f71d6c8cfb839463b7bfc779ecccfb1a86e 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME fast unstake pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/fast-unstake/src/benchmarking.rs b/substrate/frame/fast-unstake/src/benchmarking.rs index 851483e3697bfdffe0e9dcabf8f9a04111a25d0b..4828dcb9b42cb924c8b20363e2447b3c428ecf36 100644 --- a/substrate/frame/fast-unstake/src/benchmarking.rs +++ b/substrate/frame/fast-unstake/src/benchmarking.rs @@ -162,7 +162,7 @@ benchmarks! { fast_unstake_events::().last(), Some(Event::BatchChecked { .. }) )); - assert!(stashes.iter().all(|(s, _)| request.stashes.iter().find(|(ss, _)| ss == s).is_some())); + assert!(stashes.iter().all(|(s, _)| request.stashes.iter().any(|(ss, _)| ss == s))); } register_fast_unstake { diff --git a/substrate/frame/fast-unstake/src/lib.rs b/substrate/frame/fast-unstake/src/lib.rs index 153b6c2c353f6cc40ac0689fc1ea26830653ede9..04a50543bcc9f2b74c84f74dbe1615bda9f47b55 100644 --- a/substrate/frame/fast-unstake/src/lib.rs +++ b/substrate/frame/fast-unstake/src/lib.rs @@ -571,7 +571,7 @@ pub mod pallet { .any(|e| T::Staking::is_exposed_in_era(&stash, e)); if is_exposed { - T::Currency::slash_reserved(&stash, deposit); + let _ = T::Currency::slash_reserved(&stash, deposit); log!(info, "slashed {:?} by {:?}", stash, deposit); Self::deposit_event(Event::::Slashed { stash, amount: deposit }); false diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 86bbd4c44359126da056a53869b917f9256d6b10..f18076d59006a3cde093959ae434029a49069bed 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -142,6 +142,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 81388d0e0f58726d9745c77e5b0ec3a5f5e49244..068fb4e821cbad5efd83cc5a08ffd78cf1a2e9d0 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for pushing a chain to its weight limits" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index b4444d4580cd99ddd035314b96f2b0ceb831b8fb..b4f51d88c6d21a7d1367f5a29cf7398660125006 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for GRANDPA finality gadget" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 4766d5c3780cab2125e3105f98cb10a35e7f6e77..f1f51e0b118163ec1eee16fb951260306832e6da 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -206,6 +206,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index 894365748ceb06754c37729a33af38b6ce1c3f86..78f966c0535e434ec222296d36f6ef6a1915513d 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME identity management pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -29,6 +32,7 @@ sp-core = { path = "../../primitives/core" } [features] default = ["std"] + std = [ "codec/std", "enumflags2/std", diff --git a/substrate/frame/identity/src/lib.rs b/substrate/frame/identity/src/lib.rs index 133f9eeb4befcb6a004dfc4792e1227d027d1602..8588612cd5bcd3b62c0de1398dfebde864de41c5 100644 --- a/substrate/frame/identity/src/lib.rs +++ b/substrate/frame/identity/src/lib.rs @@ -1005,8 +1005,9 @@ impl Pallet { Ok((new_id_deposit, new_subs_deposit)) } - /// Set an identity with zero deposit. Only used for benchmarking that involves `rejig_deposit`. - #[cfg(feature = "runtime-benchmarks")] + /// Set an identity with zero deposit. Used for benchmarking and XCM emulator tests that involve + /// `rejig_deposit`. + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub fn set_identity_no_deposit( who: &T::AccountId, info: T::IdentityInformation, @@ -1022,15 +1023,25 @@ impl Pallet { Ok(()) } - /// Set subs with zero deposit. Only used for benchmarking that involves `rejig_deposit`. - #[cfg(feature = "runtime-benchmarks")] - pub fn set_sub_no_deposit(who: &T::AccountId, sub: T::AccountId) -> DispatchResult { + /// Set subs with zero deposit and default name. Only used for benchmarks that involve + /// `rejig_deposit`. + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] + pub fn set_subs_no_deposit( + who: &T::AccountId, + subs: Vec<(T::AccountId, Data)>, + ) -> DispatchResult { use frame_support::BoundedVec; - let subs = BoundedVec::<_, T::MaxSubAccounts>::try_from(vec![sub]).unwrap(); + let mut sub_accounts = BoundedVec::::default(); + for (sub, name) in subs { + >::insert(&sub, (who.clone(), name)); + sub_accounts + .try_push(sub) + .expect("benchmark should not pass more than T::MaxSubAccounts"); + } SubsOf::::insert::< &T::AccountId, (BalanceOf, BoundedVec), - >(&who, (Zero::zero(), subs)); + >(&who, (Zero::zero(), sub_accounts)); Ok(()) } } diff --git a/substrate/frame/im-online/Cargo.toml b/substrate/frame/im-online/Cargo.toml index 5ec260c9b5be60eee305a852e07f00cb5f036ef7..b5b01858c898a6cc4596e55e76b85433f79e9ca6 100644 --- a/substrate/frame/im-online/Cargo.toml +++ b/substrate/frame/im-online/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME's I'm online pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index 4f12ecfcce3ad094579eb5ad9cec4db614e8a0e0..4f0c780c6af35d4273bc1824830a663c54220fd3 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME indices management pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/indices/src/lib.rs b/substrate/frame/indices/src/lib.rs index 3c0b49304131f58ee64fc1e2824f9e2b9b6a33c9..ff12d092cfb8d67f7d44ca5648a120bbd8507cb3 100644 --- a/substrate/frame/indices/src/lib.rs +++ b/substrate/frame/indices/src/lib.rs @@ -223,7 +223,7 @@ pub mod pallet { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); ensure!(account == who, Error::::NotOwner); - T::Currency::slash_reserved(&who, amount); + let _ = T::Currency::slash_reserved(&who, amount); *maybe_value = Some((account, Zero::zero(), true)); Ok(()) })?; diff --git a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml index f7afbb95397e73d551b80603c69624c7d0e96799..fb1447d10457a94e255bacc4af2d73c0ca084c73 100644 --- a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml +++ b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Insecure do not use in production: FRAME randomness collective flip pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/lottery/Cargo.toml b/substrate/frame/lottery/Cargo.toml index bba17d2718cae5301f892db4e14dd8fcde9fe436..49f84b04b2578094e72dcb090b32c735a873d769 100644 --- a/substrate/frame/lottery/Cargo.toml +++ b/substrate/frame/lottery/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME Participation Lottery Pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/lottery/src/lib.rs b/substrate/frame/lottery/src/lib.rs index c54f6d76803fff51e820b99db1b86c82b7629ca6..54a8edd38606d76bfa4780d05918d05a1d690ae9 100644 --- a/substrate/frame/lottery/src/lib.rs +++ b/substrate/frame/lottery/src/lib.rs @@ -368,7 +368,8 @@ pub mod pallet { // Make sure pot exists. let lottery_account = Self::account_id(); if T::Currency::total_balance(&lottery_account).is_zero() { - T::Currency::deposit_creating(&lottery_account, T::Currency::minimum_balance()); + let _ = + T::Currency::deposit_creating(&lottery_account, T::Currency::minimum_balance()); } Self::deposit_event(Event::::LotteryStarted); Ok(()) diff --git a/substrate/frame/membership/Cargo.toml b/substrate/frame/membership/Cargo.toml index f90d70e911d89ef52350afbec7fdae87a52a9560..c4c94e202a4db090e8988afb09eafa9a1ed2da14 100644 --- a/substrate/frame/membership/Cargo.toml +++ b/substrate/frame/membership/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME membership management pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 1f31e70492841c82bae6cbb5095b93dcc2b48f25..eaa17d88e9959ee8e51f42f55b46adc45187b04b 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME Merkle Mountain Range pallet." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index 148848f2bf011b4cb67ffe5dfe7680957f4ea369..e3ab370727ea865fb7cf03884d9f99d392089cc4 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -8,11 +8,15 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet to queue and process messages" +[lints] +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.193", optional = true, features = ["derive"] } +serde = { version = "1.0.195", optional = true, features = ["derive"] } log = { version = "0.4.17", default-features = false } +environmental = { version = "1.1.4", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } @@ -34,6 +38,7 @@ rand_distr = "0.4.3" default = ["std"] std = [ "codec/std", + "environmental/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", diff --git a/substrate/frame/message-queue/src/benchmarking.rs b/substrate/frame/message-queue/src/benchmarking.rs index eedaaebeca944bd8dfd1778fea82ad7504111d03..7e99bc058584504dfea69ebcd3463657e4379d83 100644 --- a/substrate/frame/message-queue/src/benchmarking.rs +++ b/substrate/frame/message-queue/src/benchmarking.rs @@ -25,6 +25,7 @@ use super::{mock_helpers::*, Pallet as MessageQueue, *}; use frame_benchmarking::v2::*; use frame_support::traits::Get; use frame_system::RawOrigin; +use sp_io::hashing::blake2_256; use sp_std::prelude::*; #[benchmarks( @@ -142,7 +143,7 @@ mod benchmarks { // Check that it was processed. assert_last_event::( Event::Processed { - id: sp_io::hashing::blake2_256(&msg), + id: blake2_256(&msg).into(), origin: 0.into(), weight_used: 1.into_weight(), success: true, @@ -227,7 +228,7 @@ mod benchmarks { assert_last_event::( Event::Processed { - id: sp_io::hashing::blake2_256(&((msgs - 1) as u32).encode()), + id: blake2_256(&((msgs - 1) as u32).encode()).into(), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, @@ -264,7 +265,7 @@ mod benchmarks { assert_last_event::( Event::Processed { - id: sp_io::hashing::blake2_256(&((msgs - 1) as u32).encode()), + id: blake2_256(&((msgs - 1) as u32).encode()).into(), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, diff --git a/substrate/frame/message-queue/src/integration_test.rs b/substrate/frame/message-queue/src/integration_test.rs index 53dc204ab9c0770c49626b9035d9cd749d5955e6..fee5d24213538eca9b44ed4fae516ca82ec4d445 100644 --- a/substrate/frame/message-queue/src/integration_test.rs +++ b/substrate/frame/message-queue/src/integration_test.rs @@ -29,8 +29,8 @@ use crate::{ mock::{ - build_and_execute, CountingMessageProcessor, IntoWeight, MockedWeightInfo, - NumMessagesProcessed, YieldingQueues, + build_and_execute, gen_seed, Callback, CountingMessageProcessor, IntoWeight, + MessagesProcessed, MockedWeightInfo, NumMessagesProcessed, YieldingQueues, }, mock_helpers::MessageOrigin, *, @@ -120,13 +120,13 @@ impl Config for Test { /// Processing all remaining 28639 messages /// ``` #[test] -#[ignore] // Only run in the CI. +#[ignore] // Only run in the CI, otherwise its too slow. fn stress_test_enqueue_and_service() { let blocks = 20; let max_queues = 10_000; let max_messages_per_queue = 10_000; let max_msg_len = MaxMessageLenOf::::get(); - let mut rng = StdRng::seed_from_u64(43); + let mut rng = StdRng::seed_from_u64(gen_seed()); build_and_execute::(|| { let mut msgs_remaining = 0; @@ -148,6 +148,74 @@ fn stress_test_enqueue_and_service() { }); } +/// Very similar to `stress_test_enqueue_and_service`, but enqueues messages while processing them. +#[test] +#[ignore] // Only run in the CI, otherwise its too slow. +fn stress_test_recursive() { + let blocks = 20; + let mut rng = StdRng::seed_from_u64(gen_seed()); + + // We need to use thread-locals since the callback cannot capture anything. + parameter_types! { + pub static TotalEnqueued: u32 = 0; + pub static Enqueued: u32 = 0; + pub static Called: u32 = 0; + } + + Called::take(); + Enqueued::take(); + TotalEnqueued::take(); + + Callback::set(Box::new(|_, _| { + let mut rng = StdRng::seed_from_u64(Enqueued::get() as u64); + let max_queues = 1_000; + let max_messages_per_queue = 1_000; + let max_msg_len = MaxMessageLenOf::::get(); + + // Instead of directly enqueueing, we enqueue inside a `service` call. + let enqueued = enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); + TotalEnqueued::set(TotalEnqueued::get() + enqueued); + Enqueued::set(Enqueued::get() + enqueued); + Called::set(Called::get() + 1); + })); + + build_and_execute::(|| { + let mut msgs_remaining = 0; + for b in 0..blocks { + log::info!("Block #{}", b); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(format!("callback={b}").as_bytes()), + b.into(), + ); + + msgs_remaining += Enqueued::take() + 1; + // Pick a fraction of all messages currently in queue and process them. + let processed = rng.gen_range(1..=msgs_remaining); + log::info!("Processing {} of all messages {}", processed, msgs_remaining); + process_some_messages(processed); // This also advances the block. + msgs_remaining -= processed; + TotalEnqueued::set(TotalEnqueued::get() - processed + 1); + MessageQueue::do_try_state().unwrap(); + } + while Called::get() < blocks { + msgs_remaining += Enqueued::take(); + // Pick a fraction of all messages currently in queue and process them. + let processed = rng.gen_range(1..=msgs_remaining); + log::info!("Processing {} of all messages {}", processed, msgs_remaining); + process_some_messages(processed); // This also advances the block. + msgs_remaining -= processed; + TotalEnqueued::set(TotalEnqueued::get() - processed); + MessageQueue::do_try_state().unwrap(); + } + + let msgs_remaining = TotalEnqueued::take(); + log::info!("Processing all remaining {} messages", msgs_remaining); + process_all_messages(msgs_remaining); + assert_eq!(Called::get(), blocks); + post_conditions(); + }); +} + /// Simulates heavy usage of the suspension logic via `Yield`. /// /// # Example output @@ -164,14 +232,14 @@ fn stress_test_enqueue_and_service() { /// Processing all remaining 430 messages /// ``` #[test] -#[ignore] // Only run in the CI. +#[ignore] // Only run in the CI, otherwise its too slow. fn stress_test_queue_suspension() { let blocks = 20; let max_queues = 10_000; let max_messages_per_queue = 10_000; let (max_suspend_per_block, max_resume_per_block) = (100, 50); let max_msg_len = MaxMessageLenOf::::get(); - let mut rng = StdRng::seed_from_u64(43); + let mut rng = StdRng::seed_from_u64(gen_seed()); build_and_execute::(|| { let mut suspended = BTreeSet::::new(); @@ -300,6 +368,7 @@ fn process_all_messages(expected: u32) { assert_eq!(consumed, Weight::from_all(expected as u64)); assert_eq!(NumMessagesProcessed::take(), expected as usize); + MessagesProcessed::take(); } /// Returns the weight consumed by `MessageQueue::on_initialize()`. @@ -327,5 +396,6 @@ fn post_conditions() { assert!(ServiceHead::::get().is_none()); // This still works fine. assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + MessageQueue::do_try_state().unwrap(); next_block(); } diff --git a/substrate/frame/message-queue/src/lib.rs b/substrate/frame/message-queue/src/lib.rs index 12d289478b37c5a0eb81aeb5f673a6d34d8dcf4f..07eb0041985342522a113339769b98f834ef4a17 100644 --- a/substrate/frame/message-queue/src/lib.rs +++ b/substrate/frame/message-queue/src/lib.rs @@ -49,9 +49,21 @@ //! **Message Execution** //! //! Executing a message is offloaded to the [`Config::MessageProcessor`] which contains the actual -//! logic of how to handle the message since they are blobs. A message can be temporarily or -//! permanently overweight. The pallet will perpetually try to execute a temporarily overweight -//! message. A permanently overweight message is skipped and must be executed manually. +//! logic of how to handle the message since they are blobs. Storage changes are not rolled back on +//! error. +//! +//! A failed message can be temporarily or permanently overweight. The pallet will perpetually try +//! to execute a temporarily overweight message. A permanently overweight message is skipped and +//! must be executed manually. +//! +//! **Reentrancy** +//! +//! This pallet has two entry points for executing (possibly recursive) logic; +//! [`Pallet::service_queues`] and [`Pallet::execute_overweight`]. Both entry points are guarded by +//! the same mutex to error on reentrancy. The only functions that are explicitly **allowed** to be +//! called by a message processor are: [`Pallet::enqueue_message`] and +//! [`Pallet::enqueue_messages`]. All other functions are forbidden and error with +//! [`Error::RecursiveDisallowed`]. //! //! **Pagination** //! @@ -146,6 +158,7 @@ //! which is the default state for a message after being enqueued. //! - `knitting`/`unknitting`: The means of adding or removing a `Queue` from the `ReadyRing`. //! - `MEL`: The Max Encoded Length of a type, see [`codec::MaxEncodedLen`]. +//! - `Reentrance`: To enter an execution context again before it has completed. //! //! # Properties //! @@ -180,6 +193,7 @@ //! expensive. Currently this is archived by having one queue per para-chain/thread, which keeps the //! number of queues within `O(n)` and should be "good enough". +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; @@ -194,8 +208,8 @@ use frame_support::{ defensive, pallet_prelude::*, traits::{ - DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, - ProcessMessageError, QueueFootprint, QueuePausedQuery, ServiceQueues, + Defensive, DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, + ProcessMessage, ProcessMessageError, QueueFootprint, QueuePausedQuery, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -203,6 +217,7 @@ use frame_system::pallet_prelude::*; pub use pallet::*; use scale_info::TypeInfo; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_core::{defer, H256}; use sp_runtime::{ traits::{One, Zero}, SaturatedConversion, Saturating, @@ -460,6 +475,10 @@ pub mod pallet { /// Processor for a message. /// + /// Storage changes are not rolled back on error. + /// + /// # Benchmarking + /// /// Must be set to [`mock_helpers::NoopMessageProcessor`] for benchmarking. /// Other message processors that consumes exactly (1, 1) weight for any give message will /// work as well. Otherwise the benchmarking will also measure the weight of the message @@ -516,18 +535,51 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Message discarded due to an error in the `MessageProcessor` (usually a format error). - ProcessingFailed { id: [u8; 32], origin: MessageOriginOf, error: ProcessMessageError }, + ProcessingFailed { + /// The `blake2_256` hash of the message. + id: H256, + /// The queue of the message. + origin: MessageOriginOf, + /// The error that occurred. + /// + /// This error is pretty opaque. More fine-grained errors need to be emitted as events + /// by the `MessageProcessor`. + error: ProcessMessageError, + }, /// Message is processed. - Processed { id: [u8; 32], origin: MessageOriginOf, weight_used: Weight, success: bool }, + Processed { + /// The `blake2_256` hash of the message. + id: H256, + /// The queue of the message. + origin: MessageOriginOf, + /// How much weight was used to process the message. + weight_used: Weight, + /// Whether the message was processed. + /// + /// Note that this does not mean that the underlying `MessageProcessor` was internally + /// successful. It *solely* means that the MQ pallet will treat this as a success + /// condition and discard the message. Any internal error needs to be emitted as events + /// by the `MessageProcessor`. + success: bool, + }, /// Message placed in overweight queue. OverweightEnqueued { + /// The `blake2_256` hash of the message. id: [u8; 32], + /// The queue of the message. origin: MessageOriginOf, + /// The page of the message. page_index: PageIndex, + /// The index of the message within the page. message_index: T::Size, }, /// This page was reaped. - PageReaped { origin: MessageOriginOf, index: PageIndex }, + PageReaped { + /// The queue of the page. + origin: MessageOriginOf, + /// The index of the page. + index: PageIndex, + }, } #[pallet::error] @@ -554,6 +606,8 @@ pub mod pallet { /// /// This can change at any time and may resolve in the future by re-trying. QueuePaused, + /// Another call is in progress and needs to finish before this call can happen. + RecursiveDisallowed, } /// The index of the first and last (non-empty) pages. @@ -868,6 +922,21 @@ impl Pallet { page_index: PageIndex, index: T::Size, weight_limit: Weight, + ) -> Result> { + match with_service_mutex(|| { + Self::do_execute_overweight_inner(origin, page_index, index, weight_limit) + }) { + Err(()) => Err(Error::::RecursiveDisallowed), + Ok(x) => x, + } + } + + /// Same as `do_execute_overweight` but must be called while holding the `service_mutex`. + fn do_execute_overweight_inner( + origin: MessageOriginOf, + page_index: PageIndex, + index: T::Size, + weight_limit: Weight, ) -> Result> { let mut book_state = BookStateFor::::get(&origin); ensure!(!T::QueuePausedQuery::is_paused(&origin), Error::::QueuePaused); @@ -924,6 +993,14 @@ impl Pallet { /// Remove a stale page or one which has no more messages remaining to be processed. fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { + match with_service_mutex(|| Self::do_reap_page_inner(origin, page_index)) { + Err(()) => Err(Error::::RecursiveDisallowed.into()), + Ok(x) => x, + } + } + + /// Same as `do_reap_page` but must be called while holding the `service_mutex`. + fn do_reap_page_inner(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { let mut book_state = BookStateFor::::get(origin); // definitely not reapable if the page's index is no less than the `begin`ning of ready // pages. @@ -1112,6 +1189,7 @@ impl Pallet { weight: &mut WeightMeter, overweight_limit: Weight, ) -> ItemExecutionStatus { + use MessageExecutionStatus::*; // This ugly pre-checking is needed for the invariant // "we never bail if a page became complete". if page.is_complete() { @@ -1125,16 +1203,31 @@ impl Pallet { Some(m) => m, None => return ItemExecutionStatus::NoItem, }[..]; + let payload_len = payload.len() as u64; - use MessageExecutionStatus::*; - let is_processed = match Self::process_message_payload( + // Store these for the case that `process_message_payload` is recursive. + Pages::::insert(origin, page_index, &*page); + BookStateFor::::insert(origin, &*book_state); + + let res = Self::process_message_payload( origin.clone(), page_index, page.first_index, payload, weight, overweight_limit, - ) { + ); + + // And restore them afterwards to see the changes of a recursive call. + *book_state = BookStateFor::::get(origin); + if let Some(new_page) = Pages::::get(origin, page_index) { + *page = new_page; + } else { + defensive!("page must exist since we just inserted it and recursive calls are not allowed to remove anything"); + return ItemExecutionStatus::NoItem + }; + + let is_processed = match res { InsufficientWeight => return ItemExecutionStatus::Bailed, Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress, Processed | Unprocessable { permanent: true } => true, @@ -1143,7 +1236,7 @@ impl Pallet { if is_processed { book_state.message_count.saturating_dec(); - book_state.size.saturating_reduce(payload.len() as u64); + book_state.size.saturating_reduce(payload_len as u64); } page.skip_first(is_processed); ItemExecutionStatus::Executed(is_processed) @@ -1168,7 +1261,7 @@ impl Pallet { /// * `remaining_size` > 0 /// * `first` <= `last` /// * Every page can be decoded into peek_* functions - #[cfg(any(test, feature = "try-runtime"))] + #[cfg(any(test, feature = "try-runtime", feature = "std"))] pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { // Checking memory corruption for BookStateFor ensure!( @@ -1181,13 +1274,17 @@ impl Pallet { "Memory Corruption in Pages" ); - // No state to check - if ServiceHead::::get().is_none() { - return Ok(()) + // Basic checks for each book + for book in BookStateFor::::iter_values() { + ensure!(book.end >= book.begin, "Invariant"); + ensure!(book.end < 1 << 30, "Likely overflow or corruption"); + ensure!(book.message_count < 1 << 30, "Likely overflow or corruption"); + ensure!(book.size < 1 << 30, "Likely overflow or corruption"); + ensure!(book.count < 1 << 30, "Likely overflow or corruption"); } //loop around this origin - let starting_origin = ServiceHead::::get().unwrap(); + let Some(starting_origin) = ServiceHead::::get() else { return Ok(()) }; while let Some(head) = Self::bump_service_head(&mut WeightMeter::new()) { ensure!( @@ -1220,7 +1317,7 @@ impl Pallet { for page_index in head_book_state.begin..head_book_state.end { let page = Pages::::get(&head, page_index).unwrap(); let remaining_messages = page.remaining; - let mut counted_remaining_messages = 0; + let mut counted_remaining_messages: u32 = 0; ensure!( remaining_messages > 0.into(), "These must be some messages that have not been processed yet!" @@ -1237,7 +1334,7 @@ impl Pallet { } ensure!( - remaining_messages == counted_remaining_messages.into(), + remaining_messages.into() == counted_remaining_messages, "Memory Corruption" ); } @@ -1312,10 +1409,9 @@ impl Pallet { meter: &mut WeightMeter, overweight_limit: Weight, ) -> MessageExecutionStatus { - let hash = sp_io::hashing::blake2_256(message); + let mut id = sp_io::hashing::blake2_256(message); use ProcessMessageError::*; let prev_consumed = meter.consumed(); - let mut id = hash; match T::MessageProcessor::process_message(message, origin.clone(), meter, &mut id) { Err(Overweight(w)) if w.any_gt(overweight_limit) => { @@ -1339,19 +1435,44 @@ impl Pallet { }, Err(error @ BadFormat | error @ Corrupt | error @ Unsupported) => { // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { id, origin, error }); + Self::deposit_event(Event::::ProcessingFailed { id: id.into(), origin, error }); MessageExecutionStatus::Unprocessable { permanent: true } }, Ok(success) => { // Success let weight_used = meter.consumed().saturating_sub(prev_consumed); - Self::deposit_event(Event::::Processed { id, origin, weight_used, success }); + Self::deposit_event(Event::::Processed { + id: id.into(), + origin, + weight_used, + success, + }); MessageExecutionStatus::Processed }, } } } +/// Run a closure that errors on re-entrance. Meant to be used by anything that services queues. +pub(crate) fn with_service_mutex R, R>(f: F) -> Result { + // Holds the singelton token instance. + environmental::environmental!(token: Option<()>); + + token::using_once(&mut Some(()), || { + // The first `ok_or` should always be `Ok` since we are inside a `using_once`. + let hold = token::with(|t| t.take()).ok_or(()).defensive()?.ok_or(())?; + + // Put the token back when we're done. + defer! { + token::with(|t| { + *t = Some(hold); + }); + } + + Ok(f()) + }) +} + /// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type. pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); impl Get for MaxEncodedLenOf { @@ -1407,35 +1528,40 @@ impl ServiceQueues for Pallet { Weight::zero() }); - let mut next = match Self::bump_service_head(&mut weight) { - Some(h) => h, - None => return weight.consumed(), - }; - // The last queue that did not make any progress. - // The loop aborts as soon as it arrives at this queue again without making any progress - // on other queues in between. - let mut last_no_progress = None; - - loop { - let (progressed, n) = Self::service_queue(next.clone(), &mut weight, max_weight); - next = match n { - Some(n) => - if !progressed { - if last_no_progress == Some(n.clone()) { - break - } - if last_no_progress.is_none() { - last_no_progress = Some(next.clone()) - } - n - } else { - last_no_progress = None; - n - }, - None => break, + match with_service_mutex(|| { + let mut next = match Self::bump_service_head(&mut weight) { + Some(h) => h, + None => return weight.consumed(), + }; + // The last queue that did not make any progress. + // The loop aborts as soon as it arrives at this queue again without making any progress + // on other queues in between. + let mut last_no_progress = None; + + loop { + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, max_weight); + next = match n { + Some(n) => + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n + } else { + last_no_progress = None; + n + }, + None => break, + } } + weight.consumed() + }) { + Err(()) => weight.consumed(), + Ok(w) => w, } - weight.consumed() } /// Execute a single overweight message. @@ -1463,6 +1589,7 @@ impl ServiceQueues for Pallet { Error::::QueuePaused => ExecuteOverweightError::QueuePaused, Error::::NoPage | Error::::NoMessage | Error::::Queued => ExecuteOverweightError::NotFound, + Error::::RecursiveDisallowed => ExecuteOverweightError::RecursiveDisallowed, _ => ExecuteOverweightError::Other, }, ) diff --git a/substrate/frame/message-queue/src/mock.rs b/substrate/frame/message-queue/src/mock.rs index 97246900597ea209d6ac3f2f4f9541a26ebe77bb..89c6e8625109697556f6a1057f1238d2f1d99957 100644 --- a/substrate/frame/message-queue/src/mock.rs +++ b/substrate/frame/message-queue/src/mock.rs @@ -108,7 +108,10 @@ impl MockedWeightInfo { impl crate::weights::WeightInfo for MockedWeightInfo { fn reap_page() -> Weight { - WeightForCall::get().get("reap_page").copied().unwrap_or_default() + WeightForCall::get() + .get("reap_page") + .copied() + .unwrap_or(DefaultWeightForCall::get()) } fn execute_overweight_page_updated() -> Weight { WeightForCall::get() @@ -207,6 +210,10 @@ impl ProcessMessage for RecordingMessageProcessor { let required = Weight::from_parts(weight, weight); if meter.try_consume(required).is_ok() { + if let Some(p) = message.strip_prefix(&b"callback="[..]) { + let s = String::from_utf8(p.to_vec()).expect("Need valid UTF8"); + Callback::get()(&origin, s.parse().expect("Expected an u32")); + } let mut m = MessagesProcessed::get(); m.push((message.to_vec(), origin)); MessagesProcessed::set(m); @@ -217,6 +224,10 @@ impl ProcessMessage for RecordingMessageProcessor { } } +parameter_types! { + pub static Callback: Box = Box::new(|_, _| {}); +} + /// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or /// `yield` will fail with an error respectively. fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessMessageError> { @@ -264,6 +275,10 @@ impl ProcessMessage for CountingMessageProcessor { let required = Weight::from_parts(1, 1); if meter.try_consume(required).is_ok() { + if let Some(p) = message.strip_prefix(&b"callback="[..]) { + let s = String::from_utf8(p.to_vec()).expect("Need valid UTF8"); + Callback::get()(&origin, s.parse().expect("Expected an u32")); + } NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); Ok(true) } else { @@ -372,3 +387,16 @@ pub fn num_overweight_enqueued_events() -> u32 { pub fn fp(pages: u32, count: u64, size: u64) -> QueueFootprint { QueueFootprint { storage: Footprint { count, size }, pages } } + +/// A random seed that can be overwritten with `MQ_SEED`. +pub fn gen_seed() -> u64 { + use rand::Rng; + let seed = if let Ok(seed) = std::env::var("MQ_SEED") { + seed.parse().expect("Need valid u64 as MQ_SEED env variable") + } else { + rand::thread_rng().gen::() + }; + + println!("Using seed: {}", seed); + seed +} diff --git a/substrate/frame/message-queue/src/mock_helpers.rs b/substrate/frame/message-queue/src/mock_helpers.rs index f6109c127be123c427e075d78bf80fbe04baada3..28395e27cdd2aaffcda2650fbc0988f764b03f92 100644 --- a/substrate/frame/message-queue/src/mock_helpers.rs +++ b/substrate/frame/message-queue/src/mock_helpers.rs @@ -15,6 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![allow(missing_docs)] + //! Std setup helpers for testing and benchmarking. //! //! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. @@ -88,10 +90,12 @@ pub fn page(msg: &[u8]) -> PageOf { PageOf::::from_message::(msg.try_into().unwrap()) } +/// Create a book with a single message of one byte. pub fn single_page_book() -> BookStateOf { BookState { begin: 0, end: 1, count: 1, message_count: 1, size: 1, ..Default::default() } } +/// Create an empty book. pub fn empty_book() -> BookStateOf { BookState { begin: 0, end: 1, count: 1, ..Default::default() } } diff --git a/substrate/frame/message-queue/src/tests.rs b/substrate/frame/message-queue/src/tests.rs index d94ad581ea0d53a4ee445df25b806dd3ac3417fb..9198e65e2f9c0a5c2ee3bac97a63c1dd844be63f 100644 --- a/substrate/frame/message-queue/src/tests.rs +++ b/substrate/frame/message-queue/src/tests.rs @@ -181,7 +181,7 @@ fn service_queues_failing_messages_works() { assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - id: blake2_256(b"badformat"), + id: blake2_256(b"badformat").into(), origin: MessageOrigin::Here, error: ProcessMessageError::BadFormat, } @@ -190,7 +190,7 @@ fn service_queues_failing_messages_works() { assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - id: blake2_256(b"corrupt"), + id: blake2_256(b"corrupt").into(), origin: MessageOrigin::Here, error: ProcessMessageError::Corrupt, } @@ -199,7 +199,7 @@ fn service_queues_failing_messages_works() { assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - id: blake2_256(b"unsupported"), + id: blake2_256(b"unsupported").into(), origin: MessageOrigin::Here, error: ProcessMessageError::Unsupported, } @@ -1264,7 +1264,7 @@ fn permanently_overweight_limit_is_valid_basic() { RuntimeEvent::MessageQueue(Event::Processed { origin: Here, weight_used: 200.into(), - id: blake2_256(m.as_bytes()), + id: blake2_256(m.as_bytes()).into(), success: true, }) ); @@ -1321,7 +1321,7 @@ fn permanently_overweight_limit_is_valid_fuzzy() { RuntimeEvent::MessageQueue(Event::Processed { origin: Here, weight_used: 200.into(), - id: blake2_256(m.as_bytes()), + id: blake2_256(m.as_bytes()).into(), success: true, }) ); @@ -1592,7 +1592,7 @@ fn execute_overweight_respects_suspension() { assert_last_event::( Event::Processed { - id: blake2_256(b"weight=5"), + id: blake2_256(b"weight=5").into(), origin, weight_used: 5.into_weight(), success: true, @@ -1619,7 +1619,7 @@ fn service_queue_suspension_ready_ring_works() { MessageQueue::service_queues(Weight::MAX); assert_last_event::( Event::Processed { - id: blake2_256(b"weight=5"), + id: blake2_256(b"weight=5").into(), origin, weight_used: 5.into_weight(), success: true, @@ -1662,3 +1662,174 @@ fn integrity_test_checks_service_weight() { } }); } + +/// Test for . +#[test] +fn regression_issue_2319() { + build_and_execute::(|| { + Callback::set(Box::new(|_, _| { + MessageQueue::enqueue_message(mock_helpers::msg("anothermessage"), There); + })); + + use MessageOrigin::*; + MessageQueue::enqueue_message(msg("callback=0"), Here); + + // while servicing queue Here, "anothermessage" of origin There is enqueued in + // "firstmessage"'s process_message + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(b"callback=0".to_vec(), Here)]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + // It used to fail here but got fixed. + assert_eq!(MessagesProcessed::take(), vec![(b"anothermessage".to_vec(), There)]); + }); +} + +/// Enqueueing a message from within `service_queues` works. +#[test] +fn recursive_enqueue_works() { + build_and_execute::(|| { + Callback::set(Box::new(|o, i| match i { + 0 => { + MessageQueue::enqueue_message(msg(&format!("callback={}", 1)), *o); + }, + 1 => { + for _ in 0..100 { + MessageQueue::enqueue_message(msg(&format!("callback={}", 2)), *o); + } + for i in 0..100 { + MessageQueue::enqueue_message(msg(&format!("callback={}", 3)), i.into()); + } + }, + 2 | 3 => { + MessageQueue::enqueue_message(msg(&format!("callback={}", 4)), *o); + }, + 4 => (), + _ => unreachable!(), + })); + + MessageQueue::enqueue_message(msg("callback=0"), MessageOrigin::Here); + + for _ in 0..402 { + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + } + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); + + assert_eq!(MessagesProcessed::take().len(), 402); + }); +} + +/// Calling `service_queues` from within `service_queues` is forbidden. +#[test] +fn recursive_service_is_forbidden() { + use MessageOrigin::*; + build_and_execute::(|| { + Callback::set(Box::new(|_, _| { + MessageQueue::enqueue_message(msg("m1"), There); + // This call will fail since it is recursive. But it will not mess up the state. + assert_storage_noop!(MessageQueue::service_queues(10.into_weight())); + MessageQueue::enqueue_message(msg("m2"), There); + })); + + for _ in 0..5 { + MessageQueue::enqueue_message(msg("callback=0"), Here); + MessageQueue::service_queues(3.into_weight()); + + // All three messages are correctly processed. + assert_eq!( + MessagesProcessed::take(), + vec![ + (b"callback=0".to_vec(), Here), + (b"m1".to_vec(), There), + (b"m2".to_vec(), There) + ] + ); + } + }); +} + +/// Calling `service_queues` from within `service_queues` is forbidden. +#[test] +fn recursive_overweight_while_service_is_forbidden() { + use MessageOrigin::*; + build_and_execute::(|| { + Callback::set(Box::new(|_, _| { + // Check that the message was permanently overweight. + assert_last_event::( + Event::OverweightEnqueued { + id: blake2_256(b"weight=10"), + origin: There, + message_index: 0, + page_index: 0, + } + .into(), + ); + // This call will fail since it is recursive. But it will not mess up the state. + assert_noop!( + ::execute_overweight( + 10.into_weight(), + (There, 0, 0) + ), + ExecuteOverweightError::RecursiveDisallowed + ); + })); + + MessageQueue::enqueue_message(msg("weight=10"), There); + MessageQueue::enqueue_message(msg("callback=0"), Here); + + // Mark it as permanently overweight. + MessageQueue::service_queues(5.into_weight()); + assert_ok!(::execute_overweight( + 10.into_weight(), + (There, 0, 0) + )); + }); +} + +/// Calling `reap_page` from within `service_queues` is forbidden. +#[test] +fn recursive_reap_page_is_forbidden() { + use MessageOrigin::*; + build_and_execute::(|| { + Callback::set(Box::new(|_, _| { + // This call will fail since it is recursive. But it will not mess up the state. + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::RecursiveDisallowed); + })); + + // Create 10 pages more than the stale limit. + let n = (MaxStale::get() + 10) as usize; + for _ in 0..n { + MessageQueue::enqueue_message(msg("weight=2"), Here); + } + + // Mark all pages as stale since their message is permanently overweight. + MessageQueue::service_queues(1.into_weight()); + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + + assert_last_event::(Event::PageReaped { origin: Here, index: 0 }.into()); + }); +} + +#[test] +fn with_service_mutex_works() { + let mut called = 0; + with_service_mutex(|| called = 1).unwrap(); + assert_eq!(called, 1); + + // The outer one is fine but the inner one errors. + with_service_mutex(|| with_service_mutex(|| unreachable!())) + .unwrap() + .unwrap_err(); + with_service_mutex(|| with_service_mutex(|| unreachable!()).unwrap_err()).unwrap(); + with_service_mutex(|| { + with_service_mutex(|| unreachable!()).unwrap_err(); + with_service_mutex(|| unreachable!()).unwrap_err(); + called = 2; + }) + .unwrap(); + assert_eq!(called, 2); + + // Still works. + with_service_mutex(|| called = 3).unwrap(); + assert_eq!(called, 3); +} diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index de5f7411015fa155e89694dabfb7dd101a5ec7f6..ea8b1b00e49c83550e26e587c725022a19a21662 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,7 +22,7 @@ frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", default-features = false, features = ["derive"] } 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 f1ff19c0e3432226fc8d24b92a279c77ac448af4..40b0f4973a8db275cfc964fe2083e84dd0b76569 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME multi-signature dispatch pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index 19814d67d798eab43daf2d4ddfd66c2b1042ce89..355bb2a5d3e7ffe65f40c54c01457f29929cea0a 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to convert non-fungible to fungible tokens." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nfts/Cargo.toml b/substrate/frame/nfts/Cargo.toml index a70b55bc45e7ffeed7ed95a788ee460eb5b4b1f1..0d3f542c55266c0bf68333119d533860f840e945 100644 --- a/substrate/frame/nfts/Cargo.toml +++ b/substrate/frame/nfts/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME NFTs pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nfts/runtime-api/Cargo.toml b/substrate/frame/nfts/runtime-api/Cargo.toml index 8e1424a588add3e6e016cc0dbc596f1903b87bb4..8eb6726552bb6c3142a375401dac9473746aa4a9 100644 --- a/substrate/frame/nfts/runtime-api/Cargo.toml +++ b/substrate/frame/nfts/runtime-api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Runtime API for the FRAME NFTs pallet." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nfts/src/features/create_delete_collection.rs b/substrate/frame/nfts/src/features/create_delete_collection.rs index e343ad18e504f68991bbbac2df1ffa077e836222..f03df7fdd4f089509c87374164b5e1dc0d31e093 100644 --- a/substrate/frame/nfts/src/features/create_delete_collection.rs +++ b/substrate/frame/nfts/src/features/create_delete_collection.rs @@ -66,7 +66,13 @@ impl, I: 'static> Pallet { CollectionConfigOf::::insert(&collection, config); CollectionAccount::::insert(&owner, &collection, ()); + Self::deposit_event(event); + + if let Some(max_supply) = config.max_supply { + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + } + Ok(()) } diff --git a/substrate/frame/nfts/src/features/transfer.rs b/substrate/frame/nfts/src/features/transfer.rs index 0471bd67b29164358069d27c2239a2010795b7d0..bba834483e15f64f0c26b3fb7d1ff230ed6db45a 100644 --- a/substrate/frame/nfts/src/features/transfer.rs +++ b/substrate/frame/nfts/src/features/transfer.rs @@ -124,10 +124,10 @@ impl, I: 'static> Pallet { pub(crate) fn do_transfer_ownership( origin: T::AccountId, collection: T::CollectionId, - owner: T::AccountId, + new_owner: T::AccountId, ) -> DispatchResult { // Check if the new owner is acceptable based on the collection's acceptance settings. - let acceptable_collection = OwnershipAcceptance::::get(&owner); + let acceptable_collection = OwnershipAcceptance::::get(&new_owner); ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); // Try to retrieve and mutate the collection details. @@ -135,27 +135,28 @@ impl, I: 'static> Pallet { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; // Check if the `origin` is the current owner of the collection. ensure!(origin == details.owner, Error::::NoPermission); - if details.owner == owner { + if details.owner == new_owner { return Ok(()) } // Move the deposit to the new owner. T::Currency::repatriate_reserved( &details.owner, - &owner, + &new_owner, details.owner_deposit, Reserved, )?; // Update account ownership information. CollectionAccount::::remove(&details.owner, &collection); - CollectionAccount::::insert(&owner, &collection, ()); + CollectionAccount::::insert(&new_owner, &collection, ()); - details.owner = owner.clone(); - OwnershipAcceptance::::remove(&owner); + details.owner = new_owner.clone(); + OwnershipAcceptance::::remove(&new_owner); + frame_system::Pallet::::dec_consumers(&new_owner); // Emit `OwnerChanged` event. - Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Self::deposit_event(Event::OwnerChanged { collection, new_owner }); Ok(()) }) } @@ -172,8 +173,8 @@ impl, I: 'static> Pallet { who: T::AccountId, maybe_collection: Option, ) -> DispatchResult { - let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_collection.is_some()) { + let exists = OwnershipAcceptance::::contains_key(&who); + match (exists, maybe_collection.is_some()) { (false, true) => { frame_system::Pallet::::inc_consumers(&who)?; }, diff --git a/substrate/frame/nfts/src/lib.rs b/substrate/frame/nfts/src/lib.rs index 92b27432ab215ced08a8dd92ddff40f3db38415f..a7d505e2e397dbca5f547ed9ed1a7c3cc4235aa5 100644 --- a/substrate/frame/nfts/src/lib.rs +++ b/substrate/frame/nfts/src/lib.rs @@ -1153,11 +1153,11 @@ pub mod pallet { pub fn transfer_ownership( origin: OriginFor, collection: T::CollectionId, - owner: AccountIdLookupOf, + new_owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; - let owner = T::Lookup::lookup(owner)?; - Self::do_transfer_ownership(origin, collection, owner) + let new_owner = T::Lookup::lookup(new_owner)?; + Self::do_transfer_ownership(origin, collection, new_owner) } /// Change the Issuer, Admin and Freezer of a collection. diff --git a/substrate/frame/nfts/src/tests.rs b/substrate/frame/nfts/src/tests.rs index aeebf51b7c78a05e1303dfed0722bd9095aafe32..9e521537534fab31c4626c21ecf06e3df366c01b 100644 --- a/substrate/frame/nfts/src/tests.rs +++ b/substrate/frame/nfts/src/tests.rs @@ -614,8 +614,13 @@ fn transfer_owner_should_work() { Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2)), Error::::Unaccepted ); + assert_eq!(System::consumers(&account(2)), 0); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0))); + assert_eq!(System::consumers(&account(2)), 1); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2))); + assert_eq!(System::consumers(&account(2)), 1); // one consumer is added due to deposit repatriation assert_eq!(collections(), vec![(account(2), 0)]); assert_eq!(Balances::total_balance(&account(1)), 98); @@ -2191,6 +2196,10 @@ fn max_supply_should_work() { default_collection_config() )); assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); + assert!(!events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); assert_ok!(Nfts::set_collection_max_supply( RuntimeOrigin::signed(user_id.clone()), @@ -2242,9 +2251,31 @@ fn max_supply_should_work() { None )); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id.clone()), collection_id, 2, user_id, None), + Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 2, + user_id.clone(), + None + ), Error::::MaxSupplyReached ); + + // validate the event gets emitted when we set the max supply on collection create + let collection_id = 1; + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + CollectionConfig { max_supply: Some(max_supply), ..default_collection_config() } + )); + assert_eq!( + CollectionConfigOf::::get(collection_id).unwrap().max_supply, + Some(max_supply) + ); + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); }); } diff --git a/substrate/frame/nicks/Cargo.toml b/substrate/frame/nicks/Cargo.toml index 8636ac34a136dbef160e6de2c5b249683864f835..7d43f64cfe23c0ad37b0c75c312b9e2f56bb0c93 100644 --- a/substrate/frame/nicks/Cargo.toml +++ b/substrate/frame/nicks/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for nick management" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nis/Cargo.toml b/substrate/frame/nis/Cargo.toml index ec251ef6536e6be785c393ce35a6b5d99aac1cdd..f95ebc5864c889a73e82c9f878f33e95b157a4a3 100644 --- a/substrate/frame/nis/Cargo.toml +++ b/substrate/frame/nis/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for rewarding account freezing." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index abc03a8b0f4c441496cb8c4b3cf0936ebd5f2823..46fc0b34514e46275a1361b8e0dee8186ec0a2c4 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet for node authorization" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index bc24deb6f15df8cb6ed15abcd5ae897b53c41347..00c90b414dece3ae9fbc3b7a9551f4bcbb746aff 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME nomination pools pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/benchmarking/Cargo.toml b/substrate/frame/nomination-pools/benchmarking/Cargo.toml index d522dff82ba93abbb2fd0458973784dfcd62f7ee..8a4ee07dd744947fa28fc3cf664f422fd0ca598e 100644 --- a/substrate/frame/nomination-pools/benchmarking/Cargo.toml +++ b/substrate/frame/nomination-pools/benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME nomination pools pallet benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index 095df219dc842ffaf376613592ff58406e61cdbc..c58a66f6163aff113ab934c00b495e951052164e 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -119,6 +119,7 @@ impl pallet_staking::Config for Runtime { type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; type EventListeners = Pools; diff --git a/substrate/frame/nomination-pools/fuzzer/Cargo.toml b/substrate/frame/nomination-pools/fuzzer/Cargo.toml index b9d0a6197f8de8fbc1699ddadb2f66d5c29e1160..52f49b28457c26c3c247dd4c137565cd9564dcc8 100644 --- a/substrate/frame/nomination-pools/fuzzer/Cargo.toml +++ b/substrate/frame/nomination-pools/fuzzer/Cargo.toml @@ -10,6 +10,9 @@ description = "Fuzzer for fixed point arithmetic primitives." documentation = "https://docs.rs/sp-arithmetic-fuzzer" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/runtime-api/Cargo.toml b/substrate/frame/nomination-pools/runtime-api/Cargo.toml index 86d1496a41bbab276f892df5bf724b0a671ff1f8..12a897cc6b6fa16761c94f5d2a3c3e5b5892d790 100644 --- a/substrate/frame/nomination-pools/runtime-api/Cargo.toml +++ b/substrate/frame/nomination-pools/runtime-api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Runtime API for nomination-pools FRAME pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index f191126fbdd4186b2486cbbb9bf025108b6427dd..c538f12e20bf07179ef014408a8d0faeb222971c 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -460,7 +460,7 @@ pub enum ClaimPermission { PermissionlessCompound, /// Anyone can withdraw rewards on a pool member's behalf. PermissionlessWithdraw, - /// Anyone can withdraw and compound rewards on a member's behalf. + /// Anyone can withdraw and compound rewards on a pool member's behalf. PermissionlessAll, } @@ -2955,9 +2955,12 @@ impl Pallet { }, (false, false) => { // Equivalent to (current_points / current_balance) * new_funds - balance(u256(current_points).saturating_mul(u256(new_funds))) - // We check for zero above - .div(current_balance) + balance( + u256(current_points) + .saturating_mul(u256(new_funds)) + // We check for zero above + .div(u256(current_balance)), + ) }, } } @@ -3467,7 +3470,13 @@ impl Pallet { /// Check if any pool have an incorrect amount of ED frozen. /// /// This can happen if the ED has changed since the pool was created. - #[cfg(any(feature = "try-runtime", feature = "runtime-benchmarks", test, debug_assertions))] + #[cfg(any( + feature = "try-runtime", + feature = "runtime-benchmarks", + feature = "fuzzing", + test, + debug_assertions + ))] pub fn check_ed_imbalance() -> Result<(), DispatchError> { let mut failed: u32 = 0; BondedPools::::iter_keys().for_each(|id| { diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 7fe1e704bb13c3bae041e5fa44bd44545e58a204..657b50e8594aa4f41bacacc301a6049ea703e30b 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -292,6 +292,28 @@ mod bonded_pool { assert_ok!(pool.ok_to_join()); }); } + + #[test] + fn points_and_balance_conversions_are_safe() { + ExtBuilder::default().build_and_execute(|| { + let bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + commission: Commission::default(), + member_counter: 1, + points: u128::MAX, + roles: DEFAULT_ROLES, + state: PoolState::Open, + }, + }; + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), u128::MAX); + + // Max out the points and balance of the pool and make sure the conversion works as + // expected and does not overflow. + assert_eq!(bonded_pool.balance_to_point(u128::MAX), u128::MAX); + assert_eq!(bonded_pool.points_to_balance(u128::MAX), u128::MAX); + }) + } } mod reward_pool { diff --git a/substrate/frame/nomination-pools/test-staking/Cargo.toml b/substrate/frame/nomination-pools/test-staking/Cargo.toml index f0558f8314258990b3501c48f3a9b193e74c56ee..845535ae04f567bbe5fa4c4c7f22854b077176b5 100644 --- a/substrate/frame/nomination-pools/test-staking/Cargo.toml +++ b/substrate/frame/nomination-pools/test-staking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME nomination pools pallet tests with the staking pallet" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-staking/src/mock.rs index ee24b53db0676ab92729d6d9ae19849c47e8c3d9..491cd619161981f6dd8e066c96a7a7141dff834b 100644 --- a/substrate/frame/nomination-pools/test-staking/src/mock.rs +++ b/substrate/frame/nomination-pools/test-staking/src/mock.rs @@ -134,6 +134,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type EventListeners = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 0f153d9eca6403beec8db6c5f6833f7c934323f3..cda247325ef0559c9e32eca4e6e8d1bb011a5b85 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME offences pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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 4de239296a9f7260cf05e6b1f68b30cd952ad12c..cddbd6aa4d5ebebd1b259d2b8b18cc9b70ba1a6d 100644 --- a/substrate/frame/offences/benchmarking/Cargo.toml +++ b/substrate/frame/offences/benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME offences pallet benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index 62ce1990112a278ecd216899ce7ff4e74a49886d..1d642b9b4982a9b3374018daa46214064178f35d 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -185,6 +185,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/paged-list/Cargo.toml b/substrate/frame/paged-list/Cargo.toml index 676e1866a434604133b3c82df30a27b803cd700f..2370f84898ba12e905f459d8e5c3a5455b806ef6 100644 --- a/substrate/frame/paged-list/Cargo.toml +++ b/substrate/frame/paged-list/Cargo.toml @@ -8,6 +8,9 @@ edition.workspace = true license = "Apache-2.0" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/paged-list/fuzzer/Cargo.toml b/substrate/frame/paged-list/fuzzer/Cargo.toml index d659037381317586927d6c063e26266feb58b51f..5c245cc72c713975459eb025bea3464e3b819bbb 100644 --- a/substrate/frame/paged-list/fuzzer/Cargo.toml +++ b/substrate/frame/paged-list/fuzzer/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Fuzz storage types of pallet-paged-list" publish = false +[lints] +workspace = true + [[bin]] name = "pallet-paged-list-fuzzer" path = "src/paged_list.rs" diff --git a/substrate/frame/paged-list/src/paged_list.rs b/substrate/frame/paged-list/src/paged_list.rs index beea8ecc64409f9f199a94b1f8c3e0a2782e4a11..75467f3ceeb582e04c8e02eb7629637358a760e1 100644 --- a/substrate/frame/paged-list/src/paged_list.rs +++ b/substrate/frame/paged-list/src/paged_list.rs @@ -407,13 +407,11 @@ where #[allow(dead_code)] pub(crate) mod mock { pub use super::*; - pub use frame_support::{ - parameter_types, - storage::{types::ValueQuery, StorageList as _}, - StorageNoopGuard, - }; - pub use sp_io::{hashing::twox_128, TestExternalities}; - pub use sp_metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}; + pub use frame_support::parameter_types; + #[cfg(test)] + pub use frame_support::{storage::StorageList as _, StorageNoopGuard}; + #[cfg(test)] + pub use sp_io::TestExternalities; parameter_types! { pub const ValuesPerNewPage: u32 = 5; diff --git a/substrate/frame/preimage/Cargo.toml b/substrate/frame/preimage/Cargo.toml index 1806976ac9635b353c545871a055800e95cc8c89..2aa21d2a7136ec7e31bbb42fe7fe8e9d7010536f 100644 --- a/substrate/frame/preimage/Cargo.toml +++ b/substrate/frame/preimage/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet for storing preimages of hashes" +[lints] +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"] } diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index 00a2692a820ac3c00b93f2be441b2f8c8fb25edf..fd163e71bc1b062848c9c3700c21bb101f208de1 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME proxying pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/ranked-collective/Cargo.toml b/substrate/frame/ranked-collective/Cargo.toml index 145eff3b0eee2199510ffbdd1090db2004a1938e..39075b2abf911cc5e4c5a979d0df03e8a7d7eeef 100644 --- a/substrate/frame/ranked-collective/Cargo.toml +++ b/substrate/frame/ranked-collective/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/recovery/Cargo.toml b/substrate/frame/recovery/Cargo.toml index d2cd2d1a4ca8a4b139273936841b0c60a18ac4c3..6afd494bf7e1cd27d5d3d8f7f8f0960b70092251 100644 --- a/substrate/frame/recovery/Cargo.toml +++ b/substrate/frame/recovery/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME account recovery pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/recovery/src/benchmarking.rs b/substrate/frame/recovery/src/benchmarking.rs index 2deb55bb69f24fb1ee3abeca44bbb356d682a7f4..72f77336212dd6b8c7fb64d7b12a32fd0d9dfa44 100644 --- a/substrate/frame/recovery/src/benchmarking.rs +++ b/substrate/frame/recovery/src/benchmarking.rs @@ -190,7 +190,7 @@ benchmarks! { let recovery_config = RecoveryConfig { delay_period: DEFAULT_DELAY.into(), - deposit: total_deposit.clone(), + deposit: total_deposit, friends: bounded_friends.clone(), threshold: n as u16, }; @@ -243,7 +243,7 @@ benchmarks! { let recovery_config = RecoveryConfig { delay_period: 0u32.into(), - deposit: total_deposit.clone(), + deposit: total_deposit, friends: bounded_friends.clone(), threshold: n as u16, }; @@ -294,7 +294,7 @@ benchmarks! { let recovery_config = RecoveryConfig { delay_period: DEFAULT_DELAY.into(), - deposit: total_deposit.clone(), + deposit: total_deposit, friends: bounded_friends.clone(), threshold: n as u16, }; @@ -342,7 +342,7 @@ benchmarks! { let recovery_config = RecoveryConfig { delay_period: DEFAULT_DELAY.into(), - deposit: total_deposit.clone(), + deposit: total_deposit, friends: bounded_friends.clone(), threshold: n as u16, }; diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index 1747d0ac22b4fb742f0f0ef3194a89d97ea9882f..d38be0d283e9bc9a98b7b5672e7c57b4b66bb752 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for inclusive on-chain decisions" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,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.193", features = ["derive"], optional = true } +serde = { version = "1.0.195", features = ["derive"], optional = 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 } diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index 9b0c6870d056e7d5640e626841bf76378b9fae6a..1570d2dba27d821ef69edb096c40b00d066ab0d2 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "Remark storage pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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/root-offences/Cargo.toml b/substrate/frame/root-offences/Cargo.toml index a17bd51684e0b548d00d0fa77342205f8fbba8f1..0f3d3a2883d5a5d92686e0f7fda014d146e4a420 100644 --- a/substrate/frame/root-offences/Cargo.toml +++ b/substrate/frame/root-offences/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME root offences pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 5e04e9abc1358b836242ff84c1fba694e39b366a..c0c83dd08d243777ab6353aade7a8c8859da45af 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -188,6 +188,7 @@ impl pallet_staking::Config for Test { type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; diff --git a/substrate/frame/root-testing/Cargo.toml b/substrate/frame/root-testing/Cargo.toml index f4e914c86b10d7a224438b5dec39a6a37666d8f9..78aed99a56d712f65d458bd3956070b96c8e6f08 100644 --- a/substrate/frame/root-testing/Cargo.toml +++ b/substrate/frame/root-testing/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME root testing pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/safe-mode/Cargo.toml b/substrate/frame/safe-mode/Cargo.toml index d33e0b7144acc2f5c40e57d837f1689d1ddd549f..f86332483c4a740265a7f660b739700090fa567b 100644 --- a/substrate/frame/safe-mode/Cargo.toml +++ b/substrate/frame/safe-mode/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME safe-mode pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 18636a60cdb94d12e741bcf9cac92d4849e274b0..929151a9c2082c60419d55c47c892b15e26acf15 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Paymaster" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 745297bd416d0db826ba1da35daae07a03b6a729..ad4c0ba12f0b10f60f18c853975637355b7172c6 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -10,6 +10,9 @@ description = "Consensus extension module for Sassafras consensus." readme = "README.md" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/sassafras/src/benchmarking.rs b/substrate/frame/sassafras/src/benchmarking.rs index 95a2b4bbce4e5c1ae839db7f344e63aeb7a44ad1..921f2f0793d3ce9072aa762deeb97ad7b00799d5 100644 --- a/substrate/frame/sassafras/src/benchmarking.rs +++ b/substrate/frame/sassafras/src/benchmarking.rs @@ -260,7 +260,7 @@ mod benchmarks { // Update metadata let mut meta = TicketsMeta::::get(); meta.unsorted_tickets_count = tickets_count; - TicketsMeta::::set(meta.clone()); + TicketsMeta::::set(meta); log::debug!(target: LOG_TARGET, "Before sort: {:?}", meta); #[block] diff --git a/substrate/frame/sassafras/src/lib.rs b/substrate/frame/sassafras/src/lib.rs index b6f405f56549428dfe12684e36013c2489ffd790..0ee8657489b7f7321d24b209dfddcd086cf1cf37 100644 --- a/substrate/frame/sassafras/src/lib.rs +++ b/substrate/frame/sassafras/src/lib.rs @@ -272,11 +272,11 @@ pub mod pallet { #[pallet::storage] pub type RingVerifierData = StorageValue<_, vrf::RingVerifierData>; - /// Slot claim vrf-preoutput used to generate per-slot randomness. + /// Slot claim VRF pre-output used to generate per-slot randomness. /// /// The value is ephemeral and is cleared on block finalization. #[pallet::storage] - pub(crate) type ClaimTemporaryData = StorageValue<_, vrf::VrfOutput>; + pub(crate) type ClaimTemporaryData = StorageValue<_, vrf::VrfPreOutput>; /// Genesis configuration for Sassafras protocol. #[pallet::genesis_config] @@ -324,12 +324,12 @@ pub mod pallet { Self::post_genesis_initialize(claim.slot); } - let randomness_output = claim + let randomness_pre_output = claim .vrf_signature - .outputs + .pre_outputs .get(0) - .expect("Valid claim must have vrf signature; qed"); - ClaimTemporaryData::::put(randomness_output); + .expect("Valid claim must have VRF signature; qed"); + ClaimTemporaryData::::put(randomness_pre_output); let trigger_weight = T::EpochChangeTrigger::trigger::(block_num); @@ -346,9 +346,9 @@ pub mod pallet { CurrentSlot::::get(), EpochIndex::::get(), ); - let randomness_output = ClaimTemporaryData::::take() + let randomness_pre_output = ClaimTemporaryData::::take() .expect("Unconditionally populated in `on_initialize`; `on_finalize` is always called after; qed"); - let randomness = randomness_output + let randomness = randomness_pre_output .make_bytes::(RANDOMNESS_VRF_CONTEXT, &randomness_input); Self::deposit_slot_randomness(&randomness); @@ -422,15 +422,15 @@ pub mod pallet { for ticket in tickets { debug!(target: LOG_TARGET, "Checking ring proof"); - let Some(ticket_id_output) = ticket.signature.outputs.get(0) else { - debug!(target: LOG_TARGET, "Missing ticket vrf output from ring signature"); + let Some(ticket_id_pre_output) = ticket.signature.pre_outputs.get(0) else { + debug!(target: LOG_TARGET, "Missing ticket VRF pre-output from ring signature"); continue }; let ticket_id_input = vrf::ticket_id_input(&randomness, ticket.body.attempt_idx, epoch_idx); // Check threshold constraint - let ticket_id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_output); + let ticket_id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output); if ticket_id >= ticket_threshold { debug!(target: LOG_TARGET, "Ignoring ticket over threshold ({:032x} >= {:032x})", ticket_id, ticket_threshold); continue diff --git a/substrate/frame/sassafras/src/mock.rs b/substrate/frame/sassafras/src/mock.rs index b700207c4991b8d6d51c9295814576f6fd2e57ee..5aca815cc2140a1f04f05c55e60c2d77d69bcb7e 100644 --- a/substrate/frame/sassafras/src/mock.rs +++ b/substrate/frame/sassafras/src/mock.rs @@ -190,9 +190,9 @@ pub fn make_ticket_body(attempt_idx: u32, pair: &AuthorityPair) -> (TicketId, Ti let randomness = Sassafras::next_randomness(); let ticket_id_input = vrf::ticket_id_input(&randomness, attempt_idx, epoch); - let ticket_id_output = pair.as_inner_ref().vrf_output(&ticket_id_input); + let ticket_id_pre_output = pair.as_inner_ref().vrf_pre_output(&ticket_id_input); - let id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_output); + let id = vrf::make_ticket_id(&ticket_id_input, &ticket_id_pre_output); // Make a dummy ephemeral public that hopefully is unique within one test instance. // In the tests, the values within the erased public are just used to compare diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml index 1dd2e53d05474d174e0d3f209459e6a528cc4d52..c27276c607e6c44828388e5e4346cfe82bc051af 100644 --- a/substrate/frame/scheduler/Cargo.toml +++ b/substrate/frame/scheduler/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME Scheduler pallet" readme = "README.md" +[lints] +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 } diff --git a/substrate/frame/scheduler/src/migration.rs b/substrate/frame/scheduler/src/migration.rs index 9c8b0da03fc0b6368b2734c07db164a4ccc3b683..76e2e04b49cc6fc7d9f886090de4df44911c0333 100644 --- a/substrate/frame/scheduler/src/migration.rs +++ b/substrate/frame/scheduler/src/migration.rs @@ -105,7 +105,7 @@ pub mod v3 { // Check that no agenda overflows `MaxScheduledPerBlock`. let max_scheduled_per_block = T::MaxScheduledPerBlock::get() as usize; for (block_number, agenda) in Agenda::::iter() { - if agenda.iter().cloned().filter_map(|s| s).count() > max_scheduled_per_block { + if agenda.iter().cloned().flatten().count() > max_scheduled_per_block { log::error!( target: TARGET, "Would truncate agenda of block {:?} from {} items to {} items.", @@ -119,7 +119,7 @@ pub mod v3 { // Check that bounding the calls will not overflow `MAX_LENGTH`. let max_length = T::Preimages::MAX_LENGTH as usize; for (block_number, agenda) in Agenda::::iter() { - for schedule in agenda.iter().cloned().filter_map(|s| s) { + for schedule in agenda.iter().cloned().flatten() { match schedule.call { frame_support::traits::schedule::MaybeHashed::Value(call) => { let l = call.using_encoded(|c| c.len()); @@ -362,7 +362,7 @@ mod test { Some(ScheduledV3Of:: { maybe_id: Some(vec![i as u8; 320]), priority: 123, - call: MaybeHashed::Hash(undecodable_hash.clone()), + call: MaybeHashed::Hash(undecodable_hash), maybe_periodic: Some((4u64, 20)), origin: root(), _phantom: PhantomData::::default(), diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index b6eb1d044fa2321935b0bbd74ae6e89b1114bf3c..4edcfa0a7bfd8b8b48d408a99567e20deb5c0853 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as scheduler; use frame_support::{ - ord_parameter_types, parameter_types, + derive_impl, ord_parameter_types, parameter_types, traits::{ ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize, }, @@ -118,6 +118,8 @@ parameter_types! { Weight::from_parts(2_000_000_000_000, u64::MAX), ); } + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl system::Config for Test { type BaseCallFilter = BaseFilter; type BlockWeights = BlockWeights; diff --git a/substrate/frame/scored-pool/Cargo.toml b/substrate/frame/scored-pool/Cargo.toml index 8293c81f590c328a858419ce638cdba9b54056a8..7a534ddd79d1a54264cb39493cae9c165068c9f5 100644 --- a/substrate/frame/scored-pool/Cargo.toml +++ b/substrate/frame/scored-pool/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for scored pools" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index 0a997f6ddb3e49a8e64d417f15ffa75374511978..4589dbb427a01697fc9749df0f65ae04c80b637c 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME sessions pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/session/benchmarking/Cargo.toml b/substrate/frame/session/benchmarking/Cargo.toml index db2b8b72209d190a47a15e7da02b656c79836c7f..16f85048d8d281366776205b15dccb304a7a1464 100644 --- a/substrate/frame/session/benchmarking/Cargo.toml +++ b/substrate/frame/session/benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME sessions pallet benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 5c00d4bb4919ca20a325c2773558cbd50d6c68a4..e1744fa43abbb7e109c9a30d2466b36f6c06fb89 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -179,6 +179,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = ConstU32<84>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 045993290afda35bd41c06ccb75101735dbc054b..46b4f7a7d6621b1d44d3edc92968168d9960fa2f 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME society pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/society/src/lib.rs b/substrate/frame/society/src/lib.rs index ca8d96e193c843e5a6bad59cf8807e2eb16260bf..99dd580a4035ed138b494d827956435e597ebe1c 100644 --- a/substrate/frame/society/src/lib.rs +++ b/substrate/frame/society/src/lib.rs @@ -1433,8 +1433,8 @@ impl, I: 'static> Pallet { let weight_root = rank + 1; let weight = weight_root * weight_root; match approve { - true => tally.approvals.saturating_accrue(1), - false => tally.rejections.saturating_accrue(1), + true => tally.approvals.saturating_accrue(weight), + false => tally.rejections.saturating_accrue(weight), } Vote { approve, weight } } diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index ea2afef3b32b5b7ad188742408befe3fff29a338..2163575a86080843e7061ae39a8ca123c05303cc 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -720,7 +720,7 @@ fn unvouch_works() { // But their pick doesn't resign (yet). conclude_intake(false, None); // Voting still happening and voucher cannot unvouch. - assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 1))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 4))]); assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // Candidate gives in and resigns. @@ -836,72 +836,72 @@ fn founder_and_head_cannot_be_removed() { fn challenges_work() { EnvBuilder::new().execute(|| { // Add some members - place_members([20, 30, 40]); + place_members([20, 30, 40, 50]); // Votes are empty - assert_eq!(DefenderVotes::::get(0, 10), None); assert_eq!(DefenderVotes::::get(0, 20), None); assert_eq!(DefenderVotes::::get(0, 30), None); assert_eq!(DefenderVotes::::get(0, 40), None); + assert_eq!(DefenderVotes::::get(0, 50), None); // Check starting point - assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); assert_eq!(Defending::::get(), None); - // 30 will be challenged during the challenge rotation + // 40 will be challenged during the challenge rotation next_challenge(); - assert_eq!(Defending::::get().unwrap().0, 30); + assert_eq!(Defending::::get().unwrap().0, 40); // They can always free vote for themselves - assert_ok!(Society::defender_vote(Origin::signed(30), true)); + assert_ok!(Society::defender_vote(Origin::signed(40), true)); // If no one else votes, nothing happens next_challenge(); - assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); // Reset votes for last challenge assert_ok!(Society::cleanup_challenge(Origin::signed(0), 0, 10)); - // New challenge period - assert_eq!(Defending::::get().unwrap().0, 30); + // New challenge period, 20 is challenged + assert_eq!(Defending::::get().unwrap().0, 20); // Non-member cannot vote assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); // 3 people say accept, 1 reject - assert_ok!(Society::defender_vote(Origin::signed(10), true)); assert_ok!(Society::defender_vote(Origin::signed(20), true)); assert_ok!(Society::defender_vote(Origin::signed(30), true)); - assert_ok!(Society::defender_vote(Origin::signed(40), false)); + assert_ok!(Society::defender_vote(Origin::signed(40), true)); + assert_ok!(Society::defender_vote(Origin::signed(50), false)); next_challenge(); - // 30 survives - assert_eq!(members(), vec![10, 20, 30, 40]); + // 20 survives + assert_eq!(members(), vec![10, 20, 30, 40, 50]); // Reset votes for last challenge assert_ok!(Society::cleanup_challenge(Origin::signed(0), 1, 10)); // Votes are reset - assert_eq!(DefenderVotes::::get(0, 10), None); assert_eq!(DefenderVotes::::get(0, 20), None); assert_eq!(DefenderVotes::::get(0, 30), None); assert_eq!(DefenderVotes::::get(0, 40), None); + assert_eq!(DefenderVotes::::get(0, 50), None); - // One more time - assert_eq!(Defending::::get().unwrap().0, 30); + // One more time, 40 is challenged + assert_eq!(Defending::::get().unwrap().0, 40); // 2 people say accept, 2 reject - assert_ok!(Society::defender_vote(Origin::signed(10), true)); assert_ok!(Society::defender_vote(Origin::signed(20), true)); - assert_ok!(Society::defender_vote(Origin::signed(30), false)); + assert_ok!(Society::defender_vote(Origin::signed(30), true)); assert_ok!(Society::defender_vote(Origin::signed(40), false)); + assert_ok!(Society::defender_vote(Origin::signed(50), false)); next_challenge(); - // 30 is suspended - assert_eq!(members(), vec![10, 20, 40]); + // 40 is suspended + assert_eq!(members(), vec![10, 20, 30, 50]); assert_eq!( - SuspendedMembers::::get(30), - Some(MemberRecord { rank: 0, strikes: 0, vouching: None, index: 2 }) + SuspendedMembers::::get(40), + Some(MemberRecord { rank: 0, strikes: 0, vouching: None, index: 3 }) ); // Reset votes for last challenge assert_ok!(Society::cleanup_challenge(Origin::signed(0), 2, 10)); - // New defender is chosen - assert_eq!(Defending::::get().unwrap().0, 20); + // New defender is chosen, 30 is challenged + assert_eq!(Defending::::get().unwrap().0, 30); // Votes are reset - assert_eq!(DefenderVotes::::get(0, 10), None); assert_eq!(DefenderVotes::::get(0, 20), None); assert_eq!(DefenderVotes::::get(0, 30), None); assert_eq!(DefenderVotes::::get(0, 40), None); + assert_eq!(DefenderVotes::::get(0, 50), None); }); } @@ -1044,6 +1044,9 @@ fn vouching_handles_removed_member_with_candidate() { fn votes_are_working() { EnvBuilder::new().execute(|| { place_members([20]); + // Member 10 is rank 1 and Member 20 is rank 0 + assert_eq!(Members::::get(10).unwrap().rank, 1); + assert_eq!(Members::::get(20).unwrap().rank, 0); // Users make bids of various amounts assert_ok!(Society::bid(RuntimeOrigin::signed(50), 500)); assert_ok!(Society::bid(RuntimeOrigin::signed(40), 400)); @@ -1061,6 +1064,32 @@ fn votes_are_working() { assert_eq!(Votes::::get(30, 20), Some(Vote { approve: true, weight: 1 })); assert_eq!(Votes::::get(40, 10), Some(Vote { approve: true, weight: 4 })); assert_eq!(Votes::::get(50, 10), None); + + // Votes have correct weights on the tally + assert_eq!( + Candidates::::get(30).unwrap().tally, + Tally { approvals: 5, rejections: 0 } + ); + assert_eq!( + Candidates::::get(40).unwrap().tally, + Tally { approvals: 4, rejections: 0 } + ); + // Member 10 changes his vote for Candidate 30 + assert_ok!(Society::vote(Origin::signed(10), 30, false)); + // Assert the tally calculation is correct + assert_eq!( + Candidates::::get(30).unwrap().tally, + Tally { approvals: 1, rejections: 4 } + ); + // Member 10 changes his vote again + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + // Assert the tally is still correct + assert_eq!( + Candidates::::get(30).unwrap().tally, + Tally { approvals: 5, rejections: 0 } + ); + + // Finish intake conclude_intake(false, None); // Cleanup the candidacy assert_ok!(Society::cleanup_candidacy(Origin::signed(0), 30, 10)); @@ -1240,7 +1269,7 @@ fn waive_repay_works() { Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } ); - assert_eq!(Members::::get(10).unwrap().rank, 1); + assert_eq!(Members::::get(20).unwrap().rank, 1); assert_eq!(Balances::free_balance(20), 50); }); } diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 235ce01c42dcd0db6747fa118bbcddea4de5f326..2c6c03a8fbdaf72a1d7c462e99485f1f99f34c47 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true description = "FRAME pallet staking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } diff --git a/substrate/frame/staking/reward-curve/Cargo.toml b/substrate/frame/staking/reward-curve/Cargo.toml index d3a1f439cf908be322a6d02a20696ab361512c31..35bf240907a8f6b815307c2efeab461608c91d45 100644 --- a/substrate/frame/staking/reward-curve/Cargo.toml +++ b/substrate/frame/staking/reward-curve/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Reward Curve for FRAME staking pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.39", features = ["full", "visit"] } +syn = { version = "2.0.48", features = ["full", "visit"] } [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 001c2b6265660bc552593a50fae1df80b8169c82..80a27cc0f5340cdfc849503ec0399f39062769f4 100644 --- a/substrate/frame/staking/reward-fn/Cargo.toml +++ b/substrate/frame/staking/reward-fn/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Reward function for FRAME staking pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/staking/runtime-api/Cargo.toml b/substrate/frame/staking/runtime-api/Cargo.toml index 061124fd18459a794169519f6ecd9ae97751e644..b3fd4cfda017f2dff72aee122c44906d7db59d0e 100644 --- a/substrate/frame/staking/runtime-api/Cargo.toml +++ b/substrate/frame/staking/runtime-api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "RPC runtime API for transaction payment FRAME pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index c0c6a838aa595eba75af906304fec4943b8f493e..f1159c06aa11da252f4c448e47d8a968183ad93d 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -25,6 +25,7 @@ use codec::Decode; use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider}; use frame_support::{ pallet_prelude::*, + storage::bounded_vec::BoundedVec, traits::{Currency, Get, Imbalance, UnfilteredDispatchable}, }; use sp_runtime::{ @@ -249,7 +250,7 @@ benchmarks! { let original_bonded: BalanceOf = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; - T::Currency::deposit_into_existing(&stash, max_additional).unwrap(); + let _ = T::Currency::deposit_into_existing(&stash, max_additional).unwrap(); whitelist_account!(stash); }: _(RawOrigin::Signed(stash), max_additional) @@ -525,6 +526,39 @@ benchmarks! { assert_eq!(Invulnerables::::get().len(), v as usize); } + deprecate_controller_batch { + // We pass a dynamic number of controllers to the benchmark, up to + // `MaxControllersInDeprecationBatch`. + let i in 0 .. T::MaxControllersInDeprecationBatch::get(); + + let mut controllers: Vec<_> = vec![]; + let mut stashes: Vec<_> = vec![]; + for n in 0..i as u32 { + let (stash, controller) = create_unique_stash_controller::( + n, + 100, + RewardDestination::Staked, + false + )?; + controllers.push(controller); + stashes.push(stash); + } + let bounded_controllers: BoundedVec<_, T::MaxControllersInDeprecationBatch> = + BoundedVec::try_from(controllers.clone()).unwrap(); + }: _(RawOrigin::Root, bounded_controllers) + verify { + for n in 0..i as u32 { + let stash = &stashes[n as usize]; + let controller = &controllers[n as usize]; + // Ledger no longer keyed by controller. + assert_eq!(Ledger::::get(controller), None); + // Bonded now maps to the stash. + assert_eq!(Bonded::::get(stash), Some(stash.clone())); + // Ledger is now keyed by stash. + assert_eq!(Ledger::::get(stash).unwrap().stash, *stash); + } + } + force_unstake { // Slashing Spans let s in 0 .. MAX_SPANS; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 900b390f08691b8cddfe2ea2cbfc2332fe61b751..fa84568cc1f4390daec3295b8518960f34855842 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -124,6 +124,7 @@ parameter_types! { pub static SlashDeferDuration: EraIndex = 0; pub static Period: BlockNumber = 5; pub static Offset: BlockNumber = 0; + pub static MaxControllersInDeprecationBatch: u32 = 5900; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] @@ -337,6 +338,7 @@ impl crate::pallet::pallet::Config for Test { type NominationsQuota = WeightedNominationsQuota<16>; type MaxUnlockingChunks = MaxUnlockingChunks; type HistoryDepth = HistoryDepth; + type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = (StakeTracker, SlashListenerMock); type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 073bfce7be4071e543a1cfa196419c3324fb8f38..c7f397f7af115d9f7a34b919106bdd867b5bea08 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1897,7 +1897,7 @@ impl StakingInterface for Pallet { ) { let others = exposures .iter() - .map(|(who, value)| IndividualExposure { who: who.clone(), value: value.clone() }) + .map(|(who, value)| IndividualExposure { who: who.clone(), value: *value }) .collect::>(); let exposure = Exposure { total: Default::default(), own: Default::default(), others }; EraInfo::::set_exposure(*current_era, stash, exposure); diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index e62f2563a9bcaf85d85ff8f6ede002da0d42c115..6cc8a9a4ff1ba5f5b9796cbbcdca7d10bd85b76a 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -276,6 +276,9 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; + /// The maximum amount of controller accounts that can be deprecated in one call. + type MaxControllersInDeprecationBatch: Get; + /// Something that listens to staking updates and performs actions based on the data it /// receives. type EventListeners: OnStakingUpdate>; @@ -1329,7 +1332,7 @@ pub mod pallet { pub fn set_controller(origin: OriginFor) -> DispatchResult { let stash = ensure_signed(origin)?; - // the bonded map and ledger are mutated directly as this extrinsic is related to a + // The bonded map and ledger are mutated directly as this extrinsic is related to a // (temporary) passive migration. Self::ledger(StakingAccount::Stash(stash.clone())).map(|ledger| { let controller = ledger.controller() @@ -1337,10 +1340,9 @@ pub mod pallet { .ok_or(Error::::NotController)?; if controller == stash { - // stash is already its own controller. + // Stash is already its own controller. return Err(Error::::AlreadyPaired.into()) } - // update bond and ledger. >::remove(controller); >::insert(&stash, &stash); >::insert(&stash, ledger); @@ -1930,6 +1932,54 @@ pub mod pallet { Ok(Pays::No.into()) } + + /// Updates a batch of controller accounts to their corresponding stash account if they are + /// not the same. Ignores any controller accounts that do not exist, and does not operate if + /// the stash and controller are already the same. + /// + /// Effects will be felt instantly (as soon as this function is completed successfully). + /// + /// The dispatch origin must be `T::AdminOrigin`. + #[pallet::call_index(28)] + #[pallet::weight(T::WeightInfo::deprecate_controller_batch(controllers.len() as u32))] + pub fn deprecate_controller_batch( + origin: OriginFor, + controllers: BoundedVec, + ) -> DispatchResultWithPostInfo { + T::AdminOrigin::ensure_origin(origin)?; + + // Ignore controllers that do not exist or are already the same as stash. + let filtered_batch_with_ledger: Vec<_> = controllers + .iter() + .filter_map(|controller| { + let ledger = Self::ledger(StakingAccount::Controller(controller.clone())); + ledger.ok().map_or(None, |ledger| { + // If the controller `RewardDestination` is still the deprecated + // `Controller` variant, skip deprecating this account. + let payee_deprecated = Payee::::get(&ledger.stash) == { + #[allow(deprecated)] + RewardDestination::Controller + }; + + if ledger.stash != *controller && !payee_deprecated { + Some((controller.clone(), ledger)) + } else { + None + } + }) + }) + .collect(); + + // Update unique pairs. + for (controller, ledger) in filtered_batch_with_ledger { + let stash = ledger.stash.clone(); + + >::insert(&stash, &stash); + >::remove(controller); + >::insert(stash, ledger); + } + Ok(Some(T::WeightInfo::deprecate_controller_batch(controllers.len() as u32)).into()) + } } } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 932289feedd958a1580606385330dd32cde235f9..247fe7070822593bbc7563931020ea08864aada8 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -6405,7 +6405,7 @@ fn proportional_ledger_slash_works() { #[test] fn reducing_max_unlocking_chunks_abrupt() { // Concern is on validators only - // By Default 11, 10 are stash and ctrl and 21,20 + // By Default 11, 10 are stash and ctlr and 21,20 ExtBuilder::default().build_and_execute(|| { // given a staker at era=10 and MaxUnlockChunks set to 2 MaxUnlockingChunks::set(2); @@ -7066,6 +7066,183 @@ mod ledger { assert_eq!(Payee::::get(&21), RewardDestination::Stash); }) } + + #[test] + fn deprecate_controller_batch_works_full_weight() { + ExtBuilder::default().build_and_execute(|| { + // Given: + + let start = 1001; + let mut controllers: Vec<_> = vec![]; + for n in start..(start + MaxControllersInDeprecationBatch::get()).into() { + let ctlr: u64 = n.into(); + let stash: u64 = (n + 10000).into(); + + Ledger::::insert( + ctlr, + StakingLedger { + controller: None, + total: (10 + ctlr).into(), + active: (10 + ctlr).into(), + ..StakingLedger::default_from(stash) + }, + ); + Bonded::::insert(stash, ctlr); + Payee::::insert(stash, RewardDestination::Staked); + + controllers.push(ctlr); + } + + // When: + + let bounded_controllers: BoundedVec< + _, + ::MaxControllersInDeprecationBatch, + > = BoundedVec::try_from(controllers).unwrap(); + + // Only `AdminOrigin` can sign. + assert_noop!( + Staking::deprecate_controller_batch( + RuntimeOrigin::signed(2), + bounded_controllers.clone() + ), + BadOrigin + ); + + let result = + Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers); + assert_ok!(result); + assert_eq!( + result.unwrap().actual_weight.unwrap(), + ::WeightInfo::deprecate_controller_batch( + ::MaxControllersInDeprecationBatch::get() + ) + ); + + // Then: + + for n in start..(start + MaxControllersInDeprecationBatch::get()).into() { + let ctlr: u64 = n.into(); + let stash: u64 = (n + 10000).into(); + + // Ledger no longer keyed by controller. + assert_eq!(Ledger::::get(ctlr), None); + // Bonded now maps to the stash. + assert_eq!(Bonded::::get(stash), Some(stash)); + + // Ledger is now keyed by stash. + let ledger_updated = Ledger::::get(stash).unwrap(); + assert_eq!(ledger_updated.stash, stash); + + // Check `active` and `total` values match the original ledger set by controller. + assert_eq!(ledger_updated.active, (10 + ctlr).into()); + assert_eq!(ledger_updated.total, (10 + ctlr).into()); + } + }) + } + + #[test] + fn deprecate_controller_batch_works_half_weight() { + ExtBuilder::default().build_and_execute(|| { + // Given: + + let start = 1001; + let mut controllers: Vec<_> = vec![]; + for n in start..(start + MaxControllersInDeprecationBatch::get()).into() { + let ctlr: u64 = n.into(); + + // Only half of entries are unique pairs. + let stash: u64 = if n % 2 == 0 { (n + 10000).into() } else { ctlr }; + + Ledger::::insert( + ctlr, + StakingLedger { controller: None, ..StakingLedger::default_from(stash) }, + ); + Bonded::::insert(stash, ctlr); + Payee::::insert(stash, RewardDestination::Staked); + + controllers.push(ctlr); + } + + // When: + let bounded_controllers: BoundedVec< + _, + ::MaxControllersInDeprecationBatch, + > = BoundedVec::try_from(controllers.clone()).unwrap(); + + let result = + Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers); + assert_ok!(result); + assert_eq!( + result.unwrap().actual_weight.unwrap(), + ::WeightInfo::deprecate_controller_batch(controllers.len() as u32) + ); + + // Then: + + for n in start..(start + MaxControllersInDeprecationBatch::get()).into() { + let unique_pair = n % 2 == 0; + let ctlr: u64 = n.into(); + let stash: u64 = if unique_pair { (n + 10000).into() } else { ctlr }; + + // Side effect of migration for unique pair. + if unique_pair { + assert_eq!(Ledger::::get(ctlr), None); + } + // Bonded maps to the stash. + assert_eq!(Bonded::::get(stash), Some(stash)); + + // Ledger is keyed by stash. + let ledger_updated = Ledger::::get(stash).unwrap(); + assert_eq!(ledger_updated.stash, stash); + } + }) + } + + #[test] + fn deprecate_controller_batch_skips_unmigrated_controller_payees() { + ExtBuilder::default().build_and_execute(|| { + // Given: + + let stash: u64 = 1000; + let ctlr: u64 = 1001; + + Ledger::::insert( + ctlr, + StakingLedger { controller: None, ..StakingLedger::default_from(stash) }, + ); + Bonded::::insert(stash, ctlr); + #[allow(deprecated)] + Payee::::insert(stash, RewardDestination::Controller); + + // When: + + let bounded_controllers: BoundedVec< + _, + ::MaxControllersInDeprecationBatch, + > = BoundedVec::try_from(vec![ctlr]).unwrap(); + + let result = + Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers); + assert_ok!(result); + assert_eq!( + result.unwrap().actual_weight.unwrap(), + ::WeightInfo::deprecate_controller_batch(1 as u32) + ); + + // Then: + + // Esure deprecation did not happen. + assert_eq!(Ledger::::get(ctlr).is_some(), true); + + // Bonded still keyed by controller. + assert_eq!(Bonded::::get(stash), Some(ctlr)); + + // Ledger is still keyed by controller. + let ledger_updated = Ledger::::get(ctlr).unwrap(); + assert_eq!(ledger_updated.stash, stash); + }) + } } mod stake_tracker { diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index ae00509eaa84e49e613a315a4ba219d920b958d3..7c9a050016406a5ae5b8f98bcd98b0093858628a 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-11-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -66,6 +66,7 @@ pub trait WeightInfo { fn force_new_era() -> Weight; fn force_new_era_always() -> Weight; fn set_invulnerables(v: u32, ) -> Weight; + fn deprecate_controller_batch(i: u32, ) -> Weight; fn force_unstake(s: u32, ) -> Weight; fn cancel_deferred_slash(s: u32, ) -> Weight; fn payout_stakers_alive_staked(n: u32, ) -> Weight; @@ -98,8 +99,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_895_000 picoseconds. - Weight::from_parts(44_924_000, 4764) + // Minimum execution time: 42_491_000 picoseconds. + Weight::from_parts(44_026_000, 4764) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -119,8 +120,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 87_734_000 picoseconds. - Weight::from_parts(90_762_000, 8877) + // Minimum execution time: 88_756_000 picoseconds. + Weight::from_parts(91_000_000, 8877) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -146,8 +147,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 90_914_000 picoseconds. - Weight::from_parts(94_156_000, 8877) + // Minimum execution time: 91_331_000 picoseconds. + Weight::from_parts(94_781_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -166,10 +167,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 43_141_000 picoseconds. - Weight::from_parts(45_081_969, 4764) - // Standard Error: 1_010 - .saturating_add(Weight::from_parts(39_539, 0).saturating_mul(s.into())) + // 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())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -206,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: 87_743_000 picoseconds. - Weight::from_parts(96_983_484, 6248) - // Standard Error: 4_271 - .saturating_add(Weight::from_parts(1_382_993, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -241,8 +242,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_888_000 picoseconds. - Weight::from_parts(54_353_000, 4556) + // Minimum execution time: 51_532_000 picoseconds. + Weight::from_parts(53_308_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -255,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_944_000 picoseconds. - Weight::from_parts(31_116_533, 4556) - // Standard Error: 11_848 - .saturating_add(Weight::from_parts(6_422_601, 0).saturating_mul(k.into())) + // 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())) .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()))) @@ -291,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: 63_921_000 picoseconds. - Weight::from_parts(62_662_863, 6248) - // Standard Error: 15_071 - .saturating_add(Weight::from_parts(3_950_084, 0).saturating_mul(n.into())) + // 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())) .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)) @@ -318,8 +319,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_605_000 picoseconds. - Weight::from_parts(56_406_000, 6248) + // Minimum execution time: 54_194_000 picoseconds. + Weight::from_parts(55_578_000, 6248) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -333,8 +334,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_826_000 picoseconds. - Weight::from_parts(17_326_000, 4556) + // Minimum execution time: 16_597_000 picoseconds. + Weight::from_parts(16_980_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -348,8 +349,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_831_000 picoseconds. - Weight::from_parts(21_615_000, 4556) + // Minimum execution time: 20_626_000 picoseconds. + Weight::from_parts(21_242_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -361,8 +362,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 20_190_000 picoseconds. - Weight::from_parts(20_993_000, 4556) + // Minimum execution time: 19_972_000 picoseconds. + Weight::from_parts(20_470_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -372,8 +373,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_603_000 picoseconds. - Weight::from_parts(2_747_000, 0) + // Minimum execution time: 2_571_000 picoseconds. + Weight::from_parts(2_720_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -382,8 +383,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_070_000 picoseconds. - Weight::from_parts(8_745_000, 0) + // Minimum execution time: 8_056_000 picoseconds. + Weight::from_parts(8_413_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -392,8 +393,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_999_000 picoseconds. - Weight::from_parts(8_624_000, 0) + // Minimum execution time: 8_162_000 picoseconds. + Weight::from_parts(8_497_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -402,8 +403,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_131_000 picoseconds. - Weight::from_parts(8_467_000, 0) + // Minimum execution time: 8_320_000 picoseconds. + Weight::from_parts(8_564_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -413,12 +414,31 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_731_000 picoseconds. - Weight::from_parts(3_298_421, 0) - // Standard Error: 31 - .saturating_add(Weight::from_parts(10_075, 0).saturating_mul(v.into())) + // 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())) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Staking::Ledger` (r:5900 w:11800) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:5900 w:0) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:0 w:5900) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// The range of component `i` is `[0, 5900]`. + fn deprecate_controller_batch(i: u32, ) -> Weight { + // 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())) + .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())) + } /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -452,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_305_000 picoseconds. - Weight::from_parts(94_494_401, 6248) - // Standard Error: 3_602 - .saturating_add(Weight::from_parts(1_339_477, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -468,10 +488,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 100_007_000 picoseconds. - Weight::from_parts(894_033_025, 70137) - // Standard Error: 57_584 - .saturating_add(Weight::from_parts(4_870_504, 0).saturating_mul(s.into())) + // 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())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -508,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: 142_575_000 picoseconds. - Weight::from_parts(196_320_577, 30944) - // Standard Error: 29_330 - .saturating_add(Weight::from_parts(45_325_062, 0).saturating_mul(n.into())) + // 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())) .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)) @@ -535,10 +555,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_113_000 picoseconds. - Weight::from_parts(84_470_927, 8877) - // Standard Error: 5_588 - .saturating_add(Weight::from_parts(97_606, 0).saturating_mul(l.into())) + // 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())) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -573,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: 94_810_000 picoseconds. - Weight::from_parts(99_292_156, 6248) - // Standard Error: 3_677 - .saturating_add(Weight::from_parts(1_345_598, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -621,13 +641,13 @@ impl WeightInfo for SubstrateWeight { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±4) + v * (3566 ±40)` - // Minimum execution time: 583_230_000 picoseconds. - Weight::from_parts(585_794_000, 512390) - // Standard Error: 1_984_644 - .saturating_add(Weight::from_parts(65_914_551, 0).saturating_mul(v.into())) - // Standard Error: 197_758 - .saturating_add(Weight::from_parts(18_105_424, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -658,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_312_958_000 picoseconds. - Weight::from_parts(4_949_866_209, 512390) - // Standard Error: 402_931 - .saturating_add(Weight::from_parts(16_448_367, 0).saturating_mul(v.into())) - // Standard Error: 402_931 - .saturating_add(Weight::from_parts(25_361_503, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -680,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_474_646_000 picoseconds. - Weight::from_parts(2_512_113_000, 3510) - // Standard Error: 33_996 - .saturating_add(Weight::from_parts(1_992_173, 0).saturating_mul(v.into())) + // 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())) .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())) @@ -704,8 +724,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_466_000 picoseconds. - Weight::from_parts(5_861_000, 0) + // Minimum execution time: 5_073_000 picoseconds. + Weight::from_parts(5_452_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -724,8 +744,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_780_000 picoseconds. - Weight::from_parts(4_998_000, 0) + // Minimum execution time: 4_465_000 picoseconds. + Weight::from_parts(4_832_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -754,8 +774,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_261_000 picoseconds. - Weight::from_parts(72_778_000, 6248) + // Minimum execution time: 71_239_000 picoseconds. + Weight::from_parts(74_649_000, 6248) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -767,8 +787,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_497_000 picoseconds. - Weight::from_parts(13_049_000, 3510) + // Minimum execution time: 12_525_000 picoseconds. + Weight::from_parts(13_126_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -778,8 +798,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_044_000 picoseconds. - Weight::from_parts(3_278_000, 0) + // Minimum execution time: 2_918_000 picoseconds. + Weight::from_parts(3_176_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -800,8 +820,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_895_000 picoseconds. - Weight::from_parts(44_924_000, 4764) + // Minimum execution time: 42_491_000 picoseconds. + Weight::from_parts(44_026_000, 4764) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -821,8 +841,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 87_734_000 picoseconds. - Weight::from_parts(90_762_000, 8877) + // Minimum execution time: 88_756_000 picoseconds. + Weight::from_parts(91_000_000, 8877) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -848,8 +868,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 90_914_000 picoseconds. - Weight::from_parts(94_156_000, 8877) + // Minimum execution time: 91_331_000 picoseconds. + Weight::from_parts(94_781_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -868,10 +888,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 43_141_000 picoseconds. - Weight::from_parts(45_081_969, 4764) - // Standard Error: 1_010 - .saturating_add(Weight::from_parts(39_539, 0).saturating_mul(s.into())) + // 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())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -908,10 +928,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_743_000 picoseconds. - Weight::from_parts(96_983_484, 6248) - // Standard Error: 4_271 - .saturating_add(Weight::from_parts(1_382_993, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -943,8 +963,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_888_000 picoseconds. - Weight::from_parts(54_353_000, 4556) + // Minimum execution time: 51_532_000 picoseconds. + Weight::from_parts(53_308_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -957,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_944_000 picoseconds. - Weight::from_parts(31_116_533, 4556) - // Standard Error: 11_848 - .saturating_add(Weight::from_parts(6_422_601, 0).saturating_mul(k.into())) + // 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())) .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()))) @@ -993,10 +1013,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1866 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 63_921_000 picoseconds. - Weight::from_parts(62_662_863, 6248) - // Standard Error: 15_071 - .saturating_add(Weight::from_parts(3_950_084, 0).saturating_mul(n.into())) + // 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())) .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)) @@ -1020,8 +1040,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_605_000 picoseconds. - Weight::from_parts(56_406_000, 6248) + // Minimum execution time: 54_194_000 picoseconds. + Weight::from_parts(55_578_000, 6248) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1035,8 +1055,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_826_000 picoseconds. - Weight::from_parts(17_326_000, 4556) + // Minimum execution time: 16_597_000 picoseconds. + Weight::from_parts(16_980_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1050,8 +1070,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_831_000 picoseconds. - Weight::from_parts(21_615_000, 4556) + // Minimum execution time: 20_626_000 picoseconds. + Weight::from_parts(21_242_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1063,8 +1083,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 20_190_000 picoseconds. - Weight::from_parts(20_993_000, 4556) + // Minimum execution time: 19_972_000 picoseconds. + Weight::from_parts(20_470_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1074,8 +1094,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_603_000 picoseconds. - Weight::from_parts(2_747_000, 0) + // Minimum execution time: 2_571_000 picoseconds. + Weight::from_parts(2_720_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1084,8 +1104,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_070_000 picoseconds. - Weight::from_parts(8_745_000, 0) + // Minimum execution time: 8_056_000 picoseconds. + Weight::from_parts(8_413_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1094,8 +1114,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_999_000 picoseconds. - Weight::from_parts(8_624_000, 0) + // Minimum execution time: 8_162_000 picoseconds. + Weight::from_parts(8_497_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1104,8 +1124,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_131_000 picoseconds. - Weight::from_parts(8_467_000, 0) + // Minimum execution time: 8_320_000 picoseconds. + Weight::from_parts(8_564_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1115,12 +1135,31 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_731_000 picoseconds. - Weight::from_parts(3_298_421, 0) - // Standard Error: 31 - .saturating_add(Weight::from_parts(10_075, 0).saturating_mul(v.into())) + // 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())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Staking::Ledger` (r:5900 w:11800) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:5900 w:0) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:0 w:5900) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// The range of component `i` is `[0, 5900]`. + fn deprecate_controller_batch(i: u32, ) -> Weight { + // 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())) + .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())) + } /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -1154,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_305_000 picoseconds. - Weight::from_parts(94_494_401, 6248) - // Standard Error: 3_602 - .saturating_add(Weight::from_parts(1_339_477, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -1170,10 +1209,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 100_007_000 picoseconds. - Weight::from_parts(894_033_025, 70137) - // Standard Error: 57_584 - .saturating_add(Weight::from_parts(4_870_504, 0).saturating_mul(s.into())) + // 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())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1210,10 +1249,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `33297 + n * (377 ±0)` // Estimated: `30944 + n * (3774 ±0)` - // Minimum execution time: 142_575_000 picoseconds. - Weight::from_parts(196_320_577, 30944) - // Standard Error: 29_330 - .saturating_add(Weight::from_parts(45_325_062, 0).saturating_mul(n.into())) + // 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())) .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)) @@ -1237,10 +1276,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_113_000 picoseconds. - Weight::from_parts(84_470_927, 8877) - // Standard Error: 5_588 - .saturating_add(Weight::from_parts(97_606, 0).saturating_mul(l.into())) + // 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())) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1275,10 +1314,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 94_810_000 picoseconds. - Weight::from_parts(99_292_156, 6248) - // Standard Error: 3_677 - .saturating_add(Weight::from_parts(1_345_598, 0).saturating_mul(s.into())) + // 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())) .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()))) @@ -1323,13 +1362,13 @@ impl WeightInfo for () { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` - // Estimated: `512390 + n * (3566 ±4) + v * (3566 ±40)` - // Minimum execution time: 583_230_000 picoseconds. - Weight::from_parts(585_794_000, 512390) - // Standard Error: 1_984_644 - .saturating_add(Weight::from_parts(65_914_551, 0).saturating_mul(v.into())) - // Standard Error: 197_758 - .saturating_add(Weight::from_parts(18_105_424, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -1360,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_312_958_000 picoseconds. - Weight::from_parts(4_949_866_209, 512390) - // Standard Error: 402_931 - .saturating_add(Weight::from_parts(16_448_367, 0).saturating_mul(v.into())) - // Standard Error: 402_931 - .saturating_add(Weight::from_parts(25_361_503, 0).saturating_mul(n.into())) + // 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())) .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()))) @@ -1382,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_474_646_000 picoseconds. - Weight::from_parts(2_512_113_000, 3510) - // Standard Error: 33_996 - .saturating_add(Weight::from_parts(1_992_173, 0).saturating_mul(v.into())) + // 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())) .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())) @@ -1406,8 +1445,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_466_000 picoseconds. - Weight::from_parts(5_861_000, 0) + // Minimum execution time: 5_073_000 picoseconds. + Weight::from_parts(5_452_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1426,8 +1465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_780_000 picoseconds. - Weight::from_parts(4_998_000, 0) + // Minimum execution time: 4_465_000 picoseconds. + Weight::from_parts(4_832_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1456,8 +1495,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_261_000 picoseconds. - Weight::from_parts(72_778_000, 6248) + // Minimum execution time: 71_239_000 picoseconds. + Weight::from_parts(74_649_000, 6248) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1469,8 +1508,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_497_000 picoseconds. - Weight::from_parts(13_049_000, 3510) + // Minimum execution time: 12_525_000 picoseconds. + Weight::from_parts(13_126_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1480,8 +1519,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_044_000 picoseconds. - Weight::from_parts(3_278_000, 0) + // Minimum execution time: 2_918_000 picoseconds. + Weight::from_parts(3_176_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 a3e6edd5aeecaaab624ca38f7991a0fe58673e82..e27d4c469269a639411a6e5ef30c3583628c23ea 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet migration of trie" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", optional = true } +serde = { version = "1.0.195", optional = 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/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 5330634ca07642e966f469dbbcb674876b67c278..8652e8e9561c83c003f163a99b805c97115c79df 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -1637,7 +1637,7 @@ pub(crate) mod remote_tests { weight_sum += StateTrieMigration::::on_initialize(System::::block_number()); - root = System::::finalize().state_root().clone(); + root = *System::::finalize().state_root(); System::::on_finalize(System::::block_number()); } (root, weight_sum) @@ -1687,7 +1687,7 @@ pub(crate) mod remote_tests { ); loop { - let last_state_root = ext.backend.root().clone(); + let last_state_root = *ext.backend.root(); let ((finished, weight), proof) = ext.execute_and_prove(|| { let weight = run_to_block::(now + One::one()).1; if StateTrieMigration::::migration_process().finished() { diff --git a/substrate/frame/statement/Cargo.toml b/substrate/frame/statement/Cargo.toml index e4caf241233e3b20be6ae0189929df2668723b8b..d41afc3244b4fd36a0583128ee182ca26d0dcbda 100644 --- a/substrate/frame/statement/Cargo.toml +++ b/substrate/frame/statement/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet for statement store" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/sudo/Cargo.toml b/substrate/frame/sudo/Cargo.toml index 70323590085ea0fb5b92cbd60b3491f153eeeb85..027716ce3179fd5e7eecac5912c3e1b4ade2a3ef 100644 --- a/substrate/frame/sudo/Cargo.toml +++ b/substrate/frame/sudo/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for sudo" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/sudo/src/lib.rs b/substrate/frame/sudo/src/lib.rs index d556c5eb6ae638988307a6d14955051b4f2c0870..4f14c32ff76b0e0038597c9d715d632985eceeea 100644 --- a/substrate/frame/sudo/src/lib.rs +++ b/substrate/frame/sudo/src/lib.rs @@ -349,12 +349,16 @@ pub mod pallet { impl Pallet { /// Ensure that the caller is the sudo key. pub(crate) fn ensure_sudo(origin: OriginFor) -> DispatchResult { - let sender = ensure_signed(origin)?; - - if Self::key().map_or(false, |k| k == sender) { - Ok(()) + let sender = ensure_signed_or_root(origin)?; + + if let Some(sender) = sender { + if Self::key().map_or(false, |k| k == sender) { + Ok(()) + } else { + Err(Error::::RequireSudo.into()) + } } else { - Err(Error::::RequireSudo.into()) + Ok(()) } } } diff --git a/substrate/frame/sudo/src/tests.rs b/substrate/frame/sudo/src/tests.rs index 13dc069ddef1cbbcc1b94b25606bdc519d4b44c6..73689415a737fd3e2acba663099f9042f88ace4c 100644 --- a/substrate/frame/sudo/src/tests.rs +++ b/substrate/frame/sudo/src/tests.rs @@ -169,6 +169,18 @@ fn remove_key_works() { }); } +#[test] +fn using_root_origin_works() { + new_test_ext(1).execute_with(|| { + assert_ok!(Sudo::remove_key(RuntimeOrigin::root())); + assert!(Sudo::key().is_none()); + System::assert_has_event(TestEvent::Sudo(Event::KeyRemoved {})); + + assert_ok!(Sudo::set_key(RuntimeOrigin::root(), 1)); + assert_eq!(Some(1), Sudo::key()); + }); +} + #[test] fn sudo_as_basics() { new_test_ext(1).execute_with(|| { diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index 6d253f29c3fb9a535a286171f06f27d799119e1d..5c0c4091468b2730b52eeef94eb59146fe4b88e2 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -9,12 +9,15 @@ repository.workspace = true description = "Support code for the runtime." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", default-features = false } -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } @@ -43,7 +46,7 @@ sp-core-hashing-proc-macro = { path = "../../primitives/core/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.108", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } docify = "0.2.6" static_assertions = "1.1.0" diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index f1d8a7d4ca9ab624cbf77f70595a773c2b3eeed7..5e47f0dfeb0d6f0ca2e8bed065a448c057ba9995 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Proc macro of Support code for the runtime." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -21,13 +24,16 @@ cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.39", features = ["full"] } +syn = { version = "2.0.48", features = ["full"] } 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 } expander = "2.0.0" sp-core-hashing = { path = "../../../primitives/core/hashing" } +[dev-dependencies] +regex = "1" + [features] default = ["std"] std = [] diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs index a0fc6b8130b3c0b5b8fc7ce76768bbc9b0dc9908..88f9a3c6e33fd3fc99b2f4e511d5a6c0afd9263a 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -26,6 +26,7 @@ mod metadata; mod origin; mod outer_enums; mod slash_reason; +mod task; mod unsigned; pub use call::expand_outer_dispatch; @@ -38,4 +39,5 @@ pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; pub use outer_enums::{expand_outer_enum, OuterEnumType}; pub use slash_reason::expand_outer_slash_reason; +pub use task::expand_outer_task; pub use unsigned::expand_outer_validate_unsigned; diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs new file mode 100644 index 0000000000000000000000000000000000000000..6531c0e9e07075f2674856740aab2ec786e3f683 --- /dev/null +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/task.rs @@ -0,0 +1,131 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::Pallet; +use proc_macro2::{Ident, TokenStream as TokenStream2}; +use quote::quote; + +/// Expands aggregate `RuntimeTask` enum. +pub fn expand_outer_task( + runtime_name: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let mut from_impls = Vec::new(); + let mut task_variants = Vec::new(); + let mut variant_names = Vec::new(); + let mut task_paths = Vec::new(); + for decl in pallet_decls { + if decl.find_part("Task").is_none() { + continue + } + + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + from_impls.push(quote! { + impl From<#path::Task<#runtime_name>> for RuntimeTask { + fn from(hr: #path::Task<#runtime_name>) -> Self { + RuntimeTask::#variant_name(hr) + } + } + + impl TryInto<#path::Task<#runtime_name>> for RuntimeTask { + type Error = (); + + fn try_into(self) -> Result<#path::Task<#runtime_name>, Self::Error> { + match self { + RuntimeTask::#variant_name(hr) => Ok(hr), + _ => Err(()), + } + } + } + }); + + task_variants.push(quote! { + #[codec(index = #index)] + #variant_name(#path::Task<#runtime_name>), + }); + + variant_names.push(quote!(#variant_name)); + + task_paths.push(quote!(#path::Task)); + } + + let prelude = quote!(#scrate::traits::tasks::__private); + + const INCOMPLETE_MATCH_QED: &'static str = + "cannot have an instantiated RuntimeTask without some Task variant in the runtime. QED"; + + let output = quote! { + /// An aggregation of all `Task` enums across all pallets included in the current runtime. + #[derive( + Clone, Eq, PartialEq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::scale_info::TypeInfo, + #scrate::__private::RuntimeDebug, + )] + pub enum RuntimeTask { + #( #task_variants )* + } + + #[automatically_derived] + impl #scrate::traits::Task for RuntimeTask { + type Enumeration = #prelude::IntoIter; + + fn is_valid(&self) -> bool { + match self { + #(RuntimeTask::#variant_names(val) => val.is_valid(),)* + _ => unreachable!(#INCOMPLETE_MATCH_QED), + } + } + + fn run(&self) -> Result<(), #scrate::traits::tasks::__private::DispatchError> { + match self { + #(RuntimeTask::#variant_names(val) => val.run(),)* + _ => unreachable!(#INCOMPLETE_MATCH_QED), + } + } + + fn weight(&self) -> #scrate::pallet_prelude::Weight { + match self { + #(RuntimeTask::#variant_names(val) => val.weight(),)* + _ => unreachable!(#INCOMPLETE_MATCH_QED), + } + } + + fn task_index(&self) -> u32 { + match self { + #(RuntimeTask::#variant_names(val) => val.task_index(),)* + _ => unreachable!(#INCOMPLETE_MATCH_QED), + } + } + + fn iter() -> Self::Enumeration { + let mut all_tasks = Vec::new(); + #(all_tasks.extend(#task_paths::iter().map(RuntimeTask::from).collect::>());)* + all_tasks.into_iter() + } + } + + #( #from_impls )* + }; + + output +} diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 010143574ed5b0e80b6b5d8b5c64da17675d6493..7a9c4d89a74943ac23ceae8e03ccbf0a0469ebdd 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -386,6 +386,7 @@ fn construct_runtime_final_expansion( let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); + let tasks = expand::expand_outer_task(&name, &pallets, &scrate); let metadata = expand::expand_runtime_metadata( &name, &pallets, @@ -475,6 +476,8 @@ fn construct_runtime_final_expansion( #dispatch + #tasks + #metadata #outer_config diff --git a/substrate/frame/support/procedural/src/construct_runtime/parse.rs b/substrate/frame/support/procedural/src/construct_runtime/parse.rs index 9b08e16469754a98c8cc089176b973c7d1ce1fae..88f3f14dc86c541ef949beced9feaf222e9a3146 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/parse.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/parse.rs @@ -42,6 +42,7 @@ mod keyword { syn::custom_keyword!(ValidateUnsigned); syn::custom_keyword!(FreezeReason); syn::custom_keyword!(HoldReason); + syn::custom_keyword!(Task); syn::custom_keyword!(LockId); syn::custom_keyword!(SlashReason); syn::custom_keyword!(exclude_parts); @@ -404,6 +405,7 @@ pub enum PalletPartKeyword { ValidateUnsigned(keyword::ValidateUnsigned), FreezeReason(keyword::FreezeReason), HoldReason(keyword::HoldReason), + Task(keyword::Task), LockId(keyword::LockId), SlashReason(keyword::SlashReason), } @@ -434,6 +436,8 @@ impl Parse for PalletPartKeyword { Ok(Self::FreezeReason(input.parse()?)) } else if lookahead.peek(keyword::HoldReason) { Ok(Self::HoldReason(input.parse()?)) + } else if lookahead.peek(keyword::Task) { + Ok(Self::Task(input.parse()?)) } else if lookahead.peek(keyword::LockId) { Ok(Self::LockId(input.parse()?)) } else if lookahead.peek(keyword::SlashReason) { @@ -459,6 +463,7 @@ impl PalletPartKeyword { Self::ValidateUnsigned(_) => "ValidateUnsigned", Self::FreezeReason(_) => "FreezeReason", Self::HoldReason(_) => "HoldReason", + Self::Task(_) => "Task", Self::LockId(_) => "LockId", Self::SlashReason(_) => "SlashReason", } @@ -471,7 +476,7 @@ impl PalletPartKeyword { /// Returns the names of all pallet parts that allow to have a generic argument. fn all_generic_arg() -> &'static [&'static str] { - &["Event", "Error", "Origin", "Config"] + &["Event", "Error", "Origin", "Config", "Task"] } } @@ -489,6 +494,7 @@ impl ToTokens for PalletPartKeyword { Self::ValidateUnsigned(inner) => inner.to_tokens(tokens), Self::FreezeReason(inner) => inner.to_tokens(tokens), Self::HoldReason(inner) => inner.to_tokens(tokens), + Self::Task(inner) => inner.to_tokens(tokens), Self::LockId(inner) => inner.to_tokens(tokens), Self::SlashReason(inner) => inner.to_tokens(tokens), } diff --git a/substrate/frame/support/procedural/src/derive_impl.rs b/substrate/frame/support/procedural/src/derive_impl.rs index 3e044053116b1fb17102ab7cdcd93c51e5ab7100..d6d5bf68efd5689af2e96250e24cef3cd7faf47b 100644 --- a/substrate/frame/support/procedural/src/derive_impl.rs +++ b/substrate/frame/support/procedural/src/derive_impl.rs @@ -136,9 +136,15 @@ fn combine_impls( return None } if let ImplItem::Type(typ) = item.clone() { + let cfg_attrs = typ + .attrs + .iter() + .filter(|attr| attr.path().get_ident().map_or(false, |ident| ident == "cfg")) + .map(|attr| attr.to_token_stream()); if is_runtime_type(&typ) { let item: ImplItem = if inject_runtime_types { parse_quote! { + #( #cfg_attrs )* type #ident = #ident; } } else { @@ -148,6 +154,7 @@ fn combine_impls( } // modify and insert uncolliding type items let modified_item: ImplItem = parse_quote! { + #( #cfg_attrs )* type #ident = <#default_impl_path as #disambiguation_path>::#ident; }; return Some(modified_item) diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 682b135fa143277cf99a377e1e321998af7a41a3..349b6ee6599c27539f9fc11d50521b64cc14ee86 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -646,7 +646,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream /// ``` /// /// where `TestDefaultConfig` was defined and registered as follows: -/// /// ```ignore /// pub struct TestDefaultConfig; /// @@ -673,7 +672,6 @@ pub fn storage_alias(attributes: TokenStream, input: TokenStream) -> TokenStream /// ``` /// /// The above call to `derive_impl` would expand to roughly the following: -/// /// ```ignore /// impl frame_system::Config for Test { /// use frame_system::config_preludes::TestDefaultConfig; @@ -881,6 +879,7 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { let item = syn::parse_macro_input!(item as TraitItemType); if item.ident != "RuntimeCall" && item.ident != "RuntimeEvent" && + item.ident != "RuntimeTask" && item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && @@ -888,10 +887,11 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { { return syn::Error::new_spanned( item, - "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeOrigin` or `PalletInfo`", + "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ + `RuntimeTask`, `RuntimeOrigin` or `PalletInfo`", ) .to_compile_error() - .into(); + .into() } tokens } @@ -1518,6 +1518,56 @@ pub fn composite_enum(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::tasks_experimental`. +#[proc_macro_attribute] +pub fn tasks_experimental(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::task_list`. +#[proc_macro_attribute] +pub fn task_list(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::task_condition`. +#[proc_macro_attribute] +pub fn task_condition(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::task_weight`. +#[proc_macro_attribute] +pub fn task_weight(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// +/// --- +/// +/// **Rust-Analyzer users**: See the documentation of the Rust item in +/// `frame_support::pallet_macros::task_index`. +#[proc_macro_attribute] +pub fn task_index(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// Can be attached to a module. Doing so will declare that module as importable into a pallet /// via [`#[import_section]`](`macro@import_section`). /// diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index cf302faafc7805bb8bb2f20e5e7feb4444ce3932..624cde018dc40258cf12014c5923a43996f496bd 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -241,6 +241,15 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { }) .collect::>(); + let cfg_attrs = methods + .iter() + .map(|method| { + let attrs = + method.cfg_attrs.iter().map(|attr| attr.to_token_stream()).collect::>(); + quote::quote!( #( #attrs )* ) + }) + .collect::>(); + let feeless_check = methods.iter().map(|method| &method.feeless_check).collect::>(); let feeless_check_result = feeless_check.iter().zip(args_name.iter()).map(|(feeless_check, arg_name)| { @@ -297,6 +306,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::Never, ), #( + #cfg_attrs #[doc = #fn_doc] #[codec(index = #call_index)] #fn_name { @@ -310,6 +320,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { impl<#type_impl_gen> #call_ident<#type_use_gen> #where_clause { #( + #cfg_attrs #[doc = #new_call_variant_doc] pub fn #new_call_variant_fn_name( #( #args_name_stripped: #args_type ),* @@ -328,6 +339,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { fn get_dispatch_info(&self) -> #frame_support::dispatch::DispatchInfo { match *self { #( + #cfg_attrs Self::#fn_name { #( #args_name_pattern_ref, )* } => { let __pallet_base_weight = #fn_weight; @@ -365,6 +377,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { fn is_feeless(&self, origin: &Self::Origin) -> bool { match *self { #( + #cfg_attrs Self::#fn_name { #( #args_name_pattern_ref, )* } => { #feeless_check_result }, @@ -379,13 +392,13 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { { fn get_call_name(&self) -> &'static str { match *self { - #( Self::#fn_name { .. } => stringify!(#fn_name), )* + #( #cfg_attrs Self::#fn_name { .. } => stringify!(#fn_name), )* Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."), } } fn get_call_names() -> &'static [&'static str] { - &[ #( stringify!(#fn_name), )* ] + &[ #( #cfg_attrs stringify!(#fn_name), )* ] } } @@ -394,13 +407,13 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { { fn get_call_index(&self) -> u8 { match *self { - #( Self::#fn_name { .. } => #call_index, )* + #( #cfg_attrs Self::#fn_name { .. } => #call_index, )* Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."), } } fn get_call_indices() -> &'static [u8] { - &[ #( #call_index, )* ] + &[ #( #cfg_attrs #call_index, )* ] } } @@ -416,6 +429,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::dispatch_context::run_in_context(|| { match self { #( + #cfg_attrs Self::#fn_name { #( #args_name_pattern, )* } => { #frame_support::__private::sp_tracing::enter_span!( #frame_support::__private::sp_tracing::trace_span!(stringify!(#fn_name)) diff --git a/substrate/frame/support/procedural/src/pallet/expand/error.rs b/substrate/frame/support/procedural/src/pallet/expand/error.rs index 877489fd6057b85d06ba2477020776c91bf92d3c..72fb6e923572387622ef2ea820dc6931c32468ef 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/error.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/error.rs @@ -16,10 +16,14 @@ // limitations under the License. use crate::{ - pallet::{parse::error::VariantField, Def}, + pallet::{ + parse::error::{VariantDef, VariantField}, + Def, + }, COUNTER, }; use frame_support_procedural_tools::get_doc_literals; +use quote::ToTokens; use syn::spanned::Spanned; /// @@ -67,20 +71,23 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { ) ); - let as_str_matches = error.variants.iter().map(|(variant, field_ty, _)| { - let variant_str = variant.to_string(); - match field_ty { - Some(VariantField { is_named: true }) => { - quote::quote_spanned!(error.attr_span => Self::#variant { .. } => #variant_str,) - }, - Some(VariantField { is_named: false }) => { - quote::quote_spanned!(error.attr_span => Self::#variant(..) => #variant_str,) - }, - None => { - quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) - }, - } - }); + let as_str_matches = error.variants.iter().map( + |VariantDef { ident: variant, field: field_ty, docs: _, cfg_attrs }| { + let variant_str = variant.to_string(); + let cfg_attrs = cfg_attrs.iter().map(|attr| attr.to_token_stream()); + match field_ty { + Some(VariantField { is_named: true }) => { + quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* Self::#variant { .. } => #variant_str,) + }, + Some(VariantField { is_named: false }) => { + quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* Self::#variant(..) => #variant_str,) + }, + None => { + quote::quote_spanned!(error.attr_span => #( #cfg_attrs )* Self::#variant => #variant_str,) + }, + } + }, + ); let error_item = { let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[error.index]; diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 6f32e5697512f7e87a0130ec31e69eb5201c4d89..db242df781b124f86e14eb6bd084578fda723e73 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -31,6 +31,7 @@ mod origin; mod pallet_struct; mod storage; mod store_trait; +mod tasks; mod tt_default_parts; mod type_value; mod validate_unsigned; @@ -60,6 +61,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let pallet_struct = pallet_struct::expand_pallet_struct(&mut def); let config = config::expand_config(&mut def); let call = call::expand_call(&mut def); + let tasks = tasks::expand_tasks(&mut def); let error = error::expand_error(&mut def); let event = event::expand_event(&mut def); let storages = storage::expand_storages(&mut def); @@ -100,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #pallet_struct #config #call + #tasks #error #event #storages diff --git a/substrate/frame/support/procedural/src/pallet/expand/tasks.rs b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..6697e5c822a31b1ef719f03b47eb1ae32169fd03 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/expand/tasks.rs @@ -0,0 +1,267 @@ +//! Contains logic for expanding task-related items. + +// 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. + +//! Home of the expansion code for the Tasks API + +use crate::pallet::{parse::tasks::*, Def}; +use derive_syn_parse::Parse; +use inflector::Inflector; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse_quote, spanned::Spanned, ItemEnum, ItemImpl}; + +impl TaskEnumDef { + /// Since we optionally allow users to manually specify a `#[pallet::task_enum]`, in the + /// event they _don't_ specify one (which is actually the most common behavior) we have to + /// generate one based on the existing [`TasksDef`]. This method performs that generation. + pub fn generate( + tasks: &TasksDef, + type_decl_bounded_generics: TokenStream2, + type_use_generics: TokenStream2, + ) -> Self { + let variants = if tasks.tasks_attr.is_some() { + tasks + .tasks + .iter() + .map(|task| { + let ident = &task.item.sig.ident; + let ident = + format_ident!("{}", ident.to_string().to_class_case(), span = ident.span()); + + let args = task.item.sig.inputs.iter().collect::>(); + + if args.is_empty() { + quote!(#ident) + } else { + quote!(#ident { + #(#args),* + }) + } + }) + .collect::>() + } else { + Vec::new() + }; + let mut task_enum_def: TaskEnumDef = parse_quote! { + /// Auto-generated enum that encapsulates all tasks defined by this pallet. + /// + /// Conceptually similar to the [`Call`] enum, but for tasks. This is only + /// generated if there are tasks present in this pallet. + #[pallet::task_enum] + pub enum Task<#type_decl_bounded_generics> { + #( + #variants, + )* + } + }; + task_enum_def.type_use_generics = type_use_generics; + task_enum_def + } +} + +impl ToTokens for TaskEnumDef { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let item_enum = &self.item_enum; + let ident = &item_enum.ident; + let vis = &item_enum.vis; + let attrs = &item_enum.attrs; + let generics = &item_enum.generics; + let variants = &item_enum.variants; + let scrate = &self.scrate; + let type_use_generics = &self.type_use_generics; + if self.attr.is_some() { + // `item_enum` is short-hand / generated enum + tokens.extend(quote! { + #(#attrs)* + #[derive( + #scrate::CloneNoBound, + #scrate::EqNoBound, + #scrate::PartialEqNoBound, + #scrate::pallet_prelude::Encode, + #scrate::pallet_prelude::Decode, + #scrate::pallet_prelude::TypeInfo, + )] + #[codec(encode_bound())] + #[codec(decode_bound())] + #[scale_info(skip_type_params(#type_use_generics))] + #vis enum #ident #generics { + #variants + #[doc(hidden)] + #[codec(skip)] + __Ignore(core::marker::PhantomData, #scrate::Never), + } + + impl core::fmt::Debug for #ident<#type_use_generics> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct(stringify!(#ident)).field("value", self).finish() + } + } + }); + } else { + // `item_enum` is a manually specified enum (no attribute) + tokens.extend(item_enum.to_token_stream()); + } + } +} + +/// Represents an already-expanded [`TasksDef`]. +#[derive(Parse)] +pub struct ExpandedTasksDef { + pub task_item_impl: ItemImpl, + pub task_trait_impl: ItemImpl, +} + +impl ToTokens for TasksDef { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let scrate = &self.scrate; + let enum_ident = syn::Ident::new("Task", self.enum_ident.span()); + let enum_arguments = &self.enum_arguments; + let enum_use = quote!(#enum_ident #enum_arguments); + + let task_fn_idents = self + .tasks + .iter() + .map(|task| { + format_ident!( + "{}", + &task.item.sig.ident.to_string().to_class_case(), + span = task.item.sig.ident.span() + ) + }) + .collect::>(); + let task_indices = self.tasks.iter().map(|task| &task.index_attr.meta.index); + let task_conditions = self.tasks.iter().map(|task| &task.condition_attr.meta.expr); + let task_weights = self.tasks.iter().map(|task| &task.weight_attr.meta.expr); + let task_iters = self.tasks.iter().map(|task| &task.list_attr.meta.expr); + + let task_fn_impls = self.tasks.iter().map(|task| { + let mut task_fn_impl = task.item.clone(); + task_fn_impl.attrs = vec![]; + task_fn_impl + }); + + let task_fn_names = self.tasks.iter().map(|task| &task.item.sig.ident); + let task_arg_names = self.tasks.iter().map(|task| &task.arg_names).collect::>(); + + let sp_std = quote!(#scrate::__private::sp_std); + let impl_generics = &self.item_impl.generics; + tokens.extend(quote! { + impl #impl_generics #enum_use + { + #(#task_fn_impls)* + } + + impl #impl_generics #scrate::traits::Task for #enum_use + { + type Enumeration = #sp_std::vec::IntoIter<#enum_use>; + + fn iter() -> Self::Enumeration { + let mut all_tasks = #sp_std::vec![]; + #(all_tasks + .extend(#task_iters.map(|(#(#task_arg_names),*)| #enum_ident::#task_fn_idents { #(#task_arg_names: #task_arg_names.clone()),* }) + .collect::<#sp_std::vec::Vec<_>>()); + )* + all_tasks.into_iter() + } + + fn task_index(&self) -> u32 { + match self.clone() { + #(#enum_ident::#task_fn_idents { .. } => #task_indices,)* + Task::__Ignore(_, _) => unreachable!(), + } + } + + fn is_valid(&self) -> bool { + match self.clone() { + #(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => (#task_conditions)(#(#task_arg_names),* ),)* + Task::__Ignore(_, _) => unreachable!(), + } + } + + fn run(&self) -> Result<(), #scrate::pallet_prelude::DispatchError> { + match self.clone() { + #(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => { + <#enum_use>::#task_fn_names(#( #task_arg_names, )* ) + },)* + Task::__Ignore(_, _) => unreachable!(), + } + } + + #[allow(unused_variables)] + fn weight(&self) -> #scrate::pallet_prelude::Weight { + match self.clone() { + #(#enum_ident::#task_fn_idents { #(#task_arg_names),* } => #task_weights,)* + Task::__Ignore(_, _) => unreachable!(), + } + } + } + }); + } +} + +/// Expands the [`TasksDef`] in the enclosing [`Def`], if present, and returns its tokens. +/// +/// This modifies the underlying [`Def`] in addition to returning any tokens that were added. +pub fn expand_tasks_impl(def: &mut Def) -> TokenStream2 { + let Some(tasks) = &mut def.tasks else { return quote!() }; + let ExpandedTasksDef { task_item_impl, task_trait_impl } = parse_quote!(#tasks); + quote! { + #task_item_impl + #task_trait_impl + } +} + +/// Represents a fully-expanded [`TaskEnumDef`]. +#[derive(Parse)] +pub struct ExpandedTaskEnum { + pub item_enum: ItemEnum, + pub debug_impl: ItemImpl, +} + +/// Modifies a [`Def`] to expand the underlying [`TaskEnumDef`] if present, and also returns +/// its tokens. A blank [`TokenStream2`] is returned if no [`TaskEnumDef`] has been generated +/// or defined. +pub fn expand_task_enum(def: &mut Def) -> TokenStream2 { + let Some(task_enum) = &mut def.task_enum else { return quote!() }; + let ExpandedTaskEnum { item_enum, debug_impl } = parse_quote!(#task_enum); + quote! { + #item_enum + #debug_impl + } +} + +/// Modifies a [`Def`] to expand the underlying [`TasksDef`] and also generate a +/// [`TaskEnumDef`] if applicable. The tokens for these items are returned if they are created. +pub fn expand_tasks(def: &mut Def) -> TokenStream2 { + if let Some(tasks_def) = &def.tasks { + if def.task_enum.is_none() { + def.task_enum = Some(TaskEnumDef::generate( + &tasks_def, + def.type_decl_bounded_generics(tasks_def.item_impl.span()), + def.type_use_generics(tasks_def.item_impl.span()), + )); + } + } + let tasks_extra_output = expand_tasks_impl(def); + let task_enum_extra_output = expand_task_enum(def); + quote! { + #tasks_extra_output + #task_enum_extra_output + } +} diff --git a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index c9a776ee247527b8e76508144ece3708d110194b..7cc1415dfddf1514cf398555c650f4892097aefd 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -31,6 +31,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); + let task_part = def.task_enum.as_ref().map(|_| quote::quote!(Task,)); + let storage_part = (!def.storages.is_empty()).then(|| quote::quote!(Storage,)); let event_part = def.event.as_ref().map(|event| { @@ -99,7 +101,7 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { tokens = [{ expanded::{ Pallet, #call_part #storage_part #event_part #error_part #origin_part #config_part - #inherent_part #validate_unsigned_part #freeze_reason_part + #inherent_part #validate_unsigned_part #freeze_reason_part #task_part #hold_reason_part #lock_id_part #slash_reason_part } }] diff --git a/substrate/frame/support/procedural/src/pallet/parse/call.rs b/substrate/frame/support/procedural/src/pallet/parse/call.rs index f78f2baa9d10efb3518b11dc7d8fe22c214563ea..4e09b86fddec171cdbcd9d4a9c79fe9c6d922960 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/call.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/call.rs @@ -85,6 +85,8 @@ pub struct CallVariantDef { pub docs: Vec, /// Attributes annotated at the top of the dispatchable function. pub attrs: Vec, + /// The `cfg` attributes. + pub cfg_attrs: Vec, /// The optional `feeless_if` attribute on the `pallet::call`. pub feeless_check: Option, } @@ -266,6 +268,7 @@ impl CallDef { return Err(syn::Error::new(method.sig.span(), msg)) } + let cfg_attrs: Vec = helper::get_item_cfg_attrs(&method.attrs); let mut call_idx_attrs = vec![]; let mut weight_attrs = vec![]; let mut feeless_attrs = vec![]; @@ -286,8 +289,7 @@ impl CallDef { if weight_attrs.is_empty() && dev_mode { // inject a default O(1) weight when dev mode is enabled and no weight has // been specified on the call - let empty_weight: syn::Expr = syn::parse(quote::quote!(0).into()) - .expect("we are parsing a quoted string; qed"); + let empty_weight: syn::Expr = syn::parse_quote!(0); weight_attrs.push(FunctionAttr::Weight(empty_weight)); } @@ -443,6 +445,7 @@ impl CallDef { args, docs, attrs: method.attrs.clone(), + cfg_attrs, feeless_check, }); } else { diff --git a/substrate/frame/support/procedural/src/pallet/parse/composite.rs b/substrate/frame/support/procedural/src/pallet/parse/composite.rs index 6e6ea6a795c1403b64891c00507cf0f7d8dffc25..fa5f47dfdfa184d25a15ba05842f14c63fdbede3 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/composite.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/composite.rs @@ -26,11 +26,14 @@ pub mod keyword { syn::custom_keyword!(HoldReason); syn::custom_keyword!(LockId); syn::custom_keyword!(SlashReason); + syn::custom_keyword!(Task); + pub enum CompositeKeyword { FreezeReason(FreezeReason), HoldReason(HoldReason), LockId(LockId), SlashReason(SlashReason), + Task(Task), } impl ToTokens for CompositeKeyword { @@ -41,6 +44,7 @@ pub mod keyword { HoldReason(inner) => inner.to_tokens(tokens), LockId(inner) => inner.to_tokens(tokens), SlashReason(inner) => inner.to_tokens(tokens), + Task(inner) => inner.to_tokens(tokens), } } } @@ -56,6 +60,8 @@ pub mod keyword { Ok(Self::LockId(input.parse()?)) } else if lookahead.peek(SlashReason) { Ok(Self::SlashReason(input.parse()?)) + } else if lookahead.peek(Task) { + Ok(Self::Task(input.parse()?)) } else { Err(lookahead.error()) } @@ -71,6 +77,7 @@ pub mod keyword { match self { FreezeReason(_) => "FreezeReason", HoldReason(_) => "HoldReason", + Task(_) => "Task", LockId(_) => "LockId", SlashReason(_) => "SlashReason", } @@ -80,7 +87,7 @@ pub mod keyword { } pub struct CompositeDef { - /// The index of the HoldReason item in the pallet module. + /// The index of the CompositeDef item in the pallet module. pub index: usize, /// The composite keyword used (contains span). pub composite_keyword: keyword::CompositeKeyword, diff --git a/substrate/frame/support/procedural/src/pallet/parse/error.rs b/substrate/frame/support/procedural/src/pallet/parse/error.rs index 6f82ce61fc93fc118ec4a7017fb7ccc87275a71b..362df8d7340ce0caad72cf85df88378569329672 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/error.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/error.rs @@ -25,19 +25,31 @@ mod keyword { syn::custom_keyword!(Error); } -/// Records information about the error enum variants. +/// Records information about the error enum variant field. pub struct VariantField { /// Whether or not the field is named, i.e. whether it is a tuple variant or struct variant. pub is_named: bool, } +/// Records information about the error enum variants. +pub struct VariantDef { + /// The variant ident. + pub ident: syn::Ident, + /// The variant field, if any. + pub field: Option, + /// The variant doc literals. + pub docs: Vec, + /// The `cfg` attributes. + pub cfg_attrs: Vec, +} + /// This checks error declaration as a enum declaration with only variants without fields nor /// discriminant. pub struct ErrorDef { /// The index of error item in pallet module. pub index: usize, - /// Variants ident, optional field and doc literals (ordered as declaration order) - pub variants: Vec<(syn::Ident, Option, Vec)>, + /// Variant definitions. + pub variants: Vec, /// A set of usage of instance, must be check for consistency with trait. pub instances: Vec, /// The keyword error used (contains span). @@ -87,8 +99,14 @@ impl ErrorDef { let span = variant.discriminant.as_ref().unwrap().0.span(); return Err(syn::Error::new(span, msg)) } + let cfg_attrs: Vec = helper::get_item_cfg_attrs(&variant.attrs); - Ok((variant.ident.clone(), field_ty, get_doc_literals(&variant.attrs))) + Ok(VariantDef { + ident: variant.ident.clone(), + field: field_ty, + docs: get_doc_literals(&variant.attrs), + cfg_attrs, + }) }) .collect::>()?; diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index 83a881751ef308a948e9fc69a684cd90862af145..e1efdbcc2027975d89a00b1037bd5d0af68998a3 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -33,11 +33,16 @@ pub mod inherent; pub mod origin; pub mod pallet_struct; pub mod storage; +pub mod tasks; pub mod type_value; pub mod validate_unsigned; +#[cfg(test)] +pub mod tests; + use composite::{keyword::CompositeKeyword, CompositeDef}; use frame_support_procedural_tools::generate_access_from_frame_or_crate; +use quote::ToTokens; use syn::spanned::Spanned; /// Parsed definition of a pallet. @@ -49,6 +54,8 @@ pub struct Def { pub pallet_struct: pallet_struct::PalletStructDef, pub hooks: Option, pub call: Option, + pub tasks: Option, + pub task_enum: Option, pub storages: Vec, pub error: Option, pub event: Option, @@ -84,6 +91,8 @@ impl Def { let mut pallet_struct = None; let mut hooks = None; let mut call = None; + let mut tasks = None; + let mut task_enum = None; let mut error = None; let mut event = None; let mut origin = None; @@ -118,6 +127,32 @@ impl Def { }, Some(PalletAttr::RuntimeCall(cw, span)) if call.is_none() => call = Some(call::CallDef::try_from(span, index, item, dev_mode, cw)?), + Some(PalletAttr::Tasks(_)) if tasks.is_none() => { + let item_tokens = item.to_token_stream(); + // `TasksDef::parse` needs to know if attr was provided so we artificially + // re-insert it here + tasks = Some(syn::parse2::(quote::quote! { + #[pallet::tasks_experimental] + #item_tokens + })?); + + // replace item with a no-op because it will be handled by the expansion of tasks + *item = syn::Item::Verbatim(quote::quote!()); + } + Some(PalletAttr::TaskCondition(span)) => return Err(syn::Error::new( + span, + "`#[pallet::task_condition]` can only be used on items within an `impl` statement." + )), + Some(PalletAttr::TaskIndex(span)) => return Err(syn::Error::new( + span, + "`#[pallet::task_index]` can only be used on items within an `impl` statement." + )), + Some(PalletAttr::TaskList(span)) => return Err(syn::Error::new( + span, + "`#[pallet::task_list]` can only be used on items within an `impl` statement." + )), + Some(PalletAttr::RuntimeTask(_)) if task_enum.is_none() => + task_enum = Some(syn::parse2::(item.to_token_stream())?), Some(PalletAttr::Error(span)) if error.is_none() => error = Some(error::ErrorDef::try_from(span, index, item)?), Some(PalletAttr::RuntimeEvent(span)) if event.is_none() => @@ -190,6 +225,8 @@ impl Def { return Err(syn::Error::new(item_span, msg)) } + Self::resolve_tasks(&item_span, &mut tasks, &mut task_enum, items)?; + let def = Def { item, config: config @@ -198,6 +235,8 @@ impl Def { .ok_or_else(|| syn::Error::new(item_span, "Missing `#[pallet::pallet]`"))?, hooks, call, + tasks, + task_enum, extra_constants, genesis_config, genesis_build, @@ -220,6 +259,99 @@ impl Def { Ok(def) } + /// Performs extra logic checks necessary for the `#[pallet::tasks_experimental]` feature. + fn resolve_tasks( + item_span: &proc_macro2::Span, + tasks: &mut Option, + task_enum: &mut Option, + items: &mut Vec, + ) -> syn::Result<()> { + // fallback for manual (without macros) definition of tasks impl + Self::resolve_manual_tasks_impl(tasks, task_enum, items)?; + + // fallback for manual (without macros) definition of task enum + Self::resolve_manual_task_enum(tasks, task_enum, items)?; + + // ensure that if `task_enum` is specified, `tasks` is also specified + match (&task_enum, &tasks) { + (Some(_), None) => + return Err(syn::Error::new( + *item_span, + "Missing `#[pallet::tasks_experimental]` impl", + )), + (None, Some(tasks)) => + if tasks.tasks_attr.is_none() { + return Err(syn::Error::new( + tasks.item_impl.impl_token.span(), + "A `#[pallet::tasks_experimental]` attribute must be attached to your `Task` impl if the \ + task enum has been omitted", + )) + } else { + }, + _ => (), + } + + Ok(()) + } + + /// Tries to locate task enum based on the tasks impl target if attribute is not specified + /// but impl is present. If one is found, `task_enum` is set appropriately. + fn resolve_manual_task_enum( + tasks: &Option, + task_enum: &mut Option, + items: &mut Vec, + ) -> syn::Result<()> { + let (None, Some(tasks)) = (&task_enum, &tasks) else { return Ok(()) }; + let syn::Type::Path(type_path) = &*tasks.item_impl.self_ty else { return Ok(()) }; + let type_path = type_path.path.segments.iter().collect::>(); + let (Some(seg), None) = (type_path.get(0), type_path.get(1)) else { return Ok(()) }; + let mut result = None; + for item in items { + let syn::Item::Enum(item_enum) = item else { continue }; + if item_enum.ident == seg.ident { + result = Some(syn::parse2::(item_enum.to_token_stream())?); + // replace item with a no-op because it will be handled by the expansion of + // `task_enum`. We use a no-op instead of simply removing it from the vec + // so that any indices collected by `Def::try_from` remain accurate + *item = syn::Item::Verbatim(quote::quote!()); + break + } + } + *task_enum = result; + Ok(()) + } + + /// Tries to locate a manual tasks impl (an impl impling a trait whose last path segment is + /// `Task`) in the event that one has not been found already via the attribute macro + pub fn resolve_manual_tasks_impl( + tasks: &mut Option, + task_enum: &Option, + items: &Vec, + ) -> syn::Result<()> { + let None = tasks else { return Ok(()) }; + let mut result = None; + for item in items { + let syn::Item::Impl(item_impl) = item else { continue }; + let Some((_, path, _)) = &item_impl.trait_ else { continue }; + let Some(trait_last_seg) = path.segments.last() else { continue }; + let syn::Type::Path(target_path) = &*item_impl.self_ty else { continue }; + let target_path = target_path.path.segments.iter().collect::>(); + let (Some(target_ident), None) = (target_path.get(0), target_path.get(1)) else { + continue + }; + let matches_task_enum = match task_enum { + Some(task_enum) => task_enum.item_enum.ident == target_ident.ident, + None => true, + }; + if trait_last_seg.ident == "Task" && matches_task_enum { + result = Some(syn::parse2::(item_impl.to_token_stream())?); + break + } + } + *tasks = result; + Ok(()) + } + /// Check that usage of trait `Event` is consistent with the definition, i.e. it is declared /// and trait defines type RuntimeEvent, or not declared and no trait associated type. fn check_event_usage(&self) -> syn::Result<()> { @@ -408,6 +540,11 @@ impl GenericKind { mod keyword { syn::custom_keyword!(origin); syn::custom_keyword!(call); + syn::custom_keyword!(tasks_experimental); + syn::custom_keyword!(task_enum); + syn::custom_keyword!(task_list); + syn::custom_keyword!(task_condition); + syn::custom_keyword!(task_index); syn::custom_keyword!(weight); syn::custom_keyword!(event); syn::custom_keyword!(config); @@ -472,6 +609,11 @@ enum PalletAttr { /// instead of the zero weight. So to say: it works together with `dev_mode`. RuntimeCall(Option, proc_macro2::Span), Error(proc_macro2::Span), + Tasks(proc_macro2::Span), + TaskList(proc_macro2::Span), + TaskCondition(proc_macro2::Span), + TaskIndex(proc_macro2::Span), + RuntimeTask(proc_macro2::Span), RuntimeEvent(proc_macro2::Span), RuntimeOrigin(proc_macro2::Span), Inherent(proc_macro2::Span), @@ -490,8 +632,13 @@ impl PalletAttr { Self::Config(span, _) => *span, Self::Pallet(span) => *span, Self::Hooks(span) => *span, - Self::RuntimeCall(_, span) => *span, + Self::Tasks(span) => *span, + Self::TaskCondition(span) => *span, + Self::TaskIndex(span) => *span, + Self::TaskList(span) => *span, Self::Error(span) => *span, + Self::RuntimeTask(span) => *span, + Self::RuntimeCall(_, span) => *span, Self::RuntimeEvent(span) => *span, Self::RuntimeOrigin(span) => *span, Self::Inherent(span) => *span, @@ -535,6 +682,16 @@ impl syn::parse::Parse for PalletAttr { false => Some(InheritedCallWeightAttr::parse(&content)?), }; Ok(PalletAttr::RuntimeCall(attr, span)) + } else if lookahead.peek(keyword::tasks_experimental) { + Ok(PalletAttr::Tasks(content.parse::()?.span())) + } else if lookahead.peek(keyword::task_enum) { + Ok(PalletAttr::RuntimeTask(content.parse::()?.span())) + } else if lookahead.peek(keyword::task_condition) { + Ok(PalletAttr::TaskCondition(content.parse::()?.span())) + } else if lookahead.peek(keyword::task_index) { + Ok(PalletAttr::TaskIndex(content.parse::()?.span())) + } else if lookahead.peek(keyword::task_list) { + Ok(PalletAttr::TaskList(content.parse::()?.span())) } else if lookahead.peek(keyword::error) { Ok(PalletAttr::Error(content.parse::()?.span())) } else if lookahead.peek(keyword::event) { diff --git a/substrate/frame/support/procedural/src/pallet/parse/tasks.rs b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..6405bb415a6f1eed18cab8d4f6c40f8a37f2048b --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/tasks.rs @@ -0,0 +1,968 @@ +// 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. + +//! Home of the parsing code for the Tasks API + +use std::collections::HashSet; + +#[cfg(test)] +use crate::assert_parse_error_matches; + +#[cfg(test)] +use crate::pallet::parse::tests::simulate_manifest_dir; + +use derive_syn_parse::Parse; +use frame_support_procedural_tools::generate_access_from_frame_or_crate; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; +use syn::{ + parse::ParseStream, + parse2, + spanned::Spanned, + token::{Bracket, Paren, PathSep, Pound}, + Attribute, Error, Expr, Ident, ImplItem, ImplItemFn, ItemEnum, ItemImpl, LitInt, Path, + PathArguments, Result, TypePath, +}; + +pub mod keywords { + use syn::custom_keyword; + + custom_keyword!(tasks_experimental); + custom_keyword!(task_enum); + custom_keyword!(task_list); + custom_keyword!(task_condition); + custom_keyword!(task_index); + custom_keyword!(task_weight); + custom_keyword!(pallet); +} + +/// Represents the `#[pallet::tasks_experimental]` attribute and its attached item. Also includes +/// metadata about the linked [`TaskEnumDef`] if applicable. +#[derive(Clone, Debug)] +pub struct TasksDef { + pub tasks_attr: Option, + pub tasks: Vec, + pub item_impl: ItemImpl, + /// Path to `frame_support` + pub scrate: Path, + pub enum_ident: Ident, + pub enum_arguments: PathArguments, +} + +impl syn::parse::Parse for TasksDef { + fn parse(input: ParseStream) -> Result { + let item_impl: ItemImpl = input.parse()?; + let (tasks_attrs, normal_attrs) = partition_tasks_attrs(&item_impl); + let tasks_attr = match tasks_attrs.first() { + Some(attr) => Some(parse2::(attr.to_token_stream())?), + None => None, + }; + if let Some(extra_tasks_attr) = tasks_attrs.get(1) { + return Err(Error::new( + extra_tasks_attr.span(), + "unexpected extra `#[pallet::tasks_experimental]` attribute", + )) + } + let tasks: Vec = if tasks_attr.is_some() { + item_impl + .items + .clone() + .into_iter() + .filter(|impl_item| matches!(impl_item, ImplItem::Fn(_))) + .map(|item| parse2::(item.to_token_stream())) + .collect::>()? + } else { + Vec::new() + }; + let mut task_indices = HashSet::::new(); + for task in tasks.iter() { + let task_index = &task.index_attr.meta.index; + if !task_indices.insert(task_index.clone()) { + return Err(Error::new( + task_index.span(), + format!("duplicate task index `{}`", task_index), + )) + } + } + let mut item_impl = item_impl; + item_impl.attrs = normal_attrs; + + // we require the path on the impl to be a TypePath + let enum_path = parse2::(item_impl.self_ty.to_token_stream())?; + let segments = enum_path.path.segments.iter().collect::>(); + let (Some(last_seg), None) = (segments.get(0), segments.get(1)) else { + return Err(Error::new( + enum_path.span(), + "if specified manually, the task enum must be defined locally in this \ + pallet and cannot be a re-export", + )) + }; + let enum_ident = last_seg.ident.clone(); + let enum_arguments = last_seg.arguments.clone(); + + // We do this here because it would be improper to do something fallible like this at + // the expansion phase. Fallible stuff should happen during parsing. + let scrate = generate_access_from_frame_or_crate("frame-support")?; + + Ok(TasksDef { tasks_attr, item_impl, tasks, scrate, enum_ident, enum_arguments }) + } +} + +/// Parsing for a `#[pallet::tasks_experimental]` attr. +pub type PalletTasksAttr = PalletTaskAttr; + +/// Parsing for any of the attributes that can be used within a `#[pallet::tasks_experimental]` +/// [`ItemImpl`]. +pub type TaskAttr = PalletTaskAttr; + +/// Parsing for a `#[pallet::task_index]` attr. +pub type TaskIndexAttr = PalletTaskAttr; + +/// Parsing for a `#[pallet::task_condition]` attr. +pub type TaskConditionAttr = PalletTaskAttr; + +/// Parsing for a `#[pallet::task_list]` attr. +pub type TaskListAttr = PalletTaskAttr; + +/// Parsing for a `#[pallet::task_weight]` attr. +pub type TaskWeightAttr = PalletTaskAttr; + +/// Parsing for a `#[pallet:task_enum]` attr. +pub type PalletTaskEnumAttr = PalletTaskAttr; + +/// Parsing for a manually-specified (or auto-generated) task enum, optionally including the +/// attached `#[pallet::task_enum]` attribute. +#[derive(Clone, Debug)] +pub struct TaskEnumDef { + pub attr: Option, + pub item_enum: ItemEnum, + pub scrate: Path, + pub type_use_generics: TokenStream2, +} + +impl syn::parse::Parse for TaskEnumDef { + fn parse(input: ParseStream) -> Result { + let mut item_enum = input.parse::()?; + let attr = extract_pallet_attr(&mut item_enum)?; + let attr = match attr { + Some(attr) => Some(parse2(attr)?), + None => None, + }; + + // We do this here because it would be improper to do something fallible like this at + // the expansion phase. Fallible stuff should happen during parsing. + let scrate = generate_access_from_frame_or_crate("frame-support")?; + + let type_use_generics = quote!(T); + + Ok(TaskEnumDef { attr, item_enum, scrate, type_use_generics }) + } +} + +/// Represents an individual tasks within a [`TasksDef`]. +#[derive(Debug, Clone)] +pub struct TaskDef { + pub index_attr: TaskIndexAttr, + pub condition_attr: TaskConditionAttr, + pub list_attr: TaskListAttr, + pub weight_attr: TaskWeightAttr, + pub normal_attrs: Vec, + pub item: ImplItemFn, + pub arg_names: Vec, +} + +impl syn::parse::Parse for TaskDef { + fn parse(input: ParseStream) -> Result { + let item = input.parse::()?; + // we only want to activate TaskAttrType parsing errors for tasks-related attributes, + // so we filter them here + let (task_attrs, normal_attrs) = partition_task_attrs(&item); + + let task_attrs: Vec = task_attrs + .into_iter() + .map(|attr| parse2(attr.to_token_stream())) + .collect::>()?; + + let Some(index_attr) = task_attrs + .iter() + .find(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_))) + .cloned() + else { + return Err(Error::new( + item.sig.ident.span(), + "missing `#[pallet::task_index(..)]` attribute", + )) + }; + + let Some(condition_attr) = task_attrs + .iter() + .find(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_))) + .cloned() + else { + return Err(Error::new( + item.sig.ident.span(), + "missing `#[pallet::task_condition(..)]` attribute", + )) + }; + + let Some(list_attr) = task_attrs + .iter() + .find(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_))) + .cloned() + else { + return Err(Error::new( + item.sig.ident.span(), + "missing `#[pallet::task_list(..)]` attribute", + )) + }; + + let Some(weight_attr) = task_attrs + .iter() + .find(|attr| matches!(attr.meta, TaskAttrMeta::TaskWeight(_))) + .cloned() + else { + return Err(Error::new( + item.sig.ident.span(), + "missing `#[pallet::task_weight(..)]` attribute", + )) + }; + + if let Some(duplicate) = task_attrs + .iter() + .filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskCondition(_))) + .collect::>() + .get(1) + { + return Err(Error::new( + duplicate.span(), + "unexpected extra `#[pallet::task_condition(..)]` attribute", + )) + } + + if let Some(duplicate) = task_attrs + .iter() + .filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskList(_))) + .collect::>() + .get(1) + { + return Err(Error::new( + duplicate.span(), + "unexpected extra `#[pallet::task_list(..)]` attribute", + )) + } + + if let Some(duplicate) = task_attrs + .iter() + .filter(|attr| matches!(attr.meta, TaskAttrMeta::TaskIndex(_))) + .collect::>() + .get(1) + { + return Err(Error::new( + duplicate.span(), + "unexpected extra `#[pallet::task_index(..)]` attribute", + )) + } + + let mut arg_names = vec![]; + for input in item.sig.inputs.iter() { + match input { + syn::FnArg::Typed(pat_type) => match &*pat_type.pat { + syn::Pat::Ident(ident) => arg_names.push(ident.ident.clone()), + _ => return Err(Error::new(input.span(), "unexpected pattern type")), + }, + _ => return Err(Error::new(input.span(), "unexpected function argument type")), + } + } + + let index_attr = index_attr.try_into().expect("we check the type above; QED"); + let condition_attr = condition_attr.try_into().expect("we check the type above; QED"); + let list_attr = list_attr.try_into().expect("we check the type above; QED"); + let weight_attr = weight_attr.try_into().expect("we check the type above; QED"); + + Ok(TaskDef { + index_attr, + condition_attr, + list_attr, + weight_attr, + normal_attrs, + item, + arg_names, + }) + } +} + +/// The contents of a [`TasksDef`]-related attribute. +#[derive(Parse, Debug, Clone)] +pub enum TaskAttrMeta { + #[peek(keywords::task_list, name = "#[pallet::task_list(..)]")] + TaskList(TaskListAttrMeta), + #[peek(keywords::task_index, name = "#[pallet::task_index(..)")] + TaskIndex(TaskIndexAttrMeta), + #[peek(keywords::task_condition, name = "#[pallet::task_condition(..)")] + TaskCondition(TaskConditionAttrMeta), + #[peek(keywords::task_weight, name = "#[pallet::task_weight(..)")] + TaskWeight(TaskWeightAttrMeta), +} + +/// The contents of a `#[pallet::task_list]` attribute. +#[derive(Parse, Debug, Clone)] +pub struct TaskListAttrMeta { + pub task_list: keywords::task_list, + #[paren] + _paren: Paren, + #[inside(_paren)] + pub expr: Expr, +} + +/// The contents of a `#[pallet::task_index]` attribute. +#[derive(Parse, Debug, Clone)] +pub struct TaskIndexAttrMeta { + pub task_index: keywords::task_index, + #[paren] + _paren: Paren, + #[inside(_paren)] + pub index: LitInt, +} + +/// The contents of a `#[pallet::task_condition]` attribute. +#[derive(Parse, Debug, Clone)] +pub struct TaskConditionAttrMeta { + pub task_condition: keywords::task_condition, + #[paren] + _paren: Paren, + #[inside(_paren)] + pub expr: Expr, +} + +/// The contents of a `#[pallet::task_weight]` attribute. +#[derive(Parse, Debug, Clone)] +pub struct TaskWeightAttrMeta { + pub task_weight: keywords::task_weight, + #[paren] + _paren: Paren, + #[inside(_paren)] + pub expr: Expr, +} + +/// The contents of a `#[pallet::task]` attribute. +#[derive(Parse, Debug, Clone)] +pub struct PalletTaskAttr { + pub pound: Pound, + #[bracket] + _bracket: Bracket, + #[inside(_bracket)] + pub pallet: keywords::pallet, + #[inside(_bracket)] + pub colons: PathSep, + #[inside(_bracket)] + pub meta: T, +} + +impl ToTokens for TaskListAttrMeta { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let task_list = self.task_list; + let expr = &self.expr; + tokens.extend(quote!(#task_list(#expr))); + } +} + +impl ToTokens for TaskConditionAttrMeta { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let task_condition = self.task_condition; + let expr = &self.expr; + tokens.extend(quote!(#task_condition(#expr))); + } +} + +impl ToTokens for TaskWeightAttrMeta { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let task_weight = self.task_weight; + let expr = &self.expr; + tokens.extend(quote!(#task_weight(#expr))); + } +} + +impl ToTokens for TaskIndexAttrMeta { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let task_index = self.task_index; + let index = &self.index; + tokens.extend(quote!(#task_index(#index))) + } +} + +impl ToTokens for TaskAttrMeta { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + TaskAttrMeta::TaskList(list) => tokens.extend(list.to_token_stream()), + TaskAttrMeta::TaskIndex(index) => tokens.extend(index.to_token_stream()), + TaskAttrMeta::TaskCondition(condition) => tokens.extend(condition.to_token_stream()), + TaskAttrMeta::TaskWeight(weight) => tokens.extend(weight.to_token_stream()), + } + } +} + +impl ToTokens for PalletTaskAttr { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let pound = self.pound; + let pallet = self.pallet; + let colons = self.colons; + let meta = &self.meta; + tokens.extend(quote!(#pound[#pallet #colons #meta])); + } +} + +impl TryFrom> for TaskIndexAttr { + type Error = syn::Error; + + fn try_from(value: PalletTaskAttr) -> Result { + let pound = value.pound; + let pallet = value.pallet; + let colons = value.colons; + match value.meta { + TaskAttrMeta::TaskIndex(meta) => parse2(quote!(#pound[#pallet #colons #meta])), + _ => + return Err(Error::new( + value.span(), + format!("`{:?}` cannot be converted to a `TaskIndexAttr`", value.meta), + )), + } + } +} + +impl TryFrom> for TaskConditionAttr { + type Error = syn::Error; + + fn try_from(value: PalletTaskAttr) -> Result { + let pound = value.pound; + let pallet = value.pallet; + let colons = value.colons; + match value.meta { + TaskAttrMeta::TaskCondition(meta) => parse2(quote!(#pound[#pallet #colons #meta])), + _ => + return Err(Error::new( + value.span(), + format!("`{:?}` cannot be converted to a `TaskConditionAttr`", value.meta), + )), + } + } +} + +impl TryFrom> for TaskWeightAttr { + type Error = syn::Error; + + fn try_from(value: PalletTaskAttr) -> Result { + let pound = value.pound; + let pallet = value.pallet; + let colons = value.colons; + match value.meta { + TaskAttrMeta::TaskWeight(meta) => parse2(quote!(#pound[#pallet #colons #meta])), + _ => + return Err(Error::new( + value.span(), + format!("`{:?}` cannot be converted to a `TaskWeightAttr`", value.meta), + )), + } + } +} + +impl TryFrom> for TaskListAttr { + type Error = syn::Error; + + fn try_from(value: PalletTaskAttr) -> Result { + let pound = value.pound; + let pallet = value.pallet; + let colons = value.colons; + match value.meta { + TaskAttrMeta::TaskList(meta) => parse2(quote!(#pound[#pallet #colons #meta])), + _ => + return Err(Error::new( + value.span(), + format!("`{:?}` cannot be converted to a `TaskListAttr`", value.meta), + )), + } + } +} + +fn extract_pallet_attr(item_enum: &mut ItemEnum) -> Result> { + let mut duplicate = None; + let mut attr = None; + item_enum.attrs = item_enum + .attrs + .iter() + .filter(|found_attr| { + let segs = found_attr + .path() + .segments + .iter() + .map(|seg| seg.ident.clone()) + .collect::>(); + let (Some(seg1), Some(_), None) = (segs.get(0), segs.get(1), segs.get(2)) else { + return true + }; + if seg1 != "pallet" { + return true + } + if attr.is_some() { + duplicate = Some(found_attr.span()); + } + attr = Some(found_attr.to_token_stream()); + false + }) + .cloned() + .collect(); + if let Some(span) = duplicate { + return Err(Error::new(span, "only one `#[pallet::_]` attribute is supported on this item")) + } + Ok(attr) +} + +fn partition_tasks_attrs(item_impl: &ItemImpl) -> (Vec, Vec) { + item_impl.attrs.clone().into_iter().partition(|attr| { + let mut path_segs = attr.path().segments.iter(); + let (Some(prefix), Some(suffix), None) = + (path_segs.next(), path_segs.next(), path_segs.next()) + else { + return false + }; + prefix.ident == "pallet" && suffix.ident == "tasks_experimental" + }) +} + +fn partition_task_attrs(item: &ImplItemFn) -> (Vec, Vec) { + item.attrs.clone().into_iter().partition(|attr| { + let mut path_segs = attr.path().segments.iter(); + let (Some(prefix), Some(suffix)) = (path_segs.next(), path_segs.next()) else { + return false + }; + // N.B: the `PartialEq` impl between `Ident` and `&str` is more efficient than + // parsing and makes no stack or heap allocations + prefix.ident == "pallet" && + (suffix.ident == "tasks_experimental" || + suffix.ident == "task_list" || + suffix.ident == "task_condition" || + suffix.ident == "task_weight" || + suffix.ident == "task_index") + }) +} + +#[test] +fn test_parse_task_list_() { + parse2::(quote!(#[pallet::task_list(Something::iter())])).unwrap(); + parse2::(quote!(#[pallet::task_list(Numbers::::iter_keys())])).unwrap(); + parse2::(quote!(#[pallet::task_list(iter())])).unwrap(); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::task_list()])), + "expected an expression" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::task_list])), + "expected parentheses" + ); +} + +#[test] +fn test_parse_task_index() { + parse2::(quote!(#[pallet::task_index(3)])).unwrap(); + parse2::(quote!(#[pallet::task_index(0)])).unwrap(); + parse2::(quote!(#[pallet::task_index(17)])).unwrap(); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::task_index])), + "expected parentheses" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::task_index("hey")])), + "expected integer literal" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::task_index(0.3)])), + "expected integer literal" + ); +} + +#[test] +fn test_parse_task_condition() { + parse2::(quote!(#[pallet::task_condition(|x| x.is_some())])).unwrap(); + parse2::(quote!(#[pallet::task_condition(|_x| some_expr())])).unwrap(); + parse2::(quote!(#[pallet::task_condition(|| some_expr())])).unwrap(); + parse2::(quote!(#[pallet::task_condition(some_expr())])).unwrap(); +} + +#[test] +fn test_parse_tasks_attr() { + parse2::(quote!(#[pallet::tasks_experimental])).unwrap(); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::taskss])), + "expected `tasks_experimental`" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::tasks_])), + "expected `tasks_experimental`" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pal::tasks])), + "expected `pallet`" + ); + assert_parse_error_matches!( + parse2::(quote!(#[pallet::tasks_experimental()])), + "unexpected token" + ); +} + +#[test] +fn test_parse_tasks_def_basic() { + simulate_manifest_dir("../../examples/basic", || { + let parsed = parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + /// Add a pair of numbers into the totals and remove them. + #[pallet::task_list(Numbers::::iter_keys())] + #[pallet::task_condition(|i| Numbers::::contains_key(i))] + #[pallet::task_index(0)] + #[pallet::task_weight(0)] + pub fn add_number_into_total(i: u32) -> DispatchResult { + let v = Numbers::::take(i).ok_or(Error::::NotFound)?; + Total::::mutate(|(total_keys, total_values)| { + *total_keys += i; + *total_values += v; + }); + Ok(()) + } + } + }) + .unwrap(); + assert_eq!(parsed.tasks.len(), 1); + }); +} + +#[test] +fn test_parse_tasks_def_basic_increment_decrement() { + simulate_manifest_dir("../../examples/basic", || { + let parsed = parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + /// Get the value and check if it can be incremented + #[pallet::task_index(0)] + #[pallet::task_condition(|| { + let value = Value::::get().unwrap(); + value < 255 + })] + #[pallet::task_list(Vec::>::new())] + #[pallet::task_weight(0)] + fn increment() -> DispatchResult { + let value = Value::::get().unwrap_or_default(); + if value >= 255 { + Err(Error::::ValueOverflow.into()) + } else { + let new_val = value.checked_add(1).ok_or(Error::::ValueOverflow)?; + Value::::put(new_val); + Pallet::::deposit_event(Event::Incremented { new_val }); + Ok(()) + } + } + + // Get the value and check if it can be decremented + #[pallet::task_index(1)] + #[pallet::task_condition(|| { + let value = Value::::get().unwrap(); + value > 0 + })] + #[pallet::task_list(Vec::>::new())] + #[pallet::task_weight(0)] + fn decrement() -> DispatchResult { + let value = Value::::get().unwrap_or_default(); + if value == 0 { + Err(Error::::ValueUnderflow.into()) + } else { + let new_val = value.checked_sub(1).ok_or(Error::::ValueUnderflow)?; + Value::::put(new_val); + Pallet::::deposit_event(Event::Decremented { new_val }); + Ok(()) + } + } + } + }) + .unwrap(); + assert_eq!(parsed.tasks.len(), 2); + }); +} + +#[test] +fn test_parse_tasks_def_duplicate_index() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_list(Something::iter())] + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_index(0)] + #[pallet::task_weight(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + + #[pallet::task_list(Numbers::::iter_keys())] + #[pallet::task_condition(|i| Numbers::::contains_key(i))] + #[pallet::task_index(0)] + #[pallet::task_weight(0)] + pub fn bar(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + "duplicate task index `0`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_missing_task_list() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_index(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"missing `#\[pallet::task_list\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_missing_task_condition() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_list(Something::iter())] + #[pallet::task_index(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"missing `#\[pallet::task_condition\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_missing_task_index() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_list(Something::iter())] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"missing `#\[pallet::task_index\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_missing_task_weight() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_list(Something::iter())] + #[pallet::task_index(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"missing `#\[pallet::task_weight\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_unexpected_extra_task_list_attr() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_index(0)] + #[pallet::task_weight(0)] + #[pallet::task_list(Something::iter())] + #[pallet::task_list(SomethingElse::iter())] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"unexpected extra `#\[pallet::task_list\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_unexpected_extra_task_condition_attr() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_condition(|i| i % 4 == 0)] + #[pallet::task_index(0)] + #[pallet::task_list(Something::iter())] + #[pallet::task_weight(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"unexpected extra `#\[pallet::task_condition\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_unexpected_extra_task_index_attr() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + impl, I: 'static> Pallet { + #[pallet::task_condition(|i| i % 2 == 0)] + #[pallet::task_index(0)] + #[pallet::task_index(0)] + #[pallet::task_list(Something::iter())] + #[pallet::task_weight(0)] + pub fn foo(i: u32) -> DispatchResult { + Ok(()) + } + } + }), + r"unexpected extra `#\[pallet::task_index\(\.\.\)\]`" + ); + }); +} + +#[test] +fn test_parse_tasks_def_extra_tasks_attribute() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::tasks_experimental] + #[pallet::tasks_experimental] + impl, I: 'static> Pallet {} + }), + r"unexpected extra `#\[pallet::tasks_experimental\]` attribute" + ); + }); +} + +#[test] +fn test_parse_task_enum_def_basic() { + simulate_manifest_dir("../../examples/basic", || { + parse2::(quote! { + #[pallet::task_enum] + pub enum Task { + Increment, + Decrement, + } + }) + .unwrap(); + }); +} + +#[test] +fn test_parse_task_enum_def_non_task_name() { + simulate_manifest_dir("../../examples/basic", || { + parse2::(quote! { + #[pallet::task_enum] + pub enum Something { + Foo + } + }) + .unwrap(); + }); +} + +#[test] +fn test_parse_task_enum_def_missing_attr_allowed() { + simulate_manifest_dir("../../examples/basic", || { + parse2::(quote! { + pub enum Task { + Increment, + Decrement, + } + }) + .unwrap(); + }); +} + +#[test] +fn test_parse_task_enum_def_missing_attr_alternate_name_allowed() { + simulate_manifest_dir("../../examples/basic", || { + parse2::(quote! { + pub enum Foo { + Red, + } + }) + .unwrap(); + }); +} + +#[test] +fn test_parse_task_enum_def_wrong_attr() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::something] + pub enum Task { + Increment, + Decrement, + } + }), + "expected `task_enum`" + ); + }); +} + +#[test] +fn test_parse_task_enum_def_wrong_item() { + simulate_manifest_dir("../../examples/basic", || { + assert_parse_error_matches!( + parse2::(quote! { + #[pallet::task_enum] + pub struct Something; + }), + "expected `enum`" + ); + }); +} diff --git a/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a3661f3076d94511323e466ffe2ba6999b64f717 --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/tests/mod.rs @@ -0,0 +1,264 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{panic, sync::Mutex}; +use syn::parse_quote; + +#[doc(hidden)] +pub mod __private { + pub use regex; +} + +/// Allows you to assert that the input expression resolves to an error whose string +/// representation matches the specified regex literal. +/// +/// ## Example: +/// +/// ``` +/// use super::tasks::*; +/// +/// assert_parse_error_matches!( +/// parse2::(quote! { +/// #[pallet::task_enum] +/// pub struct Something; +/// }), +/// "expected `enum`" +/// ); +/// ``` +/// +/// More complex regular expressions are also possible (anything that could pass as a regex for +/// use with the [`regex`] crate.): +/// +/// ```ignore +/// assert_parse_error_matches!( +/// parse2::(quote! { +/// #[pallet::tasks_experimental] +/// impl, I: 'static> Pallet { +/// #[pallet::task_condition(|i| i % 2 == 0)] +/// #[pallet::task_index(0)] +/// pub fn foo(i: u32) -> DispatchResult { +/// Ok(()) +/// } +/// } +/// }), +/// r"missing `#\[pallet::task_list\(\.\.\)\]`" +/// ); +/// ``` +/// +/// Although this is primarily intended to be used with parsing errors, this macro is general +/// enough that it will work with any error with a reasonable [`core::fmt::Display`] impl. +#[macro_export] +macro_rules! assert_parse_error_matches { + ($expr:expr, $reg:literal) => { + match $expr { + Ok(_) => panic!("Expected an `Error(..)`, but got Ok(..)"), + Err(e) => { + let error_message = e.to_string(); + let re = $crate::pallet::parse::tests::__private::regex::Regex::new($reg) + .expect("Invalid regex pattern"); + assert!( + re.is_match(&error_message), + "Error message \"{}\" does not match the pattern \"{}\"", + error_message, + $reg + ); + }, + } + }; +} + +/// Allows you to assert that an entire pallet parses successfully. A custom syntax is used for +/// specifying arguments so please pay attention to the docs below. +/// +/// The general syntax is: +/// +/// ```ignore +/// assert_pallet_parses! { +/// #[manifest_dir("../../examples/basic")] +/// #[frame_support::pallet] +/// pub mod pallet { +/// #[pallet::config] +/// pub trait Config: frame_system::Config {} +/// +/// #[pallet::pallet] +/// pub struct Pallet(_); +/// } +/// }; +/// ``` +/// +/// The `#[manifest_dir(..)]` attribute _must_ be specified as the _first_ attribute on the +/// pallet module, and should reference the relative (to your current directory) path of a +/// directory containing containing the `Cargo.toml` of a valid pallet. Typically you will only +/// ever need to use the `examples/basic` pallet, but sometimes it might be advantageous to +/// specify a different one that has additional dependencies. +/// +/// The reason this must be specified is that our underlying parsing of pallets depends on +/// reaching out into the file system to look for particular `Cargo.toml` dependencies via the +/// [`generate_access_from_frame_or_crate`] method, so to simulate this properly in a proc +/// macro crate, we need to temporarily convince this function that we are running from the +/// directory of a valid pallet. +#[macro_export] +macro_rules! assert_pallet_parses { + ( + #[manifest_dir($manifest_dir:literal)] + $($tokens:tt)* + ) => { + { + let mut pallet: Option<$crate::pallet::parse::Def> = None; + $crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, core::panic::AssertUnwindSafe(|| { + pallet = Some($crate::pallet::parse::Def::try_from(syn::parse_quote! { + $($tokens)* + }, false).unwrap()); + })); + pallet.unwrap() + } + } +} + +/// Similar to [`assert_pallet_parses`], except this instead expects the pallet not to parse, +/// and allows you to specify a regex matching the expected parse error. +/// +/// This is identical syntactically to [`assert_pallet_parses`] in every way except there is a +/// second attribute that must be specified immediately after `#[manifest_dir(..)]` which is +/// `#[error_regex(..)]` which should contain a string/regex literal designed to match what you +/// consider to be the correct parsing error we should see when we try to parse this particular +/// pallet. +/// +/// ## Example: +/// +/// ``` +/// assert_pallet_parse_error! { +/// #[manifest_dir("../../examples/basic")] +/// #[error_regex("Missing `\\#\\[pallet::pallet\\]`")] +/// #[frame_support::pallet] +/// pub mod pallet { +/// #[pallet::config] +/// pub trait Config: frame_system::Config {} +/// } +/// } +/// ``` +#[macro_export] +macro_rules! assert_pallet_parse_error { + ( + #[manifest_dir($manifest_dir:literal)] + #[error_regex($reg:literal)] + $($tokens:tt)* + ) => { + $crate::pallet::parse::tests::simulate_manifest_dir($manifest_dir, || { + $crate::assert_parse_error_matches!( + $crate::pallet::parse::Def::try_from( + parse_quote! { + $($tokens)* + }, + false + ), + $reg + ); + }); + } +} + +/// Safely runs the specified `closure` while simulating an alternative `CARGO_MANIFEST_DIR`, +/// restoring `CARGO_MANIFEST_DIR` to its original value upon completion regardless of whether +/// the closure panics. +/// +/// This is useful in tests of `Def::try_from` and other pallet-related methods that internally +/// make use of [`generate_access_from_frame_or_crate`], which is sensitive to entries in the +/// "current" `Cargo.toml` files. +/// +/// This function uses a [`Mutex`] to avoid a race condition created when multiple tests try to +/// modify and then restore the `CARGO_MANIFEST_DIR` ENV var in an overlapping way. +pub fn simulate_manifest_dir, F: FnOnce() + std::panic::UnwindSafe>( + path: P, + closure: F, +) { + use std::{env::*, path::*}; + + /// Ensures that only one thread can modify/restore the `CARGO_MANIFEST_DIR` ENV var at a time, + /// avoiding a race condition because `cargo test` runs tests in parallel. + /// + /// Although this forces all tests that use [`simulate_manifest_dir`] to run sequentially with + /// respect to each other, this is still several orders of magnitude faster than using UI + /// tests, even if they are run in parallel. + static MANIFEST_DIR_LOCK: Mutex<()> = Mutex::new(()); + + // avoid race condition when swapping out `CARGO_MANIFEST_DIR` + let guard = MANIFEST_DIR_LOCK.lock().unwrap(); + + // obtain the current/original `CARGO_MANIFEST_DIR` + let orig = PathBuf::from( + var("CARGO_MANIFEST_DIR").expect("failed to read ENV var `CARGO_MANIFEST_DIR`"), + ); + + // set `CARGO_MANIFEST_DIR` to the provided path, relative to current working dir + set_var("CARGO_MANIFEST_DIR", orig.join(path.as_ref())); + + // safely run closure catching any panics + let result = panic::catch_unwind(closure); + + // restore original `CARGO_MANIFEST_DIR` before unwinding + set_var("CARGO_MANIFEST_DIR", &orig); + + // unlock the mutex so we don't poison it if there is a panic + drop(guard); + + // unwind any panics originally encountered when running closure + result.unwrap(); +} + +mod tasks; + +#[test] +fn test_parse_minimal_pallet() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} + +#[test] +fn test_parse_pallet_missing_pallet() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex("Missing `\\#\\[pallet::pallet\\]`")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + } + } +} + +#[test] +fn test_parse_pallet_missing_config() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex("Missing `\\#\\[pallet::config\\]`")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::pallet] + pub struct Pallet(_); + } + } +} diff --git a/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs b/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..9f143628404734d5510de9e5997deec2a63644ea --- /dev/null +++ b/substrate/frame/support/procedural/src/pallet/parse/tests/tasks.rs @@ -0,0 +1,240 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use syn::parse_quote; + +#[test] +fn test_parse_pallet_with_task_enum_missing_impl() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex("Missing `\\#\\[pallet::tasks_experimental\\]` impl")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::task_enum] + pub enum Task { + Something, + } + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + } +} + +#[test] +fn test_parse_pallet_with_task_enum_wrong_attribute() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex("expected one of")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::wrong_attribute] + pub enum Task { + Something, + } + + #[pallet::task_list] + impl frame_support::traits::Task for Task + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + } +} + +#[test] +fn test_parse_pallet_missing_task_enum() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::tasks_experimental] + #[cfg(test)] // aha, this means it's being eaten + impl frame_support::traits::Task for Task + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} + +#[test] +fn test_parse_pallet_task_list_in_wrong_place() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex("can only be used on items within an `impl` statement.")] + #[frame_support::pallet] + pub mod pallet { + pub enum MyCustomTaskEnum { + Something, + } + + #[pallet::task_list] + pub fn something() { + println!("hey"); + } + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + } +} + +#[test] +fn test_parse_pallet_manual_tasks_impl_without_manual_tasks_enum() { + assert_pallet_parse_error! { + #[manifest_dir("../../examples/basic")] + #[error_regex(".*attribute must be attached to your.*")] + #[frame_support::pallet] + pub mod pallet { + + impl frame_support::traits::Task for Task + where + T: TypeInfo, + { + type Enumeration = sp_std::vec::IntoIter>; + + fn iter() -> Self::Enumeration { + sp_std::vec![Task::increment, Task::decrement].into_iter() + } + } + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + } +} + +#[test] +fn test_parse_pallet_manual_task_enum_non_manual_impl() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + pub enum MyCustomTaskEnum { + Something, + } + + #[pallet::tasks_experimental] + impl frame_support::traits::Task for MyCustomTaskEnum + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} + +#[test] +fn test_parse_pallet_non_manual_task_enum_manual_impl() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + #[pallet::task_enum] + pub enum MyCustomTaskEnum { + Something, + } + + impl frame_support::traits::Task for MyCustomTaskEnum + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} + +#[test] +fn test_parse_pallet_manual_task_enum_manual_impl() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + pub enum MyCustomTaskEnum { + Something, + } + + impl frame_support::traits::Task for MyCustomTaskEnum + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} + +#[test] +fn test_parse_pallet_manual_task_enum_mismatch_ident() { + assert_pallet_parses! { + #[manifest_dir("../../examples/basic")] + #[frame_support::pallet] + pub mod pallet { + pub enum WrongIdent { + Something, + } + + #[pallet::tasks_experimental] + impl frame_support::traits::Task for MyCustomTaskEnum + where + T: TypeInfo, + {} + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + } + }; +} diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index bc5cc7fdda5c7513870450362237f9e9805a3696..6d1b9507797d9834691f9a527e73cd352e1c8d69 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -8,12 +8,15 @@ homepage = "https://substrate.io" repository.workspace = true description = "Proc macro helpers for procedural macros" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.39", features = ["extra-traits", "full", "visit"] } +syn = { version = "2.0.48", features = ["extra-traits", "full", "visit"] } 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 6040449df65651d4a1714a3730f5a33aadfca427..d2d34d477146882c30dcb47ed557bb627ce8aed6 100644 --- a/substrate/frame/support/procedural/tools/derive/Cargo.toml +++ b/substrate/frame/support/procedural/tools/derive/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Use to derive parsing for parsing struct." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,4 +20,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.39", features = ["extra-traits", "full", "parsing", "proc-macro"] } +syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index c81dba127667b239784cb10ac3bb1823f4c0a38d..4a313551aca634b88bed7d0f989943f7075a5050 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -664,7 +664,7 @@ mod weight_tests { use sp_runtime::{generic, traits::BlakeTwo256}; use sp_weights::RuntimeDbWeight; - pub use self::frame_system::{Call, Config, Pallet}; + pub use self::frame_system::{Call, Config}; fn from_actual_ref_time(ref_time: Option) -> PostDispatchInfo { PostDispatchInfo { @@ -695,6 +695,7 @@ mod weight_tests { type BaseCallFilter: crate::traits::Contains; type RuntimeOrigin; type RuntimeCall; + type RuntimeTask; type PalletInfo: crate::traits::PalletInfo; type DbWeight: Get; } @@ -791,6 +792,7 @@ mod weight_tests { type BaseCallFilter = crate::traits::Everything; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; type DbWeight = DbWeight; type PalletInfo = PalletInfo; } diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index fd348f62b4f725de7be538b8def864e77a953a08..af1f99be1031b0aa16ba60c1047d58e2c91274a3 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -849,7 +849,7 @@ pub mod pallet_prelude { }, traits::{ BuildGenesisConfig, ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, - IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet, + IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, Task, TypedGet, }, Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity, PartialEqNoBound, RuntimeDebugNoBound, Twox128, Twox256, Twox64Concat, @@ -2674,6 +2674,61 @@ pub mod pallet_macros { /// } /// ``` pub use frame_support_procedural::storage; + /// This attribute is attached to a function inside an `impl` block annoated with + /// [`pallet::tasks_experimental`](`tasks_experimental`) to define the conditions for a + /// given work item to be valid. + /// + /// It takes a closure as input, which is then used to define the condition. The closure + /// should have the same signature as the function it is attached to, except that it should + /// return a `bool` instead. + pub use frame_support_procedural::task_condition; + /// This attribute is attached to a function inside an `impl` block annoated with + /// [`pallet::tasks_experimental`](`tasks_experimental`) to define the index of a given + /// work item. + /// + /// It takes an integer literal as input, which is then used to define the index. This + /// index should be unique for each function in the `impl` block. + pub use frame_support_procedural::task_index; + /// This attribute is attached to a function inside an `impl` block annoated with + /// [`pallet::tasks_experimental`](`tasks_experimental`) to define an iterator over the + /// available work items for a task. + /// + /// It takes an iterator as input that yields a tuple with same types as the function + /// arguments. + pub use frame_support_procedural::task_list; + /// This attribute is attached to a function inside an `impl` block annoated with + /// [`pallet::tasks_experimental`](`tasks_experimental`) define the weight of a given work + /// item. + /// + /// It takes a closure as input, which should return a `Weight` value. + pub use frame_support_procedural::task_weight; + /// Allows you to define some service work that can be recognized by a script or an + /// off-chain worker. Such a script can then create and submit all such work items at any + /// given time. + /// + /// These work items are defined as instances of the [`Task`](frame_support::traits::Task) + /// trait. [`pallet:tasks_experimental`](`tasks_experimental`) when attached to an `impl` + /// block inside a pallet, will generate an enum `Task` whose variants are mapped to + /// functions inside this `impl` block. + /// + /// Each such function must have the following set of attributes: + /// + /// * [`pallet::task_list`](`task_list`) + /// * [`pallet::task_condition`](`task_condition`) + /// * [`pallet::task_weight`](`task_weight`) + /// * [`pallet::task_index`](`task_index`) + /// + /// All of such Tasks are then aggregated into a `RuntimeTask` by + /// [`construct_runtime`](frame_support::construct_runtime). + /// + /// Finally, the `RuntimeTask` can then used by a script or off-chain worker to create and + /// submit such tasks via an extrinsic defined in `frame_system` called `do_task`. + /// + /// ## Example + #[doc = docify::embed!("src/tests/tasks.rs", tasks_example)] + /// Now, this can be executed as follows: + #[doc = docify::embed!("src/tests/tasks.rs", tasks_work)] + pub use frame_support_procedural::tasks_experimental; } #[deprecated(note = "Will be removed after July 2023; Use `sp_runtime::traits` directly instead.")] diff --git a/substrate/frame/support/src/storage/generator/mod.rs b/substrate/frame/support/src/storage/generator/mod.rs index 2b2abdc2e830923c5171703f1dc9173b50726cbc..dd6d622852db161cbe686115663e00a6bd9cd4b0 100644 --- a/substrate/frame/support/src/storage/generator/mod.rs +++ b/substrate/frame/support/src/storage/generator/mod.rs @@ -63,6 +63,7 @@ mod tests { type BaseCallFilter: crate::traits::Contains; type RuntimeOrigin; type RuntimeCall; + type RuntimeTask; type PalletInfo: crate::traits::PalletInfo; type DbWeight: Get; } @@ -129,6 +130,7 @@ mod tests { type BaseCallFilter = crate::traits::Everything; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; + type RuntimeTask = RuntimeTask; type PalletInfo = PalletInfo; type DbWeight = (); } diff --git a/substrate/frame/support/src/storage/unhashed.rs b/substrate/frame/support/src/storage/unhashed.rs index aae83034ab71aab13a24f060e369e966d26f93ca..776c7d0f3c3a8d761d7c048e292d590056b374c3 100644 --- a/substrate/frame/support/src/storage/unhashed.rs +++ b/substrate/frame/support/src/storage/unhashed.rs @@ -27,8 +27,8 @@ pub fn get(key: &[u8]) -> Option { // TODO #3700: error should be handleable. log::error!( target: "runtime::storage", - "Corrupted state at `{:?}: {:?}`", - key, + "Corrupted state at `{}`: {:?}", + array_bytes::bytes2hex("0x", key), e, ); None diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index 3690159c5994d98d265eadf7c0a623f4630eede3..c6a0b6cde7737e7510ab124bacef86a1a55aca96 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -16,6 +16,7 @@ // limitations under the License. use super::*; +use frame_support_procedural::import_section; use sp_io::{MultiRemovalResults, TestExternalities}; use sp_metadata_ir::{ PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, @@ -27,13 +28,15 @@ pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; mod inject_runtime_type; mod storage_alias; +mod tasks; +#[import_section(tasks::tasks_example)] #[pallet] pub mod frame_system { #[allow(unused)] use super::{frame_system, frame_system::pallet_prelude::*}; pub use crate::dispatch::RawOrigin; - use crate::pallet_prelude::*; + use crate::{pallet_prelude::*, traits::tasks::Task as TaskTrait}; pub mod config_preludes { use super::{inject_runtime_type, DefaultConfig}; @@ -49,6 +52,8 @@ pub mod frame_system { type RuntimeCall = (); #[inject_runtime_type] type PalletInfo = (); + #[inject_runtime_type] + type RuntimeTask = (); type DbWeight = (); } } @@ -69,6 +74,8 @@ pub mod frame_system { #[pallet::no_default_bounds] type RuntimeCall; #[pallet::no_default_bounds] + type RuntimeTask: crate::traits::tasks::Task; + #[pallet::no_default_bounds] type PalletInfo: crate::traits::PalletInfo; type DbWeight: Get; } @@ -77,13 +84,33 @@ pub mod frame_system { pub enum Error { /// Required by construct_runtime CallFiltered, + /// Used in tasks example. + NotFound, + /// The specified [`Task`] is not valid. + InvalidTask, + /// The specified [`Task`] failed during execution. + FailedTask, } #[pallet::origin] pub type Origin = RawOrigin<::AccountId>; #[pallet::call] - impl Pallet {} + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(task.weight())] + pub fn do_task(_origin: OriginFor, task: T::RuntimeTask) -> DispatchResultWithPostInfo { + if !task.is_valid() { + return Err(Error::::InvalidTask.into()) + } + + if let Err(_err) = task.run() { + return Err(Error::::FailedTask.into()) + } + + Ok(().into()) + } + } #[pallet::storage] pub type Data = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; @@ -169,6 +196,14 @@ pub mod frame_system { } } + /// Some running total. + #[pallet::storage] + pub type Total = StorageValue<_, (u32, u32), ValueQuery>; + + /// Numbers to be added into the total. + #[pallet::storage] + pub type Numbers = StorageMap<_, Twox64Concat, u32, u32, OptionQuery>; + pub mod pallet_prelude { pub type OriginFor = ::RuntimeOrigin; @@ -622,6 +657,24 @@ fn expected_metadata() -> PalletStorageMetadataIR { default: vec![0], docs: vec![], }, + StorageEntryMetadataIR { + name: "Total", + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::<(u32, u32)>()), + default: vec![0, 0, 0, 0, 0, 0, 0, 0], + docs: vec![" Some running total."], + }, + StorageEntryMetadataIR { + name: "Numbers", + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], + key: scale_info::meta_type::(), + value: scale_info::meta_type::(), + }, + default: vec![0], + docs: vec![" Numbers to be added into the total."], + }, ], } } diff --git a/substrate/frame/support/src/tests/tasks.rs b/substrate/frame/support/src/tests/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..2774c130075785844b99f35ec8b9b3937a51e6ed --- /dev/null +++ b/substrate/frame/support/src/tests/tasks.rs @@ -0,0 +1,62 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + assert_ok, + tests::{ + frame_system::{Numbers, Total}, + new_test_ext, Runtime, RuntimeOrigin, RuntimeTask, System, + }, +}; +use frame_support_procedural::pallet_section; + +#[pallet_section] +mod tasks_example { + #[docify::export(tasks_example)] + #[pallet::tasks_experimental] + impl Pallet { + /// Add a pair of numbers into the totals and remove them. + #[pallet::task_list(Numbers::::iter_keys())] + #[pallet::task_condition(|i| Numbers::::contains_key(i))] + #[pallet::task_weight(0.into())] + #[pallet::task_index(0)] + pub fn add_number_into_total(i: u32) -> DispatchResult { + let v = Numbers::::take(i).ok_or(Error::::NotFound)?; + Total::::mutate(|(total_keys, total_values)| { + *total_keys += i; + *total_values += v; + }); + Ok(()) + } + } +} + +#[docify::export] +#[test] +fn tasks_work() { + new_test_ext().execute_with(|| { + Numbers::::insert(0, 1); + + let task = RuntimeTask::System(super::frame_system::Task::::AddNumberIntoTotal { + i: 0u32, + }); + + assert_ok!(System::do_task(RuntimeOrigin::signed(1), task.clone(),)); + assert_eq!(Numbers::::get(0), None); + assert_eq!(Total::::get(), (0, 1)); + }); +} diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 6362e750d2ab98e5ae08e4e03ed57b72885c1f6f..9afd9c161303410c5d21903eab6511d02f3569c9 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -123,6 +123,9 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify}; mod tx_pause; pub use tx_pause::{TransactionPause, TransactionPauseError}; +pub mod tasks; +pub use tasks::Task; + #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/support/src/traits/messages.rs b/substrate/frame/support/src/traits/messages.rs index 58815b107c829845a8a1a8df6c82a7dde4203ff4..995ac4f717911195e4dba202d350c6cbc09ae340 100644 --- a/substrate/frame/support/src/traits/messages.rs +++ b/substrate/frame/support/src/traits/messages.rs @@ -82,6 +82,8 @@ pub enum ExecuteOverweightError { QueuePaused, /// An unspecified error. Other, + /// Another call is currently ongoing and prevents this call from executing. + RecursiveDisallowed, } /// Can service queues and execute overweight messages. diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 78032cc0a9407cb2505863ebac39c3f1bd0aa4f6..bf3053a3f8f59b6d55acadb208a8731c7f080e41 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -1170,17 +1170,26 @@ impl PreimageRecipient for () { fn unnote_preimage(_: &Hash) {} } -/// Trait for creating an asset account with a deposit taken from a designated depositor specified -/// by the client. +/// Trait for touching/creating an asset account with a deposit taken from a designated depositor +/// specified by the client. +/// +/// Ensures that transfers to the touched account will succeed without being denied by the account +/// creation requirements. For example, it is useful for the account creation of non-sufficient +/// assets when its system account may not have the free consumer reference required for it. If +/// there is no risk of failing to meet those requirements, the touch operation can be a no-op, as +/// is common for native assets. pub trait AccountTouch { /// The type for currency units of the deposit. type Balance; - /// The deposit amount of a native currency required for creating an account of the `asset`. + /// The deposit amount of a native currency required for touching an account of the `asset`. fn deposit_required(asset: AssetId) -> Self::Balance; + /// Check if an account for a given asset should be touched to meet the existence requirements. + fn should_touch(asset: AssetId, who: &AccountId) -> bool; + /// Create an account for `who` of the `asset` with a deposit taken from the `depositor`. - fn touch(asset: AssetId, who: AccountId, depositor: AccountId) -> DispatchResult; + fn touch(asset: AssetId, who: &AccountId, depositor: &AccountId) -> DispatchResult; } #[cfg(test)] diff --git a/substrate/frame/support/src/traits/tasks.rs b/substrate/frame/support/src/traits/tasks.rs new file mode 100644 index 0000000000000000000000000000000000000000..24f3430cf50b5a23175c4c586c1642da580cf862 --- /dev/null +++ b/substrate/frame/support/src/traits/tasks.rs @@ -0,0 +1,87 @@ +// 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. + +//! Contains the [`Task`] trait, which defines a general-purpose way for defining and executing +//! service work, and supporting types. + +use codec::FullCodec; +use scale_info::TypeInfo; +use sp_runtime::DispatchError; +use sp_std::{fmt::Debug, iter::Iterator, vec, vec::IntoIter}; +use sp_weights::Weight; + +/// Contain's re-exports of all the supporting types for the [`Task`] trait. Used in the macro +/// expansion of `RuntimeTask`. +#[doc(hidden)] +pub mod __private { + pub use codec::FullCodec; + pub use scale_info::TypeInfo; + pub use sp_runtime::DispatchError; + pub use sp_std::{fmt::Debug, iter::Iterator, vec, vec::IntoIter}; + pub use sp_weights::Weight; +} + +/// A general-purpose trait which defines a type of service work (i.e., work to performed by an +/// off-chain worker) including methods for enumerating, validating, indexing, and running +/// tasks of this type. +pub trait Task: Sized + FullCodec + TypeInfo + Clone + Debug + PartialEq + Eq { + /// An [`Iterator`] over tasks of this type used as the return type for `enumerate`. + type Enumeration: Iterator; + + /// Inspects the pallet's state and enumerates tasks of this type. + fn iter() -> Self::Enumeration; + + /// Checks if a particular instance of this `Task` variant is a valid piece of work. + fn is_valid(&self) -> bool; + + /// Performs the work for this particular `Task` variant. + fn run(&self) -> Result<(), DispatchError>; + + /// Returns the weight of executing this `Task`. + fn weight(&self) -> Weight; + + /// A unique value representing this `Task` within the current pallet. Analogous to + /// `call_index`, but for tasks.' + /// + /// This value should be unique within the current pallet and can overlap with task indices + /// in other pallets. + fn task_index(&self) -> u32; +} + +impl Task for () { + type Enumeration = IntoIter; + + fn iter() -> Self::Enumeration { + vec![].into_iter() + } + + fn is_valid(&self) -> bool { + true + } + + fn run(&self) -> Result<(), DispatchError> { + Ok(()) + } + + fn weight(&self) -> Weight { + Weight::default() + } + + fn task_index(&self) -> u32 { + 0 + } +} diff --git a/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs b/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs index 995797bc8f66b10861a68831984302057ecfbe34..0e25102197007e7ce3f38eef7417425f09dbcd2f 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs @@ -20,8 +20,9 @@ use super::{super::Imbalance as ImbalanceT, Balanced, *}; use crate::traits::{ + fungibles, misc::{SameOrOther, TryDrop}, - tokens::Balance, + tokens::{AssetId, Balance}, }; use frame_support_procedural::{EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use sp_runtime::traits::Zero; @@ -87,6 +88,11 @@ impl, OppositeOnDrop: HandleImbalance pub(crate) fn new(amount: B) -> Self { Self { amount, _phantom: PhantomData } } + + /// Forget the imbalance without invoking the on-drop handler. + pub(crate) fn forget(imbalance: Self) { + sp_std::mem::forget(imbalance); + } } impl, OppositeOnDrop: HandleImbalanceDrop> @@ -149,6 +155,27 @@ impl, OppositeOnDrop: HandleImbalance } } +/// Converts a `fungibles` `imbalance` instance to an instance of a `fungible` imbalance type. +/// +/// This function facilitates imbalance conversions within the implementations of +/// [`frame_support::traits::fungibles::UnionOf`], [`frame_support::traits::fungible::UnionOf`], and +/// [`frame_support::traits::fungible::ItemOf`] adapters. It is intended only for internal use +/// within the current crate. +pub(crate) fn from_fungibles< + A: AssetId, + B: Balance, + OnDropIn: fungibles::HandleImbalanceDrop, + OppositeIn: fungibles::HandleImbalanceDrop, + OnDropOut: HandleImbalanceDrop, + OppositeOut: HandleImbalanceDrop, +>( + imbalance: fungibles::Imbalance, +) -> Imbalance { + let new = Imbalance::new(imbalance.peek()); + fungibles::Imbalance::forget(imbalance); + new +} + /// Imbalance implying that the total_issuance value is less than the sum of all account balances. pub type Debt = Imbalance< >::Balance, diff --git a/substrate/frame/support/src/traits/tokens/fungible/item_of.rs b/substrate/frame/support/src/traits/tokens/fungible/item_of.rs index 636866ab93c9ba76379a9fe6b43a371e0819ea9c..fe252c6b0893d05ddbdd33d561e0388daddee307 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/item_of.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/item_of.rs @@ -17,14 +17,16 @@ //! Adapter to use `fungibles::*` implementations as `fungible::*`. -use sp_core::Get; -use sp_runtime::{DispatchError, DispatchResult}; - use super::*; -use crate::traits::tokens::{ - fungibles, DepositConsequence, Fortitude, Imbalance as ImbalanceT, Precision, Preservation, - Provenance, Restriction, WithdrawConsequence, +use crate::traits::{ + fungible::imbalance, + tokens::{ + fungibles, DepositConsequence, Fortitude, Precision, Preservation, Provenance, Restriction, + WithdrawConsequence, + }, }; +use sp_core::Get; +use sp_runtime::{DispatchError, DispatchResult}; /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying /// a single item. @@ -381,35 +383,38 @@ impl< precision: Precision, ) -> Result, DispatchError> { >::deposit(A::get(), who, value, precision) - .map(|debt| Imbalance::new(debt.peek())) + .map(imbalance::from_fungibles) } fn issue(amount: Self::Balance) -> Credit { - Imbalance::new(>::issue(A::get(), amount).peek()) + let credit = >::issue(A::get(), amount); + imbalance::from_fungibles(credit) } fn pair(amount: Self::Balance) -> (Debt, Credit) { let (a, b) = >::pair(A::get(), amount); - (Imbalance::new(a.peek()), Imbalance::new(b.peek())) + (imbalance::from_fungibles(a), imbalance::from_fungibles(b)) } fn rescind(amount: Self::Balance) -> Debt { - Imbalance::new(>::rescind(A::get(), amount).peek()) + let debt = >::rescind(A::get(), amount); + imbalance::from_fungibles(debt) } fn resolve( who: &AccountId, credit: Credit, ) -> Result<(), Credit> { - let credit = fungibles::Imbalance::new(A::get(), credit.peek()); + let credit = fungibles::imbalance::from_fungible(credit, A::get()); >::resolve(who, credit) - .map_err(|credit| Imbalance::new(credit.peek())) + .map_err(imbalance::from_fungibles) } fn settle( who: &AccountId, debt: Debt, preservation: Preservation, ) -> Result, Debt> { - let debt = fungibles::Imbalance::new(A::get(), debt.peek()); - >::settle(who, debt, preservation) - .map(|credit| Imbalance::new(credit.peek())) - .map_err(|debt| Imbalance::new(debt.peek())) + let debt = fungibles::imbalance::from_fungible(debt, A::get()); + >::settle(who, debt, preservation).map_or_else( + |d| Err(imbalance::from_fungibles(d)), + |c| Ok(imbalance::from_fungibles(c)), + ) } fn withdraw( who: &AccountId, @@ -426,7 +431,7 @@ impl< preservation, force, ) - .map(|credit| Imbalance::new(credit.peek())) + .map(imbalance::from_fungibles) } } @@ -443,7 +448,7 @@ impl< ) -> (Credit, Self::Balance) { let (credit, amount) = >::slash(A::get(), reason, who, amount); - (Imbalance::new(credit.peek()), amount) + (imbalance::from_fungibles(credit), amount) } } diff --git a/substrate/frame/support/src/traits/tokens/fungible/mod.rs b/substrate/frame/support/src/traits/tokens/fungible/mod.rs index 61b75fd6563c84659ca19adeb39217feb91524a8..ba4a2e5e21a2c9f5115ca6331e85df0fea58bb74 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/mod.rs @@ -41,9 +41,10 @@ pub mod conformance_tests; pub mod freeze; pub mod hold; -mod imbalance; +pub(crate) mod imbalance; mod item_of; mod regular; +mod union_of; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support_procedural::{CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; @@ -67,6 +68,7 @@ pub use regular::{ use sp_arithmetic::traits::Zero; use sp_core::Get; use sp_runtime::{traits::Convert, DispatchError}; +pub use union_of::{NativeFromLeft, NativeOrWithId, UnionOf}; use crate::{ ensure, diff --git a/substrate/frame/support/src/traits/tokens/fungible/regular.rs b/substrate/frame/support/src/traits/tokens/fungible/regular.rs index f2fb5c5f7c24e4fa3517c0f4dd331e874512e36e..87c6ef68d77d6c8ca0b88d650126c7e472126b85 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/regular.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/regular.rs @@ -181,9 +181,16 @@ pub trait Unbalanced: Inspect { ) -> Result { let old_balance = Self::balance(who); let free = Self::reducible_balance(who, preservation, force); - if let BestEffort = precision { - amount = amount.min(free); + match precision { + BestEffort => { + amount = amount.min(free); + }, + Exact => + if free < amount { + return Err(TokenError::FundsUnavailable.into()) + }, } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; if let Some(dust) = Self::write_balance(who, new_balance)? { Self::handle_dust(Dust(dust)); @@ -514,3 +521,47 @@ pub trait Balanced: Inspect + Unbalanced { fn done_deposit(_who: &AccountId, _amount: Self::Balance) {} fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {} } + +/// Dummy implementation of [`Inspect`] +#[cfg(feature = "std")] +impl Inspect for () { + type Balance = u32; + fn total_issuance() -> Self::Balance { + 0 + } + fn minimum_balance() -> Self::Balance { + 0 + } + fn total_balance(_: &AccountId) -> Self::Balance { + 0 + } + fn balance(_: &AccountId) -> Self::Balance { + 0 + } + fn reducible_balance(_: &AccountId, _: Preservation, _: Fortitude) -> Self::Balance { + 0 + } + fn can_deposit(_: &AccountId, _: Self::Balance, _: Provenance) -> DepositConsequence { + DepositConsequence::Success + } + fn can_withdraw(_: &AccountId, _: Self::Balance) -> WithdrawConsequence { + WithdrawConsequence::Success + } +} + +/// Dummy implementation of [`Unbalanced`] +#[cfg(feature = "std")] +impl Unbalanced for () { + fn handle_dust(_: Dust) {} + fn write_balance( + _: &AccountId, + _: Self::Balance, + ) -> Result, DispatchError> { + Ok(None) + } + fn set_total_issuance(_: Self::Balance) {} +} + +/// Dummy implementation of [`Mutate`] +#[cfg(feature = "std")] +impl Mutate for () {} diff --git a/substrate/frame/support/src/traits/tokens/fungible/union_of.rs b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs new file mode 100644 index 0000000000000000000000000000000000000000..86505befc05f75c052f8d33f0f495ba6768d71ba --- /dev/null +++ b/substrate/frame/support/src/traits/tokens/fungible/union_of.rs @@ -0,0 +1,924 @@ +// This file is part of Substrate. + +// Copyright (Criterion) 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 to combine some `fungible::*` and `fungibles::*` implementations into one union +//! `fungibles::*` implementation. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{ + fungible::imbalance, + tokens::{ + fungible, fungibles, AssetId, DepositConsequence, Fortitude, Precision, Preservation, + Provenance, Restriction, WithdrawConsequence, + }, + AccountTouch, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::Convert, + DispatchError, DispatchResult, Either, + Either::{Left, Right}, + RuntimeDebug, +}; +use sp_std::cmp::Ordering; + +/// The `NativeOrWithId` enum classifies an asset as either `Native` to the current chain or as an +/// asset with a specific ID. +#[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, RuntimeDebug, Eq)] +pub enum NativeOrWithId +where + AssetId: Ord, +{ + /// Represents the native asset of the current chain. + /// + /// E.g., DOT for the Polkadot Asset Hub. + #[default] + Native, + /// Represents an asset identified by its underlying `AssetId`. + WithId(AssetId), +} +impl From for NativeOrWithId { + fn from(asset: AssetId) -> Self { + Self::WithId(asset) + } +} +impl Ord for NativeOrWithId { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Self::Native, Self::Native) => Ordering::Equal, + (Self::Native, Self::WithId(_)) => Ordering::Less, + (Self::WithId(_), Self::Native) => Ordering::Greater, + (Self::WithId(id1), Self::WithId(id2)) => ::cmp(id1, id2), + } + } +} +impl PartialOrd for NativeOrWithId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(::cmp(self, other)) + } +} +impl PartialEq for NativeOrWithId { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} + +/// Criterion for [`UnionOf`] where a set for [`NativeOrWithId::Native`] asset located from the left +/// and for [`NativeOrWithId::WithId`] from the right. +pub struct NativeFromLeft; +impl Convert, Either<(), AssetId>> for NativeFromLeft { + fn convert(asset: NativeOrWithId) -> Either<(), AssetId> { + match asset { + NativeOrWithId::Native => Either::Left(()), + NativeOrWithId::WithId(id) => Either::Right(id), + } + } +} + +/// Type to combine some `fungible::*` and `fungibles::*` implementations into one union +/// `fungibles::*` implementation. +/// +/// ### Parameters: +/// - `Left` is `fungible::*` implementation that is incorporated into the resulting union. +/// - `Right` is `fungibles::*` implementation that is incorporated into the resulting union. +/// - `Criterion` determines whether the `AssetKind` belongs to the `Left` or `Right` set. +/// - `AssetKind` is a superset type encompassing asset kinds from `Left` and `Right` sets. +/// - `AccountId` is an account identifier type. +pub struct UnionOf( + sp_std::marker::PhantomData<(Left, Right, Criterion, AssetKind, AccountId)>, +); + +impl< + Left: fungible::Inspect, + Right: fungibles::Inspect, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Inspect for UnionOf +{ + type AssetId = AssetKind; + type Balance = Left::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::total_issuance(), + Right(a) => >::total_issuance(a), + } + } + fn active_issuance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::active_issuance(), + Right(a) => >::active_issuance(a), + } + } + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::minimum_balance(), + Right(a) => >::minimum_balance(a), + } + } + fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::balance(who), + Right(a) => >::balance(a, who), + } + } + fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::total_balance(who), + Right(a) => >::total_balance(a, who), + } + } + fn reducible_balance( + asset: Self::AssetId, + who: &AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => + >::reducible_balance(who, preservation, force), + Right(a) => >::reducible_balance( + a, + who, + preservation, + force, + ), + } + } + fn can_deposit( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + match Criterion::convert(asset) { + Left(()) => + >::can_deposit(who, amount, provenance), + Right(a) => + >::can_deposit(a, who, amount, provenance), + } + } + fn can_withdraw( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + match Criterion::convert(asset) { + Left(()) => >::can_withdraw(who, amount), + Right(a) => >::can_withdraw(a, who, amount), + } + } + fn asset_exists(asset: Self::AssetId) -> bool { + match Criterion::convert(asset) { + Left(()) => true, + Right(a) => >::asset_exists(a), + } + } +} + +impl< + Left: fungible::InspectHold, + Right: fungibles::InspectHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::InspectHold for UnionOf +{ + type Reason = Left::Reason; + + fn reducible_total_balance_on_hold( + asset: Self::AssetId, + who: &AccountId, + force: Fortitude, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => + >::reducible_total_balance_on_hold( + who, force, + ), + Right(a) => + >::reducible_total_balance_on_hold( + a, who, force, + ), + } + } + fn hold_available(asset: Self::AssetId, reason: &Self::Reason, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(()) => >::hold_available(reason, who), + Right(a) => + >::hold_available(a, reason, who), + } + } + fn total_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::total_balance_on_hold(who), + Right(a) => >::total_balance_on_hold(a, who), + } + } + fn balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::balance_on_hold(reason, who), + Right(a) => + >::balance_on_hold(a, reason, who), + } + } + fn can_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> bool { + match Criterion::convert(asset) { + Left(()) => >::can_hold(reason, who, amount), + Right(a) => + >::can_hold(a, reason, who, amount), + } + } +} + +impl< + Left: fungible::InspectFreeze, + Right: fungibles::InspectFreeze, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::InspectFreeze for UnionOf +{ + type Id = Left::Id; + fn balance_frozen(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::balance_frozen(id, who), + Right(a) => >::balance_frozen(a, id, who), + } + } + fn balance_freezable(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::balance_freezable(who), + Right(a) => >::balance_freezable(a, who), + } + } + fn can_freeze(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(()) => >::can_freeze(id, who), + Right(a) => >::can_freeze(a, id, who), + } + } +} + +impl< + Left: fungible::Unbalanced, + Right: fungibles::Unbalanced, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Unbalanced for UnionOf +{ + fn handle_dust(dust: fungibles::Dust) + where + Self: Sized, + { + match Criterion::convert(dust.0) { + Left(()) => + >::handle_dust(fungible::Dust(dust.1)), + Right(a) => + >::handle_dust(fungibles::Dust(a, dust.1)), + } + } + fn write_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + match Criterion::convert(asset) { + Left(()) => >::write_balance(who, amount), + Right(a) => >::write_balance(a, who, amount), + } + } + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) -> () { + match Criterion::convert(asset) { + Left(()) => >::set_total_issuance(amount), + Right(a) => >::set_total_issuance(a, amount), + } + } + fn decrease_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::decrease_balance( + who, + amount, + precision, + preservation, + force, + ), + Right(a) => >::decrease_balance( + a, + who, + amount, + precision, + preservation, + force, + ), + } + } + fn increase_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => + >::increase_balance(who, amount, precision), + Right(a) => >::increase_balance( + a, who, amount, precision, + ), + } + } +} + +impl< + Left: fungible::UnbalancedHold, + Right: fungibles::UnbalancedHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::UnbalancedHold for UnionOf +{ + fn set_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::set_balance_on_hold( + reason, who, amount, + ), + Right(a) => >::set_balance_on_hold( + a, reason, who, amount, + ), + } + } + fn decrease_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::decrease_balance_on_hold( + reason, who, amount, precision, + ), + Right(a) => >::decrease_balance_on_hold( + a, reason, who, amount, precision, + ), + } + } + fn increase_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::increase_balance_on_hold( + reason, who, amount, precision, + ), + Right(a) => >::increase_balance_on_hold( + a, reason, who, amount, precision, + ), + } + } +} + +impl< + Left: fungible::Mutate, + Right: fungibles::Mutate, + Criterion: Convert>, + AssetKind: AssetId, + AccountId: Eq, + > fungibles::Mutate for UnionOf +{ + fn mint_into( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::mint_into(who, amount), + Right(a) => >::mint_into(a, who, amount), + } + } + fn burn_from( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => + >::burn_from(who, amount, precision, force), + Right(a) => + >::burn_from(a, who, amount, precision, force), + } + } + fn shelve( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::shelve(who, amount), + Right(a) => >::shelve(a, who, amount), + } + } + fn restore( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::restore(who, amount), + Right(a) => >::restore(a, who, amount), + } + } + fn transfer( + asset: Self::AssetId, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => + >::transfer(source, dest, amount, preservation), + Right(a) => >::transfer( + a, + source, + dest, + amount, + preservation, + ), + } + } + + fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::set_balance(who, amount), + Right(a) => >::set_balance(a, who, amount), + } + } +} + +impl< + Left: fungible::MutateHold, + Right: fungibles::MutateHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::MutateHold for UnionOf +{ + fn hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::hold(reason, who, amount), + Right(a) => >::hold(a, reason, who, amount), + } + } + fn release( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => + >::release(reason, who, amount, precision), + Right(a) => >::release( + a, reason, who, amount, precision, + ), + } + } + fn burn_held( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::burn_held( + reason, who, amount, precision, force, + ), + Right(a) => >::burn_held( + a, reason, who, amount, precision, force, + ), + } + } + fn transfer_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + mode: Restriction, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::transfer_on_hold( + reason, source, dest, amount, precision, mode, force, + ), + Right(a) => >::transfer_on_hold( + a, reason, source, dest, amount, precision, mode, force, + ), + } + } + fn transfer_and_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(()) => >::transfer_and_hold( + reason, + source, + dest, + amount, + precision, + preservation, + force, + ), + Right(a) => >::transfer_and_hold( + a, + reason, + source, + dest, + amount, + precision, + preservation, + force, + ), + } + } +} + +impl< + Left: fungible::MutateFreeze, + Right: fungibles::MutateFreeze, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::MutateFreeze for UnionOf +{ + fn set_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::set_freeze(id, who, amount), + Right(a) => + >::set_freeze(a, id, who, amount), + } + } + fn extend_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::extend_freeze(id, who, amount), + Right(a) => + >::extend_freeze(a, id, who, amount), + } + } + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::thaw(id, who), + Right(a) => >::thaw(a, id, who), + } + } +} + +pub struct ConvertImbalanceDropHandler< + Left, + Right, + Criterion, + AssetKind, + Balance, + AssetId, + AccountId, +>(sp_std::marker::PhantomData<(Left, Right, Criterion, AssetKind, Balance, AssetId, AccountId)>); + +impl< + Left: fungible::HandleImbalanceDrop, + Right: fungibles::HandleImbalanceDrop, + Criterion: Convert>, + AssetKind, + Balance, + AssetId, + AccountId, + > fungibles::HandleImbalanceDrop + for ConvertImbalanceDropHandler +{ + fn handle(asset: AssetKind, amount: Balance) { + match Criterion::convert(asset) { + Left(()) => Left::handle(amount), + Right(a) => Right::handle(a, amount), + } + } +} + +impl< + Left: fungible::Balanced, + Right: fungibles::Balanced, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Balanced for UnionOf +{ + type OnDropDebt = ConvertImbalanceDropHandler< + Left::OnDropDebt, + Right::OnDropDebt, + Criterion, + AssetKind, + Left::Balance, + Right::AssetId, + AccountId, + >; + type OnDropCredit = ConvertImbalanceDropHandler< + Left::OnDropCredit, + Right::OnDropCredit, + Criterion, + AssetKind, + Left::Balance, + Right::AssetId, + AccountId, + >; + + fn deposit( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + ) -> Result, DispatchError> { + match Criterion::convert(asset.clone()) { + Left(()) => >::deposit(who, value, precision) + .map(|d| fungibles::imbalance::from_fungible(d, asset)), + Right(a) => + >::deposit(a, who, value, precision) + .map(|d| fungibles::imbalance::from_fungibles(d, asset)), + } + } + fn issue(asset: Self::AssetId, amount: Self::Balance) -> fungibles::Credit { + match Criterion::convert(asset.clone()) { + Left(()) => { + let credit = >::issue(amount); + fungibles::imbalance::from_fungible(credit, asset) + }, + Right(a) => { + let credit = >::issue(a, amount); + fungibles::imbalance::from_fungibles(credit, asset) + }, + } + } + fn pair( + asset: Self::AssetId, + amount: Self::Balance, + ) -> (fungibles::Debt, fungibles::Credit) { + match Criterion::convert(asset.clone()) { + Left(()) => { + let (a, b) = >::pair(amount); + ( + fungibles::imbalance::from_fungible(a, asset.clone()), + fungibles::imbalance::from_fungible(b, asset), + ) + }, + Right(a) => { + let (a, b) = >::pair(a, amount); + ( + fungibles::imbalance::from_fungibles(a, asset.clone()), + fungibles::imbalance::from_fungibles(b, asset), + ) + }, + } + } + fn rescind(asset: Self::AssetId, amount: Self::Balance) -> fungibles::Debt { + match Criterion::convert(asset.clone()) { + Left(()) => { + let debt = >::rescind(amount); + fungibles::imbalance::from_fungible(debt, asset) + }, + Right(a) => { + let debt = >::rescind(a, amount); + fungibles::imbalance::from_fungibles(debt, asset) + }, + } + } + fn resolve( + who: &AccountId, + credit: fungibles::Credit, + ) -> Result<(), fungibles::Credit> { + let asset = credit.asset(); + match Criterion::convert(asset.clone()) { + Left(()) => { + let credit = imbalance::from_fungibles(credit); + >::resolve(who, credit) + .map_err(|credit| fungibles::imbalance::from_fungible(credit, asset)) + }, + Right(a) => { + let credit = fungibles::imbalance::from_fungibles(credit, a); + >::resolve(who, credit) + .map_err(|credit| fungibles::imbalance::from_fungibles(credit, asset)) + }, + } + } + fn settle( + who: &AccountId, + debt: fungibles::Debt, + preservation: Preservation, + ) -> Result, fungibles::Debt> { + let asset = debt.asset(); + match Criterion::convert(asset.clone()) { + Left(()) => { + let debt = imbalance::from_fungibles(debt); + match >::settle(who, debt, preservation) { + Ok(c) => Ok(fungibles::imbalance::from_fungible(c, asset)), + Err(d) => Err(fungibles::imbalance::from_fungible(d, asset)), + } + }, + Right(a) => { + let debt = fungibles::imbalance::from_fungibles(debt, a); + match >::settle(who, debt, preservation) { + Ok(c) => Ok(fungibles::imbalance::from_fungibles(c, asset)), + Err(d) => Err(fungibles::imbalance::from_fungibles(d, asset)), + } + }, + } + } + fn withdraw( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result, DispatchError> { + match Criterion::convert(asset.clone()) { + Left(()) => >::withdraw( + who, + value, + precision, + preservation, + force, + ) + .map(|c| fungibles::imbalance::from_fungible(c, asset)), + Right(a) => >::withdraw( + a, + who, + value, + precision, + preservation, + force, + ) + .map(|c| fungibles::imbalance::from_fungibles(c, asset)), + } + } +} + +impl< + Left: fungible::BalancedHold, + Right: fungibles::BalancedHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::BalancedHold for UnionOf +{ + fn slash( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> (fungibles::Credit, Self::Balance) { + match Criterion::convert(asset.clone()) { + Left(()) => { + let (credit, amount) = + >::slash(reason, who, amount); + (fungibles::imbalance::from_fungible(credit, asset), amount) + }, + Right(a) => { + let (credit, amount) = + >::slash(a, reason, who, amount); + (fungibles::imbalance::from_fungibles(credit, asset), amount) + }, + } + } +} + +impl< + Left: fungible::Inspect, + Right: fungibles::Inspect + fungibles::Create, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Create for UnionOf +{ + fn create( + asset: AssetKind, + admin: AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + // no-op for `Left` since `Create` trait is not defined within `fungible::*`. + Left(()) => Ok(()), + Right(a) => >::create( + a, + admin, + is_sufficient, + min_balance, + ), + } + } +} + +impl< + Left: fungible::Inspect + + AccountTouch<(), AccountId, Balance = >::Balance>, + Right: fungibles::Inspect + + AccountTouch< + Right::AssetId, + AccountId, + Balance = >::Balance, + >, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > AccountTouch for UnionOf +{ + type Balance = >::Balance; + + fn deposit_required(asset: AssetKind) -> Self::Balance { + match Criterion::convert(asset) { + Left(()) => >::deposit_required(()), + Right(a) => >::deposit_required(a), + } + } + + fn should_touch(asset: AssetKind, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(()) => >::should_touch((), who), + Right(a) => >::should_touch(a, who), + } + } + + fn touch(asset: AssetKind, who: &AccountId, depositor: &AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(()) => >::touch((), who, depositor), + Right(a) => + >::touch(a, who, depositor), + } + } +} diff --git a/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs b/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs index 7c0d7721a2e60908f1a5a4be8a8c876ff2bd1ae2..54c1e900b6e3664b6fbf3470e41d17eaae37e635 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs @@ -20,8 +20,9 @@ use super::*; use crate::traits::{ + fungible, misc::{SameOrOther, TryDrop}, - tokens::{AssetId, Balance}, + tokens::{imbalance::Imbalance as ImbalanceT, AssetId, Balance}, }; use frame_support_procedural::{EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; use sp_runtime::traits::Zero; @@ -93,6 +94,11 @@ impl< Self { asset, amount, _phantom: PhantomData } } + /// Forget the imbalance without invoking the on-drop handler. + pub(crate) fn forget(imbalance: Self) { + sp_std::mem::forget(imbalance); + } + pub fn drop_zero(self) -> Result<(), Self> { if self.amount.is_zero() { sp_std::mem::forget(self); @@ -168,6 +174,52 @@ impl< } } +/// Converts a `fungible` `imbalance` instance to an instance of a `fungibles` imbalance type using +/// a specified `asset`. +/// +/// This function facilitates imbalance conversions within the implementations of +/// [`frame_support::traits::fungibles::UnionOf`], [`frame_support::traits::fungible::UnionOf`], and +/// [`frame_support::traits::fungible::ItemOf`] adapters. It is intended only for internal use +/// within the current crate. +pub(crate) fn from_fungible< + A: AssetId, + B: Balance, + OnDropIn: fungible::HandleImbalanceDrop, + OppositeIn: fungible::HandleImbalanceDrop, + OnDropOut: HandleImbalanceDrop, + OppositeOut: HandleImbalanceDrop, +>( + imbalance: fungible::Imbalance, + asset: A, +) -> Imbalance { + let new = Imbalance::new(asset, imbalance.peek()); + fungible::Imbalance::forget(imbalance); + new +} + +/// Converts a `fungibles` `imbalance` instance of one type to another using a specified `asset`. +/// +/// This function facilitates imbalance conversions within the implementations of +/// [`frame_support::traits::fungibles::UnionOf`], [`frame_support::traits::fungible::UnionOf`], and +/// [`frame_support::traits::fungible::ItemOf`] adapters. It is intended only for internal use +/// within the current crate. +pub(crate) fn from_fungibles< + A: AssetId, + B: Balance, + OnDropIn: HandleImbalanceDrop, + OppositeIn: HandleImbalanceDrop, + AssetOut: AssetId, + OnDropOut: HandleImbalanceDrop, + OppositeOut: HandleImbalanceDrop, +>( + imbalance: Imbalance, + asset: AssetOut, +) -> Imbalance { + let new = Imbalance::new(asset, imbalance.peek()); + Imbalance::forget(imbalance); + new +} + /// Imbalance implying that the total_issuance value is less than the sum of all account balances. pub type Debt = Imbalance< >::AssetId, diff --git a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs index 4fd6ef43a15fafe91730fd1c5e01b2ca14bfd817..1db0706ba4fde4c3b53dbcdd5ea850a37752b1e9 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs @@ -21,11 +21,12 @@ pub mod approvals; mod enumerable; pub mod freeze; pub mod hold; -mod imbalance; +pub(crate) mod imbalance; mod lifetime; pub mod metadata; mod regular; pub mod roles; +mod union_of; pub use enumerable::Inspect as InspectEnumerable; pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze}; @@ -38,3 +39,4 @@ pub use lifetime::{Create, Destroy}; pub use regular::{ Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced, }; +pub use union_of::UnionOf; diff --git a/substrate/frame/support/src/traits/tokens/fungibles/regular.rs b/substrate/frame/support/src/traits/tokens/fungibles/regular.rs index a2fc4e55095222257953099af7b5795da9a2321a..41ef4b40c75b389971db43d8013072b269a30a39 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/regular.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/regular.rs @@ -593,3 +593,66 @@ pub trait Balanced: Inspect + Unbalanced { fn done_deposit(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} fn done_withdraw(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} } + +/// Dummy implementation of [`Inspect`] +#[cfg(feature = "std")] +impl Inspect for () { + type AssetId = u32; + type Balance = u32; + fn total_issuance(_: Self::AssetId) -> Self::Balance { + 0 + } + fn minimum_balance(_: Self::AssetId) -> Self::Balance { + 0 + } + fn total_balance(_: Self::AssetId, _: &AccountId) -> Self::Balance { + 0 + } + fn balance(_: Self::AssetId, _: &AccountId) -> Self::Balance { + 0 + } + fn reducible_balance( + _: Self::AssetId, + _: &AccountId, + _: Preservation, + _: Fortitude, + ) -> Self::Balance { + 0 + } + fn can_deposit( + _: Self::AssetId, + _: &AccountId, + _: Self::Balance, + _: Provenance, + ) -> DepositConsequence { + DepositConsequence::Success + } + fn can_withdraw( + _: Self::AssetId, + _: &AccountId, + _: Self::Balance, + ) -> WithdrawConsequence { + WithdrawConsequence::Success + } + fn asset_exists(_: Self::AssetId) -> bool { + false + } +} + +/// Dummy implementation of [`Unbalanced`] +#[cfg(feature = "std")] +impl Unbalanced for () { + fn handle_dust(_: Dust) {} + fn write_balance( + _: Self::AssetId, + _: &AccountId, + _: Self::Balance, + ) -> Result, DispatchError> { + Ok(None) + } + fn set_total_issuance(_: Self::AssetId, _: Self::Balance) {} +} + +/// Dummy implementation of [`Mutate`] +#[cfg(feature = "std")] +impl Mutate for () {} diff --git a/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs new file mode 100644 index 0000000000000000000000000000000000000000..3619db3a37b63ac527a7038b4c99bbb2c4bed7c5 --- /dev/null +++ b/substrate/frame/support/src/traits/tokens/fungibles/union_of.rs @@ -0,0 +1,897 @@ +// This file is part of Substrate. + +// Copyright (Criterion) 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. + +//! Type to combine two `fungibles::*` implementations into one union `fungibles::*` implementation. + +use frame_support::traits::{ + tokens::{ + fungibles, fungibles::imbalance, AssetId, DepositConsequence, Fortitude, Precision, + Preservation, Provenance, Restriction, WithdrawConsequence, + }, + AccountTouch, +}; +use sp_runtime::{ + traits::Convert, + DispatchError, DispatchResult, Either, + Either::{Left, Right}, +}; + +/// Type to combine two `fungibles::*` implementations into one union `fungibles::*` implementation. +/// +/// ### Parameters: +/// - `Left` is `fungibles::*` implementation that is incorporated into the resulting union. +/// - `Right` is `fungibles::*` implementation that is incorporated into the resulting union. +/// - `Criterion` determines whether the `AssetKind` belongs to the `Left` or `Right` set. +/// - `AssetKind` is a superset type encompassing asset kinds from `Left` and `Right` sets. +/// - `AccountId` is an account identifier type. +pub struct UnionOf( + sp_std::marker::PhantomData<(Left, Right, Criterion, AssetKind, AccountId)>, +); + +impl< + Left: fungibles::Inspect, + Right: fungibles::Inspect, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Inspect for UnionOf +{ + type AssetId = AssetKind; + type Balance = Left::Balance; + + fn total_issuance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::total_issuance(a), + Right(a) => >::total_issuance(a), + } + } + fn active_issuance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::active_issuance(a), + Right(a) => >::active_issuance(a), + } + } + fn minimum_balance(asset: Self::AssetId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::minimum_balance(a), + Right(a) => >::minimum_balance(a), + } + } + fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::balance(a, who), + Right(a) => >::balance(a, who), + } + } + fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::total_balance(a, who), + Right(a) => >::total_balance(a, who), + } + } + fn reducible_balance( + asset: Self::AssetId, + who: &AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::reducible_balance( + a, + who, + preservation, + force, + ), + Right(a) => >::reducible_balance( + a, + who, + preservation, + force, + ), + } + } + fn can_deposit( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + match Criterion::convert(asset) { + Left(a) => + >::can_deposit(a, who, amount, provenance), + Right(a) => + >::can_deposit(a, who, amount, provenance), + } + } + fn can_withdraw( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + match Criterion::convert(asset) { + Left(a) => >::can_withdraw(a, who, amount), + Right(a) => >::can_withdraw(a, who, amount), + } + } + fn asset_exists(asset: Self::AssetId) -> bool { + match Criterion::convert(asset) { + Left(a) => >::asset_exists(a), + Right(a) => >::asset_exists(a), + } + } +} + +impl< + Left: fungibles::InspectHold, + Right: fungibles::InspectHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::InspectHold for UnionOf +{ + type Reason = Left::Reason; + + fn reducible_total_balance_on_hold( + asset: Self::AssetId, + who: &AccountId, + force: Fortitude, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => + >::reducible_total_balance_on_hold( + a, who, force, + ), + Right(a) => + >::reducible_total_balance_on_hold( + a, who, force, + ), + } + } + fn hold_available(asset: Self::AssetId, reason: &Self::Reason, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(a) => >::hold_available(a, reason, who), + Right(a) => + >::hold_available(a, reason, who), + } + } + fn total_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::total_balance_on_hold(a, who), + Right(a) => >::total_balance_on_hold(a, who), + } + } + fn balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + ) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::balance_on_hold(a, reason, who), + Right(a) => + >::balance_on_hold(a, reason, who), + } + } + fn can_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> bool { + match Criterion::convert(asset) { + Left(a) => + >::can_hold(a, reason, who, amount), + Right(a) => + >::can_hold(a, reason, who, amount), + } + } +} + +impl< + Left: fungibles::InspectFreeze, + Right: fungibles::InspectFreeze, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::InspectFreeze for UnionOf +{ + type Id = Left::Id; + fn balance_frozen(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::balance_frozen(a, id, who), + Right(a) => >::balance_frozen(a, id, who), + } + } + fn balance_freezable(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::balance_freezable(a, who), + Right(a) => >::balance_freezable(a, who), + } + } + fn can_freeze(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(a) => >::can_freeze(a, id, who), + Right(a) => >::can_freeze(a, id, who), + } + } +} + +impl< + Left: fungibles::Unbalanced, + Right: fungibles::Unbalanced, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Unbalanced for UnionOf +{ + fn handle_dust(dust: fungibles::Dust) + where + Self: Sized, + { + match Criterion::convert(dust.0) { + Left(a) => + >::handle_dust(fungibles::Dust(a, dust.1)), + Right(a) => + >::handle_dust(fungibles::Dust(a, dust.1)), + } + } + fn write_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + match Criterion::convert(asset) { + Left(a) => >::write_balance(a, who, amount), + Right(a) => >::write_balance(a, who, amount), + } + } + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) -> () { + match Criterion::convert(asset) { + Left(a) => >::set_total_issuance(a, amount), + Right(a) => >::set_total_issuance(a, amount), + } + } + fn decrease_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::decrease_balance( + a, + who, + amount, + precision, + preservation, + force, + ), + Right(a) => >::decrease_balance( + a, + who, + amount, + precision, + preservation, + force, + ), + } + } + fn increase_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::increase_balance( + a, who, amount, precision, + ), + Right(a) => >::increase_balance( + a, who, amount, precision, + ), + } + } +} + +impl< + Left: fungibles::UnbalancedHold, + Right: fungibles::UnbalancedHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::UnbalancedHold for UnionOf +{ + fn set_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::set_balance_on_hold( + a, reason, who, amount, + ), + Right(a) => >::set_balance_on_hold( + a, reason, who, amount, + ), + } + } + fn decrease_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::decrease_balance_on_hold( + a, reason, who, amount, precision, + ), + Right(a) => >::decrease_balance_on_hold( + a, reason, who, amount, precision, + ), + } + } + fn increase_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::increase_balance_on_hold( + a, reason, who, amount, precision, + ), + Right(a) => >::increase_balance_on_hold( + a, reason, who, amount, precision, + ), + } + } +} + +impl< + Left: fungibles::Mutate, + Right: fungibles::Mutate, + Criterion: Convert>, + AssetKind: AssetId, + AccountId: Eq, + > fungibles::Mutate for UnionOf +{ + fn mint_into( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::mint_into(a, who, amount), + Right(a) => >::mint_into(a, who, amount), + } + } + fn burn_from( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => + >::burn_from(a, who, amount, precision, force), + Right(a) => + >::burn_from(a, who, amount, precision, force), + } + } + fn shelve( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::shelve(a, who, amount), + Right(a) => >::shelve(a, who, amount), + } + } + fn restore( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::restore(a, who, amount), + Right(a) => >::restore(a, who, amount), + } + } + fn transfer( + asset: Self::AssetId, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::transfer( + a, + source, + dest, + amount, + preservation, + ), + Right(a) => >::transfer( + a, + source, + dest, + amount, + preservation, + ), + } + } + + fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::set_balance(a, who, amount), + Right(a) => >::set_balance(a, who, amount), + } + } +} + +impl< + Left: fungibles::MutateHold, + Right: fungibles::MutateHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::MutateHold for UnionOf +{ + fn hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::hold(a, reason, who, amount), + Right(a) => >::hold(a, reason, who, amount), + } + } + fn release( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::release( + a, reason, who, amount, precision, + ), + Right(a) => >::release( + a, reason, who, amount, precision, + ), + } + } + fn burn_held( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::burn_held( + a, reason, who, amount, precision, force, + ), + Right(a) => >::burn_held( + a, reason, who, amount, precision, force, + ), + } + } + fn transfer_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + mode: Restriction, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::transfer_on_hold( + a, reason, source, dest, amount, precision, mode, force, + ), + Right(a) => >::transfer_on_hold( + a, reason, source, dest, amount, precision, mode, force, + ), + } + } + fn transfer_and_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + match Criterion::convert(asset) { + Left(a) => >::transfer_and_hold( + a, + reason, + source, + dest, + amount, + precision, + preservation, + force, + ), + Right(a) => >::transfer_and_hold( + a, + reason, + source, + dest, + amount, + precision, + preservation, + force, + ), + } + } +} + +impl< + Left: fungibles::MutateFreeze, + Right: fungibles::MutateFreeze, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::MutateFreeze for UnionOf +{ + fn set_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::set_freeze(a, id, who, amount), + Right(a) => + >::set_freeze(a, id, who, amount), + } + } + fn extend_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => + >::extend_freeze(a, id, who, amount), + Right(a) => + >::extend_freeze(a, id, who, amount), + } + } + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::thaw(a, id, who), + Right(a) => >::thaw(a, id, who), + } + } +} + +pub struct ConvertImbalanceDropHandler< + Left, + Right, + LeftAssetId, + RightAssetId, + Criterion, + AssetKind, + Balance, + AccountId, +>( + sp_std::marker::PhantomData<( + Left, + Right, + LeftAssetId, + RightAssetId, + Criterion, + AssetKind, + Balance, + AccountId, + )>, +); + +impl< + Left: fungibles::HandleImbalanceDrop, + Right: fungibles::HandleImbalanceDrop, + LeftAssetId, + RightAssetId, + Criterion: Convert>, + AssetKind, + Balance, + AccountId, + > fungibles::HandleImbalanceDrop + for ConvertImbalanceDropHandler< + Left, + Right, + LeftAssetId, + RightAssetId, + Criterion, + AssetKind, + Balance, + AccountId, + > +{ + fn handle(asset: AssetKind, amount: Balance) { + match Criterion::convert(asset) { + Left(a) => Left::handle(a, amount), + Right(a) => Right::handle(a, amount), + } + } +} + +impl< + Left: fungibles::Balanced, + Right: fungibles::Balanced, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Balanced for UnionOf +{ + type OnDropDebt = ConvertImbalanceDropHandler< + Left::OnDropDebt, + Right::OnDropDebt, + Left::AssetId, + Right::AssetId, + Criterion, + AssetKind, + Left::Balance, + AccountId, + >; + type OnDropCredit = ConvertImbalanceDropHandler< + Left::OnDropCredit, + Right::OnDropCredit, + Left::AssetId, + Right::AssetId, + Criterion, + AssetKind, + Left::Balance, + AccountId, + >; + + fn deposit( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + ) -> Result, DispatchError> { + match Criterion::convert(asset.clone()) { + Left(a) => >::deposit(a, who, value, precision) + .map(|debt| imbalance::from_fungibles(debt, asset)), + Right(a) => + >::deposit(a, who, value, precision) + .map(|debt| imbalance::from_fungibles(debt, asset)), + } + } + fn issue(asset: Self::AssetId, amount: Self::Balance) -> fungibles::Credit { + match Criterion::convert(asset.clone()) { + Left(a) => { + let credit = >::issue(a, amount); + imbalance::from_fungibles(credit, asset) + }, + Right(a) => { + let credit = >::issue(a, amount); + imbalance::from_fungibles(credit, asset) + }, + } + } + fn pair( + asset: Self::AssetId, + amount: Self::Balance, + ) -> (fungibles::Debt, fungibles::Credit) { + match Criterion::convert(asset.clone()) { + Left(a) => { + let (a, b) = >::pair(a, amount); + (imbalance::from_fungibles(a, asset.clone()), imbalance::from_fungibles(b, asset)) + }, + Right(a) => { + let (a, b) = >::pair(a, amount); + (imbalance::from_fungibles(a, asset.clone()), imbalance::from_fungibles(b, asset)) + }, + } + } + fn rescind(asset: Self::AssetId, amount: Self::Balance) -> fungibles::Debt { + match Criterion::convert(asset.clone()) { + Left(a) => { + let debt = >::rescind(a, amount); + imbalance::from_fungibles(debt, asset) + }, + Right(a) => { + let debt = >::rescind(a, amount); + imbalance::from_fungibles(debt, asset) + }, + } + } + fn resolve( + who: &AccountId, + credit: fungibles::Credit, + ) -> Result<(), fungibles::Credit> { + let asset = credit.asset(); + match Criterion::convert(asset.clone()) { + Left(a) => { + let credit = imbalance::from_fungibles(credit, a); + >::resolve(who, credit) + .map_err(|credit| imbalance::from_fungibles(credit, asset)) + }, + Right(a) => { + let credit = imbalance::from_fungibles(credit, a); + >::resolve(who, credit) + .map_err(|credit| imbalance::from_fungibles(credit, asset)) + }, + } + } + fn settle( + who: &AccountId, + debt: fungibles::Debt, + preservation: Preservation, + ) -> Result, fungibles::Debt> { + let asset = debt.asset(); + match Criterion::convert(asset.clone()) { + Left(a) => { + let debt = imbalance::from_fungibles(debt, a); + match >::settle(who, debt, preservation) { + Ok(credit) => Ok(imbalance::from_fungibles(credit, asset)), + Err(debt) => Err(imbalance::from_fungibles(debt, asset)), + } + }, + Right(a) => { + let debt = imbalance::from_fungibles(debt, a); + match >::settle(who, debt, preservation) { + Ok(credit) => Ok(imbalance::from_fungibles(credit, asset)), + Err(debt) => Err(imbalance::from_fungibles(debt, asset)), + } + }, + } + } + fn withdraw( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result, DispatchError> { + match Criterion::convert(asset.clone()) { + Left(a) => >::withdraw( + a, + who, + value, + precision, + preservation, + force, + ) + .map(|credit| imbalance::from_fungibles(credit, asset)), + Right(a) => >::withdraw( + a, + who, + value, + precision, + preservation, + force, + ) + .map(|credit| imbalance::from_fungibles(credit, asset)), + } + } +} + +impl< + Left: fungibles::BalancedHold, + Right: fungibles::BalancedHold, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::BalancedHold for UnionOf +{ + fn slash( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> (fungibles::Credit, Self::Balance) { + match Criterion::convert(asset.clone()) { + Left(a) => { + let (credit, amount) = + >::slash(a, reason, who, amount); + (imbalance::from_fungibles(credit, asset), amount) + }, + Right(a) => { + let (credit, amount) = + >::slash(a, reason, who, amount); + (imbalance::from_fungibles(credit, asset), amount) + }, + } + } +} + +impl< + Left: fungibles::Inspect + fungibles::Create, + Right: fungibles::Inspect + fungibles::Create, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > fungibles::Create for UnionOf +{ + fn create( + asset: AssetKind, + admin: AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => + >::create(a, admin, is_sufficient, min_balance), + Right(a) => >::create( + a, + admin, + is_sufficient, + min_balance, + ), + } + } +} + +impl< + Left: fungibles::Inspect + AccountTouch, + Right: fungibles::Inspect + + AccountTouch< + Right::AssetId, + AccountId, + Balance = >::Balance, + >, + Criterion: Convert>, + AssetKind: AssetId, + AccountId, + > AccountTouch for UnionOf +{ + type Balance = >::Balance; + + fn deposit_required(asset: AssetKind) -> Self::Balance { + match Criterion::convert(asset) { + Left(a) => >::deposit_required(a), + Right(a) => >::deposit_required(a), + } + } + + fn should_touch(asset: AssetKind, who: &AccountId) -> bool { + match Criterion::convert(asset) { + Left(a) => >::should_touch(a, who), + Right(a) => >::should_touch(a, who), + } + } + + fn touch(asset: AssetKind, who: &AccountId, depositor: &AccountId) -> DispatchResult { + match Criterion::convert(asset) { + Left(a) => >::touch(a, who, depositor), + Right(a) => + >::touch(a, who, depositor), + } + } +} diff --git a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs index afbf291a0bbb92eed7b461110c3f33b43c3a9de3..d5dc93fcf28fe32046c8895aad8775911937c681 100644 --- a/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs +++ b/substrate/frame/support/src/traits/try_runtime/decode_entire_state.rs @@ -67,7 +67,7 @@ impl TryDecodeEntireStorage for Tuple { } /// A value could not be decoded. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct TryDecodeEntireStorageError { /// The key of the undecodable value. pub key: Vec, @@ -81,9 +81,22 @@ impl core::fmt::Display for TryDecodeEntireStorageError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, - "Failed to decode storage item `{}::{}`", + "`{}::{}` key `{}` is undecodable", &sp_std::str::from_utf8(&self.info.pallet_name).unwrap_or(""), &sp_std::str::from_utf8(&self.info.storage_name).unwrap_or(""), + array_bytes::bytes2hex("0x", &self.key) + ) + } +} + +impl core::fmt::Debug for TryDecodeEntireStorageError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "key: {} value: {} info: {:?}", + array_bytes::bytes2hex("0x", &self.key), + array_bytes::bytes2hex("0x", self.raw.clone().unwrap_or_default()), + self.info ) } } @@ -96,7 +109,6 @@ impl core::fmt::Display for TryDecodeEntireStorageError { fn decode_storage_info( info: StorageInfo, ) -> Result> { - let mut next_key = info.prefix.clone(); let mut decoded = 0; let decode_key = |key: &[u8]| match sp_io::storage::get(key) { @@ -104,29 +116,39 @@ fn decode_storage_info( Some(bytes) => { let len = bytes.len(); let _ = ::decode_all(&mut bytes.as_ref()).map_err(|_| { - vec![TryDecodeEntireStorageError { + TryDecodeEntireStorageError { key: key.to_vec(), raw: Some(bytes.to_vec()), info: info.clone(), - }] + } })?; - Ok::>(len) + Ok::(len) }, }; - decoded += decode_key(&next_key)?; + let mut errors = vec![]; + let mut next_key = Some(info.prefix.clone()); loop { - match sp_io::storage::next_key(&next_key) { + match next_key { Some(key) if key.starts_with(&info.prefix) => { - decoded += decode_key(&key)?; - next_key = key; + match decode_key(&key) { + Ok(bytes) => { + decoded += bytes; + }, + Err(e) => errors.push(e), + }; + next_key = sp_io::storage::next_key(&key); }, _ => break, } } - Ok(decoded) + if errors.is_empty() { + Ok(decoded) + } else { + Err(errors) + } } impl TryDecodeEntireStorage @@ -353,6 +375,12 @@ mod tests { // two bytes, cannot be decoded into u32. sp_io::storage::set(&Map::hashed_key_for(2), &[0u8, 1]); assert!(Map::try_decode_entire_state().is_err()); + assert_eq!(Map::try_decode_entire_state().unwrap_err().len(), 1); + + // multiple errs in the same map are be detected + sp_io::storage::set(&Map::hashed_key_for(3), &[0u8, 1]); + assert!(Map::try_decode_entire_state().is_err()); + assert_eq!(Map::try_decode_entire_state().unwrap_err().len(), 2); }) } diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index b88a459d92f2eb4c6b6f3ed35eb34c71ba5a8dfc..eac2dafbf00cc199fcc45ecc564eacb06a5a9632 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -8,12 +8,15 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] static_assertions = "1.1.0" -serde = { version = "1.0.193", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", default-features = false, features = ["derive"] } 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"] } @@ -58,7 +61,10 @@ std = [ "sp-version/std", "test-pallet/std", ] -experimental = ["frame-support/experimental"] +experimental = [ + "frame-support/experimental", + "frame-system/experimental", +] try-runtime = [ "frame-executive/try-runtime", "frame-support/try-runtime", diff --git a/substrate/frame/support/test/compile_pass/Cargo.toml b/substrate/frame/support/test/compile_pass/Cargo.toml index 916771bd471775f315af3e27756f91f7798827a3..0617aa105a21f1f3a583404465da97f0af79b176 100644 --- a/substrate/frame/support/test/compile_pass/Cargo.toml +++ b/substrate/frame/support/test/compile_pass/Cargo.toml @@ -8,6 +8,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/support/test/compile_pass/src/lib.rs b/substrate/frame/support/test/compile_pass/src/lib.rs index 6ea37fb27e72f31fe0e88e1f35815675bc33660e..b304dfcb2823e15fedbe7d01bc3cc15b18a1b57f 100644 --- a/substrate/frame/support/test/compile_pass/src/lib.rs +++ b/substrate/frame/support/test/compile_pass/src/lib.rs @@ -22,7 +22,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use renamed_frame_support::{ - construct_runtime, parameter_types, + construct_runtime, derive_impl, parameter_types, traits::{ConstU16, ConstU32, ConstU64, Everything}, }; use sp_core::{sr25519, H256}; @@ -51,6 +51,7 @@ parameter_types! { pub const Version: RuntimeVersion = VERSION; } +#[derive_impl(renamed_frame_system::config_preludes::TestDefaultConfig as renamed_frame_system::DefaultConfig)] impl renamed_frame_system::Config for Runtime { type BaseCallFilter = Everything; type BlockWeights = (); diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index e1136d5bc025418384e63985d3bc509ff3c1c779..48adbcab5826157ea3edfb4eb931f77b9b9794a1 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -8,13 +8,16 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["derive"] } +serde = { version = "1.0.195", default-features = false, features = ["derive"] } 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/src/lib.rs b/substrate/frame/support/test/src/lib.rs index 6b38d42d33d0d06b0115ad887825ab31a294a70d..a8a723375033a48e10e3f7f2941bb025991ac869 100644 --- a/substrate/frame/support/test/src/lib.rs +++ b/substrate/frame/support/test/src/lib.rs @@ -50,6 +50,8 @@ pub mod pallet { + From>; /// The runtime call type. type RuntimeCall; + /// Contains an aggregation of all tasks in this runtime. + type RuntimeTask; /// The runtime event type. type RuntimeEvent: Parameter + Member diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml index 0b3b584910a9e369e24f19fa74beb87e127e3b53..632ea4e794f6f8edb46e57d190be108219935ea0 100644 --- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml +++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml @@ -8,6 +8,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index a791c313b4a877fea67051cf2bccfe0bfbc6ed5d..b08efb3a84213e107916dee6c43c62d9ee90e2d4 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -17,6 +17,7 @@ error: use of deprecated constant `WhereSection::_w`: | |_^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` = note: this error originates in the macro `frame_support::match_and_insert` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Runtime: Config` is not satisfied diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr index bf53f43b9ba7bccdc6e1ec0cf720bc6725a6717c..8458de97f6d3e60590f4138002702ac1d62b4afb 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr @@ -1,4 +1,4 @@ -error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`. +error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`, `Task`. --> tests/construct_runtime_ui/generics_in_invalid_module.rs:24:36 | 24 | Balance: balances::::{Call, Origin}, diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr index ad631de204e6766cc5d0d61106c23fc4f3fdbcb9..feb61793151da4ccbb78e53d83a648b7f2159f4f 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `Task`, `LockId`, `SlashReason` --> tests/construct_runtime_ui/invalid_module_details_keyword.rs:23:20 | 23 | system: System::{enum}, diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr index b5b89a5a270c903b0e61198a8c05a0781cd2c7a5..97943dfc1763a014621a491578f5cffca90822fa 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr @@ -1,4 +1,4 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `Task`, `LockId`, `SlashReason` --> tests/construct_runtime_ui/invalid_module_entry.rs:24:23 | 24 | Balance: balances::{Unexpected}, 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 7ed77e75da4c31b5f2623307afd6ec53b9453743..3b6329c650fa65208e01c7d991e321f256b7eecf 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 @@ -31,19 +31,34 @@ 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: `Self::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: `Self::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: `Self::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 @@ -54,10 +69,23 @@ error[E0412]: cannot find type `PalletInfo` in this scope 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 = (); + | ++++++ diff --git a/substrate/frame/support/test/tests/derive_impl.rs b/substrate/frame/support/test/tests/derive_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..675e85f4bfce5388ae0bf790a2355bff242cd477 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_impl.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::derive_impl; + +trait Shape { + fn area(&self) -> u32; +} + +struct SomeRectangle {} + +#[frame_support::register_default_impl(SomeRectangle)] +impl Shape for SomeRectangle { + #[cfg(not(feature = "feature-frame-testing"))] + fn area(&self) -> u32 { + 10 + } + + #[cfg(feature = "feature-frame-testing")] + fn area(&self) -> u32 { + 0 + } +} + +struct SomeSquare {} + +#[derive_impl(SomeRectangle)] +impl Shape for SomeSquare {} + +#[test] +fn test_feature_parsing() { + let square = SomeSquare {}; + #[cfg(not(feature = "feature-frame-testing"))] + assert_eq!(square.area(), 10); + + #[cfg(feature = "feature-frame-testing")] + assert_eq!(square.area(), 0); +} diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr index f3ac6b2328110f0e2c6e95c7c6f995b3dea2d1dc..58aae713f752d8b99a8b814a47606237b431bc3f 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.stderr @@ -2,9 +2,13 @@ error[E0412]: cannot find type `RuntimeCall` in this scope --> tests/derive_impl_ui/inject_runtime_type_fails_when_type_not_in_scope.rs:30:10 | 30 | type RuntimeCall = (); - | ^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeCall` + | ^^^^^^^^^^^ ... 35 | #[derive_impl(Pallet)] // Injects type RuntimeCall = RuntimeCall; | ---------------------- in this macro invocation | = note: this error originates in the macro `Pallet` 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 + | +30 | type Self::RuntimeCall = (); + | ++++++ 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 501aad0419f8e85a67f1b9cde11d5c5452feaff0..cda20288984ae535c0755fd1e6814384d124c515 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`, `RuntimeOrigin` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index d2e5b1851634f9ebe8b0a2853c9e826a37a547c3..0223979d7f0e2420d67871ec799272ad017f25f2 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -257,6 +257,13 @@ pub mod pallet { pub fn check_for_dispatch_context(_origin: OriginFor) -> DispatchResult { with_context::<(), _>(|_| ()).ok_or_else(|| DispatchError::Unavailable) } + + #[cfg(feature = "frame-feature-testing")] + #[pallet::call_index(5)] + #[pallet::weight({1})] + pub fn foo_feature_test(_origin: OriginFor) -> DispatchResult { + Ok(()) + } } #[pallet::error] @@ -269,6 +276,8 @@ pub mod pallet { #[codec(skip)] Skipped(u128), CompactU8(#[codec(compact)] u8), + #[cfg(feature = "frame-feature-testing")] + FeatureTest, } #[pallet::event] @@ -796,6 +805,7 @@ fn call_expand() { } ); assert_eq!(call_foo.get_call_name(), "foo"); + #[cfg(not(feature = "frame-feature-testing"))] assert_eq!( pallet::Call::::get_call_names(), &[ @@ -806,9 +816,24 @@ fn call_expand() { "check_for_dispatch_context" ], ); + #[cfg(feature = "frame-feature-testing")] + assert_eq!( + pallet::Call::::get_call_names(), + &[ + "foo", + "foo_storage_layer", + "foo_index_out_of_order", + "foo_no_post_info", + "check_for_dispatch_context", + "foo_feature_test" + ], + ); assert_eq!(call_foo.get_call_index(), 0u8); - assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]) + #[cfg(not(feature = "frame-feature-testing"))] + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]); + #[cfg(feature = "frame-feature-testing")] + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8, 5u8]); } #[test] @@ -816,7 +841,10 @@ fn call_expand_index() { let call_foo = pallet::Call::::foo_index_out_of_order {}; assert_eq!(call_foo.get_call_index(), 4u8); - assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]) + #[cfg(not(feature = "frame-feature-testing"))] + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]); + #[cfg(feature = "frame-feature-testing")] + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8, 5u8]); } #[test] @@ -838,6 +866,8 @@ fn error_expand() { }), ); assert_eq!( as PalletError>::MAX_ENCODED_SIZE, 3); + #[cfg(feature = "frame-feature-testing")] + assert_eq!(format!("{:?}", pallet::Error::::FeatureTest), String::from("FeatureTest"),); } #[test] @@ -2379,3 +2409,33 @@ fn test_dispatch_context() { .dispatch(RuntimeOrigin::root())); }); } + +#[test] +fn test_call_feature_parsing() { + let call = pallet::Call::::check_for_dispatch_context {}; + match call { + pallet::Call::::check_for_dispatch_context {} | + pallet::Call::::foo { .. } | + pallet::Call::foo_storage_layer { .. } | + pallet::Call::foo_index_out_of_order {} | + pallet::Call::foo_no_post_info {} => (), + #[cfg(feature = "frame-feature-testing")] + pallet::Call::foo_feature_test {} => (), + pallet::Call::__Ignore(_, _) => (), + } +} + +#[test] +fn test_error_feature_parsing() { + let err = pallet::Error::::InsufficientProposersBalance; + match err { + pallet::Error::InsufficientProposersBalance | + pallet::Error::NonExistentStorageValue | + pallet::Error::Code(_) | + pallet::Error::Skipped(_) | + pallet::Error::CompactU8(_) => (), + #[cfg(feature = "frame-feature-testing")] + pallet::Error::FeatureTest => (), + pallet::Error::__Ignore(_, _) => (), + } +} diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs index a8250f8b15325cf2b2a149f2c8b0414d6308395a..79e9d6786717a5a27717cda754575c2ecd478aa3 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_explicit.rs @@ -90,6 +90,12 @@ fn module_error_outer_enum_expand_explicit() { frame_system::Error::NonDefaultComposite => (), frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), + #[cfg(feature = "experimental")] + frame_system::Error::InvalidTask => (), + #[cfg(feature = "experimental")] + frame_system::Error::FailedTask => (), + frame_system::Error::NothingAuthorized => (), + frame_system::Error::Unauthorized => (), frame_system::Error::__Ignore(_, _) => (), }, diff --git a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs index 191f095f5d78d4c1a1276326451b1afed674989a..4bd8ee0bb39a574b2ff3f59b571c1209fc675da4 100644 --- a/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs +++ b/substrate/frame/support/test/tests/pallet_outer_enums_implicit.rs @@ -90,6 +90,12 @@ fn module_error_outer_enum_expand_implicit() { frame_system::Error::NonDefaultComposite => (), frame_system::Error::NonZeroRefCount => (), frame_system::Error::CallFiltered => (), + #[cfg(feature = "experimental")] + frame_system::Error::InvalidTask => (), + #[cfg(feature = "experimental")] + frame_system::Error::FailedTask => (), + frame_system::Error::NothingAuthorized => (), + frame_system::Error::Unauthorized => (), frame_system::Error::__Ignore(_, _) => (), }, diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 08ea7c0bec3a5dfca33ff497b2981e9acb17f294..40f8f129830496df6d9b03c0ed505b9bbd4958cb 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -10,6 +10,7 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound.rs:38:36 diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index 80316fcd2489747ca34a9b030474c453e7f3d08d..5744c636235081449f6f12767821016a70179f4e 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -10,6 +10,7 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:36 @@ -45,9 +46,9 @@ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is = note: required for `::Bar` to implement `Encode` error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:34:12 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:38:42 | -34 | #[pallet::call] - | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` +38 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` | = note: required for `::Bar` to implement `Decode` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index d45b74bad8428d57273e0cb4f0d304706dca9fd1..b58e4516bceb975ea4937f90ac15acec796829cc 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -10,6 +10,7 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error[E0277]: `Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound_3.rs:40:36 diff --git a/substrate/frame/support/test/tests/pallet_ui/call_missing_index.stderr b/substrate/frame/support/test/tests/pallet_ui/call_missing_index.stderr index 4d55ef798569bbd6dfa013dec3de081c6bbfa246..ba06285bdeeabbd6d6b45f18e75c3c622fbffe7b 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_missing_index.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_missing_index.stderr @@ -11,6 +11,7 @@ error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: | ^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_1::_w`: It is deprecated to use implicit call indices. diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr index cf23a76f8ea055ce4c4b0e55f228185fc4a2961b..4b5abdcd0e6d2056281af00c76508678906f8098 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr @@ -18,3 +18,4 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^^^^^^^^^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr index ccd5a935773c31d69b0b9043cf3d56d3150c7000..d399df4d85bd41f8b7d4f2eeeeb8f1be3e9ff506 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr @@ -10,3 +10,4 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr index aadb939b6454f76e042437b2427d5c950dfb3b08..d7e4951e49fcf5042bd0b450a6fd42c7ee92a3a8 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr @@ -18,6 +18,7 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: | ^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error: use of deprecated constant `pallet::warnings::ConstantWeight_1::_w`: It is deprecated to use hard-coded constant as call weight. diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr index e8e6f2fe6df06dec235227b06001ab191be4bb46..339551d9811c50511f5454a23dd36e0c14c50398 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr @@ -17,3 +17,4 @@ error: unused import: `frame_system::pallet_prelude::*` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D unused-imports` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_imports)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/call_weight_unchecked_warning.stderr b/substrate/frame/support/test/tests/pallet_ui/call_weight_unchecked_warning.stderr index 89fc1e0820f5ea0969b05b00efee7ca70c95e593..33302a2a0278a12c4037e4eb194bf7032f311f08 100644 --- a/substrate/frame/support/test/tests/pallet_ui/call_weight_unchecked_warning.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/call_weight_unchecked_warning.stderr @@ -10,3 +10,4 @@ error: use of deprecated constant `pallet::warnings::UncheckedWeightWitness_0::_ | ^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr b/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr index cdc8f623142b16118d23d87165ea80562dd79900..8de9c8990b00d3cd76c847be625fc1848e432206 100644 --- a/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr @@ -1,4 +1,4 @@ -error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` +error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason`, `Task` --> tests/pallet_ui/composite_enum_unsupported_identifier.rs:27:11 | 27 | pub enum HoldReasons {} diff --git a/substrate/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr b/substrate/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr index 942db0ab4699f779f8ec83b5e932a818483fe508..e227033d3646bac74b23267531730e0a59ce06fb 100644 --- a/substrate/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr @@ -7,3 +7,4 @@ error: use of deprecated struct `pallet::_::Store`: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr index bcfe43d008f8c43045a90f45cdd27ae031a4e12a..01254584c62693cd3665dc8f64e9e37e79da3b6c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr @@ -11,6 +11,7 @@ error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: | ^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: It is deprecated to use hard-coded constant as call weight. diff --git a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index 531e8bdffeb0c0f073e1775cdc0f683beb3d198d..02ead305dd81ad3d3dd0bdab9b74d2a255e8b61f 100644 --- a/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -11,6 +11,7 @@ error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: | ^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: It is deprecated to use hard-coded constant as call weight. @@ -26,8 +27,15 @@ error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:28:12 | -28 | #[pallet::pallet] - | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Vec` +28 | #[pallet::pallet] + | _______________^ +29 | | pub struct Pallet(_); +30 | | +31 | | // Your Pallet's configuration trait, representing custom external types and interfaces. +... | +35 | | #[pallet::storage] +36 | | type MyStorage = StorageValue<_, Vec>; + | |__________________^ the trait `MaxEncodedLen` is not implemented for `Vec` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr index ea1d0ed99cd39747bfc1f94a9e3433809baa82b5..9cefd2f4899a159c2b0e2962fb4fb542d000672c 100644 --- a/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied - --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:18:1 + --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:28:15 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` +28 | CustomError(crate::MyError), + | ^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` | = help: the following other types implement trait `PalletError`: bool @@ -14,4 +14,3 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied u8 u16 and $N others - = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs new file mode 100644 index 0000000000000000000000000000000000000000..234e220f49d8901afd3abc3c24bd947c708f63fb --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/pass/task_valid.rs @@ -0,0 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::{ensure, pallet_prelude::DispatchResult}; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|i, j| i == 0u32 && j == 2u64)] + #[pallet::task_list(vec![(0u32, 2u64), (2u32, 4u64)].iter())] + #[pallet::task_weight(0.into())] + fn foo(i: u32, j: u64) -> DispatchResult { + ensure!(i == 0, "i must be 0"); + ensure!(j == 2, "j must be 2"); + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index b5d108275249e5c2deb95bc0336165aa5a8ba8c1..4229d1e8a5458470493a2ec0003736ea6064bdc0 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -1,8 +1,15 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -16,8 +23,15 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -36,8 +50,15 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -57,8 +78,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `TypeInfo` is not implemented for `Bar` | = help: the following other types implement trait `TypeInfo`: bool @@ -76,8 +99,10 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -91,8 +116,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -111,8 +138,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -130,10 +159,12 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -143,13 +174,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -164,13 +196,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -186,4 +219,3 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index b58902590b8560f5d8d5e6bbc5bdb652aeb82b44..855d289d0a160e08e45fa0c3b9b50616fb9c8ade 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -1,8 +1,15 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -16,8 +23,15 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -36,8 +50,15 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:27:12 | -27 | #[pallet::without_storage_info] - | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +27 | #[pallet::without_storage_info] + | _______________^ +28 | | pub struct Pallet(core::marker::PhantomData); +29 | | +30 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -57,8 +78,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `TypeInfo` is not implemented for `Bar` | = help: the following other types implement trait `TypeInfo`: bool @@ -76,8 +99,10 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -91,8 +116,10 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -111,8 +138,10 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -38 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -130,10 +159,12 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeDecode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeDecode`: Box @@ -143,13 +174,14 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied = note: required for `Bar` to implement `Decode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `EncodeLike` is not implemented for `Bar` | = help: the following other types implement trait `EncodeLike`: @@ -164,13 +196,14 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:18:1 + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:38:12 | -18 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` +38 | #[pallet::storage] + | _______________^ +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `WrapperTypeEncode` is not implemented for `Bar` | = help: the following other types implement trait `WrapperTypeEncode`: Box @@ -186,4 +219,3 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `TryDecodeEntireStorage` - = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index e04de98800ec20fc449666f671bcd91058fb3335..504db21feeb2b226da122e4a1719e935145d0c31 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -1,8 +1,15 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied --> tests/pallet_ui/storage_info_unsatisfied.rs:26:12 | -26 | #[pallet::pallet] - | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` +26 | #[pallet::pallet] + | _______________^ +27 | | pub struct Pallet(core::marker::PhantomData); +28 | | +29 | | #[pallet::hooks] +... | +38 | | #[pallet::storage] +39 | | type Foo = StorageValue<_, Bar>; + | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 31fe3b57338968822c8f49837c5176aa859be2cf..6fd0b1959c860affc94b56c63b76392a310f2eea 100644 --- a/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -1,8 +1,15 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied --> tests/pallet_ui/storage_info_unsatisfied_nmap.rs:29:12 | -29 | #[pallet::pallet] - | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` +29 | #[pallet::pallet] + | _______________^ +30 | | pub struct Pallet(core::marker::PhantomData); +31 | | +32 | | #[pallet::hooks] +... | +41 | | #[pallet::storage] +42 | | type Foo = StorageNMap<_, Key, u32>; + | |____________^ the trait `MaxEncodedLen` is not implemented for `Bar` | = help: the following other types implement trait `MaxEncodedLen`: bool diff --git a/substrate/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr b/substrate/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr index 20144d825e83248b97c5c53053276aab002aa4ac..ccb55122e8169e84f776dd41504738ab0f1772f5 100644 --- a/substrate/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr @@ -7,6 +7,7 @@ error: use of deprecated struct `pallet::_::Store`: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D deprecated` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(deprecated)]` error[E0446]: private type `_GeneratedPrefixForStorageFoo` in public interface --> tests/pallet_ui/store_trait_leak_private.rs:28:37 diff --git a/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs new file mode 100644 index 0000000000000000000000000000000000000000..95f5655af198e65e7ae39a4c89534cfe7e30f55e --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + pub struct Task; +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr new file mode 100644 index 0000000000000000000000000000000000000000..eaa8e718840e6dae8fabc70dcd9490786edfa8b1 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_can_only_be_attached_to_impl.stderr @@ -0,0 +1,5 @@ +error: expected `impl` + --> tests/pallet_ui/task_can_only_be_attached_to_impl.rs:30:5 + | +30 | pub struct Task; + | ^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs new file mode 100644 index 0000000000000000000000000000000000000000..1db96869155bb1a590d60206472b8045c40ff66c --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|flag: bool| flag)] + #[pallet::task_list(vec![1, 2].iter())] + #[pallet::task_weight(0.into())] + fn foo(_i: u32) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr new file mode 100644 index 0000000000000000000000000000000000000000..9c7bad8119f54d53f10970870bc1363613faf366 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_condition_invalid_arg.stderr @@ -0,0 +1,23 @@ +error: unused import: `frame_system::pallet_prelude::OriginFor` + --> tests/pallet_ui/task_condition_invalid_arg.rs:21:6 + | +21 | use frame_system::pallet_prelude::OriginFor; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unused-imports` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_imports)]` + +error[E0308]: mismatched types + --> tests/pallet_ui/task_condition_invalid_arg.rs:35:10 + | +32 | #[pallet::task_condition(|flag: bool| flag)] + | ----------------- arguments to this function are incorrect +... +35 | fn foo(_i: u32) -> DispatchResult { + | ^^ expected `bool`, found `u32` + | +note: closure parameter defined here + --> tests/pallet_ui/task_condition_invalid_arg.rs:32:29 + | +32 | #[pallet::task_condition(|flag: bool| flag)] + | ^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs new file mode 100644 index 0000000000000000000000000000000000000000..6875bc13b8fa00c3540a4d15f20084ed427981c0 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(0)] + #[pallet::task_list(vec![1, 2].iter())] + #[pallet::task_weight(0.into())] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr new file mode 100644 index 0000000000000000000000000000000000000000..05c0ba5eecf26ccd204532023ef5d12a2e367505 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_condition.stderr @@ -0,0 +1,28 @@ +error: unused import: `frame_system::pallet_prelude::OriginFor` + --> tests/pallet_ui/task_invalid_condition.rs:21:6 + | +21 | use frame_system::pallet_prelude::OriginFor; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unused-imports` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_imports)]` + +error[E0308]: mismatched types + --> tests/pallet_ui/task_invalid_condition.rs:18:1 + | +18 | #[frame_support::pallet(dev_mode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected integer, found `()` + | expected due to this + | + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0618]: expected function, found `{integer}` + --> tests/pallet_ui/task_invalid_condition.rs:32:28 + | +18 | #[frame_support::pallet(dev_mode)] + | ---------------------------------- call expression requires function +... +32 | #[pallet::task_condition(0)] + | ^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs new file mode 100644 index 0000000000000000000000000000000000000000..2a4b40523a6868b32ff011115f201fb45b6e0722 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.rs @@ -0,0 +1,39 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index("0")] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr new file mode 100644 index 0000000000000000000000000000000000000000..d33600455bf8ae836f9e6c2a046e095006d7b346 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_index.stderr @@ -0,0 +1,5 @@ +error: expected integer literal + --> tests/pallet_ui/task_invalid_index.rs:31:24 + | +31 | #[pallet::task_index("0")] + | ^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs new file mode 100644 index 0000000000000000000000000000000000000000..bb6438aaf10505c1dac79daa7d5258be41f34e7d --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|| true)] + #[pallet::task_list(0)] + #[pallet::task_weight(0.into())] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr new file mode 100644 index 0000000000000000000000000000000000000000..536d02610cb9f0f4a2aca736dbdf1ad65f356416 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_list.stderr @@ -0,0 +1,20 @@ +error: unused import: `frame_system::pallet_prelude::OriginFor` + --> tests/pallet_ui/task_invalid_list.rs:21:6 + | +21 | use frame_system::pallet_prelude::OriginFor; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unused-imports` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_imports)]` + +error[E0689]: can't call method `map` on ambiguous numeric type `{integer}` + --> tests/pallet_ui/task_invalid_list.rs:18:1 + | +18 | #[frame_support::pallet(dev_mode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) +help: you must specify a concrete type for this numeric value, like `i32` + | +33 | #[pallet::task_list(0_i32)] + | ~~~~~ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs new file mode 100644 index 0000000000000000000000000000000000000000..a0c4040347a07f3d2fa9a923daa19fe0e5673bc1 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|| true)] + #[pallet::task_list(vec![1, 2].iter())] + #[pallet::task_weight("0")] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr new file mode 100644 index 0000000000000000000000000000000000000000..24e925a069920537d3c80275a18d7818e5845ef3 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_invalid_weight.stderr @@ -0,0 +1,28 @@ +error: unused import: `frame_system::pallet_prelude::OriginFor` + --> tests/pallet_ui/task_invalid_weight.rs:21:6 + | +21 | use frame_system::pallet_prelude::OriginFor; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unused-imports` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unused_imports)]` + +error[E0308]: mismatched types + --> tests/pallet_ui/task_invalid_weight.rs:18:1 + | +18 | #[frame_support::pallet(dev_mode)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected integer, found `()` + | expected due to this + | + = note: this error originates in the attribute macro `frame_support::pallet` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0308]: mismatched types + --> tests/pallet_ui/task_invalid_weight.rs:34:25 + | +18 | #[frame_support::pallet(dev_mode)] + | ---------------------------------- expected `Weight` because of return type +... +34 | #[pallet::task_weight("0")] + | ^^^ expected `Weight`, found `&str` diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs new file mode 100644 index 0000000000000000000000000000000000000000..6ca6e37a5bdb58ec980723650a88e9a3fdd690ec --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.rs @@ -0,0 +1,39 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr new file mode 100644 index 0000000000000000000000000000000000000000..c709ec7eac94daaa56bea48683a1a5ff5236ae19 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_condition.stderr @@ -0,0 +1,5 @@ +error: missing `#[pallet::task_condition(..)]` attribute + --> tests/pallet_ui/task_missing_condition.rs:32:6 + | +32 | fn foo() -> DispatchResult { + | ^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs new file mode 100644 index 0000000000000000000000000000000000000000..ed98d229f18b718163661fbda4673e8c727b452d --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr new file mode 100644 index 0000000000000000000000000000000000000000..ba3c9d132b810a526d4e184a22cecd5dd140fe48 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_index.stderr @@ -0,0 +1,5 @@ +error: missing `#[pallet::task_index(..)]` attribute + --> tests/pallet_ui/task_missing_index.rs:31:6 + | +31 | fn foo() -> DispatchResult { + | ^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs new file mode 100644 index 0000000000000000000000000000000000000000..427efe127634ca6bba6c92be4bdf5f5737c72884 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|| true)] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr new file mode 100644 index 0000000000000000000000000000000000000000..f4ae26a75add85f9c6708584ba7ad574885b9ebe --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_list.stderr @@ -0,0 +1,5 @@ +error: missing `#[pallet::task_list(..)]` attribute + --> tests/pallet_ui/task_missing_list.rs:33:6 + | +33 | fn foo() -> DispatchResult { + | ^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs new file mode 100644 index 0000000000000000000000000000000000000000..704be1f1e0b8bc3c3d80b86a97183202831d8426 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[frame_support::pallet(dev_mode)] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::tasks_experimental] + impl Pallet { + #[pallet::task_index(0)] + #[pallet::task_condition(|| true)] + #[pallet::task_list(vec![1, 2].iter())] + fn foo() -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr new file mode 100644 index 0000000000000000000000000000000000000000..de7b2eb172058e91077be7d02856cab4c98b8850 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/task_missing_weight.stderr @@ -0,0 +1,5 @@ +error: missing `#[pallet::task_weight(..)]` attribute + --> tests/pallet_ui/task_missing_weight.rs:34:6 + | +34 | fn foo() -> DispatchResult { + | ^^^ diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index 3b454ac18f98d106e914b52e8864dc984c9da3b4..b0bab4ec756ae662c8617b4467f5b5e075bf79bc 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME system module" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,7 +20,7 @@ cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } 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 } @@ -53,6 +56,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime", "sp-runtime/try-runtime"] +experimental = ["frame-support/experimental"] [[bench]] name = "bench" diff --git a/substrate/frame/system/benchmarking/Cargo.toml b/substrate/frame/system/benchmarking/Cargo.toml index 3e92c56408e5ac8a23ed8f51f76db5da13637b34..8b9873f44b861d23fc367f6e610f0a8369292f82 100644 --- a/substrate/frame/system/benchmarking/Cargo.toml +++ b/substrate/frame/system/benchmarking/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME System benchmarking" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/system/benchmarking/src/lib.rs b/substrate/frame/system/benchmarking/src/lib.rs index d85b631af01850265bbf25404715ed86be90370d..18bfb85f52dfd4c2b6a5c8a57e78494d97fbdeeb 100644 --- a/substrate/frame/system/benchmarking/src/lib.rs +++ b/substrate/frame/system/benchmarking/src/lib.rs @@ -21,10 +21,7 @@ #![cfg(feature = "runtime-benchmarks")] use codec::Encode; -use frame_benchmarking::{ - v1::{benchmarks, whitelisted_caller}, - BenchmarkError, -}; +use frame_benchmarking::{impl_benchmark_test_suite, v2::*}; use frame_support::{dispatch::DispatchClass, storage, traits::Get}; use frame_system::{Call, Pallet as System, RawOrigin}; use sp_core::storage::well_known_keys; @@ -55,69 +52,104 @@ pub trait Config: frame_system::Config { } } -benchmarks! { - remark { - let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32; +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn remark( + b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, + ) -> Result<(), BenchmarkError> { let remark_message = vec![1; b as usize]; let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), remark_message) - remark_with_event { - let b in 0 .. *T::BlockLength::get().max.get(DispatchClass::Normal) as u32; + #[extrinsic_call] + remark(RawOrigin::Signed(caller), remark_message); + + Ok(()) + } + + #[benchmark] + fn remark_with_event( + b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>, + ) -> Result<(), BenchmarkError> { let remark_message = vec![1; b as usize]; - let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), remark_message) + let caller: T::AccountId = whitelisted_caller(); + let hash = T::Hashing::hash(&remark_message[..]); - set_heap_pages { - }: _(RawOrigin::Root, Default::default()) + #[extrinsic_call] + remark_with_event(RawOrigin::Signed(caller.clone()), remark_message); - set_code { + System::::assert_last_event( + frame_system::Event::::Remarked { sender: caller, hash }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_heap_pages() -> Result<(), BenchmarkError> { + #[extrinsic_call] + set_heap_pages(RawOrigin::Root, Default::default()); + + Ok(()) + } + + #[benchmark] + fn set_code() -> Result<(), BenchmarkError> { let runtime_blob = T::prepare_set_code_data(); T::setup_set_code_requirements(&runtime_blob)?; - }: _(RawOrigin::Root, runtime_blob) - verify { - T::verify_set_code() + + #[extrinsic_call] + set_code(RawOrigin::Root, runtime_blob); + + T::verify_set_code(); + Ok(()) } - #[extra] - set_code_without_checks { + #[benchmark(extra)] + fn set_code_without_checks() -> Result<(), BenchmarkError> { // Assume Wasm ~4MB let code = vec![1; 4_000_000 as usize]; T::setup_set_code_requirements(&code)?; - }: _(RawOrigin::Root, code) - verify { - let current_code = storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?; + + #[block] + { + System::::set_code_without_checks(RawOrigin::Root.into(), code)?; + } + + let current_code = + storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?; assert_eq!(current_code.len(), 4_000_000 as usize); + Ok(()) } - #[skip_meta] - set_storage { - let i in 0 .. 1000; - + #[benchmark(skip_meta)] + fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { // Set up i items to add let mut items = Vec::new(); - for j in 0 .. i { + for j in 0..i { let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); items.push((hash.clone(), hash.clone())); } let items_to_verify = items.clone(); - }: _(RawOrigin::Root, items) - verify { + + #[extrinsic_call] + set_storage(RawOrigin::Root, items); + // Verify that they're actually in the storage. for (item, _) in items_to_verify { let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?; assert_eq!(value, *item); } + Ok(()) } - #[skip_meta] - kill_storage { - let i in 0 .. 1000; - + #[benchmark(skip_meta)] + fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { // Add i items to storage let mut items = Vec::with_capacity(i as usize); - for j in 0 .. i { + for j in 0..i { let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec(); storage::unhashed::put_raw(&hash, &hash); items.push(hash); @@ -130,22 +162,23 @@ benchmarks! { } let items_to_verify = items.clone(); - }: _(RawOrigin::Root, items) - verify { + + #[extrinsic_call] + kill_storage(RawOrigin::Root, items); + // Verify that they're not in the storage anymore. for item in items_to_verify { assert!(storage::unhashed::get_raw(&item).is_none()); } + Ok(()) } - #[skip_meta] - kill_prefix { - let p in 0 .. 1000; - + #[benchmark(skip_meta)] + fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> { let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec(); let mut items = Vec::with_capacity(p as usize); // add p items that share a prefix - for i in 0 .. p { + for i in 0..p { let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec(); let key = [&prefix[..], &hash[..]].concat(); storage::unhashed::put_raw(&key, &key); @@ -157,12 +190,45 @@ benchmarks! { let value = storage::unhashed::get_raw(item).ok_or("No value stored")?; assert_eq!(value, *item); } - }: _(RawOrigin::Root, prefix, p) - verify { + + #[extrinsic_call] + kill_prefix(RawOrigin::Root, prefix, p); + // Verify that they're not in the storage anymore. for item in items { assert!(storage::unhashed::get_raw(&item).is_none()); } + Ok(()) + } + + #[benchmark] + fn authorize_upgrade() -> Result<(), BenchmarkError> { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + let hash = T::Hashing::hash(&runtime_blob); + + #[extrinsic_call] + authorize_upgrade(RawOrigin::Root, hash); + + assert!(System::::authorized_upgrade().is_some()); + Ok(()) + } + + #[benchmark] + fn apply_authorized_upgrade() -> Result<(), BenchmarkError> { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + let hash = T::Hashing::hash(&runtime_blob); + // Will be heavier when it needs to do verification (i.e. don't use `...without_checks`). + System::::authorize_upgrade(RawOrigin::Root.into(), hash)?; + + #[extrinsic_call] + apply_authorized_upgrade(RawOrigin::Root, runtime_blob); + + // Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is + // gone. + assert!(System::::authorized_upgrade().is_none()); + Ok(()) } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/frame/system/rpc/runtime-api/Cargo.toml b/substrate/frame/system/rpc/runtime-api/Cargo.toml index 68dc7fc9905960bf5d621cf504417acc5a82644b..8cec5de8d1e527862c0a91316f6c84bb2ec89b22 100644 --- a/substrate/frame/system/rpc/runtime-api/Cargo.toml +++ b/substrate/frame/system/rpc/runtime-api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Runtime API definition required by System RPC extensions." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 640cb133213274e4f94531e8fa42e3c640042343..069217bcee46b4663c836a750bfd90a568c1a4d2 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -17,27 +17,60 @@ //! # System Pallet //! -//! The System pallet provides low-level access to core types and cross-cutting utilities. -//! It acts as the base layer for other pallets to interact with the Substrate framework components. +//! The System pallet provides low-level access to core types and cross-cutting utilities. It acts +//! as the base layer for other pallets to interact with the Substrate framework components. //! //! - [`Config`] //! //! ## Overview //! -//! The System pallet defines the core data types used in a Substrate runtime. -//! It also provides several utility functions (see [`Pallet`]) for other FRAME pallets. +//! The System pallet defines the core data types used in a Substrate runtime. It also provides +//! several utility functions (see [`Pallet`]) for other FRAME pallets. //! -//! In addition, it manages the storage items for extrinsics data, indexes, event records, and -//! digest items, among other things that support the execution of the current block. +//! In addition, it manages the storage items for extrinsic data, indices, event records, and digest +//! items, among other things that support the execution of the current block. //! -//! It also handles low-level tasks like depositing logs, basic set up and take down of -//! temporary storage entries, and access to previous block hashes. +//! It also handles low-level tasks like depositing logs, basic set up and take down of temporary +//! storage entries, and access to previous block hashes. //! //! ## Interface //! //! ### Dispatchable Functions //! -//! The System pallet does not implement any dispatchable functions. +//! The System pallet provides dispatchable functions that, with the exception of `remark`, manage +//! low-level or privileged functionality of a Substrate-based runtime. +//! +//! - `remark`: Make some on-chain remark. +//! - `set_heap_pages`: Set the number of pages in the WebAssembly environment's heap. +//! - `set_code`: Set the new runtime code. +//! - `set_code_without_checks`: Set the new runtime code without any checks. +//! - `set_storage`: Set some items of storage. +//! - `kill_storage`: Kill some items from storage. +//! - `kill_prefix`: Kill all storage items with a key that starts with the given prefix. +//! - `remark_with_event`: Make some on-chain remark and emit an event. +//! - `do_task`: Do some specified task. +//! - `authorize_upgrade`: Authorize new runtime code. +//! - `authorize_upgrade_without_checks`: Authorize new runtime code and an upgrade sans +//! verification. +//! - `apply_authorized_upgrade`: Provide new, already-authorized runtime code. +//! +//! #### A Note on Upgrades +//! +//! The pallet provides two primary means of upgrading the runtime, a single-phase means using +//! `set_code` and a two-phase means using `authorize_upgrade` followed by +//! `apply_authorized_upgrade`. The first will directly attempt to apply the provided `code` +//! (application may have to be scheduled, depending on the context and implementation of the +//! `OnSetCode` trait). +//! +//! The `authorize_upgrade` route allows the authorization of a runtime's code hash. Once +//! authorized, anyone may upload the correct runtime to apply the code. This pattern is useful when +//! providing the runtime ahead of time may be unwieldy, for example when a large preimage (the +//! code) would need to be stored on-chain or sent over a message transport protocol such as a +//! bridge. +//! +//! The `*_without_checks` variants do not perform any version checks, so using them runs the risk +//! of applying a downgrade or entirely other chain specification. They will still validate that the +//! `code` meets the authorized hash. //! //! ### Public Functions //! @@ -59,7 +92,7 @@ //! - [`CheckTxVersion`]: Checks that the transaction version is the same as the one used to sign //! the transaction. //! -//! Lookup the runtime aggregator file (e.g. `node/runtime`) to see the full list of signed +//! Look up the runtime aggregator file (e.g. `node/runtime`) to see the full list of signed //! extensions included in a chain. #![cfg_attr(not(feature = "std"), no_std)] @@ -77,6 +110,10 @@ use sp_runtime::{ Hash, Header, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, Saturating, SimpleBitOps, StaticLookup, Zero, }, + transaction_validity::{ + InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity, + ValidTransaction, + }, DispatchError, RuntimeDebug, }; #[cfg(any(feature = "std", test))] @@ -90,9 +127,10 @@ use frame_support::traits::BuildGenesisConfig; use frame_support::{ dispatch::{ extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, - DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, + DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, PostDispatchInfo, }, - impl_ensure_origin_with_arg_ignoring_arg, + ensure, impl_ensure_origin_with_arg_ignoring_arg, + pallet_prelude::Pays, storage::{self, StorageStreamIter}, traits::{ ConstU32, Contains, EnsureOrigin, EnsureOriginWithArg, Get, HandleLifetime, @@ -198,6 +236,20 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, } } +/// Information needed when a new runtime binary is submitted and needs to be authorized before +/// replacing the current runtime. +#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CodeUpgradeAuthorization +where + T: Config, +{ + /// Hash of the new runtime binary. + code_hash: T::Hash, + /// Whether or not to carry out version checks. + check_version: bool, +} + #[frame_support::pallet] pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; @@ -241,6 +293,8 @@ pub mod pallet { type RuntimeCall = (); #[inject_runtime_type] type PalletInfo = (); + #[inject_runtime_type] + type RuntimeTask = (); type BaseCallFilter = frame_support::traits::Everything; type BlockHashCount = frame_support::traits::ConstU64<10>; type OnSetCode = (); @@ -321,6 +375,10 @@ pub mod pallet { #[inject_runtime_type] type RuntimeCall = (); + /// The aggregated Task type, injected by `construct_runtime!`. + #[inject_runtime_type] + type RuntimeTask = (); + /// Converts a module to the index of the module, injected by `construct_runtime!`. #[inject_runtime_type] type PalletInfo = (); @@ -400,6 +458,10 @@ pub mod pallet { + Debug + From>; + /// The aggregated `RuntimeTask` type. + #[pallet::no_default_bounds] + type RuntimeTask: Task; + /// This stores the number of previous transactions associated with a sender account. type Nonce: Parameter + Member @@ -628,6 +690,79 @@ pub mod pallet { Self::deposit_event(Event::Remarked { sender: who, hash }); Ok(().into()) } + + #[cfg(feature = "experimental")] + #[pallet::call_index(8)] + #[pallet::weight(task.weight())] + pub fn do_task(origin: OriginFor, task: T::RuntimeTask) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + + if !task.is_valid() { + return Err(Error::::InvalidTask.into()) + } + + Self::deposit_event(Event::TaskStarted { task: task.clone() }); + if let Err(err) = task.run() { + Self::deposit_event(Event::TaskFailed { task, err }); + return Err(Error::::FailedTask.into()) + } + + // Emit a success event, if your design includes events for this pallet. + Self::deposit_event(Event::TaskCompleted { task }); + + // Return success. + Ok(().into()) + } + + /// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied + /// later. + /// + /// This call requires Root origin. + #[pallet::call_index(9)] + #[pallet::weight((T::SystemWeightInfo::authorize_upgrade(), DispatchClass::Operational))] + pub fn authorize_upgrade(origin: OriginFor, code_hash: T::Hash) -> DispatchResult { + ensure_root(origin)?; + Self::do_authorize_upgrade(code_hash, true); + Ok(()) + } + + /// Authorize an upgrade to a given `code_hash` for the runtime. The runtime can be supplied + /// later. + /// + /// WARNING: This authorizes an upgrade that will take place without any safety checks, for + /// example that the spec name remains the same and that the version number increases. Not + /// recommended for normal use. Use `authorize_upgrade` instead. + /// + /// This call requires Root origin. + #[pallet::call_index(10)] + #[pallet::weight((T::SystemWeightInfo::authorize_upgrade(), DispatchClass::Operational))] + pub fn authorize_upgrade_without_checks( + origin: OriginFor, + code_hash: T::Hash, + ) -> DispatchResult { + ensure_root(origin)?; + Self::do_authorize_upgrade(code_hash, false); + Ok(()) + } + + /// Provide the preimage (runtime binary) `code` for an upgrade that has been authorized. + /// + /// If the authorization required a version check, this call will ensure the spec name + /// remains unchanged and that the spec version has increased. + /// + /// Depending on the runtime's `OnSetCode` configuration, this function may directly apply + /// the new `code` in the same block or attempt to schedule the upgrade. + /// + /// All origins are allowed. + #[pallet::call_index(11)] + #[pallet::weight((T::SystemWeightInfo::apply_authorized_upgrade(), DispatchClass::Operational))] + pub fn apply_authorized_upgrade( + _: OriginFor, + code: Vec, + ) -> DispatchResultWithPostInfo { + let post = Self::do_apply_authorize_upgrade(code)?; + Ok(post) + } } /// Event for the System pallet. @@ -645,6 +780,17 @@ pub mod pallet { KilledAccount { account: T::AccountId }, /// On on-chain remark happened. Remarked { sender: T::AccountId, hash: T::Hash }, + #[cfg(feature = "experimental")] + /// A [`Task`] has started executing + TaskStarted { task: T::RuntimeTask }, + #[cfg(feature = "experimental")] + /// A [`Task`] has finished executing. + TaskCompleted { task: T::RuntimeTask }, + #[cfg(feature = "experimental")] + /// A [`Task`] failed during execution. + TaskFailed { task: T::RuntimeTask, err: DispatchError }, + /// An upgrade was authorized. + UpgradeAuthorized { code_hash: T::Hash, check_version: bool }, } /// Error for the System pallet @@ -666,6 +812,16 @@ pub mod pallet { NonZeroRefCount, /// The origin filter prevent the call to be dispatched. CallFiltered, + #[cfg(feature = "experimental")] + /// The specified [`Task`] is not valid. + InvalidTask, + #[cfg(feature = "experimental")] + /// The specified [`Task`] failed during execution. + FailedTask, + /// No upgrade authorized. + NothingAuthorized, + /// The submitted code is not authorized. + Unauthorized, } /// Exposed trait-generic origin type. @@ -781,6 +937,12 @@ pub mod pallet { #[pallet::whitelist_storage] pub(super) type ExecutionPhase = StorageValue<_, Phase>; + /// `Some` if a code upgrade has been authorized. + #[pallet::storage] + #[pallet::getter(fn authorized_upgrade)] + pub(super) type AuthorizedUpgrade = + StorageValue<_, CodeUpgradeAuthorization, OptionQuery>; + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig { @@ -800,6 +962,25 @@ pub mod pallet { sp_io::storage::set(well_known_keys::EXTRINSIC_INDEX, &0u32.encode()); } } + + #[pallet::validate_unsigned] + impl sp_runtime::traits::ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::apply_authorized_upgrade { ref code } = call { + if let Ok(hash) = Self::validate_authorized_upgrade(&code[..]) { + return Ok(ValidTransaction { + priority: 100, + requires: Vec::new(), + provides: vec![hash.as_ref().to_vec()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } + } + Err(InvalidTransaction::Call.into()) + } + } } pub type Key = Vec; @@ -1824,6 +2005,41 @@ impl Pallet { } } } + + /// To be called after any origin/privilege checks. Put the code upgrade authorization into + /// storage and emit an event. Infallible. + pub fn do_authorize_upgrade(code_hash: T::Hash, check_version: bool) { + AuthorizedUpgrade::::put(CodeUpgradeAuthorization { code_hash, check_version }); + Self::deposit_event(Event::UpgradeAuthorized { code_hash, check_version }); + } + + /// Apply an authorized upgrade, performing any validation checks, and remove the authorization. + /// Whether or not the code is set directly depends on the `OnSetCode` configuration of the + /// runtime. + pub fn do_apply_authorize_upgrade(code: Vec) -> Result { + Self::validate_authorized_upgrade(&code[..])?; + T::OnSetCode::set_code(code)?; + AuthorizedUpgrade::::kill(); + let post = PostDispatchInfo { + // consume the rest of the block to prevent further transactions + actual_weight: Some(T::BlockWeights::get().max_block), + // no fee for valid upgrade + pays_fee: Pays::No, + }; + Ok(post) + } + + /// Check that provided `code` can be upgraded to. Namely, check that its hash matches an + /// existing authorization and that it meets the specification requirements of `can_set_code`. + pub fn validate_authorized_upgrade(code: &[u8]) -> Result { + let authorization = AuthorizedUpgrade::::get().ok_or(Error::::NothingAuthorized)?; + let actual_hash = T::Hashing::hash(code); + ensure!(actual_hash == authorization.code_hash, Error::::Unauthorized); + if authorization.check_version { + Self::can_set_code(code)? + } + Ok(actual_hash) + } } /// Returns a 32 byte datum which is guaranteed to be universally unique. `entropy` is provided @@ -1879,6 +2095,11 @@ impl BlockNumberProvider for Pallet { fn current_block_number() -> Self::BlockNumber { Pallet::::block_number() } + + #[cfg(feature = "runtime-benchmarks")] + fn set_block_number(n: BlockNumberFor) { + Self::set_block_number(n) + } } /// Implement StoredMap for a simple single-item, provide-when-not-default system. This works fine diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs index c016ea9e1cd14969e5dac8d626687ee4a0553acf..e33ac2f56c875b3ef513dd947b717e4ab57e8ce6 100644 --- a/substrate/frame/system/src/mock.rs +++ b/substrate/frame/system/src/mock.rs @@ -17,7 +17,7 @@ use crate::{self as frame_system, *}; use frame_support::{ - parameter_types, + derive_impl, parameter_types, traits::{ConstU32, ConstU64}, }; use sp_core::H256; @@ -85,6 +85,7 @@ 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; diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 6fbddaaf229402d9a2457e089d9b9d2059c440bb..e437e7f9f39b0845d9f7eef33e3c90546c8dbbd2 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -675,6 +675,46 @@ fn set_code_with_real_wasm_blob() { }); } +#[test] +fn set_code_via_authorization_works() { + let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); + let mut ext = new_test_ext(); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor)); + ext.execute_with(|| { + System::set_block_number(1); + assert!(System::authorized_upgrade().is_none()); + + let runtime = substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec(); + let hash = ::Hashing::hash(&runtime); + + // Can't apply before authorization + assert_noop!( + System::apply_authorized_upgrade(RawOrigin::None.into(), runtime.clone()), + Error::::NothingAuthorized, + ); + + // Can authorize + assert_ok!(System::authorize_upgrade(RawOrigin::Root.into(), hash)); + System::assert_has_event( + SysEvent::UpgradeAuthorized { code_hash: hash, check_version: true }.into(), + ); + assert!(System::authorized_upgrade().is_some()); + + // Can't be sneaky + let mut bad_runtime = substrate_test_runtime_client::runtime::wasm_binary_unwrap().to_vec(); + bad_runtime.extend(b"sneaky"); + assert_noop!( + System::apply_authorized_upgrade(RawOrigin::None.into(), bad_runtime), + Error::::Unauthorized, + ); + + // Can apply correct runtime + assert_ok!(System::apply_authorized_upgrade(RawOrigin::None.into(), runtime)); + System::assert_has_event(SysEvent::CodeUpdated.into()); + assert!(System::authorized_upgrade().is_none()); + }); +} + #[test] fn runtime_upgraded_with_set_storage() { let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); @@ -788,7 +828,7 @@ fn last_runtime_upgrade_spec_version_usage() { // a runtime upgrade in the pipeline of being applied, you should use the spec version // of this upgrade. if System::last_runtime_upgrade_spec_version() > 1337 { - return Weight::zero(); + return Weight::zero() } // Do the migration. diff --git a/substrate/frame/system/src/weights.rs b/substrate/frame/system/src/weights.rs index b79db3654b9f7172eab2a520151e0ab470dc7527..41807dea1c55f9cd246db231fc9102d4b9eb5c4f 100644 --- a/substrate/frame/system/src/weights.rs +++ b/substrate/frame/system/src/weights.rs @@ -57,6 +57,8 @@ pub trait WeightInfo { fn set_storage(i: u32, ) -> Weight; fn kill_storage(i: u32, ) -> Weight; fn kill_prefix(p: u32, ) -> Weight; + fn authorize_upgrade() -> Weight; + fn apply_authorized_upgrade() -> Weight; } /// Weights for frame_system using the Substrate node and recommended hardware. @@ -149,6 +151,33 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_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) + 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)) + } } // For backwards compatibility and tests @@ -240,4 +269,31 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } + /// Storage: `System::AuthorizedUpgrade` (r:0 w:1) + /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) + fn authorize_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 33_027_000 picoseconds. + Weight::from_parts(33_027_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(RocksDbWeight::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) + 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(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(3)) + } } diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index fd14216bdb3497e7cee257eb43ccbb1877fe9ff4..bcf26d622b08066f233e1c688861309cb19b454a 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -10,6 +10,9 @@ description = "FRAME Timestamp Module" documentation = "https://docs.rs/pallet-timestamp" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index a86034d92f5fd1891ea643f939e3ea26e4743b02..2516e0e993d5d6614ff23ad73c94cb9eb25f9c82 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage tips" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,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 } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", features = ["derive"], optional = true } +serde = { version = "1.0.195", features = ["derive"], optional = 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/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index c431f7f82434f4724bf11b8f8fb9b8a32a0f405f..d52e8e11c82927d459bb8cdbc963f7046e288c7a 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage transaction payments" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,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.193", optional = true } +serde = { version = "1.0.195", optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } @@ -26,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.108" +serde_json = "1.0.111" pallet-balances = { path = "../balances" } [features] diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml index 7d15740e82492eaa7a9531ef07938d08c0f076f9..0bfe37a5267924b414aa840d0e6b1fca254c0113 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Pallet to manage transaction payments in assets by converting them to native assets." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs index 04a71e2ff4260bacade4d93420646b21b946a2c2..ed0ed56e6e074b49db7abf6d43ddb9305d7fb616 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs @@ -69,7 +69,6 @@ mod tests; mod payment; use frame_support::traits::tokens::AssetId; -use pallet_asset_conversion::MultiAssetIdConverter; pub use payment::*; /// Type aliases used for interaction with `OnChargeTransaction`. diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs index 081e8e53db2b7e256a2f14edccb8c916fdfcd62f..52ff3eb990575ff92f44738f3479728039c39bfd 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs @@ -16,7 +16,6 @@ use super::*; use crate as pallet_asset_conversion_tx_payment; -use codec; use frame_support::{ derive_impl, dispatch::DispatchClass, @@ -24,13 +23,19 @@ use frame_support::{ ord_parameter_types, pallet_prelude::*, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, Imbalance, OnUnbalanced}, + traits::{ + tokens::{ + fungible::{NativeFromLeft, NativeOrWithId, UnionOf}, + imbalance::ResolveAssetTo, + }, + AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, Imbalance, OnUnbalanced, + }, weights::{Weight, WeightToFee as WeightToFeeT}, PalletId, }; use frame_system as system; use frame_system::{EnsureRoot, EnsureSignedBy}; -use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter}; +use pallet_asset_conversion::{Ascending, Chain, WithFirstAsset}; use pallet_transaction_payment::CurrencyAdapter; use sp_core::H256; use sp_runtime::{ @@ -225,10 +230,9 @@ impl pallet_assets::Config for Runtime { parameter_types! { pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); - pub storage AllowMultiAssetPools: bool = false; - // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); pub const MaxSwapPathLength: u32 = 4; + pub const Native: NativeOrWithId = NativeOrWithId::Native; } ord_parameter_types! { @@ -237,28 +241,26 @@ ord_parameter_types! { impl pallet_asset_conversion::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type AssetBalance = ::Balance; - type AssetId = u32; + type Balance = Balance; + type HigherPrecisionBalance = u128; + type AssetKind = NativeOrWithId; + type Assets = UnionOf, AccountId>; + type PoolId = (Self::AssetKind, Self::AssetKind); + type PoolLocator = Chain< + WithFirstAsset>, + Ascending>, + >; type PoolAssetId = u32; - type Assets = Assets; type PoolAssets = PoolAssets; + type PoolSetupFee = ConstU64<100>; // should be more or equal to the existential deposit + type PoolSetupFeeAsset = Native; + type PoolSetupFeeTarget = ResolveAssetTo; type PalletId = AssetConversionPalletId; - type WeightInfo = (); type LPFee = ConstU32<3>; // means 0.3% - type PoolSetupFee = ConstU64<100>; // should be more or equal to the existential deposit - type PoolSetupFeeReceiver = AssetConversionOrigin; type LiquidityWithdrawalFee = LiquidityWithdrawalFee; - type AllowMultiAssetPools = AllowMultiAssetPools; type MaxSwapPathLength = MaxSwapPathLength; type MintMinLiquidity = ConstU64<100>; // 100 is good enough when the main currency has 12 decimals. - - type Balance = u64; - type HigherPrecisionBalance = u128; - - type MultiAssetId = NativeOrAssetId; - type MultiAssetIdConverter = NativeOrAssetIdConverter; - + type WeightInfo = (); pallet_asset_conversion::runtime_benchmarks_enabled! { type BenchmarkHelper = (); } @@ -267,5 +269,5 @@ impl pallet_asset_conversion::Config for Runtime { impl Config for Runtime { type RuntimeEvent = RuntimeEvent; type Fungibles = Assets; - type OnChargeAssetTransaction = AssetConversionAdapter; + type OnChargeAssetTransaction = AssetConversionAdapter; } diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs index 0d090211d035218b3bdea39fc31a58d9f7b68744..f2f2c57bb376d9b81d04f3e39a77d27c0652be20 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use pallet_asset_conversion::Swap; use sp_runtime::{ - traits::{DispatchInfoOf, PostDispatchInfoOf, Zero}, + traits::{DispatchInfoOf, Get, PostDispatchInfoOf, Zero}, transaction_validity::InvalidTransaction, Saturating, }; @@ -76,16 +76,17 @@ pub trait OnChargeAssetTransaction { /// Implements the asset transaction for a balance to asset converter (implementing [`Swap`]). /// /// The converter is given the complete fee in terms of the asset used for the transaction. -pub struct AssetConversionAdapter(PhantomData<(C, CON)>); +pub struct AssetConversionAdapter(PhantomData<(C, CON, N)>); /// Default implementation for a runtime instantiating this pallet, an asset to native swapper. -impl OnChargeAssetTransaction for AssetConversionAdapter +impl OnChargeAssetTransaction for AssetConversionAdapter where + N: Get, T: Config, C: Inspect<::AccountId>, - CON: Swap, - T::HigherPrecisionBalance: From> + TryInto>, - T::MultiAssetId: From>, + CON: Swap, AssetKind = T::AssetKind>, + BalanceOf: Into>, + T::AssetKind: From>, BalanceOf: IsType<::AccountId>>::Balance>, { type Balance = BalanceOf; @@ -116,23 +117,19 @@ where let asset_consumed = CON::swap_tokens_for_exact_tokens( who.clone(), - vec![asset_id.into(), T::MultiAssetIdConverter::get_native()], - T::HigherPrecisionBalance::from(native_asset_required), + vec![asset_id.into(), N::get()], + native_asset_required, None, who.clone(), true, ) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; - let asset_consumed = asset_consumed - .try_into() - .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; - ensure!(asset_consumed > Zero::zero(), InvalidTransaction::Payment); // charge the fee in native currency ::withdraw_fee(who, call, info, fee, tip) - .map(|r| (r, native_asset_required, asset_consumed)) + .map(|r| (r, native_asset_required, asset_consumed.into())) } /// Correct the fee and swap the refund back to asset. @@ -172,11 +169,10 @@ where match CON::swap_exact_tokens_for_tokens( who.clone(), // we already deposited the native to `who` vec![ - T::MultiAssetIdConverter::get_native(), // we provide the native - asset_id.into(), // we want asset_id back + N::get(), // we provide the native + asset_id.into(), // we want asset_id back ], - T::HigherPrecisionBalance::from(swap_back), /* amount of the native asset to - * convert to `asset_id` */ + swap_back, // amount of the native asset to convert to `asset_id` None, // no minimum amount back who.clone(), // we will refund to `who` false, // no need to keep alive diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 9e9b74a0ddb2e598863565bc2c8906825b7f5acb..62faed269d377cc1dc2b75091d79210401cd0a26 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -19,12 +19,14 @@ use frame_support::{ assert_ok, dispatch::{DispatchInfo, PostDispatchInfo}, pallet_prelude::*, - traits::{fungible::Inspect, fungibles::Mutate}, + traits::{ + fungible::{Inspect, NativeOrWithId}, + fungibles::{Inspect as FungiblesInspect, Mutate}, + }, weights::Weight, }; use frame_system as system; use mock::{ExtrinsicBaseWeight, *}; -use pallet_asset_conversion::NativeOrAssetId; use pallet_balances::Call as BalancesCall; use sp_runtime::{traits::StaticLookup, BuildStorage}; @@ -110,22 +112,32 @@ fn default_post_info() -> PostDispatchInfo { fn setup_lp(asset_id: u32, balance_factor: u64) { let lp_provider = 5; + let ed = Balances::minimum_balance(); + let ed_asset = Assets::minimum_balance(asset_id); assert_ok!(Balances::force_set_balance( RuntimeOrigin::root(), lp_provider, - 10_000 * balance_factor + 10_000 * balance_factor + ed, )); let lp_provider_account = ::Lookup::unlookup(lp_provider); - assert_ok!(Assets::mint_into(asset_id.into(), &lp_provider_account, 10_000 * balance_factor)); + assert_ok!(Assets::mint_into( + asset_id.into(), + &lp_provider_account, + 10_000 * balance_factor + ed_asset + )); - let token_1 = NativeOrAssetId::Native; - let token_2 = NativeOrAssetId::Asset(asset_id); - assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(lp_provider), token_1, token_2)); + let token_1 = NativeOrWithId::Native; + let token_2 = NativeOrWithId::WithId(asset_id); + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(lp_provider), + Box::new(token_1.clone()), + Box::new(token_2.clone()) + )); assert_ok!(AssetConversion::add_liquidity( RuntimeOrigin::signed(lp_provider), - token_1, - token_2, + Box::new(token_1), + Box::new(token_2), 1_000 * balance_factor, // 1 desired 10_000 * balance_factor, // 2 desired 1, // 1 min @@ -215,8 +227,8 @@ fn transaction_payment_in_asset_possible() { let fee_in_native = base_weight + tx_weight + len as u64; let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(asset_id), - NativeOrAssetId::Native, + NativeOrWithId::WithId(asset_id), + NativeOrWithId::Native, fee_in_native, true, ); @@ -324,8 +336,8 @@ fn transaction_payment_without_fee() { let len = 10; let fee_in_native = base_weight + weight + len as u64; let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(asset_id), - NativeOrAssetId::Native, + NativeOrWithId::WithId(asset_id), + NativeOrWithId::Native, fee_in_native, true, ); @@ -342,8 +354,8 @@ fn transaction_payment_without_fee() { assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); let refund = AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(asset_id), + NativeOrWithId::Native, + NativeOrWithId::WithId(asset_id), fee_in_native, true, ) @@ -399,8 +411,8 @@ fn asset_transaction_payment_with_tip_and_refund() { let len = 10; let fee_in_native = base_weight + weight + len as u64 + tip; let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(asset_id), - NativeOrAssetId::Native, + NativeOrWithId::WithId(asset_id), + NativeOrWithId::Native, fee_in_native, true, ); @@ -415,8 +427,8 @@ fn asset_transaction_payment_with_tip_and_refund() { let final_weight = 50; let expected_fee = fee_in_native - final_weight - tip; let expected_token_refund = AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(asset_id), + NativeOrWithId::Native, + NativeOrWithId::WithId(asset_id), fee_in_native - expected_fee - tip, true, ) @@ -480,8 +492,8 @@ fn payment_from_account_with_only_assets() { let fee_in_native = base_weight + weight + len as u64; let ed = Balances::minimum_balance(); let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(asset_id), - NativeOrAssetId::Native, + NativeOrWithId::WithId(asset_id), + NativeOrWithId::Native, fee_in_native + ed, true, ) @@ -496,8 +508,8 @@ fn payment_from_account_with_only_assets() { assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); let refund = AssetConversion::quote_price_exact_tokens_for_tokens( - NativeOrAssetId::Native, - NativeOrAssetId::Asset(asset_id), + NativeOrWithId::Native, + NativeOrWithId::WithId(asset_id), ed, true, ) @@ -572,8 +584,8 @@ fn converted_fee_is_never_zero_if_input_fee_is_not() { // validate even a small fee gets converted to asset. let fee_in_native = base_weight + weight + len as u64; let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens( - NativeOrAssetId::Asset(asset_id), - NativeOrAssetId::Native, + NativeOrWithId::WithId(asset_id), + NativeOrWithId::Native, fee_in_native, true, ) diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index 9a614316e53f2e1382eab1447be8358d38b6fc78..0cd0cea59e60bc87ce570b3e9b0819f7056e5556 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "pallet to manage transaction payments in assets" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -27,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.193", optional = true } +serde = { version = "1.0.195", optional = true } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" 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 048b7da63f6c3a869da4548fc1aa6ea0f56f48ac..5a574a944d82f2ba2c0936528749a946b3abbe65 100644 --- a/substrate/frame/transaction-payment/rpc/Cargo.toml +++ b/substrate/frame/transaction-payment/rpc/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "RPC interface for the transaction payment pallet." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/transaction-payment/rpc/runtime-api/Cargo.toml b/substrate/frame/transaction-payment/rpc/runtime-api/Cargo.toml index 17213392e1c4246f190b91666b0f5256dfa36fb3..e384fcef692e43217f3675c2304323db3d3ae13d 100644 --- a/substrate/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/substrate/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "RPC runtime API for transaction payment FRAME pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/transaction-payment/skip-feeless-payment/Cargo.toml b/substrate/frame/transaction-payment/skip-feeless-payment/Cargo.toml index 25a708d69de6ac2d08c2fcb04a6365a25ad18deb..0e3744626d3f7cbfafd4c8e9661c4ec92e3cab39 100644 --- a/substrate/frame/transaction-payment/skip-feeless-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/skip-feeless-payment/Cargo.toml @@ -7,6 +7,9 @@ license.workspace = true repository.workspace = true description = "Pallet to skip payments for calls annotated with `feeless_if` if the respective conditions are satisfied." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index a2df1a3ce2a07370346687b1c4677709b715ac6f..26f9f64fe35d4956378976ca355d149f0cae6685 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Storage chain pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,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.193", optional = true } +serde = { version = "1.0.195", optional = 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/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 298c52d0e4801f5360144748f8941e1389e9d608..01eb7c55a0a674e23d9b822079d221660b8ac866 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet to manage treasury" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = docify = "0.2.0" impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", features = ["derive"], optional = true } +serde = { version = "1.0.195", features = ["derive"], optional = 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/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index 61fe29dafcae53b9b572243ca6b28da1c5a46bf0..0b9999e37fbea566e7ec74f61357104b9b5d6376 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -78,8 +78,7 @@ fn create_approved_proposals, I: 'static>(n: u32) -> Result<(), &'s #[allow(deprecated)] Treasury::::propose_spend(RawOrigin::Signed(caller).into(), value, lookup)?; let proposal_id = >::get() - 1; - #[allow(deprecated)] - Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; + Approvals::::try_append(proposal_id).unwrap(); } ensure!(>::get().len() == n as usize, "Not all approved"); Ok(()) @@ -163,6 +162,8 @@ mod benchmarks { fn approve_proposal( p: Linear<0, { T::MaxApprovals::get() - 1 }>, ) -> Result<(), BenchmarkError> { + let approve_origin = + T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; create_approved_proposals::(p)?; let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); #[allow(deprecated)] @@ -172,8 +173,6 @@ mod benchmarks { beneficiary_lookup, )?; let proposal_id = Treasury::::proposal_count() - 1; - let approve_origin = - T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(approve_origin as T::RuntimeOrigin, proposal_id); @@ -191,8 +190,7 @@ mod benchmarks { beneficiary_lookup, )?; let proposal_id = Treasury::::proposal_count() - 1; - #[allow(deprecated)] - Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; + Approvals::::try_append(proposal_id).unwrap(); let reject_origin = T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; diff --git a/substrate/frame/try-runtime/Cargo.toml b/substrate/frame/try-runtime/Cargo.toml index 4dd51647174e3d56a9d9a2c8760d4c7e798a58f3..1d036e004476a78361ee2ecc98d5b8f44b2c55f9 100644 --- a/substrate/frame/try-runtime/Cargo.toml +++ b/substrate/frame/try-runtime/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet for democracy" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/tx-pause/Cargo.toml b/substrate/frame/tx-pause/Cargo.toml index 60623bb9d389270ae243a92acb717d53a23cb98c..5958dcc2c30dbf27c6a1ff393cbe18a8350b39ae 100644 --- a/substrate/frame/tx-pause/Cargo.toml +++ b/substrate/frame/tx-pause/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME transaction pause pallet" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index 300b319ede0fa92a232f680d633e0c12c5764c7c..218b4ffe4c054e48f5ee91b840898966eead67f2 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME NFT asset management pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/uniques/src/benchmarking.rs b/substrate/frame/uniques/src/benchmarking.rs index 821ca1794b865483d8dc3edbb7298fa15a2dd31c..80d02f1362189d34491975918b5d14ce28bc3ab0 100644 --- a/substrate/frame/uniques/src/benchmarking.rs +++ b/substrate/frame/uniques/src/benchmarking.rs @@ -431,9 +431,9 @@ benchmarks_instance_pallet! { let buyer_lookup = T::Lookup::unlookup(buyer.clone()); let price = ItemPrice::::from(0u32); let origin = SystemOrigin::Signed(seller.clone()).into(); - Uniques::::set_price(origin, collection.clone(), item, Some(price.clone()), Some(buyer_lookup))?; + Uniques::::set_price(origin, collection.clone(), item, Some(price), Some(buyer_lookup))?; T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(buyer.clone()), collection.clone(), item, price.clone()) + }: _(SystemOrigin::Signed(buyer.clone()), collection.clone(), item, price) verify { assert_last_event::(Event::ItemBought { collection: collection.clone(), diff --git a/substrate/frame/uniques/src/lib.rs b/substrate/frame/uniques/src/lib.rs index 8334a8d943e19a6aea1e6973955c6a4fc44a514f..f7cc6b044d7298704d528d9cea5de7c102cf1f24 100644 --- a/substrate/frame/uniques/src/lib.rs +++ b/substrate/frame/uniques/src/lib.rs @@ -165,7 +165,7 @@ pub mod pallet { #[pallet::storage] #[pallet::storage_prefix = "Class"] /// Details of a collection. - pub(super) type Collection, I: 'static = ()> = StorageMap< + pub type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::CollectionId, @@ -174,7 +174,7 @@ pub mod pallet { #[pallet::storage] /// The collection, if any, of which an account is willing to take ownership. - pub(super) type OwnershipAcceptance, I: 'static = ()> = + pub type OwnershipAcceptance, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; #[pallet::storage] @@ -208,7 +208,7 @@ pub mod pallet { #[pallet::storage] #[pallet::storage_prefix = "Asset"] /// The items in existence and their ownership details. - pub(super) type Item, I: 'static = ()> = StorageDoubleMap< + pub type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -257,7 +257,7 @@ pub mod pallet { #[pallet::storage] /// Price of an asset instance. - pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + pub type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -856,34 +856,37 @@ pub mod pallet { pub fn transfer_ownership( origin: OriginFor, collection: T::CollectionId, - owner: AccountIdLookupOf, + new_owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; - let owner = T::Lookup::lookup(owner)?; + let new_owner = T::Lookup::lookup(new_owner)?; - let acceptable_collection = OwnershipAcceptance::::get(&owner); + let acceptable_collection = OwnershipAcceptance::::get(&new_owner); ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); - if details.owner == owner { + if details.owner == new_owner { return Ok(()) } // Move the deposit to the new owner. T::Currency::repatriate_reserved( &details.owner, - &owner, + &new_owner, details.total_deposit, Reserved, )?; + CollectionAccount::::remove(&details.owner, &collection); - CollectionAccount::::insert(&owner, &collection, ()); - details.owner = owner.clone(); - OwnershipAcceptance::::remove(&owner); + CollectionAccount::::insert(&new_owner, &collection, ()); + + details.owner = new_owner.clone(); + OwnershipAcceptance::::remove(&new_owner); + frame_system::Pallet::::dec_consumers(&new_owner); - Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); + Self::deposit_event(Event::OwnerChanged { collection, new_owner }); Ok(()) }) } @@ -1430,8 +1433,8 @@ pub mod pallet { maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; - let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_collection.is_some()) { + let exists = OwnershipAcceptance::::contains_key(&who); + match (exists, maybe_collection.is_some()) { (false, true) => { frame_system::Pallet::::inc_consumers(&who)?; }, diff --git a/substrate/frame/uniques/src/migration.rs b/substrate/frame/uniques/src/migration.rs index 6c92b753b4ac2ab29a0c0fdaeab10ba69e37eb7d..6b2bbf375e7541d58a30af413b6aad288d77e655 100644 --- a/substrate/frame/uniques/src/migration.rs +++ b/substrate/frame/uniques/src/migration.rs @@ -17,38 +17,39 @@ //! Various pieces of common functionality. use super::*; -use frame_support::traits::{Get, GetStorageVersion, PalletInfoAccess, StorageVersion}; - -/// Migrate the pallet storage to v1. -pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfoAccess>( -) -> frame_support::weights::Weight { - let on_chain_storage_version =

::on_chain_storage_version(); - log::info!( - target: LOG_TARGET, - "Running migration storage v1 for uniques with storage version {:?}", - on_chain_storage_version, - ); - - if on_chain_storage_version < 1 { - let mut count = 0; - for (collection, detail) in Collection::::iter() { - CollectionAccount::::insert(&detail.owner, &collection, ()); - count += 1; +use frame_support::traits::{Get, OnRuntimeUpgrade}; +use sp_std::marker::PhantomData; + +mod v1 { + use super::*; + + /// Actual implementation of the storage migration. + pub struct MigrateToV1Impl(PhantomData<(T, I)>); + + impl, I: 'static> OnRuntimeUpgrade for MigrateToV1Impl { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let mut count = 0; + for (collection, detail) in Collection::::iter() { + CollectionAccount::::insert(&detail.owner, &collection, ()); + count += 1; + } + + log::info!( + target: LOG_TARGET, + "Storage migration v1 for uniques finished.", + ); + + // calculate and return migration weights + T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) } - StorageVersion::new(1).put::

(); - log::info!( - target: LOG_TARGET, - "Running migration storage v1 for uniques with storage version {:?} was complete", - on_chain_storage_version, - ); - // calculate and return migration weights - T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1) - } else { - log::warn!( - target: LOG_TARGET, - "Attempted to apply migration to v1 but failed because storage version is {:?}", - on_chain_storage_version, - ); - T::DbWeight::get().reads(1) } } + +/// Migrate the pallet storage from `0` to `1`. +pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< + 0, + 1, + v1::MigrateToV1Impl, + Pallet, + ::DbWeight, +>; diff --git a/substrate/frame/uniques/src/tests.rs b/substrate/frame/uniques/src/tests.rs index 52f7df3b5efbc29796a77591ac44efd32429796a..351dac09f7f202a62df508fe71d26bb24662d5bc 100644 --- a/substrate/frame/uniques/src/tests.rs +++ b/substrate/frame/uniques/src/tests.rs @@ -254,8 +254,11 @@ fn transfer_owner_should_work() { Uniques::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), Error::::Unaccepted ); + assert_eq!(System::consumers(&2), 0); assert_ok!(Uniques::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + assert_eq!(System::consumers(&2), 1); assert_ok!(Uniques::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(System::consumers(&2), 1); assert_eq!(collections(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); diff --git a/substrate/frame/utility/Cargo.toml b/substrate/frame/utility/Cargo.toml index 73c25a60daf42e64319a70685222da48805e701d..4aa75f9f616570966375a71f3bd6999e818f5617 100644 --- a/substrate/frame/utility/Cargo.toml +++ b/substrate/frame/utility/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME utilities pallet" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/vesting/Cargo.toml b/substrate/frame/vesting/Cargo.toml index 37339d87aeadbb7a6d27f7451104d2e060b1bea6..3b5252d61810644e42913bb00dae47db96b61c0a 100644 --- a/substrate/frame/vesting/Cargo.toml +++ b/substrate/frame/vesting/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME pallet for manage vesting" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/frame/vesting/src/benchmarking.rs b/substrate/frame/vesting/src/benchmarking.rs index 34aa04607add1a9afad621f4c8fd89dc18876436..311590873d95f84f36d25b907f9fcac8c399106c 100644 --- a/substrate/frame/vesting/src/benchmarking.rs +++ b/substrate/frame/vesting/src/benchmarking.rs @@ -55,7 +55,7 @@ fn add_vesting_schedules( let source_lookup = T::Lookup::unlookup(source.clone()); T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); - System::::set_block_number(BlockNumberFor::::zero()); + T::BlockNumberProvider::set_block_number(BlockNumberFor::::zero()); let mut total_locked: BalanceOf = Zero::zero(); for _ in 0..n { @@ -116,7 +116,7 @@ benchmarks! { add_vesting_schedules::(caller_lookup, s)?; // At block 21, everything is unlocked. - System::::set_block_number(21u32.into()); + T::BlockNumberProvider::set_block_number(21u32.into()); assert_eq!( Vesting::::vesting_balance(&caller), Some(BalanceOf::::zero()), @@ -173,7 +173,7 @@ benchmarks! { add_locks::(&other, l as u8); add_vesting_schedules::(other_lookup.clone(), s)?; // At block 21 everything is unlocked. - System::::set_block_number(21u32.into()); + T::BlockNumberProvider::set_block_number(21u32.into()); assert_eq!( Vesting::::vesting_balance(&other), @@ -335,7 +335,7 @@ benchmarks! { let total_transferred = add_vesting_schedules::(caller_lookup, s)?; // Go to about half way through all the schedules duration. (They all start at 1, and have a duration of 20 or 21). - System::::set_block_number(11u32.into()); + T::BlockNumberProvider::set_block_number(11u32.into()); // We expect half the original locked balance (+ any remainder that vests on the last block). let expected_balance = total_transferred / 2u32.into(); assert_eq!( diff --git a/substrate/frame/vesting/src/lib.rs b/substrate/frame/vesting/src/lib.rs index dbad7926a30f56a3ec3d565320ea9ea4b1c77561..4101caded4180b25c9dbdc8ecfda295900ddc0cf 100644 --- a/substrate/frame/vesting/src/lib.rs +++ b/substrate/frame/vesting/src/lib.rs @@ -71,8 +71,8 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AtLeast32BitUnsigned, Bounded, Convert, MaybeSerializeDeserialize, One, Saturating, - StaticLookup, Zero, + AtLeast32BitUnsigned, BlockNumberProvider, Bounded, Convert, MaybeSerializeDeserialize, + One, Saturating, StaticLookup, Zero, }, DispatchError, RuntimeDebug, }; @@ -176,6 +176,9 @@ pub mod pallet { /// the unvested amount. type UnvestedFundsAllowedWithdrawReasons: Get; + /// Provider for the block number. + type BlockNumberProvider: BlockNumberProvider>; + /// Maximum number of vesting schedules an account may have at a given moment. const MAX_VESTING_SCHEDULES: u32; } @@ -565,7 +568,7 @@ impl Pallet { schedules: Vec, BlockNumberFor>>, action: VestingAction, ) -> (Vec, BlockNumberFor>>, BalanceOf) { - let now = >::block_number(); + let now = T::BlockNumberProvider::current_block_number(); let mut total_locked_now: BalanceOf = Zero::zero(); let filtered_schedules = action @@ -649,7 +652,7 @@ impl Pallet { let (mut schedules, mut locked_now) = Self::report_schedule_updates(schedules.to_vec(), action); - let now = >::block_number(); + let now = T::BlockNumberProvider::current_block_number(); if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) { // Merging created a new schedule so we: // 1) need to add it to the accounts vesting schedule collection, @@ -685,7 +688,7 @@ where /// Get the amount that is currently being vested and cannot be transferred out of this account. fn vesting_balance(who: &T::AccountId) -> Option> { if let Some(v) = Self::vesting(who) { - let now = >::block_number(); + let now = T::BlockNumberProvider::current_block_number(); let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| { schedule.locked_at::(now).saturating_add(total) }); diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index 67444b8d12581086106fc0b3ada90e2abd948cc7..3af4a9c962d1b12c82586fd3e6f015950331f27e 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -96,6 +96,7 @@ impl Config for Test { type MinVestedTransfer = MinVestedTransfer; type WeightInfo = (); type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; } pub struct ExtBuilder { diff --git a/substrate/frame/whitelist/Cargo.toml b/substrate/frame/whitelist/Cargo.toml index 0d7ab6c2967aac1388d9646492a06407bd0086f8..5d9a362f9aacb799cbbf3bd291c01a68f40bbd9e 100644 --- a/substrate/frame/whitelist/Cargo.toml +++ b/substrate/frame/whitelist/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "FRAME pallet for whitelisting call, and dispatch from specific origin" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index 1be131a7b4fbdb1d5f24b7a33feb00dcaa85ec20..345647cec25df55c864215f64aaf3e8459d1e058 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate runtime api primitives" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index 46d05c78b4aaca418bdeaec3e10beaae0822b2be..eff66d3fb29dbaa747e44bc9cd46a6706e1a02c4 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Macros for declaring and implementing runtime apis." documentation = "https://docs.rs/sp-api-proc-macro" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,10 +20,10 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = { version = "2.0.39", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } proc-macro2 = "1.0.56" blake2 = { version = "0.10.4", default-features = false } -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" expander = "2.0.0" Inflector = "0.11.4" diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 68f0a77a3995d9f12f1f4b366cd8e3395ef4b874..c8c1f12d90a1645e1bd3c7df6f91a873c5d9288f 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -19,7 +19,7 @@ use crate::common::API_VERSION_ATTRIBUTE; use inflector::Inflector; use proc_macro2::{Span, TokenStream}; use proc_macro_crate::{crate_name, FoundCrate}; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote}; use syn::{ parse_quote, spanned::Spanned, token::And, Attribute, Error, FnArg, GenericArgument, Ident, ImplItem, ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, @@ -261,6 +261,7 @@ pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { /// Extract the documentation from the provided attributes. #[cfg(feature = "frame-metadata")] pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { + use quote::ToTokens; attrs .iter() .filter_map(|attr| { diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index f207f5ff02dd0127a2cf072af4305ff0f2f8516d..0346ad270ab03f9040d55dc4621ec47a58659398 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -8,6 +8,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 4bd64c974f29ced75ff54eb002711d728a64f258..788d1807f3ba2791c4593ebc7559cdf6d44078ca 100644 --- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -1,3 +1,21 @@ +error[E0603]: struct `RuntimeVersion` is private + --> tests/ui/impl_incorrect_method_signature.rs:37:27 + | +37 | fn version() -> sp_api::RuntimeVersion { + | ^^^^^^^^^^^^^^ private struct + | +note: the struct `RuntimeVersion` is defined here + --> $WORKSPACE/substrate/primitives/api/src/lib.rs + | + | use sp_version::RuntimeVersion; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider importing one of these items instead + | +37 | fn version() -> sp_api::__private::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +37 | fn version() -> sp_version::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + error[E0053]: method `test` has an incompatible type for trait --> tests/ui/impl_incorrect_method_signature.rs:33:17 | diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index 4c21a3afb9b6a2d13a7b3a0091dec785608c5a69..b4df7c068768c9236e98b44c1c2fa878311942af 100644 --- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -1,3 +1,21 @@ +error[E0603]: struct `RuntimeVersion` is private + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:39:27 + | +39 | fn version() -> sp_api::RuntimeVersion { + | ^^^^^^^^^^^^^^ private struct + | +note: the struct `RuntimeVersion` is defined here + --> $WORKSPACE/substrate/primitives/api/src/lib.rs + | + | use sp_version::RuntimeVersion; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider importing one of these items instead + | +39 | fn version() -> sp_api::__private::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +39 | fn version() -> sp_version::RuntimeVersion { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + error[E0053]: method `test` has an incompatible type for trait --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:33:17 | diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index a6c937a3469e9109932d39a7f5fdd80fa98f1f02..d8aa2689aa271d4b9764aa55997ea7e2978d53c3 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true documentation = "https://docs.rs/sp-application-crypto" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,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.193", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } sp-std = { path = "../std", default-features = false } sp-io = { path = "../io", default-features = false } diff --git a/substrate/primitives/application-crypto/test/Cargo.toml b/substrate/primitives/application-crypto/test/Cargo.toml index d9fb743e8cd74cadb43540eaad419ffde421150e..0057606b38e57112e2988d96dbaa342059616ee0 100644 --- a/substrate/primitives/application-crypto/test/Cargo.toml +++ b/substrate/primitives/application-crypto/test/Cargo.toml @@ -9,6 +9,9 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 8634dabe854f30874601dcd040b64bae5774d658..47d2902e267d03776566be3680fed8ed6b36cfa5 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -10,6 +10,9 @@ description = "Minimal fixed point arithmetic primitives and types for runtime." documentation = "https://docs.rs/sp-arithmetic" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,9 +22,9 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } integer-sqrt = "0.1.2" -num-traits = { version = "0.2.8", default-features = false } +num-traits = { version = "0.2.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } static_assertions = "1.1.0" sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/arithmetic/fuzzer/Cargo.toml b/substrate/primitives/arithmetic/fuzzer/Cargo.toml index eded5a954c5a61773a3aa3a3416ffb1355d79a89..b881e8d46dbdcc4576bc685e03bbea23a2d7c00c 100644 --- a/substrate/primitives/arithmetic/fuzzer/Cargo.toml +++ b/substrate/primitives/arithmetic/fuzzer/Cargo.toml @@ -10,6 +10,9 @@ description = "Fuzzer for fixed point arithmetic primitives." documentation = "https://docs.rs/sp-arithmetic-fuzzer" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/authority-discovery/Cargo.toml b/substrate/primitives/authority-discovery/Cargo.toml index c8a93980be280fb2da57d4f1ceaca28956c6817d..82ec5a3eb9a492eb06b146f9c6e89ff83b086ff0 100644 --- a/substrate/primitives/authority-discovery/Cargo.toml +++ b/substrate/primitives/authority-discovery/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/block-builder/Cargo.toml b/substrate/primitives/block-builder/Cargo.toml index a574689811be2c6c0201d86ee999a242af100eea..de1ffd9d9e64a4708370db0f5d1d44eee56d5870 100644 --- a/substrate/primitives/block-builder/Cargo.toml +++ b/substrate/primitives/block-builder/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "The block builder runtime api." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 33db09ce0ac2f41897957a43b8b74497b90e93a1..38b3b2030dc62a77fd7060d2fc241df8a305be71 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -10,6 +10,9 @@ description = "Substrate blockchain traits and primitives." documentation = "https://docs.rs/sp-blockchain" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/consensus/aura/Cargo.toml b/substrate/primitives/consensus/aura/Cargo.toml index 4a19999a469a33188f62257ae18246bb055b6d63..6c797e15ae805e4d257b9fac1b057bfe8842b1e3 100644 --- a/substrate/primitives/consensus/aura/Cargo.toml +++ b/substrate/primitives/consensus/aura/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { version = "0.1.57", optional = true } +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"] } sp-api = { path = "../../api", default-features = false } diff --git a/substrate/primitives/consensus/babe/Cargo.toml b/substrate/primitives/consensus/babe/Cargo.toml index 6ec50ea022b764738946006d0817933b12470736..e48b4b4817b71db8e65ba29f429b8bf8e30ec913 100644 --- a/substrate/primitives/consensus/babe/Cargo.toml +++ b/substrate/primitives/consensus/babe/Cargo.toml @@ -9,14 +9,17 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { version = "0.1.57", optional = true } +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.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = 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/lib.rs b/substrate/primitives/consensus/babe/src/lib.rs index c083bfd9a313e078e4f82de0a9bb00fc025bb93d..d6b2cdd55e0daddf7e49a132d48bbc8c1741736a 100644 --- a/substrate/primitives/consensus/babe/src/lib.rs +++ b/substrate/primitives/consensus/babe/src/lib.rs @@ -33,7 +33,7 @@ use sp_std::vec::Vec; use crate::digests::{NextConfigDescriptor, NextEpochDescriptor}; pub use sp_core::sr25519::vrf::{ - VrfInput, VrfOutput, VrfProof, VrfSignData, VrfSignature, VrfTranscript, + VrfInput, VrfPreOutput, VrfProof, VrfSignData, VrfSignature, VrfTranscript, }; /// Key type for BABE module. diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 916125d783d97455a0db78a78793982ce65f08dc..93a9be17747cce6af5dd8e4c5e963e81dce559ca 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -8,13 +8,16 @@ homepage = "https://substrate.io" repository.workspace = true description = "Primitives for BEEFY protocol." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } 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/beefy/src/mmr.rs b/substrate/primitives/consensus/beefy/src/mmr.rs index 9ac1624ca752c055b56522cfd92cd0240a1a52a6..1b9a45f86878fa6d35fa0d150146e6c94c7154fe 100644 --- a/substrate/primitives/consensus/beefy/src/mmr.rs +++ b/substrate/primitives/consensus/beefy/src/mmr.rs @@ -66,7 +66,7 @@ pub struct MmrLeaf { /// An MMR leaf versioning scheme. /// -/// Version is a single byte that constist of two components: +/// Version is a single byte that consists of two components: /// - `major` - 3 bits /// - `minor` - 5 bits /// diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index e8f6b806f8c6403296e5cd95907b3a74c4e097f2..afb7a9895fcdc8b684fcfab2eb40d3eb802ea10f 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -10,11 +10,14 @@ description = "Common utilities for building and using consensus engines in subs documentation = "https://docs.rs/sp-consensus/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" +async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } log = "0.4.17" thiserror = "1.0.48" diff --git a/substrate/primitives/consensus/grandpa/Cargo.toml b/substrate/primitives/consensus/grandpa/Cargo.toml index 1ddc89df9836de3fd5368b9ddff946df766fe205..be22f5b23df31d48aab90353608d9b607d7451b0 100644 --- a/substrate/primitives/consensus/grandpa/Cargo.toml +++ b/substrate/primitives/consensus/grandpa/Cargo.toml @@ -10,6 +10,9 @@ description = "Primitives for GRANDPA integration, suitable for WASM compilation documentation = "https://docs.rs/sp-consensus-grandpa" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = 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/pow/Cargo.toml b/substrate/primitives/consensus/pow/Cargo.toml index 5e134eb2a29aefb3bbb0f530090c3306688ab5f8..e528d8365ced3721001381f881ad8df1aa2c33c1 100644 --- a/substrate/primitives/consensus/pow/Cargo.toml +++ b/substrate/primitives/consensus/pow/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index e71f82b4382f168b46f0d995831fb6bf87a8faa0..6d44bc6c5a8fe9b879962e1ddb102b5638d9dc1b 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -11,13 +11,16 @@ documentation = "https://docs.rs/sp-consensus-sassafras" readme = "README.md" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] 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.193", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = 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/sassafras/src/vrf.rs b/substrate/primitives/consensus/sassafras/src/vrf.rs index afcb1d09f8f6832f5b794543f1f2896366859a4e..5deacd8e9945bf9dbcc05a0664029e3a2c57c1c0 100644 --- a/substrate/primitives/consensus/sassafras/src/vrf.rs +++ b/substrate/primitives/consensus/sassafras/src/vrf.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Utilities related to VRF input, output and signatures. +//! Utilities related to VRF input, pre-output and signatures. use crate::{Randomness, TicketBody, TicketId}; use scale_codec::Encode; @@ -24,7 +24,7 @@ use sp_std::vec::Vec; pub use sp_core::bandersnatch::{ ring_vrf::{RingProver, RingVerifier, RingVerifierData, RingVrfSignature}, - vrf::{VrfInput, VrfOutput, VrfSignData, VrfSignature}, + vrf::{VrfInput, VrfPreOutput, VrfSignData, VrfSignature}, }; /// Ring VRF domain size for Sassafras consensus. @@ -90,21 +90,21 @@ pub fn ticket_body_sign_data(ticket_body: &TicketBody, ticket_id_input: VrfInput ) } -/// Make ticket-id from the given VRF input and output. +/// Make ticket-id from the given VRF input and pre-output. /// /// Input should have been obtained via [`ticket_id_input`]. -/// Output should have been obtained from the input directly using the vrf secret key -/// or from the vrf signature outputs. -pub fn make_ticket_id(input: &VrfInput, output: &VrfOutput) -> TicketId { - let bytes = output.make_bytes::<16>(b"ticket-id", input); +/// Pre-output should have been obtained from the input directly using the vrf +/// secret key or from the vrf signature pre-outputs. +pub fn make_ticket_id(input: &VrfInput, pre_output: &VrfPreOutput) -> TicketId { + let bytes = pre_output.make_bytes::<16>(b"ticket-id", input); u128::from_le_bytes(bytes) } -/// Make revealed key seed from a given VRF input and ouput. +/// Make revealed key seed from a given VRF input and pre-ouput. /// /// Input should have been obtained via [`revealed_key_input`]. -/// Output should have been obtained from the input directly using the vrf secret key -/// or from the vrf signature outputs. -pub fn make_revealed_key_seed(input: &VrfInput, output: &VrfOutput) -> [u8; 32] { - output.make_bytes::<32>(b"revealed-seed", input) +/// Pre-output should have been obtained from the input directly using the vrf +/// secret key or from the vrf signature pre-outputs. +pub fn make_revealed_key_seed(input: &VrfInput, pre_output: &VrfPreOutput) -> [u8; 32] { + pre_output.make_bytes::<32>(b"revealed-seed", input) } diff --git a/substrate/primitives/consensus/slots/Cargo.toml b/substrate/primitives/consensus/slots/Cargo.toml index 129405837574d132ee52e08e17b451ab350d5fe1..91bbd1663a9c3d867624acdfd0a17412a8d2a49c 100644 --- a/substrate/primitives/consensus/slots/Cargo.toml +++ b/substrate/primitives/consensus/slots/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index 331d762e0d7b8cc9c44e5de444db6f70488d6a10..73de8057d80baaab439772186cd574a5182a7786 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Shareable Substrate types." documentation = "https://docs.rs/sp-core" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] 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.193", optional = true, default-features = false, features = ["alloc", "derive"] } +serde = { version = "1.0.195", optional = true, default-features = false, features = ["alloc", "derive"] } bounded-collections = { version = "0.1.8", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", default-features = false, optional = true } @@ -47,8 +50,8 @@ array-bytes = { version = "6.1", optional = true } ed25519-zebra = { version = "3.1.0", default-features = false, optional = true } blake2 = { version = "0.10.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } -merlin = { version = "2.0", default-features = false } +schnorrkel = { version = "0.11.4", features = ["preaudit_deprecated"], default-features = false } +merlin = { version = "3.0", default-features = false } secp256k1 = { version = "0.28.0", default-features = false, features = ["alloc", "recovery"], optional = true } sp-core-hashing = { path = "hashing", default-features = false, optional = true } sp-runtime-interface = { path = "../runtime-interface", default-features = false } @@ -56,11 +59,11 @@ sp-runtime-interface = { path = "../runtime-interface", default-features = false # bls crypto w3f-bls = { version = "0.1.3", default-features = false, optional = true } # bandersnatch crypto -bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "2019248", default-features = false, features = ["substrate-curves"], optional = true } +bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "e9782f9", default-features = false, features = ["substrate-curves"], optional = true } [dev-dependencies] criterion = "0.4.0" -serde_json = "1.0.108" +serde_json = "1.0.111" lazy_static = "1.4.0" regex = "1.6.0" sp-core-hashing-proc-macro = { path = "hashing/proc-macro" } diff --git a/substrate/primitives/core/fuzz/Cargo.toml b/substrate/primitives/core/fuzz/Cargo.toml index 9a094b07d4a10ad8632ab656deb6e618e0f2d5e3..c6b5a065b6dca7a389e6409fe623018df3cfc083 100644 --- a/substrate/primitives/core/fuzz/Cargo.toml +++ b/substrate/primitives/core/fuzz/Cargo.toml @@ -3,6 +3,9 @@ name = "sp-core-fuzz" version = "0.0.0" publish = false +[lints] +workspace = true + [package.metadata] cargo-fuzz = true diff --git a/substrate/primitives/core/hashing/Cargo.toml b/substrate/primitives/core/hashing/Cargo.toml index 7b4f4bc7438819e19d2564212ac6f09c865f7d22..011d312ba90fce623a9d351e8e349b053b676595 100644 --- a/substrate/primitives/core/hashing/Cargo.toml +++ b/substrate/primitives/core/hashing/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Primitive core crate hashing implementation." documentation = "https://docs.rs/sp-core-hashing" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/core/hashing/proc-macro/Cargo.toml b/substrate/primitives/core/hashing/proc-macro/Cargo.toml index a5e5956e94ff6948190ad922ef6b7a073dd28f81..5c215bc7799347d5fd969a50d11054e273ea632a 100644 --- a/substrate/primitives/core/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/core/hashing/proc-macro/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "This crate provides procedural macros for calculating static hash." documentation = "https://docs.rs/sp-core-hashing-proc-macro" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,5 +20,5 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = { version = "2.0.39", features = ["full", "parsing"] } +syn = { version = "2.0.48", features = ["full", "parsing"] } sp-core-hashing = { path = "..", default-features = false } diff --git a/substrate/primitives/core/src/address_uri.rs b/substrate/primitives/core/src/address_uri.rs index 862747c9a4b69947905f384ab0be52e4b776c066..211d47c0093d8800646c9a47273128af18edbad5 100644 --- a/substrate/primitives/core/src/address_uri.rs +++ b/substrate/primitives/core/src/address_uri.rs @@ -184,7 +184,7 @@ impl<'a> AddressUri<'a> { Error::in_pass(initial_input, initial_input_len - input.len()) } else { Error::in_phrase(initial_input, initial_input_len - input.len()) - }); + }) } } diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index b91c001a7de03952dc87b16984d315852958a825..463d49fd88901cc3eb7cafaab25ff94c18c64d71 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -295,7 +295,7 @@ impl TraitPair for Pair { fn verify>(signature: &Signature, data: M, public: &Public) -> bool { let data = vrf::VrfSignData::new_unchecked(SIGNING_CTX, &[data.as_ref()], None); let signature = - vrf::VrfSignature { signature: *signature, outputs: vrf::VrfIosVec::default() }; + vrf::VrfSignature { signature: *signature, pre_outputs: vrf::VrfIosVec::default() }; public.vrf_verify(&data, &signature) } @@ -319,18 +319,18 @@ pub mod vrf { ThinVrfSignature, Transcript, }; - /// Max number of inputs/outputs which can be handled by the VRF signing procedures. + /// Max number of inputs/pre-outputs which can be handled by the VRF signing procedures. /// /// The number is quite arbitrary and chosen to fulfill the use cases found so far. /// If required it can be extended in the future. pub const MAX_VRF_IOS: u32 = 3; - /// Bounded vector used for VRF inputs and outputs. + /// Bounded vector used for VRF inputs and pre-outputs. /// /// Can contain at most [`MAX_VRF_IOS`] elements. pub type VrfIosVec = BoundedVec>; - /// VRF input to construct a [`VrfOutput`] instance and embeddable in [`VrfSignData`]. + /// VRF input to construct a [`VrfPreOutput`] instance and embeddable in [`VrfSignData`]. #[derive(Clone, Debug)] pub struct VrfInput(pub(super) bandersnatch_vrfs::VrfInput); @@ -342,15 +342,15 @@ pub mod vrf { } } - /// VRF (pre)output derived from [`VrfInput`] using a [`VrfSecret`]. + /// VRF pre-output derived from [`VrfInput`] using a [`VrfSecret`]. /// /// This object is used to produce an arbitrary number of verifiable pseudo random /// bytes and is often called pre-output to emphasize that this is not the actual /// output of the VRF but an object capable of generating the output. #[derive(Clone, Debug, PartialEq, Eq)] - pub struct VrfOutput(pub(super) bandersnatch_vrfs::VrfPreOut); + pub struct VrfPreOutput(pub(super) bandersnatch_vrfs::VrfPreOut); - impl Encode for VrfOutput { + impl Encode for VrfPreOutput { fn encode(&self) -> Vec { let mut bytes = [0; PREOUT_SERIALIZED_SIZE]; self.0 @@ -360,25 +360,25 @@ pub mod vrf { } } - impl Decode for VrfOutput { + impl Decode for VrfPreOutput { fn decode(i: &mut R) -> Result { let buf = <[u8; PREOUT_SERIALIZED_SIZE]>::decode(i)?; let preout = bandersnatch_vrfs::VrfPreOut::deserialize_compressed_unchecked(buf.as_slice()) .map_err(|_| "vrf-preout decode error: bad preout")?; - Ok(VrfOutput(preout)) + Ok(VrfPreOutput(preout)) } } - impl EncodeLike for VrfOutput {} + impl EncodeLike for VrfPreOutput {} - impl MaxEncodedLen for VrfOutput { + impl MaxEncodedLen for VrfPreOutput { fn max_encoded_len() -> usize { <[u8; PREOUT_SERIALIZED_SIZE]>::max_encoded_len() } } - impl TypeInfo for VrfOutput { + impl TypeInfo for VrfPreOutput { type Identity = [u8; PREOUT_SERIALIZED_SIZE]; fn type_info() -> scale_info::Type { @@ -395,14 +395,14 @@ pub mod vrf { /// A good explaination of the topic can be found in Merlin [docs](https://merlin.cool/) /// /// The `inputs` is a sequence of [`VrfInput`]s which, during the signing procedure, are - /// first transformed to [`VrfOutput`]s. Both inputs and outputs are then appended to + /// first transformed to [`VrfPreOutput`]s. Both inputs and pre-outputs are then appended to /// the transcript before signing the Fiat-Shamir transform result (the challenge). /// /// In practice, as a user, all these technical details can be easily ignored. /// What is important to remember is: /// - *Transcript* is an object defining the protocol and used to produce the signature. This - /// object doesn't influence the `VrfOutput`s values. - /// - *Vrf inputs* is some additional data which is used to produce *vrf outputs*. This data + /// object doesn't influence the `VrfPreOutput`s values. + /// - *Vrf inputs* is some additional data which is used to produce *vrf pre-outputs*. This data /// will contribute to the signature as well. #[derive(Clone)] pub struct VrfSignData { @@ -473,7 +473,7 @@ pub mod vrf { /// VRF signature. /// - /// Includes both the transcript `signature` and the `outputs` generated from the + /// Includes both the transcript `signature` and the `pre-outputs` generated from the /// [`VrfSignData::inputs`]. /// /// Refer to [`VrfSignData`] for more details. @@ -481,14 +481,14 @@ pub mod vrf { pub struct VrfSignature { /// Transcript signature. pub signature: Signature, - /// VRF (pre)outputs. - pub outputs: VrfIosVec, + /// VRF pre-outputs. + pub pre_outputs: VrfIosVec, } #[cfg(feature = "full_crypto")] impl VrfCrypto for Pair { type VrfInput = VrfInput; - type VrfOutput = VrfOutput; + type VrfPreOutput = VrfPreOutput; type VrfSignData = VrfSignData; type VrfSignature = VrfSignature; } @@ -507,15 +507,15 @@ pub mod vrf { } } - fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { - let output = self.secret.vrf_preout(&input.0); - VrfOutput(output) + fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput { + let pre_output = self.secret.vrf_preout(&input.0); + VrfPreOutput(pre_output) } } impl VrfCrypto for Public { type VrfInput = VrfInput; - type VrfOutput = VrfOutput; + type VrfPreOutput = VrfPreOutput; type VrfSignData = VrfSignData; type VrfSignature = VrfSignature; } @@ -523,12 +523,12 @@ pub mod vrf { impl VrfPublic for Public { fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - let outputs_len = signature.outputs.len(); - if outputs_len != data.inputs.len() { + let pre_outputs_len = signature.pre_outputs.len(); + if pre_outputs_len != data.inputs.len() { return false } // Workaround to overcome backend signature generic over the number of IOs. - match outputs_len { + match pre_outputs_len { 0 => self.vrf_verify_gen::<0>(data, signature), 1 => self.vrf_verify_gen::<1>(data, signature), 2 => self.vrf_verify_gen::<2>(data, signature), @@ -546,11 +546,12 @@ pub mod vrf { let thin_signature: ThinVrfSignature = self.secret.sign_thin_vrf(data.transcript.clone(), &ios); - let outputs: Vec<_> = thin_signature.preouts.into_iter().map(VrfOutput).collect(); - let outputs = VrfIosVec::truncate_from(outputs); + let pre_outputs: Vec<_> = + thin_signature.preouts.into_iter().map(VrfPreOutput).collect(); + let pre_outputs = VrfIosVec::truncate_from(pre_outputs); let mut signature = - VrfSignature { signature: Signature([0; SIGNATURE_SERIALIZED_SIZE]), outputs }; + VrfSignature { signature: Signature([0; SIGNATURE_SERIALIZED_SIZE]), pre_outputs }; thin_signature .proof @@ -583,7 +584,7 @@ pub mod vrf { }; let preouts: [bandersnatch_vrfs::VrfPreOut; N] = - core::array::from_fn(|i| signature.outputs[i].0); + core::array::from_fn(|i| signature.pre_outputs[i].0); // Deserialize only the proof, the rest has already been deserialized // This is another hack used because backend signature type is generic over @@ -602,7 +603,7 @@ pub mod vrf { } } - impl VrfOutput { + impl VrfPreOutput { /// Generate an arbitrary number of bytes from the given `context` and VRF `input`. pub fn make_bytes( &self, @@ -804,8 +805,8 @@ pub mod ring_vrf { pub struct RingVrfSignature { /// Ring signature. pub signature: [u8; RING_SIGNATURE_SERIALIZED_SIZE], - /// VRF (pre)outputs. - pub outputs: VrfIosVec, + /// VRF pre-outputs. + pub pre_outputs: VrfIosVec, } #[cfg(feature = "full_crypto")] @@ -838,11 +839,12 @@ pub mod ring_vrf { bandersnatch_vrfs::RingProver { ring_prover: prover, secret: &self.secret } .sign_ring_vrf(data.transcript.clone(), &ios); - let outputs: Vec<_> = ring_signature.preouts.into_iter().map(VrfOutput).collect(); - let outputs = VrfIosVec::truncate_from(outputs); + let pre_outputs: Vec<_> = + ring_signature.preouts.into_iter().map(VrfPreOutput).collect(); + let pre_outputs = VrfIosVec::truncate_from(pre_outputs); let mut signature = - RingVrfSignature { outputs, signature: [0; RING_SIGNATURE_SERIALIZED_SIZE] }; + RingVrfSignature { pre_outputs, signature: [0; RING_SIGNATURE_SERIALIZED_SIZE] }; ring_signature .proof @@ -860,7 +862,7 @@ pub mod ring_vrf { /// from which the [`RingVerifier`] has been constructed. pub fn ring_vrf_verify(&self, data: &VrfSignData, verifier: &RingVerifier) -> bool { const _: () = assert!(MAX_VRF_IOS == 3, "`MAX_VRF_IOS` expected to be 3"); - let preouts_len = self.outputs.len(); + let preouts_len = self.pre_outputs.len(); if preouts_len != data.inputs.len() { return false } @@ -888,7 +890,7 @@ pub mod ring_vrf { }; let preouts: [bandersnatch_vrfs::VrfPreOut; N] = - core::array::from_fn(|i| self.outputs[i].0); + core::array::from_fn(|i| self.pre_outputs[i].0); let signature = bandersnatch_vrfs::RingVrfSignature { proof: vrf_signature.proof, preouts }; @@ -1038,11 +1040,11 @@ mod tests { let signature = pair.vrf_sign(&data); let o10 = pair.make_bytes::<32>(b"ctx1", &i1); - let o11 = signature.outputs[0].make_bytes::<32>(b"ctx1", &i1); + let o11 = signature.pre_outputs[0].make_bytes::<32>(b"ctx1", &i1); assert_eq!(o10, o11); let o20 = pair.make_bytes::<48>(b"ctx2", &i2); - let o21 = signature.outputs[1].make_bytes::<48>(b"ctx2", &i2); + let o21 = signature.pre_outputs[1].make_bytes::<48>(b"ctx2", &i2); assert_eq!(o20, o21); } @@ -1142,11 +1144,11 @@ mod tests { let signature = pair.ring_vrf_sign(&data, &prover); let o10 = pair.make_bytes::<32>(b"ctx1", &i1); - let o11 = signature.outputs[0].make_bytes::<32>(b"ctx1", &i1); + let o11 = signature.pre_outputs[0].make_bytes::<32>(b"ctx1", &i1); assert_eq!(o10, o11); let o20 = pair.make_bytes::<48>(b"ctx2", &i2); - let o21 = signature.outputs[1].make_bytes::<48>(b"ctx2", &i2); + let o21 = signature.pre_outputs[1].make_bytes::<48>(b"ctx2", &i2); assert_eq!(o20, o21); } diff --git a/substrate/primitives/core/src/const_hex2array.rs b/substrate/primitives/core/src/const_hex2array.rs new file mode 100644 index 0000000000000000000000000000000000000000..cd6071028e6cb7c1dedf29a708c603cf0bf6b8e2 --- /dev/null +++ b/substrate/primitives/core/src/const_hex2array.rs @@ -0,0 +1,162 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides a const function for converting a hex string to a `u8` array at compile time, when used +//! in the proper context. + +/// Provides a const array from given string literal. +/// +/// Valid characters are `[0-9a-fA-F]`, and the hex string should not start +/// with the `0x` prefix. +#[macro_export] +macro_rules! hex2array { + ($input:expr) => {{ + const BYTES: [u8; $input.len() / 2] = $crate::const_hex2array::private_hex2array($input); + BYTES + }}; +} + +/// Generates array from (static) string literal. +/// +/// Valid characters are `[0-9a-fA-F]`, and the hex string should not start +/// with the `0x` prefix. +/// +/// # Panics +/// +/// The function will panic at compile time when used in a const context if: +/// - The given hex string has an invalid length. +/// - It contains invalid characters. +/// +/// The function will panic at runtime when used in a non-const context if the above conditions are +/// met. +#[doc(hidden)] +pub const fn private_hex2array(hex: &str) -> [u8; N] { + const fn c2b(c: u8) -> u8 { + match c as char { + '0'..='9' => c - b'0', + 'a'..='f' => c - (b'a' - 10), + 'A'..='F' => c - (b'A' - 10), + _ => panic!("hex string contains invalid character"), + } + } + let mut output = [0; N]; + let mut i = 0; + if hex.len() != 2 * N { + panic!("hex string length is not valid"); + } + while i < N { + output[i] = 16 * c2b(hex.as_bytes()[2 * i]) + c2b(hex.as_bytes()[2 * i + 1]); + i += 1; + } + output +} + +#[cfg(test)] +mod testh2b { + use super::private_hex2array; + + #[test] + fn t00() { + const T0: [u8; 0] = private_hex2array(""); + const EMPTY: [u8; 0] = []; + assert_eq!(T0, EMPTY); + } + + macro_rules! test_byte { + ($a:expr, $b:expr) => {{ + const X: [u8; 1] = private_hex2array($a); + assert_eq!(X, [$b]); + }}; + } + + #[test] + fn t01() { + test_byte!("00", 0); + test_byte!("01", 1); + test_byte!("02", 2); + test_byte!("03", 3); + test_byte!("04", 4); + test_byte!("05", 5); + test_byte!("06", 6); + test_byte!("07", 7); + test_byte!("08", 8); + test_byte!("09", 9); + test_byte!("0a", 10); + test_byte!("0A", 10); + test_byte!("0b", 11); + test_byte!("0B", 11); + test_byte!("0c", 12); + test_byte!("0C", 12); + test_byte!("0d", 13); + test_byte!("0D", 13); + test_byte!("0e", 14); + test_byte!("0E", 14); + test_byte!("0f", 15); + test_byte!("0F", 15); + } + + #[test] + fn t02() { + const T0: [u8; 2] = private_hex2array("0a10"); + assert_eq!(T0, [10, 16]); + const T1: [u8; 2] = private_hex2array("4545"); + assert_eq!(T1, [69, 69]); + } + + #[test] + fn t02m() { + assert_eq!(hex2array!("0a10"), [10, 16]); + assert_eq!(hex2array!("4545"), [69, 69]); + assert_eq!( + hex2array!("000102030405060708090a0b0c0d0e0f"), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + ); + } + + #[test] + fn t16() { + const T16: [u8; 16] = private_hex2array("000102030405060708090a0b0c0d0e0f"); + + assert_eq!(T16, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + } + + #[test] + fn t33() { + const T33: [u8; 33] = + private_hex2array("9c8af77d3a4e3f6f076853922985b9e6724fc9675329087f47aff1ceaaae772180"); + + assert_eq!( + T33, + [ + 156, 138, 247, 125, 58, 78, 63, 111, 7, 104, 83, 146, 41, 133, 185, 230, 114, 79, + 201, 103, 83, 41, 8, 127, 71, 175, 241, 206, 170, 174, 119, 33, 128 + ] + ); + } + + #[test] + #[should_panic = "hex string length is not valid"] + fn t_panic_incorrect_length2() { + let _ = private_hex2array::<2>("454"); + } + + #[test] + #[should_panic = "hex string contains invalid character"] + fn t_panic_invalid_character() { + let _ = private_hex2array::<2>("45ag"); + } +} diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index c9719e344d3e208f8f9b81dddad10acf2eedfebb..2da38d44be4b83ea6f45c5e1aaf9b7973b5a01de 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -434,7 +434,7 @@ impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T { fn from_string(s: &str) -> Result { let cap = AddressUri::parse(s)?; if cap.pass.is_some() { - return Err(PublicError::PasswordNotAllowed); + return Err(PublicError::PasswordNotAllowed) } let s = cap.phrase.unwrap_or(DEV_ADDRESS); let addr = if let Some(stripped) = s.strip_prefix("0x") { @@ -454,7 +454,7 @@ impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T { fn from_string_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { let cap = AddressUri::parse(s)?; if cap.pass.is_some() { - return Err(PublicError::PasswordNotAllowed); + return Err(PublicError::PasswordNotAllowed) } let (addr, v) = Self::from_ss58check_with_version(cap.phrase.unwrap_or(DEV_ADDRESS))?; if cap.paths.is_empty() { @@ -1109,8 +1109,8 @@ impl<'a> TryFrom<&'a str> for KeyTypeId { pub trait VrfCrypto { /// VRF input. type VrfInput; - /// VRF output. - type VrfOutput; + /// VRF pre-output. + type VrfPreOutput; /// VRF signing data. type VrfSignData; /// VRF signature. @@ -1119,8 +1119,8 @@ pub trait VrfCrypto { /// VRF Secret Key. pub trait VrfSecret: VrfCrypto { - /// Get VRF-specific output . - fn vrf_output(&self, data: &Self::VrfInput) -> Self::VrfOutput; + /// Get VRF-specific pre-output. + fn vrf_pre_output(&self, data: &Self::VrfInput) -> Self::VrfPreOutput; /// Sign VRF-specific data. fn vrf_sign(&self, input: &Self::VrfSignData) -> Self::VrfSignature; diff --git a/substrate/primitives/core/src/lib.rs b/substrate/primitives/core/src/lib.rs index 4873d1a2112744e2e5ebbcd5c06c5f4b186f04ff..c7232563cb738a6087eeaebbaec678d36d0128be 100644 --- a/substrate/primitives/core/src/lib.rs +++ b/substrate/primitives/core/src/lib.rs @@ -51,6 +51,7 @@ pub mod hashing; #[cfg(feature = "full_crypto")] pub use hashing::{blake2_128, blake2_256, keccak_256, twox_128, twox_256, twox_64}; +pub mod const_hex2array; pub mod crypto; pub mod hexdisplay; pub use paste; diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs index edf6156f268eeccadd5da4333ab7e049eaf0c296..960b8469249e22b10352545f26d3940df3da2e77 100644 --- a/substrate/primitives/core/src/paired_crypto.rs +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -128,7 +128,7 @@ pub mod ecdsa_bls377 { let Ok(right_sig) = sig.0[ecdsa::SIGNATURE_SERIALIZED_SIZE..].try_into() else { return false }; - bls377::Pair::verify(&right_sig, message.as_ref(), &right_pub) + bls377::Pair::verify(&right_sig, message, &right_pub) } } } diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index ffa52ef97d1f58f3af2b2f2c62015c411e464ae6..b821055e2c56713067bf096899c2b10b6155c877 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -555,7 +555,7 @@ pub mod vrf { use crate::crypto::{VrfCrypto, VrfPublic}; use schnorrkel::{ errors::MultiSignatureStage, - vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}, + vrf::{VRF_PREOUT_LENGTH, VRF_PROOF_LENGTH}, SignatureError, }; @@ -628,37 +628,37 @@ pub mod vrf { /// VRF signature data #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct VrfSignature { - /// VRF output. - pub output: VrfOutput, + /// VRF pre-output. + pub pre_output: VrfPreOutput, /// VRF proof. pub proof: VrfProof, } - /// VRF output type suitable for schnorrkel operations. + /// VRF pre-output type suitable for schnorrkel operations. #[derive(Clone, Debug, PartialEq, Eq)] - pub struct VrfOutput(pub schnorrkel::vrf::VRFOutput); + pub struct VrfPreOutput(pub schnorrkel::vrf::VRFPreOut); - impl Encode for VrfOutput { + impl Encode for VrfPreOutput { fn encode(&self) -> Vec { self.0.as_bytes().encode() } } - impl Decode for VrfOutput { + impl Decode for VrfPreOutput { fn decode(i: &mut R) -> Result { - let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; - Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) + let decoded = <[u8; VRF_PREOUT_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFPreOut::from_bytes(&decoded).map_err(convert_error)?)) } } - impl MaxEncodedLen for VrfOutput { + impl MaxEncodedLen for VrfPreOutput { fn max_encoded_len() -> usize { - <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() + <[u8; VRF_PREOUT_LENGTH]>::max_encoded_len() } } - impl TypeInfo for VrfOutput { - type Identity = [u8; VRF_OUTPUT_LENGTH]; + impl TypeInfo for VrfPreOutput { + type Identity = [u8; VRF_PREOUT_LENGTH]; fn type_info() -> scale_info::Type { Self::Identity::type_info() @@ -699,7 +699,7 @@ pub mod vrf { #[cfg(feature = "full_crypto")] impl VrfCrypto for Pair { type VrfInput = VrfTranscript; - type VrfOutput = VrfOutput; + type VrfPreOutput = VrfPreOutput; type VrfSignData = VrfSignData; type VrfSignature = VrfSignature; } @@ -717,18 +717,18 @@ pub mod vrf { let proof = self.0.dleq_proove(extra, &inout, true).0; - VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) } + VrfSignature { pre_output: VrfPreOutput(inout.to_preout()), proof: VrfProof(proof) } } - fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { - let output = self.0.vrf_create_hash(input.0.clone()).to_output(); - VrfOutput(output) + fn vrf_pre_output(&self, input: &Self::VrfInput) -> Self::VrfPreOutput { + let pre_output = self.0.vrf_create_hash(input.0.clone()).to_preout(); + VrfPreOutput(pre_output) } } impl VrfCrypto for Public { type VrfInput = VrfTranscript; - type VrfOutput = VrfOutput; + type VrfPreOutput = VrfPreOutput; type VrfSignData = VrfSignData; type VrfSignature = VrfSignature; } @@ -739,7 +739,7 @@ pub mod vrf { let public = schnorrkel::PublicKey::from_bytes(self)?; let inout = - signature.output.0.attach_input_hash(&public, data.transcript.0.clone())?; + signature.pre_output.0.attach_input_hash(&public, data.transcript.0.clone())?; let extra = data .extra @@ -762,6 +762,7 @@ pub mod vrf { ScalarFormatError => "Signature error: `ScalarFormatError`".into(), NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), + InvalidKey => "Signature error: `InvalidKey`".into(), MuSigAbsent { musig_stage: Commitment } => "Signature error: `MuSigAbsent` at stage `Commitment`".into(), MuSigAbsent { musig_stage: Reveal } => @@ -802,19 +803,21 @@ pub mod vrf { &self, context: &[u8], input: &VrfInput, - output: &VrfOutput, + pre_output: &VrfPreOutput, ) -> Result<[u8; N], codec::Error> where [u8; N]: Default, { let pubkey = schnorrkel::PublicKey::from_bytes(&self.0).map_err(convert_error)?; - let inout = - output.0.attach_input_hash(&pubkey, input.0.clone()).map_err(convert_error)?; + let inout = pre_output + .0 + .attach_input_hash(&pubkey, input.0.clone()) + .map_err(convert_error)?; Ok(inout.make_bytes::<[u8; N]>(context)) } } - impl VrfOutput { + impl VrfPreOutput { /// Generate output bytes from the given VRF configuration. pub fn make_bytes( &self, @@ -1097,10 +1100,10 @@ mod tests { let input = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]); - let output = pair.vrf_output(&input); + let pre_output = pair.vrf_pre_output(&input); let out1 = pair.make_bytes::<32>(ctx, &input); - let out2 = output.make_bytes::<32>(ctx, &input, &public).unwrap(); + let out2 = pre_output.make_bytes::<32>(ctx, &input, &public).unwrap(); assert_eq!(out1, out2); let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]); @@ -1108,7 +1111,7 @@ mod tests { let signature = pair.vrf_sign(&data); assert!(public.vrf_verify(&data, &signature)); - let out3 = public.make_bytes::<32>(ctx, &input, &signature.output).unwrap(); + let out3 = public.make_bytes::<32>(ctx, &input, &signature.pre_output).unwrap(); assert_eq!(out2, out3); } @@ -1126,7 +1129,7 @@ mod tests { assert!(public.vrf_verify(&data, &signature)); let out1 = pair.make_bytes::<32>(ctx, &input); - let out2 = public.make_bytes::<32>(ctx, &input, &signature.output).unwrap(); + let out2 = public.make_bytes::<32>(ctx, &input, &signature.pre_output).unwrap(); assert_eq!(out1, out2); // Direct call to backend version of sign after check with extra params @@ -1139,9 +1142,9 @@ mod tests { }) .unwrap(); let signature2 = - VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) }; + VrfSignature { pre_output: VrfPreOutput(inout.to_preout()), proof: VrfProof(proof) }; assert!(public.vrf_verify(&data, &signature2)); - assert_eq!(signature.output, signature2.output); + assert_eq!(signature.pre_output, signature2.pre_output); } } diff --git a/substrate/primitives/crypto/ec-utils/Cargo.toml b/substrate/primitives/crypto/ec-utils/Cargo.toml index 548328fec3c130ce9a19f45b4469d5b4089f4f05..3baa8ea5b78421e37ab0e56fb0513c81f5c4a049 100644 --- a/substrate/primitives/crypto/ec-utils/Cargo.toml +++ b/substrate/primitives/crypto/ec-utils/Cargo.toml @@ -8,6 +8,9 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -23,7 +26,7 @@ ark-ed-on-bls12-381-bandersnatch-ext = { version = "0.4.1", default-features = f ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false, optional = true } ark-ed-on-bls12-377-ext = { version = "0.4.1", default-features = false, optional = true } ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false, optional = true } -ark-scale = { version = "0.0.11", default-features = false, features = ["hazmat"], optional = true } +ark-scale = { version = "0.0.12", default-features = false, features = ["hazmat"], optional = true } sp-runtime-interface = { path = "../../runtime-interface", default-features = false, optional = true } sp-std = { path = "../../std", default-features = false, optional = true } diff --git a/substrate/primitives/database/Cargo.toml b/substrate/primitives/database/Cargo.toml index 430895236d4f0eec73b8939df14e8075a32cd630..00ccf97c83e95d4a979812b59284e00fe2e74545 100644 --- a/substrate/primitives/database/Cargo.toml +++ b/substrate/primitives/database/Cargo.toml @@ -10,6 +10,9 @@ description = "Substrate database trait." documentation = "https://docs.rs/sp-database" readme = "README.md" +[lints] +workspace = true + [dependencies] kvdb = "0.13.0" parking_lot = "0.12.1" diff --git a/substrate/primitives/database/src/lib.rs b/substrate/primitives/database/src/lib.rs index 012f699552d749a1cca49f9655485841a0d8dca2..42920bbefb499e550efdfc67552aa67b52665859 100644 --- a/substrate/primitives/database/src/lib.rs +++ b/substrate/primitives/database/src/lib.rs @@ -101,7 +101,9 @@ pub trait Database>: Send + Sync { /// This may be faster than `get` since it doesn't allocate. /// Use `with_get` helper function if you need `f` to return a value from `f` fn with_get(&self, col: ColumnId, key: &[u8], f: &mut dyn FnMut(&[u8])) { - self.get(col, key).map(|v| f(&v)); + if let Some(v) = self.get(col, key) { + f(&v) + } } /// Check if database supports internal ref counting for state data. diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index 1f739c256d09ed162afeaf64dcda83a76ac10fff..b7a65d658cfc3500b3b3464149c37dde12f0e3fb 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -9,6 +9,8 @@ repository.workspace = true description = "Macros to derive runtime debug implementation." documentation = "https://docs.rs/sp-debug-derive" +[lints] +workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,7 +20,7 @@ proc-macro = true [dependencies] quote = "1.0.28" -syn = "2.0.39" +syn = "2.0.48" proc-macro2 = "1.0.56" [features] diff --git a/substrate/primitives/externalities/Cargo.toml b/substrate/primitives/externalities/Cargo.toml index 86d31c31cbae00da854cbd2e1bb9dcd80f274c4b..4c7afc38b815f7579e527598b48d9543ac4543bb 100644 --- a/substrate/primitives/externalities/Cargo.toml +++ b/substrate/primitives/externalities/Cargo.toml @@ -10,6 +10,9 @@ description = "Substrate externalities abstraction" documentation = "https://docs.rs/sp-externalities" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 00b3bc876ac0fd7992ec4bd2a89056c1ad274c18..42d6c8c3d65dc71d3612ea851c228a096bca28c6 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate GenesisConfig builder API" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,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.108", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } [features] default = ["std"] diff --git a/substrate/primitives/inherents/Cargo.toml b/substrate/primitives/inherents/Cargo.toml index 2b5bad5d74633cfb0b82b6c78a6d9680e15c6f0f..5c13694ceac1624f7b8860b39dbcd4d7cce48239 100644 --- a/substrate/primitives/inherents/Cargo.toml +++ b/substrate/primitives/inherents/Cargo.toml @@ -10,11 +10,14 @@ description = "Provides types and traits for creating and checking inherents." documentation = "https://docs.rs/sp-inherents" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { version = "0.1.57", optional = true } +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" diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index e95a9302d24b9564ec6589fe776cbca6bc26eb25..47de957e6bf9eadc375c4f06f4c1beee9843a462 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -11,6 +11,9 @@ documentation = "https://docs.rs/sp-io" readme = "README.md" build = "build.rs" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -30,10 +33,10 @@ sp-tracing = { path = "../tracing", default-features = false } log = { version = "0.4.17", optional = 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.28", default-features = false } +tracing-core = { version = "0.1.32", default-features = false } # Required for backwards compatibility reason, but only used for verifying when `UseDalekExt` is set. -ed25519-dalek = { version = "2.0", default-features = false, optional = true } +ed25519-dalek = { version = "2.1", default-features = false, optional = true } [build-dependencies] rustversion = "1.0.6" diff --git a/substrate/primitives/keyring/Cargo.toml b/substrate/primitives/keyring/Cargo.toml index a504cda756e92e280427e2ee0dee9f1a219557c4..80d773b452aed4ffa41501640b723230f5ef6f8a 100644 --- a/substrate/primitives/keyring/Cargo.toml +++ b/substrate/primitives/keyring/Cargo.toml @@ -10,11 +10,13 @@ description = "Keyring support code for the runtime. A set of test accounts." documentation = "https://docs.rs/sp-keyring" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -lazy_static = "1.4.0" strum = { version = "0.24.1", features = ["derive"], default-features = false } sp-core = { path = "../core" } sp-runtime = { path = "../runtime" } diff --git a/substrate/primitives/keyring/src/bandersnatch.rs b/substrate/primitives/keyring/src/bandersnatch.rs index 8de6786a6fbf6384de2308e420201c45a31109f6..eb60f85632725ca9efbac46c63222fc71609d74f 100644 --- a/substrate/primitives/keyring/src/bandersnatch.rs +++ b/substrate/primitives/keyring/src/bandersnatch.rs @@ -21,12 +21,9 @@ pub use sp_core::bandersnatch; use sp_core::{ bandersnatch::{Pair, Public, Signature}, crypto::UncheckedFrom, - ByteArray, Pair as PairT, + hex2array, ByteArray, Pair as PairT, }; -use lazy_static::lazy_static; -use std::{collections::HashMap, ops::Deref, sync::Mutex}; - /// Set of test accounts. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] pub enum Keyring { @@ -74,7 +71,7 @@ impl Keyring { } pub fn public(self) -> Public { - self.pair().public() + Public::from(self) } pub fn to_seed(self) -> String { @@ -129,20 +126,9 @@ impl std::str::FromStr for Keyring { } } -lazy_static! { - static ref PRIVATE_KEYS: Mutex> = - Mutex::new(Keyring::iter().map(|who| (who, who.pair())).collect()); - static ref PUBLIC_KEYS: HashMap = PRIVATE_KEYS - .lock() - .unwrap() - .iter() - .map(|(&who, pair)| (who, pair.public())) - .collect(); -} - impl From for Public { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap() + Public::unchecked_from(<[u8; PUBLIC_RAW_LEN]>::from(k)) } } @@ -154,32 +140,24 @@ impl From for Pair { impl From for [u8; PUBLIC_RAW_LEN] { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap().as_ref() - } -} - -impl From for &'static [u8; PUBLIC_RAW_LEN] { - fn from(k: Keyring) -> Self { - PUBLIC_KEYS.get(&k).unwrap().as_ref() - } -} - -impl AsRef<[u8; PUBLIC_RAW_LEN]> for Keyring { - fn as_ref(&self) -> &[u8; PUBLIC_RAW_LEN] { - PUBLIC_KEYS.get(self).unwrap().as_ref() - } -} - -impl AsRef for Keyring { - fn as_ref(&self) -> &Public { - PUBLIC_KEYS.get(self).unwrap() - } -} - -impl Deref for Keyring { - type Target = [u8; PUBLIC_RAW_LEN]; - fn deref(&self) -> &[u8; PUBLIC_RAW_LEN] { - PUBLIC_KEYS.get(self).unwrap().as_ref() + match k { + Keyring::Alice => + hex2array!("9c8af77d3a4e3f6f076853922985b9e6724fc9675329087f47aff1ceaaae772180"), + Keyring::Bob => + hex2array!("1abfbb76dc8374a1a6d93d59a5c81f07c18835f4681a6258aa0f514d363bff4780"), + Keyring::Charlie => + hex2array!("0f4a9990aca3d39a7cd8bf187e2e81a9ea6f9cedb2db405f2fffff384c5dd02680"), + Keyring::Dave => + hex2array!("bd7a87d4dfa89926a408b5acbed554ae3b053fa3532531053295cbabf07d337000"), + Keyring::Eve => + hex2array!("f992d5b8eac8fc004d521bee6edc1174cfa7fae3a1baec8262511ee351f9f85e00"), + Keyring::Ferdie => + hex2array!("1ce2613e89bc5c8e358aad884099cfb576a61176f2f9968cd0d486a04457245180"), + Keyring::One => + hex2array!("a29e03ac273e521274d8e501a6242abd2ab393d7e197221a9113bdf8e2e5b34d00"), + Keyring::Two => + hex2array!("f968d47e819ddb18a9d0f2ebd16501680b1a3f07ee375c6f81310e5f99a04f4d00"), + } } } @@ -206,4 +184,9 @@ mod tests { &Keyring::Bob.public(), )); } + #[test] + fn verify_static_public_keys() { + assert!(Keyring::iter() + .all(|k| { k.pair().public().as_ref() == <[u8; PUBLIC_RAW_LEN]>::from(k) })); + } } diff --git a/substrate/primitives/keyring/src/ed25519.rs b/substrate/primitives/keyring/src/ed25519.rs index 3060bfb1ad9870f8f7a6af51ed57e634931127e8..ade42b294940213664d0553a4e4ec537ce553f15 100644 --- a/substrate/primitives/keyring/src/ed25519.rs +++ b/substrate/primitives/keyring/src/ed25519.rs @@ -17,14 +17,12 @@ //! Support code for the runtime. A set of test accounts. -use lazy_static::lazy_static; pub use sp_core::ed25519; use sp_core::{ ed25519::{Pair, Public, Signature}, - ByteArray, Pair as PairT, H256, + hex2array, ByteArray, Pair as PairT, H256, }; use sp_runtime::AccountId32; -use std::{collections::HashMap, ops::Deref}; /// Set of test accounts. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] @@ -93,7 +91,7 @@ impl Keyring { } pub fn public(self) -> Public { - self.pair().public() + Public::from(self) } pub fn to_seed(self) -> String { @@ -128,16 +126,9 @@ impl From for sp_runtime::MultiSigner { } } -lazy_static! { - static ref PRIVATE_KEYS: HashMap = - Keyring::iter().map(|i| (i, i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap = - PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); -} - impl From for Public { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap() + Public::from_raw(k.into()) } } @@ -155,38 +146,42 @@ impl From for Pair { impl From for [u8; 32] { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() + match k { + Keyring::Alice => + hex2array!("88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee"), + Keyring::Bob => + hex2array!("d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae69"), + Keyring::Charlie => + hex2array!("439660b36c6c03afafca027b910b4fecf99801834c62a5e6006f27d978de234f"), + Keyring::Dave => + hex2array!("5e639b43e0052c47447dac87d6fd2b6ec50bdd4d0f614e4299c665249bbd09d9"), + Keyring::Eve => + hex2array!("1dfe3e22cc0d45c70779c1095f7489a8ef3cf52d62fbd8c2fa38c9f1723502b5"), + Keyring::Ferdie => + hex2array!("568cb4a574c6d178feb39c27dfc8b3f789e5f5423e19c71633c748b9acf086b5"), + Keyring::AliceStash => + hex2array!("451781cd0c5504504f69ceec484cc66e4c22a2b6a9d20fb1a426d91ad074a2a8"), + Keyring::BobStash => + hex2array!("292684abbb28def63807c5f6e84e9e8689769eb37b1ab130d79dbfbf1b9a0d44"), + Keyring::CharlieStash => + hex2array!("dd6a6118b6c11c9c9e5a4f34ed3d545e2c74190f90365c60c230fa82e9423bb9"), + Keyring::DaveStash => + hex2array!("1d0432d75331ab299065bee79cdb1bdc2497c597a3087b4d955c67e3c000c1e2"), + Keyring::EveStash => + hex2array!("c833bdd2e1a7a18acc1c11f8596e2e697bb9b42d6b6051e474091a1d43a294d7"), + Keyring::FerdieStash => + hex2array!("199d749dbf4b8135cb1f3c8fd697a390fc0679881a8a110c1d06375b3b62cd09"), + Keyring::One => + hex2array!("16f97016bbea8f7b45ae6757b49efc1080accc175d8f018f9ba719b60b0815e4"), + Keyring::Two => + hex2array!("5079bcd20fd97d7d2f752c4607012600b401950260a91821f73e692071c82bf5"), + } } } impl From for H256 { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().as_array_ref().into() - } -} - -impl From for &'static [u8; 32] { - fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() - } -} - -impl AsRef<[u8; 32]> for Keyring { - fn as_ref(&self) -> &[u8; 32] { - (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() - } -} - -impl AsRef for Keyring { - fn as_ref(&self) -> &Public { - (*PUBLIC_KEYS).get(self).unwrap() - } -} - -impl Deref for Keyring { - type Target = [u8; 32]; - fn deref(&self) -> &[u8; 32] { - (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() + k.into() } } @@ -213,4 +208,9 @@ mod tests { &Keyring::Bob.public(), )); } + + #[test] + fn verify_static_public_keys() { + assert!(Keyring::iter().all(|k| { k.pair().public().as_ref() == <[u8; 32]>::from(k) })); + } } diff --git a/substrate/primitives/keyring/src/sr25519.rs b/substrate/primitives/keyring/src/sr25519.rs index 914a66b4d837c94b1073d10cfc92187402597700..1c2a2526efb1eccb16cb0696f89cb3fc7583357b 100644 --- a/substrate/primitives/keyring/src/sr25519.rs +++ b/substrate/primitives/keyring/src/sr25519.rs @@ -17,14 +17,13 @@ //! Support code for the runtime. A set of test accounts. -use lazy_static::lazy_static; pub use sp_core::sr25519; use sp_core::{ + hex2array, sr25519::{Pair, Public, Signature}, ByteArray, Pair as PairT, H256, }; use sp_runtime::AccountId32; -use std::{collections::HashMap, ops::Deref}; /// Set of test accounts. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] @@ -93,7 +92,7 @@ impl Keyring { } pub fn public(self) -> Public { - self.pair().public() + Public::from(self) } pub fn to_seed(self) -> String { @@ -165,13 +164,6 @@ impl std::str::FromStr for Keyring { } } -lazy_static! { - static ref PRIVATE_KEYS: HashMap = - Keyring::iter().map(|i| (i, i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap = - PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); -} - impl From for AccountId32 { fn from(k: Keyring) -> Self { k.to_account_id() @@ -180,7 +172,7 @@ impl From for AccountId32 { impl From for Public { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap() + Public::from_raw(k.into()) } } @@ -192,38 +184,42 @@ impl From for Pair { impl From for [u8; 32] { fn from(k: Keyring) -> Self { - *(*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() + match k { + Keyring::Alice => + hex2array!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"), + Keyring::Bob => + hex2array!("8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"), + Keyring::Charlie => + hex2array!("90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22"), + Keyring::Dave => + hex2array!("306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20"), + Keyring::Eve => + hex2array!("e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e"), + Keyring::Ferdie => + hex2array!("1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"), + Keyring::AliceStash => + hex2array!("be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f"), + Keyring::BobStash => + hex2array!("fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e"), + Keyring::CharlieStash => + hex2array!("1e07379407fecc4b89eb7dbd287c2c781cfb1907a96947a3eb18e4f8e7198625"), + Keyring::DaveStash => + hex2array!("e860f1b1c7227f7c22602f53f15af80747814dffd839719731ee3bba6edc126c"), + Keyring::EveStash => + hex2array!("8ac59e11963af19174d0b94d5d78041c233f55d2e19324665bafdfb62925af2d"), + Keyring::FerdieStash => + hex2array!("101191192fc877c24d725b337120fa3edc63d227bbc92705db1e2cb65f56981a"), + Keyring::One => + hex2array!("ac859f8a216eeb1b320b4c76d118da3d7407fa523484d0a980126d3b4d0d220a"), + Keyring::Two => + hex2array!("1254f7017f0b8347ce7ab14f96d818802e7e9e0c0d1b7c9acb3c726b080e7a03"), + } } } impl From for H256 { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().as_array_ref().into() - } -} - -impl From for &'static [u8; 32] { - fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().as_array_ref() - } -} - -impl AsRef<[u8; 32]> for Keyring { - fn as_ref(&self) -> &[u8; 32] { - (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() - } -} - -impl AsRef for Keyring { - fn as_ref(&self) -> &Public { - (*PUBLIC_KEYS).get(self).unwrap() - } -} - -impl Deref for Keyring { - type Target = [u8; 32]; - fn deref(&self) -> &[u8; 32] { - (*PUBLIC_KEYS).get(self).unwrap().as_array_ref() + k.into() } } @@ -250,4 +246,8 @@ mod tests { &Keyring::Bob.public(), )); } + #[test] + fn verify_static_public_keys() { + assert!(Keyring::iter().all(|k| { k.pair().public().as_ref() == <[u8; 32]>::from(k) })); + } } diff --git a/substrate/primitives/keystore/Cargo.toml b/substrate/primitives/keystore/Cargo.toml index 819b2c518a0d4f7de02ff92afaa328bb0a3edad0..d60f5d6c568c50c783554beb2756610d7cae9855 100644 --- a/substrate/primitives/keystore/Cargo.toml +++ b/substrate/primitives/keystore/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Keystore primitives." documentation = "https://docs.rs/sp-core" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,7 +23,7 @@ sp-core = { path = "../core", default-features = false } sp-externalities = { path = "../externalities", default-features = false } [dev-dependencies] -rand = "0.7.2" +rand = "0.8.5" rand_chacha = "0.2.2" [features] diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index e415080779cf43774eabb933a9778f1c94b7a489..07c4e2d5fd1dc4b8cc903d34154dbc51a050426c 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -92,19 +92,19 @@ pub trait Keystore: Send + Sync { data: &sr25519::vrf::VrfSignData, ) -> Result, Error>; - /// Generate an sr25519 VRF output for a given input data. + /// Generate an sr25519 VRF pre-output for a given input data. /// /// Receives [`KeyTypeId`] and an [`sr25519::Public`] key to be able to map /// them to a private key that exists in the keystore. /// /// Returns `None` if the given `key_type` and `public` combination doesn't /// exist in the keystore or an `Err` when something failed. - fn sr25519_vrf_output( + fn sr25519_vrf_pre_output( &self, key_type: KeyTypeId, public: &sr25519::Public, input: &sr25519::vrf::VrfInput, - ) -> Result, Error>; + ) -> Result, Error>; /// Returns all ed25519 public keys for the given key type. fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec; @@ -223,7 +223,7 @@ pub trait Keystore: Send + Sync { input: &bandersnatch::vrf::VrfSignData, ) -> Result, Error>; - /// Generate a bandersnatch VRF (pre)output for a given input data. + /// Generate a bandersnatch VRF pre-output for a given input data. /// /// Receives [`KeyTypeId`] and an [`bandersnatch::Public`] key to be able to map /// them to a private key that exists in the keystore. @@ -231,12 +231,12 @@ pub trait Keystore: Send + Sync { /// Returns `None` if the given `key_type` and `public` combination doesn't /// exist in the keystore or an `Err` when something failed. #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_vrf_output( + fn bandersnatch_vrf_pre_output( &self, key_type: KeyTypeId, public: &bandersnatch::Public, input: &bandersnatch::vrf::VrfInput, - ) -> Result, Error>; + ) -> Result, Error>; /// Generate a bandersnatch ring-VRF signature for the given data. /// @@ -474,13 +474,13 @@ impl Keystore for Arc { (**self).sr25519_vrf_sign(key_type, public, data) } - fn sr25519_vrf_output( + fn sr25519_vrf_pre_output( &self, key_type: KeyTypeId, public: &sr25519::Public, input: &sr25519::vrf::VrfInput, - ) -> Result, Error> { - (**self).sr25519_vrf_output(key_type, public, input) + ) -> Result, Error> { + (**self).sr25519_vrf_pre_output(key_type, public, input) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { @@ -569,13 +569,13 @@ impl Keystore for Arc { } #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_vrf_output( + fn bandersnatch_vrf_pre_output( &self, key_type: KeyTypeId, public: &bandersnatch::Public, input: &bandersnatch::vrf::VrfInput, - ) -> Result, Error> { - (**self).bandersnatch_vrf_output(key_type, public, input) + ) -> Result, Error> { + (**self).bandersnatch_vrf_pre_output(key_type, public, input) } #[cfg(feature = "bandersnatch-experimental")] diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 9b59b6d09ea08cdae24eb90ee06d8d2b34aef20c..585efba02831a442bda511844811a862442cc87d 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -113,14 +113,14 @@ impl MemoryKeystore { Ok(sig) } - fn vrf_output( + fn vrf_pre_output( &self, key_type: KeyTypeId, public: &T::Public, input: &T::VrfInput, - ) -> Result, Error> { - let preout = self.pair::(key_type, public).map(|pair| pair.vrf_output(input)); - Ok(preout) + ) -> Result, Error> { + let pre_output = self.pair::(key_type, public).map(|pair| pair.vrf_pre_output(input)); + Ok(pre_output) } } @@ -155,13 +155,13 @@ impl Keystore for MemoryKeystore { self.vrf_sign::(key_type, public, data) } - fn sr25519_vrf_output( + fn sr25519_vrf_pre_output( &self, key_type: KeyTypeId, public: &sr25519::Public, input: &sr25519::vrf::VrfInput, - ) -> Result, Error> { - self.vrf_output::(key_type, public, input) + ) -> Result, Error> { + self.vrf_pre_output::(key_type, public, input) } fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { @@ -265,13 +265,13 @@ impl Keystore for MemoryKeystore { } #[cfg(feature = "bandersnatch-experimental")] - fn bandersnatch_vrf_output( + fn bandersnatch_vrf_pre_output( &self, key_type: KeyTypeId, public: &bandersnatch::Public, input: &bandersnatch::vrf::VrfInput, - ) -> Result, Error> { - self.vrf_output::(key_type, public, input) + ) -> Result, Error> { + self.vrf_pre_output::(key_type, public, input) } #[cfg(feature = "bls-experimental")] @@ -443,7 +443,7 @@ mod tests { } #[test] - fn sr25519_vrf_output() { + fn sr25519_vrf_pre_output() { let store = MemoryKeystore::new(); let secret_uri = "//Alice"; @@ -458,16 +458,17 @@ mod tests { ], ); - let result = store.sr25519_vrf_output(SR25519, &pair.public(), &input); + let result = store.sr25519_vrf_pre_output(SR25519, &pair.public(), &input); assert!(result.unwrap().is_none()); store .insert(SR25519, secret_uri, pair.public().as_ref()) .expect("Inserts unknown key"); - let preout = store.sr25519_vrf_output(SR25519, &pair.public(), &input).unwrap().unwrap(); + let pre_output = + store.sr25519_vrf_pre_output(SR25519, &pair.public(), &input).unwrap().unwrap(); - let result = preout.make_bytes::<32>(b"rand", &input, &pair.public()); + let result = pre_output.make_bytes::<32>(b"rand", &input, &pair.public()); assert!(result.is_ok()); } diff --git a/substrate/primitives/maybe-compressed-blob/Cargo.toml b/substrate/primitives/maybe-compressed-blob/Cargo.toml index c6fa7103672fc17097a9c446ab01262612034efc..86f73626c0a0bfdbcac66e8b5e6019602713ad03 100644 --- a/substrate/primitives/maybe-compressed-blob/Cargo.toml +++ b/substrate/primitives/maybe-compressed-blob/Cargo.toml @@ -10,6 +10,9 @@ description = "Handling of blobs, usually Wasm code, which may be compresed" documentation = "https://docs.rs/sp-maybe-compressed-blob" readme = "README.md" +[lints] +workspace = true + [dependencies] thiserror = "1.0" 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 82a00935b301b2ea6992cbb34584c9e6eb282473..c0d69a2c4f54b63f389a85f59aeadc331666326f 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Merkle Mountain Range primitives." +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,7 +19,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 } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } -serde = { version = "1.0.193", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } sp-api = { path = "../api", default-features = false } sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } diff --git a/substrate/primitives/metadata-ir/Cargo.toml b/substrate/primitives/metadata-ir/Cargo.toml index f73a1d7b38027e57e045af0f50ba80d64245b8f6..0dc496bab53130138933996c29a6022298779a59 100644 --- a/substrate/primitives/metadata-ir/Cargo.toml +++ b/substrate/primitives/metadata-ir/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Intermediate representation of the runtime metadata." documentation = "https://docs.rs/sp-metadata-ir" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/mixnet/Cargo.toml b/substrate/primitives/mixnet/Cargo.toml index a03fdab8741a22874cc08d8b380823151b97a3e1..6ea7a6cbe8c436359a763effe7075e7099787628 100644 --- a/substrate/primitives/mixnet/Cargo.toml +++ b/substrate/primitives/mixnet/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/npos-elections/Cargo.toml b/substrate/primitives/npos-elections/Cargo.toml index 1ab6c2adf826a56cf838485246930e11ff17d83d..5d7e8704f4cb0729acf5944a4fabfa716ffc7c16 100644 --- a/substrate/primitives/npos-elections/Cargo.toml +++ b/substrate/primitives/npos-elections/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "NPoS election algorithm primitives" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = 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 bd1fa856813b5cf768d80f5d16933f4740217883..29dfa809fbe57fbcda627c8d4e12765c93b9af76 100644 --- a/substrate/primitives/npos-elections/fuzzer/Cargo.toml +++ b/substrate/primitives/npos-elections/fuzzer/Cargo.toml @@ -10,11 +10,14 @@ description = "Fuzzer for phragmén implementation." documentation = "https://docs.rs/sp-npos-elections-fuzzer" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } sp-npos-elections = { path = ".." } diff --git a/substrate/primitives/offchain/Cargo.toml b/substrate/primitives/offchain/Cargo.toml index 201e75802cf7516c00e615c3d5aab36a6bff4c27..19d66ae31e9fe6941937a08b50e8dd8fdc762a56 100644 --- a/substrate/primitives/offchain/Cargo.toml +++ b/substrate/primitives/offchain/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/panic-handler/Cargo.toml b/substrate/primitives/panic-handler/Cargo.toml index 428062757c1556119c5dfe15cf7e1817f842bdd5..a0df527f56e0818c3eb78309b8171675538d15f6 100644 --- a/substrate/primitives/panic-handler/Cargo.toml +++ b/substrate/primitives/panic-handler/Cargo.toml @@ -10,6 +10,9 @@ description = "Custom panic hook with bug report link" documentation = "https://docs.rs/sp-panic-handler" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/rpc/Cargo.toml b/substrate/primitives/rpc/Cargo.toml index cf10af31977fd74a47e992bd61a7b4b54503152d..07bb3bf7293ba48c664f1a1d49eb156421026c4c 100644 --- a/substrate/primitives/rpc/Cargo.toml +++ b/substrate/primitives/rpc/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "Substrate RPC primitives and utilities." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" -serde = { version = "1.0.193", features = ["derive"] } +serde = { version = "1.0.195", features = ["derive"] } sp-core = { path = "../core" } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index 80565420f6b2321fefe787b8b55496f24281566a..a4c8457b598efa713c1b7a6a2444cd8e16edeacc 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -10,6 +10,9 @@ description = "Substrate runtime interface" documentation = "https://docs.rs/sp-runtime-interface/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index efabaee3aeb8f8577539a42ed0c2124d7a9be762..869cad06e56cc0ac2c436baea17f223032d48666 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "This crate provides procedural macros for usage within the context of the Substrate runtime interface." documentation = "https://docs.rs/sp-runtime-interface-proc-macro" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,8 +20,8 @@ proc-macro = true [dependencies] Inflector = "0.11.4" -proc-macro-crate = "2.0.0" +proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" quote = "1.0.28" expander = "2.0.0" -syn = { version = "2.0.39", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } diff --git a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs index 9818fd6842a639191e1f5e31398646984662f0a7..7d97f9f3e1ca09c7540861f8b0b859d297f72030 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/utils.rs @@ -89,7 +89,7 @@ struct RuntimeInterfaceFunctionSet { impl RuntimeInterfaceFunctionSet { fn new(version: VersionAttribute, trait_item: &TraitItemFn) -> Result { Ok(Self { - latest_version_to_call: version.is_callable().then(|| version.version), + latest_version_to_call: version.is_callable().then_some(version.version), versions: BTreeMap::from([( version.version, RuntimeInterfaceFunction::new(trait_item)?, diff --git a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 07c820c06014ad6dacd13ca5981d4cde24ca39e3..f663c6d47263b27909510d51eb1860e4caa8d12a 100644 --- a/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/substrate/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/runtime-interface/test-wasm/Cargo.toml b/substrate/primitives/runtime-interface/test-wasm/Cargo.toml index 79e79857341b60f5ea24d254516a1840346d3fb7..ecb3c7f8732dd18fc79918bd55d8b50066b754d4 100644 --- a/substrate/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/substrate/primitives/runtime-interface/test-wasm/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/runtime-interface/test/Cargo.toml b/substrate/primitives/runtime-interface/test/Cargo.toml index feb6a454af1565a874cdec4075da9023a3232560..55d70960989e8888b524a205d8cb0d58fca975d1 100644 --- a/substrate/primitives/runtime-interface/test/Cargo.toml +++ b/substrate/primitives/runtime-interface/test/Cargo.toml @@ -8,12 +8,15 @@ publish = false homepage = "https://substrate.io" repository.workspace = true +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] tracing = "0.1.29" -tracing-core = "0.1.28" +tracing-core = "0.1.32" sc-executor = { path = "../../../client/executor" } sc-executor-common = { path = "../../../client/executor/common" } sp-io = { path = "../../io" } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 827ccdbddbb5e025912a9efa9d1351ae976aa5fe..448aad39ec9ffb4da05321b5860ccbd6acbad9c6 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -10,6 +10,9 @@ description = "Runtime Modules shared primitive types." documentation = "https://docs.rs/sp-runtime" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -22,7 +25,7 @@ log = { version = "0.4.17", default-features = false } 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.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } @@ -31,11 +34,11 @@ sp-std = { path = "../std", default-features = false } sp-weights = { path = "../weights", default-features = false } docify = { version = "0.2.6" } -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } +simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b" } [dev-dependencies] rand = "0.8.5" -serde_json = "1.0.108" +serde_json = "1.0.111" 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 82ab9a61f96d8f26579bf704947b74a206b7ec99..0eeef363a06dc95e999a0277563e1c86f46e7028 100644 --- a/substrate/primitives/runtime/src/generic/header.rs +++ b/substrate/primitives/runtime/src/generic/header.rs @@ -21,16 +21,11 @@ use crate::{ codec::{Codec, Decode, Encode}, generic::Digest, scale_info::TypeInfo, - traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeFromStr, - MaybeSerializeDeserialize, Member, - }, + traits::{self, AtLeast32BitUnsigned, BlockNumber, Hash as HashT, MaybeDisplay, Member}, }; -use codec::{FullCodec, MaxEncodedLen}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_core::U256; -use sp_std::fmt::Debug; /// Abstraction over a block header for a substrate chain. #[derive(Encode, Decode, PartialEq, Eq, Clone, sp_core::RuntimeDebug, TypeInfo)] @@ -79,20 +74,7 @@ where impl traits::Header for Header where - Number: Member - + MaybeSerializeDeserialize - + MaybeFromStr - + Debug - + Default - + sp_std::hash::Hash - + MaybeDisplay - + AtLeast32BitUnsigned - + FullCodec - + Copy - + MaxEncodedLen - + Into - + TryFrom - + TypeInfo, + Number: BlockNumber, Hash: HashT, { type Number = Number; diff --git a/substrate/primitives/runtime/src/offchain/storage_lock.rs b/substrate/primitives/runtime/src/offchain/storage_lock.rs index 116e157881517db848aa29e11f92f98bb2eb7e69..a2da48721e7591909932e58cb20fffbeedb5f88d 100644 --- a/substrate/primitives/runtime/src/offchain/storage_lock.rs +++ b/substrate/primitives/runtime/src/offchain/storage_lock.rs @@ -156,7 +156,7 @@ pub struct BlockAndTimeDeadline { impl Clone for BlockAndTimeDeadline { fn clone(&self) -> Self { - Self { block_number: self.block_number.clone(), timestamp: self.timestamp } + Self { block_number: self.block_number, timestamp: self.timestamp } } } diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index ec79f43cabdc36249f56f1829de6cdb2289f9913..ad82419cb8b635d6647ba225b5b3ab18a0037ed5 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -38,7 +38,7 @@ pub use sp_arithmetic::traits::{ EnsureOp, EnsureOpAssign, EnsureSub, EnsureSubAssign, IntegerSquareRoot, One, SaturatedConversion, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; -use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; +use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId, U256}; #[doc(hidden)] pub use sp_core::{ parameter_types, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, @@ -1149,6 +1149,44 @@ pub trait IsMember { fn is_member(member_id: &MemberId) -> bool; } +/// Super trait with all the attributes for a block number. +pub trait BlockNumber: + Member + + MaybeSerializeDeserialize + + MaybeFromStr + + Debug + + sp_std::hash::Hash + + Copy + + MaybeDisplay + + AtLeast32BitUnsigned + + Into + + TryFrom + + Default + + TypeInfo + + MaxEncodedLen + + FullCodec +{ +} + +impl< + T: Member + + MaybeSerializeDeserialize + + MaybeFromStr + + Debug + + sp_std::hash::Hash + + Copy + + MaybeDisplay + + AtLeast32BitUnsigned + + Into + + TryFrom + + Default + + TypeInfo + + MaxEncodedLen + + FullCodec, + > BlockNumber for T +{ +} + /// Something which fulfills the abstract idea of a Substrate header. It has types for a `Number`, /// a `Hash` and a `Hashing`. It provides access to an `extrinsics_root`, `state_root` and /// `parent_hash`, as well as a `digest` and a block `number`. @@ -1158,18 +1196,7 @@ pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + TypeInfo + 'static { /// Header number. - type Number: Member - + MaybeSerializeDeserialize - + MaybeFromStr - + Debug - + sp_std::hash::Hash - + Copy - + MaybeDisplay - + AtLeast32BitUnsigned - + Default - + TypeInfo - + MaxEncodedLen - + FullCodec; + type Number: BlockNumber; /// Header hash type type Hash: HashOutput; /// Hashing algorithm @@ -1411,7 +1438,7 @@ pub trait Dispatchable { /// Every function call from your runtime has an origin, which specifies where the extrinsic was /// generated from. In the case of a signed extrinsic (transaction), the origin contains an /// identifier for the caller. The origin can be empty in the case of an inherent extrinsic. - type RuntimeOrigin; + type RuntimeOrigin: Debug; /// ... type Config; /// An opaque set of information attached to the transaction. This could be constructed anywhere @@ -2265,7 +2292,15 @@ pub trait BlockIdTo { /// Get current block number pub trait BlockNumberProvider { /// Type of `BlockNumber` to provide. - type BlockNumber: Codec + Clone + Ord + Eq + AtLeast32BitUnsigned; + type BlockNumber: Codec + + Clone + + Ord + + Eq + + AtLeast32BitUnsigned + + TypeInfo + + Debug + + MaxEncodedLen + + Copy; /// Returns the current block number. /// @@ -2293,6 +2328,13 @@ pub trait BlockNumberProvider { fn set_block_number(_block: Self::BlockNumber) {} } +impl BlockNumberProvider for () { + type BlockNumber = u32; + fn current_block_number() -> Self::BlockNumber { + 0 + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/primitives/session/Cargo.toml b/substrate/primitives/session/Cargo.toml index b7e43f9730042b75b74303c9703bf674e6462622..25700210feef2c0078992ace19597b72291625cc 100644 --- a/substrate/primitives/session/Cargo.toml +++ b/substrate/primitives/session/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Primitives for sessions" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index f52bf3316db2c04c4aa91ad2c27875d85f4167e1..5ffe6fbeaf5da54cb362caac1ed6cdfc27049fe4 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true description = "A crate which contains primitives that are useful for implementation that uses staking approaches in general. Definitions related to sessions, slashing, etc go here." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], 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" diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index ab07d83af6a9aa45627675f5f0cb8c9f57156605..f891a74dbf4d49459a55b198667f141bc256dc5d 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true documentation = "https://docs.rs/sp-state-machine" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/state-machine/src/lib.rs b/substrate/primitives/state-machine/src/lib.rs index 1345097e8f633ed23bbaa49675408f9e0864e07b..5909a30a814c32686acbb6c369831a6d43649a5d 100644 --- a/substrate/primitives/state-machine/src/lib.rs +++ b/substrate/primitives/state-machine/src/lib.rs @@ -142,7 +142,6 @@ pub use crate::{ mod std_reexport { pub use crate::{ basic::BasicExternalities, - error::{Error, ExecutionError}, in_memory_backend::new_in_mem, read_only::{InspectState, ReadOnlyExternalities}, testing::TestExternalities, diff --git a/substrate/primitives/state-machine/src/overlayed_changes/changeset.rs b/substrate/primitives/state-machine/src/overlayed_changes/changeset.rs index 8f2d02fd6840eb33735eb15bcd5c57f8776e6f5c..59589dbbb37e8c37022ab176df0b02c25d53b3b6 100644 --- a/substrate/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/substrate/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -298,7 +298,7 @@ impl OverlayedMap { /// Call this when control returns from the runtime. /// - /// This commits all dangling transaction left open by the runtime. + /// This rollbacks all dangling transaction left open by the runtime. /// Calling this while already outside the runtime will return an error. pub fn exit_runtime(&mut self) -> Result<(), NotInRuntime> { if let ExecutionMode::Client = self.execution_mode { diff --git a/substrate/primitives/state-machine/src/overlayed_changes/mod.rs b/substrate/primitives/state-machine/src/overlayed_changes/mod.rs index 28cfecf1dbd62b5387f79cff8938fc6fc9d4bf16..626cf6c3cafe128c98f6c5bedfe5d6cbe1ab958d 100644 --- a/substrate/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/substrate/primitives/state-machine/src/overlayed_changes/mod.rs @@ -348,7 +348,7 @@ impl OverlayedChanges { /// `None` can be used to delete a value specified by the given key. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn set_child_storage( + pub fn set_child_storage( &mut self, child_info: &ChildInfo, key: StorageKey, @@ -373,7 +373,7 @@ impl OverlayedChanges { /// Clear child storage of given storage key. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) -> u32 { + pub fn clear_child_storage(&mut self, child_info: &ChildInfo) -> u32 { self.mark_dirty(); let extrinsic_index = self.extrinsic_index(); @@ -391,7 +391,7 @@ impl OverlayedChanges { /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) -> u32 { + pub fn clear_prefix(&mut self, prefix: &[u8]) -> u32 { self.mark_dirty(); self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()) @@ -400,7 +400,7 @@ impl OverlayedChanges { /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction - pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) -> u32 { + pub fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) -> u32 { self.mark_dirty(); let extrinsic_index = self.extrinsic_index(); @@ -498,7 +498,7 @@ impl OverlayedChanges { /// Call this when control returns from the runtime. /// - /// This commits all dangling transaction left open by the runtime. + /// This rollbacks all dangling transaction left open by the runtime. /// Calling this while outside the runtime will return an error. pub fn exit_runtime(&mut self) -> Result<(), NotInRuntime> { self.top.exit_runtime()?; diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index 089af92f062392a83c35d5ce747767b5465a7f59..cacfd08f3ebf4802ee3f2f64cfb6d6b8eee2f454 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "A crate which contains primitives related to the statement store" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -25,9 +28,9 @@ sp-externalities = { path = "../externalities", default-features = false } thiserror = { version = "1.0", optional = true } # ECIES dependencies -ed25519-dalek = { version = "2.0.0", optional = true } -x25519-dalek = { version = "2.0.0", optional = true, features = ["static_secrets"] } -curve25519-dalek = { version = "4.0.0", optional = true } +ed25519-dalek = { version = "2.1", optional = true } +x25519-dalek = { version = "2.0", optional = true, features = ["static_secrets"] } +curve25519-dalek = { version = "4.1.1", optional = true } aes-gcm = { version = "0.10", optional = true } hkdf = { version = "0.12.0", optional = true } sha2 = { version = "0.10.7", optional = true } diff --git a/substrate/primitives/std/Cargo.toml b/substrate/primitives/std/Cargo.toml index eae37c6dfe37bc3d6179589947bc5433b448d4d8..f349a7b119688fab1a651f4ea709b76274af2796 100644 --- a/substrate/primitives/std/Cargo.toml +++ b/substrate/primitives/std/Cargo.toml @@ -10,6 +10,9 @@ description = "Lowest-abstraction level for the Substrate runtime: just exports documentation = "https://docs.rs/sp-std" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/storage/Cargo.toml b/substrate/primitives/storage/Cargo.toml index b7ff48cdd635c097cecde880485400a7cf2f1c89..32f59b04a12ada5cb2a1dcf853070280796fe988 100644 --- a/substrate/primitives/storage/Cargo.toml +++ b/substrate/primitives/storage/Cargo.toml @@ -10,6 +10,9 @@ repository.workspace = true documentation = "https://docs.rs/sp-storage/" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,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.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/storage/src/lib.rs b/substrate/primitives/storage/src/lib.rs index f8dc40f051c21cdd7b4a2e0f0df36bbd3f103d10..3528d0558f530b2d37711a564974cd5ab63343c9 100644 --- a/substrate/primitives/storage/src/lib.rs +++ b/substrate/primitives/storage/src/lib.rs @@ -414,12 +414,13 @@ impl ChildTrieParentKeyId { /// /// V0 and V1 uses a same trie implementation, but V1 will write external value node in the trie for /// value with size at least `TRIE_VALUE_NODE_THRESHOLD`. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "std", derive(Encode, Decode))] pub enum StateVersion { /// Old state version, no value nodes. V0 = 0, /// New state version can use value nodes. + #[default] V1 = 1, } @@ -432,12 +433,6 @@ impl Display for StateVersion { } } -impl Default for StateVersion { - fn default() -> Self { - StateVersion::V1 - } -} - impl From for u8 { fn from(version: StateVersion) -> u8 { version as u8 diff --git a/substrate/primitives/test-primitives/Cargo.toml b/substrate/primitives/test-primitives/Cargo.toml index 0f2a399bffb4bf73d268ee292cd32a9c8b3bb684..3649217cf74e136f88860d0c69466d3de9ef5a06 100644 --- a/substrate/primitives/test-primitives/Cargo.toml +++ b/substrate/primitives/test-primitives/Cargo.toml @@ -8,13 +8,16 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.193", default-features = false, features = ["derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = 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 41afab0dcc225610df1227239bfa0e8b86c7b60e..b61f36f2056b8a838e9d5c86d89614f834968f45 100644 --- a/substrate/primitives/timestamp/Cargo.toml +++ b/substrate/primitives/timestamp/Cargo.toml @@ -9,11 +9,14 @@ repository.workspace = true description = "Substrate core types and inherents for timestamps." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { version = "0.1.57", optional = true } +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 } sp-inherents = { path = "../inherents", default-features = false } diff --git a/substrate/primitives/tracing/Cargo.toml b/substrate/primitives/tracing/Cargo.toml index 43b3683fb063130ce8711520c2870309e9d53b5a..0ad3cd0705b3423bdced8be291b45e13f25b672e 100644 --- a/substrate/primitives/tracing/Cargo.toml +++ b/substrate/primitives/tracing/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Instrumentation primitives and macros for Substrate." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] # let's default to wasm32 default-target = "wasm32-unknown-unknown" @@ -23,7 +26,7 @@ codec = { version = "3.6.1", package = "parity-scale-codec", default-features = "derive", ] } tracing = { version = "0.1.29", default-features = false } -tracing-core = { version = "0.1.28", default-features = false } +tracing-core = { version = "0.1.32", default-features = false } tracing-subscriber = { version = "0.2.25", optional = true, features = [ "tracing-log", ] } diff --git a/substrate/primitives/transaction-pool/Cargo.toml b/substrate/primitives/transaction-pool/Cargo.toml index 136d320020261648d2db174995e309d3afd973c8..6e66910ac388576d761d0215b570f1c62f970e95 100644 --- a/substrate/primitives/transaction-pool/Cargo.toml +++ b/substrate/primitives/transaction-pool/Cargo.toml @@ -10,6 +10,9 @@ description = "Transaction pool runtime facing API." documentation = "https://docs.rs/sp-transaction-pool" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/transaction-storage-proof/Cargo.toml b/substrate/primitives/transaction-storage-proof/Cargo.toml index e3bb80b256293e8d92ad360b6a11bd78ede1401a..e1c50a09c59d28c40fa5c97343cc38621cfe99db 100644 --- a/substrate/primitives/transaction-storage-proof/Cargo.toml +++ b/substrate/primitives/transaction-storage-proof/Cargo.toml @@ -9,11 +9,14 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = { version = "0.1.57", optional = true } +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"] } sp-core = { path = "../core", optional = true } diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index 1b1e40c9458f8690533ddd29e09246b2415bcc9a..79ed5c2000094b422e252b99e2f15505d1b8a3f8 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -10,6 +10,9 @@ homepage = "https://substrate.io" documentation = "https://docs.rs/sp-trie" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -20,7 +23,6 @@ harness = false [dependencies] ahash = { version = "0.8.2", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -hashbrown = { version = "0.13.2", optional = true } hash-db = { version = "0.16.0", default-features = false } lazy_static = { version = "1.4.0", optional = true } memory-db = { version = "0.32.0", default-features = false } @@ -50,7 +52,6 @@ std = [ "ahash", "codec/std", "hash-db/std", - "hashbrown", "lazy_static", "memory-db/std", "nohash-hasher", diff --git a/substrate/primitives/trie/src/cache/shared_cache.rs b/substrate/primitives/trie/src/cache/shared_cache.rs index 01ac41a1e47d995a99bea354d7f6dfaf82c433c2..e3ba94a2af7c1c9a4565169c85d6bb6053107a11 100644 --- a/substrate/primitives/trie/src/cache/shared_cache.rs +++ b/substrate/primitives/trie/src/cache/shared_cache.rs @@ -19,11 +19,11 @@ ///! that combines both caches and is exported to the outside. use super::{CacheSize, NodeCached}; use hash_db::Hasher; -use hashbrown::{hash_set::Entry as SetEntry, HashSet}; use nohash_hasher::BuildNoHashHasher; use parking_lot::{Mutex, RwLock, RwLockWriteGuard}; use schnellru::LruMap; use std::{ + collections::{hash_map::Entry as SetEntry, HashMap}, hash::{BuildHasher, Hasher as _}, sync::Arc, }; @@ -148,7 +148,7 @@ pub struct SharedValueCacheLimiter { heap_size: usize, /// A set with all of the keys deduplicated to save on memory. - known_storage_keys: HashSet>, + known_storage_keys: HashMap, (), ahash::RandomState>, /// A counter with the number of elements that got evicted from the cache. /// @@ -189,10 +189,10 @@ where } self.heap_size += new_item_heap_size; - entry.insert(); + entry.insert(()); }, SetEntry::Occupied(entry) => { - key.storage_key = entry.get().clone(); + key.storage_key = entry.key().clone(); }, } @@ -491,7 +491,7 @@ impl> SharedValueCache { max_inline_size, max_heap_size, heap_size: 0, - known_storage_keys: Default::default(), + known_storage_keys: HashMap::with_hasher(RANDOM_STATE.clone()), items_evicted: 0, max_items_evicted: 0, // Will be set during `update`. }, @@ -778,7 +778,9 @@ mod tests { assert_eq!(1, cache.lru.limiter_mut().known_storage_keys.len()); assert_eq!( 3, // Two instances inside the cache + one extra in `known_storage_keys`. - Arc::strong_count(cache.lru.limiter_mut().known_storage_keys.get(&key[..]).unwrap()) + Arc::strong_count( + cache.lru.limiter_mut().known_storage_keys.get_key_value(&key[..]).unwrap().0 + ) ); assert_eq!(key.len(), cache.lru.limiter().heap_size); assert_eq!(cache.lru.len(), 2); @@ -792,7 +794,9 @@ mod tests { assert_eq!(1, cache.lru.limiter_mut().known_storage_keys.len()); assert_eq!( 3, - Arc::strong_count(cache.lru.limiter_mut().known_storage_keys.get(&key[..]).unwrap()) + Arc::strong_count( + cache.lru.limiter_mut().known_storage_keys.get_key_value(&key[..]).unwrap().0 + ) ); assert_eq!(key.len(), cache.lru.limiter().heap_size); assert_eq!(cache.lru.len(), 2); @@ -812,7 +816,9 @@ mod tests { assert_eq!(1, cache.lru.limiter_mut().known_storage_keys.len()); assert_eq!( 3, - Arc::strong_count(cache.lru.limiter_mut().known_storage_keys.get(&key[..]).unwrap()) + Arc::strong_count( + cache.lru.limiter_mut().known_storage_keys.get_key_value(&key[..]).unwrap().0 + ) ); assert_eq!(key.len(), cache.lru.limiter().heap_size); assert_eq!(cache.lru.len(), 2); @@ -833,7 +839,7 @@ mod tests { assert_eq!(cache.lru.limiter().items_evicted, 2); assert_eq!(10, cache.lru.len()); assert_eq!(10, cache.lru.limiter_mut().known_storage_keys.len()); - assert!(cache.lru.limiter_mut().known_storage_keys.get(&key[..]).is_none()); + assert!(cache.lru.limiter_mut().known_storage_keys.get_key_value(&key[..]).is_none()); assert_eq!(key.len() * 10, cache.lru.limiter().heap_size); assert_eq!(cache.lru.len(), 10); assert!(cache.lru.limiter().heap_size <= cache.lru.limiter().max_heap_size); @@ -854,6 +860,6 @@ mod tests { vec![], ); - assert!(cache.lru.limiter_mut().known_storage_keys.get(&key[..]).is_none()); + assert!(cache.lru.limiter_mut().known_storage_keys.get_key_value(&key[..]).is_none()); } } diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 9860ef54c2dbca4fa44eace06be66e07dfa1e38f..1ceda4e700f8aad52519f7864af8ad0ebfe3fe1d 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -10,6 +10,9 @@ description = "Version module for the Substrate runtime; Provides a function tha documentation = "https://docs.rs/sp-version" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -18,7 +21,7 @@ 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.193", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } thiserror = { version = "1.0.48", optional = true } sp-core-hashing-proc-macro = { path = "../core/hashing/proc-macro" } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/version/proc-macro/Cargo.toml b/substrate/primitives/version/proc-macro/Cargo.toml index 715316b842dcfe98c8864d2ba3f63b6005ee498a..adf70dbd166192c7c318f32920a15fa5da16f398 100644 --- a/substrate/primitives/version/proc-macro/Cargo.toml +++ b/substrate/primitives/version/proc-macro/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Macro for defining a runtime version." documentation = "https://docs.rs/sp-api-proc-macro" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -19,7 +22,7 @@ proc-macro = true codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.39", features = ["extra-traits", "fold", "full", "visit"] } +syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } [dev-dependencies] sp-version = { path = ".." } diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index e997f558c9d7d0855074aa3db07a2636d80c2b8c..9fe5cc1f2d00ba48fc609521194534202e8d046b 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -10,6 +10,9 @@ description = "Types and traits for interfacing between the host and the wasm ru documentation = "https://docs.rs/sp-wasm-interface" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index a573495ff91df9aeb26d15142d4e3e58e83085e8..d89182b6642cec040089f0701d68ab948b6f6a05 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -9,27 +9,31 @@ repository.workspace = true description = "Types and traits for interfacing between the host and the wasm runtime." documentation = "https://docs.rs/sp-wasm-interface" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] +bounded-collections = { version = "0.1.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"] } -serde = { version = "1.0.193", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } smallvec = "1.11.0" sp-arithmetic = { path = "../arithmetic", default-features = false } -sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-std = { path = "../std", default-features = false } +schemars = { version = "0.8.3", default-features = false, optional = true } [features] default = ["std"] std = [ + "bounded-collections/std", "codec/std", "scale-info/std", "serde/std", "sp-arithmetic/std", - "sp-core/std", "sp-debug-derive/std", "sp-std/std", ] @@ -39,8 +43,12 @@ full-metadata-docs = ["scale-info/docs"] # Serde support without relying on std features. serde = [ + "bounded-collections/serde", "dep:serde", "scale-info/serde", "sp-arithmetic/serde", - "sp-core/serde", +] + +json-schema = [ + "dep:schemars", ] diff --git a/substrate/primitives/weights/src/lib.rs b/substrate/primitives/weights/src/lib.rs index ececb622fa0f55d401498a408ff8bb7195efa37d..ef431bddee265fab663efd1e5b039a1c3e78bdc4 100644 --- a/substrate/primitives/weights/src/lib.rs +++ b/substrate/primitives/weights/src/lib.rs @@ -27,6 +27,7 @@ extern crate self as sp_weights; mod weight_meter; mod weight_v2; +use bounded_collections::Get; use codec::{CompactAs, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "serde")] @@ -36,7 +37,6 @@ use sp_arithmetic::{ traits::{BaseArithmetic, SaturatedConversion, Unsigned}, Perbill, }; -use sp_core::Get; use sp_debug_derive::RuntimeDebug; pub use weight_meter::*; @@ -270,7 +270,7 @@ pub type NoFee = FixedFee<0, T>; /// # Example /// /// ``` -/// # use sp_core::ConstU128; +/// # use bounded_collections::ConstU128; /// # use sp_weights::ConstantMultiplier; /// // Results in a multiplier of 10 for each unit of weight (or length) /// type LengthToFee = ConstantMultiplier::>; @@ -360,7 +360,7 @@ mod tests { #[test] fn constant_fee_works() { - use sp_core::ConstU128; + use bounded_collections::ConstU128; assert_eq!( ConstantMultiplier::>::weight_to_fee(&Weight::zero()), 0 diff --git a/substrate/primitives/weights/src/weight_v2.rs b/substrate/primitives/weights/src/weight_v2.rs index d692aaff8f5ab98d7cd3e701f09b85e3c9465d9c..3c10929f433b6fd23a88eb9138df54f8b67aba8d 100644 --- a/substrate/primitives/weights/src/weight_v2.rs +++ b/substrate/primitives/weights/src/weight_v2.rs @@ -23,6 +23,7 @@ use super::*; #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Eq, PartialEq, Copy, Clone, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))] pub struct Weight { #[codec(compact)] /// The weight of computational time used based on some reference hardware. diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index 59c53e952b9598c566b56a0f951ca43071018c92..32210de73e64770f1dff452405a2a9014fe1b967 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -7,11 +7,14 @@ license = "GPL-3.0 WITH Classpath-exception-2.0" homepage = "https://substrate.io" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } flate2 = "1.0" fs_extra = "1.3" glob = "0.3" diff --git a/substrate/test-utils/Cargo.toml b/substrate/test-utils/Cargo.toml index 17696e8229c0c264ff5ed93689f726eb6a7238bd..526ed7c049c708c257cb22fc44efb1dc7d6fe43b 100644 --- a/substrate/test-utils/Cargo.toml +++ b/substrate/test-utils/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Substrate test utilities" publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/test-utils/cli/Cargo.toml b/substrate/test-utils/cli/Cargo.toml index 4f20e9e2ce515fe8afc626981d1f3f09f0651ec5..d654a3aaa7258668c066657b27e3eb97deec6b19 100644 --- a/substrate/test-utils/cli/Cargo.toml +++ b/substrate/test-utils/cli/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index 9829ae531fe28b047cbc49f68e07562d44df8bc9..79071e19e2816292d31f8dd8da54e54c4444f667 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -9,16 +9,19 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" -async-trait = "0.1.57" +async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -serde = "1.0.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" sc-client-api = { path = "../../client/api" } sc-client-db = { path = "../../client/db", default-features = false, features = [ "test-helpers", diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 655c7f0fec1a742a4f45694fffe14050183144b0..1eb50771a2ce99944a400d2835ab12058affffd9 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -58,8 +61,8 @@ sp-consensus = { path = "../../primitives/consensus/common" } substrate-test-runtime-client = { path = "client" } sp-tracing = { path = "../../primitives/tracing" } json-patch = { version = "1.0.0", default-features = false } -serde = { version = "1.0.193", features = ["alloc", "derive"], default-features = false } -serde_json = { version = "1.0.108", default-features = false, features = ["alloc"] } +serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } +serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } [build-dependencies] substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true } diff --git a/substrate/test-utils/runtime/client/Cargo.toml b/substrate/test-utils/runtime/client/Cargo.toml index 40cfa8ab1b7092ab2263690fba9a314e0dcd7a24..cbb964f6785237ff45b58f6b702b9a3e4a3abbbe 100644 --- a/substrate/test-utils/runtime/client/Cargo.toml +++ b/substrate/test-utils/runtime/client/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 1a4e9fd04667c830ad912316bf7fe6fcaae11c41..16ab467772f2f60a4b6496da88b9129cc04851f5 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -27,7 +27,7 @@ pub mod substrate_test_pallet; use codec::{Decode, Encode}; use frame_support::{ - construct_runtime, + construct_runtime, derive_impl, dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, @@ -342,6 +342,7 @@ parameter_types! { .build_or_panic(); } +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::pallet::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index cb6ee6d79f448da2c161073f1c08fc6134047537..b52a897438b6854a066a95e51ba49bd0eddd7f84 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true publish = false +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index f36e4f4e0b6daae6da82db958cf2f63eb7edd84b..441f89b790f1b3bde9db9a67877f978c60a824f2 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true description = "A no-std/Substrate compatible library to construct binary merkle tree." homepage = "https://substrate.io" +[lints] +workspace = true + [dependencies] array-bytes = { version = "6.1", optional = true } log = { version = "0.4", default-features = false, optional = true } diff --git a/substrate/utils/build-script-utils/Cargo.toml b/substrate/utils/build-script-utils/Cargo.toml index ab15d5552c29a6a17dac404537a9cfc3ee59fdfe..464647ea723e0398937a0dc49f0f00f749786e41 100644 --- a/substrate/utils/build-script-utils/Cargo.toml +++ b/substrate/utils/build-script-utils/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Crate with utility functions for `build.rs` scripts." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/build-script-utils/src/git.rs b/substrate/utils/build-script-utils/src/git.rs index 057ee0af15f7623ba7e9fddbd799dc75f6605e1a..430a3e17c190a9042af6356076047163403823fa 100644 --- a/substrate/utils/build-script-utils/src/git.rs +++ b/substrate/utils/build-script-utils/src/git.rs @@ -15,7 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{env, fs, fs::File, io, io::Read, path::PathBuf}; +use std::{ + env, fs, + fs::File, + io, + io::Read, + path::{Path, PathBuf}, +}; /// Make sure the calling `build.rs` script is rerun when `.git/HEAD` or the ref of `.git/HEAD` /// changed. @@ -55,7 +61,7 @@ pub fn rerun_if_git_head_changed() { } // Code taken from https://github.com/rustyhorde/vergen/blob/8d522db8c8e16e26c0fc9ea8e6b0247cbf5cca84/src/output/envvar.rs -fn get_git_paths(path: &PathBuf) -> Result>, io::Error> { +fn get_git_paths(path: &Path) -> Result>, io::Error> { let git_dir_or_file = path.join(".git"); if let Ok(metadata) = fs::metadata(&git_dir_or_file) { diff --git a/substrate/utils/build-script-utils/src/version.rs b/substrate/utils/build-script-utils/src/version.rs index 549e499b11022304a06a5510ed7aaea809f4b488..d85c78d2c9974ef35483d7c9726e5f16ce2f0b45 100644 --- a/substrate/utils/build-script-utils/src/version.rs +++ b/substrate/utils/build-script-utils/src/version.rs @@ -25,7 +25,7 @@ pub fn generate_cargo_keys() { // We deliberately set the length here to `11` to ensure that // the emitted hash is always of the same length; otherwise // it can (and will!) vary between different build environments. - match Command::new("git").args(&["rev-parse", "--short=11", "HEAD"]).output() { + match Command::new("git").args(["rev-parse", "--short=11", "HEAD"]).output() { Ok(o) if o.status.success() => { let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned(); Cow::from(sha) diff --git a/substrate/utils/fork-tree/Cargo.toml b/substrate/utils/fork-tree/Cargo.toml index eea500641fe4eaf7631f97e1adcf2cefcda87288..27bb908986f8e35e9caafc9448167ba68c41ec51 100644 --- a/substrate/utils/fork-tree/Cargo.toml +++ b/substrate/utils/fork-tree/Cargo.toml @@ -10,6 +10,9 @@ description = "Utility library for managing tree-like ordered data with logic fo documentation = "https://docs.rs/fork-tree" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index b6eca132067fb06b13298f2270e547e9fc0b5d0c..02cb6ebc303063dd6a903fc820b54f6fe222736a 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -9,13 +9,16 @@ repository.workspace = true description = "CLI for benchmarking FRAME" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4" -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } comfy-table = { version = "7.0.1", default-features = false } handlebars = "4.2.2" @@ -24,10 +27,10 @@ itertools = "0.10.3" lazy_static = "1.4.0" linked-hash-map = "0.5.4" log = "0.4.17" -rand = { version = "0.8.4", features = ["small_rng"] } +rand = { version = "0.8.5", features = ["small_rng"] } rand_pcg = "0.3.1" -serde = "1.0.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" thiserror = "1.0.48" thousands = "0.2.0" frame-benchmarking = { path = "../../../frame/benchmarking" } diff --git a/substrate/utils/frame/frame-utilities-cli/Cargo.toml b/substrate/utils/frame/frame-utilities-cli/Cargo.toml index 6e33ed88e0a7e5265005e65620603316003338a4..96c43aee070b9b895f827446f5ac8a8658edbd6c 100644 --- a/substrate/utils/frame/frame-utilities-cli/Cargo.toml +++ b/substrate/utils/frame/frame-utilities-cli/Cargo.toml @@ -10,8 +10,11 @@ description = "cli interface for FRAME" documentation = "https://docs.rs/substrate-frame-cli" readme = "README.md" +[lints] +workspace = true + [dependencies] -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", 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/Cargo.toml b/substrate/utils/frame/generate-bags/Cargo.toml index ac22197c5ac4b3fe25de85eb704572420595cb91..4afb2a80b7710beae446c5f95d6f4e8dc727ff9b 100644 --- a/substrate/utils/frame/generate-bags/Cargo.toml +++ b/substrate/utils/frame/generate-bags/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Bag threshold generation script for pallet-bag-list" +[lints] +workspace = true + [dependencies] # FRAME frame-support = { path = "../../../frame/support" } @@ -17,5 +20,5 @@ pallet-staking = { path = "../../../frame/staking" } sp-staking = { path = "../../../primitives/staking" } # third party -chrono = { version = "0.4.27" } +chrono = { version = "0.4.31" } num-format = "0.4.3" diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index a2ee3883786bd264ef84ff95a1842aaa0c4a4bbc..07e74a59a6b0ae84cdcf5efdad29148994cf7118 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -9,9 +9,12 @@ repository.workspace = true description = "Bag threshold generation script for pallet-bag-list and kitchensink-runtime." publish = false +[lints] +workspace = true + [dependencies] kitchensink-runtime = { path = "../../../../bin/node/runtime" } generate-bags = { path = ".." } # third-party -clap = { version = "4.4.10", features = ["derive"] } +clap = { version = "4.4.13", features = ["derive"] } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index 88071f7d634d17bfee66bf5f61e98acdd19077a0..ba0e8e869ccc27e7e12809ac4ac52cfb686f3713 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "An externalities provided environment that can load itself from remote nodes or cached files" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -15,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.16.2", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } log = "0.4.17" -serde = "1.0.193" +serde = "1.0.195" sp-core = { path = "../../../primitives/core" } sp-state-machine = { path = "../../../primitives/state-machine" } sp-io = { path = "../../../primitives/io" } diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index d0f323c096ff965daec69432e0d0278539f22bda..1e8a298726eb8d710f9b78c02628e3da4a0bcb5f 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -8,13 +8,16 @@ homepage = "https://substrate.io" repository.workspace = true description = "Shared JSON-RPC client" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.16.2", features = ["ws-client"] } sc-rpc-api = { path = "../../../../client/rpc-api" } -async-trait = "0.1.57" +async-trait = "0.1.74" serde = "1" sp-runtime = { path = "../../../../primitives/runtime" } log = "0.4" 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 6d3cb545efb6ef135cd52fdae6da7b861611def3..6cd99e5a6fedc3e18464e9d3c6da621a223c0fe2 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "Node-specific RPC methods for interaction with state trie migration." readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -29,4 +32,4 @@ sc-rpc-api = { path = "../../../../client/rpc-api" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.108" +serde_json = "1.0.111" diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index da56297c82fbc43051849b707a3a5056b1eaf09a..1cc6d8e98b365e5fcfd8bc6004b4d13b8c1a0ae4 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Substrate RPC for FRAME's support" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/frame/rpc/support/src/lib.rs b/substrate/utils/frame/rpc/support/src/lib.rs index b91ae436127be62eb58e6c5171c6d9aa7fa9290b..bb5098293c2859a444b108086a79ef829869428f 100644 --- a/substrate/utils/frame/rpc/support/src/lib.rs +++ b/substrate/utils/frame/rpc/support/src/lib.rs @@ -63,6 +63,7 @@ use sp_storage::{StorageData, StorageKey}; /// # type Lookup = IdentityLookup; /// # type Block = frame_system::mocking::MockBlock; /// # type RuntimeEvent = RuntimeEvent; +/// # type RuntimeTask = RuntimeTask; /// # type BlockHashCount = (); /// # type DbWeight = (); /// # type Version = (); diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 636f2cd0485d43b456cdafc9e7da01e48953eb99..84c3265c93d36e90fda0e6bea0cb261456fe6676 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -9,6 +9,9 @@ repository.workspace = true description = "FRAME's system exposed over Substrate RPC" readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 31e95a8be417bea319018fe610f7b403a52c5f34..4f09a34fae7aaf7ff4709e8ad14f1c1440430b97 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -8,6 +8,9 @@ homepage = "https://substrate.io" repository.workspace = true description = "Cli command runtime testing and dry-running" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -34,13 +37,13 @@ sp-weights = { path = "../../../../primitives/weights" } frame-try-runtime = { path = "../../../../frame/try-runtime", optional = true } substrate-rpc-client = { path = "../../rpc/client" } -async-trait = "0.1.57" -clap = { version = "4.4.10", features = ["derive"] } +async-trait = "0.1.74" +clap = { version = "4.4.13", features = ["derive"] } hex = { version = "0.4.3", default-features = false } log = "0.4.17" parity-scale-codec = "3.6.1" -serde = "1.0.193" -serde_json = "1.0.108" +serde = "1.0.195" +serde_json = "1.0.111" 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 bf999a66111f9456ff12b02613a9eeb3af2f197e..252998d94bd1cb79736bbee907b61700b4ec2724 100644 --- a/substrate/utils/prometheus/Cargo.toml +++ b/substrate/utils/prometheus/Cargo.toml @@ -9,6 +9,9 @@ homepage = "https://substrate.io" repository.workspace = true readme = "README.md" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index fc3f6450fd7a4c0549aed17252a5c2364513b878..2154cfca177c9ea07fc642d2cfc967eeeb505a85 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true license = "Apache-2.0" homepage = "https://substrate.io" +[lints] +workspace = true + [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -17,7 +20,7 @@ build-helper = "0.1.1" cargo_metadata = "0.15.4" strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" -toml = "0.7.3" +toml = "0.8.2" walkdir = "2.3.2" sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } filetime = "0.2.16" diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 5bf44c2c9b20e5fa5f304c4f2b02c9c2bc671c35..2126a49bd7ffeac68fb8dbbb5fdb8810abff820e 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -935,7 +935,7 @@ fn generate_rerun_if_changed_instructions( while let Some(dependency) = dependencies.pop() { // Ignore all dev dependencies if dependency.kind == DependencyKind::Development { - continue; + continue } let path_or_git_dep =